commit 70db9d566e395f2ba3d12753398b6b1ddb8befd0 Author: archive Date: Fri Jun 15 00:00:00 2007 +0000 as released 2007-06-15 diff --git a/Animation Examples/animations.zip b/Animation Examples/animations.zip new file mode 100755 index 0000000..1b7383c Binary files /dev/null and b/Animation Examples/animations.zip differ diff --git a/Art Examples/Quake 4 Model Reference.zip b/Art Examples/Quake 4 Model Reference.zip new file mode 100755 index 0000000..61f2bd6 Binary files /dev/null and b/Art Examples/Quake 4 Model Reference.zip differ diff --git a/Art Examples/Quake 4 Modeling Parts Junkyard.zip b/Art Examples/Quake 4 Modeling Parts Junkyard.zip new file mode 100755 index 0000000..360b2ac Binary files /dev/null and b/Art Examples/Quake 4 Modeling Parts Junkyard.zip differ diff --git a/Art Examples/Quake 4 Texture Reference.zip b/Art Examples/Quake 4 Texture Reference.zip new file mode 100755 index 0000000..5eb7144 Binary files /dev/null and b/Art Examples/Quake 4 Texture Reference.zip differ diff --git a/Definition File Examples/monster_slimy_lobber.def b/Definition File Examples/monster_slimy_lobber.def new file mode 100755 index 0000000..393061a --- /dev/null +++ b/Definition File Examples/monster_slimy_lobber.def @@ -0,0 +1,111 @@ +//------------------------------------------------ +// SDK Example Content +// Slimy Lobber +// +// Compare the entries in this def file to those of the monster_slimy_transfer +// (def/ai/monster_slimy_transfer.def) as an example of how to implement new +// monsters and monster attacks using only existing AI code. +// +// aweldon Nov 05 +//------------------------------------------------ + + + +model model_monster_slimy_lobber +{ + inherit model_monster_slimy_transfer // Use Slimy Transfer mesh and anims as base. + + // Lobbing anim ----------------------------------------------------------------------------------------- + anim lob models/monsters/slimy_transfer/attack01.md5anim // Use existing melee anim + { + frame 1,13,27,36,57 sound snd_flesh // Use pre-existing sounds + frame 19 ai_attack melee r_wrist // Throw at appropriate frame + frame 19 fx fx_lob r_wrist + } +} + +entityDef damage_lobber_grenade +{ + // These values are copied from the monster_gunner .def file and seem to work ok. + "inherit" "damage_monster_base" + "knockback" "0" + "damage" "60" + "push" "10000" + + "tv_scale" "0.7" + "tv_time" "4200" +} + +entityDef damage_lobber_grenade_splash +{ + // These values are copied from the monster_gunner .def file and seem to work ok. + "inherit" "damage_monster_base" + "knockback" "0" + "damage" "80" + "radius" "200" + "push" "10000" + + "tv_scale" "0.7" + "tv_time" "4200" +} + + +entityDef projectile_lobber_bit +{ + "inherit" "projectile_gunner_grenade" // inherit defaults from the Gunner grenade + + "fuse" "3" //shorten the fuse + + "detonate_on_actor" "1" // Explode when hitting directly + + "model" "models/gibs/head_pork.lwo" // New 'grenade' model (brraaaaaaainnnnns...) + "fx_fly" "effects/impact/impact_flesh.fx" // When flying, play the impact effect. Admittedly not the best effect for this purpose. + "fx_detonate" "effects/monsters/gib.fx" // Use the splatty gib effect for 'explosions' + "fx_impact" "effects/monsters/gib.fx" // Use the splatty gib effect for 'explosions' + + "def_splash_damage" "damage_lobber_grenade_splash" // Use damage def specified above + "def_damage" "damage_lobber_grenade" // Use damage def specified above + + "speed" "500" // Adjust speed + "angular_velocity" "0 -900 200" // Give a nudge up and towards center. + + "bounce" "15" // Make with the bouncing + + "contact_friction" "1" // Reduced friction for better bounce behavior + + "delay_splash" "0" // Splash damage effects are instantaneous + + "snd_ricochet" "failedtransfer_flesh" // Fleshy sound on bounce +} + +// New EntityDef +entityDef monster_slimy_lobber +{ + "inherit" "monster_slimy_transfer" // Inherit from the Slimy Transfer + + // --------------------------------- Actions ------------------------------------ + "action_meleeAttack" "1" // Leave this enabled, we'll replace it with our lobbing attack + "action_meleeAttack_anim" "lob" // 'New' anim on melee attack + + "action_meleeAttack_minrange" "80" // Minimum distance (in units) for attack. + "action_meleeAttack_maxrange" "512" // Maximum distance (in units) for attack. + "action_meleeAttack_blendFrames" "6" // Blend frames on attack + "action_meleeAttack_rate" ".5" // Rate of attack + + "action_vomitAttack" "1" // Keep the vomit attack enabled + "action_vomitAttack_minrange" "0" // Minimum distance (in units) for attack. + "action_vomitAttack_maxrange" "128" // Maximum distance (in units) for attack. + + + // --------------------------------- Body ------------------------------------ + "model" "model_monster_slimy_lobber" // Use our new ModelDef from above + + + // --------------------------------- Combat ---------------------------------- + "health" "75" // Beef these guys up a bit + "painThreshold" "40" // Can take more damage before playing pain anims + + "def_attack_melee" "projectile_lobber_bit" // Change melee attack to our new projectile + + "fx_lob" "effects/impact/impact_flesh.fx" // Effect to play when lobbing (added on frame commands above) +} diff --git a/Definition File Examples/monster_strogg_marine_rocket.def b/Definition File Examples/monster_strogg_marine_rocket.def new file mode 100755 index 0000000..fa70170 --- /dev/null +++ b/Definition File Examples/monster_strogg_marine_rocket.def @@ -0,0 +1,84 @@ +//------------------------------------------------ +// SDK Example Content +// Rocket Launcher Strogg Marine +// +// Compare the entries in this def file to those of the monster_strogg_marine_sgun +// (monster_strogg_marine.def) as an example of how to implement new monsters +// and monster attacks using only existing AI code. +// +// aweldon Nov 05 +//------------------------------------------------ + +entityDef projectile_strogg_marine_rocket +{ + // We'll just steal the standard single player rocket. + "inherit" "projectile_rocket" + + // ...but that does a little too much damage! + "def_damage" "damage_strogg_marine_rocket" + "def_splash_damage" "damage_strogg_marine_rocket_splash" +} + +entityDef damage_strogg_marine_rocket +{ + // Inherit the standard single player rocket damage + "inherit" "damage_rocketDirect" + + // Lower damage from 150 to something more reasonable. + "damage" "65" +} + +entityDef damage_strogg_marine_rocket_splash +{ + // Inherit the standard single player rocket splash damage + + // Lower damage from 150 to something more reasonable. + "damage" "45" + + // This might also get a little messy... + "gib" "1" +} + +entityDef monster_strogg_marine_rocket +{ + "inherit" "monster_strogg_marine_sgun" // Use the shotgun guy as a base + "editor_usage" "Rocket Strogg Marine" // What will display in the editor + "editor_ignore" "0" // Show the entity in the right click menu + "model" "monster_strogg_marine_sgun" // Use the mesh and anims from the shotgun guy + "skin" "skins/monsters/strogg_marine/smv_shotgun" // Leave the skin as the shotgun guy + + // --------------------------------- Defs ---------------------------------- + "minShots" "1" // Will fire at least once. + "maxShots" "3" // ...and up to 3 times in sequence. + // These refer only to the number of times the fire animation will play, and not how many physical shots are fired + // That can be modified by including multiple shots on frame commands or by upping the count per attack. + + "actionTimer_rangedAttack_rate" ".5" // Was .25. Time between attacks + + "action_rangedAttack" "1" + "action_rangedAttack_anim" "shotgun_range_attack" // Anim to play when attacking + "action_rangedAttack_minRange" "128" // Minimum attack range in units. Was 0 on shotgun. + "action_rangedAttack_maxRange" "640" // Maximum attack range in units. Was 400 on shotgun. + "action_rangedAttack_failrate" "0" // Attack will never fail. + + "action_rollAttack_rate" "2" // Was .25. Time between attacks. + + "action_strafe" "0" // Disable Strafing + + "def_attack_base" "projectile_strogg_marine_rocket" // Use our new projectile for base attacks. + "attack_base_count" "1" // One projectile + "attack_base_spread" "0" // no spread + "attack_base_hitscan" "0" // Does not hit instantly + "attack_base_accuracy" "1" // increased accuracy + + "def_attack_jointDir" "projectile_strogg_marine_rocket" // Use our new projectile for joint attacks. + "attack_jointDir_locktojoint" "1" // Lock to joint direction + "attack_jointDir_count" "1" // One projectile + "attack_jointDir_spread" "0" // no spread + "attack_jointDir_hitscan" "0" // does not hit instantly + "attack_jointDir_accuracy" "0" // no change + + // --------------------------------- Effects --------------------------------- + "fx_blaster_muzzleflash" "effects\weapons\rocket\muzzleflash_world.fx" // Rocket launcher muzzle flash + "snd_weapon_fire" "weapon_rocket_fire" // Rocket launcher sound shader +} \ No newline at end of file diff --git a/Font Examples/marine_ansi.zip b/Font Examples/marine_ansi.zip new file mode 100755 index 0000000..c7912e8 Binary files /dev/null and b/Font Examples/marine_ansi.zip differ diff --git a/Font Examples/marine_symbol.zip b/Font Examples/marine_symbol.zip new file mode 100755 index 0000000..c296006 Binary files /dev/null and b/Font Examples/marine_symbol.zip differ diff --git a/Font Examples/q4font.zip b/Font Examples/q4font.zip new file mode 100755 index 0000000..0f42cee Binary files /dev/null and b/Font Examples/q4font.zip differ diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..c0b566a --- /dev/null +++ b/License.txt @@ -0,0 +1,55 @@ +QUAKE 4 (TM) SOFTWARE DEVELOPMENT KIT +LIMITED USE LICENSE AGREEMENT + +This QUAKE 4 (TM) Software Development Kit Limited Use License Agreement (this "Agreement") is a legal agreement among you, the end-user, and Id Software, Inc. ("Id Software"). BY CONTINUING THE DOWNLOAD OR INSTALLATION OF THIS SOFTWARE DEVELOPMENT KIT (THE "SOFTWARE") FOR THE GAME PROGRAM ENTITLED QUAKE 4 (TM), BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM, OR OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU ACKNOWLEDGE AND UNDERSTAND THAT IN ORDER TO OPERATE THE SOFTWARE, YOU MUST HAVE THE FULL VERSION OF THE ID SOFTWARE GAME ENTITLED QUAKE 4 (TM) INSTALLED ON YOUR COMPUTER. + +1. Grant of License. Subject to the terms and provisions of this Agreement and so long as you fully comply at all times with this Agreement, Id Software grants to you the non-exclusive and limited right to use the Software only in executable or object code form. The term "Software" includes all elements of the Software, including, without limitation, data files and screen displays. You are not receiving any ownership or proprietary right, title, or interest in or to the Software or the copyrights, trademarks, or other rights related thereto. For purposes of the first sentence of this Section, "use" means loading the Software into RAM and/or onto computer hard drive, as well as installation of the Software on a hard disk or other storage device, and means the uses permitted in Sections 2 and 5 hereinbelow. You agree that the Software will not be downloaded, shipped, transferred, exported or re-exported into any country in violation of the United States Export Administration Act (or any other law governing such matters) by you or anyone at your direction, and that you will not utilize and will not authorize anyone to utilize the Software in any other manner in violation of any applicable law. The Software shall not be downloaded or otherwise exported or re-exported into (or to a national or resident of) any country to which the United States has embargoed goods, or to anyone or into any country who/that are prohibited, by applicable law, from receiving such property. In exercising your limited rights hereunder, you shall comply, at all times, with all applicable laws, regulations, ordinances, and statutes. Id Software reserves all rights not granted in this Agreement, including, without limitation, all rights to Id Software's trademarks. + +2. Permitted New Creations. Subject to the terms and provisions of this Agreement and so long as you fully comply at all times with this Agreement, Id Software grants to you the non-exclusive and limited right to use the Software to create for the software game QUAKE 4 (TM) your own modifications (the "New Creations") that shall operate only with QUAKE 4 (TM) (but not any demo, test, or other version of QUAKE 4 (TM)). You may include within the New Creations certain textures and other images (the "Software Images") from the Software. You shall not create any New Creations that infringe against any third-party right or that are libelous, defamatory, obscene, false, misleading, or otherwise illegal or unlawful. You agree that the New Creations will not be downloaded, shipped, transferred, exported, or re-exported into any country in violation of the United States Export Administration Act (or any other law governing such matters) by you or anyone at your direction, and that you will not utilize and will not authorize anyone to utilize the New Creations in any other manner in violation of any applicable law. The New Creations shall not be downloaded or otherwise exported or re-exported into (or to a national or resident of) any country to which the United States has embargoed goods or to anyone or into any country who/that are prohibited, by applicable law, from receiving such property. You shall not rent, sell, lease, lend, offer on a pay-per-play basis, or otherwise commercially exploit or commercially distribute the New Creations. You are permitted to distribute, without any cost or charge, the New Creations only to other end-users so long as such distribution is not infringing against any third-party right and otherwise is not illegal or unlawful. As noted below, in the event you commit any breach of this Agreement, your license and this Agreement automatically shall terminate, without notice. + +3. Prohibitions with Regard to the Software. You, whether directly or indirectly, shall not do any of the following acts: + +a. rent the Software; + +b. sell the Software; + +c. lease or lend the Software; + +d. offer the Software on a pay-per-play basis; + +e. distribute the Software (except as permitted under Section 5 hereinbelow); + +f. in any other manner and through any medium whatsoever commercially exploit the Software or use the Software for any commercial purpose; + +g. disassemble, reverse engineer, decompile, modify (except as permitted under Section 2 hereinabove) or alter the Software; + +h. translate the Software; + +i. reproduce or copy the Software (except as permitted under Section 5 hereinbelow); + +j. publicly display the Software; + +k. prepare or develop derivative works based upon the Software; + +l. remove or alter any notices or other markings or legends, such as trademark or copyright notices, affixed on or within the Software; or + +m. remove, alter, modify, disable, or reduce any of the anti-piracy measures contained in the Software or in QUAKE 4 (TM), including, without limitation, measures relating to multiplayer play. + +4. Prohibition against Cheat Programs. Any attempt by you, either directly or indirectly, to circumvent or bypass any element of the Software to gain any advantage in multiplayer play of the Software is a material breach of this Agreement. It is a material breach of this Agreement for you, whether directly or indirectly, to create, develop, copy, reproduce, distribute, or otherwise make any use of any software program or any modification to the Software ("Cheat Program") itself that enables or allows the user thereof to obtain an advantage or otherwise exploit another Software player or user when playing the Software against other players or users on a local area network, any other network, or on the Internet. Hacking into the executable of the Software, modification of the Software, or any other use of the Software in connection with the creation, development, or use of any such unauthorized Cheat Program is a material breach of this Agreement. Cheat Programs include, but are not limited to, programs that allow Software players or users to see through walls or other level geometry; programs that allow Software players or users to change their rate of speed outside the allowable limits of the Software; programs that crash either and/or other Software players, users, PC clients, or network servers; programs that automatically target other Software players or users (commonly referred to as "aimbots") that automatically simulate Software player or user input for the purpose of gaining an advantage over other Software players or users; or any other program or modification that functions in a similar capacity or allows any prohibited conduct. + +In the event you breach this Section or otherwise breach this Agreement, your license and this Agreement automatically shall terminate, without notice, and you shall have no right to play the Software against other players or make any other use of the Software. + +5. Permitted Distribution and Copying. So long as this Agreement accompanies each copy you make of the Software, and so long as you comply fully at all times with this Agreement, Id Software grants to you the non-exclusive and limited right to copy the Software and to distribute such copies of the Software free of charge for non-commercial purposes that shall include the free-of-charge distribution of copies of the Software as mounted on the covers of magazines; provided, however, you shall not copy or distribute the Software in any infringing manner or in any manner that violates any law or third-party right, and you shall not distribute the Software together with any material that infringes against any third-party right or that is libelous, defamatory, obscene, false, misleading, or otherwise illegal or unlawful. Subject to the terms and conditions of this Agreement, you also may: (i) download one (1) copy of the Software or copy the Software from the CD ROM on which you received your copy of the Software onto your computer RAM; (ii) copy the Software from your computer RAM onto your computer hard drive; and (iii) make one (1) "backup" or archival copy of the Software on one (1) hard disk. In exercising your limited rights hereunder, you shall comply at all times with all applicable laws, regulations, ordinances, and statutes. Id Software reserves all rights not granted in this Agreement. You shall not distribute the Software commercially unless you first enter into a separate contract with Id Software, on terms and conditions determined in Id Software's sole discretion, and only upon your receipt of a written agreement executed by an authorized officer of Id Software. + +6. Intellectual Property Rights. The Software and all copyrights, trademarks, and all other conceivable intellectual property rights related to the Software are owned by Id Software and are protected by United States copyright laws, international treaty provisions, and all applicable law, such as the Lanham Act. You must treat the Software like any other copyrighted material, as required by 17 U.S.C. § 101 et seq. and other applicable law. You agree to use your best efforts to see that any user of the Software licensed hereunder or the New Creations complies with this Agreement. You agree that you are receiving a copy of the Software by limited license only and not by sale and that the "first sale" doctrine of 17 U.S.C. § 109 does not apply to your receipt or use of the Software. This Section shall survive the cancellation or termination of this Agreement. + +7. NO ID SOFTWARE WARRANTIES. ID SOFTWARE DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ANY WARRANTY OF NON-INFRINGEMENT, WITH RESPECT TO THE SOFTWARE, THE SOFTWARE IMAGES, AND OTHERWISE. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY. ID SOFTWARE DOES NOT WARRANT THAT THE SOFTWARE OR THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC OR SPECIAL REQUIREMENTS. ADDITIONAL STATEMENTS, WHETHER ORAL OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID SOFTWARE AND SHOULD NOT BE RELIED UPON. This Section shall survive the cancellation or termination of this Agreement. + +8. Governing Law, Venue, Indemnity, and Liability Limitation. This Agreement shall be construed in accordance with and governed by the applicable laws of the State of Texas (but excluding conflicts of laws principles) and applicable United States federal law. Except as set forth below, exclusive venue for all litigation regarding this Agreement shall be in Dallas County, Texas, and you agree to submit to the jurisdiction of the federal and state courts in Dallas County, Texas, for any such litigation. You hereby agree to indemnify, defend and hold harmless Id Software and Id Software's officers, employees, directors, agents, licensees (excluding you), sub-licensees (excluding you), successors, and assigns from and against all losses, lawsuits, damages, causes of action, and claims relating to and/or arising from the New Creations or the distribution or other use of the New Creations or relating to and/or arising from your breach of this Agreement. You agree that your unauthorized use of the Software Images or the Software, or any part thereof, immediately and irreparably may damage Id Software such that Id Software could not be adequately compensated solely by a monetary award, and in such event, at Id Software's option, that Id Software shall be entitled to an injunctive order, in addition to all other available remedies, including a monetary award, to prohibit such unauthorized use without the necessity of Id Software posting bond or other security. IN ANY CASE, ID SOFTWARE AND ID SOFTWARE'S OFFICERS, EMPLOYEES, DIRECTORS, SHAREHOLDERS, REPRESENTATIVES, AGENTS, LICENSEES (EXCLUDING YOU), SUB-LICENSEES (EXCLUDING YOU), SUCCESSORS, AND ASSIGNS SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS, SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT OR PUNITIVE DAMAGES, OR ANY OTHER DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL THEORY EVEN IF ID SOFTWARE OR ITS RESPECTIVE AGENT(S) HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY SUCH DAMAGES, OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you. This Section shall survive the cancellation or termination of this Agreement. + +9. United States Government Restricted Rights. To the extent applicable, the United States Government shall have only those rights to use the Software as expressly stated and expressly limited and restricted in this Agreement, as provided in 48 C.F.R. §§ 227.7201 through 227.7204, inclusive. + +10. General Provisions. Neither this Agreement nor any part or portion hereof shall be assigned or sublicensed by you. Id Software may assign its rights under this Agreement in its sole discretion. Should any provision of this Agreement be held to be void, invalid, unenforceable, or illegal by a court of competent jurisdiction, the validity and enforceability of the other provisions shall not be affected thereby. If any provision is determined to be unenforceable by a court of competent jurisdiction, you agree to a modification of such provision to provide for enforcement of the provision's intent, to the extent permitted by applicable law. Failure of Id Software to enforce any provision of this Agreement shall not constitute or be construed as a waiver of such provision or of the right to enforce such provision. IMMEDIATELY UPON YOUR FAILURE TO COMPLY WITH, OR YOUR BREACH OF ANY TERM OR PROVISION OF THIS AGREEMENT, YOUR LICENSE GRANTED HEREIN AND THIS AGREEMENT AUTOMATICALLY SHALL TERMINATE, WITHOUT NOTICE, AND ID SOFTWARE MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU THAT ARE AVAILABLE UNDER APPLICABLE LAW AND/OR THIS AGREEMENT. Immediately upon termination of this Agreement, any and all rights you are granted hereunder shall terminate, you shall have no right to use the Software or the New Creations, in any manner, you immediately shall destroy all copies of the Software and the New Creations in your possession, custody, or control, and all rights granted hereunder shall revert, without notice, to and be vested in Id Software. + +YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS AGREEMENT, AND YOU UNDERSTAND THAT, BY CONTINUING THE DOWNLOAD OR INSTALLATION OF THE SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM, OR OTHER STORAGE, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN, SEPARATE AGREEMENTS, IF ANY, BETWEEN ID AND YOU, THIS AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND LIABILITIES OF THE PARTIES HERETO RELATING TO THE SUBJECT MATTER HEREOF. THIS AGREEMENT SUPERSEDES ALL PRIOR ORAL AGREEMENTS, PROPOSALS, OR UNDERSTANDINGS, AND ANY OTHER COMMUNICATIONS, IF ANY, BETWEEN ID AND YOU RELATING TO THE SUBJECT MATTER OF THIS AGREEMENT. + diff --git a/Map Examples/AI map/map_ai_tetherexamples.zip b/Map Examples/AI map/map_ai_tetherexamples.zip new file mode 100755 index 0000000..253ae2b Binary files /dev/null and b/Map Examples/AI map/map_ai_tetherexamples.zip differ diff --git a/Map Examples/Programmer Example Maps/programmerexamplemaps.zip b/Map Examples/Programmer Example Maps/programmerexamplemaps.zip new file mode 100755 index 0000000..ad5d11c Binary files /dev/null and b/Map Examples/Programmer Example Maps/programmerexamplemaps.zip differ diff --git a/README b/README new file mode 100644 index 0000000..856c732 --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +For documentation and help, please see: +http://www.iddevnet.com/quake4/ diff --git a/mod Examples/MP Mod - Instagib/q4-instagib.zip b/mod Examples/MP Mod - Instagib/q4-instagib.zip new file mode 100755 index 0000000..32426ee Binary files /dev/null and b/mod Examples/MP Mod - Instagib/q4-instagib.zip differ diff --git a/mod Examples/SP Mod - DMSP/dmsp_v1.pk4 b/mod Examples/SP Mod - DMSP/dmsp_v1.pk4 new file mode 100755 index 0000000..2ef047b Binary files /dev/null and b/mod Examples/SP Mod - DMSP/dmsp_v1.pk4 differ diff --git a/q4icon.bmp b/q4icon.bmp new file mode 100644 index 0000000..a1f325e Binary files /dev/null and b/q4icon.bmp differ diff --git a/readme.txt b/readme.txt new file mode 100755 index 0000000..d9d1e29 --- /dev/null +++ b/readme.txt @@ -0,0 +1,2 @@ +For information on using the example files included with the Quake 4 (TM) SDK, please go to http://www.iddevnet.com/quake4/ + diff --git a/source/MayaImport/maya_main.h b/source/MayaImport/maya_main.h new file mode 100644 index 0000000..38c5dd7 --- /dev/null +++ b/source/MayaImport/maya_main.h @@ -0,0 +1,27 @@ + +#ifndef __MAYA_MAIN_H__ +#define __MAYA_MAIN_H__ + +/* +============================================================== + + Maya Import + +============================================================== +*/ + +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to eliminate some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys, void *(*allocator)(size_t size), void (*deallocator)(void *), size_t (*msize)(void *) ); +#else +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys ); +#endif +// RAVEN END +// RAVEN BEGIN +// bdube: default src and dest ospath's +typedef const char *( *exporterInterface_t )( const char *src_ospath, const char* dst_ospath, const char *commandline ); +// RAVEN END +typedef void ( *exporterShutdown_t )( void ); + +#endif /* !__MAYA_MAIN_H__ */ diff --git a/source/SConstruct b/source/SConstruct new file mode 100644 index 0000000..328b5dc --- /dev/null +++ b/source/SConstruct @@ -0,0 +1,799 @@ +# -*- mode: python -*- +# Quake4 build script +# TTimo +# http://scons.sourceforge.net + +import sys, os, time, commands, re, pickle, StringIO, popen2, commands, pdb, zipfile, string +import SCons + +sys.path.append( 'sys/scons' ) +import scons_utils + +conf_filename='site.conf' +# choose configuration variables which should be saved between runs +# ( we handle all those as strings ) +serialized=['CC', 'CXX', 'JOBS', 'BUILD', 'IDNET_HOST', 'GL_HARDLINK', 'DEDICATED', + 'DEBUG_MEMORY', 'LIBC_MALLOC', 'ID_NOLANADDRESS', 'ID_MCHECK', + 'TARGET_CORE', 'TARGET_CORE_SMP', 'TARGET_GAME', 'TARGET_SPGAME', 'TARGET_MPGAME', 'TARGET_MONO', 'TARGET_MONO_IS_SP', 'TARGET_DEMO', 'NOCURL', + 'BUILD_ROOT', 'Q4TEST', 'TARGET_GAMEPAK', 'OSX_BUILDSTYLE', 'SILENT', 'GCC_X86_ASM' ] + +# global build mode ------------------------------ + +g_sdk = not os.path.exists( 'sys/scons/SConscript.core' ) + +# ------------------------------------------------ + +# help ------------------------------------------- + +help_string = """ +Usage: scons [OPTIONS] [TARGET] [CONFIG] + +[OPTIONS] and [TARGET] are covered in command line options, use scons -H + +[CONFIG]: KEY="VALUE" [...] +a number of configuration options saved between runs in the """ + conf_filename + """ file +erase """ + conf_filename + """ to start with default settings again + +CC (default gcc) +CXX (default g++) + Specify C and C++ compilers (defaults gcc and g++) + ex: CC="gcc-4.1" + You can use ccache and distcc, for instance: + CC="ccache distcc gcc" CXX="ccache distcc g++" + +JOBS (default 1) + Parallel build + +BUILD (default debug) + Use debug-all/debug/release to select build settings + ex: BUILD="release" + debug-all: no optimisations, debugging symbols + debug: -O -g + release: all optimisations, including CPU target etc. + test: release with debug symbols and all optimizations except omitted frame pointers + +BUILD_ROOT (default 'build') + change the build root directory + +NOCONF (default 0, not saved) + ignore site configuration and use defaults + command line only + +SILENT ( default 0, saved ) + hide the compiler output, unless error + +GCC_X86_ASM ( defaul 0, saved ) + compile in gcc x86 asm optimizations +""" + +if ( not g_sdk ): + help_string += """ +DEDICATED (default 0) + Control regular / dedicated type of build: + 0 - client + 1 - dedicated server + 2 - both + +TARGET_CORE (default 1) + Build the core + +TARGET_CORE_SMP (default 0) + Build an SMP-enabled core + +TARGET_GAME (default 1) + Build the singleplayer and multiplayer game code + +TARGET_SPGAME (default 0) + Build the singleplayer game code + +TARGET_MPGAME (default 0) + Build the multiplayer game code + +TARGET_MONO (default 0) + Build a monolithic binary + +TARGET_MONO_IS_SP (default 0) + Build the monolithic binary as singleplayer instead of multiplayer + +TARGET_DEMO (default 0) + Build demo client ( both a core and game, no mono ) + NOTE: if you *only* want the demo client, set TARGET_CORE and TARGET_GAME to 0 + +IDNET_HOST (default to source hardcoded) + Override builtin IDNET_HOST with your own settings + +GL_HARDLINK (default 0) + Instead of dynamically loading the OpenGL libraries, use implicit dependencies + NOTE: no GL logging capability and no r_glDriver with GL_HARDLINK 1 + +DEBUG_MEMORY (default 0) + Enables memory logging to file + +LIBC_MALLOC (default 1) + Toggle idHeap memory / libc malloc usage + When libc malloc is on, memory size statistics are wrong ( no _msize ) + +ID_NOLANADDRESS (default 0) + Don't recognize any IP as LAN address. This is useful when debugging network + code where LAN / not LAN influences application behaviour + +ID_MCHECK (default 2) + Perform heap consistency checking + 0: on in Debug / off in Release + 1 forces on, 2 forces off + note that idlib has it's own block allocator/checking + this should not be considered a replacement, but an additional tool + note: this is the same as MALLOC_CHECK_, but different from mtrace + +SETUP_TAGGED (default 0, not saved) + build tagged binaries distribution implies release, excludes other setups + +SETUP_DEDICATED (default 0, not saved) + build dedicated server setup. implies release + +SETUP_DEMO (default 0, not saved) + build demo setup. implies release + +SETUP_FULL (default 0, not saved) + build full setup. implies release + +SETUP_INCREMENTAL (default 0, not saved) + builds the incremental setup + +TARGET_GAMEPAK (default 0, not saved) + build a game pak pk4 + if no setup is scheduled, from whatever game is being compiled ( current configuration ) + if setups are scheduled, controls gamepak building during setup ( might wanna use a reference one for pure-compatible updates ) + +SDK (default 0, not saved) + build an SDK release + +NOCURL (default 0) + set to 1 to disable usage of libcurl and http/ftp downloads feature + +FIX_INCLUDE (default 0, not saved) + fix include paths while compiling + wraps around the compiler call to catch and fix include path errors + note that since this process modifies files on the fly, it's not a good idea to use it with several jobs + +FIX_SUPER (default 0, not saved) + fix usage of __super msvc-ism + wraps around gcc to do a search and fix pass + +Q4TEST (default 0) + q4test build + MP-only gamecode, enables binary tagging + +ASSETS (optional, not saved) + point to the setup assets directory + +OSX_BUILDSTYLE (default 0) + Styles 1 & 2 will override CC and CXX only if they are left to the defaults + 0 - Uses the system gcc/include/libs + 1 - Uses GCC 3.3 + 10.3.9 SDK + 2 - Uses GCC 4.0 + 10.4u SDK +""" + +Help( help_string ) + +# end help --------------------------------------- + +# sanity ----------------------------------------- + +EnsureSConsVersion( 0, 96 ) + +# end sanity ------------------------------------- + +# system detection ------------------------------- + +# OS and CPU +OS = commands.getoutput( 'uname -s' ) +if ( OS == 'Linux' ): + cpu = commands.getoutput( 'uname -m' ) + if ( cpu == 'i686' ): + cpu = 'x86' + else: + cpu = 'cpu' +elif ( OS == 'Darwin' ): + cpu = commands.getoutput( 'uname -m' ) + if ( cpu == 'Power Macintosh' ): + cpu = 'ppc' + else: + cpu = 'cpu' + +# end system detection --------------------------- + +# default settings ------------------------------- + +CC = 'gcc-4.1' +CXX = 'g++-4.1' +JOBS = '1' +BUILD = 'debug' +DEDICATED = '0' +TARGET_CORE = '1' +TARGET_GAME = '1' +TARGET_SPGAME = '0' +TARGET_MPGAME = '0' +TARGET_GAMEPAK = '0' +TARGET_MONO = '0' +TARGET_MONO_IS_SP = '0' +TARGET_DEMO = '0' +IDNET_HOST = '' +GL_HARDLINK = '0' +if ( OS == 'Darwin' ): + GL_HARDLINK = '1' +DEBUG_MEMORY = '0' +LIBC_MALLOC = '1' +ID_NOLANADDRESS = '0' +ID_MCHECK = '2' +BUILD_ROOT = 'build' +SETUP_TAGGED = '0' +SETUP_DEDICATED = '0' +SETUP_DEMO = '0' +SETUP_FULL = '0' +SETUP_INCREMENTAL = '0' +SETUP = '0' # no cmdline control, will be set to 1 if any form of setup is requested +SDK = '0' +NOCONF = '0' +NOCURL = '0' +FIX_INCLUDES = '0' +FIX_SUPER = '0' +Q4TEST = '0' +ASSETS = '' +OSX_BUILDSTYLE = '0' +SILENT = '0' +TARGET_CORE_SMP = '0' +GCC_X86_ASM = '0' + +# end default settings --------------------------- + +# site settings ---------------------------------- + +if ( not ARGUMENTS.has_key( 'NOCONF' ) or ARGUMENTS['NOCONF'] != '1' ): + site_dict = {} + if (os.path.exists(conf_filename)): + site_file = open(conf_filename, 'r') + p = pickle.Unpickler(site_file) + site_dict = p.load() + print 'Loading build configuration from ' + conf_filename + ':' + for k, v in site_dict.items(): + exec_cmd = k + '=\'' + v + '\'' + print ' ' + exec_cmd + exec(exec_cmd) +else: + print 'Site settings ignored' + +# end site settings ------------------------------ + +# command line settings -------------------------- + +for k in ARGUMENTS.keys(): + exec_cmd = k + '=\'' + ARGUMENTS[k] + '\'' + print 'Command line: ' + exec_cmd + exec( exec_cmd ) + +# end command line settings ---------------------- + +# save site configuration ---------------------- + +if ( not ARGUMENTS.has_key( 'NOCONF' ) or ARGUMENTS['NOCONF'] != '1' ): + for k in serialized: + exec_cmd = 'site_dict[\'' + k + '\'] = ' + k + exec(exec_cmd) + + site_file = open(conf_filename, 'w') + p = pickle.Pickler(site_file) + p.dump(site_dict) + site_file.close() + +# end save site configuration ------------------ + +# configuration rules -------------------------- + +if ( SETUP_TAGGED != '0' or SETUP_DEDICATED != '0' or SETUP_DEMO != '0' or SETUP_FULL != '0' or SETUP_INCREMENTAL != '0' ): + DEDICATED = '2' + BUILD = 'release' + SETUP = '1' + TARGET_GAME = '1' + TARGET_CORE = '1' + TARGET_CORE_SMP = '1' + TARGET_GAMEPAK = '0' + +if ( TARGET_GAMEPAK == '1' ): + TARGET_GAME = '1' + +if ( SETUP != '0' ): + if ( SETUP_TAGGED != '0' ): + SETUP_DEDICATED = '0' + SETUP_DEMO = '0' + SETUP_FULL = '0' + Q4TEST = '1' + else: + Q4TEST = '0' + +if ( g_sdk ): + TARGET_CORE = '0' + TARGET_CORE_SMP = '0' + TARGET_MONO = '0' + TARGET_DEMO = '0' + +if ( SDK != '0' ): + DEDICATED = '0' + TARGET_CORE = '0' + TARGET_CORE_SMP = '0' + TARGET_GAME = '0' + TARGET_MPGAME = '0' + TARGET_SPGAME = '0' + TARGET_MONO = '0' + TARGET_DEMO = '0' + +if ( TARGET_GAME == '1' ): + TARGET_MPGAME = '1' + TARGET_SPGAME = '1' + +if ( BUILD == 'test' ): + print 'WARNING: compiling a release build in test configuration' + +# end configuration rules ---------------------- + +# general configuration, target selection -------- + +g_build = BUILD_ROOT + '/' + BUILD + +SConsignFile( 'scons.signatures' ) + +if ( GL_HARDLINK != '0' ): + g_build += '-hardlink' + +if ( DEBUG_MEMORY != '0' ): + g_build += '-debugmem' + +if ( LIBC_MALLOC != '1' ): + g_build += '-nolibcmalloc' + +if ( Q4TEST != '0' ): + g_build += '-q4test' + +SetOption('num_jobs', JOBS) + +LINK = CXX + +# common flags +# BASE + CORE + OPT for engine +# BASE + GAME + OPT for game +# _noopt versions of the environements are built without the OPT + +BASECPPFLAGS = [ ] +CORECPPPATH = [ ] +CORELIBPATH = [ ] +CORECPPFLAGS = [ ] +GAMECPPFLAGS = [ ] +BASELINKFLAGS = [ ] +CORELINKFLAGS = [ ] + +# for release build, further optimisations that may not work on all files +OPTCPPFLAGS = [ ] + +BASECPPFLAGS.append( '-pipe' ) +# warn all +BASECPPFLAGS.append( '-Wall' ) +# don't wrap gcc messages +BASECPPFLAGS.append( '-fmessage-length=0' ) + +if ( OS == 'Linux' ): + # gcc 4.x option only - only export what we mean to from the game SO + BASECPPFLAGS.append( '-fvisibility=hidden' ) + # get the 64 bits machine on the distcc array to produce 32 bit binaries :) + BASECPPFLAGS.append( '-m32' ) + BASELINKFLAGS.append( '-m32' ) + +if ( g_sdk or SDK != '0' ): + BASECPPFLAGS.append( '-DQ4SDK' ) + +if ( Q4TEST == '1' ): + # _MPBETA implie ID_TAGGED_BUILD but also disables some single player functionality + #BASECPPFLAGS.append( '-D_MPBETA' ) + BASECPPFLAGS.append( '-DID_TAGGED_BUILD' ) + +if ( OS == 'Darwin' ): + # a few more common defines + BASECPPFLAGS += [ '-Wno-long-double', '-arch', 'ppc', '-fasm-blocks', '-fpascal-strings', '-faltivec', '-mcpu=G5', '-mtune=G5' ] + BASECPPFLAGS += [ '-DMACOS_X' ] + BASECPPFLAGS += [ '-Wno-unknown-pragmas' ] + BASECPPFLAGS += [ '-DMAC_OS_X_VERSION_MIN_REQUIRED=1030' ] + # Override CC & CXX only if they contain the default values. Allows for distcc invocations + if ( OSX_BUILDSTYLE == '1' ): + if ( CC == 'gcc' ): + CC = [ '/usr/bin/gcc-3.3' ] + if ( CXX == 'g++' ): + CXX = [ '/usr/bin/g++-3.3' ] + + BASECPPFLAGS += [ '-isystem', '/Developer/SDKs/MacOSX10.3.9.sdk/usr/include/gcc/darwin/3.3' ] + BASECPPFLAGS += [ '-I/Developer/SDKs/MacOSX10.3.9.sdk/usr/include/gcc/darwin/3.3/c++' ] + BASECPPFLAGS += [ '-I/Developer/SDKs/MacOSX10.3.9.sdk/usr/include/gcc/darwin/3.3/c++/ppc-darwin' ] + BASECPPFLAGS += [ '-isystem', '/Developer/SDKs/MacOSX10.3.9.sdk/usr/include' ] + BASELINKFLAGS += [ '-Wl,-syslibroot,/Developer/SDKs/MacOSX10.3.9.sdk' ] + CORELIBPATH += [ '/Developer/SDKs/MacOSX10.3.9.sdk/usr/lib' ] + os.environ['NEXT_ROOT'] = '/Developer/SDKs/MacOSX10.3.9.sdk' + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3' + elif ( OSX_BUILDSTYLE == '2' ): + if ( CC == 'gcc' ): + CC = [ '/usr/bin/gcc-4.0' ] + if ( CXX == 'g++' ): + CXX = [ '/usr/bin/g++-4.0' ] + + BASECPPFLAGS += [ '-isystem', '/Developer/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/4.0' ] + BASECPPFLAGS += [ '-mone-byte-bool' ] + BASECPPFLAGS += [ '-fvisibility-inlines-hidden' ] + BASECPPFLAGS += [ '-fpermissive' ] + BASECPPFLAGS += [ '-I/Developer/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/4.0/c++' ] + BASECPPFLAGS += [ '-I/Developer/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/4.0/c++/ppc-darwin' ] + BASECPPFLAGS += [ '-isystem', '/Developer/SDKs/MacOSX10.4u.sdk/usr/include' ] + BASELINKFLAGS += [ '-Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk' ] + CORELIBPATH += [ '/Developer/SDKs/MacOSX10.4u.sdk/usr/lib' ] + os.environ['NEXT_ROOT'] = '/Developer/SDKs/MacOSX10.4u.sdk' + os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3' + +if ( BUILD == 'debug-all' ): + BASECPPFLAGS.append( '-g' ) + BASECPPFLAGS.append( '-D_DEBUG' ) + if ( ID_MCHECK == '0' ): + ID_MCHECK = '1' +elif ( BUILD == 'debug' ): + BASECPPFLAGS.append( '-g' ) + BASECPPFLAGS.append( '-O1' ) + BASECPPFLAGS.append( '-D_DEBUG' ) + if ( ID_MCHECK == '0' ): + ID_MCHECK = '1' +elif ( BUILD == 'test' ): + BASECPPFLAGS.append( '-g' ) + BASECPPFLAGS.append( '-D_FINAL' ) + BASECPPFLAGS.append( '-D_TEST' ) + if ( OS == 'Linux' ): + # Don't omit frame pointers in the test build + OPTCPPFLAGS = [ '-O3', '-march=pentium3', '-Winline', '-ffast-math', '-fno-unsafe-math-optimizations' ] + if ( ID_MCHECK == '0' ): + ID_MCHECK = '2' + elif ( OS == 'Darwin' ): + OPTCPPFLAGS = [ '-O3', '-falign-functions=16', '-falign-loops=16', '-finline' ] +elif ( BUILD == 'release' ): + BASECPPFLAGS.append( '-D_FINAL' ) + if ( OS == 'Linux' ): + # -fomit-frame-pointer: "-O also turns on -fomit-frame-pointer on machines where doing so does not interfere with debugging." + # on x86 have to set it explicitely + # -finline-functions: implicit at -O3 + # -fschedule-insns2: implicit at -O2 + # no-unsafe-math-optimizations: that should be on by default really. hit some wonko bugs in physics code because of that + OPTCPPFLAGS = [ '-O3', '-march=pentium3', '-Winline', '-ffast-math', '-fno-unsafe-math-optimizations', '-fomit-frame-pointer' ] + if ( ID_MCHECK == '0' ): + ID_MCHECK = '2' + elif ( OS == 'Darwin' ): + OPTCPPFLAGS = [ '-O3', '-falign-functions=16', '-falign-loops=16', '-finline' ] +else: + print 'Unknown build configuration ' + BUILD + sys.exit(0) + +if ( GL_HARDLINK != '0' ): + CORECPPFLAGS.append( '-DID_GL_HARDLINK' ) + +if ( DEBUG_MEMORY != '0' ): + BASECPPFLAGS += [ '-DID_DEBUG_MEMORY', '-DID_REDIRECT_NEWDELETE' ] + +if ( LIBC_MALLOC != '1' ): + BASECPPFLAGS.append( '-DUSE_LIBC_MALLOC=0' ) + +if ( len( IDNET_HOST ) ): + CORECPPFLAGS.append( '-DIDNET_HOST=\\"%s\\"' % IDNET_HOST ) + +if ( ID_NOLANADDRESS != '0' ): + CORECPPFLAGS.append( '-DID_NOLANADDRESS' ) + +if ( ID_MCHECK == '1' ): + BASECPPFLAGS.append( '-DID_MCHECK' ) + +# create the build environements + +if ( FIX_INCLUDES == '1' ): + CC = './sys/scons/fixincludes.py \'' + CC + '\'' + CXX = './sys/scons/fixincludes.py \'' + CXX + '\'' + +if ( FIX_SUPER == '1' ): + CC = './sys/scons/fixsuper.py \'' + CC + '\'' + CXX = './sys/scons/fixsuper.py \'' + CXX + '\'' + +g_base_env = Environment( ENV = os.environ, CC = CC, CXX = CXX, LINK = LINK, CPPFLAGS = BASECPPFLAGS, LINKFLAGS = BASELINKFLAGS, CPPPATH = CORECPPPATH, LIBPATH = CORELIBPATH, OS = OS ) +scons_utils.SetupUtils( g_base_env ) +g_base_env.Append( CXXFLAGS = [ '-Wno-invalid-offsetof' ] ) + +g_env = g_base_env.Copy() + +g_env['CPPFLAGS'] += OPTCPPFLAGS +g_env['CPPFLAGS'] += CORECPPFLAGS +g_env['LINKFLAGS'] += CORELINKFLAGS + +if ( BUILD != 'release' and BUILD != 'test' ): + g_env_noopt = g_env.Copy() +else: + g_env_noopt = g_base_env.Copy() + g_env_noopt['CPPFLAGS'] += CORECPPFLAGS +# g_env_noopt.Append( CPPFLAGS = '-O1' ) + g_env_noopt['LINKFLAGS'] += CORELINKFLAGS + +g_game_env = g_base_env.Copy() +g_game_env['CPPFLAGS'] += OPTCPPFLAGS +g_game_env['CPPFLAGS'] += GAMECPPFLAGS + +# maintain this dangerous optimization off at all times +g_env.Append( CPPFLAGS = '-fno-strict-aliasing' ) +g_env_noopt.Append( CPPFLAGS = '-fno-strict-aliasing' ) +g_game_env.Append( CPPFLAGS = '-fno-strict-aliasing' ) + +if ( int(JOBS) > 1 ): + print 'Using buffered process output' + silent = False + if ( SILENT == '1' ): + silent = True + scons_utils.SetupBufferedOutput( g_env, silent ) + scons_utils.SetupBufferedOutput( g_game_env, silent ) + +# mark the globals + +local_dedicated = 0 +# 0 for monolithic build +local_gamedll = 1 +# carry around rather than using .a, avoids binutils bugs +idlib_objects = [] +game_objects = [] +local_demo = 0 +# curl usage. there is a global toggle flag +local_curl = 0 +curl_lib = [] +# if idlib should produce PIC objects ( depending on core or game inclusion ) +local_idlibpic = 0 +eventdefs = None +# compile for SMP ( affects idlib and core ) +local_smp = 0 +idsdl_info = [] +local_mpgame = 0 + +GLOBALS = 'g_env g_env_noopt g_game_env OS ID_MCHECK idlib_objects game_objects local_dedicated local_gamedll local_demo local_idlibpic curl_lib local_curl local_smp idsdl_info local_mpgame eventdefs GL_HARDLINK NOCURL Q4TEST OSX_BUILDSTYLE TARGET_CORE_SMP BUILD GCC_X86_ASM' + +# end general configuration ---------------------- + +# targets ---------------------------------------- + +Export( 'GLOBALS ' + GLOBALS ) + +quake4 = None +q4ded = None +game = None +mpgame = None +q4_mon = None + +Default( None ) + +# build curl if needed +if ( NOCURL == '0' and ( TARGET_CORE == '1' or TARGET_MONO == '1' or TARGET_CORE_SMP == '1' ) ): + # 1: debug, 2: release + if ( BUILD == 'release' or BUILD == 'test' ): + local_curl = 2 + else: + local_curl = 1 + Export( 'GLOBALS ' + GLOBALS ) + curl_lib = SConscript( 'sys/scons/SConscript.curl' ) + +# build our custom SDL library if needed +if ( TARGET_CORE_SMP == '1' ): + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/sdl', '.', duplicate = 0 ) + idsdl_info = SConscript( 'sys/scons/SConscript.idsdl' ) + +if ( TARGET_CORE_SMP == '1' ): + local_gamedll = 1 + local_demo = 0 + local_idlibpic = 0 + local_dedicated = 0 + local_smp = 1 + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/core-smp/glimp', '.', duplicate = 1 ) + SConscript( g_build + '/core-smp/glimp/sys/scons/SConscript.gl' ) + BuildDir( g_build + '/core-smp', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/core-smp/sys/scons/SConscript.idlib' ) + Export( 'GLOBALS ' + GLOBALS ) # update idlib_objects + quake4smp = SConscript( g_build + '/core-smp/sys/scons/SConscript.core' ) + + if ( BUILD != 'test' ): + quake4smp = InstallAs( '#quake4smp.%s' % cpu, quake4smp ) + + if ( OS == 'Linux' ): + Default( quake4smp ) + +if ( TARGET_CORE == '1' ): + local_gamedll = 1 + local_demo = 0 + local_idlibpic = 0 + local_smp = 0 + if ( DEDICATED == '0' or DEDICATED == '2' ): + local_dedicated = 0 + Export( 'GLOBALS ' + GLOBALS ) + + BuildDir( g_build + '/core/glimp', '.', duplicate = 1 ) + SConscript( g_build + '/core/glimp/sys/scons/SConscript.gl' ) + BuildDir( g_build + '/core', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/core/sys/scons/SConscript.idlib' ) + Export( 'GLOBALS ' + GLOBALS ) # update idlib_objects + quake4 = SConscript( g_build + '/core/sys/scons/SConscript.core' ) + + if ( BUILD != 'test' ): + quake4 = InstallAs( '#quake4.%s' % cpu, quake4 ) + + if ( OS == 'Linux' ): + Default( quake4 ) + + if ( DEDICATED == '1' or DEDICATED == '2' ): + local_dedicated = 1 + Export( 'GLOBALS ' + GLOBALS ) + + BuildDir( g_build + '/dedicated/glimp', '.', duplicate = 1 ) + SConscript( g_build + '/dedicated/glimp/sys/scons/SConscript.gl' ) + BuildDir( g_build + '/dedicated', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/dedicated/sys/scons/SConscript.idlib' ) + Export( 'GLOBALS ' + GLOBALS ) + q4ded = SConscript( g_build + '/dedicated/sys/scons/SConscript.core' ) + + if ( BUILD != 'test' ): + q4ded = InstallAs( '#q4ded.%s' % cpu, q4ded ) + + if ( OS == 'Linux' ): + Default( q4ded ) + +if ( TARGET_SPGAME == '1' ): + local_gamedll = 1 + local_demo = 0 + local_dedicated = 0 + local_idlibpic = 1 + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/game', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/game/sys/scons/SConscript.idlib' ) + local_mpgame = 0 + Export( 'GLOBALS ' + GLOBALS ) + game = SConscript( g_build + '/game/sys/scons/SConscript.game' ) + + if ( BUILD != 'test' ): + if ( OS == 'Darwin' ): + game = InstallAs( '#spgame.so' , game ) + else: + game = InstallAs( '#spgame%s.so' % cpu, game ) + + Default( game ) + +if ( TARGET_MPGAME == '1' ): + local_gamedll = 1 + local_demo = 0 + local_dedicated = 0 + local_idlibpic = 1 + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/mpgame', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/mpgame/sys/scons/SConscript.idlib' ) + local_mpgame = 1 + Export( 'GLOBALS ' + GLOBALS ) + mpgame = SConscript( g_build + '/mpgame/sys/scons/SConscript.game' ) + + if ( BUILD != 'test' ): + if ( OS == 'Darwin' ): + mpgame = InstallAs( '#mpgame.so' , mpgame ) + else: + mpgame = InstallAs( '#mpgame%s.so' % cpu, mpgame ) + + Default( mpgame ) + +if ( TARGET_MONO == '1' ): + # the game in a single piece + local_gamedll = 0 + local_dedicated = 0 + local_demo = 0 + local_idlibpic = 0 + if ( TARGET_MONO_IS_SP == '1' ): + local_mpgame = 0 + else: + local_mpgame = 1 + if ( DEDICATED == '0' or DEDICATED == '2' ): + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/mono/glimp', '.', duplicate = 1 ) + SConscript( g_build + '/mono/glimp/sys/scons/SConscript.gl' ) + BuildDir( g_build + '/mono', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/mono/sys/scons/SConscript.idlib' ) + game_objects = SConscript( g_build + '/mono/sys/scons/SConscript.game' ) + Export( 'GLOBALS ' + GLOBALS ) + q4_mono = SConscript( g_build + '/mono/sys/scons/SConscript.core' ) + + if ( BUILD != 'test' ): + q4_mono = InstallAs( '#q4mono.%s' % cpu, q4_mono ) + + if ( OS == 'Linux' ): + Default( q4_mono ) + + if ( DEDICATED == '1' or DEDICATED == '2' ): + local_dedicated = 1 + Export( 'GLOBALS ' + GLOBALS ) + BuildDir( g_build + '/monoded/glimp', '.', duplicate = 1 ) + SConscript( g_build + '/monoded/glimp/sys/scons/SConscript.gl' ) + BuildDir( g_build + '/monoded', '.', duplicate = 0 ) + idlib_objects = SConscript( g_build + '/monoded/sys/scons/SConscript.idlib' ) + game_objects = SConscript( g_build + '/monoded/sys/scons/SConscript.game' ) + Export( 'GLOBALS ' + GLOBALS ) + q4_monoded = SConscript( g_build + '/monoded/sys/scons/SConscript.core' ) + + if ( BUILD != 'test' ): + q4_monoded = InstallAs( '#q4monoded.%s' % cpu, q4_monoded ) + + if ( OS == 'Linux' ): + Default( q4_monoded ) + +if ( OS == 'Darwin' ): + src = [] + if ( TARGET_CORE == '1' ): + if ( DEDICATED == '0' or DEDICATED == '2' ): + src.append( quake4 ) + if ( DEDICATED == '1' or DEDICATED == '2' ): + src.append( q4ded ) + if ( TARGET_MONO == '1' ): + if ( DEDICATED == '0' or DEDICATED == '2' ): + src.append( q4_mono ) + if ( DEDICATED == '1' or DEDICATED == '2' ): + src.append( q4_monoded ) + if ( len( src ) ): + q4mac = Command( 'q4mac', src, Action( g_env.BuildBundle ) ) + Default( q4mac ) + +if ( SETUP == '1' ): + brandelf = Program( 'brandelf', 'sys/linux/setup/brandelf.c' ) + setup_source = [ brandelf, quake4, q4ded, game, quake4smp ] + do_gamepak = ( TARGET_GAMEPAK != '0' ) + setups = [] + if ( SETUP_TAGGED == '1' ): + g_env_tagged = g_env.Copy() + g_env_tagged.Prepare( do_gamepak, ASSETS ) + setup_tagged = Command( 'setup_tagged', setup_source, Action( g_env_tagged.BuildSetup ) ) + Default( setup_tagged ) + setups.append( setup_tagged ) + if ( SETUP_DEMO == '1' ): + g_env_demo = g_env.Copy() + g_env_demo.Prepare( do_gamepak, ASSETS ) + setup_demo = Command( 'setup_demo', setup_source, Action( g_env_demo.BuildSetup ) ) + Default( setup_demo ) + setups.append( setup_demo ) + if ( SETUP_DEDICATED == '1' ): + g_env_ded = g_env.Copy() + g_env_ded.Prepare( do_gamepak, ASSETS ) + setup_ded = Command( 'setup_ded', setup_source, Action( g_env_ded.BuildSetup ) ) + Default( setup_ded ) + setups.append( setup_ded ) + if ( SETUP_FULL == '1' ): + g_env_full = g_env.Copy() + g_env_full.Prepare( do_gamepak, ASSETS ) + setup_full = Command( 'setup_full', setup_source, Action( g_env_full.BuildSetup ) ) + Default( setup_full ) + setups.append( setup_full ) + if ( SETUP_INCREMENTAL == '1' ): + g_env_incr = g_env.Copy() + g_env_incr.Prepare( do_gamepak, ASSETS ) + setup_incr = Command( 'setup_incremental', setup_source, Action( g_env_incr.BuildSetup ) ) + Default( setup_incr ) + setups.append( setup_incr ) + # setup dependencies so they are built sequentially + i = 1 + while ( i < len( setups ) ): + Depends( setups[ i ], setups[ i - 1 ] ) + i += 1 +else: + if ( TARGET_GAMEPAK == '1' ): + spgame_pak = Command( 'spgamepak', game, Action( g_env.BuildGamePak ) ) + Default( spgame_pak ) + mpgame_pak = Command( 'mpgamepak', mpgame, Action( g_env.BuildGamePak ) ) + Default( mpgame_pak ) + +if ( SDK != '0' ): + setup_sdk = Command( 'sdk', [ ], Action( g_env.BuildSDK ) ) + Default( setup_sdk ) + +# end targets ------------------------------------ diff --git a/source/aas/AASFile.h b/source/aas/AASFile.h new file mode 100644 index 0000000..40895ad --- /dev/null +++ b/source/aas/AASFile.h @@ -0,0 +1,471 @@ + +#ifndef __AASFILE_H__ +#define __AASFILE_H__ + +/* +=============================================================================== + + AAS File + +=============================================================================== +*/ + +#define AAS_FILEID "DewmAAS" +#define AAS_FILEVERSION "1.08" + +// travel flags +#define TFL_INVALID BIT(0) // not valid +#define TFL_WALK BIT(1) // walking +#define TFL_CROUCH BIT(2) // crouching +#define TFL_WALKOFFLEDGE BIT(3) // walking of a ledge +#define TFL_BARRIERJUMP BIT(4) // jumping onto a barrier +#define TFL_JUMP BIT(5) // jumping +#define TFL_LADDER BIT(6) // climbing a ladder +#define TFL_SWIM BIT(7) // swimming +#define TFL_WATERJUMP BIT(8) // jump out of the water +#define TFL_TELEPORT BIT(9) // teleportation +#define TFL_ELEVATOR BIT(10) // travel by elevator +#define TFL_FLY BIT(11) // fly +#define TFL_SPECIAL BIT(12) // special +#define TFL_WATER BIT(21) // travel through water +#define TFL_AIR BIT(22) // travel through air + +// face flags +#define FACE_SOLID BIT(0) // solid at the other side +#define FACE_LADDER BIT(1) // ladder surface +#define FACE_FLOOR BIT(2) // standing on floor when on this face +#define FACE_LIQUID BIT(3) // face seperating two areas with liquid +#define FACE_LIQUIDSURFACE BIT(4) // face seperating liquid and air + +// area flags +#define AREA_FLOOR BIT(0) // AI can stand on the floor in this area +#define AREA_GAP BIT(1) // area has a gap +#define AREA_LEDGE BIT(2) // if entered the AI bbox partly floats above a ledge +#define AREA_LADDER BIT(3) // area contains one or more ladder faces +#define AREA_LIQUID BIT(4) // area contains a liquid +#define AREA_CROUCH BIT(5) // AI cannot walk but can only crouch in this area +#define AREA_REACHABLE_WALK BIT(6) // area is reachable by walking or swimming +#define AREA_REACHABLE_FLY BIT(7) // area is reachable by flying + +// area contents flags +#define AREACONTENTS_SOLID BIT(0) // solid, not a valid area +#define AREACONTENTS_WATER BIT(1) // area contains water +#define AREACONTENTS_CLUSTERPORTAL BIT(2) // area is a cluster portal +#define AREACONTENTS_OBSTACLE BIT(3) // area contains (part of) a dynamic obstacle +#define AREACONTENTS_TELEPORTER BIT(4) // area contains (part of) a teleporter trigger + +// bits for different bboxes +#define AREACONTENTS_BBOX_BIT 24 + + +// RAVEN BEGIN +// cdr: AASTactical + +// feature bits +#define FEATURE_COVER BIT(0) // provides cover +#define FEATURE_LOOK_LEFT BIT(1) // attack by leaning left +#define FEATURE_LOOK_RIGHT BIT(2) // attack by leaning right +#define FEATURE_LOOK_OVER BIT(3) // attack by leaning over the cover +#define FEATURE_CORNER_LEFT BIT(4) // is a left corner +#define FEATURE_CORNER_RIGHT BIT(5) // is a right corner +#define FEATURE_PINCH BIT(6) // is a tight area connecting two larger areas +#define FEATURE_VANTAGE BIT(7) // provides a good view of the sampled area as a whole + +// forward reference of sensor object +struct rvAASTacticalSensor; +struct rvMarker; + +// RAVEN END + + +#define MAX_REACH_PER_AREA 256 +#define MAX_AAS_TREE_DEPTH 128 + +#define MAX_AAS_BOUNDING_BOXES 4 + +typedef enum { + + RE_WALK, + RE_WALKOFFLEDGE, + RE_FLY, + RE_SWIM, + RE_WATERJUMP, + RE_BARRIERJUMP, + RE_SPECIAL +}; + + +// reachability to another area +class idReachability { +public: + int travelType; // type of travel required to get to the area + short toAreaNum; // number of the reachable area + short fromAreaNum; // number of area the reachability starts + idVec3 start; // start point of inter area movement + idVec3 end; // end point of inter area movement + int edgeNum; // edge crossed by this reachability + unsigned short travelTime; // travel time of the inter area movement + byte number; // reachability number within the fromAreaNum (must be < 256) + byte disableCount; // number of times this reachability has been disabled + idReachability * next; // next reachability in list + idReachability * rev_next; // next reachability in reversed list + unsigned short * areaTravelTimes; // travel times within the fromAreaNum from reachabilities that lead towards this area +}; + +class idReachability_Walk : public idReachability { +}; + +class idReachability_BarrierJump : public idReachability { +}; + +class idReachability_WaterJump : public idReachability { +}; + +class idReachability_WalkOffLedge : public idReachability { +}; + +class idReachability_Swim : public idReachability { +}; + +class idReachability_Fly : public idReachability { +}; + +class idReachability_Special : public idReachability { + friend class idAASFileLocal; +private: + idDict dict; +}; + +// index +typedef int aasIndex_t; + +// vertex +typedef idVec3 aasVertex_t; + +// edge +typedef struct aasEdge_s { + int vertexNum[2]; // numbers of the vertexes of this edge +} aasEdge_t; + +// area boundary face +typedef struct aasFace_s { + unsigned short planeNum; // number of the plane this face is on + unsigned short flags; // face flags + int numEdges; // number of edges in the boundary of the face + int firstEdge; // first edge in the edge index + short areas[2]; // area at the front and back of this face +} aasFace_t; + +// area with a boundary of faces +typedef struct aasArea_s { + int numFaces; // number of faces used for the boundary of the area + int firstFace; // first face in the face index used for the boundary of the area + idBounds bounds; // bounds of the area + idVec3 center; // center of the area an AI can move towards + float ceiling; // top of the area + unsigned short flags; // several area flags + unsigned short contents; // contents of the area + short cluster; // cluster the area belongs to, if negative it's a portal + short clusterAreaNum; // number of the area in the cluster + int travelFlags; // travel flags for traveling through this area + idReachability * reach; // reachabilities that start from this area + idReachability * rev_reach; // reachabilities that lead to this area + + // RAVEN BEGIN + // cdr: AASTactical + unsigned short numFeatures; // number of features in this area + unsigned short firstFeature; // first feature in the feature index within this area + + // cdr: Obstacle Avoidance + rvMarker* firstMarker; // first obstacle avoidance threat in this area (0 if none) + // RAVEN END +} aasArea_t; + +// nodes of the bsp tree +typedef struct aasNode_s { + unsigned short planeNum; // number of the plane that splits the subspace at this node + int children[2]; // child nodes, zero is solid, negative is -(area number) +} aasNode_t; + +// cluster portal +typedef struct aasPortal_s { + short areaNum; // number of the area that is the actual portal + short clusters[2]; // number of cluster at the front and back of the portal + short clusterAreaNum[2]; // number of this portal area in the front and back cluster + unsigned short maxAreaTravelTime; // maximum travel time through the portal area +} aasPortal_t; + +// cluster +typedef struct aasCluster_s { + int numAreas; // number of areas in the cluster + int numReachableAreas; // number of areas with reachabilities + int numPortals; // number of cluster portals + int firstPortal; // first cluster portal in the index +} aasCluster_t; + +// RAVEN BEGIN +// cdr: AASTactical +typedef struct aasFeature_s { + short x; // 2 Bytes + short y; // 2 Bytes + short z; // 2 Bytes + unsigned short flags; // 2 Bytes + unsigned char normalx; // 1 Byte + unsigned char normaly; // 1 Byte + unsigned char height; // 1 Byte + unsigned char weight; // 1 Byte + + idVec3& Normal(); + idVec3& Origin(); + + void DrawDebugInfo( int index=-1 ); + int GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance=16.0f ); +} aasFeature_t; //-------------------------------- + // 12 Bytes +// RAVEN END + +// trace through the world +typedef struct aasTrace_s { + // parameters + int flags; // areas with these flags block the trace + int travelFlags; // areas with these travel flags block the trace + int maxAreas; // size of the 'areas' array + int getOutOfSolid; // trace out of solid if the trace starts in solid + // output + float fraction; // fraction of trace completed + idVec3 endpos; // end position of trace + int planeNum; // plane hit + int lastAreaNum; // number of last area the trace went through + int blockingAreaNum; // area that could not be entered + int numAreas; // number of areas the trace went through + int * areas; // array to store areas the trace went through + idVec3 * points; // points where the trace entered each new area + aasTrace_s( void ) { areas = NULL; points = NULL; getOutOfSolid = false; flags = travelFlags = maxAreas = 0; } +} aasTrace_t; + +// settings +class idAASSettings { +public: + // collision settings + int numBoundingBoxes; + idBounds boundingBoxes[MAX_AAS_BOUNDING_BOXES]; + bool usePatches; + bool writeBrushMap; + bool playerFlood; + bool noOptimize; + bool allowSwimReachabilities; + bool allowFlyReachabilities; +// RAVEN BEGIN +// bkreimeier + bool generateAllFaces; +// cdr: AASTactical + bool generateTacticalFeatures; +// scork: AASOnly numbers + int iAASOnly; // 0, else 32,48,96,250 or -1 for all +// RAVEN END + idStr fileExtension; + // physics settings + idVec3 gravity; + idVec3 gravityDir; + idVec3 invGravityDir; + float gravityValue; + float maxStepHeight; + float maxBarrierHeight; + float maxWaterJumpHeight; + float maxFallHeight; + float minFloorCos; + // fixed travel times + int tt_barrierJump; + int tt_startCrouching; + int tt_waterJump; + int tt_startWalkOffLedge; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + idVec4 debugColor; + bool debugDraw; +// RAVEN END + +public: + idAASSettings( void ); + + bool FromFile( const idStr &fileName ); +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + bool FromParser( Lexer &src ); +// RAVEN END + bool FromDict( const char *name, const idDict *dict ); + bool WriteToFile( idFile *fp ) const; + bool ValidForBounds( const idBounds &bounds ) const; + bool ValidEntity( const char *classname, bool* needFlyReachabilities=NULL ) const; + +// RAVEN BEGIN + float Radius( float scale=1.0f ) const; +// RAVEN END + + +private: +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + bool ParseBool( Lexer &src, bool &b ); + bool ParseInt( Lexer &src, int &i ); + bool ParseFloat( Lexer &src, float &f ); + bool ParseVector( Lexer &src, idVec3 &vec ); + bool ParseBBoxes( Lexer &src ); +// RAVEN END +}; + + +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + the cluster number set to the negative portal number +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +- portal zero is a dummy +- cluster zero is a dummy + +*/ +typedef struct sizeEstimate_s { + int numEdgeIndexes; + int numFaceIndexes; + int numAreas; + int numNodes; +} sizeEstimate_t; + + +class idAASFile { +public: + virtual ~idAASFile( void ) {} +// RAVEN BEGIN +// jscott: made pure virtual + virtual class idAASFile * CreateNew( void ) = 0; + virtual class idAASSettings * CreateAASSettings( void ) = 0; + virtual class idReachability * CreateReachability( int type ) = 0; +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + virtual bool FromParser( class idAASSettings *edit, Lexer &src ) = 0; +// RAVEN END + + virtual const char * GetName( void ) const = 0; + virtual unsigned int GetCRC( void ) const = 0; + virtual void SetSizes( sizeEstimate_t size ) = 0; + + virtual int GetNumPlanes( void ) const = 0; + virtual idPlane & GetPlane( int index ) = 0; + virtual int FindPlane( const idPlane &plane, const float normalEps, const float distEps ) = 0; + + virtual int GetNumVertices( void ) const = 0; + virtual aasVertex_t & GetVertex( int index ) = 0; + virtual int AppendVertex( aasVertex_t &vert ) = 0; + + virtual int GetNumEdges( void ) const = 0; + virtual aasEdge_t & GetEdge( int index ) = 0; + virtual int AppendEdge( aasEdge_t &edge ) = 0; + + virtual int GetNumEdgeIndexes( void ) const = 0; + virtual aasIndex_t & GetEdgeIndex( int index ) = 0; + virtual int AppendEdgeIndex( aasIndex_t &edgeIdx ) = 0; + + virtual int GetNumFaces( void ) const = 0; + virtual aasFace_t & GetFace( int index ) = 0; + virtual int AppendFace( aasFace_t &face ) = 0; + + virtual int GetNumFaceIndexes( void ) const = 0; + virtual aasIndex_t & GetFaceIndex( int index ) = 0; + virtual int AppendFaceIndex( aasIndex_t &faceIdx ) = 0; + + virtual int GetNumAreas( void ) const = 0; + virtual aasArea_t & GetArea( int index ) = 0; + virtual int AppendArea( aasArea_t &area ) = 0; + + virtual int GetNumNodes( void ) const = 0; + virtual aasNode_t & GetNode( int index ) = 0; + virtual int AppendNode( aasNode_t &node ) = 0; + virtual void SetNumNodes( int num ) = 0; + + virtual int GetNumPortals( void ) const = 0; + virtual aasPortal_t & GetPortal( int index ) = 0; + virtual int AppendPortal( aasPortal_t &portal ) = 0; + + virtual int GetNumPortalIndexes( void ) const = 0; + virtual aasIndex_t & GetPortalIndex( int index ) = 0; + virtual int AppendPortalIndex( aasIndex_t &portalIdx, int clusterNum ) = 0; + + virtual int GetNumClusters( void ) const = 0; + virtual aasCluster_t & GetCluster( int index ) = 0; + virtual int AppendCluster( aasCluster_t &cluster ) = 0; + + // RAVEN BEGIN + // cdr: AASTactical + virtual void ClearTactical( void ) = 0; + + virtual int GetNumFeatureIndexes( void ) const = 0; + virtual aasIndex_t & GetFeatureIndex( int index ) = 0; + virtual int AppendFeatureIndex( aasIndex_t &featureIdx ) = 0; + + virtual int GetNumFeatures( void ) const = 0; + virtual aasFeature_t & GetFeature( int index ) = 0; + virtual int AppendFeature( aasFeature_t &cluster ) = 0; + // RAVEN END + + virtual idAASSettings & GetSettings( void ) = 0; + virtual void SetSettings( const idAASSettings &in ) = 0; + + virtual void SetPortalMaxTravelTime( int index, int time ) = 0; + virtual void SetAreaTravelFlag( int index, int flag ) = 0; + virtual void RemoveAreaTravelFlag( int index, int flag ) = 0; +// RAVEN END + + virtual idVec3 EdgeCenter( int edgeNum ) const = 0; + virtual idVec3 FaceCenter( int faceNum ) const = 0; + virtual idVec3 AreaCenter( int areaNum ) const = 0; + + virtual idBounds EdgeBounds( int edgeNum ) const = 0; + virtual idBounds FaceBounds( int faceNum ) const = 0; + virtual idBounds AreaBounds( int areaNum ) const = 0; + + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &point ) const = 0; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + virtual void PrintInfo( void ) const = 0; +// RAVEN BEGIN +// jscott: added + virtual size_t GetMemorySize( void ) = 0; + + virtual void Init( void ) = 0; + virtual bool Load( const idStr &fileName, unsigned int mapFileCRC ) = 0; + virtual bool Write( const idStr &fileName, unsigned int mapFileCRC ) = 0; + virtual void Clear( void ) = 0; + virtual void FinishAreas( void ) = 0; + virtual void ReportRoutingEfficiency( void ) const = 0; + virtual void LinkReversedReachability( void ) = 0; + virtual void DeleteReachabilities( void ) = 0; + virtual void DeleteClusters( void ) = 0; + virtual void Optimize( void ) = 0; + virtual bool IsDummyFile( unsigned int mapFileCRC ) = 0; +// RAVEN END + + virtual const idDict & GetReachabilitySpecialDict( idReachability *reach ) const = 0; + virtual void SetReachabilitySpecialDictKeyValue( idReachability *reach, const char *key, const char *value ) = 0; +}; + +// RAVEN BEGIN +extern idAASFile *AASFile; +// RAVEN END + +#endif /* !__AASFILE_H__ */ diff --git a/source/aas/AASFileManager.h b/source/aas/AASFileManager.h new file mode 100644 index 0000000..a7cdd95 --- /dev/null +++ b/source/aas/AASFileManager.h @@ -0,0 +1,23 @@ + +#ifndef __AASFILEMANAGER_H__ +#define __AASFILEMANAGER_H__ + +/* +=============================================================================== + + AAS File Manager + +=============================================================================== +*/ + +class idAASFileManager { +public: + virtual ~idAASFileManager( void ) {} + + virtual idAASFile * LoadAAS( const char *fileName, unsigned int mapFileCRC ) = 0; + virtual void FreeAAS( idAASFile *file ) = 0; +}; + +extern idAASFileManager * AASFileManager; + +#endif /* !__AASFILEMANAGER_H__ */ diff --git a/source/bse/BSEInterface.h b/source/bse/BSEInterface.h new file mode 100644 index 0000000..625a64d --- /dev/null +++ b/source/bse/BSEInterface.h @@ -0,0 +1,103 @@ +#ifndef _BSE_INTERFACE_H_INC_ +#define _BSE_INTERFACE_H_INC_ + +#define BSE_EFFECT_EXTENSION "fx" + +enum +{ + VIEWEFFECT_DOUBLEVISION = 0, + VIEWEFFECT_SHAKE, + VIEWEFFECT_TUNNEL +}; + +typedef enum { + EC_IGNORE = 0, + EC_IMPACT, + EC_IMPACT_PARTICLES, + + EC_MAX, +} effectCategory_t; + +extern idCVar bse_enabled; +extern idCVar bse_render; +extern idCVar bse_debug; +extern idCVar bse_showBounds; +extern idCVar bse_physics; +extern idCVar bse_debris; +extern idCVar bse_scale; +extern idCVar bse_singleEffect; +extern idCVar bse_maxParticles; + +// Interface to the effects system + +class rvBSEManager +{ +public: + virtual ~rvBSEManager( void ) {} + + virtual bool Init( void ) = 0; + virtual bool Shutdown( void ) = 0; + + virtual bool PlayEffect( class rvRenderEffectLocal *def, float time ) = 0; + virtual bool ServiceEffect( class rvRenderEffectLocal *def, float time ) = 0; + virtual void StopEffect( rvRenderEffectLocal *def ) = 0; + virtual void FreeEffect( rvRenderEffectLocal *def ) = 0; + virtual float EffectDuration( const rvRenderEffectLocal *def ) = 0; + + virtual bool CheckDefForSound( const renderEffect_t *def ) = 0; + + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; + + virtual void StartFrame( void ) = 0; + virtual void EndFrame( void ) = 0; + virtual bool Filtered( const char *name, effectCategory_t category ) = 0; + + virtual void UpdateRateTimes( void ) = 0; + virtual bool CanPlayRateLimited( effectCategory_t category ) = 0; +}; + +extern rvBSEManager *bse; + +class rvDeclEffectEdit +{ +public: + virtual ~rvDeclEffectEdit() {} + virtual void Finish( class rvDeclEffect *edit ) = 0; + virtual class rvSegmentTemplate *GetSegmentTemplate( class rvDeclEffect *edit, const char *name ) = 0; + virtual class rvSegmentTemplate *GetSegmentTemplate( class rvDeclEffect *edit, int i ) = 0; + virtual void CopyData( class rvDeclEffect *edit, class rvDeclEffect *copy ) = 0; + virtual int AddSegment( class rvDeclEffect *edit, class rvSegmentTemplate *add ) = 0; + virtual void DeleteSegment( class rvDeclEffect *edit, int index ) = 0; + virtual void SwapSegments( class rvSegmentTemplate *seg1, class rvSegmentTemplate *seg2 ) = 0; + + virtual void CreateEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual void DeleteEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual bool CompareToEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual void RevertToEditorOriginal( class rvDeclEffect *edit ) = 0; + + virtual void Init( class rvSegmentTemplate *edit, class rvDeclEffect *effect ) = 0; + virtual bool Parse( class rvSegmentTemplate *edit, class rvDeclEffect *effect, int type, class idLexer *lexer ) = 0; + virtual void Finish( class rvSegmentTemplate *edit, class rvDeclEffect *effect ) = 0; + virtual bool Compare( class rvSegmentTemplate *edit, const class rvSegmentTemplate *other ) const = 0; + virtual void SetName( class rvSegmentTemplate *edit, const char *name ) = 0; + + virtual void Finish( class rvParticleTemplate *edit ) = 0; + virtual bool Compare( class rvParticleTemplate *edit, const class rvParticleTemplate *other ) const = 0; + virtual void Init( class rvParticleTemplate *edit ) = 0; + virtual void FixupParms( class rvParticleTemplate *edit, class rvParticleParms *parms ) = 0; + virtual void SetMaterialName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetModelName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetEntityDefName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetTrailTypeName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetTrailMaterialName( class rvParticleTemplate *edit, const char *name ) = 0; + + virtual bool Compare( class rvParticleParms *edit, const class rvParticleParms *other ) const = 0; + + virtual void CalcRate( class rvEnvParms *edit, float *rate, float duration, int count ) = 0; + virtual void Evaluate3( class rvEnvParms *edit, float time, const float *start, const float *rate, const float *end, float *dest ) = 0; +}; + +extern rvDeclEffectEdit *declEffectEdit; + +#endif // _BSE_INTERFACE_H_INC_ diff --git a/source/cm/CollisionModel.h b/source/cm/CollisionModel.h new file mode 100644 index 0000000..5799a42 --- /dev/null +++ b/source/cm/CollisionModel.h @@ -0,0 +1,150 @@ + +#ifndef __COLLISIONMODELMANAGER_H__ +#define __COLLISIONMODELMANAGER_H__ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + + Short translations are the least expensive. Retrieving contact points is + about as cheap as a short translation. Position tests are more expensive + and rotations are most expensive. + + There is no position test at the start of a translation or rotation. In other + words if a translation with start != end or a rotation with angle != 0 starts + in solid, this goes unnoticed and the collision result is undefined. + + A translation with start == end or a rotation with angle == 0 performs + a position test and fills in the trace_t structure accordingly. + +=============================================================================== +*/ + +// contact type +enum contactType_t { + CONTACT_NONE, // no contact + CONTACT_EDGE, // trace model edge hits model edge + CONTACT_MODELVERTEX, // model vertex hits trace model polygon + CONTACT_TRMVERTEX // trace model vertex hits model polygon +}; + +// contact info +struct contactInfo_t { + contactType_t type; // contact type + idVec3 point; // point of contact + idVec3 normal; // contact plane normal + float dist; // contact plane distance + float separation; // contact feature separation at initial position + int contents; // contents at other side of surface + const idMaterial * material; // surface material + int modelFeature; // contact feature on model + int trmFeature; // contact feature on trace model + int entityNum; // entity the contact surface is a part of + int id; // id of clip model the contact surface is part of +// RAVEN BEGIN +// jscott: for material type code + const rvDeclMatType *materialType; // material type of texture (possibly indirected though a hit map) +// RAVEN END +}; + +// trace result +struct trace_t { + float fraction; // fraction of movement completed, 1.0 = didn't hit anything + idVec3 endpos; // final position of trace model + idMat3 endAxis; // final axis of trace model + contactInfo_t c; // contact information, only valid if fraction < 1.0 +}; + +#define WORLD_MODEL_NAME "worldMap" // name of world model +#define CM_CLIP_EPSILON 0.25f // always stay this distance away from any model +#define CM_BOX_EPSILON 1.0f // should always be larger than clip epsilon +#define CM_MAX_TRACE_DIST 4096.0f // maximum distance a trace model may be traced, point traces are unlimited + +// collision model +class idCollisionModel { +public: + virtual ~idCollisionModel() { } + // Returns the name of the model. + virtual const char * GetName( void ) const = 0; + // Gets the bounds of the model. + virtual bool GetBounds( idBounds &bounds ) const = 0; + // Gets all contents flags of brushes and polygons of the model ored together. + virtual bool GetContents( int &contents ) const = 0; + // Gets a vertex of the model. + virtual bool GetVertex( int vertexNum, idVec3 &vertex ) const = 0; + // Gets an edge of the model. + virtual bool GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Gets a polygon of the model. + virtual bool GetPolygon( int polygonNum, idFixedWinding &winding ) const = 0; +}; + +// collision model manager +class idCollisionModelManager { +public: + virtual ~idCollisionModelManager( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // Loads collision models from a map file. + virtual void LoadMap( const idMapFile *mapFile, bool forceReload ) = 0; + // Frees all the collision models for the given map. + virtual void FreeMap( const char *mapName ) = 0; + + // Loads a collision model. + virtual idCollisionModel * LoadModel( const char *mapName, const char *modelName ) = 0; +// RAVEN BEGIN +// mwhitlock: conversion from idRenderModel to MD5R fixes (to keep redundant collision surfaces out of the MD5R files). + virtual idCollisionModel * ExtractCollisionModel( idRenderModel *renderModel, const char *modelName ) = 0; +// RAVEN END + // Precaches a collision model. + virtual void PreCacheModel( const char *mapName, const char *modelName ) = 0; + // Free the given model. + virtual void FreeModel( idCollisionModel *model ) = 0; + // Purge all unused models. + virtual void PurgeModels( void ) = 0; + + // Sets up a trace model for collision with other trace models. + virtual idCollisionModel * ModelFromTrm( const char *mapName, const char *modelName, const idTraceModel &trm, const idMaterial *material ) = 0; + // Creates a trace model from a collision model, returns true if succesfull. + virtual bool TrmFromModel( const char *mapName, const char *modelName, idTraceModel &trm ) = 0; + // Creates a trace model for each primitive of the collision model, returns the number of trace models. + virtual int CompoundTrmFromModel( const char *mapName, const char *modelName, idTraceModel *trms, int maxTrms ) = 0; + + // Translates a trace model and reports the first collision if any. + virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Rotates a trace model and reports the first collision if any. + virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Returns the contents touched by the trace model or 0 if the trace model is in free space. + virtual int Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Stores all contact points of the trace model with the model, returns the number of contacts. + virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + + // Tests collision detection. + virtual void DebugOutput( const idVec3 &viewOrigin, const idMat3 &viewAxis ) = 0; + // Draws a model. + virtual void DrawModel( idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const idMat3 &viewAxis, const float radius ) = 0; + // Lists all loaded models. + virtual void ListModels( void ) = 0; + // Prints model information, use -1 for accumulated model info. + virtual void ModelInfo( int num ) = 0; + virtual void PrintMemInfo( MemInfo *mi ) = 0; + virtual bool IsLoaded( void ) = 0; + + // Writes a collision model file for the given map entity. + virtual bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ) = 0; +}; + +extern idCollisionModelManager *collisionModelManager; + +#endif /* !__COLLISIONMODELMANAGER_H__ */ diff --git a/source/framework/BuildDefines.h b/source/framework/BuildDefines.h new file mode 100644 index 0000000..200cac1 --- /dev/null +++ b/source/framework/BuildDefines.h @@ -0,0 +1,74 @@ + +/* +=============================================================================== + + Preprocessor settings for compiling different versions. + +=============================================================================== +*/ + +// useful for network debugging, turns off 'LAN' checks, all IPs are classified 'internet' +#ifndef ID_NOLANADDRESS + #define ID_NOLANADDRESS 0 +#endif + +// let .dds be loaded from FS without altering pure state. only for developement. +#ifndef ID_PURE_ALLOWDDS + #define ID_PURE_ALLOWDDS 0 +#endif + +// build an exe with no CVAR_CHEAT controls +#ifndef ID_ALLOW_CHEATS + #define ID_ALLOW_CHEATS 0 +#endif + +#ifndef ID_ENABLE_CURL + #if !defined( _XENON ) + #define ID_ENABLE_CURL 1 + #else + #define ID_ENABLE_CURL 0 + #endif +#endif + +// fake a pure client. useful to connect an all-debug client to a server +#ifndef ID_FAKE_PURE + #define ID_FAKE_PURE 0 +#endif + +// don't do backtraces in release builds. +// atm, we have no useful way to reconstruct the trace, so let's leave it off +#define ID_BT_STUB +#ifndef ID_BT_STUB + #if defined( __linux__ ) + #if defined( _DEBUG ) + #define ID_BT_STUB + #endif + #else + #define ID_BT_STUB + #endif +#endif + +#ifndef ID_ENFORCE_KEY +# if !defined( ID_DEDICATED ) && !defined( ID_DEMO_BUILD ) +# define ID_ENFORCE_KEY 1 +# else +// twhitaker: just leave it undefined +// TTimo: that breaks the ability to control it from command line settings with !win32 builds, but I can live with it +//# define ID_ENFORCE_KEY 0 +# endif +#endif + +// verify checksums in clientinfo traffic +// NOTE: this makes the network protocol incompatible +#ifndef ID_CLIENTINFO_TAGS + #define ID_CLIENTINFO_TAGS 0 +#endif + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present. +//#define ID_DEMO_BUILD + +#if !defined( _WIN32 ) + // DOA? didn't see the pbuffer code used at all through the code + #define TMP_PBUFFSTUB +#endif diff --git a/source/framework/BuildVersion.h b/source/framework/BuildVersion.h new file mode 100644 index 0000000..dc454a5 --- /dev/null +++ b/source/framework/BuildVersion.h @@ -0,0 +1,3 @@ +// that one is no longer updated with the Q4 source in SVN +// but is used in some of the single player mishmush so I don't want to touch it +const int BUILD_NUMBER = 1283; diff --git a/source/framework/CVarSystem.h b/source/framework/CVarSystem.h new file mode 100644 index 0000000..6f801ba --- /dev/null +++ b/source/framework/CVarSystem.h @@ -0,0 +1,415 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __CVARSYSTEM_H__ +#define __CVARSYSTEM_H__ + +/* +=============================================================================== + + Console Variables (CVars) are used to hold scalar or string variables + that can be changed or displayed at the console as well as accessed + directly in code. + + CVars are mostly used to hold settings that can be changed from the + console or saved to and loaded from configuration files. CVars are also + occasionally used to communicate information between different modules + of the program. + + CVars are restricted from having the same names as console commands to + keep the console interface from being ambiguous. + + CVars can be accessed from the console in three ways: + cvarName prints the current value + cvarName X sets the value to X if the variable exists + set cvarName X as above, but creates the CVar if not present + + CVars may be declared in the global namespace, in classes and in functions. + However declarations in classes and functions should always be static to + save space and time. Making CVars static does not change their + functionality due to their global nature. + + CVars should be contructed only through one of the constructors with name, + value, flags and description. The name, value and description parameters + to the constructor have to be static strings, do not use va() or the like + functions returning a string. + + CVars may be declared multiple times using the same name string. However, + they will all reference the same value and changing the value of one CVar + changes the value of all CVars with the same name. + + CVars should always be declared with the correct type flag: CVAR_BOOL, + CVAR_INTEGER or CVAR_FLOAT. If no such flag is specified the CVar + defaults to type string. If the CVAR_BOOL flag is used there is no need + to specify an argument auto-completion function because the CVar gets + one assigned automatically. + + CVars are automatically range checked based on their type and any min/max + or valid string set specified in the constructor. + + CVars are always considered cheats except when CVAR_NOCHEAT, CVAR_INIT, + CVAR_ROM, CVAR_ARCHIVE, CVAR_USERINFO, CVAR_SERVERINFO, CVAR_NETWORKSYNC + is set. + +=============================================================================== +*/ + +typedef enum { + CVAR_ALL = -1, // all flags + CVAR_BOOL = BIT(0), // variable is a boolean + CVAR_INTEGER = BIT(1), // variable is an integer + CVAR_FLOAT = BIT(2), // variable is a float + CVAR_SYSTEM = BIT(3), // system variable + CVAR_RENDERER = BIT(4), // renderer variable + CVAR_SOUND = BIT(5), // sound variable + CVAR_GUI = BIT(6), // gui variable + CVAR_GAME = BIT(7), // game variable + CVAR_TOOL = BIT(8), // tool variable + CVAR_USERINFO = BIT(9), // sent to servers, available to menu + CVAR_SERVERINFO = BIT(10), // sent from servers, available to menu + CVAR_NETWORKSYNC = BIT(11), // cvar is synced from the server to clients + CVAR_STATIC = BIT(12), // statically declared, not user created + CVAR_CHEAT = BIT(13), // variable is considered a cheat + CVAR_NOCHEAT = BIT(14), // variable is not considered a cheat + CVAR_INIT = BIT(15), // can only be set from the command-line + CVAR_ROM = BIT(16), // display only, cannot be set by user at all + CVAR_ARCHIVE = BIT(17), // set to cause it to be saved to a config file + CVAR_MODIFIED = BIT(18), // set when the variable is modified + CVAR_INFO = BIT(19), // sent as part of the MOTD packet + CVAR_NORESET = BIT(20), // don't reset the contents on cvar system restart + CVAR_CASE_SENSITIVE = BIT(21), // a change in case of the string contents sets the modified flag + CVAR_SPECIAL_CONCAT = BIT(22), // special concatination of the incoming string to the cvar system, will remove space between ^ and the code that is produced by tokenzier + CVAR_STRIPTRAILING = BIT(23), // always strip trailing / on that cvar + CVAR_REPEATERINFO = BIT(24), // sent from repeaters, available to menu +} cvarFlags_t; + + +/* +=============================================================================== + + idCVar + +=============================================================================== +*/ + +class idCVar { +public: + // Never use the default constructor. + idCVar( void ) { assert( typeid( this ) != typeid( idCVar ) ); } + + // Always use one of the following constructors. + idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion = NULL ); + + virtual ~idCVar( void ) {} + + const char * GetName( void ) const { return internalVar->name; } + int GetFlags( void ) const { return internalVar->flags; } + const char * GetDescription( void ) const { return internalVar->description; } + float GetMinValue( void ) const { return internalVar->valueMin; } + float GetMaxValue( void ) const { return internalVar->valueMax; } + const char ** GetValueStrings( void ) const { return valueStrings; } + argCompletion_t GetValueCompletion( void ) const { return valueCompletion; } + + bool IsModified( void ) const { return ( internalVar->flags & CVAR_MODIFIED ) != 0; } + void SetModified( void ) { internalVar->flags |= CVAR_MODIFIED; } + void ClearModified( void ) { internalVar->flags &= ~CVAR_MODIFIED; } + + const char * GetString( void ) const { return internalVar->value; } + bool GetBool( void ) const { return ( internalVar->integerValue != 0 ); } + int GetInteger( void ) const { return internalVar->integerValue; } + float GetFloat( void ) const { return internalVar->floatValue; } + + void SetString( const char *value ) { internalVar->InternalSetString( value ); } + void SetBool( const bool value ) { internalVar->InternalSetBool( value ); } + void SetInteger( const int value ) { internalVar->InternalSetInteger( value ); } + void SetFloat( const float value ) { internalVar->InternalSetFloat( value ); } + + void SetInternalVar( idCVar *cvar ) { internalVar = cvar; } + + void SetFlag( const cvarFlags_t flag ) { internalVar->flags |= flag; } + void RemoveFlag( const cvarFlags_t flag ) { internalVar->flags &= ~flag; } + + static void RegisterStaticVars( void ); + +protected: + const char * name; // name + const char * value; // value + const char * description; // description + int flags; // CVAR_? flags + float valueMin; // minimum value + float valueMax; // maximum value + const char ** valueStrings; // valid value strings + argCompletion_t valueCompletion; // value auto-completion function + int integerValue; // atoi( string ) + float floatValue; // atof( value ) + idCVar * internalVar; // internal cvar + idCVar * next; // next statically declared cvar + +private: + void Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ); + + virtual void InternalSetString( const char *newValue ) {} + virtual void InternalSetBool( const bool newValue ) {} + virtual void InternalSetInteger( const int newValue ) {} + virtual void InternalSetFloat( const float newValue ) {} + + static idCVar * staticVars; +}; + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion ) { + if ( !valueCompletion && ( flags & CVAR_BOOL ) ) { + valueCompletion = idCmdSystem::ArgCompletion_Boolean; + } + Init( name, value, flags, description, 1, -1, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, valueMin, valueMax, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, 1, -1, valueStrings, valueCompletion ); +} + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + +/* +=============================================================================== + + idCVarHelp + +=============================================================================== +*/ + +typedef enum { + CVARHELP_ALL = -1, // all categories + CVARHELP_GAME = BIT(0), // game menu + CVARHELP_RENDERER = BIT(1), // renderer menu + CVARHELP_PHYSICS = BIT(2), // physics menu + CVARHELP_SOUND = BIT(3), // sound menu + CVARHELP_AI = BIT(4), // AI menu +} cvarHelpCategory_t; + + +class idCVarHelp { +public: + // Never use the default constructor. + idCVarHelp( void ) { assert( typeid(this) != typeid(idCVarHelp) ); } + + // Always use one of the following constructors. + idCVarHelp( const char *cvarName, const char *friendlyName, const char *choices, const char *values, cvarHelpCategory_t category ); + + idCVarHelp( const idCVarHelp © ); + + const char * GetCVarName( void ) const { return cvarName; } + const char * GetFriendlyName( void ) const { return friendlyName; } + const char * GetChoices( void ) const { return choices; } + const char * GetValues( void ) const { return values; } + const cvarHelpCategory_t GetCategory( void ) const { return category; } + const idCVarHelp * GetNext( void ) const { return next; } + + void SetNext( const idCVarHelp *value ) { next = value; } + + static void RegisterStatics( void ); + +private: + const char * cvarName; // the cvar this help belongs to + const char * friendlyName; // a textual name for the cvar impaired + const char * choices; // a textual list of choices for the cvar impaired + const char * values; // the list of values that goes with the choices + cvarHelpCategory_t category; // the category(s) this cvar should appear under + const idCVarHelp * next; // next statically declared cvar helper + + static idCVarHelp * staticCVarHelps; + static idCVarHelp * staticCVarHelpsTail; +}; + +ID_INLINE idCVarHelp::idCVarHelp( const idCVarHelp © ) { + cvarName = copy.cvarName; + friendlyName = copy.friendlyName; + choices = copy.choices; + values = copy.values; + category = copy.category; + next = NULL; +} + +// RAVEN END + +/* +=============================================================================== + + idCVarSystem + +=============================================================================== +*/ + +class idCVarSystem { +public: + virtual ~idCVarSystem( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual bool IsInitialized( void ) const = 0; + + // Registers a CVar. + virtual void Register( idCVar *cvar ) = 0; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + // Registers a CVarHelp. + virtual void Register( const idCVarHelp *cvarHelp ) = 0; + virtual idCVarHelp * GetHelps( cvarHelpCategory_t category ) = 0; +// RAVEN END + + // Finds the CVar with the given name. + // Returns NULL if there is no CVar with the given name. + virtual idCVar * Find( const char *name ) = 0; + + // Sets the value of a CVar by name. + virtual void SetCVarString( const char *name, const char *value, int flags = 0 ) = 0; + virtual void SetCVarBool( const char *name, const bool value, int flags = 0 ) = 0; + virtual void SetCVarInteger( const char *name, const int value, int flags = 0 ) = 0; + virtual void SetCVarFloat( const char *name, const float value, int flags = 0 ) = 0; + + // Gets the value of a CVar by name. + virtual const char * GetCVarString( const char *name ) const = 0; + virtual bool GetCVarBool( const char *name ) const = 0; + virtual int GetCVarInteger( const char *name ) const = 0; + virtual float GetCVarFloat( const char *name ) const = 0; + + // Called by the command system when argv(0) doesn't match a known command. + // Returns true if argv(0) is a variable reference and prints or changes the CVar. + virtual bool Command( const idCmdArgs &args ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + // Sets/gets/clears modified flags that tell what kind of CVars have changed. + virtual void SetModifiedFlags( int flags ) = 0; + virtual int GetModifiedFlags( void ) const = 0; + virtual void ClearModifiedFlags( int flags ) = 0; + + // Resets variables with one of the given flags set. + virtual void ResetFlaggedVariables( int flags ) = 0; + + // Removes auto-completion from the flagged variables. + virtual void RemoveFlaggedAutoCompletion( int flags ) = 0; + + // Writes variables with one of the given flags set to the given file. + virtual void WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const = 0; +// RAVEN BEGIN +// nrausch: memory cvar support + virtual unsigned int WriteFlaggedVariables( int flags, const char *setCmd, byte *buf, unsigned int bufSize ) const = 0; + virtual void ApplyFlaggedVariables( byte *buf, unsigned int bufSize ) = 0; + virtual idStr WriteFlaggedVariables( int flags ) const = 0; +// RAVEN END + + // Moves CVars to and from dictionaries. + virtual const idDict * MoveCVarsToDict( int flags ) const = 0; + virtual void SetCVarsFromDict( const idDict &dict ) = 0; +}; + +extern idCVarSystem * cvarSystem; + + +/* +=============================================================================== + + CVar Registration + + Each DLL using CVars has to declare a private copy of the static variable + idCVar::staticVars like this: idCVar * idCVar::staticVars = NULL; + Furthermore idCVar::RegisterStaticVars() has to be called after the + cvarSystem pointer is set when the DLL is first initialized. + +=============================================================================== +*/ + +ID_INLINE void idCVar::Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ) { + this->name = name; + this->value = value; + this->flags = flags; + this->description = description; + this->flags = flags | CVAR_STATIC; + this->valueMin = valueMin; + this->valueMax = valueMax; + this->valueStrings = valueStrings; + this->valueCompletion = valueCompletion; + this->integerValue = 0; + this->floatValue = 0.0f; + this->internalVar = this; + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + this->next = staticVars; + staticVars = this; + } else { + cvarSystem->Register( this ); + } +} + +ID_INLINE void idCVar::RegisterStaticVars( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + for ( idCVar *cvar = staticVars; cvar; cvar = cvar->next ) { + cvarSystem->Register( cvar ); + } + staticVars = (idCVar *)0xFFFFFFFF; + } +} + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +ID_INLINE idCVarHelp::idCVarHelp( const char *cvarName, const char *friendlyName, const char *choices, const char *values, cvarHelpCategory_t category ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + this->cvarName = cvarName; + this->friendlyName = friendlyName; + this->choices = choices; + this->values = values; + this->category = category; + this->next = NULL; + + if ( staticCVarHelps != (idCVarHelp *)0xFFFFFFFF ) { + if ( !staticCVarHelpsTail ) { + staticCVarHelps = this; + } else { + staticCVarHelpsTail->next = this; + } + staticCVarHelpsTail = this; + staticCVarHelpsTail->next = NULL; + } else { + cvarSystem->Register( this ); + } +} + +ID_INLINE void idCVarHelp::RegisterStatics( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + if ( staticCVarHelps != (idCVarHelp *)0xFFFFFFFF ) { + for ( const idCVarHelp *cvarHelp = staticCVarHelps; cvarHelp; cvarHelp = cvarHelp->next ) { + cvarSystem->Register( cvarHelp ); + } + staticCVarHelps = (idCVarHelp *)0xFFFFFFFF; + } +} +// RAVEN END + +#endif /* !__CVARSYSTEM_H__ */ diff --git a/source/framework/CmdSystem.h b/source/framework/CmdSystem.h new file mode 100644 index 0000000..3d7a969 --- /dev/null +++ b/source/framework/CmdSystem.h @@ -0,0 +1,216 @@ + +#ifndef __CMDSYSTEM_H__ +#define __CMDSYSTEM_H__ + +/* +=============================================================================== + + Console command execution and command text buffering. + + Any number of commands can be added in a frame from several different + sources. Most commands come from either key bindings or console line input, + but entire text files can be execed. + + Command execution takes a null terminated string, breaks it into tokens, + then searches for a command or variable that matches the first token. + +=============================================================================== +*/ + +// command flags +typedef enum { + CMD_FL_ALL = -1, + CMD_FL_CHEAT = BIT(0), // command is considered a cheat + CMD_FL_SYSTEM = BIT(1), // system command + CMD_FL_RENDERER = BIT(2), // renderer command + CMD_FL_SOUND = BIT(3), // sound command + CMD_FL_GAME = BIT(4), // game command + CMD_FL_TOOL = BIT(5) // tool command +} cmdFlags_t; + +// parameters for command buffer stuffing +typedef enum { + CMD_EXEC_NOW, // don't return until completed + CMD_EXEC_INSERT, // insert at current position, but don't run yet + CMD_EXEC_APPEND // add to end of the command buffer (normal case) +} cmdExecution_t; + +// command function +typedef void (*cmdFunction_t)( const idCmdArgs &args ); + +// argument completion function +typedef void (*argCompletion_t)( const idCmdArgs &args, void(*callback)( const char *s ) ); + + +class idCmdSystem { +public: + virtual ~idCmdSystem( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // Registers a command and the function to call for it. + virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ) = 0; + // Removes a command. + virtual void RemoveCommand( const char *cmdName ) = 0; + // Remove all commands with one of the flags set. + virtual void RemoveFlaggedCommands( int flags ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + // Adds command text to the command buffer, does not add a final \n + virtual void BufferCommandText( cmdExecution_t exec, const char *text ) = 0; + // Pulls off \n \r or ; terminated lines of text from the command buffer and + // executes the commands. Stops when the buffer is empty. + // Normally called once per frame, but may be explicitly invoked. + virtual void ExecuteCommandBuffer( void ) = 0; + + // Base for path/file auto-completion. + virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) = 0; + + virtual void ArgCompletion_Models( const idCmdArgs &args, void(*callback)( const char *s ), bool strogg, bool marine ) = 0; + + // Base for decl name auto-completion. + virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) = 0; + + // Adds to the command buffer in tokenized form ( CMD_EXEC_NOW or CMD_EXEC_APPEND only ) + virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) = 0; + + // Restore these cvars when the next reloadEngine is done + virtual void SetupCVarsForReloadEngine( const idDict &dict ) = 0; + // Setup a reloadEngine to happen on next command run, and give a command to execute after reload + virtual void SetupReloadEngine( const idCmdArgs &args ) = 0; + virtual bool PostReloadEngine( void ) = 0; + + // There is a cache of the last completion operation that may need to be cleared sometimes + virtual void ClearCompletion( void ) = 0; + + // Default argument completion functions. + static void ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +// RAVEN BEGIN +// mekberg: added + static void ArgCompletion_GuiName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// RAVEN END + + static void ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ForceModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ForceModelStrogg( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ForceModelMarine( const idCmdArgs &args, void(*callback)( const char *s ) ); +// RAVEN BEGIN +// nrausch: standalone video support + static void ArgCompletion_StandaloneVideoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// rjohnson: netdemo completion + static void ArgCompletion_NetDemoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// RAVEN END + static void ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +}; + +extern idCmdSystem * cmdSystem; + + +ID_INLINE void idCmdSystem::ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ) { + callback( va( "%s 0", args.Argv( 0 ) ) ); + callback( va( "%s 1", args.Argv( 0 ) ) ); +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = min; i <= max; i++ ) { + callback( va( "%s %d", args.Argv( 0 ), i ) ); + } +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = 0; strings[i]; i++ ) { + callback( va( "%s %s", args.Argv( 0 ), strings[i] ) ); + } +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_DeclName( args, callback, type ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, "", NULL ); +} + +// RAVEN BEGIN +// mekberg: added +ID_INLINE void idCmdSystem::ArgCompletion_GuiName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "guis/", false, ".gui", NULL ); +} +// RAVEN END + +ID_INLINE void idCmdSystem::ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "maps/", true, ".map", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "sound/", false, ".wav", ".ogg", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", false, ".tga", ".dds", ".jpg", ".pcx", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "video/", false, ".roq", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ForceModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, true, true ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ForceModelStrogg( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, true, false ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ForceModelMarine( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, false, true ); +} + +// RAVEN BEGIN +// nrausch: standalone video support +ID_INLINE void idCmdSystem::ArgCompletion_StandaloneVideoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "video/", false, ".wmv", NULL ); +} + +// rjohnson: netdemo completion +extern char netDemoExtension[16]; +ID_INLINE void idCmdSystem::ArgCompletion_NetDemoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "demos/", true, netDemoExtension, NULL ); +} +// RAVEN END + +ID_INLINE void idCmdSystem::ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, ".cfg", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "SaveGames/", true, ".save", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "demos/", true, ".demo", NULL ); +} + +#endif /* !__CMDSYSTEM_H__ */ diff --git a/source/framework/Common.h b/source/framework/Common.h new file mode 100644 index 0000000..c03a34f --- /dev/null +++ b/source/framework/Common.h @@ -0,0 +1,351 @@ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +/* +============================================================== + + Common + +============================================================== +*/ + + +// RAVEN BEGIN +// mekberg: added more save types +typedef enum { + ST_REGULAR, + ST_QUICK, + ST_AUTO, + ST_CHECKPOINT, +} saveType_t; +// RAVEN END + +typedef enum { + EDITOR_NONE = 0, + EDITOR_RADIANT = BIT(1), + EDITOR_GUI = BIT(2), + EDITOR_DEBUGGER = BIT(3), + EDITOR_SCRIPT = BIT(4), + EDITOR_LIGHT = BIT(5), + EDITOR_SOUND = BIT(6), + EDITOR_DECL = BIT(7), + EDITOR_AF = BIT(8), + EDITOR_PDA = BIT(9), + EDITOR_FX = BIT(10), + EDITOR_REVERB = BIT(11), + EDITOR_PLAYBACKS = BIT(12), + EDITOR_MODVIEW = BIT(13), + EDITOR_LOGVIEW = BIT(14), + EDITOR_ENTVIEW = BIT(15), + EDITOR_MATERIAL = BIT(16), + + // Just flags to prevent caching of unneeded assets + EDITOR_AAS = BIT(17), + EDITOR_RENDERBUMP = BIT(18), + EDITOR_SPAWN_GUI = BIT(19), + + // Specifies that a decl validation run is happening + EDITOR_DECL_VALIDATING = BIT(20), + + EDITOR_ALL = -1 +}; +// RAVEN END + +#define MAX_OUTPUT_HISTORY 16 + +#define STRTABLE_ID "#str_" +#define STRTABLE_ID_LENGTH 5 + +extern idCVar com_version; +extern idCVar com_skipRenderer; +extern idCVar com_asyncSound; +extern idCVar com_machineSpec; +extern idCVar com_purgeAll; +extern idCVar com_developer; +extern idCVar com_allowConsole; +extern idCVar com_speeds; +extern idCVar com_showFPS; +extern idCVar com_showMemoryUsage; +extern idCVar com_showAsyncStats; +extern idCVar com_showSoundDecoders; +extern idCVar com_makingBuild; +extern idCVar com_skipUltraQuality; +extern idCVar com_updateLoadSize; +extern idCVar com_videoRam; + +// RAVEN BEGIN +// ksergent: added bundler +extern idCVar com_Bundler; + +#ifndef _XENON +// nrausch: generate rdf's for xenon load screens +extern idCVar com_MakeLoadScreens; +#endif + +// rjohnson: added quick load +extern idCVar com_QuickLoad; +// rjohnson: added limits stuff +extern idCVar com_Limits; +// jsinger: added build for binary lexer +extern idCVar com_BinaryWrite; +extern idCVar com_BinaryRead; +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading of all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFIle; +#endif +// jshepard: warning suppresion +extern idCVar com_uniqueWarnings; +// amccarthy: show Mem_Alloc tag statistics +extern idCVar com_showMemAllocTags; +#if defined(_XENON) +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr bundles. +extern idCVar com_showXenTexCacheStats; +extern idCVar com_showXenHardwareTimers; +// ksergent: show thread usage. +extern idCVar com_showXenThreadUsage; +#endif // _XENON +extern idCVar sys_lang; +// RAVEN END + +extern int time_gameFrame; // game logic time +extern int time_gameDraw; // game present time +extern int time_frontend; // renderer frontend time +extern int time_backend; // renderer backend time +// RAVEN BEGIN +extern int time_waiting; // game logic time +// RAVEN END + +extern int com_frameTime; // time for the current frame in milliseconds +extern volatile int com_ticNumber; // 60 hz tics, incremented by async function + +// RAVEN BEGIN +// bdube: added timing dict +extern bool com_debugHudActive; // The debug hud is active in the game +// RAVEN END + +#ifdef _WINDOWS +const char DMAP_MSGID[] = "DMAPOutput"; +const char DMAP_DONE[] = "DMAPDone"; +#endif + +// RAVEN BEGIN +// bdube: forward declarations +class idInterpreter; +class idProgram; + +// converted to a class so the idStr gets constructed +class MemInfo { +public: + MemInfo( void ); + + idStr filebase; + + int total; + int assetTotals; + + // asset totals + int imageAssetsTotal; + int modelAssetsTotal; + int soundAssetsTotal; +// RAVEN BEGIN + int collAssetsTotal; + int animsAssetsTotal; + int aasAssetsTotal; + + int imageAssetsCount; + int modelAssetsCount; + int soundAssetsCount; + int collAssetsCount; + int animsAssetsCount; + int aasAssetsCount; +}; +// RAVEN END + +class idCommon { +public: + virtual ~idCommon( void ) {} + + // Initialize everything. + // if the OS allows, pass argc/argv directly (without executable name) + // otherwise pass the command line in a single string (without executable name) + virtual void Init( int argc, const char **argv, const char *cmdline ) = 0; + + // Shuts down everything. + virtual void Shutdown( void ) = 0; + + // Shuts down everything. + virtual void Quit( void ) = 0; + + // Returns true if common initialization is complete. + virtual bool IsInitialized( void ) const = 0; + + // Called repeatedly as the foreground thread for rendering and game logic. + virtual void Frame( void ) = 0; + + // Called repeatedly by blocking function calls with GUI interactivity. + virtual void GUIFrame( bool execCmd, bool network ) = 0; + + // Called 60 times a second from a background thread for sound mixing, + // and input generation. Not called until idCommon::Init() has completed. + virtual void Async( void ) = 0; + + // Checks for and removes command line "+set var arg" constructs. + // If match is NULL, all set commands will be executed, otherwise + // only a set with the exact name. Only used during startup. + // set once to clear the cvar from +set for early init code + virtual void StartupVariable( const char *match, bool once ) = 0; + +// RAVEN BEGIN + virtual int GetUserCmdHz( void ) const = 0; + + virtual int GetUserCmdMSec( void ) const = 0; + + // Returns com_frameTime - which is 0 if a command is added to the command line + virtual int GetFrameTime( void ) const = 0; + + // returns if the game is processing the last frame when it processes multiple frames + virtual bool IsRenderableGameFrame( void ) const = 0; + + virtual void SetRenderableGameFrame( bool in ) = 0; + + // returns the last message from common->Error + virtual const char *GetErrorMessage( void ) const = 0; + + // Initializes a tool with the given dictionary. + virtual void InitTool( const int tool, const idDict *dict ) = 0; + + // Returns true if an editor has focus + virtual bool IsToolActive( void ) const = 0; + + // Returns an interface to source control + virtual class rvISourceControl *GetSourceControl( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added the following method to initialize each of the memory heaps +#ifdef _RV_MEM_SYS_SUPPORT + virtual void InitHeaps( void ) = 0; // initializes each of the memory heaps for use + virtual void ShutdownHeaps( void ) = 0; // shuts down each of the memory heaps from further use +#endif +// RAVEN END + + // Activates or deactivates a tool. + virtual void ActivateTool( bool active ) = 0; + + // Writes the user's configuration to a file + virtual void WriteConfigToFile( const char *filename ) = 0; + + // Writes cvars with the given flags to a file. + virtual void WriteFlaggedCVarsToFile( const char *filename, int flags, const char *setCmd ) = 0; + +// RAVEN BEGIN +// bdube: new exports + // Modview thinks in the middle of a game frame + virtual void ModViewThink ( void ) = 0; + +// rjohnson: added option for guis to always think + virtual void RunAlwaysThinkGUIs ( int time ) = 0; + + // Debbugger hook to check if a breakpoint has been hit + virtual void DebuggerCheckBreakpoint ( idInterpreter* interpreter, idProgram* program, int instructionPointer ) = 0; + +// scork: need to test if validating to catch some model errors that would stop the validation and convert to warnings... + virtual bool DoingDeclValidation( void ) = 0; +// scork: guess + virtual void SetCrashReportAutoSendString( const char *psString ) = 0; + + virtual void LoadToolsDLL( void ) = 0; + virtual void UnloadToolsDLL( void ) = 0; +// RAVEN END + + // Begins redirection of console output to the given buffer. + virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ), bool rcon = false ) = 0; + + // Stops redirection of console output. + virtual void EndRedirect( void ) = 0; + + // Update the screen with every message printed. + virtual void SetRefreshOnPrint( bool set ) = 0; + + // Prints message to the console, which may cause a screen update if com_refreshOnPrint is set. + virtual void Printf( const char *fmt, ... )id_attribute((format(printf,2,3))) = 0; + + // Same as Printf, with a more usable API - Printf pipes to this. + virtual void VPrintf( const char *fmt, va_list arg ) = 0; + + // Prints message that only shows up if the "developer" cvar is set, + // and NEVER forces a screen update, which could cause reentrancy problems. + virtual void DPrintf( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Prints WARNING %s message and adds the warning message to a queue for printing later on. + virtual void Warning( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Prints WARNING %s message in yellow that only shows up if the "developer" cvar is set. + virtual void DWarning( const char *fmt, ...) id_attribute((format(printf,2,3))) = 0; + + // Prints all queued warnings. + virtual void PrintWarnings( void ) = 0; + + // Removes all queued warnings. + virtual void ClearWarnings( const char *reason ) = 0; + + // Issues a C++ throw. Normal errors just abort to the game loop, + // which is appropriate for media or dynamic logic errors. + virtual void Error( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Fatal errors quit all the way to a system dialog box, which is appropriate for + // static internal errors or cases where the system may be corrupted. + virtual void FatalError( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + +// RAVEN BEGIN + // Brings up notepad with the warnings generated while running the game + virtual void DumpWarnings( void ) = 0; + + // Returns the localised string of the token, of the token if it does not begin with #str_ + virtual const char * GetLocalizedString( const char *token, int langIndex = -1 ) = 0; + + // Returns the localised string at position 'index' + virtual const idLangKeyValue * GetLocalizedString( int index, int langIndex = -1 ) = 0; + + // Returns the number of languages the game found + virtual int GetNumLanguages( void ) const = 0; + + // Returns the number of strings in the English langdict + virtual int GetNumLocalizedStrings( void ) const = 0; + + // Returns the name of the language + virtual const char * GetLanguage( int index ) const = 0; + + // Returns whether the language has VO + virtual bool LanguageHasVO( int index ) const = 0; + + // Returns key bound to the command + virtual const char * KeysFromBinding( const char *bind ) = 0; + + // Returns the binding bound to the key + virtual const char * BindingFromKey( const char *key ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + +// mekberg: added + virtual int GetRModeForMachineSpec( int machineSpec ) const = 0; + virtual void SetDesiredMachineSpec( int machineSpec ) = 0; +// RAVEN END + + // returns true if we are currently executing an rcon operation + virtual bool IsRCon( void ) const = 0; +}; + +extern idCommon * common; + +#endif /* !__COMMON_H__ */ diff --git a/source/framework/DeclPDA.h b/source/framework/DeclPDA.h new file mode 100644 index 0000000..30dc106 --- /dev/null +++ b/source/framework/DeclPDA.h @@ -0,0 +1,142 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLPDA_H__ +#define __DECLPDA_H__ + +/* +=============================================================================== + + idDeclPDA + +=============================================================================== +*/ + + +class idDeclEmail : public idDecl { +public: + idDeclEmail() {} + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetFrom() const { return from; } + const char * GetBody() const { return text; } + const char * GetSubject() const { return subject; } + const char * GetDate() const { return date; } + const char * GetTo() const { return to; } + const char * GetImage() const { return image; } + +private: + idStr text; + idStr subject; + idStr date; + idStr to; + idStr from; + idStr image; +}; + + +class idDeclVideo : public idDecl { +public: + idDeclVideo() {}; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetRoq() const { return video; } + const char * GetWave() const { return audio; } + const char * GetVideoName() const { return videoName; } + const char * GetInfo() const { return info; } + const char * GetPreview() const { return preview; } + +private: + idStr preview; + idStr video; + idStr videoName; + idStr info; + idStr audio; +}; + + +class idDeclAudio : public idDecl { +public: + idDeclAudio() {}; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetAudioName() const { return audioName; } + const char * GetWave() const { return audio; } + const char * GetInfo() const { return info; } + const char * GetPreview() const { return preview; } + +private: + idStr audio; + idStr audioName; + idStr info; + idStr preview; +}; + + +class idDeclPDA : public idDecl { +public: + idDeclPDA() { originalEmails = originalVideos = 0; }; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + virtual void AddVideo( const char *name, bool unique = true ) const; + virtual void AddAudio( const char *name, bool unique = true ) const; + virtual void AddEmail( const char *name, bool unique = true ) const; + virtual void RemoveAddedEmailsAndVideos() const; + + virtual const int GetNumVideos() const; + virtual const int GetNumAudios() const; + virtual const int GetNumEmails() const; + virtual const idDeclVideo *GetVideoByIndex( int index ) const; + virtual const idDeclAudio *GetAudioByIndex( int index ) const; + virtual const idDeclEmail *GetEmailByIndex( int index ) const; + + virtual void SetSecurity( const char *sec ) const; + + const char * GetPdaName() const { return pdaName; } + const char * GetSecurity() const {return security; } + const char * GetFullName() const { return fullName; } + const char * GetIcon() const { return icon; } + const char * GetPost() const { return post; } + const char * GetID() const { return id; } + const char * GetTitle() const { return title; } + +private: + mutable idStrList videos; + mutable idStrList audios; + mutable idStrList emails; + idStr pdaName; + idStr fullName; + idStr icon; + idStr id; + idStr post; + idStr title; + mutable idStr security; + mutable int originalEmails; + mutable int originalVideos; +}; + +#endif /* !__DECLPDA_H__ */ diff --git a/source/framework/DeclPlayerModel.h b/source/framework/DeclPlayerModel.h new file mode 100644 index 0000000..6c11ddb --- /dev/null +++ b/source/framework/DeclPlayerModel.h @@ -0,0 +1,41 @@ +//---------------------------------------------------------------- +// DeclPlayerModel.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __DECLPLAYERMODEL_H__ +#define __DECLPLAYERMODEL_H__ + +/* +=============================================================================== + +rvDeclPlayerModel + +=============================================================================== +*/ + +class rvDeclPlayerModel : public idDecl { +public: + rvDeclPlayerModel(); + + idStr model; + idStr head; + idVec3 headOffset; + idStr uiHead; + idStr team; + idStr skin; + idStr description; + idDict sounds; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ); + + virtual bool RebuildTextSource( void ) { return( false ); } + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +}; + +#endif diff --git a/source/framework/File.h b/source/framework/File.h new file mode 100644 index 0000000..ee1d82a --- /dev/null +++ b/source/framework/File.h @@ -0,0 +1,366 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __FILE_H__ +#define __FILE_H__ + +/* +============================================================== + + File Streams. + +============================================================== +*/ + +// mode parm for Seek +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +class idFileSystemLocal; + +// RAVEN BEGIN +// jscott: made idFile a pure virtual interface +class idFile { +public: + virtual ~idFile( void ) {} + // Get the name of the file. + virtual const char * GetName( void ) = 0; + // Get the full file path. + virtual const char * GetFullPath( void ) = 0; + // Read data from the file to the buffer. + virtual int Read( void *buffer, int len ) = 0; + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ) = 0; + // Returns the length of the file. + virtual int Length( void ) = 0; + // Return a time value for reload operations. + virtual unsigned int Timestamp( void ) = 0; + // Returns offset in file. + virtual int Tell( void ) = 0; + // Forces flush on files being writting to. + virtual void ForceFlush( void ) = 0; + // Causes any buffered data to be written to the file. + virtual void Flush( void ) = 0; + // Seek on a file. + virtual int Seek( long offset, fsOrigin_t origin ) = 0; + // Go back to the beginning of the file. + virtual void Rewind( void ) = 0; + // Like fprintf. + virtual int Printf( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + // Like fprintf but with argument pointer + virtual int VPrintf( const char *fmt, va_list arg ) = 0; + // Write a string with high precision floating point numbers to the file. + virtual int WriteFloatString( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ) = 0; + virtual int ReadUnsignedInt( unsigned int &value ) = 0; + virtual int ReadShort( short &value ) = 0; + virtual int ReadUnsignedShort( unsigned short &value ) = 0; + virtual int ReadChar( char &value ) = 0; + virtual int ReadUnsignedChar( unsigned char &value ) = 0; + virtual int ReadFloat( float &value ) = 0; + virtual int ReadBool( bool &value ) = 0; + virtual int ReadString( idStr &string ) = 0; + virtual int ReadVec2( idVec2 &vec ) = 0; + virtual int ReadVec3( idVec3 &vec ) = 0; + virtual int ReadVec4( idVec4 &vec ) = 0; + virtual int ReadVec5( idVec5 &vec ) = 0; + virtual int ReadVec6( idVec6 &vec ) = 0; + virtual int ReadMat3( idMat3 &mat ) = 0; + virtual int ReadBounds( idBounds &bounds ) = 0; + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ) = 0; + virtual int WriteUnsignedInt( const unsigned int value ) = 0; + virtual int WriteShort( const short value ) = 0; + virtual int WriteUnsignedShort( unsigned short value ) = 0; + virtual int WriteChar( const char value ) = 0; + virtual int WriteUnsignedChar( const unsigned char value ) = 0; + virtual int WriteFloat( const float value ) = 0; + virtual int WriteBool( const bool value ) = 0; + virtual int WriteString( const char *string ) = 0; + virtual int WriteVec2( const idVec2 &vec ) = 0; + virtual int WriteVec3( const idVec3 &vec ) = 0; + virtual int WriteVec4( const idVec4 &vec ) = 0; + virtual int WriteVec5( const idVec5 &vec ) = 0; + virtual int WriteVec6( const idVec6 &vec ) = 0; + virtual int WriteMat3( const idMat3 &mat ) = 0; + virtual int WriteBounds( const idBounds &bounds ) = 0; + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ) = 0; + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ) = 0; + virtual void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) = 0; +}; + +class idFile_Common : public idFile +{ +public: + virtual ~idFile_Common( void ) {} + // Get the name of the file. + virtual const char * GetName( void ) { return( "" ); } + // Get the full file path. + virtual const char * GetFullPath( void ) { return( "" ); } + // Read data from the file to the buffer. + virtual int Read( void *buffer, int len ); + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ); + // Returns the length of the file. + virtual int Length( void ) { return( 0 ); } + // Return a time value for reload operations. + virtual unsigned int Timestamp( void ) { return( 0 ); } + // Returns offset in file. + virtual int Tell( void ) { return( 0 ); } + // Forces flush on files being writting to. + virtual void ForceFlush( void ) {} + // Causes any buffered data to be written to the file. + virtual void Flush( void ) {} + // Seek on a file. + virtual int Seek( long offset, fsOrigin_t origin ) { return( -1 ); } + // Go back to the beginning of the file. + virtual void Rewind( void ); + // Like fprintf. + virtual int Printf( const char *fmt, ... ) id_attribute((format(printf,2,3))); + // Like fprintf but with argument pointer + virtual int VPrintf( const char *fmt, va_list arg ); + // Write a string with high precision floating point numbers to the file. + virtual int WriteFloatString( const char *fmt, ... ) id_attribute((format(printf,2,3))); + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ); + virtual int ReadUnsignedInt( unsigned int &value ); + virtual int ReadShort( short &value ); + virtual int ReadUnsignedShort( unsigned short &value ); + virtual int ReadChar( char &value ); + virtual int ReadUnsignedChar( unsigned char &value ); + virtual int ReadFloat( float &value ); + virtual int ReadBool( bool &value ); + virtual int ReadString( idStr &string ); + virtual int ReadVec2( idVec2 &vec ); + virtual int ReadVec3( idVec3 &vec ); + virtual int ReadVec4( idVec4 &vec ); + virtual int ReadVec5( idVec5 &vec ); + virtual int ReadVec6( idVec6 &vec ); + virtual int ReadMat3( idMat3 &mat ); + virtual int ReadBounds( idBounds &bounds ); + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ); + virtual int WriteUnsignedInt( const unsigned int value ); + virtual int WriteShort( const short value ); + virtual int WriteUnsignedShort( unsigned short value ); + virtual int WriteChar( const char value ); + virtual int WriteUnsignedChar( const unsigned char value ); + virtual int WriteFloat( const float value ); + virtual int WriteBool( const bool value ); + virtual int WriteString( const char *string ); + virtual int WriteVec2( const idVec2 &vec ); + virtual int WriteVec3( const idVec3 &vec ); + virtual int WriteVec4( const idVec4 &vec ); + virtual int WriteVec5( const idVec5 &vec ); + virtual int WriteVec6( const idVec6 &vec ); + virtual int WriteMat3( const idMat3 &mat ); + virtual int WriteBounds( const idBounds &bounds ); + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ); + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ); + virtual void ReadSyncId( const char *detail = "unspecific", const char *classname = NULL ); +}; + +class idFile_Memory : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_Memory( void ); // file for writing without name + idFile_Memory( const char *name ); // file for writing + idFile_Memory( const char *name, char *data, int length ); // file for writing + idFile_Memory( const char *name, const char *data, int length ); // file for reading + virtual ~idFile_Memory( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + virtual void Rewind( void ); + + // changes memory file to read only + virtual void MakeReadOnly( void ); + // clear the file + virtual void Clear( bool freeMemory = true ); + // set data for reading + void SetData( const char *data, int length ); + // returns const pointer to the memory buffer + const char * GetDataPtr( void ) const { return filePtr; } + // set the file granularity + void SetGranularity( int g ) { assert( g > 0 ); granularity = g; } + +private: + idStr name; // name of the file + int mode; // open mode + int maxSize; // maximum size of file + int fileSize; // size of the file + int allocated; // allocated size + int granularity; // file granularity + char * filePtr; // buffer holding the file data + char * curPtr; // current read/write pointer +}; + + +// RAVEN BEGIN +class idFile_BitMsg : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_BitMsg( idBitMsg &msg ); + idFile_BitMsg( const idBitMsg &msg ); + virtual ~idFile_BitMsg( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file + int mode; // open mode + idBitMsg * msg; +}; + + +// RAVEN BEGIN +class idFile_Permanent : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_Permanent( void ); + virtual ~idFile_Permanent( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + + // returns file pointer + FILE * GetFilePtr( void ) { return o; } + +private: + idStr name; // relative path of the file - relative path + idStr fullPath; // full file path - OS path + int mode; // open mode + int fileSize; // size of the file + FILE * o; // file handle + bool handleSync; // true if written data is immediately flushed +}; + +class idFile_ASCII : public idFile_Permanent +{ + int inside; + +public: + idFile_ASCII( void ); + + virtual int Read( void *buffer, int len ); + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ); + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ); + virtual int ReadUnsignedInt( unsigned int &value ); + virtual int ReadShort( short &value ); + virtual int ReadUnsignedShort( unsigned short &value ); + virtual int ReadChar( char &value ); + virtual int ReadUnsignedChar( unsigned char &value ); + virtual int ReadFloat( float &value ); + virtual int ReadBool( bool &value ); + virtual int ReadString( idStr &string ); + virtual int ReadVec2( idVec2 &vec ); + virtual int ReadVec3( idVec3 &vec ); + virtual int ReadVec4( idVec4 &vec ); + virtual int ReadVec5( idVec5 &vec ); + virtual int ReadVec6( idVec6 &vec ); + virtual int ReadMat3( idMat3 &mat ); + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ); + virtual int WriteUnsignedInt( const unsigned int value ); + virtual int WriteShort( const short value ); + virtual int WriteUnsignedShort( unsigned short value ); + virtual int WriteChar( const char value ); + virtual int WriteUnsignedChar( const unsigned char value ); + virtual int WriteFloat( const float value ); + virtual int WriteBool( const bool value ); + virtual int WriteString( const char *string ); + virtual int WriteVec2( const idVec2 &vec ); + virtual int WriteVec3( const idVec3 &vec ); + virtual int WriteVec4( const idVec4 &vec ); + virtual int WriteVec5( const idVec5 &vec ); + virtual int WriteVec6( const idVec6 &vec ); + virtual int WriteMat3( const idMat3 &mat ); + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ); + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ); + virtual void ReadSyncId( const char *detail = "unspecific", const char *classname = NULL ); +}; + +// RAVEN BEGIN +class idFile_InZip : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_InZip( void ); + virtual ~idFile_InZip( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file in the pak + idStr fullPath; // full file path including pak file name + int zipFilePos; // zip file info position in pak + int fileSize; // size of the file + void * z; // unzip info +}; + +#endif /* !__FILE_H__ */ diff --git a/source/framework/FileSystem.h b/source/framework/FileSystem.h new file mode 100644 index 0000000..9a88638 --- /dev/null +++ b/source/framework/FileSystem.h @@ -0,0 +1,418 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __FILESYSTEM_H__ +#define __FILESYSTEM_H__ + +/* +=============================================================================== + + File System + + No stdio calls should be used by any part of the game, because of all sorts + of directory and separator char issues. Throughout the game a forward slash + should be used as a separator. The file system takes care of the conversion + to an OS specific separator. The file system treats all file and directory + names as case insensitive. + + The following cvars store paths used by the file system: + + "fs_basepath" path to local install, read-only + "fs_savepath" path to config, save game, etc. files, read & write + "fs_cdpath" path to cd, read-only + "fs_devpath" path to files created during development, read & write + + The base path for file saving can be set to "fs_savepath" or "fs_devpath". + +=============================================================================== +*/ + +static const unsigned FILE_NOT_FOUND_TIMESTAMP = 0xFFFFFFFF; +static const int MAX_PURE_PAKS = 128; +// master server can keep server updated with a list of allowed paks per OS +static const int MAX_GAMEPAK_PER_OS = 10; +static const int MAX_OSPATH = 256; + +// modes for OpenFileByMode. used as bit mask internally +typedef enum { + FS_READ = 0, + FS_WRITE = 1, + FS_APPEND = 2 +} fsMode_t; + +typedef enum { + PURE_OK, // we are good to connect as-is + PURE_RESTART, // restart required + PURE_MISSING, // pak files missing on the client + PURE_NODLL, // no DLL could be extracted + // PURE_DIFFERENT, // differing checksums +} fsPureReply_t; + +typedef enum { + DLTYPE_URL, + DLTYPE_FILE +} dlType_t; + +typedef enum { + DL_WAIT, // waiting in the list for beginning of the download + DL_INPROGRESS, // in progress + DL_DONE, // download completed, success + DL_ABORTING, // this one can be set during a download, it will force the next progress callback to abort - then will go to DL_FAILED + DL_FAILED +} dlStatus_t; + +typedef enum { + FILE_EXEC, + FILE_OPEN +} dlMime_t; + +typedef enum { + FIND_NO, + FIND_YES, + FIND_ADDON +} findFile_t; + +typedef struct urlDownload_s { + idStr url; + idStr urlAuth; + idStr referer; + char dlerror[ MAX_STRING_CHARS ]; + int dltotal; + int dlnow; + int dlstatus; + dlStatus_t status; +} urlDownload_t; + +typedef struct fileDownload_s { + int position; + int length; + void * buffer; +} fileDownload_t; + +typedef struct proxyDownload_s { + idStr proxyUrl; + idStr proxyAuth; +} proxyDownload_t; + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) +#include "Xtl.h" +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) + +// Define this to enable background streaming via Xenon's native file I/O +// routines (unbuffered reads). +#define _XENON_USE_NATIVE_FILEIO + +typedef struct backgroundDownload_s +{ + static const int MAX_SEGMENTS = 2; + + struct backgroundDownload_s *next; // Set by the fileSystem. + dlType_t opcode; + idFile * f; + int numSegments; + fileDownload_t file[MAX_SEGMENTS]; + urlDownload_t url; + volatile bool completed; + double ticksToLoad; // Used for performance profiling. + +public: + void Clear(void) + { + numSegments=0; + completed=false; + f=0; + } + + void AddSegment(int position, int length, void* buffer) + { + assert(position>0); + assert(length>0); + assert(buffer!=0); + assert(numSegments game pak checksum + virtual bool UpdateGamePakChecksums( void ) = 0; + // 0-terminated list of pak checksums + // if pureChecksums[ 0 ] == 0, all data sources will be allowed + // otherwise, only pak files that match one of the checksums will be checked for files + // with the sole exception of .cfg files. + // the function tries to configure pure mode from the paks already referenced and this new list + // it returns wether the switch was successfull, and sets the missing checksums + // use fs_debug 1 to verbose + virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum[ MAX_GAMEPAK_PER_OS ], int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum, int *restartGamePakChecksu ) = 0; + // fills a 0-terminated list of pak checksums for a client + // if OS is -1, give the current game pak checksum. if >= 0, lookup the game pak table (server only) + // indexes > 0 may be provided on servers when the master is giving additional game paks to let in + virtual void GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int gamePakChecksum[ MAX_GAMEPAK_PER_OS ] ) = 0; + // before doing a restart, force the pure list and the search order + // if the given checksum list can't be completely processed and set, will error out + virtual void SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ) = 0; + // equivalent to calling SetPureServerChecksums with an empty list + virtual void ClearPureChecksums( void ) = 0; + // get a mask of supported OSes. if not pure, returns -1 + virtual unsigned int GetOSMask( void ) = 0; + // Reads a complete file. + // Returns the length of the file, or -1 on failure. + // A null buffer will just return the file length without loading. + // A null timestamp will be ignored. + // As a quick check for existance. -1 length == not present. + // A 0 byte will always be appended at the end, so string ops are safe. + // The buffer should be considered read-only, because it may be cached for other uses. + virtual int ReadFile( const char *relativePath, void **buffer, unsigned *timestamp = NULL ) = 0; + // Frees the memory allocated by ReadFile. + virtual void FreeFile( void *buffer ) = 0; + // Writes a complete file, will create any needed subdirectories. + // Returns the length of the file, or -1 on failure. + virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" ) = 0; + // Removes the given file. +// RAVEN BEGIN +// rjohnson: can specify the path cvar + virtual void RemoveFile( const char *relativePath, const char *basePath = "fs_savepath" ) = 0; +// bdube: added method + // Removes the given file and returns the filesystem status of the removal + virtual int RemoveExplicitFile ( const char *OSPath ) = 0; + +// mekberg: is file loading allowed? + virtual void SetIsFileLoadingAllowed(bool mode) = 0; + +// dluetscher: returns file loading status + virtual bool GetIsFileLoadingAllowed() const = 0; + +// amccarthy: set the current asset log name. + virtual void SetAssetLogName(const char *logName) = 0; +// amccarthy: write out a list of all files loaded. + virtual void WriteAssetLog() = 0; +// jnewquist: clear list of all files loaded. + virtual void ClearAssetLog() = 0; +// jnewquist: Accessor for asset log name (with filter) + virtual const char* GetAssetLogName() = 0; + +// jscott: new functions for tools + virtual idFile * GetNewFileMemory( void ) = 0; + virtual idFile * GetNewFilePermanent( void ) = 0; + +// RAVEN END + // Opens a file for reading. + virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ) = 0; + // Opens a file for writing, will create any needed subdirectories. + virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath", bool ASCII = false ) = 0; + // Opens a file for writing at the end. + virtual idFile * OpenFileAppend( const char *filename, bool sync = false, const char *basePath = "fs_basepath" ) = 0; + // Opens a file for reading, writing, or appending depending on the value of mode. + virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode ) = 0; + // Opens a file for reading from a full OS path. + virtual idFile * OpenExplicitFileRead( const char *OSPath ) = 0; + // Opens a file for writing to a full OS path. + virtual idFile * OpenExplicitFileWrite( const char *OSPath ) = 0; + // Closes a file. + virtual void CloseFile( idFile *f ) = 0; + // Returns immediately, performing the read from a background thread. + virtual void BackgroundDownload( backgroundDownload_t *bgl ) = 0; + // resets the bytes read counter + virtual void ResetReadCount( void ) = 0; + // retrieves the current read count + virtual int GetReadCount( void ) = 0; + // adds to the read count + virtual void AddToReadCount( int c ) = 0; + // look for a dynamic module + virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum ) = 0; + // case sensitive filesystems use an internal directory cache + // the cache is cleared when calling OpenFileWrite and RemoveFile + // in some cases you may need to use this directly + virtual void ClearDirCache( void ) = 0; + + // lookup a relative path, return the size or 0 if not found + virtual int RelativeDownloadPathForChecksum( int checksum, char path[ MAX_STRING_CHARS ] ) = 0; + + // verify the file can be downloaded, lookup a relative path, return the size or 0 if not found + virtual int ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isGamePak ) = 0; + + // verify the file can be downloaded, lookup an absolute (OS) path, return the size or 0 if not found + virtual int ValidateDownloadPakForRelativePath( const char *relativePath, char path[ MAX_STRING_CHARS ], bool &isGamePakReturn ) = 0; + + virtual idFile * MakeTemporaryFile( void ) = 0; + + // make downloaded pak files known so pure negociation works next time + virtual int AddZipFile( const char *path ) = 0; + +// RAVEN BEGIN +// nrausch: explicit pak add/removal +#ifdef _XENON + virtual bool AddDownloadedPak( const char *path ) = 0; + virtual void RemoveDownloadedPak( const char *path ) = 0; + + virtual bool AddExplicitPak( const char *path ) = 0; + virtual void RemoveExplicitPak( const char *path ) = 0; + virtual bool IsPakLoaded( const char *path ) = 0; +#endif +// RAVEN END + + // look for a file in the loaded paks or the addon paks + // if the file is found in addons, FS's internal structures are ready for a reloadEngine + virtual findFile_t FindFile( const char *path ) = 0; + + // get map/addon decls and take into account addon paks that are not on the search list + // the decl 'name' is in the "path" entry of the dict + virtual int GetNumMaps() = 0; + virtual int GetMapDeclIndex( const char *mapName ) = 0; + virtual const idDict * GetMapDecl( int i ) = 0; + virtual const idDict * GetMapDecl( const char *mapName ) = 0; + virtual void FindMapScreenshot( const char *path, char *buf, int len ) = 0; + +// RAVEN BEGIN +// rjohnson: added new functions + // Converts a full OS path to an import path. + virtual bool OSpathToImportPath( const char *osPath, idStr &iPath, bool stripTemp = false ) = 0; + // Opens a file for reading from the fs_importpath directory + virtual idFile * OpenImportFileRead( const char *filename ) = 0; + // Copy a file + virtual void CopyOSFile( const char *fromOSPath, const char *toOSPath ) = 0; + virtual void CopyOSFile( idFile *src, const char *toOSPath ) = 0; +// RAVEN END + + // demo functions - only for use by the core ( and not DLL boundary / memory safe anyway ) + // ReadDemoHeader: returns -1 if a reloadEngine is requested before trying again, 0 if we failed with no backup, and 1 if we can continue + // if demo_enforceFS is 0, will always ret 1 + virtual void WriteDemoHeader( idFile *file ) = 0; + virtual int ReadDemoHeader( idFile *file ) = 0; + + // new in 1.3 + // indicates if the filesystem is currently running with pak files restrictions or addons + virtual bool IsRunningWithRestrictions( void ) = 0; + + // new in 1.4 + virtual void ReadCodePakLists( const idBitMsg &msg ) = 0; + virtual bool HaveCodePakLists( void ) const = 0; + + // pick best language - used by the core to pick a good default language based on present zpaks + virtual void SelectDefaultLanguage( void ) = 0; + + virtual void ClearAddonList( void ) = 0; +}; + +extern idFileSystem * fileSystem; + +#endif /* !__FILESYSTEM_H__ */ diff --git a/source/framework/UsercmdGen.h b/source/framework/UsercmdGen.h new file mode 100644 index 0000000..3875b9e --- /dev/null +++ b/source/framework/UsercmdGen.h @@ -0,0 +1,233 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __USERCMDGEN_H__ +#define __USERCMDGEN_H__ + +/* +=============================================================================== + + Samples a set of user commands from player input. + +=============================================================================== +*/ + +const int USERCMD_HZ_SP = 60; // 60 frames per second +const int USERCMD_MSEC_SP = 1000 / USERCMD_HZ_SP; + +//const int USERCMD_HZ_MP = 30; // 30 frames per second +//const int USERCMD_MSEC_MP = 1000 / USERCMD_HZ_MP; + +//const int USERCMD_HZ_MP = 60; // 60 frames per second +//const int USERCMD_MSEC_MP = 1000 / USERCMD_HZ_MP; + +// usercmd_t->button bits +const int BUTTON_ATTACK = BIT(0); +const int BUTTON_RUN = BIT(1); +const int BUTTON_ZOOM = BIT(2); +const int BUTTON_SCORES = BIT(3); +const int BUTTON_MLOOK = BIT(4); +// RAVEN BEGIN +// ddynerman: stats +const int BUTTON_INGAMESTATS = BIT(5); +// jscott: for voicechat +const int BUTTON_VOICECHAT = BIT(6); +// ddynerman: tourney display +const int BUTTON_TOURNEY = BIT(7); +// twhitaker: strafe +const int BUTTON_STRAFE = BIT(8); +// RAVEN END + + +// usercmd_t->impulse commands +const int IMPULSE_0 = 0; // weap 0 +const int IMPULSE_1 = 1; // weap 1 +const int IMPULSE_2 = 2; // weap 2 +const int IMPULSE_3 = 3; // weap 3 +const int IMPULSE_4 = 4; // weap 4 +const int IMPULSE_5 = 5; // weap 5 +const int IMPULSE_6 = 6; // weap 6 +const int IMPULSE_7 = 7; // weap 7 +const int IMPULSE_8 = 8; // weap 8 +const int IMPULSE_9 = 9; // weap 9 +const int IMPULSE_10 = 10; // weap 10 +const int IMPULSE_11 = 11; // weap 11 +const int IMPULSE_12 = 12; // weap 12 +const int IMPULSE_13 = 13; // weap reload +const int IMPULSE_14 = 14; // weap next +const int IMPULSE_15 = 15; // weap prev +const int IMPULSE_16 = 16; // +const int IMPULSE_17 = 17; // ready to play ( toggles ui_ready ) +const int IMPULSE_18 = 18; // center view +const int IMPULSE_19 = 19; // show PDA/INV/MAP +const int IMPULSE_20 = 20; // toggle team ( toggles ui_team ) +const int IMPULSE_21 = 21; // tourney toggle waiting room/spec +const int IMPULSE_22 = 22; // spectate +const int IMPULSE_23 = 23; // +const int IMPULSE_24 = 24; // +const int IMPULSE_25 = 25; // +const int IMPULSE_26 = 26; // +const int IMPULSE_27 = 27; // +const int IMPULSE_28 = 28; // vote yes +const int IMPULSE_29 = 29; // vote no +const int IMPULSE_40 = 40; // repeast last radio chatter + +// RAVEN BEGIN +// bdube: added flashlight +const int IMPULSE_50 = 50; // activate flashlight +const int IMPULSE_51 = 51; // switch to last weapon +// ddynerman: mp stats +const int IMPULSE_52 = 52; // mp statistics +// RAVEN END + + +// RAVEN BEGIN +// asalmon: impulses for weapons combos for xbox +#ifdef _XBOX +const int IMPULSE_70 = 70; //Weapon switch up +const int IMPULSE_71 = 71; //Weapon switch down +const int IMPULSE_72 = 72; //Weapon switch right +const int IMPULSE_73 = 73; //Weapon Switch left +#endif +//RAVEN END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +const int IMPULSE_100 = 100; // Buy weapon_shotgun +const int IMPULSE_101 = 101; // Buy weapon_machinegun +const int IMPULSE_102 = 102; // Buy weapon_hyperblaster +const int IMPULSE_103 = 103; // Buy weapon_grenadelauncher +const int IMPULSE_104 = 104; // Buy weapon_nailgun +const int IMPULSE_105 = 105; // Buy weapon_rocketlauncher +const int IMPULSE_106 = 106; // Buy weapon_railgun +const int IMPULSE_107 = 107; // Buy weapon_lightninggun +const int IMPULSE_108 = 108; // UNUSED +const int IMPULSE_109 = 109; // Buy weapon_napalmgun +const int IMPULSE_110 = 110; // Buy weapon_dmg +const int IMPULSE_111 = 111; // UNUSED +const int IMPULSE_112 = 112; // UNUSED +const int IMPULSE_113 = 113; // UNUSED +const int IMPULSE_114 = 114; // UNUSED +const int IMPULSE_115 = 115; // UNUSED +const int IMPULSE_116 = 116; // UNUSED +const int IMPULSE_117 = 117; // UNUSED +const int IMPULSE_118 = 118; // Buy item_armor_small +const int IMPULSE_119 = 119; // Buy item_armor_large +const int IMPULSE_120 = 120; // Buy ammorefill +const int IMPULSE_121 = 121; // UNUSED +const int IMPULSE_122 = 122; // UNUSED +const int IMPULSE_123 = 123; // Buy team powerup: ammo_regen +const int IMPULSE_124 = 124; // Buy team powerup: health_regen +const int IMPULSE_125 = 125; // Buy team powerup: damage_boost +const int IMPULSE_126 = 126; // UNUSED +const int IMPULSE_127 = 127; // UNUSED +// RITUAL END + + +const int KEY_MOVESPEED = 127; + +// usercmd_t->flags +const int UCF_IMPULSE_SEQUENCE = 0x0001; // toggled every time an impulse command is sent + +class usercmd_t { +public: + int gameFrame; // frame number + int gameTime; // game time + int realTime; // real game time + int duplicateCount; // duplication count for networking +// RAVEN BEGIN +// ddynerman: expand buttons to 2 bytes + short buttons; // buttons +// RAVEN END + signed char forwardmove; // forward/backward movement + signed char rightmove; // left/right movement + signed char upmove; // up/down movement + short angles[3]; // view angles + short mx; // mouse delta x + short my; // mouse delta y + signed char impulse; // impulse command + byte flags; // additional flags + int sequence; // just for debugging + +public: + void ByteSwap(); // on big endian systems, byte swap the shorts and ints + bool operator==( const usercmd_t &rhs ) const; +}; + +typedef enum { + INHIBIT_SESSION = 0, + INHIBIT_ASYNC +} inhibit_t; + +const int MAX_BUFFERED_USERCMD = 64; + +class idUsercmdGen { +public: + virtual ~idUsercmdGen( void ) {} + + // Sets up all the cvars and console commands. + virtual void Init( void ) = 0; + + // Prepares for a new map. + virtual void InitForNewMap( void ) = 0; + + // Shut down. + virtual void Shutdown( void ) = 0; + + // Clears all key states and face straight. + virtual void Clear( void ) = 0; + + // Clears view angles. + virtual void ClearAngles( void ) = 0; + + // When the console is down or the menu is up, only emit default usercmd, so the player isn't moving around. + // Each subsystem (session and game) may want an inhibit will OR the requests. + virtual void InhibitUsercmd( inhibit_t subsystem, bool inhibit ) = 0; + + // Returns a buffered command for the given game tic. + virtual usercmd_t TicCmd( int ticNumber ) = 0; + + // Called async at regular intervals. + virtual void UsercmdInterrupt( void ) = 0; + + // Set a value that can safely be referenced by UsercmdInterrupt() for each key binding. + virtual int CommandStringUsercmdData( const char *cmdString ) = 0; + + // Returns the number of user commands. + virtual int GetNumUserCommands( void ) = 0; + + // Returns the name of a user command via index. + virtual const char *GetUserCommandName( int index ) = 0; + + // Continuously modified, never reset. For full screen guis. + virtual void MouseState( int *x, int *y, int *button, bool *down ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + + // Directly sample a usercmd. + virtual usercmd_t GetUsercmd( void ) = 0; + +//RAVEN BEGIN +//asalmon: slow down the joystick movement for aim assist on xenon + virtual void SetSlowJoystick(int slow) = 0; + +// nrausch: Stuff an arbitrary impulse in, which will override any other impulse this cycle + virtual void StuffImpulse( int impulse ) = 0; + + virtual void StuffKey( const char *keyName, bool down ) = 0; + +#ifdef _XENON + virtual void GetInputs( int &y, int &p, int &r, int &f ) = 0; +#endif + +//RAVEN END +}; + +extern idUsercmdGen *usercmdGen; + +#endif /* !__USERCMDGEN_H__ */ diff --git a/source/framework/async/NetworkSystem.h b/source/framework/async/NetworkSystem.h new file mode 100644 index 0000000..3f51e60 --- /dev/null +++ b/source/framework/async/NetworkSystem.h @@ -0,0 +1,133 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __NETWORKSYSTEM_H__ +#define __NETWORKSYSTEM_H__ + + +/* +=============================================================================== + + Network System. + +=============================================================================== +*/ + +typedef struct { + idStr nickname; + idStr clan; + short ping; + int rate; +} scannedClient_t; + +typedef struct { + netadr_t adr; + idDict serverInfo; + int ping; + + int clients; + + int OSMask; + +//RAVEN BEGIN +// shouchard: added favorite flag + bool favorite; // true if this has been marked by a user as a favorite + bool dedicated; +// shouchard: added performance filtered flag + bool performanceFiltered; // true if the client machine is too wimpy to have good performance +//RAVEN END +} scannedServer_t; + +typedef enum { + SC_NONE = -1, + SC_FAVORITE, + SC_LOCKED, + SC_DEDICATED, + SC_PB, + SC_NAME, + SC_PING, + SC_REPEATER, + SC_PLAYERS, + SC_GAMETYPE, + SC_MAP, + SC_ALL, + NUM_SC +} sortColumn_t; + +typedef struct { + sortColumn_t column; + idList::cmp_t* compareFn; + idList::filter_t* filterFn; + const char* description; +} sortInfo_t; + +class idNetworkSystem { +public: + virtual ~idNetworkSystem( void ) {} + + virtual void Shutdown( void ); + + virtual void ServerSendReliableMessage( int clientNum, const idBitMsg &msg, bool inhibitRepeater = false ); + virtual void RepeaterSendReliableMessage( int clientNum, const idBitMsg &msg, bool inhibitHeader = false, int including = -1 ); + virtual void RepeaterSendReliableMessageExcluding( int excluding, const idBitMsg &msg, bool inhibitHeader = false, int clientNum = -1 ); // NOTE: Message is sent to all viewers if clientNum is -1; excluding is used for playback. + virtual void ServerSendReliableMessageExcluding( int clientNum, const idBitMsg &msg, bool inhibitRepeater = false ); + virtual int ServerGetClientPing( int clientNum ); + virtual int ServerGetClientTimeSinceLastPacket( int clientNum ); + virtual int ServerGetClientTimeSinceLastInput( int clientNum ); + virtual int ServerGetClientOutgoingRate( int clientNum ); + virtual int ServerGetClientIncomingRate( int clientNum ); + virtual float ServerGetClientIncomingPacketLoss( int clientNum ); + virtual int ServerGetClientNum( int clientId ); + virtual int ServerGetServerTime( void ); + + // returns the new clientNum or -1 if there weren't any free. + virtual int ServerConnectBot( void ); + + virtual int RepeaterGetClientNum( int clientId ); + + virtual void ClientSendReliableMessage( const idBitMsg &msg ); + virtual int ClientGetPrediction( void ); + virtual int ClientGetTimeSinceLastPacket( void ); + virtual int ClientGetOutgoingRate( void ); + virtual int ClientGetIncomingRate( void ); + virtual float ClientGetIncomingPacketLoss( void ); +// RAVEN BEGIN +// ddynerman: added some utility functions + // uses a static buffer, copy it before calling in game again + virtual const char* GetServerAddress( void ); + virtual const char* GetClientAddress( int clientNum ); + virtual void AddFriend( int clientNum ); + virtual void RemoveFriend( int clientNum ); + // for MP games + virtual void SetLoadingText( const char* loadingText ); + virtual void AddLoadingIcon( const char* icon ); + virtual const char* GetClientGUID( int clientNum ); +// RAVEN END + + virtual void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + + // server browser + virtual int GetNumScannedServers( void ); + virtual const scannedServer_t* GetScannedServerInfo( int serverNum ); + virtual const scannedClient_t* GetScannedServerClientInfo( int serverNum, int clientNum ); + virtual void AddSortFunction( const sortInfo_t &sortInfo ); + virtual bool RemoveSortFunction( const sortInfo_t &sortInfo ); + virtual void UseSortFunction( const sortInfo_t &sortInfo, bool use = true ); + virtual bool SortFunctionIsActive( const sortInfo_t &sortInfo ); + + // returns true if enabled + virtual bool HTTPEnable( bool enable ); + + virtual void ClientSetServerInfo( const idDict &serverSI ); + virtual void RepeaterSetInfo( const idDict &info ); + + virtual const char* GetViewerGUID( int clientNum ); + +private: + scannedServer_t scannedServer; + scannedClient_t scannedClient; +}; + +extern idNetworkSystem * networkSystem; + +#endif /* !__NETWORKSYSTEM_H__ */ diff --git a/source/framework/declAF.h b/source/framework/declAF.h new file mode 100644 index 0000000..c02fa88 --- /dev/null +++ b/source/framework/declAF.h @@ -0,0 +1,272 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLAF_H__ +#define __DECLAF_H__ + +/* +=============================================================================== + + Articulated Figure + +=============================================================================== +*/ + +class idDeclAF; + +typedef enum { + DECLAF_CONSTRAINT_INVALID, + DECLAF_CONSTRAINT_FIXED, + DECLAF_CONSTRAINT_BALLANDSOCKETJOINT, + DECLAF_CONSTRAINT_UNIVERSALJOINT, + DECLAF_CONSTRAINT_HINGE, + DECLAF_CONSTRAINT_SLIDER, + DECLAF_CONSTRAINT_SPRING +} declAFConstraintType_t; + +typedef enum { + DECLAF_JOINTMOD_AXIS, + DECLAF_JOINTMOD_ORIGIN, + DECLAF_JOINTMOD_BOTH +} declAFJointMod_t; + +typedef bool (*getJointTransform_t)( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idAFVector : public Serializable<'AFV '> { +public: + // Serialization methods + void Write(SerialOutputStream &stream) const; + void AddReferences() const; + idAFVector(SerialInputStream &stream); + + +#else +class idAFVector { +public: +#endif + enum idAFVectorType_t { +// RAVEN END + VEC_COORDS = 0, + VEC_JOINT, + VEC_BONECENTER, + VEC_BONEDIR + } type; + idStr joint1; + idStr joint2; + +public: + idAFVector( void ); + + bool Parse( idLexer &src ); + bool Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + bool Write( idFile *f ) const; + const char * ToString( idStr &str, const int precision = 8 ); + const idVec3 & ToVec3( void ) const { return vec; } + idVec3 & ToVec3( void ) { return vec; } + +private: + mutable idVec3 vec; + bool negate; +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF_Body : public Serializable<'DAFB'> { +public: + + // Serializable Methods + void Write( SerialOutputStream &stream ) const; + idDeclAF_Body( SerialInputStream &stream ); + void AddReferences() const; +#else +class idDeclAF_Body { +#endif +public: + idDeclAF_Body(); +// RAVEN END + idStr name; + idStr jointName; + declAFJointMod_t jointMod; + int modelType; + idAFVector v1, v2; + int numSides; + float width; + float density; + idAFVector origin; + idAngles angles; + int contents; + int clipMask; + bool selfCollision; + idMat3 inertiaScale; + float linearFriction; + float angularFriction; + float contactFriction; + idStr containedJoints; + idAFVector frictionDirection; + idAFVector contactMotorDirection; +public: + void SetDefault( const idDeclAF *file ); +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF_Constraint : public Serializable<'DAFC'> { +public: + // Serializable Methods + void Write( SerialOutputStream &stream ) const; + idDeclAF_Constraint( SerialInputStream &stream ); + void AddReferences() const; +#else +class idDeclAF_Constraint { +public: +#endif + idDeclAF_Constraint(); +// RAVEN END + idStr name; + idStr body1; + idStr body2; + declAFConstraintType_t type; + float friction; + float stretch; + float compress; + float damping; + float restLength; + float minLength; + float maxLength; + idAFVector anchor; + idAFVector anchor2; + idAFVector shaft[2]; + idAFVector axis; +// RAVEN BEGIN +// jsinger: added declAFLimitType_t to support serialization/deserialization of binary decls + enum declAFLimitType_t { + LIMIT_NONE = -1, + LIMIT_CONE, + LIMIT_PYRAMID + } limit; +// RAVEN END + idAFVector limitAxis; + float limitAngles[3]; + +public: + void SetDefault( const idDeclAF *file ); +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF : public idDecl, public Serializable<'DAF '> { +public: + idDeclAF( SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; + void AddReferences() const; +#else +class idDeclAF : public idDecl { +#endif +// RAVEN END + friend class idAFFileManager; +public: + idDeclAF( void ); + virtual ~idDeclAF( void ); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + virtual void Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + + bool Save( void ); + + void NewBody( const char *name ); + void RenameBody( const char *oldName, const char *newName ); + void DeleteBody( const char *name ); + + void NewConstraint( const char *name ); + void RenameConstraint( const char *oldName, const char *newName ); + void DeleteConstraint( const char *name ); + + static int ContentsFromString( const char *str ); + static const char * ContentsToString( const int contents, idStr &str ); + + static declAFJointMod_t JointModFromString( const char *str ); + static const char * JointModToString( declAFJointMod_t jointMod ); + +public: + bool modified; + idStr model; + idStr skin; + float defaultLinearFriction; + float defaultAngularFriction; + float defaultContactFriction; + float defaultConstraintFriction; + float totalMass; + idVec2 suspendVelocity; + idVec2 suspendAcceleration; + float noMoveTime; + float noMoveTranslation; + float noMoveRotation; + float minMoveTime; + float maxMoveTime; + int contents; + int clipMask; + bool selfCollision; +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; +// RAVEN END + idList bodies; + idList constraints; + +private: + bool ParseContents( idLexer &src, int &c ) const; + bool ParseBody( idLexer &src ); + bool ParseFixed( idLexer &src ); + bool ParseBallAndSocketJoint( idLexer &src ); + bool ParseUniversalJoint( idLexer &src ); + bool ParseHinge( idLexer &src ); + bool ParseSlider( idLexer &src ); + bool ParseSpring( idLexer &src ); + bool ParseSettings( idLexer &src ); + + bool WriteBody( idFile *f, const idDeclAF_Body &body ) const; + bool WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSettings( idFile *f ) const; + + bool RebuildTextSource( void ); +}; + +// RAVEN BEGIN +class rvDeclAFEdit +{ +public: + virtual ~rvDeclAFEdit() { } + virtual bool Save( idDeclAF *edit ) = 0; + virtual void NewBody( idDeclAF *edit, char const *name ) = 0; + virtual void RenameBody( idDeclAF *edit, char const *oldName, char const *newName ) = 0; + virtual void DeleteBody( idDeclAF *edit, char const *name ) = 0; + virtual void NewConstraint( idDeclAF *edit, char const *name ) = 0; + virtual void RenameConstraint( idDeclAF *edit, char const *oldName, char const *newName ) = 0; + virtual void DeleteConstraint( idDeclAF *edit, char const *name ) = 0; +}; + +extern rvDeclAFEdit *declAFEdit; +// RAVEN END + +#endif /* !__DECLAF_H__ */ diff --git a/source/framework/declEntityDef.h b/source/framework/declEntityDef.h new file mode 100644 index 0000000..54f9b84 --- /dev/null +++ b/source/framework/declEntityDef.h @@ -0,0 +1,46 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLENTITYDEF_H__ +#define __DECLENTITYDEF_H__ + +/* +=============================================================================== + + idDeclEntityDef + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclEntityDef : public idDecl, public Serializable<'DED '> { +public: + virtual void AddReferences() const; + virtual void Write(SerialOutputStream &stream) const; + idDeclEntityDef(SerialInputStream &stream); +#else +class idDeclEntityDef : public idDecl { +#endif +// RAVEN END +public: + idDeclEntityDef(); + idDict dict; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + +}; + +#endif /* !__DECLENTITYDEF_H__ */ diff --git a/source/framework/declLipSync.h b/source/framework/declLipSync.h new file mode 100644 index 0000000..21b1aff --- /dev/null +++ b/source/framework/declLipSync.h @@ -0,0 +1,74 @@ + +#ifndef __DECLLIPSYNC_H__ +#define __DECLLIPSYNC_H__ + +// A way of cross referencing strings and sound shaders +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class rvDeclLipSync : public idDecl, public Serializable<'RDLS'> +{ +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclLipSync( SerialInputStream &stream ); +#else +class rvDeclLipSync : public idDecl +{ +#endif +// RAVEN END +public: + rvDeclLipSync( void ) {} + ~rvDeclLipSync( void ) {} + + void SetDescription( const char *desc ) { mDescription = desc; } + const idStr &GetDescription( void ) const { return( mDescription ); } + + void SetHMM( idStr &hmm ) { mHMM = hmm; } + const idStr &GetHMM( void ) const { return( mHMM ); } + + void SetTranscribeText( const char *text ) { mTranscribeText = text; } + const char *GetTranscribeText( int langIndex = -1 ) const { return( common->GetLocalizedString( mTranscribeText, langIndex ) ); } + const char *GetRawTranscribeText( void ) const { return( mTranscribeText.c_str() ); } + + void SetLipSyncData( const char *lsd, const char *lang ); + const char *GetLipSyncData( int langIdx = -1 ) const; + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual bool RebuildTextSource( void ); + virtual size_t Size( void ) const; + + // scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; + +private: + idStr mDescription; + idStr mTranscribeText; + idStr mHMM; + idDict mLipSyncData; +}; + +ID_INLINE const char *rvDeclLipSync::GetLipSyncData( int langIdx ) const +{ + if( common->LanguageHasVO( langIdx ) ) + { + return( mLipSyncData.GetString( common->GetLanguage( langIdx ) ) ); + } + + return( mLipSyncData.GetString( "english" ) ); +} + +class rvDeclLipSyncEdit +{ +public: + virtual ~rvDeclLipSyncEdit() { } + virtual void SetLipSyncDescription( rvDeclLipSync *edit, const char *desc ) = 0; + virtual void SetLipSyncTranscribeText( rvDeclLipSync *edit, const char *text ) = 0; + virtual void SetLipSyncData( rvDeclLipSync *edit, const char *lsd, const char *lang ) = 0; +}; + +extern rvDeclLipSyncEdit *declLipSyncEdit; + +#endif // __DECLLIPSYNC_H__ diff --git a/source/framework/declManager.h b/source/framework/declManager.h new file mode 100644 index 0000000..e92262f --- /dev/null +++ b/source/framework/declManager.h @@ -0,0 +1,458 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLMANAGER_H__ +#define __DECLMANAGER_H__ + +/* +=============================================================================== + + Declaration Manager + + All "small text" data types, like materials, sound shaders, fx files, + entity defs, etc. are managed uniformly, allowing reloading, purging, + listing, printing, etc. All "large text" data types that never have more + than one declaration in a given file, like maps, models, AAS files, etc. + are not handled here. + + A decl will never, ever go away once it is created. The manager is + garranteed to always return the same decl pointer for a decl type/name + combination. The index of a decl in the per type list also stays the + same throughout the lifetime of the engine. Although the pointer to + a decl always stays the same, one should never maintain pointers to + data inside decls. The data stored in a decl is not garranteed to stay + the same for more than one engine frame. + + The decl indexes of explicitely defined decls are garrenteed to be + consistent based on the parsed decl files. However, the indexes of + implicit decls may be different based on the order in which levels + are loaded. + + The decl namespaces are separate for each type. Comments for decls go + above the text definition to keep them associated with the proper decl. + + During decl parsing, errors should never be issued, only warnings + followed by a call to MakeDefault(). + +=============================================================================== +*/ + +typedef enum { + DECL_TABLE = 0, + DECL_MATERIAL, + DECL_SKIN, + DECL_SOUND, + DECL_ENTITYDEF, + DECL_MODELDEF, +// RAVEN BEGIN +// jscott: added new decls + DECL_MATERIALTYPE, + DECL_LIPSYNC, + DECL_PLAYBACK, + DECL_EFFECT, +// rjohnson: camera is now contained in a def for frame commands + DECL_CAMERADEF, +// jscott: don't use these +// DECL_FX, +// DECL_PARTICLE, +// RAVEN END + DECL_AF, + DECL_PDA, + DECL_VIDEO, + DECL_AUDIO, + DECL_EMAIL, + DECL_MODELEXPORT, + DECL_MAPDEF, + + // new decl types can be added here + DECL_PLAYER_MODEL, + + DECL_MAX_TYPES = 32 +} declType_t; + +typedef enum { + DS_UNPARSED, + DS_DEFAULTED, // set if a parse failed due to an error, or the lack of any source + DS_PARSED +} declState_t; + +const int DECL_LEXER_FLAGS = LEXFL_NOSTRINGCONCAT | // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS | // no escape characters inside strings + LEXFL_ALLOWPATHNAMES | // allow path seperators in names + LEXFL_ALLOWMULTICHARLITERALS | // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT | // allow multiple strings seperated by '\' to be concatenated + LEXFL_NOFATALERRORS; // just set a flag instead of fatal erroring + + +class idDeclBase { +public: + virtual ~idDeclBase() {}; + virtual const char * GetName( void ) const = 0; + virtual declType_t GetType( void ) const = 0; + virtual declState_t GetState( void ) const = 0; + virtual bool IsImplicit( void ) const = 0; + virtual bool IsValid( void ) const = 0; + virtual void Invalidate( void ) = 0; + virtual void Reload( void ) = 0; + virtual void EnsureNotPurged( void ) = 0; + virtual int Index( void ) const = 0; + virtual int GetLineNum( void ) const = 0; + virtual const char * GetFileName( void ) const = 0; + virtual void GetText( char *text ) const = 0; + virtual int GetTextLength( void ) const = 0; +// RAVEN BEGIN + virtual int GetCompressedLength( void ) const = 0; +// RAVEN END + virtual void SetText( const char *text ) = 0; + virtual bool ReplaceSourceFileText( void ) = 0; + virtual bool SourceFileChanged( void ) const = 0; + virtual void MakeDefault( void ) = 0; + virtual bool EverReferenced( void ) const = 0; +// RAVEN BEGIN + virtual void SetReferencedThisLevel( void ) = 0; +// RAVEN END + virtual bool SetDefaultText( void ) = 0; + virtual const char * DefaultDefinition( void ) const = 0; + virtual bool Parse( const char *text, const int textLength, bool noCaching ) = 0; + virtual void FreeData( void ) = 0; + virtual size_t Size( void ) const = 0; + virtual void List( void ) const = 0; + virtual void Print( void ) const = 0; +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: Validation call for detailed error-reporting + virtual bool Validate( const char *psText, int iLength, idStr &strReportTo ) const = 0; +// RAVEN END +}; + +// RAVEN BEGIN +// jscott: for guides +#define MAX_GUIDE_PARMS 20 +#define MAX_GUIDE_SHADER_SIZE 20480 + +class rvDeclGuide +{ +private: + idStr mName; + idStr mParms[MAX_GUIDE_PARMS]; + idStr mDefinition; + int mNumParms; + +public: + rvDeclGuide( idStr &name ); + ~rvDeclGuide( void ); + + const char *GetName( void ) const { return( mName.c_str() ); } + int GetNumParms( void ) const { return( mNumParms ); } + const char *GetParm( int index ) const { assert( index < mNumParms ); return( mParms[index].c_str() ); } + + void SetParm( int index, const char *value ); + void RemoveOuterBracing( void ); + void Parse( idLexer *src ); + bool Evaluate( idLexer *src, idStr &definition ); +}; +// RAVEN END + +class idDecl { +public: + // The constructor should initialize variables such that + // an immediate call to FreeData() does no harm. + idDecl( void ) { base = NULL; } + virtual ~idDecl( void ) {}; + + // Returns the name of the decl. + const char * GetName( void ) const { return base->GetName(); } + + // Returns the decl type. + declType_t GetType( void ) const { return base->GetType(); } + + // Returns the decl state which is usefull for finding out if a decl defaulted. + declState_t GetState( void ) const { return base->GetState(); } + + // Returns true if the decl was defaulted or the text was created with a call to SetDefaultText. + bool IsImplicit( void ) const { return base->IsImplicit(); } + + // The only way non-manager code can have an invalid decl is if the *ByIndex() + // call was used with forceParse = false to walk the lists to look at names + // without touching the media. + bool IsValid( void ) const { return base->IsValid(); } + + // Sets state back to unparsed. + // Used by decl editors to undo any changes to the decl. + void Invalidate( void ) { base->Invalidate(); } + + // if a pointer might possible be stale from a previous level, + // call this to have it re-parsed + void EnsureNotPurged( void ) { base->EnsureNotPurged(); } + + // Returns the index in the per-type list. + int Index( void ) const { return base->Index(); } + + // Returns the line number the decl starts. + int GetLineNum( void ) const { return base->GetLineNum(); } + + // Returns the name of the file in which the decl is defined. + const char * GetFileName( void ) const { return base->GetFileName(); } + + // Returns the decl text. + void GetText( char *text ) const { base->GetText( text ); } + + // Returns the length of the decl text. + int GetTextLength( void ) const { return base->GetTextLength(); } + + // Returns the compressed length of the decl text. + int GetCompressedLength( void ) const { return( base->GetCompressedLength() ); } + + // Sets new decl text. + void SetText( const char *text ) { base->SetText( text ); } + + // Saves out new text for the decl. + // Used by decl editors to replace the decl text in the source file. + bool ReplaceSourceFileText( void ) { return base->ReplaceSourceFileText(); } + + // Returns true if the source file changed since it was loaded and parsed. + bool SourceFileChanged( void ) const { return base->SourceFileChanged(); } + + // Frees data and makes the decl a default. + void MakeDefault( void ) { base->MakeDefault(); } + + // Returns true if the decl was ever referenced. + bool EverReferenced( void ) const { return base->EverReferenced(); } + +public: + // Sets textSource to a default text if necessary. + // This may be overridden to provide a default definition based on the + // decl name. For instance materials may default to an implicit definition + // using a texture with the same name as the decl. + virtual bool SetDefaultText( void ) { return base->SetDefaultText(); } + + // Each declaration type must have a default string that it is guaranteed + // to parse acceptably. When a decl is not explicitly found, is purged, or + // has an error while parsing, MakeDefault() will do a FreeData(), then a + // Parse() with DefaultDefinition(). The defaultDefintion should start with + // an open brace and end with a close brace. + virtual const char * DefaultDefinition( void ) const { return base->DefaultDefinition(); } + + // The manager will have already parsed past the type, name and opening brace. + // All necessary media will be touched before return. + // The manager will have called FreeData() before issuing a Parse(). + // The subclass can call MakeDefault() internally at any point if + // there are parse errors. + virtual bool Parse( const char *text, const int textLength, bool noCaching ) { return base->Parse( text, textLength, noCaching ); } + + // Frees any pointers held by the subclass. This may be called before + // any Parse(), so the constructor must have set sane values. The decl will be + // invalid after issuing this call, but it will always be immediately followed + // by a Parse() + virtual void FreeData( void ) { base->FreeData(); } + + // Returns the size of the decl in memory. + virtual size_t Size( void ) const { return base->Size(); } + + // If this isn't overridden, it will just print the decl name. + // The manager will have printed 7 characters on the line already, + // containing the reference state and index number. + virtual void List( void ) const { base->List(); } + + // The print function will already have dumped the text source + // and common data, subclasses can override this to dump more + // explicit data. + virtual void Print( void ) const { base->Print(); } + +// RAVEN BEGIN + // Rebuilds the text source of the decl for saving + virtual bool RebuildTextSource( void ) { return( base->RebuildTextSource() ); } + + // Marks this decl as referenced this level + virtual void SetReferencedThisLevel( void ) { base->SetReferencedThisLevel(); } + +// scork: for detailed error reporting + virtual bool Validate( const char *psText, int iLength, idStr &strReportTo ) const { return base->Validate( psText, iLength, strReportTo ); } +// RAVEN END + +public: + idDeclBase * base; +}; + + +template< class type > +ID_INLINE idDecl *idDeclAllocator( void ) { + return new type; +} + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +template< class type > +ID_INLINE SerializableBase *idDeclStreamAllocator( SerialInputStream &stream ) { + type *ptr = new type(stream); + + return dynamic_cast(ptr); +} +#endif + + +class idMaterial; +class idDeclTable; +class idDeclSkin; +class idSoundShader; + +// RAVEN BEGIN +// jscott: new decl types +class rvDeclMatType; +class rvDeclLipSync; +class rvDeclPlayback; +class rvDeclEffect; +class rvDeclPlayerModel; +// RAVEN END + +class idDeclManager { +public: + virtual ~idDeclManager( void ) {} + + virtual void SetInsideLoad( bool var ) = 0; + virtual bool GetInsideLoad( void ) = 0; + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Reload( bool force ) = 0; + + virtual void BeginLevelLoad() = 0; + virtual void EndLevelLoad() = 0; + + // Registers a new decl type. +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ), SerializableBase *(*streamAllocator)( SerialInputStream &stream ) ) = 0; +#else + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ) ) = 0; +#endif +// jsinger: Added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE + virtual void StartLoadingDecls() = 0; + virtual void FinishLoadingDecls() = 0; + virtual void LoadDeclsFromFile() = 0; + virtual void WriteDeclFile() = 0; + virtual void FlushDecls() = 0; +#endif +// RAVEN END + +// RAVEN BEGIN +// jscott: for timing + // Registers a new folder with decl files. + virtual void RegisterDeclFolderWrapper( const char *folder, const char *extension, declType_t defaultType, bool unique = false, bool norecurse = false ) = 0; +// RAVEN END + + // Returns a checksum for all loaded decl text. + virtual int GetChecksum( void ) const = 0; + + // Returns the number of decl types. + virtual int GetNumDeclTypes( void ) const = 0; + + // Returns the type name for a decl type. + virtual const char * GetDeclNameFromType( declType_t type ) const = 0; + + // Returns the decl type for a type name. + virtual declType_t GetDeclTypeFromName( const char *typeName ) const = 0; + + // If makeDefault is true, a default decl of appropriate type will be created + // if an explicit one isn't found. If makeDefault is false, NULL will be returned + // if the decl wasn't explcitly defined. + virtual const idDecl * FindType( declType_t type, const char *name, bool makeDefault = true, bool noCaching = false ) = 0; + + virtual const idDecl* FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault = true ) = 0; + + virtual void ReloadFile( const char* filename, bool force ) = 0; + + // Returns the number of decls of the given type. + virtual int GetNumDecls( declType_t type ) = 0; + + // The complete lists of decls can be walked to populate editor browsers. + // If forceParse is set false, you can get the decl to check name / filename / etc. + // without causing it to parse the source and load media. + virtual const idDecl * DeclByIndex( declType_t type, int index, bool forceParse = true ) = 0; + + // List and print decls. + virtual void ListType( const idCmdArgs &args, declType_t type ) = 0; + virtual void PrintType( const idCmdArgs &args, declType_t type ) = 0; + + // Creates a new default decl of the given type with the given name in + // the given file used by editors to create a new decls. + virtual idDecl * CreateNewDecl( declType_t type, const char *name, const char *fileName ) = 0; + + // BSM - Added for the material editors rename capabilities + virtual bool RenameDecl( declType_t type, const char* oldName, const char* newName ) = 0; + + // When media files are loaded, a reference line can be printed at a + // proper indentation if decl_show is set + virtual void MediaPrint( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + virtual void WritePrecacheCommands( idFile *f ) = 0; + +// RAVEN BEGIN +// jscott: precache any guide (template) files + virtual void ParseGuides( void ) = 0; + virtual void ShutdownGuides( void ) = 0; + virtual bool EvaluateGuide( idStr &name, idLexer *src, idStr &definition ) = 0; + virtual bool EvaluateInlineGuide( idStr &name, idStr &definition ) = 0; +// RAVEN END + // Convenience functions for specific types. + virtual const idMaterial * FindMaterial( const char *name, bool makeDefault = true ) = 0; + virtual const idDeclTable * FindTable( const char *name, bool makeDefault = true ) = 0; + virtual const idDeclSkin * FindSkin( const char *name, bool makeDefault = true ) = 0; + virtual const idSoundShader * FindSound( const char *name, bool makeDefault = true ) = 0; +// RAVEN BEGIN +// jscott: for new Raven decls + virtual const rvDeclMatType * FindMaterialType( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclLipSync * FindLipSync( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclPlayback * FindPlayback( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclEffect * FindEffect( const char *name, bool makeDefault = true ) = 0; +// RAVEN END + + virtual const idMaterial * MaterialByIndex( int index, bool forceParse = true ) = 0; + virtual const idDeclTable * TableByIndex( int index, bool forceParse = true ) = 0; + virtual const idDeclSkin * SkinByIndex( int index, bool forceParse = true ) = 0; + virtual const idSoundShader * SoundByIndex( int index, bool forceParse = true ) = 0; +// RAVEN BEGIN +// jscott: for new Raven decls + virtual const rvDeclMatType * MaterialTypeByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclLipSync * LipSyncByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclPlayback * PlaybackByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclEffect * EffectByIndex( int index, bool forceParse = true ) = 0; + + virtual void StartPlaybackRecord( rvDeclPlayback *playback ) = 0; + virtual bool SetPlaybackData( rvDeclPlayback *playback, int now, int control, class rvDeclPlaybackData *pbd ) = 0; + virtual bool GetPlaybackData( const rvDeclPlayback *playback, int control, int now, int last, class rvDeclPlaybackData *pbd ) = 0; + virtual bool FinishPlayback( rvDeclPlayback *playback ) = 0; + + virtual idStr GetNewName( declType_t type, const char *base ) = 0; + virtual const char * GetDeclTypeName( declType_t type ) = 0; + virtual size_t ListDeclSummary( const idCmdArgs &args ) = 0; + virtual void RemoveDeclFile( const char *file ) = 0; +// scork: Validation call for detailed error-reporting + virtual bool Validate( declType_t type, int iIndex, idStr &strReportTo ) = 0; + virtual idDecl * AllocateDecl( declType_t type ) = 0; + +#if defined(_XENON) +// mwhitlock: Xenon texture streaming + virtual void SetLightMaterialList(idList* materialList) = 0; + virtual void SetEntityMaterialList(idList* materialList) = 0; + virtual void PurgeType( declType_t type ) = 0; +#endif +// RAVEN END +}; + +extern idDeclManager * declManager; + +template< declType_t type > +ID_INLINE void idListDecls_f( const idCmdArgs &args ) { + declManager->ListType( args, type ); +} + +template< declType_t type > +ID_INLINE void idPrintDecls_f( const idCmdArgs &args ) { + declManager->PrintType( args, type ); +} + +#endif /* !__DECLMANAGER_H__ */ diff --git a/source/framework/declMatType.h b/source/framework/declMatType.h new file mode 100644 index 0000000..17eb1ed --- /dev/null +++ b/source/framework/declMatType.h @@ -0,0 +1,53 @@ + +#ifndef __DECLMATTYPE_H__ +#define __DECLMATTYPE_H__ + +// Defines a material type - such as concrete, metal, glass etc +#ifdef RV_BINARYDECLS +class rvDeclMatType : public idDecl, public Serializable<'RDMT'> +{ +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclMatType( SerialInputStream &stream ); +#else +class rvDeclMatType : public idDecl +{ +public: +#endif + rvDeclMatType( void ) { *( ulong *)mTint = 0; } + ~rvDeclMatType( void ) {} + + void SetDescription( idStr &desc ) { mDescription = desc; } + const idStr &GetDescription( void ) const { return( mDescription ); } + + void SetTint( byte tint[4] ) { *( ulong *)mTint = *( ulong *)tint; } + int GetTint( void ) const { return( *( int *)mTint ); } + + float GetRed( void ) const { return( mTint[0] / 255.0f ); } + float GetGreen( void ) const { return( mTint[1] / 255.0f ); } + float GetBlue( void ) const { return( mTint[2] / 255.0f ); } + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual size_t Size( void ) const; + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + +private: + + idStr mDescription; + byte mTint[4]; +}; + +byte *MT_GetMaterialTypeArray( idStr image, int &width, int &height ); + +#endif // __DECLMATTYPE_H__ diff --git a/source/framework/declPlayback.h b/source/framework/declPlayback.h new file mode 100644 index 0000000..a3f03ee --- /dev/null +++ b/source/framework/declPlayback.h @@ -0,0 +1,185 @@ + +#ifndef __DECLPLAYBACK_H__ +#define __DECLPLAYBACK_H__ + +// WARNING: These must stay mirrored in both Q4Monster.state +#define PBFL_GET_POSITION BIT( 0 ) // Stored in playback file +#define PBFL_GET_ANGLES BIT( 1 ) +#define PBFL_GET_BUTTONS BIT( 2 ) + +#define PBFL_GET_VELOCITY BIT( 4 ) // Derived from data +#define PBFL_GET_ACCELERATION BIT( 5 ) +#define PBFL_GET_ANGLES_FROM_VEL BIT( 6 ) + +#define PBFL_AT_DEST BIT( 7 ) +#define PBFL_RELATIVE_POSITION BIT( 8 ) + +#define PBFL_ED_MODIFIED BIT( 29 ) +#define PBFL_ED_NEW BIT( 30 ) +#define PBFL_ED_CHECKEDIN BIT( 31 ) + +#define PBFL_ED_MASK ( PBFL_ED_MODIFIED | PBFL_ED_NEW | PBFL_ED_CHECKEDIN ) + +#define PBCB_NONE 0 +#define PBCB_BUTTON_DOWN 1 +#define PBCB_BUTTON_UP 2 +#define PBCB_IMPULSE 3 + +typedef void ( *pbCallback_t )( int type, float time, const void *data ); + +class rvDeclPlaybackData +{ +public: + void Init( void ) { entity = NULL; Callback = NULL; position.Zero(); velocity.Zero(); acceleration.Zero(); angles.Zero(); changed = 0; button = 0; impulse = 0; } + + void SetPosition( const idVec3 &pos ) { position = pos; } + void SetVelocity( const idVec3 &vel ) { velocity = vel; } + void SetAcceleration( const idVec3 &accel ) { acceleration = accel; } + void SetAngles( const idAngles &ang ) { angles = ang; } + void SetChanged( const byte chg ) { changed = chg; } + void SetButtons( const byte btn ) { button = btn; } + void SetImpulse( const byte imp ) { impulse = imp; } + + const idVec3 &GetPosition( void ) const { return( position ); } + const idVec3 &GetVelocity( void ) const { return( velocity ); } + const idVec3 &GetAcceleration( void ) const { return( acceleration ); } + const idAngles &GetAngles( void ) const { return( angles ); } + byte GetChanged( void ) const { return( changed ); } + byte GetButtons( void ) const { return( button ); } + byte GetImpulse( void ) const { return( impulse ); } + + class idEntity *GetEntity( void ) const { return( entity ); } + + void SetCallback( class idEntity *ent, pbCallback_t cb ) { entity = ent; Callback = cb; } + void CallCallback( int type, float time ) { if( Callback ) { Callback( type, time, this ); } } +private: + class idEntity *entity; + pbCallback_t Callback; + + idVec3 position; + idVec3 velocity; + idVec3 acceleration; + idAngles angles; + byte changed; + byte button; + byte impulse; +}; + +class rvButtonState +{ +public: + void Init( float t, byte b = 0, byte i = 0 ) { time = t; state = b; impulse = i; } + + float time; + byte state; + byte impulse; +}; + +#ifdef RV_BINARYDECLS +class rvDeclPlayback : public idDecl, public Serializable<'RDP '> +{ +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclPlayback( SerialInputStream &stream ); +#else +class rvDeclPlayback : public idDecl +{ +#endif +public: + rvDeclPlayback( void ); + ~rvDeclPlayback( void ); + + void SetFlag( bool on, int flag ) { on ? flags |= flag : flags &= ~flag; } + + bool GetHasPositions( void ) const { return( !!( flags & PBFL_GET_POSITION ) ); } + bool GetHasAngles( void ) const { return( !!( flags & PBFL_GET_ANGLES ) ); } + bool GetHasButtons( void ) const { return( !!( flags & PBFL_GET_BUTTONS ) ); } + bool GetEditorModified( void ) const { return( !!( flags & PBFL_ED_MODIFIED ) ); } + bool GetEditorNew( void ) const { return( !!( flags & PBFL_ED_NEW ) ); } + bool GetEditorCheckedIn( void ) const { return( !!( flags & PBFL_ED_CHECKEDIN ) ); } + + void SetHasPositions( bool pos ) { SetFlag( pos, PBFL_GET_POSITION ); } + void SetHasAngles( bool ang ) { SetFlag( ang, PBFL_GET_ANGLES ); } + void SetHasButtons( bool btn ) { SetFlag( btn, PBFL_GET_BUTTONS ); } + void SetEditorModified( bool em ) { SetFlag( em, PBFL_ED_MODIFIED ); } + void SetEditorNew( bool en ) { SetFlag( en, PBFL_ED_NEW ); } + void SetEditorCheckedIn( bool eci ) { SetFlag( eci, PBFL_ED_CHECKEDIN ); } + + int GetFlags( void ) const { return( flags ); } + void SetFlags( int in ) { flags = in; } + + float GetFrameRate( void ) const { return( frameRate ); } + void SetFrameRate( float in ) { frameRate = in; } + + idVec3 GetOrigin( void ) const { return( origin ); } + void SetOrigin( idVec3 &in ) { origin = in; } + + float GetDuration( void ) const { return( duration ); } + void SetDuration( float dur ) { duration = dur; } + + idBounds GetBounds( void ) const { return( bounds ); } + + void ParseSample( idLexer *src, idVec3 &pos, idAngles &ang ); + void WriteData( idFile_Memory &f ); + void WriteButtons( idFile_Memory &f ); + void WriteSequence( idFile_Memory &f ); + + bool ParseData( idLexer *src ); + void ParseButton( idLexer *src, byte &button, rvButtonState &state ); + bool ParseSequence( idLexer *src ); + bool ParseButtons( idLexer *src ); + + void Copy( rvDeclPlayback *pb ); + void SetOrigin( void ); + void Start( void ); + bool Finish( float desiredDuration = -1.0f ); + + bool SetCurrentData( float localTime, int control, rvDeclPlaybackData *pbd ); + bool GetCurrentOffset( float localTime, idVec3 &pos ) const; + bool GetCurrentAngles( float localTime, idAngles &ang ) const; + bool GetCurrentData( int control, float localTime, float lastTime, rvDeclPlaybackData *pbd ) const; + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual bool RebuildTextSource( void ); + virtual size_t Size( void ) const; + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + + idCurve_UniformCubicBSpline &GetPoints( void ) { return( points ); } + idCurve_UniformCubicBSpline &GetAngles( void ) { return( angles ); } + idList &GetButtons( void ) { return( buttons ); } + +private: + + int flags; + float frameRate; + float duration; + idVec3 origin; + idBounds bounds; + + idCurve_UniformCubicBSpline points; + idCurve_UniformCubicBSpline angles; + idList buttons; +}; + +class rvDeclPlaybackEdit +{ +public: + virtual ~rvDeclPlaybackEdit() {} + virtual bool Finish( rvDeclPlayback *edit, float desiredDuration ) = 0; + virtual void SetOrigin( rvDeclPlayback *edit ) = 0; + virtual void SetOrigin( rvDeclPlayback *edit, idVec3 &origin ) = 0; + virtual void Copy( rvDeclPlayback *edit, rvDeclPlayback *copy ) = 0; +}; + +extern rvDeclPlaybackEdit *declPlaybackEdit; + +#endif // __DECLPLAYBACK_H__ diff --git a/source/framework/declSkin.h b/source/framework/declSkin.h new file mode 100644 index 0000000..a1927b3 --- /dev/null +++ b/source/framework/declSkin.h @@ -0,0 +1,74 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLSKIN_H__ +#define __DECLSKIN_H__ + +/* +=============================================================================== + + idDeclSkin + +=============================================================================== +*/ + +typedef struct { + const idMaterial * from; // 0 == any unmatched shader + const idMaterial * to; +} skinMapping_t; + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclSkin : public idDecl, public Serializable<'DSKN'> { +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream) const; + virtual void AddReferences() const; + idDeclSkin(SerialInputStream &stream); +#else +class idDeclSkin : public idDecl { +#endif +public: + idDeclSkin(); + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + void StreamAllSkinTargets(bool inBackground); + void GetSkinTargetsList(idList& outList) const; +#endif +// RAVEN END + const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const; + + // model associations are just for the preview dialog in the editor +// RAVEN BEGIN +// jscott: inlined for access from tools dll + const int GetNumModelAssociations() const { return( associatedModels.Num() ); } +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: validation member for more detailed error-checks + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + const char * GetAssociatedModel( int index ) const; + +private: + idList mappings; + idStrList associatedModels; +}; + +// RAVEN BEGIN +// jscott: inlined for access from tools dll +ID_INLINE const char *idDeclSkin::GetAssociatedModel( int index ) const { + if ( index >= 0 && index < associatedModels.Num() ) { + return associatedModels[ index ]; + } + return ""; +} +// RAVEN END + +#endif /* !__DECLSKIN_H__ */ diff --git a/source/framework/declTable.h b/source/framework/declTable.h new file mode 100644 index 0000000..894f223 --- /dev/null +++ b/source/framework/declTable.h @@ -0,0 +1,58 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLTABLE_H__ +#define __DECLTABLE_H__ + +/* +=============================================================================== + + tables are used to map a floating point input value to a floating point + output value, with optional wrap / clamp and interpolation + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclTable : public idDecl, public Serializable<'DTAB'> { +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream) const; + virtual void AddReferences() const; + idDeclTable( SerialInputStream &stream); +#else +class idDeclTable : public idDecl { +#endif +public: + idDeclTable(); + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: for BSE + float GetMaxValue( void ) const { return( maxValue ); } + float GetMinValue( void ) const { return( minValue ); } +// bdube: made virtual so it can be accessed in game + virtual float TableLookup( float index ) const; +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + +private: + bool clamp; + bool snap; +// RAVEN BEGIN +// jscott: for BSE + float minValue; + float maxValue; +// RAVEN END + idList values; +}; + +#endif /* !__DECLTABLE_H__ */ diff --git a/source/framework/licensee.h b/source/framework/licensee.h new file mode 100644 index 0000000..e97c794 --- /dev/null +++ b/source/framework/licensee.h @@ -0,0 +1,128 @@ + +/* +=============================================================================== + + Definitions for information that is related to a licensee's game name and location. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: this is the name of the game we are making +#define GAME_NAME "Quake4" // appears on window titles and errors +#define GAME_ICON "q4icon.bmp" + +// jnewquist: build type +#if defined(_DEBUG) +#define GAME_BUILD_TYPE "Debug" +#elif defined(_MPBETA) +#define GAME_BUILD_TYPE "MPBeta" +#elif defined(_FINAL) +#define GAME_BUILD_TYPE "" +#elif defined(_RELEASE) +#define GAME_BUILD_TYPE "" +#endif + +// paths +#define CD_BASEDIR "Quake4" +#define BASE_GAMEDIR "q4base" +#define BASE_MPGAMEDIR "q4mp" +#define DEMO_GAMEDIR "demo" + +// filenames +#define CD_EXE "Quake4.exe" + +#ifdef _XENON +#define CONFIG_FILE "save:/Quake4Config.cfg" +#else +#define CONFIG_FILE "Quake4Config.cfg" +#endif + +// base folder where the source code lives +#define SOURCE_CODE_BASE_FOLDER "code" + +#define DEVELOPER_DOMAIN "ravensoft.com" +// RAVEN END + + +// RAVEN BEGIN +// rjohnson: changed the host to our temp address +// default idnet host address +#ifndef IDNET_HOST + #define IDNET_HOST "q4master.idsoftware.com" +#endif +// RAVEN END + +// default idnet master port +#ifndef IDNET_MASTER_PORT + #define IDNET_MASTER_PORT "27650" +#endif + +#ifndef MOTD_HOST + #define MOTD_HOST "q4m-test.ravensoft.com" +#endif + +#ifndef MOTD_PORT + #define MOTD_PORT "27700" +#endif + +// default network server port +#ifndef PORT_SERVER +//RAVEN BEGIN +#define PORT_SERVER 28004 +//RAVEN END +#endif + +// Q4TV default network repeater port +#ifndef PORT_REPEATER +#define PORT_REPEATER 28104 +#endif + +#ifndef PORT_HTTP +#define PORT_HTTP 28004 +#endif + +// broadcast scan this many ports after PORT_SERVER so a single machine can run multiple servers +#define NUM_SERVER_PORTS 4 + +// see ASYNC_PROTOCOL_VERSION +// use a different major for each game +// RAVEN BEGIN +// ddynerman: rev ASYNC_PROTOCOL_MAJOR to 2 for Quake 4 +#define ASYNC_PROTOCOL_MAJOR 2 +// RAVEN END + +// Savegame Version +// Update when you can no longer maintain compatibility with previous savegames. +// For testing, we're using the build number to ensure no one ever tries to load a stale savegame +#define SAVEGAME_VERSION VERSION_BUILD_NUMBER + +// editor info +#define EDITOR_WINDOWTEXT "QuakeEdit" + +// win32 info +#define WIN32_CONSOLE_CLASS "Quake 4 WinConsole" +#define WIN32_SPLASH_CLASS "Quake 4 Splash" +#define WIN32_WINDOW_CLASS_NAME "Quake4" +#define WIN32_FAKE_WINDOW_CLASS_NAME "QUAKE4_WGL_FAKE" + +#ifdef __linux__ + #define DEFAULT_BASE_PATH "/usr/local/games/quake4" +#elif defined( MACOS_X ) + #define DEFAULT_BASE_PATH "/Applications/Quake4" +#endif + +// CD Key file info +#define CDKEY_FILE "quake4key" +#define CDKEY_TEXT "\n// Do not give this file to ANYONE.\n" \ + "// id Software, Raven Software or Activision will NOT ask you to send this file to them.\n" + +// FIXME: Update to Doom +// Product ID. Stored in "productid.txt". +// This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena +#undef PRODUCT_ID +#define PRODUCT_ID 220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 250, 169, 104, 231, 231, 21, 201, 170, 208, 135, 175, 130, 136, 85, 215, 71, 23, 96, 32, 96, 83, 44, 240, 219, 138, 184, 215, 73, 27, 196, 247, 55, 139, 148, 68, 78, 203, 213, 238, 139, 23, 45, 205, 118, 186, 236, 230, 231, 107, 212, 1, 10, 98, 30, 20, 116, 180, 216, 248, 166, 35, 45, 22, 215, 229, 35, 116, 250, 167, 117, 3, 57, 55, 201, 229, 218, 222, 128, 12, 141, 149, 32, 110, 168, 215, 184, 53, 31, 147, 62, 12, 138, 67, 132, 54, 125, 6, 221, 148, 140, 4, 21, 44, 198, 3, 126, 12, 100, 236, 61, 42, 44, 251, 15, 135, 14, 134, 89, 92, 177, 246, 152, 106, 124, 78, 118, 80, 28, 42 +#undef PRODUCT_ID_LENGTH +#define PRODUCT_ID_LENGTH 152 + +#define CONFIG_SPEC "config.spec" diff --git a/source/game.vcproj b/source/game.vcproj new file mode 100644 index 0000000..677ec6b --- /dev/null +++ b/source/game.vcproj @@ -0,0 +1,1115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/game/AF.cpp b/source/game/AF.cpp new file mode 100644 index 0000000..0619c82 --- /dev/null +++ b/source/game/AF.cpp @@ -0,0 +1,1336 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ +#define ARTICULATED_FIGURE_ANIM "af_pose" +#define POSE_BOUNDS_EXPANSION 5.0f + +/* +================ +idAF::idAF +================ +*/ +idAF::idAF( void ) { + self = NULL; + animator = NULL; + modifiedAnim = 0; + baseOrigin.Zero(); + baseAxis.Identity(); + poseTime = -1; + restStartTime = -1; + isLoaded = false; + isActive = false; + hasBindConstraints = false; +} + +/* +================ +idAF::~idAF +================ +*/ +idAF::~idAF( void ) { +} + +/* +================ +idAF::Save +================ +*/ +void idAF::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + savefile->WriteString( GetName() ); + savefile->WriteBool( hasBindConstraints ); + savefile->WriteVec3( baseOrigin ); + savefile->WriteMat3( baseAxis ); + savefile->WriteInt( poseTime ); + savefile->WriteInt( restStartTime ); + savefile->WriteBool( isLoaded ); + savefile->WriteBool( isActive ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idAF::Restore +================ +*/ +void idAF::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( name ); + savefile->ReadBool( hasBindConstraints ); + savefile->ReadVec3( baseOrigin ); + savefile->ReadMat3( baseAxis ); + savefile->ReadInt( poseTime ); + savefile->ReadInt( restStartTime ); + savefile->ReadBool( isLoaded ); + savefile->ReadBool( isActive ); + + animator = NULL; + modifiedAnim = 0; + + if ( self ) { + SetAnimator( self->GetAnimator() ); + Load( self, name ); + if ( hasBindConstraints ) { + AddBindConstraints(); + } + } + + savefile->ReadStaticObject( physicsObj ); + + if ( self ) { + if ( isActive ) { + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + + // switch to articulated figure physics + self->RestorePhysics( &physicsObj ); + physicsObj.EnableClip(); + } + UpdateAnimation(); + } +} + +/* +================ +idAF::UpdateAnimation +================ +*/ +bool idAF::UpdateAnimation( void ) { + int i; + idVec3 origin, renderOrigin, bodyOrigin; + idMat3 axis, renderAxis, bodyAxis; + renderEntity_t *renderEntity; + + if ( !IsLoaded() ) { + return false; + } + + if ( !IsActive() ) { + return false; + } + + renderEntity = self->GetRenderEntity(); + if ( !renderEntity ) { + return false; + } + + if ( physicsObj.IsAtRest() ) { + if ( restStartTime == physicsObj.GetRestStartTime() ) { + return false; + } + restStartTime = physicsObj.GetRestStartTime(); + } + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // create an animation frame which reflects the current pose of the articulated figure + animator->InitAFPose(); + for ( i = 0; i < jointMods.Num(); i++ ) { + // check for the origin joint + if ( jointMods[i].jointHandle == 0 ) { + continue; + } + bodyOrigin = physicsObj.GetOrigin( jointMods[i].bodyId ); + bodyAxis = physicsObj.GetAxis( jointMods[i].bodyId ); + axis = jointMods[i].jointBodyAxis.Transpose() * ( bodyAxis * renderAxis.Transpose() ); + origin = ( bodyOrigin - jointMods[i].jointBodyOrigin * axis - renderOrigin ) * renderAxis.Transpose(); + animator->SetAFPoseJointMod( jointMods[i].jointHandle, jointMods[i].jointMod, axis, origin ); + } + animator->FinishAFPose( modifiedAnim, GetBounds().Expand( POSE_BOUNDS_EXPANSION ), gameLocal.time ); + animator->SetAFPoseBlendWeight( 1.0f ); + + return true; +} + +/* +================ +idAF::GetBounds + + returns bounds for the current pose +================ +*/ +idBounds idAF::GetBounds( void ) const { + int i; + idAFBody *body; + idVec3 origin, entityOrigin; + idMat3 axis, entityAxis; + idBounds bounds, b; + + bounds.Clear(); + + // get model base transform + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + + entityAxis = baseAxis.Transpose() * axis; + entityOrigin = origin - baseOrigin * entityAxis; + + // get bounds relative to base + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + origin = ( body->GetWorldOrigin() - entityOrigin ) * entityAxis.Transpose(); + axis = body->GetWorldAxis() * entityAxis.Transpose(); + b.FromTransformedBounds( body->GetClipModel()->GetBounds(), origin, axis ); + + bounds += b; + } + + return bounds; +} + +/* +================ +idAF::SetupPose + + Transforms the articulated figure to match the current animation pose of the given entity. +================ +*/ +void idAF::SetupPose( idEntity *ent, int time ) { + int i; + idAFBody *body; + idVec3 origin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + } + + if ( isActive ) { + physicsObj.UpdateClipModels(); + } +} + +/* +================ +idAF::ChangePose + + Change the articulated figure to match the current animation pose of the given entity + and set the velocity relative to the previous pose. +================ +*/ +void idAF::ChangePose( idEntity *ent, int time ) { + int i; + float invDelta; + idAFBody *body; + idVec3 origin, lastOrigin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + invDelta = 1.0f / MS2SEC( time - poseTime ); + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + lastOrigin = body->GetWorldOrigin(); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + body->SetLinearVelocity( ( body->GetWorldOrigin() - lastOrigin ) * invDelta ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::EntitiesTouchingAF +================ +*/ +int idAF::EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const { + int i, j, numClipModels; + idAFBody *body; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + int numTouching; + + if ( !IsLoaded() ) { + return 0; + } + + numTouching = 0; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( self, physicsObj.GetAbsBounds(), -1, clipModels, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + for ( j = 0; j < numClipModels; j++ ) { + cm = clipModels[j]; + + if ( !cm || cm->GetEntity() == self ) { + continue; + } + + if ( !cm->IsTraceModel() ) { + continue; + } + + if ( !body->GetClipModel()->GetAbsBounds().IntersectsBounds( cm->GetAbsBounds() ) ) { + continue; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.ContentsModel( self, body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), -1, cm->GetCollisionModel(), cm->GetOrigin(), cm->GetAxis() ) ) { +// RAVEN END + touchList[ numTouching ].touchedByBody = body; + touchList[ numTouching ].touchedClipModel = cm; + touchList[ numTouching ].touchedEnt = cm->GetEntity(); + numTouching++; + clipModels[j] = NULL; + } + } + } + + return numTouching; +} + +/* +================ +idAF::BodyForClipModelId +================ +*/ +int idAF::BodyForClipModelId( int id ) const { + if ( id >= 0 ) { + return id; + } else { + id = CLIPMODEL_ID_TO_JOINT_HANDLE( id ); + if ( id < jointBody.Num() ) { + return jointBody[id]; + } else { + return 0; + } + } +} + +/* +================ +idAF::GetPhysicsToVisualTransform +================ +*/ +void idAF::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const { + origin = - baseOrigin; + axis = baseAxis.Transpose(); +} + +/* +================ +idAF::GetImpactInfo +================ +*/ +void idAF::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + SetupPose( self, gameLocal.time ); + physicsObj.GetImpactInfo( BodyForClipModelId( id ), point, info ); +} + +/* +================ +idAF::ApplyImpulse +================ +*/ +void idAF::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + SetupPose( self, gameLocal.time ); + physicsObj.ApplyImpulse( BodyForClipModelId( id ), point, impulse ); +} + +/* +================ +idAF::AddForce +================ +*/ +void idAF::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + SetupPose( self, gameLocal.time ); + physicsObj.AddForce( BodyForClipModelId( id ), point, force ); +} + +/* +================ +idAF::AddBody + + Adds a body. +================ +*/ +void idAF::AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ) { + int index; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + handle = animator->GetJointHandle( jointName ); + if ( handle == INVALID_JOINT ) { + gameLocal.Error( "idAF for entity '%s' at (%s) modifies unknown joint '%s'", self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), jointName ); + } + + assert( handle < animator->NumJoints() ); + origin = joints[ handle ].ToVec3(); + axis = joints[ handle ].ToMat3(); + + index = jointMods.Num(); + jointMods.SetNum( index + 1, false ); + jointMods[index].bodyId = physicsObj.GetBodyId( body ); + jointMods[index].jointHandle = handle; + jointMods[index].jointMod = mod; + jointMods[index].jointBodyOrigin = ( body->GetWorldOrigin() - origin ) * axis.Transpose(); + jointMods[index].jointBodyAxis = body->GetWorldAxis() * axis.Transpose(); +} + +/* +================ +idAF::SetBase + + Sets the base body. +================ +*/ +void idAF::SetBase( idAFBody *body, const idJointMat *joints ) { + physicsObj.ForceBodyId( body, 0 ); + baseOrigin = body->GetWorldOrigin(); + baseAxis = body->GetWorldAxis(); + AddBody( body, joints, animator->GetJointName( animator->GetFirstChild( "origin" ) ), AF_JOINTMOD_AXIS ); +} + +/* +================ +idAF::LoadBody +================ +*/ +bool idAF::LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ) { + int id, i; + float length, mass; + idTraceModel trm; + idClipModel *clip; + idAFBody *body; + idMat3 axis, inertiaTensor; + idVec3 centerOfMass, origin; + idBounds bounds; + idList jointList; + + origin = fb->origin.ToVec3(); + axis = fb->angles.ToMat3(); + bounds[0] = fb->v1.ToVec3(); + bounds[1] = fb->v2.ToVec3(); + + switch( fb->modelType ) { + case TRM_BOX: { + trm.SetupBox( bounds ); + break; + } + case TRM_OCTAHEDRON: { + trm.SetupOctahedron( bounds ); + break; + } + case TRM_DODECAHEDRON: { + trm.SetupDodecahedron( bounds ); + break; + } + case TRM_CYLINDER: { + trm.SetupCylinder( bounds, fb->numSides ); + break; + } + case TRM_CONE: { + // place the apex at the origin + bounds[0].z -= bounds[1].z; + bounds[1].z = 0.0f; + trm.SetupCone( bounds, fb->numSides ); + break; + } + case TRM_BONE: { + // direction of bone + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + length = axis[2].Normalize(); + // axis of bone trace model + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + // create bone trace model + trm.SetupBone( length, fb->width ); + break; + } + default: + assert( 0 ); + break; + } + + trm.GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + trm.Translate( -centerOfMass ); + origin += centerOfMass * axis; + + body = physicsObj.GetBody( fb->name ); + if ( body ) { + clip = body->GetClipModel(); + if ( !clip->IsEqual( trm ) ) { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body->SetClipModel( clip ); + } + clip->SetContents( fb->contents ); + body->SetDensity( fb->density, fb->inertiaScale ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( axis ); + id = physicsObj.GetBodyId( body ); + } + else { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body = new idAFBody( fb->name, clip, fb->density ); + if ( fb->inertiaScale != mat3_identity ) { + body->SetDensity( fb->density, fb->inertiaScale ); + } + id = physicsObj.AddBody( body ); + } + if ( fb->linearFriction != -1.0f ) { + body->SetFriction( fb->linearFriction, fb->angularFriction, fb->contactFriction ); + } + body->SetClipMask( fb->clipMask ); + body->SetSelfCollision( fb->selfCollision ); + + if ( fb->jointName == "origin" ) { + SetBase( body, joints ); + } else { + AFJointModType_t mod; + if ( fb->jointMod == DECLAF_JOINTMOD_AXIS ) { + mod = AF_JOINTMOD_AXIS; + } else if ( fb->jointMod == DECLAF_JOINTMOD_ORIGIN ) { + mod = AF_JOINTMOD_ORIGIN; + } else if ( fb->jointMod == DECLAF_JOINTMOD_BOTH ) { + mod = AF_JOINTMOD_BOTH; + } else { + mod = AF_JOINTMOD_AXIS; + } + AddBody( body, joints, fb->jointName, mod ); + } + + if ( fb->frictionDirection.ToVec3() != vec3_origin ) { + body->SetFrictionDirection( fb->frictionDirection.ToVec3() ); + } + if ( fb->contactMotorDirection.ToVec3() != vec3_origin ) { + body->SetContactMotorDirection( fb->contactMotorDirection.ToVec3() ); + } + + // update table to find the nearest articulated figure body for a joint of the skeletal model + animator->GetJointList( fb->containedJoints, jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + if ( jointBody[ jointList[ i ] ] != -1 ) { + +// RAVEN BEGIN +// kfuller: better load time warning for joints contained by multiple bodies + gameLocal.Warning( "%s: body '%s': joint '%s' is already contained by body '%s'", + name.c_str(), fb->name.c_str(), + animator->GetJointName( (jointHandle_t)jointList[i] ), + physicsObj.GetBody( jointBody[ jointList[ i ] ] )->GetName().c_str() ); +// RAVEN END + } + jointBody[ jointList[ i ] ] = id; + } + + return true; +} + +/* +================ +idAF::LoadConstraint +================ +*/ +bool idAF::LoadConstraint( const idDeclAF_Constraint *fc ) { + idAFBody *body1, *body2; + + body1 = physicsObj.GetBody( fc->body1 ); + body2 = physicsObj.GetBody( fc->body2 ); + + switch( fc->type ) { + case DECLAF_CONSTRAINT_FIXED: { + idAFConstraint_Fixed *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Fixed( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + break; + } + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_BallAndSocketJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0], fc->shaft[0].ToVec3() ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1], fc->shaft[0].ToVec3() ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_UniversalJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetShafts( fc->shaft[0].ToVec3(), fc->shaft[1].ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0] ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1] ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_HINGE: { + idAFConstraint_Hinge *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Hinge( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetAxis( fc->axis.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + idVec3 left, up, axis, shaft; + fc->axis.ToVec3().OrthogonalBasis( left, up ); + axis = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[0] ); + shaft = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[2] ); + c->SetLimit( axis, fc->limitAngles[1], shaft ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_SLIDER: { + idAFConstraint_Slider *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Slider( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAxis( fc->axis.ToVec3() ); + break; + } + case DECLAF_CONSTRAINT_SPRING: { + idAFConstraint_Spring *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Spring( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3(), fc->anchor2.ToVec3() ); + c->SetSpring( fc->stretch, fc->compress, fc->damping, fc->restLength ); + c->SetLimit( fc->minLength, fc->maxLength ); + break; + } + } + return true; +} + +/* +================ +GetJointTransform +================ +*/ +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + jointHandle_t joint; + + joint = reinterpret_cast(model)->GetJointHandle( jointName ); + if ( ( joint >= 0 ) && ( joint < reinterpret_cast(model)->NumJoints() ) ) { + origin = frame[ joint ].ToVec3(); + axis = frame[ joint ].ToMat3(); + return true; + } else { + return false; + } +} + +/* +================ +idAF::Load +================ +*/ +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one +bool idAF::Load( idEntity *ent, const char *fileName, bool purgeAF /* = false */ ) { +// RAVEN END + int i, j; + const idDeclAF *file; + const idDeclModelDef *modelDef; + idRenderModel *model; + int numJoints; + idJointMat *joints; + + assert( ent ); + + self = ent; + physicsObj.SetSelf( self ); + + if ( animator == NULL ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s): NULL animator\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + name = fileName; + name.StripFileExtension(); + + file = static_cast( declManager->FindType( DECL_AF, name ) ); + if ( !file ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s)\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + if ( file->bodies.Num() == 0 || file->bodies[0]->jointName != "origin" ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no body which modifies the origin joint.", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + modelDef = animator->ModelDef(); + if ( modelDef == NULL || modelDef->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted modelDef '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), modelDef ? modelDef->GetName() : "" ); + return false; + } + + model = animator->ModelHandle(); + if ( model == NULL || model->IsDefaultModel() ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted model '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), model ? model->Name() : "" ); + return false; + } + + // get the modified animation + modifiedAnim = animator->GetAnim( ARTICULATED_FIGURE_ANIM ); + if ( !modifiedAnim ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no modified animation '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), ARTICULATED_FIGURE_ANIM ); + return false; + } + + // create the animation frame used to setup the articulated figure + numJoints = animator->NumJoints(); + joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + gameEdit->ANIM_CreateAnimFrame( model, animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset(), animator->RemoveOrigin() ); + + // set all vector positions from model joints + file->Finish( GetJointTransform, joints, animator ); + + // initialize articulated figure physics + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( file->clipMask ); + physicsObj.SetDefaultFriction( file->defaultLinearFriction, file->defaultAngularFriction, file->defaultContactFriction ); + physicsObj.SetSuspendSpeed( file->suspendVelocity, file->suspendAcceleration ); + physicsObj.SetSuspendTolerance( file->noMoveTime, file->noMoveTranslation, file->noMoveRotation ); + physicsObj.SetSuspendTime( file->minMoveTime, file->maxMoveTime ); + physicsObj.SetSelfCollision( file->selfCollision ); +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + physicsObj.SetFastEval( file->fastEval ); +// RAVEN END + + // clear the list with transforms from joints to bodies + jointMods.SetNum( 0, false ); + + // clear the joint to body conversion list + jointBody.AssureSize( animator->NumJoints() ); + for ( i = 0; i < jointBody.Num(); i++ ) { + jointBody[i] = -1; + } + +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + // delete any bodies in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + physicsObj.DeleteBody( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + idAFBody *body = physicsObj.GetBody( i ); + for ( j = 0; j < file->bodies.Num(); j++ ) { + if ( file->bodies[j]->name.Icmp( body->GetName() ) == 0 ) { + break; + } + } + if ( j >= file->bodies.Num() ) { + physicsObj.DeleteBody( i ); + i--; + } + } + } + + // delete any constraints in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + idAFConstraint *constraint = physicsObj.GetConstraint( i ); + for ( j = 0; j < file->constraints.Num(); j++ ) { + if ( file->constraints[j]->name.Icmp( constraint->GetName() ) == 0 && + file->constraints[j]->type == constraint->GetType() ) { + break; + } + } + if ( j >= file->constraints.Num() ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } + } +// RAVEN END + // load bodies from the file + for ( i = 0; i < file->bodies.Num(); i++ ) { + LoadBody( file->bodies[i], joints ); + } + + // load constraints from the file + for ( i = 0; i < file->constraints.Num(); i++ ) { + LoadConstraint( file->constraints[i] ); + } + + physicsObj.UpdateClipModels(); + + // check if each joint is contained by a body + for( i = 0; i < animator->NumJoints(); i++ ) { + if ( jointBody[i] == -1 ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) joint '%s' is not contained by a body", + name.c_str(), self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), animator->GetJointName( (jointHandle_t)i ) ); + } + } + + physicsObj.SetMass( file->totalMass ); + physicsObj.SetChanged(); + + // disable the articulated figure for collision detection until activated + physicsObj.DisableClip(); + + isLoaded = true; + + poseTime = -1; + + return true; +} + +/* +================ +idAF::Start +================ +*/ +void idAF::Start( void ) { + if ( !IsLoaded() ) { + return; + } + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + // switch to articulated figure physics + self->SetPhysics( &physicsObj ); + // start the articulated figure physics simulation + physicsObj.EnableClip(); + + physicsObj.Activate(); + isActive = true; +} + +/* +================ +idAF::TestSolid +================ +*/ +bool idAF::TestSolid( void ) const { + int i; + idAFBody *body; + trace_t trace; + idStr str; + bool solid; + + if ( !IsLoaded() ) { + return false; + } + + if ( !af_testSolid.GetBool() ) { + return false; + } + + solid = false; + + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + body = physicsObj.GetBody( i ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Translation( self, trace, body->GetWorldOrigin(), body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), body->GetClipMask(), self ) ) { +// RAVEN END + float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); + + body->SetWorldOrigin( body->GetWorldOrigin() + trace.c.normal * ( depth + 8.0f ) ); + + gameLocal.DWarning( "%s: body '%s' stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", self->name.c_str(), + body->GetName().c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); + solid = true; + + } + } + return solid; +} + +/* +================ +idAF::StartFromCurrentPose +================ +*/ +void idAF::StartFromCurrentPose( int inheritVelocityTime ) { + if ( !IsLoaded() ) { + return; + } + + // if the ragdoll should inherit velocity from the animation + if ( inheritVelocityTime > 0 ) { + + // make sure the ragdoll is at rest + physicsObj.PutToRest(); + + // set the pose for some time back + SetupPose( self, gameLocal.time - inheritVelocityTime ); + + // change the pose for the current time and set velocities + ChangePose( self, gameLocal.time ); + } + else { + // transform the articulated figure to reflect the current animation pose + SetupPose( self, gameLocal.time ); + } + + physicsObj.UpdateClipModels(); + + TestSolid(); + + Start(); + + UpdateAnimation(); + + // update the render entity origin and axis + self->UpdateModel(); + + // make sure the renderer gets the updated origin and axis + self->Present(); +} + +/* +================ +idAF::Stop +================ +*/ +void idAF::Stop( void ) { + // disable the articulated figure for collision detection + physicsObj.UnlinkClip(); + isActive = false; +} + +/* +================ +idAF::Rest +================ +*/ +void idAF::Rest( void ) { + physicsObj.PutToRest(); +} + +/* +================ +idAF::SetConstraintPosition + + Only moves constraints that bind the entity to another entity. +================ +*/ +void idAF::SetConstraintPosition( const char *name, const idVec3 &pos ) { + idAFConstraint *constraint; + + constraint = GetPhysics()->GetConstraint( name ); + + if ( !constraint ) { + gameLocal.Warning( "can't find a constraint with the name '%s'", name ); + return; + } + + if ( constraint->GetBody2() != NULL ) { + gameLocal.Warning( "constraint '%s' does not bind to another entity", name ); + return; + } + + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *bs = static_cast(constraint); + bs->Translate( pos - bs->GetAnchor() ); + break; + } + case CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *uj = static_cast(constraint); + uj->Translate( pos - uj->GetAnchor() ); + break; + } + case CONSTRAINT_HINGE: { + idAFConstraint_Hinge *hinge = static_cast(constraint); + hinge->Translate( pos - hinge->GetAnchor() ); + break; + } + default: { + gameLocal.Warning( "cannot set the constraint position for '%s'", name ); + break; + } + } +} + +/* +================ +idAF::SaveState +================ +*/ +void idAF::SaveState( idDict &args ) const { + int i; + idAFBody *body; + idStr key, value; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + key = "body " + body->GetName(); + value = body->GetWorldOrigin().ToString( 8 ); + value += " "; + value += body->GetWorldAxis().ToAngles().ToString( 8 ); + args.Set( key, value ); + } +} + +/* +================ +idAF::LoadState +================ +*/ +void idAF::LoadState( const idDict &args ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idVec3 origin; + idAngles angles; + + kv = args.MatchPrefix( "body ", NULL ); + while ( kv ) { + + name = kv->GetKey(); + name.Strip( "body " ); + body = physicsObj.GetBody( name ); + if ( body ) { + sscanf( kv->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( angles.ToMat3() ); + } else { + gameLocal.Warning("Unknown body part %s in articulated figure %s", name.c_str(), this->name.c_str()); + } + + kv = args.MatchPrefix( "body ", kv ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::AddBindConstraints +================ +*/ +void idAF::AddBindConstraints( void ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idLexer lexer; + idToken type, bodyName, jointName; + idVec3 origin, renderOrigin; + idMat3 axis, renderAxis; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + idToken jointFriction; +// RAVEN END + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // parse all the bind constraints + for ( kv = args.MatchPrefix( "bindConstraint ", NULL ); kv; kv = args.MatchPrefix( "bindConstraint ", kv ) ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + + lexer.ReadToken( &bodyName ); + body = physicsObj.GetBody( bodyName ); + if ( !body ) { + gameLocal.Warning( "idAF::AddBindConstraints: body '%s' not found on entity '%s'", bodyName.c_str(), self->name.c_str() ); + lexer.FreeSource(); + continue; + } + + if ( type.Icmp( "fixed" ) == 0 ) { + idAFConstraint_Fixed *c; + + c = new idAFConstraint_Fixed( name, body, NULL ); + physicsObj.AddConstraint( c ); + } + else if ( type.Icmp( "ballAndSocket" ) == 0 ) { + idAFConstraint_BallAndSocketJoint *c; + + c = new idAFConstraint_BallAndSocketJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if ( type.Icmp( "universal" ) == 0 ) { + idAFConstraint_UniversalJoint *c; + + c = new idAFConstraint_UniversalJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if (type.Icmp( "hinge" ) == 0 ) + { + idAFConstraint_Hinge *c; + c = new idAFConstraint_Hinge( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) + { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found\n", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetAxis(renderAxis[1]); + c->SetNoLimit(); + if (lexer.ReadToken(&jointFriction)) + { + float frictionValue = 0; + + sscanf(jointFriction.c_str(), "%f", &frictionValue); + c->SetFriction(frictionValue); + } + idToken hingeAxis; + if (lexer.ReadToken(&hingeAxis)) + { + int hingeAxisValue = 1; + + sscanf(hingeAxis.c_str(), "%d", &hingeAxisValue); + if (hingeAxisValue >= 0 && hingeAxisValue <= 2) + { + c->SetAxis(renderAxis[hingeAxisValue]); + } + } + } +// RAVEN END + else { + gameLocal.Warning( "idAF::AddBindConstraints: unknown constraint type '%s' on entity '%s'", type.c_str(), self->name.c_str() ); + } + + lexer.FreeSource(); + } + + hasBindConstraints = true; +} + +/* +================ +idAF::RemoveBindConstraints +================ +*/ +void idAF::RemoveBindConstraints( void ) { + const idKeyValue *kv; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + idStr name; + + kv = args.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + if ( physicsObj.GetConstraint( name ) ) { + physicsObj.DeleteConstraint( name ); + } + + kv = args.MatchPrefix( "bindConstraint ", kv ); + } + + hasBindConstraints = false; +} diff --git a/source/game/AF.h b/source/game/AF.h new file mode 100644 index 0000000..8d6e8b5 --- /dev/null +++ b/source/game/AF.h @@ -0,0 +1,97 @@ + +#ifndef __GAME_AF_H__ +#define __GAME_AF_H__ + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ + +typedef struct jointConversion_s { + int bodyId; // id of the body + jointHandle_t jointHandle; // handle of joint this body modifies + AFJointModType_t jointMod; // modify joint axis, origin or both + idVec3 jointBodyOrigin; // origin of body relative to joint + idMat3 jointBodyAxis; // axis of body relative to joint +} jointConversion_t; + +typedef struct afTouch_s { + idEntity * touchedEnt; + idClipModel * touchedClipModel; + idAFBody * touchedByBody; +} afTouch_t; + +class idAF { +public: + idAF( void ); + ~idAF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetAnimator( idAnimator *a ) { animator = a; } +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + bool Load( idEntity *ent, const char *fileName, bool purgeAF = false ); +// RAVEN END + bool IsLoaded( void ) const { return isLoaded && self != NULL; } + const char * GetName( void ) const { return name.c_str(); } + void SetupPose( idEntity *ent, int time ); + void ChangePose( idEntity *ent, int time ); + int EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const; + void Start( void ); + void StartFromCurrentPose( int inheritVelocityTime ); + void Stop( void ); + void Rest( void ); + bool IsActive( void ) const { return isActive; } + void SetConstraintPosition( const char *name, const idVec3 &pos ); + + idPhysics_AF * GetPhysics( void ) { return &physicsObj; } + const idPhysics_AF * GetPhysics( void ) const { return &physicsObj; } + idBounds GetBounds( void ) const; + bool UpdateAnimation( void ); + + void GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const; + void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + idPhysics_AF physicsObj; // articulated figure physics + bool TestSolid( void ) const; + +protected: + idStr name; // name of the loaded .af file + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // anim to modify + idVec3 baseOrigin; // offset of base body relative to skeletal model origin + idMat3 baseAxis; // axis of base body relative to skeletal model origin + idListjointMods; // list with transforms from skeletal model joints to articulated figure bodies + idList jointBody; // table to find the nearest articulated figure body for a joint of the skeletal model + int poseTime; // last time the articulated figure was transformed to reflect the current animation pose + int restStartTime; // time the articulated figure came to rest + bool isLoaded; // true when the articulated figure is properly loaded + bool isActive; // true if the articulated figure physics is active + bool hasBindConstraints; // true if the bind constraints have been added + +protected: + void SetBase( idAFBody *body, const idJointMat *joints ); + void AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ); + + bool LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ); + bool LoadConstraint( const idDeclAF_Constraint *fc ); + +}; + +#endif /* !__GAME_AF_H__ */ diff --git a/source/game/AFEntity.cpp b/source/game/AFEntity.cpp new file mode 100644 index 0000000..c07739e --- /dev/null +++ b/source/game/AFEntity.cpp @@ -0,0 +1,3203 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + + idMultiModelAF + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMultiModelAF ) +END_CLASS + +/* +================ +idMultiModelAF::Spawn +================ +*/ +void idMultiModelAF::Spawn( void ) { + physicsObj.SetSelf( this ); +} + +/* +================ +idMultiModelAF::~idMultiModelAF +================ +*/ +idMultiModelAF::~idMultiModelAF( void ) { + int i; + + for ( i = 0; i < modelDefHandles.Num(); i++ ) { + if ( modelDefHandles[i] != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandles[i] ); + modelDefHandles[i] = -1; + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMultiModelAF::SetModelForId +================ +*/ +void idMultiModelAF::SetModelForId( int id, const idStr &modelName ) { + modelHandles.AssureSize( id+1, NULL ); + modelDefHandles.AssureSize( id+1, -1 ); + modelHandles[id] = renderModelManager->FindModel( modelName ); +} + +/* +================ +idMultiModelAF::Present +================ +*/ +void idMultiModelAF::Present( void ) { + int i; + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + for ( i = 0; i < modelHandles.Num(); i++ ) { + + if ( !modelHandles[i] ) { + continue; + } + + renderEntity.origin = physicsObj.GetOrigin( i ); + renderEntity.axis = physicsObj.GetAxis( i ); + renderEntity.hModel = modelHandles[i]; + renderEntity.bodyId = i; + + // add to refresh list + if ( modelDefHandles[i] == -1 ) { + modelDefHandles[i] = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandles[i], &renderEntity ); + } + } +} + +/* +================ +idMultiModelAF::Think +================ +*/ +void idMultiModelAF::Think( void ) { + RunPhysics(); + Present(); +} + + +/* +=============================================================================== + + idChain + +=============================================================================== +*/ + +CLASS_DECLARATION( idMultiModelAF, idChain ) +END_CLASS + +/* +================ +idChain::BuildChain + + builds a chain hanging down from the ceiling + the highest link is a child of the link below it etc. + this allows an object to be attached to multiple chains while keeping a single tree structure +================ +*/ +void idChain::BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld ) { + int i; + float halfLinkLength = linkLength * 0.5f; + idTraceModel trm; + idClipModel *clip; + idAFBody *body, *lastBody; + idAFConstraint_BallAndSocketJoint *bsj; + idAFConstraint_UniversalJoint *uj; + idVec3 org; + + // create a trace model + trm = idTraceModel( linkLength, linkWidth ); + trm.Translate( -trm.offset ); + + org = origin - idVec3( 0, 0, halfLinkLength ); + + lastBody = NULL; + for ( i = 0; i < numLinks; i++ ) { + + // add body +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clip = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip->SetContents( CONTENTS_SOLID ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, org, mat3_identity ); +// RAVEN END +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + body = new idAFBody( name + idStr(i), clip, density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.AddBody( body ); + + // visual model for body + SetModelForId( physicsObj.GetBodyId( body ), spawnArgs.GetString( "model" ) ); + + // add constraint + if ( bindToWorld ) { + if ( !lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), body, lastBody ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, -1 ), idVec3( 0, 0, 1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, -1 ), 30.0f ); + //uj->SetPyramidLimit( idVec3( 0, 0, -1 ), idVec3( 1, 0, 0 ), 90.0f, 30.0f ); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, 1 ), 30.0f ); + } + uj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + uj->SetFriction( 0.9f ); + physicsObj.AddConstraint( uj ); + } + else { + if ( lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + bsj = new idAFConstraint_BallAndSocketJoint( "joint" + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + bsj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + bsj->SetConeLimit( idVec3( 0, 0, 1 ), 60.0f, idVec3( 0, 0, 1 ) ); + physicsObj.AddConstraint( bsj ); + } + } + + org[2] -= linkLength; + + lastBody = body; + } +} + +/* +================ +idChain::Spawn +================ +*/ +void idChain::Spawn( void ) { + int numLinks; + float length, linkLength, linkWidth, density; + bool drop; + idVec3 origin; + + spawnArgs.GetBool( "drop", "0", drop ); + spawnArgs.GetInt( "links", "3", numLinks ); + spawnArgs.GetFloat( "length", idStr( numLinks * 32.0f ), length ); + spawnArgs.GetFloat( "width", "8", linkWidth ); + spawnArgs.GetFloat( "density", "0.2", density ); + linkLength = length / numLinks; + origin = GetPhysics()->GetOrigin(); + + // initialize physics + physicsObj.SetSelf( this ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY ); + SetPhysics( &physicsObj ); + + BuildChain( "link", origin, linkLength, linkWidth, density, numLinks, !drop ); +} + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +const idEventDef EV_ClearAnims( "clearAnims" ); + +CLASS_DECLARATION( idAnimatedEntity, idAFAttachment ) +// RAVEN BEGIN +// jshepard: we'd like these to animate. + EVENT( AI_PlayAnim, idAFAttachment::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAFAttachment::Event_PlayCycle ) + EVENT( EV_ClearAnims, idAFAttachment::Event_ClearAnims ) +// RAVEN END +END_CLASS + +/* +===================== +idAFAttachment::idAFAttachment +===================== +*/ +idAFAttachment::idAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; + +// RAVEN BEGIN +// jscott: Lip Syncing variables + lipSyncAnim = 0; + lipSyncData = NULL; + soundJoint = INVALID_JOINT; +// MCG + noPlayerImpactFX = false; +// RAVEN END +} + +/* +===================== +idAFAttachment::~idAFAttachment +===================== +*/ +idAFAttachment::~idAFAttachment( void ) { + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN + EndLipSyncing(); +// RAVEN END +} + +/* +===================== +idAFAttachment::Spawn +===================== +*/ +void idAFAttachment::Spawn( void ) { + idStr jointName; + + idleAnim = animator.GetAnim( "idle" ); + +// RAVEN BEGIN + // Init the lip sync data + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } + + // Grab the joint to play sounds off + soundJoint = INVALID_JOINT; + spawnArgs.GetString( "sound_bone", "", jointName ); + + // Do we want a bone to play sounds on? + if( jointName.Length() ) { + + // Try to find the specified bone + soundJoint = animator.GetJointHandle( jointName ); + + if ( soundJoint == INVALID_JOINT ) { + // The def specified a bone for this and we can't find it - warn them. + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +// RAVEN END +} + +/* +===================== +idAFAttachment::InitCopyJoints +===================== +*/ +void idAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on entity %s", jointName, name.c_str() ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %s", kv->GetValue().c_str(), name.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +idAFAttachment::CopyJointsFromBody +===================== +*/ +void idAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +idAFAttachment::SetBody +===================== +*/ +void idAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + fl.takedamage = true; + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +idAFAttachment::ClearBody +===================== +*/ +void idAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +idAFAttachment::GetBody +===================== +*/ +idEntity *idAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Save + +archive object for savegame file +================ +*/ +void idAFAttachment::Save( idSaveGame *savefile ) const { + int i; + + body.Save ( savefile ); + savefile->WriteInt( idleAnim ); + savefile->WriteJoint( damageJoint ); + savefile->WriteJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->WriteInt( lipSyncAnim ); +// RAVEN END + + savefile->WriteInt( copyJoints.Num() ); + for( i = 0; i < copyJoints.Num(); i++ ) { + savefile->WriteInt( copyJoints[i].mod ); + savefile->WriteJoint( copyJoints[i].from ); + savefile->WriteJoint( copyJoints[i].to ); + } + + bool hadCombatModel = false; + if ( combatModel ) { + hadCombatModel = true; + } + savefile->WriteBool( hadCombatModel ); + + savefile->WriteBool( noPlayerImpactFX ); +} + +/* +================ +idAFAttachment::Restore + +unarchives object from save game file +================ +*/ +void idAFAttachment::Restore( idRestoreGame *savefile ) { + body.Restore ( savefile ); + savefile->ReadInt( idleAnim ); + savefile->ReadJoint( damageJoint ); + savefile->ReadJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->ReadInt( lipSyncAnim ); +// jscott: difficult to start a sound mid sentence and impossible to sync up the timing with the animation + lipSyncData = NULL; +// RAVEN END + + int i; + int num; + savefile->ReadInt( num ); + copyJoints.SetNum( num ); + for( i = 0; i < num; i++ ) { + int val; + savefile->ReadInt( val ); + copyJoints[i].mod = static_cast( val ); + savefile->ReadJoint( copyJoints[i].from ); + savefile->ReadJoint( copyJoints[i].to ); + } + + bool hadCombatModel; + savefile->ReadBool( hadCombatModel ); + if ( hadCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + savefile->ReadBool( noPlayerImpactFX ); + + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void idAFAttachment::Hide( void ) { + idEntity::Hide(); + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void idAFAttachment::Show( void ) { + idEntity::Show(); + LinkCombat(); +} + +/* +============ +idAFAttachment::Damage + +Pass damage to body at the bindjoint +============ +*/ +void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if ( body ) { + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, damageJoint ); + } +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void idAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +idAFAttachment::GetImpactInfo +================ +*/ +void idAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFAttachment::CanPlayImpactEffect +================ +*/ +bool idAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFAttachment::GetNoPlayerImpactFX +================ +*/ +bool idAFAttachment::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFAttachment::ApplyImpulse +================ +*/ +void idAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFAttachment::AddForce +================ +*/ +void idAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFAttachment::PlayIdleAnim +================ +*/ +// RAVEN BEGIN +// bdube: added channel +void idAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +// RAVEN END +} + +/* +================ +idAfAttachment::Think +================ +*/ +void idAFAttachment::Think( void ) { +// RAVEN BEGIN +// jscott: Lip sync main code + HandleLipSync(); +// RAVEN END + + idAnimatedEntity::Think(); +} + +// RAVEN BEGIN +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAFAttachment::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = GetPhysics()->GetAxis(); + } else { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); + } + + return true; +} +// RAVEN END + +/* +================ +idAFAttachment::UpdateAnimationControllers +================ +*/ +bool idAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return idAnimatedEntity::UpdateAnimationControllers( ); +} + +/* +================ +idAFAttachment::SetCombatModel +================ +*/ +void idAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + combatModel->SetOwner( body ); +} + +/* +================ +idAFAttachment::GetCombatModel +================ +*/ +idClipModel *idAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFAttachment::LinkCombat +================ +*/ +void idAFAttachment::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFAttachment::UnlinkCombat +================ +*/ +void idAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +// RAVEN BEGIN +// bdube: return the body entity for damage +/* +================ +idAFAttachment::GetDamageEntity +================ +*/ +idEntity* idAFAttachment::GetDamageEntity ( void ) { + return body ? body : this; +} + +// jshepard: we need to animate these +/* +================ +idAFAttachment::Event_PlayAnim +================ +*/ +void idAFAttachment::Event_PlayAnim ( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animTime - gameLocal.time ) ); + } + blendFrames = 0; +} + +// jdischler: and we want cycling, too. +/* +=============== +idAFAttachment::Event_PlayCycle +=============== +*/ +void idAFAttachment::Event_PlayCycle( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +================ +idAFAttachment::Event_ClearAnims + +Clears any animation running on the idAFAttachment +================ +*/ +void idAFAttachment::Event_ClearAnims( void ) { + //animator.ClearAllAnims( gameLocal.time, 100 ); + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, 0 ); +} + +// RAVEN END + +/* +=============================================================================== + + idAFEntity_Base + +=============================================================================== +*/ + +const idEventDef EV_SetConstraintPosition( "SetConstraintPosition", "sv" ); +// RAVEN BEGIN +// kfuller: added +const idEventDef EV_TPose( "tpose", NULL ); +// RAVEN END + +CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base ) + EVENT( EV_SetConstraintPosition, idAFEntity_Base::Event_SetConstraintPosition ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idAFEntity_Base::idAFEntity_Base +================ +*/ +idAFEntity_Base::idAFEntity_Base( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); + noPlayerImpactFX = false; +} + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +idAFEntity_Base::~idAFEntity_Base( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +idAFEntity_Base::Save +================ +*/ +void idAFEntity_Base::Save( idSaveGame *savefile ) const { + savefile->WriteBool( noPlayerImpactFX ); + savefile->WriteInt( combatModelContents ); + savefile->WriteClipModel( combatModel ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( nextSoundTime ); + af.Save( savefile ); +} + +/* +================ +idAFEntity_Base::Restore +================ +*/ +void idAFEntity_Base::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( noPlayerImpactFX ); + savefile->ReadInt( combatModelContents ); + savefile->ReadClipModel( combatModel ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( nextSoundTime ); + LinkCombat(); + + af.Restore( savefile ); +} + +/* +================ +idAFEntity_Base::Spawn +================ +*/ +void idAFEntity_Base::Spawn( void ) { + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + nextSoundTime = 0; + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +} + +/* +================ +idAFEntity_Base::LoadAF +================ +*/ +bool idAFEntity_Base::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + if ( !af.Load( this, fileName ) ) { + gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() ); + } + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +idAFEntity_Base::Think +================ +*/ +void idAFEntity_Base::Think( void ) { + RunPhysics(); + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + +// RAVEN BEGIN +// kfuller: added + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } + +// rjohnson: added check to see if we no longer need to think! + if (GetPhysics()->IsAtRest() && !animator.IsAnimating( gameLocal.time, true ) ) { + BecomeInactive( TH_ANIMATE ); + } +// RAVEN END +} + +/* +================ +idAFEntity_Base::BodyForClipModelId +================ +*/ +int idAFEntity_Base::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +idAFEntity_Base::SaveState +================ +*/ +void idAFEntity_Base::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +idAFEntity_Base::LoadState +================ +*/ +void idAFEntity_Base::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +idAFEntity_Base::AddBindConstraints +================ +*/ +void idAFEntity_Base::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +idAFEntity_Base::RemoveBindConstraints +================ +*/ +void idAFEntity_Base::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +idAFEntity_Base::GetImpactInfo +================ +*/ +void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFEntity_Base::ApplyImpulse +================ +*/ +void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( !splash && ( noPlayerImpactFX || GetNoPlayerImpactFX( ) ) ) { + if ( ent->IsType( idPlayer::GetClassType() ) ) + {//player + return; + } + if ( ent->IsType( idProjectile::GetClassType() ) ) + {//projectile + if ( ((idProjectile*)ent)->GetOwner() && ((idProjectile*)ent)->GetOwner()->IsType( idPlayer::GetClassType() ) ) + {//owned by player + return; + } + } + } + + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFEntity_Base::AddForce +================ +*/ +void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + idEntity::AddForce( ent, id, point, force ); + } +} + +bool idAFEntity_Base::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFEntity_Base::Collide +================ +*/ +bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +idAFEntity_Base::GetPhysicsToVisualTransform +================ +*/ +bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return idEntity::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +idAFEntity_Base::UpdateAnimationControllers +================ +*/ +bool idAFEntity_Base::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +idAFEntity_Base::SetCombatModel +================ +*/ +void idAFEntity_Base::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::GetCombatModel +================ +*/ +idClipModel *idAFEntity_Base::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFEntity_Base::SetCombatContents +================ +*/ +void idAFEntity_Base::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +idAFEntity_Base::LinkCombat +================ +*/ +void idAFEntity_Base::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::UnlinkCombat +================ +*/ +void idAFEntity_Base::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +idAFEntity_Base::FreeModelDef +================ +*/ +void idAFEntity_Base::FreeModelDef( void ) { + UnlinkCombat(); + idEntity::FreeModelDef(); +} + +/* +=============== +idAFEntity_Base::ShowEditingDialog +=============== +*/ +void idAFEntity_Base::ShowEditingDialog( void ) { + common->InitTool( EDITOR_AF, &spawnArgs ); +} + +/* +================ +idAFEntity_Base::DropAFs + + The entity should have the following key/value pairs set: + "def_dropAF" "af def" + "dropSkin" "skin name" + To drop multiple articulated figures the following key/value pairs can be used: + "def_dropAF*" "af def" + where * is an aribtrary string. +================ +*/ +void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName; + idEntity *newEnt; + idAFEntity_Base *af; + idDict args; + const idDeclSkin *skin; + + // drop the articulated figures + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL ); + while ( kv ) { + + args.Set( "classname", kv->GetValue() ); + gameLocal.SpawnEntityDef( args, &newEnt ); + + if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(newEnt); + af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() ); + af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() ); + af->af.SetupPose( ent, gameLocal.time ); + if ( list ) { + list->Append( af ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv ); + } + + // change the skin to hide all the dropped articulated figures + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +================ +idAFEntity_Base::GetNoPlayerImpactFX +================ +*/ +bool idAFEntity_Base::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFEntity_Base::Event_SetConstraintPosition +================ +*/ +void idAFEntity_Base::Event_SetConstraintPosition( const char *name, const idVec3 &pos ) { + af.SetConstraintPosition( name, pos ); +} + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +const idEventDef EV_Gib( "gib", "s" ); +const idEventDef EV_Gibbed( "" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Gibbable ) + EVENT( EV_Gib, idAFEntity_Gibbable::Event_Gib ) + EVENT( EV_Gibbed, idAFEntity_Base::Event_Remove ) +END_CLASS + + +/* +================ +idAFEntity_Gibbable::idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::idAFEntity_Gibbable( void ) { + skeletonModel = NULL; + skeletonModelDefHandle = -1; + gibbed = false; +} + +/* +================ +idAFEntity_Gibbable::~idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::~idAFEntity_Gibbable() { + if ( skeletonModelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( skeletonModelDefHandle ); + skeletonModelDefHandle = -1; + } +} + +/* +================ +idAFEntity_Gibbable::Save +================ +*/ +void idAFEntity_Gibbable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( gibbed ); + savefile->WriteBool( combatModel != NULL ); +} + +/* +================ +idAFEntity_Gibbable::Restore +================ +*/ +void idAFEntity_Gibbable::Restore( idRestoreGame *savefile ) { + bool hasCombatModel; + + savefile->ReadBool( gibbed ); + savefile->ReadBool( hasCombatModel ); + + InitSkeletonModel(); + + if ( hasCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::Spawn +================ +*/ +void idAFEntity_Gibbable::Spawn( void ) { + InitSkeletonModel(); + + gibbed = false; + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::InitSkeletonModel +================ +*/ +void idAFEntity_Gibbable::InitSkeletonModel( void ) { + const char *modelName; + const idDeclModelDef *modelDef; + + skeletonModel = NULL; + skeletonModelDefHandle = -1; + + modelName = spawnArgs.GetString( "model_gib" ); + + modelDef = NULL; + if ( modelName[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + skeletonModel = modelDef->ModelHandle(); + } else { + skeletonModel = renderModelManager->FindModel( modelName ); + } + if ( skeletonModel != NULL && renderEntity.hModel != NULL ) { + if ( skeletonModel->NumJoints() != renderEntity.hModel->NumJoints() ) { + gameLocal.Error( "gib model '%s' has different number of joints than model '%s'", + skeletonModel->Name(), renderEntity.hModel->Name() ); + } + } + } +} + +/* +================ +idAFEntity_Gibbable::Present +================ +*/ +void idAFEntity_Gibbable::Present( void ) { + renderEntity_t skeleton; + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // update skeleton model + if ( gibbed && !IsHidden() && skeletonModel != NULL ) { + skeleton = renderEntity; + skeleton.hModel = skeletonModel; + // add to refresh list + if ( skeletonModelDefHandle == -1 ) { + skeletonModelDefHandle = gameRenderWorld->AddEntityDef( &skeleton ); + } else { + gameRenderWorld->UpdateEntityDef( skeletonModelDefHandle, &skeleton ); + } + } + + idEntity::Present(); +} + +/* +================ +idAFEntity_Gibbable::Damage +================ +*/ +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } +} + +/* +===================== +idAFEntity_Gibbable::SpawnGibs +===================== +*/ +void idAFEntity_Gibbable::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + int i; + bool gibNonSolid; + idVec3 entityCenter, velocity; + idList list; + + assert( !gameLocal.isClient ); + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + // spawn gib articulated figures + idAFEntity_Base::DropAFs( this, "gib", &list ); + + // spawn gib items + idMoveableItem::DropItems( this, "gib", &list ); + + // blow out the gibs in the given direction away from the center of the entity + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + gibNonSolid = damageDef->GetBool( "gibNonSolid" ); + for ( i = 0; i < list.Num(); i++ ) { + if ( gibNonSolid ) { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( 0 ); + list[i]->GetPhysics()->UnlinkClip(); + list[i]->GetPhysics()->PutToRest(); + } else { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + list[i]->GetPhysics()->SetLinearVelocity( velocity * 225.0f ); + } + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; +// RAVEN BEGIN +// dluetscher: reduced the gib lifetime from 4. to 1.5 + list[i]->PostEventSec( &EV_Remove, 1.5f ); +// RAVEN END + } +} + +/* +============ +idAFEntity_Gibbable::Gib +============ +*/ +void idAFEntity_Gibbable::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + if ( damageDef->GetBool( "gibNonSolid" ) ) { + GetAFPhysics()->SetContents( 0 ); + GetAFPhysics()->SetClipMask( 0 ); + GetAFPhysics()->UnlinkClip(); + GetAFPhysics()->PutToRest(); + } else { + GetAFPhysics()->SetContents( CONTENTS_CORPSE ); + GetAFPhysics()->SetClipMask( CONTENTS_SOLID ); + } + + UnlinkCombat(); +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( g_decals.GetBool() ) { +// RAVEN END + if ( gameLocal.time > gameLocal.GetGibTime() ) { + gameLocal.SetGibTime( gameLocal.time + GIB_DELAY ); + SpawnGibs( dir, damageDefName ); + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + StartSound( "snd_gibbed", SND_CHANNEL_ANY, 0, false, NULL ); + gibbed = true; + } + } else { + gibbed = true; + } + +// RAVEN BEGIN +// bdube: default is to remove the character immediately + // however, this is bad in the case of the player. What was happening is that the + // player was being removed right away, but the respawn logic runs off of + // idPlayer::EvaluateControls, which no longer gets run when the player is gibbed and removed. + // So...the game effectively sits locked on a black screen and you never get a menu.. + if ( !gameLocal.isMultiplayer && this->IsType( idPlayer::GetClassType() )) { + return; + } + PostEventSec( &EV_Gibbed, spawnArgs.GetFloat ( "gibRemoveDelay", "0" ) ); +// RAVEN END +} + +/* +============ +idAFEntity_Gibbable::Event_Gib +============ +*/ +void idAFEntity_Gibbable::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_Generic ) + EVENT( EV_Activate, idAFEntity_Generic::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_Generic::idAFEntity_Generic +================ +*/ +idAFEntity_Generic::idAFEntity_Generic( void ) { + keepRunningPhysics = false; +} + +/* +================ +idAFEntity_Generic::~idAFEntity_Generic +================ +*/ +idAFEntity_Generic::~idAFEntity_Generic( void ) { +} + +/* +================ +idAFEntity_Generic::Save +================ +*/ +void idAFEntity_Generic::Save( idSaveGame *savefile ) const { + savefile->WriteBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Restore +================ +*/ +void idAFEntity_Generic::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Think +================ +*/ +void idAFEntity_Generic::Think( void ) { + idAFEntity_Base::Think(); + + if ( keepRunningPhysics ) { + BecomeActive( TH_PHYSICS ); + } +} + +/* +================ +idAFEntity_Generic::Spawn +================ +*/ +void idAFEntity_Generic::Spawn( void ) { + if ( !LoadAF() ) { + gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() ); + } + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + +// RAVEN BEGIN +// kfuller: we just put the guy to rest so don't be affected by an attractor +/* + const char *attractor = spawnArgs.GetString("attractor"); + if (attractor && attractor[0]) { + rvRagdollAttractor *attractorEnt = dynamic_cast(gameLocal.FindEntity(attractor)); + if (attractorEnt) { + attractorEnt->RemoveAttractee(entityNumber); + } else { + gameLocal.Warning("idAFEntity::LoadAF -- failed to find attractor '%s'", attractor); + } + } +*/ +// RAVEN END + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; +} + +/* +================ +idAFEntity_Generic::Event_Activate +================ +*/ +void idAFEntity_Generic::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_WithAttachedHead ) + EVENT( EV_Gib, idAFEntity_WithAttachedHead::Event_Gib ) + EVENT( EV_Activate, idAFEntity_WithAttachedHead::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead() { + head = NULL; +} + +/* +================ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead() { + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Spawn +================ +*/ +void idAFEntity_WithAttachedHead::Spawn( void ) { + SetupHead(); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; + + if ( head.GetEntity() ) { + int anim = head.GetEntity()->GetAnimator()->GetAnim( "dead" ); + + if ( anim ) { +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + head.GetEntity()->GetAnimator()->SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + } + } + + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Save +================ +*/ +void idAFEntity_WithAttachedHead::Save( idSaveGame *savefile ) const { + head.Save( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::Restore +================ +*/ +void idAFEntity_WithAttachedHead::Restore( idRestoreGame *savefile ) { + head.Restore( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::SetupHead +================ +*/ +void idAFEntity_WithAttachedHead::SetupHead( const char* headDefName ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); + } + + if ( headDefName[ 0 ] ) { + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headDefName, joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + + head->InitCopyJoints ( ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Think +================ +*/ +void idAFEntity_WithAttachedHead::Think( void ) { + idAFEntity_Base::Think(); +} + +/* +================ +idAFEntity_WithAttachedHead::LinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::UnlinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Hide +================ +*/ +void idAFEntity_WithAttachedHead::Hide( void ) { + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + UnlinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::Show +================ +*/ +void idAFEntity_WithAttachedHead::Show( void ) { + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + LinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::ProjectOverlay +================ +*/ +void idAFEntity_WithAttachedHead::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + + idEntity::ProjectOverlay( origin, dir, size, material ); + + if ( head.GetEntity() ) { + head.GetEntity()->ProjectOverlay( origin, dir, size, material ); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Gib +============ +*/ +void idAFEntity_WithAttachedHead::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Event_Gib +============ +*/ +void idAFEntity_WithAttachedHead::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +================ +idAFEntity_WithAttachedHead::Event_Activate +================ +*/ +void idAFEntity_WithAttachedHead::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_Vehicle + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Vehicle ) +END_CLASS + +/* +================ +idAFEntity_Vehicle::idAFEntity_Vehicle +================ +*/ +idAFEntity_Vehicle::idAFEntity_Vehicle( void ) { + player = NULL; + eyesJoint = INVALID_JOINT; + steeringWheelJoint = INVALID_JOINT; + wheelRadius = 0.0f; + steerAngle = 0.0f; + steerSpeed = 0.0f; +// dustSmoke = NULL; +} + +/* +================ +idAFEntity_Vehicle::Spawn +================ +*/ +void idAFEntity_Vehicle::Spawn( void ) { + const char *eyesJointName = spawnArgs.GetString( "eyesJoint", "eyes" ); + const char *steeringWheelJointName = spawnArgs.GetString( "steeringWheelJoint", "steeringWheel" ); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + if ( !eyesJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no eyes joint specified", name.c_str() ); + } + eyesJoint = animator.GetJointHandle( eyesJointName ); + if ( !steeringWheelJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no steering wheel joint specified", name.c_str() ); + } + steeringWheelJoint = animator.GetJointHandle( steeringWheelJointName ); + + spawnArgs.GetFloat( "wheelRadius", "20", wheelRadius ); + spawnArgs.GetFloat( "steerSpeed", "5", steerSpeed ); + + player = NULL; + steerAngle = 0.0f; + +/* + const char *smokeName = spawnArgs.GetString( "smoke_vehicle_dust", "muzzlesmoke" ); + if ( *smokeName != '\0' ) { + dustSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } +*/ +} + +/* +================ +idAFEntity_Vehicle::Use +================ +*/ +void idAFEntity_Vehicle::Use( idPlayer *other ) { + idVec3 origin; + idMat3 axis; + + if ( player ) { + if ( player == other ) { + other->Unbind(); + player = NULL; + + af.GetPhysics()->SetComeToRest( true ); + } + } + else { + player = other; + animator.GetJointTransform( eyesJoint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + player->GetPhysics()->SetOrigin( origin ); + player->BindToBody( this, 0, true ); + + af.GetPhysics()->SetComeToRest( false ); + af.GetPhysics()->Activate(); + } +} + +/* +================ +idAFEntity_Vehicle::GetSteerAngle +================ +*/ +float idAFEntity_Vehicle::GetSteerAngle( void ) { + float idealSteerAngle, angleDelta; + + idealSteerAngle = player->usercmd.rightmove * ( 30.0f / 128.0f ); + angleDelta = idealSteerAngle - steerAngle; + + if ( angleDelta > steerSpeed ) { + steerAngle += steerSpeed; + } else if ( angleDelta < -steerSpeed ) { + steerAngle -= steerSpeed; + } else { + steerAngle = idealSteerAngle; + } + + return steerAngle; +} + + +/* +=============================================================================== + + idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleFourWheels ) +END_CLASS + + +/* +================ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels +================ +*/ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; +} + +/* +================ +idAFEntity_VehicleFourWheels::Spawn +================ +*/ +void idAFEntity_VehicleFourWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 4; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 2; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleFourWheels::Think +================ +*/ +void idAFEntity_VehicleFourWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 2; i++ ) { + wheels[2+i]->SetContactMotorVelocity( velocity ); + wheels[2+i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + wheels[2]->SetContactMotorVelocity( velocity * 0.5f ); + } + else if ( steerAngle > 0.0f ) { + wheels[3]->SetContactMotorVelocity( velocity * 0.5f ); + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + for ( i = 0; i < 2; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSixWheels ) +END_CLASS + + /* +================ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels +================ +*/ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels( void ) { + int i; + + for ( i = 0; i < 6; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; + steering[2] = NULL; + steering[3] = NULL; +} + +/* +================ +idAFEntity_VehicleSixWheels::Spawn +================ +*/ +void idAFEntity_VehicleSixWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyMiddleLeft", + "wheelBodyMiddleRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointMiddleLeft", + "wheelJointMiddleRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + "steeringHingeRearLeft", + "steeringHingeRearRight" + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 6; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 4; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSixWheels::Think +================ +*/ +void idAFEntity_VehicleSixWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 6; i++ ) { + wheels[i]->SetContactMotorVelocity( velocity ); + wheels[i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + else if ( steerAngle > 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[1+(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + steering[2]->SetSteerAngle( -steerAngle ); + steering[3]->SetSteerAngle( -steerAngle ); + for ( i = 0; i < 4; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 6; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_SteamPipe + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_SteamPipe ) +END_CLASS + + +/* +================ +idAFEntity_SteamPipe::idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::idAFEntity_SteamPipe( void ) { + steamBody = 0; + steamForce = 0.0f; + steamUpForce = 0.0f; + steamModelDefHandle = -1; + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); +} + +/* +================ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe( void ) { + if ( steamModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( steamModelDefHandle ); + } +} + +/* +================ +idAFEntity_SteamPipe::Save +================ +*/ +void idAFEntity_SteamPipe::Save( idSaveGame *savefile ) const { +} + +/* +================ +idAFEntity_SteamPipe::Restore +================ +*/ +void idAFEntity_SteamPipe::Restore( idRestoreGame *savefile ) { + Spawn(); +} + +/* +================ +idAFEntity_SteamPipe::Spawn +================ +*/ +void idAFEntity_SteamPipe::Spawn( void ) { + idVec3 steamDir; + const char *steamBodyName; + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + steamBodyName = spawnArgs.GetString( "steamBody", "" ); + steamForce = spawnArgs.GetFloat( "steamForce", "2000" ); + steamUpForce = spawnArgs.GetFloat( "steamUpForce", "10" ); + steamDir = af.GetPhysics()->GetAxis( steamBody )[2]; + steamBody = af.GetPhysics()->GetBodyId( steamBodyName ); + force.SetPosition( af.GetPhysics(), steamBody, af.GetPhysics()->GetOrigin( steamBody ) ); + force.SetForce( steamDir * -steamForce ); + + InitSteamRenderEntity(); + + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_SteamPipe::InitSteamRenderEntity +================ +*/ +void idAFEntity_SteamPipe::InitSteamRenderEntity( void ) { + const char *temp; + const idDeclModelDef *modelDef; + + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); + steamRenderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + modelDef = NULL; + temp = spawnArgs.GetString ( "model_steam" ); + if ( *temp != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + steamRenderEntity.hModel = modelDef->ModelHandle(); + } + } + + if ( !steamRenderEntity.hModel ) { + steamRenderEntity.hModel = renderModelManager->FindModel( temp ); + } + + if ( steamRenderEntity.hModel ) { + steamRenderEntity.bounds = steamRenderEntity.hModel->Bounds( &steamRenderEntity ); + } else { + steamRenderEntity.bounds.Zero(); + } + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + steamModelDefHandle = gameRenderWorld->AddEntityDef( &steamRenderEntity ); + } +} + +/* +================ +idAFEntity_SteamPipe::Think +================ +*/ +void idAFEntity_SteamPipe::Think( void ) { + idVec3 steamDir; + + if ( thinkFlags & TH_THINK ) { + steamDir.x = gameLocal.random.CRandomFloat() * steamForce; + steamDir.y = gameLocal.random.CRandomFloat() * steamForce; + steamDir.z = steamUpForce; + force.SetForce( steamDir ); + force.Evaluate( gameLocal.time ); + //gameRenderWorld->DebugArrow( colorWhite, af.GetPhysics()->GetOrigin( steamBody ), af.GetPhysics()->GetOrigin( steamBody ) - 10.0f * steamDir, 4 ); + } + + if ( steamModelDefHandle >= 0 ){ + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + gameRenderWorld->UpdateEntityDef( steamModelDefHandle, &steamRenderEntity ); + } + + idAFEntity_Base::Think(); +} + + +/* +=============================================================================== + + idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +const idEventDef EV_SetFingerAngle( "setFingerAngle", "f" ); +const idEventDef EV_StopFingers( "stopFingers" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_ClawFourFingers ) + EVENT( EV_SetFingerAngle, idAFEntity_ClawFourFingers::Event_SetFingerAngle ) + EVENT( EV_StopFingers, idAFEntity_ClawFourFingers::Event_StopFingers ) +END_CLASS + +static const char *clawConstraintNames[] = { + "claw1", "claw2", "claw3", "claw4" +}; + +/* +================ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers +================ +*/ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers( void ) { + fingers[0] = NULL; + fingers[1] = NULL; + fingers[2] = NULL; + fingers[3] = NULL; +} + +/* +================ +idAFEntity_ClawFourFingers::Save +================ +*/ +void idAFEntity_ClawFourFingers::Save( idSaveGame *savefile ) const { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->Save( savefile ); + } +} + +/* +================ +idAFEntity_ClawFourFingers::Restore +================ +*/ +void idAFEntity_ClawFourFingers::Restore( idRestoreGame *savefile ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + fingers[i]->Restore( savefile ); + } + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFEntity_ClawFourFingers::Spawn +================ +*/ +void idAFEntity_ClawFourFingers::Spawn( void ) { + int i; + + LoadAF(); + + SetCombatModel(); + + af.GetPhysics()->LockWorldConstraints( true ); + af.GetPhysics()->SetForcePushable( true ); + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + if ( !fingers[i] ) { + gameLocal.Error( "idClaw_FourFingers '%s': can't find claw constraint '%s'", name.c_str(), clawConstraintNames[i] ); + } + } +} + +/* +================ +idAFEntity_ClawFourFingers::Event_SetFingerAngle +================ +*/ +void idAFEntity_ClawFourFingers::Event_SetFingerAngle( float angle ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( angle ); + fingers[i]->SetSteerSpeed( 0.5f ); + } + af.GetPhysics()->Activate(); +} + +/* +================ +idAFEntity_ClawFourFingers::Event_StopFingers +================ +*/ +void idAFEntity_ClawFourFingers::Event_StopFingers( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( fingers[i]->GetAngle() ); + } +} + + +/* +=============================================================================== + + editor support routines + +=============================================================================== +*/ + + +/* +================ +idGameEdit::AF_SpawnEntity +================ +*/ +bool idGameEdit::AF_SpawnEntity( const char *fileName ) { + idDict args; + idPlayer *player; + idAFEntity_Generic *ent; + const idDeclAF *af; + idVec3 org; + float yaw; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return false; + } + + af = static_cast( declManager->FindType( DECL_AF, fileName ) ); + if ( !af ) { + return false; + } + + yaw = player->viewAngles.yaw; + args.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + args.Set( "origin", org.ToString() ); + args.Set( "spawnclass", "idAFEntity_Generic" ); + if ( af->model[0] ) { + args.Set( "model", af->model.c_str() ); + } else { + args.Set( "model", fileName ); + } + if ( af->skin[0] ) { + args.Set( "skin", af->skin.c_str() ); + } + args.Set( "articulatedFigure", fileName ); + args.Set( "nodrop", "1" ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + ent = static_cast(gameLocal.SpawnEntityType( idAFEntity_Generic::GetClassType(), &args)); +// RAVEN END + + // always update this entity + ent->BecomeActive( TH_THINK ); + ent->KeepRunningPhysics(); + ent->fl.forcePhysicsUpdate = true; + + player->dragEntity.SetSelected( ent ); + + return true; +} + +/* +================ +idGameEdit::AF_UpdateEntities +================ +*/ +void idGameEdit::AF_UpdateEntities( const char *fileName ) { + idEntity *ent; + idAFEntity_Base *af; + idStr name; + + name = fileName; + name.StripFileExtension(); + + // reload any idAFEntity_Generic which uses the given articulated figure file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( name.Icmp( af->GetAFName() ) == 0 ) { + af->LoadAF(); + af->GetAFPhysics()->PutToRest(); + } + } + } +} + +/* +================ +idGameEdit::AF_UndoChanges +================ +*/ +void idGameEdit::AF_UndoChanges( void ) { + int i, c; + idEntity *ent; + idAFEntity_Base *af; + idDeclAF *decl; + + c = declManager->GetNumDecls( DECL_AF ); + for ( i = 0; i < c; i++ ) { + decl = static_cast( const_cast( declManager->DeclByIndex( DECL_AF, i, false ) ) ); + if ( !decl->modified ) { + continue; + } + + decl->Invalidate(); + declManager->FindType( DECL_AF, decl->GetName() ); + + // reload all AF entities using the file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) { + af->LoadAF(); + } + } + } + } +} + +/* +================ +GetJointTransform +================ +*/ +typedef struct { + renderEntity_t *ent; + const idMD5Joint *joints; +} jointTransformData_t; + +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + int i; + jointTransformData_t *data = reinterpret_cast(model); + + for ( i = 0; i < data->ent->numJoints; i++ ) { + if ( data->joints[i].name.Icmp( jointName ) == 0 ) { + break; + } + } + if ( i >= data->ent->numJoints ) { + return false; + } + origin = frame[i].ToVec3(); + axis = frame[i].ToMat3(); + return true; +} + +/* +================ +GetArgString +================ +*/ +static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) { + const char *s; + + s = args.GetString( key ); + if ( !s[0] && defArgs ) { + s = defArgs->GetString( key ); + } + return s; +} + +/* +================ +idGameEdit::AF_CreateMesh +================ +*/ +idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) { + int i, jointNum; + const idDeclAF *af; + const idDeclAF_Body *fb = NULL; + renderEntity_t ent; + idVec3 origin, *bodyOrigin, *newBodyOrigin, *modifiedOrigin; + idMat3 axis, *bodyAxis, *newBodyAxis, *modifiedAxis; + declAFJointMod_t *jointMod; + idAngles angles; + const idDict *defArgs; + const idKeyValue *arg; + idStr name; + jointTransformData_t data; + const char *classname, *afName, *modelName; + idRenderModel *md5; + const idDeclModelDef *modelDef; + const idMD5Anim *MD5anim; + const idMD5Joint *MD5joint; + const idMD5Joint *MD5joints; + int numMD5joints; + idJointMat *originalJoints; + int parentNum; + + poseIsSet = false; + meshOrigin.Zero(); + meshAxis.Identity(); + + classname = args.GetString( "classname" ); + defArgs = gameLocal.FindEntityDefDict( classname ); + + // get the articulated figure + afName = GetArgString( args, defArgs, "articulatedFigure" ); + af = static_cast( declManager->FindType( DECL_AF, afName ) ); + if ( !af ) { + return NULL; + } + + // get the md5 model + modelName = GetArgString( args, defArgs, "model" ); + modelDef = static_cast< const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( !modelDef ) { + return NULL; + } + + // make sure model hasn't been purged + if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) { + modelDef->ModelHandle()->LoadModel(); + } + + // get the md5 + md5 = modelDef->ModelHandle(); + if ( !md5 || md5->IsDefaultModel() ) { + return NULL; + } + + // get the articulated figure pose anim + int animNum = modelDef->GetAnim( "af_pose" ); + if ( !animNum ) { + return NULL; + } + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + MD5anim = anim->MD5Anim( 0 ); + MD5joints = md5->GetJoints(); + numMD5joints = md5->NumJoints(); + + // setup a render entity + memset( &ent, 0, sizeof( ent ) ); + ent.customSkin = modelDef->GetSkin(); + ent.bounds.Clear(); + ent.numJoints = numMD5joints; + ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) ); + + // create animation from of the af_pose + ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false ); + + // buffers to store the initial origin and axis for each body + bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + + // finish the AF positions + data.ent = &ent; + data.joints = MD5joints; + af->Finish( GetJointTransform, ent.joints, &data ); + + // get the initial origin and axis for each AF body + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->modelType == TRM_BONE ) { + // axis of bone trace model + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + } else { + axis = fb->angles.ToMat3(); + } + + newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3(); + newBodyAxis[i] = bodyAxis[i] = axis; + } + + // get any new body transforms stored in the key/value pairs + for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) { + name = arg->GetKey(); + name.Strip( "body " ); + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + if ( fb->name.Icmp( name ) == 0 ) { + break; + } + } + if ( i >= af->bodies.Num() ) { + continue; + } + sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + meshAxis = bodyAxis[i].Transpose() * angles.ToMat3(); + meshOrigin = origin - bodyOrigin[i] * meshAxis; + poseIsSet = true; + } else { + newBodyOrigin[i] = origin; + newBodyAxis[i] = angles.ToMat3(); + } + } + + // save the original joints + originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN END + + // buffer to store the joint mods + jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) ); + memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) ); + modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) ); + memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) ); + modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) ); + memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) ); + + // get all the joint modifications + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + continue; + } + + for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) { + if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) { + break; + } + } + + if ( jointNum >= 0 && jointNum < ent.numJoints ) { + jointMod[ jointNum ] = fb->jointMod; + modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() ); + // FIXME: calculate correct modifiedOrigin + modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3(); + } + } + + // apply joint modifications to the skeleton + MD5joint = MD5joints + 1; + for( i = 1; i < numMD5joints; i++, MD5joint++ ) { + + parentNum = MD5joint->parent - MD5joints; + idMat3 parentAxis = originalJoints[ parentNum ].ToMat3(); + idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose(); + idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose(); + + switch( jointMod[i] ) { + case DECLAF_JOINTMOD_ORIGIN: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + case DECLAF_JOINTMOD_AXIS: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + case DECLAF_JOINTMOD_BOTH: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + default: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + } + } + + // instantiate a mesh using the joint information from the render entity + return md5->InstantiateDynamicModel( &ent, NULL, NULL ); +} + +// RAVEN BEGIN +// bdube: af attractors + +/* +=============================================================================== + + rvAFAttractor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAFAttractor ) +END_CLASS + +// RAVEN END diff --git a/source/game/AFEntity.h b/source/game/AFEntity.h new file mode 100644 index 0000000..22eee07 --- /dev/null +++ b/source/game/AFEntity.h @@ -0,0 +1,508 @@ + +#ifndef __GAME_AFENTITY_H__ +#define __GAME_AFENTITY_H__ + + +/* +=============================================================================== + +idMultiModelAF + +Entity using multiple separate visual models animated with a single +articulated figure. Only used for debugging! + +=============================================================================== +*/ +const int GIB_DELAY = 200; // only gib this often to keep performace hits when blowing up several mobs + +class idMultiModelAF : public idEntity { +public: + CLASS_PROTOTYPE( idMultiModelAF ); + + void Spawn( void ); + ~idMultiModelAF( void ); + + virtual void Think( void ); + virtual void Present( void ); + +protected: + idPhysics_AF physicsObj; + + void SetModelForId( int id, const idStr &modelName ); + +private: + idList modelHandles; + idList modelDefHandles; +}; + + +/* +=============================================================================== + +idChain + +Chain hanging down from the ceiling. Only used for debugging! + +=============================================================================== +*/ + +class idChain : public idMultiModelAF { +public: + CLASS_PROTOTYPE( idChain ); + + void Spawn( void ); + +protected: + void BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld = true ); +}; + + +/* +=============================================================================== + +idAFAttachment + +=============================================================================== +*/ + +typedef struct { + jointModTransform_t mod; + jointHandle_t from; + jointHandle_t to; +} copyJoints_t; + +class idAFAttachment : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFAttachment ); + + idAFAttachment( void ); + virtual ~idAFAttachment( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + +// RAVEN BEGIN +// bdube: added channel + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + // for getting th speaker position + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// jshepard: animations for heads + void Event_PlayAnim ( int channel, const char *animname ); +// jdischler: animations for heads + void Event_PlayCycle ( int channel, const char *animname ); + void Event_ClearAnims ( void ); + + +// RAVEN END + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + int StartLipSyncing( const char *speechDecl ); + void HandleLipSync( void ); + void EndLipSyncing( void ); + bool IsLipSyncing( void ) const; + + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + + + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + jointHandle_t soundJoint; + + int lipSyncAnim; // Anim that contains the visemes + class rvLipSyncData* lipSyncData; // The current instance of lip syncing data + + idList copyJoints; // copied from the body animation to the head model + + bool noPlayerImpactFX; +}; + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE bool idAFAttachment::IsLipSyncing( void ) const { + return !!lipSyncData; +} + +ID_INLINE void idAFAttachment::SetDamageJoint ( jointHandle_t _damageJoint ) { + damageJoint = _damageJoint; +} +// RAVEN END + +/* +=============================================================================== + +idAFEntity_Base + +=============================================================================== +*/ + +class idAFEntity_Base : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFEntity_Base ); + + idAFEntity_Base( void ); + virtual ~idAFEntity_Base( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeModelDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + virtual void ShowEditingDialog( void ); + + static void DropAFs( idEntity *ent, const char *type, idList *list ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound + + bool noPlayerImpactFX; + + void Event_SetConstraintPosition( const char *name, const idVec3 &pos ); +}; + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +extern const idEventDef EV_Gib; +extern const idEventDef EV_Gibbed; + +class idAFEntity_Gibbable : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Gibbable ); + + idAFEntity_Gibbable( void ); + ~idAFEntity_Gibbable( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +protected: + idRenderModel * skeletonModel; + int skeletonModelDefHandle; + bool gibbed; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void InitSkeletonModel( void ); + + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +class idAFEntity_Generic : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_Generic ); + + idAFEntity_Generic( void ); + ~idAFEntity_Generic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void KeepRunningPhysics( void ) { keepRunningPhysics = true; } + +private: + void Event_Activate( idEntity *activator ); + + bool keepRunningPhysics; +}; + + +/* +=============================================================================== + +idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +class idAFEntity_WithAttachedHead : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_WithAttachedHead ); + + idAFEntity_WithAttachedHead(); + ~idAFEntity_WithAttachedHead(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SetupHead( const char* headDefName = "" ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + +protected: + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + + idEntityPtr head; // safe pointer to attached head + +private: + + void Event_Gib( const char *damageDefName ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idAFEntity_Vehicle + +=============================================================================== +*/ + +class idAFEntity_Vehicle : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Vehicle ); + + idAFEntity_Vehicle( void ); + + void Spawn( void ); + void Use( idPlayer *player ); + +protected: + idPlayer * player; + jointHandle_t eyesJoint; + jointHandle_t steeringWheelJoint; + float wheelRadius; + float steerAngle; + float steerSpeed; +// const idDeclParticle * dustSmoke; + + float GetSteerAngle( void ); +}; + + +/* +=============================================================================== + +idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleFourWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleFourWheels ); + + idAFEntity_VehicleFourWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +protected: + idAFBody * wheels[4]; + idAFConstraint_Hinge * steering[2]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleSixWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSixWheels ); + + idAFEntity_VehicleSixWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +private: + idAFBody * wheels[6]; + idAFConstraint_Hinge * steering[4]; + jointHandle_t wheelJoints[6]; + float wheelAngles[6]; +}; + + +/* +=============================================================================== + +idAFEntity_SteamPipe + +=============================================================================== +*/ + +class idAFEntity_SteamPipe : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_SteamPipe ); + + idAFEntity_SteamPipe( void ); + ~idAFEntity_SteamPipe( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + int steamBody; + float steamForce; + float steamUpForce; + idForce_Constant force; + renderEntity_t steamRenderEntity; + qhandle_t steamModelDefHandle; + + void InitSteamRenderEntity( void ); +}; + + +/* +=============================================================================== + +idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +class idAFEntity_ClawFourFingers : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_ClawFourFingers ); + + idAFEntity_ClawFourFingers( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idAFConstraint_Hinge * fingers[4]; + + void Event_SetFingerAngle( float angle ); + void Event_StopFingers( void ); +}; + +// RAVEN BEGIN +// bdube: AFAttractor + +/* +=============================================================================== + +idAFAttractor + +=============================================================================== +*/ + +class rvAFAttractor : public idEntity { +public: + CLASS_PROTOTYPE( rvAFAttractor ); + + rvAFAttractor( void ) { } + +private: +}; + +// RAVEN END + +#endif /* !__GAME_AFENTITY_H__ */ diff --git a/source/game/Actor.cpp b/source/game/Actor.cpp new file mode 100644 index 0000000..5b97abf --- /dev/null +++ b/source/game/Actor.cpp @@ -0,0 +1,3855 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "Vehicle/Vehicle.h" +#endif + +#include "ai/AI.h" +#include "ai/AI_Manager.h" + +/*********************************************************************** + + idAnimState + +***********************************************************************/ + +/* +===================== +idAnimState::idAnimState +===================== +*/ +idAnimState::idAnimState() { + self = NULL; + animator = NULL; + idleAnim = true; + disabled = true; + channel = ANIMCHANNEL_ALL; + animBlendFrames = 0; + lastAnimBlendFrames = 0; +} + +/* +===================== +idAnimState::~idAnimState +===================== +*/ +idAnimState::~idAnimState() { +} + +/* +===================== +idAnimState::Save +===================== +*/ +void idAnimState::Save( idSaveGame *savefile ) const { + + savefile->WriteBool( idleAnim ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( lastAnimBlendFrames ); + + savefile->WriteObject( self ); + + // Save the entity owner of the animator + savefile->WriteObject( animator->GetEntity() ); + + savefile->WriteInt( channel ); + savefile->WriteBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Save( savefile ); +// RAVEN END +} + +/* +===================== +idAnimState::Restore +===================== +*/ +void idAnimState::Restore( idRestoreGame *savefile ) { + + savefile->ReadBool( idleAnim ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( lastAnimBlendFrames ); + + savefile->ReadObject( reinterpret_cast( self ) ); + + idEntity *animowner; + savefile->ReadObject( reinterpret_cast( animowner ) ); + if ( animowner ) { + animator = animowner->GetAnimator(); + } + + savefile->ReadInt( channel ); + savefile->ReadBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Restore( savefile, self ); +// RAVEN END +} + +/* +===================== +idAnimState::Init +===================== +*/ +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it +void idAnimState::Init( idEntity *owner, idAnimator *_animator, int animchannel ) { +// RAVEN BEGIN + assert( owner ); + assert( _animator ); + self = owner; + animator = _animator; + channel = animchannel; + + stateThread.SetName ( va("%s_anim_%d", owner->GetName(), animchannel ) ); + stateThread.SetOwner ( owner ); +} + +/* +===================== +idAnimState::Shutdown +===================== +*/ +void idAnimState::Shutdown( void ) { + stateThread.Clear ( true ); +} + +/* +===================== +idAnimState::PostState +===================== +*/ +void idAnimState::PostState ( const char* statename, int blendFrames, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, blendFrames, delay, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + disabled = false; +} + +/* +===================== +idAnimState::SetState +===================== +*/ +void idAnimState::SetState( const char *statename, int blendFrames, int flags ) { + if ( SRESULT_OK != stateThread.SetState ( statename, blendFrames, 0, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + disabled = false; + idleAnim = false; +} + +/* +===================== +idAnimState::StopAnim +===================== +*/ +void idAnimState::StopAnim( int frames ) { + animBlendFrames = 0; + animator->Clear( channel, gameLocal.time, FRAME2MS( frames ) ); +} + +/* +===================== +idAnimState::PlayAnim +===================== +*/ +void idAnimState::PlayAnim( int anim ) { + if ( anim ) { + animator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::CycleAnim +===================== +*/ +void idAnimState::CycleAnim( int anim ) { + if ( anim ) { + animator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::BecomeIdle +===================== +*/ +void idAnimState::BecomeIdle( void ) { + idleAnim = true; +} + +/* +===================== +idAnimState::Disabled +===================== +*/ +bool idAnimState::Disabled( void ) const { + return disabled; +} + +/* +===================== +idAnimState::AnimDone +===================== +*/ +bool idAnimState::AnimDone( int blendFrames ) const { + int animDoneTime; + + animDoneTime = animator->CurrentAnim( channel )->GetEndTime(); + if ( animDoneTime < 0 ) { + // playing a cycle + return false; + } else if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } else { + return false; + } +} + +/* +===================== +idAnimState::IsIdle +===================== +*/ +bool idAnimState::IsIdle( void ) const { + return disabled || idleAnim; +} + +/* +===================== +idAnimState::GetAnimFlags +===================== +*/ +animFlags_t idAnimState::GetAnimFlags( void ) const { + animFlags_t flags; + + memset( &flags, 0, sizeof( flags ) ); + if ( !disabled && !AnimDone( 0 ) ) { + flags = animator->GetAnimFlags( animator->CurrentAnim( channel )->AnimNum() ); + } + + return flags; +} + +/* +===================== +idAnimState::Enable +===================== +*/ +void idAnimState::Enable( int blendFrames ) { + if ( disabled ) { + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + } +} + +/* +===================== +idAnimState::Disable +===================== +*/ +void idAnimState::Disable( void ) { + disabled = true; + idleAnim = false; +} + +/* +===================== +idAnimState::UpdateState +===================== +*/ +bool idAnimState::UpdateState( void ) { + if ( disabled ) { + return false; + } + + stateThread.Execute ( ); + + return true; +} + +/*********************************************************************** + + idActor + +***********************************************************************/ + +const idEventDef AI_EnableEyeFocus( "enableEyeFocus" ); +const idEventDef AI_DisableEyeFocus( "disableEyeFocus" ); +const idEventDef AI_EnableBlink( "enableBlinking" ); +const idEventDef AI_DisableBlink( "disableBlinking" ); +const idEventDef EV_Footstep( "footstep" ); +const idEventDef EV_FootstepLeft( "leftFoot" ); +const idEventDef EV_FootstepRight( "rightFoot" ); +const idEventDef EV_EnableWalkIK( "EnableWalkIK" ); +const idEventDef EV_DisableWalkIK( "DisableWalkIK" ); +const idEventDef EV_EnableLegIK( "EnableLegIK", "d" ); +const idEventDef EV_DisableLegIK( "DisableLegIK", "d" ); +const idEventDef AI_StopAnim( "stopAnim", "dd" ); +const idEventDef AI_PlayAnim( "playAnim", "ds", 'd' ); +const idEventDef AI_PlayCycle( "playCycle", "ds", 'd' ); +const idEventDef AI_IdleAnim( "idleAnim", "ds", 'd' ); +const idEventDef AI_SetSyncedAnimWeight( "setSyncedAnimWeight", "ddf" ); +const idEventDef AI_SetBlendFrames( "setBlendFrames", "dd" ); +const idEventDef AI_GetBlendFrames( "getBlendFrames", "d", 'd' ); +const idEventDef AI_AnimDone( "animDone", "dd", 'd' ); +const idEventDef AI_OverrideAnim( "overrideAnim", "d" ); +const idEventDef AI_EnableAnim( "enableAnim", "dd" ); +const idEventDef AI_PreventPain( "preventPain", "f" ); +const idEventDef AI_DisablePain( "disablePain" ); +const idEventDef AI_EnablePain( "enablePain" ); +const idEventDef AI_SetAnimPrefix( "setAnimPrefix", "s" ); +const idEventDef AI_HasEnemies( "hasEnemies", NULL, 'd' ); +const idEventDef AI_NextEnemy( "nextEnemy", "E", 'e' ); +const idEventDef AI_ClosestEnemyToPoint( "closestEnemyToPoint", "v", 'e' ); +const idEventDef AI_GetHead( "getHead", NULL, 'e' ); + + +// RAVEN BEGIN +// bdube: added +const idEventDef AI_Flashlight("flashlight", "d" ); +const idEventDef AI_Teleport("teleport", "vv"); +const idEventDef AI_EnterVehicle ( "enterVehicle", "e" ); +const idEventDef AI_ExitVehicle ( "exitVehicle", "d" ); +const idEventDef AI_PostExitVehicle ( "", "d" ); + +//jshepard: change animation rate +const idEventDef AI_SetAnimRate ( "setAnimRate","f"); +//MCG: damage over time +const idEventDef EV_DamageOverTime ( "damageOverTime","ddEEvsfd" ); +const idEventDef EV_DamageOverTimeEffect ( "damageOverTimeEffect","dds" ); +// MCG: script-callable joint crawl effect +const idEventDef EV_JointCrawlEffect ( "jointCrawlEffect","sf" ); + +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idActor ) + EVENT( AI_EnableEyeFocus, idActor::Event_EnableEyeFocus ) + EVENT( AI_DisableEyeFocus, idActor::Event_DisableEyeFocus ) + EVENT( AI_EnableBlink, idActor::Event_EnableBlink ) + EVENT( AI_DisableBlink, idActor::Event_DisableBlink ) + EVENT( EV_Footstep, idActor::Event_Footstep ) + EVENT( EV_FootstepLeft, idActor::Event_Footstep ) + EVENT( EV_FootstepRight, idActor::Event_Footstep ) + EVENT( EV_EnableWalkIK, idActor::Event_EnableWalkIK ) + EVENT( EV_DisableWalkIK, idActor::Event_DisableWalkIK ) + EVENT( EV_EnableLegIK, idActor::Event_EnableLegIK ) + EVENT( EV_DisableLegIK, idActor::Event_DisableLegIK ) + EVENT( AI_PreventPain, idActor::Event_PreventPain ) + EVENT( AI_DisablePain, idActor::Event_DisablePain ) + EVENT( AI_EnablePain, idActor::Event_EnablePain ) + EVENT( AI_SetAnimPrefix, idActor::Event_SetAnimPrefix ) + EVENT( AI_SetSyncedAnimWeight, idActor::Event_SetSyncedAnimWeight ) + EVENT( AI_SetBlendFrames, idActor::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idActor::Event_GetBlendFrames ) + EVENT( AI_OverrideAnim, idActor::Event_OverrideAnim ) + EVENT( AI_EnableAnim, idActor::Event_EnableAnim ) + EVENT( AI_HasEnemies, idActor::Event_HasEnemies ) + EVENT( AI_NextEnemy, idActor::Event_NextEnemy ) + EVENT( AI_ClosestEnemyToPoint, idActor::Event_ClosestEnemyToPoint ) + EVENT( EV_StopSound, idActor::Event_StopSound ) + EVENT( AI_GetHead, idActor::Event_GetHead ) + +// RAVEN BEGIN +// bdube: added + EVENT( AI_Flashlight, idActor::Event_Flashlight ) + EVENT( AI_Teleport, idActor::Event_Teleport ) + EVENT( AI_EnterVehicle, idActor::Event_EnterVehicle ) + + // twhitaker: Yeah... this just got confusing. + // basically, I need a delay in between the time the space bar is hit and the time the person actually get's ejected. + // this is mostly for things such as screen fades when exiting a vehicle. This was the least obtrusive way I could think of. + EVENT( AI_ExitVehicle, idActor::Event_PreExitVehicle ) + EVENT( AI_PostExitVehicle, idActor::Event_ExitVehicle ) + +// jshepard: added + EVENT( AI_SetAnimRate, idActor::Event_SetAnimRate ) + +// twhitaker: added animation support (mostly for vehicle purposes) + EVENT( AI_PlayAnim, idActor::Event_PlayAnim ) + +// MCG: added recurring damage + EVENT( EV_DamageOverTime, idActor::Event_DamageOverTime ) + EVENT( EV_DamageOverTimeEffect, idActor::Event_DamageOverTimeEffect ) +// MCG: script-callable joint crawl effect + EVENT( EV_JointCrawlEffect, idActor::Event_JointCrawlEffect ) +// RAVEN END + +END_CLASS + +CLASS_STATES_DECLARATION ( idActor ) + STATE ( "Wait_Frame", idActor::State_Wait_Frame ) + STATE ( "Wait_LegsAnim", idActor::State_Wait_LegsAnim ) + STATE ( "Wait_TorsoAnim", idActor::State_Wait_TorsoAnim ) +END_CLASS_STATES + +// RAVEN END + +/* +===================== +idActor::idActor +===================== +*/ +idActor::idActor( void ) +{ + viewAxis.Identity(); + + use_combat_bbox = false; + head = NULL; + + eyeOffset.Zero(); + chestOffset.Zero(); + modelOffset.Zero(); + + team = 0; + rank = 0; + fovDot = 0.0f; + pain_debounce_time = 0; + pain_delay = 0; + + leftEyeJoint = INVALID_JOINT; + rightEyeJoint = INVALID_JOINT; + soundJoint = INVALID_JOINT; + + deltaViewAngles.Zero(); + + painTime = 0; + inDamageEvent = false; +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END + allowEyeFocus = false; + + blink_anim = NULL; + blink_time = 0; + blink_min = 0; + blink_max = 0; + + finalBoss = false; + + attachments.SetGranularity( 1 ); + + enemyNode.SetOwner( this ); + enemyList.SetOwner( this ); + + teamNode.SetOwner ( this ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlightHandle = -1; + + deathPushTime = 0; + + eyeOffsetJoint = INVALID_JOINT; + chestOffsetJoint = INVALID_JOINT; + + lightningEffects = 0; + lightningNextTime = 0; +} + +/* +===================== +idActor::~idActor +===================== +*/ +idActor::~idActor( void ) { +// RAVEN BEGIN +// bdube: flashlights + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef( flashlightHandle ); + flashlightHandle = -1; + } +// RAVEN END + + int i; + idEntity *ent; + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + ShutdownThreads(); +} + +/* +===================== +idActor::Spawn +===================== +*/ +void idActor::Spawn( void ) { + idEntity *ent; + idStr jointName; + float fovDegrees; + float fovDegreesClose; + + animPrefix = ""; + + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "team", "0", team ); + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + spawnArgs.GetBool( "use_combat_bbox", "0", use_combat_bbox ); + + viewAxis = GetPhysics()->GetAxis(); + + spawnArgs.GetFloat( "fov", "90", fovDegrees ); + spawnArgs.GetFloat( "fovClose", "200", fovDegreesClose ); + spawnArgs.GetFloat( "fovCloseRange", "180", fovCloseRange ); + SetFOV( fovDegrees, fovDegreesClose ); + + pain_debounce_time = 0; + + pain_delay = SEC2MS( spawnArgs.GetFloat( "pain_delay" ) ); + + LoadAF ( ); + + walkIK.Init( this, IK_ANIM, modelOffset ); + + // the animation used to be set to the IK_ANIM at this point, but that was fixed, resulting in + // attachments not binding correctly, so we're stuck setting the IK_ANIM before attaching things. + animator.ClearAllAnims( gameLocal.time, 0 ); +// RAVEN BEGIN +// jscott: new setframe stuff + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, animator.GetAnim( IK_ANIM ), frameBlend ); +// RAVEN END + + // spawn any attachments we might have + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_attach", NULL ); + while ( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue().c_str() ); + + // make items non-touchable so the player can't take them out of the character's hands + args.Set( "no_touch", "1" ); + + // don't let them drop to the floor + args.Set( "dropToFloor", "0" ); + + gameLocal.SpawnEntityDef( args, &ent ); + if ( !ent ) { + gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); + } else { + Attach( ent ); + } + kv = spawnArgs.MatchPrefix( "def_attach", kv ); + } + + SetupDamageGroups(); + + // MP sets up heads on players from UpdateModelSetup() + if( !gameLocal.isMultiplayer || !IsType( idPlayer::GetClassType() ) ) { + SetupHead ( ); + } + + // clear the bind anim + animator.ClearAllAnims( gameLocal.time, 0 ); + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } + + // set up blinking + blink_anim = headAnimator->GetAnim( "blink" ); + blink_time = 0; // it's ok to blink right away + blink_min = SEC2MS( spawnArgs.GetFloat( "blink_min", "0.5" ) ); + blink_max = SEC2MS( spawnArgs.GetFloat( "blink_max", "8" ) ); + fl.allowAutoBlink = spawnArgs.GetBool( "allowAutoBlink", "1" ); + + if ( spawnArgs.GetString( "sound_bone", "", jointName ) ) { + soundJoint = animator.GetJointHandle( jointName ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + finalBoss = spawnArgs.GetBool( "finalBoss" ); + +// RAVEN BEGIN +// bdube: flashlight + flashlightJoint = animator.GetJointHandle( spawnArgs.GetString ( "joint_flashlight", "flashlight" ) ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlight.suppressLightInViewID = entityNumber + 1; + flashlight.allowLightInViewID = 0; + flashlight.lightId = 1 + entityNumber; + + flashlight.allowLightInViewID = 1; + + idVec3 color; + spawnArgs.GetVector ( "flashlightColor", "1 1 1", color ); + + flashlight.pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + flashlight.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_flashlight", "muzzleflash" ), false ); + flashlight.shaderParms[ SHADERPARM_RED ] = color[0]; + flashlight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + flashlight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + flashlight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + flashlight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + flashlight.lightRadius[0] = flashlight.lightRadius[1] = + flashlight.lightRadius[2] = spawnArgs.GetFloat ( "flashlightRadius" ); + + if ( !flashlight.pointLight ) { + flashlight.target = spawnArgs.GetVector( "flashlightTarget" ); + flashlight.up = spawnArgs.GetVector( "flashlightUp" ); + flashlight.right = spawnArgs.GetVector( "flashlightRight" ); + flashlight.end = spawnArgs.GetVector( "flashlightTarget" ); + } + + spawnArgs.GetVector ( "flashlightOffset", "0 0 0", flashlightOffset ); + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + +// RAVEN END + + stateThread.SetName ( GetName() ); + stateThread.SetOwner ( this ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = true; +// RAVEN END + + FinishSetup(); +} + +/* +================ +idActor::FinishSetup +================ +*/ +void idActor::FinishSetup( void ) { + if ( spawnArgs.GetBool ( "flashlight", "0" ) ) { + FlashlightUpdate ( true ); + } + + SetupBody(); +} + +/* +================ +idActor::SetupHead +================ +*/ +void idActor::SetupHead( const char* headDefName, idVec3 headOffset ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); +// jshepard: allow for heads to override persona defs + headDefName = spawnArgs.GetString( "override_head", headDefName ); + } + + if ( headDefName[ 0 ] ) { + // free the old head if we want a new one + if( gameLocal.isServer ) { + if( head && idStr::Icmp( head->spawnArgs.GetString( "classname" ), headDefName ) ) { + head->SetName( va( "%s_oldhead", name.c_str() ) ); + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } else if( head ) { + // the current head is OK + return; + } + } + + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin + headOffset ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } + + if ( head ) { + int i; + // set the damage joint to be part of the head damage group + for( i = 0; i < damageGroups.Num(); i++ ) { + if ( damageGroups[ i ] == "head" ) { + head->SetDamageJoint ( static_cast( i ) ); + break; + } + } + + head->InitCopyJoints ( ); + head->SetInstance( instance ); + } +} + +/* +================ +idActor::Restart +================ +*/ +void idActor::Restart( void ) { + assert( !head.GetEntity() ); + // MP sets up heads from UpdateModelSetup() + if( !gameLocal.isMultiplayer ) { + SetupHead(); + } + FinishSetup(); +} + +/* +================ +idActor::Save + +archive object for savegame file +================ +*/ +void idActor::Save( idSaveGame *savefile ) const { + idActor *ent; + int i; + + savefile->WriteInt( team ); + + // cnicholson: This line was already commented out, so we aint changing it. + // No need to write/read teamNode + // idLinkList teamNode; + + savefile->WriteInt( rank ); + savefile->WriteMat3( viewAxis ); + +// twhitaker: this confuses me... should we be writing out enemyList.Num() or enemyList.Next()->enemyNode->Num(). I'm not sure what these variables represent. +// cnicholson: bdube said to do it how id does it, so here goes: + savefile->WriteInt( enemyList.Num() ); + for ( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + savefile->WriteObject( ent ); + } + + savefile->WriteInt( lightningNextTime );// cnicholson: Added unwritten var + savefile->WriteInt( lightningEffects ); // cnicholson: Added unwritten var + + savefile->WriteFloat( fovDot ); + savefile->WriteFloat( fovCloseDot ); + savefile->WriteFloat( fovCloseRange ); + savefile->WriteVec3( eyeOffset ); + savefile->WriteVec3( chestOffset ); + savefile->WriteVec3( modelOffset ); + savefile->WriteAngles( deltaViewAngles ); + + savefile->WriteInt( pain_debounce_time ); + savefile->WriteInt( pain_delay ); + + savefile->WriteInt( damageGroups.Num() ); + for( i = 0; i < damageGroups.Num(); i++ ) { + savefile->WriteString( damageGroups[ i ] ); + } + + savefile->WriteInt( damageScale.Num() ); + for( i = 0; i < damageScale.Num(); i++ ) { + savefile->WriteFloat( damageScale[ i ] ); + } +//MCG + savefile->WriteBool( inDamageEvent ); + + savefile->WriteBool( use_combat_bbox ); + + savefile->WriteJoint( leftEyeJoint ); + savefile->WriteJoint( rightEyeJoint ); + savefile->WriteJoint( soundJoint ); + savefile->WriteJoint( eyeOffsetJoint ); + savefile->WriteJoint( chestOffsetJoint ); + savefile->WriteJoint( neckJoint ); + savefile->WriteJoint( headJoint ); + + walkIK.Save( savefile ); + + savefile->WriteString( animPrefix ); + savefile->WriteString( painType ); + savefile->WriteString( painAnim ); + + savefile->WriteInt( blink_anim ); + savefile->WriteInt( blink_time ); + savefile->WriteInt( blink_min ); + savefile->WriteInt( blink_max ); + + headAnim.Save( savefile ); + torsoAnim.Save( savefile ); + legsAnim.Save( savefile ); + + stateThread.Save( savefile ); + + // idEntityPtr head; + head.Save( savefile ); // cnicholson: Added unwritten var + + savefile->WriteBool( disablePain ); + savefile->WriteBool( allowEyeFocus ); + savefile->WriteBool( finalBoss ); + + savefile->WriteInt( painTime ); + + savefile->WriteInt( attachments.Num() ); + for ( i = 0; i < attachments.Num(); i++ ) { + attachments[i].ent.Save( savefile ); + savefile->WriteInt( attachments[i].channel ); + } + + vehicleController.Save ( savefile ); + + // These aren't saved in the same order as they're declared, due to the dependency (I didn't see the need to change the order of declaration) + savefile->WriteInt ( flashlightHandle ); + savefile->WriteJoint ( flashlightJoint ); + savefile->WriteVec3 ( flashlightOffset ); + savefile->WriteRenderLight ( flashlight ); + + savefile->WriteInt( deathPushTime ); + savefile->WriteVec3( deathPushForce ); + savefile->WriteJoint( deathPushJoint ); +} + +/* +================ +idActor::Restore + +unarchives object from save game file +================ +*/ +void idActor::Restore( idRestoreGame *savefile ) { + int i, num; + idActor *ent; + + savefile->ReadInt( team ); + +// cnicholson: This line was already commented out, so we aint changing it. +// No need to write/read teamNode + // idLinkList teamNode; + + savefile->ReadInt( rank ); + savefile->ReadMat3( viewAxis ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->enemyNode.AddToEnd( enemyList ); + } + } + + savefile->ReadInt( lightningEffects ); + savefile->ReadInt( lightningNextTime ); + + savefile->ReadFloat( fovDot ); + savefile->ReadFloat( fovCloseDot ); + savefile->ReadFloat( fovCloseRange ); + savefile->ReadVec3( eyeOffset ); + savefile->ReadVec3( chestOffset ); + savefile->ReadVec3( modelOffset ); + savefile->ReadAngles( deltaViewAngles ); + + savefile->ReadInt( pain_debounce_time ); + savefile->ReadInt( pain_delay ); + + savefile->ReadInt( num ); + damageGroups.SetGranularity( 1 ); + damageGroups.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( damageGroups[ i ] ); + } + + savefile->ReadInt( num ); + damageScale.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadFloat( damageScale[ i ] ); + } +//MCG + savefile->ReadBool( inDamageEvent ); + + savefile->ReadBool( use_combat_bbox ); + + savefile->ReadJoint( leftEyeJoint ); + savefile->ReadJoint( rightEyeJoint ); + savefile->ReadJoint( soundJoint ); + savefile->ReadJoint( eyeOffsetJoint ); + savefile->ReadJoint( chestOffsetJoint ); + savefile->ReadJoint( neckJoint ); + savefile->ReadJoint( headJoint ); + + walkIK.Restore( savefile ); + + savefile->ReadString( animPrefix ); + savefile->ReadString( painType ); + savefile->ReadString( painAnim ); + + savefile->ReadInt( blink_anim ); + savefile->ReadInt( blink_time ); + savefile->ReadInt( blink_min ); + savefile->ReadInt( blink_max ); + + headAnim.Restore( savefile ); + torsoAnim.Restore( savefile ); + legsAnim.Restore( savefile ); + + stateThread.Restore( savefile, this ); + +// cnicholson: Restore unread var + // idEntityPtr head; + head.Restore(savefile); + + savefile->ReadBool( disablePain ); + savefile->ReadBool( allowEyeFocus ); + savefile->ReadBool( finalBoss ); + + savefile->ReadInt( painTime ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idAttachInfo &attach = attachments.Alloc(); + attach.ent.Restore( savefile ); + savefile->ReadInt( attach.channel ); + } + +// RAVEN BEGIN +// bdube: added + vehicleController.Restore ( savefile ); + + savefile->ReadInt ( flashlightHandle ); + savefile->ReadJoint ( flashlightJoint ); + savefile->ReadVec3 ( flashlightOffset ); + savefile->ReadRenderLight ( flashlight ); + if ( flashlightHandle != -1 ) { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + + savefile->ReadInt( deathPushTime ); + savefile->ReadVec3( deathPushForce ); + savefile->ReadJoint( deathPushJoint ); + +// mekberg: update this + FlashlightUpdate( ); +// RAVEN END +} + +/* +================ +idActor::Hide +================ +*/ +void idActor::Hide( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Hide(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Off(); + } + } + } + UnlinkCombat(); +} + +/* +================ +idActor::Show +================ +*/ +void idActor::Show( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Show(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->On(); + } + } + } + LinkCombat(); +} + +/* +============== +idActor::GetDefaultSurfaceType +============== +*/ +int idActor::GetDefaultSurfaceType( void ) const { + return SURFTYPE_FLESH; +} + +/* +================ +idActor::ProjectOverlay +================ +*/ +void idActor::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + idEntity *ent; + idEntity *next; + + idEntity::ProjectOverlay( origin, dir, size, material ); + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + if ( ent->fl.takedamage && ent->spawnArgs.GetBool( "bleed" ) ) { + ent->ProjectOverlay( origin, dir, size, material ); + } + } + } +} + +/* +================ +idActor::LoadAF +================ +*/ +bool idActor::LoadAF( const char* keyname, bool purgeAF /* = false */ ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) || !fileName.Length() ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName, purgeAF ); +} + +/* +===================== +idActor::SetupBody +===================== +*/ +void idActor::SetupBody( void ) { + const char* jointname; + idAnimator* headAnimator; + float height; + + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ClearAllJoints(); + + // Cache the head entity pointer and determine which animator to use + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator ( ); + } else { + headAnimator = GetAnimator( ); + } + + // Get left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_leftEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_leftEye" ); + } + leftEyeJoint = headAnimator->GetJointHandle( jointname ); + + // Get right eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_rightEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_rightEye" ); + } + rightEyeJoint = headAnimator->GetJointHandle( jointname ); + + // If head height is specified, just use that + if ( spawnArgs.GetFloat ( "eye_height", "0", height ) ) { + SetEyeHeight ( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_eyeOffset", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_eyeOffset" ); + } + // Get the eye offset joint + eyeOffsetJoint = headAnimator->GetJointHandle ( jointname ); + } + + // If eye height is specified, just use that + if ( spawnArgs.GetFloat ( "chest_height", "0", height ) ) { + SetChestHeight ( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + spawnArgs.GetString ( "joint_chestOffset", "", &jointname ); + // Get the chest offset joint + chestOffsetJoint = animator.GetJointHandle ( jointname ); + } + + // Get the neck joint + spawnArgs.GetString ( "joint_look_neck", "", &jointname ); + neckJoint = animator.GetJointHandle ( jointname ); + + // Get the head joint + spawnArgs.GetString ( "joint_look_head", "", &jointname ); + headJoint = animator.GetJointHandle ( jointname ); + + headAnim.Init ( this, &animator, ANIMCHANNEL_HEAD ); + torsoAnim.Init( this, &animator, ANIMCHANNEL_TORSO ); + legsAnim.Init( this, &animator, ANIMCHANNEL_LEGS ); +} + +/* +===================== +idActor::CheckBlink +===================== +*/ +void idActor::CheckBlink( void ) { + // check if it's time to blink + if ( !blink_anim || ( health <= 0 ) || ( blink_time > gameLocal.time ) || !fl.allowAutoBlink ) { + return; + } + + idAnimator *animator = head.GetEntity() ? head->GetAnimator() : &this->animator; + animator->PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + + // set the next blink time + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +================ +idActor::GetPhysicsToVisualTransform +================ +*/ +bool idActor::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } +// RAVEN BEGIN +// bdube: position player in seat (nmckenzie: copy and paste from the player version of this call) + if ( vehicleController.IsDriving ( ) ) { + vehicleController.GetDriverPosition ( origin, axis ); + return true; + } +// RAVEN END + + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +idActor::GetPhysicsToSoundTransform +================ +*/ +bool idActor::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + origin += modelOffset; + axis = viewAxis; + } else { + origin = GetPhysics()->GetGravityNormal() * -eyeOffset.z; + axis.Identity(); + } + return true; +} + +/*********************************************************************** + + script state management + +***********************************************************************/ + +/* +================ +idActor::ShutdownThreads +================ +*/ +void idActor::ShutdownThreads( void ) { + headAnim.Shutdown(); + torsoAnim.Shutdown(); + legsAnim.Shutdown(); +} + +/* +===================== +idActor::OnStateThreadClear +===================== +*/ +void idActor::OnStateThreadClear( const char *statename, int flags ) { +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const char *statename, int flags ) { + OnStateThreadClear( statename, flags ); + stateThread.SetState ( statename, 0, 0, flags ); +} + +/* +===================== +idActor::PostState +===================== +*/ +void idActor::PostState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::InterruptState +===================== +*/ +void idActor::InterruptState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.InterruptState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::UpdateState +===================== +*/ +void idActor::UpdateState ( void ) { + stateThread.Execute ( ); +} + +/*********************************************************************** + + vision + +***********************************************************************/ + +/* +===================== +idActor::setFov +===================== +*/ +void idActor::SetFOV( float fov, float fovClose ) { + fovDot = idMath::Cos( DEG2RAD( fov * 0.5f ) ); + fovCloseDot = idMath::Cos( DEG2RAD( fovClose * 0.5f ) ); +} + +/* +===================== +idActor::SetEyeHeight +===================== +*/ +void idActor::SetEyeHeight( float height ) { + eyeOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::EyeHeight +===================== +*/ +float idActor::EyeHeight( void ) const { + return eyeOffset.z; +} + +/* +===================== +idActor::SetChestHeight +===================== +*/ +void idActor::SetChestHeight( float height ) { + chestOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::GetEyePosition +===================== +*/ +idVec3 idActor::GetEyePosition( void ) const { + return GetPhysics()->GetOrigin() + eyeOffset * viewAxis; +} + +/* +===================== +idActor::GetChestPosition +===================== +*/ +idVec3 idActor::GetChestPosition ( void ) const { + return GetPhysics()->GetOrigin() + chestOffset * viewAxis; +} + +// RAVEN BEGIN +// bdube: flashlights + +/* +===================== +idActor::GetGroundEntity +===================== +*/ +idEntity* idActor::GetGroundEntity( void ) const { + return static_cast(GetPhysics())->GetGroundEntity(); +} + +/* +===================== +idActor::Present +===================== +*/ +void idActor::Present( void ) { + idAFEntity_Gibbable::Present(); + + FlashlightUpdate(); +} + +/* +===================== +idActor::Event_Teleport +===================== +*/ +void idActor::Event_Teleport( idVec3 &newPos, idVec3 &newAngles ) { + Teleport ( newPos, newAngles.ToAngles(), NULL ); +} + +/* +===================== +idActor::Event_EnterVehicle +===================== +*/ +void idActor::Event_EnterVehicle ( idEntity* vehicle ) { + if ( IsInVehicle ( ) ) { + return; + } + EnterVehicle ( vehicle ); +} + +/* +===================== +idActor::Event_ExitVehicle +===================== +*/ +void idActor::Event_ExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + ExitVehicle ( force ); +} + +/* +===================== +idActor::Event_PreExitVehicle +===================== +*/ +void idActor::Event_PreExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + + // call the script func regardless of the fact that we may not be getting out just yet. + // this allows things in the script to happen before ejection actually occurs (such as screen fades). + vehicleController.GetVehicle()->OnExit(); + + // this is done because having an exit delay when the player is dead was causing bustedness if you died in the walker + // specifically the restart menu would not appear if you were still in the walker + if ( health > 0 ) { + PostEventMS( &AI_PostExitVehicle, vehicleController.GetVehicle()->spawnArgs.GetInt( "exit_vehicle_delay" ), force ); + } + else { + ExitVehicle(true); + } +} + +/* +===================== +idActor::Event_Flashlight +===================== +*/ +void idActor::Event_Flashlight( bool on ) { + if ( on ) { + FlashlightUpdate(true); + } else { + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( flashlightHandle ); + flashlightHandle = -1; + } + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + StopEffect( "fx_flashlight", true ); + } + } +} + +/* +===================== +idActor::FlashlightUpdate +===================== +*/ +void idActor::FlashlightUpdate ( bool forceOn ) { + + // Dont do anything if flashlight is off and its not being forced on + if ( !forceOn && flashlightHandle == -1 ) { + return; + } + + if ( !flashlight.lightRadius[0] || flashlightJoint == INVALID_JOINT ) { + return; + } + + if ( forceOn && flashlightHandle == -1 ) { + //first time turning it on + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + ShowSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + PlayEffect( "fx_flashlight", flashlightJoint, true ); + } + } + + // the flash has an explicit joint for locating it + GetJointWorldTransform ( flashlightJoint, gameLocal.time, flashlight.origin, flashlight.axis ); + flashlight.origin += flashlightOffset * flashlight.axis; + + if ( flashlightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( flashlightHandle, &flashlight ); + } else { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + +} + +// RAVEN END + +/* +===================== +idActor::GetViewPos +===================== +*/ +void idActor::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + origin = GetEyePosition(); + axis = viewAxis; +} + +/* +===================== +idActor::CheckFOV +===================== +*/ +bool idActor::CheckFOV( const idVec3 &pos, float ang ) const { + float testAng = ( ang != -1.0f ) ? idMath::Cos( DEG2RAD( ang * 0.5f ) ) : fovDot; + if ( testAng == 1.0f ) { + return true; + } + + if(!GetPhysics()) { + return false; + } + + float dot; + float dist; + idVec3 delta; + + delta = pos - GetEyePosition(); + dist = delta.LengthFast(); + + //NOTE!!! + //This logic is BACKWARDS, but it's too late in the project + //for me to feel comfortable fixing this. It SHOULD be: + //if ( ang == -1.0f ) + // - MCG + if ( ang != -1.0f ) + {//not overriding dot test value + if (distGetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + dot = viewAxis[ 0 ] * delta; + + return ( dot >= testAng ); +} + +/* +===================== +idActor::HasFOV +===================== +*/ + +bool idActor::HasFOV( idEntity *ent ) +{ + // Fixme: Make this do something, anything. + return true; +} +// RAVEN END + +/* +===================== +idActor::CanSee +===================== +*/ +bool idActor::CanSee( const idEntity *ent, bool useFov ) const { + return CanSeeFrom ( GetEyePosition ( ), ent, useFov ); +} + +/* +===================== +idActor::CanSeeFrom +===================== +*/ +bool idActor::CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFov ) const { + idVec3 toPos; + + if ( !ent || ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ((idActor*)ent)->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetAbsBounds().GetCenter ( ); + } + + return CanSeeFrom ( from, toPos, useFov ); +} + +bool idActor::CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFov ) const { + trace_t tr; + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + return true; + } + gameLocal.TracePoint( this, tr, from, toPos, MASK_OPAQUE, this ); + if ( tr.fraction >= 1.0f ) { // || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + + return false; +} + +/* +===================== +idActor::GetRenderView +===================== +*/ +renderView_t *idActor::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->viewaxis = viewAxis; + rv->vieworg = GetEyePosition(); + return rv; +} + +/*********************************************************************** + + Model/Ragdoll + +***********************************************************************/ + +/* +================ +idActor::SetCombatModel +================ +*/ +void idActor::SetCombatModel( void ) { + idAFAttachment *headEnt; + +// RAVEN BEGIN +// bdube: set the combat model reguardless + if ( 1 ) { // !use_combat_bbox ) { +// RAVEN END + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->SetCombatModel(); + } + } +} + +/* +================ +idActor::GetCombatModel +================ +*/ +idClipModel *idActor::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idActor::LinkCombat +================ +*/ +void idActor::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden || use_combat_bbox ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idActor::UnlinkCombat +================ +*/ +void idActor::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idActor::StartRagdoll +================ +*/ +bool idActor::StartRagdoll( void ) { + float slomoStart, slomoEnd; + float jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd; + float contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd; + + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // Raise the origin up 5 units to help ensure the ragdoll doesnt start in the ground + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -5.0f ); + UpdateModelTransform(); + + // disable the monster bounding box + GetPhysics()->DisableClip(); + + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" ); + slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" ); + + // do the first part of the death in slow motion + af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd ); + + jointFrictionDent = spawnArgs.GetFloat( "ragdoll_jointFrictionDent", "0.1" ); + jointFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionStart", "0.2" ); + jointFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionEnd", "1.2" ); + + // set joint friction dent + af.GetPhysics()->SetJointFrictionDent( jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd ); + + contactFrictionDent = spawnArgs.GetFloat( "ragdoll_contactFrictionDent", "0.1" ); + contactFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionStart", "1.0" ); + contactFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionEnd", "2.0" ); + + // set contact friction dent + af.GetPhysics()->SetContactFrictionDent( contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd ); + + // drop any items the actor is holding + idList list; + idMoveableItem::DropItems( this, "death", &list ); + for ( int i = 0; i < list.Num(); i++ ) { + if ( list[i] && list[i]->GetPhysics() ) + { + idVec3 velocity; + float pitchDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float yawDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float rollDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + velocity.Set( pitchDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + yawDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + (gameLocal.random.RandomFloat() * 300.0f) + 100.0f ); + list[i]->GetPhysics()->SetAngularVelocity( idVec3( pitchDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + yawDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + rollDir*((gameLocal.random.RandomFloat() * 10.0f) + 3.0f))); + if ( gibbed ) { + //only throw them if we end up gibbed? + list[i]->GetPhysics()->SetLinearVelocity( velocity ); + } + } + } + + // drop any articulated figures the actor is holding + idAFEntity_Base::DropAFs( this, "death", NULL ); + + RemoveAttachments(); + +// RAVEN BEGIN +// bdube: evaluate one ragdoll frame + RunPhysics(); +// RAVEN END + + return true; +} + +/* +================ +idActor::StopRagdoll +================ +*/ +void idActor::StopRagdoll( void ) { + if ( af.IsActive() ) { + af.Stop(); + } +} + +/* +================ +idActor::UpdateAnimationControllers +================ +*/ +bool idActor::UpdateAnimationControllers( void ) { + + if ( af.IsActive() ) { + return idAFEntity_Gibbable::UpdateAnimationControllers(); + } else { + animator.ClearAFPose(); + } + + if ( walkIK.IsInitialized() ) { + walkIK.Evaluate(); + return true; + } + + idMat3 axis; + idAnimatedEntity* headEnt = head.GetEntity( ); + if ( !headEnt ) { + headEnt = this; + } + + // Dynamically update the eye offset if a joint was specified + if ( eyeOffsetJoint != INVALID_JOINT ) { + headEnt->GetJointWorldTransform( eyeOffsetJoint, gameLocal.time, eyeOffset, axis ); + eyeOffset = (eyeOffset - GetPhysics()->GetOrigin()) * viewAxis.Transpose( ); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorRed, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + // Dynamically update the chest offset if a joint was specified + if ( chestOffsetJoint != INVALID_JOINT ) { + GetJointWorldTransform( chestOffsetJoint, gameLocal.time, chestOffset, axis ); + chestOffset = ( chestOffset - GetPhysics()->GetOrigin() ) * viewAxis.Transpose(); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorPink, GetChestPosition(), GetChestPosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + return false; +} + +/* +================ +idActor::RemoveAttachments +================ +*/ +void idActor::RemoveAttachments( void ) { + int i; + idEntity *ent; + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && ent->spawnArgs.GetBool( "remove" ) ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +================ +idActor::Attach +================ +*/ +void idActor::Attach( idEntity *ent ) { + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + idStr jointName; + idAttachInfo &attach = attachments.Alloc(); + idAngles angleOffset; + idVec3 originOffset; + + jointName = ent->spawnArgs.GetString( "joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); + } + + angleOffset = ent->spawnArgs.GetAngles( "angles" ); + originOffset = ent->spawnArgs.GetVector( "origin" ); + + attach.channel = animator.GetChannelForJoint( joint ); + GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + attach.ent = ent; + + ent->SetOrigin( origin + originOffset * renderEntity.axis ); + idMat3 rotate = angleOffset.ToMat3(); + idMat3 newAxis = rotate * axis; + ent->SetAxis( newAxis ); + ent->BindToJoint( this, joint, true ); + ent->cinematic = cinematic; +} + +idEntity* idActor::FindAttachment( const char* attachmentName ) +{ + idEntity *ent = NULL; + const char* fullName = va("idAFAttachment_%s",attachmentName); + // find the specified attachment + for( int i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && !ent->name.CmpPrefix(fullName) ) { + return ent; + } + } + return NULL; +} + +void idActor::HideAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Hide(); + } +} + +void idActor::ShowAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Show(); + } +} + +/* +================ +idActor::Teleport +================ +*/ +void idActor::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + viewAxis = angles.ToMat3(); + + UpdateVisuals(); + + if ( !IsHidden() ) { + // kill anything at the new position + gameLocal.KillBox( this ); + } +} + +/* +================ +idActor::GetDeltaViewAngles +================ +*/ +const idAngles &idActor::GetDeltaViewAngles( void ) const { + return deltaViewAngles; +} + +/* +================ +idActor::SetDeltaViewAngles +================ +*/ +void idActor::SetDeltaViewAngles( const idAngles &delta ) { + deltaViewAngles = delta; +} + +/* +================ +idActor::HasEnemies +================ +*/ +bool idActor::HasEnemies( void ) const { + idActor *ent; + + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden ) { + return true; + } + } + + return false; +} + +/* +================ +idActor::ClosestEnemyToPoint +================ +*/ +idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos, float maxRange, bool returnFirst, bool checkPVS ) { + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + pvsHandle_t pvs; + + //just to supress the compiler warning + pvs.i = 0; + + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + } + + bestDistSquared = maxRange?(maxRange*maxRange):idMath::INFINITY; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - pos; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + if ( checkPVS ) { + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + } + bestEnt = ent; + bestDistSquared = distSquared; + if ( returnFirst ) { + break; + } + } + } + + if ( checkPVS ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + return bestEnt; +} + +/* +================ +idActor::EnemyWithMostHealth +================ +*/ +idActor *idActor::EnemyWithMostHealth() { + idActor *ent; + idActor *bestEnt; + + int most = -9999; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden && ( ent->health > most ) ) { + bestEnt = ent; + most = ent->health; + } + } + return bestEnt; +} + +/* +================ +idActor::OnLadder +================ +*/ +bool idActor::OnLadder( void ) const { + return false; +} + +/* +============== +idActor::GetAASLocation +============== +*/ +void idActor::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + idVec3 size; + idBounds bounds; + + GetFloorPos( 64.0f, pos ); + if ( !aas ) { + areaNum = 0; + return; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aas->PushPointIntoAreaNum( areaNum, pos ); + } +} + +/*********************************************************************** + + animation state + +***********************************************************************/ + +/* +===================== +idActor::StopAnimState +===================== +*/ +void idActor::StopAnimState ( int channel ) { + GetAnimState ( channel ).Shutdown ( ); +} + +/* +===================== +idActor::PostAnimState +===================== +*/ +void idActor::PostAnimState ( int channel, const char* statename, int blendFrames, int delay, int flags ) { + GetAnimState ( channel ).PostState ( statename, blendFrames, delay, flags ); +} + +/* +===================== +idActor::SetAnimState +===================== +*/ +void idActor::SetAnimState( int channel, const char *statename, int blendFrames, int flags ) { + switch ( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.GetStateThread().Clear ( ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.GetStateThread().Clear ( ); + legsAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.GetStateThread().Clear ( ); + torsoAnim.Enable( blendFrames ); + break; + } + + OnStateChange ( channel ); + + PostAnimState ( channel, statename, blendFrames, flags ); +} + +/* +===================== +idActor::OnStateChange +===================== +*/ +void idActor::OnStateChange ( int channel ) { + allowEyeFocus = true; + + // Only clear eye focus on head channel change + if ( channel == ANIMCHANNEL_HEAD ) { + return; + } + + disablePain = false; +} + +/* +===================== +idActor::OnFriendlyFire +===================== +*/ +void idActor::OnFriendlyFire ( idActor* attacker ) { +} + +/* +===================== +idActor::UpdateAnimState +===================== +*/ +void idActor::UpdateAnimState( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ANIM); +// RAVEN END + headAnim.UpdateState(); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); +} + +/* +===================== +idActor::GetAnim +===================== +*/ +int idActor::GetAnim( int channel, const char *animname, bool forcePrefix ) { + int anim; + const char *temp; + idAnimator *animatorPtr; + + if ( channel == ANIMCHANNEL_HEAD ) { + if ( !head.GetEntity() ) { + return 0; + } + animatorPtr = head.GetEntity()->GetAnimator(); + } else { + animatorPtr = &animator; + } + + // Allow for anim substitution + animname = spawnArgs.GetString ( va("anim %s", animname ), animname ); + + if ( animPrefix.Length() ) { + temp = va( "%s_%s", animPrefix.c_str(), animname ); + anim = animatorPtr->GetAnim( temp ); + if ( anim ) { + return anim; + } else if ( forcePrefix ) { + return NULL; + } + } + + anim = animatorPtr->GetAnim( animname ); + + return anim; +} + +/* +=============== +idActor::SyncAnimChannels +=============== +*/ +void idActor::SyncAnimChannels( int channel, int syncToChannel, int blendFrames ) { + idAnimator *headAnimator; + idAFAttachment *headEnt; + int anim; + idAnimBlend *syncAnim; + int starttime; + int blendTime; + int cycle; + + blendTime = FRAME2MS( blendFrames ); + if ( channel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = animator.CurrentAnim( syncToChannel ); + if ( syncAnim ) { + anim = headAnimator->GetAnim( syncAnim->AnimFullName() ); + if ( !anim ) { + anim = headAnimator->GetAnim( syncAnim->AnimName() ); + } + if ( anim ) { + cycle = animator.CurrentAnim( syncToChannel )->GetCycleCount(); + starttime = animator.CurrentAnim( syncToChannel )->GetStartTime(); + headAnimator->PlayAnim( ANIMCHANNEL_LEGS, anim, gameLocal.time, blendTime ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetCycleCount( cycle ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetStartTime( starttime ); + } else { + headEnt->PlayIdleAnim( ANIMCHANNEL_LEGS, blendTime ); + } + } + } + } else if ( syncToChannel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS ); + if ( syncAnim ) { + anim = GetAnim( channel, syncAnim->AnimFullName() ); + if ( !anim ) { + anim = GetAnim( channel, syncAnim->AnimName() ); + } + if ( anim ) { + cycle = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetCycleCount(); + starttime = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetStartTime(); + animator.PlayAnim( channel, anim, gameLocal.time, blendTime ); + animator.CurrentAnim( channel )->SetCycleCount( cycle ); + animator.CurrentAnim( channel )->SetStartTime( starttime ); + } + } + } + } else { + animator.SyncAnimChannels( channel, syncToChannel, gameLocal.time, blendTime ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idActor::Gib +============ +*/ +void idActor::Gib( const idVec3 &dir, const char *damageDefName ) { + // for multiplayer we use client-side models + if ( gameLocal.isMultiplayer ) { + return; + } + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + StopSound( SND_CHANNEL_VOICE, false ); + + gameLocal.PlayEffect ( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +} + +void idActor::CheckDeathObjectives( void ) +{ + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player || !player->GetObjectiveHud() ) { + return; + } + + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_failed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_failed" ) ) ); + + player->FailObjective( spawnArgs.GetString( "objectivetitle_failed" ) ); + } + + if ( spawnArgs.GetString( "objectivetitle_completed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_completed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_completed" ) ) ); + + player->CompleteObjective( spawnArgs.GetString( "objectivetitle_completed" ) ); + } +} +/* +============ +idActor::Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +Bleeding wounds and surface overlays are applied in the collision code that +calls Damage() +============ +*/ +void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // friendly fire damage + bool noDmgFeedback = false; + if ( attacker->IsType ( idActor::Type ) && static_cast(attacker)->team == team ) { + + OnFriendlyFire ( static_cast(attacker) ); + + // jshepard: + // if the player deals friendly fire damage it is reduced to 0. If the damage is splash damage, + // then the victim should use a pain anim. + if( static_cast( attacker ) == gameLocal.GetLocalPlayer() ) { + + //play pain (maybe one day a special anim?) for damages that have the cower keyword + if ( damageDef->GetBool( "cower" )) { + Pain( inflictor, attacker, damage, dir, location ); + } + + //reduce the damage + damage = 0; + noDmgFeedback = true; + } + + // reduce friendly fire damage by the teamscale + damage = floor( damage * damageDef->GetFloat ( "teamScale", "0.5" ) ); + + + } + + if ( !IsType( idPlayer::GetClassType() ) && attacker->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)attacker)->aifl.killerGuard ) { + //Hard-coded to do lots of damage + damage = 100; + } + } + + if ( !noDmgFeedback ) { + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + } + +// RAVEN BEGIN +// jnewquist: FIXME - Was this removed from Xenon intentionally? +#ifdef _XENON + // singleplayer stat reporting. + if(!gameLocal.isMultiplayer) { + int methodOfDeath = -1; + // jnewquist: Use accessor for static class type + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + // RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::GetClassType() ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + if( methodOfDeath != -1 && attacker && attacker->IsType( idActor::Type ) ) { +// jnewquist: Fix Xenon compile warning + statManager->WeaponHit( static_cast (attacker) , this, methodOfDeath, !!damage ); + } + } +#endif +// RAVEN END + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + if ( damage > 0 ) { + int oldHealth = health; + AdjustHealthByDamage ( damage ); + if ( health <= 0 ) { + + //allow for quick burning + if (damageDef->GetFloat( "quickburn", "0" ) && !spawnArgs.GetFloat("no_quickburn", "0")) { + fl.quickBurn = true; + } + + if ( health < -999 ) { + health = -999; + } + //annoying hack for StartRagdoll + bool saveGibbed = gibbed; + bool canDMG_Gib = (spawnArgs.GetBool( "gib" ) | spawnArgs.GetBool( "DMG_gib" )); + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + gibbed = true; + } + } + Killed( inflictor, attacker, damage, dir, location ); + gibbed = saveGibbed; + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + Gib( dir, damageDefName ); + } + } + + if ( oldHealth > 0 && !gibbed && !fl.quickBurn) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::GetClassType() ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, damageDef, pushScale ); + } + } else { + painType = damageDef->GetString ( "pain" ); + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + /* + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calli/ng af.Rest() + BecomeActive( TH_PHYSICS ); + } + */ + } +} + +/* +===================== +idActor::InitDeathPush +===================== +*/ +void idActor::InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale ) { + idVec2 forceMin; + idVec2 forceMax; + + if( !af.IsActive() ) { + return; + } + + if ( deathPushTime > gameLocal.time ) { + return; + } + + if ( !damageDict->GetInt ( "deathPush", "0", deathPushTime ) || deathPushTime <= 0 ) { + return; + } + + damageDict->GetVec2( "deathPushMin", "", forceMin ); + damageDict->GetVec2( "deathPushMax", "", forceMax ); + +/* + forceMin *= (pushScale * GetPhysics()->GetMass()); + forceMax *= (pushScale * GetPhysics()->GetMass()); +*/ + forceMin *= (pushScale); + forceMax *= (pushScale); + + deathPushForce = dir; + deathPushForce.Normalize ( ); + deathPushForce = rvRandom::flrand ( forceMin.x, forceMax.x ) * deathPushForce + + -rvRandom::flrand ( forceMin.y, forceMax.y ) * GetPhysics()->GetGravityNormal ( ); + + deathPushTime += gameLocal.time; + deathPushJoint = (jointHandle_t) location; +} + +/* +===================== +idActor::DeathPush +===================== +*/ +void idActor::DeathPush ( void ) { + if ( deathPushTime <= gameLocal.time ) { + return; + } + + idVec3 center; + center = GetPhysics()->GetAbsBounds ( ).GetCenter(); + + GetPhysics()->ApplyImpulse ( 0, center, -0.5f * GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()) * GetPhysics()->GetGravity ( ) ); + + if ( deathPushJoint != INVALID_JOINT ) { + idVec3 origin; + idMat3 axis; + GetJointWorldTransform ( deathPushJoint, gameLocal.time, origin, axis ); + GetPhysics()->ApplyImpulse ( 0, origin, deathPushForce ); + } else { + GetPhysics()->ApplyImpulse ( 0, center, deathPushForce ); + } +} + +/* +===================== +idActor::SkipImpulse +===================== +*/ +bool idActor::SkipImpulse( idEntity* ent, int id ) { + return idAFEntity_Gibbable::SkipImpulse( ent, id ) || health <= 0 || gibbed || ent->IsType( idActor::GetClassType() ) || ent->IsType( idProjectile::GetClassType() ); +} + +/* +===================== +idActor::AddDamageEffect +===================== +*/ +void idActor::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !gameLocal.isMultiplayer && inflictor && inflictor->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast(inflictor)->team == team ) { + return; + } + } + idAFEntity_Gibbable::AddDamageEffect( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +idActor::ClearPain +===================== +*/ +void idActor::ClearPain( void ) { + pain_debounce_time = 0; +} + +/* +===================== +idActor::Pain +===================== +*/ +bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + if ( gameLocal.time < pain_debounce_time ) { + return false; + } + + // No pain if being hit by a friendly target + // jshepard: friendly targets can now cause pain +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return false; + } + } +*/ + + // don't play pain sounds more than necessary + pain_debounce_time = gameLocal.time + pain_delay; + + float f; +// RAVEN BEGIN +// mekberg: fixed divide by zero + float spawnHealth = spawnArgs.GetFloat ( "health", "1" ); + if (spawnHealth<1.0f) { + spawnHealth = 1.0f; // more devide by zero nonsense + } + f = (float)damage / spawnHealth; +// RAVEN END + if( gameLocal.isMultiplayer && IsType( idPlayer::GetClassType() ) && (health < 0.25f * ((idPlayer*)this)->inventory.maxHealth) ) { + StartSound( "snd_pain_low_health", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + if ( f > 0.75f ) { + StartSound( "snd_pain_huge", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.5f ) { + StartSound( "snd_pain_large", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.25f ) { + StartSound( "snd_pain_medium", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + StartSound( "snd_pain_small", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( disablePain || ( gameLocal.time < painTime ) ) { + // don't play a pain anim + return false; + } + + // set the pain anim + idStr damageGroup = GetDamageGroup( location ); + + painAnim.Clear ( ); + + // If we have both a damage group and a pain type then check that combination first + if ( damageGroup.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s_%s", painType.c_str(), damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the pain type? + if ( !painAnim.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s", painType.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the damage group? + if ( !painAnim.Length ( ) && damageGroup.Length ( ) ) { + painAnim = va ( "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + if ( !painAnim.Length() ) { + painAnim = "pain"; + } + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + return true; +} + +/* +===================== +idActor::SpawnGibs +===================== +*/ +void idActor::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + idAFEntity_Gibbable::SpawnGibs( dir, damageDefName ); + RemoveAttachments(); +} + +/* +===================== +idActor::SetupDamageGroups + +FIXME: only store group names once and store an index for each joint +===================== +*/ +void idActor::SetupDamageGroups( void ) { + int i; + const idKeyValue *arg; + idStr groupname; + idList jointList; + int jointnum; + float scale; + + // create damage zones + damageGroups.SetNum( animator.NumJoints() ); + arg = spawnArgs.MatchPrefix( "damage_zone ", NULL ); + while ( arg ) { + groupname = arg->GetKey(); + groupname.Strip( "damage_zone " ); + animator.GetJointList( arg->GetValue(), jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + damageGroups[ jointnum ] = groupname; + } + jointList.Clear(); + arg = spawnArgs.MatchPrefix( "damage_zone ", arg ); + } + + // initilize the damage zones to normal damage + damageScale.SetNum( animator.NumJoints() ); + for( i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = 1.0f; + } + + // set the percentage on damage zones + arg = spawnArgs.MatchPrefix( "damage_scale ", NULL ); + while ( arg ) { + scale = atof( arg->GetValue() ); + groupname = arg->GetKey(); + groupname.Strip( "damage_scale " ); + for( i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupname ) { + damageScale[ i ] = scale; + } + } + arg = spawnArgs.MatchPrefix( "damage_scale ", arg ); + } +} + +/* +===================== +idActor::GetDamageForLocation +===================== +*/ +int idActor::GetDamageForLocation( int damage, int location ) { + if ( ( location < 0 ) || ( location >= damageScale.Num() ) ) { + return damage; + } + + return (int)ceil( damage * damageScale[ location ] ); +} + +/* +===================== +idActor::GetDamageGroup +===================== +*/ +const char *idActor::GetDamageGroup( int location ) { + if ( ( location < 0 ) || ( location >= damageGroups.Num() ) ) { + return ""; + } + + return damageGroups[ location ]; +} + + +// RAVEN BEGIN +// bdube: added for vehicle +/* +============== +idActor::ExitVehicle +============== +*/ +bool idActor::ExitVehicle ( bool force ) { + idMat3 axis; + idVec3 origin; + + if ( !IsInVehicle ( ) ) { + return false; + } + + if ( vehicleController.GetVehicle()->IsLocked() ) { + if ( force ) { + vehicleController.GetVehicle()->Unlock(); + } else { + return false; + } + } + + if( !vehicleController.FindClearExitPoint(origin, axis) ) { + if ( force ) { + origin = GetPhysics()->GetOrigin() + idVec3( spawnArgs.GetVector( "forced_exit_offset", "-100 0 0" ) ); + axis = GetPhysics()->GetAxis(); + } else { + return false; + } + } + + vehicleController.Eject ( force ); + + GetPhysics()->SetOrigin( origin ); + viewAxis = axis[0].ToMat3(); + GetPhysics()->SetAxis( mat3_identity ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + return true; +} + +/* +===================== +idActor::EnterVehicle +===================== +*/ +bool idActor::EnterVehicle ( idEntity* ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsInVehicle ( ) || !ent->IsType ( rvVehicle::GetClassType() ) ) { +// RAVEN END + return false ; + } + + // Get in the vehicle + if ( !vehicleController.Drive ( static_cast(ent), this ) ) { + return false; + } + + return true; +} + +// RAVEN END + + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +===================== +idActor::FootStep +===================== +*/ +void idActor::FootStep ( void ) { + const char* sound; + const rvDeclMatType* materialType; + + if ( !GetPhysics()->HasGroundContacts() ) { + return; + } + + // start footstep sound based on material type + materialType = GetPhysics()->GetContact( 0 ).materialType; + sound = NULL; + + // Sound based on material type? + if ( materialType ) { + sound = spawnArgs.GetString( va( "snd_footstep_%s", materialType->GetName() ) ); + } + if ( !sound || !*sound ) { + sound = spawnArgs.GetString( "snd_footstep" ); + } + + // If we have a sound then play it + if ( sound && *sound ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::Event_EnableEyeFocus( void ) { + allowEyeFocus = true; + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableEyeFocus +===================== +*/ +void idActor::Event_DisableEyeFocus( void ) { + allowEyeFocus = false; + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } else { + animator.Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } +} + +/* +===================== +idActor::Event_EnableBlink +===================== +*/ +void idActor::Event_EnableBlink( void ) { + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableBlink +===================== +*/ +void idActor::Event_DisableBlink( void ) { + blink_time = 0x7FFFFFFF; +} + +/* +=============== +idActor::Event_Footstep +=============== +*/ +void idActor::Event_Footstep( void ) { + FootStep ( ); +} + +/* +===================== +idActor::Event_EnableWalkIK +===================== +*/ +void idActor::Event_EnableWalkIK( void ) { + walkIK.EnableAll(); +} + +/* +===================== +idActor::Event_DisableWalkIK +===================== +*/ +void idActor::Event_DisableWalkIK( void ) { + walkIK.DisableAll(); +} + +/* +===================== +idActor::Event_EnableLegIK +===================== +*/ +void idActor::Event_EnableLegIK( int num ) { + walkIK.EnableLeg( num ); +} + +/* +===================== +idActor::Event_DisableLegIK +===================== +*/ +void idActor::Event_DisableLegIK( int num ) { + walkIK.DisableLeg( num ); +} + +/* +===================== +idActor::Event_PreventPain +===================== +*/ +void idActor::Event_PreventPain( float duration ) { + painTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idActor::Event_DisablePain +=============== +*/ +void idActor::Event_DisablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END +} + +/* +=============== +idActor::Event_EnablePain +=============== +*/ +void idActor::Event_EnablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = false; +// RAVEN END +} + +/* +===================== +idActor::Event_SetAnimPrefix +===================== +*/ +void idActor::Event_SetAnimPrefix( const char *prefix ) { + animPrefix = prefix; +} + +/* +=============== +idActor::Event_StopAnim +=============== +*/ +void idActor::Event_StopAnim( int channel, int frames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.StopAnim( frames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_PlayAnim +=============== +*/ +void idActor::Event_PlayAnim( int channel, const char *animname ) { + idThread::ReturnFloat( MS2SEC(PlayAnim(channel, animname, -1)) ); +} + +/* +=============== +idActor::Event_PlayCycle +=============== +*/ +void idActor::Event_PlayCycle( int channel, const char *animname ) { + PlayCycle ( channel, animname, -1 ); + idThread::ReturnInt( true ); +} + +/* +===================== +idAI::DebugFilter +===================== +*/ +bool idActor::DebugFilter ( const idCVar& test ) const { + return ( health>0 && (test.GetBool() || test.GetInteger()>0) && (!ai_debugFilterString.GetString()[0] || !stricmp( name.c_str(), ai_debugFilterString.GetString() ))); +} + +/* +=============== +idActor::Event_IdleAnim +=============== +*/ +void idActor::Event_IdleAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso body if it doesn't override idle anims + headAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + // everything is idle, so play the anim on the head and copy it to the torso and legs + headAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } else if ( torsoAnim.IsIdle() ) { + // sync the head and torso to the legs + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, headAnim.animBlendFrames ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } else { + // sync the head to the torso + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, headAnim.animBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + if ( legsAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to legs if legs anim doesn't override idle anims + torsoAnim.CycleAnim( anim ); + } else if ( legsAnim.IsIdle() ) { + // play the anim in both legs and torso + torsoAnim.CycleAnim( anim ); + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + // sync the anim to the legs + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } + + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso if torso anim doesn't override idle anims + legsAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() ) { + // play the anim in both legs and torso + legsAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } else { + // sync the anim to the torso + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, legsAnim.animBlendFrames ); + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +================ +idActor::Event_SetSyncedAnimWeight +================ +*/ +void idActor::Event_SetSyncedAnimWeight( int channel, int anim, float weight ) { + idEntity *headEnt; + + headEnt = head.GetEntity(); + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headEnt ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } else { + animator.CurrentAnim( ANIMCHANNEL_HEAD )->SetSyncedAnimWeight( anim, weight ); + } + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + case ANIMCHANNEL_TORSO : + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + break; + + case ANIMCHANNEL_LEGS : + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +=============== +idActor::Event_OverrideAnim +=============== +*/ +void idActor::Event_OverrideAnim( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Disable(); + if ( !torsoAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_EnableAnim +=============== +*/ +void idActor::Event_EnableAnim( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Enable( blendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_SetBlendFrames +=============== +*/ +void idActor::Event_SetBlendFrames( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.animBlendFrames = blendFrames; + headAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.animBlendFrames = blendFrames; + torsoAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.animBlendFrames = blendFrames; + legsAnim.lastAnimBlendFrames = blendFrames; + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_GetBlendFrames +=============== +*/ +void idActor::Event_GetBlendFrames( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + idThread::ReturnInt( headAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_TORSO : + idThread::ReturnInt( torsoAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_LEGS : + idThread::ReturnInt( legsAnim.animBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +================ +idActor::Event_HasEnemies +================ +*/ +void idActor::Event_HasEnemies( void ) { + bool hasEnemy; + + hasEnemy = HasEnemies(); + idThread::ReturnInt( hasEnemy ); +} + +/* +================ +idActor::Event_NextEnemy +================ +*/ +void idActor::Event_NextEnemy( idEntity *ent ) { + idActor *actor; + + if ( !ent || ( ent == this ) ) { + actor = enemyList.Next(); + } else { + if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' cannot be an enemy", ent->name.c_str() ); + } + + actor = static_cast( ent ); + if ( actor->enemyNode.ListHead() != &enemyList ) { + gameLocal.Error( "'%s' is not in '%s' enemy list", actor->name.c_str(), name.c_str() ); + } + } + + for( ; actor != NULL; actor = actor->enemyNode.Next() ) { + if ( !actor->fl.hidden ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idActor::Event_ClosestEnemyToPoint +================ +*/ +void idActor::Event_ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *bestEnt = ClosestEnemyToPoint( pos ); + idThread::ReturnEntity( bestEnt ); +} + +/* +================ +idActor::Event_StopSound +================ +*/ +void idActor::Event_StopSound( int channel, int netSync ) { + if ( channel == SND_CHANNEL_VOICE ) { + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->StopSound( channel, ( netSync != 0 ) ); + } + } + StopSound( channel, ( netSync != 0 ) ); +} + +/* +===================== +idActor::Event_GetHead +===================== +*/ +void idActor::Event_GetHead( void ) { + idThread::ReturnEntity( head.GetEntity() ); +} + +// RAVEN BEGIN +// jshepard: added +/* +===================== +idActor::Event_SetAnimRate +===================== +*/ +void idActor::Event_SetAnimRate( float multiplier ) { + animator.SetPlaybackRate(multiplier); +} + + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idActor::State_Wait_Frame + +Stop a state thread for a single frame +================ +*/ +stateResult_t idActor::State_Wait_Frame ( const stateParms_t& parms ) { + return SRESULT_DONE_WAIT; +} + +/* +================ +idActor::State_Wait_LegsAnim + +Stop a state thread until the animation running on the legs channel is finished +================ +*/ +stateResult_t idActor::State_Wait_LegsAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::State_Wait_TorsoAnim + +Stop a state thread until the animation running on the torso channel is finished +================ +*/ +stateResult_t idActor::State_Wait_TorsoAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::PlayAnim +================ +*/ +int idActor::PlayAnim ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + idEntity *headEnt; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + + if( ai_animShow.GetBool() ){ + gameLocal.DPrintf( "Playing animation '%s' on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "head", "" ) ); + } + + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return 0; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnim.idleAnim = false; + headAnim.PlayAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.PlayAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.PlayAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default : + gameLocal.Error( "Unknown anim group" ); + break; + } + + return animator.CurrentAnim( channel )->Length(); +} + +/* +================ +idActor::PlayCycle +================ +*/ +bool idActor::PlayCycle ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return false; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.idleAnim = false; + headAnim.CycleAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.CycleAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.CycleAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + return true; +} + +void idActor::IdleAnim ( int channel, const char *name, int blendFrames ) { + Event_SetBlendFrames ( channel, blendFrames ); + Event_IdleAnim ( channel, name ); +} + +void idActor::OverrideAnim ( int channel ) { + Event_OverrideAnim ( channel ); +} + +idAnimState& idActor::GetAnimState ( int channel ) { + switch ( channel ) { + case ANIMCHANNEL_LEGS: return legsAnim; + case ANIMCHANNEL_TORSO: return torsoAnim; + case ANIMCHANNEL_HEAD: return headAnim; + default: + gameLocal.Error( "idActor::GetAnimState: Unknown anim channel" ); + return torsoAnim; + } +} + +void idActor::DisableAnimState ( int channel ) { + Event_OverrideAnim ( channel ); +// GetAnimState ( channel ).Disable ( ); +} + +void idActor::EnableAnimState ( int channel ) { + GetAnimState ( channel ).Enable ( 4 ); +} + +bool idActor::HasAnim ( int channel, const char* animname, bool forcePrefix ) { + return GetAnim( channel, animname, forcePrefix ) != NULL; +} + +bool idActor::AnimDone ( int channel, int blendFrames ) { + return GetAnimState( channel ).AnimDone ( blendFrames ); +} + +/* +===================== +idActor::GetDebugInfo +===================== +*/ +void idActor::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAFEntity_Gibbable::GetDebugInfo ( proc, userData ); + + proc ( "idActor", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); + + proc ( "idActor", "legs_state", legsAnim.GetStateThread().GetState()?legsAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "legs_disable", legsAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "legs_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) : "", userData ); + + proc ( "idActor", "torso_state", torsoAnim.GetStateThread().GetState()?torsoAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "torso_disabled", torsoAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "torso_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) : "", userData ); + + proc ( "idActor", "head_state", headAnim.GetStateThread().GetState()?headAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "head_disabled", headAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "head_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD )->AnimName ( ) : "", userData ); + + proc ( "idActor", "painAnim", painAnim.c_str(), userData ); + proc ( "idActor", "animPrefix", animPrefix.c_str(), userData ); +} + +//MCG: damage over time +void idActor::Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + inDamageEvent = true; + Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + inDamageEvent = false; + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + } +} + +void idActor::Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), this, interval ); + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +// MCG: script-callable joint crawl effect +void idActor::Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ) { + if ( effectKeyName ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( spawnArgs, effectKeyName ), this, 100 ); + effect->Play ( gameLocal.GetTime(), false ); + crawlSecs -= 0.1f; + if ( crawlSecs >= 0.1f ) { + PostEventMS( &EV_JointCrawlEffect, 100, effectKeyName, crawlSecs ); + } + } +} + +idEntity* idActor::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + + if ( testElevator && groundEnt != testElevator ) { + return groundEnt; + } + + idEntity* traceEnt; + idVec3 testPoint = GetPhysics()->GetOrigin(); + idVec3 testBottom; + testPoint.z += 1.0f; + + for ( int x = 0; x < 2; x++ ) { + testPoint.x = GetPhysics()->GetAbsBounds()[x].x; + for ( int y = 0; y < 2; y++ ) { + testPoint.y = GetPhysics()->GetAbsBounds()[y].y; + testBottom = testPoint; + testBottom.z -= 65.0f; + + trace_t tr; + gameLocal.TracePoint( this, tr, testPoint, testBottom, GetPhysics()->GetClipMask(), this ); + traceEnt = gameLocal.FindEntity( tr.c.entityNum ); + if ( !traceEnt ) { + return NULL; + } + while ( traceEnt->GetBindMaster() ) { + traceEnt = traceEnt->GetBindMaster(); + } + if ( traceEnt != groundEnt ) { + return traceEnt; + } + if ( testElevator && traceEnt != testElevator ) { + return traceEnt; + } + } + } + + return groundEnt; +} + +void idActor::GuidedProjectileIncoming( idGuidedProjectile *projectile ) +{ + if ( IsInVehicle() ) + { + if ( vehicleController.GetVehicle() ) + { + vehicleController.GetVehicle()->GuidedProjectileIncoming( projectile ); + } + } +} +// RAVEN END diff --git a/source/game/Actor.h b/source/game/Actor.h new file mode 100644 index 0000000..3476c35 --- /dev/null +++ b/source/game/Actor.h @@ -0,0 +1,426 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ACTOR_H__ +#define __GAME_ACTOR_H__ + +/* +=============================================================================== + + idActor + +=============================================================================== +*/ + +extern const idEventDef AI_EnableEyeFocus; +extern const idEventDef AI_DisableEyeFocus; +extern const idEventDef EV_Footstep; +extern const idEventDef EV_FootstepLeft; +extern const idEventDef EV_FootstepRight; +extern const idEventDef EV_EnableWalkIK; +extern const idEventDef EV_DisableWalkIK; +extern const idEventDef EV_EnableLegIK; +extern const idEventDef EV_DisableLegIK; +extern const idEventDef AI_SetAnimPrefix; +extern const idEventDef AI_PlayAnim; +extern const idEventDef AI_PlayCycle; +extern const idEventDef AI_AnimDone; +extern const idEventDef AI_SetBlendFrames; +extern const idEventDef AI_GetBlendFrames; +extern const idEventDef AI_ScriptedMove; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; + +// RAVEN BEGIN +// bdube: added flashlight +extern const idEventDef AI_Flashlight; +extern const idEventDef AI_EnterVehicle; +extern const idEventDef AI_ExitVehicle; +// nmckenzie: +extern const idEventDef AI_OverrideAnim; +extern const idEventDef AI_IdleAnim; +extern const idEventDef AI_SetState; +// jshepard: adjust animation speed +extern const idEventDef AI_SetAnimRate; +//MCG: damage over time +extern const idEventDef EV_DamageOverTime; +extern const idEventDef EV_DamageOverTimeEffect; +//MCG: script-callable joint crawl effect +extern const idEventDef EV_JointCrawlEffect; + +// abahr: +extern const idEventDef AI_LookAt; +extern const idEventDef AI_FaceEnemy; +extern const idEventDef AI_FaceEntity; +extern const idEventDef AI_JumpDown; +extern const idEventDef AI_SetLeader; +// RAVEN END + +class idAnimState { +public: + + bool idleAnim; + int animBlendFrames; + int lastAnimBlendFrames; // allows override anims to blend based on the last transition time + +public: + idAnimState(); + ~idAnimState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Init( idEntity *owner, idAnimator *_animator, int animchannel ); + void Shutdown( void ); + void SetState ( const char *name, int blendFrames, int flags = 0 ); + void PostState ( const char* name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnim( int frames ); + void PlayAnim( int anim ); + void CycleAnim( int anim ); + void BecomeIdle( void ); + bool UpdateState( void ); + bool Disabled( void ) const; + void Enable( int blendFrames ); + void Disable( void ); + bool AnimDone( int blendFrames ) const; + bool IsIdle( void ) const; + animFlags_t GetAnimFlags( void ) const; + + rvStateThread& GetStateThread ( void ); + + idAnimator * GetAnimator( void ) const {return animator;}; +private: +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it + idEntity * self; +// RAVEN END + idAnimator * animator; + int channel; + bool disabled; + + rvStateThread stateThread; +}; + +ID_INLINE rvStateThread& idAnimState::GetStateThread ( void ) { + return stateThread; +} + +class idAttachInfo { +public: + idEntityPtr ent; + int channel; +}; + +class idActor : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idActor ); + + int team; + idLinkList teamNode; + int rank; // monsters don't fight back if the attacker's rank is higher + idMat3 viewAxis; // view axis of the actor + + idLinkList enemyNode; // node linked into an entity's enemy list for quick lookups of who is attacking him + idLinkList enemyList; // list of characters that have targeted the player as their enemy + +public: + idActor( void ); + virtual ~idActor( void ); + + void Spawn( void ); + virtual void Restart( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual int GetDefaultSurfaceType( void ) const; + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual bool LoadAF( const char* keyname = NULL, bool purgeAF = false ); + void SetupBody( void ); + + virtual void CheckBlink( void ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + // script state management + void ShutdownThreads ( void ); + void UpdateState ( void ); + + virtual void OnStateThreadClear ( const char *statename, int flags = 0 ); + void SetState ( const char *statename, int flags = 0 ); + void PostState ( const char* statename, int delay = 0, int flags = 0 ); + void InterruptState ( const char* statename, int delay = 0, int flags = 0 ); + + // vision testing + void SetEyeHeight( float height ); + void SetChestHeight ( float height ); + float EyeHeight( void ) const; + + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 GetChestPosition ( void ) const; + idEntity* GetGroundEntity ( void ) const; + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + void Present( void ); + + virtual void GetViewPos ( idVec3 &origin, idMat3 &axis ) const; + void SetFOV ( float fov, float fovClose ); + bool CheckFOV ( const idVec3 &pos, float ang = -1.0f ) const; + virtual bool HasFOV ( idEntity *ent ); + virtual bool CanSee ( const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFOV ) const; + + // damage + void SetupDamageGroups( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +// RAVEN BEGIN +// nmckenzie: a final hook in the middle of the damage function + virtual void AdjustHealthByDamage ( int inDamage ){health -= inDamage;} +// RAVEN END + + virtual int GetDamageForLocation( int damage, int location ); + const char * GetDamageGroup( int location ); + void ClearPain( void ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + // model/combat model/ragdoll + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + bool StartRagdoll( void ); + void StopRagdoll( void ); + virtual bool UpdateAnimationControllers( void ); + + // delta view angles to allow movers to rotate the view of the actor + const idAngles & GetDeltaViewAngles( void ) const; + void SetDeltaViewAngles( const idAngles &delta ); + + bool HasEnemies( void ) const; + idActor * ClosestEnemyToPoint( const idVec3 &pos, float maxRange=0.0f, bool returnFirst=false, bool checkPVS=false ); + idActor * EnemyWithMostHealth(); + + virtual bool OnLadder ( void ) const; + virtual void OnStateChange ( int channel ); + virtual void OnFriendlyFire ( idActor* attacker ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + + void Attach( idEntity *ent ); + idEntity* FindAttachment( const char* attachmentName ); + void HideAttachment( const char* attachmentName ); + void ShowAttachment( const char* attachmentName ); + idEntity* GetHead() { return head; } + + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual renderView_t * GetRenderView(); + + // Animation + int PlayAnim ( int channel, const char *name, int blendFrames ); + bool PlayCycle ( int channel, const char *name, int blendFrames ); + void IdleAnim ( int channel, const char *name, int blendFrames ); + void OverrideAnim ( int channel ); + bool HasAnim ( int channel, const char *name, bool forcePrefix = false ); + int GetAnim ( int channel, const char *name, bool forcePrefix = false ); + bool AnimDone ( int channel, int blendFrames ); + + // animation state control + void UpdateAnimState ( void ); + void SetAnimState ( int channel, const char *name, int blendFrames = 0, int flags = 0 ); + void PostAnimState ( int channel, const char *name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnimState ( int channel ); + bool InAnimState ( int channel, const char *name ); + + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +// RAVEN BEGIN +// bdube: added for vehicle + bool IsInVehicle ( void ) const; + rvVehicleController& GetVehicleController ( void ); + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); + + bool DebugFilter (const idCVar& test) const; +// RAVEN END + virtual bool IsCrouching ( void ) const {return false;}; + + virtual bool SkipImpulse( idEntity* ent, int id ); + + int lightningNextTime; + int lightningEffects; + +protected: + friend class idAnimState; + + float fovDot; // cos( fovDegrees ) + float fovCloseDot; // cos( fovDegreesClose ) + float fovCloseRange; // range within to use fovCloseDot + idVec3 eyeOffset; // offset of eye relative to physics origin + idVec3 chestOffset; // offset of chest relative to physics origin + idVec3 modelOffset; // offset of visual model relative to the physics origin + + idAngles deltaViewAngles; // delta angles relative to view input angles + + int pain_debounce_time; // next time the actor can show pain + int pain_delay; // time between playing pain sound + + idStrList damageGroups; // body damage groups + idList damageScale; // damage scale per damage gruop + bool inDamageEvent; // hacky-ass bool to prevent us from starting a new EV_DamageOverTime in our ::Damage + + bool use_combat_bbox; // whether to use the bounding box for combat collision + + // joint handles + jointHandle_t leftEyeJoint; + jointHandle_t rightEyeJoint; + jointHandle_t soundJoint; + jointHandle_t eyeOffsetJoint; + jointHandle_t chestOffsetJoint; + jointHandle_t neckJoint; + jointHandle_t headJoint; + + idIK_Walk walkIK; + + idStr animPrefix; + idStr painType; + idStr painAnim; + + // blinking + int blink_anim; + int blink_time; + int blink_min; + int blink_max; + + idAnimState headAnim; + idAnimState torsoAnim; + idAnimState legsAnim; + + rvStateThread stateThread; + + idEntityPtr head; // safe pointer to attached head + + bool disablePain; + bool allowEyeFocus; + bool finalBoss; + + int painTime; + + idList attachments; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void CheckDeathObjectives( void ); + +// RAVEN BEGIN +// bdube: vehicles + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); +// RAVEN END + + // removes attachments with "remove" set for when character dies + void RemoveAttachments( void ); + +// RAVEN BEGIN +// bdube: vehicles + rvVehicleController vehicleController; +// bdube: flashlights + renderLight_t flashlight; + int flashlightHandle; + jointHandle_t flashlightJoint; + idVec3 flashlightOffset; + +// bdube: death force + int deathPushTime; + idVec3 deathPushForce; + jointHandle_t deathPushJoint; + + void FlashlightUpdate ( bool forceOn = false ); + void InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale = 1.0f ); + void DeathPush ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +protected: + + virtual void FootStep ( void ); + virtual void SetupHead( const char* headDefName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + void SyncAnimChannels( int channel, int syncToChannel, int blendFrames ); + void FinishSetup( void ); + + void Event_EnableEyeFocus( void ); + void Event_DisableEyeFocus( void ); + void Event_EnableBlink( void ); + void Event_DisableBlink( void ); + void Event_Footstep( void ); + void Event_EnableWalkIK( void ); + void Event_DisableWalkIK( void ); + void Event_EnableLegIK( int num ); + void Event_DisableLegIK( int num ); + void Event_SetAnimPrefix( const char *name ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_PreventPain( float duration ); + void Event_DisablePain( void ); + void Event_EnablePain( void ); + void Event_StopAnim( int channel, int frames ); + void Event_PlayAnim( int channel, const char *name ); + void Event_PlayCycle( int channel, const char *name ); + void Event_IdleAnim( int channel, const char *name ); + void Event_SetSyncedAnimWeight( int channel, int anim, float weight ); + void Event_OverrideAnim( int channel ); + void Event_EnableAnim( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_HasEnemies( void ); + void Event_NextEnemy( idEntity *ent ); + void Event_ClosestEnemyToPoint( const idVec3 &pos ); + void Event_StopSound( int channel, int netsync ); + void Event_GetHead( void ); + + void Event_Teleport ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Flashlight ( bool enable ); + void Event_EnterVehicle ( idEntity* vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_PreExitVehicle( bool force ); + + void Event_SetAnimRate ( float multiplier ); + void Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, const char *damageDefName, const float damageScale, int location ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + void Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ); + + CLASS_STATES_PROTOTYPE ( idActor ); + +protected: + + // Wait states + stateResult_t State_Wait_LegsAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_TorsoAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_Frame ( const stateParms_t& parms ); + + void DisableAnimState ( int channel ); + void EnableAnimState ( int channel ); + idAnimState& GetAnimState ( int channel ); +}; + +ID_INLINE bool idActor::IsInVehicle( void ) const { + return vehicleController.IsDriving(); +} + +ID_INLINE rvVehicleController& idActor::GetVehicleController( void ) { + return vehicleController; +} + +#endif /* !__GAME_ACTOR_H__ */ + +// RAVEN END diff --git a/source/game/BrittleFracture.cpp b/source/game/BrittleFracture.cpp new file mode 100644 index 0000000..d277e8e --- /dev/null +++ b/source/game/BrittleFracture.cpp @@ -0,0 +1,1355 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes +#ifdef _MD5R_SUPPORT +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" +#endif +// RAVEN END + +CLASS_DECLARATION( idEntity, idBrittleFracture ) + EVENT( EV_Activate, idBrittleFracture::Event_Activate ) + EVENT( EV_Touch, idBrittleFracture::Event_Touch ) +END_CLASS + +const int SHARD_ALIVE_TIME = 5000; +const int SHARD_FADE_START = 2000; + +static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; + +/* +================ +idBrittleFracture::idBrittleFracture +================ +*/ +idBrittleFracture::idBrittleFracture( void ) { + material = NULL; + decalMaterial = NULL; + decalSize = 0.0f; + maxShardArea = 0.0f; + maxShatterRadius = 0.0f; + minShatterRadius = 0.0f; + linearVelocityScale = 0.0f; + angularVelocityScale = 0.0f; + shardMass = 0.0f; + density = 0.0f; + friction = 0.0f; + bouncyness = 0.0f; + fxFracture.Clear(); + + bounds.Clear(); + disableFracture = false; + + lastRenderEntityUpdate = -1; + changed = false; + + fl.networkSync = true; +} + +/* +================ +idBrittleFracture::~idBrittleFracture +================ +*/ +idBrittleFracture::~idBrittleFracture( void ) { + int i; + + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->decals.DeleteContents( true ); + delete shards[i]; + } + + // make sure the render entity is freed before the model is freed + FreeModelDef(); + renderModelManager->FreeModel( renderEntity.hModel ); +} + +/* +================ +idBrittleFracture::Save +================ +*/ +void idBrittleFracture::Save( idSaveGame *savefile ) const { + int i, j; + + savefile->WriteInt( health ); + savefile->Write( &fl, sizeof( fl ) ); + + // setttings + savefile->WriteMaterial( material ); + savefile->WriteMaterial( decalMaterial ); + savefile->WriteFloat( decalSize ); + savefile->WriteFloat( maxShardArea ); + savefile->WriteFloat( maxShatterRadius ); + savefile->WriteFloat( minShatterRadius ); + savefile->WriteFloat( linearVelocityScale ); + savefile->WriteFloat( angularVelocityScale ); + savefile->WriteFloat( shardMass ); + savefile->WriteFloat( density ); + savefile->WriteFloat( friction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteString( fxFracture ); + + // state + savefile->WriteBounds( bounds ); + savefile->WriteBool( disableFracture ); + + savefile->WriteInt( lastRenderEntityUpdate ); + savefile->WriteBool( changed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( shards.Num() ); + for ( i = 0; i < shards.Num(); i++ ) { + savefile->WriteWinding( shards[i]->winding ); + + savefile->WriteInt( shards[i]->decals.Num() ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { + savefile->WriteWinding( *shards[i]->decals[j] ); + } + + savefile->WriteInt( shards[i]->neighbours.Num() ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index = shards.FindIndex(shards[i]->neighbours[j]); + assert(index != -1); + savefile->WriteInt( index ); + } + + savefile->WriteInt( shards[i]->edgeHasNeighbour.Num() ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->WriteBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->WriteInt( shards[i]->droppedTime ); + savefile->WriteInt( shards[i]->islandNum ); + savefile->WriteBool( shards[i]->atEdge ); + savefile->WriteStaticObject( shards[i]->physicsObj ); + } +} + +/* +================ +idBrittleFracture::Restore +================ +*/ +void idBrittleFracture::Restore( idRestoreGame *savefile ) { + int i, j , num; + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; + + savefile->ReadInt( health ); + savefile->Read( &fl, sizeof( fl ) ); + + // setttings + savefile->ReadMaterial( material ); + savefile->ReadMaterial( decalMaterial ); + savefile->ReadFloat( decalSize ); + savefile->ReadFloat( maxShardArea ); + savefile->ReadFloat( maxShatterRadius ); + savefile->ReadFloat( minShatterRadius ); + savefile->ReadFloat( linearVelocityScale ); + savefile->ReadFloat( angularVelocityScale ); + savefile->ReadFloat( shardMass ); + savefile->ReadFloat( density ); + savefile->ReadFloat( friction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadString( fxFracture ); + + // state + savefile->ReadBounds(bounds); + savefile->ReadBool( disableFracture ); + + savefile->ReadInt( lastRenderEntityUpdate ); + savefile->ReadBool( changed ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + shards.SetNum( num ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + for ( i = 0; i < num; i++ ) { + shards[i] = new shard_t; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + savefile->ReadWinding( shards[i]->winding ); + + savefile->ReadInt( j ); + shards[i]->decals.SetNum( j ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shards[i]->decals[j] = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + savefile->ReadWinding( *shards[i]->decals[j] ); + } + + savefile->ReadInt( j ); + shards[i]->neighbours.SetNum( j ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index; + savefile->ReadInt( index ); + assert(index != -1); + shards[i]->neighbours[j] = shards[index]; + } + + savefile->ReadInt( j ); + shards[i]->edgeHasNeighbour.SetNum( j ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->ReadBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->ReadInt( shards[i]->droppedTime ); + savefile->ReadInt( shards[i]->islandNum ); + savefile->ReadBool( shards[i]->atEdge ); + savefile->ReadStaticObject( shards[i]->physicsObj ); + if ( shards[i]->droppedTime < 0 ) { + shards[i]->clipModel = physicsObj.GetClipModel( i ); + } else { + shards[i]->clipModel = shards[i]->physicsObj.GetClipModel(); + } + } +} + +/* +================ +idBrittleFracture::Spawn +================ +*/ +void idBrittleFracture::Spawn( void ) { + + // get shard properties + decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) ); + decalSize = spawnArgs.GetFloat( "decalSize", "40" ); + maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ); + maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea ); + maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" ); + minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" ); + linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" ); + angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" ); + fxFracture = spawnArgs.GetString( "fx" ); + + // get rigid body properties + shardMass = spawnArgs.GetFloat( "shardMass", "20" ); + shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass ); + spawnArgs.GetFloat( "density", "0.1", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.4", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + disableFracture = spawnArgs.GetBool( "disableFracture", "0" ); + health = spawnArgs.GetInt( "health", "40" ); + fl.takedamage = true; + + // FIXME: set "bleed" so idProjectile calls AddDamageEffect + spawnArgs.SetBool( "bleed", 1 ); + + CreateFractures( renderEntity.hModel ); + + FindNeighbours(); + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; +} + +/* +================ +idBrittleFracture::AddShard +================ +*/ +void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shard_t *shard = new shard_t; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shard->clipModel = clipModel; + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + shards.Append( shard ); +} + +/* +================ +idBrittleFracture::RemoveShard +================ +*/ +void idBrittleFracture::RemoveShard( int index ) { + int i; + + delete shards[index]; + shards.RemoveIndex( index ); + physicsObj.RemoveIndex( index ); + + for ( i = index; i < shards.Num(); i++ ) { + shards[i]->clipModel->SetId( i ); + } +} + +/* +================ +idBrittleFracture::UpdateRenderEntity +================ +*/ +bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + int i, j, k, n, msec, numTris, numDecalTris; + float fade; + dword packedColor; + srfTriangles_t *tris, *decalTris; + modelSurface_t surface; + idDrawVert *v; + idPlane plane; + idMat3 tangents; + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + return false; + } + + // don't regenerate it if it is current + if ( lastRenderEntityUpdate == gameLocal.time || !changed ) { + return false; + } + + lastRenderEntityUpdate = gameLocal.time; + changed = false; + + numTris = 0; + numDecalTris = 0; + for ( i = 0; i < shards.Num(); i++ ) { + n = shards[i]->winding.GetNumPoints(); + if ( n > 2 ) { + numTris += n - 2; + } + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + n = shards[i]->decals[k]->GetNumPoints(); + if ( n > 2 ) { + numDecalTris += n - 2; + } + } + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName ); + + // allocate triangle surfaces for the fractures and decals + { + ConditionalAutoCrit crit; + + tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 ); + decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 ); + } + + for ( i = 0; i < shards.Num(); i++ ) { + const idVec3 &origin = shards[i]->clipModel->GetOrigin(); + const idMat3 &axis = shards[i]->clipModel->GetAxis(); + + fade = 1.0f; + if ( shards[i]->droppedTime >= 0 ) { + msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START; + if ( msec > 0 ) { + fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START ); + } + } + packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade, + renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade, + renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade, + fade ) ); + + const idWinding &winding = shards[i]->winding; + + winding.GetPlane( plane ); + tangents = ( plane.Normal() * axis ).ToMat3(); + + for ( j = 2; j < winding.GetNumPoints(); j++ ) { + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[0].ToVec3() * axis; + v->st[0] = winding[0].s; + v->st[1] = winding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j-1].ToVec3() * axis; + v->st[0] = winding[j-1].s; + v->st[1] = winding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j].ToVec3() * axis; + v->st[0] = winding[j].s; + v->st[1] = winding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + + if ( material->ShouldCreateBackSides() ) { + + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + } + } + + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + const idWinding &decalWinding = *shards[i]->decals[k]; + + for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) { + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[0].ToVec3() * axis; + v->st[0] = decalWinding[0].s; + v->st[1] = decalWinding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j-1].ToVec3() * axis; + v->st[0] = decalWinding[j-1].s; + v->st[1] = decalWinding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j].ToVec3() * axis; + v->st[0] = decalWinding[j].s; + v->st[1] = decalWinding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + + if ( decalMaterial->ShouldCreateBackSides() ) { + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + } + } + } + } + + tris->tangentsCalculated = true; + decalTris->tangentsCalculated = true; + + SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts ); + SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = material; + surface.id = 0; + surface.geometry = tris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = decalMaterial; + surface.id = 1; + surface.geometry = decalTris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + return true; +} + +/* +================ +idBrittleFracture::ModelCallback +================ +*/ +bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + const idBrittleFracture *ent; + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idBrittleFracture::Present +================ +*/ +void idBrittleFracture::Present() { + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + renderEntity.bounds = bounds; + renderEntity.origin.Zero(); + renderEntity.axis.Identity(); + + // force an update because the bounds/origin/axis may stay the same while the model changes + renderEntity.forceUpdate = true; + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + + changed = true; +} + +/* +================ +idBrittleFracture::Think +================ +*/ +void idBrittleFracture::Think( void ) { + int i, startTime, endTime, droppedTime; + shard_t *shard; + bool atRest = true, fading = false; + + // remove overdue shards + for ( i = 0; i < shards.Num(); i++ ) { + droppedTime = shards[i]->droppedTime; + if ( droppedTime != -1 ) { + if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) { + RemoveShard( i ); + i--; + } + fading = true; + } + } + + // remove the entity when nothing is visible + if ( !shards.Num() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( thinkFlags & TH_PHYSICS ) { + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + // run physics on shards + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime == -1 ) { + continue; + } + + shard->physicsObj.Evaluate( endTime - startTime, endTime ); + + if ( !shard->physicsObj.IsAtRest() ) { + atRest = false; + } + } + + if ( atRest ) { + BecomeInactive( TH_PHYSICS ); + } else { + BecomeActive( TH_PHYSICS ); + } + } + + if ( !atRest || bounds.IsCleared() ) { + bounds.Clear(); + for ( i = 0; i < shards.Num(); i++ ) { + bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() ); + } + } + + if ( fading ) { + BecomeActive( TH_UPDATEVISUALS | TH_THINK ); + } else { + BecomeInactive( TH_THINK ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idBrittleFracture::ApplyImpulse +================ +*/ +void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.ApplyImpulse( 0, point, impulse ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, impulse, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::AddForce +================ +*/ +void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.AddForce( 0, point, force ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, force, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::ProjectDecal +================ +*/ +void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { + int i, j, bits, clipBits; + float a, c, s; + idVec2 st[MAX_POINTS_ON_WINDING]; + idVec3 origin; + idMat3 axis, axistemp; + idPlane textureAxis[2]; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( dir[0] ); + msg.WriteFloat( dir[1] ); + msg.WriteFloat( dir[2] ); + ServerSendInstanceEvent( EVENT_PROJECT_DECAL, &msg, true, -1 ); + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; + } + } + + if ( time >= gameLocal.time ) { + // try to get the sound from the damage def + const idDeclEntityDef *damageDef = NULL; + const idSoundShader *sndShader = NULL; + if ( damageDefName ) { + damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) ); + } + } + + if ( sndShader ) { + StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); + } else { + StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + + axis[2] = -dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + textureAxis[0] = axis[0] * ( 1.0f / decalSize ); + textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; + + textureAxis[1] = axis[1] * ( 1.0f / decalSize ); + textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; + + for ( i = 0; i < shards.Num(); i++ ) { + idFixedWinding &winding = shards[i]->winding; + origin = shards[i]->clipModel->GetOrigin(); + axis = shards[i]->clipModel->GetAxis(); + float d0, d1; + + clipBits = -1; + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + idVec3 p = origin + winding[j].ToVec3() * axis; + + st[j].x = d0 = textureAxis[0].Distance( p ); + st[j].y = d1 = textureAxis[1].Distance( p ); + + bits = FLOATSIGNBITSET( d0 ); + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 2; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 1; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + clipBits &= bits; + } + + if ( clipBits ) { + continue; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idFixedWinding *decal = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shards[i]->decals.Append( decal ); + + decal->SetNumPoints( winding.GetNumPoints() ); + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + (*decal)[j].ToVec3() = winding[j].ToVec3(); + (*decal)[j].s = st[j].x; + (*decal)[j].t = st[j].y; + } + } + + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idBrittleFracture::DropShard +================ +*/ +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { + int i, j, clipModelId; + float dist, f; + idVec3 dir2, origin; + idMat3 axis; + shard_t *neighbour; + + // don't display decals on dropped shards + shard->decals.DeleteContents( true ); + + // remove neighbour pointers of neighbours pointing to this shard + for ( i = 0; i < shard->neighbours.Num(); i++ ) { + neighbour = shard->neighbours[i]; + for ( j = 0; j < neighbour->neighbours.Num(); j++ ) { + if ( neighbour->neighbours[j] == shard ) { + neighbour->neighbours.RemoveIndex( j ); + break; + } + } + } + + // remove neighbour pointers + shard->neighbours.Clear(); + + // remove the clip model from the static physics object + clipModelId = shard->clipModel->GetId(); + physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false ); + + origin = shard->clipModel->GetOrigin(); + axis = shard->clipModel->GetAxis(); + + // set the dropped time for fading + shard->droppedTime = time; + + dir2 = origin - point; + dist = dir2.Normalize(); +// RAVEN BEGIN +// jscott: changed to avoid negative sqrt call which propagated into badness + if( dist > maxShatterRadius ) { + f = 1.0f; + } else if( dist < minShatterRadius ) { + f = 0.0f; + } else { + f = idMath::Sqrt( dist - minShatterRadius ) * idMath::InvSqrt( maxShatterRadius - minShatterRadius ); + } +// RAVEN END + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, density ); + shard->physicsObj.SetMass( shardMass ); + shard->physicsObj.SetOrigin( origin ); + shard->physicsObj.SetAxis( axis ); + shard->physicsObj.SetBouncyness( bouncyness ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); + + shard->clipModel->SetId( clipModelId ); + + BecomeActive( TH_PHYSICS ); +} + +/* +================ +idBrittleFracture::Shatter +================ +*/ +void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i; + idVec3 dir; + shard_t *shard; + float m; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( impulse[0] ); + msg.WriteFloat( impulse[1] ); + msg.WriteFloat( impulse[2] ); + ServerSendInstanceEvent( EVENT_SHATTER, &msg, true, -1 ); + } + + if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) { + StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( !IsBroken() ) { + Break(); + } + +// RAVEN BEGIN +// bdube: raven effect system + PlayEffect ( "fx_shatter", point, GetPhysics()->GetAxis() ); +// RAVEN END + + dir = impulse; + m = dir.Normalize(); + + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime != -1 ) { + continue; + } + + if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) { + continue; + } + + DropShard( shard, point, dir, m, time ); + } + + DropFloatingIslands( point, impulse, time ); +} + +/* +================ +idBrittleFracture::DropFloatingIslands +================ +*/ +void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i, j, numIslands; + int queueStart, queueEnd; + shard_t *curShard, *nextShard, **queue; + bool touchesEdge; + idVec3 dir; + + dir = impulse; + dir.Normalize(); + + numIslands = 0; + queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->islandNum = 0; + } + + for ( i = 0; i < shards.Num(); i++ ) { + + if ( shards[i]->droppedTime != -1 ) { + continue; + } + + if ( shards[i]->islandNum ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = shards[i]; + shards[i]->islandNum = numIslands+1; + touchesEdge = false; + + if ( shards[i]->atEdge ) { + touchesEdge = true; + } + + for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) { + + for ( j = 0; j < curShard->neighbours.Num(); j++ ) { + + nextShard = curShard->neighbours[j]; + + if ( nextShard->droppedTime != -1 ) { + continue; + } + + if ( nextShard->islandNum ) { + continue; + } + + queue[queueEnd++] = nextShard; + nextShard->islandNum = numIslands+1; + + if ( nextShard->atEdge ) { + touchesEdge = true; + } + } + } + numIslands++; + + // if the island is not connected to the world at any edges + if ( !touchesEdge ) { + for ( j = 0; j < queueEnd; j++ ) { + DropShard( queue[j], point, dir, 0.0f, time ); + } + } + } +} + +/* +================ +idBrittleFracture::Break +================ +*/ +void idBrittleFracture::Break( void ) { + fl.takedamage = false; + physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); +} + +/* +================ +idBrittleFracture::IsBroken +================ +*/ +bool idBrittleFracture::IsBroken( void ) const { + return ( fl.takedamage == false ); +} + +/* +================ +idBrittleFracture::Killed +================ +*/ +void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !disableFracture ) { + ActivateTargets( this ); + Break(); + } +} + +/* +================ +idBrittleFracture::AddDamageEffect +================ +*/ +void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !disableFracture ) { + ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName ); + } +} + +/* +================ +idBrittleFracture::Fracture_r +================ +*/ +void idBrittleFracture::Fracture_r( idFixedWinding &w ) { + int i, j, bestPlane; + float a, c, s, dist, bestDist; + idVec3 origin; + idPlane windingPlane, splitPlanes[2]; + idMat3 axis, axistemp; + idFixedWinding back; + idTraceModel trm; + idClipModel *clipModel; + + while( 1 ) { + origin = w.GetCenter(); + w.GetPlane( windingPlane ); + + if ( w.GetArea() < maxShardArea ) { + break; + } + + // randomly create a split plane + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + axis[2] = windingPlane.Normal(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + // get the best split plane + bestDist = 0.0f; + bestPlane = 0; + for ( i = 0; i < 2; i++ ) { + splitPlanes[i].SetNormal( axis[i] ); + splitPlanes[i].FitThroughPoint( origin ); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + dist = splitPlanes[i].Distance( w[j].ToVec3() ); + if ( dist > bestDist ) { + bestDist = dist; + bestPlane = i; + } + } + } + + // split the winding + if ( !w.Split( &back, splitPlanes[bestPlane] ) ) { + break; + } + + // recursively create shards for the back winding + Fracture_r( back ); + } + + // translate the winding to it's center + origin = w.GetCenter(); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + w[j].ToVec3() -= origin; + } + w.RemoveEqualPoints(); + + trm.SetupPolygon( w ); + trm.Shrink( CM_CLIP_EPSILON ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() ); + + AddShard( clipModel, w ); +} + +/* +================ +idBrittleFracture::CreateFractures +================ +*/ +void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) { + int i, j, k; + const modelSurface_t *surf; + const idDrawVert *v; + idFixedWinding w; + + if ( !renderModel ) { + return; + } + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 ); + + for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) { + surf = renderModel->Surface( i ); + material = surf->shader; + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes + srfTriangles_t * tri = surf->geometry; +#ifdef _MD5R_SUPPORT + if ( tri->primBatchMesh != NULL ) { + + srfTriangles_t *tempTri = (srfTriangles_t *) _alloca16( sizeof(srfTriangles_t) ); + memset( tempTri, 0, sizeof( srfTriangles_t ) ); + + assert( tri->silTraceVerts != NULL ); + + tempTri->numVerts = tri->primBatchMesh->GetNumDrawVertices(); + tempTri->numIndexes = tri->primBatchMesh->GetNumDrawIndices(); + + tempTri->indexes = (glIndex_t *) _alloca16( tempTri->numIndexes * sizeof( tempTri->indexes[0] ) ); + tempTri->verts = (idDrawVert *) _alloca16( tempTri->numVerts * sizeof( idDrawVert ) ); + + renderSystem->CopyPrimBatchTriangles(tempTri->verts, tempTri->indexes, tri->primBatchMesh, tri->silTraceVerts ); + + tri = tempTri; + } +#endif +// RAVEN END + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + for ( k = 0; k < 3; k++ ) { +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes (referred to surf->geometry as tri) + v = &tri->verts[ tri->indexes[ j + 2 - k ] ]; +// RAVEN END + w.AddPoint( v->xyz ); + w[k].s = v->st[0]; + w[k].t = v->st[1]; + } + Fracture_r( w ); + } + } + + physicsObj.SetContents( material->GetContentFlags() ); + SetPhysics( &physicsObj ); +} + +/* +================ +idBrittleFracture::FindNeighbours +================ +*/ +void idBrittleFracture::FindNeighbours( void ) { + int i, j, k, l; + idVec3 p1, p2, dir; + idMat3 axis; + idPlane plane[4]; + + for ( i = 0; i < shards.Num(); i++ ) { + + shard_t *shard1 = shards[i]; + const idWinding &w1 = shard1->winding; + const idVec3 &origin1 = shard1->clipModel->GetOrigin(); + const idMat3 &axis1 = shard1->clipModel->GetAxis(); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + + p1 = origin1 + w1[k].ToVec3() * axis1; + p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1; + dir = p2 - p1; + dir.Normalize(); + axis = dir.ToMat3(); + + plane[0].SetNormal( dir ); + plane[0].FitThroughPoint( p1 ); + plane[1].SetNormal( -dir ); + plane[1].FitThroughPoint( p2 ); + plane[2].SetNormal( axis[1] ); + plane[2].FitThroughPoint( p1 ); + plane[3].SetNormal( axis[2] ); + plane[3].FitThroughPoint( p1 ); + + for ( j = 0; j < shards.Num(); j++ ) { + + if ( i == j ) { + continue; + } + + shard_t *shard2 = shards[j]; + + for ( l = 0; l < shard1->neighbours.Num(); l++ ) { + if ( shard1->neighbours[l] == shard2 ) { + break; + } + } + if ( l < shard1->neighbours.Num() ) { + continue; + } + + const idWinding &w2 = shard2->winding; + const idVec3 &origin2 = shard2->clipModel->GetOrigin(); + const idMat3 &axis2 = shard2->clipModel->GetAxis(); + + for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) { + p1 = origin2 + w2[l].ToVec3() * axis2; + p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2; + if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) { + if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) { + if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) { + shard1->neighbours.Append( shard2 ); + shard1->edgeHasNeighbour[k] = true; + shard2->neighbours.Append( shard1 ); + shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true; + break; + } + } + } + } + } + } + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + if ( !shard1->edgeHasNeighbour[k] ) { + break; + } + } + if ( k < w1.GetNumPoints() ) { + shard1->atEdge = true; + } else { + shard1->atEdge = false; + } + } +} + +/* +================ +idBrittleFracture::Event_Activate +================ +*/ +void idBrittleFracture::Event_Activate( idEntity *activator ) { + disableFracture = false; + if ( health <= 0 ) { + Break(); + } +} + +/* +================ +idBrittleFracture::Event_Touch +================ +*/ +void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 point, impulse; + + if ( !IsBroken() ) { + return; + } + + if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) { + return; + } + + point = shards[trace->c.id]->clipModel->GetOrigin(); + impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass(); + + Shatter( point, impulse, gameLocal.time ); +} + +/* +================ +idBrittleFracture::ClientPredictionThink +================ +*/ +void idBrittleFracture::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientReceiveEvent +================ +*/ +bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 point, dir; + + switch( event ) { + case EVENT_PROJECT_DECAL: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + ProjectDecal( point, dir, time, NULL ); + return true; + } + case EVENT_SHATTER: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + Shatter( point, dir, time ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} diff --git a/source/game/BrittleFracture.h b/source/game/BrittleFracture.h new file mode 100644 index 0000000..aa90aa2 --- /dev/null +++ b/source/game/BrittleFracture.h @@ -0,0 +1,103 @@ + +#ifndef __GAME_BRITTLEFRACTURE_H__ +#define __GAME_BRITTLEFRACTURE_H__ + + +/* +=============================================================================== + +B-rep Brittle Fracture - Static entity using the boundary representation +of the render model which can fracture. + +=============================================================================== +*/ + +typedef struct shard_s { + idClipModel * clipModel; + idFixedWinding winding; + idList decals; + idList edgeHasNeighbour; + idList neighbours; + idPhysics_RigidBody physicsObj; + int droppedTime; + bool atEdge; + int islandNum; +} shard_t; + + +class idBrittleFracture : public idEntity { + +public: + CLASS_PROTOTYPE( idBrittleFracture ); + + idBrittleFracture( void ); + virtual ~idBrittleFracture( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Present( void ); + virtual void Think( void ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ); + bool IsBroken( void ) const; + + enum { + EVENT_PROJECT_DECAL = idEntity::EVENT_MAXEVENTS, + EVENT_SHATTER, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + // setttings + const idMaterial * material; + const idMaterial * decalMaterial; + float decalSize; + float maxShardArea; + float maxShatterRadius; + float minShatterRadius; + float linearVelocityScale; + float angularVelocityScale; + float shardMass; + float density; + float friction; + float bouncyness; + idStr fxFracture; + + // state + idPhysics_StaticMulti physicsObj; + idList shards; + idBounds bounds; + bool disableFracture; + + // for rendering + mutable int lastRenderEntityUpdate; + mutable bool changed; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void AddShard( idClipModel *clipModel, idFixedWinding &w ); + void RemoveShard( int index ); + void DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ); + void Shatter( const idVec3 &point, const idVec3 &impulse, const int time ); + void DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ); + void Break( void ); + void Fracture_r( idFixedWinding &w ); + void CreateFractures( const idRenderModel *renderModel ); + void FindNeighbours( void ); + + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_BRITTLEFRACTURE_H__ */ diff --git a/source/game/Camera.cpp b/source/game/Camera.cpp new file mode 100644 index 0000000..eac2a43 --- /dev/null +++ b/source/game/Camera.cpp @@ -0,0 +1,2208 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idCamera + + Base class for cameras + +=============================================================================== +*/ + +ABSTRACT_DECLARATION( idEntity, idCamera ) +END_CLASS + +/* +===================== +idCamera::Spawn +===================== +*/ +void idCamera::Spawn( void ) { +} + +/* +===================== +idCamera::GetRenderView +===================== +*/ +renderView_t *idCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + GetViewParms( rv ); + return rv; +} + +/*********************************************************************** + + idCameraView + +***********************************************************************/ +const idEventDef EV_Camera_SetAttachments( "", NULL ); + +// RAVEN BEGIN +// bdube: added events +const idEventDef EV_SetFOV ( "setFOV", "f" ); +const idEventDef EV_GetFOV ( "getFOV", NULL, 'f' ); +const idEventDef EV_BlendFOV ( "blendFOV", "fff"); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraView ) + EVENT( EV_Activate, idCameraView::Event_Activate ) + EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) + +// RAVEN BEGIN +// bdube: added events + EVENT( EV_SetFOV, idCameraView::Event_SetFOV ) + EVENT( EV_BlendFOV, idCameraView::Event_BlendFOV ) + EVENT( EV_GetFOV, idCameraView::Event_GetFOV ) +// RAVEN END +END_CLASS + +/* +=============== +idCameraView::idCameraView +================ +*/ +idCameraView::idCameraView() { +// RAVEN BEGIN +// bdube: interpolate fov +// scork: get it from the cvar, don't assume 90 + fov.Init ( gameLocal.time, 0, g_fov.GetFloat(), g_fov.GetFloat() ); +// RAVEN END + attachedTo = NULL; + attachedView = NULL; +} + +/* +=============== +idCameraView::Save +================ +*/ +void idCameraView::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// bdube: fov interpolated now + savefile->WriteInt( fov.GetDuration() ); + savefile->WriteInt( fov.GetStartTime() ); + savefile->WriteFloat( fov.GetStartValue() ); + savefile->WriteFloat( fov.GetEndValue() ); +// RAVEN END + savefile->WriteObject( attachedTo ); + savefile->WriteObject( attachedView ); +} + +/* +=============== +idCameraView::Restore +================ +*/ +void idCameraView::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// bdube: fov interpolated now + int set; + float setf; + savefile->ReadInt( set ); + fov.SetDuration( set ); + savefile->ReadInt( set ); + fov.SetStartTime( set ); + savefile->ReadFloat( setf ); + fov.SetStartValue( setf ); + savefile->ReadFloat( setf ); + fov.SetEndValue( setf ); +// RAVEN END + savefile->ReadObject( reinterpret_cast( attachedTo ) ); + savefile->ReadObject( reinterpret_cast( attachedView ) ); +} + +/* +=============== +idCameraView::Event_SetAttachments +================ +*/ +void idCameraView::Event_SetAttachments( ) { + SetAttachment( &attachedTo, "attachedTo" ); + SetAttachment( &attachedView, "attachedView" ); +} + +/* +=============== +idCameraView::Event_Activate +================ +*/ +void idCameraView::Event_Activate( idEntity *activator ) { + if (spawnArgs.GetBool("trigger")) { + if (gameLocal.GetCamera() != this) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + gameLocal.SetCamera(this); + } else { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + } + } +} + +/* +===================== +idCameraView::Stop +===================== +*/ +void idCameraView::Stop( void ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + ActivateTargets( gameLocal.GetLocalPlayer() ); +} + +// RAVEN BEGIN +// bdube: added events +/* +===================== +idCameraView::Event_SetFOV +===================== +*/ +void idCameraView::Event_SetFOV ( float newfov ) +{ + fov.Init ( gameLocal.time, 0, newfov, newfov ); +} + +/* +===================== +idCameraView::Event_BlendFOV +===================== +*/ +void idCameraView::Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ) +{ + fov.Init ( gameLocal.time, SEC2MS(blendTime), beginFOV, endFOV ); +} + +/* +===================== +idCameraView::Event_GetFOV +===================== +*/ +void idCameraView::Event_GetFOV() +{ + idThread::ReturnFloat(fov.GetCurrentValue(gameLocal.time)); +} +// RAVEN END + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::SetAttachment( idEntity **e, const char *p ) { + const char *cam = spawnArgs.GetString( p ); + if ( strlen ( cam ) ) { + *e = gameLocal.FindEntity( cam ); + } +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::Spawn( void ) { + // if no target specified use ourself + const char *cam = spawnArgs.GetString("cameraTarget"); + if ( strlen ( cam ) == 0) { + spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); + } +// RAVEN BEGIN +// bdube: interpolate fov +// scork: ... but default from the cvar, not hardwired 90 + fov.Init ( gameLocal.time, 0, spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())), spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())) ); +// RAVEN END + + PostEventMS( &EV_Camera_SetAttachments, 0 ); + + UpdateChangeableSpawnArgs(NULL); +} + +/* +===================== +idCamera::RenderView +===================== +*/ +void idCameraView::GetViewParms( renderView_t *view ) { + assert( view ); + + if (view == NULL) { + return; + } + + idVec3 dir; + idEntity *ent; + + if ( attachedTo ) { + ent = attachedTo; + } else { + ent = this; + } + + view->vieworg = ent->GetPhysics()->GetOrigin(); + if ( attachedView ) { + dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; + dir.Normalize(); + view->viewaxis = dir.ToMat3(); + } else { + view->viewaxis = ent->GetPhysics()->GetAxis(); + } + +// RAVEN BEGIN +// bdube: interpolate fov + gameLocal.CalcFov( fov.GetCurrentValue( gameLocal.time ), view->fov_x, view->fov_y ); +// RAVEN END +} + + + + + + + + + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/*********************************************************************** + + rvCameraAnimation + +***********************************************************************/ +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( void ) { + frameRate = 0; +} + +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ) { + cameraCuts = anim->cameraCuts; + camera = anim->camera; + frameLookup = anim->frameLookup; + frameCommands = anim->frameCommands; + frameRate = anim->frameRate; + name = anim->name; + realname = anim->realname; +} + +/* +===================== +rvCameraAnimation::~rvCameraAnimation +===================== +*/ +rvCameraAnimation::~rvCameraAnimation( void ) { +} + +/* +===================== +rvCameraAnimation::Name +===================== +*/ +const char *rvCameraAnimation::Name( void ) const { + return name; +} + +/* +===================== +rvCameraAnimation::FullName +===================== +*/ +const char *rvCameraAnimation::FullName( void ) const { + return realname; +} + +/* +===================== +rvCameraAnimation::NumFrames +===================== +*/ +int rvCameraAnimation::NumFrames( void ) const { + return camera.Num(); +} + +/* +===================== +rvCameraAnimation::NumCuts +===================== +*/ +int rvCameraAnimation::NumCuts( void ) const { + return cameraCuts.Num(); +} + +void rvCameraAnimation::SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ) { + int version; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + int numFrames; + int numCuts; + int i; + + filename.SetFileExtension( MD5_CAMERA_EXT ); + if ( !parser.LoadFile( filename ) ) { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate <= 0 ) { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) { + cameraCuts[ i ] = parser.ParseInt(); + if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); + camera[ i ].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); + +#if 0 + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + idDebugGraph gGraph; + idDebugGraph tGraph; + idDebugGraph qGraph; + idDebugGraph dtGraph; + idDebugGraph dqGraph; + gGraph.SetNumSamples( numFrames ); + tGraph.SetNumSamples( numFrames ); + qGraph.SetNumSamples( numFrames ); + dtGraph.SetNumSamples( numFrames ); + dqGraph.SetNumSamples( numFrames ); + + gameLocal.Printf( "\n\ndelta vec:\n" ); + float diff_t, last_t, t; + float diff_q, last_q, q; + diff_t = last_t = 0.0f; + diff_q = last_q = 0.0f; + for( i = 1; i < numFrames; i++ ) { + t = ( camera[ i ].t - camera[ i - 1 ].t ).Length(); + q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length(); + diff_t = t - last_t; + diff_q = q - last_q; + gGraph.AddValue( ( i % 10 ) == 0 ); + tGraph.AddValue( t ); + qGraph.AddValue( q ); + dtGraph.AddValue( diff_t ); + dqGraph.AddValue( diff_q ); + + gameLocal.Printf( "%d: %.8f : %.8f, %.8f : %.8f\n", i, t, diff_t, q, diff_q ); + last_t = t; + last_q = q; + } + + gGraph.Draw( colorBlue, 300.0f ); + tGraph.Draw( colorOrange, 60.0f ); + dtGraph.Draw( colorYellow, 6000.0f ); + qGraph.Draw( colorGreen, 60.0f ); + dqGraph.Draw( colorCyan, 6000.0f ); +#endif +} + +/* +===================== +rvCameraAnimation::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *rvCameraAnimation::AddFrameCommand( const idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { +/* if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token );*/ + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( camera.Num() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > camera.Num() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, name.c_str(), camera.Num() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +/* +===================== +rvCameraAnimation::CallFrameCommandSound +===================== +*/ +void rvCameraAnimation::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} + +/* +===================== +rvCameraAnimation::CallFrameCommands +===================== +*/ +void rvCameraAnimation::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + if ( !frameLookup.Num() ) { + return; + } + + numframes = NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "Cameraframecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect ( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect ( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect ( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_origin, mat3_identity ); + } else { + cent = gameLocal.PlayEffect ( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent && command.joint2 && ent->IsType ( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + cent->SetEndOrigin ( ent->GetAnimator()->GetJointHandle ( *command.joint2 ) ); + } +// RAVEN END + break; + } + case FC_SKIN: +// ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { + break; + } + case FC_FOOTSTEP : { +// ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { +// ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { +// ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { +// ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { +// ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { +// ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { +// ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: +// ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: +// ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: +// ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: +// ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: +// ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: +// ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: +// ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_ACT_ATTACH_SHOW: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_AI_ATTACK_MELEE: +// ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + + + + + + + + + +/*********************************************************************** + + idDeclCameraDef + +***********************************************************************/ + +/* +===================== +idDeclCameraDef::idDeclCameraDef +===================== +*/ +idDeclCameraDef::idDeclCameraDef() { +} + +/* +===================== +idDeclCameraDef::~idDeclCameraDef +===================== +*/ +idDeclCameraDef::~idDeclCameraDef() { + FreeData(); +} + +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +/* +===================== +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +===================== +*/ +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::Write( SerialOutputStream &stream ) const +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::AddReferences() const +{ + // not supported yet + assert(false); +} +#endif +// RAVEN END +/* +================= +idDeclCameraDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclCameraDef::Size( void ) const { + + size_t size; + + size = sizeof( idDeclCameraDef ); + size += anims.Allocated(); + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclCameraDef::CopyDecl +===================== +*/ +void idDeclCameraDef::CopyDecl( const idDeclCameraDef *decl ) { + int i; + + FreeData(); + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new rvCameraAnimation( this, decl->anims[ i ] ); + } +} + +/* +===================== +idDeclCameraDef::FreeData +===================== +*/ +void idDeclCameraDef::FreeData( void ) { + anims.DeleteContents( true ); +} + +/* +================ +idDeclCameraDef::DefaultDefinition +================ +*/ +const char *idDeclCameraDef::DefaultDefinition( void ) const { + return "{ }"; +} + + + +/* +===================== +idDeclCameraDef::Touch +===================== +*/ +void idDeclCameraDef::Touch( void ) const { +} + +/* +===================== +idDeclCameraDef::ParseAnim +===================== +*/ +bool idDeclCameraDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + rvCameraAnimation *anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + + numAnims = 0; + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new rvCameraAnimation(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, token ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + return true; +} + +/* +================ +idDeclCameraDef::Parse +================ +*/ +bool idDeclCameraDef::Parse( const char *text, const int textLength, bool noCaching ) { + idStr filename; + idStr extension; + idLexer src; + idToken token; + idToken token2; + int numDefaultAnims; + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + else if ( token == "anim" ) { + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclCameraDef::Validate +===================== +*/ +bool idDeclCameraDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclCameraDef *pSelf = (idDeclCameraDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclCameraDef::HasAnim +===================== +*/ +bool idDeclCameraDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclCameraDef::NumAnims +===================== +*/ +int idDeclCameraDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclCameraDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclCameraDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclCameraDef::GetAnim +===================== +*/ +int idDeclCameraDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + + + + + + + + + + +/* +=============================================================================== + + idCameraAnim + +=============================================================================== +*/ +const idEventDef EV_Camera_Start( "start", NULL ); +const idEventDef EV_Camera_Stop( "stop", NULL ); + +// RAVEN BEGIN +// mekberg: wait support +const idEventDef EV_Camera_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraAnim ) + EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) + EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) + EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) + EVENT( EV_Activate, idCameraAnim::Event_Activate ) + + // RAVEN BEGIN + // mekberg: wait support + EVENT( EV_IsActive, idCameraAnim::Event_IsActive ) + // RAVEN END +END_CLASS + + +/* +===================== +idCameraAnim::idCameraAnim +===================== +*/ +idCameraAnim::idCameraAnim() { + threadNum = 0; + offset.Zero(); + cycle = 1; + starttime = 0; + activator = NULL; + lastFrame = -1; +} + +/* +===================== +idCameraAnim::~idCameraAnim +===================== +*/ +idCameraAnim::~idCameraAnim() { + if ( gameLocal.GetCamera() == this ) { + gameLocal.SetCamera( NULL ); + } +} + +/* +=============== +idCameraAnim::Save +================ +*/ +void idCameraAnim::Save( idSaveGame *savefile ) const { + savefile->WriteInt( threadNum ); + savefile->WriteVec3( offset ); + savefile->WriteInt( starttime ); + savefile->WriteInt( cycle ); + savefile->WriteInt( lastFrame ); // cnicholson: Added unsaved var + + activator.Save( savefile ); +} + +/* +=============== +idCameraAnim::Restore +================ +*/ +void idCameraAnim::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + savefile->ReadVec3( offset ); + savefile->ReadInt( starttime ); + savefile->ReadInt( cycle ); + savefile->ReadInt( lastFrame ); // cnicholson: Added unread var + + activator.Restore( savefile ); + + LoadAnim(); +} + +/* +===================== +idCameraAnim::Spawn +===================== +*/ +void idCameraAnim::Spawn( void ) { + if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { + offset = GetPhysics()->GetOrigin() - offset; + } else { + offset.Zero(); + } + + // always think during cinematics + cinematic = true; + + LoadAnim(); + +// RAVEN BEGIN +#ifndef _CONSOLE + // touch the cinematic streaming command file during build + if ( cvarSystem->GetCVarBool("com_makingBuild") && cvarSystem->GetCVarBool("com_Bundler") ) + { + idFile *file; + idStr filename = "cinematics/"; + filename += gameLocal.mapFileNameStripped; + filename += "_"; + filename += name; + filename += ".cincmd"; + file = fileSystem->OpenFileRead( filename ); + fileSystem->CloseFile( file ); + } +#endif +// RAVEN END +} + +/* +================ +idCameraAnim::Load +================ +*/ +void idCameraAnim::LoadAnim( void ) { + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + idStr filename; + const char *key; + + key = spawnArgs.GetString( "camera" ); + const idDecl *mapDecl = declManager->FindType( DECL_CAMERADEF, key, false ); + cameraDef = static_cast( mapDecl ); + + key = spawnArgs.GetString( "anim" ); + if ( !key ) { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + +} + +/* +=============== +idCameraAnim::Start +================ +*/ +void idCameraAnim::Start( void ) { + cycle = spawnArgs.GetInt( "cycle" ); + if ( !cycle ) { + cycle = 1; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + lastFrame = -1; + starttime = gameLocal.time; + gameLocal.SetCamera( this ); + BecomeActive( TH_THINK ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_BEGIN, cameraDef->GetAnim(1)->GetFrameRate(), GetName() ); +#endif +// RAVEN END + + // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame +// RAVEN BEGIN +// mekberg: make sure render view is valid. + if ( gameLocal.GetLocalPlayer( )->GetRenderView( ) && gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) { +// RAVEN END + gameLocal.GetLocalPlayer()->CalculateRenderView(); + } +} + +/* +===================== +idCameraAnim::Stop +===================== +*/ +void idCameraAnim::Stop( void ) { + if ( gameLocal.GetCamera() == this ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + + BecomeInactive( TH_THINK ); + gameLocal.SetCamera( NULL ); + if ( threadNum ) { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; + } + ActivateTargets( activator.GetEntity() ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_END, cameraDef->GetAnim(1)->GetFrameRate() ); +#endif +// RAVEN END + } +} + +/* +===================== +idCameraAnim::Think +===================== +*/ +void idCameraAnim::Think( void ) { + int frame; + int frameTime; + + if ( thinkFlags & TH_THINK ) { + // check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics). +// RAVEN BEGIN +// abahr: removed '!' + if ( gameLocal.skipCinematic ) { +// RAVEN END + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() < 2 ) { + // 1 frame anims never end + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + } + + if ( frame > cameraDef->GetAnim(1)->NumFrames() + cameraDef->GetAnim(1)->NumCuts() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + lastFrame = -1; + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + } else { + Stop(); + } + } + } +} + +/* +===================== +idCameraAnim::GetViewParms +===================== +*/ +void idCameraAnim::GetViewParms( renderView_t *view ) { + int realFrame; + int frame; + int frameTime; + float lerp; + float invlerp; + const cameraFrame_t *camFrame; + int i; + int cut; + idQuat q1, q2, q3; + + assert( view ); + if ( !view ) { + return; + } +//RAVEN BEGIN +//jshepard: safety first + if( !cameraDef ) { + gameLocal.Warning("Invalid cameraDef in GetViewParms"); + return; + } +//RAVEN END + if ( !cameraDef->GetAnim(1) ) { + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() == 0 ) { + // we most likely are in the middle of a restore + // FIXME: it would be better to fix it so this doesn't get called during a restore + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + lerp = 0.0f; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + lerp = ( frameTime % 1000 ) * 0.001f; + } + + // skip any frames where camera cuts occur + realFrame = frame; + cut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( frame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + frame++; + cut++; + } + if ( lastFrame != frame ) { + cameraDef->GetAnim(1)->CallFrameCommands( this, lastFrame, frame ); + lastFrame = frame; + } + + if ( g_debugCinematic.GetBool() ) { + int prevFrameTime = ( gameLocal.time - starttime - gameLocal.msec ) * cameraDef->GetAnim(1)->GetFrameRate(); + int prevFrame = prevFrameTime / 1000; + int prevCut; + + prevCut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( prevFrame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + prevFrame++; + prevCut++; + } + + if ( prevCut != cut ) { + gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); + } + } + + // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, + // but since they're mainly used for static cams anyway, just stay on it infinitely. + if ( ( frame < 0 ) || ( cameraDef->GetAnim(1)->NumFrames() < 2 ) ) { + view->viewaxis = cameraDef->GetAnim(1)->GetAnim( 0 )->q.ToQuat().ToMat3(); + view->vieworg = cameraDef->GetAnim(1)->GetAnim( 0 )->t + offset; + view->fov_x = cameraDef->GetAnim(1)->GetAnim( 0 )->fov; + } else if ( frame > cameraDef->GetAnim(1)->NumFrames() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + GetViewParms( view ); + return; + } + + Stop(); + if ( gameLocal.GetCamera() != NULL ) { + // we activated another camera when we stopped, so get it's viewparms instead + gameLocal.GetCamera()->GetViewParms( view ); + return; + } else { + // just use our last frame + camFrame = cameraDef->GetAnim(1)->GetAnim( cameraDef->GetAnim(1)->NumFrames() - 1 ); + view->viewaxis = camFrame->q.ToQuat().ToMat3(); + view->vieworg = camFrame->t + offset; + view->fov_x = camFrame->fov; + } + } else if ( lerp == 0.0f ) { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + view->viewaxis = camFrame[ 0 ].q.ToMat3(); + view->vieworg = camFrame[ 0 ].t + offset; + view->fov_x = camFrame[ 0 ].fov; + } else { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + invlerp = 1.0f - lerp; + q1 = camFrame[ 0 ].q.ToQuat(); + q2 = camFrame[ 1 ].q.ToQuat(); + q3.Slerp( q1, q2, lerp ); + view->viewaxis = q3.ToMat3(); + view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; + view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; + } + + gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); + + // setup the pvs for this frame + UpdatePVSAreas( view->vieworg ); + +#if 0 + static int lastFrame = 0; + static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); + if ( gameLocal.time != lastFrame ) { + gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); + gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); + gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + lastFrameVec = view->vieworg; + lastFrame = gameLocal.time; + } +#endif + + if ( g_showcamerainfo.GetBool() ) { + gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ); + } +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_UPDATE, realFrame ); +#endif +} +// RAVEN END + +/* +=============== +idCameraAnim::Event_Activate +================ +*/ +void idCameraAnim::Event_Activate( idEntity *_activator ) { + activator = _activator; + if ( thinkFlags & TH_THINK ) { + Stop(); + } else { + Start(); + } +} + +/* +=============== +idCameraAnim::Event_Start +================ +*/ +void idCameraAnim::Event_Start( void ) { + Start(); +} + +/* +=============== +idCameraAnim::Event_Stop +================ +*/ +void idCameraAnim::Event_Stop( void ) { + Stop(); +} + +/* +================ +idCameraAnim::Event_SetCallback +================ +*/ +void idCameraAnim::Event_SetCallback( void ) { + if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// mekberg: wait support +/* +================ +idCameraAnim::Event_IsActive +================ +*/ +void idCameraAnim::Event_IsActive( void ) { + idThread::ReturnFloat( gameLocal.GetCamera( ) ? 1.0f : 0.0f ); +} + +// RAVEN END + +// RAVEN BEGIN +// jscott: portal sky support + +/*********************************************************************** + + rvCameraPortalSky + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPortalSky ) +END_CLASS + +/* +=============== +rvCameraPortalSky::Save +================ +*/ +void rvCameraPortalSky::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPortalSky::Restore +================ +*/ +void rvCameraPortalSky::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPortalSky::Spawn +===================== +*/ +void rvCameraPortalSky::Spawn( void ) +{ + if( gameLocal.GetPortalSky() ) + { + gameLocal.Error( "Only one portal sky camera allowed" ); + } + gameLocal.SetPortalSky( this ); +} + +/* +===================== +rvCameraPortalSky::GetViewParms +===================== +*/ +void rvCameraPortalSky::GetViewParms( renderView_t *view ) +{ + assert( view ); + if( view ) + { + view->vieworg = GetPhysics()->GetOrigin(); + view->viewID = -1; + } +} + + +/*********************************************************************** + + rvCameraPlayback + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPlayback ) +END_CLASS + +/* +=============== +rvCameraPlayback::Save +================ +*/ +void rvCameraPlayback::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPlayback::Restore +================ +*/ +void rvCameraPlayback::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPlayback::Spawn +===================== +*/ +void rvCameraPlayback::Spawn( void ) +{ + startTime = gameLocal.time; + playback = declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); +} + +/* +===================== +rvCameraPlayback::GetViewParms +===================== +*/ +void rvCameraPlayback::GetViewParms( renderView_t *view ) +{ + rvDeclPlaybackData pbd; + + assert( view ); + if( view ) + { + pbd.Init(); + if( declManager->GetPlaybackData( playback, PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, gameLocal.time - startTime, gameLocal.time - startTime, &pbd ) ) + { + startTime = gameLocal.time; + } + + view->vieworg = pbd.GetPosition(); + view->viewaxis = pbd.GetAngles().ToMat3(); + + // field of view +// RAVEN BEGIN +// jshepard: fov as a float for smoove transitions + gameLocal.CalcFov ( g_fov.GetFloat(), view->fov_x, view->fov_y ); + +// RAVEN END + } +} + +// RAVEN END diff --git a/source/game/Camera.h b/source/game/Camera.h new file mode 100644 index 0000000..21237e0 --- /dev/null +++ b/source/game/Camera.h @@ -0,0 +1,296 @@ + +#ifndef __GAME_CAMERA_H__ +#define __GAME_CAMERA_H__ + +/* +=============================================================================== + +Camera providing an alternative view of the level. + +=============================================================================== +*/ + +class idCamera : public idEntity { +public: + ABSTRACT_PROTOTYPE( idCamera ); + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ) = 0; + virtual renderView_t * GetRenderView(); + virtual void Stop( void ){} ; +}; + +/* +=============================================================================== + +idCameraView + +=============================================================================== +*/ + +extern const idEventDef EV_SetFOV; +extern const idEventDef EV_Camera_Start; +extern const idEventDef EV_Camera_Stop; + +class idCameraView : public idCamera { +public: + CLASS_PROTOTYPE( idCameraView ); + idCameraView(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( ); + virtual void GetViewParms( renderView_t *view ); + virtual void Stop( void ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_SetAttachments(); + void SetAttachment( idEntity **e, const char *p ); +// RAVEN BEGIN +// bdube: changed fov to interpolated value + idInterpolate fov; +// RAVEN END + idEntity *attachedTo; + idEntity *attachedView; + +// RAVEN BEGIN +// bdube: added setfov event + void Event_SetFOV ( float fov ); + void Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ); + void Event_GetFOV ( void ); +// RAVEN END +}; + + + +/* +=============================================================================== + +A camera which follows a path defined by an animation. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/* +============================================================================================== + + rvCameraAnimation + +============================================================================================== +*/ +class idDeclCameraDef; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +class rvCameraAnimation { +private: + idList cameraCuts; + idList camera; + idList frameLookup; + idList frameCommands; + int frameRate; + idStr name; + idStr realname; + +public: + rvCameraAnimation(); + rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ); + ~rvCameraAnimation(); + + void SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ); + const char *Name( void ) const; + const char *FullName( void ) const; + int NumFrames( void ) const; + const cameraFrame_t * GetAnim( int index ) const; + int NumCuts( void ) const; + const int GetCut( int index ) const; + const int GetFrameRate( void ) const; + + const char *AddFrameCommand( const class idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ); + void CallFrameCommands( idEntity *ent, int from, int to ) const; + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; +}; + +ID_INLINE const cameraFrame_t *rvCameraAnimation::GetAnim( int index ) const { + return &camera[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetCut( int index ) const { + return cameraCuts[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetFrameRate( void ) const { + return frameRate; +} + +/* +============================================================================================== + + idDeclCameraDef + +============================================================================================== +*/ +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclCameraDef : public idDecl, public Serializable<'IDCD'> { +public: + idDeclCameraDef( SerialInputStream &stream ); + + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; +#else +class idDeclCameraDef : public idDecl { +#endif +// RAVEN END +public: + idDeclCameraDef(); + ~idDeclCameraDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + int NumAnims( void ) const; + const rvCameraAnimation * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + +private: + void CopyDecl( const idDeclCameraDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idList anims; +}; + +ID_INLINE const rvCameraAnimation *idDeclCameraDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idCameraAnim + +============================================================================================== +*/ +class idCameraAnim : public idCamera { +public: + CLASS_PROTOTYPE( idCameraAnim ); + + idCameraAnim(); + ~idCameraAnim(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int threadNum; + idVec3 offset; + int starttime; + int cycle; + const idDeclCameraDef *cameraDef; + int lastFrame; + idEntityPtr activator; + + void Start( void ); + void Stop( void ); + void Think( void ); + + void LoadAnim( void ); + void Event_Start( void ); + void Event_Stop( void ); + void Event_SetCallback( void ); + void Event_Activate( idEntity *activator ); + +// RAVEN BEGIN +// mekberg: wait support + void Event_IsActive( ); + + idList imageTable; + idList imageCmds; +// RAVEN END +}; +// RAVEN END + +// RAVEN BEGIN +/* +=============================================================================== + +rvCameraPortalSky + +=============================================================================== +*/ +// jscott: for portal skies +class rvCameraPortalSky : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPortalSky ); + + rvCameraPortalSky( void ) {} + ~rvCameraPortalSky( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); +}; + +/* +=============================================================================== + +rvCameraPlayback + +=============================================================================== +*/ +class rvCameraPlayback : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPlayback ); + + rvCameraPlayback( void ) {} + ~rvCameraPlayback( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int startTime; + const rvDeclPlayback *playback; +}; +// RAVEN END + +#endif /* !__GAME_CAMERA_H__ */ diff --git a/source/game/Effect.cpp b/source/game/Effect.cpp new file mode 100644 index 0000000..b810a7a --- /dev/null +++ b/source/game/Effect.cpp @@ -0,0 +1,463 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Effect.h" +#include "client/ClientEffect.h" + +const idEventDef EV_LookAtTarget( "lookAtTarget", NULL ); +const idEventDef EV_Attenuate( "attenuate", "f" ); + +CLASS_DECLARATION( idEntity, rvEffect ) + EVENT( EV_Activate, rvEffect::Event_Activate ) + EVENT( EV_LookAtTarget, rvEffect::Event_LookAtTarget ) + EVENT( EV_Earthquake, rvEffect::Event_EarthQuake ) + EVENT( EV_Camera_Start, rvEffect::Event_Start ) + EVENT( EV_Camera_Stop, rvEffect::Event_Stop ) + EVENT( EV_Attenuate, rvEffect::Event_Attenuate ) + EVENT( EV_IsActive, rvEffect::Event_IsActive ) +END_CLASS + +/* +================ +rvEffect::rvEffect +================ +*/ +rvEffect::rvEffect ( void ) { + fl.networkSync = true; + loop = false; + lookAtTarget = false; + effect = NULL; + endOrigin.Zero(); +} + +/* +================ +rvEffect::Spawn +================ +*/ +void rvEffect::Spawn( void ) { + const char* fx; + if ( !spawnArgs.GetString ( "fx", "", &fx ) || !*fx ) { + if ( !( gameLocal.editors & EDITOR_FX ) ) { + gameLocal.Warning ( "no effect file specified on effect entity '%s'", name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + } else { + effect = ( const idDecl * )declManager->FindEffect( spawnArgs.GetString ( "fx" ) ); + if( effect->IsImplicit() ) { + common->Warning( "Unknown effect \'%s\' on entity \'%s\'", spawnArgs.GetString ( "fx" ), GetName() ); + } + } + + spawnArgs.GetVector ( "endOrigin", "0 0 0", endOrigin ); + + spawnArgs.GetBool ( "loop", "0", loop ); + + // If look at target is set the effect will continually update itself to look at its target + spawnArgs.GetBool( "lookAtTarget", "0", lookAtTarget ); + + renderEntity.shaderParms[SHADERPARM_ALPHA] = spawnArgs.GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[SHADERPARM_BRIGHTNESS] = spawnArgs.GetFloat ( "_brightness", "1" ); + + if( spawnArgs.GetBool( "start_on", loop ? "1" : "0" ) ) { + ProcessEvent( &EV_Activate, this ); + } +#if 0 + // If anyone ever gets around to a flood fill from the origin rather than the over generous PushVolumeIntoTree bounds, + // this warning will become useful. Until then, it's a bogus warning. + if( gameRenderWorld->PointInArea( GetPhysics()->GetOrigin() ) < 0 ) { + common->Warning( "Effect \'%s\' out of world", name.c_str() ); + } +#endif +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Think( void ) { + + if( clientEntities.IsListEmpty ( ) ) { + BecomeInactive( TH_THINK ); + + // Should the func_fx be removed now? + if( !(gameLocal.editors & EDITOR_FX) && spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + return; + } + else if( lookAtTarget ) { + // If activated and looking at its target then update the target information + ProcessEvent( &EV_LookAtTarget ); + } + + UpdateVisuals(); + Present ( ); +} + +/* +================ +rvEffect::Save +================ +*/ +void rvEffect::Save( idSaveGame *savefile ) const { + savefile->WriteBool( loop ); + savefile->WriteBool( lookAtTarget ); + savefile->WriteString( effect->GetName() ); + savefile->WriteVec3( endOrigin ); + clientEffect.Save( savefile ); +} + +/* +================ +rvEffect::Restore +================ +*/ +void rvEffect::Restore( idRestoreGame *savefile ) { + idStr name; + + savefile->ReadBool( loop ); + savefile->ReadBool( lookAtTarget ); + savefile->ReadString( name ); + effect = declManager->FindType( DECL_EFFECT, name ); + savefile->ReadVec3( endOrigin ); + clientEffect.Restore( savefile ); +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Stop( bool destroyParticles ) { + StopEffect ( effect, destroyParticles ); +} + +/* +================ +rvEffect::Play +================ +*/ +bool rvEffect::Play( void ) { + clientEffect = PlayEffect ( effect, renderEntity.origin, renderEntity.axis, loop, endOrigin ); + if ( clientEffect ) { + + idVec4 color; + color[0] = renderEntity.shaderParms[SHADERPARM_RED]; + color[1] = renderEntity.shaderParms[SHADERPARM_GREEN]; + color[2] = renderEntity.shaderParms[SHADERPARM_BLUE]; + color[3] = renderEntity.shaderParms[SHADERPARM_ALPHA]; + clientEffect->SetColor ( color ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + clientEffect->SetAmbient( true ); + + BecomeActive ( TH_THINK ); + return true; + } + + return false; +} + +/* +================ +rvEffect::Attenuate +================ +*/ +void rvEffect::Attenuate ( float attenuation ) { + rvClientEntity* cent; + for( cent = clientEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { +// RAVEN END + static_cast(cent)->Attenuate ( attenuation ); + } + } +} + +/* +================ +rvEffect::Restart +================ +*/ +void rvEffect::Restart( void ) { + Stop( false ); + + if( loop ) { + Play(); + } +} + +/* +================ +rvEffect::UpdateChangeableSpawnArgs +================ +*/ +void rvEffect::UpdateChangeableSpawnArgs( const idDict *source ) { + const char* fx; + const idDecl *newEffect; + bool newLoop; + + idEntity::UpdateChangeableSpawnArgs(source); + if ( !source ) { + return; + } + + if ( source->GetString ( "fx", "", &fx ) && *fx ) { + newEffect = ( const idDecl * )declManager->FindEffect( fx ); + } else { + newEffect = NULL; + } + + idVec3 color; + source->GetVector( "_color", "1 1 1", color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = source->GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] = source->GetFloat ( "_brightness", "1" ); + if ( clientEffect ) { + clientEffect->SetColor ( idVec4(color[0],color[1],color[2],renderEntity.shaderParms[ SHADERPARM_ALPHA ]) ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + } + + source->GetBool ( "loop", "0", newLoop ); + + spawnArgs.Copy( *source ); + + // IF the effect handle has changed or the loop status has changed then restart the effect + if ( newEffect != effect || loop != newLoop ) { + Stop ( false ); + + loop = newLoop; + effect = newEffect; + + if ( effect ) { + Play ( ); + BecomeActive( TH_THINK ); + UpdateVisuals(); + } else { + BecomeInactive ( TH_THINK ); + UpdateVisuals(); + } + } +} + +/* +=============== +rvEffect::ShowEditingDialog +=============== +*/ +void rvEffect::ShowEditingDialog( void ) { + common->InitTool( EDITOR_FX, &spawnArgs ); +} + +/* +================= +rvEffect::WriteToSnapshot +================= +*/ +void rvEffect::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteBits( loop, 1 ); +} + +/* +================= +rvEffect::ReadFromSnapshot +================= +*/ +void rvEffect::ReadFromSnapshot( const idBitMsgDelta &msg ) { + const idDecl *old = effect; + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + loop = ( msg.ReadBits( 1 ) != 0 ); + + if ( effect && !old ) { + // TODO: need to account for when the effect really started + Play(); + } +} + +/* +================= +rvEffect::ClientPredictionThink +================= +*/ +void rvEffect::ClientPredictionThink( void ) { + if ( gameLocal.isNewFrame ) { + Think ( ); + } + RunPhysics(); + Present(); +} + +/* +================ +rvEffect::Event_Start +================ +*/ +void rvEffect::Event_Start ( void ) { + if( !effect || !clientEntities.IsListEmpty ( ) ) { + return; + } + + if( !Play() ) { + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + } else { + gameLocal.Warning( "Unable to play effect '%s'", effect->GetName() ); + } + BecomeInactive ( TH_THINK ); + } + + ProcessEvent( &EV_LookAtTarget ); +} + +/* +================ +rvEffect::Event_Stop +================ +*/ +void rvEffect::Event_Stop ( void ) { + if( !effect ) { + return; + } + + Stop( false ); +} + +/* +================= +rvEffect::Event_Activate +================= +*/ +void rvEffect::Event_Activate( idEntity *activator ) { + // Stop the effect if its already playing + if( !clientEntities.IsListEmpty ( ) ) { + Event_Stop ( ); + } else { + Event_Start ( ); + } + + ActivateTargets( activator ); +} + +/* +================ +rvEffect::Event_LookAtTarget + +Reorients the effect entity towards its target and sets the end origin as well +================ +*/ +void rvEffect::Event_LookAtTarget ( void ) { + const idKeyValue *kv; + idVec3 dir; + + if ( !effect || !clientEffect ) { + return; + } + + kv = spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if( ent ) { + if( !idStr::Icmp( ent->GetEntityDefName(), "target_null" ) ) { + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + + clientEffect->SetEndOrigin ( ent->GetPhysics()->GetOrigin() ); + clientEffect->SetAxis ( dir.ToMat3( ) ); + return; + } + } + kv = spawnArgs.MatchPrefix( "target", kv ); + } +} + +/* +================ +rvEffect::Event_EarthQuake +================ +*/ +void rvEffect::Event_EarthQuake ( float requiresLOS ) { + float quakeChance; + + if ( !spawnArgs.GetFloat("quakeChance", "0", quakeChance) ) { + return; + } + + if ( rvRandom::flrand(0, 1.0f) > quakeChance ) { + // failed its activation roll + return; + } + + if ( requiresLOS ) { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + + // activate this effect now + ProcessEvent ( &EV_Activate, gameLocal.entities[ENTITYNUM_WORLD] ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_Attenuate( float attenuation ) { + Attenuate( attenuation ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_IsActive( void ) { + idThread::ReturnFloat( ( !effect || !clientEntities.IsListEmpty() ) ? 0.0f : 1.0f ); +} + +/* +================ +rvEffect::InstanceLeave +================ +*/ +void rvEffect::InstanceLeave( void ) { + idEntity::InstanceLeave(); + Stop( true ); +} + +/* +================ +rvEffect::InstanceJoin +================ +*/ +void rvEffect::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + Restart(); +} \ No newline at end of file diff --git a/source/game/Effect.h b/source/game/Effect.h new file mode 100644 index 0000000..8b93585 --- /dev/null +++ b/source/game/Effect.h @@ -0,0 +1,66 @@ +//---------------------------------------------------------------- +// Effect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_EFFECT_H__ +#define __GAME_EFFECT_H__ + +class rvEffect : public idEntity +{ +public: + + CLASS_PROTOTYPE( rvEffect ); + + rvEffect ( void ); + + const bool GetEndOrigin ( idVec3 &result ) const; + void SetEndOrigin ( const idVec3 &origin ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Play ( void ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + void Attenuate ( float attenuation ); + + float GetBrightness ( void ) const; + + bool IsLooping ( void ) { return( loop ); } + + virtual void UpdateChangeableSpawnArgs ( const idDict *source ); + virtual void ShowEditingDialog ( void ); + + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + void ClientPredictionThink ( void ); + virtual void InstanceLeave ( void ); + virtual void InstanceJoin ( void ); + +protected: + + bool loop; + bool lookAtTarget; + const idDecl *effect; + idVec3 endOrigin; + rvClientEntityPtr clientEffect; + +private: + + void Event_Activate ( idEntity *activator ); + void Event_LookAtTarget ( void ); + void Event_EarthQuake ( float requiresLOS ); + + void Event_Start ( void ); + void Event_Stop ( void ); + + void Event_Attenuate ( float attenuation ); + void Event_IsActive ( void ); +}; + +#endif // __GAME_EFFECT_H__ diff --git a/source/game/Entity.cpp b/source/game/Entity.cpp new file mode 100644 index 0000000..656af46 --- /dev/null +++ b/source/game/Entity.cpp @@ -0,0 +1,6789 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// bdube: client effects +#include "client/ClientEffect.h" +//mcg: need to know team for AddDamageEffects +#include "ai/AI_Manager.h" +// RAVEN END + +/* +=============================================================================== + + idEntity + +=============================================================================== +*/ + +// overridable events +const idEventDef EV_PostSpawn( "", NULL ); +const idEventDef EV_FindTargets( "", NULL ); +const idEventDef EV_Touch( "", "et" ); +const idEventDef EV_GetName( "getName", NULL, 's' ); +const idEventDef EV_SetName( "setName", "s" ); +const idEventDef EV_Activate( "activate", "e" ); +const idEventDef EV_ActivateTargets( "activateTargets", "e" ); +const idEventDef EV_NumTargets( "numTargets", NULL, 'f' ); +const idEventDef EV_GetTarget( "getTarget", "f", 'e' ); +const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' ); +const idEventDef EV_Bind( "bind", "e" ); +const idEventDef EV_BindPosition( "bindPosition", "e" ); +const idEventDef EV_BindToJoint( "bindToJoint", "esf" ); +const idEventDef EV_Unbind( "unbind", NULL ); +const idEventDef EV_RemoveBinds( "removeBinds" ); +const idEventDef EV_SpawnBind( "", NULL ); +const idEventDef EV_SetOwner( "setOwner", "e" ); +const idEventDef EV_SetModel( "setModel", "s" ); +const idEventDef EV_SetSkin( "setSkin", "s" ); +const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' ); +const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" ); +const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' ); +const idEventDef EV_SetOrigin( "setOrigin", "v" ); +const idEventDef EV_GetAngles( "getAngles", NULL, 'v' ); +const idEventDef EV_SetAngles( "setAngles", "v" ); +const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' ); +const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" ); +const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' ); +const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" ); +const idEventDef EV_GetSize( "getSize", NULL, 'v' ); +const idEventDef EV_SetSize( "setSize", "vv" ); +const idEventDef EV_GetMins( "getMins", NULL, 'v' ); +const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' ); +const idEventDef EV_IsHidden( "isHidden", NULL, 'd' ); +const idEventDef EV_Hide( "hide", NULL ); +const idEventDef EV_Show( "show", NULL ); +const idEventDef EV_Touches( "touches", "E", 'd' ); +const idEventDef EV_ClearSignal( "clearSignal", "d" ); +const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' ); +const idEventDef EV_SetShaderParm( "setShaderParm", "df" ); +const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" ); +const idEventDef EV_SetColor( "setColor", "fff" ); +const idEventDef EV_GetColor( "getColor", NULL, 'v' ); +const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" ); +const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' ); +const idEventDef EV_StartSound( "startSound", "sdd", 'f' ); +const idEventDef EV_StopSound( "stopSound", "dd" ); +const idEventDef EV_FadeSound( "fadeSound", "dff" ); +const idEventDef EV_SetGuiParm( "setGuiParm", "ss" ); +const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" ); +const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' ); +const idEventDef EV_SetKey( "setKey", "ss" ); +const idEventDef EV_GetKey( "getKey", "s", 's' ); +const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' ); +const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' ); +const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' ); +const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' ); +const idEventDef EV_RestorePosition( "restorePosition" ); +const idEventDef EV_UpdateCameraTarget( "", NULL ); +const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' ); +const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' ); +const idEventDef EV_StartFx( "startFx", "s" ); +const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); +const idEventDef EV_CallFunction( "callFunction", "s" ); +const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); + +// RAVEN BEGIN +// bgeisler: go back to default skin +const idEventDef EV_ClearSkin( "clearSkin"); +// kfuller: added events +const idEventDef EV_SetContents( "setContents", "d" ); +const idEventDef EV_GetLastBlocker( "getLastBlocker", NULL, 'e' ); +const idEventDef EV_Earthquake( "earthquake", "f" ); +// we should probably try to integrate this with AI_PlayAnim +const idEventDef EV_PlayAnim("playAnimNoChannel", "s"); +const idEventDef EV_PlayAnimXTimes("playAnimXTimes", "sf"); +// bdube: effect events +const idEventDef EV_PlayEffect( "playEffect", "ssd" ); +const idEventDef EV_StopEffect( "stopEffect", "s" ); +const idEventDef EV_StopAllEffects( "stopAllEffects" ); +const idEventDef EV_GetHealth ( "getHealth", NULL, 'f' ); +// bdube: surface related events +const idEventDef EV_HideSurface( "hideSurface", "s" ); +const idEventDef EV_ShowSurface( "showSurface", "s" ); +// bdube: added gui events +const idEventDef EV_GuiEvent ( "guiEvent", "s" ); +// jscott: for playback button handling +const idEventDef EV_PlaybackCallback( "playbackCallback", "ddd" ); +// nmckenzie: +const idEventDef EV_GetBindMaster( "getBindMaster", NULL, 'e' ); +const idEventDef EV_ApplyImpulse( "applyImpulse", "evv" ); +// abahr: +const idEventDef EV_RemoveNullTargets( "removeNullTargets" ); +const idEventDef EV_IsA( "isA", "s", 'f' ); +const idEventDef EV_IsSameTypeAs( "isSameTypeAs", "e", 'f' ); +const idEventDef EV_MatchPrefix( "matchPrefix", "ss", 's' ); +const idEventDef EV_ClearTargetList( "clearTargetList", "f" ); +// twhitaker: +const idEventDef EV_AppendTarget( "appendTarget", "E", 'f' ); +const idEventDef EV_RemoveTarget( "removeTarget", "e" ); +// mekberg: +const idEventDef EV_SetHealth( "setHealth", "f" ); +// RAVEN END + +ABSTRACT_DECLARATION( idClass, idEntity ) + EVENT( EV_GetName, idEntity::Event_GetName ) + EVENT( EV_SetName, idEntity::Event_SetName ) + EVENT( EV_FindTargets, idEntity::Event_FindTargets ) + EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets ) + EVENT( EV_NumTargets, idEntity::Event_NumTargets ) + EVENT( EV_GetTarget, idEntity::Event_GetTarget ) + EVENT( EV_RandomTarget, idEntity::Event_RandomTarget ) + EVENT( EV_BindToJoint, idEntity::Event_BindToJoint ) + EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds ) + EVENT( EV_Bind, idEntity::Event_Bind ) + EVENT( EV_BindPosition, idEntity::Event_BindPosition ) + EVENT( EV_Unbind, idEntity::Event_Unbind ) + EVENT( EV_SpawnBind, idEntity::Event_SpawnBind ) + EVENT( EV_SetOwner, idEntity::Event_SetOwner ) + EVENT( EV_SetModel, idEntity::Event_SetModel ) + EVENT( EV_SetSkin, idEntity::Event_SetSkin ) + EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm ) + EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm ) + EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms ) + EVENT( EV_SetColor, idEntity::Event_SetColor ) + EVENT( EV_GetColor, idEntity::Event_GetColor ) + EVENT( EV_IsHidden, idEntity::Event_IsHidden ) + EVENT( EV_Hide, idEntity::Event_Hide ) + EVENT( EV_Show, idEntity::Event_Show ) + EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader ) + EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader ) + EVENT( EV_StartSound, idEntity::Event_StartSound ) + EVENT( EV_StopSound, idEntity::Event_StopSound ) + EVENT( EV_FadeSound, idEntity::Event_FadeSound ) + EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin ) + EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin ) + EVENT( EV_GetOrigin, idEntity::Event_GetOrigin ) + EVENT( EV_SetOrigin, idEntity::Event_SetOrigin ) + EVENT( EV_GetAngles, idEntity::Event_GetAngles ) + EVENT( EV_SetAngles, idEntity::Event_SetAngles ) + EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity ) + EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity ) + EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity ) + EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity ) + EVENT( EV_GetSize, idEntity::Event_GetSize ) + EVENT( EV_SetSize, idEntity::Event_SetSize ) + EVENT( EV_GetMins, idEntity::Event_GetMins) + EVENT( EV_GetMaxs, idEntity::Event_GetMaxs ) + EVENT( EV_Touches, idEntity::Event_Touches ) + EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm ) + EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat ) + EVENT( EV_GetNextKey, idEntity::Event_GetNextKey ) + EVENT( EV_SetKey, idEntity::Event_SetKey ) + EVENT( EV_GetKey, idEntity::Event_GetKey ) + EVENT( EV_GetIntKey, idEntity::Event_GetIntKey ) + EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey ) + EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey ) + EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey ) + EVENT( EV_RestorePosition, idEntity::Event_RestorePosition ) + EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget ) + EVENT( EV_DistanceTo, idEntity::Event_DistanceTo ) + EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint ) + EVENT( EV_StartFx, idEntity::Event_StartFx ) + EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame ) + EVENT( EV_Thread_Wait, idEntity::Event_Wait ) + EVENT( EV_HasFunction, idEntity::Event_HasFunction ) + EVENT( EV_CallFunction, idEntity::Event_CallFunction ) + EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) + +// RAVEN BEGIN +// bgeisler: go back to default skin + EVENT( EV_ClearSkin, idEntity::Event_ClearSkin ) +// kfuller: added events + EVENT( EV_SetContents, idEntity::Event_SetContents ) + EVENT( EV_GetLastBlocker, idEntity::Event_GetLastBlocker) +// bdube: effect events + EVENT( EV_PlayEffect, idEntity::Event_PlayEffect ) + EVENT( EV_StopEffect, idEntity::Event_StopEffect ) + EVENT( EV_StopAllEffects, idEntity::Event_StopAllEffects ) + EVENT( EV_GetHealth, idEntity::Event_GetHealth ) +// bdube: mesh events + EVENT( EV_HideSurface, idEntity::Event_HideSurface ) + EVENT( EV_ShowSurface, idEntity::Event_ShowSurface ) +// bdube: gui events + EVENT( EV_GuiEvent, idEntity::Event_GuiEvent ) +// jscott: playback callback + EVENT( EV_PlaybackCallback, idEntity::Event_PlaybackCallback ) +// nmckenzie: Check who we're bound to. + EVENT( EV_GetBindMaster, idEntity::Event_GetBindMaster ) + EVENT( EV_ApplyImpulse, idEntity::Event_ApplyImpulse ) +// abahr: so we can call this from script + EVENT( EV_RemoveNullTargets, idEntity::Event_RemoveNullTargets ) + EVENT( EV_IsA, idEntity::Event_IsA ) + EVENT( EV_IsSameTypeAs, idEntity::Event_IsSameTypeAs ) + EVENT( EV_MatchPrefix, idEntity::Event_MatchPrefix ) + EVENT( EV_ClearTargetList, idEntity::Event_ClearTargetList ) +// twhitaker: to dynamically add/remove targets in script + EVENT( EV_AppendTarget, idEntity::Event_AppendTarget ) + EVENT( EV_RemoveTarget, idEntity::Event_RemoveTarget ) +// mekberg: added + EVENT( EV_SetHealth, idEntity::Event_SetHealth ) +// RAVEN END +END_CLASS + +/* +================ +UpdateGuiParms +================ +*/ +void UpdateGuiParms( idUserInterface *gui, const idDict *args ) { + if ( gui == NULL || args == NULL ) { + return; + } + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + while( kv ) { + gui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = args->MatchPrefix( "gui_parm", kv ); + } + gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ; + gui->StateChanged( gameLocal.time ); +} + +/* +================ +AddRenderGui +================ +*/ +void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) { + + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + *gui = uiManager->FindGui( name, true, ( kv != NULL ) || args->GetBool( "gui_noninteractive" ) ); + UpdateGuiParms( *gui, args ); +} + +/* +================ +idGameEdit::ParseSpawnArgsToRenderEntity + +parse the static model parameters +this is the canonical renderEntity parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) { + int i; + const char *temp; + idVec3 color; + float angle; + const idDeclModelDef *modelDef; + + memset( renderEntity, 0, sizeof( *renderEntity ) ); + + temp = args->GetString( "model" ); + + modelDef = NULL; + if ( temp[0] != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + renderEntity->hModel = modelDef->ModelHandle(); + if ( renderEntity->hModel && !renderEntity->hModel->IsLoaded() ) { + renderEntity->hModel->LoadModel(); + } + } + } + + if ( !renderEntity->hModel ) { + renderEntity->hModel = renderModelManager->FindModel( temp ); + } + } + if ( renderEntity->hModel ) { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } else { + renderEntity->bounds.Zero(); + } + + temp = args->GetString( "skin" ); + if ( temp[0] != '\0' ) { + renderEntity->customSkin = declManager->FindSkin( temp ); + } else if ( modelDef ) { + renderEntity->customSkin = modelDef->GetDefaultSkin(); + } + + temp = args->GetString( "shader" ); + if ( temp[0] != '\0' ) { + renderEntity->customShader = declManager->FindMaterial( temp ); + } + + args->GetVector( "origin", "0 0 0", renderEntity->origin ); + + // get the rotation matrix in either full form, or single angle form + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) { + angle = args->GetFloat( "angle" ); +// RAVEN BEGIN +// abahr: allowing up and down buttons to affect orientation + if( angle == -1.0f ) { + renderEntity->axis = idAngles( -90.0f, 0.0f, 0.0f ).ToMat3(); + } else if( angle == -2.0f ) { + renderEntity->axis = idAngles( 90.0f, 0.0f, 0.0f ).ToMat3(); + } else +// RAVEN END + if ( angle != 0.0f ) { + renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + renderEntity->axis.Identity(); + } + } + +// RAVEN BEGIN + renderEntity->referenceSoundHandle = -1; +// RAVEN END + + // get shader parms + args->GetVector( "_color", "1 1 1", color ); + renderEntity->shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" ); + renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" ); + renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" ); + renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" ); + renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" ); + renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" ); + renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" ); + renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" ); + renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" ); + + // check noDynamicInteractions flag + renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" ); + + // check noshadows flag + renderEntity->noShadow = args->GetBool( "noshadows" ); + + // check noselfshadows flag + renderEntity->noSelfShadow = args->GetBool( "noselfshadows" ); + + // init any guis, including entity-specific states + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) ); + if ( temp[ 0 ] != '\0' ) { + AddRenderGui( temp, &renderEntity->gui[ i ], args ); + } + } +} + +/* +================ +idGameEdit::ParseSpawnArgsToRefSound + +parse the sound parameters +this is the canonical refSound parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) { + const char *temp; + + memset( refSound, 0, sizeof( *refSound ) ); + refSound->referenceSoundHandle = -1; + +// RAVEN BEGIN + refSound->parms.minDistance = args->GetFloat( "s_mindistance" ); + refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" ); + // WARNING: This overrides the volume; it does not modify it + if( args->GetFloat( "s_volume" ) != 0.0f ) { + refSound->parms.volume = idMath::dBToScale( args->GetFloat( "s_volume" ) ); + } + + if( refSound->parms.volume < 0.0f || refSound->parms.volume > 5.0f ) { + common->Warning( "Unreasonable volume (%g) on entity \'%s\'", refSound->parms.volume, args->GetString( "name" ) ); + refSound->parms.volume = 5.0f; + } +// RAVEN END + refSound->parms.shakes = args->GetFloat( "s_shakes" ); + + args->GetVector( "origin", "0 0 0", refSound->origin ); + + // if a diversity is not specified, every sound start will make + // a random one. Specifying diversity is usefull to make multiple + // lights all share the same buzz sound offset, for instance. + refSound->diversity = args->GetFloat( "s_diversity", "-1" ); + refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" ); + + if ( args->GetBool( "s_omni" ) ) { + refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + if ( args->GetBool( "s_looping" ) ) { + refSound->parms.soundShaderFlags |= SSF_LOOPING; + } + if ( args->GetBool( "s_occlusion" ) ) { + refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + if ( args->GetBool( "s_global" ) ) { + refSound->parms.soundShaderFlags |= SSF_GLOBAL; + } + if ( args->GetBool( "s_unclamped" ) ) { + refSound->parms.soundShaderFlags |= SSF_UNCLAMPED; + } + if ( args->GetBool( "s_center" ) ) { + refSound->parms.soundShaderFlags |= SSF_CENTER; + } + + refSound->parms.soundClass = args->GetInt( "s_soundClass" ); + + temp = args->GetString( "s_shader" ); + if ( temp[0] != '\0' ) { + refSound->shader = declManager->FindSound( temp ); + } + +// RAVEN BEGIN + if( refSound->parms.maxDistance < refSound->parms.minDistance ) { + common->Warning( "ParseSpawnArgsToRefSound: Max distance less than min distance for entity \'%s\'", args->GetString( "name", "*unknown*" ) ); + } +// RAVEN END +} + +/* +=============== +idEntity::UpdateChangeableSpawnArgs + +Any key val pair that might change during the course of the game ( via a gui or whatever ) +should be initialize here so a gui or other trigger can change something and have it updated +properly. An optional source may be provided if the values reside in an outside dictionary and +first need copied over to spawnArgs +=============== +*/ +void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) { + int i; + const char *target; + + if ( !source ) { + source = &spawnArgs; + } + cameraTarget = NULL; + target = source->GetString( "cameraTarget" ); + if ( target && target[0] ) { +// RAVEN BEGIN +// bdube: EV_UpdateCameraTarget pulls from spawnargs so we need to move the target over + spawnArgs.Set ( "cameraTarget", target ); +// RAVEN END + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], source ); + } +} + +/* +================ +idEntity::idEntity +================ +*/ +idEntity::idEntity() { + + entityNumber = ENTITYNUM_NONE; + entityDefNumber = -1; + + spawnNode.SetOwner( this ); + activeNode.SetOwner( this ); + + snapshotNode.SetOwner( this ); + snapshotSequence = -1; + snapshotBits = 0; + + thinkFlags = 0; + dormantStart = 0; + cinematic = false; + renderView = NULL; + cameraTarget = NULL; + health = 0; + + physics = NULL; + bindMaster = NULL; + bindJoint = INVALID_JOINT; + bindBody = -1; + teamMaster = NULL; + teamChain = NULL; + signals = NULL; + + memset( PVSAreas, 0, sizeof( PVSAreas ) ); + numPVSAreas = -1; + + memset( &fl, 0, sizeof( fl ) ); + fl.neverDormant = true; // most entities never go dormant + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + modelDefHandle = -1; + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + + mpGUIState = -1; + +// RAVEN BEGIN +// rjohnson: added this to persist long thinking entities + mLastLongThinkTime = 0; + mLastLongThinkColor.Zero(); +// ddynerman: instance, clipworld + SetInstance( 0 ); + SetClipWorld( 0 ); + fl.persistAcrossInstances = false; +// twhitaker + forwardDamageEnt = NULL; +// ddynerman: optional preprediction + predictTime = 0; +// RAVEN END +} + +/* +================ +idEntity::Spawn +================ +*/ +void idEntity::Spawn( void ) { + int i; + const char *temp; + idVec3 origin; + idMat3 axis; + const idKeyValue *networkSync; + const char *classname; + const char *scriptObjectName; + + gameLocal.RegisterEntity( this ); + +// bdube: make sure there is a classname before trying to use it + if ( spawnArgs.GetString( "classname", NULL, &classname ) ) { + const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false ); + if ( def ) { + entityDefNumber = def->Index(); + } + } + + // Persona is a set of keys that augment an entity giving it its own custom persona + const idDict* dict; + dict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_persona", "" ), false ); + if ( dict ) { + spawnArgs.Copy ( *dict ); + } +// RAVEN END + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + +// RAVEN BEGIN +// bdube: added hidesurface + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } +// RAVEN END + + renderEntity.entityNum = entityNumber; + +// RAVEN BEGIN +// ddynerman: LOD code + renderEntity.shadowLODDistance = spawnArgs.GetFloat( "shadow_lod_distance", "768.0" ); + renderEntity.shadowLODDistance *= renderEntity.shadowLODDistance; +// ddynerman: multiple clip worlds + int spawnInstance = spawnArgs.GetInt( "instance" ); + SetInstance( spawnInstance ); +// RAVEN END + + // go dormant within 5 frames so that when the map starts most monsters are dormant + dormantStart = gameLocal.time - DELAY_DORMANT_TIME + gameLocal.msec * 5; + + origin = renderEntity.origin; + axis = renderEntity.axis; + + // do the audio parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + // only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId + // don't spatialize sounds from the same entity + refSound.listenerId = entityNumber + 1; + + cameraTarget = NULL; + temp = spawnArgs.GetString( "cameraTarget" ); + if ( temp && temp[0] ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs ); + } + + fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" ); +// RAVEN BEGIN +// bdube: usable + fl.usable = spawnArgs.GetBool ( "usable", "0" ); +// RAVEN END + + fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" ); + fl.hidden = spawnArgs.GetBool( "hide", "0" ); + if ( fl.hidden ) { + // make sure we're hidden, since a spawn function might not set it up right + PostEventMS( &EV_Hide, 0 ); + } + cinematic = spawnArgs.GetBool( "cinematic", "0" ); + + networkSync = spawnArgs.FindKey( "networkSync" ); + if ( networkSync ) { + fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 ); + } + + // every object will have a unique name + temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) ); + SetName( temp ); + + // if we have targets, wait until all entities are spawned to get them + if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); + + temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + + if ( spawnArgs.GetString( "bind", "", &temp ) ) { + PostEventMS( &EV_SpawnBind, 0 ); + } + + // auto-start a sound on the entity + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + // setup script object + if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + +// RAVEN BEGIN + fl.persistAcrossInstances = false; +// bgeisler: added + fl.triggerAnim = spawnArgs.GetBool( "trigger_anim" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +// RAVEN END +} + +/* +================ +idEntity::~idEntity +================ +*/ +idEntity::~idEntity( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + if ( thinkFlags ) { + BecomeInactive( thinkFlags ); + } + activeNode.Remove(); + + Signal( SIG_REMOVED ); + + // we have to set back the default physics object before unbinding because the entity + // specific physics object might be an entity variable and as such could already be destroyed. + SetPhysics( NULL ); + + // remove any entities that are bound to me + RemoveBinds(); + + // unbind from master + Unbind(); + QuitTeam(); + + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + + delete renderView; + renderView = NULL; + + delete signals; + signals = NULL; + +// RAVEN BEGIN +// bdube: make sure all sounds and attached effects are stopped + StopSound( SCHANNEL_ANY, false ); + + RemoveClientEntities(); +// RAVEN END + + FreeModelDef(); + FreeSoundEmitter( false ); + + gameLocal.UnregisterEntity( this ); +} + +/* +================ +idEntity::Save +================ +*/ +void idEntity::Save( idSaveGame *savefile ) const { + int i, j; + rvClientEntity* cent; + + savefile->WriteInt( entityNumber ); + savefile->WriteInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->WriteInt( snapshotSequence ); + savefile->WriteInt( snapshotBits ); + + savefile->WriteString( name ); + savefile->WriteDict( &spawnArgs ); + scriptObject.Save( savefile ); + + savefile->WriteInt( thinkFlags ); + savefile->WriteInt( dormantStart ); + savefile->WriteBool( cinematic ); + + // renderView_t * renderView; + + savefile->WriteObject( cameraTarget ); + + savefile->WriteInt( targets.Num() ); + for( i = 0; i < targets.Num(); i++ ) { + targets[ i ].Save( savefile ); + } + + savefile->WriteInt( health ); + + savefile->WriteInt( clientEntities.Num() ); + for( cent = clientEntities.Next(); cent; cent = cent->bindNode.Next() ) { + savefile->WriteObject( cent ); + } + +// savefile->WriteInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->WriteVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( modelDefHandle ); + savefile->WriteRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper save + forwardDamageEnt.Save ( savefile ); +// RAVEN END + + savefile->WriteStaticObject( defaultPhysicsObj ); + + savefile->WriteObject( bindMaster.GetEntity() ); + savefile->WriteJoint( bindJoint ); + savefile->WriteInt( bindBody ); + savefile->WriteObject( teamMaster ); + savefile->WriteObject( teamChain ); + + savefile->WriteInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->WriteInt( PVSAreas[ i ] ); + } + + if ( !signals ) { + savefile->WriteBool( false ); + } else { + savefile->WriteBool( true ); + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->WriteInt( signals->signal[ i ].Num() ); + for( j = 0; j < signals->signal[ i ].Num(); j++ ) { + savefile->WriteInt( signals->signal[ i ][ j ].threadnum ); + savefile->WriteString( signals->signal[ i ][ j ].function->Name() ); + } + } + } + + savefile->WriteInt( mpGUIState ); + + savefile->WriteInt( instance ); + savefile->WriteInt( clipWorld ); +} + +/* +================ +idEntity::Restore +================ +*/ +void idEntity::Restore( idRestoreGame *savefile ) { + int i, j; + int num; + rvClientEntity *temp; + idStr funcname; + + savefile->ReadInt( entityNumber ); + savefile->ReadInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->ReadInt( snapshotSequence ); + savefile->ReadInt( snapshotBits ); + + savefile->ReadString( name ); + SetName( name ); + savefile->ReadDict( &spawnArgs ); + + scriptObject.Restore( savefile ); + + savefile->ReadInt( thinkFlags ); + savefile->ReadInt( dormantStart ); + savefile->ReadBool( cinematic ); + + // renderView_t * renderView; + + savefile->ReadObject( reinterpret_cast( cameraTarget ) ); + + targets.Clear(); + savefile->ReadInt( num ); + targets.SetNum( num ); + for( i = 0; i < num; i++ ) { + targets[ i ].Restore( savefile ); + } + + savefile->ReadInt( health ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( temp ) ); + if( temp ) { + temp->bindNode.AddToEnd( clientEntities ); + } + } + +// savefile->ReadInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->ReadVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Read( &fl, sizeof( fl ) ); + +// RAVEN BEGIN + savefile->ReadRenderEntity( renderEntity, &spawnArgs ); +// RAVEN END + savefile->ReadInt( modelDefHandle ); + savefile->ReadRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper restore + forwardDamageEnt.Restore ( savefile ); +// RAVEN END + + savefile->ReadStaticObject( defaultPhysicsObj ); + RestorePhysics( &defaultPhysicsObj ); + + idEntity *templol = 0; + savefile->ReadObject( reinterpret_cast( templol ) ); + bindMaster = templol; + + savefile->ReadJoint( bindJoint ); + savefile->ReadInt( bindBody ); + savefile->ReadObject( reinterpret_cast( teamMaster ) ); + savefile->ReadObject( reinterpret_cast( teamChain ) ); + + savefile->ReadInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->ReadInt( PVSAreas[ i ] ); + } + + bool readsignals; + savefile->ReadBool( readsignals ); + if ( readsignals ) { + signals = new signalList_t; + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->ReadInt( num ); + signals->signal[ i ].SetNum( num ); + for( j = 0; j < num; j++ ) { + savefile->ReadInt( signals->signal[ i ][ j ].threadnum ); + savefile->ReadString( funcname ); + signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname ); + if ( !signals->signal[ i ][ j ].function ) { + savefile->Error( "Function '%s' not found", funcname.c_str() ); + } + } + } + } + + savefile->ReadInt( mpGUIState ); + + // restore must retrieve modelDefHandle from the renderer + if ( modelDefHandle != -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + savefile->ReadInt( instance ); + savefile->ReadInt( clipWorld ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +} + +/* +================ +idEntity::GetEntityDefName +================ +*/ +const char * idEntity::GetEntityDefName( void ) const { + if ( entityDefNumber < 0 ) { + return "*unknown*"; + } + return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); +} + +/* +================ +idEntity::SetName +================ +*/ +void idEntity::SetName( const char *newname ) { + if ( name.Length() ) { + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, NULL ); + } + + name = newname; + if ( name.Length() ) { + if ( ( name == "NULL" ) || ( name == "null_entity" ) ) { + gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() ); + } + gameLocal.AddEntityToHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, this ); + } +} + +/* +================ +idEntity::GetName +================ +*/ +const char * idEntity::GetName( void ) const { + return name.c_str(); +} + + +/*********************************************************************** + + Thinking + +***********************************************************************/ + +/* +================ +idEntity::Think +================ +*/ +void idEntity::Think( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::DoDormantTests + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::DoDormantTests( void ) { + // Never go dormant? + if ( fl.neverDormant || (gameLocal.inCinematic && cinematic) ) { + return false; + } + + // if the monster area is not topologically connected to a player + if ( !gameLocal.InPlayerConnectedArea( this ) ) { + return true; + } else { + // the monster area is topologically connected to a player, but if + // the monster hasn't been woken up before, do the more precise PVS check + if ( !fl.hasAwakened ) { + if ( !gameLocal.InPlayerPVS( this ) ) { + return true; + } + } + } + + return false; +} + +/* +================ +idEntity::CheckDormant + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::CheckDormant( void ) { + bool dormant; + + dormant = DoDormantTests(); + if ( dormant ) { + if ( dormantStart == 0 ) { + dormantStart = gameLocal.time; + } + if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) { + dormant = false; + } + } else { + dormantStart = 0; + fl.hasAwakened = true; + } + + if ( dormant && !fl.isDormant ) { + fl.isDormant = true; + DormantBegin(); + } else if ( !dormant && fl.isDormant ) { + fl.isDormant = false; + DormantEnd(); + } + + return dormant; +} + +/* +================ +idEntity::DormantBegin + +called when entity becomes dormant +================ +*/ +void idEntity::DormantBegin( void ) { +} + +/* +================ +idEntity::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idEntity::DormantEnd( void ) { +} + +/* +================ +idEntity::IsActive +================ +*/ +bool idEntity::IsActive( void ) const { + return activeNode.InList(); +} + +/* +================ +idEntity::BecomeActive +================ +*/ +void idEntity::BecomeActive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // enable the team master if this entity is part of a physics team + if ( teamMaster && teamMaster != this ) { + teamMaster->BecomeActive( TH_PHYSICS ); + } else if ( !( thinkFlags & TH_PHYSICS ) ) { + // if this is a pusher +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Parametric::GetClassType() ) || physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + gameLocal.sortPushers = true; + } +// RAVEN BEGIN +// abahr: +// jnewquist: Use accessor for static class type + if( physics->IsType( rvPhysics_Spline::GetClassType() ) ) { + gameLocal.sortPushers = true; + } +// RAVEN END + } + } + + int oldFlags = thinkFlags; + thinkFlags |= flags; + if ( thinkFlags ) { + if ( !IsActive() ) { + activeNode.AddToEnd( gameLocal.activeEntities ); + } else if ( !oldFlags ) { + // we became inactive this frame, so we have to decrease the count of entities to deactivate + gameLocal.numEntitiesToDeactivate--; + } + } +} + +/* +================ +idEntity::BecomeInactive +================ +*/ +void idEntity::BecomeInactive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // may only disable physics on a team master if no team members are running physics or bound to a joints + if ( teamMaster == this ) { + for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) { + if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) { + flags &= ~TH_PHYSICS; + break; + } + } + } + } + + if ( thinkFlags ) { + thinkFlags &= ~flags; + if ( !thinkFlags && IsActive() ) { + gameLocal.numEntitiesToDeactivate++; + } + } + + if ( ( flags & TH_PHYSICS ) ) { + // if this entity has a team master + if ( teamMaster && teamMaster != this ) { + // if the team master is at rest + if ( teamMaster->IsAtRest() ) { + teamMaster->BecomeInactive( TH_PHYSICS ); + } + } + } +} + +/*********************************************************************** + + Visuals + +***********************************************************************/ + +/* +================ +idEntity::SetShaderParm +================ +*/ +void idEntity::SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderEntity.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( float red, float green, float blue ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = red; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = green; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec3 &color ) { + SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec3 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec4 &color ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec4 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idEntity::UpdateAnimationControllers +================ +*/ +bool idEntity::UpdateAnimationControllers( void ) { + // any ragdoll and IK animation controllers should be updated here + return false; +} + +/* +================ +idEntity::SetModel +================ +*/ +void idEntity::SetModel( const char *modelname ) { + assert( modelname ); + + FreeModelDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetSkin +================ +*/ +void idEntity::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} +// RAVEN BEGIN +// bgeisler: go back to default skin +/* +================ +idEntity::ClearSkin +================ +*/ +void idEntity::ClearSkin( void ) +{ + if ( GetAnimator() && GetAnimator()->ModelDef() ) { + renderEntity.customSkin = GetAnimator()->ModelDef()->GetDefaultSkin(); + } else { + renderEntity.customSkin = NULL; + } + + UpdateVisuals(); +} +// RAVEN END + +/* +================ +idEntity::GetSkin +================ +*/ +const idDeclSkin *idEntity::GetSkin( void ) const { + return renderEntity.customSkin; +} + +/* +================ +idEntity::FreeModelDef +================ +*/ +void idEntity::FreeModelDef( void ) { + if ( modelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandle ); + modelDefHandle = -1; + + rvClientEntity* cent; + + for( cent = clientEntities.Next(); cent != NULL; cent = cent->bindNode.Next() ) { + cent->FreeEntityDef(); + } + } +} + +/* +================ +idEntity::FreeLightDef +================ +*/ +void idEntity::FreeLightDef( void ) { +} + +/* +================ +idEntity::IsHidden +================ +*/ +bool idEntity::IsHidden( void ) const { + return fl.hidden; +} + +/* +================ +idEntity::Hide +================ +*/ +void idEntity::Hide( void ) { + if ( !IsHidden() ) { + fl.hidden = true; + FreeModelDef(); + UpdateVisuals(); + } +} + +/* +================ +idEntity::Show +================ +*/ +void idEntity::Show( void ) { + if ( IsHidden() ) { + fl.hidden = false; + UpdateVisuals(); + } +} + +/* +================ +idEntity::UpdateModelTransform +================ +*/ +void idEntity::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +idEntity::UpdateModel +================ +*/ +void idEntity::UpdateModel( void ) { + UpdateModelTransform(); + +// RAVEN BEGIN +// abahr: moved GetAnimator call because its invalid when called from a destructor + UpdateRenderEntityCallback(); +// RAVEN END + + // set to invalid number to force an update the next time the PVS areas are retrieved + ClearPVSAreas(); + + // ensure that we call Present this frame + BecomeActive( TH_UPDATEVISUALS ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEntity::UpdateRenderEntityCallback +================ +*/ +void idEntity::UpdateRenderEntityCallback() { +} +// RAVEN END + +/* +================ +idEntity::UpdateVisuals +================ +*/ +void idEntity::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( void ) { + int localNumPVSAreas, localPVSAreas[32]; + idBounds modelAbsBounds; + int i; + + modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis ); + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + + // FIXME: some particle systems may have huge bounds and end up in many PVS areas + // the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should + if ( localNumPVSAreas > MAX_PVS_AREAS ) { + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( renderEntity.origin ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + } + + for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) { + PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas]; + } + + for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) { + PVSAreas[ i ] = 0; + } +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( const idVec3 &pos ) { + int i; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS ); + i = numPVSAreas; + while ( i < MAX_PVS_AREAS ) { + PVSAreas[ i++ ] = 0; + } +} + +/* +================ +idEntity::GetNumPVSAreas +================ +*/ +int idEntity::GetNumPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return numPVSAreas; +} + +/* +================ +idEntity::GetPVSAreas +================ +*/ +const int *idEntity::GetPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return PVSAreas; +} + +/* +================ +idEntity::ClearPVSAreas +================ +*/ +void idEntity::ClearPVSAreas( void ) { + numPVSAreas = -1; +} + +/* +================ +idEntity::PhysicsTeamInPVS + + FIXME: for networking also return true if any of the entity shadows is in the PVS +================ +*/ +bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) { + idEntity *part; + + if ( teamMaster ) { + for ( part = teamMaster; part; part = part->teamChain ) { + if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) { + return true; + } + } + } else { + return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() ); + } + return false; +} + +/* +============== +idEntity::ProjectOverlay +============== +*/ +void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( modelDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +idEntity::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idEntity::Present( void ) { + + if ( !gameLocal.isNewFrame ) { + return; + } + + // if there is no handle yet, go ahead and add it, ignoring the last predict frame early out + // if not, that causes next render frame to have a bunch of spurious primitive draws ( r_showPrimitives ) + // ( we suspect this is because TH_UPDATEVISUALS doesn't get cleared? ) + if ( !gameLocal.isLastPredictFrame && modelDefHandle != -1 ) { + return; + } + +// RAVEN BEGIN +// ddynerman: don't render objects not in our instance (only on server) + if ( gameLocal.isServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != GetInstance() ) { + FreeModelDef(); + return; + } +// RAVEN END + + // don't render server demo stuff that's not in our instance + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ) { + if ( instance != 0 ) { + FreeModelDef(); + return; + } + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views +// RAVEN BEGIN +// rjohnson: removed PVS check for when func_static's are not starting in your PVS + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { +// RAVEN END + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idEntity::UpdateRenderEntity +================ +*/ +bool idEntity::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +idEntity::ModelCallback + + NOTE: may not change the game state whatsoever! +================ +*/ +bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + idEntity *ent; + + ent = gameLocal.entities[ renderEntity->entityNum ]; + if ( !ent ) { + gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity '%d'", renderEntity->entityNum ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idEntity::GetAnimator + +Subclasses will be responsible for allocating animator. +================ +*/ +idAnimator *idEntity::GetAnimator( void ) { + return NULL; +} + +/* +============= +idEntity::GetRenderView + +This is used by remote camera views to look from an entity +============= +*/ +renderView_t *idEntity::GetRenderView( void ) { + if ( !renderView ) { + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + renderView->vieworg = GetPhysics()->GetOrigin(); + renderView->fov_x = 120; + renderView->fov_y = 120; + renderView->viewaxis = GetPhysics()->GetAxis(); + + // copy global shader parms + for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time = gameLocal.time; + + return renderView; +} + +// RAVEN BEGIN +// bdube: added convienince functions for effects + +/*********************************************************************** + + effects + +***********************************************************************/ + +/* +================ +idEntity::PlayEffect +================ +*/ +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + if ( joint == INVALID_JOINT ) { + return NULL; + } + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteLong( joint ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT_JOINT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( originOffset ); + clientEffect->SetAxis( axisOffset ); + clientEffect->Bind( this, joint ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + idVec3 localOrigin; + idMat3 localAxis; + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( entityNumber == ENTITYNUM_WORLD ) { + return gameLocal.PlayEffect( effect, origin, axis, loop, endOrigin, broadcast, false, category, effectTint ); + } + + // Calculate the local origin and axis from the given globals + localOrigin = ( origin - renderEntity.origin ) * renderEntity.axis.Transpose(); + localAxis = axis * renderEntity.axis.Transpose(); + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idCQuat quat; + + quat = localAxis.ToCQuat(); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( localOrigin.x ); + msg.WriteFloat( localOrigin.y ); + msg.WriteFloat( localOrigin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return( NULL ); + } +// ddynerman: a listen server might get this far re: playing effects, don't actually play out of instance effects + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); +// RAVEN END + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( localOrigin ); + clientEffect->SetAxis( localAxis ); + clientEffect->Bind( this ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +/* +================ +idEntity::StopAllEffects +================ +*/ +void idEntity::StopAllEffects( bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + static_cast( cent )->Stop( destroyParticles ); + } + } +} + +/* +================ +idEntity::StopEffect +================ +*/ +void idEntity::StopEffect( const idDecl *effect, bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + if( !effect ) { + return; + } + + // Build a list of all the effects to stop + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + + // Is this client entity an effect? + if ( !cent->IsType( rvClientEffect::GetClassType() ) ) { + continue; + } + + // Now check to make sure its the specific effect we want to stop + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex() == effect->Index() ) { + clientEffect->Stop( destroyParticles ); + } + } +} + +void idEntity::StopEffect( const char* effectName, bool destroyParticles ) { + StopEffect( gameLocal.GetEffect( spawnArgs, effectName ), destroyParticles ); +} + +// RAVEN END + +/*********************************************************************** + + Sound + +***********************************************************************/ + +/* +================ +idEntity::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idEntity::CanPlayChatterSounds( void ) const { + return true; +} + +/* +================ +idEntity::StartSound +================ +*/ +bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); +} + +/* +================ +idEntity::StartSoundShader +================ +*/ +bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + float diversity; + int len; + + if ( length ) { + *length = 0; + } + + if ( !shader ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return true; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, shader ); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STARTSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return false; + } + + // rjohnson: don't play sounds on a dedicated server! + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return false; + } + + // set a random value for diversity unless one was parsed from the entity + if ( refSound.diversity < 0.0f ) { + diversity = gameLocal.random.RandomFloat(); + } else { + diversity = refSound.diversity; + } + +// RAVEN BEGIN + // if we don't have a soundEmitter allocated yet, get one now + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + len = emitter->StartSound( shader, channel, diversity, soundShaderFlags ); + if ( length ) { + *length = len; + } + } +// RAVEN END + + // set reference to the sound for shader synced effects + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + + return true; +} + +/* +================ +idEntity::StopSound +================ +*/ +void idEntity::StopSound( const s_channelType channel, bool broadcast ) { + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STOPSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return; + } + +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->StopSound( channel ); + } +// RAVEN END +} + +/* +================ +idEntity::SetSoundVolume + + Must be called before starting a new sound. +================ +*/ +void idEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + +/* +================ +idEntity::UpdateSound +================ +*/ +void idEntity::UpdateSound( void ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { +// RAVEN END + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + +// RAVEN BEGIN + refSound.velocity = GetPhysics()->GetLinearVelocity(); + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); +// RAVEN END + } +} + +/* +================ +idEntity::GetListenerId +================ +*/ +int idEntity::GetListenerId( void ) const { + return refSound.listenerId; +} + +/* +================ +idEntity::GetSoundEmitter +================ +*/ +// RAVEN BEGIN +int idEntity::GetSoundEmitter( void ) const { + return( refSound.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idEntity::FreeSoundEmitter +================ +*/ +void idEntity::FreeSoundEmitter( bool immediate ) { +// RAVEN BEGIN + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, immediate ); + refSound.referenceSoundHandle = -1; +// RAVEN END +} + +// RAVEN BEGIN +// bdube: client entities + +/*********************************************************************** + + client entities + +***********************************************************************/ + +/* +================ +idEntity::RemoveClientEntities +================ +*/ +void idEntity::RemoveClientEntities( void ) { + rvClientEntity* cent; + // Unbinding should remove the node from the list so keep using the head until + // there are no more entities + for( cent = clientEntities.Next(); cent != NULL; cent = clientEntities.Next() ) { + cent->Unbind( ); + delete cent; + } + clientEntities.Clear( ); +} +// RAVEN END + +/*********************************************************************** + + entity binding + +***********************************************************************/ + +/* +================ +idEntity::PreBind +================ +*/ +void idEntity::PreBind( void ) { +} + +/* +================ +idEntity::PostBind +================ +*/ +void idEntity::PostBind( void ) { +} + +/* +================ +idEntity::PreUnbind +================ +*/ +void idEntity::PreUnbind( void ) { +} + +/* +================ +idEntity::PostUnbind +================ +*/ +void idEntity::PostUnbind( void ) { +} + +/* +================ +idEntity::InitBind +================ +*/ +bool idEntity::InitBind( idEntity *master ) { + + if ( master == this ) { + gameLocal.Error( "Tried to bind an object to itself." ); + return false; + } + + if ( this == gameLocal.world ) { + gameLocal.Error( "Tried to bind world to another entity" ); + return false; + } + + // unbind myself from my master + Unbind(); + + // add any bind constraints to an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master && IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->AddBindConstraints(); + } + + if ( !master || master == gameLocal.world ) { + // this can happen in scripts, so safely exit out. + return false; + } + + return true; +} + +/* +================ +idEntity::FinishBind +================ +*/ +void idEntity::FinishBind( void ) { + + // set the master on the physics object + physics->SetMaster( bindMaster, fl.bindOrientated ); + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindMaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + JoinTeam( bindMaster ); + + // if our bindMaster is enabled during a cinematic, we must be, too +// RAVEN BEGIN +// rjohnson: players should always have cinematic turned on, no matter what + if ( !IsType ( idPlayer::GetClassType() ) ) { + cinematic = bindMaster->cinematic; + } +// RAVEN END + + // make sure the team master is active so that physics get run + teamMaster->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idEntity::Bind + + bind relative to the visual position of the master +================ +*/ +void idEntity::Bind( idEntity *master, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind( ); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) { + jointHandle_t jointnum; + idAnimator *masterAnimator; + + if ( !InitBind( master ) ) { + return; + } + + masterAnimator = master->GetAnimator(); + if ( !masterAnimator ) { + gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() ); + return; + } + + jointnum = masterAnimator->GetJointHandle( jointname ); + if ( jointnum == INVALID_JOINT ) { + gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() ); + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToBody + + bind relative to a collision model used by the physics of the master +================ +*/ +void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + if ( bodyId < 0 ) { + gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId ); + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = bodyId; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::Unbind +================ +*/ +void idEntity::Unbind( void ) { + idEntity * prev; + idEntity * next; + idEntity * last; + idEntity * ent; + + // remove any bind constraints from an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->RemoveBindConstraints(); + } + + if ( !bindMaster ) { + return; + } + + if ( !teamMaster ) { + // Teammaster already has been freed + bindMaster = NULL; + return; + } + + PreUnbind(); + + if ( physics ) { + physics->SetMaster( NULL, fl.bindOrientated ); + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = teamMaster; + for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) { + prev = ent; + } + + assert( ent == this ); // If ent is not pointing to this, then something is very wrong. + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = teamChain; next != NULL; next = next->teamChain ) { + if ( !next->IsBoundTo( this ) ) { + break; + } + + // Tell them I'm now the teamMaster + next->teamMaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->teamChain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( teamMaster != this ) { + prev->teamChain = next; + if ( !next && ( teamMaster == prev ) ) { + prev->teamMaster = NULL; + } + } else if ( next ) { + // If we were the teamMaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamMaster = next; + } + next->teamMaster = next; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( teamChain ) { + // make myself my own team + teamMaster = this; + } else { + // no longer a team + teamMaster = NULL; + } + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = NULL; + + PostUnbind(); +} + +/* +================ +idEntity::RemoveBinds +================ +*/ +void idEntity::RemoveBinds( void ) { + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->teamChain; + if ( ent->bindMaster == this ) { + ent->Unbind(); + ent->PostEventMS( &EV_Remove, 0 ); + next = teamChain; + } + } +} + +/* +================ +idEntity::IsBound +================ +*/ +bool idEntity::IsBound( void ) const { + if ( bindMaster ) { + return true; + } + return false; +} + +/* +================ +idEntity::IsBoundTo +================ +*/ +// RAVEN BEGIN +// abahr: added const so it can be called from const functions +bool idEntity::IsBoundTo( const idEntity *master ) const { +// RAVEN END + idEntity *ent; + + if ( !bindMaster ) { + return false; + } + + for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) { + if ( ent == master ) { + return true; + } + } + + return false; +} + +/* +================ +idEntity::GetBindMaster +================ +*/ +idEntity *idEntity::GetBindMaster( void ) const { + return bindMaster; +} + +/* +================ +idEntity::GetBindJoint +================ +*/ +jointHandle_t idEntity::GetBindJoint( void ) const { + return bindJoint; +} + +/* +================ +idEntity::GetBindBody +================ +*/ +int idEntity::GetBindBody( void ) const { + return bindBody; +} + +/* +================ +idEntity::GetTeamMaster +================ +*/ +idEntity *idEntity::GetTeamMaster( void ) const { + return teamMaster; +} + +/* +================ +idEntity::GetNextTeamEntity +================ +*/ +idEntity *idEntity::GetNextTeamEntity( void ) const { + return teamChain; +} + +/* +===================== +idEntity::ConvertLocalToWorldTransform +===================== +*/ +void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + UpdateModelTransform(); + + offset = renderEntity.origin + offset * renderEntity.axis; + axis *= renderEntity.axis; +} + +/* +================ +idEntity::GetLocalVector + +Takes a vector in worldspace and transforms it into the parent +object's localspace. + +Note: Does not take origin into acount. Use getLocalCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetLocalCoordinates + +Takes a vector in world coordinates and transforms it into the parent +object's local coordinates. +================ +*/ +idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec - masterOrigin, pos ); + + return pos; +} + +// RAVEN BEGIN +// kfuller: added method + +/* +================ +idEntity::DistanceTo2d +================ +*/ +float idEntity::DistanceTo2d ( const idVec3& pos ) const { + idVec3 pos1; + idVec3 pos2; + pos1 = pos - (pos * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + pos2 = GetPhysics()->GetOrigin ( ); + pos2 = pos2 - (pos2 * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + return (pos2 - pos1).LengthFast ( ); +} + +/* +================ +idEntity::GetLocalAngles +================ +*/ +void idEntity::GetLocalAngles(idAngles &localAng) +{ + idVec3 localVec = GetPhysics()->GetAxis()[0]; + + GetLocalVector(localVec); + localAng = localVec.ToAngles(); +} +// RAVEN END + +/* +================ +idEntity::GetWorldVector + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. + +Note: Does not take origin into acount. Use getWorldCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldCoordinates + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. +================ +*/ +idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + pos += masterOrigin; + + return pos; +} + +/* +================ +idEntity::GetMasterPosition +================ +*/ +bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idVec3 localOrigin; + idMat3 localAxis; + idAnimator *masterAnimator; + + if ( bindMaster ) { + // if bound to a joint of an animated model + if ( bindJoint != INVALID_JOINT ) { + masterAnimator = bindMaster->GetAnimator(); + if ( !masterAnimator ) { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } else { + masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis ); + masterAxis *= bindMaster->renderEntity.axis; + masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis; + } + } else if ( bindBody >= 0 && bindMaster->GetPhysics() ) { + masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody ); + masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody ); + } else { + masterOrigin = bindMaster->renderEntity.origin; + masterAxis = bindMaster->renderEntity.axis; + } + return true; + } else { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } +} + +// RAVEN BEGIN +// abahr: needed so client get the correct position +/* +================ +idEntity::GetPosition +================ +*/ +void idEntity::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = renderEntity.origin; + axis = renderEntity.axis; +} +// RAVEN END + +/* +================ +idEntity::GetWorldVelocities +================ +*/ +void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const { + + linearVelocity = physics->GetLinearVelocity(); + angularVelocity = physics->GetAngularVelocity(); + + if ( bindMaster ) { + idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity; + idMat3 masterAxis; + + // get position of master + GetMasterPosition( masterOrigin, masterAxis ); + + // get master velocities + bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity ); + + // linear velocity relative to master plus master linear and angular velocity + linearVelocity = linearVelocity * masterAxis + masterLinearVelocity + + masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin ); + } +} + +/* +================ +idEntity::JoinTeam +================ +*/ +void idEntity::JoinTeam( idEntity *teammember ) { + idEntity *ent; + idEntity *master; + idEntity *prev; + idEntity *next; + + // if we're already on a team, quit it so we can join this one + if ( teamMaster && ( teamMaster != this ) ) { + QuitTeam(); + } + + assert( teammember ); + + if ( teammember == this ) { + teamMaster = this; + return; + } + + // check if our new team mate is already on a team + master = teammember->teamMaster; + if ( !master ) { + // he's not on a team, so he's the new teamMaster + master = teammember; + teammember->teamMaster = teammember; + teammember->teamChain = this; + + // make anyone who's bound to me part of the new team + for( ent = teamChain; ent != NULL; ent = ent->teamChain ) { + ent->teamMaster = master; + } + } else { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->teamChain; + if ( bindMaster ) { + // if we have a bindMaster, join after any entities bound to the entity + // we're joining + while( next && next->IsBoundTo( teammember ) ) { + prev = next; + next = next->teamChain; + } + } else { + // if we're not bound to someone, then put us at the end of the team + while( next ) { + prev = next; + next = next->teamChain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamChain->teamMaster = master; + } + + prev->teamChain = this; + ent->teamChain = next; + } + + teamMaster = master; + + // reorder the active entity list + gameLocal.sortTeamMasters = true; +} + +/* +================ +idEntity::QuitTeam +================ +*/ +void idEntity::QuitTeam( void ) { + idEntity *ent; + + if ( !teamMaster ) { + return; + } + + // check if I'm the teamMaster + if ( teamMaster == this ) { + // do we have more than one teammate? + if ( !teamChain->teamChain ) { + // no, break up the team + teamChain->teamMaster = NULL; + } else { + // yes, so make the first teammate the teamMaster + for( ent = teamChain; ent; ent = ent->teamChain ) { + ent->teamMaster = teamChain; + } + } + } else { + assert( teamMaster ); + assert( teamMaster->teamChain ); + + // find the previous member of the teamChain + ent = teamMaster; + while( ent->teamChain != this ) { + assert( ent->teamChain ); // this should never happen + ent = ent->teamChain; + } + + // remove this from the teamChain + ent->teamChain = teamChain; + + // if no one is left on the team, break it up + if ( !teamMaster->teamChain ) { + teamMaster->teamMaster = NULL; + } + } + + teamMaster = NULL; + teamChain = NULL; +} + +/*********************************************************************** + + Physics. + +***********************************************************************/ + +/* +================ +idEntity::InitDefaultPhysics +================ +*/ +void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( temp ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); +// RAVEN BEGIN +// bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); +// RAVEN END + } else { + trm.SetupBox( bounds ); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { +// RAVEN BEGIN +// jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( this ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; +} + +/* +================ +idEntity::SetPhysics +================ +*/ +void idEntity::SetPhysics( idPhysics *phys ) { + // clear any contacts the current physics object has + if ( physics ) { + physics->ClearContacts(); + } + // set new physics object or set the default physics if NULL + if ( phys != NULL ) { + defaultPhysicsObj.SetClipModel( NULL, 1.0f ); + physics = phys; + physics->Activate(); + } else { + physics = &defaultPhysicsObj; + } + physics->UpdateTime( gameLocal.time ); + physics->SetMaster( bindMaster, fl.bindOrientated ); +} + +/* +================ +idEntity::RestorePhysics +================ +*/ +void idEntity::RestorePhysics( idPhysics *phys ) { + assert( phys != NULL ); + // restore physics pointer + physics = phys; +} + +/* +================ +idEntity::RunPhysics +================ +*/ +bool idEntity::RunPhysics( void ) { + int i, reachedTime, startTime, endTime; + idEntity * part, *blockedPart, *blockingEntity = NULL; + trace_t results; + bool moved; + + moved = false; + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PHYSICS); +// RAVEN END + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } +// RAVEN BEGIN +// kfuller: we want to be able to debug draw the bbox regardless + physics->DebugDraw(); +// RAVEN END + return false; + } + + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics +// RAVEN BEGIN +// ddynerman: optional pre-prediction + moved = part->physics->Evaluate( endTime - startTime + part->predictTime, endTime ); + part->predictTime = 0; +// RAVEN END + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // cdr: Obstacle Avoidance + if (ai_useRVMasterMove.GetBool() && moved && fl.isAIObstacle) { + AI_EntityMoved(this); + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + + if ( gameLocal.isClient ) { + return false; + } + + // if the master pusher has a "blocked" function, call it + Signal( SIG_BLOCKED ); + ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity ); + // call the blocked function on the blocked part + blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity ); + return false; + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->physics->SetPushed( endTime - startTime ); + } + + if ( gameLocal.isClient ) { + return true; + } + + // post reached event if the current time is at or past the end point of the motion + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + reachedTime = part->physics->GetLinearEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedPos ); + } + reachedTime = part->physics->GetAngularEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedAng ); + } + } + } + + return true; +} + +/* +================ +idEntity::UpdateFromPhysics +================ +*/ +void idEntity::UpdateFromPhysics( bool moveBack ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idActor::GetClassType() ) ) { +// RAVEN END + idActor *actor = static_cast( this ); + + // set master delta angles for actors + if ( GetBindMaster() ) { + idAngles delta = actor->GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= static_cast(physics)->GetMasterDeltaYaw(); + } else { + delta.yaw += static_cast(physics)->GetMasterDeltaYaw(); + } + actor->SetDeltaViewAngles( delta ); + } + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetOrigin +================ +*/ +void idEntity::SetOrigin( const idVec3 &org ) { + + GetPhysics()->SetOrigin( org ); + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAxis +================ +*/ +void idEntity::SetAxis( const idMat3 &axis ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + static_cast(this)->viewAxis = axis; + } else { + GetPhysics()->SetAxis( axis ); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAngles +================ +*/ +void idEntity::SetAngles( const idAngles &ang ) { + SetAxis( ang.ToMat3() ); +} + +/* +================ +idEntity::GetFloorPos +================ +*/ +bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const { + trace_t result; + + if ( !GetPhysics()->HasGroundContacts() ) { + GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL ); + if ( result.fraction < 1.0f ) { + floorpos = result.endpos; + return true; + } else { + floorpos = GetPhysics()->GetOrigin(); + return false; + } + } else { + floorpos = GetPhysics()->GetOrigin(); + return true; + } +} + +/* +================ +idEntity::GetPhysicsToVisualTransform +================ +*/ +bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +idEntity::GetPhysicsToSoundTransform +================ +*/ +bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + // by default play the sound at the center of the bounding box of the first clip model + if ( GetPhysics()->GetNumClipModels() > 0 ) { + origin = GetPhysics()->GetBounds().GetCenter(); + axis.Identity(); + return true; + } + return false; +} + +/* +================ +idEntity::Collide +================ +*/ +bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + // this entity collides with collision.c.entityNum + return false; +} + +/* +================ +idEntity::GetImpactInfo +================ +*/ +void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +idEntity::ApplyImpulse +================ +*/ +void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( SkipImpulse(ent, id) ) { + return; + } + + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +idEntity::AddForce +================ +*/ +void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +idEntity::ActivatePhysics +================ +*/ +void idEntity::ActivatePhysics( idEntity *ent ) { + GetPhysics()->Activate(); +} + +/* +================ +idEntity::IsAtRest +================ +*/ +bool idEntity::IsAtRest( void ) const { + return GetPhysics()->IsAtRest(); +} + +/* +================ +idEntity::GetRestStartTime +================ +*/ +int idEntity::GetRestStartTime( void ) const { + return GetPhysics()->GetRestStartTime(); +} + +/* +================ +idEntity::AddContactEntity +================ +*/ +void idEntity::AddContactEntity( idEntity *ent ) { + GetPhysics()->AddContactEntity( ent ); +} + +/* +================ +idEntity::RemoveContactEntity +================ +*/ +void idEntity::RemoveContactEntity( idEntity *ent ) { +// RAVEN BEGIN + if( GetPhysics() ) { + + GetPhysics()->RemoveContactEntity( ent ); + } +// RAVEN END +} + + + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idEntity::CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +// RAVEN BEGIN +// bdube: added ignore entity +bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt ) const { +// RAVEN END + idVec3 dest; + trace_t tr; + idVec3 midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5; + + dest = midpoint; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + // this should probably check in the plane of projection, rather than in world coordinate + dest = midpoint; + dest[0] += 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN ENE + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] += 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN EN + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] += 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] -= 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + return false; +} + +/* +================ +idEntity::DamageFeedback + +callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // implemented in subclasses +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +============ +*/ +void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + return; + } + + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + //jshepard: this is kinda important, no? + health -= damage; + + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + +/* +============ +idEntity::SkipImpulse +============ +*/ +// RAVEN BEGIN +// abahr: push stuff +bool idEntity::SkipImpulse( idEntity *ent, int id ) { + return false;//ent == this; +} + +/* +============ +idEntity::ApplyImpulse +============ +*/ +void idEntity::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ) { + ApplyImpulse( ent, id, point, dir * damageDef->GetFloat("push", "5000") ); +} +// RAVEN END + +/* +================ +idEntity::AddDamageEffect +================ +*/ +void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + const char *sound, *decal, *key; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); +// RAVEN BEGIN +// bdube: impact_blood is now in the damage def + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { +// RAVEN END + return; + } + + const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + if ( g_decals.GetBool() ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + } + } +} + +/* +================ +idEntity::CanPlayImpactEffect +================ +*/ +bool idEntity::CanPlayImpactEffect ( idEntity* owner, idEntity* ent ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsTeamGame() && !cvarSystem->GetCVarBool("si_teamDamage") && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && ((idPlayer*)owner)->team == ((idPlayer*)ent)->team ) { + return false; + } + + // default to blood + return true; + } else { + idActor* actorOwner; + idAI* aiEnt; + actorOwner = dynamic_cast( owner ); + + if ( ent->IsType ( idAFAttachment::GetClassType ( ) ) ) { + aiEnt = dynamic_cast( static_cast( ent )->GetBody ( ) ); + } else { + aiEnt = dynamic_cast( ent ); + } + + if ( !actorOwner || !aiEnt ) { + return true; + } + + return (actorOwner->team != aiEnt->team); + } +} + +/* +============ +idEntity::Pain + +Called whenever an entity recieves damage. Returns whether the entity responds to the pain. +This is a virtual function that subclasses are expected to implement. +============ +*/ +bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return false; +} + +/* +============ +idEntity::Killed + +Called whenever an entity's health is reduced to 0 or less. +This is a virtual function that subclasses are expected to implement. +============ +*/ +void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +} + +/*********************************************************************** + + Script functions + +***********************************************************************/ + +/* +================ +idEntity::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idEntity::ShouldConstructScriptObjectAtSpawn( void ) const { + return true; +} + +/* +================ +idEntity::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idEntity::ConstructScriptObject( void ) { + idThread *thread; + const function_t *constructor; + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, constructor, true ); + thread->DelayedStart( 0 ); + } else { + thread = NULL; + } + + // clear out the object's memory + scriptObject.ClearObject(); + + return thread; +} + +/* +================ +idEntity::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idEntity::DeconstructScriptObject( void ) { + idThread *thread; + const function_t *destructor; + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and be destroyed +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_TEMPORARY); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, destructor, true ); + thread->Execute(); + delete thread; + } +} + +/* +================ +idEntity::HasSignal +================ +*/ +bool idEntity::HasSignal( signalNum_t signalnum ) const { + if ( !signals ) { + return false; + } + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + return ( signals->signal[ signalnum ].Num() > 0 ); +} + +/* +================ +idEntity::SetSignal +================ +*/ +void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) { + int i; + int num; + signal_t sig; + int threadnum; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + signals = new signalList_t; + } + + assert( thread ); + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ][ i ].function = function; + return; + } + } + + if ( num >= MAX_SIGNAL_THREADS ) { + thread->Error( "Exceeded maximum number of signals per object" ); + } + + sig.threadnum = threadnum; + sig.function = function; + signals->signal[ signalnum ].Append( sig ); +} + +/* +================ +idEntity::ClearSignal +================ +*/ +void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) { + assert( thread ); + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + signals->signal[ signalnum ].Clear(); +} + +/* +================ +idEntity::ClearSignalThread +================ +*/ +void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) { + int i; + int num; + int threadnum; + + assert( thread ); + + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ].RemoveIndex( i ); + return; + } + } +} + +/* +================ +idEntity::Signal +================ +*/ +void idEntity::Signal( signalNum_t signalnum ) { + int i; + int num; + signal_t sigs[ MAX_SIGNAL_THREADS ]; + idThread *thread; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + return; + } + + // we copy the signal list since each thread has the potential + // to end any of the threads in the list. By copying the list + // we don't have to worry about the list changing as we're + // processing it. + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + sigs[ i ] = signals->signal[ signalnum ][ i ]; + } + + // clear out the signal list so that we don't get into an infinite loop + signals->signal[ signalnum ].Clear(); + + for( i = 0; i < num; i++ ) { + thread = idThread::GetThread( sigs[ i ].threadnum ); + if ( thread ) { + thread->CallFunction( this, sigs[ i ].function, true ); + thread->Execute(); + } + } +} + +/* +================ +idEntity::SignalEvent +================ +*/ +void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) { + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + Signal( signalnum ); +} + +/*********************************************************************** + + Guis. + +***********************************************************************/ + + +/* +================ +idEntity::TriggerGuis +================ +*/ +void idEntity::TriggerGuis( void ) { + int i; + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->Trigger( gameLocal.time ); + } + } +} + +/* +================ +idEntity::HandleGuiCommands +================ +*/ +bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) { + idEntity *targetEnt; + bool ret = false; + if ( entityGui && cmds && *cmds ) { + idLexer src; + idToken token, token2, token3, token4; + src.LoadMemory( cmds, strlen( cmds ), "guiCommands" ); + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + return ret; + } + + if ( token == ";" ) { + continue; + } + + if ( token.Icmp( "activate" ) == 0 ) { + bool targets = true; + if ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + } else { + targets = false; + } + } + + if ( targets ) { + entityGui->ActivateTargets( this ); + } else { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + } + } + + entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f; + continue; + } + + + if ( token.Icmp( "runScript" ) == 0 ) { + if ( src.ReadToken( &token2 ) ) { + while( src.CheckTokenString( "::" ) ) { + idToken token3; + if ( !src.ReadToken( &token3 ) ) { + gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() ); + } + token2 += "::" + token3; + } + } +// RAVEN BEGIN +// abahr: allow parms to be passed in +// For some reason the semi colon is used as a delimeter so we need the above code + rvScriptFuncUtility utility; + if( utility.Init(token2) > SFU_ERROR ) { + utility.InsertEntity( entityGui, 0 ); + utility.CallFunc( &entityGui->spawnArgs ); + } +// RAVEN END + continue; + } + + if ( token.Icmp("play") == 0 ) { + if ( src.ReadToken( &token2 ) ) { + const idSoundShader *shader = declManager->FindSound(token2); + entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + continue; + } + + if ( token.Icmp( "setkeyval" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->spawnArgs.Set( token3, token4 ); + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + } + } + continue; + } + + if ( token.Icmp( "setshaderparm" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) { + entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) ); + entityGui->UpdateVisuals(); + } + continue; + } + + if ( token.Icmp("close") == 0 ) { + ret = true; + continue; + } + + // handy for debugging GUI stuff + if ( !token.Icmp( "print" ) ) { + idStr msg; + while ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + break; + } + msg += token2.c_str(); + } + common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() ); + continue; + } + + // if we get to this point we don't know how to handle it + src.UnreadToken(&token); + if ( !HandleSingleGuiCommand( entityGui, &src ) ) { + // not handled there see if entity or any of its targets can handle it + // this will only work for one target atm + if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) { + continue; + } + + int c = entityGui->targets.Num(); + int i; + for ( i = 0; i < c; i++) { + targetEnt = entityGui->targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) { + break; + } + } + + if ( i == c ) { + // not handled + common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() ); + src.ReadToken( &token ); + } + } + + } + } + return ret; +} + +/* +================ +idEntity::HandleSingleGuiCommand +================ +*/ +bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + return false; +} + +/*********************************************************************** + + Targets + +***********************************************************************/ + +/* +=============== +idEntity::FindTargets + +We have to wait until all entities are spawned +Used to build lists of targets after the entity is spawned. Since not all entities +have been spawned when the entity is created at map load time, we have to wait +=============== +*/ +void idEntity::FindTargets( void ) { + int i; + + // targets can be a list of multiple names + gameLocal.GetTargets( spawnArgs, targets, "target" ); + + // ensure that we don't target ourselves since that could cause an infinite loop when activating entities + for( i = 0; i < targets.Num(); i++ ) { + if ( targets[ i ].GetEntity() == this ) { + gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() ); + } + } +} + +/* +================ +idEntity::RemoveNullTargets +================ +*/ +void idEntity::RemoveNullTargets( void ) { + int i; + + for( i = targets.Num() - 1; i >= 0; i-- ) { + if ( !targets[ i ].GetEntity() ) { + targets.RemoveIndex( i ); + } + } +} + +/* +============================== +idEntity::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idEntity::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->renderEntity.gui[ j ] ) { + ent->renderEntity.gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +// RAVEN BEGIN +// twhitaker: added (meant to be used from script) +/* +================ +idEntity::AppendTarget +================ +*/ +int idEntity::AppendTarget( idEntity *appendMe ) { + + int index = -1; + // silently fail if they pass in null + if ( appendMe ) { + index = targets.Append( appendMe ); + RemoveNullTargets(); + } + return index; +} +/* +================ +idEntity::RemoveTarget +================ +*/ +void idEntity::RemoveTarget( idEntity *removeMe ) { + + targets.Remove( removeMe ); + RemoveNullTargets(); +} +/* +================ +idEntity::RemoveTargets +================ +*/ +void idEntity::RemoveTargets( bool destroyContents ) { + if( destroyContents ) { + targets.RemoveContents( true ); + } else { + targets.Clear(); + } +} + +// jshepard: added +/* +================ +idEntity::UnbindTargets +================ +*/ +void idEntity::UnbindTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + ent->Unbind(); + + } +} + +// bdube: added +/* +================ +idEntity::Event_SetContents +================ +*/ +void idEntity::Event_SetContents( int contents ) { + GetPhysics()->SetContents( contents ); +} + +/* +================ +idEntity::Event_GetLastBlocker +================ +*/ +void idEntity::Event_GetLastBlocker(idThread *thread) { + int whichEntNum = GetLastBlocker(); + + if (whichEntNum < 0 || whichEntNum == ENTITYNUM_WORLD) { + thread->ReturnEntity(this); + return; + } + thread->ReturnEntity(gameLocal.entities[whichEntNum]); +} + +/* +================ +idEntity::ShowSurface +================ +*/ +void idEntity::ShowSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask &= (~renderEntity.hModel->GetSurfaceMask ( surface )); +} + +/* +================ +idEntity::Event_ShowSurface +================ +*/ +void idEntity::Event_ShowSurface ( const char* surface ) { + ShowSurface ( surface ); +} + +/* +================ +idEntity::HideSurface +================ +*/ +void idEntity::HideSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask |= renderEntity.hModel->GetSurfaceMask ( surface ) ; +} + +/* +================ +idEntity::Event_HideSurface +================ +*/ +void idEntity::Event_HideSurface ( const char* surface ) { + HideSurface ( surface ); +} + +/* +================ +idEntity::Event_GuiEvent +================ +*/ +void idEntity::Event_GuiEvent ( const char* eventName ) { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( eventName ); + } +} + +/* +================ +idEntity::Event_clearSkin +================ +*/ +void idEntity::Event_ClearSkin( void ) { + ClearSkin(); +} + +/* +================ +idEntity::Event_StopAllEffects +================ +*/ +void idEntity::Event_StopAllEffects ( void ) { + StopAllEffects ( ); +} + +/* +================ +idEntity::Event_GetHealth +================ +*/ +void idEntity::Event_GetHealth ( void ) { + idThread::ReturnFloat( health ); +} + +// jscott: +/* +================ +idEntity::Event_PlaybackCallback +================ +*/ +void idEntity::Event_PlaybackCallback ( int type, int changed, int impulse ) { + common->Printf( "Playback callback type %d - %d/%d\n", type, changed, impulse ); +} + +// nmckenzie: Check who we're bound to. +/* +================ +idEntity::Event_GetBindMaster +================ +*/ + +void idEntity::Event_GetBindMaster ( void ) { + idThread::ReturnEntity( GetBindMaster() ); +} + +/* +================ +idEntity::Event_ApplyImpulse +================ +*/ + +void idEntity::Event_ApplyImpulse( idEntity *source, const idVec3 &point, const idVec3 &impulse ){ + ApplyImpulse( source, 0, point, impulse ); +} + +/* +================ +idEntity::Event_PlayEffect +================ +*/ +void idEntity::Event_PlayEffect( const char *effectName, const char* jointName, bool loop ) { + jointHandle_t joint; + joint = GetAnimator ( ) ? GetAnimator()->GetJointHandle ( jointName ) : INVALID_JOINT; + if ( joint != INVALID_JOINT ) { + PlayEffect ( effectName, joint, loop ); + } else { + PlayEffect ( effectName, renderEntity.origin, renderEntity.axis, loop ); + } +} + +/* +================ +idEntity::Event_StopEffect +================ +*/ +void idEntity::Event_StopEffect( const char *effectName ) { + StopEffect ( effectName ); +} + +// END RAVEN + +/*********************************************************************** + + Misc. + +***********************************************************************/ + +/* +================ +idEntity::Teleport +================ +*/ +void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( angles.ToMat3() ); + + UpdateVisuals(); +} + +/* +============ +idEntity::TouchTriggers + + Activate all trigger entities touched at the current position. + + Optionally only activate triggers of ownerType +============ +*/ +bool idEntity::TouchTriggers( const idTypeInfo* ownerType ) const { + int i, numClipModels, numEntities; + idClipModel * cm; + idClipModel * clipModels[ MAX_GENTITIES ]; + idEntity * ent; + trace_t trace; + + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = GetPhysics()->GetOrigin(); + trace.endAxis = GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES ); +// RAVEN END + numEntities = 0; + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + // don't touch it if we're the owner + if ( cm->GetOwner() == this ) { + continue; + } + + ent = cm->GetEntity(); + + if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } + + if( ownerType && !ent->IsType( *ownerType ) ) { + continue; + } + +// RAVEN BEGIN +// abahr: needed so tram car can has collision model and touch triggers + bool useSimpleClip = spawnArgs.GetBool("useSimpleTriggerClip"); + if ( !useSimpleClip && !GetPhysics()->ClipContents( cm ) ) { +// RAVEN END + continue; + } + + numEntities++; + + trace.c.contents = cm->GetContents(); + trace.c.entityNum = cm->GetEntity()->entityNumber; + trace.c.id = cm->GetId(); + + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, this, &trace ); + + if ( !gameLocal.entities[ entityNumber ] ) { + gameLocal.Printf( "entity was removed while touching triggers\n" ); + return true; + } + } + + return ( numEntities != 0 ); +} + +/* +================ +idEntity::GetSpline +================ +*/ +idCurve_Spline *idEntity::GetSpline( void ) const { + int i, numPoints, t; + const idKeyValue *kv; + idLexer lex; + idVec3 v; + idCurve_Spline *spline; + const char *curveTag = "curve_"; + + kv = spawnArgs.MatchPrefix( curveTag ); + if ( !kv ) { + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) ); + if ( str.Icmp( "CatmullRomSpline" ) == 0 ) { + spline = new idCurve_CatmullRomSpline(); + } else if ( str.Icmp( "nubs" ) == 0 ) { + spline = new idCurve_NonUniformBSpline(); + } else if ( str.Icmp( "nurbs" ) == 0 ) { + spline = new idCurve_NURBS(); + } else { + spline = new idCurve_BSpline(); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + spline->SetBoundaryType( idCurve_Spline::BT_CLAMPED ); + + lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag ); + numPoints = lex.ParseInt(); + lex.ExpectTokenString( "(" ); + for ( t = i = 0; i < numPoints; i++, t += 100 ) { + v.x = lex.ParseFloat(); + v.y = lex.ParseFloat(); + v.z = lex.ParseFloat(); + spline->AddValue( t, v ); + } + lex.ExpectTokenString( ")" ); + + return spline; +} + +/* +=============== +idEntity::ShowEditingDialog +=============== +*/ +void idEntity::ShowEditingDialog( void ) { +} + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +================ +idEntity::Event_GetName +================ +*/ +void idEntity::Event_GetName( void ) { + idThread::ReturnString( name.c_str() ); +} + +/* +================ +idEntity::Event_SetName +================ +*/ +void idEntity::Event_SetName( const char *newname ) { + SetName( newname ); +} + +/* +=============== +idEntity::Event_FindTargets +=============== +*/ +void idEntity::Event_FindTargets( void ) { + FindTargets(); +} + +/* +============ +idEntity::Event_ActivateTargets + +Activates any entities targeted by this entity. Mainly used as an +event to delay activating targets. +============ +*/ +void idEntity::Event_ActivateTargets( idEntity *activator ) { + ActivateTargets( activator ); +} + +// RAVEN BEGIN +// jshepard: added +/* +============ +idEntity::Event_UnbindTargets + +Unbinds all targets of this entity. Useful to make held or clamped items +drop when shot, and for breakable walls. +============ +*/ +void idEntity::Event_UnbindTargets( idEntity *activator ) { + UnbindTargets( activator ); +} + +// RAVEN END + + +/* +================ +idEntity::Event_NumTargets +================ +*/ +void idEntity::Event_NumTargets( void ) { + idThread::ReturnFloat( targets.Num() ); +} + +/* +================ +idEntity::Event_GetTarget +================ +*/ +void idEntity::Event_GetTarget( float index ) { + int i; + + i = ( int )index; + if ( ( i < 0 ) || i >= targets.Num() ) { + idThread::ReturnEntity( NULL ); + } else { + idThread::ReturnEntity( targets[ i ].GetEntity() ); + } +} + +/* +================ +idEntity::Event_RandomTarget +================ +*/ +void idEntity::Event_RandomTarget( const char *ignore ) { + int num; + idEntity *ent; + int i; + int ignoreNum; + + RemoveNullTargets(); + if ( !targets.Num() ) { + idThread::ReturnEntity( NULL ); + return; + } + + ignoreNum = -1; + if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ( ent->name == ignore ) ) { + ignoreNum = i; + break; + } + } + } + + if ( ignoreNum >= 0 ) { + num = gameLocal.random.RandomInt( targets.Num() - 1 ); + if ( num >= ignoreNum ) { + num++; + } + } else { + num = gameLocal.random.RandomInt( targets.Num() ); + } + + ent = targets[ num ].GetEntity(); + idThread::ReturnEntity( ent ); +} + +// RAVEN BEGIN +// abahr: so we can call this from script +/* +================ +idEntity::Event_RemoveNullTargets +================ +*/ +void idEntity::Event_RemoveNullTargets() { + RemoveNullTargets(); +} + +// twhitaker: So targets can be added from script +/* +================ +idEntity::Event_AppendTarget +================ +*/ +void idEntity::Event_AppendTarget( idEntity *appendMe ) { + idThread::ReturnFloat( AppendTarget( appendMe ) ); +} + +/* +================ +idEntity::Event_RemoveTarget +================ +*/ +void idEntity::Event_RemoveTarget( idEntity *removeMe ) { + RemoveTarget( removeMe ); +} + +/* +================ +idEntity::Event_ClearTargetList +================ +*/ +void idEntity::Event_ClearTargetList( float destroyContents ) { + RemoveTargets( destroyContents != 0.0f ); +} + +/* +================ +idEntity::Event_MatchPrefix +================ +*/ +void idEntity::Event_MatchPrefix( const char *prefix, const char* previousKey ) { + const idKeyValue* kv = (previousKey[0]) ? spawnArgs.FindKey(previousKey) : NULL; + + kv = spawnArgs.MatchPrefix( prefix, kv ); + if( !kv || !kv->GetValue() ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( kv->GetKey() ); +} + +/* +================ +idEntity::Event_IsA +================ +*/ +void idEntity::Event_IsA( const char* entityDefName ) { + const idDict* dict = gameLocal.FindEntityDefDict( entityDefName ); + if( !dict ) { + idThread::ReturnFloat( false ); + return; + } + + idTypeInfo* info = idClass::GetClass( dict->GetString("spawnclass") ); + if( !info ) { + idThread::ReturnFloat( false ); + return; + } + + idThread::ReturnFloat( IsType(*info) ); +} + +/* +================ +idEntity::Event_IsSameTypeAs +================ +*/ +void idEntity::Event_IsSameTypeAs( const idEntity* ent ) { + assert( ent ); + + idThread::ReturnFloat( IsType(ent->Type) ); +} + +// mekberg: allow sethealth on all entities. +// jshepard: removed clamping +/* +================ +idEntity::Event_SetHealth +================ +*/ +void idEntity::Event_SetHealth( float newHealth ) { + health = newHealth; +} +// RAVEN END + +/* +================ +idEntity::Event_BindToJoint +================ +*/ +void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) { + BindToJoint( master, jointname, ( orientated != 0.0f ) ); +} + +/* +================ +idEntity::Event_RemoveBinds +================ +*/ +void idEntity::Event_RemoveBinds( void ) { + RemoveBinds(); +} + +/* +================ +idEntity::Event_Bind +================ +*/ +void idEntity::Event_Bind( idEntity *master ) { + Bind( master, true ); +} + +/* +================ +idEntity::Event_BindPosition +================ +*/ +void idEntity::Event_BindPosition( idEntity *master ) { + Bind( master, false ); +} + +/* +================ +idEntity::Event_Unbind +================ +*/ +void idEntity::Event_Unbind( void ) { + Unbind(); +} + +/* +================ +idEntity::Event_SpawnBind +================ +*/ +void idEntity::Event_SpawnBind( void ) { + idEntity *parent; + const char *bind, *joint, *bindanim; + jointHandle_t bindJoint; + bool bindOrientated; + int id; + const idAnim *anim; + int animNum; + idAnimator *parentAnimator; + + if ( spawnArgs.GetString( "bind", "", &bind ) ) { + if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) { + //FIXME: Completely unneccessary since the worldspawn is called "world" + parent = gameLocal.world; + } else { + parent = gameLocal.FindEntity( bind ); + } + bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" ); + if ( parent ) { + // bind to a joint of the skeletal model of the parent + if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) { + parentAnimator = parent->GetAnimator(); + if ( !parentAnimator ) { + gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() ); + } + bindJoint = parentAnimator->GetJointHandle( joint ); + if ( bindJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() ); + } + + // bind it relative to a specific anim + if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) { + animNum = parentAnimator->GetAnim( bindanim ); + if ( !animNum ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + anim = parentAnimator->GetAnim( animNum ); + if ( !anim ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + + // make sure parent's render origin has been set + parent->UpdateModelTransform(); + + //FIXME: need a BindToJoint that accepts a joint position + parentAnimator->CreateFrame( gameLocal.time, true ); + idJointMat *frame = parent->renderEntity.joints; + gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() ); + BindToJoint( parent, joint, bindOrientated ); + parentAnimator->ForceUpdate(); + } else { + BindToJoint( parent, joint, bindOrientated ); + } + } + // bind to a body of the physics object of the parent + else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) { + BindToBody( parent, id, bindOrientated ); + } + // bind to the parent + else { + Bind( parent, bindOrientated ); + } + } + } +} + +/* +================ +idEntity::Event_SetOwner +================ +*/ +void idEntity::Event_SetOwner( idEntity *owner ) { + int i; + + for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) { + GetPhysics()->GetClipModel( i )->SetOwner( owner ); + } +} + +/* +================ +idEntity::Event_SetModel +================ +*/ +void idEntity::Event_SetModel( const char *modelname ) { + SetModel( modelname ); +} + +/* +================ +idEntity::Event_SetSkin +================ +*/ +void idEntity::Event_SetSkin( const char *skinname ) { + renderEntity.customSkin = declManager->FindSkin( skinname ); + UpdateVisuals(); +} + +/* +================ +idEntity::Event_GetShaderParm +================ +*/ +void idEntity::Event_GetShaderParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] ); +} + +/* +================ +idEntity::Event_SetShaderParm +================ +*/ +void idEntity::Event_SetShaderParm( int parmnum, float value ) { + SetShaderParm( parmnum, value ); +} + +/* +================ +idEntity::Event_SetShaderParms +================ +*/ +void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + UpdateVisuals(); +} + + +/* +================ +idEntity::Event_SetColor +================ +*/ +void idEntity::Event_SetColor( float red, float green, float blue ) { + SetColor( red, green, blue ); +} + +/* +================ +idEntity::Event_GetColor +================ +*/ +void idEntity::Event_GetColor( void ) { + idVec3 out; + + GetColor( out ); + idThread::ReturnVector( out ); +} + +/* +================ +idEntity::Event_IsHidden +================ +*/ +void idEntity::Event_IsHidden( void ) { + idThread::ReturnInt( fl.hidden ); +} + +/* +================ +idEntity::Event_Hide +================ +*/ +void idEntity::Event_Hide( void ) { + Hide(); +} + +/* +================ +idEntity::Event_Show +================ +*/ +void idEntity::Event_Show( void ) { + Show(); +} + +/* +================ +idEntity::Event_CacheSoundShader +================ +*/ +void idEntity::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idEntity::Event_StartSoundShader +================ +*/ +void idEntity::Event_StartSoundShader( const char *soundName, int channel ) { + int length; + + StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length ); + idThread::ReturnFloat( MS2SEC( length ) ); +} + +/* +================ +idEntity::Event_StopSound +================ +*/ +void idEntity::Event_StopSound( int channel, int netSync ) { + StopSound( channel, ( netSync != 0 ) ); +} + +/* +================ +idEntity::Event_StartSound +================ +*/ +void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) { + int time; + + StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +/* +================ +idEntity::Event_FadeSound +================ +*/ +void idEntity::Event_FadeSound( int channel, float to, float over ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->FadeSound( channel, to, over ); + } +// RAVEN END +} + +/* +================ +idEntity::Event_GetWorldOrigin +================ +*/ +void idEntity::Event_GetWorldOrigin( void ) { + idThread::ReturnVector( GetPhysics()->GetOrigin() ); +} + +/* +================ +idEntity::Event_SetWorldOrigin +================ +*/ +void idEntity::Event_SetWorldOrigin( idVec3 const &org ) { + idVec3 neworg = GetLocalCoordinates( org ); + SetOrigin( neworg ); +} + +/* +================ +idEntity::Event_SetOrigin +================ +*/ +void idEntity::Event_SetOrigin( idVec3 const &org ) { + SetOrigin( org ); +} + +/* +================ +idEntity::Event_GetOrigin +================ +*/ +void idEntity::Event_GetOrigin( void ) { + idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) ); +} + +/* +================ +idEntity::Event_SetAngles +================ +*/ +void idEntity::Event_SetAngles( idAngles const &ang ) { + SetAngles( ang ); +} + +/* +================ +idEntity::Event_GetAngles +================ +*/ +void idEntity::Event_GetAngles( void ) { + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idEntity::Event_SetLinearVelocity +================ +*/ +void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) { + GetPhysics()->SetLinearVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetLinearVelocity +================ +*/ +void idEntity::Event_GetLinearVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetLinearVelocity() ); +} + +/* +================ +idEntity::Event_SetAngularVelocity +================ +*/ +void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) { + GetPhysics()->SetAngularVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetAngularVelocity +================ +*/ +void idEntity::Event_GetAngularVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetAngularVelocity() ); +} + +/* +================ +idEntity::Event_SetSize +================ +*/ +void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) { + GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f ); +} + +/* +================ +idEntity::Event_GetSize +================ +*/ +void idEntity::Event_GetSize( void ) { + idBounds bounds; + + bounds = GetPhysics()->GetBounds(); + idThread::ReturnVector( bounds[1] - bounds[0] ); +} + +/* +================ +idEntity::Event_GetMins +================ +*/ +void idEntity::Event_GetMins( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[0] ); +} + +/* +================ +idEntity::Event_GetMaxs +================ +*/ +void idEntity::Event_GetMaxs( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[1] ); +} + +/* +================ +idEntity::Event_Touches +================ +*/ +void idEntity::Event_Touches( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + const idBounds &myBounds = GetPhysics()->GetAbsBounds(); + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) ); +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiParm( const char *key, const char *val ) { +// RAVEN BEGIN +// mekberg: added + idStr temp = key; + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) ) { + temp.Insert( "gui_", 0 ); + } + spawnArgs.Set( temp.c_str(), val ); +// RAVEN END + + renderEntity.gui[ i ]->SetStateString( key, val ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiFloat( const char *key, float f ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_GetNextKey +================ +*/ +void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) { + const idKeyValue *kv; + const idKeyValue *previous; + + if ( *lastMatch ) { + previous = spawnArgs.FindKey( lastMatch ); + } else { + previous = NULL; + } + + kv = spawnArgs.MatchPrefix( prefix, previous ); + if ( !kv ) { + idThread::ReturnString( "" ); + } else { + idThread::ReturnString( kv->GetKey() ); + } +} + +/* +================ +idEntity::Event_SetKey +================ +*/ +void idEntity::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +/* +================ +idEntity::Event_GetKey +================ +*/ +void idEntity::Event_GetKey( const char *key ) { + const char *value; + + spawnArgs.GetString( key, "", &value ); + idThread::ReturnString( value ); +} + +/* +================ +idEntity::Event_GetIntKey +================ +*/ +void idEntity::Event_GetIntKey( const char *key ) { + int value; + + spawnArgs.GetInt( key, "0", value ); + + // scripts only support floats + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetFloatKey +================ +*/ +void idEntity::Event_GetFloatKey( const char *key ) { + float value; + + spawnArgs.GetFloat( key, "0", value ); + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetVectorKey +================ +*/ +void idEntity::Event_GetVectorKey( const char *key ) { + idVec3 value; + + spawnArgs.GetVector( key, "0 0 0", value ); + idThread::ReturnVector( value ); +} + +/* +================ +idEntity::Event_GetEntityKey +================ +*/ +void idEntity::Event_GetEntityKey( const char *key ) { + idEntity *ent; + const char *entname; + + if ( !spawnArgs.GetString( key, NULL, &entname ) ) { + idThread::ReturnEntity( NULL ); + return; + } + + ent = gameLocal.FindEntity( entname ); + if ( !ent ) { + gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + } + + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_RestorePosition +================ +*/ +void idEntity::Event_RestorePosition( void ) { + idVec3 org; + idAngles angles; + idMat3 axis; + idEntity * part; + + spawnArgs.GetVector( "origin", "0 0 0", org ); + + // get the rotation matrix in either full form, or single angle form + if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { + angles = axis.ToAngles(); + } else { + angles[ 0 ] = 0; + angles[ 1 ] = spawnArgs.GetFloat( "angle" ); + angles[ 2 ] = 0; + } + + Teleport( org, angles, NULL ); + + for ( part = teamChain; part != NULL; part = part->teamChain ) { + if ( part->bindMaster != this ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + if ( static_cast(part->GetPhysics())->IsPusher() ) { + gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() ); + } + } else if ( part->GetPhysics()->IsType( idPhysics_AF::GetClassType() ) ) { +// RAVEN END + gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() ); + } + } +} + +/* +================ +idEntity::Event_UpdateCameraTarget +================ +*/ +void idEntity::Event_UpdateCameraTarget( void ) { + const char *target; + const idKeyValue *kv; + idVec3 dir; + + target = spawnArgs.GetString( "cameraTarget" ); + + cameraTarget = gameLocal.FindEntity( target ); + + if ( cameraTarget ) { + kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) { + dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin(); + dir.Normalize(); + cameraTarget->SetAxis( dir.ToMat3() ); +// RAVEN BEGIN +// rjohnson: if you have a func_cameraview pointing to an info_null via "cameratarget" and +// you have a func_static pointing to the func_cameraview via a "cameratarget" then +// the func_static evaluates 'target' to the func_cameraview and its target it the info null +// the SexAxis() is then applied to the the func_static rather than the func_cameraview +// SetAxis(dir.ToMat3()); +// RAVEN END + break; + } + kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv ); + } + } + UpdateVisuals(); +} + +/* +================ +idEntity::Event_DistanceTo +================ +*/ +void idEntity::Event_DistanceTo( idEntity *ent ) { + if ( !ent ) { + // just say it's really far away + idThread::ReturnFloat( MAX_WORLD_SIZE ); + } else { + float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast(); + idThread::ReturnFloat( dist ); + } +} + +/* +================ +idEntity::Event_DistanceToPoint +================ +*/ +void idEntity::Event_DistanceToPoint( const idVec3 &point ) { + float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast(); + idThread::ReturnFloat( dist ); +} + +/* +================ +idEntity::Event_StartFx +================ +*/ +void idEntity::Event_StartFx( const char *fx ) { +// RAVEN BEGIN +// bdube: not used +// idEntityFx::StartFx( fx, NULL, NULL, this, true ); +// RAVEN END +} + +/* +================ +idEntity::Event_WaitFrame +================ +*/ +void idEntity::Event_WaitFrame( void ) { + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->WaitFrame(); + } +} + +/* +===================== +idEntity::Event_Wait +===================== +*/ +void idEntity::Event_Wait( float time ) { + idThread *thread = idThread::CurrentThread(); + + if ( !thread ) { + gameLocal.Error( "Event 'wait' called from outside thread" ); + } + + thread->WaitSec( time ); +} + +/* +===================== +idEntity::Event_HasFunction +===================== +*/ +void idEntity::Event_HasFunction( const char *name ) { + const function_t *func; + + func = scriptObject.GetFunction( name ); + if ( func ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idEntity::Event_CallFunction +===================== +*/ +void idEntity::Event_CallFunction( const char *funcname ) { +// RAVEN BEGIN +// bdube: states + stateParms_t parms = {0}; + if ( ProcessState ( funcname, parms ) != SRESULT_ERROR ) { + return; + } + gameLocal.CallObjectFrameCommand ( this, funcname ); +// RAVEN END +} + +/* +================ +idEntity::Event_SetNeverDormant +================ +*/ +void idEntity::Event_SetNeverDormant( int enable ) { + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + +/*********************************************************************** + + Network + +***********************************************************************/ + +/* +================ +idEntity::ClientPredictionThink +================ +*/ +void idEntity::ClientPredictionThink( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::WriteBindToSnapshot +================ +*/ +void idEntity::WriteBindToSnapshot( idBitMsgDelta &msg ) const { + int bindInfo; + + if ( bindMaster ) { + bindInfo = bindMaster->entityNumber; + bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS; + if ( bindJoint != INVALID_JOINT ) { + bindInfo |= 1 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS ); + } else if ( bindBody != -1 ) { + bindInfo |= 2 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS ); + } + } else { + bindInfo = ENTITYNUM_NONE; + } + msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 ); +} + +/* +================ +idEntity::ReadBindFromSnapshot +================ +*/ +void idEntity::ReadBindFromSnapshot( const idBitMsgDelta &msg ) { + int bindInfo, bindEntityNum, bindPos; + bool bindOrientated; + idEntity *master; + + bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 ); + bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + + if ( bindEntityNum != ENTITYNUM_NONE ) { + master = gameLocal.entities[ bindEntityNum ]; + + bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1; + bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) ); + switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) { + case 1: { + BindToJoint( master, (jointHandle_t) bindPos, bindOrientated ); + break; + } + case 2: { + BindToBody( master, bindPos, bindOrientated ); + break; + } + default: { + Bind( master, bindOrientated ); + break; + } + } + } else if ( bindMaster ) { + Unbind(); + } +} + +/* +================ +idEntity::WriteColorToSnapshot +================ +*/ +void idEntity::WriteColorToSnapshot( idBitMsgDelta &msg ) const { + idVec4 color; + + color[0] = renderEntity.shaderParms[ SHADERPARM_RED ]; + color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; + msg.WriteLong( PackColor( color ) ); +} + +/* +================ +idEntity::ReadColorFromSnapshot +================ +*/ +void idEntity::ReadColorFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 color; + + UnpackColor( msg.ReadLong(), color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3]; +} + +/* +================ +idEntity::WriteGUIToSnapshot +================ +*/ +void idEntity::WriteGUIToSnapshot( idBitMsgDelta &msg ) const { + // no need to loop over MAX_RENDERENTITY_GUI at this time + if ( renderEntity.gui[ 0 ] ) { + msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) ); + } else { + msg.WriteByte( 0 ); + } +} + +/* +================ +idEntity::ReadGUIFromSnapshot +================ +*/ +void idEntity::ReadGUIFromSnapshot( const idBitMsgDelta &msg ) { + int state; + idUserInterface *gui; + state = msg.ReadByte( ); + gui = renderEntity.gui[ 0 ]; + if ( gui && state != mpGUIState ) { + mpGUIState = state; + gui->SetStateInt( "networkState", state ); + gui->HandleNamedEvent( "networkState" ); + } +} + +/* +================ +idEntity::WriteToSnapshot +================ +*/ +void idEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idEntity::ReadFromSnapshot +================ +*/ +void idEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +idEntity::ServerSendEvent + + Saved events are also sent to any client that connects late so all clients + always receive the events nomatter what time they join the game. + ================ + */ +void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ServerSendReliableMessageExcluding( excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ServerSendInstanceEvent +================ +*/ +void idEntity::ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + gameLocal.ServerSendInstanceReliableMessageExcluding( this, excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ClientSendEvent +================ +*/ +void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isClient ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idEntity::ServerReceiveEvent +================ +*/ +bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case 0: { + } + default: { + return false; + } + } +} + +/* +================ +idEntity::ClientReceiveEvent +================ +*/ +bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + const idSoundShader *shader; + s_channelType channel; + + switch( event ) { + case EVENT_STARTSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + if ( time < gameLocal.realClientTime - 300 ) { + // too old, skip it + common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time ); + return true; + } + shader = static_cast< const idSoundShader* >( idGameLocal::ReadDecl( msg, DECL_SOUND ) ); + channel = (s_channelType)msg.ReadByte(); + StartSoundShader( shader, channel, 0, false, NULL ); + return true; + } + case EVENT_STOPSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + channel = (s_channelType)msg.ReadByte(); + StopSound( channel, false ); + return true; + } +// RAVEN BEGIN +// bdube: new events + case EVENT_PLAYEFFECT_JOINT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin; + rvClientEffect* clientEffect; + effectCategory_t category; + jointHandle_t jointHandle; + bool loop; + + // TMP - not quite sure this is still used for anything + common->Warning( "FIXME: idEntity::PlayEffect happens" ); + assert( false ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + jointHandle = ( jointHandle_t )msg.ReadLong(); + loop = ( msg.ReadBits ( 1 ) != 0 ); + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( vec3_origin ); + clientEffect->SetAxis ( mat3_identity ); + clientEffect->Bind( this, jointHandle ); + + clientEffect->Play( time, loop, origin ); + } + return true; + } + + case EVENT_PLAYEFFECT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* clientEffect; + effectCategory_t category; + bool loop; + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + loop = ( msg.ReadBits( 1 ) != 0 ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( origin ); + clientEffect->SetAxis ( quat.ToMat3() ); + clientEffect->Bind ( this ); + + clientEffect->Play ( time, loop, origin2 ); + } + return true; + } +// RAVEN END + default: { + return false; + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idEntity::ClientStale +================ +*/ +bool idEntity::ClientStale( void ) { + FreeModelDef(); + UpdateVisuals(); + GetPhysics()->UnlinkClip(); + return false; +} + +/* +================ +idEntity::ClientUnstale +================ +*/ +void idEntity::ClientUnstale( void ) { +} + +/* +================ +idEntity::GetDamageEntity + +Returns the entity that should take damage in place of this entity. The default is the +entity itself. +================ +*/ +idEntity* idEntity::GetDamageEntity( void ) { + return forwardDamageEnt.IsValid() ? forwardDamageEnt.GetEntity() : this; +} + +// rjohnson: moved entity info out of idGameLocal into its own function +/* +================ +idEntity::DrawDebugEntityInfo +================ +*/ +void idEntity::DrawDebugEntityInfo( idBounds *viewBounds, idBounds *viewTextBounds, idVec4 *overrideColor ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idMat3 axis = player->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + // skip if the entity is very far away + if ( viewBounds && !viewBounds->IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + return; + } + + const idBounds &entBounds = GetPhysics()->GetAbsBounds(); + + if (overrideColor) { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( *overrideColor, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( *overrideColor, entBounds, vec3_origin ); + } + } else { + int contents = GetPhysics()->GetContents(); + if ( contents & CONTENTS_BODY ) { + gameRenderWorld->DebugBounds ( colorCyan, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_TRIGGER ) { + gameRenderWorld->DebugBounds( colorOrange, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_SOLID ) { + gameRenderWorld->DebugBounds( colorGreen, entBounds, vec3_origin ); + } else { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds, vec3_origin ); + } + } + } + + if ( !viewTextBounds || viewTextBounds->IntersectsBounds( entBounds ) ) { + gameRenderWorld->DrawText( name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + + if ( gameLocal.GetLocalPlayer() && this != gameLocal.GetLocalPlayer() && teamMaster != gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + } +} + +/* +===================== +idEntity::SetInstance +===================== +*/ +void idEntity::SetInstance( int newInstance ) { + instance = newInstance; + + if( gameLocal.isServer ) { + SetClipWorld( newInstance ); + } +} + +/* +===================== +idEntity::InstanceJoin +Gets called when the local player joins the same instance as this entity +===================== +*/ +void idEntity::InstanceJoin( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ); + + BecomeActive( TH_UPDATEVISUALS ); + Present(); +} + +/* +===================== +idEntity::InstanceLeave +Gets called when the local player leaves the same instance as this entity +===================== +*/ +void idEntity::InstanceLeave( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ); + + FreeLightDef(); + FreeModelDef(); + //RemoveClientEntities(); + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +===================== +idEntity::GetDebugInfo +===================== +*/ +void idEntity::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idClass::GetDebugInfo ( proc, userData ); + + proc ( "idEntity", "health", va("%d",health), userData ); + proc ( "idEntity", "name", name, userData ); + proc ( "idEntity", "entityNumber", va("%d",entityNumber), userData ); + proc ( "idEntity", "origin", renderEntity.origin.ToString ( ), userData ); + + proc ( "idEntity", "notarget", fl.notarget?"true":"false", userData ); + proc ( "idEntity", "takedamage", fl.takedamage?"true":"false", userData ); + proc ( "idEntity", "hidden", fl.hidden?"true":"false", userData ); + proc ( "idEntity", "bindOrientated",fl.bindOrientated?"true":"false", userData ); + proc ( "idEntity", "isDormant", fl.isDormant?"true":"false", userData ); + proc ( "idEntity", "neverDormant", fl.neverDormant?"true":"false", userData ); + proc ( "idEntity", "isAIObstacle", fl.isAIObstacle?"true":"false", userData ); + + proc ( "idEntity", "forwardDamageEnt",forwardDamageEnt.GetEntity() ? forwardDamageEnt.GetEntity()->GetName() : "", userData ); + + proc ( "idEntity", "bindMaster", bindMaster ? bindMaster->GetName() : "", userData ); + proc ( "idEntity", "bindJoint", va("%d",((int)bindJoint)), userData ); + proc ( "idEntity", "bindBody", va("%d",bindBody), userData ); + + proc ( "idEntity", "teamMaster", teamMaster ? teamMaster->GetName() : "", userData ); + proc ( "idEntity", "teamChain", teamChain ? teamChain->GetName() : "", userData ); +} + +// mwhitlock: memory profiling +/* +===================== +idEntity::Size() + +Returns memory size of an idEntity instance +===================== +*/ + +size_t idEntity::Size( void ) const +{ + // TODO: more crap needs to go here! + return sizeof (idEntity); +} +// RAVEN END + + + +/* +=============================================================================== + + idAnimatedEntity + +=============================================================================== +*/ + +const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' ); +const idEventDef EV_ClearAllJoints( "clearAllJoints" ); +const idEventDef EV_ClearJoint( "clearJoint", "d" ); +const idEventDef EV_SetJointPos( "setJointPos", "ddv" ); +const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); +const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); +const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); + + +// RAVEN BEGIN +// bdube: programmer controlled joint events +const idEventDef EV_SetJointAngularVelocity ( "setJointAngularVelocity", "sfffd" ); +const idEventDef EV_CollapseJoints ( "collapseJoints", "ss" ); +// jshepard: clear out all animations still running on the model +const idEventDef EV_ClearAnims( "clearAnims" ); + +// RAVEN END + +CLASS_DECLARATION( idEntity, idAnimatedEntity ) + EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) + EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) + EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint ) + EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos ) + EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) + EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) + EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) + +// RAVEEN BEGIN +// bdube: programmer controlled joint events + EVENT( EV_SetJointAngularVelocity, idAnimatedEntity::Event_SetJointAngularVelocity ) + EVENT( EV_CollapseJoints, idAnimatedEntity::Event_CollapseJoints ) +// RAVEN END +END_CLASS + +/* +================ +idAnimatedEntity::idAnimatedEntity +================ +*/ +idAnimatedEntity::idAnimatedEntity() { + animator.SetEntity( this ); + damageEffects = NULL; +} + +/* +================ +idAnimatedEntity::~idAnimatedEntity +================ +*/ +idAnimatedEntity::~idAnimatedEntity() { + damageEffect_t *de; + + for ( de = damageEffects; de; de = damageEffects ) { + damageEffects = de->next; + delete de; + } +} + +/* +================ +idAnimatedEntity::Save + +archives object for save game file +================ +*/ +void idAnimatedEntity::Save( idSaveGame *savefile ) const { + animator.Save( savefile ); + + // Wounds are very temporary, ignored at this time + //damageEffect_t *damageEffects; +} + +/* +================ +idAnimatedEntity::Restore + +unarchives object from save game file +================ +*/ +void idAnimatedEntity::Restore( idRestoreGame *savefile ) { + animator.Restore( savefile ); + + // check if the entity has an MD5 model + if ( animator.ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( modelDefHandle != -1 ) { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + } +} + +/* +================ +idAnimatedEntity::ClientPredictionThink +================ +*/ +void idAnimatedEntity::ClientPredictionThink( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::Think +================ +*/ +void idAnimatedEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::UpdateAnimation +================ +*/ +void idAnimatedEntity::UpdateAnimation( void ) { + // don't do animations if they're not enabled + if ( !( thinkFlags & TH_ANIMATE ) ) { + return; + } + + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + +// RAVEN BEGIN +// bgeisler: for triggered anims + // call any frame commands that have happened in the past frame + if ( !fl.hidden || fl.triggerAnim ) { + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } +// RAVEN END + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() && !fl.hidden ) { + gameLocal.DPrintf( "idAnimatedEntity %s %d: inside out bounds - %d\n", GetName(), entityNumber, gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +idAnimatedEntity::GetAnimator +================ +*/ +idAnimator *idAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +================ +idAnimatedEntity::SetModel +================ +*/ +void idAnimatedEntity::SetModel( const char *modelname ) { + FreeModelDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + idEntity::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + UpdateVisuals(); +} + +/* +===================== +idAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass(); + axis = renderEntity.axis; + return true; + } + + if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) { + return false; + } + + ConvertLocalToWorldTransform( offset, axis ); + return true; +} + +/* +============== +idAnimatedEntity::GetJointTransformForAnim +============== +*/ +bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const { + const idAnim *anim; + int numJoints; + idJointMat *frame; + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass() - GetPhysics()->GetOrigin(); + axis = renderEntity.axis; + return true; + } + + anim = animator.GetAnim( animNum ); + if ( !anim ) { + assert( 0 ); + return false; + } + + numJoints = animator.NumJoints(); + if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) { + assert( 0 ); + return false; + } + + frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) ); + gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() ); + + offset = frame[ jointHandle ].ToVec3(); + axis = frame[ jointHandle ].ToMat3(); + + return true; +} + +// RAVEN BEGIN +// ddynerman: removed/merged AddLocalDamageEffect() (redundant math) +/* +============== +idAnimatedEntity::AddDamageEffect + + Dammage effects track the animating impact position, spitting out particles. +============== +*/ +void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // ddynerman: note, on client the collision struct is incomplete. Only contains impact point and material + const char *splat, *decal, *key; + idVec3 dir; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { + return; + } + + if ( !spawnArgs.GetBool( "bleed" ) ) { + return; + } + + dir = velocity; + dir.Normalize(); + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( collision.c.point[0] ); + msg.WriteFloat( collision.c.point[1] ); + msg.WriteFloat( collision.c.point[2] ); + msg.WriteDir( dir, 24 ); + idGameLocal::WriteDecl( msg, def ); + idGameLocal::WriteDecl( msg, collision.c.material ); + ServerSendInstanceEvent( EVENT_ADD_DAMAGE_EFFECT, &msg, false, -1 ); + } + + if ( !g_decals.GetBool() ) { + return; + } + + if ( gameLocal.GetLocalPlayer() && GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; // no blood from other instances + } + + // blood splats are thrown onto nearby surfaces + splat = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_splat_%s", collision.c.material->GetMaterialType()->GetName() ); + splat = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !splat || !*splat ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + } + if ( splat && *splat ) { + //jshepard original 64.0f + // dluetscher: changed from 64. to 48. for performance reasons + gameLocal.BloodSplat( this, collision.c.point, dir, 48.0f, splat ); + } + + // can't see wounds on the player model in single player mode + if ( !( IsType( idPlayer::GetClassType() ) && !gameLocal.isMultiplayer ) ) { + //If this is a buddy marine, no wound decals until they're actually dead unless it's mp. + if ( gameLocal.isMultiplayer + || !IsType( idAI::GetClassType() ) + || this->health <= 0 + || ((idAI*)this)->team != AITEAM_MARINE ) { + // place a wound overlay on the model + decal = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_wound_%s", collision.c.material->GetMaterialType()->GetName() ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !decal || !*decal ) { + decal = spawnArgs.RandomPrefix( "mtr_wound", gameLocal.random ); + } + if ( decal && *decal ) { + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + if( IsType( idPlayer::GetClassType() ) ) { + ProjectHeadOverlay( collision.c.point, dir, 20.0f, decal ); + } + } + } + } +} +// RAVEN END + +/* +============== +idAnimatedEntity::GetDefaultSurfaceType +============== +*/ +int idAnimatedEntity::GetDefaultSurfaceType( void ) const { + return SURFTYPE_METAL; +} + +/* +================ +idAnimatedEntity::ClientReceiveEvent +================ +*/ +bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 origin, dir; + + switch( event ) { + case EVENT_ADD_DAMAGE_EFFECT: { + origin[0] = msg.ReadFloat(); + origin[1] = msg.ReadFloat(); + origin[2] = msg.ReadFloat(); + dir = msg.ReadDir( 24 ); + const idDeclEntityDef *damageDef = static_cast< const idDeclEntityDef* >( idGameLocal::ReadDecl( msg, DECL_ENTITYDEF ) ); + const idMaterial *collisionMaterial = static_cast< const idMaterial* >( idGameLocal::ReadDecl( msg, DECL_MATERIAL ) ); +// RAVEN BEGIN +// ddynerman: removed redundant AddLocalDamageEffect() + trace_t collision; + collision.c.point = origin; + collision.c.material = collisionMaterial; + AddDamageEffect( collision, dir, damageDef->GetName(), NULL ); +// RAVEN END + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// abahr: so we don't crash if UpdateModel is called from a destructor +/* +================ +idAnimatedEntity::UpdateRenderEntityCallback +================ +*/ +void idAnimatedEntity::UpdateRenderEntityCallback() { + // check if the entity has an MD5 model + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + } +} +// RAVEN END + +/* +================ +idAnimatedEntity::Event_GetJointHandle + +looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found. +================ +*/ +void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) { + jointHandle_t joint; + + joint = animator.GetJointHandle( jointname ); + idThread::ReturnInt( joint ); +} + +/* +================ +idAnimatedEntity::Event_ClearAllJoints + +removes any custom transforms on all joints +================ +*/ +void idAnimatedEntity::Event_ClearAllJoints( void ) { + animator.ClearAllJoints(); +} + +/* +================ +idAnimatedEntity::Event_ClearJoint + +removes any custom transforms on the specified joint +================ +*/ +void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) { + animator.ClearJoint( jointnum ); +} + +/* +================ +idAnimatedEntity::Event_ClearAnims + +Clears any animation running on the animated entity +================ +*/ +void idAnimatedEntity::Event_ClearAnims( void ) { + animator.Clear( ANIMCHANNEL_ALL, gameLocal.GetTime(), gameLocal.GetTime() ); +} + +/* +================ +idAnimatedEntity::Event_SetJointPos + +modifies the position of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + animator.SetJointPos( jointnum, transform_type, pos ); +} + +/* +================ +idAnimatedEntity::Event_SetJointAngle + +modifies the orientation of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +/* +================ +idAnimatedEntity::Event_GetJointPos + +returns the position of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idThread::ReturnVector( offset ); +} + +/* +================ +idAnimatedEntity::Event_GetJointAngle + +returns the orientation of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idAngles ang = axis.ToAngles(); + idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); + idThread::ReturnVector( vec ); +} + +// RAVEN BEGIN +// bdube: moved to idAnimatedEntity +/* +================ +idAnimatedEntity::Event_SetJointAngularVelocity +================ +*/ +void idAnimatedEntity::Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ) { + jointHandle_t joint = animator.GetJointHandle ( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + animator.SetJointAngularVelocity ( joint, idAngles(pitch,yaw,roll), gameLocal.time, blendTime ); +} + +/* +================ +idAnimatedEntity::Event_CollapseJoints +================ +*/ +void idAnimatedEntity::Event_CollapseJoints ( const char* jointnames, const char* collapseTo ) { + jointHandle_t collapseToJoint = animator.GetJointHandle ( collapseTo ); + if ( collapseToJoint == INVALID_JOINT ) { + return; + } + + animator.CollapseJoints ( jointnames, collapseToJoint ); +} +// RAVEN END + diff --git a/source/game/Entity.h b/source/game/Entity.h new file mode 100644 index 0000000..fd85832 --- /dev/null +++ b/source/game/Entity.h @@ -0,0 +1,849 @@ + +#ifndef __GAME_ENTITY_H__ +#define __GAME_ENTITY_H__ + +/* +=============================================================================== + + Game entity base class. + +=============================================================================== +*/ + +static const int DELAY_DORMANT_TIME = 3000; + +extern const idEventDef EV_PostSpawn; +extern const idEventDef EV_FindTargets; +extern const idEventDef EV_Touch; +extern const idEventDef EV_Use; +extern const idEventDef EV_Activate; +extern const idEventDef EV_ActivateTargets; +extern const idEventDef EV_Hide; +extern const idEventDef EV_Show; +extern const idEventDef EV_GetShaderParm; +extern const idEventDef EV_SetShaderParm; +extern const idEventDef EV_SetOwner; +extern const idEventDef EV_GetAngles; +extern const idEventDef EV_SetAngles; +extern const idEventDef EV_SetLinearVelocity; +extern const idEventDef EV_SetAngularVelocity; +extern const idEventDef EV_SetSkin; +extern const idEventDef EV_StartSoundShader; +extern const idEventDef EV_StopSound; +extern const idEventDef EV_CacheSoundShader; + +// RAVEN BEGIN +extern const idEventDef EV_CallFunction; +extern const idEventDef EV_SetGuiParm; +extern const idEventDef EV_SetGuiFloat; +extern const idEventDef EV_ClearSkin; +// bdube: more global events for idEntity +extern const idEventDef EV_GetFloatKey; +extern const idEventDef EV_HideSurface; +extern const idEventDef EV_ShowSurface; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_StopAllEffects; +extern const idEventDef EV_PlayEffect; +extern const idEventDef EV_Earthquake; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_SetKey; +// jscott: +extern const idEventDef EV_PlaybackCallback; +// jshepard: +extern const idEventDef EV_UnbindTargets; +// twhitaker: +extern const idEventDef EV_ApplyImpulse; +// RAVEN END + +class idGuidedProjectile; + +// Think flags +enum { + TH_ALL = -1, + TH_THINK = 1, // run think function each frame + TH_PHYSICS = 2, // run physics each frame + TH_ANIMATE = 4, // update animation each frame + TH_UPDATEVISUALS = 8, // update renderEntity +}; + +// +// Signals +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + SIG_TOUCH, // object was touched + SIG_USE, // object was used + SIG_TRIGGER, // object was activated + SIG_REMOVED, // object was removed from the game + SIG_DAMAGE, // object was damaged + SIG_BLOCKED, // object was blocked + + SIG_MOVER_POS1, // mover at position 1 (door closed) + SIG_MOVER_POS2, // mover at position 2 (door open) + SIG_MOVER_1TO2, // mover changing from position 1 to 2 + SIG_MOVER_2TO1, // mover changing from position 2 to 1 + +// RAVEN BEGIN +// kfuller: added signals + // WARNING: these entries are mirrored in scripts/defs.script so make sure if they move + // here that they move there as well + SIG_REACHED, // object reached it's rotation/motion destination +// RAVEN END + + NUM_SIGNALS +} signalNum_t; + +// FIXME: At some point we may want to just limit it to one thread per signal, but +// for now, I'm allowing multiple threads. We should reevaluate this later in the project +#define MAX_SIGNAL_THREADS 16 // probably overkill, but idList uses a granularity of 16 + +struct signal_t { + int threadnum; + const function_t *function; +}; + +class signalList_t { +public: + idList signal[ NUM_SIGNALS ]; +}; + +class idEntity : public idClass { +public: + static const int MAX_PVS_AREAS = 4; + + int entityNumber; // index into the entity list + int entityDefNumber; // index into the entity def list + + idLinkList spawnNode; // for being linked into spawnedEntities list + idLinkList activeNode; // for being linked into activeEntities list + + idLinkList snapshotNode; // for being linked into snapshotEntities list + int snapshotSequence; // last snapshot this entity was in + int snapshotBits; // number of bits this entity occupied in the last snapshot + + idStr name; // name of entity + idDict spawnArgs; // key/value pairs used to spawn and initialize entity + idScriptObject scriptObject; // contains all script defined data for this entity + + int thinkFlags; // TH_? flags + int dormantStart; // time that the entity was first closed off from player + bool cinematic; // during cinematics, entity will only think if cinematic is set + + renderView_t * renderView; // for camera views from this entity + idEntity * cameraTarget; // any remoteRenderMap shaders will use this + + idList< idEntityPtr > targets; // when this entity is activated these entities entity are activated + + int health; // FIXME: do all objects really need health? + +// RAVEN BEGIN +// ddynerman: optional pre-prediction + int predictTime; +// bdube: client entities + idLinkList clientEntities; + +// rjohnson: will now draw entity info for long thinkers + int mLastLongThinkTime; + idVec4 mLastLongThinkColor; +// RAVEN END + + struct entityFlags_s { + bool notarget :1; // if true never attack or target this entity + bool noknockback :1; // if true no knockback from hits + bool takedamage :1; // if true this entity can be damaged + bool hidden :1; // if true this entity is not visible + bool bindOrientated :1; // if true both the master orientation is used for binding + bool solidForTeam :1; // if true this entity is considered solid when a physics team mate pushes entities + bool forcePhysicsUpdate :1; // if true always update from the physics whether the object moved or not + bool selected :1; // if true the entity is selected for editing + bool neverDormant :1; // if true the entity never goes dormant + bool isDormant :1; // if true the entity is dormant + bool hasAwakened :1; // before a monster has been awakened the first time, use full PVS for dormant instead of area-connected + bool networkSync :1; // if true the entity is synchronized over the network + +// RAVEN BEGIN +// bdube: added + bool networkStale :1; // was in the snapshot but isnt anymore +// bgeisler: added block + bool triggerAnim :1; + bool usable :1; // if true the entity is usable by the player +// cdr: Obstacle Avoidance + bool isAIObstacle :1; // if true, this entity will add itself to the obstacles list in each aas area it touches +// RAVEN END + +// RAVEN BEGIN +// ddynerman: exclude this entity from instance-purging + // twhitaker: moved variable to be within the bit flags + bool persistAcrossInstances :1; +// twhitaker: exited vehicle already? + bool exitedVehicle :1; +// twhitaker: blinking + bool allowAutoBlink :1; +// jshepard: instant burnout when destroyed + bool quickBurn :1; + +// RAVEN END + } fl; + +public: + ABSTRACT_PROTOTYPE( idEntity ); + + idEntity(); + ~idEntity(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + const char * GetEntityDefName( void ) const; + void SetName( const char *name ); + const char * GetName( void ) const; + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + // clients generate views based on all the player specific options, + // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + + // thinking + virtual void Think( void ); + bool CheckDormant( void ); // dormant == on the active list, but out of PVS + virtual void DormantBegin( void ); // called when entity becomes dormant + virtual void DormantEnd( void ); // called when entity wakes from being dormant + bool IsActive( void ) const; + void BecomeActive( int flags ); + void BecomeInactive( int flags ); + void UpdatePVSAreas( const idVec3 &pos ); + +// RAVEN BEGIN +// abahr: + bool IsActive( int flags ) const { return (flags & thinkFlags) > 0; } + const char* GetEntityDefClassName() const { return spawnArgs.GetString("classname"); } + bool IsEntityDefClass( const char* className ) const { return !idStr::Icmp(className, GetEntityDefClassName()); } + virtual void GetPosition( idVec3& origin, idMat3& axis ) const; +// kfuller: added methods + virtual void GetLocalAngles(idAngles &localAng); +// RAVEN END + + // visuals + virtual void Present( void ); + // instance visuals + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); +// RAVEN BEGIN +// bdube: removed virtual so it could be inlined + renderEntity_t * GetRenderEntity( void ); + int GetModelDefHandle( void ); +// RAVEN END + virtual void SetModel( const char *modelname ); + void SetSkin( const idDeclSkin *skin ); + const idDeclSkin * GetSkin( void ) const; + +// RAVEN BEGIN +// bdube: surfaces + void HideSurface ( const char* surface ); + void ShowSurface ( const char* surface ); + void ClearSkin( void ); +// RAVEN END + + void SetShaderParm( int parmnum, float value ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec4 &out ) const; + virtual void FreeModelDef( void ); + virtual void FreeLightDef( void ); + virtual void Hide( void ); + virtual void Show( void ); + bool IsHidden( void ) const; + void UpdateVisuals( void ); + void UpdateModel( void ); +// RAVEN BEGIN +// abahr: added virtual to UpdateModelTransform + virtual + void UpdateModelTransform( void ); + virtual void UpdateRenderEntityCallback(); + virtual const idAnimator * GetAnimator( void ) const { return NULL; } // returns animator object used by this entity +// RAVEN END + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + int GetNumPVSAreas( void ); + const int * GetPVSAreas( void ); + void ClearPVSAreas( void ); + bool PhysicsTeamInPVS( pvsHandle_t pvsHandle ); + + // animation + virtual bool UpdateAnimationControllers( void ); + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + virtual idAnimator * GetAnimator( void ); // returns animator object used by this entity + + // sound + virtual bool CanPlayChatterSounds( void ) const; + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound( const s_channelType channel, bool broadcast ); // pass SND_CHANNEL_ANY to stop all sounds + void SetSoundVolume( float volume ); + void UpdateSound( void ); + int GetListenerId( void ) const; +// RAVEN BEGIN + int GetSoundEmitter( void ) const; +// RAVEN END + void FreeSoundEmitter( bool immediate ); + +// RAVEN BEGIN +// bdube: added effect functions + // effects + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + + rvClientEffect* PlayEffect ( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + void StopEffect ( const char* effectName, bool destroyParticles = false ); + void StopEffect ( const idDecl *effect, bool destroyParticles = false ); + void StopAllEffects ( bool destroyParticles = false ); + void UpdateEffects ( void ); + + float DistanceTo ( idEntity* ent ); + float DistanceTo ( const idVec3& pos ) const; + float DistanceTo2d ( idEntity* ent ); + float DistanceTo2d ( const idVec3& pos ) const; + + virtual bool CanTakeDamage ( void ) const; +// RAVEN END + + // entity binding + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void PreUnbind( void ); + virtual void PostUnbind( void ); + void JoinTeam( idEntity *teammember ); + void Bind( idEntity *master, bool orientated ); + void BindToJoint( idEntity *master, const char *jointname, bool orientated ); + void BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ); + void BindToBody( idEntity *master, int bodyId, bool orientated ); + void Unbind( void ); + bool IsBound( void ) const; +// RAVEN BEGIN +// abahr: added const so it can be called from const functions + bool IsBoundTo( const idEntity *master ) const; +// RAVEN END + idEntity * GetBindMaster( void ) const; + jointHandle_t GetBindJoint( void ) const; + int GetBindBody( void ) const; + idEntity * GetTeamMaster( void ) const; + idEntity * GetNextTeamEntity( void ) const; +// RAVEN BEGIN +// abahr: added virtual + virtual + void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); +// RAVEN END + idVec3 GetLocalVector( const idVec3 &vec ) const; + idVec3 GetLocalCoordinates( const idVec3 &vec ) const; + idVec3 GetWorldVector( const idVec3 &vec ) const; + idVec3 GetWorldCoordinates( const idVec3 &vec ) const; + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + void GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const; + + // physics + // set a new physics object to be used by this entity + void SetPhysics( idPhysics *phys ); + // get the physics object used by this entity + idPhysics * GetPhysics( void ) const; + // restore physics pointer for save games + void RestorePhysics( idPhysics *phys ); + // run the physics for this entity + bool RunPhysics( void ); + // set the origin of the physics object (relative to bindMaster if not NULL) + void SetOrigin( const idVec3 &org ); + // set the axis of the physics object (relative to bindMaster if not NULL) + void SetAxis( const idMat3 &axis ); + // use angles to set the axis of the physics object (relative to bindMaster if not NULL) + void SetAngles( const idAngles &ang ); + // get the floor position underneath the physics object + bool GetFloorPos( float max_dist, idVec3 &floorpos ) const; + // retrieves the transformation going from the physics origin/axis to the visual origin/axis + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + // retrieves the transformation going from the physics origin/axis to the sound origin/axis + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + // called from the physics object when colliding, should return true if the physics simulation should stop + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { hitTeleporter = false; return Collide(collision, velocity); } + // retrieves impact information, 'ent' is the entity retrieving the info + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + // apply an impulse to the physics object, 'ent' is the entity applying the impulse + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + // add a force to the physics object, 'ent' is the entity adding the force + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + // activate the physics object, 'ent' is the entity activating this entity + virtual void ActivatePhysics( idEntity *ent ); + // returns true if the physics object is at rest + virtual bool IsAtRest( void ) const; + // returns the time the physics object came to rest + virtual int GetRestStartTime( void ) const; + // add a contact entity + virtual void AddContactEntity( idEntity *ent ); + // remove a touching entity + virtual void RemoveContactEntity( idEntity *ent ); + +// RAVEN BEGIN +// kfuller: added blocked methods + virtual void LastBlockedBy(int blockedEntNum) {} + virtual int GetLastBlocker(void) { return -1; } +// rjohnson: moved entity info out of idGameLocal into its own function + virtual void DrawDebugEntityInfo( idBounds *viewBounds = 0, idBounds *viewTextBounds = 0, idVec4 *overrideColor = 0 ); +// nmckenzie: Adding ability for non-Actors to be enemies for AI characters. Rename this function at some point, most likely. + virtual idVec3 GetEyePosition( void ) const { return GetPhysics()->GetOrigin(); } +// abahr: + virtual bool SkipImpulse( idEntity *ent, int id ); + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ); +// RAVEN END + + // damage +// RAVEN BEGIN + // twhitaker: // Sets the damage enitty + void SetDamageEntity ( idEntity * forward ) { forwardDamageEnt = forward; } + + // bdube: added ignore entity + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + + // returns true if this entity can be damaged from the given origin + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt = NULL ) const; +// RAVEN END + // applies damage to this entity + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + // adds a damage effect like overlays, blood, sparks, debris etc. + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + // callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + // notifies this entity that it is in pain + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + // notifies this entity that is has been killed + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // scripting + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread * ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ); + void ClearSignal( idThread *thread, signalNum_t signalnum ); + void ClearSignalThread( signalNum_t signalnum, idThread *thread ); + bool HasSignal( signalNum_t signalnum ) const; + void Signal( signalNum_t signalnum ); + void SignalEvent( idThread *thread, signalNum_t signalnum ); + + // gui + void TriggerGuis( void ); + bool HandleGuiCommands( idEntity *entityGui, const char *cmds ); + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + + // targets +// RAVEN BEGIN +// abahr: made virtual + virtual + void FindTargets( void ); + virtual + void RemoveNullTargets( void ); + virtual + void ActivateTargets( idEntity *activator ) const; +// jshepard: unbind targets + void UnbindTargets( idEntity *activator ) const; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: Add to the list of targets (usually from script) + int AppendTarget( idEntity *appendMe ); + void RemoveTarget( idEntity *removeMe ); + void RemoveTargets( bool destroyContents ); +// RAVEN END + + // misc + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + bool TouchTriggers( const idTypeInfo* ownerType = NULL ) const; + idCurve_Spline *GetSpline( void ) const; + virtual void ShowEditingDialog( void ); + + enum { + EVENT_STARTSOUNDSHADER, + EVENT_STOPSOUNDSHADER, +// RAVEN BEGIN +// bdube: new events + EVENT_PLAYEFFECT, + EVENT_PLAYEFFECT_JOINT, + EVENT_STOPEFFECT, +// RAVEN END + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); +// RAVEN BEGIN + // the entity was not in the snapshot sent by server. means it either went out of PVS, or was deleted server side + // return true if the entity wishes to be deleted locally + // depending on the entity, understanding stale as just another state, or a removal is best + // ( by default, idEntity keeps the entity around after a little cleanup ) + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); +// RAVEN END + + void WriteBindToSnapshot( idBitMsgDelta &msg ) const; + void ReadBindFromSnapshot( const idBitMsgDelta &msg ); + void WriteColorToSnapshot( idBitMsgDelta &msg ) const; + void ReadColorFromSnapshot( const idBitMsgDelta &msg ); + void WriteGUIToSnapshot( idBitMsgDelta &msg ) const; + void ReadGUIFromSnapshot( const idBitMsgDelta &msg ); + + void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ClientSendEvent( int eventId, const idBitMsg *msg ) const; + +// RAVEN BEGIN +// bdube: debugging + virtual void GetDebugInfo( debugInfoProc_t proc, void* userData ); +// mwhitlock: memory profiling + virtual size_t Size( void ) const; +// ddynerman: multiple arenas (for MP) + virtual void SetInstance( int newInstance ); + virtual int GetInstance( void ) const; +// ddynerman: multiple clip world support + virtual int GetClipWorld( void ) const; + virtual void SetClipWorld( int newCW ); +// scork: accessors so sound editor can indicate current-highlit ent + virtual int GetRefSoundShaderFlags( void ) const; + virtual void SetRefSoundShaderFlags( int iFlags ); +// twhitaker: guided projectiles + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ) { } +// RAVEN END + +protected: + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + refSound_t refSound; // used to present sound to the audio engine + idEntityPtr< idEntity > forwardDamageEnt; // damage applied to the invoking object will be forwarded to this entity + idEntityPtr< idEntity > bindMaster; // entity bound to if unequal NULL + jointHandle_t bindJoint; // joint bound to if unequal INVALID_JOINT +private: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics * physics; // physics used for this entity + int bindBody; // body bound to if unequal -1 + idEntity * teamMaster; // master of the physics team + idEntity * teamChain; // next entity in physics team + + int numPVSAreas; // number of renderer areas the entity covers + int PVSAreas[MAX_PVS_AREAS]; // numbers of the renderer areas the entity covers + + signalList_t * signals; + + int mpGUIState; // local cache to avoid systematic SetStateInt +// RAVEN BEGIN +// abahr: changed to protected for access in children classes +protected: +// ddynerman: multiple game instances + int instance; +// ddynerman: multiple collision worlds + int clipWorld; +// RAVEN END + +// RAVEN BEGIN +// bdube: made virtual + virtual bool DoDormantTests( void ); // dormant == on the active list, but out of PVS +// RAVEN END + + // physics + // initialize the default physics + void InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ); + // update visual position from the physics + void UpdateFromPhysics( bool moveBack ); + + // entity binding + bool InitBind( idEntity *master ); // initialize an entity binding + void FinishBind( void ); // finish an entity binding + void RemoveBinds( void ); // deletes any entities bound to this object + void QuitTeam( void ); // leave the current team + + void UpdatePVSAreas( void ); + +// RAVEN BEGIN +// bdube: client entities + void RemoveClientEntities ( void ); // deletes any client entities bound to this object +// RAVEN END + + // events + void Event_GetName( void ); + void Event_SetName( const char *name ); + void Event_FindTargets( void ); + void Event_ActivateTargets( idEntity *activator ); + void Event_NumTargets( void ); + void Event_GetTarget( float index ); + void Event_RandomTarget( const char *ignore ); + void Event_Bind( idEntity *master ); + void Event_BindPosition( idEntity *master ); + void Event_BindToJoint( idEntity *master, const char *jointname, float orientated ); + void Event_Unbind( void ); + void Event_RemoveBinds( void ); + void Event_SpawnBind( void ); + void Event_SetOwner( idEntity *owner ); + void Event_SetModel( const char *modelname ); + void Event_SetSkin( const char *skinname ); + void Event_GetShaderParm( int parmnum ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetColor( float red, float green, float blue ); + void Event_GetColor( void ); + void Event_IsHidden( void ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_StartSoundShader( const char *soundName, int channel ); + void Event_StopSound( int channel, int netSync ); + void Event_StartSound( const char *soundName, int channel, int netSync ); + void Event_FadeSound( int channel, float to, float over ); + void Event_GetWorldOrigin( void ); + void Event_SetWorldOrigin( idVec3 const &org ); + void Event_GetOrigin( void ); + void Event_SetOrigin( const idVec3 &org ); + void Event_GetAngles( void ); + void Event_SetAngles( const idAngles &ang ); + void Event_SetLinearVelocity( const idVec3 &velocity ); + void Event_GetLinearVelocity( void ); + void Event_SetAngularVelocity( const idVec3 &velocity ); + void Event_GetAngularVelocity( void ); + void Event_SetSize( const idVec3 &mins, const idVec3 &maxs ); + void Event_GetSize( void ); + void Event_GetMins( void ); + void Event_GetMaxs( void ); + void Event_Touches( idEntity *ent ); + void Event_SetGuiParm( const char *key, const char *val ); + void Event_SetGuiFloat( const char *key, float f ); + void Event_GetNextKey( const char *prefix, const char *lastMatch ); + void Event_SetKey( const char *key, const char *value ); + void Event_GetKey( const char *key ); + void Event_GetIntKey( const char *key ); + void Event_GetFloatKey( const char *key ); + void Event_GetVectorKey( const char *key ); + void Event_GetEntityKey( const char *key ); + void Event_RestorePosition( void ); + void Event_UpdateCameraTarget( void ); + void Event_DistanceTo( idEntity *ent ); + void Event_DistanceToPoint( const idVec3 &point ); + void Event_StartFx( const char *fx ); + void Event_WaitFrame( void ); + void Event_Wait( float time ); + void Event_HasFunction( const char *name ); + void Event_CallFunction( const char *name ); + void Event_SetNeverDormant( int enable ); + +// RAVEN BEGIN +// kfuller: added events + void Event_SetContents ( int contents ); + void Event_GetLastBlocker ( idThread *thread ); +// begisler: added + void Event_ClearSkin ( void ); +// bdube: effect events + void Event_PlayEffect ( const char* effectName, const char* boneName, bool loop ); + void Event_StopEffect ( const char* effectName ); + void Event_StopAllEffects ( void ); + void Event_GetHealth ( void ); +// bdube: mesh events + void Event_ShowSurface ( const char* surface ); + void Event_HideSurface ( const char* surface ); +// bdube: gui events + void Event_GuiEvent ( const char* eventName ); +// jscott: + void Event_PlaybackCallback ( int type, int changed, int impulse ); +// nmckenzie: get bind master + void Event_GetBindMaster ( void ); + void Event_ApplyImpulse ( idEntity *source, const idVec3 &point, const idVec3 &impulse ); +// jshepard: unbind all targets + void Event_UnbindTargets ( idEntity *activator); +// abahr: + void Event_RemoveNullTargets (); + void Event_IsA ( const char* entityDefName ); + void Event_IsSameTypeAs ( const idEntity* ent ); + void Event_MatchPrefix ( const char *prefix, const char* previousKey ); + void Event_ClearTargetList ( float destroyContents ); +// twhitaker: added - to add targets from script + void Event_AppendTarget ( idEntity *appendMe ); + void Event_RemoveTarget ( idEntity *removeMe ); +// mekberg: added + void Event_SetHealth ( float newHealth ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added inlines +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), origin, axis, loop, endOrigin, broadcast, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, vec3_origin, mat3_identity, loop, endOrigin, broadcast, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, originOffset, axisOffset, loop, endOrigin, broadcast, category, effectTint ); +} + + +ID_INLINE idPhysics *idEntity::GetPhysics( void ) const { + return physics; +} + +ID_INLINE renderEntity_t *idEntity::GetRenderEntity( void ) { + return &renderEntity; +} + +ID_INLINE int idEntity::GetModelDefHandle( void ) { + return modelDefHandle; +} + +// scork: accessors so sound editor can indicate current-highlit ent +ID_INLINE int idEntity::GetRefSoundShaderFlags( void ) const +{ + return refSound.parms.soundShaderFlags; +} + +ID_INLINE void idEntity::SetRefSoundShaderFlags( int iFlags ) +{ + refSound.parms.soundShaderFlags = iFlags; +} + + +// RAVEN END + +/* +=============================================================================== + + Animated entity base class. + +=============================================================================== +*/ + +typedef struct damageEffect_s { + jointHandle_t jointNum; + idVec3 localOrigin; + idVec3 localNormal; + int time; +// RAVEN BEGIN +// jscott: not using +// const idDeclParticle* type; +// RAVEN END + struct damageEffect_s * next; +} damageEffect_t; + +class idAnimatedEntity : public idEntity { +public: + CLASS_PROTOTYPE( idAnimatedEntity ); + + idAnimatedEntity(); + ~idAnimatedEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + virtual void Think( void ); + + void UpdateAnimation( void ); + + virtual idAnimator * GetAnimator( void ); + virtual void SetModel( const char *modelname ); + + bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + bool GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int currentTime, idVec3 &offset, idMat3 &axis ) const; + + virtual int GetDefaultSurfaceType( void ) const; + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) {} + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +// RAVEN BEGIN +// abahr: + virtual const idAnimator * GetAnimator( void ) const { return &animator; } + virtual void UpdateRenderEntityCallback(); +// RAVEN BEGIN + + enum { + EVENT_ADD_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +protected: + idAnimator animator; + damageEffect_t * damageEffects; + +// RAVEN BEGIN +// jshepard: + void Event_ClearAnims ( void ); +// RAVEN END + +private: + void Event_GetJointHandle( const char *jointname ); + void Event_ClearAllJoints( void ); + void Event_ClearJoint( jointHandle_t jointnum ); + void Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + void Event_GetJointPos( jointHandle_t jointnum ); + void Event_GetJointAngle( jointHandle_t jointnum ); + +// RAVEN BEGIN +// bdube: programmer controlled joint events + void Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ); + void Event_CollapseJoints ( const char* jointnames, const char* collapseTo ); + + + +// RAVEN END +}; + +// RAVEN BEGIN +void UpdateGuiParms( idUserInterface *gui, const idDict *args ); +// RAVEN END + +ID_INLINE float idEntity::DistanceTo ( idEntity* ent ) { + return DistanceTo ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE float idEntity::DistanceTo ( const idVec3& pos ) const { + return (pos - GetPhysics()->GetOrigin()).LengthFast ( ); +} + +ID_INLINE float idEntity::DistanceTo2d ( idEntity* ent ) { + return DistanceTo2d ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE bool idEntity::CanTakeDamage ( void ) const { + return fl.takedamage; +} + +// RAVEN BEGIN +// ddynerman: MP arena stuff +ID_INLINE int idEntity::GetInstance( void ) const { + return instance; +} + +// ddynerman: multiple collision worlds +ID_INLINE int idEntity::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idEntity::SetClipWorld( int newCW ) { + clipWorld = newCW; + if( GetPhysics() ) { + GetPhysics()->UnlinkClip(); + GetPhysics()->LinkClip(); + } +} +// RAVEN END +#endif /* !__GAME_ENTITY_H__ */ diff --git a/source/game/FreeView.cpp b/source/game/FreeView.cpp new file mode 100644 index 0000000..48987da --- /dev/null +++ b/source/game/FreeView.cpp @@ -0,0 +1,165 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +=============== +idFreeView::~idFreeView +=============== +*/ +idFreeView::~idFreeView() { + if ( physics ) { + delete physics; + physics = NULL; + } +} + +/* +=============== +idFreeView::SetFreeView +=============== +*/ +void idFreeView::SetFreeView( int clientNum ) { + trace_t trace; + idPlayer *p; + idVec3 start, end; + idClip *clipWorld; + + if ( !physics ) { + Setup(); + } + p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p ) { + PickRandomSpawn(); + return; + } + start = p->GetEyePosition(); + end = start; end[2] += 20.0f; + clipWorld = gameLocal.GetEntityClipWorld( p ); + if ( clipWorld ) { + clipWorld->Translation( trace, start, end, physics->GetClipModel(), mat3_identity, MASK_PLAYERSOLID, NULL, NULL ); + physics->SetOrigin( trace.endpos ); + } else { + assert( false ); + physics->SetOrigin( start ); + } + viewAngles = p->viewAngles; + viewAngles[2] = 0.0f; + snapAngle = true; +} + +/* +=============== +idFreeView::PickRandomSpawn +=============== +*/ +void idFreeView::PickRandomSpawn( void ) { + if ( !physics ) { + Setup(); + } + idPlayerStart *start = gameLocal.RandomSpawn(); + physics->SetOrigin( start->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, pm_normalheight.GetFloat() ) ); + viewAngles = start->GetPhysics()->GetAxis().ToAngles(); + snapAngle = true; +} + +/* +=============== +idFreeView::Fly +=============== +*/ +void idFreeView::Fly( const usercmd_t &ucmd ) { + idAngles src; + + assert( physics ); + + src[0] = SHORT2ANGLE( ucmd.angles[0] ); + src[1] = SHORT2ANGLE( ucmd.angles[1] ); + src[2] = 0.0f; + + if ( snapAngle ) { + viewAngleOffset = viewAngles - src; + snapAngle = false; + } + + viewAngles = src + viewAngleOffset; + + physics->SetPlayerInput( ucmd, viewAngles ); + physics->Evaluate( gameLocal.time - gameLocal.previousTime, gameLocal.time ); +} + +/* +=============== +idFreeView::Draw +=============== +*/ +void idFreeView::Draw( void ) { + + assert( physics ); + + view.vieworg = physics->PlayerGetOrigin(); + view.viewaxis = viewAngles.ToMat3(); + view.time = gameLocal.time; + gameLocal.CalcFov( g_fov.GetFloat(), view.fov_x, view.fov_y ); + + // free flying demo client rendering + gameLocal.mpGame.SetShaderParms( &view ); + // FIXME: player hud? draw scoreboard? + soundSystem->PlaceListener( view.vieworg, view.viewaxis, 0, gameLocal.time, "Undefined" ); + // from idPlayerView::SingleView + idCamera *portalSky = gameLocal.GetPortalSky(); + if ( portalSky ) { + renderView_t portalSkyView = view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( &view, RF_PRIMARY_VIEW ); +} + +/* +=============== +idFreeView::Setup +=============== +*/ +void idFreeView::Setup( void ) { + idClipModel *clip; + idBounds b; + + assert( !physics ); + + memset( &view, 0, sizeof( view ) ); + view.viewID = 0; + view.x = view.y = 0; + view.width = 640; + view.height = 480; + gameLocal.CalcFov( g_fov.GetFloat(), view.fov_x, view.fov_y ); + view.cramZNear = false; + + b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + clip = new idClipModel( idTraceModel( b ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + physics = new idPhysics_Player(); + physics->SetSpeed( pm_spectatespeed.GetFloat(), pm_crouchspeed.GetFloat() ); + physics->SetClipModelNoLink( clip ); + physics->SetClipMask( MASK_PLAYERSOLID ); + physics->SetMovementType( PM_SPECTATOR ); +} + +/* +=============== +idFreeView::Shutdown +=============== +*/ +void idFreeView::Shutdown( void ) { + if ( physics ) { + delete physics; + physics = NULL; + } +} + +/* +=============== +idFreeView::GetOrigin +=============== +*/ +const idVec3 &idFreeView::GetOrigin( void ) { + return view.vieworg; +} diff --git a/source/game/FreeView.h b/source/game/FreeView.h new file mode 100644 index 0000000..73b46b1 --- /dev/null +++ b/source/game/FreeView.h @@ -0,0 +1,42 @@ +#ifndef __FREEVIEW_H__ +#define __FREEVIEW_H__ + +class idPhysics_Player; + +class idFreeView { +public: + + idFreeView() { physics = NULL; snapAngle = false; } + + ~idFreeView(); + + // start free flying from this client's current position + void SetFreeView( int clientNum ); + + // pick a random spawn in the map + void PickRandomSpawn( void ); + + // update view and position + void Fly( const usercmd_t &ucmd ); + + void Draw( void ); + + bool Initialized( void ) const { return physics != NULL; } + + void Shutdown( void ); + + const idVec3 & GetOrigin( void ); + +private: + + void Setup( void ); + + renderView_t view; + idPhysics_Player *physics; + idAngles viewAngles; + + bool snapAngle; + idAngles viewAngleOffset; +}; + +#endif diff --git a/source/game/Game.def b/source/game/Game.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/source/game/Game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/source/game/Game.h b/source/game/Game.h new file mode 100644 index 0000000..8896b1e --- /dev/null +++ b/source/game/Game.h @@ -0,0 +1,718 @@ +#ifndef __GAME_H__ +#define __GAME_H__ + +/* +=============================================================================== + + Public game interface with methods to run the game. + +=============================================================================== +*/ + +// RAVEN BEGIN +// bgeisler: moved into scripts directory +// default scripts +#define SCRIPT_DEFAULTDEFS "scripts/defs.script" +#define SCRIPT_DEFAULT "scripts/main.script" +// RAVEN END +#define SCRIPT_DEFAULTFUNC "doom_main" + +struct gameReturn_t { + char sessionCommand[MAX_STRING_CHARS]; // "map", "disconnect", "victory", etc + int consistencyHash; // used to check for network game divergence + int health; + int heartRate; + int stamina; + int combat; + bool syncNextGameFrame; // used when cinematics are skipped to prevent session from simulating several game frames to + // keep the game time in sync with real time +}; + +enum allowReply_t { + ALLOW_YES = 0, + ALLOW_BADPASS, // core will prompt for password and connect again + ALLOW_NOTYET, // core will wait with transmitted message + ALLOW_NO // core will abort with transmitted message +}; + +enum escReply_t { + ESC_IGNORE = 0, // do nothing + ESC_MAIN, // start main menu GUI + ESC_GUI // set an explicit GUI +}; + +enum demoState_t { + DEMO_NONE, + DEMO_RECORDING, + DEMO_PLAYING +}; + +enum demoReliableGameMessage_t { + DEMO_RECORD_CLIENTNUM, + DEMO_RECORD_EXCLUDE, + DEMO_RECORD_COUNT +}; + +// +// these defines work for all startsounds from all entity types +// make sure to change script/doom_defs.script if you add any channels, or change their order +// +typedef enum { + SND_CHANNEL_ANY = SCHANNEL_ANY, + SND_CHANNEL_VOICE = SCHANNEL_ONE, + SND_CHANNEL_VOICE2, + SND_CHANNEL_BODY, + SND_CHANNEL_BODY2, + SND_CHANNEL_BODY3, + SND_CHANNEL_WEAPON, + SND_CHANNEL_ITEM, + SND_CHANNEL_HEART, + SND_CHANNEL_DEMONIC, + SND_CHANNEL_RADIO, + + // internal use only. not exposed to script or framecommands. + SND_CHANNEL_AMBIENT, + SND_CHANNEL_DAMAGE + +// RAVEN BEGIN +// bdube: added custom to tell us where the end of the predefined list is + , + SND_CHANNEL_POWERUP, + SND_CHANNEL_POWERUP_IDLE, + SND_CHANNEL_MP_ANNOUNCER, + SND_CHANNEL_CUSTOM +// RAVEN END +} gameSoundChannel_t; + +// RAVEN BEGIN +// bdube: forward reference +class rvClientEffect; +// RAVEN END + +struct ClientStats_t { + bool isLastPredictFrame; + bool isLagged; + bool isNewFrame; +}; + +typedef struct userOrigin_s { + idVec3 origin; + int followClient; +} userOrigin_t; + +class idGame { +public: + virtual ~idGame() {} + + // Initialize the game for the first time. +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ) = 0; +#else + virtual void Init( void ) = 0; +#endif + + // Shut down the entire game. + virtual void Shutdown( void ) = 0; + + // Set the local client number. Distinguishes listen ( == 0 ) / dedicated ( == -1 ) + virtual void SetLocalClient( int clientNum ) = 0; + + // Sets the user info for a client. + // The game can modify the user info in the returned dictionary pointer, server will forward back. + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) = 0; + + // Retrieve the game's userInfo dict for a client. + virtual const idDict * GetUserInfo( int clientNum ) = 0; + + // Sets the user info for a viewer. + // The game can modify the user info in the returned dictionary pointer. + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) = 0; + + // Checks to see if a client is active + virtual bool IsClientActive( int clientNum ) = 0; + + // The game gets a chance to alter userinfo before they are emitted to server. + virtual void ThrottleUserInfo( void ) = 0; + + // Sets the serverinfo at map loads and when it changes. + virtual void SetServerInfo( const idDict &serverInfo ) = 0; + + // The session calls this before moving the single player game to a new level. + virtual const idDict & GetPersistentPlayerInfo( int clientNum ) = 0; + + // The session calls this right before a new level is loaded. + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) = 0; + + // Loads a map and spawns all the entities. + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) = 0; + + // Loads a map from a savegame file. + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) = 0; + + // Saves the current game state, the session may have written some data to the file already. +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ) = 0; +// RAVEN END + + // Shut down the current map. + virtual void MapShutdown( void ) = 0; + + // Caches media referenced from in key/value pairs in the given dictionary. + virtual void CacheDictionaryMedia( const idDict *dict ) = 0; + + // Spawns the player entity to be used by the client. + virtual void SpawnPlayer( int clientNum ) = 0; + +// RAVEN BEGIN + // Runs a game frame, may return a session command for level changing, etc + // lastCatchupFrame is always true except if we are running several game frames in a row and this one is not the last one + // subsystems which can tolerate skipping frames will not run during those catchup frames + // several game frames in a row happen when game + renderer time goes above the tick time ( 16ms ) + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) = 0; + + virtual void MenuFrame( void ) = 0; +// RAVEN END + + // Runs a repeater frame + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ) = 0; + + // Makes rendering and sound system calls to display for a given clientNum. + virtual bool Draw( int clientNum ) = 0; + + // Let the game do it's own UI when ESCAPE is used + virtual escReply_t HandleESC( idUserInterface **gui ) = 0; + + // get the games menu if appropriate ( multiplayer ) + virtual idUserInterface * StartMenu() = 0; + + // When the game is running it's own UI fullscreen, GUI commands are passed through here + // return NULL once the fullscreen UI mode should stop, or "main" to go to main menu + virtual const char * HandleGuiCommands( const char *menuCommand ) = 0; + + // main menu commands not caught in the engine are passed here + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) = 0; + + // Early check to deny connect. + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) = 0; + + // Connects a client. + virtual void ServerClientConnect( int clientNum, const char *guid ) = 0; + + // Spawns the player entity to be used by the client. + virtual void ServerClientBegin( int clientNum ) = 0; + + // Disconnects a client and removes the player entity from the game. + virtual void ServerClientDisconnect( int clientNum ) = 0; + + // Writes initial reliable messages a client needs to recieve when first joining the game. + virtual void ServerWriteInitialReliableMessages( int clientNum ) = 0; + + // Early check to deny connect. + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) = 0; + + // Connects a client. + virtual void RepeaterClientConnect( int clientNum ) = 0; + + // Spawns the player entity to be used by the client. + virtual void RepeaterClientBegin( int clientNum ) = 0; + + // Disconnects a client and removes the player entity from the game. + virtual void RepeaterClientDisconnect( int clientNum ) = 0; + + // Writes initial reliable messages a client needs to recieve when first joining the game. + virtual void RepeaterWriteInitialReliableMessages( int clientNum ) = 0; + + // Writes a snapshot of the server game state for the given client. + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) = 0; + + // Patches the network entity states at the server with a snapshot for the given client. + virtual bool ServerApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from a client. + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Patches the network entity states at the server with a snapshot for the given client. + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from a client. + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Reads a snapshot and updates the client game state. + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) = 0; + + // Patches the network entity states at the client with a snapshot. + virtual bool ClientApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from the server. + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Runs prediction on entities at the client. + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ) = 0; + +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ) = 0; + virtual void ClientEndFrame( void ) = 0; + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ) = 0; + +// RAVEN END + + virtual bool ValidateServerSettings( const char *map, const char *gameType ) = 0; + + // Returns a summary of stats for a given client + virtual void GetClientStats( int clientNum, char *data, const int len ) = 0; + + // Switch a player to a particular team + virtual void SwitchTeam( int clientNum, int team ) = 0; + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) = 0; + + // return true to allow download from the built-in http server + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ) = 0; + +// RAVEN BEGIN +// jscott: for the effects system + virtual void StartViewEffect( int type, float time, float scale ) = 0; + virtual rvClientEffect* PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ) = 0; + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ) = 0; + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const = 0; + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) = 0; + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) = 0; +// bdube: debugging stuff + virtual void DebugSetString ( const char* name, const char* value ) = 0; + virtual void DebugSetFloat ( const char* name, float value ) = 0; + virtual void DebugSetInt ( const char* name, int value ) = 0; + virtual const char* DebugGetStatString ( const char* name ) = 0; + virtual int DebugGetStatInt ( const char* name ) = 0; + virtual float DebugGetStatFloat ( const char* name ) = 0; + virtual bool IsDebugHudActive ( void ) const = 0; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ) = 0; + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ) = 0; + virtual bool PlayerChatDisabled( int clientNum ) = 0; + virtual void SetViewComments( const char *text = 0 ) = 0; +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ) = 0; + virtual void GetPlayerClan( int clientNum, char* clan ) = 0; + virtual void SetFriend( int clientNum, bool isFriend ) = 0; + virtual const char* GetLongGametypeName( const char* gametype ) = 0; + virtual void ReceiveRemoteConsoleOutput( const char* output ) = 0; +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ) = 0; +// shouchard: for ban lists + virtual void RegisterClientGuid( int clientNum, const char *guid ) = 0; + virtual bool IsMultiplayer( void ) = 0; +// mekberg: added + virtual bool InCinematic( void ) = 0; +// mekberg: so banlist can be populated outside of multiplayer game + virtual void PopulateBanList( idUserInterface* hud ) = 0; + virtual void RemoveGuidFromBanList( const char *guid ) = 0; +// mekberg: interface + virtual void AddGuidToBanList( const char *guid ) = 0; + virtual const char* GetGuidByClientNum( int clientNum ) = 0; +// jshepard: updating player post-menu + virtual void UpdatePlayerPostMainMenu( void ) = 0; + virtual void ResetRconGuiStatus( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ) = 0; +#endif +// RAVEN END + + // Set the demo state. + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ) = 0; + + // Set the repeater state; engine will call this with true for isRepeater if this is a repeater, and true for serverIsRepeater if we are connected to a repeater + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ) = 0; + + // Writes current network info to a file (used as initial state for demo recording). + virtual void WriteNetworkInfo( idFile* file, int clientNum ) = 0; + + // Reads current network info from a file (used as initial state for demo playback). + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) = 0; + + // Let gamecode decide if it wants to accept demos from older releases of the engine. + virtual bool ValidateDemoProtocol( int minor_ref, int minor ) = 0; + + // Write a snapshot for server demo recording. + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) = 0; + + // Read a snapshot from a server demo stream. + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) = 0; + + // Write a snapshot for repeater clients. + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) = 0; + + // Done writing snapshots for repeater clients. + virtual void RepeaterEndSnapshots( void ) = 0; + + // Read a snapshot from a repeater stream. + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) = 0; + + // Get the currently followed client in demo playback + virtual int GetDemoFollowClient( void ) = 0; + + // Build a bot's userCmd + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) = 0; + + // Return the name of a gui to override the loading screen + virtual const char * GetLoadingGui( const char *mapDeclName ) = 0; + + // Set any additional gui variables needed by the loading screen + virtual void SetupLoadingGui( idUserInterface *gui ) = 0; +}; + +extern idGame * game; + + +/* +=============================================================================== + + Public game interface with methods for in-game editing. + +=============================================================================== +*/ + +struct refSound_t { +// RAVEN BEGIN + int referenceSoundHandle; // this is the interface to the sound system, created + // with idSoundWorld::AllocSoundEmitter() when needed +// RAVEN END + idVec3 origin; +// RAVEN BEGIN +// jscott: for Miles doppler + idVec3 velocity; +// RAVEN END + int listenerId; // SSF_PRIVATE_SOUND only plays if == listenerId from PlaceListener + // no spatialization will be performed if == listenerID + const idSoundShader * shader; // this really shouldn't be here, it is a holdover from single channel behavior + float diversity; // 0.0 to 1.0 value used to select which + // samples in a multi-sample list from the shader are used + bool waitfortrigger; // don't start it at spawn time + soundShaderParms_t parms; // override volume, flags, etc +}; + +enum { + TEST_PARTICLE_MODEL = 0, + TEST_PARTICLE_IMPACT, + TEST_PARTICLE_MUZZLE, + TEST_PARTICLE_FLIGHT, + TEST_PARTICLE_SELECTED +}; + +class idEntity; +class idMD5Anim; +// RAVEN BEGIN +// bdube: more forward declarations +class idProgram; +class idInterpreter; +class idThread; + +typedef void (*debugInfoProc_t) ( const char* classname, const char* name, const char* value, void *userdata ); +// RAVEN END + +// FIXME: this interface needs to be reworked but it properly separates code for the time being +class idGameEdit { +public: + virtual ~idGameEdit( void ) {} + + // These are the canonical idDict to parameter parsing routines used by both the game and tools. + virtual bool ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ); + virtual void ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ); + virtual void ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ); + + // Animation system calls for non-game based skeletal rendering. + virtual idRenderModel * ANIM_GetModelFromEntityDef( const char *classname ); + virtual const idVec3 &ANIM_GetModelOffsetFromEntityDef( const char *classname ); + virtual idRenderModel * ANIM_GetModelFromEntityDef( const idDict *args ); + virtual idRenderModel * ANIM_GetModelFromName( const char *modelName ); + virtual const idMD5Anim * ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ); +// RAVEN BEGIN +// bdube: added +// scork: added 'const' qualifiers so other stuff would compile + virtual const idMD5Anim * ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ); + virtual float ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ); + virtual const char* ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ); +// RAVEN END + virtual int ANIM_GetNumAnimsFromEntityDef( const idDict *args ); + virtual const char * ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ); + virtual const idMD5Anim * ANIM_GetAnim( const char *fileName ); + virtual int ANIM_GetLength( const idMD5Anim *anim ); + virtual int ANIM_GetNumFrames( const idMD5Anim *anim ); +// RAVEN BEGIN +// bdube: added + virtual const char * ANIM_GetFilename( const idMD5Anim* anim ); + virtual int ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ); + virtual int ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ); +// RAVEN END + virtual void ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *frame, int time, const idVec3 &offset, bool remove_origin_offset ); + virtual idRenderModel * ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ); + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant + virtual void FlushUnusedAnims( void ); +// RAVEN END + + // Articulated Figure calls for AF editor and Radiant. + virtual bool AF_SpawnEntity( const char *fileName ); + virtual void AF_UpdateEntities( const char *fileName ); + virtual void AF_UndoChanges( void ); + virtual idRenderModel * AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ); + + + // Entity selection. + virtual void ClearEntitySelection( void ); + virtual int GetSelectedEntities( idEntity *list[], int max ); + virtual void AddSelectedEntity( idEntity *ent ); + + // Selection methods + virtual void TriggerSelected(); + + // Entity defs and spawning. + virtual const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + virtual void SpawnEntityDef( const idDict &args, idEntity **ent ); + virtual idEntity * FindEntity( const char *name ) const; + virtual const char * GetUniqueEntityName( const char *classname ) const; + + // Entity methods. + virtual void EntityGetOrigin( idEntity *ent, idVec3 &org ) const; + virtual void EntityGetAxis( idEntity *ent, idMat3 &axis ) const; + virtual void EntitySetOrigin( idEntity *ent, const idVec3 &org ); + virtual void EntitySetAxis( idEntity *ent, const idMat3 &axis ); + virtual void EntityTranslate( idEntity *ent, const idVec3 &org ); +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile + virtual const idDict * EntityGetSpawnArgs( const idEntity *ent ) const; +// RAVEN END + virtual void EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ); + virtual void EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ); + virtual void EntityUpdateVisuals( idEntity *ent ); + virtual void EntitySetModel( idEntity *ent, const char *val ); + virtual void EntityStopSound( idEntity *ent ); + virtual void EntityDelete( idEntity *ent ); + virtual void EntitySetColor( idEntity *ent, const idVec3 color ); +// RAVEN BEGIN +// bdube: added + virtual const char* EntityGetName ( idEntity* ent ) const; + virtual int EntityToSafeId( idEntity* ent ) const; + virtual idEntity * EntityFromSafeId( int safeID) const; + virtual void EntitySetSkin ( idEntity *ent, const char* temp ) const; + virtual void EntityClearSkin ( idEntity *ent ) const; + virtual void EntityShow ( idEntity* ent ) const; + virtual void EntityHide ( idEntity* ent ) const; + virtual void EntityGetBounds ( idEntity* ent, idBounds &bounds ) const; + virtual int EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ); + virtual void EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ); + virtual void EntityStopAllEffects ( idEntity* ent ); + virtual void EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ); + virtual void EntityRemoveOriginOffset ( idEntity* ent, bool remove ); + virtual const char* EntityGetClassname ( idEntity* ent ) const; + virtual bool EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const; + virtual renderEntity_t* EntityGetRenderEntity ( idEntity* ent ); +// scork: accessor functions for various utils + virtual idEntity * EntityGetNextTeamEntity( idEntity *pEnt ) const; + virtual void GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL ) const; + virtual void SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum = -1 ) const; + virtual void EntitySetName( idEntity* pEnt, const char *psName ); +// RAVEN END + + // Player methods. + virtual bool PlayerIsValid() const; + virtual void PlayerGetOrigin( idVec3 &org ) const; + virtual void PlayerGetAxis( idMat3 &axis ) const; + virtual void PlayerGetViewAngles( idAngles &angles ) const; + virtual void PlayerGetEyePosition( idVec3 &org ) const; +// RAVEN BEGIN +// bdube: new game edit stuff + virtual bool PlayerTraceFromEye ( trace_t &results, float length, int contentMask ); + + // Effect methods + virtual void EffectRefreshTemplate ( const idDecl *effect ) const; + + // Light entity methods + virtual void LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ); + + // Common editing functions + virtual int GetGameTime ( int *previous = NULL ) const; + virtual void SetGameTime ( int time ) const; + virtual bool TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const; + virtual void CacheDictionaryMedia ( const idDict* dict ) const; + virtual void SetCamera ( idEntity* camera ) const; +// RAVEN BEGIN +// bdube: added + virtual int GetGameEntityRegisterTime ( void ) const; + virtual idEntity* GetFirstSpawnedEntity ( void ) const; + virtual idEntity* GetNextSpawnedEntity ( idEntity* from ) const; +// jscott: added + virtual void DrawPlaybackDebugInfo( void ); + virtual void RecordPlayback( const usercmd_t &cmd, idEntity *source ); + virtual bool PlayPlayback( void ); + virtual void ShutdownPlaybacks( void ); +// RAVEN END + + // Script methods + virtual int ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const; + virtual const char* ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const; + virtual int ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const; + virtual void* ScriptGetCurrentFunction ( idInterpreter* interpreter ) const; + virtual const char* ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const; + virtual int ScriptGetCallstackDepth ( idInterpreter* interpreter ) const; + virtual void* ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const; + virtual const char* ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const; + virtual int ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const; + virtual bool ScriptIsReturnOperator ( int op ) const; + virtual const char* ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const; + virtual idThread* ScriptGetThread ( idInterpreter* interpreter ) const; + + // Thread methods + virtual int ThreadGetCount ( void ); + virtual idThread* ThreadGetThread ( int index ); + virtual const char* ThreadGetName ( idThread* thread ); + virtual int ThreadGetNumber ( idThread* thread ); + virtual const char* ThreadGetState ( idThread* thread ); + + // Class externals for entity viewer + virtual void GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ); + + // In game map editing support. + virtual const idDict * MapGetEntityDict( const char *name ) const; + virtual void MapSave( const char *path = NULL ) const; +// RAVEN BEGIN +// rjohnson: added entity export + virtual bool MapHasExportEntities( void ) const; +// scork: simple func for the sound editor + virtual const char* MapLoaded( void ) const; +// cdr: AASTactical + virtual idAASFile* GetAASFile( int i ); +// jscott: added entries for memory tracking + virtual void PrintMemInfo( MemInfo *mi ); + virtual size_t ScriptSummary( const idCmdArgs &args ) const; + virtual size_t ClassSummary( const idCmdArgs &args ) const; + virtual size_t EntitySummary( const idCmdArgs &args ) const; +// RAVEN END + virtual void MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const ; + virtual void MapCopyDictToEntity( const char *name, const idDict *dict ) const; + virtual int MapGetUniqueMatchingKeyVals( const char *key, const char *list[], const int max ) const; + virtual void MapAddEntity( const idDict *dict ) const; + virtual int MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const; + virtual void MapRemoveEntity( const char *name ) const; + virtual void MapEntityTranslate( const char *name, const idVec3 &v ) const; +}; + +extern idGameEdit * gameEdit; + +// RAVEN BEGIN +// bdube: game logging +/* +=============================================================================== + + Game Log. + +=============================================================================== +*/ +class rvGameLog { +public: + virtual ~rvGameLog( void ) {} + + virtual void Init ( void ) = 0; + virtual void Shutdown ( void ) = 0; + + virtual void BeginFrame ( int time ) = 0; + virtual void EndFrame ( void ) = 0; + + virtual void Set ( const char* keyword, int value ) = 0; + virtual void Set ( const char* keyword, float value ) = 0; + virtual void Set ( const char* keyword, const char* value ) = 0; + virtual void Set ( const char* keyword, bool value ) = 0; + + virtual void Add ( const char* keyword, int value ) = 0; + virtual void Add ( const char* keyword, float value ) = 0; +}; + +extern rvGameLog * gameLog; + +#define GAMELOG_SET(x,y) {if(g_gamelog.GetBool())gameLog->Set ( x, y );} +#define GAMELOG_ADD(x,y) {if(g_gamelog.GetBool())gameLog->Add ( x, y );} + +#define GAMELOG_SET_IF(x,y,z) {if(g_gamelog.GetBool()&&(z))gameLog->Set ( x, y );} +#define GAMELOG_ADD_IF(x,y,z) {if(g_gamelog.GetBool()&&(z))gameLog->Add ( x, y );} + +// RAVEN END + +/* +=============================================================================== + + Game API. + +=============================================================================== +*/ + +// 4: network demos +// 5: fix idNetworkSystem ( memory / DLL boundary related ) +// 6: more network demo APIs +// 7: cleanups +// 8: added some demo functions to the FS class +// 9: bump up for 1.1 patch +// 9: Q4 Gold +// 10: Patch 2 changes +// 14: 1.3 +// 26: 1.4 beta +// 30: 1.4 +// 37: 1.4.2 +const int GAME_API_VERSION = 37; + +struct gameImport_t { + + int version; // API version + idSys * sys; // non-portable system services + idCommon * common; // common + idCmdSystem * cmdSystem; // console command system + idCVarSystem * cvarSystem; // console variable system + idFileSystem * fileSystem; // file system + idNetworkSystem * networkSystem; // network system + idRenderSystem * renderSystem; // render system + idSoundSystem * soundSystem; // sound system + idRenderModelManager * renderModelManager; // render model manager + idUserInterfaceManager * uiManager; // user interface manager + idDeclManager * declManager; // declaration manager + idAASFileManager * AASFileManager; // AAS file manager + idCollisionModelManager * collisionModelManager; // collision model manager + +// RAVEN BEGIN +// jscott: + rvBSEManager * bse; // Raven effects system +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added the following members to exchange memory system data +#ifdef _RV_MEM_SYS_SUPPORT + rvHeapArena * heapArena; // main heap arena that all other heaps use + rvHeap * systemHeapArray[MAX_SYSTEM_HEAPS]; // array of pointers to rvHeaps that are common to idLib, Game, and executable +#endif +// RAVEN END +}; + +struct gameExport_t { + + int version; // API version + idGame * game; // interface to run the game + idGameEdit * gameEdit; // interface for in-game editing +// RAVEN BEGIN +// bdube: added + rvGameLog * gameLog; // interface for game logging +// RAVEN END +}; + +extern "C" { +typedef gameExport_t * (*GetGameAPI_t)( gameImport_t *import ); +} + +#endif /* !__GAME_H__ */ diff --git a/source/game/GameEdit.cpp b/source/game/GameEdit.cpp new file mode 100644 index 0000000..bc74e35 --- /dev/null +++ b/source/game/GameEdit.cpp @@ -0,0 +1,1960 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: added +#include "Effect.h" +// nmckenzie: +//#include "rvAI/AI.h" +#include "ai/AI.h" +#include "client/ClientEffect.h" +// RAVEN END + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idCursor3D ) +END_CLASS + +/* +=============== +idCursor3D::idCursor3D +=============== +*/ +idCursor3D::idCursor3D( void ) { + draggedPosition.Zero(); +} + +/* +=============== +idCursor3D::~idCursor3D +=============== +*/ +idCursor3D::~idCursor3D( void ) { +} + +/* +=============== +idCursor3D::Spawn +=============== +*/ +void idCursor3D::Spawn( void ) { +} + +/* +=============== +idCursor3D::Present +=============== +*/ +void idCursor3D::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + const idVec3 &origin = GetPhysics()->GetOrigin(); + const idMat3 &axis = GetPhysics()->GetAxis(); + gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 ); + gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 ); +} + +/* +=============== +idCursor3D::Think +=============== +*/ +void idCursor3D::Think( void ) { + if ( thinkFlags & TH_THINK ) { + drag.Evaluate( gameLocal.time ); + } + Present(); +} + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +#define MAX_DRAG_TRACE_DISTANCE 2048.0f + +/* +============== +idDragEntity::idDragEntity +============== +*/ +idDragEntity::idDragEntity( void ) { + cursor = NULL; + Clear(); +} + +/* +============== +idDragEntity::~idDragEntity +============== +*/ +idDragEntity::~idDragEntity( void ) { + StopDrag(); + selected = NULL; + delete cursor; + cursor = NULL; +} + + +/* +============== +idDragEntity::Clear +============== +*/ +void idDragEntity::Clear() { + dragEnt = NULL; + joint = INVALID_JOINT; + id = 0; + localEntityPoint.Zero(); + localPlayerPoint.Zero(); + bodyName.Clear(); + selected = NULL; +} + +/* +============== +idDragEntity::StopDrag +============== +*/ +void idDragEntity::StopDrag( void ) { + dragEnt = NULL; + if ( cursor ) { + cursor->BecomeInactive( TH_THINK ); + } +} + +/* +============== +idDragEntity::Update +============== +*/ +void idDragEntity::Update( idPlayer *player ) { + idVec3 viewPoint, origin; + idMat3 viewAxis, axis; + trace_t trace; + idEntity *newEnt; + idAngles angles; + jointHandle_t newJoint = INVALID_JOINT; + idStr newBodyName; + + player->GetViewPos( viewPoint, viewAxis ); + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + + if ( player->usercmd.buttons & BUTTON_ATTACK ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( player, trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + + newEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( newEnt ) { + + if ( newEnt->GetBindMaster() ) { + if ( newEnt->GetBindJoint() ) { + trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); + } else { + trace.c.id = newEnt->GetBindBody(); + } + newEnt = newEnt->GetBindMaster(); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( newEnt->IsType( idAFEntity_Base::GetClassType() ) && static_cast(newEnt)->IsActiveAF() ) { +// RAVEN END + idAFEntity_Base *af = static_cast(newEnt); + + // joint being dragged + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + // get the body id from the trace model id which might be a joint handle + trace.c.id = af->BodyForClipModelId( trace.c.id ); + // get the name of the body being dragged + newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( !newEnt->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + + if ( trace.c.id < 0 ) { + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + } else { + newJoint = INVALID_JOINT; + } + newBodyName = ""; + + } else { + + newJoint = INVALID_JOINT; + newEnt = NULL; + } + } + if ( newEnt ) { + dragEnt = newEnt; + selected = newEnt; + joint = newJoint; + id = trace.c.id; + bodyName = newBodyName; + + if ( !cursor ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::GetClassType() ); +// RAVEN END + } + + idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); + localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); + origin = phys->GetOrigin( id ); + axis = phys->GetAxis( id ); + localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); + + cursor->drag.Init( g_dragDamping.GetFloat() ); + cursor->drag.SetPhysics( phys, id, localEntityPoint ); + cursor->Show(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( phys->IsType( idPhysics_AF::GetClassType() ) || + phys->IsType( idPhysics_RigidBody::GetClassType() ) || + phys->IsType( idPhysics_Monster::GetClassType() ) ) { +// RAVEN END + cursor->BecomeActive( TH_THINK ); + } + } + } + } + } + + // if there is an entity selected for dragging + idEntity *drag = dragEnt.GetEntity(); + if ( drag ) { + + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag(); + return; + } + + cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); + cursor->SetAxis( viewAxis ); + + cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); + + renderEntity_t *renderEntity = drag->GetRenderEntity(); + idAnimator *dragAnimator = drag->GetAnimator(); + + if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) { + dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); + cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; + gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } else { + cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); + gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } + } + + // if there is a selected entity + if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { + // draw the bbox of the selected entity + renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); + if ( renderEntity ) { + gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); + } + } +} + +/* +============== +idDragEntity::SetSelected +============== +*/ +void idDragEntity::SetSelected( idEntity *ent ) { + selected = ent; + StopDrag(); +} + +/* +============== +idDragEntity::DeleteSelected +============== +*/ +void idDragEntity::DeleteSelected( void ) { + delete selected.GetEntity(); + selected = NULL; + StopDrag(); +} + +/* +============== +idDragEntity::BindSelected +============== +*/ +void idDragEntity::BindSelected( void ) { + int num, largestNum; + idLexer lexer; + idToken type, bodyName; + idStr key, value, bindBodyName; + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(dragEnt.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName(); + largestNum = 1; + + // parse all the bind constraints + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + key = kv->GetKey(); + key.Strip( "bindConstraint " ); + if ( sscanf( key, "bind%d", &num ) ) { + if ( num >= largestNum ) { + largestNum = num + 1; + } + } + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + lexer.ReadToken( &bodyName ); + lexer.FreeSource(); + + // if there already exists a bind constraint for this body + if ( bodyName.Icmp( bindBodyName ) == 0 ) { + // delete the bind constraint + af->spawnArgs.Delete( kv->GetKey() ); + kv = NULL; + } + + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + sprintf( key, "bindConstraint bind%d", largestNum ); + sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) ); + + af->spawnArgs.Set( key, value ); + af->spawnArgs.Set( "bind", "worldspawn" ); + af->Bind( gameLocal.world, true ); +} + +/* +============== +idDragEntity::UnbindSelected +============== +*/ +void idDragEntity::UnbindSelected( void ) { + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(selected.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + // unbind the selected entity + af->Unbind(); + + // delete all the bind constraints + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + selected.GetEntity()->spawnArgs.Delete( kv->GetKey() ); + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + } + + // delete any bind information + af->spawnArgs.Delete( "bind" ); + af->spawnArgs.Delete( "bindToJoint" ); + af->spawnArgs.Delete( "bindToBody" ); +} + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ + +/* +============== +idEditEntities::idEditEntities +============== +*/ +idEditEntities::idEditEntities( void ) { + selectableEntityClasses.Clear(); + nextSelectTime = 0; +} + +// RAVEN BEGIN +// bdube: made this special to edit entities +/* +============= +idEditEntities::FindTraceEntity +============= +*/ +idEntity* idEditEntities::FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ) { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent != skip && EntityIsSelectable ( ent ) ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} +// RAVEN END + +/* +============= +idEditEntities::SelectEntity +============= +*/ +bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) { + idVec3 end; + idEntity *ent; + + if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) { + return false; + } + + if ( gameLocal.time < nextSelectTime ) { + return true; + } + nextSelectTime = gameLocal.time + 300; + + end = origin + dir * 4096.0f; + +// RAVEN BEGIN +// bdube: more generic + ent = NULL; + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + ent = FindTraceEntity( origin, end, skip ); + if ( ent ) { + break; + } + } + if ( !ent ) { + return false; + } + + ClearSelectedEntities(); + + AddSelectedEntity( ent ); + gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() ); + + if ( gameLocal.editors & EDITOR_ENTVIEW ) { + common->InitTool ( EDITOR_ENTVIEW, &ent->spawnArgs ); + } else { + ent->ShowEditingDialog(); + } + + return true; +// RAVEN END +} + +/* +============= +idEditEntities::AddSelectedEntity +============= +*/ +void idEditEntities::AddSelectedEntity(idEntity *ent) { + ent->fl.selected = true; + selectedEntities.AddUnique(ent); +} + +/* +============== +idEditEntities::RemoveSelectedEntity +============== +*/ +void idEditEntities::RemoveSelectedEntity( idEntity *ent ) { + if ( selectedEntities.Find( ent ) ) { + selectedEntities.Remove( ent ); + } +} + +/* +============= +idEditEntities::ClearSelectedEntities +============= +*/ +void idEditEntities::ClearSelectedEntities() { + int i, count; + + count = selectedEntities.Num(); + for ( i = 0; i < count; i++ ) { + selectedEntities[i]->fl.selected = false; + } + selectedEntities.Clear(); +} + + +/* +============= +idEditEntities::EntityIsSelectable +============= +*/ +bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) { +// RAVEN BEGIN +// bdube: no matter what dont let the player or its entities be selectable + idPlayer* player; + if ( 0 != (player = gameLocal.GetLocalPlayer() )) { + if ( ent == player || ent == player->GetWeaponViewModel ( ) || ent == player->GetWeaponWorldModel ( ) ) { + return false; + } + } + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + if ( ent->IsType ( *selectableEntityClasses[i].typeInfo ) ) { +// RAVEN END + if ( text ) { + *text = selectableEntityClasses[i].textKey; + } + if ( color ) { + if ( ent->fl.selected ) { + *color = colorRed; + } else { + switch( i ) { + case 1 : + *color = colorYellow; + break; + case 2 : + *color = colorBlue; + break; + default: + *color = colorGreen; + } + } + } + return true; + } + } + return false; +} + +/* +============= +idEditEntities::DisplayEntities +============= +*/ +void idEditEntities::DisplayEntities( void ) { + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + selectableEntityClasses.Clear(); + selectedTypeInfo_t sit; + + switch( g_editEntityMode.GetInteger() ) { + case 1: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idLight::GetClassType(); +// RAVEN END + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 2: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idSound::GetClassType(); +// scork: added secondary "name" field as well (Zack request) + sit.textKey = "s_shader|name"; + selectableEntityClasses.Append( sit ); +// scork: Zack (reasonably enough) doesn't want the lights displayed when editing sounds +// sit.typeInfo = &idLight::GetClassType(); +// sit.textKey = "texture"; +// selectableEntityClasses.Append( sit ); +// RAVEN END + break; + case 3: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAFEntity_Base::GetClassType(); +// RAVEN END + sit.textKey = "articulatedFigure"; + selectableEntityClasses.Append( sit ); + break; + case 4: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idFuncEmitter::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + case 5: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAI::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 6: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 7: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; +// RAVEN BEGIN +// bdube: added fx entities + case 8: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &rvEffect::GetClassType(); +// RAVEN END + sit.textKey = "fx"; + selectableEntityClasses.Append ( sit ); + break; +// RAVEN END + default: + return; + } + + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + + viewBounds.ExpandSelf( g_editEntityDistance.GetFloat() ); +// RAVEN BEGIN +// scork: changed from 128 to 256 so we can see speaker ent descriptions before getting right up to them +// rhummer: Added cvar to adjust the distance for the text too. + viewTextBounds.ExpandSelf( g_editEntityTextDistance.GetFloat() ); +// RAVEN END + + idStr textKey; +// RAVEN BEGIN +// scork: secondary field + idStr textKey2; + idStr strOutput; +// RAVEN END + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + idVec4 color; + + textKey = ""; + if ( !EntityIsSelectable( ent, &color, &textKey ) ) { + continue; + } + +// RAVEN BEGIN +// scork: handle optional secondary field + textKey2 = ""; + int iIndex = textKey.Find('|'); + if (iIndex >= 0) + { + textKey2 = textKey.Mid ( iIndex+1, textKey.Length()-(iIndex+1) ); // hmmm, they emulate 99% of MS CString but don't have a single-param Mid() func? + textKey = textKey.Left( iIndex ); + } +// RAVEN END + + + bool drawArrows = false; +// RAVEN BEGIN +// bdube: added + bool drawDirection = false; +// RAVEN END +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetType() == &idAFEntity_Base::GetClassType() ) { +// RAVEN END + if ( !static_cast(ent)->IsActiveAF() ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &idSound::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) + { + drawArrows = true; + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag |= SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + else + { + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag &= ~SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + ent->UpdateSound(); + const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) ); + if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) { + color.Set( 1.0f, 0.0f, 1.0f, 1.0f ); + } +// RAVEN BEGIN +// bdube: added +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &rvEffect::GetClassType() ) { + drawDirection = true; + if ( ent->fl.selected ) { + drawArrows = true; + } + } else if ( ent->GetType() == &idFuncEmitter::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) { + drawArrows = true; + } + } + + if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + continue; + } + + gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) ); + if ( drawArrows ) { + idVec3 start = ent->GetPhysics()->GetOrigin(); + idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 1, 0, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * +20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * +20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * -20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis ); + } + +// RAVEN BEGIN +// bdube: added + if ( drawDirection ) { + idVec3 start = ent->GetPhysics()->GetOrigin ( ); + idVec3 end = start + ent->GetPhysics()->GetAxis()[0] * 35.0f; + gameRenderWorld->DebugArrow ( colorYellow, start, end, 6 ); + } +// RAVEN END + + if ( textKey.Length() ) { +// RAVEN BEGIN +// scork: handle optional secondary field, plus only call GetString when bounds are within view + if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + strOutput = ent->spawnArgs.GetString( textKey ); + if (!textKey2.IsEmpty()) + { + strOutput += " ( "; + strOutput += ent->spawnArgs.GetString( textKey2 ); + strOutput += " )"; + } + gameRenderWorld->DrawText( strOutput.c_str(), ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 ); + } +// RAVEN END + } + } +} + + +/* +=============================================================================== + + idGameEdit + +=============================================================================== +*/ + +idGameEdit gameEditLocal; +idGameEdit * gameEdit = &gameEditLocal; + + +/* +============= +idGameEdit::GetSelectedEntities +============= +*/ +int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) { + int num = 0; + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + list[num++] = ent; + if ( num >= max ) { + break; + } + } + } + return num; +} + +/* +============= +idGameEdit::TriggerSelected +============= +*/ +void idGameEdit::TriggerSelected() { + idEntity *ent; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + } + } +} + +/* +================ +idGameEdit::ClearEntitySelection +================ +*/ +void idGameEdit::ClearEntitySelection() { + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + ent->fl.selected = false; + } +// RAVEN BEGIN +// bdube: fixed potential crash + if ( gameLocal.editEntities ) { + gameLocal.editEntities->ClearSelectedEntities(); + } +// RAVEN END +} + +/* +================ +idGameEdit::AddSelectedEntity +================ +*/ +void idGameEdit::AddSelectedEntity( idEntity *ent ) { +// RAVEN BEGIN +// mekberg: fixed crash + if ( ent && gameLocal.editEntities ) { + gameLocal.editEntities->AddSelectedEntity( ent ); + } +// RAVEN END +} + +/* +================ +idGameEdit::FindEntityDefDict +================ +*/ +const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const { + return gameLocal.FindEntityDefDict( name, makeDefault ); +} + +/* +================ +idGameEdit::SpawnEntityDef +================ +*/ +void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) { + gameLocal.SpawnEntityDef( args, ent ); +} + +/* +================ +idGameEdit::FindEntity +================ +*/ +idEntity *idGameEdit::FindEntity( const char *name ) const { + return gameLocal.FindEntity( name ); +} + +/* +============= +idGameEdit::GetUniqueEntityName + +generates a unique name for a given classname +============= +*/ +const char *idGameEdit::GetUniqueEntityName( const char *classname ) const { + int id; + static char name[1024]; + + // can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one + for( id = 0; id < MAX_GENTITIES; id++ ) { + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + if ( !gameLocal.FindEntity( name ) ) { + return name; + } + } + + // id == MAX_GENTITIES + 1, which can't be in use if we get here + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + return name; +} + +/* +================ +idGameEdit::EntityGetOrigin +================ +*/ +void idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const { + if ( ent ) { + org = ent->GetPhysics()->GetOrigin(); + } +} + +/* +================ +idGameEdit::EntityGetAxis +================ +*/ +void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const { + if ( ent ) { + axis = ent->GetPhysics()->GetAxis(); + } +} + +/* +================ +idGameEdit::EntitySetOrigin +================ +*/ +void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->SetOrigin( org ); + } +} + +/* +================ +idGameEdit::EntitySetAxis +================ +*/ +void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) { + if ( ent ) { + ent->SetAxis( axis ); + } +} + +/* +================ +idGameEdit::EntitySetColor +================ +*/ +void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) { + if ( ent ) { + ent->SetColor( color ); + } +} + +/* +================ +idGameEdit::EntityTranslate +================ +*/ +void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->GetPhysics()->Translate( org ); + } +} + +/* +================ +idGameEdit::EntityGetSpawnArgs +================ +*/ +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile +const idDict *idGameEdit::EntityGetSpawnArgs( const idEntity *ent ) const { +// RAVEN END + if ( ent ) { + return &ent->spawnArgs; + } + return NULL; +} + +/* +================ +idGameEdit::EntityUpdateChangeableSpawnArgs +================ +*/ +void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) { + if ( ent ) { + ent->UpdateChangeableSpawnArgs( dict ); + } +} + +/* +================ +idGameEdit::EntityChangeSpawnArgs +================ +*/ +void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) { + if ( ent ) { + for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) { + const idKeyValue *kv = newArgs->GetKeyVal( i ); + + if ( kv->GetValue().Length() > 0 ) { + ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() ); + } else { + ent->spawnArgs.Delete ( kv->GetKey() ); + } + } + } +} + +/* +================ +idGameEdit::EntityUpdateVisuals +================ +*/ +void idGameEdit::EntityUpdateVisuals( idEntity *ent ) { + if ( ent ) { + ent->UpdateVisuals(); + } +} + +/* +================ +idGameEdit::EntitySetModel +================ +*/ +void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) { + if ( ent ) { + ent->spawnArgs.Set( "model", val ); + ent->SetModel( val ); + } +} + +/* +================ +idGameEdit::EntityStopSound +================ +*/ +void idGameEdit::EntityStopSound( idEntity *ent ) { + if ( ent ) { + ent->StopSound( SND_CHANNEL_ANY, false ); + } +} + +/* +================ +idGameEdit::EntityDelete +================ +*/ +void idGameEdit::EntityDelete( idEntity *ent ) { + delete ent; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idGameEdit::EntityGetRenderEntity +================ +*/ +renderEntity_t* idGameEdit::EntityGetRenderEntity ( idEntity* ent ) { + return ent->GetRenderEntity(); +} + +/* +================ +idGameEdit::EntityGetName +================ +*/ +const char* idGameEdit::EntityGetName ( idEntity* ent ) const { + if ( !ent ) { + return ""; + } + return ent->GetName(); +} + +/* +================ +idGameEdit::EntityGetClassname +================ +*/ +const char* idGameEdit::EntityGetClassname ( idEntity* ent ) const { + return ent->GetType()->classname; +} + +/* +================ +idGameEdit::EntityIsDerivedFrom +================ +*/ +bool idGameEdit::EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const { + idTypeInfo* type; + type = idClass::GetClass ( classname ); + if ( !type ) { + return false; + } + + return ent->IsType ( *type ); +} + +/* +================ +idGameEdit::EntityIsValid +================ +*/ +int idGameEdit::EntityToSafeId ( idEntity* ent ) const { + if ( !ent ) { + return 0; + } + return ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameEdit::EntityFromSafeId +================ +*/ +idEntity *idGameEdit::EntityFromSafeId( int safeID ) const { + int entityNum = safeID & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( safeID >> GENTITYNUM_BITS ) ) ) { + return gameLocal.entities[ entityNum ]; + } + return NULL; +} + +/* +================ +idGameEdit::EntitySetSkin +================ +*/ +void idGameEdit::EntitySetSkin ( idEntity* ent, const char* temp ) const { + ent->SetSkin ( declManager->FindSkin ( temp ) ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityClearSkin ( idEntity* ent ) const { + ent->ClearSkin ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityShow ( idEntity* ent ) const { + ent->Show ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityHide ( idEntity* ent ) const { + ent->Hide ( ); +} + +/* +================ +idGameEdit::EntityGetBounds +================ +*/ +void idGameEdit::EntityGetBounds ( idEntity* ent, idBounds &bounds ) const { + bounds = ent->GetRenderEntity()->bounds; +} + +/* +================ +idGameEdit::EntityPlayAnim +================ +*/ +int idGameEdit::EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ) { + if ( !ent->GetAnimator ( ) ) { + return 0; + } + ent->GetAnimator()->PlayAnim ( ANIMCHANNEL_ALL, animNum, time, blendtime ); + ent->GetAnimator()->ServiceAnims ( time, time ); + return ent->GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->GetEndTime ( ); +} + +/* +================ +idGameEdit::EntitySetFrame +================ +*/ +void idGameEdit::EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ) { + idAnimator* animator; + + animator = ent->GetAnimator ( ); + if ( !animator ) { + return; + } + + animator->ClearAllAnims ( time, time ); + + // Move to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, frame, frame, 1.0f, 0 }; + animator->SetFrame ( ANIMCHANNEL_ALL, animNum, frameBlend ); +// RAVEN END + animator->ForceUpdate ( ); +} + +/* +================ +idGameEdit::EntityGetDelta +================ +*/ +void idGameEdit::EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ) { + ent->GetAnimator()->GetDelta ( fromTime, toTime, delta ); +} + +/* +================ +idGameEdit::EntityRemoveOriginOffset +================ +*/ +void idGameEdit::EntityRemoveOriginOffset ( idEntity* ent, bool remove ) { + ent->GetAnimator()->RemoveOriginOffset ( remove ); +} + +/* +================ +idGameEdit::EntityStopAllEffects +================ +*/ +void idGameEdit::EntityStopAllEffects ( idEntity* ent ) { + ent->StopAllEffects ( ); + ent->StopSound ( SND_CHANNEL_ANY, false ); +} + +// RAVEN BEGIN +// scork: some accessor functions for various utils +idEntity *idGameEdit::EntityGetNextTeamEntity( idEntity *pEnt ) const { + return pEnt->GetNextTeamEntity(); +} +void idGameEdit::GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum, idAngles *deltaViewAngles ) const +{ + game->GetPlayerInfo( v3Origin, mat3Axis, PlayerNum, deltaViewAngles ); +} + +void idGameEdit::SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum ) const +{ + game->SetPlayerInfo( v3Origin, mat3Axis, PlayerNum ); +} +void idGameEdit::EntitySetName( idEntity* pEnt, const char *psName ) +{ + pEnt->SetName( psName ); +} +// RAVEN END + +/* +================ +idGameEdit::LightSetParms +================ +*/ +void idGameEdit::LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ) { + int data; + idLight* light; + + // Switch to a light entity + light = dynamic_cast(ent); + if ( !light ) + { + return; + } + + light->ProcessEvent ( &EV_Light_SetMaxLightLevel, maxLevel ); + light->ProcessEvent ( &EV_Light_SetCurrentLightLevel, (int)currentLevel ); + + (*(float*)&data) = radius; + light->ProcessEventArgPtr ( &EV_Light_SetRadius, &data ); + + light->SetLightLevel(); +} +// RAVEN END + +/* +================ +idGameEdit::PlayerIsValid +================ +*/ +bool idGameEdit::PlayerIsValid() const { + return ( gameLocal.GetLocalPlayer() != NULL ); +} + +/* +================ +idGameEdit::PlayerGetOrigin +================ +*/ +void idGameEdit::PlayerGetOrigin( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); +} + +/* +================ +idGameEdit::PlayerGetAxis +================ +*/ +void idGameEdit::PlayerGetAxis( idMat3 &axis ) const { + axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis(); +} + +/* +================ +idGameEdit::PlayerGetViewAngles +================ +*/ +void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const { + angles = gameLocal.GetLocalPlayer()->viewAngles; +} + +/* +================ +idGameEdit::PlayerGetEyePosition +================ +*/ +void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetEyePosition(); +} + + +/* +================ +idGameEdit::MapGetEntityDict +================ +*/ +const idDict *idGameEdit::MapGetEntityDict( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + return &mapent->epairs; + } + } + return NULL; +} + +/* +================ +idGameEdit::MapSave +================ +*/ +void idGameEdit::MapSave( const char *path ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { +// RAVEN BEGIN +// rjohnson: added entity export + if (mapFile->HasExportEntities()) { + mapFile->WriteExport( (path) ? path : mapFile->GetName() ); + } else { + if ( path ) { + mapFile->Write( path, ".map"); + } else { + idStr osPath; + osPath = mapFile->GetName ( ); + osPath.DefaultFileExtension ( ".map" ); + idFile* file = fileSystem->OpenFileRead ( osPath ); + if ( file ) { + osPath = file->GetFullPath ( ); + fileSystem->CloseFile ( file ); + mapFile->Write ( osPath, ".map", false ); + } else { + mapFile->Write ( file->GetName(), ".map" ); + } + } + } +// RAVEN END + } +} + +// RAVEN BEGIN +// rjohnson: added entity export +bool idGameEdit::MapHasExportEntities( void ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { + return mapFile->HasExportEntities(); + } + return false; +} +// scork: simple query function for the sound editor +// cdr: changed to also return the full string name of the map file (still compatable as a bool test) +const char* idGameEdit::MapLoaded( void ) const { + + const char *psMapName = gameLocal.GetMapName(); + if (psMapName && psMapName[0]) { + return psMapName; + } + return 0; +} + +// cdr: AASTactical +idAASFile* idGameEdit::GetAASFile( int i ) { + if (gameLocal.GetAAS( i )) { + return gameLocal.GetAAS( i )->GetFile(); + } + return 0; +} +// RAVEN END + +/* +================ +idGameEdit::MapSetEntityKeyVal +================ +*/ +void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + mapent->epairs.Set( key, val ); + } + } +} + +/* +================ +idGameEdit::MapCopyDictToEntity +================ +*/ +void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + const char *key = kv->GetKey(); + const char *val = kv->GetValue(); + mapent->epairs.Set( key, val ); + } + } + } +} + + + +/* +================ +idGameEdit::MapGetUniqueMatchingKeyVals +================ +*/ +int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *ent = mapFile->GetEntity( i ); + if ( ent ) { + const char *k = ent->epairs.GetString( key ); + if ( k && *k && count < max ) { + list[count++] = k; + } + } + } + } + return count; +} + +/* +================ +idGameEdit::MapAddEntity +================ +*/ +void idGameEdit::MapAddEntity( const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = new idMapEntity(); + ent->epairs = *dict; + mapFile->AddEntity( ent ); + } +} + +/* +================ +idGameEdit::MapRemoveEntity +================ +*/ +void idGameEdit::MapRemoveEntity( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = mapFile->FindEntity( name ); + if ( ent ) { + mapFile->RemoveEntity( ent ); + } + } +} + + +/* +================ +idGameEdit::MapGetEntitiesMatchignClassWithString +================ +*/ +int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + int entCount = mapFile->GetNumEntities(); + for ( int i = 0 ; i < entCount; i++ ) { + idMapEntity *ent = mapFile->GetEntity(i); + if (ent) { + idStr work = ent->epairs.GetString("classname"); + if ( work.Icmp( classname ) == 0 ) { + if ( match && *match ) { + work = ent->epairs.GetString( "soundgroup" ); + if ( count < max && work.Icmp( match ) == 0 ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } else if ( count < max ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } + } + } + } + return count; +} + + +/* +================ +idGameEdit::MapEntityTranslate +================ +*/ +void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + idVec3 origin; + mapent->epairs.GetVector( "origin", "", origin ); + origin += v; + mapent->epairs.SetVector( "origin", origin ); + } + } +} + +// RAVEN BEGIN +// bdube: new game edit stuff +/* +================ +idGameEdit::PlayerTraceFromEye +================ +*/ +bool idGameEdit::PlayerTraceFromEye ( trace_t &results, float length, int contentMask ) { + idVec3 start; + idVec3 end; + idAngles angles; + + PlayerGetEyePosition( start ); + PlayerGetEyePosition( end ); + PlayerGetViewAngles ( angles ); + + end += angles.ToForward() * length; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint ( gameLocal.GetLocalPlayer(), results, start, end, contentMask, gameLocal.GetLocalPlayer() ); +// RAVEN END +} + +/* +================ +idGameEdit::EffectRefreshTemplate +================ +*/ +void idGameEdit::EffectRefreshTemplate ( const idDecl *effect ) const { + rvClientEntity* cent; + + // Restart all effects + for ( cent = gameLocal.clientSpawnedEntities.Next(); cent; cent = cent->spawnNode.Next() ) { + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex ( ) == effect->Index() ) { + clientEffect->Restart ( ); + } + } + } +} + +/* +================ +idGameEdit::GetGameTime +================ +*/ +int idGameEdit::GetGameTime ( int *previous ) const { + if ( previous ) { + *previous = gameLocal.previousTime; + } + + return gameLocal.time; +} + +/* +================ +idGameEdit::SetGameTime +================ +*/ +void idGameEdit::SetGameTime ( int time ) const { + gameLocal.time = time; + gameLocal.previousTime = time; +} + +/* +================ +idGameEdit::TracePoint +================ +*/ +bool idGameEdit::TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint( gameLocal.GetLocalPlayer(), results, start, end, contentMask, NULL ); +// RAVEN END +} + +/* +================ +idGameEdit::CacheDictionaryMedia +================ +*/ +void idGameEdit::CacheDictionaryMedia ( const idDict* dict ) const { + gameLocal.CacheDictionaryMedia ( dict ); +} + +/* +================ +idGameEdit::SetCamera +================ +*/ +void idGameEdit::SetCamera ( idEntity* camera ) const { + gameLocal.SetCamera ( dynamic_cast(camera) ); +} + +/* +================ +idGameEdit::ScriptGetStatementLineNumber +================ +*/ +int idGameEdit::ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).linenumber; +} + +/* +================ +idGameEdit::ScriptGetStatementFileName +================ +*/ +const char* idGameEdit::ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const { + return program->GetFilename ( program->GetStatement ( instructionPointer ).file ); +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).op; +} + +/* +================ +idGameEdit::ScriptGetCurrentFunction +================ +*/ +void* idGameEdit::ScriptGetCurrentFunction ( idInterpreter* interpreter ) const { + return (void*)interpreter->GetCurrentFunction ( ); +} + +/* +================ +idGameEdit::ScriptGetCurrentFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const { + if ( interpreter->GetCurrentFunction ( ) ) { + return interpreter->GetCurrentFunction ( )->Name(); + } + return ""; +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetCallstackDepth ( idInterpreter* interpreter ) const { + return interpreter->GetCallstackDepth ( ); +} + +/* +================ +idGameEdit::ScriptGetCallstackFunction +================ +*/ +void* idGameEdit::ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const { + return (void*)interpreter->GetCallstack ( )[depth].f; +} + +/* +================ +idGameEdit::ScriptGetCallstackFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].f->Name(); +} + +/* +================ +idGameEdit::ScriptGetCallstackStatement +================ +*/ +int idGameEdit::ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].s; +} + +/* +================ +idGameEdit::ScriptIsReturnOperator +================ +*/ +bool idGameEdit::ScriptIsReturnOperator ( int op ) const { + return op == OP_RETURN; +} + +/* +================ +idGameEdit::ScriptGetRegisterValue +================ +*/ +const char* idGameEdit::ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const { + static char value[4096]; + idStr out; + + value[0] = '\0'; + if ( interpreter->GetRegisterValue ( varname, out, callstackDepth ) ) { + idStr::snPrintf ( value, 4095, out.c_str() ); + } + + return value; +} + +/* +================ +idGameEdit::ScriptGetThread +================ +*/ +idThread* idGameEdit::ScriptGetThread ( idInterpreter* interpreter ) const { + return interpreter->GetThread(); +} + +/* +================ +idGameEdit::ThreadGetCount +================ +*/ +int idGameEdit::ThreadGetCount ( void ) { + return idThread::GetThreads().Num(); +} + +/* +================ +idGameEdit::ThreadGetThread +================ +*/ +idThread* idGameEdit::ThreadGetThread ( int index ) { + return idThread::GetThreads()[index]; +} + +/* +================ +idGameEdit::ThreadGetName +================ +*/ +const char* idGameEdit::ThreadGetName ( idThread* thread ) { + return thread->GetThreadName ( ); +} + +/* +================ +idGameEdit::ThreadGetNumber +================ +*/ +int idGameEdit::ThreadGetNumber ( idThread* thread ) { + return thread->GetThreadNum ( ); +} + +/* +================ +idGameEdit::ThreadGetState +================ +*/ +const char* idGameEdit::ThreadGetState ( idThread* thread ) { + if ( thread->IsDying() ) { + return "Dying"; + } else if ( thread->IsWaiting() ) { + return "Waiting"; + } else if ( thread->IsDoneProcessing() ) { + return "Stopped"; + } + + return "Running"; +} + +/* +================ +idGameEdit::GetClassDebugInfo +================ +*/ +void idGameEdit::GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ) { + const_cast( entity )->GetDebugInfo ( proc, userdata ); +} + +/* +================ +idGameEdit::GetGameEntityRegisterTime +================ +*/ +int idGameEdit::GetGameEntityRegisterTime ( void ) const { + return gameLocal.entityRegisterTime; +} + +/* +================ +idGameEdit::GetFirstSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetFirstSpawnedEntity ( void ) const { + return gameLocal.spawnedEntities.Next(); +} + +/* +================ +idGameEdit::GetNextSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetNextSpawnedEntity ( idEntity* from ) const { + if ( !from ) { + return NULL; + } + return from->spawnNode.Next(); +} + + +// RAVEN END + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant +void idGameEdit::FlushUnusedAnims ( void ) { + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END +} + +/* +=============================================================================== + + rvModviewModel + + Actor model for modview + +=============================================================================== +*/ + +class rvModviewModel : public idActor { +public: + CLASS_PROTOTYPE( rvModviewModel ); + + rvModviewModel ( void ); + +private: + + void Event_Speak ( const char* lipsync ); +}; + +CLASS_DECLARATION( idActor, rvModviewModel ) + EVENT( AI_Speak, rvModviewModel::Event_Speak ) +END_CLASS + +/* +===================== +rvModviewModel::rvModviewModel +===================== +*/ +rvModviewModel::rvModviewModel ( void ) { +} + +/* +===================== +rvModviewModel::Event_Speak +===================== +*/ +void rvModviewModel::Event_Speak ( const char* lipsync ) { + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + lipsync = spawnArgs.GetString ( lipsync ); + if ( !lipsync || !*lipsync ) { + return; + } + + if ( head ) { + head->StartLipSyncing( lipsync ); + } else { + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +// RAVEN END + diff --git a/source/game/GameEdit.h b/source/game/GameEdit.h new file mode 100644 index 0000000..e70503a --- /dev/null +++ b/source/game/GameEdit.h @@ -0,0 +1,96 @@ + +#ifndef __GAME_EDIT_H__ +#define __GAME_EDIT_H__ + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +class idCursor3D : public idEntity { +public: + CLASS_PROTOTYPE( idCursor3D ); + + idCursor3D( void ); + ~idCursor3D( void ); + + void Spawn( void ); + void Present( void ); + void Think( void ); + + idForce_Drag drag; + idVec3 draggedPosition; +}; + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +class idDragEntity { +public: + idDragEntity( void ); + ~idDragEntity( void ); + + void Clear(); + void Update( idPlayer *player ); + void SetSelected( idEntity *ent ); + idEntity * GetSelected( void ) const { return selected.GetEntity(); } + void DeleteSelected( void ); + void BindSelected( void ); + void UnbindSelected( void ); + +private: + idEntityPtr dragEnt; // entity being dragged + jointHandle_t joint; // joint being dragged + int id; // id of body being dragged + idVec3 localEntityPoint; // dragged point in entity space + idVec3 localPlayerPoint; // dragged point in player space + idStr bodyName; // name of the body being dragged + idCursor3D * cursor; // cursor entity + idEntityPtr selected; // last dragged entity + + void StopDrag( void ); +}; + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ +typedef struct selectedTypeInfo_s { + idTypeInfo *typeInfo; + idStr textKey; +} selectedTypeInfo_t; + +class idEditEntities { +public: + idEditEntities( void ); + bool SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ); + void AddSelectedEntity( idEntity *ent ); + void RemoveSelectedEntity( idEntity *ent ); + void ClearSelectedEntities( void ); + void DisplayEntities( void ); + bool EntityIsSelectable( idEntity *ent, idVec4 *color = NULL, idStr *text = NULL ); +// RAVEN BEGIN +// bdube: added + idEntity* FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ); +// RAVEN END +private: + int nextSelectTime; + idList selectableEntityClasses; + idList selectedEntities; +}; + +#endif /* !__GAME_EDIT_H__ */ diff --git a/source/game/Game_Debug.cpp b/source/game/Game_Debug.cpp new file mode 100644 index 0000000..59c1143 --- /dev/null +++ b/source/game/Game_Debug.cpp @@ -0,0 +1,372 @@ +//---------------------------------------------------------------- +// Game_Debug.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +rvGameDebug gameDebug; + +/* +=============================================================================== + + rvGameDebug + +=============================================================================== +*/ + +/* +================ +rvGameDebug::rvGameDebug +================ +*/ +rvGameDebug::rvGameDebug ( void ) { +} + +/* +================ +rvGameDebug::Init +================ +*/ +void rvGameDebug::Init ( void ) { + focusEntity = NULL; + overrideEntity = NULL; + currentHud = NULL; + memset ( &hud, 0, sizeof(hud) ); + + jumpIndex = -1; + jumpPoints.Clear ( ); +} + +/* +================ +rvGameDebug::Shutdown +================ +*/ +void rvGameDebug::Shutdown ( void ) { + nonGameState.Clear ( ); + gameStats.Clear ( ); + + currentHud = NULL; + focusEntity = NULL; + overrideEntity = NULL; + + memset ( &hud, 0, sizeof(hud) ); +} + +/* +================ +rvGameDebug::Think +================ +*/ +void rvGameDebug::BeginFrame ( void ) { + int hudIndex; + + inFrame = true; + + hudIndex = g_showDebugHud.GetInteger(); + if ( hudIndex <= 0 ) { + focusEntity = NULL; + currentHud = NULL; + return; + } + + // Update the current debug hud if the cvar has changed + if ( g_showDebugHud.IsModified() || !currentHud ) { + if ( hudIndex > DBGHUD_MAX ) { + g_showDebugHud.SetInteger( 0 ); + focusEntity = NULL; + currentHud = NULL; + return; + } + + g_showDebugHud.ClearModified( ); + + // If the debug hud hasnt been loaded yet then load it now + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->FindGui( va("guis/debug/hud%d.gui",hudIndex), true, true, true ); + + // If the hud wasnt found auto-generate one + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->Alloc(); + } + } + + // Cache the debug hud state. + currentHud = hud[hudIndex]; + } + + currentHud->ClearState ( ); + + // IF there is an override entity just use that, otherwise find one that + // is in front of the players crosshair + if ( overrideEntity ) { + focusEntity = overrideEntity; + overrideEntity = NULL; + } else { + idPlayer* player; + idVec3 start; + idVec3 end; + trace_t tr; + + player = gameLocal.GetLocalPlayer ( ); + start = player->GetEyePosition(); + end = start + player->viewAngles.ToForward() * 4096.0f; + + gameLocal.TracePoint( player, tr, start, end, MASK_SHOT_BOUNDINGBOX, player ); + if ( tr.fraction < 1.0 && tr.c.entityNum != ENTITYNUM_WORLD ) { + focusEntity = static_cast(gameLocal.entities[ tr.c.entityNum ]); + } else { + focusEntity = NULL; + } + } + + // Automatically add some basic entity information + if ( focusEntity ) { + SetInt ( "entityNumber", focusEntity->entityNumber ); + SetInt ( "entityHealth", focusEntity->health ); + SetString ( "entityName", focusEntity->name ); + SetString ( "entityClass", focusEntity->GetClassname ( ) ); + } + + // General map information + SetString ( "mapname", gameLocal.GetMapName ( ) ); + SetString ( "version", cvarSystem->GetCVarString ( "si_version" ) ); + if ( gameLocal.GetLocalPlayer() ) { + SetString ( "viewpos", gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin().ToString() ); + } +} + +/* +================ +rvGameDebug::EndFrame +================ +*/ +void rvGameDebug::EndFrame ( void ) { + inFrame = false; +} + +/* +================ +rvGameDebug::DrawHud +================ +*/ +void rvGameDebug::DrawHud ( void ) { + if ( !currentHud ) { + return; + } + + // The scratch hud displays key value pairs in a list so + // we need to push the keys into the list + if ( IsHudActive ( DBGHUD_SCRATCH ) ) { + int index; + idDict tempState; + + tempState.Copy ( currentHud->State() ); + currentHud->ClearState ( ); + + for ( index = 0; index < tempState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + + kv = tempState.GetKeyVal ( index ); + SetString ( va("scratchKey_item_%d", index ), kv->GetKey() ); + SetString ( va("scratchValue_item_%d", index ), kv->GetValue() ); + } + } + else { + int index; + for ( index = 0; index < nonGameState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + kv = nonGameState.GetKeyVal ( index ); + currentHud->SetStateString ( kv->GetKey(), kv->GetValue() ); + } + } + + // Activate the hud to ensure lists get updated and redraw it + currentHud->StateChanged ( gameLocal.time ); + currentHud->Redraw( gameLocal.time ); +} + +/* +================ +rvGameDebug::AppendList +================ +*/ +void rvGameDebug::AppendList ( const char* listname, const char* value ) { + if ( !currentHud ) { + return; + } + + int count; + char countName[1024]; + char itemName[1024]; + + idStr::snPrintf ( countName, 1023, "%sCount", listname ); + count = GetInt ( countName ); + + idStr::snPrintf ( itemName, 1023, "%s_item_%d", listname, count ); + SetString ( va("%s_item_%d", listname, count ), value ); + + SetInt ( countName, count + 1 ); +} + +/* +================ +rvGameDebug::Set +================ +*/ +void rvGameDebug::SetInt ( const char* key, int value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateInt( key, value ); + } + } else { + nonGameState.SetInt ( key, value ); + } +} + +void rvGameDebug::SetFloat ( const char* key, float value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateFloat( key, value ); + } + } else { + nonGameState.SetFloat ( key, value ); + } +} + +void rvGameDebug::SetString ( const char* key, const char* value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateString( key, value ); + } + } else { + nonGameState.Set ( key, value ); + } +} + +/* +================ +rvGameDebug::Get +================ +*/ +int rvGameDebug::GetInt ( const char* key ) { + return currentHud ? currentHud->State().GetInt ( key ) : 0; +} + +float rvGameDebug::GetFloat ( const char* key ) { + return currentHud ? currentHud->State().GetFloat ( key ) : 0.0f; +} + +const char* rvGameDebug::GetString ( const char* key ) { + return currentHud ? currentHud->State().GetString ( key ) : ""; +} + +/* +================ +rvGameDebug::SetStat +================ +*/ +void rvGameDebug::SetStatInt ( const char* key, int value ) { + gameStats.SetInt ( key, value ); +} + +void rvGameDebug::SetStatFloat ( const char* key, float value ) { + gameStats.SetFloat ( key, value ); +} + +void rvGameDebug::SetStatString ( const char* key, const char* value ) { + gameStats.Set ( key, value ); +} + +/* +================ +rvGameDebug::GetStat +================ +*/ +int rvGameDebug::GetStatInt ( const char* key ) { + return gameStats.GetInt ( key ); +} + +float rvGameDebug::GetStatFloat ( const char* key ) { + return gameStats.GetFloat ( key ); +} + +const char* rvGameDebug::GetStatString ( const char* key ) { + return gameStats.GetString ( key ); +} + + +/* +================ +rvGameDebug::JumpAdd +================ +*/ +void rvGameDebug::JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ) { + debugJumpPoint_t jump; + jump.name = name; + jump.origin = origin; + jump.angles = angles; + jumpPoints.Append ( jump ); +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( const char* name ) { + int index; + for ( index = 0; index < jumpPoints.Num(); index ++ ) { + if ( !jumpPoints[index].name.Icmp ( name ) ) { + JumpTo ( index ); + return; + } + } +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( int jumpIndex ) { + if ( jumpIndex >= jumpPoints.Num() ) { + return; + } + + jumpIndex = jumpIndex; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player ) { + player->Teleport( jumpPoints[jumpIndex].origin, jumpPoints[jumpIndex].angles, NULL ); + } +} + +/* +================ +rvGameDebug::JumpNext +================ +*/ +void rvGameDebug::JumpNext ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + 1 ) % jumpPoints.Num() ); +} + +/* +================ +rvGameDebug::JumpPrev +================ +*/ +void rvGameDebug::JumpPrev ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + jumpPoints.Num() - 1 ) % jumpPoints.Num() ); +} diff --git a/source/game/Game_Debug.h b/source/game/Game_Debug.h new file mode 100644 index 0000000..6a14e56 --- /dev/null +++ b/source/game/Game_Debug.h @@ -0,0 +1,95 @@ +//---------------------------------------------------------------- +// Game_Debug.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_DEBUG_H__ +#define __GAME_DEBUG_H__ + +#define DBGHUD_NONE (0) +#define DBGHUD_PLAYER (1<<0) +#define DBGHUD_PHYSICS (1<<1) +#define DBGHUD_AI (1<<2) +#define DBGHUD_VEHICLE (1<<3) +#define DBGHUD_PERFORMANCE (1<<4) +#define DBGHUD_FX (1<<5) +#define DBGHUD_MAPINFO (1<<6) +#define DBGHUD_AI_PERFORM (1<<7) +#define DBGHUD_SCRATCH (1<<31) +#define DBGHUD_ANY (0xFFFFFFFF) + +#define DBGHUD_MAX 32 + +typedef struct debugJumpPoint_s +{ + idStr name; + idVec3 origin; + idAngles angles; + +} debugJumpPoint_t; + +class rvGameDebug { +public: + + rvGameDebug( ); + + void Init ( void ); + void Shutdown ( void ); + void BeginFrame ( void ); + void EndFrame ( void ); + + void SetFocusEntity ( idEntity* focusEnt ); + + bool IsHudActive ( int hudMask, const idEntity* focusEnt = NULL ); + + void DrawHud ( void ); + + void AppendList ( const char* listname, const char* value ); + + void SetInt ( const char* key, int value ); + void SetFloat ( const char* key, float value ); + void SetString ( const char* key, const char* value ); + + int GetInt ( const char* key ); + float GetFloat ( const char* key ); + const char* GetString ( const char* key ); + + void SetStatInt ( const char* key, int value ); + void SetStatFloat ( const char* key, float value ); + void SetStatString ( const char* key, const char* value ); + + int GetStatInt ( const char* key ); + float GetStatFloat ( const char* key ); + const char* GetStatString ( const char* key ); + + void JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ); + void JumpTo ( const char* name ); + void JumpTo ( int jumpIndex ); + void JumpNext ( void ); + void JumpPrev ( void ); + +private: + + idList jumpPoints; + int jumpIndex; + + idEntityPtr focusEntity; + idEntityPtr overrideEntity; + idUserInterface * hud[DBGHUD_MAX+1]; + idUserInterface * currentHud; + idDict nonGameState, gameStats; + bool inFrame; +}; + +ID_INLINE bool rvGameDebug::IsHudActive ( int hudMask, const idEntity* ent ) { + return (g_showDebugHud.GetInteger() && (hudMask & (1 << (g_showDebugHud.GetInteger()-1))) && (!ent || focusEntity == ent ) ); +} + +ID_INLINE void rvGameDebug::SetFocusEntity ( idEntity* ent ) { + overrideEntity = ent; +} + +extern rvGameDebug gameDebug; + +#endif /* !__GAME_DEBUG_H__ */ diff --git a/source/game/Game_Log.cpp b/source/game/Game_Log.cpp new file mode 100644 index 0000000..e12bec9 --- /dev/null +++ b/source/game/Game_Log.cpp @@ -0,0 +1,228 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Game_Log.h" + +/* +=============================================================================== + + rvGameLog + +=============================================================================== +*/ + +rvGameLogLocal gameLogLocal; +rvGameLog* gameLog = &gameLogLocal; + +/* +================ +rvGameLogLocal::rvGameLogLocal +================ +*/ +rvGameLogLocal::rvGameLogLocal ( ) { + initialized = false; +} + +/* +================ +rvGameLogLocal::Init +================ +*/ +void rvGameLogLocal::Init ( void ) { + file = NULL; + indexCount = 0; + initialized = true; + + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); +} + +/* +================ +rvGameLogLocal::Shutdown +================ +*/ +void rvGameLogLocal::Shutdown ( void ) { + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); + + if ( initialized && file ) { + const char* out; + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + file->Flush ( ); + fileSystem->CloseFile ( file ); + file = NULL; + } + initialized = false; +} + +/* +================ +rvGameLogLocal::BeginFrame +================ +*/ +void rvGameLogLocal::BeginFrame ( int time ) { + // See if logging has been turned on or not + if ( g_gamelog.GetBool ( ) != initialized ) { + if ( initialized ) { + Shutdown ( ); + return; + } else { + Init ( ); + } + } else if ( !g_gamelog.GetBool ( ) ) { + return; + } +} + +/* +================ +rvGameLogLocal::EndFrame +================ +*/ +void rvGameLogLocal::EndFrame ( void ) { + int i; + const char* out; + bool wroteTime; + + // Dont do anything if not logging + if ( !g_gamelog.GetBool ( ) ) { + return; + } + + // When not in multiplayer, log the players approx origin and viewangles + if ( !gameLocal.isMultiplayer ) { + idPlayer* player; + player = gameLocal.GetLocalPlayer ( ); + if ( player ) { + Set ( "player0_origin", va("%d %d %d", (int)player->GetPhysics()->GetOrigin()[0], (int)player->GetPhysics()->GetOrigin()[1], (int)player->GetPhysics()->GetOrigin()[2] ) ); + Set ( "player0_angles_yaw", va("%g", (float)player->viewAngles[YAW] ) ); + Set ( "player0_angles_pitch", va("%g", (float)player->viewAngles[PITCH] ) ); + Set ( "player0_buttons", player->usercmd.buttons ); + Set ( "player0_health", player->health ); + Set ( "player0_armor", player->inventory.armor ); + } + } + + if ( !file ) { + idStr mapName; + idStr filename; + mapName = gameLocal.serverInfo.GetString( "si_map" ); + mapName.StripFileExtension ( ); + filename = "logs/" + mapName + "/" + cvarSystem->GetCVarString("win_username") + "_"; + + // Find a unique filename + for ( i = 0; fileSystem->ReadFile( filename + va("%06d.log", i ), NULL, NULL ) > 0; i ++ ); + + // Actually open the file now + file = fileSystem->OpenFileWrite ( filename + va("%06d.log", i ), "fs_cdpath" ); + if ( !file ) { + return; + } + + timer_fps.Stop( ); + timer_fps.Clear ( ); + timer_fps.Start ( ); + } else { + static int fpsIndex; + static float fpsValue[4]; + + timer_fps.Stop ( ); + fpsValue[(fpsIndex++)%4] = 1000.0f / (timer_fps.Milliseconds ( ) + 1); + if ( fpsIndex >= 4 ) { + GAMELOG_SET ( "fps", Min(60,(int)((int)(fpsValue[0] + fpsValue[1] + fpsValue[2] + fpsValue[3]) / 40.0f) * 10) ); + } + timer_fps.Clear ( ); + timer_fps.Start ( ); + } + + // Write out any new indexes that were added this frame + for ( ; indexCount < index.Num(); indexCount ++ ) { + const char* out; + out = va("#%d ", indexCount ); + file->Write ( out, strlen ( out ) ); + file->Write ( index[indexCount].c_str(), index[indexCount].Length() ); + file->Write ( "\r\n", 2 ); + } + + // Write out any data that was added this frame + wroteTime = false; + for ( i = frame.Num() - 1; i >= 0; i -- ) { + // TODO: filter + if ( oldframe[i] != frame[i] ) { + if ( !wroteTime ) { + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + wroteTime = true; + } + out = va(" %d \"", i ); + file->Write ( out, strlen(out) ); + file->Write ( frame[i].c_str(), frame[i].Length ( ) ); + file->Write ( "\"", 1 ); + oldframe[i] = frame[i]; + } + } + + if ( wroteTime ) { + file->Write ( "\r\n", 2 ); + file->Flush ( ); + } + + // Clear the frame for next time + for ( i = index.Num() - 1; i >= 0; i -- ) { + frame[i] = ""; + } +} + +/* +================ +rvGameLogLocal::Set +================ +*/ +void rvGameLogLocal::Set ( const char* keyword, const char* value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = value; +} + +void rvGameLogLocal::Set ( const char* keyword, int value ) { + Set ( keyword, va("%d", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, float value ) { + Set ( keyword, va("%g", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, bool value ) { + Set ( keyword, va("%d", (int)value ) ); +} + +/* +================ +rvGameLogLocal::Add +================ +*/ +void rvGameLogLocal::Add ( const char* keyword, int value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%d",atoi(frame[i].c_str()) + value ); +} + +void rvGameLogLocal::Add ( const char* keyword, float value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%g",atof(frame[i].c_str()) + value ); +} + + diff --git a/source/game/Game_Log.h b/source/game/Game_Log.h new file mode 100644 index 0000000..a76d15f --- /dev/null +++ b/source/game/Game_Log.h @@ -0,0 +1,40 @@ + +#ifndef __GAME_LOG_H__ +#define __GAME_LOG_H__ + +//============================================================================ + +class rvGameLogLocal : public rvGameLog { +public: + + rvGameLogLocal ( void ); + + virtual void Init ( void ); + virtual void Shutdown ( void ); + + virtual void BeginFrame ( int time ); + virtual void EndFrame ( void ); + + virtual void Set ( const char* keyword, int value ); + virtual void Set ( const char* keyword, float value ); + virtual void Set ( const char* keyword, const char* value ); + virtual void Set ( const char* keyword, bool value ); + + virtual void Add ( const char* keyword, int value ); + virtual void Add ( const char* keyword, float value ); + +protected: + + int lastTime; + int indexCount; + idStrList index; + idStrList frame; + idStrList oldframe; + idFile* file; + bool initialized; + idTimer timer_fps; +}; + +extern rvGameLogLocal gameLogLocal; + +#endif // __GAME_LOG_H__ diff --git a/source/game/Game_local.cpp b/source/game/Game_local.cpp new file mode 100644 index 0000000..edf22f7 --- /dev/null +++ b/source/game/Game_local.cpp @@ -0,0 +1,8508 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +#include "../bse/BSEInterface.h" +#include "Projectile.h" +#include "client/ClientEffect.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AAS_tactical.h" +#include "Game_Log.h" +// RAVEN END + +//#define UI_DEBUG 1 + +#ifdef GAME_DLL + +idSys * sys = NULL; +idCommon * common = NULL; +idCmdSystem * cmdSystem = NULL; +idCVarSystem * cvarSystem = NULL; +idFileSystem * fileSystem = NULL; +idNetworkSystem * networkSystem = NULL; +idRenderSystem * renderSystem = NULL; +idSoundSystem * soundSystem = NULL; +idRenderModelManager * renderModelManager = NULL; +idUserInterfaceManager * uiManager = NULL; +idDeclManager * declManager = NULL; +idAASFileManager * AASFileManager = NULL; +idCollisionModelManager * collisionModelManager = NULL; + +// RAVEN BEGIN +// jscott: game interface to the fx system +rvBSEManager * bse = NULL; +// RAVEN END + +idCVar * idCVar::staticVars = NULL; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +idCVarHelp * idCVarHelp::staticCVarHelps = NULL; +idCVarHelp * idCVarHelp::staticCVarHelpsTail = NULL; +// RAVEN END + +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); + +#endif + +idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world + +static gameExport_t gameExport; + +// global animation lib +// RAVEN BEGIN +// jsinger: changed to a pointer to prevent its constructor from allocating +// memory before the unified allocator could be initialized +idAnimManager *animationLib = NULL; +// RAVEN END + +// the rest of the engine will only reference the "game" variable, while all local aspects stay hidden +idGameLocal gameLocal; +idGame * game = &gameLocal; // statically pointed at an idGameLocal + +const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { + "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", + "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" +}; + +/* +=========== +GetGameAPI +============ +*/ + +#if __GNUC__ >= 4 +#pragma GCC visibility push(default) +#endif +extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { + + if ( import->version == GAME_API_VERSION ) { + + // set interface pointers used by the game + sys = import->sys; + common = import->common; + cmdSystem = import->cmdSystem; + cvarSystem = import->cvarSystem; + fileSystem = import->fileSystem; + networkSystem = import->networkSystem; + renderSystem = import->renderSystem; + soundSystem = import->soundSystem; + renderModelManager = import->renderModelManager; + uiManager = import->uiManager; + declManager = import->declManager; + AASFileManager = import->AASFileManager; + collisionModelManager = import->collisionModelManager; +// RAVEN BEGIN +// jscott: import the fx system + bse = import->bse; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: import the memory system variables +#ifdef _RV_MEM_SYS_SUPPORT + ::currentHeapArena = import->heapArena; + rvSetAllSysHeaps( import->systemHeapArray ); +#endif +// RAVEN END + } + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // setup export interface + gameExport.version = GAME_API_VERSION; + gameExport.game = game; + gameExport.gameEdit = gameEdit; +// RAVEN BEGIN +// bdube: game log + gameExport.gameLog = gameLog; +// RAVEN END + + return &gameExport; +} +#if __GNUC__ >= 4 +#pragma GCC visibility pop +#endif + +/* +=========== +TestGameAPI +============ +*/ +void TestGameAPI( void ) { + gameImport_t testImport; + gameExport_t testExport; + + testImport.sys = ::sys; + testImport.common = ::common; + testImport.cmdSystem = ::cmdSystem; + testImport.cvarSystem = ::cvarSystem; + testImport.fileSystem = ::fileSystem; + testImport.networkSystem = ::networkSystem; + testImport.renderSystem = ::renderSystem; + testImport.soundSystem = ::soundSystem; + testImport.renderModelManager = ::renderModelManager; + testImport.uiManager = ::uiManager; + testImport.declManager = ::declManager; + testImport.AASFileManager = ::AASFileManager; + testImport.collisionModelManager = ::collisionModelManager; + +// RAVEN BEGIN +// jscott: import the fx system + testImport.bse = ::bse; +// RAVEN END + + testExport = *GetGameAPI( &testImport ); +} + +/* +================ +idGameLocal::BuildModList +================ +*/ +void idGameLocal::BuildModList( ) { + int i; + idStr currentMod; + + int numServers = networkSystem->GetNumScannedServers(); + + if ( filterMod >= 0 && filterMod < modList.Num() ) { + currentMod = modList[ filterMod ]; + } else { + currentMod = ""; + } + + modList.Clear(); + for (i = 0; i < numServers; i++) { + const scannedServer_t *server; + idStr modname; + + server = networkSystem->GetScannedServerInfo( i ); + + server->serverInfo.GetString( "fs_game", "", modname ); + modname.ToLower(); + modList.AddUnique( modname ); + } + + modList.Sort(); + + if ( modList.Num() > 0 && (modList[ 0 ].Cmp( "" ) == 0) ) { + modList.RemoveIndex( 0 ); + } + + filterMod = modList.Num(); + for (i = 0; i < modList.Num(); i++) { + if ( modList[ i ].Icmp( currentMod ) == 0 ) { + filterMod = i; + } + } +} + +/* +================ +FilterByMod +================ +*/ +static int FilterByMod( const int* serverIndex ) { + const scannedServer_t *server; + + if ( gameLocal.filterMod < 0 || gameLocal.filterMod >= gameLocal.modList.Num() ) { + return (int)false; + } + + server = networkSystem->GetScannedServerInfo( *serverIndex ); + + return (int)(gameLocal.modList[ gameLocal.filterMod ].Icmp( server->serverInfo.GetString( "fs_game" ) ) != 0); +} + +static sortInfo_t filterByMod = { + SC_ALL, + NULL, + FilterByMod, + "#str_123006" +}; + +/* +=========== +idGameLocal::idGameLocal +============ +*/ +idGameLocal::idGameLocal() { + Clear(); +} + +/* +=========== +idGameLocal::Clear +============ +*/ +void idGameLocal::Clear( void ) { + int i; + + serverInfo.Clear(); + numClients = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + usercmds = NULL; + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + firstFreeIndex = 0; + num_entities = 0; + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortPushers = false; + sortTeamMasters = false; + persistentLevelInfo.Clear(); + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + random.SetSeed( 0 ); + world = NULL; + frameCommandThread = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// ddynerman: multiple clip worlds + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + clientsPVS[ i ].i = -1; + clientsPVS[ i ].h = -1; + } + freePlayerPVS = false; + pvs.Shutdown(); + sessionCommand.Clear(); + locationEntities = NULL; + editEntities = NULL; + entityHash.Clear( 1024, MAX_GENTITIES ); + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + framenum = 0; + previousTime = 0; + time = 0; + vacuumAreaNum = 0; + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + mapFileName.Clear(); +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped.Clear(); +// RAVEN END + mapFile = NULL; + spawnCount = INITIAL_SPAWN_COUNT; + memset( isMapEntity, 0, sizeof( bool ) * MAX_GENTITIES ); + + camera = NULL; + +// RAVEN BEGIN +// jscott: for portal skies + portalSky = NULL; +// RAVEN END + + aasList.Clear(); + aasNames.Clear(); +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + spawnArgs.Clear(); + gravity.Set( 0, 0, -1 ); + playerPVS.i = -1; + playerPVS.h = -1; + playerConnectedAreas.i = -1; + playerConnectedAreas.h = -1; + gamestate = GAMESTATE_UNINITIALIZED; + skipCinematic = false; + influenceActive = false; + + localClientNum = 0; + isMultiplayer = false; + isServer = false; + isClient = false; + realClientTime = 0; + isNewFrame = true; + entityDefBits = 0; + + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = false; +// RITUAL END + globalMaterial = NULL; + newInfo.Clear(); + lastGUIEnt = NULL; + lastGUI = 0; + + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + eventQueue.Init(); + + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset( clientEntities, 0, sizeof( clientEntities ) ); + memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) ); + + gameDebug.Shutdown(); + gameLogLocal.Shutdown(); + currentThinkingEntity = NULL; + + memset( lagometer, 0, sizeof( lagometer ) ); + + demoState = DEMO_NONE; + serverDemo = false; + timeDemo = false; + + memset( &usercmd, 0, sizeof( usercmd ) ); + memset( &oldUsercmd, 0, sizeof( oldUsercmd ) ); + followPlayer = -1; // start free flying + demo_hud = NULL; + demo_mphud = NULL; + demo_cursor = NULL; + + demo_protocol = 0; + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + minSpawnIndex = MAX_CLIENTS; + + modList.Clear(); + filterMod = -1; + if ( networkSystem ) { + networkSystem->UseSortFunction( filterByMod, false ); + } +} + +/* +=========== +idGameLocal::Init + + initialize the game object, only happens once at startup, not each level load +============ +*/ +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +extern idHashTable *visemeTable100; +extern idHashTable *visemeTable66; +extern idHashTable *visemeTable33; +#ifdef RV_UNIFIED_ALLOCATOR +void idGameLocal::Init( void *(*allocator)(size_t size), void (*deallocator)( void *ptr ), size_t (*msize)(void *ptr) ) { +#else +void idGameLocal::Init( void ) { +#endif +// RAVEN END + const idDict *dict; + idAAS *aas; + +#ifndef GAME_DLL + + TestGameAPI(); + +#else + + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + Memory::InitAllocator(allocator, deallocator, msize); +#endif +// RAVEN END + // initialize idLib + idLib::Init(); + + // register static cvars declared in the game + idCVar::RegisterStaticVars(); + + // initialize processor specific SIMD + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + +#endif +// RAVEN BEGIN +// jsinger: these need to be initialized after the InitAllocator call above in order +// to avoid crashes when the allocator is used. + animationLib = new idAnimManager(); + visemeTable100 = new idHashTable; + visemeTable66 = new idHashTable; + visemeTable33 = new idHashTable; +// RAVEN END + + Printf( "------------- Initializing Game -------------\n" ); + Printf( "gamename: %s\n", GAME_VERSION ); + Printf( "gamedate: %s\n", __DATE__ ); + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + idCVarHelp::RegisterStatics(); + +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + idStr prefix=""; + if(cvarSystem->GetCVarBool("com_binaryDeclRead")) + { + prefix = "binary/"; + } + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator, idDeclStreamAllocator ); + //declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclFolderWrapper( prefix + "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( prefix + "af", ".af", DECL_AF ); +#else +// RAVEN END + // register game specific decl types + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator ); +// RAVEN END + // register game specific decl folders +// RAVEN BEGIN +#ifndef RV_SINGLE_DECL_FILE + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); +// bdube: not used in quake 4 +// declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); +// declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); +// declManager->RegisterDeclFolderWrapper( "newpdas", ".pda", DECL_PDA ); +#else + if(!cvarSystem->GetCVarBool("com_SingleDeclFile")) + { + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); + } + else + { + // loads the second set of decls from the file which will contain + // modles, cameras + declManager->LoadDeclsFromFile(); + declManager->FinishLoadingDecls(); + } +#endif +#endif // RV_BINARYDECLS +// RAVEN END + + cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + + Clear(); + + idEvent::Init(); +// RAVEN BEGIN +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + idClass::RegisterClasses(); +// RAVEN END + idClass::Init(); + + InitConsoleCommands(); + // load default scripts + program.Startup( SCRIPT_DEFAULT ); + + // set up the aas +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + dict = FindEntityDefDict( "aas_types", false ); +// RAVEN END + if ( !dict ) { + Error( "Unable to find entityDef for 'aas_types'" ); + } + + // allocate space for the aas + const idKeyValue *kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + aas = idAAS::Alloc(); + aasList.Append( aas ); + aasNames.Append( kv->GetValue() ); + kv = dict->MatchPrefix( "type", kv ); + } + + gamestate = GAMESTATE_NOMAP; + + Printf( "...%d aas types\n", aasList.Num() ); + Printf( "game initialized.\n" ); + Printf( "---------------------------------------------\n" ); + +// RAVEN BEGIN +// bdube: debug stuff + gameDebug.Init(); + gameLogLocal.Init(); + +// jscott: facial animation init + if( !FAS_Init( "annosoft" ) ) { + common->Warning( "Failed to load viseme file" ); + } + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_RENDER); +// shouchard: make sure ban list starts out in a known state + banListLoaded = false; + banListChanged = false; + memset( clientGuids, 0, sizeof( clientGuids ) ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + for(int i=0;iAddSortFunction( filterByMod ); +} + +/* +=========== +idGameLocal::Shutdown + + shut down the entire game +============ +*/ +void idGameLocal::Shutdown( void ) { + + int i; + + if ( !common ) { + return; + } + +// RAVEN BEGIN +// jscott: FAS + FAS_Shutdown(); +// shouchard: clean up ban list stuff + SaveBanList(); + FlushBanList(); +// RAVEN END + + Printf( "--------------- Game Shutdown ---------------\n" ); + + networkSystem->RemoveSortFunction( filterByMod ); + + mpGame.Shutdown(); + + MapShutdown(); + + aasList.DeleteContents( true ); + aasNames.Clear(); + + idAI::FreeObstacleAvoidanceNodes(); + + // shutdown the model exporter + idModelExport::Shutdown(); + + idEvent::Shutdown(); + + program.Shutdown(); + + idClass::Shutdown(); + + // clear list with forces + idForce::ClearForceList(); + + // free the program data + program.FreeData(); + + // delete the .map file + delete mapFile; + mapFile = NULL; + + // free the collision map + collisionModelManager->FreeMap( GetMapName() ); + +// RAVEN BEGIN +// jscott: free up static objects + for( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[i].Clear(); + } + + serverInfo.Clear(); + persistentLevelInfo.Clear(); + sessionCommand.Clear(); + mapFileName.Clear(); + mapFileNameStripped.Clear(); + entityUsageList.Clear(); + + newInfo.Clear(); + spawnArgs.Clear(); + shakeSounds.Clear(); + aiManager.Clear(); +// RAVEN END + + ShutdownConsoleCommands(); + + // free memory allocated by class objects + Clear(); + + // shut down the animation manager +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->Shutdown(); +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + entityUsageList.Clear(); +// RAVEN END + + freeView.Shutdown(); + + Printf( "---------------------------------------------\n" ); + + instances.DeleteContents( true ); + +#ifdef GAME_DLL + + // remove auto-completion function pointers pointing into this DLL + cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); + + // enable leak test + Mem_EnableLeakTest( "game" ); + + // shutdown idLib + idLib::ShutDown(); + +#endif +} + +/* +=========== +idGameLocal::SaveGame + +save the current player state, level name, and level state +the session may have written some data to the file already +============ +*/ +// RAVEN BEGIN +// mekberg: added saveTypes +void idGameLocal::SaveGame( idFile *f, saveType_t saveType ) { +// RAVEN END + int i; + idEntity *ent; + idEntity *link; + + //remove weapon effect entites from the world + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + + GetLocalPlayer()->weapon->PreSave(); + } + + idSaveGame savegame( f ); + + if (g_flushSave.GetBool( ) == true ) { + // force flushing with each write... for tracking down + // save game bugs. + f->ForceFlush(); + } + + savegame.WriteBuildNumber( BUILD_NUMBER ); + + // go through all entities and threads and add them to the object list + for( i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if ( ent ) { + if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { + continue; + } + for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { + savegame.AddObject( link ); + } + } + } + + idList threads; + threads = idThread::GetThreads(); + + for( i = 0; i < threads.Num(); i++ ) { + savegame.AddObject( threads[i] ); + } + +// RAVEN BEGIN +// abahr: saving clientEntities + rvClientEntity* clientEnt = NULL; + for( i = 0; i < MAX_CENTITIES; ++i ) { + clientEnt = clientEntities[ i ]; + if( !clientEnt ) { + continue; + } +// if( clientEnt->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.AddObject( clientEnt ); + } +// RAVEN END + + // write out complete object list + savegame.WriteObjectList(); + + program.Save( &savegame ); + + savegame.WriteInt( g_skill.GetInteger() ); + + savegame.WriteDict( &serverInfo ); + + savegame.WriteInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't write out userinfo. Grab from cvars +// savegame.WriteDict( &userInfo[ i ] ); +// RAVEN END +// savegame.WriteUsercmd( usercmds[ i ] ); + savegame.WriteDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.WriteObject( entities[ i ] ); + savegame.WriteInt( spawnIds[ i ] ); + } + +// RAVEN BEGIN +// abahr: more clientEntities saving + for( i = 0; i < MAX_CENTITIES; i++ ) { +// if( clientEntities[ i ] && clientEntities[ i ]->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.WriteObject( clientEntities[ i ] ); + savegame.WriteInt( clientSpawnIds[ i ] ); + } +// RAVEN END + + savegame.WriteInt( firstFreeIndex ); + savegame.WriteInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.WriteObject( world ); + + savegame.WriteInt( spawnedEntities.Num() ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( scriptObjectProxies.Num() ); + for( i = 0; i < scriptObjectProxies.Num(); ++i ) { + scriptObjectProxies[i].Save( &savegame ); + } + // used to write clientSpawnedEntities.Num() then iterate through the spawn nodes + // this is fragile however, as some elsewhere get those out of sync (mantis #70) + int countClientSpawnedEntities = 0; + for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + countClientSpawnedEntities++; + } + if ( countClientSpawnedEntities != clientSpawnedEntities.Num() ) { + common->Warning( "countClientSpawnedEntities %d != clientSpawnedEntities.Num() %d\n", countClientSpawnedEntities, clientSpawnedEntities.Num() ); + } + savegame.WriteInt( countClientSpawnedEntities ); + for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + savegame.WriteObject( clientEnt ); + } + // same as above. haven't seen a problem with that one but I can't trust it either + int countActiveEntities = 0; + for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + countActiveEntities++; + } + if ( countActiveEntities != activeEntities.Num() ) { + common->Warning( "countActiveEntities %d != activeEntities.Num() %d\n", countActiveEntities, activeEntities.Num() ); + } + savegame.WriteInt( countActiveEntities ); + for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( numEntitiesToDeactivate ); + savegame.WriteBool( sortPushers ); + savegame.WriteBool( sortTeamMasters ); + savegame.WriteDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.WriteFloat( globalShaderParms[ i ] ); + } + + savegame.WriteInt( random.GetSeed() ); + savegame.WriteObject( frameCommandThread ); + + // clip + // push + // pvs + + testmodel = NULL; +// RAVEN BEGIN +// bdube: no test fx +// testFx = NULL; +// RAVEN END + + savegame.WriteString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system save itself too. + aiManager.Save( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.WriteInt( cinematicSkipTime ); + savegame.WriteInt( cinematicStopTime ); + savegame.WriteInt( cinematicMaxSkipTime ); + savegame.WriteBool( inCinematic ); + savegame.WriteBool( skipCinematic ); + + savegame.WriteBool( isMultiplayer ); + savegame.WriteInt( gameType ); + + savegame.WriteInt( framenum ); + savegame.WriteInt( previousTime ); + savegame.WriteInt( time ); + + savegame.WriteInt( vacuumAreaNum ); + + savegame.WriteInt( entityDefBits ); + savegame.WriteBool( isServer ); + savegame.WriteBool( isClient ); +// RAVEN BEGIN + savegame.WriteBool( isListenServer ); +// RAVEN END + + savegame.WriteInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.WriteInt( realClientTime ); + savegame.WriteBool( isNewFrame ); + + savegame.WriteBool( mapCycleLoaded ); + savegame.WriteInt( spawnCount ); + + if ( !locationEntities ) { + savegame.WriteInt( 0 ); + } else { + savegame.WriteInt( gameRenderWorld->NumAreas() ); + for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { + savegame.WriteObject( locationEntities[ i ] ); + } + } + + savegame.WriteObject( camera ); + + savegame.WriteMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertActor.Save( &savegame ); + lastAIAlertEntity.Save( &savegame ); + savegame.WriteInt( lastAIAlertEntityTime ); + savegame.WriteInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.WriteDict( &spawnArgs ); + + savegame.WriteInt( playerPVS.i ); + savegame.WriteInt( playerPVS.h ); + savegame.WriteInt( playerConnectedAreas.i ); + savegame.WriteInt( playerConnectedAreas.h ); + + savegame.WriteVec3( gravity ); + + // gamestate + + savegame.WriteBool( influenceActive ); + savegame.WriteInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // write out pending events + idEvent::Save( &savegame ); + + savegame.Close(); + +// RAVEN BEGIN +// mekberg: added saveTypes and wrapped saveMessage + if ( saveType != ST_AUTO && GetLocalPlayer() && GetLocalPlayer()->GetHud() ) { + GetLocalPlayer()->SaveMessage(); + } + +// jshepard: resume weapon operation + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + GetLocalPlayer()->weapon->PostSave(); + } +// RAVEN END +} + +/* +=========== +idGameLocal::GetPersistentPlayerInfo +============ +*/ +const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { + idEntity *ent; + + persistentPlayerInfo[ clientNum ].Clear(); + ent = entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SavePersistantInfo(); + } + + return persistentPlayerInfo[ clientNum ]; +} + +/* +=========== +idGameLocal::SetPersistentPlayerInfo +============ +*/ +void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + persistentPlayerInfo[ clientNum ] = playerInfo; +} + +/* +============ +idGameLocal::Printf +============ +*/ +void idGameLocal::Printf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::DPrintf +============ +*/ +void idGameLocal::DPrintf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::Warning +============ +*/ +void idGameLocal::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); +// MERGE:FIXME - the following now gets into a recursive stack overflow when enemies are killed. Not sure why. +// nmckenzie: +/* if ( thread ) { + thread->Warning( "%s", text ); + } else {*/ + common->Warning( "%s", text ); +// } +} + +/* +============ +idGameLocal::DWarning +============ +*/ +void idGameLocal::DWarning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->DWarning( "%s", text ); + } +} + +/* +============ +idGameLocal::Error +============ +*/ +void idGameLocal::Error( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN +// scork: some model errors arrive here during validation which kills the whole process, so let's just warn about them instead... + if ( common->DoingDeclValidation() ) { + this->Warning( "%s", text ); + return; + } +// RAVEN END + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Error( "%s", text ); + } else { + common->Error( "%s", text ); + } +} + +/* +=============== +gameError +=============== +*/ +void gameError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + gameLocal.Error( "%s", text ); +} + +/* +=========== +idGameLocal::SetLocalClient +============ +*/ +void idGameLocal::SetLocalClient( int clientNum ) { + localClientNum = clientNum; +} + +/* +=========== +idGameLocal::SetUserInfo +============ +*/ +const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + bool modifiedInfo = false; + + this->isClient = isClient; + + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + idGameLocal::userInfo[ clientNum ] = userInfo; + + // server sanity + if ( !isClient ) { + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < numClients; i++ ) { + if ( i == clientNum ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_ because of client %d\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), i ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + modifiedInfo |= static_cast( entities[ clientNum ] )->UserInfoChanged(); + } + + if ( !isClient ) { + // now mark this client in game + mpGame.EnterGame( clientNum ); + } + } + + if ( modifiedInfo ) { + newInfo = idGameLocal::userInfo[ clientNum ]; + return &newInfo; + } + return NULL; +} + +/* +=========== +idGameLocal::GetUserInfo +============ +*/ +const idDict* idGameLocal::GetUserInfo( int clientNum ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return &userInfo[ clientNum ]; + } + return NULL; +} + +/* +=========== +idGameLocal::IsClientActive +============ +*/ +bool idGameLocal::IsClientActive( int clientNum ) { + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + return true; + } + + return false; +} + +/* +=========== +idGameLocal::SetServerInfo +============ +*/ +void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + bool timeLimitChanged = false; + +// RAVEN BEGIN +// mekberg: clear announcer and reschedule time announcements + if ( ( isClient || isListenServer ) && mpGame.GetGameState( ) && mpGame.GetGameState( )->GetMPGameState( ) == GAMEON && + serverInfo.GetInt( "si_timelimit" ) != _serverInfo.GetInt( "si_timelimit" ) ) { + timeLimitChanged = true; + } + + serverInfo = _serverInfo; + + if ( timeLimitChanged ) { + mpGame.ClearAnnouncerSounds( ); + mpGame.ScheduleTimeAnnouncements( ); + } +// RAVEN END + + if ( isServer ) { + + // Let our clients know the server info changed + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } +} + +/* +=================== +idGameLocal::LoadMap + +Initializes all map variables common to both save games and spawned games. +=================== +*/ +void idGameLocal::LoadMap( const char *mapName, int randseed ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PARSER); +// RAVEN END + bool sameMap = (mapFile && idStr::Icmp(mapFile->GetName(), mapName) == 0); + + networkSystem->SetLoadingText( mapName ); + + InitAsyncNetwork(); + + // these can changed based upon sp / mp + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + + if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { + // load the .map file + if ( mapFile ) { + delete mapFile; + } + mapFile = new idMapFile; + if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { + delete mapFile; + mapFile = NULL; + Error( "Couldn't load %s", mapName ); + } +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + mapFile->Resolve(); +// RAVEN END + } + mapFileName = mapFile->GetName(); + + assert(!idStr::Cmp(mapFileName, mapFile->GetName())); + +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped = mapFileName; + mapFileNameStripped.StripFileExtension(); + mapFileNameStripped.StripPath(); + + const char* entityFilter; + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + mapFileNameStripped += " "; + mapFileNameStripped += entityFilter; + } +// RAVEN END + + // load the collision map + networkSystem->SetLoadingText( common->GetLocalizedString( "#str_107668" ) ); + collisionModelManager->LoadMap( mapFile, false ); + + numClients = 0; + + // initialize all entities for this game + memset( entities, 0, sizeof( entities ) ); + usercmds = NULL; + memset( spawnIds, -1, sizeof( spawnIds ) ); + spawnCount = INITIAL_SPAWN_COUNT; + + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortTeamMasters = false; + sortPushers = false; + lastGUIEnt = NULL; + lastGUI = 0; + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset ( clientSpawnIds, -1, sizeof(clientSpawnIds ) ); + memset ( clientEntities, 0, sizeof(clientEntities) ); + firstFreeClientIndex = 0; +// RAVEN END + + globalMaterial = NULL; + + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + num_entities = MAX_CLIENTS; + firstFreeIndex = MAX_CLIENTS; + + // reset the random number generator. + random.SetSeed( isMultiplayer ? randseed : 0 ); + + camera = NULL; + world = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// RAVEN END + +// RAVEN BEGIN +// bdube: merged + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + + previousTime = 0; + time = 0; + framenum = 0; + sessionCommand = ""; + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = 0; +// RITUAL END + + vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + if ( !editEntities ) { + editEntities = new idEditEntities; + } + + if ( gameLocal.isMultiplayer ) { + gravity.Set( 0, 0, -g_mp_gravity.GetFloat() ); + } else { + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + } + + spawnArgs.Clear(); + +// RAVEN BEGIN +// nmckenzie: + //make sure we clear all reachabilities we marked as blocked! + aiManager.UnMarkAllReachBlocked(); + aiManager.Clear(); +// RAVEN END + + skipCinematic = false; + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + +// RAVEN BEGIN +// ddynerman: main world instance + PACIFIER_UPDATE; + AddInstance( 0, true ); + assert( instances.Num() == 1 && instances[ 0 ]->GetInstanceID() == 0 ); +// RAVEN END + pvs.Init(); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // Experimental use of game's PVS for reducing amount of stuff streamed. Will + // do this a bit more cleanly if I decide to keep this. + extern idPVS* pvsForStreaming; + pvsForStreaming=&pvs; +#endif +// RAVEN END + + playerPVS.i = -1; + playerConnectedAreas.i = -1; + + // load navigation system for all the different monster sizes + for( i = 0; i < aasNames.Num(); i++ ) { + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); + } + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + AI_MoveInitialize(); +// RAVEN END + + FindEntityDef( "preCacheExtras", false ); + + if ( !sameMap ) { + mapFile->RemovePrimitiveData(); + } + +// RAVEN BEGIN +// ddynerman: ambient light list + ambientLights.Clear(); +// RAVEN END +} + +/* +=================== +idGameLocal::LocalMapRestart +=================== +*/ +void idGameLocal::LocalMapRestart( int instance ) { + int i, latchSpawnCount; + + Printf( "----------- Game Map Restart (%s) ------------\n", instance == -1 ? "all instances" : va( "instance %d", instance ) ); + + // client always respawns everything, so make sure it picks up the right map entities + if( instance == -1 || isClient ) { + memset( isMapEntity, 0, sizeof(bool) * MAX_GENTITIES ); + } else { + assert( instance >= 0 && instance < instances.Num() ); + + for( int i = 0; i < instances[ instance ]->GetNumMapEntities(); i++ ) { + if ( instances[ instance ]->GetMapEntityNumber( i ) >= 0 && instances[ instance ]->GetMapEntityNumber( i ) < MAX_GENTITIES ) { + isMapEntity[ instances[ instance ]->GetMapEntityNumber( i ) ] = false; + } + } + } + + gamestate = GAMESTATE_SHUTDOWN; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); + } + } + + eventQueue.Shutdown(); + + MapClear( false, instance ); + + // clear the sound system + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + + // clear icons + iconManager->Shutdown(); + + // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId + // if we don't do that, network clients are confused and don't show any map entities + latchSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + + gamestate = GAMESTATE_STARTUP; + + program.Restart(); + + InitScriptForMap(); + + MapPopulate( instance ); + + // once the map is populated, set the spawnCount back to where it was so we don't risk any collision + // (note that if there are no players in the game, we could just leave it at it's current value) + spawnCount = latchSpawnCount; + + // setup the client entities again + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->Restart(); + } + } + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +=================== +idGameLocal::MapRestart +=================== +*/ +void idGameLocal::MapRestart( int instance ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idDict newInfo; + int i; + const idKeyValue *keyval, *keyval2; + + if ( isClient ) { +// RAVEN BEGIN +// ddynerman: check if gametype changed + SetGameType(); +// RAVEN END + LocalMapRestart( instance ); + } else { + + if ( mpGame.PickMap( "", true ) ) { + common->Warning( "map %s and gametype %s are incompatible, aborting map change", si_map.GetString(), si_gameType.GetString() ); + return; + } + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + // this has to be after the cvars are moved to the dict + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + break; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + break; + } +//RAVEN BEGIN + //asalmon: need to restart if the game type has changed but the map has not cause someone could be connecting + if( keyval->GetValue().Icmp( keyval2->GetValue() ) && (!keyval->GetKey().Icmp("si_gametype"))) + { + break; + } +//RAVEN END + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + + SetGameType(); + + mpGame.isBuyingAllowedRightNow = false; + + if ( i != newInfo.GetNumKeyVals() ) { + gameLocal.sessionCommand = "nextMap"; + } else { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + LocalMapRestart( instance ); + mpGame.MapRestart(); + } + } +} + +/* +=================== +idGameLocal::VerifyServerSettings_f +=================== +*/ +void idGameLocal::VerifyServerSettings_f( const idCmdArgs &args ) { + gameLocal.mpGame.PickMap( si_gameType.GetString() ); +} + +/* +=================== +idGameLocal::MapRestart_f +=================== +*/ +void idGameLocal::MapRestart_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running - use spawnServer\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + return; + } + + int instance = -1; + if( args.Argc() > 1 ) { + instance = atoi( args.Args( 1 ) ); + if( instance < 0 || instance >= gameLocal.GetNumInstances() ) { + common->Warning( "idGameLocal::MapRestart_f() - Invalid instance '%d' specified\n", instance ); + return; + } + gameLocal.LocalMapRestart( instance ); + return; + } + + gameLocal.MapRestart( instance ); +} + +/* +=================== +idGameLocal::NextMap +=================== +*/ +bool idGameLocal::NextMap( void ) { + const function_t *func; + idThread *thread; + idDict newInfo; + const idKeyValue *keyval, *keyval2; + int i; + const char *mapCycleList, *currentMap; + +// RAVEN BEGIN +// rjohnson: traditional map cycle +// si_mapCycle "mp/q4dm4;mp/q4dm5;mp/q4dm6" + mapCycleList = si_mapCycle.GetString(); + if ( mapCycleList && strlen( mapCycleList ) ) { + idLexer src; + idToken token, firstFound; + int numMaps = 0; + bool foundMap = false; + + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( mapCycleList, strlen( mapCycleList ), "idGameLocal::NextMap" ); + if ( src.IsLoaded() ) { + + currentMap = si_map.GetString(); + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + continue; + } + numMaps++; + + if ( numMaps == 1 ) { + // guarantee that we use a map no matter what ( or when we wrap ) + firstFound = token; + } + if ( foundMap ) { + firstFound = token; + break; + } + + if ( idStr::Icmp( token, currentMap ) == 0 ) { + foundMap = true; + } + } + + if ( firstFound != "" ) { + si_map.SetString( firstFound ); + return true; + } + } + + return false; + } +// RAVEN END + + if ( !g_mapCycle.GetString()[0] ) { + Printf( common->GetLocalizedString( "#str_104294" ) ); + return false; + } + if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) { + if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) { + Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() ); + return false; + } else { + g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) ); + } + } + + Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + if ( !func ) { + program.CompileFile( g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + } + if ( !func ) { + Printf( "Couldn't find mapcycle::cycle\n" ); + return false; + } + thread = new idThread( func ); + thread->Start(); + delete thread; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 || keyval->GetValue().Icmp( keyval2->GetValue() ) ) { + break; + } + } + return ( i != newInfo.GetNumKeyVals() ); +} + +/* +=================== +idGameLocal::NextMap_f +=================== +*/ +void idGameLocal::NextMap_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running\n" ); + return; + } + + gameLocal.NextMap( ); + // next map was either voted for or triggered by a server command - always restart + gameLocal.MapRestart( ); +} + +/* +=============== +idGameLocal::GetStartingIndexForInstance +=============== +*/ +int idGameLocal::GetStartingIndexForInstance( int instanceID ) { + if ( isServer ) { + assert( instancesEntityIndexWatermarks.Num() >= instanceID ); + if ( instanceID == 0 ) { + return MAX_CLIENTS; + } else { + // the high watermark of the previous instance is the starting index of the next one + return instancesEntityIndexWatermarks[ instanceID - 1 ]; + } + } else { + assert( instanceID == 0 ); + return clientInstanceFirstFreeIndex; + } +} + +/* +=============== +idGameLocal::ServerSetEntityIndexWatermark +keep track of the entity layout at the server - specially when there are multiple instances ( tourney ) +=============== +*/ +void idGameLocal::ServerSetEntityIndexWatermark( int instanceID ) { + if ( isClient ) { + return; + } + instancesEntityIndexWatermarks.AssureSize( instanceID + 1, MAX_CLIENTS ); + // make sure there is no drift. if a value was already set it has to match + // otherwise that means the server is repopulating with different indexes, and that would likely lead to net corruption + assert( instancesEntityIndexWatermarks[ instanceID ] == MAX_CLIENTS || instancesEntityIndexWatermarks[ instanceID ] == firstFreeIndex ); + instancesEntityIndexWatermarks[ instanceID ] = firstFreeIndex; +} + +/* +=============== +idGameLocal::ServerSetMinSpawnIndex +=============== +*/ +void idGameLocal::ServerSetMinSpawnIndex( void ) { + if ( isClient ) { + return; + } + // setup minSpawnIndex with enough headroom so gameplay entities don't cause bad offsets + // only needed on server, clients are completely slaved up to server entity layout + if ( !idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) ) { + minSpawnIndex = MAX_CLIENTS + GetNumMapEntities() * MAX_INSTANCES; + } +} + +/* +=================== +idGameLocal::MapPopulate +=================== +*/ +void idGameLocal::MapPopulate( int instance ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + if ( isMultiplayer ) { + cvarSystem->SetCVarBool( "r_skipSpecular", false ); + } + + minSpawnIndex = MAX_CLIENTS; + + // parse the key/value pairs and spawn entities +// RAVEN BEGIN +// ddynerman: instance code + // reload the instances + if ( instance == -1 ) { + int i; + firstFreeIndex = MAX_CLIENTS; + for ( i = 0; i < instances.Num(); i++ ) { + if ( instances[ i ] ) { + instances[ i ]->Restart(); + + ServerSetEntityIndexWatermark( i ); + } + } + } else { + assert( instance >= 0 && instance < instances.Num() ); + instances[ instance ]->Restart(); + } +// RAVEN END + + ServerSetMinSpawnIndex(); + + // mark location entities in all connected areas + SpreadLocations(); + + // RAVEN BEGIN + // ddynerman: prepare the list of spawn spots + InitializeSpawns(); + // RAVEN END + + // execute pending events before the very first game frame + // this makes sure the map script main() function is called + // before the physics are run so entities can bind correctly + Printf( "------------ Processing events --------------\n" ); + idEvent::ServiceEvents(); +} + +/* +=================== +idGameLocal::InitFromNewMap +=================== +*/ +void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + this->isServer = isServer; + this->isClient = isClient; +// RAVEN BEGIN +// ddynerman: listen server + this->isListenServer = isServer && !cvarSystem->GetCVarBool( "net_serverDedicated" ); +// RAVEN END + this->isMultiplayer = isServer || isClient; + + if ( this->isMultiplayer ) + gameLocal.Error( "This mod is for singleplayer only" ); + + PACIFIER_UPDATE; + +//RAVEN BEGIN +//asalmon: stats for single player + if (!this->isMultiplayer) { +#ifdef _MPBETA + return; +#else + statManager->EndGame(); +#ifdef _XENON + Live()->DeleteSPSession(true); +#endif + statManager->Shutdown(); + statManager->Init(); + statManager->BeginGame(); + statManager->ClientConnect(0); +#ifdef _XENON + Live()->CreateSPSession(); +#endif +#endif // _MPBETA + } +//RAVEN END + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "-------------- Game Map Init ----------------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->BeginLevelLoad(); +#endif + +// ddynerman: set gametype + SetGameType(); +// RAVEN END + + LoadMap( mapName, randseed ); + + InitScriptForMap(); + + MapPopulate(); + + mpGame.Reset(); + + mpGame.Precache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->EndLevelLoad(); +#endif +// RAVEN END + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "---------------------------------------------\n" ); +} + +/* +================= +idGameLocal::InitFromSaveGame +================= +*/ +bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + int num; + idEntity *ent; + idDict si; + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "---------- Game Map Init SaveGame -----------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + + idRestoreGame savegame( saveGameFile ); + + savegame.ReadBuildNumber(); + + // Create the list of all objects in the game + savegame.CreateObjects(); + + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame + if ( program.Restore( &savegame ) == false ) { + + // Abort the load process, and let the session know so that it can restart the level + // with the player persistent data. + savegame.DeleteObjects(); + program.Restart(); + + return false; + } + + // load the map needed for this savegame + LoadMap( mapName, 0 ); + + savegame.ReadInt( i ); + g_skill.SetInteger( i ); + + // precache any media specified in the map + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *mapEnt = mapFile->GetEntity( i ); + + if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { + CacheDictionaryMedia( &mapEnt->epairs ); + const char *classname = mapEnt->epairs.GetString( "classname" ); + if ( classname != '\0' ) { + FindEntityDef( classname, false ); + } + } + } + + savegame.ReadDict( &si ); + SetServerInfo( si ); + + savegame.ReadInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't read in userinfo. Grab from cvars +// savegame.ReadDict( &userInfo[ i ] ); +// RAVEN END +// savegame.ReadUsercmd( usercmds[ i ] ); + savegame.ReadDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); + savegame.ReadInt( spawnIds[ i ] ); + + // restore the entityNumber + if ( entities[ i ] != NULL ) { + entities[ i ]->entityNumber = i; + } + } + + // Precache the player +// RAVEN BEGIN +// bdube: changed so we actually cache stuff + FindEntityDef( idPlayer::GetSpawnClassname() ); + +// abahr: saving clientEntities + for( i = 0; i < MAX_CENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( clientEntities[ i ] ) ); + savegame.ReadInt( clientSpawnIds[ i ] ); + + // restore the entityNumber + if ( clientEntities[ i ] != NULL ) { + clientEntities[ i ]->entityNumber = i; + } + } +// RAVEN END + + savegame.ReadInt( firstFreeIndex ); + savegame.ReadInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.ReadObject( reinterpret_cast( world ) ); + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->spawnNode.AddToEnd( spawnedEntities ); + } + } + +// RAVEN BEGIN +// abahr: save scriptObject proxies + savegame.ReadInt( num ); + scriptObjectProxies.SetNum( num ); + for( i = 0; i < num; ++i ) { + scriptObjectProxies[i].Restore( &savegame ); + } +// abahr: save client entity stuff + rvClientEntity* clientEnt = NULL; + savegame.ReadInt( num ); + for( i = 0; i < num; ++i ) { + savegame.ReadObject( reinterpret_cast( clientEnt ) ); + assert( clientEnt ); + if ( clientEnt ) { + clientEnt->spawnNode.AddToEnd( clientSpawnedEntities ); + } + } +// RAVEN END + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->activeNode.AddToEnd( activeEntities ); + } + } + + savegame.ReadInt( numEntitiesToDeactivate ); + savegame.ReadBool( sortPushers ); + savegame.ReadBool( sortTeamMasters ); + savegame.ReadDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); + } + + savegame.ReadInt( i ); + random.SetSeed( i ); + + savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); + + // clip + // push + // pvs + + // testmodel = "" + // testFx = "" + + savegame.ReadString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system load itself too. + aiManager.Restore( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.ReadInt( cinematicSkipTime ); + savegame.ReadInt( cinematicStopTime ); + savegame.ReadInt( cinematicMaxSkipTime ); + savegame.ReadBool( inCinematic ); + savegame.ReadBool( skipCinematic ); + + savegame.ReadBool( isMultiplayer ); + savegame.ReadInt( (int &)gameType ); + + savegame.ReadInt( framenum ); + savegame.ReadInt( previousTime ); + savegame.ReadInt( time ); + + savegame.ReadInt( vacuumAreaNum ); + + savegame.ReadInt( entityDefBits ); + savegame.ReadBool( isServer ); + savegame.ReadBool( isClient ); +// RAVEN BEGIN + savegame.ReadBool( isListenServer ); +// RAVEN END + + savegame.ReadInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.ReadInt( realClientTime ); + savegame.ReadBool( isNewFrame ); + + savegame.ReadBool( mapCycleLoaded ); + savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); + if ( num ) { + if ( num != gameRenderWorld->NumAreas() ) { + savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); + } + + locationEntities = new idLocationEntity *[ num ]; + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); + } + } + + savegame.ReadObject( reinterpret_cast( camera ) ); + + savegame.ReadMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity.Restore( &savegame ); + savegame.ReadInt( lastAIAlertEntityTime ); + lastAIAlertActor.Restore( &savegame ); + savegame.ReadInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.ReadDict( &spawnArgs ); + + savegame.ReadInt( playerPVS.i ); + savegame.ReadInt( (int &)playerPVS.h ); + savegame.ReadInt( playerConnectedAreas.i ); + savegame.ReadInt( (int &)playerConnectedAreas.h ); + + savegame.ReadVec3( gravity ); + + // gamestate is restored after restoring everything else + + savegame.ReadBool( influenceActive ); + savegame.ReadInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // Read out pending events + idEvent::Restore( &savegame ); + + // call the restore functions to read out all the data from the entities + savegame.RestoreObjects(); + + mpGame.Reset(); + + mpGame.Precache(); + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); + + return true; +} + +/* +=========== +idGameLocal::MapClear +=========== +*/ +// RAVEN BEGIN +// ddynerman: multiple instances +void idGameLocal::MapClear( bool clearClients, int instance ) { +// RAVEN END + int i; + +// RAVEN BEGIN +// bdube: delete client entities first since they reference real entities + for( i = 0; i < MAX_CENTITIES; i++ ) { + // on the server we need to keep around client entities bound to entities in our instance2 + if( gameLocal.isServer && gameLocal.GetLocalPlayer() && instance != -1 && + instance != gameLocal.GetLocalPlayer()->GetInstance() && + clientEntities[ i ] && clientEntities[ i ]->GetBindMaster() && + clientEntities[ i ]->GetBindMaster()->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + delete clientEntities[ i ]; + clientEntities[ i ] = NULL; + clientSpawnIds[ i ] = -1; + } +// RAVEN END + + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { + if( instance >= 0 && entities[ i ] && entities[ i ]->GetInstance() != instance ) { + continue; + } + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); +// RAVEN BEGIN +// see FIXME in idRestoreGame::Error + entities[ i ] = NULL; +// RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + spawnedEntities.Clear(); + } +// RAVEN END + + if ( !clearClients ) { + // add back the hashes of the clients/stuff in other instances + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + entities[ i ]->spawnNode.AddToEnd( spawnedEntities ); + } +// RAVEN END + } + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; +// abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + +// RAVEN BEGIN +// ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); +// RAVEN END + + // set the free index back at MAX_CLIENTS for registering entities again + // the deletion of map entities causes the index to go down, but it may not have been left exactly at 32 + // under such conditions, the map populate that will follow may be offset + firstFreeIndex = MAX_CLIENTS; +} + +// RAVEN BEGIN +// ddynerman: instance-specific clear +void idGameLocal::InstanceClear( void ) { + // note: clears all ents EXCEPT those in the instance + int i; + + for( i = 0; i < MAX_CENTITIES; i++ ) { + delete clientEntities[ i ]; + assert( !clientEntities[ i ] ); + clientSpawnIds[ i ] = -1; + } + + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + if( i == ENTITYNUM_CLIENT || i == ENTITYNUM_WORLD ) { + continue; + } + + if( entities[ i ] && entities[ i ]->fl.persistAcrossInstances ) { + //common->DPrintf( "Instance %d: persistant: excluding ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + continue; + } + + //if( entities[ i ] ) { + //Printf( "Instance %d: CLEARING ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + //} + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); + // RAVEN BEGIN + // see FIXME in idRestoreGame::Error + entities[ i ] = NULL; + // RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; + // abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + + // RAVEN BEGIN + // ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); + + +} +// RAVEN END + +/* +=========== +idGameLocal::MapShutdown +============ +*/ +void idGameLocal::MapShutdown( void ) { + Printf( "------------ Game Map Shutdown --------------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + if ( soundSystem ) { + soundSystem->ResetListener(); + } + +// RAVEN BEGIN +// rjohnson: new blur special effect + renderSystem->ShutdownSpecialEffects(); +// RAVEN END + + // clear out camera if we're in a cinematic + if ( inCinematic ) { + camera = NULL; + inCinematic = false; + } + +// RAVEN BEGIN +// jscott: cleanup playbacks + gameEdit->ShutdownPlaybacks(); +// RAVEN END + + MapClear( true ); + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + +// RAVEN BEGIN +// jscott: make sure any spurious events are killed + idEvent::ClearEventList(); + + // reset the script to the state it was before the map was started + program.Restart(); + +// bdube: game debug + gameDebug.Shutdown( ); + gameLogLocal.Shutdown( ); +// RAVEN END + + iconManager->Shutdown(); + + pvs.Shutdown(); + +// RAVEN BEGIN +// ddynerman: MP multiple instances + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + idClipModel::ClearTraceModelCache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + idForce::ClearForceList(); +#endif +// RAVEN END + + ShutdownAsyncNetwork(); + + mapFileName.Clear(); + + gameRenderWorld = NULL; + + gamestate = GAMESTATE_NOMAP; + + Printf( "---------------------------------------------\n" ); +} + +/* +=================== +idGameLocal::DumpOggSounds +=================== +*/ +void idGameLocal::DumpOggSounds( void ) { + int i, j, k, size, totalSize; + idFile *file; + idStrList oggSounds, weaponSounds; + const idSoundShader *soundShader; + const soundShaderParms_t *parms; + idStr soundName; + + for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) { + soundShader = static_cast(declManager->DeclByIndex( DECL_SOUND, i, false )); + parms = soundShader->GetParms(); + + if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) { + + const_cast(soundShader)->EnsureNotPurged(); + + for ( j = 0; j < soundShader->GetNumSounds(); j++ ) { + soundName = soundShader->GetSound( j ); + soundName.BackSlashesToSlashes(); + + // don't OGG sounds that cause a shake because that would + // cause continuous seeking on the OGG file which is expensive + if ( parms->shakes != 0.0f ) { + shakeSounds.AddUnique( soundName ); + continue; + } + + // if not voice over or combat chatter + if ( soundName.Find( "/vo/", false ) == -1 && + soundName.Find( "/combat_chatter/", false ) == -1 && + soundName.Find( "/bfgcarnage/", false ) == -1 && + soundName.Find( "/enpro/", false ) == - 1 && + soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) { + // don't OGG weapon sounds + if ( soundName.Find( "weapon", false ) != -1 || + soundName.Find( "gun", false ) != -1 || + soundName.Find( "bullet", false ) != -1 || + soundName.Find( "bfg", false ) != -1 || + soundName.Find( "plasma", false ) != -1 ) { + weaponSounds.AddUnique( soundName ); + continue; + } + } + + for ( k = 0; k < shakeSounds.Num(); k++ ) { + if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) { + break; + } + } + if ( k < shakeSounds.Num() ) { + continue; + } + + oggSounds.AddUnique( soundName ); + } + } + } + + file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" ); + if ( file == NULL ) { + common->Warning( "Couldn't open makeogg.bat" ); + return; + } + + // list all the shake sounds + totalSize = 0; + for ( i = 0; i < shakeSounds.Num(); i++ ) { + size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL ); + totalSize += size; + shakeSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 ); + + // list all the weapon sounds + totalSize = 0; + for ( i = 0; i < weaponSounds.Num(); i++ ) { + size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL ); + totalSize += size; + weaponSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 ); + + // list commands to convert all other sounds to ogg + totalSize = 0; + for ( i = 0; i < oggSounds.Num(); i++ ) { +// RAVEN BEGIN +// rjohnson: changed path to raven's directories +// jnewquist: Use filesystem search path to get absolute file path + idStr tempFile; + idFile *f = fileSystem->OpenFileRead( oggSounds[i] ); + if ( !f ) { + continue; + } + size = f->Length(); + totalSize += size; + tempFile = f->GetFullPath(); + fileSystem->CloseFile(f); + f = NULL; + tempFile.Replace( "/", "\\" ); + +// jnewquist: prevent alterations to files relative to cdpath + const char *cdPath = cvarSystem->GetCVarString("fs_cdpath"); + const int cdPathLen = idStr::Length(cdPath); + if ( cdPathLen > 0 && idStr::Icmpn(cdPath, tempFile, cdPathLen) == 0 ) { + file->Printf( "rem Ignored file from CD path: %s\n", tempFile.c_str() ); + continue; + } + + file->Printf( "echo %d / %d\n", i, oggSounds.Num() ); + file->Printf( "k:\\utility\\oggenc -q 0 \"%s\"\n", tempFile.c_str() ); + file->Printf( "del \"%s\"\n", tempFile.c_str() ); +// RAVEN END + } + file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 ); + + fileSystem->CloseFile( file ); + + shakeSounds.Clear(); +} + +/* +=================== +idGameLocal::GetShakeSounds +=================== +*/ +void idGameLocal::GetShakeSounds( const idDict *dict ) { + const idSoundShader *soundShader; + const char *soundShaderName; + idStr soundName; + + soundShaderName = dict->GetString( "s_shader" ); + if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) { + soundShader = declManager->FindSound( soundShaderName ); + + for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) { + soundName = soundShader->GetSound( i ); + soundName.BackSlashesToSlashes(); + + shakeSounds.AddUnique( soundName ); + } + } +} + +/* +=================== +idGameLocal::CacheDictionaryMedia + +This is called after parsing an EntityDef and for each entity spawnArgs before +merging the entitydef. It could be done post-merge, but that would +avoid the fast pre-cache check associated with each entityDef +=================== +*/ +void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { + idDict spawnerArgs; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( dict == NULL ) { +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild") ) { + DumpOggSounds(); + } +#endif + return; + } + +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) { + GetShakeSounds( dict ); + } +#endif + + int numVals = dict->GetNumKeyVals(); + + for ( int i = 0; i < numVals; ++i ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + + #define MATCH(s) \ + (!kv->GetKey().Icmpn( s, strlen(s) )) + /**/ + + if ( !kv || !kv->GetValue().Length() ) { + continue; + } + + if ( MATCH( "model" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MODEL); + declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); + // precache model/animations + if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( kv->GetValue() ); + // precache .cm files only + collisionModelManager->PreCacheModel( GetMapName(), kv->GetValue() ); + } + } else if ( MATCH( "s_shader" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "snd_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "gui_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_GUI); + if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) + || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) + || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { + // unfortunate flag names, they aren't actually a gui + } else { + declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); + uiManager->FindGui( kv->GetValue().c_str(), true ); + } + } else if ( MATCH( "texture" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "mtr_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "screenShot" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "inv_icon" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "teleport" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + int teleportType = atoi( kv->GetValue() ); + const char *p = ( teleportType ) ? va( "effects/teleporter%i.fx", teleportType ) : "effects/teleporter.fx"; + declManager->FindType( DECL_EFFECT, p ); + } else if ( MATCH( "fx_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); + declManager->FindEffect( kv->GetValue() ); + } else if ( MATCH( "skin" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_SKIN, kv->GetValue() ); + } else if ( MATCH( "def_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + FindEntityDef( kv->GetValue().c_str(), false ); + } else if ( MATCH( "playback_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching playback %s\n", kv->GetValue().c_str() ); + declManager->FindPlayback( kv->GetValue() ); + } else if ( MATCH( "lipsync_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching lipsync %s\n", kv->GetValue().c_str() ); + declManager->FindLipSync( kv->GetValue() ); + declManager->FindSound ( kv->GetValue() ); + } else if ( MATCH( "icon " ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + idLexer src ( LEXFL_ALLOWPATHNAMES ); + idToken token; + idToken token2; + src.LoadMemory( kv->GetValue(), kv->GetValue().Length(), "icon" ); + + src.ReadToken ( &token ); + if ( src.CheckTokenString ( "," ) ) { + int x, y, w, h; + src.ReadToken ( &token2 ) ; + x = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + y = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + w = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + h = token2.GetIntValue ( ); + + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token, x, y, w, h ); + } else { + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token ); + } + } else if ( MATCH( "spawn_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + spawnerArgs.Set ( kv->GetKey ( ).c_str() + 6, kv->GetValue ( ) ); + } + + #undef MATCH + } + + if ( spawnerArgs.GetNumKeyVals() ) { + CacheDictionaryMedia ( &spawnerArgs ); + } +// RAVEN END +} + +/* +=========== +idGameLocal::InitScriptForMap +============ +*/ +void idGameLocal::InitScriptForMap( void ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + // create a thread to run frame commands on + frameCommandThread = new idThread(); + frameCommandThread->ManualDelete(); + frameCommandThread->SetThreadName( "frameCommands" ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_SCRIPT); + + // run the main game script function (not the level specific main) + const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_DEFAULT); + + if ( func != NULL ) { + idThread *thread = new idThread( func ); + if ( thread->Start() ) { + // thread has finished executing, so delete it + delete thread; + } + } + +// RAVEN END + +} + +/* +=========== +idGameLocal::SpawnPlayer +============ +*/ +void idGameLocal::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idEntity *ent; + idDict args; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + // they can connect + common->DPrintf( "SpawnPlayer: %i\n", clientNum ); + + args.SetInt( "spawn_entnum", clientNum ); + args.Set( "name", va( "player%d", clientNum + 1 ) ); +// RAVEN BEGIN +// bdube: changed marine class + args.Set( "classname", idPlayer::GetSpawnClassname() ); +// RAVEN END + + // This takes a really long time. + PACIFIER_UPDATE; + if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) { + Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); + } + + // make sure it's a compatible class +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); + } + + if ( clientNum >= numClients ) { + numClients = clientNum + 1; + } + + PACIFIER_UPDATE; + mpGame.SpawnPlayer( clientNum ); +} + +/* +================ +idGameLocal::GetClientByNum +================ +*/ +idPlayer *idGameLocal::GetClientByNum( int current ) const { + if ( current == MAX_CLIENTS ) { + return NULL; + } + if ( current < 0 || current >= numClients ) { + // that's a bit nasty but I suppose it has it's use + current = 0; + } + if ( entities[current] ) { + return static_cast( entities[ current ] ); + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByName +================ +*/ +idPlayer *idGameLocal::GetClientByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END +// RAVEN BEGIN +// bdube: escape codes + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { +// RAVEN END + return static_cast( ent ); + } + } + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByCmdArgs +================ +*/ +idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const { + idPlayer *player; + idStr client = args.Argv( 1 ); + if ( !client.Length() ) { + return NULL; + } + // we don't allow numeric ui_name so this can't go wrong + if ( client.IsNumeric() ) { + player = GetClientByNum( atoi( client.c_str() ) ); + } else { + player = GetClientByName( client.c_str() ); + } + if ( !player ) { + common->Printf( "Player '%s' not found\n", client.c_str() ); + } + return player; +} + +/* +================ +idGameLocal::GetClientNumByName +================ +*/ +int idGameLocal::GetClientNumByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { + return i; + } + } + } + return -1; +} + +/* +================ +idGameLocal::GetNextClientNum +================ +*/ +int idGameLocal::GetNextClientNum( int _current ) const { + int i, current; + + current = 0; + for ( i = 0; i < numClients; i++) { + current = ( _current + i + 1 ) % numClients; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ current ] && entities[ current ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return current; + } + } + + return current; +} + +/* +================ +idGameLocal::GetLocalPlayer + +Nothing in the game tic should EVER make a decision based on what the +local client number is, it shouldn't even be aware that there is a +draw phase even happening. + +localClientNum == MAX_CLIENTS when playing server demos +don't send back the wrong entity obviously +================ +*/ +idPlayer *idGameLocal::GetLocalPlayer() const { + if ( localClientNum < 0 || localClientNum == MAX_CLIENTS ) { + return NULL; + } + + if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) { + // not fully in game yet + return NULL; + } + + // through idGameLocal::MapShutdown, deleting player ents, calls idEntity::StopSound + // calls in here and triggers the assert because proper type info is already gone + //assert( gamestate == GAMESTATE_SHUTDOWN || entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ); + + return static_cast( entities[ localClientNum ] ); +} + +/* +================ +idGameLocal::SetupClientPVS +for client spectating others, get the pvs of spectated +================ +*/ +pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { + if ( player->GetPrivateCameraView() ) { + return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); + } else if ( camera ) { + return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); + } else { + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + player = static_cast( entities[ player->spectator ] ); + } + return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +================ +idGameLocal::GetSpawnCount +================ +*/ +int idGameLocal::GetSpawnCount ( void ) const { + return spawnCount; +} + +/* +================ +idGameLocal::SetupPortalSkyPVS +================ +*/ +bool idGameLocal::SetupPortalSkyPVS( idPlayer *player ) { + + int i, count, numAreas; + const int *areaNums; + bool *visibleAreas; + + if( !portalSky ) { + + return( false ); + } + + // Allocate room for the area flags + numAreas = gameRenderWorld->NumAreas(); + visibleAreas = ( bool * )_alloca( numAreas ); + memset( visibleAreas, 0, numAreas ); + + // Grab the areas the player can see.... + count = player->GetNumPVSAreas(); + areaNums = player->GetPVSAreas(); + for( i = 0; i < count; i++ ) { + + // Work out the referenced areas + gameRenderWorld->FindVisibleAreas( player->GetPhysics()->GetOrigin(), areaNums[i], visibleAreas ); + } + + // Do any of the visible areas have a skybox? + for( i = 0; i < numAreas; i++ ) { + + if( !visibleAreas[i] ) { + + continue; + } + + if( gameRenderWorld->HasSkybox( i ) ) { + + break; + } + } + + // .. if any one has a skybox component, then merge in the portal sky + return ( i != numAreas ); +} +// RAVEN END + +/* +=============== +idGameLocal::UpdateClientsPVS +=============== +*/ +void idGameLocal::UpdateClientsPVS( void ) { + int i; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[ i ] ) { + continue; + } + assert( clientsPVS[ i ].i == -1 ); + clientsPVS[ i ] = GetClientPVS( static_cast< idPlayer * >( entities[ i ] ), PVS_NORMAL ); + } +} + +/* +================ +idGameLocal::SetupPlayerPVS +================ +*/ +void idGameLocal::SetupPlayerPVS( void ) { + int i = 0; + idPlayer * player = NULL; + pvsHandle_t otherPVS, newPVS; + + UpdateClientsPVS( ); + + playerPVS.i = -1; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[i] ) { + return; + } + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + + player = static_cast( entities[ i ] ); + + if ( playerPVS.i == -1 ) { + playerPVS = clientsPVS[ i ]; + freePlayerPVS = false; // don't try to free it as long as we stick to the client PVS + } else { + otherPVS = clientsPVS[ i ]; + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS = newPVS; + freePlayerPVS = true; // that merged one will need to be freed + } + +// RAVEN BEGIN +// jscott: for portal skies + portalSkyVisible = SetupPortalSkyPVS( player ); + if ( portalSkyVisible ) { + + otherPVS = pvs.SetupCurrentPVS( portalSky->GetPVSAreas(), portalSky->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + freePlayerPVS = true; + } +// RAVEN END + + if ( playerConnectedAreas.i == -1 ) { + playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); + } else { + otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } +} + +/* +================ +idGameLocal::FreePlayerPVS +================ +*/ +void idGameLocal::FreePlayerPVS( void ) { + int i; + + // only clear playerPVS if it's a different handle than the one in clientsPVS + if ( freePlayerPVS && playerPVS.i != -1 ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS.i = -1; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( clientsPVS[ i ].i >= 0 ) { + pvs.FreeCurrentPVS( clientsPVS[ i ] ); + clientsPVS[i].i = -1; + } + } + if ( playerConnectedAreas.i != -1 ) { + pvs.FreeCurrentPVS( playerConnectedAreas ); + playerConnectedAreas.i = -1; + } +} + +/* +================ +idGameLocal::InPlayerPVS + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerPVS( idEntity *ent ) const { + if ( playerPVS.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::InPlayerConnectedArea + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { + if ( playerConnectedAreas.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + + +/* +================ +idGameLocal::UpdateGravity +================ +*/ +void idGameLocal::UpdateGravity( void ) { + idEntity *ent; + + idCVar* gravityCVar = NULL; + + if( gameLocal.isMultiplayer ) { + gravityCVar = &g_mp_gravity; + } else { + gravityCVar = &g_gravity; + } + + if ( gravityCVar->IsModified() ) { + if ( gravityCVar->GetFloat() == 0.0f ) { + gravityCVar->SetFloat( 1.0f ); + } + gravity.Set( 0, 0, -gravityCVar->GetFloat() ); + + // update all physics objects + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + idPhysics *phys = ent->GetPhysics(); + if ( phys ) { + phys->SetGravity( gravity ); + } +// RAVEN BEGIN +// ddynerman: jump pads + } else if ( ent->IsType( rvJumpPad::GetClassType() ) ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + } +// RAVEN END + } + gravityCVar->ClearModified(); + } +} + +/* +================ +idGameLocal::GetGravity +================ +*/ +const idVec3 &idGameLocal::GetGravity( void ) const { + return gravity; +} + +/* +================ +idGameLocal::SortActiveEntityList + + Sorts the active entity list such that pushing entities come first, + actors come next and physics team slaves appear after their master. +================ +*/ +void idGameLocal::SortActiveEntityList( void ) { + idEntity *ent, *next_ent, *master, *part; + + // if the active entity list needs to be reordered to place physics team masters at the front + if ( sortTeamMasters ) { + // Sort bind masters first + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + for ( part = ent->GetBindMaster ( ); part; part = part->GetBindMaster ( ) ) { + // Ensure we dont rerun the whole active entity list if our cached next_ent is one + // of the entities we are moving + if ( next_ent == part ) { + next_ent = next_ent->activeNode.Next(); + part = ent->GetBindMaster ( ); + continue; + } + part->activeNode.Remove(); + part->activeNode.AddToFront( activeEntities ); + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( master && master == ent ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + // if the active entity list needs to be reordered to place pushers at the front + if ( sortPushers ) { + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an actor on the team + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + break; + } + } + // if there is an actor on the team + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an entity on the team using parametric physics + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + break; + } + if ( part->GetPhysics()->IsType( rvPhysics_Spline::GetClassType() ) ) { + break; + } + } + // if there is an entity on the team using parametric physics + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + } + + sortTeamMasters = false; + sortPushers = false; +} + +/* +================ +idGameLocal::MenuFrame +Called each session frame when a map is not running (e.g. usually in the main menu) +================ +*/ +void idGameLocal::MenuFrame( void ) { } + +/* +================ +idGameLocal::RunFrame +================ +*/ +// RAVEN BEGIN + gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + idTimer timer_misc, timer_misc2; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + + editors = activeEditors; + isLastPredictFrame = lastCatchupFrame; + + assert( !isClient ); + + player = GetLocalPlayer(); + + if ( !isMultiplayer && g_stopTime.GetBool() ) { + + // set the user commands for this frame + usercmds = clientCmds; + + if ( player ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->Think(); + currentThinkingEntity = NULL; + } + } else do { + // update the game time + framenum++; + previousTime = time; + // bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); + + realClientTime = time; + { +TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()"); + // bdube: added advanced debug support + gameDebug.BeginFrame( ); + gameLogLocal.BeginFrame( time ); + } + +#ifdef GAME_DLL + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + } +#endif + + // make sure the random number counter is used each frame so random events + // are influenced by the player's actions + random.RandomInt(); + + if ( player ) { + // update the renderview so that any gui videos play from the right frame + view = player->GetRenderView(); + if ( view ) { + gameRenderWorld->SetRenderView( view ); + } + } + + // If modview is running then let it think + common->ModViewThink( ); + + // rjohnson: added option for guis to always think + common->RunAlwaysThinkGUIs( time ); + + // nmckenzie: Let AI System stuff update itself. + if ( !isMultiplayer ) { +#ifndef _MPBETA + aiManager.RunFrame(); +#endif // !_MPBETA + } + + timer_misc.Start(); + + // set the user commands for this frame + usercmds = clientCmds; + + // create a merged pvs for all players + // do this before we process events, which may rely on PVS info + SetupPlayerPVS(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->StartFrame(); + } + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + + // jscott: for timing and effect handling + timer_misc2.Start(); + timer_misc.Stop(); + + // let entities think + if ( g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + idPlayer *player; + idVec3 origin; + + player = GetLocalPlayer(); + if ( player ) { + origin = player->GetPhysics()->GetOrigin(); + } + + idBounds viewTextBounds( origin ); + viewTextBounds.ExpandSelf( 128.0f ); + + num = 0; + + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + timer_singlethink.Clear(); + timer_singlethink.Start(); + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + timer_singlethink.Stop(); + ms = timer_singlethink.Milliseconds(); + if ( ms >= g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + Printf( "%d: entity '%s' [%s]: %.1f ms\n", time, ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(), ms ); + + if ( ms >= g_timeentities.GetFloat() * 3.0f ) + { + ent->mLastLongThinkColor = colorRed; + } + else + { + ent->mLastLongThinkColor[0] = 1.0f; + ent->mLastLongThinkColor[1] = 2.0f - (( ms - g_timeentities.GetFloat()) / g_timeentities.GetFloat() ); + ent->mLastLongThinkColor[2] = 0.0f; + ent->mLastLongThinkColor[3] = 1.0f; + } + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + ent->mLastLongThinkTime = time + 2000; + } + else if ( ent->mLastLongThinkTime > time ) + { + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + } + num++; + } + } else { + if ( inCinematic ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } + } + + // remove any entities that have stopped thinking + if ( numEntitiesToDeactivate ) { + idEntity *next_ent; + int c = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + if ( !ent->thinkFlags ) { + ent->activeNode.Remove(); + c++; + } + } + //assert( numEntitiesToDeactivate == c ); + numEntitiesToDeactivate = 0; + } + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + if ( isLastPredictFrame ) { + // bdube: client entities + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // nrausch: player could have been deleted in an event + player = GetLocalPlayer(); + + timer_events.Stop(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->EndFrame(); + } + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // free the player pvs + FreePlayerPVS(); + + // display how long it took to calculate the current game frame + if ( g_frametime.GetBool() ) { + Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", + time, timer_think.Milliseconds() + timer_events.Milliseconds(), + timer_think.Milliseconds(), timer_events.Milliseconds(), num ); + } + + // build the return value + ret.consistencyHash = 0; + ret.sessionCommand[0] = 0; + + if ( !isMultiplayer && player ) { + ret.health = player->health; + ret.heartRate = 0.0f; + ret.stamina = 0.0f; + // combat is a 0-100 value based on lastHitTime and lastDmgTime + // each make up 50% of the time spread over 10 seconds + ret.combat = 0; + if ( player->lastDmgTime > 0 && time < player->lastDmgTime + 10000 ) { + ret.combat += 50.0f * (float) ( time - player->lastDmgTime ) / 10000; + } + if ( player->lastHitTime > 0 && time < player->lastHitTime + 10000 ) { + ret.combat += 50.0f * (float) ( time - player->lastHitTime ) / 10000; + } + } + + // see if a target_sessionCommand has forced a changelevel + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + break; + } + + // make sure we don't loop forever when skipping a cinematic + if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) { + Warning( "Exceeded maximum cinematic skip length. Cinematic may be looping infinitely." ); + skipCinematic = false; + break; + } + + // jscott: additional timings + timer_misc2.Stop(); + + if ( g_frametime.GetInteger() > 1 ) { + gameLocal.Printf( "misc:%.1f misc2:%.1f\n", timer_misc.Milliseconds(), timer_misc2.Milliseconds() ); + } + // bdube: let gameDebug know that its not in a game frame anymore + gameDebug.EndFrame( ); + gameLogLocal.EndFrame( ); + + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->EndCinematic(); + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + g_simpleItems.ClearModified(); + return ret; +} +// RAVEN END + + +/* +====================================================================== + + Game view drawing + +====================================================================== +*/ + +/* +==================== +idGameLocal::CalcFov + +Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio +==================== +*/ +void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { + float x; + float y; + float ratio_x; + float ratio_y; + + if ( !sys->FPU_StackIsEmpty() ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: FPU stack not empty" ); + } + +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( g_fixedHorizFOV.GetBool() ) { + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + ratio_x = 4.0f; + ratio_y = 3.0f; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + x = ratio_x / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( ratio_y, x ); + fov_y = y * 360.0f / idMath::PI; + fov_x = base_fov; + return; + } +// RAVEN END + + // first, calculate the vertical fov based on a 640x480 view + x = 640.0f / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( 480.0f, x ); + fov_y = y * 360.0f / idMath::PI; + + // FIXME: somehow, this is happening occasionally + assert( fov_y > 0 ); + if ( fov_y <= 0 ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } + + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + fov_x = base_fov; + return; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + + y = ratio_y / idMath::Tan( fov_y / 360.0f * idMath::PI ); + fov_x = idMath::ATan( ratio_x, y ) * 360.0f / idMath::PI; + + if ( fov_x < base_fov ) { + fov_x = base_fov; + x = ratio_x / idMath::Tan( fov_x / 360.0f * idMath::PI ); + fov_y = idMath::ATan( ratio_y, x ) * 360.0f / idMath::PI; + } + + // FIXME: somehow, this is happening occasionally + assert( ( fov_x > 0 ) && ( fov_y > 0 ) ); + if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } +} + +void ClearClipProfile( void ); +void DisplayClipProfile( void ); + +/* +================ +idGameLocal::Draw + +makes rendering and sound system calls +================ +*/ +bool idGameLocal::Draw( int clientNum ) { +// DisplayClipProfile( ); +// ClearClipProfile( ); + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + idPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + renderView_t *view = player->GetRenderView(); + // nrausch: view doesn't necessarily exist yet + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + } +#endif +// RAVEN END + // render the scene + player->playerView.RenderPlayerView( player->hud ); + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud( ); +// RAVEN END + + return true; +} + +/* +================ +idGameLocal::HandleESC +================ +*/ +escReply_t idGameLocal::HandleESC( idUserInterface **gui ) { + +//RAVEN BEGIN +//asalmon: xbox dedicated server needs to bring up a special menu +#ifdef _XBOX + if( cvarSystem->GetCVarBool( "net_serverDedicated" )) + { + return ESC_MAIN; + } +#endif +//RAVEN END + + if ( isMultiplayer ) { + *gui = StartMenu(); + // we may set the gui back to NULL to hide it + return ESC_GUI; + } + idPlayer *player = GetLocalPlayer(); + if ( player ) { + if ( player->HandleESC() ) { + return ESC_IGNORE; + } else { + return ESC_MAIN; + } + } + return ESC_MAIN; +} + +/* +================ +idGameLocal::UpdatePlayerPostMainMenu +================ +*/ +void idGameLocal::UpdatePlayerPostMainMenu() { + idPlayer* player = GetLocalPlayer(); + + //dedicated server? + if( !player) { + return; + } + + //crosshair may have changed + player->UpdateHudWeapon(); +} + +/* +================ +idGameLocal::StartMenu +================ +*/ +idUserInterface* idGameLocal::StartMenu( void ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.StartMenu(); +} + +/* +================ +idGameLocal::GetClientStats +================ +*/ +void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { + mpGame.PlayerStats( clientNum, data, len ); +} + +/* +================ +idGameLocal::SwitchTeam +================ +*/ +void idGameLocal::SwitchTeam( int clientNum, int team ) { + + idPlayer * player; + player = clientNum >= 0 ? static_cast( gameLocal.entities[ clientNum ] ) : NULL; + + if ( !player ) { + return; + } + + int oldTeam = player->team; + + // Put in spectator mode + if ( team == -1 ) { + static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true ); + } + // Switch to a team + else { + mpGame.SwitchToTeam ( clientNum, oldTeam, team ); + } +} + +/* +================ +idGameLocal::HandleGuiCommands +================ +*/ +const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.HandleGuiCommands( menuCommand ); +} + +/* +================ +idGameLocal::HandleMainMenuCommands +================ +*/ +void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { + if ( !idStr::Icmp( menuCommand, "initCreateServerSettings" ) ) { + int guiValue = 0; + + switch ( mpGame.GameTypeToVote( si_gameType.GetString() ) ) { + case idMultiplayerGame::VOTE_GAMETYPE_DM: + guiValue = 0; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TOURNEY: + guiValue = 1; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TDM: + guiValue = 2; + break; + case idMultiplayerGame::VOTE_GAMETYPE_CTF: + guiValue = 3; + break; + case idMultiplayerGame::VOTE_GAMETYPE_ARENA_CTF: + guiValue = 4; + break; + case idMultiplayerGame::VOTE_GAMETYPE_DEADZONE: + guiValue = 5; + break; + } + + gui->SetStateInt( "currentGametype", guiValue ); + } else if ( !idStr::Icmp( menuCommand, "filterByNextMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = 0; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + ++filterMod; + if ( filterMod < modList.Num() ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "filterByPrevMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = modList.Num() - 1; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + --filterMod; + if ( filterMod >= 0 ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "updateFilterByMod" ) ) { + if ( filterMod < 0 || filterMod >= modList.Num() ) { + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } else { + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } + } else if ( !idStr::Icmp( menuCommand, "server_clearSort" ) ) { + filterMod = -1; + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + + return; +} + +/* +================ +idGameLocal::GetLevelMap + + should only be used for in-game level editing +================ +*/ +idMapFile *idGameLocal::GetLevelMap( void ) { +// RAVEN BEGIN +// rhummer: Added the HasBeenResolved check, if resolve has been run then it doesn't have func_groups. +// Which we probably don't want, so force the map to be reloaded. + if ( mapFile && mapFile->HasPrimitiveData() && !mapFile->HasBeenResloved() ) { +// RAVEN END + return mapFile; + } + if ( !mapFileName.Length() ) { + return NULL; + } + + if ( mapFile ) { + delete mapFile; + } + + mapFile = new idMapFile; + if ( !mapFile->Parse( mapFileName ) ) { + delete mapFile; + mapFile = NULL; + } + + return mapFile; +} + +/* +================ +idGameLocal::GetMapName +================ +*/ +const char *idGameLocal::GetMapName( void ) const { + return mapFileName.c_str(); +} + +/* +================ +idGameLocal::CallFrameCommand +================ +*/ +void idGameLocal::CallFrameCommand( idEntity* ent, const char* frameCommand ) { + const function_t *func = program.FindFunction( frameCommand ); + if ( !func ) { + Warning( "Script function '%s' not found.", frameCommand ); + return; + } + CallFrameCommand ( ent, func ); +} + +void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { + frameCommandThread->CallFunction( ent, frameCommand, true ); + frameCommandThread->Execute(); +} + +/* +================ +idGameLocal::CallObjectFrameCommand +================ +*/ +void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { + const function_t *func; + + func = ent->scriptObject.GetFunction( frameCommand ); + if ( !func ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idTestModel::GetClassType() ) ) { +// RAVEN END + Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); + } + } else { + frameCommandThread->CallFunction( ent, func, true ); + frameCommandThread->Execute(); + } +} + +/* +================ +idGameLocal::ShowTargets +================ +*/ +void idGameLocal::ShowTargets( void ) { + idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); + idBounds viewTextBounds( viewPos ); + idBounds viewBounds( viewPos ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + idBounds totalBounds; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + totalBounds = ent->GetPhysics()->GetAbsBounds(); + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); + } + } + + if ( !viewBounds.IntersectsBounds( totalBounds ) ) { + continue; + } + + float dist; + idVec3 dir = totalBounds.GetCenter() - viewPos; + dir.NormalizeFast(); + totalBounds.RayIntersection( viewPos, dir, dist ); + float frac = ( 512.0f - dist ) / 512.0f; + if ( frac < 0.0f ) { + continue; + } + + gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); + gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); + } + } + } +} + +/* +================ +idGameLocal::RunDebugInfo +================ +*/ +void idGameLocal::RunDebugInfo( void ) { + idEntity *ent; + idPlayer *player; + + player = GetLocalPlayer(); + if ( !player ) { + return; + } + + const idVec3 &origin = player->GetPhysics()->GetOrigin(); + + if ( g_showEntityInfo.GetBool() ) { + idBounds viewTextBounds( origin ); + idBounds viewBounds( origin ); + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // don't draw the worldspawn + if ( ent == world ) { + continue; + } + +// RAVEN BEGIN +// rjohnson: moved entity info out of idGameLocal into its own function + ent->DrawDebugEntityInfo(&viewBounds, &viewTextBounds); +// RAVEN END + } + } + + // debug tool to draw bounding boxes around active entities + if ( g_showActiveEntities.GetBool() ) { + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + idBounds b = ent->GetPhysics()->GetBounds(); + if ( b.GetVolume() <= 0 ) { + b[0][0] = b[0][1] = b[0][2] = -8; + b[1][0] = b[1][1] = b[1][2] = 8; + } + if ( ent->fl.isDormant ) { + gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); + } else { + gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); + } + } + } + +// RAVEN BEGIN +// bdube: client entities + if ( cl_showEntityInfo.GetBool ( ) ) { + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->DrawDebugInfo ( ); + } + } +// RAVEN END + + if ( g_showTargets.GetBool() ) { + ShowTargets(); + } + + if ( g_showTriggers.GetBool() ) { + idTrigger::DrawDebugInfo(); + } + + if ( ai_showCombatNodes.GetBool() ) { +// idCombatNode::DrawDebugInfo(); + } + + if ( ai_showPaths.GetBool() ) { + idPathCorner::DrawDebugInfo(); + } + + if ( g_editEntityMode.GetBool() ) { + if ( gameLocal.isMultiplayer ) { + g_editEntityMode.SetInteger(0); + Printf( "Not allowed in multiplayer.\n" ); + } else { + editEntities->DisplayEntities(); + } + } + + if ( g_showCollisionWorld.GetBool() ) { + // use g_maxShowDistance value instead of 128.0f + collisionModelManager->DrawModel( clip[0]->GetWorldCollisionModel(), vec3_origin, mat3_identity, origin, mat3_identity, g_maxShowDistance.GetFloat() ); + } + + if ( g_showCollisionModels.GetBool() ) { + if( g_showCollisionModels.GetInteger() == 2 ) { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player, &idPlayer::GetClassType() ); + } else { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); + } + + } + + if ( g_showCollisionTraces.GetBool() ) { + clip[ 0 ]->PrintStatistics(); + } + + if ( g_showPVS.GetInteger() ) { + pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); + } + +// RAVEN BEGIN +// rjohnson: added debug hud + if ( gameDebug.IsHudActive ( DBGHUD_PHYSICS ) ) { + clip[ 0 ]->DebugHudStatistics(); + } + + clip[ 0 ]->ClearStatistics(); +// RAVEN END + + if ( aas_test.GetInteger() >= 0 ) { + idAAS *aas = GetAAS( aas_test.GetInteger() ); + if ( aas ) { + aas->Test( origin ); + if ( ai_testPredictPath.GetBool() ) { + idVec3 velocity; + predictedPath_t path; + + velocity.x = idMath::Cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.y = idMath::Sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.z = 0.0f; + idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); + } + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin ); + } + } + } + if ( aas_showProblemAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin, true ); + } + } + } +// RAVEN END + } + + if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { + idAAS *aas = GetAAS( 0 ); + if ( aas ) { + idVec3 seekPos; + obstaclePath_t path; + + seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; + idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); + } + } + + // collision map debug output + collisionModelManager->DebugOutput( player->GetEyePosition(), mat3_identity ); + +// RAVEN BEGIN +// jscott: for debugging playbacks + if( g_showPlayback.GetInteger() ) { + gameEdit->DrawPlaybackDebugInfo(); + } +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if ( g_showClipSectors.GetBool() ) { + clip[ 0 ]->DrawClipSectors(); + } + + if ( g_showAreaClipSectors.GetFloat() ) { + clip[ 0 ]->DrawAreaClipSectors( g_showAreaClipSectors.GetFloat() ); + } +// RAVEN END +} + +/* +================== +idGameLocal::NumAAS +================== +*/ +int idGameLocal::NumAAS( void ) const { + return aasList.Num(); +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( int num ) const { + if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { + if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { + return aasList[ num ]; + } + } + return NULL; +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( const char *name ) const { + int i; + + for ( i = 0; i < aasNames.Num(); i++ ) { + if ( aasNames[ i ] == name ) { + if ( !aasList[ i ]->GetSettings() ) { + return NULL; + } else { + return aasList[ i ]; + } + } + } + return NULL; +} + +/* +================== +idGameLocal::SetAASAreaState +================== +*/ +void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->SetAreaState( bounds, areaContents, closed ); + } +} + +/* +================== +idGameLocal::AddAASObstacle +================== +*/ +aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { + int i; + aasHandle_t obstacle; + aasHandle_t check; + + if ( !aasList.Num() ) { + return -1; + } + + obstacle = aasList[ 0 ]->AddObstacle( bounds ); + for( i = 1; i < aasList.Num(); i++ ) { + check = aasList[ i ]->AddObstacle( bounds ); + assert( check == obstacle ); + } + + return obstacle; +} + +/* +================== +idGameLocal::RemoveAASObstacle +================== +*/ +void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveObstacle( handle ); + } +} + +/* +================== +idGameLocal::RemoveAllAASObstacles +================== +*/ +void idGameLocal::RemoveAllAASObstacles( void ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveAllObstacles(); + } +} + +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. +/* +================== +idGameLocal::GetEntityMemoryUsage + +Compute combined total memory footprint of server and client entity storage. +================== +*/ + +size_t idGameLocal::GetEntityMemoryUsage ( void ) const { + + // Server ents. + size_t serverEntitiesSize = 0; + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + serverEntitiesSize += sizeof( idEntity ); + } + + // Client ents. + size_t clientEntitiesSize = 0; + for( rvClientEntity *ent = gameLocal.clientSpawnedEntities.Next() ; ent != NULL; ent = ent->spawnNode.Next() ) { + clientEntitiesSize += ent->Size(); + } + + return serverEntitiesSize + clientEntitiesSize; +} +// RAVEN END + +/* +================== +idGameLocal::CheatsOk +================== +*/ +bool idGameLocal::CheatsOk( bool requirePlayer ) { + idPlayer *player; + + if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + Printf( "Not allowed in multiplayer.\n" ); + return false; + } + + if ( developer.GetBool() ) { + return true; + } + + player = GetLocalPlayer(); + if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) { + return true; + } + + Printf( "You must be alive to use this command.\n" ); + + return false; +} + +/* +=================== +idGameLocal::RegisterEntity +=================== +*/ +void idGameLocal::RegisterEntity( idEntity *ent ) { + int spawn_entnum; + + if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { + Error( "idGameLocal::RegisterEntity: spawn count overflow" ); + } + + firstFreeIndex = Max( minSpawnIndex, firstFreeIndex ); + + if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { + while ( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) { + firstFreeIndex++; + } + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } + spawn_entnum = firstFreeIndex++; + } else { + assert( spawn_entnum < MAX_CLIENTS || spawn_entnum >= minSpawnIndex ); + } + + entities[ spawn_entnum ] = ent; + spawnIds[ spawn_entnum ] = spawnCount++; + ent->entityNumber = spawn_entnum; + ent->spawnNode.AddToEnd( spawnedEntities ); + ent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( spawn_entnum >= num_entities ) { + num_entities++; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=================== +idGameLocal::UnregisterEntity +=================== +*/ +void idGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( editEntities ) { + editEntities->RemoveSelectedEntity( ent ); + } + + if ( ( ent->entityNumber != ENTITYNUM_NONE ) && ( entities[ ent->entityNumber ] == ent ) ) { + ent->spawnNode.Remove(); + entities[ ent->entityNumber ] = NULL; + spawnIds[ ent->entityNumber ] = -1; + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) { + firstFreeIndex = ent->entityNumber; + } + ent->entityNumber = ENTITYNUM_NONE; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=============== +idGameLocal::SkipEntityIndex +=============== +*/ +void idGameLocal::SkipEntityIndex( void ) { + assert( entities[ firstFreeIndex ] == NULL ); + firstFreeIndex++; + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } +} + +/* +================ +idGameLocal::SpawnEntityType +================ +*/ +idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { + idClass *obj; + +#ifdef _DEBUG + if ( isClient ) { + assert( bIsClientReadSnapshot ); + } +#endif + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !classdef.IsType( idEntity::GetClassType() ) ) { +// RAVEN END + Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); + } + + try { + if ( args ) { + spawnArgs = *args; + } else { + spawnArgs.Clear(); + } + obj = classdef.CreateInstance(); + obj->CallSpawn(); + } + + catch( idAllocError & ) { + obj = NULL; + } + spawnArgs.Clear(); + + return static_cast(obj); +} + +/* +=================== +idGameLocal::SpawnClientEntityDef + +Finds the spawn function for the client entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnClientEntityDef( const idDict &args, rvClientEntity **cent, bool setDefaults, const char* spawn ) { + const char *classname; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + if ( cent ) { + *cent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ){ + //not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { + error = va( " on '%s'", name ); + } + + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { + // RAVEN BEGIN + // jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } + // RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + + // check if we should spawn a class object + if( spawn == NULL ) { + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + } + + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + + if ( cent && obj->IsType( rvClientEntity::GetClassType() ) ) { + *cent = static_cast(obj); + } + + return true; + } + + Warning( "%s doesn't include a spawnfunc%s.", classname, error.c_str() ); + return false; +} + +/* +=================== +idGameLocal::SpawnEntityDef + +Finds the spawn function for the entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { + const char *classname; + const char *spawn; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( ent ) { + *ent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ) + {//not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + error = va( " on '%s'", name ); +// RAVEN END + } + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { +// RAVEN BEGIN +// jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } +// RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + if ( g_keepEntityStats.GetBool() ) { + if ( idStr::Icmp( classname, "func_spawner" ) == 0 || + idStr::Icmp( classname, "func_spawner_enemy" ) == 0 ) { + // special case for spawners + for( int i = 1; ; i++ ) { + char tempSpawn[128]; + const char *tempClassname; + + sprintf( tempSpawn, "def_spawn_type%d", i ); + tempClassname = spawnArgs.GetString( tempSpawn, NULL ); + if ( tempClassname ) { + const idDeclEntityDef *tempDef = FindEntityDef( tempClassname, false ); + + if ( tempDef ) { + idDict tempArgs = tempDef->dict; + + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } else { + break; + } + } + } + else if ( def->dict.GetBool( "report_stats" ) ) { + idDict tempArgs = spawnArgs; + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } +// RAVEN END + + // check if we should spawn a class object + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && obj->IsType( idEntity::GetClassType() ) ) { +// RAVEN END + *ent = static_cast(obj); + } + + return true; + } + + // check if we should call a script function to spawn + spawnArgs.GetString( "spawnfunc", NULL, &spawn ); + if ( spawn ) { + const function_t *func = program.FindFunction( spawn ); + if ( !func ) { + Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + + return true; + } + + Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); + return false; +} + +// abahr: +idEntity* idGameLocal::SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idDict finalArgs; + const idDict* entityDict = NULL; + idEntity* entity = NULL; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if( !entityDefName ) { + return NULL; + } + + entityDict = FindEntityDefDict( entityDefName, false ); + if( !entityDict ) { + return NULL; + } + + if( !additionalArgs ) { + SpawnEntityDef( *entityDict, &entity ); + } else { + finalArgs.Copy( *entityDict ); + finalArgs.Copy( *additionalArgs ); + SpawnEntityDef( finalArgs, &entity ); + } + + return entity; +} +// RAVEN END + +/* +================ +idGameLocal::FindEntityDef +================ +*/ +const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDecl *decl = NULL; + if ( isMultiplayer ) { + decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); + } + if ( !decl ) { + decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); + } + return static_cast( decl ); +} + +/* +================ +idGameLocal::FindEntityDefDict +================ +*/ +const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); + return decl ? &decl->dict : NULL; +} + +/* +================ +idGameLocal::InhibitEntitySpawn +================ +*/ +bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + + bool result = false; + + if ( isMultiplayer ) { + spawnArgs.GetBool( "not_multiplayer", "0", result ); + } else if ( g_skill.GetInteger() == 0 ) { + spawnArgs.GetBool( "not_easy", "0", result ); + } else if ( g_skill.GetInteger() == 1 ) { + spawnArgs.GetBool( "not_medium", "0", result ); + } else { + spawnArgs.GetBool( "not_hard", "0", result ); + } + + const char *name; +#ifndef ID_DEMO_BUILD + if ( g_skill.GetInteger() == 3 ) { + name = spawnArgs.GetString( "classname" ); + if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ) { + result = true; + } + } +#endif + +// RAVEN BEGIN +// bdube: suppress ents that don't match the entity filter + const char* entityFilter; + if ( serverInfo.GetString( "si_entityFilter", "", &entityFilter ) && *entityFilter ) { + if ( spawnArgs.MatchPrefix ( "filter_" ) && !spawnArgs.GetBool ( va("filter_%s", entityFilter) ) ) { + return true; + } + } +// RAVEN END + +// RITUAL BEGIN +// squirrel: suppress ents that aren't supported in Buying modes (if that's the mode we're in) + if ( mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( spawnArgs.GetBool( "disableSpawnInBuying", "0" ) ) { + return true; + } + + /// Don't spawn weapons or armor vests or ammo in Buying modes + idStr classname = spawnArgs.GetString( "classname" ); + if( idStr::FindText( classname, "weapon_" ) == 0 || + idStr::FindText( classname, "item_armor_small" ) == 0 || + idStr::FindText( classname, "ammo_" ) == 0 || + idStr::FindText( classname, "item_armor_large" ) == 0 ) + { + return true; + } + } +// RITUAL END + + // suppress deadzone triggers if we're not running DZ + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "DeadZone" ) != 0 ) { + if ( idStr::Icmp( spawnArgs.GetString( "classname" ), "trigger_controlzone" ) == 0 ) { + return true; + } + } + + return result; +} + +/* +================ +idGameLocal::SetSkill +================ +*/ +void idGameLocal::SetSkill( int value ) { + int skill_level; + + if ( value < 0 ) { + skill_level = 0; + } else if ( value > 3 ) { + skill_level = 3; + } else { + skill_level = value; + } + + g_skill.SetInteger( skill_level ); +} + +/* +============== +idGameLocal::GameState + +Used to allow entities to know if they're being spawned during the initial spawn. +============== +*/ +gameState_t idGameLocal::GameState( void ) const { + return gamestate; +} + +/* +============== +idGameLocal::SpawnMapEntities + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple game instances +void idGameLocal::SpawnMapEntities( int instance, unsigned short* entityNumIn, unsigned short* entityNumOut, int* startSpawnCount ) { +// RAVEN END + int i; + int num; + int inhibit; + int latchedSpawnCount = -1; + idMapEntity *mapEnt; + int numEntities; + idDict args; + idDict items; + + bool proto69 = ( GetCurrentDemoProtocol() == 69 ); + + Printf( "Spawning entities\n" ); + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( mapFile == NULL ) { + Printf("No mapfile present\n"); + return; + } + + SetSkill( g_skill.GetInteger() ); + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + +// RAVEN BEGIN +// ddynerman: on the server, perform world-initialization when spawning instance 0 only and while starting up +// on the client, perform world-init when spawning while starting up, as client main instance may not be ID 0 +// always spawn world-init in single-player + num = inhibit = 0; + + // the worldspawn is a special that performs any global setup + // needed by a level + if ( ( gameLocal.isServer && instance == 0 && gamestate == GAMESTATE_STARTUP ) || ( gameLocal.isClient && gamestate == GAMESTATE_STARTUP ) || !gameLocal.isMultiplayer ) { + mapEnt = mapFile->GetEntity( 0 ); + args = mapEnt->epairs; + args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); + + // on the client, spawnCount won't always start at INITIAL_SPAWN_COUNT (see rvInstance::Populate()) + // make sure the world and physics ent get the right spawn id + if( gameLocal.isClient && spawnCount != INITIAL_SPAWN_COUNT ) { + latchedSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + } + +// abahr: precache stuff on worldSpawn like player def and other misc things + CacheDictionaryMedia( &args ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::GetClassType() ) ) { + Error( "Problem spawning world entity" ); + } + num++; + +// bdube: dummy entity for client entities with physics + args.Clear(); + args.SetInt( "spawn_entnum", ENTITYNUM_CLIENT ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityType( rvClientPhysics::GetClassType(), &args, true ) || !entities[ ENTITYNUM_CLIENT ] ) { + Error( "Problem spawning client physics entity" ); + } + + if( gameLocal.isClient && latchedSpawnCount != -1 ) { + spawnCount = latchedSpawnCount; + } + + isMapEntity[ ENTITYNUM_CLIENT ] = true; + isMapEntity[ ENTITYNUM_WORLD ] = true; +// RAVEN END + } + + // capture spawn count of start of map entities (after we've spawned in the physics ents) + if ( startSpawnCount ) { + (*startSpawnCount) = spawnCount; + } + + for ( i = 1 ; i < numEntities ; i++ ) { + + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + +// RAVEN BEGIN +// ddynerman: merge the dicts ahead of SpawnEntityDef() so we can inhibit using merged info + const idDeclEntityDef* entityDef = FindEntityDef( args.GetString( "classname" ), false ); + + if( entityDef == NULL ) { + gameLocal.Error( "idGameLocal::SpawnMapEntities() - Unknown entity classname '%s'\n", args.GetString( "classname" ) ); + return; + } + args.SetDefaults( &(entityDef->dict) ); +// RAVEN END + + if ( !InhibitEntitySpawn( args ) ) { + + if( args.GetBool( "inv_item" ) ) { + if( !items.GetBool( args.GetString( "inv_icon" ) ) ) { + networkSystem->AddLoadingIcon( args.GetString( "inv_icon" ) ); + networkSystem->SetLoadingText( common->GetLocalizedString( args.GetString( "inv_name" ) ) ); + items.SetBool( args.GetString( "inv_icon" ), true ); + } + } + + // precache any media specified in the map entity + CacheDictionaryMedia( &args ); +// RAVEN BEGIN + if ( instance != 0 ) { +// ddynerman: allow this function to be called multiple-times to respawn map entities in other instances + args.SetInt( "instance", instance ); + args.Set( "name", va( "%s_instance%d", args.GetString( "name" ), instance ) ); + //gameLocal.Printf( "Instance %d: Spawning %s (class: %s)\n", instance, args.GetString( "name" ), args.GetString( "classname" ) ); + + // need a better way to do this - map entities that target other map entities need + // to target the ones in the correct instance. + idStr target; + if( args.GetString( "target", "", target ) ) { + args.Set( "target", va( "%s_instance%d", target.c_str(), instance ) ); + } + + // and also udpate binds on a per-instance bases + idStr bind; + if( args.GetString( "bind", "", bind ) ) { + args.Set( "bind", va( "%s_instance%d", bind.c_str(), instance ) ); + } + + // and also door triggers + idStr triggerOnOpen; + if( args.GetString( "triggerOnOpen", "", triggerOnOpen ) ) { + args.Set( "triggerOnOpen", va( "%s_instance%d", triggerOnOpen.c_str(), instance ) ); + } + + idStr triggerOpened; + if( args.GetString( "triggerOpened", "", triggerOpened ) ) { + args.Set( "triggerOpened", va( "%s_instance%d", triggerOpened.c_str(), instance ) ); + } + } + + // backward compatible 1.2 playback: proto69 reads the entity mapping list from the server + // only 1.2 backward replays should get passed a != NULL entityNumIn + assert( entityNumIn == NULL || proto69 ); + if ( proto69 && entityNumIn ) { + assert( gameLocal.isClient ); + if ( entityNumIn[ i ] < 0 || entityNumIn[ i ] >= MAX_GENTITIES ) { + // this entity was not spawned in on the server, ignore it here + // this is one of the symptoms of net corruption <= 1.2 - fix here acted as a band aid + // if you replay a 1.2 netdemo and hit this, it's likely other things will go wrong in the replay + common->Warning( "entity inhibited on server not properly inhibited on client - map ent %d ( %s )", i, args.GetString( "name" ) ); + inhibit++; + continue; + } + args.SetInt( "spawn_entnum", entityNumIn[ i ] ); + } + + idEntity* ent = NULL; + SpawnEntityDef( args, &ent ); + //common->Printf( "pop: spawn map ent %d at %d ( %s )\n", i, ent->entityNumber, args.GetString( "name" ) ); + + if ( ent && entityNumOut ) { + entityNumOut[ i ] = ent->entityNumber; + } + + if ( gameLocal.GetLocalPlayer() && ent && gameLocal.isServer && instance != gameLocal.GetLocalPlayer()->GetInstance() ) { + // if we're spawning entities not in our instance, tell them not to draw + ent->BecomeInactive( TH_UPDATEVISUALS ); + } + + if ( ent ) { + isMapEntity[ ent->entityNumber ] = true; + } +// RAVEN END + num++; + } else { + if ( !proto69 ) { + // keep counting and leave an empty slot in the entity list for inhibited entities + // this so we maintain the same layout as the server and don't change it across restarts with different inhibit schemes + //common->Printf( "pop: skip map ent %d at index %d ( %s )\n", i, firstFreeIndex, args.GetString( "name" ) ); + SkipEntityIndex(); + } else { + // backward 1.2 netdemo replay compatibility - no skipping + // they might net corrupt but there's no fix client side + assert( isClient ); + } + inhibit++; + } + } + + Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); +} + +/* +================ +idGameLocal::AddEntityToHash +================ +*/ +void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { + if ( FindEntity( name ) ) { + Error( "Multiple entities named '%s'", name ); + } + entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); +} + +/* +================ +idGameLocal::RemoveEntityFromHash +================ +*/ +bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { + entityHash.Remove( hash, i ); + return true; + } + } + return false; +} + +/* +================ +idGameLocal::GetTargets +================ +*/ +int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { + int i, num, refLength; + const idKeyValue *arg; + idEntity *ent; + + list.Clear(); + + refLength = strlen( ref ); + num = args.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + + arg = args.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { + + ent = FindEntity( arg->GetValue() ); + if ( ent ) { + idEntityPtr &entityPtr = list.Alloc(); + entityPtr = ent; + } + } + } + + return list.Num(); +} + +/* +============= +idGameLocal::GetTraceEntity + +returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. +============= +*/ +idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { + idEntity *master; + + if ( !entities[ trace.c.entityNum ] ) { + return NULL; + } + master = entities[ trace.c.entityNum ]->GetBindMaster(); + if ( master ) { + return master; + } + return entities[ trace.c.entityNum ]; +} + +/* +============= +idGameLocal::ArgCompletion_EntityName + +Argument completion for entity names +============= +*/ +void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::ArgCompletion_AIName + +Argument completion for idAI entity names +============= +*/ +void idGameLocal::ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idAI::GetClassType() ) ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::FindEntity + +Returns the entity whose name matches the specified string. +============= +*/ +idEntity *idGameLocal::FindEntity( const char *name ) const { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { + return entities[i]; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindEntityUsingDef + +Searches all active entities for the next one using the specified entityDef. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. +============= +*/ +idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { + idEntity *ent; + + if ( !from ) { + ent = spawnedEntities.Next(); + } else { + ent = from->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + assert( ent ); + if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { + return ent; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindTraceEntity + +Searches all active entities for the closest ( to start ) match that intersects +the line start,end +============= +*/ +idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( c ) && ent != skip ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} + +/* +================ +idGameLocal::EntitiesWithinRadius +================ +*/ +int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { + idEntity *ent; + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + entityList[entCount++] = ent; + } + } + + return entCount; +} + +/* +================= +idGameLocal::KillBox + +Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. +Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. +If catch_teleport, this only marks teleport players for death on exit +================= +*/ +void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idPhysics *phys; + + phys = ent->GetPhysics(); + if ( !phys->GetNumClipModels() ) { + return; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = ClipModelsTouchingBounds( ent, phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == ent ) || !hit->fl.takedamage ) { + continue; + } + + if ( !phys->ClipContents( cm ) ) { + continue; + } + + // nail it +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( hit->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { +// RAVEN END + static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + + if ( !gameLocal.isMultiplayer ) { + // let the mapper know about it + Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() ); + } + } +} + +/* +================ +idGameLocal::RequirementMet +================ +*/ +bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { + if ( requires.Length() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + idPlayer *player = static_cast(activator); + idDict *item = player->FindInventoryItem( requires ); + if ( item ) { + if ( removeItem ) { + player->RemoveInventoryItem( item ); + } + return true; + } else { + return false; + } + } + } + + return true; +} + + +/* +================ +idGameLocal::IsWeaponsStayOn +================ +*/ +bool idGameLocal::IsWeaponsStayOn( void ) { + /// Override weapons stay when buying is active + if( isMultiplayer && mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + return false; + } + + return serverInfo.GetBool( "si_weaponStay" ); +} + + +/* +============ +idGameLocal::AlertAI +============ +*/ +void idGameLocal::AlertAI( idEntity *ent ) { +// RAVEN BEGIN +// bdube: merged + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // alert them for the next frame + lastAIAlertActorTime = time + GetMSec(); + lastAIAlertActor = static_cast( ent ); + } else { + lastAIAlertEntityTime = time + GetMSec(); + lastAIAlertEntity = ent; + } + } else { + lastAIAlertEntityTime = 0; + lastAIAlertActorTime = 0; + lastAIAlertEntity = NULL; + lastAIAlertActor = NULL; + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: alert entity returns an entity, alert actor a an actor +/* +============ +idGameLocal::GetAlertEntity +============ +*/ +idEntity *idGameLocal::GetAlertEntity( void ) { + if ( lastAIAlertEntityTime >= time ) { + return lastAIAlertEntity.GetEntity(); + } + + return NULL; +} + +/* +============ +idGameLocal::GetAlertActor +============ +*/ +idActor *idGameLocal::GetAlertActor( void ) { + if ( lastAIAlertActorTime >= time ) { + return lastAIAlertActor.GetEntity(); + } + + return NULL; +} +// RAVEN END + +/* +============ +SortClipModelsByEntity +============ +*/ +static int SortClipModelsByEntity( const void* left, const void* right ) { + idEntity* leftEntity = left ? (*((const idClipModel**)left))->GetEntity() : NULL; + idEntity* rightEntity = right ? (*((const idClipModel**)right))->GetEntity() : NULL; + + int entityNumLeft = (leftEntity) ? leftEntity->entityNumber : 0; + int entityNumRight = (rightEntity) ? rightEntity->entityNumber : 0; + + return entityNumLeft - entityNumRight; +} + +// RAVEN BEGIN +/* +============ +idGameLocal::RadiusDamage +Returns the number of actors damaged +============ +*/ +// abahr: changed to work with deathPush +void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower, int* hitCount ) { + float dist, damageScale, attackerDamageScale, attackerPushScale; + idEntity* ent = NULL; + idEntity* lastEnt = NULL; + idClipModel* clipModel = NULL; + idClipModel* clipModelList[ MAX_GENTITIES ]; + int numListedClipModels; + modelTrace_t result; + idVec3 v, damagePoint, dir; + int i, damage, radius, push; + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + if( gameLocal.isMultiplayer ) { + damageDef->GetFloat( "attackerPushScale", "2", attackerPushScale ); + } else { + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + } + + if ( radius < 1 ) { + radius = 1; + } + + +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, idBounds(origin).Expand(radius), MASK_ALL, clipModelList, MAX_GENTITIES ); + if( numListedClipModels > 0 ) { + //Sort list by unique entities for easier searching + qsort( clipModelList, numListedClipModels, sizeof(clipModelList[0]), SortClipModelsByEntity ); + } + + if ( inflictor ) { + inflictor = inflictor->GetDamageEntity ( ); + } + if ( attacker ) { + attacker = attacker->GetDamageEntity ( ); + } + if ( ignoreDamage ) { + ignoreDamage = ignoreDamage->GetDamageEntity ( ); + } + + for( int c = 0; c < numListedClipModels; ++c ) { + clipModel = clipModelList[ c ]; + assert( clipModel ); + + ent = clipModel->GetEntity(); + + // Skip all entitys that arent primary damage entities + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } + + // Dont damage inflictor or the ignore entity + if( ent == inflictor || ent == ignoreDamage ) { + continue; + } + + idBounds absBounds = clipModel->GetAbsBounds(); + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < absBounds[0][ i ] ) { + v[ i ] = absBounds[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > absBounds[1][ i ] ) { + v[ i ] = origin[ i ] - absBounds[1][ i ]; + } else { + v[ i ] = 0; + } + } + + dist = v.Length(); + if ( dist >= radius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, absBounds.GetCenter()) ) { + continue; + } + + RadiusPushClipModel ( inflictor, origin, push, clipModel ); + + // Only damage unique entities. This works because we have a sorted list + if( lastEnt == ent ) { + continue; + } + + lastEnt = ent; + + if ( ent->CanDamage( origin, damagePoint, ignoreDamage ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + if( gameLocal.isMultiplayer ) { + // fudge the direction in MP to account for player height difference and origin shift + // 31.875 = origin is 23.875 units lower in Q4 than Q3 + player is 8 units taller in Q4 + dir = ( ent->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 31.875f ) ) - origin; + } else { + dir = ent->GetPhysics()->GetOrigin() - origin; + } + + dir[2] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - dist / radius ); + + if ( ent == attacker ) { + damageScale *= attackerDamageScale; + } + + dir.Normalize(); + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(ent->GetPhysics()->GetClipModel()->GetId()) ); + + // for stats, count the first + if( attacker && attacker->IsType( idPlayer::GetClassType() ) && inflictor && inflictor->IsType( idProjectile::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && hitCount ) { + // with splash damage projectiles, one projectile fire can damage multiple people. If anyone is hit, + // the shot counts as a hit but only report accuracy on the first one to avoid accuracies > 100% + statManager->WeaponHit( (const idActor*)attacker, ent, ((idProjectile*)inflictor)->methodOfDeath, (*hitCount) == 0 ); + (*hitCount)++; + } + } + } +} +// RAVEN END + +/* +============== +idGameLocal::RadiusPush +============== +*/ +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { + int i, numListedClipModels; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 dir; + idBounds bounds; + modelTrace_t result; + idEntity *ent; + float scale; + +// RAVEN BEGIN +// abahr: need to use gravity instead of assuming z is up + dir = -GetCurrentGravity( const_cast(inflictor) ).ToNormal(); +// RAVEN END + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, bounds, -1, clipModelList, MAX_GENTITIES ); + +// bdube: getdamageentity + if ( inflictor ) { + inflictor = ((idEntity*)inflictor)->GetDamageEntity ( ); + } + if ( ignore ) { + ignore = ((idEntity*)ignore)->GetDamageEntity ( ); + } +// RAVEN END + + // apply impact to all the clip models through their associated physics objects + for ( i = 0; i < numListedClipModels; i++ ) { + + clipModel = clipModelList[i]; + + // never push render models + if ( clipModel->IsRenderModel() ) { + continue; + } + + ent = clipModel->GetEntity(); +// RAVEN BEGIN +// bdube: damage entity + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } +// RAVEN END + + // never push projectiles +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + continue; + } + + // players use "knockback" in idPlayer::Damage +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idPlayer::GetClassType() ) && !quake ) { +// RAVEN END + continue; + } + + // don't push the ignore entity + if ( ent == ignore ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { + continue; + } + + // scale the push for the inflictor + if ( ent == inflictor ) { + scale = inflictorScale; + } else { + scale = 1.0f; + } + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { +// RAVEN BEGIN +// bdube: inflictor + RadiusPushClipModel( (idEntity*)inflictor, origin, scale * push, clipModel ); +// RAVEN END + } + } +} + +/* +============== +idGameLocal::RadiusPushClipModel +============== +*/ +// RAVEN BEGIN +// bdube: inflictor +void idGameLocal::RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ) { +// RAVEN END + int i, j; + float dot, dist, area; + const idTraceModel *trm; + const traceModelPoly_t *poly; + idFixedWinding w; + idVec3 v, localOrigin, center, impulse; + + trm = clipModel->GetTraceModel(); + if ( !trm || 1 ) { + impulse = clipModel->GetAbsBounds().GetCenter() - origin; + impulse.Normalize(); +// RAVEN BEGIN +// abahr: removed because z isn't always up + //impulse.z += 1.0f; + //impulse = idVec3( 0.0, 0.0, 1.0 ); + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), clipModel->GetOrigin(), push * impulse, true ); +// RAVEN END + return; + } + + localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); + for ( i = 0; i < trm->numPolys; i++ ) { + poly = &trm->polys[i]; + + center.Zero(); + for ( j = 0; j < poly->numEdges; j++ ) { + v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ]; + center += v; + v -= localOrigin; + v.NormalizeFast(); // project point on a unit sphere + w.AddPoint( v ); + } + center /= poly->numEdges; + v = center - localOrigin; + dist = v.NormalizeFast(); + dot = v * poly->normal; + if ( dot > 0.0f ) { + continue; + } + area = w.GetArea(); + // impulse in polygon normal direction + impulse = poly->normal * clipModel->GetAxis(); + // always push up for nicer effect + impulse.z -= 1.0f; + // scale impulse based on visible surface area and polygon angle + impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); + // scale away distance for nicer effect + impulse *= ( dist * 2.0f ); + // impulse is applied to the center of the polygon + center = clipModel->GetOrigin() + center * clipModel->GetAxis(); + +// RAVEN BEGIN +// bdube: inflictor + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), center, impulse ); +// RAVEN END + } +} + +/* +=============== +idGameLocal::ProjectDecal +=============== +*/ +void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { + float s, c; + idMat3 axis, axistemp; + idFixedWinding winding; + idVec3 windingOrigin, projectionOrigin; + + static idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !g_decals.GetBool() ) { + return; + } + +// RAVEN BEGIN +// rjohnson: no decals on dedicated server + if ( isMultiplayer && !isClient && !isListenServer ) { + // no decals on dedicated server + return; + } +// RAVEN END + + // randomly rotate the decal winding + idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); + + // winding orientation + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + windingOrigin = origin + depth * axis[2]; + if ( parallel ) { + projectionOrigin = origin - depth * axis[2]; + } else { + projectionOrigin = origin; + } + + size *= 0.5f; + + winding.Clear(); + winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); + gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), time ); +} + +/* +============== +idGameLocal::BloodSplat +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple collision models +void idGameLocal::BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dirArg, float size, const char *material ) { + + float halfSize = size * 0.5f; + idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), + idVec3( 0.0f, +halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, +halfSize ) }; + idVec3 dir = dirArg; + idTraceModel trm; + idClipModel mdl; + trace_t results; + +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( !g_decals.GetBool() ) { + return; + } +// RAVEN END + + size = halfSize + random.RandomFloat() * halfSize; + trm.SetupPolygon( verts, 4 ); + mdl.LoadModel( trm, NULL ); + + // I don't want dir to be axis aligned, as it is more likely to streak them (because most architecture is axis aligned + dir.Set( dirArg.x*.1f, dirArg.y*.1f, -1 ); + dir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Translation( ent, results, origin, origin + dir * 72.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); +// RAVEN END + ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); +} + +/* +============= +idGameLocal::SetCamera +============= +*/ +void idGameLocal::SetCamera( idCamera *cam ) { + int i; + idEntity *ent; + idAI *ai; + + // this should fix going into a cinematic when dead.. rare but happens + idPlayer *client = GetLocalPlayer(); + if ( client->health <= 0 || client->pfl.dead ) { + return; + } + + camera = cam; + if ( camera ) { +// RAVEN BEGIN +// bdube: tool support + inCinematic = false; + if( !( gameLocal.editors & ( EDITOR_MODVIEW | EDITOR_PLAYBACKS ) ) ) { + inCinematic = true; + } +// RAVEN END + + if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return; + } + + if ( time > cinematicStopTime ) { + cinematicSkipTime = time + CINEMATIC_SKIP_DELAY; + } + + // set r_znear so that transitioning into/out of the player's head doesn't clip through the view + cvarSystem->SetCVarFloat( "r_znear", 1.0f ); + + // hide all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + client = static_cast< idPlayer* >( entities[ i ] ); + client->EnterCinematic(); + } + } + + if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { + // kill any active monsters that are enemies of the player + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->cinematic || ent->fl.isDormant ) { + // only kill entities that aren't needed for cinematics and aren't dormant + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAI::GetClassType() ) ) { +// RAVEN END + ai = static_cast( ent ); + if ( !ai->GetEnemy() || !ai->IsActive() ) { + // no enemy, or inactive, so probably safe to ignore + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { + // remove anything marked to be removed during cinematics + } else { + // ignore everything else + continue; + } + + // remove it + DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + } else { + inCinematic = false; + cinematicStopTime = time + msec; + + // restore r_znear + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // show all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); + client->ExitCinematic(); + } + } + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +============= +idGameLocal::GetPortalSky +============= +*/ +idCamera *idGameLocal::GetPortalSky( void ) const +{ + if( !portalSkyVisible ) { + + return( NULL ); + } + return( portalSky ); +} +/* +============= +idGameLocal::SetPortalSky +============= +*/ +void idGameLocal::SetPortalSky( idCamera *cam ) +{ + portalSky = cam; +} +// RAVEN END + +/* +============= +idGameLocal::GetCamera +============= +*/ +idCamera *idGameLocal::GetCamera( void ) const { + return camera; +} + +/* +============= +idGameLocal::SkipCinematic +============= +*/ +bool idGameLocal::SkipCinematic( void ) { + if ( camera ) { + if ( camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return false; + } + + if ( camera->spawnArgs.GetBool( "instantSkip" ) ) { + camera->Stop(); + return false; + } + } + + soundSystem->SetMute( true ); + if ( !skipCinematic ) { + skipCinematic = true; + cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() ); + } + + return true; +} + +// RAVEN BEGIN +/* +====================== +idGameLocal::StartViewEffect + +For passing in an effect triggered view effect +====================== +*/ +void idGameLocal::StartViewEffect( int type, float time, float scale ) +{ + idPlayer *player; + idPlayerView *view; + + player = GetLocalPlayer(); + if( player ) + { + view = &player->playerView; + + switch( type ) + { + case VIEWEFFECT_DOUBLEVISION: + view->SetDoubleVisionParms( time, scale ); + break; + + case VIEWEFFECT_SHAKE: + if( !gameLocal.isMultiplayer ) { + view->SetShakeParms( time, scale ); + } + break; + + case VIEWEFFECT_TUNNEL: + view->SetTunnelParms( time, scale ); + break; + + default: + gameLocal.Warning( "Invalid view effect" ); + break; + } + } +} + +/* +====================== +idGameLocal::GetPlayerView +====================== +*/ +void idGameLocal::GetPlayerView( idVec3 &origin, idMat3 &axis ) +{ + idPlayer *player; + renderView_t *view; + + player = GetLocalPlayer(); + if( player ) + { + view = player->GetRenderView(); + origin = view->vieworg; + axis = view->viewaxis; + } + else + { + origin = vec3_origin; + axis = mat3_identity; + } +} + +/* +====================== +idGameLocal::Translation + +small portion of physics required for the effects system +====================== +*/ +void idGameLocal::Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) { + + if( !trm ) { + // HACK + clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL ); + } + else { + idClipModel cm; + + cm.LoadModel( *trm, NULL ); + // HACK + clip[0]->Translation( trace, source, dest, &cm, mat3_identity, clipMask, NULL, NULL ); + } +} + +/* +====================== +idGameLocal::SpawnClientMoveable +====================== +*/ +void idGameLocal::SpawnClientMoveable( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) { + // find the debris def + const idDict* args = gameLocal.FindEntityDefDict( name, false ); + if ( !args ) { + return; + } + + // Ensure client moveables never last forever + if ( lifetime <= 0 ) { + lifetime = SEC2MS(args->GetFloat( "duration", "5" )); + } + int burn_time = idMath::ClampInt( 0, lifetime, SEC2MS(args->GetFloat( "burn_time", "2.5" )) ); + + // Spawn the debris + + rvClientMoveable* cent = NULL; + // force the args to spawn a rvClientMoveable + SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + return; + } + + cent->SetOrigin( origin ); + cent->SetAxis( axis ); + + cent->GetPhysics()->SetLinearVelocity( velocity ); + cent->GetPhysics()->SetAngularVelocity( angular_velocity ); + + if ( !burn_time ) { + //just disappear + cent->PostEventMS( &EV_Remove, lifetime ); + } else { + cent->PostEventMS( &CL_FadeOut, lifetime-burn_time, burn_time ); + } +} + +/* +====================== +idGameLocal::DebugSet +====================== +*/ +void idGameLocal::DebugSetString( const char* name, const char* value ) { + gameDebug.SetString( name, value ); +} +void idGameLocal::DebugSetFloat( const char* name, float value ) { + gameDebug.SetFloat( name, value ); +} +void idGameLocal::DebugSetInt( const char* name, int value ) { + gameDebug.SetInt( name, value ); +} + +/* +====================== +idGameLocal::DebugGetStat +====================== +*/ +const char* idGameLocal::DebugGetStatString ( const char* name ) { + return gameDebug.GetStatString( name ); +} + +int idGameLocal::DebugGetStatInt ( const char* name ) { + return gameDebug.GetStatInt( name ); +} + +float idGameLocal::DebugGetStatFloat ( const char* name ) { + return gameDebug.GetStatFloat( name ); +} + +/* +====================== +idGameLocal::IsDebugHudActive +====================== +*/ +bool idGameLocal::IsDebugHudActive ( void ) const { + return gameDebug.IsHudActive( DBGHUD_ANY ); +} + + +// rjohnson: added player info support for note taking system +/* +====================== +idGameLocal::GetPlayerInfo +====================== +*/ +bool idGameLocal::GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum, idAngles *deltaViewAngles, int reqClientNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if( reqClientNum != -1 ) { + idPlayer* reqClient = GetClientByNum( reqClientNum ); + if( reqClient && player ) { + if( reqClient->GetInstance() != player->GetInstance() ) { + return false; + } + } + } + + if ( !player ) { + return false; + } + + player->GetViewPos( origin, axis ); + origin = player->GetPhysics()->GetOrigin(); + + if ( deltaViewAngles ) { + *deltaViewAngles = player->GetDeltaViewAngles(); + } + + return true; +}; + +/* +====================== +idGameLocal::SetCurrentPlayerInfo +====================== +*/ +void idGameLocal::SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if ( !player ) { + return; + } + + player->Teleport( origin, axis.ToAngles(), NULL ); +// RAVEN BEGIN +// ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->CalculateFirstPersonView(); + player->CalculateRenderView(); + currentThinkingEntity = NULL; +// RAVEN END + + return; +}; + +bool idGameLocal::PlayerChatDisabled( int clientNum ) { + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !entities[ clientNum ] ) { + return false; + } + + return !( ((idPlayer*)entities[ clientNum ])->isChatting || ((idPlayer*)entities[ clientNum ])->pfl.dead ); +} + +void idGameLocal::SetViewComments( const char *text ) { + idPlayer *player; + + player = GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( text ) { + player->hud->SetStateString( "viewcomments", text ); + player->hud->HandleNamedEvent( "showViewComments" ); + } + else { + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); + } +} + +/* +=================== +idGameLocal::GetNumGravityAreas +=================== +*/ +int idGameLocal::GetNumGravityAreas() const { + return gravityInfo.Num(); +} + +/* +=================== +idGameLocal::GetGravityInfo +=================== +*/ +const rvGravityArea* idGameLocal::GetGravityInfo( int index ) const { + return gravityInfo[ index ]; +} + +/* +=================== +idGameLocal::SetGravityArea +=================== +*/ +void idGameLocal::SetGravityInfo( int index, rvGravityArea* info ) { + gravityInfo[ index ] = info; +} + +/* +=================== +idGameLocal::AddUniqueGravityInfo +=================== +*/ +void idGameLocal::AddUniqueGravityInfo( rvGravityArea* info ) { + gravityInfo.AddUnique( info ); +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( const idVec3& origin ) const { + int numGravityAreas = GetNumGravityAreas(); + if( !numGravityAreas ) { + return -1; + } + + int areaNum = gameRenderWorld->PointInArea( origin ); + + for( int ix = 0; ix < numGravityAreas; ++ix ) { + if( !gameRenderWorld->AreasAreConnected(GetGravityInfo(ix)->GetArea(), areaNum, PS_BLOCK_GRAVITY) ) { + continue; + } + + return ix; + } + + return -1; +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( idEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const { + int index = GetCurrentGravityInfoIndex( origin ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(origin, axis, MASK_SOLID, NULL) : GetGravity(); +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( rvClientEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::ReferenceScriptObjectProxy +=================== +*/ +idEntity* idGameLocal::ReferenceScriptObjectProxy( const char* scriptObjectName ) { + idEntity* proxy = NULL; + idEntityPtr safeProxy; + idDict args; + idScriptObject* object = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + proxy = scriptObjectProxies[ ix ].GetEntity(); + assert( proxy ); + + object = &proxy->scriptObject; + if( !object->data ) { + object->SetType( scriptObjectName ); + proxy->ConstructScriptObject(); + return proxy; + } + } + + args.Set( "classname", "func_static" ); + args.Set( "scriptobject", scriptObjectName ); + args.SetBool( "noclipmodel", true ); + bool spawned = SpawnEntityDef(args, &proxy); + if ( !spawned ) { + assert( 0 ); + } + safeProxy = proxy; + scriptObjectProxies.AddUnique( safeProxy ); + return proxy; +} + +/* +=================== +idGameLocal::ReleaseScriptObjectProxy +=================== +*/ +void idGameLocal::ReleaseScriptObjectProxy( const char* proxyName ) { + idScriptObject* object = NULL; + idEntity* entity = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + entity = scriptObjectProxies[ ix ].GetEntity(); + if( entity && !idStr::Icmp(entity->GetName(), proxyName) ) { + object = &entity->scriptObject; + if( !object ) { + continue; + } + + entity->DeconstructScriptObject(); + object->Free(); + } + } +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +void idGameLocal::ListEntityStats( const idCmdArgs &args ) { + int i, j; + idStr currentMap; + idList uniqueMapNames; + + + for( i = 1; i < args.Argc(); i++ ) { + if ( idStr::Icmp( args.Argv( i ), "clear" ) == 0 ) { + entityUsageList.Clear(); + common->Printf("Entity stats cleared.\n"); + return; + } + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[ i ].SetInt( "reported_stat", false ); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) ) { + continue; + } + + entityUsageList[ i ].GetString( "mapFileName", "none", mapFileName ); + if ( currentMap != mapFileName ) + { + if ( i ) { + common->Printf( "\n" ); + } + common->Printf( "================ %s ================\n", mapFileName.c_str() ); + currentMap = mapFileName; + uniqueMapNames.Insert( mapFileName ); + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + if ( checkMapFileName != mapFileName ) { + break; + } + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 1 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + common->Printf( "\n" ); + common->Printf( "\n" ); + common->Printf( "================ OVERALL ================\n" ); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 2 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 2 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + idFile *FH = fileSystem->OpenFileWrite( "EntityStats.csv" ); + if ( FH ) { + int size = sizeof( int ) * uniqueMapNames.Num(); + int *count = ( int * )_alloca( size ); + + FH->Printf("\"Definition\""); + for( i = 0; i < uniqueMapNames.Num(); i++ ) { + FH->Printf( ",\"%s\"", uniqueMapNames[ i ].c_str() ); + } + FH->Printf(",Total\n"); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr className; + int total; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 3 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + + memset( count, 0, size ); + for( j = i; j < entityUsageList.Num(); j++ ) + { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 3 ); + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + + int loc = uniqueMapNames.FindIndex( checkMapFileName ); + if ( loc >= 0 ) { + count[ loc ]++; + } + } + } + + total = 0; + FH->Printf( "\"%s\"", className.c_str() ); + for( j = 0; j < uniqueMapNames.Num(); j++ ) { + FH->Printf( ",%d", count[ j ] ); + total += count[ j ]; + } + FH->Printf( ",%d\n", total ); + } + + fileSystem->CloseFile( FH ); + } +} +// RAVEN END + +/* +====================== +idGameLocal::SpreadLocations + +Now that everything has been spawned, associate areas with location entities +====================== +*/ +void idGameLocal::SpreadLocations() { + idEntity *ent; + +// RAVEN BEGIN + if( !gameRenderWorld ) { + common->Error( "GameRenderWorld is NULL!" ); + } +// RAVEN END + + // allocate the area table + int numAreas = gameRenderWorld->NumAreas(); + locationEntities = new idLocationEntity *[ numAreas ]; + memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); + + // for each location entity, make pointers from every area it touches + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLocationEntity::GetClassType() ) ) { +// RAVEN END + continue; + } + idVec3 point = ent->spawnArgs.GetVector( "origin" ); + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); + continue; + } + if ( areaNum >= numAreas ) { + Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), + locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); + continue; + } + locationEntities[areaNum] = static_cast(ent); + + // spread to all other connected areas + for ( int i = 0 ; i < numAreas ; i++ ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(ent); + } + } + } +} + +/* +=================== +idGameLocal::AddLocation +=================== +*/ +idLocationEntity* idGameLocal::AddLocation( const idVec3& point, const char* name ) { + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Warning ( "idGameLocal::AddLocation: cannot add location entity '%s' at '%g %g %g'\n", name, point.x, point.y, point.z ); + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::AddLocation: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning ( "idGameLocal::AddLocation: location '%s' already exists at '%g %g %g'\n", locationEntities[areaNum]->GetName(), point.x, point.y, point.z ); + return NULL; + } + + // Spawn the new location entity + idDict args; + args.Set ( "location", name ); + locationEntities[areaNum] = static_cast(SpawnEntityType ( idLocationEntity::GetClassType(), &args )); + + // spread to all other connected areas + for ( int i = gameRenderWorld->NumAreas() - 1 ; i >= 0 ; i-- ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(locationEntities[areaNum]); + } + } + + return locationEntities[areaNum]; +} + +/* +=================== +idGameLocal::LocationForPoint + +The player checks the location each frame to update the HUD text display +May return NULL +=================== +*/ +idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { + if ( !locationEntities ) { + // before SpreadLocations() has been called + return NULL; + } + + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); + } + + return locationEntities[ areaNum ]; +} + +/* +============ +idGameLocal::SetPortalState +============ +*/ +void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL ); + outMsg.WriteLong( portal ); + outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + gameRenderWorld->SetPortalState( portal, blockingBits ); +} + +/* +============ +idGameLocal::sortSpawnPoints +============ +*/ +int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { + const spawnSpot_t *spot1 = static_cast( ptr1 ); + const spawnSpot_t *spot2 = static_cast( ptr2 ); + float diff; + + diff = spot1->dist - spot2->dist; + if ( diff < 0.0f ) { + return 1; + } else if ( diff > 0.0f ) { + return -1; + } else { + return 0; + } +} + +// RAVEN BEGIN +// ddynerman: new gametype specific spawn code +// TODO this should be moved to idMultiplayerGame +/* +=========== +idGameLocal::InitializeSpawns +randomize the order of the initial spawns +prepare for a sequence of initial player spawns +============ +*/ +void idGameLocal::InitializeSpawns( void ) { + idEntity* spot = NULL; + + // initialize the spawns for clients as well, need them for free fly demo replays + if ( !isMultiplayer ) { + return; + } + + spawnSpots.Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[i].Clear(); + } + + spot = FindEntityUsingDef( NULL, "info_player_team" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + spot = FindEntityUsingDef( NULL, "info_player_deathmatch" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + spawnSpots.Append ( static_cast(spot) ); + } + spot = FindEntityUsingDef( spot, "info_player_deathmatch" ); + } + + while( spot ) { + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { + // RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + if( IsFlagGameType() && ( teamSpawnSpots[ TEAM_STROGG ].Num() == 0 || teamSpawnSpots[ TEAM_MARINE ].Num() == 0 ) ) { + Error( "InitializeSpawns() - Map must have at least one Marine and one Strogg spawn for CTF gametype."); + } + + if( spawnSpots.Num() == 0 ) { + Error( "InitializeSpawns() - Map must have a spawn spot." ); + } + + common->Printf( "%d general spawns\n", spawnSpots.Num() ); + common->Printf( "%d team spawns (%d strogg/%d marine)\n", teamSpawnSpots[TEAM_STROGG].Num() + teamSpawnSpots[TEAM_MARINE].Num(), + teamSpawnSpots[TEAM_STROGG].Num(), teamSpawnSpots[TEAM_MARINE].Num()); +} + +/* +=========== +idGameLocal::UpdateForwardSpawn +ddynerman: Updates forward spawn lists +=========== +*/ +void idGameLocal::UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ) { + teamForwardSpawnSpots[ team ].Append( point ); +} + +/* +=========== +idGameLocal::ClearForwardSpawn +ddynerman: Clears forward spawn lists +=========== +*/ +void idGameLocal::ClearForwardSpawns( void ) { + for( int i = 0; i < TEAM_MAX; i++ ) { + teamForwardSpawnSpots[ i ].Clear(); + } +} + +/* +=========== +idGameLocal::SpotWouldTelefrag +=========== +*/ +bool idGameLocal::SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ) { + idPlayer* playerList[ MAX_CLIENTS ]; + idBounds bound = player->GetPhysics()->GetBounds(); + + bound.TranslateSelf( spawn->GetPhysics()->GetOrigin() ); + int numEntities = PlayersTouchingBounds( player, bound, CONTENTS_BODY, playerList, MAX_CLIENTS ); + + return !( numEntities == 0 ); +} + +/* +=========== +idGameLocal::SelectSpawnSpot +ddynerman: Selects a spawn spot randomly from spots furthest from the player + This is taken from q3 +=========== +*/ +idEntity* idGameLocal::SelectSpawnPoint( idPlayer* player ) { + if( !isMultiplayer ) { + idEntity* ent = FindEntityUsingDef( NULL, "info_player_start" ); + if ( !ent ) { + Error( "No info_player_start on map.\n" ); + } + return ent; + } + + if ( player == NULL ) { + return NULL; + } + + // Give spectators any old random spot + if ( player->team < 0 || player->team >= TEAM_MAX || player->spectating ) { + common->DPrintf("Returning a random spot\n"); + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; + } + + idList weightedSpawns; + idList* spawnArray = NULL; + + // Pick which spawns to use based on gametype +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_TOURNEY ) { + spawnArray = &spawnSpots; + } + else if( IsFlagGameType() || gameLocal.gameType == GAME_DEADZONE ) { + if( teamForwardSpawnSpots[ player->team ].Num() ) { + spawnArray = &teamForwardSpawnSpots[ player->team ]; + } else { + spawnArray = &teamSpawnSpots[ player->team ]; + } + } +// RITUAL END + + if ( spawnArray == NULL ) { + Error( "SelectSpawnPoint() - invalid spawn list." ); + return NULL; + } + + idVec3 refPos; + if ( player->lastKiller != NULL && !player->lastKiller->spectating && player->lastKiller->GetInstance() == player->GetInstance() ) { + refPos = player->lastKiller->GetPhysics()->GetOrigin(); + } else { + refPos = player->GetPhysics()->GetOrigin(); + } + + for ( int i = 0; i < spawnArray->Num(); i++ ) { + idPlayerStart* spot = (*spawnArray)[i]; + + if ( spot->GetInstance() != player->GetInstance() || SpotWouldTelefrag( player, spot ) ) { + continue; + } + + idVec3 pos = spot->GetPhysics()->GetOrigin(); + float dist = ( pos - refPos ).LengthSqr(); + + spawnSpot_t newSpot; + + newSpot.dist = dist; + newSpot.ent = (*spawnArray)[ i ]; + weightedSpawns.Append( newSpot ); + } + + if ( weightedSpawns.Num() == 0 ) { + // no spawns avaialable, spawn randomly + common->DPrintf("no spawns avaialable, spawn randomly\n"); + return (*spawnArray)[ random.RandomInt( spawnArray->Num() ) ]; + } + + qsort( ( void * )weightedSpawns.Ptr(), weightedSpawns.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + int rnd = rvRandom::flrand( 0.0, 1.0 ) * (weightedSpawns.Num() / 2); + return weightedSpawns[ rnd ].ent; +} +/* +================ +idGameLocal::UpdateServerInfoFlags +================ +*/ +// RAVEN BEGIN +// ddynerman: new gametype strings +void idGameLocal::SetGameType( void ) { + gameType = GAME_SP; + + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "singleplayer" ) ) { + mpGame.SetGameType(); + } +} +// RAVEN END + +/* +================ +idGameLocal::SetGlobalMaterial +================ +*/ +void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { + globalMaterial = mat; +} + +/* +================ +idGameLocal::GetGlobalMaterial +================ +*/ +const idMaterial *idGameLocal::GetGlobalMaterial() { + return globalMaterial; +} + +/* +================ +idGameLocal::GetSpawnId +================ +*/ +int idGameLocal::GetSpawnId( const idEntity* ent ) const { + return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameLocal::ThrottleUserInfo +================ +*/ +void idGameLocal::ThrottleUserInfo( void ) { + mpGame.ThrottleUserInfo(); +} + +/* +=========== +idGameLocal::ValidateServerSettings +============ +*/ +bool idGameLocal::ValidateServerSettings( const char* map, const char* gametype ) { + // PickMap uses si_map directly + // PickMap returns wether we would have to change the maps, which means settings are invalid + assert( !idStr::Icmp( si_map.GetString(), map ) ); + if ( mpGame.PickMap( gametype, true ) ) { + common->Printf( "map '%s' and gametype '%s' are not compatible\n", map, gametype ); + return false; + } + return true; +} + +/* +=========== +idGameLocal::NeedRestart +============ +*/ +bool idGameLocal::NeedRestart() { + + idDict newInfo; + const idKeyValue *keyval, *keyval2; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + + for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + return true; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + return true; + } + } + return false; +} + +// RAVEN BEGIN +// jshepard: update player hud to alert to end of level +/* +=================== +idGameLocal::UpdateEndLevel +=================== +*/ +void idGameLocal::UpdateEndLevel() { + idPlayer * player = GetLocalPlayer(); + + if( player && player->GetHud() ) { + player->GetHud()->HandleNamedEvent( "showExit" ); + } +} + + +// bdube: added +/* +================ +idGameLocal::GetEffect + +Get the handle of the effect with the given name +================ +*/ +const idDecl *idGameLocal::GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType ) { + const char *effectFile = NULL; + + float chance = args.GetFloat ( idStr("effectchance ") + effectName, "1" ); + if ( random.RandomFloat ( ) > chance ) { + return NULL; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + assert( !idStr::Icmpn( effectName, "fx_", 3 ) ); + + if ( materialType ) { + idStr temp; + const char* result = NULL; + + temp = effectName; + temp += "_"; + temp += materialType->GetName(); + + // See if the given material effect is specified + if ( isMultiplayer ) { + idStr testMP = temp; + testMP += "_mp"; + + result = args.GetString( testMP ); + } + if ( !result || !*result ) { + result = args.GetString( temp ); + } + if ( result && *result) { + return( ( const idDecl * )declManager->FindEffect( result ) ); + } + } + + // grab the non material effect name + if ( isMultiplayer ) { + idStr testMP = effectName; + testMP += "_mp"; + + effectFile = args.GetString( testMP ); + } + + if ( !effectFile || !*effectFile ) { + effectFile = args.GetString( effectName ); + } + + if ( !effectFile || !*effectFile ) { + return NULL; + } + + return( ( const idDecl * )declManager->FindEffect( effectFile ) ); +} + +/* +================ +idGameLocal::PlayEffect + +Plays an effect at the given origin using the given direction +================ +*/ +rvClientEffect* idGameLocal::PlayEffect( + const idDecl *effect, + const idVec3& origin, + const idMat3& axis, + bool loop, + const idVec3& endOrigin, + bool broadcast, + bool predictBit, + effectCategory_t category, + const idVec4& effectTint ) { + + if ( !effect ) { + return NULL; + } + + if ( !gameLocal.isNewFrame ) { + return NULL; + } + + if ( isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idCQuat quat; + + quat = axis.ToCQuat(); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EFFECT ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( origin.x ); + msg.WriteFloat( origin.y ); + msg.WriteFloat( origin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + + // send to everyone who has start or end in it's PVS + SendUnreliableMessagePVS( msg, currentThinkingEntity, pvs.GetPVSArea( origin ), pvs.GetPVSArea( endOrigin ) ); + } + + if ( isServer && localClientNum < 0 ) { + // no effects on dedicated server + return NULL; + } + + if ( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } +// RAVEN END + + if ( gameLocal.isListenServer && currentThinkingEntity && gameLocal.GetLocalPlayer() ) { + if ( currentThinkingEntity->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( origin ); + clientEffect->SetAxis( axis ); + clientEffect->SetGravity( GetCurrentGravity( origin, axis ) ); + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +void idGameLocal::CheckPlayerWhizzBy( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ) +{ + //FIXME: make this client-side? Work in MP? + if ( gameLocal.isMultiplayer ) { + return; + } + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + if ( player->IsHidden() ) { + return; + } + if ( player == attacker ) { + return; + } + if ( player == hitEnt ) { + return; + } + idVec3 center = player->firstPersonViewOrigin; + idVec3 diff = end-center; + if ( diff.Length() < 64.0f ) { + //hit too close - didn't actually pass by, will hear impact sound instead + return; + } + idVec3 closestPoint = player->firstPersonViewOrigin; + if ( closestPoint.ProjectToLineSeg( start, end ) ) { + //on line seg + diff = closestPoint-center; + if ( diff.Length() < 48.0f ) { + //close enough to hear whizz-by + idVec3 dir = end-start; + dir.Normalize(); + idVec3 fxStart = closestPoint+dir*-32.0f; + idVec3 fxEnd = closestPoint+dir*32.0f; + player->PlayEffect( "fx_whizby", fxStart, player->firstPersonViewAxis, false, fxEnd ); + } + } +} + +/* +================ +idGameLocal::HitScan + +Run a hitscan trace from the given origin and direction +================ +*/ +idEntity* idGameLocal::HitScan( + const idDict& hitscanDict, + const idVec3& origOrigin, + const idVec3& origDir, + const idVec3& origFxOrigin, + idEntity* owner, + bool noFX, + float damageScale, +// twhitaker: added additionalIgnore parameter + idEntity* additionalIgnore, + int areas[ 2 ] + ) { + + idVec3 dir; + idVec3 origin; + idVec3 fxOrigin; + idVec3 fxDir; + idVec3 impulse; + idVec4 hitscanTint( 1.0f, 1.0f, 1.0f, 1.0f ); + int reflect; + float tracerChance; + idEntity* ignore; + float penetrate; + + if ( areas ) { + areas[ 0 ] = pvs.GetPVSArea( origFxOrigin ); + areas[ 1 ] = -1; + } + + ignore = owner; + penetrate = hitscanDict.GetFloat( "penetrate" ); + + if( hitscanDict.GetBool( "hitscanTint" ) && owner->IsType( idPlayer::GetClassType() ) ) { + hitscanTint = ((idPlayer*)owner)->GetHitscanTint(); + } + + // twhitaker: additionalIgnore parameter + if ( !additionalIgnore ) { + additionalIgnore = ignore; + } + + origin = origOrigin; + fxOrigin = origFxOrigin; + dir = origDir; + tracerChance = ((g_perfTest_weaponNoFX.GetBool())?0:hitscanDict.GetFloat( "tracerchance", "0" )); + + // Apply player powerups + if ( owner && owner->IsType( idPlayer::GetClassType() ) ) { + damageScale *= static_cast(owner)->PowerUpModifier(PMOD_PROJECTILE_DAMAGE); + } + + // Run reflections + for ( reflect = hitscanDict.GetFloat( "reflect", "0" ); reflect >= 0; reflect-- ) { + idVec3 start; + idVec3 end; + idEntity* ent; + idEntity* actualHitEnt; + trace_t tr; + int contents; + int collisionArea; + idVec3 collisionPoint; + bool tracer; + + // Calculate the end point of the trace + start = origin; + if ( g_perfTest_hitscanShort.GetBool() ) { + end = start + (dir.ToMat3() * idVec3(idMath::ClampFloat(0,2048,hitscanDict.GetFloat ( "range", "2048" )),0,0)); + } else { + end = start + (dir.ToMat3() * idVec3(hitscanDict.GetFloat ( "range", "40000" ),0,0)); + } + if ( g_perfTest_hitscanBBox.GetBool() ) { + contents = MASK_SHOT_BOUNDINGBOX|CONTENTS_PROJECTILE; + } else { + contents = MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE; + } + + // Loop the traces to handle cases where something can be shot through + while ( 1 ) { + // Trace to see if we hit any entities +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( hitscanDict.GetFloat( "trace_size", "0" ) > 0.0f ) + { + float range = hitscanDict.GetFloat ( "range", "1024" ); + if ( range > 4096.0f ) + { + assert( !(range > 4096.0f) ); + Warning( "idGameLocal::HitScan: hitscan def (%s) with trace_size must have max range of 4096!", hitscanDict.GetString( "classname" ) ); + range = idMath::ClampFloat( 0.0f, 4096.0f, range ); + } + end = start + (dir * range); + + idBounds traceBounds; + traceBounds.Zero(); + traceBounds.ExpandSelf( hitscanDict.GetFloat( "trace_size", "0" ) ); + // twhitaker: additionalIgnore parameter + TraceBounds( owner, tr, start, end, traceBounds, contents, additionalIgnore ); + } + else + { + // twhitaker: additionalIgnore parameter + TracePoint( owner, tr, start, end, contents, additionalIgnore ); + } + //gameRenderWorld->DebugArrow( colorRed, start, end, 10, 5000 ); +// RAVEN END + + // If the hitscan hit a no impact surface we can just return out + //assert( tr.c.material ); + if ( tr.fraction >= 1.0f || (tr.c.material && tr.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + PlayEffect( hitscanDict, "fx_path", fxOrigin, dir.ToMat3(), false, tr.endpos, false, EC_IGNORE, hitscanTint ); + if ( random.RandomFloat( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, dir.ToMat3(), false, tr.endpos ); + tracer = true; + } else { + tracer = false; + } + + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.endpos ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + + return NULL; + } + + // computing the collisionArea from the collisionPoint fails sometimes + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.c.point ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + collisionPoint = tr.c.point - ( tr.c.normal * tr.c.point - tr.c.dist ) * tr.c.normal; + ent = entities[ tr.c.entityNum ]; + actualHitEnt = NULL; + start = collisionPoint; + + // Keep tracing if we hit water + if ( (ent->GetPhysics()->GetContents() & CONTENTS_WATER) || (tr.c.material && (tr.c.material->GetContentFlags() & CONTENTS_WATER)) ) { + // Apply force to the water entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -(hitscanDict.GetFloat( "push", "5000" )) * tr.c.normal ); + // Continue on excluding water + contents &= (~CONTENTS_WATER); + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + continue; + // Reflect off a bounce target? + } else if ( (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && !hitscanDict.GetBool ( "noBounce" ) ) { + reflect++; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->fl.takedamage && ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + if ( !gameLocal.isClient ) { + + // Apply force to the entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -tr.c.normal, &hitscanDict ); + + // Handle damage to the entity + if ( ent->fl.takedamage && !(( tr.c.material != NULL ) && ( tr.c.material->GetSurfaceFlags() & SURF_NODAMAGE )) ) { + const char* damage; + + damage = NULL; + + // RAVEN BEGIN + // jdischler: code from the other project..to ensure that if an attached head is hit, the body will use the head joint + // otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(tr.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == tr.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } + // RAVEN END + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + + if ( damage && damage[0] ) { + // RAVEN BEGIN + // ddynerman: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, ((idPlayer*)owner)->GetCurrentWeapon() ); + } + // RAVEN END + ent->Damage( owner, owner, dir, damage, damageScale, hitJoint ); + } + + // Let the entity add its own damage effect + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect ( tr, dir, damage, owner ); + } + } else { + if ( actualHitEnt + && actualHitEnt != ent + && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + const char* damage = NULL; + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + if ( damage && damage[0] ) { + actualHitEnt->Damage( owner, owner, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( tr.c.id ) ); + } + } + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect( tr, dir, hitscanDict.GetString ( "def_damage" ), owner ); + } + } + } + + + // Pass through actors + if ( ent->IsType ( idActor::GetClassType() ) && penetrate > 0.0f ) { + start = collisionPoint; + additionalIgnore = ent; + damageScale *= penetrate; + continue; + } + break; + } + + // Path effect + fxDir = collisionPoint - fxOrigin; + fxDir.Normalize( ); + PlayEffect( hitscanDict, "fx_path", fxOrigin, fxDir.ToMat3(), false, collisionPoint, false, EC_IGNORE, hitscanTint ); + if ( !ent->fl.takedamage && random.RandomFloat ( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, fxDir.ToMat3(), false, collisionPoint ); + tracer = true; + } else { + tracer = false; + } + + if ( !reflect ) { + //on initial trace only + if ( hitscanDict.GetBool( "doWhizz" ) ) { + //play whizz-by sound if trace is close to player's head + CheckPlayerWhizzBy( origin, collisionPoint, ent, owner ); + } + } + + // Play a different effect when reflecting + if ( !reflect || ent->fl.takedamage ) { + idMat3 axis; + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType ( idActor::GetClassType() ) ) { + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + } else { + axis = tr.c.normal.ToMat3(); + } + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + // End of reflection + return ent; + } else { + PlayEffect( GetEffect( hitscanDict, "fx_reflect", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3() ); + } + + // Calc new diretion based on bounce + origin = start; + fxOrigin = start; + dir = ( dir - ( 2.0f * DotProduct( dir, tr.c.normal ) * tr.c.normal ) ); + dir.Normalize( ); + + // Increase damage scale on reflect + damageScale += hitscanDict.GetFloat( "reflect_powerup", "0" ); + } + + assert( false ); + + return NULL; +} + +/* +=================== +idGameLocal::RegisterClientEntity +=================== +*/ +void idGameLocal::RegisterClientEntity( rvClientEntity *cent ) { + int entityNumber; + + assert ( cent ); + + if ( clientSpawnCount >= ( 1 << ( 32 - CENTITYNUM_BITS ) ) ) { +// Error( "idGameLocal::RegisterClientEntity: spawn count overflow" ); + clientSpawnCount = INITIAL_SPAWN_COUNT; + } + + // Find a free entity index to use + while( clientEntities[firstFreeClientIndex] && firstFreeClientIndex < MAX_CENTITIES ) { + firstFreeClientIndex++; + } + + if ( firstFreeClientIndex >= MAX_CENTITIES ) { + cent->PostEventMS ( &EV_Remove, 0 ); + Warning( "idGameLocal::RegisterClientEntity: no free client entities" ); + return; + } + + entityNumber = firstFreeClientIndex++; + + // Add the client entity to the lists + clientEntities[ entityNumber ] = cent; + clientSpawnIds[ entityNumber ] = clientSpawnCount++; + cent->entityNumber = entityNumber; + cent->spawnNode.AddToEnd( clientSpawnedEntities ); + cent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( entityNumber >= num_clientEntities ) { + num_clientEntities++; + } +} + +/* +=================== +idGameLocal::UnregisterClientEntity +=================== +*/ +void idGameLocal::UnregisterClientEntity( rvClientEntity* cent ) { + assert( cent ); + + // No entity number then it failed to register + if ( cent->entityNumber == -1 ) { + return; + } + + cent->spawnNode.Remove ( ); + cent->bindNode.Remove ( ); + + if ( clientEntities [ cent->entityNumber ] == cent ) { + clientEntities [ cent->entityNumber ] = NULL; + clientSpawnIds[ cent->entityNumber ] = -1; + if ( cent->entityNumber < firstFreeClientIndex ) { + firstFreeClientIndex = cent->entityNumber; + } + cent->entityNumber = -1; + } +} + +// RAVEN BEGIN +// ddynerman: idClip wrapper functions + +/* +=================== +idGameLocal::Translation +=================== +*/ +bool idGameLocal::Translation( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Translation( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } + + return false; +} + +/* +=================== +idGameLocal::Rotation +=================== +*/ +bool idGameLocal::Rotation( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::Motion +=================== +*/ +bool idGameLocal::Motion( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Motion( results, start, end, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + + +/* +=================== +idGameLocal::Contacts +=================== +*/ +int idGameLocal::Contacts( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contacts( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, passEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::Contents +=================== +*/ +int idGameLocal::Contents( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contents( start, mdl, trmAxis, contentMask, passEntity, touchedEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::TracePoint +=================== +*/ +bool idGameLocal::TracePoint( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TracePoint( results, start, end, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TraceBounds +=================== +*/ +bool idGameLocal::TraceBounds( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TraceBounds( results, start, end, bounds, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TranslationModel +=================== +*/ +void idGameLocal::TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationModel( results, start, end, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::RotationModel +=================== +*/ +void idGameLocal::RotationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->RotationModel( results, start, rotation, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::ContactsModel +=================== +*/ +int idGameLocal::ContactsModel( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContactsModel( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::ContentsModel +=================== +*/ +int idGameLocal::ContentsModel( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContentsModel( start, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::TranslationEntities +=================== +*/ +void idGameLocal::TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationEntities( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } +} + +/* +=================== +idGameLocal::GetModelContactFeature +=================== +*/ +bool idGameLocal::GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->GetModelContactFeature( contact, clipModel, winding ); + } + + return false; +} + +/* +=================== +idGameLocal::EntitiesTouchingBounds +=================== +*/ +int idGameLocal::EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->EntitiesTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::ClipModelsTouchingBounds +=================== +*/ +int idGameLocal::ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ClipModelsTouchingBounds( bounds, contentMask, clipModelList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::PlayersTouchingBounds +=================== +*/ +int idGameLocal::PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->PlayersTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::GetWorldBounds +=================== +*/ +const idBounds& idGameLocal::GetWorldBounds( const idEntity* ent ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->GetWorldBounds(); + } + + return clip[ 0 ]->GetWorldBounds(); +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +const idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) const { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::AddClipWorld +=================== +*/ +int idGameLocal::AddClipWorld( int id ) { + if( id >= clip.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = clip.Num(); i <= id; i++ ) { + clip.Append( NULL ); + } + } + + if( clip[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); +// RAVEN END + clip[ id ] = new idClip(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip[ id ]->Init(); + } + return id; +} + +/* +=================== +idGameLocal::RemoveClipWorld +=================== +*/ +void idGameLocal::RemoveClipWorld( int id ) { + assert( id >= 0 && id < clip.Num() ); + + clip[ id ]->Shutdown(); + delete clip[ id ]; + clip[ id ] = NULL; +} + +/* +=================== +idGameLocal::ShutdownInstances +=================== +*/ +void idGameLocal::ShutdownInstances( void ) { + if( gamestate == GAMESTATE_UNINITIALIZED ) { + return; + } + + instances.DeleteContents( true ); + + // free the trace model used for the defaultClipModel + idClip::FreeDefaultClipModel(); +} + +/* +=================== +idGameLocal::AddInstance +=================== +*/ +int idGameLocal::AddInstance( int id, bool deferPopulate ) { + if ( id == -1 ) { + id = instances.Num(); + } + + if ( id >= instances.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = instances.Num(); i <= id; i++ ) { + instances.Append( NULL ); + } + } + + if ( instances[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + instances[ id ] = new rvInstance( id, deferPopulate ); + RV_POP_HEAP(); +// RAVEN END + + if ( !deferPopulate ) { + // keep track of the high watermark + ServerSetEntityIndexWatermark( id ); + } + + common->DPrintf( "idGameLocal::AddInstance(): Adding instance %d\n", instances[ id ]->GetInstanceID() ); + } else { + common->DPrintf( "idGameLocal::AddInstance(): Instance %d already exists\n", instances[ id ]->GetInstanceID() ); + } + + // keep the min spawn index correctly set + ServerSetMinSpawnIndex(); + + return instances[ id ]->GetInstanceID(); +} + +/* +=================== +idGameLocal::RemoveInstance +=================== +*/ +void idGameLocal::RemoveInstance( int id ) { + delete instances[ id ]; + instances[ id ] = NULL; +} + +/* +=================== +idGameLocal::GetPlayerName +Returns the specified player name, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerName( int clientNum, char* name ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( name, gameLocal.GetUserInfo( clientNum )->GetString( "ui_name" ), 64 ); + name[ 63 ] = 0; +} + +/* +=================== +idGameLocal::GetPlayerClan +Returns the specified player clan, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerClan( int clientNum, char* clan ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( clan, gameLocal.GetUserInfo( clientNum )->GetString( "ui_clan" ), 64 ); + clan[ 63 ] = 0; +} + +/* +=================== +idGameLocal::SetFriend +=================== +*/ +void idGameLocal::SetFriend( int clientNum, bool isFriend ) { + if( !gameLocal.GetLocalPlayer() ) { + Warning( "idGameLocal::SetFriend() - SetFriend() called with NULL local player\n" ); + return; + } + + gameLocal.GetLocalPlayer()->SetFriend( clientNum, isFriend ); +} + +/* +=================== +idGameLocal::GetLongGametypeName +=================== +*/ +const char* idGameLocal::GetLongGametypeName( const char* gametype ) { + return mpGame.GetLongGametypeName( gametype ); +} + +void idGameLocal::Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ) { + int instance = 0; + + if ( args.Argc() > 1 ) { + instance = atoi( args.Argv( 1 ) ); + } + + if( gameLocal.instances[ instance ] ) { + gameLocal.instances[ instance ]->PrintMapNumbers(); + } +} + +void idGameLocal::Cmd_PrintSpawnIds_f( const idCmdArgs& args ) { + for( int i = 0; i < MAX_GENTITIES; i++ ) { + if( gameLocal.entities[ i ] ) { + gameLocal.Printf( "Spawn id %d: %d\n", i, gameLocal.spawnIds[ i ] ); + } + } +} + +/* +=============== +idGameLocal::GetDemoHud +=============== +*/ +idUserInterface *idGameLocal::GetDemoHud( void ) { + if ( !demo_hud ) { + demo_hud = uiManager->FindGui( "guis/hud.gui", true, false, true ); + assert( demo_hud ); + } + return demo_hud; +} + +/* +=============== +idGameLocal::GetDemoMphud +=============== +*/ +idUserInterface *idGameLocal::GetDemoMphud( void ) { + if ( !demo_mphud ) { + demo_mphud = uiManager->FindGui( "guis/mphud.gui", true, false, true ); + assert( demo_mphud ); + } + return demo_mphud; +} + +/* +=============== +idGameLocal::GetDemoCursor +=============== +*/ +idUserInterface *idGameLocal::GetDemoCursor( void ) { + if ( !demo_cursor ) { + demo_cursor = uiManager->FindGui( "guis/cursor.gui", true, false, true ); + assert( demo_cursor ); + } + return demo_cursor; +} + +/* +=============== +idGameLocal::IsTeamPowerups +=============== +*/ +bool idGameLocal::IsTeamPowerups( void ) { + if ( !serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + return false; + } + if ( !IsTeamGameType() ) { + return false; + } + return ( gameType != GAME_ARENA_CTF ); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +=================== +idGameLocal::FlushBeforelevelLoad +=================== +*/ +void idGameLocal::FlushBeforelevelLoad( void ) +{ + TIME_THIS_SCOPE( __FUNCLINE__); + +#ifndef _XENON + MapShutdown(); +#else + mpGame.Clear(); +#endif + for(int i = 0; i < aasNames.Num(); i++) + { + aasList[i]->Shutdown(); + } +} +#endif +// RAVEN END + +// dluetscher: moved the overloaded new/delete to sys_local.cpp and Game_local.cpp (from Heap.h) +// so that the tools.dll will link. +#if !defined(_XBOX) && (defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT)) + +#undef new +#undef delete +#undef Mem_Alloc +#undef Mem_Free + +#ifdef ID_DEBUG_MEMORY +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p, "", 0 ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p, "", 0 ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void *operator new( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p ); +} +#endif // #else #ifdef ID_DEBUG_MEMORY +#endif // #if defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END diff --git a/source/game/Game_local.h b/source/game/Game_local.h new file mode 100644 index 0000000..7a4a67c --- /dev/null +++ b/source/game/Game_local.h @@ -0,0 +1,1412 @@ +#ifndef __GAME_LOCAL_H__ +#define __GAME_LOCAL_H__ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +inline void *operator new( size_t s ) { return Memory::Allocate(s); } +inline void operator delete( void *p ) { Memory::Free(p); } +inline void *operator new[]( size_t s ) { return Memory::Allocate(s); } +inline void operator delete[]( void *p ) { Memory::Free(p); } +#endif +// RAVEN END + +/* +=============================================================================== + + Local implementation of the public game interface. + +=============================================================================== +*/ + +#define LAGO_IMG_WIDTH 64 +#define LAGO_IMG_HEIGHT 64 +#define LAGO_WIDTH 64 +#define LAGO_HEIGHT 44 +#define LAGO_MATERIAL "textures/mptextures/lagometer" +#define LAGO_IMAGE "textures/mptextures/lagometer.tga" + +// if set to 1 the server sends the client PVS with snapshots and the client compares against what it sees +#ifndef ASYNC_WRITE_PVS + #define ASYNC_WRITE_PVS 0 +#endif + +extern idRenderWorld * gameRenderWorld; + +#include "../sys/AutoVersion.h" +// the "gameversion" client command will print this plus compile date +#define GAME_VERSION "baseQUAKE4-1" + +// classes used by idGameLocal +class idEntity; +class idActor; +class idPlayer; +class idCamera; +class idWorldspawn; +class idTestModel; +class idAAS; +class idAI; +// RAVEN BEGIN +// bdube: not using id effects +//class idSmokeParticles; +//class idEntityFx; +// bdube: client side entities +class rvInstance; +class rvClientEntity; +class rvClientModel; +class rvCTFAssaultPlayerStart; +class idPlayerStart; +// RAVEN END +class idTypeInfo; +class idProgram; +class idThread; +class idEditEntities; +class idLocationEntity; + +// RAVEN BEGIN +// dluetscher: reduced max clients for memory usage +#ifdef _XENON +#define MAX_CLIENTS 16 +#else +// RAVEN END +#define MAX_CLIENTS 32 +#endif + +#define GENTITYNUM_BITS 12 +#define MAX_GENTITIES (1<PostEventMS(&EV_Remove, 0); (p) = NULL; } +// RAVEN END + +//============================================================================ + +void gameError( const char *fmt, ... ); + +#include "gamesys/Event.h" +// RAVEN BEGIN +// bdube: added +#include "gamesys/State.h" +// RAVEN END +#include "gamesys/Class.h" +#include "gamesys/SysCvar.h" +#include "gamesys/SysCmds.h" +#include "gamesys/SaveGame.h" +#include "gamesys/DebugGraph.h" + +#include "script/Script_Program.h" + +#include "anim/Anim.h" + +#include "ai/AAS.h" + +#include "physics/Clip.h" +#include "physics/Push.h" + +#include "Pvs.h" + +#include "FreeView.h" + +//============================================================================ + +const int MAX_GAME_MESSAGE_SIZE = 8192; +const int MAX_ENTITY_STATE_SIZE = 512; +const int ENTITY_PVS_SIZE = ((MAX_GENTITIES+31)>>5); +// RAVEN BEGIN +// abahr: changed to NUM_PORTAL_ATTRIBUTES to take into account gravity +const int NUM_RENDER_PORTAL_BITS = NUM_PORTAL_ATTRIBUTES; +// RAVEN END + +typedef struct entityState_s { + int entityNumber; + idBitMsg state; + byte stateBuf[MAX_ENTITY_STATE_SIZE]; + struct entityState_s * next; +} entityState_t; + +typedef struct snapshot_s { + int sequence; + entityState_t * firstEntityState; + int pvs[ENTITY_PVS_SIZE]; + struct snapshot_s * next; +} snapshot_t; + +const int MAX_EVENT_PARAM_SIZE = 128; + +typedef struct entityNetEvent_s { + int spawnId; + int event; + int time; + int paramsSize; + byte paramsBuf[MAX_EVENT_PARAM_SIZE]; + struct entityNetEvent_s *next; + struct entityNetEvent_s *prev; +} entityNetEvent_t; + +enum { + GAME_RELIABLE_MESSAGE_SPAWN_PLAYER, + GAME_RELIABLE_MESSAGE_DELETE_ENT, + GAME_RELIABLE_MESSAGE_CHAT, + GAME_RELIABLE_MESSAGE_TCHAT, + GAME_RELIABLE_MESSAGE_DB, + GAME_RELIABLE_MESSAGE_KILL, + GAME_RELIABLE_MESSAGE_DROPWEAPON, + GAME_RELIABLE_MESSAGE_RESTART, + GAME_RELIABLE_MESSAGE_SERVERINFO, + GAME_RELIABLE_MESSAGE_CALLVOTE, + GAME_RELIABLE_MESSAGE_CASTVOTE, + GAME_RELIABLE_MESSAGE_STARTVOTE, + GAME_RELIABLE_MESSAGE_UPDATEVOTE, + GAME_RELIABLE_MESSAGE_PORTALSTATES, + GAME_RELIABLE_MESSAGE_PORTAL, + GAME_RELIABLE_MESSAGE_VCHAT, + GAME_RELIABLE_MESSAGE_STARTSTATE, + GAME_RELIABLE_MESSAGE_MENU, + GAME_RELIABLE_MESSAGE_EVENT, +// RAVEN BEGIN +// bdube: effect + GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND, +// ddynerman: death messages + GAME_RELIABLE_MESSAGE_DEATH, +// ddynerman: game state + GAME_RELIABLE_MESSAGE_GAMESTATE, +// ddynerman: game stat + GAME_RELIABLE_MESSAGE_STAT, +// asalmon: game stats for xenon + GAME_RELIABLE_MESSAGE_ALL_STATS, +// ddynerman: ingame awards + GAME_RELIABLE_MESSAGE_INGAMEAWARD, +// ddynerman: instances + GAME_RELIABLE_MESSAGE_SET_INSTANCE, +// shouchard: for voicechat + GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING, +// shouchard: for server admin + GAME_RELIABLE_MESSAGE_SERVER_ADMIN, +// shouchard: for voting + GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE, + GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE, +// mekberg: get ban list for server + GAME_RELIABLE_MESSAGE_GETADMINBANLIST, + GAME_RELIABLE_MESSAGE_PRINT +// jscott: for voice comms +// TTimo: implemented or not by the OS, the network protocol should not be affected + , + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST +// RAVEN END +}; + +enum { + GAME_UNRELIABLE_MESSAGE_EVENT, + GAME_UNRELIABLE_MESSAGE_EFFECT, + GAME_UNRELIABLE_MESSAGE_HITSCAN, + GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER +}; + +enum { + GAME_UNRELIABLE_RECORD_CLIENTNUM, + GAME_UNRELIABLE_RECORD_AREAS, + + GAME_UNRELIABLE_RECORD_COUNT +}; + +typedef enum { + GAMESTATE_UNINITIALIZED, // prior to Init being called + GAMESTATE_NOMAP, // no map loaded + GAMESTATE_STARTUP, // inside InitFromNewMap(). spawning map entities. + GAMESTATE_RESTART, // spawning map entities from an instance restart, but not fully restarting + GAMESTATE_ACTIVE, // normal gameplay + GAMESTATE_SHUTDOWN // inside MapShutdown(). clearing memory. +} gameState_t; + +typedef struct { + idPlayerStart *ent; + int dist; +} spawnSpot_t; + +//============================================================================ +class idEventQueue { +public: + typedef enum { + OUTOFORDER_IGNORE, + OUTOFORDER_DROP, + OUTOFORDER_SORT + } outOfOrderBehaviour_t; + + idEventQueue() : start( NULL ), end( NULL ) {} + + entityNetEvent_t * Alloc(); + void Free( entityNetEvent_t *event ); + void Shutdown(); + + void Init(); + void Enqueue( entityNetEvent_t* event, outOfOrderBehaviour_t oooBehaviour ); + entityNetEvent_t * Dequeue( void ); + entityNetEvent_t * RemoveLast( void ); + + entityNetEvent_t * Start( void ) { return start; } + +private: + entityNetEvent_t * start; + entityNetEvent_t * end; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc eventAllocator; +// RAVEN END +}; + +//============================================================================ + +template< class type > +class idEntityPtr { +public: + idEntityPtr(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + idEntityPtr & operator=( type *ent ); + + // synchronize entity pointers over the network + int GetSpawnId( void ) const { return spawnId; } + bool SetSpawnId( int id ); + bool UpdateSpawnId( void ); + + bool IsValid( void ) const; + type * GetEntity( void ) const; + int GetEntityNum( void ) const; + +// RAVEN BEGIN +// bdube: overloaded operators + idEntityPtr( type* ent ) { *this = ent; } + idEntityPtr& operator=( idEntityPtr& ent ) { *this = ent.GetEntity(); return *this; } + type * operator->( void ) const; + operator type *( void ) const; +// RAVEN END + +private: + int spawnId; +}; + +// RAVEN BEGIN +// abahr: forward declaration +class rvGravityArea; +// RAVEN END + +//============================================================================ +// ddynerman: moved MultiplayerGame.h down here, so it can use more stuff in Game_local (idEntityPtr) +#include "MultiplayerGame.h" + +//============================================================================ + +class idGameLocal : public idGame { +public: + idDict serverInfo; // all the tunable parameters, like numclients, etc + int numClients; // pulled from serverInfo and verified + idDict userInfo[MAX_CLIENTS]; // client specific settings + const usercmd_t *usercmds; // client input commands + idDict persistentPlayerInfo[MAX_CLIENTS]; + idEntity * entities[MAX_GENTITIES];// index to entities + int spawnIds[MAX_GENTITIES];// for use in idEntityPtr + int firstFreeIndex; // first free index in the entities array + int minSpawnIndex; // when spawning multiple instances, so nothing pollutes in between the instances + int num_entities; // current number <= MAX_GENTITIES + idHashIndex entityHash; // hash table to quickly find entities by name + idWorldspawn * world; // world entity + idLinkList spawnedEntities; // all spawned entities + idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) + int numEntitiesToDeactivate;// number of entities that became inactive in current frame + bool sortPushers; // true if active lists needs to be reordered to place pushers at the front + bool sortTeamMasters; // true if active lists needs to be reordered to place physics team masters before their slaves + idDict persistentLevelInfo; // contains args that are kept around between levels + +// RAVEN BEGIN +// bdube: client entities + rvClientEntity * clientEntities[MAX_CENTITIES]; // index to client entities + int clientSpawnIds[MAX_CENTITIES]; // for use in idClientEntityPtr + idLinkList clientSpawnedEntities; // all client side entities + int num_clientEntities; // current number of client entities + int firstFreeClientIndex; // first free index in the client entities array + + int entityRegisterTime; +// RAVEN END + + // can be used to automatically effect every material in the world that references globalParms + float globalShaderParms[ MAX_GLOBAL_SHADER_PARMS ]; + + idRandom random; // random number generator used throughout the game + + idProgram program; // currently loaded script and data space + idThread * frameCommandThread; + + idPush push; // geometric pushing + idPVS pvs; // potential visible set + pvsHandle_t clientsPVS[MAX_CLIENTS];// PVS of multiplayer clients updated every frame + + idTestModel * testmodel; // for development testing of models +// RAVEN BEGIN +// bdube: not using id effects +// idEntityFx * testFx; // for development testing of fx +// RAVEN END + + // only set when an end level is activated, which will take over camera positioning + // and draw end-level guis, then + + idStr sessionCommand; // a target_sessionCommand can set this to return something to the session + + idMultiplayerGame mpGame; // handles rules for standard dm + + idEditEntities * editEntities; // in game editing + + int cinematicSkipTime; // don't allow skipping cinemetics until this time has passed so player doesn't skip out accidently from a firefight + int cinematicStopTime; // cinematics have several camera changes, so keep track of when we stop them so that we don't reset cinematicSkipTime unnecessarily + int cinematicMaxSkipTime; // time to end cinematic when skipping. there's a possibility of an infinite loop if the map isn't set up right. + bool inCinematic; // game is playing cinematic (player controls frozen) + bool skipCinematic; + + // are kept up to date with changes to serverInfo + int framenum; + int previousTime; // time in msec of last frame + int time; // in msec + int msec; // time since last update in milliseconds + int mHz; // hertz + + int vacuumAreaNum; // -1 if level doesn't have any outside areas + +// RAVEN BEGIN +// abahr: + idList gravityInfo; // area num for each gravity zone + idList< idEntityPtr > scriptObjectProxies; +// RAVEN END + + gameType_t gameType; + bool isMultiplayer; // set if the game is run in multiplayer mode + bool isServer; // set if the game is run for a dedicated or listen server + bool isClient; // set if the game is run for a client + // discriminates between the RunFrame path and the ClientPrediction path + // NOTE: on a listen server, isClient is false +// RAVEN BEGIN +// ddynerman: set if we're a server and not dedicated + bool isListenServer; +// RAVEN END + int localClientNum; // number of the local client. MP: -1 on a dedicated, MAX_CLIENTS when playing a server demo + idLinkList snapshotEntities; // entities from the last snapshot + int realClientTime; // real client time + bool isNewFrame; // true if this is a new game frame, not a rerun due to prediction + int entityDefBits; // bits required to store an entity def number + + static const char * sufaceTypeNames[ MAX_SURFACE_TYPES ]; // text names for surface types + + idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f + int lastGUI; // last GUI on the lastGUIEnt + +// RAVEN BEGIN +// bdube: added + int editors; // Mirrored editors flags from common determine which editors are running + bool isLastPredictFrame; // on an MP server or in SP game this means 'last catchup frame' rather than predict +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + idStr mapFileNameStripped; // name of the map, empty string if no map loaded, with path and extension removed. If entity filter, that is appended + idList entityUsageList; +// ddynerman: the entity currently thinking, used to play effects/etc only in the appropriate instance + idEntity* currentThinkingEntity; + + const static int INITIAL_SPAWN_COUNT = 1; + + idFreeView freeView; + +// RAVEN END + + int filterMod; + idList modList; + + // ---------------------- Public idGame Interface ------------------- + + idGameLocal(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ); +#else + virtual void Init( void ); +#endif +// RAVEN END + virtual void Shutdown( void ); + virtual void SetLocalClient( int clientNum ); + virtual void ThrottleUserInfo( void ); + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ); + virtual const idDict * GetUserInfo( int clientNum ); + virtual bool IsClientActive( int clientNum ); + virtual void SetServerInfo( const idDict &serverInfo ); + + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) { assert(false); return NULL; } + + virtual const idDict & GetPersistentPlayerInfo( int clientNum ); + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randSeed ); + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ); +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ); +// RAVEN END + virtual void MapShutdown( void ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual void SpawnPlayer( int clientNum ); +// RAVEN BEGIN + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ); + virtual void MenuFrame( void ); +// RAVEN END + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int spoolTime = 0 ) {}; + virtual bool Draw( int clientNum ); + virtual escReply_t HandleESC( idUserInterface **gui ); + virtual idUserInterface *StartMenu( void ); + virtual const char * HandleGuiCommands( const char *menuCommand ); + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ); + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void ServerClientConnect( int clientNum, const char *guid ); + virtual void ServerClientBegin( int clientNum ); + virtual void ServerClientDisconnect( int clientNum ); + virtual void ServerWriteInitialReliableMessages( int clientNum ); + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) { idStr::Copynz( reason, "#str_107239" /* zinx - FIXME - not banned... */, sizeof(reason) ); return ALLOW_NO; }; + virtual void RepeaterClientConnect( int clientNum ) {assert(false);}; + virtual void RepeaterClientBegin( int clientNum ) {assert(false);}; + virtual void RepeaterClientDisconnect( int clientNum ) {assert(false);}; + virtual void RepeaterWriteInitialReliableMessages( int clientNum ) {assert(false);}; +// RAVEN BEGIN +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ); +// RAVEN END + virtual bool ServerApplySnapshot( int clientNum, int sequence ); + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ) { assert(false); return false; } + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) { assert(false); } + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ); + virtual bool ClientApplySnapshot( int clientNum, int sequence ); + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ); +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ); + virtual void ClientEndFrame( void ); + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ); + virtual void ResetRconGuiStatus( void ); +// RAVEN END + + virtual void GetClientStats( int clientNum, char *data, const int len ); + virtual void SwitchTeam( int clientNum, int team ); + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ); + + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ); + +// RAVEN BEGIN +// bdube: client hitscan + virtual void ClientHitScan( const idBitMsg &msg ); +// jscott: for effects system + virtual void StartViewEffect( int type, float time, float scale ); + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ); + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ); + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ); +// bdube: added debug methods + virtual void DebugSetString ( const char* name, const char* value ); + virtual void DebugSetFloat ( const char* name, float value ); + virtual void DebugSetInt ( const char* name, int value ); + virtual const char* DebugGetStatString ( const char* name ); + virtual int DebugGetStatInt ( const char* name ); + virtual float DebugGetStatFloat ( const char* name ); + virtual bool IsDebugHudActive ( void ) const; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ); + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ); + virtual bool PlayerChatDisabled( int clientNum ); + virtual void SetViewComments( const char *text = 0 ); +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ); + virtual void GetPlayerClan( int clientNum, char* clan ); + virtual void SetFriend( int clientNum, bool isFriend ); + static void Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ); + static void Cmd_PrintSpawnIds_f( const idCmdArgs& args ); +// abahr: + virtual int GetNumGravityAreas() const; + virtual const rvGravityArea* GetGravityInfo( int index ) const; + virtual void SetGravityInfo( int index, rvGravityArea* info ); + virtual void AddUniqueGravityInfo( rvGravityArea* info ); + + virtual int GetCurrentGravityInfoIndex( const idVec3& origin ) const; + virtual bool InGravityArea( idEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( idEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( idEntity* entity ) const; + + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const; + + virtual bool InGravityArea( rvClientEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( rvClientEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( rvClientEntity* entity ) const; + virtual idEntity* ReferenceScriptObjectProxy( const char* scriptObjectName ); + virtual void ReleaseScriptObjectProxy( const char* proxyName ); + +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ); +// RAVEN END + + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ); + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ) {if (isRepeater || serverIsRepeater) Warning("Repeater does not work for single player.");}; + virtual void WriteNetworkInfo( idFile* file, int clientNum ); + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ); + virtual bool ValidateDemoProtocol( int minor_ref, int minor ); + + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ); + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ); + + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) {assert(false);}; + virtual void RepeaterEndSnapshots( void ) {}; + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) {assert(false);}; + + virtual int GetDemoFollowClient( void ) { return serverDemo ? followPlayer : -1; } + + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) { Error( "Bot input requested\n" ); }; + + virtual const char * GetLoadingGui( const char *mapDeclName ) { return NULL; } + virtual void SetupLoadingGui( idUserInterface *gui ) {} + + // ---------------------- Public idGameLocal Interface ------------------- + + void Printf( const char *fmt, ... ) const; + void DPrintf( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + void DWarning( const char *fmt, ... ) const; + void Error( const char *fmt, ... ) const; + + // Initializes all map variables common to both save games and spawned games + void LoadMap( const char *mapName, int randseed ); + + void LocalMapRestart( int instance = -1 ); + void MapRestart( int instance = -1 ); + static void VerifyServerSettings_f( const idCmdArgs &args ); + static void MapRestart_f( const idCmdArgs &args ); + bool NextMap( void ); // returns wether serverinfo settings have been modified + static void NextMap_f( const idCmdArgs &args ); + + idMapFile * GetLevelMap( void ); + const char * GetMapName( void ) const; + + int NumAAS( void ) const; + idAAS * GetAAS( int num ) const; + idAAS * GetAAS( const char *name ) const; +// RAVEN BEGIN +// jscott: added accessor for memory tracking + int GetNumAAS( void ) const { return( aasList.Num() ); } +// RAVEN END + void SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ); + aasHandle_t AddAASObstacle( const idBounds &bounds ); + void RemoveAASObstacle( const aasHandle_t handle ); + void RemoveAllAASObstacles( void ); +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. + size_t GetEntityMemoryUsage ( void ) const; +// RAVEN END + bool CheatsOk( bool requirePlayer = true ); + void SetSkill( int value ); + gameState_t GameState( void ) const; + void SetGameState( gameState_t newState ) { gamestate = newState; } + idEntity * SpawnEntityType( const idTypeInfo &classdef, const idDict *args = NULL, bool bIsClientReadSnapshot = false ); + bool SpawnEntityDef( const idDict &args, idEntity **ent = NULL, bool setDefaults = true ); + bool SpawnClientEntityDef( const idDict &args, rvClientEntity **ent = NULL, bool setDefaults = true, const char* spawn = NULL ); +// abahr: + idEntity* SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + template< class type > + type* SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + int GetPreviousTime() const { return previousTime; } +// RAVEN END + int GetSpawnId( const idEntity *ent ) const; + + const idDeclEntityDef * FindEntityDef( const char *name, bool makeDefault = true ) const; + const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + + void RegisterEntity( idEntity *ent ); + void UnregisterEntity( idEntity *ent ); + // used to skip one when registering entities, leaving an empty entity in the array + void SkipEntityIndex( void ); + + bool RequirementMet( idEntity *activator, const idStr &requires, int removeItem ); + +// RITUAL BEGIN +// squirrel: accessor for si_weaponStay checks + bool IsWeaponsStayOn( void ); +// RITUAL END + +// RAVEN BEGIN +// bdube: client entities + void RegisterClientEntity( rvClientEntity *cent ); + void UnregisterClientEntity( rvClientEntity *cent ); +// RAVEN END + + void AlertAI( idEntity *ent ); +// RAVEN BEGIN +// bdube: added get alert actor + idActor * GetAlertActor( void ); + idEntity * GetAlertEntity( void ); +// RAVEN END + + + bool InPlayerPVS( idEntity *ent ) const; + bool InPlayerConnectedArea( idEntity *ent ) const; + + void SetCamera( idCamera *cam ); + idCamera * GetCamera( void ) const; + bool SkipCinematic( void ); + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *GetPortalSky( void ) const; + void SetPortalSky( idCamera *cam ); +// RAVEN END + + void CalcFov( float base_fov, float &fov_x, float &fov_y ) const; + + void AddEntityToHash( const char *name, idEntity *ent ); + bool RemoveEntityFromHash( const char *name, idEntity *ent ); + int GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const; + + // returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. + idEntity * GetTraceEntity( const trace_t &trace ) const; + + static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); + idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; + static void ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +//RAVEN BEGIN +// bgeisler: added, I don't want to have to do this work myself every single time I have an entityNumber + idEntity * FindEntity( int entityNumber ) { return ((entityNumber >= 0 && entityNumber < MAX_GENTITIES) ? entities[entityNumber] : NULL); } +//RAVEN BEGIN + + idEntity * FindEntity( const char *name ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; + int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; + + void KillBox( idEntity *ent, bool catch_teleport = false ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); +// RAVEN BEGIN +// ddynerman: return number of people damaged + void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f, int* hitCount = NULL ); +// bdube: inflictor + void RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ); +// RAVEN END + + void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + void BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dir, float size, const char *material ); +// RAVEN END + + void CallFrameCommand( idEntity *ent, const function_t *frameCommand ); +// RAVEN BEGIN +// bdube: added script object frame commands + void CallFrameCommand( idScriptObject* obj, const function_t* frameCommand ); + void CallFrameCommand( idEntity* ent, const char* frameCommand ); +// RAVEN END + + void CallObjectFrameCommand( idEntity *ent, const char *frameCommand ); + + const idVec3 & GetGravity( void ) const; + + // added the following to assist licensees with merge issues + int GetFrameNum() const { return framenum; } + int GetTime() const { return time; } + int GetMSec() const { return msec; } + int GetMHz() const { return mHz; } + + int GetNextClientNum( int current ) const; + idPlayer * GetClientByNum( int current ) const; + idPlayer * GetClientByName( const char *name ) const; + idPlayer * GetClientByCmdArgs( const idCmdArgs &args ) const; + int GetClientNumByName( const char *name ) const; + + idPlayer * GetLocalPlayer() const; + +// RAVEN BEGIN +// jshepard: update player data after main menu close + void UpdatePlayerPostMainMenu(); + +// bdube: added + int GetSpawnCount ( void ) const; + void SetSpawnCount ( int newSpawnCount ) { spawnCount = newSpawnCount; } +// ddynerman: team type + bool IsTeamGame ( void ) const; +// RAVEN END + + void SpreadLocations(); + idLocationEntity * LocationForPoint( const idVec3 &point ); // May return NULL +// RAVEN BEGIN +// bdube: added + idLocationEntity* AddLocation ( const idVec3& point, const char* name ); +// ddynerman: new gametype specific spawn code + bool SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ); + idEntity* SelectSpawnPoint( idPlayer* player ); + void UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ); + void ClearForwardSpawns( void ); +// RAVEN END + + void SetPortalState( qhandle_t portal, int blockingBits ); + void ServerSendChatMessage( int to, const char *name, const char *text, const char *parm = "" ); + + void SetGlobalMaterial( const idMaterial *mat ); + const idMaterial * GetGlobalMaterial(); + + void SetGibTime( int _time ) { nextGibTime = _time; } + int GetGibTime() { return nextGibTime; } +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + void SetUnFreezeTime( int _time ) { unFreezeTime = _time; }; + int GetUnFreezeTime() { return unFreezeTime; }; + void SetIsFrozen( bool _isFrozen ) { isFrozen = _isFrozen; }; + bool GetIsFrozen() { return isFrozen; }; +// RITUAL END + bool NeedRestart(); + +// RAVEN BEGIN +// jshepard: update end of level on player hud + void UpdateEndLevel(); +// MCG: added whizz-by sound + void CheckPlayerWhizzBy ( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ); +// bdube: added hitscan +// twhitaker: added additionalIgnore parameter + idEntity* HitScan ( const idDict& hitscanDef, const idVec3& origin, const idVec3& dir, const idVec3& fxOrigin, idEntity* owner = NULL, bool noFX = false, float damageScale = 1.0f, idEntity * additionalIgnore = NULL, int *areas = NULL ); +// bdube: added effect calls + virtual rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + const idDecl *GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType = NULL ); + + idList ambientLights; // lights that cast ambient + +// ddynerman: multiple collision world - game collision wrapper functions to +// use the correct idClip +// --------------------------------------------------------------- +// These are wrapper functions around idClip collision detection +// functions. They expose the collision detection engine to the +// game code, but do collision world determination in one spot. +// 'ent' refers to the entity we want collision information about + bool Translation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + bool Rotation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contents ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); + // special case translations versus the rest of the world + bool TracePoint ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ); + bool TraceBounds ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ); + // clip versus a specific model + void TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // clip versus all entities but not the world + void TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + // get a contact feature + bool GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + int PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; + const idBounds & GetWorldBounds( const idEntity* ent ) const; + + void Link( idClipModel* clip, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); + + idClip* GetEntityClipWorld( const idEntity* ent ); + const idClip* GetEntityClipWorld( const idEntity* ent ) const; + int GetNumMapEntities( void ) const; + + int AddClipWorld( int id ); + void RemoveClipWorld( int id ); + int AddInstance( int id = -1, bool deferPopulate = false ); + void RemoveInstance( int id ); + rvInstance* GetInstance( int id ); + int GetNumInstances( void ); +// ddynerman: multiple game instances + void SpawnMapEntities( int instance = 0, unsigned short* entityNumIn = NULL, unsigned short* entityNumOut = NULL, int* startSpawnCount = NULL ); + void InstanceClear( void ); +// ddynerman: utility function + virtual const char* GetLongGametypeName( const char* gametype ); + virtual void ReceiveRemoteConsoleOutput( const char* output ); + + bool IsFlagGameType( void ) { return ( gameType == GAME_CTF || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_ARENA_1F_CTF ); } + bool IsTeamGameType( void ) { return ( gameType == GAME_TDM || gameType == GAME_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ); } + bool IsTeamPowerups( void ); + + // twhitaker: needed this for difficulty settings + float GetDifficultyModifier( void ) { const static float difficulty[] = { -0.3f, 0.0f, 0.4f, 0.8f }; return difficulty[ idMath::ClampInt( 0, 3, g_skill.GetInteger() ) ]; } + + bool IsMultiplayer( void ) { return isMultiplayer; } + +// mekberg: added + bool InCinematic( void ) { return inCinematic; } + + // mekberg: so ban list can be populated outside of multiplayer game + void PopulateBanList( idUserInterface* hud ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ); +#endif +// RAVEN END + + void ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ); + void ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ); + + void SendUnreliableMessage( const idBitMsg &msg, const int clientNum ); + // note: local client on dedicated server is always excluded + void SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1 = -1, int area2 = -1 ); + + demoState_t GetDemoState( void ) const { return demoState; } + bool IsServerDemo( void ) const { return serverDemo; } + bool IsTimeDemo( void ) const { return timeDemo; } + int GetDemoFollowClient( void ) const { return serverDemo ? followPlayer : -1; } + idUserInterface *GetDemoHud( void ); + idUserInterface *GetDemoMphud( void ); + idUserInterface *GetDemoCursor( void ); + + /* + do not synchronize implicit decls over the network + implicit decls are created when loading sounds and materials that don't have an explicit entry in the def files + clients and server may load those in different orders in a same map, or even join in with different orders because of different map histories before this game + in D3 we maintain remap tables, but it's much better to have tighter multiplayer assets so we have no need at all + still, you want to catch when bad things happen, so indexes should ALL be read and written through these functions + */ + static void WriteDecl( idBitMsg &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsg &msg, declType_t type ); + static void WriteDecl( idBitMsgDelta &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsgDelta &msg, declType_t type ); + + idPlayerStart *RandomSpawn( void ); + + int GetStartingIndexForInstance( int instanceID ); + void ClientSetStartingIndex( int i ) { clientInstanceFirstFreeIndex = i; } + void ServerSetMinSpawnIndex( void ); + void ServerSetEntityIndexWatermark( int instanceID ); + +private: +// RAVEN BEGIN +// ddynerman: multiple instance for MP + idList clip; // collision detection + idList instances; +// RAVEN END + + // keep watermarks on the high entity index + // server transmits this to clients so they use the right entity layout + idList instancesEntityIndexWatermarks; + int clientInstanceFirstFreeIndex; + + idStr mapFileName; // name of the map, empty string if no map loaded + idMapFile * mapFile; // will be NULL during the game unless in-game editing is used + bool mapCycleLoaded; + + int spawnCount; + bool isMapEntity[ MAX_GENTITIES ]; // it's handy to know which entities are part of the map +// RAVEN BEGIN +// bdube: client entities + int clientSpawnCount; +// RAVEN END + + idLocationEntity ** locationEntities; // for location names, etc + + idCamera * camera; + const idMaterial * globalMaterial; // for overriding everything + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *portalSky; + bool portalSkyVisible; +// RAVEN END + + idList aasList; // area system + idStrList aasNames; + +// RAVEN BEGIN +// bdube: GetAlertActor + idEntityPtr lastAIAlertActor; + int lastAIAlertActorTime; + idEntityPtr lastAIAlertEntity; + int lastAIAlertEntityTime; +// RAVEN END + + idDict spawnArgs; // spawn args used during entity spawning FIXME: shouldn't be necessary anymore +// RAVEN BEGIN +// nmckenzie: + const idDeclEntityDef * spawnOverrides; +// RAVEN END + + pvsHandle_t playerPVS; // merged pvs of all players + bool freePlayerPVS; // tracks if playerPVS needs to be released + pvsHandle_t playerConnectedAreas; // all areas connected to any player area + + idVec3 gravity; // global gravity vector + gameState_t gamestate; // keeps track of whether we're spawning, shutting down, or normal gameplay + bool influenceActive; // true when a phantasm is happening + int nextGibTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int unFreezeTime; // time at which players unfreeze and the match begins + bool isFrozen; // true if the match is frozen (for buying, etc.) +// RITUAL END + + entityState_t * clientEntityStates[MAX_CLIENTS+1][MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int clientPVS[MAX_CLIENTS+1][ENTITY_PVS_SIZE]; + snapshot_t * clientSnapshots[MAX_CLIENTS+1]; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc entityStateAllocator; + idBlockAlloc snapshotAllocator; +// RAVEN END + + idEventQueue eventQueue; + + idList spawnSpots; +// RAVEN BEGIN +// ddynerman: two lists to hold team spawn points for team based games + idList teamSpawnSpots[TEAM_MAX]; + idList teamForwardSpawnSpots[TEAM_MAX]; // forward spawn positions, used in CTF +// RAVEN END + + idDict newInfo; + + idStrList shakeSounds; + + byte lagometer[ LAGO_IMG_HEIGHT ][ LAGO_IMG_WIDTH ][ 4 ]; + + idMsgQueue unreliableMessages[ MAX_CLIENTS+1 ]; // MAX_CLIENTS slot for server demo recording + + demoState_t demoState; + bool serverDemo; + bool timeDemo; + + // demo interaction usercmds + usercmd_t usercmd, oldUsercmd; + int followPlayer; // free fly or spectate follow through local interaction during server demo replays + idUserInterface *demo_hud; + idUserInterface *demo_mphud; + idUserInterface *demo_cursor; + + int demo_protocol; // keep track of the protocol of the demo we're replaying + +private: + + void Clear( void ); + // returns true if the entity shouldn't be spawned at all in this game type or difficulty level + bool InhibitEntitySpawn( idDict &spawnArgs ); + // spawn entities from the map file + // commons used by init, shutdown, and restart + void MapPopulate( int instance = -1 ); + void MapClear( bool clearClients, int instance = -1 ); + + bool SetupPortalSkyPVS( idPlayer *player ); +// RAVEN END + void SetupPlayerPVS( void ); + void FreePlayerPVS( void ); + void UpdateGravity( void ); + void SortActiveEntityList( void ); + void ShowTargets( void ); + void RunDebugInfo( void ); + + void InitScriptForMap( void ); + void InitConsoleCommands( void ); + void ShutdownConsoleCommands( void ); + + void InitAsyncNetwork( void ); + void ShutdownAsyncNetwork( void ); + void InitLocalClient( int clientNum ); + void FreeSnapshotsOlderThanSequence( int clientNum, int sequence ); + bool ApplySnapshot( int clientNum, int sequence ); + void WriteGameStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadGameStateFromSnapshot( const idBitMsgDelta &msg ); + void NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) id_attribute((format(printf,3,4))); + void ServerProcessEntityNetworkEventQueue( void ); + void ClientProcessEntityNetworkEventQueue( void ); + void ClientShowSnapshot( int clientNum ) const; + void SetGameType( void ); +// RAVEN BEGIN +// ddynerman: gametype specific spawn code + void InitializeSpawns( void ); + idList WeightSpawnSpots( idPlayer* player ); +// RAVEN END + static int sortSpawnPoints( const void *ptr1, const void *ptr2 ); + + void DumpOggSounds( void ); + void GetShakeSounds( const idDict *dict ); + bool ValidateServerSettings( const char *map, const char *gametype ); + + void Tokenize( idStrList &out, const char *in ); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void ShutdownInstances( void ); +// shouchard: ban list support (lives in game_network.cpp) + + void UpdateLagometer( int aheadOfServer, int dupeUsercmds ); + + void ClientReadUnreliableMessages( const idBitMsg &msg ); + void ProcessUnreliableMessage( const idBitMsg &msg ); + + void UpdateClientsPVS( void ); + + bool IsDemoReplayInAreas( int area1, int area2 ); + + void BuildModList( void ); + +public: + void LoadBanList(); + void SaveBanList(); + void FlushBanList(); + bool IsPlayerBanned( const char *name ); + bool IsGuidBanned( const char *guid ); + void AddGuidToBanList( const char *guid ); + void RemoveGuidFromBanList( const char *guid ); + virtual void RegisterClientGuid( int clientNum, const char *guid ); + + int GetBanListCount(); + const mpBanInfo_t* GetBanListEntry( int entry ); // returns client name + const char* GetGuidByClientNum( int clientNum ); // returns GUID + int GetClientNumByGuid( const char* ); // returns clientNum + +// mekberg: get and send ban list + void ServerSendBanList( int clientNum ); + +// jscott: made public + pvsHandle_t GetClientPVS( idPlayer *player, pvsType_t type ); + + int GetCurrentDemoProtocol( void ) { return demo_protocol; } + +private: + char clientGuids[ MAX_CLIENTS ][ CLIENT_GUID_LENGTH ]; + idList banList; + bool banListLoaded; + bool banListChanged; +// RAVEN END +}; + +//============================================================================ + +extern idGameLocal gameLocal; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer to prevent it from allocating memory +// before the unified allocator is initialized +extern idAnimManager *animationLib; +// RAVEN END + +//============================================================================ + +ID_INLINE void idGameLocal::WriteDecl( idBitMsg &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsg &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +ID_INLINE void idGameLocal::WriteDecl( idBitMsgDelta &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsgDelta &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +//============================================================================ + +class idGameError : public idException { +public: + idGameError( const char *text ) : idException( text ) {} +}; + +//============================================================================ + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_OPAQUE|CONTENTS_SIGHTCLIP) +#define MASK_SHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL) +#define MASK_SHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY) +#define MASK_LARGESHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_LARGESHOTCLIP) +#define MASK_LARGESHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_LARGESHOTCLIP) +#define MASK_DMGSOLID (CONTENTS_SOLID|CONTENTS_LARGESHOTCLIP) + +// RAVEN BEGIN +// creed: added monster clip +#define MASK_MONSTERCLIP (CONTENTS_SOLID|CONTENTS_MONSTERCLIP) +// RAVEN END + +const float DEFAULT_GRAVITY = 1066.0f; +const float DEFAULT_GRAVITY_MP = 800.0f; + +#define DEFAULT_GRAVITY_STRING "1066" +#define DEFAULT_MP_GRAVITY_STRING "800" +const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); + +const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); + +//============================================================================ + +#include "physics/Force.h" +#include "physics/Force_Constant.h" +#include "physics/Force_Drag.h" +#include "physics/Force_Field.h" +#include "physics/Force_Spring.h" +#include "physics/Physics.h" +#include "physics/Physics_Static.h" +#include "physics/Physics_StaticMulti.h" +#include "physics/Physics_Base.h" +#include "physics/Physics_Actor.h" +#include "physics/Physics_Monster.h" +#include "physics/Physics_Player.h" +#include "physics/Physics_Parametric.h" +#include "physics/Physics_RigidBody.h" +#include "physics/Physics_AF.h" +#include "physics/Physics_Particle.h" +#include "physics/Physics_VehicleMonster.h" + +#include "vehicle/VehicleController.h" + +#include "Entity.h" +#include "Game_Debug.h" +#include "IconManager.h" +#include "GameEdit.h" +#include "AF.h" +#include "IK.h" +#include "AFEntity.h" +#include "Misc.h" +#include "Actor.h" + +// client entities +#include "client/ClientEntity.h" +#include "client/ClientEffect.h" +#include "client/ClientMoveable.h" +#include "client/ClientModel.h" +#include "client/ClientAFEntity.h" + +#include "Weapon.h" + +#include "script/ScriptFuncUtility.h" + +#include "Light.h" +#include "WorldSpawn.h" +#include "Item.h" +#include "PlayerView.h" +// TTimo: moved AI.h up, can't do template instanciation on forward declared-classes +#include "ai/AI.h" +#include "Player.h" +#include "Mover.h" +// RAVEN BEGIN +// abahr: +#include "vehicle/VehicleParts.h" +#include "vehicle/Vehicle.h" +#include "SplineMover.h" +#include "TramGate.h" +#include "vehicle/VehicleDriver.h" +// RAVEN END +#include "Camera.h" +#include "Moveable.h" +#include "Target.h" +#include "Trigger.h" +#include "Sound.h" +#include "SecurityCamera.h" +#include "BrittleFracture.h" + +// RAVEN BEGIN +// nmckenzie: Reduce dependencies. +#include "mp/CTF.h" +#include "mp/stats/StatManager.h" +#include "mp/Tourney.h" +#include "Instance.h" +// RAVEN END +#include "anim/Anim_Testmodel.h" + +// RAVEN BEGIN +// jscott: for lip syncing +#include "LipSync.h" +// RAVEN END + +#include "script/Script_Compiler.h" +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE rvClientEffect* idGameLocal::PlayEffect( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect ( GetEffect ( args, effectName ), origin, axis, loop, endOrigin, broadcast, false, category, effectTint ); +} + +ID_INLINE bool idGameLocal::IsTeamGame( void ) const { + return ( isMultiplayer && ( gameType == GAME_CTF || gameType == GAME_TDM || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ) ); +} + +ID_INLINE int idGameLocal::GetNumMapEntities( void ) const { + if ( mapFile == NULL ) { + return -1; + } else { + return mapFile->GetNumEntities(); + } +} + +ID_INLINE rvInstance* idGameLocal::GetInstance( int id ) { + return instances[ id ]; +} + +ID_INLINE int idGameLocal::GetNumInstances( void ) { + return instances.Num(); +} + +ID_INLINE void idGameLocal::ReceiveRemoteConsoleOutput( const char* output ) { + if( isMultiplayer ) { + mpGame.ReceiveRemoteConsoleOutput( output ); + } +} + +// abahr: +template< class type > +type* idGameLocal::SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idEntity* entity = SpawnEntityDef( entityDefName, additionalArgs ); + if( !entity ) { + return NULL; + } + + if( !entity->IsType(type::GetClassType()) ) { + entity->PostEventMS( &EV_Remove, 0 ); + return NULL; + } + + return static_cast( entity ); +} +// RAVEN END + +template< class type > +ID_INLINE idEntityPtr::idEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE void idEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void idEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +template< class type > +ID_INLINE idEntityPtr &idEntityPtr::operator=( type *ent ) { + if ( ent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool idEntityPtr::SetSpawnId( int id ) { + if ( id == spawnId ) { + return false; + } + if ( ( id >> GENTITYNUM_BITS ) == gameLocal.spawnIds[ id & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool idEntityPtr::IsValid( void ) const { + return ( gameLocal.spawnIds[ spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] == ( spawnId >> GENTITYNUM_BITS ) ); +} + +template< class type > +ID_INLINE type *idEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( spawnId >> GENTITYNUM_BITS ) ) ) { + return static_cast( gameLocal.entities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int idEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ); +} + +// RAVEN BEGIN +// bdube: overloaded operator +template< class type > +ID_INLINE type * idEntityPtr::operator->( void ) const { + return GetEntity ( ); +} + +template< class type > +ID_INLINE idEntityPtr::operator type * ( void ) const { + return GetEntity(); +} +// RAVEN END + +#include "../idlib/containers/ListGame.h" + +#endif /* !__GAME_LOCAL_H__ */ diff --git a/source/game/Game_network.cpp b/source/game/Game_network.cpp new file mode 100644 index 0000000..45588ba --- /dev/null +++ b/source/game/Game_network.cpp @@ -0,0 +1,3515 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: client entities +#include "client/ClientEffect.h" +// shouchard: ban list support +#define BANLIST_FILENAME "banlist.txt" +// RAVEN END + +/* +=============================================================================== + + Client running game code: + - entity events don't work and should not be issued + - entities should never be spawned outside idGameLocal::ClientReadSnapshot + +=============================================================================== +*/ + +// adds tags to the network protocol to detect when things go bad ( internal consistency ) +// NOTE: this changes the network protocol +#ifndef ASYNC_WRITE_TAGS + #define ASYNC_WRITE_TAGS 0 +#endif + +idCVar net_clientShowSnapshot( "net_clientShowSnapshot", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar net_clientShowSnapshotRadius( "net_clientShowSnapshotRadius", "128", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar net_clientLagOMeter( "net_clientLagOMeter", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "draw prediction graph" ); + +// RAVEN BEGIN +// ddynerman: performance profiling +int net_entsInSnapshot; +int net_snapshotSize; + +extern const int ASYNC_PLAYER_FRAG_BITS; +// RAVEN END + +/* +================ +idGameLocal::InitAsyncNetwork +================ +*/ +void idGameLocal::InitAsyncNetwork( void ) { + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + eventQueue.Init(); + + // NOTE: now that we removed spawning by typeNum, we could stick to a >0 entityDefBits + // not making the change at this point because of proto69 back compat stuff + entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 ); + localClientNum = 0; // on a listen server SetLocalUser will set this right + realClientTime = 0; + isNewFrame = true; +} + +/* +================ +idGameLocal::ShutdownAsyncNetwork +================ +*/ +void idGameLocal::ShutdownAsyncNetwork( void ) { + entityStateAllocator.Shutdown(); + snapshotAllocator.Shutdown(); + eventQueue.Shutdown(); + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); +} + +/* +================ +idGameLocal::InitLocalClient +================ +*/ +void idGameLocal::InitLocalClient( int clientNum ) { + isServer = false; + isClient = true; + localClientNum = clientNum; +} + +/* +================ +idGameLocal::ServerAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + +// RAVEN BEGIN +// shouchard: ban support + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } +// RAVEN END + + if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107140" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + // check private clients + if( serverInfo.GetInt( "si_privatePlayers" ) > 0 ) { + // just in case somehow we have a stale private clientId that matches a new client + mpGame.RemovePrivatePlayer( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privatePassword" ); + if( privatePass[ 0 ] == '\0' ) { + common->Warning( "idGameLocal::ServerAllowClient() - si_privatePlayers > 0 with no g_privatePassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_privatePlayers is set but g_privatePassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + + int numPrivateClients = cvarSystem->GetCVarInteger( "si_numPrivatePlayers" ); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, serverInfo.GetInt( "si_privatePlayers" ), numPrivateClients ); + + if ( !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + mpGame.AddPrivatePlayer( clientId ); + } else if( (numClients - numPrivateClients) >= (serverInfo.GetInt( "si_maxPlayers" ) - serverInfo.GetInt( "si_privatePlayers" )) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + } + + + if ( !cvarSystem->GetCVarBool( "si_usePass" ) ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_password" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "si_usePass is set but g_password is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_usePass is set but g_password is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting client %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ServerClientConnect +================ +*/ +void idGameLocal::ServerClientConnect( int clientNum, const char *guid ) { + // make sure no parasite entity is left + if ( entities[ clientNum ] ) { + common->DPrintf( "ServerClientConnect: remove old player entity\n" ); + delete entities[ clientNum ]; + } + unreliableMessages[ clientNum ].Init( 0 ); + userInfo[ clientNum ].Clear(); + mpGame.ServerClientConnect( clientNum ); + Printf( "client %d connected.\n", clientNum ); +} + +/* +================ +idGameLocal::ServerClientBegin +================ +*/ +void idGameLocal::ServerClientBegin( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn the player + SpawnPlayer( clientNum ); + if ( clientNum == localClientNum ) { + mpGame.EnterGame( clientNum ); + } + + // ddynerman: connect time + ((idPlayer*)entities[ clientNum ])->SetConnectTime( time ); + + // send message to spawn the player at the clients + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteLong( spawnIds[ clientNum ] ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + if( gameType != GAME_TOURNEY ) { + ((idPlayer*)entities[ clientNum ])->JoinInstance( 0 ); + } else { + // instance 0 might be empty in Tourney + ((idPlayer*)entities[ clientNum ])->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } +//RAVEN BEGIN +//asalmon: This client has finish loading and will be spawned mark them as ready. +#ifdef _XENON + Live()->ClientReady(clientNum); +#endif +//RAVEN END +} + +/* +================ +idGameLocal::ServerClientDisconnect +clientNum == MAX_CLIENTS for cleanup of server demo recording data +================ +*/ +void idGameLocal::ServerClientDisconnect( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( clientNum < MAX_CLIENTS ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT ); + outMsg.WriteBits( ( spawnIds[ clientNum ] << GENTITYNUM_BITS ) | clientNum, 32 ); // see GetSpawnId + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( clientNum, 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( clientEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( clientEntityStates[ clientNum ][ i ] ); + clientEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( clientPVS[ clientNum ], 0, sizeof( clientPVS[ clientNum ] ) ); + + if ( clientNum == MAX_CLIENTS ) { + return; + } + + // only drop MP clients if we're in multiplayer and the server isn't going down + if ( gameLocal.isMultiplayer && !(gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) ) { + // idMultiplayerGame::DisconnectClient will do the delete in MP + mpGame.DisconnectClient( clientNum ); + } else { + // delete the player entity + delete entities[ clientNum ]; + } + + +} + +/* +================ +idGameLocal::ServerWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::ServerWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL || i == clientNum ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + mpGame.ServerWriteInitialReliableMessages( clientNum ); +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( int clientNum, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence < sequence ) { + for ( state = snapshot->firstEntityState; state; state = snapshot->firstEntityState ) { + snapshot->firstEntityState = snapshot->firstEntityState->next; + entityStateAllocator.Free( state ); + } + if ( lastSnapshot ) { + lastSnapshot->next = snapshot->next; + } else { + clientSnapshots[clientNum] = snapshot->next; + } + snapshotAllocator.Free( snapshot ); + } else { + lastSnapshot = snapshot; + } + } +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( int clientNum, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + FreeSnapshotsOlderThanSequence( clientNum, sequence ); + + for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence == sequence ) { + for ( state = snapshot->firstEntityState; state; state = state->next ) { + if ( clientEntityStates[clientNum][state->entityNumber] ) { + entityStateAllocator.Free( clientEntityStates[clientNum][state->entityNumber] ); + } + clientEntityStates[clientNum][state->entityNumber] = state; + } + // ~512 bytes + memcpy( clientPVS[clientNum], snapshot->pvs, sizeof( snapshot->pvs ) ); + if ( lastSnapshot ) { + lastSnapshot->next = nextSnapshot; + } else { + clientSnapshots[clientNum] = nextSnapshot; + } + snapshotAllocator.Free( snapshot ); + return true; + } else { + lastSnapshot = snapshot; + } + } + + return false; +} + +/* +================ +idGameLocal::WriteGameStateToSnapshot +================ +*/ +void idGameLocal::WriteGameStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + msg.WriteFloat( globalShaderParms[i] ); + } + + mpGame.WriteToSnapshot( msg ); +} + +/* +================ +idGameLocal::ReadGameStateFromSnapshot +================ +*/ +void idGameLocal::ReadGameStateFromSnapshot( const idBitMsgDelta &msg ) { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + globalShaderParms[i] = msg.ReadFloat(); + } + + mpGame.ReadFromSnapshot( msg ); +} + +/* +================ +idGameLocal::ServerWriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +// RAVEN BEGIN +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. +void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) { +// RAVEN END + int i, msgSize, msgWriteBit; + idPlayer *player, *spectated = NULL; + idEntity *ent; + pvsHandle_t pvsHandle; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + player = static_cast( entities[ clientNum ] ); + if ( !player ) { + return; + } + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + // free too old snapshots + // ( that's a security, normal acking from server keeps a smaller backlog of snaps ) + FreeSnapshotsOlderThanSequence( clientNum, sequence - 64 ); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + memset( snapshot->pvs, 0, sizeof( snapshot->pvs ) ); + + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( random.RandomInt() ); + msg.WriteLong( tagRandom.GetSeed() ); +#endif + + // write unreliable messages + unreliableMessages[ clientNum ].FlushTo( msg ); + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + + // create the snapshot + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // if the entity is not in the player PVS + if ( !ent->PhysicsTeamInPVS( pvsHandle ) && ent->entityNumber != clientNum ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: don't transmit entities not in your clip world + if ( ent->GetInstance() != player->GetInstance() ) { + continue; + } +// RAVEN END + + // if the entity is a map entity, mark it in PVS + if ( isMapEntity[ ent->entityNumber ] ) { + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + } + + // if that entity is not marked for network synchronization + if ( !ent->fl.networkSync ) { + continue; + } + + // add the entity to the snapshot PVS + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + + // save the write state to which we can revert when the entity didn't change at all + msg.SaveWriteState( msgSize, msgWriteBit ); + + // write the entity to the snapshot + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + // write the class specific data to the snapshot + ent->WriteToSnapshot( deltaMsg ); + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + } + } + + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write the PVS to the snapshot +#if ASYNC_WRITE_PVS + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + if ( i < numSourceAreas ) { + msg.WriteLong( sourceAreas[ i ] ); + } else { + msg.WriteLong( 0 ); + } + } + gameLocal.pvs.WritePVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + msg.WriteDeltaLong( clientPVS[clientNum][i], snapshot->pvs[i] ); + } + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // write the game and player state to the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + assert( entities[ player->spectator ]->IsType( idPlayer::GetClassType() ) ); + deltaMsg.WriteBits( player->spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->WritePlayerStateToSnapshot( deltaMsg ); + } else { + deltaMsg.WriteBits( player->entityNumber, idMath::BitsForInteger( MAX_CLIENTS ) ); + player->WritePlayerStateToSnapshot( deltaMsg ); + } + WriteGameStateToSnapshot( deltaMsg ); + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = snapshot->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::ServerWriteServerDemoSnapshot +=============== +*/ +void idGameLocal::ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) { + snapshot_t *snapshot; + int i, msgSize, msgWriteBit; + idEntity *ent; + entityState_t *base, *newBase; + idBitMsgDelta deltaMsg; + + bool ret = ServerApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // silence warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[ MAX_CLIENTS ]; + clientSnapshots[ MAX_CLIENTS ] = snapshot; + + unreliableMessages[ MAX_CLIENTS ].FlushTo( msg ); + + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + if ( !ent->fl.networkSync ) { + continue; + } + + // only record instance 0 ( tourney games ) + if ( ent->GetInstance() != 0 ) { + continue; + } + + msg.SaveWriteState( msgSize, msgWriteBit ); + + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; + if ( base ) { + base->state.BeginReading(); + } + + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + ent->WriteToSnapshot( deltaMsg ); + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + } + } + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write player states and game states + base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + // all the players + for ( i = 0; i < numClients; i++ ) { + if ( entities[i] ) { + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + idPlayer *p = static_cast< idPlayer * >( entities[i] ); + p->WritePlayerStateToSnapshot( deltaMsg ); + } + } + // and the game state + WriteGameStateToSnapshot( deltaMsg ); + +} + +/* +================ +idGameLocal::ServerApplySnapshot +================ +*/ +bool idGameLocal::ServerApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::NetworkEventWarning +================ +*/ +void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { + char buf[1024]; + int length = 0; + va_list argptr; + + int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + int id = event->spawnId >> GENTITYNUM_BITS; + + length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); + va_end( argptr ); + idStr::Append( buf, sizeof(buf), "\n" ); + + common->DWarning( buf ); +} + +/* +================ +idGameLocal::ServerProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ServerProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ServerSendChatMessage +================ +*/ +void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text, const char *parm ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( parm ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + + if ( to == -1 || to == localClientNum ) { + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s", name, temp.c_str() ); + } +} + +/* +================ +idGameLocal::ServerProcessReliableMessage +================ +*/ +void idGameLocal::ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + // This parameter is ignored - it is only used when going to client from server + msg.ReadString( parm, sizeof( parm ) ); + + mpGame.ProcessChatMessage( clientNum, id == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + int index = msg.ReadLong(); + bool team = msg.ReadBits( 1 ) != 0; + mpGame.ProcessVoiceChat( clientNum, team, index ); + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + mpGame.WantKilled( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + mpGame.DropWeapon( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + mpGame.ServerCallVote( clientNum, msg ); + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + bool vote = ( msg.ReadByte() != 0 ); + mpGame.CastVote( clientNum, vote ); + break; + } +// RAVEN BEGIN +// shouchard: multivalue votes + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + mpGame.ServerCallPackedVote( clientNum, msg ); + break; + } +// RAVEN END +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + mpGame.ReceiveAndForwardVoiceData( clientNum, msg, id - GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT ); + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + int client = msg.ReadByte(); + statManager->SendStat( clientNum, client ); + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + int clientDest = msg.ReadByte(); + bool mute = ( 0 != msg.ReadByte() ); + mpGame.ServerHandleVoiceMuting( clientNum, clientDest, mute ); + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + int commandType = msg.ReadByte(); + int clientNum = msg.ReadByte(); + if ( SERVER_ADMIN_REMOVE_BAN == commandType ) { + mpGame.HandleServerAdminRemoveBan( "" ); + } else if ( SERVER_ADMIN_KICK == commandType ) { + mpGame.HandleServerAdminKickPlayer( clientNum ); + } else if ( SERVER_ADMIN_FORCE_SWITCH == commandType ) { + mpGame.HandleServerAdminForceTeamSwitch( clientNum ); + } else { + Warning( "Server admin packet with bad type %d", commandType ); + } + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + ServerSendBanList( clientNum ); + break; + } +// RAVEN END + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::ClientShowSnapshot +================ +*/ +void idGameLocal::ClientShowSnapshot( int clientNum ) const { + int baseBits; + idEntity *ent; + idPlayer *player; + idMat3 viewAxis; + idBounds viewBounds; + entityState_t *base; + + if ( !net_clientShowSnapshot.GetInteger() ) { + return; + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + viewAxis = player->viewAngles.ToMat3(); + viewBounds = player->GetPhysics()->GetAbsBounds().Expand( net_clientShowSnapshotRadius.GetFloat() ); + + for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + + if ( net_clientShowSnapshot.GetInteger() == 1 && ent->snapshotBits == 0 ) { + continue; + } + + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( !entBounds.IntersectsBounds( viewBounds ) ) { + continue; + } + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + baseBits = base->state.GetNumBitsWritten(); + } else { + baseBits = 0; + } + + if ( net_clientShowSnapshot.GetInteger() == 2 && baseBits == 0 ) { + continue; + } + + gameRenderWorld->DebugBounds( colorGreen, entBounds ); + gameRenderWorld->DrawText( va( "%d: %s (%d,%d bytes of %d,%d)\n", ent->entityNumber, + ent->name.c_str(), ent->snapshotBits >> 3, ent->snapshotBits & 7, baseBits >> 3, baseBits & 7 ), + entBounds.GetCenter(), 0.1f, colorWhite, viewAxis, 1 ); + } +} + +/* +================ +idGameLocal::UpdateLagometer +================ +*/ +void idGameLocal::UpdateLagometer( int aheadOfServer, int dupeUsercmds ) { + int i, j, ahead; + for ( i = 0; i < LAGO_HEIGHT; i++ ) { + memmove( (byte *)lagometer + LAGO_WIDTH * 4 * i, (byte *)lagometer + LAGO_WIDTH * 4 * i + 4, ( LAGO_WIDTH - 1 ) * 4 ); + } + j = LAGO_WIDTH - 1; + for ( i = 0; i < LAGO_HEIGHT; i++ ) { + lagometer[i][j][0] = lagometer[i][j][1] = lagometer[i][j][2] = lagometer[i][j][3] = 0; + } + ahead = idMath::Rint( (float)aheadOfServer / 16.0f ); + if ( ahead >= 0 ) { + for ( i = 2 * Max( 0, 5 - ahead ); i < 2 * 5; i++ ) { + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } + } else { + for ( i = 2 * 5; i < 2 * ( 5 + Min( 10, -ahead ) ); i++ ) { + lagometer[i][j][0] = 255; + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } + } + for ( i = LAGO_HEIGHT - 2 * Min( 6, dupeUsercmds ); i < LAGO_HEIGHT; i++ ) { + lagometer[i][j][0] = 255; + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } +} + +/* +================ +idGameLocal::ClientReadSnapshot +================ +*/ +void idGameLocal::ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) { + int i, entityDefNumber, numBitsRead; + idEntity *ent; + idPlayer *player, *spectated; + pvsHandle_t pvsHandle; + idDict args; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int spawnId; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + const idDeclEntityDef *decl; + + if ( net_clientLagOMeter.GetBool() && renderSystem && !IsServerDemo() ) { + UpdateLagometer( aheadOfServer, dupeUsercmds ); + if ( !renderSystem->UploadImage( LAGO_IMAGE, (byte *)lagometer, LAGO_IMG_WIDTH, LAGO_IMG_HEIGHT ) ) { + common->Printf( "lagometer: UploadImage failed. turning off net_clientLagOMeter\n" ); + net_clientLagOMeter.SetBool( false ); + } + } + + InitLocalClient( clientNum ); + + gameRenderWorld->DebugClear( time ); + + // update the game time + framenum = gameFrame; + time = gameTime; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + previousTime = time - GetMSec(); +// RAVEN END + + // so that StartSound/StopSound doesn't risk skipping + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = snapshotSequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( msg.ReadLong() ); +#endif + + ClientReadUnreliableMessages( msg ); + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + Error( "error after read unreliable" ); + } +#endif + + // read all entities from the snapshot + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + base = clientEntityStates[clientNum][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SPAWN_PLAYER should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" ); + assert( entityDefNumber >= 0 ); + assert( entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ); + const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); + Error( "write to and read from snapshot out of sync for classname '%s'\n", classname ); + } +#endif + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + + // read the PVS from the snapshot +#if ASYNC_WRITE_PVS + int serverPVS[idEntity::MAX_PVS_AREAS]; + i = numSourceAreas; + while ( i < idEntity::MAX_PVS_AREAS ) { + sourceAreas[ i++ ] = 0; + } + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + serverPVS[ i ] = msg.ReadLong(); + } + if ( memcmp( sourceAreas, serverPVS, idEntity::MAX_PVS_AREAS * sizeof( int ) ) ) { + common->Warning( "client PVS areas != server PVS areas, sequence 0x%x", snapshotSequence ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", sourceAreas[ i ] ); + } + common->DPrintf( "\n" ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", serverPVS[ i ] ); + } + common->DPrintf( "\n" ); + } + gameLocal.pvs.ReadPVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + snapshot->pvs[i] = msg.ReadDeltaLong( clientPVS[clientNum][i] ); + } +// RAVEN BEGIN +// ddynerman: performance profiling + net_entsInSnapshot += snapshotEntities.Num(); + net_snapshotSize += msg.GetSize(); +// RAVEN END + // add entities in the PVS that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == snapshotSequence ) { + continue; + } + + // if the entity is not in the snapshot PVS + if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) { + + if ( !ent->fl.networkSync ) { + // don't do stale / unstale on entities that are not marked network sync + continue; + } + + if ( ent->PhysicsTeamInPVS( pvsHandle ) ) { + if ( ent->entityNumber >= MAX_CLIENTS && isMapEntity[ ent->entityNumber ] ) { + // server says it's not in PVS, client says it's in PVS + // if that happens on map entities, most likely something is wrong + // I can see that moving pieces along several PVS could be a legit situation though + // this is a band aid, which means something is not done right elsewhere + if ( net_warnStale.GetInteger() > 1 || ( net_warnStale.GetInteger() == 1 && !ent->fl.networkStale ) ) { + common->Warning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), snapshotSequence ); + } + } +// RAVEN BEGIN +// bdube: hide while not in snapshot + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + + } else { + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + } +// RAVEN END + + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + ent->snapshotBits = 0; + +// RAVEN BEGIN +// bdube: hide while not in snapshot + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + if ( !ent->fl.networkSync ) { + // this is not supposed to happen + // it did however, when restarting a map with a different inhibit of entities caused entity numbers to be laid differently + // an idLight would occupy the entity number of an idItem for instance, and although it's not network-synced ( static level light ), + // the presence of a base would cause the system to think that it is and corrupt things + // we changed the map population so the entity numbers are kept the same no matter how things are inhibited + // this code is left as a fall-through fixup / sanity type of thing + // if this still happens, it's likely "client thinks map entity is stale" is happening as well, and we're still at risk of corruption + Warning( "ClientReadSnapshot: entity %d of type %s is not networkSync and has a snapshot base", ent->entityNumber, ent->GetType()->classname ); + entityStateAllocator.Free( clientEntityStates[clientNum][ent->entityNumber] ); + clientEntityStates[clientNum][ent->entityNumber] = NULL; + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( int i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // read the game and player state from the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + int targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( entities[ targetPlayer ] ) { + static_cast< idPlayer* >( entities[ targetPlayer ] )->ReadPlayerStateFromSnapshot( deltaMsg ); + } else { + player->ReadPlayerStateFromSnapshot( deltaMsg ); + } + + ReadGameStateFromSnapshot( deltaMsg ); + + // visualize the snapshot + ClientShowSnapshot( clientNum ); + + // process entity events + ClientProcessEntityNetworkEventQueue(); +} + +/* +=============== +idGameLocal::ClientReadServerDemoSnapshot + +server demos use a slightly different snapshot format +mostly, we don't need to transmit any PVS visibility information, as we transmit the whole entity activity +plus, we read that data to the virtual 'seeing it all' MAX_CLIENTS client + +=============== +*/ +void idGameLocal::ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) { + int i; + snapshot_t *snapshot; + entityState_t *base, *newBase; + idBitMsgDelta deltaMsg; + int numBitsRead, spawnId, entityDefNumber; + idEntity *ent; + idDict args; + const idDeclEntityDef *decl; + + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + bool ret = ClientApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // silence warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + gameRenderWorld->DebugClear( time ); + + framenum = gameFrame; + time = gameTime; + previousTime = time - GetMSec(); + + isNewFrame = true; + + snapshotEntities.Clear(); + + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[ MAX_CLIENTS ]; + clientSnapshots[ MAX_CLIENTS ] = snapshot; + + ClientReadUnreliableMessages( msg ); + + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + + base = clientEntityStates[ MAX_CLIENTS ][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SpawnPlayer should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadServerDemoSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + // FIXME: hu ho gonna have to rework that for server demos + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = sequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + + } + + // add entities that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == sequence ) { + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = sequence; + ent->snapshotBits = 0; + + // Ensure the clipmodel is relinked when transitioning from stale + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } + + base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // if the entity is not the right type + if ( ent->entityDefNumber != entityDefNumber ) { + // should never happen + common->DWarning( "entity '%s' is not the right type ( 0x%x, expected 0x%x )", ent->GetName(), ent->entityDefNumber, entityDefNumber ); + continue; + } + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if( ent->fl.networkStale ) { + // FIXME: does this happen ( in a server demo replay? ) + assert( false ); + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // visualize the snapshot + // FIXME + // ClientShowSnapshot( MAX_CLIENTS ); + + // read the game and player states + base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + // all the players + for ( i = 0; i < numClients; i++ ) { + if ( entities[i] ) { + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + idPlayer *p = static_cast< idPlayer * >( entities[i] ); + p->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + // the game state + ReadGameStateFromSnapshot( deltaMsg ); + + // process entity events + ClientProcessEntityNetworkEventQueue(); + +} + +/* +================ +idGameLocal::ClientApplySnapshot +================ +*/ +bool idGameLocal::ClientApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::ClientProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ClientProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while( eventQueue.Start() ) { + event = eventQueue.Start(); + + // only process forward, in order + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + // if new entity exists in this position, silently ignore + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +// RAVEN BEGIN +// bdube: client side hitscan + +/* +================ +idGameLocal::ClientHitScan +================ +*/ +void idGameLocal::ClientHitScan( const idBitMsg &msg ) { + int hitscanDefIndex; + idVec3 muzzleOrigin; + idVec3 dir; + idVec3 fxOrigin; + const idDeclEntityDef *decl; + int num_hitscans; + int i; + idEntity *owner; + + assert( isClient ); + + hitscanDefIndex = msg.ReadLong(); + decl = static_cast< const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, hitscanDefIndex ) ); + if ( !decl ) { + common->Warning( "idGameLocal::ClientHitScan: entity def index %d not found\n", hitscanDefIndex ); + return; + } + num_hitscans = decl->dict.GetInt( "hitscans", "1" ); + + owner = entities[ msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ) ]; + + muzzleOrigin[0] = msg.ReadFloat(); + muzzleOrigin[1] = msg.ReadFloat(); + muzzleOrigin[2] = msg.ReadFloat(); + fxOrigin[0] = msg.ReadFloat(); + fxOrigin[1] = msg.ReadFloat(); + fxOrigin[2] = msg.ReadFloat(); + + // one direction sent per hitscan + for( i = 0; i < num_hitscans; i++ ) { + dir = msg.ReadDir( 24 ); + gameLocal.HitScan( decl->dict, muzzleOrigin, dir, fxOrigin, owner ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientProcessReliableMessage +================ +*/ +void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + idDict backupSI; + + InitLocalClient( clientNum ); + + if ( serverDemo ) { + assert( demoState == DEMO_PLAYING ); + int record_type = msg.ReadByte(); + assert( record_type < DEMO_RECORD_COUNT ); + // if you need to do some special filtering: + switch ( record_type ) { + case DEMO_RECORD_CLIENTNUM: { + msg.ReadByte(); + /* + int client = msg.ReadByte(); + if ( client != -1 ) { + // reliable was targetted + if ( followPlayer != client ) { + // we're free flying or following someone else + return; + } + } + */ + break; + } + case DEMO_RECORD_EXCLUDE: { + int exclude = msg.ReadByte(); + exclude = exclude; // silence warning + assert( exclude != -1 ); + /* + if ( exclude == followPlayer ) { + return; + } + */ + break; + } + } + } + + id = msg.ReadByte(); + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + int client = msg.ReadByte(); + int spawnId = msg.ReadLong(); + if ( !entities[ client ] ) { + SpawnPlayer( client ); + entities[ client ]->FreeModelDef(); + } + // fix up the spawnId to match what the server says + // otherwise there is going to be a bogus delete/new of the client entity in the first ClientReadFromSnapshot + spawnIds[ client ] = spawnId; + break; + } + case GAME_RELIABLE_MESSAGE_DELETE_ENT: { + int spawnId = msg.ReadBits( 32 ); + idEntityPtr< idEntity > entPtr; + if( !entPtr.SetSpawnId( spawnId ) ) { + break; + } + if( entPtr.GetEntity() && entPtr.GetEntity()->entityNumber < MAX_CLIENTS ) { + delete entPtr.GetEntity(); + gameLocal.mpGame.UpdatePlayerRanks(); + } else { + delete entPtr.GetEntity(); + } + + break; + } + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { // (client should never get a TCHAT though) + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s\n", name, temp.c_str() ); + break; + } + case GAME_RELIABLE_MESSAGE_DB: { + msg_evt_t msg_evt = (msg_evt_t)msg.ReadByte(); + int parm1, parm2; + parm1 = msg.ReadByte( ); + parm2 = msg.ReadByte( ); + mpGame.PrintMessageEvent( -1, msg_evt, parm1, parm2 ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_SERVERINFO: { + idDict info; + msg.ReadDeltaDict( info, NULL ); + SetServerInfo( info ); + break; + } + case GAME_RELIABLE_MESSAGE_RESTART: { + MapRestart(); + break; + } + case GAME_RELIABLE_MESSAGE_STARTVOTE: { + char voteString[ MAX_STRING_CHARS ]; + int clientNum = msg.ReadByte( ); + msg.ReadString( voteString, sizeof( voteString ) ); + mpGame.ClientStartVote( clientNum, voteString ); + break; + } + case GAME_RELIABLE_MESSAGE_PRINT: { + char str[ MAX_PRINT_LEN ] = { '\0' }; + msg.ReadString( str, MAX_PRINT_LEN ); + mpGame.PrintMessage( -1, str ); + break; + } +// RAVEN BEGIN +// shouchard: multifield vote stuff + case GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE: { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + int clientNum = msg.ReadByte(); + voteData.m_fieldFlags = msg.ReadShort(); + char mapName[256]; + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + msg.ReadString( mapName, sizeof( mapName ) ); + voteData.m_map = mapName; + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientStartPackedVote( clientNum, voteData ); + break; + } +// RAVEN END + case GAME_RELIABLE_MESSAGE_UPDATEVOTE: { + int result = msg.ReadByte( ); + int yesCount = msg.ReadByte( ); + int noCount = msg.ReadByte( ); +// RAVEN BEGIN +// shouchard: multifield vote stuff + int multiVote = msg.ReadByte( ); + voteStruct_t voteData; + char mapNameBuffer[256]; + memset( &voteData, 0, sizeof( voteData ) ); + if ( multiVote ) { + voteData.m_fieldFlags = msg.ReadShort(); + voteData.m_kick = msg.ReadByte(); + msg.ReadString( mapNameBuffer, sizeof( mapNameBuffer ) ); + voteData.m_map = mapNameBuffer; + voteData.m_gameType = msg.ReadByte(); + voteData.m_timeLimit = msg.ReadByte(); + voteData.m_fragLimit = msg.ReadShort(); + voteData.m_tourneyLimit = msg.ReadShort(); + voteData.m_captureLimit = msg.ReadShort(); + voteData.m_buying = msg.ReadByte(); + voteData.m_teamBalance = msg.ReadByte(); + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) { + voteData.m_controlTime = 0; + } else { + voteData.m_controlTime = msg.ReadShort(); + } + } + mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount, voteData ); +// RAVEN END + break; + } + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + int numPortals = msg.ReadLong(); + assert( numPortals == gameRenderWorld->NumPortals() ); + for ( int i = 0; i < numPortals; i++ ) { + gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); + } + break; + } + case GAME_RELIABLE_MESSAGE_PORTAL: { + qhandle_t portal = msg.ReadLong(); + int blockingBits = msg.ReadBits( NUM_RENDER_PORTAL_BITS ); + assert( portal > 0 && portal <= gameRenderWorld->NumPortals() ); + gameRenderWorld->SetPortalState( portal, blockingBits ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + mpGame.ClientReadStartState( msg ); + break; + } +// RAVEN BEGIN +// bdube: + case GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND: + mpGame.PlayGlobalItemAcquireSound( msg.ReadBits ( gameLocal.entityDefBits ) ); + break; + +// ddynerman: death messagse + case GAME_RELIABLE_MESSAGE_DEATH: { + int attackerEntityNumber = msg.ReadByte( ); + int attackerScore = -1; + if( attackerEntityNumber >= 0 && attackerEntityNumber < MAX_CLIENTS ) { + attackerScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + int victimEntityNumber = msg.ReadByte( ); + int victimScore = -1; + if( victimEntityNumber >= 0 && victimEntityNumber < MAX_CLIENTS ) { + victimScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + + idPlayer* attacker = (attackerEntityNumber != 255 ? static_cast(gameLocal.entities[ attackerEntityNumber ]) : NULL); + idPlayer* victim = (victimEntityNumber != 255 ? static_cast(gameLocal.entities[ victimEntityNumber ]) : NULL); + int methodOfDeath = msg.ReadByte( ); + + mpGame.ReceiveDeathMessage( attacker, attackerScore, victim, victimScore, methodOfDeath ); + break; + } +// ddynerman: game state + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + mpGame.GetGameState()->ReceiveState( msg ); + break; + } +// ddynerman: game stats + case GAME_RELIABLE_MESSAGE_STAT: { + statManager->ReceiveStat( msg ); + break; + } +// asalmon: game stats for Xenon receive all client stats + case GAME_RELIABLE_MESSAGE_ALL_STATS: { + statManager->ReceiveAllStats( msg ); + break; + } +// ddynerman: multiple instances + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + mpGame.ClientSetInstance( msg ); + break; + } +// ddynerman: awards + case GAME_RELIABLE_MESSAGE_INGAMEAWARD: { + statManager->ReceiveInGameAward( msg ); + break; + } + +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + mpBanInfo_t banInfo; + char name[MAX_STRING_CHARS]; + char guid[MAX_STRING_CHARS]; + + FlushBanList( ); + while ( msg.ReadString( name, MAX_STRING_CHARS ) && msg.ReadString( guid, MAX_STRING_CHARS ) ) { + banInfo.name = name; + strncpy( banInfo.guid, guid, CLIENT_GUID_LENGTH ); + banList.Append( banInfo ); + } + + break; + } +// RAVEN END +// RAVEN END + default: { + Error( "Unknown server->client reliable message: %d", id ); + break; + } + } +} + +// RAVEN BEGIN +/* +================ +idGameLocal::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientRun( void ) { + if( isMultiplayer ) { + mpGame.ClientRun(); + } +} + +/* +================ +idGameLocal::ClientEndFrame +Called once each client render frame (after all ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientEndFrame( void ) { + if( isMultiplayer ) { + mpGame.ClientEndFrame(); + } +} + +/* +================ +idGameLocal::ProcessRconReturn +================ +*/ +void idGameLocal::ProcessRconReturn( bool success ) { + if( isMultiplayer ) { + mpGame.ProcessRconReturn( success ); + } +} + +/* +================ +idGameLocal::ResetGuiRconStatus +================ +*/ +void idGameLocal::ResetRconGuiStatus( void ) { + if( isMultiplayer ) { + mpGame.ResetRconGuiStatus( ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientPrediction +server demos: clientNum == MAX_CLIENTS +================ +*/ +gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame, ClientStats_t *cs ) { + idEntity *ent; + idPlayer *player; // may be NULL when predicting for a server demo + gameReturn_t ret; + + ret.sessionCommand[ 0 ] = '\0'; + + if ( clientNum == MAX_CLIENTS ) { + // clientCmds[ MAX_CLIENTS ] has the local interaction + // firing -> cycle follow players, jump -> free fly and cycle map spawns + + int btn_mask; + + player = NULL; + oldUsercmd = usercmd; + usercmd = clientCmds[ MAX_CLIENTS ]; + btn_mask = usercmd.buttons ^ oldUsercmd.buttons; + + if ( usercmd.buttons & btn_mask & BUTTON_ATTACK ) { + // find the next suitable player to follow + int delta = 0; + while ( true ) { + int i_next = GetNextClientNum( followPlayer ); + if ( followPlayer < i_next ) { + delta += i_next - followPlayer; + } else { + delta += numClients - followPlayer + i_next; + } + if ( delta > numClients ) { + // tried them all, no fit + followPlayer = -1; + break; + } + followPlayer = i_next; + if ( !entities[ followPlayer ] ) { + continue; + } + idPlayer *p = static_cast< idPlayer * >( entities[ followPlayer ] ); + if ( p->spectating ) { + continue; + } + // Tourney games, we only record instance 0, only cycle on instance 0 players + if ( p->GetInstance() != 0 ) { + continue; + } + break; + } + } + if ( usercmd.upmove & !oldUsercmd.upmove ) { + if ( followPlayer != -1 ) { + // set yourself up a bit above whoever you were following + freeView.SetFreeView( followPlayer ); + } else { + // pick a random spawn spot to start flying from + freeView.PickRandomSpawn(); + } + followPlayer = -1; + } + + player = NULL; + if ( followPlayer >= 0 ) { + player = static_cast< idPlayer* >( entities[ followPlayer ] ); + if ( !player ) { + // that player we were following was removed from the game + freeView.PickRandomSpawn(); + } else if ( player->spectating ) { + // our followed player went spectator, go free fly + freeView.SetFreeView( followPlayer ); + player = NULL; + followPlayer = -1; + } + } + + if ( !player && !freeView.Initialized() ) { + freeView.PickRandomSpawn(); + } + + } else { + player = static_cast( entities[clientNum] ); + } + +// RAVEN BEGIN +// bdube: added advanced debug support + if ( g_showDebugHud.GetInteger() && net_entsInSnapshot && net_snapshotSize) { + gameDebug.SetInt( "snap_ents", net_entsInSnapshot ); + gameDebug.SetInt( "snap_size", net_snapshotSize ); + + net_entsInSnapshot = 0; + net_snapshotSize = 0; + } + + if ( clientNum == localClientNum ) { + gameDebug.BeginFrame( ); + gameLog->BeginFrame( time ); + } + + isLastPredictFrame = lastPredictFrame; +// RAVEN END + + // check for local client lag + if ( player ) { + if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) { + player->isLagged = true; + } else { + player->isLagged = false; + } + } + + InitLocalClient( clientNum ); + + // update the game time + framenum++; + previousTime = time; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); +// RAVEN END + + // update the real client time and the new frame flag + if ( time > realClientTime ) { + realClientTime = time; + isNewFrame = true; + } else { + isNewFrame = false; + } + + if ( cs ) { + cs->isLastPredictFrame = isLastPredictFrame; + cs->isLagged = player ? player->isLagged : false; + cs->isNewFrame = isNewFrame; + } + + // set the user commands for this frame +// RAVEN BEGIN + usercmds = clientCmds; +// RAVEN END + + if ( clientNum == MAX_CLIENTS && !player ) { + freeView.Fly( usercmd ); + } + + // TMP + bool verbose = cvarSystem->GetCVarBool( "verbose_predict" ); + + // run prediction on all entities from the last snapshot + for ( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { +#if 0 + ent->thinkFlags |= TH_PHYSICS; + ent->ClientPredictionThink(); +#else + // don't force TH_PHYSICS on, only call ClientPredictionThink if thinkFlags != 0 + // it's better to synchronize TH_PHYSICS on specific entities when needed ( movers may be trouble ) + // thinkMask is a temp thing see if there are problems with only checking for TH_PHYSICS + if ( ent->thinkFlags != 0 ) { + if ( verbose ) { + common->Printf( "%d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); + } + ent->ClientPredictionThink(); + } else { + if ( verbose ) { + common->Printf( "skip %d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); + } + } +#endif + } + +// RAVEN BEGIN +// bdube: client entities + // run client entities + if ( isNewFrame ) { + // rjohnson: only run the entire logic when it is a new frame + rvClientEntity* cent; + for ( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } +// RAVEN END + + // service any pending events + idEvent::ServiceEvents(); + + // show any debug info for this frame + if ( isNewFrame ) { + RunDebugInfo(); + D_DrawDebugLines(); + } + + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + } + +// RAVEN BEGIN +// ddynerman: client logging/debugging + if ( clientNum == localClientNum ) { + gameDebug.EndFrame(); + gameLog->EndFrame(); + } +// RAVEN END + + g_simpleItems.ClearModified(); + return ret; +} + +/* +=============== +idGameLocal::Tokenize +=============== +*/ +void idGameLocal::Tokenize( idStrList &out, const char *in ) { + char buf[ MAX_STRING_CHARS ]; + char *token, *next; + + idStr::Copynz( buf, in, MAX_STRING_CHARS ); + token = buf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + idStr::ToLower( token ); + out.Append( token ); + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } +} + +/* +=============== +idGameLocal::DownloadRequest +=============== +*/ +bool idGameLocal::DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) { + if ( !cvarSystem->GetCVarInteger( "net_serverDownload" ) ) { + return false; + } + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 1 ) { + // 1: single URL redirect + if ( !strlen( cvarSystem->GetCVarString( "si_serverURL" ) ) ) { + common->Warning( "si_serverURL not set" ); + return false; + } + idStr::snPrintf( urls, MAX_STRING_CHARS, "1;%s", cvarSystem->GetCVarString( "si_serverURL" ) ); + return true; + } else { + // 2: table of pak URLs + // first token is the game pak if requested, empty if not requested by the client + // there may be empty tokens for paks the server couldn't pinpoint - the order matters + idStr reply = "2;"; + idStrList dlTable, pakList; + bool matchAll = false; + int i, j; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + matchAll = true; + } else { + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + } + Tokenize( pakList, paks ); + + for ( i = 0; i < pakList.Num(); i++ ) { + if ( i > 0 ) { + reply += ";"; + } + if ( pakList[ i ][ 0 ] == '\0' ) { + if ( i == 0 ) { + // pak 0 will always miss when client doesn't ask for game bin + common->DPrintf( "no game pak request\n" ); + } else { + common->DPrintf( "no pak %d\n", i ); + } + continue; + } + + if ( matchAll ) { + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + url.AppendPath( pakList[i] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } else { + for ( j = 0; j < dlTable.Num(); j++ ) { + if ( !pakList[ i ].Icmp( dlTable[ j ] ) ) { + break; + } + } + if ( j == dlTable.Num() ) { + common->Printf( "download for %s: pak not matched: %s\n", IP, pakList[ i ].c_str() ); + } else { + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + url.AppendPath( dlTable[ j ] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } + } + } + + idStr::Copynz( urls, reply, MAX_STRING_CHARS ); + return true; + } +} + +/* +=============== +idGameLocal::HTTPRequest +=============== +*/ +bool idGameLocal::HTTPRequest( const char *IP, const char *file, bool isGamePak ) { + return false; +} + +/* +=============== +idEventQueue::Alloc +=============== +*/ +entityNetEvent_t* idEventQueue::Alloc() { + entityNetEvent_t* event = eventAllocator.Alloc(); + event->prev = NULL; + event->next = NULL; + return event; +} + +/* +=============== +idEventQueue::Free +=============== +*/ +void idEventQueue::Free( entityNetEvent_t *event ) { + // should only be called on an unlinked event! + assert( !event->next && !event->prev ); + eventAllocator.Free( event ); +} + +/* +=============== +idEventQueue::Shutdown +=============== +*/ +void idEventQueue::Shutdown() { + eventAllocator.Shutdown(); + this->Init(); +} + +/* +=============== +idEventQueue::Init +=============== +*/ +void idEventQueue::Init( void ) { + start = NULL; + end = NULL; +} + +/* +=============== +idEventQueue::Dequeue +=============== +*/ +entityNetEvent_t* idEventQueue::Dequeue( void ) { + entityNetEvent_t* event = start; + if ( !event ) { + return NULL; + } + + start = start->next; + + if ( !start ) { + end = NULL; + } else { + start->prev = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::RemoveLast +=============== +*/ +entityNetEvent_t* idEventQueue::RemoveLast( void ) { + entityNetEvent_t *event = end; + if ( !event ) { + return NULL; + } + + end = event->prev; + + if ( !end ) { + start = NULL; + } else { + end->next = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::Enqueue +=============== +*/ +void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { + if ( behaviour == OUTOFORDER_DROP ) { + // go backwards through the queue and determine if there are + // any out-of-order events + while ( end && end->time > event->time ) { + entityNetEvent_t *outOfOrder = RemoveLast(); + common->DPrintf( "WARNING: new event with id %d ( time %d ) caused removal of event with id %d ( time %d ), game time = %d.\n", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); + Free( outOfOrder ); + } + } else if ( behaviour == OUTOFORDER_SORT && end ) { + // NOT TESTED -- sorting out of order packets hasn't been + // tested yet... wasn't strictly necessary for + // the patch fix. + entityNetEvent_t *cur = end; + // iterate until we find a time < the new event's + while ( cur && cur->time > event->time ) { + cur = cur->prev; + } + if ( !cur ) { + // add to start + event->next = start; + event->prev = NULL; + start = event; + } else { + // insert + event->prev = cur; + event->next = cur->next; + cur->next = event; + } + return; + } + + // add the new event + event->next = NULL; + event->prev = NULL; + + if ( end ) { + end->next = event; + event->prev = end; + } else { + start = event; + } + end = event; +} + +// RAVEN BEGIN +// shouchard: ban list stuff here + +/* +================ +idGameLocal::LoadBanList +================ +*/ +void idGameLocal::LoadBanList() { + + // open file + idStr token; + idFile *banFile = fileSystem->OpenFileRead( BANLIST_FILENAME ); + mpBanInfo_t banInfo; + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::LoadBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + // parse file (read three consecutive strings per banInfo (real complex ;) ) ) + while ( banFile->ReadString( token ) > 0 ) { + // name + banInfo.name = token; + + // guid + if ( banFile->ReadString( token ) > 0 && token.Length() >= 11 ) { + idStr::Copynz( banInfo.guid, token.c_str(), CLIENT_GUID_LENGTH ); + + banList.Append( banInfo ); + continue; + } + + gameLocal.Warning( "idGameLocal::LoadBanList: Potential curruption of banlist file (%s).", BANLIST_FILENAME ); + } + fileSystem->CloseFile( banFile ); + + banListLoaded = true; + banListChanged = false; +} + +/* +================ +idGameLocal::SaveBanList +================ +*/ +void idGameLocal::SaveBanList() { + if ( !banListChanged ) { + return; + } + + // open file + idFile *banFile = fileSystem->OpenFileWrite( BANLIST_FILENAME ); + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::SaveBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + for ( int i = 0; i < banList.Num(); i++ ) { + const mpBanInfo_t& banInfo = banList[ i ]; + char temp[ 16 ] = { 0, }; + banFile->WriteString( va( "%s", banInfo.name.c_str() ) ); + idStr::Copynz( temp, banInfo.guid, CLIENT_GUID_LENGTH ); + banFile->WriteString( temp ); +// idStr::Copynz( temp, (const char*)banInfo.ip, 15 ); +// banFile->WriteString( "255.255.255.255" ); + } + fileSystem->CloseFile( banFile ); + banListChanged = false; +} + +/* +================ +idGameLocal::FlushBanList +================ +*/ +void idGameLocal::FlushBanList() { + banList.Clear(); + banListLoaded = false; + banListChanged = false; +} + +/* +================ +idGameLocal::IsPlayerBanned +================ +*/ +bool idGameLocal::IsPlayerBanned( const char *name ) { + assert( name ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( name, banList[ i ].name ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::IsGuidBanned +================ +*/ +bool idGameLocal::IsGuidBanned( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::AddGuidToBanList +================ +*/ +void idGameLocal::AddGuidToBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + mpBanInfo_t banInfo; + char name[ 512 ]; // TODO: clean this up + gameLocal.GetPlayerName( gameLocal.GetClientNumByGuid( guid ), name ); + banInfo.name = name; + idStr::Copynz( banInfo.guid, guid, CLIENT_GUID_LENGTH ); +// SIMDProcessor->Memset( banInfo.ip, 0xFF, 15 ); + banList.Append( banInfo ); + banListChanged = true; +} + +/* +================ +idGameLocal::RemoveGuidFromBanList +================ +*/ +void idGameLocal::RemoveGuidFromBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we find a match remove it. + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + banList.RemoveIndex( i ); + banListChanged = true; + return; + } + } +} + +/* +================ +idGameLocal::RegisterClientGuid +================ +*/ +void idGameLocal::RegisterClientGuid( int clientNum, const char *guid ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + assert( guid ); + memset( clientGuids[ clientNum ], 0, CLIENT_GUID_LENGTH ); // just in case + idStr::Copynz( clientGuids[ clientNum ], guid, CLIENT_GUID_LENGTH ); +} + +/* +================ +idGameLocal::GetBanListCount +================ +*/ +int idGameLocal::GetBanListCount() { + if ( !banListLoaded ) { + LoadBanList(); + } + + return banList.Num(); +} + +/* +================ +idGameLocal::GetBanListEntry +================ +*/ +const mpBanInfo_t* idGameLocal::GetBanListEntry( int entry ) { + if ( !banListLoaded ) { + LoadBanList(); + } + + if ( entry < 0 || entry >= banList.Num() ) { + return NULL; + } + + return &banList[ entry ]; +} + +/* +================ +idGameLocal::GetGuidByClientNum +================ +*/ +const char *idGameLocal::GetGuidByClientNum( int clientNum ) { + assert( clientNum >= 0 && clientNum < numClients ); + + return clientGuids[ clientNum ]; +} + +/* +================ +idGameLocal::GetClientNumByGuid +================ +*/ +int idGameLocal::GetClientNumByGuid( const char * guid ) { + assert( guid ); + + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( !idStr::Icmp( networkSystem->GetClientGUID( i ), guid ) ) { + return i; + } + } + return -1; +} + +// mekberg: send ban list to client +/* +================ +idGameLocal::ServerSendBanList +================ +*/ +void idGameLocal::ServerSendBanList( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + + if ( !banListLoaded ) { + LoadBanList(); + } + + int i; + int c = banList.Num(); + for ( i = 0; i < c; i++ ) { + outMsg.WriteString( banList[ i ].name.c_str() ); + outMsg.WriteString( banList[ i ].guid, CLIENT_GUID_LENGTH ); + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); +} + +// mekberg: so we can populate ban list outside of multiplayer game +/* +=================== +idGameLocal::PopulateBanList +=================== +*/ +void idGameLocal::PopulateBanList( idUserInterface* hud ) { + if ( !hud ) { + return; + } + + int bans = GetBanListCount(); + for ( int i = 0; i < bans; i++ ) { + const mpBanInfo_t * banInfo = GetBanListEntry( i ); + hud->SetStateString( va( "sa_banList_item_%d", i ), va( "%d: %s\t%s", i+1, banInfo->name.c_str(), banInfo->guid ) ); + } + hud->DeleteStateVar( va( "sa_banList_item_%d", bans ) ); + hud->SetStateString( "sa_banList_sel_0", "-1" ); + // used to trigger a redraw, was slow, and doesn't seem to do anything so took it out. fixes #13675 + hud->StateChanged( gameLocal.time, false ); +} +// RAVEN END + +/* +================ +idGameLocal::ServerSendInstanceReliableMessageExcluding +Works like networkSystem->ServerSendReliableMessageExcluding, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg ); + return; + } + + for( i = 0; i < numClients; i++ ) { + if ( i == excludeClient ) { + continue; + } + + if( entities[ i ] == NULL ) { + continue; + } + + if( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg ); + } +} + +/* +================ +idGameLocal::ServerSendInstanceReliableMessage +Works like networkSystem->ServerSendReliableMessage, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if( owner == NULL ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); + return; + } + + if( clientNum == -1 ) { + for( i = 0; i < numClients; i++ ) { + if( entities[ i ] == NULL ) { + continue; + } + + if( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg ); + } + } else { + if( entities[ clientNum ] && entities[ clientNum ]->GetInstance() == owner->GetInstance() ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); + } + } +} + +/* +=============== +idGameLocal::SendUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +=============== +*/ +void idGameLocal::SendUnreliableMessage( const idBitMsg &msg, const int clientNum ) { + int icl; + idPlayer *player; + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // not to local client + // note that if local is spectated he will still get it + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( icl != clientNum ) { + player = static_cast< idPlayer * >( entities[ icl ] ); + // drop all clients except the ones that follow the client we emit to + if ( !player->spectating || player->spectator != clientNum ) { + continue; + } + } + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING ) { + // record the type and destination for remap on readback + idBitMsg dest; + byte msgBuf[ 16 ]; + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_CLIENTNUM ); + dest.WriteByte( clientNum ); + + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessagePVS +instanceEnt to NULL for no instance checks +excludeClient to -1 for no exclusions +=============== +*/ +void idGameLocal::SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1, int area2 ) { + int icl; + int matchInstance = instanceEnt ? instanceEnt->GetInstance() : -1; + idPlayer *player; + int areas[ 2 ]; + int numEvAreas; + + numEvAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numEvAreas++; + } + if ( area2 != -1 ) { + areas[ numEvAreas ] = area2; + numEvAreas++; + } + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // local client is always excluded + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( matchInstance >= 0 && entities[ icl ]->GetInstance() != matchInstance ) { + continue; + } + if ( clientsPVS[ icl ].i < 0 ) { + // clients for which we don't have PVS info won't get anything + continue; + } + player = static_cast< idPlayer * >( entities[ icl ] ); + + // if no areas are given, this is a global emit + if ( numEvAreas ) { + // ony send if pvs says this client can see it + if ( !pvs.InCurrentPVS( clientsPVS[ icl ], areas, numEvAreas ) ) { + continue; + } + } + + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING ) { + // record the target areas to the message + idBitMsg dest; + byte msgBuf[ 16 ]; + + // Tourney games: only record from instance 0 + if ( !instanceEnt || instanceEnt->GetInstance() == 0 ) { + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_AREAS ); + dest.WriteLong( area1 ); + dest.WriteLong( area2 ); + + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + } +} + +/* +=============== +idGameLocal::ClientReadUnreliableMessages +=============== +*/ +void idGameLocal::ClientReadUnreliableMessages( const idBitMsg &_msg ) { + idMsgQueue localQueue; + int size; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idBitMsg msg; + + localQueue.ReadFrom( _msg ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + while ( localQueue.Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + ProcessUnreliableMessage( msg ); + msg.BeginWriting(); + } +} + +/* +=============== +idGameLocal::DemoReplayInAreas +checks if our current demo replay view ( server demo ) matches the areas given +=============== +*/ +bool idGameLocal::IsDemoReplayInAreas( int area1, int area2 ) { + int areas[2]; + int numAreas; + + idVec3 view; + pvsHandle_t handle; + + bool ret; + + numAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numAreas++; + } + if ( area2 != -1 ) { + areas[ numAreas ] = area2; + numAreas++; + } + + assert( serverDemo ); + assert( demoState == DEMO_PLAYING ); + + if ( followPlayer == -1 ) { + view = freeView.GetOrigin(); + } else { + view = entities[ followPlayer ]->GetPhysics()->GetOrigin(); + } + + // could probably factorize this, at least for processing all unreliable messages, maybe at a higher level of the loop? + handle = pvs.SetupCurrentPVS( view ); + ret = pvs.InCurrentPVS( handle, areas, numAreas ); + pvs.FreeCurrentPVS( handle ); + return ret; +} + +/* +=============== +idGameLocal::ProcessUnreliableMessage +=============== +*/ +void idGameLocal::ProcessUnreliableMessage( const idBitMsg &msg ) { + if ( serverDemo ) { + assert( demoState == DEMO_PLAYING ); + int record_type = msg.ReadByte(); + assert( record_type < GAME_UNRELIABLE_RECORD_COUNT ); + switch ( record_type ) { + case GAME_UNRELIABLE_RECORD_CLIENTNUM: { + int client = msg.ReadByte(); + if ( client != -1 ) { + // unreliable was targetted + if ( followPlayer != client ) { + // either free flying, or following someone else + return; + } + } + break; + } + case GAME_UNRELIABLE_RECORD_AREAS: { + int area1 = msg.ReadLong(); + int area2 = msg.ReadLong(); + if ( !IsDemoReplayInAreas( area1, area2 ) ) { + return; + } + break; + } + } + } + + int type = msg.ReadByte(); + switch ( type ) { + case GAME_UNRELIABLE_MESSAGE_EVENT: { + idEntityPtr p; + int spawnId = msg.ReadBits( 32 ); + p.SetSpawnId( spawnId ); + + if ( p.GetEntity() ) { + p.GetEntity()->ClientReceiveEvent( msg.ReadByte(), time, msg ); + } else { + Warning( "ProcessUnreliableMessage: no local entity 0x%x for event %d", spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ), msg.ReadByte() ); + } + break; + } + case GAME_UNRELIABLE_MESSAGE_EFFECT: { + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* effect; + effectCategory_t category; + const idDecl *decl; + + decl = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + bool loop = msg.ReadBits( 1 ) != 0; + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + effect = new rvClientEffect( decl ); + effect->SetOrigin( origin ); + effect->SetAxis( quat.ToMat3() ); + effect->Play( time, loop, origin2 ); + } + + break; + } + case GAME_UNRELIABLE_MESSAGE_HITSCAN: { + ClientHitScan( msg ); + break; + } +#ifdef _USE_VOICECHAT + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + mpGame.ReceiveAndPlayVoiceData( msg ); + break; + } +#else + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + break; + } +#endif + default: { + Error( "idGameLocal::ProcessUnreliableMessage() - Unknown unreliable message '%d'\n", type ); + } + } +} + +/* +=============== +idGameLocal::WriteNetworkInfo +=============== +*/ +void idGameLocal::WriteNetworkInfo( idFile* file, int clientNum ) { + int i, j; + snapshot_t *snapshot; + entityState_t *entityState; + + if ( !IsServerDemo() ) { + + // save the current states + for ( i = 0; i < MAX_GENTITIES; i++ ) { + entityState = clientEntityStates[clientNum][i]; + file->WriteBool( !!entityState ); + if ( entityState ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + } + + // save the PVS states + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->WriteInt( clientPVS[i][j] ); + } + } + + } + + // players ( including local client ) + j = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + j++; + } + file->WriteInt( j ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + file->WriteInt( i ); + file->WriteInt( spawnIds[ i ] ); + } + + if ( !IsServerDemo() ) { + + // write number of snapshots so on readback we know how many to allocate + i = 0; + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + i++; + } + file->WriteInt( i ); + + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + file->WriteInt( snapshot->sequence ); + + // write number of entity states in the snapshot + i = 0; + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + i++; + } + file->WriteInt( i ); + + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + + file->Write( snapshot->pvs, sizeof( snapshot->pvs ) ); + } + + } + + // write the 'initial reliables' data + mpGame.WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idGameLocal::ReadNetworkInfo +=============== +*/ +void idGameLocal::ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) { + int i, j, num, numStates, stateSize; + snapshot_t *snapshot, **lastSnap; + entityState_t *entityState, **lastState; + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + assert( clientNum == MAX_CLIENTS || !IsServerDemo() ); + InitLocalClient( clientNum ); + + time = gameTime; + previousTime = gameTime; + + // force new frame + realClientTime = 0; + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + if ( !IsServerDemo() ) { + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + bool isValid; + + file->ReadBool( isValid ); + if ( isValid ) { + clientEntityStates[clientNum][i] = entityStateAllocator.Alloc(); + entityState = clientEntityStates[clientNum][i]; + entityState->next = NULL; + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + } else { + clientEntityStates[clientNum][i] = NULL; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->ReadInt( clientPVS[i][j] ); + } + } + + } + + // spawn player entities. ( numClients is not a count but the watermark of client indexes ) + file->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + int icl, spawnId; + file->ReadInt( icl ); + file->ReadInt( spawnId ); + SpawnPlayer( icl ); + spawnIds[ icl ] = spawnId; + numClients = icl + 1; + } + + if ( !IsServerDemo() ) { + + file->ReadInt( num ); + lastSnap = &clientSnapshots[ localClientNum ]; + for ( i = 0; i < num; i++ ) { + snapshot = snapshotAllocator.Alloc(); + snapshot->firstEntityState = NULL; + snapshot->next = NULL; + file->ReadInt( snapshot->sequence ); + + file->ReadInt( numStates ); + lastState = &snapshot->firstEntityState; + for ( j = 0; j < numStates; j++ ) { + entityState = entityStateAllocator.Alloc(); + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + entityState->next = NULL; + assert( !(*lastState ) ); + *lastState = entityState; + lastState = &entityState->next; + } + + file->Read( snapshot->pvs, sizeof( snapshot->pvs ) ); + + assert( !(*lastSnap) ); + *lastSnap = snapshot; + lastSnap = &snapshot->next; + } + + // spawn entities + for ( i = 0; i < ENTITYNUM_NONE; i++ ) { + int spawnId, entityDefNumber; + idBitMsgDelta deltaMsg; + idDict args; + entityState_t *base = clientEntityStates[clientNum][i]; + idEntity *ent = entities[i]; + const idDeclEntityDef *decl; + + if ( !base ) { + continue; + } + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString("spawnclass") ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // this is useful. for instance on idPlayer, resets stuff so powerups actually appear + ent->ClientUnstale(); + } + + { + // specific state read for game and player state + idBitMsgDelta deltaMsg; + entityState_t *base = clientEntityStates[clientNum][ENTITYNUM_NONE]; + idPlayer *player; + int targetPlayer; + + // it's possible to have a recording start right at CS_INGAME and not have a base for reading this yet + if ( base ) { + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + player = static_cast< idPlayer* >( entities[ targetPlayer ] ); + if ( !player ) { + Error( "ReadNetworkInfo: no local player entity" ); + return; + } + player->ReadPlayerStateFromSnapshot( deltaMsg ); + ReadGameStateFromSnapshot( deltaMsg ); + } + } + + // set self spectating state according to userinfo settings + GetLocalPlayer()->Spectate( idStr::Icmp( userInfo[ clientNum ].GetString( "ui_spectate" ), "Spectate" ) == 0 ); + + } + + // read the 'initial reliables' data + mpGame.ReadNetworkInfo( file, clientNum ); +} + +/* +============ +idGameLocal::SetDemoState +============ +*/ +void idGameLocal::SetDemoState( demoState_t state, bool _serverDemo, bool _timeDemo ) { + if ( demoState == DEMO_RECORDING && state == DEMO_NONE ) { + ServerClientDisconnect( MAX_CLIENTS ); + } + demoState = state; + serverDemo = _serverDemo; + timeDemo = _timeDemo; + if ( demoState == DEMO_NONE ) { + demo_hud = NULL; + demo_mphud = NULL; + demo_cursor = NULL; + } +} + +/* +=============== +idGameLocal::ValidateDemoProtocol +=============== +*/ +bool idGameLocal::ValidateDemoProtocol( int minor_ref, int minor ) { + // 1.1 beta : 67 + // 1.1 final: 68 + // 1.2 : 69 + // 1.3 : 71 + + // let 1.3 play 1.2 demos - keep a careful eye on snapshotting changes + demo_protocol = minor; + return ( minor_ref == minor || ( minor_ref == 71 && minor == 69 ) ); +} + +/* +=============== +idGameLocal::RandomSpawn +=============== +*/ +idPlayerStart *idGameLocal::RandomSpawn( void ) { + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; +} diff --git a/source/game/Healing_Station.cpp b/source/game/Healing_Station.cpp new file mode 100644 index 0000000..5a6e412 --- /dev/null +++ b/source/game/Healing_Station.cpp @@ -0,0 +1,209 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Healing_Station.h" + +CLASS_DECLARATION( idAnimatedEntity, rvHealingStation ) +END_CLASS + +/* +================ +rvHealingStation::Think +================ +*/ +void rvHealingStation::Think ( void ) { + // TODO: I'm guessing this is bad, but I wanted to get this in so that people could start + // placing it. The entity decided to stop thinking and I didn't have time to debug it. + BecomeActive( TH_ALL ); + + stateThread.Execute(); + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + if ( healthDispensed > 0 ) { + CreateFrame( float( healthDispensed ) / maxHealth ); + } + Present(); + } +} + +/* +================ +rvHealingStation::Spawn +================ +*/ +void rvHealingStation::Spawn ( void ) { + entityToHeal = 0; + nextHealTime = 0; + healFrequency = spawnArgs.GetInt( "heal_frequency", "24" ); + healAmount = spawnArgs.GetInt( "heal_amount", "1" ); + + healthDispensed = 0; + soundStartTime = 0; + soundLength = 0; + maxHealth = spawnArgs.GetInt( "max_health", "100" ); + + dispenseAnim = GetAnimator()->GetAnim( spawnArgs.GetString( "dispense_anim", "dispense" ) ); + + CreateFrame( 0 ); + + stateThread.SetOwner( this ); + stateThread.SetName( GetName() ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 4 ); +} + +/* +================ +rvHealingStation::Save +================ +*/ +void rvHealingStation::Save ( idSaveGame *savefile ) const { + stateThread.Save( savefile ); + entityToHeal.Save ( savefile ); + savefile->WriteInt( nextHealTime ); + savefile->WriteInt( healFrequency ); + savefile->WriteInt( healAmount ); + savefile->WriteInt( healthDispensed ); + savefile->WriteInt( maxHealth ); + savefile->WriteInt( dispenseAnim ); + savefile->WriteInt( soundStartTime ); + savefile->WriteInt( soundLength ); +} + +/* +================ +rvHealingStation::Restore +================ +*/ +void rvHealingStation::Restore ( idRestoreGame *savefile ) { + stateThread.Restore( savefile, this ); + entityToHeal.Restore ( savefile ); + savefile->ReadInt( nextHealTime ); + savefile->ReadInt( healFrequency ); + savefile->ReadInt( healAmount ); + savefile->ReadInt( healthDispensed ); + savefile->ReadInt( maxHealth ); + savefile->ReadInt( dispenseAnim ); + savefile->ReadInt( soundStartTime ); + savefile->ReadInt( soundLength ); +} + +/* +================ +rvHealingStation::BeginHealing +================ +*/ +void rvHealingStation::BeginHealing ( idEntity *toHeal ) { + entityToHeal = toHeal; + stateThread.SetState( "Healing" ); +} + +/* +================ +rvHealingStation::EndHealing +================ +*/ +void rvHealingStation::EndHealing ( void ) { + entityToHeal = NULL; +} + +/* +================ +rvHealingStation::CreateFrame +================ +*/ +void rvHealingStation::CreateFrame ( float station_health ) { + // Update the GUI + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->SetStateFloat( "station_health", 1.0f - station_health ); + renderEntity.gui[ 0 ]->StateChanged( gameLocal.time, true ); + } + + // Update the Animation + int numFrames = GetAnimator()->GetAnim( dispenseAnim )->NumFrames(); + float lerp = numFrames * station_health; + int frame = lerp; + lerp = lerp - frame; + frameBlend_t frameBlend = { 0, frame, frame + 1, 1.0f - lerp, lerp }; + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, dispenseAnim, frameBlend ); +} + +/* +================ +rvHealingStation::IsPlaying +================ +*/ +bool rvHealingStation::IsPlaying ( void ) { + idSoundEmitter* emitter = soundSystem->EmitterForIndex ( SOUNDWORLD_GAME, GetSoundEmitter ( ) ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvHealingStation ) + STATE ( "Healing", rvHealingStation::State_Healing ) +END_CLASS_STATES + +/* +================ +rvHealingStation::State_Healing +================ +*/ +stateResult_t rvHealingStation::State_Healing ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_DISPENSE, + }; + + if ( entityToHeal.IsValid() ) { + idPlayer* player = static_cast( entityToHeal.GetEntity( ) ); + const int entityMaxHealth = player->inventory.maxHealth; + + if ( healthDispensed < maxHealth && // and we have health to dispense... + entityToHeal->health < entityMaxHealth && // and the entity needs health. + entityToHeal->health > 0 ) // and he's still alive. + { + switch ( parms.stage ) { + case STAGE_INIT: + soundStartTime = gameLocal.time; + StartSound( "snd_start", SND_CHANNEL_ANY, 0, false, &soundLength ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time > soundStartTime + soundLength ) { + soundStartTime = 0; + soundLength = 0; + return SRESULT_STAGE ( STAGE_DISPENSE ); + } + return SRESULT_WAIT; + + case STAGE_DISPENSE: + if ( gameLocal.time > nextHealTime ) { // If it's time to heal... + int healthGiven = Min( maxHealth - healthDispensed, Min( healAmount, entityMaxHealth - entityToHeal->health ) ); + entityToHeal->health += healthGiven; + healthDispensed += healthGiven; + nextHealTime = gameLocal.time + healFrequency; + } + if ( !IsPlaying ( ) ) { + StartSound( "snd_loop", SND_CHANNEL_ANY, 0, false, NULL ); + } + return SRESULT_WAIT; + } + } + } + + StopSound ( SND_CHANNEL_ANY, 0 ); + StartSound ( "snd_stop", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_DONE; +} diff --git a/source/game/Healing_Station.h b/source/game/Healing_Station.h new file mode 100644 index 0000000..14b950b --- /dev/null +++ b/source/game/Healing_Station.h @@ -0,0 +1,45 @@ +/* +=============================================================================== + + rvHealingStation + +=============================================================================== +*/ +class rvHealingStation : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvHealingStation ); + + virtual void Think ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BeginHealing ( idEntity *toHeal ); + void EndHealing ( void ); + +protected: + + void CreateFrame ( float station_health ); + + stateResult_t State_Healing ( const stateParms_t& parms ); + + rvStateThread stateThread; + idEntityPtr entityToHeal; + int nextHealTime; + int healFrequency; + int healAmount; + int healthDispensed; + int maxHealth; + int dispenseAnim; + int soundStartTime; + int soundLength; + +private: + + bool IsPlaying ( void ); + + CLASS_STATES_PROTOTYPE ( rvHealingStation ); +}; + diff --git a/source/game/IK.cpp b/source/game/IK.cpp new file mode 100644 index 0000000..5b1d786 --- /dev/null +++ b/source/game/IK.cpp @@ -0,0 +1,1062 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idIK + +=============================================================================== +*/ + +/* +================ +idIK::idIK +================ +*/ +idIK::idIK( void ) { + ik_activate = false; + initialized = false; + self = NULL; + animator = NULL; + modifiedAnim = 0; + modelOffset.Zero(); +} + +/* +================ +idIK::~idIK +================ +*/ +idIK::~idIK( void ) { +} + +/* +================ +idIK::Save +================ +*/ +void idIK::Save( idSaveGame *savefile ) const { + savefile->WriteBool( initialized ); + savefile->WriteBool( ik_activate ); + savefile->WriteObject( self ); + savefile->WriteString( animator != NULL && animator->GetAnim( modifiedAnim ) ? animator->GetAnim( modifiedAnim )->Name() : "" ); + //savefile->WriteInt( modifiedAnim ); // Recomputed during restore, do not save + savefile->WriteVec3( modelOffset ); +} + +/* +================ +idIK::Restore +================ +*/ +void idIK::Restore( idRestoreGame *savefile ) { + idStr anim; + + savefile->ReadBool( initialized ); + savefile->ReadBool( ik_activate ); + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( anim ); + //savefile->ReadInt( modifiedAnim ); // This is defined below + savefile->ReadVec3( modelOffset ); + + if ( self ) { + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + } else { + animator = NULL; + modifiedAnim = 0; + } +} + +/* +================ +idIK::IsInitialized +================ +*/ +bool idIK::IsInitialized( void ) const { + return initialized && ik_enable.GetBool(); +} + +/* +================ +idIK::Init +================ +*/ +bool idIK::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + idRenderModel *model; + + if ( self == NULL ) { + return false; + } + + this->self = self; + + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + if ( animator->ModelDef()->ModelHandle() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) uses default model.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + model = animator->ModelHandle(); + if ( model == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + this->modelOffset = modelOffset; + + return true; +} + +/* +================ +idIK::Evaluate +================ +*/ +void idIK::Evaluate( void ) { +} + +/* +================ +idIK::ClearJointMods +================ +*/ +void idIK::ClearJointMods( void ) { + ik_activate = false; +} + +/* +================ +idIK::SolveTwoBones +================ +*/ +bool idIK::SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ) { + float length, lengthSqr, lengthInv, x, y; + idVec3 vec0, vec1; + + vec0 = endPos - startPos; + lengthSqr = vec0.LengthSqr(); + lengthInv = idMath::InvSqrt( lengthSqr ); + length = lengthInv * lengthSqr; + + // if the start and end position are too far out or too close to each other + if ( length > len0 + len1 || length < idMath::Fabs( len0 - len1 ) ) { + jointPos = startPos + 0.5f * vec0; + return false; + } + + vec0 *= lengthInv; + vec1 = dir - vec0 * dir * vec0; + vec1.Normalize(); + + x = ( length * length + len0 * len0 - len1 * len1 ) * ( 0.5f * lengthInv ); + y = idMath::Sqrt( len0 * len0 - x * x ); + + jointPos = startPos + x * vec0 + y * vec1; + + return true; +} + +/* +================ +idIK::GetBoneAxis +================ +*/ +float idIK::GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ) { + float length; + axis[0] = endPos - startPos; + length = axis[0].Normalize(); + axis[1] = dir - axis[0] * dir * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[1], axis[0] ); + return length; +} + + +/* +=============================================================================== + + idIK_Walk + +=============================================================================== +*/ + +/* +================ +idIK_Walk::idIK_Walk +================ +*/ +idIK_Walk::idIK_Walk() { + int i; + + initialized = false; + footModel = NULL; + numLegs = 0; + enabledLegs = 0; + for ( i = 0; i < MAX_LEGS; i++ ) { + footJoints[i] = INVALID_JOINT; + ankleJoints[i] = INVALID_JOINT; + kneeJoints[i] = INVALID_JOINT; + hipJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + hipForward[i].Zero(); + kneeForward[i].Zero(); + upperLegLength[i] = 0.0f; + lowerLegLength[i] = 0.0f; + upperLegToHipJoint[i].Identity(); + lowerLegToKneeJoint[i].Identity(); + oldAnkleHeights[i] = 0.0f; + } + waistJoint = INVALID_JOINT; + + smoothing = 0.75f; + waistSmoothing = 0.5f; + footShift = 0.0f; + waistShift = 0.0f; + minWaistFloorDist = 0.0f; + minWaistAnkleDist = 0.0f; + footUpTrace = 32.0f; + footDownTrace = 32.0f; + tiltWaist = false; + usePivot = false; + + pivotFoot = -1; + pivotYaw = 0.0f; + pivotPos.Zero(); + + oldHeightsValid = false; + oldWaistHeight = 0.0f; + waistOffset.Zero(); +} + +/* +================ +idIK_Walk::~idIK_Walk +================ +*/ +idIK_Walk::~idIK_Walk() { + if ( footModel ) { + delete footModel; + } +} + +/* +================ +idIK_Walk::Save +================ +*/ +void idIK_Walk::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteClipModel( footModel ); + + savefile->WriteInt( numLegs ); + savefile->WriteInt( enabledLegs ); + savefile->Write( footJoints, sizeof( footJoints ) ); + savefile->Write( ankleJoints, sizeof( ankleJoints ) ); + savefile->Write( kneeJoints, sizeof( kneeJoints ) ); + savefile->Write( hipJoints, sizeof( hipJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + savefile->Write( &waistJoint, sizeof( waistJoint ) ); + + savefile->Write( hipForward, sizeof( hipForward ) ); + savefile->Write( kneeForward, sizeof( kneeForward ) ); + + savefile->Write( upperLegLength, sizeof( upperLegLength ) ); + savefile->Write( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Write( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Write( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->WriteFloat( smoothing ); + savefile->WriteFloat( waistSmoothing ); + savefile->WriteFloat( footShift ); + savefile->WriteFloat( waistShift ); + savefile->WriteFloat( minWaistFloorDist ); + savefile->WriteFloat( minWaistAnkleDist ); + savefile->WriteFloat( footUpTrace ); + savefile->WriteFloat( footDownTrace ); + savefile->WriteBool( tiltWaist ); + savefile->WriteBool( usePivot ); + + savefile->WriteInt( pivotFoot ); + savefile->WriteFloat( pivotYaw ); + savefile->WriteVec3( pivotPos ); + savefile->WriteBool( oldHeightsValid ); + savefile->WriteFloat( oldWaistHeight ); + savefile->Write( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->WriteVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Restore +================ +*/ +void idIK_Walk::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadClipModel( footModel ); + + savefile->ReadInt( numLegs ); + savefile->ReadInt( enabledLegs ); + savefile->Read( footJoints, sizeof( footJoints ) ); + savefile->Read( ankleJoints, sizeof( ankleJoints ) ); + savefile->Read( kneeJoints, sizeof( kneeJoints ) ); + savefile->Read( hipJoints, sizeof( hipJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + savefile->Read( &waistJoint, sizeof( waistJoint ) ); + + savefile->Read( hipForward, sizeof( hipForward ) ); + savefile->Read( kneeForward, sizeof( kneeForward ) ); + + savefile->Read( upperLegLength, sizeof( upperLegLength ) ); + savefile->Read( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Read( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Read( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->ReadFloat( smoothing ); + savefile->ReadFloat( waistSmoothing ); + savefile->ReadFloat( footShift ); + savefile->ReadFloat( waistShift ); + savefile->ReadFloat( minWaistFloorDist ); + savefile->ReadFloat( minWaistAnkleDist ); + savefile->ReadFloat( footUpTrace ); + savefile->ReadFloat( footDownTrace ); + savefile->ReadBool( tiltWaist ); + savefile->ReadBool( usePivot ); + + savefile->ReadInt( pivotFoot ); + savefile->ReadFloat( pivotYaw ); + savefile->ReadVec3( pivotPos ); + savefile->ReadBool( oldHeightsValid ); + savefile->ReadFloat( oldWaistHeight ); + savefile->Read( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->ReadVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Init +================ +*/ +bool idIK_Walk::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + float footSize; + idVec3 verts[4]; + idTraceModel trm; + const char *jointName; + idVec3 dir, ankleOrigin, kneeOrigin, hipOrigin, dirOrigin; + idMat3 axis, ankleAxis, kneeAxis, hipAxis; + + static idVec3 footWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !self ) { + return false; + } + + numLegs = Min( self->spawnArgs.GetInt( "ik_numLegs", "0" ), MAX_LEGS ); + if ( numLegs == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledLegs = 0; + + // get all the joints + for ( i = 0; i < numLegs; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_foot%d", i+1 ) ); + footJoints[i] = animator->GetJointHandle( jointName ); + if ( footJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_ankle%d", i+1 ) ); + ankleJoints[i] = animator->GetJointHandle( jointName ); + if ( ankleJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_knee%d", i+1 ) ); + kneeJoints[i] = animator->GetJointHandle( jointName ); + if ( kneeJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_hip%d", i+1 ) ); + hipJoints[i] = animator->GetJointHandle( jointName ); + if ( hipJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_dir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledLegs |= 1 << i; + } + + jointName = self->spawnArgs.GetString( "ik_waist" ); + waistJoint = animator->GetJointHandle( jointName ); + if ( waistJoint == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'\n", jointName ); + } + + // get the leg bone lengths and rotation matrices + for ( i = 0; i < numLegs; i++ ) { + oldAnkleHeights[i] = 0.0f; + + ankleAxis = joints[ ankleJoints[ i ] ].ToMat3(); + ankleOrigin = joints[ ankleJoints[ i ] ].ToVec3(); + + kneeAxis = joints[ kneeJoints[ i ] ].ToMat3(); + kneeOrigin = joints[ kneeJoints[ i ] ].ToVec3(); + + hipAxis = joints[ hipJoints[ i ] ].ToMat3(); + hipOrigin = joints[ hipJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - kneeOrigin; + } else { + dir.Set( 1.0f, 0.0f, 0.0f ); + } + + hipForward[i] = dir * hipAxis.Transpose(); + kneeForward[i] = dir * kneeAxis.Transpose(); + + // conversion from upper leg bone axis to hip joint axis + upperLegLength[i] = GetBoneAxis( hipOrigin, kneeOrigin, dir, axis ); + upperLegToHipJoint[i] = hipAxis * axis.Transpose(); + + // conversion from lower leg bone axis to knee joint axis + lowerLegLength[i] = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); + lowerLegToKneeJoint[i] = kneeAxis * axis.Transpose(); + } + + smoothing = self->spawnArgs.GetFloat( "ik_smoothing", "0.75" ); + waistSmoothing = self->spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); + footShift = self->spawnArgs.GetFloat( "ik_footShift", "0" ); + waistShift = self->spawnArgs.GetFloat( "ik_waistShift", "0" ); + minWaistFloorDist = self->spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); + minWaistAnkleDist = self->spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); + footUpTrace = self->spawnArgs.GetFloat( "ik_footUpTrace", "32" ); + footDownTrace = self->spawnArgs.GetFloat( "ik_footDownTrace", "32" ); + tiltWaist = self->spawnArgs.GetBool( "ik_tiltWaist", "0" ); + usePivot = self->spawnArgs.GetBool( "ik_usePivot", "0" ); + + // setup a clip model for the feet + footSize = self->spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; + if ( footSize > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + verts[i] = footWinding[i] * footSize; + } + trm.SetupPolygon( verts, 4 ); + footModel = new idClipModel( trm ); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Walk::Evaluate +================ +*/ +void idIK_Walk::Evaluate( void ) { + int i, newPivotFoot = 0; + float modelHeight, jointHeight, lowestHeight, floorHeights[MAX_LEGS]; + float shift, smallestShift, newHeight, step, newPivotYaw, height, largestAnkleHeight; + idVec3 modelOrigin, normal, hipDir, kneeDir, start, end, jointOrigins[MAX_LEGS]; + idVec3 footOrigin, ankleOrigin, kneeOrigin, hipOrigin, waistOrigin; + idMat3 modelAxis, waistAxis, axis; + idMat3 hipAxis[MAX_LEGS], kneeAxis[MAX_LEGS], ankleAxis[MAX_LEGS]; + trace_t results; + + if ( !self || !gameLocal.isNewFrame ) { + return; + } + + // if no IK enabled on any legs + if ( !enabledLegs ) { + return; + } + + normal = - self->GetPhysics()->GetGravityNormal(); + modelOrigin = self->GetPhysics()->GetOrigin(); + modelAxis = self->GetRenderEntity()->axis; + modelHeight = modelOrigin * normal; + + modelOrigin += modelOffset * modelAxis; + + // create frame without joint mods + animator->CreateFrame( gameLocal.time, false ); + + // get the joint positions for the feet + lowestHeight = idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + animator->GetJointTransform( footJoints[i], gameLocal.time, footOrigin, axis ); + jointOrigins[i] = modelOrigin + footOrigin * modelAxis; + jointHeight = jointOrigins[i] * normal; + if ( jointHeight < lowestHeight ) { + lowestHeight = jointHeight; + newPivotFoot = i; + } + } + + if ( usePivot ) { + + newPivotYaw = modelAxis[0].ToYaw(); + + // change pivot foot + if ( newPivotFoot != pivotFoot || idMath::Fabs( idMath::AngleNormalize180( newPivotYaw - pivotYaw ) ) > 30.0f ) { + pivotFoot = newPivotFoot; + pivotYaw = newPivotYaw; + animator->GetJointTransform( footJoints[pivotFoot], gameLocal.time, footOrigin, axis ); + pivotPos = modelOrigin + footOrigin * modelAxis; + } + + // keep pivot foot in place + jointOrigins[pivotFoot] = pivotPos; + } + + // get the floor heights for the feet + for ( i = 0; i < numLegs; i++ ) { + + if ( !( enabledLegs & ( 1 << i ) ) ) { + continue; + } + + start = jointOrigins[i] + normal * footUpTrace; + end = jointOrigins[i] - normal * footDownTrace; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, mat3_identity, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + floorHeights[i] = results.endpos * normal; + + if ( ik_debug.GetBool() && footModel ) { + idFixedWinding w; + for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { + w += footModel->GetTraceModel()->verts[j]; + } + gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); + } + } + + const idPhysics *phys = self->GetPhysics(); + + // test whether or not the character standing on the ground + bool onGround = phys->HasGroundContacts(); + + // test whether or not the character is standing on a plat + bool onPlat = false; + for ( i = 0; i < phys->GetNumContacts(); i++ ) { + idEntity *ent = gameLocal.entities[ phys->GetContact( i ).entityNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != NULL && ent->IsType( idPlat::GetClassType() ) ) { +// RAVEN END + onPlat = true; + break; + } + } + + // adjust heights of the ankles + smallestShift = idMath::INFINITY; + largestAnkleHeight = -idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + + if ( onGround && ( enabledLegs & ( 1 << i ) ) ) { + shift = floorHeights[i] - modelHeight + footShift; + } else { + shift = 0.0f; + } + + if ( shift < smallestShift ) { + smallestShift = shift; + } + + animator->GetJointTransform( ankleJoints[i], gameLocal.time, ankleOrigin, ankleAxis[i] ); + jointOrigins[i] = modelOrigin + ankleOrigin * modelAxis; + + height = jointOrigins[i] * normal; + + if ( oldHeightsValid && !onPlat ) { + step = height + shift - oldAnkleHeights[i]; + shift -= smoothing * step; + } + + newHeight = height + shift; + if ( newHeight > largestAnkleHeight ) { + largestAnkleHeight = newHeight; + } + + oldAnkleHeights[i] = newHeight; + + jointOrigins[i] += shift * normal; + } + + animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin, waistAxis ); + waistOrigin = modelOrigin + waistOrigin * modelAxis; + + // adjust position of the waist + waistOffset = ( smallestShift + waistShift ) * normal; + + // if the waist should be at least a certain distance above the floor + if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { + start = waistOrigin; + end = waistOrigin + waistOffset - normal * minWaistFloorDist; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, modelAxis, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + height = ( waistOrigin + waistOffset - results.endpos ) * normal; + if ( height < minWaistFloorDist ) { + waistOffset += ( minWaistFloorDist - height ) * normal; + } + } + + // if the waist should be at least a certain distance above the ankles + if ( minWaistAnkleDist > 0.0f ) { + height = ( waistOrigin + waistOffset ) * normal; + if ( height - largestAnkleHeight < minWaistAnkleDist ) { + waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; + } + } + + if ( oldHeightsValid ) { + // smoothly adjust height of waist + newHeight = ( waistOrigin + waistOffset ) * normal; + step = newHeight - oldWaistHeight; + waistOffset -= waistSmoothing * step * normal; + } + + // save height of waist for smoothing + oldWaistHeight = ( waistOrigin + waistOffset ) * normal; + + if ( !oldHeightsValid ) { + oldHeightsValid = true; + return; + } + + // solve IK + for ( i = 0; i < numLegs; i++ ) { + + // get the position of the hip in world space + animator->GetJointTransform( hipJoints[i], gameLocal.time, hipOrigin, axis ); + hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; + hipDir = hipForward[i] * axis * modelAxis; + + // get the IK bend direction + animator->GetJointTransform( kneeJoints[i], gameLocal.time, kneeOrigin, axis ); + kneeDir = kneeForward[i] * axis * modelAxis; + + // solve IK and calculate knee position + SolveTwoBones( hipOrigin, jointOrigins[i], kneeDir, upperLegLength[i], lowerLegLength[i], kneeOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, hipOrigin, kneeOrigin ); + gameRenderWorld->DebugLine( colorRed, kneeOrigin, jointOrigins[i] ); + gameRenderWorld->DebugLine( colorYellow, kneeOrigin, kneeOrigin + hipDir ); + gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + kneeDir ); + } + + // get the axis for the hip joint + GetBoneAxis( hipOrigin, kneeOrigin, hipDir, axis ); + hipAxis[i] = upperLegToHipJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the knee joint + GetBoneAxis( kneeOrigin, jointOrigins[i], kneeDir, axis ); + kneeAxis[i] = lowerLegToKneeJoint[i] * ( axis * modelAxis.Transpose() ); + } + + // set the joint mods + animator->SetJointAxis( waistJoint, JOINTMOD_WORLD_OVERRIDE, waistAxis ); + animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_WORLD_OVERRIDE, hipAxis[i] ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_WORLD_OVERRIDE, kneeAxis[i] ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_WORLD_OVERRIDE, ankleAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Walk::ClearJointMods +================ +*/ +void idIK_Walk::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + animator->SetJointAxis( waistJoint, JOINTMOD_NONE, mat3_identity ); + animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} + +/* +================ +idIK_Walk::EnableAll +================ +*/ +void idIK_Walk::EnableAll( void ) { + enabledLegs = ( 1 << numLegs ) - 1; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::DisableAll +================ +*/ +void idIK_Walk::DisableAll( void ) { + enabledLegs = 0; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::EnableLeg +================ +*/ +void idIK_Walk::EnableLeg( int num ) { + enabledLegs |= 1 << num; +} + +/* +================ +idIK_Walk::DisableLeg +================ +*/ +void idIK_Walk::DisableLeg( int num ) { + enabledLegs &= ~( 1 << num ); +} + + +/* +=============================================================================== + + idIK_Reach + +=============================================================================== +*/ + +/* +================ +idIK_Reach::idIK_Reach +================ +*/ +idIK_Reach::idIK_Reach() { + int i; + + initialized = false; + numArms = 0; + enabledArms = 0; + for ( i = 0; i < MAX_ARMS; i++ ) { + handJoints[i] = INVALID_JOINT; + elbowJoints[i] = INVALID_JOINT; + shoulderJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + shoulderForward[i].Zero(); + elbowForward[i].Zero(); + upperArmLength[i] = 0.0f; + lowerArmLength[i] = 0.0f; + upperArmToShoulderJoint[i].Identity(); + lowerArmToElbowJoint[i].Identity(); + } +} + +/* +================ +idIK_Reach::~idIK_Reach +================ +*/ +idIK_Reach::~idIK_Reach() { +} + +/* +================ +idIK_Reach::Save +================ +*/ +void idIK_Reach::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteInt( numArms ); + savefile->WriteInt( enabledArms ); + savefile->Write( handJoints, sizeof( handJoints ) ); + savefile->Write( elbowJoints, sizeof( elbowJoints ) ); + savefile->Write( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + + savefile->Write( shoulderForward, sizeof( shoulderForward ) ); + savefile->Write( elbowForward, sizeof( elbowForward ) ); + + savefile->Write( upperArmLength, sizeof( upperArmLength ) ); + savefile->Write( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Write( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Write( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Restore +================ +*/ +void idIK_Reach::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadInt( numArms ); + savefile->ReadInt( enabledArms ); + savefile->Read( handJoints, sizeof( handJoints ) ); + savefile->Read( elbowJoints, sizeof( elbowJoints ) ); + savefile->Read( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + + savefile->Read( shoulderForward, sizeof( shoulderForward ) ); + savefile->Read( elbowForward, sizeof( elbowForward ) ); + + savefile->Read( upperArmLength, sizeof( upperArmLength ) ); + savefile->Read( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Read( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Read( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Init +================ +*/ +bool idIK_Reach::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + const char *jointName; + idTraceModel trm; + idVec3 dir, handOrigin, elbowOrigin, shoulderOrigin, dirOrigin; + idMat3 axis, handAxis, elbowAxis, shoulderAxis; + + if ( !self ) { + return false; + } + + numArms = Min( self->spawnArgs.GetInt( "ik_numArms", "0" ), MAX_ARMS ); + if ( numArms == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledArms = 0; + + // get all the joints + for ( i = 0; i < numArms; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_hand%d", i+1 ) ); + handJoints[i] = animator->GetJointHandle( jointName ); + if ( handJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid hand joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbow%d", i+1 ) ); + elbowJoints[i] = animator->GetJointHandle( jointName ); + if ( elbowJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid elbow joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_shoulder%d", i+1 ) ); + shoulderJoints[i] = animator->GetJointHandle( jointName ); + if ( shoulderJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid shoulder joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbowDir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledArms |= 1 << i; + } + + // get the arm bone lengths and rotation matrices + for ( i = 0; i < numArms; i++ ) { + + handAxis = joints[ handJoints[ i ] ].ToMat3(); + handOrigin = joints[ handJoints[ i ] ].ToVec3(); + + elbowAxis = joints[ elbowJoints[ i ] ].ToMat3(); + elbowOrigin = joints[ elbowJoints[ i ] ].ToVec3(); + + shoulderAxis = joints[ shoulderJoints[ i ] ].ToMat3(); + shoulderOrigin = joints[ shoulderJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - elbowOrigin; + } else { + dir.Set( -1.0f, 0.0f, 0.0f ); + } + + shoulderForward[i] = dir * shoulderAxis.Transpose(); + elbowForward[i] = dir * elbowAxis.Transpose(); + + // conversion from upper arm bone axis to should joint axis + upperArmLength[i] = GetBoneAxis( shoulderOrigin, elbowOrigin, dir, axis ); + upperArmToShoulderJoint[i] = shoulderAxis * axis.Transpose(); + + // conversion from lower arm bone axis to elbow joint axis + lowerArmLength[i] = GetBoneAxis( elbowOrigin, handOrigin, dir, axis ); + lowerArmToElbowJoint[i] = elbowAxis * axis.Transpose(); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Reach::Evaluate +================ +*/ +void idIK_Reach::Evaluate( void ) { + int i; + idVec3 modelOrigin, shoulderOrigin, elbowOrigin, handOrigin, shoulderDir, elbowDir; + idMat3 modelAxis, axis; + idMat3 shoulderAxis[MAX_ARMS], elbowAxis[MAX_ARMS]; + trace_t trace; + + modelOrigin = self->GetRenderEntity()->origin; + modelAxis = self->GetRenderEntity()->axis; + + // solve IK + for ( i = 0; i < numArms; i++ ) { + + // get the position of the shoulder in world space + animator->GetJointTransform( shoulderJoints[i], gameLocal.time, shoulderOrigin, axis ); + shoulderOrigin = modelOrigin + shoulderOrigin * modelAxis; + shoulderDir = shoulderForward[i] * axis * modelAxis; + + // get the position of the hand in world space + animator->GetJointTransform( handJoints[i], gameLocal.time, handOrigin, axis ); + handOrigin = modelOrigin + handOrigin * modelAxis; + + // get first collision going from shoulder to hand +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( self, trace, shoulderOrigin, handOrigin, CONTENTS_SOLID, self ); +// RAVEN END + handOrigin = trace.endpos; + + // get the IK bend direction + animator->GetJointTransform( elbowJoints[i], gameLocal.time, elbowOrigin, axis ); + elbowDir = elbowForward[i] * axis * modelAxis; + + // solve IK and calculate elbow position + SolveTwoBones( shoulderOrigin, handOrigin, elbowDir, upperArmLength[i], lowerArmLength[i], elbowOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, shoulderOrigin, elbowOrigin ); + gameRenderWorld->DebugLine( colorRed, elbowOrigin, handOrigin ); + gameRenderWorld->DebugLine( colorYellow, elbowOrigin, elbowOrigin + elbowDir ); + gameRenderWorld->DebugLine( colorGreen, elbowOrigin, elbowOrigin + shoulderDir ); + } + + // get the axis for the shoulder joint + GetBoneAxis( shoulderOrigin, elbowOrigin, shoulderDir, axis ); + shoulderAxis[i] = upperArmToShoulderJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the elbow joint + GetBoneAxis( elbowOrigin, handOrigin, elbowDir, axis ); + elbowAxis[i] = lowerArmToElbowJoint[i] * ( axis * modelAxis.Transpose() ); + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_WORLD_OVERRIDE, shoulderAxis[i] ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_WORLD_OVERRIDE, elbowAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Reach::ClearJointMods +================ +*/ +void idIK_Reach::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( handJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} diff --git a/source/game/IK.h b/source/game/IK.h new file mode 100644 index 0000000..f426e72 --- /dev/null +++ b/source/game/IK.h @@ -0,0 +1,155 @@ + +#ifndef __GAME_IK_H__ +#define __GAME_IK_H__ + +/* +=============================================================================== + + IK base class with a simple fast two bone solver. + +=============================================================================== +*/ + +#define IK_ANIM "ik_pose" + +class idIK { +public: + idIK( void ); + virtual ~idIK( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsInitialized( void ) const; + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + bool SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ); + float GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ); + +protected: + bool initialized; + bool ik_activate; + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // animation modified by the IK + idVec3 modelOffset; +}; + + +/* +=============================================================================== + + IK controller for a walking character with an arbitrary number of legs. + +=============================================================================== +*/ + +class idIK_Walk : public idIK { +public: + + idIK_Walk( void ); + virtual ~idIK_Walk( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + void EnableAll( void ); + void DisableAll( void ); + void EnableLeg( int num ); + void DisableLeg( int num ); + +private: + static const int MAX_LEGS = 8; + + idClipModel * footModel; + + int numLegs; + int enabledLegs; + jointHandle_t footJoints[MAX_LEGS]; + jointHandle_t ankleJoints[MAX_LEGS]; + jointHandle_t kneeJoints[MAX_LEGS]; + jointHandle_t hipJoints[MAX_LEGS]; + jointHandle_t dirJoints[MAX_LEGS]; + jointHandle_t waistJoint; + + idVec3 hipForward[MAX_LEGS]; + idVec3 kneeForward[MAX_LEGS]; + + float upperLegLength[MAX_LEGS]; + float lowerLegLength[MAX_LEGS]; + + idMat3 upperLegToHipJoint[MAX_LEGS]; + idMat3 lowerLegToKneeJoint[MAX_LEGS]; + + float smoothing; + float waistSmoothing; + float footShift; + float waistShift; + float minWaistFloorDist; + float minWaistAnkleDist; + float footUpTrace; + float footDownTrace; + bool tiltWaist; + bool usePivot; + + // state + int pivotFoot; + float pivotYaw; + idVec3 pivotPos; + bool oldHeightsValid; + float oldWaistHeight; + float oldAnkleHeights[MAX_LEGS]; + idVec3 waistOffset; +}; + + +/* +=============================================================================== + + IK controller for reaching a position with an arm or leg. + +=============================================================================== +*/ + +class idIK_Reach : public idIK { +public: + + idIK_Reach( void ); + virtual ~idIK_Reach( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + +private: + + static const int MAX_ARMS = 2; + + int numArms; + int enabledArms; + jointHandle_t handJoints[MAX_ARMS]; + jointHandle_t elbowJoints[MAX_ARMS]; + jointHandle_t shoulderJoints[MAX_ARMS]; + jointHandle_t dirJoints[MAX_ARMS]; + + idVec3 shoulderForward[MAX_ARMS]; + idVec3 elbowForward[MAX_ARMS]; + + float upperArmLength[MAX_ARMS]; + float lowerArmLength[MAX_ARMS]; + + idMat3 upperArmToShoulderJoint[MAX_ARMS]; + idMat3 lowerArmToElbowJoint[MAX_ARMS]; +}; + +#endif /* !__GAME_IK_H__ */ diff --git a/source/game/Icon.cpp b/source/game/Icon.cpp new file mode 100644 index 0000000..e89c8f8 --- /dev/null +++ b/source/game/Icon.cpp @@ -0,0 +1,96 @@ +//---------------------------------------------------------------- +// Icon.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Icon.h" + +/* +=============== +rvIcon::rvIcon +=============== +*/ +rvIcon::rvIcon() { + iconHandle = -1; +} + +/* +=============== +rvIcon::~rvIcon +=============== +*/ +rvIcon::~rvIcon() { + FreeIcon(); +} + +/* +=============== +rvIcon::FreeIcon +=============== +*/ +void rvIcon::FreeIcon( void ) { + if ( iconHandle != - 1 ) { + gameRenderWorld->FreeEntityDef( iconHandle ); + iconHandle = -1; + } +} + +/* +=============== +rvIcon::CreateIcon +=============== +*/ +qhandle_t rvIcon::CreateIcon( const char *mtr, int suppressViewID ) { + FreeIcon(); + + memset( &renderEnt, 0, sizeof( renderEnt ) ); + renderEnt.origin = vec3_origin; + renderEnt.axis = mat3_identity; + renderEnt.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 16.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 16.0f; + renderEnt.hModel = renderModelManager->FindModel( "_sprite" ); + renderEnt.callback = NULL; + renderEnt.numJoints = 0; + renderEnt.joints = NULL; + renderEnt.customSkin = 0; + renderEnt.noShadow = true; + renderEnt.noSelfShadow = true; + renderEnt.customShader = declManager->FindMaterial( mtr ); + renderEnt.referenceShader = 0; + renderEnt.bounds = renderEnt.hModel->Bounds( &renderEnt ); + renderEnt.suppressSurfaceInViewID = suppressViewID; + + iconHandle = gameRenderWorld->AddEntityDef( &renderEnt ); + + return iconHandle; +} + +/* +=============== +rvIcon::UpdateIcon +=============== +*/ +void rvIcon::UpdateIcon( const idVec3 &origin, const idMat3 &axis ) { + assert( iconHandle >= 0 ); + + renderEnt.origin = origin; + renderEnt.axis = axis; + gameRenderWorld->UpdateEntityDef( iconHandle, &renderEnt ); +} + +int rvIcon::GetWidth( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ]; +} + +int rvIcon::GetHeight( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ]; +} diff --git a/source/game/Icon.h b/source/game/Icon.h new file mode 100644 index 0000000..7d2a532 --- /dev/null +++ b/source/game/Icon.h @@ -0,0 +1,34 @@ +//---------------------------------------------------------------- +// Icon.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICON_H__ +#define __ICON_H__ + +class rvIcon { +public: + rvIcon(); + ~rvIcon(); + void UpdateIcon( const idVec3 &origin, const idMat3 &axis ); + qhandle_t CreateIcon( const char *mtr, int suppressViewID = 0 ); + void FreeIcon( void ); + qhandle_t GetHandle( void ) const; + + int GetHeight( void ) const; + int GetWidth( void ) const; +private: + void Draw( jointHandle_t joint ); + void Draw( const idVec3 &origin ); + + renderEntity_t renderEnt; + qhandle_t iconHandle; +}; + +ID_INLINE qhandle_t rvIcon::GetHandle( void ) const { + return iconHandle; +} + +#endif /* !_ICON_H_ */ + diff --git a/source/game/IconManager.cpp b/source/game/IconManager.cpp new file mode 100644 index 0000000..66c012f --- /dev/null +++ b/source/game/IconManager.cpp @@ -0,0 +1,205 @@ +//---------------------------------------------------------------- +// IconManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "IconManager.h" + +rvIconManager iconManagerLocal; +rvIconManager* iconManager = &iconManagerLocal; + +/* +=============================================================================== + + rvIconManager + +=============================================================================== +*/ + +void rvIconManager::AddIcon( int clientNum, const char* iconName ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + icons[ clientNum ].Append( rvPair(new rvIcon(), gameLocal.time + ICON_STAY_TIME) ); + icons[ clientNum ][ icons[ clientNum ].Num() - 1 ].First()->CreateIcon( player->spawnArgs.GetString( iconName ), (clientNum == gameLocal.localClientNum ? gameLocal.localClientNum + 1 : 0) ); +} + +void rvIconManager::UpdateIcons( void ) { + if( gameLocal.GetLocalPlayer() == NULL || !gameLocal.GetLocalPlayer()->GetRenderView() ) { + return; + } + + // draw team icons + if( gameLocal.IsTeamGame() ) { + UpdateTeamIcons(); + } + + // draw chat icons + UpdateChatIcons(); + + // remove old icons and icons not in our snapshot + // ** if you want to have permanent icons, you'll have to add support + // ** for the icons to re-appear when the player comes back in your snapshot (like team icons and chat icons) + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( gameLocal.time > icons[ i ][ j ].Second() || (gameLocal.entities[ i ] && gameLocal.entities[ i ]->fl.networkStale) ) { + rvIcon* oldIcon = icons[ i ][ j ].First(); + oldIcon->FreeIcon(); + icons[ i ].RemoveIndex( j-- ); + delete oldIcon; + } + } + } + + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + // draw extra icons + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + if( player->IsHidden() || !icons[ i ].Num() ) { + continue; + } + + // distribute the icons appropriately + int maxHeight = 0; + int totalWidth = 0; + + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + totalWidth += icons[ i ][ j ].First()->GetWidth(); + } + + idVec3 centerIconPosition = player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + + + if( teamIcons[ player->entityNumber ].GetHandle() >= 0 ) { + centerIconPosition[ 2 ] += teamIcons[ player->entityNumber ].GetHeight(); + } + + int incrementalWidth = 0; + for( int j = 0; j < icons[ i ].Num(); j++ ) { + idVec3 iconPosition = centerIconPosition; + iconPosition += ( (-totalWidth / 2) + incrementalWidth + (icons[ i ][ j ].First()->GetWidth() / 2) ) * localPlayer->GetRenderView()->viewaxis[ 1 ]; + incrementalWidth += icons[ i ][ j ].First()->GetWidth(); + icons[ i ][ j ].First()->UpdateIcon( player->GetPhysics()->GetOrigin() + iconPosition, localPlayer->GetRenderView()->viewaxis ); + } + } + } +} + +void rvIconManager::UpdateTeamIcons( void ) { + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return; + } + int localTeam = localPlayer->team; + bool spectating = localPlayer->spectating; + + if( localPlayer->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ localPlayer->spectator ]; + if( spec ) { + localTeam = spec->team; + localPlayer = spec; + } else { + localTeam = -1; + } + } + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + //if entity i is a player, manage his icon. + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale && !spectating ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + //if the player is alive and not hidden, show his icon. + if( player->team == localTeam && !player->IsHidden() && !player->pfl.dead && gameLocal.mpGame.IsInGame( i ) ) { + if( teamIcons[ i ].GetHandle() < 0 ) { + teamIcons[ i ].CreateIcon( player->spawnArgs.GetString( player->team ? "mtr_team_icon_strogg" : "mtr_team_icon_marine" ), (player == localPlayer ? localPlayer->entityNumber + 1 : 0) ); + } + teamIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ), localPlayer->GetRenderView()->viewaxis ); + //else, the player is hidden, dead, or otherwise not needing an icon-- free it. + } else { + if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } + //if entity i is not a player, free icon i from the map. + } else if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } +} + +void rvIconManager::UpdateChatIcons( void ) { + + int localInst = gameLocal.GetLocalPlayer()->GetInstance(); + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( gameLocal.localClientNum == i ) { + continue; + } + + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[ i ] ); + + if ( player->isChatting && + !player->IsHidden() && + !( ( idPhysics_Player* )player->GetPhysics() )->IsDead() && + gameLocal.mpGame.IsInGame( i ) + && (localInst == player->GetInstance())) { + if ( chatIcons[ i ].GetHandle() < 0 ) { + chatIcons[ i ].CreateIcon( player->spawnArgs.GetString( "mtr_icon_chatting" ), ( player == gameLocal.GetLocalPlayer() ? player->entityNumber + 1 : 0) ); + } + int maxHeight = 0; + for ( int j = 0; j < icons[ i ].Num(); j++ ) { + if ( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + } + if ( teamIcons[ i ].GetHandle() >= 0 && teamIcons[ i ].GetHeight() > maxHeight ) { + maxHeight = teamIcons[ i ].GetHeight(); + } + idVec3 centerIconPosition = player->spawnArgs.GetVector( ( player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + centerIconPosition[ 2 ] += maxHeight; + chatIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + centerIconPosition, gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis ); + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } +} + +/* +=============== +rvIconManager::Shutdown +=============== +*/ +void rvIconManager::Shutdown( void ) { + int i, j; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < icons[ i ].Num(); j++ ) { + icons[ i ][ j ].First()->FreeIcon(); + } + icons[ i ].Clear(); + teamIcons[ i ].FreeIcon(); + chatIcons[ i ].FreeIcon(); + } +} diff --git a/source/game/IconManager.h b/source/game/IconManager.h new file mode 100644 index 0000000..b48f9f9 --- /dev/null +++ b/source/game/IconManager.h @@ -0,0 +1,30 @@ +//---------------------------------------------------------------- +// IconManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICONMANAGER_H__ +#define __ICONMANAGER_H__ + +#include "Icon.h" + +const int ICON_STAY_TIME = 2000; + +class rvIconManager { +public: + void AddIcon( int clientNum, const char* iconName ); + void UpdateIcons( void ); + void UpdateTeamIcons( void ); + void UpdateChatIcons( void ); + void Shutdown( void ); + +private: + idList > icons[ MAX_CLIENTS ]; + rvIcon teamIcons[ MAX_CLIENTS ]; + rvIcon chatIcons[ MAX_CLIENTS ]; +}; + +extern rvIconManager* iconManager; + +#endif diff --git a/source/game/Instance.cpp b/source/game/Instance.cpp new file mode 100644 index 0000000..2288964 --- /dev/null +++ b/source/game/Instance.cpp @@ -0,0 +1,233 @@ +//---------------------------------------------------------------- +// Instance.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Instance.h" + +rvInstance::rvInstance( int id, bool deferPopulate ) { + instanceID = id; + spawnInstanceID = id; + + gameLocal.AddClipWorld( id ); + + mapEntityNumbers = NULL; + numMapEntities = 0; + + initialSpawnCount = idGameLocal::INITIAL_SPAWN_COUNT; + + if ( !deferPopulate ) { + Populate(); + } +} + +rvInstance::~rvInstance() { + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + } + gameLocal.RemoveClipWorld( instanceID ); +} + +void rvInstance::Populate( int serverChecksum ) { + gameState_t currentState = gameLocal.GameState(); + + // disable the minSpawnIndex lock out + int latchMinSpawnIndex = gameLocal.minSpawnIndex; + gameLocal.minSpawnIndex = MAX_CLIENTS; + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( GAMESTATE_RESTART ); + } + + if ( gameLocal.isServer ) { + // When populating on a server, record the entity numbers + numMapEntities = gameLocal.GetNumMapEntities(); + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + } + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + // read the index we should start populating at as transmitted by the server + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + //common->Printf( "pos: get starting index for instance %d sets firstFreeIndex to %d\n", instanceID, gameLocal.firstFreeIndex ); + + // remember the spawnCount ahead of time, so that the client can accurately reconstruct its spawnIds + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers, &initialSpawnCount ); + + // only build the message in MP + if ( gameLocal.isMultiplayer ) { + BuildInstanceMessage(); + + // force joins of anyone in our instance so they get potentially new map entitynumbers + for( int i = 0; i < MAX_CLIENTS; i++ ) { + PACIFIER_UPDATE; + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player && player->GetInstance() == instanceID ) { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg ); + } + } + } + } else { + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + // When populating on a client, spawn the map using existing numbers + // check for good state + // OK to spawn w/o specific entity numbers if we're in the startup process. Otherwise, + // we need entity numbers from the server. + // TTimo: only valid for backward 1.2 playback now + assert( !proto69 || ( mapEntityNumbers || ( instanceID == 0 && gameLocal.GameState() == GAMESTATE_STARTUP ) ) ); + + if ( !proto69 ) { + // have the client produce a log of the entity layout so we can match it with the server's + // this is also going to be used to issue the EV_FindTargets below + if ( mapEntityNumbers ) { + delete []mapEntityNumbers; + } + numMapEntities = gameLocal.GetNumMapEntities(); + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + } + + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + + gameLocal.SetSpawnCount( initialSpawnCount ); // that was transmitted through the instance msg + if ( proto69 ) { + gameLocal.SpawnMapEntities( spawnInstanceID, mapEntityNumbers, NULL ); + } else { + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers ); + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + if ( serverChecksum != 0 && checksum != serverChecksum ) { + common->Error( "client side map populate checksum ( 0x%x ) doesn't match server's ( 0x%x )", checksum, serverChecksum ); + } + } + } + + + for ( int i = 0; i < numMapEntities; i++ ) { + if ( mapEntityNumbers[ i ] < 0 || mapEntityNumbers[ i ] >= MAX_GENTITIES ) { + continue; + } + + if ( (i % 100) == 0 ) { + PACIFIER_UPDATE; + } + + idEntity* ent = gameLocal.entities[ mapEntityNumbers[ i ] ]; + + if ( ent ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + ent->PostEventMS( &EV_PostSpawn, 0 ); + } + } + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( currentState ); + } + + // re-enable the min spawn index + assert( latchMinSpawnIndex == MAX_CLIENTS || gameLocal.firstFreeIndex <= latchMinSpawnIndex ); + gameLocal.minSpawnIndex = latchMinSpawnIndex; +} + +void rvInstance::PopulateFromMessage( const idBitMsg& msg ) { + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( proto69 ) { + numMapEntities = msg.ReadShort(); + } + initialSpawnCount = msg.ReadShort(); + + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + + if ( proto69 ) { + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + for ( int i = 0; i < numMapEntities; i++ ) { + mapEntityNumbers[ i ] = msg.ReadShort(); + } + Populate(); + } else { + int populateIndex = msg.ReadLong(); + gameLocal.ClientSetStartingIndex( populateIndex ); + //common->Printf( "pos: set firstFreeIndex to %d\n", populateIndex ); + int checksum = msg.ReadLong(); + Populate( checksum ); + } +} + +void rvInstance::Restart( void ) { + if ( gameLocal.isMultiplayer ) { + Populate(); + } else { + gameLocal.SpawnMapEntities(); + } +} + +void rvInstance::BuildInstanceMessage( void ) { + // Build the client join instance msg + mapEntityMsg.BeginWriting(); + mapEntityMsg.Init( mapEntityMsgBuf, sizeof( byte ) * MAX_GAME_MESSAGE_SIZE ); + mapEntityMsg.WriteByte( GAME_RELIABLE_MESSAGE_SET_INSTANCE ); + + mapEntityMsg.WriteByte( instanceID ); + mapEntityMsg.WriteShort( initialSpawnCount ); + // we need to send that down for tourney so the index will match + mapEntityMsg.WriteLong( gameLocal.GetStartingIndexForInstance( instanceID ) ); + + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + //common->Printf( "pop: server checksum: 0x%x\n", checksum ); + mapEntityMsg.WriteLong( checksum ); +} + +void rvInstance::JoinInstance( idPlayer* player ) { + assert( player && gameLocal.isServer ); + + // Transmit the instance information to the new client + if( gameLocal.isListenServer && player == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ServerSetInstance( instanceID ); + } else { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg ); + } +} + +void rvInstance::PrintMapNumbers( void ) { + gameLocal.Printf( "Instance: %d\n", instanceID ); + gameLocal.Printf( "Num Map Entities: %d\n", numMapEntities ); + + for( int i = 0; i < numMapEntities; i++ ) { + gameLocal.Printf( "%d\n", mapEntityNumbers[ i ] ); + } +} + +/* +================ +rvInstance::SetSpawnInstanceID +Sets the spawn instance ID for this instance. On the client, only instance 0 is ever +used, but it spawns in map entities for other instances. spawnInstanceID is used to +spawn map entities with the correct instance number on the client. +================ +*/ +void rvInstance::SetSpawnInstanceID( int newInstance ) { + assert( gameLocal.isClient ); + + spawnInstanceID = newInstance; +} diff --git a/source/game/Instance.h b/source/game/Instance.h new file mode 100644 index 0000000..e452679 --- /dev/null +++ b/source/game/Instance.h @@ -0,0 +1,47 @@ +//---------------------------------------------------------------- +// Instance.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include "Game_local.h" + +class rvInstance { +public: + rvInstance( int id, bool deferPopulate = false ); + ~rvInstance(); + + void Populate( int serverChecksum = 0 ); + void PopulateFromMessage( const idBitMsg& msg ); + void Restart( void ); + + void JoinInstance( idPlayer* player ); + int GetInstanceID( void ); + + void SetSpawnInstanceID( int newInstance ); + + void PrintMapNumbers( void ); + int GetNumMapEntities( void ) { return numMapEntities; } + unsigned short GetMapEntityNumber( int i ) { return mapEntityNumbers[ i ]; } + +private: + void BuildInstanceMessage( void ); + + int instanceID; + int spawnInstanceID; + unsigned short* mapEntityNumbers; + int numMapEntities; + int initialSpawnCount; + + idBitMsg mapEntityMsg; + byte mapEntityMsgBuf[ MAX_GAME_MESSAGE_SIZE ]; +}; + +ID_INLINE int rvInstance::GetInstanceID( void ) { + return instanceID; +} + +#endif diff --git a/source/game/Item.cpp b/source/game/Item.cpp new file mode 100644 index 0000000..ba48a9a --- /dev/null +++ b/source/game/Item.cpp @@ -0,0 +1,2375 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idItem + +=============================================================================== +*/ + +const idEventDef EV_DropToFloor( "" ); +const idEventDef EV_RespawnItem( "respawn" ); +const idEventDef EV_RespawnFx( "" ); +const idEventDef EV_GetPlayerPos( "" ); +const idEventDef EV_HideObjective( "", "e" ); +const idEventDef EV_CamShot( "" ); + +// RAVEN BEGIN +// abahr: +const idEventDef EV_SetGravity( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idItem ) + EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) + EVENT( EV_Touch, idItem::Event_Touch ) + EVENT( EV_Activate, idItem::Event_Trigger ) + EVENT( EV_RespawnItem, idItem::Event_Respawn ) + EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +// RAVEN BEGIN +// abahr + EVENT( EV_SetGravity, idItem::Event_SetGravity ) +// RAVEN END + +END_CLASS + + +/* +================ +idItem::idItem +================ +*/ +idItem::idItem() { + spin = false; + inView = false; + skin = NULL; + pickupSkin = NULL; + inViewTime = 0; + lastCycle = 0; + lastRenderViewTime = -1; + itemShellHandle = -1; + shellMaterial = NULL; + orgOrigin.Zero(); + canPickUp = true; + fl.networkSync = true; + trigger = NULL; + syncPhysics = false; + srvReady = -1; + clReady = -1; + effectIdle = NULL; + itemPVSArea = 0; + effectIdle = NULL; + simpleItem = false; + pickedUp = false; +} + +/* +================ +idItem::~idItem +================ +*/ +idItem::~idItem() { + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + } + if ( trigger ) { + delete trigger; + } + + SetPhysics( NULL ); +} + +/* +================ +idItem::Save +================ +*/ +void idItem::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteBool( spin ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( pickupSkin ); + + savefile->WriteVec3( orgOrigin ); + + savefile->WriteBool( pulse ); + savefile->WriteBool( canPickUp ); + + savefile->WriteStaticObject( physicsObj ); + +// savefile->WriteInt(itemShellHandle); // cnicholson: Set at end of Restore, do not save + savefile->WriteMaterial( shellMaterial ); + + savefile->WriteBool( inView ); + savefile->WriteInt( inViewTime ); + savefile->WriteInt( lastCycle ); + savefile->WriteInt( lastRenderViewTime ); +} + +/* +================ +idItem::Restore +================ +*/ +void idItem::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadBool( spin ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( pickupSkin ); + + savefile->ReadVec3( orgOrigin ); + + savefile->ReadBool( pulse ); + savefile->ReadBool( canPickUp ); + + savefile->ReadStaticObject ( physicsObj ); + +// savefile->ReadInt(itemShellHandle); // cnicholson: Set at end of function, do not restore + savefile->ReadMaterial( shellMaterial ); + + savefile->ReadBool( inView ); + savefile->ReadInt( inViewTime ); + savefile->ReadInt( lastCycle ); + savefile->ReadInt( lastRenderViewTime ); + + RestorePhysics( &physicsObj ); + + physicsObj.SetSelf( this ); + + itemShellHandle = -1; +} + +/* +================ +idItem::UpdateRenderEntity +================ +*/ +bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + if( simpleItem ) { + return false; + } + + if ( lastRenderViewTime == renderView->time ) { + return false; + } + + lastRenderViewTime = renderView->time; + + // check for glow highlighting if near the center of the view + idVec3 dir = renderEntity->origin - renderView->vieworg; + dir.Normalize(); + float d = dir * renderView->viewaxis[0]; + + // two second pulse cycle + float cycle = ( renderView->time - inViewTime ) / 2000.0f; + + if ( d > 0.94f ) { + if ( !inView ) { + inView = true; + if ( cycle > lastCycle ) { + // restart at the beginning + inViewTime = renderView->time; + cycle = 0.0f; + } + } + } else { + if ( inView ) { + inView = false; + lastCycle = ceil( cycle ); + } + } + + // fade down after the last pulse finishes + if ( !inView && cycle > lastCycle ) { + renderEntity->shaderParms[4] = 0.0f; + } else { + // pulse up in 1/4 second + cycle -= (int)cycle; + if ( cycle < 0.1f ) { + renderEntity->shaderParms[4] = cycle * 10.0f; + } else if ( cycle < 0.2f ) { + renderEntity->shaderParms[4] = 1.0f; + } else if ( cycle < 0.3f ) { + renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f; + } else { + // stay off between pulses + renderEntity->shaderParms[4] = 0.0f; + } + } + + // update every single time this is in view + return true; +} + +/* +================ +idItem::UpdateTrigger +================ +*/ +void idItem::UpdateTrigger ( void ) { + // update trigger position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END +} + +/* +================ +idItem::ModelCallback +================ +*/ +bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) { + const idItem *ent; + + // this may be triggered by a model trace or other non-view related source + if ( !renderView ) { + return false; + } + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + + +/* +================ +idItem::GetPhysicsToVisualTransform +================ +*/ +bool idItem::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if( simpleItem ) { + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetRenderView() ) { + if( gameLocal.GetLocalPlayer()->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ gameLocal.GetLocalPlayer()->spectator ]; + if( spec && spec->GetRenderView() ) { + axis = spec->GetRenderView()->viewaxis; + } + } else { + axis = gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis; + } + } else { + // dedicated server for instance + axis = mat3_identity; + } + origin = idVec3( 0.0f, 0.0f, 32.0f ); + return true; + } + + if( !spin || (gameLocal.isServer && !gameLocal.isListenServer) ) { + return false; + } + + idAngles ang; + ang.pitch = ang.roll = 0.0f; + ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + axis = ang.ToMat3() * GetPhysics()->GetAxis(); + + float scale = 0.005f; + float offset = entityNumber * 0.685145f; // rjohnson: just a random number here to shift the cos curve + + origin.Zero(); + + origin += GetPhysics()->GetAxis()[2] * (4.0f + idMath::Cos( ( ( gameLocal.time + 1000 ) * scale ) + offset ) * 4.0f); + + return true; +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idItem::Collide +================ +*/ +bool idItem::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_Remove, 0 ); + } + return false; +} +// RAVEN END + +/* +================ +idItem::Think +================ +*/ +void idItem::Think( void ) { + if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + UpdateTrigger(); + } + + if ( gameLocal.IsMultiplayer() && g_skipItemShadowsMP.GetBool() ) { + renderEntity.suppressShadowInViewID = gameLocal.localClientNum + 1; + } else { + renderEntity.suppressShadowInViewID = 0; + } + + if( !(simpleItem && pickedUp) ) { + UpdateVisuals(); + Present(); + } +} + +/* +================ +idItem::Present +================ +*/ +void idItem::Present( void ) { + idEntity::Present(); + + if ( !fl.hidden && pulse ) { + // also add a highlight shell model + renderEntity_t shell; + + shell = renderEntity; + + // we will mess with shader parms when the item is in view + // to give the "item pulse" effect + shell.callback = idItem::ModelCallback; + shell.entityNum = entityNumber; + shell.customShader = shellMaterial; + if ( itemShellHandle == -1 ) { + itemShellHandle = gameRenderWorld->AddEntityDef( &shell ); + } else { + gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell ); + } + } +} + +/* +================ +idItem::InstanceJoin +================ +*/ +void idItem::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::InstanceLeave +================ +*/ +void idItem::InstanceLeave( void ) { + idEntity::InstanceLeave(); + + StopEffect( "fx_idle", true ); +} + +/* +================ +idItem::Spawn +================ +*/ +void idItem::Spawn( void ) { + idStr giveTo; + idEntity * ent; + idVec3 vSize; + idBounds bounds(vec3_origin); + + // check for triggerbounds, which allows for non-square triggers (useful for, say, a CTF flag) + if ( spawnArgs.GetVector( "triggerbounds", "16 16 16", vSize )) { + bounds.AddPoint(idVec3( vSize.x*0.5f, vSize.y*0.5f, 0.0f)); + bounds.AddPoint(idVec3(-vSize.x*0.5f, -vSize.y*0.5f, vSize.z)); + } + else { + // create a square trigger for item pickup + float tsize; + spawnArgs.GetFloat( "triggersize", "16.0", tsize ); + bounds.ExpandSelf( tsize ); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds )); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + + physicsObj.SetSelf ( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID ); + physicsObj.SetFriction( 0.0f, 0.0f, 6.0f ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_off" ) ) { + trigger->SetContents( 0 ); + Hide(); + } else { + trigger->SetContents( CONTENTS_TRIGGER ); + } + + giveTo = spawnArgs.GetString( "owner" ); + if ( giveTo.Length() ) { + ent = gameLocal.FindEntity( giveTo ); + if ( !ent ) { + gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() ); + } + PostEventMS( &EV_Touch, 0, ent, NULL ); + } + + if ( spawnArgs.GetBool( "spin" ) || gameLocal.isMultiplayer ) { + spin = true; + BecomeActive( TH_THINK ); + } + + // pulse ( and therefore itemShellHandle ) was taken out and shot. do not sync + //pulse = !spawnArgs.GetBool( "nopulse" ); + pulse = false; + orgOrigin = GetPhysics()->GetOrigin(); + + canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) ); + + inViewTime = -1000; + lastCycle = -1; + itemShellHandle = -1; +// RAVEN BEGIN +// abahr: move texture to def file for precaching + shellMaterial = declManager->FindMaterial( spawnArgs.GetString("mtr_highlight", "_default") ); + PostEventMS( &EV_SetGravity, 0 ); +// RAVEN END + if ( spawnArgs.GetString( "skin", NULL ) ) { + skin = declManager->FindSkin( spawnArgs.GetString( "skin" ), false ); + if( skin ) { + SetSkin( skin ); + srvReady = 1; + } + } else { + skin = NULL; + } + + if ( spawnArgs.GetString( "skin_pickup", NULL ) ) { + pickupSkin = declManager->FindSkin( spawnArgs.GetString( "skin_pickup" ), false ); + } else { + pickupSkin = NULL; + } + + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( srvReady == -1 ) { + srvReady = IsHidden() ? 0 : 1; + } + +// RAVEN BEGIN +// mekberg: added for removing pickups in mp in pits + if ( gameLocal.isMultiplayer ) { + trigger->SetContents( trigger->GetContents() | CONTENTS_ITEMCLIP ); + } +// RAVEN END + + if( gameLocal.isMultiplayer ) { + itemPVSArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + } else { + itemPVSArea = 0; + } + + simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !IsType( rvItemCTFFlag::GetClassType() ); + if( simpleItem ) { + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 32.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 32.0f; + renderEntity.hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + renderEntity.customSkin = 0; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customShader = declManager->FindMaterial( spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity.referenceShader = 0; + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + SetAxis( mat3_identity ); + } else { + if ( spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + pickedUp = false; +} + +/* +================ +idItem::Event_SetGravity +================ +*/ +void idItem::Event_SetGravity() { + // If the item isnt a dropped item then see if it should settle itself + // to the floor or not + if ( !spawnArgs.GetBool( "dropped" ) ) { + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + PostEventMS( &EV_DropToFloor, 0 ); + } + } +} +// RAVEN END + +/* +================ +idItem::GetAttributes +================ +*/ +void idItem::GetAttributes( idDict &attributes ) { + int i; + const idKeyValue *arg; + + for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Left( 4 ) == "inv_" ) { + attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() ); + } + } +} + +/* +================ +idItem::GiveToPlayer +================ +*/ +bool idItem::GiveToPlayer( idPlayer *player ) { + if ( player == NULL ) { + return false; + } + + if ( spawnArgs.GetBool( "inv_carry" ) ) { + return player->GiveInventoryItem( &spawnArgs ); + } + + // Handle the special ammo pickup that gives ammo for the weapon the player currently has + if ( spawnArgs.GetBool( "item_currentWeaponAmmo" ) ) { + const char *ammoName = player->weapon->GetAmmoNameForIndex(player->weapon->GetAmmoType()); + if ( player->weapon->TotalAmmoCount() != player->weapon->maxAmmo && player->weapon->AmmoRequired() ) { + player->GiveItem(ammoName); + player->GiveInventoryItem( &spawnArgs ); + return true; + } + return false; + } + + return player->GiveItem( this ); +} + +/* +=============== +idItem::SendPickupMsg +=============== +*/ +void idItem::SendPickupMsg( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_PICKUP ); + msg.WriteByte( clientNum ); + + // send as unreliable to client picking up the item, so it can play HUD things + gameLocal.SendUnreliableMessagePVS( msg, this, itemPVSArea ); +} + +/* +================ +idItem::Pickup +================ +*/ +bool idItem::Pickup( idPlayer *player ) { + //dropped weapon? + bool dropped = spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && !dropped && spawnArgs.FindKey( "weaponclass" ) + && gameLocal.IsWeaponsStayOn() && gameLocal.time > player->lastPickupTime + 1000 ) { + + idDict attr; + GetAttributes( attr ); + const idKeyValue* arg = attr.FindKey( "weapon" ); + + if ( arg ) { + if ( !player->inventory.Give( player, player->spawnArgs, arg->GetKey(), arg->GetValue(), NULL, false, dropped, true ) ) { + StartSound( "snd_noacquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + } + + // only predict noacquire on client + if ( gameLocal.isClient ) { + return false; + } + + int givenToPlayer = spawnArgs.GetInt( "givenToPlayer", "-1" ); + if ( player == NULL || ( givenToPlayer != -1 && givenToPlayer != player->entityNumber ) ) { + // idPlayer::GiveItem spawns an idItem for pickup, which appears at the origin before being picked + // and could sometimes be picked by someone else, particularly in buy mode when there is a play spawn sitting on the origin ;-) + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + player->lastPickupTime = gameLocal.time; + + //if a placed item and si_weaponStay is on and we're a weapon, don't remove and respawn + if ( gameLocal.IsMultiplayer() ) { + if ( !dropped ) { + if ( spawnArgs.FindKey( "weaponclass" ) ) { + if ( gameLocal.IsWeaponsStayOn() ) { + return true; + } + } + } + } + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model, or switch to the pickup skin + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + BecomeInactive( TH_THINK ); + } + + pickedUp = true; + + // allow SetSkin or Hide() to get called regardless of simpleitem mode + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + // asalmon: Added option for a differnt respawn rate based on gametype. + float respawn = spawnArgs.GetFloat(va("respawn_%s",gameLocal.serverInfo.GetString( "si_gameType" )), "-1.0"); + if( respawn == -1.0f ) { + respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + } + + bool no_respawn = spawnArgs.GetBool( "no_respawn" ); + + if ( !gameLocal.isMultiplayer ) { + respawn = 0.0f; + } else if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( givenToPlayer != -1 ) { + respawn = 0.0f; + } + } + + if ( respawn && !dropped && !no_respawn ) { + const char *sfx = spawnArgs.GetString( "fx_Respawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, respawn - 0.5f ); + } + PostEventSec( &EV_RespawnItem, respawn ); + } else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) { + // give some time for the pickup sound to play + // FIXME: Play on the owner + if ( !spawnArgs.GetBool( "inv_carry" ) ) { + PostEventMS( &EV_Remove, 5000 ); + } + } + + trigger->SetContents( 0 ); + + StopEffect( "fx_idle" ); + + return true; +} + +/* +================ +idItem::Hide +================ +*/ +void idItem::Hide( void ) { + srvReady = 0; + idEntity::Hide( ); + trigger->SetContents( 0 ); +} + +/* +================ +idItem::Show +================ +*/ +void idItem::Show( void ) { + srvReady = 1; + idEntity::Show( ); + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +idItem::ClientStale +================ +*/ +bool idItem::ClientStale( void ) { + idEntity::ClientStale(); + + StopEffect( "fx_idle" ); + return false; +} + +/* +================ +idItem::ClientUnstale +================ +*/ +void idItem::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::ClientPredictionThink +================ +*/ +void idItem::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + +/* +================ +idItem::WriteFromSnapshot +================ +*/ +void idItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + assert( srvReady != -1 ); + msg.WriteBits( ( srvReady == 1 ), 1 ); +} + +/* +================ +idItem::ReadFromSnapshot +================ +*/ +void idItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + int newReady = ( msg.ReadBits( 1 ) != 0 ); + idVec3 resetOrigin( 0, 0, 0 ); + // client spawns the ent with ready == -1 so the state set happens at least once + if ( newReady != clReady ) { + if ( newReady ) { + // g_simpleItems might force a hide even with a pickup skin + if ( pickupSkin ) { + SetSkin( skin ); + } else { + SetSkin( skin ); + Show(); + } + + if( simpleItem ) { + UpdateVisuals(); + } + pickedUp = false; + + if ( effectIdle.GetEntity( ) ) { + UpdateModelTransform(); + effectIdle->SetOrigin( resetOrigin ); + } else if ( spawnArgs.GetString( "fx_idle" ) && !simpleItem ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } else { + if ( pickupSkin ) { + SetSkin( pickupSkin ); + } else { + Hide(); + } + + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + pickedUp = true; + + StopEffect( "fx_idle" ); + effectIdle = NULL; + } + } + clReady = newReady; +} + +/* +================ +idItem::Event_Pickup +================ +*/ +void idItem::Event_Pickup( int clientNum ) { + idPlayer *player; + + assert( gameLocal.isClient ); + + // play pickup sound + if ( !spawnArgs.GetBool( "globalAcquireSound" ) ) { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + if( clientNum == gameLocal.localClientNum ) { + player = (idPlayer*)gameLocal.entities[ clientNum ]; + player->lastPickupTime = gameLocal.time; + if ( player ) { + player->GiveItem( this ); + } + } +} + +/* +================ +idItem::ClientReceiveEvent +================ +*/ +bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_PICKUP: { + int clientNum = msg.ReadByte(); + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + Event_Pickup( clientNum ); + } + return true; + } + case EVENT_RESPAWNFX: { + Event_RespawnFx(); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idItem::Event_DropToFloor +================ +*/ +void idItem::Event_DropToFloor( void ) { + // don't drop the floor if bound to another entity + if ( GetBindMaster() != NULL && GetBindMaster() != this ) { + return; + } + + physicsObj.DropToFloor( ); +} + +/* +================ +idItem::Event_Touch +================ +*/ +void idItem::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( !canPickUp ) { + return; + } + + Pickup( static_cast(other) ); +} + +/* +================ +idItem::Event_Trigger +================ +*/ +void idItem::Event_Trigger( idEntity *activator ) { + if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) { + canPickUp = true; + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Pickup( static_cast( activator ) ); + } +} + +/* +================ +idItem::Event_Respawn +================ +*/ +void idItem::Event_Respawn( void ) { + + // with simple items, re-show the item, but still set srvReady to true to let clients + // know how to deal + if ( pickupSkin ) { + srvReady = true; + if( !simpleItem ) { + SetSkin( skin ); + } + } + + if( !pickupSkin ) { + BecomeActive( TH_THINK ); + Show(); + } + + if( simpleItem ) { + Show(); + } + + pickedUp = false; + + inViewTime = -1000; + lastCycle = -1; + trigger->SetContents ( CONTENTS_TRIGGER ); + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL ); + PostEventMS( &EV_SetGravity, 0 ); + CancelEvents( &EV_RespawnItem ); // don't double respawn + + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::Event_RespawnFx +================ +*/ +void idItem::Event_RespawnFx( void ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_RESPAWNFX ); + // send unreliable PVS-sensitive + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ) ); + } + + if( !simpleItem ) { + gameLocal.PlayEffect( spawnArgs, "fx_respawn", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } +} + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +/* +=============== +idItemPowerup +=============== +*/ + +CLASS_DECLARATION( idItem, idItemPowerup ) +END_CLASS + +/* +================ +idItemPowerup::idItemPowerup +================ +*/ +idItemPowerup::idItemPowerup() { + time = 0; + type = 0; + droppedTime = 0; + unique = false; +} + +/* +================ +idItemPowerup::Save +================ +*/ +void idItemPowerup::Save( idSaveGame *savefile ) const { + savefile->WriteInt( time ); + savefile->WriteInt( type ); + savefile->WriteInt( droppedTime ); // cnicholson: Added unsaved var +} + +/* +================ +idItemPowerup::Restore +================ +*/ +void idItemPowerup::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( type ); + savefile->ReadInt( droppedTime ); // cnicholson: Added unrestored var +} + +/* +================ +idItemPowerup::Spawn +================ +*/ +void idItemPowerup::Spawn( void ) { + time = SEC2MS( spawnArgs.GetInt( "time", "30" ) ); + // SEC2MS screws up when we want -1 time (no expiration) + if( spawnArgs.GetInt( "time" ) == -1 ) { + time = -1; + } + + type = spawnArgs.GetInt( "type", "0" ); + + // If the powerup was dropped then make it dissapear using its remaining time. + if ( spawnArgs.GetBool( "dropped" ) && time != -1 ) { + droppedTime = gameLocal.time + time; + PostEventMS( &EV_Remove, time ); + } + + // unique powerpus won't respawn while a player has them + unique = spawnArgs.GetBool( "unique", "0" ); + if( unique ) { + spawnArgs.SetBool( "no_respawn", true ); + } + + if ( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else { + team = -1; + } +} + +/* +================ +idItemPowerup::GiveToPlayer +================ +*/ +bool idItemPowerup::GiveToPlayer( idPlayer *player ) { + if ( player == NULL || player->spectating ) { + return false; + } + + // only one arena CTF powerup at a time + if ( type >= POWERUP_AMMOREGEN && type <= POWERUP_SCOUT ) { + if ( ( player->inventory.powerups & ARENA_POWERUP_MASK ) != 0 ) { + return false; + } + } + + // in flavours of arena CTF (or are idItemPowerups only used in Arena? or even, are idItemPowerups MP only?), + // ensure that items with a team can only be picked up by members of that team + if ( gameLocal.IsMultiplayer() && gameLocal.IsTeamGame() && team >= 0 ) { + if( team != player->team ) { + return false; + } + } + + if ( droppedTime > 0 ) { + player->GivePowerUp( type, droppedTime - gameLocal.time ); + } else { + player->GivePowerUp( type, time ); + } + + // also call idItem::GiveToPlayer so any inv_* keywords get applied + idItem::GiveToPlayer( player ); + + return true; +} + +/* +================ +idItemPowerup::Think +================ +*/ +void idItemPowerup::Think( void ) { + int i; + + // idItem::Think() only needs to be called if we're spawned in + if( !IsHidden() || (pickupSkin && GetSkin() != pickupSkin ) ) { + // only get here if spawned in + idItem::Think(); + return; + } + + if( !unique ) { + // non-unique despawned powerups don't need to think + return; + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL ) { + continue; + } + + // only spawn back in if noone on your team has the powerup + if( p->PowerUpActive( type ) && p->team == team ) { + break; + } + } + + if( i >= gameLocal.numClients ) { + PostEventMS( &EV_RespawnItem, 0 ); + } +} + +/* +================ +idItemPowerup::Pickup +================ +*/ +bool idItemPowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + + if( gameLocal.isClient ) { + // no client-side powerup prediction + return false; + } + + pickup = idItem::Pickup( player ); + if( unique ) { + BecomeActive( TH_THINK ); + } + + return pickup; +} + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idObjective ) + EVENT( EV_Activate, idObjective::Event_Trigger ) + EVENT( EV_HideObjective, idObjective::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjective::Event_GetPlayerPos ) + EVENT( EV_CamShot, idObjective::Event_CamShot ) +END_CLASS + +/* +================ +idObjective::idObjective +================ +*/ +idObjective::idObjective() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjective::Save +================ +*/ +void idObjective::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjective::Restore +================ +*/ +void idObjective::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +// RAVEN BEGIN +// jnewquist: Don't do this on Xenon, we want prebuilt textures +#ifndef _XENON + PostEventMS( &EV_CamShot, 250 ); +#endif +// RAVEN END +} + +/* +================ +idObjective::Spawn +================ +*/ +void idObjective::Spawn( void ) { + Hide(); +// RAVEN BEGIN +// jnewquist: Only post a camshot event if the spawn args request it +#ifndef _XENON + const char *camName; + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + common->Warning( "SpawnArg camShot on %s is not recommended.", spawnArgs.GetString( "name" ) ); + PostEventMS( &EV_CamShot, 250 ); + } +#endif +// RAVEN END +} + +/* +================ +idObjective::Event_Screenshot +================ +*/ +void idObjective::Event_CamShot( ) { + const char *camName; +// RAVEN BEGIN +// bdube: changed screenshot location + idStr shotName = "gfx/objectives/"; + shotName += spawnArgs.GetString( "screenshot" ); + shotName.SetFileExtension( ".tga" ); + // RAVEN END + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + idEntity *ent = gameLocal.FindEntity( camName ); + if ( ent && ent->cameraTarget ) { + const renderView_t *view = ent->cameraTarget->GetRenderView(); + renderView_t fullView = *view; + fullView.width = SCREEN_WIDTH; + fullView.height = SCREEN_HEIGHT; + + // draw a view to a texture + renderSystem->CropRenderSize( 256, 256, true ); + gameRenderWorld->RenderScene( &fullView ); + // TTimo: I don't think this jives with SMP code, but I grepped through the final maps and didn't see any using it + renderSystem->CaptureRenderToFile( shotName ); + renderSystem->UnCrop(); + } + } +} + +/* +================ +idObjective::Event_Trigger +================ +*/ +void idObjective::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so when in a vehicle we update the vehicle hud +// twhitaker: changed player->hud to player->GetObjectiveHud(), to resolve all issues + if ( player && player->GetObjectiveHud() ) { +// bdube: changed screenshot location + idStr shotName = spawnArgs.GetString( "screenshot" ); + + player->GetObjectiveHud()->SetStateString( "screenshot", shotName ); + player->GetObjectiveHud()->SetStateString( "objective", "1" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), shotName ); + + // a tad slow but keeps from having to update all objectives in all maps with a name ptr + for( int i = 0; i < gameLocal.num_entities; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::GetClassType() ) ) { +// RAVEN END + if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){ + gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true ); + break; + } + } + } + + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjective::Event_GetPlayerPos +================ +*/ +void idObjective::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjective::Event_HideObjective +================ +*/ +void idObjective::Event_HideObjective(idEntity *e) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idMoveableItem ) + EVENT( EV_Gib, idMoveableItem::Event_Gib ) +END_CLASS + +/* +================ +idMoveableItem::idMoveableItem +================ +*/ +idMoveableItem::idMoveableItem() { +} + +/* +================ +idMoveableItem::~idMoveableItem +================ +*/ +idMoveableItem::~idMoveableItem() { + // If this entity has been allocated, but not spawned, the physics object will + // not have a self pointer, and will not unregister itself from the entity. + SetPhysics( NULL ); +} + +/* +================ +idMoveableItem::Save +================ +*/ +void idMoveableItem::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idMoveableItem::Restore +================ +*/ +void idMoveableItem::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); +} + +/* +================ +idMoveableItem::Spawn +================ +*/ +void idMoveableItem::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + idStr clipModelName; + idBounds bounds; + + // if the model should be shrinked + if ( spawnArgs.GetBool( "clipshrink" ) ) { + trm.Shrink( CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + // setup the physics + physicsObj.SetSelf( this ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // check if a clip model is set + spawnArgs.GetString( "itemclipmodel", "", clipModelName ); + if ( clipModelName[0] ) { + if ( collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + physicsObj.SetClipModel( new idClipModel( trm ), density ); + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + +// RAVEN BEGIN +// mekberg: added + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } +// RAVEN END +} + +/* +================ +idMoveableItem::Think +================ +*/ +void idMoveableItem::Think( void ) { + RunPhysics(); + UpdateTrigger( ); + Present(); +} + +/* +================ +idMoveableItem::DropItem +================ +*/ +idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) { + idDict args; + idEntity *item; + + args.Set( "classname", classname ); + args.Set( "dropped", "1" ); + + // we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor + args.Set( "nodrop", "1" ); + + if ( activateDelay ) { + args.SetBool( "triggerFirst", true ); + } + + gameLocal.SpawnEntityDef( args, &item ); + if ( item ) { + // set item position + item->GetPhysics()->SetOrigin( origin ); + item->GetPhysics()->SetAxis( axis ); + item->GetPhysics()->SetLinearVelocity( velocity ); + item->UpdateVisuals(); + if ( activateDelay ) { + item->PostEventMS( &EV_Activate, activateDelay, item ); + } + if ( !removeDelay ) { + removeDelay = 5 * 60 * 1000; + } + // always remove a dropped item after 5 minutes in case it dropped to an unreachable location + item->PostEventMS( &EV_Remove, removeDelay ); + } + + return item; +} + +/* +================ +idMoveableItem::DropItems + + The entity should have the following key/value pairs set: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + "skin_drop" "skin name" + To drop multiple items the following key/value pairs can be used: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + where is an aribtrary string. +================ +*/ +void idMoveableItem::DropItems( idAnimatedEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName, *c, *jointName; + idStr key, key2; + idVec3 origin; + idMat3 axis; + idAngles angles; + const idDeclSkin *skin; + jointHandle_t joint; + idEntity *item; + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL ); + while ( kv ) { + + c = kv->GetKey().c_str() + kv->GetKey().Length(); + if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) { + + key = kv->GetKey().c_str() + 4; + key2 = key; + key += "Joint"; + key2 += "Offset"; + jointName = ent->spawnArgs.GetString( key ); + joint = ent->GetAnimator()->GetJointHandle( jointName ); + if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + if ( g_dropItemRotation.GetString()[0] ) { + angles.Zero(); + sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll ); + } else { + key = kv->GetKey().c_str() + 4; + key += "Rotation"; + ent->spawnArgs.GetAngles( key, "0 0 0", angles ); + } + axis *= angles.ToMat3() * axis; + + origin += ent->spawnArgs.GetVector( key2, "0 0 0" ); + + item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 ); + if ( list && item ) { + list->Append( item ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv ); + } + + // change the skin to hide all items + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +====================== +idMoveableItem::WriteToSnapshot +====================== +*/ +void idMoveableItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +====================== +idMoveableItem::ReadFromSnapshot +====================== +*/ +void idMoveableItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +============ +idMoveableItem::Gib +============ +*/ +void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) { + // remove the entity + PostEventMS( &EV_Remove, 0 ); +} + +/* +============ +idMoveableItem::Event_Gib +============ +*/ +void idMoveableItem::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idItemRemover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idItemRemover ) + EVENT( EV_Activate, idItemRemover::Event_Trigger ) +END_CLASS + +/* +================ +idItemRemover::Spawn +================ +*/ +void idItemRemover::Spawn( void ) { +} + +/* +================ +idItemRemover::RemoveItem +================ +*/ +void idItemRemover::RemoveItem( idPlayer *player ) { + const char *remove; + + remove = spawnArgs.GetString( "remove" ); + player->RemoveInventoryItem( remove ); +} + +/* +================ +idItemRemover::Event_Trigger +================ +*/ +void idItemRemover::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + RemoveItem( static_cast(activator) ); + } +} + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +CLASS_DECLARATION( idItemRemover, idObjectiveComplete ) + EVENT( EV_Activate, idObjectiveComplete::Event_Trigger ) + EVENT( EV_HideObjective, idObjectiveComplete::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjectiveComplete::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjectiveComplete::idObjectiveComplete +================ +*/ +idObjectiveComplete::idObjectiveComplete() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjectiveComplete::Save +================ +*/ +void idObjectiveComplete::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Restore +================ +*/ +void idObjectiveComplete::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Spawn +================ +*/ +void idObjectiveComplete::Spawn( void ) { + spawnArgs.SetBool( "objEnabled", false ); + Hide(); +} + +/* +================ +idObjectiveComplete::Event_Trigger +================ +*/ +void idObjectiveComplete::Event_Trigger( idEntity *activator ) { + if ( !spawnArgs.GetBool( "objEnabled" ) ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + RemoveItem( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so if in a vehicle we update that hud +// twhitaker: moved objective system to it's own hud, to solve vehicle hud issues. + if ( player->GetObjectiveHud() ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) ); + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjectiveComplete::Event_GetPlayerPos +================ +*/ +void idObjectiveComplete::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjectiveComplete::Event_HideObjective +================ +*/ +void idObjectiveComplete::Event_HideObjective( idEntity *e ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->GetObjectiveHud() ) { + idVec3 v = player->GetPhysics()->GetOrigin(); + v -= playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ +CLASS_DECLARATION( idItemRemover, rvObjectiveFailed ) + EVENT( EV_Activate, rvObjectiveFailed::Event_Trigger ) +END_CLASS + +rvObjectiveFailed::rvObjectiveFailed ( void ) { +} + +/* +================ +rvObjectiveFailed::Event_Trigger +================ +*/ +void rvObjectiveFailed::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->GetObjectiveHud() ) { + return; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + return; + } + + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); + player->FailObjective( spawnArgs.GetString( "objectivetitle" ) ); +} + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +const idEventDef EV_ResetFlag ( "" ); +const idEventDef EV_LinkTrigger( "" ); +CLASS_DECLARATION( idItem, rvItemCTFFlag ) + EVENT( EV_ResetFlag, rvItemCTFFlag::Event_ResetFlag ) + EVENT( EV_LinkTrigger, rvItemCTFFlag::Event_LinkTrigger ) +END_CLASS + +/* +================ +rvItemCTFFlag::rvItemCTFFlag +================ +*/ +rvItemCTFFlag::rvItemCTFFlag() { +} + + +/* +================ +rvItemCTFFlag::Spawn +================ +*/ +void rvItemCTFFlag::Spawn () { +/* rjohnson: I fixed the crash so we don't need to remove them... + //don't spawn outside of CTF games + if( (gameLocal.gameType != GAME_CTF) && (gameLocal.gameType != GAME_1F_CTF) && + (gameLocal.gameType != GAME_ARENA_CTF) && (gameLocal.gameType != GAME_ARENA_1F_CTF) ){ + //don't spawn! + gameLocal.Error("Flag spawn on in non-CTF gametype."); + PostEventMS ( &EV_Remove, 0 ); + return; + } +*/ + spawnArgs.GetBool ( "dropped", "0", dropped ); + spawnArgs.GetInt ( "team", "0", team ); + + bool reset = false; + spawnArgs.GetBool ( "reset", "0", reset ); + + switch ( team ) { + case TEAM_MARINE: { + powerup = POWERUP_CTF_MARINEFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_MARINE ); + break; + } + case TEAM_STROGG: { + powerup = POWERUP_CTF_STROGGFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_STROGG ); + break; + } + case TEAM_MAX: { + powerup = POWERUP_CTF_ONEFLAG; + break; + } + default: + gameLocal.Warning ( "Unknown ctf flag team '%d' on entity '%s'", team, name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + if ( dropped ) { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_DROPPED ); + } + if ( reset ) { + //PostEventSec( &EV_ResetFlag, 1 ); + } else { + PostEventSec( &EV_ResetFlag, 30 ); + } + + // Let powerups settle for a frame before allowing them to be picked up - we need to + // make sure we hit the dropped state for VO, etc to work properly + trigger->SetContents( 0 ); + PostEventSec( &EV_LinkTrigger, 0 ); + } else { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + + spin = false; +} + +/* +================ +rvItemCTFFlag::Event_LinkTrigger +================ +*/ +void rvItemCTFFlag::Event_LinkTrigger( void ) { + trigger->SetContents( CONTENTS_TRIGGER | CONTENTS_ITEMCLIP ); +} + +/* +================ +rvItemCTFFlag::GiveToPlayer +================ +*/ +bool rvItemCTFFlag::GiveToPlayer( idPlayer* player ) { + if ( !gameLocal.IsMultiplayer() ) { + return false; + } + + if ( player->spectating || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && (gameLocal.mpGame.GetGameState())->GetMPGameState() != SUDDENDEATH && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return false; + } + + int teamPowerup; + int enemyPowerup; + + bool canPickup = ( team == player->team ); + + switch ( player->team ) { + default: + case TEAM_MARINE: + teamPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + break; + + case TEAM_STROGG: + teamPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + break; + } + + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + // in one flag CTF, we score touching the enemy's flag + canPickup = ( team == gameLocal.mpGame.OpposingTeam( player->team ) ); + enemyPowerup = POWERUP_CTF_ONEFLAG; + } + + // If the player runs over their own flag the only thing they + // can do is score or return it. + + // when the player touches the one flag, he always gets it + if ( canPickup ) { + // If the flag was dropped, return it + if ( dropped ) { + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + statManager->FlagReturned( player ); + ResetFlag ( teamPowerup ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagReturned", 0 ) ); +// RITUAL END + } else if ( player->PowerUpActive ( enemyPowerup ) ) { + // If they have the enemy flag then they score + if ( !gameLocal.mpGame.CanCapture ( player->team ) ) { + return false; + } + + ResetFlag( enemyPowerup ); + + gameLocal.mpGame.FlagCaptured( player ); + } + return false; + } + + // only pickup one flag in arena CTF + if( ( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) && team != TEAM_MAX ) { + return false; + } + + player->GivePowerUp( enemyPowerup, -1 ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagStolen", 0 ) ); +// RITUAL END + return true; +} + +/* +================ +rvItemCTFFlag::Pickup +================ +*/ +bool rvItemCTFFlag::Pickup( idPlayer *player ) { + if( gameLocal.isClient ) { + // no client-side CTF flag prediction + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + // ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + gameLocal.mpGame.SetFlagEntity( NULL, team ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 1 ); + + if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_ARENA_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_TAKEN ); + } else if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, (player->team == TEAM_MARINE ? FS_TAKEN_MARINE : FS_TAKEN_STROGG) ); + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagCarrier( team, player->entityNumber ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + BecomeInactive( TH_THINK ); + + return true; +} + +/* +================ +rvItemCTFFlag::ResetFlag +================ +*/ +void rvItemCTFFlag::ResetFlag( int powerup ) { + idEntity* ent; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // Make sure no players have the flag anymore +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType ( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->ClearPowerup ( powerup ); + continue; + } + + // If its not a CTF flag item then skip it + if ( !ent->IsType( rvItemCTFFlag::Type ) ) { + continue; + } + + // Make sure its the right type first + rvItemCTFFlag* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->dropped ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + flag->PostEventMS( &EV_RespawnItem, 0 ); + gameLocal.mpGame.SetFlagEntity( flag, flag->team ); + } + } + + if ( !gameLocal.isClient ) { + int team = -1; + if ( powerup == POWERUP_CTF_MARINEFLAG ) { + team = TEAM_MARINE; + } else if ( powerup == POWERUP_CTF_STROGGFLAG ) { + team = TEAM_STROGG; + } else if ( powerup == POWERUP_CTF_ONEFLAG ) { + team = TEAM_MAX; + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + +} + +/* +================ +rvItemCTFFlag::Event_ResetFlag +================ +*/ +void rvItemCTFFlag::Event_ResetFlag( void ) { + ResetFlag( powerup ); +} + +void rvItemCTFFlag::Think( void ) { + idItem::Think(); +} + +/* +================ +rvItemCTFFlag::Collide +================ +*/ +bool rvItemCTFFlag::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + + if ( collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + ResetFlag( powerup ); + } + return false; +} +// RAVEN END + + + + +const idEventDef EV_ResetSpawn ( "" ); +CLASS_DECLARATION( idItemPowerup, riDeadZonePowerup ) + EVENT( EV_ResetSpawn, riDeadZonePowerup::Event_ResetSpawn ) +END_CLASS + +/* +================ +riDeadZonePowerup::idItemPowerup +================ +*/ +riDeadZonePowerup::riDeadZonePowerup() { +} + +/* +================ +riDeadZonePowerup::Save +================ +*/ +void riDeadZonePowerup::Save( idSaveGame *savefile ) const { +} + +/* +================ +riDeadZonePowerup::Restore +================ +*/ +void riDeadZonePowerup::Restore( idRestoreGame *savefile ) { +} + + +/* +================ +riDeadZonePowerup::Show +================ +*/ +void riDeadZonePowerup::Show() +{ + idItem::Show(); +} + + +/* +================ +riDeadZonePowerup::Spawn +================ +*/ +void riDeadZonePowerup::Spawn( void ) { + powerup = POWERUP_DEADZONE; + + time = SEC2MS( gameLocal.serverInfo.GetInt("si_deadZonePowerupTime") ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + time = SEC2MS( spawnArgs.GetInt( "time", "10" ) ); + if ( time > SEC2MS(10) ) + time = SEC2MS(10); + + Show(); + CancelEvents(&EV_Remove); + PostEventSec( &EV_ResetSpawn, MS2SEC(time) ); + } + else + Hide(); +} + + +/* +================ +riDeadZonePowerup::Pickup +================ +*/ +bool riDeadZonePowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + if ( player->PowerUpActive(POWERUP_DEADZONE) ) + return false; + + pickup = idItemPowerup::Pickup( player ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + // Cancel the respawn so the powerup doesn't get removed + // from the player that just picked it up. + PostEventMS( &EV_Remove, 0 ); + CancelEvents( &EV_ResetSpawn ); + } + + return pickup; +} + +/* +================ +riDeadZonePowerup::ResetSpawn +================ +*/ +void riDeadZonePowerup::ResetSpawn( int powerup ) { + int count = 1; + idEntity* ent; + riDeadZonePowerup* spawnSpot = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::Type ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && flag == this ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + if ( !flag->IsVisible() ) { + if ( !(rand()%count) ) { + spawnSpot = flag; + if ( flag->spawnArgs.GetBool("dropped", "0") ) + gameLocal.DPrintf("WARNING: Trying to spawn a powerup at a DROPPED location!"); + } + count++; + } + } + } + + if ( spawnSpot ) { + spawnSpot->Show(); + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + } + else { + gameLocal.DPrintf("WARNING: Failed to find a valid spawn spot!"); + } +} + +/* +================ +riDeadZonePowerup::Collide +================ +*/ +bool riDeadZonePowerup::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_ResetSpawn, 0 ); // Just respawn it. + } + return false; +} + +/* +================ +riDeadZonePowerup::Event_ResetFlag +================ +*/ +void riDeadZonePowerup::Event_ResetSpawn( void ) { + ResetSpawn( POWERUP_DEADZONE ); +} diff --git a/source/game/Item.h b/source/game/Item.h new file mode 100644 index 0000000..e462263 --- /dev/null +++ b/source/game/Item.h @@ -0,0 +1,362 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ITEM_H__ +#define __GAME_ITEM_H__ + +extern const int ARENA_POWERUP_MASK; +extern const idEventDef EV_ResetFlag; +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_SetGravity; + +/* +=============================================================================== + + Items the player can pick up or use. + +=============================================================================== +*/ + +class idItem : public idEntity { +public: + CLASS_PROTOTYPE( idItem ); + + idItem(); + virtual ~idItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void GetAttributes( idDict &attributes ); + virtual bool GiveToPlayer( idPlayer *player ); + virtual bool Pickup( idPlayer *player ); + virtual void Think( void ); + virtual void Present(); + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// mekberg: added + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); +// RAVEN END + + enum { + EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, + EVENT_RESPAWNFX, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + int IsVisible() { return srvReady; } + + rvClientEntityPtr effectIdle; + bool simpleItem; + bool pickedUp; + const idDeclSkin* pickupSkin; + void Event_DropToFloor ( void ); +protected: + + void UpdateTrigger( void ); + void SendPickupMsg( int clientNum ); + + idClipModel * trigger; + bool spin; + // only a small subset of idItem need their physics object synced + bool syncPhysics; + + bool pulse; + bool canPickUp; + const idDeclSkin* skin; + +private: + idVec3 orgOrigin; + + rvPhysics_Particle physicsObj; + + // for item pulse effect + int itemShellHandle; + const idMaterial * shellMaterial; + + // used to update the item pulse effect + mutable bool inView; + mutable int inViewTime; + mutable int lastCycle; + mutable int lastRenderViewTime; + + // synced through snapshots to indicate show/hide or pickupSkin state + // -1 on a client means undef, 0 not ready, 1 ready +public: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int srvReady; +private: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int clReady; + + int itemPVSArea; + + bool UpdateRenderEntity ( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback ( renderEntity_s *renderEntity, const renderView_t *renderView ); + + + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_Trigger ( idEntity *activator ); + void Event_Respawn ( void ); + void Event_RespawnFx ( void ); + void Event_Pickup ( int clientNum ); + +// RAVEN BEGIN +// abahr + void Event_SetGravity(); +// RAVEN END +}; + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +class idItemPowerup : public idItem { +public: + CLASS_PROTOTYPE( idItemPowerup ); + + idItemPowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool GiveToPlayer( idPlayer *player ); + virtual void Think( void ); + virtual bool Pickup( idPlayer *player ); + +protected: + + int time; + int type; + int droppedTime; + int team; + bool unique; +}; + +/* +=============================================================================== + + riDeadZonePowerup + +=============================================================================== +*/ + +class riDeadZonePowerup : public idItemPowerup { +public: + CLASS_PROTOTYPE( riDeadZonePowerup ); + + riDeadZonePowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Pickup( idPlayer *player ); + + void Spawn(); + void Show(); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + int powerup; + +protected: + void ResetSpawn( int powerup ); + +private: + void Event_ResetSpawn( void ); +}; + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +class rvItemCTFFlag : public idItem { +public: + CLASS_PROTOTYPE( rvItemCTFFlag ); + + rvItemCTFFlag(); + + void Spawn(); + virtual bool GiveToPlayer ( idPlayer* player ); + virtual bool Pickup( idPlayer *player ); + + static void ResetFlag( int type ); + virtual void Think( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + +private: + int team; + int powerup; + bool dropped; + + void Event_ResetFlag( void ); + void Event_LinkTrigger( void ); +}; + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +class idObjective : public idItem { +public: + CLASS_PROTOTYPE( idObjective ); + + idObjective(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); + void Event_CamShot(); +}; + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +class idMoveableItem : public idItem { +public: + CLASS_PROTOTYPE( idMoveableItem ); + + idMoveableItem(); + virtual ~idMoveableItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual void Think( void ); + + static void DropItems( idAnimatedEntity *ent, const char *type, idList *list ); + static idEntity* DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + idPhysics_RigidBody physicsObj; + + void Gib( const idVec3 &dir, const char *damageDefName ); + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + Item removers. + +=============================================================================== +*/ + +class idItemRemover : public idEntity { +public: + CLASS_PROTOTYPE( idItemRemover ); + + void Spawn(); + void RemoveItem( idPlayer *player ); + +private: + void Event_Trigger( idEntity *activator ); +}; + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +class idObjectiveComplete : public idItemRemover { +public: + CLASS_PROTOTYPE( idObjectiveComplete ); + + idObjectiveComplete(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ + +class rvObjectiveFailed : public idItemRemover { +public: + CLASS_PROTOTYPE( rvObjectiveFailed ); + + rvObjectiveFailed (); +private: + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_ITEM_H__ */ + + +// RAVEN END diff --git a/source/game/Light.cpp b/source/game/Light.cpp new file mode 100644 index 0000000..eed389b --- /dev/null +++ b/source/game/Light.cpp @@ -0,0 +1,1486 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idLight + +=============================================================================== +*/ + +const idEventDef EV_Light_SetShader( "setShader", "s" ); +const idEventDef EV_Light_GetLightParm( "getLightParm", "d", 'f' ); +const idEventDef EV_Light_SetLightParm( "setLightParm", "df" ); +const idEventDef EV_Light_SetLightParms( "setLightParms", "ffff" ); +const idEventDef EV_Light_SetRadiusXYZ( "setRadiusXYZ", "fff" ); +const idEventDef EV_Light_SetRadius( "setRadius", "f" ); +const idEventDef EV_Light_On( "On", NULL ); +const idEventDef EV_Light_Off( "Off", NULL ); +const idEventDef EV_Light_FadeOut( "fadeOutLight", "f" ); +const idEventDef EV_Light_FadeIn( "fadeInLight", "f" ); + +// RAVEN BEGIN +// bdube: added +const idEventDef EV_Light_SetLightGUI ( "setLightGUI", "s" ); +// jscott: added for modview +const idEventDef EV_Light_SetCurrentLightLevel( "setCurrentLightLevel", "d" ); +const idEventDef EV_Light_SetMaxLightLevel( "setMaxLightLevel", "d" ); +// kfuller: 8/11/03 +const idEventDef EV_Light_IsOn( "isOn", NULL, 'f' ); +const idEventDef EV_Light_Break( "break", "ef" ); +// kfuller: lights that blink to life +const idEventDef EV_Light_DoneBlinking( "doneBlinking", NULL ); +// kfuller: lights that blink off +const idEventDef EV_Light_DoneBlinkingOff( "doneBlinkingOff", NULL ); +// abahr: +const idEventDef EV_Light_Timer( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idLight ) + EVENT( EV_Light_SetShader, idLight::Event_SetShader ) + EVENT( EV_Light_GetLightParm, idLight::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idLight::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idLight::Event_SetLightParms ) + EVENT( EV_Light_SetRadiusXYZ, idLight::Event_SetRadiusXYZ ) + EVENT( EV_Light_SetRadius, idLight::Event_SetRadius ) + EVENT( EV_Hide, idLight::Event_Hide ) + EVENT( EV_Show, idLight::Event_Show ) + EVENT( EV_Light_On, idLight::Event_On ) + EVENT( EV_Light_Off, idLight::Event_Off ) + EVENT( EV_Activate, idLight::Event_ToggleOnOff ) + EVENT( EV_PostSpawn, idLight::Event_SetSoundHandles ) + EVENT( EV_Light_FadeOut, idLight::Event_FadeOut ) + EVENT( EV_Light_FadeIn, idLight::Event_FadeIn ) + +// RAVEN BEGIN +// bdube: added + EVENT( EV_Light_SetLightGUI, idLight::Event_SetLightGUI ) + EVENT( EV_Light_SetCurrentLightLevel, idLight::Event_SetCurrentLightLevel ) + EVENT( EV_Light_SetMaxLightLevel, idLight::Event_SetMaxLightLevel ) +// kfuller: 8/11/03 + EVENT( EV_Light_IsOn, idLight::Event_IsOn ) + EVENT( EV_Light_Break, idLight::Event_Break ) +// kfuller: lights that blink to life + EVENT( EV_Light_DoneBlinking, idLight::Event_DoneBlinking ) +// kfuller: lights that blink off + EVENT( EV_Light_DoneBlinkingOff, idLight::Event_DoneBlinkingOff ) + EVENT( EV_Earthquake, idLight::Event_EarthQuake ) +// abahr: + EVENT( EV_Light_Timer, idLight::Event_Timer ) +// RAVEN END +END_CLASS + + +/* +================ +idGameEdit::ParseSpawnArgsToRenderLight + +parse the light parameters +this is the canonical renderLight parm parsing, +which should be used by dmap and the editor +================ +*/ +bool idGameEdit::ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ) { + bool gotTarget, gotUp, gotRight; + const char *texture; + idVec3 color; + bool rv = true; + + memset( renderLight, 0, sizeof( *renderLight ) ); + + if (!args->GetVector("light_origin", "", renderLight->origin)) { + args->GetVector( "origin", "", renderLight->origin ); + } + + gotTarget = args->GetVector( "light_target", "", renderLight->target ); + gotUp = args->GetVector( "light_up", "", renderLight->up ); + gotRight = args->GetVector( "light_right", "", renderLight->right ); + args->GetVector( "light_start", "0 0 0", renderLight->start ); + if ( !args->GetVector( "light_end", "", renderLight->end ) ) { + renderLight->end = renderLight->target; + } + + // we should have all of the target/right/up or none of them + if ( ( gotTarget || gotUp || gotRight ) != ( gotTarget && gotUp && gotRight ) ) { + gameLocal.Warning( "Light at (%f,%f,%f) has bad target info\n", + renderLight->origin[0], renderLight->origin[1], renderLight->origin[2] ); + + return false; + } + + if ( !gotTarget ) { + renderLight->pointLight = true; + + // allow an optional relative center of light and shadow offset + args->GetVector( "light_center", "0 0 0", renderLight->lightCenter ); + +// RAVEN BEGIN +// bdube: default light radius changed to 320 + // create a point light + if (!args->GetVector( "light_radius", "320 320 320", renderLight->lightRadius ) ) { + float radius; + + args->GetFloat( "light", "320", radius ); +// RAVEN END + renderLight->lightRadius[0] = renderLight->lightRadius[1] = renderLight->lightRadius[2] = radius; + } + + if ( renderLight->lightRadius[0] == 0 || + renderLight->lightRadius[1] == 0 || + renderLight->lightRadius[2] == 0 ) { + gameLocal.Warning( "PointLight at ( %d, %d, %d ) has at least one radius component of 0!", + ( int )renderLight->origin[0], ( int )renderLight->origin[1], ( int )renderLight->origin[2] ); + rv = false; + } + } + + // get the rotation matrix in either full form, or single angle form + idAngles angles; + idMat3 mat; + if ( !args->GetMatrix( "light_rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + args->GetFloat( "angle", "0", angles[ 1 ] ); + angles[ 0 ] = 0; + angles[ 1 ] = idMath::AngleNormalize360( angles[ 1 ] ); + angles[ 2 ] = 0; + mat = angles.ToMat3(); + } + } + + // fix degenerate identity matrices + mat[0].FixDegenerateNormal(); + mat[1].FixDegenerateNormal(); + mat[2].FixDegenerateNormal(); + + renderLight->axis = mat; + + // check for other attributes + args->GetVector( "_color", "1 1 1", color ); + renderLight->shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight->shaderParms[ SHADERPARM_BLUE ] = color[2]; + args->GetFloat( "shaderParm3", "1", renderLight->shaderParms[ SHADERPARM_TIMESCALE ] ); + if ( !args->GetFloat( "shaderParm4", "0", renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] ) ) { + // offset the start time of the shader to sync it to the game time + renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + args->GetFloat( "shaderParm5", "0", renderLight->shaderParms[5] ); + args->GetFloat( "shaderParm6", "0", renderLight->shaderParms[6] ); + args->GetFloat( "shaderParm7", "0", renderLight->shaderParms[ SHADERPARM_MODE ] ); + args->GetBool( "noshadows", "0", renderLight->noShadows ); + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + args->GetFloat( "detailLevel", "10", renderLight->detailLevel ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: dynamic shadows + args->GetBool( "noDynamicShadows", "0", renderLight->noDynamicShadows ); +// RAVEN END + args->GetBool( "nospecular", "0", renderLight->noSpecular ); + args->GetBool( "parallel", "0", renderLight->parallel ); + + args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL + renderLight->shader = declManager->FindMaterial( texture, false ); + + return rv; +} + +/* +================ +idLight::UpdateChangeableSpawnArgs +================ +*/ +void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + source->Print(); + } + FreeSoundEmitter( true ); + gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); + + UpdateVisuals(); +} + +/* +================ +idLight::idLight +================ +*/ +idLight::idLight() { + memset( &renderLight, 0, sizeof( renderLight ) ); +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + localLightOrigin = vec3_zero; + localLightAxis = mat3_identity; + lightDefHandle = -1; + levels = 0; + currentLevel = 0; + baseColor = vec3_zero; + breakOnTrigger = false; + count = 0; + triggercount = 0; + lightParent = NULL; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + soundWasPlaying = false; + +// RAVEN BEGIN +// bdube: light gui + lightGUI = NULL; + random = 0.0f; + wait = 0.0f; +// RAVEN END +} + +/* +================ +idLight::~idLight +================ +*/ +idLight::~idLight() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idLight::Save + +archives object for save game file +================ +*/ +void idLight::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( renderLight ); + + savefile->WriteBool( renderLight.prelightModel != NULL ); + + savefile->WriteVec3( localLightOrigin ); + savefile->WriteMat3( localLightAxis ); + //qhandle_t lightDefHandle; // cnicholson: This wasn't here from id, so I didnt add it either. + + savefile->WriteString( brokenModel ); + savefile->WriteInt( levels ); + savefile->WriteInt( currentLevel ); + + savefile->WriteVec3( baseColor ); + savefile->WriteBool( breakOnTrigger ); + savefile->WriteInt( count ); + savefile->WriteInt( triggercount ); + savefile->WriteObject( lightParent ); + + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + + lightGUI.Save( savefile ); // cnicholson: added unsaved var + + savefile->WriteBool( soundWasPlaying ); +} + +/* +================ +idLight::Restore + +unarchives object from save game file +================ +*/ +void idLight::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// jscott: constants can be read from the spawnargs + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// RAVEN END + + bool hadPrelightModel; + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadBool( hadPrelightModel ); + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + if ( ( renderLight.prelightModel == NULL ) && hadPrelightModel ) { + assert( 0 ); + if ( developer.GetBool() ) { + // we really want to know if this happens + gameLocal.Error( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } else { + // but let it slide after release + gameLocal.Warning( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } + } + + savefile->ReadVec3( localLightOrigin ); + savefile->ReadMat3( localLightAxis ); + + savefile->ReadString( brokenModel ); + savefile->ReadInt( levels ); + savefile->ReadInt( currentLevel ); + + savefile->ReadVec3( baseColor ); + savefile->ReadBool( breakOnTrigger ); + savefile->ReadInt( count ); + savefile->ReadInt( triggercount ); + savefile->ReadObject( reinterpret_cast( lightParent ) ); + + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +// RAVEN BEGIN +// bdube: light gui + lightGUI.Restore ( savefile ); +// RAVEN END + + savefile->ReadBool( soundWasPlaying ); + + lightDefHandle = -1; + + SetLightLevel(); +} + +/* +================ +idLight::Spawn +================ +*/ +void idLight::Spawn( void ) { + bool start_off; + bool needBroken; + const char *demonic_shader; + + // do the parsing the same way dmap and the editor do + if ( !gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ) ) { + gameLocal.Warning( "Removing invalid light named: %s", GetName() ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + // we need the origin and axis relative to the physics origin/axis + localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); + localLightAxis = renderLight.axis * GetPhysics()->GetAxis().Transpose(); + + // set the base color from the shader parms + baseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + + // set the number of light levels + spawnArgs.GetInt( "levels", "1", levels ); + currentLevel = levels; + if ( levels <= 0 ) { + gameLocal.Error( "Invalid light level set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + // make sure the demonic shader is cached + if ( spawnArgs.GetString( "mat_demonic", NULL, &demonic_shader ) ) { + declManager->FindType( DECL_MATERIAL, demonic_shader ); + } + + // game specific functionality, not mirrored in + // editor or dmap light parsing + + // also put the light texture on the model, so light flares + // can get the current intensity of the light + renderEntity.referenceShader = renderLight.shader; + + lightDefHandle = -1; // no static version yet + + // see if an optimized shadow volume exists + // the renderer will ignore this value after a light has been moved, + // but there may still be a chance to get it wrong if the game moves + // a light before the first present, and doesn't clear the prelight + renderLight.prelightModel = 0; + if ( name[ 0 ] ) { + // this will return 0 if not found + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + } + + spawnArgs.GetBool( "start_off", "0", start_off ); + if ( start_off ) { + Off(); + } + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "break", "0", breakOnTrigger ); + spawnArgs.GetInt( "count", "1", count ); + + triggercount = 0; + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // if we have a health make light breakable + if ( health ) { + idStr model = spawnArgs.GetString( "model" ); // get the visual model + if ( !model.Length() ) { + gameLocal.Error( "Breakable light without a model set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + fl.takedamage = true; + + // see if we need to create a broken model name + needBroken = true; + if ( model.Length() && !brokenModel.Length() ) { + int pos; + + needBroken = false; + + pos = model.Find( "." ); + if ( pos < 0 ) { + pos = model.Length(); + } + if ( pos > 0 ) { + model.Left( pos, brokenModel ); + } + brokenModel += "_broken"; + if ( pos > 0 ) { + brokenModel += &model[ pos ]; + } + } + + // make sure the model gets cached + if ( !renderModelManager->CheckModel( brokenModel ) ) { + if ( needBroken ) { + gameLocal.Error( "Model '%s' not found for entity %d(%s)", brokenModel.c_str(), entityNumber, name.c_str() ); + } else { + brokenModel = ""; + } + } + + GetPhysics()->SetContents( spawnArgs.GetBool( "nonsolid" ) ? 0 : CONTENTS_SOLID ); + } + + PostEventMS( &EV_PostSpawn, 0 ); + +// RAVEN BEGIN +// bdube: light guis + const char* lightGUI; + if ( spawnArgs.GetString ( "light_gui", "", &lightGUI ) ) { + PostEventMS( &EV_Light_SetLightGUI, 0, lightGUI ); + } + +// abahr: + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// AReis: Minor light optimization stuff. + spawnArgs.GetBool( "globalLight", "0", renderLight.globalLight ); +// RAVEN END + + UpdateVisuals(); + +// RAVEN BEGIN +// ddynerman: ambient lights added clientside + if( renderLight.shader && renderLight.shader->IsAmbientLight() ) { + if ( !gameLocal.ambientLights.Find( static_cast(this) ) ) { + gameLocal.ambientLights.Append( static_cast(this) ); + } + fl.networkSync = false; // don't transmit ambient lights + } +// RAVEN END +} + +/* +================ +idLight::SetLightLevel +================ +*/ +void idLight::SetLightLevel( void ) { + idVec3 color; + float intensity; + + intensity = ( float )currentLevel / ( float )levels; + color = baseColor * intensity; + renderLight.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec3 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec4 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderLight.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( float red, float green, float blue ) { + baseColor.Set( red, green, blue ); + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec4 &color ) { + baseColor = color.ToVec3(); + renderLight.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + SetLightLevel(); +} + +/* +================ +idLight::SetShader +================ +*/ +void idLight::SetShader( const char *shadername ) { + // allow this to be NULL + renderLight.shader = declManager->FindMaterial( shadername, false ); + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParm +================ +*/ +void idLight::SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + renderLight.shaderParms[ parmnum ] = value; + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParms +================ +*/ +void idLight::SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + renderLight.shaderParms[ SHADERPARM_RED ] = parm0; + renderLight.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderLight.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderLight.shaderParms[ SHADERPARM_ALPHA ] = parm3; + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + baseColor.Set( parm0, parm1, parm2 ); + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::SetRadiusXYZ +================ +*/ +void idLight::SetRadiusXYZ( float x, float y, float z ) { + renderLight.lightRadius[0] = x; + renderLight.lightRadius[1] = y; + renderLight.lightRadius[2] = z; + PresentLightDefChange(); +} + +/* +================ +idLight::SetRadius +================ +*/ +void idLight::SetRadius( float radius ) { + renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = radius; + PresentLightDefChange(); +} + +/* +================ +idLight::On +================ +*/ +void idLight::On( void ) { + currentLevel = levels; + // offset the start time of the shader to sync it to the game time + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + +// RAVEN BEGIN + idStr blinkOnSound; + if (spawnArgs.GetString("snd_blinkOn", "", blinkOnSound)) + { + refSound.shader = declManager->FindSound(blinkOnSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + PostEventMS(&EV_Light_DoneBlinking, howLongInMS); + soundWasPlaying = false; + idStr blinkOnTexture; + if (spawnArgs.GetString( "mtr_blinkOn", "", blinkOnTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOnTexture, false ); + UpdateVisuals(); + Present(); + } + } + else +// RAVEN END + + if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Off +================ +*/ +void idLight::Off( void ) { +// RAVEN BEGIN +// kfuller: lights can flicker off + idStr blinkOffSound; + if (spawnArgs.GetString("snd_blinkOff", "", blinkOffSound)) + { + refSound.shader = declManager->FindSound(blinkOffSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL );//*1000; + PostEventMS(&EV_Light_DoneBlinkingOff, howLongInMS); + soundWasPlaying = false; + idStr blinkOffTexture; + if (spawnArgs.GetString( "mtr_blinkOff", "", blinkOffTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOffTexture, false ); + UpdateVisuals(); + Present(); + } + } + else + { + currentLevel = 0; + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { + StopSound( SND_CHANNEL_ANY, false ); + soundWasPlaying = true; + } + } +// RAVEN END + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Fade +================ +*/ +void idLight::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idLight::FadeOut +================ +*/ +void idLight::FadeOut( float time ) { + Fade( colorBlack, time ); +} + +/* +================ +idLight::FadeIn +================ +*/ +void idLight::FadeIn( float time ) { + idVec3 color; + idVec4 color4; + + currentLevel = levels; + spawnArgs.GetVector( "_color", "1 1 1", color ); + color4.Set( color.x, color.y, color.z, 1.0f ); + Fade( color4, time ); +} + +/* +================ +idLight::Killed +================ +*/ +void idLight::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + BecomeBroken( attacker ); +} + +/* +================ +idLight::BecomeBroken +================ +*/ +void idLight::BecomeBroken( idEntity *activator ) { + const char *damageDefName; + + fl.takedamage = false; + + if ( brokenModel.Length() ) { + SetModel( brokenModel ); + + if ( !spawnArgs.GetBool( "nonsolid" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel( brokenModel.c_str() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + } else if ( spawnArgs.GetBool( "hideModelOnBreak" ) ) { + SetModel( "" ); + GetPhysics()->SetContents( 0 ); + } + + if ( gameLocal.isServer ) { + + ServerSendInstanceEvent( EVENT_BECOMEBROKEN, NULL, true, -1 ); + + if ( spawnArgs.GetString( "def_damage", "", &damageDefName ) ) { + idVec3 origin = renderEntity.origin + renderEntity.bounds.GetCenter() * renderEntity.axis; + gameLocal.RadiusDamage( origin, activator, activator, this, this, damageDefName ); + } + + } + + ActivateTargets( activator ); + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + renderLight.shaderParms[ SHADERPARM_MODE ] = 1; + + // if the light has a sound, either start the alternate (broken) sound, or stop the sound + const char *parm = spawnArgs.GetString( "snd_broken" ); + if ( refSound.shader || ( parm && *parm ) ) { + StopSound( SND_CHANNEL_ANY, false ); + const idSoundShader *alternate = refSound.shader ? refSound.shader->GetAltSound() : declManager->FindSound( parm ); + if ( alternate ) { + // start it with no diversity, so the leadin break sound plays + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound( alternate, SND_CHANNEL_ANY, 0.0, 0 ); + } + } + } + + parm = spawnArgs.GetString( "mtr_broken" ); + if ( parm && *parm ) { + SetShader( parm ); + } + + UpdateVisuals(); +} + +/* +================ +idLight::PresentLightDefChange +================ +*/ +void idLight::PresentLightDefChange( void ) { + // let the renderer apply it to the world + if ( ( lightDefHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idLight::PresentModelDefChange +================ +*/ +void idLight::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idLight::Present +================ +*/ +void idLight::Present( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_RENDER ); +// RAVEN END + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + idEntity::Present(); + + // current transformation + renderLight.axis = localLightAxis * GetPhysics()->GetAxis(); + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * localLightOrigin; + + // reference the sound for shader synced effects + if ( lightParent ) { +// RAVEN BEGIN + renderLight.referenceSoundHandle = lightParent->GetSoundEmitter(); + renderEntity.referenceSoundHandle = lightParent->GetSoundEmitter(); + } + else { + renderLight.referenceSoundHandle = refSound.referenceSoundHandle; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; +// RAVEN END + } + + // update the renderLight and renderEntity to render the light and flare + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::Think +================ +*/ +void idLight::Think( void ) { + idVec4 color; + + if ( thinkFlags & TH_THINK ) { + if ( fadeEnd > 0 ) { + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + +// RAVEN BEGIN +// bdube: gui controlled lights + if ( lightGUI ) { + SetColor ( lightGUI->GetRenderEntity()->gui[0]->GetLightColor ( ) ); + } +// RAVEN END + } + + RunPhysics(); + Present(); +} + +/* +================ +idLight::GetPhysicsToSoundTransform +================ +*/ +bool idLight::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = localLightOrigin + renderLight.lightCenter; + axis = localLightAxis * GetPhysics()->GetAxis(); + return true; +} + +/* +================ +idLight::FreeLightDef +================ +*/ +void idLight::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================ +idLight::SaveState +================ +*/ +void idLight::SaveState( idDict *args ) { + int i, c = spawnArgs.GetNumKeyVals(); + for ( i = 0; i < c; i++ ) { + const idKeyValue *pv = spawnArgs.GetKeyVal(i); + if ( pv->GetKey().Find( "editor_", false ) >= 0 || pv->GetKey().Find( "parse_", false ) >= 0 ) { + continue; + } + args->Set( pv->GetKey(), pv->GetValue() ); + } +} + +/* +=============== +idLight::ShowEditingDialog +=============== +*/ +void idLight::ShowEditingDialog( void ) { + if ( g_editEntityMode.GetInteger() == 1 ) { + common->InitTool( EDITOR_LIGHT, &spawnArgs ); + } else { + common->InitTool( EDITOR_SOUND, &spawnArgs ); + } +} + +/* +================ +idLight::Event_SetShader +================ +*/ +void idLight::Event_SetShader( const char *shadername ) { + SetShader( shadername ); +} + +/* +================ +idLight::Event_GetLightParm +================ +*/ +void idLight::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderLight.shaderParms[ parmnum ] ); +} + +/* +================ +idLight::Event_SetLightParm +================ +*/ +void idLight::Event_SetLightParm( int parmnum, float value ) { + SetLightParm( parmnum, value ); +} + +/* +================ +idLight::Event_SetLightParms +================ +*/ +void idLight::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + SetLightParms( parm0, parm1, parm2, parm3 ); +} + +/* +================ +idLight::Event_SetRadiusXYZ +================ +*/ +void idLight::Event_SetRadiusXYZ( float x, float y, float z ) { + SetRadiusXYZ( x, y, z ); +} + +/* +================ +idLight::Event_SetRadius +================ +*/ +void idLight::Event_SetRadius( float radius ) { + SetRadius( radius ); +} + +/* +================ +idLight::Event_Hide +================ +*/ +void idLight::Event_Hide( void ) { + Hide(); + PresentModelDefChange(); + Off(); +} + +/* +================ +idLight::Event_Show +================ +*/ +void idLight::Event_Show( void ) { + Show(); + PresentModelDefChange(); + On(); +} + +/* +================ +idLight::Event_On +================ +*/ +void idLight::Event_On( void ) { + On(); +} + +/* +================ +idLight::Event_Off +================ +*/ +void idLight::Event_Off( void ) { + Off(); +} + +/* +================ +idLight::Event_ToggleOnOff +================ +*/ +void idLight::Event_ToggleOnOff( idEntity *activator ) { +// RAVEN BEGIN +// abahr: + if( wait > 0 ) { + if( EventIsPosted(&EV_Light_Timer) ) { + CancelEvents( &EV_Light_Timer ); + } else { + ProcessEvent( &EV_Light_Timer ); + } + } else { +// RAVEN END + triggercount++; + if ( triggercount < count ) { + return; + } + + // reset trigger count + triggercount = 0; + + if ( breakOnTrigger ) { + BecomeBroken( activator ); + breakOnTrigger = false; + return; + } + + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } +// RAVEN BEGIN +// abahr: + } +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSound::Event_Timer +================ +*/ +void idLight::Event_Timer( void ) { +// FIXME: think about putting this logic in helper function so we don't have cut and pasted code + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } + + PostEventSec( &EV_Light_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idLight::Event_SetSoundHandles + + set the same sound def handle on all targeted lights +================ +*/ +void idLight::Event_SetSoundHandles( void ) { + int i; + idEntity *targetEnt; + + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idLight::Type ) ) { + idLight *light = static_cast(targetEnt); + light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->renderEntity.referenceSoundHandle = renderEntity.referenceSoundHandle; + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } +// RAVEN BEGIN +// rjohnson: func_static's can now have their color parms affected by lights + else if ( targetEnt && targetEnt->IsType( idStaticEntity::GetClassType() ) ) { + targetEnt->GetRenderEntity()->referenceShader = renderLight.shader; + targetEnt->GetRenderEntity()->referenceSoundHandle = renderEntity.referenceSoundHandle; + } +// RAVEN END + } +} + +/* +================ +idLight::Event_FadeOut +================ +*/ +void idLight::Event_FadeOut( float time ) { + FadeOut( time ); +} + +/* +================ +idLight::Event_FadeIn +================ +*/ +void idLight::Event_FadeIn( float time ) { + FadeIn( time ); +} + +/* +================ +idLight::ClientPredictionThink +================ +*/ +void idLight::ClientPredictionThink( void ) { + Think(); +} + +/* +================ +idLight::WriteToSnapshot +================ +*/ +void idLight::WriteToSnapshot( idBitMsgDelta &msg ) const { + + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + + msg.WriteByte( currentLevel ); + msg.WriteLong( PackColor( baseColor ) ); + // msg.WriteBits( lightParent.GetEntityNum(), GENTITYNUM_BITS ); + +/* // only helps prediction + msg.WriteLong( PackColor( fadeFrom ) ); + msg.WriteLong( PackColor( fadeTo ) ); + msg.WriteLong( fadeStart ); + msg.WriteLong( fadeEnd ); +*/ + + // FIXME: send renderLight.shader + msg.WriteFloat( renderLight.lightRadius[0], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[1], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[2], 5, 10 ); + + msg.WriteLong( PackColor( idVec4( renderLight.shaderParms[SHADERPARM_RED], + renderLight.shaderParms[SHADERPARM_GREEN], + renderLight.shaderParms[SHADERPARM_BLUE], + renderLight.shaderParms[SHADERPARM_ALPHA] ) ) ); + + msg.WriteFloat( renderLight.shaderParms[SHADERPARM_TIMESCALE], 5, 10 ); + msg.WriteLong( renderLight.shaderParms[SHADERPARM_TIMEOFFSET] ); + //msg.WriteByte( renderLight.shaderParms[SHADERPARM_DIVERSITY] ); + msg.WriteShort( renderLight.shaderParms[SHADERPARM_MODE] ); + + WriteColorToSnapshot( msg ); +} + +/* +================ +idLight::ReadFromSnapshot +================ +*/ +void idLight::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 shaderColor; + int oldCurrentLevel = currentLevel; + idVec3 oldBaseColor = baseColor; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + currentLevel = msg.ReadByte(); + if ( currentLevel != oldCurrentLevel ) { + // need to call On/Off for flickering lights to start/stop the sound + // while doing it this way rather than through events, the flickering is out of sync between clients + // but at least there is no question about saving the event and having them happening globally in the world + if ( currentLevel ) { + On(); + } else { + Off(); + } + } + UnpackColor( msg.ReadLong(), baseColor ); + // lightParentEntityNum = msg.ReadBits( GENTITYNUM_BITS ); + +/* // only helps prediction + UnpackColor( msg.ReadLong(), fadeFrom ); + UnpackColor( msg.ReadLong(), fadeTo ); + fadeStart = msg.ReadLong(); + fadeEnd = msg.ReadLong(); +*/ + + // FIXME: read renderLight.shader + renderLight.lightRadius[0] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[1] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[2] = msg.ReadFloat( 5, 10 ); + + UnpackColor( msg.ReadLong(), shaderColor ); + renderLight.shaderParms[SHADERPARM_RED] = shaderColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = shaderColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = shaderColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = shaderColor[3]; + + renderLight.shaderParms[SHADERPARM_TIMESCALE] = msg.ReadFloat( 5, 10 ); + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadLong(); + //renderLight.shaderParms[SHADERPARM_DIVERSITY] = msg.ReadFloat(); + renderLight.shaderParms[SHADERPARM_MODE] = msg.ReadShort(); + + ReadColorFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( ( currentLevel != oldCurrentLevel ) || ( baseColor != oldBaseColor ) ) { + SetLightLevel(); + } else { + PresentLightDefChange(); + PresentModelDefChange(); + } + } +} + +/* +================ +idLight::ClientReceiveEvent +================ +*/ +bool idLight::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_BECOMEBROKEN: { + BecomeBroken( NULL ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// kfuller: 8/11/03 +void idLight::Event_IsOn() +{ + // not entirely sure this is the best way to check for offness + if (currentLevel == 0) + { + idThread::ReturnFloat( false ); + } + else + { + idThread::ReturnFloat( true ); + } +} + +void idLight::Event_Break(idEntity *activator, float turnOff) +{ + BecomeBroken(activator); + if (turnOff) + { + Off(); + } +} + +void idLight::Event_DoneBlinking() +{ + // switch to a new (possibly non-blinking) shader for the light as well as a new looping sound + idStr blinkedOn; + if (spawnArgs.GetString( "mtr_doneBlinking", "", blinkedOn )) + { + renderLight.shader = declManager->FindMaterial( blinkedOn, false ); + UpdateVisuals(); + Present(); + } + idStr doneBlinkingSound; + if (spawnArgs.GetBool("doneBlinkingNoSound")) + { + StopSound( SCHANNEL_ANY, false ); + } + else if (spawnArgs.GetString("snd_doneBlinking", "", doneBlinkingSound)) + { + StopSound( SCHANNEL_ANY, false ); + if (doneBlinkingSound.Icmp("none")) + { + refSound.shader = declManager->FindSound(doneBlinkingSound); + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + } +} + +void idLight::Event_DoneBlinkingOff() +{ + // switch light and sound off + currentLevel = 0; + SetLightLevel(); +// RAVEN BEGIN + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying ( ) ) { +// RAVEN END + StopSound( SCHANNEL_ANY, false ); + soundWasPlaying = true; + } +} + +// kfuller: want fx entities to be able to respond to earthquakes +void idLight::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + + if (requiresLOS) + { + bool inPVS = gameLocal.InPlayerPVS( this ); + + // for lights, a line-of-sight check doesn't make as much sense, so if the quake requires an LOS check + //we'll actually perform a PVS check + if (!inPVS) + { + return; + } + } + // do something with this light + if (spawnArgs.GetBool("quakeBreak")) + { + spawnArgs.SetBool("quakeBreak", false); + BecomeBroken(gameLocal.entities[ENTITYNUM_WORLD]); + return; + } + + float offTime = spawnArgs.GetFloat("quakeOffTime", "1.0"); + + Off(); + PostEventMS(&EV_Light_On, offTime*1000.0f); +} + +/* +================ +idLight::Event_SetLightGUI +================ +*/ +void idLight::Event_SetLightGUI ( const char* gui ) { + lightGUI = gameLocal.FindEntity( gui ); + if ( lightGUI && lightGUI->GetRenderEntity() && lightGUI->GetRenderEntity()->gui[0] ) { + BecomeActive( TH_THINK ); + } else { + lightGUI = NULL; + } +} + +/* +================ +idLight::Event_SetCurrentLightLevel +================ +*/ +void idLight::Event_SetCurrentLightLevel( int in ) { + currentLevel = in; + SetLightLevel(); +} + +/* +================ +idLight::Event_SetMaxLightLevel +================ +*/ +void idLight::Event_SetMaxLightLevel( int in ) { + levels = in; + SetLightLevel(); +} + +// RAVEN END diff --git a/source/game/Light.h b/source/game/Light.h new file mode 100644 index 0000000..72cfd08 --- /dev/null +++ b/source/game/Light.h @@ -0,0 +1,146 @@ +#ifndef __GAME_LIGHT_H__ +#define __GAME_LIGHT_H__ + +/* +=============================================================================== + + Generic light. + +=============================================================================== +*/ + +extern const idEventDef EV_Light_GetLightParm; +extern const idEventDef EV_Light_SetLightParm; +extern const idEventDef EV_Light_SetLightParms; + +class idLight : public idEntity { +public: + CLASS_PROTOTYPE( idLight ); + + idLight(); + ~idLight(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + virtual void Think( void ); + virtual void FreeLightDef( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + void Present( void ); + + void SaveState( idDict *args ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void GetColor( idVec4 &out ) const; + const idVec3 & GetBaseColor( void ) const { return baseColor; } + void SetShader( const char *shadername ); + void SetLightParm( int parmnum, float value ); + void SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void SetRadiusXYZ( float x, float y, float z ); + void SetRadius( float radius ); + void On( void ); + void Off( void ); + void Fade( const idVec4 &to, float fadeTime ); + void FadeOut( float time ); + void FadeIn( float time ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void BecomeBroken( idEntity *activator ); + qhandle_t GetLightDefHandle( void ) const { return lightDefHandle; } + void SetLightParent( idEntity *lparent ) { lightParent = lparent; } + void SetLightLevel( void ); + +// RAVEN BEGIN +// jshepard: other entities (speakers) need access to the refSound of a light object + void SetRefSound( int rSound ) { refSound.referenceSoundHandle = rSound;} +// ddynerman: sometimes the game needs to know if this light is ambient + bool IsAmbient( void ) { return renderLight.shader->IsAmbientLight(); } +// RAVEN END + virtual void ShowEditingDialog( void ); + + enum { + EVENT_BECOMEBROKEN = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + renderLight_t renderLight; // light presented to the renderer + idVec3 localLightOrigin; // light origin relative to the physics origin + idMat3 localLightAxis; // light axis relative to physics axis + qhandle_t lightDefHandle; // handle to renderer light def + idStr brokenModel; + int levels; + int currentLevel; + idVec3 baseColor; + bool breakOnTrigger; + int count; + int triggercount; + idEntity * lightParent; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; +// RAVEN BEGIN +// bdube: light gui + idEntityPtr lightGUI; +// abahr: + float wait; + float random; +// RAVEN END + +private: + bool soundWasPlaying; + + void PresentLightDefChange( void ); + void PresentModelDefChange( void ); + +// RAVEN BEGIN +// jscott: added events for light level +private: + void Event_SetCurrentLightLevel ( int in ); + void Event_SetMaxLightLevel ( int in ); + void Event_IsOn( void ); + void Event_Break( idEntity *activator, float turnOff ); + void Event_DoneBlinking( void ); + void Event_DoneBlinkingOff( void ); + void Event_EarthQuake( float requiresLOS ); + void Event_Timer( void ); +// RAVEN END + +private: + void Event_SetShader( const char *shadername ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetRadiusXYZ( float x, float y, float z ); + void Event_SetRadius( float radius ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_On( void ); + void Event_Off( void ); + void Event_ToggleOnOff( idEntity *activator ); + void Event_SetSoundHandles( void ); + void Event_FadeOut( float time ); + void Event_FadeIn( float time ); +// RAVEN BEGIN +// bdube: set light gui + void Event_SetLightGUI( const char* gui ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: externed events +extern const idEventDef EV_Light_SetCurrentLightLevel; +extern const idEventDef EV_Light_SetMaxLightLevel; +extern const idEventDef EV_Light_SetRadius; +// RAVEN END + +#endif /* !__GAME_LIGHT_H__ */ diff --git a/source/game/LipSync.cpp b/source/game/LipSync.cpp new file mode 100644 index 0000000..2854fcf --- /dev/null +++ b/source/game/LipSync.cpp @@ -0,0 +1,467 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// 1 normal +// 2 scared +// 3 surprised +// 4 panicked +// 5 angry +// 6 suspicious with rt eyelid raised +// 7 suspicious with lft eyelid raised +// 8 curious +// 9 tired +// 10 happy + +idCVar fas_debug( "fas_debug", "0", CVAR_INTEGER, "debug info for facial animation system" ); +idCVar fas_threshhold0( "fas_threshhold0", "60", CVAR_INTEGER, "intensity required to use frame set 0" ); +idCVar fas_threshhold1( "fas_threshhold1", "30", CVAR_INTEGER, "intensity required to use frame set 1" ); +idCVar fas_blendBias( "fas_blendBias", "1.5", 0, "multiplier to the per phoneme blend time" ); +idCVar fas_intensityBias( "fas_intensityBias", "0", CVAR_INTEGER, "bias applied to the intensity of the phoneme when trying to extract the viseme" ); +idCVar fas_timeOffset( "fas_timeOffset", "50", CVAR_INTEGER, "ms offset to the viseme frame" ); + +idStr phonemeFile; +idHashTable *visemeTable100; +idHashTable *visemeTable66; +idHashTable *visemeTable33; + +/* +================ +rvViseme::Init +================ +*/ +void rvViseme::Init( idStr &phon, int f, int bt ) +{ + phoneme = phon; + frame = f; + blendTime = bt; +} + +/* +================ +FAS_LoadPhonemes + +Load in the the file that cross references phonemes with visemes. +================ +*/ +bool FAS_LoadPhonemes( const char *visemes ) +{ + idStr visemeFile; + rvViseme viseme; + idLexer lexer; + idToken token; + idStr phoneme; + int frame, blendTime, intensity; + + phonemeFile = visemes; + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + + common->Printf( "Loading viseme file: %s\n", visemes ); + + visemeFile = "lipsync/"; + visemeFile += visemes; + visemeFile += ".viseme"; + + lexer.SetFlags( DECL_LEXER_FLAGS ); + lexer.LoadFile( visemeFile ); + + if( !lexer.ExpectTokenString( "visemes" ) ) + { + return( false ); + } + + if( !lexer.ExpectTokenString( "{" ) ) + { + return( false ); + } + + while( true ) + { + if( !lexer.ReadToken( &token ) ) + { + return( false ); + } + + if( token == "}" ) + { + break; + } + + phoneme = token; + lexer.ExpectTokenString( "," ); + frame = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + blendTime = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + intensity = lexer.ParseInt(); + + viseme.Init( phoneme, frame, blendTime ); + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Set( phoneme, viseme ); + } + else if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Set( phoneme, viseme ); + } + else + { + visemeTable33->Set( phoneme, viseme ); + } + } + + return( true ); +} + +/* +================ +rvLipSyncData +================ +*/ +rvLipSyncData::rvLipSyncData( const rvDeclLipSync *ls, int time ) +{ + const char *lsd = ls->GetLipSyncData(); + + mLexer.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWNUMBERNAMES ); + mLexer.LoadMemory( lsd, idStr::Length( lsd ), ls->GetName() ); + mFlags = 0; + mFrame = 0; + mBlendTime = 0; + mEmotion = "idle"; + mNextTokenTime = time; +} + +void rvLipSyncData::SetFrame( int frame ) +{ + mLastFrame = mFrame; + mFrame = frame; + mVisemeStartTime = mNextTokenTime; +} + +float rvLipSyncData::GetFrontLerp( void ) +{ + float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( float )( gameLocal.GetTime() + fas_timeOffset.GetInteger() - mVisemeStartTime ) / ( float )mBlendTime ); + return( lerp ); +} + +/* +================ +FAS_StartVisemeExtraction + +Use a lexer to extract the phoneme data. This is a pretty slow but safe way. +There shouldn't be much in the way of multiple lip syncs going on, and if there are, it should be in a non performance critical cinematic. +================ +*/ +rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ) +{ + rvLipSyncData *lsd; + + lsd = new rvLipSyncData( ls, time ); + + return( lsd ); +} + +/* +================ +FAS_EndVisemeExtraction + +Delete the workspace. FAS could use a void pointer if it wanted +================ +*/ +void FAS_EndVisemeExtraction( rvLipSyncData *lsd ) +{ + delete lsd; +} + +/* +================ +FAS_ExtractViseme + +Extract the correct viseme frame from the lexer +================ +*/ +void FAS_ExtractViseme( rvLipSyncData *lsd, int time ) +{ + idToken token; + rvViseme *viseme; + idStr phoneme, duration; + int index, intensity; + + // Make sure not to return any garbage + lsd->ClearFlags(); + + // Grab all the visemes, phrases and emotions until we are current + while( lsd->Ready( time ) ) + { + if( !lsd->ReadToken( &token ) ) + { + lsd->SetFlags( FAS_ENDED ); + return; + } + + if( token == "<" ) + { + // Extract phrase + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse phrase from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetLastPhrase( token ); + lsd->ExpectTokenString( ">" ); + + lsd->SetFlags( FAS_NEW_PHRASE ); + } + else if( token == "{" ) + { + // Extract emotion + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse emotion from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetEmotion( token ); + lsd->ExpectTokenString( "}" ); + + lsd->SetFlags( FAS_NEW_EMOTION ); + } + else + { + // Extract phoneme data + index = 0; + phoneme = idStr( token[index] ); + if( isupper( phoneme[0] ) ) + { + index++; + phoneme += token[index]; + } + + // Extract duration + index++; + + duration = idStr( token[index] ); + index++; + if( isdigit( token[index] ) ) + { + duration += token[index]; + index++; + } + + // Extract intensity + intensity = ( token[index] - 'a' ) * 4; + intensity += fas_intensityBias.GetInteger(); + + // Extract the viseme data for the selected viseme + viseme = NULL; + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Get( phoneme, &viseme ); + } + if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Get( phoneme, &viseme ); + } + else + { + visemeTable33->Get( phoneme, &viseme ); + } + + if( !viseme ) + { + common->Printf( "FAS: Failed to find phoneme %s intensity %d", phoneme.c_str(), intensity ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetFrame( viseme->GetFrame() ); + lsd->SetNextTokenTime( atol( duration ) * 10 ); + lsd->SetBlendTime( int( viseme->GetBlendTime() * fas_blendBias.GetFloat() ) ); + lsd->SetFlags( FAS_NEW_VISEME ); + } + } +} + +/* +================ +FAS_Reload_f +================ +*/ +void FAS_Reload_f( const idCmdArgs &args ) +{ + // mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + + FAS_LoadPhonemes( phonemeFile.c_str() ); + + fileSystem->SetIsFileLoadingAllowed( false ); +} + +/* +================ +FAS_Init +================ +*/ +bool FAS_Init( const char *visemes ) +{ + // jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_ANIM ); + + cmdSystem->AddCommand( "reloadFAS", FAS_Reload_f, CMD_FL_SYSTEM, "reloads the viseme data" ); + return( FAS_LoadPhonemes( visemes ) ); +} + +/* +================ +FAS_Shutdown +================ +*/ +void FAS_Shutdown( void ) +{ + phonemeFile.Clear(); + if ( visemeTable100 ) { + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + } + + cmdSystem->RemoveCommand( "reloadFAS" ); +} + +/* +================ +idAFAttachment::EndLipSyncing +================ +*/ +void idAFAttachment::EndLipSyncing( void ) +{ + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0.0f }; + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + animator.CycleAnim( ANIMCHANNEL_HEAD, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + + FAS_EndVisemeExtraction( lipSyncData ); + lipSyncData = NULL; +} + +/* +================ +idAFAttachment::StartLipSyncing +================ +*/ +int idAFAttachment::StartLipSyncing( const char *speechDecl ) +{ + int length; + + length = 0; + + // Clean up any spurious data + EndLipSyncing(); + + // Start a new lipsync if there is one + if( speechDecl[0] ) + { + const rvDeclLipSync *lipSync; + int emotion; + idStr anim; + + lipSync = declManager->FindLipSync( speechDecl ); + lipSyncData = FAS_StartVisemeExtraction( lipSync, gameLocal.GetTime() ); + + // Output debug info + if( lipSync->GetDescription() && fas_debug.GetInteger() ) + { + gameLocal.Printf( "Name: %s\n", speechDecl ); + gameLocal.Printf( "Sub: %s\n", lipSync->GetDescription().c_str() ); + gameLocal.Printf( "Lip: %s\n", lipSync->GetLipSyncData() ); + } + + // Start the associated sound + refSound.diversity = 0.0f; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + StartSoundShader( declManager->FindSound( lipSync->GetName() ), SND_CHANNEL_VOICE, refSound.parms.soundShaderFlags | SSF_IS_VO, false, &length ); + + // Start the default emotion + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + } + + return( length ); +} + +/* +================ +idAFAttachment::HandleLipSync +================ +*/ +void idAFAttachment::HandleLipSync( void ) +{ + idStr anim; + int emotion; + + if( !lipSyncData ) + { + return; + } + + FAS_ExtractViseme( lipSyncData, gameLocal.GetTime() + fas_timeOffset.GetInteger() ); + if( lipSyncData->HasEnded() ) + { + EndLipSyncing(); + return; + } + + // If frame non zero - blend to it as a new viseme + if( lipSyncData->GetFrame() || lipSyncData->GetLastFrame() ) + { + frameBlend_t frameBlend; + + frameBlend.cycleCount = 0; + frameBlend.frame1 = idMath::ClampInt( 0, 120, lipSyncData->GetLastFrame() - 1 ); + frameBlend.frame2 = idMath::ClampInt( 0, 120, lipSyncData->GetFrame() - 1 ); + frameBlend.frontlerp = lipSyncData->GetFrontLerp(); + frameBlend.backlerp = 1.0f - frameBlend.frontlerp; + + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + if( fas_debug.GetInteger() > 1 ) + { + common->Printf( "Blending: %d (%2f) -> %d (%2f)\n", frameBlend.frame1, frameBlend.frontlerp, frameBlend.frame2, frameBlend.backlerp ); + } + } + + // If an embedded emotion command, play it. + if( lipSyncData->HasNewEmotion() ) + { + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + + if( fas_debug.GetInteger() ) + { + common->Printf( "Emotion: %s\n", lipSyncData->GetEmotion().c_str() ); + } + } + + // If a new phrase, display in debug mode + if( fas_debug.GetInteger() && lipSyncData->HasNewPhrase() ) + { + common->Printf( "Phrase: %s(%i)\n", lipSyncData->GetLastPhrase().c_str(), lipSyncData->GetFrame() ); + } +} + +// end diff --git a/source/game/LipSync.h b/source/game/LipSync.h new file mode 100644 index 0000000..59dd52e --- /dev/null +++ b/source/game/LipSync.h @@ -0,0 +1,87 @@ +#ifndef _LIPSYNC_H_INC_ +#define _LIPSYNC_H_INC_ + +// Possible TBDs +// Auto head swapping from simple to lip sync head +// Return time of sound from the speak call for the script to wait +// Set emotion directly without encoding it into the string + +class rvViseme +{ +public: + rvViseme( void ) {} + ~rvViseme( void ) { phoneme.Clear(); } + + void Init( idStr &phon, int f, int bt ); + + int GetFrame( void ) const { return( frame ); } + int GetBlendTime( void ) const { return( blendTime ); } + +private: + idStr phoneme; + int frame; + int blendTime; +}; + +#define FAS_NEW_VISEME BIT( 0 ) +#define FAS_NEW_PHRASE BIT( 1 ) +#define FAS_NEW_EMOTION BIT( 2 ) +#define FAS_ENDED BIT( 3 ) + +class rvLipSyncData +{ +public: + rvLipSyncData( const rvDeclLipSync *ls, int time ); + ~rvLipSyncData( void ) {} + + bool Ready( int time ) { return( time >= mNextTokenTime ); } + + int ReadToken( idToken *token ) { return( mLexer.ReadToken( token ) ); } + int ExpectTokenString( const char *str ) { return( mLexer.ExpectTokenString( str ) ); } + void SetNextTokenTime( int time ) { mNextTokenTime += time; } + + void ClearFlags( void ) { mFlags = 0; } + void SetFlags( int flags ) { mFlags |= flags; } + bool HasNewPhoneme( void ) const { return( !!( mFlags & FAS_NEW_VISEME ) ); } + bool HasNewPhrase( void ) const { return( !!( mFlags & FAS_NEW_PHRASE ) ); } + bool HasNewEmotion( void ) const { return( !!( mFlags & FAS_NEW_EMOTION ) ); } + bool HasEnded( void ) const { return( !!( mFlags & FAS_ENDED ) ); } + + void SetFrame( int frame ); + int GetFrame( void ) const { return( mFrame ); } + int GetLastFrame( void ) { return( mLastFrame ); } + + void SetBlendTime( int bt ) { mBlendTime = bt; } + int GetBlendTime( void ) const { return( mBlendTime ); } + + float GetFrontLerp( void ); + + void SetEmotion( idStr &str ) { mEmotion = str; } + const idStr &GetEmotion( void ) const { return( mEmotion ); } + + void SetLastPhrase( idStr &str ) { mLastPhrase = str; } + const idStr &GetLastPhrase( void ) const { return( mLastPhrase ); } + +private: + int mNextTokenTime; + int mFlags; + int mFrame; + int mLastFrame; + int mVisemeStartTime; + int mBlendTime; + idStr mEmotion; + idStr mLastPhrase; + idLexer mLexer; +}; + +bool FAS_Init( const char *visemes ); +void FAS_Shutdown( void ); + +void FAS_ExtractViseme( class rvLipSyncData *lsd, int time ); +class rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ); +void FAS_EndVisemeExtraction( class rvLipSyncData *lsd ); + +extern idCVar fas_debug; +extern idCVar fas_timeOffset; + +#endif // _LIPSYNC_H_INC_ diff --git a/source/game/Misc.cpp b/source/game/Misc.cpp new file mode 100644 index 0000000..c215a33 --- /dev/null +++ b/source/game/Misc.cpp @@ -0,0 +1,4386 @@ +/* + +Various utility objects and functions. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// nmckenzie: added ai +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#include "ai/AI.h" +// RAVEN END + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpawnableEntity ) +END_CLASS + +/* +====================== +idSpawnableEntity::Spawn +====================== +*/ +void idSpawnableEntity::Spawn() { + // this just holds dict information +} + + +/* +=============================================================================== + + idPlayerStart + +=============================================================================== +*/ + +const idEventDef EV_TeleportStage( "", "e" ); + +CLASS_DECLARATION( idEntity, idPlayerStart ) + EVENT( EV_Activate, idPlayerStart::Event_Teleport ) + EVENT( EV_TeleportStage, idPlayerStart::Event_TeleportStage ) +END_CLASS + +/* +=============== +idPlayerStart::idPlayerStart +================ +*/ +idPlayerStart::idPlayerStart( void ) { + teleportStage = 0; +} + +/* +=============== +idPlayerStart::Spawn +================ +*/ +void idPlayerStart::Spawn( void ) { + teleportStage = 0; +} + +/* +================ +idPlayerStart::Save +================ +*/ +void idPlayerStart::Save( idSaveGame *savefile ) const { + savefile->WriteInt( teleportStage ); +} + +/* +================ +idPlayerStart::Restore +================ +*/ +void idPlayerStart::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( teleportStage ); +} + +/* +================ +idPlayerStart::ClientReceiveEvent +================ +*/ +bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int entityNumber; + + switch( event ) { + case EVENT_TELEPORTPLAYER: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); + float x = msg.ReadFloat(); + float y = msg.ReadFloat(); + float z = msg.ReadFloat(); + + idVec3 prevOrigin( x, y, z ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( player != NULL && player->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Event_TeleportEntity( player, false, prevOrigin ); + } + return true; + } + case EVENT_TELEPORTITEM: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idEntity* activator = gameLocal.entities[ entityNumber ]; + + if ( activator != NULL ) { + Event_TeleportEntity( activator, false ); + } + return true; + } + + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +=============== +idPlayerStart::Event_TeleportStage + +FIXME: add functionality to fx system ( could be done with player scripting too ) +================ +*/ +void idPlayerStart::Event_TeleportStage( idPlayer *player ) { + float teleportDelay = spawnArgs.GetFloat( "teleportDelay" ); + switch ( teleportStage ) { + case 0: + player->playerView.Flash( colorWhite, 125 ); + player->SetInfluenceLevel( INFLUENCE_LEVEL3 ); + player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -20.0f, teleportDelay ); + teleportStage++; + PostEventSec( &EV_TeleportStage, teleportDelay, player ); + break; + case 1: + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0.25f ); + teleportStage++; + PostEventSec( &EV_TeleportStage, 0.25f, player ); + break; + case 2: + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + TeleportPlayer( player ); + player->StopSound( SND_CHANNEL_BODY2, false ); + player->SetInfluenceLevel( INFLUENCE_NONE ); + teleportStage = 0; + break; + default: + break; + } +} + +/* +=============== +idPlayerStart::TeleportPlayer +================ +*/ +void idPlayerStart::TeleportPlayer( idPlayer *player ) { + float pushVel = spawnArgs.GetFloat( "push", "50000" ); + float f = spawnArgs.GetFloat( "visualEffect", "0" ); + const char *viewName = spawnArgs.GetString( "visualView", "" ); + idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL; + + if ( f && ent ) { + // place in private camera view for some time + // the entity needs to teleport to where the camera view is to have the PVS right + player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this ); + + player->SetPrivateCameraView( static_cast(ent) ); + // the player entity knows where to spawn from the previous Teleport call + if ( !gameLocal.isClient ) { + player->PostEventSec( &EV_Player_ExitTeleporter, f ); + } + } else { + // direct to exit, Teleport will take care of the killbox + player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL ); + + // multiplayer hijacked this entity, so only push the player in multiplayer + if ( gameLocal.isMultiplayer ) { + //push + idVec3 impulse( pushVel, 0, 0 ); + impulse *= GetPhysics( )->GetAxis ( ); + player->ApplyImpulse( gameLocal.world, 0, player->GetPhysics( )->GetOrigin( ), impulse ); + } + } +} + +/* +=============== +idPlayerStart::Teleport +For non-players +================ +*/ +void idPlayerStart::Teleport( idEntity* other ) { + other->SetOrigin( GetPhysics()->GetOrigin() ); + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + vel *= GetPhysics()->GetAxis(); + other->GetPhysics()->SetLinearVelocity( vel ); +} + +void idPlayerStart::Event_TeleportEntity( idEntity* activator, bool predicted, idVec3& prevOrigin ) { + idPlayer *player; + + if ( activator->IsType( idPlayer::GetClassType() ) ) { + player = static_cast( activator ); + } else { + player = NULL; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( activator->entityNumber, GENTITYNUM_BITS ); + ServerSendInstanceEvent( EVENT_TELEPORTITEM, &msg, false, -1 ); + } + + idVec3 oldOrigin = activator->GetPhysics()->GetOrigin(); + Teleport( activator ); + + if( gameLocal.isMultiplayer ) { + if( gameLocal.isServer ) { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", activator->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else if( predicted ) { + // only predict teleport enter + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + + if ( player ) { + if ( spawnArgs.GetBool( "visualFx" ) ) { + + teleportStage = 0; + Event_TeleportStage( player ); + + } else { + idVec3 oldOrigin; + + if( gameLocal.isServer || prevOrigin == vec3_zero ) { + oldOrigin = activator->GetPhysics()->GetOrigin(); + } else { + oldOrigin = prevOrigin; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + msg.WriteFloat( oldOrigin.x ); + msg.WriteFloat( oldOrigin.y ); + msg.WriteFloat( oldOrigin.z ); + ServerSendInstanceEvent( EVENT_TELEPORTPLAYER, &msg, false, -1 ); + } + + TeleportPlayer( player ); + + if( gameLocal.isMultiplayer ) { + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport", player->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + } + + +/* +=============== +idPlayerStart::Event_TeleportPlayer +================ +*/ +void idPlayerStart::Event_Teleport( idEntity *activator ) { + Event_TeleportEntity( activator, true ); +} + +/* +=============================================================================== + + idActivator + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idActivator ) + EVENT( EV_Activate, idActivator::Event_Activate ) +END_CLASS + +/* +=============== +idActivator::Save +================ +*/ +void idActivator::Save( idSaveGame *savefile ) const { + savefile->WriteBool( stay_on ); +} + +/* +=============== +idActivator::Restore +================ +*/ +void idActivator::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( stay_on ); + + if ( stay_on ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Spawn +================ +*/ +void idActivator::Spawn( void ) { + bool start_off; + + spawnArgs.GetBool( "stay_on", "0", stay_on ); + spawnArgs.GetBool( "start_off", "0", start_off ); + +// RAVEN BEGIN +// bdube: optional clip model on activators + const char *temp; + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel(temp), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } else { + GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f ); + } + + GetPhysics()->SetContents( 0 ); +// RAVEN END + + if ( !start_off ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Think +================ +*/ +void idActivator::Think( void ) { + RunPhysics(); + if ( thinkFlags & TH_THINK ) { + if ( TouchTriggers() ) { + if ( !stay_on ) { + BecomeInactive( TH_THINK ); + } + } + } + Present(); +} + +/* +=============== +idActivator::Activate +================ +*/ +void idActivator::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + +/* +=============================================================================== + +idPathCorner + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPathCorner ) +// EVENT( AI_RandomPath, idPathCorner::Event_RandomPath ) +END_CLASS + +/* +===================== +idPathCorner::Spawn +===================== +*/ +void idPathCorner::Spawn( void ) { +} + +/* +===================== +idPathCorner::DrawDebugInfo +===================== +*/ +void idPathCorner::DrawDebugInfo( void ) { + idEntity *ent; + idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idPathCorner::Type ) ) { + continue; + } + + idVec3 org = ent->GetPhysics()->GetOrigin(); + gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 ); + } +} + +/* +============ +idPathCorner::RandomPath +============ +*/ +idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) { + int i; + int num; + int which; + idEntity *ent; + idPathCorner *path[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < source->targets.Num(); i++ ) { + ent = source->targets[ i ].GetEntity(); + if ( ent && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) { + path[ num++ ] = static_cast( ent ); + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + return NULL; + } + + which = gameLocal.random.RandomInt( num ); + return path[ which ]; +} + +/* +===================== +idPathCorner::Event_RandomPath +===================== +*/ +void idPathCorner::Event_RandomPath( void ) { + idPathCorner *path; + + path = RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +=============================================================================== + + idDamagable + +=============================================================================== +*/ + +const idEventDef EV_RestoreDamagable( "" ); +// RAVEN BEGIN +// kfuller: spawn other things +const idEventDef EV_SpawnForcefield( "" ); +const idEventDef EV_SpawnTriggerHurt( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idDamagable ) + EVENT( EV_Activate, idDamagable::Event_BecomeBroken ) + EVENT( EV_RestoreDamagable, idDamagable::Event_RestoreDamagable ) +END_CLASS + +/* +================ +idDamagable::idDamagable +================ +*/ +idDamagable::idDamagable( void ) { + count = 0; + nextTriggerTime = 0; + invincibleTime = 0; +} + +/* +================ +idDamagable::Save +================ +*/ +void idDamagable::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( invincibleTime ); + + savefile->WriteInt( stage ); + savefile->WriteInt( stageNext ); + + // cnicholson: Don't save the stageDict, its setup in the restore + + savefile->WriteInt( stageEndTime ); + savefile->WriteInt( stageEndHealth ); + savefile->WriteInt( stageEndSpeed ); + savefile->WriteBool( stageEndOnGround ); + savefile->WriteBool( activateStageOnTrigger ); + + savefile->WriteInt( count ); + savefile->WriteInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Restore +================ +*/ +void idDamagable::Restore( idRestoreGame *savefile ) { + + const char* stageName; + + savefile->ReadInt( invincibleTime ); + + savefile->ReadInt( stage ); + savefile->ReadInt( stageNext ); + savefile->ReadInt( stageEndTime ); + savefile->ReadInt( stageEndHealth ); + savefile->ReadInt( stageEndSpeed ); + savefile->ReadBool( stageEndOnGround ); + savefile->ReadBool( activateStageOnTrigger ); + + savefile->ReadInt( count ); + savefile->ReadInt( nextTriggerTime ); + + + // Get the stage name, if there is none then there are no more stages + stageDict = NULL; + if ( spawnArgs.GetString ( va( "def_stage%d", stage ), "", &stageName ) && stageName ) { + + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + } +} + +/* +================ +idDamagable::Spawn +================ +*/ +void idDamagable::Spawn( void ) { + idStr broken; + bool keepContents; + +// RAVEN BEGIN +// abahr: stage stuff + stage = 0; + stageNext = 1; + stageDict = NULL; + stageEndTime = 0; + stageEndHealth = 9999; + stageEndSpeed = 0; +//jshepard: + stageEndOnGround = false; +// RAVEN END + + health = spawnArgs.GetInt( "health", "5" ); + +// RAVEN BEGIN +// abahr: stage stuff + stageEndHealth = health - 1; + activateStageOnTrigger = spawnArgs.GetBool("activateOnTrigger"); +// RAVEN END + + spawnArgs.GetInt( "count", "1", count ); + invincibleTime = gameLocal.GetTime() + SEC2MS( spawnArgs.GetFloat( "invincibleTime", "0" ) ); + nextTriggerTime = 0; + + // make sure the model gets cached + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) { + gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() ); + } + +// RAVEN BEGIN +// bdube: turn take damage off if it has no health + fl.takedamage = health > 0 ? true : false; +// RAVEN END + + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idDamagable::BecomeBroken +================ +*/ +void idDamagable::BecomeBroken( idEntity *activator ) { + float forceState; + int numStates; + int cycle; + float wait; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + spawnArgs.GetFloat( "wait", "0.1", wait ); + nextTriggerTime = gameLocal.time + SEC2MS( wait ); + if ( count > 0 ) { + count--; + if ( !count ) { + fl.takedamage = false; + } else { + health = spawnArgs.GetInt( "health", "5" ); + } + } + + idStr broken; + + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() ) { + SetModel( broken ); + } + + // offset the start time of the shader to sync it to the gameLocal time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetInt( "numstates", "1", numStates ); + spawnArgs.GetInt( "cycle", "0", cycle ); + spawnArgs.GetFloat( "forcestate", "0", forceState ); + + // set the state parm + if ( cycle ) { + renderEntity.shaderParms[ SHADERPARM_MODE ]++; + if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = 0; + } + } else if ( forceState ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState; + } else { + renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1; + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + ActivateTargets( activator ); + + if ( spawnArgs.GetBool( "hideWhenBroken" ) ) { + Hide(); + PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time ); + BecomeActive( TH_THINK ); + } +} + +/* +============ +idMoveable::Damage +============ +*/ +// RAVEN BEGIN +// abahr +void idDamagable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if ( invincibleTime > gameLocal.GetTime() ) { + return; + } + + // If there is a damage filter we need to check to see if the damage def meets the requirement + const char* damageFilter; + if ( spawnArgs.GetString ( "damage_filter", "", &damageFilter ) && *damageFilter ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + // If the filter isnt matched then ignore it + if ( !damageDef->GetBool ( va("filter_%s", damageFilter ) ) ) { + return; + } + } + if ( spawnArgs.GetBool( "nosplash", "0" ) ) { + //ignore splash damage + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + //if it has radius, then it's splash damage and ignore it + if ( damageDef->GetFloat ( "radius", "0" ) ) { + return; + } + } + + idEntity::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Force a stage update so impact effects will be correct + UpdateStage(); +} + +/* +================ +idDamagable::Killed +================ +*/ +void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + BecomeBroken( attacker ); +} + +/* +================ +idDamagable::UpdateStage +================ +*/ +void idDamagable::UpdateStage ( void ) { + + // If there is a stage to go to then see if its time to do that + if ( stage != stageNext ) { + int oldstage; + + // Check to see if the stage is complete + oldstage = stage; + while ( health < stageEndHealth || (stageEndTime != 0 && gameLocal.time > stageEndTime ) || (stageEndOnGround && GetPhysics()->HasGroundContacts()) || activateStageOnTrigger ) { + stage = stageNext; + + //this only needs to happen once + activateStageOnTrigger = false; + + // Get the stage name, if there is none then there are no more stages + const char* stageName; + if ( !spawnArgs.GetString ( va("def_stage%d", stage ), "", &stageName ) || !stageName ) { + stage = 0; + break; + } + + // Get the stage dictionary + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + if ( !stageDict ) { + gameLocal.Warning ( "could not find stage '%s' for moveable '%s'", stageName, name.c_str() ); + stage = 0; + break; + } + + // Get the end conditions of the stage + stageEndHealth = stageDict->GetInt ( "end_health", "-9999" ); + + stageEndOnGround = stageDict->GetBool( "end_onGround" ); + + // Timed? + stageEndTime = SEC2MS ( GetStageFloat ( "end_time" ) ); + if ( stageEndTime > 0 ) { + stageEndTime += gameLocal.time; + } + + // Set the next stage to move to when end conditions met + stageNext = stage + 1; + + // Execute the new stage + ExecuteStage ( ); + } + } +} + +/* +================ +idDamagable::ExecuteStage +================ +*/ +void idDamagable::ExecuteStage ( void ) { + const idKeyValue* kv; + bool remove; + + // Remove the entity this frame? + remove = stageDict->GetBool ( "remove" ); + + // Targets? + if ( stageDict->GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + // Unbind targets + if ( stageDict->GetBool( "unbindTargets" ) ) { + UnbindTargets( this ); + } + + // Stop current looping effects + StopAllEffects ( ); + + // Play all effects (if they start with "fx_loop" then also loop them + for ( kv = stageDict->MatchPrefix ( "fx_" ); kv; kv = stageDict->MatchPrefix("fx_",kv) ) { + if ( !kv->GetKey().Icmpn ( "fx_loop", 7 ) ) { + if ( !remove ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, kv->GetKey() ), + renderEntity.origin + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() * renderEntity.axis, + true ); + } + } else { + gameLocal.PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_explode" ), + GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ) * GetPhysics()->GetAxis(), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() ); + } + } + + // Kick off debris + for ( kv = stageDict->MatchPrefix ( "def_debris" ); kv; kv = stageDict->MatchPrefix("def_debris",kv) ) { + const idDict* args = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !args ) { + continue; + } + + rvClientMoveable* cent = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + continue; + } + cent->SetOrigin ( GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis() ); + cent->SetAxis ( GetPhysics()->GetAxis ( ) ); + + idVec3 vel; + vel = GetStageVector ( va("vel_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis(); + vel += GetPhysics()->GetLinearVelocity ( ); + cent->GetPhysics()->SetLinearVelocity ( vel ); + cent->PostEventMS ( &CL_FadeOut, 2500, 2500 ); + } + + // Apply force to all objects nearby + float fPush; + stageDict->GetFloat("radiusPush","0",fPush); + if (fPush > 0) { + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 128, fPush, this, this, 1.0f, true ); + } + + // Remove the entity now? + if ( remove ) { + Hide ( ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Switch model? + const char* model; + if ( stageDict->GetString ( "model", "", &model ) && *model ) { + SetModel( model ); + } + + // Skin? + const char* skin; + if ( stageDict->GetString ( "skin", "", &skin ) && *skin ) { + renderEntity.customSkin = declManager->FindSkin ( skin, false ); + } + + // Velocities + idVec3 vel; + vel = GetStageVector ( "vel_world" ); + vel += (GetStageVector ( "vel_local" ) * GetPhysics()->GetAxis() ); + vel += GetPhysics()->GetLinearVelocity ( ); + GetPhysics()->SetLinearVelocity ( vel ); + + // Enable thinking to ensure stages are run + BecomeActive ( TH_THINK ); +} + +/* +================ +idDamagable::GetStageVector +================ +*/ +idVec3 idDamagable::GetStageVector ( const char* key, const char* defaultString ) const { + idVec3 mins; + if ( stageDict->GetVector ( va("%s_min", key ), "", mins ) ) { + idVec3 maxs; + stageDict->GetVector ( va("%s_max", key), mins.ToString(), maxs ); + return mins + (maxs - mins) * gameLocal.random.RandomFloat ( ); + } + + return stageDict->GetVector ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageFloat +================ +*/ +float idDamagable::GetStageFloat ( const char* key, const char* defaultString ) const { + float minValue; + if ( stageDict->GetFloat ( va("%s_min", key ), "", minValue ) ) { + float maxValue; + stageDict->GetFloat ( va("%s_max", key), va("%g",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetFloat ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageInt +================ +*/ +int idDamagable::GetStageInt ( const char* key, const char* defaultString ) const { + int minValue; + if ( stageDict->GetInt ( va("%s_min", key ), "", minValue ) ) { + int maxValue; + stageDict->GetInt ( va("%s_max", key), va("%d",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetInt ( key, defaultString ); +} + +/* +================ +idDamagable::Event_BecomeBroken +================ +*/ +void idDamagable::Event_BecomeBroken( idEntity *activator ) { + BecomeBroken( activator ); + +// RAVEN BEGIN +// bdube: start stages + UpdateStage ( ); +// RAVEN END +} + +/* +================ +idDamagable::Event_RestoreDamagable +================ +*/ +void idDamagable::Event_RestoreDamagable( void ) { + health = spawnArgs.GetInt( "health", "5" ); + Show(); +} + + +/* +=============================================================================== + + idExplodable + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idExplodable ) + EVENT( EV_Activate, idExplodable::Event_Explode ) +END_CLASS + +/* +================ +idExplodable::Spawn +================ +*/ +void idExplodable::Spawn( void ) { + Hide(); +} + +/* +================ +idExplodable::Event_Explode +================ +*/ +void idExplodable::Event_Explode( idEntity *activator ) { + const char *temp; + + if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms + renderEntity.shaderParms[SHADERPARM_RED] = 1.0f; + renderEntity.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEntity.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = 0.0f; + Show(); + + PostEventMS( &EV_Remove, 2000 ); + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpring ) + EVENT( EV_PostSpawn, idSpring::Event_LinkSpring ) +END_CLASS + +/* +================ +idSpring::Think +================ +*/ +void idSpring::Think( void ) { + idVec3 start, end, origin; + idMat3 axis; + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + // evaluate force + spring.Evaluate( gameLocal.time ); + + start = p1; + if ( ent1 && ent1->GetPhysics() ) { + axis = ent1->GetPhysics()->GetAxis(); + origin = ent1->GetPhysics()->GetOrigin(); + start = origin + start * axis; + } + + end = p2; + if ( ent2 && ent2->GetPhysics() ) { + axis = ent2->GetPhysics()->GetAxis(); + origin = ent2->GetPhysics()->GetOrigin(); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true ); + } + + Present(); +} + +/* +================ +idSpring::Event_LinkSpring +================ +*/ +void idSpring::Event_LinkSpring( void ) { + idStr name1, name2; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + + if ( name1.Length() ) { + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() ); + } + } + else { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + if ( name2.Length() ) { + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() ); + } + } + else { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 ); + BecomeActive( TH_THINK ); +} + +/* +================ +idSpring::Spawn +================ +*/ +void idSpring::Spawn( void ) { + float Kstretch, damping, restLength; + + spawnArgs.GetInt( "id1", "0", id1 ); + spawnArgs.GetInt( "id2", "0", id2 ); + spawnArgs.GetVector( "point1", "0 0 0", p1 ); + spawnArgs.GetVector( "point2", "0 0 0", p2 ); + spawnArgs.GetFloat( "constant", "100.0f", Kstretch ); + spawnArgs.GetFloat( "damping", "10.0f", damping ); + spawnArgs.GetFloat( "restlength", "0.0f", restLength ); + + spring.InitSpring( Kstretch, 0.0f, damping, restLength ); + + ent1 = ent2 = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idSpring::Save +================ +*/ +void idSpring::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( id1 ); + savefile->WriteInt ( id2 ); + savefile->WriteVec3 ( p1 ); + savefile->WriteVec3 ( p2 ); + spring.Save ( savefile ); +} + +/* +================ +idSpring::Restore +================ +*/ +void idSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( id1 ); + savefile->ReadInt ( id2 ); + savefile->ReadVec3 ( p1 ); + savefile->ReadVec3 ( p2 ); + spring.Restore ( savefile ); + Event_LinkSpring ( ); +} + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const idEventDef EV_Toggle( "Toggle", NULL ); + +CLASS_DECLARATION( idEntity, idForceField ) + EVENT( EV_Activate, idForceField::Event_Activate ) + EVENT( EV_Toggle, idForceField::Event_Toggle ) + EVENT( EV_FindTargets, idForceField::Event_FindTargets ) +END_CLASS + +/* +=============== +idForceField::Toggle +================ +*/ +void idForceField::Toggle( void ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idForceField::Think +================ +*/ +void idForceField::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + } + Present(); +} + +/* +================ +idForceField::Save +================ +*/ +void idForceField::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( forceField ); +} + +/* +================ +idForceField::Restore +================ +*/ +void idForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( forceField ); +} + +/* +================ +idForceField::Spawn +================ +*/ +void idForceField::Spawn( void ) { + idVec3 uniform; + float explosion, implosion, randomTorque; + + if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) { + forceField.Uniform( uniform ); + } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) { + forceField.Explosion( explosion ); + } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) { + forceField.Implosion( implosion ); + } + + if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) { + forceField.RandomTorque( randomTorque ); + } + + if ( spawnArgs.GetBool( "applyForce", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_FORCE ); + } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE ); + } else { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + } + + forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) ); + forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) ); + + // set the collision model on the force field +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + forceField.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + forceField.SetOwner( this ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idForceField::Event_Toggle +================ +*/ +void idForceField::Event_Toggle( void ) { + Toggle(); +} + +/* +================ +idForceField::Event_Activate +================ +*/ +void idForceField::Event_Activate( idEntity *activator ) { + float wait; + + Toggle(); + if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) { + PostEventSec( &EV_Toggle, wait ); + } +} + +/* +================ +idForceField::Event_FindTargets +================ +*/ +void idForceField::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ); + } +} + +// RAVEN BEGIN +// bdube: jump pads + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const int JUMPPAD_EFFECT_DELAY = 100; + +CLASS_DECLARATION( idForceField, rvJumpPad ) + EVENT( EV_FindTargets, rvJumpPad::Event_FindTargets ) +END_CLASS + +/* +================ +rvJumpPad::rvJumpPad +================ +*/ +rvJumpPad::rvJumpPad ( void ) { + lastEffectTime = -1; +} + +/* +================ +rvJumpPad::Think +================ +*/ +void rvJumpPad::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + + // If force has been applied to an entity and jump pad effect hasnt been played for a bit + // then play it now. + if ( forceField.GetLastApplyTime ( ) - lastEffectTime > JUMPPAD_EFFECT_DELAY ) { + // start locally + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + + // send through unreliable + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMPFX ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( renderEntity.origin ) ); + } + + lastEffectTime = forceField.GetLastApplyTime( ); + } + } + Present(); +} + +/* +================ +rvJumpPad::Spawn +================ +*/ +void rvJumpPad::Spawn( void ) { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + forceField.SetOwner( this ); +} + +/* +================ +rvJumpPad::Event_FindTargets +================ +*/ +void rvJumpPad::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + idEntity* ent; + ent = targets[0].GetEntity(); + assert( ent ); + + idVec3 vert; + idVec3 diff; + idVec3 vel; + idVec3 localGravity( gameLocal.GetCurrentGravity(this) ); + idVec3 localGravityNormal( localGravity.ToNormal() ); + float time; + float dist; + + // Find the distance along the gravity vector between the jump pad and its target + diff = (ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()); + vert = (diff * localGravityNormal) * localGravityNormal; + + // Determine how long it would take to cover the distance along the gravity vector + time = idMath::Sqrt( vert.Length() / (0.5f * localGravity.Length()) ); + if ( !time ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // The final velocity is the direction between the jump pad and its target using + // the travel time to determine the forward vector and adding in an inverse gravity vector. + vel = diff - vert; + dist = vel.Normalize(); + + vel = vel * (dist / time); + vel += (localGravity * -time); + + forceField.Uniform( vel ); + + // calculate a coordinate axis where the vector to the jumppad target is forward + // use this to play the fx_jump fx + diff.Normalize(); + effectAxis = diff.ToMat3(); + } +} + +/* +=============== +rvJumpPad::ClientReceiveEvent +=============== +*/ +bool rvJumpPad::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_JUMPFX: { + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + return true; + } + default: + return idEntity::ClientReceiveEvent( event, time, msg ); + } +} + +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +const idEventDef EV_Animated_Start( "" ); +const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" ); +const idEventDef EV_LaunchMissilesUpdate( "", "dddd" ); +const idEventDef EV_AnimDone( "", "d" ); +const idEventDef EV_StartRagdoll( "startRagdoll" ); + +// RAVEN BEGIN +// bdube: script object +const idEventDef EV_SetAnimState ( "setAnimState", "sd" ); +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated ) + EVENT( EV_Activate, idAnimated::Event_Activate ) + EVENT( EV_Animated_Start, idAnimated::Event_Start ) + EVENT( EV_StartRagdoll, idAnimated::Event_StartRagdoll ) + EVENT( EV_AnimDone, idAnimated::Event_AnimDone ) + EVENT( EV_Footstep, idAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, idAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, idAnimated::Event_Footstep ) + EVENT( EV_LaunchMissiles, idAnimated::Event_LaunchMissiles ) + EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate ) + +// RAVEN BEGIN +// bdube: script object + EVENT( EV_SetAnimState, idAnimated::Event_SetAnimState ) + EVENT( AI_PlayAnim, idAnimated::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAnimated::Event_PlayCycle ) + EVENT( AI_AnimDone, idAnimated::Event_AnimDone2 ) +// RAVEN END + +END_CLASS + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::idAnimated() { + int i; + + anim = 0; + blendFrames = 0; + soundJoint = INVALID_JOINT; + activated = false; + combatModel = NULL; + activator = NULL; + current_anim_index = 0; + num_anims = 0; +// RAVEN BEGIN +// bdube: script control + scriptThread = NULL; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + animDoneTime[i] = 0; + } +// RAVEN END +} + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::~idAnimated() { + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN +// bdube: kill script object + delete scriptThread; +// RAVEN END +} + +/* +=============== +idAnimated::Save +================ +*/ +void idAnimated::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( num_anims ); + savefile->WriteInt( current_anim_index ); + savefile->WriteInt( anim ); + savefile->WriteInt( blendFrames ); + savefile->WriteJoint( soundJoint ); + activator.Save( savefile ); + savefile->WriteBool( activated ); + +// RAVEN BEGIN + savefile->WriteObject( scriptThread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Restore +================ +*/ +void idAnimated::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadInt( num_anims ); + savefile->ReadInt( current_anim_index ); + savefile->ReadInt( anim ); + savefile->ReadInt( blendFrames ); + savefile->ReadJoint( soundJoint ); + activator.Restore( savefile ); + savefile->ReadBool( activated ); + +// RAVEN BEGIN + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Spawn +================ +*/ +void idAnimated::Spawn( void ) { + idStr animname; + int anim2; + float wait; + const char *joint; + + joint = spawnArgs.GetString( "sound_bone", "origin" ); + soundJoint = animator.GetJointHandle( joint ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint ); + } + + LoadAF( NULL ); + + // allow bullets to collide with a combat model + if ( spawnArgs.GetBool( "combatModel", "0" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + // allow the entity to take damage + if ( spawnArgs.GetBool( "takeDamage", "0" ) ) { + fl.takedamage = true; + } + + blendFrames = 0; + +// RAVEN BEGIN +// bdube: script control + // setup script object + const char* scriptObjectName; + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + const function_t *constructor; + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + scriptThread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + scriptThread->ManualDelete(); + scriptThread->ManualControl(); + scriptThread->SetThreadName( name.c_str() ); + scriptThread->CallFunction( this, constructor, true ); + scriptThread->Execute ( ); + } + + fl.takedamage = true; + + return; + } +// RAVEN END + + current_anim_index = 0; + spawnArgs.GetInt( "num_anims", "0", num_anims ); + + blendFrames = spawnArgs.GetInt( "blend_in" ); + + animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" ); + if ( !animname.Length() ) { + anim = 0; + } else { + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + } + + if ( spawnArgs.GetBool( "hide" ) ) { + Hide(); + + if ( !num_anims ) { + blendFrames = 0; + } + } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) { + anim2 = animator.GetAnim( animname ); + if ( !anim2 ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 ); + } else if ( anim ) { + // init joints to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + + if ( !num_anims ) { + blendFrames = 0; + } + } + + spawnArgs.GetFloat( "wait", "-1", wait ); + + if ( wait >= 0 ) { + PostEventSec( &EV_Activate, wait, this ); + } +} + +/* +=============== +idAnimated::LoadAF +=============== +*/ +bool idAnimated::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = renderEntity.axis; + return true; +} + +/* +================ +idAnimated::StartRagdoll +================ +*/ +bool idAnimated::StartRagdoll( void ) { + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable any collision model used + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + return true; +} + +/* +===================== +idAnimated::PlayNextAnim +===================== +*/ +void idAnimated::PlayNextAnim( void ) { + const char *animname; + int len; + int cycle; + + if ( current_anim_index >= num_anims ) { + Hide(); + if ( spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } else { + current_anim_index = 0; + } + return; + } + + Show(); + current_anim_index++; + + spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname ); + if ( !animname ) { + anim = 0; + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) ); + return; + } + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + spawnArgs.GetInt( "cycle", "1", cycle ); + if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) { + cycle = -1; + } + + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, current_anim_index ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_StartRagdoll +================ +*/ +void idAnimated::Event_StartRagdoll( void ) { + StartRagdoll(); +} + +/* +=============== +idAnimated::Event_AnimDone +================ +*/ +void idAnimated::Event_AnimDone( int animindex ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + + if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "auto_advance" ) ) { + PlayNextAnim(); + } else { + activated = false; + } + + ActivateTargets( activator.GetEntity() ); +} + +/* +=============== +idAnimated::Event_Activate +================ +*/ +void idAnimated::Event_Activate( idEntity *_activator ) { +// RAVEN BEGIN +// bdube: script object support + if ( scriptThread ) { + CallHandler ( "onActivate" ); + return; + } +// RAVEN END + + if ( num_anims ) { + PlayNextAnim(); + activator = _activator; + return; + } + + if ( activated ) { + // already activated + return; + } + + activated = true; + activator = _activator; + ProcessEvent( &EV_Animated_Start ); +} + +/* +=============== +idAnimated::Event_Start +================ +*/ +void idAnimated::Event_Start( void ) { + int cycle; + int len; + + Show(); + + if ( num_anims ) { + PlayNextAnim(); + return; + } + + if ( anim ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + spawnArgs.GetInt( "cycle", "1", cycle ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, 1 ); + } + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_Footstep +=============== +*/ +void idAnimated::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +===================== +idAnimated::Event_LaunchMissilesUpdate +===================== +*/ +void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) { + idVec3 launchPos; + idVec3 targetPos; + idMat3 axis; + idVec3 dir; + idEntity * ent; + idProjectile * projectile; + const idDict * projectileDef; + const char * projectilename; + + projectilename = spawnArgs.GetString( "projectilename" ); + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL ); + + animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis ); + launchPos = renderEntity.origin + launchPos * renderEntity.axis; + +// RAVEN BEGIN +// bdube: with no target bone it will just shoot out the direction of the bones orientation + if ( targetjoint != INVALID_JOINT ) { + animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis ); + targetPos = renderEntity.origin + targetPos * renderEntity.axis; + dir = targetPos - launchPos; + } else { + axis *= renderEntity.axis; + dir = axis[0]; + } +// RAVEN END + + dir.Normalize(); + + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + } + projectile = ( idProjectile * )ent; + projectile->Create( this, launchPos, dir ); + projectile->Launch( launchPos, dir, vec3_origin ); + + if ( numshots > 0 ) { + PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay ); + } +} + +/* +===================== +idAnimated::Event_LaunchMissiles +===================== +*/ +void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) { + const idDict * projectileDef; + jointHandle_t launch; + jointHandle_t target; + + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + launch = animator.GetJointHandle( launchjoint ); + if ( launch == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint ); + gameLocal.Error( "Unknown joint '%s'", launchjoint ); + } + + target = animator.GetJointHandle( targetjoint ); +// RAVEN BEGIN +// bdube: invalid is ok now, it means shoot out the direction of the bone +// if ( target == INVALID_JOINT ) { +// gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint ); +// } +// RAVEN END + + spawnArgs.Set( "projectilename", projectilename ); + spawnArgs.Set( "missilesound", sound ); + + CancelEvents( &EV_LaunchMissilesUpdate ); +// RAVEN BEGIN +// nmckenzie: Do this now. No delays. No event loops or updates. Just a straightforward DO THIS. Tacky. + if ( numshots == 1 && framedelay == 0 ){ + Event_LaunchMissilesUpdate ( launch, target, 1, 0 ); + } + else{ + ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay ); + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idAnimated::ShouldConstructScriptObjectAtSpawn +===================== +*/ +bool idAnimated::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Think ( void ) { + UpdateScript ( ); + idAFEntity_Gibbable::Think ( ); +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Damage ( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if ( !scriptThread ) { + return; + } + + CallHandler ( "onDamage" ); +} + +/* +===================== +idAnimated::Event_PlayAnim +===================== +*/ +void idAnimated::Event_PlayAnim( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animDoneTime[channel] - gameLocal.time ) ); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_PlayCycle +=============== +*/ +void idAnimated::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_AnimDone2 +=============== +*/ +void idAnimated::Event_AnimDone2( int channel, int blend ) { + if ( animDoneTime[channel] - FRAME2MS( blend ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idAnimated::Event_SetAnimState +=============== +*/ +void idAnimated::Event_SetAnimState ( const char* statename, int blend ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + idealState = statename; + blendFrames = blend; + scriptThread->DoneProcessing(); +} + +/* +================ +idAnimated::UpdateScript +================ +*/ +void idAnimated::UpdateScript( void ) { + int count; + + if ( !scriptThread || !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + + // If no state has been set then dont execute nothing + if ( !state.Length ( ) || scriptThread->IsWaiting() ) { + return; + } + + // update script state + count = 10; + while( ( scriptThread->Execute() || idealState.Length() ) && count-- ) { + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + } +} + +/* +===================== +idAnimated::CallHandler +===================== +*/ +void idAnimated::CallHandler ( const char* handler ) { + const function_t *func; + func = scriptObject.GetFunction( handler ); + if ( !func ) { + return; + } + + scriptThread->CallFunction( this, func, false ); + scriptThread->Execute ( ); +} + +/* +===================== +idAnimated::SetState +===================== +*/ +void idAnimated::SetState( const char *statename, int frames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + scriptThread->CallFunction( this, func, true ); + state = statename; + blendFrames = frames; + idealState = ""; +} + +// RAVEN END + +/* +=============================================================================== + + idStaticEntity + + Some static entities may be optimized into inline geometry by dmap + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idStaticEntity ) + EVENT( EV_Activate, idStaticEntity::Event_Activate ) +END_CLASS + +/* +=============== +idStaticEntity::idStaticEntity +=============== +*/ +idStaticEntity::idStaticEntity( void ) { + spawnTime = 0; + active = false; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + runGui = false; +} + +/* +=============== +idStaticEntity::Save +=============== +*/ +void idStaticEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnTime ); + savefile->WriteBool( active ); + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( runGui ); +} + +/* +=============== +idStaticEntity::Restore +=============== +*/ +void idStaticEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnTime ); + savefile->ReadBool( active ); + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( runGui ); +} + +/* +=============== +idStaticEntity::Spawn +=============== +*/ +void idStaticEntity::Spawn( void ) { + bool solid; + bool hidden; + bool keepContents; + +// RAVEN BEGIN +// rjohnson: inline models can be solid, since the entities hang around anyway (don't know why it was done that way) + solid = spawnArgs.GetBool( "solid" ); + + // an inline static model will not do anything at all + if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) { + Hide(); + if ( solid ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + return; + } +// RAVEN END + + hidden = spawnArgs.GetBool( "hide" ); + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else if ( solid && !hidden ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + GetPhysics()->SetContents( 0 ); + } + + spawnTime = gameLocal.time; + active = false; + + idStr model = spawnArgs.GetString( "model" ); + // FIXME: temp also catch obsolete .ips extension + if ( model.Find( ".ips" ) >= 0 || model.Find( ".prt" ) >= 0 ) { + // we want the parametric particles out of sync with each other + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 ); + } + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // NOTE: this should be used very rarely because it is expensive + runGui = spawnArgs.GetBool( "runGui" ); + if ( runGui ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idStaticEntity::ShowEditingDialog +================ +*/ +void idStaticEntity::ShowEditingDialog( void ) { +// RAVEN BEGIN +// bdube: not using +// common->InitTool( EDITOR_PARTICLE, &spawnArgs ); +// RAVEN END +} +/* +================ +idStaticEntity::Think +================ +*/ +void idStaticEntity::Think( void ) { + idEntity::Think(); + if ( thinkFlags & TH_THINK ) { + if ( runGui && renderEntity.gui[0] ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->objectiveSystemOpen ) { + for( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } + } + } + } + if ( fadeEnd > 0 ) { + idVec4 color; + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } +} + +/* +================ +idStaticEntity::Fade +================ +*/ +void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idStaticEntity::Hide +================ +*/ +void idStaticEntity::Hide( void ) { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idStaticEntity::Show +================ +*/ +void idStaticEntity::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid" ) ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idStaticEntity::Event_Activate +================ +*/ +void idStaticEntity::Event_Activate( idEntity *activator ) { + idStr activateGui; + + spawnTime = gameLocal.time; + active = !active; + + const idKeyValue *kv = spawnArgs.FindKey( "hide" ); + if ( kv ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime ); + renderEntity.shaderParms[5] = active; + // this change should be a good thing, it will automatically turn on + // lights etc.. when triggered so that does not have to be specifically done + // with trigger parms.. it MIGHT break things so need to keep an eye on it + renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ? 0.0f : 1.0f; + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idStaticEntity::WriteToSnapshot +================ +*/ +void idStaticEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + WriteGUIToSnapshot( msg ); + msg.WriteBits( IsHidden()?1:0, 1 ); +} + +/* +================ +idStaticEntity::ReadFromSnapshot +================ +*/ +void idStaticEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool hidden; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + hidden = msg.ReadBits( 1 ) == 1; + if ( hidden != IsHidden() ) { + if ( hidden ) { + Hide(); + } else { + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncEmitter ) +EVENT( EV_Activate, idFuncEmitter::Event_Activate ) +END_CLASS + +/* +=============== +idFuncEmitter::idFuncEmitter +=============== +*/ +idFuncEmitter::idFuncEmitter( void ) { + hidden = false; +} + +/* +=============== +idFuncEmitter::Spawn +=============== +*/ +void idFuncEmitter::Spawn( void ) { + if ( spawnArgs.GetBool( "start_off" ) ) { + hidden = true; + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 ); + UpdateVisuals(); + } else { + hidden = false; + } +} + +/* +=============== +idFuncEmitter::Save +=============== +*/ +void idFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteBool( hidden ); +} + +/* +=============== +idFuncEmitter::Restore +=============== +*/ +void idFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( hidden ); +} + +/* +================ +idFuncEmitter::Event_Activate +================ +*/ +void idFuncEmitter::Event_Activate( idEntity *activator ) { + if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + hidden = false; + } else { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + hidden = true; + } + UpdateVisuals(); +} + +/* +================ +idFuncEmitter::WriteToSnapshot +================ +*/ +void idFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( hidden ? 1 : 0, 1 ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncEmitter::ReadFromSnapshot +================ +*/ +void idFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hidden = msg.ReadBits( 1 ) != 0; + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + + +const idEventDef EV_Splat( "" ); +CLASS_DECLARATION( idFuncEmitter, idFuncSplat ) +EVENT( EV_Activate, idFuncSplat::Event_Activate ) +EVENT( EV_Splat, idFuncSplat::Event_Splat ) +END_CLASS + +/* +=============== +idFuncSplat::idFuncSplat +=============== +*/ +idFuncSplat::idFuncSplat( void ) { +} + +/* +=============== +idFuncSplat::Spawn +=============== +*/ +void idFuncSplat::Spawn( void ) { +} + +/* +================ +idFuncSplat::Event_Splat +================ +*/ +void idFuncSplat::Event_Splat( void ) { + const char *splat = NULL; + int count = spawnArgs.GetInt( "splatCount", "1" ); + for ( int i = 0; i < count; i++ ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + if ( splat && *splat ) { + float size = spawnArgs.GetFloat( "splatSize", "128" ); + float dist = spawnArgs.GetFloat( "splatDistance", "128" ); + float angle = spawnArgs.GetFloat( "splatAngle", "0" ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle ); + } + } + StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +idFuncSplat::Event_Activate +================ +*/ +void idFuncSplat::Event_Activate( idEntity *activator ) { + idFuncEmitter::Event_Activate( activator ); + PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) ); + StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +=============================================================================== + + idTextEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTextEntity ) +END_CLASS + +/* +================ +idTextEntity::Spawn +================ +*/ +void idTextEntity::Spawn( void ) { + // these are cached as the are used each frame + text = spawnArgs.GetString( "text" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + bool force = spawnArgs.GetBool( "force" ); + if ( developer.GetBool() || force ) { + BecomeActive(TH_THINK); + } +} + +/* +================ +idTextEntity::Save +================ +*/ +void idTextEntity::Save( idSaveGame *savefile ) const { + savefile->WriteString( text ); + savefile->WriteBool( playerOriented ); +} + +/* +================ +idTextEntity::Restore +================ +*/ +void idTextEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadString( text ); + savefile->ReadBool( playerOriented ); +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::Think( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::ClientPredictionThink( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + + + +/* +=============================================================================== + + idVacuumSeperatorEntity + + Can be triggered to let vacuum through a portal (blown out window) + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity ) + EVENT( EV_Activate, idVacuumSeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +idVacuumSeparatorEntity::idVacuumSeparatorEntity +================ +*/ +idVacuumSeparatorEntity::idVacuumSeparatorEntity( void ) { + portal = 0; +} + +/* +================ +idVacuumSeparatorEntity::Save +================ +*/ +void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +idVacuumSeparatorEntity::Restore +================ +*/ +void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +idVacuumSeparatorEntity::Spawn +================ +*/ +void idVacuumSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION ); +} + +/* +================ +idVacuumSeparatorEntity::Event_Activate +================ +*/ +void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_NONE ); +} + + +/* +=============================================================================== + +idLocationSeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationSeparatorEntity ) +END_CLASS + +/* +================ +idLocationSeparatorEntity::Spawn +================ +*/ +void idLocationSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + qhandle_t portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + } + gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION ); +} + + +/* +=============================================================================== + + idVacuumEntity + + Levels should only have a single vacuum entity. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumEntity ) +END_CLASS + +/* +================ +idVacuumEntity::Spawn +================ +*/ +void idVacuumEntity::Spawn() { + if ( gameLocal.vacuumAreaNum != -1 ) { + gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" ); + return; + } + + idVec3 org = spawnArgs.GetVector( "origin" ); + + gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org ); +} + +// RAVEN BEGIN +// abahr: +/* +=============================================================================== + + rvGravitySeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvGravitySeparatorEntity ) + EVENT( EV_Activate, rvGravitySeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +rvGravitySeparatorEntity::idVacuumSeparatorEntity +================ +*/ +rvGravitySeparatorEntity::rvGravitySeparatorEntity( void ) { + portal = 0; +} + +/* +================ +rvGravitySeparatorEntity::Save +================ +*/ +void rvGravitySeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +rvGravitySeparatorEntity::Restore +================ +*/ +void rvGravitySeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +rvGravitySeparatorEntity::Spawn +================ +*/ +void rvGravitySeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "GravitySeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, gameRenderWorld->GetPortalState(portal) | PS_BLOCK_GRAVITY ); +} + +/* +================ +rvGravitySeparatorEntity::Event_Activate +================ +*/ +void rvGravitySeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + + int portalState = gameRenderWorld->GetPortalState( portal ); + gameLocal.SetPortalState( portal, (portalState & PS_BLOCK_GRAVITY) > 0 ? (portalState & ~PS_BLOCK_GRAVITY) : portalState | PS_BLOCK_GRAVITY ); +} + +/* +=============================================================================== + + rvGravityEntity + +=============================================================================== +*/ +ABSTRACT_DECLARATION( idEntity, rvGravityArea ) +END_CLASS + +/* +================ +rvGravityArea::Spawn +================ +*/ +void rvGravityArea::Spawn() { + area = gameRenderWorld->PointInArea( spawnArgs.GetVector("origin") ); + gameLocal.AddUniqueGravityInfo( this ); +} + +/* +================ +rvGravityArea::Save +================ +*/ +void rvGravityArea::Save( idSaveGame *savefile ) const { + savefile->WriteInt( area ); +} + +/* +================ +rvGravityArea::Restore +================ +*/ +void rvGravityArea::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( area ); +} + +/* +================ +rvGravityArea::IsEqualTo +================ +*/ +bool rvGravityArea::IsEqualTo( const rvGravityArea* area ) const { + assert( area ); + return !idStr::Icmp( GetName(), area->GetName() ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea* area ) const { + return IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea& area ) const { + return IsEqualTo( &area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea* area ) const { + return !IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea& area ) const { + return !IsEqualTo( &area ); +} + +/* +=============================================================================== + + rvGravityEntity_Static + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_Static ) +END_CLASS + +/* +================ +rvGravityArea_Static::Spawn +================ +*/ +void rvGravityArea_Static::Spawn() { + gravity = spawnArgs.GetVector( "gravity" );// * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_Static::Save +================ +*/ +void rvGravityArea_Static::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( gravity ); +} + +/* +================ +rvGravityArea_Static::Restore +================ +*/ +void rvGravityArea_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( gravity ); +} + +/* +=============================================================================== + + rvGravityArea_SurfaceNormal + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_SurfaceNormal ) +END_CLASS + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { + trace_t results; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if( !gameLocal.TracePoint( this, results, origin, origin + -axis[2] * 50.0f, clipMask, passEntity ) ) { +// RAVEN END + return gameLocal.GetGravity(); + } + + return -results.c.normal * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const rvClientEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idPhysics* physics ) const { + return GetGravity( physics->GetOrigin(), physics->GetAxis(), physics->GetClipMask(), physics->GetSelf() ); +} +// RAVEN END + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationEntity ) +END_CLASS + +/* +====================== +idLocationEntity::Spawn +====================== +*/ +void idLocationEntity::Spawn() { + idStr realName; + + // this just holds dict information + + // if "location" not already set, use the entity name. + if ( !spawnArgs.GetString( "location", "", realName ) ) { + spawnArgs.Set( "location", name ); + } +} + +/* +====================== +idLocationEntity::GetLocation +====================== +*/ +const char *idLocationEntity::GetLocation( void ) const { + return spawnArgs.GetString( "location" ); +} + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idBeam ) + EVENT( EV_PostSpawn, idBeam::Event_MatchTarget ) + EVENT( EV_Activate, idBeam::Event_Activate ) +END_CLASS + +/* +=============== +idBeam::idBeam +=============== +*/ +idBeam::idBeam() { + target = NULL; + master = NULL; +} + +/* +=============== +idBeam::Save +=============== +*/ +void idBeam::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + master.Save( savefile ); +} + +/* +=============== +idBeam::Restore +=============== +*/ +void idBeam::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + master.Restore( savefile ); +} + +/* +=============== +idBeam::Spawn +=============== +*/ +void idBeam::Spawn( void ) { + float width; + + if ( spawnArgs.GetFloat( "width", "0", width ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width; + } + + SetModel( "_BEAM" ); + Hide(); + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idBeam::Think +================ +*/ +void idBeam::Think( void ) { + idBeam *masterEnt; + + if ( !IsHidden() && !target.GetEntity() ) { + // hide if our target is removed + Hide(); + } + + RunPhysics(); + + masterEnt = master.GetEntity(); + if ( masterEnt ) { + const idVec3 &origin = GetPhysics()->GetOrigin(); + masterEnt->SetBeamTarget( origin ); + } + Present(); +} + +/* +================ +idBeam::SetMaster +================ +*/ +void idBeam::SetMaster( idBeam *masterbeam ) { + master = masterbeam; +} +/* +================ +idBeam::SetBeamTarget +================ +*/ +void idBeam::SetBeamTarget( const idVec3 &origin ) { + if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z; + UpdateVisuals(); + } +} + +/* +================ +idBeam::Show +================ +*/ +void idBeam::Show( void ) { + idBeam *targetEnt; + + idEntity::Show(); + + targetEnt = target.GetEntity(); + if ( targetEnt ) { + const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin(); + SetBeamTarget( origin ); + } +} + +/* +================ +idBeam::Event_MatchTarget +================ +*/ +void idBeam::Event_MatchTarget( void ) { + int i; + idEntity *targetEnt; + idBeam *targetBeam; + + if ( !targets.Num() ) { + return; + } + + targetBeam = NULL; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idBeam::Type ) ) { + targetBeam = static_cast( targetEnt ); + break; + } + } + + if ( !targetBeam ) { + gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() ); + } + + target = targetBeam; + targetBeam->SetMaster( this ); + if ( !spawnArgs.GetBool( "start_off" ) ) { + Show(); + } +} + +/* +================ +idBeam::Event_Activate +================ +*/ +void idBeam::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } +} + +/* +================ +idBeam::WriteToSnapshot +================ +*/ +void idBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] ); +} + +/* +================ +idBeam::ReadFromSnapshot +================ +*/ +void idBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLiquid ) + EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + +/* +================ +idLiquid::Save +================ +*/ +void idLiquid::Save( idSaveGame *savefile ) const { + // Nothing to save + // cnicholson: It says nothing to save here, no mention of *model +} + +/* +================ +idLiquid::Restore +================ +*/ +void idLiquid::Restore( idRestoreGame *savefile ) { + //FIXME: NO! + Spawn(); +} + +/* +================ +idLiquid::Spawn +================ +*/ +void idLiquid::Spawn() { +/* + model = dynamic_cast( renderEntity.hModel ); + if ( !model ) { + gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() ); + } + model->Reset(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +*/ +} + +/* +================ +idLiquid::Event_Touch +================ +*/ +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { + // FIXME: for QuakeCon +/* + idVec3 pos; + + pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); +*/ +} + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idShaking ) + EVENT( EV_Activate, idShaking::Event_Activate ) +END_CLASS + +/* +=============== +idShaking::idShaking +=============== +*/ +idShaking::idShaking() { + active = false; +} + + +idShaking::~idShaking() { + SetPhysics( NULL ); +} + +/* +=============== +idShaking::Save +=============== +*/ +void idShaking::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idShaking::Restore +=============== +*/ +void idShaking::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +=============== +idShaking::Spawn +=============== +*/ +void idShaking::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + SetPhysics( &physicsObj ); + + active = false; + if ( !spawnArgs.GetBool( "start_off" ) ) { + BeginShaking(); + } +} + +/* +================ +idShaking::BeginShaking +================ +*/ +void idShaking::BeginShaking( void ) { + int phase; + idAngles shake; + int period; + + active = true; + phase = gameLocal.random.RandomInt( 1000 ); + shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" ); + period = spawnArgs.GetFloat( "period", "0.05" ) * 1000; + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero ); +} + +/* +================ +idShaking::Event_Activate +================ +*/ +void idShaking::Event_Activate( idEntity *activator ) { + if ( !active ) { + BeginShaking(); + } else { + active = false; + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero ); + } +} + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ +idCVar g_earthquake( "g_earthquake", "1", 0, "controls earthquake effect" ); + +CLASS_DECLARATION( idEntity, idEarthQuake ) + EVENT( EV_Activate, idEarthQuake::Event_Activate ) +END_CLASS + +/* +=============== +idEarthQuake::idEarthQuake +=============== +*/ +idEarthQuake::idEarthQuake() { + wait = 0.0f; + random = 0.0f; + nextTriggerTime = 0; + shakeStopTime = 0; + triggered = false; + playerOriented = false; + disabled = false; + shakeTime = 0.0f; +} + +/* +=============== +idEarthQuake::Save +=============== +*/ +void idEarthQuake::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextTriggerTime ); + savefile->WriteInt( shakeStopTime ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteBool( triggered ); + savefile->WriteBool( playerOriented ); + savefile->WriteBool( disabled ); + savefile->WriteFloat( shakeTime ); +} + +/* +=============== +idEarthQuake::Restore +=============== +*/ +void idEarthQuake::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( nextTriggerTime ); + savefile->ReadInt( shakeStopTime ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadBool( triggered ); + savefile->ReadBool( playerOriented ); + savefile->ReadBool( disabled ); + savefile->ReadFloat( shakeTime ); + + if ( shakeStopTime > gameLocal.time ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idEarthQuake::Spawn +=============== +*/ +void idEarthQuake::Spawn( void ) { + nextTriggerTime = 0; + shakeStopTime = 0; + wait = spawnArgs.GetFloat( "wait", "15" ); + random = spawnArgs.GetFloat( "random", "5" ); + triggered = spawnArgs.GetBool( "triggered" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + disabled = false; + shakeTime = spawnArgs.GetFloat( "shakeTime", "0" ); + + if ( !triggered ){ + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this ); + } + BecomeInactive( TH_THINK ); +} + +/* +================ +idEarthQuake::Event_Activate +================ +*/ +void idEarthQuake::Event_Activate( idEntity *activator ) { + + if ( nextTriggerTime > gameLocal.time ) { + return; + } + + if ( disabled && activator == this ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + nextTriggerTime = 0; + + if ( !triggered && activator != this ){ + // if we are not triggered ( i.e. random ), disable or enable + disabled ^= 1; + if (disabled) { + return; + } else { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } + } + + ActivateTargets( activator ); + + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) ); + if ( playerOriented ) { + player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } else { + StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off + AffectNearbyEntities(spawnArgs.GetFloat("affectEntities", "0")); +// RAVEN END + + if ( shakeTime > 0.0f ) { + shakeStopTime = gameLocal.time + SEC2MS( shakeTime ); + BecomeActive( TH_THINK ); + } + + if ( wait > 0.0f ) { + if ( !triggered ) { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } else { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } + } else if ( shakeTime == 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round, earthquake, etc goes off +void idEarthQuake::AffectNearbyEntities(float affectRadius) +{ + if( !g_earthquake.GetBool() ) + { + return; + } + + if (affectRadius == 0) + { + return; + } + idVec3 affectOrigin(GetPhysics()->GetOrigin()); + + if (playerOriented) + { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) + { + return; + } + affectOrigin = player->GetPhysics()->GetOrigin(); + } + + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities = 0; + idEntity *curEnt = NULL; + bool requiresLOS = spawnArgs.GetBool("requiresLOS", "false"); + + // get all entities touching the bounds + numListedEntities = gameLocal.EntitiesWithinRadius(affectOrigin, affectRadius, entityList, MAX_GENTITIES ); + for (int i = 0; i < numListedEntities; i++) + { + // only affect certain entities. first check if they have the "quakeChance" key/value, then send them an earthquake event + curEnt = entityList[i]; + if (curEnt && curEnt->spawnArgs.GetFloat("quakeChance")) + { + curEnt->PostEventMS(&EV_Earthquake, 0, (float)requiresLOS); + } + } +} +// RAVEN END + +/* +=============== +idEarthQuake::Think +================ +*/ +void idEarthQuake::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( gameLocal.time > shakeStopTime ) { + BecomeInactive( TH_THINK ); + if ( wait <= 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } + return; + } + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, gameLocal.GetLocalPlayer()->firstPersonViewOrigin ); + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true ); + } + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncPortal ) + EVENT( EV_Activate, idFuncPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncPortal::idFuncPortal +=============== +*/ +idFuncPortal::idFuncPortal() { + portal = 0; + state = false; +} + +/* +=============== +idFuncPortal::Save +=============== +*/ +void idFuncPortal::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteBool( state ); +} + +/* +=============== +idFuncPortal::Restore +=============== +*/ +void idFuncPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)portal ); + savefile->ReadBool( state ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); +} + +/* +=============== +idFuncPortal::Spawn +=============== +*/ +void idFuncPortal::Spawn( void ) { + portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) ); + if ( portal > 0 ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +================ +idFuncPortal::Event_Activate +================ +*/ +void idFuncPortal::Event_Activate( idEntity *activator ) { + if ( portal > 0 ) { + state = !state; + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASPortal ) + EVENT( EV_Activate, idFuncAASPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASPortal::idFuncAASPortal +=============== +*/ +idFuncAASPortal::idFuncAASPortal() { + state = false; +} + +/* +=============== +idFuncAASPortal::Save +=============== +*/ +void idFuncAASPortal::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASPortal::Restore +=============== +*/ +void idFuncAASPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============== +idFuncAASPortal::Spawn +=============== +*/ +void idFuncAASPortal::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +================ +idFuncAASPortal::Event_Activate +================ +*/ +void idFuncAASPortal::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASObstacle ) + EVENT( EV_Activate, idFuncAASObstacle::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASObstacle::idFuncAASObstacle +=============== +*/ +idFuncAASObstacle::idFuncAASObstacle() { + state = false; +} + +/* +=============== +idFuncAASObstacle::Save +=============== +*/ +void idFuncAASObstacle::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASObstacle::Restore +=============== +*/ +void idFuncAASObstacle::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============== +idFuncAASObstacle::Spawn +=============== +*/ +void idFuncAASObstacle::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::Event_Activate +================ +*/ +void idFuncAASObstacle::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::SetState +================ +*/ +void idFuncAASObstacle::SetState ( bool _state ) { + state = _state; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +const idEventDef EV_ResetRadioHud( "", "e" ); +idEntityPtr idFuncRadioChatter::lastRadioChatter = 0; + +CLASS_DECLARATION( idEntity, idFuncRadioChatter ) +EVENT( EV_Activate, idFuncRadioChatter::Event_Activate ) +EVENT( EV_ResetRadioHud, idFuncRadioChatter::Event_ResetRadioHud ) +EVENT( EV_IsActive, idFuncRadioChatter::Event_IsActive ) +END_CLASS + +/* +=============== +idFuncRadioChatter::idFuncRadioChatter +=============== +*/ +idFuncRadioChatter::idFuncRadioChatter() { + time = 0.0f; + isActive = false; +} + +/* +=============== +idFuncRadioChatter::Save +=============== +*/ +void idFuncRadioChatter::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( time ); + savefile->WriteBool( isActive ); // cnicholson: Added unsaved var + lastRadioChatter.Save ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Restore +=============== +*/ +void idFuncRadioChatter::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( time ); + savefile->ReadBool( isActive ); // cnicholson: Added unrestored var + lastRadioChatter.Restore ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Spawn +=============== +*/ +void idFuncRadioChatter::Spawn( void ) { + time = spawnArgs.GetFloat( "time", "5.0" ); +} + +/* +================ +idFuncRadioChatter::Event_Activate +================ +*/ +void idFuncRadioChatter::Event_Activate( idEntity *activator ) { + idPlayer *player; + const char *sound; + const idSoundShader *shader; + int length; + bool isDirectedAtPlayer = false; + + lastRadioChatter = this; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + player = static_cast( activator ); + } else { + player = gameLocal.GetLocalPlayer(); + } + +// RAVEN BEGIN +// bdube: replaced named event with method + player->StartRadioChatter(); +// twhitaker: for isActive() script event + isActive = true; +// RAVEN END + + sound = spawnArgs.GetString( "snd_radiochatter", "" ); + if ( sound && *sound ) { + shader = declManager->FindSound( sound ); + if ( shader && shader->IsVO_ForPlayer()){ + isDirectedAtPlayer = true; + } + player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length ); + time = MS2SEC( length + 150 ); + } + + if ( player && player->GetHud() ) { + player->GetHud()->SetStateBool( "rhinochatter", isDirectedAtPlayer ); + } + + // we still put the hud up because this is used with no sound on + // certain frame commands when the chatter is triggered + PostEventSec( &EV_ResetRadioHud, time, player ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + idPlayer *player = ( activator->IsType( idPlayer::GetClassType() ) ) ? static_cast( activator ) : gameLocal.GetLocalPlayer(); +// RAVEN END +// RAVEN BEGIN +// bdube: replaced named event with method + player->StopRadioChatter(); +// twhitaker: for isActive() script event + isActive = false; +// RAVEN END + ActivateTargets( activator ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_IsActive( void ) { + idThread::ReturnFloat( static_cast< float >( isActive ) ); +} + +/* +================ +idFuncRadioChatter::RepeatLast +================ +*/ +void idFuncRadioChatter::RepeatLast( void ) { + if ( lastRadioChatter ) { + lastRadioChatter->Event_Activate( gameLocal.GetLocalPlayer() ); + } +} + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPhantomObjects ) + EVENT( EV_Activate, idPhantomObjects::Event_Activate ) +END_CLASS + +/* +=============== +idPhantomObjects::idPhantomObjects +=============== +*/ +idPhantomObjects::idPhantomObjects() { + target = NULL; + end_time = 0; + throw_time = 0.0f; + shake_time = 0.0f; + shake_ang.Zero(); + speed = 0.0f; + min_wait = 0; + max_wait = 0; + fl.neverDormant = false; +} + +/* +=============== +idPhantomObjects::Save +=============== +*/ +void idPhantomObjects::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( end_time ); + savefile->WriteFloat( throw_time ); + savefile->WriteFloat( shake_time ); + savefile->WriteVec3( shake_ang ); + savefile->WriteFloat( speed ); + savefile->WriteInt( min_wait ); + savefile->WriteInt( max_wait ); + target.Save( savefile ); + savefile->WriteInt( targetTime.Num() ); + for( i = 0; i < targetTime.Num(); i++ ) { + savefile->WriteInt( targetTime[ i ] ); + } + + for( i = 0; i < lastTargetPos.Num(); i++ ) { + savefile->WriteVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Restore +=============== +*/ +void idPhantomObjects::Restore( idRestoreGame *savefile ) { + int num; + int i; + + savefile->ReadInt( end_time ); + savefile->ReadFloat( throw_time ); + savefile->ReadFloat( shake_time ); + savefile->ReadVec3( shake_ang ); + savefile->ReadFloat( speed ); + savefile->ReadInt( min_wait ); + savefile->ReadInt( max_wait ); + target.Restore( savefile ); + + savefile->ReadInt( num ); + targetTime.SetGranularity( 1 ); + targetTime.SetNum( num ); + lastTargetPos.SetGranularity( 1 ); + lastTargetPos.SetNum( num ); + + for( i = 0; i < num; i++ ) { + savefile->ReadInt( targetTime[ i ] ); + } + + if ( savefile->GetBuildNumber() == INITIAL_RELEASE_BUILD_NUMBER ) { + // these weren't saved out in the first release + for( i = 0; i < num; i++ ) { + lastTargetPos[ i ].Zero(); + } + } else { + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( lastTargetPos[ i ] ); + } + } +} + +/* +=============== +idPhantomObjects::Spawn +=============== +*/ +void idPhantomObjects::Spawn( void ) { + throw_time = spawnArgs.GetFloat( "time", "5" ); + speed = spawnArgs.GetFloat( "speed", "1200" ); + shake_time = spawnArgs.GetFloat( "shake_time", "1" ); + throw_time -= shake_time; + if ( throw_time < 0.0f ) { + throw_time = 0.0f; + } + min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) ); + max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) ); + + shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" ); + Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idPhantomObjects::Event_Activate +================ +*/ +void idPhantomObjects::Event_Activate( idEntity *activator ) { + int i; + float time; + float frac; + float scale; + + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + return; + } + + RemoveNullTargets(); + if ( !targets.Num() ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !activator || !activator->IsType( idActor::GetClassType() ) ) { +// RAVEN END + target = gameLocal.GetLocalPlayer(); + } else { + target = static_cast( activator ); + } + + end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) ); + + targetTime.SetNum( targets.Num() ); + lastTargetPos.SetNum( targets.Num() ); + + const idVec3 &toPos = target.GetEntity()->GetEyePosition(); + + // calculate the relative times of all the objects + time = 0.0f; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = SEC2MS( time ); + lastTargetPos[ i ] = toPos; + + frac = 1.0f - ( float )i / ( float )targetTime.Num(); + time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f; + } + + // scale up the times to fit within throw_time + scale = throw_time / time; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale; + } + + BecomeActive( TH_THINK ); +} + +/* +=============== +idPhantomObjects::Think +================ +*/ +void idPhantomObjects::Think( void ) { + int i; + int num; + float time; + idVec3 vel; + idVec3 ang; + idEntity *ent; + idActor *targetEnt; + idPhysics *entPhys; + trace_t tr; + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !( thinkFlags & TH_THINK ) ) { + BecomeInactive( thinkFlags & ~TH_THINK ); + return; + } + + targetEnt = target.GetEntity(); + if ( !targetEnt || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) { + BecomeInactive( TH_THINK ); + } + + const idVec3 &toPos = targetEnt->GetEyePosition(); + + num = 0; + for ( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + if ( !targetTime[ i ] ) { + // already threw this object + continue; + } + + num++; + + time = MS2SEC( targetTime[ i ] - gameLocal.time ); + if ( time > shake_time ) { + continue; + } + + entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, entOrg, toPos, MASK_OPAQUE, ent ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) { + lastTargetPos[ i ] = toPos; + } + + if ( time < 0.0f ) { + idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel ); + vel *= speed; + entPhys->SetLinearVelocity( vel ); + if ( !end_time ) { + targetTime[ i ] = 0; + } else { + targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } + } else { + // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :) + ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z ); + ang *= ( 1.0f - time / shake_time ); + entPhys->SetAngularVelocity( ang ); + } + } + + if ( !num ) { + BecomeInactive( TH_THINK ); + } +} + +// RAVEN BEGIN +// bdube: jump points + +/* +=============================================================================== + +rvDebugJumpPoint + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvDebugJumpPoint ) +END_CLASS + +/* +===================== +rvDebugJumpPoint::Spawn +===================== +*/ +void rvDebugJumpPoint::Spawn() { + // Add the jump point + gameDebug.JumpAdd ( name, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles ( ) ); + + // Dont need the entity any more + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvFuncSaveGame ) + EVENT( EV_Activate, rvFuncSaveGame::Event_Activate ) +END_CLASS + +void rvFuncSaveGame::Spawn( void ) { + +} + +void rvFuncSaveGame::Event_Activate( idEntity *activator ) { + // TODO: Xenon - request the player to save the game. + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "saveGame checkPoint" ); +} diff --git a/source/game/Misc.h b/source/game/Misc.h new file mode 100644 index 0000000..9dd6a74 --- /dev/null +++ b/source/game/Misc.h @@ -0,0 +1,974 @@ + +#ifndef __GAME_MISC_H__ +#define __GAME_MISC_H__ + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +class idSpawnableEntity : public idEntity { +public: + CLASS_PROTOTYPE( idSpawnableEntity ); + + void Spawn( void ); + +private: +}; + +/* +=============================================================================== + + Potential spawning position for players. + The first time a player enters the game, they will be at an 'initial' spot. + Targets will be fired when someone spawns in on them. + + When triggered, will cause player to be teleported to spawn spot. + +=============================================================================== +*/ + +class idPlayerStart : public idEntity { +public: + CLASS_PROTOTYPE( idPlayerStart ); + + enum { + EVENT_TELEPORTPLAYER = idEntity::EVENT_MAXEVENTS, + EVENT_TELEPORTITEM, + EVENT_MAXEVENTS + }; + + idPlayerStart( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + int teleportStage; + + void Event_TeleportEntity( idEntity *activator, bool predicted, idVec3& prevOrigin = vec3_origin ); + void Event_Teleport( idEntity *activator ); + void Teleport( idEntity* other ); + void Event_TeleportStage( idPlayer *player ); + void Event_ResetCamera( idPlayer *player ); + void TeleportPlayer( idPlayer *player ); +}; + + +/* +=============================================================================== + + Non-displayed entity used to activate triggers when it touches them. + Bind to a mover to have the mover activate a trigger as it moves. + When target by triggers, activating the trigger will toggle the + activator on and off. Check "start_off" to have it spawn disabled. + +=============================================================================== +*/ + +class idActivator : public idEntity { +public: + CLASS_PROTOTYPE( idActivator ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + bool stay_on; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + Path entities for monsters to follow. + +=============================================================================== +*/ +class idPathCorner : public idEntity { +public: + CLASS_PROTOTYPE( idPathCorner ); + + void Spawn( void ); + + static void DrawDebugInfo( void ); + + static idPathCorner *RandomPath( const idEntity *source, const idEntity *ignore ); + +private: + void Event_RandomPath( void ); +}; + +// RAVEN BEGIN +// bdube: jump points +/* +=============================================================================== + + Debug Jump Point + +=============================================================================== +*/ + +class rvDebugJumpPoint : public idEntity { +public: + + CLASS_PROTOTYPE( rvDebugJumpPoint ); + + void Spawn(); +}; + +/* +=============================================================================== + + Object that fires targets and changes shader parms when damaged. + +=============================================================================== +*/ + +class idDamagable : public idEntity { +public: + CLASS_PROTOTYPE( idDamagable ); + + idDamagable( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +// RAVEN BEGIN +// abahr: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + int invincibleTime; +// RAVEN BEGIN +// abahr: changed to protected +protected: + int stage; + int stageNext; + const idDict* stageDict; + int stageEndTime; + int stageEndHealth; + int stageEndSpeed; +//jshepard: used to end a stage if a moveable is on the ground (for falling objects) + bool stageEndOnGround; +//jshepard: we want to activate certain objects when triggered-- falling blocks yes, barrels no. + bool activateStageOnTrigger; + + virtual void ExecuteStage ( void ); + void UpdateStage ( void ); + idVec3 GetStageVector ( const char* key, const char* defaultString = "" ) const; + float GetStageFloat ( const char* key, const char* defaultString = "" ) const; + int GetStageInt ( const char* key, const char* defaultString = "" ) const; +// RAVEN END + + int count; + int nextTriggerTime; + + void BecomeBroken( idEntity *activator ); + void Event_BecomeBroken( idEntity *activator ); + void Event_RestoreDamagable( void ); +}; + +/* +=============================================================================== + + Hidden object that explodes when activated + +=============================================================================== +*/ + +class idExplodable : public idEntity { +public: + CLASS_PROTOTYPE( idExplodable ); + + void Spawn( void ); + +private: + void Event_Explode( idEntity *activator ); +}; + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +class idSpring : public idEntity { +public: + CLASS_PROTOTYPE( idSpring ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + idEntityPtr ent1; + idEntityPtr ent2; + int id1; + int id2; + idVec3 p1; + idVec3 p2; + idForce_Spring spring; + + void Event_LinkSpring( void ); +}; + + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +class idForceField : public idEntity { +public: + CLASS_PROTOTYPE( idForceField ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: idDamagable may want to change some things on the fly + void SetExplosion(float force) { forceField.Explosion(force); } +// RAVEN END + + +// RAVEN BEGIN +// bdube: made force field protected +protected: + + idForce_Field forceField; + +private: +// RAVEN END + void Toggle( void ); + + void Event_Activate( idEntity *activator ); + void Event_Toggle( void ); + void Event_FindTargets( void ); +}; + +// RAVEN BEGIN +// bdube: jump pads +/* +=============================================================================== + + rvJumpPad + +=============================================================================== +*/ + +class rvJumpPad : public idForceField { +public: + CLASS_PROTOTYPE( rvJumpPad ); + + rvJumpPad ( void ); + + void Spawn( void ); + void Think( void ); + +private: + + int lastEffectTime; + + void Event_FindTargets( void ); + + enum { + EVENT_JUMPFX = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + idMat3 effectAxis; +}; +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +class idAnimated : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAnimated ); + + idAnimated(); + ~idAnimated(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual bool LoadAF( const char* keyname ); + bool StartRagdoll( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// bdube: script + void Think ( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; +// RAVEN END + +private: + int num_anims; + int current_anim_index; + int anim; + int blendFrames; + jointHandle_t soundJoint; + idEntityPtr activator; + bool activated; + +// RAVEN BEGIN +// bdube: script variables + // script control + idThread * scriptThread; + idStr state; + idStr idealState; + int animDoneTime[ANIM_NumAnimChannels]; + + // Script state management + void UpdateScript( void ); + void SetState( const char *statename, int blend ); + void CallHandler ( const char* handler ); +// RAVEN END + + void PlayNextAnim( void ); + + void Event_Activate( idEntity *activator ); + void Event_Start( void ); + void Event_StartRagdoll( void ); + void Event_AnimDone( int animIndex ); + void Event_Footstep( void ); + void Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ); + void Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetAnimState( const char* state, int blendframes ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone2( int channel, int blendFrames ); +// RAVEN END +}; + +/* +=============================================================================== + + idStaticEntity + +=============================================================================== +*/ + +class idStaticEntity : public idEntity { +public: + CLASS_PROTOTYPE( idStaticEntity ); + + idStaticEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void ShowEditingDialog( void ); + virtual void Hide( void ); + virtual void Show( void ); + void Fade( const idVec4 &to, float fadeTime ); + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_Activate( idEntity *activator ); + + int spawnTime; + bool active; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool runGui; +}; + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + +class idFuncEmitter : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncEmitter ); + + idFuncEmitter( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Event_Activate( idEntity *activator ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + bool hidden; + +}; + + +// RAVEN BEGIN +// bdube: not using +#if 0 +// RAVEN END + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +class idFuncSmoke : public idEntity { +public: + CLASS_PROTOTYPE( idFuncSmoke ); + + idFuncSmoke(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void Event_Activate( idEntity *activator ); + +private: + int smokeTime; + const idDeclParticle * smoke; + bool restart; +}; + +// RAVEN BEGIN +// bdube: not using +#endif +// RAVEN END + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + +class idFuncSplat : public idFuncEmitter { +public: + CLASS_PROTOTYPE( idFuncSplat ); + + idFuncSplat( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Splat(); +}; + + +/* +=============================================================================== + +idTextEntity + +=============================================================================== +*/ + +class idTextEntity : public idEntity { +public: + CLASS_PROTOTYPE( idTextEntity ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +private: + idStr text; + bool playerOriented; +}; + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +class idLocationEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationEntity ); + + void Spawn( void ); + + const char * GetLocation( void ) const; + +private: +}; + +class idLocationSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationSeparatorEntity ); + + void Spawn( void ); + +private: +}; + +class idVacuumSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumSeparatorEntity ); + + idVacuumSeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class idVacuumEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumEntity ); + + void Spawn( void ); + +private: +}; + +// RAVEN BEGIN +// abahr +class rvGravitySeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( rvGravitySeparatorEntity ); + + rvGravitySeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class rvGravityArea : public idEntity { +public: + ABSTRACT_PROTOTYPE( rvGravityArea ); + + void Spawn( void ); + + virtual int GetArea() const { return area; } + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const = 0; + virtual const idVec3 GetGravity( const idEntity* ent ) const = 0; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const = 0; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsEqualTo( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea& area ) const; + bool operator!=( const rvGravityArea* area ) const; + bool operator!=( const rvGravityArea& area ) const; + +protected: + int area; +}; + +class rvGravityArea_Static : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_Static ); + + void Spawn( void ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { return gravity; } + virtual const idVec3 GetGravity( const idEntity* ent ) const { return gravity; } + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const { return gravity; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 gravity; +}; + +class rvGravityArea_SurfaceNormal : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_SurfaceNormal ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const; + virtual const idVec3 GetGravity( const idEntity* ent ) const; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const; + +protected: + virtual const idVec3 GetGravity( const idPhysics* physics ) const; +}; +// RAVEN END + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +class idBeam : public idEntity { +public: + CLASS_PROTOTYPE( idBeam ); + + idBeam(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + void SetMaster( idBeam *masterbeam ); + void SetBeamTarget( const idVec3 &origin ); + + virtual void Show( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_MatchTarget( void ); + void Event_Activate( idEntity *activator ); + + idEntityPtr target; + idEntityPtr master; +}; + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +class idRenderModelLiquid; + +class idLiquid : public idEntity { +public: + CLASS_PROTOTYPE( idLiquid ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Touch( idEntity *other, trace_t *trace ); + + + idRenderModelLiquid *model; +}; + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +class idShaking : public idEntity { +public: + CLASS_PROTOTYPE( idShaking ); + + idShaking(); + ~idShaking(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idPhysics_Parametric physicsObj; + bool active; + + void BeginShaking( void ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +class idEarthQuake : public idEntity { +public: + CLASS_PROTOTYPE( idEarthQuake ); + + idEarthQuake(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off +protected: + void AffectNearbyEntities(float affectRadius); +// RAVEN END + +private: + int nextTriggerTime; + int shakeStopTime; + float wait; + float random; + bool triggered; + bool playerOriented; + bool disabled; + float shakeTime; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +class idFuncPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncPortal ); + + idFuncPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + qhandle_t portal; + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +class idFuncAASPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASPortal ); + + idFuncAASPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +class idFuncAASObstacle : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASObstacle ); + + idFuncAASObstacle(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetState ( bool _state ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +class idFuncRadioChatter : public idEntity { +public: + CLASS_PROTOTYPE( idFuncRadioChatter ); + + idFuncRadioChatter(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RepeatLast ( void ); + +private: + static idEntityPtr lastRadioChatter; + float time; + bool isActive; + void Event_Activate( idEntity *activator ); + void Event_ResetRadioHud( idEntity *activator ); + void Event_IsActive( void ); +}; + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +class idPhantomObjects : public idEntity { +public: + CLASS_PROTOTYPE( idPhantomObjects ); + + idPhantomObjects(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Throw( void ); + void Event_ShakeObject( idEntity *object, int starttime ); + + int end_time; + float throw_time; + float shake_time; + idVec3 shake_ang; + float speed; + int min_wait; + int max_wait; + idEntityPtrtarget; + idList targetTime; + idList lastTargetPos; +}; + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +class rvFuncSaveGame : public idEntity { +public: + CLASS_PROTOTYPE( rvFuncSaveGame ); + + void Spawn( void ); + + void Event_Activate ( idEntity *activator ); + +private: +}; + + +#endif /* !__GAME_MISC_H__ */ diff --git a/source/game/Moveable.cpp b/source/game/Moveable.cpp new file mode 100644 index 0000000..27ab3de --- /dev/null +++ b/source/game/Moveable.cpp @@ -0,0 +1,1347 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif + +/* +=============================================================================== + + idMoveable + +=============================================================================== +*/ + +const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); +const idEventDef EV_SetOwnerFromSpawnArgs( "" ); +const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); +const idEventDef EV_CanDamage( "canDamage", "f" ); +const idEventDef EV_SetHealth( "setHealth", "f" ); +const idEventDef EV_RadiusDamage( "", "es" ); + +CLASS_DECLARATION( idDamagable, idMoveable ) + EVENT( EV_Activate, idMoveable::Event_Activate ) + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) + EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) + EVENT( EV_CanDamage, idMoveable::Event_CanDamage ) + EVENT( EV_SetHealth, idMoveable::Event_SetHealth ) + EVENT( EV_RadiusDamage, idMoveable::Event_RadiusDamage ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; +static const int BOUNCE_SOUND_DELAY_MIN = 500.0f; +static const int BOUNCE_SOUND_DELAY_MAX = 1200.0f; + +/* +================ +idMoveable::idMoveable +================ +*/ +idMoveable::idMoveable( void ) { + minDamageVelocity = 100.0f; + maxDamageVelocity = 200.0f; + nextCollideFxTime = 0; + initialSpline = NULL; + initialSplineDir = vec3_zero; + unbindOnDeath = false; + allowStep = false; + canDamage = false; + + lastAttacker = NULL; +} + +/* +================ +idMoveable::~idMoveable +================ +*/ +idMoveable::~idMoveable( void ) { + delete initialSpline; + initialSpline = NULL; + SetPhysics( NULL ); +} + +/* +================ +idMoveable::Spawn +================ +*/ +void idMoveable::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + int clipShrink; + idStr clipModelName; + bool setClipModel = false; + idBounds bounds; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + idVec3 size; + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on moveable '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on moveable '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + } + + if ( setClipModel ) { + trm.SetupBox( bounds ); + } else { + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString ( "model" ); // use the visual model + } + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + } + + // if the model should be shrinked + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + unbindOnDeath = spawnArgs.GetBool( "unbindondeath" ); + + nextCollideFxTime = 0; + + damage = spawnArgs.GetString( "def_damage", "" ); + canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true; + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + + if ( health ) { + if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { + gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() ); + } + } + + fl.takedamage = (health > 0 ); + + // setup the physics + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM( this ); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( trm, GetRenderModelMaterial() ), density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + physicsObj.DropToFloor(); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + + allowStep = spawnArgs.GetBool( "allowStep", "1" ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = !physicsObj.IsPushable(); +// RAVEN END + + PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 ); +} + +/* +================ +idMoveable::Save +================ +*/ +void idMoveable::Save( idSaveGame *savefile ) const { + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteString( brokenModel ); + savefile->WriteString( damage ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteFloat( minDamageVelocity ); + savefile->WriteFloat( maxDamageVelocity ); + + savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 ); + savefile->WriteVec3( initialSplineDir ); + + savefile->WriteBool( unbindOnDeath ); + savefile->WriteBool( allowStep ); + savefile->WriteBool( canDamage ); + + lastAttacker.Save(savefile); // cnicholson: Added unsaved var +} + +/* +================ +idMoveable::Restore +================ +*/ +void idMoveable::Restore( idRestoreGame *savefile ) { + int initialSplineTime; + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadString( brokenModel ); + savefile->ReadString( damage ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadFloat( minDamageVelocity ); + savefile->ReadFloat( maxDamageVelocity ); + + savefile->ReadInt( initialSplineTime ); + if ( initialSplineTime != -1 ) { + InitInitialSpline( initialSplineTime ); + } else { + initialSpline = NULL; + } + + savefile->ReadVec3( initialSplineDir ); + savefile->ReadBool( unbindOnDeath ); + savefile->ReadBool( allowStep ); + savefile->ReadBool( canDamage ); // cnicholson: Added unrestored var + + lastAttacker.Restore(savefile); // cnicholson: Added unrestored var + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMoveable::Hide +================ +*/ +void idMoveable::Hide( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMoveable::Show +================ +*/ +void idMoveable::Show( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Show(); + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } +} + +/* +================= +idMoveable::Collide +================= +*/ +bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + float len, f; + idVec3 dir; + idEntity *ent; + + dir = velocity; + len = dir.NormalizeFast(); + + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( gameLocal.time > nextCollideFxTime ) { + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_collide",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3() ); +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( len <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + SetSoundVolume( f ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, false, NULL ); + + nextCollideFxTime = gameLocal.time + BOUNCE_SOUND_DELAY_MIN + gameLocal.random.RandomInt(BOUNCE_SOUND_DELAY_MAX - BOUNCE_SOUND_DELAY_MIN); + } + } + + if ( canDamage && damage.Length() ) { + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent && len > minDamageVelocity ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > maxDamageVelocity ) { + f = 1.0f; + } else if( len <= minDamageVelocity ) { + f = 0.0f; + } else { + f = idMath::Sqrt( len - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); + } +// RAVEN END + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + } + } + + return false; +} + +/* +============ +idMoveable::Damage +============ +*/ +void idMoveable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idDamagable::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Cache the attacker to ensure credit for a kill is given to the entity that caused the damage + lastAttacker = inflictor; +// jshepard: +// gameLocal.Warning("idMoveable Damaged! Health is %d", health); + +} + +/* +============ +idMoveable::Killed +============ +*/ +void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + +// jshepard: I am dead! +// gameLocal.Warning("idMoveable Killed! Health is %d", health); + +/* + // No more taking damage + fl.takedamage = false; + + // Should the moveable launch around when killed? + int launchTime; + launchTime = SEC2MS ( spawnArgs.GetFloat ( "launch_time", "0" ); + if ( launchTime > 0 ) { + launchOffset = spawnArgs.GetVector ( "launch_offset" ); + launchDir = spawnArgs.GetVector ( " + } + + spawnArgs.GetFloat ( "explode_lapse", + PostEventSec ( &EV_Remove, explodeLapse ); + + // Two part explosion? + explode_impulse + explode_lapse + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( brokenModel != "" ) { + SetModel( brokenModel ); + } + + if ( explode ) { + if ( brokenModel == "" ) { + Hide(); + physicsObj.PutToRest(); + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 0 ); + } + + const char *splash = spawnArgs.GetString( "def_splash_damage", "" ); + if ( splash && *splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), inflictor, inflictor, this, this, splash ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + StopAllEffects ( ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + + fl.takedamage = false; +*/ +} + + +/* +================ +idMoveable::ExecuteStage +================ +*/ +void idMoveable::ExecuteStage ( void ) { + // Splash damage? + const char *splash; + if ( stageDict->GetString( "def_splash_damage", "", &splash ) && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, splash ); + } + + idDamagable::ExecuteStage(); +} + +/* +================ +idMoveable::AddDamageEffect +================ +*/ +void idMoveable::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + + // Play an impact effect during this stage? + if ( stageDict ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_impact" ), + collision.c.point, collision.c.normal.ToMat3(), + true, vec3_origin, true ); + } +} + +/* +================ +idMoveable::AllowStep +================ +*/ +bool idMoveable::AllowStep( void ) const { + return allowStep; +} + +/* +================ +idMoveable::BecomeNonSolid +================ +*/ +void idMoveable::BecomeNonSolid( void ) { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + +/* +================ +idMoveable::EnableDamage +================ +*/ +void idMoveable::EnableDamage( bool enable, float duration ) { + canDamage = enable; + if ( duration ) { + PostEventSec( &EV_CanDamage, duration, ( !enable ) ? 0.0f : 1.0f ); + } +} + +/* +================ +idMoveable::InitInitialSpline +================ +*/ +void idMoveable::InitInitialSpline( int startTime ) { + int initialSplineTime; + + initialSpline = GetSpline(); + initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" ); + + if ( initialSpline != NULL ) { + initialSpline->MakeUniform( initialSplineTime ); + initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) ); + initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime ); + initialSplineDir *= physicsObj.GetAxis().Transpose(); + initialSplineDir.Normalize(); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idMoveable::FollowInitialSplinePath +================ +*/ +bool idMoveable::FollowInitialSplinePath( void ) { + if ( initialSpline != NULL ) { + if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { + idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.GetMHz(); + physicsObj.SetLinearVelocity( linearVelocity ); + + idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); + idVec3 dir = initialSplineDir * physicsObj.GetAxis(); + idVec3 angularVelocity = dir.Cross( splineDir ); + angularVelocity.Normalize(); + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.GetMHz(); + physicsObj.SetAngularVelocity( angularVelocity ); + return true; + } else { + delete initialSpline; + initialSpline = NULL; + } + } + return false; +} + +/* +================ +idMoveable::Think +================ +*/ +void idMoveable::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // Move to the next stage? + UpdateStage ( ); + + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Think(); +} + +/* +================ +idMoveable::GetRenderModelMaterial +================ +*/ +const idMaterial *idMoveable::GetRenderModelMaterial( void ) const { + if ( renderEntity.customShader ) { + return renderEntity.customShader; + } + if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) { + return renderEntity.hModel->Surface( 0 )->shader; + } + return NULL; +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idMoveable::Event_BecomeNonSolid +================ +*/ +void idMoveable::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idMoveable::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + if ( !spawnArgs.GetInt( "notPushable" ) ) { + physicsObj.EnableImpact(); + } + + physicsObj.Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } + + InitInitialSpline( gameLocal.time ); + +// RAVEN BEGIN +// jshepard: we should update it's stage on activation, specifically for falling blocks. + UpdateStage(); +// RAVEN END + +} + +/* +================ +idMoveable::Event_SetOwnerFromSpawnArgs +================ +*/ +void idMoveable::Event_SetOwnerFromSpawnArgs( void ) { + idStr owner; + + if ( spawnArgs.GetString( "owner", "", owner ) ) { + ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) ); + } +} + +/* +================ +idMoveable::Event_IsAtRest +================ +*/ +void idMoveable::Event_IsAtRest( void ) { + idThread::ReturnInt( physicsObj.IsAtRest() ); +} + +/* +================ +idMoveable::Event_CanDamage +================ +*/ +void idMoveable::Event_CanDamage( float enable ) { + canDamage = ( enable != 0.0f ); +} + +/* +================ +idMoveable::Event_SetHealth +================ +*/ +void idMoveable::Event_SetHealth( float newHealth ) { + health = newHealth; +} + +/* +================ +idMoveable::Event_RadiusDamage +================ +*/ +void idMoveable::Event_RadiusDamage( idEntity *attacker, const char* splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash ); +} + +/* +=============================================================================== + + idBarrel + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveable, idBarrel ) +END_CLASS + +/* +================ +idBarrel::idBarrel +================ +*/ +idBarrel::idBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +idBarrel::Save +================ +*/ +void idBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +idBarrel::Restore +================ +*/ +void idBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +idBarrel::BarrelThink +================ +*/ +void idBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // Progress to the next stage? + UpdateStage ( ); + + // run physics + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + onGround = GetPhysics()->HasGroundContacts(); + curOrigin = GetPhysics()->GetOrigin(); + curAxis = GetPhysics()->GetAxis(); + + // if the barrel is on the ground + if ( onGround ) { + gravityNormal = GetPhysics()->GetGravityNormal(); + + dir = curOrigin - lastOrigin; + dir -= gravityNormal * dir * gravityNormal; + movedDistance = dir.LengthSqr(); + + // if the barrel moved and the barrel is not aligned with the gravity direction + if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) { + + // barrel movement since last think frame orthogonal to the barrel axis + movedDistance = idMath::Sqrt( movedDistance ); + dir *= 1.0f / movedDistance; + movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance; + + // get rotation about barrel axis since last think frame + angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3]; + angle = idMath::ACos( angle ); + // distance along cylinder hull + rotatedDistance = angle * radius; + + // if the barrel moved further than it rotated about it's axis + if ( movedDistance > rotatedDistance ) { + + // additional rotation of the visual model to make it look + // like the barrel rolls instead of slides + angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI); + if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) { + additionalRotation += angle; + } else { + additionalRotation -= angle; + } + dir = vec3_origin; + dir[barrelAxis] = 1.0f; + additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3(); + } + } + } + + // save state for next think + lastOrigin = curOrigin; + lastAxis = curAxis; + } + + Present(); +} + +/* +================ +idBarrel::Think +================ +*/ +void idBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +idBarrel::GetPhysicsToVisualTransform +================ +*/ +bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +idBarrel::Spawn +================ +*/ +void idBarrel::Spawn( void ) { + const idBounds &bounds = GetPhysics()->GetBounds(); + + // radius of the barrel cylinder + radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f; + + // always a vertical barrel with cylinder axis parallel to the z-axis + barrelAxis = 2; + + lastOrigin = GetPhysics()->GetOrigin(); + lastAxis = GetPhysics()->GetAxis(); + + additionalRotation = 0.0f; + additionalAxis.Identity(); +} + +/* +================ +idBarrel::ClientPredictionThink +================ +*/ +void idBarrel::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============================================================================== + +idExplodingBarrel + +=============================================================================== +*/ +const idEventDef EV_Respawn( "" ); +const idEventDef EV_TriggerTargets( "" ); + +CLASS_DECLARATION( idBarrel, idExplodingBarrel ) + EVENT( EV_Activate, idExplodingBarrel::Event_Activate ) + EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn ) + EVENT( EV_Explode, idExplodingBarrel::Event_Explode ) + EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets ) +END_CLASS + +/* +================ +idExplodingBarrel::idExplodingBarrel +================ +*/ +idExplodingBarrel::idExplodingBarrel() { + spawnOrigin.Zero(); + spawnAxis.Zero(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + ipsTime = 0; + lightTime = 0; + time = 0.0f; + explodeFinishTime = 0; +} + +/* +================ +idExplodingBarrel::~idExplodingBarrel +================ +*/ +idExplodingBarrel::~idExplodingBarrel() { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + if ( lightHandle >= 0 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + } +} + +/* +================ +idExplodingBarrel::Save +================ +*/ +void idExplodingBarrel::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( state ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( ipsHandle ); + savefile->WriteInt( lightHandle ); + savefile->WriteRenderEntity( ipsEntity ); + savefile->WriteRenderLight( light ); + savefile->WriteInt( ipsTime ); + savefile->WriteInt( lightTime ); + savefile->WriteFloat( time ); + savefile->WriteInt( explodeFinishTime ); +} + +/* +================ +idExplodingBarrel::Restore +================ +*/ +void idExplodingBarrel::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( (int &)state ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( (int &)ipsHandle ); + savefile->ReadInt( (int &)lightHandle ); +// RAVEN BEGIN + savefile->ReadRenderEntity( ipsEntity, &spawnArgs ); +// RAVEN END + savefile->ReadRenderLight( light ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &light ); + } + savefile->ReadInt( ipsTime ); + savefile->ReadInt( lightTime ); + savefile->ReadFloat( time ); + savefile->ReadInt( explodeFinishTime ); + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } + + if ( ipsHandle != -1 ) { + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::Spawn +================ +*/ +void idExplodingBarrel::Spawn( void ) { + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + lightTime = 0; + ipsTime = 0; + time = spawnArgs.GetFloat( "time" ); + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + explodeFinishTime = 0; + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Think( void ) { + idBarrel::BarrelThink(); + + // MP: EXPLODED means no effect on client when updating the state + if ( !gameLocal.isClient && state == EXPLODING ) { + if ( gameLocal.time > explodeFinishTime ) { + state = EXPLODED; + } + } + + if ( lightHandle >= 0 ){ + if ( state == BURNING ) { + // ramp the color up over 250 ms + float pct = (gameLocal.time - lightTime) / 250.f; + if ( pct > 1.0f ) { + pct = 1.0f; + } + light.origin = physicsObj.GetAbsBounds().GetCenter(); + light.axis = mat3_identity; + light.shaderParms[ SHADERPARM_RED ] = pct; + light.shaderParms[ SHADERPARM_GREEN ] = pct; + light.shaderParms[ SHADERPARM_BLUE ] = pct; + light.shaderParms[ SHADERPARM_ALPHA ] = pct; + gameRenderWorld->UpdateLightDef( lightHandle, &light ); + } else { + if ( gameLocal.time - lightTime > 250 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } + return; + } + } + + if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) { + BecomeInactive( TH_THINK ); + return; + } + + if ( ipsHandle >= 0 ){ + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + gameRenderWorld->UpdateEntityDef( ipsHandle, &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::AddIPS +================ +*/ +void idExplodingBarrel::AddIPS( const char *name, bool burn ) { + if ( name && *name ) { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + memset( &ipsEntity, 0, sizeof ( ipsEntity ) ); + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name ) ); + if ( modelDef ) { + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + ipsEntity.hModel = modelDef->ModelHandle(); + float rgb = ( burn ) ? 0.0f : 1.0f; + ipsEntity.shaderParms[ SHADERPARM_RED ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_GREEN ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_BLUE ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + ipsEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 ); + if ( !ipsEntity.hModel ) { + ipsEntity.hModel = renderModelManager->FindModel( name ); + } + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + if ( burn ) { + BecomeActive( TH_THINK ); + } + ipsTime = gameLocal.time; + } + } +} + +/* +================ +idExplodingBarrel::AddLight +================ +*/ +void idExplodingBarrel::AddLight( const char *name, bool burn ) { + if ( lightHandle >= 0 ){ + gameRenderWorld->FreeLightDef( lightHandle ); + } + memset( &light, 0, sizeof ( light ) ); + light.axis = mat3_identity; + light.lightRadius.x = spawnArgs.GetFloat( "light_radius" ); + light.lightRadius.y = light.lightRadius.z = light.lightRadius.x; + light.origin = physicsObj.GetOrigin(); + light.origin.z += 128; + light.pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: made sure that barrels are set to no shadows + light.noShadows = true; +// RAVEN END + light.shader = declManager->FindMaterial( name ); + light.shaderParms[ SHADERPARM_RED ] = 2.0f; + light.shaderParms[ SHADERPARM_GREEN ] = 2.0f; + light.shaderParms[ SHADERPARM_BLUE ] = 2.0f; + light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f; + lightHandle = gameRenderWorld->AddLightDef( &light ); + lightTime = gameLocal.time; + BecomeActive( TH_THINK ); +} + +/* +================ +idExplodingBarrel::ExplodingEffects +================ +*/ +void idExplodingBarrel::ExplodingEffects( void ) { + const char *temp; + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + temp = spawnArgs.GetString( "model_damage", "" ); + if ( temp && *temp ) { + SetModel( temp ); + Show(); + } + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + temp = spawnArgs.GetString( "mtr_lightexplode", "" ); + if ( temp && *temp ) { + AddLight( temp, false ); + } +*/ + StopEffect ( "fx_burn" ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); +} + +/* +================ +idExplodingBarrel::Killed +================ +*/ +void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if ( IsHidden() || state == EXPLODED || state == EXPLODING || state == BURNING ) { + return; + } + + float f = spawnArgs.GetFloat( "burn" ); + if ( f > 0.0f && state == NORMAL ) { + state = BURNING; + PostEventSec( &EV_Explode, f ); + StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_burn"), + vec3_origin, (-GetPhysics()->GetGravityNormal()).ToMat3(), true, vec3_origin, true ); + return; + } else { + state = EXPLODING; + spawnArgs.GetInt( "explode_lapse", "1000", explodeFinishTime ); + explodeFinishTime += gameLocal.time; + } + + // do this before applying radius damage so the ent can trace to any damagable ents nearby + Hide(); + physicsObj.SetContents( 0 ); + + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, inflictor, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, this, splash); + } + + ExplodingEffects( ); + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + //FIXME: need to precache all the debris stuff here and in the projectiles + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } +*/ +// RAVEN END + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + f = spawnArgs.GetFloat( "respawn" ); + if ( f > 0.0f ) { + PostEventSec( &EV_Respawn, f ); + } else { + PostEventMS( &EV_Remove, 5000 ); + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { + PostEventMS( &EV_Explode, 400 ); + } else { + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + + +/* +================ +idExplodingBarrel::Event_TriggerTargets +================ +*/ +void idExplodingBarrel::Event_TriggerTargets() { + ActivateTargets( this ); +} + +/* +================ +idExplodingBarrel::Event_Explode +================ +*/ +void idExplodingBarrel::Event_Explode() { + if ( state == NORMAL || state == BURNING ) { + state = BURNEXPIRED; + Killed( NULL, NULL, 0, vec3_zero, 0 ); + } +} + +/* +================ +idExplodingBarrel::Event_Respawn +================ +*/ +void idExplodingBarrel::Event_Respawn() { + const char *temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = (health > 0); + physicsObj.SetOrigin( spawnOrigin ); + physicsObj.SetAxis( spawnAxis ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.DropToFloor(); + state = NORMAL; + Show(); + UpdateVisuals(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idExplodingBarrel::Event_Activate( idEntity *activator ) { + Killed( activator, activator, 0, vec3_origin, 0 ); +} + + + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const { + idMoveable::WriteToSnapshot( msg ); + msg.WriteBits( IsHidden(), 1 ); + msg.WriteBits( state, 3 ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) { + explode_state_t newState; + + idMoveable::ReadFromSnapshot( msg ); + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } + newState = (explode_state_t)msg.ReadBits( 3 ); + if ( newState != state ) { + state = newState; + if ( state == EXPLODING ) { + ExplodingEffects( ); + } + } +} + +// RAVEN END diff --git a/source/game/Moveable.h b/source/game/Moveable.h new file mode 100644 index 0000000..39a7903 --- /dev/null +++ b/source/game/Moveable.h @@ -0,0 +1,179 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 06/02/2004 + +#ifndef __GAME_MOVEABLE_H__ +#define __GAME_MOVEABLE_H__ + +/* +=============================================================================== + + Entity using rigid body physics. + +=============================================================================== +*/ + +extern const idEventDef EV_BecomeNonSolid; +extern const idEventDef EV_IsAtRest; + +class idMoveable : public idDamagable { +public: + CLASS_PROTOTYPE( idMoveable ); + + idMoveable( void ); + ~idMoveable( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + bool AllowStep( void ) const; + void EnableDamage( bool enable, float duration ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + idPhysics_RigidBody physicsObj; // physics object + idStr brokenModel; // model set when health drops down to or below zero + idStr damage; // if > 0 apply damage to hit entities + int nextCollideFxTime; // next time it is ok to spawn collision fx + float minDamageVelocity; // minimum velocity before moveable applies damage + float maxDamageVelocity; // velocity at which the maximum damage is applied + idCurve_Spline *initialSpline; // initial spline path the moveable follows + idVec3 initialSplineDir; // initial relative direction along the spline path + bool unbindOnDeath; // unbind from master when health drops down to or below zero + bool allowStep; // allow monsters to step on the object + bool canDamage; // only apply damage when this is set + + idEntityPtr lastAttacker; + + virtual void ExecuteStage ( void ); + + const idMaterial * GetRenderModelMaterial( void ) const; + void BecomeNonSolid( void ); + void InitInitialSpline( int startTime ); + bool FollowInitialSplinePath( void ); + + void Event_Activate( idEntity *activator ); + void Event_BecomeNonSolid( void ); + void Event_SetOwnerFromSpawnArgs( void ); + void Event_IsAtRest( void ); + void Event_CanDamage ( float enable ); + void Event_SetHealth ( float newHealth ); + void Event_RadiusDamage( idEntity *attacker, const char* splash ); +}; + + +/* +=============================================================================== + + A barrel using rigid body physics. The barrel has special handling of + the view model orientation to make it look like it rolls instead of slides. + +=============================================================================== +*/ + +class idBarrel : public idMoveable { + +public: + CLASS_PROTOTYPE( idBarrel ); + idBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void BarrelThink( void ); + virtual void Think( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void ClientPredictionThink( void ); + +private: + float radius; // radius of barrel + int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to + idVec3 lastOrigin; // origin of the barrel the last think frame + idMat3 lastAxis; // axis of the barrel the last think frame + float additionalRotation; // additional rotation of the barrel about it's axis + idMat3 additionalAxis; // additional rotation axis +}; + + +/* +=============================================================================== + + A barrel using rigid body physics and special handling of the view model + orientation to make it look like it rolls instead of slides. The barrel + can burn and explode when damaged. + +=============================================================================== +*/ + +class idExplodingBarrel : public idBarrel { +public: + CLASS_PROTOTYPE( idExplodingBarrel ); + + idExplodingBarrel(); + ~idExplodingBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + typedef enum { + NORMAL = 0, + BURNING, + BURNEXPIRED, + EXPLODING, + EXPLODED + } explode_state_t; + explode_state_t state; + + idVec3 spawnOrigin; + idMat3 spawnAxis; + qhandle_t ipsHandle; + qhandle_t lightHandle; + renderEntity_t ipsEntity; + renderLight_t light; + int ipsTime; + int lightTime; + float time; + + int explodeFinishTime; + + void AddIPS( const char *name, bool burn ); + void AddLight( const char *name , bool burn ); + void ExplodingEffects( void ); + + void Event_Activate( idEntity *activator ); + void Event_Respawn(); + void Event_Explode(); + void Event_TriggerTargets(); +}; + +#endif /* !__GAME_MOVEABLE_H__ */ + +// RAVEN END diff --git a/source/game/Mover.cpp b/source/game/Mover.cpp new file mode 100644 index 0000000..8b57e34 --- /dev/null +++ b/source/game/Mover.cpp @@ -0,0 +1,5977 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +// a mover will update any gui entities in it's target list with +// a key/val pair of "mover" "state" from below.. guis can represent +// realtime info like this +// binary only +static const char *guiBinaryMoverStates[] = { + "1", // pos 1 + "2", // pos 2 + "3", // moving 1 to 2 + "4" // moving 2 to 1 +}; + + +/* +=============================================================================== + +idMover + +=============================================================================== +*/ + +const idEventDef EV_FindGuiTargets( "", NULL ); +const idEventDef EV_TeamBlocked( "", "ee" ); +const idEventDef EV_PartBlocked( "", "e" ); +const idEventDef EV_ReachedPos( "", NULL ); +const idEventDef EV_ReachedAng( "", NULL ); +// RAVEN BEGIN +const idEventDef EV_PostRestoreExt( "", "ddddd" ); +// RAVEN END +const idEventDef EV_StopMoving( "stopMoving", NULL ); +const idEventDef EV_StopRotating( "stopRotating", NULL ); +const idEventDef EV_Speed( "speed", "f" ); +const idEventDef EV_Time( "time", "f" ); +const idEventDef EV_AccelTime( "accelTime", "f" ); +const idEventDef EV_DecelTime( "decelTime", "f" ); +const idEventDef EV_MoveTo( "moveTo", "e" ); +const idEventDef EV_MoveToPos( "moveToPos", "v" ); +const idEventDef EV_Move( "move", "ff" ); +const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); +const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); +const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); +const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); +const idEventDef EV_RotateTo( "rotateTo", "v" ); +const idEventDef EV_Rotate( "rotate", "v" ); +const idEventDef EV_RotateOnce( "rotateOnce", "v" ); +const idEventDef EV_Bob( "bob", "ffv" ); +const idEventDef EV_Sway( "sway", "ffv" ); +const idEventDef EV_Mover_OpenPortal( "openPortal" ); +const idEventDef EV_Mover_ClosePortal( "closePortal" ); +const idEventDef EV_AccelSound( "accelSound", "s" ); +const idEventDef EV_DecelSound( "decelSound", "s" ); +// RAVEN BEGIN +// cnicholson: added stop sound support +const idEventDef EV_StoppedSound( "stoppedSound", "s" ); +// RAVEN END +const idEventDef EV_MoveSound( "moveSound", "s" ); +const idEventDef EV_Mover_InitGuiTargets( "", NULL ); +const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); +const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); +const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); +const idEventDef EV_StartSpline( "startSpline", "e" ); +const idEventDef EV_StopSpline( "stopSpline", NULL ); +const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); +const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); +// RAVEN BEGIN +// abahr: +const idEventDef EV_GetSplineEntity( "getSplineEntity", NULL, 'E' ); +const idEventDef EV_MoveAlongVector( "moveAlongVector", "v" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idMover ) + EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) + EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) + EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) + EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) +// RAVEN BEGIN + EVENT( EV_PostRestoreExt, idMover::Event_PostRestoreExt ) +// RAVEN END + EVENT( EV_StopMoving, idMover::Event_StopMoving ) + EVENT( EV_StopRotating, idMover::Event_StopRotating ) + EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) + EVENT( EV_Time, idMover::Event_SetMoveTime ) + EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) + EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) + EVENT( EV_MoveTo, idMover::Event_MoveTo ) + EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) + EVENT( EV_Move, idMover::Event_MoveDir ) + EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) + EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) + EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) + EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) + EVENT( EV_RotateTo, idMover::Event_RotateTo ) + EVENT( EV_Rotate, idMover::Event_Rotate ) + EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) + EVENT( EV_Bob, idMover::Event_Bob ) + EVENT( EV_Sway, idMover::Event_Sway ) + EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) + EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) + EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) +// RAVEN BEGIN +// cnicholson: added stop sound support + EVENT( EV_StoppedSound, idMover::Event_SetStoppedSound ) +// RAVEN END + EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) + EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) + EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) + EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) + EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) + EVENT( EV_StartSpline, idMover::Event_StartSpline ) + EVENT( EV_StopSpline, idMover::Event_StopSpline ) + EVENT( EV_Activate, idMover::Event_Activate ) + EVENT( EV_IsMoving, idMover::Event_IsMoving ) + EVENT( EV_IsRotating, idMover::Event_IsRotating ) +// RAVEN BEGIN +// abahr: + EVENT( EV_GetSplineEntity, idMover::Event_GetSplineEntity ) + EVENT( EV_MoveAlongVector, idMover::Event_MoveAlongVector ) +// RAVEN END +END_CLASS + +/* +================ +idMover::idMover +================ +*/ +idMover::idMover( void ) { + memset( &move, 0, sizeof( move ) ); + memset( &rot, 0, sizeof( rot ) ); + move_thread = 0; + rotate_thread = 0; + dest_angles.Zero(); + angle_delta.Zero(); + dest_position.Zero(); + move_delta.Zero(); + move_speed = 0.0f; + move_time = 0; + deceltime = 0; + acceltime = 0; + stopRotation = false; + useSplineAngles = true; + attenuate = false; + useIdleSound = false; + lastCommand = MOVER_NONE; + damage = 0.0f; + areaPortal = 0; + maxAttenuation = 0.0f; + attenuationScale = 0.0f; + lastOrigin.Zero( ); + lastTime = 0; + splineStartTime = 0; + + fl.networkSync = true; +} + + +idMover::~idMover( void ) { + SetPhysics( NULL ); +} + + + +/* +================ +idMover::Save +================ +*/ +void idMover::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteStaticObject( physicsObj ); + + savefile->Write( &move, sizeof( move ) ); + savefile->Write( &rot, sizeof( rot ) ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( rotate_thread ); + + savefile->WriteAngles( dest_angles ); + savefile->WriteAngles( angle_delta ); + savefile->WriteVec3( dest_position ); + savefile->WriteVec3( move_delta ); + + savefile->WriteFloat( move_speed ); + savefile->WriteInt( move_time ); + savefile->WriteInt( deceltime ); + savefile->WriteInt( acceltime ); + savefile->WriteBool( stopRotation ); + savefile->WriteBool( useSplineAngles ); + savefile->WriteInt( lastCommand ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal > 0 ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } + + if ( splineEnt.GetEntity() ) { + idCurve_Spline *spline = physicsObj.GetSpline(); + if (spline) { + + savefile->WriteBool( true ); + splineEnt.Save( savefile ); + savefile->WriteInt( spline->GetTime( 0 ) ); + savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); + savefile->WriteInt( physicsObj.GetSplineAcceleration() ); + savefile->WriteInt( physicsObj.GetSplineDeceleration() ); + savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); + } else { + savefile->WriteBool( false ); + } + + } else { + savefile->WriteBool( false ); + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->WriteBool( attenuate ); + savefile->WriteFloat( maxAttenuation ); + savefile->WriteFloat( attenuationScale ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteInt( lastTime ); + savefile->WriteBool( useIdleSound ); + splineStateThread.Save( savefile ); + savefile->WriteInt( splineStartTime ); +// RAVEN END +} + +/* +================ +idMover::Restore +================ +*/ +void idMover::Restore( idRestoreGame *savefile ) { + int i, num; + bool hasSpline = false; + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->Read( &move, sizeof( move ) ); + savefile->Read( &rot, sizeof( rot ) ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( rotate_thread ); + + savefile->ReadAngles( dest_angles ); + savefile->ReadAngles( angle_delta ); + savefile->ReadVec3( dest_position ); + savefile->ReadVec3( move_delta ); + + savefile->ReadFloat( move_speed ); + savefile->ReadInt( move_time ); + savefile->ReadInt( deceltime ); + savefile->ReadInt( acceltime ); + savefile->ReadBool( stopRotation ); + savefile->ReadBool( useSplineAngles ); + savefile->ReadInt( (int &)lastCommand ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal > 0 ) { + int portalState = 0; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } + + savefile->ReadBool( hasSpline ); + if ( hasSpline ) { + int starttime; + int totaltime; + int accel; + int decel; + int useAngles; + + splineEnt.Restore( savefile ); + savefile->ReadInt( starttime ); + savefile->ReadInt( totaltime ); + savefile->ReadInt( accel ); + savefile->ReadInt( decel ); + savefile->ReadInt( useAngles ); + +// RAVEN BEGIN + PostEventMS( &EV_PostRestoreExt, 0, starttime, totaltime, accel, decel, useAngles ); +// RAVEN END + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->ReadBool( attenuate ); + savefile->ReadFloat( maxAttenuation ); + savefile->ReadFloat( attenuationScale ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadInt( lastTime ); + savefile->ReadBool( useIdleSound ); + splineStateThread.Restore( savefile, this ); + savefile->ReadInt( splineStartTime ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +/* +================ +idMover::Event_PostRestoreExt +================ +*/ +// RAVEN BEGIN +void idMover::Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ) { +// RAVEN END + idCurve_Spline *spline; + + idEntity *splineEntity = splineEnt.GetEntity(); + if ( !splineEntity ) { + // We should never get this event if splineEnt is invalid + common->Warning( "Invalid spline entity during restore\n" ); + return; + } + + spline = splineEntity->GetSpline(); + + spline->MakeUniform( total ); + spline->ShiftTime( start - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, accel, decel, useSplineAng ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Spawn +================ +*/ +void idMover::Spawn( void ) { + move_thread = 0; + rotate_thread = 0; + stopRotation = false; + lastCommand = MOVER_NONE; + + acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); + deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); + move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value + move_speed = spawnArgs.GetFloat( "move_speed", "0" ); + + spawnArgs.GetFloat( "damage" , "0", damage ); + + dest_position = GetPhysics()->GetOrigin(); + dest_angles = GetPhysics()->GetAxis().ToAngles(); + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + if( spawnArgs.GetBool("removeGimbleLock") ) { + physicsObj.SetAxisOffset( spawnArgs.GetMatrix("rotation", mat3_identity.ToString()) ); + physicsObj.SetAxis( mat3_identity ); + UpdateVisuals(); + } + +// mekberg: attenuation + attenuate = spawnArgs.GetBool( "attenuate" ); + if( attenuate ) { + maxAttenuation = spawnArgs.GetFloat( "maxAttenuation", "3" ); + attenuationScale = spawnArgs.GetFloat( "attenuationScale", "100" ); + + // Check for bad value/prevent divide by zero + if ( attenuationScale == 0.0f || attenuationScale < 0.0f ) { + attenuationScale = 100; + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } else { + maxAttenuation = 0.0f; + attenuationScale = 1.0f; + } + + if ( !idStr::Icmp( spawnArgs.GetString( "snd_idle", "" ), "" ) ) { + useIdleSound = false; + } else { + StartSound( "snd_idle", SND_CHANNEL_BODY, 0, false, NULL ); + useIdleSound = true; + } + + splineStateThread.SetName( "SplineStateThread" ); + splineStateThread.SetOwner( this ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idMover::Think +================ +*/ +void idMover::Think( void ) { + idVec3 deltaPosition; + float deltaTime; + float speed; + float attenuation; + + if ( physicsObj.GetSpline( ) ) { + splineStateThread.Execute( ); + } + + if ( attenuate ) { + deltaPosition = physicsObj.GetOrigin( ) - lastOrigin; + deltaTime = gameLocal.time - lastTime; + + if ( !deltaTime ) { + deltaTime = 1; + } + + speed = deltaPosition.Length( ) * ( 1000.0f / float( deltaTime ) ); + + if ( speed >= VECTOR_EPSILON ) { + soundShaderParms_t parms = refSound.parms; + + attenuation = 0.8f + 0.2f * ( speed / attenuationScale ); + parms.frequencyShift = idMath::ClampFloat( 0.0f, maxAttenuation, attenuation ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( SND_CHANNEL_BODY, &parms ); + } + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } + + idEntity::Think( ); +} + +// abahr +/* +================ +idMover::GetPhysicsToVisualTransform +================ +*/ +bool idMover::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin.Zero(); + axis = physicsObj.GetAxisOffset(); + return physicsObj.UseAxisOffset(); +} + +/* +================ +idMover::MoveAlongVector +================ +*/ +void idMover::MoveAlongVector( const idVec3& vec ) { + idAngles ang; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.GetLocalAngles( ang ); + dest_position = org + vec * ang.ToMat3(); + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAlongVector +================ +*/ +void idMover::Event_MoveAlongVector( const idVec3& vec ) { + MoveAlongVector( vec ); +} +// RAVEN END + +/* +================ +idMover::Hide +================ +*/ +void idMover::Hide( void ) { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMover::Show +================ +*/ +void idMover::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } + SetPhysics( &physicsObj ); +} + +/* +============ +idMover::Killed +============ +*/ +void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + ActivateTargets( this ); +} + + +/* +================ +idMover::Event_SetCallback +================ +*/ +void idMover::Event_SetCallback( void ) { + if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { + lastCommand = MOVER_NONE; + rotate_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { + lastCommand = MOVER_NONE; + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idMover::VectorForDir +================ +*/ +void idMover::VectorForDir( float angle, idVec3 &vec ) { + idAngles ang; + + switch( ( int )angle ) { + case DIR_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 90; + vec = ang.ToForward(); + break; + + case DIR_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw -= 90; + vec = ang.ToForward(); + break; + + case DIR_FORWARD : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + vec = ang.ToForward(); + break; + + case DIR_BACK : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 180; + vec = ang.ToForward(); + break; + + case DIR_REL_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_REL_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_REL_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + vec *= -1; + break; + + case DIR_REL_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + break; + + case DIR_REL_FORWARD : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward(); + break; + + case DIR_REL_BACK : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward() * -1; + break; + + default: + ang.Set( 0, angle, 0 ); + vec = GetWorldVector( ang.ToForward() ); + break; + } +} + +/* +================ +idMover::FindGuiTargets +================ +*/ +void idMover::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover::SetGuiState( const char *key, const char *val ) const { + gameLocal.Printf( "Setting %s to %s\n", key, val ); + for( int i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover::SetGuiStates +================ +*/ +void idMover::SetGuiStates( const char *state ) { + int i; + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( "movestate", state ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_InitGuiTargets( void ) { + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); +} + +/*********************************************************************** + + Translation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopMoving +================ +*/ +void idMover::Event_StopMoving( void ) { + physicsObj.GetLocalOrigin( dest_position ); + DoneMoving(); +} + +/* +================ +idMover::DoneMoving +================ +*/ +void idMover::DoneMoving( void ) { + + if ( lastCommand != MOVER_SPLINE ) { + // set our final position so that we get rid of any numerical inaccuracy + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + } + + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added sig reached + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + +// RAVEN BEGIN +// mekberg: for idle sound + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } +} + +/* +================ +idMover::UpdateMoveSound +================ +*/ +void idMover::UpdateMoveSound( moveStage_t stage ) { +// RAVEN BEGIN +// mekberg: Idle sound plays instead of snd_move. Don't stop idle sounds. + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case LINEAR_STAGE: { + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case DECELERATION_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + // cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + } +// RAVEN END +} + +/* +================ +idMover::Event_UpdateMove +================ +*/ +void idMover::Event_UpdateMove( void ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + + UpdateMoveSound( move.stage ); + + switch( move.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, move.dir, vec3_origin ); + if ( move.movetime > 0 ) { + move.stage = LINEAR_STAGE; + } else if ( move.deceleration > 0 ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, move.movetime, org, move.dir, vec3_origin ); + if ( move.deceleration ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, move.dir, vec3_origin ); + move.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' move done\n", gameLocal.time, name.c_str() ); + } + DoneMoving(); + break; + } + } +} + +/* +================ +idMover::BeginMove +================ +*/ +void idMover::BeginMove( idThread *thread ) { + moveStage_t stage; + idVec3 org; + float dist; + float acceldist; + int totalacceltime; + int at; + int dt; + + lastCommand = MOVER_MOVING; + move_thread = 0; + + physicsObj.GetLocalOrigin( org ); + + move_delta = dest_position - org; + if ( move_delta.Compare( vec3_zero ) ) { + DoneMoving(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + + // if we're moving at a specific speed, we need to calculate the move time + if ( move_speed ) { + dist = move_delta.Length(); + + totalacceltime = acceltime + deceltime; + + // calculate the distance we'll move during acceleration and deceleration + acceldist = totalacceltime * 0.5f * 0.001f * move_speed; + if ( acceldist >= dist ) { + // going too slow for this distance to move at a constant speed + move_time = totalacceltime; + } else { + // calculate move time taking acceleration into account + move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; + } + } + + // scale time up to a whole physics frames + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + move.stage = stage; + move.acceleration = at; + move.movetime = move_time - at - dt; + move.deceleration = dt; + move.dir = move_delta; + + ProcessEvent( &EV_ReachedPos ); +} + +/*********************************************************************** + + Rotation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopRotating +================ +*/ +void idMover::Event_StopRotating( void ) { + physicsObj.GetLocalAngles( dest_angles ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + DoneRotating(); +} + +/* +================ +idMover::DoneRotating +================ +*/ +void idMover::DoneRotating( void ) { + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added reached signal + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( rotate_thread, this ); + rotate_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateRotationSound +================ +*/ +void idMover::UpdateRotationSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); +// RAVEN BEGIN +// cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY, 0, false, NULL ); +// RAVEN END + break; + } + } +} + +/* +================ +idMover::Event_UpdateRotation +================ +*/ +void idMover::Event_UpdateRotation( void ) { + idAngles ang; + + physicsObj.GetLocalAngles( ang ); + + UpdateRotationSound( rot.stage ); + + switch( rot.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, rot.acceleration, ang, rot.rot, ang_zero ); + if ( rot.movetime > 0 ) { + rot.stage = LINEAR_STAGE; + } else if ( rot.deceleration > 0 ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + if ( !stopRotation && !rot.deceleration ) { + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } else { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } + + if ( rot.deceleration ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, rot.deceleration, ang, rot.rot, ang_zero ); + rot.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + lastCommand = MOVER_NONE; + if ( stopRotation ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { + // keep our angular velocity constant + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, rot.rot, ang_zero ); + } + + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.time, name.c_str() ); + } + + DoneRotating(); + break; + } + } +} + +/* +================ +idMover::BeginRotation +================ +*/ +void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { + moveStage_t stage; + idAngles ang; + int at; + int dt; + + lastCommand = MOVER_ROTATING; + rotate_thread = 0; + + // rotation always uses move_time so that if a move was started before the rotation, + // the rotation will take the same amount of time as the move. If no move has been + // started and no time is set, the rotation takes 1 second. + if ( !move_time ) { + move_time = 1; + } + + physicsObj.GetLocalAngles( ang ); + angle_delta = dest_angles - ang; + if ( angle_delta == ang_zero ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + DoneRotating(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + stopRotation = stopwhendone || ( dt != 0 ); + + rot.stage = stage; + rot.acceleration = at; + rot.movetime = move_time - at - dt; + rot.deceleration = dt; + rot.rot = angle_delta; + + ProcessEvent( &EV_ReachedAng ); +} + + +/*********************************************************************** + + Script callable routines + +***********************************************************************/ + +/* +=============== +idMover::Event_TeamBlocked +=============== +*/ +void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +=============== +idMover::Event_PartBlocked +=============== +*/ +void idMover::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +================ +idMover::Event_SetMoveSpeed +================ +*/ +void idMover::Event_SetMoveSpeed( float speed ) { + if ( speed <= 0 ) { + gameLocal.Error( "Cannot set speed less than or equal to 0." ); + } + + move_speed = speed; + move_time = 0; // move_time is calculated for each move when move_speed is non-0 +} + +/* +================ +idMover::Event_SetMoveTime +================ +*/ +void idMover::Event_SetMoveTime( float time ) { + if ( time <= 0 ) { + gameLocal.Error( "Cannot set time less than or equal to 0." ); + } + + move_speed = 0; + move_time = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetAccellerationTime +================ +*/ +void idMover::Event_SetAccellerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set acceleration time less than 0." ); + } + + acceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetDecelerationTime +================ +*/ +void idMover::Event_SetDecelerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set deceleration time less than 0." ); + } + + deceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_MoveTo +================ +*/ +void idMover::Event_MoveTo( idEntity *ent ) { + if ( !ent ) { + gameLocal.Warning( "Entity not found" ); +// RAVEN BEGIN +// abahr: added return so the NULL ptr doesn't get used + return; +// RAVEN END + } + + dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::MoveToPos +================ +*/ +void idMover::MoveToPos( const idVec3 &pos ) { + dest_position = GetLocalCoordinates( pos ); + BeginMove( NULL ); +} + +/* +================ +idMover::Event_MoveToPos +================ +*/ +void idMover::Event_MoveToPos( idVec3 &pos ) { + MoveToPos( pos ); +} + +/* +================ +idMover::Event_MoveDir +================ +*/ +void idMover::Event_MoveDir( float angle, float distance ) { + idVec3 dir; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + VectorForDir( angle, dir ); + dest_position = org + dir * distance; + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAccelerateTo +================ +*/ +void idMover::Event_MoveAccelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int at; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); + } + + // if already moving faster than the desired speed + if ( v >= speed ) { + return; + } + + at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = ACCELERATION_STAGE; + move.acceleration = at; + move.movetime = 0; + move.deceleration = 0; + + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, dir * ( speed - v ), dir * v ); +} + +/* +================ +idMover::Event_MoveDecelerateTo +================ +*/ +void idMover::Event_MoveDecelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int dt; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); + } + + // if already moving slower than the desired speed + if ( v <= speed ) { + return; + } + + dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = DECELERATION_STAGE; + move.acceleration = 0; + move.movetime = 0; + move.deceleration = dt; + + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); +} + +/* +================ +idMover::Event_RotateDownTo +================ +*/ +void idMover::Event_RotateDownTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] > ang[ axis ] ) { + dest_angles[ axis ] -= 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateUpTo +================ +*/ +void idMover::Event_RotateUpTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] < ang[ axis ] ) { + dest_angles[ axis ] += 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateTo +================ +*/ +void idMover::Event_RotateTo( idAngles &angles ) { + dest_angles = angles; + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Rotate +================ +*/ +void idMover::Event_Rotate( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; + + BeginRotation( idThread::CurrentThread(), false ); +} + +/* +================ +idMover::Event_RotateOnce +================ +*/ +void idMover::Event_RotateOnce( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles; + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Bob +================ +*/ +void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); +} + +/* +================ +idMover::Event_Sway +================ +*/ +void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { + idAngles ang, angSpeed; + float duration; + + physicsObj.GetLocalAngles( ang ); + assert ( speed > 0.0f ); + duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; + angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); +} + +/* +================ +idMover::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover::Event_OpenPortal( void ) { + if ( areaPortal ) { + SetPortalState( true ); + } +} + +/* +================ +idMover::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover::Event_ClosePortal( void ) { + if ( areaPortal ) { + SetPortalState( false ); + } +} + +/* +================ +idMover::Event_SetAccelSound +================ +*/ +void idMover::Event_SetAccelSound( const char *sound ) { +// refSound.SetSound( "accel", sound ); +} + +/* +================ +idMover::Event_SetDecelSound +================ +*/ +void idMover::Event_SetDecelSound( const char *sound ) { +// refSound.SetSound( "decel", sound ); +} + +// RAVEN BEGIN +// cnicholson: added stop sound support +/* +================ +idMover::Event_SetStoppedSound +================ +*/ +void idMover::Event_SetStoppedSound( const char *sound ) { +// refSound.SetSound( "stopped", sound ); +} +// RAVEN END + +/* +================ +idMover::Event_SetMoveSound +================ +*/ +void idMover::Event_SetMoveSound( const char *sound ) { +// refSound.SetSound( "move", sound ); +} + +/* +================ +idMover::Event_EnableSplineAngles +================ +*/ +void idMover::Event_EnableSplineAngles( void ) { + useSplineAngles = true; +} + +/* +================ +idMover::Event_DisableSplineAngles +================ +*/ +void idMover::Event_DisableSplineAngles( void ) { + useSplineAngles = false; +} + +/* +================ +idMover::Event_RemoveInitialSplineAngles +================ +*/ +void idMover::Event_RemoveInitialSplineAngles( void ) { + idCurve_Spline *spline; + idAngles ang; + + spline = physicsObj.GetSpline(); + if ( !spline ) { + return; + } + ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); +} + +/* +================ +idMover::Event_StartSpline +================ +*/ +void idMover::Event_StartSpline( idEntity *splineEntity ) { + idCurve_Spline *spline; + + if ( !splineEntity ) { + return; + } + + // Needed for savegames + splineEnt = splineEntity; + + spline = splineEntity->GetSpline(); + if ( !spline ) { + return; + } + + lastCommand = MOVER_SPLINE; + move_thread = 0; + +// RAVEN BEGIN +// bdube: movement speed + // Use movement speed? + if ( idMath::Fabs(move_speed) >= VECTOR_EPSILON ) { + // Set a fixed time to determine the length from + spline->MakeUniform( 1000 ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + + // Calculate the move time from the speed + move.movetime = SEC2MS( spline->GetLengthForTime(spline->GetTime(spline->GetNumValues() - 1)) / move_speed ); + move_time = move.movetime; + + spline->SetConstantSpeed( move.movetime ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } else { + spline->MakeUniform( move_time ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } +// RAVEN END + + if ( acceltime + deceltime > move_time ) { + acceltime = move_time / 2; + deceltime = move_time - acceltime; + } + + move.stage = FINISHED_STAGE; + move.acceleration = acceltime; + move.movetime = move_time; + move.deceleration = deceltime; + + physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + +// RAVEN BEGIN +// mekberg: let the splines use a state thread instead of linear extrapolation + if ( acceltime ) { + splineStateThread.SetState( "Accel" ); + } else { + splineStateThread.SetState( "Linear" ); + } + + splineStartTime = gameLocal.time; +// RAVEN END +} + +/* +================ +idMover::Event_StopSpline +================ +*/ +void idMover::Event_StopSpline( void ) { + physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); + splineEnt = NULL; +} + +/* +================ +idMover::Event_Activate +================ +*/ +void idMover::Event_Activate( idEntity *activator ) { + Show(); + Event_StartSpline( this ); +} + +/* +================ +idMover::Event_IsMoving +================ +*/ +void idMover::Event_IsMoving( void ) { + if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::Event_IsRotating +================ +*/ +void idMover::Event_IsRotating( void ) { + if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::WriteToSnapshot +================ +*/ +void idMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ( ( thinkFlags & TH_PHYSICS ) != 0 ), 1 ); + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( move.stage, 3 ); + msg.WriteBits( rot.stage, 3 ); + WriteBindToSnapshot( msg ); + WriteGUIToSnapshot( msg ); +} + +/* +================ +idMover::ReadFromSnapshot +================ +*/ +void idMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moveStage_t oldMoveStage = move.stage; + moveStage_t oldRotStage = rot.stage; + + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + if ( !proto69 ) { + // sync down the TH_PHYSICS flag for movers, now that we skip ClientPredictionThink when thinkFlags == 0 and no longer force TH_PHYSICS on + // movers still have prediction issues though, they predict a stop and clear TH_PHYSICS too early + bool physics_on = ( msg.ReadBits( 1 ) != 0 ); + if ( physics_on ) { + thinkFlags |= TH_PHYSICS; + } else { + thinkFlags &= ~TH_PHYSICS; + } + } + + physicsObj.ReadFromSnapshot( msg ); + move.stage = (moveStage_t) msg.ReadBits( 3 ); + rot.stage = (moveStage_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( move.stage != oldMoveStage ) { + UpdateMoveSound( oldMoveStage ); + } + if ( rot.stage != oldRotStage ) { + UpdateRotationSound( oldRotStage ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover::SetPortalState +================ +*/ +void idMover::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +// RAVEN BEGIN +// abahr: +void idMover::Event_GetSplineEntity() { + idThread::ReturnEntity( splineEnt.GetEntity() ); +} + +CLASS_STATES_DECLARATION( idMover ) + STATE( "Accel", idMover::State_Accel ) + STATE( "Linear", idMover::State_Linear ) + STATE( "Decel", idMover::State_Decel ) +END_CLASS_STATES + +// mekberg: spline states +/* +================ +idMover::State_Accel +================ +*/ +stateResult_t idMover::State_Accel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + acceltime ) { + splineStateThread.SetState( "Linear" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Linear +================ +*/ +stateResult_t idMover::State_Linear( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time - deceltime ) { + if ( deceltime ) { + splineStateThread.SetState( "Decel" ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Decel +================ +*/ +stateResult_t idMover::State_Decel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +// RAVEN END + +/* +=============================================================================== + + idSplinePath, holds a spline path to be used by an idMover + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger +const idEventDef EV_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idSplinePath ) +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger + EVENT( EV_Activate, idSplinePath::Event_Toggle ) + EVENT( EV_IsActive, idSplinePath::Event_IsActive ) +// RAVEN END +END_CLASS + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::idSplinePath() { + sampledTimes = NULL; + numSamples = 0; +} + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::~idSplinePath() { + if ( sampledTimes ) { + delete [] sampledTimes; + sampledTimes = NULL; + } +} + +/* +================ +idSplinePath::Spawn +================ +*/ +void idSplinePath::Spawn( void ) { +// RAVEN BEGIN +// abahr: + SetActive( spawnArgs.GetBool("start_active", "1") ); + SampleSpline ( ); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSplinePath::FindTargets +================ +*/ +void idSplinePath::FindTargets() { + idEntity::FindTargets(); + + gameLocal.GetTargets( spawnArgs, backwardPathTargets, "target_reverse" ); + + // This alows us to seperate forward targets from backward targets + for( int ix = backwardPathTargets.Num() - 1; ix >= 0; --ix ) { + targets.Remove( backwardPathTargets[ix] ); + } +} + +/* +================ +idSplinePath::SortTargets +================ +*/ +int rvSortByActiveState( const void* a, const void* b ) { + idEntityPtr splineA; + idEntityPtr splineB; + + splineA = *(idEntityPtr*)a; + splineB = *(idEntityPtr*)b; + + return splineB->IsActive() - splineA->IsActive(); +} +int idSplinePath::SortTargets( idList< idEntityPtr >& list ) { + int numActive = 0; + idSplinePath* target = NULL; + + RemoveNullTargets(); + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByActiveState ); + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + target = static_cast( list[ix].GetEntity() ); + if( target->IsActive() ) { + numActive++; + } + } + + return numActive; +} + +int idSplinePath::SortTargets() { + return SortTargets( targets ); +} + +int idSplinePath::SortBackwardsTargets() { + return SortTargets( backwardPathTargets ); +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( idList< idEntityPtr >& list ) { + int i; + + for( i = list.Num() - 1; i >= 0; i-- ) { + if ( !list[ i ].GetEntity() ) { + list.RemoveIndex( i ); + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < list.Num(); i++ ) { + ent = list[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( void ) { + RemoveNullTargets( targets ); + RemoveNullTargets( backwardPathTargets ); +} + +/* +============================== +idSplinePath::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idSplinePath::ActivateTargets( idEntity *activator ) const { + ActivateTargets( activator, targets ); + ActivateTargets( activator, backwardPathTargets ); +} + +/* +================ +idSplinePath::Save +================ +*/ +void idSplinePath::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); +} + +/* +================ +idSplinePath::Restore +================ +*/ +void idSplinePath::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); +} + +/* +================ +idSplinePath::Event_IsActive +================ +*/ +void idSplinePath::Event_IsActive() { + idThread::ReturnInt( IsActive() ); +} + +/* +================ +idSplinePath::SampleSpline +================ +*/ +void idSplinePath::SampleSpline ( void ) { + int i; + float splineLength; + idCurve_Spline* tempSpline = GetSpline ( ); + splineLength = tempSpline->GetLengthForTime( tempSpline->GetTime(tempSpline->GetNumValues() - 1) ); + + if ( splineLength > SPLINE_SAMPLE_RATE ) { + numSamples = int( splineLength / SPLINE_SAMPLE_RATE ); + sampledTimes = new float[ numSamples ]; + float stepSize = splineLength / ( numSamples - 1 ); + + for ( i = 0; i < numSamples; i++ ) { + float time = float( i * stepSize ); + if ( time >= splineLength ) { + time = splineLength; + } + sampledTimes[ i ] = tempSpline->GetTimeForLength ( time, 0.01f ); + } + } + SAFE_DELETE_PTR( tempSpline ); +} + +/* +================ +idSplinePath::GetSampledTime +================ +*/ +float idSplinePath::GetSampledTime ( float distance ) const { + if ( sampledTimes && distance >= 0.0f ) { + int lowIndex, highIndex; + float lerp = distance / SPLINE_SAMPLE_RATE; + int actualLowIndex = int( lerp ); + lowIndex = idMath::ClampInt ( 0, numSamples - 2, actualLowIndex ); + highIndex = lowIndex + 1; + lerp = lerp - idMath::Floor ( lerp ); + return ( actualLowIndex != lowIndex ) ? sampledTimes[ highIndex ] : idMath::Lerp( sampledTimes[ lowIndex ], sampledTimes[ highIndex ], lerp ); + } + return -1.0f; +} + +// RAVEN END + + +/* +=============================================================================== + +idElevator + +=============================================================================== +*/ +const idEventDef EV_PostArrival( "postArrival", NULL ); +const idEventDef EV_GotoFloor( "gotoFloor", "d" ); +const idEventDef EV_UpdateFloorInfo ( "updateFloorInfo", NULL ); + +CLASS_DECLARATION( idMover, idElevator ) + EVENT( EV_Activate, idElevator::Event_Activate ) + EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) + EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) + EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) + EVENT( EV_Touch, idElevator::Event_Touch ) + EVENT( EV_UpdateFloorInfo, idElevator::Event_UpdateFloorInfo ) +END_CLASS + +/* +================ +idElevator::idElevator +================ +*/ +idElevator::idElevator( void ) { + state = INIT; + floorInfo.Clear(); + currentFloor = 0; + pendingFloor = 0; + lastFloor = 0; + controlsDisabled = false; + lastTouchTime = 0; + returnFloor = 0; + returnTime = 0; +} + +/* +================ +idElevator::Save +================ +*/ +void idElevator::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( (int)state ); + + savefile->WriteInt( floorInfo.Num() ); + for ( i = 0; i < floorInfo.Num(); i++ ) { + savefile->WriteVec3( floorInfo[ i ].pos ); + savefile->WriteString( floorInfo[ i ].door ); + savefile->WriteInt( floorInfo[ i ].floor ); + } + + savefile->WriteInt( currentFloor ); + savefile->WriteInt( pendingFloor ); + savefile->WriteInt( lastFloor ); + savefile->WriteBool( controlsDisabled ); +// savefile->WriteBool( waitingForPlayerFollowers ); + savefile->WriteFloat( returnTime ); + savefile->WriteInt( returnFloor ); + savefile->WriteInt( lastTouchTime ); +} + +/* +================ +idElevator::Restore +================ +*/ +void idElevator::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)state ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + floorInfo_s floor; + + savefile->ReadVec3( floor.pos ); + savefile->ReadString( floor.door ); + savefile->ReadInt( floor.floor ); + + floorInfo.Append( floor ); + } + + savefile->ReadInt( currentFloor ); + savefile->ReadInt( pendingFloor ); + savefile->ReadInt( lastFloor ); + savefile->ReadBool( controlsDisabled ); +// savefile->ReadBool( waitingForPlayerFollowers ); + savefile->ReadFloat( returnTime ); + savefile->ReadInt( returnFloor ); + savefile->ReadInt( lastTouchTime ); +} + +/* +================ +idElevator::Spawn +================ +*/ +void idElevator::Spawn( void ) { + lastFloor = 0; + currentFloor = 0; + pendingFloor = spawnArgs.GetInt( "floor", "1" ); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); + + returnTime = spawnArgs.GetFloat( "returnTime" ); + returnFloor = spawnArgs.GetInt( "returnFloor" ); + + UpdateFloorInfo ( ); + + lastTouchTime = 0; + state = INIT; + BecomeActive( TH_THINK | TH_PHYSICS ); + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); + controlsDisabled = false; +// waitingForPlayerFollowers = false; +} + +/* +============== +idElevator::UpdateFloorInfo +=============== +*/ +void idElevator::UpdateFloorInfo ( void ) { + int len1; + idStr str; + + floorInfo.Clear ( ); + + len1 = strlen( "floorPos_" ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); + while( kv ) { + str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); + floorInfo_s fi; + fi.floor = atoi( str ); + fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); + fi.pos = spawnArgs.GetVector( kv->GetKey() ); + floorInfo.Append( fi ); + kv = spawnArgs.MatchPrefix( "floorPos_", kv ); + } +} + +/* +============== +idElevator::Event_UpdateFloorInfo +=============== +*/ +void idElevator::Event_UpdateFloorInfo ( void ) { + UpdateFloorInfo ( ); +} + +/* +============== +idElevator::Event_Touch +=============== +*/ +void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { + + if ( gameLocal.time < lastTouchTime + 2000 ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + lastTouchTime = gameLocal.time; + + if ( thinkFlags & TH_PHYSICS ) { + return; + } + + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); + } +} + +/* +================ +idElevator::Think +================ +*/ +void idElevator::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( state == INIT ) { + state = IDLE; + if ( doorent ) { + doorent->BindTeam( this ); + doorent->spawnArgs.Set( "snd_open", "" ); + doorent->spawnArgs.Set( "snd_close", "" ); + doorent->spawnArgs.Set( "snd_opened", "" ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->SetCompanion( doorent ); + } + } + + Event_GotoFloor( pendingFloor ); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + +// RAVEN BEGIN +// bdube: provide floor information to status guis + if ( floorInfo.Num ( ) > 0 ) { + int j; + // Guis on the elevator are considered status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI && renderEntity.gui[j]; j++ ) { + InitStatusGui ( renderEntity.gui[j] ); + } + + // Initialize all the status guis of the elevator + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix( "statusGui" ); kv; kv = spawnArgs.MatchPrefix( "statusGui", kv ) ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( !ent || !ent->GetRenderEntity() ) { + continue; + } + for ( j = 0; j < MAX_RENDERENTITY_GUI && ent->GetRenderEntity()->gui[j]; j++ ) { + InitStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + } +// RAVEN END + + } else if ( state == WAITING_ON_DOORS ) { + if ( doorent ) { + state = doorent->IsOpen() ? WAITING_ON_DOORS : IDLE; + } else { + state = IDLE; + } + if ( state == IDLE ) { + lastFloor = currentFloor; + currentFloor = pendingFloor; + floorInfo_s *fi = GetFloorInfo( currentFloor ); + if ( fi ) { + MoveToPos( fi->pos ); + } + } + } + RunPhysics(); + Present(); +} + +/* +================ +idElevator::Event_Activate +================ +*/ +void idElevator::Event_Activate( idEntity *activator ) { + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + Event_GotoFloor( triggerFloor ); + } +} + +/* +================ +idElevator::Event_TeamBlocked +================ +*/ +void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( blockedEntity == this ) { + Event_GotoFloor( lastFloor ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( blockedEntity && blockedEntity->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + // open the inner doors if one is blocked + idDoor *blocked = static_cast( blockedEntity ); + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && blocked->GetMoveMaster() == door->GetMoveMaster() ) { + door->SetBlocked(true); + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } + } +} + + +/* +=============== +idElevator::HandleSingleGuiCommand +=============== +*/ +bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( controlsDisabled ) { + return false; + } + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "changefloor" ) == 0 ) { + if ( src->ReadToken( &token ) ) { +// RAVEN BEGIN +// bdube: up and down floor commands + int newFloor; + if (!token.Cmp("up")) { + newFloor = currentFloor + 1; + } else if (!token.Cmp("down")) { + newFloor = currentFloor - 1; + } else { + newFloor = atoi( token ); + } +// RAVEN END + + if ( newFloor == currentFloor ) { + // open currentFloor and interior doors + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } else { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, newFloor ); + } else { + ProcessEvent( &EV_GotoFloor, newFloor ); + } + } + return true; + } + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +idElevator::OpenFloorDoor +================ +*/ +void idElevator::OpenFloorDoor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( fi->door ); + if ( door ) { + door->Open(); + } + } +} + +/* +================ +idElevator::OpenInnerDoor +================ +*/ +void idElevator::OpenInnerDoor( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Open(); + } +} + +/* +================ +idElevator::GetFloorInfo +================ +*/ +floorInfo_s *idElevator::GetFloorInfo( int floor ) { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == floor ) { + return &floorInfo[i]; + } + } + return NULL; +} + +/* +================ +idElevator::Event_GotoFloor +================ +*/ +void idElevator::Event_GotoFloor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + if ( door->IsBlocked() || door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + /* + if ( !gameLocal.isMultiplayer ) { + //FIXME: make sure player is my activator? + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->GetGroundEntity() ) { + //player in air + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + if ( player->GetGroundElevator() == this ) { + idActor* actor = NULL; + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + if ( !actor->IsHidden() && actor->health > 0 && actor->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)(actor))->leader == player && !((idAI*)(actor))->move.fl.disabled && !((idAI*)(actor))->aifl.scripted ) { + if ( actor->GetGroundElevator( this ) != this ) { + waitingForPlayerFollowers = true; + //follower of player is not standing on me, don't move! + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + } + } + } else if ( waitingForPlayerFollowers ) { + //player got off, cancel + waitingForPlayerFollowers = false; + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + UpdateStatusGuis ( ); + return; + } + } + } + waitingForPlayerFollowers = false; + */ + DisableAllDoors(); + CloseAllDoors(); + state = WAITING_ON_DOORS; + pendingFloor = floor; + } +} + +/* +================ +idElevator::BeginMove +================ +*/ +void idElevator::BeginMove( idThread *thread ) { + controlsDisabled = true; + CloseAllDoors(); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); + +// RAVEN BEGIN +// bdube: replaced with function + SetAASAreaState ( true ); + + idMover::BeginMove( thread ); + + UpdateStatusGuis ( ); +// RAVEN END +} + +/* +================ +idElevator::GetDoor +================ +*/ +idDoor *idElevator::GetDoor( const char *name ) { + idEntity *ent; + idEntity *master; + idDoor *doorEnt; + + doorEnt = NULL; + if ( name && *name ) { + ent = gameLocal.FindEntity( name ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( ent ); + master = doorEnt->GetMoveMaster(); + if ( master != doorEnt ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( master ); + } else { + doorEnt = NULL; + } + } + } + } + + return doorEnt; +} + +/* +================ +idElevator::Event_PostFloorArrival +================ +*/ +void idElevator::Event_PostFloorArrival() { + OpenFloorDoor( currentFloor ); + OpenInnerDoor(); + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + controlsDisabled = false; + if ( returnTime > 0.0f && returnFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); + } +} + +/* +================ +idElevator::DoneMoving +================ +*/ +void idElevator::DoneMoving( void ) { + idMover::DoneMoving(); + EnableProperDoors(); +// RAVEN BEGIN +// bdube: factored into a function + UpdateStatusGuis ( ); +// RAVEN END + if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { + PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); + } else { + Event_PostFloorArrival(); + } + +// RAVEN BEGIN +// kfuller: we want an elevator to fire its targets when it reaches a floor + SetAASAreaState( false ); + + // Floor targets + if ( lastFloor != 0 ) { + const char* floorTarget = spawnArgs.GetString ( va("floorTarget_%d", currentFloor ) ); + if ( floorTarget && *floorTarget ) { + idEntity* ent = gameLocal.FindEntity ( floorTarget ); + if ( ent ) { + ent->ProcessEvent ( &EV_Activate, this ); + } + } + } + + if (spawnArgs.GetInt("fireTargetsAtFloor") == currentFloor) { + ActivateTargets(gameLocal.entities[ENTITYNUM_WORLD]); + spawnArgs.SetInt("fireTargetsAtFloor", -1); + } +// RAVEN END +} + +/* +================ +idElevator::CloseAllDoors +================ +*/ +void idElevator::CloseAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Close(); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Close(); + } + } +} + +/* +================ +idElevator::DisableAllDoors +================ +*/ +void idElevator::DisableAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( false ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( false ); + } + } +} + +/* +================ +idElevator::EnableProperDoors +================ +*/ +void idElevator::EnableProperDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( true ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == currentFloor ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( true ); + break; + } + } + } +} + +// RAVEN BEGIN +// bdube: more advanced status gui control + +/* +================ +idElevator::SetAASAreaState +================ +*/ +void idElevator::SetAASAreaState ( bool enable ) { + idEntity* ents[16]; + int numEnts; + numEnts = gameLocal.EntitiesTouchingBounds ( this, physicsObj.GetAbsBounds(), CONTENTS_AAS_OBSTACLE, ents, 16 ); + for ( numEnts--; numEnts >= 0; numEnts -- ) { + idFuncAASObstacle* obstacle = dynamic_cast(ents[numEnts]); + if ( obstacle ) { + obstacle->SetState ( enable ); + } + } +} + +/* +================ +idElevator::InitStatusGui +================ +*/ +void idElevator::InitStatusGui ( idUserInterface* gui ) { + int floor; + int topFloor; + int bottomFloor; + + topFloor = -1; + bottomFloor = 9999; + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + topFloor = Max( floorInfo[floor].floor, topFloor ); + bottomFloor = Min( floorInfo[floor].floor, bottomFloor ); + } + + gui->SetStateInt ( "topFloor", topFloor ); + gui->SetStateInt ( "bottomFloor", bottomFloor ); + + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + idStr keySrc; + idStr keyDest; + gui->SetStateInt ( va("floorNumber_%d", floor ), floorInfo[floor].floor ); + keySrc = va("floorName_%d", floorInfo[floor].floor ); + keyDest = va("floorName_%d", floor ); + gui->SetStateString ( keyDest, spawnArgs.GetString ( keySrc, va("%d", floorInfo[floor].floor ) ) ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGui +================ +*/ +void idElevator::UpdateStatusGui ( idUserInterface* gui ) { + idStr floorName; + floorName = va("floorName_%d", currentFloor ); + gui->SetStateInt ( "floor", (physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE) ? currentFloor : lastFloor ); + gui->SetStateInt ( "floorNext", currentFloor ); + gui->SetStateString( "floorName", spawnArgs.GetString ( floorName, va("%d", currentFloor ) ) ); + gui->StateChanged( gameLocal.time, true ); + + // mekberg: trigger all status guis if we moved the elevator from another gui. + if ( lastFloor && !( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) ) { + gui->HandleNamedEvent( "triggerGui" ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGuis +================ +*/ +void idElevator::UpdateStatusGuis ( void ) { + int j; + + // Treat the guis on the elevator as status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( renderEntity.gui[ j ] ) { + UpdateStatusGui ( renderEntity.gui[ j ] ); + } + } + + // All entities linked as status guis should get updated + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + UpdateStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } +} +// RAVEN END + +/* +=============================================================================== + +idMover_Binary + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" + +=============================================================================== +*/ + +const idEventDef EV_Mover_ReturnToPos1( "", NULL ); +const idEventDef EV_Mover_MatchTeam( "", "dd" ); +const idEventDef EV_Mover_Enable( "enable", NULL ); +const idEventDef EV_Mover_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idMover_Binary ) + EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) + EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) + EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) + EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) + EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) + EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) + EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) + EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) + EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) +END_CLASS + +/* +================ +idMover_Binary::idMover_Binary() +================ +*/ +idMover_Binary::idMover_Binary() { + pos1.Zero(); + pos2.Zero(); + moverState = MOVER_POS1; + moveMaster = NULL; + activateChain = NULL; + soundPos1 = 0; + sound1to2 = 0; + sound2to1 = 0; + soundPos2 = 0; + soundLoop = 0; + wait = 0.0f; + damage = 0.0f; + duration = 0; + accelTime = 0; + decelTime = 0; + activatedBy = this; + stateStartTime = 0; + team.Clear(); + enabled = false; + deferedOpen = false; + move_thread = 0; + updateStatus = 0; + areaPortal = 0; + blocked = false; + fl.networkSync = true; +} + +/* +================ +idMover_Binary::~idMover_Binary +================ +*/ +idMover_Binary::~idMover_Binary() { + idMover_Binary *mover; + + // if this is the mover master + if ( this == moveMaster ) { + // make the next mover in the chain the move master + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + mover->moveMaster = this->activateChain; + } + } + else { + // remove mover from the activate chain + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + if ( mover->activateChain == this ) { + mover->activateChain = this->activateChain; + break; + } + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMover_Binary::Save +================ +*/ +void idMover_Binary::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteVec3( pos1 ); + savefile->WriteVec3( pos2 ); + savefile->WriteInt( (moverState_t)moverState ); + + savefile->WriteObject( moveMaster ); + savefile->WriteObject( activateChain ); + + savefile->WriteInt( soundPos1 ); + savefile->WriteInt( sound1to2 ); + savefile->WriteInt( sound2to1 ); + savefile->WriteInt( soundPos2 ); + savefile->WriteInt( soundLoop ); + + savefile->WriteFloat( wait ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( duration ); + savefile->WriteInt( accelTime ); + savefile->WriteInt( decelTime ); + + activatedBy.Save( savefile ); + + savefile->WriteInt( stateStartTime ); + savefile->WriteString( team ); + savefile->WriteBool( enabled ); + savefile->WriteBool( deferedOpen ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( updateStatus ); + + savefile->WriteInt( buddies.Num() ); + for ( i = 0; i < buddies.Num(); i++ ) { + savefile->WriteString( buddies[ i ] ); + } + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( blocked ); + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } +} + +/* +================ +idMover_Binary::Restore +================ +*/ +void idMover_Binary::Restore( idRestoreGame *savefile ) { + int i, num, portalState; + idStr temp; + + savefile->ReadVec3( pos1 ); + savefile->ReadVec3( pos2 ); + savefile->ReadInt( (int &)moverState ); + + savefile->ReadObject( reinterpret_cast( moveMaster ) ); + savefile->ReadObject( reinterpret_cast( activateChain ) ); + + savefile->ReadInt( soundPos1 ); + savefile->ReadInt( sound1to2 ); + savefile->ReadInt( sound2to1 ); + savefile->ReadInt( soundPos2 ); + savefile->ReadInt( soundLoop ); + + savefile->ReadFloat( wait ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( duration ); + savefile->ReadInt( accelTime ); + savefile->ReadInt( decelTime ); + + activatedBy.Restore( savefile ); + + savefile->ReadInt( stateStartTime ); + + savefile->ReadString( team ); + savefile->ReadBool( enabled ); + savefile->ReadBool( deferedOpen ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( updateStatus ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( temp ); + buddies.Append( temp ); + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( blocked ); + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } +} + +/* +================ +idMover_Binary::Spawn + +Base class for all movers. + +"wait" wait before returning (3 default, -1 = never return) +"speed" movement speed +================ +*/ +void idMover_Binary::Spawn( void ) { + idEntity *ent; + const char *temp; + + move_thread = 0; + enabled = true; + areaPortal = 0; + + activateChain = NULL; + + spawnArgs.GetFloat( "wait", "0", wait ); + + spawnArgs.GetInt( "updateStatus", "0", updateStatus ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); + while( kv ) { + buddies.Append( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "buddy", kv ); + } + + spawnArgs.GetString( "team", "", &temp ); + team = temp; + + if ( !team.Length() ) { + ent = this; + } else { + // find the first entity spawned on this team (which could be us) + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { + break; + } + } + if ( !ent ) { + ent = this; + } + } + moveMaster = static_cast(ent); + + // create a physics team for the binary mover parts + if ( ent != this ) { + JoinTeam( ent ); + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( moveMaster != this ) { + JoinActivateTeam( moveMaster ); + } + + idBounds soundOrigin; + idMover_Binary *slave; + + soundOrigin.Clear(); + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + soundOrigin += slave->GetPhysics()->GetAbsBounds(); + } + moveMaster->refSound.origin = soundOrigin.GetCenter(); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } +} + +/* +=============== +idMover_Binary::GetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { + if ( angle == -1 ) { + movedir.Set( 0, 0, 1 ); + } else if ( angle == -2 ) { + movedir.Set( 0, 0, -1 ); + } else { + movedir = idAngles( 0, angle, 0 ).ToForward(); + } +} + +/* +================ +idMover_Binary::Event_SetCallback +================ +*/ +void idMover_Binary::Event_SetCallback( void ) { + if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idMover_Binary::UpdateMoverSound +=============== +*/ +void idMover_Binary::UpdateMoverSound( moverState_t state ) { + if ( moveMaster == this ) { + switch( state ) { + case MOVER_POS1: + break; + case MOVER_POS2: + break; + case MOVER_1TO2: + StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); + break; + case MOVER_2TO1: + StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } +} + +/* +=============== +idMover_Binary::SetMoverState +=============== +*/ +void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { + idVec3 delta; + + moverState = newstate; + move_thread = 0; + + UpdateMoverSound( newstate ); + + stateStartTime = time; + switch( moverState ) { + case MOVER_POS1: { + Signal( SIG_MOVER_POS1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); + break; + } + case MOVER_POS2: { + Signal( SIG_MOVER_POS2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); + break; + } + case MOVER_1TO2: { + Signal( SIG_MOVER_1TO2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + case MOVER_2TO1: { + Signal( SIG_MOVER_2TO1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + } +} + +/* +================ +idMover_Binary::MatchActivateTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { + idMover_Binary *slave; + deferedOpen = false; + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->SetMoverState( newstate, time ); + } +} + +/* +================ +idMover_Binary::Enable +================ +*/ +void idMover_Binary::Enable( bool b ) { + enabled = b; +} + +/* +================ +idMover_Binary::Event_MatchActivateTeam +================ +*/ +void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { + MatchActivateTeam( newstate, time ); +} + +/* +================ +idMover_Binary::BindTeam + +All entities in a mover team will be bound +================ +*/ +void idMover_Binary::BindTeam( idEntity *bindTo ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->Bind( bindTo, true ); + } +} + +/* +================ +idMover_Binary::JoinActivateTeam + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { + this->activateChain = master->activateChain; + master->activateChain = this; +} + +/* +================ +idMover_Binary::Event_Enable + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::Event_Enable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_Disable + +Set all entities in a mover team to be disabled +================ +*/ +void idMover_Binary::Event_Disable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover_Binary::Event_OpenPortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( slave->areaPortal ) { + slave->SetPortalState( true ); + } + } +} + +/* +================ +idMover_Binary::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover_Binary::Event_ClosePortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( !slave->IsHidden() ) { + if ( slave->areaPortal ) { + slave->SetPortalState( false ); + } + } + } +} + +/* +================ +idMover_Binary::Event_ReturnToPos1 +================ +*/ +void idMover_Binary::Event_ReturnToPos1( void ) { + MatchActivateTeam( MOVER_2TO1, gameLocal.time ); +} + +/* +================ +idMover_Binary::Event_Reached_BinaryMover +================ +*/ +void idMover_Binary::Event_Reached_BinaryMover( void ) { + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + if ( moveMaster == this ) { + StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); + } + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 1 ); +// RAVEN END + + if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { + // return to pos1 after a delay + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + + SetBlocked ( false ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 0 ); +// RAVEN END + + // close areaportals + if ( moveMaster == this ) { +// RAVEN BEGIN +// kfuller: added "snd_closed" + StartSound( "snd_closed", SND_CHANNEL_ANY, 0, false, NULL ); +// RAVEN END + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, wait, this ); + } + + SetBlocked ( false ); + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +idMover_Binary::GotoPosition1 +================ +*/ +void idMover_Binary::GotoPosition1( void ) { + idMover_Binary *slave; + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition1(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS2 ) { + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } + if ( !spawnArgs.GetBool( "toggle" ) ) { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + // if already at at position 1 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::GotoPosition2 +================ +*/ +void idMover_Binary::GotoPosition2( void ) { + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition2(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS1 ) { + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + + // only partway up before reversing + if ( moverState == MOVER_2TO1 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + // if already at at position 2 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::UpdateBuddies +================ +*/ +void idMover_Binary::UpdateBuddies( int val ) +{ +// RAVEN BEGIN +// jdischler: was using update status but that was never getting set anyway. +// Additionally, shaderparm_mode was never getting set on the mover itself, which creates +// extra work for the designers. + int c = buddies.Num(); + for ( int i = 0; i < c; i++ ) { + idEntity *buddy = gameLocal.FindEntity( buddies[i] ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, val ); + buddy->UpdateVisuals(); + } + } + // Update the mover itself, too. + SetShaderParm( SHADERPARM_MODE, val ); + UpdateVisuals(); +// RAVEN END +} + +/* +================ +idMover_Binary::SetGuiStates +================ +*/ +void idMover_Binary::SetGuiStates( const char *state ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + + idMover_Binary *mb = activateChain; + while( mb ) { + if ( mb->guiTargets.Num() ) { + mb->SetGuiState( "movestate", state ); + } + mb = mb->activateChain; + } +} + +/* +================ +idMover_Binary::Use_BinaryMover +================ +*/ +void idMover_Binary::Use_BinaryMover( idEntity *activator ) { + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + activatedBy = activator; + + if ( moverState == MOVER_POS1 && !deferedOpen) { + + float openWait = spawnArgs.GetFloat( "openWait", "0" ); + deferedOpen = true; + PostEventMS( &EV_Mover_MatchTeam, SEC2MS(openWait), MOVER_1TO2, SEC2MS(openWait) + gameLocal.time ); + + // TODO: might want to delay these as well if openWait is present? + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + + // any things we should trigger when the door is first asked to open? + // NOTE: with openWait, it's possible (and desirable as per aweldon's request) + // that this will be called before the door actually opens. This is specifically + // used by the rotate/lock doors...the triggering below starts another entity rotating + // and onWait is used to offset the actual open so the rotating piece has time to work... + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOnOpen" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOnOpen", kv ); + } + + return; + } + + // if all the way up, just delay before coming down + if ( moverState == MOVER_POS2 ) { + idMover_Binary *slave; + + if ( wait == -1 ) { + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); + } + return; + } + + // only partway down before reversing + if ( moverState == MOVER_2TO1 ) { + GotoPosition2(); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + GotoPosition1(); + return; + } +} + +/* +================ +idMover_Binary::Event_Use_BinaryMover +================ +*/ +void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { + Use_BinaryMover( activator ); +} + +/* +================ +idMover_Binary::PreBind +================ +*/ +void idMover_Binary::PreBind( void ) { + pos1 = GetWorldCoordinates( pos1 ); + pos2 = GetWorldCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::PostBind +================ +*/ +void idMover_Binary::PostBind( void ) { + pos1 = GetLocalCoordinates( pos1 ); + pos2 = GetLocalCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::FindGuiTargets +================ +*/ +void idMover_Binary::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover_Binary::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover_Binary::SetGuiState( const char *key, const char *val ) const { + int i; + + for( i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_InitGuiTargets( void ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); + } +} + +/* +================ +idMover_Binary::InitSpeed + +pos1, pos2, and speed are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { + idVec3 move; + float distance; + float speed; + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + speed = mspeed ? mspeed : 100; + + // calculate time to reach second position from speed + move = pos2 - pos1; + distance = move.Length(); + duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::InitTime + +pos1, pos2, and time are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::SetBlocked +================ +*/ +void idMover_Binary::SetBlocked( bool b ) { + for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->blocked = b; + if ( b ) { + const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); + } + } + } +} + +/* +================ +idMover_Binary::IsBlocked +================ +*/ +bool idMover_Binary::IsBlocked( void ) { + return blocked; +} + +/* +================ +idMover_Binary::GetActivator +================ +*/ +idEntity *idMover_Binary::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +idMover_Binary::WriteToSnapshot +================ +*/ +void idMover_Binary::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( moverState, 3 ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Binary::ReadFromSnapshot +================ +*/ +void idMover_Binary::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moverState_t oldMoverState = moverState; + + physicsObj.ReadFromSnapshot( msg ); + moverState = (moverState_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( moverState != oldMoverState ) { + UpdateMoverSound( moverState ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover_Binary::SetPortalState +================ +*/ +void idMover_Binary::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + +idDoor + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +const idEventDef EV_Door_StartOpen( "", NULL ); +const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); +const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); +const idEventDef EV_Door_Open( "open", NULL ); +const idEventDef EV_Door_Close( "close", NULL ); +const idEventDef EV_Door_Lock( "lock", "d" ); +const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); +const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); + +CLASS_DECLARATION( idMover_Binary, idDoor ) + EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) + EVENT( EV_Touch, idDoor::Event_Touch ) + EVENT( EV_Activate, idDoor::Event_Activate ) + EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) + EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) + EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) + EVENT( EV_Door_Open, idDoor::Event_Open ) + EVENT( EV_Door_Close, idDoor::Event_Close ) + EVENT( EV_Door_Lock, idDoor::Event_Lock ) + EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) + EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) + EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) + EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) + EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) +// RAVEN BEGIN +// abahr: + EVENT( EV_Mover_ReturnToPos1, idDoor::Event_ReturnToPos1 ) +// RAVEN END +END_CLASS + +/* +================ +idDoor::idDoor +================ +*/ +idDoor::idDoor( void ) { + triggersize = 1.0f; + crusher = false; + noTouch = false; + aas_area_closed = false; + buddyStr.Clear(); + trigger = NULL; + sndTrigger = NULL; + nextSndTriggerTime = 0; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); + requires.Clear(); + removeItem = 0; + syncLock.Clear(); + companionDoor = NULL; + normalAxisIndex = 0; +} + +/* +================ +idDoor::~idDoor +================ +*/ +idDoor::~idDoor( void ) { + if ( trigger ) { + delete trigger; + } + if ( sndTrigger ) { + delete sndTrigger; + } +} + +/* +================ +idDoor::Save +================ +*/ +void idDoor::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( triggersize ); + savefile->WriteBool( crusher ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteString( buddyStr ); + + savefile->WriteClipModel( trigger ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); + + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); + + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteString( syncLock ); + savefile->WriteInt( normalAxisIndex ); + + savefile->WriteObject( companionDoor ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Save( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Restore +================ +*/ +void idDoor::Restore( idRestoreGame *savefile ) { + + savefile->ReadFloat( triggersize ); + savefile->ReadBool( crusher ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( aas_area_closed ); + SetAASAreaState( aas_area_closed ); + savefile->ReadString( buddyStr ); + + savefile->ReadClipModel( trigger ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); + + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); + + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadString( syncLock ); + savefile->ReadInt( normalAxisIndex ); + + savefile->ReadObject( reinterpret_cast( companionDoor ) ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Restore( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Spawn +================ +*/ +void idDoor::Spawn( void ) { + idVec3 abs_movedir; + float distance; + idVec3 size; + idVec3 movedir; + float dir; + float lip; + bool start_open; + float time; + float speed; + + // get the direction to move + if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { + // no movedir, so angle defines movement direction and not orientation, + // a la oldschool Quake + SetAngles( ang_zero ); + spawnArgs.GetFloat( "angle", "0", dir ); + } + GetMovedir( dir, movedir ); + + // default speed of 400 + spawnArgs.GetFloat( "speed", "400", speed ); + + // default wait of 2 seconds + spawnArgs.GetFloat( "wait", "3", wait ); + + // default lip of 8 units + spawnArgs.GetFloat( "lip", "8", lip ); + + // by default no damage + spawnArgs.GetFloat( "damage", "0", damage ); + + // trigger size + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + + spawnArgs.GetBool( "crusher", "0", crusher ); + spawnArgs.GetBool( "start_open", "0", start_open ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // expects syncLock to be a door that must be closed before this door will open + spawnArgs.GetString( "syncLock", "", syncLock ); + + spawnArgs.GetString( "buddy", "", buddyStr ); + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + + // ever separate piece of a door is considered solid when other team mates push entities + fl.solidForTeam = true; + + // first position at start + pos1 = GetPhysics()->GetOrigin(); + + // calculate second position + abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); + abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); + abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); + size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; + distance = ( abs_movedir * size ) - lip; + pos2 = pos1 + distance * movedir; + + // if "start_open", reverse position 1 and 2 + if ( start_open ) { + // post it after EV_SpawnBind + PostEventMS( &EV_Door_StartOpen, 1 ); + } + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } + + if ( moveMaster == this ) { + if ( health ) { + fl.takedamage = true; + } + if ( noTouch || health ) { + // non touch/shoot doors + PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.time ); + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } else { + // spawn trigger + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + if ( !start_open ) { + // start closed + ProcessEvent( &EV_Mover_ClosePortal ); + } + + int locked = spawnArgs.GetInt( "locked" ); + if ( locked ) { + // make sure all members of the team get locked + PostEventMS( &EV_Door_Lock, 0, locked ); + } + + if ( spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); + } + + // sounds have a habit of stuttering when portals close, so make them unoccluded + refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; + + companionDoor = NULL; + + enabled = true; + blocked = false; + +// RAVEN BEGIN +// bdube: added + // Instruct ai to avoid standing in doors + if ( !spawnArgs.GetBool ( "noavoid" ) ) { + aiManager.AddAvoid ( GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetBounds().GetRadius(), -1 ); + } +// RAVEN END +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + if ( sndTrigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idDoor::PreBind +================ +*/ +void idDoor::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idDoor::PostBind +================ +*/ +void idDoor::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); +} + +/* +================ +idDoor::SetAASAreaState +================ +*/ +void idDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); +} + +/* +================ +idDoor::Hide +================ +*/ +void idDoor::Hide( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Hide(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Hide(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Disable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Disable(); + } + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + slave->GetPhysics()->GetClipModel()->Disable(); + slave->idMover_Binary::Hide(); + } + } +} + +/* +================ +idDoor::Show +================ +*/ +void idDoor::Show( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Show(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Show(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Enable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Enable(); + } + if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + slave->GetPhysics()->GetClipModel()->Enable(); + slave->idMover_Binary::Show(); + } + } +} + +/* +================ +idDoor::GetLocalTriggerPosition +================ +*/ +void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +================ +idDoor::Use +================ +*/ +void idDoor::Use( idEntity *other, idEntity *activator ) { + if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + ActivateTargets( activator ); + Use_BinaryMover( activator ); + } +} + +/* +================ +idDoor::Open +================ +*/ +void idDoor::Open( void ) { + GotoPosition2(); +} + +/* +================ +idDoor::Close +================ +*/ +void idDoor::Close( void ) { + GotoPosition1(); +} + +/* +================ +idDoor::Lock +================ +*/ +void idDoor::Lock( int f ) { + idMover_Binary *other; + + // lock all the doors on the team + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + idDoor *door = static_cast( other ); + if ( other == moveMaster ) { + if ( door->sndTrigger == NULL ) { + // in this case the sound trigger never got spawned + const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); + if ( sndtemp && *sndtemp ) { + door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } + if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { + door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + door->spawnArgs.SetInt( "locked", f ); +// RAVEN BEGIN +// jdischler + // locking/unlocking doors should update the shaderparm7 of any buddies + door->UpdateBuddies( !f ); +// RAVEN END + + if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { + door->SetAASAreaState( f != 0 ); + } + } + } + + if ( f ) { + Close(); + } +} + +/* +================ +idDoor::IsLocked +================ +*/ +int idDoor::IsLocked( void ) { + return spawnArgs.GetInt( "locked" ); +} + +/* +================ +idDoor::IsOpen +================ +*/ +bool idDoor::IsOpen( void ) { + return ( moverState != MOVER_POS1 ); +} + +/* +================ +idDoor::IsNoTouch +================ +*/ +bool idDoor::IsNoTouch( void ) { + return noTouch; +} + +/* +====================== +idDoor::CalcTriggerBounds + +Calcs bounds for a trigger. +====================== +*/ +void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { + idMover_Binary *other; + int i; + int best; + + // find the bounds of everything on the team + bounds = GetPhysics()->GetAbsBounds(); + + if ( health > 0 ) { + fl.takedamage = true; + } + for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + // find the bounds of everything on the team + bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); + + // set all of the slaves as shootable + other->fl.takedamage = true; + } + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + normalAxisIndex = best; + bounds[0][ best ] -= size; + bounds[1][ best ] += size; + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); +} + +// RAVEN BEGIN +// abahr: +/* +============================== +idDoor::SetDoorFrameController +============================== +*/ +void idDoor::SetDoorFrameController( idEntity* controller ) { + doorFrameController = controller; +} + +/* +============================== +idDoor::ActivateTargets + +If we have a frame controller we activate its targets +============================== +*/ +void idDoor::ActivateTargets( idEntity *activator ) const { + if ( doorFrameController.IsValid() && static_cast< const idMover_Binary *>( GetMoveMaster() ) == this && moverState == MOVER_POS1 ) { + doorFrameController->ActivateTargets( activator ); + } + + idMover_Binary::ActivateTargets( activator ); +} +// RAVEN END + +/* +====================== +idDoor::Event_StartOpen + +if "start_open", reverse position 1 and 2 +====================== +*/ +void idDoor::Event_StartOpen( void ) { + float time; + float speed; + + // if "start_open", reverse position 1 and 2 + pos1 = pos2; + pos2 = GetPhysics()->GetOrigin(); + + spawnArgs.GetFloat( "speed", "400", speed ); + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } +} + +/* +====================== +idDoor::Event_SpawnDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them. +====================== +*/ +void idDoor::Event_SpawnDoorTrigger( void ) { + idBounds bounds; + idMover_Binary *other; + bool toggle; + + if ( trigger ) { + // already have a trigger, so don't spawn a new one. + return; + } + + // check if any of the doors are marked as toggled + toggle = false; + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { + toggle = true; + break; + } + } + + if ( toggle ) { + // mark them all as toggled + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + other->spawnArgs.Set( "toggle", "1" ); + } + } + // don't spawn trigger + return; + } + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( trigger ); + + MatchActivateTeam( moverState, gameLocal.time ); +} + +/* +====================== +idDoor::Event_SpawnSoundTrigger + +Spawn a sound trigger to activate locked sound if it exists. +====================== +*/ +void idDoor::Event_SpawnSoundTrigger( void ) { + idBounds bounds; + + if ( sndTrigger ) { + return; + } + + CalcTriggerBounds( triggersize * 0.5f, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + sndTrigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 254, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( sndTrigger ); +} + +/* +================ +idDoor::Event_Reached_BinaryMover +================ +*/ +void idDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked( false ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); + } + } else if ( moverState == MOVER_1TO2 ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); + } + } + idMover_Binary::Event_Reached_BinaryMover(); +} + +/* +================ +idDoor::Blocked_Door +================ +*/ +void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + SetBlocked( true ); + + if ( crusher ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( moveMaster->GetActivator() ); + + if ( companionDoor ) { + companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); + } +} + +/* +=============== +idDoor::SetCompanion +=============== +*/ +void idDoor::SetCompanion( idDoor *door ) { + companionDoor = door; +} + +/* +=============== +idDoor::Event_PartBlocked +=============== +*/ +void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idDoor::Event_ReturnToPos1 +================ +*/ +void idDoor::Event_ReturnToPos1( void ) { + idMover_Binary::Event_ReturnToPos1(); + + if( doorFrameController.IsValid() ) { + doorFrameController->ProcessEvent( &EV_CloseGate ); + } +} +// RAVEN END + +/* +================ +idDoor::Event_Touch +================ +*/ +void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate; + idVec3 planeaxis1, planeaxis2, normal; + idBounds bounds; + + if ( !enabled ) { + return; + } + + if ( trigger && trace->c.id == trigger->GetId() ) { + if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { +// RAVEN BEGIN +// abahr: allowing animated door frames + if( doorFrameController.IsValid() && doorFrameController != other ) { + doorFrameController->ProcessEvent( &EV_Touch, other, trace ); + } else { + Use( this, other ); + } +// RAVEN END + } + } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( other && other->IsType( idPlayer::GetClassType() ) && IsLocked() && gameLocal.time > nextSndTriggerTime ) { +// RAVEN END + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + } +} + +/* +================ +idDoor::Event_SpectatorTouch +================ +*/ +void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate, normal; + idBounds bounds; + idPlayer *p; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( other && other->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( other )->spectating ); +// RAVEN END + + p = static_cast< idPlayer * >( other ); + // avoid flicker when stopping right at clip box boundaries + if ( p->lastSpectateTeleport > gameLocal.time - 1000 ) { + return; + } + if ( trigger && !IsOpen() ) { + // teleport to the other side, center to the middle of the trigger brush + bounds = trigger->GetAbsBounds(); + contact = trace->endpos - bounds.GetCenter(); + translate = bounds.GetCenter(); + normal.Zero(); + normal[ normalAxisIndex ] = 1.0f; + if ( normal * contact > 0 ) { + translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } else { + translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } + p->SetOrigin( translate ); + p->lastSpectateTeleport = gameLocal.time; + } +} + +/* +================ +idDoor::Event_Activate +================ +*/ +void idDoor::Event_Activate( idEntity *activator ) { + int old_lock; + + if ( spawnArgs.GetInt( "locked" ) ) { + if ( !trigger ) { + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + UpdateBuddies( 1 ); + + old_lock = spawnArgs.GetInt( "locked" ); + Lock( 0 ); + if ( old_lock == 2 ) { + return; + } + } + + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + +// RAVEN BEGIN +// abahr: + if( doorFrameController.IsValid() && doorFrameController != activator ) { + doorFrameController->ProcessEvent( &EV_Activate, activator ); + } else { + ActivateTargets( activator ); + + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + UpdateVisuals(); + + Use_BinaryMover( activator ); + } +//RAVEN END +} + +/* +================ +idDoor::Event_Open +================ +*/ +void idDoor::Event_Open( void ) { + Open(); +} + +/* +================ +idDoor::Event_Close +================ +*/ +void idDoor::Event_Close( void ) { + Close(); +} + +/* +================ +idDoor::Event_Lock +================ +*/ +void idDoor::Event_Lock( int f ) { + Lock( f ); +} + +/* +================ +idDoor::Event_IsOpen +================ +*/ +void idDoor::Event_IsOpen( void ) { + bool state; + + state = IsOpen(); + idThread::ReturnFloat( state ); +} + +/* +================ +idDoor::Event_Locked +================ +*/ +void idDoor::Event_Locked( void ) { + idThread::ReturnFloat( spawnArgs.GetInt("locked") ); +} + +/* +================ +idDoor::Event_OpenPortal + +Sets the portal associtated with this door to be open +================ +*/ +void idDoor::Event_OpenPortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + } +} + +/* +================ +idDoor::Event_ClosePortal + +Sets the portal associtated with this door to be closed +================ +*/ +void idDoor::Event_ClosePortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( !slave->IsHidden() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + } + } +} + + +/* +=============================================================================== + +idPlat + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Binary, idPlat ) + EVENT( EV_Touch, idPlat::Event_Touch ) + EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) +END_CLASS + +/* +=============== +idPlat::idPlat +=============== +*/ +idPlat::idPlat( void ) { + trigger = NULL; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); +} + +/* +=============== +idPlat::~idPlat +=============== +*/ +idPlat::~idPlat( void ) { + if ( trigger ) { + delete trigger; + } +} + +/* +=============== +idPlat::Save +=============== +*/ +void idPlat::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Restore +=============== +*/ +void idPlat::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Spawn +=============== +*/ +void idPlat::Spawn( void ) { + float lip; + float height; + float time; + float speed; + float accel; + float decel; + bool noTouch; + + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetFloat( "damage", "0", damage ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetFloat( "lip", "8", lip ); + spawnArgs.GetFloat( "accel_time", "0.25", accel ); + spawnArgs.GetFloat( "decel_time", "0.25", decel ); + + // create second position + if ( !spawnArgs.GetFloat( "height", "0", height ) ) { + height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; + } + + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // pos1 is the rest (bottom) position, pos2 is the top + pos2 = GetPhysics()->GetOrigin(); + pos1 = pos2; + pos1[2] -= height; + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, accel, decel ); + } else { + InitSpeed( pos1, pos2, speed, accel, decel ); + } + + SetMoverState( MOVER_POS1, gameLocal.time ); + UpdateVisuals(); + + // spawn the trigger if one hasn't been custom made + if ( !noTouch ) { + // spawn trigger + SpawnPlatTrigger( pos1 ); + } +} + +/* +================ +idPlat::Think +================ +*/ +void idPlat::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idPlat::PreBind +================ +*/ +void idPlat::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idPlat::PostBind +================ +*/ +void idPlat::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ); +} + +/* +================ +idPlat::GetLocalTriggerPosition +================ +*/ +void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +============== +idPlat::SpawnPlatTrigger +=============== +*/ +void idPlat::SpawnPlatTrigger( idVec3 &pos ) { + idBounds bounds; + idVec3 tmin; + idVec3 tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + + bounds = GetPhysics()->GetBounds(); + + tmin[0] = bounds[0][0] + 33; + tmin[1] = bounds[0][1] + 33; + tmin[2] = bounds[0][2]; + + tmax[0] = bounds[1][0] - 33; + tmax[1] = bounds[1][1] - 33; + tmax[2] = bounds[1][2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; + tmax[1] = tmin[1] + 1; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +============== +idPlat::Event_Touch +=============== +*/ +void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { + Use_BinaryMover( other ); + } +} + +/* +================ +idPlat::Event_TeamBlocked +================ +*/ +void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + Use_BinaryMover( activatedBy.GetEntity() ); + + Use_BinaryMover( this ); + +} + +/* +=============== +idPlat::Event_PartBlocked +=============== +*/ +void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + + +/* +=============================================================================== + +idMover_Periodic + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMover_Periodic ) + EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) +END_CLASS + +/* +=============== +idMover_Periodic::idMover_Periodic +=============== +*/ +idMover_Periodic::idMover_Periodic( void ) { + damage = 0.0f; + fl.neverDormant = false; +} + +idMover_Periodic::~idMover_Periodic( void ) { + SetPhysics( NULL ); +} + +/* +=============== +idMover_Periodic::Spawn +=============== +*/ +void idMover_Periodic::Spawn( void ) { + spawnArgs.GetFloat( "damage", "0", damage ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + GetPhysics()->SetContents( 0 ); + } +} + +/* +=============== +idMover_Periodic::Save +=============== +*/ +void idMover_Periodic::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( damage ); +} + +/* +=============== +idMover_Periodic::Restore +=============== +*/ +void idMover_Periodic::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + savefile->ReadFloat( damage ); + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMover_Periodic::Think +================ +*/ +void idMover_Periodic::Think( void ) { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + RunPhysics(); + Present(); +} + +/* +=============== +idMover_Periodic::Event_TeamBlocked +=============== +*/ +void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } +} + +/* +=============== +idMover_Periodic::Event_PartBlocked +=============== +*/ +void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idMover_Periodic::WriteToSnapshot +================ +*/ +void idMover_Periodic::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Periodic::ReadFromSnapshot +================ +*/ +void idMover_Periodic::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idRotater + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRotater ) + EVENT( EV_Activate, idRotater::Event_Activate ) +END_CLASS + +/* +=============== +idRotater::idRotater +=============== +*/ +idRotater::idRotater( void ) { + activatedBy = this; +} + +/* +=============== +idRotater::Spawn +=============== +*/ +void idRotater::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idRotater::Save +=============== +*/ +void idRotater::Save( idSaveGame *savefile ) const { + activatedBy.Save( savefile ); +} + +/* +=============== +idRotater::Restore +=============== +*/ +void idRotater::Restore( idRestoreGame *savefile ) { + activatedBy.Restore( savefile ); +} + +/* +=============== +idRotater::Event_Activate +=============== +*/ +void idRotater::Event_Activate( idEntity *activator ) { + float speed; + bool x_axis; + bool y_axis; + idAngles delta; + + activatedBy = activator; + + delta.Zero(); + + if ( !spawnArgs.GetBool( "rotate" ) ) { + spawnArgs.Set( "rotate", "1" ); + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of rotation + if ( x_axis ) { + delta[2] = speed; + } else if ( y_axis ) { + delta[0] = speed; + } else { + delta[1] = speed; + } + } else { + spawnArgs.Set( "rotate", "0" ); + } + + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idBobber ) +END_CLASS + +/* +=============== +idBobber::idBobber +=============== +*/ +idBobber::idBobber( void ) { +} + +/* +=============== +idBobber::Spawn +=============== +*/ +void idBobber::Spawn( void ) { + float speed; + float height; + float phase; + bool x_axis; + bool y_axis; + idVec3 delta; + + spawnArgs.GetFloat( "speed", "4", speed ); + spawnArgs.GetFloat( "height", "32", height ); + spawnArgs.GetFloat( "phase", "0", phase ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of bobbing + delta = vec3_origin; + if ( x_axis ) { + delta[ 0 ] = height; + } else if ( y_axis ) { + delta[ 1 ] = height; + } else { + delta[ 2 ] = height; + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idPendulum + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idPendulum ) +END_CLASS + +/* +=============== +idPendulum::idPendulum +=============== +*/ +idPendulum::idPendulum( void ) { +} + +/* +=============== +idPendulum::Spawn +=============== +*/ +void idPendulum::Spawn( void ) { + float speed; + float freq; + float length; + float phase; + + spawnArgs.GetFloat( "speed", "30", speed ); + spawnArgs.GetFloat( "phase", "0", phase ); + + if ( spawnArgs.GetFloat( "freq", "", freq ) ) { + if ( freq <= 0.0f ) { + gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); + } + } else { + // find pendulum length + length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); + if ( length < 8 ) { + length = 8; + } + + if( gameLocal.isMultiplayer ) { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_mp_gravity.GetFloat() / ( 3 * length ) ); + } else { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); + } + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRiser ) +EVENT( EV_Activate, idRiser::Event_Activate ) +END_CLASS + +/* +=============== +idRiser::idRiser +=============== +*/ +idRiser::idRiser( void ) { +} + +/* +=============== +idRiser::Spawn +=============== +*/ +void idRiser::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + SetPhysics( &physicsObj ); +} + +/* +================ +idRiser::Event_Activate +================ +*/ +void idRiser::Event_Activate( idEntity *activator ) { + + if ( !IsHidden() && spawnArgs.GetBool("hide") ) { + Hide(); + } else { + Show(); + float time; + float height; + idVec3 delta; + + spawnArgs.GetFloat( "time", "4", time ); + spawnArgs.GetFloat( "height", "32", height ); + + delta = vec3_origin; + delta[ 2 ] = height; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); + } +} + + +/* +=============================================================================== + +rvConveyor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvConveyor ) + EVENT( EV_FindTargets, rvConveyor::Event_FindTargets ) +END_CLASS + +/* +================ +rvConveyor::rvConveyor +================ +*/ +rvConveyor::rvConveyor ( void ) { +} + +/* +================ +rvConveyor::Spawn +================ +*/ +void rvConveyor::Spawn ( void ) { + spawnArgs.GetFloat ( "speed", "100", moveSpeed ); + + float angle; + if ( spawnArgs.GetFloat ( "moveAngle", "0", angle ) ) { + moveDir = idAngles ( 0, angle, 0 ).ToMat3()[0]; + } else { + moveDir = GetPhysics()->GetAxis()[0]; + } + + GetPhysics()->SetContents ( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID ); + GetPhysics()->EnableClip ( ); + + BecomeActive ( TH_THINK|TH_PHYSICS ); +} + +/* +================ +rvConveyor::Think +================ +*/ +void rvConveyor::Think ( void ) { + trace_t pushResults; + idVec3 newOrigin; + idMat3 newAxis; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin ( ); + oldAxis = GetPhysics()->GetAxis ( ); + + newOrigin = GetPhysics()->GetOrigin() + moveDir * moveSpeed * MS2SEC ( gameLocal.GetMSec() ); + newAxis = oldAxis; + gameLocal.push.ClipPush( pushResults, this, 0, + GetPhysics()->GetOrigin(), oldAxis, newOrigin, newAxis ); + + GetPhysics()->SetOrigin ( oldOrigin ); + GetPhysics()->SetAxis ( oldAxis ); + + idEntity::Think(); +} + +/* +================ +rvConveyor::Save +================ +*/ +void rvConveyor::Save( idSaveGame *savefile ) const +{ + savefile->WriteVec3( moveDir ); + savefile->WriteFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Restore +================ +*/ +void rvConveyor::Restore( idRestoreGame *savefile ) +{ + savefile->ReadVec3( moveDir ); + savefile->ReadFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Event_FindTargets +================ +*/ +void rvConveyor::Event_FindTargets ( void ) { + FindTargets ( ); + + moveDir = GetPhysics()->GetAxis ( )[0]; + if ( !targets.Num ( ) || !targets[0] ) { + return; + } + + idEntity* path; + idEntity* next; + path = targets[0]; + path->FindTargets ( ); + next = path->targets.Num() ? path->targets[0].GetEntity() : NULL; + if ( !next ) { + return; + } + + moveDir = next->GetPhysics()->GetOrigin() - path->GetPhysics()->GetOrigin(); + moveDir.Normalize ( ); + + path->PostEventMS ( &EV_Remove, 0 ); +} + + +CLASS_DECLARATION( idMover, rvPusher ) +END_CLASS + +/* +================ +rvPusher::rvPusher +================ +*/ +rvPusher::rvPusher( void ) { + parent = 0; +} + +/* +================ +rvPusher::~rvPusher +================ +*/ +rvPusher::~rvPusher( void ) { +} + +/* +================ +rvPusher::Spawn +================ +*/ +void rvPusher::Spawn( void ) { + parent = 0; +} + +/* +================ +rvPusher::Think +================ +*/ +void rvPusher::Think( void ) { + + // Total hack, but it gets the job done + BecomeActive( TH_ALL ); + + if ( parent ) { + idAnimator *parentAnimator = parent->GetAnimator(); + if ( parentAnimator ) { + + idStr jointName; + if ( spawnArgs.GetString( "attachedBone", "", jointName ) ) { + bindJointHandle = parentAnimator->GetJointHandle( jointName ); + + trace_t pushResults; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin(); + oldAxis = GetPhysics()->GetAxis(); + + parentAnimator->CreateFrame( gameLocal.time, true ); + parentAnimator->ServiceAnims( gameLocal.previousTime, gameLocal.time ); + parentAnimator->GetJointTransform( bindJointHandle, gameLocal.time, pusherOrigin, pusherAxis ); + pusherAxis *= parent->GetRenderEntity()->axis; + pusherOrigin = parent->GetRenderEntity()->origin + pusherOrigin * parent->GetRenderEntity()->axis; + MoveToPos( pusherOrigin ); + gameLocal.push.ClipTranslationalPush(pushResults, this, 0, pusherOrigin, pusherOrigin - oldOrigin ); + GetPhysics()->SetOrigin ( pusherOrigin ); + } + + } + } else { + idStr bindEntName; + parent = 0; + if ( spawnArgs.GetString( "attachedEntity", "", bindEntName ) ) { + parent = gameLocal.FindEntity( bindEntName ); + } + } + + idMover::Think(); +} + + + +// RAVEN END diff --git a/source/game/Mover.h b/source/game/Mover.h new file mode 100644 index 0000000..52f7d19 --- /dev/null +++ b/source/game/Mover.h @@ -0,0 +1,707 @@ + +#ifndef __GAME_MOVER_H__ +#define __GAME_MOVER_H__ + +extern const idEventDef EV_TeamBlocked; +extern const idEventDef EV_PartBlocked; +extern const idEventDef EV_ReachedPos; +extern const idEventDef EV_ReachedAng; + +// RAVEN BEGIN +// bdube: more externs +extern const idEventDef EV_Door_Lock; +extern const idEventDef EV_Door_IsLocked; +// abahr: +extern const idEventDef EV_GetSplineEntity; +extern const idEventDef EV_MoveAlongVector; +extern const idEventDef EV_Door_Open; +extern const idEventDef EV_Door_Close; +extern const idEventDef EV_Door_IsOpen; +extern const idEventDef EV_Move; +extern const idEventDef EV_RotateOnce; +// twhitaker: +extern const idEventDef EV_Speed; +extern const idEventDef EV_IsActive; +extern const idEventDef EV_IsMoving; + +// mekberg: spline sampling +#define SPLINE_SAMPLE_RATE 60.0f +// RAVEN END + +/* +=============================================================================== + + General movers. + +=============================================================================== +*/ + +class idMover : public idEntity { +public: + CLASS_PROTOTYPE( idMover ); + + idMover( void ); + ~idMover( void ); + + void Spawn( void ); + +// RAVEN BEGIN +// mekberg: added + void Think( void ); +// RAVEN END + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + void SetPortalState( bool open ); + +// RAVEN BEGIN +// mekberg: sounds for splines + void UpdateSplineStage ( void ); +// RAVEN END + +protected: + typedef enum { + ACCELERATION_STAGE, + LINEAR_STAGE, + DECELERATION_STAGE, + FINISHED_STAGE + } moveStage_t; + + typedef enum { + MOVER_NONE, + MOVER_ROTATING, + MOVER_MOVING, + MOVER_SPLINE + } moverCommand_t; + + // + // mover directions. make sure to change script/doom_defs.script if you add any, or change their order + // + typedef enum { + DIR_UP = -1, + DIR_DOWN = -2, + DIR_LEFT = -3, + DIR_RIGHT = -4, + DIR_FORWARD = -5, + DIR_BACK = -6, + DIR_REL_UP = -7, + DIR_REL_DOWN = -8, + DIR_REL_LEFT = -9, + DIR_REL_RIGHT = -10, + DIR_REL_FORWARD = -11, + DIR_REL_BACK = -12 + } moverDir_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idVec3 dir; + } moveState_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idAngles rot; + } rotationState_t; + + idPhysics_Parametric physicsObj; + + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_PartBlocked( idEntity *blockingEntity ); + + void MoveToPos( const idVec3 &pos); + void UpdateMoveSound( moveStage_t stage ); + void UpdateRotationSound( moveStage_t stage ); + void SetGuiStates( const char *state ); + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + virtual void DoneMoving( void ); + virtual void DoneRotating( void ); + virtual void BeginMove( idThread *thread = NULL ); + virtual void BeginRotation( idThread *thread, bool stopwhendone ); + moveState_t move; + + rvStateThread splineStateThread; + float damage; + +private: + rotationState_t rot; + + int move_thread; + int rotate_thread; + idAngles dest_angles; + idAngles angle_delta; + idVec3 dest_position; + idVec3 move_delta; + float move_speed; + int move_time; + int deceltime; + int acceltime; + bool stopRotation; + bool useSplineAngles; + bool attenuate; + bool useIdleSound; + idEntityPtr splineEnt; + moverCommand_t lastCommand; + + qhandle_t areaPortal; // 0 = no portal + +// mekberg: attenution and sound additions + float maxAttenuation; + float attenuationScale; + idVec3 lastOrigin; + int lastTime; + int splineStartTime; +// RAVEN END + + idList< idEntityPtr > guiTargets; + +// RAVEN BEGIN +// abahr: + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void MoveAlongVector( const idVec3& vec ); +// RAVEN END + + void VectorForDir( float dir, idVec3 &vec ); + idCurve_Spline *GetSpline( idEntity *splineEntity ) const; + + void Event_SetCallback( void ); + void Event_TeamBlocked( idEntity *blockedPart, idEntity *blockingEntity ); + void Event_StopMoving( void ); + void Event_StopRotating( void ); + void Event_UpdateMove( void ); + void Event_UpdateRotation( void ); + void Event_SetMoveSpeed( float speed ); + void Event_SetMoveTime( float time ); + void Event_SetDecelerationTime( float time ); + void Event_SetAccellerationTime( float time ); + void Event_MoveTo( idEntity *ent ); + void Event_MoveToPos( idVec3 &pos ); + void Event_MoveDir( float angle, float distance ); + void Event_MoveAccelerateTo( float speed, float time ); + void Event_MoveDecelerateTo( float speed, float time ); + void Event_RotateDownTo( int axis, float angle ); + void Event_RotateUpTo( int axis, float angle ); + void Event_RotateTo( idAngles &angles ); + void Event_Rotate( idAngles &angles ); + void Event_RotateOnce( idAngles &angles ); + void Event_Bob( float speed, float phase, idVec3 &depth ); + void Event_Sway( float speed, float phase, idAngles &depth ); + void Event_SetAccelSound( const char *sound ); + void Event_SetDecelSound( const char *sound ); +// RAVEN BEGIN +// cnicholson: added stop sound support + void Event_SetStoppedSound( const char *sound ); +// RAVEN END + void Event_SetMoveSound( const char *sound ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + void Event_EnableSplineAngles( void ); + void Event_DisableSplineAngles( void ); + void Event_RemoveInitialSplineAngles( void ); + void Event_StartSpline( idEntity *splineEntity ); + void Event_StopSpline( void ); + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN + void Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ); +// RAVEN END + void Event_IsMoving( void ); + void Event_IsRotating( void ); +// RAVEN BEGIN +// abahr: + void Event_GetSplineEntity(); + void Event_MoveAlongVector( const idVec3& vec ); + +// mekberg: spline states + CLASS_STATES_PROTOTYPE ( idMover ); + + stateResult_t State_Accel( const stateParms_t& parms ); + stateResult_t State_Linear( const stateParms_t& parms ); + stateResult_t State_Decel( const stateParms_t& parms ); +// RAVEN END +}; + +class idSplinePath : public idEntity { +public: + CLASS_PROTOTYPE( idSplinePath ); + + idSplinePath(); + ~idSplinePath(); + + void Spawn( void ); + +// RAVEN BEGIN +// abahr: so we can ignore these if needed + void Toggle() { SetActive(!active); } + void Activate() { SetActive(true); } + void Deactivate() { SetActive(false); } + + bool IsActive() const { return active; } + + int SortTargets(); + int SortBackwardsTargets(); + int SortTargets( idList< idEntityPtr >& list ); + void RemoveNullTargets( void ); + void ActivateTargets( idEntity *activator ) const; + + void RemoveNullTargets( idList< idEntityPtr >& list ); + void ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idList< idEntityPtr > backwardPathTargets; + +// mekberg: spline sampling + float GetSampledTime ( float distance ) const; + +protected: + void SetActive( bool activate ) { active = activate; } + virtual void FindTargets(); + + void Event_Toggle( idEntity* activator ) { Toggle(); } + void Event_IsActive(); + +// mekberg: spline sampling + void SampleSpline ( void ); + +protected: + bool active; + +// mekberg: sample splines. + float* sampledTimes; + float sampledSpeed; + int numSamples; +// RAVEN END +}; + + +struct floorInfo_s { + idVec3 pos; + idStr door; + int floor; +}; + +class idElevator : public idMover { +public: + CLASS_PROTOTYPE( idElevator ); + + idElevator( void ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand ( idEntity *entityGui, idLexer *src ); + floorInfo_s * GetFloorInfo ( int floor ); + +protected: + virtual void DoneMoving( void ); + virtual void BeginMove( idThread *thread = NULL ); + void SpawnTrigger( const idVec3 &pos ); + void GetLocalTriggerPosition(); + void Event_Touch( idEntity *other, trace_t *trace ); + +// RAVEN BEGIN +// bdube: added + void SetAASAreaState ( bool enable ); + void InitStatusGui ( idUserInterface* gui ); + void UpdateStatusGui ( idUserInterface* gui ); + void UpdateStatusGuis ( void ); + void UpdateFloorInfo ( void ); +// RAVEN END + +private: + typedef enum { + INIT, + IDLE, + WAITING_ON_DOORS + } elevatorState_t; + + elevatorState_t state; + idList floorInfo; + int currentFloor; + int pendingFloor; + int lastFloor; + bool controlsDisabled; +// bool waitingForPlayerFollowers; + float returnTime; + int returnFloor; + int lastTouchTime; + + class idDoor * GetDoor( const char *name ); + void Think( void ); + void OpenInnerDoor( void ); + void OpenFloorDoor( int floor ); + void CloseAllDoors( void ); + void DisableAllDoors( void ); + void EnableProperDoors( void ); + + void Event_TeamBlocked ( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_Activate ( idEntity *activator ); + void Event_PostFloorArrival ( void ); + void Event_GotoFloor ( int floor ); + void Event_UpdateFloorInfo ( void ); +}; + + +/* +=============================================================================== + + Binary movers. + +=============================================================================== +*/ + +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +class idMover_Binary : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Binary ); + + idMover_Binary(); + ~idMover_Binary(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void PreBind( void ); + virtual void PostBind( void ); + + void Enable( bool b ); + void InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ); + void InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ); + void GotoPosition1( void ); + void GotoPosition2( void ); + void Use_BinaryMover( idEntity *activator ); + void SetGuiStates( const char *state ); + void UpdateBuddies( int val ); + idMover_Binary * GetActivateChain( void ) const { return activateChain; } + idMover_Binary * GetMoveMaster( void ) const { return moveMaster; } + void BindTeam( idEntity *bindTo ); + void SetBlocked( bool b ); + bool IsBlocked( void ); + idEntity * GetActivator( void ) const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetPortalState( bool open ); + +protected: + idVec3 pos1; + idVec3 pos2; + moverState_t moverState; + idMover_Binary * moveMaster; + idMover_Binary * activateChain; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + float wait; + float damage; + int duration; + int accelTime; + int decelTime; + idEntityPtr activatedBy; + int stateStartTime; + idStr team; + bool enabled; + bool deferedOpen; + int move_thread; + int updateStatus; // 1 = lock behaviour, 2 = open close status + idStrList buddies; + idPhysics_Parametric physicsObj; + qhandle_t areaPortal; // 0 = no portal + bool blocked; + idList< idEntityPtr > guiTargets; + + void MatchActivateTeam( moverState_t newstate, int time ); + void JoinActivateTeam( idMover_Binary *master ); + + void UpdateMoverSound( moverState_t state ); + void SetMoverState( moverState_t newstate, int time ); + moverState_t GetMoverState( void ) const { return moverState; } + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + void Event_SetCallback( void ); + void Event_ReturnToPos1( void ); + void Event_Use_BinaryMover( idEntity *activator ); + void Event_Reached_BinaryMover( void ); + void Event_MatchActivateTeam( moverState_t newstate, int time ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + + static void GetMovedir( float dir, idVec3 &movedir ); +}; + +class idDoor : public idMover_Binary { +public: + CLASS_PROTOTYPE( idDoor ); + + idDoor( void ); + ~idDoor( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void Hide( void ); + virtual void Show( void ); + + bool IsOpen( void ); + bool IsNoTouch( void ); + int IsLocked( void ); + void Lock( int f ); + void Use( idEntity *other, idEntity *activator ); + void Close( void ); + void Open( void ); + void SetCompanion( idDoor *door ); + +// RAVEN BEGIN +// abahr: + bool IsClosed( void ) const { return moverState == MOVER_POS1; } + void SetDoorFrameController( idEntity* controller ); + virtual void ActivateTargets( idEntity *activator ) const; +// RAVEN END + +private: + float triggersize; + bool crusher; + bool noTouch; + bool aas_area_closed; + idStr buddyStr; + idClipModel * trigger; + idClipModel * sndTrigger; + int nextSndTriggerTime; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + idStr requires; + int removeItem; + idStr syncLock; + int normalAxisIndex; // door faces X or Y for spectator teleports + idDoor * companionDoor; + +// RAVEN BEGIN +// abahr: so we can route calls through a tramGate + idEntityPtr doorFrameController; +// RAVEN END + + void SetAASAreaState( bool closed ); + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void CalcTriggerBounds( float size, idBounds &bounds ); + + void Event_Reached_BinaryMover( void ); + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + void Event_StartOpen( void ); + void Event_SpawnDoorTrigger( void ); + void Event_SpawnSoundTrigger( void ); + void Event_Close( void ); + void Event_Open( void ); + void Event_Lock( int f ); + void Event_IsOpen( void ); + void Event_Locked( void ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + +// RAVEN BEGIN +// abahr: + void Event_ReturnToPos1( void ); +// RAVEN END +}; + +class idPlat : public idMover_Binary { +public: + CLASS_PROTOTYPE( idPlat ); + + idPlat( void ); + ~idPlat( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + +private: + idClipModel * trigger; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void SpawnPlatTrigger( idVec3 &pos ); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Special periodic movers. + +=============================================================================== +*/ + +class idMover_Periodic : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Periodic ); + + idMover_Periodic( void ); + ~idMover_Periodic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idPhysics_Parametric physicsObj; + float damage; + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); +}; + +class idRotater : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRotater ); + + idRotater( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idEntityPtr activatedBy; + + void Event_Activate( idEntity *activator ); +}; + +class idBobber : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idBobber ); + + idBobber( void ); + + void Spawn( void ); + +private: +}; + +class idPendulum : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idPendulum ); + + idPendulum( void ); + + void Spawn( void ); + +private: + +}; + +class idRiser : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRiser ); + + idRiser( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: conveyor belts +class rvConveyor : public idEntity { +public: + CLASS_PROTOTYPE( rvConveyor ); + + rvConveyor ( void ); + + void Spawn( void ); + void Think ( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +private: + + idVec3 moveDir; + float moveSpeed; + + void Event_FindTargets ( void ); +}; + +// nrausch: +class rvPusher : public idMover { +public: + CLASS_PROTOTYPE( rvPusher ); + + rvPusher( void ); + ~rvPusher( void ); + + virtual void Spawn( void ); + virtual void Think ( void ); + +private: + jointHandle_t bindJointHandle; + idEntity *parent; + idVec3 pusherOrigin; + idMat3 pusherAxis; +}; + +// RAVEN END + +#endif /* !__GAME_MOVER_H__ */ diff --git a/source/game/MultiplayerGame.cpp b/source/game/MultiplayerGame.cpp new file mode 100644 index 0000000..4613a0b --- /dev/null +++ b/source/game/MultiplayerGame.cpp @@ -0,0 +1,9210 @@ +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" ); + +const char *idMultiplayerGame::MPGuis[] = { +// RAVEN BEGIN +// bdube: use regular hud for now + "guis/hud.gui", +// RAVEN END + "guis/mpmain.gui", + "guis/mpmsgmode.gui", + "guis/netmenu.gui", + "guis/mphud.gui", + NULL +}; + +const char *idMultiplayerGame::ThrottleVars[] = { + "ui_spectate", + "ui_ready", + "ui_team", + NULL +}; + +const char *idMultiplayerGame::ThrottleVarsInEnglish[] = { + "#str_106738", + "#str_106737", + "#str_101991", + NULL +}; + +const int idMultiplayerGame::ThrottleDelay[] = { + 8, + 5, + 5 +}; + +const char* idMultiplayerGame::teamNames[ TEAM_MAX ] = { + "Marine", + "Strogg" +}; + +idCVar gui_ui_name( "gui_ui_name", "", CVAR_GAME | CVAR_NOCHEAT, "copy-over cvar for ui_name" ); + +/* +================ +ComparePlayerByScore +================ +*/ +int ComparePlayersByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +CompareTeamByScore +================ +*/ +int CompareTeamsByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +idMultiplayerGame::idMultiplayerGame +================ +*/ +idMultiplayerGame::idMultiplayerGame() { +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = NULL; +// RITUAL END + scoreBoard = NULL; + statSummary = NULL; + mainGui = NULL; + mapList = NULL; + msgmodeGui = NULL; + defaultWinner = -1; + deadZonePowerupCount = -1; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + + memset( lights, 0, sizeof( lights ) ); + memset( lightHandles, -1, sizeof( lightHandles ) ); + + Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + flagEntities[ i ] = NULL; + teamDeadZoneScore[i] = 0; + } + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + announcerSoundQueue.Clear(); + announcerPlayTime = 0; + + gameState = NULL; + currentSoundOverride = false; + + rankTextPlayer = NULL; + + privatePlayers = 0; + + lastAnnouncerSound = AS_NUM_SOUNDS; +} + +/* +================ +idMultiplayerGame::Shutdown +================ +*/ +void idMultiplayerGame::Shutdown( void ) { + + Clear(); + statManager->Shutdown(); + + if( gameState ) { + delete gameState; + } + gameState = NULL; +} + +/* +================ +idMultiplayerGame::Reset +================ +*/ +void idMultiplayerGame::Reset() { + Clear(); + assert( !scoreBoard && !mainGui && !mapList ); + + mpBuyingManager.Reset(); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = uiManager->FindGui( "guis/buymenu.gui", true, false, true ); + buyMenu->SetStateString( "field_credits", "$0.00"); + buyMenu->SetStateBool( "gameDraw", true ); +// RITUAL END + PACIFIER_UPDATE; + scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true ); + +#ifdef _XENON + statSummary = scoreBoard; +#else + statSummary = uiManager->FindGui( "guis/summary.gui", true, false, true ); + statSummary->SetStateBool( "gameDraw", true ); +#endif + + PACIFIER_UPDATE; + + mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true ); + mapList = uiManager->AllocListGUI( ); + mapList->Config( mainGui, "mapList" ); + + // set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI + mainGui->SetStateBool( "gameDraw", true ); + mainGui->SetKeyBindingNames(); + mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + +// SetMenuSkin(); + + PACIFIER_UPDATE; + msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true ); + msgmodeGui->SetStateBool( "gameDraw", true ); + + memset ( lights, 0, sizeof( lights ) ); + memset ( lightHandles, -1, sizeof( lightHandles ) ); + + renderLight_t *light; + const char *shader; + + light = &lights[ MPLIGHT_CTF_MARINE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 142.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 190.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 84.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_MARINE; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_CTF_STROGG ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 153.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_QUAD ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 0.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 128.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_HASTE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 225.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_REGEN ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 0.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + PACIFIER_UPDATE; + ClearGuis(); + +//asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = 0; + + iconManager->Shutdown(); + + // update serverinfo + UpdatePrivatePlayerCount(); + + lastReadyToggleTime = -1; + + cvarSystem->SetCVarBool( "s_voiceChatTest", false ); +} + +/* +================ +idMultiplayerGame::ServerClientConnect +================ +*/ +void idMultiplayerGame::ServerClientConnect( int clientNum ) { + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + statManager->ClientConnect( clientNum ); +} + +/* +================ +idMultiplayerGame::SpawnPlayer +================ +*/ +void idMultiplayerGame::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + bool ingame = playerState[ clientNum ].ingame; + // keep ingame to true if needed, that should only happen for local player + + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + if ( !gameLocal.isClient ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + p->spawnedTime = gameLocal.time; + //if ( gameLocal.IsTeamGame() ) { + // SwitchToTeam( clientNum, -1, p->team ); + //} + playerState[ clientNum ].ingame = ingame; + } + + if ( clientNum == gameLocal.localClientNum ) { + tourneyGUI.SetupTourneyGUI( gameLocal.GetLocalPlayer()->mphud, scoreBoard ); + } + + lastVOAnnounce = 0; +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::Clear() { + + int i; + + pingUpdateTime = 0; + vote = VOTE_NONE; + voteTimeOut = 0; + voteExecTime = 0; + matchStartedTime = 0; + memset( &playerState, 0 , sizeof( playerState ) ); + currentMenu = 0; + bCurrentMenuMsg = false; + nextMenu = 0; + pureReady = false; + scoreBoard = NULL; + buyMenu = NULL; + isBuyingAllowedRightNow = false; + statSummary = NULL; + mainGui = NULL; + msgmodeGui = NULL; + if ( mapList ) { + uiManager->FreeListGUI( mapList ); + mapList = NULL; + } + memset( &switchThrottle, 0, sizeof( switchThrottle ) ); + voiceChatThrottle = 0; + + voteValue.Clear(); + voteString.Clear(); + + prevAnnouncerSnd = -1; + + localisedGametype.Clear(); + + for( i = 0; i < MAX_CLIENTS; i++ ) { + kickVoteMapNames[ i ].Clear(); + } + + for ( i = 0; i < MPLIGHT_MAX; i ++ ) { + FreeLight( i ); + } + + chatHistory.Clear(); + rconHistory.Clear(); + + memset( rankedTeams, 0, sizeof( rvPair ) * TEAM_MAX ); + + if( gameState ) { + gameState->Clear(); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + unrankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + assaultPoints.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); +#endif +// RAVEN END + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + assaultPoints.Clear(); + + ClearAnnouncerSounds(); + + rankTextPlayer = NULL; + + for ( i = 0; i < TEAM_MAX; i++ ) { + flagEntities[ i ] = NULL; + } +} + +/* +================ +idMultiplayerGame::ClearMap +================ +*/ +void idMultiplayerGame::ClearMap( void ) { + assaultPoints.Clear(); + ClearAnnouncerSounds(); + announcerPlayTime = 0; + powerupCount = 0; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + prevAnnouncerSnd = -1; + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + // Dead Zone uses teamFragCount as the "player score" + // so we need to clear it at the beginning of every round. + if ( gameLocal.gameType == GAME_DEADZONE ) { + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + playerState[i].teamFragCount = 0; + playerState[i].deadZoneScore = 0; + } + } +} + +/* +================ +idMultiplayerGame::ClearGuis +================ +*/ +void idMultiplayerGame::ClearGuis() { + int i; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "player%i",i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + scoreBoard->SetStateInt( "rank_self", 0 ); + + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->hud ) { + continue; + } + player->hud->SetStateString( va( "player%i",i+1 ), "" ); + player->hud->SetStateString( va( "player%i_score", i+1 ), "" ); + player->hud->SetStateString( va( "player%i_ready", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + player->hud->SetStateInt( "rank_self", 0 ); + + player->hud->SetStateInt( "team", TEAM_MARINE ); + player->hud->HandleNamedEvent( "flagReturn" ); + player->hud->SetStateInt( "team", TEAM_STROGG ); + player->hud->HandleNamedEvent( "flagReturn" ); + } + + ClearVote(); +} + +/* +================ +idMultiplayerGame::GetPlayerRank +Returns the player rank (0 best), returning the best rank in the case of a tie +================ +*/ +int idMultiplayerGame::GetPlayerRank( idPlayer* player, bool& isTied ) { + int initialRank = -1; + int rank = -1; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rank = i; + initialRank = rank; + } + } + + if( rank == -1 ) { + return rank; + } + + if( rank > 0 ) { + if( rankedPlayers[ rank - 1 ].Second() == rankedPlayers[ rank ].Second() ) { + rank = rankedPlayers[ rank - 1 ].First()->GetRank(); + } else { + rank = rankedPlayers[ rank - 1 ].First()->GetRank() + 1; + } + } + + // check for tie + isTied = false; + + for( int i = rank - 1; i <= rank + 1; i++ ) { + if( i < 0 || i >= rankedPlayers.Num() || rankedPlayers[ i ].First() == player ) { + continue; + } + + if( rankedPlayers[ i ].Second() == rankedPlayers[ initialRank ].Second() ) { + isTied = true; + break; + } + } + + return rank; +} + +/* +================ +idMultiplayerGame::UpdatePlayerRanks +================ +*/ +void idMultiplayerGame::UpdatePlayerRanks( playerRankMode_t rankMode ) { + idEntity* ent = NULL; + + if( rankMode == PRM_AUTO ) { + if( gameLocal.IsTeamGame() ) { + rankMode = PRM_TEAM_SCORE_PLUS_SCORE; + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + rankMode = PRM_WINS; + } else { + rankMode = PRM_SCORE; + } + } + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer* player = (idPlayer*)ent; + + if ( !CanPlay( player ) ) { + unrankedPlayers.Append( player ); + } else { + int rankingValue = 0; + switch( rankMode ) { + case PRM_SCORE: { + rankingValue = GetScore( player ); + break; + } + case PRM_TEAM_SCORE: { + rankingValue = GetTeamScore( player ); + break; + } + case PRM_TEAM_SCORE_PLUS_SCORE: { + rankingValue = GetScore( player ) + GetTeamScore( player ); + break; + } + case PRM_WINS: { + rankingValue = GetWins( player ); + break; + } + default: { + gameLocal.Error( "idMultiplayerGame::UpdatePlayerRanks() - Bad ranking mode '%d'\n", rankMode ); + } + } + rankedPlayers.Append( rvPair(player, rankingValue ) ); + } + } + + qsort( rankedPlayers.Ptr(), rankedPlayers.Num(), rankedPlayers.TypeSize(), ComparePlayersByScore ); + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + bool tied; + rankedPlayers[ i ].First()->SetRank( GetPlayerRank( rankedPlayers[ i ].First(), tied ) ); + } + + for( int i = 0; i < unrankedPlayers.Num(); i++ ) { + unrankedPlayers[ i ]->SetRank( -1 ); + } +} + +/* +================ +idMultiplayerGame::UpdateTeamRanks +================ +*/ +void idMultiplayerGame::UpdateTeamRanks( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + rankedTeams[ i ] = rvPair( i, teamScore[ i ] ); + } + + qsort( rankedTeams, TEAM_MAX, sizeof( rvPair ), CompareTeamsByScore ); +} + +/* +================ +idMultiplayerGame::UpdateRankColor +================ +*/ +void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) { + for ( int j = 1; j < 4; j++ ) { + gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] ); + } +} + +/* +================ +idMultiplayerGame::CanCapture + +Determines if the given flag can be captured in the given gamestate +================ +*/ +bool idMultiplayerGame::CanCapture( int team ) { + // no AP's in one flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + return true; + } else if( gameLocal.gameType != GAME_CTF && gameLocal.gameType != GAME_ARENA_CTF ) { + return false; // no flag caps in none-CTF games + } + + if ( !assaultPoints.Num() ) { + return true; + } + + // since other logic ensures AP's are captured in order, we just need to check the last AP before the enemy flag + if ( team == TEAM_STROGG ) { + // AP 0 is always next to the marine flag + return ((rvCTFGameState*)gameState)->GetAPOwner( 0 ) == TEAM_STROGG; + } + if ( team == TEAM_MARINE ) { + // the last AP is always the one next to the strogg flag + return ((rvCTFGameState*)gameState)->GetAPOwner( assaultPoints.Num() - 1 ) == TEAM_MARINE; + } + + return false; +} + +void idMultiplayerGame::FlagCaptured( idPlayer *player ) { + if( !gameLocal.isClient ) { + AddTeamScore( player->team, 1 ); + AddPlayerTeamScore( player, 5 ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + float teamCashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_flagCapture", 0 ); + GiveCashToTeam( player->team, teamCashAward ); + + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagCapture", 0 ); + player->GiveCash( cashAward ); + } +// RITUAL END + + gameLocal.ClearForwardSpawns(); + + for( int i = 0; i < assaultPoints.Num(); i++ ) { + assaultPoints[ i ]->Reset(); + ((rvCTFGameState*)gameState)->SetAPOwner( i, AS_NEUTRAL ); + } + + statManager->FlagCaptured( player, OpposingTeam( player->team ) ); + player->SetEmote( PE_CHEER ); + } +} + +/* +================ +idMultiplayerGame::SendDeathMessage +================ +*/ +void idMultiplayerGame::SendDeathMessage( idPlayer* attacker, idPlayer* victim, int methodOfDeath ) { + if( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DEATH ); + if( attacker ) { + outMsg.WriteByte( attacker->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ attacker->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + if( victim ) { + outMsg.WriteByte( victim->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ victim->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + outMsg.WriteByte( methodOfDeath ); + + gameLocal.ServerSendInstanceReliableMessage( victim, -1, outMsg ); + + if( gameLocal.isListenServer && gameLocal.GetLocalPlayer() && victim && gameLocal.GetLocalPlayer()->GetInstance() == victim->GetInstance() ) + { + // This is for listen servers, which won't get to ClientProcessReliableMessage + ReceiveDeathMessage( attacker, attacker ? playerState[ attacker->entityNumber ].fragCount : -1, victim, victim ? playerState[ victim->entityNumber ].fragCount : -1, methodOfDeath ); + } + } +} + +/* +================ +idMultiplayerGame::ReceiveDeathMessage +================ +*/ +void idMultiplayerGame::ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + return; + } + +// RITUAL BEGIN +// squirrel: force buy menu open when you die + //if( gameLocal.IsMultiplayer() && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && victim == gameLocal.GetLocalPlayer() ) + //{ + // OpenLocalBuyMenu(); + //} +// RITUAL END + + const char* icon = ""; + + // if methodOfDeath is in range [0, MAX_WEAPONS - 1] it refers to a specific weapon. MAX_WEAPONS refers to + // a generic or unknown death (i.e. "Killer killed victim") and values above MAX_WEAPONS + 1 refer + // to other non-weapon deaths (i.e. telefrags) + + // setup to either use weapon icons for a weapon death, or generic death icons + if ( methodOfDeath < MAX_WEAPONS ) { + icon = va( "w%02d", methodOfDeath ); + } else { + icon = va( "dm%d", methodOfDeath - MAX_WEAPONS ); + } + + char* message = NULL; + + if ( gameLocal.IsTeamGame() ) { + idStr attackerStr( ( attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "" ) ); + idStr victimStr( ( victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "" ) ); + + attackerStr.RemoveEscapes(); + victimStr.RemoveEscapes(); + + message = va ( "%s%s ^r^i%s %s%s", (attacker ? (attacker->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + attackerStr.c_str(), + icon, + (victim ? (victim->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + victimStr.c_str() ); + } else { + message = va ( "%s ^r^i%s %s", (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : ""), + icon, + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "") ); + } + + if( gameLocal.GetLocalPlayer()->hud ) { + gameLocal.GetLocalPlayer()->hud->SetStateString ( "deathinfo", message ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent ( "addDeathLine" ); + } + + // echo to console + gameLocal.Printf( gameLocal.GetLocalPlayer()->spawnArgs.GetString( va( "%s_text", icon ), "%s killed %s" ), + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "world"), + (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "world") ); + gameLocal.Printf( "\n" ); + + // display message on hud + if( attacker && victim && (gameLocal.GetLocalPlayer() == attacker || gameLocal.GetLocalPlayer() == victim) && attacker != victim && methodOfDeath < MAX_WEAPONS ) { + if( gameLocal.GetLocalPlayer() == attacker ) { +// RAVEN BEGIN +// rhummer: Added lang entries for "You fragged %s" and "You were fragged by %s" + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107295" ), gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) ) ); + } else { + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107296" ), gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + + if( gameLocal.gameType == GAME_DM ) { + // print rank text next time after we update scores + + // stash the scores on the client so we can print accurate rank info + if( gameLocal.isClient ) { + if( victim ) { + playerState[ victim->entityNumber ].fragCount = victimScore; + } + + if( attacker ) { + playerState[ attacker->entityNumber ].fragCount = attackerScore; + } + } + + if( victim && (gameLocal.GetLocalPlayer() == victim || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == victim->entityNumber)) ) { + rankTextPlayer = victim; + } + + if( attacker && (gameLocal.GetLocalPlayer() == attacker || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == attacker->entityNumber)) ) { + rankTextPlayer = attacker; + } + } + } +} + + +// ddynerman: Gametype specific scoreboard +/* +================ +idMultiplayerGame::UpdateScoreboard +================ +*/ +void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard ) { + scoreBoard->SetStateInt( "gametype", gameLocal.gameType ); + + //statManager->UpdateInGameHud( scoreBoard, true ); + + if( gameLocal.IsTeamGame() ) { + UpdateTeamScoreboard( scoreBoard ); + } else { + UpdateDMScoreboard( scoreBoard ); + } + + return; +} + +/* +================ +idMultiplayerGame::UpdateDMScoreboard +================ +*/ +void idMultiplayerGame::UpdateDMScoreboard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + int i; + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if( !player ) { + return; + } + + scoreBoard->SetStateString( "scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + if( gameLocal.gameType == GAME_DM ) { + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + } else if( gameLocal.gameType == GAME_TOURNEY ) { + // loop through twice listing players who are playing, then players who have been eliminated + int listIndex = 0; + + + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), false ); + listIndex++; + } + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() != PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), true ); + listIndex++; + } + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%s\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + playerState[ unrankedPlayers[ i ]->entityNumber ].ping, // ping + "" ) ); + } else { + scoreBoard->SetStateString( va( "spectator_scores_item_%i", i ), "" ); + } + } + + for( i = listIndex; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "scores_item_%i", i ), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() + unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); + + scoreBoard->SetStateString( "position_text", GetPlayerRankText( gameLocal.GetLocalPlayer() ) ); + // shouchard: added map name + // mekberg: localized string + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) == 1 ) { + // stupid english plurals + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107712" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } else { + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107715" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "tourney_count", va( common->GetLocalizedString( "#str_107713" ), ((rvTourneyGameState*)gameState)->GetTourneyCount(), gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) ); + if( gameLocal.GetLocalPlayer() ) { + inNonTimedState |= ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_SUDDEN_DEATH; + } + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || inNonTimedState ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTeamScoreboard +================ +*/ + +// only output 16 clients onto the scoreboard +#define SCOREBOARD_MAX_CLIENTS 16 + +void idMultiplayerGame::UpdateTeamScoreboard( idUserInterface *scoreBoard ) { + idStr gameinfo; + int numTeamEntries[ TEAM_MAX ]; + idPlayer* player = gameLocal.GetLocalPlayer(); + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if( !player ) { + return; + } + + SIMDProcessor->Memset( numTeamEntries, 0, sizeof( int ) * TEAM_MAX ); + + scoreBoard->SetStateString( "team_0_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "team_1_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + + for ( int i = 0; i < SCOREBOARD_MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( va("team_%i_scores_sel_0", rankedPlayer->team ), numTeamEntries[ rankedPlayer->team ] ); + } + +// RAVEN BEGIN +// mekberg: redid this + if ( gameLocal.gameType == GAME_TDM ) + { + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } + //else if ( gameLocal.gameType == GAME_DEADZONE ) + //{ + // // mekberg: made this check slightly more sane. + // const char* flagString = ""; + // if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + // flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + // } else if ( gameLocal.gameType == GAME_ARENA_CTF && gameLocal.GetLocalPlayer() && rankedPlayer->team == gameLocal.GetLocalPlayer()->team ) { + // flagString = rankedPlayer->GetArenaPowerupString( ); + // } + // scoreBoard->SetStateString ( + // va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + // va("%s\t%s\t%s\t%s\t%s\t%s\t%.01f\t%i\t%i\t%i\t", + // ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + // ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + // ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + // flagString, // shouchard: twhitaker: updated steve's original flag system + // rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + // rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + // rankedScore * 0.1f, // score + // playerState[ rankedPlayer->entityNumber ].fragCount, // kills + // GetPlayerTime( rankedPlayer ), // time + // playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + // numTeamEntries[ rankedPlayer->team ]++; + //} + else + { + // mekberg: made this check slightly more sane. + const char* flagString = ""; + if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + } else if ( gameLocal.gameType == GAME_ARENA_CTF && gameLocal.GetLocalPlayer() && rankedPlayer->team == gameLocal.GetLocalPlayer()->team ) { + flagString = rankedPlayer->GetArenaPowerupString( ); + } + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + flagString, // shouchard: twhitaker: updated steve's original flag system + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].fragCount, // kills + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } +// RAVEN END + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + +// RAVEN BEGIN +// mekberg: redid this + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectating && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping // ping +// RAVEN END + + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + } + } + + // clear unused space + for( int k = 0; k < TEAM_MAX; k++ ) { + for( int i = numTeamEntries[ k ]; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString ( va("team_%i_scores_item_%i", k, i), "" ); + } + } + + scoreBoard->SetStateInt ( "playerteam", gameLocal.GetLocalPlayer()->team ); + + scoreBoard->SetStateInt ( "strogg_score", teamScore[ TEAM_STROGG ] ); + scoreBoard->SetStateInt ( "marine_score", teamScore[ TEAM_MARINE ] ); + scoreBoard->SetStateInt ( "num_strogg_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] ) ); + scoreBoard->SetStateInt ( "num_marine_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] + unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); +// RAVEN BEGIN +// shouchard: added map name +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); +// RAVEN END + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + if ( gameLocal.IsFlagGameType() ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ); + } else if ( gameLocal.gameType == GAME_DEADZONE ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_122008" ), gameLocal.serverInfo.GetInt( "si_controlTime" ) ) ); + } else { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || state == WARMUP || state == GAMEREVIEW || state == SUDDENDEATH ); + + scoreBoard->StateChanged( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::BuildSummaryListString +Returns a summary string for the specified player +================ +*/ +const char* idMultiplayerGame::BuildSummaryListString( idPlayer* player, int rankedScore ) { + // track top 3 accuracies + rvPlayerStat* stat = statManager->GetPlayerStat( player->entityNumber ); + idList > bestAccuracies; + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + // only consider weapons we fired more than a few shots + if( stat->weaponShots[ j ] <= 10 ) { + continue; + } + + float accuracy = (float)stat->weaponHits[ j ] / (float)stat->weaponShots[ j ]; + bestAccuracies.Append( rvPair( j, accuracy ) ); + } + + bestAccuracies.Sort( rvPair::rvPairSecondCompareDirect ); + + // hold upto 3 top weapons at 5 chars each + idStr weaponString; + for( int j = 0; j < 3; j++ ) { + if( j >= bestAccuracies.Num() ) { + continue; + } + + weaponString += va( "^iw%02d", bestAccuracies[ j ].First() ); + } + + return va("%d. %s\t%s\t%d\t%s\t", + player->GetRank() + 1, + player->GetUserInfo()->GetString( "ui_name" ), // name + player->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + weaponString.c_str() ); +} + +/* +================ +idMultiplayerGame::UpdateSummaryBoard +Shows top 10 players if local player is in top 10, otherwise shows top 9 and localplayer +================ +*/ +void idMultiplayerGame::UpdateSummaryBoard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player ) { + return; + } + + int playerIndex = -1; + + // update our ranks in case we call this the same frame it happens + UpdatePlayerRanks(); + + // highlight top 3 players + idVec4 blueHighlight = idStr::ColorForIndex( C_COLOR_BLUE ); + idVec4 redHighlight = idStr::ColorForIndex( C_COLOR_RED ); + idVec4 yellowHighlight = idStr::ColorForIndex( C_COLOR_YELLOW ); + blueHighlight[ 3 ] = 0.15f; + redHighlight[ 3 ] = 0.15f; + yellowHighlight[ 3 ] = 0.15f; + + if( gameLocal.IsTeamGame() ) { + scoreBoard->HandleNamedEvent( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? "marine_wins" : "strogg_wins" ); + // summary is top 5 players on each team + int lastHighIndices[ TEAM_MAX ]; + memset( lastHighIndices, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < 5; i++ ) { + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_marine_names", i ), "" ); + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_strogg_names", i ), "" ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + for( int j = 0; j < 5; j++ ) { + idPlayer* rankedPlayer = NULL; + int rankedScore = 0; + int k; + for( k = lastHighIndices[ i ]; k < rankedPlayers.Num(); k++ ) { + if( rankedPlayers[ k ].First()->team == i ) { + rankedPlayer = rankedPlayers[ k ].First(); + rankedScore = rankedPlayers[ k ].Second(); + break; + } + } + + // no more teammates + if( k >= rankedPlayers.Num() ) { + break; + } + + if( j == 4 && playerIndex == -1 && player->team == i ) { + int z; + for( z = 0; z < rankedPlayers.Num(); z++ ) { + if( rankedPlayers[ z ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ z ].Second(); + break; + } + } + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + playerIndex = j; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", i == TEAM_MARINE ? "summary_marine_names" : "summary_strogg_names", j ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + lastHighIndices[ i ] = k + 1; + } + } + + if( playerIndex > 0 ) { + if( player->team == TEAM_MARINE ) { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + } + } else { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } + } else { + for ( int i = 0; i < 10; i++ ) { + + // mekberg: delete old highlights + scoreBoard->DeleteStateVar( va( "summary_names_item_%d_highlight", i ) ); + + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( i == 9 && playerIndex == -1 ) { + // if the player is ranked, substitute them in + int i; + for( i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ i ].Second(); + break; + } + } + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + playerIndex = i; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_names", i ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + if( rankedPlayer->GetRank() == 0 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), blueHighlight ); + } else if( rankedPlayer->GetRank() == 1 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), redHighlight ); + } else if( rankedPlayer->GetRank() == 2 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), yellowHighlight ); + } + } else { + scoreBoard->SetStateString ( va("summary_names_item_%i", i), "" ); + } + } + + // highlight who we are (only if not ranked in the top 3) + if( player->GetRank() >= 0 && player->GetRank() < 3 ) { + scoreBoard->SetStateInt( "summary_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_names_sel_0", playerIndex ); + } + } + + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTestScoreboard +================ +*/ +void idMultiplayerGame::UpdateTestScoreboard ( idUserInterface *scoreBoard ) { + int i; + + gameLocal.random.SetSeed ( g_testScoreboard.GetInteger ( ) ); + + if( gameLocal.IsTeamGame() ) { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), "" ); + i++; + } + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), "" ); + i++; + } + + scoreBoard->SetStateInt ( "strogg_score", gameLocal.random.RandomInt ( 10 ) ); + scoreBoard->SetStateInt ( "marine_score", gameLocal.random.RandomInt ( 10 ) ); + } else { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( gameLocal.random.RandomInt() % 2 ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( gameLocal.random.RandomInt() % 2 ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + "", // shouchard: flag + name.c_str(), // name + "Clan", // clan + "", // team score (unused in DM) + gameLocal.random.RandomInt ( 50 ), // score + gameLocal.random.RandomInt ( 10 ), // time + gameLocal.random.RandomInt ( 300 ) + 20 ) ); + + + } + // clear remaining lines (empty slots) + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + i++; + } + } + + scoreBoard->SetStateInt ( "num_marine_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_strogg_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_players", g_testScoreboard.GetInteger() ); + + scoreBoard->SetStateInt( "rank_self", 2 ); + scoreBoard->SetStateInt ( "playercount", g_testScoreboard.GetInteger ( ) ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->SetStateString( "gameinfo", va( "Game Type:%s Frag Limit:%i Time Limit:%i", gameLocal.serverInfo.GetString( "si_gameType" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->Redraw( gameLocal.time ); +} +// RAVEN END + +/* +================ +idMultiplayerGame::GameTime +================ +*/ +const char *idMultiplayerGame::GameTime( void ) { + static char buff[32]; + int m, s, t, ms; + + bool inCountdown = false; + + ms = 0; + if( gameState->GetMPGameState() == COUNTDOWN ) { + inCountdown = true; + ms = gameState->GetNextMPGameStateTime() - gameLocal.realClientTime; + } else if( gameLocal.GetLocalPlayer() && gameLocal.gameType == GAME_TOURNEY && ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_WARMUP ) { + inCountdown = true; + ms = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetNextStateTime() - gameLocal.realClientTime; + } + if ( inCountdown ) { + s = ms / 1000 + 1; + if ( ms <= 0 ) { + // in tourney mode use a different string since warmups happen before each round + // (not really before the overall game) + idStr::snPrintf( buff, sizeof( buff ), "%s --", ( gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY ) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ) ); + } else { + idStr::snPrintf( buff, sizeof( buff ), "%s %i", (gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ), s ); + } + } else { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int startTime = matchStartedTime; + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.GetLocalPlayer() ) { + startTime = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetMatchStartTime(); + } + } + if ( timeLimit ) { + ms = ( timeLimit * 60000 ) - ( gameLocal.time - startTime ); + } else { + ms = gameLocal.time - startTime; + } + if ( ms < 0 ) { + ms = 0; + } + + s = ms / 1000; + m = s / 60; + s -= m * 60; + t = s / 10; + s -= t * 10; + + sprintf( buff, "%i:%i%i", m, t, s ); + } + return &buff[0]; +} + +/* +================ +idMultiplayerGame::NumActualClients +================ +*/ +int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) { + idPlayer *p; + int c = 0; + + if ( teamcounts ) { + teamcounts[ 0 ] = teamcounts[ 1 ] = 0; + } + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + p = static_cast< idPlayer * >( ent ); + if ( countSpectators || CanPlay( p ) ) { + c++; + } + if ( teamcounts && CanPlay( p ) ) { + teamcounts[ p->team ]++; + } + } + return c; +} + +/* +================ +idMultiplayerGame::EnoughClientsToPlay +================ +*/ +bool idMultiplayerGame::EnoughClientsToPlay() { + int team[ 2 ]; + int clients = NumActualClients( false, &team[ 0 ] ); + if ( gameLocal.IsTeamGame() ) { + return clients >= 2 && team[ 0 ] && team[ 1 ]; + } else { + return clients >= 2; + } +} + +/* +================ +idMultiplayerGame::AllPlayersReady +================ +*/ +bool idMultiplayerGame::AllPlayersReady( idStr* reason ) { + int i, minClients, numClients; + idEntity *ent; + idPlayer *p; + int team[ 2 ]; + bool notReady; + + notReady = false; + + minClients = Max( 2, gameLocal.serverInfo.GetInt( "si_minPlayers" ) ); + numClients = NumActualClients( false, &team[ 0 ] ); + if ( numClients < minClients ) { + if( reason ) { + // stupid english plurals + if( minClients == 2 ) { + *reason = common->GetLocalizedString( "#str_107674" ); + } else { + *reason = va( common->GetLocalizedString( "#str_107732" ), minClients - numClients ); + } + + } + + return false; + } + + if ( gameLocal.IsTeamGame() ) { + if ( !team[ 0 ] || !team[ 1 ] ) { + if( reason ) { + *reason = common->GetLocalizedString( "#str_107675" ); + } + + return false; + } + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + p = static_cast< idPlayer * >( ent ); + + if ( CanPlay( p ) && !p->IsReady() ) { + notReady = true; + } + team[ p->team ]++; + } + + if( notReady ) { + if( reason ) { + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() ) { + // Tourney has a different hud layout, so needs a different "you are (not)ready" string + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110018" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107711" ), common->KeysFromBinding( "_impulse17" ) ); + } + } else if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110017" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107710" ), common->KeysFromBinding( "_impulse17" ) ); + } + } + } + return false; + } + + return true; +} + +/* +================ +idMultiplayerGame::FragLimitHit +return the winning player (team player) +if there is no FragLeader(), the game is tied and we return NULL +================ +*/ +idPlayer *idMultiplayerGame::FragLimitHit() { + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + idPlayer *leader = NULL; + + if ( fragLimit <= 0 ) { + return NULL; // fraglimit disabled + } + + leader = FragLeader(); + if ( !leader ) { + return NULL; + } + + if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) { + return leader; + } + + return NULL; +} + +/* +================ +idMultiplayerGame::TimeLimitHit +================ +*/ +bool idMultiplayerGame::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +================ +idMultiplayerGame::FragLeader +return the current winner +NULL if even +relies on UpdatePlayerRanks() being called earlier in frame to sort players +================ +*/ +idPlayer* idMultiplayerGame::FragLeader( void ) { + if( rankedPlayers.Num() < 2 ) { + return NULL; + } + + // mark leaders + int i; + int high = GetScore( rankedPlayers[ 0 ].First() ); + idPlayer* p; + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + p = rankedPlayers[ i ].First(); + if ( !p ) { + continue; + } + p->SetLeader( false ); + + if ( !CanPlay( p ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY ) { + continue; + } + if ( p->spectating ) { + continue; + } + + if ( GetScore( p ) >= high ) { + p->SetLeader( true ); + } + } + + if( gameLocal.IsTeamGame() ) { + // in a team game, find the first player not on the leader's team, and make sure they aren't tied + int i = 0; + while( i < rankedPlayers.Num() && rankedPlayers[ i ].First()->team == rankedPlayers[ 0 ].First()->team ) { + i++; + } + if( i < rankedPlayers.Num() ) { + if( GetScore( rankedPlayers[ i ].First()->entityNumber ) == GetScore( rankedPlayers[ 0 ].First()->entityNumber ) ) { + return NULL; + } + } + } else if( GetScore( rankedPlayers[ 0 ].First()->entityNumber ) == GetScore( rankedPlayers[ 1 ].First()->entityNumber ) ) { + return NULL; + } + + return rankedPlayers[ 0 ].First(); +} + +/* +================ +idMultiplayerGame::PlayerDeath +================ +*/ +void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ) { + // don't do PrintMessageEvent + assert( !gameLocal.isClient ); + + if ( killer ) { + if ( gameLocal.IsTeamGame() ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + + // in flag games, we subtract suicides from team-score rather than player score, which is the true + // kill count + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( killer == dead ? dead : killer, -1 ); + } else { + AddPlayerScore( killer == dead ? dead : killer, -1 ); + } + + } else { + // mark a kill + AddPlayerScore( killer, 1 ); + } + + // additional CTF points + if( gameLocal.IsFlagGameType() ) { + if( dead->PowerUpActive( killer->team ? POWERUP_CTF_STROGGFLAG : POWERUP_CTF_MARINEFLAG ) ) { + AddPlayerTeamScore( killer, 2 ); + } + } + if( gameLocal.gameType == GAME_TDM ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + AddTeamScore( killer->team, -1 ); + } else { + AddTeamScore( killer->team, 1 ); + } + } + } else { + // in tourney mode, we don't award points while in the waiting arena + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameState)->GetArena( killer->GetArena() ).GetState() != AS_WARMUP ) { + AddPlayerScore( killer, ( killer == dead ) ? -1 : 1 ); + } + + // in tourney mode, frags track performance over the entire level load, team score keeps track of + // individual rounds + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( killer, ( killer == dead ) ? -1 : 1 ); + } + } + } else { + // e.g. an environmental death + + // flag gametypes subtract points from teamscore, not playerscore + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( dead, -1 ); + } else { + AddPlayerScore( dead, -1 ); + } + + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( dead, -1 ); + } + if( gameLocal.gameType == GAME_TDM ) { + AddTeamScore( dead->team, -1 ); + } + } + + SendDeathMessage( killer, dead, methodOfDeath ); + + statManager->Kill( dead, killer, methodOfDeath ); + +// RAVEN BEGIN +// shouchard: hack for CTF drop messages for listen servers + if ( dead == gameLocal.GetLocalPlayer() && + dead->PowerUpActive( dead->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + if ( dead->mphud ) { + dead->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + dead->mphud->HandleNamedEvent( "main_notice" ); + } + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::PlayerStats +================ +*/ +void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) { + + idEntity *ent; + int team; + + *data = 0; + + // make sure we don't exceed the client list + if ( clientNum < 0 || clientNum > gameLocal.numClients ) { + return; + } + + // find which team this player is on + ent = gameLocal.entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + team = static_cast< idPlayer * >(ent)->team; + } else { + return; + } + + idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount ); +} + +/* +================ +idMultiplayerGame::PlayerVote +================ +*/ +void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) { + playerState[ clientNum ].vote = vote; +} + +/* +================ +idMultiplayerGame::ExecuteVote +the votes are checked for validity/relevance before they are started +we assume that they are still legit when reaching here +================ +*/ +void idMultiplayerGame::ExecuteVote( void ) { + bool needRestart; + ClearVote(); + switch ( vote ) { + case VOTE_RESTART: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_TIMELIMIT: + si_timeLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_FRAGLIMIT: + si_fragLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_GAMETYPE: + cvarSystem->SetCVarString( "si_gametype", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_KICK: + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) ); + break; + case VOTE_MAP: + cvarSystem->SetCVarString( "si_map", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_BUYING: + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) + gameLocal.Error("MIN_PLAYERS vote in a Quake 4 1.2 recorded demo ( protocol 69 ) is not supported."); + cvarSystem->SetCVarString( "si_isBuyingEnabled", voteValue ); + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; +// RAVEN BEGIN +// shouchard: added capture limit + case VOTE_CAPTURELIMIT: + si_captureLimit.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + // todo: round limit here (if we add it) + case VOTE_AUTOBALANCE: + si_autobalance.SetInteger( atoi( voteValue ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + break; + case VOTE_MULTIFIELD: + ExecutePackedVote(); + break; +// RAVEN END + case VOTE_CONTROLTIME: + si_controlTime.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + case VOTE_NEXTMAP: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" ); + break; + } +} + +/* +================ +idMultiplayerGame::CheckVote +================ +*/ +void idMultiplayerGame::CheckVote( void ) { + int numVoters, i; + + if ( vote == VOTE_NONE ) { + return; + } + + if ( voteExecTime ) { + if ( gameLocal.time > voteExecTime ) { + voteExecTime = 0; + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + ExecuteVote(); + vote = VOTE_NONE; + } + return; + } + + // count voting players + numVoters = 0; + + for ( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) { + numVoters++; + } + } + if ( !numVoters ) { + // abort + vote = VOTE_NONE; + ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes, currentVoteData ); + return; + } + if ( float(yesVotes) / numVoters > 0.5f ) { + ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes, currentVoteData ); + voteExecTime = gameLocal.time + 2000; + return; + } + if ( gameLocal.time > voteTimeOut || float(noVotes) / numVoters >= 0.5f ) { + ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes, currentVoteData ); + vote = VOTE_NONE; + return; + } +} + +// RAVEN BEGIN +// shouchard: multifield voting here + +/* +================ +idMultiplayerGame::ClientCallPackedVote + +The assumption is that the zero changes case has been handled above. +================ +*/ +void idMultiplayerGame::ClientCallPackedVote( const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( 0 != voteData.m_fieldFlags ); + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ServerCallPackedVote +================ +*/ +void idMultiplayerGame::ServerCallPackedVote( int clientNum, const idBitMsg &msg ) { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + assert( -1 != clientNum ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + // this is set to false if an invalid parameter is asked for-- time limit of -1, or frag limit of "jeff" or whatever. + // if it's a multivote, it may still be valid, but this value is only checked if there are no vote parameters changed. + bool validVote = true; + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + + // flags (short) + voteData.m_fieldFlags = msg.ReadShort(); + + // clear any unallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if ( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // kick + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + if ( voteData.m_kick == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_KICK ); + } + } + + // map (string) + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + char buffer[128]; + msg.ReadString( buffer, sizeof( buffer ) ); + voteData.m_map = buffer; + if ( 0 == idStr::Icmp( buffer, si_map.GetString() ) ) { + //gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map is the same as current map." ); + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + + // because of addon pk4's clients may submit votes for maps the server doesn't have - audit here + const idDeclEntityDef *mapDef = (const idDeclEntityDef*)declManager->FindType( DECL_MAPDEF, voteData.m_map.c_str(), false ); + if( !mapDef ) { + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map does not exist on the server" ); + } + } + + // gametype + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + const char *voteString = "DM"; + switch ( voteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + voteString = "Tourney"; + break; + case VOTE_GAMETYPE_CTF: + voteString = "CTF"; + break; + case VOTE_GAMETYPE_TDM: + voteString = "Team DM"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + voteString = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + voteString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + voteString = "DM"; + break; + } + if ( !idStr::Icmp( voteString, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + } + + // timelimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + if ( voteData.m_timeLimit < si_timeLimit.GetMinValue() || voteData.m_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %d\n", clientNum, voteData.m_timeLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + if ( voteData.m_timeLimit == si_timeLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + } + + // tourneylimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + if ( voteData.m_tourneyLimit < si_tourneyLimit.GetMinValue() || voteData.m_tourneyLimit > si_tourneyLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104261" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + if ( voteData.m_tourneyLimit == si_tourneyLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104260" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + } + + // capture limit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + if ( voteData.m_captureLimit < si_captureLimit.GetMinValue() || voteData.m_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: caplimit value out of range for vote: %d\n", clientNum, voteData.m_captureLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + if ( voteData.m_captureLimit == si_captureLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + } + + // fraglimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + if ( voteData.m_fragLimit < si_fragLimit.GetMinValue() || voteData.m_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %d\n", clientNum, voteData.m_fragLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + if ( voteData.m_fragLimit == si_fragLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + } + + // spectators +/* if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_SPECTATORS ) ) { + voteData.m_spectators = msg.ReadByte(); + if ( voteData.m_spectators == si_spectators.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104421" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_SPECTATORS ); + } + } */ + + // buying + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadShort(); + if ( voteData.m_buying == si_isBuyingEnabled.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + validVote = false; + voteData.m_buying &= ( ~VOTEFLAG_BUYING ); + } + } + + // autobalance teams + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + if ( voteData.m_teamBalance == si_autobalance.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TEAMBALANCE ); + } + } + + // control time + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + if ( voteData.m_controlTime == si_controlTime.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CONTROLTIME ); + } + } + + // check for no changes at all + if ( 0 == voteData.m_fieldFlags ) { + // If the vote was called empty, announce there were no valid changes. Otherwise, say nothing, there's already been a warning message. + if( validVote ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104400" ); + } + return; + } + + ServerStartPackedVote( clientNum, voteData ); + ClientStartPackedVote( clientNum, voteData ); +} + +/* +================ +idMultiplayerGame::ClientStartPackedVote +================ +*/ +void idMultiplayerGame::ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + assert( 0 != voteData.m_fieldFlags ); + + if ( !gameLocal.isListenServer && !gameLocal.isClient ) { + return; + } + + // "%s has called a vote!" + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + + // display the vote called text on the hud and play an announcer sound + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + currentVoteData = voteData; + idUserInterface * mpHud = gameLocal.GetLocalPlayer()->mphud; + + // push data to the interface + if ( mpHud && mainGui ) { + int voteLineCount = 1; + int menuVoteLineCount = 0; + bool kickActive = false; + bool maxWindows = false; + idStr yesKey = common->KeysFromBinding("_impulse28"); + + mainGui->SetStateInt( "vote_going", 1 ); + + //dynamic vote yes/no box + mpHud->SetStateString( "voteNoticeText", va( common->GetLocalizedString( "#str_107242" ), yesKey.c_str(), common->KeysFromBinding("_impulse29") )); + + // kick should always be the highest one + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + // mpGui here, not mpHud + //mpHud->SetStateString( "vote_data0", va( common->GetLocalizedString( "#str_104422" ), player->GetName() ); + kickActive = true; + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + + const char *mapName = currentVoteData.m_map.c_str(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = common->GetLocalizedString( "#str_110011" ); + switch( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = common->GetLocalizedString( "#str_110012" ); + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = common->GetLocalizedString( "#str_110013" ); + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = common->GetLocalizedString( "#str_110014" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = common->GetLocalizedString( "#str_110015" ); + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = common->GetLocalizedString( "#str_110011" ); + break; + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + + //jshep: max of 7 windows and the 7th is always "..." + if( maxWindows ) { + mpHud->SetStateString( "voteInfo_7", "..." ); + } + + mainGui->DeleteStateVar( va( "voteData_item_%d", menuVoteLineCount ) ); + mainGui->SetStateInt( "vote_going", 1 ); + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), yesVotes, noVotes ) ); + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerStartPackedVote +================ +*/ +void idMultiplayerGame::ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( vote == VOTE_NONE ); + + if ( !gameLocal.isServer ) { + return; + } + + // #13705: clients passing a vote during server restart could abuse the voting system into passing the vote right away after the new map loads + if ( !playerState[ clientNum ].ingame ) { + common->Printf( "ignore vote called by client %d: not in game\n", clientNum ); + return; + } + + // setup + yesVotes = 1; + noVotes = 0; + vote = VOTE_MULTIFIELD; + currentVoteData = voteData; + voteTimeOut = gameLocal.time + 30000; // 30 seconds? might need to be longer because it requires fiddling with the GUI + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); +} + +/* +================ +idMultiplayerGame::ExecutePackedVote +================ +*/ +void idMultiplayerGame::ExecutePackedVote( void ) { + assert( VOTE_MULTIFIELD == vote ); + + if ( 0 == currentVoteData.m_fieldFlags ) { + return; + } + + bool needRestart = false; + bool needNextMap = false; + bool needRescanSI = false; + + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + si_isBuyingEnabled.SetInteger( currentVoteData.m_buying ); + needRescanSI = true; + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + si_autobalance.SetInteger( currentVoteData.m_teamBalance ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + si_controlTime.SetInteger( currentVoteData.m_controlTime ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + ShuffleTeams(); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d", currentVoteData.m_kick ) ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + si_map.SetString( currentVoteData.m_map.c_str() ); + needNextMap = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = NULL; + //jshepard: Currently the DM gametypes can be played on any map. The other gametypes require specially configured maps. + //if further gametypes are added that can be played on any map, don't set the "runPickMap" flag. + bool runPickMap = false; + switch ( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = "Tourney"; + runPickMap = true; + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = "Team DM"; + runPickMap = true; + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = "CTF"; + runPickMap = true; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = "Arena CTF"; + runPickMap = true; + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + runPickMap = true; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = "DM"; + break; + } + assert( gameTypeString ); + si_gameType.SetString( gameTypeString ); + //jshepard: run a pick map here in case the packed vote is trying to pick the wrong map type. + //PickMap returns true if the map has changed (requiring a nextMap call) + if( runPickMap ) { + if( PickMap( gameTypeString ) ) { + needNextMap = true; + } else { + needRestart = true; + } + } else { + needRestart = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + si_timeLimit.SetInteger( currentVoteData.m_timeLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + si_tourneyLimit.SetInteger( currentVoteData.m_tourneyLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + si_captureLimit.SetInteger( currentVoteData.m_captureLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + si_fragLimit.SetInteger( currentVoteData.m_fragLimit ); + needRescanSI = true; + } + + if ( needRescanSI ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + if ( needNextMap ) { + gameLocal.sessionCommand = "nextMap"; + } + else if ( needRestart || gameLocal.NeedRestart() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } +} + +// RAVEN END + +/* +================ +idMultiplayerGame::ClientEndFrame +Called once each render frame (client) after all idGameLocal::ClientPredictionThink() calls +================ +*/ +void idMultiplayerGame::ClientEndFrame( void ) { + iconManager->UpdateIcons(); +} + +/* +================ +idMultiplayerGame::CommonRun +Called once each render frame (client)/once each game frame (server) +================ +*/ +void idMultiplayerGame::CommonRun( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + // twhitaker r282 + // TTimo: sure is a nasty way to do it + if ( gameLocal.isServer && ( gameLocal.serverInfo.GetInt( "net_serverDedicated" ) != cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + } + + if ( player && player->mphud ) { + // update icons + if ( gameLocal.isServer ) { + iconManager->UpdateIcons(); + } + +#ifdef _USE_VOICECHAT + float micLevel; + bool sending, testing; + + // jscott: enable the voice recording + testing = cvarSystem->GetCVarBool( "s_voiceChatTest" ); + sending = soundSystem->EnableRecording( !!( player->usercmd.buttons & BUTTON_VOICECHAT ), testing, micLevel ); + + if( mainGui ) { + mainGui->SetStateFloat( "s_micLevel", micLevel ); + mainGui->SetStateFloat( "s_micInputLevel", cvarSystem->GetCVarFloat( "s_micInputLevel" ) ); + } + +// RAVEN BEGIN +// shouchard: let the UI know about voicechat states + if ( !testing && sending ) { + player->mphud->HandleNamedEvent( "show_transmit_self" ); + } else { + player->mphud->HandleNamedEvent( "hide_transmit_self" ); + } + + if( player->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) { + int maxChannels = soundSystem->GetNumVoiceChannels(); + int clientNum = -1; + for (int channels = 0; channels < maxChannels; channels++ ) { + clientNum = soundSystem->GetCommClientNum( channels ); + if ( -1 != clientNum ) { + break; + } + } + + // Sanity check for network errors + assert( clientNum > -2 && clientNum < MAX_CLIENTS ); + + if ( clientNum > -1 && clientNum < MAX_CLIENTS ) { + idPlayer *from = ( idPlayer * )gameLocal.entities[clientNum]; + if( from ) { + player->mphud->SetStateString( "audio_name", from->GetUserInfo()->GetString( "ui_name" ) ); + player->mphud->HandleNamedEvent( "show_transmit" ); + } + } else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } + } + else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } +#endif // _USE_VOICECHAT +// RAVEN END + } +#ifdef _USE_VOICECHAT + // jscott: Send any new voice data + XmitVoiceData(); +#endif + + int oldRank = -1; + int oldLeadingTeam = -1; + bool wasTied = false; + int oldHighScore = idMath::INT_MIN; + + if( player && rankedPlayers.Num() ) { + if( gameLocal.gameType == GAME_DM ) { + oldRank = GetPlayerRank( player, wasTied ); + oldHighScore = rankedPlayers[ 0 ].Second(); + } else if( gameLocal.IsTeamGame() ) { + oldLeadingTeam = rankedTeams[ 0 ].First(); + wasTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + oldHighScore = rankedTeams[ 0 ].Second(); + } + } + + UpdatePlayerRanks(); + if ( gameLocal.IsTeamGame() ) { + UpdateTeamRanks(); + } + + if ( player && rankedPlayers.Num() && gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType == GAME_DM ) { + // leader message + bool isTied = false; + int newRank = GetPlayerRank( player, isTied ); + + if ( newRank == 0 ) { + if( ( oldRank != 0 || wasTied ) && !isTied ) { + // we've gained first place or the person we were tied with dropped out of first place + ScheduleAnnouncerSound( AS_DM_YOU_HAVE_TAKEN_LEAD, gameLocal.time ); + } else if( oldRank != 0 || (!wasTied && isTied) ) { + // we tied first place or we were in first and someone else tied + ScheduleAnnouncerSound( AS_DM_YOU_TIED_LEAD, gameLocal.time ); + } + } else if ( oldRank == 0 ) { + // we lost first place + ScheduleAnnouncerSound( AS_DM_YOU_LOST_LEAD, gameLocal.time ); + } + } else if ( gameLocal.IsTeamGame() ) { + int leadingTeam = rankedTeams[ 0 ].First(); + bool isTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + + if ( !wasTied && isTied ) { + if ( gameLocal.gameType != GAME_DEADZONE ) + ScheduleAnnouncerSound( AS_TEAM_TEAMS_TIED, gameLocal.time ); + } else if ( (leadingTeam != oldLeadingTeam && !isTied) || ( wasTied && !isTied ) ) { + ScheduleAnnouncerSound( leadingTeam ? AS_TEAM_STROGG_LEAD : AS_TEAM_MARINES_LEAD, gameLocal.time ); + } + + if ( gameLocal.gameType == GAME_TDM && oldHighScore != teamScore[ rankedTeams[ 0 ].First() ] && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + } + + if( ( gameLocal.gameType == GAME_DM ) && rankedPlayers[ 0 ].Second() != oldHighScore && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + // fraglimit warning + if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + + } + + if ( rankTextPlayer ) { + bool tied = false; + int rank = GetPlayerRank( rankTextPlayer, tied ); + (gameLocal.GetLocalPlayer())->GUIMainNotice( GetPlayerRankText( rank, tied, playerState[ rankTextPlayer->entityNumber ].fragCount ) ); + rankTextPlayer = NULL; + } + + PlayAnnouncerSounds(); + + + // asalmon: Need to refresh stats periodically if the player is looking at stats + if ( currentStatClient != -1 ) { + rvPlayerStat* clientStat = statManager->GetPlayerStat( currentStatClient ); + if ( ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) { + statManager->SelectStatWindow(currentStatClient, currentStatTeam); + } + } + + bool updateModels = false; + if( g_forceModel.IsModified() && !gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceModel.ClearModified(); + } + + if( g_forceMarineModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceMarineModel.ClearModified(); + } + + if( g_forceStroggModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceStroggModel.ClearModified(); + } + + if( updateModels ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + player->UpdateModelSetup(); + } + } + } + + // do this here rather than in idItem::Think() because clients don't run Think on ents outside their snap + if( g_simpleItems.IsModified() ) { + + for( int i = 0; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( !ent || !ent->IsType( idItem::GetClassType() ) || ent->IsType( rvItemCTFFlag::GetClassType() ) ) { + continue; + } + + idItem* item = (idItem*)ent; + + item->FreeModelDef(); + + renderEntity_t* renderEntity = item->GetRenderEntity(); + memset( renderEntity, 0, sizeof( renderEntity ) ); + + item->simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !item->IsType( rvItemCTFFlag::GetClassType() ); + + if( item->simpleItem ) { + renderEntity->shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 32.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 32.0f; + renderEntity->hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity->callback = NULL; + renderEntity->numJoints = 0; + renderEntity->joints = NULL; + renderEntity->customSkin = 0; + renderEntity->noShadow = true; + renderEntity->noSelfShadow = true; + renderEntity->customShader = declManager->FindMaterial( item->spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity->referenceShader = 0; + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + renderEntity->axis = mat3_identity; + + item->StopEffect( "fx_idle", true ); + item->effectIdle = NULL; + item->SetAxis( mat3_identity ); + if( item->pickedUp ) { + item->FreeModelDef(); + item->UpdateVisuals(); + } + } else { + gameEdit->ParseSpawnArgsToRenderEntity( &item->spawnArgs, renderEntity ); + item->SetAxis( renderEntity->axis ); + + if ( item->spawnArgs.GetString( "fx_idle" ) ) { + item->UpdateModelTransform(); + item->effectIdle = item->PlayEffect( "fx_idle", renderEntity->origin, renderEntity->axis, true ); + } + + if( item->pickedUp && item->pickupSkin ) { + item->SetSkin( item->pickupSkin ); + } + } + if ( !item->spawnArgs.GetBool( "dropped" ) ) { + if ( item->spawnArgs.GetBool( "nodrop" ) ) { + item->GetPhysics()->PutToRest(); + } else { + item->Event_DropToFloor(); + } + } + } + + g_simpleItems.ClearModified(); + } +} + +/* +================ +idMultiplayerGame::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idMultiplayerGame::ClientRun( void ) { + CommonRun(); +} + + +/* +================ +idMultiplayerGame::ReportZoneControllingPlayer +================ +*/ +void idMultiplayerGame::ReportZoneControllingPlayer( idPlayer* player ) +{ + if ( !player ) + return; + + playerState[player->entityNumber].deadZoneScore += gameLocal.GetMSec(); + playerState[player->entityNumber].teamFragCount = playerState[player->entityNumber].deadZoneScore / 1000; + + float cashPerSecondForDeadZoneControl = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_deadZoneControlPerSecond", 0 ); +// player->GiveCash( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); + player->buyMenuCash += ( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); +} + + +/* +================ +idMultiplayerGame::ReportZoneController +================ +*/ +void idMultiplayerGame::ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger) +{ + powerupCount = pCount; + + idTrigger_Multi* zTrigger = 0; + if ( zoneTrigger && zoneTrigger->IsType( idTrigger_Multi::GetClassType() ) ) { + zTrigger = static_cast( zoneTrigger ); + } + + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != GAMEON && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ) + { + // We're not playing right now. However, make sure all the clients are updated to know + // that the zone is neutral. + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_NONE); + ((riDZGameState*)gameState)->SetDZState(TEAM_STROGG, DZ_NONE); + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + return; + } + + if ( IsValidTeam(team) ) { + const int t = gameLocal.serverInfo.GetInt( "si_controlTime" ); + teamDeadZoneScore[team] += gameLocal.GetMSec() * powerupCount; + teamScore[team] = (int)((float)teamDeadZoneScore[team] / 1000.0f); + + // We have a winner! + if ( teamDeadZoneScore[team] > t*1000 ) { + // Set the shaders and lights back to neutral. + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(idVec3(0.75f, 0.75f, 0.75f)); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + + OnDeadZoneTeamVictory( team ); + + return; + } + } + + // Someone took control of a zone, report this to the + if ( situation == DZ_MARINES_TAKEN || situation == DZ_STROGG_TAKEN || situation == DZ_MARINE_TO_STROGG || + situation == DZ_STROGG_TO_MARINE || situation == DZ_MARINE_REGAIN || situation == DZ_STROGG_REGAIN ) { + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_NONE); // Clear hacked deadlock + ((riDZGameState*)gameState)->SetDZState(team, DZ_TAKEN); + } + + const int NOCHANGE = -2; + const int DEADLOCK = 3; + int controlSit = NOCHANGE; + switch ( situation ) { + case DZ_NONE : + controlSit = NOCHANGE; + break; + case DZ_MARINES_TAKEN : + controlSit = TEAM_MARINE; + break; + case DZ_MARINES_LOST : + controlSit = TEAM_NONE; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_LOST); + break; + case DZ_STROGG_TAKEN : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_LOST : + controlSit = TEAM_NONE; + ((riDZGameState*)gameState)->SetDZState(TEAM_STROGG, DZ_LOST); + break; + case DZ_MARINE_TO_STROGG : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_TO_MARINE : + controlSit = TEAM_MARINE; + break; + case DZ_MARINE_DEADLOCK : + controlSit = DEADLOCK; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_STROGG_DEADLOCK : + controlSit = DEADLOCK; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_MARINE_REGAIN : + controlSit = TEAM_MARINE; + break; + case DZ_STROGG_REGAIN : + controlSit = TEAM_STROGG; + break; + } + + if ( zTrigger && controlSit == NOCHANGE && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + // There's been no change in status, but keep these variables updated on the client + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = (int)targetEnt->GetRenderEntity()->shaderParms[7]; + } + } + + if ( controlSit == NOCHANGE || !zTrigger ) + return; // We're done. + + idVec3 colorVec; + int parmNum = 2; + if ( controlSit == TEAM_NONE ) { + colorVec = idVec3(0.75f, 0.75f, 0.75f); + parmNum = 2; + } + else if ( controlSit == TEAM_MARINE ) { + colorVec = idVec3(0.0f, 1.0f, 0.0f); + parmNum = 0; + } + else if ( controlSit == TEAM_STROGG ) { + colorVec = idVec3(1.0f, 0.5f, 0.0f); + parmNum = 1; + } + else if ( controlSit == DEADLOCK ) { + colorVec = idVec3(1.0f, 0.0f, 0.0f); + parmNum = 3; + } + + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(colorVec); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = parmNum; + targetEnt->SetShaderParm(7, (float)parmNum); + } + } +} + + + +bool idMultiplayerGame::IsValidTeam(int team) +{ + if ( team == TEAM_MARINE || team == TEAM_STROGG ) + return true; + + return false; +} + + +void idMultiplayerGame::OnDeadZoneTeamVictory( int winningTeam ) +{ + OnBuyModeTeamVictory( winningTeam ); + + gameState->NewState( GAMEREVIEW ); +} + +void idMultiplayerGame::OnBuyModeTeamVictory( int winningTeam ) + { + if( !IsBuyingAllowedInTheCurrentGameMode() ) + return; + + float teamCashForWin = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeWin", 0 ); + float teamCashForTie = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeTie", 0 ); + float teamCashForLoss = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeLoss", 0 ); + + if( winningTeam == TEAM_NONE ) + { + GiveCashToTeam( TEAM_MARINE, teamCashForTie ); + GiveCashToTeam( TEAM_STROGG, teamCashForTie ); + } + else + { + int losingTeam = 1 - winningTeam; + GiveCashToTeam( winningTeam, teamCashForWin ); + GiveCashToTeam( losingTeam, teamCashForLoss ); + } + } + +/* +================ +idMultiplayerGame::Run +================ +*/ +void idMultiplayerGame::Run( void ) { + pureReady = true; + + assert( gameLocal.isMultiplayer && gameLocal.isServer && gameState ); + + CommonRun(); + + CheckVote(); + + CheckRespawns(); + + CheckSpecialLights( ); + +//RITUAL BEGIN + UpdateTeamPowerups(); +//RITUAL END + gameState->Run(); + + gameState->SendState(); + + // don't update the ping every frame to save bandwidth + if ( gameLocal.time > pingUpdateTime ) { + for ( int i = 0; i < gameLocal.numClients; i++ ) { + playerState[i].ping = networkSystem->ServerGetClientPing( i ); + } + pingUpdateTime = gameLocal.time + 1000; + } + + +} + +/* +================ +idMultiplayerGame::UpdateMainGui +================ +*/ +void idMultiplayerGame::UpdateMainGui( void ) { + int i; + mainGui->SetStateInt( "readyon", gameState->GetMPGameState() == WARMUP ? 1 : 0 ); + mainGui->SetStateInt( "readyoff", gameState->GetMPGameState() != WARMUP ? 1 : 0 ); + idStr strReady = cvarSystem->GetCVarString( "ui_ready" ); + if ( strReady.Icmp( "ready") == 0 ){ + strReady = common->GetLocalizedString( "#str_104248" ); + } else { + strReady = common->GetLocalizedString( "#str_104247" ); + } + mainGui->SetStateString( "ui_ready", strReady ); + mainGui->SetStateInt( "num_spec_players", unrankedPlayers.Num() ); + + mainGui->SetStateInt( "gametype", gameLocal.gameType ); + mainGui->SetStateBool( "s_useOpenAL", cvarSystem->GetCVarBool( "s_useOpenAL" ) ); + mainGui->SetStateBool( "s_loadOpenALFailed", cvarSystem->GetCVarBool( "s_loadOpenALFailed" ) ); + + idVec4 hitscanTint; + idStr hitScanValue = cvarSystem->GetCVarString( "ui_hitscanTint" ); + sscanf( hitScanValue.c_str(), "%f %f %f %f", &hitscanTint.x, &hitscanTint.y, &hitscanTint.z, &hitscanTint.w ); + mainGui->SetStateFloat( "ui_hitscanTint", hitscanTint.x ); + + // RAVEN BEGIN +// bdube: capture the flag + if ( gameLocal.IsTeamGame() ) { + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + mainGui->SetStateInt( "teamon", 1 ); + mainGui->SetStateInt( "teamoff", 0 ); + } else { + mainGui->SetStateInt( "teamon", 0 ); + mainGui->SetStateInt( "teamoff", 1 ); + } +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + mainGui->SetStateInt( "teamon", (gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + mainGui->SetStateInt( "teamoff", !(gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { +// RITUAL END + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + } + // setup vote + mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 ); + mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 ); + // send the current serverinfo values + for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) { + const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i ); + mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() ); + } + mainGui->StateChanged( gameLocal.time ); +#if defined( __linux__ ) + // replacing the oh-so-useful s_reverse with sound backend prompt + mainGui->SetStateString( "driver_prompt", "1" ); +#else + mainGui->SetStateString( "driver_prompt", "0" ); +#endif + +//RAVEN BEGIN +// cnicholson: Add Custom Crosshair update + mainGui->SetStateString( "g_crosshairCustom", cvarSystem->GetCVarBool( "g_crosshairCustom" ) ? "1" : "0" ); +//RAVEN END + +// RAVEN BEGIN +// cnicholson: We need to setup the custom crosshair so it shows up the first time the player enters the MP settings menu. +// This block checks the current crosshair, and compares it against the list of crosshairs in player.def (mtr_crosshair*) under the +// player_marine_mp section. If it finds a match, it assigns the crosshair, otherwise, the first found crosshair is used. +#ifndef _XENON + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + +#ifndef _XENON + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { // Loop through all crosshairs listed in the def + if ( kv->GetValue() == currentCrosshair.c_str() ) { // Until a match is found + break; + } + kv = defCH->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL ); // If no natches are found, use the first one. + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + + +//asalmon: Set up a state var for match type of Xbox 360 +#ifdef _XENON + mainGui->SetStateBool("CustomHost", Live()->IsCustomHost()); + mainGui->SetStateInt("MatchType", Live()->GetMatchtype()); + mainGui->SetStateString("si_gametype", gameLocal.serverInfo.GetString("si_gametype")); + const char *damage; + if (gameLocal.serverInfo.GetBool("si_teamdamage")){ + damage = "Yes" ; + } + else { + damage = "No"; + } + mainGui->SetStateString("si_teamdamage", damage); + const char *shuffle; + if (gameLocal.serverInfo.GetBool("si_shuffleMaps")){ + shuffle = "Yes" ; + } + else { + shuffle = "No"; + } + mainGui->SetStateString("si_shuffleMaps", shuffle); + mainGui->SetStateString("si_fraglimit", gameLocal.serverInfo.GetString("si_fraglimit")); + mainGui->SetStateString("si_capturelimit", gameLocal.serverInfo.GetString("si_capturelimit")); + mainGui->SetStateString("si_timelimit", gameLocal.serverInfo.GetString("si_timelimit")); + +// mekberg: send spectating to the mainGui. + if ( gameLocal.GetLocalPlayer( ) ) { + mainGui->SetStateBool( "spectating", gameLocal.GetLocalPlayer( )->spectating ); + if( gameLocal.gameType == GAME_TOURNEY ) { + // additionally in tourney, indicate whether the player is voluntarily spectating + mainGui->SetStateBool( "tourneyspectating", !idStr::Icmp( gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_spectate" ), "Spectate" ) ); + } + } else { + mainGui->SetStateBool( "spectating", false ); + } +#endif +// RAVEN END + +} + +/* +================ +idMultiplayerGame::SetupBuyMenuItems +================ +*/ +void idMultiplayerGame::SetupBuyMenuItems() +{ + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) + return; + + buyMenu->SetStateInt( "buyStatus_shotgun", player->ItemBuyStatus( "weapon_shotgun" ) ); + buyMenu->SetStateInt( "buyStatus_hyperblaster", player->ItemBuyStatus( "weapon_hyperblaster" ) ); + buyMenu->SetStateInt( "buyStatus_grenadelauncher", player->ItemBuyStatus( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "buyStatus_nailgun", player->ItemBuyStatus( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "buyStatus_rocketlauncher", player->ItemBuyStatus( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "buyStatus_railgun", player->ItemBuyStatus( "weapon_railgun" ) ); + buyMenu->SetStateInt( "buyStatus_lightninggun", player->ItemBuyStatus( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "buyStatus_dmg", player->ItemBuyStatus( "weapon_dmg" ) ); + buyMenu->SetStateInt( "buyStatus_napalmgun", player->ItemBuyStatus( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "buyStatus_lightarmor", player->ItemBuyStatus( "item_armor_small" ) ); + buyMenu->SetStateInt( "buyStatus_heavyarmor", player->ItemBuyStatus( "item_armor_large" ) ); + buyMenu->SetStateInt( "buyStatus_ammorefill", player->ItemBuyStatus( "ammorefill" ) ); + + buyMenu->SetStateInt( "buyStatus_special0", player->ItemBuyStatus( "ammo_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special1", player->ItemBuyStatus( "health_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special2", player->ItemBuyStatus( "damage_boost" ) ); + + buyMenu->SetStateInt( "playerTeam", player->team ); + + if ( player->weapon ) + buyMenu->SetStateString( "ammoIcon", player->weapon->spawnArgs.GetString ( "inv_icon" ) ); + + buyMenu->SetStateInt( "player_weapon", player->GetCurrentWeapon() ); +} + +/* +================ +idMultiplayerGame::StartMenu +================ +*/ +idUserInterface* idMultiplayerGame::StartMenu( void ) { + if ( mainGui == NULL ) { + return NULL; + } + //if we're the server, allow access to the admin tab right away. Otherwise, make sure we don't have it. + if( gameLocal.isServer ) { + mainGui->SetStateInt( "password_valid", 1 ); + } else { + mainGui->SetStateInt( "password_valid", 0 ); + } + + int i, j; + + if ( currentMenu ) { + currentMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + } else { + if ( nextMenu >= 2 ) { + currentMenu = nextMenu; + } else { + // for default and explicit + currentMenu = 1; + } + cvarSystem->SetCVarBool( "ui_chat", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = true; + } + + nextMenu = 0; + if ( currentMenu == 1 ) { + UpdateMainGui(); + + // UpdateMainGui sets most things, but it doesn't set these because + // it'd be pointless and/or harmful to set them every frame (for various reasons) + // Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this. + + // player kick data + for ( i = 0; i < 16; i++ ) { + kickVoteMapNames[ i ].Clear(); + kickVoteMap[ i ] = -1; + } + + idStr kickList; + j = 0; + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( kickList.Length() ) { + kickList += ";"; + } + kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) ); + kickVoteMap[ j ] = i; +// RAVEN BEGIN +// shouchard: names for kick vote map + kickVoteMapNames[ j ] = gameLocal.userInfo[ i ].GetString( "ui_name" ); +// RAVEN END + j++; + } + } + mainGui->SetStateString( "kickChoices", kickList ); + + mainGui->SetStateString( "chattext", "" ); + mainGui->Activate( true, gameLocal.time ); + + idPlayer *localP = gameLocal.GetLocalPlayer(); +#ifndef _XENON + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp", false ); +#else + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp_ui", false ); +#endif + idStr buildValues, buildNames; + + int numModels = declManager->GetNumDecls( DECL_PLAYER_MODEL ); + for( int i = 0; i < numModels; i++ ) { + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->DeclByIndex( DECL_PLAYER_MODEL, i, false ); + + if( !playerModel ) { + continue; + } + + const char *resultValue = playerModel->GetName(); + + if ( !resultValue || !resultValue[0] ) { + continue; + } + + const char *team = playerModel->team.c_str(); + + if ( gameLocal.IsTeamGame() ) { + if ( team && localP && localP->team >= 0 && localP->team < TEAM_MAX && idStr::Icmp( teamNames[ localP->team ], team ) == 0 ) { + } else { + // doesn't match, so skip + continue; + } + } + + if ( i ) { + buildValues += ";"; + buildNames += ";"; + } + buildValues += resultValue; + + const char *resultName = common->GetLocalizedString( playerModel->description.c_str() ); + + if ( !resultName || !resultName[0] ) { + buildNames += resultValue; + } else { + buildNames += resultName; + } + } + + mainGui->SetStateString( "model_values", buildValues.c_str() ); + mainGui->SetStateString( "model_names", buildNames.c_str() ); + mainGui->SetStateBool( "player_model_updated", true ); + + const char *model; + if ( localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if( *model == '\0' ) { + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + if( *model == '\0' ) { + model = def->dict.GetString( "def_default_model" ); + } + } + + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + mainGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + mainGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + mainGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + mainGui->SetStateBool( "need_update", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + cvarSystem->SetCVarString( "gui_ui_name", gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_name" ) ); + } + + return mainGui; + } else if ( currentMenu == 2 ) { + // the setup is done in MessageMode + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; + } + msgmodeGui->Activate( true, gameLocal.time ); + cvarSystem->SetCVarBool( "ui_chat", true ); + return msgmodeGui; + } else if ( currentMenu == 3 ) { + statSummary->Activate( true, gameLocal.time ); + statManager->SetupStatWindow( statSummary ); + UpdateScoreboard( statSummary ); + UpdateSummaryBoard( statSummary ); + statSummary->SetStateFloat( "ready", 0 ); + statSummary->StateChanged( gameLocal.time ); + + // Moved the announcer sound here. This way we can be sure the client has updated team score information by this point. + // #13576 #13544 causing double sounds because it's getting triggered twice at endgame ( from GameStateChanged and from ReceiveAllStats ) + // the move to here was for fixing some problem when running at the previous location ( GameStateChanged ) + // there are too many codepaths leading to various orders of ReceiveAllStats and GameStateChanged + // various attempts to flag the right call that should trigger the sound failed, so just using a timeout now + if ( gameLocal.time - lastVOAnnounce > 1000 ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() ) { + int winningTeam = GetScoreForTeam( TEAM_MARINE ) > GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + if( player->team == winningTeam ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } else if ( gameLocal.gameType != GAME_TOURNEY ) { + if( player->GetRank() == 0 ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } + lastVOAnnounce = gameLocal.time; + } + + return statSummary; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + //if( mpClientGameState.gameState.currentState == COUNTDOWN ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->SetStateInt( "price_shotgun", player->GetItemCost("weapon_shotgun") ); + buyMenu->SetStateInt( "price_hyperblaster", player->GetItemCost("weapon_hyperblaster") ); + buyMenu->SetStateInt( "price_grenadelauncher", player->GetItemCost( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "price_nailgun", player->GetItemCost( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "price_rocketlauncher", player->GetItemCost( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "price_railgun", player->GetItemCost( "weapon_railgun" ) ); + buyMenu->SetStateInt( "price_lightninggun", player->GetItemCost( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "price_dmg", player->GetItemCost( "weapon_dmg" ) ); + buyMenu->SetStateInt( "price_napalmgun", player->GetItemCost( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "price_lightarmor", player->GetItemCost( "item_armor_small" ) ); + buyMenu->SetStateInt( "price_heavyarmor", player->GetItemCost( "item_armor_large" ) ); + buyMenu->SetStateInt( "price_ammorefill", player->GetItemCost( "ammorefill" ) ); + + buyMenu->SetStateInt( "price_special0", player->GetItemCost( "ammo_regen" ) ); + buyMenu->SetStateInt( "price_special1", player->GetItemCost( "health_regen" ) ); + buyMenu->SetStateInt( "price_special2", player->GetItemCost( "damage_boost" ) ); + SetupBuyMenuItems(); + buyMenu->Activate(true, gameLocal.time); + return buyMenu; + //} +// RITUAL END + } + + return NULL; +} + +/* +================ +idMultiplayerGame::DisableMenu +================ +*/ +void idMultiplayerGame::DisableMenu( void ) { + if ( currentMenu == 1 ) { + mainGui->Activate( false, gameLocal.time ); + } else if ( currentMenu == 2 ) { + msgmodeGui->Activate( false, gameLocal.time ); + } else if( currentMenu == 3 ) { + statSummary->Activate( false, gameLocal.time ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + buyMenu->Activate( false, gameLocal.time ); +// RITUAL END + } + + // copy over name from temp cvar + if( currentMenu == 1 && idStr::Cmp( cvarSystem->GetCVarString( "gui_ui_name" ), cvarSystem->GetCVarString( "ui_name" ) ) ) { + cvarSystem->SetCVarString( "ui_name", cvarSystem->GetCVarString( "gui_ui_name" ) ); + } + + currentMenu = 0; + nextMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; +//RAVEN BEGIN +//asalmon: make the scoreboard on Xenon close +#ifdef _XENON + gameLocal.GetLocalPlayer()->scoreBoardOpen = false; +#endif +//RAVEN END + if( gameLocal.GetLocalPlayer()->mphud) { + gameLocal.GetLocalPlayer()->mphud->Activate( true, gameLocal.time ); + } + } + + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = -1; +} + +/* +================ +idMultiplayerGame::SetMapShot +================ +*/ +void idMultiplayerGame::SetMapShot( void ) { +#ifdef _XENON + // Should not be used + assert( 0 ); +#else + char screenshot[ MAX_STRING_CHARS ]; + int mapNum = mapList->GetSelection( NULL, 0 ); + const idDict *dict = NULL; + if ( mapNum >= 0 ) { + dict = fileSystem->GetMapDecl( mapNum ); + } + fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS ); + mainGui->SetStateString( "current_levelshot", screenshot ); +// RAVEN BEGIN +// cnicholson: Need to sort the material screenshot so it doesn't overlap other things + const idMaterial *mat = declManager->FindMaterial( screenshot ); + mat->SetSort( SS_GUI ); +// RAVEN END +#endif +} + +/* +================ +LocalServerRedirect +Dummy local redirect for gui rcon functionality on a local server +================ +*/ +void LocalServerRedirect( const char* string ) { + gameLocal.mpGame.ReceiveRemoteConsoleOutput( string ); +} + +/* +================ +idMultiplayerGame::HandleGuiCommands +================ +*/ +const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) { + idUserInterface *currentGui; +// RAVEN BEGIN +// shouchard: removed the code that deals with these variables + //const char *voteValue; + //int vote_clientNum; +// RAVEN END + int icmd; + idCmdArgs args; + + + + if ( !_menuCommand[ 0 ] ) { + common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" ); + return "continue"; + } + +#ifdef _XENON + if ( currentMenu == 0 && (session->GetActiveGUI() != scoreBoard) ) { +#else + if ( currentMenu == 0 ) { +#endif + return NULL; // this will tell session to not send us events/commands anymore + } + + if ( currentMenu == 1 ) { + currentGui = mainGui; + } +#ifdef _XENON + else if (session->GetActiveGUI() != scoreBoard) { + currentGui = msgmodeGui; + } else { + currentGui = scoreBoard; + } +#else + else if( currentMenu == 2 ) { + currentGui = msgmodeGui; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + currentGui = buyMenu; +// jmartel: make sure var is initialized (compiler complained) + } else if( currentMenu == 3 ) { + currentGui = statSummary; + } else { + gameLocal.Warning( "idMultiplayerGame::HandleGuiCommands() - Unknown current menu '%d'\n", currentMenu ); + currentGui = mainGui; + } +// RITUAL END +#endif + + + args.TokenizeString( _menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( !idStr::Icmp( cmd, ";" ) ) { + continue; + } else if ( !idStr::Icmp( cmd, "inGameMenu" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + currentGui->SetStateString( "chat", chatHistory.c_str() ); + + if( gameLocal.GetLocalPlayer() ) { + currentGui->SetStateInt( "player_team", gameLocal.GetLocalPlayer()->team ); + } + + // mekberg: added + UpdateMPSettingsModel ( currentGui ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) ) { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107699" ) ); + } else { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107700" ) ); + } + } + + currentGui->SetStateBool( "useReady", gameLocal.serverInfo.GetBool( "si_useReady", "0" ) && gameState->GetMPGameState() == WARMUP ); + if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104247" ) ); + } else if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104248" ) ); + } else { + currentGui->SetStateString( "readyStatus", "" ); + } + + currentGui->SetStateBool( "si_allowVoting", gameLocal.serverInfo.GetBool( "si_allowVoting" ) ); + currentGui->SetStateBool( "si_allowVoice", gameLocal.serverInfo.GetBool( "si_voiceChat" ) ); + + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), false ); + } else { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), true ); + } + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "video" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + if ( idStr::Icmp( vcmd, "low" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 0 ); + } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 1 ); + } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 2 ); + } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 3 ); + } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + } + +// RAVEN BEGIN +// mekberg: set the r_mode. + cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); + currentGui->SetStateInt( "r_aspectRatio", 0 ); + currentGui->HandleNamedEvent( "forceAspect0" ); + currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + currentGui->StateChanged( gameLocal.realClientTime ); + cvarSystem->SetCVarInteger( "r_mode", common->GetRModeForMachineSpec ( cvarSystem->GetCVarInteger( "com_machineSpec" ) ) ); + common->SetDesiredMachineSpec( cvarSystem->GetCVarInteger( "com_machineSpec" ) ); +// RAVEN END + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec" ); + if ( idStr::Icmp( vcmd, "restart" ) == 0) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart\n" ); + } + + continue; + } else if ( !idStr::Icmp( cmd, "join" ) ) { + if ( args.Argc() - icmd >= 1 ) { + JoinTeam( args.Argv( icmd++ ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "quit" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "close" ) ) { + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "spectate" ) ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "admin" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + idStr input( currentGui->State().GetString( "admin_console_input" ) ); + input.StripTrailing( "\n" ); + //jshepard: check to see if this is a server before using rcon! + if( gameLocal.isServer ) { + char redirectBuffer[ RCON_HISTORY_SIZE ]; + common->BeginRedirect( (char *)redirectBuffer, sizeof( redirectBuffer ), LocalServerRedirect ); + + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + ReceiveRemoteConsoleOutput( input.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "%s\n", input.c_str() ) ); + } + + common->EndRedirect(); + } else { + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon \"%s\"\n", input.c_str() ) ); + ReceiveRemoteConsoleOutput( input.c_str() ); + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "chatmessage" ) ) { + int mode = currentGui->State().GetInt( "messagemode" ); +// RAVEN BEGIN +// bdube: dont send chat message if there was no text specified + const char* text; + text = currentGui->State().GetString( "chattext" ); + if ( *text ) { + if ( mode ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", text ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); + } + } +// RAVEN BEGIN + currentGui->SetStateString( "chattext", "" ); + if ( currentMenu == 1 || currentMenu == 3 ) { + return "continue"; + } else { + DisableMenu(); + return NULL; + } + } else if ( !idStr::Icmp( cmd, "toggleReady" ) ) { + ToggleReady( ); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + int channel = 1; + if ( snd.Length() == 1 ) { + channel = atoi( snd ); + snd = args.Argv( icmd++ ); + } + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, snd, channel ); + } + continue; + } else if ( !idStr::Icmp( cmd, "callVote" ) ) { +// RAVEN BEGIN +// shouchard: new functionality to match the new interface + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + // kick + int uiKickSelection = mainGui->State().GetInt( "playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + voteData.m_kick = kickVoteMap[ uiKickSelection ]; + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + // restart + if ( 0 != mainGui->State().GetInt( "vote_val2_sel" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } + + if ( 0 != mainGui->State().GetBool( "si_shuffleteams" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_SHUFFLE; + } + // map + int uiMapSelection = mainGui->State().GetInt( "mapList_sel_0" ); + if ( -1 != uiMapSelection ) { +// rjohnson: code commented out below would get the text friendly name of the map and not the file name + int mapNum = mainGui->State().GetInt( va( "mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + voteData.m_map = dict->GetString( "path" ); + voteData.m_fieldFlags |= VOTEFLAG_MAP; + } +// const char *mapName = mainGui->State().GetString( va( "mapList_item_%d", uiMapSelection ) ); +// if ( NULL != mapName && '\0' != mapName[0] ) { +// if ( mapFileName[ 0 ] ) { +// voteData.m_map = va( "mp/%s", mapName ); +// voteData.m_fieldFlags |= VOTEFLAG_MAP; +// } + } + // gametype + // todo: need a function for switching between gametype strings and values + int uiGameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gametype" ); + int serverGameTypeInt = GameTypeToVote( currentGameTypeString ); + + if ( uiGameTypeInt != serverGameTypeInt ) { + voteData.m_gameType = uiGameTypeInt; + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + } + // time limit + int uiTimeLimit = mainGui->GetStateInt( "timeLimit" ); + if ( uiTimeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + voteData.m_timeLimit = uiTimeLimit; + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + } + // autobalance + int uiBalanceTeams = mainGui->GetStateInt( "vote_val6_sel" ); + if ( uiBalanceTeams != gameLocal.serverInfo.GetInt( "si_autobalance" ) ) { + voteData.m_teamBalance = uiBalanceTeams; + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + } + // allow spectators + /* int uiAllowSpectators = mainGui->GetStateInt( "vote_val7_sel" ); + if ( uiAllowSpectators != gameLocal.serverInfo.GetInt( "si_spectators" ) ) { + voteData.m_spectators = uiAllowSpectators; + voteData.m_fieldFlags |= VOTEFLAG_SPECTATORS; + } */ + // minimum players + int uiBuying = mainGui->GetStateInt( "buying" ); + if ( uiBuying != gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + voteData.m_buying = uiBuying; + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + } + // roundlimit (tourney only) + int uiTourneyLimit = mainGui->GetStateInt( "tourneylimit" ); + if ( uiTourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + voteData.m_tourneyLimit = uiTourneyLimit; + voteData.m_fieldFlags |= VOTEFLAG_TOURNEYLIMIT; + } + // capturelimit (ctf only) + int uiCaptureLimit = mainGui->GetStateInt( "capturelimit" ); + if ( uiCaptureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + voteData.m_captureLimit = uiCaptureLimit; + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + } + // controltime (deadzone only) + int uiControlTime = mainGui->GetStateInt( "controlTime" ); + if ( uiControlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + voteData.m_controlTime = uiControlTime; + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + } + // fraglimit (DM & TDM only) + int uiFragLimit = mainGui->GetStateInt( "fraglimit" ); + if ( uiFragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + voteData.m_fragLimit = uiFragLimit; + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + } + DisableMenu(); + + // clear any disallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // this means we haven't changed anything + if ( 0 == voteData.m_fieldFlags ) { + //AddChatLine( common->GetLocalizedString( "#str_104400" ) ); + } else { + ClientCallPackedVote( voteData ); + } + /* + // sjh: original doom code here + vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" ); + if ( voteIndex == VOTE_MAP ) { + int mapNum = mapList->GetSelection( NULL, 0 ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + if ( dict ) { + ClientCallVote( VOTE_MAP, dict->GetString( "path" ) ); + } + } + } else { + voteValue = mainGui->State().GetString( "str_voteValue" ); + if ( voteIndex == VOTE_KICK ) { + vote_clientNum = kickVoteMap[ atoi( voteValue ) ]; + ClientCallVote( voteIndex, va( "%d", vote_clientNum ) ); + } else { + ClientCallVote( voteIndex, voteValue ); + } + } + */ + return NULL; + } else if ( !idStr::Icmp( cmd, "voteYes" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteNo" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "click_playerList" ) ) { + // push data into the name field + int sel = mainGui->GetStateInt( "playerList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "playerKick", "" ); + } else { + mainGui->SetStateString( "playerKick", kickVoteMapNames[ sel ] ); + } + continue; + } else if ( !idStr::Icmp( cmd, "click_voteMapList" ) ) { + int sel = mainGui->GetStateInt( "mapList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "mapName", "" ); + } else { + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", sel ) ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "setVoteMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + int gameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *gameType = NULL; + switch ( gameTypeInt ) { + case VOTE_GAMETYPE_DM: + gameType = "DM"; + break; + case VOTE_GAMETYPE_TOURNEY: + gameType = "Tourney"; + break; + case VOTE_GAMETYPE_TDM: + gameType = "Team DM"; + break; + case VOTE_GAMETYPE_CTF: + gameType = "CTF"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameType = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + gameType = "DeadZone"; + break; + } + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } + + idStr originalMapName = gameLocal.serverInfo.GetString( "si_map" ); + originalMapName.StripFileExtension(); + + bool foundOriginalMap = false; + int originalMapIndex = -1; + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + + if ( idStr::Icmp(dict->GetString( "path" ), originalMapName) == 0 ) { + foundOriginalMap = true; + originalMapIndex = numMapsAdded; + } + + mainGui->SetStateString( va( "mapList_item_%d", numMapsAdded), mapName ); + mainGui->SetStateInt( va( "mapList_item_%d_id", numMapsAdded), i ); + + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "mapList_item_%d", numMapsAdded ) ); + + if ( !foundOriginalMap ) { + mainGui->SetStateInt( "mapList_sel_0", 0 ); + mainGui->SetStateString( "mapName", mainGui->GetStateString( "mapList_item_0" ) ); + } else { + mainGui->SetStateInt( "mapList_sel_0", originalMapIndex ); + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", originalMapIndex ) ) ); + } + +#endif + continue; + } else if ( !idStr::Icmp( cmd, "setVoteData" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // push data into the vote_ cvars so the UI can start at where we currently are + int players; + for ( players=0; playersSetStateString( va( "playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "playerList_item_%d", players ) ); + } + mainGui->SetStateString( "playerList_sel_0", "-1" ); + mainGui->SetStateString( "playerKick", "" ); + mainGui->SetStateInt( "vote_val2_sel", 0 ); + +// RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mainGui->SetStateString( "mapName", mapName ); +// RAVEN END + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); // this will need to change for multi-votes + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } +// RAVEN BEGIN +// jshepard: if gametype is DM, then we can play on DM, TeamDM, Tourney or CTF maps. So, all of them, basically. + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mainGui->SetStateString( va( "mapList_item_%d", numMapsAdded), mapName ); + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "mapList_item_%d", numMapsAdded ) ); + mainGui->SetStateString( "mapList_sel_0", "-1" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "currentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "vote_val6_sel", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "si_shuffleteams", 0 ); + mainGui->StateChanged( gameLocal.time ); + mainGui->HandleNamedEvent( "gametypeChange" ); +#endif + continue; + } else if ( !idStr::Icmp( cmd, "populateServerInfo" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + mainGui->SetStateString( "serverInfoList_item_0", va( "%s:\t%s", common->GetLocalizedString( "#str_107725" ), gameLocal.serverInfo.GetString( "si_name" ) ) ); + idStr serverAddress = networkSystem->GetServerAddress( ); + mainGui->SetStateString( "serverInfoList_item_1", va( "%s:\t%s", common->GetLocalizedString( "#str_107726" ), serverAddress.c_str() ) ); + mainGui->SetStateString( "serverInfoList_item_2", va( "%s:\t%s", common->GetLocalizedString( "#str_107727" ), LocalizeGametype() ) ); + + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } +// rhummer localized "map name" + mainGui->SetStateString( "serverInfoList_item_3", va( "%s\t%s", common->GetLocalizedString( "#str_107730" ), mapName ) ); + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetString( "si_captureLimit" ) ) ); + } + else if ( 0 == idStr::Icmp( gameType, "DM" ) || 0 == idStr::Icmp( gameType, "Team DM" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetString( "si_fragLimit" ) ) ); + } + mainGui->SetStateString( "serverInfoList_item_5", va( "%s:\t%s", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetString( "si_timeLimit" ) ) ); + mainGui->SetStateString( "serverInfoList_item_6", va( "%s:\t%s", common->GetLocalizedString( "#str_107662" ), gameLocal.serverInfo.GetString( "si_pure" ) ) ); + mainGui->SetStateString( "serverInfoList_item_7", va( "%s:\t%s", common->GetLocalizedString( "#str_107663" ), gameLocal.serverInfo.GetString( "si_maxPlayers" ) ) ); + mainGui->SetStateString( "serverInfoList_item_8", va( "%s:\t%s", common->GetLocalizedString( "#str_107664" ), gameLocal.serverInfo.GetString( "si_teamDamage" ) ) ); + mainGui->SetStateString( "serverInfoList_item_9", va( "%s:\t%s", common->GetLocalizedString( "#str_104254" ), gameLocal.serverInfo.GetString( "si_spectators" ) ) ); +#endif + continue; + // handler for the server admin tab (normal stuff) + } else if ( !idStr::Icmp( cmd, "checkAdminPass" )) { + //password has been added, so call the rcon verifypassword command + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon verifyRconPass" ); + continue; + + } else if ( !idStr::Icmp( cmd, "initServerAdmin" ) ) { + mainGui->SetStateInt( "admin_server_val1_sel", 0 ); // restart defaults to off + // maplist handled in initServerAdminMaplist; this needs to be called first + // to properly set the gametype since we read it back to show an appropriate list + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "admincurrentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "sa_timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "sa_tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "sa_capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "sa_controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "sa_autobalance", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "sa_buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "sa_fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "sa_shuffleteams", 0 ); +// mekberg: get the ban list if not server + if ( !gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + networkSystem->ClientSendReliableMessage( outMsg ); + } + + mainGui->StateChanged( gameLocal.time ); + + continue; + // handler for populating the map list; called both on open and on change gametype so it'll show the right maps + } else if ( !idStr::Icmp( cmd, "initServerAdminMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mainGui->SetStateString( "sa_mapName", mapName ); +// RAVEN END + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + const char *gameType = "DM"; + + int gameTypeInt = mainGui->GetStateInt( "adminCurrentGametype" ); + + if ( VOTE_GAMETYPE_TOURNEY == gameTypeInt ) { + gameType = "Tourney"; + } else if ( VOTE_GAMETYPE_TDM == gameTypeInt ) { + gameType = "Team DM"; + } else if ( VOTE_GAMETYPE_CTF == gameTypeInt ) { + gameType = "CTF"; + } else if ( VOTE_GAMETYPE_ARENA_CTF == gameTypeInt ) { + gameType = "Arena CTF"; + } else if ( VOTE_GAMETYPE_DEADZONE == gameTypeInt ) { + gameType = "DeadZone"; + } else { + gameType = "DM"; + } + // sanity check + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if ( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mainGui->SetStateString( va( "sa_mapList_item_%d", numMapsAdded), mapName ); + mainGui->SetStateInt( va( "sa_mapList_item_%d_id", numMapsAdded), i ); + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "sa_mapList_item_%d", numMapsAdded ) ); + mainGui->SetStateString( "sa_mapList_sel_0", "-1" ); +#endif + continue; + // handler for updating the current map in the name + } else if ( !idStr::Icmp( cmd, "serverAdminUpdateMap" ) ) { + int mapSelection = mainGui->GetStateInt( "sa_mapList_sel_0" ); + if ( -1 == mapSelection ) { + + idDecl *mapDecl = const_cast(declManager->FindType( DECL_MAPDEF, gameLocal.serverInfo.GetString( "si_map" ), false )); + if ( mapDecl ) { + idDeclEntityDef *mapDef = static_cast( mapDecl ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( mapDef->dict.GetString( "name" )) ); + } else { + mainGui->SetStateString( "sa_mapName", gameLocal.serverInfo.GetString( "si_map" ) ); + } + + } else { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", mapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( dict->GetString( "name" )) ); + } + } + continue; + // handler for initializing the player list on the admin player tab + } else if ( !idStr::Icmp( cmd, "initServerAdminPlayer" ) ) { + int players; + for ( players=0; playersSetStateString( va( "sa_playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", players ) ); + //common->Printf( "DELETING at slot %d\n", players ); + } + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + continue; + // handler for actually changing something on the server admin tab + } else if ( !idStr::Icmp( cmd, "handleServerAdmin" ) ) { + // read in a bunch of data, pack it into the appropriate structure + serverAdminData_t data; + memset( &data, 0, sizeof( data ) ); + data.restartMap = 0 != mainGui->GetStateInt( "admin_server_val1_sel" ); + // map list here + int uiMapSelection = mainGui->State().GetInt( "sa_mapList_sel_0" ); + if (-1 != uiMapSelection ) { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + data.mapName = common->GetLocalizedString( dict->GetString( "path" )); + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + + switch ( mainGui->GetStateInt( "admincurrentGametype" ) ) { + case VOTE_GAMETYPE_DM: + data.gameType = GAME_DM; + break; + case VOTE_GAMETYPE_TOURNEY: + data.gameType = GAME_TOURNEY; + break; + case VOTE_GAMETYPE_TDM: + data.gameType = GAME_TDM; + break; + case VOTE_GAMETYPE_CTF: + data.gameType = GAME_CTF; + break; + case VOTE_GAMETYPE_ARENA_CTF: + data.gameType = GAME_ARENA_CTF; + break; + case VOTE_GAMETYPE_DEADZONE: + data.gameType = GAME_DEADZONE; + } + data.captureLimit = mainGui->GetStateInt( "sa_captureLimit" ); + data.fragLimit = mainGui->GetStateInt( "sa_fragLimit" ); + data.tourneyLimit = mainGui->GetStateInt( "sa_tourneylimit" ); + data.timeLimit = mainGui->GetStateInt( "sa_timeLimit" ); + data.buying = mainGui->GetStateInt( "sa_buying" ); + data.autoBalance = 0 != mainGui->GetStateInt( "sa_autobalance" ); + data.buying = 0 != mainGui->GetStateInt( "sa_buying" ); + data.controlTime = mainGui->GetStateInt( "sa_controlTime" ); + data.shuffleTeams = 0 != mainGui->GetStateInt( "sa_shuffleteams" ); + + // make the call to change the server data + if ( gameLocal.mpGame.HandleServerAdminCommands( data ) ) { + DisableMenu(); + return NULL; + } + continue; + // handler for the kick button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminKick" ) ) { + int uiKickSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + HandleServerAdminKickPlayer( kickVoteMap[ uiKickSelection ] ); + DisableMenu(); + return NULL; + } + //common->Printf( "HANDLE SERVER ADMIN KICK!\n" ); + continue; + // handler for the ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiBanSelection ) { + HandleServerAdminBanPlayer( kickVoteMap[ uiBanSelection ] ); + DisableMenu(); + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + return NULL; + } + continue; + // handler for the remove ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminRemoveBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN REMOVE BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_banList_sel_0" ); + if ( -1 != uiBanSelection ) { + idStr guid = &mainGui->GetStateString( va( "sa_banList_item_%d", uiBanSelection ) )[ 4 ]; + guid = guid.ReplaceChar( '\t', '\0' ); + guid = &guid.c_str()[ strlen( guid.c_str() ) + 1 ]; + HandleServerAdminRemoveBan( guid.c_str() ); + DisableMenu(); + return NULL; + } + continue; + // handler for the switch teams button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminSwitchTeams" ) ) { + if ( gameLocal.IsTeamGame() ) { + int uiSwitchSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiSwitchSelection ) { + HandleServerAdminForceTeamSwitch( kickVoteMap[ uiSwitchSelection ] ); + DisableMenu(); + return NULL; + } + } + continue; + // handler for the show ban list button of the server admin gui + } else if ( !idStr::Icmp( cmd, "populateBanList" ) ) { + gameLocal.PopulateBanList( mainGui ); + continue; +// RAVEN END + } else if ( !idStr::Icmp( cmd, "voteyes" ) ) { + CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteno" ) ) { + CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "bind" ) ) { + if ( args.Argc() - icmd >= 2 ) { + idStr key = args.Argv( icmd++ ); + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "clearbind" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "MAPScan" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" ); + if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) { + gametype = "DM"; + } + + int i, num; + idStr si_map = gameLocal.serverInfo.GetString("si_map"); + const idDict *dict; + + mapList->Clear(); + mapList->SetSelection( -1 ); + num = fileSystem->GetNumMaps(); + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if ( dict ) { + // any MP gametype supported + bool isMP = false; + int igt = GAME_SP + 1; + while ( si_gameTypeArgs[ igt ] ) { + if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) { + isMP = true; + break; + } + igt++; + } + if ( isMP ) { + const char *mapName = dict->GetString( "name" ); + if ( mapName[0] == '\0' ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mapList->Add( i, mapName ); + if ( !si_map.Icmp( dict->GetString( "path" ) ) ) { + mapList->SetSelection( mapList->Num() - 1 ); + } + } + } + } + // set the current level shot + SetMapShot( ); +#endif + return "continue"; + } else if ( !idStr::Icmp( cmd, "click_maplist" ) ) { + SetMapShot( ); + return "continue"; + } else if ( !idStr::Icmp( cmd, "sm_select_player" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + int index = atoi( vcmd.c_str() ); + if( index > 0 && index < MAX_CLIENTS && statSummary && currentMenu == 3 ) { + statManager->UpdateEndGameHud( statSummary, index - 1 ); + } + return "continue"; + } else if ( !idStr::Icmp( cmd, "update_model" ) ) { + UpdateMPSettingsModel( currentGui ); + continue; + } else if( !idStr::Icmp( cmd, "ingameStats" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + // setup the player list + statManager->SetupStatWindow( currentGui ); + } else if( !igArg.Icmp( "spectator" ) ) { + int currentSel = currentGui->State().GetInt( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MAX ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MAX; + } else if( !igArg.Icmp( "dm" ) ) { + int currentSel = currentGui->State().GetInt( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, 0 ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = 0; + } else if( !igArg.Icmp( "strogg" ) ) { + int currentSel = currentGui->State().GetInt( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_STROGG ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_STROGG; + } else if( !igArg.Icmp( "marine" ) ) { + int currentSel = currentGui->State().GetInt( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MARINE ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MARINE; + } + } + continue; + } else if( !idStr::Icmp( cmd, "mainMenu" ) ) { + DisableMenu(); + static idStr menuCmd; + menuCmd.Clear(); // cnicholson: In order to avoid repeated eventnames from screwing up the menu system, clear it. + menuCmd.Append( "main" ); + const char* eventName = ""; + if( args.Argc() - icmd >= 1 ) { + eventName = args.Argv( icmd++ ); + menuCmd.Append( " " ); + menuCmd.Append( eventName ); + } + return menuCmd.c_str(); + } +// RAVEN BEGIN +// cnicholson: The menu calls this prior to entering multiplayer settings. What it does is to check the current crosshair, and compare it +// agasint the list of crosshairs in player.def under the player_marine_mp section. If it finds a match, it assigns the +// crosshair to the next one in the list. If there isn't one, or if its the end of the list, the first found crosshair is used. + else if ( !idStr::Icmp( cmd, "chooseCrosshair" ) ) { +#ifndef _XENON + +#ifndef _XENON + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = def->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { + if ( kv->GetValue() == currentCrosshair.c_str() ) { + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + break; + } + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = def->dict.MatchPrefix("mtr_crosshair", NULL ); + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + } +// RAVEN END + else if( !idStr::Icmp( cmd, "friend" ) ) { + // we friend/unfriend from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + if( ( client < 0 || client >= MAX_CLIENTS ) || !gameLocal.GetLocalPlayer() ) { + continue; + } + + // un-mark this client as a friend + if( gameLocal.GetLocalPlayer()->IsFriend( client ) ) { + networkSystem->RemoveFriend( client ); + } else { + networkSystem->AddFriend( client ); + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + continue; + } else if( !idStr::Icmp( cmd, "mute" ) ) { + // we mute/unmute from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + + ClientVoiceMute( client, !gameLocal.GetLocalPlayer()->IsPlayerMuted( client ) ); + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + + continue; + } +//RAVEN BEGIN +//asalmon: pass through some commands that need to be handled in the main menu handle function + else if(strstr( cmd, "LiveInviteAccept" ) == cmd){ +#ifdef _XENON + Live()->SetInvite(); +#endif + } + else if ((strstr( cmd, "FilterMPMapList" ) == cmd) + || (strstr( cmd, "AddMapLive" ) == cmd) + || (strstr( cmd, "RemoveMapLive" ) == cmd) + ) { + static idStr menuCmd; + menuCmd.Clear(); + menuCmd.Append( cmd ); + return menuCmd.c_str(); + } else if( !idStr::Icmp( cmd, "toggleTourney" ) ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } + continue; + } +//RAVEN END + common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd ); + + } + return "continue"; +} + +/* +=============== +idMultiplayerGame::SetShaderParms +=============== +*/ +void idMultiplayerGame::SetShaderParms( renderView_t *view ) { + if ( gameLocal.IsFlagGameType() ) { + view->shaderParms[ 1 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_MARINE ) != FS_AT_BASE ); + view->shaderParms[ 2 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_STROGG ) != FS_AT_BASE ); + } +} + +/* +================ +idMultiplayerGame::Draw +server demo: clientNum == MAX_CLIENTS +================ +*/ +bool idMultiplayerGame::Draw( int clientNum ) { + idPlayer *player, *viewPlayer; + idUserInterface *hud = NULL; + + if ( clientNum == MAX_CLIENTS ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + clientNum = gameLocal.GetDemoFollowClient(); + hud = gameLocal.GetDemoHud(); + if ( clientNum == -1 ) { + gameLocal.freeView.Draw(); + return true; + } + } + + player = viewPlayer = static_cast( gameLocal.entities[ clientNum ] ); + + if ( player == NULL ) { + return false; + } + + if ( player->spectating ) { + viewPlayer = static_cast( gameLocal.entities[ player->spectator ] ); + if ( viewPlayer == NULL ) { + return false; + } + } + + if ( !viewPlayer->GetRenderView() ) { + return false; + } + + SetShaderParms( viewPlayer->GetRenderView() ); + + // use the hud of the local player + if ( !hud ) { + hud = player->hud; + } + viewPlayer->playerView.RenderPlayerView( hud ); + + // allow force scoreboard to overwrite a fullscreen menu + if ( currentMenu ) { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( !bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 1, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = true; + } +#endif + if ( player->wantSpectate ) { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104249" ) ); + } else { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104250" ) ); + } + // if we died, isChatting is cleared, so re-set our chatting cvar + if ( gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->isChatting && !gameLocal.GetLocalPlayer()->pfl.dead ) { + cvarSystem->SetCVarBool( "ui_chat", true ); + cvarSystem->SetModifiedFlags( CVAR_USERINFO ); // force update + } + if ( currentMenu == 1 ) { + UpdateMainGui(); + mainGui->Redraw( gameLocal.time ); + } else if( currentMenu == 2 ) { + msgmodeGui->Redraw( gameLocal.time ); + } else if( currentMenu == 3 ) { + DrawStatSummary(); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + SetupBuyMenuItems(); + player->UpdateHudStats( buyMenu ); + buyMenu->HandleNamedEvent( "update_buymenu" ); + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->Redraw(gameLocal.time); +// RITUAL END + } + } else { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = false; + } +#endif + DrawScoreBoard( player ); + } + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud(); +// RAVEN END + return true; +} + +/* +================ +idMultiplayerGame::UpdateHud +================ +*/ +void idMultiplayerGame::UpdateHud( idUserInterface* _mphud ) { + idPlayer *localPlayer; + + if ( !_mphud ) { + return; + } + + // server demos don't have a true local player, but need one for hud updates + localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + +//RAVEN BEGIN +//asalmon: Turn on/off the lag icon so that clients know that they are losing connection + if ( networkSystem->ClientGetTimeSinceLastPacket() > 0 && ( networkSystem->ClientGetTimeSinceLastPacket() > cvarSystem->GetCVarInteger("net_clientServerTimeout")*500 ) ) { + _mphud->SetStateBool("IsLagged", true); + } + else{ + _mphud->SetStateBool("IsLagged", false); + } +//RAVEN END + + _mphud->SetStateInt( "marine_score", teamScore[ TEAM_MARINE ] ); + _mphud->SetStateInt( "strogg_score", teamScore[ TEAM_STROGG ] ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + + // Always show GameTime() for WARMUP and COUNTDOWN. + mpGameState_t state = gameState->GetMPGameState(); + _mphud->SetStateString( "timeleft", GameTime() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + /// Set "credits" gui element + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + int cash = 0; + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + + idPlayer* specPlayer = NULL; + if ( localPlayer->spectating ) + specPlayer = gameLocal.GetClientByNum( localPlayer->spectator ); + + if ( specPlayer ) + cash = (int)specPlayer->buyMenuCash; + else + cash = (int)localPlayer->buyMenuCash; + + if( localPlayer->CanBuy() ) { + _mphud->SetStateString("credits", va("%s %d %s", common->GetLocalizedString( "#str_122015" ), + cash, common->GetLocalizedString( "#str_122016" ))); + } + else { + _mphud->SetStateString("credits", va("%s %d", common->GetLocalizedString( "#str_122015" ), cash)); + } + } + else + { + _mphud->SetStateString("credits", ""); + } +// RITUAL END + + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + bool inCountdownState = (state == COUNTDOWN); + if( gameLocal.gameType == GAME_TOURNEY ) { + inNonTimedState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_SUDDEN_DEATH); + inCountdownState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_WARMUP); + } + _mphud->SetStateBool( "infinity", ( !timeLimit && !inCountdownState ) || inNonTimedState ); + + if( gameLocal.gameType == GAME_DM ) { + if( rankedPlayers.Num() ) { + _mphud->SetStateString( "player1_name", rankedPlayers[ 0 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player1_score", va( "%d", GetScore( rankedPlayers[ 0 ].First() ) ) ); + _mphud->SetStateString( "player1_rank", "1." ); + + // if we're in the lead or spectating, show the person in 2nd + if( ( (rankedPlayers[ 0 ].First() == localPlayer) || (localPlayer->spectating) ) && rankedPlayers.Num() > 1 ) { + _mphud->SetStateString( "player2_name", rankedPlayers[ 1 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( rankedPlayers[ 1 ].First() ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", rankedPlayers[ 1 ].First()->GetRank() + 1 ) ); + } else if( rankedPlayers[ 0 ].First() != localPlayer && !localPlayer->spectating ) { + // otherwise, show our score + _mphud->SetStateString( "player2_name", localPlayer->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( localPlayer ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", localPlayer->GetRank() + 1 ) ); + } else { + // no person to place in 2nd + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } else { + _mphud->SetStateString( "player1_name", "" ); + _mphud->SetStateString( "player1_score", "" ); + _mphud->SetStateString( "player1_rank", "" ); + + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } + + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DEADZONE ) { + + static int lastMarineScore = teamScore[ TEAM_MARINE ]; + static int lastStroggScore = teamScore[ TEAM_STROGG ]; + int marineScore = teamScore[ TEAM_MARINE ]; + int stroggScore = teamScore[ TEAM_STROGG ]; + const float asymptoticAverageWeight = 0.95f; + + /// Check if Marines have scored since last frame + if( marineScore != lastMarineScore ) + { + /// Pulse the bar's color + marineScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_MARINE ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + marineScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Check if Strogg have scored since last frame + if( stroggScore != lastStroggScore ) + { + /// Pulse the bar's color + stroggScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_STROGG ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + stroggScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Set "gameStatus" gui element + _mphud->SetStateString("gameStatus", "" ); + + _mphud->SetStateFloat( "marine_pulse_amount", marineScoreBarPulseAmount ); + _mphud->SetStateFloat( "strogg_pulse_amount", stroggScoreBarPulseAmount ); + + lastMarineScore = teamScore[ TEAM_MARINE ]; + lastStroggScore = teamScore[ TEAM_STROGG ]; + } + // RITUAL END + + if( gameLocal.gameType == GAME_TOURNEY && localPlayer->GetArena() == MAX_ARENAS ) { + int numWaitingArenaPlayers = 0; + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() && rankedPlayers[ i ].First()->GetArena() == MAX_ARENAS ) { + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers++ ), rankedPlayers[ i ].First()->GetUserInfo()->GetString( "ui_name" ) ); + } + } + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers ), "" ); + _mphud->SetStateBool( "waitroom", true ); + _mphud->SetStateInt( "num_waitroom_players", numWaitingArenaPlayers ); + } else { + _mphud->SetStateBool( "waitroom", false ); + } + + idStr spectateText0; + idStr spectateText1; + idStr spectateText2; + + if( gameLocal.gameType == GAME_TOURNEY ) { + // line 1 - why we aren't playing + if( localPlayer->wantSpectate ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + } else { + rvTourneyArena& currentArena = ((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ); + if( gameState->GetMPGameState() == WARMUP ) { + // grab the reason we aren't playing yet + AllPlayersReady( &spectateText0 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText0 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + spectateText0 = common->GetLocalizedString( "#str_107687" ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ADVANCED ) { + spectateText0 = common->GetLocalizedString( "#str_107688" ); + } else if( ((rvTourneyGameState*)gameState)->HasBye( localPlayer ) ) { + spectateText0 = common->GetLocalizedString( "#str_107709" ); + } else if( currentArena.IsPlaying( localPlayer ) ) { + spectateText0 = va( "%s %d; %s", common->GetLocalizedString( "#str_107716" ), localPlayer->GetArena() + 1, ((rvTourneyGameState*)gameState)->GetRoundDescription() ); + } else if( localPlayer->spectating ) { + // this should only happen if the player was spectating at start of round, but then decides + // to join the tourney + spectateText0 = common->GetLocalizedString( "#str_107684" ); + } + } + + // line 2 - will or wont be seeded, how to cycle + // line 3 - how to enter waiting room + if( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN ) { + if( localPlayer->wantSpectate ) { + spectateText1 = common->GetLocalizedString( "#str_107685" ); + spectateText2 = common->GetLocalizedString( "#str_107695" ); + } else { + spectateText1 = common->GetLocalizedString( "#str_107684" ); + spectateText2 = common->GetLocalizedString( "#str_107694" ); + } + } else if( localPlayer->spectating ) { + if( localPlayer->GetArena() == MAX_ARENAS ) { + spectateText1 = common->GetLocalizedString( "#str_107686" ); + } else { + spectateText1 = va( common->GetLocalizedString( "#str_107670" ), common->KeysFromBinding( "_impulse14" ), common->KeysFromBinding( "_impulse15" ) ); + } + } + } else { + // non-tourney spectate text + if( localPlayer->spectating ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + + // spectating instructions + if( localPlayer->spectator != localPlayer->entityNumber ) { + //cycle & exit follow + spectateText1 = va( common->GetLocalizedString( "#str_107698" ), common->KeysFromBinding( "_attack" ), common->KeysFromBinding( "_moveup" ) ); + } else { + //start follow + spectateText1 = va( common->GetLocalizedString( "#str_108024" ), common->KeysFromBinding( "_attack" ) ); + } + + } + + if( gameState->GetMPGameState() == WARMUP ) { + AllPlayersReady( &spectateText1 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText1 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } + } + + _mphud->SetStateString( "spectatetext0", spectateText0 ); + _mphud->SetStateString( "spectatetext1", spectateText1 ); + _mphud->SetStateString( "spectatetext2", spectateText2 ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + _mphud->StateChanged( gameLocal.time ); + + statManager->UpdateInGameHud( _mphud, ( localPlayer->usercmd.buttons & BUTTON_INGAMESTATS ) != 0 ); + + //update awards + if ( gameLocal.isClient || gameLocal.isListenServer) { + statManager->CheckAwardQueue(); + } +} + +/* +================ +idMultiplayerGame::DrawScoreBoard +================ +*/ +void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) { + if ( player->scoreBoardOpen ) { + if ( !playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( true, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = true; + player->disableHud = true; + } + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameState)->UpdateTourneyBrackets(); + } + UpdateScoreboard( scoreBoard ); + } else { + if ( playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( false, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = false; + player->disableHud = false; + } + } +} + +/* +=============== +idMultiplayerGame::AddChatLine +=============== +*/ +void idMultiplayerGame::AddChatLine( const char *fmt, ... ) { + idStr temp; + va_list argptr; + +// mekberg: chat changes. + wrapInfo_t wrapInfo; + idStr wrap1; + idStr wrap2; + + va_start( argptr, fmt ); + vsprintf( temp, fmt, argptr ); + va_end( argptr ); + + temp.StripTrailingOnce("\n"); + + // this isn't a good way to color informational lines.... + if( temp.Find( ":" ) > 0 && temp.Find( ":" ) < temp.Length() - 1 ) { + gameLocal.Printf( "%s^0^2%s\n", temp.Left( temp.Find( ":" ) + 1 ).c_str(), temp.Right( temp.Length() - temp.Find( ":" ) - 1).c_str() ); + } else { + gameLocal.Printf( "%s\n", temp.c_str() ); + } + + // bdube: new chat interraction with hud + if ( gameLocal.GetLocalPlayer() != NULL && gameLocal.GetLocalPlayer()->mphud ) { + wrap1 = temp; + wrap2 = temp; + do { + memset( &wrapInfo, -1, sizeof ( wrapInfo_t ) ); + gameLocal.GetLocalPlayer( )->mphud->GetMaxTextIndex( "history1", wrap1.c_str( ), wrapInfo ); + + // If we have a whitespace near the end. Otherwise the user could enter a giant word. + if ( wrapInfo.lastWhitespace != -1 && float( wrapInfo.lastWhitespace ) / float( wrapInfo.maxIndex ) > .75 ) { + wrap2 = wrap1.Left( wrapInfo.lastWhitespace++ ); + + // Just text wrap, no word wrap. + } else if ( wrapInfo.maxIndex != -1 ) { + wrap2 = wrap1.Left( wrapInfo.maxIndex ); + + // We fit in less than a line. + } else { + wrap2 = wrap1; + } + + // Recalc the base string. + wrap1 = wrap1.Right( wrap1.Length( ) - wrap2.Length( ) ); + + // Push to gui. + gameLocal.GetLocalPlayer( )->mphud->SetStateString( "chattext", wrap2.c_str( ) ); + gameLocal.GetLocalPlayer( )->mphud->HandleNamedEvent( "addchatline" ); + } while ( wrapInfo.maxIndex != -1 ); + } + + if( chatHistory.Length() + temp.Length() > CHAT_HISTORY_SIZE ) { + int removeLength = chatHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + chatHistory.Empty(); + } else { + while( (chatHistory.Length() - removeLength) + temp.Length() > CHAT_HISTORY_SIZE ) { + removeLength = chatHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + chatHistory.Empty(); + break; + } + } + } + chatHistory = chatHistory.Right( chatHistory.Length() - removeLength ); + } + + chatHistory.Append( temp ); + chatHistory.Append( '\n' ); + + if( mainGui ) { + mainGui->SetStateString( "chat", chatHistory.c_str() ); + } + if( statSummary ) { + statSummary->SetStateString( "chat", chatHistory.c_str() ); + } + + // play chat sound + char* chatSound = "snd_chat"; + if( gameLocal.GetLocalPlayer() ) { + // not a great way to detect teams + if( gameLocal.IsTeamGame() ) { + int i = temp.Find( gameLocal.GetLocalPlayer()->team ? "Strogg" : "Marine", false ); + int firstColon = temp.Find( ":" ); + if( firstColon >= 0 && i < firstColon && i >= 1 && temp[ i - 6 ] == '(' ) { + chatSound = "snd_teamchat"; + } + } + + gameLocal.GetLocalPlayer()->StartSound( chatSound, SND_CHANNEL_ANY, 0, false, NULL ); + } + +} + +void idMultiplayerGame::DrawStatSummary( void ) { + if ( !statSummary->GetStateFloat( "ready" ) ) { + statSummary->SetStateFloat( "ready", 1 ); + statSummary->HandleNamedEvent( "chatFocus" ); + statSummary->StateChanged( gameLocal.time ); + } + statSummary->Redraw( gameLocal.time ); +} + +void idMultiplayerGame::ShowStatSummary( void ) { + if ( !gameLocal.GetLocalPlayer() ) { + assert( false ); + return; + } + DisableMenu( ); + nextMenu = 3; + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.GetLocalPlayer()->GUIMainNotice( "" ); + gameLocal.GetLocalPlayer()->GUIFragNotice( "" ); +} + +/* +================ +idMultiplayerGame::WriteToSnapshot +================ +*/ +void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + int value; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + assert( MAX_CLIENTS % 8 == 0 ); +// RITUAL BEGIN - DeadZone Messages + msg.WriteBits(isBuyingAllowedRightNow, 1); + msg.WriteShort(powerupCount); + msg.WriteFloat( marineScoreBarPulseAmount ); + msg.WriteFloat( stroggScoreBarPulseAmount ); +// RITUAL END + + +// RAVEN BEGIN +// ddynerman: CTF scoring +// FIXME - not in the snapshot + for( i = 0; i < TEAM_MAX; i++ ) { + msg.WriteShort( teamScore[i] ); + msg.WriteLong( teamDeadZoneScore[i] ); + } +// RAVEN END + + // write ingame bits first, then we only sync down for ingame clients + // do a single write, this doesn't change often it's best to deltify in a single shot + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ingame[ i / 8 ] |= 1 << ( i % 8 ); + } else { + ingame[ i / 8 ] &= ~( 1 << ( i % 8 ) ); + } + } + msg.WriteData( ingame, MAX_CLIENTS / 8 ); + + // those rarely change as well and will deltify away nicely + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + // clamp all values to min/max possible value that we can send over + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + msg.WriteLong( playerState[i].deadZoneScore ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins ); + msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS ); + // only transmit instance info in tourney + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !ent ) { + msg.WriteBits( 0, 1 ); + } else { + msg.WriteBits( 1, 1 ); + value = idMath::ClampInt( 0, MAX_INSTANCES, ent->GetInstance() ); + msg.WriteBits( value, ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( ((idPlayer*)ent)->GetTourneyStatus(), ASYNC_PLAYER_TOURNEY_STATUS_BITS ); + } + } + } + } + + // those change all the time, keep them in a single pack + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping ); + msg.WriteBits( value, ASYNC_PLAYER_PING_BITS ); + } + } +} + +/* +================ +idMultiplayerGame::ReadFromSnapshot +================ +*/ +void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, newInstance; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( proto69 ) { + isBuyingAllowedRightNow = false; + powerupCount = 0; + marineScoreBarPulseAmount = 0; + stroggScoreBarPulseAmount = 0; + } else { +// RITUAL BEGIN - DeadZone & buying Messages + isBuyingAllowedRightNow = msg.ReadBits(1); + powerupCount = msg.ReadShort(); + // TTimo: NOTE: sounds excessive to be transmitting floats for that + marineScoreBarPulseAmount = msg.ReadFloat(); + stroggScoreBarPulseAmount = msg.ReadFloat(); +// RITUAL END + } + + // CTF/TDM scoring + for( i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = msg.ReadShort( ); + if ( proto69 ) { + teamDeadZoneScore[ i ] = 0; + } else { + teamDeadZoneScore[ i ] = msg.ReadLong( ); + } + } + + msg.ReadData( ingame, MAX_CLIENTS / 8 ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( ingame[ i / 8 ] & ( 1 << ( i % 8 ) ) ) { + playerState[i].ingame = true; + } else { + playerState[i].ingame = false; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + playerState[ i ].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + if ( proto69 ) { + playerState[ i ].deadZoneScore = 0; + } else { + playerState[ i ].deadZoneScore = msg.ReadLong(); + } + playerState[ i ].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if( msg.ReadBits( 1 ) ) { + newInstance = msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ); + if( newInstance != ent->GetInstance() ) { + ent->SetInstance( newInstance ); + if( gameLocal.GetLocalPlayer() && i != gameLocal.localClientNum ) { + if( ent->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + ((idPlayer*)ent)->ClientInstanceJoin(); + } else { + ((idPlayer*)ent)->ClientInstanceLeave(); + } + } + } + ((idPlayer*)ent)->SetTourneyStatus( (playerTourneyStatus_t)msg.ReadBits( ASYNC_PLAYER_TOURNEY_STATUS_BITS ) ); + } + } + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + playerState[ i ].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS ); + } + } +} + +// RAVEN BEGIN +// bdube: global item sounds +/* +================ +idMultiplayerGame::PlayGlobalItemAcquireSound +================ +*/ +void idMultiplayerGame::PlayGlobalItemAcquireSound( int defIndex ) { + const idDeclEntityDef* def; + def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, defIndex, false ) ); + if ( !def ) { + gameLocal.Warning ( "NET: invalid entity def index (%d) for global item acquire sound", defIndex ); + return; + } + + if( !gameLocal.GetLocalPlayer() || !gameLocal.currentThinkingEntity || gameLocal.GetLocalPlayer()->GetInstance() == gameLocal.currentThinkingEntity->GetInstance() ) { + soundSystem->PlayShaderDirectly ( SOUNDWORLD_GAME, def->dict.GetString ( "snd_acquire" ) ); + } + + if ( gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND ); + outMsg.WriteBits( defIndex, gameLocal.entityDefBits ); + gameLocal.ServerSendInstanceReliableMessage( gameLocal.currentThinkingEntity, -1, outMsg ); + } +} +// RAVEN END + +/* +================ +idMultiplayerGame::PrintMessageEvent +================ +*/ +void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( to == -1 || ( p && to == p->entityNumber ) ) { + switch ( evt ) { + case MSG_SUICIDE: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_KILLED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_KILLEDTEAM: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_TELEFRAGGED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_DIED: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_VOTE: + AddChatLine( common->GetLocalizedString( "#str_104288" ) ); + break; + case MSG_SUDDENDEATH: + AddChatLine( common->GetLocalizedString( "#str_104287" ) ); + break; + case MSG_FORCEREADY: + AddChatLine( common->GetLocalizedString( "#str_104286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true; + } + break; + case MSG_JOINEDSPEC: + AddChatLine( common->GetLocalizedString( "#str_104285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_TIMELIMIT: + AddChatLine( common->GetLocalizedString( "#str_104284" ) ); + break; + case MSG_FRAGLIMIT: + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { + // RITUAL END + // RAVEN BEGIN + // rhummer: localized "Strogg" and "Marine" + AddChatLine( common->GetLocalizedString( "#str_107665" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + } else { + AddChatLine( common->GetLocalizedString( "#str_104281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + } + break; + case MSG_CAPTURELIMIT: + // RAVEN BEGIN + // rhummer: localized "%s team hit the capture limit." and "Strogg and "Marine" + AddChatLine( common->GetLocalizedString( "#str_108027" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + break; + case MSG_HOLYSHIT: + AddChatLine( common->GetLocalizedString( "#str_106732" ) ); + break; + default: + gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt ); + return; + } + } + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB ); + outMsg.WriteByte( evt ); + outMsg.WriteByte( parm1 ); + outMsg.WriteByte( parm2 ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::PrintMessage +================ +*/ +void idMultiplayerGame::PrintMessage( int to, const char* msg ) { + if( idStr::Length( msg ) >= MAX_PRINT_LEN ) { + common->Warning( "idMultiplayerGame::PrintMessage() - Not transmitting message of length %d", idStr::Length( msg ) ); + return; + } + + AddChatLine( msg ); + + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PRINT ); + outMsg.WriteString( msg ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::CheckSpawns +================ +*/ +void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) { + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast(ent); + + // once we hit sudden death, nobody respawns till game has ended + // no respawns in tourney mode, the tourney manager manually handles spawns + if ( (WantRespawn( p ) || p == spectator) ) { + if ( gameState->GetMPGameState() == SUDDENDEATH && gameLocal.gameType != GAME_TOURNEY ) { + // respawn rules while sudden death are different + // sudden death may trigger while a player is dead, so there are still cases where we need to respawn + // don't do any respawns while we are in end game delay though + if ( gameLocal.IsTeamGame() || p->IsLeader() ) { + //everyone respawns in team games, only fragleaders respawn in DM + p->ServerSpectate( false ); + } else {//if ( !p->IsLeader() ) { + // sudden death is rolling, this player is not a leader, have him spectate + p->ServerSpectate( true ); + CheckAbortGame(); + } + } else { + if ( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType != GAME_TOURNEY ) { + // wait for team to be set before spawning in + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( false ); + } + + } else { + if( p->GetArena() >= 0 && p->GetArena() < MAX_ARENAS ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameState)->GetArena( p->GetArena() ); + if( ( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) && ( p == arena.GetPlayers()[ 0 ] || p == arena.GetPlayers()[ 1 ] ) ) { + // only allow respawn if the arena we're in is active + // and we're one of the assigned players (we're not just spectating it) + p->ServerSpectate( false ); + } + } else { + // always allow respawn in the waiting room + assert( p->GetArena() == MAX_ARENAS ); + p->ServerSpectate( false ); + } + } + } + } + } else if ( p->wantSpectate && !p->spectating ) { + playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets + p->ServerSpectate( true ); + CheckAbortGame(); + } + } +} + +void idMultiplayerGame::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 && gameRenderWorld ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + +void idMultiplayerGame::UpdateLight ( int lightID, idPlayer *player ) { + lights[ lightID ].origin = player->GetPhysics()->GetOrigin() + idVec3( 0, 0, 20 ); + + if ( lightHandles[ lightID ] == -1 ) { + lightHandles[ lightID ] = gameRenderWorld->AddLightDef ( &lights[ lightID ] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[ lightID ], &lights[ lightID ] ); + } +} + +void idMultiplayerGame::CheckSpecialLights( void ) { + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idPlayer *marineFlagCarrier = NULL; + idPlayer *stroggFlagCarrier = NULL; + idPlayer *quadDamageCarrier = NULL; + idPlayer *regenerationCarrier = NULL; + idPlayer *hasteCarrier = NULL; + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast( ent ); + + if( gameLocal.GetLocalPlayer() && p->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + + if ( p->PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + marineFlagCarrier = p; + } + else if ( p->PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + stroggFlagCarrier = p; + } + else if( p->PowerUpActive( POWERUP_QUADDAMAGE ) || p->PowerUpActive( POWERUP_TEAM_DAMAGE_MOD )) { + quadDamageCarrier = p; + } + else if( p->PowerUpActive( POWERUP_REGENERATION ) ) { + regenerationCarrier = p; + } + else if( p->PowerUpActive( POWERUP_HASTE ) ) { + hasteCarrier = p; + } + } + + if ( marineFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_MARINE, marineFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_MARINE ); + } + + if ( stroggFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_STROGG, stroggFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_STROGG ); + } + + if ( quadDamageCarrier ) { + UpdateLight( MPLIGHT_QUAD, quadDamageCarrier ); + } else { + FreeLight( MPLIGHT_QUAD ); + } + + if ( regenerationCarrier ) { + UpdateLight( MPLIGHT_REGEN, regenerationCarrier ); + } else { + FreeLight( MPLIGHT_REGEN ); + } + + if ( hasteCarrier ) { + UpdateLight( MPLIGHT_HASTE, hasteCarrier ); + } else { + FreeLight( MPLIGHT_HASTE ); + } +} + +/* +================ +idMultiplayerGame::ForceReady +================ +*/ +void idMultiplayerGame::ForceReady( ) { + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + idPlayer *p = static_cast( ent ); + if ( !p->IsReady() ) { + PrintMessageEvent( -1, MSG_FORCEREADY, i ); + p->forcedReady = true; + } + } +} + +/* +================ +idMultiplayerGame::ForceReady_f +================ +*/ +void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + gameLocal.Printf( "forceReady: multiplayer server only\n" ); + return; + } + gameLocal.mpGame.ForceReady(); +} + +/* +================ +idMultiplayerGame::DropWeapon +================ +*/ +void idMultiplayerGame::DropWeapon( int clientNum ) { + assert( !gameLocal.isClient ); + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } +// RAVEN BEGIN +// bdube: removed parameter + static_cast< idPlayer* >( ent )->DropWeapon( ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::DropWeapon_f +================ +*/ +void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "clientDropWeapon: only valid in multiplayer\n" ); + return; + } + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::MessageMode_f +================ +*/ +void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) { + gameLocal.mpGame.MessageMode( args ); +} + +/* +================ +idMultiplayerGame::MessageMode +================ +*/ +void idMultiplayerGame::MessageMode( const idCmdArgs &args ) { + const char *mode; + int imode; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientMessageMode: only valid in multiplayer\n" ); + return; + } + if ( !mainGui ) { + common->Printf( "no local client\n" ); + return; + } + mode = args.Argv( 1 ); + if ( !mode[ 0 ] || !gameLocal.IsTeamGame() ) { + imode = 0; + } else { + imode = atoi( mode ); + } + msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" ); + msgmodeGui->SetStateString( "chattext", "" ); + nextMenu = 2; + // let the session know that we want our ingame main menu opened + gameLocal.sessionCommand = "game_startmenu"; +} + +/* +================ +idMultiplayerGame::Vote_f +================ +*/ +void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// shouchard: implemented for testing + if ( args.Argc() < 2 ) { + common->Printf( common->GetLocalizedString( "#str_104418" ) ); + return; + } + + const char *szArg1 = args.Argv(1); + bool voteValue = false; + if ( 0 == idStr::Icmp( szArg1, "yes" ) ) { + voteValue = true; + } + + gameLocal.mpGame.CastVote( gameLocal.localClientNum, voteValue ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::CallVote_f +moved this over the use the packed voting +still only does one vote though, can easily be extended to do more +================ +*/ +void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { + const char *szArg1 = args.Argv(1); + const char *szArg2 = args.Argv(2); + if ( '\0' == *szArg1 ) { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + if ( 0 == idStr::Icmp( szArg1, "restart" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } else if ( 0 == idStr::Icmp( szArg1, "timelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104406" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + voteData.m_timeLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "fraglimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104407" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + voteData.m_fragLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "gametype" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104408" ) ); + common->Printf( common->GetLocalizedString( "#str_104409" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + voteData.m_gameType = gameLocal.mpGame.GameTypeToVote( szArg2 ); + } + else if ( 0 == idStr::Icmp( szArg1, "kick" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104412" ) ); + return; + } + voteData.m_kick = gameLocal.mpGame.GetClientNumFromPlayerName( szArg2 ); + if ( voteData.m_kick >= 0 ) { + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + } else if ( 0 == idStr::Icmp( szArg1, "map" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104413" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_MAP; + voteData.m_map = szArg2; + } else if ( 0 == idStr::Icmp( szArg1, "buying" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122012" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + voteData.m_buying = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "capturelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104415" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + voteData.m_captureLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "autobalance" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104416" ) ); + } + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + voteData.m_teamBalance = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "controlTime" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122002" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + } + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + voteData.m_controlTime = atoi(szArg2 ); + } else { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + if ( voteData.m_fieldFlags != 0 ) { + gameLocal.mpGame.ClientCallPackedVote( voteData ); + } +} + +// RAVEN BEGIN +// shouchard: added voice mute and unmute console commands; sans XBOX to not step on their live voice stuff +#ifndef _XBOX +/* +================ +idMultiplayerGame::VoiceMute_f +================ +*/ +void idMultiplayerGame::VoiceMute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoicemute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), true ); +} + +/* +================ +idMultiplayerGame::VoiceUnmute_f +================ +*/ +void idMultiplayerGame::VoiceUnmute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoiceunmute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), false ); +} + +// RAVEN END +#endif // _XBOX + +// RAVEN BEGIN +/* +================ +idMultiplayerGame::ForceTeamChange_f +================ +*/ +void idMultiplayerGame::ForceTeamChange_f( const idCmdArgs &args) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Forces player to change teams. Usage: ForceTeamChange \n" ); + return; + } + + idStr clientId; + int clientNum; + + clientId = args.Argv( 1 ); + if ( !clientId.IsNumeric() ) { + common->Printf( "usage: ForceTeamChange \n" ); + return; + } + + clientNum = atoi( clientId ); + + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + } + +} + + +/* +================ +idMultiplayerGame::RemoveClientFromBanList_f +================ +*/ +void idMultiplayerGame::RemoveClientFromBanList_f( const idCmdArgs& args ) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Remove player from banlist. Usage: RemoveClientFromBanList \n" ); + return; + } + + idStr clientId; + clientId = args.Argv( 1 ); + int clientNum; + + if ( !clientId.IsNumeric() ) { + common->Printf( "Usage: RemoveClientFromBanList \n" ); + return; + } + + clientNum = atoi( clientId ); + + const char *clientGuid = networkSystem->GetClientGUID( clientNum ); // gameLocal.GetGuidByClientNum( clientNum ); + + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + // remove from the ban list + gameLocal.RemoveGuidFromBanList( clientGuid ); + } + +} + +/* +================ +idMultiplayerGame::ProcessRconReturn +================ +*/ +void idMultiplayerGame::ProcessRconReturn( bool success ) { + + if( success ) { + mainGui->HandleNamedEvent("adminPasswordSuccess"); + } else { + mainGui->HandleNamedEvent("adminPasswordFail"); + } + + +} + + +// RAVEN END + +/* +================ +idMultiplayerGame::ServerStartVote +================ +*/ +void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) { + int i; + + assert( vote == VOTE_NONE ); + + // setup + yesVotes = 1; + noVotes = 0; + vote = voteIndex; + voteValue = value; + voteTimeOut = gameLocal.time + 20000; + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } +} + +/* +================ +idMultiplayerGame::ClientStartVote +================ +*/ +void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteString( _voteString ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + voteString = _voteString; + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); +// RAVEN BEGIN +// shouchard: better info when a vote called in the chat buffer + AddChatLine( voteString ); // TODO: will push this into a UI field later +// shouchard: display the vote called text on the hud + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } +// RAVEN END + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ClientUpdateVote +================ +*/ +void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + const char * localizedString = 0; + idPlayer* player = gameLocal.GetLocalPlayer( ); + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE ); + outMsg.WriteByte( status ); + outMsg.WriteByte( yesCount ); + outMsg.WriteByte( noCount ); +// RAVEN BEGIN +// shouchard: multifield vote support + if ( VOTE_MULTIFIELD != vote ) { + outMsg.WriteByte( 0 ); + } else { + outMsg.WriteByte( 1 ); + outMsg.WriteShort( voteData.m_fieldFlags ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + outMsg.WriteString( voteData.m_map.c_str() ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } else { + currentVoteData = voteData; + } +// RAVEN END + + if ( vote == VOTE_NONE ) { + // clients coming in late don't get the vote start and are not allowed to vote + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + } + return; + } + + switch ( status ) { + case VOTE_FAILED: + localizedString = common->GetLocalizedString( "#str_104278" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_FAILED, gameLocal.time ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_PASSED: + localizedString = common->GetLocalizedString( "#str_104277" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_PASSED, gameLocal.time ); + break; + case VOTE_RESET: + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_ABORTED: + localizedString = common->GetLocalizedString( "#str_104276" ); + AddChatLine( localizedString ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_UPDATE: + if ( player && player->mphud && voted ) { + player->mphud->SetStateString( "voteNoticeText", va("^:%s\n%s: %d %s: %d", + common->GetLocalizedString( "#str_107724" ), + common->GetLocalizedString( "#str_107703" ), + yesCount, + common->GetLocalizedString( "#str_107704" ), + noCount ) ); + } + + if ( mainGui ) { + mainGui->SetStateInt( "playerVoted", voted ); + } + break; + default: + break; + } + + if ( gameLocal.isClient ) { + yesVotes = yesCount; + noVotes = noCount; + } + +// RAVEN BEGIN +// shouchard: remove vote notification + if ( VOTE_FAILED == status || VOTE_PASSED == status || VOTE_RESET == status ) { + ClearVote(); + } + + if ( mainGui ) { + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), (int)yesVotes, (int)noVotes ) ); + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::ClientCallVote +================ +*/ +void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE ); + outMsg.WriteByte( voteIndex ); + outMsg.WriteString( voteValue ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::CastVote +================ +*/ +void idMultiplayerGame::CastVote( int clientNum, bool castVote ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } + + if ( gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE ); + outMsg.WriteByte( castVote ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // sanity + if ( vote == VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104275" ); + common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum ); + return; + } + if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104274" ); + common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote ); + return; + } + + if ( castVote ) { + playerState[ clientNum ].vote = PLAYER_VOTE_YES; + yesVotes++; + } else { + playerState[ clientNum ].vote = PLAYER_VOTE_NO; + noVotes++; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerCallVote +================ +*/ +void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) { + vote_flags_t voteIndex; + int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex, vote_buying; //, vote_kickIndex; +// RAVEN BEGIN +// shouchard: added capture limit and autobalance + int vote_captureLimit; + bool vote_autobalance; +// RAVEN END + int vote_controlTime; + char value[ MAX_STRING_CHARS ]; + + assert( clientNum != -1 ); + assert( !gameLocal.isClient ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + voteIndex = (vote_flags_t)msg.ReadByte( ); + msg.ReadString( value, sizeof( value ) ); + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + switch ( voteIndex ) { + case VOTE_RESTART: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104271" ) ); + break; + } + case VOTE_NEXTMAP: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104272" ) ); + break; + } + case VOTE_TIMELIMIT: { + vote_timeLimit = strtol( value, NULL, 10 ); + if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum ); + return; + } + if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104268" ), vote_timeLimit ) ); + break; + } + case VOTE_FRAGLIMIT: { + vote_fragLimit = strtol( value, NULL, 10 ); + if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum ); + return; + } + if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104303" ), common->GetLocalizedString( "#str_104265" ), vote_fragLimit ) ); + break; + } + case VOTE_GAMETYPE: { +// RAVEN BEGIN +// shouchard: removed magic numbers & added CTF type + vote_gameTypeIndex = strtol( value, NULL, 10 ); + assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex < VOTE_GAMETYPE_COUNT ); + switch ( vote_gameTypeIndex ) { + case VOTE_GAMETYPE_DM: + strcpy( value, "DM" ); + break; + case VOTE_GAMETYPE_TOURNEY: + strcpy( value, "Tourney" ); + break; + case VOTE_GAMETYPE_TDM: + strcpy( value, "Team DM" ); + break; + case VOTE_GAMETYPE_CTF: + strcpy( value, "CTF" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + strcpy( value, "Arena CTF" ); + break; +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + case VOTE_GAMETYPE_DEADZONE: + strcpy( value, "DeadZone" ); + break; +// RITUAL END + + } + if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104258" ), value ) ); + break; + } + case VOTE_KICK: { + vote_clientNum = strtol( value, NULL, 10 ); + if ( vote_clientNum == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) ); + break; + } + case VOTE_MAP: { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) { + + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value ); + return; + } + int num = fileSystem->GetNumMaps(); + int i; + const idDict *dict = NULL; + bool haveMap = false; + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if( !dict ) { + gameLocal.Warning( "idMultiplayerGame::ServerCallVote() - bad map decl index on vote\n" ); + break; + } + if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) { + haveMap = true; + break; + } + } + if ( !haveMap ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104296", value ); + common->Printf( "client %d: map not found: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104256" ), dict ? dict->GetString( "name" ) : value ) ); +#endif + break; + } + case VOTE_BUYING: { + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) + gameLocal.Error("MIN_PLAYERS vote in a pre-1.2 recorded demo is not supported."); + vote_buying = strtol( value, NULL, 10 ); + if ( vote_buying == gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + common->DPrintf( "client %d: already at the voted buying mode\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_122014" ), vote_buying ) ); + break; + } +// RAVEN BEGIN +// shouchard: added capture limit, round limit, and autobalance + case VOTE_CAPTURELIMIT: { + vote_captureLimit = strtol( value, NULL, 10 ); + if ( vote_captureLimit == gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + common->DPrintf( "client %d: already at the voted Capture Limit\n", clientNum ); + return; + } + if ( vote_captureLimit < si_captureLimit.GetMinValue() || vote_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_captureLimit" ); + break; + } + // round limit is for tourneys + case VOTE_ROUNDLIMIT: { + // need a CVar or something to change here + break; + } + case VOTE_AUTOBALANCE: { + vote_autobalance = (0 != strtol( value, NULL, 10 ) ); + if ( vote_autobalance == gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + common->DPrintf( "client %d: already at the voted balance teams\n", clientNum ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_autobalance" ); + break; + } +// RAVEN END + case VOTE_CONTROLTIME: { + vote_controlTime = strtol( value, NULL, 10 ); + if ( vote_controlTime == gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + common->DPrintf( "client %d: already at the voted Control Time\n", clientNum ); + return; + } + if ( vote_controlTime < si_controlTime.GetMinValue() || vote_controlTime > si_controlTime.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122018" ); + common->DPrintf( "client %d: controlTime value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_controlTime" ); + break; + } + default: { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104297", va( "%d", ( int )voteIndex ) ); + common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex ); + } + } +} + + +/* +================ +idMultiplayerGame::DisconnectClient +================ +*/ +void idMultiplayerGame::DisconnectClient( int clientNum ) { + // gameLocal.entities[ clientNum ] could be null if server is shutting down + if( gameLocal.entities[ clientNum ] ) { + // only kill non-spectators + if( !((idPlayer*)gameLocal.entities[ clientNum ])->spectating ) { + static_cast( gameLocal.entities[ clientNum ] )->Kill( true, true ); + } + statManager->ClientDisconnect( clientNum ); + } + + delete gameLocal.entities[ clientNum ]; + + UpdatePlayerRanks(); + CheckAbortGame(); + + privatePlayers &= ~( 1 << clientNum ); + + // update serverinfo + UpdatePrivatePlayerCount(); +} + +/* +================ +idMultiplayerGame::CheckAbortGame +================ +*/ +void idMultiplayerGame::CheckAbortGame( void ) { + // only checks for aborts -> game review below + if ( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != GAMEON && gameState->GetMPGameState() != SUDDENDEATH ) { + return; + } + + // in tourney, if we don't have enough clients to play we need to cycle back to + // warmup to re-seed + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( !EnoughClientsToPlay() ) { + gameState->NewState( WARMUP ); + } + } else { + if ( !EnoughClientsToPlay() && TimeLimitHit() ) { + gameState->NewState( GAMEREVIEW ); + } + } +} + +/* +================ +idMultiplayerGame::WantKilled +================ +*/ +void idMultiplayerGame::WantKilled( int clientNum ) { + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Kill( false, false ); + } +} + +/* +================ +idMultiplayerGame::ClearVote +================ +*/ +void idMultiplayerGame::ClearVote( int clientNum ) { + int start = 0; + int end = MAX_CLIENTS; + + if( clientNum != -1 ) { + start = clientNum; + end = clientNum + 1; + } + + for ( int i = start; i < end; i++ ) { + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + mainGui->StateChanged( gameLocal.time ); + } +} +/* +================ +idMultiplayerGame::MapRestart +================ +*/ +void idMultiplayerGame::MapRestart( void ) { + int clientNum; + // jshepard: clean up votes + ClearVote(); + + ClearAnnouncerSounds(); + + assert( !gameLocal.isClient ); + if ( gameLocal.GameState() != GAMESTATE_SHUTDOWN && gameState->GetMPGameState() != WARMUP ) { + gameState->NewState( WARMUP ); + // force an immediate state detection/update, otherwise if we update our state this + // same frame we'll miss transitions + gameState->SendState(); + + gameState->SetNextMPGameState( INACTIVE ); + gameState->SetNextMPGameStateTime( 0 ); + + } + + // mekberg: moved this before the updateUI just in case these values weren't reset. + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } + + // mekberg: Re-wrote this loop to always updateUI. Previously the player would be + // on a team but the UI wouldn't know about it + // shouchard: balance teams extended to CTF + for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) { + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + // mekberg: clear wins only on map restart + idPlayer *player = static_cast( gameLocal.entities[ clientNum ] ); + SetPlayerWin( player, 0 ); + + /*if( clientNum == gameLocal.localClientNum ) { + if ( player->alreadyDidTeamAnnouncerSound ) { + player->alreadyDidTeamAnnouncerSound = false; + } else { + if ( gameLocal.IsTeamGame() ) { + player->alreadyDidTeamAnnouncerSound = true; + if( player->team == TEAM_STROGG ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( player->team == TEAM_MARINE ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + }*/ + // let the player rejoin the team through normal channels + //player->ServerSpectate( true ); + //player->team = -1; + //player->latchedTeam = -1; + + // shouchard: BalanceTDM->BalanceTeam + //if ( gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // player->BalanceTeam(); + //} + + // core is in charge of syncing down userinfo changes + // it will also call back game through SetUserInfo with the current info for update + /*cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );*/ + } + } +} + +/* +================ +idMultiplayerGame::SwitchToTeam +================ +*/ +void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) { + assert( gameLocal.IsTeamGame() ); + + assert( oldteam != newteam ); + assert( !gameLocal.isClient ); + + if ( !gameLocal.isClient && newteam >= 0 ) { + // clients might not have userinfo of joining client at this point, so + // send down the player's name + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p->wantSpectate ) { + PrintMessage( -1, va( common->GetLocalizedString( "#str_104280" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ), newteam ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ) ); + } + } + + if ( oldteam != -1 ) { + // kill and respawn + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( p->IsInTeleport() ) { + p->ServerSendInstanceEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 ); + p->SetPrivateCameraView( NULL ); + } +//RITUAL BEGIN + p->inventory.carryOverWeapons = 0; + p->ResetCash(); +//RITUAL END + p->Kill( true, true ); + CheckAbortGame(); + } +} + +/* +================ +idMultiplayerGame::JoinTeam +================ +*/ +void idMultiplayerGame::JoinTeam( const char* team ) { + if( !idStr::Icmp( team, "auto" ) ) { + int teamCount[ TEAM_MAX ]; + idEntity *ent; + + memset( teamCount, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ ((idPlayer*)ent)->team ]++; + } + } + } + + int minCount = idMath::INT_MAX; + int minCountTeam = -1; + for( int i = 0; i < TEAM_MAX; i++ ) { + if( teamCount[ i ] < minCount ) { + minCount = teamCount[ i ]; + minCountTeam = i; + } + } + + if( minCountTeam >= 0 && minCountTeam < TEAM_MAX ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ minCountTeam ] ); + } else { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ gameLocal.random.RandomInt( TEAM_MAX - 1 ) ] ); + } + } else if( !idStr::Icmp( team, "spectator" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + if( !idStr::Icmp( team, teamNames[ i ] ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ i ] ); + break; + } + } + if( i >= TEAM_MAX ) { + gameLocal.Warning( "idMultiplayerGame::JoinTeam() - unknown team '%s'\n", team ); + } + } +} + +/* +================ +idMultiplayerGame::ProcessChatMessage +================ +*/ +void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + const char *suffix = NULL; + int send_to; // 0 - all, 1 - specs, 2 - team + int i; + idEntity *ent; + idPlayer *p; + idStr suffixed_name; + idStr prefixed_text; + + assert( !gameLocal.isClient ); + + if ( clientNum >= 0 ) { + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating && ( p->wantSpectate || gameLocal.gameType == GAME_TOURNEY ) ) { + suffix = "spectating"; + if ( team || ( !g_spectatorChat.GetBool() && ( gameState->GetMPGameState() == GAMEON || gameState->GetMPGameState() == SUDDENDEATH ) ) ) { + // to specs + send_to = 1; + } else { + // to all + send_to = 0; + } + } else if ( team ) { + suffix = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, p->team ? "Strogg^0" : "Marine^0" ); + // to team + send_to = 2; + } else { + if( gameLocal.gameType == GAME_TOURNEY ) { + suffix = va( "Arena %d", (p->GetArena() + 1) ); + } + // to all + send_to = 0; + } + } else { + p = NULL; + send_to = 0; + } + // put the message together + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + + if ( suffix ) { + suffixed_name = va( "^0%s^0 (%s)", name, suffix ); + } else { + suffixed_name = va( "^0%s^0", name ); + } + if( p && send_to == 2 ) { + prefixed_text = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, common->GetLocalizedString( text ) ); + } else { + prefixed_text = common->GetLocalizedString( text ); + } + + if( suffixed_name.Length() + prefixed_text.Length() >= 240 ) { + gameLocal.Warning( "idMultiplayerGame::ProcessChatMessage() - Chat line too long\n" ); + return; + } + + outMsg.WriteString( suffixed_name ); + outMsg.WriteString( prefixed_text ); + outMsg.WriteString( "" ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *to = static_cast< idPlayer * >( ent ); + switch( send_to ) { + case 0: + if ( !p || !to->IsPlayerMuted( p ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 1: + if ( !p || ( to->spectating && !to->IsPlayerMuted( p ) ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 2: + if ( !p || ( to->team == p->team && !to->IsPlayerMuted( p ) ) ) { + if ( !to->spectating ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + } + break; + } + } +} + +/* +================ +idMultiplayerGame::Precache +================ +*/ +void idMultiplayerGame::Precache( void ) { + int i; + + if ( !gameLocal.isMultiplayer ) { + return; + } + gameLocal.FindEntityDef( "player_marine", false ); + + // MP game sounds + for ( i = 0; i < AS_NUM_SOUNDS; i++ ) { + declManager->FindSound( announcerSoundDefs[ i ], false ); + } + + // MP guis. just make sure we hit all of them + i = 0; + while ( MPGuis[ i ] ) { + uiManager->FindGui( MPGuis[ i ], true ); + i++; + } +} + +/* +================ +idMultiplayerGame::ToggleSpectate +================ +*/ +void idMultiplayerGame::ToggleSpectate( void ) { + bool spectating; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( spectating ) { + // always allow toggling to play + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + } else { + // only allow toggling to spectate if spectators are enabled. + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_106747" ) ); + } + } +} + +/* +================ +idMultiplayerGame::ToggleReady +================ +*/ +void idMultiplayerGame::ToggleReady( void ) { + bool ready; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + if ( lastReadyToggleTime == -1 ) { + lastReadyToggleTime = gameLocal.time; + } else { + int currentTime = gameLocal.time; + if ( currentTime - lastReadyToggleTime < 500 ) { + return; + } else { + lastReadyToggleTime = currentTime; + } + } + + ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready ) { + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + } else { + cvarSystem->SetCVarString( "ui_ready", "Ready" ); + } +} + +/* +================ +idMultiplayerGame::ToggleTeam +================ +*/ +void idMultiplayerGame::ToggleTeam( void ) { + bool team; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + // RAVEN BEGIN + // ddynerman: new multiplayer teams + team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Marine" ) == 0 ); + if ( team ) { + cvarSystem->SetCVarString( "ui_team", "Strogg" ); + } else { + cvarSystem->SetCVarString( "ui_team", "Marine" ); + } + // RAVEN END +} + +/* +================ +idMultiplayerGame::ToggleUserInfo +================ +*/ +void idMultiplayerGame::ThrottleUserInfo( void ) { + int i; + + assert( gameLocal.localClientNum >= 0 ); + + i = 0; + while ( ThrottleVars[ i ] ) { + if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ), + cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) { + if ( gameLocal.realClientTime < switchThrottle[ i ] ) { + AddChatLine( common->GetLocalizedString( "#str_104299" ), common->GetLocalizedString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 ); + cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) ); + } else { + switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000; + } + } + i++; + } +} + +/* +================ +idMultiplayerGame::CanPlay +================ +*/ +bool idMultiplayerGame::CanPlay( idPlayer *p ) { + return !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::EnterGame +================ +*/ +void idMultiplayerGame::EnterGame( int clientNum ) { + assert( !gameLocal.isClient ); + + if ( !playerState[ clientNum ].ingame ) { + playerState[ clientNum ].ingame = true; + if ( gameLocal.isMultiplayer ) { + // can't use PrintMessageEvent as clients don't know the nickname yet + //gameLocal.ServerSendChatMessage( -1, common->GetLocalizedString( "#str_102047" ), va( common->GetLocalizedString( "#str_107177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + } + + // mark them as private and update si_numPrivatePlayers + for( int i = 0; i < privateClientIds.Num(); i++ ) { + int num = networkSystem->ServerGetClientNum( privateClientIds[ i ] ); + + // check for timed out clientids + if( num < 0 ) { + privateClientIds.RemoveIndex( i ); + i--; + continue; + } + + if( num == clientNum ) { + privatePlayers |= (1 << clientNum); + } + } + + // update serverinfo + UpdatePrivatePlayerCount(); + } +} + +/* +================ +idMultiplayerGame::WantRespawn +================ +*/ +bool idMultiplayerGame::WantRespawn( idPlayer *p ) { + return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, false ); +} + +/* +================ +idMultiplayerGame::UpdateMPSettingsModel +================ +*/ +void idMultiplayerGame::UpdateMPSettingsModel( idUserInterface* currentGui ) { + if ( !currentGui ) { + return; + } + + const char *model; + idPlayer *localP = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() && localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + cvarSystem->SetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ), model ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( "def_default_model" ); + cvarSystem->SetCVarString( "ui_model", model ); + } + } + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + currentGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + currentGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + currentGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + currentGui->SetStateBool( "need_update", true ); + } +} + +/* +================ +idMultiplayerGame::VoiceChatTeam +================ +*/ +void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, true ); +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) { + idBitMsg outMsg; + byte msgBuf[128]; + const char *voc; + const idDict *spawnArgs; + const idKeyValue *keyval; + int index; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientVoiceChat: only valid in multiplayer\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "clientVoiceChat: bad args\n" ); + return; + } + // throttle + if ( gameLocal.realClientTime < voiceChatThrottle ) { + return; + } + + voc = args.Argv( 1 ); + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + index = 0; + while ( keyval ) { + if ( !keyval->GetValue().Icmp( voc ) ) { + break; + } + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index++; + } + if ( !keyval ) { + common->Printf( "Voice command not found: %s\n", voc ); + return; + } + voiceChatThrottle = gameLocal.realClientTime + 1000; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT ); + outMsg.WriteLong( index ); + outMsg.WriteBits( team ? 1 : 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ProcessVoiceChat +================ +*/ +void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) { + const idDict *spawnArgs; + const idKeyValue *keyval; + idStr name; + idStr snd_key; + idStr text_key; + idPlayer *p; + + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating ) { + return; + } + + // lookup the sound def + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + while ( index > 0 && keyval ) { + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index--; + } + if ( !keyval ) { + common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index ); + return; + } + snd_key = keyval->GetKey(); + name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" ); + sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() ); + if ( team || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEREVIEW ) { + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) ); + } else { + p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL ); + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL ); + } +} + +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat +/* +================ +idMultiplayerGame::ClientVoiceMute +================ +*/ +void idMultiplayerGame::ClientVoiceMute( int muteClient, bool mute ) { + // clients/listen server only + assert( gameLocal.isListenServer || gameLocal.isClient ); + + if ( NULL == gameLocal.GetLocalPlayer() ) { + return; + } + + if ( muteClient == -1 || !gameLocal.mpGame.IsInGame( muteClient ) ) { + gameLocal.Warning( "idMultiplayerGame::ClientVoiceMute() - Invalid client '%d' specified", muteClient ); + return; + } + + // do the mute/unmute + gameLocal.GetLocalPlayer()->MutePlayer( muteClient, mute ); + + // tell the server + if( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING ); + outMsg.WriteByte( muteClient ); + outMsg.WriteByte( mute ? 1 : 0 ); // 1 for mute, 0 for unmute + networkSystem->ClientSendReliableMessage( outMsg ); + } + + // display some niceties + common->Printf( "Player %s's has been %s.\n", gameLocal.GetUserInfo( muteClient )->GetString( "ui_name" ), mute ? "muted" : "unmuted" ); +} + +/* +================ +idMultiplayerGame::GetClientNumFromPlayerName +================ +*/ +int idMultiplayerGame::GetClientNumFromPlayerName( const char *playerName ) { + if ( NULL == playerName || '\0' == *playerName ) { + return -1; + } + + int clientNum = -1; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + if ( 0 == idStr::Icmp( gameLocal.userInfo[ i ].GetString( "ui_name" ), playerName ) ) { + clientNum = i; + break; + } + } + } + + if ( -1 == clientNum ) { + common->Warning( "idMultiplayerGame::GetClientNumFromPlayerName(): unknown player '%s'", playerName ); + } + + return clientNum; +} + +/* +================ +idMultiplayerGame::ServerHandleVoiceMuting +================ +*/ +void idMultiplayerGame::ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ) { + assert( !gameLocal.isClient ); + + idPlayer *playerSrc = gameLocal.GetClientByNum( clientSrc ); + idPlayer *playerDest = gameLocal.GetClientByNum( clientDest ); + + if ( NULL == playerSrc ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientSrc ); + return; + } + + if ( NULL == playerDest ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientDest ); + return; + } + + if ( mute ) { + playerSrc->MutePlayer( playerDest, true ); + common->DPrintf( "DEBUG: client %s muted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } else { + playerSrc->MutePlayer( playerDest, false ); + common->DPrintf( "DEBUG: client %s unmuted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } +} + + +/* +================ +idMultiplayerGame::ClearAnnouncerSounds + +This method deletes unplayed announcer sounds at the end of a game round. +This fixes a bug where the round time warnings were being played from +previous rounds. +================ +*/ +void idMultiplayerGame::ClearAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + snd->announcerSoundNode.Remove ( ); + delete snd; + } + + announcerPlayTime = 0; +} + +/* +================ +idMultiplayerGame::HandleServerAdminBanPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminBanPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + if ( gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban the host!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i ban", clientNum ) ); + } else { + if ( clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban yourserlf!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i ban", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminRemoveBan +================ +*/ +void idMultiplayerGame::HandleServerAdminRemoveBan( const char * clientGuid ) { + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + gameLocal.RemoveGuidFromBanList( clientGuid ); + } else { + int clientNum = gameLocal.GetClientNumByGuid( clientGuid ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon removeClientFromBanList %d", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminKickPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminKickPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminKickPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i", clientNum ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminForceTeamSwitch +================ +*/ +void idMultiplayerGame::HandleServerAdminForceTeamSwitch( int clientNum ) { + if ( !gameLocal.IsTeamGame() ) { + return; + } + + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminForceTeamSwitch: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "forceTeamChange %d\n", clientNum)); + +/* if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + }*/ + } else { +/* idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVER_ADMIN ); + outMsg.WriteByte( SERVER_ADMIN_FORCE_SWITCH ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); */ + + //jshepard: need to be able to do this via rcon + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon forceTeamChange %d\n", clientNum)); + + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminCommands +================ +*/ +bool idMultiplayerGame::HandleServerAdminCommands( serverAdminData_t &data ) { + bool restartNeeded = false; + bool nextMapNeeded = false; + bool anyChanges = false; + bool runPickMap = false; + int nGameType = 0; + idStr currentMap = si_map.GetString( ); + + const char *szGameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( szGameType, "DM" ) ) { + nGameType = GAME_DM; + } else if ( 0 == idStr::Icmp( szGameType, "Team DM" ) ) { + nGameType = GAME_TDM; + } else if ( 0 == idStr::Icmp( szGameType, "CTF" ) ) { + nGameType = GAME_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "Tourney" ) ) { + nGameType = GAME_TOURNEY; + } else if ( 0 == idStr::Icmp( szGameType, "Arena CTF" ) ) { + nGameType = GAME_ARENA_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "DeadZone" ) ) { + nGameType = GAME_DEADZONE; + } else { + nGameType = GAME_SP; + } + if ( nGameType != data.gameType ) { + + switch ( data.gameType ) { + case GAME_TDM: szGameType = "Team DM"; runPickMap = true; break; + case GAME_TOURNEY: szGameType = "Tourney"; runPickMap = true; break; + case GAME_CTF: szGameType = "CTF"; runPickMap = true; break; + case GAME_ARENA_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + + // mekberg: hack, if we had 1f ctf the gui index wouldn't be off =( + case GAME_1F_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + case GAME_DEADZONE: szGameType = "DeadZone"; runPickMap = true; break; + default: + case GAME_DM: szGameType = "DM"; break; + } + + //we're going to reset the map here, so make sure to kill the active vote. + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + + si_gameType.SetString( szGameType ); + if( runPickMap && gameLocal.isServer ) { + //set the selected map to the admin data value, then make sure it can run the selected gametype. + si_map.SetString( data.mapName.c_str() ); + if( PickMap( szGameType ) || idStr::Icmp( si_map.GetString( ), currentMap.c_str( ) ) ) { + nextMapNeeded = true; + restartNeeded = false; + data.mapName = idStr( si_map.GetString() ); + data.restartMap = true; + } + } + } + + if ( gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) != data.buying ) + restartNeeded = true; + + // Rcon these cvars if this isn't the server. We can trust the input from the gui that the + // gametype and map always match. + if ( !gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_autoBalance %d", data.autoBalance ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_isBuyingEnabled %d", data.buying ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_captureLimit %d", data.captureLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_controlTime %d", data.controlTime ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_fragLimit %d", data.fragLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_gameType %s", szGameType ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_map %s", data.mapName.c_str() ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_tourneyLimit %d", data.tourneyLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_minPlayers %d", data.minPlayers ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_timeLimit %d", data.timeLimit ) ); + if( runPickMap ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon verifyServerSettings" ) ); + } + + if( data.shuffleTeams ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon shuffleTeams" ); + } + + if( restartNeeded ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverMapRestart" ); + } else if( data.restartMap || nextMapNeeded || idStr::Icmp( gameLocal.serverInfo.GetString( "si_map" ), data.mapName.c_str() ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverNextMap" ); + } + else + { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon rescanSI" ); + } + + return true; + } + + if ( data.restartMap ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + } + + if ( data.shuffleTeams ) { + ShuffleTeams(); + anyChanges = true; + } + + //this section won't be encountered if the gametype was changed. But that's ok. + if ( data.mapName.c_str() && idStr::Icmp( data.mapName.c_str(), si_map.GetString() ) ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + si_map.SetString(data.mapName.c_str()); + cvarSystem->SetCVarString( "si_map", data.mapName.c_str() ); + nextMapNeeded = true; + anyChanges = true; + } + + if ( data.captureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + si_captureLimit.SetInteger( data.captureLimit ); + anyChanges = true; + } + if ( data.fragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + si_fragLimit.SetInteger( data.fragLimit ); + anyChanges = true; + } + if ( data.tourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + si_tourneyLimit.SetInteger( data.tourneyLimit ); + anyChanges = true; + } + if ( data.timeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + si_timeLimit.SetInteger( data.timeLimit ); + anyChanges = true; + } + if ( data.buying != gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + si_isBuyingEnabled.SetInteger( data.buying ); + anyChanges = true; + restartNeeded = true; + } + if ( data.autoBalance != gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + si_autobalance.SetBool( data.autoBalance ); + anyChanges = true; + } + if ( data.controlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + si_controlTime.SetInteger( data.controlTime ); + anyChanges = true; + } + + if ( nextMapNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + gameLocal.sessionCommand = "nextMap"; + return anyChanges; + } + else if ( gameLocal.NeedRestart() || restartNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + return anyChanges; +} + + +// RAVEN END + +/* +=============== +idMultiplayerGame::WriteStartState +=============== +*/ + void idMultiplayerGame::WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ) { + int i; + idEntity *ent; + + // send the start time + msg.WriteLong( matchStartedTime ); + // send the powerup states and the spectate states + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( withLocalClient || i != clientNum ) && ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + msg.WriteShort( i ); + msg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups ); + msg.WriteBits( ent->GetInstance(), ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 ); + } + } + msg.WriteShort( MAX_CLIENTS ); +} + +/* +================ +idMultiplayerGame::ServerWriteInitialReliableMessages +================ +*/ +void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE ); + WriteStartState( clientNum, outMsg, false ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // we send SI in connectResponse messages, but it may have been modified already + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + gameState->SendInitialState( clientNum ); +} + +/* +================ +idMultiplayerGame::ClientReadStartState +================ +*/ +void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) { + int i, client, powerup; + + assert( gameLocal.isClient ); + + // read the state in preparation for reading snapshot updates + matchStartedTime = msg.ReadLong( ); + while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::GetClassType() ) ); +// RAVEN END + powerup = msg.ReadShort(); + + int instance = ( msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ) ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->SetInstance( instance ); + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate ); + + // set powerups after we get instance information for this client + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( powerup & ( 1 << i ) ) { + static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 ); + } + } + } +} + +const char* idMultiplayerGame::announcerSoundDefs[ AS_NUM_SOUNDS ] = { + // General announcements + "announce_general_one", // AS_GENERAL_ONE + "announce_general_two", // AS_GENERAL_TWO + "announce_general_three", // AS_GENERAL_THREE + "announce_general_you_win", // AS_GENERAL_YOU_WIN + "announce_general_you_lose", // AS_GENERAL_YOU_LOSE + "announce_general_fight", // AS_GENERAL_FIGHT + "announce_general_sudden_death", // AS_GENERAL_SUDDEN_DEATH + "announce_general_vote_failed", // AS_GENERAL_VOTE_FAILED + "announce_general_vote_passed", // AS_GENERAL_VOTE_PASSED + "announce_general_vote_now", // AS_GENERAL_VOTE_NOW + "announce_general_one_frag", // AS_GENERAL_ONE_FRAG + "announce_general_two_frags", // AS_GENERAL_TWO_FRAGS + "announce_general_three_frags", // AS_GENERAL_THREE_FRAGS + "announce_general_one_minute", // AS_GENERAL_ONE_MINUTE + "announce_general_five_minute", // AS_GENERAL_FIVE_MINUTE + "announce_general_prepare_to_fight", // AS_GENERAL_PREPARE_TO_FIGHT + "announce_general_quad_damage", // AS_GENERAL_QUAD_DAMAGE + "announce_general_regeneration", // AS_GENERAL_REGENERATION + "announce_general_haste", // AS_GENERAL_HASTE + "announce_general_invisibility", // AS_GENERAL_INVISIBILITY + // DM announcements + "announce_dm_you_tied_lead", // AS_DM_YOU_TIED_LEAD + "announce_dm_you_have_taken_lead", // AS_DM_YOU_HAVE_TAKEN_LEAD + "announce_dm_you_lost_lead", // AS_DM_YOU_LOST_LEAD + // Team announcements + "announce_team_enemy_score", // AS_TEAM_ENEMY_SCORES + "announce_team_you_score", // AS_TEAM_YOU_SCORE + "announce_team_teams_tied", // AS_TEAM_TEAMS_TIED + "announce_team_strogg_lead", // AS_TEAM_STROGG_LEAD + "announce_team_marines_lead", // AS_TEAM_MARINES_LEAD + "announce_team_join_marine", // AS_TEAM_JOIN_MARINE + "announce_team_join_strogg", // AS_TEAM_JOIN_STROGG + // CTF announcements + "announce_ctf_you_have_flag", // AS_CTF_YOU_HAVE_FLAG + "announce_ctf_your_team_has_flag", // AS_CTF_YOUR_TEAM_HAS_FLAG + "announce_ctf_enemy_has_flag", // AS_CTF_ENEMY_HAS_FLAG + "announce_ctf_your_team_drops_flag", // AS_CTF_YOUR_TEAM_DROPS_FLAG + "announce_ctf_enemy_drops_flag", // AS_CTF_ENEMY_DROPS_FLAG + "announce_ctf_your_flag_returned", // AS_CTF_YOUR_FLAG_RETURNED + "announce_ctf_enemy_returns_flag", // AS_CTF_ENEMY_RETURNS_FLAG + // Tourney announcements + "announce_tourney_advance", // AS_TOURNEY_ADVANCE + "announce_tourney_join_arena_one", // AS_TOURNEY_JOIN_ARENA_ONE + "announce_tourney_join_arena_two", // AS_TOURNEY_JOIN_ARENA_TWO + "announce_tourney_join_arena_three", // AS_TOURNEY_JOIN_ARENA_THREE + "announce_tourney_join_arena_four", // AS_TOURNEY_JOIN_ARENA_FOUR + "announce_tourney_join_arena_five", // AS_TOURNEY_JOIN_ARENA_FIVE + "announce_tourney_join_arena_six", // AS_TOURNEY_JOIN_ARENA_SIX + "announce_tourney_join_arena_seven", // AS_TOURNEY_JOIN_ARENA_SEVEN + "announce_tourney_join_arena_eight", // AS_TOURNEY_JOIN_ARENA_EIGHT + "announce_tourney_join_arena_waiting", // AS_TOURNEY_JOIN_ARENA_WAITING + "announce_tourney_done", // AS_TOURNEY_DONE + "announce_tourney_start", // AS_TOURNEY_START + "announce_tourney_eliminated", // AS_TOURNEY_ELIMINATED + "announce_tourney_won", // AS_TOURNEY_WON + "announce_tourney_prelims", // AS_TOURNEY_PRELIMS + "announce_tourney_quarter_finals", // AS_TOURNEY_QUARTER_FINALS + "announce_tourney_semi_finals", // AS_TOURNEY_SEMI_FINALS + "announce_tourney_final_match", // AS_TOURNEY_FINAL_MATCH + "sound/vo/mp/9_99_320_10", // AS_GENERAL_TEAM_AMMOREGEN + "sound/vo/mp/9_99_360_6" // AS_GENERAL_TEAM_DOUBLER +}; + +void idMultiplayerGame::ScheduleAnnouncerSound( announcerSound_t sound, float time, int instance, bool allowOverride ) { + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + if ( time < gameLocal.time ) { + return; + } + + if ( sound >= AS_NUM_SOUNDS ) { + return; + } + + announcerSoundNode_t* newSound = new announcerSoundNode_t; + newSound->soundShader = sound; + newSound->time = time; + newSound->announcerSoundNode.SetOwner( newSound ); + newSound->instance = instance; + newSound->allowOverride = allowOverride; + + announcerSoundNode_t* snd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = snd->announcerSoundNode.Next() ) { + if ( snd->time > newSound->time ) { + newSound->announcerSoundNode.InsertBefore( snd->announcerSoundNode ); + break; + } + } + if ( snd == NULL ) { + newSound->announcerSoundNode.AddToEnd( announcerSoundQueue ); + } +} + +void idMultiplayerGame::RemoveAnnouncerSound( int type ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + if ( snd->soundShader == type ) { + snd->announcerSoundNode.Remove( ); + delete snd; + break; + } + } + + // if a sound is currently playing, stop it + if( lastAnnouncerSound == type ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + } +} + +void idMultiplayerGame::RemoveAnnouncerSoundRange( int startType, int endType ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + for( int i = startType; i <= endType; i++ ) { + if ( snd->soundShader == i ) { + snd->announcerSoundNode.Remove( ); + delete snd; + } + } + } + + // if a sound is currently playing, stop it + for( int i = startType; i <= endType; i++ ) { + if( lastAnnouncerSound == i ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + break; + } + } +} + + +void idMultiplayerGame::ScheduleTimeAnnouncements( void ) { + if( !gameLocal.GetLocalPlayer() || !gameState ) { + // too early + return; + } + + if( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != WARMUP ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int endGameTime = 0; + + if( gameLocal.gameType == GAME_TOURNEY ) { + int arena = gameLocal.GetLocalPlayer()->GetArena(); + if( !((rvTourneyGameState*)gameState)->GetArena( arena ).IsPlaying() ) { + return; // arena is not active + } + // per-arena timelimits + endGameTime = ((rvTourneyGameState*)gameState)->GetArena( arena ).GetMatchStartTime() + ( timeLimit * 60000 ); + } else { + endGameTime = matchStartedTime + ( timeLimit * 60000 ); + } + + // clean out any preexisting announcer sounds + RemoveAnnouncerSound( AS_GENERAL_ONE_MINUTE ); + RemoveAnnouncerSound( AS_GENERAL_FIVE_MINUTE ); + + if( timeLimit > 5 ) { + ScheduleAnnouncerSound( AS_GENERAL_FIVE_MINUTE, endGameTime - (5 * 60000) ); + } + if( timeLimit > 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_MINUTE, endGameTime - (60000) ); + } + } +} + +void idMultiplayerGame::PlayAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + // if we're done playing the last sound reset override status + if( announcerPlayTime <= gameLocal.time ) { + currentSoundOverride = false; + } + + if ( announcerPlayTime > gameLocal.time && !currentSoundOverride ) { + return; + } + + // in tourney only play sounds scheduled for your current arena + if ( gameLocal.gameType == GAME_TOURNEY ) { + // go through and find the first sound to play in our arena, delete any sounds + // for other arenas we see along the way. + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + + if( snd->time > gameLocal.time ) { + return; + } + + if( snd->instance == -1 || snd->soundShader == AS_GENERAL_VOTE_NOW || snd->soundShader == AS_GENERAL_VOTE_PASSED || snd->soundShader == AS_GENERAL_VOTE_FAILED ) { + // all-instance sound + break; + } + + if( snd->instance == gameLocal.GetLocalPlayer()->GetInstance() ) { + if( snd->allowOverride && nextSnd && nextSnd->time <= gameLocal.time ) { + // this sound is OK with being over-ridden, + // and the next sound is ready to play, so go ahead and look at the next sound + snd->announcerSoundNode.Remove ( ); + delete snd; + + continue; + } else { + break; + } + } + + snd->announcerSoundNode.Remove ( ); + delete snd; + } + } else { + snd = announcerSoundQueue.Next(); + if( snd && snd->time > gameLocal.time ) { + return; + } + } + + // play the sound locally + if ( snd && snd->soundShader < AS_NUM_SOUNDS ) { + int length = 0; + + //don't play timelimit countdown announcements if game is already over + mpGameState_t state = gameState->GetMPGameState(); + if ( state == GAMEREVIEW //game is over, in scoreboard + && ( snd->soundShader == AS_GENERAL_ONE_MINUTE + || snd->soundShader == AS_GENERAL_FIVE_MINUTE ) ) { + //ignore scheduled time limit warnings that haven't executed yet + snd->announcerSoundNode.Remove(); + delete snd; + } else { + snd->announcerSoundNode.Remove(); + + gameLocal.GetLocalPlayer()->StartSoundShader( declManager->FindSound( announcerSoundDefs[ snd->soundShader ], false ), SND_CHANNEL_MP_ANNOUNCER, 0, false, &length ); + currentSoundOverride = snd->allowOverride; + lastAnnouncerSound = snd->soundShader; + + delete snd; + } + + // if sounds remain to be played, check again + announcerPlayTime = gameLocal.time + length; + } +} + +void idMultiplayerGame::ClearTeamScores ( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } +} + +void idMultiplayerGame::AddTeamScore ( int team, int amount ) { + if ( team < 0 || team >= TEAM_MAX ) { + return; + } + + teamScore[ team ] += amount; +} + +void idMultiplayerGame::AddPlayerScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount += amount; + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].fragCount ); +} + +void idMultiplayerGame::AddPlayerTeamScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount += amount; + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].teamFragCount ); +} + +void idMultiplayerGame::AddPlayerWin( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins += amount; + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ player->entityNumber ].wins ); +} + +void idMultiplayerGame::SetPlayerScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); + +} + +void idMultiplayerGame::SetPlayerTeamScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); +} + +void idMultiplayerGame::SetPlayerDeadZoneScore( idPlayer* player, float value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].deadZoneScore = value; +} + +void idMultiplayerGame::SetPlayerWin( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, value ); +} + +rvCTF_AssaultPoint* idMultiplayerGame::NextAP( int team ) { + for( int i = 0; i < assaultPoints.Num(); i++ ) { + if( assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]->GetOwner() == team ) { + continue; + } + return assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]; + } + return NULL; +} + +void idMultiplayerGame::ClientSetInstance( const idBitMsg& msg ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int instance = msg.ReadByte(); + + gameLocal.GetInstance( 0 )->SetSpawnInstanceID( instance ); + // on the client, we delete all entities, + // the server will send over new ones + gameLocal.InstanceClear(); + // set the starting offset for repopulation back to matching what the server will have + // this should be covered by setting indexes when populating the instances as well, but it doesn't hurt + gameLocal.firstFreeIndex = MAX_CLIENTS; + + player->SetArena( instance ); + player->SetInstance( instance ); + + // spawn the instance entities + gameLocal.GetInstance( 0 )->PopulateFromMessage( msg ); + + // players in other instances might have been hidden, update them + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p ) { + if( p->GetInstance() == instance ) { + p->ClientInstanceJoin(); + } else { + p->ClientInstanceLeave(); + } + } + } +} + +void idMultiplayerGame::ServerSetInstance( int instance ) { + for( int i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( ent ) { + if( ent->GetInstance() != instance ) { + ent->InstanceLeave(); + } else { + ent->InstanceJoin(); + } + } + } +} + +const char* idMultiplayerGame::GetLongGametypeName( const char* gametype ) { + if( !idStr::Icmp( gametype, "Tourney" ) ) { + return common->GetLocalizedString( "#str_107676" ); + } else if( !idStr::Icmp( gametype, "Team DM" ) ) { + return common->GetLocalizedString( "#str_107677" ); + } else if( !idStr::Icmp( gametype, "CTF" ) ) { + return common->GetLocalizedString( "#str_107678" ); + } else if( !idStr::Icmp( gametype, "DM" ) ) { + return common->GetLocalizedString( "#str_107679" ); + } else if( !idStr::Icmp( gametype, "One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107680" ); + } else if( !idStr::Icmp( gametype, "Arena CTF" ) ) { + return common->GetLocalizedString( "#str_107681" ); + } else if( !idStr::Icmp( gametype, "Arena One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107682" ); + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + } else if( !idStr::Icmp( gametype, "DeadZone" ) ) { + return common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + // RITUAL END + } + + return ""; +} + +int idMultiplayerGame::GameTypeToVote( const char *gameType ) { + if ( 0 == idStr::Icmp( gameType, "DM" ) ) { + return VOTE_GAMETYPE_DM; + } else if ( 0 == idStr::Icmp( gameType, "Tourney" ) ) { + return VOTE_GAMETYPE_TOURNEY; + } else if ( 0 == idStr::Icmp( gameType, "Team DM" ) ) { + return VOTE_GAMETYPE_TDM; + } else if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + return VOTE_GAMETYPE_CTF; + } else if ( 0 == idStr::Icmp( gameType, "Arena CTF" ) ) { + return VOTE_GAMETYPE_ARENA_CTF; + } else if ( 0 == idStr::Icmp( gameType, "DeadZone" ) ) { + return VOTE_GAMETYPE_DEADZONE; + } + + return VOTE_GAMETYPE_DM; +} + +float idMultiplayerGame::GetPlayerDeadZoneScore( idPlayer* player ) { + return playerState[ player->entityNumber ].deadZoneScore; +} + +int idMultiplayerGame::GetPlayerTime( idPlayer* player ) { + return ( gameLocal.time - player->GetConnectTime() ) / 60000; +} + +int idMultiplayerGame::GetTeamScore( idPlayer* player ) { + return GetTeamScore( player->entityNumber ); +} + +int idMultiplayerGame::GetScore( idPlayer* player ) { + return GetScore( player->entityNumber ); +} + +int idMultiplayerGame::GetWins( idPlayer* player ) { + return GetWins( player->entityNumber ); +} + +void idMultiplayerGame::EnableDamage( bool enable ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + player->fl.takedamage = enable; + } +} + +void idMultiplayerGame::ReceiveRemoteConsoleOutput( const char* output ) { + if( mainGui ) { + idStr newOutput( output ); + + if( rconHistory.Length() + newOutput.Length() > RCON_HISTORY_SIZE ) { + int removeLength = rconHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + rconHistory.Empty(); + } else { + while( (rconHistory.Length() - removeLength) + newOutput.Length() > RCON_HISTORY_SIZE ) { + removeLength = rconHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + rconHistory.Empty(); + break; + } + } + } + rconHistory = rconHistory.Right( rconHistory.Length() - removeLength ); + } + + + int consoleInputStart = newOutput.Find( "Console Input: " ); + if( consoleInputStart != -1 ) { + idStr consoleInput = newOutput.Right( newOutput.Length() - consoleInputStart - 15 ); + newOutput = newOutput.Left( consoleInputStart ); + newOutput.StripTrailing( "\n" ); + consoleInput.StripTrailing( "\n" ); + mainGui->SetStateString( "admin_console_input", consoleInput.c_str() ); + } + + if( newOutput.Length() ) { + rconHistory.Append( newOutput ); + rconHistory.Append( '\n' ); + } + + mainGui->SetStateString( "admin_console_history", rconHistory.c_str() ); + } +} + +/* +=============== +idMultiplayerGame::ShuffleTeams +=============== +*/ +void idMultiplayerGame::ShuffleTeams( void ) { + // turn off autobalance if its on + bool autoBalance = gameLocal.serverInfo.GetBool( "si_autoBalance" ); + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", false ); + } + + int loosingTeam = teamScore[ TEAM_MARINE ] < teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG; + int winningTeam = loosingTeam == TEAM_MARINE ? TEAM_STROGG : TEAM_MARINE; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( !(i % 2) ) { + // switch even players to losing team + if( rankedPlayers[ i ].First()->team != loosingTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ loosingTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } else { + if( rankedPlayers[ i ].First()->team != winningTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ winningTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } + } + + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", true ); + } +} + + +rvGameState* idMultiplayerGame::GetGameState( void ) { + return gameState; +} + +void idMultiplayerGame::SetGameType( void ) { + if( gameState ) { + delete gameState; + } + gameState = NULL; + + if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) == 0 ) ) { + gameLocal.gameType = GAME_DM; + gameState = new rvDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Tourney" ) == 0 ) ) { + gameLocal.gameType = GAME_TOURNEY; + gameState = new rvTourneyGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) == 0 ) ) { + gameLocal.gameType = GAME_TDM; + gameState = new rvTeamDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DeadZone" ) == 0 ) ) { + gameLocal.gameType = GAME_DEADZONE; + gameState = new riDZGameState; + } else { + gameLocal.Error( "idMultiplayerGame::SetGameType() - Unknown gametype '%s'\n", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } + + // force entity filter to gametype name in multiplayer + if( gameLocal.gameType != GAME_SP ) { + gameLocal.serverInfo.Set( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + // also set as a CVar for when serverinfo is rescanned + cvarSystem->SetCVarString( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } +} + +//asalmon: need to access total frags for a team and total score for a team +int idMultiplayerGame::GetTeamsTotalFrags( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + if(rankedPlayers[ j ].First()->team == i) + { + total += GetScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +int idMultiplayerGame::GetTeamsTotalScore( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + idPlayer foo; + + if(rankedPlayers[ j ].First()->team == i) + { + total += GetTeamScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +/* +=============== +idMultiplayerGame::PickMap +=============== +*/ +bool idMultiplayerGame::PickMap( idStr gameType, bool checkOnly ) { + + idStrList maps; + int miss = 0; + const idDecl *mapDecl; + const idDeclEntityDef *mapDef; + int index = 0; + int btype; + const char* mapName; + + mapName = si_map.GetString(); + + // if we didn't set up a gametype, grab the current game type. + if ( gameType.IsEmpty() ) { + gameType = si_gameType.GetString(); + } + + // if we're playing a map of this gametype, don't change. + mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + mapDef = static_cast( mapDecl ); + if ( mapDef ) { + btype = mapDef->dict.GetInt( gameType ); + if ( btype ) { + // ( not sure what the gloubi boulga is about re-setting si_map two ways after reading it at the start of the function already ) + cvarSystem->SetCVarString( "si_map", mapName ); + si_map.SetString( mapName ); + return false; + } + } + + if ( checkOnly ) { + // always allow switching to DM mode, whatever the settings on the map ( DM should always be possible ) + if ( !idStr::Icmp( si_gameType.GetString(), "DM" ) ) { + return false; + } + // don't actually change anything, indicate we would + return true; + } + + int i; + idFileList *files; + idStrList fileList; + + int count = 0; + + files = fileSystem->ListFiles( "maps/mp", ".map" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + fileList.AddUnique( va( "mp/%s", files->GetList()[i].c_str() ) ); + } + fileSystem->FreeFileList( files ); + + files = fileSystem->ListFiles( "maps/mp", ".mapc" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + idStr fixedExtension(files->GetList()[i]); + fixedExtension.SetFileExtension("map"); + fileList.AddUnique( va( "mp/%s", fixedExtension.c_str() ) ); + } + + fileList.Sort(); + + idStr name; + idStr cycle; + + //Populate the map list + for ( i = 0; i < fileList.Num(); i++) { + //Add only MP maps. + if(!idStr::FindText(fileList[i].c_str(), "mp/")) + { + maps.AddUnique(fileList[i].c_str()); + } + } + maps.Sort(); + + if(maps.Num() > 0) + { + while(miss < 100) + { + index = gameLocal.random.RandomInt( maps.Num() ); + mapName = maps[index].c_str(); + + mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + mapDef = static_cast( mapDecl ); + if ( mapDef ) { + btype = mapDef->dict.GetInt( gameType ); + if(btype) + { + cvarSystem->SetCVarString("si_map",mapName); + si_map.SetString( mapName ); + return true; + + } + } + miss++; + + } + + } + + //something is wrong and there are no maps for this game type. This should never happen. + gameLocal.Error( "No maps found for game type: %s.\n", gameType.c_str() ); + return false; +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( int rank, bool tied, int score ) { + char* placeString; + + if( rank == 0 ) { + //"1st^0 place with" + placeString = va( "%s%s %d", S_COLOR_BLUE, common->GetLocalizedString( "#str_107689" ), score ); + } else if( rank == 1 ) { + //"2nd^0 place with" + placeString = va( "%s%s %d", S_COLOR_RED, common->GetLocalizedString( "#str_107690" ), score ); + } else if( rank == 2 ) { + //"3rd^0 place with" + placeString = va( "%s%s %d", S_COLOR_YELLOW, common->GetLocalizedString( "#str_107691" ), score ); + } else { + //"th^0 place with" + placeString = va( "%d%s %d", rank + 1, common->GetLocalizedString( "#str_107692" ), score ); + } + + if( tied ) { + //Tied for + return va( "%s %s", common->GetLocalizedString( "#str_107693" ), placeString ); + } else { + return placeString; + } +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( idPlayer* player ) { + if( player == NULL ) { + return ""; + } + + bool tied = false; + int rank = GetPlayerRank( player, tied ); + return GetPlayerRankText( rank, tied, GetScore( player ) ); +} + +/* +=============== +idMultiplayerGame::WriteNetworkInfo +=============== +*/ +void idMultiplayerGame::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteStartState( clientNum, msg, true ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); + + gameState->WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idMultiplayerGame::ReadNetworkInfo +=============== +*/ +void idMultiplayerGame::ReadNetworkInfo( idFile* file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ClientReadStartState( msg ); + + gameState->ReadNetworkInfo( file, clientNum ); +} + +void idMultiplayerGame::AddPrivatePlayer( int clientId ) { + privateClientIds.Append( clientId ); +} + +void idMultiplayerGame::RemovePrivatePlayer( int clientId ) { + for( int i = 0; i < privateClientIds.Num(); i++ ) { + if( clientId == privateClientIds[ i ] ) { + privateClientIds.RemoveIndex( i ); + i--; + } + } +} + +void idMultiplayerGame::UpdatePrivatePlayerCount( void ) { + if ( !gameLocal.isServer ) { + return; + } + + int numPrivatePlayers = 0; + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( privatePlayers & (1 << i) ) { + if( gameLocal.entities[ i ] ) { + numPrivatePlayers++; + } else { + privatePlayers &= ~( 1 << i ); + } + } + } + + cvarSystem->SetCVarInteger( "si_numPrivatePlayers", numPrivatePlayers ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); +} + +void idMultiplayerGame::SetFlagEntity( idEntity* ent, int team ) { + assert( ( team == TEAM_STROGG || team == TEAM_MARINE ) ); + + flagEntities[ team ] = ent; +} + +idEntity* idMultiplayerGame::GetFlagEntity( int team ) { + assert( team >= 0 && team < TEAM_MAX ); + + return flagEntities[ team ]; +} + + +// +void idMultiplayerGame::CheckTeamBalance_f( const idCmdArgs &args ) { + + if ( args.Argc() < 5 ) { + return; + } + + idPlayer *localPlayer = gameLocal.GetClientByNum( gameLocal.localClientNum ); + + const char *team = args.Argv(1); + const char *yesEvent = args.Argv(2); + const char *noEvent = args.Argv(3); + const char *sameTeamEvent = args.Argv(4); + + if ( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) || !gameLocal.IsTeamGame() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + return; + } + + int teamCount[2]; + teamCount[0] = teamCount[1] = 0; + + for ( int i = 0; i < gameLocal.numClients; ++i ) { + idEntity *ent = gameLocal.entities[i]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != localPlayer ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + if ( idStr::Icmp( team, "marine" ) == 0 ) { + if ( localPlayer->team == TEAM_MARINE && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_MARINE] > teamCount[TEAM_STROGG] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } else { + + if ( localPlayer->team == TEAM_STROGG && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_STROGG] > teamCount[TEAM_MARINE] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } +} + +/* +================ +idMultiplayerGame::LocalizeGametype + +dupe of rvServerScanGUI::LocalizeGametype +================ +*/ +const char *idMultiplayerGame::LocalizeGametype( void ) { + + const char *gameType; + + gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + localisedGametype = gameType; + + if( !idStr::Icmp( gameType, "DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110011" ); + } + if( !idStr::Icmp( gameType, "Tourney" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110012" ); + } + if( !idStr::Icmp( gameType, "Team DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110013" ); + } + if( !idStr::Icmp( gameType, "CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110014" ); + } + if( !idStr::Icmp( gameType, "Arena CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110015" ); + } + if( !idStr::Icmp( gameType, "DeadZone" ) ) { + localisedGametype = common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + } + + return( localisedGametype.c_str() ); +} + +int idMultiplayerGame::VerifyTeamSwitch( int wantTeam, idPlayer *player ) { + idEntity* ent; + int teamCount[ TEAM_MAX ]; + int balanceTeam = -1; + + if( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) ) { + return wantTeam; + } + + teamCount[ TEAM_MARINE ] = teamCount[ TEAM_STROGG ] = 0; + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != player ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ TEAM_MARINE ] > teamCount[ TEAM_STROGG ] ) { + balanceTeam = TEAM_STROGG; + } else if ( teamCount[ TEAM_STROGG ] > teamCount[ TEAM_MARINE ] ) { + balanceTeam = TEAM_MARINE; + } + + return (balanceTeam == -1) ? wantTeam : balanceTeam; +} + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================ +idMultiplayerGame::NumberOfPlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfPlayersOnTeam( int team ) +{ + int teamPlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + teamPlayerCount ++; + } + } + } + + return teamPlayerCount; +} + + +/* +================ +idMultiplayerGame::NumberOfAlivePlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfAlivePlayersOnTeam( int team ) +{ + int teamAlivePlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team && entPlayer->allowedToRespawn ) + { + teamAlivePlayerCount ++; + } + } + } + + return teamAlivePlayerCount; +} + + +// RITUAL END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +/* +================ +idMultiplayerGame::OpenLocalBuyMenu +================ +*/ +void idMultiplayerGame::OpenLocalBuyMenu( void ) +{ + // Buy menu work in progress + //if ( gameLocal.mpGame.GetCurrentMenu() == 4 ) + //{ + // return; + //} + + if ( currentMenu == 4 ) + return; // Already open + + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.mpGame.nextMenu = 4; +} + +/* +================ +idMultiplayerGame::RedrawLocalBuyMenu +================ +*/ +void idMultiplayerGame::RedrawLocalBuyMenu( void ) +{ + if ( !buyMenu ) + return; + + SetupBuyMenuItems(); + buyMenu->HandleNamedEvent( "update_buymenu" ); +} + + +/* +================ +idMultiplayerGame::GiveCashToTeam +================ +*/ +void idMultiplayerGame::GiveCashToTeam( int team, float cashAmount ) +{ + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + entPlayer->GiveCash( cashAmount ); + } + } + } + +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode( void ) { + if ( !gameLocal.isMultiplayer ) { + return false; + } + + if ( gameLocal.gameType != GAME_TOURNEY ) { + return gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ); + } + + return false; +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedRightNow +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedRightNow( void ) +{ + return ( IsBuyingAllowedInTheCurrentGameMode() && isBuyingAllowedRightNow ); +} + + +void idMultiplayerGame::AddTeamPowerup(int powerup, int time, int team) +{ + int i; + for ( i=0; i > assaultPoints; + + // Buying Manager - authority for buying system game balance constants (awards, + // costs, etc.) + riBuyingManager mpBuyingManager; + + idUserInterface* statSummary; // stat summary + rvTourneyGUI tourneyGUI; + + void ShowStatSummary( void ); + bool CanCapture( int team ); + void FlagCaptured( idPlayer *player ); + + void UpdatePlayerRanks( playerRankMode_t rankMode = PRM_AUTO ); + void UpdateTeamRanks( void ); + void UpdateHud( idUserInterface* _mphud ); + idPlayer * FragLimitHit( void ); + idPlayer * FragLeader( void ); + bool TimeLimitHit( void ); + int GetCurrentMenu( void ) { return currentMenu; } + + void SetFlagEntity( idEntity* ent, int team ); + idEntity* GetFlagEntity( int team ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile* file, int clientNum ); + + void SetShaderParms( renderView_t *view ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int NumberOfPlayersOnTeam( int team ); + int NumberOfAlivePlayersOnTeam( int team ); + void ReportZoneControllingPlayer( idPlayer* player ); + void ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger = 0); + bool IsValidTeam(int team); + void ControlZoneStateChanged( int team ); + + int powerupCount; + int prevAnnouncerSnd; + int defaultWinner; + int deadZonePowerupCount; + dzState_t dzState[ TEAM_MAX ]; + float marineScoreBarPulseAmount; + float stroggScoreBarPulseAmount; +// RITUAL END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + bool isBuyingAllowedRightNow; + + void OpenLocalBuyMenu( void ); + void RedrawLocalBuyMenu( void ); + void GiveCashToTeam( int team, float cashAmount ); + bool IsBuyingAllowedInTheCurrentGameMode( void ); + bool IsBuyingAllowedRightNow( void ); +// RITUAL END + static const char* teamNames[ TEAM_MAX ]; + +private: + static const char *MPGuis[]; + static const char *ThrottleVars[]; + static const char *ThrottleVarsInEnglish[]; + static const int ThrottleDelay[]; + + char killNotificationMsg[ KILL_NOTIFICATION_LEN ]; + + int pingUpdateTime; // time to update ping + + mpPlayerState_t playerState[ MAX_CLIENTS ]; + + // game state + rvGameState* gameState; + + // vote vars + vote_flags_t vote; // active vote or VOTE_NONE + int voteTimeOut; // when the current vote expires + int voteExecTime; // delay between vote passed msg and execute + int yesVotes; // counter for yes votes + int noVotes; // and for no votes + idStr voteValue; // the data voted upon ( server ) + idStr voteString; // the vote string ( client ) + bool voted; // hide vote box ( client ) + int kickVoteMap[ MAX_CLIENTS ]; +// RAVEN BEGIN +// shouchard: names for kickVoteMap + idStr kickVoteMapNames[ MAX_CLIENTS ]; + voteStruct_t currentVoteData; // used for multi-field votes +// RAVEN END + + idStr localisedGametype; + + // time related + int matchStartedTime; // time current match started + + // guis +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + //int sqRoundNumber; // round number in DeadZone; match expires when this equals "sq_numRoundsPerMatch" (cvar) +// squirrel: Mode-agnostic buymenus + idUserInterface *buyMenu; // buy menu +// RITUAL END + idUserInterface *scoreBoard; // scoreboard + idUserInterface *mainGui; // ready / nick / votes etc. + idListGUI *mapList; + idUserInterface *msgmodeGui; // message mode + int currentMenu; // 0 - none, 1 - mainGui, 2 - msgmodeGui + int nextMenu; // if 0, will do mainGui + bool bCurrentMenuMsg; // send menu state updates to server + + + enum { + MPLIGHT_CTF_MARINE, + MPLIGHT_CTF_STROGG, + MPLIGHT_QUAD, + MPLIGHT_HASTE, + MPLIGHT_REGEN, + MPLIGHT_MAX + }; + + int lightHandles[ MPLIGHT_MAX ]; + renderLight_t lights[ MPLIGHT_MAX ]; + + // chat buffer + idStr chatHistory; + + // rcon buffer + idStr rconHistory; + +//RAVEN BEGIN +//asalmon: Need to refresh stats periodically if the player is looking at stats + int currentStatClient; + int currentStatTeam; +//RAVEN END + +public: + // current player rankings + idList > rankedPlayers; + idList unrankedPlayers; + + rvPair rankedTeams[ TEAM_MAX ]; + +private: + + int lastVOAnnounce; + + int lastReadyToggleTime; + bool pureReady; // defaults to false, set to true once server game is running with pure checksums + bool currentSoundOverride; + int switchThrottle[ 3 ]; + int voiceChatThrottle; + + void SetupBuyMenuItems(); + + idList privateClientIds; + int privatePlayers; + + // player who's rank info we're displaying + idEntityPtr rankTextPlayer; + + idEntityPtr flagEntities[ TEAM_MAX ]; + idEntityPtr flagCarriers[ TEAM_MAX ]; + + // updates the passed gui with current score information + void UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ); + + // bdube: test scoreboard + void UpdateTestScoreboard( idUserInterface *scoreBoard ); + + // ddynerman: gametype specific scoreboard + void UpdateScoreboard( idUserInterface *scoreBoard ); + + void UpdateDMScoreboard( idUserInterface *scoreBoard ); + void UpdateTeamScoreboard( idUserInterface *scoreBoard ); + void UpdateSummaryBoard( idUserInterface *scoreBoard ); + + int GetPlayerRank( idPlayer* player, bool& isTied ); + char* GetPlayerRankText( idPlayer* player ); + char* GetPlayerRankText( int rank, bool tied, int score ); + + const char* BuildSummaryListString( idPlayer* player, int rankedScore ); + + void UpdatePrivatePlayerCount( void ); + + typedef struct announcerSoundNode_s { + announcerSound_t soundShader; + float time; + idLinkList announcerSoundNode; + int instance; + bool allowOverride; + } announcerSoundNode_t; + + idLinkList announcerSoundQueue; + announcerSound_t lastAnnouncerSound; + + static const char* announcerSoundDefs[ AS_NUM_SOUNDS ]; + + float announcerPlayTime; + + void PlayAnnouncerSounds ( void ); + + int teamScore[ TEAM_MAX ]; + int teamDeadZoneScore[ TEAM_MAX]; + void ClearTeamScores ( void ); + + void UpdateLeader( idPlayer* oldLeader ); + + void ClearGuis( void ); + void DrawScoreBoard( idPlayer *player ); + void CheckVote( void ); + bool AllPlayersReady( idStr* reason = NULL ); + + const char * GameTime( void ); + + bool EnoughClientsToPlay( void ); + void DrawStatSummary( void ); + // go through the clients, and see if they want to be respawned, and if the game allows it + // called during normal gameplay for death -> respawn cycles + // and for a spectator who want back in the game (see param) + void CheckRespawns( idPlayer *spectator = NULL ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID, idPlayer *player ); + void CheckSpecialLights( void ); + void ForceReady(); + // when clients disconnect or join spectate during game, check if we need to end the game + void CheckAbortGame( void ); + void MessageMode( const idCmdArgs &args ); + void DisableMenu( void ); + void SetMapShot( void ); + // scores in TDM + void VoiceChat( const idCmdArgs &args, bool team ); + +// RAVEN BEGIN +// mekberg: added + void UpdateMPSettingsModel( idUserInterface* currentGui ); +// RAVEN END + + void WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ); +}; + +ID_INLINE bool idMultiplayerGame::IsPureReady( void ) const { + return pureReady; +} + +ID_INLINE void idMultiplayerGame::ClearFrags( int clientNum ) { + playerState[ clientNum ].fragCount = 0; +} + +ID_INLINE bool idMultiplayerGame::IsInGame( int clientNum ) { + return playerState[ clientNum ].ingame; +} + +ID_INLINE int idMultiplayerGame::OpposingTeam( int team ) { + return (team == TEAM_STROGG ? TEAM_MARINE : TEAM_STROGG); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetRankedPlayer( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].First(); + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetRankedPlayerScore( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].Second(); + } else { + return 0; + } +} + +ID_INLINE int idMultiplayerGame::GetNumUnrankedPlayers( void ) { + return unrankedPlayers.Num(); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetUnrankedPlayer( int i ) { + if( i >= 0 && i < unrankedPlayers.Num() ) { + return unrankedPlayers[ i ]; + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetNumRankedPlayers( void ) { + return rankedPlayers.Num(); +} + +ID_INLINE int idMultiplayerGame::GetTeamScore( int i ) { + return playerState[ i ].teamFragCount; +} + +ID_INLINE int idMultiplayerGame::GetScore( int i ) { + return playerState[ i ].fragCount; +} + +ID_INLINE int idMultiplayerGame::GetWins( int i ) { + return playerState[ i ].wins; +} + +ID_INLINE void idMultiplayerGame::ResetRconGuiStatus( void ) { + if( mainGui) { + mainGui->SetStateInt( "password_valid", 0 ); + } +} + +// asalmon: needed access team scores for rich presence +ID_INLINE int idMultiplayerGame::GetScoreForTeam( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + return teamScore[ i ]; +} + +ID_INLINE int idMultiplayerGame::TeamLeader( void ) { + if( teamScore[ TEAM_MARINE ] == teamScore[ TEAM_STROGG ] ) { + return -1; + } else { + return ( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG ); + } +} + +int ComparePlayersByScore( const void* left, const void* right ); +int CompareTeamsByScore( const void* left, const void* right ); + +#endif /* !__MULTIPLAYERGAME_H__ */ + +// RAVEN END diff --git a/source/game/Playback.cpp b/source/game/Playback.cpp new file mode 100644 index 0000000..3f874e5 --- /dev/null +++ b/source/game/Playback.cpp @@ -0,0 +1,417 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI.h" + +#define RECORD_STATE_TRACE_LEN 2048.0f + +class rvGamePlayback +{ +public: + rvGamePlayback( void ); + ~rvGamePlayback( void ); + + void RecordData( const usercmd_t &cmd, idEntity *source ); +private: + int mStartTime; + byte mOldFlags; + idStr mName; + idClipModel *mClipModel; + rvDeclPlayback *mPlayback; +}; + +idCVar g_recordPlayback( "g_recordPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "record the player movement in a playback" ); +idCVar g_playPlayback( "g_playPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "plays the current playback in a camera path" ); + +rvGamePlayback *gamePlayback = NULL; +rvCameraPlayback *playbackCamera = NULL; + +rvGamePlayback::rvGamePlayback( void ) +{ + idStr newName; + const idVec3 trace_mins( -1.0f, -1.0f, -1.0f ); + const idVec3 trace_maxs( 1.0f, 1.0f, 1.0f ); + const idBounds trace_bounds( trace_mins, trace_maxs ); + idTraceModel traceModel( trace_bounds ); + + mStartTime = gameLocal.time; + mOldFlags = 0; + mClipModel = new idClipModel( traceModel ); + + if( !g_currentPlayback.GetInteger() ) + { + newName = declManager->GetNewName( DECL_PLAYBACK, "playbacks/untitled" ); + mPlayback = ( rvDeclPlayback * )declManager->CreateNewDecl( DECL_PLAYBACK, newName, newName + ".playback" ); + mPlayback->ReplaceSourceFileText(); + mPlayback->Invalidate(); + + g_currentPlayback.SetInteger( mPlayback->Index() ); + } + else + { + mPlayback = ( rvDeclPlayback * )declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); + } + + declManager->StartPlaybackRecord( mPlayback ); + common->Printf( "Starting playback record to %s type %d\n", mPlayback->GetName(), g_recordPlayback.GetInteger() ); +} + +rvGamePlayback::~rvGamePlayback( void ) +{ + declManager->FinishPlayback( mPlayback ); + delete mClipModel; + + common->Printf( "Stopping playback play/record\n" ); +} + +void rvGamePlayback::RecordData( const usercmd_t &cmd, idEntity *source ) +{ + idPlayer *player; + trace_t trace; + idMat3 axis; + idVec3 start, end; + rvDeclPlaybackData info; + + info.Init(); + + switch( g_recordPlayback.GetInteger() ) + { + case 1: + info.SetPosition( source->GetPhysics()->GetOrigin() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + break; + + case 2: + gameLocal.GetPlayerView( start, axis ); + + end = start + axis[0] * RECORD_STATE_TRACE_LEN; + + gameLocal.Translation( gameLocal.GetLocalPlayer(), trace, start, end, mClipModel, mat3_identity, CONTENTS_SOLID | CONTENTS_RENDERMODEL, source ); + + if( trace.fraction != 1.0f ) + { + info.SetPosition( trace.endpos ); + info.SetAngles( trace.c.normal.ToAngles() ); + } + break; + + case 3: + assert( source->IsType( idPlayer::GetClassType() ) ); + if( source->IsType( idPlayer::GetClassType() ) ) + { + player = static_cast( source ); + info.SetPosition( player->GetEyePosition() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + } + break; + } + + // Record buttons + info.SetButtons( cmd.buttons ); + + // Record impulses + if( ( cmd.flags & UCF_IMPULSE_SEQUENCE ) != ( mOldFlags & UCF_IMPULSE_SEQUENCE ) ) + { + info.SetImpulse( cmd.impulse ); + } + else + { + info.SetImpulse( 0 ); + } + mOldFlags = cmd.flags; + + declManager->SetPlaybackData( mPlayback, gameLocal.time - mStartTime, -1, &info ); +} + +// ================================================================================================ + +void idGameEdit::DrawPlaybackDebugInfo( void ) +{ + int duration, time; + rvDeclPlaybackData pbd, pbdOld; + const rvDeclPlayback *pb; + + pb = declManager->PlaybackByIndex( g_currentPlayback.GetInteger(), true ); + if( pb ) + { + duration = SEC2MS( pb->GetDuration() ); + pbd.Init(); + pbdOld.Init(); + + declManager->GetPlaybackData( pb, -1, 0, 0, &pbdOld ); + for( time = gameLocal.GetMSec(); time < duration; time += gameLocal.GetMSec() * g_showPlayback.GetInteger() ) + { + declManager->GetPlaybackData( pb, -1, time, time, &pbd ); + gameRenderWorld->DebugArrow( colorGreen, pbdOld.GetPosition(), pbd.GetPosition(), 2 ); + pbdOld = pbd; + } + + gameRenderWorld->DebugBounds( colorRed, pb->GetBounds(), pb->GetOrigin() ); + } +} + +void idGameEdit::RecordPlayback( const usercmd_t &cmd, idEntity *source ) +{ + // Not recording - so instantly exit + if( !g_recordPlayback.GetInteger() && !gamePlayback ) + { + return; + } + + if( !gamePlayback ) + { + gamePlayback = new rvGamePlayback(); + } + + if( g_recordPlayback.GetInteger() ) + { + gamePlayback->RecordData( cmd, source ); + } + else + { + delete gamePlayback; + gamePlayback = NULL; + } +} + +// ================================================================================================ + +bool idGameEdit::PlayPlayback( void ) +{ + // Not playing - so instantly exit + if( !g_playPlayback.GetInteger() && !playbackCamera ) + { + return( false ); + } + + if( !playbackCamera ) + { + playbackCamera = static_cast( gameLocal.SpawnEntityType( rvCameraPlayback::GetClassType() ) ); + SetCamera( playbackCamera ); + + common->Printf( "Starting playback play\n" ); + } + + if( g_currentPlayback.IsModified() ) + { + // Spawn is a misnomer - it should be init with new data + playbackCamera->Spawn(); + g_currentPlayback.ClearModified(); + } + + if( !g_playPlayback.GetInteger() ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } + + return( true ); +} + +// ================================================================================================ + +void idGameEdit::ShutdownPlaybacks( void ) +{ + g_recordPlayback.SetInteger( 0 ); + g_playPlayback.SetInteger( 0 ); + + if( gamePlayback ) + { + delete gamePlayback; + gamePlayback = NULL; + } + + if( playbackCamera ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } +} + +// ================================================================================================ + +/* +============ +rvPlaybackDriver::Start + +Start a new playback automatically blending with the old playback (if any) over numFrames +============ +*/ +bool rvPlaybackDriver::Start( const char *playback, idEntity *owner, int flags, int numFrames ) +{ + idVec3 startPos; + + if( !idStr::Length( playback ) ) + { + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + return( true ); + } + + const rvDeclPlayback *pb = declManager->FindPlayback( playback ); + + if( g_showPlayback.GetInteger() ) + { + common->Printf( "Starting playback: %s\n", pb->GetName() ); + } + + mOldPlaybackDecl = mPlaybackDecl; + mOldFlags = mFlags; + mOldStartTime = mStartTime; + mOldOffset = mOffset; + + mPlaybackDecl = pb; + mFlags = flags; + mStartTime = gameLocal.time; + mOffset.Zero(); + + if( flags & PBFL_RELATIVE_POSITION ) + { + mOffset = owner->GetPhysics()->GetOrigin() - pb->GetOrigin(); + } + + mTransitionTime = numFrames * gameLocal.GetMSec(); + return( true ); +} + +/* +============ +PlaybackCallback + +Called whenever a button up or down, or an impulse event is found while getting the playback data +============ +*/ +void PlaybackCallback( int type, float time, const void *data ) +{ + const rvDeclPlaybackData *pbd = ( const rvDeclPlaybackData * )data; + idEntity *ent = pbd->GetEntity(); + + ent->PostEventSec( &EV_PlaybackCallback, time, type, pbd->GetChanged(), pbd->GetImpulse() ); +} + +/* +============ +rvPlaybackDriver::UpdateFrame + +Blend two playbacks together +============ +*/ +bool rvPlaybackDriver::UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ) +{ + rvDeclPlaybackData pbd, oldPbd; + float blend, invBlend; + idStr ret; + bool expired, oldExpired; + + // Get the current playback position + pbd.Init(); + pbd.SetCallback( ent, PlaybackCallback ); + expired = declManager->GetPlaybackData( mPlaybackDecl, mFlags, gameLocal.time - mStartTime, mLastTime - mStartTime, &pbd ); + pbd.SetPosition( pbd.GetPosition() + mOffset ); + + // Get the playback data we are merging from + oldPbd.Init(); + oldExpired = declManager->GetPlaybackData( mOldPlaybackDecl, mOldFlags, gameLocal.time - mOldStartTime, gameLocal.time - mOldStartTime, &oldPbd ); + oldPbd.SetPosition( oldPbd.GetPosition() + mOldOffset ); + + mLastTime = gameLocal.time; + + if( g_showPlayback.GetInteger() && mPlaybackDecl ) + { + common->Printf( "Running playback: %s at %.1f\n", mPlaybackDecl->GetName(), MS2SEC( gameLocal.time - mStartTime ) ); + } + + // Fully merged - so delete the old one + if( gameLocal.time > mStartTime + mTransitionTime ) + { + oldExpired = true; + } + + // Interpolate the result + if( expired && oldExpired ) + { + out.Init(); + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + } + else if( !expired && oldExpired ) + { + out = pbd; + mOldPlaybackDecl = NULL; + } + else if( expired && !oldExpired ) + { + out = oldPbd; + mPlaybackDecl = NULL; + } + else + { + // Linear zero to one + blend = idMath::ClampFloat( 0.0f, 1.0f, ( gameLocal.time - mStartTime ) / ( float )mTransitionTime ); + + // Sinusoidal + blend = idMath::Sin( blend * idMath::HALF_PI ); + invBlend = 1.0f - blend; + + out.SetPosition( blend * pbd.GetPosition() + invBlend * oldPbd.GetPosition() ); + out.SetAngles( blend * pbd.GetAngles() + invBlend * oldPbd.GetAngles() ); + out.SetButtons( pbd.GetButtons() ); + out.SetImpulse( pbd.GetImpulse() ); + } + + return( expired ); +} + +// cnicholson: Begin Added save/restore functionality +/* +============ +rvPlaybackDriver::Save + +Save all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( mLastTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mTransitionTime ); // cnicholson: Added unsaved var + + savefile->WriteInt( mStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->WriteVec3( mOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt( mOldStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mOldFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->WriteVec3( mOldOffset ); // cnicholson: Added unsaved var +} + +/* +============ +rvPlaybackDriver::Restore + +Restore all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( mLastTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mTransitionTime ); // cnicholson: Added unrestored var + + savefile->ReadInt( mStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->ReadVec3( mOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt( mOldStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mOldFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->ReadVec3( mOldOffset ); // cnicholson: Added unrestored var +} +// cnicholson: End Added save/restore functionality + +// end + diff --git a/source/game/Player.cpp b/source/game/Player.cpp new file mode 100644 index 0000000..d292cee --- /dev/null +++ b/source/game/Player.cpp @@ -0,0 +1,14077 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "Weapon.h" +#include "Projectile.h" +#include "vehicle/Vehicle.h" +#include "client/ClientModel.h" +#include "ai/AAS_tactical.h" +#include "Healing_Station.h" +#include "ai/AI_Medic.h" + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off +#ifdef _XENON +#include "../ui/Window.h" + +// nrausch: support for direct button input +#include "../sys/xenon/xen_input.h" +#endif +// RAVEN END + +idCVar net_predictionErrorDecay( "net_predictionErrorDecay", "112", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "time in milliseconds it takes to fade away prediction errors", 0.0f, 200.0f ); +idCVar net_showPredictionError( "net_showPredictionError", "-1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "show prediction errors for the given client", -1, MAX_CLIENTS ); + + +/* +=============================================================================== + + Player control. + This object handles all player movement and world interaction. + +=============================================================================== +*/ + +#ifdef _XENON +bool g_ObjectiveSystemOpen = false; +#endif + +// distance between ladder rungs (actually is half that distance, but this sounds better) +const int LADDER_RUNG_DISTANCE = 32; + +// amount of health per dose from the health station +const int HEALTH_PER_DOSE = 10; + +// time before a weapon dropped to the floor disappears +const int WEAPON_DROP_TIME = 20 * 1000; + +// time before a next or prev weapon switch happens +const int WEAPON_SWITCH_DELAY = 150; + +const float PLAYER_ITEM_DROP_SPEED = 100.0f; + +// how many units to raise spectator above default view height so it's in the head of someone +const int SPECTATE_RAISE = 25; + +const int HEALTH_PULSE = 1000; // Regen rate and heal leak rate (for health > 100) +const int ARMOR_PULSE = 1000; // armor ticking down due to being higher than maxarmor +const int AMMO_REGEN_PULSE = 1000; // ammo regen in Arena CTF +const int POWERUP_BLINKS = 5; // Number of times the powerup wear off sound plays +const int POWERUP_BLINK_TIME = 1000; // Time between powerup wear off sounds +const float MIN_BOB_SPEED = 5.0f; // minimum speed to bob and play run/walk animations at +const int MAX_RESPAWN_TIME = 10000; +const int RAGDOLL_DEATH_TIME = 3000; +#ifdef _XENON + const int RAGDOLL_DEATH_TIME_XEN_SP = 1000; + const int MAX_RESPAWN_TIME_XEN_SP = 3000; +#endif +const int STEPUP_TIME = 200; +const int MAX_INVENTORY_ITEMS = 20; + +const int ARENA_POWERUP_MASK = ( 1 << POWERUP_AMMOREGEN ) | ( 1 << POWERUP_GUARD ) | ( 1 << POWERUP_DOUBLER ) | ( 1 << POWERUP_SCOUT ); + +//const idEventDef EV_Player_HideDatabaseEntry ( "", NULL ); +const idEventDef EV_Player_ZoomIn ( "" ); +const idEventDef EV_Player_ZoomOut ( "" ); + +const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' ); +const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' ); +const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' ); +const idEventDef EV_Player_SetViewAngles( "setViewAngles", "v" ); +const idEventDef EV_Player_StopFxFov( "stopFxFov" ); +const idEventDef EV_Player_EnableWeapon( "enableWeapon" ); +const idEventDef EV_Player_DisableWeapon( "disableWeapon" ); +const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' ); +const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' ); +const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" ); +const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' ); +const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" ); +const idEventDef EV_Player_HideTip( "hideTip" ); +const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); +const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); +const idEventDef EV_Player_GetViewPos("getViewPos", NULL, 'v'); +const idEventDef EV_Player_FinishHearingLoss ( "", "f" ); +const idEventDef EV_Player_GetAmmoData( "getAmmoData", "s", 'v'); +const idEventDef EV_Player_RefillAmmo( "refillAmmo" ); +const idEventDef EV_Player_SetExtraProjPassEntity( "setExtraProjPassEntity", "E" ); +const idEventDef EV_Player_SetArmor( "setArmor", "f" ); +const idEventDef EV_Player_DamageEffect( "damageEffect", "sE" ); +const idEventDef EV_Player_AllowFallDamage( "allowFallDamage", "d" ); + +// mekberg: allow enabling/disabling of objectives +const idEventDef EV_Player_EnableObjectives( "enableObjectives" ); +const idEventDef EV_Player_DisableObjectives( "disableObjectives" ); + +// mekberg: don't suppress showing of new objectives anymore +const idEventDef EV_Player_AllowNewObjectives( "" ); + +// RAVEN END + +CLASS_DECLARATION( idActor, idPlayer ) +// EVENT( EV_Player_HideDatabaseEntry, idPlayer::Event_HideDatabaseEntry ) + EVENT( EV_Player_ZoomIn, idPlayer::Event_ZoomIn ) + EVENT( EV_Player_ZoomOut, idPlayer::Event_ZoomOut ) + EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) + EVENT( EV_Player_GetMove, idPlayer::Event_GetMove ) + EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles ) + EVENT( EV_Player_SetViewAngles, idPlayer::Event_SetViewAngles ) + EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov ) + EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon ) + EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon ) + EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon ) + EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon ) + EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity ) + EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter ) + EVENT( EV_Player_HideTip, idPlayer::Event_HideTip ) + EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) + EVENT( EV_Player_GetViewPos, idPlayer::Event_GetViewPos ) + EVENT( EV_Player_FinishHearingLoss, idPlayer::Event_FinishHearingLoss ) + EVENT( EV_Player_GetAmmoData, idPlayer::Event_GetAmmoData ) + EVENT( EV_Player_RefillAmmo, idPlayer::Event_RefillAmmo ) + EVENT( EV_Player_AllowFallDamage, idPlayer::Event_AllowFallDamage ) + + +// mekberg: allow enabling/disabling of objectives + EVENT ( EV_Player_EnableObjectives, idPlayer::Event_EnableObjectives ) + EVENT ( EV_Player_DisableObjectives, idPlayer::Event_DisableObjectives ) + +// mekberg: don't suppress showing of new objectives anymore + EVENT ( EV_Player_AllowNewObjectives, idPlayer::Event_AllowNewObjectives ) +// RAVEN END + + EVENT( AI_EnableTarget, idPlayer::Event_EnableTarget ) + EVENT( AI_DisableTarget, idPlayer::Event_DisableTarget ) + + EVENT( EV_ApplyImpulse, idPlayer::Event_ApplyImpulse ) + +// RAVEN BEGIN +// mekberg: sethealth on player. + EVENT( AI_SetHealth, idPlayer::Event_SetHealth ) +//MCG: setArmor + EVENT( EV_Player_SetArmor, idPlayer::Event_SetArmor ) +// RAVEN END; + EVENT( EV_Player_SetExtraProjPassEntity,idPlayer::Event_SetExtraProjPassEntity ) +//MCG: direct damage + EVENT( EV_Player_DamageEffect, idPlayer::Event_DamageEffect ) +END_CLASS + +// RAVEN BEGIN +// asalmon: Xenon weapon combo system +#ifdef _XENON +nextWeaponCombo_t weaponComboChart[12] = { + // up, down, left, right + {1,3,2,4}, // 0: empty slot (select none) + {10,0,1,1}, + {2,2,5,0}, + {0,7,3,3}, + {4,4,0,9}, + {5,5,6,2}, + {6,6,6,5}, + {3,7,7,7}, + {8,8,9,8}, + {9,9,4,8}, + {10,1,10,10}, + {0,0,0,0} +}; +#endif +// RAVEN END + +const idVec4 marineHitscanTint( 0.69f, 1.0f, 0.4f, 1.0f ); +const idVec4 stroggHitscanTint( 1.0f, 0.5f, 0.0f, 1.0f ); +const idVec4 defaultHitscanTint( 0.4f, 1.0f, 0.4f, 1.0f ); + +/* +============== +idInventory::Clear +============== +*/ +void idInventory::Clear( void ) { + maxHealth = 0; + weapons = 0; + carryOverWeapons = 0; + powerups = 0; + armor = 0; + maxarmor = 0; + secretAreasDiscovered = 0; + + memset( ammo, 0, sizeof( ammo ) ); + + ClearPowerUps(); + + memset( weaponMods, 0, sizeof(weaponMods) ); + + // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level + memset( clip, -1, sizeof( clip ) ); + + items.DeleteContents( true ); + pdas.Clear(); + videos.Clear(); + + levelTriggers.Clear(); + + nextItemPickup = 0; + nextItemNum = 1; + onePickupTime = 0; + objectiveNames.Clear(); + + ammoPredictTime = 0; + lastGiveTime = 0; + + memset( ammoRegenStep, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoIndices, -1, sizeof( int ) * MAX_WEAPONS ); + memset( startingAmmo, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoRegenTime, -1, sizeof( int ) * MAX_WEAPONS ); +} + +/* +============== +idInventory::GivePowerUp +============== +*/ +void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) { + powerups |= 1 << powerup; + powerupEndTime[ powerup ] = msec == -1 ? -1 : (gameLocal.time + msec); +} + +/* +============== +idInventory::ClearPowerUps +============== +*/ +void idInventory::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + powerupEndTime[ i ] = 0; + } + powerups = 0; +} + +/* +============== +idInventory::GetPersistantData +============== +*/ +void idInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // armor + dict.SetInt( "armor", armor ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex( i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + // items + num = 0; + for( i = 0; i < items.Num(); i++ ) { + item = items[ i ]; + + // copy all keys with "inv_" + kv = item->MatchPrefix( "inv_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "inv_", kv ); + } + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + dict.SetInt( va( "weapon_mods_%i", i ), weaponMods[ i ] ); + } + + dict.SetInt( "levelTriggers", levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + sprintf( key, "levelTrigger_Level_%i", i ); + dict.Set( key, levelTriggers[i].levelName ); + sprintf( key, "levelTrigger_Trigger_%i", i ); + dict.Set( key, levelTriggers[i].triggerName ); + } +} + +/* +============== +idInventory::RestoreInventory +============== +*/ +void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + //We might not need to clear it out. + //Clear(); + + // health/armor + maxHealth = dict.GetInt( "maxhealth", "100" ); + armor = dict.GetInt( "armor", "50" ); + maxarmor = dict.GetInt( "maxarmor", "100" ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex ( i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + // items + num = dict.GetInt( "items" ); + items.SetNum( num ); + for( i = 0; i < num; i++ ) { + item = new idDict(); + items[ i ] = item; + sprintf( itemname, "item_%i ", i ); + kv = dict.MatchPrefix( itemname ); + while( kv ) { + key = kv->GetKey(); + key.Strip( itemname ); + item->Set( key, kv->GetValue() ); + kv = dict.MatchPrefix( itemname, kv ); + } + } + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + +// RAVEN BEGIN +// mekberg: removed nightmare weapon check. + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); +// RAVEN END + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + weaponMods[ i ] = dict.GetInt( va( "weapon_mods_%i", i ) ); + } + // forcefully invalidate the weapon + owner->GiveWeaponMods( 0 ); + + num = dict.GetInt( "levelTriggers" ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "levelTrigger_Level_%i", i ); + idLevelTriggerInfo lti; + lti.levelName = dict.GetString( itemname ); + sprintf( itemname, "levelTrigger_Trigger_%i", i ); + lti.triggerName = dict.GetString( itemname ); + levelTriggers.Append( lti ); + } + +} + +/* +============== +idInventory::Save +============== +*/ +void idInventory::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( maxHealth ); + savefile->WriteInt( weapons ); + savefile->WriteInt( powerups ); + savefile->WriteInt( armor ); + savefile->WriteInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->WriteInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->WriteInt( clip[ i ] ); + savefile->WriteInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->WriteInt( powerupEndTime[ i ] ); + } + + savefile->WriteInt( ammoPredictTime ); + savefile->WriteInt( lastGiveTime ); + + // Save Items + savefile->WriteInt( items.Num() ); + for( i = 0; i < items.Num(); i++ ) { + savefile->WriteDict( items[ i ] ); + } + + // TOSAVE: idStrList pdas; + // TOSAVE: idStrList pdaSecurity; + // TOSAVE: idStrList videos; + + // Save level triggers + savefile->WriteInt( levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + savefile->WriteString( levelTriggers[i].levelName ); + savefile->WriteString( levelTriggers[i].triggerName ); + } + + savefile->WriteInt( nextItemPickup ); + savefile->WriteInt( nextItemNum ); + savefile->WriteInt( onePickupTime ); + + // Save pick up item names + savefile->WriteInt( pickupItemNames.Num() ); + for( i = 0; i < pickupItemNames.Num(); i++ ) { + savefile->WriteString( pickupItemNames[ i ].name ); + savefile->WriteString( pickupItemNames[ i ].icon ); + } + + // Save objectives + savefile->WriteInt( objectiveNames.Num() ); + for( i = 0; i < objectiveNames.Num(); i++ ) { + savefile->WriteString( objectiveNames[i].screenshot ); + savefile->WriteString( objectiveNames[i].text ); + savefile->WriteString( objectiveNames[i].title ); + } +/* + // Save database + savefile->WriteInt ( database.Num() ); + for ( i = 0; i < database.Num(); i ++ ) { + savefile->WriteString ( database[i].title ); + savefile->WriteString ( database[i].text ); + savefile->WriteString ( database[i].image ); + savefile->WriteString ( database[i].filter ); + } +*/ + savefile->WriteInt( secretAreasDiscovered ); + + savefile->WriteSyncId(); +} + +/* +============== +idInventory::Restore +============== +*/ +void idInventory::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( maxHealth ); + savefile->ReadInt( weapons ); + savefile->ReadInt( powerups ); + savefile->ReadInt( armor ); + savefile->ReadInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->ReadInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->ReadInt( clip[ i ] ); + savefile->ReadInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->ReadInt( powerupEndTime[ i ] ); + } + + savefile->ReadInt( ammoPredictTime ); + savefile->ReadInt( lastGiveTime ); + + // Load Items + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idDict *itemdict = new idDict; + + savefile->ReadDict( itemdict ); + items.Append( itemdict ); + } + + // TORESTORE: idStrList pdas; + // TORESTORE: idStrList pdaSecurity; + // TORESTORE: idStrList videos; + + // Load level triggers + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idLevelTriggerInfo lti; + savefile->ReadString( lti.levelName ); + savefile->ReadString( lti.triggerName ); + levelTriggers.Append( lti ); + } + + savefile->ReadInt( nextItemPickup ); + savefile->ReadInt( nextItemNum ); + savefile->ReadInt( onePickupTime ); + + // Load pickup items + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idItemInfo itemInfo; + savefile->ReadString( itemInfo.name ); + savefile->ReadString( itemInfo.icon ); + pickupItemNames.Append( itemInfo ); + } + + // Load objectives + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idObjectiveInfo obj; + savefile->ReadString( obj.screenshot ); + savefile->ReadString( obj.text ); + savefile->ReadString( obj.title ); + objectiveNames.Append( obj ); + } +/* + // Load database + savefile->ReadInt ( num ); + for ( i = 0; i < num; i++ ) { + rvDatabaseEntry entry; + savefile->ReadString ( entry.title ); + savefile->ReadString ( entry.text ); + savefile->ReadString ( entry.image ); + savefile->ReadString ( entry.filter ); + database.Append ( entry ); + } +*/ + savefile->ReadInt( secretAreasDiscovered ); + + savefile->ReadSyncId( "idInventory::Restore" ); +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +int idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const { + return rvWeapon::GetAmmoIndexForName( ammo_classname ); +} + +/* +============== +idInventory::MaxAmmoForAmmoClass +============== +*/ +int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); +} + +/* +============== +idInventory::AmmoIndexForWeaponClass +============== +*/ +int idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + if ( ammoRequired ) { + *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + } + return AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); +} + +/* +============== +idInventory::AmmoClassForWeaponClass +============== +*/ +const char * idInventory::AmmoClassForWeaponClass( const char *weapon_classname ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + + return decl->dict.GetString( "ammoType" ); +} + +// RAVEN BEGIN +// mekberg: if the player can pick up ammo at this time +/* +============== +idInventory::DetermineAmmoAvailability +============== +*/ +bool idInventory::DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ) { + const idDeclEntityDef *weaponDecl = NULL; + const idDict* weaponDict = NULL; + const char* mod = NULL; + const idDict* modDict = NULL; + int weaponIndex = -1; + int clipSize = 0; + int modClipSize = 0; + int difference = 0; + idStr realAmmoName( ammoName ); + + // Early out + if ( ammo[ ammoIndex ] == ammoMax ) { + return false; + } + + // Make sure the clip info is updated. + if ( owner->weapon ) { + clip[ owner->GetCurrentWeapon( ) ] = owner->weapon->AmmoInClip( ); + } + + if ( !idStr::Icmpn( ammoName, "start_ammo_", 11 ) ) { + realAmmoName.StripLeading( "start_" ); + } + + // Find the entityDef for the weapon that uses this ammo. + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + + weaponDecl = owner->GetWeaponDef( i ); + + if ( !weaponDecl ) { + continue; + } + + if ( !idStr::Icmp ( weaponDecl->dict.GetString( "ammoType" ), realAmmoName.c_str() ) ) { + weaponDict = &( weaponDecl->dict ); + weaponIndex = i; + break; + } + } + + // If we didn't find one. + if ( weaponIndex == -1 ) { + // If we are picking up ammo and we aren't currently full. + if ( ammoAmount && ammo[ ammoIndex ] != ammoMax ) { + ammo[ ammoIndex ] += ammoAmount; + if ( ammo[ ammoIndex ] > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } + return true; + } + return false; + } + + clipSize = weaponDict->GetInt( "clipSize", "0" ); + + // Find the weaponmods for this weapon and see if we have any clipsize mods. + for ( int m = 0; m < MAX_WEAPONMODS; m ++ ) { + if ( ! ( weaponMods[ weaponIndex ] & ( 1 << m ) ) ) { + continue; + } + + mod = weaponDict->GetString ( va ( "def_mod%d" , m + 1 ) ); + if ( !mod || !*mod ) { + break; + } + + modDict = gameLocal.FindEntityDefDict ( mod, false ); + modClipSize = modDict->GetInt( "clipSize", "0" ); + + if ( modClipSize > clipSize ) { + clipSize = modClipSize; + } + } + + // Don't bother with these checks if we don't have a clipsize + if ( clipSize ) { + difference = ( ammoMax - clipSize ) - ( ammo[ ammoIndex ] - clip[ weaponIndex ] ); + + if ( difference ) { + if ( ammoAmount > difference ) { + ammo[ ammoIndex ] += difference; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; + } else { + return false; + } + } else if ( ( ammo[ ammoIndex ] + ammoAmount ) > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; +} +// RAVEN END + + +/* +============== +idInventory::AmmoIndexForWeaponIndex +============== +*/ +int idInventory::AmmoIndexForWeaponIndex( int weaponIndex ) { + if( ammoIndices[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoIndexForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + ammoIndices[ weaponIndex ] = AmmoIndexForWeaponClass( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ) ); + } + + return ammoIndices[ weaponIndex ]; +} + +/* +============== +idInventory::StartingAmmoForWeaponIndex +============== +*/ +int idInventory::StartingAmmoForWeaponIndex( int weaponIndex ) { + if( startingAmmo[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::StartingAmmoForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + const idKeyValue* kv = weaponDict->MatchPrefix( "inv_start_ammo" ); + if( kv == NULL ) { + startingAmmo[ weaponIndex ] = 1; + } else { + startingAmmo[ weaponIndex ] = atoi( kv->GetValue() ); + kv = weaponDict->MatchPrefix( "inv_start_ammo", kv ); + if( kv != NULL ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Weapon dict for player's def_weapon%d has multiple inv_start_ammo entries\n", weaponIndex ); + return -1; + } + } + } + + return startingAmmo[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenStepForWeaponIndex +============== +*/ +int idInventory::AmmoRegenStepForWeaponIndex( int weaponIndex ) { + if( ammoRegenStep[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenStepForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenStepForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenStep[ weaponIndex ] = weaponDict->GetInt( "ammoRegenStep", "1" ); + } + + return ammoRegenStep[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenTimeForAmmoIndex +============== +*/ +int idInventory::AmmoRegenTimeForWeaponIndex( int weaponIndex ) { + if ( ammoRegenTime[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenTimeForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenTimeForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenTime[ weaponIndex ] = weaponDict->GetInt( "ammoRegenTime", "1" ); + } + + return ammoRegenTime[ weaponIndex ]; +} + + +/* +============== +idInventory::Give +If checkOnly is true, check only for possibility of adding to inventory, don't actually add +============== +*/ +bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped, bool checkOnly ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + int amount; + + if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( ammo[ i ] >= max ) { + return false; + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmpn( statname, "start_ammo_", 11 ) ) { + // starting ammo gives only if current ammo is below it + idStr ammoname( statname ); + ammoname.StripLeading( "start_" ); + i = AmmoIndexForAmmoClass( ammoname.c_str() ); + max = MaxAmmoForAmmoClass( owner, ammoname.c_str() ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( amount ) { + if ( ammo[ i ] >= amount ) { + amount = 1; + } else { + amount = amount - ammo[ i ]; + } + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor * 2 ) { + return false; + } + } else if ( !idStr::Icmp( statname, "health" ) ) { + if ( owner->health >= maxHealth ) { + return false; + } + } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { + i = owner->SlotForWeapon ( statname + 7 ); + if ( i != -1 && !checkOnly ) { + // set, don't add. not going over the clip size limit. + clip[ i ] = atoi( value ); + } + } else if ( !idStr::Icmp( statname, "quad" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_QUADDAMAGE, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "regen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_REGENERATION, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "haste" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_HASTE, SEC2MS( atof( value ) ) ); + } else if( !idStr::Icmp( statname, "ammoregen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_AMMOREGEN, -1 ); + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + bool tookWeapon = false; + for( pos = value; pos != NULL; pos = end ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponName( pos, 0, len ); + + // find the number of the matching weapon names + i = owner->SlotForWeapon ( weaponName ); +// RAVEN BEGIN +// mekberg: check for not found weapons + if ( i == -1 ) { + gameLocal.Warning( "Unknown weapon '%s'", weaponName.c_str() ); + return false; + } +// RAVEN END + + if ( gameLocal.isMultiplayer + && ( weapons & ( 1 << i ) ) ) { + //already have this weapon + if ( !dropped ) { + //a placed weapon item + if ( gameLocal.IsWeaponsStayOn() ) { + //don't pick up weapons at all if you already have them... + continue; + } + } + // don't pickup "no ammo" weapon types twice + // not for singleplayer.. there is only one case in the game where you can get a no ammo + // weapon when you might already have it, in that case it is more consistent to pick it up + // cache the media for this weapon + const idDict* dict; + dict = &owner->GetWeaponDef ( i )->dict; + if ( dict && !dict->GetInt( "ammoRequired" ) ) { + continue; + } + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) ) { + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) + || ( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) ) + && idealWeapon && !checkOnly ) + { + // client prediction should not get here + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time && !checkOnly ) { + owner->hud->SetStateInt( "newWeapon", i ); + owner->hud->HandleNamedEvent( "newWeapon" ); + lastGiveTime = gameLocal.time; + } + if( !checkOnly ) { + weapons |= ( 1 << i ); + } + tookWeapon = true; + } + } + } + return tookWeapon; + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + return false; + } else { + // unknown item + gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname ); + return false; + } + + return true; +} + +/* +=============== +idInventoy::Drop +=============== +*/ +void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) { + // remove the weapon bit + // also remove the ammo associated with the weapon as we pushed it in the item + assert( weapon_index != -1 || weapon_classname ); + if ( weapon_index == -1 ) { + for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) { + if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) { + break; + } + } + + if ( weapon_index >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weapon_classname ); + } + } else if ( !weapon_classname ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ); + } + weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) ); + int ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL ); + if ( ammo_i ) { + clip[ weapon_index ] = -1; + ammo[ ammo_i ] = 0; + } + + weaponMods[weapon_index] = 0; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( int index, int amount ) { + if ( ( index == 0 ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ index ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ index ] / amount; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + int index; + index = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( index, ammoRequired ); +} + +/* +=============== +idInventory::UseAmmo +=============== +*/ +bool idInventory::UseAmmo( int index, int amount ) { + if ( !HasAmmo( index, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ index ] >= 0 ) { + ammo[ index ] -= amount; + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +/* +============== +idPlayer::idPlayer +============== +*/ +idPlayer::idPlayer() { + memset( &usercmd, 0, sizeof( usercmd ) ); + + alreadyDidTeamAnnouncerSound = false; + + doInitWeapon = false; + noclip = false; + godmode = false; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + spawnAnglesSet = false; + spawnAngles = ang_zero; + viewAngles = ang_zero; + deltaViewAngles = ang_zero; + cmdAngles = ang_zero; + + demoViewAngleTime = 0; + demoViewAngles = ang_zero; + + oldButtons = 0; + buttonMask = 0; + oldFlags = 0; + + lastHitTime = 0; + lastSavingThrowTime = 0; + + weapon = NULL; + + hud = NULL; + mphud = NULL; + objectiveSystem = NULL; + objectiveSystemOpen = false; + showNewObjectives = false; +#ifdef _XENON + g_ObjectiveSystemOpen = false; +#endif + objectiveButtonReleased = false; + cinematicHud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + lastDmgTime = 0; + deathClearContentsTime = 0; + nextHealthPulse = 0; + + scoreBoardOpen = false; + forceScoreBoard = false; + forceScoreBoardTime = 0; + forceRespawn = false; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// squirrel: Mode-agnostic buymenus + inBuyZone = false; + inBuyZonePrev = false; +// RITUAL END + spectating = false; + spectator = 0; + forcedReady = false; + wantSpectate = false; + + lastHitToggle = false; + lastArmorHit = false; + + minRespawnTime = 0; + maxRespawnTime = 0; + + firstPersonViewOrigin = vec3_zero; + firstPersonViewAxis = mat3_identity; + + hipJoint = INVALID_JOINT; + chestJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + + bobFoot = 0; + bobFrac = 0.0f; + bobfracsin = 0.0f; + bobCycle = 0; + xyspeed = 0.0f; + stepUpTime = 0; + stepUpDelta = 0.0f; + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + viewBobAngles = ang_zero; + viewBob = vec3_zero; + landChange = 0; + landTime = 0; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + carryOverCurrentWeapon = -1; +// RITUAL END + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = true; + oldInventoryWeapons = 0; + +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; +// RAVEN END + + skin = NULL; + weaponViewSkin = NULL; + headSkin = NULL; + powerUpSkin = NULL; + + numProjectilesFired = 0; + numProjectileHits = 0; + + airless = false; + airTics = 0; + lastAirDamage = 0; + + gibDeath = false; + gibsLaunched = false; + gibDir = vec3_zero; + + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + privateCameraView = NULL; + + memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) ); + memset( loggedAccel, 0, sizeof( loggedAccel ) ); + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + + cursor = NULL; + talkCursor = 0; + + oldMouseX = 0; + oldMouseY = 0; + + lastDamageDef = 0; + lastDamageDir = vec3_zero; + lastDamageLocation = 0; + + predictedFrame = 0; + predictedOrigin = vec3_zero; + predictedAngles = ang_zero; + predictedUpdated = false; + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + predictionErrorTime = 0; + + fl.networkSync = true; + + latchedTeam = -1; + hudTeam = -1; + doingDeathSkin = false; + weaponGone = false; + useInitialSpawns = false; + lastSpectateTeleport = 0; + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + lastKiller = NULL; + + respawning = false; + ready = false; + leader = false; + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + weaponCatchup = false; + lastSnapshotSequence = 0; + + aimClientNum = -1; + + spawnedTime = 0; + + isTelefragged = false; + isLagged = false; + isChatting = false; + + intentDir.Zero(); + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + ResetCash(); +// RITUAL END + + zoomFov.Init ( 0, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + + memset ( cachedWeaponDefs, 0, sizeof(cachedWeaponDefs) ); + memset ( cachedPowerupDefs, 0, sizeof(cachedPowerupDefs) ); + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time; + + weaponChangeIconsUp = false; + + reloadModel = false; + modelDecl = NULL; + + disableHud = false; + + mutedPlayers = 0; + friendPlayers = 0; + connectTime = 0; + rank = -1; + arena = 0; + + memset( nextAmmoRegenPulse, 0, sizeof( int ) * MAX_AMMO ); + + spectator = 0; + + quadOverlay = NULL; + hasteOverlay = NULL; + regenerationOverlay = NULL; + invisibilityOverlay = NULL; + powerUpOverlay = NULL; + + tourneyStatus = PTS_UNKNOWN; + + vsMsgState = false; + + deathSkinTime = 0; + + jumpDuringHitch = false; + + lastPickupTime = 0; + + int i; + + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + voiceDest[i] = -1; + voiceDestTimes[i] = 0; + } + + itemCosts = NULL; + + teamHealthRegen = NULL; + teamHealthRegenPending = false; + teamAmmoRegen = NULL; + teamAmmoRegenPending = false; + teamDoubler = NULL; + teamDoublerPending = false; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +void idPlayer::SetShowHud( bool showHud ) { + disableHud = !showHud; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +bool idPlayer::GetShowHud( void ) { + return !disableHud; +} + +/* +============== +idPlayer::SetWeapon +============== +*/ +void idPlayer::SetWeapon( int weaponIndex ) { + if ( weapon && weaponIndex == currentWeapon ) { + return; + } + + // Clear the weapon entity + delete weapon; + weapon = NULL; + + previousWeapon = currentWeapon; + currentWeapon = weaponIndex; + weaponGone = false; + + if ( weaponIndex < 0 ) { + weaponGone = true; + return; + } + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + + idTypeInfo* typeInfo; + weaponDef = GetWeaponDef( currentWeapon ); + if ( !weaponDef ) { + gameLocal.Error( "Weapon definition not found for weapon %d", currentWeapon ) ; + } + typeInfo = idClass::GetClass( weaponDef->dict.GetString( "weaponclass", "rvWeapon" ) ); + if ( !typeInfo || !typeInfo->IsType( rvWeapon::GetClassType() ) ) { + gameLocal.Error( "Invalid weapon class '%s' specified for weapon '%s'", animPrefix.c_str(), weaponDef->dict.GetString ( "weaponclass", "rvWeapon" ) ); + } + weapon = static_cast( typeInfo->CreateInstance() ); + weapon->Init( this, weaponDef, currentWeapon, isStrogg ); + weapon->CallSpawn( ); + + // Reset the zoom fov on weapon change + if ( zoomed ) { + zoomFov.Init ( gameLocal.time, 100, CalcFov(true), DefaultFov() ); + zoomed = false; + } + + UpdateHudWeapon(); + + // Remove the "weapon_" from the anim prefect for the player world anims + animPrefix.Strip( "weapon_" ); + + // Make sure weapon is hidden + if ( !weaponEnabled ) { + Event_DisableWeapon( ); + } +} + +/* +============== +idPlayer::SetupWeaponEntity +============== +*/ +void idPlayer::SetupWeaponEntity( void ) { + int w; + const char *weap; + const idDeclEntityDef *decl; + idEntity *spawn; + + // don't setup weapons for spectators + if ( gameLocal.isClient || (weaponViewModel && weaponWorldModel) || spectating ) { + return; + } + + idDict args; + + if ( !weaponViewModel ) { + // setup the view model +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_viewweapon", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_viewweapon" ); + } + args.Set( "name", va( "%s_weapon", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponViewModel" ); + } + weaponViewModel = static_cast(spawn); + weaponViewModel->SetName( va("%s_weapon", name.c_str() ) ); + weaponViewModel->SetInstance( instance ); + } + + + if ( !weaponWorldModel ) { + // setup the world model + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_animatedentity", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_animatedentity" ); + } + args.Set( "name", va( "%s_weapon_world", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponWorldModel" ); + } + weaponWorldModel = static_cast(spawn); + weaponWorldModel->fl.networkSync = true; + weaponWorldModel->SetName ( va("%s_weapon_world", name.c_str() ) ); + weaponWorldModel->SetInstance( instance ); + } + + currentWeapon = -1; + + weaponWorldModel->fl.persistAcrossInstances = true; + weaponViewModel->fl.persistAcrossInstances = true; + + for( w = 0; w < MAX_WEAPONS; w++ ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap && *weap ) { + rvWeapon::CacheWeapon( weap ); + } + } +} + +/* +============== +idPlayer::Init +============== +*/ +void idPlayer::Init( void ) { + const char *value; + + noclip = false; + godmode = false; + godmodeDamage = 0; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + oldButtons = 0; + oldFlags = 0; + + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + oldInventoryWeapons = 0; + + lastDmgTime = 0; + + bobCycle = 0; + bobFrac = 0.0f; + landChange = 0; + landTime = 0; + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + talkCursor = 0; + + lightningEffects = 0; + lightningNextTime = 0; + + modelName = idStr(); + + // Remove any hearing loss that may be set up from the last map + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0 ); + + // remove any damage effects + playerView.ClearEffects(); + + // damage values + fl.takedamage = true; + ClearPain(); + + // restore persistent data + RestorePersistantInfo(); + + bobCycle = 0; + + SetupWeaponEntity( ); + currentWeapon = -1; + previousWeapon = -1; + + flashlightOn = false; + + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + +// RAVEN BEGIN +// abahr: need to init this + vehicleCameraDist = 0.0f; + +// mekberg: moved into function. + SetPMCVars( ); +// RAVEN END + + // air always initialized to maximum too + airTics = pm_airTics.GetFloat(); + airless = false; + + gibDeath = false; + gibsLaunched = false; + gibDir.Zero(); + +// RAVEN BEGIN +// abahr: changed to GetCurrentGravity + // set the gravity + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); +// RAVEN END + + // start out standing + SetEyeHeight( pm_normalviewheight.GetFloat() ); + + stepUpTime = 0; + stepUpDelta = 0.0f; + viewBobAngles.Zero(); + viewBob.Zero(); + + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + value = spawnArgs.GetString( va( "model_%s", team ? "strogg" : "marine" ), NULL ); + } + else { + value = spawnArgs.GetString( "model" ); + } + + if( gameLocal.isMultiplayer ) { + UpdateModelSetup( true ); + } else { + if ( value && ( *value != 0 ) ) { + SetModel( value ); + } + // check head + if( idStr::Icmp( head ? head->spawnArgs.GetString( "classname", "" ) : "", spawnArgs.GetString( "def_head", "" ) ) ) { + SetupHead(); + } + } + + if ( cursor ) { + cursor->SetStateInt( "talkcursor", 0 ); + cursor->HandleNamedEvent( "showCrossCombat" ); + } + + if ( !gameLocal.isMultiplayer ) { + if ( g_testDeath.GetBool() && skin ) { + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) { + skin = declManager->FindSkin( value ); + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + if( clientHead->GetModelDefHandle() > 0) { + gameRenderWorld->RemoveDecals( clientHead->GetModelDefHandle() ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + value = spawnArgs.GetString( "joint_hips", "" ); + hipJoint = animator.GetJointHandle( value ); + if ( hipJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_hips' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_chest", "" ); + chestJoint = animator.GetJointHandle( value ); + if ( chestJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_chest' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_head", "" ); + headJoint = animator.GetJointHandle( value ); + if ( headJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", value, name.c_str() ); + } + + // initialize the script variables + memset ( &pfl, 0, sizeof( pfl ) ); + pfl.onGround = true; + pfl.noFallingDamage = false; + + // Start in idle + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + forceScoreBoard = false; + forceScoreBoardTime = 0; + forcedReady = false; + + privateCameraView = NULL; + + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + lastKiller = NULL; + teleportKiller = -1; + leader = false; + + SetPrivateCameraView( NULL ); + + lastSnapshotSequence = 0; + + if ( !gameLocal.isMultiplayer ) { + // in MP we set isStrogg in UpdateModelSetup() + isStrogg = spawnArgs.GetBool ( "strogg", "0" ); + } + + + aimClientNum = -1; + if ( mphud ) { + mphud->HandleNamedEvent( "aim_fade" ); + } + + isChatting = false; + + SetInitialHud(); + + emote = PE_NONE; + + powerupEffectTime = 0; + powerupEffect = NULL; + powerupEffectType = 0; + hasteEffect = NULL; + flagEffect = NULL; + arenaEffect = NULL; + + quadOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_quaddamage_overlay" ), false ); + hasteOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_haste_overlay" ), false ); + regenerationOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_regeneration_overlay" ), false ); + invisibilityOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_invisibility_overlay" ), false ); + powerUpOverlay = NULL; + + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP) && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ){ + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_WARMUP && ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_SUDDEN_DEATH ) { + // don't clear notices while in warmup modes or sudden death + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP) && vsMsgState ) { + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + deathSkinTime = 0; + deathStateHitch = false; + jumpDuringHitch = false; + + lastPickupTime = 0; + + if ( teamHealthRegenPending ) { + assert( teamHealthRegen == NULL ); + teamHealthRegenPending = false; + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamAmmoRegenPending ) { + assert( teamAmmoRegen == NULL ); + teamAmmoRegenPending = false; + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamDoublerPending ) { + assert( teamDoubler == NULL ); + teamDoublerPending = false; + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +=============== +idPlayer::ProjectHeadOverlay +=============== +*/ +void idPlayer::ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) { + + if( clientHead ) { + clientHead.GetEntity()->ProjectOverlay( point, dir, size, decal ); + } +} + +/* +=============== +idPlayer::GetCursorGUI +=============== +*/ +idUserInterface* idPlayer::GetCursorGUI( void ) { + idStr temp; + + assert( !gameLocal.isMultiplayer || gameLocal.localClientNum == entityNumber ); + if ( cursor ) { + return cursor; + } + if ( spawnArgs.GetString( "cursor", "", temp ) ) { + cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer ); + } + return cursor; +} + +/* +============== +idPlayer::Spawn + +Prepare any resources used by the player. +============== +*/ +void idPlayer::Spawn( void ) { + idStr temp; + idBounds bounds; + + if ( entityNumber >= MAX_CLIENTS ) { + gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); + } + + // allow thinking during cinematics + cinematic = true; + + if ( gameLocal.isMultiplayer ) { + // always start in spectating state waiting to be spawned in + // do this before SetClipModel to get the right bounding box + spectating = true; + } + + // set our collision model + physicsObj.SetSelf( this ); + SetClipModel( ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + SetPhysics( &physicsObj ); + InitAASLocation(); + + skin = renderEntity.customSkin; + + // only the local player needs guis + // for server netdemos that have no local player, we use demo_* guis in idGameLocal + if ( !gameLocal.isMultiplayer || entityNumber == gameLocal.localClientNum ) { + + // load HUD + hud = NULL; + mphud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + objectiveSystem = NULL; + + if ( spawnArgs.GetString( "hud", "", temp ) ) { + hud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No hud for player." ); + } + + if ( gameLocal.isMultiplayer ) { + if ( spawnArgs.GetString( "mphud", "", temp ) ) { + mphud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No MP hud overlay while in MP."); + } + } + + if ( hud ) { + hud->Activate( true, gameLocal.time ); + } + + if ( mphud ) { + mphud->Activate( true, gameLocal.time ); + } + + // load cursor + GetCursorGUI(); + if ( cursor ) { + cursor->Activate( true, gameLocal.time ); + } + + // Load + + if ( spawnArgs.GetString ( "cinematicHud", "", temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + + if ( !gameLocal.isMultiplayer ) { + objectiveSystem = uiManager->FindGui( spawnArgs.GetString( "wristcomm", "guis/wristcomm.gui" ), true, false, true ); + objectiveSystemOpen = false; +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + + // clear votes + // if we want to display current votes that were started before a player was connected + // but are still being voted on, this should check the current vote and update the gui appropriately + gameLocal.mpGame.ClearVote( entityNumber ); + } + + SetLastHitTime( 0, false ); + + // load the armor sound feedback + declManager->FindSound( "player_sounds_hitArmor" ); + + animator.RemoveOriginOffset( true ); + + // initialize user info related settings + // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game + // ocassionally, a race condition may mark a client in-game before he is spawned, if this is the case, parse the userinfo here + if ( (gameLocal.isClient || entityNumber == gameLocal.localClientNum) || (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber ) ) ) { + UserInfoChanged(); + } + + // create combat collision hull for exact collision detection + SetCombatModel(); + + // init the damage effects + playerView.SetPlayerEntity( this ); + + // supress model in non-player views, but allow it in mirrors and remote views + renderEntity.suppressSurfaceInViewID = entityNumber+1; + + // don't project shadow on self or weapon + renderEntity.noSelfShadow = true; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + clientHead->GetRenderEntity()->noSelfShadow = true; + } + } else { + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1; + headEnt->GetRenderEntity()->noSelfShadow = true; + } + } + + if ( gameLocal.isMultiplayer ) { + Init(); + Hide(); // properly hidden if starting as a spectator + + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + if( inOtherInstance ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } + + if ( !gameLocal.isClient ) { + // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + spectator = entityNumber; + forceRespawn = true; + assert( spectating ); + } + } else { + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + } + + // trigger playtesting item gives, if we didn't get here from a previous level + // the devmap key will be set on the first devmap, but cleared on any level + // transitions + if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) { + // fire a trigger with the name "devmap" + idEntity *ent = gameLocal.FindEntity( "devmap" ); + if ( ent ) { + ent->ActivateTargets( this ); + } + } + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + hiddenWeapon = true; + if ( weapon ) { + weapon->LowerWeapon(); + } +// RAVEN BEGIN +// mekberg: set to blaster now and disable the weapon. + idealWeapon = SlotForWeapon ( "weapon_blaster" ); + Event_DisableWeapon( ); +// RAVEN END + } else { + hiddenWeapon = false; + } + + if ( hud ) { + UpdateHudWeapon( ); + hud->StateChanged( gameLocal.time ); + } + + tipUp = false; + objectiveUp = false; + + aiManager.AddTeammate ( this ); + + if ( inventory.levelTriggers.Num() ) { + PostEventMS( &EV_Player_LevelTrigger, 0 ); + } + + // ddynerman: defaults for these values are the single player fall deltas + fatalFallDelta = spawnArgs.GetFloat("fatal_fall_delta", "65"); + hardFallDelta = spawnArgs.GetFloat("hard_fall_delta", "45"); + softFallDelta = spawnArgs.GetFloat("soft_fall_delta", "30"); + noFallDelta = spawnArgs.GetFloat("no_fall_delta", "7"); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); + + gibSkin = declManager->FindSkin( spawnArgs.GetString( "skin_gibskin" ) ); + + // Skil levels + dynamicProtectionScale = 1.0f; + if ( !gameLocal.isMultiplayer ) { + if ( g_skill.GetInteger() < 2 ) { + if ( health < 25 ) { + health = 25; + } + } else { + //g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + } + } + + // Powerup joints? + if ( spawnArgs.GetString ( "powerup_effect_joints", "", temp ) ) { + animator.GetJointList ( temp, powerupEffectJoints ); + } + + // RAVEN BEGIN + // mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; + + // mekberg: new objectives are suppressed until this event is processed + PostEventMS( &EV_Player_AllowNewObjectives, 5000 ); + tourneyStatus = PTS_UNKNOWN; + + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + + // zero out view angles when we spawn ourselves in MP - the server will send down + // the correct ones (only zero if our input is still zero'd) + if( gameLocal.isClient && gameLocal.localClientNum == entityNumber && usercmd.angles[ 0 ] == 0 && usercmd.angles[ 1 ] == 0 && usercmd.angles[ 2 ] == 0 ) { + deltaViewAngles = ang_zero; + } +//RITUAL BEGIN + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = 0; +//RITUAL END + + itemCosts = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "ItemCostConstants", false ) ); +} + +/* +============== +idPlayer::~idPlayer() + +Release any resources used by the player. +============== +*/ +idPlayer::~idPlayer() { + if( gameLocal.mpGame.GetGameState() ) { + gameLocal.mpGame.GetGameState()->ClientDisconnect( this ); + } + + delete weaponViewModel; + delete weaponWorldModel; + delete weapon; + delete aasSensor; + + SetPhysics( NULL ); +} + +/* +=========== +idPlayer::Save +=========== +*/ +void idPlayer::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteUsercmd( usercmd ); + + playerView.Save( savefile ); + + savefile->WriteBool( noclip ); + savefile->WriteBool( godmode ); + savefile->WriteInt ( godmodeDamage ); + savefile->WriteBool( undying ); + + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame + savefile->WriteAngles( spawnAngles ); + savefile->WriteAngles( viewAngles ); + savefile->WriteAngles( cmdAngles ); + + savefile->WriteInt( buttonMask ); + savefile->WriteInt( oldButtons ); + savefile->WriteInt( oldFlags ); + + savefile->WriteInt( lastHitTime ); + savefile->WriteInt( 0 ); + savefile->WriteInt( lastSavingThrowTime ); + + // idBoolFields don't need to be saved, just re-linked in Restore + savefile->Write( &pfl, sizeof( pfl ) ); + + inventory.Save( savefile ); + + //weapon->Save( savefile ); // Don't save this + + weaponViewModel.Save( savefile ); + weaponWorldModel.Save ( savefile ); + // weaponDef restore = weaponDef = GetWeaponDef ( currentWeapon ); + + savefile->WriteUserInterface( hud, false ); +// savefile->WriteUserInterface( mphud, false ); // Don't save MP stuff + savefile->WriteUserInterface( objectiveSystem, false ); + savefile->WriteUserInterface( cinematicHud, false ); + savefile->WriteBool( objectiveSystemOpen ); + savefile->WriteBool( disableHud ); + + savefile->WriteInt( lastDmgTime ); + savefile->WriteInt( deathClearContentsTime ); + savefile->WriteBool( doingDeathSkin ); + savefile->WriteInt( nextHealthPulse ); + savefile->WriteInt( nextArmorPulse ); + savefile->WriteBool( hiddenWeapon ); + +// savefile->WriteInt( spectator ); // Don't save MP stuff + +// savefile->WriteBool( scoreBoardOpen ); // Don't save MP stuff +// savefile->WriteBool( tourneyBracketsOpen ); // Don't save MP stuff +// savefile->WriteBool( forceScoreBoard ); // Don't save MP stuff +// savefile->WriteBool( forceRespawn ); // Don't save MP stuff + +// savefile->WriteBool( spectating ); // Don't save MP stuff +// savefile->WriteBool( lastHitToggle ); // Don't save MP stuff +// savefile->WriteBool( forcedReady ); // Don't save MP stuff +// savefile->WriteBool( wantSpectate ); // Don't save MP stuff + +// savefile->WriteBool( weaponGone ); // Don't save MP stuff +// savefile->WriteBool( useInitialSpawns ); // Don't save MP stuff +// savefile->WriteBool( isLagged ); // Don't save MP stuff +// savefile->WriteBool( isChatting ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateTeleport ); // Don't save MP stuff +// savefile->WriteInt( latchedTeam ); // Don't save MP stuff +// savefile->WriteInt( spawnedTime ); // Don't save MP stuff + +// teleportEntity.Save( savefile ); // Don't save MP stuff +// savefile->WriteInt( teleportKiller ); // Don't save MP stuff + + savefile->WriteInt( minRespawnTime ); + savefile->WriteInt( maxRespawnTime ); + + savefile->WriteVec3( firstPersonViewOrigin ); + savefile->WriteMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity or aasSensor since it's a dev tool + savefile->WriteVec3( intentDir ); + + savefile->WriteFloat ( vehicleCameraDist ); + + savefile->WriteJoint( hipJoint ); + savefile->WriteJoint( chestJoint ); + savefile->WriteJoint( headJoint ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( aasLocation.Num() ); + for( i = 0; i < aasLocation.Num(); i++ ) { + savefile->WriteInt( aasLocation[ i ].areaNum ); + savefile->WriteVec3( aasLocation[ i ].pos ); + } + + savefile->WriteString( modelName ); // cnicholson: Added unsaved var + // TOSAVE: const idDict* modelDict + + savefile->WriteInt( bobFoot ); + savefile->WriteFloat( bobFrac ); + savefile->WriteFloat( bobfracsin ); + savefile->WriteInt( bobCycle ); + savefile->WriteFloat( xyspeed ); + savefile->WriteInt( stepUpTime ); + savefile->WriteFloat( stepUpDelta ); + savefile->WriteFloat( idealLegsYaw ); + savefile->WriteFloat( legsYaw ); + savefile->WriteBool( legsForward ); + savefile->WriteFloat( oldViewYaw ); + savefile->WriteAngles( viewBobAngles ); + savefile->WriteVec3( viewBob ); + savefile->WriteInt( landChange ); + savefile->WriteInt( landTime ); + + savefile->WriteFloat( fatalFallDelta ); + savefile->WriteFloat( hardFallDelta ); + savefile->WriteFloat( softFallDelta ); + savefile->WriteFloat( noFallDelta ); + + savefile->WriteInt( currentWeapon ); + savefile->WriteInt( idealWeapon ); + savefile->WriteInt( previousWeapon ); + savefile->WriteInt( weaponSwitchTime ); + savefile->WriteBool( weaponEnabled ); + + savefile->WriteBool ( flashlightOn); + savefile->WriteBool ( zoomed ); + + savefile->WriteBool( reloadModel ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( powerUpSkin ); + savefile->WriteSkin( gibSkin ); + + savefile->WriteInt( numProjectilesFired ); + savefile->WriteInt( numProjectileHits ); + + savefile->WriteBool( airless ); + savefile->WriteInt( airTics ); + savefile->WriteInt( lastAirDamage ); + + savefile->WriteBool( gibDeath ); + savefile->WriteBool( gibsLaunched ); + savefile->WriteVec3( gibDir ); + + savefile->WriteBool( isStrogg ); + + savefile->WriteInterpolate( zoomFov ); + savefile->WriteInterpolate( centerView ); + savefile->WriteBool( fxFov ); + + savefile->WriteFloat( influenceFov ); + savefile->WriteInt( influenceActive ); + savefile->WriteObject( influenceEntity ); + savefile->WriteMaterial( influenceMaterial ); + savefile->WriteFloat( influenceRadius ); + savefile->WriteSkin( influenceSkin ); + + savefile->WriteObject( privateCameraView ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->WriteAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->WriteInt( loggedAccel[ i ].time ); + savefile->WriteVec3( loggedAccel[ i ].dir ); + } + savefile->WriteInt( currentLoggedAccel ); + + savefile->WriteUserInterface( focusUI, false ); + savefile->WriteInt( focusTime ); + savefile->WriteInt ( focusType ); + focusEnt.Save ( savefile ); + savefile->WriteUserInterface( focusBrackets, false ); + savefile->WriteInt( focusBracketsTime ); + + talkingNPC.Save( savefile ); + + extraProjPassEntity.Save( savefile ); + + savefile->WriteInt( talkCursor ); + savefile->WriteUserInterface( cursor, false ); + + savefile->WriteUserInterface( overlayHud, false ); + savefile->WriteInt ( overlayHudTime ); + + savefile->WriteBool( targetFriendly ); + + savefile->WriteInt( oldMouseX ); + savefile->WriteInt( oldMouseY ); + + savefile->WriteBool( tipUp ); + savefile->WriteBool( objectiveUp ); + + savefile->WriteFloat( dynamicProtectionScale ); + savefile->WriteInt( lastDamageDef ); + savefile->WriteVec3( lastDamageDir ); + savefile->WriteInt( lastDamageLocation ); + + savefile->WriteInt( predictedFrame ); + savefile->WriteVec3( predictedOrigin ); + savefile->WriteAngles( predictedAngles ); + savefile->WriteBool( predictedUpdated ); + savefile->WriteVec3( predictionOriginError ); + savefile->WriteAngles( predictionAnglesError ); + savefile->WriteInt( predictionErrorTime ); + +// savefile->WriteBool( ready ); // Don't save MP stuff +// savefile->WriteBool( respawning ); // Don't save MP stuff +// savefile->WriteBool( leader ); // Don't save MP stuff +// savefile->WriteBool( weaponCatchup ); // Don't save MP stuff +// savefile->WriteBool( isTelefragged ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateChange ); // Don't save MP stuff +// savefile->WriteInt( lastTeleFX ); // Don't save MP stuff +// savefile->WriteInt( lastSnapshotSequence ); // Don't save MP stuff + +// savefile->WriteInt( aimClientNum ); // Don't save MP stuff + +// lastImpulsePlayer->Save( savefile ); // Don't save MP stuff + +// savefile->WriteInt( arena ); // Don't save MP stuff + +// savefile->WriteInt( connectTime ); // Don't save MP stuff +// savefile->WriteInt( mutedPlayers ); // Don't save MP stuff +// savefile->WriteInt( friendPlayers ); // Don't save MP stuff + +// savefile->WriteInt( rank ); // Don't save MP stuff + + savefile->WriteInt( lastImpulseTime ); + bossEnemy.Save( savefile ); // cnicholson: Added unsaved var + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->WriteBool( weaponChangeIconsUp ); // cnicholson: Added unsaved var + + // mekberg: added + savefile->WriteBool( showNewObjectives ); + savefile->WriteBool( objectivesEnabled ); + + savefile->WriteBool( flagCanFire ); + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + +#ifndef _XENON + if ( hud ) { + hud->SetStateString( "message", common->GetLocalizedString( "#str_102916" ) ); + hud->HandleNamedEvent( "Message" ); + } +#endif +} + +/* +=========== +idPlayer::Restore +=========== +*/ +void idPlayer::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadUsercmd( usercmd ); + + playerView.Restore( savefile ); + + savefile->ReadBool( noclip ); + savefile->ReadBool( godmode ); + savefile->ReadInt ( godmodeDamage ); + savefile->ReadBool( undying ); + + savefile->ReadAngles( spawnAngles ); + savefile->ReadAngles( viewAngles ); + savefile->ReadAngles( cmdAngles ); + + memset( usercmd.angles, 0, sizeof( usercmd.angles ) ); + SetViewAngles( viewAngles ); + spawnAnglesSet = true; + + savefile->ReadInt( buttonMask ); + savefile->ReadInt( oldButtons ); + savefile->ReadInt( oldFlags ); + + usercmd.flags = 0; + oldFlags = 0; + + savefile->ReadInt( lastHitTime ); + int foo; + savefile->ReadInt( foo ); + savefile->ReadInt( lastSavingThrowTime ); + + savefile->Read( &pfl, sizeof( pfl ) ); + + inventory.Restore( savefile ); + + assert( !weapon ); + + weaponViewModel.Restore( savefile ); + weaponWorldModel.Restore( savefile ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + assert( !mphud ); // Don't save MP stuff + savefile->ReadUserInterface( objectiveSystem, &spawnArgs ); + savefile->ReadUserInterface( cinematicHud, &spawnArgs ); + savefile->ReadBool( objectiveSystemOpen ); + +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + + objectiveButtonReleased = false; + savefile->ReadBool( disableHud ); // cnicholson: Added unrestored var + + savefile->ReadInt( lastDmgTime ); + savefile->ReadInt( deathClearContentsTime ); + savefile->ReadBool( doingDeathSkin ); + savefile->ReadInt( nextHealthPulse ); + savefile->ReadInt( nextArmorPulse ); + savefile->ReadBool( hiddenWeapon ); + + assert( !spectator ); // Don't save MP stuff + + assert( !scoreBoardOpen ); // Don't save MP stuff + assert( !forceScoreBoard ); // Don't save MP stuff + assert( !forceRespawn ); // Don't save MP stuff + + assert( !spectating ); // Don't save MP stuff + assert( !lastHitToggle ); // Don't save MP stuff + assert( !forcedReady ); // Don't save MP stuff + assert( !wantSpectate ); // Don't save MP stuff + + assert( !weaponGone ); // Don't save MP stuff + assert( !useInitialSpawns ); // Don't save MP stuff + assert( !isLagged ); // Don't save MP stuff + assert( !isChatting ); // Don't save MP stuff + + assert( !lastSpectateTeleport ); // Don't save MP stuff + assert( latchedTeam == -1 ); // Don't save MP stuff + assert( !spawnedTime ); // Don't save MP stuff + + assert( !teleportEntity ); // Don't save MP stuff + assert( teleportKiller == -1 ); // Don't save MP stuff + + savefile->ReadInt( minRespawnTime ); + savefile->ReadInt( maxRespawnTime ); + + savefile->ReadVec3( firstPersonViewOrigin ); + savefile->ReadMat3( firstPersonViewAxis ); + + // don't bother restoring dragEntity since it's a dev tool + dragEntity.Clear(); + savefile->ReadVec3( intentDir ); + + savefile->ReadFloat ( vehicleCameraDist ); + + savefile->ReadJoint( hipJoint ); + savefile->ReadJoint( chestJoint ); + savefile->ReadJoint( headJoint ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( aasLocation[ i ].areaNum ); + savefile->ReadVec3( aasLocation[ i ].pos ); + } + + savefile->ReadString( modelName ); // cnicholson: Added unrestored var + // TORESTORE: const idDict* modelDict + + savefile->ReadInt( bobFoot ); + savefile->ReadFloat( bobFrac ); + savefile->ReadFloat( bobfracsin ); + savefile->ReadInt( bobCycle ); + savefile->ReadFloat( xyspeed ); + savefile->ReadInt( stepUpTime ); + savefile->ReadFloat( stepUpDelta ); + savefile->ReadFloat( idealLegsYaw ); + savefile->ReadFloat( legsYaw ); + savefile->ReadBool( legsForward ); + savefile->ReadFloat( oldViewYaw ); + savefile->ReadAngles( viewBobAngles ); + savefile->ReadVec3( viewBob ); + savefile->ReadInt( landChange ); + savefile->ReadInt( landTime ); + + savefile->ReadFloat( fatalFallDelta ); + savefile->ReadFloat( hardFallDelta ); + savefile->ReadFloat( softFallDelta ); + savefile->ReadFloat( noFallDelta ); + + savefile->ReadInt( currentWeapon ); + savefile->ReadInt( idealWeapon ); + savefile->ReadInt( previousWeapon ); + savefile->ReadInt( weaponSwitchTime ); + savefile->ReadBool( weaponEnabled ); + + savefile->ReadBool ( flashlightOn ); + savefile->ReadBool ( zoomed ); + + savefile->ReadBool ( reloadModel ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( powerUpSkin ); + savefile->ReadSkin( gibSkin ); + + savefile->ReadInt( numProjectilesFired ); + savefile->ReadInt( numProjectileHits ); + + savefile->ReadBool( airless ); + savefile->ReadInt( airTics ); + savefile->ReadInt( lastAirDamage ); + + savefile->ReadBool( gibDeath ); + savefile->ReadBool( gibsLaunched ); + savefile->ReadVec3( gibDir ); + + savefile->ReadBool( isStrogg ); + + savefile->ReadInterpolate( zoomFov ); + savefile->ReadInterpolate( centerView ); + savefile->ReadBool( fxFov ); + + savefile->ReadFloat( influenceFov ); + savefile->ReadInt( influenceActive ); + savefile->ReadObject( reinterpret_cast( influenceEntity ) ); + savefile->ReadMaterial( influenceMaterial ); + savefile->ReadFloat( influenceRadius ); + savefile->ReadSkin( influenceSkin ); + + savefile->ReadObject( reinterpret_cast( privateCameraView ) ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->ReadAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->ReadInt( loggedAccel[ i ].time ); + savefile->ReadVec3( loggedAccel[ i ].dir ); + } + savefile->ReadInt( currentLoggedAccel ); + + savefile->ReadUserInterface( focusUI, &spawnArgs ), + savefile->ReadInt( focusTime ); + savefile->ReadInt( (int&)focusType ); + focusEnt.Restore ( savefile ); + savefile->ReadUserInterface( focusBrackets, &spawnArgs ); + savefile->ReadInt( focusBracketsTime ); + + talkingNPC.Restore( savefile ); + + extraProjPassEntity.Restore( savefile ); + + savefile->ReadInt( talkCursor ); + savefile->ReadUserInterface( cursor, &spawnArgs ); + + savefile->ReadUserInterface( overlayHud, &spawnArgs ); // cnicholson: Added unrestored var + savefile->ReadInt ( overlayHudTime ); // cnicholson: Added unrestored var + + savefile->ReadBool( targetFriendly ); + + savefile->ReadInt( oldMouseX ); + savefile->ReadInt( oldMouseY ); + + savefile->ReadBool( tipUp ); + savefile->ReadBool( objectiveUp ); + + savefile->ReadFloat( dynamicProtectionScale ); // cnicholson: Added unrestored var + savefile->ReadInt( lastDamageDef ); + savefile->ReadVec3( lastDamageDir ); + savefile->ReadInt( lastDamageLocation ); + + savefile->ReadInt( predictedFrame ); + savefile->ReadVec3( predictedOrigin ); + savefile->ReadAngles( predictedAngles ); + savefile->ReadBool( predictedUpdated ); + savefile->ReadVec3( predictionOriginError ); + savefile->ReadAngles( predictionAnglesError ); + savefile->ReadInt( predictionErrorTime ); + + assert( !ready ); // Don't save MP stuff + assert( !respawning ); // Don't save MP stuff + assert( !leader ); // Don't save MP stuff + assert( !weaponCatchup ); // Don't save MP stuff + assert( !isTelefragged ); // Don't save MP stuff + + assert( !lastSpectateChange ); // Don't save MP stuff + assert( lastTeleFX == -9999 ); // Don't save MP stuff + assert( !lastSnapshotSequence ); // Don't save MP stuff + + assert( aimClientNum == -1 ); // Don't save MP stuff + + assert( !lastImpulsePlayer ); // Don't save MP stuff + + assert( !arena ); // Don't save MP stuff + + assert( !connectTime ); // Don't save MP stuff + assert( !mutedPlayers ); // Don't save MP stuff + assert( !friendPlayers ); // Don't save MP stuff + + assert( rank == -1 ); // Don't save MP stuff + + savefile->ReadInt( lastImpulseTime ); + bossEnemy.Restore( savefile ); // cnicholson: Added unrestored var + + // TORESTORE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TORESTORE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->ReadBool( weaponChangeIconsUp ); // cnicholson: Added unrestored var + +// mekberg: added + savefile->ReadBool( showNewObjectives ); + savefile->ReadBool( objectivesEnabled ); + + savefile->ReadBool( flagCanFire ); + + // set the pm_ cvars + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + + // Loading a game on easy mode ensures you alwasy have 20% health when you load + i = spawnArgs.GetInt ( va("minRestoreHealth%d", g_skill.GetInteger ( ) ) ); + if ( health < i ) { + health = i; + } + + //if there's hearing loss, make sure we post a finishing event + if( pfl.hearingLoss ) { + Event_FinishHearingLoss( 3.0f ); + } else { + Event_FinishHearingLoss( 0.05f ); + } + // create combat collision hull for exact collision detection + SetCombatModel(); + +// RAVEN BEGIN +// mekberg: Grab from user info. + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); +// RAVEN END +} + +/* +=============== +idPlayer::PrepareForRestart +================ +*/ +void idPlayer::PrepareForRestart( void ) { + ClearPowerUps(); + Spectate( true ); + + forceRespawn = true; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// RITUAL END + + // we will be restarting program, clear the client entities from program-related things first + ShutdownThreads(); + + // the sound world is going to be cleared, don't keep references to emitters + FreeSoundEmitter( false ); +} + +/* +=============== +idPlayer::Restart +================ +*/ +void idPlayer::Restart( void ) { + idActor::Restart(); + + // client needs to setup the animation script object again + if ( gameLocal.isClient ) { + // clear the existing model to force a reload + Init(); + } else { + // choose a random spot and prepare the point of view in case player is left spectating + assert( spectating ); + SpawnFromSpawnSpot(); + } + + lastKiller = NULL; + useInitialSpawns = true; +} + +/* +=============== +idPlayer::ServerSpectate +================ +*/ +void idPlayer::ServerSpectate( bool spectate ) { + assert( !gameLocal.isClient ); + + if ( spectating != spectate ) { + // if we select spectating on the client + // mekberg: drop and clear powerups from the player. + DropPowerups(); + ClearPowerUps(); + Spectate( spectate ); + gameLocal.mpGame.GetGameState()->Spectate( this ); + if ( spectate ) { + SetSpectateOrigin( ); + } + } + if ( !spectate ) { + SpawnFromSpawnSpot( ); + } +} + +/* +=========== +idPlayer::SelectSpawnPoint + +Find a spawn point marked, otherwise use normal spawn selection. +============ +*/ +bool idPlayer::SelectSpawnPoint( idVec3 &origin, idAngles &angles ) { + idEntity *spot; + idStr skin; + + spot = gameLocal.SelectSpawnPoint( this ); + + // no spot, try again next frame + if( !spot ) { + forceRespawn = true; + return false; + } + + // set the player skin from the spawn location + if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) { + spawnArgs.Set( "spawn_skin", skin ); + } + + if ( spot->spawnArgs.GetString( "spawn_model", NULL ) ) { + spawnArgs.Set( "model", spot->spawnArgs.GetString( "spawn_model", NULL ) ); + } + + if ( spot->spawnArgs.GetString( "def_head", NULL ) ) { + spawnArgs.Set( "def_head", spot->spawnArgs.GetString( "def_head", NULL ) ); + } + + // activate the spawn locations targets + spot->PostEventMS( &EV_ActivateTargets, 0, this ); + + origin = spot->GetPhysics()->GetOrigin(); + origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor + angles = spot->GetPhysics()->GetAxis().ToAngles(); + + return true; +} + +/* +=========== +idPlayer::SpawnFromSpawnSpot + +Chooses a spawn location and spawns the player +============ +*/ +void idPlayer::SpawnFromSpawnSpot( void ) { + idVec3 spawn_origin; + idAngles spawn_angles; + + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + forceRespawn = true; + return; + } + SpawnToPoint( spawn_origin, spawn_angles ); +} + +/* +=========== +idPlayer::SpawnToPoint + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState + +when called here with spectating set to true, just place yourself and init +============ +*/ +void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) { + idVec3 spec_origin; + + assert( !gameLocal.isClient ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // Record previous weapons for later restoration + inventory.carryOverWeapons &= ~CARRYOVER_WEAPONS_MASK; + inventory.carryOverWeapons |= inventory.weapons; + } +// RITUAL END + + respawning = true; + + Init(); + + // Force players to use bounding boxes when in multiplayer + if ( gameLocal.isMultiplayer ) { + use_combat_bbox = true; + + // Make sure the combat model is unlinked + if ( combatModel ) { + combatModel->Unlink( ); + } + } + + // Any health over max health will tick down + if ( health > inventory.maxHealth ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } + + fl.noknockback = false; + // stop any ragdolls being used + StopRagdoll(); + // set back the player physics + SetPhysics( &physicsObj ); + physicsObj.SetClipModelAxis(); + physicsObj.EnableClip(); + if ( !spectating ) { + SetCombatContents( true ); + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + // setup our initial view + if ( !spectating ) { + SetOrigin( spawn_origin ); +// RAVEN BEGIN +// abahr: taking into account gravity + SetAxis( spawn_angles.ToMat3() ); +// RAVEN END + } else { + spec_origin = spawn_origin; + spec_origin[ 2 ] += pm_normalheight.GetFloat(); + spec_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spec_origin ); + } + + // if this is the first spawn of the map, we don't have a usercmd yet, + // so the delta angles won't be correct. This will be fixed on the first think. + viewAngles = ang_zero; + SetDeltaViewAngles( ang_zero ); + SetViewAngles( spawn_angles ); + spawnAngles = spawn_angles; + spawnAnglesSet = false; + + legsForward = true; + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( spectating ) { + Hide(); + } else { + Show(); + } + + if ( gameLocal.isMultiplayer ) { + if ( !spectating ) { + // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof' + if ( lastTeleFX < gameLocal.time - 1000 ) { + // currentThinkingEntity not set here (called out of Run()) + idEntity* thinker = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + gameLocal.PlayEffect( spawnArgs, "fx_spawn", renderEntity.origin, idVec3(0,0,1).ToMat3(), false, vec3_origin, true ); + lastTeleFX = gameLocal.time; + gameLocal.currentThinkingEntity = thinker; + } + } +//RAVEN BEGIN +//asalmon: Clear the respwan message + if ( mphud ) { + mphud->SetStateInt( "respawnNotice", 0 ); + } +//RAVEN END + pfl.teleport = true; + } else { + pfl.teleport = false; + } + + // kill anything at the new position + if ( !spectating ) { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it +// RAVEN BEGIN +// abahr: this is killing the tram car when spawning in. Ooooops! + if( gameLocal.isMultiplayer ) { + gameLocal.KillBox( this ); + } +// RAVEN END + } + + // don't allow full run speed for a bit + physicsObj.SetKnockBack( 100 ); + + // set our respawn time and buttons so that if we're killed we don't respawn immediately + minRespawnTime = gameLocal.time; + maxRespawnTime = gameLocal.time; + if ( !spectating ) { + forceRespawn = false; + } + + privateCameraView = NULL; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + // restore previous weapons + inventory.weapons |= inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + for( int weaponIndex = 0; weaponIndex < MAX_WEAPONS; weaponIndex++ ) + { + if( inventory.weapons & ( 1 << weaponIndex ) ) + { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( weaponIndex ); + inventory.ammo[ ammoIndex ] = inventory.StartingAmmoForWeaponIndex( weaponIndex ); + } + } + + /// Restore armor purchased while dead + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_LIGHT; + GiveItem( "item_armor_small" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_HEAVY; + GiveItem( "item_armor_large" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_AMMO; + GiveItem( "ammorefill" ); + } + + // Reactivate team powerups + gameLocal.mpGame.SetUpdateForTeamPowerups(team); + UpdateTeamPowerups(); + } +// RITUAL END + + BecomeActive( TH_THINK ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + Think(); + + respawning = false; + isTelefragged = false; + isLagged = false; + isChatting = false; + + lastImpulsePlayer = NULL; + lastImpulseTime = 0; +} + +/* +=============== +idPlayer::SavePersistantInfo + +Saves any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::SavePersistantInfo( void ) { + idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; + + playerInfo.Clear(); + inventory.GetPersistantData( playerInfo ); + playerInfo.SetInt( "health", health ); + playerInfo.SetInt( "current_weapon", currentWeapon ); +} + +/* +=============== +idPlayer::RestorePersistantInfo + +Restores any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::RestorePersistantInfo( void ) { + if ( gameLocal.isMultiplayer ) { + gameLocal.persistentPlayerInfo[entityNumber].Clear(); + } + + spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); + + inventory.RestoreInventory( this, spawnArgs ); + health = spawnArgs.GetInt( "health", "100" ); + if ( !gameLocal.isClient ) { + idealWeapon = spawnArgs.GetInt( "current_weapon", "0" ); + } +} + +/* +================ +idPlayer::GetUserInfo +================ +*/ +idDict *idPlayer::GetUserInfo( void ) { + return &gameLocal.userInfo[ entityNumber ]; +} + +/* +============== +idPlayer::UpdateModelSetup +Updates the player's model setup. Model setups are read from the player def, and contain +information about the player's model, the player's head model, a skin, and a team for the +composite model. +============== +*/ +void idPlayer::UpdateModelSetup( bool forceReload ) { + const idDict* dict; + const char* uiKeyName = NULL; + const char* defaultModel = NULL; + const char* newModelName = NULL; + + if( !gameLocal.isMultiplayer || spectating ) { + return; + } + + if( gameLocal.IsTeamGame() ) { + defaultModel = spawnArgs.GetString( va( "def_default_model_%s", idMultiplayerGame::teamNames[ team ] ), NULL ); + + if( g_forceMarineModel.GetString()[ 0 ] && team == TEAM_MARINE ) { + newModelName = g_forceMarineModel.GetString(); + } else if( g_forceStroggModel.GetString()[ 0 ] && team == TEAM_STROGG ) { + newModelName = g_forceStroggModel.GetString(); + } else { + uiKeyName = va( "ui_model_%s", idMultiplayerGame::teamNames[ team ] ); + newModelName = GetUserInfo()->GetString( uiKeyName ); + } + } else { + defaultModel = spawnArgs.GetString( "def_default_model" ); + + if( g_forceModel.GetString()[ 0 ] ) { + newModelName = g_forceModel.GetString(); + } else { + uiKeyName = "ui_model"; + newModelName = GetUserInfo()->GetString( uiKeyName ); + } + } + + if( !idStr::Icmp( newModelName, "" ) ) { + newModelName = defaultModel; + } + + // model hasn't changed + if( !modelName.Icmp( newModelName ) && !forceReload ) { + return; + } + + rvDeclPlayerModel* model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + + // validate that the model they've selected is OK for this team game + if( gameLocal.IsTeamGame() && model ) { + if( idStr::Icmp( model->team, idMultiplayerGame::teamNames[ team ] ) ) { + gameLocal.Warning( "idPlayer::UpdateModelSetup() - Player %d (%s) set to model %s which is restricted to team %s (Player team: %s)\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), newModelName, model->team.c_str(), idMultiplayerGame::teamNames[ team ] ); + if( uiKeyName ) { + cvarSystem->SetCVarBool( uiKeyName, "" ); + } + + dict = NULL; + } + } + + // check to see if the user-specified ui_model/ui_model_strogg/ui_model_marine is valid + if( !model ) { + newModelName = defaultModel; + + model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + if( !model ) { + gameLocal.Error( "idPlayer::UpdateModelSetup() - Can't find default model (%s)\n", defaultModel ); + } else { + // If it's not valid, set the cvar to the default model + if( uiKeyName ) { + GetUserInfo()->Set( uiKeyName, defaultModel ); + + if( entityNumber == gameLocal.localClientNum ) { + cvarSystem->SetCVarString( uiKeyName, defaultModel ); + } + + if( gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "updateUI %d\n", entityNumber ) ); + return; + } + } + + } + } + + modelName = newModelName; + modelDecl = model; + + reloadModel = true; + + // if we have a strogg model, set the strogg flag here + isStrogg = false; + if( modelDecl && !modelDecl->team.Icmp( "strogg" ) ) { + isStrogg = true; + } +} + +/* +============== +idPlayer::BalanceTeam +============== +*/ +bool idPlayer::BalanceTeam( void ) { + int i, balanceTeam, teamCount[2]; + idEntity *ent; + + teamCount[ 0 ] = teamCount[ 1 ] = 0; + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ 0 ] < teamCount[ 1 ] ) { + balanceTeam = 0; + } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) { + balanceTeam = 1; + } + + if ( balanceTeam != -1 && team != balanceTeam && teamCount[ balanceTeam ]+1 != teamCount[ !balanceTeam ] ) { + common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "strogg" : "marine" ); + team = balanceTeam; + GetUserInfo()->Set( "ui_team", team ? "Strogg" : "Marine" ); + return true; + } + return false; +} + +void HSVtoRGB( float &r, float &g, float &b, float h, float s, float v ) { + int i; + float f, p, q, t; + + h = idMath::ClampFloat( 0.0f, 360.0f, h ); + s = idMath::ClampFloat( 0.0f, 1.0f, s ); + v = idMath::ClampFloat( 0.75f, 1.0f, v ); + + if( s == 0 ) { + // achromatic (grey) + r = g = b = v; + return; + } + + h /= 60; // sector 0 to 5 + i = floor( h ); + f = h - i; // factorial part of h + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch( i ) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: // case 5: + r = v; + g = p; + b = q; + break; + } +} + +/* +============== +idPlayer::UserInfoChanged +============== +*/ +bool idPlayer::UserInfoChanged( void ) { + idDict *userInfo; + bool modifiedInfo; + bool spec; + bool newready; + + userInfo = GetUserInfo(); + showWeaponViewModel = userInfo->GetBool( "ui_showGun" ); + + if ( !gameLocal.isMultiplayer ) { + return false; + } + + modifiedInfo = false; + + spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + // never let spectators go back to game while sudden death is on + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == SUDDENDEATH && !spec && wantSpectate == true ) { + userInfo->Set( "ui_spectate", "Spectate" ); + modifiedInfo |= true; + } else { + if ( spec != wantSpectate && !spec ) { + // returning from spectate, set forceRespawn so we don't get stuck in spectate forever + forceRespawn = true; + } + wantSpectate = spec; + } + } else { + if ( spec ) { + userInfo->Set( "ui_spectate", "Play" ); + modifiedInfo |= true; + } else if ( spectating ) { + // allow player to leaving spectator mode if they were in it when it was disallowed + forceRespawn = true; + } + wantSpectate = false; + } + + if ( gameLocal.serverInfo.GetBool( "si_useReady" ) ) { + newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready != newready && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP && !wantSpectate ) { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_107180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLocalizedString( "#str_104300" ) : common->GetLocalizedString( "#str_104301" ) ); + } + ready = newready; + } + + int newTeam = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Strogg" ) == 0 ); + + if( hud && gameLocal.IsTeamGame() ) { + hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + } else if( hud ) { + hud->HandleNamedEvent( "setTeam_marine" ); + } + + if ( gameLocal.IsTeamGame() && newTeam != latchedTeam ) { + team = newTeam; + + if ( gameLocal.isServer ) { + int verifyTeam = gameLocal.mpGame.VerifyTeamSwitch( newTeam, this ); + if( verifyTeam != newTeam ) { + if( verifyTeam == TEAM_MARINE || verifyTeam == TEAM_STROGG ) { + gameLocal.userInfo[ entityNumber ].Set( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + if( gameLocal.localClientNum == entityNumber ) { + cvarSystem->SetCVarString( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + } + modifiedInfo = true; + team = verifyTeam; + } + } + } + + // if still OK to change + if( team != latchedTeam ) { + if( gameLocal.isServer ) { + gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team ); + } + + SetInitialHud(); + if( mphud ) { + mphud->SetStateInt( "playerteam", team ); + mphud->HandleNamedEvent( "TeamChange" ); + } + + if( entityNumber == gameLocal.localClientNum ) { + alreadyDidTeamAnnouncerSound = true; + // the client might set its team to a value before the server corrects for team balance + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_MARINE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_STROGG ); + + if ( !wantSpectate ) { + if( team == TEAM_STROGG ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( team == TEAM_MARINE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + + // ATVI DevTrack #13224 - update on each team change + iconManager->UpdateTeamIcons(); + + latchedTeam = team; + } + } + + //if ( !gameLocal.isClient && gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // bool teamsBalanced = BalanceTeam(); + // modifiedInfo |= teamsBalanced; + //} + + UpdateModelSetup(); + + if( (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber )) || (gameLocal.isClient && gameLocal.localClientNum == entityNumber) ) { + isChatting = userInfo->GetBool( "ui_chat", "0" ); + if ( isChatting && pfl.dead ) { + // if dead, always force chat icon off. + isChatting = false; + userInfo->SetBool( "ui_chat", false ); + modifiedInfo |= true; + } + } + + // grab hitscan tint + hitscanTint = userInfo->GetVec4( "ui_hitscanTint", "81 1 1 1" ); + HSVtoRGB( hitscanTint.x, hitscanTint.y, hitscanTint.z, hitscanTint.x, hitscanTint.y, hitscanTint.z ); + // force alpha to 1 + hitscanTint.w = 1.0f; + + return modifiedInfo; +} + +/* +=============== +idPlayer::UpdateHudAmmo +=============== +*/ +void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) { + int inclip; + int ammoamount; + + assert( weapon ); + assert( _hud ); + + inclip = weapon->AmmoInClip(); + ammoamount = weapon->AmmoAvailable(); + + if ( ammoamount < 0 ) { + // show infinite ammo + _hud->SetStateString( "player_ammo", "-1" ); + _hud->SetStateString( "player_totalammo", "-1" ); + _hud->SetStateFloat ( "player_ammopct", 1.0f ); + } else if ( weapon->ClipSize ( ) && !gameLocal.isMultiplayer ) { + _hud->SetStateInt ( "player_clip_size", weapon->ClipSize() ); + _hud->SetStateFloat ( "player_ammopct", (float)inclip / (float)weapon->ClipSize ( ) ); + if ( weapon->ClipSize ( )==1) { + _hud->SetStateInt ( "player_totalammo", ammoamount ); + } + else { + _hud->SetStateInt ( "player_totalammo", ammoamount - inclip ); + } + _hud->SetStateInt ( "player_ammo", inclip ); + } else { + _hud->SetStateFloat ( "player_ammopct", (float)ammoamount / (float)weapon->maxAmmo ); + _hud->SetStateInt ( "player_totalammo", ammoamount ); + _hud->SetStateInt ( "player_ammo", -1 ); + } + + _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); +} + +/* +=============== +idPlayer::UpdateHudStats +=============== +*/ +void idPlayer::UpdateHudStats( idUserInterface *_hud ) { + int temp; + + assert ( _hud ); + + temp = _hud->State().GetInt ( "player_health", "-1" ); + if ( temp != health ) { + _hud->SetStateInt ( "player_healthDelta", temp == -1 ? 0 : (temp - health) ); + _hud->SetStateInt ( "player_health", health < -100 ? -100 : health ); + _hud->SetStateFloat ( "player_healthpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)health / (float)inventory.maxHealth ) ); + _hud->HandleNamedEvent ( "updateHealth" ); + } + + temp = _hud->State().GetInt ( "player_armor", "-1" ); + if ( temp != inventory.armor ) { + _hud->SetStateInt ( "player_armorDelta", temp == -1 ? 0 : (temp - inventory.armor) ); + _hud->SetStateInt ( "player_armor", inventory.armor ); + _hud->SetStateFloat ( "player_armorpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)inventory.armor / (float)inventory.maxarmor ) ); + _hud->HandleNamedEvent ( "updateArmor" ); + } + + // Boss bar + if ( _hud->State().GetInt ( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt ( "boss_health", -1 ); + _hud->HandleNamedEvent ( "hideBossBar" ); + _hud->HandleNamedEvent ( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt ( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent ( "updateBossBar" ); + } + } + + // god mode information + _hud->SetStateString( "player_god", va( "%i", (godmode && g_showGodDamage.GetBool()) ) ); + _hud->SetStateString( "player_god_damage", va( "%i", godmodeDamage ) ); + + // Update the hit direction + idVec3 localDir; + viewAxis.ProjectVector( lastDamageDir, localDir ); + _hud->SetStateFloat( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + + //_hud->HandleNamedEvent( "updateArmorHealthAir" ); + + if ( weapon ) { + UpdateHudAmmo( _hud ); + } + + _hud->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateHudWeapon( int displayWeapon ) { + + if ( (displayWeapon < -1) || (displayWeapon >= MAX_WEAPONS) ) { + common->DPrintf( "displayweapon was out of range" ); + return; + } + + int index = 0; + int idealIndex = 0; + idUserInterface * hud = idPlayer::hud; + idUserInterface * mphud = idPlayer::mphud; + idUserInterface * cursor = idPlayer::cursor; + + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + hud = gameLocal.GetDemoHud(); + mphud = gameLocal.GetDemoMphud(); + cursor = gameLocal.GetDemoCursor(); + } + } else { + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud && p->mphud ); + hud = p->hud; + mphud = p->mphud; + cursor = p->GetCursorGUI(); + } + } + + if ( !hud || !weapon ) { + return; + } + + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + const char *weapnum = va( "weapon%d", i ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << i ) && spawnArgs.GetBool( va( "weapon%d_cycle", i ) ) ) { + hud->SetStateBool( weapnum, true ); + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + weapstate++; + + if ( idealWeapon == i ) { + idealIndex = index; + } + + const char *weaponIcon = GetWeaponDef ( i )->dict.GetString ( "inv_icon" ); + //const idMaterial *material = declManager->FindMaterial( weaponIcon ); + //if ( material ) { + // material->SetSort( SS_GUI ); + //} + + hud->SetStateInt ( va("weapon%d_index", i ), index++ ); + hud->SetStateString ( va("weapon%d_icon", i ), weaponIcon ); + hud->SetStateInt ( va("weapon%d_ammo", i ), inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ] ); + } + } else { + hud->SetStateBool( weapnum, false ); + } + } + + hud->SetStateInt( "weaponcount", index ); + + const idMaterial *material = declManager->FindMaterial( weapon->GetIcon() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + material = declManager->FindMaterial( weapon->spawnArgs.GetString ( "inv_icon" ) ); + //if ( material ) { + // material->SetSort( SS_GUI ); + //} + + hud->SetStateString( "weapicon", weapon->GetIcon() ); + hud->SetStateString( "ammoIcon", weapon->spawnArgs.GetString ( "inv_icon" ) ); + hud->SetStateInt( "player_weapon", currentWeapon ); + hud->SetStateInt( "player_lastweapon", previousWeapon ); + hud->SetStateInt( "player_idealweapon", ((displayWeapon!=-1)?displayWeapon:idealWeapon) ); + hud->SetStateInt( "player_idealIndex", idealIndex ); + + // Weapon name for weapon selection + const idDeclEntityDef* w = GetWeaponDef( ( displayWeapon != -1 ) ? displayWeapon:idealWeapon ); + if ( w ) { + idStr langToken = w->dict.GetString( "inv_name" ); + hud->SetStateString( "weaponname", common->GetLocalizedString( langToken ) ); + } + + UpdateHudAmmo( hud ); + + if ( cursor ) { + weapon->UpdateCrosshairGUI( cursor ); + + // mekberg: force a redraw so ON_INIT gets called and doesn't stomp all over + // the color values we set in weaponChange. + cursor->Redraw( gameLocal.time ); + cursor->HandleNamedEvent( "weaponChange" ); + } + + hud->HandleNamedEvent( "weaponChange" ); + hud->StateChanged( gameLocal.time ); + weaponChangeIconsUp = true; + + // if previousWeapon is -1, the weaponChange state won't update it, so manually reset it +#ifdef _XENON + if( previousWeapon == -1 ) { + ResetHUDWeaponSwitch(); + } +#endif +} + +/* +=============== +idPlayer::StartRadioChatter +=============== +*/ +void idPlayer::StartRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterUp" ); + } + if ( vehicleController.IsDriving ( ) ) { + vehicleController.StartRadioChatter ( ); + } +} + +/* +=============== +idPlayer::StopRadioChatter +=============== +*/ +void idPlayer::StopRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + if ( vehicleController.IsDriving( ) ) { + vehicleController.StopRadioChatter( ); + } +} + +/* +=============== +idPlayer::DrawShadow +=============== +*/ +void idPlayer::DrawShadow( renderEntity_t *headRenderEnt ) { + if ( gameLocal.isMultiplayer && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all player shadows for the local client + renderEntity.suppressShadowInViewID = gameLocal.localClientNum+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = gameLocal.localClientNum+1; + } + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all player shadows + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + // Only show player shadows for other clients + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } +} + +/* +=============== +idPlayer::DrawHUD +=============== +*/ +void idPlayer::DrawHUD( idUserInterface *_hud ) { + idUserInterface * cursor = idPlayer::cursor; + + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + cursor = gameLocal.GetDemoCursor(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + } else { + + if ( team != gameLocal.GetLocalPlayer()->hudTeam && _hud ) { + _hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + gameLocal.GetLocalPlayer()->hudTeam = team; + } + + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + } + + if ( disableHud || influenceActive != INFLUENCE_NONE || privateCameraView || !_hud || !g_showHud.GetBool() ) { + return; + } + + if ( objectiveSystemOpen ) { + if ( !GuiActive() ) { + // showing weapon zoom gui when objectives are open because that's the way I'z told to make it werkz + if ( weapon && weapon->GetZoomGui( ) && zoomed ) { + weapon->GetZoomGui( )->Redraw( gameLocal.time ); + } + } + return; + } + + if ( gameLocal.isMultiplayer ) { + _hud->SetStateBool( "mp", true ); + } else { + _hud->SetStateBool( "mp", false ); + } + + // Draw the cinematic hud when in a cinematic + if ( gameLocal.GetCamera( ) ) { + if ( cinematicHud && !(gameLocal.editors & EDITOR_MODVIEW) ) { + cinematicHud->Redraw( gameLocal.time ); + } + return; + } + + // Let the vehicle draw the hud instead + if ( vehicleController.IsDriving( ) ) { + if ( !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + vehicleController.DrawHUD( ); + if ( cursor && health > 0 ) { + // mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->SetStateBool( "vehiclecursor", true ); + + vehicleController.UpdateCursorGUI( cursor ); + cursor->Redraw( gameLocal.time ); + } + } + _hud = GetHud(); + // Boss bar + if ( _hud && _hud->State().GetInt( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt( "boss_health", -1 ); + _hud->HandleNamedEvent( "hideBossBar" ); + _hud->HandleNamedEvent( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent( "updateBossBar" ); + } + } + return; + } + + if ( cursor ) { + cursor->SetStateBool( "vehiclecursor", false ); + } + + // FIXME: this is temp to allow the sound meter to show up in the hud + // it should be commented out before shipping but the code can remain + // for mod developers to enable for the same functionality + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + // don't draw main hud in spectator (only mphud) + if ( !spectating && !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( weapon && weapon->GetZoomGui( ) && zoomed ) { + weapon->GetZoomGui( )->Redraw( gameLocal.time ); + } +// RAVEN BEGIN +// mekberg: removed check for weapon->ShowCrosshair. +// we want to show the crosshair regardless for NPC tags + if ( cursor && health > 0 ) { + // Pass the current weapon to the cursor gui for custom crosshairs +// mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); +// RAVEN END + cursor->Redraw( gameLocal.time ); + } + } + + UpdateHudStats( _hud ); + + if ( focusBrackets ) { + // If 2d_calc is still true then the gui didnt render so we can abandon it + if ( focusBrackets->State().GetBool( "2d_calc" ) ) { + focusBrackets->SetStateBool( "2d_calc", false ); + focusBrackets = NULL; + focusBracketsTime = 0; + _hud->HandleNamedEvent( "hideBrackets" ); + } else { + _hud->SetStateString( "bracket_left", focusBrackets->State().GetString( "2d_min_x" ) ); + _hud->SetStateString( "bracket_top", focusBrackets->State().GetString( "2d_min_y" ) ); + _hud->SetStateFloat( "bracket_width", focusBrackets->State().GetFloat( "2d_max_x" ) - focusBrackets->State().GetFloat( "2d_min_x" ) ); + _hud->SetStateFloat( "bracket_height", focusBrackets->State().GetFloat( "2d_max_y" ) - focusBrackets->State().GetFloat( "2d_min_y" ) ); + // TODO: Find a way to get bracket text from gui to hud + } + } + _hud->Redraw( gameLocal.realClientTime ); + } + + if ( gameLocal.isMultiplayer ) { + idUserInterface* _mphud = mphud; + // server netdemos don't have a local player, grab the right mphud + if ( !gameLocal.GetLocalPlayer() ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + _mphud = gameLocal.GetDemoMphud(); + } else if ( gameLocal.GetLocalPlayer() != this ) { + // if we're spectating someone else, use our local hud + _mphud = gameLocal.GetLocalPlayer()->mphud; + } + if ( _mphud ) { + gameLocal.mpGame.UpdateHud( _mphud ); + _mphud->Redraw( gameLocal.time ); + } + + if ( overlayHud && overlayHudTime > gameLocal.time && overlayHudTime != 0 ) { + overlayHud->Redraw( gameLocal.time ); + } else { + overlayHud = NULL; + overlayHudTime = 0; + } + } +} + +/* +=============== +idPlayer::EnterCinematic +=============== +*/ +void idPlayer::EnterCinematic( void ) { + Hide(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(true); +// RAVEN END + + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + + cinematicHud = NULL; + + // See if camera has custom cinematic gui + if ( gameLocal.GetCamera ( ) ) { + const char* guiCinematic; + guiCinematic = gameLocal.GetCamera()->spawnArgs.GetString ( "guiCinematic", "" ); + if ( *guiCinematic ) { + cinematicHud = uiManager->FindGui( guiCinematic, true, false, true ); + } + } + + // Load default cinematic gui? + if ( !cinematicHud ) { + const char* temp; + if ( spawnArgs.GetString ( "cinematicHud", "", &temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + } + + // Have the cinematic hud start + if ( cinematicHud ) { + cinematicHud->Activate ( true, gameLocal.time ); + cinematicHud->HandleNamedEvent ( "cinematicStart" ); +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( cvarSystem->GetCVarInteger( "r_aspectRatio" ) != 0 ) { + cinematicHud->HandleNamedEvent ( "hideLetterbox" ); + } +// RAVEN END + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + if ( weaponEnabled && weapon ) { + //this preSave kills all effects and sounds that we don't need lingering around. + weapon->PreSave(); + weapon->EnterCinematic(); + } + + // Reset state flags + memset ( &pfl, 0, sizeof(pfl) ); + pfl.onGround = true; + pfl.dead = (health <= 0); +} + +/* +=============== +idPlayer::ExitCinematic +=============== +*/ +void idPlayer::ExitCinematic( void ) { + Show(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(false); +// RAVEN END + + if ( weaponEnabled && weapon ) { + //and this will restore them! + weapon->PostSave(); + weapon->ExitCinematic(); + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + UpdateState(); +} + +/* +=============== +idPlayer::SkipCinematic +=============== +*/ +bool idPlayer::SkipCinematic( void ) { + StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL ); + return gameLocal.SkipCinematic(); +} + +/* +===================== +idPlayer::UpdateConditions +===================== +*/ +void idPlayer::UpdateConditions( void ) { + idVec3 velocity; + float fallspeed; + float forwardspeed; + float sidespeed; + + // minus the push velocity to avoid playing the walking animation and sounds when riding a mover + velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity(); + fallspeed = velocity * physicsObj.GetGravityNormal(); + + if ( influenceActive ) { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } else if ( gameLocal.time - lastDmgTime < 500 ) { + forwardspeed = velocity * viewAxis[ 0 ]; + sidespeed = velocity * viewAxis[ 1 ]; + pfl.forward = pfl.onGround && ( forwardspeed > 20.01f ); + pfl.backward = pfl.onGround && ( forwardspeed < -20.01f ); + pfl.strafeLeft = pfl.onGround && ( sidespeed > 20.01f ); + pfl.strafeRight = pfl.onGround && ( sidespeed < -20.01f ); + } else if ( xyspeed > MIN_BOB_SPEED ) { + pfl.forward = pfl.onGround && ( usercmd.forwardmove > 0 ); + pfl.backward = pfl.onGround && ( usercmd.forwardmove < 0 ); + pfl.strafeLeft = pfl.onGround && ( usercmd.rightmove < 0 ); + pfl.strafeRight = pfl.onGround && ( usercmd.rightmove > 0 ); + } else { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } + + pfl.run = 1; + pfl.dead = ( health <= 0 ); +} + +/* +================== +idPlayer::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) { + // force a blink + blink_time = 0; + + // play the fire animation + pfl.weaponFired = true; + + // Bias the intent direction more heavily due to firing + BiasIntentDir( viewAxis[0]*100.0f, 1.0f ); + + // update view feedback + playerView.WeaponFireFeedback( weaponDef ); +} + +/* +=============== +idPlayer::StopFiring +=============== +*/ +void idPlayer::StopFiring( void ) { + pfl.attackHeld = false; + pfl.weaponFired = false; + pfl.reload = false; + if ( weapon ) { + weapon->EndAttack(); + } +} + +/* +=============== +idPlayer::FireWeapon +=============== +*/ +void idPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + +//RITUAL BEGIN + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + return; + } +//RITUAL END + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ); + return; + } + + if ( !hiddenWeapon && weapon->IsReady() ) { + // cheap hack so in MP the LG isn't allowed to fire in the short lapse while it goes from Fire -> Idle before changing to another weapon + // this gimps the weapon a lil bit but is consistent with the visual feedback clients are getting since 1.0 + bool noFireWhileSwitching = false; + noFireWhileSwitching = ( gameLocal.isMultiplayer && idealWeapon != currentWeapon && weapon->NoFireWhileSwitching() ); + if ( !noFireWhileSwitching ) { + if ( weapon->AmmoInClip() || weapon->AmmoAvailable() ) { + pfl.attackHeld = true; + weapon->BeginAttack(); + } else { + pfl.attackHeld = false; + pfl.weaponFired = false; + StopFiring(); + NextBestWeapon(); + } + } else { + StopFiring(); + } + } + // If reloading when fire is hit cancel the reload + else if ( weapon->IsReloading() ) { + weapon->CancelReload(); + } +/* twhitaker: removed this at the request of Matt Vainio. + if ( !gameLocal.isMultiplayer ) { + if ( hud && tipUp ) { + HideTip(); + } + // may want to track with with a bool as well + // keep from looking up named events so often + if ( objectiveSystem && objectiveUp ) { + HideObjective(); + } + } +*/ + if( hud && weaponChangeIconsUp ) { + hud->HandleNamedEvent( "weaponFire" ); + // nrausch: objectiveSystem does not necessarily exist (in mp it doesn't) + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "weaponFire" ); + } + weaponChangeIconsUp = false; + } +} + +/* +=============== +idPlayer::CacheWeapons +=============== +*/ +void idPlayer::CacheWeapons( void ) { + idStr weap; + int w; + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + if ( inventory.weapons & ( 1 << w ) ) { + if ( !GetWeaponDef ( w ) ) { + inventory.weapons &= ~( 1 << w ); + } else { + rvWeapon::CacheWeapon( spawnArgs.GetString( va( "def_weapon%d", w ) ) ); + } + } + } +} + +/* +=============== +idPlayer::Give +=============== +*/ +bool idPlayer::Give( const char *statname, const char *value, bool dropped ) { + int amount; + + if ( pfl.dead ) { + return false; + } + + if ( IsInVehicle ( ) ) { + vehicleController.Give ( statname, value ); + } + + int boundaryHealth = inventory.maxHealth; + int boundaryArmor = inventory.maxarmor; + if( PowerUpActive( POWERUP_GUARD ) ) { + boundaryHealth = inventory.maxHealth / 2; + boundaryArmor = inventory.maxarmor / 2; + } + if( PowerUpActive( POWERUP_SCOUT ) ) { + boundaryArmor = 0; + } + if ( gameLocal.isMultiplayer ) { + //In MP, you can get twice your max from pickups + boundaryArmor *= 2; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= boundaryHealth ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth ) { + health = boundaryHealth; + } + } + } else if ( !idStr::Icmp( statname, "bonushealth" ) ) { + // allow health over max health + if ( health >= boundaryHealth * 2 ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth * 2 ) { + health = boundaryHealth * 2; + } + } + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( inventory.armor >= boundaryArmor ) { + return false; + } + amount = atoi( value ); + + inventory.armor += amount; + if ( inventory.armor > boundaryArmor ) { + inventory.armor = boundaryArmor; + } + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airTics >= pm_airTics.GetInteger() ) { + return false; + } + airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger(); + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } else if ( !idStr::Icmp ( statname, "weaponmod" ) ) { + if( !idStr::Icmp( value, "all" ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + GiveWeaponMods( i, 0xFFFFFFFF ); + } + } + } else { + const char* pos = value; + + while( pos != NULL ) { + const char* end = strchr( pos, ',' ); + int len; + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponMod ( pos, 0, len ); + GiveWeaponMod ( weaponMod ); + + pos = end; + } + } + } else { + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true, dropped ); + } + return true; +} + +/* +=============== +idPlayer::GiveItem + +Returns false if the item shouldn't be picked up +=============== +*/ +bool idPlayer::GiveItem( idItem *item ) { + int i; + const idKeyValue *arg; + idDict attr; + bool gave; + + bool dropped = item->spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && spectating ) { + return false; + } + + item->GetAttributes( attr ); + + if( gameLocal.isServer || !gameLocal.isMultiplayer ) { + gave = false; + bool skipWeaponKey = false; + bool skipRestOfKeys = false; + if ( gameLocal.IsMultiplayer() ) { + dropped = item->spawnArgs.GetBool( "dropped" ); + if ( item->spawnArgs.FindKey( "weaponclass" ) ) { + //this is really fucking lame, but + //this is the only way we know we're trying + //to pick up a weapon before we blindly start + //processesing the attribute arguments in + //whatever order they're in below. We need + //to not process any at all if we're not allowed + //to pick up the weapon in the first place! + arg = attr.FindKey( "weapon" ); + if ( arg ) { + skipWeaponKey = true; + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } else if ( !dropped//not a dropped weapon + && gameLocal.IsWeaponsStayOn() ) { + //if failed to give weapon, don't give anything else with the weapon + skipRestOfKeys = true; + } + } + } + } + if ( !skipRestOfKeys ) { + for( i = 0; i < attr.GetNumKeyVals(); i++ ) { + arg = attr.GetKeyVal( i ); + if ( skipWeaponKey && arg->GetKey() == "weapon" ) { + //already processed this above + continue; + } + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } + } + } + + // hack - powerups call into this code to let them give stuff based on inv_ keywords + // for powerups that don't have any ammo/etc to give to the player, we still want to + // display the inv_name on the hud + // since idItemPowerup::GiveToPlayer() handles whether or not a player gets a powerup, + // we can override gave here for powerups + if ( !gave && !item->IsType( idItemPowerup::GetClassType() ) ) { + return false; + } + } else { + gave = true; + } + + arg = item->spawnArgs.MatchPrefix( "inv_ammo_", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "ammoPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_health", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL ); + if ( arg && hud ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + if ( gameLocal.isMultiplayer ) { + UpdateHudWeapon( ); + } else { + //so weapon mods highlight the correct weapon when received + int weapon = SlotForWeapon ( arg->GetValue() ); + UpdateHudWeapon( weapon ); + } + hud->HandleNamedEvent( "weaponPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_armor", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "armorPulse" ); + } + +// GiveDatabaseEntry ( &item->spawnArgs ); + + // Show the item pickup on the hud + if ( hud ) { + idStr langToken = item->spawnArgs.GetString( "inv_name" ); + hud->SetStateString ( "itemtext", common->GetLocalizedString( langToken ) ); + hud->SetStateString ( "itemicon", item->spawnArgs.GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } +//RITUAL BEGIN + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + gameLocal.mpGame.RedrawLocalBuyMenu(); +//RITUAL END + + return gave; +} + +/* +=============== +idPlayer::PowerUpModifier +=============== +*/ +float idPlayer::PowerUpModifier( int type ) { + float mod = 1.0f; + + if ( PowerUpActive( POWERUP_QUADDAMAGE ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_PROJECTILE_DEATHPUSH: { + mod *= 2.0f; + break; + } + } + } + + if ( PowerUpActive( POWERUP_HASTE ) ) { + switch ( type ) { + case PMOD_SPEED: + mod *= 1.3f; + break; + + case PMOD_FIRERATE: + mod *= 0.7f; + break; + } + } + + // Arena CTF powerups + if( PowerUpActive( POWERUP_AMMOREGEN ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= 0.7f; + break; + } + } + } + + if( PowerUpActive( POWERUP_DOUBLER ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 2.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 2.0f; + break; + } + } + } + +//RITUAL BEGIN + if( PowerUpActive( POWERUP_TEAM_DAMAGE_MOD ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_FIRERATE: { + mod *= 0.80f; + break; + } + } + } +//RITUAL END + if( PowerUpActive( POWERUP_SCOUT ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= (2.0f / 3.0f); + break; + } + case PMOD_SPEED: { + mod *= 1.5f; + break; + } + } + } + + return mod; +} + +/* +=============== +idPlayer::PowerUpActive +=============== +*/ +bool idPlayer::PowerUpActive( int powerup ) const { + return ( inventory.powerups & ( 1 << powerup ) ) != 0; +} + +/* +=============== +idPlayer::StartPowerUpEffect +=============== +*/ +void idPlayer::StartPowerUpEffect( int powerup ) { + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_marine_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_marine_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_strogg_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_strogg_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_ONEFLAG: { + AddClientModel( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + PlayEffect( "fx_deadzone", animator.GetJointHandle( "origin" ), true ); + break; + } + case POWERUP_QUADDAMAGE: { + powerUpOverlay = quadOverlay; + + StopEffect( "fx_regeneration" ); + PlayEffect( "fx_quaddamage", animator.GetJointHandle( "chest" ), true ); + StartSound( "snd_quaddamage_idle", SND_CHANNEL_POWERUP_IDLE, 0, false, NULL ); + + // Spawn quad effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_quaddamage_crawl" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_QUADDAMAGE; + + break; + } + + case POWERUP_REGENERATION: { + + // when buy mode is enabled, we use the guard effect for team powerup regen ( more readable than everyone going red ) + if ( gameLocal.IsTeamPowerups() ) { + // don't setup the powerup on dead bodies, it will float up where the body is invisible and the orientation will be messed up + if ( teamHealthRegen == NULL ) { + if ( health <= 0 ) { + // we can't start it now, it will be floating where the hidden dead body is + teamHealthRegenPending = true; + } else { + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + powerUpOverlay = regenerationOverlay; + + StopEffect( "fx_quaddamage" ); + PlayEffect( "fx_regeneration", animator.GetJointHandle( "chest" ), true ); + + // Spawn regen effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_regeneration" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_REGENERATION; + } + + break; + } + + case POWERUP_HASTE: { + powerUpOverlay = hasteOverlay; + + hasteEffect = PlayEffect( "fx_haste", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), true ); + break; + } + + case POWERUP_INVISIBILITY: { + powerUpOverlay = invisibilityOverlay; + + powerUpSkin = declManager->FindSkin( spawnArgs.GetString( "skin_invisibility" ), false ); + break; + } + + case POWERUP_GUARD: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_guard", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_SCOUT: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_scout", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_AMMOREGEN: { + if ( gameLocal.IsTeamPowerups() ) { + if ( teamAmmoRegen == NULL ) { + if ( health <= 0 ) { + teamAmmoRegenPending = true; + } else { + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + break; + } + + case POWERUP_TEAM_DAMAGE_MOD: { + assert( gameLocal.IsTeamPowerups() ); + if ( teamDoubler == NULL ) { + if ( health <= 0 ) { + teamDoublerPending = true; + } else { + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + } + break; + } + + case POWERUP_DOUBLER: { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + break; + } + } +} + +/* +=============== +idPlayer::StopPowerUpEffect +=============== +*/ +void idPlayer::StopPowerUpEffect( int powerup ) { + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if( !( + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) + ) ) { + + powerUpOverlay = NULL; + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + switch( powerup ) { + case POWERUP_QUADDAMAGE: { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_quaddamage" ); + break; + } + case POWERUP_REGENERATION: { + if ( gameLocal.IsTeamPowerups() ) { + teamHealthRegenPending = false; + StopEffect( "fx_guard" ); + } else { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_regeneration" ); + } + break; + } + case POWERUP_HASTE: { + StopEffect( "fx_haste" ); + break; + } + case POWERUP_INVISIBILITY: { + powerUpSkin = NULL; + break; + } + case POWERUP_CTF_STROGGFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + break; + } + case POWERUP_CTF_MARINEFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + break; + } + case POWERUP_CTF_ONEFLAG: { + RemoveClientModel ( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + StopEffect( "fx_deadzone" ); + break; + } + case POWERUP_SCOUT: { + StopEffect( "fx_scout" ); + break; + } + case POWERUP_GUARD: { + StopEffect( "fx_guard" ); + break; + } + case POWERUP_TEAM_DAMAGE_MOD: + teamDoublerPending = false; + // fallthrough + case POWERUP_DOUBLER: { + StopEffect( "fx_doubler" ); + break; + } + case POWERUP_AMMOREGEN: { + teamAmmoRegenPending = false; + StopEffect( "fx_ammoregen" ); + break; + } + } +} + +/* +=============== +idPlayer::GivePowerUp +passiveEffectsOnly - GivePowerup() is used to restore effects on stale players coming +back into snapshot. We don't want to announce powerups in this case +(just re-start effects) +=============== +*/ +bool idPlayer::GivePowerUp( int powerup, int time, bool team ) { + if ( powerup < 0 || powerup >= POWERUP_MAX ) { + gameLocal.Warning( "Player given power up %i\n which is out of range", powerup ); + return false; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( powerup ); + msg.WriteBits( 1, 1 ); + // team flag only needed for POWERUP_AMMOREGEN + msg.WriteBits( team, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.GivePowerUp( this, powerup, time ); + + // only start client effects in the same instance + // play all stuff in instance 0 for server netdemo - atm other instances are not recorded + bool playClientEffects = ( ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && instance == 0 ) || + ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) ); + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_ONEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_QUADDAMAGE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_QUAD_DAMAGE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + + case POWERUP_REGENERATION: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + + // Have to test for this because buying the team regeneration powerup will cause + // this to get hit multiple times as the server distributes the powerups to the clients. + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_REGENERATION, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_HASTE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_HASTE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_INVISIBILITY: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_INVISIBILITY, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_GUARD: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + inventory.maxHealth = 200; + inventory.maxarmor = 200; + + break; + } + case POWERUP_SCOUT: { + inventory.armor = 0; + + break; + } + case POWERUP_AMMOREGEN: { + if ( team && gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_AMMOREGEN, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_TEAM_DAMAGE_MOD: { + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_DOUBLER, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } +//RITUAL BEGIN + case POWERUP_DEADZONE: { + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_122000" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + mphud->HandleNamedEvent( "main_notice" ); + } + } + break; + } +//RITUAL END + } + + // only start effects if in our instances and snapshot + if ( playClientEffects && !fl.networkStale ) { + StartPowerUpEffect( powerup ); + } + + return true; +} + +/* +============== +idPlayer::ClearPowerup +============== +*/ +void idPlayer::ClearPowerup( int i ) { + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( i ); + msg.WriteBits( 0, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.powerups &= ~( 1 << i ); + inventory.powerupEndTime[ i ] = 0; + + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if( !( + (inventory.powerups & ( 1 << POWERUP_TEAM_DAMAGE_MOD ) ) || + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) || + (inventory.powerups & ( 1 << POWERUP_DEADZONE ) ) + ) ) { + + powerUpOverlay = NULL; + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + StopPowerUpEffect( i ); +} + +/* +============== +idPlayer::GetArenaPowerupString +============== +*/ +const char* idPlayer::GetArenaPowerupString ( void ) { + if ( PowerUpActive( POWERUP_SCOUT ) ) { + return "^isct"; + } else if ( PowerUpActive( POWERUP_GUARD ) ) { + return "^igrd"; + } else if ( PowerUpActive( POWERUP_DOUBLER ) ) { + return "^idbl"; + } else if ( PowerUpActive( POWERUP_AMMOREGEN ) ) { + return "^irgn"; + } else { + return "^ixxx"; + } +} + +/* +============== +idPlayer::UpdatePowerUps +============== +*/ +void idPlayer::UpdatePowerUps( void ) { + int i; + int index; + int wearoff; + + idUserInterface *hud = idPlayer::hud; + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + hud = gameLocal.GetDemoHud(); + } + } else { + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud && p->mphud ); + hud = p->hud; + } + } + + wearoff = -1; + if ( hud ) { + hud->HandleNamedEvent( "clearPowerups" ); + } + + for ( i = 0, index = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + if ( hud ) { + // Play the wearoff sound for the powerup that is closest to wearing off + if ( ( wearoff == -1 || inventory.powerupEndTime[i] < inventory.powerupEndTime[wearoff] ) && inventory.powerupEndTime[i] != -1 ) { + wearoff = i; + } + + // for flags, set the powerup_flag_* variables, which give us a special pulsing flag display + if( i == POWERUP_CTF_MARINEFLAG || i == POWERUP_CTF_STROGGFLAG || i == POWERUP_CTF_ONEFLAG ) { + hud->SetStateInt( "powerup_flag_visible", 1 ); + } else { + hud->SetStateString ( va("powerup%d_icon", index ), GetPowerupDef(i)->dict.GetString ( "inv_icon" ) ); + hud->SetStateString ( va("powerup%d_time", index ), inventory.powerupEndTime[i] == -1 ? "" : va( "%d" , (int)MS2SEC(inventory.powerupEndTime[i] - gameLocal.time) + 1 ) ); + hud->SetStateInt ( va( "powerup%d_visible", index ), 1 ); + index++; + } + } + + continue; + } else if ( inventory.powerupEndTime[ i ] != -1 && gameLocal.isServer ) { + // This particular powerup needs to respawn in a special way. + if ( i == POWERUP_DEADZONE ) { + gameLocal.mpGame.GetGameState()->SpawnDeadZonePowerup(); + } + // Powerup time has run out so take it away from the player + ClearPowerup( i ); + } + } + + // PLay wear off sound? + if ( gameLocal.isNewFrame && wearoff != -1 ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) < POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) / POWERUP_BLINK_TIME != ( inventory.powerupEndTime[wearoff] - gameLocal.previousTime ) / POWERUP_BLINK_TIME ) { + StartSound ( "snd_powerup_wearoff", SND_CHANNEL_POWERUP, 0, false, NULL ); + } + } + } + + // Reneration regnerates faster when less than maxHealth and can regenerate up to maxHealth * 2 + if ( gameLocal.time > nextHealthPulse ) { +// RITUAL BEGIN +// squirrel: health regen only applies if you have positive health + if( health > 0 ) { + if ( PowerUpActive ( POWERUP_REGENERATION ) || PowerUpActive ( POWERUP_GUARD ) ) { + int healthBoundary = inventory.maxHealth; // health will regen faster under this value, slower above + int healthTic = 15; + + if( PowerUpActive ( POWERUP_GUARD ) ) { + // guard max health == 200, so set the boundary back to 100 + healthBoundary = inventory.maxHealth / 2; + if( PowerUpActive (POWERUP_REGENERATION) ) { + healthTic = 30; + } + } + + if ( health < healthBoundary ) { + // only actually give health on the server + if( gameLocal.isServer ) { + health += healthTic; + if ( health > (healthBoundary * 1.1f) ) { + health = healthBoundary * 1.1f; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( health < (healthBoundary * 2) ) { + if( gameLocal.isServer ) { + health += healthTic / 3; + if ( health > (healthBoundary * 2) ) { + health = healthBoundary * 2; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + // Health above max technically isnt a powerup but functions as one so handle it here + } else if ( health > inventory.maxHealth && gameLocal.isServer ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + health--; + } + } +// RITUAL END + } + + // Regenerate ammo + if( gameLocal.isServer && PowerUpActive( POWERUP_AMMOREGEN ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( inventory.weapons & ( 1 << i ) ) { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( i ); + int max = inventory.StartingAmmoForWeaponIndex( i ); + + // only regen ammo if lower than starting + if( gameLocal.time > nextAmmoRegenPulse[ ammoIndex ] && inventory.ammo[ ammoIndex ] < max ) { + int step = inventory.AmmoRegenStepForWeaponIndex( i ); + int time = inventory.AmmoRegenTimeForWeaponIndex( i ); + + if( inventory.ammo[ ammoIndex ] < max ) { + inventory.ammo[ ammoIndex ] += step; + } + if( inventory.ammo[ ammoIndex ] >= max ) { + inventory.ammo[ ammoIndex ] = max; + } + + nextAmmoRegenPulse[ ammoIndex ] = gameLocal.time + time; + } + } + } + } + + // Tick armor down if greater than max armor + if ( !gameLocal.isClient && gameLocal.time > nextArmorPulse ) { + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse += ARMOR_PULSE; + inventory.armor--; + } + } + + // Assign the powerup skin as long as we are alive + if ( health > 0 ) { + if ( powerUpSkin ) { + renderEntity.customSkin = powerUpSkin; + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + + if( weaponWorldModel ) { + weaponWorldModel->SetSkin( powerUpSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( powerUpSkin ); + } + } else { + renderEntity.customSkin = skin; + + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetOverlayShader( powerUpOverlay ); + } + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + if( weaponWorldModel ) { + weaponWorldModel->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + renderEntity.overlayShader = powerUpOverlay; + } else { + renderEntity.overlayShader = NULL; + powerUpOverlay = NULL; + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = NULL; + } + + if ( renderEntity.customSkin != gibSkin ) { + if ( influenceSkin ) { + renderEntity.customSkin = influenceSkin; + } else { + renderEntity.customSkin = skin; + } + } + } + + // Spawn quad effect + if( PowerUpActive( powerupEffectType ) && powerupEffect && gameLocal.time >= powerupEffectTime ) { + rvClientCrawlEffect* effect = new rvClientCrawlEffect( powerupEffect, this, 100, &powerupEffectJoints ); + effect->Play ( gameLocal.time, false ); + effect->GetRenderEffect()->suppressSurfaceInViewID = entityNumber+1; + powerupEffectTime = gameLocal.time + 400; + } + + // Attenuate haste effect + if ( hasteEffect ) { + hasteEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if ( flagEffect ) { + flagEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if( arenaEffect ) { + arenaEffect->SetOrigin( vec3_zero ); + } +} + +/* +=============== +idPlayer::ClearPowerUps +=============== +*/ +void idPlayer::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( PowerUpActive( i ) ) { + ClearPowerup( i ); + } + } + + inventory.ClearPowerUps(); +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int mods ) { + inventory.weaponMods[currentWeapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int weapon, int mods ) { + inventory.weaponMods[weapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +============== +idPlayer::GiveWeaponMod +============== +*/ +void idPlayer::GiveWeaponMod ( const char* weaponmod ) { + const idDict* modDict; + const idDict* weaponDict; + const char* weaponClass; + int m; + int weaponIndex; + + // Grab the weapon mod dictionary + modDict = gameLocal.FindEntityDefDict ( weaponmod, false ); + if ( !modDict ) { + gameLocal.Warning ( "Invalid weapon modification def specified '%s'", weaponmod ); + return; + } + + // Get the weapon it modifies + weaponClass = modDict->GetString ( "weapon" ); + weaponDict = gameLocal.FindEntityDefDict ( weaponClass, false ); + if ( !weaponDict ) { + gameLocal.Warning ( "Invalid weapon classname '%s' specified on weapon modification '%s'", weaponClass, weaponmod ); + return; + } + + weaponIndex = SlotForWeapon ( weaponClass ); + + // Find the index of the weapon mod + for ( m = 0; m < MAX_WEAPONMODS; m ++ ) { + const char* mod; + mod = weaponDict->GetString ( va("def_mod%d",m+1) ); + if ( !mod || !*mod ) { + break; + } + + if ( !idStr::Icmp ( weaponmod, mod ) ) { + if ( !(inventory.weaponMods[weaponIndex] & (1<GetLocalizedString( item->GetString( "inv_name" ) ); + hud->SetStateString ( "itemtext", itemName ); + hud->SetStateString ( "itemicon", item->GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } + + return true; +} + +/* +============== +idPlayer::UpdateObjectiveInfo +============== + */ +void idPlayer::UpdateObjectiveInfo( void ) { + if ( objectiveSystem == NULL ) { + return; + } + objectiveSystem->SetStateString( "objective1", "" ); + objectiveSystem->SetStateString( "objective2", "" ); + objectiveSystem->SetStateString( "objective3", "" ); + +// RAVEN BEGIN +// mekberg: swap objective positions to allow for stack-like appearance. + int objectiveCount = inventory.objectiveNames.Num(); + for ( int i = 0; i < inventory.objectiveNames.Num(); i++, objectiveCount-- ) { + objectiveSystem->SetStateString( va( "objective%i", objectiveCount ), "1" ); + objectiveSystem->SetStateString( va( "objectivetitle%i", objectiveCount ), inventory.objectiveNames[i].title.c_str() ); + objectiveSystem->SetStateString( va( "objectivetext%i", objectiveCount), inventory.objectiveNames[i].text.c_str() ); + objectiveSystem->SetStateInt( va( "objectiveLength%i", objectiveCount), inventory.objectiveNames[i].text.Length() ); + objectiveSystem->SetStateString( va( "objectiveshot%i", objectiveCount), inventory.objectiveNames[i].screenshot.c_str() ); + } + objectiveSystem->SetStateBool( "noObjective", !objectiveCount ); +// RAVEN END + + objectiveSystem->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::GiveObjective +=============== +*/ +void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) { + idObjectiveInfo info; +// RAVEN BEGIN + info.title = common->GetLocalizedString( title ); + info.text = common->GetLocalizedString( text ); +// RAVEN END + info.screenshot = screenshot; + inventory.objectiveNames.Append( info ); + if ( showNewObjectives ) { + ShowObjective( "newObjective" ); + } + if ( objectiveSystem ) { + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + } +} + +/* +=============== +idPlayer::CompleteObjective +=============== +*/ +void idPlayer::CompleteObjective( const char *title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); +// RAVEN END + int c = inventory.objectiveNames.Num(); + for ( int i = 0; i < c; i++ ) { + if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) { + inventory.objectiveNames.RemoveIndex( i ); + break; + } + } + ShowObjective( "newObjectiveComplete" ); + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "newObjectiveComplete" ); + } + + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } +} + +/* +=============== +idPlayer::FailObjective +=============== +*/ +void idPlayer::FailObjective ( const char* title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); + +// mekberg: prevent save games if objective failed. + gameLocal.sessionCommand = "objectiveFailed "; + gameLocal.sessionCommand += title; +// RAVEN END + HideObjective ( ); + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "objectiveFailed" ); + } + if( IsInVehicle() ) { + vehicleController.GetVehicle()->EjectAllDrivers(); + } + fl.takedamage = true; + pfl.objectiveFailed = true; +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; +#else + playerView.Fade( colorBlack, 12000 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict *idPlayer::FindInventoryItem( const char *name ) { + for ( int i = 0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname && *iname ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + return inventory.items[i]; + } + } + } + return NULL; +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( const char *name ) { + idDict *item = FindInventoryItem(name); + if ( item ) { + RemoveInventoryItem( item ); + } +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( idDict *item ) { + inventory.items.Remove( item ); + delete item; +} + +/* +=============== +idPlayer::GiveItem +=============== +*/ +void idPlayer::GiveItem( const char *itemname ) { + idDict args; + + args.Set( "classname", itemname ); + args.Set( "owner", name.c_str() ); + args.Set( "givenToPlayer", va( "%d", entityNumber ) ); + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // check if this is a weapon + if( !idStr::Icmpn( itemname, "weapon_", 7 ) ) { + int weaponIndex = SlotForWeapon( itemname ); + if( weaponIndex >= 0 && weaponIndex < MAX_WEAPONS ) + { + int weaponIndexBit = ( 1 << weaponIndex ); + inventory.weapons |= weaponIndexBit; + inventory.carryOverWeapons |= weaponIndexBit; + carryOverCurrentWeapon = weaponIndex; + } + } + + // if the player is dead, credit him with this armor or ammo purchase + if ( health <= 0 ) { + if( !idStr::Icmp( itemname, "item_armor_small" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_LIGHT; + } else if( !idStr::Icmp( itemname, "item_armor_large" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_HEAVY; + } else if( !idStr::Icmp( itemname, "ammorefill" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_AMMO; + } + } else { + if ( !idStr::Icmp( itemname, "ammorefill" ) ) { + int i; + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + int a = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( rvWeapon::GetAmmoNameForIndex( i ), 0 ); + inventory.ammo[i] += a; + if ( inventory.ammo[i] > inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) { + inventory.ammo[i] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ); + } + } + } + } + } + + // spawn the item if the player is alive + if ( health > 0 && idStr::Icmp( itemname, "ammorefill" ) ) { + gameLocal.SpawnEntityDef( args ); + } + +} + +/* +================== +idPlayer::SlotForWeapon +================== +*/ +int idPlayer::SlotForWeapon( const char *weaponName ) { + int i; + + for( i = 0; i < MAX_WEAPONS; i++ ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + return i; + } + } + + // not found + return -1; +} + +/* +=============== +idPlayer::Reload +=============== +*/ +void idPlayer::Reload( void ) { + if ( gameLocal.isClient || spectating || gameLocal.inCinematic || influenceActive || !weapon ) { + return; + } + + weapon->Reload(); +} + +#ifdef _XENON +/* +=============== +idPlayer::ScheduleWeaponSwitch +=============== +*/ +void idPlayer::ScheduleWeaponSwitch(int weapon) +{ + CancelEvents(&EV_Player_SelectWeapon); + hud->SetStateInt("player_selectedWeapon", weapon-1); + hud->HandleNamedEvent( "weaponSelect" ); + + // nrausch: support for turning the weapon change ui on and off + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( false ); + } + + if ( weapon > 0 ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", weapon-1 ) ); + PostEventSec(&EV_Player_SelectWeapon, 0.25f, weap); + } +} +#endif + +/* +=============== +idPlayer::ShowCrosshair +=============== +*/ +void idPlayer::ShowCrosshair( void ) { + if ( !weaponEnabled ) { + return; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::HideCrosshair +=============== +*/ +void idPlayer::HideCrosshair( void ) { + if ( cursor ) { + cursor->HandleNamedEvent( "crossHide" ); + } +} + +/* +=============== +idPlayer::LastWeapon +=============== +*/ +void idPlayer::LastWeapon( void ) { + // Dont bother if previousWeapon is invalid or the player is spectating + if ( spectating || previousWeapon < 0 ) { + return; + } + + // Do we have the weapon still? + if ( !(inventory.weapons & ( 1 << previousWeapon ) ) ) { + return; + } + + idealWeapon = previousWeapon; +} + +/* +=============== +idPlayer::NextBestWeapon +=============== +*/ +void idPlayer::NextBestWeapon( void ) { + const char *weap; + int w = MAX_WEAPONS; + + if ( gameLocal.isClient || !weaponEnabled ) { + return; + } + + while ( w > 0 ) { + w--; + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) { + continue; + } + if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) { + continue; + } + break; + } + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::NextWeapon +=============== +*/ +void idPlayer::NextWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + +#ifdef _XENON + + w = idealWeapon; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + if ( entityNumber == gameLocal.localClientNum ) { + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( true ); + } + } + + if ( gameLocal.isClient ) { + return; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#else + + if ( gameLocal.isClient ) { + return; + } + + w = idealWeapon; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#endif +// RAVEN END +} + +/* +=============== +idPlayer::PrevWeapon +=============== +*/ +void idPlayer::PrevWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + +#ifdef _XENON + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + if ( entityNumber == gameLocal.localClientNum ) { + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( true ); + } + } + + if ( gameLocal.isClient ) { + return; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#else + + if ( gameLocal.isClient ) { + return; + } + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +#endif +// RAVEN END +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( const char *weapon_name ) { + Event_SelectWeapon( weapon_name ); +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( int num, bool force ) { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + weap = spawnArgs.GetString( va( "def_weapon%d", num ) ); + if ( !weap[ 0 ] ) { + gameLocal.Warning( "Invalid weapon def_weapon%d\n", num ); + return; + } + + // cycle in-between weapons + // if a weapon_def has a "def_weapon_swap" keyvalue pointing to another + // weapon, hitting that impulse twice will cycle to the target swap. + if( num == currentWeapon ) { + const idDict* weapDict = gameLocal.FindEntityDefDict( weap, false ); + + if( weapDict == NULL ) { + gameLocal.Warning( "Invalid weapon entity %s\n", weap ); + return; + } + +// RAVEN BEGIN +// nrausch: we have no need for weapon swapping on the xenon, and it gets in the way of the quick weapon select ui +#ifndef _XENON + const char* destWeapon = weapDict->GetString( "def_weapon_swap", NULL ); + + if( destWeapon != NULL ) { + int swapNum = SlotForWeapon( destWeapon ); + if( swapNum == -1 ) { + gameLocal.Warning( "Swap weapon for %s (%s) is invalid", weap, destWeapon ); + } else { + num = swapNum; + } + } +#endif +// RAVEN END + } + + if ( force || ( inventory.weapons & ( 1 << num ) ) ) { + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) { + return; + } + if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) { + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) { + return; + } + idealWeapon = previousWeapon; +/* NO PDA yet + } else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) { + ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true ); + return; +*/ + } else { + idealWeapon = num; + } + UpdateHudWeapon(); + } +} + +/* +================= +idPlayer::DropItem +================= +*/ +idEntity* idPlayer::DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.SetFloat ( "angle", 360.0f * gameLocal.random.RandomFloat ( ) ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + // If a velocity was given then just use that, otherwise randomly throw it around + if ( velocity != vec3_origin ) { + ent->GetPhysics()->SetLinearVelocity ( velocity ); + } else { + idVec3 vel; + float ang; + ang = idMath::TWO_PI * gameLocal.random.RandomFloat(); + vel[0] = PLAYER_ITEM_DROP_SPEED * idMath::Cos ( ang ); + vel[1] = PLAYER_ITEM_DROP_SPEED * idMath::Sin ( ang ); + vel[2] = PLAYER_ITEM_DROP_SPEED * 2; + ent->GetPhysics()->SetLinearVelocity ( vel ); + } + return ent; +} + +/* +================= +idPlayer::DropPowerups +================= +*/ +void idPlayer::DropPowerups( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + // These powerups aren't dropped + if ( i >= POWERUP_TEAM_AMMO_REGEN && i <= POWERUP_TEAM_DAMAGE_MOD ) + continue; + + // Don't drop this either with buying enabled. + if ( i == POWERUP_REGENERATION && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + continue; + + /// Don't drop arena rune powerups in non-Arena modes + if( gameLocal.gameType != GAME_ARENA_CTF ) + { + if( i == POWERUP_AMMOREGEN || + i == POWERUP_GUARD || + i == POWERUP_DOUBLER || + i == POWERUP_SCOUT ) + { + continue; + } + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + if( def->dict.GetBool( "nodrop" ) ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + args.SetInt( "instance", GetInstance() ); + item = DropItem ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +idPlayer::ResetFlag +================= +*/ +idEntity* idPlayer::ResetFlag ( const char* itemClass, const idDict& customArgs ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.Set( "reset", "1" ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + return ent; +} + +/* +================= +idPlayer::RespawnFlags +================= +*/ +void idPlayer::RespawnFlags ( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = POWERUP_CTF_MARINEFLAG; i < POWERUP_CTF_ONEFLAG; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + item = ResetFlag ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +DropWeapon +================= +*/ +void idPlayer::DropWeapon( void ) { + idEntity* item; + idDict args; + const char* itemClass; + + assert( !gameLocal.isClient ); + + if( !gameLocal.isMultiplayer ) { + return; + } + +// RITUAL BEGIN +// squirrel: don't drop weapons in Buying modes unless "always drop" is on + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && !gameLocal.serverInfo.GetBool( "si_dropWeaponsInBuyingModes" ) ) { + return; + } +// RITUAL END + + if ( spectating || weaponGone || !weapon ) { + return; + } + + // Make sure the weapon is droppable + itemClass = weapon->spawnArgs.GetString ( "def_dropItem" ); + if ( !itemClass || !*itemClass ) { + return; + } + + // If still alive then the weapon is being thrown so start it a bit in front of the player + + // copy over the instance + args.SetInt( "instance", GetInstance() ); + + if ( health > 0 ) { + idVec3 forward; + idVec3 up; + viewAngles.ToVectors( &forward, NULL, &up ); + args.SetBool( "triggerFirst", true ); + item = DropItem ( itemClass, args, 250.0f*forward + 150.0f*up ); + } else { + item = DropItem ( itemClass, args ); + } + + // Drop the weapon + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop weapon '%s'", entityNumber, weapon->spawnArgs.GetString ( "def_dropItem" ) ); + return; + } + + // Since this weapon was dropped, replace any starting ammo values with real ammo values + const idKeyValue* keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_" ); + idDict newArgs; + while( keyval ) { + newArgs.Set( va( "inv_ammo_%s", keyval->GetKey().Right( keyval->GetKey().Length() - 15 ).c_str() ), keyval->GetValue().c_str() ); + item->spawnArgs.Set( keyval->GetKey(), "" ); + keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_", keyval ); + } + + item->spawnArgs.SetDefaults( &newArgs ); + + // Set the appropriate mods on the dropped item + int i; + int mods; + idStr out; + mods = weapon->GetMods ( ); + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + if ( mods & (1<spawnArgs.GetString ( va("def_mod%d", i+1) ); + } + } + if ( out.Length() ) { + item->spawnArgs.Set ( "inv_weaponmod", out ); + } + + // Make sure the weapon removes itself over time. + item->PostEventMS ( &EV_Remove, WEAPON_DROP_TIME ); + + // Delay aquire since the weapon is being thrown + if ( health > 0 ) { + item->PostEventMS ( &EV_Activate, 500, item ); + inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 ); + NextWeapon(); + } +} + +/* +=============== +idPlayer::ActiveGui +=============== +*/ +idUserInterface *idPlayer::ActiveGui( void ) { +#ifdef _XENON + if ( objectiveSystemOpen ) { + return 0; + } +#endif + return focusUI; +} + +/* +=============== +idPlayer::Weapon_Combat +=============== +*/ +void idPlayer::Weapon_Combat( void ) { + + if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + if ( weapon ) { + weapon->RaiseWeapon(); + + if ( weapon->IsReloading() ) { + if ( !pfl.reload ) { + pfl.reload = true; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Reload", 4 ); + UpdateState(); + } + } else { + pfl.reload = false; + } + } + + if ( idealWeapon != currentWeapon ) { + if ( !weapon || weaponCatchup ) { + assert( gameLocal.isClient ); + weaponGone = false; + SetWeapon( idealWeapon ); + + weapon->NetCatchup(); + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + } else { + if ( weapon->IsReady() || weapon->IsReloading() ) { + weapon->PutAway(); + } + + if ( weapon->IsHolstered() && weaponViewModel ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + SetWeapon( idealWeapon ); + + weapon->Raise(); + } + } + } else { + weaponGone = false; + if ( weapon->IsHolstered() ) { + if ( !weapon->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else { + weapon->Raise(); + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 3 ); + } + } + } + + weaponCatchup = false; + + // check for attack + pfl.weaponFired = false; + if ( !influenceActive ) { + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + pfl.attackHeld = false; + weapon->EndAttack(); + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + UpdateHudWeapon(); + } + + // update our ammo clip in our inventory + if ( gameLocal.GetLocalPlayer() == this && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + if ( hud && ( currentWeapon == idealWeapon ) ) { + UpdateHudAmmo( hud ); + } + } +} + +/* +=============== +idPlayer::Weapon_Vehicle +=============== +*/ +void idPlayer::Weapon_Vehicle( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + ProcessEvent ( &AI_EnterVehicle, focusEnt.GetEntity() ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_Usable +=============== +*/ +void idPlayer::Weapon_Usable( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + focusEnt->ProcessEvent ( &EV_Activate, this ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_NPC +=============== +*/ +void idPlayer::Weapon_NPC( void ) { + + flagCanFire = false; + + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + + if ( currentWeapon ) { + StopFiring(); + } + + if ( !focusEnt || focusEnt->health <= 0 ) { + ClearFocus ( ); + return; + } + + if ( talkCursor && ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + buttonMask |= BUTTON_ATTACK; + if ( !talkingNPC ) { + idAI *focusAI = static_cast(focusEnt.GetEntity()); + if ( focusAI ) { + focusAI->TalkTo( this ); + talkingNPC = focusAI; + } + } + } else if ( currentWeapon == SlotForWeapon ( "weapon_blaster" ) ) { + Weapon_Combat(); + } +} + + +/* +=============== +idPlayer::LowerWeapon +=============== +*/ +void idPlayer::LowerWeapon( void ) { + if ( weapon && !weapon->IsHidden() ) { + weapon->LowerWeapon(); + } +} + +/* +=============== +idPlayer::RaiseWeapon +=============== +*/ +void idPlayer::RaiseWeapon( void ) { + if ( weapon && weapon->IsHidden() ) { + weapon->RaiseWeapon(); + } +} + +/* +=============== +idPlayer::WeaponLoweringCallback +=============== +*/ +void idPlayer::WeaponLoweringCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LowerWeapon", 3 ); + UpdateState(); +} + +/* +=============== +idPlayer::WeaponRisingCallback +=============== +*/ +void idPlayer::WeaponRisingCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 2 ); + UpdateState(); +} + +/* +=============== +idPlayer::Weapon_GUI +=============== +*/ +void idPlayer::Weapon_GUI( void ) { + + flagCanFire = false; + + if ( !objectiveSystemOpen ) { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon->LowerWeapon(); + } + + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { + sysEvent_t ev; + const char *command = NULL; + bool updateVisuals = false; + + idUserInterface *ui = ActiveGui(); + if ( ui ) { + ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +=============== +idPlayer::UpdateWeapon +=============== +*/ +void idPlayer::UpdateWeapon( void ) { + if ( health <= 0 ) { + return; + } + + assert( !spectating ); + + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( gameLocal.isClient && (!weaponViewModel || !weaponWorldModel) ) { + return; + } + + // always make sure the weapon is correctly setup before accessing it + if ( !weapon ) { + if ( idealWeapon != -1 ) { + SetWeapon( idealWeapon ); + weaponCatchup = false; + assert( weapon ); + } else { + return; + } + } + + + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { + HideTip(); + } + + // Make sure the weapon is in a settled state before preventing thinking due + // to drag entity. This way things like hitting reload, zoom, etc, wont crash + if ( g_dragEntity.GetInteger() ) { + StopFiring(); + flagCanFire = false; + if ( weapon ) { + weapon->LowerWeapon(); + } + dragEntity.Update( this ); + return; + } else if ( focusType == FOCUS_CHARACTER) { + flagCanFire = false; + Weapon_NPC(); + } else if ( focusType == FOCUS_VEHICLE ) { + flagCanFire = false; + Weapon_Vehicle(); + } else if ( focusType == FOCUS_USABLE || focusType == FOCUS_USABLE_VEHICLE ) { + flagCanFire = false; + Weapon_Usable(); + } else if ( ActiveGui() ) { + flagCanFire = false; + Weapon_GUI(); + } else if ( !hiddenWeapon ) { /* no pda yet || ( ( weapon_pda >= 0 ) && ( idealWeapon == weapon_pda ) ) ) { */ + flagCanFire = true; + Weapon_Combat(); + } + + // Range finder for debugging + if ( g_showRange.GetBool ( ) ) { + idVec3 start; + idVec3 end; + trace_t tr; + + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 50000.0f; + gameLocal.TracePoint( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); + + idVec3 forward; + idVec3 right; + idVec3 up; + viewAngles.ToVectors ( &forward, &right, &up ); + gameRenderWorld->DrawText( va( "%d qu", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d m", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f - up * 6.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d 2d", ( int )DistanceTo2d( tr.endpos ) ), start + forward * 100.0f + right * 25.0f - up * 12.0f, .2f, colorCyan, viewAxis ); + } + + if ( hiddenWeapon ) { + weapon->LowerWeapon(); + } + + // update weapon state, particles, dlights, etc + weaponViewModel->PresentWeapon( showWeaponViewModel ); +} + +/* +=============== +idPlayer::SpectateFreeFly +=============== +*/ +void idPlayer::SpectateFreeFly( bool force ) { + idPlayer *player; + idVec3 newOrig; + idVec3 spawn_origin; + idAngles spawn_angles; + + player = gameLocal.GetClientByNum( spectator ); + if ( force || gameLocal.time > lastSpectateChange ) { + spectator = entityNumber; + if ( player && player != this && !player->spectating && !player->IsInTeleport() ) { + newOrig = player->GetPhysics()->GetOrigin(); + if ( player->physicsObj.IsCrouching() ) { + newOrig[ 2 ] += pm_crouchviewheight.GetFloat(); + } else { + newOrig[ 2 ] += pm_normalviewheight.GetFloat(); + } + newOrig[ 2 ] += SPECTATE_RAISE; + idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + idVec3 start = player->GetPhysics()->GetOrigin(); + start[2] += pm_spectatebbox.GetFloat() * 0.5f; + trace_t t; + // assuming spectate bbox is inside stand or crouch box +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( player, t, start, newOrig, b, MASK_PLAYERSOLID, player ); +// RAVEN END + newOrig.Lerp( start, newOrig, t.fraction ); + SetOrigin( newOrig ); + idAngles angle = player->viewAngles; + angle[ 2 ] = 0; + SetViewAngles( angle ); + } else { + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + return; + } + spawn_origin[ 2 ] += pm_normalviewheight.GetFloat(); + spawn_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spawn_origin ); + SetViewAngles( spawn_angles ); + } + lastSpectateChange = gameLocal.time + 500; + } +} + +/* +=============== +idPlayer::SpectateCycle +=============== +*/ +void idPlayer::SpectateCycle( void ) { + idPlayer *player; + + if ( gameLocal.time > lastSpectateChange ) { + int latchedSpectator = spectator; + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + assert( player ); // never call here when the current spectator is wrong + // ignore other spectators + while ( latchedSpectator != spectator && player->spectating ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + } + lastSpectateChange = gameLocal.time + 500; + + if ( player ) { + UpdateHudWeapon( player->currentWeapon ); + } + } +} + +/* +=============== +idPlayer::UpdateSpectating +=============== +*/ +void idPlayer::UpdateSpectating( void ) { + assert( spectating ); + assert( !gameLocal.isClient ); + assert( IsHidden() ); + idPlayer *player; + if ( !gameLocal.isMultiplayer ) { + return; + } + player = gameLocal.GetClientByNum( spectator ); + if ( !player || ( player->spectating && player != this ) ) { + SpectateFreeFly( true ); + } else if ( usercmd.upmove > 0 && player && player != this ) { + // following someone and hit jump? release. + SpectateFreeFly( false ); + } else if ( usercmd.buttons & BUTTON_ATTACK && gameLocal.gameType != GAME_TOURNEY ) { + // tourney mode uses seperate cycling + SpectateCycle(); + } +} + +/* +=============== +idPlayer::HandleSingleGuiCommand +=============== +*/ +bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "addhealth" ) == 0 ) { + if ( entityGui && health < 100 ) { + int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); + int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health; + _health -= amt; + entityGui->spawnArgs.SetInt( "gui_parm1", _health ); + if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) { + entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health ); + } + health += amt; + if ( health > 100 ) { + health = 100; + } + } + return true; + } + + if ( token.Icmp( "ready" ) == 0 ) { + PerformImpulse( IMPULSE_17 ); + return true; + } + +// RAVEN BEGIN +// twhitaker: no more database system +/* if ( token.Icmp( "updateDB" ) == 0 ) { + UpdateDatabaseInfo(); + return true; + } + + if ( token.Icmp ( "filterDB" ) == 0 ) { + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( objectiveSystem ) { + objectiveSystem->SetStateString ( "dbFilter", token ); + UpdateDatabaseInfo ( ); + } + } +*/ +// RAVEN END + + if ( token.Icmp( "heal" ) == 0 && + entityGui->IsType( rvHealingStation::GetClassType() ) && + src->ReadToken( &token ) ) + { + rvHealingStation * station = static_cast< rvHealingStation * >( entityGui ); + + if ( token.Icmp( "begin" ) == 0 ) { + station->BeginHealing( this ); + } else if ( token.Icmp( "end" ) == 0 ) { + station->EndHealing( ); + } else { + return false; + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +============== +idPlayer::Collide +============== +*/ +bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *other; + other = gameLocal.entities[ collision.c.entityNum ]; + + // allow client-side prediction of item collisions for simple client effects + if ( gameLocal.isClient && !other->IsType( idItem::GetClassType() ) ) { + return false; + } + + + if ( other ) { + other->Signal( SIG_TOUCH ); + if ( !spectating ) { + if ( other->RespondsTo( EV_Touch ) ) { + other->ProcessEvent( &EV_Touch, this, &collision ); + } + } else { + if ( other->RespondsTo( EV_SpectatorTouch ) ) { + other->ProcessEvent( &EV_SpectatorTouch, this, &collision ); + } + } + } + return false; +} + +/* +================ +idPlayer::UpdateLocation + +Searches nearby locations +================ +*/ + void idPlayer::UpdateLocation( void ) { + if ( hud ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if ( locationEntity ) { + hud->SetStateString( "location", locationEntity->GetLocation() ); + } else { +// RAVEN BEGIN +// rjohnson: temp fix until id corrects slow downs created from constant string lookup +// hud->SetStateString( "location", common->GetLocalizedString( "#str_102911" ) ); + hud->SetStateString( "location", "Unidentified" ); +// RAVEN END + } + } +} + +/* +================ +idPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event +================ +*/ +void idPlayer::UpdateFocus( void ) { + + // These only need to be updated at the last tic + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idClipModel* clipModelList[ MAX_GENTITIES ]; + idClipModel* clip; + int listedClipModels; + idEntity* ent; + idUserInterface* oldBrackets; + int oldTalkCursor; + int i; + int j; + idVec3 start; + idVec3 end; + trace_t renderTrace, bboxTrace, allTrace; + guiPoint_t pt; + +// RAVEN BEGIN + // mekberg: removed check to see if attack was held. +// RAVEN END + + // No focus during cinimatics + if ( gameLocal.inCinematic ) { + return; + } + + // Focus has a limited time, make sure it hasnt expired + if ( focusTime && gameLocal.time > focusTime ) { + ClearFocus ( ); + } + + if ( spectating ) { + return; + } + + if ( g_perfTest_noPlayerFocus.GetBool() ) { + return; + } + +#ifndef _XENON + cvarSystem->SetCVarInteger( "pm_isZoomed", zoomed ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + +#ifdef _XENON + if ( cursor ) { + if ( weapon ) { + cursor->SetStateInt( "autoaim", weapon->AllowAutoAim() ? 1 : 0 ); + } else { + cursor->SetStateInt( "autoaim", 0 ); + } + } +#endif + + // Kill the focus brackets when their time has elapsed + oldBrackets = focusBrackets; + if ( focusBracketsTime && gameLocal.time > focusBracketsTime ) { + focusBrackets = NULL; + } + + oldTalkCursor = talkCursor; + talkCursor = 0; + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 768.0f; + + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + trace_t trace; + idVec3 end = start + viewAngles.ToForward() * 768.0f; + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + // no aim text if player is invisible + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) && ( !((idPlayer*)gameLocal.entities[ trace.c.entityNum ])->PowerUpActive( POWERUP_INVISIBILITY ) ) ) { + char* teammateHealth = ""; + idPlayer* p = static_cast(gameLocal.entities[ trace.c.entityNum ]); + + if( trace.c.entityNum != aimClientNum ) { + if( mphud ) { + mphud->SetStateString( "aim_text", va( "%s\n%s", gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_name" ), gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_clan" ) ) ); + if( gameLocal.IsTeamGame() ) { + mphud->SetStateInt( "aim_player_team", p->team ); + + if( p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + // when looking at a friendly, color the crosshair + if( cursor ) { + if( p->team == team ) { + cursor->HandleNamedEvent( "targetFriendly" ); + } else { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + + + mphud->HandleNamedEvent( "aim_text" ); + aimClientNum = trace.c.entityNum; + } + } else { + // update health + if( gameLocal.IsTeamGame() && p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + } + } else { + if( mphud && aimClientNum != -1 ) { + mphud->HandleNamedEvent( "aim_fade" ); + aimClientNum = -1; + } + if( cursor ) { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + +#ifdef _XENON + + bboxTrace.fraction = -1; + + bestEnemy = NULL; + if ( gameLocal.isMultiplayer ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + return; + } + + idEntity *ent = NULL; + idActor *actor = NULL; + bool inside; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + + if ( showDebug ) { + gameRenderWorld->DebugFrustum(colorRed, aimArea, false, 20); + } + + idLinkList *entities = &gameLocal.snapshotEntities; + if ( gameLocal.isServer ) { + entities = &gameLocal.spawnedEntities; + } + ent = entities->Next(); + while ( ent != NULL ) { + + bool isOk = true; + + if ( !ent->IsType(idPlayer::GetClassType()) ) { + isOk = false; + } else if ( gameLocal.IsTeamGame() && ((idPlayer*)ent)->team == team ) { + isOk = false; + } else if ( ((idPlayer*)ent)->instance != instance ) { + isOk = false; + } else if ( ((idPlayer*)ent)->spectating ) { + isOk = false; + } else if ( !idStr::Icmp(name.c_str(),ent->name.c_str()) ) { + isOk = false; + } else if ( ent->health <= 0 ) { + isOk = false; + } + + if ( !isOk ) { + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + continue; + } + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + if ( showDebug ) { + gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + } + inside = aimArea.IntersectsBounds(bounds); + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + } + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + if ( bestEnemy ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + trace_t trace; + + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + return; + } + +#endif + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); + + // Do autoaim + + +//RAVEN BEGIN +//asalmon: auto-aim for Xenon, finds a best enemy and changes the crosshairs. +#ifdef _XBOX + + bool doAimAssist = cvarSystem->GetCVarBool("pm_AimAssist") || gameLocal.isMultiplayer; + + bestEnemy = NULL; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + + if ( weapon && doAimAssist ) { + + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = bDist; +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + if ( showDebug ) { + gameRenderWorld->DebugFrustum( colorRed, aimArea, false, 20 ); + } + } + +#endif +//RAVEN END + + // dluetscher: added optimization to eliminate redundant traces + renderTrace.fraction = -1; + bboxTrace.fraction = -1; + allTrace.fraction = -1; + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + bool wasTargetFriendly = targetFriendly; + targetFriendly = false; + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); +//RITUAL BEGIN +//singlis: if ent is null, continue; + if(ent == NULL || ent->IsHidden()) { + continue; + } +//RITUAL END + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + // SP only + // basically what was happening was that heads, which are an idAFAttachment, were being used for the focusLength + // calculations, but that generates a different focus length than the body would (when you scan the crosshair back + // and forth between the head and body). This ends up looking like a bug when you are right at the threshold where + // the body will display the name and rank, but doesn't when you pitch up to aim at the head. Hence, using the body + // for this special case. + if ( !gameLocal.isMultiplayer && ent->IsType( idAFAttachment::GetClassType() )) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType()) ) { + focusLength = (body->GetPhysics()->GetOrigin() - start).LengthFast() - body->GetPhysics()->GetBounds().GetRadius(); + } + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + isFriendly = (static_cast( ent )->team == team); + } + + //change crosshair color if over a friendly + if ( !gameLocal.isMultiplayer + && focusType == FOCUS_NONE + && !g_crosshairCharInfoFar.GetBool() ) { + if ( focusLength < 512 ) { + bool newTargetFriendly = false; + if ( isAI && isFriendly ) { + newTargetFriendly = true; + } else if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->team == team ) ) { + newTargetFriendly = true; + } + } + if ( newTargetFriendly ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + targetFriendly = true; + if( cursor && !wasTargetFriendly ) { + cursor->HandleNamedEvent( "showCrossBuddy" ); + } + } + } + } + } + +// RAVEN BEGIN + +#ifdef _XENON + if ( doAimAssist ) { + if ( isAI && !isFriendly && (ent->health > 0) ) { + + if ( idStr::Icmp( name.c_str(),ent->name.c_str() ) != 0 ) { + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + //if ( showDebug ) { + //gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + //} + bool inside = aimArea.IntersectsBounds(bounds); + + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } + } +#endif + +// mekberg: allowFocus removed + if ( focusLength < (g_crosshairCharInfoFar.GetBool()?256.0f:80.0f) ) { +// RAVEN END + if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->GetTalkState() >= TALK_OK ) ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, body, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + + if ( isAI ) { + if ( static_cast( ent )->GetTalkState() >= TALK_OK ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, ent, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + } + + if ( focusLength < 80.0f ) { + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + rvVehicle* vehicle = static_cast(ent); + // dluetscher: added optimization to eliminate redundant traces + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum == ent->entityNumber ) && ((end - start).Length() * bboxTrace.fraction < vehicle->FocusLength()) ) { + //jshepard: locked or unusable vehicles + if ( !vehicle->IsLocked() && vehicle->HasOpenPositions() ) { + SetFocus ( FOCUS_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + + //jshepard: unusable vehicle + if( ent->spawnArgs.GetBool( "unusableVehicle", "0" ) ) { + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + break; + } + } + // Usable entities are last + if ( ent->fl.usable ) { + //jshepard: fake vehicles + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + if( ent->spawnArgs.GetBool("crosshair_vehicle")) { + SetFocus ( FOCUS_USABLE_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_USABLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + } + + if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) { + continue; + } + + if ( ent->spawnArgs.GetBool( "inv_item" ) ) { + // don't allow guis on pickup items focus + continue; + } + + pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end ); + if ( pt.x != -1 ) { + idUserInterface* ui = NULL; + renderEntity_t* focusGUIrenderEntity; + + focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId >= 1 && pt.guiId <= MAX_RENDERENTITY_GUI ) { + ui = focusGUIrenderEntity->gui[ pt.guiId-1 ]; + } + + if ( ui == NULL || !ui->IsInteractive ( ) ) { + continue; + } + + // All focused guis get brackets + focusBrackets = ui; + + // Any GUI that is too far away will just get bracket focus so the player can still shoot + // but still see which guis are interractive + if ( focusLength > 300.0f ) { + ClearFocus ( ); + break; + } else if ( focusLength > 80.0f ) { + ClearFocus ( ); + focusType = FOCUS_BRACKETS; +#ifdef _XENON + // Hide the "press whatever" text + hud->SetStateInt( "GUIIsNotUsingDPad", 3 ); + hud->SetStateInt( "hideInteractive", 1 ); +#endif + break; + } + + // If this is the first time this gui was activated then set up some things + if ( focusEnt != ent ) { + const idKeyValue* kv; + + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + ui->SetStateInt( "inv_count", inventory.items.Num() ); + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + ui->SetStateString( va( "inv_name_%i", j), iname ); + ui->SetStateString( va( "inv_icon_%i", j), iicon ); + ui->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + ui->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + ui->SetStateInt( iname, 1 ); + } + + for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) { + const char *p = inventory.pdaSecurity[ j ]; + + if ( p && *p ) { + ui->SetStateInt( p, 1 ); + } + } + + ui->SetStateString( "player_health", va("%i", health ) ); + ui->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); + + kv = ent->spawnArgs.MatchPrefix( "gui_", NULL ); + while ( kv ) { + ui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = ent->spawnArgs.MatchPrefix( "gui_", kv ); + } + } + + // clamp the mouse to the corner + const char* command; + sysEvent_t ev; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + +#ifdef _XENON + int usepad = 0; + if ( focusUI ) { + idWindow *dwin = ui->GetDesktop(); + if ( dwin ) { + idWinVar *wv = dwin->GetWinVarByName("dpadGUI"); + if ( wv ) { + usepad = atoi(wv->c_str()); + } + } + } + hud->SetStateInt( "GUIIsNotUsingDPad", (usepad) ? 0 : 1 ); + hud->SetStateInt( "hideInteractive", 0 ); +#endif + + SetFocus ( FOCUS_GUI, FOCUS_GUI_TIME, ent, ui ); + break; + } + } + +#ifdef _XENON + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } else { + + if ( bestEnemy && doAimAssist ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + } + trace_t trace; + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + } +#endif + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + if ( wasTargetFriendly && !targetFriendly && focusType == FOCUS_NONE ) { + if ( cursor ) { + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); + } + } + } + + // Update the focus brackets within the hud + if ( focusBrackets && hud ) { + if (focusType == FOCUS_BRACKETS || focusType == FOCUS_GUI ) { + if ( !oldBrackets || focusBracketsTime ) { + hud->HandleNamedEvent ( "showBrackets" ); + } + focusBracketsTime = 0; + } else { + if ( focusBracketsTime == 0 ) { + hud->HandleNamedEvent ( "fadeBrackets" ); + focusBracketsTime = gameLocal.time + 2000; + } + } + focusBrackets->SetStateBool ( "2d_calc", true ); + } + + if ( cursor && ( oldTalkCursor != talkCursor ) ) { + cursor->SetStateInt( "talkcursor", talkCursor ); + } + +} + +/* +================ +idPlayer::ClearFocus +================ +*/ +void idPlayer::ClearFocus ( void ) { + SetFocus ( FOCUS_NONE, 0, NULL, NULL ); +} + +void idPlayer::UpdateFocusCharacter( idEntity* newEnt ) { + if ( !cursor ) { + return; + } + // Handle character interaction + cursor->SetStateString( "npc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_name", "Joe" )) ); + cursor->SetStateString( "npcdesc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_description", "" )) ); + if ( newEnt->IsType( rvAIMedic::GetClassType() ) ) { + if ( ((rvAIMedic*)newEnt)->isTech ) { + cursor->SetStateInt( "npc_medictech", 2 ); + } else { + cursor->SetStateInt( "npc_medictech", 1 ); + } + } else { + cursor->SetStateInt( "npc_medictech", 0 ); + } +} +/* +================ +idPlayer::SetFocus +================ +*/ +void idPlayer::SetFocus ( playerFocus_t newType, int _focusTime, idEntity* newEnt, idUserInterface* newUI ) { + const char* command; + + // Handle transitions from one user interface to another or to none + if ( newUI != focusUI ) { + if ( focusUI ) { + command = focusUI->Activate( false, gameLocal.time ); + HandleGuiCommands( focusEnt, command ); + StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } + if ( newUI ) { + command = newUI->Activate( true, gameLocal.time ); + HandleGuiCommands( newEnt, command ); + StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + + // Hide the weapon when a gui is being used + /* + if ( weapon ) { + weapon->Hide(); + } + */ + }/* + else if ( weapon ) { + // Show the weapon since it was hidden when a gui first got focus + weapon->Show(); + } + */ + } + + //jshepard: the medic/tech crosshair won't update unless handleNamedEvent is called. I moved this outside + //of the switch below because the focus type is the same when moving directly from marine to marine, but the + //medic/tech status may change. + if ( newType == FOCUS_CHARACTER ) { + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + cursor->HandleNamedEvent ( "showCrossTalk" ); + } + } + // Show the appropriate cursor for the current focus type + if ( cursor && ( focusType != newType ) ) { + switch ( newType ) { + case FOCUS_VEHICLE: + case FOCUS_USABLE_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicle" ); + break; + case FOCUS_LOCKED_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicleLocked" ); + break; + case FOCUS_USABLE: + cursor->HandleNamedEvent ( "showCrossUsable" ); + break; + case FOCUS_GUI: + cursor->HandleNamedEvent ( "showCrossGui" ); + break; + case FOCUS_CHARACTER: + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + } + cursor->HandleNamedEvent ( "showCrossTalk" ); + break; + default: + // Make sure the weapon is shown in the default state +// RAVEN BEGIN +// abahr: don't do this if weapons are disabled + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); +// RAVEN END + break; + } + } + + + focusType = newType; + focusEnt = newEnt; + focusUI = newUI; + + if ( focusType == FOCUS_NONE ) { + focusTime = 0; + } else { + focusTime = gameLocal.time + _focusTime; + } +} + +/* +================= +idPlayer::CrashLand + +Check for hard landings that generate sound events +================= +*/ +void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + idVec3 origin, velocity; + idVec3 gravityVector, gravityNormal; + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + waterLevel_t waterLevel; + bool noDamage; + + pfl.softLanding = false; + pfl.hardLanding = false; + + // if the player is not on the ground + if ( !physicsObj.HasGroundContacts() ) { + return; + } + + gravityNormal = physicsObj.GetGravityNormal(); + + // if the player wasn't going down + if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { + return; + } + + waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + noDamage = false; + for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { + const contactInfo_t &contact = physicsObj.GetContact( i ); + if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { + noDamage = true; + break; + } + } + + //jshepard: no falling damage if falling damage is disabled + if( pfl.noFallingDamage ) { + return; + } + + origin = GetPhysics()->GetOrigin(); + gravityVector = physicsObj.GetGravity(); + + // calculate the exact velocity on landing + dist = ( origin - oldOrigin ) * -gravityNormal; + vel = oldVelocity * -gravityNormal; + acc = -gravityVector.Length(); + + a = acc / 2.0f; + b = vel; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < 1.0f ) { + return; + } + + // Some bug in the player movement code causes double fall damage on the 2nd frame in certain situations, + // so hack around this. Basically, if the next frame's velocity is going to be great enough to cause damage, + // don't do the damage this frame. This feels a lot safer than messing with things that could break player + // movement in worse ways. + if ( gameLocal.isMultiplayer ) { + + float vel2 = GetPhysics()->GetLinearVelocity() * -gravityNormal; + + a = acc / 2.0f; + b = vel2; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + float delta2 = vel2 + t * acc; + delta2 = delta2 * delta2 * 0.0001; + + if ( delta2 > softFallDelta ) { + return; + } + } + + + // ddynerman: moved height delta selection to player def + if ( delta > fatalFallDelta && fatalFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -32; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + } + } else if ( delta > hardFallDelta && hardFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -24; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + } + } else if ( delta > softFallDelta && softFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -16; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + } + } else if ( delta > noFallDelta && noFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -8; + landTime = gameLocal.time; + } + + // ddynerman: sometimes the actual landing animation is pre-empted by another animation (i.e. sliding, moving forward) + // so we play the landing sound here instead of relying on the anim + if( pfl.hardLanding ) { + StartSound ( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL ); + StartSound ( "snd_land_hard_pain", SND_CHANNEL_ANY, 0, false, NULL ); + } else if ( pfl.softLanding ) { + // todo - 2 different landing sounds for variety? + StartSound ( "snd_land_soft", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +/* +=============== +idPlayer::BobCycle +=============== +*/ +void idPlayer::BobCycle( const idVec3 &pushVelocity ) { + float bobmove; + int old, deltaTime; + idVec3 vel, gravityDir, velocity; + idMat3 viewaxis; + float bob; + float delta; + float speed; + float f; + + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + velocity = physicsObj.GetLinearVelocity() - pushVelocity; + + if ( noclip ) { + velocity.Zero ( ); + } + + gravityDir = physicsObj.GetGravityNormal(); + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) { + // airborne + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { + // start at beginning of cycle again + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + if ( physicsObj.IsCrouching() ) { + bobmove = pm_crouchbob.GetFloat(); + // ducked characters never play footsteps + } else { + // vary the bobbing based on the speed of the player + bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; + } + + // check for footstep / splash sounds + old = bobCycle; + bobCycle = (int)( old + bobmove * gameLocal.GetMSec() ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( idMath::Sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + // no view bob at all in MP while zoomed in + if( gameLocal.isMultiplayer && IsZoomed() ) { + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + return; + } + + viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + + // add angles based on velocity + delta = velocity * viewaxis[0]; + viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); + + delta = velocity * viewaxis[1]; + viewBobAngles.roll -= delta * pm_runroll.GetFloat(); + + // add angles based on bob + // make sure the bob is visible even at low speeds + speed = xyspeed > 200 ? xyspeed : 200; + + delta = bobfracsin * pm_bobpitch.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching + } + viewBobAngles.pitch += delta; + delta = bobfracsin * pm_bobroll.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching accentuates roll + } + if ( bobFoot & 1 ) { + delta = -delta; + } + viewBobAngles.roll += delta; + + // calculate position for view bobbing + viewBob.Zero(); + + if ( physicsObj.HasSteppedUp() ) { + + // check for stepping up before a previous step is completed + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); + } else { + stepUpDelta = physicsObj.GetStepUp(); + } + if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { + stepUpDelta = 2.0f * pm_stepsize.GetFloat(); + } + stepUpTime = gameLocal.time; + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // if the player stepped up recently + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); + } + + // add bob height after any movement smoothing + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if ( bob > 6 ) { + bob = 6; + } +// RAVEN BEGIN +// abahr: added gravity + viewBob += bob * -gravityDir; +// RAVEN END + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + viewBob -= gravity * ( landChange * f ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + viewBob -= gravity * ( landChange * f ); + } +} + +/* +================ +idPlayer::UpdateDeltaViewAngles +================ +*/ +void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); + } + SetDeltaViewAngles( delta ); +} + +/* +================ +idPlayer::SetViewAngles +================ +*/ +void idPlayer::SetViewAngles( const idAngles &angles ) { + UpdateDeltaViewAngles(angles); + viewAngles = angles; +} + +/* +================ +idPlayer::UpdateViewAngles +================ +*/ +void idPlayer::UpdateViewAngles( void ) { + int i; + idAngles delta; + + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) { + // no view changes at all, but we still want to update the deltas or else when + // we get out of this mode, our view will snap to a kind of random angle + UpdateDeltaViewAngles( viewAngles ); + return; + } + + // if dead + if ( health <= 0 ) { + if ( pm_thirdPersonDeath.GetBool() ) { + viewAngles.roll = 0.0f; + viewAngles.pitch = 30.0f; + } else { + viewAngles.roll = 40.0f; + viewAngles.pitch = -15.0f; + } + return; + } + + // circularly clamp the angles with deltas +// if( gameLocal.localClientNum == entityNumber ) { +// gameLocal.Printf( "BEFORE VIEWANGLES: %s\n", viewAngles.ToString() ); +// gameLocal.Printf( "\tUSERCMD: <%d, %d, %d>\n", usercmd.angles[ 0 ], usercmd.angles[ 1 ], usercmd.angles[ 2 ] ); +// gameLocal.Printf( "\tDELTAVIEW: %s\n", deltaViewAngles.ToString() ); +// } + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); + if ( influenceActive == INFLUENCE_LEVEL3 ) { + viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) ); + } else { + viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i] ) + deltaViewAngles[i] ); + } + } + if ( !centerView.IsDone( gameLocal.time ) ) { + viewAngles.pitch = centerView.GetCurrentValue( gameLocal.time ); + } +// if( gameLocal.localClientNum == entityNumber ) { +// gameLocal.Printf( "AFTER VIEWANGLES: %s\n", viewAngles.ToString() ); +// } + + // clamp the pitch + if ( noclip ) { + if ( viewAngles.pitch > 89.0f ) { + // don't let the player look down more than 89 degrees while noclipping + viewAngles.pitch = 89.0f; + } else if ( viewAngles.pitch < -89.0f ) { + // don't let the player look up more than 89 degrees while noclipping + viewAngles.pitch = -89.0f; + } + } else { + if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) { + // don't let the player look down enough to see the shadow of his (non-existant) feet + viewAngles.pitch = pm_maxviewpitch.GetFloat(); + } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) { + // don't let the player look up more than 89 degrees + viewAngles.pitch = pm_minviewpitch.GetFloat(); + } + } + + UpdateDeltaViewAngles( viewAngles ); + + // orient the model towards the direction we're looking + SetAngles( idAngles( 0, viewAngles.yaw, 0 ) ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; +} + +/* +============== +idPlayer::UpdateAir +============== +*/ +void idPlayer::UpdateAir( void ) { + + if ( health <= 0 ) { + return; + } + + // see if the player is connected to the info_vacuum + bool newAirless = false; + + if ( gameLocal.vacuumAreaNum != -1 ) { + int num = GetNumPVSAreas(); + if ( num > 0 ) { + int areaNum; + + // if the player box spans multiple areas, get the area from the origin point instead, + // otherwise a rotating player box may poke into an outside area + if ( num == 1 ) { + const int *pvsAreas = GetPVSAreas(); + areaNum = pvsAreas[0]; + } else { + areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() ); + } + newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR ); + } + } + + if ( newAirless ) { + if ( !airless ) { + StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( hud ) { + hud->HandleNamedEvent( "noAir" ); + } + } + airTics--; + if ( airTics < 0 ) { + airTics = 0; + // check for damage + const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); + int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); + if ( gameLocal.time > lastAirDamage + dmgTiming ) { + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + lastAirDamage = gameLocal.time; + } + } + + } else { + if ( airless ) { + StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + if ( hud ) { + hud->HandleNamedEvent( "Air" ); + } + } + airTics+=2; // regain twice as fast as lose + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } + + airless = newAirless; + + if ( hud ) { + hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() ); + } +} + +// RAVEN BEGIN +// abahr +/* +============== +idPlayer::UpdateGravity +============== +*/ +void idPlayer::UpdateGravity( void ) { + GetPhysics()->SetGravity( gameLocal.GetCurrentGravity(this) ); +} +// RAVEN END + +/* +============== +idPlayer::ToggleObjectives +============== +*/ +void idPlayer::ToggleObjectives ( void ) { +// RAVEN BEGIN +// mekberg: allow disabling of objectives. + if ( objectiveSystem == NULL || !objectivesEnabled ) { + return; + } +// RAVEN END + + if ( !objectiveSystemOpen ) { + int j, c = inventory.items.Num(); + objectiveSystem->SetStateInt( "inv_count", c ); + for ( j = 0; j < c; j++ ) { + idDict *item = inventory.items[j]; + if ( !item->GetBool( "inv_pda" ) ) { + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname ); + objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon ); + objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext ); + const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL ); + if ( kv ) { + objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + } + } + + for ( j = 0; j < MAX_WEAPONS; j++ ) { + const char *weapnum = va( "def_weapon%d", j ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << j ) ) { + const char *weap = spawnArgs.GetString( weapnum ); + if ( weap && *weap ) { + weapstate++; + } + } + objectiveSystem->SetStateInt( weapnum, weapstate ); + } + + UpdateObjectiveInfo(); + objectiveSystem->Activate( true, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommShow" ); + } else { + objectiveSystem->Activate( false, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommHide" ); + } + objectiveSystemOpen ^= 1; +} + +/* +============== +idPlayer::ToggleScoreboard +============== +*/ +void idPlayer::ToggleScoreboard( void ) { + scoreBoardOpen ^= 1; +} + +/* +============== +idPlayer::Spectate +============== +*/ +void idPlayer::Spectate( bool spectate, bool force ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + // track invisible player bug + // all hiding and showing should be performed through Spectate calls + // except for the private camera view, which is used for teleports + // ok for players in other instances to be hidden + assert( force || ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() == spectating ) || ( IsHidden() && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ); + + if ( spectating == spectate && !force ) { + return; + } + + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + + spectating = spectate; + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteBits( spectating, 1 ); + ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 ); + } + + // on the client, we'll get spectate messages about clients in other instances - always assume they are spectating + if ( spectating || inOtherInstance ) { + // join the spectators + delete clientHead; + clientHead = NULL; + + ClearPowerUps(); + spectator = this->entityNumber; + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + common->DPrintf( "idPlayer::Spectate() - Disabling clip for %d '%s' - spectate: %d force: %d\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), spectate, force ); + physicsObj.DisableClip(); + Hide(); + Event_DisableWeapon(); + + // remove the weapon + delete weapon; + weapon = NULL; + if ( !gameLocal.isClient ) { + delete weaponViewModel; + weaponViewModel = NULL; + delete weaponWorldModel; + weaponWorldModel = NULL; + } + } else { + // put everything back together again + UpdateModelSetup( true ); + currentWeapon = -1; // to make sure the def will be loaded if necessary + Show(); + Event_EnableWeapon(); + } + + SetClipModel( inOtherInstance ); + + if ( inOtherInstance ) { + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } +} + +/* +============== +idPlayer::SetClipModel +============== +*/ +void idPlayer::SetClipModel( bool forceSpectatorBBox ) { + idBounds bounds; + + common->DPrintf( "idPlayer::SetClipModel() - Called on %d '%s' forceSpectatorBBox = %d spectate = %d instance = %d local instance = %d\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), forceSpectatorBBox, spectating, instance, gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->GetInstance() : -1 ); + if ( spectating || forceSpectatorBBox ) { + bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + } else { + bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 ); + bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() ); + } + // the origin of the clip model needs to be set before calling SetClipModel + // otherwise our physics object's current origin value gets reset to 0 + idClipModel *newClip; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + if ( pm_usecylinder.GetBool() ) { + newClip = new idClipModel( idTraceModel( bounds, 8 ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } else { + newClip = new idClipModel( idTraceModel( bounds ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } +} + +/* +============== +idPlayer::EnterVehicle +============== +*/ +bool idPlayer::EnterVehicle( idEntity* vehicle ) { + if ( !idActor::EnterVehicle ( vehicle ) ) { + return false; + } + +// RAVEN BEGIN +// jshepard: safety first + if( weapon) { + weapon->Hide(); + } + +// abahr: + //HideCrosshair(); +// RAVEN END + + return true; +} + +/* +============== +idPlayer::ExitVehicle +============== +*/ +bool idPlayer::ExitVehicle ( bool force ) { + if ( !idActor::ExitVehicle ( force ) ) { + return false; + } + + SetViewAngles( viewAxis[0].ToAngles() ); + +// RAVEN BEGIN +// jshepard: had this crash on me more than once :( + if(weapon) { + weapon->Show(); + } +// abahr: Would like to call something more specific + ShowCrosshair(); +// RAVEN END + + return true; +} + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + +/* +============== +GetItemCost +============== +*/ +int idPlayer::GetItemCost( const char* itemName ) { + if ( !itemCosts ) { + assert( false ); + return 99999; + } + return itemCosts->dict.GetInt( itemName, "99999" ); +} + +/* +============== +GetItemBuyImpulse +============== +*/ +int GetItemBuyImpulse( const char* itemName ) +{ + struct ItemBuyImpulse + { + const char* itemName; + int itemBuyImpulse; + }; + + ItemBuyImpulse itemBuyImpulseTable[] = + { + { "weapon_shotgun", IMPULSE_100, }, + { "weapon_machinegun", IMPULSE_101, }, + { "weapon_hyperblaster", IMPULSE_102, }, + { "weapon_grenadelauncher", IMPULSE_103, }, + { "weapon_nailgun", IMPULSE_104, }, + { "weapon_rocketlauncher", IMPULSE_105, }, + { "weapon_railgun", IMPULSE_106, }, + { "weapon_lightninggun", IMPULSE_107, }, + // IMPULSE_108 - Unused + { "weapon_napalmgun", IMPULSE_109, }, + // { "weapon_dmg", IMPULSE_110, }, + // IMPULSE_111 - Unused + // IMPULSE_112 - Unused + // IMPULSE_113 - Unused + // IMPULSE_114 - Unused + // IMPULSE_115 - Unused + // IMPULSE_116 - Unused + // IMPULSE_117 - Unused + { "item_armor_small", IMPULSE_118, }, + { "item_armor_large", IMPULSE_119, }, + { "ammorefill", IMPULSE_120, }, + // IMPULSE_121 - Unused + // IMPULSE_122 - Unused + { "ammo_regen", IMPULSE_123, }, + { "health_regen", IMPULSE_124, }, + { "damage_boost", IMPULSE_125, }, + // IMPULSE_126 - Unused + // IMPULSE_127 - Unused + }; + const int itemBuyImpulseTableSize = sizeof(itemBuyImpulseTable) / sizeof(itemBuyImpulseTable[0]); + + for( int i = 0; i < itemBuyImpulseTableSize; ++ i ) + { + if( !stricmp( itemBuyImpulseTable[ i ].itemName, itemName ) ) + { + return itemBuyImpulseTable[ i ].itemBuyImpulse; + } + } + + return 0; +} + + +bool idPlayer::CanBuyItem( const char* itemName ) +{ + itemBuyStatus_t buyStatus = ItemBuyStatus( itemName ); + return( buyStatus == IBS_CAN_BUY ); +} + + +itemBuyStatus_t idPlayer::ItemBuyStatus( const char* itemName ) +{ + idStr itemNameStr = itemName; + if ( itemNameStr == "notimplemented" ) + { + return IBS_NOT_ALLOWED; + } + else if( !idStr::Cmpn( itemName, "wpmod_", 6 ) ) + { + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_small" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_large" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "ammorefill" ) + { + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + return IBS_ALREADY_HAVE; + + // If we are full of ammo for all weapons, you can't buy the ammo refill anymore. + bool fullAmmo = true; + for ( int i = 0 ; i < MAX_AMMOTYPES; i++ ) + { + if ( inventory.ammo[i] != inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) + fullAmmo = false; + } + if ( fullAmmo ) + return IBS_NOT_ALLOWED; + } + else if ( itemNameStr == "fc_armor_regen" ) + { + return IBS_NOT_ALLOWED; + } + + if ( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TOURNEY || gameLocal.gameType == GAME_ARENA_CTF || gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if ( itemNameStr == "ammo_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "health_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "damage_boost" ) + return IBS_NOT_ALLOWED; + } + + if ( CanSelectWeapon(itemName) != -1 ) + return IBS_ALREADY_HAVE; + + int cost = GetItemCost(itemName); + if ( cost > (int)buyMenuCash ) + { + return IBS_CANNOT_AFFORD; + } + + return IBS_CAN_BUY; +} + +/* +============== +idPlayer::UpdateTeamPowerups +============== +*/ +void idPlayer::UpdateTeamPowerups( bool isBuying ) { + for ( int i=0; iIsType( idPlayer::Type ) ) + continue; + + idPlayer* player = static_cast< idPlayer * >( ent ); + + // Not my teammate + if ( player->team != team ) + continue; + + // when a teammate respawns while a powerup is active, we don't want to go through other players which already have the powerup + // otherwise they'll get multiple VO announces + // ignore this when setting up powerups after a buying impulse, it only means someone is buying before expiration + if ( !isBuying && ( ( player->inventory.powerups & ( 1 << powerup ) ) != 0 ) ) { + continue; + } + + player->GivePowerUp( powerup, time, true ); + } + + gameLocal.mpGame.teamPowerups[team][i].update = false; + } + } +} + + +/* +============== +idPlayer::AttemptToBuyTeamPowerup +============== +*/ +bool idPlayer::AttemptToBuyTeamPowerup( const char* itemName ) +{ + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_AMMOREGEN, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "health_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_REGENERATION, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "damage_boost" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_TEAM_DAMAGE_MOD, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + + return false; +} + +/* +============== +idPlayer::AttemptToBuyItem +============== +*/ +bool idPlayer::AttemptToBuyItem( const char* itemName ) +{ + if ( gameLocal.isClient ) { + return false; + } + + if( !itemName ) { + return false; + } + + int itemCost = GetItemCost( itemName ); + + /// Check if the player is allowed to buy this item + if( !CanBuyItem( itemName ) ) + { + return false; + } + + const char* playerName = GetUserInfo()->GetString( "ui_name" ); + common->DPrintf( "Player %s about to buy item %s; player has %d (%g) credits, cost is %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + buyMenuCash -= (float)itemCost; + + common->DPrintf( "Player %s just bought item %s; player now has %d (%g) credits, cost was %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + + // Team-based effects + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" || itemNameStr == "health_regen" || itemNameStr == "damage_boost" ) { + return AttemptToBuyTeamPowerup(itemName); + } + + GiveStuffToPlayer( this, itemName, NULL ); + gameLocal.mpGame.RedrawLocalBuyMenu(); + return true; +} + +bool idPlayer::CanBuy( void ) { + bool ret = gameLocal.mpGame.IsBuyingAllowedRightNow(); + if ( !ret ) { + return false; + } + return !spectating; +} + + +void idPlayer::GenerateImpulseForBuyAttempt( const char* itemName ) { + if ( !CanBuy() ) + return; + + int itemBuyImpulse = GetItemBuyImpulse( itemName ); + PerformImpulse( itemBuyImpulse ); +} +// RITUAL END + + +/* +============== +idPlayer::PerformImpulse +============== +*/ +void idPlayer::PerformImpulse( int impulse ) { + +//RAVEN BEGIN +// nrausch: Don't send xenon dpad impulses over the network +#ifdef _XENON + + if ( objectiveSystemOpen ) { + return; + } + + if ( gameLocal.isClient && (impulse < IMPULSE_70) ) { +#else + if ( gameLocal.isClient ) { +#endif +//RAVEN END + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, IMPULSE_NUMBER_OF_BITS ); + ClientSendEvent( EVENT_IMPULSE, &msg ); + } + + if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) { + SelectWeapon( impulse, false ); + return; + } + +//RAVEN BEGIN +//asalmon: D-pad events for in game guis on Xenon +#ifdef _XBOX + sysEvent_t ev; + ev.evType = SE_KEY; + ev.evValue2 = 1; + ev.evPtrLength = 0; + ev.evPtr = NULL; + + const char *command = NULL; + idUserInterface *ui = ActiveGui(); + bool updateVisuals = false; +#endif +//RAVEN END + + switch( impulse ) { + case IMPULSE_13: { + Reload(); + break; + } + case IMPULSE_14: { + NextWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCycleNext( this ); + } + break; + } + case IMPULSE_15: { + PrevWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCyclePrev( this ); + } + break; + } + case IMPULSE_17: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleReady( ); + } + break; + } + case IMPULSE_18: { + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); + break; + } + case IMPULSE_19: { +/* + // when we're not in single player, IMPULSE_19 is used for showScores + // otherwise it does IMPULSE_12 (PDA) + if ( !gameLocal.isMultiplayer ) { + if ( !objectiveSystemOpen ) { + if ( weapon ) { + weapon->Hide (); + } + } + ToggleMap(); + } +*/ + break; + } + case IMPULSE_20: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleTeam( ); + } + break; + } + case IMPULSE_21: { + if( gameLocal.isServer && gameLocal.gameType == GAME_TOURNEY ) { + // only allow a client to join the waiting arena if they are not currently assigned to an arena + + // removed waiting arena functionality for now + /*rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( this != arena.GetPlayers()[ 0 ] && this != arena.GetPlayers()[ 1 ] ) { + if( instance == MAX_ARENAS && !spectating ) { + ServerSpectate( true ); + JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } else if( spectating ) { + JoinInstance( MAX_ARENAS ); + ServerSpectate( false ); + } + }*/ + } + break; + } + case IMPULSE_22: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleSpectate( ); + } + break; + } + + case IMPULSE_28: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + } + break; + } + case IMPULSE_29: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + } + break; + } + case IMPULSE_40: { + idFuncRadioChatter::RepeatLast(); + break; + } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + case IMPULSE_100: AttemptToBuyItem( "weapon_shotgun" ); break; + case IMPULSE_101: AttemptToBuyItem( "weapon_machinegun" ); break; + case IMPULSE_102: AttemptToBuyItem( "weapon_hyperblaster" ); break; + case IMPULSE_103: AttemptToBuyItem( "weapon_grenadelauncher" ); break; + case IMPULSE_104: AttemptToBuyItem( "weapon_nailgun" ); break; + case IMPULSE_105: AttemptToBuyItem( "weapon_rocketlauncher" ); break; + case IMPULSE_106: AttemptToBuyItem( "weapon_railgun" ); break; + case IMPULSE_107: AttemptToBuyItem( "weapon_lightninggun" ); break; + case IMPULSE_108: break; // Unused + case IMPULSE_109: AttemptToBuyItem( "weapon_napalmgun" ); break; + case IMPULSE_110: /* AttemptToBuyItem( "weapon_dmg" );*/ break; + case IMPULSE_111: break; // Unused + case IMPULSE_112: break; // Unused + case IMPULSE_113: break; // Unused + case IMPULSE_114: break; // Unused + case IMPULSE_115: break; // Unused + case IMPULSE_116: break; // Unused + case IMPULSE_117: break; // Unused + case IMPULSE_118: AttemptToBuyItem( "item_armor_small" ); break; + case IMPULSE_119: AttemptToBuyItem( "item_armor_large" ); break; + case IMPULSE_120: AttemptToBuyItem( "ammorefill" ); break; + case IMPULSE_121: break; // Unused + case IMPULSE_122: break; // Unused + case IMPULSE_123: AttemptToBuyItem( "ammo_regen" ); break; + case IMPULSE_124: AttemptToBuyItem( "health_regen" ); break; + case IMPULSE_125: AttemptToBuyItem( "damage_boost" ); break; + case IMPULSE_126: break; // Unused + case IMPULSE_127: break; // Unused +// RITUAL END + + case IMPULSE_50: { + ToggleFlashlight ( ); + break; + } + + case IMPULSE_51: { + LastWeapon(); + break; + } + } + +//RAVEN BEGIN +//asalmon: route d-pad input to the active gui. +#ifdef _XBOX + if (ui && ev.evValue != 0 && !objectiveSystemOpen ) { + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +#endif +//RAVEN END +} + +/* +============== +idPlayer::HandleESC +============== +*/ +bool idPlayer::HandleESC( void ) { + +// jdischler: Straight from the top, cinematic skipping on xenon is OFFICIALLY OUT. Too many problems with it and not enough time to properly address them. +#ifndef _XENON + if ( gameLocal.inCinematic ) { + return SkipCinematic(); + } +#endif + return false; +} + +/* +============== +idPlayer::HandleObjectiveInput +============== +*/ +void idPlayer::HandleObjectiveInput() { +#ifdef _XENON + if ( gameLocal.inCinematic ) { + return; + } + if ( !objectiveButtonReleased ) { + if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 ) { + objectiveButtonReleased = true; + } + } else { + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 ) { + ToggleObjectives ( ); + g_ObjectiveSystemOpen = objectiveSystemOpen; + objectiveButtonReleased = false; + } + } +#else + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 && !objectiveSystemOpen && !gameLocal.inCinematic ) { + ToggleObjectives ( ); + } else if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 && objectiveSystemOpen ) { + ToggleObjectives ( ); + } else if ( objectiveSystemOpen && gameLocal.inCinematic ) { + ToggleObjectives ( ); + } +#endif +} + +/* +============== +idPlayer::EvaluateControls +============== +*/ +void idPlayer::EvaluateControls( void ) { + // check for respawning + if ( pfl.dead || pfl.objectiveFailed ) { +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( allowedToRespawn ) { + if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) { + forceRespawn = true; + } else if ( gameLocal.time > maxRespawnTime ) { + forceRespawn = true; + } + } + else + { + Spectate(true); + } +// RITUAL END + } + + // in MP, idMultiplayerGame decides spawns + if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) { + // in single player, we let the session handle restarting the level or loading a game + gameLocal.sessionCommand = "died"; + } + + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + + if( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + oldFlags = usercmd.flags; + + AdjustSpeed(); + + // update the viewangles + UpdateViewAngles(); +} + +/* +============== +idPlayer::AdjustSpeed +============== +*/ +void idPlayer::AdjustSpeed( void ) { + float speed; + + if ( spectating ) { + speed = pm_spectatespeed.GetFloat(); + bobFrac = 0.0f; + } else if ( noclip ) { + speed = pm_noclipspeed.GetFloat(); + bobFrac = 0.0f; + } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { + bobFrac = 1.0f; + speed = pm_speed.GetFloat(); + } else { + speed = pm_walkspeed.GetFloat(); + bobFrac = 0.0f; + } + + speed *= PowerUpModifier(PMOD_SPEED); + + if ( influenceActive == INFLUENCE_LEVEL3 ) { + speed *= 0.33f; + } + + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); +} + +/* +============== +idPlayer::AdjustBodyAngles +============== +*/ +void idPlayer::AdjustBodyAngles( void ) { + idMat3 lookAxis; + idMat3 legsAxis; + bool blend; + float diff; + float frac; + float upBlend; + float forwardBlend; + float downBlend; + + if ( health < 0 ) { + return; + } + + blend = true; + + if ( !physicsObj.HasGroundContacts() ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else if ( usercmd.forwardmove < 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = false; + } else if ( usercmd.forwardmove > 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = true; + } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) { + if ( !legsForward ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() ); + } else { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() ); + } + } else if ( usercmd.rightmove != 0 ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else { + legsForward = true; + diff = idMath::Fabs( idealLegsYaw - legsYaw ); + idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw ); + if ( diff < 0.1f ) { + legsYaw = idealLegsYaw; + blend = false; + } + } + + if ( !physicsObj.IsCrouching() ) { + legsForward = true; + } + + oldViewYaw = viewAngles.yaw; + + pfl.turnLeft = false; + pfl.turnRight = false; + if ( idealLegsYaw < -45.0f ) { + idealLegsYaw = 0; + pfl.turnRight = true; + blend = true; + } else if ( idealLegsYaw > 45.0f ) { + idealLegsYaw = 0; + pfl.turnLeft = true; + blend = true; + } + + if ( blend ) { + legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f; + } + legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3(); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis ); + + // calculate the blending between down, straight, and up + frac = viewAngles.pitch / 90.0f; + if ( frac > 0.0f ) { + downBlend = frac; + forwardBlend = 1.0f - frac; + upBlend = 0.0f; + } else { + downBlend = 0.0f; + forwardBlend = 1.0f + frac; + upBlend = -frac; + } + + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend ); + + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend ); + +} + +/* +============== +idPlayer::InitAASLocation +============== +*/ +void idPlayer::InitAASLocation( void ) { + int i; + int num; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + GetFloorPos( 64.0f, origin ); + + num = gameLocal.NumAAS(); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < aasLocation.Num(); i++ ) { + aasLocation[ i ].areaNum = 0; + aasLocation[ i ].pos = origin; + aas = gameLocal.GetAAS( i ); + if ( aas && aas->GetSettings() ) { + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + } + } +} + +/* +============== +idPlayer::SetAASLocation +============== +*/ +void idPlayer::SetAASLocation( void ) { + int i; + int areaNum; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + if ( !GetFloorPos( 64.0f, origin ) ) { + return; + } + + for( i = 0; i < aasLocation.Num(); i++ ) { + aas = gameLocal.GetAAS( i ); + if ( !aas ) { + continue; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aasLocation[ i ].pos = origin; + aasLocation[ i ].areaNum = areaNum; + } + } +} + +/* +============== +idPlayer::GetAASLocation +============== +*/ +void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + int i; + + if ( aas != NULL ) { + for( i = 0; i < aasLocation.Num(); i++ ) { + if ( aas == gameLocal.GetAAS( i ) ) { + areaNum = aasLocation[ i ].areaNum; + pos = aasLocation[ i ].pos; + return; + } + } + } + + areaNum = 0; + pos = physicsObj.GetOrigin(); +} + +/* +============== +idPlayer::Move +============== +*/ +void idPlayer::Move( void ) { + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || pfl.objectiveFailed || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + + // If the player is dead then only run physics on new + // frames since articulated figures are not synchronized over the network + if ( health <= 0 ) { + if ( gameLocal.isNewFrame ) { + DeathPush(); + RunPhysics(); + } + } else { + RunPhysics(); + } + + // update our last valid AAS location for the AI + if( !gameLocal.isMultiplayer ) { + SetAASLocation(); + } + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( IsInVehicle ( ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + pfl.crouch = false; + pfl.onGround = ( influenceActive == INFLUENCE_LEVEL2 ); + pfl.onLadder = false; + pfl.jump = false; + } else { + pfl.crouch = physicsObj.IsCrouching(); + pfl.onGround = physicsObj.HasGroundContacts(); + pfl.onLadder = physicsObj.OnLadder(); + pfl.jump = physicsObj.HasJumped(); + + // check if we're standing on top of a monster and give a push if we are + idEntity *groundEnt = physicsObj.GetGroundEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( groundEnt && groundEnt->IsType( idAI::GetClassType() ) ) { +// RAVEN END + idVec3 vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() < 0.1f ) { + vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); + vel.ToVec2().NormalizeFast(); + vel.ToVec2() *= pm_speed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( pfl.jump ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + } + + if ( pfl.onLadder ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + UpdateIntentDir( ); + + BobCycle( pushVelocity ); + +// RAVEN BEGIN +// abahr: don't crashland while no clipping. + if( !noclip ) { + CrashLand( oldOrigin, oldVelocity ); + } +// RAVEN END +} + +/* +================== +idPlayer::BiasIntentDir + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::BiasIntentDir( idVec3 newIntentDir, float prevBias ) { + if ( !newIntentDir.Compare( vec3_origin ) ) { + if ( intentDir.Compare( vec3_origin ) ) { + //initialize it + intentDir = newIntentDir; + } else { + intentDir = ((intentDir*prevBias)+newIntentDir)/(prevBias+1.0f); + float iDirLen = idMath::ClampFloat(0.0f,1024.0f,intentDir.Normalize()); + intentDir *= iDirLen; + } + } +} + +/* +============== +idPlayer::UpdateIntentDir +============== +*/ +void idPlayer::UpdateIntentDir ( void ) { + idVec3 newIntentDir; + idVec3 viewDir = viewAxis[0]; + viewDir.z = 0; + viewDir.Normalize(); + float prevBias = 199.0f; + if ( intentDir.Compare( vec3_origin ) ) { + newIntentDir = viewDir*50.0f; + } else { + newIntentDir = viewDir*intentDir.Length(); + if ( flashlightOn ) { + //bias them more heavily to looking where I'm looking + prevBias = 19.0f; + } + } + if ( pfl.onGround ) { + idVec3 moveDir = physicsObj.GetLinearVelocity(); + if ( moveDir.x || moveDir.y ) { + // moving, too + moveDir.z = 0; + newIntentDir = moveDir; + prevBias = 39.0f; + } + } + BiasIntentDir( newIntentDir, prevBias ); + if ( ai_debugSquad.GetBool() ) { + idVec4 color = colorCyan; + if ( pfl.weaponFired ) { + color = colorRed; + } + gameRenderWorld->DebugArrow( color, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + intentDir*4.0f, 8, 0 ); + } +} + +/* +============== +idPlayer::UpdateHud +============== +*/ +void idPlayer::UpdateHud( void ) { + if ( !hud ) { + return; + } + + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + + // FIXME: this is here for level ammo balancing to see pct of hits + hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); + if ( numProjectilesFired ) { + hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); + } else { + hud->SetStateString( "projectilepct", "Hit % 0.0" ); + } + + if ( isLagged && gameLocal.isMultiplayer && gameLocal.localClientNum == entityNumber ) { + hud->SetStateString( "hudLag", "1" ); + } else { + hud->SetStateString( "hudLag", "0" ); + } +} + +/* +============== +idPlayer::UpdateDeathSkin +============== +*/ +void idPlayer::UpdateDeathSkin( bool state_hitch ) { + if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) { + return; + } + if ( health <= 0 ) { + if ( !doingDeathSkin && !deathSkinTime ) { + deathSkinTime = gameLocal.time + 1000; + deathStateHitch = state_hitch; + } + + // wait a bit before switching off the content + if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) { + SetCombatContents( false ); + deathClearContentsTime = 0; + } + } else { + renderEntity.noShadow = false; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } + UpdateVisuals(); + doingDeathSkin = false; + deathClearContentsTime = 0; + } +} + +/* +=============== +idPlayer::LoadDeferredModel +=============== +*/ +void idPlayer::LoadDeferredModel( void ) { + if( !modelDecl ) { + gameLocal.Warning( "idPlayer::LoadDeferredModel() - reloadModel without vaid modelDict\n" ); + return; + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + + if( weapon ) { + weapon->NetCatchup(); + } + + if( !modelDecl->skin.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( modelDecl->skin.c_str(), false ); + } + + SetModel( modelDecl->model ); + + if( !modelDecl->head.Length() ) { + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + } else { + SetupHead( modelDecl->head.c_str(), modelDecl->headOffset ); + } + + if( clientHead && health <= 0 ) { + // update death shader for new head + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ]; + clientHead.GetEntity()->GetRenderEntity()->noShadow = renderEntity.noShadow; + } + + if( powerUpSkin != NULL ) { + SetSkin( powerUpSkin ); + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + } +} + +/* +============== +idPlayer::Think + +Called every tic for each player +============== +*/ +void idPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + if ( talkingNPC ) { + if ( !talkingNPC.IsValid() ) { + talkingNPC = NULL; + } else { + idAI *talkingNPCAI = (idAI*)(talkingNPC.GetEntity()); + if ( !talkingNPCAI ) { + //wtf? + talkingNPC = NULL; + } else if ( talkingNPCAI->talkTarget != this || !talkingNPCAI->IsSpeaking() || DistanceTo( talkingNPCAI ) > 256.0f ) { + //forget about them, okay to talk to someone else now + talkingNPC = NULL; + } + } + } + + if ( !gameLocal.usercmds ) { + return; + } + +#ifdef _XENON + // change the crosshair if it's modified + if ( cursor && weapon && g_crosshairColor.IsModified() ) { + weapon->UpdateCrosshairGUI( cursor ); + cursor->HandleNamedEvent( "weaponChange" ); + g_crosshairColor.ClearModified(); + } +#endif + + // Dont do any thinking if we are in modview + if ( gameLocal.editors & EDITOR_MODVIEW || gameEdit->PlayPlayback() ) { + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + FreeModelDef(); + + if ( weapon ) { + weapon->GetWorldModel()->FreeModelDef(); + } + + if ( head.GetEntity() ) { + head->FreeModelDef(); + } + + if ( clientHead ) { + clientHead->FreeEntityDef(); + } + + return; + } + + if( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + gameEdit->RecordPlayback( usercmd, this ); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + HandleObjectiveInput(); + if ( objectiveSystemOpen ) { + HandleCheats(); + } else { + ClearCheatState(); + } + + aasSensor->Update(); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + // we need to let the camera think inside of this routine + CalculateRenderView(); + return; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // if this is the very first frame of the map, set the delta view angles + // based on the usercmd angles + if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { + spawnAnglesSet = true; + SetViewAngles( spawnAngles ); + oldFlags = usercmd.flags; + } + + if ( gameLocal.inCinematic || influenceActive +#ifdef _XENON + || objectiveSystemOpen +#endif + ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent ( &EV_Player_ZoomIn ); + } else { + ProcessEvent ( &EV_Player_ZoomOut ); + } + + if ( vehicleController.IsDriving( ) ) { +#ifdef _XENON + usercmdGen->SetSlowJoystick( zoom ? pm_zoomedSlow.GetInteger() : 100 ); +#else + cvarSystem->SetCVarInteger( "pm_isZoomed", zoom ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + } + + } + + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + thinkFlags |= TH_PHYSICS; + RunPhysics(); + + if ( health > 0 ) { + TouchTriggers(); + } + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + LinkCombat(); + } else { + UpdateModel(); + } + + // I don't want to have to add this but if you are in a locked vehicle and you fail your objective, you won't be + // ejected from the vehicle (and I don't think we'd want that even though we do have the option of forcing it..) + // and since you are still in the vehicle, EvaluateControls (which covers the logic below for player usercmds) + // will never get called. + if ( pfl.objectiveFailed ) + { + if ( ( gameLocal.time > minRespawnTime && (usercmd.buttons & BUTTON_ATTACK)) || + gameLocal.time > maxRespawnTime ) + { + gameLocal.sessionCommand = "died"; + } + } + + return; + } + + // log movement changes for weapon bobbing effects + if ( usercmd.forwardmove != oldCmd.forwardmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; + acc->dir[1] = acc->dir[2] = 0; + } + + if ( usercmd.rightmove != oldCmd.rightmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; + acc->dir[0] = acc->dir[2] = 0; + } + + // freelook centering + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) { + centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 ); + } + + // if we have an active gui, we will unrotate the view angles as + // we turn the mouse movements into gui events + idUserInterface *gui = ActiveGui(); + if ( gui && gui != focusUI ) { + RouteGuiMouse( gui ); + } + + // set the push velocity on the weapon before running the physics + if ( weapon ) { + weapon->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END + + Move(); + + if ( !g_stopTime.GetBool() ) { + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } + + // not done on clients for various reasons. don't do it on server and save the sound channel for other things + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && dynamicProtectionScale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { + if ( dynamicProtectionScale < 1.0f ) { + dynamicProtectionScale += 0.05f; + } + if ( dynamicProtectionScale > 1.0f ) { + dynamicProtectionScale = 1.0f; + } + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateState(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + } + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeroson / camera view + CalculateRenderView(); + + if ( spectating ) { + UpdateSpectating(); + } else if ( health > 0 && !gameLocal.inCinematic ) { + UpdateWeapon(); + } + + UpdateAir(); + + UpdateHud(); + + UpdatePowerUps(); + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + if( entityNumber == gameLocal.localClientNum ) { + renderEntity.suppressLOD = 1; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + } else { + renderEntity.suppressLOD = 0; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 0; + } + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: get first person flashlight into this + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + + Present(); + + LinkCombat(); + } + + if ( !( thinkFlags & TH_THINK ) ) { + common->DPrintf( "player %d not thinking?\n", entityNumber ); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + common->DPrintf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + common->DPrintf( "%d: enemies\n", num ); + } + + if ( !inBuyZonePrev ) + inBuyZone = false; + + inBuyZonePrev = false; +} + +/* +================= +idPlayer::RouteGuiMouse +================= +*/ +void idPlayer::RouteGuiMouse( idUserInterface *gui ) { + sysEvent_t ev; + const char *command; + + if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) { + ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY ); + command = gui->HandleEvent( &ev, gameLocal.time ); + oldMouseX = usercmd.mx; + oldMouseY = usercmd.my; + } +} + +bool idPlayer::CanZoom( void ) +{ + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + return weapon && weapon->CanZoom(); + } + + return weapon && weapon->CanZoom() && !weapon->IsReloading ( ); +} + +/* +================== +idPlayer::LookAtKiller +================== +*/ +void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) { + idVec3 dir; + + if ( attacker && attacker != this ) { + dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else if ( inflictor && inflictor != this ) { + dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else { + dir = viewAxis[ 0 ]; + } + + idAngles ang( 0, dir.ToYaw(), 0 ); + SetViewAngles( ang ); +} + +/* +============== +idPlayer::Kill +============== +*/ +void idPlayer::Kill( bool delayRespawn, bool nodamage ) { + if ( spectating ) { + SpectateFreeFly( false ); + } else if ( health > 0 ) { + godmode = false; + if ( !g_forceUndying.GetBool() ) { + undying = false; + } + if ( nodamage ) { + ServerSpectate( true ); + forceRespawn = true; + } else { + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + if ( delayRespawn ) { + forceRespawn = false; + int delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + } + } +} + +/* +================== +idPlayer::Killed +================== +*/ +void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + float delay; + + assert( !gameLocal.isClient ); + + // stop taking knockback once dead + fl.noknockback = true; + if ( health < -999 ) { + health = -999; + } + + if ( pfl.dead ) { + pfl.pain = true; + return; + } + +// squirrel: Mode-agnostic buymenus + if ( gameLocal.isMultiplayer ) { + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + if( gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP ) + { + /// Remove the player's armor + inventory.armor = 0; + + /// Preserve this player's weapons at the state of his death, to be restored on respawn + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = inventory.weapons; + + if( attacker ) + { + idPlayer* killer = NULL; + if ( attacker->IsType( idPlayer::Type ) ) + { + killer = static_cast(attacker); + if( killer == this ) + { + // Killed by self + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingSelf", 0 ); + killer->GiveCash( cashAward ); + } + else if( gameLocal.IsTeamGame() && killer->team == team ) + { + // Killed by teammate + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingTeammate", 0 ); + killer->GiveCash( cashAward ); + } + else + { + // Killed by enemy + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetOpponentKillCashAward(); + killer->GiveCash( cashAward ); + } + } + } + } + } + } +// RITUAL END + + bool noDrop = false; + if ( inflictor + && inflictor->IsType( idTrigger_Hurt::GetClassType() ) + && inflictor->spawnArgs.GetBool( "nodrop" ) ) { + //don't drop weapon or items here, flag auto-returns. + noDrop = true; + } + + if ( !g_testDeath.GetBool() && !gameLocal.isMultiplayer ) { +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); +#else + playerView.Fade( colorBlack, 12000 ); +#endif + } + + + pfl.dead = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + + animator.ClearAllJoints(); + + if ( StartRagdoll() ) { + pm_modelView.SetInteger( 0 ); +#ifdef _XENON + if ( gameLocal.isMultiplayer ) + { + // didn't want to have any chance of affecting multiplayer... + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + else + { + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; + } +#else + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif + } else { + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + + physicsObj.SetMovementType( PM_DEAD ); + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + + fl.takedamage = true; // can still be gibbed + + if (weapon) { // cnicholson: Fix for crash if player dies while in vehicle + weapon->OwnerDied(); // get rid of weapon + if ( !noDrop ) { + DropWeapon( ); // drop the weapon as an item + } + delete weapon; + } + + weapon = NULL; + currentWeapon = -1; + + if ( !g_testDeath.GetBool() ) { + LookAtKiller( inflictor, attacker ); + } + + if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) { + idPlayer *killer = NULL; + int methodOfDeath = MAX_WEAPONS + isTelefragged; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + + lastKiller = killer; + if ( gameLocal.IsTeamGame() && killer->team == team ) { + // don't worry about team killers + lastKiller = NULL; + } + if ( killer == this ) { + // don't worry about yourself + lastKiller = NULL; + } + + if ( health < -20 || killer->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + gibDeath = true; + gibDir = dir; + gibsLaunched = false; + if( gameLocal.isMultiplayer && gameLocal.isListenServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) { + ClientGib( dir ); + } + } + + if( !isTelefragged ) { + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + } + + if( methodOfDeath == -1 ) { + methodOfDeath = MAX_WEAPONS + isTelefragged; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( attacker->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + if( lastImpulseTime > gameLocal.time && lastImpulsePlayer ) { + killer = lastImpulsePlayer; + } + } + + gameLocal.mpGame.PlayerDeath( this, killer, methodOfDeath ); + } else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } + + if ( gameLocal.isMultiplayer && gameLocal.IsFlagGameType() ) { + if ( PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + RemoveClientModel( "mp_ctf_one_flag" ); + } + + if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) || PowerUpActive( POWERUP_CTF_MARINEFLAG ) || PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + idPlayer* killer = (idPlayer*)attacker; + if ( killer != NULL && killer->IsType( idPlayer::GetClassType() ) && killer != this ) { + if ( killer->team != team ) { + // killing the flag carrier gives killer double cash + killer->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingOpponentFlagCarrier", 0 ) ); + } + } + statManager->FlagDropped( this, attacker ); + } + } + + DropPowerups(); + + ClearPowerUps(); + + UpdateVisuals(); + + // AI sometimes needs to respond to having killed someone. + // Note: Would it be better to make this a virtual funciton of... something? + aiManager.RemoveTeammate( this ); + + isChatting = false; +} + +/* +================= +CalcDamagePoints + +Calculates how many health and armor points will be inflicted, but +doesn't actually do anything with them. This is used to tell when an attack +would have killed the player, possibly allowing a "saving throw" +================= +*/ +void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ) { + int damage; + int armorSave; + float pDmgScale; + + damageDef->GetInt( "damage", "20", damage ); + damage = GetDamageForLocation( damage, location ); + + // optional different damage in team games + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() && damageDef->GetInt( "damage_team" ) ) { + damage = damageDef->GetInt( "damage_team" ); + } + + idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + switch ( g_skill.GetInteger() ) { + case 0: + damage = ceil(0.80f*(float)damage); + break; + case 2: + damage *= 1.7f; + break; + case 3: + damage *= 3.5f; + break; + default: + //damage *= 1.1f; reverted to 1.0 for default damage... as per Biessman's request. + break; + } + } + } + + damage = ceil(damageScale*(float)damage); + + pDmgScale = damageDef->GetFloat( "playerScale", "1" ); + damage = ceil(pDmgScale*(float)damage); + + // check for completely getting out of the damage + if ( !damageDef->GetBool( "noGod" ) ) { + // check for godmode + if ( godmode ) { + godmodeDamage += damage; + damage = 0; + } + } + + // save some from armor + if ( !damageDef->GetBool( "noArmor" ) ) { + float armor_protection; + + armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat(); + armorSave = ceil( damage * armor_protection ); + if ( armorSave >= inventory.armor ) { + armorSave = inventory.armor; + } + + if ( !damage ) { + armorSave = 0; + } else if ( armorSave >= damage ) { + armorSave = damage - 1; + damage = 1; + } else { + damage -= armorSave; + } + } else { + armorSave = 0; + } + + // check for team damage + if ( gameLocal.IsTeamGame() + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team ) { + damage = 0; + } + + *health = damage; + *armor = armorSave; +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space + +damageDef an idDict with all the options for damage effects + +inflictor, attacker, dir, and point can be NULL for environmental effects +============ +*/ +void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + idVec3 kick; + int damage; + int armorSave; + int knockback; + idVec3 damage_from; + float attackerPushScale; + + // RAVEN BEGIN + // twhitaker: difficulty levels + float modifiedDamageScale = damageScale; + + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + modifiedDamageScale *= ( 1.0f + gameLocal.GetDifficultyModifier() ); + } + } + // RAVEN END + + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, modifiedDamageScale, location ); + return; + } + + // damage is only processed on server + if ( gameLocal.isClient ) { + return; + } + + if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) { + // If in vehicle let it know that something is trying to hurt the invisible player + if ( IsInVehicle ( ) ) { + const idDict *damageDict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDict ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // If the damage def is marked as a hazard then issue a warning to the vehicle + if ( damageDict->GetBool ( "hazard", "0" ) ) { + vehicleController.GetVehicle()->IssueHazardWarning ( ); + } + } + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + // MCG: player doesn't take friendly fire damage, except from self! + if ( !gameLocal.isMultiplayer && attacker != this ) { + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == team ) { + return; + } + } + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->dict.GetBool( "ignore_player" ) ) { + return; + } + + if ( damageDef->dict.GetBool( "lightning_damage_effect" ) ) + { + lightningEffects = 0; + lightningNextTime = gameLocal.GetTime(); + } + + // We pass in damageScale, because this function calculates a modified damageScale + // based on g_skill, and we don't want to compensate for skill level twice. + CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave ); + + // + // determine knockback + // + damageDef->dict.GetInt( "knockback", "0", knockback ); + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + damageDef->dict.GetInt( "knockback_team", va( "%d", knockback ), knockback ); + } + + knockback *= damageScale; + + if ( knockback != 0 && !fl.noknockback ) { + if ( !gameLocal.isMultiplayer && attacker == this ) { + //In SP, no knockback from your own stuff + knockback = 0; + } else { + if ( attacker != this ) { + attackerPushScale = 1.0f; + } else { + // since default attackerDamageScale is 0.5, default attackerPushScale should be 2 + damageDef->dict.GetFloat( "attackerPushScale", "2", attackerPushScale ); + } + + kick = dir; + + kick.Normalize(); + kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick ); + + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) ); + } + } + + if ( damageDef->dict.GetBool( "burn" ) ) { + StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL ); + } else if ( damageDef->dict.GetBool( "no_air" ) ) { + if ( !armorSave && health > 0 ) { + StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + + // give feedback on the player view and audibly when armor is helping + inventory.armor -= armorSave; + + if ( g_debugDamage.GetInteger() ) { + gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n", + entityNumber, health, damage, armorSave ); + } + + // move the world direction vector to local coordinates + ClientDamageEffects ( damageDef->dict, dir, damage ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + +//RAVEN BEGIN +//asalmon: Xenon needs stats in singleplayer +#ifndef _XENON + if( gameLocal.isMultiplayer ) +#endif + { +//RAVEN END + idEntity* attacker = NULL; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + int methodOfDeath = -1; + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + attacker = static_cast(inflictor)->GetOwner(); + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + attacker = inflictor; + } + + statManager->Damage( attacker, this, methodOfDeath, damage ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->dict.GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->dict.GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->dict.GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->dict.GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->dict.GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->dict.GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + // do the damage + if ( damage > 0 ) { + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) { + if ( gameLocal.time > lastDmgTime + 500 && dynamicProtectionScale > 0.25f ) { + dynamicProtectionScale -= 0.05f; + } + } + + if ( dynamicProtectionScale > 0.0f ) { + damage *= dynamicProtectionScale; + } + } + + if ( damage < 1 ) { + damage = 1; + } + + int oldHealth = health; + health -= damage; + + GAMELOG_ADD ( va("player%d_damage_taken", entityNumber ), damage ); + GAMELOG_ADD ( va("player%d_damage_%s", entityNumber, damageDefName), damage ); + + // Check undying mode + if ( !damageDef->dict.GetBool( "noGod" ) ) { + if ( undying ) { + if ( health < 1 ) { + health = 1; + } + } + } + + if ( health <= 0 ) { + + if ( health < -999 ) { + health = -999; + } + + isTelefragged = damageDef->dict.GetBool( "telefrag" ); + + lastDmgTime = gameLocal.time; + + Killed( inflictor, attacker, damage, dir, location ); + + if ( oldHealth > 0 ) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::Type ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, &damageDef->dict, pushScale ); + } + } else { + // force a blink + blink_time = 0; + + // let the anim script know we took damage + pfl.pain = Pain( inflictor, attacker, damage, dir, location ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + } + } else { + // don't accumulate impulses + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } + + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = location; +} + +/* +===================== +idPlayer::CanPlayImpactEffect +===================== +*/ +bool idPlayer::CanPlayImpactEffect( idEntity* attacker, idEntity* target ) +{ + if ( !gameLocal.isMultiplayer && attacker->IsType( idAI::GetClassType()) && target->IsType( idPlayer::GetClassType())) + { + // don't display impact effects when marines on our team shoot us... + idPlayer *player = static_cast( target ); + idAI *ai = static_cast( attacker ); + if ( player->team == ai->team ) + { + return false; + } + } + + return idAFEntity_Base::CanPlayImpactEffect( attacker, target ); +} + +/* +===================== +idPlayer::AddDamageEffect +===================== +*/ +void idPlayer::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if( gameLocal.isMultiplayer ) { + if( ! cvarSystem->GetCVarBool("si_teamDamage") && inflictor && inflictor->IsType( idPlayer::GetClassType() ) && gameLocal.IsTeamGame() && ((idPlayer*)inflictor)->team == team ) { + return; + } + } + + idActor::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +=========== +idPlayer::Teleport +============ +*/ +void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + idVec3 org; + + if ( weapon ) { + weapon->LowerWeapon(); + } + + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) { + SetOrigin( org ); + } + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + GetPhysics()->SetLinearVelocity( vec3_origin ); + SetViewAngles( angles ); + + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( gameLocal.isMultiplayer ) { + playerView.Flash( colorWhite, 140 ); + } + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + UpdateVisuals(); + + teleportEntity = destination; + + if ( !gameLocal.isClient && !noclip ) { + if ( gameLocal.isMultiplayer ) { + // kill anything at the new position or mark for kill depending on immediate or delayed teleport + gameLocal.KillBox( this, destination != NULL ); + } else { + // kill anything at the new position + gameLocal.KillBox( this, true ); + } + } +} + +/* +==================== +idPlayer::SetPrivateCameraView +==================== +*/ +void idPlayer::SetPrivateCameraView( idCamera *camView ) { + privateCameraView = camView; + if ( camView ) { + StopFiring(); + Hide(); + } else { + if ( !spectating ) { + Show(); + } + } +} + +/* +==================== +idPlayer::DefaultFov + +Returns the base FOV +==================== +*/ +float idPlayer::DefaultFov( void ) const { + float fov; + + fov = g_fov.GetFloat(); + if ( gameLocal.isMultiplayer ) { + if ( fov < 90.0f ) { + return 90.0f; + } else if ( fov > 175.0f ) { + return 175.0f; + } + } + + return fov; +} + +/* +==================== +idPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +float idPlayer::CalcFov( bool honorZoom ) { + float fov; + + if ( fxFov ) { + return DefaultFov() + 10.0f + idMath::Cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f; + } + + if ( influenceFov ) { + return influenceFov; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } else { + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } + + // bound normal viewsize + if ( fov < 1 ) { + fov = 1; + } else if ( fov > 179 ) { + fov = 179; + } + + return fov; +} + +/* +============== +idPlayer::GunTurningOffset + +generate a rotational offset for the gun based on the view angle +history in loggedViewAngles +============== +*/ +idAngles idPlayer::GunTurningOffset( void ) { + idAngles a; + + a.Zero(); + + if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) { + return a; + } + + idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles av, base; + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale, weaponAngleOffsetMax; + + weapon->GetAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax ); + + av = current; + + // calcualte this so the wrap arounds work properly + for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) { + idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles delta = a2 - current; + + if ( delta[1] > 180 ) { + delta[1] -= 360; + } else if ( delta[1] < -180 ) { + delta[1] += 360; + } + + av += delta * ( 1.0f / weaponAngleOffsetAverages ); + } + + a = ( av - current ) * weaponAngleOffsetScale; + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( a[i] < -weaponAngleOffsetMax ) { + a[i] = -weaponAngleOffsetMax; + } else if ( a[i] > weaponAngleOffsetMax ) { + a[i] = weaponAngleOffsetMax; + } + } + + return a; +} + +/* +============== +idPlayer::GunAcceleratingOffset + +generate a positional offset for the gun based on the movement +history in loggedAccelerations +============== +*/ +idVec3 idPlayer::GunAcceleratingOffset( void ) { + idVec3 ofs; + float weaponOffsetTime; + float weaponOffsetScale; + + ofs.Zero(); + + weapon->GetTimeOffsets( &weaponOffsetTime, &weaponOffsetScale ); + + int stop = currentLoggedAccel - NUM_LOGGED_ACCELS; + if ( stop < 0 ) { + stop = 0; + } + for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) { + loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)]; + + float f; + float t = gameLocal.time - acc->time; + if ( t >= weaponOffsetTime ) { + break; // remainder are too old to care about + } + + f = t / weaponOffsetTime; + f = ( idMath::Cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f; + ofs += f * weaponOffsetScale * acc->dir; + } + + return ofs; +} + +/* +============== +idPlayer::CalculateViewWeaponPos + +Calculate the bobbing position of the view weapon +============== +*/ +void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) { + float scale; + float fracsin; + idAngles angles; + int delta; + + // CalculateRenderView must have been called first + const idVec3 &viewOrigin = firstPersonViewOrigin; + const idMat3 &viewAxis = firstPersonViewAxis; + + // these cvars are just for hand tweaking before moving a value to the weapon def + idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() ); + gunpos += weapon->GetViewModelOffset ( ); + + // as the player changes direction, the gun will take a small lag + idVec3 gunOfs = GunAcceleratingOffset(); + origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis; + + // on odd legs, invert some angles + if ( noclip || 1 ) { + scale = 0; + } else if ( bobCycle & 128 ) { + scale = -xyspeed; + } else { + scale = xyspeed; + } + + // gun angles from bobbing + angles.roll = scale * bobfracsin * 0.005f + g_gun_roll.GetFloat(); + angles.yaw = scale * bobfracsin * 0.01f + g_gun_yaw.GetFloat(); + angles.pitch = xyspeed * bobfracsin * 0.005f + g_gun_pitch.GetFloat(); + + angles += weapon->GetViewModelAngles(); + + // gun angles from turning + if ( gameLocal.isMultiplayer ) { + idAngles offset = GunTurningOffset(); + offset *= g_mpWeaponAngleScale.GetFloat(); + angles += offset; + } else { + angles += GunTurningOffset(); + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + +// RAVEN BEGIN +// abahr: when looking down, really large deflections cause back of weapons to show + float landChangeFrac = idMath::Lerp( 0.25f, 0.05f, viewAngles.ToForward() * gravity); +// RAVEN ABAHR + + // drop the weapon when landing after a jump / fall + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { +// RAVEN BEGIN +// abahr: changed to use landChangeFrac + origin -= gravity * ( landChange*landChangeFrac * delta / LAND_DEFLECT_TIME ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin -= gravity * ( landChange*landChangeFrac * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME ); +// RAVEN END + } + + // speed sensitive idle drift + if ( !noclip ) { + scale = xyspeed * 0.5f + 40.0f; + fracsin = scale * idMath::Sin( MS2SEC( gameLocal.time ) ) * 0.01f; + angles.roll += fracsin; + angles.yaw += fracsin; + angles.pitch += fracsin; + } + + axis = angles.ToMat3() * viewAxis; +} + +/* +=============== +idPlayer::OffsetThirdPersonVehicleView +=============== +*/ +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world +void idPlayer::OffsetThirdPersonVehicleView( bool clip ) { +// RAVEN END + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + idVec3 origin; + idAngles angles, angles2; + idEntity* vehicle; + + assert ( IsInVehicle ( ) ); + + vehicle = vehicleController.GetVehicle(); + + origin = vehicle->GetRenderEntity()->origin; + angles = vehicle->GetRenderEntity()->axis.ToAngles(); + + angles.yaw += pm_thirdPersonAngle.GetFloat(); + angles.pitch += 25.0f; + +// angles.pitch += viewAngles.pitch; +// angles.yaw += viewAngles.yaw; + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * 8.0f; +// RAVEN END + + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + float speed = vehicle->GetPhysics()->GetLinearVelocity() * + vehicle->GetPhysics()->GetAxis()[0]; + + speed = idMath::Fabs( speed ); + speed *= pm_vehicleCameraSpeedScale.GetFloat(); + if( speed > pm_vehicleCameraScaleMax.GetFloat() ) + { + speed = pm_vehicleCameraScaleMax.GetFloat(); + } + + vehicleCameraDist += ( MS2SEC( gameLocal.GetMSec() ) * ( ( pm_vehicleCameraMinDist.GetFloat() + speed ) - vehicleCameraDist ) ); + + view -= vehicleCameraDist * renderView->viewaxis[ 0 ]; + +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + if ( clip ) { + // 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 + const idVec3 clip_mins( -4.0f, -4.0f, -4.0f ); + const idVec3 clip_maxs( 4.0f, 4.0f, 4.0f ); + const idBounds clip_bounds( clip_mins, clip_maxs ); + idClipModel clipBounds( clip_bounds );// We clip when using a tram gun in the tram car +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + if ( trace.fraction != 1.0 ) + { + view = trace.endpos; +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32; + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + view = trace.endpos; + } + } +// RAVEN END + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) + { + focusDist = 1; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::OffsetThirdPersonView +=============== +*/ +void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + float forwardScale, sideScale; + idVec3 origin; + idAngles angles; + idMat3 axis; + idBounds bounds; + + angles = viewAngles; + GetViewPos( origin, axis ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + focusPoint.z += height; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * (8.0f + height); +// RAVEN END + + angles.pitch *= 0.5f; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale ); + view -= range * forwardScale * renderView->viewaxis[ 0 ]; + view += range * sideScale * renderView->viewaxis[ 1 ]; + + if ( clip ) { + // 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 + bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + if ( trace.fraction != 1.0f ) { + view = trace.endpos; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32.0f; +// RAVEN END + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + view = trace.endpos; + } + } + + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1.0f ) { + focusDist = 1.0f; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + angles.yaw -= angle; + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::GetEyePosition +=============== +*/ +idVec3 idPlayer::GetEyePosition( void ) const { + idVec3 org; + + if ( WantSmoothing() ) { + org = predictedOrigin; + } else { + org = GetPhysics()->GetOrigin(); + } + + return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +=============== +idPlayer::GetViewPos +=============== +*/ +void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + idAngles angles; + + // if dead, fix the angle and don't add any kick + if ( health <= 0 ) { + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else if ( IsInVehicle ( ) ) { + vehicleController.GetEyePosition ( origin, axis ); + + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + + origin += shakeOffset; + axis = (shakeAngleOffset + playerView.AngleOffset()).ToMat3() * axis; + } else { + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + origin = GetEyePosition() + viewBob + shakeOffset; + angles = viewAngles + viewBobAngles + shakeAngleOffset + playerView.AngleOffset(); + + axis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + // adjust the origin based on the camera nodal distance (eye distance from neck) + origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat(); + origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat(); + } +} + +/* +=============== +idPlayer::CalculateFirstPersonView +=============== +*/ +void idPlayer::CalculateFirstPersonView( void ) { + if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) { + // Displays the view from the point of view of the "camera" joint in the player model + + idMat3 axis; + idVec3 origin; + idAngles ang; + + ang = viewBobAngles + playerView.AngleOffset(); + ang.yaw += viewAxis[ 0 ].ToYaw(); + + jointHandle_t joint = animator.GetJointHandle( "camera" ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob; + firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis(); + } else { + // offset for local bobbing and kicks + GetViewPos( firstPersonViewOrigin, firstPersonViewAxis ); + } +} + +/* +================== +idPlayer::GetRenderView + +Returns the renderView that was calculated for this tic +================== +*/ +renderView_t *idPlayer::GetRenderView( void ) { + return renderView; +} + +/* +================== +idPlayer::SmoothenRenderView + +various situations where the view angles need smoothing: + +demo replay: + On a slow client with low fps multiple game frames are run in quick succession + inbetween rendered frames. As a result the usercmds are not recorded at fixed + time intervals but in small bursts. This routine interpolates the view angles + based on the real time at which the usercmds were recorded to make a demo + recorded on a slow client play back smoothly on a fast client. + +spectate follow? +================== +*/ +void idPlayer::SmoothenRenderView( bool firstPerson ) { + int d1, d2; + idAngles angles, anglesDelta, newAngles; + + if ( gameLocal.GetDemoState() == DEMO_PLAYING ) { + + d1 = usercmd.gameTime - demoViewAngleTime; + if ( d1 < 0 ) { + return; + } + d2 = usercmd.realTime - demoViewAngleTime; + if ( d2 <= 0 ) { + return; + } + if ( d1 >= d2 ) { + return; + } + + angles = renderView->viewaxis.ToAngles(); + + anglesDelta = angles - demoViewAngles; + anglesDelta.Normalize180(); + newAngles = demoViewAngles + ( (float) d1 / d2 ) * anglesDelta; + renderView->viewaxis = newAngles.ToMat3(); + + if ( usercmd.gameTime + gameLocal.msec > usercmd.realTime ) { + demoViewAngleTime = usercmd.realTime; + demoViewAngles = angles; + } + + if ( firstPerson ) { + // make sure the view weapon moves smoothly + firstPersonViewAxis = renderView->viewaxis; + } + } +} + +/* +================== +idPlayer::CalculateRenderView + +create the renderView for the current tic +================== +*/ +void idPlayer::CalculateRenderView( void ) { + int i; + float range; + + if ( !renderView ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + // copy global shader parms + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + renderView->time = gameLocal.time; + + // calculate size of 3D view + renderView->x = 0; + renderView->y = 0; + renderView->width = SCREEN_WIDTH; + renderView->height = SCREEN_HEIGHT; + renderView->viewID = 0; + + // check if we should be drawing from a camera's POV + if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) { + // get origin, axis, and fov + if ( privateCameraView ) { + privateCameraView->GetViewParms( renderView ); + } else { + gameLocal.GetCamera()->GetViewParms( renderView ); + } + } else { + bool cameraIsSet = false; + + // First try out any camera views that can possibly fail. + if( !cameraIsSet ){ + if ( g_stopTime.GetBool() ) { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + + if ( !pm_thirdPerson.GetBool() ) { + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } else if ( pm_thirdPerson.GetBool() && IsInVehicle ( ) ) { +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + OffsetThirdPersonVehicleView( pm_thirdPersonClip.GetBool() ); +// RAVEN END + SmoothenRenderView( false ); + } else if ( pm_thirdPerson.GetBool() ) { + OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() ); + SmoothenRenderView( false ); + } else if ( pm_thirdPersonDeath.GetBool() ) { + range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f; + OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false ); + SmoothenRenderView( false ); + } else { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } + + // field of view + gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y ); + + } + + if ( renderView->fov_y == 0 ) { + common->Error( "renderView->fov_y == 0" ); + } + + if ( g_showviewpos.GetBool() ) { + gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() ); + } +} + +/* +================== +idPlayer::Event_EnableTarget +================== +*/ +void idPlayer::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +================== +idPlayer::Event_DisableTarget +================== +*/ +void idPlayer::Event_DisableTarget ( void ) { + fl.notarget = true; +} + +/* +================== +idPlayer::Event_GetViewPos +================== +*/ +void idPlayer::Event_GetViewPos( void ) { + idVec3 viewOrigin; + idMat3 viewAxis; + + GetViewPos(viewOrigin, viewAxis); + idThread::ReturnVector( viewOrigin ); +} + +/* +================== +idPlayer::Event_FinishHearingLoss +================== +*/ +void idPlayer::Event_FinishHearingLoss ( float fadeTime ) { + if ( fadeTime <= 0.0f ) { + StopSound ( SND_CHANNEL_DEMONIC, false ); + pfl.hearingLoss = false; + } else { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, 0.0f ); + } +} + +// RAVEN BEGIN +// twhitaker: added the event +/* +============= +idPlayer::Event_ApplyImpulse +============= +*/ +void idPlayer::Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ) { + GetPhysics()->ApplyImpulse( 0, point, impulse ); +} + +// mekberg: added Event_EnableObjectives +/* +============= +idPlayer::Event_EnableObjectives +============= +*/ +void idPlayer::Event_EnableObjectives ( void ) { + objectivesEnabled = true; +} + +// mekberg: added Event_DisableObjectives +/* +============= +idPlayer::Event_DisableObjectives +============= +*/ +void idPlayer::Event_DisableObjectives ( void ) { + // if it's open, it should be closed + if (objectiveSystemOpen ) { + ToggleObjectives(); + } + objectivesEnabled = false; +} + +// mekberg: added Event_AllowNewObjectives +void idPlayer::Event_AllowNewObjectives ( void ) { + showNewObjectives = true; +} + +// mekberg: added sethealth +/* +============= +idPlayer::Event_SetHealth +============= +*/ +void idPlayer::Event_SetHealth( float newHealth ) { + health = idMath::ClampInt( 1 , inventory.maxHealth, newHealth ); +} +/* +============= +idPlayer::Event_SetArmor +============= +*/ +void idPlayer::Event_SetArmor( float newArmor ) { + inventory.armor = idMath::ClampInt( 0 , inventory.maxarmor, newArmor ); +} + +/* +============= +idPlayer::Event_SetExtraProjPassEntity +============= +*/ +void idPlayer::Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ) { + extraProjPassEntity = _extraProjPassEntity; +} +// RAVEN END + +/* +============= +idPlayer::AddProjectilesFired +============= +*/ +void idPlayer::AddProjectilesFired( int count ) { + numProjectilesFired += count; +} + +/* +============= +idPlayer::AddProjectileHites +============= +*/ +void idPlayer::AddProjectileHits( int count ) { + numProjectileHits += count; +} + +/* +============= +idPlayer::SetLastHitTime +============= +*/ +void idPlayer::SetLastHitTime( int time, bool armorHit ) { + if ( !time ) { + // level start and inits + return; + } + + idUserInterface *cursor = idPlayer::cursor; + bool spectated = false; + if ( gameLocal.GetLocalPlayer() ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + spectated = true; + } + } else if ( gameLocal.IsServerDemo() ) { + // server netdemo + assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + if ( gameLocal.GetDemoFollowClient() == entityNumber ) { + cursor = gameLocal.GetDemoCursor(); + spectated = true; + } + } + + if ( lastHitTime != time ) { + if ( cursor ) { + cursor->HandleNamedEvent( "weaponHit" ); + } + if ( gameLocal.isMultiplayer ) { + // spectated so we get sounds for a client we're following + // localClientNum check so listen server plays only for local player + if ( spectated || gameLocal.localClientNum == entityNumber ) { + const char* sound; + + if ( armorHit ) { + if ( spawnArgs.GetString ( "snd_armorHit", "", &sound ) ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + } else { + if ( spawnArgs.GetString ( "snd_weaponHit", "", &sound ) ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + } + } + + if ( aimClientNum != -1 ) { + if ( mphud ) { + mphud->HandleNamedEvent( "aim_hit" ); + } + } + + } + lastHitTime = time; + lastArmorHit = armorHit; + lastHitToggle ^= 1; + } +} + +/* +============= +idPlayer::SetInfluenceLevel +============= +*/ +void idPlayer::SetInfluenceLevel( int level ) { + if ( level != influenceActive ) { + if ( level ) { + for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + ent->PostEventMS( &EV_Remove, 0 ); + } + } + if ( weaponEnabled && weapon ) { + weapon->EnterCinematic(); + } + } else { + physicsObj.SetLinearVelocity( vec3_origin ); + if ( weaponEnabled && weapon ) { + weapon->ExitCinematic(); + } + } + influenceActive = level; + } +} + +/* +============= +idPlayer::SetInfluenceView +============= +*/ +void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) { + influenceMaterial = NULL; + influenceEntity = NULL; + influenceSkin = NULL; + if ( mtr && *mtr ) { + influenceMaterial = declManager->FindMaterial( mtr ); + } + if ( skinname && *skinname ) { + influenceSkin = declManager->FindSkin( skinname ); + if ( head.GetEntity() ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + UpdateVisuals(); + } + influenceRadius = radius; + if ( radius > 0.0f ) { + influenceEntity = ent; + } +} + +/* +============= +idPlayer::SetInfluenceFov +============= +*/ +void idPlayer::SetInfluenceFov( float fov ) { + influenceFov = fov; +} + +/* +================ +idPlayer::OnLadder +================ +*/ +bool idPlayer::OnLadder( void ) const { + return physicsObj.OnLadder(); +} + +/* +================== +idPlayer::Event_GetButtons +================== +*/ +void idPlayer::Event_GetButtons( void ) { + idThread::ReturnInt( usercmd.buttons ); +} + +/* +================== +idPlayer::Event_GetMove +================== +*/ +void idPlayer::Event_GetMove( void ) { + idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove ); + idThread::ReturnVector( move ); +} + +/* +================ +idPlayer::Event_GetViewAngles +================ +*/ +void idPlayer::Event_GetViewAngles( void ) { + idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) ); +} + +/* +================ +idPlayer::Event_SetViewAngles +================ +*/ +void idPlayer::Event_SetViewAngles( const idVec3 & vec ) { + idAngles ang; + ang.Set( vec.z, vec.y, vec.x ); + SetViewAngles( ang ); +} + +/* +================== +idPlayer::Event_StopFxFov +================== +*/ +void idPlayer::Event_StopFxFov( void ) { + fxFov = false; +} + +/* +================== +idPlayer::StartFxFov +================== +*/ +void idPlayer::StartFxFov( float duration ) { + fxFov = true; + PostEventSec( &EV_Player_StopFxFov, duration ); +} + +/* +================== +idPlayer::Event_EnableWeapon +================== +*/ +void idPlayer::Event_EnableWeapon( void ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", 0 ); + Give( "weapon", spawnArgs.GetString( va( "def_weapon%d", 0 ) ) ); + hiddenWeapon = false; + weaponEnabled = true; + if ( weapon ) { + weapon->ExitCinematic(); + } + ShowCrosshair(); +} + +/* +================== +idPlayer::Event_DisableWeapon +================== +*/ +void idPlayer::Event_DisableWeapon( void ) { + hiddenWeapon = true; + weaponEnabled = false; + if ( weapon ) { + weapon->EnterCinematic(); + } + HideCrosshair(); +} + +/* +================== +idPlayer::Event_GetCurrentWeapon +================== +*/ +void idPlayer::Event_GetCurrentWeapon( void ) { + if ( currentWeapon >= 0 ) { + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ) ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_GetPreviousWeapon +================== +*/ +void idPlayer::Event_GetPreviousWeapon( void ) { + if ( previousWeapon >= 0 ) { + int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon; + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", pw) ) ); + } else { + idThread::ReturnString( "def_weapon0" ); + } +} + +/* +================== +idPlayer::Event_SelectWeapon +================== +*/ +void idPlayer::Event_SelectWeapon( const char *weaponName ) { + int i; + int weaponNum; + + if ( gameLocal.isClient ) { + gameLocal.Warning( "Cannot switch weapons from script in multiplayer" ); + return; + } + + weaponNum = -1; + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + + if ( !inventory.HasAmmo( weap ) ) { + return; + } + weaponNum = i; + break; + } + } + } + + if ( weaponNum < 0 ) { + gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName ); + return; + } + + hiddenWeapon = false; + idealWeapon = weaponNum; + + UpdateHudWeapon(); +} + +/* +================== +idPlayer::Event_GetAmmoData +================== +*/ +void idPlayer::Event_GetAmmoData( const char *ammoClass ) { + + idVec3 weaponAmmo; + + //ammo vector is this: current ammo count, max ammo count, and % + weaponAmmo.x = inventory.ammo[ inventory.AmmoIndexForAmmoClass( ammoClass) ]; + weaponAmmo.y = inventory.MaxAmmoForAmmoClass( this, ammoClass ); + + if( weaponAmmo.y == 0) + weaponAmmo.z = 0; + else + weaponAmmo.z = (float)(weaponAmmo.x / weaponAmmo.y); + + idThread::ReturnVector( weaponAmmo); +} + +/* +================== +idPlayer::Event_RefillAmmo +================== +*/ +void idPlayer::Event_RefillAmmo( void ) { + int a; + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + a = inventory.AmmoIndexForWeaponIndex( i ); + inventory.ammo[ a ] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex( a ) ); + } + } + } +} + +/* +================== +idPlayer::Event_AllowFallDamage +================== +*/ +void idPlayer::Event_AllowFallDamage( int toggle ) { + if( toggle ) { + pfl.noFallingDamage = false; + } else { + pfl.noFallingDamage = true; + } + +} +/* +================== +idPlayer::Event_GetWeaponEntity +================== +*/ +void idPlayer::Event_GetWeaponEntity( void ) { + idThread::ReturnEntity( weaponViewModel ); +} + +/* +================== +idPlayer::Event_HideDatabaseEntry +================== +*/ +void idPlayer::Event_HideDatabaseEntry ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "closeDatabaseEntry" ); + } +} + +/* +================== +idPlayer::Event_ZoomIn +================== +*/ +void idPlayer::Event_ZoomIn ( void ) { + float currentFov; + float t; + + if ( zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming in while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( weapon && this->weaponEnabled ) { + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::Event_ZoomOut +================== +*/ +void idPlayer::Event_ZoomOut ( void ) { + float t; + float currentFov; + + if ( !zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else { + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::TeleportDeath +================== +*/ +void idPlayer::TeleportDeath( int killer ) { + teleportKiller = killer; +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ExitTeleporter( void ) { + idEntity *exitEnt; + float pushVel; + + // verify and setup + exitEnt = teleportEntity; + if ( !exitEnt ) { + common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber ); + return; + } + + pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" ); + + if ( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 ); + } + + SetPrivateCameraView( NULL ); + // setup origin and push according to the exit target + SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() ); + physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel ); + physicsObj.ClearPushedVelocity( ); + // teleport fx + playerView.Flash( colorWhite, 120 ); + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + UpdateVisuals(); + + gameLocal.PlayEffect( spawnArgs, "fx_teleport", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + + StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( teleportKiller != -1 ) { + // we got killed while being teleported + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + teleportKiller = -1; + } else { + // kill anything that would have waited at teleport exit + gameLocal.KillBox( this ); + } + teleportEntity = NULL; +} + +/* +=============== +idPlayer::Event_DamageOverTimeEffect +=============== +*/ +void idPlayer::Event_DamageOverTimeEffect( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), GetWeaponViewModel(), interval ); + RV_POP_HEAP(); + + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +/* +=============== +idPlayer::LocalClientPredictionThink +=============== +*/ +void idPlayer::LocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + + if ( IsInVehicle( ) ) { + vehicleController.SetInput( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } + + AdjustSpeed(); + + UpdateViewAngles(); + +/* +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END +*/ + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + Move(); + + // predict collisions with items + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers( &idItem::GetClassType() ); + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + UpdateHud(); + + if ( gameLocal.isNewFrame ) { + UpdatePowerUps(); + } + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +=============== +idPlayer::NonLocalClientPredictionThink +=============== +*/ +#define LIMITED_PREDICTION 1 + +void idPlayer::NonLocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + //jshepard: added this to make sure clients can see other clients and the host switching weapons + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + +#if !LIMITED_PREDICTION + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } +#endif + + AdjustSpeed(); + + UpdateViewAngles(); + + if ( gameLocal.isLastPredictFrame && jumpDuringHitch ) { + // only play sound if still alive + if ( health > 0 ) { + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + } + jumpDuringHitch = false; + } + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + // NOTE: only running on new frames causes prediction errors even when the input does not change! + if ( gameLocal.isNewFrame ) { + Move(); + } else { + PredictionErrorDecay(); + } + } + +#if defined( _XENON ) || !LIMITED_PREDICTION + // update GUIs, Items, and character interactions + UpdateFocus(); +#endif + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + if ( gameLocal.isLastPredictFrame ) { + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + UpdateHud(); + UpdatePowerUps(); + } + +//#if !LIMITED_PREDICTION + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); +//#endif + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +================ +idPlayer::ClientPredictionThink +================ +*/ +void idPlayer::ClientPredictionThink( void ) { + + if ( doInitWeapon ) { + InitWeapon(); + } + + // common code for both the local & non local clients + if ( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + if ( entityNumber == gameLocal.GetDemoFollowClient() ) { + LocalClientPredictionThink(); + return; + } + + if ( entityNumber == gameLocal.localClientNum ) { + LocalClientPredictionThink(); + return; + } + + assert( gameLocal.localClientNum >= 0 ); + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p && p->spectating && p->spectator == entityNumber ) { + LocalClientPredictionThink(); + return; + } + + NonLocalClientPredictionThink(); +} + +/* +================ +idPlayer::GetMasterPosition +================ +*/ +bool idPlayer::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + if( !IsInVehicle() ) { + return idActor::GetMasterPosition( masterOrigin, masterAxis ); + } + + vehicleController.GetDriverPosition( masterOrigin, masterAxis ); + return true; +} + +/* +=============== +idPlayer::PredictionErrorDecay +=============== +*/ +void idPlayer::PredictionErrorDecay( void ) { + if ( predictedUpdated ) { + return; + } + + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + return; + } + + if ( gameLocal.framenum >= predictedFrame ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + + if ( gameLocal.framenum == predictedFrame ) { + + predictionOriginError = predictedOrigin - renderOrigin; + predictionAnglesError = predictedAngles - viewAngles; + predictionAnglesError.Normalize180(); + predictionErrorTime = gameLocal.time; + + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( predictionOriginError.Length(), 0.0f, 100.0f, colorGreen ); + renderSystem->DebugGraph( predictionAnglesError.Length(), 0.0f, 180.0f, colorBlue ); + } + } + + int t = gameLocal.time - predictionErrorTime; + float f = ( net_predictionErrorDecay.GetFloat() - t ) / net_predictionErrorDecay.GetFloat(); + if ( f > 0.0f && f < 1.0f ) { + predictedOrigin = renderOrigin + f * predictionOriginError; + predictedAngles = viewAngles + f * predictionAnglesError; + predictedAngles.Normalize180(); + } else { + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + } + + predictedFrame = gameLocal.framenum; + + } + + viewAngles = predictedAngles; + // adjust them now so they are right for the bound objects ( head and weapon ) + AdjustBodyAngles(); + + predictedUpdated = true; +} + +/* +=============== +idPlayer::WantSmoothing +=============== +*/ +bool idPlayer::WantSmoothing( void ) const { + if ( !gameLocal.isClient ) { + return false; + } + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + return false; + } + return true; +} + +/* +================ +idPlayer::GetPhysicsToVisualTransform +================ +*/ +bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + if ( vehicleController.IsDriving() ) { + vehicleController.GetDriverPosition( origin, axis ); + origin.Zero(); + return true; + } + + PredictionErrorDecay(); + + // smoothen the rendered origin and angles of other clients + if ( gameLocal.framenum >= predictedFrame && WantSmoothing() ) { + + axis = idAngles( 0.0f, predictedAngles.yaw, 0.0f ).ToMat3(); + origin = ( predictedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose(); + + } else { + + axis = viewAxis; + origin = modelOffset; + + } + + return true; +} + +/* +================ +idPlayer::GetPhysicsToSoundTransform +================ +*/ +bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idCamera *camera; + + if ( privateCameraView ) { + camera = privateCameraView; + } else { + camera = gameLocal.GetCamera(); + } + + if ( camera ) { + renderView_t view; + + memset( &view, 0, sizeof( view ) ); + camera->GetViewParms( &view ); + origin = view.vieworg; + axis = view.viewaxis; + return true; + } else { + return idActor::GetPhysicsToSoundTransform( origin, axis ); + } +} + +/* +================ +idPlayer::WriteToSnapshot +================ +*/ +void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + msg.WriteShort( health ); + msg.WriteByte( inventory.armor ); + msg.WriteBits( lastDamageDef, gameLocal.entityDefBits ); + msg.WriteDir( lastDamageDir, 9 ); + msg.WriteShort( lastDamageLocation ); + msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) ); + msg.WriteBits( inventory.weapons, MAX_WEAPONS ); + msg.WriteBits( weaponViewModel.GetSpawnId(), 32 ); + msg.WriteBits( weaponWorldModel.GetSpawnId(), 32 ); +// msg.WriteBits( head.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( lastHitToggle, 1 ); + msg.WriteBits( lastArmorHit, 1 ); + msg.WriteBits( weaponGone, 1 ); + msg.WriteBits( isLagged, 1 ); + msg.WriteBits( isChatting, 1 ); + msg.WriteLong( connectTime ); + msg.WriteByte( lastKiller ? lastKiller->entityNumber : 255 ); + + // vehicleController.WriteToSnapshot( msg ); + + if ( weapon ) { + msg.WriteBits( 1, 1 ); + weapon->WriteToSnapshot( msg ); + } else { + msg.WriteBits( 0, 1 ); + } +//RITUAL BEGIN + msg.WriteBits( inBuyZone, 1 ); + msg.WriteLong( (int)buyMenuCash ); +//RITUAL END +} + +/* +================ +idPlayer::ReadFromSnapshot +================ +*/ +void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, oldHealth, newIdealWeapon, weaponSpawnId, weaponWorldSpawnId; + bool newHitToggle, stateHitch, newHitArmor; + int lastKillerEntity; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + health = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + lastDamageDef = msg.ReadBits( gameLocal.entityDefBits ); + lastDamageDir = msg.ReadDir( 9 ); + lastDamageLocation = msg.ReadShort(); + newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) ); + inventory.weapons = msg.ReadBits( MAX_WEAPONS ); + weaponSpawnId = msg.ReadBits( 32 ); + weaponWorldSpawnId = msg.ReadBits( 32 ); + int latchedSpectator = spectator; + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( spectating && latchedSpectator != spectator && this == gameLocal.GetLocalPlayer() ) { + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // this is where the client updates their spectated player + if ( gameLocal.gameType == GAME_TOURNEY ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (spectator != arena.GetPlayers()[ 0 ]->entityNumber && spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_BRACKET ); + } else if( spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_ONE ); + } else if( spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + if ( gameLocal.entities[ spectator ] ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ spectator ] ); + p->UpdateHudWeapon( p->currentWeapon ); + if ( p->weapon ) { + p->weapon->SpectatorCycle(); + } + } + } + newHitToggle = msg.ReadBits( 1 ) != 0; + newHitArmor = msg.ReadBits( 1 ) != 0; + weaponGone = msg.ReadBits( 1 ) != 0; + isLagged = msg.ReadBits( 1 ) != 0; + isChatting = msg.ReadBits( 1 ) != 0; + connectTime = msg.ReadLong(); + lastKillerEntity = msg.ReadByte(); + if( lastKillerEntity >= 0 && lastKillerEntity < MAX_CLIENTS) { + lastKiller = static_cast(gameLocal.entities[ lastKillerEntity ]); + } else { + lastKiller = NULL; + } + + if ( idealWeapon != newIdealWeapon ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + StopFiring(); + UpdateHudWeapon(); + usercmd.buttons &= (~BUTTON_ATTACK); + } + + // Attach the world and view entities + weaponWorldModel.SetSpawnId( weaponWorldSpawnId ); + if ( weaponWorldModel.IsValid() && weaponViewModel.SetSpawnId( weaponSpawnId ) ) { + currentWeapon = -1; + SetWeapon( idealWeapon ); + } + + // rjohnson: instance persistance information + if ( weaponWorldModel.IsValid() ) { + weaponWorldModel->fl.persistAcrossInstances = true; + weaponWorldModel->SetInstance( GetInstance() ); + } + if ( weaponViewModel.IsValid() ) { + weaponViewModel->fl.persistAcrossInstances = true; + weaponViewModel->SetInstance( GetInstance() ); + } + + // If we have a weapon then update it from the snapshot, otherwise + // we just skip whatever it would have read if it were there + if ( msg.ReadBits( 1 ) ) { + if ( weapon ) { + weapon->ReadFromSnapshot( msg ); + } else { + rvWeapon::SkipFromSnapshot( msg ); + } + } + if ( proto69 ) { + inBuyZone = false; + buyMenuCash = 0.0f; + } else { +//RITUAL BEGIN + inBuyZone = msg.ReadBits( 1 ) != 0; + int cash = msg.ReadLong(); + if ( cash != (int)buyMenuCash ) { + buyMenuCash = (float)cash; + gameLocal.mpGame.RedrawLocalBuyMenu(); + } +//RITUAL END + } + // no msg reading below this + + // if not a local client assume the client has all ammo types + if ( entityNumber != gameLocal.localClientNum ) { + for( i = 0; i < MAX_AMMO; i++ ) { + inventory.ammo[ i ] = 999; + } + } + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + pfl.dead = true; + ClearPowerUps(); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + animator.ClearAllJoints(); + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + + if ( !stateHitch ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + const idDeclEntityDef* def = static_cast(declManager->DeclByIndex ( DECL_ENTITYDEF, lastDamageDef )); + if ( def ) { + // TODO: get attackers push scale? + InitDeathPush ( lastDamageDir, lastDamageLocation, &def->dict, 1.0f ); + ClientDamageEffects ( def->dict, lastDamageDir, ( oldHealth - health ) * 4 ); + } + + //gib them here + if ( health < -20 || ( lastKiller && lastKiller->PowerUpActive( POWERUP_QUADDAMAGE )) ) { + ClientGib( lastDamageDir ); + } + + if ( weapon ) { + weapon->OwnerDied(); + + // Get rid of the weapon now + delete weapon; + weapon = NULL; + currentWeapon = -1; + } + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + //common->DPrintf( "idPlayer::ReadFromSnapshot() - Player respawn detected for %d '%s' - re-enabling clip\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // this is the first time we've seen the player since we heard he died - he may have picked up + // some powerups since he actually spawned in, so restore those + int latchPowerup = inventory.powerups; + Init(); + inventory.powerups = latchPowerup; + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } else if ( oldHealth - health > 2 && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + ClientDamageEffects ( def->dict, lastDamageDir, oldHealth - health ); + pfl.pain = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef ); + } + } + } + + if ( lastHitToggle != newHitToggle ) { + SetLastHitTime( gameLocal.realClientTime, newHitArmor ); + } + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } + + /*if ( (head == NULL || headSpawnId != head.GetSpawnId()) && headSpawnId > 0 ) { + head.SetSpawnId( headSpawnId ); + SetupHead(); + }*/ +} + +/* +================ +idPlayer::WritePlayerStateToSnapshot +================ +*/ +void idPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + msg.WriteDeltaByte( 0, bobCycle ); + msg.WriteDeltaLong( 0, stepUpTime ); + msg.WriteDeltaFloat( 0.0f, stepUpDelta ); + + msg.WriteShort( inventory.weapons ); + msg.WriteByte( inventory.armor ); + msg.WriteShort( inventory.powerups ); + + for( i = 0; i < MAX_AMMO; i++ ) { + msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS ); + } + + for( i = 0; i < POWERUP_MAX; i ++ ) { + msg.WriteLong( inventory.powerupEndTime[ i ] ); + } +} + +/* +================ +idPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + int i, ammo; + + bobCycle = msg.ReadDeltaByte( 0 ); + stepUpTime = msg.ReadDeltaLong( 0 ); + stepUpDelta = msg.ReadDeltaFloat( 0.0f ); + + inventory.weapons = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + inventory.powerups = msg.ReadShort(); + + for( i = 0; i < MAX_AMMO; i++ ) { + ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS ); + if ( gameLocal.time >= inventory.ammoPredictTime ) { + inventory.ammo[ i ] = ammo; + } + } + + int powerup_max = gameLocal.GetCurrentDemoProtocol() == 69 ? 11 : POWERUP_MAX; + for ( i = 0; i < powerup_max; i ++ ) { + inventory.powerupEndTime[ i ] = msg.ReadLong(); + } + while ( i < POWERUP_MAX ) { + inventory.powerupEndTime[ i ] = 0; + i++; + } + + if ( gameLocal.IsMultiplayer() ) { + if ( (inventory.weapons&~oldInventoryWeapons) ) { + //added a weapon from inventory, bring up bar + UpdateHudWeapon(); + } + oldInventoryWeapons = inventory.weapons; + } +} + +/* +================ +idPlayer::ServerReceiveEvent +================ +*/ +bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + + if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { + return true; + } + + // client->server events + switch( event ) { + case EVENT_IMPULSE: { + PerformImpulse( msg.ReadBits( IMPULSE_NUMBER_OF_BITS ) ); + return true; + } + case EVENT_EMOTE: { + // forward the emote on to all clients except the one that sent it to us + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, entityNumber ); + + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return false; + } + } +} + +/* +================ +idPlayer::ClientReceiveEvent +================ +*/ +bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int powerup; + bool start; + + switch ( event ) { + case EVENT_EXIT_TELEPORTER: + Event_ExitTeleporter(); + return true; + case EVENT_ABORT_TELEPORTER: + SetPrivateCameraView( NULL ); + return true; + case EVENT_POWERUP: { + powerup = msg.ReadShort(); + start = ( msg.ReadBits( 1 ) != 0 ); + if ( start ) { + bool team = false; + if ( gameLocal.GetCurrentDemoProtocol() != 69 ) { + team = ( msg.ReadBits( 1 ) != 0 ); + } + GivePowerUp( powerup, 0, team ); + } else { + ClearPowerup( powerup ); + } + + return true; + } + case EVENT_SPECTATE: { + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + // force to spectator if we got this event about a client in a different + // instance + Spectate( spectate ); + + // spectate might re-link clip for stale players, so re-call ClientStale if we're stale + if( fl.networkStale ) { + ClientStale(); + } + return true; + } + case EVENT_ADD_DAMAGE_EFFECT: { + if ( spectating ) { + // if we're spectating, ignore + // happens if the event and the spectate change are written on the server during the same frame (fraglimit) + return true; + } + return idActor::ClientReceiveEvent( event, time, msg ); + } + case EVENT_EMOTE: { + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return idActor::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idPlayer::Hide +================ +*/ +void idPlayer::Hide( void ) { + idActor::Hide(); + + if ( weapon ) { + weapon->HideWorldModel( ); + } +} + +/* +================ +idPlayer::Show +================ +*/ +void idPlayer::Show( void ) { + idActor::Show(); + + if ( weapon ) { + weapon->ShowWorldModel( ); + } +} + +/* +=============== +idPlayer::ShowTip +=============== +*/ +void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) { + if ( tipUp ) { + return; + } + hud->SetStateString( "tip", tip ); + hud->SetStateString( "tiptitle", title ); + hud->HandleNamedEvent( "tipWindowUp" ); + if ( autoHide ) { + PostEventSec( &EV_Player_HideTip, 5.0f ); + } + tipUp = true; +} + +/* +=============== +idPlayer::HideTip +=============== +*/ +void idPlayer::HideTip( void ) { + hud->HandleNamedEvent( "tipWindowDown" ); + tipUp = false; +} + +/* +=============== +idPlayer::Event_HideTip +=============== +*/ +void idPlayer::Event_HideTip( void ) { + HideTip(); +} + +/* +=============== +idPlayer::ShowObjective +=============== +*/ +void idPlayer::ShowObjective( const char *obj ) { + objectiveSystem->HandleNamedEvent( obj ); + objectiveUp = true; +} + + +/* +=============== +idPlayer::HideObjective +=============== +*/ +void idPlayer::HideObjective( void ) { + objectiveSystem->HandleNamedEvent( "closeObjective" ); + objectiveUp = false; +} + +/* +=============== +idPlayer::SetSpectateOrigin +=============== +*/ +void idPlayer::SetSpectateOrigin( void ) { + idVec3 neworig; + + neworig = GetPhysics()->GetOrigin(); + neworig[ 2 ] += EyeHeight(); + neworig[ 2 ] += 25; + SetOrigin( neworig ); +} + +/* +=============== +idPlayer::RemoveWeapon +=============== +*/ +void idPlayer::RemoveWeapon( const char *weap ) { + if ( weap && *weap ) { + inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 ); + } +} + +/* +=============== +idPlayer::CanShowWeaponViewmodel +=============== +*/ +bool idPlayer::CanShowWeaponViewmodel( void ) const { + return showWeaponViewModel; +} + +/* +=============== +idPlayer::SetLevelTrigger +=============== +*/ +void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) { + if ( levelName && *levelName && triggerName && *triggerName ) { + idLevelTriggerInfo lti; + lti.levelName = levelName; + lti.triggerName = triggerName; + inventory.levelTriggers.Append( lti ); + } +} + + +/* +=============== +idPlayer::Event_LevelTrigger +=============== +*/ +void idPlayer::Event_LevelTrigger( void ) { + idStr mapName = gameLocal.GetMapName(); + mapName.StripPath(); + mapName.StripFileExtension(); + for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){ + idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 1, this ); + } + } + } +} + +/* +================ +idPlayer::ToggleFlashlight +================ +*/ +void idPlayer::ToggleFlashlight ( void ) { + // Dead people can use flashlights +// RAVEN BEGIN +// mekberg: check to see if the weapon is enabled. + if ( health <= 0 || !weaponEnabled ) { + return; + } +// RAVEN END + + int flashlightWeapon = currentWeapon; + if ( !spawnArgs.GetBool( va( "weapon%d_flashlight", flashlightWeapon ) ) ) { + // TODO: find the first flashlight weapon that has ammo starting at the bottom + for( flashlightWeapon = MAX_WEAPONS - 1; flashlightWeapon >= 0; flashlightWeapon-- ) { + if ( inventory.weapons & ( 1 << flashlightWeapon ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", flashlightWeapon ) ); + int ammo = inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ]; + + if ( !ammo ) { + continue; + } + + if ( spawnArgs.GetBool ( va ( "weapon%d_flashlight", flashlightWeapon ) ) ) { + break; + } + } + } + + // Couldnt find flashlight + if ( flashlightWeapon < 0 ) { + return; + } + } + + // If the current weapon isnt the flashlight then always force the flashlight on + if ( flashlightWeapon != idealWeapon ) { + flashlightOn = true; + idealWeapon = flashlightWeapon; + // Inform the weapon to toggle the flashlight, this will eventually cause the players + // Flashlight method to be called + } else if ( weapon ) { + weapon->Flashlight ( ); + } +} + +/* +================ +idPlayer::Flashlight +================ +*/ +void idPlayer::Flashlight ( bool on ) { + flashlightOn = on; +} + +/* +================ +idPlayer::DamageFeedback +================ +*/ +void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + + //rvTramCars weren't built on the idActor inheritance hierarchy but need to be treated like one when shot. + //TODO: Maybe add a key to entity flags that will allow them to be shot as actors even if they aren't actors? + if( !victim || ( !victim->IsType( idActor::GetClassType() ) && !victim->IsType( rvTramCar::GetClassType() ) ) || victim->health <= 0 ) { + return; + } + + bool armorHit = false; + + if( gameLocal.isMultiplayer && victim->IsType( idPlayer::GetClassType() ) ) { + if( this == victim ) { + // no feedback for self hits + return; + } + if( gameLocal.IsTeamGame() && ((idPlayer*)victim)->team == team ) { + // no feedback for team hits + return; + } + if( ((idPlayer*)victim)->inventory.armor > 0 ) { + armorHit = true; + } + } + + SetLastHitTime( gameLocal.time, armorHit ); +} + +/* +============== +idPlayer::GetWeaponDef +============== +*/ +const idDeclEntityDef* idPlayer::GetWeaponDef ( int weaponIndex ) { + if ( cachedWeaponDefs[weaponIndex] ) { + return cachedWeaponDefs[weaponIndex]; + } + + idStr weapon; + weapon = spawnArgs.GetString ( va("def_weapon%d", weaponIndex ) ); + if ( !weapon.Length() ) { + return NULL; + } + + cachedWeaponDefs[weaponIndex] = gameLocal.FindEntityDef ( weapon, false ); + if ( !cachedWeaponDefs[weaponIndex] ) { + gameLocal.Error( "Could not find weapon definition '%s'", weapon.c_str() ); + } + + return cachedWeaponDefs[weaponIndex]; +} + +/* +============== +idPlayer::GetPowerupDef + +Returns the powerup dictionary for the given powerup index. The dictionary is cached to ensure a +speedy retrieval after the first call. +============== +*/ +const idDeclEntityDef* idPlayer::GetPowerupDef ( int powerupIndex ) { + const idDict* types; + int i; + int num; + + if ( cachedPowerupDefs[powerupIndex] ) { + return cachedPowerupDefs[powerupIndex]; + } + + types = gameLocal.FindEntityDefDict( "powerup_types", false ); + if ( !types ) { + gameLocal.Error( "Could not find entity definition for 'powerup_types'" ); + } + + num = types->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + const idKeyValue* kv; + kv = types->GetKeyVal( i ); + if ( atoi(kv->GetValue()) == powerupIndex ) { + cachedPowerupDefs[powerupIndex] = gameLocal.FindEntityDef ( kv->GetKey(), false ); + if ( !cachedPowerupDefs[powerupIndex] ) { + gameLocal.Error( "Could not find powerup definition '%s'", kv->GetKey().c_str() ); + } + return cachedPowerupDefs[powerupIndex]; + } + } + + gameLocal.Error( "Could not find powerup definition '%d'", powerupIndex ); + + return NULL; +} + +/* +============== +idPlayer::discoverSecretArea + +Announces a secret area and increases the secret area tally +============== +*/ +void idPlayer::DiscoverSecretArea(const char* _description) { + + //increment the secret area tally + inventory.secretAreasDiscovered++; +} + +/* +============== +idPlayer::StartBossBattle + +Starts a boss battle with the given entity. During a boss battle the health of the boss +will be displayed on the HUD +============== +*/ +void idPlayer::StartBossBattle ( idEntity* enemy ) { + bossEnemy = enemy; + idUserInterface *hud_ = GetHud(); + if ( hud_ ) { + hud_->SetStateInt ( "boss_maxhealth", enemy->health ); + hud_->HandleNamedEvent ( "showBossBar" ); + } +} + +/* +===================== +idPlayer::SetInitialHud +===================== +*/ +void idPlayer::SetInitialHud ( void ) { + if ( !mphud || !gameLocal.isMultiplayer || gameLocal.GetLocalPlayer() != this ) { + return; + } + + mphud->SetStateInt( "gametype", gameLocal.gameType ); + + if( hud ) { + hud->SetStateInt( "gametype", gameLocal.gameType ); + } + + mphud->HandleNamedEvent( "InitHud" ); + mphud->HandleNamedEvent( "TeamChange" ); + + if( gameLocal.IsFlagGameType() ) { + mphud->SetStateFloat( "ap", gameLocal.mpGame.assaultPoints.Num() ); + + for( int i = 0; i < TEAM_MAX; i++ ) { + mphud->SetStateInt( "team", i ); + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_DROPPED ) { + mphud->HandleNamedEvent( "flagDrop" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN ) { + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_MARINE ) { + mphud->SetStateInt( "team", TEAM_MARINE ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_STROGG ) { + mphud->SetStateInt( "team", TEAM_STROGG ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_AT_BASE ) { + mphud->SetStateInt( "team", i ); + mphud->HandleNamedEvent( "flagReturn" ); + } + } + + for( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + mphud->SetStateFloat( "apindex", i ); + //mphud->SetStateInt( "apteam", ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetAPOwner( i ) ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "APCaptured" ); + } + } + + mphud->StateChanged ( gameLocal.time ); +} + +void idPlayer::RemoveClientModel ( const char *entityDefName ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp ( ( static_cast ( cent ) )->GetClassname(), entityDefName ) ) { + cent->Unbind ( ); + delete cent; + } + } + } +} + +rvClientEntityPtr idPlayer::AddClientModel ( const char* entityDefName, const char* shaderName ) { + rvClientEntityPtr ptr; + ptr = NULL; + + if ( entityDefName == NULL ) { + return ptr; + } + + const idDict* entityDef = gameLocal.FindEntityDefDict ( entityDefName, false ); + + if ( entityDef == NULL ) { + return ptr; + } + + rvClientModel *newModel = NULL; + + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientModel" ); + + if( newModel == NULL ) { + return ptr; + } + idMat3 rotation; + rotation = entityDef->GetAngles( "angles" ).ToMat3(); + newModel->SetAxis( rotation ); + + newModel->SetOrigin( entityDef->GetVector( "origin" ) * rotation ); + + newModel->Bind ( this, animator.GetJointHandle( entityDef->GetString ( "joint" ) ) ); + + newModel->SetCustomShader ( shaderName ); + newModel->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + newModel->GetRenderEntity()->noSelfShadow = true; + newModel->GetRenderEntity()->noShadow = true; + + ptr = newModel; + + return ptr; +} + +void idPlayer::RemoveClientModels ( void ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + cent->Unbind ( ); + delete cent; + } + } +} + +/* +===================== +idPlayer::ClientGib() +ddynerman: Spawns client side gibs around this player +===================== +*/ +void idPlayer::ClientGib( const idVec3& dir ) { + + if( !spawnArgs.GetBool( "gib" ) ) { + return; + } + + int i; + idVec3 entityCenter, velocity; + idList list; + + + // hide the player + SetSkin( gibSkin ); + + //and the head + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->UnlinkCombat(); + delete clientHead; + } + } else { + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + } + + // blow out the gibs in the given direction away from the center of the entity + + + // spawn gib client models + rvClientMoveable::SpawnClientMoveables( this, "clientgib", &list ); + + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + for ( i = 0; i < list.Num(); i++ ) { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + // we don't want collision on gibs + //list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + + list[i]->GetPhysics()->ApplyImpulse( 0, list[i]->GetPhysics()->GetOrigin(), velocity * ( 25000.0f + ( gameLocal.random.RandomFloat() * 50000.0f)) ); +// list[i]->GetPhysics()->SetLinearVelocity( velocity * ( 25.0f + ( gameLocal.random.RandomFloat() * 300.0f))); + list[i]->GetPhysics()->SetAngularVelocity( velocity * ( -250.0f + ( gameLocal.random.RandomFloat() * 500.0f))); + + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + + list[i]->PostEventMS( &CL_FadeOut, SEC2MS( 4.0f ), SEC2MS( 2.0f ) ); + } + + + //play gib fx + gameLocal.PlayEffect( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + // gibs are PVS agnostic. If we gib a player outside of our PVS, set the oldHealth + // to below 0 so when this player re-appears in our snap we respawn him + if( gameLocal.isClient && gameLocal.GetLocalPlayer() && health > 0 ) { + health = -100; + } +} + +/* +===================== +idPlayer::CanDamage +===================== +*/ +bool idPlayer::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ) { + if( gameLocal.isMultiplayer && health <= 0 ) { + return false; + } + + return idActor::CanDamage( origin, damagePoint, ignoreEnt ); +} + +/* +===================== +idPlayer::ClientDamageEffects + +===================== +*/ +void idPlayer::ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ) { + idVec3 from; + idVec3 localDir; + float fadeDB; + + // Only necessary on clients + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + from = dir; + from.Normalize(); + viewAxis.ProjectVector( from, localDir ); + + if ( damage ) { +// RAVEN BEGIN +// jnewquist: Controller rumble + idPlayer *p = gameLocal.GetLocalPlayer(); + + if ( p && ( p == this || ( p->spectating && p->spectator == entityNumber ) ) ) { + playerView.DamageImpulse( localDir, &damageDef, damage ); + } +// RAVEN END + } + + // Visual effects + if ( health > 0 && damage ) { + // Let the hud know about the hit + if ( hud ) { + hud->SetStateFloat ( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + hud->HandleNamedEvent ( "playerHit" ); + } + } + + // Sound effects + if ( damageDef.GetFloat ( "hl_volumeDB", "-40", fadeDB ) ) { + float fadeTime; + + fadeTime = 0.0f; + if ( !pfl.hearingLoss ) { + const char* fade; + + fadeTime = damageDef.GetFloat ( "hl_fadeOutTime", ".25" ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); + + pfl.hearingLoss = true; + + // sound overlayed? + if ( damageDef.GetString ( "snd_hl", "", &fade ) && *fade ) { + StartSoundShader ( declManager->FindSound ( fade ), SND_CHANNEL_DEMONIC, 0, false, NULL ); + } + } + + fadeTime += damageDef.GetFloat ( "hl_time", "1" ); + + CancelEvents ( &EV_Player_FinishHearingLoss ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, damageDef.GetFloat ( "hl_fadeInTime", ".25" ) ); + } +} + +/* +===================== +idPlayer::GetDebugInfo +===================== +*/ +void idPlayer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idActor::GetDebugInfo ( proc, userData ); + proc ( "idPlayer", "inventory.armor", va("%d", inventory.armor ), userData ); + proc ( "idPlayer", "inventory.weapons", va("%d", inventory.weapons ), userData ); + proc ( "idPlayer", "inventory.powerups", va("%d", inventory.powerups ), userData ); +} + + + +// RAVEN END + +/* +===================== +idPlayer::ApplyImpulse +===================== +*/ +void idPlayer::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( !ent) { + gameLocal.Warning( "idPlayer::ApplyImpulse called with null entity as instigator."); + return; + } + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time + 1000; + + if( ent->IsType( idPlayer::Type ) && ent != this ) { + lastImpulsePlayer = static_cast(ent); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + idEntity* owner = static_cast(ent)->GetOwner(); + if( owner && owner->IsType( idPlayer::Type ) && owner != this ) { + lastImpulsePlayer = static_cast(owner); + } + } + + idAFEntity_Base::ApplyImpulse( ent, id, point, impulse, splash ); +} + +/* +===================== +idPlayer::SetupHead +===================== +*/ +void idPlayer::SetupHead( const char* headModel, idVec3 headOffset ) { + if( gameLocal.isMultiplayer ) { + // player's don't use idActor's real head entities - uses clientEntities instead + if( clientHead.GetEntity() ) { + delete clientHead.GetEntity(); + clientHead = NULL; + } + + + if( spectating || (gameLocal.GetLocalPlayer() && instance != gameLocal.GetLocalPlayer()->GetInstance()) ) { + return; + } + + const idDict* headDict = gameLocal.FindEntityDefDict( headModel, false ); + if ( !headDict ) { + return; + } + + rvClientAFAttachment* headEnt = clientHead.GetEntity(); + gameLocal.SpawnClientEntityDef( *headDict, (rvClientEntity**)&headEnt, false ); + if( headEnt ) { + idStr jointName = spawnArgs.GetString( "joint_head" ); + jointHandle_t joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + headEnt->SetBody ( this, headDict->GetString ( "model" ), joint ); + + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->Bind( this, joint, true ); + headEnt->InitCopyJoints(); + + // Spawn might have parsed a skin from the spawnargs, save it for future use here + headSkin = headEnt->GetRenderEntity()->customSkin; + clientHead = headEnt; + } + } else { + idActor::SetupHead( headModel, headOffset ); + + if ( head ) { + head->fl.persistAcrossInstances = true; + } + } +} + +/* +===================== +idPlayer::GUIMainNotice +===================== +*/ +void idPlayer::GUIMainNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "main_notice_text", message ); + mphud->SetStateBool( "main_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "main_notice" ); +} + +/* +===================== +idPlayer::GUIFragNotice +===================== +*/ +void idPlayer::GUIFragNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "frag_notice_text", message ); + mphud->SetStateBool( "frag_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "frag_notice" ); +} + +/* +===================== +idPlayer::SetHudOverlay +===================== +*/ +void idPlayer::SetHudOverlay( idUserInterface* overlay, int duration ) { + overlayHud = overlay; + overlayHudTime = gameLocal.time + duration; +} + +// RAVEN BEGIN +// mekberg: wrap saveMessages +/* +===================== +idPlayer::SaveMessage +===================== +*/ +void idPlayer::SaveMessage( void ) { +#ifndef _XENON + if ( GetHud( ) ) { + GetHud()->HandleNamedEvent( "saveMessage" ); + } + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "saveMessage" ); + } +#endif +} + +// mekberg: set pm_ cvars +/* +===================== +idPlayer::SetPMCVars +===================== +*/ +void idPlayer::SetPMCVars( void ) { + const idKeyValue *kv; + + if ( !gameLocal.isMultiplayer || gameLocal.isServer ) { + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + } +} +// RAVEN END + +/* +===================== +idPlayer::GetSpawnClassname +===================== +*/ +const char* idPlayer::GetSpawnClassname ( void ) { + idEntity* world; + const char* entityFilter; + + // Test player def + if ( *g_testPlayer.GetString() ) { + return g_testPlayer.GetString ( ); + } + + // Multiplayer + if ( gameLocal.isMultiplayer ) { + return "player_marine_mp"; + } + + // See if the world spawn specifies a player + world = gameLocal.entities[ENTITYNUM_WORLD]; + assert( world ); + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + return world->spawnArgs.GetString( va("player_%s", entityFilter ), world->spawnArgs.GetString( "player", "player_marine" ) ); + } + + return world->spawnArgs.GetString( "player", "player_marine" ); +} + +/* +=============== +idPlayer::SetInstance +=============== +*/ +void idPlayer::SetInstance( int newInstance ) { + common->DPrintf( "idPlayer::SetInstance() - Setting instance for '%s' to %d\n", name.c_str(), newInstance ); + idEntity::SetInstance( newInstance ); + + if( head.GetEntity() ) { + head.GetEntity()->SetInstance( newInstance ); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetInstance( newInstance ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetInstance( newInstance ); + } + } + + if( weaponWorldModel ) { + weaponWorldModel->SetInstance( newInstance ); + } + + if( weaponViewModel ) { + weaponViewModel->SetInstance( newInstance ); + } + + // reschedule time announcements if needed + if( this == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleTimeAnnouncements(); + + if( gameLocal.isServer ) { + // remove/add heads on server + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + if( player->instance != newInstance ) { + if( player->clientHead.GetEntity() ) { + delete player->clientHead; + player->clientHead = NULL; + } + } else { + player->UpdateModelSetup( true ); + } + } + } + } + } +} + +/* +=============== +idPlayer::JoinInstance +=============== +*/ +void idPlayer::JoinInstance( int newInstance ) { + assert( gameLocal.isServer ); + if( instance == newInstance ) { + return; + } + + if( newInstance < 0 || newInstance >= MAX_ARENAS ) { + gameLocal.Warning( "idPlayer::JoinInstance() - Invalid instance %d specified\n", newInstance ); + } + + if( gameLocal.GetNumInstances() <= newInstance || gameLocal.GetInstance( newInstance ) == NULL ) { + // don't populate instance until player gets linked into the right one + gameLocal.AddInstance( newInstance ); + + if( this == gameLocal.GetLocalPlayer() ) { + // ensure InstanceLeave() gets called on newly spawned entities before + // InstanceJoin() gets called. + gameLocal.mpGame.ServerSetInstance( instance ); + } + } + + SetArena( newInstance ); + SetInstance( newInstance ); + + gameLocal.GetInstance( newInstance )->JoinInstance( this ); +} + +/* +=============== +idPlayer::SetEmote +=============== +*/ +void idPlayer::SetEmote( playerEmote_t newEmote ) { + emote = newEmote; + + // if we're the ones generating the emote, pass it along + if( gameLocal.localClientNum == entityNumber ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( emote ); + if( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, -1 ); + } else { + ClientSendEvent( EVENT_EMOTE, &msg ); + } + } +} + +/* +=============== +idPlayer::GetGroundElevator +=============== +*/ +idEntity* idPlayer::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + //NOTE: for player, don't care if all the way on, or not + return groundEnt; +} + +/* +=================== +idPlayer::IsCrouching +=================== +*/ +bool idPlayer::IsCrouching( void ) const { + return physicsObj.IsCrouching(); +} + +/* +=============== +idPlayer::SetArena +=============== +*/ +void idPlayer::SetArena( int newArena ) { + if( arena == newArena ) { + return; + } + + arena = newArena; + if( gameLocal.GetLocalPlayer() == this && gameLocal.gameType == GAME_TOURNEY ) { + if( arena >= 0 && arena <= MAX_ARENAS ) { + if( arena < MAX_ARENAS ) { +// RAVEN BEGIN +// rhummer: localized these strings. + GUIMainNotice( va( common->GetLocalizedString( "#str_107270" ), newArena + 1 ) ); + } else { + GUIMainNotice( common->GetLocalizedString( "#str_107271" ) ); + } +// RAVEN END + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( newArena, TGH_BRACKET ); + } + gameLocal.mpGame.RemoveAnnouncerSoundRange( AS_TOURNEY_JOIN_ARENA_ONE, AS_TOURNEY_JOIN_ARENA_EIGHT ); + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)(AS_TOURNEY_JOIN_ARENA_ONE + arena), gameLocal.time ); + } + } +} + +/* +=============== +idPlayer::Event_DamageEffect +=============== +*/ +void idPlayer::Event_DamageEffect( const char *damageDefName, idEntity* _damageFromEnt ) +{ + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) + { + idVec3 dir = (_damageFromEnt!=NULL)?(GetEyePosition()-_damageFromEnt->GetEyePosition()):viewAxis[2]; + dir.Normalize(); + int damage = 1; + ClientDamageEffects( damageDef->dict, dir, damage ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = 0; + } +} + +/* +=============== +idPlayer::UpdateDeathShader +=============== +*/ +void idPlayer::UpdateDeathShader ( bool state_hitch ) { + if ( !doingDeathSkin && gameLocal.time > deathSkinTime && deathSkinTime ) { + deathSkinTime = 0; + + deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" ); + doingDeathSkin = true; + if ( state_hitch ) { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } else { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } + renderEntity.noShadow = true; + UpdateVisuals(); + } +} + +/* +=============== +idPlayer::Event_InitWeapon +=============== +*/ +void idPlayer::InitWeapon( void ) { + doInitWeapon = false; + currentWeapon = -1; + SetWeapon( idealWeapon ); +} + +/* +=============== +idPlayer::GetHitscanTint +=============== +*/ +const idVec4& idPlayer::GetHitscanTint( void ) { + if( gameLocal.IsTeamGame() ) { + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 2 ) { + if( team == TEAM_MARINE ) { + return marineHitscanTint; + } else if( team == TEAM_STROGG ) { + return stroggHitscanTint; + } else { + gameLocal.Error( "idPlayer::GetHitscanTint() - Unknown team '%d' on player %d '%s'\n", team, entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + } + } else { + return defaultHitscanTint; + } + } + + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 1 ) { + return hitscanTint; + } + + return defaultHitscanTint; +} + +/* +=============== +idPlayer::IsReady +=============== +*/ +bool idPlayer::IsReady( void ) { + return !gameLocal.serverInfo.GetBool( "si_useReady" ) || ready || forcedReady; +} + +/* +=============== +idPlayer::ForceScoreboard +=============== +*/ +void idPlayer::ForceScoreboard( bool force, int time ) { + forceScoreBoard = force; + forceScoreBoardTime = time; +} + +/* +=============== +idPlayer::GetTextTourneyStatus +=============== +*/ +const char* idPlayer::GetTextTourneyStatus( void ) { + if( tourneyStatus == PTS_ADVANCED ) { + return common->GetLocalizedString( "#str_107740" ); + } else if( tourneyStatus == PTS_ELIMINATED ) { + return common->GetLocalizedString( "#str_107729" ); + } else if( tourneyStatus == PTS_PLAYING ) { + return common->GetLocalizedString( "#str_107728" ); + } else if( tourneyStatus == PTS_UNKNOWN ) { + return common->GetLocalizedString( "#str_107739" ); + } + return "UNKNOWN TOURNEY STATUS"; +} + +/* +=============== +idPlayer::ClientInstanceJoin +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceJoin( void ) { + assert( gameLocal.isClient ); + + common->DPrintf( "idPlayer::ClientInstanceJoin() - client %d ('%s') is being shown\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // restore client + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientInstanceLeave +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceLeave( void ) { + assert( gameLocal.isClient ); + common->DPrintf( "idPlayer::ClientInstanceLeave() - client %d ('%s') is being hidden\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // force client to spectate + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientStale +=============== +*/ +bool idPlayer::ClientStale( void ) { + idEntity::ClientStale(); + + // remove all powerup effects + for( int i = 0; i < POWERUP_MAX; i++ ) { + if( inventory.powerups & ( 1 << i ) ) { + StopPowerUpEffect( i ); + } + } + + + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + + // never delete client + return false; +} + +/* +=============== +idPlayer::ClientUnstale +=============== +*/ +void idPlayer::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + // force render ent to position + renderEntity.axis = physicsObj.GetAxis(); + renderEntity.origin = physicsObj.GetOrigin(); + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // the powerup effects ( rvClientEntity ) will do some bindings, which in turn will call GetPosition + // which uses the predictedOrigin .. which won't be updated till we Think() so just don't leave the predictedOrigin to the old position + predictedOrigin = renderEntity.origin; + + // restart powerup effects on clients that are coming back into our snapshot + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( inventory.powerups & (1 << i) ) { + StartPowerUpEffect( i ); + } + } + + UpdateModelSetup( true ); + + if ( weapon ) { + weapon->ClientUnstale(); + } +} + +/* +=============== +idPlayer::AllowedVoiceDest +=============== +*/ +bool idPlayer::AllowedVoiceDest( int from ) { + + int i, free; + + free = -1; + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + if( voiceDest[i] == from ) { + voiceDestTimes[i] = gameLocal.time; + return true; + } + + if( voiceDestTimes[i] + 200 < gameLocal.time ) { + free = i; + } + } + + if( free > -1 ) { + voiceDest[free] = from; + voiceDestTimes[i] = gameLocal.time; + return true; + } + + return false; +} + +// RITUAL BEGIN +void idPlayer::ClampCash( float minCash, float maxCash ) +{ + if( buyMenuCash < minCash ) + buyMenuCash = minCash; + + if( buyMenuCash > maxCash ) + buyMenuCash = maxCash; +} + +void idPlayer::GiveCash( float cashDeltaAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + float oldCash = buyMenuCash; + buyMenuCash += cashDeltaAmount; + ClampCash( minCash, maxCash ); + + if( (int)buyMenuCash != (int)oldCash ) + { + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if( (int)buyMenuCash > (int)oldCash ) + { + // Play the "get cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } + else if( (int)buyMenuCash < (int)oldCash ) + { + // Play the "lose cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +void idPlayer::SetCash( float newCashAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + buyMenuCash = newCashAmount; + ClampCash( minCash, maxCash ); +} + +void idPlayer::ResetCash() +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + //buyMenuCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerStartingCash", 0 ); + + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + buyMenuCash = (float) gameLocal.serverInfo.GetInt("si_buyModeStartingCredits"); + ClampCash( minCash, maxCash ); +} + +/** + * Checks to see if the player can accept this item in their inventory + * + * weaponName Name of the weapon. + */ +int idPlayer::CanSelectWeapon(const char* weaponName) +{ + int weaponNum = -1; + if(weaponName == NULL) + return weaponNum; + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + weaponNum = i; + break; + } + } + } + + return weaponNum; +} + +// RITUAL END diff --git a/source/game/Player.h b/source/game/Player.h new file mode 100644 index 0000000..c499507 --- /dev/null +++ b/source/game/Player.h @@ -0,0 +1,1332 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYER_H__ +#define __GAME_PLAYER_H__ + +/* +=============================================================================== + + Player entity. + +=============================================================================== +*/ + +extern const idEventDef EV_Player_GetButtons; +extern const idEventDef EV_Player_GetMove; +extern const idEventDef EV_Player_GetViewAngles; +extern const idEventDef EV_Player_SetViewAngles; +extern const idEventDef EV_Player_EnableWeapon; +extern const idEventDef EV_Player_DisableWeapon; +extern const idEventDef EV_Player_ExitTeleporter; +extern const idEventDef EV_Player_SelectWeapon; +extern const idEventDef EV_Player_Freeze; +extern const idEventDef EV_SpectatorTouch; +extern const idEventDef EV_Player_SetArmor; +extern const idEventDef EV_Player_SetExtraProjPassEntity; +extern const idEventDef EV_Player_DamageEffect; + +const float THIRD_PERSON_FOCUS_DISTANCE = 512.0f; +const int LAND_DEFLECT_TIME = 150; +const int LAND_RETURN_TIME = 300; +const int FOCUS_TIME = 200; +const int FOCUS_GUI_TIME = 300; +const int FOCUS_USABLE_TIME = 100; + +const int MAX_WEAPONS = 16; +const int MAX_AMMO = 16; +const int CARRYOVER_FLAG_AMMO = 0x40000000; +const int CARRYOVER_FLAG_ARMOR_LIGHT = 0x20000000; +const int CARRYOVER_FLAG_ARMOR_HEAVY = 0x10000000; +const int CARRYOVER_WEAPONS_MASK = 0x0FFFFFFF; +const int CARRYOVER_FLAGS_MASK = 0xF0000000; + +const int MAX_SKILL_LEVELS = 4; + +const int ZERO_VOLUME = -40; // volume at zero +const int DMG_VOLUME = 5; // volume when taking damage +const int DEATH_VOLUME = 15; // volume at death + +const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five seconds + +const int ASYNC_PLAYER_INV_AMMO_BITS = idMath::BitsForInteger( 999 ); // 9 bits to cover the range [0, 999] +const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60] +const int ASYNC_PLAYER_INV_WPMOD_BITS = 3; // 3 bits (max of 3 mods per gun) +// NOTE: protocol 69 used 6 bits, but that's only used for client -> server traffic, so doesn't affect backwards protocol replay compat +const int IMPULSE_NUMBER_OF_BITS = 8; // allows for 2< items; + idStrList pdas; + idStrList pdaSecurity; + idStrList videos; + + idList levelTriggers; + + idInventory() { Clear(); } + ~idInventory() { Clear(); } + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Clear( void ); + void GivePowerUp( idPlayer* player, int powerup, int msec ); + void ClearPowerUps( void ); + void GetPersistantData( idDict &dict ); + void RestoreInventory( idPlayer *owner, const idDict &dict ); + bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped = false, bool checkOnly = false ); + void Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ); + int AmmoIndexForAmmoClass( const char *ammo_classname ) const; + int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + int AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired = NULL ); + const char * AmmoClassForWeaponClass( const char *weapon_classname); + +// RAVEN BEGIN +// mekberg: if the player can pick up the ammo at this time + bool DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ); +// RAVEN END + + int AmmoIndexForWeaponIndex( int weaponIndex ); + int StartingAmmoForWeaponIndex( int weaponIndex ); + int AmmoRegenStepForWeaponIndex( int weaponIndex ); + int AmmoRegenTimeForWeaponIndex( int weaponIndex ); + + int HasAmmo( int index, int amount ); + bool UseAmmo( int index, int amount ); + int HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first + + int nextItemPickup; + int nextItemNum; + int onePickupTime; + idList pickupItemNames; + idList objectiveNames; +// idList database; + + int secretAreasDiscovered; +}; + +class idPlayer : public idActor { +public: + + enum { + EVENT_IMPULSE = idEntity::EVENT_MAXEVENTS, + EVENT_EXIT_TELEPORTER, + EVENT_ABORT_TELEPORTER, + EVENT_POWERUP, + EVENT_SPECTATE, + EVENT_EMOTE, + EVENT_MAXEVENTS + }; + + friend class idThread; + + usercmd_t usercmd; + + class idPlayerView playerView; // handles damage kicks and effects + + bool alreadyDidTeamAnnouncerSound; + bool noclip; + bool godmode; + int godmodeDamage; + bool undying; + + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles + idAngles spawnAngles; + idAngles viewAngles; // player view angles + idAngles cmdAngles; // player cmd angles + + int buttonMask; + int oldButtons; + int oldFlags; + + int lastHitTime; // last time projectile fired by player hit target + int lastSavingThrowTime; // for the "free miss" effect + + struct playerFlags_s { + bool forward :1; + bool backward :1; + bool strafeLeft :1; + bool strafeRight :1; + bool attackHeld :1; + bool weaponFired :1; + bool jump :1; + bool crouch :1; + bool onGround :1; + bool onLadder :1; + bool dead :1; + bool run :1; + bool pain :1; + bool hardLanding :1; + bool softLanding :1; + bool reload :1; + bool teleport :1; + bool turnLeft :1; + bool turnRight :1; + bool hearingLoss :1; + bool objectiveFailed :1; + bool noFallingDamage :1; + } pfl; + + // inventory + idInventory inventory; + + rvWeapon* weapon; + idEntityPtr weaponViewModel; + idEntityPtr weaponWorldModel; + const idDeclEntityDef* weaponDef; + + + idUserInterface * hud; // Common hud + idUserInterface * mphud; // hud overlay containing MP elements + + idUserInterface * objectiveSystem; + idUserInterface * cinematicHud; + bool objectiveSystemOpen; + bool objectiveButtonReleased; + bool disableHud; + bool showNewObjectives; + + int lastDmgTime; + int deathClearContentsTime; + bool doingDeathSkin; + int nextHealthPulse; // time when health will tick down + int nextAmmoRegenPulse[ MAX_AMMO ]; // time when ammo will regenerate + int nextArmorPulse; // time when armor will tick down + bool hiddenWeapon; // if the weapon is hidden ( in noWeapons maps ) + + // mp stuff + int spectator; + + bool scoreBoardOpen; + bool forceScoreBoard; + bool forceRespawn; + int forceScoreBoardTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + bool allowedToRespawn; +// squirrel: Mode-agnostic buymenus + bool inBuyZone; + bool inBuyZonePrev; +// RITUAL END + bool spectating; + bool lastHitToggle; + bool lastArmorHit; + bool forcedReady; + int lastArenaChange; + + bool wantSpectate; // from userInfo + bool jumpDuringHitch; + + bool weaponGone; // force stop firing + bool useInitialSpawns; // toggled by a map restart to be active for the first game spawn + bool isLagged; // replicated from server, true if packets haven't been received from client. + bool isChatting; // replicated from server, true if the player is chatting. + + int lastSpectateTeleport; + int latchedTeam; // need to track when team gets changed + int spawnedTime; // when client first enters the game + int hudTeam; + + idEntityPtr teleportEntity; // while being teleported, this is set to the entity we'll use for exit + int teleportKiller; // entity number of an entity killing us at teleporter exit + + idEntityPtr lastKiller; + + // timers + int minRespawnTime; // can respawn when time > this, force after g_forcerespawn + int maxRespawnTime; // force respawn after this time + + // the first person view values are always calculated, even + // if a third person view is used + idVec3 firstPersonViewOrigin; + idMat3 firstPersonViewAxis; + + idDragEntity dragEntity; + idVec3 intentDir; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr extraProjPassEntity; + + bool vsMsgState; + + int lastPickupTime; +//RAVEN BEGIN +// asalmon: the eneny the player is most likely currently aiming at +#ifdef _XBOX + idEntityPtr bestEnemy; +#endif +//RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + float buyMenuCash; +// RITUAL END + +public: + CLASS_PROTOTYPE( idPlayer ); + + idPlayer(); + virtual ~idPlayer(); + + void Spawn( void ); + void Think( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + static const char* GetSpawnClassname ( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + void Init( void ); + void PrepareForRestart( void ); + virtual void Restart( void ); + void SetWeapon ( int weapon ); + void SetupWeaponEntity( void ); + bool SelectSpawnPoint( idVec3 &origin, idAngles &angles ); + void SpawnFromSpawnSpot( void ); + void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + void SetClipModel( bool forceSpectatorBBox = false ); // spectator mode uses a different bbox size + + void SavePersistantInfo( void ); + void RestorePersistantInfo( void ); + void SetLevelTrigger( const char *levelName, const char *triggerName ); + + bool UserInfoChanged( void ); + idDict * GetUserInfo( void ); +// RAVEN BEGIN +// shouchard: BalanceTDM->BalanceTeam (now used for CTF as well as TDM) + bool BalanceTeam( void ); +// RAVEN END + void CacheWeapons( void ); + + bool HandleESC( void ); + void EnterCinematic( void ); + void ExitCinematic( void ); + bool SkipCinematic( void ); + + void UpdateConditions( void ); + void SetViewAngles( const idAngles &angles ); + + void BiasIntentDir ( idVec3 newIntentDir, float prevBias = 199.0f ); + + // delta view angles to allow movers to rotate the view of the player + void UpdateDeltaViewAngles( const idAngles &angles ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ); + // use exitEntityNum to specify a teleport with private camera view and delayed exit + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ); + + void Kill( bool delayRespawn, bool nodamage ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + renderView_t * GetRenderView( void ); + void SmoothenRenderView( bool firstPerson ); + void CalculateRenderView( void ); // called every tic by player code + void CalculateFirstPersonView( void ); + + void DrawShadow( renderEntity_t *headRenderEnt ); + void DrawHUD( idUserInterface *hud ); + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + float DefaultFov( void ) const; + float CalcFov( bool honorZoom ); + void CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ); + void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void OffsetThirdPersonView( float angle, float range, float height, bool clip ); +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + void OffsetThirdPersonVehicleView ( bool clip ); +// RAVEN END + bool OffsetThirdPersonTargetView ( void ); + + bool Give( const char *statname, const char *value, bool dropped = false ); + bool GiveItem( idItem *item ); + void GiveItem( const char *name ); + + // Inventory + bool GiveInventoryItem( idDict *item ); + void RemoveInventoryItem( idDict *item ); + bool GiveInventoryItem( const char *name ); + void RemoveInventoryItem( const char *name ); + idDict * FindInventoryItem( const char *name ); + + // Wrist computer + void GiveObjective ( const char *title, const char *text, const char *screenshot ); + void CompleteObjective ( const char *title ); + void FailObjective ( const char *title ); + void GiveDatabaseEntry ( const idDict* dbEntry, bool hudPopup = true ); + bool IsObjectiveUp ( void ) const { return objectiveUp; } + idUserInterface * GetObjectiveHud ( void ) { return objectiveSystem; } + + // Secret Areas + void DiscoverSecretArea ( const char *description); + + void StartBossBattle ( idEntity* ent ); + + // Powerups + bool GivePowerUp ( int powerup, int time, bool team = false ); + void ClearPowerUps ( void ); + + void StartPowerUpEffect ( int powerup ); + void StopPowerUpEffect ( int powerup ); + + bool PowerUpActive ( int powerup ) const; + float PowerUpModifier ( int type ); + void ClearPowerup ( int i ); + const char* GetArenaPowerupString ( void ); + + // Helper methods to retrieving dictionaries + const idDeclEntityDef* GetWeaponDef ( int weaponIndex ); + const idDeclEntityDef* GetPowerupDef ( int powerupIndex ); + + // Weapons + bool GiveWeaponMods ( int mods ); + bool GiveWeaponMods ( int weapon, int mods ); + void GiveWeaponMod ( const char* weaponmod ); + + int SlotForWeapon ( const char *weaponName ); + + idEntity* DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity = vec3_origin ) const; + void DropPowerups ( void ); + idEntity* ResetFlag ( const char* itemClass, const idDict& customArgs ) const; + void RespawnFlags ( void ); + void DropWeapon ( void ); + +// RAVEN BEGIN +// abahr: + bool WeaponIsEnabled ( void ) const { return weaponEnabled; } + void ShowCrosshair ( void ); + void HideCrosshair ( void ); +// RAVEN END + +//RAVEN BEGIN +//asalmon: switch weapon based on d-pad combo +#ifdef _XBOX + void ScheduleWeaponSwitch (int weapon); +#endif +//RAVEN BEGIN + + void Reload ( void ); + void NextWeapon ( void ); + void NextBestWeapon ( void ); + void PrevWeapon ( void ); + void LastWeapon ( void ); + void SelectWeapon ( int num, bool force ); + void SelectWeapon ( const char * ); + void AddProjectilesFired ( int count ); + void AddProjectileHits ( int count ); + void SetLastHitTime ( int time, bool armorHit ); + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void WeaponLoweringCallback ( void ); + void WeaponRisingCallback ( void ); + void RemoveWeapon ( const char *weap ); + void Flashlight ( bool on ); + void ToggleFlashlight ( void ); + bool CanShowWeaponViewmodel ( void ) const; + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + bool GuiActive( void ) { return focusType == FOCUS_GUI; } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + void GenerateImpulseForBuyAttempt( const char* itemName ); + bool AttemptToBuyItem( const char* itemName ); + bool AttemptToBuyTeamPowerup( const char* itemName ); + void UpdateTeamPowerups( bool isBuying = false ); + bool CanBuy( void ); + int CanSelectWeapon ( const char* weaponName ); + int GetItemCost(const char* itemName); +// RITUAL END + void PerformImpulse( int impulse ); + void Spectate( bool spectate, bool force = false ); + void ToggleObjectives ( void ); + void ToggleScoreboard( void ); + void RouteGuiMouse( idUserInterface *gui ); + void UpdateHud( void ); + idUserInterface* GetHud(); + const idUserInterface* GetHud() const; + void SetInfluenceFov( float fov ); + void SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ); + void SetInfluenceLevel( int level ); + int GetInfluenceLevel( void ) { return influenceActive; }; + void SetPrivateCameraView( idCamera *camView ); + idCamera * GetPrivateCameraView( void ) const { return privateCameraView; } + void StartFxFov( float duration ); + void UpdateHudWeapon( int displayWeapon=-1 ); +#ifdef _XENON + void ResetHUDWeaponSwitch( void ); +#endif + void UpdateHudStats( idUserInterface *hud ); + void UpdateHudAmmo( idUserInterface *hud ); + void ShowTip( const char *title, const char *tip, bool autoHide ); + void HideTip( void ); + bool IsTipVisible( void ) { return tipUp; }; + void ShowObjective( const char *obj ); + void HideObjective( void ); + idVec3 GetEyePosition( void ) const; + + void LocalClientPredictionThink( void ); + void NonLocalClientPredictionThink( void ); + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + void WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + bool IsBeingTalkedTo ( void ); + bool IsReady ( void ); + bool IsRespawning ( void ); + bool IsInTeleport ( void ); + bool IsZoomed ( void ); + bool IsFlashlightOn ( void ); + virtual bool IsCrouching ( void ) const; + + // voice com muting + bool IsPlayerMuted ( idPlayer* player ) const; + bool IsPlayerMuted ( int clientNum ) const; + void MutePlayer ( idPlayer* player, bool mute ); + void MutePlayer ( int clientNum, bool mute ); + + + // buddy list + void SetFriend ( idPlayer* player, bool isFriend ); + void SetFriend ( int clientNum, bool isFriend ); + bool IsFriend ( idPlayer* player ) const; + bool IsFriend ( int clientNum ) const; + + // time joined server + int GetConnectTime ( void ) const; + void SetConnectTime ( int time ); + + // emotes + void SetEmote ( playerEmote_t emote ); + + // rankings + int GetRank ( void ) const; + void SetRank ( int newRank ); + + // arenas for tourney mode + int GetArena ( void ) const; + void SetArena ( int newArena ); + + idEntity *GetInfluenceEntity( void ) { return influenceEntity; }; + const idMaterial *GetInfluenceMaterial( void ) { return influenceMaterial; }; + float GetInfluenceRadius( void ) { return influenceRadius; }; + + // server side work for in/out of spectate. takes care of spawning it into the world as well + void ServerSpectate( bool spectate ); + + // for very specific usage. != GetPhysics() + idPhysics *GetPlayerPhysics( void ); + void TeleportDeath( int killer ); + void SetLeader( bool lead ); + bool IsLeader( void ); + + void UpdateModelSetup( bool forceReload = false ); + + bool OnLadder( void ) const; + + rvViewWeapon* GetWeaponViewModel ( void ) const; + idAnimatedEntity* GetWeaponWorldModel ( void ) const; + int GetCurrentWeapon ( void ) const; + + bool IsGibbed ( void ) const; + const idVec3& GetGibDir ( void ) const; + + void SetInitialHud ( void ); + + void RemoveClientModel ( const char* entityDefName ); + void RemoveClientModels ( void ); + + rvClientEntityPtr AddClientModel ( const char* entityDefName, const char* shaderName = NULL ); + + void ClientGib ( const idVec3& dir ); + void ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ); + + + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + + void GUIMainNotice( const char* message, bool persist = false ); + void GUIFragNotice( const char* message, bool persist = false ); + + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); + + virtual void SetClipWorld( int newCW ); + virtual int GetClipWorld( void ) const; + + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + int GetWeaponIndex( const char* weaponName ) const; + + virtual void SetInstance( int newInstance ); + void JoinInstance( int newInstance ); + + void ClientInstanceJoin( void ); + void ClientInstanceLeave( void ); + + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void SetShowHud( bool showHud ); + bool GetShowHud( void ); + + + // mekberg: wrap saveMessages + void SaveMessage( void ); + + // mekberg: set pm_ cvars + void SetPMCVars( void ); + + void SetTourneyStatus( playerTourneyStatus_t newStatus ) { tourneyStatus = newStatus; } + playerTourneyStatus_t GetTourneyStatus( void ) { return tourneyStatus; } + const char* GetTextTourneyStatus( void ); + + const idVec4& GetHitscanTint( void ); + + void ForceScoreboard( bool force, int time ); + + // call only on the local player + idUserInterface* GetCursorGUI( void ); + + bool CanFire( void ) const; + + bool AllowedVoiceDest( int from ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer + itemBuyStatus_t ItemBuyStatus( const char* itemName ); + bool CanBuyItem( const char* itemName ); + void GiveCash( float cashDeltaAmount ); + void ClampCash( float minCash, float maxCash ); + void SetCash( float newCashAmount ); + void ResetCash(); +// RITUAL END + +protected: + void SetupHead( const char* modelKeyName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + float vehicleCameraDist; + + jointHandle_t hipJoint; + jointHandle_t chestJoint; + + idPhysics_Player physicsObj; // player physics + + idList aasLocation; // for AI tracking the player + + idStr modelName; // current model name + const rvDeclPlayerModel* modelDecl; + + int bobFoot; + float bobFrac; + float bobfracsin; + int bobCycle; // for view bobbing and footstep generation + float xyspeed; + int stepUpTime; + float stepUpDelta; + float idealLegsYaw; + float legsYaw; + bool legsForward; + float oldViewYaw; + idAngles viewBobAngles; + idVec3 viewBob; + int landChange; + int landTime; + + // ddynerman: we read fall deltas from spawnargs, cache them to save some lookups + float fatalFallDelta; + float hardFallDelta; + float softFallDelta; + float noFallDelta; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + int carryOverCurrentWeapon; +// RITUAL END + int currentWeapon; + int idealWeapon; + int previousWeapon; + int weaponSwitchTime; + bool weaponEnabled; + bool showWeaponViewModel; + + rvClientEntityPtr clientHead; +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + bool objectivesEnabled; +// jshepard: false when player is looking at an npc or gui + bool flagCanFire; +// RAVEN END + + bool flashlightOn; + bool zoomed; + + bool reloadModel; + + const idDeclSkin * skin; + const idDeclSkin * weaponViewSkin; + const idDeclSkin * headSkin; + + const idDeclSkin * powerUpSkin; // active powerup skin + const idDeclSkin * gibSkin; + + const idMaterial* quadOverlay; + const idMaterial* hasteOverlay; + const idMaterial* regenerationOverlay; + const idMaterial* invisibilityOverlay; + const idMaterial* powerUpOverlay; + + int numProjectilesFired; // number of projectiles fired + int numProjectileHits; // number of hits on mobs + + bool airless; + int airTics; // set to pm_airTics at start, drops in vacuum + int lastAirDamage; + + bool gibDeath; + bool gibsLaunched; + idVec3 gibDir; + + playerTourneyStatus_t tourneyStatus; + bool isStrogg; + + idInterpolate zoomFov; + idInterpolate centerView; + bool fxFov; + + float influenceFov; + int influenceActive; // level of influence.. 1 == no gun or hud .. 2 == 1 + no movement + idEntity * influenceEntity; + const idMaterial * influenceMaterial; + float influenceRadius; + const idDeclSkin * influenceSkin; + + idCamera * privateCameraView; + + static const int NUM_LOGGED_VIEW_ANGLES = 64; // for weapon turning angle offsets + idAngles loggedViewAngles[NUM_LOGGED_VIEW_ANGLES]; // [gameLocal.framenum&(LOGGED_VIEW_ANGLES-1)] + static const int NUM_LOGGED_ACCELS = 16; // for weapon turning angle offsets + loggedAccel_t loggedAccel[NUM_LOGGED_ACCELS]; // [currentLoggedAccel & (NUM_LOGGED_ACCELS-1)] + int currentLoggedAccel; + + int demoViewAngleTime; + idAngles demoViewAngles; + + // if there is a focusGUIent, the attack button will be changed into mouse clicks + idUserInterface * focusUI; + int focusTime; + playerFocus_t focusType; + idEntityPtr focusEnt; + idUserInterface * focusBrackets; + int focusBracketsTime; + + bool targetFriendly; + + idEntityPtr talkingNPC; // NPC who's currently talking to us + int talkCursor; // show the state of the focusCharacter (0 == can't talk/dead, 1 == ready to talk, 2 == busy talking) + idUserInterface * cursor; + + idUserInterface * overlayHud; // a temporary hud overlay + int overlayHudTime; + + // full screen guis track mouse movements directly + int oldMouseX; + int oldMouseY; + + bool tipUp; + bool objectiveUp; + + float dynamicProtectionScale; // value to scale damage by due to dynamic protection + int lastDamageDef; + idVec3 lastDamageDir; + int lastDamageLocation; + + int predictedFrame; + idVec3 predictedOrigin; + idAngles predictedAngles; + bool predictedUpdated; + idVec3 predictionOriginError; + idAngles predictionAnglesError; + int predictionErrorTime; + + // mp + bool ready; // from userInfo + bool respawning; // set to true while in SpawnToPoint for telefrag checks + bool leader; // for sudden death situations + bool weaponCatchup; // raise up the weapon silently ( state catchups ) + bool isTelefragged; // proper obituaries + + int lastSpectateChange; + int lastTeleFX; + unsigned int lastSnapshotSequence; // track state hitches on clients + + int aimClientNum; // player num in aim + + idPlayer* lastImpulsePlayer; // the last player who gave me an impulse, may be null + + int arena; // current arena for tourney gameplay + + int connectTime; + int mutedPlayers; // bitfield set to which clients this player wants muted + int friendPlayers; // bitfield set to which clients this player has marked as friends + + int voiceDest[MAX_CONCURRENT_VOICES]; + int voiceDestTimes[MAX_CONCURRENT_VOICES]; + + int rank; + + int deathSkinTime; + bool deathStateHitch; + + playerEmote_t emote; + + int powerupEffectTime; + const idDecl *powerupEffect; + int powerupEffectType; + idList powerupEffectJoints; + rvClientEffectPtr hasteEffect; + rvClientEffectPtr flagEffect; + rvClientEffectPtr arenaEffect; + + rvClientEffectPtr teamHealthRegen; + bool teamHealthRegenPending; + rvClientEffectPtr teamAmmoRegen; + bool teamAmmoRegenPending; + rvClientEffectPtr teamDoubler; + bool teamDoublerPending; + + idVec4 hitscanTint; + // end mp + + int lastImpulseTime; // time of last impulse + idEntityPtr bossEnemy; + + const idDeclEntityDef* cachedWeaponDefs[ MAX_WEAPONS ]; + const idDeclEntityDef* cachedPowerupDefs[ POWERUP_MAX ]; + + bool weaponChangeIconsUp; + + int oldInventoryWeapons; + + const idDeclEntityDef* itemCosts; + + bool WantSmoothing( void ) const; + void PredictionErrorDecay( void ); + + bool CanZoom(void); + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); + + void StopFiring( void ); + void FireWeapon( void ); + void Weapon_Combat( void ); + void Weapon_NPC( void ); + void Weapon_GUI( void ); + void Weapon_Vehicle ( void ); + void Weapon_Usable ( void ); + void SpectateFreeFly( bool force ); // ignore the timeout to force when followed spec is no longer valid + void SpectateCycle( void ); + idAngles GunTurningOffset( void ); + idVec3 GunAcceleratingOffset( void ); + + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + void BobCycle( const idVec3 &pushVelocity ); + void EvaluateControls( void ); + void AdjustSpeed( void ); + void AdjustBodyAngles( void ); + void Move( void ); + void SetSpectateOrigin( void ); + + void InitAASLocation( void ); + void SetAASLocation( void ); + + idUserInterface * ActiveGui( void ); + + void UpdateWeapon ( void ); + void UpdateSpectating ( void ); + void UpdateAir ( void ); +// RAVEN BEGIN +// abahr + void UpdateGravity ( void ); +// nrausch: common handling for objective screen toggle + void HandleObjectiveInput ( void ); + void HandleCheats ( void ); + void ClearCheatState ( void ); +// RAVEN END + void UpdateViewAngles ( void ); + void UpdatePowerUps ( void ); + void UpdateDeathSkin ( bool state_hitch ); + void UpdateFocus ( void ); + void UpdateLocation ( void ); + void UpdateObjectiveInfo ( void ); +// void UpdateDatabaseInfo ( void ); + void UpdateIntentDir ( void ); + + void LoadDeferredModel ( void ); + + void ClearFocus ( void ); + void UpdateFocusCharacter ( idEntity* newEnt ); + void SetFocus ( playerFocus_t type, int focusTime, idEntity* ent, idUserInterface* ui ); + + void Event_GetButtons ( void ); + void Event_GetMove ( void ); + void Event_GetViewAngles ( void ); + void Event_SetViewAngles ( const idVec3 &vec ); + void Event_StopFxFov ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_GetCurrentWeapon ( void ); + void Event_GetPreviousWeapon ( void ); + void Event_SelectWeapon ( const char *weaponName ); + void Event_GetWeaponEntity ( void ); + void Event_ExitTeleporter ( void ); + void Event_HideTip ( void ); + void Event_LevelTrigger ( void ); + void Event_GetViewPos ( void ); + void Event_TeleportPlayer ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Freeze ( float f ); + void Event_HideDatabaseEntry ( void ); + void Event_ZoomIn ( void ); + void Event_ZoomOut ( void ); + void Event_FinishHearingLoss ( float fadeTime ); + void Event_GetAmmoData ( const char *ammoClass ); + void Event_RefillAmmo ( void ); + void Event_AllowFallDamage ( int toggle ); + + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + + // RAVEN BEGIN + // twhitaker: added Event_ApplyImpulse + void Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ); + + // mekberg: added sethealth + void Event_SetHealth ( float newHealth ); + void Event_SetArmor ( float newArmor ); + + void Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ); + void Event_DamageEffect ( const char *damageDefName, idEntity* _damageFromEnt ); + + // mekberg: allow enabling/disabling of objectives + void Event_EnableObjectives ( void ); + void Event_DisableObjectives ( void ); + + // mekberg: don't supress showing new objectives anymore + void Event_AllowNewObjectives ( void ); + + // twhitaker: death shader + void UpdateDeathShader ( bool state_hitch ); + + bool doInitWeapon; + void InitWeapon ( void ); + // RAVEN END + + bool IsLegsIdle ( bool crouching ) const; + + stateResult_t State_Wait_Alive ( const stateParms_t& parms ); + stateResult_t State_Wait_ReloadAnim ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_IdleThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + stateResult_t State_Torso_RaiseWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_LowerWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire_Windup ( const stateParms_t& parms ); + + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_Dead ( const stateParms_t& parms ); + stateResult_t State_Torso_Emote ( const stateParms_t& parms ); + + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Uncrouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Jump ( const stateParms_t& parms ); + stateResult_t State_Legs_Fall ( const stateParms_t& parms ); + stateResult_t State_Legs_Land ( const stateParms_t& parms ); + stateResult_t State_Legs_Dead ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( idPlayer ); +}; + +ID_INLINE bool idPlayer::IsBeingTalkedTo( void ) { + return talkingNPC!=NULL; +} + +ID_INLINE bool idPlayer::IsRespawning( void ) { + return respawning; +} + +ID_INLINE idPhysics* idPlayer::GetPlayerPhysics( void ) { + return &physicsObj; +} + +ID_INLINE bool idPlayer::IsInTeleport( void ) { + return ( teleportEntity.GetEntity() != NULL ); +} + +ID_INLINE void idPlayer::SetLeader( bool lead ) { + leader = lead; +} + +ID_INLINE bool idPlayer::IsLeader( void ) { + return leader; +} + +ID_INLINE bool idPlayer::IsZoomed( void ) { + return zoomed; +} + +ID_INLINE bool idPlayer::IsFlashlightOn( void ) { + return flashlightOn; +} + +ID_INLINE rvViewWeapon* idPlayer::GetWeaponViewModel( void ) const { + return weaponViewModel; +} + +ID_INLINE idAnimatedEntity* idPlayer::GetWeaponWorldModel( void ) const { + return weaponWorldModel; +} + +ID_INLINE int idPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +ID_INLINE bool idPlayer::IsGibbed( void ) const { + return gibDeath; +} + +ID_INLINE const idVec3& idPlayer::GetGibDir( void ) const { + return gibDir; +} + +ID_INLINE int idPlayer::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idPlayer::SetClipWorld( int newCW ) { + idEntity::SetClipWorld( newCW ); + + if( head.GetEntity() ) { + head.GetEntity()->SetClipWorld( newCW ); + + head.GetEntity()->UnlinkCombat(); + head.GetEntity()->LinkCombat(); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetClipWorld( newCW ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetClipWorld( newCW ); + } + } + + UnlinkCombat(); + LinkCombat(); +} + +ID_INLINE int idPlayer::GetWeaponIndex( const char* weaponName ) const { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( !idStr::Icmp( spawnArgs.GetString( va( "def_weapon%d", i ), "" ), weaponName ) ) { + return i; + } + } + + return 0; +} + +ID_INLINE idUserInterface* idPlayer::GetHud() { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE const idUserInterface* idPlayer::GetHud() const { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE bool idPlayer::IsPlayerMuted( idPlayer* player ) const { + return !!( mutedPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsPlayerMuted( int clientNum ) const { + return !!( mutedPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::MutePlayer( idPlayer* player, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << player->entityNumber ); + } else { + mutedPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::MutePlayer( int clientNum, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << clientNum ); + } else { + mutedPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE bool idPlayer::IsFriend( idPlayer* player ) const { + return !!( friendPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsFriend( int clientNum ) const { + return !!( friendPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::SetFriend( idPlayer* player, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << player->entityNumber ); + } else { + friendPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::SetFriend( int clientNum, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << clientNum ); + } else { + friendPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE int idPlayer::GetConnectTime( void ) const { + return connectTime; +} + +ID_INLINE void idPlayer::SetConnectTime( int time ) { + connectTime = time; +} + +ID_INLINE int idPlayer::GetRank( void ) const { + return rank; +} + +ID_INLINE void idPlayer::SetRank( int newRank ) { + rank = newRank; +} + +ID_INLINE int idPlayer::GetArena( void ) const { + return arena; +} + +ID_INLINE bool idPlayer::CanFire( void ) const { + return flagCanFire; +} + +#endif /* !__GAME_PLAYER_H__ */ + +// RAVEN END diff --git a/source/game/PlayerView.cpp b/source/game/PlayerView.cpp new file mode 100644 index 0000000..7b048c0 --- /dev/null +++ b/source/game/PlayerView.cpp @@ -0,0 +1,784 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if defined(_XENON) && (defined(_PROFILE) || defined(_DEBUG)) +#include "../sys/xenon/ProfilingSupport.h" +#endif + +const int IMPULSE_DELAY = 150; +/* +============== +idPlayerView::idPlayerView +============== +*/ +idPlayerView::idPlayerView() { + memset( screenBlobs, 0, sizeof( screenBlobs ) ); + memset( &view, 0, sizeof( view ) ); + player = NULL; + dvMaterial = NULL; + dvMaterialBlend = NULL; + tunnelMaterial = NULL; + armorMaterial = NULL; + bloodSprayMaterial = NULL; + bfgVision = false; + dvFinishTime = 0; + tvFinishTime = 0; + tvStartTime = 0; + kickFinishTime = 0; + kickAngles.Zero(); + lastDamageTime = 0.0f; + fadeTime = 0; + fadeRate = 0.0; + fadeFromColor.Zero(); + fadeToColor.Zero(); + fadeColor.Zero(); + + ClearEffects(); + + dvScale = 1.0f; + shakeScale = 1.0f; + tvScale = 1.0f; +} + +/* +============== +idPlayerView::Save +============== +*/ +void idPlayerView::Save( idSaveGame *savefile ) const { + int i; + const screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->WriteMaterial( blob->material ); + savefile->WriteFloat( blob->x ); + savefile->WriteFloat( blob->y ); + savefile->WriteFloat( blob->w ); + savefile->WriteFloat( blob->h ); + savefile->WriteFloat( blob->s1 ); + savefile->WriteFloat( blob->t1 ); + savefile->WriteFloat( blob->s2 ); + savefile->WriteFloat( blob->t2 ); + savefile->WriteInt( blob->finishTime ); + savefile->WriteInt( blob->startFadeTime ); + savefile->WriteFloat( blob->driftAmount ); + } + + savefile->WriteInt( dvFinishTime ); + savefile->WriteMaterial( dvMaterial ); + + savefile->WriteMaterial( dvMaterialBlend ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( dvScale ); + + savefile->WriteInt( kickFinishTime ); + savefile->WriteAngles( kickAngles ); + + savefile->WriteBool( bfgVision ); + + savefile->WriteMaterial( tunnelMaterial ); + savefile->WriteMaterial( armorMaterial ); + + savefile->WriteMaterial( bloodSprayMaterial ); + + savefile->WriteFloat( lastDamageTime ); + + savefile->WriteFloat( shakeFinishTime ); + savefile->WriteFloat( shakeScale ); + savefile->WriteFloat( tvScale ); + savefile->WriteInt( tvFinishTime ); + savefile->WriteInt( tvStartTime ); + + savefile->WriteVec4( fadeColor ); + savefile->WriteVec4( fadeToColor ); + savefile->WriteVec4( fadeFromColor ); + savefile->WriteFloat( fadeRate ); + savefile->WriteInt( fadeTime ); + + savefile->WriteObject( player ); + savefile->WriteRenderView( view ); +} + +/* +============== +idPlayerView::Restore +============== +*/ +void idPlayerView::Restore( idRestoreGame *savefile ) { + int i; + screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->ReadMaterial( blob->material ); + savefile->ReadFloat( blob->x ); + savefile->ReadFloat( blob->y ); + savefile->ReadFloat( blob->w ); + savefile->ReadFloat( blob->h ); + savefile->ReadFloat( blob->s1 ); + savefile->ReadFloat( blob->t1 ); + savefile->ReadFloat( blob->s2 ); + savefile->ReadFloat( blob->t2 ); + savefile->ReadInt( blob->finishTime ); + savefile->ReadInt( blob->startFadeTime ); + savefile->ReadFloat( blob->driftAmount ); + } + + savefile->ReadInt( dvFinishTime ); + savefile->ReadMaterial( dvMaterial ); + + savefile->ReadMaterial( dvMaterialBlend ); // cnicholson: Added unrestored var + + savefile->ReadFloat( dvScale ); + + savefile->ReadInt( kickFinishTime ); + savefile->ReadAngles( kickAngles ); + + savefile->ReadBool( bfgVision ); + + savefile->ReadMaterial( tunnelMaterial ); + savefile->ReadMaterial( armorMaterial ); + + savefile->ReadMaterial( bloodSprayMaterial ); + + savefile->ReadFloat( lastDamageTime ); + + savefile->ReadFloat( shakeFinishTime ); + savefile->ReadFloat( shakeScale ); + savefile->ReadFloat( tvScale ); + savefile->ReadInt( tvFinishTime ); + savefile->ReadInt ( tvStartTime ); + + savefile->ReadVec4( fadeColor ); + savefile->ReadVec4( fadeToColor ); + savefile->ReadVec4( fadeFromColor ); + savefile->ReadFloat( fadeRate ); + savefile->ReadInt( fadeTime ); + + savefile->ReadObject( reinterpret_cast( player ) ); + savefile->ReadRenderView( view ); +} + +/* +============== +idPlayerView::SetPlayerEntity +============== +*/ +void idPlayerView::SetPlayerEntity( idPlayer *playerEnt ) { + player = playerEnt; + + const idDict* dict = NULL; + if( !playerEnt ) { + return; + } + + dict = gameLocal.FindEntityDefDict( playerEnt->spawnArgs.GetString("def_playerView"), false ); + + if( dict ) { + dvMaterial = declManager->FindMaterial( dict->GetString("mtr_doubleVision") ); + dvMaterialBlend = declManager->FindMaterial( dict->GetString("mtr_doubleVisionBlend") ); + tunnelMaterial = declManager->FindMaterial( dict->GetString("mtr_tunnel") ); + armorMaterial = declManager->FindMaterial( dict->GetString("mtr_armourEffect") ); + bloodSprayMaterial = declManager->FindMaterial( dict->GetString("mtr_bloodspray") ); + lagoMaterial = declManager->FindMaterial( LAGO_MATERIAL, false ); + } +} + +/* +============== +idPlayerView::ClearEffects +============== +*/ +void idPlayerView::ClearEffects() { + lastDamageTime = MS2SEC( gameLocal.time - 99999 ); + + dvFinishTime = ( gameLocal.time - 99999 ); + tvFinishTime = ( gameLocal.time - 99999 ); + tvStartTime = ( gameLocal.time - 99999 ); + kickFinishTime = ( gameLocal.time - 99999 ); + shakeFinishTime = gameLocal.time; + + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlobs[i].finishTime = gameLocal.time; + } + + fadeTime = 0; + bfgVision = false; +} + +/* +============== +idPlayerView::GetScreenBlob +============== +*/ +screenBlob_t *idPlayerView::GetScreenBlob() { + screenBlob_t *oldest = &screenBlobs[0]; + + for ( int i = 1 ; i < MAX_SCREEN_BLOBS ; i++ ) { + if ( screenBlobs[i].finishTime < oldest->finishTime ) { + oldest = &screenBlobs[i]; + } + } + return oldest; +} + +/* +============== +idPlayerView::DamageImpulse + +LocalKickDir is the direction of force in the player's coordinate system, +which will determine the head kick direction +============== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ) { + // + // double vision effect + // + float tvTime = damageDef->GetFloat( "tv_time" ); + if ( tvTime ) { + tvStartTime = gameLocal.time; + tvFinishTime = gameLocal.time + tvTime; + damageDef->GetFloat ( "tv_scale", "1", tvScale ); + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + // + // double vision effect + // + float dvTime = damageDef->GetFloat( "dv_time" ); + if ( dvTime ) { + dvFinishTime = gameLocal.time + (g_dvTime.GetFloat() * dvTime); + damageDef->GetFloat ( "dv_scale", "1", dvScale ); + } + + // + // head angle kick + // + const float modifierScale = 0.25f; + const float inverseModifier = ( 1.0f - modifierScale ); + + float modifier = idMath::ClampFloat( 0.0f, inverseModifier, damage / 100.0f * inverseModifier ) + modifierScale; + float kickTime = damageDef->GetFloat( "kick_time" ); + + if ( kickTime ) { + kickFinishTime = gameLocal.time + g_kickTime.GetFloat() * kickTime; + + // forward / back kick will pitch view + kickAngles[0] = localKickDir[0]; + + // side kick will yaw view + kickAngles[1] = localKickDir[1]*0.5f; + + // up / down kick will pitch view + kickAngles[0] += localKickDir[2]; + + // roll will come from side + kickAngles[2] = localKickDir[1]; + + float kickAmplitude = damageDef->GetFloat( "kick_amplitude" ); + if ( kickAmplitude ) { + kickAngles *= kickAmplitude; + } + + if ( modifier < kickAmplitude ) { + modifier = kickAmplitude; + } + } + else { + kickTime = 500; + } + + // + // screen blob + // + float blobTime = damageDef->GetFloat( "blob_time" ); + if ( blobTime ) { + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + blobTime * g_blobTime.GetFloat(); + + const char *materialName = damageDef->GetString( "mtr_blob" ); + blob->material = declManager->FindMaterial( materialName ); + blob->x = damageDef->GetFloat( "blob_x" ); + blob->x += ( gameLocal.random.RandomInt()&63 ) - 32; + blob->y = damageDef->GetFloat( "blob_y" ); + blob->y += ( gameLocal.random.RandomInt()&63 ) - 32; + + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = damageDef->GetFloat( "blob_width" ) * g_blobSize.GetFloat() * scale; + blob->h = damageDef->GetFloat( "blob_height" ) * g_blobSize.GetFloat() * scale; + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + } + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); +} +// RAVEN END + +/* +================== +idPlayerView::AddBloodSpray + +If we need a more generic way to add blobs then we can do that +but having it localized here lets the material be pre-looked up etc. +================== +*/ +void idPlayerView::AddBloodSpray( float duration ) { +/* + if ( duration <= 0 || bloodSprayMaterial == NULL || g_skipViewEffects.GetBool() ) { + return; + } + // visit this for chainsaw + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + ( duration * 1000 ); + blob->material = bloodSprayMaterial; + blob->x = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->y = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->driftAmount = 0.5f + gameLocal.random.CRandomFloat() * 0.5; + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = 600 * g_blobSize.GetFloat() * scale; + blob->h = 480 * g_blobSize.GetFloat() * scale; + float s1 = 0.0f; + float t1 = 0.0f; + float s2 = 1.0f; + float t2 = 1.0f; + if ( blob->driftAmount < 0.6 ) { + s1 = 1.0f; + s2 = 0.0f; + } else if ( blob->driftAmount < 0.75 ) { + t1 = 1.0f; + t2 = 0.0f; + } else if ( blob->driftAmount < 0.85 ) { + s1 = 1.0f; + s2 = 0.0f; + t1 = 1.0f; + t2 = 0.0f; + } + blob->s1 = s1; + blob->t1 = t1; + blob->s2 = s2; + blob->t2 = t2; +*/ +} + +/* +================== +idPlayerView::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + int recoilTime; + + recoilTime = weaponDef->GetInt( "recoilTime" ); + // don't shorten a damage kick in progress + if ( recoilTime && recoilTime > (kickFinishTime - gameLocal.time) ) { + idAngles angles; + weaponDef->GetAngles( "recoilAngles", "5 0 0", angles ); + kickAngles = angles; + int finish = gameLocal.time + g_kickTime.GetFloat() * recoilTime; + kickFinishTime = finish; + } + +} + +/* +=================== +idPlayerView::CalculateShake +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +float idPlayerView::CalculateShake( idAngles &shakeAngleOffset ) const { + idVec3 origin, matrix; + + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, player->firstPersonViewOrigin ); + // + // shakeVolume should somehow be molded into an angle here + // it should be thought of as being in the range 0.0 -> 1.0, although + // since CurrentShakeAmplitudeForPosition() returns all the shake sounds + // the player can hear, it can go over 1.0 too. + // + shakeAngleOffset[0] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[1] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[2] = gameLocal.random.CRandomFloat() * shakeVolume; + + return shakeVolume; +} +// RAVEN END + +/* +=================== +idPlayerView::AngleOffset + + kickVector, a world space direction that the attack should +=================== +*/ +idAngles idPlayerView::AngleOffset() const { + idAngles ang; + + ang.Zero(); + + if ( gameLocal.time < kickFinishTime ) { + float offset = kickFinishTime - gameLocal.time; + + ang = kickAngles * offset * offset * g_kickAmplitude.GetFloat(); + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( ang[i] > 70.0f ) { + ang[i] = 70.0f; + } else if ( ang[i] < -70.0f ) { + ang[i] = -70.0f; + } + } + } + return ang; +} + +/* +=================== +idPlayerView::ShakeOffsets +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const { + float shakeVolume = 0.0f; + shakeOffset.Zero(); + shakeAngleOffset.Zero(); + + if( gameLocal.isMultiplayer ) { + return; + } + + shakeVolume = CalculateShake( shakeAngleOffset ); + + if( gameLocal.time < shakeFinishTime ) { + float offset = ( shakeFinishTime - gameLocal.time ) * shakeScale * 0.001f; + + shakeOffset[0] = idMath::ClampFloat( bounds[0][0] - 1.0f, bounds[1][0] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[1] = idMath::ClampFloat( bounds[0][1] - 1.0f, bounds[1][1] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[2] = idMath::ClampFloat( bounds[0][2] - 1.0f, bounds[1][2] + 1.0f, rvRandom::flrand( -offset, offset ) ); + + shakeAngleOffset[0] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[1] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[2] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + } +} +// RAVEN END + +/* +================== +idPlayerView::SingleView +================== +*/ +void idPlayerView::SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags ) { + // normal rendering + if ( !view ) { + return; + } + + if ( !( RF_GUI_ONLY & renderFlags ) ) { + // jscott: portal sky rendering with KRABS + idCamera *portalSky = gameLocal.GetPortalSky(); + if( portalSky ) { + renderView_t portalSkyView = *view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, ( renderFlags & ( ~RF_PRIMARY_VIEW ) ) | RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( view, renderFlags | RF_PENUMBRA_MAP ); + } + + if ( RF_NO_GUI & renderFlags ) { + return; + } + + // draw screen blobs + if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlob_t *blob = &screenBlobs[i]; + if ( blob->finishTime <= gameLocal.time ) { + continue; + } + + blob->y += blob->driftAmount; + + float fade = (float)( blob->finishTime - gameLocal.time ) / ( blob->finishTime - blob->startFadeTime ); + if ( fade > 1.0f ) { + fade = 1.0f; + } + if ( fade ) { + renderSystem->SetColor4( 1,1,1,fade ); + renderSystem->DrawStretchPic( blob->x, blob->y, blob->w, blob->h,blob->s1, blob->t1, blob->s2, blob->t2, blob->material ); + } + } + + // Render tunnel vision + if ( gameLocal.time < tvFinishTime ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, tvScale * ((float)(tvFinishTime - gameLocal.time) / (float)(tvFinishTime - tvStartTime)) ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } + + player->DrawHUD( hud ); + + +/* + // tunnel vision + float health = 0.0f; + if ( g_testHealthVision.GetFloat() != 0.0f ) { + health = g_testHealthVision.GetFloat(); + } else { + health = player->health; + } + float alpha = health / 100.0f; + if ( alpha < 0.0f ) { + alpha = 0.0f; + } + if ( alpha > 1.0f ) { + alpha = 1.0f; + } + + if ( alpha < 1.0f ) { + renderSystem->SetColor4( ( player->health <= 0.0f ) ? MS2SEC( gameLocal.time ) : lastDamageTime, 1.0f, 1.0f, ( player->health <= 0.0f ) ? 0.0f : alpha ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } +*/ + + + + // Render the object system + // RAVEN BEGIN + // twhitaker: always draw objective system + if ( player->objectiveSystem ) { + player->objectiveSystem->Redraw( gameLocal.time ); + } + // RAVEN END + } + + // test a single material drawn over everything + if ( g_testPostProcess.GetString()[0] ) { + const idMaterial *mtr = declManager->FindMaterial( g_testPostProcess.GetString(), false ); + if ( !mtr ) { + common->Printf( "Material not found.\n" ); + g_testPostProcess.SetString( "" ); + } else { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, mtr ); + } + } +} + +/* +=================== +idPlayerView::DoubleVision +=================== +*/ +void idPlayerView::DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ) { + + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } + + float scale = offset * g_dvAmplitude.GetFloat() * dvScale; + if( scale < 0.0f ) { + return; + } + + if ( scale > 0.5f ) { + scale = 0.5f; + } + float shift = scale * idMath::Sin( idMath::Sqrt ( offset ) * g_dvFrequency.GetFloat() ); + shift = fabs( shift ); + + // if double vision, render to a texture + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view, RF_NO_GUI ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + + // carry red tint if in berserk mode + idVec4 color(1, 1, 1, 1); + + renderSystem->SetColor4( color.x, color.y, color.z, 1.0f ); +// RAVEN BEGIN +// jnewquist: Call DrawStretchCopy, which will flip the texcoords for D3D + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, shift, 1, 1, 0, dvMaterial ); + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, 1-shift, 0, dvMaterialBlend ); +// RAVEN END +} + +/* +================= +idPlayerView::Flash + +flashes the player view with the given color +================= +*/ +void idPlayerView::Flash(idVec4 color, int time ) { + Fade(idVec4(0, 0, 0, 0), time); + fadeFromColor = colorWhite; +} + +/* +================= +idPlayerView::Fade + +used for level transition fades +assumes: color.w is 0 or 1 +================= +*/ +void idPlayerView::Fade( idVec4 color, int time ) { + + if ( !fadeTime ) { + fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] ); + } else { + fadeFromColor = fadeColor; + } + fadeToColor = color; + + if ( time <= 0 ) { + fadeRate = 0; + time = 0; + fadeColor = fadeToColor; + } else { + fadeRate = 1.0f / ( float )time; + } + + if ( gameLocal.realClientTime == 0 && time == 0 ) { + fadeTime = 1; + } else { + fadeTime = gameLocal.realClientTime + time; + } +} + +/* +================= +idPlayerView::ScreenFade +================= +*/ +void idPlayerView::ScreenFade() { + int msec; + float t; + + if ( !fadeTime ) { + return; + } + + msec = fadeTime - gameLocal.realClientTime; + + if ( msec <= 0 ) { + fadeColor = fadeToColor; + if ( fadeColor[ 3 ] == 0.0f ) { + fadeTime = 0; + } + } else { + t = ( float )msec * fadeRate; + fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t ); + } + + if ( fadeColor[ 3 ] != 0.0f ) { + renderSystem->SetColor4( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } +} + +/* +=================== +idPlayerView::InfluenceVision +=================== +*/ +void idPlayerView::InfluenceVision( idUserInterface *hud, const renderView_t *view ) { + + float distance = 0.0f; + float pct = 1.0f; + if ( player->GetInfluenceEntity() ) { + distance = ( player->GetInfluenceEntity()->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).Length(); + if ( player->GetInfluenceRadius() != 0.0f && distance < player->GetInfluenceRadius() ) { + pct = distance / player->GetInfluenceRadius(); + pct = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, pct ); + } + } + if ( player->GetInfluenceMaterial() ) { + SingleView( hud, view ); + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, pct ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, player->GetInfluenceMaterial() ); + } else if ( player->GetInfluenceEntity() == NULL ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } else { + int offset = 25 + idMath::Sin ( gameLocal.time ); + DoubleVision( hud, view, pct * offset ); + } +} + +/* +=================== +idPlayerView::RenderPlayerView +=================== +*/ +void idPlayerView::RenderPlayerView( idUserInterface *hud ) { + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( !view ) { + return; + } + + bool guiRendered = false; + + // place the sound origin for the player + soundSystem->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, "Undefined" ); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if ( player->GetInfluenceMaterial() || player->GetInfluenceEntity() ) { + InfluenceVision( hud, view ); + guiRendered = true; + } else if ( g_doubleVision.GetBool() && gameLocal.time < dvFinishTime ) { + DoubleVision( hud, view, dvFinishTime - gameLocal.time ); + guiRendered = false; + } else { + SingleView( hud, view, RF_NO_GUI | RF_PRIMARY_VIEW ); + } + + // Now draw GUI's. + if ( !guiRendered ) { + SingleView( hud, view, RF_GUI_ONLY ); + } + + ScreenFade(); + } + + if ( net_clientLagOMeter.GetBool() && lagoMaterial && gameLocal.isClient && !( gameLocal.GetDemoState() == DEMO_PLAYING && ( gameLocal.IsServerDemo() || gameLocal.IsTimeDemo() ) ) ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 10.0f, 380.0f, 64.0f, 64.0f, 0.0f, 0.0f, 1.0f, 1.0f, lagoMaterial ); + } + +} + +// RAVEN END diff --git a/source/game/PlayerView.h b/source/game/PlayerView.h new file mode 100644 index 0000000..db69d9c --- /dev/null +++ b/source/game/PlayerView.h @@ -0,0 +1,143 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYERVIEW_H__ +#define __GAME_PLAYERVIEW_H__ + +/* +=============================================================================== + + Player view. + +=============================================================================== +*/ + +// screenBlob_t are for the on-screen damage claw marks, etc +typedef struct { + const idMaterial * material; + float x, y, w, h; + float s1, t1, s2, t2; + int finishTime; + int startFadeTime; + float driftAmount; +} screenBlob_t; + +#define MAX_SCREEN_BLOBS 8 + +class idPlayerView { +public: + idPlayerView(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayerEntity( class idPlayer *playerEnt ); + + void ClearEffects( void ); + +// RAVEN BEGIN +// jnewquist: Controller rumble + void DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ); +// RAVEN END + + void WeaponFireFeedback( const idDict *weaponDef ); + + idAngles AngleOffset( void ) const; // returns the current kick angle + +// RAVEN BEGIN +// jnewquist: Controller rumble + float CalculateShake( idAngles &shakeAngleOffset ) const; +// RAVEN END + +// RAVEN BEGIN +// jscott: for screen shake + void ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const; +// RAVEN END + + // adds little entities to the renderer for local blood blobs, etc + + // this may involve rendering to a texture and displaying + // that with a warp model or in double vision mode + void RenderPlayerView( idUserInterface *hud ); + + void Fade( idVec4 color, int time ); + + void Flash( idVec4 color, int time ); + + void AddBloodSpray( float duration ); + + // temp for view testing + void EnableBFGVision( bool b ) { bfgVision = b; }; + +// RAVEN BEGIN +// jscott: accessors required for the fx system + void SetDoubleVisionParms( float time, float scale ) { dvFinishTime = SEC2MS( time ); dvScale = scale; } + void SetShakeParms( float time, float scale ) { shakeFinishTime = SEC2MS( time ); shakeScale = scale; } + void SetTunnelParms( float time, float scale ) { tvStartTime = gameLocal.time; tvFinishTime = tvStartTime + time; tvScale = 1.0f / scale; } +// RAVEN END + +private: +// RAVEN BEGIN +// AReis: Modified SingleView() signature to include renderFlags variable. + void SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags = RF_NORMAL ); +// RAVEN END + void DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ); + void BerserkVision( idUserInterface *hud, const renderView_t *view ); + void InfluenceVision( idUserInterface *hud, const renderView_t *view ); + void ScreenFade(); + + screenBlob_t * GetScreenBlob(); + + screenBlob_t screenBlobs[MAX_SCREEN_BLOBS]; + + int dvFinishTime; // double vision will be stopped at this time + const idMaterial * dvMaterial; // material to take the double vision screen shot +// RAVEN BEGIN +// jscott: to make double vision work with alpha components + const idMaterial * dvMaterialBlend; +// jscott: for effects + float dvScale; +// RAVEN END + + int kickFinishTime; // view kick will be stopped at this time + idAngles kickAngles; + + bool bfgVision; // + + const idMaterial * tunnelMaterial; // health tunnel vision + const idMaterial * armorMaterial; // armor damage view effect +// RAVEN BEGIN +// bdube: not using these +// const idMaterial * berserkMaterial; // berserk effect +// const idMaterial * irGogglesMaterial; // ir effect + const idMaterial * bloodSprayMaterial; // blood spray +// const idMaterial * bfgMaterial; // when targeted with BFG +// RAVEN END + const idMaterial * lagoMaterial; // lagometer drawing + + float lastDamageTime; // accentuate the tunnel effect for a while + +// RAVEN BEGIN +// jscott: for effects + float shakeFinishTime; + float shakeScale; + float tvScale; + int tvFinishTime; + int tvStartTime; +// RAVEN END + + idVec4 fadeColor; // fade color + idVec4 fadeToColor; // color to fade to + idVec4 fadeFromColor; // color to fade from + float fadeRate; // fade rate + int fadeTime; // fade time + + idPlayer * player; + renderView_t view; +}; + +#endif /* !__GAME_PLAYERVIEW_H__ */ + +// RAVEN END diff --git a/source/game/Player_Cheats.cpp b/source/game/Player_Cheats.cpp new file mode 100644 index 0000000..a4e3ccc --- /dev/null +++ b/source/game/Player_Cheats.cpp @@ -0,0 +1,16 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +============== +idPlayer::HandleCheats +============== +*/ +void idPlayer::HandleCheats() { +} + + +void idPlayer::ClearCheatState() { +} diff --git a/source/game/Player_States.cpp b/source/game/Player_States.cpp new file mode 100644 index 0000000..9f1c829 --- /dev/null +++ b/source/game/Player_States.cpp @@ -0,0 +1,919 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +CLASS_STATES_DECLARATION ( idPlayer ) + + // Wait States + STATE ( "Wait_Alive", idPlayer::State_Wait_Alive ) + STATE ( "Wait_ReloadAnim", idPlayer::State_Wait_ReloadAnim ) + + // Torso States + STATE ( "Torso_Idle", idPlayer::State_Torso_Idle ) + STATE ( "Torso_IdleThink", idPlayer::State_Torso_IdleThink ) + STATE ( "Torso_Teleport", idPlayer::State_Torso_Teleport ) + STATE ( "Torso_RaiseWeapon", idPlayer::State_Torso_RaiseWeapon ) + STATE ( "Torso_LowerWeapon", idPlayer::State_Torso_LowerWeapon ) + STATE ( "Torso_Fire", idPlayer::State_Torso_Fire ) + STATE ( "Torso_Fire_Windup", idPlayer::State_Torso_Fire_Windup ) + STATE ( "Torso_Reload", idPlayer::State_Torso_Reload ) + STATE ( "Torso_Pain", idPlayer::State_Torso_Pain ) + STATE ( "Torso_Dead", idPlayer::State_Torso_Dead ) + STATE ( "Torso_Emote", idPlayer::State_Torso_Emote ) + + // Leg States + STATE ( "Legs_Idle", idPlayer::State_Legs_Idle ) + STATE ( "Legs_Crouch", idPlayer::State_Legs_Crouch ) + STATE ( "Legs_Uncrouch", idPlayer::State_Legs_Uncrouch ) + STATE ( "Legs_Run_Forward", idPlayer::State_Legs_Run_Forward ) + STATE ( "Legs_Run_Backward", idPlayer::State_Legs_Run_Backward ) + STATE ( "Legs_Run_Left", idPlayer::State_Legs_Run_Left ) + STATE ( "Legs_Run_Right", idPlayer::State_Legs_Run_Right ) + STATE ( "Legs_Walk_Forward", idPlayer::State_Legs_Walk_Forward ) + STATE ( "Legs_Walk_Backward", idPlayer::State_Legs_Walk_Backward ) + STATE ( "Legs_Walk_Left", idPlayer::State_Legs_Walk_Left ) + STATE ( "Legs_Walk_Right", idPlayer::State_Legs_Walk_Right ) + STATE ( "Legs_Crouch_Idle", idPlayer::State_Legs_Crouch_Idle ) + STATE ( "Legs_Crouch_Forward", idPlayer::State_Legs_Crouch_Forward ) + STATE ( "Legs_Crouch_Backward", idPlayer::State_Legs_Crouch_Backward ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Jump", idPlayer::State_Legs_Jump ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Land", idPlayer::State_Legs_Land ) + STATE ( "Legs_Dead", idPlayer::State_Legs_Dead ) + +END_CLASS_STATES + +/* +================ +idPlayer::State_Torso_Idle +================ +*/ +stateResult_t idPlayer::State_Torso_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT == State_Torso_IdleThink ( parms ) ) { + PlayCycle ( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_IdleThink", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_IdleThink +================ +*/ +stateResult_t idPlayer::State_Torso_IdleThink ( const stateParms_t& parms ) { + if ( pfl.teleport ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Teleport", 0 ); + return SRESULT_DONE; + } + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 0 ); + return SRESULT_DONE; + } + + if( pfl.attackHeld && weapon && weapon->wfl.hasWindupAnim) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_Windup", 0 ); + return SRESULT_DONE; + } + + if ( pfl.attackHeld && HasAnim ( ANIMCHANNEL_TORSO, "startfire" ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_StartFire", 2 ); + return SRESULT_DONE; + } + if ( pfl.pain ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); + return SRESULT_DONE; + } + if ( emote != PE_NONE ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Emote", 5 ); + return SRESULT_DONE; + } + + + return SRESULT_WAIT; +} + +/* +================ +idPlayer::State_Torso_Teleport +================ +*/ +stateResult_t idPlayer::State_Torso_Teleport ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + pfl.teleport = false; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Torso_RaiseWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_RaiseWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "raise", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_LowerWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_LowerWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "lower", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire +================ +*/ +stateResult_t idPlayer::State_Torso_Fire ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_FIRE_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_FIRE_WAIT); + + // Wait for the firing animation to be finished + case TORSO_FIRE_WAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", parms.blendFrames ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Keep the gun aimed but dont shoot + case TORSO_FIRE_AIM: + PlayAnim ( ANIMCHANNEL_TORSO, "aim", 3 ); + return SRESULT_STAGE(TORSO_FIRE_AIMWAIT); + + // Keep the gun aimed as long as the attack button is held and nothing is firing + case TORSO_FIRE_AIMWAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 3 ); + return SRESULT_DONE; + } else if ( !pfl.attackHeld ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire_Windup +================ +*/ +stateResult_t idPlayer::State_Torso_Fire_Windup ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_WINDUP_WAIT, + TORSO_FIRE_LOOP, + TORSO_FIRE_WAIT, + TORSO_WINDDOWN_START, + TORSO_WINDDOWN_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + //jshepard: HACK for now we're blending here, but we need to support charge up anims here + PlayAnim ( ANIMCHANNEL_TORSO, "fire", 4 ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_WINDUP_WAIT); + + // wait for the windup anim to end, or the attackHeld to be false. + case TORSO_WINDUP_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + if( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames )) { + return SRESULT_STAGE( TORSO_FIRE_LOOP ); + } + return SRESULT_WAIT; + + // play the firing loop + case TORSO_FIRE_LOOP: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + + // loop the fire anim + case TORSO_FIRE_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + //loop the attack anim + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + } + return SRESULT_WAIT; + + //wind down + case TORSO_WINDDOWN_START: + //jshepard: HACK just blend back into idle for now, we could support winddown anims here. + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + pfl.weaponFired = false; + return SRESULT_DONE; + + } + return SRESULT_DONE; +} +/* +================ +idPlayer::State_Torso_Reload +================ +*/ +stateResult_t idPlayer::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + TORSO_RELOAD_START, + TORSO_RELOAD_STARTWAIT, + TORSO_RELOAD_LOOP, + TORSO_RELOAD_LOOPWAIT, + TORSO_RELOAD_WAIT, + TORSO_RELOAD_END + }; + switch ( parms.stage ) { + // Start the reload by either playing the reload animation or the reload_start animation + case TORSO_RELOAD_START: + if ( HasAnim ( ANIMCHANNEL_TORSO, "reload_start" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "reload_start", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_STARTWAIT); + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for the reload_start animation to finish and transition to reload_loop + case TORSO_RELOAD_STARTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Play a single reload from the reload loop + case TORSO_RELOAD_LOOP: + if ( !pfl.reload ) { + return SRESULT_STAGE(TORSO_RELOAD_END); + } + PlayAnim ( ANIMCHANNEL_TORSO, "reload_loop", 0 ); + return SRESULT_STAGE(TORSO_RELOAD_LOOPWAIT); + + // Wait for the looping reload to finish and either start a new one or end the reload + case TORSO_RELOAD_LOOPWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // End the reload + case TORSO_RELOAD_END: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload_end", 3 ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for reload to finish (called by both reload_end and reload) + case TORSO_RELOAD_WAIT: + if ( pfl.weaponFired || AnimDone ( ANIMCHANNEL_TORSO, 3 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Pain +================ +*/ +stateResult_t idPlayer::State_Torso_Pain ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, painAnim.Length()?painAnim:"pain", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Dead +================ +*/ +stateResult_t idPlayer::State_Torso_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Emote +================ +*/ +stateResult_t idPlayer::State_Torso_Emote ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if( emote == PE_GRAB_A ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_a", parms.blendFrames ); + } else if( emote == PE_GRAB_B ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_b", parms.blendFrames ); + } else if( emote == PE_SALUTE ) { + PlayAnim ( ANIMCHANNEL_TORSO, "salute", parms.blendFrames ); + } else if( emote == PE_CHEER ) { + PlayAnim ( ANIMCHANNEL_TORSO, "cheer", parms.blendFrames ); + } else if( emote == PE_TAUNT ) { + PlayAnim ( ANIMCHANNEL_TORSO, "taunt", parms.blendFrames ); + } + + emote = PE_NONE; + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idPlayer::IsLegsIdle +================ +*/ +bool idPlayer::IsLegsIdle ( bool crouching ) const { + return ( (pfl.crouch == crouching) && pfl.onGround && (pfl.forward==pfl.backward) && (pfl.strafeLeft==pfl.strafeRight) ); +} + +/* +================ +idPlayer::State_Legs_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", parms.blendFrames ); + return SRESULT_DONE; + } + IdleAnim ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // If now crouching go back to idle so we can transition to crouch + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + return SRESULT_DONE; + } else if ( pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Jump", 4 ); + return SRESULT_DONE; + } else if ( !pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + }else if ( pfl.forward && !pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", parms.blendFrames ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "crouch", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.crouch || pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", 4 ); + return SRESULT_DONE; + } else if ( (pfl.forward && !pfl.backward) || (pfl.strafeLeft != pfl.strafeRight) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Forward", parms.blendFrames ); + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_down", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( true ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Uncrouch +================ +*/ +stateResult_t idPlayer::State_Legs_Uncrouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_up", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Run_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Forward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && ((!pfl.backward && pfl.forward) || (pfl.strafeLeft != pfl.strafeRight)) ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Backward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk_backward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && !pfl.forward && pfl.backward ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Jump +================ +*/ +stateResult_t idPlayer::State_Legs_Jump ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // prevent infinite recursion + pfl.jump = false; + if ( pfl.run ) { + PlayAnim ( ANIMCHANNEL_LEGS, "run_jump", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "jump", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Fall +================ +*/ +stateResult_t idPlayer::State_Legs_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "fall", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Land +================ +*/ +stateResult_t idPlayer::State_Legs_Land ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( IsLegsIdle ( false ) && ( pfl.hardLanding || pfl.softLanding ) ) { + if ( pfl.hardLanding ) { + PlayAnim ( ANIMCHANNEL_LEGS, "hard_land", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "soft_land", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Dead +================ +*/ +stateResult_t idPlayer::State_Legs_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_Alive + +Waits until the player is alive again. +================ +*/ +stateResult_t idPlayer::State_Wait_Alive ( const stateParms_t& parms ) { + if ( pfl.dead ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_ReloadAnim +================ +*/ +stateResult_t idPlayer::State_Wait_ReloadAnim ( const stateParms_t& parms ) { + // The gun firing can cancel any of the relod animations + if ( pfl.weaponFired ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // wait for the animation to finish + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} diff --git a/source/game/Projectile.cpp b/source/game/Projectile.cpp new file mode 100644 index 0000000..d3e1eaf --- /dev/null +++ b/source/game/Projectile.cpp @@ -0,0 +1,2124 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" +#include "Projectile.h" +#include "spawner.h" + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_Explode( "", NULL ); +const idEventDef EV_Fizzle( "", NULL ); +const idEventDef EV_RadiusDamage( "", "E" ); +const idEventDef EV_ResidualDamage ( "", "E" ); + +CLASS_DECLARATION( idEntity, idProjectile ) + EVENT( EV_Explode, idProjectile::Event_Explode ) + EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) + EVENT( EV_Touch, idProjectile::Event_Touch ) + EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) + EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage ) +END_CLASS + +/* +================ +idProjectile::idProjectile +================ +*/ +idProjectile::idProjectile( void ) { + methodOfDeath = -1; + owner = NULL; + memset( &projectileFlags, 0, sizeof( projectileFlags ) ); + damagePower = 1.0f; + + memset( &renderLight, 0, sizeof( renderLight ) ); + + lightDefHandle = -1; + lightOffset.Zero(); + lightStartTime = 0; + lightEndTime = 0; + lightColor.Zero(); + + visualAngles.Zero(); + angularVelocity.Zero(); + speed.Init( 0.0f, 0.0f, 0.0f, 0.0f ); + updateVelocity = false; + + rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() ); + + flyEffect = NULL; + flyEffectAttenuateSpeed = 0.0f; + bounceCount = 0; + hitCount = 0; + state = SPAWNED; + + fl.networkSync = true; + + prePredictTime = 0; + + syncPhysics = false; + + launchTime = 0; + launchOrig = vec3_origin; + launchDir = vec3_origin; + launchSpeed = 0.0f; +} + +/* +================ +idProjectile::Spawn +================ +*/ +void idProjectile::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( 0 ); + physicsObj.PutToRest(); + SetPhysics( &physicsObj ); + prePredictTime = spawnArgs.GetInt( "predictTime", "0" ); + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( gameLocal.isClient ) { + Hide(); + } +} + +/* +================ +idProjectile::Save +================ +*/ +void idProjectile::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var + owner.Save( savefile ); + savefile->Write( &projectileFlags, sizeof( projectileFlags ) ); + savefile->WriteFloat( damagePower ); + + savefile->WriteRenderLight( renderLight ); + + savefile->WriteInt( ( int )lightDefHandle ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var + savefile->WriteAngles( angularVelocity ); // cnicholson: moved var + + // Save speed + savefile->WriteBool ( updateVelocity ); + savefile->WriteFloat( speed.GetStartTime() ); + savefile->WriteFloat( speed.GetDuration() ); + savefile->WriteFloat( speed.GetStartValue() ); + savefile->WriteFloat( speed.GetEndValue() ); + + // rotation; this is a class, so it doesnt get saved here + + flyEffect.Save( savefile ); // cnicholson: added unsaved var + savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->WriteInt ( bounceCount ); + savefile->WriteInt ( hitCount ); + + savefile->WriteInt( (int)state ); +} + +/* +================ +idProjectile::Restore +================ +*/ +void idProjectile::Restore( idRestoreGame *savefile ) { + float fset; + idVec3 temp; + + savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var + owner.Restore( savefile ); + savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); + savefile->ReadFloat( damagePower ); + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadInt( (int &)lightDefHandle ); + if ( lightDefHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + + // Restore the physics + savefile->ReadStaticObject( physicsObj ); + RestorePhysics ( &physicsObj ); + + savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var + savefile->ReadAngles( angularVelocity ); // cnicholson: moved var + + // Restore speed + savefile->ReadBool ( updateVelocity ); + savefile->ReadFloat( fset ); + speed.SetStartTime( fset ); + savefile->ReadFloat( fset ); + speed.SetDuration( fset ); + savefile->ReadFloat( fset ); + speed.SetStartValue( fset ); + savefile->ReadFloat( fset ); + speed.SetEndValue( fset ); + + // rotation? + + flyEffect.Restore( savefile ); // cnicholson: added unrestored var + savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->ReadInt ( bounceCount ); + savefile->ReadInt ( hitCount ); + + savefile->ReadInt( (int &)state ); +} + +/* +================ +idProjectile::GetOwner +================ +*/ +idEntity *idProjectile::GetOwner( void ) const { + return owner.GetEntity(); +} + +/* +================ +idProjectile::SetSpeed +================ +*/ +void idProjectile::SetSpeed( float s, int accelTime ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s ); + + if ( accelTime > 0 ) { + updateVelocity = true; + } else { + updateVelocity = false; + } + + // Update the velocity to match the direction we are facing and include any accelerations + physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel ); +} + +/* +================ +idProjectile::Create +================ +*/ +void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) { + idDict args; + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + idVec3 tmp; + idMat3 axis; + + Unbind(); + + axis = dir.ToMat3(); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner ); + physicsObj.extraPassEntity = extraPassEntity; + + owner = _owner; + + memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); + spawnArgs.GetVector( "light_color", "1 1 1", light_color ); + renderLight.shaderParms[0] = light_color[0]; + renderLight.shaderParms[1] = light_color[1]; + renderLight.shaderParms[2] = light_color[2]; + renderLight.shaderParms[3] = 1.0f; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: set the projectile lights to be no shadows + renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + lightStartTime = 0; + lightEndTime = 0; + + damagePower = 1.0f; + + UpdateVisuals(); + + state = CREATED; +} + +/* +================= +idProjectile::~idProjectile +================= +*/ +idProjectile::~idProjectile() { + StopSound( SND_CHANNEL_ANY, false ); + FreeLightDef(); + SetPhysics( NULL ); +} + +/* +================= +idProjectile::FreeLightDef +================= +*/ +void idProjectile::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================= +idProjectile::Launch +================= +*/ +void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) { + float fuse; + idVec3 velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + float temp, temp2; + idVec3 gravVec; + idVec3 tmp; + int contents; + int clipMask; + + // allow characters to throw projectiles during cinematics, but not the player + if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + cinematic = owner.GetEntity()->cinematic; + } else { + cinematic = false; + } + + // Set the damage + damagePower = dmgPower; + + if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) { + spawnArgs.GetVector( "velocity", "0 0 0", tmp ); + temp = tmp[0]; + } else { + float speedRandom; + if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) { + temp += gameLocal.random.CRandomFloat()*speedRandom; + } + } + if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) { + temp2 = temp; + } + float speedDuration; + speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) ); + speed.Init( gameLocal.time, speedDuration, temp, temp2 ); + if ( speedDuration > 0 && temp != temp2 ) { + // only support constant velocity projectiles in MP + // ( we also assume that no MP projectiles use speedRandom ) + assert( !gameLocal.isServer ); + updateVelocity = true; + } + launchSpeed = temp; + + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() ); + bounceCount = spawnArgs.GetInt( "bounce_count", "-1" ); + + //spawn impact entity information + impactEntity = spawnArgs.GetString("def_impactEntity",""); + numImpactEntities = spawnArgs.GetInt("numImpactEntities","0"); + ieMinPitch = spawnArgs.GetInt("ieMinPitch","0"); + ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0"); + ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0"); + + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); + projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); + projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); + projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" ); + + lightStartTime = 0; + lightEndTime = 0; + + impactedEntity = 0; + + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero; +// RAVEN END + + Unbind(); + + contents = 0; + clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX; + + // all projectiles are projectileclip + clipMask |= CONTENTS_PROJECTILECLIP; + + if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) { + clipMask |= CONTENTS_LARGESHOTCLIP; + } + + if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) { + clipMask |= CONTENTS_MOVEABLECLIP; + } + if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) { + clipMask |= CONTENTS_MONSTERCLIP; + } + + if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { + contents |= CONTENTS_TRIGGER; + } + + if ( !spawnArgs.GetBool( "no_contents", "1" ) ) { + contents |= CONTENTS_PROJECTILE; + } + + clipMask |= CONTENTS_PROJECTILE; + + // don't do tracers on client, we don't know origin and direction + if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { + SetModel( spawnArgs.GetString( "model_tracer" ) ); + projectileFlags.isTracer = true; + } + + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce ); + physicsObj.SetGravity( gravVec ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask | CONTENTS_WATER ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( dir.ToMat3() ); + + if ( !gameLocal.isClient ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Explode, fuse ); + } else { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + idQuat q( dir.ToMat3().ToQuat() ); + rotation.Init( gameLocal.GetTime(), 0.0f, q, q ); + + if ( projectileFlags.isTracer ) { + StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); + } else { + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + } + + // used for the plasma bolts but may have other uses as well + if ( projectileFlags.randomShaderSpin ) { + float f = gameLocal.random.RandomFloat(); + f *= 0.5f; + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; + } + + UpdateVisuals(); + + // Make sure these come after update visuals so the origin and axis are correct + PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis ); + + flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true ); + flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" ); + + state = LAUNCHED; + + hitCount = 0; + + predictTime = prePredictTime; + + if ( gameLocal.isServer ) { + // store launch information for networking + launchTime = gameLocal.time; + launchOrig = physicsObj.GetOrigin(); + launchDir = dir; + } + + if ( g_perfTest_noProjectiles.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idProjectile::Think +================ +*/ +void idProjectile::Think( void ) { + // run physics + if ( thinkFlags & TH_PHYSICS ) { + + // Update the velocity to match the changing speed + if ( updateVelocity ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize ( ); + physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel ); + if ( speed.IsDone ( gameLocal.time ) ) { + updateVelocity = false; + } + } + + RunPhysics(); + + // If we werent at rest and are now then start the atrest fuse + if ( physicsObj.IsAtRest( ) ) { + float fuse = spawnArgs.GetFloat( "fuse_atrest" ); + if ( fuse > 0.0f ) { + if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + CancelEvents( &EV_Explode ); + PostEventSec( &EV_Explode, fuse ); + } else { + CancelEvents( &EV_Fizzle ); + PostEventSec( &EV_Fizzle, fuse ); + } + } + } + + // Stop the trail effect if the physics flag was removed + if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) { + if ( physicsObj.IsAtRest( ) ) { + flyEffect->Stop( ); + flyEffect = NULL; + } else { + float speed; + speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) ); + flyEffect->Attenuate( speed / flyEffectAttenuateSpeed ); + } + } + + UpdateVisualAngles(); + } + + Present(); + + // add the light + if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; + renderLight.axis = GetPhysics()->GetAxis(); + if ( ( lightDefHandle != -1 ) ) { + if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) { + idVec3 color( 0, 0, 0 ); + if ( gameLocal.time < lightEndTime ) { + float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); + color.Lerp( lightColor, color, frac ); + } + renderLight.shaderParms[SHADERPARM_RED] = color.x; + renderLight.shaderParms[SHADERPARM_GREEN] = color.y; + renderLight.shaderParms[SHADERPARM_BLUE] = color.z; + } + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } +} + +/* +================= +idProjectile::UpdateVisualAngles +================= +*/ +void idProjectile::UpdateVisualAngles() { + idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() ); + + if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() ); + return; + } + + if( physicsObj.GetNumContacts() ) { + return; + } + + if( !rotation.IsDone(gameLocal.GetTime()) ) { + return; + } + + if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) { + return; + } + + visualAngles += angularVelocity; + idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat(); + rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q ); +} + +/* +================= +idProjectile::Collide +================= +*/ +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { + bool dummy = false; + return Collide( collision, velocity, dummy ); +} + +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { + idEntity* ent; + idEntity* actualHitEnt = NULL; + idEntity* ignore; + const char* damageDefName; + idVec3 dir; + bool canDamage; + + hitTeleporter = false; + + if ( state == EXPLODED || state == FIZZLED ) { + return true; + } + + // allow projectiles to hit triggers (teleports) + // predict this on a client + if( collision.c.contents & CONTENTS_TRIGGER ) { + idEntity* trigger = gameLocal.entities[ collision.c.entityNum ]; + + if( trigger ) { + if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) { + + hitTeleporter = true; + + trace_t trace; + + trace.endpos = physicsObj.GetOrigin(); + trace.endAxis = physicsObj.GetAxis(); + + trace.c.contents = collision.c.contents; + trace.c.entityNum = collision.c.entityNum; + if( trigger->GetPhysics()->GetClipModel() ) { + trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId(); + } else { + trace.c.id = 0; + } + + + trigger->Signal( SIG_TOUCH ); + trigger->ProcessEvent( &EV_Touch, this, &trace ); + } + } + + // when we hit a trigger, align our velocity to the trigger's coordinate plane + if( gameLocal.isServer ) { + idVec3 up( 0.0f, 0.0f, 1.0f ); + idVec3 right = collision.c.normal.Cross( up ); + idMat3 mat( collision.c.normal, right, up ); + + physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) ); + physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) ); + + // update the projectile's launchdir and launch origin + // this will propagate the change to the clients for prediction + // re-launch the projectile + + idVec3 newDir = physicsObj.GetLinearVelocity(); + newDir.Normalize(); + launchTime = gameLocal.time; + launchDir = newDir; + physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) ); + physicsObj.SetAxis( newDir.ToMat3() ); + launchOrig = physicsObj.GetOrigin(); + } + + if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) { + return false; + } + } + + // don't predict collisions on projectiles that need their physics synced (e.g. grenades) + if ( gameLocal.isClient && (!g_clientProjectileCollision.GetBool() || syncPhysics) ) { + // optionally do not try to predict detonates, that causes projectiles disappearing + // but most of the time works well to stop projectiles clipping through geometry onc lients + return false; + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + // assert( 0 ); // twhitaker: this isn't necessary + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::GetClassType() ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + // Can the projectile damage? + canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE )); + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse +// RAVEN BEGIN +// abahr: added call to SkipDamageImpulse changed where push comes from + damageDefName = NULL; + if ( collision.c.materialType ) { + damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) ); + } + if ( !damageDefName || !*damageDefName ) { + damageDefName = spawnArgs.GetString ( "def_damage" ); + } + + if( damageDefName && damageDefName[0] ) { + const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( dict ) { + ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict ); + } + } +// RAVEN END + + //Spawn any impact entities if necessary. + SpawnImpactEntities(collision, velocity); + + //Apply any impact force if the necessary + //ApplyImpactForce(ent, collision, dir); + + // MP: projectiles open doors + if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + ent->ProcessEvent( &EV_Activate , this ); + } + + // If the projectile hits water then we need to let the projectile keep going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + if ( !physicsObj.IsInWater( ) ) { + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + } + // Pass through water + return false; + } else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + bool bounce = false; + + // Determine if the projectile should bounce + bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage; + bounce = bounce && (bounceCount == -1 || bounceCount > 0); + //assert(collision.c.material); + if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) { + bounce = !projectileFlags.detonate_on_bounce; + } + + if ( bounce ) { + if ( bounceCount != -1 ) { + bounceCount--; + } + + StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ); + + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( ent->IsType ( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } + } else { + // FIXME: clean up + idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() ); + axis[0].ProjectOntoPlane( collision.c.normal ); + axis[0].Normalize(); + axis[2] = collision.c.normal; + axis[1] = axis[2].Cross( axis[0] ).ToNormal(); + + rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() ); + } + if ( actualHitEnt + && actualHitEnt != ent + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); +// SetAxis( collision.endAxis ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + ignore = NULL; + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + bool willPlayDamageEffect = true; + +// jnewquist: Use accessor for static class type + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + // if the projectile hit an ai + if ( ent->IsType( idAI::GetClassType() ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + idAI * ai_ent = static_cast(ent); + if( ai_ent->team == player->team) { + willPlayDamageEffect = false; + } + } + } + +// RAVEN END + + // if the hit entity takes damage + if ( canDamage ) { + + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); +// RAVEN BEGIN +// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint +// otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == collision.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } +// RAVEN END + ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint ); + + if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) { + statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 ); + hitCount++; + } + } + } + + ignore = ent; + + // TODO: fixme + ent->AddDamageEffect ( collision, velocity, damageDefName, owner ); + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any effects. +// this should make sure that only explosion effects play when the player shoots his comrades. + if( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + } +// RAVEN END + +/* + // if the projectile causes a damage effect + bool explodeFX = true; + if ( spawnArgs.GetBool( "impact_damage_effect" ) ) { + // if the hit entity has a special damage effect + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + ent->AddDamageEffect( collision, velocity, damageDefName ); + } else { + DefaultDamageEffect( collision, velocity, damageDefName ); + explodeFX = false; + } + } +*/ + + // don't predict explosions on clients + if( gameLocal.isClient ) { + return true; + } + + Explode( &collision, false, ignore ); + + return true; +} + +void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity) +{ + if( impactEntity.Length() == 0 || numImpactEntities == 0 ) + return; + + const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity); + if(impactEntityDict == NULL) + return; + + idVec3 tempDirection; + idVec3 direction; + direction.Zero(); + + idVec3 up = collision.c.normal; + + //Calculate the axes for that are oriented to the impact point. + idMat3 impactAxes; + + idVec3 right = velocity.Cross(up); + idVec3 forward = up.Cross(right); + + right.Normalize(); + forward.Normalize(); + impactAxes[0] = forward; + impactAxes[1] = right; + impactAxes[2] = up; + + //Calculate the reflection vector by calculating the forward component and up component of the projectile direction + //idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up)); + idVec3 reflectionVelocity = up * 0.01f; + + //The algorithm below will launch entities at a random pitch and somewhat random yaw. + //The yaw is calculated by dividing 360 by the number of entities to spawn. This creates + //a distribution slice. Then using the slice percentage, this will determine how much of the + //slice to use. + //This creates a random,but somewhat even coverage of the circle. + + //Calculate the slice size and pick a random start position. + int sliceSize = 360 / numImpactEntities; + int startPosition = rvRandom::irand(0, 360); + + //Move the origin away from the collision point. This prevents the projectiles + //from colliding with the surface. + idVec3 origin = collision.endpos; + origin += 10.0f * collision.c.normal; + for(int i = 0;i < numImpactEntities; i++) + { + idProjectile* spawnProjectile = NULL; + gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile); + if(spawnProjectile != NULL) + { + int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch); + int sliceMiddle = (i * sliceSize) + startPosition; + int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2; + + int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness); + yaw = yaw % 360; + + float cosPitch = idMath::Cos(DEG2RAD(pitch)); + tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw)); + tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw)); + tempDirection.z = idMath::Sin(DEG2RAD(pitch)); + + spawnProjectile->SetOwner(owner); + + //Now orient the direction to the surface world orientation. + direction = impactAxes * tempDirection; + spawnProjectile->Launch(origin, direction, reflectionVelocity); + } + } +} + +/* +================= +idProjectile::DefaultDamageEffect +================= +*/ +void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) { + idEntity* ent; + idMat3 axis; + ent = gameLocal.entities[ tr.c.entityNum ]; + + // Make sure we want to play effects + if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" )) + && owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) { + return; + } + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) { + idVec3 dir; + dir = velocity; + dir.Normalize ( ); + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + + // Play an actor specific impact effect? + const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType ); + if ( actorImpactEffect ) { + gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true ); + return; + } + } else { + axis = tr.c.normal.ToMat3(); + } + + // Play an impact effect on the entity that got hit + if ( ent->IsType( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true ); + } +} + +/* +================ +idProjectile::Killed +================ +*/ +void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + Explode( NULL, true ); + physicsObj.ClearContacts(); + physicsObj.PutToRest(); + } else { + Fizzle(); + } +} + +/* +================ +idProjectile::Fizzle +================ +*/ +void idProjectile::Fizzle( void ) { + + if ( gameLocal.isClient || state == EXPLODED || state == FIZZLED ) { + return; + } + + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + // No more fly effects + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + Hide(); + FreeLightDef(); + + state = FIZZLED; + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_RadiusDamage( idEntity *ignore ) { + const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount ); + } +} + +/* +================ +idProjectile::Event_ResidualDamage +================ +*/ +void idProjectile::Event_ResidualDamage ( idEntity* ignore ) { + const char *residual_damage = spawnArgs.GetString( "def_residual_damage" ); + if ( residual_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore ); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) { + idVec3 normal, endpos; + int removeTime; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { + GetPhysics()->SetAxis( normal.ToMat3() ); + } else { + normal = collision ? collision->c.normal : idVec3( 0, 0, 1 ); + } + endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin(); + + removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + // play sound + StopSound( SND_CHANNEL_BODY, false ); + StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL ); + + if ( showExplodeFX ) { + idVec3 fxDir; + if ( physicsObj.GetGravityNormal( ) != vec3_zero ) { + fxDir = -physicsObj.GetGravityNormal( ); + } else { + fxDir = -physicsObj.GetLinearVelocity( ); + fxDir.Normalize( ); + } + // FIXME: This should be done in a better way + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + + // Stop the fly effect without destroying particles to ensure the trail within can persist. + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + // Stop the remaining particles + StopAllEffects( ); + + Hide(); + FreeLightDef(); + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + state = EXPLODED; + + if ( gameLocal.isClient ) { + return; + } + + // alert the ai + gameLocal.AlertAI( owner.GetEntity() ); + + // bind the projectile to the impact entity if necesary + if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { + Bind( gameLocal.entities[collision->c.entityNum], true ); + } + + // splash damage + removeTime = 0; + float delay = spawnArgs.GetFloat( "delay_splash" ); + if ( delay ) { + if ( removeTime < delay * 1000 ) { + removeTime = ( delay + 0.10 ) * 1000; + } + PostEventSec( &EV_RadiusDamage, delay, ignore ); + } else { + Event_RadiusDamage( ignore ); + } + + // Residual damage (damage over time) + delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) ); + if ( delay > 0.0f ) { + PostEventMS ( &EV_ResidualDamage, delay, ignore ); + + // Keep the projectile around until the residual damage is done + delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) ); + if ( removeTime < delay ) { + removeTime = delay; + } + } + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::GetVelocity +================ +*/ +idVec3 idProjectile::GetVelocity( const idDict *projectile ) { + idVec3 velocity; + + velocity.Zero ( ); + if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) { + projectile->GetVector( "velocity", "0 0 0", velocity ); + } + return velocity; +} + +/* +================ +idProjectile::GetGravity +================ +*/ +idVec3 idProjectile::GetGravity( const idDict *projectile ) { + if ( projectile ) { + return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" ); + } + return vec3_origin; +} + +/* +================ +idProjectile::PlayPainEffect +================ +*/ +void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) { + static int damageTable[] = { 100, 50, 25, 10, 0 }; + int index; + + // Normalize the damage value to the damage table + for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ ); + + // loop until we find a pain effect, trying lower damage numbers if needed + for ( ; damageTable[index]; index ++ ) { + // Try the pain effect for the current damage value and if it plays then + // we are done + if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) { + return; + } + } + + // Play the default pain effect + ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() ); +} + +/* +================ +idProjectile::PlayDetonateEffect +================ +*/ +void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis ) { + if( physicsObj.HasGroundContacts() ) { + if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) { + trace_t tr; + idVec3 down; + down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f; + gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this ); + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true ); + } + return; + } + + gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true ); +} + +/* +================ +idProjectile::Event_Explode +================ +*/ +void idProjectile::Event_Explode( void ) { + // events are processed outside of the think loop, so set the current thinking ent appropriately + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Explode( NULL, true ); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Fizzle +================ +*/ +void idProjectile::Event_Fizzle( void ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Fizzle(); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Touch +================ +*/ +void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { + if ( IsHidden() ) { + return; + } + + if ( other != owner.GetEntity() ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + DefaultDamageEffect( collision, collision.c.normal, NULL ); + Explode( NULL, true ); + + gameLocal.currentThinkingEntity = think; + } +} + +/* +================ +idProjectile::ClientPredictionThink +================ +*/ +void idProjectile::ClientPredictionThink( void ) { + if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) { + return; + } + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + Think(); +} + +/* +================ +idProjectile::WriteToSnapshot +================ +*/ +void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + + msg.WriteBits( state, 3 ); + if ( state >= LAUNCHED ) { + // feed the client with start position, direction and time. let the client do everything else + // this won't change during projectile life and be completely deltified away + msg.WriteLong( launchTime ); + msg.WriteFloat( launchOrig[ 0 ] ); + msg.WriteFloat( launchOrig[ 1 ] ); + msg.WriteFloat( launchOrig[ 2 ] ); + msg.WriteDir( launchDir, 24 ); + } +} + +/* +================ +idProjectile::ReadFromSnapshot +================ +*/ +void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + projectileState_t newState; + + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + + newState = (projectileState_t) msg.ReadBits( 3 ); + if ( newState >= LAUNCHED ) { + launchTime = msg.ReadLong(); + launchOrig[ 0 ] = msg.ReadFloat(); + launchOrig[ 1 ] = msg.ReadFloat(); + launchOrig[ 2 ] = msg.ReadFloat(); + launchDir = msg.ReadDir( 24 ); + } + // we always create and launch at the same time + // state on a client can be SPAWNED, LAUNCHED, EXPLODED + // expect never to get a CREATED projectile, they should launch right away + assert( state != CREATED && state != FIZZLED && newState != CREATED ); + if ( newState != state ) { + switch ( newState ) { + case LAUNCHED: + Create( NULL, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + break; + case FIZZLED: + case EXPLODED: + if ( state != EXPLODED ) { + StopSound( SND_CHANNEL_BODY, false ); + StopAllEffects(); + Hide(); + FreeLightDef(); + state = EXPLODED; + } + break; + } + } + + if ( msg.HasChanged() ) { + if( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + + UpdateVisuals(); + } +} + +/* +=============== +idProjectile::ClientStale +=============== +*/ +bool idProjectile::ClientStale( void ) { + // delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway ) + StopAllEffects(); + return true; +} + +/* +================ +idProjectile::GetPhysicsToVisualTransform +================ +*/ +bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose(); + + origin.Zero(); + return true; +} + +/* +=============================================================================== + + idGuidedProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, idGuidedProjectile ) +END_CLASS + +/* +================ +idGuidedProjectile::idGuidedProjectile( void ) +================ +*/ +idGuidedProjectile::idGuidedProjectile( void ) { + guideType = GUIDE_NONE; + turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f ); +} + +/* +================= +idGuidedProjectile::~idGuidedProjectile +================= +*/ +idGuidedProjectile::~idGuidedProjectile() { +} + +/* +================ +idGuidedProjectile::Save +================ +*/ +void idGuidedProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( guideType ); + guideEnt.Save( savefile ); + savefile->WriteVec3 ( guideDir ); + savefile->WriteVec3 ( guidePos ); + savefile->WriteJoint ( guideJoint ); + savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var + + savefile->WriteInt ( driftTime ); + savefile->WriteInt ( driftRate ); + savefile->WriteFloat ( driftRange ); + savefile->WriteFloat ( driftRadius ); + savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var + savefile->WriteFloat ( driftAngle ); + savefile->WriteFloat ( driftAngleStep ); + savefile->WriteFloat ( driftProjectRange ); + + savefile->WriteFloat( turn_max.GetStartTime() ); + savefile->WriteFloat( turn_max.GetDuration() ); + savefile->WriteFloat( turn_max.GetStartValue() ); + savefile->WriteFloat( turn_max.GetEndValue() ); + + savefile->WriteInt ( launchTime ); + savefile->WriteInt ( guideDelay ); + savefile->WriteInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::Restore +================ +*/ +void idGuidedProjectile::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadInt ( guideType ); + guideEnt.Restore( savefile ); + savefile->ReadVec3 ( guideDir ); + savefile->ReadVec3 ( guidePos ); + savefile->ReadJoint ( guideJoint ); + savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var + + savefile->ReadInt ( driftTime ); + savefile->ReadInt ( driftRate ); + savefile->ReadFloat ( driftRange ); + savefile->ReadFloat ( driftRadius ); + savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var + savefile->ReadFloat ( driftAngle ); + savefile->ReadFloat ( driftAngleStep ); + savefile->ReadFloat ( driftProjectRange ); + + savefile->ReadFloat( set ); + turn_max.SetStartTime( set ); + savefile->ReadFloat( set ); + turn_max.SetDuration( set ); + savefile->ReadFloat( set ); + turn_max.SetStartValue( set ); + savefile->ReadFloat( set ); + turn_max.SetEndValue( set ); + + savefile->ReadInt ( launchTime ); + savefile->ReadInt ( guideDelay ); + savefile->ReadInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::GetGuideDir +================ +*/ +bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) { + // Dont start guiding immeidately? + if ( gameLocal.GetTime() - launchTime < guideDelay ) { + return false; + } + + switch ( guideType ) { + case GUIDE_ENTITY: + // If the guide entity is gone or dead then cancel the guide + if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) { + CancelGuide ( ); + return false; + } + // Use eye position for actors and center of bounds for everything else + if ( guideJoint != INVALID_JOINT ) { + idMat3 jointAxis; + guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis ); + outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis); + if ( !guidePos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis; + outDir += jointAxis[0]*guidePos[0]; + outDir += jointAxis[1]*guidePos[1]; + outDir += jointAxis[2]*guidePos[2]; + } + } else { + outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter(); + if ( guideEnt->IsType( idActor::GetClassType() ) ) { + outDir += static_cast(guideEnt.GetEntity())->GetEyePosition(); + outDir *= 0.5f; + } + } + outDir -= physicsObj.GetOrigin(); + break; + + case GUIDE_POS: + outDir = guidePos - physicsObj.GetOrigin(); + break; + + case GUIDE_DIR: + // Project our current position on to the desired direction + outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir); + + // Seek towards a point forward along our desired direction + outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin(); + break; + + default: + return false; + } + + // Add drifting + if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) { + idMat3 axis; + + outDist = outDir.NormalizeFast(); + axis = outDir.ToMat3(); + + if ( gameLocal.time > driftTime ) { + driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity; + driftTime = gameLocal.time + driftRate; + } else { + driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec ); + idMath::AngleNormalize360 ( driftAngle ); + } + + float angle; + angle = DEG2RAD ( driftAngle ); + outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange ); + outDir += axis[2] * (driftRadius * idMath::Sin ( angle )); + outDir += axis[1] * (driftRadius * idMath::Cos ( angle )); + + outDir -= physicsObj.GetOrigin(); + } + + outDist = outDir.Normalize ( ); + + return true; +} + +/* +================ +idGuidedProjectile::Think +================ +*/ +void idGuidedProjectile::Think( void ) { + + if ( state == LAUNCHED ) { + idVec3 dir; + idVec3 vel; + float angle; + float maxangle; + idMat3 axis; + float dist; + + // crank up to normal speed ? + if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) { + float newSpeed = spawnArgs.GetFloat( "speed" ); + float newSpeed2; + if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) { + newSpeed2 = newSpeed; + } + float newSpeedDuration; + newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) ); + speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 ); + guideDelay = 0; + } + + if ( !GetGuideDir( dir, dist ) ) { + idProjectile::Think(); + return; + } + + // Direction of travel + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + + // Calculate the angle between the current projectile direction and where we want to go + angle = RAD2DEG( idMath::ACos( dir * vel ) ); + + // Make sure the angle doesnt cross our max turn radius + maxangle = turn_max.GetCurrentValue( gameLocal.time ); + if ( angle < -maxangle ) { + angle = -maxangle; + } else if ( angle > maxangle ) { + angle = maxangle; + } + + // Debug information + if ( g_debugWeapon.GetBool ( ) ) { + gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f ); + gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f ); + } + + // Calculate the new axis by rotating the current forward vector around the cross of the forward + // vector and the direction vector. + vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle ); + physicsObj.SetLinearVelocity( vel * GetSpeed ( ) ); + + // If within the minium distance to the target anything over a 45 degree change will cancel the guide + if ( guideMinDist != 0.0f && dist < guideMinDist ) { + // Stop guiding if we have passed our target + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize( ); + if ( vel * dir < 0.7f ) { + guideType = GUIDE_NONE; + } + } + + idProjectile::Think(); + } else { + idProjectile::Think(); + } +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + launchTime = gameLocal.GetTime(); + + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) { + GuideTo ( static_cast( owner.GetEntity() )->GetEnemy() ); + } + } + + guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" ); + guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) ); + + if ( guideDelay ) { + float delaySpeed; + if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) { + float delaySpeed2; + if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) { + delaySpeed2 = delaySpeed; + } + float delaySpeedDuration; + delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) ); + speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + } + } + + driftTime = 0; + driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) ); + driftRange = spawnArgs.GetFloat ( "driftRange" ); + driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" ); + driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f; + driftAngleStep = spawnArgs.GetFloat ( "driftRotate" ); + driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f); + driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" )); + + driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" ); + + // Turn rate can be ramped up over time + turn_max.Init ( gameLocal.time, + SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), + 0, + spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() ); + + UpdateVisuals(); +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + idProjectile::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + rvDriftingProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvDriftingProjectile ) +END_CLASS + +/* +================ +rvDriftingProjectile::rvDriftingProjectile( void ) +================ +*/ +rvDriftingProjectile::rvDriftingProjectile( void ) { +} + +/* +================= +rvDriftingProjectile::~rvDriftingProjectile +================= +*/ +rvDriftingProjectile::~rvDriftingProjectile ( void ) { +} + +/* +================ +rvDriftingProjectile::Save +================ +*/ +void rvDriftingProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteVec3 ( startDir ); + savefile->WriteVec3 ( startOrigin ); + savefile->WriteMat3 ( startAxis ); + savefile->WriteFloat ( startSpeed ); + savefile->WriteFloat ( driftOffsetMax ); + savefile->WriteFloat ( driftSpeedMax ); + savefile->WriteFloat ( driftTime ); + + savefile->WriteInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->WriteInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Restore +================ +*/ +void rvDriftingProjectile::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3 ( startDir ); + savefile->ReadVec3 ( startOrigin ); + savefile->ReadMat3 ( startAxis ); + savefile->ReadFloat ( startSpeed ); + savefile->ReadFloat ( driftOffsetMax ); + savefile->ReadFloat ( driftSpeedMax ); + savefile->ReadFloat ( driftTime ); + + savefile->ReadInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->ReadInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Think +================ +*/ +void rvDriftingProjectile::Think( void ) { + idVec3 diff; + idVec3 origin; + idVec3 oldOrigin; + idVec3 dir; + float dist; + + oldOrigin = GetPhysics()->GetOrigin ( ); + + diff = oldOrigin - startOrigin; + dist = diff.Length ( ); + origin = startOrigin + startDir * dist; + + if ( driftSpeed.IsDone ( gameLocal.time ) ) { + driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftSpeed.GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[0].IsDone ( gameLocal.time ) ) { + driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftOffset[0].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[1].IsDone ( gameLocal.time ) ) { + driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, + driftOffset[1].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time ); + origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time ); + + GetPhysics ( )->SetOrigin ( origin ); + GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) ); + + idProjectile::Think(); + + // Now orient the projectile using the old origin + dir = GetPhysics()->GetOrigin() - oldOrigin; + dir.Normalize ( ); + GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) { + startDir = dir; + startOrigin = start; + startAxis = dir.ToMat3(); + + driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" ); + driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" ); + driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) ); + + idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + startSpeed = GetPhysics()->GetLinearVelocity().Length ( ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::UpdateVisualAngles ( void ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() ); +} + +/* +=============================================================================== + + rvSpawnerProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvSpawnerProjectile ) + EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn ) +END_CLASS + +/* +================ +rvSpawnerProjectile::rvSpawnerProjectile( void ) +================ +*/ +rvSpawnerProjectile::rvSpawnerProjectile( void ) { + spawnState = STATE_NONE; +} + +/* +================= +rvSpawnerProjectile::~rvSpawnerProjectile +================= +*/ +rvSpawnerProjectile::~rvSpawnerProjectile ( void ) { + if ( spawnState == STATE_ADDED && spawner ) { + spawner->RemoveSpawnPoint ( this ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::Spawn ( void ) { + if ( *spawnArgs.GetString ( "spawner" ) ) { + PostEventMS ( &EV_PostSpawn, 0 ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +================= +rvSpawnerProjectile::Think +================= +*/ +void rvSpawnerProjectile::Think ( void ) { + idProjectile::Think ( ); + + if ( physicsObj.IsAtRest ( ) ) { + if ( spawnState == STATE_NONE && spawner ) { + spawner->AddSpawnPoint ( this ); + spawnState = STATE_ADDED; + } + } +} + +/* +================= +rvSpawnerProjectile::Event_PostSpawn +================= +*/ +void rvSpawnerProjectile::Event_PostSpawn ( void ) { + const char* temp; + temp = spawnArgs.GetString ( "spawner" ); + if ( temp && *temp ) { + idEntity* ent; + ent = gameLocal.FindEntity ( temp ); + if ( !ent ) { + gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) { + gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else { + SetSpawner ( static_cast(ent) ); + } + } +} + + +/* +=============================================================================== + + rvMIRVProjectile + +=============================================================================== +*/ +idEventDef EV_LaunchWarheads( "launchWarheads" ); + +CLASS_DECLARATION( idProjectile, rvMIRVProjectile ) + EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads ) +END_CLASS + +/* +================ +rvMIRVProjectile::rvMIRVProjectile( void ) +================ +*/ +rvMIRVProjectile::rvMIRVProjectile( void ) { + +} + +/* +================= +rvMIRVProjectile::~rvMIRVProjectile +================= +*/ +rvMIRVProjectile::~rvMIRVProjectile ( void ) { + +} + +/* +================ +void rvMIRVProjectile::Spawn( void ) +================ +*/ +void rvMIRVProjectile::Spawn( void ) { + + float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0"); + //post event for warhead launch + PostEventSec( &EV_LaunchWarheads, launchDelay ); +} + +/* +================ +void rvMIRVProjectile::Event_LaunchWarheads( void ) +================ +*/ +void rvMIRVProjectile::Event_LaunchWarheads( void ) { + + const char* warhead; + int count; + + warhead = spawnArgs.GetString("def_warhead",""); + count = spawnArgs.GetFloat("warhead_count","0"); + + if( warhead && warhead[0] ) { + + //start launching! + float angle = (360.0f / count); + idMat3 normalMat = this->GetPhysics()->GetAxis( ); + idVec3 normal = normalMat[0]; + idVec3 axis = normalMat[1]; + + float t; + idMat3 axisMat; + idProjectile * warheadEntity; + + //hey how 'bout that. + normal.Normalize(); + + for( t = 0; t< count; t++) { + + //rotate axis around normal by angle degrees. + axisMat = axis.ToMat3(); + axisMat.RotateArbitrary( normal, angle * t); + warheadEntity = static_cast(gameLocal.SpawnEntityDef( warhead)); + warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this ); + warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] ); + + } + //foom! + gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + PostEventSec( &EV_Explode, 0 ); + } + + + +} +// RAVEN END diff --git a/source/game/Projectile.h b/source/game/Projectile.h new file mode 100644 index 0000000..66bc85d --- /dev/null +++ b/source/game/Projectile.h @@ -0,0 +1,358 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_PROJECTILE_H__ +#define __GAME_PROJECTILE_H__ + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_Explode; + +class idProjectile : public idEntity { +public : + CLASS_PROTOTYPE( idProjectile ); + + idProjectile(); + virtual ~idProjectile(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore = NULL, idEntity* extraPassEntity = NULL ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + virtual void FreeLightDef( void ); + +//RITUAL BEGIN + void SetOwner(idEntity* ent) { owner = ent; } +// RITUAL END + + idEntity * GetOwner( void ) const; + + virtual void Think( void ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ); + virtual void Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore = NULL, const char *sndExplode = "snd_explode" ); + void Fizzle( void ); + + static idVec3 GetVelocity( const idDict *projectile ); + static idVec3 GetGravity( const idDict *projectile ); + + void SetSpeed ( float s, int accelTime = 0 ); + float GetSpeed ( void ) const; + + virtual void UpdateVisualAngles(); + + // information about what kind of projectile we are, used for death messages + int methodOfDeath; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + +protected: + void SpawnImpactEntities(const trace_t& collision, const idVec3 projectileDirection); + + + idEntityPtr owner; + + struct projectileFlags_s { + bool detonate_on_world : 1; + bool detonate_on_actor : 1; + bool detonate_on_bounce : 1; // Detonate if hit a bounce surface + bool randomShaderSpin : 1; + bool isTracer : 1; + } projectileFlags; + + float damagePower; + + renderLight_t renderLight; + qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + idEntity* impactedEntity; + + rvPhysics_Particle physicsObj; + idAngles visualAngles; + idAngles angularVelocity; + idInterpolate speed; + bool updateVelocity; + + rvSphericalInterpolate rotation; + + rvClientEffectPtr flyEffect; + float flyEffectAttenuateSpeed; + + int bounceCount; + bool sticky; + + idStr impactEntity; + int numImpactEntities; + int ieMinPitch; + int ieMaxPitch; + float ieSlicePercentage; + +// RAVEN BEGIN +// ddynerman: hit count for stats + int hitCount; +// ddynerman: pre-prediction ( rocket jumping ) + int prePredictTime; +// RAVEN END + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4 + } projectileState_t; + + projectileState_t state; + + void PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& direction ); + virtual void PlayDetonateEffect ( const idVec3& origin, const idMat3& axis ); + +private: + void DefaultDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + + void Event_Explode ( void ); + void Event_Fizzle ( void ); + void Event_RadiusDamage ( idEntity *ignore ); + void Event_ResidualDamage ( idEntity *ignore ); + void Event_Touch ( idEntity *other, trace_t *trace ); + + bool syncPhysics; + + // cheap linear client side projectiles + // transmitted in snapshot + int launchTime; + idVec3 launchOrig; + idVec3 launchDir; + // set from def file in :Launch on both client and server + float launchSpeed; +}; + +ID_INLINE float idProjectile::GetSpeed ( void ) const { + return speed.GetCurrentValue( gameLocal.time ); +} + +/* +=============================================================================== + +idGuidedProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_UpdateGuideTarget; +extern const idEventDef EV_GuideToEntity; +extern const idEventDef EV_GuideToPos; + +class idGuidedProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idGuidedProjectile ); + + idGuidedProjectile( void ); + ~idGuidedProjectile( void ); + + enum { + GUIDE_NONE, + GUIDE_ENTITY, + GUIDE_POS, + GUIDE_DIR, + GUIDE_MAX + }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + void GuideTo ( const idVec3& post, const idVec3& dir ); + void GuideTo ( const idVec3& pos ); + void GuideTo ( idEntity* ent, jointHandle_t guideJoint=INVALID_JOINT, const idVec3 &offset=vec3_origin ); + void CancelGuide ( void ); + + int GetGuideType ( void ) const; + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int guideType; + idEntityPtr guideEnt; + idVec3 guideDir; + idVec3 guidePos; + jointHandle_t guideJoint; + float guideMinDist; + + int driftTime; + int driftRate; + float driftRange; + float driftRadius; + float driftDiversity; + float driftAngle; + float driftAngleStep; + float driftProjectRange; + + virtual bool GetGuideDir ( idVec3 &outDir, float& outDist ); + +private: + + idInterpolate turn_max; + int launchTime; + int guideDelay; + int driftDelay; +}; + +ID_INLINE int idGuidedProjectile::GetGuideType ( void ) const { + return guideType; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos, const idVec3& dir ) { + guideType = GUIDE_DIR; + guidePos = pos; + guideDir = dir; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos ) { + guideType = GUIDE_POS; + guidePos = pos; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( idEntity* ent, jointHandle_t joint, const idVec3 &offset ) { + guideType = GUIDE_ENTITY; + guideEnt = ent; + guideJoint = joint; + guidePos = offset; + + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( this ); + } +} + +ID_INLINE void idGuidedProjectile::CancelGuide ( void ) { + guideType = GUIDE_NONE; + + // twhitaker: TEMP + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + // +} + +/* +=============================================================================== + +rvDriftingProjectile + +=============================================================================== +*/ + +class rvDriftingProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDriftingProjectile ); + + rvDriftingProjectile ( void ); + ~rvDriftingProjectile ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + virtual void Launch ( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + +protected: + + virtual void UpdateVisualAngles ( void ); + + idVec3 startDir; + idVec3 startOrigin; + idMat3 startAxis; + float startSpeed; + + idInterpolateAccelDecelLinear driftOffset[2]; + idInterpolateAccelDecelLinear driftSpeed; + float driftOffsetMax; + float driftSpeedMax; + float driftTime; +}; + +/* +=============================================================================== + +rvSpawnerProjectile + +=============================================================================== +*/ + +class rvSpawner; +class rvSpawnerProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvSpawnerProjectile ); + + rvSpawnerProjectile ( void ); + ~rvSpawnerProjectile ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + void SetSpawner ( rvSpawner* spawner ); + +protected: + + idEntityPtr spawner; + + enum { + STATE_NONE, + STATE_ADDED, + } spawnState; + +private: + + void Event_PostSpawn ( void ); +}; + +/* +=============================================================================== + +rvMIRVProjectile + +=============================================================================== +*/ + +class rvMIRVProjectile : public idProjectile { + CLASS_PROTOTYPE( rvMIRVProjectile ); + + rvMIRVProjectile ( void ); + ~rvMIRVProjectile ( void ); + + + void Spawn ( void ); + +private: + + void Event_LaunchWarheads ( void ); +}; + +#endif /* !__GAME_PROJECTILE_H__ */ + +// RAVEN END diff --git a/source/game/Pvs.cpp b/source/game/Pvs.cpp new file mode 100644 index 0000000..08c6ff3 --- /dev/null +++ b/source/game/Pvs.cpp @@ -0,0 +1,1439 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define MAX_BOUNDS_AREAS 16 + + +typedef struct pvsPassage_s { + byte * canSee; // bit set for all portals that can be seen through this passage +} pvsPassage_t; + + +typedef struct pvsPortal_s { + int areaNum; // area this portal leads to + idWinding * w; // winding goes counter clockwise seen from the area this portal is part of + idBounds bounds; // winding bounds + idPlane plane; // winding plane, normal points towards the area this portal leads to + pvsPassage_t * passages; // passages to portals in the area this portal leads to + bool done; // true if pvs is calculated for this portal + byte * vis; // PVS for this portal + byte * mightSee; // used during construction +} pvsPortal_t; + + +typedef struct pvsArea_s { + int numPortals; // number of portals in this area + idBounds bounds; // bounds of the whole area + pvsPortal_t ** portals; // array with pointers to the portals of this area +} pvsArea_t; + + +typedef struct pvsStack_s { + struct pvsStack_s * next; // next stack entry + byte * mightSee; // bit set for all portals that might be visible through this passage/portal stack +} pvsStack_t; + + +/* +================ +idPVS::idPVS +================ +*/ +idPVS::idPVS( void ) { + int i; + + numAreas = 0; + numPortals = 0; + + connectedAreas = NULL; + areaQueue = NULL; + areaPVS = NULL; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = NULL; + } + + pvsAreas = NULL; + pvsPortals = NULL; +} + +/* +================ +idPVS::~idPVS +================ +*/ +idPVS::~idPVS( void ) { + Shutdown(); +} + +/* +================ +idPVS::GetPortalCount +================ +*/ +int idPVS::GetPortalCount( void ) const { + int i, na, np; + + na = gameRenderWorld->NumAreas(); + np = 0; + for ( i = 0; i < na; i++ ) { + np += gameRenderWorld->NumPortalsInArea( i ); + } + return np; +} + +/* +================ +idPVS::CreatePVSData +================ +*/ +void idPVS::CreatePVSData( void ) { + int i, j, n, cp; + exitPortal_t portal; + pvsArea_t *area; + pvsPortal_t *p, **portalPtrs; + + if ( !numPortals ) { + return; + } + + pvsPortals = new pvsPortal_t[numPortals]; + pvsAreas = new pvsArea_t[numAreas]; + memset( pvsAreas, 0, numAreas * sizeof( *pvsAreas ) ); + + cp = 0; + portalPtrs = new pvsPortal_t*[numPortals]; + + for ( i = 0; i < numAreas; i++ ) { + + area = &pvsAreas[i]; + area->bounds.Clear(); + area->portals = portalPtrs + cp; + + n = gameRenderWorld->NumPortalsInArea( i ); + + for ( j = 0; j < n; j++ ) { + + portal = gameRenderWorld->GetPortal( i, j ); + + p = &pvsPortals[cp++]; + // the winding goes counter clockwise seen from this area + p->w = portal.w->Copy(); + p->areaNum = portal.areas[1]; // area[1] is always the area the portal leads to + + p->vis = new byte[portalVisBytes]; + memset( p->vis, 0, portalVisBytes ); + p->mightSee = new byte[portalVisBytes]; + memset( p->mightSee, 0, portalVisBytes ); + p->w->GetBounds( p->bounds ); + p->w->GetPlane( p->plane ); + // plane normal points to outside the area + p->plane = -p->plane; + // no PVS calculated for this portal yet + p->done = false; + + area->portals[area->numPortals] = p; + area->numPortals++; + + area->bounds += p->bounds; + } + } +} + +/* +================ +idPVS::DestroyPVSData +================ +*/ +void idPVS::DestroyPVSData( void ) { + int i; + + if ( !pvsAreas ) { + return; + } + + // delete portal pointer array + delete[] pvsAreas[0].portals; + + // delete all areas + delete[] pvsAreas; + pvsAreas = NULL; + + // delete portal data + for ( i = 0; i < numPortals; i++ ) { + delete[] pvsPortals[i].vis; + delete[] pvsPortals[i].mightSee; + delete pvsPortals[i].w; + } + + // delete portals + delete[] pvsPortals; + pvsPortals = NULL; +} + +/* +================ +idPVS::FloodFrontPortalPVS_r +================ +*/ +void idPVS::FloodFrontPortalPVS_r( pvsPortal_t *portal, int areaNum ) const { + int i, n; + pvsArea_t *area; + pvsPortal_t *p; + + area = &pvsAreas[ areaNum ]; + + for ( i = 0; i < area->numPortals; i++ ) { + p = area->portals[i]; + n = p - pvsPortals; + // don't flood through if this portal is not at the front + if ( !( portal->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + continue; + } + // don't flood through if already visited this portal + if ( portal->vis[ n>>3 ] & (1 << (n&7)) ) { + continue; + } + // this portal might be visible + portal->vis[ n>>3 ] |= (1 << (n&7)); + // flood through the portal + FloodFrontPortalPVS_r( portal, p->areaNum ); + } +} + +/* +================ +idPVS::FrontPortalPVS +================ +*/ +void idPVS::FrontPortalPVS( void ) const { + int i, j, k, n, p, side1, side2, areaSide; + pvsPortal_t *p1, *p2; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + + for ( j = 0; j < numAreas; j++ ) { + + area = &pvsAreas[j]; + + areaSide = side1 = area->bounds.PlaneSide( p1->plane ); + + // if the whole area is at the back side of the portal + if ( areaSide == PLANESIDE_BACK ) { + continue; + } + + for ( p = 0; p < area->numPortals; p++ ) { + + p2 = area->portals[p]; + + // if we the whole area is not at the front we need to check + if ( areaSide != PLANESIDE_FRONT ) { + // if the second portal is completely at the back side of the first portal + side1 = p2->bounds.PlaneSide( p1->plane ); + if ( side1 == PLANESIDE_BACK ) { + continue; + } + } + + // if the first portal is completely at the front of the second portal + side2 = p1->bounds.PlaneSide( p2->plane ); + if ( side2 == PLANESIDE_FRONT ) { + continue; + } + + // if the second portal is not completely at the front of the first portal + if ( side1 != PLANESIDE_FRONT ) { + // more accurate check + for ( k = 0; k < p2->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the front side + if ( p1->plane.Side( (*p2->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_FRONT ) { + break; + } + } + if ( k >= p2->w->GetNumPoints() ) { + continue; // second portal is at the back of the first portal + } + } + + // if the first portal is not completely at the back side of the second portal + if ( side2 != PLANESIDE_BACK ) { + // more accurate check + for ( k = 0; k < p1->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the back side + if ( p2->plane.Side( (*p1->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_BACK ) { + break; + } + } + if ( k >= p1->w->GetNumPoints() ) { + continue; // first portal is at the front of the second portal + } + } + + // the portal might be visible at the front + n = p2 - pvsPortals; + p1->mightSee[ n >> 3 ] |= 1 << (n&7); + } + } + } + + // flood the front portal pvs for all portals + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + FloodFrontPortalPVS_r( p1, p1->areaNum ); + } +} + +/* +=============== +idPVS::FloodPassagePVS_r +=============== +*/ +pvsStack_t *idPVS::FloodPassagePVS_r( pvsPortal_t *source, const pvsPortal_t *portal, pvsStack_t *prevStack ) const { + int i, j, n, m; + pvsPortal_t *p; + pvsArea_t *area; + pvsStack_t *stack; + pvsPassage_t *passage; + long *sourceVis, *passageVis, *portalVis, *mightSee, *prevMightSee, more; + + area = &pvsAreas[portal->areaNum]; + + stack = prevStack->next; + // if no next stack entry allocated + if ( !stack ) { + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + prevStack->next = stack; + } + + // check all portals for flooding into other areas + for ( i = 0; i < area->numPortals; i++ ) { + + passage = &portal->passages[i]; + + // if this passage is completely empty + if ( !passage->canSee ) { + continue; + } + + p = area->portals[i]; + n = p - pvsPortals; + + // if this portal cannot be seen through our current portal/passage stack + if ( !( prevStack->mightSee[n >> 3] & (1 << (n & 7)) ) ) { + continue; + } + + // mark the portal as visible + source->vis[n >> 3] |= (1 << (n & 7)); + + // get pointers to vis data + prevMightSee = reinterpret_cast(prevStack->mightSee); + passageVis = reinterpret_cast(passage->canSee); + sourceVis = reinterpret_cast(source->vis); + mightSee = reinterpret_cast(stack->mightSee); + + more = 0; + // use the portal PVS if it has been calculated + if ( p->done ) { + portalVis = reinterpret_cast(p->vis); + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++ & *portalVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + else { + // the p->mightSee is implicitely stored in the passageVis + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + + // if nothing more can be seen + if ( !more ) { + continue; + } + + // go through the portal + stack->next = FloodPassagePVS_r( source, p, stack ); + } + + return stack; +} + +/* +=============== +idPVS::PassagePVS +=============== +*/ +void idPVS::PassagePVS( void ) const { + int i; + pvsPortal_t *source; + pvsStack_t *stack, *s; + + // create the passages + CreatePassages(); + + // allocate first stack entry + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + + // calculate portal PVS by flooding through the passages + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + memset( source->vis, 0, portalVisBytes ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( stack->mightSee, source->mightSee, portalVisBytes ); +// RAVEN END + FloodPassagePVS_r( source, source, stack ); + source->done = true; + } + + // free the allocated stack + for ( s = stack; s; s = stack ) { + stack = stack->next; + delete[] s; + } + + // destroy the passages + DestroyPassages(); +} + +/* +=============== +idPVS::AddPassageBoundaries +=============== +*/ +void idPVS::AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const { + int i, j, k, l; + idVec3 v1, v2, normal; + float d, dist; + bool flipTest, front; + idPlane plane; + + + // check all combinations + for ( i = 0; i < source.GetNumPoints(); i++ ) { + + l = (i + 1) % source.GetNumPoints(); + v1 = source[l].ToVec3() - source[i].ToVec3(); + + // find a vertex of pass that makes a plane that puts all of the + // vertices of pass on the front side and all of the vertices of + // source on the back side + for ( j = 0; j < pass.GetNumPoints(); j++ ) { + + v2 = pass[j].ToVec3() - source[i].ToVec3(); + + normal = v1.Cross( v2 ); + if ( normal.Normalize() < 0.01f ) { + continue; + } + dist = normal * pass[j].ToVec3(); + + // + // find out which side of the generated seperating plane has the + // source portal + // + flipTest = false; + for ( k = 0; k < source.GetNumPoints(); k++ ) { + if ( k == i || k == l ) { + continue; + } + d = source[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + // source is on the negative side, so we want all + // pass and target on the positive side + flipTest = false; + break; + } + else if ( d > ON_EPSILON ) { + // source is on the positive side, so we want all + // pass and target on the negative side + flipTest = true; + break; + } + } + if ( k == source.GetNumPoints() ) { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (flipTest) { + normal = -normal; + dist = -dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + front = false; + for ( k = 0; k < pass.GetNumPoints(); k++ ) { + if ( k == j ) { + continue; + } + d = pass[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + break; + } + else if ( d > ON_EPSILON ) { + front = true; + } + } + if ( k < pass.GetNumPoints() ) { + continue; // points on negative side, not a seperating plane + } + if ( !front ) { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if ( flipClip ) { + plane.SetNormal( -normal ); + plane.SetDist( -dist ); + } + else { + plane.SetNormal( normal ); + plane.SetDist( dist ); + } + + // check if the plane is already a passage boundary + for ( k = 0; k < numBounds; k++ ) { + if ( plane.Compare( bounds[k], 0.001f, 0.01f ) ) { + break; + } + } + if ( k < numBounds ) { + break; + } + + if ( numBounds >= maxBounds ) { + gameLocal.Warning( "max passage boundaries." ); + break; + } + bounds[numBounds] = plane; + numBounds++; + break; + } + } +} + +/* +================ +idPVS::CreatePassages +================ +*/ +#define MAX_PASSAGE_BOUNDS 128 + +void idPVS::CreatePassages( void ) const { + int i, j, l, n, numBounds, front, passageMemory, byteNum, bitNum; + int sides[MAX_PASSAGE_BOUNDS]; + idPlane passageBounds[MAX_PASSAGE_BOUNDS]; + pvsPortal_t *source, *target, *p; + pvsArea_t *area; + pvsPassage_t *passage; + idFixedWinding winding; + byte canSee, mightSee, bit; + + passageMemory = 0; + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + area = &pvsAreas[source->areaNum]; + + source->passages = new pvsPassage_t[area->numPortals]; + + for ( j = 0; j < area->numPortals; j++ ) { + target = area->portals[j]; + n = target - pvsPortals; + + passage = &source->passages[j]; + + // if the source portal cannot see this portal + if ( !( source->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + // not all portals in the area have to be visible because areas are not necesarily convex + // also no passage has to be created for the portal which is the opposite of the source + passage->canSee = NULL; + continue; + } + + passage->canSee = new byte[portalVisBytes]; + passageMemory += portalVisBytes; + + // boundary plane normals point inwards + numBounds = 0; + AddPassageBoundaries( *(source->w), *(target->w), false, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + AddPassageBoundaries( *(target->w), *(source->w), true, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + + // get all portals visible through this passage + for ( byteNum = 0; byteNum < portalVisBytes; byteNum++) { + + canSee = 0; + mightSee = source->mightSee[byteNum] & target->mightSee[byteNum]; + + // go through eight portals at a time to speed things up + for ( bitNum = 0; bitNum < 8; bitNum++ ) { + + bit = 1 << bitNum; + + if ( !( mightSee & bit ) ) { + continue; + } + + p = &pvsPortals[(byteNum << 3) + bitNum]; + + if ( p->areaNum == source->areaNum ) { + continue; + } + + for ( front = 0, l = 0; l < numBounds; l++ ) { + sides[l] = p->bounds.PlaneSide( passageBounds[l] ); + // if completely at the back of the passage bounding plane + if ( sides[l] == PLANESIDE_BACK ) { + break; + } + // if completely at the front + if ( sides[l] == PLANESIDE_FRONT ) { + front++; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + + // if not at the front of all bounding planes and thus not completely inside the passage + if ( front != numBounds ) { + + winding = *p->w; + + for ( l = 0; l < numBounds; l++ ) { + // only clip if the winding possibly crosses this plane + if ( sides[l] != PLANESIDE_CROSS ) { + continue; + } + // clip away the part at the back of the bounding plane + winding.ClipInPlace( passageBounds[l] ); + // if completely clipped away + if ( !winding.GetNumPoints() ) { + break; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + } + + canSee |= bit; + } + + // store results of all eight portals + passage->canSee[byteNum] = canSee; + } + + // can always see the target portal + passage->canSee[n >> 3] |= (1 << (n&7)); + } + } + if ( passageMemory < 1024 ) { + gameLocal.Printf( "%5d bytes passage memory used to build PVS\n", passageMemory ); + } + else { + gameLocal.Printf( "%5d KB passage memory used to build PVS\n", passageMemory>>10 ); + } +} + +/* +================ +idPVS::DestroyPassages +================ +*/ +void idPVS::DestroyPassages( void ) const { + int i, j; + pvsPortal_t *p; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + area = &pvsAreas[p->areaNum]; + for ( j = 0; j < area->numPortals; j++ ) { + if ( p->passages[j].canSee ) { + delete[] p->passages[j].canSee; + } + } + delete[] p->passages; + } +} + +/* +================ +idPVS::CopyPortalPVSToMightSee +================ +*/ +void idPVS::CopyPortalPVSToMightSee( void ) const { + int i; + pvsPortal_t *p; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( p->mightSee, p->vis, portalVisBytes ); +// RAVEN BEGIN + } +} + +/* +================ +idPVS::AreaPVSFromPortalPVS +================ +*/ +int idPVS::AreaPVSFromPortalPVS( void ) const { + int i, j, k, areaNum, totalVisibleAreas; + long *p1, *p2; + byte *pvs, *portalPVS; + pvsArea_t *area; + + totalVisibleAreas = 0; + + if ( !numPortals ) { + return totalVisibleAreas; + } + + memset( areaPVS, 0, numAreas * areaVisBytes ); + + for ( i = 0; i < numAreas; i++ ) { + area = &pvsAreas[i]; + pvs = areaPVS + i * areaVisBytes; + + // the area is visible to itself + pvs[ i >> 3 ] |= 1 << (i & 7); + + if ( !area->numPortals ) { + continue; + } + + // store the PVS of all portals in this area at the first portal + for ( j = 1; j < area->numPortals; j++ ) { + p1 = reinterpret_cast(area->portals[0]->vis); + p2 = reinterpret_cast(area->portals[j]->vis); + for ( k = 0; k < portalVisLongs; k++ ) { + *p1++ |= *p2++; + } + } + + // the portals of this area are always visible + for ( j = 0; j < area->numPortals; j++ ) { + k = area->portals[j] - pvsPortals; + area->portals[0]->vis[ k >> 3 ] |= 1 << (k & 7); + } + + // set all areas to visible that can be seen from the portals of this area + portalPVS = area->portals[0]->vis; + for ( j = 0; j < numPortals; j++ ) { + // if this portal is visible + if ( portalPVS[j>>3] & (1 << (j&7)) ) { + areaNum = pvsPortals[j].areaNum; + pvs[ areaNum >> 3 ] |= 1 << (areaNum & 7); + } + } + + // count the number of visible areas + for ( j = 0; j < numAreas; j++ ) { + if ( pvs[j>>3] & (1 << (j&7)) ) { + totalVisibleAreas++; + } + } + } + return totalVisibleAreas; +} + +/* +================ +idPVS::Init +================ +*/ +void idPVS::Init( void ) { + int totalVisibleAreas; + + Shutdown(); + + numAreas = gameRenderWorld->NumAreas(); + if ( numAreas <= 0 ) { + return; + } + + connectedAreas = new bool[numAreas]; + areaQueue = new int[numAreas]; + + areaVisBytes = ( ((numAreas+31)&~31) >> 3); + areaVisLongs = areaVisBytes/sizeof(long); + areaPVS = new byte[numAreas * areaVisBytes]; + memset( areaPVS, 0xFF, numAreas * areaVisBytes ); + + numPortals = GetPortalCount(); + + portalVisBytes = ( ((numPortals+31)&~31) >> 3); + portalVisLongs = portalVisBytes/sizeof(long); + + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = new byte[areaVisBytes]; + memset( currentPVS[i].pvs, 0, areaVisBytes ); + } + + idTimer timer; + timer.Start(); + + CreatePVSData(); + + FrontPortalPVS(); + + CopyPortalPVSToMightSee(); + + PassagePVS(); + + totalVisibleAreas = AreaPVSFromPortalPVS(); + + DestroyPVSData(); + + timer.Stop(); + +#if 0 + // bkreimeier20060123 - per MrE's recommendation, used this in Wolf + // if the PVS is not relexive for (i,j), we assume floating point errors + // and assume a false positive - remove either from either + for ( int i=0; i>3] & (1<<(j&7))); + const bool i_in_j = (pvs_j[i>>3] & (1<<(i&7))); + if ( i_in_j!=j_in_i ) { + pvs_i[j>>3] &= ~(1 << (j&7)); + pvs_j[i>>3] &= ~(1 << (i&7)); + } + } + } +#endif + + gameLocal.Printf( "%5.0f msec to calculate PVS\n", timer.Milliseconds() ); + gameLocal.Printf( "%5d areas\n", numAreas ); + gameLocal.Printf( "%5d portals\n", numPortals ); + +// RAVEN BEGIN +// rjohnson: fix for div by 0 + if( numAreas ) { + gameLocal.Printf( "%5d areas visible on average\n", totalVisibleAreas / numAreas ); + } +// RAVEN END + + if ( numAreas * areaVisBytes < 1024 ) { + gameLocal.Printf( "%5d bytes PVS data\n", numAreas * areaVisBytes ); + } + else { + gameLocal.Printf( "%5d KB PVS data\n", (numAreas * areaVisBytes) >> 10 ); + } +} + +/* +================ +idPVS::Shutdown +================ +*/ +void idPVS::Shutdown( void ) { + if ( connectedAreas ) { + delete connectedAreas; + connectedAreas = NULL; + } + if ( areaQueue ) { + delete areaQueue; + areaQueue = NULL; + } + if ( areaPVS ) { + delete areaPVS; + areaPVS = NULL; + } + if ( currentPVS ) { + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { +// RAVEN BEGIN +// jsinger: modified to check to make sure the pointers have a value before attempting to delete +// them. This prevents a call through the unified allocator before it has been initialized + if(currentPVS[i].pvs) + { + delete currentPVS[i].pvs; + } +// RAVEN END + currentPVS[i].pvs = NULL; + } + } +} + +/* +================ +idPVS::GetConnectedAreas + + assumes the 'areas' array is initialized to false +================ +*/ +void idPVS::GetConnectedAreas( int srcArea, bool *areas ) const { + int curArea, nextArea; + int queueStart, queueEnd; + int i, n; + exitPortal_t portal; + + queueStart = -1; + queueEnd = 0; + areas[srcArea] = true; + + for ( curArea = srcArea; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + n = gameRenderWorld->NumPortalsInArea( curArea ); + + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( curArea, i ); + + if ( portal.blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // area[1] is always the area the portal leads to + nextArea = portal.areas[1]; + + // if already visited this area + if ( areas[nextArea] ) { + continue; + } + + // add area to queue + areaQueue[queueEnd++] = nextArea; + areas[nextArea] = true; + } + } +} + +/* +================ +idPVS::GetPVSArea +================ +*/ +int idPVS::GetPVSArea( const idVec3 &point ) const { + return gameRenderWorld->PointInArea( point ); +} + +/* +================ +idPVS::GetPVSAreas +================ +*/ +int idPVS::GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + return gameRenderWorld->BoundsInAreas( bounds, areas, maxAreas ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idVec3 &source, const pvsType_t type ) const { + int sourceArea; + + sourceArea = gameRenderWorld->PointInArea( source ); + + return SetupCurrentPVS( sourceArea, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idBounds &source, const pvsType_t type ) const { + int numSourceAreas, sourceAreas[MAX_BOUNDS_AREAS]; + + numSourceAreas = gameRenderWorld->BoundsInAreas( source, sourceAreas, MAX_BOUNDS_AREAS ); + + return SetupCurrentPVS( sourceAreas, numSourceAreas, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int sourceArea, const pvsType_t type ) const { + int i; + pvsHandle_t handle; + + handle = AllocCurrentPVS( *reinterpret_cast(&sourceArea) ); + + if ( sourceArea < 0 || sourceArea >= numAreas ) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceArea * areaVisBytes, areaVisBytes ); +// RAVEN END + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + GetConnectedAreas( sourceArea, connectedAreas ); + + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type ) const { + int i, j; + unsigned int h; + long *vis, *pvs; + pvsHandle_t handle; + + h = 0; + for ( i = 0; i < numSourceAreas; i++ ) { + h ^= *reinterpret_cast(&sourceAreas[i]); + } + handle = AllocCurrentPVS( h ); + + if ( !numSourceAreas || sourceAreas[0] < 0 || sourceAreas[0] >= numAreas) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + // merge PVS of all areas the source is in +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceAreas[0] * areaVisBytes, areaVisBytes ); +// RAVEN END + for ( i = 1; i < numSourceAreas; i++ ) { + + assert( sourceAreas[i] >= 0 && sourceAreas[i] < numAreas ); + + vis = reinterpret_cast(areaPVS + sourceAreas[i] * areaVisBytes); + pvs = reinterpret_cast(currentPVS[handle.i].pvs); + for ( j = 0; j < areaVisLongs; j++ ) { + *pvs++ |= *vis++; + } + } + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + // get all areas connected to any of the source areas + for ( i = 0; i < numSourceAreas; i++ ) { + if ( !connectedAreas[sourceAreas[i]] ) { + GetConnectedAreas( sourceAreas[i], connectedAreas ); + } + } + + // remove unconnected areas from the PVS + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::MergeCurrentPVS +================ +*/ +pvsHandle_t idPVS::MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const { + int i; + long *pvs1Ptr, *pvs2Ptr, *ptr; + pvsHandle_t handle; + + if ( pvs1.i < 0 || pvs1.i >= MAX_CURRENT_PVS || pvs1.h != currentPVS[pvs1.i].handle.h || + pvs2.i < 0 || pvs2.i >= MAX_CURRENT_PVS || pvs2.h != currentPVS[pvs2.i].handle.h ) { + gameLocal.Error( "idPVS::MergeCurrentPVS: invalid handle" ); + } + + handle = AllocCurrentPVS( pvs1.h ^ pvs2.h ); + + ptr = reinterpret_cast(currentPVS[handle.i].pvs); + pvs1Ptr = reinterpret_cast(currentPVS[pvs1.i].pvs); + pvs2Ptr = reinterpret_cast(currentPVS[pvs2.i].pvs); + + for ( i = 0; i < areaVisLongs; i++ ) { + *ptr++ = *pvs1Ptr++ | *pvs2Ptr++; + } + + return handle; +} + +/* +================ +idPVS::AllocCurrentPVS +================ +*/ +pvsHandle_t idPVS::AllocCurrentPVS( unsigned int h ) const { + int i; + pvsHandle_t handle; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + if ( currentPVS[i].handle.i == -1 ) { + currentPVS[i].handle.i = i; + currentPVS[i].handle.h = h; + return currentPVS[i].handle; + } + } + + gameLocal.Error( "idPVS::AllocCurrentPVS: no free PVS left" ); + + handle.i = -1; + handle.h = 0; + return handle; +} + +/* +================ +idPVS::FreeCurrentPVS +================ +*/ +void idPVS::FreeCurrentPVS( pvsHandle_t handle ) const { + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::FreeCurrentPVS: invalid handle" ); + } + currentPVS[handle.i].handle.i = -1; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const { + int targetArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + targetArea = gameRenderWorld->PointInArea( target ); + + if ( targetArea == -1 ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const { + int i, numTargetAreas, targetAreas[MAX_BOUNDS_AREAS]; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + numTargetAreas = gameRenderWorld->BoundsInAreas( target, targetAreas, MAX_BOUNDS_AREAS ); + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const { + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + if ( targetArea < 0 || targetArea >= numAreas ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const { + int i; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( targetAreas[i] < 0 || targetAreas[i] >= numAreas ) { + continue; + } + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idVec3 &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idBounds &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, num, areas[MAX_BOUNDS_AREAS]; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + num = gameRenderWorld->BoundsInAreas( source, areas, MAX_BOUNDS_AREAS ); + + if ( !num ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + for ( i = 0; i < num; i++ ) { + if ( j == areas[i] ) { + break; + } + } + if ( i < num ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::DrawCurrentPVS: invalid handle" ); + } + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } +} + +#if ASYNC_WRITE_PVS + +/* +=================== +idPVS::WritePVS +=================== +*/ +void idPVS::WritePVS( const pvsHandle_t handle, idBitMsg &msg ) { + msg.WriteData( currentPVS[ handle.i ].pvs, areaVisBytes ); +} + +/* +=================== +idPVS::ReadPVS +=================== +*/ +void idPVS::ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ) { + byte l_pvs[ 256 ]; + int i; + + assert( areaVisBytes <= 256 ); + msg.ReadData( l_pvs, areaVisBytes ); + if ( memcmp( l_pvs, currentPVS[ handle.i ].pvs, areaVisBytes ) ) { + common->Printf( "PVS not matching ( %d areaVisBytes ) - server then client:\n", areaVisBytes ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", l_pvs[ i ] ); + } + common->Printf( "\n" ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", currentPVS[ handle.i ].pvs[ i ] ); + } + common->Printf( "\n" ); + } +} + +#endif + diff --git a/source/game/Pvs.h b/source/game/Pvs.h new file mode 100644 index 0000000..3f84689 --- /dev/null +++ b/source/game/Pvs.h @@ -0,0 +1,104 @@ + +#ifndef __GAME_PVS_H__ +#define __GAME_PVS_H__ + +/* +=================================================================================== + + PVS + + Note: mirrors and other special view portals are not taken into account + +=================================================================================== +*/ + + +typedef struct pvsHandle_s { + int i; // index to current pvs + unsigned int h; // handle for current pvs +} pvsHandle_t; + +typedef struct pvsCurrent_s { + pvsHandle_t handle; // current pvs handle + byte * pvs; // current pvs bit string +} pvsCurrent_t; + +// must be a power of 2 +// base was 8, MP now stores client PVS from frame to frame ( MAX_CLIENTS ) +#define MAX_CURRENT_PVS 64 + +typedef enum { + PVS_NORMAL = 0, // PVS through portals taking portal states into account + PVS_ALL_PORTALS_OPEN = 1, // PVS through portals assuming all portals are open + PVS_CONNECTED_AREAS = 2 // PVS considering all topologically connected areas visible +} pvsType_t; + + +class idPVS { +public: + idPVS( void ); + ~idPVS( void ); + // setup for the current map + void Init( void ); + void Shutdown( void ); + // get the area(s) the source is in + int GetPVSArea( const idVec3 &point ) const; // returns the area number + int GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const; // returns number of areas + // setup current PVS for the source + pvsHandle_t SetupCurrentPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int sourceArea, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const; + void FreeCurrentPVS( pvsHandle_t handle ) const; + // returns true if the target is within the current PVS + bool InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const; + // draw all portals that are within the PVS of the source + void DrawPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + void DrawPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + // visualize the PVS the handle points to + void DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const; + +#if ASYNC_WRITE_PVS + void WritePVS( const pvsHandle_t handle, idBitMsg &msg ); + void ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ); +#endif + +private: + int numAreas; + int numPortals; + bool * connectedAreas; + int * areaQueue; + byte * areaPVS; + + // current PVS for a specific source possibly taking portal states (open/closed) into account + mutable pvsCurrent_t currentPVS[MAX_CURRENT_PVS]; + // used to create PVS + int portalVisBytes; + int portalVisLongs; + int areaVisBytes; + int areaVisLongs; + struct pvsPortal_s *pvsPortals; + struct pvsArea_s * pvsAreas; + +private: + int GetPortalCount( void ) const; + void CreatePVSData( void ); + void DestroyPVSData( void ); + void CopyPortalPVSToMightSee( void ) const; + void FloodFrontPortalPVS_r( struct pvsPortal_s *portal, int areaNum ) const; + void FrontPortalPVS( void ) const; + struct pvsStack_s * FloodPassagePVS_r( struct pvsPortal_s *source, const struct pvsPortal_s *portal, struct pvsStack_s *prevStack ) const; + void PassagePVS( void ) const; + void AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const; + void CreatePassages( void ) const; + void DestroyPassages( void ) const; + int AreaPVSFromPortalPVS( void ) const; + void GetConnectedAreas( int srcArea, bool *connectedAreas ) const; + pvsHandle_t AllocCurrentPVS( unsigned int h ) const; +}; + +#endif /* !__GAME_PVS_H__ */ diff --git a/source/game/SecurityCamera.cpp b/source/game/SecurityCamera.cpp new file mode 100644 index 0000000..ad9c047 --- /dev/null +++ b/source/game/SecurityCamera.cpp @@ -0,0 +1,578 @@ +/* + + SecurityCamera.cpp + + Security camera that triggers targets when player is in view + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idSecurityCamera + +***********************************************************************/ + +const idEventDef EV_SecurityCam_ReverseSweep( "" ); +const idEventDef EV_SecurityCam_ContinueSweep( "" ); +const idEventDef EV_SecurityCam_Pause( "" ); +const idEventDef EV_SecurityCam_Alert( "" ); +const idEventDef EV_SecurityCam_AddLight( "" ); + +CLASS_DECLARATION( idEntity, idSecurityCamera ) + EVENT( EV_SecurityCam_ReverseSweep, idSecurityCamera::Event_ReverseSweep ) + EVENT( EV_SecurityCam_ContinueSweep, idSecurityCamera::Event_ContinueSweep ) + EVENT( EV_SecurityCam_Pause, idSecurityCamera::Event_Pause ) + EVENT( EV_SecurityCam_Alert, idSecurityCamera::Event_Alert ) + EVENT( EV_SecurityCam_AddLight, idSecurityCamera::Event_AddLight ) +END_CLASS + + +idSecurityCamera::~idSecurityCamera() { + SetPhysics( NULL ); +} + +/* +================ +idSecurityCamera::Save +================ +*/ +void idSecurityCamera::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( angle ); + savefile->WriteFloat( sweepAngle ); + savefile->WriteInt( modelAxis ); + savefile->WriteBool( flipAxis ); + savefile->WriteFloat( scanDist ); + savefile->WriteFloat( scanFov ); + + savefile->WriteFloat( sweepStart ); + savefile->WriteFloat( sweepEnd ); + savefile->WriteBool( negativeSweep ); + savefile->WriteBool( sweeping ); + savefile->WriteInt( alertMode ); + savefile->WriteFloat( stopSweeping ); + savefile->WriteFloat( scanFovCos ); + + savefile->WriteVec3( viewOffset ); + + savefile->WriteInt( pvsArea ); + savefile->WriteStaticObject( physicsObj ); + savefile->Write( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Restore +================ +*/ +void idSecurityCamera::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( angle ); + savefile->ReadFloat( sweepAngle ); + savefile->ReadInt( modelAxis ); + savefile->ReadBool( flipAxis ); + savefile->ReadFloat( scanDist ); + savefile->ReadFloat( scanFov ); + + savefile->ReadFloat( sweepStart ); + savefile->ReadFloat( sweepEnd ); + savefile->ReadBool( negativeSweep ); + savefile->ReadBool( sweeping ); + savefile->ReadInt( alertMode ); + savefile->ReadFloat( stopSweeping ); + savefile->ReadFloat( scanFovCos ); + + savefile->ReadVec3( viewOffset ); + + savefile->ReadInt( pvsArea ); + savefile->ReadStaticObject( physicsObj ); + savefile->Read( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Spawn +================ +*/ +void idSecurityCamera::Spawn( void ) { + idStr str; + + sweepAngle = spawnArgs.GetFloat( "sweepAngle", "90" ); + health = spawnArgs.GetInt( "health", "100" ); + scanFov = spawnArgs.GetFloat( "scanFov", "90" ); + scanDist = spawnArgs.GetFloat( "scanDist", "200" ); + flipAxis = spawnArgs.GetBool( "flipAxis" ); + + modelAxis = spawnArgs.GetInt( "modelAxis" ); + if ( modelAxis < 0 || modelAxis > 2 ) { + modelAxis = 0; + } + + spawnArgs.GetVector( "viewOffset", "0 0 0", viewOffset ); + + if ( spawnArgs.GetBool( "spotLight" ) ) { + PostEventMS( &EV_SecurityCam_AddLight, 0 ); + } + + negativeSweep = ( sweepAngle < 0 ) ? true : false; + sweepAngle = abs( sweepAngle ); + + scanFovCos = idMath::Cos( scanFov * idMath::PI / 360.0f ); + + angle = GetPhysics()->GetAxis().ToAngles().yaw; + StartSweep(); + SetAlertMode( SCANNING ); + BecomeActive( TH_THINK ); + + if ( health ) { + fl.takedamage = true; + } + + pvsArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + // if no target specified use ourself + str = spawnArgs.GetString( "cameraTarget" ); + if ( str.Length() == 0 ) { + spawnArgs.Set( "cameraTarget", spawnArgs.GetString( "name" ) ); + } + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", str ); + if ( !str[0] ) { + str = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), str, trm ) ) { + gameLocal.Error( "idSecurityCamera '%s': cannot load collision model %s", name.c_str(), str.c_str() ); + return; + } + + GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + // setup the physics + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idSecurityCamera::Event_AddLight +================ +*/ +void idSecurityCamera::Event_AddLight( void ) { + idDict args; + idVec3 right, up, target, temp; + idVec3 dir; + float radius; + idVec3 lightOffset; + idLight *spotLight; + + dir = GetAxis(); + dir.NormalVectors( right, up ); + target = GetPhysics()->GetOrigin() + dir * scanDist; + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + up = dir + up * radius; + up.Normalize(); + up = GetPhysics()->GetOrigin() + up * scanDist; + up -= target; + + right = dir + right * radius; + right.Normalize(); + right = GetPhysics()->GetOrigin() + right * scanDist; + right -= target; + + spawnArgs.GetVector( "lightOffset", "0 0 0", lightOffset ); + + args.Set( "origin", ( GetPhysics()->GetOrigin() + lightOffset ).ToString() ); + args.Set( "light_target", target.ToString() ); + args.Set( "light_right", right.ToString() ); + args.Set( "light_up", up.ToString() ); + args.SetFloat( "angle", GetPhysics()->GetAxis()[0].ToYaw() ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + spotLight = static_cast( gameLocal.SpawnEntityType( idLight::GetClassType(), &args ) ); +// RAVEN END + spotLight->Bind( this, true ); + spotLight->UpdateVisuals(); +} + +/* +================ +idSecurityCamera::DrawFov +================ +*/ +void idSecurityCamera::DrawFov( void ) { + int i; + float radius, a, s, c, halfRadius; + idVec3 right, up; + idVec4 color(1, 0, 0, 1), color2(0, 0, 1, 1); + idVec3 lastPoint, point, lastHalfPoint, halfPoint, center; + + idVec3 dir = GetAxis(); + dir.NormalVectors( right, up ); + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + halfRadius = radius * 0.5f; + lastPoint = dir + up * radius; + lastPoint.Normalize(); + lastPoint = GetPhysics()->GetOrigin() + lastPoint * scanDist; + lastHalfPoint = dir + up * halfRadius; + lastHalfPoint.Normalize(); + lastHalfPoint = GetPhysics()->GetOrigin() + lastHalfPoint * scanDist; + center = GetPhysics()->GetOrigin() + dir * scanDist; + for ( i = 1; i < 12; i++ ) { + a = idMath::TWO_PI * i / 12.0f; + idMath::SinCos( a, s, c ); + point = dir + right * s * radius + up * c * radius; + point.Normalize(); + point = GetPhysics()->GetOrigin() + point * scanDist; + gameRenderWorld->DebugLine( color, lastPoint, point ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), point ); + lastPoint = point; + + halfPoint = dir + right * s * halfRadius + up * c * halfRadius; + halfPoint.Normalize(); + halfPoint = GetPhysics()->GetOrigin() + halfPoint * scanDist; + gameRenderWorld->DebugLine( color2, point, halfPoint ); + gameRenderWorld->DebugLine( color2, lastHalfPoint, halfPoint ); + lastHalfPoint = halfPoint; + + gameRenderWorld->DebugLine( color2, halfPoint, center ); + } +} + +/* +================ +idSecurityCamera::GetRenderView +================ +*/ +renderView_t *idSecurityCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->fov_x = scanFov; + rv->fov_y = scanFov; + rv->viewaxis = GetAxis().ToAngles().ToMat3(); + rv->vieworg = GetPhysics()->GetOrigin() + viewOffset; + return rv; +} + +/* +================ +idSecurityCamera::CanSeePlayer +================ +*/ +bool idSecurityCamera::CanSeePlayer( void ) { + int i; + float dist; + idPlayer *ent; + trace_t tr; + idVec3 dir; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( pvsArea ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = static_cast(gameLocal.entities[ i ]); + + if ( !ent || ( ent->fl.notarget ) ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.pvs.InCurrentPVS( handle, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dist = dir.Normalize(); + + if ( dist > scanDist ) { + continue; + } + + if ( dir * GetAxis() < scanFovCos ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, GetPhysics()->GetOrigin(), ent->GetEyePosition(), MASK_OPAQUE, this ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return true; + } + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + return false; +} + +/* +================ +idSecurityCamera::SetAlertMode +================ +*/ +void idSecurityCamera::SetAlertMode( int alert ) { + if (alert >= SCANNING && alert <= ACTIVATED) { + alertMode = alert; + } + renderEntity.shaderParms[ SHADERPARM_MODE ] = alertMode; + UpdateVisuals(); +} + +/* +================ +idSecurityCamera::Think +================ +*/ +void idSecurityCamera::Think( void ) { + float pct; + float travel; + + if ( thinkFlags & TH_THINK ) { + if ( g_showEntityInfo.GetBool() ) { + DrawFov(); + } + + if (health <= 0) { + BecomeInactive( TH_THINK ); + return; + } + } + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + if (CanSeePlayer()) { + if (alertMode == SCANNING) { + float sightTime; + + SetAlertMode(ALERT); + stopSweeping = gameLocal.time; + if (sweeping) { + CancelEvents( &EV_SecurityCam_Pause ); + } else { + CancelEvents( &EV_SecurityCam_ReverseSweep ); + } + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_sight", SND_CHANNEL_BODY, 0, false, NULL ); + + sightTime = spawnArgs.GetFloat( "sightTime", "5" ); + PostEventSec(&EV_SecurityCam_Alert, sightTime); + } + } else { + if (alertMode == ALERT) { + float sightResume; + + SetAlertMode(LOSINGINTEREST); + CancelEvents( &EV_SecurityCam_Alert ); + + sightResume = spawnArgs.GetFloat( "sightResume", "1.5" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, sightResume ); + } + + if ( sweeping ) { + idAngles a = GetPhysics()->GetAxis().ToAngles(); + + pct = ( gameLocal.time - sweepStart ) / ( sweepEnd - sweepStart ); + travel = pct * sweepAngle; + if ( negativeSweep ) { + a.yaw = angle + travel; + } else { + a.yaw = angle - travel; + } + + SetAngles( a ); + } + } + } + Present(); +} + +/* +================ +idSecurityCamera::GetAxis +================ +*/ +const idVec3 idSecurityCamera::GetAxis( void ) const { + return (flipAxis) ? -GetPhysics()->GetAxis()[modelAxis] : GetPhysics()->GetAxis()[modelAxis]; +}; + +/* +================ +idSecurityCamera::SweepSpeed +================ +*/ +float idSecurityCamera::SweepSpeed( void ) const { + return spawnArgs.GetFloat( "sweepSpeed", "5" ); +} + +/* +================ +idSecurityCamera::StartSweep +================ +*/ +void idSecurityCamera::StartSweep( void ) { + int speed; + + sweeping = true; + sweepStart = gameLocal.time; + speed = SEC2MS( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed ); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idSecurityCamera::Event_ContinueSweep +================ +*/ +void idSecurityCamera::Event_ContinueSweep( void ) { + float pct = (stopSweeping - sweepStart) / (sweepEnd - sweepStart); + float f = gameLocal.time - (sweepEnd - sweepStart) * pct; + int speed; + + sweepStart = f; + speed = MS2SEC( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed * (1.0 - pct)); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); + SetAlertMode(SCANNING); + sweeping = true; +} + +/* +================ +idSecurityCamera::Event_Alert +================ +*/ +void idSecurityCamera::Event_Alert( void ) { + float wait; + + SetAlertMode(ACTIVATED); + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_activate", SND_CHANNEL_BODY, 0, false, NULL ); + ActivateTargets(this); + CancelEvents( &EV_SecurityCam_ContinueSweep ); + + wait = spawnArgs.GetFloat( "wait", "20" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, wait ); +} + +/* +================ +idSecurityCamera::Event_ReverseSweep +================ +*/ +void idSecurityCamera::Event_ReverseSweep( void ) { + angle = GetPhysics()->GetAxis().ToAngles().yaw; + negativeSweep = !negativeSweep; + StartSweep(); +} + +/* +================ +idSecurityCamera::Event_Pause +================ +*/ +void idSecurityCamera::Event_Pause( void ) { + float sweepWait; + + sweepWait = spawnArgs.GetFloat( "sweepWait", "0.5" ); + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_stop", SND_CHANNEL_BODY, 0, false, NULL ); + PostEventSec( &EV_SecurityCam_ReverseSweep, sweepWait ); +} + +/* +============ +idSecurityCamera::Killed +============ +*/ +void idSecurityCamera::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); +// RAVEN BEGIN +// bdube: replaced fx call with raven call + gameLocal.PlayEffect ( spawnArgs, "fx_destroyed", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +/* + const char *fx = spawnArgs.GetString( "fx_destroyed" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( trm ), 0.02f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( 0.2f ); + physicsObj.SetFriction( 0.6f, 0.6f, 0.2f ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + physicsObj.DropToFloor(); +} + + +/* +============ +idSecurityCamera::Pain +============ +*/ +bool idSecurityCamera::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +// RAVEN BEGIN +// bdube: replaced fx call with raven call + PlayEffect ( "fx_damage", renderEntity.origin, renderEntity.axis ); +/* + const char *fx = spawnArgs.GetString( "fx_damage" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + return true; +} + + +/* +================ +idSecurityCamera::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idSecurityCamera::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} diff --git a/source/game/SecurityCamera.h b/source/game/SecurityCamera.h new file mode 100644 index 0000000..ed8b19c --- /dev/null +++ b/source/game/SecurityCamera.h @@ -0,0 +1,71 @@ + +#ifndef __GAME_SECURITYCAMERA_H__ +#define __GAME_SECURITYCAMERA_H__ + +/* +=================================================================================== + + Security camera + +=================================================================================== +*/ + + +class idSecurityCamera : public idEntity { +public: + CLASS_PROTOTYPE( idSecurityCamera ); + + ~idSecurityCamera( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual renderView_t * GetRenderView(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Present( void ); + +private: + + enum { SCANNING, LOSINGINTEREST, ALERT, ACTIVATED }; + + float angle; + float sweepAngle; + int modelAxis; + bool flipAxis; + float scanDist; + float scanFov; + + float sweepStart; + float sweepEnd; + bool negativeSweep; + bool sweeping; + int alertMode; + float stopSweeping; + float scanFovCos; + + idVec3 viewOffset; + + int pvsArea; + idPhysics_RigidBody physicsObj; + idTraceModel trm; + + void StartSweep( void ); + bool CanSeePlayer( void ); + void SetAlertMode( int status ); + void DrawFov( void ); + const idVec3 GetAxis( void ) const; + float SweepSpeed( void ) const; + + void Event_ReverseSweep( void ); + void Event_ContinueSweep( void ); + void Event_Pause( void ); + void Event_Alert( void ); + void Event_AddLight( void ); +}; + +#endif /* !__GAME_SECURITYCAMERA_H__ */ diff --git a/source/game/Sound.cpp b/source/game/Sound.cpp new file mode 100644 index 0000000..5c9d226 --- /dev/null +++ b/source/game/Sound.cpp @@ -0,0 +1,371 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + SOUND + +=============================================================================== +*/ + +const idEventDef EV_Speaker_On( "On", NULL ); +const idEventDef EV_Speaker_Off( "Off", NULL ); +const idEventDef EV_Speaker_Timer( "", NULL ); + +CLASS_DECLARATION( idEntity, idSound ) + EVENT( EV_Activate, idSound::Event_Trigger ) + EVENT( EV_Speaker_On, idSound::Event_On ) + EVENT( EV_Speaker_Off, idSound::Event_Off ) + EVENT( EV_Speaker_Timer, idSound::Event_Timer ) +END_CLASS + + +/* +================ +idSound::idSound +================ +*/ +idSound::idSound( void ) { + lastSoundVol = 0.0f; + soundVol = 0.0f; + shakeTranslate.Zero(); + shakeRotate.Zero(); + random = 0.0f; + wait = 0.0f; + timerOn = false; + playingUntilTime = 0; +} + +/* +================ +idSound::Save +================ +*/ +void idSound::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( lastSoundVol ); + savefile->WriteFloat( soundVol ); + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( timerOn ); + savefile->WriteVec3( shakeTranslate ); + savefile->WriteAngles( shakeRotate ); + savefile->WriteInt( playingUntilTime ); +} + +/* +================ +idSound::Restore +================ +*/ +void idSound::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( lastSoundVol ); + savefile->ReadFloat( soundVol ); + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( timerOn ); + savefile->ReadVec3( shakeTranslate ); + savefile->ReadAngles( shakeRotate ); + savefile->ReadInt( playingUntilTime ); +} + +/* +================ +idSound::Spawn +================ +*/ +void idSound::Spawn( void ) { + spawnArgs.GetVector( "move", "0 0 0", shakeTranslate ); + spawnArgs.GetAngles( "rotate", "0 0 0", shakeRotate ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + soundVol = 0.0f; + lastSoundVol = 0.0f; + + if ( ( shakeRotate != ang_zero ) || ( shakeTranslate != vec3_zero ) ) { + BecomeActive( TH_THINK ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else { + timerOn = false; + } + +} + +/* +================ +idSound::Event_Trigger + +this will toggle the idle idSound on and off +================ +*/ +void idSound::Event_Trigger( idEntity *activator ) { + if ( wait > 0.0f ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } else { + timerOn = true; + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + } else { + if ( gameLocal.isMultiplayer ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && ( gameLocal.time < playingUntilTime ) ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } else { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } + } +} + +/* +================ +idSound::Event_Timer +================ +*/ +void idSound::Event_Timer( void ) { + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idSound::Think +================ +*/ +void idSound::Think( void ) { + idAngles ang; + + // run physics + RunPhysics(); + + // clear out our update visuals think flag since we never call Present + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============== +idSound::UpdateChangableSpawnArgs +=============== +*/ +void idSound::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + FreeSoundEmitter( true ); + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + + spawnArgs.Copy( *source ); + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + DoSound( false ); + CancelEvents( &EV_Speaker_Timer ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +// RAVEN BEGIN + } else if ( !refSound.waitfortrigger ) { + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + // start it if it isn't already playing, and we aren't waitForTrigger + DoSound( true ); + timerOn = false; + } + } + } +} + +/* +=============== +idSound::SetSound +=============== +*/ +void idSound::SetSound( const char *sound, int channel ) { + const idSoundShader *shader = declManager->FindSound( sound ); + if ( shader != refSound.shader ) { + FreeSoundEmitter( true ); + } + gameEdit->ParseSpawnArgsToRefSound(&spawnArgs, &refSound); + refSound.shader = shader; +// RAVEN BEGIN + // start it if it isn't already playing, and we aren't waitForTrigger + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !refSound.waitfortrigger && !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + DoSound( true ); + } + +} + +/* +================ +idSound::DoSound +================ +*/ +void idSound::DoSound( bool play ) { + if ( play ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); + playingUntilTime += gameLocal.time; + } else { + StopSound( SND_CHANNEL_ANY, true ); + playingUntilTime = 0; + } +} + +/* +================ +idSound::Event_On +================ +*/ +void idSound::Event_On( void ) { + if ( wait > 0.0f ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + DoSound( true ); +} + +/* +================ +idSound::Event_Off +================ +*/ +void idSound::Event_Off( void ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } + DoSound( false ); +} + +// RAVEN BEGIN +// abahr: so we only set the referenceSounds on our targets once +/* +================ +idSound::FindTargets +================ +*/ +void idSound::FindTargets() { + idEntity::FindTargets(); + + if( !targets.Num() ) {// I don't think we ever get in here unless we have targets + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + // if we have targets lets get an emitter + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + SetTargetSoundHandles(); +} +// RAVEN END + +/* +=============== +idSound::ShowEditingDialog +=============== +*/ +void idSound::ShowEditingDialog( void ) { + common->InitTool( EDITOR_SOUND, &spawnArgs ); +} + +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and have those lights use this speaker's refSound +/* +=============== +idSound::SetSoundHandles +=============== +*/ +void idSound::SetTargetSoundHandles( void ) { +//this code is mostly boosted from a similar function in light.cpp + int i; + idEntity *targetEnt; + + //is this check really necessary? We are a speaker after all... + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( targetEnt && targetEnt->IsType( idLight::GetClassType() ) ) { +// RAVEN END + idLight *light = static_cast(targetEnt); + + //no need to make this speaker a lightparent.... + //light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->SetRefSound(refSound.referenceSoundHandle); + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } + } +} + +// abahr: +/* +================ +idSound::GetPhysicsToSoundTransform +================ +*/ +bool idSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = shakeTranslate.Random( spawnArgs.GetVector("move_random_delta"), gameLocal.random ); + axis = shakeRotate.Random( spawnArgs.GetVector("shake_random_delta"), gameLocal.random ).ToMat3(); + return true; +} +// RAVEN END diff --git a/source/game/Sound.h b/source/game/Sound.h new file mode 100644 index 0000000..20ee484 --- /dev/null +++ b/source/game/Sound.h @@ -0,0 +1,58 @@ + +#ifndef __GAME_SOUND_H__ +#define __GAME_SOUND_H__ + +/* +=============================================================================== + + Generic sound emitter. + +=============================================================================== +*/ + +class idSound : public idEntity { +public: + CLASS_PROTOTYPE( idSound ); + + idSound( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + void Spawn( void ); + + void ToggleOnOff( idEntity *other, idEntity *activator ); + void Think( void ); + void SetSound( const char *sound, int channel = SND_CHANNEL_ANY ); + + virtual void ShowEditingDialog( void ); + +private: + float lastSoundVol; + float soundVol; + float random; + float wait; + bool timerOn; + idVec3 shakeTranslate; + idAngles shakeRotate; + int playingUntilTime; + + void Event_Trigger( idEntity *activator ); + void Event_Timer( void ); + void Event_On( void ); + void Event_Off( void ); + + void DoSound( bool play ); +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and tie them to the speaker's ref sound + void SetTargetSoundHandles( void ); +// abahr + virtual void FindTargets(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +// RAVEN END + +}; + +#endif /* !__GAME_SOUND_H__ */ diff --git a/source/game/SplineMover.cpp b/source/game/SplineMover.cpp new file mode 100644 index 0000000..ca9ca50 --- /dev/null +++ b/source/game/SplineMover.cpp @@ -0,0 +1,3289 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" + +const idEventDef EV_OnAcceleration( "" ); +const idEventDef EV_OnDeceleration( "" ); +const idEventDef EV_OnCruising( "" ); + +const idEventDef EV_OnStartMoving( "" ); +const idEventDef EV_OnStopMoving( "" ); + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Spline ) + EVENT( EV_PostRestore, rvPhysics_Spline::Event_PostRestore ) +END_CLASS + +void splinePState_t::ApplyAccelerationDelta( float timeStepSec ) { + speed = SignZero(idealSpeed) * Min( idMath::Fabs(idealSpeed), idMath::Fabs(speed) + acceleration * timeStepSec ); +} + +void splinePState_t::ApplyDecelerationDelta( float timeStepSec ) { + speed = SignZero(speed) * Max( idMath::Fabs(idealSpeed), idMath::Fabs(speed) - deceleration * timeStepSec ); +} + +void splinePState_t::UpdateDist( float timeStepSec ) { + dist += speed * timeStepSec; +} + +bool splinePState_t::ShouldAccelerate() const { + if( Sign(idealSpeed) == Sign(speed) ) { + return idMath::Fabs(speed) < idMath::Fabs(idealSpeed); + } else if( !Sign(speed) ) { + return true; + } + + return false; +} + +bool splinePState_t::ShouldDecelerate() const { + if( Sign(speed) == Sign(idealSpeed) ) { + return idMath::Fabs(speed) > idMath::Fabs(idealSpeed); + } else if( !Sign(idealSpeed) ) { + return true; + } + + return false; +} + +void splinePState_t::Clear() { + origin.Zero(); + localOrigin.Zero(); + axis.Identity(); + localAxis.Identity(); + speed = 0.0f; + idealSpeed = 0.0f; + dist = 0.0f; + acceleration = 0.0f; + deceleration = 0.0f; +} + +void splinePState_t::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteDeltaVec3( vec3_zero, origin ); + msg.WriteDeltaVec3( vec3_zero, localOrigin ); + msg.WriteDeltaMat3( mat3_identity, axis ); + msg.WriteDeltaMat3( mat3_identity, localAxis ); + msg.WriteDeltaFloat( 0.0f, speed ); + msg.WriteDeltaFloat( 0.0f, idealSpeed ); + msg.WriteDeltaFloat( 0.0f, dist ); + msg.WriteDeltaFloat( 0.0f, acceleration ); + msg.WriteDeltaFloat( 0.0f, deceleration ); +} + +void splinePState_t::ReadFromSnapshot( const idBitMsgDelta &msg ) { + origin = msg.ReadDeltaVec3( vec3_zero ); + localOrigin = msg.ReadDeltaVec3( vec3_zero ); + axis = msg.ReadDeltaMat3( mat3_identity ); + localAxis = msg.ReadDeltaMat3( mat3_identity ); + speed = msg.ReadDeltaFloat( 0.0f ); + idealSpeed = msg.ReadDeltaFloat( 0.0f ); + dist = msg.ReadDeltaFloat( 0.0f ); + acceleration = msg.ReadDeltaFloat( 0.0f ); + deceleration = msg.ReadDeltaFloat( 0.0f ); +} + +void splinePState_t::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( origin ); + savefile->WriteVec3( localOrigin ); + savefile->WriteMat3( axis ); + savefile->WriteMat3( localAxis ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( dist ); + savefile->WriteFloat( acceleration ); + savefile->WriteFloat( deceleration ); +} + +void splinePState_t::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( origin ); + savefile->ReadVec3( localOrigin ); + savefile->ReadMat3( axis ); + savefile->ReadMat3( localAxis ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( dist ); + savefile->ReadFloat( acceleration ); + savefile->ReadFloat( deceleration ); +} + +splinePState_t& splinePState_t::Assign( const splinePState_t* state ) { + SIMDProcessor->Memcpy( this, state, sizeof(splinePState_t) ); + return *this; +} + +splinePState_t& splinePState_t::operator=( const splinePState_t& state ) { + return Assign( &state ); +} + +splinePState_t& splinePState_t::operator=( const splinePState_t* state ) { + return Assign( state ); +} + +/* +================ +rvPhysics_Spline::rvPhysics_Spline +================ +*/ +rvPhysics_Spline::rvPhysics_Spline( void ) { + accelDecelStateThread.SetName( "AccelDecel" ); + accelDecelStateThread.SetOwner( this ); + accelDecelStateThread.SetState( "Cruising" ); + + clipModel = NULL; + + spline = NULL; + SetSplineEntity( NULL ); + + memset( &pushResults, 0, sizeof(trace_t) ); + pushResults.fraction = 1.0f; + + current.Clear(); + SaveState(); +} + +/* +================ +rvPhysics_Spline::~rvPhysics_Spline +================ +*/ +rvPhysics_Spline::~rvPhysics_Spline( void ) { + SAFE_DELETE_PTR( clipModel ); + + SAFE_DELETE_PTR( spline ); +} + +/* +================ +rvPhysics_Spline::Save +================ +*/ +void rvPhysics_Spline::Save( idSaveGame *savefile ) const { + + current.Save( savefile ); + saved.Save( savefile ); + + savefile->WriteFloat( splineLength ); + // This spline was retored as NULL, so there's no reason to save it. + //savefile->WriteInt(spline != NULL ? spline->GetTime( 0 ) : -1 ); // cnicholson: Added unsaved var + splineEntity.Save( savefile ); + + savefile->WriteTrace( pushResults ); + + savefile->WriteClipModel( clipModel ); + + accelDecelStateThread.Save( savefile ); +} + +/* +================ +rvPhysics_Spline::Restore +================ +*/ +void rvPhysics_Spline::Event_PostRestore( void ) { + + if( splineEntity.IsValid() ) { + spline = splineEntity->GetSpline(); + } +} + +void rvPhysics_Spline::Restore( idRestoreGame *savefile ) { + + current.Restore( savefile ); + saved.Restore( savefile ); + + savefile->ReadFloat( splineLength ); + SAFE_DELETE_PTR( spline ); + splineEntity.Restore( savefile ); + + savefile->ReadTrace( pushResults ); + + savefile->ReadClipModel( clipModel ); + + accelDecelStateThread.Restore( savefile, this ); +} + +/* +================ +rvPhysics_Spline::SetClipModel +================ +*/ +void rvPhysics_Spline::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; + + LinkClip(); +} + +/* +================ +rvPhysics_Spline::GetClipModel +================ +*/ +idClipModel *rvPhysics_Spline::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Spline::SetContents +================ +*/ +void rvPhysics_Spline::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Spline::GetContents +================ +*/ +int rvPhysics_Spline::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Spline::GetBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Spline::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Spline::SetSpline +================ +*/ +void rvPhysics_Spline::SetSpline( idCurve_Spline* spline ) { + SAFE_DELETE_PTR( this->spline ); + + //Keep any left over dist from last spline to minimize hitches + if( GetSpeed() >= 0.0f ) { + current.dist = Max( 0.0f, current.dist - splineLength ); + } + + if( !spline ) { + splineLength = 0.0f; + return; + } + + this->spline = spline; + + splineLength = spline->GetLengthForTime( spline->GetTime(spline->GetNumValues() - 1) ); + if( GetSpeed() < 0.0f ) { + current.dist = splineLength - current.dist; + } + + Activate(); +} + +/* +================ +rvPhysics_Spline::SetSplineEntity +================ +*/ +void rvPhysics_Spline::SetSplineEntity( idSplinePath* spline ) { + splineEntity = spline; + SetSpline( (spline) ? spline->GetSpline() : NULL ); +} + +/* +================ +rvPhysics_Spline::ComputeDecelFromSpline +================ +*/ +float rvPhysics_Spline::ComputeDecelFromSpline() const { + // FIXME: merge this in better. It seems very special case + float numerator = GetSpeed() * GetSpeed(); + float denomonator = 2.0f * ((GetSpeed() >= 0.0f) ? (splineLength - current.dist) : current.dist); + + assert( denomonator > VECTOR_EPSILON ); + + return numerator / denomonator; +} + +/* +================ +rvPhysics_Spline::SetLinearAcceleration +================ +*/ +void rvPhysics_Spline::SetLinearAcceleration( const float accel ) { + current.acceleration = accel; +} + +/* +================ +rvPhysics_Spline::SetLinearDeceleration +================ +*/ +void rvPhysics_Spline::SetLinearDeceleration( const float decel ) { + current.deceleration = decel; +} + +/* +================ +rvPhysics_Spline::SetSpeed +================ +*/ +void rvPhysics_Spline::SetSpeed( float speed ) { + if( IsAtRest() || StoppedMoving() ) { + current.dist = (speed < 0.0f) ? splineLength - current.dist : current.dist; + } + + current.idealSpeed = speed; + Activate(); +} + +/* +================ +rvPhysics_Spline::GetSpeed +================ +*/ +float rvPhysics_Spline::GetSpeed() const { + return current.speed; +} + +/* +================ +rvPhysics_Spline::Evaluate +================ +*/ +bool rvPhysics_Spline::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + splinePState_t previous = current; + + if( HasValidSpline() ) { + if( StoppedMoving() ) { + Rest(); + return false; + } + + accelDecelStateThread.Execute(); + + // FIXME: clean this up + if( IsAtBeginningOfSpline() || IsAtEndOfSpline() ) { + current = previous; + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + + if( gameLocal.program.GetReturnedBool() ) { + current.speed = 0.0f; + return false; + } else { + return true; + } + } + + float currentTime = splineEntity->GetSampledTime ( current.dist ); + if ( currentTime == -1.0f ) { + currentTime = spline->GetTimeForLength( Min(current.dist, splineLength), 0.01f ); + } + + current.axis = spline->GetCurrentFirstDerivative(currentTime).ToAngles().Normalize360().ToMat3(); + current.origin = spline->GetCurrentValue( currentTime ); + current.localOrigin = current.origin; + current.localAxis = current.axis; + } else if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAxis * masterAxis; + current.origin = masterOrigin + current.localOrigin * masterAxis; + } else { + Rest(); + return false; + } + + gameLocal.push.ClipPush( pushResults, self, 0, previous.origin, previous.axis, current.origin, current.axis ); + if( pushResults.fraction < 1.0f ) { + current = previous; + LinkClip(); + current.speed = 0.0f; + return false; + } + + LinkClip(); + + if( StoppedMoving() && !self->IsBound() ) { + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + return !gameLocal.program.GetReturnedBool(); + } + + return true; +} + +/* +================ +rvPhysics_Spline::Activate +================ +*/ +void rvPhysics_Spline::Activate( void ) { + assert( self ); + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::Rest +================ +*/ +void rvPhysics_Spline::Rest( void ) { + assert( self ); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtRest +================ +*/ +bool rvPhysics_Spline::IsAtRest( void ) const { + assert( self ); + return !self->IsActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtEndOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtEndOfSpline( void ) const { + return current.dist >= splineLength; +} + +/* +================ +rvPhysics_Spline::IsAtBeginningOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtBeginningOfSpline( void ) const { + return current.dist <= 0.0f; +} + +/* +================ +rvPhysics_Spline::IsPushable +================ +*/ +bool rvPhysics_Spline::IsPushable( void ) const { + return !HasValidSpline() && idPhysics_Base::IsPushable(); +} + +/* +================ +rvPhysics_Spline::StartingToMove +================ +*/ +bool rvPhysics_Spline::StartingToMove( void ) const { + float firstDeltaSpeed = current.acceleration * MS2SEC(gameLocal.GetMSec()); + return idMath::Fabs(current.idealSpeed) > VECTOR_EPSILON && idMath::Fabs(current.speed) <= firstDeltaSpeed; +} + +/* +================ +rvPhysics_Spline::StoppedMoving +================ +*/ +bool rvPhysics_Spline::StoppedMoving( void ) const { + return idMath::Fabs(current.idealSpeed) < VECTOR_EPSILON && idMath::Fabs(current.speed) < VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::HasValidSpline +================ +*/ +bool rvPhysics_Spline::HasValidSpline() const { + return spline && splineLength > VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::SaveState +================ +*/ +void rvPhysics_Spline::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Spline::RestoreState +================ +*/ +void rvPhysics_Spline::RestoreState( void ) { + current = saved; + + LinkClip(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Spline::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + + LinkClip(); + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Spline::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } + else { + current.axis = newAxis; + } + + LinkClip(); + Activate(); +} + +/* +================ +rvPhysics_Spline::Translate +================ +*/ +void rvPhysics_Spline::Translate( const idVec3 &translation, int id ) { + SetOrigin( GetLocalOrigin() + translation ); +} + +/* +================ +rvPhysics_Spline::Rotate +================ +*/ +void rvPhysics_Spline::Rotate( const idRotation &rotation, int id ) { + SetAxis( GetLocalAxis() * rotation.ToMat3() ); + SetOrigin( GetLocalOrigin() * rotation ); +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetOrigin( int id ) { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetAxis( int id ) { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) const { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) const { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::SetMaster +================ +*/ +void rvPhysics_Spline::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if( master ) { + if( self->IsBound() ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( GetOrigin() - masterOrigin ) * masterAxis.Transpose(); + current.localAxis = GetAxis() * masterAxis.Transpose(); + } + } +} + +/* +================ +rvPhysics_Spline::ClipTranslation +================ +*/ +void rvPhysics_Spline::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipRotation +================ +*/ +void rvPhysics_Spline::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipContents +================ +*/ +int rvPhysics_Spline::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.ContentsModel( self, GetOrigin(), clipModel, GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, GetOrigin(), clipModel, GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::DisableClip +================ +*/ +void rvPhysics_Spline::DisableClip( void ) { + if( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +rvPhysics_Spline::EnableClip +================ +*/ +void rvPhysics_Spline::EnableClip( void ) { + if( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +rvPhysics_Spline::UnlinkClip +================ +*/ +void rvPhysics_Spline::UnlinkClip( void ) { + if( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +rvPhysics_Spline::LinkClip +================ +*/ +void rvPhysics_Spline::LinkClip( void ) { + if( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), GetOrigin(), GetAxis() ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::GetBlockingInfo +================ +*/ +const trace_t* rvPhysics_Spline::GetBlockingInfo( void ) const { + return (pushResults.fraction < 1.0f) ? &pushResults : NULL; +} + +/* +================ +rvPhysics_Spline::GetBlockingEntity +================ +*/ +idEntity* rvPhysics_Spline::GetBlockingEntity( void ) const { + return (pushResults.fraction < 1.0f) ? gameLocal.entities[ pushResults.c.entityNum ] : NULL; +} + +/* +================ +rvPhysics_Spline::WriteToSnapshot +================ +*/ +void rvPhysics_Spline::WriteToSnapshot( idBitMsgDelta &msg ) const { + current.WriteToSnapshot( msg ); +} + +/* +================ +rvPhysics_Spline::ReadFromSnapshot +================ +*/ +void rvPhysics_Spline::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.ReadFromSnapshot( msg ); + + LinkClip(); +} + +CLASS_STATES_DECLARATION( rvPhysics_Spline ) + STATE( "Accelerating", rvPhysics_Spline::State_Accelerating ) + STATE( "Decelerating", rvPhysics_Spline::State_Decelerating ) + STATE( "Cruising", rvPhysics_Spline::State_Cruising ) +END_CLASS_STATES + +/* +================ +rvPhysics_Spline::State_Accelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Accelerating( const stateParms_t& parms ) { + stateResult_t returnResult = SRESULT_WAIT; + + if( !current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( current.ShouldDecelerate() ? "Decelerating" : "Cruising" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + if( StartingToMove() ) { + self->ProcessEvent( &EV_OnStartMoving ); + } + self->ProcessEvent( &EV_OnAcceleration ); + + returnResult = SRESULT_STAGE( parms.stage + 1 ); + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyAccelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + return returnResult; +} + +/* +================ +rvPhysics_Spline::State_Decelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Decelerating( const stateParms_t& parms ) { + if( !current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( current.ShouldAccelerate() ? "Accelerating" : "Cruising" ); + return SRESULT_DONE; + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyDecelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnDeceleration ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( StoppedMoving() ) { + self->ProcessEvent( &EV_OnStopMoving ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvPhysics_Spline::State_Cruising +================ +*/ +stateResult_t rvPhysics_Spline::State_Cruising( const stateParms_t& parms ) { + if( current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( "Accelerating" ); + return SRESULT_DONE; + } else if( current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( "Decelerating" ); + return SRESULT_DONE; + } + + current.UpdateDist( MS2SEC(gameLocal.GetMSec()) ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnCruising ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + return SRESULT_WAIT; +} + + + +const idEventDef EV_SetSpline( "setSpline", "E" ); + +const idEventDef EV_SetAccel( "setAccel", "f" ); +const idEventDef EV_SetDecel( "setDecel", "f" ); + +const idEventDef EV_SetSpeed( "setSpeed", "f" ); +const idEventDef EV_GetSpeed( "getSpeed", "", 'f' ); + +const idEventDef EV_TramCar_SetIdealSpeed( "setIdealSpeed", "f" ); +const idEventDef EV_TramCar_GetIdealSpeed( "getIdealSpeed", "", 'f' ); + +const idEventDef EV_TramCar_ApplySpeedScale( "applySpeedScale", "f" ); + +const idEventDef EV_GetCurrentTrackInfo( "getCurrentTrackInfo", "", 's' ); +const idEventDef EV_GetTrackInfo( "getTrackInfo", "e", 's' ); + +const idEventDef EV_DoneMoving( "", "", 'd' ); +const idEventDef EV_StartSoundPeriodic( "", "sddd" ); + +idLinkList rvSplineMover::splineMovers; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvSplineMover ) + EVENT( EV_PostSpawn, rvSplineMover::Event_PostSpawn ) + EVENT( EV_Activate, rvSplineMover::Event_Activate ) + EVENT( EV_SetSpline, rvSplineMover::Event_SetSpline ) + EVENT( EV_SetAccel, rvSplineMover::Event_SetAcceleration ) + EVENT( EV_SetDecel, rvSplineMover::Event_SetDeceleration ) + EVENT( EV_SetSpeed, rvSplineMover::Event_SetSpeed ) + EVENT( EV_GetSpeed, rvSplineMover::Event_GetSpeed ) + EVENT( EV_Thread_SetCallback, rvSplineMover::Event_SetCallBack ) + EVENT( EV_DoneMoving, rvSplineMover::Event_DoneMoving ) + EVENT( EV_GetSplineEntity, rvSplineMover::Event_GetSpline ) + EVENT( EV_GetCurrentTrackInfo, rvSplineMover::Event_GetCurrentTrackInfo ) + EVENT( EV_GetTrackInfo, rvSplineMover::Event_GetTrackInfo ) + EVENT( EV_TramCar_SetIdealSpeed, rvSplineMover::Event_SetIdealSpeed ) + EVENT( EV_TramCar_GetIdealSpeed, rvSplineMover::Event_GetIdealSpeed ) + EVENT( EV_TramCar_ApplySpeedScale, rvSplineMover::Event_ApplySpeedScale ) + EVENT( EV_OnAcceleration, rvSplineMover::Event_OnAcceleration ) + EVENT( EV_OnDeceleration, rvSplineMover::Event_OnDeceleration ) + EVENT( EV_OnCruising, rvSplineMover::Event_OnCruising ) + EVENT( EV_OnStartMoving, rvSplineMover::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvSplineMover::Event_OnStopMoving ) + EVENT( EV_StartSoundPeriodic, rvSplineMover::Event_StartSoundPeriodic ) + EVENT( EV_PartBlocked, rvSplineMover::Event_PartBlocked ) +END_CLASS + +/* +================ +rvSplineMover::Spawn +================ +*/ +void rvSplineMover::Spawn() { + waitThreadId = -1; + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( spawnArgs.GetBool("solid", "1") ? CONTENTS_SOLID : 0 ); + physicsObj.SetClipMask( spawnArgs.GetBool("solidClip") ? CONTENTS_SOLID : 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat("accel", "50") ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat("decel", "50") ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + AddSelfToGlobalList(); + + // This is needed so we get sorted correctly + BecomeInactive( TH_PHYSICS ); + BecomeActive( TH_PHYSICS ); + + PlayAnim( ANIMCHANNEL_ALL, "idle", 0 ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvSplineMover::~rvSplineMover +================ +*/ +rvSplineMover::~rvSplineMover() { + RemoveSelfFromGlobalList(); + SetPhysics( NULL ); +} + +/* +================ +rvSplineMover::SetSpeed +================ +*/ +void rvSplineMover::SetSpeed( float newSpeed ) { + physicsObj.SetSpeed( newSpeed ); +} + +/* +================ +rvSplineMover::GetSpeed +================ +*/ +float rvSplineMover::GetSpeed() const { + return physicsObj.GetSpeed(); +} + +/* +================ +rvSplineMover::SetIdealSpeed +================ +*/ +void rvSplineMover::SetIdealSpeed( float newIdealSpeed ) { + idealSpeed = newIdealSpeed; + SetSpeed( newIdealSpeed ); +} + +/* +================ +rvSplineMover::GetIdealSpeed +================ +*/ +float rvSplineMover::GetIdealSpeed() const { + return idealSpeed; +} + +/* +================ +rvSplineMover::SetSpline +================ +*/ +void rvSplineMover::SetSpline( idSplinePath* spline ) { + physicsObj.SetSplineEntity( spline ); + CheckSplineForOverrides( physicsObj.GetSpline(), &spline->spawnArgs ); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +const idSplinePath* rvSplineMover::GetSpline() const { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +idSplinePath* rvSplineMover::GetSpline() { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::SetAcceleration +================ +*/ +void rvSplineMover::SetAcceleration( float accel ) { + physicsObj.SetLinearAcceleration( accel ); +} + +/* +================ +rvSplineMover::SetDeceleration +================ +*/ +void rvSplineMover::SetDeceleration( float decel ) { + physicsObj.SetLinearDeceleration( decel ); +} + +/* +================ +rvSplineMover::CheckSplineForOverrides +================ +*/ +void rvSplineMover::CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ) { + if( !spline || !args ) { + return; + } + + int endSpline = args->GetInt( "end_spline" ); + if( endSpline && Sign(endSpline) == Sign(GetSpeed()) ) { + physicsObj.SetLinearDeceleration( physicsObj.ComputeDecelFromSpline() ); + SetIdealSpeed( 0.0f ); + } +} + +/* +================ +rvSplineMover::RestoreFromOverrides +================ +*/ +void rvSplineMover::RestoreFromOverrides( const idDict* args ) { + if( !args ) { + return; + } + + physicsObj.SetLinearDeceleration( args->GetFloat("decel") ); +} + +/* +================ +rvSplineMover::PlayAnim +================ +*/ +int rvSplineMover::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim( channel )->Length(); +} + +/* +================ +rvSplineMover::CycleAnim +================ +*/ +void rvSplineMover::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return; + } + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvSplineMover::ClearChannel +================ +*/ +void rvSplineMover::ClearChannel( int channel, int clearFrame ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(clearFrame) ); +} + +/* +================ +rvSplineMover::PreBind +================ +*/ +void rvSplineMover::PreBind() { + idAnimatedEntity::PreBind(); + + SetSpline( NULL ); +} + +/* +================ +rvSplineMover::Save +================ +*/ +void rvSplineMover::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteInt( waitThreadId ); +} + +/* +================ +rvSplineMover::Restore +================ +*/ +void rvSplineMover::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadFloat( idealSpeed ); + savefile->ReadInt( waitThreadId ); + + AddSelfToGlobalList(); +} + +/* +================ +rvSplineMover::WriteToSnapshot +================ +*/ +void rvSplineMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvSplineMover::ReadFromSnapshot +================ +*/ +void rvSplineMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvSplineMover::AddSelfToGlobalList +================ +*/ +void rvSplineMover::AddSelfToGlobalList() { + splineMoverNode.SetOwner( this ); + + if( !InGlobalList() ) { + splineMoverNode.AddToEnd( splineMovers ); + } +} + +/* +================ +rvSplineMover::RemoveSelfFromGlobalList +================ +*/ +void rvSplineMover::RemoveSelfFromGlobalList() { + splineMoverNode.Remove(); +} + +/* +================ +rvSplineMover::InGlobalList +================ +*/ +bool rvSplineMover::InGlobalList() const { + return splineMoverNode.InList(); +} + +/* +================ +rvSplineMover::WhosVisible +================ +*/ +bool rvSplineMover::WhosVisible( const idFrustum& frustum, idList& list ) const { + list.Clear(); + + if( !frustum.IsValid() ) { + return false; + } + + for( rvSplineMover* node = splineMovers.Next(); node; node = node->splineMoverNode.Next() ) { + if( node == this ) { + continue; + } + + if( frustum.IntersectsBounds(node->GetPhysics()->GetAbsBounds()) ) { + list.AddUnique( node ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvSplineMover::GetTrackInfo +================ +*/ +idStr rvSplineMover::GetTrackInfo( const idSplinePath* track ) const { + if( !track ) { + return idStr( "" ); + } + + idStr info( track->GetName() ); + return info.Mid( info.Last('_') - 1, 1 ); +} + +/* +================ +rvSplineMover::ConvertToMover +================ +*/ +rvSplineMover* rvSplineMover::ConvertToMover( idEntity* mover ) const { + return mover && mover->IsType(rvSplineMover::Type) ? static_cast(mover) : NULL; +} + +/* +================ +rvSplineMover::ConvertToSplinePath +================ +*/ +idSplinePath* rvSplineMover::ConvertToSplinePath( idEntity* spline ) const { + return (spline && spline->IsType(idSplinePath::GetClassType())) ? static_cast(spline) : NULL; +} + +/* +================ +rvSplineMover::PreDoneMoving +================ +*/ +void rvSplineMover::PreDoneMoving() { + if( waitThreadId >= 0 ) { + idThread::ObjectMoveDone( waitThreadId, this ); + waitThreadId = -1; + } + + RestoreFromOverrides( &spawnArgs ); +} + +/* +================ +rvSplineMover::PostDoneMoving +================ +*/ +void rvSplineMover::PostDoneMoving() { + CallScriptEvents( physicsObj.GetSplineEntity(), "call_doneMoving", this ); +} + +/* +============== +rvSplineMover::CallScriptEvents +============== +*/ +// FIXME: very similier code is in the spawner...if possible try and make one function for both to call +void rvSplineMover::CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ) { + if( !spline || !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spline->spawnArgs.MatchPrefix(prefixKey); kv; kv = spline->spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( spline, 0 ); + func.InsertEntity( parm, 1 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +================ +rvSplineMover::Event_PostSpawn +================ +*/ +void rvSplineMover::Event_PostSpawn() { + idEntityPtr target; + for( int ix = targets.Num() - 1; ix >= 0; --ix ) { + target = targets[ix]; + + if( target.IsValid() && target->IsType(idSplinePath::GetClassType()) ) { + SetSpline( static_cast(target.GetEntity()) ); + break; + } + } + + SetIdealSpeed( spawnArgs.GetBool("waitForTrigger") ? 0.0f : spawnArgs.GetFloat("speed", "50") ); +} + +/* +=============== +rvSplineMover::Event_PartBlocked +=============== +*/ +void rvSplineMover::Event_PartBlocked( idEntity *blockingEntity ) { + assert( blockingEntity ); + + float damage = spawnArgs.GetFloat( "damage" ); + if( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.GetTime(), GetName(), blockingEntity->GetName() ); + } +} + +/* +================ +rvSplineMover::Event_SetSpline +================ +*/ +void rvSplineMover::Event_SetSpline( idEntity* spline ) { + SetSpline( ConvertToSplinePath(spline) ); +} + +/* +================ +rvSplineMover::Event_GetSpline +================ +*/ +void rvSplineMover::Event_GetSpline() { + idThread::ReturnEntity( GetSpline() ); +} + +/* +================ +rvSplineMover::Event_SetAcceleration +================ +*/ +void rvSplineMover::Event_SetAcceleration( float accel ) { + SetAcceleration( accel ); +} + +/* +================ +rvSplineMover::Event_SetDeceleration +================ +*/ +void rvSplineMover::Event_SetDeceleration( float decel ) { + SetDeceleration( decel ); +} + +/* +================ +rvSplineMover::Event_SetSpeed +================ +*/ +void rvSplineMover::Event_SetSpeed( float speed ) { + SetIdealSpeed( speed ); + SetSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetSpeed +================ +*/ +void rvSplineMover::Event_GetSpeed() { + idThread::ReturnFloat( GetSpeed() ); +} + +/* +================ +rvSplineMover::Event_SetIdealSpeed +================ +*/ +void rvSplineMover::Event_SetIdealSpeed( float speed ) { + SetIdealSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetIdealSpeed +================ +*/ +void rvSplineMover::Event_GetIdealSpeed() { + idThread::ReturnFloat( GetIdealSpeed() ); +} + +/* +================ +rvSplineMover::Event_ApplySpeedScale +================ +*/ +void rvSplineMover::Event_ApplySpeedScale( float scale ) { + SetIdealSpeed( spawnArgs.GetFloat("speed", "50") * scale ); +} + +/* +================ +rvSplineMover::Event_SetCallBack +================ +*/ +void rvSplineMover::Event_SetCallBack() { + if( waitThreadId >= 0 ) { + idThread::ReturnInt( false ); + } + + waitThreadId = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); +} + +/* +================ +rvSplineMover::Event_DoneMoving +================ +*/ +void rvSplineMover::Event_DoneMoving() { + PreDoneMoving(); + PostDoneMoving(); + + idThread::ReturnInt( !physicsObj.HasValidSpline() ); +} + +/* +================ +rvSplineMover::Event_GetCurrentTrackInfo +================ +*/ +void rvSplineMover::Event_GetCurrentTrackInfo() { + Event_GetTrackInfo( physicsObj.GetSplineEntity() ); +} + +/* +================ +rvSplineMover::Event_GetTrackInfo +================ +*/ +void rvSplineMover::Event_GetTrackInfo( idEntity* track ) { + idThread::ReturnString( GetTrackInfo(ConvertToSplinePath(track)) ); +} + +/* +================ +rvSplineMover::Event_Activate +================ +*/ +void rvSplineMover::Event_Activate( idEntity* activator ) { + // This is for my special case in tram1b + + //if( physicsObj.StoppedMoving() ) { + // SetIdealSpeed( spawnArgs.GetFloat("speed", "50") ); + //} +} + +/* +================ +rvSplineMover::Event_OnAcceleration +================ +*/ +void rvSplineMover::Event_OnAcceleration() { + StartSound( "snd_accel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnDeceleration +================ +*/ +void rvSplineMover::Event_OnDeceleration() { + StartSound( "snd_decel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnCruising +================ +*/ +void rvSplineMover::Event_OnCruising() { + idVec2 range( spawnArgs.GetVec2("noisePeriodRange") * idMath::M_SEC2MS ); + if( !EventIsPosted(&EV_StartSoundPeriodic) && range.Length() > VECTOR_EPSILON ) { + ProcessEvent( &EV_StartSoundPeriodic, "snd_noise", (int)SND_CHANNEL_ANY, (int)range[0], (int)range[1] ); + } +} + +/* +================ +rvSplineMover::Event_OnStopMoving +================ +*/ +void rvSplineMover::Event_OnStopMoving() { + StopSound( SND_CHANNEL_ANY, false ); + CancelEvents( &EV_StartSoundPeriodic ); +} + +/* +================ +rvSplineMover::Event_OnStartMoving +================ +*/ +void rvSplineMover::Event_OnStartMoving() { +} + +/* +================ +rvSplineMover::Event_StartSoundPeriodic +================ +*/ +void rvSplineMover::Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ) { + CancelEvents( &EV_StartSoundPeriodic ); + + if( physicsObj.StoppedMoving() ) { + return; + } + + int length; + StartSound( sndKey, channel, 0, false, &length ); + + PostEventMS( &EV_StartSoundPeriodic, Max(rvRandom::irand(minDelay, maxDelay), length), sndKey, (int)channel, minDelay, maxDelay ); +} + + +const idEventDef EV_TramCar_RadiusDamage( "", "vs" ); +const idEventDef EV_TramCar_SetIdealTrack( "setIdealTrack", "s" ); +const idEventDef EV_TramCar_DriverSpeak( "driverSpeak", "s", 'e' ); +const idEventDef EV_TramCar_GetDriver( "getDriver", "", 'E' ); + +const idEventDef EV_TramCar_OpenDoors( "openDoors" ); +const idEventDef EV_TramCar_CloseDoors( "closeDoors" ); + +//======================================================= +// +// rvTramCar +// +//======================================================= +CLASS_DECLARATION( rvSplineMover, rvTramCar ) + EVENT( EV_TramCar_DriverSpeak, rvTramCar::Event_DriverSpeak ) + EVENT( EV_TramCar_GetDriver, rvTramCar::Event_GetDriver ) + EVENT( EV_Activate, rvTramCar::Event_Activate ) + EVENT( EV_TramCar_RadiusDamage, rvTramCar::Event_RadiusDamage ) + EVENT( EV_TramCar_SetIdealTrack, rvTramCar::Event_SetIdealTrack ) + EVENT( EV_OnStartMoving, rvTramCar::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvTramCar::Event_OnStopMoving ) + EVENT( EV_TramCar_OpenDoors, rvTramCar::Event_OpenDoors ) + EVENT( EV_TramCar_CloseDoors, rvTramCar::Event_CloseDoors ) + EVENT( AI_SetHealth, rvTramCar::Event_SetHealth ) +END_CLASS + +/* +================ +rvTramCar::Spawn +================ +*/ +void rvTramCar::Spawn() { + RegisterStateThread( idealTrackStateThread, "IdealTrack" ); + RegisterStateThread( speedSoundEffectsStateThread, "SpeedSoundEffects" ); + + numTracksOnMap = gameLocal.world->spawnArgs.GetInt( "numTramCarTracks", "1" ); + + idStr track; + if ( numTracksOnMap == 1 ) { + track += ConvertToTrackLetter( numTracksOnMap - 1 ); + } else { + track = spawnArgs.RandomPrefix( "idealTrack", gameLocal.random, "0" ); + } + Event_SetIdealTrack( track.c_str() ); + idealTrackStateThread.SetState( GetIdealTrack() < 0 ? "RandomTrack" : "AssignedTrack" ); + + SpawnDriver( "def_driver" ); + SpawnWeapons( "def_weapon" ); + SpawnOccupants( "def_occupant" ); + SpawnDoors(); + + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; + const char* frustumKey = spawnArgs.GetString( "collisionFov" ); + if( frustumKey[0] ) { + sscanf( frustumKey, "%f %f %f %f", &dNear, &dFar, &dLeft, &dUp ); + collisionFov.SetSize( dNear, dFar, dLeft, dUp ); + } + + fl.takedamage = health > 0; + + BecomeActive( TH_THINK ); + + SetSpline( NULL ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +rvTramCar::~rvTramCar() { + SAFE_REMOVE( driver ); + occupants.RemoveContents( true ); + weapons.RemoveContents( true ); + + SAFE_REMOVE( leftDoor ); + SAFE_REMOVE( rightDoor ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +void rvTramCar::SpawnDriver( const char* driverKey ) { + driver = (idAI*)SpawnPart( spawnArgs.GetString(driverKey), "def_occupant" ); +} + +/* +================ +rvTramCar::SpawnWeapons +================ +*/ +void rvTramCar::SpawnWeapons( const char* partKey ) { + idEntityPtr weapon; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + weapon = (rvVehicle*)SpawnPart( kv->GetValue().c_str(), partKey ); + weapon->IdleAnim( ANIMCHANNEL_LEGS, "idle", 0 ); + //if( weapon->GetAnimator()->GetAnim("toFire") && weapon->GetAnimator()->GetAnim("toIdle") ) { + weapons.AddUnique( weapon ); + //} + } +} + +/* +================ +rvTramCar::SpawnOccupants +================ +*/ +void rvTramCar::SpawnOccupants( const char* partKey ) { + idEntityPtr occupant; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + occupant = (idAI*)SpawnPart( kv->GetValue().c_str(), partKey ); + occupants.AddUnique( occupant ); + } +} + +/* +================ +rvTramCar::SpawnPart +================ +*/ +idEntity* rvTramCar::SpawnPart( const char* partDefName, const char* subPartDefName ) { + idEntity* part = NULL; + idDict entityDef; + const idDict* info = gameLocal.FindEntityDefDict( partDefName, false ); + if( !info ) { + return NULL; + } + + const idDict* def = gameLocal.FindEntityDefDict( info->GetString(subPartDefName), false ); + if( !def ) { + return NULL; + } + + entityDef.Copy( *def ); + + entityDef.SetBool( "trigger", spawnArgs.GetBool("waitForTrigger") ); + entityDef.SetVector( "origin", GetPhysics()->GetOrigin() + info->GetVector("spawn_offset") * GetPhysics()->GetAxis() ); + entityDef.SetFloat( "angle", info->GetInt("spawn_facing_offset") + spawnArgs.GetFloat("angle") ); + entityDef.Set( "bind", GetName() ); + gameLocal.SpawnEntityDef( entityDef, &part ); + + return part; +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +void rvTramCar::SpawnDoors() { + leftDoor = SpawnDoor( "clipModel_doorLeft" ); + rightDoor = SpawnDoor( "clipModel_doorRight" ); +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +idMover* rvTramCar::SpawnDoor( const char* key ) { + idDict args; + const idDict* dict = NULL; + idMover* outer = NULL; + idMover* inner = NULL; + + dict = gameLocal.FindEntityDefDict( spawnArgs.GetString(key), false ); + if( !dict ) { + return NULL; + } + + args.Set( "open_rotation", dict->GetString("open_rotation") ); + args.Set( "close_rotation", dict->GetString("close_rotation") ); + + args.Set( "open_dir", dict->GetString("open_dir") ); + args.Set( "close_dir", dict->GetString("close_dir") ); + + args.Set( "distExt", dict->GetString("distExt") ); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + dict->GetVector("offset") * GetPhysics()->GetAxis() ); + args.SetMatrix( "rotation", GetPhysics()->GetAxis() ); + args.Set( "clipModel", dict->GetString("clipModel") ); + args.Set( "bind", GetName() ); + outer = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + assert( outer ); + + args.Set( "clipModel", dict->GetString("clipModelExt") ); + args.Set( "bind", outer->GetName() ); + inner = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + + return inner; +} + +/* +================ +rvTramCar::Think +================ +*/ +void rvTramCar::Think() { + RunPhysics(); + + MidThink(); + + Present(); +} + +/* +================ +rvTramCar::MidThink +================ +*/ +void rvTramCar::MidThink() { + if( !physicsObj.StoppedMoving() ) { + LookAround(); + TouchTriggers(); + speedSoundEffectsStateThread.Execute(); + } +} + +/* +================ +rvTramCar::RegisterStateThread +================ +*/ +void rvTramCar::RegisterStateThread( rvStateThread& stateThread, const char* name ) { + stateThread.SetName( name ); + stateThread.SetOwner( this ); +} + +/* +================ +rvTramCar::SetIdealTrack +================ +*/ +void rvTramCar::SetIdealTrack( int track ) { + idealTrack = track; +} + +/* +================ +rvTramCar::GetIdealTrack +================ +*/ +int rvTramCar::GetIdealTrack() const { + return idealTrack; +} + +/* +================ +rvTramCar::AddDamageEffect +================ +*/ +void rvTramCar::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + rvSplineMover::AddDamageEffect( collision, velocity, damageDefName, inflictor ); + + if( GetDamageScale() >= 0.5f && rvRandom::flrand() <= spawnArgs.GetFloat("damageEffectFreq", "0.25") ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 v = (center - collision.endpos).ToNormal(); + float dot = v * collision.c.normal; + PlayEffect( (dot > 0.0f) ? "fx_damage_internal" : "fx_damage_external", collision.endpos, collision.c.normal.ToMat3(2) ); + } +} + +/* +================ +rvTramCar::Damage +================ +*/ +void rvTramCar::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( attacker && GetPhysics()->GetAbsBounds().Contains(attacker->GetPhysics()->GetAbsBounds()) ) { + return; + } + + if( attacker && g_debugVehicle.GetInteger() == 3 ) { + gameLocal.Printf( "Was damaged by %s. Current damage scale: %f\n", attacker->GetName(), GetDamageScale() ); + } + + rvSplineMover::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvTramCar::Killed +================ +*/ +void rvTramCar::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + //Calling gameLocal's version because we don't want to get bound and hidden + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_explode" ), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + PostEventMS( &EV_TramCar_RadiusDamage, 0, center, spawnArgs.GetString("def_damage_explode") ); + + //TODO: think about giving rigid body physics and have it fall from track + Hide(); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetDamageScale() const { + return 1.0f - GetHealthScale(); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetHealthScale() const { + float spawnHealth = spawnArgs.GetFloat( "health" ); + return ( idMath::Fabs(spawnHealth) <= VECTOR_EPSILON ) ? 1.0f : (health / spawnHealth); +} + +/* +================ +rvTramCar::Save +================ +*/ +void rvTramCar::Save( idSaveGame *savefile ) const { + savefile->WriteInt( idealTrack ); +// savefile->WriteString( idealTrackTag.c_str() ); // cnicholson: FIXME: This has a comment of HACK in the .h file... Not sure how to write a idStr yet + + savefile->WriteFrustum( collisionFov ); + + idealTrackStateThread.Save( savefile ); + speedSoundEffectsStateThread.Save( savefile ); + + driver.Save( savefile ); + + savefile->WriteInt( occupants.Num() ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Save( savefile ); + } + + savefile->WriteInt( weapons.Num() ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Save( savefile ); + } + + savefile->WriteInt( numTracksOnMap ); + + leftDoor.Save ( savefile ); + rightDoor.Save ( savefile ); +} + +/* +================ +rvTramCar::Restore +================ +*/ +void rvTramCar::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idealTrack ); + + savefile->ReadFrustum( collisionFov ); + + idealTrackStateThread.Restore( savefile, this ); + speedSoundEffectsStateThread.Restore( savefile, this ); + + driver.Restore( savefile ); + + int num = 0; + savefile->ReadInt( num ); + occupants.SetNum( num ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Restore( savefile ); + } + + savefile->ReadInt( num ); + weapons.SetNum( num ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Restore( savefile ); + } + + savefile->ReadInt( numTracksOnMap ); + + leftDoor.Restore ( savefile ); + rightDoor.Restore ( savefile ); +} + +/* +================ +rvTramCar::WriteToSnapshot +================ +*/ +void rvTramCar::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar::ReadFromSnapshot +================ +*/ +void rvTramCar::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar::HeadTowardsIdealTrack +================ +*/ +void rvTramCar::HeadTowardsIdealTrack() { + idSplinePath* ideal = FindSplineToIdealTrack( GetSpline() ); + if( !ideal ) { + ideal = GetRandomSpline(GetSpline()); + } + + SetSpline( ideal ); +} + +/* +================ +rvTramCar::FindSplineToTrack +================ +*/ +enum { + LOOK_LEFT = -1, + LOOK_RIGHT = 1 +}; +idSplinePath* rvTramCar::FindSplineToTrack( idSplinePath* spline, const idStr& track ) const { + idSplinePath* target = NULL; + idEntity* ent = NULL; + idStr trackInfo; + idList list; + + if( !spline ) { + return NULL; + } + + for( int ix = SortSplineTargets(spline) - 1; ix >= 0; --ix ) { + ent = GetSplineTarget( spline, ix ); + target = static_cast( ent ); + assert( target->IsActive() ); + + trackInfo = GetTrackInfo( target ); + if( -1 >= trackInfo.Find(track) ) { + continue; + } + + // HACK: I hate switch statements + switch( ConvertToTrackNumber(trackInfo) - GetCurrentTrack() ) { + case LOOK_LEFT: { + if( !LookLeft(list) ) { + return target; + } + break; + } + + case LOOK_RIGHT: { + if( !LookRight(list) ) { + return target; + } + break; + } + + default: { + return target; + } + } + // HACK + } + + return NULL; +} + +/* +================ +rvTramCar::SortSplineTargets +================ +*/ +int rvTramCar::SortSplineTargets( idSplinePath* spline ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->SortTargets() : spline->SortBackwardsTargets(); +} + +/* +================ +rvTramCar::GetSplineTarget +================ +*/ +idEntity* rvTramCar::GetSplineTarget( idSplinePath* spline, int index ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->targets[index].GetEntity() : spline->backwardPathTargets[index].GetEntity(); +} + +/* +================ +rvTramCar::FindSplineToIdealTrack +================ +*/ +idSplinePath* rvTramCar::FindSplineToIdealTrack( idSplinePath* spline ) const { + // HACK + int trackDelta = idMath::ClampInt( -1, 1, GetIdealTrack() - GetCurrentTrack() ); + idStr trackLetter( ConvertToTrackLetter(GetCurrentTrack() + trackDelta) ); + idSplinePath* s = NULL; + + if( idealTrackTag.Length() && !trackDelta ) {// On ideal track + s = FindSplineToTrack( spline, idealTrackTag + trackLetter ); + } + if( !s ) { + s = FindSplineToTrack( spline, trackLetter ); + } + return s; +} + +/* +================ +rvTramCar::GetRandomSpline +================ +*/ +idSplinePath* rvTramCar::GetRandomSpline( idSplinePath* spline ) const { + if( !spline ) { + return NULL; + } + + int numActiveTargets = SortSplineTargets( spline ); + if( !numActiveTargets ) { + return NULL; + } + + idEntity* target = GetSplineTarget( spline, rvRandom::irand(0, numActiveTargets - 1) ); + return (target && target->IsType(idSplinePath::GetClassType())) ? static_cast(target) : NULL; +} + +/* +================ +rvTramCar::GetCurrentTrack +================ +*/ +int rvTramCar::GetCurrentTrack() const { + return ConvertToTrackNumber( GetTrackInfo(GetSpline()) ); +} + +/* +================ +rvTramCar::UpdateChannel +================ +*/ +void rvTramCar::UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( channel, &parms ); + } +} + +/* +================ +rvTramCar::AttenuateTrackChannel +================ +*/ +void rvTramCar::AttenuateTrackChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.frequencyShift = attenuation;//idMath::MidPointLerp( 0.0f, 1.0f, 1.1f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY, parms ); +} + +/* +================ +rvTramCar::AttenuateTramCarChannel +================ +*/ +void rvTramCar::AttenuateTramCarChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.volume = (attenuation + 1.0f) * 0.5f; + parms.frequencyShift = Max( 0.4f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY2, parms ); +} + +/* +================ +rvTramCar::LookForward +================ +*/ +bool rvTramCar::LookForward( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( GetPhysics()->GetAxis() ); + + Look( collisionFov, list ); + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( !OnSameTrackAs(list[ix]) ) { + list.Remove( list[ix] ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvTramCar::LookLeft +================ +*/ +bool rvTramCar::LookLeft( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRight +================ +*/ +bool rvTramCar::LookRight( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookLeftForTrackChange +================ +*/ +bool rvTramCar::LookLeftForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRightForTrackChange +================ +*/ +bool rvTramCar::LookRightForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::Look +================ +*/ +int rvSortByDist( const void* left, const void* right ) { + rvSplineMover* leftMover = *(rvSplineMover**)left; + rvSplineMover* rightMover = *(rvSplineMover**)right; + + return rightMover->spawnArgs.GetFloat("distAway") - leftMover->spawnArgs.GetFloat("distAway"); +} +bool rvTramCar::Look( const idFrustum& fov, idList& list ) const { + bool result = WhosVisible( fov, list ); + + if( g_debugVehicle.GetInteger() == 3 ) { + gameRenderWorld->DebugFrustum( colorRed, fov ); + } + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + list[ix]->spawnArgs.SetFloat( "distAway", (list[ix]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).Length() ); + } + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByDist ); + + // Do we need to get rid of these keyvalues? + + return result; +} + +/* +================ +rvTramCar::OnSameTrackAs +================ +*/ +bool rvTramCar::OnSameTrackAs( const rvSplineMover* tram ) const { + return tram && !GetTrackInfo(GetSpline()).Icmp( GetTrackInfo(tram->GetSpline()) ); +} + +/* +================ +rvTramCar::SameIdealTrackAs +================ +*/ +bool rvTramCar::SameIdealTrackAs( const rvSplineMover* tram ) const { + if( !tram ) { + return false; + } + return GetIdealTrack() == static_cast(tram)->GetIdealTrack(); +} + +/* +================ +rvTramCar::LookAround +================ +*/ +void rvTramCar::LookAround() { + idList moverList; + + if( !AdjustSpeed(moverList) ) { + SetSpeed( GetIdealSpeed() ); + } +} + +/* +================ +rvTramCar::AdjustSpeed +================ +*/ +bool rvTramCar::AdjustSpeed( idList& moverList ) { + if( LookForward(moverList) ) { + if( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugLine( colorRed, GetPhysics()->GetOrigin(), moverList[0]->GetPhysics()->GetOrigin() ); + } + + //Slow down and try to get onto other track if allowed + SetSpeed( moverList[0]->GetSpeed() * rvRandom::flrand(0.5f, 0.75f) ); + + // Is this safe if we are on a specified track + SetIdealTrack( (GetIdealTrack() + rvRandom::irand(1, 2)) % numTracksOnMap ); + return true; + } + + return false; +} + +/* +================ +rvTramCar::DriverSpeak +================ +*/ +idEntity* rvTramCar::DriverSpeak( const char* speechDecl, bool random ) { + if( !driver.IsValid() || driver->IsSpeaking() ) { + return NULL; + } + + driver->Speak( speechDecl, random ); + return driver; +} + +/* +================ +rvTramCar::OccupantSpeak +================ +*/ +idEntity* rvTramCar::OccupantSpeak( const char *speechDecl, bool random ) { + idEntityPtr occupant; + + if( !occupants.Num() ) { + return NULL; + } + + occupant = occupants[ rvRandom::irand(0, occupants.Num() - 1) ]; + if( !occupant.IsValid() || occupant->IsSpeaking() ) { + return NULL; + } + + occupant->Speak( speechDecl, random ); + return occupant; +} + +/* +================ +rvTramCar::PostDoneMoving +================ +*/ +void rvTramCar::PostDoneMoving() { + rvSplineMover::PostDoneMoving(); + + HeadTowardsIdealTrack(); +} + +/* +================ +rvTramCar::DeployRamp +================ +*/ +void rvTramCar::DeployRamp() { + OperateRamp( "open" ); +} + +/* +================ +rvTramCar::RetractRamp +================ +*/ +void rvTramCar::RetractRamp() { + OperateRamp( "close" ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation ) { + if( !operation || !operation[0] ) { + return; + } + + PlayAnim( ANIMCHANNEL_ALL, operation, 0 ); + + OperateRamp( operation, leftDoor ); + OperateRamp( operation, rightDoor ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation, idMover* door ) { + if( !operation || !operation[0] ) { + return; + } + + idAngles ang; + idVec3 vec; + if( !door ) { + return; + } + + if( !door->IsBound() ) { + return; + } + + ang = door->spawnArgs.GetAngles( va("%s%s", operation, "_rotation") ); + vec.Set( ang[0], ang[1], ang[2] ); + door->GetBindMaster()->ProcessEvent( &EV_RotateOnce, vec ); + + vec = door->spawnArgs.GetVector(va("%s%s", operation, "_dir")).ToNormal() * door->spawnArgs.GetFloat("distExt"); + door->PostEventSec( &EV_MoveAlongVector, 0.5f, vec ); +} + +/* +================ +rvTramCar::Event_OnStopMoving +================ +*/ +void rvTramCar::Event_OnStopMoving() { + rvSplineMover::Event_OnStopMoving(); + + speedSoundEffectsStateThread.SetState( "IdleSpeed" ); +} + +/* +================ +rvTramCar::Event_OnStartMoving +================ +*/ +void rvTramCar::Event_OnStartMoving() { + rvSplineMover::Event_OnStartMoving(); + + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); +} + +/* +================ +rvTramCar::Event_DriverSpeak +================ +*/ +void rvTramCar::Event_DriverSpeak( const char* voKey ) { + idThread::ReturnEntity( DriverSpeak(voKey) ); +} + +/* +================ +rvTramCar::Event_GetDriver +================ +*/ +void rvTramCar::Event_GetDriver() { + idThread::ReturnEntity( driver ); +} + +/* +================ +rvTramCar::Event_Activate +================ +*/ +void rvTramCar::Event_Activate( idEntity* activator ) { + rvSplineMover::Event_Activate( activator ); + + // If being activated by a spawner we need to attach to it + if( activator->IsType(rvSpawner::GetClassType()) ) { + static_cast(activator)->Attach( this ); + } else { + if( driver.IsValid() ) { + driver->ProcessEvent( &EV_Activate, activator ); + } + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->ProcessEvent( &EV_Activate, activator ); + } + } +} + +/* +================ +rvTramCar::Event_RadiusDamage +================ +*/ +void rvTramCar::Event_RadiusDamage( const idVec3& origin, const char* damageDefName ) { + gameLocal.RadiusDamage( origin, this, this, this, this, damageDefName ); +} + +/* +================ +rvTramCar::Event_SetIdealTrack +================ +*/ +void rvTramCar::Event_SetIdealTrack( const char* track ) { + idStr ideal = track; + + if( ideal.Length() > 1 ) { + idealTrackTag = ideal.Left( ideal.Length() - 1 ); + } + + idealTrackStateThread.SetState( "AssignedTrack" ); + SetIdealTrack( ConvertToTrackNumber(ideal.Right(1)) ); +} + +/* +================ +rvTramCar::Event_OpenDoors +================ +*/ +void rvTramCar::Event_OpenDoors() { + DeployRamp(); +} + +/* +================ +rvTramCar::Event_CloseDoors +================ +*/ +void rvTramCar::Event_CloseDoors() { + RetractRamp(); +} + +/* +================ +rvTramCar::Event_SetHealth +================ +*/ +void rvTramCar::Event_SetHealth ( float health ) { + this->health = health; +} + +CLASS_STATES_DECLARATION( rvTramCar ) + STATE( "IdleSpeed", rvTramCar::State_Idle ) + STATE( "NormalSpeed", rvTramCar::State_NormalSpeed ) + STATE( "ExcessiveSpeed", rvTramCar::State_ExcessiveSpeed ) + STATE( "RandomTrack", rvTramCar::State_RandomTrack ) + STATE( "AssignedTrack", rvTramCar::State_AssignedTrack ) +END_CLASS_STATES + +/* +================ +rvTramCar::State_Idle +================ +*/ +stateResult_t rvTramCar::State_Idle( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_idle_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_idle_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_NormalSpeed +================ +*/ +stateResult_t rvTramCar::State_NormalSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_normal_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_normal_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = 0.8f + 0.2f * ( GetSpeed() / GetNormalSpeed() ); + + if( speedScale >= 1.0f ) { + speedSoundEffectsStateThread.SetState( "ExcessiveSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_ExcessiveSpeed +================ +*/ +stateResult_t rvTramCar::State_ExcessiveSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_excessive_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_excessive_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = GetSpeed() / GetNormalSpeed(); + + if( speedScale < 1.0f ) { + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_RandomTrack +================ +*/ +stateResult_t rvTramCar::State_RandomTrack( const stateParms_t& parms ) { + SetIdealTrack( rvRandom::irand(0, numTracksOnMap - 1) ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_AssignedTrack +================ +*/ +stateResult_t rvTramCar::State_AssignedTrack( const stateParms_t& parms ) { + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +const idEventDef EV_TramCar_UseMountedGun( "useMountedGun", "e" ); +const idEventDef EV_TramCar_SetPlayerDamageEnt( "setPlayerDamageEnt", "f" ); + +CLASS_DECLARATION( rvTramCar, rvTramCar_Marine ) + EVENT( EV_TramCar_UseMountedGun, rvTramCar_Marine::Event_UseMountedGun ) + EVENT( EV_TramCar_SetPlayerDamageEnt, rvTramCar_Marine::Event_SetPlayerDamageEntity ) +END_CLASS + +/* +================ +rvTramCar_Marine::Spawn +================ +*/ +void rvTramCar_Marine::Spawn() { + RegisterStateThread( playerOccupationStateThread, "PlayerOccupation" ); + playerOccupationStateThread.SetState( "NotOccupied" ); + + RegisterStateThread( playerUsingMountedGunStateThread, "MountedGunInUse" ); + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); + lastHeal = 0; + healDelay = spawnArgs.GetInt( "heal_delay", "15" ); + healAmount = spawnArgs.GetInt( "heal_amount", "2" ); +} + +/* +================ +rvTramCar_Marine::MidThink +================ +*/ +void rvTramCar_Marine::MidThink() { + rvTramCar::MidThink(); + + if ( healDelay > 0 && lastHeal + healDelay < gameLocal.time && health < maxHealth ) { + health += healAmount; + + if ( health > maxHealth ) { + health = maxHealth; + } + } + + playerOccupationStateThread.Execute(); +} + +/* +================ +rvTramCar_Marine::ActivateTramHud +================ +*/ +void rvTramCar_Marine::ActivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "enterTram" ); +} + +/* +================ +rvTramCar_Marine::DeactivateTramHud +================ +*/ +void rvTramCar_Marine::DeactivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "leaveTram" ); +} + +/* +================ +rvTramCar_Marine::UpdateTramHud +================ +*/ +void rvTramCar_Marine::UpdateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->SetStateFloat( "tram_healthpct", GetHealthScale() ); +} + +/* +============ +rvTramCar_Marine::Damage +============ +*/ +void rvTramCar_Marine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + rvTramCar::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if( attacker == gameLocal.GetLocalPlayer() ) { + return; + } + + if( fl.takedamage && GetDamageScale() < 0.35f ) { + return; + } + + if( rvRandom::flrand() > spawnArgs.GetFloat("damageWarningFreq", "0.2") ) { + return; + } + + DriverSpeak( "lipsync_damageWarning", true ); +} + +/* +================ +rvTramCar_Marine::Killed +================ +*/ +void rvTramCar_Marine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + playerOccupationStateThread.Execute(); + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && EntityIsInside(player) ) { + player->SetDamageEntity( NULL );// This should fix the damage from tram car propogation issue + player->ExitVehicle( true );// This should fix the issue that was causing the player to get removed + } + + rvTramCar::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvTramCar_Marine::Save +================ +*/ +void rvTramCar_Marine::Save( idSaveGame *savefile ) const { + savefile->WriteInt( visibleEnemies.Num() ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Save( savefile ); + } + + playerOccupationStateThread.Save( savefile ); + playerUsingMountedGunStateThread.Save( savefile ); + + // no need to save this since it's set in the restore + //savefile->WriteInt( maxHealth ); // cnicholson: added unsaved var + savefile->WriteInt( lastHeal ); + savefile->WriteInt( healDelay ); + savefile->WriteInt( healAmount ); +} + +/* +================ +rvTramCar_Marine::Restore +================ +*/ +void rvTramCar_Marine::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + visibleEnemies.SetNum( num ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Restore( savefile ); + } + + playerOccupationStateThread.Restore( savefile, this ); + playerUsingMountedGunStateThread.Restore( savefile, this ); + + savefile->ReadInt( lastHeal ); + savefile->ReadInt( healDelay ); + savefile->ReadInt( healAmount ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); +} + +/* +================ +rvTramCar_Marine::LookAround +================ +*/ +void rvTramCar_Marine::LookAround() { + idList moverList; + + rvTramCar::LookAround(); + + if( !driver.IsValid() || driver->IsSpeaking() ) { + return; + } + + if( LookOverLeftShoulder(moverList)) { + DriverSpeak( "lipsync_warningLeft", true ); + driver->ScriptedAnim( "point_left", 0, false, true ); + } else if( LookOverRightShoulder(moverList)) { + DriverSpeak( "lipsync_warningRight", true ); + driver->ScriptedAnim( "point_right", 0, false, true ); + } +} + + +/* +================ +rvTramCar_Marine::LookOverLeftShoulder +================ +*/ +bool rvTramCar_Marine::LookOverLeftShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, -120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::LookOverRightShoulder +================ +*/ +bool rvTramCar_Marine::LookOverRightShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, 120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::EntityIsInside +================ +*/ +bool rvTramCar_Marine::EntityIsInside( const idEntity* entity ) const { + assert( entity ); + return GetPhysics()->GetAbsBounds().ContainsPoint( entity->GetPhysics()->GetAbsBounds().GetCenter() ); +} + +/* +================ +rvTramCar_Marine::DeployRamp +================ +*/ +void rvTramCar_Marine::DeployRamp() { + rvTramCar::DeployRamp(); + + if( weapons.Num() ) { + weapons[0]->Unlock(); + weapons[0]->EjectAllDrivers( true ); + } +} + +/* +================ +rvTramCar_Marine::RetractRamp +================ +*/ +void rvTramCar_Marine::RetractRamp() { + rvTramCar::RetractRamp(); + + UseMountedGun( gameLocal.GetLocalPlayer() ); +} + +/* +================ +rvTramCar_Marine::UseMountedGun +================ +*/ +void rvTramCar_Marine::UseMountedGun( idPlayer* player ) { + if( playerOccupationStateThread.CurrentStateIs("Occupied") && EntityIsInside(player) && weapons.Num() ) { + player->EnterVehicle( weapons[0] ); + weapons[0]->Lock(); + } +} + +/* +================ +rvTramCar_Marine::Event_UseMountedGun +================ +*/ +void rvTramCar_Marine::Event_UseMountedGun( idEntity* ent ) { + if( !ent || !ent->IsType(idPlayer::GetClassType()) ) { + return; + } + + UseMountedGun( static_cast(ent) ); +} + +/* +================ +rvTramCar_Marine::Event_SetPlayerDamageEntity +================ +*/ +void rvTramCar_Marine::Event_SetPlayerDamageEntity(float f) { + + gameLocal.GetLocalPlayer()->SetDamageEntity( (f? this : NULL) ); +} + +CLASS_STATES_DECLARATION( rvTramCar_Marine ) + STATE( "Occupied", rvTramCar_Marine::State_Occupied ) + STATE( "NotOccupied", rvTramCar_Marine::State_NotOccupied ) + STATE( "UsingMountedGun", rvTramCar_Marine::State_UsingMountedGun ) + STATE( "NotUsingMountedGun",rvTramCar_Marine::State_NotUsingMountedGun ) +END_CLASS_STATES + + +/* +================ +rvTramCar_Marine::State_Occupied +================ +*/ +stateResult_t rvTramCar_Marine::State_Occupied( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_DisableWeapon ); + ActivateTramHud( player ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( !PlayerIsInside() ) { + playerOccupationStateThread.SetState( "NotOccupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( NULL ); + fl.takedamage = false; + return SRESULT_DONE_WAIT; + } + + playerUsingMountedGunStateThread.Execute(); + + UpdateTramHud( player ); + + return SRESULT_WAIT; +} + + + +/* +================ +rvTramCar_Marine::State_NotOccupied +================ +*/ +stateResult_t rvTramCar_Marine::State_NotOccupied( const stateParms_t& parms ) { + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_EnableWeapon ); + DeactivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( PlayerIsInside() ) { + playerOccupationStateThread.SetState( "Occupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( this ); + fl.takedamage = true; + return SRESULT_DONE_WAIT; + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_UsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_UsingMountedGun( const stateParms_t& parms ) { + if( !parms.stage ) { + ActivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && !player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_NotUsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_NotUsingMountedGun( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "UsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +CLASS_DECLARATION( rvTramCar, rvTramCar_Strogg ) + EVENT( EV_PostSpawn, rvTramCar_Strogg::Event_PostSpawn ) +END_CLASS + +/* +================ +rvTramCar_Strogg::Spawn +================ +*/ +void rvTramCar_Strogg::Spawn() { + SetTarget( NULL ); + + RegisterStateThread( targetSearchStateThread, "targetSearch" ); + targetSearchStateThread.SetState( "LookingForTarget" ); +} + +/* +================ +rvTramCar_Strogg::SetTarget +================ +*/ +void rvTramCar_Strogg::SetTarget( idEntity* newTarget ) { + target = ConvertToMover( newTarget ); +} + +/* +================ +rvTramCar_Strogg::GetTarget +================ +*/ +const rvSplineMover* rvTramCar_Strogg::GetTarget() const { + return target.GetEntity(); +} + +/* +================ +rvTramCar_Strogg::Save +================ +*/ +void rvTramCar_Strogg::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + targetSearchStateThread.Save( savefile ); +} + +/* +================ +rvTramCar_Strogg::Restore +================ +*/ +void rvTramCar_Strogg::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + targetSearchStateThread.Restore( savefile, this ); +} + +/* +================ +rvTramCar_Strogg::WriteToSnapshot +================ +*/ +void rvTramCar_Strogg::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar_Strogg::ReadFromSnapshot +================ +*/ +void rvTramCar_Strogg::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar_Strogg::Event_PostSpawn +================ +*/ +void rvTramCar_Strogg::Event_PostSpawn() { + idEntity* enemy = gameLocal.FindEntity( spawnArgs.GetString("enemy") ); + + SetTarget( enemy ); + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->SetEnemy( enemy ); + } + + rvTramCar::Event_PostSpawn(); +} + +/* +================ +rvTramCar_Strogg::TargetIsToLeft +================ +*/ +bool rvTramCar_Strogg::TargetIsToLeft() { + idList list; + + if( LookLeft(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::TargetIsToRight +================ +*/ +bool rvTramCar_Strogg::TargetIsToRight() { + idList list; + + if( LookRight(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::LookAround +================ +*/ +void rvTramCar_Strogg::LookAround() { + targetSearchStateThread.Execute(); +} + +CLASS_STATES_DECLARATION( rvTramCar_Strogg ) + STATE( "LookingForTarget", rvTramCar_Strogg::State_LookingForTarget ) + STATE( "TargetInSight", rvTramCar_Strogg::State_TargetInSight ) +END_CLASS_STATES + +/* +================ +rvTramCar_Strogg::State_LookingForTarget +================ +*/ +stateResult_t rvTramCar_Strogg::State_LookingForTarget( const stateParms_t& parms ) { + static const int MSEC_DELAY = 100; + idList list; + + if( !parms.stage ) {// Go back to random if we are supposed to be random + //idealTrackStateThread.SetState( "RandomTrack" ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + // Could optimise based on what track + if( TargetIsToLeft() || TargetIsToRight() ) { + targetSearchStateThread.SetState( "TargetInSight" ); + return SRESULT_DONE; + } + + SetSpeed( GetIdealSpeed() ); + return SRESULT_DELAY( MSEC_DELAY ); +} + +/* +================ +rvTramCar_Strogg::State_TargetInSight +================ +*/ +stateResult_t rvTramCar_Strogg::State_TargetInSight( const stateParms_t& parms ) { + static const int MSEC_DELAY = 3000; + + if( !target.IsValid() || (!TargetIsToLeft() && !TargetIsToRight()) ) { + targetSearchStateThread.SetState( "LookingForTarget" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + SetIdealTrack( GetCurrentTrack() ); + SetSpeed( target->GetSpeed() * 0.5f ); + idealTrackStateThread.SetState( "AssignedTrack" ); + StartSound( "snd_horn", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idList list; + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + float dot = GetPhysics()->GetAxis()[0] * (target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToNormal(); + float deltaSpeed = target->GetIdealSpeed() * rvRandom::flrand(0.0f, 0.1f) * SignZero(dot); + SetSpeed( target->GetIdealSpeed() + deltaSpeed ); + + return SRESULT_DELAY( MSEC_DELAY ); +} diff --git a/source/game/SplineMover.h b/source/game/SplineMover.h new file mode 100644 index 0000000..5633c96 --- /dev/null +++ b/source/game/SplineMover.h @@ -0,0 +1,494 @@ +#ifndef __RV_SPLINE_MOVER_H +#define __RV_SPLINE_MOVER_H + +extern const idEventDef EV_SetSpline; + +struct splinePState_t { + idVec3 origin; + idVec3 localOrigin; + idMat3 axis; + idMat3 localAxis; + + float speed; + float idealSpeed; + float dist; + + float acceleration; + float deceleration; + + bool ShouldAccelerate() const; + bool ShouldDecelerate() const; + + void ApplyAccelerationDelta( float timeStepSec ); + + void ApplyDecelerationDelta( float timeStepSec ); + + void UpdateDist( float timeStepSec ); + void Clear(); + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + splinePState_t& Assign( const splinePState_t* state ); + splinePState_t& operator=( const splinePState_t& state ); + splinePState_t& operator=( const splinePState_t* state ); +}; + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +class rvPhysics_Spline : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Spline ); + + rvPhysics_Spline( void ); + virtual ~rvPhysics_Spline( void ); + + void Save( idSaveGame *savefile ) const; + void Event_PostRestore( void ); + void Restore( idRestoreGame *savefile ); + + void SetSpline( idCurve_Spline* spline ); + const idCurve_Spline* GetSpline() const { return spline; } + idCurve_Spline* GetSpline() { return spline; } + + void SetSplineEntity( idSplinePath* spline ); + const idSplinePath* GetSplineEntity() const { return splineEntity; } + idSplinePath* GetSplineEntity() { return splineEntity; } + + void SetLinearAcceleration( const float accel ); + void SetLinearDeceleration( const float decel ); + + void SetSpeed( float speed ); + float GetSpeed( void ) const; + + virtual bool StartingToMove( void ) const; + virtual bool StoppedMoving( void ) const; + + float ComputeDecelFromSpline( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool EvaluateSpline( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + bool EvaluateMaster( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + + void Activate( void ); + void Rest( void ); + + bool IsAtRest( void ) const; + bool IsAtEndOfSpline( void ) const; + bool IsAtBeginningOfSpline( void ) const; + + virtual bool IsPushable( void ) const; + + bool HasValidSpline( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + idVec3 & GetOrigin( int id = 0 ); + idMat3 & GetAxis( int id = 0 ); + + void SetMaster( idEntity *master, const bool orientated ); + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual const trace_t* GetBlockingInfo( void ) const; + virtual idEntity* GetBlockingEntity( void ) const; + +public: + stateResult_t State_Accelerating( const stateParms_t& parms ); + stateResult_t State_Decelerating( const stateParms_t& parms ); + stateResult_t State_Cruising( const stateParms_t& parms ); + +protected: + const idVec3 & GetLocalOrigin( int id = 0 ) const; + const idMat3 & GetLocalAxis( int id = 0 ) const; + + idVec3 & GetLocalOrigin( int id = 0 ); + idMat3 & GetLocalAxis( int id = 0 ); + +protected: + splinePState_t current; + splinePState_t saved; + + float splineLength; + idCurve_Spline* spline; + idEntityPtr splineEntity; + + trace_t pushResults; + + idClipModel* clipModel; + + rvStateThread accelDecelStateThread; + + CLASS_STATES_PROTOTYPE( rvPhysics_Spline ); +}; + +extern const idEventDef EV_DoneMoving; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +class rvSplineMover : public idAnimatedEntity { + CLASS_PROTOTYPE( rvSplineMover ); + +public: + void Spawn(); + virtual ~rvSplineMover(); + + virtual void SetSpeed( float newSpeed ); + virtual float GetSpeed() const; + virtual void SetIdealSpeed( float newIdealSpeed ); + virtual float GetIdealSpeed() const; + + virtual void SetSpline( idSplinePath* spline ); + virtual const idSplinePath* GetSpline() const; + virtual idSplinePath* GetSpline(); + + virtual void SetAcceleration( float accel ); + virtual void SetDeceleration( float decel ); + + virtual void CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ); + virtual void RestoreFromOverrides( const idDict* args ); + + int PlayAnim( int channel, const char* animName, int blendFrames ); + void CycleAnim( int channel, const char* animName, int blendFrames ); + void ClearChannel( int channel, int clearFrame ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected:// TramCar utility functions + void AddSelfToGlobalList(); + void RemoveSelfFromGlobalList(); + bool InGlobalList() const; + bool WhosVisible( const idFrustum& frustum, idList& list ) const; + +protected: + virtual idStr GetTrackInfo( const idSplinePath* track ) const; + rvSplineMover* ConvertToMover( idEntity* mover ) const; + idSplinePath* ConvertToSplinePath( idEntity* spline ) const; + + void CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ); + virtual void PreBind(); + + virtual void PreDoneMoving(); + virtual void PostDoneMoving(); + +protected: + void Event_PostSpawn(); + + void Event_SetSpline( idEntity* spline ); + void Event_GetSpline(); + + void Event_SetAcceleration( float accel ); + void Event_SetDeceleration( float decel ); + + void Event_OnAcceleration(); + void Event_OnDeceleration(); + void Event_OnCruising(); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_SetSpeed( float speed ); + void Event_GetSpeed(); + void Event_SetIdealSpeed( float speed ); + void Event_GetIdealSpeed(); + void Event_ApplySpeedScale( float scale ); + + void Event_SetCallBack(); + void Event_DoneMoving(); + + void Event_GetCurrentTrackInfo(); + void Event_GetTrackInfo( idEntity* track ); + + void Event_Activate( idEntity* activator ); + + void Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ); + void Event_PartBlocked( idEntity *blockingEntity ); + +protected: + rvPhysics_Spline physicsObj; + + float idealSpeed; + + int waitThreadId; + +private: + idLinkList splineMoverNode; + static idLinkList splineMovers; +}; + +//======================================================= +// +// rvTramCar +// +//======================================================= +class rvTramCar : public rvSplineMover { + CLASS_PROTOTYPE( rvTramCar ); + +public: + void Spawn(); + virtual ~rvTramCar(); + + virtual void Think(); + virtual void MidThink(); + + virtual float GetNormalSpeed() const { return spawnArgs.GetFloat("normalSpeed"); } + + virtual void SetIdealTrack( int track ); + virtual int GetIdealTrack() const; + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual float GetDamageScale() const; + virtual float GetHealthScale() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_Idle( const stateParms_t& parms ); + stateResult_t State_NormalSpeed( const stateParms_t& parms ); + stateResult_t State_ExcessiveSpeed( const stateParms_t& parms ); + + stateResult_t State_RandomTrack( const stateParms_t& parms ); + stateResult_t State_AssignedTrack( const stateParms_t& parms ); + +protected: + void SpawnDriver( const char* driverKey ); + void SpawnWeapons( const char* partKey ); + void SpawnOccupants( const char* partKey ); + idEntity* SpawnPart( const char* partDefName, const char* subPartDefName ); + void SpawnDoors(); + idMover* SpawnDoor( const char* key ); + + int SortSplineTargets( idSplinePath* spline ) const; + idEntity* GetSplineTarget( idSplinePath* spline, int index ) const; + + void HeadTowardsIdealTrack(); + idSplinePath* FindSplineToTrack( idSplinePath* spline, const idStr& track ) const; + idSplinePath* FindSplineToIdealTrack( idSplinePath* spline ) const; + idSplinePath* GetRandomSpline( idSplinePath* spline ) const; + + char ConvertToTrackLetter( int trackNum ) const { return trackNum + 'A'; } + int ConvertToTrackNumber( char trackLetter ) const { return trackLetter - 'A'; } + int ConvertToTrackNumber( const idStr& trackInfo ) const { return ConvertToTrackNumber(idStr::ToUpper(trackInfo.Right(1)[0])); } + int GetCurrentTrack() const; + + virtual void UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ); + virtual void AttenuateTrackChannel( float attenuation ); + virtual void AttenuateTramCarChannel( float attenuation ); + + virtual void RegisterStateThread( rvStateThread& stateThread, const char* name ); + + bool LookForward( idList& list ) const; + bool LookLeft( idList& list ) const; + bool LookRight( idList& list ) const; + + bool LookLeftForTrackChange( idList& list ) const; + bool LookRightForTrackChange( idList& list ) const; + + bool Look( const idFrustum& fov, idList& list ) const; + + virtual void LookAround(); + virtual bool AdjustSpeed( idList& moverList ); + + virtual bool OnSameTrackAs( const rvSplineMover* tram ) const; + virtual bool SameIdealTrackAs( const rvSplineMover* tram ) const; + + virtual idEntity* DriverSpeak( const char *speechDecl, bool random = false ); + virtual idEntity* OccupantSpeak( const char *speechDecl, bool random = false ); + + virtual void PostDoneMoving(); + + virtual void DeployRamp(); + virtual void RetractRamp(); + void OperateRamp( const char* operation ); + void OperateRamp( const char* operation, idMover* door ); + +protected: + void Event_DriverSpeak( const char* voKey ); + void Event_GetDriver(); + void Event_Activate( idEntity* activator ); + void Event_RadiusDamage( const idVec3& origin, const char* damageDefName ); + void Event_SetIdealTrack( const char* track ); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_OpenDoors(); + void Event_CloseDoors(); + + void Event_SetHealth( float health ); + +protected: + int idealTrack; + idStr idealTrackTag;// HACK + + mutable idFrustum collisionFov; + + rvStateThread idealTrackStateThread; + rvStateThread speedSoundEffectsStateThread; + + idEntityPtr driver; + idList< idEntityPtr > occupants; + idList< idEntityPtr > weapons; + + int numTracksOnMap; + + idEntityPtr leftDoor; + idEntityPtr rightDoor; + + CLASS_STATES_PROTOTYPE( rvTramCar ); +}; + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +class rvTramCar_Marine : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Marine ); + +public: + void Spawn(); + + virtual void MidThink(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: + stateResult_t State_Occupied( const stateParms_t& parms ); + stateResult_t State_NotOccupied( const stateParms_t& parms ); + + stateResult_t State_UsingMountedGun( const stateParms_t& parms ); + stateResult_t State_NotUsingMountedGun( const stateParms_t& parms ); + +protected: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void LookAround(); + + bool LookOverLeftShoulder( idList& list ); + bool LookOverRightShoulder( idList& list ); + + bool EntityIsInside( const idEntity* entity ) const; + bool PlayerIsInside() const { return EntityIsInside(gameLocal.GetLocalPlayer()); } + + void ActivateTramHud( idPlayer* player ); + void DeactivateTramHud( idPlayer* player ); + void UpdateTramHud( idPlayer* player ); + + virtual void DeployRamp(); + virtual void RetractRamp(); + + void UseMountedGun( idPlayer* player ); + + void Event_UseMountedGun( idEntity* ent ); + void Event_SetPlayerDamageEntity(float f); + +protected: + idList< idEntityPtr > visibleEnemies; + + rvStateThread playerOccupationStateThread; + rvStateThread playerUsingMountedGunStateThread; + + int maxHealth; + int lastHeal; + int healDelay; + int healAmount; + + CLASS_STATES_PROTOTYPE( rvTramCar_Marine ); +}; + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +class rvTramCar_Strogg : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Strogg ); + +public: + void Spawn(); + + virtual void SetTarget( idEntity* newTarget ); + virtual const rvSplineMover* GetTarget() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_LookingForTarget( const stateParms_t& parms ); + stateResult_t State_TargetInSight( const stateParms_t& parms ); + +protected: + bool TargetIsToLeft(); + bool TargetIsToRight(); + + virtual void LookAround(); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr target; + + rvStateThread targetSearchStateThread; + + CLASS_STATES_PROTOTYPE( rvTramCar_Strogg ); +}; + +#endif diff --git a/source/game/Target.cpp b/source/game/Target.cpp new file mode 100644 index 0000000..78abf12 --- /dev/null +++ b/source/game/Target.cpp @@ -0,0 +1,2321 @@ +/* + +Invisible entities that affect other entities or the world when activated. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTarget ) +END_CLASS + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Remove ) + EVENT( EV_Activate, idTarget_Remove::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Remove::Event_Activate +================ +*/ +void idTarget_Remove::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Show ) + EVENT( EV_Activate, idTarget_Show::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Show::Event_Activate +================ +*/ +void idTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Damage ) + EVENT( EV_Activate, idTarget_Damage::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Damage::Event_Activate +================ +*/ +void idTarget_Damage::Event_Activate( idEntity *activator ) { + int i; + const char *damage; + idEntity * ent; + + damage = spawnArgs.GetString( "def_damage", "damage_generic" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + } + } +} + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SessionCommand ) + EVENT( EV_Activate, idTarget_SessionCommand::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SessionCommand::Event_Activate +================ +*/ +void idTarget_SessionCommand::Event_Activate( idEntity *activator ) { + gameLocal.sessionCommand = spawnArgs.GetString( "command" ); +} + + +/* +=============================================================================== + +idTarget_EndLevel + +Just a modified form of idTarget_SessionCommand +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EndLevel::Event_Activate +================ +*/ +void idTarget_EndLevel::Event_Activate( idEntity *activator ) { + idStr nextMap; + +#ifdef ID_DEMO_BUILD + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endofDemo"; + return; + } +#else + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endOfGame"; + return; + } +#endif + if ( !spawnArgs.GetString( "nextMap", "", nextMap ) ) { + gameLocal.Printf( "idTarget_SessionCommand::Event_Activate: no nextMap key\n" ); + return; + } + + if ( spawnArgs.GetInt( "devmap", "0" ) ) { + gameLocal.sessionCommand = "devmap "; // only for special demos + } else { + gameLocal.sessionCommand = "map "; + } + + gameLocal.sessionCommand += nextMap; + +// RAVEN BEGIN +// jscott: additional info for multiple maps + const char* entityFilter; + if( spawnArgs.GetString( "entityFilter", "", &entityFilter ) && *entityFilter ) { + gameLocal.sessionCommand += " "; + gameLocal.sessionCommand += entityFilter; + } +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_WaitForButton ) + EVENT( EV_Activate, idTarget_WaitForButton::Event_Activate ) +END_CLASS + +/* +================ +idTarget_WaitForButton::Event_Activate +================ +*/ +void idTarget_WaitForButton::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTarget_WaitForButton::Think +================ +*/ +void idTarget_WaitForButton::Think( void ) { + idPlayer *player; + + if ( thinkFlags & TH_THINK ) { + player = gameLocal.GetLocalPlayer(); + if ( player && ( !player->oldButtons & BUTTON_ATTACK ) && ( player->usercmd.buttons & BUTTON_ATTACK ) ) { + player->usercmd.buttons &= ~BUTTON_ATTACK; + BecomeInactive( TH_THINK ); + ActivateTargets( player ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetGlobalShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetGlobalShaderTime ) +EVENT( EV_Activate, idTarget_SetGlobalShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetGlobalShaderTime::Event_Activate +================ +*/ +void idTarget_SetGlobalShaderTime::Event_Activate( idEntity *activator ) { + int parm = spawnArgs.GetInt( "globalParm" ); + float time = -MS2SEC( gameLocal.time ); + if ( parm >= 0 && parm < MAX_GLOBAL_SHADER_PARMS ) { + gameLocal.globalShaderParms[parm] = time; + } +} + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderParm ) + EVENT( EV_Activate, idTarget_SetShaderParm::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderParm::Event_Activate +================ +*/ +void idTarget_SetShaderParm::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float value; + idVec3 color; + int parmnum; + + // set the color on the targets + if ( spawnArgs.GetVector( "_color", "1 1 1", color ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + } + } + } + + // set any shader parms on the targets + for( parmnum = 0; parmnum < MAX_ENTITY_SHADER_PARMS; parmnum++ ) { + if ( spawnArgs.GetFloat( va( "shaderParm%d", parmnum ), "0", value ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderTime ) + EVENT( EV_Activate, idTarget_SetShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderTime::Event_Activate +================ +*/ +void idTarget_SetShaderTime::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float time; + + time = -MS2SEC( gameLocal.time ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( SHADERPARM_TIMEOFFSET, time ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SetLightParm( SHADERPARM_TIMEOFFSET, time ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_FadeEntity ) + EVENT( EV_Activate, idTarget_FadeEntity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_FadeEntity::idTarget_FadeEntity +================ +*/ +idTarget_FadeEntity::idTarget_FadeEntity( void ) { + fadeFrom.Zero(); + fadeStart = 0; + fadeEnd = 0; +} + +/* +================ +idTarget_FadeEntity::Save +================ +*/ +void idTarget_FadeEntity::Save( idSaveGame *savefile ) const { + savefile->WriteVec4( fadeFrom ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Restore +================ +*/ +void idTarget_FadeEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadVec4( fadeFrom ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Event_Activate +================ +*/ +void idTarget_FadeEntity::Event_Activate( idEntity *activator ) { + idEntity *ent; + int i; + + if ( !targets.Num() ) { + return; + } + + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->GetColor( fadeFrom ); + break; + } + } + + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "fadetime" ) ); +} + +/* +================ +idTarget_FadeEntity::Think +================ +*/ +void idTarget_FadeEntity::Think( void ) { + int i; + idEntity *ent; + idVec4 color; + idVec4 fadeTo; + float frac; + + if ( thinkFlags & TH_THINK ) { + GetColor( fadeTo ); + if ( gameLocal.time >= fadeEnd ) { + color = fadeTo; + BecomeInactive( TH_THINK ); + } else { + frac = ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ); + color.Lerp( fadeFrom, fadeTo, frac ); + } + + // set the color on the targets + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeIn ) + EVENT( EV_Activate, idTarget_LightFadeIn::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeIn::Event_Activate +================ +*/ +void idTarget_LightFadeIn::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeIn( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeOut ) + EVENT( EV_Activate, idTarget_LightFadeOut::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeOut::Event_Activate +================ +*/ +void idTarget_LightFadeOut::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeOut( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Give ) + EVENT( EV_Activate, idTarget_Give::Event_Activate ) +// RAVEN BEGIN +// abahr: + EVENT( EV_PostSpawn, idTarget_Give::Event_PostSpawn ) +// RAVEN END +END_CLASS + +/* +================ +idTarget_Give::Spawn +================ +*/ +void idTarget_Give::Spawn( void ) { +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + if ( spawnArgs.GetBool( "onSpawn" ) ) { + PostEventMS( &EV_PostSpawn, 50 ); + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +#endif + } +// RAVEN END + + // precache decls + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), false, false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +} + +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs +/* +================ +idTarget_Give::Event_PostSpawn +================ +*/ +void idTarget_Give::Event_PostSpawn() { + ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); +} +// RAVEN END + +/* +================ +idTarget_Give::Event_Activate +================ +*/ +void idTarget_Give::Event_Activate( idEntity *activator ) { + + if ( spawnArgs.GetBool( "development" ) && developer.GetInteger() == 0 ) { + return; + } + + static int giveNum = 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( dict ) { + idDict d2; + d2.Copy( *dict ); + d2.Set( "name", va( "givenitem_%i", giveNum++ ) ); + idEntity *ent = NULL; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.SpawnEntityDef( d2, &ent ) && ent && ent->IsType( idItem::GetClassType() ) ) { +// RAVEN END + idItem *item = static_cast(ent); + item->GiveToPlayer( gameLocal.GetLocalPlayer() ); + item->PostEventMS ( &EV_Remove, 0 ); + + // rules are that if we are given a weapon by a character, we are supposed to switch to it regardless of + // whether auto-switch is on or not. + if ( !gameLocal.isMultiplayer && !player->GetUserInfo()->GetBool( "ui_autoSwitch" ) && !spawnArgs.GetBool( "onSpawn" )) { + const idKeyValue *kv = ent->spawnArgs.FindKey( "weaponclass" ); + if ( kv ) { + // does player already have this weapon selected? + if ( player->weapon && idStr::Icmp(player->weapon->GetClassname(), kv->GetValue())) { + kv = ent->spawnArgs.FindKey( "inv_weapon" ); + if ( kv ) { + // nope, so attempt to switch to this weapon + player->SelectWeapon(kv->GetValue()); + } + } + } + } + } + } + kv = spawnArgs.MatchPrefix( "item", kv ); + } + } +} + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveEmail ) +EVENT( EV_Activate, idTarget_GiveEmail::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Spawn +================ +*/ +void idTarget_GiveEmail::Spawn( void ) { +} + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveEmail::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using email +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + const idDeclPDA *pda = player->GetPDA(); + if ( pda ) { + player->GiveEmail( spawnArgs.GetString( "email" ) ); + } else { + player->ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_PDANeeded" ), true ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetModel ) + EVENT( EV_Activate, idTarget_SetModel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetModel::Spawn +================ +*/ +void idTarget_SetModel::Spawn( void ) { + const char *model; + + model = spawnArgs.GetString( "newmodel" ); + if ( declManager->FindType( DECL_MODELDEF, model, false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( model ); + // precache .cm files only + collisionModelManager->PreCacheModel( gameLocal.GetMapName(), model ); + } +} + +/* +================ +idTarget_SetModel::Event_Activate +================ +*/ +void idTarget_SetModel::Event_Activate( idEntity *activator ) { + for( int i = 0; i < targets.Num(); i++ ) { + idEntity *ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetModel( spawnArgs.GetString( "newmodel" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +const idEventDef EV_RestoreInfluence( "" ); +const idEventDef EV_GatherEntities( "" ); +const idEventDef EV_Flash( "", "fd" ); +const idEventDef EV_ClearFlash( "", "f" ); + +CLASS_DECLARATION( idTarget, idTarget_SetInfluence ) + EVENT( EV_Activate, idTarget_SetInfluence::Event_Activate ) + EVENT( EV_RestoreInfluence, idTarget_SetInfluence::Event_RestoreInfluence ) + EVENT( EV_GatherEntities, idTarget_SetInfluence::Event_GatherEntities ) + EVENT( EV_Flash, idTarget_SetInfluence::Event_Flash ) + EVENT( EV_ClearFlash, idTarget_SetInfluence::Event_ClearFlash ) +END_CLASS + +/* +================ +idTarget_SetInfluence::idTarget_SetInfluence +================ +*/ +idTarget_SetInfluence::idTarget_SetInfluence( void ) { + flashIn = 0.0f; + flashOut = 0.0f; + delay = 0.0f; + switchToCamera = NULL; + soundFaded = false; + restoreOnTrigger = false; +} + +/* +================ +idTarget_SetInfluence::Save +================ +*/ +void idTarget_SetInfluence::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( lightList.Num() ); + for( i = 0; i < lightList.Num(); i++ ) { + savefile->WriteInt( lightList[ i ] ); + } + + savefile->WriteInt( guiList.Num() ); + for( i = 0; i < guiList.Num(); i++ ) { + savefile->WriteInt( guiList[ i ] ); + } + + savefile->WriteInt( soundList.Num() ); + for( i = 0; i < soundList.Num(); i++ ) { + savefile->WriteInt( soundList[ i ] ); + } + + savefile->WriteInt( genericList.Num() ); + for( i = 0; i < genericList.Num(); i++ ) { + savefile->WriteInt( genericList[ i ] ); + } + + savefile->WriteFloat( flashIn ); + savefile->WriteFloat( flashOut ); + + savefile->WriteFloat( delay ); + + savefile->WriteString( flashInSound ); + savefile->WriteString( flashOutSound ); + + savefile->WriteObject( switchToCamera ); + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); + + savefile->WriteBool( soundFaded ); + savefile->WriteBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Restore +================ +*/ +void idTarget_SetInfluence::Restore( idRestoreGame *savefile ) { + int i, num; + int itemNum; + float set; + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + lightList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + guiList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + soundList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + genericList.Append( itemNum ); + } + + savefile->ReadFloat( flashIn ); + savefile->ReadFloat( flashOut ); + + savefile->ReadFloat( delay ); + + savefile->ReadString( flashInSound ); + savefile->ReadString( flashOutSound ); + + savefile->ReadObject( reinterpret_cast( switchToCamera ) ); + + savefile->ReadFloat( set ); + fovSetting.SetStartTime( set ); + savefile->ReadFloat( set ); + fovSetting.SetDuration( set ); + savefile->ReadFloat( set ); + fovSetting.SetStartValue( set ); + savefile->ReadFloat( set ); + fovSetting.SetEndValue( set ); + + savefile->ReadBool( soundFaded ); + savefile->ReadBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Spawn +================ +*/ +void idTarget_SetInfluence::Spawn() { + PostEventMS( &EV_GatherEntities, 0 ); + flashIn = spawnArgs.GetFloat( "flashIn", "0" ); + flashOut = spawnArgs.GetFloat( "flashOut", "0" ); + flashInSound = spawnArgs.GetString( "snd_flashin" ); + flashOutSound = spawnArgs.GetString( "snd_flashout" ); + delay = spawnArgs.GetFloat( "delay" ); + soundFaded = false; + restoreOnTrigger = false; + + // always allow during cinematics + cinematic = true; +} + +/* +================ +idTarget_SetInfluence::Event_Flash +================ +*/ +void idTarget_SetInfluence::Event_Flash( float flash, int out ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( idVec4( 1, 1, 1, 1 ), flash ); + const idSoundShader *shader = NULL; + if ( !out && flashInSound.Length() ){ + shader = declManager->FindSound( flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( out && ( flashOutSound.Length() || flashInSound.Length() ) ) { + shader = declManager->FindSound( flashOutSound.Length() ? flashOutSound : flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + PostEventSec( &EV_ClearFlash, flash, flash ); +} + + +/* +================ +idTarget_SetInfluence::Event_ClearFlash +================ +*/ +void idTarget_SetInfluence::Event_ClearFlash( float flash ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( vec4_zero , flash ); +} +/* +================ +idTarget_SetInfluence::Event_GatherEntities +================ +*/ +void idTarget_SetInfluence::Event_GatherEntities() { + int i, listedEntities; + idEntity *entityList[ MAX_GENTITIES ]; + + bool lights = spawnArgs.GetBool( "effect_lights" ); + bool sounds = spawnArgs.GetBool( "effect_sounds" ); + bool guis = spawnArgs.GetBool( "effect_guis" ); + bool models = spawnArgs.GetBool( "effect_models" ); + bool vision = spawnArgs.GetBool( "effect_vision" ); + bool targetsOnly = spawnArgs.GetBool( "targetsOnly" ); + + lightList.Clear(); + guiList.Clear(); + soundList.Clear(); + + if ( spawnArgs.GetBool( "effect_all" ) ) { + lights = sounds = guis = models = vision = true; + } + + if ( targetsOnly ) { + listedEntities = targets.Num(); + for ( i = 0; i < listedEntities; i++ ) { + entityList[i] = targets[i].GetEntity(); + } + } else { + float radius = spawnArgs.GetFloat( "radius" ); + listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + } + + for( i = 0; i < listedEntities; i++ ) { + idEntity *ent = entityList[ i ]; + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( lights && ent->IsType( idLight::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + lightList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( sounds && ent->IsType( idSound::GetClassType() ) && ent->spawnArgs.FindKey( "snd_demonic" ) ) { +// RAVEN END + soundList.Append( ent->entityNumber ); + continue; + } + if ( guis && ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ 0 ] && ent->spawnArgs.FindKey( "gui_demonic" ) ) { + guiList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idStaticEntity::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + genericList.Append( ent->entityNumber ); + continue; + } + } + } + idStr temp; + temp = spawnArgs.GetString( "switchToView" ); + switchToCamera = ( temp.Length() ) ? gameLocal.FindEntity( temp ) : NULL; + +} + +/* +================ +idTarget_SetInfluence::Event_Activate +================ +*/ +void idTarget_SetInfluence::Event_Activate( idEntity *activator ) { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + const char *parm; + const char *skin; + bool update; + idVec3 color; + idVec4 colorTo; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + + if ( spawnArgs.GetBool( "triggerActivate" ) ) { + if ( restoreOnTrigger ) { + ProcessEvent( &EV_RestoreInfluence ); + restoreOnTrigger = false; + return; + } + restoreOnTrigger = true; + } + + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + + if ( delay > 0.0f ) { + PostEventSec( &EV_Activate, delay, activator ); + delay = 0.0f; + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + return; + } else if ( fadeTime && !soundFaded ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( activator ); + } + + if ( flashIn ) { + PostEventSec( &EV_Flash, 0.0f, flashIn, 0 ); + } + + parm = spawnArgs.GetString( "snd_influence" ); + if ( parm && *parm ) { + PostEventSec( &EV_StartSoundShader, flashIn, parm, SND_CHANNEL_ANY ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventSec( &EV_Activate, flashIn + 0.05f, this ); + } + +// RAVEN BEGIN +// mekberg: allow for initial fov and both fovs. + int fov = spawnArgs.GetInt( "fov" ); + int fovInitial = spawnArgs.GetInt( "fov_initial" ); + if ( fov && fovInitial) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, fov ); + BecomeActive( TH_THINK ); + } else if ( fov ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), player->DefaultFov(), fov ); + BecomeActive( TH_THINK ); + } else if ( fovInitial ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, player->DefaultFov() ); + BecomeActive( TH_THINK ); + } +// RAVEN END + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "color_demonic" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + parm = light->spawnArgs.GetString( "mat_demonic" ); + if ( parm && *parm ) { + light->SetShader( parm ); + } + + color = light->spawnArgs.GetVector( "_color" ); + color = light->spawnArgs.GetVector( "color_demonic", color.ToString() ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + parm = sound->spawnArgs.GetString( "snd_demonic" ); + if ( parm && *parm ) { + if ( sound->spawnArgs.GetBool( "overlayDemonic" ) ) { + sound->StartSound( "snd_demonic", SND_CHANNEL_DEMONIC, 0, false, NULL ); + } else { + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( parm ); + } + } + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || ent->GetRenderEntity() == NULL ) { + continue; + } + update = false; + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] && ent->spawnArgs.FindKey( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ) ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ), true ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + + } + + player->SetInfluenceLevel( spawnArgs.GetInt( "influenceLevel" ) ); + + int snapAngle = spawnArgs.GetInt( "snapAngle" ); + if ( snapAngle ) { + idAngles ang( 0, snapAngle, 0 ); + player->SetViewAngles( ang ); + player->SetAngles( ang ); + } + + if ( spawnArgs.GetBool( "effect_vision" ) ) { + parm = spawnArgs.GetString( "mtrVision" ); + skin = spawnArgs.GetString( "skinVision" ); + player->SetInfluenceView( parm, skin, spawnArgs.GetInt( "visionRadius" ), this ); + } + + parm = spawnArgs.GetString( "mtrWorld" ); + if ( parm && *parm ) { + gameLocal.SetGlobalMaterial( declManager->FindMaterial( parm ) ); + } + + if ( !restoreOnTrigger ) { + PostEventMS( &EV_RestoreInfluence, SEC2MS( spawnArgs.GetFloat( "time" ) ) ); + } +} + +/* +================ +idTarget_SetInfluence::Think +================ +*/ +void idTarget_SetInfluence::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + if ( !spawnArgs.GetBool( "leaveFOV" ) ) { + player->SetInfluenceFov( 0 ); + } + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +================ +idTarget_SetInfluence::Event_RestoreInfluence +================ +*/ +void idTarget_SetInfluence::Event_RestoreInfluence() { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + bool update; + idVec3 color; + idVec4 colorTo; + + if ( flashOut ) { + PostEventSec( &EV_Flash, 0.0f, flashOut, 1 ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventMS( &EV_Activate, 0.0f, this ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + colorTo.Set( 1.0f, 1.0f, 1.0f, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + if ( !light->spawnArgs.GetBool( "leave_demonic_mat" ) ) { + const char *texture = light->spawnArgs.GetString( "texture", "lights/squarelight1" ); + light->SetShader( texture ); + } + color = light->spawnArgs.GetVector( "_color" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( sound->spawnArgs.GetString( "s_shader" ) ); + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || GetRenderEntity() == NULL ) { + continue; + } + update = false; + for( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui" : va( "gui%d", j+1 ) ) ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceLevel( 0 ); + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + player->SetInfluenceFov( 0 ); + gameLocal.SetGlobalMaterial( NULL ); + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime / 2.0f ); + } + +} + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetKeyVal ) + EVENT( EV_Activate, idTarget_SetKeyVal::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetKeyVal::Event_Activate +================ +*/ +void idTarget_SetKeyVal::Event_Activate( idEntity *activator ) { + int i; + idStr key, val; + idEntity *ent; + const idKeyValue *kv; + int n; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + kv = spawnArgs.MatchPrefix("keyval"); + while ( kv ) { + n = kv->GetValue().Find( ";" ); + if ( n > 0 ) { + key = kv->GetValue().Left( n ); + val = kv->GetValue().Right( kv->GetValue().Length() - n - 1 ); + ent->spawnArgs.Set( key, val ); + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time ); + } + } + } + } + kv = spawnArgs.MatchPrefix( "keyval", kv ); + } + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetFov ) + EVENT( EV_Activate, idTarget_SetFov::Event_Activate ) +END_CLASS + + +/* +================ +idTarget_SetFov::Save +================ +*/ +void idTarget_SetFov::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); +} + +/* +================ +idTarget_SetFov::Restore +================ +*/ +void idTarget_SetFov::Restore( idRestoreGame *savefile ) { + float setting; + + savefile->ReadFloat( setting ); + fovSetting.SetStartTime( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetDuration( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetStartValue( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetEndValue( setting ); + + fovSetting.GetCurrentValue( gameLocal.time ); +} + +/* +================ +idTarget_SetFov::Event_Activate +================ +*/ +void idTarget_SetFov::Event_Activate( idEntity *activator ) { + // always allow during cinematics + cinematic = true; + + idPlayer *player = gameLocal.GetLocalPlayer(); + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "time" ) ), player ? player->DefaultFov() : g_fov.GetFloat(), spawnArgs.GetFloat( "fov" ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTarget_SetFov::Think +================ +*/ +void idTarget_SetFov::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + player->SetInfluenceFov( 0.0f ); + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetPrimaryObjective ) + EVENT( EV_Activate, idTarget_SetPrimaryObjective::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetPrimaryObjective::Event_Activate +================ +*/ +void idTarget_SetPrimaryObjective::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->objectiveSystem ) { + player->objectiveSystem->SetStateString( "missionobjective", spawnArgs.GetString( "text", common->GetLocalizedString( "#str_104253" ) ) ); + } +} + +// RAVEN BEGIN +// bdube: added database entry trigger +// twhitaker: removed database entry trigger +/* +=============================================================================== + +rvTarget_AddDatabaseEntry + +=============================================================================== +*/ +/* +CLASS_DECLARATION( idTarget, rvTarget_AddDatabaseEntry ) + EVENT( EV_Activate, rvTarget_AddDatabaseEntry::Event_Activate ) +END_CLASS +*/ +/* +================ +rvTarget_AddDatabaseEntry::Event_Activate +================ +*/ +/*void rvTarget_AddDatabaseEntry::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveDatabaseEntry ( gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_db", "" ) ) ); + } +} +*/ +// jshepard: secret area trigger +/* +=============================================================================== + +rvTarget_SecretArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_SecretArea ) + EVENT( EV_Activate, rvTarget_SecretArea::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_SecretArea::Event_Activate +================ +*/ +void rvTarget_SecretArea::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->DiscoverSecretArea( spawnArgs.GetString ( "description" )); + } +} + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +const idEventDef EV_SetShieldPercent( "setShieldPercent", "f" ); +const idEventDef EV_SetBossMaxHealth( "setMaxBossHealth", "f" ); +const idEventDef EV_AllowShieldBar( "allowShieldBar", "f" ); +const idEventDef EV_AllowShieldWarningBar( "allowShieldWarnBar", "f" ); + + +CLASS_DECLARATION( idTarget, rvTarget_BossBattle ) + EVENT( EV_Activate, rvTarget_BossBattle::Event_Activate ) + EVENT( EV_SetShieldPercent, rvTarget_BossBattle::Event_SetShieldPercent ) + EVENT( EV_SetBossMaxHealth, rvTarget_BossBattle::Event_SetBossMaxHealth ) + EVENT( EV_AllowShieldBar, rvTarget_BossBattle::Event_AllowShieldBar ) + EVENT( EV_AllowShieldWarningBar, rvTarget_BossBattle::Event_AllowShieldWarningBar ) +END_CLASS + +/* +================ +rvTarget_BossBattle::Event_Activate +================ +*/ +void rvTarget_BossBattle::Event_Activate( idEntity *activator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + idEntity* enemy = gameLocal.FindEntity ( spawnArgs.GetString ( "target" ) ); + if ( player && enemy ) { + player->StartBossBattle ( enemy ); + } + + StartSound ( "snd_activate", SND_CHANNEL_ANY, 0, false, NULL ); +} + + +/* +================ +rvTarget_BossBattle::Event_AllowShieldBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldBar" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldBar" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_AllowShieldWarningBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldWarningBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldWarn" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldWarn" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_SetShieldPercent +================ +*/ +void rvTarget_BossBattle::Event_SetShieldPercent( float percent ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } +} + +/* +================ +rvTarget_BossBattle::Event_SetMaxBossHealth +================ +*/ +void rvTarget_BossBattle::Event_SetBossMaxHealth( float f ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + + hud->SetStateInt ( "boss_maxhealth",f ); + } +} + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +const idEventDef EV_LaunchProjectile( "launchProjectile", "e" ); +CLASS_DECLARATION( idTarget, rvTarget_LaunchProjectile ) + EVENT( EV_Activate, rvTarget_LaunchProjectile::Event_Activate ) + EVENT( EV_LaunchProjectile, rvTarget_LaunchProjectile::Event_LaunchProjectile ) +END_CLASS + +/* +================ +rvTarget_LaunchProjectile::Spawn +================ +*/ +void rvTarget_LaunchProjectile::Spawn( void ) { + if ( spawnArgs.GetBool( "start_on" ) ) { + PostEventMS( &EV_Activate, 0, this ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_LaunchProjectile +================ +*/ +void rvTarget_LaunchProjectile::Event_LaunchProjectile( idEntity *activator ) { + const idDict* projectileDict; + idEntity* ent; + + projectileDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ), false ); + if ( !projectileDict ) { + gameLocal.Error ( "Could not find entityDef '%s' for launch projectile target '%s'", spawnArgs.GetString ( "def_projectile" ), GetName() ); + } + + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + gameLocal.Error( "Could not spawn entityDef '%s'", projectileDict->GetString( "classname" ) ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idProjectile", ent->GetClassname() ); + } + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + idProjectile* proj = static_cast(ent); + + proj->Create( this, GetPhysics()->GetOrigin(), dir, this ); + proj->Launch( GetPhysics()->GetOrigin(), dir, GetPhysics()->GetLinearVelocity(), 0.0f, 1.0f ); + + + if ( targets.Num() && proj->IsType ( idGuidedProjectile::GetClassType ( ) ) ) { + static_cast(proj)->GuideTo ( targets[0] ); + } + + if ( spawnArgs.GetFloat( "loop_interval" ) ) + { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "loop_interval" ), activator ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_Activate +================ +*/ +void rvTarget_LaunchProjectile::Event_Activate( idEntity *activator ) { + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + if ( spawnArgs.GetString( "fx_launch", NULL ) ) + { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_launch" ), GetPhysics()->GetOrigin(), dir.ToMat3() ); + } + + if ( spawnArgs.GetFloat( "delay" ) ) + { + PostEventSec( &EV_LaunchProjectile, spawnArgs.GetFloat( "delay" ), activator ); + return; + } + ProcessEvent( &EV_LaunchProjectile, activator ); +} + +/* +=============================================================================== + +rvTarget_ExitArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_ExitAreaAlert ) + EVENT( EV_Activate, rvTarget_ExitAreaAlert::Event_Activate ) +END_CLASS + +void rvTarget_ExitAreaAlert::Event_Activate( idEntity *activator ) { + gameLocal.UpdateEndLevel(); +} + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ + +int CompareAmmoData( const void* ammo1, const void* ammo2) { + if ((( ammodata_t *)ammo2)->percentFull > (( ammodata_t *)ammo1)->percentFull ) { + return -1; + } else { + return 1; + } +} + +CLASS_DECLARATION( idTarget, rvTarget_AmmoStash ) + EVENT( EV_Activate, rvTarget_AmmoStash::Event_Activate ) +END_CLASS + +void rvTarget_AmmoStash::Event_Activate( idEntity *activator ) { + + const idKeyValue* kv; + idPlayer* player; + int typeCount; + idEntity* entAmmo; + idItem* item; + idDict args; + + + player = gameLocal.GetLocalPlayer(); + + //set up the array + memset( AmmoArray, 0, sizeof( ammodata_t) * AMMO_ARRAY_SIZE); + for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + AmmoArray[ t ].percentFull = 2.0f; + } + + // we only check for certain types of ammo. + kv = spawnArgs.MatchPrefix ( "def_ammo", NULL ); + if ( kv ) { + kv->GetValue(); + for ( typeCount = 0; typeCount < AMMO_ARRAY_SIZE && kv; kv = spawnArgs.MatchPrefix ( "def_ammo", kv ) ) { + + AmmoArray[ typeCount ].ammoName = kv->GetValue(); + AmmoArray[ typeCount ].ammoIndex = player->inventory.AmmoIndexForAmmoClass( AmmoArray[ typeCount ].ammoName.c_str() ); + + //check and see how much ammo this weapon can hold... + AmmoArray[ typeCount ].ammoMax = player->inventory.MaxAmmoForAmmoClass( player, AmmoArray[ typeCount ].ammoName.c_str() ); + + //and the current amount + AmmoArray[ typeCount ].ammoCount = player->inventory.HasAmmo( AmmoArray[ typeCount ].ammoIndex, 1); + + if( AmmoArray[ typeCount ].ammoMax > 0) { + AmmoArray[ typeCount ].percentFull = float( AmmoArray[ typeCount ].ammoCount / AmmoArray[ typeCount ].ammoMax ); + } else { + AmmoArray[ typeCount ].percentFull = 2.0f; + } + + //increment + typeCount++; + } + + } else { + gameLocal.Warning("Bad ammo data on rvTarget_AmmoStash '%s'", this->GetName()); + return; + } + + //TEMP: print out the ammo counts + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + //sort the types of ammo by need. The most-needed ammo will be placed. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + int i; + //run through our targets until we have no more targets. + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + //drop the most needed ammo at ent's location. + args.Set ( "origin", ent->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", ent->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", AmmoArray[ 0 ].ammoName ); + + gameLocal.SpawnEntityDef ( args, &entAmmo ); + + //now assume that the item was picked up. + item = static_cast< idItem* >(entAmmo); + kv = entAmmo->spawnArgs.MatchPrefix ( "inv_ammo", NULL ); + + if( kv ) { + + //add the ammo as if the player picked it up + AmmoArray[ 0 ].ammoCount += atoi(kv->GetValue().c_str()); + AmmoArray[ 0 ].percentFull = (float)AmmoArray[ 0 ].ammoCount / (float)AmmoArray[ 0 ].ammoMax; + + //resort the ammo. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + } else { + gameLocal.Warning("Bad ammo data on target_ammostash '%s'", this->GetName()); + return; + } + } + +} + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_TetherAI ) + EVENT( EV_Activate, rvTarget_TetherAI::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_TetherAI::Event_Activate +================ +*/ +void rvTarget_TetherAI::Event_Activate( idEntity *activator ) { + int i; + if ( activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + } + } +} +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_Nailable ) +END_CLASS +void rvTarget_Nailable::Spawn() { + + //InitDefaultPhysics( origin, axis ); + +// int contents = GetPhysics()->GetContents(); +// contents &= CONTENTS_BODY; +// contents &= CONTENTS_SOLID; + GetPhysics()->SetContents( MASK_SHOT_BOUNDINGBOX ); + +} + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LockDoor ) + EVENT( EV_Activate, idTarget_LockDoor::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LockDoor::Event_Activate +================ +*/ +void idTarget_LockDoor::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + int lock; + + lock = spawnArgs.GetInt( "locked", "1" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_CallObjectFunction ) + EVENT( EV_Activate, idTarget_CallObjectFunction::Event_Activate ) +END_CLASS + +/* +================ +idTarget_CallObjectFunction::Event_Activate +================ +*/ +void idTarget_CallObjectFunction::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + + funcName = spawnArgs.GetString( "call" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ent->scriptObject.HasObject() ) { + func = ent->scriptObject.GetFunction( funcName ); + if ( !func ) { + gameLocal.Error( "Function '%s' not found on entity '%s' for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' on entity '%s' has the wrong number of parameters for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( !ent->scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' on entity '%s' is the wrong type for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + // create a thread and call the function +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableLevelWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableLevelWeapons ) + EVENT( EV_Activate, idTarget_EnableLevelWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableLevelWeapons::Event_Activate +================ +*/ +void idTarget_EnableLevelWeapons::Event_Activate( idEntity *activator ) { + int i; + const char *weap; + + gameLocal.world->spawnArgs.SetBool( "no_Weapons", spawnArgs.GetBool( "disable" ) ); + + if ( spawnArgs.GetBool( "disable" ) ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_DisableWeapon ); + } + } + } else { + weap = spawnArgs.GetString( "weapon" ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_EnableWeapon ); + if ( weap && weap[ 0 ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, weap ); + } + } + } + } +} + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +const idEventDef EV_TipOff( "" ); +extern const idEventDef EV_GetPlayerPos( "" ); + +CLASS_DECLARATION( idTarget, idTarget_Tip ) + EVENT( EV_Activate, idTarget_Tip::Event_Activate ) + EVENT( EV_TipOff, idTarget_Tip::Event_TipOff ) + EVENT( EV_GetPlayerPos, idTarget_Tip::Event_GetPlayerPos ) +END_CLASS + + +/* +================ +idTarget_Tip::idTarget_Tip +================ +*/ +idTarget_Tip::idTarget_Tip( void ) { + playerPos.Zero(); +} + +/* +================ +idTarget_Tip::Spawn +================ +*/ +void idTarget_Tip::Spawn( void ) { +} + +/* +================ +idTarget_Tip::Save +================ +*/ +void idTarget_Tip::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Restore +================ +*/ +void idTarget_Tip::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_GetPlayerPos( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_TipOff, 100 ); + } +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->IsTipVisible() ) { + PostEventSec( &EV_Activate, 5.1f, activator ); + return; + } + player->ShowTip( spawnArgs.GetString( "text_title" ), spawnArgs.GetString( "text_tip" ), false ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } +} + +/* +================ +idTarget_Tip::Event_TipOff +================ +*/ +void idTarget_Tip::Event_TipOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 96.0f ) { + player->HideTip(); + } else { + PostEventMS( &EV_TipOff, 100 ); + } + } +} + + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveSecurity ) +EVENT( EV_Activate, idTarget_GiveSecurity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveSecurity::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using security +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveSecurity( spawnArgs.GetString( "text_security" ) ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RemoveWeapons ) +EVENT( EV_Activate, idTarget_RemoveWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RemoveWeapons::Event_Activate +================ +*/ +void idTarget_RemoveWeapons::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "weapon", NULL ); + while ( kv ) { + player->RemoveWeapon( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "weapon", kv ); + } +// RAVEN BEGIN +// bdube: default to initial weapon + player->SelectWeapon( 0, true ); +// RAVEN END + } + } +} + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LevelTrigger ) +EVENT( EV_Activate, idTarget_LevelTrigger::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LevelTrigger::Event_Activate +================ +*/ +void idTarget_LevelTrigger::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + player->SetLevelTrigger( spawnArgs.GetString( "levelName" ), spawnArgs.GetString( "triggerName" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableStamina ) +EVENT( EV_Activate, idTarget_EnableStamina::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableStamina::Event_Activate +================ +*/ +void idTarget_EnableStamina::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + if ( spawnArgs.GetBool( "enable" ) ) { + pm_stamina.SetFloat( player->spawnArgs.GetFloat( "pm_stamina" ) ); + } else { + pm_stamina.SetFloat( 0.0f ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ + +const idEventDef EV_RestoreVolume( "" ); +CLASS_DECLARATION( idTarget, idTarget_FadeSoundClass ) +EVENT( EV_Activate, idTarget_FadeSoundClass::Event_Activate ) +EVENT( EV_RestoreVolume, idTarget_FadeSoundClass::Event_RestoreVolume ) +END_CLASS + +/* +================ +idTarget_FadeSoundClass::Event_Activate +================ +*/ +void idTarget_FadeSoundClass::Event_Activate( idEntity *activator ) { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + float fadeDuration = spawnArgs.GetFloat( "fadeDuration" ); + int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, fadeClass, spawnArgs.GetBool( "fadeIn" ) ? fadeDB : 0.0f - fadeDB, fadeTime ); + if ( fadeDuration ) { + PostEventSec( &EV_RestoreVolume, fadeDuration ); + } + } +} + +/* +================ +idTarget_FadeSoundClass::Event_RestoreVolume +================ +*/ +void idTarget_FadeSoundClass::Event_RestoreVolume() { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); +// int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // restore volume + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); +} + diff --git a/source/game/Target.h b/source/game/Target.h new file mode 100644 index 0000000..ac0398a --- /dev/null +++ b/source/game/Target.h @@ -0,0 +1,704 @@ + +#ifndef __GAME_TARGET_H__ +#define __GAME_TARGET_H__ + +//Used to compare two ammoData structs and see who has more. +int CompareAmmoData( const void* ammo1, const void* ammo2); + + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +class idTarget : public idEntity { +public: + CLASS_PROTOTYPE( idTarget ); +}; + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +class idTarget_Remove : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Remove ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +class idTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Show ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +class idTarget_Damage : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Damage ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +class idTarget_SessionCommand : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SessionCommand ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_EndLevel + +=============================================================================== +*/ + +class idTarget_EndLevel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + +private: + void Event_Activate( idEntity *activator ); + +}; + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +class idTarget_WaitForButton : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_WaitForButton ); + + void Think( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetGlobalShaderTime + +=============================================================================== +*/ + +class idTarget_SetGlobalShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetGlobalShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +class idTarget_SetShaderParm : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderParm ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +class idTarget_SetShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +class idTarget_FadeEntity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeEntity ); + + idTarget_FadeEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idVec4 fadeFrom; + int fadeStart; + int fadeEnd; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +class idTarget_LightFadeIn : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeIn ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +class idTarget_LightFadeOut : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeOut ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +class idTarget_Give : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Give ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + void Event_PostSpawn(); +// RAVEN END +}; + + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +class idTarget_GiveEmail : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveEmail ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +class idTarget_SetModel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetModel ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +class idTarget_SetInfluence : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetInfluence ); + + idTarget_SetInfluence( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreInfluence(); + void Event_GatherEntities(); + void Event_Flash( float flash, int out ); + void Event_ClearFlash( float flash ); + void Think( void ); + + idList lightList; + idList guiList; + idList soundList; + idList genericList; + float flashIn; + float flashOut; + float delay; + idStr flashInSound; + idStr flashOutSound; + idEntity * switchToCamera; + idInterpolatefovSetting; + bool soundFaded; + bool restoreOnTrigger; +}; + + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +class idTarget_SetKeyVal : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetKeyVal ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +class idTarget_SetFov : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetFov ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idInterpolate fovSetting; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +class idTarget_SetPrimaryObjective : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetPrimaryObjective ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: added player database +/* +=============================================================================== + +idTarget_AddDatabaseEntry + +=============================================================================== +*/ + +class rvTarget_AddDatabaseEntry : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AddDatabaseEntry ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// jshepard: secret area discovery + +/* +=============================================================================== + +idTarget_SecretArea + +=============================================================================== +*/ + +class rvTarget_SecretArea : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_SecretArea ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +class rvTarget_BossBattle : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_BossBattle ); + +private: + void Event_Activate( idEntity *activator ); + void Event_SetShieldPercent( float percent ); + void Event_SetBossMaxHealth( float f ); + void Event_AllowShieldBar( float activate ); + void Event_AllowShieldWarningBar( float activate ); + +}; + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +class rvTarget_TetherAI : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_TetherAI ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +class rvTarget_Nailable : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_Nailable ); + +private: + void Spawn( void ); +}; + + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +class idTarget_CallObjectFunction : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_CallObjectFunction ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_EnableLevelWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableLevelWeapons ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +class idTarget_Tip : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Tip ); + + idTarget_Tip( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idVec3 playerPos; + + void Event_Activate( idEntity *activator ); + void Event_TipOff( void ); + void Event_GetPlayerPos( void ); +}; + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ +class idTarget_GiveSecurity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveSecurity ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ +class idTarget_RemoveWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RemoveWeapons ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ +class idTarget_LevelTrigger : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LevelTrigger ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ +class idTarget_EnableStamina : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableStamina ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ +class idTarget_FadeSoundClass : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeSoundClass ); +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreVolume(); +}; + + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +class rvTarget_LaunchProjectile : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_LaunchProjectile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_LaunchProjectile( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_ExitAreaAlert + +=============================================================================== +*/ + +class rvTarget_ExitAreaAlert : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_ExitAreaAlert ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ +typedef struct ammodata_s { + + int ammoIndex; + idStr ammoName; + int ammoCount; + int ammoMax; + float percentFull; + +} ammodata_t; + +#define AMMO_ARRAY_SIZE 10 + +class rvTarget_AmmoStash : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AmmoStash ); + + //used to detect which weapons need ammo. The values stored are 0 to 1, with -1 meaning don't check this out. + ammodata_t AmmoArray[ AMMO_ARRAY_SIZE]; + +private: + void Event_Activate( idEntity *activator ); +}; +#endif /* !__GAME_TARGET_H__ */ diff --git a/source/game/TramGate.cpp b/source/game/TramGate.cpp new file mode 100644 index 0000000..9a149fc --- /dev/null +++ b/source/game/TramGate.cpp @@ -0,0 +1,311 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +const idEventDef EV_OpenGate( "" ); +const idEventDef EV_CloseGate( "" ); + +//======================================================= +// +// rvTramGate +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvTramGate ) + EVENT( EV_Touch, rvTramGate::Event_Touch ) + EVENT( EV_Activate, rvTramGate::Event_Activate ) + EVENT( EV_OpenGate, rvTramGate::Event_OpenGate ) + EVENT( EV_CloseGate, rvTramGate::Event_CloseGate ) + EVENT( EV_Door_Lock, rvTramGate::Event_Lock ) + EVENT( EV_Door_IsOpen, rvTramGate::Event_IsOpen ) + EVENT( EV_Door_IsLocked, rvTramGate::Event_IsLocked ) +END_CLASS + +/* +================ +rvTramGate::Spawn +================ +*/ +void rvTramGate::Spawn() { + SpawnDoors(); + + AdjustFrameRate(); +} + +/* +================ +rvTramGate::~rvTramGate +================ +*/ +rvTramGate::~rvTramGate() { + doorList.RemoveContents( true ); +} + +/* +================ +rvTramGate::SpawnDoors +================ +*/ +void rvTramGate::SpawnDoors() { + idDict args; + idVec3 dir = spawnArgs.GetAngles("doorsAxisOffset").ToMat3() * GetPhysics()->GetAxis()[1]; + + args.Set( "team", GetName() ); + args.SetMatrix( "rotation", dir.ToMat3() ); + args.SetVector( "origin", GetPhysics()->GetOrigin() ); + + int len = strlen("door_"); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("door"); kv; kv = spawnArgs.MatchPrefix("door", kv) ) { + args.Set( kv->GetKey().Right(kv->GetKey().Length() - len), kv->GetValue() ); + } + + args.SetFloat( "movedir", (-dir).ToYaw() ); + idDoor* door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door1"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + args.SetFloat( "movedir", dir.ToYaw() ); + door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door2"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + //assert( GetDoorMaster() == door->GetMoveMaster() ); +} + +/* +================ +rvTramGate::AdjustFrameRate +================ +*/ +void rvTramGate::AdjustFrameRate() { + GetAnimator()->SetPlaybackRate( "open", spawnArgs.GetFloat("openFrameRateScale") ); + GetAnimator()->SetPlaybackRate( "close", spawnArgs.GetFloat("closeFrameRateScale") ); +} + +/* +================ +rvTramGate::OpenGate +================ +*/ +void rvTramGate::OpenGate() { + PlayAnim( ANIMCHANNEL_ALL, "open" ); +} + +/* +================ +rvTramGate::CloseGate +================ +*/ +void rvTramGate::CloseGate() { + PlayAnim( ANIMCHANNEL_ALL, "close" ); +} + +/* +================ +rvTramGate::Save +================ +*/ +void rvTramGate::Save( idSaveGame *savefile ) const { + savefile->WriteInt( doorList.Num() ); + for( int i = 0; i < doorList.Num(); i++ ) { + doorList[i].Save( savefile ); + } +} + +/* +================ +rvTramGate::Restore +================ +*/ +void rvTramGate::Restore( idRestoreGame *savefile ) { + int num = 0; + idEntityPtr temp; + savefile->ReadInt( num ); + for( int i = 0; i < num; i++ ) { + temp.Restore( savefile ); + doorList.Append( temp ); + } +} + +/* +================ +rvTramGate::PlayAnim +================ +*/ +int rvTramGate::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return 0; + } + + GetAnimator()->PlayAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim(channel)->PlayLength(); +} + +/* +================ +rvTramGate::CycleAnim +================ +*/ +void rvTramGate::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return; + } + + GetAnimator()->CycleAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAllAnims( int blendFrames ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAnim( int channel, int blendFrames ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +bool rvTramGate::AnimIsPlaying( int channel, int blendFrames ) { + return GetAnimator()->CurrentAnim(channel)->GetEndTime() - FRAME2MS(blendFrames) >= gameLocal.GetTime(); +} + +/* +================ +rvTramGate::IsOpen +================ +*/ +bool rvTramGate::IsOpen() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsOpen() : false; +} + +/* +================ +rvTramGate::IsClosed +================ +*/ +bool rvTramGate::IsClosed() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsClosed() : false; +} + +/* +================ +rvTramGate::GetDoorMaster +================ +*/ +idDoor* rvTramGate::GetDoorMaster() const { + return doorList.Num() ? doorList[0] : NULL; +} + +/* +================ +rvTramGate::IsDoorMasterValid +================ +*/ +bool rvTramGate::IsDoorMasterValid() const { + return GetDoorMaster() != NULL; +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::Event_Touch( idEntity* other, trace_t* trace ) { + if( !IsDoorMasterValid() ) { + return; + } + + if( IsClosed() ) { + OpenGate(); + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } else if( IsOpen() ) { + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } +} + +/* +================ +rvTramGate::Event_Activate +================ +*/ +void rvTramGate::Event_Activate( idEntity* activator ) { + if( !IsDoorMasterValid() ) { + return; + } + + // FIXME: may need some better logic than this. + const char* animName = (IsClosed()) ? "open" : (IsOpen()) ? "close" : NULL; + if( animName ) { + PlayAnim( ANIMCHANNEL_ALL, animName ); + GetDoorMaster()->ProcessEvent( &EV_Activate, this ); + } +} + +/* +================ +rvTramGate::Event_OpenGate +================ +*/ +void rvTramGate::Event_OpenGate() { + OpenGate(); +} + +/* +================ +rvTramGate::Event_CloseGate +================ +*/ +void rvTramGate::Event_CloseGate() { + CloseGate(); +} + +/* +================ +rvTramGate::Event_IsOpen +================ +*/ +void rvTramGate::Event_IsOpen( void ) { + idThread::ReturnInt( IsOpen() ); +} + +/* +================ +rvTramGate::Event_IsLocked +================ +*/ +void rvTramGate::Event_IsLocked( void ) { + idThread::ReturnInt( (IsDoorMasterValid()) ? GetDoorMaster()->IsLocked() : 0 ); +} + +/* +================ +rvTramGate::Event_Lock +================ +*/ +void rvTramGate::Event_Lock( int f ) { + if( IsDoorMasterValid() ) { + GetDoorMaster()->Lock( f ); + } +} diff --git a/source/game/TramGate.h b/source/game/TramGate.h new file mode 100644 index 0000000..272aaec --- /dev/null +++ b/source/game/TramGate.h @@ -0,0 +1,51 @@ +#ifndef __RV_TRAM_GATE_H +#define __RV_TRAM_GATE_H + +extern const idEventDef EV_OpenGate; +extern const idEventDef EV_CloseGate; + +class rvTramGate : public idAnimatedEntity { + CLASS_PROTOTYPE( rvTramGate ); + +public: + void Spawn(); + virtual ~rvTramGate(); + + void OpenGate(); + void CloseGate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnDoors(); + void AdjustFrameRate(); + + int PlayAnim( int channel, const char* animName, int blendFrames = 0 ); + void CycleAnim( int channel, const char* animName, int blendFrames = 0 ); + void ClearAllAnims( int blendFrames = 0 ); + void ClearAnim( int channel, int blendFrames = 0 ); + bool AnimIsPlaying( int channel, int blendFrames = 0 ); + + bool IsOpen() const; + bool IsClosed() const; + + idDoor* GetDoorMaster() const; + bool IsDoorMasterValid() const; + +protected: + void Event_Touch( idEntity* other, trace_t* trace ); + void Event_Activate( idEntity* activator ); + + void Event_OpenGate(); + void Event_CloseGate(); + + void Event_IsOpen( void ); + void Event_IsLocked( void ); + void Event_Lock( int f ); + +protected: + idList< idEntityPtr > doorList; +}; + +#endif diff --git a/source/game/Trigger.cpp b/source/game/Trigger.cpp new file mode 100644 index 0000000..2b7bd46 --- /dev/null +++ b/source/game/Trigger.cpp @@ -0,0 +1,1546 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +/* +=============================================================================== + + idTrigger + +=============================================================================== +*/ + +const idEventDef EV_Enable( "enable", NULL ); +const idEventDef EV_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idTrigger ) + EVENT( EV_Enable, idTrigger::Event_Enable ) + EVENT( EV_Disable, idTrigger::Event_Disable ) +END_CLASS + +/* +================ +idTrigger::DrawDebugInfo +================ +*/ +void idTrigger::DrawDebugInfo( void ) { + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + bool show; + const function_t *func; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { + show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); + if ( !show ) { + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + show = true; + break; + } + } + } + + if ( !show ) { + continue; + } + + gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + if ( ent->IsType( idTrigger::Type ) ) { + func = static_cast( ent )->GetScriptFunction(); + } else { + func = NULL; + } + + if ( func ) { + gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); + } + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); + if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + } + } + } + } + } +} + +/* +================ +idTrigger::Enable +================ +*/ +void idTrigger::Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); +} + +/* +================ +idTrigger::Disable +================ +*/ +void idTrigger::Disable( void ) { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); +} + +/* +================ +idTrigger::CallScript +================ +*/ +void idTrigger::CallScript( idEntity* scriptEntity ) { +// RAVEN BEGIN +// abahr + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].InsertEntity( scriptEntity, 0 );//We could pass both the activator and self if wanted + scriptFunctions[ix].CallFunc( &spawnArgs ); + scriptFunctions[ix].RemoveIndex( 0 ); + } +// RAVEN END + +} + +/* +================ +idTrigger::GetScriptFunction +================ +*/ +const function_t *idTrigger::GetScriptFunction( void ) const { +// RAVEN BEGIN +// abahr: + return (scriptFunctions.Num()) ? scriptFunctions[0].GetFunc() : NULL; +// RAVEN END +} + +/* +================ +idTrigger::Save +================ +*/ +void idTrigger::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// abahr + savefile->WriteInt( scriptFunctions.Num() ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Save( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Restore +================ +*/ +void idTrigger::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// abahr + int numScripts = 0; + savefile->ReadInt( numScripts ); + scriptFunctions.SetNum( numScripts ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Restore( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Event_Enable +================ +*/ +void idTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +idTrigger::Event_Disable +================ +*/ +void idTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +idTrigger::idTrigger +================ +*/ +idTrigger::idTrigger() { +// RAVEN BEGIN +// abahr: scriptFunction init's itself + //scriptFunction = NULL; +// RAVEN END +} + +/* +================ +idTrigger::Spawn +================ +*/ +void idTrigger::Spawn( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + +// RAVEN BEGIN +// abahr: + scriptFunctions.SetGranularity( 1 ); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("call"); kv; kv = spawnArgs.MatchPrefix("call", kv) ) { + if( !kv->GetValue() ) { + continue; + } + + rvScriptFuncUtility& utility = scriptFunctions.Alloc(); + if( !utility.Init(kv->GetValue()) ) { + gameLocal.Warning( "Trigger '%s' at (%s) trying to call an unknown function.", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + } +// RAVEN END +} + + +/* +=============================================================================== + + idTrigger_Multi + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: changed to 'E' to allow NULL entities +const idEventDef EV_TriggerAction( "", "E" ); +// RAVEN END + +CLASS_DECLARATION( idTrigger, idTrigger_Multi ) + EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) + EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) + +// RAVEN BEGIN +// kfuller: respond to earthquakes + EVENT( EV_Earthquake, idTrigger_Multi::Event_EarthQuake ) +// RAVEN END +END_CLASS + + +/* +================ +idTrigger_Multi::idTrigger_Multi +================ +*/ +idTrigger_Multi::idTrigger_Multi( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + removeItem = 0; + touchClient = false; + touchOther = false; + touchVehicle = false; + triggerFirst = false; + triggerWithSelf = false; + buyZoneTrigger = 0; + controlZoneTrigger = 0; + prevZoneController = TEAM_NONE; +} + +/* +================ +idTrigger_Multi::Save +================ +*/ +void idTrigger_Multi::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( touchClient ); + savefile->WriteBool( touchOther ); + savefile->WriteBool( touchVehicle ); + savefile->WriteBool( triggerFirst ); + savefile->WriteBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Restore +================ +*/ +void idTrigger_Multi::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( touchClient ); + savefile->ReadBool( touchOther ); + savefile->ReadBool( touchVehicle ); + savefile->ReadBool( triggerFirst ); + savefile->ReadBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Spawn + +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"call" : Script function to call when triggered +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +================ +*/ +void idTrigger_Multi::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); + spawnArgs.GetInt( "buyZone", "0", buyZoneTrigger); + spawnArgs.GetInt( "controlZone", "0", controlZoneTrigger); + + if ( buyZoneTrigger == -1 ) + gameLocal.Warning( "trigger_buyzone '%s' at (%s) has no buyZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + if ( controlZoneTrigger == -1 ) + gameLocal.Warning( "trigger_controlzone '%s' at (%s) has no controlZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + + if ( spawnArgs.GetBool( "onlyVehicle" ) ) { + touchVehicle = true; + } else if ( spawnArgs.GetBool( "anyTouch" ) ) { + touchClient = true; + touchOther = true; + } else if ( spawnArgs.GetBool( "noTouch" ) ) { + touchClient = false; + touchOther = false; + } else if ( spawnArgs.GetBool( "noClient" ) ) { + touchClient = false; + touchOther = true; + } else { + touchClient = true; + touchOther = false; + } + + nextTriggerTime = 0; + + if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); + } else if ( spawnArgs.GetBool( "projectile_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_PROJECTILE ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Multi::CheckFacing +================ +*/ +bool idTrigger_Multi::CheckFacing( idEntity *activator ) { + if ( spawnArgs.GetBool( "facing" ) ) { + if ( !activator->IsType( idPlayer::GetClassType() ) ) { + return true; + } + idPlayer *player = static_cast< idPlayer* >( activator ); + + // Unfortunately, the angle key rotates the trigger entity also. So I've added + // an angleFacing key which is used instead when present, otherwise the code defaults + // to the behaviour present prior to this change + idVec3 tFacing = GetPhysics()->GetAxis()[0]; + if ( spawnArgs.FindKey( "angleFacing" )) { + idAngles angs(0,spawnArgs.GetFloat( "angleFacing", "0" ),0); + tFacing = angs.ToForward(); + } + float dot = player->viewAngles.ToForward() * tFacing; + + float angle = RAD2DEG( idMath::ACos( dot ) ); + if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { + return false; + } + } + return true; +} + + +/* +================ +idTrigger_Multi::TriggerAction +================ +*/ +void idTrigger_Multi::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// jdischler: added for Aweldon. The trigger, when activated, will call the listed func with all attached targets, then return. + if ( spawnArgs.GetBool( "_callWithTargets", "0" )) + { + idEntity *ent; + for( int i = 0; i < targets.Num(); i++ ) + { + ent = targets[ i ].GetEntity(); + if ( !ent ) + { + continue; + } + CallScript( ent ); + } + return; + } +// RAVEN END + ActivateTargets( triggerWithSelf ? this : activator ); + CallScript( triggerWithSelf ? this : activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_Multi::Event_TriggerAction +================ +*/ +void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_Multi::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_Multi::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// bdube: moved trigger first + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( activator ) ) { + return; + } +// RAVEN END + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + + +void idTrigger_Multi::HandleControlZoneTrigger() +{ + // This only does something in multiplayer + if ( !gameLocal.isMultiplayer ) + return; + + const int TEAM_DEADLOCK = 2; + + int pCount = 0; + int count = 0, controllingTeam = TEAM_NONE; + count = playersInTrigger.Num(); + + for ( int i = 0; iPowerUpActive( POWERUP_DEADZONE ) ) + continue; + + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + { + pCount++; + } + + int team = playersInTrigger[i]->team; + + if ( i == 0 ) + controllingTeam = playersInTrigger[i]->team; + + // Assign the controlling team based on the first player + // for zones that accept both. + if ( team != controllingTeam ) + { + controllingTeam = TEAM_DEADLOCK; + pCount = 0; + } + } + + if ( controllingTeam != controlZoneTrigger-1 && controlZoneTrigger != 3 ) + { + controllingTeam = TEAM_NONE; + pCount = 0; + } + + int situation = DZ_NONE; + if ( controllingTeam != prevZoneController ) + { + if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_NONE ) + situation = DZ_MARINES_TAKEN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_NONE ) + situation = DZ_STROGG_TAKEN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINES_LOST; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_LOST; + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_TO_MARINE; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_TO_STROGG; + + // DEADLOCK + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_NONE ) + situation = DZ_MARINE_DEADLOCK; // Unlikely case, just use this. + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINE_REGAIN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_STROGG_REGAIN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINES_LOST; // Unlikely case, just use this. + } + + /// Report individual credits + for( int i = 0; i < count; i++ ) + { + idPlayer* player = playersInTrigger[i]; + + // No token? Ignore em! + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") && !player->PowerUpActive( POWERUP_DEADZONE ) ) + continue; + + int team = player->team; + if( team == controllingTeam ) + { + gameLocal.mpGame.ReportZoneControllingPlayer( player ); + } + } + + /// Report zone control to multiplayer game manager + gameLocal.mpGame.ReportZoneController(controllingTeam, pCount, situation, this); + + playersInTrigger.Clear(); + prevZoneController = controllingTeam; +} + + +/* +================ +idTrigger_Multi::Think +================ +*/ +void idTrigger_Multi::Think() +{ + // Control zone handling + if ( controlZoneTrigger > 0 ) + HandleControlZoneTrigger(); +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + +// RAVEN BEGIN +// jdischler: vehicle only trigger + if ( touchVehicle ) { + if ( !other->IsType(rvVehicle::GetClassType()) ) { + return; + } + } else { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool player = other->IsType( idPlayer::GetClassType() ); +// RAVEN END + if ( player ) { + if ( !touchClient ) { + return; + } + if ( static_cast< idPlayer * >( other )->spectating ) { + return; + } + + // Buy zone handling + if ( buyZoneTrigger /*&& gameLocal.mpGame.mpGameState.gameState.currentState != 1*/ ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( buyZoneTrigger-1 == p->team || buyZoneTrigger == 3) + { + p->inBuyZone = true; + p->inBuyZonePrev = true; + } + } + + // Control zone handling + if ( controlZoneTrigger > 0 ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( p->PowerUpActive(POWERUP_DEADZONE) || !spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + playersInTrigger.Append(p); + } + + } else if ( !touchOther ) { + return; + } + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { + triggerFirst = true; + } + +// RAVEN BEGIN +// rjohnson: added block + if ( developer.GetBool() && *spawnArgs.GetString ( "message" ) ) { + gameLocal.DPrintf ( "Trigger: %s\n", spawnArgs.GetString ( "message" ) ); + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +// RAVEN BEGIN +// kfuller: +void idTrigger_Multi::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + if (requiresLOS) + { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + // activate this effect now + TriggerAction(gameLocal.entities[ENTITYNUM_WORLD]); +} + +// RAVEN END + +/* +=============================================================================== + + idTrigger_EntityName + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) + EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) + EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_EntityName::idTrigger_EntityName +================ +*/ +idTrigger_EntityName::idTrigger_EntityName( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + triggerFirst = false; +} + +/* +================ +idTrigger_EntityName::Save +================ +*/ +void idTrigger_EntityName::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( triggerFirst ); + savefile->WriteString( entityName ); +} + +/* +================ +idTrigger_EntityName::Restore +================ +*/ +void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( triggerFirst ); + savefile->ReadString( entityName ); +} + +/* +================ +idTrigger_EntityName::Spawn +================ +*/ +void idTrigger_EntityName::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + + entityName = spawnArgs.GetString( "entityname" ); + if ( !entityName.Length() ) { + gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + nextTriggerTime = 0; + + if ( !spawnArgs.GetBool( "noTouch" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } +} + +/* +================ +idTrigger_EntityName::TriggerAction +================ +*/ +void idTrigger_EntityName::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// abahr: want same functionality as trigger_multi. Need to move this code into these two function calls + idEntity* scriptEntity = spawnArgs.GetBool("triggerWithSelf") ? this : activator; + ActivateTargets( scriptEntity ); + CallScript( scriptEntity ); +// RAVEN END + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_EntityName::Event_TriggerAction +================ +*/ +void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_EntityName::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !activator ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && activator->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && activator->name != entityName ) { + return; + } +// RAVEN END + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_EntityName::Event_Touch +================ +*/ +void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !other ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && other->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && other->name != entityName ) { + return; + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_Timer + +=============================================================================== +*/ + +const idEventDef EV_Timer( "", NULL ); + +CLASS_DECLARATION( idTrigger, idTrigger_Timer ) + EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) + EVENT( EV_Activate, idTrigger_Timer::Event_Use ) +END_CLASS + +/* +================ +idTrigger_Timer::idTrigger_Timer +================ +*/ +idTrigger_Timer::idTrigger_Timer( void ) { + random = 0.0f; + wait = 0.0f; + on = false; + delay = 0.0f; +} + +/* +================ +idTrigger_Timer::Save +================ +*/ +void idTrigger_Timer::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteString( onName ); + savefile->WriteString( offName ); +} + +/* +================ +idTrigger_Timer::Restore +================ +*/ +void idTrigger_Timer::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadString( onName ); + savefile->ReadString( offName ); +} + +/* +================ +idTrigger_Timer::Spawn + +Repeatedly fires its targets. +Can be turned on or off by using. +================ +*/ +void idTrigger_Timer::Spawn( void ) { + spawnArgs.GetFloat( "random", "1", random ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetBool( "start_on", "0", on ); + spawnArgs.GetFloat( "delay", "0", delay ); + onName = spawnArgs.GetString( "onName" ); + offName = spawnArgs.GetString( "offName" ); + + if ( random >= wait && wait >= 0 ) { + random = wait - 0.001; + gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( on ) { + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Enable +================ +*/ +void idTrigger_Timer::Enable( void ) { + // if off, turn it on + if ( !on ) { + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Disable +================ +*/ +void idTrigger_Timer::Disable( void ) { + // if on, turn it off + if ( on ) { + on = false; + CancelEvents( &EV_Timer ); + } +} + +/* +================ +idTrigger_Timer::Event_Timer +================ +*/ +void idTrigger_Timer::Event_Timer( void ) { + ActivateTargets( this ); + + // set time before next firing + if ( wait >= 0.0f ) { + PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } +} + +/* +================ +idTrigger_Timer::Event_Use +================ +*/ +void idTrigger_Timer::Event_Use( idEntity *activator ) { + // if on, turn it off + if ( on ) { + if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { + return; + } + on = false; + CancelEvents( &EV_Timer ); + } else { + // turn it on + if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { + return; + } + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +=============================================================================== + + idTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Count ) + EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_Count::idTrigger_Count +================ +*/ +idTrigger_Count::idTrigger_Count( void ) { + goal = 0; + count = 0; + delay = 0.0f; +} + +/* +================ +idTrigger_Count::Save +================ +*/ +void idTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +idTrigger_Count::Restore +================ +*/ +void idTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +idTrigger_Count::Spawn +================ +*/ +void idTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +idTrigger_Count::Event_Trigger +================ +*/ +void idTrigger_Count::Event_Trigger( idEntity *activator ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + PostEventSec( &EV_TriggerAction, delay, activator ); + } + } +} + +/* +================ +idTrigger_Count::Event_TriggerAction +================ +*/ +void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript( activator ); + if ( goal == -1 ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============================================================================== + + idTrigger_Hurt + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) + EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) + EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) +END_CLASS + +/* +================ +idTrigger_Hurt::idTrigger_Hurt +================ +*/ +idTrigger_Hurt::idTrigger_Hurt( void ) { + on = false; + delay = 0.0f; + nextTime = 0; +} + +/* +================ +idTrigger_Hurt::Save +================ +*/ +void idTrigger_Hurt::Save( idSaveGame *savefile ) const { + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->WriteBool ( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Restore +================ +*/ +void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->ReadBool( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Spawn + + Damages activator + Can be turned on or off by using. +================ +*/ +void idTrigger_Hurt::Spawn( void ) { + spawnArgs.GetBool( "on", "1", on ); + spawnArgs.GetFloat( "delay", "1.0", delay ); + +// RAVEN BEGIN +// kfuller: playeronly flag + spawnArgs.GetBool( "playerOnly", "0", playerOnly ); +// RAVEN END + + nextTime = gameLocal.time; + Enable(); +} + +/* +================ +idTrigger_Hurt::Event_Touch +================ +*/ +void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { + const char *damage; + +// RAVEN BEGIN +// kfuller: playeronly flag +// jnewquist: Use accessor for static class type + if ( playerOnly && !other->IsType( idPlayer::GetClassType() ) ) { + return; + } +// RAVEN END + + if ( on && other && gameLocal.time >= nextTime ) { + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + other->Damage( this, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); + + ActivateTargets( other ); + CallScript( other ); + + nextTime = gameLocal.time + SEC2MS( delay ); + } +} + +/* +================ +idTrigger_Hurt::Event_Toggle +================ +*/ +void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { + on = !on; +} + + +/* +=============================================================================== + + idTrigger_Fade + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Fade ) + EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) +END_CLASS + +/* +================ +idTrigger_Fade::Event_Trigger +================ +*/ +void idTrigger_Fade::Event_Trigger( idEntity *activator ) { + idVec4 fadeColor; + int fadeTime; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); + fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); + player->playerView.Fade( fadeColor, fadeTime ); + PostEventMS( &EV_ActivateTargets, fadeTime, activator ); + } +} + +/* +=============================================================================== + + idTrigger_Touch + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Touch ) + EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) +END_CLASS + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::idTrigger_Touch( void ) { + clipModel = NULL; +} + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::~idTrigger_Touch( ) { + if ( clipModel ) { + delete clipModel; + clipModel = 0; + } +} + +/* +================ +idTrigger_Touch::Spawn +================ +*/ +void idTrigger_Touch::Spawn( void ) { + // get the clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( GetPhysics()->GetClipModel() ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } + filterTeam = -1; + idStr filterTeamStr = spawnArgs.GetString( "filterTeam" ); + if ( filterTeamStr.Size() ) + { + if ( !idStr::Icmp( "marine", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_MARINE; + } + else if ( !idStr::Icmp( "strogg", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_STROGG; + } + } +} + +/* +================ +idTrigger_Touch::Save +================ +*/ +void idTrigger_Touch::Save( idSaveGame *savefile ) { + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::Restore +================ +*/ +void idTrigger_Touch::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::TouchEntities +================ +*/ +void idTrigger_Touch::TouchEntities( void ) { + int numClipModels, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + +// RAVEN BEGIN +// abahr: now scriptFunction list + if ( clipModel == NULL || !scriptFunctions.Num() ) { +// RAVEN END + return; + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ); +// RAVEN BEGIN +// MCG: filterTeam + if ( filterTeam != -1 ) + { + idActor* actor; + // Iterate through the filter team + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)filterTeam ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden actors and actors that can't be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + if ( !bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + continue; + } + cm = actor->GetPhysics()->GetClipModel(); + if ( !cm || !cm->IsTraceModel() ) { + continue; + } + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ) ) { + continue; + } + ActivateTargets( (idEntity*)actor ); + + CallScript( (idEntity*)actor ); + } + return; + } +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + ActivateTargets( entity ); + +// RAVEN BEGIN +// abahr: changed to be compatible with new script function utility + CallScript( entity ); +// RAVEN END + } +} + +/* +================ +idTrigger_Touch::Think +================ +*/ +void idTrigger_Touch::Think( void ) { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} + +/* +================ +idTrigger_Touch::Event_Trigger +================ +*/ +void idTrigger_Touch::Event_Trigger( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Enable +================ +*/ +void idTrigger_Touch::Enable( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Touch::Disable +================ +*/ +void idTrigger_Touch::Disable( void ) { + BecomeInactive( TH_THINK ); +} diff --git a/source/game/Trigger.h b/source/game/Trigger.h new file mode 100644 index 0000000..3207b30 --- /dev/null +++ b/source/game/Trigger.h @@ -0,0 +1,288 @@ + +#ifndef __GAME_TRIGGER_H__ +#define __GAME_TRIGGER_H__ + +extern const idEventDef EV_Enable; +extern const idEventDef EV_Disable; + +/* +=============================================================================== + + Trigger base. + +=============================================================================== +*/ + +class idTrigger : public idEntity { +public: + CLASS_PROTOTYPE( idTrigger ); + + static void DrawDebugInfo( void ); + + idTrigger(); + void Spawn( void ); + + const function_t * GetScriptFunction( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + +protected: +// RAVEN BEGIN +// abahr: removed const from function + void CallScript( idEntity* scriptEntity ); +// RAVEN END + + void Event_Enable( void ); + void Event_Disable( void ); + +// RAVEN BEGIN +// abahr: changed to allow parms to be passed + idList scriptFunctions; + //const function_t * scriptFunction; +// RAVEN END +}; + + +/* +=============================================================================== + + Trigger which can be activated multiple times. + +=============================================================================== +*/ + +class idTrigger_Multi : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Multi ); + + idTrigger_Multi( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + idStr requires; + int removeItem; + bool touchClient; + bool touchOther; + bool touchVehicle; + bool triggerFirst; + bool triggerWithSelf; + int buyZoneTrigger; + int controlZoneTrigger; + int prevZoneController; + + idList playersInTrigger; + + bool CheckFacing( idEntity *activator ); + void HandleControlZoneTrigger(); + +// RAVEN BEGIN +// kfuller: want trigger_relays entities to be able to respond to earthquakes + void Event_EarthQuake (float requiresLOS); +// RAVEN END + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Trigger which can only be activated by an entity with a specific name. + +=============================================================================== +*/ + +class idTrigger_EntityName : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_EntityName ); + + idTrigger_EntityName( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + bool triggerFirst; + idStr entityName; + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +/* +=============================================================================== + + Trigger which repeatedly fires targets. + +=============================================================================== +*/ + +class idTrigger_Timer : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Timer ); + + idTrigger_Timer( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Enable( void ); + virtual void Disable( void ); + +private: + float random; + float wait; + bool on; + float delay; + idStr onName; + idStr offName; + + void Event_Timer( void ); + void Event_Use( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ + +class idTrigger_Count : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Count ); + + idTrigger_Count( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + int goal; + int count; + float delay; + + void Event_Trigger( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which hurts touching entities. + +=============================================================================== +*/ + +class idTrigger_Hurt : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Hurt ); + + idTrigger_Hurt( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + bool on; + float delay; + int nextTime; + +// RAVEN BEGIN +// kfuller: added playeronly + bool playerOnly; +// RAVEN END + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Toggle( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fades the player view. + +=============================================================================== +*/ + +class idTrigger_Fade : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Fade ); + +private: + void Event_Trigger( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which continuously tests whether other entities are touching it. + +=============================================================================== +*/ + +class idTrigger_Touch : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Touch ); + + idTrigger_Touch( void ); + ~idTrigger_Touch( ); + + void Spawn( void ); + virtual void Think( void ); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + + void TouchEntities( void ); + +private: + idClipModel * clipModel; + int filterTeam; + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_TRIGGER_H__ */ diff --git a/source/game/Weapon.cpp b/source/game/Weapon.cpp new file mode 100644 index 0000000..10743b1 --- /dev/null +++ b/source/game/Weapon.cpp @@ -0,0 +1,3334 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "Weapon.h" +#include "Projectile.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "client/ClientEffect.h" +//#include "../renderer/tr_local.h" + +/*********************************************************************** + + rvViewWeapon + +***********************************************************************/ + +// class def +CLASS_DECLARATION( idAnimatedEntity, rvViewWeapon ) + EVENT( EV_CallFunction, rvViewWeapon::Event_CallFunction ) +END_CLASS + +/*********************************************************************** + + init + +***********************************************************************/ + +/* +================ +rvViewWeapon::rvViewWeapon() +================ +*/ +rvViewWeapon::rvViewWeapon() { + modelDefHandle = -1; + weapon = NULL; + + Clear(); + + fl.networkSync = true; +} + +/* +================ +rvViewWeapon::~rvViewWeapon() +================ +*/ +rvViewWeapon::~rvViewWeapon() { + Clear(); +} + +/* +================ +rvViewWeapon::Spawn +================ +*/ +void rvViewWeapon::Spawn( void ) { + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +/* +================ +rvViewWeapon::Save +================ +*/ +void rvViewWeapon::Save( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( pendingGUIEvents.Num() ); + for ( i = 0; i < pendingGUIEvents.Num(); i ++ ) { + savefile->WriteString ( pendingGUIEvents[i] ); + } + + // TOSAVE: const idDeclSkin * saveSkin; + // TOSAVE: const idDeclSkin * invisSkin; + // TOSAVE: const idDeclSkin * saveWorldSkin; + // TOSAVE: const idDeclSkin * worldInvisSkin; + // TOSAVE: const idDeclSkin * saveHandsSkin; + // TOSAVE: const idDeclSkin * handsSkin; + + // TOSAVE: friend rvWeapon; + // TOSAVE: rvWeapon* weapon; +} + +/* +================ +rvViewWeapon::Restore +================ +*/ +void rvViewWeapon::Restore( idRestoreGame *savefile ) { + int i; + int num; + savefile->ReadInt ( num ); + pendingGUIEvents.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadString ( pendingGUIEvents[i] ); + } +} + +/* +=============== +rvViewWeapon::ClientPredictionThink +=============== +*/ +void rvViewWeapon::ClientPredictionThink( void ) { + UpdateAnimation(); +} + +/*********************************************************************** + + Weapon definition management + +***********************************************************************/ + +/* +================ +rvViewWeapon::Clear +================ +*/ +void rvViewWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + StopAllEffects( ); + + // TTimo - the weapon doesn't get a proper Event_DisableWeapon sometimes, so the sound sticks + // typically, client side instance join in tourney mode just wipes all ents + StopSound( SND_CHANNEL_ANY, false ); + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.entityNum = entityNumber; + + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customSkin = NULL; + + // set default shader parms + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[3] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f; + renderEntity.shaderParms[5] = 0.0f; + renderEntity.shaderParms[6] = 0.0f; + renderEntity.shaderParms[7] = 0.0f; + + memset( &refSound, 0, sizeof( refSound_t ) ); + refSound.referenceSoundHandle = -1; + + // setting diversity to 0 results in no random sound. -1 indicates random. + refSound.diversity = -1.0f; + + if ( weapon && weapon->GetOwner ( ) ) { + // don't spatialize the weapon sounds + refSound.listenerId = weapon->GetOwner( )->GetListenerId(); + } + + animator.ClearAllAnims( gameLocal.time, 0 ); + + FreeModelDef(); +} + +/* +===================== +rvViewWeapon::GetDebugInfo +===================== +*/ +void rvViewWeapon::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAnimatedEntity::GetDebugInfo( proc, userData ); + weapon->GetDebugInfo( proc, userData ); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +rvViewWeapon::PostGUIEvent +================ +*/ +void rvViewWeapon::PostGUIEvent( const char* event ) { + pendingGUIEvents.Append ( event ); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ + +/* +================ +rvViewWeapon::SetPowerUpSkin +================ +*/ +void rvViewWeapon::SetPowerUpSkin( const char *name ) { +/* FIXME + saveSkin = renderEntity.customSkin; + renderEntity.customSkin = invisSkin; + if ( worldModel.GetEntity() ) { + saveWorldSkin = worldModel.GetEntity()->GetSkin(); + worldModel.GetEntity()->SetSkin( worldInvisSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::UpdateSkin +================ +*/ +void rvViewWeapon::UpdateSkin( void ) { +/* FIXME + renderEntity.customSkin = saveSkin; + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( saveWorldSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::SetModel +================ +*/ +void rvViewWeapon::SetModel( const char *modelname, int mods ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + renderEntity.hModel = animator.SetModel( modelname ); + if ( renderEntity.hModel ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + } else { + renderEntity.customSkin = NULL; + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + } + + // hide the model until an animation is played + Hide(); +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvViewWeapon::Think +================ +*/ +void rvViewWeapon::Think( void ) { + // do nothing because the present is called from the player through PresentWeapon +} + +/* +===================== +rvViewWeapon::ConvertLocalToWorldTransform +===================== +*/ +void rvViewWeapon::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + if( !weapon ) { + idAnimatedEntity::ConvertLocalToWorldTransform( offset, axis ); + return; + } + + offset = GetPhysics()->GetOrigin() + offset * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + axis *= GetPhysics()->GetAxis(); +} + +/* +================ +rvViewWeapon::UpdateModelTransform +================ +*/ +void rvViewWeapon::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( !weapon ) { + idAnimatedEntity::UpdateModelTransform(); + return; + } + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +rvViewWeapon::PresentWeapon +================ +*/ +void rvViewWeapon::PresentWeapon( bool showViewModel ) { + // Dont do anything with the weapon while its stale + if ( fl.networkStale ) { + return; + } + +// RAVEN BEGIN +// rjohnson: cinematics should never be done from the player's perspective, so don't think the weapon ( and their sounds! ) + if ( gameLocal.inCinematic ) { + return; + } +// RAVEN END + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = weapon->GetOwner()->entityNumber + 1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHackInViewID = weapon->GetOwner()->entityNumber + 1; + + weapon->Think(); + + // present the model + if ( showViewModel && !(weapon->wsfl.zoom && weapon->GetZoomGui() ) ) { + Present(); + } else { + FreeModelDef(); + } + + UpdateSound(); +} + +/* +================ +rvViewWeapon::WriteToSnapshot +================ +*/ +void rvViewWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvViewWeapon::ReadFromSnapshot +================ +*/ +void rvViewWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvViewWeapon::ClientStale +================ +*/ +bool rvViewWeapon::ClientStale ( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( weapon ) { + weapon->ClientStale( ); + } + + idEntity::ClientStale( ); + + return false; +} + +/* +================ +rvViewWeapon::ClientReceiveEvent +================ +*/ +bool rvViewWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + if ( idEntity::ClientReceiveEvent( event, time, msg ) ) { + return true; + } + if ( weapon ) { + return weapon->ClientReceiveEvent ( event, time, msg ); + } + return false; +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +===================== +rvViewWeapon::Event_CallFunction +===================== +*/ +void rvViewWeapon::Event_CallFunction( const char *funcname ) { + if ( weapon ) { + stateParms_t parms = {0}; + if ( weapon->ProcessState ( funcname, parms ) == SRESULT_ERROR ) { + gameLocal.Error ( "Unknown function '%s' on entity '%s'", funcname, GetName() ); + } + } +} + +/* +================ +rvViewWeapon::SetSkin +================ +*/ +void rvViewWeapon::SetSkin( const char *skinname ) { + const idDeclSkin *skinDecl; + + if ( !skinname || !skinname[ 0 ] ) { + skinDecl = NULL; + } else { + skinDecl = declManager->FindSkin( skinname ); + } + + renderEntity.customSkin = skinDecl; + UpdateVisuals(); + + // Set the skin on the world model as well + if ( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skinDecl ); + } +} + +void rvViewWeapon::SetSkin( const idDeclSkin* skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); + + if( weapon && weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skin ); + } +} + +/* +================ +rvViewWeapon::GetPosition +================ +*/ +void rvViewWeapon::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); +} + +void rvViewWeapon::SetOverlayShader( const idMaterial* material ) { + renderEntity.overlayShader = material; +} + +/*********************************************************************** + + rvWeapon + +***********************************************************************/ + +CLASS_DECLARATION( idClass, rvWeapon ) +END_CLASS + +/* +================ +rvWeapon::rvWeapon +================ +*/ +rvWeapon::rvWeapon ( void ) { + viewModel = NULL; + worldModel = NULL; + weaponDef = NULL; + +#ifdef _XENON + aimAssistFOV = 10.0f; +#endif + + memset ( &animDoneTime, 0, sizeof(animDoneTime) ); + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + + hitscanAttackDef = -1; + + forceGUIReload = false; +} + +/* +================ +rvWeapon::~rvWeapon +================ +*/ +rvWeapon::~rvWeapon( void ) { + int i; + + // Free all current light defs + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + FreeLight ( i ); + } + + // Disassociate with the view model + if ( viewModel ) { + viewModel->weapon = NULL; + } +} + +/* +================ +rvWeapon::Init +================ +*/ +void rvWeapon::Init( idPlayer* _owner, const idDeclEntityDef* def, int _weaponIndex, bool _isStrogg ) { + int i; + + viewModel = _owner->GetWeaponViewModel( ); + worldModel = _owner->GetWeaponWorldModel( ); + weaponDef = def; + owner = _owner; + scriptObject = &viewModel->scriptObject; + weaponIndex = _weaponIndex; + mods = owner->inventory.weaponMods[ weaponIndex ]; + isStrogg = _isStrogg; + + spawnArgs = weaponDef->dict; + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif + + // Apply the mod dictionaries + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict; + if ( !(mods & (1<weapon = this; +} + +/* +================ +rvWeapon::FindViewModelPositionStyle +================ +*/ +void rvWeapon::FindViewModelPositionStyle( idVec3& viewOffset, idAngles& viewAngles ) const { + int viewStyle = g_gunViewStyle.GetInteger(); + const char* styleDefName = spawnArgs.GetString( va("def_viewStyle%d", viewStyle) ); + const idDict* styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + if( !styleDef ) { + styleDefName = spawnArgs.GetString( "def_viewStyle" ); + styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + } + assert( styleDef ); + + viewAngles = styleDef->GetAngles( "viewangles" ); + viewOffset = styleDef->GetVector( "viewoffset" ); +} + +/* +================ +rvWeapon::Spawn +================ +*/ +void rvWeapon::Spawn ( void ) { + + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + +// RAVEN BEGIN +// nrausch: +#if defined(_XENON) + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +// RAVEN END + + // Initialize variables + projectileEnt = NULL; + kick_endtime = 0; + hideStart = 0.0f; + hideEnd = 0.0f; + hideOffset = 0.0f; + status = WP_HOLSTERED; + lastAttack = 0; + clipPredictTime = 0; + + muzzleAxis.Identity(); + muzzleOrigin.Zero(); + pushVelocity.Zero(); + playerViewAxis.Identity(); + playerViewOrigin.Zero(); + viewModelAxis.Identity(); + viewModelOrigin.Zero(); + + // View + viewModelForeshorten = spawnArgs.GetFloat ( "foreshorten", "1" ); + + FindViewModelPositionStyle( viewModelOffset, viewModelAngles ); + + // Offsets + weaponAngleOffsetAverages = spawnArgs.GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = spawnArgs.GetFloat( "weaponAngleOffsetScale", "0.25" ); + weaponAngleOffsetMax = spawnArgs.GetFloat( "weaponAngleOffsetMax", "10" ); + weaponOffsetTime = spawnArgs.GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = spawnArgs.GetFloat( "weaponOffsetScale", "0.005" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate" ) ); + altFireRate = SEC2MS ( spawnArgs.GetFloat ( "altFireRate" ) ); + if( altFireRate == 0 ) { + altFireRate = fireRate; + } + spread = (gameLocal.IsMultiplayer()&&spawnArgs.FindKey("spread_mp"))?spawnArgs.GetFloat ( "spread_mp" ):spawnArgs.GetFloat ( "spread" ); + nextAttackTime = 0; + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); + wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); + + // Attack related values + muzzle_kick_time = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = spawnArgs.GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = spawnArgs.GetVector( "muzzle_kick_offset" ); + + // General weapon properties + wfl.silent_fire = spawnArgs.GetBool( "silent_fire" ); + wfl.hasWindupAnim = spawnArgs.GetBool( "has_windup", "0" ); + icon = spawnArgs.GetString( "mtr_icon" ); + hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) ); + hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" ); + hideStartTime = gameLocal.time - hideTime; + muzzleOffset = weaponDef->dict.GetFloat ( "muzzleOffset", "14" ); + + // Ammo + clipSize = spawnArgs.GetInt( "clipSize" ); + ammoRequired = spawnArgs.GetInt( "ammoRequired" ); + lowAmmo = spawnArgs.GetInt( "lowAmmo" ); + ammoType = GetAmmoIndexForName( spawnArgs.GetString( "ammoType" ) ); + maxAmmo = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ); + + if ( ( ammoType < 0 ) || ( ammoType >= MAX_AMMO ) ) { + gameLocal.Warning( "Unknown ammotype for class '%s'", this->GetClassname ( ) ); + } + + // If the weapon has a clip, then fill it up + ammoClip = owner->inventory.clip[weaponIndex]; + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + } + + // Complex initializations Initialize + InitDefs( ); + InitWorldModel( ); + InitViewModel( ); + + // Requires the view model so must be done after it + InitLights( ); + + viewModel->PostGUIEvent( "weapon_init" ); + viewModel->PostGUIEvent( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent( "weapon_noammo" ); + } + + stateThread.SetName( va("%s_%s_%s", owner->GetName(), viewModel->GetName ( ), spawnArgs.GetString("classname") ) ); + stateThread.SetOwner( this ); + + forceGUIReload = true; +} + +/* +================ +rvWeapon::InitViewModel +================ +*/ +void rvWeapon::InitViewModel( void ) { + const char* guiName; + const char* temp; + int i; + const idKeyValue* kv; + + // Reset view model to clean state + viewModel->Clear ( ); + // Make sure the sound handle is initted + viewModel->refSound.referenceSoundHandle = -1; + + // Intialize the weapon guis + if ( spawnArgs.GetString ( "gui", "", &guiName ) ) { + int g = 0; + do { + viewModel->GetRenderEntity()->gui[g++] = uiManager->FindGui ( guiName, true, false, true ); + guiName = spawnArgs.GetString ( va("gui%d", g + 1 ) ); + } while ( *guiName && viewModel->GetRenderEntity()->gui[g-1] ); + } + + // Set the view models spawn args + viewModel->spawnArgs = weaponDef->dict; + + // Set the model for the view model + if ( isStrogg ) { + temp = spawnArgs.GetString ( "model_view_strogg", spawnArgs.GetString ( "model_view" ) ); + } else { + temp = spawnArgs.GetString ( "model_view" ); + } + viewModel->SetModel( temp, mods ); + + // Hide surfaces + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + + // Show and Hide the mods + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_mod%d", i+1) ), false ); + if ( !modDict ) { + continue; + } + + // Hide any show surfaces for mods that arent on + if ( !(mods & (1<MatchPrefix ( "mod_showsurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_showsurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); // NOTE: HIDING them because we don't have this mod yet + } + } else { + for ( kv = modDict->MatchPrefix ( "mod_hidesurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + } + } + + // find some joints in the model for locating effects + viewAnimator = viewModel->GetAnimator ( ); + barrelJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_barrel", "barrel" ) ); + flashJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flash", "flash" ) ); + ejectJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + guiLightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_guiLight", "guiLight" ) ); + flashlightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flashlight", "flashlight" ) ); + + // Eject offset + spawnArgs.GetVector ( "ejectOffset", "0 0 0", ejectOffset ); + + // Setup a skin for the view model + if ( spawnArgs.GetString ( "skin", "", &temp ) ) { + viewModel->GetRenderEntity()->customSkin = declManager->FindSkin ( temp ); + } + + // make sure we have the correct skin + viewModel->UpdateSkin(); +} + +/* +================ +rvWeapon::InitLights +================ +*/ +void rvWeapon::InitLights ( void ) { + const char* shader; + idVec4 color; + renderLight_t* light; + + memset ( lights, 0, sizeof(lights) ); + memset ( lightHandles, -1, sizeof(lightHandles) ); + + // setup gui light + light = &lights[WPLIGHT_GUI]; + shader = spawnArgs.GetString( "mtr_guiLightShader", "" ); + if ( shader && *shader && viewModel->GetRenderEntity()->gui[0] ) { + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = spawnArgs.GetFloat("glightRadius", "3" ); + color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light->shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->lightId = WPLIGHT_GUI * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + spawnArgs.GetVector ( "glightOffset", "0 0 0", guiLightOffset ); + } + + // Muzzle flash + light = &lights[WPLIGHT_MUZZLEFLASH]; + shader = spawnArgs.GetString( "mtr_flashShader", "muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector ( "flashTarget" ); + light->up = spawnArgs.GetVector ( "flashUp" ); + light->right = spawnArgs.GetVector ( "flashRight" ); + light->end = light->target; + } + light->lightId = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + muzzleFlashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) ); + muzzleFlashEnd = 0; + spawnArgs.GetVector ( "flashViewOffset", "0 0 0", muzzleFlashViewOffset ); + } + + // the world muzzle flash is the same, just positioned differently + lights[WPLIGHT_MUZZLEFLASH_WORLD] = lights[WPLIGHT_MUZZLEFLASH]; + light = &lights[WPLIGHT_MUZZLEFLASH_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_MUZZLEFLASH_WORLD * 100 + owner->entityNumber; + + // flashlight + light = &lights[WPLIGHT_FLASHLIGHT]; + shader = spawnArgs.GetString( "mtr_flashlightShader", "lights/muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashlightColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = + (float)spawnArgs.GetInt( "flashlightRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector( "flashlightTarget" ); + light->up = spawnArgs.GetVector( "flashlightUp" ); + light->right = spawnArgs.GetVector( "flashlightRight" ); + light->end = light->target; + } + + light->allowLightInViewID = owner->entityNumber+1; + light->lightId = WPLIGHT_FLASHLIGHT * 100 + owner->entityNumber; + spawnArgs.GetVector ( "flashlightViewOffset", "0 0 0", flashlightViewOffset ); + } + + // the world muzzle flashlight is the same, just positioned differently + lights[WPLIGHT_FLASHLIGHT_WORLD] = lights[WPLIGHT_FLASHLIGHT]; + light = &lights[WPLIGHT_FLASHLIGHT_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_FLASHLIGHT_WORLD * 100 + owner->entityNumber; +} + +/* +================ +rvWeapon::InitDefs +================ +*/ +void rvWeapon::InitDefs( void ) { + const char* name; + const idDeclEntityDef* def; + const char* spawnclass; + idTypeInfo* cls; + + // get the projectile + attackDict.Clear(); + + // Projectile + if ( spawnArgs.GetString( "def_projectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_hitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + hitscanAttackDef = def->Index(); + } + wfl.attackHitscan = true; + } + + // Alternate projectile + attackAltDict.Clear (); + if ( spawnArgs.GetString( "def_altprojectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown alt projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for alt projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_althitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + wfl.attackAltHitscan = true; + } + + // get the melee damage def + meleeDistance = spawnArgs.GetFloat( "melee_distance" ); + if ( spawnArgs.GetString( "def_melee", "", &name ) && *name ) { + meleeDef = gameLocal.FindEntityDef( name, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s' for weapon '%s'", name, weaponDef->GetName() ); + } + } else { + meleeDef = NULL; + } + + // get the brass def + brassDict.Clear(); + if ( spawnArgs.GetString( "def_ejectBrass", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + brassDict = def->dict; + // force any brass to spawn as client moveable + brassDict.Set( "spawnclass", "rvClientMoveable" ); + } + } +} + +/* +================ +rvWeapon::Think +================ +*/ +void rvWeapon::Think ( void ) { + + // Cache the player origin and axis + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + // calculate weapon position based on player movement bobbing + owner->CalculateViewWeaponPos( viewModelOrigin, viewModelAxis ); + + // hide offset is for dropping the gun when approaching a GUI or NPC + // This is simpler to manage than doing the weapon put-away animation + if ( gameLocal.time - hideStartTime < hideTime ) { + float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime; + if ( hideStart < hideEnd ) { + frac = 1.0f - frac; + frac = 1.0f - frac * frac; + } else { + frac = frac * frac; + } + hideOffset = hideStart + ( hideEnd - hideStart ) * frac; + } else { + hideOffset = hideEnd; + } + viewModelOrigin += hideOffset * viewModelAxis[ 2 ]; + + // kick up based on repeat firing + MuzzleRise( viewModelOrigin, viewModelAxis ); + + if ( viewModel ) { + // set the physics position and orientation + viewModel->GetPhysics()->SetOrigin( viewModelOrigin ); + viewModel->GetPhysics()->SetAxis( viewModelAxis ); + viewModel->UpdateVisuals(); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + + // Update the zoom variable before updating the script + wsfl.zoom = owner->IsZoomed( ); + + // Only update the state loop on new frames + if ( gameLocal.isNewFrame ) { + stateThread.Execute( ); + } + + if ( viewModel ) { + viewModel->UpdateAnimation( ); + } + + // Clear reload and flashlight flags + wsfl.reload = false; + wsfl.flashlight = false; + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( worldModel && worldModel->GetRenderEntity() ) { + // always show your own weapon + if( owner->entityNumber == gameLocal.localClientNum ) { + worldModel->GetRenderEntity()->suppressLOD = 1; + } else { + worldModel->GetRenderEntity()->suppressLOD = 0; + } + + if ( gameLocal.IsMultiplayer() && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all weapon shadows for the local client + worldModel->GetRenderEntity()->suppressShadowInViewID = gameLocal.localClientNum+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all weapon shadows + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + // Only show weapon shadows for other clients + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } + + UpdateGUI(); + + // Update lights + UpdateFlashlight ( ); + UpdateMuzzleFlash ( ); + + // update the gui light + renderLight_t& light = lights[WPLIGHT_GUI]; + if ( light.lightRadius[0] && guiLightJointView != INVALID_JOINT ) { + if ( viewModel ) { + idVec4 color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light.shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light.shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light.shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + GetGlobalJointTransform( true, guiLightJointView, light.origin, light.axis, guiLightOffset ); + UpdateLight ( WPLIGHT_GUI ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + } + + // Alert Monsters if the flashlight is one or a muzzle flash is active? + if ( !gameLocal.isMultiplayer ) { + if ( !owner->fl.notarget && (lightHandles[WPLIGHT_MUZZLEFLASH] != -1 || lightHandles[WPLIGHT_FLASHLIGHT] != -1 ) ) { + AlertMonsters ( ); + } + } +} + +/* +================ +rvWeapon::InitWorldModel +================ +*/ +void rvWeapon::InitWorldModel( void ) { + idEntity *ent; + + ent = worldModel; + if ( !ent ) { + gameLocal.Warning ( "InitWorldModel failed due to missing entity" ); + return; + } + + const char *model = spawnArgs.GetString( "model_world" ); + const char *attach = spawnArgs.GetString( "joint_attach" ); + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner, attach, true ); + ent->GetPhysics()->SetOrigin( vec3_origin ); + ent->GetPhysics()->SetAxis( mat3_identity ); + + // supress model in player views, but allow it in mirrors and remote views + renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity(); + if ( worldModelRenderEntity ) { + worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } + + // the renderEntity is reused, so the relevant fields (except this one) appear to be correctly reinitialized + worldModel->GetRenderEntity()->suppressSurfaceMask = 0; + + // Cache the world joints + worldAnimator = ent->GetAnimator ( ); + flashJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flash", "flash" ) ); + flashlightJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flashlight", "flashlight" ) ); + ejectJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_eject", "eject" ) ); +} + +/* +================ +rvWeapon::SetState +================ +*/ +void rvWeapon::SetState( const char *statename, int blendFrames ) { + stateThread.SetState( statename, blendFrames ); +} + +/* +================ +rvWeapon::PostState +================ +*/ +void rvWeapon::PostState( const char* statename, int blendFrames ) { + stateThread.PostState( statename, blendFrames ); +} + +/* +===================== +rvWeapon::ExecuteState +===================== +*/ +void rvWeapon::ExecuteState ( const char* statename ) { + SetState ( statename, 0 ); + stateThread.Execute ( ); +} + +/* +================ +rvWeapon::UpdateLight +================ +*/ +void rvWeapon::UpdateLight ( int lightID ) { + if ( lightHandles[lightID] == -1 ) { + lightHandles[lightID] = gameRenderWorld->AddLightDef ( &lights[lightID] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[lightID], &lights[lightID] ); + } +} + +/* +================ +rvWeapon::FreeLight +================ +*/ +void rvWeapon::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + + +/*********************************************************************** + + Networking + +***********************************************************************/ + +/* +================ +rvWeapon::WriteToSnapshot +================ +*/ +void rvWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ammoClip, ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ReadFromSnapshot +================ +*/ +void rvWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ammoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::SkipFromSnapshot +================ +*/ +void rvWeapon::SkipFromSnapshot ( const idBitMsgDelta &msg ) { + msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ClientStale +================ +*/ +void rvWeapon::ClientStale( void ) { +} + +/* +================ +rvWeapon::ClientReceiveEvent +================ +*/ +bool rvWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_RELOAD: { + if ( gameLocal.time - time < 1000 ) { + wsfl.netReload = true; + wsfl.netEndReload = false; + } + return true; + } + case EVENT_ENDRELOAD: { + wsfl.netEndReload = true; + return true; + } + case EVENT_CHANGESKIN: { +/* + // FIXME: use idGameLocal::ReadDecl + int index = msg.ReadLong(); + renderEntity.customSkin = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL; + UpdateVisuals(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( renderEntity.customSkin ); + } + */ + return true; + } + } + return false; +} + +/*********************************************************************** + + Save / Load + +***********************************************************************/ + +/* +================ +rvWeapon::Save +================ +*/ +void rvWeapon::Save ( idSaveGame *savefile ) const { + int i; + + // Flags + savefile->Write ( &wsfl, sizeof( wsfl ) ); + savefile->Write ( &wfl, sizeof( wfl ) ); + + // Write all cached joints + savefile->WriteJoint ( barrelJointView ); + savefile->WriteJoint ( flashJointView ); + savefile->WriteJoint ( ejectJointView ); + savefile->WriteJoint ( guiLightJointView ); + savefile->WriteJoint ( flashlightJointView ); + + savefile->WriteJoint ( flashJointWorld ); + savefile->WriteJoint ( ejectJointWorld ); + savefile->WriteJoint ( flashlightJointWorld ); + + savefile->WriteInt ( status ); + savefile->WriteInt ( lastAttack ); + + // Hide / Show + savefile->WriteInt ( hideTime ); + savefile->WriteFloat ( hideDistance ); + savefile->WriteInt ( hideStartTime ); + savefile->WriteFloat ( hideStart ); + savefile->WriteFloat ( hideEnd ); + savefile->WriteFloat ( hideOffset ); + + // Write attack related values + savefile->WriteVec3 ( pushVelocity ); + savefile->WriteInt ( kick_endtime ); + savefile->WriteInt ( muzzle_kick_time ); + savefile->WriteInt ( muzzle_kick_maxtime ); + savefile->WriteAngles ( muzzle_kick_angles ); + savefile->WriteVec3 ( muzzle_kick_offset ); + savefile->WriteVec3 ( muzzleOrigin ); + savefile->WriteMat3 ( muzzleAxis ); + savefile->WriteFloat ( muzzleOffset ); + projectileEnt.Save ( savefile ); + savefile->WriteVec3 ( ejectOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt ( fireRate ); + savefile->WriteFloat ( spread ); + // savefile->WriteInt ( nextAttackTime ); // cnicholson: This is set to 0 in restore, so don't save it + + // cnicholson: These 3 idDicts are setup during restore, no need to save them. + // TOSAVE: idDict attackAltDict; + // TOSAVE: idDict attackDict; + // TOSAVE: idDict brassDict; + + // Defs + // TOSAVE: const idDeclEntityDef * meleeDef; // cnicholson: This is setup in restore, so don't save it + savefile->WriteFloat ( meleeDistance ); + + // Zoom + savefile->WriteInt ( zoomFov ); + savefile->WriteUserInterface ( zoomGui, true ); + savefile->WriteFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->WriteInt( lightHandles[i] ); + savefile->WriteRenderLight( lights[i] ); + } + savefile->WriteVec3 ( guiLightOffset ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashViewOffset ); + savefile->WriteVec3 ( flashlightViewOffset ); + savefile->WriteBool ( flashlightOn ); // cnicholson: Added unsaved var + savefile->WriteVec3 ( flashlightViewOffset ); // cnicholson: Added unsaved var + + // Write ammo values + savefile->WriteInt ( ammoType ); + savefile->WriteInt ( ammoRequired ); + savefile->WriteInt ( clipSize ); + savefile->WriteInt ( ammoClip ); + savefile->WriteInt ( lowAmmo ); + savefile->WriteInt ( maxAmmo ); + + // multiplayer + savefile->WriteInt ( clipPredictTime ); // TOSAVE: Save MP value? + + // View + savefile->WriteVec3 ( playerViewOrigin ); + savefile->WriteMat3 ( playerViewAxis ); + + savefile->WriteVec3 ( viewModelOrigin ); + savefile->WriteMat3 ( viewModelAxis ); + savefile->WriteAngles ( viewModelAngles ); + savefile->WriteVec3 ( viewModelOffset ); // cnicholson: Added unsaved var + + // Offsets + savefile->WriteInt ( weaponAngleOffsetAverages ); + savefile->WriteFloat ( weaponAngleOffsetScale ); + savefile->WriteFloat ( weaponAngleOffsetMax ); + savefile->WriteFloat ( weaponOffsetTime ); + savefile->WriteFloat ( weaponOffsetScale ); + + savefile->WriteString ( icon ); + savefile->WriteBool ( isStrogg ); + + // TOSAVE: idDict spawnArgs; + + // TOSAVE: idEntityPtr viewModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* viewAnimator; + // TOSAVE: idEntityPtr worldModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* worldAnimator; + // TOSAVE: const idDeclEntityDef* weaponDef; + // TOSAVE: idScriptObject* scriptObject; + savefile->WriteObject ( owner ); + savefile->WriteInt ( weaponIndex ); // cnicholson: Added unsaved var + savefile->WriteInt ( mods ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( viewModelForeshorten ); + + stateThread.Save( savefile ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } + + savefile->WriteInt ( methodOfDeath ); // cnicholson: Added unsaved var +} + +/* +================ +rvWeapon::Restore +================ +*/ +void rvWeapon::Restore ( idRestoreGame *savefile ) { + int i; + const idDeclEntityDef* def; + + // General + savefile->Read ( &wsfl, sizeof( wsfl ) ); + savefile->Read ( &wfl, sizeof( wfl ) ); + + // Read cached joints + savefile->ReadJoint ( barrelJointView ); + savefile->ReadJoint ( flashJointView ); + savefile->ReadJoint ( ejectJointView ); + savefile->ReadJoint ( guiLightJointView ); + savefile->ReadJoint ( flashlightJointView ); + + savefile->ReadJoint ( flashJointWorld ); + savefile->ReadJoint ( ejectJointWorld ); + savefile->ReadJoint ( flashlightJointWorld ); + + savefile->ReadInt ( (int&)status ); + savefile->ReadInt ( lastAttack ); + + // Hide / Show + savefile->ReadInt ( hideTime ); + savefile->ReadFloat ( hideDistance ); + savefile->ReadInt ( hideStartTime ); + savefile->ReadFloat ( hideStart ); + savefile->ReadFloat ( hideEnd ); + savefile->ReadFloat ( hideOffset ); + + // Read attack related values + savefile->ReadVec3 ( pushVelocity ); + savefile->ReadInt ( kick_endtime ); + savefile->ReadInt ( muzzle_kick_time ); + savefile->ReadInt ( muzzle_kick_maxtime ); + savefile->ReadAngles ( muzzle_kick_angles ); + savefile->ReadVec3 ( muzzle_kick_offset ); + savefile->ReadVec3 ( muzzleOrigin ); + savefile->ReadMat3 ( muzzleAxis ); + savefile->ReadFloat ( muzzleOffset ); + projectileEnt.Restore ( savefile ); + savefile->ReadVec3 ( ejectOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt ( fireRate ); + savefile->ReadFloat ( spread ); + nextAttackTime = 0; + + // Attack Alt Def + attackAltDict.Clear( ); + wfl.attackAltHitscan = false; + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_altprojectile" ), false ); + if ( def ) { + attackAltDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_althitscan" ), false ); + if ( def ) { + attackAltDict = def->dict; + wfl.attackAltHitscan = true; + } + } + + // Attack def + attackDict.Clear( ); + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + wfl.attackHitscan = false; + if ( def ) { + attackDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_hitscan" ), false ); + if ( def ) { + attackDict = def->dict; + wfl.attackHitscan = true; + } + } + + // Brass Def + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_ejectBrass" ), false ); + if ( def ) { + brassDict = def->dict; + } else { + brassDict.Clear(); + } + + // Melee Def + meleeDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_melee" ), false ); + savefile->ReadFloat( meleeDistance ); + + // Zoom + savefile->ReadInt ( zoomFov ); + savefile->ReadUserInterface ( zoomGui, &spawnArgs ); + savefile->ReadFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->ReadInt ( lightHandles[i] ); + savefile->ReadRenderLight( lights[i] ); + if ( lightHandles[i] != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandles[i] = gameRenderWorld->AddLightDef ( &lights[i] ); + } + } + savefile->ReadVec3 ( guiLightOffset ); + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashViewOffset ); + savefile->ReadVec3 ( flashlightViewOffset ); + savefile->ReadBool ( flashlightOn ); // cnicholson: Added unrestored var + savefile->ReadVec3 ( flashlightViewOffset ); // cnicholson: Added unrestored var + + // Read the ammo values + savefile->ReadInt ( (int&)ammoType ); + savefile->ReadInt ( ammoRequired ); + savefile->ReadInt ( clipSize ); + savefile->ReadInt ( ammoClip ); + savefile->ReadInt ( lowAmmo ); + savefile->ReadInt ( maxAmmo ); + + // multiplayer + savefile->ReadInt ( clipPredictTime ); // TORESTORE: Restore MP value? + + // View + savefile->ReadVec3 ( playerViewOrigin ); + savefile->ReadMat3 ( playerViewAxis ); + + savefile->ReadVec3 ( viewModelOrigin ); + savefile->ReadMat3 ( viewModelAxis ); + savefile->ReadAngles ( viewModelAngles ); + savefile->ReadVec3 ( viewModelOffset ); // cnicholson: Added unrestored var + + // Offsets + savefile->ReadInt ( weaponAngleOffsetAverages ); + savefile->ReadFloat ( weaponAngleOffsetScale ); + savefile->ReadFloat ( weaponAngleOffsetMax ); + savefile->ReadFloat ( weaponOffsetTime ); + savefile->ReadFloat ( weaponOffsetScale ); + + savefile->ReadString ( icon ); + savefile->ReadBool ( isStrogg ); + + // TORESTORE: idDict spawnArgs; + + // TORESTORE: idAnimator* viewAnimator; + // TORESTORE: idAnimator* worldAnimator; + // TORESTORE: const idDeclEntityDef* weaponDef; + // TORESTORE: idScriptObject* scriptObject; + + // Entities + savefile->ReadObject( reinterpret_cast( owner ) ); + viewModel = owner->GetWeaponViewModel ( ); + worldModel = owner->GetWeaponWorldModel ( ); + + savefile->ReadInt ( weaponIndex ); // cnicholson: Added unrestored var + savefile->ReadInt ( mods ); // cnicholson: Added unrestored var + + savefile->ReadFloat ( viewModelForeshorten ); + + stateThread.Restore( savefile, this ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } + + savefile->ReadInt ( methodOfDeath ); // cnicholson: Added unrestored var + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvWeapon::Hide +================ +*/ +void rvWeapon::Hide( void ) { + muzzleFlashEnd = 0; + + if ( viewModel ) { + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide ( ); + } + + // Stop flashlight and gui lights + FreeLight ( WPLIGHT_GUI ); + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::Show +================ +*/ +void rvWeapon::Show ( void ) { + if ( viewModel ) { + viewModel->Show(); + } + if ( worldModel ) { + worldModel->Show(); + } +} + +/* +================ +rvWeapon::IsHidden +================ +*/ +bool rvWeapon::IsHidden( void ) const { + return !viewModel || viewModel->IsHidden(); +} + +/* +================ +rvWeapon::HideWorldModel +================ +*/ +void rvWeapon::HideWorldModel ( void ) { + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::ShowWorldModel +================ +*/ +void rvWeapon::ShowWorldModel ( void ) { + if ( worldModel ) { + worldModel->Show(); + } +} + + +/* +================ +rvWeapon::LowerWeapon +================ +*/ +void rvWeapon::LowerWeapon( void ) { + if ( !wfl.hide ) { + hideStart = 0.0f; + hideEnd = hideDistance; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = true; + } +} + +/* +================ +rvWeapon::RaiseWeapon +================ +*/ +void rvWeapon::RaiseWeapon( void ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->Show(); + + if ( forceGUIReload ) { + forceGUIReload = false; + int ammo = AmmoInClip(); + for ( int g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + idUserInterface* gui = viewModel->GetRenderEntity()->gui[g]; + if ( gui ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + } + + if ( wfl.hide ) { + hideStart = hideDistance; + hideEnd = 0.0f; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = false; + } +} + +/* +================ +rvWeapon::PutAway +================ +*/ +void rvWeapon::PutAway( void ) { + wfl.hasBloodSplat = false; + wsfl.lowerWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_lower" ); +} + +/* +================ +rvWeapon::Raise +================ +*/ +void rvWeapon::Raise( void ) { + wsfl.raiseWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_raise" ); +} + +/* +================ +rvWeapon::Flashlight +================ +*/ +void rvWeapon::Flashlight ( void ) { + wsfl.flashlight = true; +} + +/* +================ +rvWeapon::SetPushVelocity +================ +*/ +void rvWeapon::SetPushVelocity( const idVec3& _pushVelocity ) { + pushVelocity = _pushVelocity; +} + +/* +================ +rvWeapon::Reload +NOTE: this is only for impulse-triggered reload, auto reload is scripted +================ +*/ +void rvWeapon::Reload( void ) { + if ( clipSize ) { + wsfl.reload = true; + } +} + +/* +================ +rvWeapon::CancelReload +================ +*/ +void rvWeapon::CancelReload( void ) { + wsfl.attack = true; +} + +/* +================ +rvWeapon::AutoReload +================ +*/ +bool rvWeapon::AutoReload ( void ) { + assert( owner ); + + // on a network client, never predict reloads of other clients. wait for the server + if ( gameLocal.isClient ) { + return false; + } + return gameLocal.userInfo[ owner->entityNumber ].GetBool( "ui_autoReload" ); +} + +/* +================ +rvWeapon::NetReload +================ +*/ +void rvWeapon::NetReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_RELOAD, NULL, false, -1 ); + } +} + +/* +=============== +rvWeapon::NetEndReload +=============== +*/ +void rvWeapon::NetEndReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_ENDRELOAD, NULL, false, -1 ); + } +} + +/* +================ +rvWeapon::SetStatus +================ +*/ +void rvWeapon::SetStatus ( weaponStatus_t _status ) { + status = _status; + switch ( status ) { + case WP_READY: + wsfl.raiseWeapon = false; + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_ready" ); + break; + case WP_OUTOFAMMO: + wsfl.raiseWeapon = false; + break; + case WP_RELOAD: + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_reload" ); + break; + case WP_HOLSTERED: + case WP_RISING: + wsfl.lowerWeapon = false; + owner->WeaponRisingCallback(); + break; + case WP_LOWERING: + wsfl.raiseWeapon = false; + owner->WeaponLoweringCallback(); + break; + } +} + +/* +================ +rvWeapon::OwnerDied +================ +*/ +void rvWeapon::OwnerDied( void ) { + + CleanupWeapon(); + + ExecuteState( "OwnerDied" ); + + if ( viewModel ) { + viewModel->StopSound( SCHANNEL_ANY, false ); + viewModel->StopAllEffects( ); + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::BeginAttack +================ +*/ +void rvWeapon::BeginAttack( void ) { + wsfl.attack = true; + + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } +} + +/* +================ +rvWeapon::EndAttack +================ +*/ +void rvWeapon::EndAttack( void ) { + wsfl.attack = false; +} + +/* +================ +rvWeapon::isReady +================ +*/ +bool rvWeapon::IsReady( void ) const { + return !wfl.hide && !(gameLocal.time - hideStartTime < hideTime) && (viewModel && !viewModel->IsHidden()) && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +rvWeapon::IsReloading +================ +*/ +bool rvWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +rvWeapon::IsHolstered +================ +*/ +bool rvWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + +/* +================ +rvWeapon::ShowCrosshair +================ +*/ +bool rvWeapon::ShowCrosshair( void ) const { + if ( owner->IsZoomed ( ) && zoomGui && wfl.zoomHideCrosshair ) { + return false; + } + return !( status == WP_HOLSTERED ); +} + +/* +===================== +rvWeapon::CanDrop +===================== +*/ +bool rvWeapon::CanDrop( void ) const { + const char *classname = spawnArgs.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/* +===================== +rvViewWeapon::CanZoom +===================== +*/ +bool rvWeapon::CanZoom( void ) const { +#ifdef _XENON + // apparently a xenon specific bug in medlabs. + return zoomFov != -1 && !IsHidden(); +#else + return zoomFov != -1; +#endif +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +rvWeapon::MuzzleRise +================ +*/ +void rvWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = kick_endtime - gameLocal.time; + if ( time <= 0 ) { + return; + } + + if ( muzzle_kick_maxtime <= 0 ) { + return; + } + + if ( time > muzzle_kick_maxtime ) { + time = muzzle_kick_maxtime; + } + + amount = ( float )time / ( float )muzzle_kick_maxtime; + ang = muzzle_kick_angles * amount; + offset = muzzle_kick_offset * amount; + + origin = origin - axis * offset; + axis = ang.ToMat3() * axis; +} + +/* +================ +rvWeapon::UpdateFlashPosition +================ +*/ +void rvWeapon::UpdateMuzzleFlash ( void ) { + // remove the muzzle flash light when it's done + if ( gameLocal.time >= muzzleFlashEnd || !gameLocal.GetLocalPlayer() || !owner || gameLocal.GetLocalPlayer()->GetInstance() != owner->GetInstance() ) { + FreeLight ( WPLIGHT_MUZZLEFLASH ); + FreeLight ( WPLIGHT_MUZZLEFLASH_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + light.origin = playerViewOrigin + (playerViewAxis * muzzleFlashViewOffset); + light.axis = playerViewAxis; + + // put the world muzzle flash on the end of the joint, no matter what + GetGlobalJointTransform( false, flashJointWorld, lightWorld.origin, lightWorld.axis ); + + UpdateLight ( WPLIGHT_MUZZLEFLASH ); + UpdateLight ( WPLIGHT_MUZZLEFLASH_WORLD ); +} + +/* +================ +rvWeapon::UpdateFlashlight +================ +*/ +void rvWeapon::UpdateFlashlight ( void ) { + // Turn flashlight off? + if (! owner->IsFlashlightOn ( ) ) { + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_FLASHLIGHT]; + renderLight_t& lightWorld = lights[WPLIGHT_FLASHLIGHT_WORLD]; + trace_t tr; + + // the flash has an explicit joint for locating it + GetGlobalJointTransform( true, flashlightJointView, light.origin, light.axis, flashlightViewOffset ); + + // if the desired point is inside or very close to a wall, back it up until it is clear + gameLocal.TracePoint( owner, tr, light.origin - playerViewAxis[0] * 8.0f, light.origin, MASK_SHOT_BOUNDINGBOX, owner ); + + // be at least 8 units away from a solid + light.origin = tr.endpos - (tr.fraction < 1.0f ? (playerViewAxis[0] * 8) : vec3_origin); + + // put the world muzzle flash on the end of the joint, no matter what + if ( flashlightJointWorld != INVALID_JOINT ) { + GetGlobalJointTransform( false, flashlightJointWorld, lightWorld.origin, lightWorld.axis ); + } else { + lightWorld.origin = playerViewOrigin + playerViewAxis[0] * 20.0f; + lightWorld.axis = playerViewAxis; + } + + UpdateLight ( WPLIGHT_FLASHLIGHT ); + UpdateLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::MuzzleFlash +================ +*/ +void rvWeapon::MuzzleFlash ( void ) { + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + if ( !g_muzzleFlash.GetBool() || flashJointView == INVALID_JOINT || !light.lightRadius[0] ) { + return; + } + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( viewModel ) { + // these will be different each fire + light.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + light.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + light.noShadows = true; + + lightWorld.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lightWorld.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + lightWorld.noShadows = true; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + UpdateMuzzleFlash ( ); +} + + +/* +================ +rvWeapon::UpdateGUI +================ +*/ +void rvWeapon::UpdateGUI( void ) { + idUserInterface* gui; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + gui = viewModel->GetRenderEntity()->gui[0]; + if ( !gui || status == WP_HOLSTERED ) { + return; + } + int g; +for ( g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + gui = viewModel->GetRenderEntity()->gui[g]; + + if ( gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( !p ) { + return; + } + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } + + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + // show remaining ammo + if ( gui->State().GetInt ( "player_cachedammo", "-1") != ammo ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + +// viewModel->GetRenderEntity()->gui[g]->SetStateInt ( "player_clip_size", ClipSize() ); +} + for ( int i = 0; i < viewModel->pendingGUIEvents.Num(); i ++ ) { + gui->HandleNamedEvent( viewModel->pendingGUIEvents[i] ); + } + viewModel->pendingGUIEvents.Clear(); +} + +/* +================ +rvWeapon::UpdateCrosshairGUI +================ +*/ +void rvWeapon::UpdateCrosshairGUI( idUserInterface* gui ) const { +// RAVEN BEGIN +// cnicholson: Added support for universal crosshair + + // COMMENTED OUT until Custom crosshair GUI is implemented. + if ( g_crosshairCustom.GetBool() ) { // If there's a custom crosshair, use it. + gui->SetStateString( "crossImage", g_crosshairCustomFile.GetString()); + + const idMaterial *material = declManager->FindMaterial( g_crosshairCustomFile.GetString() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } else { + gui->SetStateString( "crossImage", spawnArgs.GetString( "mtr_crosshair" ) ); + + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } + +// Original Block + //gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); +// RAVEN END + gui->SetStateString( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt( "crossOffsetX", spawnArgs.GetInt( "crosshairOffsetX", "0" ) ); + gui->SetStateInt( "crossOffsetY", spawnArgs.GetInt( "crosshairOffsetY", "0" ) ); + gui->StateChanged( gameLocal.time ); +} + +/* +================ +rvWeapon::ForeshortenAxis +================ +*/ +idMat3 rvWeapon::ForeshortenAxis( const idMat3& axis ) const { + return idMat3( axis[0] * viewModelForeshorten, axis[1], axis[2] ); +} + +/* +================ +rvWeapon::GetAngleOffsets +================ +*/ +void rvWeapon::GetAngleOffsets ( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +rvWeapon::GetTimeOffsets +================ +*/ +void rvWeapon::GetTimeOffsets ( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + +/* +================ +rvWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool rvWeapon::GetGlobalJointTransform ( bool view, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset ) { + if ( view) { + // view model + if ( viewModel && viewAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = origin * ForeshortenAxis(viewModelAxis) + viewModelOrigin; + axis = axis * viewModelAxis; + return true; + } + } else { + // world model + if ( worldModel && worldAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = worldModel->GetPhysics()->GetOrigin() + origin * worldModel->GetPhysics()->GetAxis(); + axis = axis * worldModel->GetPhysics()->GetAxis(); + return true; + } + } + origin = viewModelOrigin + offset * viewModelAxis; + axis = viewModelAxis; + return false; +} + +/*********************************************************************** + + Ammo + +***********************************************************************/ + +/* +================ +rvWeapon::GetAmmoIndexForName +================ +*/ +int rvWeapon::GetAmmoIndexForName( const char *ammoname ) { + int num; + const idDict *ammoDict; + + assert( ammoname ); + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + if ( !ammoname[ 0 ] ) { + return 0; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= MAX_AMMOTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, MAX_AMMOTYPES ); + } + + return num; +} + +/* +================ +rvWeapon::GetAmmoNameForNum +================ +*/ +const char* rvWeapon::GetAmmoNameForIndex( int index ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + char text[ 32 ]; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + sprintf( text, "%d", index ); + + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( kv->GetValue() == text ) { + return kv->GetKey(); + } + } + + return NULL; +} + +/* +================ +rvWeapon::TotalAmmoCount +================ +*/ +int rvWeapon::TotalAmmoCount ( void ) const { + return owner->inventory.HasAmmo( ammoType, 1 ); +} + +/* +================ +rvWeapon::AmmoAvailable +================ +*/ +int rvWeapon::AmmoAvailable( void ) const { + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, ammoRequired ); + } else { + return 0; + } +} + +/* +================ +rvWeapon::AmmoInClip +================ +*/ +int rvWeapon::AmmoInClip( void ) const { + if ( !clipSize ) { + return AmmoAvailable(); + } + return ammoClip; +} + +/* +================ +rvWeapon::ResetAmmoClip +================ +*/ +void rvWeapon::ResetAmmoClip( void ) { + ammoClip = -1; +} + +/* +================ +rvWeapon::GetAmmoType +================ +*/ +int rvWeapon::GetAmmoType( void ) const { + return ammoType; +} + +/* +================ +rvWeapon::ClipSize +================ +*/ +int rvWeapon::ClipSize( void ) const { + return clipSize; +} + +/* +================ +rvWeapon::LowAmmo +================ +*/ +int rvWeapon::LowAmmo() const { + return lowAmmo; +} + +/* +================ +rvWeapon::AmmoRequired +================ +*/ +int rvWeapon::AmmoRequired( void ) const { + return ammoRequired; +} + +/* +================ +rvWeapon::SetClip +================ +*/ +void rvWeapon::SetClip ( int amount ) { + ammoClip = amount; + if ( amount < 0 ) { + ammoClip = 0; + } else if ( amount > clipSize ) { + ammoClip = clipSize; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/* +================ +rvWeapon::UseAmmo +================ +*/ +void rvWeapon::UseAmmo ( int amount ) { + owner->inventory.UseAmmo( ammoType, amount * ammoRequired ); + if ( clipSize && ammoRequired ) { + ammoClip -= ( amount * ammoRequired ); + if ( ammoClip < 0 ) { + ammoClip = 0; + } + } +} + +/* +================ +rvWeapon::AddToClip +================ +*/ +void rvWeapon::AddToClip ( int amount ) { + int ammoAvail; + + if ( gameLocal.isClient ) { + return; + } + + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoAvail > 0 && ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/*********************************************************************** + + Attack + +***********************************************************************/ + + +/* +================ +rvWeapon::Attack +================ +*/ +void rvWeapon::Attack( bool altAttack, int num_attacks, float spread, float fuseOffset, float power ) { + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + if ( viewModel->IsHidden() ) { + return; + } + + // avoid all ammo considerations on an MP client + if ( !gameLocal.isClient ) { + // check if we're out of ammo or the clip is empty + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) { + return; + } + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + if ( clipSize && ammoRequired ) { + clipPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + ammoClip -= 1; + } + + // wake up nearby monsters + if ( !wfl.silent_fire ) { + gameLocal.AlertAI( owner ); + } + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + viewModel->SetShaderParm ( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + viewModel->SetShaderParm ( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + if ( worldModel.GetEntity() ) { + worldModel->SetShaderParm( SHADERPARM_DIVERSITY, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel->SetShaderParm( SHADERPARM_TIMEOFFSET, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && spawnArgs.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // go straight out of the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + muzzleOrigin += playerViewAxis[0] * muzzleOffset; + } + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.realClientTime ) { + kick_endtime = gameLocal.realClientTime; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime; + } + + // add the muzzleflash + MuzzleFlash(); + + // quad damage overlays a sound + if ( owner->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + viewModel->StartSound( "snd_quaddamage", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + // Muzzle flash effect + bool muzzleTint = spawnArgs.GetBool( "muzzleTint" ); + viewModel->PlayEffect( "fx_muzzleflash", flashJointView, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict, "fx_muzzleflash_world" ), flashJointWorld, vec3_origin, mat3_identity, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // Inform the gui of the ammo change + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } + + // The attack is either a hitscan or a launched projectile, do that now. + if ( !gameLocal.isClient ) { + idDict& dict = altAttack ? attackAltDict : attackDict; + power *= owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ); + if ( altAttack ? wfl.attackAltHitscan : wfl.attackHitscan ) { + Hitscan( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, power ); + } else { + LaunchProjectiles( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, fuseOffset, power ); + } + //asalmon: changed to keep stats even in single player + statManager->WeaponFired( owner, weaponIndex, num_attacks ); + + } +} + +/* +================ +rvWeapon::LaunchProjectiles +================ +*/ +void rvWeapon::LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ) { + idProjectile* proj; + idEntity* ent; + int i; + float spreadRad; + idVec3 dir; + idBounds ownerBounds; + + if ( gameLocal.isClient ) { + return; + } + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack ( owner, muzzleOrigin, muzzleAxis[0] ); + } + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + spreadRad = DEG2RAD( spread ); + + idVec3 dirOffset; + idVec3 startOffset; + + spawnArgs.GetVector( "dirOffset", "0 0 0", dirOffset ); + spawnArgs.GetVector( "startOffset", "0 0 0", startOffset ); + + for( i = 0; i < num_projectiles; i++ ) { + float ang; + float spin; + idVec3 dir; + idBounds projBounds; + idVec3 muzzle_pos; + + // Calculate a random launch direction based on the spread + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; +#endif +//RAVEN END + dir.Normalize(); + + // If a projectile entity has already been created then use that one, otherwise + // spawn a new one based on the given dictionary + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + dict.SetInt( "instance", owner->GetInstance() ); + gameLocal.SpawnEntityDef( dict, &ent, false ); + } + + // Make sure it spawned + if ( !ent ) { + gameLocal.Error( "failed to spawn projectile for weapon '%s'", weaponDef->GetName ( ) ); + } + + assert ( ent->IsType( idProjectile::GetClassType() ) ); + + // Create the projectile + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin + startOffset, dir, NULL, owner->extraProjPassEntity ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + idVec3 start; + float distance; + trace_t tr; +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) { + start = muzzle_pos + distance * muzzleAxis[0]; + } +#else + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } +#endif +//RAVEN END + else { + start = ownerBounds.GetCenter(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( owner, tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + muzzle_pos = tr.endpos; + } + + // Launch the actual projectile + proj->Launch( muzzle_pos + startOffset, dir, pushVelocity, fuseOffset, power ); + + // Increment the projectile launch count and let the derived classes + // mess with it if they want. + OnLaunchProjectile ( proj ); + } +} + +/* +================ +rvWeapon::OnLaunchProjectile +================ +*/ +void rvWeapon::OnLaunchProjectile ( idProjectile* proj ) { + owner->AddProjectilesFired( 1 ); + if ( proj ) { + proj->methodOfDeath = owner->GetCurrentWeapon(); + } +} + +/* +================ +rvWeapon::Hitscan +================ +*/ +void rvWeapon::Hitscan( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ) { + idVec3 fxOrigin; + idMat3 fxAxis; + int i; + float ang; + float spin; + idVec3 dir; + int areas[ 2 ]; + + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack( owner, muzzleOrigin, muzzleAxis[0] ); + } + + GetGlobalJointTransform( true, flashJointView, fxOrigin, fxAxis, dict.GetVector( "fxOriginOffset" ) ); + + if ( gameLocal.isServer ) { + + assert( hitscanAttackDef >= 0 ); + assert( owner && owner->entityNumber < MAX_CLIENTS ); + int ownerId = owner ? owner->entityNumber : 0; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_HITSCAN ); + msg.WriteLong( hitscanAttackDef ); + msg.WriteBits( ownerId, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteFloat( muzzleOrigin[0] ); + msg.WriteFloat( muzzleOrigin[1] ); + msg.WriteFloat( muzzleOrigin[2] ); + msg.WriteFloat( fxOrigin[0] ); + msg.WriteFloat( fxOrigin[1] ); + msg.WriteFloat( fxOrigin[2] ); + } + + float spreadRad = DEG2RAD( spread ); + idVec3 end; + for( i = 0; i < num_hitscans; i++ ) { + if( weaponDef->dict.GetBool( "machinegunSpreadStyle" ) ) { + float r = gameLocal.random.RandomFloat() * idMath::PI * 2.0f; + float u = idMath::Sin( r ) * gameLocal.random.CRandomFloat() * spread * 16; + r = idMath::Cos( r ) * gameLocal.random.CRandomFloat() * spread * 16; +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else if( weaponDef->dict.GetBool( "shotgunSpreadStyle" ) ) { + float r = gameLocal.random.CRandomFloat() * spread * 16; + float u = gameLocal.random.CRandomFloat() * spread * 16; + +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //RAVEN BEGIN + //asalmon: xbox must use the muzzleAxis so the aim can be adjusted for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#endif + //RAVEN END + } + dir.Normalize(); + + gameLocal.HitScan( dict, muzzleOrigin, dir, fxOrigin, owner, false, 1.0f, NULL, areas ); + + if ( gameLocal.isServer ) { + msg.WriteDir( dir, 24 ); + if ( i == num_hitscans - 1 ) { + // NOTE: we emit to the areas of the last hitscan + // there is a remote possibility that multiple hitscans for shotgun would cover more than 2 areas, + // so in some rare case a client might miss it + gameLocal.SendUnreliableMessagePVS( msg, owner, areas[0], areas[1] ); + } + } + } +} + +/* +================ +rvWeapon::AlertMonsters +================ +*/ +void rvWeapon::AlertMonsters( void ) { + trace_t tr; + idEntity *ent; + idVec3 end; + renderLight_t& muzzleFlash = lights[WPLIGHT_MUZZLEFLASH]; + + end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target; + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } + + // jitter the trace to try to catch cases where a trace down the center doesn't hit the monster + end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f ); + end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f ); + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } +} + +/* +================ +rvWeapon::EjectBrass +================ +*/ +void rvWeapon::EjectBrass ( void ) { + if ( g_brassTime.GetFloat() <= 0.0f || !owner->CanShowWeaponViewmodel() ) { + return; + } + + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) { + return; + } + + // Spawn the client side moveable for the brass + rvClientMoveable* cent = NULL; + + gameLocal.SpawnClientEntityDef( brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOwner( GetOwner() ); + cent->SetOrigin ( origin + playerViewAxis * ejectOffset ); + cent->SetAxis ( playerViewAxis ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = GetOwner()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 2.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict.GetVector("linear_velocity").Random( brassDict.GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( GetOwner()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict.GetAngles("angular_velocity").Random( brassDict.GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/* +================ +rvWeapon::BloodSplat +================ +*/ +bool rvWeapon::BloodSplat( float size ) { + float s, c; + idMat3 localAxis, axistemp; + idVec3 localOrigin, normal; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + + if ( wfl.hasBloodSplat ) { + return true; + } + + wfl.hasBloodSplat = true; + + if ( viewModel->modelDefHandle < 0 ) { + return false; + } + + if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) { + return false; + } + + localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f; + localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f; + localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f; + + normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 ); + normal.Normalize(); + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + localAxis[2] = -normal; + localAxis[2].NormalVectors( axistemp[0], axistemp[1] ); + localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + localAxis[0] *= 1.0f / size; + localAxis[1] *= 1.0f / size; + + idPlane localPlane[2]; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" ); + + gameRenderWorld->ProjectOverlay( viewModel->modelDefHandle, localPlane, mtr ); + + return true; +} + +/* +================ +rvWeapon::EnterCinematic +================ +*/ +void rvWeapon::EnterCinematic( void ) { + if( viewModel ) { + viewModel->StopSound( SND_CHANNEL_ANY, false ); + } + ExecuteState( "EnterCinematic" ); + + memset( &wsfl, 0, sizeof(wsfl) ); + + wfl.disabled = true; + + LowerWeapon(); +} + +/* +================ +rvWeapon::ExitCinematic +================ +*/ +void rvWeapon::ExitCinematic( void ) { + wfl.disabled = false; + ExecuteState ( "ExitCinematic" ); + RaiseWeapon(); +} + +/* +================ +rvWeapon::NetCatchup +================ +*/ +void rvWeapon::NetCatchup( void ) { + ExecuteState ( "NetCatchup" ); +} + +/* +=============== +rvWeapon::PlayAnim +=============== +*/ +void rvWeapon::PlayAnim( int channel, const char *animname, int blendFrames ) { + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + int anim; + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::PlayCycle +=============== +*/ +void rvWeapon::PlayCycle( int channel, const char *animname, int blendFrames ) { + int anim; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + anim = worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::AnimDone +=============== +*/ +bool rvWeapon::AnimDone( int channel, int blendFrames ) { + if ( animDoneTime[channel] - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } + return false; +} + +/* +=============== +rvWeapon::StartSound +=============== +*/ +bool rvWeapon::StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + return viewModel->StartSound( soundName, channel, soundShaderFlags, broadcast, length ); +} + +/* +=============== +rvWeapon::StopSound +=============== +*/ +void rvWeapon::StopSound( const s_channelType channel, bool broadcast ) { + if ( viewModel ) { + viewModel->StopSound( channel, broadcast ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } +} + +/* +=============== +rvWeapon::PlayEffect +=============== +*/ +rvClientEffect* rvWeapon::PlayEffect( const char* effectName, jointHandle_t joint, bool loop, const idVec3& endOrigin, bool broadcast ) { + if ( viewModel ) { + return viewModel->PlayEffect( effectName, joint, loop, endOrigin, broadcast ); + } + + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return 0; +} + +/* +================ +rvWeapon::CacheWeapon +================ +*/ +void rvWeapon::CacheWeapon( const char *weaponName ) { + const idDeclEntityDef *weaponDef; + const char *brassDefName; + const char *clipModelName; + idTraceModel trm; + + weaponDef = gameLocal.FindEntityDef( weaponName, false ); + if ( !weaponDef ) { + return; + } + + // precache the brass collision model + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( brassDef ) { + brassDef->dict.GetString( "clipmodel", "", &clipModelName ); + if ( idStr::Icmp( clipModelName, SIMPLE_TRI_NAME ) == 0 ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + if ( !clipModelName[0] ) { + clipModelName = brassDef->dict.GetString( "model" ); // use the visual model + } + // load the trace model + collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ); + } + } + } + + const idKeyValue* kv; + + kv = weaponDef->dict.MatchPrefix( "gui", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + uiManager->FindGui( kv->GetValue().c_str(), true, false, true ); + } + kv = weaponDef->dict.MatchPrefix( "gui", kv ); + } +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeapon ) + STATE ( "Raise", rvWeapon::State_Raise ) + STATE ( "Lower", rvWeapon::State_Lower ) + STATE ( "ExitCinematic", rvWeapon::State_ExitCinematic ) + STATE ( "NetCatchup", rvWeapon::State_NetCatchup ) + STATE ( "EjectBrass", rvWeapon::Frame_EjectBrass ) +END_CLASS_STATES + +/* +================ +rvWeapon::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeapon::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeapon::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeapon::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_NetCatchup ( const stateParms_t& parms ) { + SetState ( "idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_ExitCinematic ( const stateParms_t& parms ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::Frame_EjectBrass +===================== +*/ +stateResult_t rvWeapon::Frame_EjectBrass( const stateParms_t& parms ) { + EjectBrass(); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::GetDebugInfo +===================== +*/ +void rvWeapon::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idClass::GetDebugInfo ( proc, userData ); + proc ( "rvWeapon", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); +} diff --git a/source/game/Weapon.h b/source/game/Weapon.h new file mode 100644 index 0000000..c508b48 --- /dev/null +++ b/source/game/Weapon.h @@ -0,0 +1,492 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 9/30/2004 + +#ifndef __GAME_WEAPON_H__ +#define __GAME_WEAPON_H__ + +/* +=============================================================================== + + Player Weapon + +=============================================================================== +*/ + +typedef enum { + WP_READY, + WP_OUTOFAMMO, + WP_RELOAD, + WP_HOLSTERED, + WP_RISING, + WP_LOWERING, + WP_FLASHLIGHT, +} weaponStatus_t; + +static const int MAX_WEAPONMODS = 4; +static const int MAX_AMMOTYPES = 16; + +class idPlayer; +class idItem; +class idAnimatedEntity; +class idProjectile; +class rvWeapon; + +class rvViewWeapon : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvViewWeapon ); + + rvViewWeapon( void ); + virtual ~rvViewWeapon( void ); + + // Init + void Spawn ( void ); + + // save games + void Save ( idSaveGame *savefile ) const; // archives object for save game file + void Restore ( idRestoreGame *savefile ); // unarchives object from save game file + + + // Weapon definition management + void Clear ( void ); + + // GUIs + void PostGUIEvent ( const char* event ); + + virtual void SetModel ( const char *modelname, int mods = 0 ); + void SetPowerUpSkin ( const char *name ); + void UpdateSkin ( void ); + + // State control/player interface + void Think ( void ); + + // Visual presentation + void PresentWeapon ( bool showViewModel ); + + // Networking + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientPredictionThink ( void ); + virtual bool ClientStale ( void ); + + virtual void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); + virtual void UpdateModelTransform ( void ); + + // Debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + void SetSkin ( const char *skinname ); + void SetSkin ( const idDeclSkin* skin ); + + void SetOverlayShader ( const idMaterial* material ); + + virtual void GetPosition ( idVec3& origin, idMat3& axis ) const; + +private: + + idStrList pendingGUIEvents; + + // effects + const idDeclSkin * saveSkin; + const idDeclSkin * invisSkin; + const idDeclSkin * saveWorldSkin; + const idDeclSkin * worldInvisSkin; + const idDeclSkin * saveHandsSkin; + const idDeclSkin * handsSkin; + + void Event_CallFunction ( const char* function ); + + friend class rvWeapon; + rvWeapon* weapon; +}; + +class rvWeapon : public idClass { +public: + + CLASS_PROTOTYPE( rvWeapon ); + + rvWeapon( void ); + virtual ~rvWeapon( void ); + + enum { + WPLIGHT_MUZZLEFLASH, + WPLIGHT_MUZZLEFLASH_WORLD, + WPLIGHT_FLASHLIGHT, + WPLIGHT_FLASHLIGHT_WORLD, + WPLIGHT_GUI, + WPLIGHT_MAX + }; + + enum { + EVENT_RELOAD = idEntity::EVENT_MAXEVENTS, + EVENT_ENDRELOAD, + EVENT_CHANGESKIN, + EVENT_MAXEVENTS + }; + + void Init ( idPlayer* _owner, const idDeclEntityDef* def, int weaponIndex, bool isStrogg = false ); + + // Virtual overrides + void Spawn ( void ); + virtual void Think ( void ); + virtual void CleanupWeapon ( void ) {} + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientStale ( void ); + virtual void ClientUnstale ( void ) { } + virtual void Attack ( bool altFire, int num_attacks, float spread, float fuseOffset, float power ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + virtual void SpectatorCycle ( void ) { } + virtual bool NoFireWhileSwitching ( void ) const { return false; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void PreSave ( void ); + virtual void PostSave ( void ); + + + // Visual presentation + bool BloodSplat ( float size ); + void MuzzleFlash ( void ); + void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + float GetMuzzleFlashLightParm ( int parm ); + void SetMuzzleFlashLightParm ( int parm, float value ); + void GetAngleOffsets ( int *average, float *scale, float *max ); + void GetTimeOffsets ( float *time, float *scale ); + bool GetGlobalJointTransform ( bool viewModel, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset = vec3_origin ); + + // State control/player interface + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void Raise ( void ); + void PutAway ( void ); + void Hide ( void ); + void Show ( void ); + void HideWorldModel ( void ); + void ShowWorldModel ( void ); + void SetFlashlight ( bool on = true ); + void Flashlight ( void ); + void SetPushVelocity ( const idVec3 &pushVelocity ); + void Reload ( void ); + void OwnerDied ( void ); + void BeginAttack ( void ); + void EndAttack ( void ); + bool IsReady ( void ) const; + bool IsReloading ( void ) const; + bool IsHolstered ( void ) const; + bool ShowCrosshair ( void ) const; + bool CanDrop ( void ) const; + bool CanZoom ( void ) const; + void CancelReload ( void ); + void SetStatus ( weaponStatus_t status ); + bool AutoReload ( void ); + bool IsHidden ( void ) const; + void EjectBrass ( void ); + + // Network helpers + void NetReload ( void ); + void NetEndReload ( void ); + void NetCatchup ( void ); + + // Ammo + static int GetAmmoIndexForName ( const char *ammoname ); + static const char* GetAmmoNameForIndex ( int index ); + int GetAmmoType ( void ) const; + int AmmoAvailable ( void ) const; + int AmmoInClip ( void ) const; + void ResetAmmoClip ( void ); + int ClipSize ( void ) const; + int LowAmmo ( void ) const; + int AmmoRequired ( void ) const; + void AddToClip ( int amount ); + void UseAmmo ( int amount ); + void SetClip ( int amount ); + int TotalAmmoCount ( void ) const; + + // Attack + bool PerformAttack ( idVec3& muzzleOrigin, idMat3& muzzleAxis, float dmgPower ); + void LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ); + void Hitscan ( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ); + void AlertMonsters ( void ); + + // Mods + int GetMods ( void ) const; + + // Zoom + idUserInterface* GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + int GetZoomFov ( void ) const; + + rvViewWeapon* GetViewModel ( void ) const; + idAnimatedEntity* GetWorldModel ( void ) const; + idPlayer* GetOwner ( void ) const; + const char * GetIcon ( void ) const; + renderLight_t& GetLight ( int light ); + const idAngles& GetViewModelAngles ( void ) const; + const idVec3& GetViewModelOffset ( void ) const; + + static void CacheWeapon ( const char *weaponName ); + static void SkipFromSnapshot ( const idBitMsgDelta &msg ); + + void EnterCinematic ( void ); + void ExitCinematic ( void ); + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetState ( const char *statename, int blendFrames ); + void PostState ( const char *statename, int blendFrames ); + void ExecuteState ( const char *statename ); + + void PlayAnim ( int channel, const char *animname, int blendFrames ); + void PlayCycle ( int channel, const char *animname, int blendFrames ); + bool AnimDone ( int channel, int blendFrames ); + bool StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound ( const s_channelType channel, bool broadcast ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false ); + + void FindViewModelPositionStyle ( idVec3& viewOffset, idAngles& viewAngles ) const; + +public: + + void InitLights ( void ); + void InitWorldModel ( void ); + void InitViewModel ( void ); + void InitDefs ( void ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID ); + + void UpdateMuzzleFlash ( void ); + void UpdateFlashlight ( void ); + + void UpdateGUI ( void ); + void UpdateCrosshairGUI ( idUserInterface* gui ) const; + + idMat3 ForeshortenAxis ( const idMat3& axis ) const; + + // Script state management + struct weaponStateFlags_s { + bool attack :1; + bool reload :1; + bool netReload :1; + bool netEndReload :1; + bool raiseWeapon :1; + bool lowerWeapon :1; + bool flashlight :1; + bool zoom :1; + } wsfl; + + // Generic flags + struct weaponFlags_s { + bool attackAltHitscan :1; + bool attackHitscan :1; + bool hide :1; + bool disabled :1; + bool hasBloodSplat :1; + bool silent_fire :1; + bool zoomHideCrosshair :1; + bool flashlightOn :1; + bool hasWindupAnim :1; + } wfl; + + // joints from models + jointHandle_t barrelJointView; + jointHandle_t flashJointView; + jointHandle_t ejectJointView; + jointHandle_t guiLightJointView; + jointHandle_t flashlightJointView; + + jointHandle_t flashJointWorld; + jointHandle_t ejectJointWorld; + jointHandle_t flashlightJointWorld; + + weaponStatus_t status; + int lastAttack; + + // hiding weapon + int hideTime; + float hideDistance; + int hideStartTime; + float hideStart; + float hideEnd; + float hideOffset; + + // Attack + idVec3 pushVelocity; + int kick_endtime; + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + float muzzleOffset; + idEntityPtr projectileEnt; + idVec3 ejectOffset; + + int fireRate; + int altFireRate; + float spread; + int nextAttackTime; + + // we maintain local copies of the projectile and brass dictionaries so they + // do not have to be copied across the DLL boundary when entities are spawned + idDict attackAltDict; + idDict attackDict; + idDict brassDict; + + // Melee + const idDeclEntityDef * meleeDef; + float meleeDistance; + + // zoom + int zoomFov; // variable zoom fov per weapon (-1 is no zoom) + idUserInterface* zoomGui; // whether or not to overlay a zoom scope + float zoomTime; // time it takes to zoom in + + // lights + renderLight_t lights[WPLIGHT_MAX]; + int lightHandles[WPLIGHT_MAX]; + idVec3 guiLightOffset; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashViewOffset; + bool flashlightOn; + idVec3 flashlightViewOffset; + + // ammo management + int ammoType; + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; // if ammo in clip hits this threshold, snd_ + int maxAmmo; + + // multiplayer + int clipPredictTime; + + // these are the player render view parms, which include bobbing + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + + + // View Model + idVec3 viewModelOrigin; + idMat3 viewModelAxis; + idAngles viewModelAngles; + idVec3 viewModelOffset; + + // weighting for viewmodel offsets + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // General + idStr icon; + bool isStrogg; + + bool forceGUIReload; + +public: + + idDict spawnArgs; + +protected: + + idEntityPtr viewModel; + idAnimator* viewAnimator; + idEntityPtr worldModel; + idAnimator* worldAnimator; + const idDeclEntityDef* weaponDef; + idScriptObject* scriptObject; + idPlayer * owner; + int weaponIndex; + int mods; + + float viewModelForeshorten; + + rvStateThread stateThread; + int animDoneTime[ANIM_NumAnimChannels]; + +private: + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_ExitCinematic ( const stateParms_t& parms ); + stateResult_t State_NetCatchup ( const stateParms_t& parms ); + + stateResult_t Frame_EjectBrass ( const stateParms_t& parms ); + + // store weapon index information for death messages + int methodOfDeath; + + // multiplayer hitscans + int hitscanAttackDef; + + CLASS_STATES_PROTOTYPE ( rvWeapon ); +}; + +ID_INLINE rvViewWeapon* rvWeapon::GetViewModel ( void ) const { + return viewModel.GetEntity(); +} + +ID_INLINE idAnimatedEntity* rvWeapon::GetWorldModel ( void ) const { + return worldModel; +} + +ID_INLINE idPlayer* rvWeapon::GetOwner ( void ) const { + return owner; +} + +ID_INLINE const char* rvWeapon::GetIcon ( void ) const { + return icon; +} + +ID_INLINE renderLight_t& rvWeapon::GetLight ( int light ) { + assert ( light < WPLIGHT_MAX ); + return lights[light]; +} + +ID_INLINE const idAngles& rvWeapon::GetViewModelAngles( void ) const { + return viewModelAngles; +} + +ID_INLINE const idVec3& rvWeapon::GetViewModelOffset ( void ) const { + return viewModelOffset; +} + +ID_INLINE int rvWeapon::GetZoomFov ( void ) const { + return zoomFov; +} + +ID_INLINE idUserInterface* rvWeapon::GetZoomGui ( void ) const { + return zoomGui; +} + +ID_INLINE float rvWeapon::GetZoomTime ( void ) const { + return zoomTime; +} + +ID_INLINE int rvWeapon::GetMods ( void ) const { + return mods; +} + +ID_INLINE void rvWeapon::PreSave ( void ) { +} + +ID_INLINE void rvWeapon::PostSave ( void ) { +} + + +#endif /* !__GAME_WEAPON_H__ */ + +// RAVEN END diff --git a/source/game/WorldSpawn.cpp b/source/game/WorldSpawn.cpp new file mode 100644 index 0000000..b50e7dc --- /dev/null +++ b/source/game/WorldSpawn.cpp @@ -0,0 +1,107 @@ +/* +game_worldspawn.cpp + +Worldspawn class. Each map has one worldspawn which handles global spawnargs. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================ +idWorldspawn + +Every map should have exactly one worldspawn. +================ +*/ +CLASS_DECLARATION( idEntity, idWorldspawn ) + EVENT( EV_Remove, idWorldspawn::Event_Remove ) + EVENT( EV_SafeRemove, idWorldspawn::Event_Remove ) +END_CLASS + +/* +================ +idWorldspawn::Spawn +================ +*/ +void idWorldspawn::Spawn( void ) { + idStr scriptname; + idThread *thread; + const function_t *func; + const idKeyValue *kv; + + assert( gameLocal.world == NULL ); + gameLocal.world = this; + + // load script + scriptname = gameLocal.GetMapName(); + scriptname.SetFileExtension( ".script" ); + if ( fileSystem->ReadFile( scriptname, NULL, NULL ) > 0 ) { + gameLocal.program.CompileFile( scriptname ); + + // call the main function by default + func = gameLocal.program.FindFunction( "main" ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // call any functions specified in worldspawn + kv = spawnArgs.MatchPrefix( "call" ); + while( kv != NULL ) { + func = gameLocal.program.FindFunction( kv->GetValue() ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + + thread = new idThread( func ); + thread->DelayedStart( 0 ); + kv = spawnArgs.MatchPrefix( "call", kv ); + } +} + +/* +================= +idWorldspawn::Save +================= +*/ +void idWorldspawn::Save( idRestoreGame *savefile ) { +} + +/* +================= +idWorldspawn::Restore +================= +*/ +void idWorldspawn::Restore( idRestoreGame *savefile ) { + assert( gameLocal.world == this ); + +// RAVEN BEGIN +// bdube: gravity change + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY) ) ); +// RAVEN END +} + +/* +================ +idWorldspawn::~idWorldspawn +================ +*/ +idWorldspawn::~idWorldspawn() { + if ( gameLocal.world == this ) { + gameLocal.world = NULL; + } +} + +/* +================ +idWorldspawn::Event_Remove +================ +*/ +void idWorldspawn::Event_Remove( void ) { + gameLocal.Error( "Tried to remove world" ); +} diff --git a/source/game/WorldSpawn.h b/source/game/WorldSpawn.h new file mode 100644 index 0000000..1415336 --- /dev/null +++ b/source/game/WorldSpawn.h @@ -0,0 +1,28 @@ + +#ifndef __GAME_WORLDSPAWN_H__ +#define __GAME_WORLDSPAWN_H__ + +/* +=============================================================================== + + World entity. + +=============================================================================== +*/ + +class idWorldspawn : public idEntity { +public: + CLASS_PROTOTYPE( idWorldspawn ); + + ~idWorldspawn(); + + void Spawn( void ); + + void Save( idRestoreGame *savefile ); + void Restore( idRestoreGame *savefile ); + +private: + void Event_Remove( void ); +}; + +#endif /* !__GAME_WORLDSPAWN_H__ */ diff --git a/source/game/ai/AAS.cpp b/source/game/ai/AAS.cpp new file mode 100644 index 0000000..f5c08a4 --- /dev/null +++ b/source/game/ai/AAS.cpp @@ -0,0 +1,462 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +/* +============ +idAAS::Alloc +============ +*/ +idAAS *idAAS::Alloc( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + return new idAASLocal; +} + +/* +============ +idAAS::idAAS +============ +*/ +idAAS::~idAAS( void ) { +} + +/* +============ +idAASLocal::idAASLocal +============ +*/ +idAASLocal::idAASLocal( void ) { + file = NULL; +} + +/* +============ +idAASLocal::~idAASLocal +============ +*/ +idAASLocal::~idAASLocal( void ) { + Shutdown(); +} + +/* +============ +idAASLocal::Init +============ +*/ +bool idAASLocal::Init( const idStr &mapName, unsigned int mapFileCRC ) { + if ( file && mapName.Icmp( file->GetName() ) == 0 && mapFileCRC == file->GetCRC() ) { + gameLocal.Printf( "Keeping %s\n", file->GetName() ); + RemoveAllObstacles(); + } + else { + Shutdown(); + + file = AASFileManager->LoadAAS( mapName, mapFileCRC ); + if ( !file ) { + common->DWarning( "Couldn't load AAS file: '%s'", mapName.c_str() ); + return false; + } +// RAVEN BEGIN +// rhummer: Check if this is a dummy file, since it really has no valid data dump it. + else if ( file->IsDummyFile( mapFileCRC ) ) { + AASFileManager->FreeAAS( file ); + file = NULL; + return false; + } +// RAVEN END + SetupRouting(); + } + return true; +} + +/* +============ +idAASLocal::Shutdown +============ +*/ +void idAASLocal::Shutdown( void ) { + if ( file ) { + ShutdownRouting(); + RemoveAllObstacles(); + AASFileManager->FreeAAS( file ); + file = NULL; + } +} + +/* +============ +idAASLocal::Stats +============ +*/ +void idAASLocal::Stats( void ) const { + if ( !file ) { + return; + } + common->Printf( "[%s]\n", file->GetName() ); + file->PrintInfo(); + RoutingStats(); +} + +// RAVEN BEGIN +// jscott: added +/* +============ +idAASLocal::StatsSummary +============ +*/ +size_t idAASLocal::StatsSummary( void ) const { + + int size; + + if( !file ) { + + return( 0 ); + } + + size = ( numAreaTravelTimes * sizeof( unsigned short ) ) + + ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) + + ( portalCacheIndexSize * sizeof( idRoutingCache * ) ); + + return( file->GetMemorySize() + size ); +} +// RAVEN END + +/* +============ +idAASLocal::GetSettings +============ +*/ +const idAASSettings *idAASLocal::GetSettings( void ) const { + if ( !file ) { + return NULL; + } + return &file->GetSettings(); +} + +/* +============ +idAASLocal::PointAreaNum +============ +*/ +int idAASLocal::PointAreaNum( const idVec3 &origin ) const { + if ( !file ) { + return 0; + } + return file->PointAreaNum( origin ); +} + +/* +============ +idAASLocal::PointReachableAreaNum +============ +*/ +int idAASLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->PointReachableAreaNum( origin, searchBounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::BoundsReachableAreaNum +============ +*/ +int idAASLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->BoundsReachableAreaNum( bounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::PushPointIntoAreaNum +============ +*/ +void idAASLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { + if ( !file ) { + return; + } + file->PushPointIntoAreaNum( areaNum, origin ); +} + +/* +============ +idAASLocal::AreaCenter +============ +*/ +idVec3 idAASLocal::AreaCenter( int areaNum ) const { + if ( !file ) { + return vec3_origin; + } + return file->GetArea( areaNum ).center; +} + +// RAVEN BEGIN +// bdube: added +/* +============ +idAASLocal::AreaRadius +============ +*/ +float idAASLocal::AreaRadius( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).bounds.GetRadius(); +} +// mcg: added +/* +============ +idAASLocal::AreaBounds +============ +*/ +idBounds & idAASLocal::AreaBounds( int areaNum ) const { + return file->GetArea( areaNum ).bounds; +} +/* +============ +idAASLocal::AreaCeiling +============ +*/ +float idAASLocal::AreaCeiling( int areaNum ) const { + return file->GetArea( areaNum ).ceiling; +} +// RAVEN END + +/* +============ +idAASLocal::AreaFlags +============ +*/ +int idAASLocal::AreaFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).flags; +} + +/* +============ +idAASLocal::AreaTravelFlags +============ +*/ +int idAASLocal::AreaTravelFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).travelFlags; +} + +/* +============ +idAASLocal::Trace +============ +*/ +bool idAASLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + if ( !file ) { + trace.fraction = 0.0f; + trace.lastAreaNum = 0; + trace.numAreas = 0; + return true; + } + return file->Trace( trace, start, end ); +} + +/* +============ +idAASLocal::GetPlane +============ +*/ +const idPlane &idAASLocal::GetPlane( int planeNum ) const { + if ( !file ) { + static idPlane dummy; + return dummy; + } + return file->GetPlane( planeNum ); +} + +/* +============ +idAASLocal::GetEdgeVertexNumbers +============ +*/ +void idAASLocal::GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const { + if ( !file ) { + verts[0] = verts[1] = 0; + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + verts[0] = v[INTSIGNBITSET(edgeNum)]; + verts[1] = v[INTSIGNBITNOTSET(edgeNum)]; +} + +/* +============ +idAASLocal::GetEdge +============ +*/ +void idAASLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( !file ) { + start.Zero(); + end.Zero(); + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + start = file->GetVertex( v[INTSIGNBITSET(edgeNum)] ); + end = file->GetVertex( v[INTSIGNBITNOTSET(edgeNum)] ); +} + + +/* +=============================================================================== + + idAASCallback + +=============================================================================== +*/ + +/* +============ +idAASCallback::~idAASCallback +============ +*/ +idAASCallback::~idAASCallback ( void ) { +} + +/* +============ +idAASCallback::Test +============ +*/ +idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ) { + // Get AAS file + idAASFile* file = ((idAAS&)*aas).GetFile ( ); + if ( !file ) { + return TEST_BADAREA; + } + + // Get area for edges + aasArea_t& area = file->GetArea ( areaNum ); + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorYellow, area.center, area.center + idVec3(0,0,80.0f), 10000 ); + } + + // Make sure the area itself is valid + if ( !TestArea ( aas, areaNum, area ) ) { + return TEST_BADAREA; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 && point ) { + gameRenderWorld->DebugLine ( colorMagenta, *point, *point + idVec3(0,0,64.0f), 10000 ); + } + + // Test the original origin first + if ( point && TestPointDistance ( origin, *point, minDistance, maxDistance) && TestPoint ( aas, *point ) ) { + goal.areaNum = areaNum; + goal.origin = *point; + return TEST_OK; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorCyan, area.center, area.center + idVec3(0,0,64.0f), 10000 ); + } + + // Test the center of the area + if ( TestPointDistance ( origin, area.center, minDistance, maxDistance) && TestPoint ( aas, area.center, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = area.center; + return TEST_OK; + } + + // For each face test all available edges + int f; + int e; + for ( f = 0; f < area.numFaces; f ++ ) { + aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) ); + + // for each edge test a point between the center of the edge and the center + for ( e = 0; e < face.numEdges; e ++ ) { + idVec3 edgeCenter = file->EdgeCenter ( abs( file->GetEdgeIndex( face.firstEdge + e ) ) ); + idVec3 dir = area.center - edgeCenter; + float dist; + for ( dist = dir.Normalize() - 64.0f; dist > 0.0f; dist -= 64.0f ) { + idVec3 testPoint = edgeCenter + dir * dist; + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorPurple, testPoint, testPoint + idVec3(0,0,64.0f), 10000 ); + } + + if ( TestPointDistance ( origin, testPoint, minDistance, maxDistance) && TestPoint ( aas, testPoint, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = testPoint; + return TEST_OK; + } + } + } + } + + return TEST_BADPOINT; +} + +/* +============ +idAASCallback::Init +============ +*/ +bool idAASCallback::TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ) { + float dist = (origin - point).LengthFast ( ); + if ( minDistance > 0.0f && dist < minDistance ) { + return false; + } + if ( maxDistance > 0.0f && dist > maxDistance ) { + return false; + } + return true; +} + +/* +============ +idAASCallback::Init +============ +*/ +void idAASCallback::Init ( void ) { +} + +/* +============ +idAASCallback::Finish +============ +*/ +void idAASCallback::Finish ( void ) { +} + +/* +============ +idAASCallback::TestArea +============ +*/ +bool idAASCallback::TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ) { + return true; +} + +/* +============ +idAASCallback::TestPoint +============ +*/ +bool idAASCallback::TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow ) { + return true; +} + +// RAVEN END diff --git a/source/game/ai/AAS.h b/source/game/ai/AAS.h new file mode 100644 index 0000000..cc8dbe6 --- /dev/null +++ b/source/game/ai/AAS.h @@ -0,0 +1,163 @@ + +#ifndef __AAS_H__ +#define __AAS_H__ + +/* +=============================================================================== + + Area Awareness System + +=============================================================================== +*/ + +enum { + PATHTYPE_WALK, + PATHTYPE_WALKOFFLEDGE, + PATHTYPE_BARRIERJUMP, + PATHTYPE_JUMP +}; + +typedef struct aasPath_s { + int type; // path type + idVec3 moveGoal; // point the AI should move towards + int moveAreaNum; // number of the area the AI should move towards + idVec3 secondaryGoal; // secondary move goal for complex navigation + const idReachability * reachability; // reachability used for navigation +} aasPath_t; + + +typedef struct aasGoal_s { + int areaNum; // area the goal is in + idVec3 origin; // position of goal +} aasGoal_t; + + +typedef struct aasObstacle_s { + idBounds absBounds; // absolute bounds of obstacle + idBounds expAbsBounds; // expanded absolute bounds of obstacle +} aasObstacle_t; + +class idAASCallback { +public: + virtual ~idAASCallback ( void ); + + enum testResult_t { + TEST_OK, + TEST_BADAREA, + TEST_BADPOINT + }; + + virtual void Init ( void ); + virtual void Finish ( void ); + + testResult_t Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + bool TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ); +}; + +typedef int aasHandle_t; + +class idAAS { +public: + static idAAS * Alloc( void ); + virtual ~idAAS( void ) = 0; + // Initialize for the given map. + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ) = 0; +// RAVEN BEGIN +// jscott: added + // Prints out the memory used by this AAS + virtual size_t StatsSummary( void ) const = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Shutdown( void ) = 0; +#endif +// RAVEN END + // Print AAS stats. + virtual void Stats( void ) const = 0; + // Test from the given origin. + virtual void Test( const idVec3 &origin ) = 0; + // Get the AAS settings. + virtual const idAASSettings *GetSettings( void ) const = 0; + // Returns the number of the area the origin is in. + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + // Returns the number of the nearest reachable area for the given point. + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &bounds, const int areaFlags ) const = 0; + // Returns the number of the first reachable area in or touching the bounds. + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const = 0; + // Push the point into the area. + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const = 0; + // Returns a reachable point inside the given area. + virtual idVec3 AreaCenter( int areaNum ) const = 0; +// RAVEN BEGIN +// bdube: added + // Returns a reachable point inside the given area. + virtual float AreaRadius( int areaNum ) const = 0; + virtual idBounds & AreaBounds( int areaNum ) const = 0; + virtual float AreaCeiling( int areaNum ) const = 0; +// RAVEN END + // Returns the area flags. + virtual int AreaFlags( int areaNum ) const = 0; + // Returns the travel flags for traveling through the area. + virtual int AreaTravelFlags( int areaNum ) const = 0; + // Trace through the areas and report the first collision. + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + // Get a plane for a trace. + virtual const idPlane & GetPlane( int planeNum ) const = 0; + // Get wall edges. + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const = 0; + // Sort the wall edges to create continuous sequences of walls. + virtual void SortWallEdges( int *edges, int numEdges ) const = 0; + // Get the vertex numbers for an edge. + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const = 0; + // Get an edge. + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Find all areas within or touching the bounds with the given contents and disable/enable them for routing. + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) = 0; + // Add an obstacle to the routing system. + virtual aasHandle_t AddObstacle( const idBounds &bounds ) = 0; + // Remove an obstacle from the routing system. + virtual void RemoveObstacle( const aasHandle_t handle ) = 0; + // Remove all obstacles from the routing system. + virtual void RemoveAllObstacles( void ) = 0; + // Returns the travel time towards the goal area in 100th of a second. + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const = 0; + // Get the travel time and first reachability to be used towards the goal, returns true if there is a path. + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const = 0; + // Creates a walk path towards the goal. + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can walk along a straight line from the origin to the goal origin. + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Creates a fly path towards the goal. + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can fly along a straight line from the origin to the goal origin. + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Show the walk path from the origin towards the area. + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Show the fly path from the origin towards the area. + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Find the nearest goal which satisfies the callback. + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const = 0; + +// RAVEN BEGIN +// CDR : Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) = 0; +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ) = 0; + +// rjohnson: added more debug drawing + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const = 0; + virtual bool IsValid( void ) const = 0; +// RAVEN END +}; + +#endif /* !__AAS_H__ */ diff --git a/source/game/ai/AAS_Find.cpp b/source/game/ai/AAS_Find.cpp new file mode 100644 index 0000000..f2de154 --- /dev/null +++ b/source/game/ai/AAS_Find.cpp @@ -0,0 +1,340 @@ +/* +=============================================================================== + +AAS_Find.cpp + +This file has all aas search classes. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + rvAASFindGoalForHide + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForHide::rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::rvAASFindGoalForHide( const idVec3 &hideFromPos ) { + int numPVSAreas; + idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) ); + + // setup PVS + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForHide::~rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::~rvAASFindGoalForHide() { + gameLocal.pvs.FreeCurrentPVS( hidePVS ); +} + +/* +rvAASFindGoalForHide +rvAASFindHide::TestArea +============ +*/ +bool rvAASFindGoalForHide::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ).TranslateSelf(idVec3(0,0,1)), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) { + return true; + } + + return false; +} + +/* +=============================================================================== + + rvAASFindGoalOutOfRange + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange +============ +*/ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange( idAI* _owner ) { + owner = _owner; +} + +/* +============ +rvAASFindAreaOutOfRange::TestArea +============ +*/ +bool rvAASFindGoalOutOfRange::TestPoint ( idAAS* aas, const idVec3& pos, const float zAllow ) { + return aiManager.ValidateDestination ( owner, pos ); +} + +/* +=============================================================================== + +rvAASFindGoalForAttack + +Find a position to move to that allows the ai to at their target + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForAttack::rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::rvAASFindGoalForAttack( idAI* _owner ) { + owner = _owner; + cachedIndex = 0; +} + +/* +============ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack ( void ) { +} + +/* +============ +rvAASFindGoalForAttack::Init +============ +*/ +void rvAASFindGoalForAttack::Init ( void ) { + // setup PVS + int numPVSAreas; + numPVSAreas = gameLocal.pvs.GetPVSAreas( owner->enemy.ent->GetPhysics()->GetAbsBounds(), PVSAreas, idEntity::MAX_PVS_AREAS ); + targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); + + cachedGoals.SetGranularity ( 1024 ); +} + +/* +============ +rvAASFindGoalForAttack::Finish +============ +*/ +void rvAASFindGoalForAttack::Finish ( void ) { + gameLocal.pvs.FreeCurrentPVS( targetPVS ); +} + +/* +============ +rvAASFindGoalForAttack::TestArea +============ +*/ +bool rvAASFindGoalForAttack::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + cachedAreaNum = areaNum; + + // If the whole area is out of range then skip it + float range; + range = area.bounds.ShortestDistance ( owner->enemy.lastKnownPosition ); + if ( range > owner->combat.attackRange[1] ) { + return false; + } + + // Out of pvs? + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + return gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForAttack::TestPoint +============ +*/ +bool rvAASFindGoalForAttack::TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow ) { + float dist; + + idVec3 localPoint = point; + float bestZ = owner->enemy.ent->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + + // Out of attack range? + dist = (localPoint - owner->enemy.ent->GetPhysics()->GetOrigin ( )).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // If tethered make sure the point is within the tether range + if ( owner->tether ) { + idVec3 localPoint = point; + float bestZ = owner->tether.GetEntity()->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !owner->tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + } + + aasGoal_t& goal = cachedGoals.Alloc ( ); + goal.areaNum = cachedAreaNum; + goal.origin = localPoint; + + return false; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedGoal +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoal ( int index ) { + const aasGoal_t& goal = cachedGoals[index]; + + // Out of attack range? + float dist = (goal.origin - owner->enemy.ent->GetPhysics()->GetOrigin ( ) ).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // Someone already there? + if ( !aiManager.ValidateDestination ( owner, goal.origin, true ) ) { + return false; + } + + // Can we see the enemy from this position? + if ( !owner->CanSeeFrom ( goal.origin - owner->GetPhysics()->GetGravityNormal ( ) * owner->combat.visStandHeight, owner->GetEnemy(), false ) ) { + return false; + } + + return true; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedPoints +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoals ( int count, aasGoal_t& goal ) { + int i; + + goal.areaNum = 0; + + // Test as many points as we are allowed to test + for ( i = 0; i < count && cachedIndex < cachedGoals.Num(); cachedIndex ++, i ++ ) { + // Retest simple checks + if ( TestCachedGoal( cachedIndex ) ) { + goal = cachedGoals[cachedIndex]; + return true; + } + } + + return !(cachedIndex >= cachedGoals.Num()); +} + +/* +=============================================================================== + +rvAASFindGoalForTether + +Find a goal to move to that is within the given tether. + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::rvAASFindGoalForTether( idAI* _owner, rvAITether* _tether ) { + owner = _owner; + tether = _tether; +} + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::~rvAASFindGoalForTether( void ) { +} + +/* +============ +rvAASFindGoalForTether::TestArea +============ +*/ +bool rvAASFindGoalForTether::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + // Test super class first + if ( !idAASCallback::TestArea ( aas, areaNum, area ) ) { + return false; + } + + // Make sure the area bounds is remotely valid for the tether + idBounds tempBounds = area.bounds; + tempBounds[1].z = area.ceiling; + if ( !tether->ValidateBounds ( tempBounds ) ) { + return false; + } + return true; +} + +/* +============ +rvAASFindGoalForTether::TestPoint +============ +*/ +bool rvAASFindGoalForTether::TestPoint ( class idAAS* aas, const idVec3& point, const float zAllow ) { + if ( !tether ) { + return false; + } + + idVec3 localPoint = point; + float bestZ = tether->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + + if ( !aiManager.ValidateDestination ( owner, localPoint ) ) { + return false; + } + + return true; +} diff --git a/source/game/ai/AAS_Find.h b/source/game/ai/AAS_Find.h new file mode 100644 index 0000000..20e6b4f --- /dev/null +++ b/source/game/ai/AAS_Find.h @@ -0,0 +1,112 @@ +/* +================ + +AAS_Find.h + +================ +*/ + +#ifndef __AAS_FIND__ +#define __AAS_FIND__ + +class idAI; +class rvAIHelper; + +/* +=============================================================================== + rvAASFindHide +=============================================================================== +*/ + +class rvAASFindGoalForHide : public idAASCallback { +public: + rvAASFindGoalForHide ( const idVec3 &hideFromPos ); + ~rvAASFindGoalForHide ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + +private: + + pvsHandle_t hidePVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +/* +=============================================================================== + rvAASFindAreaOutOfRange +=============================================================================== +*/ + +class rvAASFindGoalOutOfRange : public idAASCallback { +public: + + rvAASFindGoalOutOfRange ( idAI* _owner ); + +protected: + + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + +private: + + idAI* owner; +}; + +/* +=============================================================================== + rvAASFindAttackPosition +=============================================================================== +*/ + +class rvAASFindGoalForAttack : public idAASCallback { +public: + rvAASFindGoalForAttack ( idAI *self ); + ~rvAASFindGoalForAttack ( void ); + + + bool TestCachedGoals ( int count, aasGoal_t& goal ); + + virtual void Init ( void ); + virtual void Finish ( void ); + +private: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + + bool TestCachedGoal ( int index ); + + idAI* owner; + + pvsHandle_t targetPVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + idList cachedGoals; + int cachedIndex; + int cachedAreaNum; +}; + +/* +=============================================================================== + rvAASFindGoalForTether +=============================================================================== +*/ + +class rvAASFindGoalForTether : public idAASCallback { +public: + rvAASFindGoalForTether ( idAI* owner, rvAITether* helper ); + ~rvAASFindGoalForTether ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS* aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + idAI* owner; + rvAITether* tether; +}; + +#endif // __AAS_FIND__ diff --git a/source/game/ai/AAS_debug.cpp b/source/game/ai/AAS_debug.cpp new file mode 100644 index 0000000..f35a5d0 --- /dev/null +++ b/source/game/ai/AAS_debug.cpp @@ -0,0 +1,909 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for cvars and debug drawing +#include "AI.h" +#include "AAS_Find.h" + + +/* +============ +idAASLocal::DrawCone +============ +*/ +void idAASLocal::DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const { + int i; + idMat3 axis; + idVec3 center, top, p, lastp; + + axis[2] = dir; + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + center = origin + dir; + top = center + dir * (3.0f * radius); + lastp = center + radius * axis[1]; + + for ( i = 20; i <= 360; i += 20 ) { + p = center + idMath::Sin( DEG2RAD(i) ) * radius * axis[0] + idMath::Cos( DEG2RAD(i) ) * radius * axis[1]; + gameRenderWorld->DebugLine( color, lastp, p, 0 ); + gameRenderWorld->DebugLine( color, p, top, 0 ); + lastp = p; + } +} + +/* +============ +idAASLocal::DrawReachability +============ +*/ +void idAASLocal::DrawReachability( const idReachability *reach ) const { + gameRenderWorld->DebugArrow( colorCyan, reach->start, reach->end, 2 ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", reach->edgeNum ), ( reach->start + reach->end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis ); + } + + switch( reach->travelType ) { + case TFL_WALK: { +// const idReachability_Walk *walk = static_cast(reach); + break; + } + default: { + break; + } + } +} + +/* +============ +idAASLocal::DrawEdge +============ +*/ +void idAASLocal::DrawEdge( int edgeNum, bool arrow ) const { + const aasEdge_t *edge; + idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &colorRed; + if ( arrow ) { + gameRenderWorld->DebugArrow( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ), 1 ); + } else { + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); + } + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", edgeNum ), ( file->GetVertex( edge->vertexNum[0] ) + file->GetVertex( edge->vertexNum[1] ) ) * 0.5f + idVec3(0,0,4), 0.1f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } +} + +/* +============ +idAASLocal::DrawFace +============ +*/ +void idAASLocal::DrawFace( int faceNum, bool side ) const { + int i, j, numEdges, firstEdge; + const aasFace_t *face; + idVec3 mid, end; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + if ( !numEdges ) + {//wtf? A face with no edges?! + return; + } + + mid = vec3_origin; + for ( i = 0; i < numEdges; i++ ) { + DrawEdge( abs( file->GetEdgeIndex( firstEdge + i ) ), ( face->flags & FACE_FLOOR ) != 0 ); + j = file->GetEdgeIndex( firstEdge + i ); + mid += file->GetVertex( file->GetEdge( abs( j ) ).vertexNum[ j < 0 ] ); + } + + mid /= numEdges; + if ( side ) { + end = mid - 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } else { + end = mid + 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } + gameRenderWorld->DebugArrow( colorGreen, mid, end, 1 ); +} + +/* +============ +idAASLocal::DrawAreaBounds +============ +*/ +void idAASLocal::DrawAreaBounds( int areaNum ) const { + const aasArea_t *area; + if ( !file ) { + return; + } + area = &file->GetArea( areaNum ); + + idVec3 points[8]; + bool drawn[8][8]; + memset( drawn, false, sizeof( drawn ) ); + + area->bounds.ToPoints( points ); + for ( int p1 = 0; p1 < 8; p1++ ) { + for ( int p2 = 0; p2 < 8; p2++ ) { + if ( !drawn[p2][p1] ) { + if ( (points[p1].x == points[p2].x && (points[p1].y == points[p2].y||points[p1].z == points[p2].z)) + || (points[p1].y == points[p2].y && (points[p1].x == points[p2].x||points[p1].z == points[p2].z)) + || (points[p1].z == points[p2].z && (points[p1].x == points[p2].x||points[p1].y == points[p2].y)) ) { + //an edge + gameRenderWorld->DebugLine( colorRed, points[p1], points[p2] ); + drawn[p1][p2] = true; + } + } + } + } +} + +/* +============ +idAASLocal::DrawArea +============ +*/ +void idAASLocal::DrawArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idReachability *reach; + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawFace( abs( file->GetFaceIndex( firstFace + i ) ), file->GetFaceIndex( firstFace + i ) < 0 ); + } + + if (aas_showRevReach.GetInteger()) + { + for ( reach = area->rev_reach; reach; reach = reach->rev_next ) { + DrawReachability( reach ); + } + } + else + { + for ( reach = area->reach; reach; reach = reach->next ) { + DrawReachability( reach ); + } + } +} + +/* +============ +idAASLocal::DefaultSearchBounds +============ +*/ +const idBounds &idAASLocal::DefaultSearchBounds( void ) const { + return file->GetSettings().boundingBoxes[0]; +} + +/* +============ +idAASLocal::ShowArea +============ +*/ +void idAASLocal::ShowArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + const aasArea_t *area; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + org = origin; + PushPointIntoAreaNum( areaNum, org ); + + if ( aas_goalArea.GetInteger() ) { + int travelTime; + idReachability *reach; + + RouteToGoalArea( areaNum, org, aas_goalArea.GetInteger(), TFL_WALK|TFL_AIR, travelTime, &reach ); + gameLocal.Printf( "\rtt = %4d", travelTime ); + if ( reach ) { + gameLocal.Printf( " to area %4d", reach->toAreaNum ); + DrawArea( reach->toAreaNum ); + } + } + + if ( areaNum != lastAreaNum ) { + area = &file->GetArea( areaNum ); + gameLocal.Printf( "area %d: ", areaNum ); + if ( area->flags & AREA_LEDGE ) { + gameLocal.Printf( "AREA_LEDGE " ); + } + if ( area->flags & AREA_REACHABLE_WALK ) { + gameLocal.Printf( "AREA_REACHABLE_WALK " ); + } + if ( area->flags & AREA_REACHABLE_FLY ) { + gameLocal.Printf( "AREA_REACHABLE_FLY " ); + } + if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) { + gameLocal.Printf( "AREACONTENTS_CLUSTERPORTAL " ); + } + if ( area->contents & AREACONTENTS_OBSTACLE ) { + gameLocal.Printf( "AREACONTENTS_OBSTACLE " ); + } + gameLocal.Printf( "\n" ); + lastAreaNum = areaNum; + } + + if ( org != origin ) { + idBounds bnds = file->GetSettings().boundingBoxes[ 0 ]; + bnds[ 1 ].z = bnds[ 0 ].z; + gameRenderWorld->DebugBounds( colorYellow, bnds, org ); + } + + DrawArea( areaNum ); +} + +// RAVEN BEGIN +// rjohnson: added more debug drawing +/* +============ +idAASLocal::DrawSimpleEdge +============ +*/ +void idAASLocal::DrawSimpleEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &file->GetSettings().debugColor; + + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); +} + +/* +============ +idAASLocal::DrawSimpleFace +============ +*/ +const int MAX_AAS_WALL_EDGES = 256; + +// RAVEN BEGIN +// cdr: added visited check +void idAASLocal::DrawSimpleFace( int faceNum, bool visited ) const { +// RAVEN END + int i, numEdges, firstEdge, edgeNum; + const aasFace_t *face; + idVec3 sides[MAX_AAS_WALL_EDGES]; + const aasEdge_t *edge, *nextEdge; + idVec4 color2; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + DrawSimpleEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } + + if ( numEdges >= 2 && numEdges <= MAX_AAS_WALL_EDGES ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + edgeNum = abs( file->GetEdgeIndex( firstEdge + 1 ) ); + nextEdge = &file->GetEdge( abs( edgeNum ) ); + + // need to find the first common edge so that we go form the polygon in the right direction + if ( file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[0] ) || + file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[1] ) ) { + sides[ 0 ] = file->GetVertex( edge->vertexNum[0] ); + } else { + sides[ 0 ] = file->GetVertex( edge->vertexNum[1] ); + } + + for ( i = 1; i < numEdges; i++ ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge + i ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + + if ( sides[ i-1 ] == file->GetVertex( edge->vertexNum[0] ) ) { + sides[ i ] = file->GetVertex( edge->vertexNum[1] ); + } else { + sides[ i ] = file->GetVertex( edge->vertexNum[0] ); + } + } + + color2 = file->GetSettings().debugColor; + color2[3] = 0.20f; + + // RAVEN BEGIN + // cdr: added visited check + if (!visited) + { + color2[3] = 0.05f; + } + // RAVEN END + idWinding winding( sides, numEdges ); + gameRenderWorld->DebugPolygon( color2, winding, 0, true ); + } +} + + +/* +============ +idAASLocal::DrawSimpleArea +============ +*/ +void idAASLocal::DrawSimpleArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawSimpleFace( abs( file->GetFaceIndex( firstFace + i )), true ); + } +} + +/* +============ +idAASLocal::IsValid +============ +*/ +bool idAASLocal::IsValid( void ) const { + if ( !file ) { + return false; + } + + return true; +} + +/* +============ +idAASLocal::ShowAreas +============ +*/ +void idAASLocal::ShowAreas( const idVec3 &origin, bool ShowProblemAreas ) const { + int i, areaNum; + idPlayer *player; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + idReachability *reach; + int travelFlags = (TFL_WALK); + const idBounds bounds = idBounds( origin ).Expand( 256.0f ); + + if ( !file ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = 0; + queueEnd = 1; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + area = &file->GetArea( curArea ); + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + + if ( ShowProblemAreas ) { + for ( i = 0; i < queueEnd; i++ ) { + ShowProblemArea( areaQueue[ i ] ); + } + } else { + for ( i = 0; i < queueEnd; i++ ) { + DrawSimpleArea( areaQueue[ i ] ); + } + } +} + +/* +============ +idAASLocal::ShowProblemEdge +============ +*/ +void idAASLocal::ShowProblemEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + idVec4 color2; + idTraceModel trm; + idClipModel mdl; + idVec3 sides[4]; + trace_t results; + idVec3 forward, forwardNormal, left, down, start, end; + float hullSize, hullHeight; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + + start = (idVec3)file->GetVertex( edge->vertexNum[0] ); + end = (idVec3)file->GetVertex( edge->vertexNum[1] ); + forward = end - start ; + forward.Normalize(); + forwardNormal = forward; + forward.NormalVectors( left, down ); + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ) - 1.0f; // assumption that x and y are the same size + hullHeight = ( file->GetSettings().boundingBoxes[0][1][2] - file->GetSettings().boundingBoxes[0][0][2] ); + + left *= hullSize; + forward *= hullSize; + + sides[0] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[1] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[2] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + sides[3] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + trm.SetupPolygon( sides, 4 ); + mdl.LoadModel( trm, NULL ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( gameLocal.GetLocalPlayer(), results, start, end, &mdl, mat3_identity, MASK_MONSTERSOLID, gameLocal.GetLocalPlayer() ); +// RAVEN END + + if ( results.fraction != 1.0f ) { + color = &file->GetSettings().debugColor; + gameRenderWorld->DebugLine( *color, start - left, end - left ); + gameRenderWorld->DebugLine( *color, start + left, end + left ); + + color = &colorYellow; + gameRenderWorld->DebugLine( *color, start, end ); + + color2 = colorOrange; + color2[3] = 0.25f; + sides[0] += results.endpos; + sides[1] += results.endpos; + sides[2] += results.endpos; + sides[3] += results.endpos; + idWinding winding( sides, 4 ); + gameRenderWorld->DebugPolygon( color2, winding ); + } +} + +/* +============ +idAASLocal::ShowProblemFace +============ +*/ +void idAASLocal::ShowProblemFace( int faceNum ) const { + int i, numEdges, firstEdge; + const aasFace_t *face; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + ShowProblemEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + float hullSize; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + numFaces = area->numFaces; + firstFace = area->firstFace; + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ); // assumption that x and y are the same size + + bounds = area->bounds; + bounds.Expand( hullSize ); +// RAVEN BEGIN +// ddynerman: multiple clip world + numListedEntities = gameLocal.EntitiesTouchingBounds( gameLocal.GetLocalPlayer(), bounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entityList[ i ]->IsType( idMoveable::GetClassType() ) || + entityList[ i ]->IsType( idMover::GetClassType() ) || + entityList[ i ]->IsType( idAI::GetClassType() ) ) { +// RAVEN END + entityList[ i ]->GetPhysics()->DisableClip(); + continue; + } + + entityList[ i ] = NULL; + } + + for ( i = 0; i < numFaces; i++ ) { + ShowProblemFace( abs( file->GetFaceIndex( firstFace + i ) ) ); + } + + for( i = 0; i < numListedEntities; i++) { + if ( entityList[ i ] ) { + entityList[ i ]->GetPhysics()->EnableClip(); + } + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + ShowProblemArea( areaNum ); +} + +// RAVEN END + +/* +============ +idAASLocal::ShowWalkPath +============ +*/ +void idAASLocal::ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_WALK ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorGreen, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( WalkPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowFlyPath +============ +*/ +void idAASLocal::ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_FLY ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_FLY|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorPurple, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( FlyPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_FLY|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowWallEdges +============ +*/ +void idAASLocal::ShowWallEdges( const idVec3 &origin ) const { + int i, areaNum, numEdges, edges[1024]; + idVec3 start, end; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + numEdges = GetWallEdges( areaNum, idBounds( origin ).Expand( 256.0f ), TFL_WALK, edges, 1024 ); + for ( i = 0; i < numEdges; i++ ) { + GetEdge( edges[i], start, end ); + gameRenderWorld->DebugLine( colorRed, start, end ); + gameRenderWorld->DrawText( va( "%d", edges[i] ), ( start + end ) * 0.5f, 0.1f, colorWhite, player->viewAxis ); + } +} + +/* +============ +idAASLocal::ShowHideArea +============ +*/ +void idAASLocal::ShowHideArea( const idVec3 &origin, int targetAreaNum ) const { + int areaNum, numObstacles; + idVec3 target; + aasGoal_t goal; + aasObstacle_t obstacles[10]; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + target = AreaCenter( targetAreaNum ); + + // consider the target an obstacle + obstacles[0].absBounds = idBounds( target ).Expand( 16 ); + numObstacles = 1; + + DrawCone( target, idVec3(0,0,1), 16.0f, colorYellow ); + + rvAASFindGoalForHide findHide ( target ); + if ( FindNearestGoal( goal, areaNum, origin, target, TFL_WALK|TFL_AIR, 0.0f, 0.0f, obstacles, numObstacles, findHide ) ) { + DrawArea( goal.areaNum ); + ShowWalkPath( origin, goal.areaNum, goal.origin ); + DrawCone( goal.origin, idVec3(0,0,1), 16.0f, colorWhite ); + } +} + +/* +============ +idAASLocal::PullPlayer +============ +*/ +bool idAASLocal::PullPlayer( const idVec3 &origin, int toAreaNum ) const { + int areaNum; + idVec3 areaCenter, dir, vel; + idAngles delta; + aasPath_t path; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return true; + } + + idPhysics *physics = player->GetPhysics(); + if ( !physics ) { + return true; + } + + if ( !toAreaNum ) { + return false; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + areaCenter = AreaCenter( toAreaNum ); + if ( player->GetPhysics()->GetAbsBounds().Expand( 8 ).ContainsPoint( areaCenter ) ) { + return false; + } + if ( WalkPathToGoal( path, areaNum, origin, toAreaNum, areaCenter, TFL_WALK|TFL_AIR ) ) { + dir = path.moveGoal - origin; + dir[2] *= 0.5f; + dir.Normalize(); + delta = dir.ToAngles() - player->cmdAngles - player->GetDeltaViewAngles(); + delta.Normalize180(); + player->SetDeltaViewAngles( player->GetDeltaViewAngles() + delta * 0.1f ); + dir[2] = 0.0f; + dir.Normalize(); + dir *= 100.0f; + vel = physics->GetLinearVelocity(); + dir[2] = vel[2]; + physics->SetLinearVelocity( dir ); + return true; + } + else { + return false; + } +} + +/* +============ +idAASLocal::RandomPullPlayer +============ +*/ +void idAASLocal::RandomPullPlayer( const idVec3 &origin ) const { + int rnd, i, n; + + if ( !PullPlayer( origin, aas_pullPlayer.GetInteger() ) ) { + + rnd = gameLocal.random.RandomFloat() * file->GetNumAreas(); + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + n = (rnd + i) % file->GetNumAreas(); + if ( file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) { + aas_pullPlayer.SetInteger( n ); + } + } + } else { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + } +} + +/* +============ +idAASLocal::ShowPushIntoArea +============ +*/ +void idAASLocal::ShowPushIntoArea( const idVec3 &origin ) const { + int areaNum; + idVec3 target; + + target = origin; + areaNum = PointReachableAreaNum( target, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + PushPointIntoAreaNum( areaNum, target ); + gameRenderWorld->DebugArrow( colorGreen, origin, target, 1 ); +} + +/* +============ +idAASLocal::Test +============ +*/ +void idAASLocal::Test( const idVec3 &origin ) { + + if ( !file ) { + return; + } + + if ( aas_randomPullPlayer.GetBool() ) { + RandomPullPlayer( origin ); + } + if ( ( aas_pullPlayer.GetInteger() > 0 ) && ( aas_pullPlayer.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + PullPlayer( origin, aas_pullPlayer.GetInteger() ); + } + if ( ( aas_showPath.GetInteger() > 0 ) && ( aas_showPath.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_showPath.GetInteger(), AreaCenter( aas_showPath.GetInteger() ) ); + } + if ( ( aas_showFlyPath.GetInteger() > 0 ) && ( aas_showFlyPath.GetInteger() < file->GetNumAreas() ) ) { + ShowFlyPath( origin, aas_showFlyPath.GetInteger(), AreaCenter( aas_showFlyPath.GetInteger() ) ); + } + if ( ( aas_showHideArea.GetInteger() > 0 ) && ( aas_showHideArea.GetInteger() < file->GetNumAreas() ) ) { + ShowHideArea( origin, aas_showHideArea.GetInteger() ); + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 1 ) { + ShowArea( origin ); + } + else if ( aas_showAreas.GetInteger() == 2 ) { + ShowAreas( origin ); + } + if ( aas_showProblemAreas.GetInteger() == 1 ) { + ShowProblemArea( origin ); + } + else if ( aas_showProblemAreas.GetInteger() == 2 ) { + ShowAreas( origin, true ); + } +// RAVEN END + if ( aas_showWallEdges.GetBool() ) { + ShowWallEdges( origin ); + } + if ( aas_showPushIntoArea.GetBool() ) { + ShowPushIntoArea( origin ); + } +} diff --git a/source/game/ai/AAS_local.h b/source/game/ai/AAS_local.h new file mode 100644 index 0000000..35a7f73 --- /dev/null +++ b/source/game/ai/AAS_local.h @@ -0,0 +1,193 @@ + +#ifndef __AAS_LOCAL_H__ +#define __AAS_LOCAL_H__ + +#include "AAS.h" +#include "../Pvs.h" + + +class idRoutingCache { + friend class idAASLocal; + +public: + idRoutingCache( int size ); + ~idRoutingCache( void ); + + int Size( void ) const; + +private: + int type; // portal or area cache + int size; // size of cache + int cluster; // cluster of the cache + int areaNum; // area of the cache + int travelFlags; // combinations of the travel flags + idRoutingCache * next; // next in list + idRoutingCache * prev; // previous in list + idRoutingCache * time_next; // next in time based list + idRoutingCache * time_prev; // previous in time based list + unsigned short startTravelTime; // travel time to start with + unsigned char * reachabilities; // reachabilities used for routing + unsigned short * travelTimes; // travel time for every area +}; + + +class idRoutingUpdate { + friend class idAASLocal; + +private: + int cluster; // cluster number of this update + int areaNum; // area number of this update + unsigned short tmpTravelTime; // temporary travel time + unsigned short * areaTravelTimes; // travel times within the area + idVec3 start; // start point into area + idRoutingUpdate * next; // next in list + idRoutingUpdate * prev; // prev in list + bool isInList; // true if the update is in the list +}; + + +class idRoutingObstacle { + friend class idAASLocal; + idRoutingObstacle( void ) { } + +private: + idBounds bounds; // obstacle bounds + idList areas; // areas the bounds are in +}; + +class idAASLocal : public idAAS { +public: + idAASLocal( void ); + virtual ~idAASLocal( void ); + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ); + virtual void Shutdown( void ); +// RAVEN BEGIN +// jscott: added summary flag + virtual size_t StatsSummary( void ) const; +// RAVEN END + virtual void Stats( void ) const; + virtual void Test( const idVec3 &origin ); + virtual const idAASSettings *GetSettings( void ) const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; + virtual idVec3 AreaCenter( int areaNum ) const; +// RAVEN BEGIN +// bdube: added + virtual float AreaRadius( int areaNum ) const; + virtual idBounds & AreaBounds( int areaNum ) const; + virtual float AreaCeiling( int areaNum ) const; +// RAVEN END + virtual int AreaFlags( int areaNum ) const; + virtual int AreaTravelFlags( int areaNum ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual const idPlane & GetPlane( int planeNum ) const; + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const; + virtual void SortWallEdges( int *edges, int numEdges ) const; + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const; + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); + virtual aasHandle_t AddObstacle( const idBounds &bounds ); + virtual void RemoveObstacle( const aasHandle_t handle ); + virtual void RemoveAllObstacles( void ); + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const; + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const; +// RAVEN BEGIN +// creed: Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) {return file;} +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ); +// rjohnson: added more debug drawing + virtual bool IsValid( void ) const; + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const; +// RAVEN END + + +private: + idAASFile * file; + idStr name; + +private: // routing data + idRoutingCache *** areaCacheIndex; // for each area in each cluster the travel times to all other areas in the cluster + int areaCacheIndexSize; // number of area cache entries + idRoutingCache ** portalCacheIndex; // for each area in the world the travel times from each portal + int portalCacheIndexSize; // number of portal cache entries + idRoutingUpdate * areaUpdate; // memory used to update the area routing cache + idRoutingUpdate * portalUpdate; // memory used to update the portal routing cache + unsigned short * goalAreaTravelTimes; // travel times to goal areas + unsigned short * areaTravelTimes; // travel times through the areas + int numAreaTravelTimes; // number of area travel times + mutable idRoutingCache * cacheListStart; // start of list with cache sorted from oldest to newest + mutable idRoutingCache * cacheListEnd; // end of list with cache sorted from oldest to newest + mutable int totalCacheMemory; // total cache memory used + idList obstacleList; // list with obstacles + +private: // routing + bool SetupRouting( void ); + void ShutdownRouting( void ); + unsigned short AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const; + void CalculateAreaTravelTimes( void ); + void DeleteAreaTravelTimes( void ); + void SetupRoutingCache( void ); + void DeleteClusterCache( int clusterNum ); + void DeletePortalCache( void ); + void ShutdownRoutingCache( void ); + void RoutingStats( void ) const; + void LinkCache( idRoutingCache *cache ) const; + void UnlinkCache( idRoutingCache *cache ) const; + void DeleteOldestCache( void ) const; + idReachability * GetAreaReachability( int areaNum, int reachabilityNum ) const; + int ClusterAreaNum( int clusterNum, int areaNum ) const; + void UpdateAreaRoutingCache( idRoutingCache *areaCache ) const; + idRoutingCache * GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void UpdatePortalRoutingCache( idRoutingCache *portalCache ) const; + idRoutingCache * GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void RemoveRoutingCacheUsingArea( int areaNum ); + void DisableArea( int areaNum ); + void EnableArea( int areaNum ); + bool SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ); + void GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const; + void SetObstacleState( const idRoutingObstacle *obstacle, bool enable ); + +private: // pathing + bool EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const; + bool FloorEdgeSplitPoint( idVec3 &split, int areaNum, const idPlane &splitPlane, const idPlane &frontPlane, bool closest ) const; + idVec3 SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + idVec3 SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + +private: // debug + const idBounds & DefaultSearchBounds( void ) const; + void DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const; + void DrawAreaBounds( int areaNum ) const; + void DrawArea( int areaNum ) const; + void DrawFace( int faceNum, bool side ) const; + void DrawEdge( int edgeNum, bool arrow ) const; + void DrawReachability( const idReachability *reach ) const; + void ShowArea( const idVec3 &origin ) const; + void ShowWallEdges( const idVec3 &origin ) const; + void ShowHideArea( const idVec3 &origin, int targerAreaNum ) const; + bool PullPlayer( const idVec3 &origin, int toAreaNum ) const; + void RandomPullPlayer( const idVec3 &origin ) const; + void ShowPushIntoArea( const idVec3 &origin ) const; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + void DrawSimpleEdge( int edgeNum ) const; + void DrawSimpleFace( int faceNum, bool visited ) const; + void DrawSimpleArea( int areaNum ) const; + void ShowProblemEdge( int edgeNum ) const; + void ShowProblemFace( int faceNum ) const; + void ShowProblemArea( int areaNum ) const; + void ShowProblemArea( const idVec3 &origin ) const; +// RAVEN END +}; + +#endif /* !__AAS_LOCAL_H__ */ diff --git a/source/game/ai/AAS_pathing.cpp b/source/game/ai/AAS_pathing.cpp new file mode 100644 index 0000000..fc61ada --- /dev/null +++ b/source/game/ai/AAS_pathing.cpp @@ -0,0 +1,734 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +#define SUBSAMPLE_WALK_PATH 1 +#define SUBSAMPLE_FLY_PATH 0 + +const int maxWalkPathIterations = 10; +const float maxWalkPathDistance = 500.0f; +const float walkPathSampleDistance = 8.0f; + +const int maxFlyPathIterations = 10; +const float maxFlyPathDistance = 500.0f; +const float flyPathSampleDistance = 8.0f; + + +/* +============ +idAASLocal::EdgeSplitPoint + + calculates split point of the edge with the plane + returns true if the split point is between the edge vertices +============ +*/ +bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const { + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + edge = &file->GetEdge( edgeNum ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * plane.Normal() - plane.Dist(); + d2 = v2 * plane.Normal() - plane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + return false; + } + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + return true; +} + +// RAVEN BEGIN +// rjohnson: optimized function +/* +============ +idAASLocal::FloorEdgeSplitPoint + + calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane + the point has to be on the front side of the frontPlane to be valid +============ +*/ +bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const { + int i, j, faceNum, edgeNum; + const aasArea_t *area; + const aasFace_t *face; + idVec3 split; + float dist, bestDist; + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + area = &file->GetArea( areaNum ); + if ( closest ) { + bestDist = maxWalkPathDistance; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist >= -0.1f && dist < bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist < maxWalkPathDistance ); + + } else { + bestDist = -0.1f; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist > bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist > -0.1f ); + } +} +// RAVEN END + +/* +============ +idAASLocal::WalkPathValid + + returns true if one can walk in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex; + idPlane pathPlane, frontPlane, farPlane; + idReachability *reach; + const aasArea_t *area; + idVec3 p, dir; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) ); + pathPlane.Normalize(); + pathPlane.FitThroughPoint( origin ); + + frontPlane.SetNormal( goalOrigin - origin ); + frontPlane.Normalize(); + frontPlane.FitThroughPoint( origin ); + + farPlane.SetNormal( frontPlane.Normal() ); + farPlane.FitThroughPoint( goalOrigin ); + + curAreaNum = areaNum; + lastAreaNum = curAreaNum; + + while ( 1 ) { + + // find the furthest floor face split point on the path + if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) { + endPos = origin; + } + + // if we found a point near or further than the goal we're done + if ( farPlane.Distance( endPos ) > -0.5f ) { + break; + } + + // if we reached the goal area we're done + if ( curAreaNum == goalAreaNum ) { + break; + } + + frontPlane.SetDist( frontPlane.Normal() * endPos ); + + area = &file->GetArea( curAreaNum ); + + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType != TFL_WALK ) { + continue; + } + + // if the reachability goes back to a previous area + if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] || + reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) { + continue; + } + + // if undesired travel flags are required to travel through the area + if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) { + continue; + } + + // don't optimize through an area near a ledge + if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) { + continue; + } + + // find the closest floor face split point on the path + if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) { + continue; + } + + // direction parallel to gravity + dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) - + ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir ); + if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) { + continue; + } + + // direction orthogonal to gravity + dir = endPos - p - dir; + if ( dir.LengthSqr() > Square( 0.2f ) ) { + continue; + } + + break; + } + + if ( !reach ) { + return false; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + } + + endAreaNum = curAreaNum; + + return true; +} + +/* +============ +idAASLocal::SubSampleWalkPath +============ +*/ +idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) { + return point; + } + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::WalkPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxWalkPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.reachability = reach; + // RAVEN END + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + // only optimize a limited distance ahead + if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( reach->travelType != TFL_WALK ) { + break; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + switch( reach->travelType ) { + case TFL_WALKOFFLEDGE: + path.type = PATHTYPE_WALKOFFLEDGE; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_BARRIERJUMP: + path.type |= PATHTYPE_BARRIERJUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_JUMP: + path.type |= PATHTYPE_JUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + default: + break; + } + + return true; +} + +/* +============ +idAASLocal::FlyPathValid + + returns true if one can fly in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + aasTrace_t trace; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + file->Trace( trace, origin, goalOrigin ); + + endPos = trace.endpos; + endAreaNum = trace.lastAreaNum; + + if ( trace.fraction >= 1.0f ) { + return true; + } + + return false; +} + +/* +============ +idAASLocal::SubSampleFlyPath +============ +*/ +idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) { + return point; + } + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::FlyPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxFlyPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + return true; +} + +typedef struct wallEdge_s { + int edgeNum; + int verts[2]; + struct wallEdge_s * next; +} wallEdge_t; + +/* +============ +idAASLocal::SortWallEdges +============ +*/ +void idAASLocal::SortWallEdges( int *edges, int numEdges ) const { + int i, j, k, numSequences; + wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge; + + wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) ); + sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + + for ( i = 0; i < numEdges; i++ ) { + wallEdges[i].edgeNum = edges[i]; + GetEdgeVertexNumbers( edges[i], wallEdges[i].verts ); + wallEdges[i].next = NULL; + sequenceFirst[i] = &wallEdges[i]; + sequenceLast[i] = &wallEdges[i]; + } + numSequences = numEdges; + + for ( i = 0; i < numSequences; i++ ) { + for ( j = i+1; j < numSequences; j++ ) { + if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) { + sequenceLast[j]->next = sequenceFirst[i]; + sequenceFirst[i] = sequenceFirst[j]; + break; + } + if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) { + sequenceLast[i]->next = sequenceFirst[j]; + break; + } + } + if ( j < numSequences ) { + numSequences--; + for ( k = j; k < numSequences; k++ ) { + sequenceFirst[k] = sequenceFirst[k+1]; + sequenceLast[k] = sequenceLast[k+1]; + } + i = -1; + } + } + + k = 0; + for ( i = 0; i < numSequences; i++ ) { + for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) { + edges[k++] = wallEdge->edgeNum; + } + } +} + +/* +============ +idAASLocal::GetWallEdges +============ +*/ +int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const { + int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + const aasFace_t *face1, *face2; + idReachability *reach; + + if ( !file ) { + return 0; + } + + numEdges = 0; + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = -1; + queueEnd = 0; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + area = &file->GetArea( curArea ); + + for ( i = 0; i < area->numFaces; i++ ) { + face1Num = file->GetFaceIndex( area->firstFace + i ); + face1 = &file->GetFace( abs(face1Num) ); + + if ( !(face1->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face1->numEdges; j++ ) { + edge1Num = file->GetEdgeIndex( face1->firstEdge + j ); + absEdge1Num = abs( edge1Num ); + + // test if the edge is shared by another floor face of this area + for ( k = 0; k < area->numFaces; k++ ) { + if ( k == i ) { + continue; + } + face2Num = file->GetFaceIndex( area->firstFace + k ); + face2 = &file->GetFace( abs(face2Num) ); + + if ( !(face2->flags & FACE_FLOOR ) ) { + continue; + } + + for ( l = 0; l < face2->numEdges; l++ ) { + edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) ); + if ( edge2Num == absEdge1Num ) { + break; + } + } + if ( l < face2->numEdges ) { + break; + } + } + if ( k < area->numFaces ) { + continue; + } + + // test if the edge is used by a reachability + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + if ( reach->edgeNum == absEdge1Num ) { + break; + } + } + } + if ( reach ) { + continue; + } + + // test if the edge is already in the list + for ( k = 0; k < numEdges; k++ ) { + if ( edge1Num == edges[k] ) { + break; + } + } + if ( k < numEdges ) { + continue; + } + + // add the edge to the list + edges[numEdges++] = edge1Num; + if ( numEdges >= maxEdges ) { + return numEdges; + } + } + } + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + return numEdges; +} diff --git a/source/game/ai/AAS_routing.cpp b/source/game/ai/AAS_routing.cpp new file mode 100644 index 0000000..5b1bd4b --- /dev/null +++ b/source/game/ai/AAS_routing.cpp @@ -0,0 +1,1424 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for print and error + +#define CACHETYPE_AREA 1 +#define CACHETYPE_PORTAL 2 + +#define MAX_ROUTING_CACHE_MEMORY (2*1024*1024) + +#define LEDGE_TRAVELTIME_PANALTY 250 + +/* +============ +idRoutingCache::idRoutingCache +============ +*/ +idRoutingCache::idRoutingCache( int size ) { + areaNum = 0; + cluster = 0; + next = prev = NULL; + time_next = time_prev = NULL; + travelFlags = 0; + startTravelTime = 0; + type = 0; + this->size = size; + reachabilities = new byte[size]; + memset( reachabilities, 0, size * sizeof( reachabilities[0] ) ); + travelTimes = new unsigned short[size]; + memset( travelTimes, 0, size * sizeof( travelTimes[0] ) ); +} + +/* +============ +idRoutingCache::~idRoutingCache +============ +*/ +idRoutingCache::~idRoutingCache( void ) { + delete [] reachabilities; + delete [] travelTimes; +} + +/* +============ +idRoutingCache::Size +============ +*/ +int idRoutingCache::Size( void ) const { + return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] ); +} + +/* +============ +idAASLocal::AreaTravelTime +============ +*/ +unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const { + float dist; + + dist = ( end - start ).Length(); + + if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) { + dist *= 100.0f / 100.0f; + } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) { + dist *= 100.0f / 150.0f; + } else { + dist *= 100.0f / 300.0f; + } + if ( dist < 1.0f ) { + return 1; + } + return (unsigned short) idMath::FtoiFast( dist ); +} + +/* +============ +idAASLocal::CalculateAreaTravelTimes +============ +*/ +void idAASLocal::CalculateAreaTravelTimes(void) { + int n, i, j, numReach, numRevReach, t, maxt; + byte *bytePtr; + idReachability *reach, *rev_reach; + + // get total memory for all area travel times + numAreaTravelTimes = 0; + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + numReach = 0; + for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) { + numReach++; + } + + numRevReach = 0; + for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + numRevReach++; + } + numAreaTravelTimes += numReach * numRevReach; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + bytePtr = (byte *) areaTravelTimes; + + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + // for each reachability that starts in this area calculate the travel time + // towards all the reachabilities that lead towards this area + for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) { + assert( i < MAX_REACH_PER_AREA ); + if ( i >= MAX_REACH_PER_AREA ) { +// RAVEN BEGIN +// bdube: better error message + gameLocal.Error( "i >= MAX_REACH_PER_AREA on area %d at %g,%g,%g", n, file->GetArea( n ).center.x, file->GetArea( n ).center.y, file->GetArea( n ).center.z ); +// RAVEN END + } + reach->number = i; + reach->disableCount = 0; + reach->areaTravelTimes = (unsigned short *) bytePtr; + for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) { + t = AreaTravelTime( n, reach->start, rev_reach->end ); + reach->areaTravelTimes[j] = t; + if ( t > maxt ) { + maxt = t; + } + } + bytePtr += j * sizeof( unsigned short ); + } + + // if this area is a portal + if ( file->GetArea( n ).cluster < 0 ) { + // set the maximum travel time through this portal + file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt ); + } + } + + assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) ); +} + +/* +============ +idAASLocal::DeleteAreaTravelTimes +============ +*/ +void idAASLocal::DeleteAreaTravelTimes( void ) { + Mem_Free( areaTravelTimes ); + areaTravelTimes = NULL; + numAreaTravelTimes = 0; +} + +/* +============ +idAASLocal::SetupRoutingCache +============ +*/ +void idAASLocal::SetupRoutingCache( void ) { + int i; + byte *bytePtr; + + areaCacheIndexSize = 0; + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndexSize += file->GetCluster( i ).numReachableAreas; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) + + areaCacheIndexSize * sizeof( idRoutingCache *), MA_AAS ); +//RAVEN END + bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** ); + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr; + bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * ); + } + + portalCacheIndexSize = file->GetNumAreas(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), MA_AAS ); + + areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ),MA_AAS ); + portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ),MA_AAS ); + + goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::DeleteClusterCache +============ +*/ +void idAASLocal::DeleteClusterCache( int clusterNum ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !areaCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) { + for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) { + areaCacheIndex[clusterNum][i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::DeletePortalCache +============ +*/ +void idAASLocal::DeletePortalCache( void ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !portalCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) { + portalCacheIndex[i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::ShutdownRoutingCache +============ +*/ +void idAASLocal::ShutdownRoutingCache( void ) { + int i; + + for ( i = 0; i < file->GetNumClusters(); i++ ) { + DeleteClusterCache( i ); + } + + DeletePortalCache(); + + Mem_Free( areaCacheIndex ); + areaCacheIndex = NULL; + areaCacheIndexSize = 0; + Mem_Free( portalCacheIndex ); + portalCacheIndex = NULL; + portalCacheIndexSize = 0; +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( areaUpdate ) { + Mem_Free( areaUpdate ); + areaUpdate = NULL; + } + if ( portalUpdate ) { + Mem_Free( portalUpdate ); + portalUpdate = NULL; + } + if ( goalAreaTravelTimes ) { + Mem_Free( goalAreaTravelTimes ); + goalAreaTravelTimes = NULL; + } +// RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::SetupRouting +============ +*/ +bool idAASLocal::SetupRouting( void ) { +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + areaCacheIndex = NULL; + portalCacheIndex = NULL; + areaUpdate = NULL; + portalUpdate = NULL; + goalAreaTravelTimes = NULL; +// RAVEN END + + CalculateAreaTravelTimes(); + SetupRoutingCache(); + return true; +} + +/* +============ +idAASLocal::ShutdownRouting +============ +*/ +void idAASLocal::ShutdownRouting( void ) { + DeleteAreaTravelTimes(); + ShutdownRoutingCache(); +} + +/* +============ +idAASLocal::RoutingStats +============ +*/ +void idAASLocal::RoutingStats( void ) const { + idRoutingCache *cache; + int numAreaCache, numPortalCache; + int totalAreaCacheMemory, totalPortalCacheMemory; + + numAreaCache = numPortalCache = 0; + totalAreaCacheMemory = totalPortalCacheMemory = 0; + for ( cache = cacheListStart; cache; cache = cache->time_next ) { + if ( cache->type == CACHETYPE_AREA ) { + numAreaCache++; + totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } else { + numPortalCache++; + totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } + } + + gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 ); + gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 ); + gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 ); + gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 ); + gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); + gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); +} + +/* +============ +idAASLocal::RemoveRoutingCacheUsingArea +============ +*/ +void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) { + int clusterNum; + + clusterNum = file->GetArea( areaNum ).cluster; + if ( clusterNum > 0 ) { + // remove all the cache in the cluster the area is in + DeleteClusterCache( clusterNum ); + } + else { + // if this is a portal remove all cache in both the front and back cluster + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] ); + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] ); + } + DeletePortalCache(); +} + +/* +============ +idAASLocal::DisableArea +============ +*/ +void idAASLocal::DisableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) { + return; + } + + file->SetAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::EnableArea +============ +*/ +void idAASLocal::EnableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) { + return; + } + + file->RemoveAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::SetAreaState_r +============ +*/ +bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) { + int res; + const aasNode_t *node; + bool foundClusterPortal = false; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + // if this area is a cluster portal + if ( file->GetArea( -nodeNum ).contents & areaContents ) { + if ( disabled ) { + DisableArea( -nodeNum ); + } else { + EnableArea( -nodeNum ); + } + foundClusterPortal |= true; + } + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled ); + nodeNum = node->children[0]; + } + } + + return foundClusterPortal; +} + +/* +============ +idAASLocal::SetAreaState +============ +*/ +bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { + idBounds expBounds; + + if ( !file ) { + return false; + } + + expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + + // find all areas within or touching the bounds with the given contents and disable/enable them for routing + return SetAreaState_r( 1, expBounds, areaContents, disabled ); +} + +/* +============ +idAASLocal::GetBoundsAreas_r +============ +*/ +void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const { + int res; + const aasNode_t *node; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + areas.Append( -nodeNum ); + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + GetBoundsAreas_r( node->children[1], bounds, areas ); + nodeNum = node->children[0]; + } + } +} + +/* +============ +idAASLocal::SetObstacleState +============ +*/ +void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) { + int i; + const aasArea_t *area; + idReachability *reach, *rev_reach; + bool inside; + + for ( i = 0; i < obstacle->areas.Num(); i++ ) { + + RemoveRoutingCacheUsingArea( obstacle->areas[i] ); + + area = &file->GetArea( obstacle->areas[i] ); + + for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + + if ( rev_reach->travelType & TFL_INVALID ) { + continue; + } + + inside = false; + + if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) { + inside = true; + } + else { + for ( reach = area->reach; reach; reach = reach->next ) { + if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) { + inside = true; + break; + } + } + } + + if ( inside ) { + if ( enable ) { + rev_reach->disableCount--; + if ( rev_reach->disableCount <= 0 ) { + rev_reach->travelType &= ~TFL_INVALID; + rev_reach->disableCount = 0; + } + } + else { + rev_reach->travelType |= TFL_INVALID; + rev_reach->disableCount++; + } + } + } + } +} + +// RAVEN BEGIN +// cdr: Alternate Routes Bug + +/* +============ +idAASLocal::SetReachabilityState +============ +*/ +void idAASLocal::SetReachabilityState( idReachability* reach, bool enable ) { + if ( enable && reach->travelType&TFL_INVALID) { + reach->disableCount--; + if ( reach->disableCount <= 0 ) { + reach->travelType &= ~TFL_INVALID; + reach->disableCount = 0; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } + } else if (!enable && !(reach->travelType&TFL_INVALID)) { + reach->travelType |= TFL_INVALID; + reach->disableCount++; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } +} +// RAVEN END + + +/* +============ +idAASLocal::AddObstacle +============ +*/ +aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) { + idRoutingObstacle *obstacle; + + if ( !file ) { + return -1; + } + + obstacle = new idRoutingObstacle; + obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas ); + SetObstacleState( obstacle, true ); + + obstacleList.Append( obstacle ); + return obstacleList.Num() - 1; +} + +/* +============ +idAASLocal::RemoveObstacle +============ +*/ +void idAASLocal::RemoveObstacle( const aasHandle_t handle ) { + if ( !file ) { + return; + } + if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) { + SetObstacleState( obstacleList[handle], false ); + + delete obstacleList[handle]; + obstacleList.RemoveIndex( handle ); + } +} + +/* +============ +idAASLocal::RemoveAllObstacles +============ +*/ +void idAASLocal::RemoveAllObstacles( void ) { + int i; + + if ( !file ) { + return; + } + + for ( i = 0; i < obstacleList.Num(); i++ ) { + SetObstacleState( obstacleList[i], false ); + delete obstacleList[i]; + } + obstacleList.Clear(); +} + +/* +============ +idAASLocal::LinkCache + + link the cache in the cache list sorted from oldest to newest cache +============ +*/ +void idAASLocal::LinkCache( idRoutingCache *cache ) const { + + // if the cache is already linked + if ( cache->time_next || cache->time_prev || cacheListStart == cache ) { + UnlinkCache( cache ); + } + + totalCacheMemory += cache->Size(); + + // add cache to the end of the list + cache->time_next = NULL; + cache->time_prev = cacheListEnd; + if ( cacheListEnd ) { + cacheListEnd->time_next = cache; + } + cacheListEnd = cache; + if ( !cacheListStart ) { + cacheListStart = cache; + } +} + +/* +============ +idAASLocal::UnlinkCache +============ +*/ +void idAASLocal::UnlinkCache( idRoutingCache *cache ) const { + + totalCacheMemory -= cache->Size(); + + // unlink the cache + if ( cache->time_next ) { + cache->time_next->time_prev = cache->time_prev; + } else { + cacheListEnd = cache->time_prev; + } + if ( cache->time_prev ) { + cache->time_prev->time_next = cache->time_next; + } else { + cacheListStart = cache->time_next; + } + cache->time_next = cache->time_prev = NULL; +} + +/* +============ +idAASLocal::DeleteOldestCache +============ +*/ +void idAASLocal::DeleteOldestCache( void ) const { + idRoutingCache *cache; + + assert( cacheListStart ); + + // unlink the oldest cache + cache = cacheListStart; + UnlinkCache( cache ); + + // unlink the oldest cache from the area or portal cache index + if ( cache->next ) { + cache->next->prev = cache->prev; + } + if ( cache->prev ) { + cache->prev->next = cache->next; + } + else if ( cache->type == CACHETYPE_AREA ) { + areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next; + } + else if ( cache->type == CACHETYPE_PORTAL ) { + portalCacheIndex[cache->areaNum] = cache->next; + } + + delete cache; +} + +/* +============ +idAASLocal::GetAreaReachability +============ +*/ +idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const { + idReachability *reach; + + for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) { + if ( --reachabilityNum < 0 ) { + return reach; + } + } + return NULL; +} + +/* +============ +idAASLocal::ClusterAreaNum +============ +*/ +ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const { + int side, areaCluster; + + areaCluster = file->GetArea( areaNum ).cluster; + if ( areaCluster > 0 ) { + return file->GetArea( areaNum ).clusterAreaNum; + } + else { + side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum; + return file->GetPortal( -areaCluster ).clusterAreaNum[side]; + } +} + +/* +============ +idAASLocal::UpdateAreaRoutingCache +============ +*/ +void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const { + int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas; + unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA]; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + + // number of reachability areas within this cluster + numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas; + + // number of the start area within the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + return; + } + + areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime; + badTravelFlags = ~areaCache->travelFlags; + memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) ); + + // initialize first update + curUpdate = &areaUpdate[clusterAreaNum]; + curUpdate->areaNum = areaCache->areaNum; + curUpdate->areaTravelTimes = startAreaTravelTimes; + curUpdate->tmpTravelTime = areaCache->startTravelTime; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the list + while( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->fromAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + // get the cluster number of the area + cluster = nextArea->cluster; + // don't leave the cluster, however do flood into cluster portals + if ( cluster > 0 && cluster != areaCache->cluster ) { + continue; + } + + // get the number of the area in the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + continue; // should never happen + } + + assert( clusterAreaNum < areaCache->size ); + + // time already travelled plus the traveltime through the current area + // plus the travel time of the reachability towards the next area + t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime; + + if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) { + + areaCache->travelTimes[clusterAreaNum] = t; + areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area + nextUpdate = &areaUpdate[clusterAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->areaTravelTimes = reach->areaTravelTimes; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetAreaRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + int clusterAreaNum; + idRoutingCache *cache, *clusterCache; + + // number of the area in the cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // pointer to the cache for the area in the cluster + clusterCache = areaCacheIndex[clusterNum][clusterAreaNum]; + // check if cache without undesired travel flags already exists + for ( cache = clusterCache; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas ); + cache->type = CACHETYPE_AREA; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = clusterCache; + if ( clusterCache ) { + clusterCache->prev = cache; + } + areaCacheIndex[clusterNum][clusterAreaNum] = cache; + UpdateAreaRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::UpdatePortalRoutingCache +============ +*/ +void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const { + int i, portalNum, clusterAreaNum; + unsigned short t; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *cache; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + + curUpdate = &portalUpdate[ file->GetNumPortals() ]; + curUpdate->cluster = portalCache->cluster; + curUpdate->areaNum = portalCache->areaNum; + curUpdate->tmpTravelTime = portalCache->startTravelTime; + + //put the area to start with in the current read list + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the current list + while( updateListStart ) { + + curUpdate = updateListStart; + // remove the current update from the list + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + // current update is removed from the list + curUpdate->isInList = false; + + cluster = &file->GetCluster( curUpdate->cluster ); + cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags ); + + // take all portals of the cluster + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + assert( portalNum < portalCache->size ); + portal = &file->GetPortal( portalNum ); + + clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum ); + if ( clusterAreaNum >= cluster->numReachableAreas ) { + continue; + } + + t = cache->travelTimes[clusterAreaNum]; + if ( t == 0 ) { + continue; + } + t += curUpdate->tmpTravelTime; + + if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) { + + portalCache->travelTimes[portalNum] = t; + portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum]; + nextUpdate = &portalUpdate[portalNum]; + if ( portal->clusters[0] == curUpdate->cluster ) { + nextUpdate->cluster = portal->clusters[1]; + } + else { + nextUpdate->cluster = portal->clusters[0]; + } + nextUpdate->areaNum = portal->areaNum; + // add travel time through the actual portal area for the next update + nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime; + + if ( !nextUpdate->isInList ) { + + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetPortalRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + idRoutingCache *cache; + + // check if cache without undesired travel flags already exists + for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetNumPortals() ); + cache->type = CACHETYPE_PORTAL; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = portalCacheIndex[areaNum]; + if ( portalCacheIndex[areaNum] ) { + portalCacheIndex[areaNum]->prev = cache; + } + portalCacheIndex[areaNum] = cache; + UpdatePortalRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::RouteToGoalArea +============ +*/ +bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const { + int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum; + unsigned short int t, bestTime; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *areaCache, *portalCache, *clusterCache; + idReachability *bestReach, *r, *nextr; + + travelTime = 0; + *reach = NULL; + + if ( !file ) { + return false; + } + + if ( areaNum == goalAreaNum ) { + return true; + } + + if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum ); +// RAVEN END + return false; + } + if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum ); +// RAVEN END + return false; + } + + while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) { + DeleteOldestCache(); + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the source area is a cluster portal, read directly from the portal cache + if ( clusterNum < 0 ) { + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] ); + travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start ); + return true; + } + + bestTime = 0; + bestReach = NULL; + + // check if the goal area is a portal of the source area cluster + if ( goalClusterNum < 0 ) { + portal = &file->GetPortal( -goalClusterNum ); + if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) { + goalClusterNum = clusterNum; + } + } + + // if both areas are in the same cluster + if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) { + clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags ); + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + if ( clusterCache->travelTimes[clusterAreaNum] ) { + bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] ); + bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start ); + } + else { + clusterCache = NULL; + } + } + else { + clusterCache = NULL; + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + + // the cluster the area is in + cluster = &file->GetCluster( clusterNum ); + // current area inside the current cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // if the area is not a reachable area + if ( clusterAreaNum >= cluster->numReachableAreas) { + return false; + } + + // find the portal of the source area cluster leading towards the goal area + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + + // if the goal area isn't reachable from the portal + if ( !portalCache->travelTimes[portalNum] ) { + continue; + } + + portal = &file->GetPortal( portalNum ); + // get the cache of the portal area + areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags ); + // if the portal is not reachable from this area + if ( !areaCache->travelTimes[clusterAreaNum] ) { + continue; + } + + r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] ); + + if ( clusterCache ) { + // if the next reachability from the portal leads back into the cluster + nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] ); + if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) { + continue; + } + } + + // the total travel time is the travel time from the portal area to the goal area + // plus the travel time from the source area towards the portal area + t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum]; + // NOTE: Should add the exact travel time through the portal area. + // However we add the largest travel time through the portal area. + // We cannot directly calculate the exact travel time through the portal area + // because the reachability used to travel into the portal area is not known. + t += portal->maxAreaTravelTime; + + // if the time is better than the one already found + if ( !bestTime || t < bestTime ) { + bestReach = r; + bestTime = t; + } + } + + if ( !bestReach ) { + return false; + } + + *reach = bestReach; + travelTime = bestTime; + + return true; +} + +/* +============ +idAASLocal::TravelTimeToGoalArea +============ +*/ +int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { + int travelTime; + idReachability *reach; + + if ( !file ) { + return 0; + } + + if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) { + return 0; + } + return travelTime; +} + +/* +============ +idAASLocal::FindNearestGoal +============ +*/ +bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const { + int i, j, k, badTravelFlags, nextAreaNum; + aasGoal_t bestGoal; + unsigned short t, bestTravelTime; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + idVec3 v1, v2, p; + float targetDist, dist; + + if ( file == NULL || areaNum <= 0 ) { + goal.areaNum = areaNum; + goal.origin = origin; + return false; + } + + // setup obstacles + for ( k = 0; k < numObstacles; k++ ) { + obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0]; + } + + badTravelFlags = ~travelFlags; + SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) ); + + targetDist = (target - origin).Length(); + + // initialize first update + curUpdate = &areaUpdate[areaNum]; + curUpdate->areaNum = areaNum; + curUpdate->tmpTravelTime = 0; + curUpdate->start = origin; + curUpdate->next = NULL; + curUpdate->prev = NULL; + + callback.Init ( ); + + // if the first area is valid goal, just return the origin + curUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, areaNum, origin, minDistance, maxDistance, &origin, goal ); + if ( curUpdate->cluster == idAASCallback::TEST_OK ) { + callback.Finish ( ); + return true; + } + + updateListStart = curUpdate; + updateListEnd = curUpdate; + + bestTravelTime = 0; + bestGoal.areaNum = 0; + + // while there are updates in the list + while ( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + // if we already found a closer location + if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) { + continue; + } + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->toAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + t = curUpdate->tmpTravelTime + + AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) + + reach->travelTime; + + // project target origin onto movement vector through the area + v1 = reach->end - curUpdate->start; + v1.Normalize(); + v2 = target - curUpdate->start; + p = curUpdate->start + (v2 * v1) * v1; + + // get the point on the path closest to the target + for ( j = 0; j < 3; j++ ) { + if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) || + (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) { + break; + } + } + if ( j >= 3 ) { + dist = (target - p).Length(); + } else { + dist = (target - reach->end).Length(); + } + + // avoid moving closer to the target + if ( dist < targetDist ) { + t += ( targetDist - dist ) * 10; + } + + // if we already found a closer location + if ( bestTravelTime && t >= bestTravelTime ) { + continue; + } + + // if this is not the best path towards the next area + if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) { + continue; + } + + // path may not go through any obstacles + for ( k = 0; k < numObstacles; k++ ) { + // If the start of the movement vector is inside the expanded bounds then we are already too + // close to the obstacle, so use its unexpanded bounds instead. + if ( obstacles[k].expAbsBounds.ContainsPoint ( curUpdate->start ) ) { + if ( obstacles[k].absBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + // if the movement vector intersects the expanded obstacle bounds + } else if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + } + if ( k < numObstacles ) { + continue; + } + + goalAreaTravelTimes[nextAreaNum] = t; + nextUpdate = &areaUpdate[nextAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->start = reach->end; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + // If outside of max distance skip this area + idVec3 point = origin; + float areaDist; + file->PushPointIntoAreaNum ( nextAreaNum, point ); + areaDist = (origin-point).LengthFast(); + if ( maxDistance > 0.0f && areaDist > maxDistance ) { + curUpdate->cluster = idAASCallback::TEST_BADAREA; + continue; + } + + // don't put goal near a ledge + if ( !( nextArea->flags & AREA_LEDGE ) ) { + + // add travel time through the area + t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center ); + + if ( !bestTravelTime || t < bestTravelTime ) { + // if the area is not visible to the target + nextUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, reach->toAreaNum, origin, minDistance, maxDistance, NULL, bestGoal ); + switch ( nextUpdate->cluster ) { + case idAASCallback::TEST_OK: + bestTravelTime = t; + break; + case idAASCallback::TEST_BADAREA: + if ( curUpdate->cluster != idAASCallback::TEST_BADAREA ) { + continue; + } + break; + } + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + + callback.Finish ( ); + + if ( bestGoal.areaNum ) { + goal = bestGoal; + return true; + } + + return false; +} diff --git a/source/game/ai/AAS_tactical.cpp b/source/game/ai/AAS_tactical.cpp new file mode 100644 index 0000000..7229aa9 --- /dev/null +++ b/source/game/ai/AAS_tactical.cpp @@ -0,0 +1,1765 @@ +/////////////////////////////////////////////////////////////////////////////////// +// rvAAS_tactical.cpp +// +// AAST Tactical Search Parameters Documentation +// --------------------------------------------- +// There are 10 tests at your disposal when creating search functions, 3 +// sets of 3 and one special case test. Each of these 10 tests can have +// hard min and max limits, as well as a "soft" weight which will determine how +// highly the feature is ranked against other features. Let's examine a +// diagram of what these 10 tests are: +// +// [F] ######### Key: +// | ######### [F] = Focus Test Set (enemy, or forward projection) +// / ######### [O] = Owner Test Set (actor who is doing the search) +// [P] ######### [P] = Path Test Set (line between owner and focus) +// | ######### <-> = Advance Test (In front / behind owner) +// / x ##### x = Feature (feature being tested) +// <---[O]---> ##### ### = Walls +// #################### +// #################### +// #################### +// +// Each Test Set Has The Following: +// - Distance RANGE=[ 0, 1] WEIGHT=(-1=Close, 1=Far) +// Distance is computed from the origin of the subject to the origin of the +// feature being tested (x in the above diagram). Distance will be a common +// test to use on all three sets, with all mannor of clamped ranges and +// sort values. Most common though will be to sort close to the owner so +// as to minimize how long it will take for the AI to get to the spot. +// +// - FacingDot RANGE=[-1, 1] WEIGHT=(-1=Behind, 1=In Front) +// FacingDot is computed with the dotproduct of the subject's facing and +// the feature's normal. This too will be a common test to compare with +// all three subjects. With it you can prefer points in front or behind +// things. +// +// - DirectionDot RANGE=[-1, 1] WEIGHT=(-1=Toward, 1=Away) +// DirectionDot is computed by subtracing the origins of the feature and +// the subject, normalizing and taking the dotproduct of the result with +// the feature's normal. Usually, you will want a negative weight on this +// test to comare how close the feature is pointing at the subject (usually +// tested against Focus) +// +// Lastly, there is a special "additional" test for "advance": +// - Advance RANGE=[-1, 1] WEIGHT=(-1=Backward, 1=Forward) +// Advance is computed using the owner direction dot with the path facing. +// This test will tell you how much the feature is between the owner and +// the focus, reguardless of what direction either is facing at the time. +// As a result, it approximates "advancing". +// +/////////////////////////////////////////////////////////////////////////////////// +#include "../../idlib/precompiled.h" +#pragma hdrstop + + +/////////////////////////////////////////////////////////////////////////////////// +// Includes +/////////////////////////////////////////////////////////////////////////////////// +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_local.h" +#include "AAS_tactical.h" + +/////////////////////////////////////////////////////////////////////////////////// +// Global::CONTANER SIZES AND OTHER LIMITS +/////////////////////////////////////////////////////////////////////////////////// +const int MAX_FEATURE_LIST = 20; +const int MAX_AREAS_TOUCHED = 450; + +/////////////////////////////////////////////////////////////////////////////////// +// Global::FEATURE TESTING DISTANCES +/////////////////////////////////////////////////////////////////////////////////// +const float MAX_DISTANCE = 650.0f; +const float MIN_DISTANCE = 72.0f; +const float MIN_NEAR_DISTANCE = 112.0f; +const float FROM_ENEMY_PROJECT = 350.0f; +const float TEST_TEAMMATE_DIST = 150.0f; + + + + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Normal +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Normal() +{ + static idVec3 n(0,0,0); + n[0] = ((float)(normalx) / 127.0f) - 1.0f; + n[1] = ((float)(normaly) / 127.0f) - 1.0f; + return n; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Origin() +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Origin() +{ + static idVec3 o; + o.Set((float)x, (float)y, (float)z); + return o; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::GetLookPos() +/////////////////////////////////////////////////////////////////////////////// +int aasFeature_s::GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance ) +{ + static idVec3 up(0.0f,0.0f,1.0f); + static idVec3 direction; + static idVec3 right; + static float rightDot; + static float distance; + + lookPos = Origin(); + lookPos[2] += height - leanDistance; + direction = aimAtOrigin - lookPos; + distance = direction.NormalizeFast(); + right = Normal().Cross(up); + rightDot = right * direction; + + + // Check For Optimal Conditions + //------------------------------ + if (flags&FEATURE_LOOK_OVER && fabsf(rightDot)<0.2f) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT && rightDot>0.0f) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT && rightDot<0.0f) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + + // So Nothing Matches Perfectly, Let's Try Fallback Cases In This Order + //---------------------------------------------------------------------- + if (flags&FEATURE_LOOK_OVER) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + // This Is Odd, There Must Be No Look Flags On This Feature At All + //----------------------------------------------------------------- + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvSortReach +// +// This structure is used by the search heap below to sort areas by actual +// distance from the start point to do a distance based BFS instead of a +// least links based BFS. +/////////////////////////////////////////////////////////////////////////////// +struct rvSortReach +{ + int mAreaNum; + idReachability* mReach; + float mDistance; + + bool operator<(const rvSortReach& r) const + { + return (mDistance>r.mDistance); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTest +// +// A test is a mechanism to weight values and to provide min and max bounds. +/////////////////////////////////////////////////////////////////////////////// +struct rvTest +{ + float mMin; + float mMax; + float mWeight; + float mValue; + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteFloat(mMin); + savefile->WriteFloat(mMax); + savefile->WriteFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadFloat(mMin); + savefile->ReadFloat(mMax); + savefile->ReadFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Reset - Sets the values to defaults + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mMax = 1.0f; + mMin = -1.0f; + mWeight = 0.0f; + mValue = 0.0f; + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Simple compute weight + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return mValue * mWeight; + } + + /////////////////////////////////////////////////////////////////////////// + // Test - Records the value out of the range and returns true if succeeded + /////////////////////////////////////////////////////////////////////////// + bool Test(float Value, float MaxScale=1.0f) + { + if (mMin>-1.0f || mMax<1.0f || mWeight!=0.0f) + { + mValue = Value; + if (MaxScale!=1.0f) + { + mValue /= MaxScale; + } + mValue = (mValue-mMin) / (mMax-mMin); // Now Scale It [0.0, 1.0] Of Min And Max + return (mValue>=0.0f && mValue<=1.0f); // If Not In [0.0, 1.0], Test Fails + } + return true; // Test Is Not Active + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Returns the abs of the weight, and adds to negatives + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + if (mWeight<0.0f) + { + negatives += mWeight; + } + return fabsf(mWeight); + } + + void DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction); + void DrawDebugInfo(const idVec4 color, const idVec3& origin); +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTestSet +// +// Test sets are designed to be used by the rvAASTacticalSensorLocal class to compute +// various vectors against a given feature. +/////////////////////////////////////////////////////////////////////////////// +struct rvTestSet +{ + // Parameters + //------------ + bool mProjectOrigin; // If True, Origin Is Cast Out Along mFacing Vector + idVec3 mOrigin; // Position Of The Test Subject + idVec3 mFacing; // Orientation Of The Test Subject + + // Computed During Test() + //------------------------ + rvTest mDistance; // Resulting Distance To Feature + rvTest mFacingDot; // Resulting Facing Dot Product To Feature + rvTest mDirectionDot; // Resulting Direction Dot Product To Feature + idVec3 mDirection; // Resulting Direction To Feature + + /////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////// + rvTestSet( void ) + : mOrigin(0,0,0), mFacing(0,0,0), mProjectOrigin(false) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteBool(mProjectOrigin); + savefile->WriteVec3(mOrigin); + savefile->WriteVec3(mFacing); + mDistance.Save(savefile); + mFacingDot.Save(savefile); + mDirectionDot.Save(savefile); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadBool(mProjectOrigin); + savefile->ReadVec3(mOrigin); + savefile->ReadVec3(mFacing); + mDistance.Restore(savefile); + mFacingDot.Restore(savefile); + mDirectionDot.Restore(savefile); + } + + + /////////////////////////////////////////////////////////////////////////// + // Reset - Restets all sub tests + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mProjectOrigin = false; + mDirectionDot.Reset(); + mFacingDot.Reset(); + mDistance.Reset(); +// bdube: Had to comment this out becaue it would break the test function which checks for non defaults +// mDistance.mMin = 0.0f; // special case (default would be -1.0f because all others are dot products) + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Adds up the weights of the sub tests + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return (mDistance.Weight() + mFacingDot.Weight() + mDirectionDot.Weight()); + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Computes weight range of all sub tests + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + return (mDistance.WeightRange(negatives) + mFacingDot.WeightRange(negatives) + mDirectionDot.WeightRange(negatives)); + } + + /////////////////////////////////////////////////////////////////////////// + // Test - This is the actuall feature test + /////////////////////////////////////////////////////////////////////////// + bool Test(aasFeature_t* f, float distance=0.0f) + { + // First Compute The Direction + //----------------------------- + mDirection = f->Origin() - mOrigin; + + // If Project Origin Is True, Then Move Origin Along Facing Vector + //----------------------------------------------------------------- + if (mProjectOrigin) + { + mDirection.ProjectOntoVector(mFacing); + mOrigin += mDirection; + mDirection = f->Origin() - mOrigin; + } + + // If No Override On Distance, Compute It By Normalizing The Direction + //--------------------------------------------------------------------- + if (!distance) + { + distance = mDirection.Normalize(); + } + else + { + mDirection.Normalize(); + } + + + // THE DISTANCE TEST + //------------------- + if (!mDistance.Test(distance, MAX_DISTANCE)) + { + return false; + } + + // THE FACING TEST + //----------------- + if (!mFacingDot.Test(f->Normal()*mFacing)) + { + return false; + } + + // THE DIRECTION TEST + //-------------------- + if (!mDirectionDot.Test(f->Normal()*mDirection)) + { + return false; + } + return true; + } + + /////////////////////////////////////////////////////////////////////////// + // SetupOriginAndFacing - Called For Each Test Set + /////////////////////////////////////////////////////////////////////////// + void SetupOriginAndFacing(const idEntity* ent, const idVec3* originOverride=0, const idVec3* facingOverride=0) + { + if (!ent) + { + mOrigin = (originOverride)?(*originOverride):(vec3_origin); + mFacing = (facingOverride)?(*facingOverride):(vec3_origin); + } + else + { + const idActor* entActor = dynamic_cast(ent); // bleh. Base entity class should properly return origin, angles, and forward vector + + mOrigin = (originOverride)?(*originOverride):(ent->GetPhysics()->GetOrigin()); + mFacing = (facingOverride)?(*facingOverride):(entActor?entActor->viewAxis[0]:ent->GetPhysics()->GetAxis(0)[0]); + } + + mFacing[2] = 0; + mFacing.Normalize(); + } + + /////////////////////////////////////////////////////////////////////////// + // ProjectOriginForward - Used By Several Setup Options To Project Origin + // Along Facing Direction Some + /////////////////////////////////////////////////////////////////////////// + void ProjectOriginForward(float distance, float xyRange=0.0f, bool randomFacing=0.0f) + { + mOrigin += (mFacing * distance); + if (xyRange) + { + mOrigin[0] += rvRandom::flrand(-xyRange, xyRange); + mOrigin[1] += rvRandom::flrand(-xyRange, xyRange); + } + if (randomFacing) + { + mFacing[0] = rvRandom::flrand(-1.0f, 1.0f); + mFacing[1] = rvRandom::flrand(-1.0f, 1.0f); + } + mFacing[2] = 0.0f; + mFacing.Normalize(); + } + + void DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin); +}; + + + + + +/////////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +// +// This is the local implimentation of the rvAASTacticalSensor interface. It +// contains all the various local data and functions needed to execute a +// search of the tactical data in AAS. +/////////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensorLocal : rvAASTacticalSensor +{ + // Owner + /////////////////////////////////////////////////////////////////////////////// + idActor* mOwner; // Owner Of The Sensor + idAI* mOwnerAI; // Owner As Already Cast To AI Type + + // Search Parameters + /////////////////////////////////////////////////////////////////////////////// + idStr mSearchName; // Current Search Name + int mFlagsMatchAny; // Features must match AT LEAST ONE of these flags + int mFlagsMatchAll; // Features must match ALL of these flags + int mFlagsMatchNone; // Features must match NONE of these flags + int mFeaturesSearchMax; // Maximum number of features to extract from the grid + int mFeaturesFinalMax; // After sorting, prune list down to this size + rvTestSet mFromOwner; // Test Set For Owner Relation + rvTestSet mFromEnemy; // Test Set For Focus Relation + rvTestSet mFromTether; // Test Set for Tether Relation + rvTestSet mFromPath; // Test Set For Path Relation + rvTest mAdvance; // Single Test For Advance / Retreat + rvTest mAssignment; // Single Test For Assignment Direction Dot Product + rvTest mLeanNormal; // Single Test For Lean Normal Biasing + idVec3 mAssignmentDirection; // Used By Assignment Test + bool mAssignmentValid; // Turns On And Off The Assignment Test + idEntityPtr mEnemyOverride; // Overrides Enemy Pointer To Any Entity + + // Search & Update Results + /////////////////////////////////////////////////////////////////////////////// + idList mFeatures; // The list of all features found in the most recent search + idVec3 mReservedOrigin; // Origin of feature that is currently reserved + aasFeature_t* mReserved; // Which feature is currently reserved + aasFeature_t* mNear; // Which feature is closest + aasFeature_t* mLook; // Which feature to look down + int mLookStartTime; + float mLookStopDist; + + + + + + // Local API + /////////////////////////////////////////////////////////////////////////////// + rvAASTacticalSensorLocal(); + ~rvAASTacticalSensorLocal(); + void Update(); + void Save(idSaveGame *savefile); + void Restore(idRestoreGame *savefile); + void Clear(); + void DrawDebugInfo(); + + // Search + /////////////////////////////////////////////////////////////////////////////// + void Search(); + void SearchReset(idEntity* enemyOverride=0, float ownerRangeMin=0.0f, float ownerRangeMax=1.0f); + void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f); + void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f); + void SearchHide(idEntity* from=0); + void SearchFlank(); + void SearchAdvance(); + void SearchRetreat(); + void SearchAmbush(); + void SearchDebug(); + float SearchComputeWeightRange(float& rangeNegative); + float SearchComputeWeight(); + + // Feature Testing + /////////////////////////////////////////////////////////////////////////////// + void TestSetupCurrentValues(); + bool TestValid(aasFeature_t* f, float walkDistanceToFeature); + bool TestValidWithCurrentState(aasFeature_t* f=0); + + // Feature Reservation + /////////////////////////////////////////////////////////////////////////////// + void Reserve(aasFeature_t* f); + + // Access To Results + /////////////////////////////////////////////////////////////////////////////// + int FeatureCount() {return mFeatures.Num();} + aasFeature_t* Feature(int i) {return mFeatures[i];} + aasFeature_t* Near() const {return mNear;} + aasFeature_t* Look() const {return mLook;} + aasFeature_t* Reserved() const {return mReserved;} + const idVec3& ReservedOrigin() const {return mReservedOrigin;} +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Objects +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal* mSensor; +float mDebugRadius; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Typedefines +/////////////////////////////////////////////////////////////////////////////// +typedef idEntityPtr TEntPtr; +typedef aasFeature_t* TFeaturePtr; + + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor::CREATE_SENSOR +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensor* rvAASTacticalSensor::CREATE_SENSOR(idActor* owner) +{ + rvAASTacticalSensorLocal* nSensor = new rvAASTacticalSensorLocal(); + nSensor->mOwner = owner; + nSensor->mOwnerAI = dynamic_cast(owner); + return nSensor; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::rvAASTacticalSensorLocal() +{ + mOwner = 0; + mOwnerAI = 0; + Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Destructor +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::~rvAASTacticalSensorLocal() +{ + Reserve(0); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Clear +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Clear() +{ + mReserved = 0; + mNear = 0; + mLook = 0; + mSearchName = ""; + mFeatures.Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Save +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Save(idSaveGame *savefile) +{ + savefile->WriteString(mSearchName); + savefile->WriteInt(mFlagsMatchAny); + savefile->WriteInt(mFlagsMatchAll); + savefile->WriteInt(mFlagsMatchNone); + savefile->WriteInt(mFeaturesSearchMax); + savefile->WriteInt(mFeaturesFinalMax); + mFromOwner.Save(savefile); + mFromEnemy.Save(savefile); + mFromTether.Save(savefile); + mFromPath.Save(savefile); + mAdvance.Save(savefile); + mAssignment.Save(savefile); + mLeanNormal.Save(savefile); + savefile->WriteVec3(mAssignmentDirection); + savefile->WriteBool(mAssignmentValid); + mEnemyOverride.Save(savefile); + +// cnicholson: NOTE: The following 4 vars are set to 0 / cleared in the restore, so don't save them. + // NOSAVE: idList mFeatures; + // NOSAVE: savefile->WriteVec3(mFeatures); + // NOSAVE: aasFeature_t* mReserved; + // NOSAVE: aasFeature_t* mNear; + // NOSAVE: aasFeature_t* mLook; + savefile->WriteInt(mLookStartTime); // cnicholson: Added unsaved var + savefile->WriteFloat(mLookStopDist);// cnicholson: Added unsaved var +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Restore +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Restore(idRestoreGame *savefile) +{ + // Clear Old Data + //---------------- + mFeatures.Clear(); + mNear = 0; + mLook = 0; + mReserved = 0; + + // Read The Save File Search Parameters + //-------------------------------------- + savefile->ReadString(mSearchName); + savefile->ReadInt(mFlagsMatchAny); + savefile->ReadInt(mFlagsMatchAll); + savefile->ReadInt(mFlagsMatchNone); + savefile->ReadInt(mFeaturesSearchMax); + savefile->ReadInt(mFeaturesFinalMax); + mFromOwner.Restore(savefile); + mFromEnemy.Restore(savefile); + mFromTether.Restore(savefile); + mFromPath.Restore(savefile); + mAdvance.Restore(savefile); + mAssignment.Restore(savefile); + mLeanNormal.Restore(savefile); + savefile->ReadVec3(mAssignmentDirection); + savefile->ReadBool(mAssignmentValid); + mEnemyOverride.Restore(savefile); +// Search(); + + savefile->ReadInt(mLookStartTime); // cnicholson: Added unrestored var + savefile->ReadFloat(mLookStopDist);// cnicholson: Added unrestored var +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Update +// +// If called regularly, this function will handle drawing debug information +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Update() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden()) + { + return; + } + + idAASFile* file = aas->GetFile(); + + idVec3 velocityFwd = mOwner->GetPhysics()->GetLinearVelocity(); + const idVec3& ownerOrigin = mOwner->GetPhysics()->GetOrigin(); + int ownerAreaNum = mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aasFeature_t* feature = 0; + aasArea_t& area = file->GetArea(ownerAreaNum); + idActor* teammate = NULL; + float featureDistance = 0.0f; + float featureDotLeft = 0.0f; + float featureDotDirection = 0.0f; + float teammateDistance = 0.0f; + float closestDistance = 0.0f; + idVec3 velocityLeft; + idVec3 velocityDown; + idVec3 featureDirection; + idVec3 teammateDirection; + + + + // Update And Possibly Clear The Near Feature + //-------------------------------------------- + if (mNear) + { + closestDistance = mNear->Origin().Dist(ownerOrigin); + if (closestDistance>MIN_NEAR_DISTANCE) + { + mNear = 0; + } + } + + + // Search For Features In This Area That Are Close To Owner Origin + //----------------------------------------------------------------- + if (area.numFeatures) + { + for (int areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + if (feature!=mNear) + { + featureDirection = feature->Origin(); + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + if (featureDistance1.0f) + { + // Compute Velocity Vectors + //-------------------------- + velocityFwd.NormalizeFast(); + velocityFwd.NormalVectors(velocityLeft, velocityDown); + + + // Check If We Should Clear The Look Feature + //------------------------------------------- + if (mLook) + { + const idVec3& featureOrigin = mLook->Origin(); + const idVec3& featureNormal = mLook->Normal(); + + // Too Far? + //---------- + if (featureOrigin.Dist(ownerOrigin)>mLookStopDist) + { + mLook = 0; + } + + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + mLook = 0; + break; + } + } + teammate = NULL; + } + + // And, If We Are Moving, Check The Near Feature To See If It Qualifies As A Look Feature + //---------------------------------------------------------------------------------------- + if (mNear && mNear!=mLook && (gameLocal.GetTime() - mLookStartTime)>3000) + { + const idVec3& featureOrigin = mNear->Origin(); + const idVec3& featureNormal = mNear->Normal(); + + // Compute Feature Direction + //--------------------------- + featureDirection = featureOrigin; + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + // Must Be Behind Me (I've Alreay Walked Past It) + //------------------------------------------------ + if (featureDistance>16.0f && featureDirection*velocityFwd<0.0f) + { + // Must Be Facing Away From Me + //----------------------------- + featureDotDirection = featureNormal*featureDirection; + if (featureDotDirection>-0.8f) + { + // Must Be Roughly Perpendicular To My Velocity (Within 45 Degrees) + //------------------------------------------------------------------ + featureDotLeft = featureNormal*velocityLeft; + if (fabsf(featureDotLeft)>0.5f) + { + // If On Left Of Me, Must Have A Right Lean, And Converse + //-------------------------------------------------------- + if ((featureDotLeft>0.0f && mNear->flags&FEATURE_LOOK_RIGHT) || + (featureDotLeft<0.0f && mNear->flags&FEATURE_LOOK_LEFT)) + { + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + break; + } + } + + if (!teammate) + { + mLook = mNear; + mLookStartTime = gameLocal.GetTime(); + mLookStopDist = rvRandom::flrand(48.0f, 80.0f); + } + } + } + } + } + } + } + else if (!mOwnerAI || !mOwnerAI->move.fl.moving) + { + mLook = 0; + } + + // Draw Any Debug Info + //--------------------- + DrawDebugInfo(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Reserve +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Reserve(aasFeature_t* f) +{ + if (f!=mReserved && mOwner) + { + mReserved = f; + + if ( f ) + { + mReservedOrigin = f->Origin ( ); + } + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// FEATURE TESTING +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// TestSetupCurrentValuesFor +// +// This function sets up the test sets to have new +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::TestSetupCurrentValues() +{ + // Owner Test Set + //---------------- + mFromOwner.SetupOriginAndFacing(mOwner); + + + // Enemy Test Set + //---------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + if (mOwnerAI && mEnemyOverride) + { + mFromEnemy.SetupOriginAndFacing(mEnemyOverride, &mOwnerAI->LastKnownPosition(mEnemyOverride), 0); + } + else if (mOwnerAI && mOwnerAI->GetEnemy()) + { + mFromEnemy.SetupOriginAndFacing(mOwnerAI->GetEnemy(), &mOwnerAI->LastKnownPosition(mOwnerAI->GetEnemy()), 0); + } + + // Tether Test Set + //---------------- + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.SetupOriginAndFacing(mOwnerAI->GetTether ( )); + } + + // Path Test Set + //---------------- + mFromPath.mProjectOrigin = true; + mFromPath.mOrigin = mFromOwner.mOrigin; + mFromPath.mFacing = mFromEnemy.mOrigin - mFromOwner.mOrigin; + mFromPath.mFacing[2] = 0; + mFromPath.mFacing.Normalize(); + + // Advance Test Set + //------------------ + // NOTHING TO DO HERE FOR NOW... +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValidReserved +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValidWithCurrentState(aasFeature_t* f) +{ + // If Trying To Hide From An Enemy That No Longer Exists, Any Feature is Invalid + //------------------------------------------------------------------------------- + if (mEnemyOverride.GetSpawnId() && !mEnemyOverride.IsValid()) + { + return false; + } + + // Reset The Test Parameters With Current Origins (Cuz Things May Have Moved) + //---------------------------------------------------------------------------- + TestSetupCurrentValues(); + + // And Run The Test That The Original Search Ran + //----------------------------------------------- + if (!f) + { + return TestValid(mReserved, 0.0f); + } + return TestValid(f, 0.0f); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValid +// +// This is THE function that tests if a given feature matches the current +// search parameters. An optional walk distance may be passed into the function +// to replace the from owner straight line distance. +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValid(aasFeature_t* f, float walkDistanceToFeature) +{ + static idVec3 LeanNormal; + static idVec3 Up(0.0f,0.0f,1.0f); + static float LeanNormalDot; + + + // Is There A Feature At All + //--------------------------- + if (!f) + { + return false; + } + + // Does It Match The Flags? + //-------------------------- + if (!(f->flags&mFlagsMatchAny) || + ((f->flags&mFlagsMatchAll)!=mFlagsMatchAll) || + (f->flags&mFlagsMatchNone)) + { + return false; + } + + // Does It Pass The Tests? + //------------------------- + if (!mFromOwner.Test(f, walkDistanceToFeature) || + !mFromEnemy.Test(f) || + !mFromTether.Test(f) || + !mFromPath.Test(f) || + !mAdvance.Test(mFromOwner.mDirection*mFromPath.mFacing) || + !mAssignment.Test(mFromOwner.mDirection*mAssignmentDirection)) + { + return false; + } + + // Make sure this cover point is vaild for the current tether + //------------------------------------------------------------ + if ( mOwnerAI && mOwnerAI->IsTethered() && !mOwnerAI->GetTether()->ValidateDestination ( mOwnerAI, f->Origin() ) ) + { + return false; + } + + // Does It Pass The Lean Normal Test? + //------------------------------------ + if (mOwnerAI && (mEnemyOverride||mOwnerAI->GetEnemy())) + {//we have an enemy position to test against + mLeanNormal.mValue = 0.0f; + if (!(f->flags&FEATURE_LOOK_OVER) && ((f->flags&FEATURE_LOOK_RIGHT) || (f->flags&FEATURE_LOOK_LEFT))) + { + LeanNormal = f->Normal().Cross(Up); // Start With Left + LeanNormalDot = LeanNormal * mFromEnemy.mDirection; + + if (!(f->flags&FEATURE_LOOK_LEFT) || ((f->flags&FEATURE_LOOK_RIGHT) && LeanNormalDot<0.0f)) + { + LeanNormalDot *= -1.0f; // Use The Right Normal + } + + if (!mLeanNormal.Test(LeanNormalDot)) + { + return false; + } + } + } + + + // Is Anyone Else Going There? + //----------------------------- + if (mOwnerAI && !aiManager.ValidateDestination(mOwnerAI, f->Origin())) + { + return false; + } + + // Everything Passed, This Feature Is Good To Go + //----------------------------------------------- + return true; +} + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SEARCH PARAMETERS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchReset +// +// Initialize Default Flags, Feature Counts, And Population Points. This +// function must be called before first in any search function, because +// the search functions rely on this standard set of parameters and then +// build upon them. +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchReset(idEntity* enemyOverride, float ownerRangeMin, float ownerRangeMax) +{ + if (!mOwner) + { + return; + } + + // Setup Base Search Parameters + //------------------------------ + mFlagsMatchAll = 0; // Must Have All Of These + mFlagsMatchAny = (FEATURE_LOOK_LEFT|FEATURE_LOOK_RIGHT|FEATURE_LOOK_OVER); // Must Have At Least One Of These + mFlagsMatchNone = (FEATURE_PINCH|FEATURE_VANTAGE); // Don't Want Any Of These + mFeaturesSearchMax = 100; + mFeaturesFinalMax = 20; + mAssignmentValid = false; // TODO: Turn This Back On + mAssignmentDirection = vec3_zero; + mEnemyOverride = enemyOverride; + + + // Lean Normal Test + //------------------ + mLeanNormal.Reset(); + mLeanNormal.mMin =-0.2f; // Must Lean Toward Enemy + + + // Owner Test Set Default Values + //------------------------------- + mFromOwner.Reset(); + mFromOwner.mDistance.mMin = ownerRangeMin; + mFromOwner.mDistance.mMax = ownerRangeMax; + mFromOwner.mDistance.mWeight =-1.0f; // Prefer Close To Owner + + // Enemy Test Set Default Values + //------------------------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + mFromEnemy.Reset(); + if ( mOwnerAI && mOwnerAI->enemy.ent ) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; // must be at least 100 units from enemy + mFromEnemy.mDistance.mMax = 2.0f; // don't care how far the distance is to the enemy, let it go over max (up to 1600) + mFromEnemy.mDirectionDot.mMax =-0.7f; // Must Face Within 45 Degrees Of enemy + mFromEnemy.mDirectionDot.mWeight =-0.3f; // Prefer To Face Toward Enemy + if (mOwnerAI && mOwnerAI->enemy.ent ) + { + // Cap Min And Max Distances To Attack Range, and Aware Range + //------------------------------------------------------------ + mFromEnemy.mDistance.mMax = mOwnerAI->combat.attackRange[1] / MAX_DISTANCE; + mFromEnemy.mDistance.mMin = mOwnerAI->combat.attackRange[0] / MAX_DISTANCE; + + if (mFromEnemy.mDistance.mMin < (mOwnerAI->combat.awareRange / MAX_DISTANCE)) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; + } + + // If haven't seen enemy in a while, allow you to go right to his last known spot + //-------------------------------------------------------------------------------- + if ( mOwnerAI->enemy.lastVisibleTime && (gameLocal.GetTime() - mOwnerAI->enemy.lastVisibleTime)>mOwnerAI->combat.maxLostVisTime/2.0f) + { + mFromEnemy.mDistance.mMin = 0.0f; + } + } + } + + + // Tether test set + //------------------------------- + mFromTether.Reset(); + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.mFacingDot.mMin = 0.5f; + mFromTether.mFacingDot.mWeight = 0.3f; + + // Disable the owner distance test + mFromOwner.Reset(); + } + + // Other Test Sets + //----------------- + mFromPath.Reset(); + mAdvance.Reset(); + mAssignment.Reset(); + + + // Default Test Values + //--------------------- + TestSetupCurrentValues(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchDebug +// +// TO RUN THIS FUNCTION, TYPE "extract_tactical" ON THE CONSOLE. +// +// Feel free to modify this function to test whatever search or other +// operation you need. The owner will be the player. +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchDebug() +{ + SearchCover(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRadius +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRadius(const idVec3& origin, float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Radius"; + mFromEnemy.Reset(); // Don't Care About Enemy At All + if ( origin != vec3_origin ) + { + mFromOwner.mOrigin = origin; // Override the owner origin + } + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchCover +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchCover(float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Cover"; + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchHide +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchHide(idEntity* from) +{ + SearchReset(from); + mSearchName = "Hide"; + mFlagsMatchNone |= FEATURE_LOOK_OVER; // Want Full Height Walls Here + mFromEnemy.mDirectionDot.mMax = -0.8f; // Must Almost Exactly At The Enemy + mFromOwner.mDistance.mMax = 2.0f; // Go as far as you need to - ignore tethering for hide + mFromOwner.mDistance.mMin = 0.4f; // Get A Good Distance Away + mFromOwner.mDirectionDot.Reset(); // Ignore any direction dot with the leader + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchFlank +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchFlank() +{ + SearchReset(); + mSearchName = "Flank"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMin = -0.2f; // Must Be Behind Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAdvance +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAdvance() +{ + SearchReset(); + mSearchName = "Advance"; + mFromOwner.mDistance.mMin = 0.15f; // Make Sure To Move Some + mAdvance.mMin = 0.3f; // Forward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRetreat +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRetreat() +{ + SearchReset(); + mSearchName = "Retreat"; + mFromOwner.mDistance.mMin = 0.1f; // Make Sure To Move Some + mAdvance.mMax = -0.3f; // Backward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAmbush +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAmbush() +{ + SearchReset(); + mSearchName = "Ambush"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMax = 0.2f; // Must Be In Front Of Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// THE SEARCH +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchComputeWeightRange +// +// We compute the weight range so that we can make a better scale factor +// between 0.0 and 1.0 later on. It's not critical that all the weights +// factor exactly 0.0 to 1.0, but it is nice to see how "good" a feature +// matches the given search parameters +/////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeightRange(float& rangeNegative) +{ + return (mFromOwner.WeightRange(rangeNegative) + + mFromEnemy.WeightRange(rangeNegative) + + mFromTether.WeightRange(rangeNegative) + + mFromPath.WeightRange(rangeNegative) + + mAdvance.WeightRange(rangeNegative) + + mLeanNormal.WeightRange(rangeNegative) + + mAssignment.WeightRange(rangeNegative)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Weight +// +// Add up the computed weight of all tests. +/////////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeight() +{ + return (mFromOwner.Weight() + + mFromEnemy.Weight() + + mFromTether.Weight() + + mFromPath.Weight() + + mAdvance.Weight() + + mLeanNormal.Weight() + + mAssignment.Weight()); +} + +/////////////////////////////////////////////////////////////////////////////// +// SortFeature function (used by Search() below) +/////////////////////////////////////////////////////////////////////////////// +ID_INLINE int rvSortFeature( const TFeaturePtr *a, const TFeaturePtr *b ) +{ + if ((*a)->weight > (*b)->weight) + { + return -1; + } + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Search +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Search() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!mOwner || !aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures()) + { + return; + } + + + static idAASFile* file; + static int areaNum; + static int areaNumOwner; + static rvBits<32000> areaVisit; + static int areaVisitCount; + static int travelTime; + static int areaFeatureNum; + static int featureNum; + static aasFeature_t* featurePtr; + static idVec3 featureOrigin; + static idReachability* reach; + static idList searchHeap; + static rvSortReach sortReach; + static float walkDistanceToFeature; + static float weight; + static float weightRangeNegative; + static float weightRangeTotal; + static idVec3 from; + static idVec3 endPos; + static int endAreaNum; + + + + +//----------------------------------------------------------------------------- +// SETUP +//----------------------------------------------------------------------------- + file = aas->GetFile(); + weightRangeNegative = 0.0f; + weightRangeTotal = SearchComputeWeightRange(weightRangeNegative); + + mFeatures.Clear(); + searchHeap.Clear(); + areaVisit.clear(); + if (!mAssignmentValid) + { + mAssignment.Reset(); // Never Worry About Squad Assignments If No Leader Is Active + } + + + + +//----------------------------------------------------------------------------- +// PHASE I - POPULATE AREA QUEUE +//----------------------------------------------------------------------------- + if ( mOwnerAI ) + { + areaNumOwner = mOwnerAI->PointReachableAreaNum ( mFromOwner.mOrigin ); + } + else + { + areaNumOwner = aas->PointReachableAreaNum(mFromOwner.mOrigin, mOwner->GetPhysics()->GetBounds(), AREA_REACHABLE_WALK); + } + + from = mFromOwner.mOrigin; + areaNum = areaNumOwner; + sortReach.mAreaNum = areaNumOwner; + sortReach.mDistance = 0.0f; + sortReach.mReach = 0; + searchHeap.Append(sortReach); + + + + +//----------------------------------------------------------------------------- +// PHASE II - BREADTH FIRST SEARCH NEIGHBORING AREAS FOR FEATURES THAT TEST OK +//----------------------------------------------------------------------------- + areaVisitCount = 0; + while (searchHeap.Num() && areaVisitCountGetArea(sr.mAreaNum); + if (area.numFeatures) + { + for (areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + featureOrigin = featurePtr->Origin(); + + // If Walk Path Is Valid, Allow From Owner Test To Use Computed Straight Line Distance + //------------------------------------------------------------------------------------- + if (aas->WalkPathValid(areaNumOwner, mFromOwner.mOrigin, sr.mAreaNum, featureOrigin, TFL_WALK, endPos, endAreaNum)) + { + walkDistanceToFeature = 0.0f; // Allows Test to use straight line distance computed + } + + // If It Is Not Possible To Straight Line Walk To A Feature, Use The Enter Point And Distance Of The Area (Which Is A Rough Appx) + //-------------------------------------------------------------------------------------------------------------------------------- + else + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(featureOrigin)):(mFromOwner.mOrigin.Dist(featureOrigin))); + } + + // Test The Feature To See If It's Valid + //--------------------------------------- + if (!TestValid(featurePtr, walkDistanceToFeature)) + { + continue; + } + + // Compute The Weight + //-------------------- + if (weightRangeTotal>0.0f) + { + weight = SearchComputeWeight(); // Compute Weight Sum + weight -= weightRangeNegative; // Bring it into a positive range + weight /= weightRangeTotal; // Scale down to 0.0 - 1.0 + + assert(weight>0.0f && weight<255.0f); + featurePtr->weight = (char)(weight*255); + } + else + { + featurePtr->weight = (unsigned char)(128); // No Sorting + } + + + // Append The Feature To The List + //-------------------------------- + mFeatures.Append(featurePtr); + } + } + + + // Add Neighboring Areas To Search + //--------------------------------- + for (reach=area.reach; reach; reach=reach->next) + { + if ((reach->travelType&TFL_WALK)) + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(reach->end)):(mFromOwner.mOrigin.Dist(reach->end))); + + if (walkDistanceToFeaturetoAreaNum; + sortReach.mReach = reach; + searchHeap.HeapAdd(sortReach); + } + } + } + } + + + + +//----------------------------------------------------------------------------- +// PHASE III - SORT AND CLIP THE FEATURE LIST +//----------------------------------------------------------------------------- + mFeatures.Sort(rvSortFeature); + + // Now, Clip The Sorted List To The Max Size + //------------------------------------------- + if (mFeatures.Num()>MAX_FEATURE_LIST) + { + mFeatures.Resize(MAX_FEATURE_LIST); + } + + // Now Reset Any Parameters Which Were Only "Temporary" During The Search, and Do Not Invalidate The Point Later + //---------------------------------------------------------------------------------------------------------------- + mFromOwner.mDistance.mMin = 0.0f; // Allow Getting Close Again + + + // Print Search Results + //---------------------- + if (ai_showTacticalFeatures.GetInteger()==3) + { + common->Printf( "[%10d] Search%s Found %d Features For %s\n", gameLocal.GetTime(), mSearchName.c_str(), mFeatures.Num(), mOwner->GetName() ); + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DEBUG GRAPHICS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_t::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void aasFeature_t::DrawDebugInfo( int index ) +{ + static idVec3 Height; + static idVec3 Orig; + static idVec3 Norm; + static idVec3 Text; + static idVec4 color; + static idVec3 Left; + + int lifetime = 0; + + color = colorWhite; + + if (flags & FEATURE_COVER) + { + color = colorGreen; + } + + Orig = Origin(); + Orig[2] += 1.0f; + + Height = Orig; + Height[2] += height; + + Norm = Orig; + Norm += Normal() * mDebugRadius; + Left = Normal().Cross(idVec3(0,0,-1)) * mDebugRadius; + + gameRenderWorld->DebugLine( color, Orig, Height, lifetime ); + gameRenderWorld->DebugLine( color, Orig, Norm, lifetime ); + + if (index>=0) + { + Text = (Origin() + Height) * 0.5f; + gameRenderWorld->DrawText( va( "%d", index ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + + Text[2] += 15.0f; + gameRenderWorld->DrawText( va( "%d", (int)weight ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + } + + // Left Corner Is On The Ground + //------------------------------ + if (flags & FEATURE_CORNER_LEFT) + { + gameRenderWorld->DebugLine( color, Orig, Orig + Left, lifetime ); + } + + // Otherwise Windows Are At The Given Height + //------------------------------------------- + else if (flags & FEATURE_LOOK_LEFT) + { + gameRenderWorld->DebugLine( color, Height, Height + Left, lifetime ); + } + + if (flags & FEATURE_CORNER_RIGHT) + { + gameRenderWorld->DebugLine( color, Orig, Orig - Left, lifetime ); + } + else if (flags & FEATURE_LOOK_RIGHT) + { + gameRenderWorld->DebugLine( color, Height, Height - Left, lifetime ); + } + + if (flags & FEATURE_LOOK_OVER) + { + gameRenderWorld->DebugLine( color, Height, Height + (Normal()*mDebugRadius), lifetime ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction) +{ + gameRenderWorld->DebugFOV(color, origin, direction, mMax, 20.0f, mMin, 10.0f, 20.0f); +} + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin) +{ + static idVec3 up(0.0f,0.0f,-1.0f); + if (mMax<1.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMax * MAX_DISTANCE), 25); + } + if (mMin>0.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMin * MAX_DISTANCE), 25); + } +} +/////////////////////////////////////////////////////////////////////////// +// rvTestSet::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTestSet::DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin) +{ + static int lifetime = 0; + static idVec3 origin; + + origin = mOrigin; + if (mProjectOrigin) + { + origin = nonProjectedOrigin; + } + + gameRenderWorld->DebugArrow( color, origin, origin+mFacing * 25.0f, 8, lifetime ); + mFacingDot.DrawDebugInfo(color, origin, mFacing); + mDistance.DrawDebugInfo(color, origin); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::DrawDebugInfo() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden() || (ai_showTacticalFeatures.GetInteger()<2 && !mOwner->DebugFilter(ai_showTacticalFeatures) && !mOwner->DebugFilter(ai_debugTactical))) + { + return; + } + + + static idVec3 pos; + bool reservedDrawn = false; + bool nearDrawn = false; + bool lookDrawn = false; + + mDebugRadius = aas->GetSettings()->boundingBoxes[0][1][0]; + + + // Draw Parameters + //----------------- + if (ai_showTacticalFeatures.GetInteger()==1) + { + if (!mSearchName.IsEmpty()) + { + pos = mFromOwner.mOrigin + mFromEnemy.mOrigin; + pos *= 0.5f; + pos[2] += 25.0f; + gameRenderWorld->DrawText(mSearchName.c_str(), pos, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, 0 ); + pos[2] -= 25.0f; + + // Draw Tests + //------------ + mFromEnemy.DrawDebugInfo(colorYellow, pos); + mFromTether.DrawDebugInfo(colorOrange, pos); + mFromOwner.DrawDebugInfo(colorMagenta, pos); + mFromPath.DrawDebugInfo(colorCyan, pos); + mAssignment.DrawDebugInfo(colorPink, pos); + mAdvance.DrawDebugInfo(colorPurple, mFromOwner.mOrigin, mFromPath.mFacing); + mLeanNormal.DrawDebugInfo(colorPurple, pos); + } + + + // Draw Features + //--------------- + for (int i=0; iDrawDebugInfo(i); + if (mFeatures[i]==mReserved) + { + reservedDrawn = true; + } + if (mFeatures[i]==mNear) + { + nearDrawn = true; + } + if (mFeatures[i]==mLook) + { + lookDrawn = true; + } + } + } + + // Draw All Neighboring Features If Player & CVar==2 + //--------------------------------------------------- + if (mOwner==gameLocal.GetLocalPlayer() && ai_showTacticalFeatures.GetInteger()>=2) + { + idAASFile* file = aas->GetFile(); + const idVec3& playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + for (int i=0; iGetNumFeatures(); i++) + { + if (file->GetFeature(i).Origin().Dist(playerOrigin)<600.0f) + { + file->GetFeature(i).DrawDebugInfo(); + } + } + } + + + // Always Draw Reserved + //---------------------- + if (mReserved) + { + if (!reservedDrawn) + { + mReserved->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorBlue, mOwner->GetPhysics()->GetOrigin(), mReserved->Origin(), 8); + } + + // If Near Is Valid, Draw It + //--------------------------- + if (mNear && (!mReserved || mNear!=mReserved)) + { + if (!nearDrawn) + { + mNear->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorOrange, mOwner->GetPhysics()->GetOrigin(), mNear->Origin(), 8); + } + + // If Look Is True, Then Draw That + //--------------------------------- + if (mLook && (!mOwnerAI || mOwnerAI->InLookAtCoverMode())) + { + idVec3 n = mLook->Normal(); + n *= 64.0f; + gameRenderWorld->DebugArrow(colorYellow, mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32), mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32) + n, 8); + } +} + + + + +// RAVENEND - CDR + + + + + diff --git a/source/game/ai/AAS_tactical.h b/source/game/ai/AAS_tactical.h new file mode 100644 index 0000000..fc1385d --- /dev/null +++ b/source/game/ai/AAS_tactical.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////// +// AAS_tactical +// +// This file is the interface to an AAS Tactical Extractor, which can search +// out from a given start point and report a variety of tactically important +// objectives, including corners, walls, and pinch points. +// +// By seeing the AAS graph as a qualitative and simplified spatial +// representation of the game world. This representation is amply capapble of +// rendering higher level tactical data efficiently in real time. +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AAS_TACTICAL_H__ +#define __AAS_TACTICAL_H__ + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor +// +// The sensor structure is the public interface to the internals of AAS +// tactical features. +/////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensor +{ + // Regular Update Function + /////////////////////////////////////////////////////////////////////// + virtual void Update() = 0; + virtual void Save(idSaveGame *savefile) = 0; + virtual void Restore(idRestoreGame *savefile) = 0; + virtual void Clear() = 0; + + // Search + /////////////////////////////////////////////////////////////////////// + virtual void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchHide(idEntity* from=0) = 0; + virtual void SearchFlank() = 0; + virtual void SearchAdvance() = 0; + virtual void SearchRetreat() = 0; + virtual void SearchAmbush() = 0; + virtual void SearchDebug() = 0; + + // Feature Testing + /////////////////////////////////////////////////////////////////////// + virtual bool TestValid(aasFeature_t* f, float walkDistanceToFeature) = 0; + virtual bool TestValidWithCurrentState(aasFeature_t* f=0) = 0; + + // Feature Reservation + /////////////////////////////////////////////////////////////////////// + virtual void Reserve(aasFeature_t* f) = 0; + + // Access To Results + /////////////////////////////////////////////////////////////////////// + virtual int FeatureCount() = 0; + virtual aasFeature_t* Feature(int i) = 0; + virtual aasFeature_t* Near() const = 0; + virtual aasFeature_t* Look() const = 0; + virtual aasFeature_t* Reserved() const = 0; + virtual const idVec3& ReservedOrigin() const = 0; + + + + // STATIC SYSTEM FUNCTIONS + /////////////////////////////////////////////////////////////////////// + static rvAASTacticalSensor* CREATE_SENSOR(idActor* owner); +}; + + +#endif /* !__AAS_TACTICAL_H__ */ diff --git a/source/game/ai/AI.cpp b/source/game/ai/AI.cpp new file mode 100644 index 0000000..8fce0d7 --- /dev/null +++ b/source/game/ai/AI.cpp @@ -0,0 +1,5151 @@ +/* +================ + +AI.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "../Projectile.h" +#include "../spawner.h" +#include "AI_Tactical.h" + +const char* aiTalkMessageString [ ] = { + "None", + "primary", + "secondary", + "loop" +}; + +static const float AI_SIGHTDELAYSCALE = 5000.0f; // Full sight delay at 5 seconds or more of not seeing enemy + + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +/* +===================== +idAI::idAI +===================== +*/ +idAI::idAI ( void ) { + projectile_height_to_distance_ratio = 1.0f; + + aas = NULL; + aasSensor = NULL; + aasFind = NULL; + + lastHitCheckResult = false; + lastHitCheckTime = 0; + lastAttackTime = 0; + projectile = NULL; + projectileClipModel = NULL; + chatterTime = 0; + talkState = TALK_NEVER; + talkTarget = NULL; + talkMessage = TALKMSG_NONE; + talkBusyCount = 0; + + enemy.ent = NULL; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + + fl.neverDormant = false; // AI's can go dormant + + allowEyeFocus = true; + disablePain = false; + allowJointMod = true; + focusEntity = NULL; + focusTime = 0; + alignHeadTime = 0; + forceAlignHeadTime = 0; + + orientationJoint = INVALID_JOINT; + + eyeVerticalOffset = 0.0f; + eyeHorizontalOffset = 0.0f; + headFocusRate = 0.0f; + eyeFocusRate = 0.0f; + focusAlignTime = 0; + focusRange = 0.0f; + focusType = AIFOCUS_NONE; + + memset ( &combat.fl, 0, sizeof(combat.fl) ); + combat.max_chasing_turn = 0; + combat.shotAtTime = 0; + combat.shotAtAngle = 0.0f; + combat.meleeRange = 0.0f; + combat.tacticalPainTaken = 0; + combat.tacticalFlinches = 0; + combat.investigateTime = 0; + combat.aggressiveScale = 1.0f; + + passive.animFidgetPrefix.Clear ( ); + passive.animIdlePrefix.Clear ( ); + passive.animTalkPrefix.Clear ( ); + passive.idleAnim.Clear(); + passive.prefix.Clear(); + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + memset ( &passive.fl, 0, sizeof(passive.fl) ); + + pain.lastTakenTime = 0; + pain.takenThisFrame = 0; + pain.loopEndTime = 0; + + enemy.range = 0; + enemy.range2d = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + enemy.lastKnownPosition.Zero ( ); + enemy.lastVisibleEyePosition.Zero ( ); + enemy.lastVisibleChestPosition.Zero ( ); + enemy.lastVisibleFromEyePosition.Zero ( ); + enemy.checkTime = 0; + enemy.changeTime = 0; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + memset ( &enemy.fl, 0, sizeof(enemy.fl) ); + + currentFocusPos.Zero(); + eyeAng.Zero(); + lookAng.Zero(); + destLookAng.Zero(); + lookMin.Zero(); + lookMax.Zero(); + + eyeMin.Zero(); + eyeMax.Zero(); + + helperCurrent = NULL; + helperIdeal = NULL; + + speakTime = 0; + + actionAnimNum = 0; + actionSkipTime = 0; + actionTime = 0; +} + +/* +===================== +idAI::~idAI +===================== +*/ +idAI::~idAI() { + // Make sure we arent stuck in the simple think list + simpleThinkNode.Remove ( ); + + delete aasFind; + delete aasSensor; + delete projectileClipModel; + DeconstructScriptObject(); + scriptObject.Free(); + aiManager.RemoveTeammate ( this ); + SetPhysics( NULL ); +} + +/* +===================== +idAI::Save +===================== +*/ +void idAI::Save( idSaveGame *savefile ) const { + int i; + +// cnicholson: These 3 vars are intentionally not saved, as noted in the restore + // NOSAVE: idLinkList simpleThinkNode; + // NOSAVE: idAAS* aas; + // NOSAVE: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + // Movement + move.Save( savefile ); + savedMove.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + savefile->WriteBool( lastHitCheckResult ); + savefile->WriteInt( lastHitCheckTime ); + savefile->WriteInt( lastAttackTime ); + savefile->WriteFloat( projectile_height_to_distance_ratio ); + + savefile->WriteInt( attackAnimInfo.Num() ); + for( i = 0; i < attackAnimInfo.Num(); i++ ) { + savefile->WriteVec3( attackAnimInfo[ i ].attackOffset ); + savefile->WriteVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TOSAVE: mutable idClipModel* projectileClipModel; + projectile.Save ( savefile ); + + // Talking + savefile->WriteInt( chatterTime ); +// savefile->WriteInt( chatterRateCombat ); // NOSAVE: +// savefile->WriteInt( chatterRateIdle ); // NOSAVE: + savefile->WriteInt( talkState ); + talkTarget.Save( savefile ); + savefile->WriteInt( talkMessage ); + savefile->WriteInt( talkBusyCount ); + savefile->WriteInt ( speakTime ); + + // Focus + lookTarget.Save ( savefile ); + savefile->WriteInt( focusType ); + focusEntity.Save ( savefile ); + savefile->WriteFloat ( focusRange ); + savefile->WriteInt ( focusAlignTime ); + savefile->WriteInt ( focusTime ); + savefile->WriteVec3( currentFocusPos ); + + // Looking + savefile->WriteBool( allowJointMod ); + savefile->WriteInt( alignHeadTime ); + savefile->WriteInt( forceAlignHeadTime ); + savefile->WriteAngles( eyeAng ); + savefile->WriteAngles( lookAng ); + savefile->WriteAngles( destLookAng ); + savefile->WriteAngles( lookMin ); + savefile->WriteAngles( lookMax ); + + savefile->WriteInt( lookJoints.Num() ); + for( i = 0; i < lookJoints.Num(); i++ ) { + savefile->WriteJoint( lookJoints[ i ] ); + savefile->WriteAngles( lookJointAngles[ i ] ); + } + + savefile->WriteFloat( eyeVerticalOffset ); + savefile->WriteFloat( eyeHorizontalOffset ); + savefile->WriteFloat( headFocusRate ); + savefile->WriteFloat( eyeFocusRate ); + + // Joint Controllers + savefile->WriteAngles( eyeMin ); + savefile->WriteAngles( eyeMax ); + savefile->WriteJoint( orientationJoint ); + + pusher.Save( savefile ); // cnicholson: Added unsaved var + scriptedActionEnt.Save( savefile ); // cnicholson: Added unsaved var + + savefile->Write( &aifl, sizeof( aifl ) ); + + // Misc + savefile->WriteInt ( actionAnimNum ); + savefile->WriteInt ( actionTime ); + savefile->WriteInt ( actionSkipTime ); + savefile->WriteInt ( flagOverrides ); + + // Combat variables + savefile->Write( &combat.fl, sizeof( combat.fl ) ); + savefile->WriteFloat ( combat.max_chasing_turn ); + savefile->WriteFloat ( combat.shotAtTime ); + savefile->WriteFloat ( combat.shotAtAngle ); + savefile->WriteVec2 ( combat.hideRange ); + savefile->WriteVec2 ( combat.attackRange ); + savefile->WriteInt ( combat.attackSightDelay ); + savefile->WriteFloat ( combat.meleeRange ); + savefile->WriteFloat ( combat.aggressiveRange ); + savefile->WriteFloat ( combat.aggressiveScale ); + savefile->WriteInt ( combat.investigateTime ); + savefile->WriteFloat ( combat.visStandHeight ); + savefile->WriteFloat ( combat.visCrouchHeight ); + savefile->WriteFloat ( combat.visRange ); + savefile->WriteFloat ( combat.earRange ); + savefile->WriteFloat ( combat.awareRange ); + savefile->WriteInt ( combat.tacticalMaskAvailable ); + savefile->WriteInt ( (int&)combat.tacticalCurrent ); + savefile->WriteInt ( combat.tacticalUpdateTime ); + savefile->WriteInt ( combat.tacticalPainTaken ); + savefile->WriteInt ( combat.tacticalPainThreshold ); + savefile->WriteInt ( combat.tacticalFlinches ); + savefile->WriteInt ( combat.maxLostVisTime ); + savefile->WriteFloat ( combat.threatBase ); + savefile->WriteFloat ( combat.threatCurrent ); + savefile->WriteInt ( combat.coverValidTime ); + savefile->WriteInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->WriteString ( passive.prefix ); + savefile->WriteString ( passive.animFidgetPrefix ); + savefile->WriteString ( passive.animIdlePrefix); + savefile->WriteString ( passive.animTalkPrefix ); + savefile->WriteString ( passive.idleAnim ); + savefile->WriteInt ( passive.idleAnimChangeTime ); + savefile->WriteInt ( passive.fidgetTime ); + savefile->WriteInt ( passive.talkTime ); + savefile->Write ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Save ( savefile ); + savefile->Write ( &enemy.fl, sizeof(enemy.fl) ); + savefile->WriteInt ( enemy.lastVisibleChangeTime ); + savefile->WriteVec3 ( enemy.lastKnownPosition ); + savefile->WriteVec3 ( enemy.smoothedLinearVelocity ); + savefile->WriteVec3 ( enemy.smoothedPushedVelocity ); + savefile->WriteVec3 ( enemy.lastVisibleEyePosition ); + savefile->WriteVec3 ( enemy.lastVisibleChestPosition ); + savefile->WriteVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->WriteInt ( enemy.lastVisibleTime ); + savefile->WriteFloat ( enemy.range ); + savefile->WriteFloat ( enemy.range2d ); + savefile->WriteInt ( enemy.changeTime ); + savefile->WriteInt ( enemy.checkTime ); + + // Pain variables + savefile->WriteFloat ( pain.threshold ); + savefile->WriteFloat ( pain.takenThisFrame ); + savefile->WriteInt ( pain.lastTakenTime ); + savefile->WriteInt ( pain.loopEndTime ); + savefile->WriteString ( pain.loopType ); + + // Functions + funcs.first_sight.Save ( savefile ); + funcs.sight.Save ( savefile ); + funcs.pain.Save ( savefile ); + funcs.damage.Save ( savefile ); + funcs.death.Save ( savefile ); + funcs.attack.Save ( savefile ); + funcs.init.Save ( savefile ); + funcs.onclick.Save ( savefile ); + funcs.launch_projectile.Save ( savefile ); + funcs.footstep.Save ( savefile ); + + mPlayback.Save( savefile ); // cnicholson: Added save functionality + mLookPlayback.Save( savefile ); // cnicholson: Added save functionality + + // Tactical Sensor + aasSensor->Save( savefile ); + + // Helpers + tether.Save ( savefile ); + helperCurrent.Save ( savefile ); + helperIdeal.Save ( savefile ); + leader.Save ( savefile ); + spawner.Save ( savefile ); + + // Action timers + actionTimerRangedAttack.Save ( savefile ); + actionTimerEvade.Save ( savefile ); + actionTimerSpecialAttack.Save ( savefile ); + actionTimerPain.Save ( savefile ); + + // Actions + actionEvadeLeft.Save ( savefile ); + actionEvadeRight.Save ( savefile ); + actionRangedAttack.Save ( savefile ); + actionMeleeAttack.Save ( savefile ); + actionLeapAttack.Save ( savefile ); + actionJumpBack.Save ( savefile ); +} + +/* +===================== +idAI::Restore +===================== +*/ +void idAI::Restore( idRestoreGame *savefile ) { + bool restorePhysics; + int i; + int num; + idBounds bounds; + + InitNonPersistentSpawnArgs ( ); + + // INTENTIONALLY NOT SAVED: idLinkList simpleThinkNode; + // INTENTIONALLY NOT SAVED: idAAS* aas; + // INTENTIONALLY NOT SAVED: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + move.Restore( savefile ); + savedMove.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + savefile->ReadBool( restorePhysics ); + if ( restorePhysics ) { + RestorePhysics( &physicsObj ); + } + + savefile->ReadBool( lastHitCheckResult ); + savefile->ReadInt( lastHitCheckTime ); + savefile->ReadInt( lastAttackTime ); + savefile->ReadFloat( projectile_height_to_distance_ratio ); + + savefile->ReadInt( num ); + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( attackAnimInfo[ i ].attackOffset ); + savefile->ReadVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TORESTORE: mutable idClipModel* projectileClipModel; + projectile.Restore( savefile ); + + // Talking + savefile->ReadInt( chatterTime ); +// savefile->ReadInt( chatterRateCombat ); // Don't save +// savefile->ReadInt( chatterRateIdle ); // Don't save + savefile->ReadInt( i ); + talkState = static_cast( i ); + talkTarget.Restore( savefile ); + savefile->ReadInt( i ); + talkMessage = static_cast( i ); + savefile->ReadInt( talkBusyCount ); + savefile->ReadInt ( speakTime ); + + // Focus + lookTarget.Restore ( savefile ); + savefile->ReadInt( i ); + focusType = static_cast( i ); + focusEntity.Restore ( savefile ); + savefile->ReadFloat ( focusRange ); + savefile->ReadInt ( focusAlignTime ); + savefile->ReadInt ( focusTime ); + savefile->ReadVec3( currentFocusPos ); + + // Looking + savefile->ReadBool( allowJointMod ); + savefile->ReadInt( alignHeadTime ); + savefile->ReadInt( forceAlignHeadTime ); + savefile->ReadAngles( eyeAng ); + savefile->ReadAngles( lookAng ); + savefile->ReadAngles( destLookAng ); + savefile->ReadAngles( lookMin ); + savefile->ReadAngles( lookMax ); + + savefile->ReadInt( num ); + lookJoints.SetGranularity( 1 ); + lookJoints.SetNum( num ); + lookJointAngles.SetGranularity( 1 ); + lookJointAngles.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadJoint( lookJoints[ i ] ); + savefile->ReadAngles( lookJointAngles[ i ] ); + } + + savefile->ReadFloat( eyeVerticalOffset ); + savefile->ReadFloat( eyeHorizontalOffset ); + savefile->ReadFloat( headFocusRate ); + savefile->ReadFloat( eyeFocusRate ); + + // Joint Controllers + savefile->ReadAngles( eyeMin ); + savefile->ReadAngles( eyeMax ); + savefile->ReadJoint( orientationJoint ); + + pusher.Restore( savefile ); // cnicholson: Added unrestored var + scriptedActionEnt.Restore ( savefile ); // cnicholson: Added unrestored var + + savefile->Read( &aifl, sizeof( aifl ) ); + + // Misc + savefile->ReadInt ( actionAnimNum ); + savefile->ReadInt ( actionTime ); + savefile->ReadInt ( actionSkipTime ); + savefile->ReadInt ( flagOverrides ); + + // Combat variables + savefile->Read ( &combat.fl, sizeof( combat.fl ) ); + savefile->ReadFloat ( combat.max_chasing_turn ); + savefile->ReadFloat ( combat.shotAtTime ); + savefile->ReadFloat ( combat.shotAtAngle ); + savefile->ReadVec2 ( combat.hideRange ); + savefile->ReadVec2 ( combat.attackRange ); + savefile->ReadInt ( combat.attackSightDelay ); + savefile->ReadFloat ( combat.meleeRange ); + savefile->ReadFloat ( combat.aggressiveRange ); + savefile->ReadFloat ( combat.aggressiveScale ); + savefile->ReadInt ( combat.investigateTime ); + savefile->ReadFloat ( combat.visStandHeight ); + savefile->ReadFloat ( combat.visCrouchHeight ); + savefile->ReadFloat ( combat.visRange ); + savefile->ReadFloat ( combat.earRange ); + savefile->ReadFloat ( combat.awareRange ); + savefile->ReadInt ( combat.tacticalMaskAvailable ); + savefile->ReadInt ( (int&)combat.tacticalCurrent ); + savefile->ReadInt ( combat.tacticalUpdateTime ); + savefile->ReadInt ( combat.tacticalPainTaken ); + savefile->ReadInt ( combat.tacticalPainThreshold ); + savefile->ReadInt ( combat.tacticalFlinches ); + savefile->ReadInt ( combat.maxLostVisTime ); + savefile->ReadFloat ( combat.threatBase ); + savefile->ReadFloat ( combat.threatCurrent ); + savefile->ReadInt ( combat.coverValidTime ); + savefile->ReadInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->ReadString ( passive.prefix ); + savefile->ReadString ( passive.animFidgetPrefix ); + savefile->ReadString ( passive.animIdlePrefix); + savefile->ReadString ( passive.animTalkPrefix ); + savefile->ReadString ( passive.idleAnim ); + savefile->ReadInt ( passive.idleAnimChangeTime ); + savefile->ReadInt ( passive.fidgetTime ); + savefile->ReadInt ( passive.talkTime ); + savefile->Read ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Restore ( savefile ); + savefile->Read ( &enemy.fl, sizeof(enemy.fl) ); + savefile->ReadInt ( enemy.lastVisibleChangeTime ); + savefile->ReadVec3 ( enemy.lastKnownPosition ); + savefile->ReadVec3 ( enemy.smoothedLinearVelocity ); + savefile->ReadVec3 ( enemy.smoothedPushedVelocity ); + savefile->ReadVec3 ( enemy.lastVisibleEyePosition ); + savefile->ReadVec3 ( enemy.lastVisibleChestPosition ); + savefile->ReadVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->ReadInt ( enemy.lastVisibleTime ); + savefile->ReadFloat ( enemy.range ); + savefile->ReadFloat ( enemy.range2d ); + savefile->ReadInt ( enemy.changeTime ); + savefile->ReadInt ( enemy.checkTime ); + + // Pain variables + savefile->ReadFloat ( pain.threshold ); + savefile->ReadFloat ( pain.takenThisFrame ); + savefile->ReadInt ( pain.lastTakenTime ); + savefile->ReadInt ( pain.loopEndTime ); + savefile->ReadString ( pain.loopType ); + + // Functions + funcs.first_sight.Restore ( savefile ); + funcs.sight.Restore ( savefile ); + funcs.pain.Restore ( savefile ); + funcs.damage.Restore ( savefile ); + funcs.death.Restore ( savefile ); + funcs.attack.Restore ( savefile ); + funcs.init.Restore ( savefile ); + funcs.onclick.Restore ( savefile ); + funcs.launch_projectile.Restore ( savefile ); + funcs.footstep.Restore ( savefile ); + + mPlayback.Restore( savefile ); // cnicholson: Added restore functionality + mLookPlayback.Restore( savefile ); // cnicholson: Added restore functionality + + // Tactical Sensor + aasSensor->Restore( savefile ); + + // Helpers + tether.Restore ( savefile ); + helperCurrent.Restore( savefile ); + helperIdeal.Restore ( savefile ); + leader.Restore ( savefile ); + spawner.Restore ( savefile ); + + // Action timers + actionTimerRangedAttack.Restore ( savefile ); + actionTimerEvade.Restore ( savefile ); + actionTimerSpecialAttack.Restore ( savefile ); + actionTimerPain.Restore ( savefile ); + + // Actions + actionEvadeLeft.Restore ( savefile ); + actionEvadeRight.Restore ( savefile ); + actionRangedAttack.Restore ( savefile ); + actionMeleeAttack.Restore ( savefile ); + actionLeapAttack.Restore ( savefile ); + actionJumpBack.Restore ( savefile ); + + // Set the AAS if the character has the correct gravity vector + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + if ( gravity == gameLocal.GetGravity() ) { + SetAAS(); + } + + // create combat collision hull for exact collision detection, do initial set un-hidden + SetCombatModel(); + LinkCombat(); +} + +/* +===================== +idAI::InitNonPersistentSpawnArgs +===================== +*/ +void idAI::InitNonPersistentSpawnArgs ( void ) { + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + aasFind = NULL; + + simpleThinkNode.Remove ( ); + simpleThinkNode.SetOwner ( this ); + + chatterRateIdle = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_idle", "0" ) ); + chatterRateCombat = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_combat", "0" ) ); + chatterTime = 0; + + combat.tacticalMaskUpdate = 0; + + enemy.smoothVelocityRate = spawnArgs.GetFloat ( "smoothVelocityRate", "0.1" ); +} + +/* +===================== +idAI::Spawn +===================== +*/ +void idAI::Spawn( void ) { + const char* jointname; + const idKeyValue* kv; + idStr jointName; + idAngles jointScale; + jointHandle_t joint; + idVec3 local_dir; + + // Are all monsters disabled? + if ( !g_monsters.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Initialize the non saved spawn args + InitNonPersistentSpawnArgs ( ); + + spawnArgs.GetInt( "team", "1", team ); + spawnArgs.GetInt( "rank", "0", rank ); + + animPrefix = spawnArgs.GetString ( "animPrefix", "" ); + + // Standard flags + fl.notarget = spawnArgs.GetBool ( "notarget", "0" ); + fl.quickBurn = false; + + // AI flags + flagOverrides = 0; + memset ( &aifl, 0, sizeof(aifl) ); + aifl.ignoreFlashlight = spawnArgs.GetBool ( "ignore_flashlight", "1" ); + aifl.lookAtPlayer = spawnArgs.GetBool ( "lookAtPlayer", "0" ); + aifl.disableLook = spawnArgs.GetBool ( "noLook", "0" ); + aifl.undying = spawnArgs.GetBool ( "undying", "0" ); + aifl.killerGuard = spawnArgs.GetBool ( "killer_guard", "0" ); + aifl.scriptedEndWithIdle = true; + + // Setup Move Data + move.Spawn( spawnArgs ); + + pain.threshold = spawnArgs.GetInt ( "painThreshold", "0" ); + pain.takenThisFrame = 0; + + // Initialize combat variables + combat.fl.aware = spawnArgs.GetBool ( "ambush", "0" ); + combat.fl.tetherNoBreak = spawnArgs.GetBool ( "tetherNoBreak", "0" ); + combat.fl.noChatter = spawnArgs.GetBool ( "noCombatChatter" ); + combat.hideRange = spawnArgs.GetVec2 ( "hideRange", "150 750" ); + combat.attackRange = spawnArgs.GetVec2 ( "attackRange", "0 1000" ); + combat.attackSightDelay = SEC2MS ( spawnArgs.GetFloat ( "attackSightDelay", "1" ) ); + combat.visRange = spawnArgs.GetFloat( "visRange", "2048" ); + combat.visStandHeight = spawnArgs.GetFloat( "visStandHeight", "68" ); + combat.visCrouchHeight = spawnArgs.GetFloat( "visCrouchHeight", "48" ); + combat.earRange = spawnArgs.GetFloat( "earRange", "2048" ); + combat.awareRange = spawnArgs.GetFloat( "awareRange", "150" ); + combat.aggressiveRange = spawnArgs.GetFloat( "aggressiveRange", "0" ); + combat.maxLostVisTime = SEC2MS ( spawnArgs.GetFloat ( "maxLostVisTime", "10" ) ); + combat.tacticalPainThreshold = spawnArgs.GetInt ( "tactical_painThreshold", va("%d", health / 4) ); + combat.coverValidTime = 0; + combat.maxInvalidCoverTime = SEC2MS ( spawnArgs.GetFloat ( "maxInvalidCoverTime", "1" ) ); + combat.threatBase = spawnArgs.GetFloat ( "threatBase", "1" ); + combat.threatCurrent = combat.threatBase; + + SetPassivePrefix ( spawnArgs.GetString ( "passivePrefix" ) ); + + disablePain = spawnArgs.GetBool ( "nopain", "0" ); + + spawnArgs.GetFloat( "melee_range", "64", combat.meleeRange ); + spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio ); + + //melee superhero -- take far reduced damage from melee. + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) && spawnArgs.GetBool( "meleeSuperhero", "1") ) { + aifl.meleeSuperhero = true; + } else { + aifl.meleeSuperhero = false; + } + + //announce rate + announceRate = spawnArgs.GetFloat( "announceRate" ); + if( 0.0f == announceRate ) { + announceRate = AISPEAK_CHANCE; + } + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" ); + lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" ); + + lookJoints.SetGranularity( 1 ); + lookJointAngles.SetGranularity( 1 ); + kv = spawnArgs.MatchPrefix( "look_joint", NULL ); + while( kv ) { + jointName = kv->GetKey(); + jointName.StripLeadingOnce( "look_joint " ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + } else { + jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" ); + jointScale.roll = 0.0f; + + // if no scale on any component, then don't bother adding it. this may be done to + // zero out rotation from an inherited entitydef. + if ( jointScale != ang_zero ) { + lookJoints.Append( joint ); + lookJointAngles.Append( jointScale ); + } + } + kv = spawnArgs.MatchPrefix( "look_joint", kv ); + } + + // calculate joint positions on attack frames so we can do proper "can hit" tests + CalculateAttackOffsets(); + + eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" ); + eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" ); + eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" ); + eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" ); + headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.06" ); + eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" ); + focusRange = spawnArgs.GetFloat( "focus_range", "0" ); + focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) ); + + jointname = spawnArgs.GetString( "joint_orientation" ); + if ( *jointname ) { + orientationJoint = animator.GetJointHandle( jointname ); + if ( orientationJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + jointname = spawnArgs.GetString( "joint_flytilt" ); + if ( *jointname ) { + move.flyTiltJoint = animator.GetJointHandle( jointname ); + if ( move.flyTiltJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY ); + } else { + if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + } + + // move up to make sure the monster is at least an epsilon above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + physicsObj.SetGravity( gravity ); + + SetPhysics( &physicsObj ); + + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + lookAng.Zero ( ); + lookAng.yaw = move.current_yaw; + + + SetAAS(); + + projectile = NULL; + projectileClipModel = NULL; + + if ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "trigger_anim" ) ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + Hide(); + } else { + // play a looping ambient sound if we have one + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); + } + + if ( health <= 0 ) { + gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() ); + health = 1; + } + + BecomeActive( TH_THINK ); + + if ( move.fl.allowPushMovables ) { + af.SetupPose( this, gameLocal.time ); + af.GetPhysics()->EnableClip(); + } + + // init the move variables + StopMove( MOVE_STATUS_DONE ); + + // Initialize any scripts + idStr prefix = "script_"; + for ( kv = spawnArgs.MatchPrefix ( prefix.c_str(), NULL ); + kv; + kv = spawnArgs.MatchPrefix ( prefix.c_str(), kv ) ) { + SetScript ( kv->GetKey().c_str() + prefix.Length(), kv->GetValue() ); + } + + // Initialize actions + actionEvadeLeft.Init ( spawnArgs, "action_evadeLeft", NULL, 0 ); + actionEvadeRight.Init ( spawnArgs, "action_evadeRight", NULL, 0 ); + actionRangedAttack.Init ( spawnArgs, "action_rangedAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK|AIACTIONF_MELEE ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", NULL, AIACTIONF_ATTACK ); + actionJumpBack.Init ( spawnArgs, "action_jumpBack", NULL, 0 ); + + // Global action timers + actionTimerRangedAttack.Init ( spawnArgs, "actionTimer_RangedAttack" ); + actionTimerEvade.Init ( spawnArgs, "actionTimer_Evade" ); + actionTimerSpecialAttack.Init ( spawnArgs, "actionTimer_SpecialAttack" ); + actionTimerPain.Init ( spawnArgs, "actionTimer_pain" ); + + // Available tactical options + combat.tacticalUpdateTime = 0; + combat.tacticalCurrent = AITACTICAL_NONE; + combat.tacticalMaskUpdate = 0; + combat.tacticalMaskAvailable = AITACTICAL_TURRET_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_MOVE_TETHER_BIT; + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_cover", "0" ) ? AITACTICAL_COVER_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_ranged", "0" ) ? AITACTICAL_RANGED_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_hide", "0" ) ? AITACTICAL_HIDE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_rush", "0" ) ? AITACTICAL_MELEE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_passive", "0" ) ? AITACTICAL_PASSIVE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "allowPlayerPush", "0" ) ? AITACTICAL_MOVE_PLAYERPUSH_BIT : 0); + + // Talking? + const char* npc; + if ( spawnArgs.GetString( "npc_name", NULL, &npc ) != NULL && *npc ) { + if ( spawnArgs.GetBool ( "follows", "0" ) ) { + SetTalkState ( TALK_FOLLOW ); + } else { + switch ( spawnArgs.GetInt( "talks" ) ) + { + case 2: + SetTalkState ( TALK_WAIT ); + break; + case 1: + SetTalkState ( TALK_OK ); + break; + case 0: + default: + SetTalkState ( TALK_BUSY ); + break; + } + } + } else { + SetTalkState ( TALK_NEVER ); + } + + // Passive or aggressive ai? + if ( spawnArgs.GetBool ( "passive" ) ) { + Event_BecomePassive ( true ); + + if ( spawnArgs.GetInt ( "passive" ) > 1 ) { + aifl.disableLook = true; + } + } + + // Print out a warning about any AI that is spawned unhidden since they will be all thinking + if( gameLocal.GameState ( ) == GAMESTATE_STARTUP && !spawnArgs.GetInt( "hide" ) && !spawnArgs.GetInt( "trigger_anim" ) && !spawnArgs.GetInt( "trigger_cover" ) && !spawnArgs.GetInt( "trigger_move" ) ){ + gameLocal.Warning( "Unhidden AI placed in map (will be constantly active): %s (%s)", name.c_str(), GetPhysics()->GetOrigin().ToString() ); + } + + Begin ( ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + PostEventMS( &EV_PostSpawn, 0 ); + // RAVEN END +} + +/* +=================== +idAI::Begin +=================== +*/ +void idAI::Begin ( void ) { + const char* temp; + bool animWalk; + bool animRun; + + // Look for the lack of a run or walk animation and force the opposite + animWalk = HasAnim ( ANIMCHANNEL_LEGS, "walk" ); + animRun = HasAnim ( ANIMCHANNEL_LEGS, "run" ); + if ( !animWalk && animRun ) { + move.fl.noWalk = true; + } else if ( !animRun && animWalk ) { + move.fl.noRun = true; + } + + // If a trigger anim or hide is specified then wait until activated before waking up + if ( spawnArgs.GetString ( "trigger_anim", "", &temp ) || spawnArgs.GetBool ( "hide" ) ) { + Hide(); + PostState ( "Wait_Activated" ); + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + // Wake up + } else { + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + } +} + +/* +=================== +idAI::WakeUp +=================== +*/ +void idAI::WakeUp ( void ) { + const char* temp; + + // Already awake? + if ( aifl.awake ) { + return; + } + + // Find the closest helper + UpdateHelper ( ); + + aifl.awake = true; + + // If the monster is flying then start them with the flying movement type + if ( spawnArgs.GetBool ( "static" ) ) { + SetMoveType ( MOVETYPE_STATIC ); + } else if ( spawnArgs.GetBool ( "flying" ) ) { + SetMoveType ( MOVETYPE_FLY ); + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += move.fly_offset; + } else { + SetMoveType ( MOVETYPE_ANIM ); + } + + // Wake up any linked entities + WakeUpTargets ( ); + + // Default enemy? + if ( !combat.fl.ignoreEnemies ) { + if ( spawnArgs.GetString ( "enemy", "", &temp ) && *temp ) { + SetEnemy ( gameLocal.FindEntity ( temp ) ); + } else if ( spawnArgs.GetBool ( "forceEnemy", "0" ) ) { + SetEnemy ( FindEnemy ( false, true ) ); + } + } + + // Default leader? + if ( spawnArgs.GetString ( "leader", "", &temp ) && *temp ) { + SetLeader ( gameLocal.FindEntity ( temp ) ); + } + + // If hidden and face enemy is specified we should orient ourselves toward our enemy immediately + if ( enemy.ent && IsHidden ( ) && spawnArgs.GetBool ( "faceEnemy" ) ) { + TurnToward ( enemy.ent->GetPhysics()->GetOrigin() ); + move.current_yaw = move.ideal_yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + Show ( ); + + aiManager.AddTeammate ( this ); + + // External script functions + ExecScriptFunction( funcs.init, this ); + + OnWakeUp ( ); +} + +/* +=================== +idAI::List_f +=================== +*/ +void idAI::List_f( const idCmdArgs &args ) { + int e; + int i; + idEntity* check; + int count; + idDict countsFixed; + idDict countsSpawned; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" ); + gameLocal.Printf( "------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + if ( !check ) { + continue; + } + + if ( check->IsType ( idAI::Type ) ) { + idAI* checkAI = static_cast(check); + + // Skip spawned AI + if ( checkAI->spawner ) { + continue; + } + countsFixed.SetInt ( check->GetEntityDefName(), countsFixed.GetInt ( check->GetEntityDefName(), "0" ) + 1 ); + + gameLocal.Printf( "%4i: %-20s %-20s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), checkAI->move.fl.allowAnimMove ); + count++; + } else if ( check->IsType ( rvSpawner::GetClassType() ) ) { + const idKeyValue* kv; + rvSpawner* checkSpawner = static_cast(check); + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + for ( i = 0; i < checkSpawner->GetNumSpawnPoints(); i ++ ) { + check = checkSpawner->GetSpawnPoint ( i ); + if ( !check ) { + continue; + } + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + } + } + } + + // Combine the two lists + for ( e = 0; e < countsSpawned.GetNumKeyVals(); e ++ ) { + const char* keyName = countsSpawned.GetKeyVal ( e )->GetKey(); + countsFixed.Set ( keyName, va("(%s) %3d", + countsSpawned.GetKeyVal ( e )->GetValue().c_str(), + countsFixed.GetInt ( keyName, "0" ) + atoi(countsSpawned.GetKeyVal ( e )->GetValue()) ) ); + } + + // Print out total counts + if ( countsFixed.GetNumKeyVals() ) { + gameLocal.Printf( "------------------------------------------------\n" ); + for ( e = 0; e < countsFixed.GetNumKeyVals(); e ++ ) { + const idKeyValue* kv = countsFixed.GetKeyVal ( e ); + gameLocal.Printf( "%10s: %-20s\n", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + } + + gameLocal.Printf( "------------------------------------------------\n" ); + gameLocal.Printf( "...%d monsters (%d unique types)\n", count, countsFixed.GetNumKeyVals() ); +} + +/* +================ +idAI::DormantBegin + +called when entity becomes dormant +================ +*/ +void idAI::DormantBegin( void ) { + // since dormant happens on a timer, we wont get to update particles to + // hidden through the think loop, but we need to hide them though. + if ( enemyNode.InList() ) { + // remove ourselves from the enemy's enemylist + enemyNode.Remove(); + } + idActor::DormantBegin(); +} + +/* +================ +idAI::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idAI::DormantEnd( void ) { + if ( enemy.ent && !enemyNode.InList() ) { + // let our enemy know we're back on the trail + idActor *enemyEnt = dynamic_cast< idActor *>( enemy.ent.GetEntity() ); + if( enemyEnt ){ + enemyNode.AddToEnd( enemyEnt->enemyList ); + } + } + idActor::DormantEnd(); +} + +/* +===================== +idAI::ValidateCover + +Validate the reserved cover feature with current conditions. Also quickly tests if +cover does not face enemy at all, which triggers an instant update (ignoring maxCoverInvalidTime) +===================== +*/ +bool idAI::ValidateCover( ) { + if ( !InCoverMode( ) ) { + return false; + } + + if ( aasSensor->TestValidWithCurrentState() && (!enemy.ent || enemy.range > combat.awareRange ) ) { + combat.coverValidTime = gameLocal.time; + } else if ( combat.coverValidTime && !IsCoverValid ( ) ) { + combat.coverValidTime = 0; + OnCoverInvalidated(); + } + + return IsCoverValid(); +} + +/* +===================== +idAI::DoDormantTests +===================== +*/ +bool idAI::DoDormantTests ( void ) { + // If in a scripted move that we should never go dormant in + if ( aifl.scripted && aifl.scriptedNeverDormant ) { + return false; + } + // If following a player never go dormant + if ( leader && leader->IsType ( idPlayer::GetClassType ( ) ) ) { + return false; + } + // AI should no longer go dormant when outside of tether + if ( IsTethered () && !IsWithinTether ( ) ) { + return false; + } + return idActor::DoDormantTests ( ); +} + +/* +===================== +idAI::Think +===================== +*/ +void idAI::Think( void ) { + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + // Simple think this frame? + aifl.simpleThink = aiManager.IsSimpleThink ( this ); + + aiManager.thinkCount++; + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Start ( ); + } + + if ( thinkFlags & TH_THINK ) { + // clear out the enemy when he dies or is hidden + idEntity* enemyEnt = enemy.ent; + idActor* enemyAct = dynamic_cast( enemyEnt ); + + // Clear our enemy if necessary + if ( enemyEnt ) { + if (enemyAct && enemyAct->IsInVehicle()) { + SetEnemy(enemyAct->GetVehicleController().GetVehicle()); // always get angry at the enemy's vehicle first, not the enemy himself + } else { + bool enemyDead = (enemyEnt->fl.takedamage && enemyEnt->health <= 0); + if ( enemyDead || enemyEnt->fl.notarget || enemyEnt->IsHidden() || (enemyAct && enemyAct->team == team)) { + ClearEnemy ( enemyDead ); + } + } + } + + // Action time is stopped when the torso is not idle + if ( !aifl.action ) { + actionTime += gameLocal.msec; + } + + ValidateCover(); + + move.current_yaw += deltaViewAngles.yaw; + move.ideal_yaw = idMath::AngleNormalize180( move.ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + + if( move.moveType != MOVETYPE_PLAYBACK ){ + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + if ( !move.fl.allowHiddenMove && IsHidden() ) { + // hidden monsters + UpdateStates (); + } else if( !ai_freeze.GetBool() ) { + Prethink(); + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // update enemy position if not dead + if ( !aifl.dead ) { + UpdateEnemy ( ); + } + + // update state machine + UpdateStates(); + + // run all movement commands + Move(); + + // if not dead, chatter and blink + if( move.moveType != MOVETYPE_DEAD ){ + UpdateChatter(); + CheckBlink(); + } + + Postthink(); + } else { + DrawTactical ( ); + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + aifl.pain = false; + aifl.damage = false; + aifl.pushed = false; + pusher = NULL; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + if ( move.fl.allowPushMovables ) { + PushWithAF(); + } + + if ( fl.hidden && move.fl.allowHiddenMove ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + aasSensor->Update(); + + UpdateAnimation(); + Present(); + LinkCombat(); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Stop ( ); + } +} + +/* +============ +idAI::UpdateFocus +============ +*/ +void idAI::UpdateFocus ( const idMat3& orientationAxis ) { + // Alwasy look at enemy + if ( !allowJointMod || !allowEyeFocus ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( IsBehindCover() && !aifl.action && !IsSpeaking() ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( !aifl.scripted && (IsEnemyRecentlyVisible(2.0f) || gameLocal.GetTime()-lastAttackTime<1000) ) { + //not scripted and: have an enemy OR we shot at an enemy recently (for when we kill an enemy in the middle of a volley) + SetFocus ( AIFOCUS_ENEMY, 1000 ); + } else if ( move.fl.moving && InCoverMode ( ) && DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_COVER, 1000 ); + } else if ( InLookAtCoverMode() ) { + SetFocus ( AIFOCUS_COVERLOOK, 1000 ); + } else if ( talkTarget && gameLocal.time < passive.talkTime ) { + SetFocus ( AIFOCUS_TALK, 100 ); + } else if ( lookTarget ) { + SetFocus ( AIFOCUS_TARGET, 100 ); + } else if ( !IsBehindCover() && tether && DistanceTo ( move.moveDest ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_TETHER, 100 ); + } else if ( IsTethered() && (move.fl.moving || IsBehindCover()) ) { + //tethered and at cover or moving - don't look at leader or player + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( helperCurrent && helperCurrent->IsCombat ( ) ) { + SetFocus ( AIFOCUS_HELPER, 100 ); + } else if ( !aifl.scripted && !move.fl.moving ) { + idEntity* lookat = NULL; + aiFocus_t newfocus = AIFOCUS_NONE; + + if ( leader ) { + idVec3 dir2Leader = leader->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin(); + if ( (viewAxis[0] * dir2Leader) > 0.0f ) { + //leader is in front of me + newfocus = AIFOCUS_LEADER; + lookat = leader; + } else if ( focusType == AIFOCUS_LEADER ) { + //stop looking at him! + SetFocus ( AIFOCUS_NONE, 0 ); + } + } else if ( aifl.lookAtPlayer && !move.fl.moving ) { + newfocus = AIFOCUS_PLAYER; + lookat = gameLocal.GetLocalPlayer(); + } + + if ( newfocus != AIFOCUS_NONE && lookat ) { + if ( DistanceTo ( lookat ) < focusRange * (move.fl.moving?2.0f:1.0f) && CheckFOV ( lookat->GetPhysics()->GetOrigin() ) ) { + SetFocus ( newfocus, 1000 ); + } + } + } + + // Make sure cover look wasnt invalidated + if ( focusType == AIFOCUS_COVERLOOK && !aasSensor->Look() ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_COVER && !aasSensor->Reserved ( ) ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_HELPER && !helperCurrent ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_TETHER && !tether ) { + focusType = AIFOCUS_NONE; + } + + // Update focus type to none when the focus time runs out + if ( focusType != AIFOCUS_NONE ) { + if ( gameLocal.time > focusTime ) { + focusType = AIFOCUS_NONE; + } + } + + // Calculate the focus position + if ( focusType == AIFOCUS_NONE ) { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } else if ( focusType == AIFOCUS_COVER ) { + currentFocusPos = GetEyePosition() + aasSensor->Reserved()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_COVERLOOK ) { + currentFocusPos = GetEyePosition() + aasSensor->Look()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_ENEMY ) { + currentFocusPos = enemy.lastVisibleEyePosition; + } else if ( focusType == AIFOCUS_HELPER ) { + currentFocusPos = helperCurrent->GetPhysics()->GetOrigin() + helperCurrent->GetDirection ( this ); + } else if ( focusType == AIFOCUS_TETHER ) { + currentFocusPos = GetEyePosition() + tether->GetPhysics()->GetAxis()[0] * 64.0f; + } else { + idEntity* focusEnt = NULL; + switch ( focusType ) { + case AIFOCUS_LEADER: focusEnt = leader; break; + case AIFOCUS_PLAYER: focusEnt = gameLocal.GetLocalPlayer(); break; + case AIFOCUS_TALK: focusEnt = talkTarget; break; + case AIFOCUS_TARGET: focusEnt = lookTarget; break; + } + if ( focusEnt ) { + if ( focusEnt->IsType ( idActor::GetClassType ( ) ) ) { + currentFocusPos = static_cast(focusEnt)->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + currentFocusPos = focusEnt->GetPhysics()->GetOrigin(); + } + } else { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } + } + + //draw it so we can see what they think they're looking at! + if ( DebugFilter(ai_debugMove) ) { // Cyan & Blue = currentFocusPos + if ( focusType != AIFOCUS_NONE ) { + gameRenderWorld->DebugArrow( colorCyan, GetEyePosition(), currentFocusPos, 0 ); + } else { + gameRenderWorld->DebugArrow( colorBlue, GetEyePosition(), currentFocusPos, 0 ); + } + } +} + +/* +===================== +idAI::UpdateStates +===================== +*/ +void idAI::UpdateStates ( void ) { + MEM_SCOPED_TAG(tag,MA_DEFAULT); + + // Continue updating tactical state if for some reason we dont have one + if ( !aifl.dead && !aifl.scripted && !aifl.action && stateThread.IsIdle ( ) && aifl.scriptedEndWithIdle ) { + UpdateTactical ( 0 ); + } else { + UpdateState(); + } + + // clear the hit enemy flag so we catch the next time we hit someone + aifl.hitEnemy = false; + + if ( move.fl.allowHiddenMove || !IsHidden() ) { + // update the animstate if we're not hidden + UpdateAnimState(); + } +} + +/* +===================== +idAI::OnStateChange +===================== +*/ +void idAI::OnStateChange ( int channel ) { +} + +/* +===================== +idAI::OverrideFlag +===================== +*/ +void idAI::OverrideFlag ( aiFlagOverride_t flag, bool value ) { + bool oldValue; + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: oldValue = disablePain; break; + case AIFLAGOVERRIDE_DAMAGE: oldValue = fl.takedamage; break; + case AIFLAGOVERRIDE_NOTURN: oldValue = move.fl.noTurn; break; + case AIFLAGOVERRIDE_NOGRAVITY: oldValue = move.fl.noGravity; break; + default: + return; + } + + if ( oldValue == value ) { + return; + } + + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + + if ( (flagOverrides & flagOverride) && !!(flagOverrides & flagOverrideValue) == value ) { + flagOverrides &= ~flagOverride; + } else { + if ( oldValue ) { + flagOverrides |= flagOverrideValue; + } else { + flagOverrides &= ~flagOverrideValue; + } + flagOverrides |= flagOverride; + } + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/* +===================== +idAI::RestoreFlag +===================== +*/ +void idAI::RestoreFlag ( aiFlagOverride_t flag ) { + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + bool value; + + if ( !(flagOverrides&flagOverride) ) { + return; + } + + value = !!(flagOverrides & flagOverrideValue); + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +===================== +idAI::ReactionTo +===================== +*/ +int idAI::ReactionTo( const idEntity *ent ) { + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::GetClassType() ) ) { + return ATTACK_IGNORE; + } + + if( combat.fl.ignoreEnemies ){ + return ATTACK_IGNORE; + } + + const idActor *actor = static_cast( ent ); + if ( actor->IsType( idPlayer::GetClassType() ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + // actors on different teams will always fight each other + if ( actor->team != team ) { + if ( actor->fl.notarget ) { + // don't attack on sight when attacker is notargeted + return ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE ; */ + } + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE; */ + } + + //FIXME: temporarily disabled monsters of same rank fighting because it's happening too much. + // monsters will fight when attacked by lower or equal ranked monsters. rank 0 never fights back. + //if ( rank && ( actor->rank <= rank ) ) { + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +idAI::AdjustHealthByDamage +===================== +*/ +void idAI::AdjustHealthByDamage ( int damage ) { + if ( aifl.undying ) { + return; + } + idActor::AdjustHealthByDamage ( damage ); + + if ( g_perfTest_aiUndying.GetBool() && health <= 0 ) { + //so we still take pain! + health = 1; + } +} + +/* +===================== +idAI::Pain +===================== +*/ +bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + aifl.pain = idActor::Pain( inflictor, attacker, damage, dir, location ); + aifl.damage = true; + + // force a blink + blink_time = 0; + + // No special handling if friendly fire + // jshepard: friendly fire will cause pain. Players will only be able to pain buddy marines + // via splash damage. + +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return aifl.pain; + } + } +*/ + + // ignore damage from self + if ( attacker != this ) { + // React to taking pain + ReactToPain ( attacker, damage ); + + pain.takenThisFrame += damage; + pain.lastTakenTime = gameLocal.time; + combat.tacticalPainTaken += damage; + + // If taken too much pain where we then skip the current destination + if ( combat.tacticalPainThreshold && combat.tacticalPainTaken > combat.tacticalPainThreshold ) { + if (team==AITEAM_STROGG && + (combat.tacticalMaskAvailable&AITACTICAL_COVER_BITS) && + IsType(rvAITactical::GetClassType()) && + ai_allowTacticalRush.GetBool() && + spawnArgs.GetBool("rushOnPain", "1")) { + + // clear any tether + tether = NULL; + + // change ranged distances + combat.attackRange[0] = 0.0f; + combat.attackRange[1] = 100.0f; // make them get really close + + // remove cover behavior + combat.tacticalMaskAvailable &= ~AITACTICAL_COVER_BITS; + + // add ranged behavior + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BITS; + } + ForceTacticalUpdate ( ); + return true; + } + + // If looping pain and a new paintype comes in we can stop the loop + if ( pain.loopEndTime && pain.loopType.Icmp ( painType ) ) { + pain.loopEndTime = 0; + pain.loopType = painType; + } + + ExecScriptFunction( funcs.damage ); + } + + // If we were hit by our enemy and our enemy isnt visible then + // force the enemy visiblity to be updated as if we saw him momentarily + if ( enemy.ent == attacker && !enemy.fl.visible ) { + UpdateEnemyPosition ( true ); + } + + return aifl.pain; +} + +/* +===================== +idAI::Killed +===================== +*/ +void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idAngles ang; + const char* modelDeath; + const idKeyValue* kv; + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( aifl.dead ) { + aifl.pain = true; + aifl.damage = true; + return; + } + + aifl.dead = true; + + // turn off my flashlight, if I had one + ProcessEvent( &AI_Flashlight, false ); + + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + // Hide surfaces on death + for ( kv = spawnArgs.MatchPrefix ( "deathhidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "deathhidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } + + // stop all voice sounds + StopSpeaking( true ); + + SetMoveType ( MOVETYPE_DEAD ); + + move.fl.noGravity = false; + move.fl.allowPushMovables = false; + aifl.scripted = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + + aiManager.AnnounceKill ( this, attacker, inflictor ); + aiManager.AnnounceDeath ( this, attacker ); + } + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( this ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + OnDeath(); + CheckDeathObjectives(); + + ClearEnemy(); + + // make monster nonsolid + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + + Unbind(); + if ( g_perfTest_aiNoRagdoll.GetBool() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else if( fl.quickBurn ){ + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else { + if ( StartRagdoll() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) { + // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + SetModel( modelDeath ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + } + } + + SetState ( "State_Killed" ); + + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while( kv ) { + idDict args; + idEntity *tEnt; + if( kv->GetValue() != "" ){ + args.Set( "classname", kv->GetValue() ); + args.Set( "origin", physicsObj.GetAbsBounds().GetCenter().ToString() ); + // Let items know that they are of the dropped variety + args.Set( "dropped", "1" ); + if (gameLocal.SpawnEntityDef( args, &tEnt )) { + if ( tEnt && tEnt->GetPhysics()) { //tEnt *should* be valid, but hey... + // magic/arbitrary number to give it some spin. Some constants used to ensure guns rarely fall standing up + tEnt->GetPhysics()->SetAngularVelocity( idVec3( (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f)); + } + } + } + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/*********************************************************************** + + Targeting/Combat + +***********************************************************************/ + +/* +===================== +idAI::Activate + +Notifies the script that a monster has been activated by a trigger or flashlight +===================== +*/ +void idAI::Activate( idEntity *activator ) { + idPlayer *player; + + // Set our tether? + if ( activator && activator->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(activator) ); + } + + if ( aifl.dead ) { + // ignore it when they're dead + return; + } + + // make sure he's not dormant + dormantStart = 0; + + aifl.activated = true; + if ( !activator || !activator->IsType( idPlayer::GetClassType() ) ) { + player = gameLocal.GetLocalPlayer(); + } else { + player = static_cast( activator ); + } + + if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) { + SetEnemy( player ); + } + + // If being activated by a spawner we need to attach to it + if ( activator && activator->IsType ( rvSpawner::GetClassType() ) ) { + rvSpawner* spawn = static_cast( activator ); + spawn->Attach ( this ); + SetSpawner ( spawn ); + } +} + +/* +===================== +idAI::TalkTo +===================== +*/ +void idAI::TalkTo( idActor *actor ) { + + // jshepard: the dead do not speak. + if ( aifl.dead ) + return; + + ExecScriptFunction( funcs.onclick ); + + // Cant talk when already talking + if ( aifl.action || IsSpeaking ( ) || !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_TALK ) ) { + return; + } + + + switch ( GetTalkState ( ) ) { + case TALK_OK: + if ( talkMessage >= TALKMSG_LOOP ) { + //MCG: requested change: stop responding after one loop + return; + } + // at least one second between talking to people + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_TALK, 2000 ); + + talkTarget = actor; + + // Move to the next talk message + talkMessage = (talkMessage_t)((int)talkMessage + 1); + + // Loop until we find a valid talk message + while ( 1 ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Try the lipsync for the specific passive prefix first + if ( Speak ( va("lipsync_%stalk_%s", passive.prefix.c_str(), postfix.c_str() ) ) ) { + break; + } + // Try the generic lipsync for the current talk message + if ( Speak ( va("lipsync_talk_%s", postfix.c_str() ) ) ) { + break; + } + // If the first loop failed then there is no other options + if ( talkMessage == TALKMSG_LOOP ) { + return; + } else if ( talkMessage >= TALKMSG_LOOP ) { + talkMessage = TALKMSG_LOOP; + } else { + talkMessage = (talkMessage_t)((int)talkMessage + 1); + } + } + + // Start talking anim if we have one + if ( IsSpeaking ( ) ) { + passive.talkTime = speakTime; + } + + break; + + case TALK_FOLLOW: + if ( actor == leader ) { + leader = NULL; + Speak ( "lipsync_stopfollow", true ); + } else { + leader = actor; + Speak ( "lipsync_follow", true ); + } + break; + + case TALK_BUSY: + if ( talkBusyCount > 3 ) + {//only say a max of 4 lines + return; + } + talkBusyCount++; + //try to say this variant - if we can't, stop forever + if ( !Speak ( va("lipsync_busy_%d",talkBusyCount) ) ) + {//nevermore + talkBusyCount = 999; + } + break; + + case TALK_WAIT: + //do nothing + break; + } +} + +/* +===================== +idAI::GetTalkState +===================== +*/ +talkState_t idAI::GetTalkState( void ) const { + if ( ( talkState != TALK_NEVER ) && aifl.dead ) { + return TALK_DEAD; + } + if ( IsHidden() ) { + return TALK_NEVER; + } + if ( (talkState == TALK_OK || talkState == TALK_FOLLOW) && aifl.scripted ) { + return TALK_BUSY; + } + return talkState; +} + +/* +===================== +idAI::TouchedByFlashlight +===================== +*/ +void idAI::TouchedByFlashlight( idActor *flashlight_owner ) { + if ( RespondToFlashlight() ) { + Activate( flashlight_owner ); + } +} + +/* +===================== +idAI::ClearEnemy +===================== +*/ +void idAI::ClearEnemy( bool dead ) { + // Dont bother if we dont have an enemy + if ( !enemy.ent ) { + return; + } + + if ( move.moveCommand == MOVE_TO_ENEMY ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + } else if ( !aifl.scripted + && move.moveCommand == MOVE_TO_COVER + && !move.fl.done + && aasSensor->Reserved() + && !IsBehindCover() ) { + //clear cover and stop move + aasSensor->Reserve( NULL ); + combat.coverValidTime = 0; + OnCoverInvalidated(); + ForceTacticalUpdate(); + StopMove( MOVE_STATUS_DONE ); + } + + enemyNode.Remove(); + enemy.ent = NULL; + enemy.fl.dead = dead; + combat.fl.seenEnemyDirectly = false; + + UpdateHelper ( ); +} + +/* +===================== +idAI::UpdateEnemyPosition +===================== +*/ +void idAI::UpdateEnemyPosition ( bool forceUpdate ) { + if( !enemy.ent || (!enemy.fl.visible && !forceUpdate) ) { + return; + } + + idActor* enemyActor = dynamic_cast(enemy.ent.GetEntity()); + idEntity* enemyEnt = static_cast(enemy.ent.GetEntity()); + + enemy.lastVisibleFromEyePosition = GetEyePosition ( ); + + // Update the enemy origin if it isnt locked + if ( !enemy.fl.lockOrigin ) { + enemy.lastKnownPosition = enemyEnt->GetPhysics()->GetOrigin(); + + if ( enemyActor ) { + enemy.lastVisibleEyePosition = enemyActor->GetEyePosition(); + enemy.lastVisibleChestPosition = enemyActor->GetChestPosition(); + } else { + enemy.lastVisibleEyePosition = enemy.ent->GetPhysics()->GetOrigin ( ); + enemy.lastVisibleChestPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + } + } +} + +/* +===================== +idAI::UpdateEnemy +===================== +*/ +void idAI::UpdateEnemy ( void ) { + predictedPath_t predictedPath; + + // If we lost our enemy then clear it out to be sure + if( !enemy.ent ) { + return; + } + + // Rest to not being in fov + enemy.fl.inFov = false; + + // Bail out if we arent queued to do a complex think + if ( aifl.simpleThink ) { + // Keep the fov flag up to date + if ( IsEnemyVisible ( ) ) { + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + } + } else { + bool oldVisible; + + // Cache current enemy visiblity so we can see if it changes + oldVisible = IsEnemyVisible ( ); + + // See if enemy still visible + UpdateEnemyVisibility ( ); + + // If our enemy isnt visible but is within aware range then we know where he is + if ( !IsEnemyVisible ( ) && DistanceTo ( enemy.ent ) < combat.awareRange ) { + UpdateEnemyPosition( true ); + } else { +/* + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertActor() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin() ).LengthSqr(); + if ( dist < Square( combat.earRange ) ) { + SetEnemyPosition(); + } + } +*/ + } + + // Update fov + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + + // Set enemy.visibleTime to the time when the visibility flag changed + if ( oldVisible != IsEnemyVisible ( ) ) { + // Just caught sight of the enemy after loosing him, delay the attack actions a bit + if ( !oldVisible && combat.attackSightDelay ) { + float delay; + if ( enemy.lastVisibleChangeTime ) { + // The longer the enemy has not been visible, the more we should delay + delay = idMath::ClampFloat ( 0.0f, 1.0f, (gameLocal.time - enemy.lastVisibleChangeTime) / AI_SIGHTDELAYSCALE ); + } else { + // The enemy has never been visible so delay the full amount + delay = 1.0f; + } + // Add to the ranged attack timer providing its not already running a timer + if ( actionTimerRangedAttack.IsDone ( actionTime) ) { + actionTimerRangedAttack.Clear ( actionTime ); + actionTimerRangedAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + // Add to the special attack timer providing its not already running a timer + if ( actionTimerSpecialAttack.IsDone ( actionTime ) ) { + actionTimerSpecialAttack.Clear ( actionTime ); + actionTimerSpecialAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + } + + enemy.lastVisibleChangeTime = gameLocal.time; + } + + // Handler for visibility change + if ( oldVisible != IsEnemyVisible ( ) ) { + OnEnemyVisiblityChange ( oldVisible ); + } + } + + // Adjust smoothed linear and pushed velocities for enemies + if ( enemy.fl.visible ) { + enemy.smoothedLinearVelocity += ((enemy.ent->GetPhysics()->GetLinearVelocity ( ) - enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedLinearVelocity) * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity += ((enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedPushedVelocity) * enemy.smoothVelocityRate ); + } else { + enemy.smoothedLinearVelocity -= (enemy.smoothedLinearVelocity * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity -= (enemy.smoothedPushedVelocity * enemy.smoothVelocityRate ); + } + + // Update enemy range + enemy.range = DistanceTo ( enemy.lastKnownPosition ); + enemy.range2d = DistanceTo2d ( enemy.lastKnownPosition ); + + // Calulcate the aggression scale + // Skill level 0: (1.0) + // Skill level 1: (1.0 - 1.25) + // Skill level 2: (1.0 - 1.5) + // Skill level 3: (1.0 - 1.75) + if ( combat.aggressiveRange > 0.0f ) { + combat.aggressiveScale = (g_skill.GetFloat() / MAX_SKILL_LEVELS); + combat.aggressiveScale *= 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, enemy.range / combat.aggressiveRange ); + combat.aggressiveScale += 1.0f; + } +} + +/* +===================== +idAI::LastKnownPosition +===================== +*/ +const idVec3& idAI::LastKnownPosition ( const idEntity *ent ) { + return (ent==enemy.ent)?(enemy.lastKnownPosition):(ent->GetPhysics()->GetOrigin()); +} + +/* +===================== +idAI::UpdateEnemyVisibility + +Update whether or not the AI can see its enemy or not and determine how. +===================== +*/ +void idAI::UpdateEnemyVisibility ( void ) { + enemy.fl.visible = false; + + // No enemy so nothing to see + if ( !enemy.ent ) { + return; + } + + // Update enemy visibility flag + enemy.fl.visible = CanSeeFrom ( GetEyePosition ( ), enemy.ent, false ); + + // IF the enemy isnt visible and not forcing an update we can just early out + if ( !enemy.fl.visible ) { + return; + } + + // If we are seeing our enemy for the first time after changing enemies, call him out + if ( enemy.lastVisibleTime < enemy.changeTime ) { + AnnounceNewEnemy( ); + } + + combat.fl.seenEnemyDirectly = true; + enemy.lastVisibleTime = gameLocal.time; + + // Update our known locations of the enemy since we just saw him + UpdateEnemyPosition ( ); +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +void idAI::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +rvSpawner* idAI::GetSpawner ( void ) { + return spawner; +} + +/* +===================== +idAI::SetEnemy +===================== +*/ +bool idAI::SetEnemy( idEntity *newEnemy ) { + idEntity* oldEnemy; + + // Look for obvious early out + if ( enemy.ent == newEnemy ) { + return true; + } + + // Cant set enemy if dead + if ( aifl.dead ) { + ClearEnemy ( false ); + return false; + } + + // Cant set enemy if the enemy is dead or has no target set + if ( newEnemy ) { + if ( newEnemy->health <= 0 || newEnemy->fl.notarget ) { + return false; + } + } + + oldEnemy = enemy.ent; + enemy.fl.dead = false; + enemy.fl.lockOrigin = false; + + if ( !newEnemy ) { + ClearEnemy ( false ); + } else { + // Set our current enemy + enemy.ent = newEnemy; + + // If the enemy is an actor then add to the actors enemy list + if( newEnemy->IsType( idActor::GetClassType() ) ){ + idActor* enemyActor = static_cast( newEnemy ); + enemyNode.AddToEnd( enemyActor->enemyList ); + } + } + + // Allow custom handling of enemy change + if ( enemy.ent != oldEnemy ) { + OnEnemyChange ( oldEnemy ); + } + + return true; +} + + +/* +=================== +idAI::CalculateAttackOffsets + +calculate joint positions on attack frames so we can do proper "can hit" tests +=================== +*/ +void idAI::CalculateAttackOffsets ( void ) { + const idDeclModelDef* modelDef; + int num; + int i; + int frame; + const frameCommand_t* command; + idMat3 axis; + const idAnim* anim; + jointHandle_t joint; + + modelDef = animator.ModelDef(); + if ( !modelDef ) { + return; + } + num = modelDef->NumAnims(); + + // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim + animator.RemoveOriginOffset( false ); + + // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for + // launch offsets so that anim number can be used without subtracting 1. + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num + 1 ); + attackAnimInfo[ 0 ].attackOffset.Zero(); + attackAnimInfo[ 0 ].eyeOffset.Zero(); + + for( i = 1; i <= num; i++ ) { + attackAnimInfo[ i ].attackOffset.Zero(); + attackAnimInfo[ i ].eyeOffset.Zero(); + anim = modelDef->GetAnim( i ); + if ( !anim ) { + continue; + } + + frame = anim->FindFrameForFrameCommand( FC_AI_ATTACK, &command ); + if ( frame >= 0 ) { + joint = animator.GetJointHandle( command->joint->c_str() ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Invalid joint '%s' on 'ai_attack' frame command on frame %d of model '%s'", command->joint->c_str(), frame, modelDef->GetName() ); + } + GetJointTransformForAnim( joint, i, FRAME2MS( frame ), attackAnimInfo[ i ].attackOffset, axis ); + + if ( chestOffsetJoint!= INVALID_JOINT ) { + GetJointTransformForAnim( chestOffsetJoint, i, FRAME2MS( frame ), attackAnimInfo[ i ].eyeOffset, axis ); + } + } + } + + animator.RemoveOriginOffset( true ); +} + +/* +===================== +idAI::CreateProjectileClipModel +===================== +*/ +void idAI::CreateProjectileClipModel( void ) const { + if ( projectileClipModel == NULL ) { + idBounds projectileBounds( vec3_origin ); + projectileBounds.ExpandSelf( 2.0f ); // projectileRadius ); + projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); + } +} + +/* +===================== +idAI::GetPredictedAimDirOffset +===================== +*/ +void idAI::GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const { + float a; + float b; + float c; + float d; + float t; + idVec3 r; + + // Make sure there is something to predict + if ( targetVelocity.LengthSqr ( ) == 0.0f ) { + offset.Zero ( ); + return; + } + + // Solve for (t): magnitude(targetVelocity * t + target - source) = projectileSpeed * t + + r = (target-source); + a = (targetVelocity * targetVelocity) - Square(projectileSpeed); + b = (targetVelocity * 2.0f) * r; + c = r*r; + d = b*b - 4*a*c; + t = 0.0f; + + if ( d >= 0.0f ) { + float t1; + float t2; + float denom; + d = idMath::Sqrt(d); + denom = 1.0f / (2.0f * a); + t1 = (-b + d) * denom; + t2 = (-b - d) * denom; + if ( t1 < 0.0f && t2 < 0.0f ) { + t = 0.0f; + } else if ( t1 < 0.0f ) { + t = t2; + } else if ( t2 < 0.0f ) { + t = t1; + } else { + t = Min(t1,t2); + } + } + + offset = targetVelocity * t; +} + +/* +===================== +idAI::GetAimDir +===================== +*/ +bool idAI::GetAimDir( + const idVec3& firePos, + const idEntity* target, + const idDict* projectileDef, + idEntity* ignore, + idVec3& aimDir, + float aimOffset, + float predict + ) const +{ + idVec3 targetPos1; + idVec3 targetPos2; + idVec3 targetLinearVel; + idVec3 targetPushedVel; + idVec3 delta; + float max_height; + bool result; + idEntity* targetEnt = const_cast(target); + + // if no aimAtEnt or projectile set + if ( !targetEnt ) { + aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + return false; + } + + float projectileSpeed = idProjectile::GetVelocity ( projectileDef ).LengthFast ( ); + idVec3 projectileGravity = idProjectile::GetGravity ( projectileDef ); + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + if ( targetEnt == enemy.ent ) { + targetPos2 = enemy.lastVisibleEyePosition; + targetPos1 = enemy.lastVisibleChestPosition; + targetPushedVel = enemy.smoothedPushedVelocity; + targetLinearVel = enemy.smoothedLinearVelocity; + } else if ( targetEnt->IsType( idActor::GetClassType() ) ) { + targetPos2 = static_cast(targetEnt)->GetEyePosition ( ); + targetPos1 = static_cast(targetEnt)->GetChestPosition ( ); + targetPushedVel = target->GetPhysics()->GetPushedLinearVelocity ( ); + targetLinearVel = (target->GetPhysics()->GetLinearVelocity ( ) - targetPushedVel); + } else { + targetPos1 = targetEnt->GetPhysics()->GetAbsBounds().GetCenter(); + targetPos2 = targetPos1; + targetPushedVel.Zero ( ); + targetLinearVel.Zero ( ); + } + + // Target prediction + if ( projectileSpeed == 0.0f ) { + // Hitscan prediction actually causes the hitscan to miss unless it is predicted. + delta = (targetLinearVel * (-1.0f + predict)); + } else { + // Projectile prediction must figure out how far to lead the shot to hit the target + GetPredictedAimDirOffset ( firePos, targetPos1, projectileSpeed, (targetLinearVel*predict)+targetPushedVel, delta ); + } + targetPos1 += delta; + targetPos2 += delta; + + result = false; + + // try aiming for chest + delta = targetPos1 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos1 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + if ( result || !targetEnt->IsType( idActor::GetClassType() ) ) { + return result; + } + } + + // try aiming for head + delta = targetPos2 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos2 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + } + + return result; +} + +/* +===================== +idAI::CreateProjectile +===================== +*/ +idProjectile *idAI::CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ) { + idEntity* ent; + const char* clsname; + + if ( !projectile.GetEntity() ) { + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + clsname = projectileDict->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + projectile = (idProjectile*)ent; + } + + projectile.GetEntity()->Create( this, pos, dir ); + + return projectile.GetEntity(); +} + +/* +===================== +idAI::RemoveProjectile +===================== +*/ +void idAI::RemoveProjectile( void ) { + if ( projectile.GetEntity() ) { + projectile.GetEntity()->PostEventMS( &EV_Remove, 0 ); + projectile = NULL; + } +} + +/* +===================== +idAI::Attack +===================== +*/ +bool idAI::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + // Get the attack dictionary + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", attackName ) ), false ); + if ( !attackDict ) { + gameLocal.Error ( "could not find attack entityDef 'def_attack_%s (%s)' on AI entity %s", attackName, spawnArgs.GetString ( va("def_attack_%s", attackName ) ), GetName ( ) ); + } + + // Melee Attack? + if ( spawnArgs.GetBool ( va("attack_%s_melee", attackName ), "0" ) ) { + return AttackMelee ( attackName, attackDict ); + } + + // Ranged attack (hitscan or projectile)? + return ( AttackRanged ( attackName, attackDict, joint, target, pushVelocity ) != NULL ); +} + +/* +===================== +idAI::AttackRanged +===================== +*/ +idProjectile* idAI::AttackRanged ( + const char* attackName, + const idDict* attackDict, + jointHandle_t joint, + idEntity* target, + const idVec3& pushVelocity + ) +{ + float attack_accuracy; + float attack_cone; + float attack_spread; + int attack_count; + bool attack_hitscan; + float attack_predict; + float attack_pullback; + int i; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + idVec3 dir; + idAngles ang; + idMat3 axis; + idProjectile* lastProjectile; + + lastProjectile = NULL; + + // Generic attack properties + attack_accuracy = spawnArgs.GetFloat ( va("attack_%s_accuracy", attackName ), "7" ); + attack_cone = spawnArgs.GetFloat ( va("attack_%s_cone", attackName ), "75" ); + attack_spread = DEG2RAD ( spawnArgs.GetFloat ( va("attack_%s_spread", attackName ), "0" ) ); + attack_count = spawnArgs.GetInt ( va("attack_%s_count", attackName ), "1" ); + attack_hitscan = spawnArgs.GetBool ( va("attack_%s_hitscan", attackName ), "0" ); + attack_predict = spawnArgs.GetFloat ( va("attack_%s_predict", attackName ), "0" ); + attack_pullback = spawnArgs.GetFloat ( va("attack_%s_pullback", attackName ), "0" ); + + // Get the muzzle origin and axis from the given launch joint + GetMuzzle( joint, muzzleOrigin, muzzleAxis ); + if ( attack_pullback ) + { + muzzleOrigin -= muzzleAxis[0]*attack_pullback; + } + + // set aiming direction + bool calcAim = true; + if ( GetEnemy() && GetEnemy() == target && GetEnemy() == gameLocal.GetLocalPlayer() && spawnArgs.GetBool( va("attack_%s_missFirstShot",attackName) ) ) { + //purposely miss + if ( gameLocal.random.RandomFloat() < 0.5f ) { + //actually, hit anyway... + } else { + idVec3 targetPos = enemy.lastVisibleEyePosition; + idVec3 eFacing; + idVec3 left, up; + + up.Set( 0, 0, 1 ); + left = viewAxis[0].Cross(up); + + if ( GetEnemy()->IsType( idActor::GetClassType() ) ) + { + eFacing = ((idActor*)GetEnemy())->viewAxis[0]; + } + else + { + eFacing = GetEnemy()->GetPhysics()->GetAxis()[0]; + } + if ( left*eFacing > 0 ) + { + targetPos += left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + else + { + targetPos -= left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + dir = targetPos-muzzleOrigin; + dir.Normalize(); + attack_accuracy = 0; + + calcAim = false; + //don't miss next time + spawnArgs.SetBool( va("attack_%s_missFirstShot",attackName), false ); + } + } + if ( calcAim ) { + if ( target && !spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) { + GetAimDir( muzzleOrigin, target, attackDict, this, dir, + spawnArgs.GetFloat ( va("attack_%s_aimoffset", attackName )), + attack_predict ); + } else { + dir = muzzleAxis[0]; + if ( spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) + { + if ( spawnArgs.GetBool( va("attack_%s_traceToNextJoint", attackName ), "0" ) ) + { + jointHandle_t endJoint = animator.GetFirstChild( joint ); + if ( endJoint != INVALID_JOINT && endJoint != joint ) + { + idVec3 endJointPos; + idMat3 blah; + GetJointWorldTransform( endJoint, gameLocal.GetTime(), endJointPos, blah ); + dir = endJointPos-muzzleOrigin; + //ARGHH: need to be able to set range!!! + //const_cast(attackDict)->SetFloat( "range", dir.Normalize() );//NOTE: yes, I intentionally normalize here as well as use the result for length... + dir.Normalize(); + } + } + } + } + } + + // random accuracy + ang = dir.ToAngles(); + ang.pitch += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + ang.yaw += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + + // Lock attacks to a cone? + if ( attack_cone ) { + float diff; + // clamp the attack direction to be within monster's attack cone so he doesn't do + // things like throw the missile backwards if you're behind him + diff = idMath::AngleDelta( ang.yaw, move.current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = move.current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = move.current_yaw - attack_cone; + } + } + + ang.Normalize360 ( ); + axis = ang.ToMat3(); + + for( i = 0; i < attack_count; i++ ) { + float angle; + float spin; + + // spread the projectiles out + angle = idMath::Sin( attack_spread * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( attack_hitscan ) { + gameLocal.HitScan( *attackDict, muzzleOrigin, dir, muzzleOrigin, this, false, combat.aggressiveScale ); + } else { + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( attackDict, muzzleOrigin, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzleOrigin, dir, pushVelocity, 0.0f, combat.aggressiveScale ); + + // Let the script manage projectiles if need be + ExecScriptFunction ( funcs.launch_projectile, lastProjectile ); + + projectile = NULL; + } + } + + lastAttackTime = gameLocal.time; + + // If shooting at another ai entity then kick off an attack reaction + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) ) { + static_cast(enemy.ent.GetEntity())->ReactToShotAt ( this, muzzleOrigin, axis[0] ); + } + + return lastProjectile; +} + +/*s +================ +idAI::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( ( victim == this ) && inflictor->IsType( idProjectile::GetClassType() ) ) { + // monsters only get half damage from their own projectiles + damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage + } else if ( victim == enemy.ent ) { + aifl.hitEnemy = true; + } +} + +/* +===================== +idAI::DirectDamage + +Causes direct damage to an entity + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + +// ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ); + ent->Damage( this, this, globalKickDir, meleeDefName, damageScale, NULL ); +} + +/* +===================== +idAI::TestMelee +===================== +*/ +bool idAI::TestMelee( void ) const { + trace_t trace; + idEntity* enemyEnt = enemy.ent; + + if ( !enemyEnt || !combat.meleeRange ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( DebugFilter(ai_debugMove) ) { //YELLOW = Test Melee Bounds + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +/* +===================== +idAI::AttackMelee + +jointname allows the endpoint to be exactly specified in the model, +as for the commando tentacle. If not specified, it will be set to +the facing direction + combat.meleeRange. + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +bool idAI::AttackMelee ( const char *attackName, const idDict* meleeDict ) { + idEntity* enemyEnt = enemy.ent; + const char* p; + const idSoundShader* shader; + + if ( !enemyEnt ) { + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // check for the "saving throw" automatic melee miss on lethal blow + // stupid place for this. + bool forceMiss = false; + if ( enemyEnt->IsType( idPlayer::GetClassType() ) && g_skill.GetInteger() < 2 ) { + int damage, armor; + idPlayer *player = static_cast( enemyEnt ); + player->CalcDamagePoints( this, this, meleeDict, 1.0f, INVALID_JOINT, &damage, &armor ); + + if ( enemyEnt->health <= damage ) { + int t = gameLocal.time - player->lastSavingThrowTime; + if ( t > SAVING_THROW_TIME ) { + player->lastSavingThrowTime = gameLocal.time; + t = 0; + } + if ( t < 1000 ) { + forceMiss = true; + } + } + } + + // make sure the trace can actually hit the enemy + if ( forceMiss || !TestMelee( ) ) { + // missed + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // do the damage + // + p = meleeDict->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDict->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + // This allows some AI to be soft against melee-- most marines are selfMeleeDamageScale of 3, which means they take 3X from melee attacks! + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ) * enemyEnt->spawnArgs.GetFloat ( "selfMeleeDamageScale", "1" ); + + //if attacker is a melee superhero, damageScale is way increased + idAI* enemyAI = static_cast(enemyEnt); + if( aifl.meleeSuperhero) { + if( damageScale >=1 ) { + damageScale *= 6; + } else { + damageScale = 6; + } + } + + //if the defender is a melee superhero, damageScale is way decreased + if( enemyAI->aifl.meleeSuperhero) { + damageScale = 0.5f; + } + + int location = INVALID_JOINT; + if ( enemyEnt->IsType ( idAI::Type ) ) { + location = static_cast(enemyEnt)->chestOffsetJoint; + } + enemyEnt->Damage( this, this, globalKickDir, meleeDict->GetString ( "classname" ), damageScale, location ); + + if ( meleeDict->GetString( "fx_impact", NULL ) ) { + if ( enemyEnt == gameLocal.GetLocalPlayer() ) { + idPlayer *ePlayer = static_cast(enemyEnt); + if ( ePlayer ) { + idVec3 dir = ePlayer->firstPersonViewOrigin-GetEyePosition(); + dir.Normalize(); + idVec3 org = ePlayer->firstPersonViewOrigin + (dir * -((gameLocal.random.RandomFloat()*8.0f)+8.0f) ); + idAngles ang = ePlayer->firstPersonViewAxis.ToAngles() * -1; + idMat3 axis = ang.ToMat3(); + gameLocal.PlayEffect( *meleeDict, "fx_impact", org, viewAxis ); + } + } + } + + lastAttackTime = gameLocal.time; + + return true; +} + +/* +================ +idAI::PushWithAF +================ +*/ +void idAI::PushWithAF( void ) { + int i, j; + afTouch_t touchList[ MAX_GENTITIES ]; + idEntity *pushed_ents[ MAX_GENTITIES ]; + idEntity *ent; + idVec3 vel; + int num_pushed; + + num_pushed = 0; + af.ChangePose( this, gameLocal.time ); + int num = af.EntitiesTouchingAF( touchList ); + for( i = 0; i < num; i++ ) { + if ( touchList[ i ].touchedEnt->IsType( idProjectile::GetClassType() ) ) { + // skip projectiles + continue; + } + + // make sure we havent pushed this entity already. this avoids causing double damage + for( j = 0; j < num_pushed; j++ ) { + if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) { + break; + } + } + if ( j >= num_pushed ) { + ent = touchList[ i ].touchedEnt; + pushed_ents[num_pushed++] = ent; + vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); + vel.Normalize(); + ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); + } + } +} + +/*********************************************************************** + + Misc + +***********************************************************************/ + +/* +================ +idAI::GetMuzzle +================ +*/ +void idAI::GetMuzzle( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ) { + if ( joint == INVALID_JOINT ) { + muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; + muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; + } else { + GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); + //MCG + //pull the muzzle back inside the bounds if possible + //FIXME: this is nasty, we should just be able to check for starting in solid and register that as a hit! But... + /* + float scale = 0.0f; + if ( physicsObj.GetBounds().RayIntersection( muzzle, -axis[0], scale ) ) + { + if ( scale != 0.0f ) + {//not already inside + muzzle += scale * -axis[0]; + } + } + else + {//just pull it back a little anyway? + idVec3 xyOfs = muzzle-physicsObj.GetOrigin(); + xyOfs.z = 0; + muzzle += xyOfs.Length() * -axis[0]; + } + */ + } +} + +/* +================ +idAI::Hide +================ +*/ +void idAI::Hide( void ) { + idActor::Hide(); + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + StopSound( SND_CHANNEL_AMBIENT, false ); + + enemy.fl.inFov = false; + enemy.fl.visible = false; + + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::Show +================ +*/ +void idAI::Show( void ) { + idActor::Show(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + + physicsObj.GetClipModel()->Link(); + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); +} + +/* +================ +idAI::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idAI::CanPlayChatterSounds( void ) const { + if ( aifl.dead ) { + return false; + } + + if ( IsHidden() ) { + return false; + } + + if ( enemy.ent ) { + return true; + } + + if ( spawnArgs.GetBool( "no_idle_chatter" ) ) { + return false; + } + + return true; +} + +/* +===================== +idAI::UpdateChatter +===================== +*/ +void idAI::UpdateChatter ( void ) { + int chatterRate; + const char* chatter; + + // No chatter? + if ( !chatterRateIdle && !chatterRateCombat ) { + return; + } + + // check if it's time to play a chat sound + if ( IsHidden() || !aifl.awake || aifl.dead || ( chatterTime > gameLocal.time ) ) { + return; + } + + // Skip first chatter + if ( enemy.ent ) { + chatter = "lipsync_chatter_combat"; + chatterRate = chatterRateCombat; + } else { + chatter = "lipsync_chatter_idle"; + chatterRate = chatterRateIdle; + } + + // Can chatter ? + if ( !chatterRate ) { + return; + } + + // Start chattering, but not if he's already speaking. And he might already be speaking because he was scripted to do so. + if ( chatterTime > 0 && !IsSpeaking() ) { + Speak ( chatter, true ); + } + + // set the next chat time + chatterTime = gameLocal.time + chatterRate + (gameLocal.random.RandomFloat() * chatterRate * 0.5f) - (chatterRate * 0.25f); +} + +/* +===================== +idAI::HeardSound +===================== +*/ +idEntity *idAI::HeardSound( int ignore_team ){ + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertActor(); + if ( actor && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { + idVec3 pos = actor->GetPhysics()->GetOrigin(); + idVec3 org = physicsObj.GetOrigin(); + float dist = ( pos - org ).LengthSqr(); + + if ( dist < Square( combat.earRange ) ) { + //really close? + if ( dist < Square( combat.earRange/4.0f ) ) { + return actor; + //possible LOS + } else if ( dist < Square( combat.visRange * 2.0f ) && CanSee( actor, false ) ) { + return actor; + } else if ( combat.fl.aware ) { + //FIXME: or, maybe find cover/hide/ambush spot and wait to ambush them? + //don't have an enemy and not tethered to a position + } else if ( !GetEnemy() && !tether ) { + //go into search mode + WanderAround(); + move.fl.noRun = false; + move.fl.idealRunning = true; + //undid this: was causing them to not be able to do fine nav. + //move.fl.noWalk = true; + } + } + } + + return NULL; +} + +/* +============ +idAI::SetLeader +============ +*/ +void idAI::SetLeader ( idEntity *newLeader ) { + idEntity* oldLeader = leader; + + if( !newLeader ){ + leader = NULL; + } else if ( !newLeader->IsType( idActor::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", newLeader->name.c_str() ); + } else { + leader = static_cast( newLeader ); + } + + if ( oldLeader != leader ) { + OnLeaderChange ( oldLeader ); + } +} + + + +/*********************************************************************** + + Head & torso aiming + +***********************************************************************/ + +/* +================ +idAI::UpdateAnimationControllers +================ +*/ +bool idAI::UpdateAnimationControllers( void ) { + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + float currentHeadFocusRate = ( combat.fl.aware ? headFocusRate : headFocusRate * 0.5f ); + + MEM_SCOPED_TAG(tag,MA_ANIM); + + if ( aifl.dead ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = move.current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. + // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which + // are already up to date because of getting the joints in this function) and then sets their positions, which + // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, + // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no + // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number + // in order to see how many times an entity transforms the joints per frame. + idActor::UpdateAnimationControllers(); + + // Update the focus position + UpdateFocus( orientationJointAxis ); + + //MCG NOTE: don't know why Dube added this extra check for the torsoAnim (5/09/05), but it was causing popping, so I took it out... :/ + //bool canLook = ( !torsoAnim.AnimDone(0) || !torsoAnim.GetAnimator()->CurrentAnim(ANIMCHANNEL_TORSO)->IsDone(gameLocal.GetTime()) || torsoAnim.Disabled() ) && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook; + //bool canLook = (!torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook); + + bool canLook = (!animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look && !aifl.disableLook); + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + currentFocusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 64.0f; + } + + // Determine the new look yaw + dir = currentFocusPos - orientationJointPos; + newLookAng.yaw = dir.ToYaw( ); + + // Determine the new look pitch + dir = currentFocusPos - GetEyePosition(); + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + newLookAng.roll = 0.0f; + + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + newLookAng.yaw = orientationJointAxis[0].ToYaw(); + newLookAng.pitch = 0; + } + // Add the angle change using the head focus rate and convert the look angles to + // local angles so they can be properly clamped. + float f = lookAng.yaw; + if ( lookMin.yaw <= -360.0f && lookMax.yaw >= 360.0f ) + { + lookAng.yaw = f - orientationJointYaw; + float diff; + diff = ( idMath::AngleNormalize360(newLookAng.yaw) - idMath::AngleNormalize360(f) ); + diff = idMath::AngleNormalize180( diff ); + lookAng.yaw += diff * currentHeadFocusRate; + //lookAng.yaw += ( newLookAng.yaw - f ) * currentHeadFocusRate; + } + else + { + lookAng.yaw = idMath::AngleNormalize180 ( f - orientationJointYaw ); + lookAng.yaw += (idMath::AngleNormalize180 ( newLookAng.yaw - f ) * currentHeadFocusRate); + } + lookAng.pitch += ( idMath::AngleNormalize180( newLookAng.pitch - lookAng.pitch ) * currentHeadFocusRate ); + + // Clamp the look angles + lookAng.Clamp( lookMin, lookMax ); + + // Calcuate the eye angles + f = eyeAng.yaw; + eyeAng.yaw = idMath::AngleNormalize180( f - orientationJointYaw ); + eyeAng.yaw += (idMath::AngleNormalize180( newLookAng.yaw - f ) * eyeFocusRate); + eyeAng.pitch += (idMath::AngleNormalize180( newLookAng.pitch - eyeAng.pitch ) * eyeFocusRate); + + // Clamp eye angles relative to the look angles + jointAng = eyeAng - lookAng; + jointAng.Normalize180( ); + jointAng.Clamp( eyeMin, eyeMax ); + eyeAng = lookAng + jointAng; + eyeAng.Normalize180( ); + + if ( canLook ) { + // Apply the look angles to the look joints + if ( animator.GetAnimFlags( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimNum()).ai_look_head_only ) { + if ( neckJoint != INVALID_JOINT || headJoint != INVALID_JOINT ) { + float jScale = 1.0f; + if ( neckJoint != INVALID_JOINT && headJoint != INVALID_JOINT ) { + jScale = 0.5f; + } + jointAng.pitch = lookAng.pitch * jScale; + jointAng.yaw = lookAng.yaw * jScale; + if ( neckJoint != INVALID_JOINT ) { + animator.SetJointAxis( neckJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + if ( headJoint != INVALID_JOINT ) { + animator.SetJointAxis( headJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + //what if we have a previous joint mod on the rest of the lookJoints + //from an anim that *wasn't* ai_look_head_only...? + //just clear them? Or move them towards 0? + //FIXME: move them towards zero... + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + } + } else { + //blend back + //yes, this is framerate dependant and inefficient and wrong, but... eliminates pops + jointAng.roll = 0.0f; + jointMod_t *curJointMod; + idAngles curJointAngleMod; + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //blend back + curJointMod = animator.FindExistingJointMod( lookJoints[ i ], NULL ); + if ( curJointMod ) + { + curJointAngleMod = curJointMod->mat.ToAngles(); + curJointAngleMod *= 0.75f; + if ( fabs(curJointAngleMod.pitch) < 1.0f + && fabs(curJointAngleMod.yaw) < 1.0f + && fabs(curJointAngleMod.roll) < 1.0f ) + { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + else + { + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, curJointAngleMod.ToMat3() ); + } + } + } + } + } + } else { + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } else { + //if ( animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look || aifl.disableLook ) { + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + animator.ClearJoint ( lookJoints[i] ); + } + } else { + //PCJ back to neutral + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } + + // Convert the look angles back to world angles. This is done to prevent dramatic orietation changes + // from changing the look direction abruptly (unless of course the minimums are not met) + lookAng.yaw = idMath::AngleNormalize180( lookAng.yaw + orientationJointYaw ); + eyeAng.yaw = idMath::AngleNormalize180( eyeAng.yaw + orientationJointYaw ); + + if ( move.moveType == MOVETYPE_FLY || move.fl.flyTurning ) { + // lean into turns + AdjustFlyingAngles(); + } + + // Orient the eyes towards their target + if ( leftEyeJoint != INVALID_JOINT && rightEyeJoint != INVALID_JOINT ) { + if ( head ) { + idAnimator *headAnimator = head->GetAnimator(); + + if ( focusType != AIFOCUS_NONE && allowEyeFocus && canLook ) { + //tweak these since it's looking at the wrong spot and not calculating from each eye and I don't have time to bother rewriting all of this properly + eyeAng.yaw -= 0.5f; + eyeAng.pitch += 3.25f; + idMat3 eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + eyeAng.yaw += 3.0f; + eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + if ( ai_debugEyeFocus.GetBool() ) { + idVec3 eyeOrigin; + idMat3 axis; + + head->GetJointWorldTransform ( rightEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + + head->GetJointWorldTransform ( leftEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + } + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus && focusType != AIFOCUS_NONE && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook ) { + idMat3 eyeAxis = ( eyeAng ).ToMat3() * GetPhysics()->GetAxis().Transpose ( ); + animator.SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + animator.SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + } + + return true; +} + +void idAI::OnTouch( idEntity *other, trace_t *trace ) { + // if we dont have an enemy or had one for at least a second that is a potential enemy the set the enemy + if ( other->IsType( idActor::GetClassType() ) + && !other->fl.notarget + && ( ReactionTo( other )&ATTACK_ON_SIGHT) + && (!enemy.ent || gameLocal.time - enemy.changeTime > 1000 ) ) { + SetEnemy( other ); + } + + if ( !enemy.ent && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + } + pusher = other; + aifl.pushed = true; + + // If pushed by the player update tactical + if ( pusher && pusher->IsType ( idPlayer::GetClassType() ) && (combat.tacticalMaskAvailable & AITACTICAL_MOVE_PLAYERPUSH_BIT) ) { + ForceTacticalUpdate ( ); + } +} + +idProjectile* idAI::AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ) { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel* projClip; + idMat3 axis; + float distance; + idProjectile* result; + + if ( !projectileDict ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + axis = ang.ToMat3(); + if ( !projectile.GetEntity() ) { + CreateProjectile( projectileDict, org, axis[ 0 ] ); + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); +// RAVEN END + + // launch the projectile + projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); + result = projectile; + projectile = NULL; + + lastAttackTime = gameLocal.time; + + return result; +} + +void idAI::RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { + jointHandle_t joint; + idVec3 org; + idMat3 axis; + + if ( !jointname || !jointname[ 0 ] ) { + org = physicsObj.GetOrigin(); + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, org, axis ); + } + + gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); +} + +/* +===================== +idAI::CanBecomeSolid + +returns true if the AI entity could become solid at its current position +===================== +*/ +bool idAI::CanBecomeSolid ( void ) { + int i; + int num; + idEntity * hit; + idClipModel* cm; + idClipModel* clipModels[ MAX_GENTITIES ]; + + // Determine what we are currently touching +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.ClipModelsTouchingBounds( this, physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + return false; + } + } + + return true; +} + +/* +===================== +idAI::BecomeSolid +===================== +*/ +void idAI::BecomeSolid( void ) { + physicsObj.EnableClip(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + physicsObj.GetClipModel()->Link(); +// RAVEN END + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); +} + +/* +===================== +idAI::BecomeNonSolid +===================== +*/ +void idAI::BecomeNonSolid( void ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); +} + +const char *idAI::ChooseAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + return head.GetEntity()->GetAnimator()->AnimFullName( anim ); + } + } else { + return animator.AnimFullName( anim ); + } + } + + return ""; +} + +/* +============ +idAI::ExecScriptFunction +============ +*/ +void idAI::ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm ) { + if( parm ) { + func.InsertEntity( parm, 0 ); + } else { + func.InsertEntity( this, 0 ); + } + + func.CallFunc( &spawnArgs ); + + if( parm ) { + func.RemoveIndex( 0 ); + } +} + +/* +============ +idAI::Prethink +============ +*/ +void idAI::Prethink ( void ) { + // Update our helper if we are moving + if ( move.fl.moving ) { + UpdateHelper ( ); + } + + if ( leader ) { + idEntity* groundEnt = leader->GetGroundEntity ( ); + if ( !(tether && !aifl.tetherMover) && groundEnt ) { + if ( groundEnt->IsType ( idMover::GetClassType ( ) ) ) { + idEntity* ent; + idEntity* next; + for( ent = groundEnt->GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == groundEnt && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(ent) ); + aifl.tetherMover = true; + break; + } + } + } else { + SetTether ( NULL ); + } + } + } else if ( tether && aifl.tetherMover ) { + SetTether ( NULL ); + } +} + +/* +============ +idAI::Postthink +============ +*/ +void idAI::Postthink( void ){ + if ( !aifl.simpleThink ) { + pain.takenThisFrame = 0; + } + + // Draw debug tactical information + DrawTactical ( ); + + if( vehicleController.IsDriving() ){ // Generate some sort of command? + usercmd_t usercmd; + + // Note! usercmd angles stuff is in deltas, not in absolute values. + + memset( &usercmd, 0, sizeof( usercmd ) ); + + idVec3 toEnemy; + + if( enemy.ent ){ + toEnemy = enemy.ent->GetPhysics()->GetOrigin(); + toEnemy -= GetPhysics()->GetOrigin(); + toEnemy.Normalize(); + + idAngles enemyAng; + + enemyAng = toEnemy.ToAngles(); + + usercmd.angles[PITCH] = ANGLE2SHORT( enemyAng.pitch ); + usercmd.angles[YAW] = ANGLE2SHORT( enemyAng.yaw ); + + usercmd.buttons = BUTTON_ATTACK; + + vehicleController.SetInput ( usercmd, enemyAng ); + } + } + + // Keep our threat value up to date + UpdateThreat ( ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::OnDeath( void ){ + if( vehicleController.IsDriving() ){ + usercmd_t usercmd; + + memset( &usercmd, 0, sizeof( usercmd ) ); + usercmd.buttons = BUTTON_ATTACK; + usercmd.upmove = 300.0f; // This will cause the character to eject. + + vehicleController.SetInput( usercmd, idAngles( 0, 0, 0 ) ); + + // Fixme! Is this safe to do immediately? + vehicleController.Eject(); + } + + aiManager.RemoveTeammate ( this ); + + ExecScriptFunction( funcs.death ); + +/* DONT DROP ANYTHING FOR NOW + float rVal = gameLocal.random.RandomInt( 100 ); + + if( spawnArgs.GetFloat( "no_drops" ) >= 1.0 ){ + spawnArgs.Set( "def_dropsItem1", "" ); + }else{ + // Fixme! Better guys should drop better stuffs! Make drops related to guy type? Do something cooler here? + if( rVal < 25 ){ // Half of guys drop nothing? + spawnArgs.Set( "def_dropsItem1", "" ); + }else if( rVal < 50 ){ + spawnArgs.Set( "def_dropsItem1", "item_health_small" ); + } + } +*/ +} + +/* +============ +idAI::OnWakeUp +============ +*/ +void idAI::OnWakeUp ( void ) { +} + +/* +============ +idAI::OnUpdatePlayback +============ +*/ +void idAI::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + return; +} + +/* +============ +idAI::OnLeaderChange +============ +*/ +void idAI::OnLeaderChange ( idEntity* oldLeader ) { + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnEnemyChange +============ +*/ +void idAI::OnEnemyChange ( idEntity* oldEnemy ) { + // Make sure we update our tactical state immediately + ForceTacticalUpdate ( ); + + // see if we should announce the enemy + if ( enemy.ent ) { + combat.fl.aware = true; + combat.investigateTime = 0; + + enemy.changeTime = gameLocal.time; + + enemy.lastKnownPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + + UpdateEnemyVisibility ( ); + UpdateEnemyPosition ( true ); + UpdateEnemy ( ); + } else { + enemy.range = 0; + enemy.range2d = 0; + enemy.changeTime = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + } + + enemy.fl.visible = false; +} + +/* +============ +idAI::OnTacticalChange +============ +*/ +void idAI::OnTacticalChange ( aiTactical_t oldTactical ) { + // if acutally moving to a new tactial location announce it + if ( move.fl.moving ) { + AnnounceTactical( combat.tacticalCurrent ); + } +} + +/* +============ +idAI::OnFriendlyFire +============ +*/ +void idAI::OnFriendlyFire ( idActor* attacker ) { + AnnounceFriendlyFire( static_cast(attacker) ); +} + +/* +============ +idAI::OnStartMoving +============ +*/ +void idAI::OnStartMoving ( void ) { + aifl.simpleThink = false; + combat.fl.crouchViewClear = false; +} + +/* +============ +idAI::OnStopMoving +============ +*/ +void idAI::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { +} + +/* +============ +idAI::OnStartAction +============ +*/ +void idAI::OnStartAction ( void ) { +} + +/* +============ +idAI::OnStopAction +============ +*/ +void idAI::OnStopAction ( void ) { +} + +/* +============ +idAI::OnEnemyVisiblityChange +============ +*/ +void idAI::OnEnemyVisiblityChange ( bool oldVisible ) { +} + +/* +============ +idAI::OnSetKey +============ +*/ +void idAI::OnSetKey ( const char* key, const char* value ) { + if ( !idStr::Icmp ( key, "noCombatChatter" ) ) { + combat.fl.noChatter = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "allowPlayerPush" ) ) { + combat.tacticalMaskAvailable &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + if ( spawnArgs.GetBool ( key ) ) { + combat.tacticalMaskAvailable |= AITACTICAL_MOVE_PLAYERPUSH_BIT; + } + } else if ( !idStr::Icmp ( key, "noLook" ) ) { + aifl.disableLook = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "killer_guard" ) ) { + aifl.killerGuard = spawnArgs.GetBool ( key ); + } +} + +/* +============ +idAI::OnCoverInvalidated +============ +*/ +void idAI::OnCoverInvalidated ( void ) { + // Force a tactical update now + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnCoverNotFacingEnemy +============ +*/ +void idAI::OnCoverNotFacingEnemy ( void ) { + // Clear attack timers so we can shoot right now + actionTimerRangedAttack.Clear ( actionTime ); + actionRangedAttack.timer.Clear( actionTime ); +} + +/* +============ +idAI::SkipCurrentDestination + +Is the AI's current destination ok enough to stay at? +============ +*/ +bool idAI::SkipCurrentDestination ( void ) const { + // can only skip current destination when we are stopped + if ( move.fl.moving ) { + return false; + } +/* + // If we are currently behind cover and that cover is no longer valid we should skip it + if ( IsBehindCover ( ) && !IsCoverValid ( ) ) { + return true; + } +*/ + return false; +} + +/* +============ +idAI::SkipImpulse +============ +*/ +bool idAI::SkipImpulse( idEntity *ent, int id ){ + bool skip = idActor::SkipImpulse( ent, id ); + + if( af.IsActive ( ) ) { + return false; + } + if( !fl.takedamage ){ + return true; + } + if( move.moveCommand == MOVE_RV_PLAYBACK ){ + return true; + } + + return skip; +} + +/* +============ +idAI::CanHitEnemy +============ +*/ +bool idAI::CanHitEnemy ( void ) { + trace_t tr; + idEntity *hit; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + idVec3 eye = GetEyePosition(); + idVec3 dir; + + // expand the ray out as far as possible so we can detect anything behind the enemy + dir = toPos - eye; + dir.Normalize(); + toPos = eye + dir * MAX_WORLD_SIZE; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return lastHitCheckResult; + } + + gameLocal.TracePoint( this, tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + hit = gameLocal.GetTraceEntity( tr ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { + lastHitCheckResult = true; + } else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && + ( static_cast( hit )->team != team ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI::CanHitEnemyFromJoint +============ +*/ + +bool idAI::CanHitEnemyFromJoint( const char *jointname ){ + trace_t tr; + idVec3 muzzle; + idMat3 axis; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return true; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + jointHandle_t joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); + muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, muzzle, toPos, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI:: +============ + +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ); + +int idAI::TestTrajectory( const idVec3 &firePos, const idVec3 &target, const char *projectileName ){ + idVec3 projVelocity; + idVec3 projGravity; + float testTime; + float zVel, height, pitch, s, c; + idVec3 dir; + float newVel; + float delta_x; + float delta_z; + + projectileDef = gameLocal.FindEntityDefDict ( projectileName ); + projVelocity = idProjectile::GetVelocity( projectileDef ); + projGravity = idProjectile::GetGravity( projectileDef ).z * GetPhysics()->GetGravity(); + pitch = DEG2RAD( gameLocal.random.RandomFloat() * 50 + 20 ); // Random pitch range between 20 and 70. Should this be customizeable? + + idMath::SinCos( pitch, s, c ); + + delta_x = idMath::Sqrt( ( target.x - firePos.x ) * ( target.x - firePos.x ) + ( target.y - firePos.y ) * ( target.y - firePos.y ) ); + delta_z = target.z - firePos.z; + newVel = ( delta_x / idMath::Cos( pitch ) ) * idMath::Sqrt( projGravity.z / ( 2.0f * ( delta_x * idMath::Tan( pitch ) - delta_z ) ) ); + testTime = delta_x / ( newVel * c ); + zVel = newVel * s; + + float a = idMath::ASin ( delta_x * GetPhysics()->GetGravity().Length() / (projVelocity.x * projVelocity.x) ); + a = a / 2; + + float r = (projVelocity.x * projVelocity.x) * idMath::Sin ( 2 * a ) / GetPhysics()->GetGravity().Length(); + if ( r < delta_x - (delta_x * 0.1) ) { + mVar.valid_lobbed_shot = 0; + return 0; + } else { + mVar.lobDir = target - firePos; + mVar.lobDir.z = 0; + mVar.lobDir.z = idMath::Tan ( a ) * mVar.lobDir.LengthFast(); + mVar.lobDir.Normalize ( ); + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = 1.0f; // newVel / projVelocity.Length(); + return 1; + } + + projGravity[2] *= -1.0f; + + dir = target - firePos; + dir.z = 0; + dir.Normalize(); + delta_x = idMath::Sqrt( 1 - ( c * c ) ); + dir.x *= delta_x; + dir.y *= delta_x; + dir.z = c; + + height = HeightForTrajectory( firePos, zVel, projGravity[2] ) - firePos.z; + if ( height > MAX_WORLD_SIZE ) { + // goes higher than we want to allow + mVar.valid_lobbed_shot = 0; + return 0; + } + + if ( idAI::TestTrajectory ( firePos, target, zVel, projGravity[2], testTime, height * 2, NULL, + MASK_SHOT_RENDERMODEL, this, enemy.GetEntity(), ai_debugTrajectory.GetBool() ? 4000 : 0 ) ) { + + if ( ai_debugTrajectory.GetBool() ) { + float t = testTime / 100.0f; + idVec3 velocity = dir * newVel; + idVec3 lastPos, pos; + lastPos = firePos; + pos = firePos; + for ( int j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, ai_debugTrajectory.GetBool() ? 4000 : 0 ); + lastPos = pos; + } + } + + mVar.lobDir = dir; + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = newVel / projVelocity.Length(); + }else{ + mVar.valid_lobbed_shot = 0; + } + + return ( (int)mVar.valid_lobbed_shot != 0 ); + +} +*/ + +/* +============ +idAI:: +============ +*/ + +float idAI::GetTurnDelta( void ){ + float amount; + + if ( move.turnRate ) { + amount = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + return amount; + } else { + return 0.0f; + } +} + +/* +============ +idAI::GetIdleAnimName +============ +*/ +const char* idAI::GetIdleAnimName ( void ) { + const char* animName = NULL; + + // Start idle animation + if ( enemy.ent ) { + animName = "idle_alert"; + } + + if ( animName && HasAnim ( ANIMCHANNEL_ALL, animName ) ) { + return animName; + } + + return "idle"; +} + +/* +=============================================================================== + + idAI - Enemy Finding + +=============================================================================== +*/ + +/* +============ +idAI::FindEnemy +============ +*/ +idEntity *idAI::FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr ){ + idActor* actor; + idActor* bestEnemy; + idActor* bestEnemyBackup; + float bestThreat; + float bestThreatBackup; + float distSqr; + float enemyRangeSqr; + float awareRangeSqr; + idVec3 origin; + idVec3 delta; + pvsHandle_t pvs; + + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + bestThreat = 0.0f; + bestThreatBackup = 0.0f; + bestEnemy = NULL; + bestEnemyBackup = NULL; + awareRangeSqr = Square ( combat.awareRange ); + enemyRangeSqr = enemy.ent ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + origin = GetEyePosition ( ); + + // Iterate through the enemy team + for( actor = aiManager.GetEnemyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden enemies and enemies that cant be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + + // Calculate the distance between ourselves and our potential enemy + delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); + distSqr = delta.LengthSqr(); + + // Calculate the adjusted threat for this actor + float threat = CalculateEnemyThreat ( actor ); + + // Save the highest threat enemy as a backup in case we cannot find one we can see + if ( threat > bestThreatBackup ) { + bestThreatBackup = threat; + bestEnemyBackup = actor; + } + + // If we have already found a more threatening enemy then attack that + if ( threat < bestThreat ) { + continue; + } + + // If this enemy isnt in the same pvps then use them as a backup + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // this is pretty specific to Quake 4, but we don't want the player to be around an enemy too long who can't see him. We need to randomly spike the + // awareRange on creatures to simulate them looking behind them, or noticing someone standing around for too long. + // Modders take note, this will prevent most "sneaking up on bad guys" action because they will likely spike their aware ranges out + // during the sneaking. + if( gameLocal.random.RandomFloat() < 0.005f ) { + awareRangeSqr *= 15; + } + + // fov doesn't matter if they're within awareRange, we "sense" them if we're alert... (or should LOS not even matter at this point?) + if ( distSqr < awareRangeSqr || CanSeeFrom ( origin, actor, (inFov && !(combat.fl.aware&&distSqrIsType ( idAI::Type ) ) { + idAI* teammateAI = static_cast(teammate); + enemy.smoothedLinearVelocity = teammateAI->enemy.smoothedLinearVelocity; + enemy.smoothedPushedVelocity = teammateAI->enemy.smoothedPushedVelocity; + enemy.lastKnownPosition = teammateAI->enemy.lastKnownPosition; + enemy.lastVisibleEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleFromEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleChestPosition = teammateAI->enemy.lastVisibleChestPosition; + enemy.lastVisibleTime = 0; + } + + return true; +} + +/* +============ +idAI::CheckForCloserEnemy +============ +*/ +bool idAI::CheckForCloserEnemy ( void ) { + idEntity* newEnemy = NULL; + float maxDistSqr; + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + // See if we happen to have heard someone this frame that we can use + newEnemy = HeardSound( true ); + if ( newEnemy && newEnemy != enemy.ent && newEnemy->IsType( idActor::GetClassType() ) ) { + //heard someone else! + float newDist = DistanceTo ( newEnemy->GetPhysics()->GetOrigin() ); + + // Are they closer than the enemy we are fighting? + if ( newDist < enemy.range ) { + //new enemy is closer than current one, take them! + SetEnemy( newEnemy ); + return true; + } + } + + if ( GetEnemy() && enemy.range ) { + maxDistSqr = Min( Square ( enemy.range ), Square ( combat.awareRange ) ); + } else { + maxDistSqr = Square ( combat.awareRange ); + } + + newEnemy = FindEnemy( false, 0, maxDistSqr ); + + if ( !newEnemy ) { + return false; + } + + SetEnemy( newEnemy ); + return true; +} + +/* +============ +idAI::CheckForReplaceEnemy + +TODO: Call CalculateThreat ( ent ) and compare to current entity +============ +*/ +bool idAI::CheckForReplaceEnemy ( idEntity* replacement ) { + bool replace; + + // If our replacement is a driver a vehicle and they are hidden we will + // want to shoot back at their vehicle not them. + idActor* actor; + actor = dynamic_cast(replacement); + if ( actor && actor->IsInVehicle ( ) && actor->IsHidden ( ) ) { + replacement = actor->GetVehicleController ( ).GetVehicle ( ); + } + + // Invalid replacement? + if ( !replacement) { + return false; + } + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + if ( replacement == enemy.ent ) { + return false; + } + + // Dont want to set our enemy to a friendly target + if ( replacement->IsType( idActor::GetClassType() ) && (static_cast(replacement))->team == team ) { + return false; + } + + // Not having an enemy will set it immediately + if ( !enemy.ent ) { + SetEnemy ( replacement ); + return true; + } + + // Dont change enemies too often when being hit + if ( gameLocal.time - enemy.changeTime < 1000 ) { + return false; + } + + replace = false; + + // If new enemy is more threatening then replace it reguardless if we can see it + if ( CalculateEnemyThreat ( replacement ) > CalculateEnemyThreat ( enemy.ent ) ) { + replace = true; + // Replace our enemy if we havent seen ours in a bit + } else if ( !IsEnemyRecentlyVisible ( 0.25f ) ) { + replace = true; + } + + // Replace enemy? + if ( replace ) { + SetEnemy ( replacement ); + } + + return replace; +} + +/* +============ +idAI::UpdateThreat +============ +*/ +void idAI::UpdateThreat ( void ) { + // Start threat at base threat level + combat.threatCurrent = combat.threatBase; + + // Adjust threat using current tactical state + switch ( combat.tacticalCurrent ) { + case AITACTICAL_HIDE: combat.threatCurrent *= 0.5f; break; + case AITACTICAL_MELEE: combat.threatCurrent *= 2.0f; break; + } + + // Signifigantly reduced threat when in undying mode + if ( aifl.undying ) { + combat.threatCurrent *= 0.25f; + } +} + +/* +============ +idAI::CalculateEnemyThreat +============ +*/ +float idAI::CalculateEnemyThreat ( idEntity* enemyEnt ) { + // Calculate the adjusted threat for this actor + float threat = 1.0f; + if ( enemyEnt->IsType ( idAI::GetClassType ( ) ) ) { + idAI* enemyAI = static_cast(enemyEnt); + threat = enemyAI->combat.threatCurrent; + + // Increase threat for enemies that are targetting us + if ( enemyAI->enemy.ent == this && enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE ) { + threat *= 2.0f; + } + } else if ( enemyEnt->IsType ( idPlayer::GetClassType ( ) ) ) { + threat = 2.0f; + } else { + threat = 1.0f; + } + + float enemyRangeSqr; + float distSqr; + + enemyRangeSqr = (enemy.ent) ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + distSqr = (physicsObj.GetOrigin ( ) - enemyEnt->GetPhysics()->GetOrigin ( )).LengthSqr ( ); + + if ( distSqr > 0 ) { + return threat * (enemyRangeSqr / distSqr); + } + + return threat; +} + +/* +============ +idAI::CheckBlink +============ +*/ +void idAI::CheckBlink ( void ) { +// if ( IsSpeaking ( ) ) { +// return; +// } + idActor::CheckBlink ( ); +} + +/* +============ +idAI::Speak +============ +*/ +bool idAI::Speak( const char *lipsync, bool random ){ + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + if ( random ) { + // If there is no lipsync then skip it + if ( spawnArgs.MatchPrefix ( lipsync ) ) { + lipsync = spawnArgs.RandomPrefix ( lipsync, gameLocal.random ); + } else { + lipsync = NULL; + } + } else { + lipsync = spawnArgs.GetString ( lipsync ); + } + + if ( !lipsync || !*lipsync ) { + return false; + } + + if ( head ) { + speakTime = head->StartLipSyncing( lipsync ); + } else { + speakTime = 0; + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, SSF_IS_VO, false, &speakTime ); + } + + speakTime += gameLocal.time; + return true; +} + +/* +============ +idAI::StopSpeaking +============ +*/ +void idAI::StopSpeaking( bool stopAnims ){ + speakTime = 0; + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + if ( stopAnims ) { + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + } +} + +/* +============ +idAI::CanHitEnemyFromAnim +============ +*/ +bool idAI::CanHitEnemyFromAnim( int animNum, idVec3 offset ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin()+offset; + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + +/* + if( DebugFilter(ai_debugTactical) ) { + gameRenderWorld->DebugLine ( colorYellow, org + attackAnimInfo[ animNum ].eyeOffset * viewAxis, from, 5000 ); + gameRenderWorld->DebugLine ( colorOrange, from, enemy.lastVisibleEyePosition, 5000 ); + } +*/ + + // If the point we are shooting from is within our bounds then we are good to go, otherwise make sure its not in a wall + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + if ( !ownerBounds.ContainsPoint ( from ) ) { + trace_t tr; + if ( !g_perfTest_aiNoVisTrace.GetBool() ) { + gameLocal.TracePoint( this, tr, org + attackAnimInfo[ animNum ].eyeOffset * axis, from, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction < 1.0f ) { + return false; + } + } + } + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +idAI::ScriptedBegin +================ +*/ +bool idAI::ScriptedBegin ( bool endWithIdle, bool allowDormant ) { + if ( aifl.dead ) { + return false; + } + + // Wakeup if not awake already + WakeUp ( ); + + aifl.scriptedEndWithIdle = endWithIdle; + aifl.scripted = true; +// combat.fl.aware = false; + combat.tacticalCurrent = AITACTICAL_NONE; + + // Make sure the entity never goes dormant during a scripted event or + // the event may never end. + aifl.scriptedNeverDormant = !allowDormant; + dormantStart = 0; + +/* + // actors will ignore enemies during scripted events + ClearEnemy ( ); +*/ + + // Cancel any current movement + StopMove ( MOVE_STATUS_DONE ); + + move.fl.allowAnimMove = true; + + return true; +} + +/* +================ +idAI::ScriptedEnd +================ +*/ +void idAI::ScriptedEnd ( void ) { + dormantStart = 0; + aifl.scripted = false; +} + +/* +================ +idAI::ScriptedStop +================ +*/ +void idAI::ScriptedStop ( void ) { + if ( !aifl.scripted ) { + return; + } + aifl.scriptedEndWithIdle = true; + aifl.scripted = false; + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::ScriptedMove +================ +*/ +void idAI::ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + //disable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.UnMarkAllReachBlocked(); + //attempt the move - NOTE: this *can* fail if there's no route or AAS obstacles are in the way! + MoveToEntity ( destEnt, minDist ); + //re-enable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.ReMarkAllReachBlocked(); + + // Move the torso to the idle state if its not already there + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Move the legs into the idle state so he will start moving + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + + // Set up state loop for moving + SetState ( "State_ScriptedMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedFace +================ +*/ +void idAI::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + // Force idle while facing + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Start facing the entity + FaceEntity ( faceEnt ); + + SetState ( "State_ScriptedFace" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedAnim + +Plays an the given animation in an un-interruptable state. If looping will continue indefinately until +another operation which will stop a scripted sequence is called. When done can optionally return the character +back to their normal processing if endWithIdle is set to true. +================ +*/ +void idAI::ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle, true ) ) { + return; + } + + TurnToward ( move.current_yaw ); + + if ( loop ) { + // Loop the given animation + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + // Play the given animation + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + + SetAnimState ( ANIMCHANNEL_LEGS, "Wait_Frame" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_ScriptedAnim" ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + + SetState ( "Wait_ScriptedDone" ); + PostState ( "State_ScriptedStop" ); +} + + +/* +============ +idAI::ScriptedPlaybackAim +============ +*/ +void idAI::ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + mLookPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ); + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackAim" ); + PostState ( "State_ScriptedStop" ); +} + +/* +============ +idAI::ScriptedAction +============ +*/ +void idAI::ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + const char* actionName; + + if ( !actionEnt ) { + return; + } + + // Get the action name + actionName = actionEnt->spawnArgs.GetString ( "action" ); + if ( !*actionName ) { + gameLocal.Error ( "missing action keyword on scripted action entity '%s' for ai '%s'", + actionEnt->GetName(), + GetName() ); + return; + } + + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + scriptedActionEnt = actionEnt; + + SetState ( "State_ScriptedStop" ); + PerformAction ( va("TorsoAction_%s", actionName ), 4, true ); +} + +/* +============ +idAI::FootStep +============ +*/ +void idAI::FootStep ( void ) { + idActor::FootStep ( ); + + ExecScriptFunction( funcs.footstep ); +} + +/* +============ +idAI::SetScript +============ +*/ +void idAI::SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "first_sight" ) ) { + funcs.first_sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "sight" ) ) { + funcs.sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "pain" ) ) { + funcs.pain.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "damage" ) ) { + funcs.damage.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "death" ) ) { + funcs.death.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "attack" ) ) { + funcs.attack.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "init" ) ) { + funcs.init.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "onclick" ) ) { + funcs.onclick.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "launch_projectile" ) ) { + funcs.launch_projectile.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "footstep" ) ) { + funcs.footstep.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "postHeal" ) ) { + // hax: this is a medic only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else if ( !idStr::Icmp ( scriptName, "postWeaponDestroyed" ) ) { + // hax: this is a Gladiator/Light Tank only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else { + gameLocal.Warning ( "unknown script '%s' specified on entity '%s'", scriptName, name.c_str() ); + } +} + +/* +=============================================================================== + + idAI - Reactions + +=============================================================================== +*/ + +/* +============ +idAI::ReactToShotAt +============ +*/ +void idAI::ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ) { + if ( g_perfTest_aiNoDodge.GetBool() ) { + return; + } + + idVec3 foo; + idVec3 diff; + diff = GetPhysics()->GetOrigin() - origOrigin; + diff = origOrigin + diff.ProjectOntoVector ( origDir ) * origDir; + diff = diff - GetPhysics()->GetOrigin(); + diff.NormalizeFast ( ); + diff.z = 0; + + idAngles angles = diff.ToAngles ( ); + float angleDelta = idMath::AngleDelta ( angles[YAW], move.current_yaw ); + + combat.shotAtTime = gameLocal.time; + combat.shotAtAngle = angleDelta; + + // Someone is attacking us so give them a chance to be our new enemy + CheckForReplaceEnemy ( attacker ); +} + +/* +============ +idAI::ReactToPain +============ +*/ +void idAI::ReactToPain ( idEntity* attacker, int damage ) { + CheckForReplaceEnemy ( attacker ); +} + +/* +=============================================================================== + + idAI - Helpers + +=============================================================================== +*/ + +/* +============ +idAI::UpdateHelper +============ +*/ +void idAI::UpdateHelper ( void ) { + rvAIHelper* oldhelper; + + // Link ourselves to the closest helper + oldhelper = helperCurrent; + helperCurrent = aiManager.FindClosestHelper ( physicsObj.GetOrigin() ); + + // Ideal stays the same as current as long as it was the same when we started + if ( oldhelper == helperIdeal ) { + helperIdeal = helperCurrent; + } +} + +/* +============ +idAI::GetActiveHelper + +When we have an enemy our current helper becomes the active helper, when we dont have an +enemy we instead use our ideal. +============ +*/ +rvAIHelper* idAI::GetActiveHelper ( void ) { + return GetEnemy ( ) ? helperCurrent : helperIdeal; +} + +/* +=============================================================================== + + idAI - Tethers + +=============================================================================== +*/ + +/* +============ +idAI::SetTether +============ +*/ +void idAI::SetTether ( rvAITether* newTether ) { + aifl.tetherMover = false; + + // Clear our current tether? + if ( !newTether ) { + if ( tether ) { + tether = NULL; + ForceTacticalUpdate ( ); + } + } else if ( newTether->IsType ( rvAITetherClear::GetClassType ( ) ) ) { + SetTether ( NULL ); + } else { + if ( newTether && !newTether->ValidateAAS ( this ) ) { + // If you have aas error out to make them fix it + if ( aas ) { + gameLocal.Error ( "tether entity '%s' does no link into the aas for ai '%s'. (try moving it closer to the floor where the aas is)", + newTether->GetName(), GetName () ); + // If we dont have aas, just warn + } else { + gameLocal.Warning ( "tether entity '%s' does no link into the aas for ai '%s'. (there is no aas available)", + newTether->GetName(), GetName () ); + } + SetTether ( NULL ); + } else if ( newTether != tether ) { + tether = newTether; + ForceTacticalUpdate ( ); + } + } +} + +/* +============ +idAI::GetTether +============ +*/ +rvAITether* idAI::GetTether ( void ) { + return tether; +} + +/* +============ +idAI::IsTethered +============ +*/ +bool idAI::IsTethered ( void ) const { + // Need a tether entity to be tethered + if ( !tether ) { + return false; + } + // If we have an enemy and that enemy is within our tether then break it if we can + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) && tether->CanBreak ( ) ) { + if ( tether->ValidateDestination ( static_cast(enemy.ent.GetEntity()), enemy.lastKnownPosition ) ) { + return false; + } + } + return true; +} + +/* +============ +idAI::IsWithinTether +============ +*/ +bool idAI::IsWithinTether ( void ) const { + if ( !IsTethered ( ) ) { + return false; + } + if ( !tether->ValidateDestination ( (idAI*)this, physicsObj.GetOrigin ( ) ) ) { + return false; + } + return true; +} + +/* +=============================================================================== + + idAI - NonCombat + +=============================================================================== +*/ + +/* +===================== +idAI::SetTalkState +===================== +*/ +void idAI::SetTalkState ( talkState_t state ) { + // Make sure state is valid + if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { + gameLocal.Error( "Invalid talk state (%d)", (int)state ); + } + + // Same state we are already in? + if ( talkState == state ) { + return; + } + + // Set new talk state + talkState = state; + +} + +/* +============ +idAI::SetPassivePrefix +============ +*/ +void idAI::SetPassivePrefix ( const char* prefix ) { + passive.prefix = prefix; + if ( passive.prefix.Length() ) { + passive.prefix += "_"; + } + + // Force an idle change + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + + // Get animation prefixs + passive.fl.multipleIdles = GetPassiveAnimPrefix ( "idle", passive.animIdlePrefix ); + GetPassiveAnimPrefix ( "fidget", passive.animFidgetPrefix ); + GetPassiveAnimPrefix ( "talk", passive.animTalkPrefix ); +} + +/* +============ +idAI::GetPassiveAnimPrefix +============ +*/ +bool idAI::GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ) { + const idKeyValue* key; + + // First see if we have custom idle animations for the passive prefix + key = NULL; + if ( passive.prefix.Length ( ) ) { + animPrefix = va("anim_%s%s", passive.prefix.c_str(), animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + // If there are no custom idle animations for the prefix then see if there are any custom anims at all + if ( !key ) { + animPrefix = va("anim_%s", animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + if ( !key ) { + animPrefix = ""; + return false; + } + + return spawnArgs.MatchPrefix ( animPrefix, key ) ? true : false; +} + +/* +=================== +idAI::IsMeleeNeeded +=================== +*/ +bool idAI::IsMeleeNeeded( void ) { + + if( enemy.ent && enemy.ent->IsType ( idAI::Type )) { + + idAI* enemyAI = static_cast(enemy.ent.GetEntity()); + + //if our enemy is closing in on us and demands melee, we'll meet him. + if ( enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE && enemy.range < combat.meleeRange ) { + return true; + } + + //other checks... + } + + return false; +} + +/* +=================== +idAI::IsCrouching +=================== +*/ +bool idAI::IsCrouching( void ) const { + return move.fl.crouching; +} + +bool idAI::CheckDeathCausesMissionFailure( void ) +{ + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) + { + return true; + } + if ( targets.Num() ) + { + //go through my targets and see if any are of class rvObjectiveFailed + idEntity* targEnt; + for( int i = 0; i < targets.Num(); i++ ) { + targEnt = targets[ i ].GetEntity(); + if ( !targEnt ) + { + continue; + } + if ( !targEnt->IsType( rvObjectiveFailed::GetClassType() ) ) { + continue; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + continue; + } + //yep! + return true; + } + } + return false; +} diff --git a/source/game/ai/AI.h b/source/game/ai/AI.h new file mode 100644 index 0000000..4607fd7 --- /dev/null +++ b/source/game/ai/AI.h @@ -0,0 +1,1373 @@ +/* +================ + +AI.h + +================ +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +// moved all motion related code to AI_Move.h +#ifndef __AI_MOVE_H__ + #include "AI_Move.h" +#endif +#ifndef __AAS_TACTICAL_H__ + #include "AAS_tactical.h" +#endif + +typedef enum { + AITACTICAL_NONE, + AITACTICAL_MELEE, // Rush towards the enemy and perform melee attacks when within range + AITACTICAL_MOVE_FOLLOW, // Move towards leader, stop when within range + AITACTICAL_MOVE_TETHER, // Move within tether, stop when within tether + AITACTICAL_MOVE_PLAYERPUSH, // Move away when the player pushes us (or another ai entity pushes us that was pushe by player) + AITACTICAL_COVER, // Move to cover and perform attacks from that cover position + AITACTICAL_COVER_FLANK, + AITACTICAL_COVER_ADVANCE, + AITACTICAL_COVER_RETREAT, + AITACTICAL_COVER_AMBUSH, + AITACTICAL_RANGED, // Move to position in which the enemy can be attacked from range, stop when there and attack enemy + AITACTICAL_TURRET, // Stay in current position and attack enemy + AITACTICAL_HIDE, // Move to a position where we cannot be seen by our enemy + AITACTICAL_PASSIVE, // Stay in current position with multiple idles, twitch animations, and conversations + + AITACTICAL_MAX +} aiTactical_t; + +typedef enum { + AICTRESULT_OK, + AICTRESULT_SKIP, + AICTRESULT_NOMOVE, +} aiCTResult_t; + +const int AITACTICAL_NONE_BIT = BIT(AITACTICAL_NONE); +const int AITACTICAL_MELEE_BIT = BIT(AITACTICAL_MELEE); +const int AITACTICAL_MOVE_FOLLOW_BIT = BIT(AITACTICAL_MOVE_FOLLOW); +const int AITACTICAL_MOVE_TETHER_BIT = BIT(AITACTICAL_MOVE_TETHER); +const int AITACTICAL_MOVE_PLAYERPUSH_BIT = BIT(AITACTICAL_MOVE_PLAYERPUSH); +const int AITACTICAL_COVER_BIT = BIT(AITACTICAL_COVER); +const int AITACTICAL_COVER_FLANK_BIT = BIT(AITACTICAL_COVER_FLANK); +const int AITACTICAL_COVER_ADVANCE_BIT = BIT(AITACTICAL_COVER_ADVANCE); +const int AITACTICAL_COVER_RETREAT_BIT = BIT(AITACTICAL_COVER_RETREAT); +const int AITACTICAL_COVER_AMBUSH_BIT = BIT(AITACTICAL_COVER_AMBUSH); +const int AITACTICAL_RANGED_BIT = BIT(AITACTICAL_RANGED); +const int AITACTICAL_TURRET_BIT = BIT(AITACTICAL_TURRET); +const int AITACTICAL_HIDE_BIT = BIT(AITACTICAL_HIDE); +const int AITACTICAL_PASSIVE_BIT = BIT(AITACTICAL_PASSIVE); + +const int AITACTICAL_COVER_BITS = (AITACTICAL_COVER_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); +const int AITACTICAL_RANGED_BITS = (AITACTICAL_RANGED_BIT); +const int AITACTICAL_NONMOVING_BITS = (AITACTICAL_NONE_BIT|AITACTICAL_TURRET_BIT|AITACTICAL_PASSIVE_BIT); + +typedef enum { + TALKMSG_NONE, + TALKMSG_PRIMARY, + TALKMSG_SECONDARY, + TALKMSG_LOOP, +} talkMessage_t; + +typedef enum { + AIFLAGOVERRIDE_DAMAGE, + AIFLAGOVERRIDE_DISABLEPAIN, + AIFLAGOVERRIDE_NOTURN, + AIFLAGOVERRIDE_NOGRAVITY +} aiFlagOverride_t; + +typedef enum { + TALK_NEVER, // Never talk to the player + TALK_DEAD, // Cant talk due to being dead + TALK_OK, // Can talk + TALK_FOLLOW, // Talking to will cause a follow + TALK_BUSY, // Cant talk right now, is busy + TALK_WAIT, // Wait a bit - he's probably in the middle of a conversation (this is so you can still see their names but they won't talk to you when clicked on) + NUM_TALK_STATES +} talkState_t; + +//chance that AI will make an announcement when they have the option (0 - 1.0f) +#define AISPEAK_CHANCE 0.2f + +typedef enum { + AIFOCUS_NONE, + AIFOCUS_LEADER, + AIFOCUS_TARGET, + AIFOCUS_TALK, + AIFOCUS_PLAYER, + AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_ENEMY = AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_COVER, + AIFOCUS_COVERLOOK, + AIFOCUS_HELPER, + AIFOCUS_TETHER, + AIFOCUS_MAX +} aiFocus_t; + +typedef enum { + AIMOVESPEED_DEFAULT, // Choose run/walk depending on situation + AIMOVESPEED_RUN, // Always run + AIMOVESPEED_WALK, // alwasy walk +} aiMoveSpeed_t; + +typedef struct rvAIFuncs_s { + rvScriptFuncUtility first_sight; // script to run when an enemy is first sighted + rvScriptFuncUtility sight; // script to run every time an enemy is sighted + rvScriptFuncUtility pain; // script to run when the AI takes pain + rvScriptFuncUtility damage; // script to run when the AI takes damage + rvScriptFuncUtility death; // script to run when the AI dies + rvScriptFuncUtility attack; // script to run when attacking an enemy + rvScriptFuncUtility init; // script to run on initialization + rvScriptFuncUtility onclick; // script to run when a friendly AI is clicked on + rvScriptFuncUtility launch_projectile; // script to run when a projectile is launched + rvScriptFuncUtility footstep; // script to run on a footstep +} rvAIFuncs_t; + +typedef struct rvAICombat_s{ + struct combatFlags_s { + bool ignoreEnemies :1; + bool alert :1; + bool aware :1; + bool tetherNoBreak :1; // Set to true to prevent enemies from breaking tethers + bool tetherAutoBreak :1; // Set to true to automatically break tethers when within range + bool tetherOutOfRange :1; // Set to true when we are out of range of our curren tether + bool seenEnemyDirectly :1; // Has directly seen an enemy (as opposed to having heard or been told about him) + bool noChatter :1; // No combat announcements + bool crouchViewClear :1; // Can I crouch at the position that I stopped at? + } fl; + + float max_chasing_turn; + float shotAtTime; + float shotAtAngle; + idVec2 hideRange; + idVec2 attackRange; + int attackSightDelay; + float meleeRange; + float aggressiveRange; // Range to become more aggressive + float aggressiveScale; // Scale to use when altering numbers due to aggression + int investigateTime; + + float visStandHeight; // Height to check enemy visibiliy while standing + float visCrouchHeight; // Height to check enemy visiblity while crouching + float visRange; // Maximum distance to check enemy visibility + float earRange; // Maximum distance to check hearing an enemy from + float awareRange; // Distance to become automatically aware of an enemy + + int tacticalMaskAvailable; // Currently available tactical states + int tacticalMaskUpdate; // states currently being evaluated + aiTactical_t tacticalCurrent; // Current tactical state + int tacticalUpdateTime; // Last time the tacitcal state was updated (for delaying updating) + int tacticalPainTaken; // Amount of damage taken in the current tactical state + int tacticalPainThreshold; // Threshold of pain before invalidating the current tactical state + int tacticalFlinches; // Number of flinches that have occured at the current tactical state + + float threatBase; // Base amount of threat generated by AI + float threatCurrent; // Current amount of threat generatd by AI based on current state + + int maxLostVisTime; + + int coverValidTime; + int maxInvalidCoverTime; +} rvAICombat_t; + +typedef struct rvAIPain_s { + float threshold; + float takenThisFrame; + int lastTakenTime; + int loopEndTime; + idStr loopType; +} rvAIPain_t; + +typedef struct rvAIEnemy_s { + struct flags_s { + bool lockOrigin :1; // Stop tracking enemy origin until state changes + bool dead :1; // Enemy is dead + bool inFov :1; // Enemy is currently in fov + bool sighted :1; // Enemy was sighted at least once + bool visible :1; // Enemy is visible? + } fl; + + idEntityPtr ent; + int lastVisibleChangeTime; // last time the visible state of the enemy changed + idVec3 lastVisibleFromEyePosition; // Origin used in last successfull visibility check + idVec3 lastVisibleEyePosition; // Origin of last known visible eye position + idVec3 lastVisibleChestPosition; // Origin of last known visible chest position + int lastVisibleTime; // Time we last saw and enemy + + idVec3 smoothedLinearVelocity; + idVec3 smoothedPushedVelocity; + float smoothVelocityRate; + + idVec3 lastKnownPosition; // last place the enemy was known to be (does not mean visiblity) + + float range; // range to enemy + float range2d; // 2d range to enemy + int changeTime; // Time enemy was last changed + int checkTime; // Time we last checked for a new enemy +} rvAIEnemy_t; + +typedef struct rvAIPassive_s { + struct flags_s { + bool disabled :1; // No advanced passive state + bool multipleIdles :1; // Has multiple idle animations to play + bool fidget :1; // Has fidget animations + } fl; + + idStr animIdlePrefix; + idStr animFidgetPrefix; + idStr animTalkPrefix; + //idStr talkPrefix; + + idStr prefix; + idStr idleAnim; + int idleAnimChangeTime; + int fidgetTime; + int talkTime; +} rvAIPassive_t; + +typedef struct rvAIAttackAnimInfo_s { + idVec3 attackOffset; + idVec3 eyeOffset; +} rvAIAttackAnimInfo_t; + +#define AIACTIONF_ATTACK BIT(0) +#define AIACTIONF_MELEE BIT(1) + +class rvAIActionTimer { +public: + + rvAIActionTimer ( void ); + + bool Init ( const idDict& args, const char* name ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( int currentTime ); + void Reset ( int currentTime, float diversity = 0.0f, float scale = 1.0f ); + void Add ( int _time, float diversity = 0.0f ); + + bool IsDone ( int currentTime ) const; + + int GetTime ( void ) const; + int GetRate ( void ) const; + +protected: + + int time; + int rate; +}; + +ID_INLINE bool rvAIActionTimer::IsDone ( int currentTime ) const { + return currentTime >= time; +} + +ID_INLINE int rvAIActionTimer::GetTime ( void ) const { + return time; +} + +ID_INLINE int rvAIActionTimer::GetRate ( void ) const { + return rate; +} +class rvAIAction { +public: + + rvAIAction ( void ); + + bool Init ( const idDict& args, const char* name, const char* defaultState, int flags ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum EStatus { + STATUS_UNUSED, + STATUS_OK, // action was performed + STATUS_FAIL_DISABLED, // action is current disabled + STATUS_FAIL_TIMER, // actions timer has not finished + STATUS_FAIL_EXTERNALTIMER, // external timer passed in to PerformAction has not finished + STATUS_FAIL_MINRANGE, // enemy is not within minimum range + STATUS_FAIL_MAXRANGE, // enemy is out of maximum range + STATUS_FAIL_CHANCE, // action chance check failed + STATUS_FAIL_ANIM, // bad animation + STATUS_FAIL_CONDITION, // condition given to PerformAction failed + STATUS_FAIL_NOENEMY, // enemy cant be attacked + STATUS_MAX + }; + + struct flags_s { + bool disabled :1; // action disabled? + bool noPain :1; // no pain during action + bool noTurn :1; // no turning during action + bool isAttack :1; // attack? + bool isMelee :1; // melee? + bool overrideLegs :1; // override legs on this action? + bool noSimpleThink :1; // dont use simple think logic for this action + } fl; + + + idStrList anims; + idStr state; + + rvAIActionTimer timer; + + int blendFrames; + int failRate; + + float minRange; + float maxRange; + float minRange2d; + float maxRange2d; + + float chance; + float diversity; + + EStatus status; +}; + +typedef bool (idAI::*checkAction_t)(rvAIAction*,int); + +class rvPlaybackDriver +{ +public: + rvPlaybackDriver( void ) { mPlaybackDecl = NULL; mOldPlaybackDecl = NULL; } + + bool Start( const char *playback, idEntity *owner, int flags, int numFrames ); + bool UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ); + void EndFrame( void ); + bool IsActive( void ) { return( !!mPlaybackDecl || !!mOldPlaybackDecl ); } + + const char *GetDestination( void ); + +// cnicholson: Begin Added save/restore functionality + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +// cnicholson: End Added save/restore functionality + +private: + int mLastTime; + int mTransitionTime; + + int mStartTime; + int mFlags; + const rvDeclPlayback *mPlaybackDecl; + idVec3 mOffset; + + int mOldStartTime; + int mOldFlags; + const rvDeclPlayback *mOldPlaybackDecl; + idVec3 mOldOffset; +}; + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +const float AI_TURN_SCALE = 60.0f; +const float AI_SEEK_PREDICTION = 0.3f; +const float AI_FLY_DAMPENING = 0.15f; +const float AI_HEARING_RANGE = 2048.0f; +const float AI_COVER_MINRANGE = 4.0f; +const float AI_PAIN_LOOP_DELAY = 200; +const int DEFAULT_FLY_OFFSET = 68.0f; + +#define ATTACK_IGNORE 0 +#define ATTACK_ON_DAMAGE BIT(0) +#define ATTACK_ON_ACTIVATE BIT(1) +#define ATTACK_ON_SIGHT BIT(2) + +// defined in script/ai_base.script. please keep them up to date. + +#define DI_NODIR -1 + + +// +// events +// +extern const idEventDef AI_DirectDamage; +extern const idEventDef AI_JumpFrame; +extern const idEventDef AI_EnableClip; +extern const idEventDef AI_DisableClip; +extern const idEventDef AI_EnableGravity; +extern const idEventDef AI_DisableGravity; +extern const idEventDef AI_EnablePain; +extern const idEventDef AI_DisablePain; +extern const idEventDef AI_EnableTarget; +extern const idEventDef AI_DisableTarget; +extern const idEventDef AI_EnableMovement; +extern const idEventDef AI_DisableMovement; +extern const idEventDef AI_Vagary_ChooseObjectToThrow; +extern const idEventDef AI_Speak; +extern const idEventDef AI_SpeakRandom; +extern const idEventDef AI_Attack; +extern const idEventDef AI_AttackMelee; +extern const idEventDef AI_WaitMove; +extern const idEventDef AI_EnableDamage; +extern const idEventDef AI_DisableDamage; +extern const idEventDef AI_LockEnemyOrigin; +extern const idEventDef AI_SetEnemy; +extern const idEventDef AI_ScriptedAnim; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; +extern const idEventDef AI_SetScript; +extern const idEventDef AI_BecomeSolid; +extern const idEventDef AI_BecomePassive; +extern const idEventDef AI_BecomeAggressive; +extern const idEventDef AI_SetHealth; +extern const idEventDef AI_TakeDamage; +extern const idEventDef AI_EnableBlink; +extern const idEventDef AI_DisableBlink; +extern const idEventDef AI_EnableAutoBlink; +extern const idEventDef AI_DisableAutoBlink; + +class idPathCorner; +class idProjectile; +class rvSpawner; +class rvAIHelper; +class rvAITether; + +class idAI : public idActor { +friend class rvAIManager; +friend class idAASFindAttackPosition; +public: + CLASS_PROTOTYPE( idAI ); + + idAI(); + ~idAI(); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Spawn ( void ); + virtual void TalkTo ( idActor *actor ); + + idEntity* GetEnemy ( void ) const; + idEntity* GetGoalEntity ( void ) const; + talkState_t GetTalkState ( void ) const; + const idVec2& GetAttackRange ( void ) const; + const idVec2& GetFollowRange ( void ) const; + int GetTravelFlags ( void ) const; + + void TouchedByFlashlight ( idActor *flashlight_owner ); + + idEntity * FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr = 0.0f ); + void SetSpawner ( rvSpawner* _spawner ); + rvSpawner* GetSpawner ( void ); + + idActor* GetLeader ( void ) const; + + // Outputs a list of all monsters to the console. + static void List_f( const idCmdArgs &args ); + + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + bool IsEnemyVisible ( void ) const; + bool InCoverMode ( void ) const; + bool InCrouchCoverMode ( void ) const; + bool LookAtCoverTall ( void ) const; + bool InLookAtCoverMode ( void ) const; + bool IsBehindCover ( void ) const; + bool IsLipSyncing ( void ) const; + bool IsSpeaking ( void ) const; + bool IsFacingEnt ( idEntity* targetEnt ); + bool IsCoverValid ( void ) const; + virtual bool IsCrouching ( void ) const; + + +public: + + idLinkList simpleThinkNode; + + // navigation + idAAS* aas; + idAASCallback* aasFind; + + // movement + idMoveState move; + idMoveState savedMove; + + // physics + idPhysics_Monster physicsObj; + + // weapon/attack vars + bool lastHitCheckResult; + int lastHitCheckTime; + int lastAttackTime; + float projectile_height_to_distance_ratio; // calculates the maximum height a projectile can be thrown + idList attackAnimInfo; + + mutable idClipModel* projectileClipModel; + idEntityPtr projectile; + + // chatter/talking + int chatterTime; + int chatterRateCombat; + int chatterRateIdle; + talkState_t talkState; + idEntityPtr talkTarget; + talkMessage_t talkMessage; + int talkBusyCount; + int speakTime; + + // Focus + idEntityPtr lookTarget; + aiFocus_t focusType; + idEntityPtr focusEntity; + float focusRange; + int focusAlignTime; + int focusTime; + idVec3 currentFocusPos; + + // Looking + bool allowJointMod; + int alignHeadTime; + int forceAlignHeadTime; + idAngles eyeAng; + idAngles lookAng; + idAngles destLookAng; + idAngles lookMin; + idAngles lookMax; + idList lookJoints; + idList lookJointAngles; + float eyeVerticalOffset; + float eyeHorizontalOffset; + float headFocusRate; + float eyeFocusRate; + + // joint controllers + idAngles eyeMin; + idAngles eyeMax; + jointHandle_t orientationJoint; + + idEntityPtr pusher; + idEntityPtr scriptedActionEnt; + + // script variables + struct aiFlags_s { + bool awake :1; // set to false until state_wakeup is called. + bool damage :1; + bool pain :1; + bool dead :1; + bool activated :1; + bool jump :1; + bool hitEnemy :1; + bool pushed :1; + bool disableAttacks :1; + bool scriptedEndWithIdle :1; + bool scriptedNeverDormant :1; // Prevent going dormant while in scripted sequence + bool scripted :1; + bool simpleThink :1; + bool ignoreFlashlight :1; + bool action :1; + bool lookAtPlayer :1; + bool disableLook :1; + bool undying :1; + bool tetherMover :1; // Currently using a dynamic tether to a mover + bool meleeSuperhero :1; + bool killerGuard :1; // Do 100 points of damage with each hit + } aifl; + + // + // ai/ai.cpp + // + void SetAAS ( void ); + virtual void DormantBegin ( void ); // called when entity becomes dormant + virtual void DormantEnd ( void ); // called when entity wakes from being dormant + virtual void Think ( void ); + void Activate ( idEntity *activator ); + virtual void Hide ( void ); + virtual void Show ( void ); + virtual void AdjustHealthByDamage ( int inDamage ); + void CalculateAttackOffsets ( void ); + + void InitNonPersistentSpawnArgs ( void ); + + /* + =============================================================================== + Speaking & Chatter + =============================================================================== + */ +public: + + bool Speak ( const char *speechDecl, bool random = false ); + void StopSpeaking ( bool stopAnims ); + virtual void CheckBlink ( void ); + +protected: + + virtual bool CanPlayChatterSounds ( void ) const; + void UpdateChatter ( void ); + + + /* + =============================================================================== + Movement + =============================================================================== + */ + + // static helper functions +public: + // Finds a path around dynamic obstacles. + static bool FindPathAroundObstacles ( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ); + // Frees any nodes used for the dynamic obstacle avoidance. + static void FreeObstacleAvoidanceNodes ( void ); + // Predicts movement, returns true if a stop event was triggered. + static bool PredictPath ( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore = NULL ); + // Return true if the trajectory of the clip model is collision free. + static bool TestTrajectory ( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ); + // Finds the best collision free trajectory for a clip model. + static bool PredictTrajectory ( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ); + + + // special flying code + void AdjustFlyingAngles ( void ); + void AddFlyBob ( idVec3 &vel ); + void AdjustFlyHeight ( idVec3 &vel, const idVec3 &goalPos ); + void FlySeekGoal ( idVec3 &vel, idVec3 &goalPos ); + void AdjustFlySpeed ( idVec3 &vel ); + void FlyTurn ( void ); + + // movement types + void Move ( void ); + virtual void DeadMove ( void ); + void AnimMove ( void ); + void SlideMove ( void ); + void PlaybackMove ( void ); + void FlyMove ( void ); + void StaticMove ( void ); + void RVMasterMove ( void ); + void SetMoveType ( moveType_t moveType ); + //twhitaker: added custom move type + virtual void CustomMove ( void ); + + // movement actions + void KickObstacles ( const idVec3 &dir, float force, idEntity *alwaysKick ); + + // steering + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + void GetAnimMoveDelta ( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ); + void CheckObstacleAvoidance ( const idVec3 &goalPos, idVec3 &newPos, idReachability* goalReach=0 ); + bool GetMovePos ( idVec3 &seekPos, idReachability** seekReach=0 ); + + + // navigation + float TravelDistance ( const idVec3 &end ) const; + float TravelDistance ( const idVec3 &start, const idVec3 &end ) const; + float TravelDistance ( idEntity* ent ) const; + float TravelDistance ( idEntity* start, idEntity* end ) const; + int PointReachableAreaNum ( const idVec3 &pos, const float boundsScale = 2.0f ) const; + bool PathToGoal ( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + void BlockedFailSafe ( void ); + + // turning + void Turn ( void ); + bool TurnToward ( float yaw ); + bool TurnToward ( const idVec3 &pos ); + bool TurnTowardLeader ( bool faceLeaderByDefault=false ); + bool FacingIdeal ( void ); + bool DirectionalTurnToward ( const idVec3 &pos ); + + // movement control + bool FaceEnemy ( void ); + bool FaceEntity ( idEntity *ent ); + bool SlideToPosition ( const idVec3 &pos, float time ); + bool WanderAround ( void ); + bool StepDirection ( float dir ); + bool NewWanderDir ( const idVec3 &dest ); + + + /* + =============================================================================== + Reactions + =============================================================================== + */ + void ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ); + void ReactToPain ( idEntity* attacker, int damage ); + + /* + =============================================================================== + AI helpers + =============================================================================== + */ + +public: + + void UpdateHelper ( void ); + rvAIHelper* GetActiveHelper ( void ); + + /* + =============================================================================== + Sensory Perception + =============================================================================== + */ + +public: + + const idVec3& LastKnownPosition ( const idEntity *ent ); + const idVec3& LastKnownFacing ( const idEntity *ent ); + idEntity * HeardSound ( int ignore_team ); + int ReactionTo ( const idEntity *ent ); + void SetLastVisibleEnemyTime ( int time=-1/* DEFAULT IS CURRENT TIME*/ ); + bool IsEnemyRecentlyVisible ( float maxLostVisTimeScale = 1.0f ) const; + + /* + =============================================================================== + Passive + =============================================================================== + */ + +public: + + void SetTalkState ( talkState_t state ); + void SetPassivePrefix ( const char* prefix ); + +protected: + + bool GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ); + + /* + =============================================================================== + Combat + =============================================================================== + */ + +public: + + // enemy managment + bool SetEnemy ( idEntity *newEnemy ); + void ClearEnemy ( bool dead = false ); + + void UpdateEnemy ( void ); + void UpdateEnemyPosition ( bool force = true ); + void UpdateEnemyVisibility ( void ); + + // Attack direction + bool GetAimDir ( const idVec3& source, const idEntity* aimAtEnt, const idDict* projectileDict, idEntity *ignore, idVec3 &aimDir, float aimOffset, float predict ) const; + void GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const; + + // damage + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool CheckDeathCausesMissionFailure ( void ); + + // attacks + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ); + virtual bool AttackMelee ( const char* attackName, const idDict* meleeDict ); + + void CreateProjectileClipModel ( void ) const; + idProjectile* CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ); + void RemoveProjectile ( void ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + void DirectDamage ( const char *meleeDefName, idEntity *ent ); + bool TestMelee ( void ) const; + void PushWithAF ( void ); + bool IsMeleeNeeded ( void ); + + // special effects + void GetMuzzle ( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ); + void LerpLookAngles ( idAngles &curAngles, idAngles newAngles, float orientationJointYaw, float focusRate ); + virtual bool UpdateAnimationControllers ( void ); + + // AI script state management + void UpdateStates ( void ); + void UpdateFocus ( const idMat3& orientationAxis ); + void SetFocus ( aiFocus_t focus, int time ); + + // event? + virtual void FootStep ( void ); + + void CreateMissile ( jointHandle_t joint ); + void RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void BecomeSolid ( void ); + void BecomeNonSolid ( void ); + const char * ChooseAnim ( int channel, const char *animname ); + + // + // ai/ai_events.cpp + // +public: + + + virtual bool CanTakeDamage ( void ) const; + virtual bool CanTakePain ( void ) const; + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual bool CanAnnounce ( float chance ) const; + + virtual bool SkipCurrentDestination ( void ) const; + +// ----------------------------- Functions ------------------------------------ + + virtual bool DoDormantTests ( void ); + + void OverrideFlag ( aiFlagOverride_t flag, bool value ); + void RestoreFlag ( aiFlagOverride_t flag ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual const char* GetIdleAnimName ( void ); + + bool RespondToFlashlight ( void ) { return !aifl.ignoreFlashlight;} + bool ForceFaceEnemy ( void ) { return ( move.moveCommand == MOVE_TO_ENEMY ); } + + bool CanBecomeSolid ( void ); + bool CanHitEnemyFromAnim ( int animNum, idVec3 offset = vec3_origin ); + bool CanHitEnemy ( void ); + bool CanHitEnemyFromJoint ( const char *jointname ); + + float GetTurnDelta ( void ); + + int TestTrajectory ( const idVec3 &firePos, const idVec3 &target, const char *projectileName ); + bool TestAnimMove ( int animNum, idEntity *ignore = NULL, idVec3 *pMoveVec = NULL ); + void ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm = NULL ); + void SetLeader ( idEntity *newLeader ); + + int CheckMelee ( bool disableAttack ); + bool CheckForEnemy ( bool useFov, bool force = false ); + bool CheckForCloserEnemy ( void ); + bool CheckForReplaceEnemy ( idEntity* replacement ); + bool CheckForTeammateEnemy ( void ); + + void DrawSuspicion ( void ); + float RateSuspiciousness ( idActor* shady, bool rateSound = false ); + void RateSuspicionLevel ( void ); + + void UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ); + + void LookAtEntity ( idEntity *ent, float duration ); + +// ----------------------------- Variables ------------------------------------ + + int actionAnimNum; // Index of animation to use for the upcoming action + int actionTime; // Time line for actions (time is stopped when an action is running) + int actionSkipTime; // Time to use if an action is skipped by another + + int flagOverrides; + + float announceRate; // How often (0 - 1.0f) the AI will make certain announcements. + + rvAICombat_t combat; // Members related to combat state + rvAIPassive_t passive; // Members related to passive state + rvAIEnemy_t enemy; // Members related to tracking enemies + rvAIPain_t pain; + rvAIFuncs_t funcs; + + rvPlaybackDriver mPlayback; + rvPlaybackDriver mLookPlayback; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr tether; + idEntityPtr helperCurrent; + idEntityPtr helperIdeal; + idEntityPtr leader; + idEntityPtr spawner; + + bool ValidateCover ( void ); + + virtual bool UpdateRunStatus ( void ); + bool UpdateTactical ( int delay = 0 ); + void ForceTacticalUpdate ( void ); + bool UpdateTactical_r ( void ); + virtual int FilterTactical ( int availableTactical ); + void WakeUpTargets ( void ); + + virtual aiCTResult_t CheckTactical ( aiTactical_t tactical ); + + void Begin ( void ); + void WakeUp ( void ); + + virtual void Prethink ( void ); + virtual void Postthink ( void ); + + /* + =============================================================================== + Threat Management + =============================================================================== + */ + +protected: + + virtual void UpdateThreat ( void ); + virtual float CalculateEnemyThreat ( idEntity* enemy ); + + /* + =============================================================================== + Tethers + =============================================================================== + */ + +public: + + virtual bool IsTethered ( void ) const; + bool IsWithinTether ( void ) const; + rvAITether* GetTether ( void ); + virtual void SetTether ( rvAITether* newTether ); + + /* + =============================================================================== + Scripting + =============================================================================== + */ + +public: + + void ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void ScriptedStop ( void ); + + void SetScript ( const char* scriptName, const char* funcName ); + +private: + + bool ScriptedBegin ( bool endWithIdle, bool allowDormant = false ); + void ScriptedEnd ( void ); + + /* + =============================================================================== + Handlers + =============================================================================== + */ + +protected: + + virtual void OnDeath ( void ); + virtual void OnStateChange ( int channel ); + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnLeaderChange ( idEntity* oldLeader ); + virtual void OnStartMoving ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnFriendlyFire ( idActor* attacker ); + virtual void OnWakeUp ( void ); + virtual void OnTouch ( idEntity *other, trace_t *trace ); + virtual void OnCoverInvalidated ( void ); + virtual void OnCoverNotFacingEnemy ( void ); + virtual void OnEnemyVisiblityChange ( bool oldVisible ); + virtual void OnStartAction ( void ); + virtual void OnStopAction ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + /* + =============================================================================== + Movement / Turning + =============================================================================== + */ + +protected: + + bool StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ); + void StopMove ( moveStatus_t status ); + + bool MoveTo ( const idVec3 &pos, float range = 0.0f ); + bool MoveToAttack ( idEntity *ent, int attack_anim ); + bool MoveToTether ( rvAITether* tether ); + virtual bool MoveToEnemy ( void ); + bool MoveToEntity ( idEntity *ent, float range = 0.0f ); + bool MoveToCover ( float minRange, float maxRange, aiTactical_t coverType ); + bool MoveToHide ( void ); + + bool MoveOutOfRange ( idEntity *entity, float range, float minRange=0.0f ); + + void AnimTurn ( float angles, bool force ); + + bool ReachedPos ( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range = 0.0f ) const; + + /* + =============================================================================== + Debug + =============================================================================== + */ + +public: + + void DrawRoute ( void ) const; + void DrawTactical ( void ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + +public: + + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + void AnnounceNewEnemy ( void ); + void AnnounceKill ( idActor *victim ); + void AnnounceTactical ( aiTactical_t newTactical ); + void AnnounceSuppressed ( idActor *suppressor ); + void AnnounceSuppressing ( void ); + void AnnounceFlinch ( idEntity *attacker ); + void AnnounceInjured ( void ); + void AnnounceFriendlyFire ( idActor* attacker ); + void AnnounceGrenade ( void ); + void AnnounceGrenadeThrow ( void ); + + /* + =============================================================================== + Actions + =============================================================================== + */ + +protected: + + rvAIActionTimer actionTimerRangedAttack; + rvAIActionTimer actionTimerEvade; + rvAIActionTimer actionTimerSpecialAttack; + rvAIActionTimer actionTimerPain; + + rvAIAction actionEvadeLeft; + rvAIAction actionEvadeRight; + rvAIAction actionRangedAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionLeapAttack; + rvAIAction actionJumpBack; + + bool UpdateAction ( void ); + virtual bool CheckActions ( void ); + virtual bool CheckPainActions ( void ); + + bool PerformAction ( rvAIAction* action, bool (idAI::*)(rvAIAction*,int), rvAIActionTimer* timer = NULL ); + void PerformAction ( const char* stateName, int blendFrames = 0, bool noPain = false ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + virtual void Event_PostSpawn ( void ); + // RAVEN END + +public: + + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + + /* + =============================================================================== + Events + =============================================================================== + */ + +private: + + // Orphaned events + void Event_ClosestReachableEnemyOfEntity ( idEntity *team_mate ); + void Event_GetReachableEntityPosition ( idEntity *ent ); + void Event_EntityInAttackCone ( idEntity *ent ); + void Event_TestAnimMoveTowardEnemy ( const char *animname ); + void Event_TestAnimMove ( const char *animname ); + void Event_TestMoveToPosition ( const idVec3 &position ); + void Event_TestMeleeAttack ( void ); + void Event_TestAnimAttack ( const char *animname ); + void Event_SaveMove ( void ); + void Event_RestoreMove ( void ); + void Event_ThrowMoveable ( void ); + void Event_ThrowAF ( void ); + void Event_PredictEnemyPos ( float time ); + void Event_FindActorsInBounds ( const idVec3 &mins, const idVec3 &maxs ); + + void Event_Activate ( idEntity *activator ); + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_LookAt ( idEntity* lookAt ); + + void Event_SetAngles ( idAngles const &ang ); + void Event_SetEnemy ( idEntity *ent ); + void Event_SetHealth ( float newHealth ); + void Event_SetTalkTarget ( idEntity *target ); + void Event_SetTalkState ( int state ); + void Event_SetLeader ( idEntity *newLeader ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetMoveSpeed ( int speed ); + void Event_SetPassivePrefix ( const char* prefix ); + + void Event_GetAngles ( void ); + void Event_GetEnemy ( void ); + void Event_GetLeader ( void ); + + void Event_Attack ( const char* attackName, const char* jointName ); + void Event_AttackMelee ( const char *meleeDefName ); + + void Event_DirectDamage ( idEntity *damageTarget, const char *damageDefName ); + void Event_RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void Event_CanBecomeSolid ( void ); + void Event_BecomeSolid ( void ); + void Event_BecomeNonSolid ( void ); + void Event_BecomeRagdoll ( void ); + void Event_StopRagdoll ( void ); + void Event_FaceEnemy ( void ); + void Event_FaceEntity ( idEntity *ent ); + void Event_WaitMove ( void ); + + void Event_BecomePassive ( int ignoreEnemies ); + void Event_BecomeAggressive ( void ); + + void Event_EnableDamage ( void ); + void Event_EnableClip ( void ); + void Event_EnableGravity ( void ); + void Event_EnableAFPush ( void ); + void Event_EnablePain ( void ); + void Event_DisableDamage ( void ); + void Event_DisableClip ( void ); + void Event_DisableGravity ( void ); + void Event_DisableAFPush ( void ); + void Event_DisablePain ( void ); + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + void Event_TakeDamage ( float takeDamage ); + void Event_SetUndying ( float setUndying ); + void Event_EnableAutoBlink ( void ); + void Event_DisableAutoBlink ( void ); + + void Event_LockEnemyOrigin ( void ); + + void Event_StopThinking ( void ); + void Event_JumpFrame ( void ); + void Event_RealKill ( void ); + void Event_Kill ( void ); + void Event_RemoveUpdateSpawner ( void ); + void Event_AllowHiddenMovement ( int enable ); + void Event_CanReachPosition ( const idVec3 &pos ); + void Event_CanReachEntity ( idEntity *ent ); + void Event_CanReachEnemy ( void ); + + void Event_IsSpeaking ( void ); + void Event_IsTethered ( void ); + void Event_IsWithinTether ( void ); + void Event_IsMoving ( void ); + + void Event_Speak ( const char *speechDecl ); + void Event_SpeakRandom ( const char *speechDecl ); + + void Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void Event_ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_ScriptedJumpDown ( float yaw ); + + void Event_FindEnemy ( float distSquare ); + void Event_SetKey ( const char *key, const char *value ); + + + /* + =============================================================================== + States + =============================================================================== + */ + +protected: + + // Wait states + stateResult_t State_Wait_Activated ( const stateParms_t& parms ); + stateResult_t State_Wait_ScriptedDone ( const stateParms_t& parms ); + stateResult_t State_Wait_Action ( const stateParms_t& parms ); + stateResult_t State_Wait_ActionNoPain ( const stateParms_t& parms ); + + // Global states + stateResult_t State_WakeUp ( const stateParms_t& parms ); + stateResult_t State_TriggerAnim ( const stateParms_t& parms ); + stateResult_t State_Wander ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + stateResult_t State_LightningDeath ( const stateParms_t& parms ); + stateResult_t State_Burn ( const stateParms_t& parms ); + stateResult_t State_Remove ( const stateParms_t& parms ); + + stateResult_t State_Passive ( const stateParms_t& parms ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_CombatCover ( const stateParms_t& parms ); + stateResult_t State_CombatMelee ( const stateParms_t& parms ); + stateResult_t State_CombatRanged ( const stateParms_t& parms ); + stateResult_t State_CombatTurret ( const stateParms_t& parms ); + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + + stateResult_t State_MovePlayerPush ( const stateParms_t& parms ); + stateResult_t State_MoveTether ( const stateParms_t& parms ); + stateResult_t State_MoveFollow ( const stateParms_t& parms ); + + stateResult_t State_ScriptedMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + stateResult_t State_ScriptedStop ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackAim ( const stateParms_t& parms ); + stateResult_t State_ScriptedJumpDown ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_Sight ( const stateParms_t& parms ); + stateResult_t State_Torso_CustomCycle ( const stateParms_t& parms ); + stateResult_t State_Torso_Action ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishAction ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ScriptedAnim ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveIdle ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveFidget ( const stateParms_t& parms ); + + // Leg States + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnLeft ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnRight ( const stateParms_t& parms ); + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_Legs_MoveThink ( const stateParms_t& parms ); + stateResult_t State_Legs_ChangeDirection ( const stateParms_t& parms ); + + // Head states + stateResult_t State_Head_Idle ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( idAI ); +}; + +/* +=============================================================================== + + idAI Inlines + +=============================================================================== +*/ + +ID_INLINE int DelayTime( int min, int range ) { + return min + gameLocal.random.RandomInt ( range + 1 ); +} + +ID_INLINE const idVec2& idAI::GetAttackRange ( void ) const { + return combat.attackRange; +} + +ID_INLINE const idVec2& idAI::GetFollowRange ( void ) const { + return move.followRange; +} + +ID_INLINE int idAI::GetTravelFlags ( void ) const { + return move.travelFlags; +} + +ID_INLINE bool idAI::IsEnemyVisible ( void ) const { + return enemy.ent && enemy.fl.visible; +} + +ID_INLINE bool idAI::IsEnemyRecentlyVisible( float maxLostVisTimeScale ) const { + return (enemy.ent + && combat.fl.seenEnemyDirectly + && (enemy.lastVisibleTime && gameLocal.time-enemy.lastVisibleTime < (combat.maxLostVisTime * maxLostVisTimeScale))); +} + +ID_INLINE bool idAI::LookAtCoverTall( void ) const { + return ( aasSensor->Look() + && (aasSensor->Look()->flags&FEATURE_LOOK_OVER) + && aasSensor->Look()->height > 40.0f ); +} + +ID_INLINE bool idAI::InLookAtCoverMode ( void ) const { + return (!IsBehindCover() + && !aifl.action + && move.fl.moving + && (combat.fl.alert || combat.fl.aware) + && aasSensor->Look() + && combat.tacticalCurrent != AITACTICAL_MELEE + && combat.tacticalCurrent != AITACTICAL_HIDE + && !IsEnemyRecentlyVisible(0.2f)); +} + +ID_INLINE bool idAI::InCoverMode ( void ) const { + return ( (1<Reserved ( ); +} + +ID_INLINE bool idAI::InCrouchCoverMode ( void ) const { + return ( InCoverMode() && (aasSensor->Reserved()->flags&FEATURE_LOOK_OVER) ); +} + +ID_INLINE bool idAI::IsBehindCover ( void ) const { + return ( InCoverMode() && move.fl.done && (aifl.action || DistanceTo2d ( aasSensor->ReservedOrigin() ) < AI_COVER_MINRANGE) ); +} + +ID_INLINE bool idAI::IsSpeaking ( void ) const { + return speakTime && gameLocal.time < speakTime; +} + +ID_INLINE bool idAI::IsFacingEnt ( idEntity* targetEnt ) { + return( move.moveCommand == MOVE_FACE_ENTITY && move.goalEntity == targetEnt && FacingIdeal() ); +} + +ID_INLINE bool idAI::IsCoverValid ( ) const { + return combat.coverValidTime && (gameLocal.time - combat.coverValidTime < combat.maxInvalidCoverTime); +} + +ID_INLINE idActor* idAI::GetLeader ( void ) const { + return leader; +} + +ID_INLINE idEntity* idAI::GetGoalEntity ( void ) const { + return move.goalEntity; +} + +ID_INLINE bool idAI::CanTakeDamage( void ) const { + return idActor::CanTakeDamage( ); +} + +ID_INLINE bool idAI::CanTakePain ( void ) const { + return !disablePain; +} + +ID_INLINE bool idAI::CanTurn ( void ) const { + return move.turnRate && !move.fl.noTurn && !move.fl.disabled; +} + +ID_INLINE bool idAI::CanMove ( void ) const { + return !move.fl.disabled && !move.fl.blocked && gameLocal.GetTime()>move.blockTime; +} + +ID_INLINE bool idAI::CanAnnounce ( float chance ) const { + return !aifl.dead && !IsSpeaking ( ) && !af.IsActive ( ) && !combat.fl.noChatter && ( gameLocal.random.RandomFloat() < chance ); +} + +ID_INLINE void idAI::SetFocus ( aiFocus_t focus, int time ) { + focusType = focus; + focusTime = gameLocal.time + time; +} + +ID_INLINE idEntity *idAI::GetEnemy( void ) const { + return enemy.ent; +} + +ID_INLINE void idAI::ForceTacticalUpdate ( void ) { + combat.tacticalUpdateTime = 0; + combat.tacticalMaskUpdate = 0; + delete aasFind; + aasFind = NULL; +} + +/* +=============================================================================== + + externs + +=============================================================================== +*/ + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; +extern const char* aiTalkMessageString [ ]; +extern const char* aiTacticalString [ AITACTICAL_MAX ]; +extern const char* aiMoveCommandString [ NUM_MOVE_COMMANDS ]; +extern const char* aiFocusString [ AIFOCUS_MAX ]; + +#endif /* !__AI_H__ */ + diff --git a/source/game/ai/AI_Actions.cpp b/source/game/ai/AI_Actions.cpp new file mode 100644 index 0000000..a1f6883 --- /dev/null +++ b/source/game/ai/AI_Actions.cpp @@ -0,0 +1,592 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + + rvAIActionTimer + +=============================================================================== +*/ + +/* +================ +rvAIActionTimer::rvAIActionTimer +================ +*/ +rvAIActionTimer::rvAIActionTimer ( void ) { + time = 0; +} + +/* +================ +rvAIActionTimer::Init +================ +*/ +bool rvAIActionTimer::Init ( const idDict& args, const char* name ) { + rate = SEC2MS ( args.GetFloat ( va("%s_rate",name), "0" ) ); + return true; +} + +/* +================ +rvAIActionTimer::Save +================ +*/ +void rvAIActionTimer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( rate ); + savefile->WriteInt ( time ); +} + +/* +================ +rvAIActionTimer::Restore +================ +*/ +void rvAIActionTimer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( rate ); + savefile->ReadInt ( time ); +} + +/* +================ +rvAIActionTimer::Reset +================ +*/ +void rvAIActionTimer::Reset ( int currentTime, float diversity, float scale ) { + float _rate = rate * scale; + time = currentTime + (-gameLocal.random.RandomInt( 2.0f * _rate * diversity ) + _rate * diversity + _rate); +} + +/* +================ +rvAIActionTimer::Clear +================ +*/ +void rvAIActionTimer::Clear ( int currentTime ) { + time = currentTime; +} + +/* +================ +rvAIActionTimer::Add +================ +*/ +void rvAIActionTimer::Add ( int _time, float diversity ) { + time += (-gameLocal.random.RandomInt( 2.0f * _time * diversity ) + _time * diversity + _time); +} + +/* +=============================================================================== + + rvAIAction + +=============================================================================== +*/ + +/* +================ +rvAIAction::rvAIAction +================ +*/ +rvAIAction::rvAIAction ( void ) { + memset ( &fl, 0, sizeof(fl) ); + status = STATUS_UNUSED; +} + +/* +================ +rvAIAction::Init +================ +*/ +bool rvAIAction::Init ( const idDict& args, const char* name, const char* defaultState, int _flags ) { + const idKeyValue* kv; + + if ( _flags & AIACTIONF_ATTACK ) { + fl.isAttack = true; + } + if ( _flags & AIACTIONF_MELEE ) { + fl.isMelee = true; + } + + // Initialize timer + timer.Init ( args, name ); + + // Is this action enabled? + fl.disabled = !args.GetBool ( va("%s",name), "0" ); + fl.noPain = args.GetBool ( va("%s_nopain",name), "0" ); + fl.noTurn = args.GetBool ( va("%s_noturn",name), "1" ); + fl.overrideLegs = args.GetBool ( va("%s_overrideLegs",name), "1" ); + fl.noSimpleThink = args.GetBool ( va("%s_nosimplethink",name), "0" ); + + blendFrames = args.GetInt ( va("%s_blendFrames",name), "4" ); + failRate = SEC2MS ( args.GetInt ( va("%s_failRate",name), ".1" ) ); + + minRange = args.GetInt ( va("%s_minRange",name), "0" ); + maxRange = args.GetInt ( va("%s_maxRange",name), (_flags & AIACTIONF_ATTACK ) ? "-1" : "0" ); + minRange2d = args.GetInt ( va("%s_minRange2d",name), "0" ); + maxRange2d = args.GetInt ( va("%s_maxRange2d",name), "0" ); + + chance = args.GetFloat ( va("%s_chance",name), "1" ); + diversity = args.GetFloat ( va("%s_diversity", name ), ".5" ); + + // action state + state = args.GetString ( va("%s_state",name), (!defaultState||!*defaultState) ? "Torso_Action" : defaultState ); + + // allow for multiple animations + const char* prefix = va("%s_anim",name); + for ( kv = args.MatchPrefix ( prefix, NULL ); kv; kv = args.MatchPrefix ( prefix, kv ) ) { + if ( kv->GetValue ( ).Length ( ) ) { + anims.Append ( kv->GetValue ( ) ); + } + } + + return true; +} + +/* +================ +rvAIAction::Save +================ +*/ +void rvAIAction::Save ( idSaveGame *savefile ) const { + int i; + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteInt ( anims.Num ( ) ); + for ( i = 0; i < anims.Num(); i ++ ) { + savefile->WriteString ( anims[i] ); + } + savefile->WriteString ( state ); + + timer.Save ( savefile ); + + savefile->WriteInt ( blendFrames ); + savefile->WriteInt ( failRate ); + + savefile->WriteFloat ( minRange ); + savefile->WriteFloat ( maxRange ); + savefile->WriteFloat ( minRange2d ); + savefile->WriteFloat ( maxRange2d ); + + savefile->WriteFloat ( chance ); + savefile->WriteFloat ( diversity ); + + savefile->WriteInt ( (int)status ); +} + +/* +================ +rvAIAction::Restore +================ +*/ +void rvAIAction::Restore ( idRestoreGame *savefile ) { + int num; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadInt ( num ); + anims.Clear ( ); + anims.SetNum ( num ); + for ( num--; num >= 0; num -- ) { + savefile->ReadString ( anims[num] ); + } + savefile->ReadString ( state ); + + timer.Restore ( savefile ); + + savefile->ReadInt ( blendFrames ); + savefile->ReadInt ( failRate ); + + savefile->ReadFloat ( minRange ); + savefile->ReadFloat ( maxRange ); + savefile->ReadFloat ( minRange2d ); + savefile->ReadFloat ( maxRange2d ); + + savefile->ReadFloat ( chance ); + savefile->ReadFloat ( diversity ); + + savefile->ReadInt ( (int&)status ); +} + +/* +=============================================================================== + + Actions + +=============================================================================== +*/ + +/* +================ +idAI::CheckAction_EvadeLeft +================ +*/ +bool idAI::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + // TODO: dont evade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_EvadeRight +================ +*/ +bool idAI::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_JumpBack +================ +*/ +bool idAI::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage ) { + return false; + } + // TODO: enemy must be in front to jump backwards + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_RangedAttack +================ +*/ +bool idAI::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_MeleeAttack +================ +*/ +bool idAI::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_LeapAttack +================ +*/ +bool idAI::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( enemy.range > 64.0f && !TestAnimMove ( animNum, enemy.ent ) ) { + return false; + } + // Must be looking right at the enemy to leap + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 4 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::UpdateAction +================ +*/ +bool idAI::UpdateAction ( void ) { + // Update action MUST be called from the main state loop + assert ( stateThread.IsExecuting ( ) ); + + // If an action is already running then dont let another start + if ( aifl.action ) { + return false; + } + + return CheckActions ( ); +} + +/* +================ +idAI::CheckPainActions +================ +*/ +bool idAI::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + return false; + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +idAI::CheckActions +================ +*/ +bool idAI::CheckActions ( void ) { + + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // Actions are limited at a cover position to shooting and leaning + if ( IsBehindCover ( ) ) { + // Test ranged attack first + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +idAI::PerformAction +================ +*/ +void idAI::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + if ( legsAnim.Disabled() ) { + //MCG: Hmmm... I hope this doesn't break anything, but if an action happens *right* + // at the end of a trigger_anim, then the legs will be enabled (by the SetAnimState + // on the torso) with no state! The actor will then be stuck in place until + // something actually sets the legsAnim state... so let's check for disabled and + // set a default state right here...? + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + } + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + + // Main state will wait until action is finished before continuing + if ( noPain ) { + InterruptState ( "Wait_ActionNoPain" ); + } else { + InterruptState ( "Wait_Action" ); + } + + OnStartAction ( ); +} + +bool idAI::PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + // If we arent ignoring simple think then dont perform this action on a simple think frame + if ( !action->fl.noSimpleThink && aifl.simpleThink ) { + return false; + } + + // Is the action disabled? + if ( action->fl.disabled ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + + // Action timers still running? + if ( !action->timer.IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_TIMER; + return false; + } + + if ( timer && !timer->IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_EXTERNALTIMER; + return false; + } + + // Special code for attacks + if ( action->fl.isAttack ) { + // Attacks disabled? + if ( ai_disableAttacks.GetBool() ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + // No attack actions if we have no enemy or our enemy cant be hurt + if ( !enemy.ent || enemy.ent->health <= 0 ) { + action->status = rvAIAction::STATUS_FAIL_NOENEMY; + return false; + } + } + + // Min Range check + if ( action->minRange ) { + if ( !enemy.ent || !enemy.range || enemy.range < action->minRange ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + if ( action->minRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d < action->minRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + + // Max Range check + if ( action->maxRange != 0 ) { + float maxrange = action->maxRange == -1 ? combat.attackRange[1] : action->maxRange; + if ( !enemy.ent || !enemy.range || enemy.range > maxrange ) { + if ( action->fl.isMelee && GetEnemy() ) { + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = GetEnemy()->GetPhysics()->GetOrigin(); + idBounds enemyBounds = GetEnemy()->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } else { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + } + if ( action->maxRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d > action->maxRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + + int animNum; + if ( action->anims.Num ( ) ) { + // Pick a random animation from the list + animNum = GetAnim ( ANIMCHANNEL_TORSO, action->anims[gameLocal.random.RandomInt(action->anims.Num())] ); + if ( !animNum ) { + action->status = rvAIAction::STATUS_FAIL_ANIM; + return false; + } + } else { + animNum = -1; + } + + // Random chance? + if ( action->chance < 1.0f && gameLocal.random.RandomFloat ( ) > action->chance ) { + action->status = rvAIAction::STATUS_FAIL_CHANCE; + action->timer.Clear ( actionTime ); + action->timer.Add ( 100 ); + return false; + } + + // Check the condition + if ( condition && !(this->*(condition)) ( action, animNum ) ) { + action->status = rvAIAction::STATUS_FAIL_CONDITION; + action->timer.Clear ( actionTime ); + action->timer.Add ( action->failRate ); + return false; + } + + // Disallow turning during action? + if ( action->fl.noTurn ) { + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + } + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + // Override legs for this state? + if ( action->fl.overrideLegs ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + + // When attacking scale the time by the aggression scale + float scale; + if ( action->fl.isAttack ) { + scale = 2.0f - combat.aggressiveScale; + } else { + scale = 1.0f; + } + + // Restart the action timer using the length of the animation being played + action->timer.Reset ( actionTime, action->diversity, scale ); + + // If the action gets interrupted it will be skipped, if it is then move the action timers forward + // by half of its normal delay to allow it to be performed again quicker than usual. This also allows + // other actions that may be still pending to be performed and thus cause less of a pause after taking pain. + actionSkipTime = (action->timer.GetTime ( ) + actionTime) / 2; + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + + action->status = rvAIAction::STATUS_OK; + + actionAnimNum = animNum; + + return true; +} diff --git a/source/game/ai/AI_Announcements.cpp b/source/game/ai/AI_Announcements.cpp new file mode 100644 index 0000000..e68f943 --- /dev/null +++ b/source/game/ai/AI_Announcements.cpp @@ -0,0 +1,554 @@ +/* +================ + +AI_Announcements.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +/* +===================== +idAI::AnnounceNewEnemy + +Announce that we are going to shooting at a different enemy +===================== +*/ +void idAI::AnnounceNewEnemy( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + if ( !enemy.ent || !enemy.ent->IsType( idActor::GetClassType() ) ) { + return; + } + + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY ) ) { + return; + } + + // Check to see if we should announce the new enemy as a sniper + // jshepard: Disabled by request + /* + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER ) && enemy.ent->spawnArgs.GetBool ( "sniper" ) ) { + // Should we announce the sniper as a high up sniper? + if ( (enemy.lastKnownPosition - physicsObj.GetOrigin()) * physicsObj.GetGravityNormal() >= 250.0f ) { + if ( Speak ( "lipsync_high_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + return; + } + + // Just announce the sniper + if ( Speak ( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + */ + + idActor* newEnemyAct = static_cast(enemy.ent.GetEntity()); + bool result = false; + + //first see if the new enemy is behind our buddy closest to the enemy + idActor* teammate = aiManager.NearestTeammateToPoint( this, newEnemyAct->GetPhysics()->GetOrigin(), false, 200.0f ); + if ( teammate ) { + if ( aiManager.ActorIsBehindActor( newEnemyAct, teammate ) ) { + result = Speak ( "lipsync_enemy_back", true ); + } + } + + // If we havent spoken yet then find a teammat near us that we can tell about our enemy and + // determine the best announcement to make based on their location + if ( !result ) { + teammate = aiManager.NearestTeammateToPoint( this, GetPhysics()->GetOrigin(), false, 300 ); + if ( teammate ) { + if ( newEnemyAct->GetPhysics()->GetOrigin().z-GetPhysics()->GetOrigin().z >= 250.0f ) { + result = Speak ( "lipsync_enemy_high", true ); + } + + // IF we still havent spoken and we're talking to the player, give him directional info + if ( !result && teammate->IsType( idPlayer::GetClassType() ) ) { + idPlayer* teamPlayer = static_cast(teammate); + idVec3 dir = newEnemyAct->GetPhysics()->GetOrigin()-teamPlayer->GetPhysics()->GetOrigin(); + dir.z = 0; + dir.NormalizeFast(); + idVec3 fwd = teamPlayer->viewAxis[0]; + fwd.z = 0.0f; + fwd.NormalizeFast(); + idVec3 lt = teamPlayer->viewAxis[1]; + lt.z = 0.0f; + lt.NormalizeFast(); + if ( fabs( dir * fwd ) < 0.4f ) { + // more to the side than the front + if ( dir * lt > 0 ) { + result = Speak ( "lipsync_enemy_left", true ); + } else { + result = Speak ( "lipsync_enemy_right", true ); + } + } + } + + // If we still havent spoken, just announce the enemy + if ( !result ) { + //FIXME: check right & left? + //FIXME: check for surrounded? + result = Speak ( "lipsync_enemy_default", true ); + } + } + } + + // If we spoke then set the timer + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY, 2000 ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announce that we have killed our enemy +===================== +*/ +void idAI::AnnounceKill( idActor* victim ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // If already speaking or one the same team, dont announce the kill + if ( victim->team == team ) { + return; + } + //jshepard: "Watch It" sounds have been cut. Replaced with Victory. + + // Generic annoucement of enemy's death + Speak( "lipsync_victory", true ); + + + return; + +/* + // If the victim is targetting the player and was close to the player when he died + // then announce to the player to be careful + if ( victim->IsType( idAI::Type ) ) { + idAI* vicAI = static_cast(victim); + if ( vicAI && vicAI->GetEnemy() && vicAI->GetEnemy()->IsType ( idPlayer::GetClassType() ) ) { + idPlayer* vicEnemyPlayer = static_cast(vicAI->GetEnemy()); + if ( vicEnemyPlayer->team == team ) { + idVec3 diff = vicAI->GetPhysics()->GetOrigin() - vicEnemyPlayer->GetPhysics()->GetOrigin(); + if ( !vicEnemyPlayer->CheckFOV ( vicAI->GetPhysics()->GetOrigin() ) || diff.LengthSqr() < 300.0f * 300.0f ) { + Speak( "lipsync_watchit", true ); + return; + } + } + } + } + + // Chance that we say "watch it" to the closest teammate to him if the guy that died was facing the teammate + // when he died (ie, was a possible threat) + idActor* teammate = aiManager.NearestTeammateToPoint( this, victim->GetPhysics()->GetOrigin(), false, 300.0f ); + if ( teammate && victim->CheckFOV(teammate->GetPhysics()->GetOrigin()) ) { + if ( gameLocal.random.RandomInt(2) < 1 ) { + Speak( "lipsync_watchit", true ); + return; + } + } +*/ + +} + +/* +===================== +idAI::AnnounceTactical + +Announce the changing of tactical status +===================== +*/ +void idAI::AnnounceTactical( aiTactical_t newTactical ) { + bool result = false; + + // If already speaking dont bother + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL ) ) { + return; + } + + switch ( newTactical ) { + case AITACTICAL_MELEE: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_rush", true ); + } + break; +/* + case AITACTICAL_COVER: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_cover", true ); + } + break; + case AITACTICAL_COVER_FLANK: + result = Speak( "lipsync_flank", true ); + break; + case AITACTICAL_COVER_ADVANCE: + result = Speak( "lipsync_moveup", true ); + break; + case AITACTICAL_COVER_RETREAT: + result = Speak( "lipsync_fallback", true ); + break; + case AITACTICAL_COVER_AMBUSH: + break; + case AITACTICAL_RANGED: + break; + case AITACTICAL_TURRET: + break; + case AITACTICAL_HIDE: + result = Speak( "lipsync_cover", true ); + break; +*/ + } + + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL, 2000 ); + } +} + +/* +===================== +idAI::AnnounceSuppressed + +Announce that someone is using supressing fire on us +===================== +*/ +void idAI::AnnounceSuppressed( idActor *suppressor ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if we are already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED ) ) { + return; + } + + //FIXME: check crossfire? + if ( Speak( "lipsync_supressed", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED, 3000 ); + } +*/ + +} + +/* +===================== +idAI::AnnounceSuppressing + +Announce that we are about to use supressing fire +===================== +*/ +void idAI::AnnounceSuppressing( void ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING ) ) { + return; + } + + //Make the guy being shot at know this + if ( enemy.ent->IsType( idAI::Type ) ) { + idAI* enemyAI = dynamic_cast(enemy.ent.GetEntity()); + if ( enemyAI ) { + enemyAI->AnnounceSuppressed( this ); + } + } + + if ( Speak( "lipsync_supressing", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING, 5000 ); + } +*/ +} + +/* +===================== +idAI::AnnounceFlinch + +Announce that an attack just missed us +===================== +*/ +void idAI::AnnounceFlinch( idEntity *attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + idActor* attackActor = dynamic_cast(attacker); + + // Friendly fire? + if ( attackActor && attackActor->team == team ) { + if ( gameLocal.random.RandomFloat() < 0.2f ) { + AnnounceFriendlyFire( attackActor ); + } + } +//jshepard: sniper announcement removed by request +/* + else if ( attackActor->spawnArgs.GetBool ( "sniper" ) ) { + //TEMP: static debounce timer + static int lastPlayed2 = 0; + if ( gameLocal.time - lastPlayed2 < 10000 ) { + return; + } + lastPlayed2 = gameLocal.time; + Speak( "lipsync_sniper", true ); + } else { +*/ + else { + if ( gameLocal.random.RandomFloat() < 0.4f ) { + return; + } + //TEMP: static debounce timer + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + Speak( "lipsync_closeone", true ); + } +} + +/* +===================== +idAI::AnnounceInjured + +Announce that we have been injured +===================== +*/ +void idAI::AnnounceInjured( void ) { + if ( !CanAnnounce ( 1.0f ) ) { + return; + } + + Speak( "lipsync_needhelp", true ); +} + +/* +===================== +idAI::AnnounceFriendlyFire + +Announce that someone on our own team is shooting us +===================== +*/ +void idAI::AnnounceFriendlyFire( idActor* attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Early outs + if ( health <= 0 || attacker == this ) { + return; + } + + // Don't react to ff from other buddy AI + if ( !attacker->IsType( idPlayer::GetClassType() ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE ) ) { + return; + } + + // Must be on same team for friendly fire + if ( team != attacker->team ) { + return; + } + + // Must be close enough to hear it + if ( DistanceTo ( attacker ) > 300.0f ) { + return; + } + + //FIXME: escalate? + if ( Speak( "lipsync_checkfire", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, 1000 ); + } +} + +/* +===================== +idAI::AnnounceGrenade +===================== +*/ +void idAI::AnnounceGrenade( void ) { + if ( !CanAnnounce ( 1 ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_grenade", true ); +} + +/* +===================== +idAI::AnnounceGrenadeThrow + +Announce that we are throwing a grenade +===================== +*/ +void idAI::AnnounceGrenadeThrow( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 1000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_throw_grenade", true ); +} + + +/* +===================== +rvAIManager::AnnounceDeath + +Announce through an ally of the victem that they have died +===================== +*/ +void rvAIManager::AnnounceDeath( idAI* victim, idEntity* attacker ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire kill? + //MCG NOTE: This isn't even possible anymore... + if ( attacker->IsType ( idPlayer::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + teammate = NearestTeammateToPoint( static_cast(attacker), attacker->GetPhysics()->GetOrigin(), true, 500.0f ); + teammateAI = dynamic_cast(teammate); + + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + teammateAI->Speak( "lipsync_traitor", true ); + return; + } + } + + teammate = NearestTeammateToPoint( victim, victim->GetPhysics()->GetOrigin(), true, 1000.0f ); + teammateAI = dynamic_cast(teammate); + + //jshepard: double check to make sure we don't call out our own death! + if( teammateAI == victim ) { + //MCG: note - NearestTeammateToPoint should *never* allow this, should never happen + assert(0); + return; + } + + // Early out if we dont have a teammate or our teammate cant talk + if ( !attacker || !teammateAI || !teammateAI->CanAnnounce ( teammateAI->announceRate ) ) { + return; + } + + // Announce sniper? + // jshepard: sniper announcement removed by request +/* + if ( attacker->spawnArgs.GetBool ( "sniper" ) ) { + if ( !aiManager.CheckTeamTimer( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER ) ) { + if ( teammateAI->Speak( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + } +*/ + // Annoucne specific death or just a generic death + const char* shortName; + if ( !victim->spawnArgs.GetString ( "npc_shortname", "", &shortName ) || !*shortName || + !teammateAI->Speak ( va("lipsync_%s_killed", shortName ), true ) ) { + teammateAI->Speak( "lipsync_mandown", true ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announces an ai being killed +===================== +*/ +void rvAIManager::AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire deaths are handled elsewhere + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + return; + } + + // If it was an AI guy that did the killing then just let him announce it + if ( attacker->IsType( idAI::Type ) ) { + //announce the kill + static_cast(attacker)->AnnounceKill( victim ); + } else if ( attacker->IsType( idPlayer::GetClassType() ) ) { + idPlayer* attackerPlayer = static_cast(attacker); + + // If the guy who died is an AI guy who was targetting a buddy nearby, have him say "thanks!" + // jshepard: these are cut unless we can get some tighter "thanks" quotes +/* if ( victim->IsType( idAI::Type ) ) { + idAI* victimAI = static_cast(victim); + //if the victim's enemy is a teammate of mine, make the teammate say "thanks!" + if ( victimAI && victimAI->GetEnemy() ) { + idAI* victimEnemyAI = dynamic_cast(victimAI->GetEnemy()); + + // See if the enemy of the guy who died is a teammate and wants to say thanks + if ( victimEnemyAI && victimEnemyAI->CanAnnounce ( ) && victimEnemyAI->team == attackerPlayer->team ) { + float distSqr = (victimAI->GetPhysics()->GetOrigin() - victimEnemyAI->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr < Square ( 300.0f ) ) { + //teammate was fighting him or close to him + victimEnemyAI->Speak( "lipsync_thanks", true ); + return; + } + } + } + } */ + + // Grab a nearby teammate of the player and say "nice shot!" + teammate = NearestTeammateToPoint( attackerPlayer, attacker->GetPhysics()->GetOrigin(), true, 500.0f, true ); + teammateAI = dynamic_cast(teammate); + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + idProjectile* proj = dynamic_cast(inflictor); + //killed them with a grenade? + if ( proj && proj->spawnArgs.GetBool( "thrown" ) ) { + teammateAI->Speak( "lipsync_nicetoss", true ); + // Or just shot them? + } else { + teammateAI->Speak( "lipsync_niceshot", true ); + } + return; + } + } +} + diff --git a/source/game/ai/AI_Debug.cpp b/source/game/ai/AI_Debug.cpp new file mode 100644 index 0000000..21bd80f --- /dev/null +++ b/source/game/ai/AI_Debug.cpp @@ -0,0 +1,330 @@ +/* +================ + +AI_Debug.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" + +const char *aiMoveCommandString[ NUM_MOVE_COMMANDS ] = { + "MOVE_NONE", + "MOVE_FACE_ENEMY", + "MOVE_FACE_ENTITY", + "MOVE_TO_ENEMY", + "MOVE_TO_ENTITY", + "MOVE_TO_ATTACK", + "MOVE_TO_HELPER", + "MOVE_TO_TETHER", + "MOVE_TO_COVER", + "MOVE_TO_HIDE", + "MOVE_TO_POSITION", + "MOVE_OUT_OF_RANGE", + "MOVE_SLIDE_TO_POSITION", + "MOVE_WANDER" +}; + +const char* aiMoveStatusString[ NUM_MOVE_STATUS ] = { + "MOVE_STATUS_DONE", + "MOVE_STATUS_MOVING", + "MOVE_STATUS_WAITING", + "MOVE_STATUS_DEST_NOT_FOUND", + "MOVE_STATUS_DEST_UNREACHABLE", + "MOVE_STATUS_BLOCKED_BY_WALL", + "MOVE_STATUS_BLOCKED_BY_OBJECT", + "MOVE_STATUS_BLOCKED_BY_ENEMY", + "MOVE_STATUS_BLOCKED_BY_MONSTER", + "MOVE_STATUS_BLOCKED_BY_PLAYER", + "MOVE_STATUS_DISABLED" +}; + +const char* aiMoveDirectionString [ MOVEDIR_MAX ] = { + "MOVEDIR_FORWARD", + "MOVEDIR_BACKWARD", + "MOVEDIR_LEFT", + "MOVEDIR_RIGHT" +}; + +const char* aiTacticalString [ AITACTICAL_MAX ] = { + "AITACTICAL_NONE", + "AITACTICAL_MELEE", + "AITACTICAL_MOVE_FOLLOW", + "AITACTICAL_MOVE_TETHER", + "AITACTICAL_MOVE_PLAYERPUSH", + "AITACTICAL_COVER", + "AITACTICAL_COVER_FLANK", + "AITACTICAL_COVER_ADVANCE", + "AITACTICAL_COVER_RETREAT", + "AITACTICAL_COVER_AMBUSH", + "AITACTICAL_RANGED", + "AITACTICAL_TURRET", + "AITACTICAL_HIDE", + "AITACTICAL_PASSIVE", +}; + +const char* aiFocusString [ AIFOCUS_MAX ] = { + "AIFOCUS_NONE", + "AIFOCUS_LEADER", + "AIFOCUS_TARGET", + "AIFOCUS_TALK", + "AIFOCUS_PLAYER", + "AIFOCUS_ENEMY", + "AIFOCUS_COVER", + "AIFOCUS_COVERLOOK", + "AIFOCUS_HELPER", + "AIFOCUS_TETHER", +}; + +const char* aiActionStatusString [ rvAIAction::STATUS_MAX ] = { + "Unused", + "OK", + "Disabled", + "Failed: timer", + "Failed: external timer", + "Failed: within min range", + "Failed: out of max range", + "Failed: random chance", + "Failed: bad animation", + "Failed: condition", + "Failed: no enemy", +}; + +/* +===================== +idAI::GetDebugInfo +===================== +*/ +void idAI::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idActor::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "aifl.damage", aifl.damage ? "true" : "false", userData ); + proc ( "idAI", "aifl.undying", aifl.undying ? "true" : "false", userData ); + proc ( "idAI", "aifl.pain", aifl.pain ? "true" : "false", userData ); + proc ( "idAI", "aifl.dead", aifl.dead ? "true" : "false", userData ); + proc ( "idAI", "aifl.activated", aifl.activated ? "true" : "false", userData ); + proc ( "idAI", "aifl.jump", aifl.jump ? "true" : "false", userData ); + proc ( "idAI", "aifl.hitEnemy", aifl.hitEnemy ? "true" : "false", userData ); + proc ( "idAI", "aifl.pushed", aifl.pushed ? "true" : "false", userData ); + proc ( "idAI", "aifl.lookAtPlayer", aifl.lookAtPlayer ? "true" : "false", userData ); + proc ( "idAI", "aifl.disableAttacks", aifl.disableAttacks ? "true" : "false", userData ); + proc ( "idAI", "aifl.simpleThink", aifl.simpleThink ? "true" : "false", userData ); + proc ( "idAI", "aifl.action", aifl.action ? "true" : "false", userData ); + proc ( "idAI", "aifl.scripted", aifl.scripted? "true" : "false", userData ); + + proc ( "idAI", "leader", leader.GetEntity() ? leader.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "move.followRangeMin", va("%g",move.followRange[0]), userData ); + proc ( "idAI", "move.followRangeMax", va("%g",move.followRange[1]), userData ); + proc ( "idAI", "combat.fl.aware", combat.fl.aware ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.ignoreEnemies", combat.fl.ignoreEnemies ? "true" : "false", userData ); + + proc ( "idAI", "enemy", enemy.ent ? enemy.ent->GetName() : "", userData ); + proc ( "idAI", "enemy.fl.inFov", enemy.fl.inFov ? "true" : "false", userData ); + proc ( "idAI", "enemy.fl.visible", enemy.fl.visible ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.seenEnemyDirectly",combat.fl.seenEnemyDirectly ? "true" : "false", userData ); + proc ( "idAI", "enemy.lastVisibleTime", va("%d",enemy.lastVisibleTime), userData ); + proc ( "idAI", "combat.maxLostVisTime", va("%d",combat.maxLostVisTime), userData ); + proc ( "idAI", "enemy.range", va("%g",enemy.range), userData ); + proc ( "idAI", "enemy.ranged2d", va("%g",enemy.range2d), userData ); + proc ( "idAI", "lastAttackTime", va("%d",lastAttackTime), userData ); + + proc ( "idAI", "move.fl.done", move.fl.done ? "true" : "false", userData ); + proc ( "idAI", "move.fl.disabled", move.fl.disabled ? "true" : "false", userData ); + proc ( "idAI", "move.fl.onGround", move.fl.onGround ? "true" : "false", userData ); + proc ( "idAI", "move.fl.blocked", move.fl.blocked ? "true" : "false", userData ); + proc ( "idAI", "move.fl.obstacleInPath", move.fl.obstacleInPath ? "true" : "false", userData ); + proc ( "idAI", "move.fl.goalUnreachable", move.fl.goalUnreachable ? "true" : "false", userData ); + proc ( "idAI", "move.fl.moving", move.fl.moving ? "true" : "false", userData ); + proc ( "idAI", "move.command", aiMoveCommandString[move.moveCommand], userData ); + proc ( "idAI", "move.status", aiMoveStatusString[move.moveStatus], userData ); + proc ( "idAI", "move.fl.allowDirectional", move.fl.allowDirectional ? "true" : "false", userData ); + proc ( "idAI", "move.direction_ideal", aiMoveDirectionString[move.idealDirection ], userData ); + proc ( "idAI", "move.direction_current", aiMoveDirectionString[move.currentDirection ], userData ); + proc ( "idAI", "move.yaw_ideal", va("%d",(int)move.ideal_yaw), userData ); + proc ( "idAI", "move.yaw_current", va("%d",(int)move.current_yaw), userData ); + proc ( "idAI", "move.fly_roll", va("%g", move.fly_roll ), userData ); + proc ( "idAI", "move.fly_pitch", va("%g", move.fly_pitch ), userData ); + + proc ( "idAI", "tether", tether!=NULL?tether->GetName():"", userData ); + proc ( "idAI", "IsTethered()", IsTethered()?"true":"false", userData ); + + proc ( "idAI", "lookTarget", lookTarget.GetEntity() ? lookTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "talkTarget", talkTarget.GetEntity() ? talkTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "focusType", aiFocusString[focusType], userData ); + proc ( "idAI", "look.yaw", va("%g", lookAng.yaw ), userData ); + proc ( "idAI", "look.pitch", va("%g", lookAng.pitch ), userData ); + + proc ( "idAI", "combat.attackRangeMin", va("%g",combat.attackRange[0]), userData ); + proc ( "idAI", "combat.attackRangeMax", va("%g",combat.attackRange[1]), userData ); + proc ( "idAI", "combat.tacticalCurrent", aiTacticalString[combat.tacticalCurrent], userData ); + + proc ( "idAI", "action_rangedAttack", aiActionStatusString[actionRangedAttack.status], userData ); + proc ( "idAI", "action_meleeAttack", aiActionStatusString[actionMeleeAttack.status], userData ); + proc ( "idAI", "action_leapAttack", aiActionStatusString[actionLeapAttack.status], userData ); + proc ( "idAI", "action_jumpBack", aiActionStatusString[actionJumpBack.status], userData ); + proc ( "idAI", "action_evadeLeft", aiActionStatusString[actionEvadeLeft.status], userData ); + proc ( "idAI", "action_evadeRight", aiActionStatusString[actionEvadeRight.status], userData ); + + proc ( "idAI", "npc_name", spawnArgs.FindKey("npc_name")?common->GetLocalizedString(spawnArgs.GetString( "npc_name", "")):"", userData ); +} + +/* +===================== +idAI::DrawRoute +===================== +*/ +void idAI::DrawRoute( void ) const { + if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } else { + aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } + } +} + +/* +===================== +idAI::DrawTactical + +Draw the debug tactical information +===================== +*/ +void idAI::DrawTactical ( void ) { + if ( !DebugFilter(ai_debugTactical) ) { + return; + } + + // Colors For Lines In This File + //------------------------------- + // Majenta = Move Dest + // Green / Red = Enemy (On Player Side, On Enemy Side) + // Pink = Tether Radius + // Grey = FOV + + // Colors From AAST Draw Debug Info + //---------------------------------- + // Blue = Reserved Feature + // Orange = Near Feature + // Yellow = Look Feature + + + // Get Origin + //------------ + idVec3 origin = GetPhysics()->GetOrigin(); + origin += (GetPhysics()->GetGravityNormal() * 5.0f); + + // Draw FOV (Must be close to player and on enemy team) + //------------------------------------------------------ + if (team && DistanceTo(gameLocal.GetLocalPlayer())<500.0f) { + if (!combat.fl.aware) { + gameRenderWorld->DebugFOV(colorLtGrey, origin, viewAxis[0], fovDot, 300.0f, fovCloseDot, fovCloseRange, 0.3f, 10); + } else if (!combat.fl.seenEnemyDirectly) { + gameRenderWorld->DebugFOV(colorMdGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, 0.3f, 10); + } else { + float alpha = (1.0f - ((float)(gameLocal.GetTime() - enemy.lastVisibleTime) / (float)combat.maxLostVisTime)) * 0.35f; + gameRenderWorld->DebugFOV(colorDkGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, Max(alpha, 0.1f), 10); + } + } + + // Move Over Head + //---------------- + origin -= GetPhysics()->GetGravityNormal() * (GetPhysics()->GetBounds()[ 1 ].z - GetPhysics()->GetBounds()[ 0 ].z); + + // Draw Enemy Related Info + //------------------------- + if ( enemy.ent ) { + // Draw Lost Time + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > combat.maxLostVisTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > ( combat.maxLostVisTime/2 ) ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorGreen, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Enemy Chest position for enemy lines + idVec3 enemyEyePosition; + idVec3 offset; + if ( enemy.ent->IsType ( idActor::GetClassType() ) ){ + enemyEyePosition = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + enemyEyePosition = enemy.ent->GetPhysics()->GetOrigin(); + } + + offset = idVec3(0,0,team*2.0f); + + gameRenderWorld->DebugLine ( aiTeamColor[team], GetEyePosition() + offset, enemy.lastVisibleFromEyePosition + offset, 4.0f ); + if ( enemyEyePosition != enemy.lastVisibleEyePosition ) { + gameRenderWorld->DebugLine ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemy.lastVisibleEyePosition + offset, 4.0f ); + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } else { + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } + } + + // Ivalid Cover Timer + //-------------------- + if ( IsBehindCover() && combat.coverValidTime && combat.coverValidTime < gameLocal.time) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("invalid cover time: %d", (gameLocal.GetTime() - combat.coverValidTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } + + + // Vis Crouch + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Vis Stand + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Aggression + if ( combat.aggressiveScale != 1.0f ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("aggressive: %g", combat.aggressiveScale), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw anim prefix + //----------------------- + if ( animPrefix.Length ( ) ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("anim: %s", animPrefix.c_str()), origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw focus type + //----------------------- + if ( focusType != AIFOCUS_NONE ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiFocusString[focusType], origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw Tactical Current + //----------------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiTacticalString[combat.tacticalCurrent], origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + + // Draw My Name + //-------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText(name, origin, 0.12f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis); + + // Draw the tethered radius + if ( IsTethered ( ) ) { + tether->DebugDraw ( ); + } + + // Draw Move Destination + if ( !move.fl.done ) { + gameRenderWorld->DebugArrow ( colorMagenta, GetPhysics()->GetOrigin(), move.moveDest, 5 ); + } +} diff --git a/source/game/ai/AI_Manager.cpp b/source/game/ai/AI_Manager.cpp new file mode 100644 index 0000000..dbe71d3 --- /dev/null +++ b/source/game/ai/AI_Manager.cpp @@ -0,0 +1,767 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" + +idVec4 aiTeamColor[AITEAM_NUM] = { idVec4 ( 0.0f, 1.0f, 0.0f, 1.0f ), + idVec4 ( 1.0f, 0.0f, 0.0f, 1.0f ) }; + +rvAIManager aiManager; + +/* +================ +rvAIManager::rvAIManager +================ +*/ +rvAIManager::rvAIManager ( void ) { + Clear(); +} + +/* +================ +rvAIManager::IsActive +================ +*/ +bool rvAIManager::IsActive( void ){ + if( gameLocal.isMultiplayer ){ + return false; + } + + return true; +} + +/* +================ +rvAIManager::RunFrame +================ +*/ +void rvAIManager::RunFrame ( void ){ + if ( !IsActive() ) { + return; + } + + // Pop the top simple think off the list + if ( !simpleThink.IsListEmpty() ) { + simpleThink.Next()->simpleThinkNode.Remove ( ); + } + + // Display current ai speeds + if ( ai_speeds.GetBool ( ) && thinkCount > 0 ) { + gameLocal.Printf ( "ai:%6i n:%2i s:%2i all:%5.2f t:%5.2f e:%5.2f m:%5.f\n", + gameLocal.framenum, thinkCount, simpleThinkCount, + timerThink.Milliseconds(), + timerTactical.Milliseconds(), + timerFindEnemy.Milliseconds(), + timerMove.Milliseconds() ); + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + int i; + int j; + // look for reaches that are no longer blocked + for ( i=0; ib.time) { + break; + } + + idEntityPtr blockent = b.blockers[j]; + if (!blockent.IsValid() || blockent->DistanceTo(b.positions[j])>10.0f) { + break; + } + if (blockent->IsType(idAI::GetClassType())) { + idAI* blockentAI = static_cast(blockent.GetEntity()); + if (blockentAI->move.moveDest.Dist2XY(b.positions[j])>100.0f) { + break; + } + } + } + + // if any one of the blockers moved or no longer exists, re-enable this reach + if ( !b.blockers.Num() || jSetReachabilityState(b.reach, true); + blockedReaches.RemoveIndex(i); + break; + } + + // DEBUG GRAPHICS + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, b.reach->start, b.reach->start + idVec3(0,0,40.0f), gameLocal.msec ); + for ( j=0; jDebugArrow( colorRed, b.blockers[j]->GetPhysics()->GetOrigin(), b.reach->start, 8, gameLocal.msec ); + } + } + } + } + // RAVEN END + + timerThink.Clear ( ); + timerTactical.Clear ( ); + timerFindEnemy.Clear ( ); + timerMove.Clear ( ); + + gameDebug.SetStatInt( "ai_thinkCount", thinkCount ); + + thinkCount = 0; + simpleThinkCount = 0; + + // Draw any debugging information + DebugDraw ( ); +} + +/* +================ +rvAIManager::Clear +================ +*/ +void rvAIManager::Clear( void ) { + thinkCount = 0; + simpleThinkCount = 0; + timerThink.Clear ( ); + timerFindEnemy.Clear ( ); + timerTactical.Clear ( ); + timerMove.Clear ( ); + + blockedReaches.Clear ( ); + helpers.Clear ( ); + simpleThink.Clear ( ); + avoids.Clear ( ); + + memset ( &teamTimers, 0, sizeof(teamTimers) ); +} + +/* +================ +rvAIManager::UnMarkAllReachBlocked +================ +*/ +void rvAIManager::UnMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, true); + } +} + +/* +================ +rvAIManager::ReMarkAllReachBlocked +================ +*/ +void rvAIManager::ReMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, false); + } +} + +/* +================ +rvAIManager::MarkReachBlocked +================ +*/ +void rvAIManager::MarkReachBlocked(idAAS* aas, idReachability* reach, const idList& blockers) { + + // only if not already blocked + if (!(reach->travelType&TFL_INVALID)) { + aiBlocked_t blocked; + blocked.aas = aas; + blocked.reach = reach; + blocked.time = gameLocal.GetTime() + 5000.0f; + + for (int i=0; iGetPhysics() && (blockers[i]->GetPhysics()->IsAtRest() || blockers[i]->GetPhysics()->GetLinearVelocity().LengthSqr()<2500.0f)) { + blocked.blockers.Append(blockers[i]); + blocked.positions.Append(blockers[i]->GetPhysics()->GetOrigin()); + } + } + if (blocked.blockers.Num()) { + aas->SetReachabilityState(reach, false); + blockedReaches.Append(blocked); + } + } + +} + +/* +================ +rvAIManager::ReactToPlayerAttack +================ +*/ +void rvAIManager::ReactToPlayerAttack ( idPlayer* player, const idVec3 &origin, const idVec3 &dir ){ + idActor* actor; + float expandSize; + + // Check all enemies and see if they need to react + for ( actor = GetEnemyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + // Skip non ai entities + if ( !actor->IsType ( idAI::Type ) ) { + continue; + } + + idAI *curAI = static_cast(actor); + + // See if it will pass through an expanded bounding box + expandSize = curAI->spawnArgs.GetFloat( "shotAtReactionRange", "16" ); + if ( !curAI->GetPhysics()->GetAbsBounds ( ).Expand(expandSize).LineIntersection ( origin, origin + dir * curAI->combat.visRange ) ) { + continue; + } + + curAI->ReactToShotAt ( player, origin, dir ); + } +} + +/* +================ +rvAIManager::Save +================ +*/ +void rvAIManager::Save( idSaveGame *savefile ) const { + int i; + int j; + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->WriteInt( teams[i].Num() ); + for( actor = teams[i].Next(); actor != NULL; actor = actor->teamNode.Next() ) { + savefile->WriteObject( actor ); + } + } + + // Write out team timers + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->WriteInt ( teamTimers[i][j] ); + } + } + + // Write out team timers + savefile->WriteInt ( avoids.Num ( ) ); + for ( i = 0; i < avoids.Num ( ); i ++ ) { + savefile->WriteVec3 ( avoids[i].origin ); + savefile->WriteFloat ( avoids[i].radius ); + savefile->WriteInt ( avoids[i].team ); + } +} + +/* +================ +rvAIManager::Restore +================ +*/ +void rvAIManager::Restore( idRestoreGame *savefile ){ + int i; + int j; + + Clear ( ); + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->ReadInt( j ); + for ( ; j > 0; j -- ) { + savefile->ReadObject ( reinterpret_cast( actor ) ); + if ( actor ) { + actor->teamNode.AddToEnd ( teams[i] ); + } + } + } + + // Read team timers + memset ( teamTimers, 0, sizeof(teamTimers) ); + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->ReadInt ( teamTimers[i][j] ); + } + } + + // Read in team timers + savefile->ReadInt ( j ); + avoids.SetNum ( j ); + for ( i = 0; i < j; i ++ ) { + savefile->ReadVec3 ( avoids[i].origin ); + savefile->ReadFloat ( avoids[i].radius ); + savefile->ReadInt ( avoids[i].team ); + } +} + +/* +===================== +rvAIManager::AddTeammate +===================== +*/ +void rvAIManager::AddTeammate ( idActor* actor ) { + // If its already in a team least then ignore the call. + // NOTE: You have to call removeteammate before addteammate to switch the + // actor from one team to another + if ( actor->teamNode.InList ( ) ) { + return; + } + actor->teamNode.AddToEnd ( teams[actor->team] ); +} + +/* +===================== +rvAIManager::RemoveTeammate +===================== +*/ +void rvAIManager::RemoveTeammate ( idActor* actor ) { + actor->teamNode.Remove ( ); +} + +/* +===================== +rvAIManager::IsSimpleThink + +Determines whether or not the given AI entity should be simple thinking this frame +===================== +*/ +bool rvAIManager::IsSimpleThink ( idAI* ai ) { + + // no simple think if simple thinking is disabled or we are the head node + if ( ai_disableSimpleThink.GetBool() ) { + return false; + } + + // Add to simple think list + if ( !ai->simpleThinkNode.InList ( ) ) { + ai->simpleThinkNode.AddToEnd ( simpleThink ); + } + + // If head of list then its a complex think + if ( ai->simpleThinkNode.Prev() == NULL ) { + return false; + } + + return true; +} + +/* +================ +rvAIManager::GetEnemyTeam +================ +*/ +idActor* rvAIManager::GetEnemyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_STROGG].Next(); + case AITEAM_STROGG: + return teams[AITEAM_MARINE].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::GetAllyTeam +================ +*/ +idActor* rvAIManager::GetAllyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_MARINE].Next(); + case AITEAM_STROGG: + return teams[AITEAM_STROGG].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::ValidateDestination + +Validate whether or not the destinations is a destination +================ +*/ +bool rvAIManager::ValidateDestination ( idAI* ai, const idVec3& dest, bool skipCurrent, idActor* skipActor ) const { + int i; + idBounds bounds; + idAI* ignore; + + ignore = (!skipCurrent && ai && !ai->SkipCurrentDestination ( )) ? ai : NULL; + + bounds = ai->GetPhysics()->GetBounds ( ); + bounds.TranslateSelf ( dest ); + bounds.ExpandSelf ( 16.0f ); + + // All teams and all actors on those teams + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + // Ignored? + if ( actor == ignore || actor == skipActor || actor->IsHidden ( ) ) { + continue; + } + + // If the actor is AI that is moving we should check their destination rather than where they are now + if ( actor->IsType ( idAI::Type ) ) { + idAI* aiactor = static_cast(actor); + if ( aiactor->move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( bounds.IntersectsBounds ( aiactor->GetPhysics()->GetBounds().Translate ( aiactor->move.moveDest ) ) ) { + return false; + } + continue; + } + } + + // Does the destination overlap this actor? + if ( bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + return false; + } + } + } + + // Destinations inside an avoid area are invalid + for ( i = avoids.Num() - 1; i >= 0; i -- ) { + const aiAvoid_t& avoid = avoids[i]; + // Skip all avoids that arent meant for this team + if ( avoid.team != -1 && ai->team != avoid.team ) { + continue; + } + // Skip if within range of the avoid + if ( (avoid.origin - dest).LengthSqr ( ) < Square ( avoid.radius ) ) { + if ( ai_debugMove.GetBool() || ai_debugTactical.GetBool() ) { + gameRenderWorld->DebugCircle( colorRed, avoid.origin, idVec3(0,0,1), avoid.radius, 25 ); + } + return false; + } + } + + return true; +} + +/* +================ +rvAIManager::NearestTeammateToPoint + +Returns the teammate closest to the given point with the given parameters +================ +*/ +idActor* rvAIManager::NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer, float maxRange, bool checkFOV, bool checkLOS ) { + idActor* actor = NULL; + idActor* closestActor = NULL; + float distSqr; + float bestDistSqr; + + closestActor = 0x0; + bestDistSqr = Square ( maxRange ); + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Player? + if ( nonPlayer && actor->IsType( idPlayer::GetClassType() ) ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (point - actor->GetPhysics()->GetOrigin()).LengthSqr(); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV && !actor->CheckFOV( point ) ) { + continue; + } + //actor has clear LOS to point? + if ( checkLOS && !actor->CanSeeFrom ( from->GetEyePosition ( ), point, false ) ) { + continue; + } + // New best actor + bestDistSqr = distSqr; + closestActor = actor; + } + return closestActor; +} + +/* +================ +rvAIManager::NearestTeammateEnemy + +Returns the closest enemy of an ally. +================ +*/ +idEntity* rvAIManager::NearestTeammateEnemy( idActor* from, float maxRange, bool checkFOV, bool checkLOS, idActor** closestAllyWithEnemy ) { + idActor* actor; + idAI* allyAI; + idEntity* allyEnemy; + idEntity* closestAllyEnemy; + float distSqr; + float bestDistSqr; + + bestDistSqr = Square ( maxRange ); + closestAllyEnemy = NULL; + + // Optionally return the ally whos enemy it is + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = NULL; + } + + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + allyAI = dynamic_cast(actor); + if ( !allyAI ) { + //player? + allyEnemy = actor->EnemyWithMostHealth(); + if ( !allyEnemy ) { + continue; + } + if ( allyEnemy->health <= 0 ) { + continue; + } + } else { + allyEnemy = allyAI->GetEnemy(); + //has enemy? + if ( !allyEnemy ) { + continue; + } + //still alive? + if ( allyAI->enemy.fl.dead ) { + continue; + } + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (actor->GetPhysics()->GetOrigin() - from->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV ) { + if ( !from->CheckFOV( actor->GetPhysics()->GetOrigin() ) ) { + continue; + } + } + //actor has clear LOS to point? + if ( checkLOS ) { + if ( !from->CanSee( actor, false ) ) { + continue; + } + } + + bestDistSqr = distSqr; + closestAllyEnemy = allyEnemy; + + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = actor; + } + } + return closestAllyEnemy; +} + +/* +================ +rvAIManager::LocalTeamHasEnemies + +Returns true if nearby members of my team have any enemies or if any nearby enemies have enemies that are nearby members of my team +================ +*/ +bool rvAIManager::LocalTeamHasEnemies ( idAI* self, float maxBuddyRange, float maxEnemyRange, bool checkPVS ) { + idActor* actor = NULL; + pvsHandle_t pvs; + + if ( !self ) { + return false; + } + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)self->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + if ( actor->IsType( idAI::GetClassType() ) ) { + //Has an enemy? + if ( !((idAI*)actor)->GetEnemy() ) { + continue; + } + //Has an enemy + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ((idAI*)actor)->GetEnemy()->GetPVSAreas(), ((idAI*)actor)->GetEnemy()->GetNumPVSAreas() ) ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + continue; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + + } + //close enough? + if ( actor != self && self->DistanceTo( actor->GetPhysics()->GetOrigin() ) > maxBuddyRange ) { + continue; + } + //Anyone mad at him? + if ( !actor->ClosestEnemyToPoint( actor->GetPhysics()->GetOrigin(), maxEnemyRange, true, checkPVS ) ) { + continue; + } + return true; + } + return false; +} + +/* +================ +rvAIManager::ActorIsBehindActor + +Returns true if the given 'ambuser' is behind the given 'victim' +================ +*/ +bool rvAIManager::ActorIsBehindActor( idActor* ambusher, idActor* victim ) { + idVec3 dir2Ambusher = ambusher->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin(); + float dist = dir2Ambusher.Normalize(); + if ( DotProduct(dir2Ambusher, victim->viewAxis[0] ) < 0 && dist < 200.0f ) { + return true; + } + return false; +} + +/* +=============================================================================== + + rvAIManager - Helpers + +=============================================================================== +*/ + +/* +===================== +rvAIManager::RegisterHelper +===================== +*/ +void rvAIManager::RegisterHelper ( rvAIHelper* helper ) { + helper->helperNode.AddToEnd ( helpers ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::UnregisterHelper +===================== +*/ +void rvAIManager::UnregisterHelper ( rvAIHelper* helper ) { + helper->helperNode.Remove ( ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::FindClosestHelper +===================== +*/ +rvAIHelper* rvAIManager::FindClosestHelper ( const idVec3& origin ) { + rvAIHelper* helper; + rvAIHelper* bestHelper; + float bestDist; + float dist; + + bestDist = idMath::INFINITY; + bestHelper = NULL; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + dist = (origin - helper->GetPhysics()->GetOrigin()).LengthFast ( ); + if ( dist < bestDist ) { + bestDist = dist; + bestHelper = helper; + } + } + return bestHelper; +} + +/* +================ +rvAIManager::UpdateHelpers +================ +*/ +void rvAIManager::UpdateHelpers ( void ) { + int i; + + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + if ( actor->IsHidden ( ) || !actor->IsType ( idAI::Type ) ) { + continue; + } + static_cast(actor)->UpdateHelper ( ); + } + } +} + +/* +=============================================================================== + + rvAIManager - Debugging + +=============================================================================== +*/ + +/* +================ +rvAIManager::DebugDraw +================ +*/ +void rvAIManager::DebugDraw ( void ) { + // Draw helpers? + if ( ai_debugHelpers.GetBool ( ) ) { + DebugDrawHelpers ( ); + + int i; + for ( i = 0; i < avoids.Num(); i ++ ) { + const aiAvoid_t& avoid = avoids[i]; + gameRenderWorld->DebugCircle ( colorOrange, avoid.origin, idVec3(0,0,1), avoid.radius, 10, 0 ); + } + } + + +} + +/* +================ +rvAIManager::DebugDrawHelpers +================ +*/ +void rvAIManager::DebugDrawHelpers ( void ) { + rvAIHelper* helper; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + helper->DrawDebugEntityInfo ( ); + } +} diff --git a/source/game/ai/AI_Manager.h b/source/game/ai/AI_Manager.h new file mode 100644 index 0000000..9116837 --- /dev/null +++ b/source/game/ai/AI_Manager.h @@ -0,0 +1,236 @@ +#ifndef __AI_MANAGER_H__ +#define __AI_MANAGER_H__ + +typedef enum { + AITEAM_MARINE, + AITEAM_STROGG, + AITEAM_NUM +} aiTeam_t; + +typedef enum { + AITEAMTIMER_ANNOUNCE_TACTICAL, // Tactical change + AITEAMTIMER_ANNOUNCE_SUPPRESSING, + AITEAMTIMER_ANNOUNCE_SUPPRESSED, + AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, // Shot by a teammate + AITEAMTIMER_ANNOUNCE_ENEMYSTEATH, + AITEAMTIMER_ANNOUNCE_NEWENEMY, // New enemy was aquired + AITEAMTIMER_ANNOUNCE_SNIPER, // Sniper sighted + AITEAMTIMER_ANNOUNCE_CANIHELPYOU, // Player standing in front of a friendly too long + AITEAMTIMER_ANNOUNCE_SIGHT, // First time seeing an enemy + AITEAMTIMER_ACTION_RELAX, // Play relax animation + AITEAMTIMER_ACTION_PEEK, // Play peek animation + AITEAMTIMER_ACTION_TALK, // Able to talk to another person yet? + AITEAMTIMER_MAX +} aiTeamTimer_t; + +extern idVec4 aiTeamColor[AITEAM_NUM]; + +/* +===================== +blockedReach_t +===================== +*/ +// cdr: Alternate Routes Bug +typedef struct aiBlocked_s { + idAAS* aas; + idReachability* reach; + int time; + idList< idEntityPtr > blockers; + idList< idVec3 > positions; +} aiBlocked_t; + +/* +===================== +aiAvoid_t +===================== +*/ +typedef struct aiAvoid_s { + idVec3 origin; + float radius; + int team; +} aiAvoid_t; + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +class rvAIHelper : public idEntity { +public: + CLASS_PROTOTYPE( rvAIHelper ); + + rvAIHelper ( void ); + + idLinkList helperNode; + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + + idVec3 GetDirection ( const idAI* ent ) const; + +protected: + + virtual void OnActivate ( bool active ); + +private: + + void Event_Activate ( idEntity *activator ); +}; + +/* +=============================================================================== + +rvAIManager + +=============================================================================== +*/ + +class rvAIManager { +public: + + rvAIManager ( void ); + ~rvAIManager ( void ) {} + + /* + =============================================================================== + General + =============================================================================== + */ + + void RunFrame ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( void ); + + bool IsActive ( void ); + bool IsSimpleThink ( idAI* ai ); + + /* + =============================================================================== + Navigation + =============================================================================== + */ + + void UnMarkAllReachBlocked ( void ); + void ReMarkAllReachBlocked ( void ); + void MarkReachBlocked ( idAAS* aas, idReachability* reach, const idList& blockers); + bool ValidateDestination ( idAI* ignore, const idVec3& dest, bool skipCurrent = false, idActor* skipActor = NULL ) const; + void AddAvoid ( const idVec3& origin, float range, int team ); + + /* + =============================================================================== + Helpers + =============================================================================== + */ + + void RegisterHelper ( rvAIHelper* helper ); + void UnregisterHelper ( rvAIHelper* helper ); + rvAIHelper* FindClosestHelper ( const idVec3& origin ); + + /* + =============================================================================== + Team Management + =============================================================================== + */ + + void AddTeammate ( idActor* ent ); + void RemoveTeammate ( idActor* ent ); + + idActor* GetAllyTeam ( aiTeam_t team ); + idActor* GetEnemyTeam ( aiTeam_t team ); + + idActor* NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer = false, float maxRange = 1000.0f, bool checkFOV = false, bool checkLOS = false ); + idEntity* NearestTeammateEnemy ( idActor* from, float maxRange=1000.0f, bool checkFOV = false, bool checkLOS = false, idActor** ally = NULL ); + bool LocalTeamHasEnemies ( idAI* self, float maxBuddyRange=640.0f, float maxEnemyRange=1024.0f, bool checkPVS=false ); + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + + /* + =============================================================================== + Team Timers + =============================================================================== + */ + + bool CheckTeamTimer ( int team, aiTeamTimer_t timer ); + void ClearTeamTimer ( int team, aiTeamTimer_t timer ); + void SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + + void AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ); + void AnnounceDeath ( idAI* victim, idEntity* attacker ); + + /* + =============================================================================== + Reactions + =============================================================================== + */ + + void ReactToPlayerAttack ( idPlayer* player, const idVec3 &origOrigin, const idVec3 &origDir ); + + /* + =============================================================================== + Debugging + =============================================================================== + */ + + idTimer timerFindEnemy; + idTimer timerTactical; + idTimer timerMove; + idTimer timerThink; + + int thinkCount; + int simpleThinkCount; + +protected: + + void UpdateHelpers ( void ); + + void DebugDraw ( void ); + void DebugDrawHelpers ( void ); + + idList blockedReaches; + idLinkList simpleThink; + idLinkList helpers; + + idLinkList teams[AITEAM_NUM]; + int teamTimers[AITEAM_NUM][AITEAMTIMER_MAX]; + + idList avoids; +}; + +ID_INLINE bool rvAIManager::CheckTeamTimer ( int team, aiTeamTimer_t timer ) { + return gameLocal.time >= teamTimers[team][timer]; +} + +ID_INLINE void rvAIManager::ClearTeamTimer ( int team, aiTeamTimer_t timer ) { + teamTimers[team][timer] = 0; +} + +ID_INLINE void rvAIManager::SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ) { + teamTimers[team][timer] = gameLocal.time + delay; +} + +ID_INLINE void rvAIManager::AddAvoid ( const idVec3& origin, float radius, int team ) { + if ( !IsActive ( ) ) { + return; + } + aiAvoid_t& a = avoids.Alloc ( ); + a.origin = origin; + a.radius = radius; + a.team = team; +} + +extern rvAIManager aiManager; + +#endif // __AI_MANAGER_H__ diff --git a/source/game/ai/AI_Medic.cpp b/source/game/ai/AI_Medic.cpp new file mode 100644 index 0000000..b6cea16 --- /dev/null +++ b/source/game/ai/AI_Medic.cpp @@ -0,0 +1,836 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Medic.h" + + +const idEventDef AI_DisableHeal ( "disableHeal" ); +const idEventDef AI_EnableHeal ( "enableHeal" ); +const idEventDef AI_TakePatient ( "takePatient", "e" ); + +CLASS_DECLARATION( rvAITactical, rvAIMedic ) + EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal ) + EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal ) + EVENT( AI_TakePatient, rvAIMedic::TakePatient ) + EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement ) +END_CLASS + +/* +================ +rvAIMedic::rvAIMedic +================ +*/ +rvAIMedic::rvAIMedic ( void ) { + patient = NULL; + isTech = false; + noAutoHeal = false; + stationary = false; + silent = false; + healObeyTether = false; + healing = false; + lastPatientCheckTime = 0; + emergencyOverride = false; + //healedAmount = 0; + healDisabled = false; + wasAware = false; + wasIgnoreEnemies = false; + healDebounceTime = 0; +} + +void rvAIMedic::InitSpawnArgsVariables( void ) +{ + //NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded? + isTech = spawnArgs.GetBool( "tech" ); + noAutoHeal = spawnArgs.GetBool( "noAutoHeal" ); + healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" ); + healObeyTether = spawnArgs.GetBool( "healObeyTether" ); + patientRange = spawnArgs.GetFloat( "patientRange", "640" ); + buddyRange = spawnArgs.GetFloat( "buddyRange", "640" ); + enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" ); + healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) ); + + /* + // JTD: check for player being strogg, since the limits will be different in that case.. + idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" ); + str.Strip( "player_" ); + // ...we'll end up with something like minMarineHeal...or minStroggHeal + */ + minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" ); + maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" ); +} +/* +================ +rvAIMedic::Spawn +================ +*/ +void rvAIMedic::Spawn ( void ) { + InitSpawnArgsVariables(); + + stationary = spawnArgs.GetBool( "stationary" ); + silent = spawnArgs.GetBool( "silent", "0"); + if ( spawnArgs.GetBool( "disableHeal" ) ) { + Event_DisableHeal(); + } + if ( spawnArgs.GetBool( "enableHeal" ) ) { + Event_EnableHeal(); + } + const char *func; + if ( spawnArgs.GetString( "script_postHeal", "", &func ) ) + { + mPostHealScript.Init( func ); + } +} + +/* +================ +rvAIMedic::Show +================ +*/ +void rvAIMedic::Show( void ) { + rvAITactical::Show(); + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +/* +================ +rvAIMedic::Save +================ +*/ +void rvAIMedic::Save( idSaveGame *savefile ) const { + patient.Save( savefile ); + savefile->WriteBool( healing ); + savefile->WriteInt( lastPatientCheckTime ); + savefile->WriteBool( emergencyOverride ); + savefile->WriteBool( healDisabled ); + savefile->WriteBool( wasAware ); + savefile->WriteBool( wasIgnoreEnemies ); + //savefile->WriteInt( healedAmount ); + savefile->WriteInt( healDebounceTime ); + savefile->WriteBool( stationary ); + savefile->WriteBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->WriteInt( curHealValue ); + savefile->WriteInt( maxPatientValue ); + mPostHealScript.Save( savefile ); +} + +/* +================ +rvAIMedic::Restore +================ +*/ +void rvAIMedic::Restore( idRestoreGame *savefile ) { + patient.Restore( savefile ); + savefile->ReadBool( healing ); + savefile->ReadInt( lastPatientCheckTime ); + savefile->ReadBool( emergencyOverride ); + savefile->ReadBool( healDisabled ); + savefile->ReadBool( wasAware ); + savefile->ReadBool( wasIgnoreEnemies ); + //savefile->ReadInt( healedAmount ); + savefile->ReadInt( healDebounceTime ); + savefile->ReadBool( stationary ); + savefile->ReadBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->ReadInt( curHealValue ); + savefile->ReadInt( maxPatientValue ); + mPostHealScript.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvAIMedic::Event_EnableHeal +===================== +*/ +void rvAIMedic::Event_EnableHeal( void ) { + healDisabled = false; +} + +/* +===================== +rvAIMedic::Event_DisableHeal +===================== +*/ +void rvAIMedic::Event_DisableHeal( void ) { + healDisabled = true; + /* + if ( patient ) { + //drop them right now - NOTE: should be done before any scripting? + DropPatient(); + } + */ +} + +/* +================== +rvAIMedic::Event_EnableMovement +================== +*/ +void rvAIMedic::Event_EnableMovement( void ) { + stationary = false; +} + +/* +================== +rvAIMedic::Event_DisableMovement +================== +*/ +void rvAIMedic::Event_DisableMovement ( void ) { + stationary = true; +} + +/* +===================== +rvAIMedic::TalkTo +===================== +*/ +void rvAIMedic::TalkTo( idActor *actor ) { + if ( actor->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* player = dynamic_cast(actor); + if ( player ) + { + emergencyOverride = true; + if ( AvailableToTakePatient() && CheckTakePatient( player ) ) + { + return; + } + emergencyOverride = false; + } + } + rvAITactical::TalkTo( actor ); + + if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) { + if ( GetEnemy() || patient ) { + Speak( "lipsync_heal_busy_", true ); + } else { + //Never got proper VO for this, disabling it... :/ + //Speak( "lipsync_heal_noheal_", true ); + } + } +} + +/* +================ +rvAIMedic::GetDebugInfo +================ +*/ +void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + rvAITactical::GetDebugInfo ( proc, userData ); + + proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData ); + proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData ); + proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData ); + proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData ); + proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData ); + proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData ); + proc ( "rvAIMedic", "silent", silent?"true":"false", userData ); + proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData ); + proc ( "rvAIMedic", "patient", patient==NULL?"":patient->GetName(), userData ); + proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData ); + proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData ); + proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData ); + proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData ); + proc ( "rvAIMedic", "healing", healing?"true":"false", userData ); + proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData ); + //proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData ); + proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData ); + proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData ); + proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData ); + proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData ); + proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData ); + proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData ); + proc ( "rvAIMedic", "tech", isTech?"true":"false", userData ); +} + +/* +============ +rvAIMedic::IsTethered +============ +*/ +bool rvAIMedic::IsTethered ( void ) const { + if ( rvAITactical::IsTethered() ) { + if ( !healObeyTether && patient ) { + return false; + } + return true; + } + return false; +} + +void rvAIMedic::TakePatient( idPlayer* pPatient ) +{ + patient = pPatient; + if ( emergencyOverride ) + { + ClearEnemy(); + } + + wasAware = combat.fl.aware; + wasIgnoreEnemies = combat.fl.ignoreEnemies; + ProcessEvent( &AI_BecomePassive, true ); + combat.fl.ignoreEnemies = true; + ClearEnemy(); + lookTarget = patient; + healing = false; + //healedAmount = 0; + + if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent ) + { + //have enough time to say this before we get there...? + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_call_player_tech_", true ); + } else { + Speak( "lipsync_call_player_", true ); + } + } + + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + SetState( "State_Medic" ); + PostState( "State_Combat" ); + + //just in case + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } +} + +void rvAIMedic::DropPatient( void ) +{ + /* + if ( !spawnArgs.GetBool( "ignoreEnemies" ) ) + { + combat.fl.ignoreEnemies = false; + } + */ + /* + if ( !aifl.scripted ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT ); + } + */ + healing = false; + if ( !aifl.scripted ) { + ProcessEvent( &AI_BecomeAggressive ); + combat.fl.aware = wasAware; + combat.fl.ignoreEnemies = wasIgnoreEnemies; + lookTarget = NULL; + } else if ( lookTarget == patient ) { + //if scripted, clear the looktarget only if it's the patient? This could be wrong, though.... + lookTarget = NULL; + } + + patient = NULL; + + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } + + ForceTacticalUpdate(); + if ( !aifl.scripted ) { + UpdateTactical ( 0 ); + } + //FIXME: what if they stay in this state? + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +void rvAIMedic::SetHealValues( idPlayer* player ) +{ + if ( !player ) + { + return; + } + if ( isTech ) + { + curHealValue = player->inventory.armor; + maxPatientValue = player->inventory.maxarmor; + } + else + { + curHealValue = player->health; + maxPatientValue = player->inventory.maxHealth; + } +} + +bool rvAIMedic::CheckTakePatient( idPlayer* player ) +{ + if ( !player ) + { + return false; + } + if ( player->IsHidden() ) + { + return false; + } + if ( player->health <= 0 ) + { + return false; + } + SetHealValues( player ); + + if ( curHealValue < maxPatientValue ) + {//they are hurt + if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) ) + {//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one) + if ( DistanceTo( player ) < patientRange ) + {//close enough + if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange ) + {//either I am allowed to move or player is close enough that I don't have to + //if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) ) + {//not tethered or patient is in our tether + if ( emergencyOverride || CanSee( player, false ) ) + {//can see the patient - OR: just check PVS? + TakePatient( player ); + return true; + } + } + } + } + } + } + + return false; +} + +bool rvAIMedic::SituationAllowsPatient( void ) +{ + if ( !aifl.scripted && !IsHidden() && !aifl.dead ) + {//not scripted right now + if ( combat.fl.ignoreEnemies ) { + return true; + } + bool enemyInPVS = false; + bool enemyInRange = false; + if ( GetEnemy() ) + {//sorry, we have to bail on you! + enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange); + if ( enemyInRange ) { + pvsHandle_t pvs; + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) { + enemyInPVS = true; + emergencyOverride = false; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + } + if ( emergencyOverride ) + {//don't care how crazy it is, go for it! + return true; + } + if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 ) + {//haven't had an enemy for 5 seconds + if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) ) + {//local buddies don't have enemies + //NOTE: which buddies and enemies are local can change as we head to our patient! + return true; + } + } + } + return false; +} + +bool rvAIMedic::AvailableToTakePatient( void ) +{ + if ( !healDisabled ) + {//not forced to disabled + if ( !aifl.action ) + {//not in the middle of an action + if ( !patient ) + {//don't already have a patient + if ( !IsSpeaking() ) + {//not talking + return SituationAllowsPatient(); + } + } + } + } + return false; +} + +/* +================ +rvAIMedic::Think +================ +*/ +void rvAIMedic::Think ( void ) { + rvAITactical::Think ( ); + +// while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1") +//??? + if ( !noAutoHeal ) + { + if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 ) + { + lastPatientCheckTime = gameLocal.GetTime(); + if ( !patient ) + { + emergencyOverride = false; + } + if ( AvailableToTakePatient() ) + { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( CheckTakePatient( player ) ) + { + return; + } + //otherwise, check team? + /* + idActor* actor; + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) + { + if ( CheckTakePatient( actor ) ) + { + return; + } + } + */ + } + } + } +} + +/* +===================== +rvAIMedic::OnStateThreadClear +===================== +*/ +void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) { + if ( idStr::Icmp( statename, "State_Medic" ) ) { + if ( patient ) { + //BAH! Someone changed our state on us! + if ( lookTarget == patient ) { + lookTarget = NULL; + } + patient = NULL; + healing = false; + } + } +} + +/* +============ +rvAIMedic::OnStartMoving +============ +*/ +void rvAIMedic::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( patient ) + {//we were trying to heal! + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//being told to leave the patient + //abort the heal, for now + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + } +} + +/* +===================== +rvAIMedic::Pain +===================== +*/ +bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( retVal && patient ) { + //if get hit while trying to heal, protect ourselves + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + StopMove ( MOVE_STATUS_DONE ); + } + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + return retVal; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAIMedic ) + STATE ( "State_Medic", rvAIMedic::State_Medic ) +END_CLASS_STATES + +/* +================ +idAI::State_Medic + +Rush towards the patient to melee range and heal until they're okay +================ +*/ +stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the patient, speak & start anim + STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim + STAGE_HEAL_START_WAIT,// Wait for start anim to finish + STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed + STAGE_WAIT_FINISH // Finish anim + }; + if ( !patient || patient->health <= 0 || !SituationAllowsPatient() ) + {//patient dead or situation is bad + //NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume? + StopMove ( MOVE_STATUS_DONE ); + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + + if ( !move.fl.done ) + { + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//something stomped our move, give it up, for now... :/ + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + } + + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) + { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) ) + {//wtf, we're not there yet, try again! + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + } + else + {//we're there! + if ( !healing ) + { + SetHealValues( patient ); + if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent ) + {//didn't speak in last couple seconds + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_heal_start_tech_", true ); + } else { + Speak( "lipsync_heal_start_", true ); + } + } + } + StopMove ( MOVE_STATUS_DONE ); + healing = true; + // check for preHeal anim key and if it's present, play it first. + const char *preHealAnim; + if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim )) + { + PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 ); + return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT ); + } + else // otherwise just use the regular anim to start healing + { + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + } + } + if ( !stationary && move.range != 42.0f ) + { + //MCG: if you ever get this assert, please call me over so I can debug it! + assert(move.range==42.0f); + move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_( + } + /* + else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) ) + {//we're here, just stop + Speak( "lipsync_medic_arrive", true ); + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + */ + + // Perform actions on the way to the patient + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + } + + return SRESULT_WAIT; + + // intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity + case STAGE_PRE_HEAL_ANIM_WAIT: + const char *preHealAnim; + spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim )) + {//finished or interrupted + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + return SRESULT_WAIT; + + + case STAGE_HEAL_START_WAIT: + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) ) + {//finished or interrupted +// PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + //show the tool, just in case the anim was interrupted + ShowAttachment( spawnArgs.GetString("def_attach") ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + return SRESULT_WAIT; + + case STAGE_HEAL: + { + SetHealValues( patient ); + /* + if ( curHealValue >= maxPatientValue + || (curHealValue > minHealValue && healedAmount >= healAmt) ) + {//patient fully healed (or we've used up our allotment), we're done here + Speak( "lipsync_heal_end_", true ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + */ + + // If we are out of melee range or lost sight of our patient then start moving again + /* + if ( !stationary && !move.fl.disabled ) { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) { + MoveToEntity( patient, 42 ); + Speak( "lipsync_heal_move_", true ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_STAGE ( STAGE_MOVE ); + } + } + */ + + /* + combat.fl.ignoreEnemies = true; + */ + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + + // Always face patient when in melee range + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + // Perform actions while standing still + /* + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + combat.tacticalUpdateTime = gameLocal.GetTime(); + if ( UpdateTactical( 100000 ) ) { + DropPatient(); + return SRESULT_DONE_WAIT; + } + } + */ + + //jshepard: tech/medic dependent speech + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) { + if ( !isTech ) { + patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt; + if( !silent) { + Speak( "lipsync_heal_end_", true ); + } + } else { + patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;; + if( !silent) { + Speak( "lipsync_heal_end_tech_", true ); + } + } + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + if ( gameLocal.random.RandomFloat() > 0.5f ) + { + if ( !isTech ) + { + if ( patient->health < maxPatientValue ) { + patient->health++; + } + } + else + { + if ( patient->inventory.armor < maxPatientValue ) { + patient->inventory.armor++; + } + } + } + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) ) + {//finished or interrupted + //turn off the tool, just in case we were interrupted + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/AI_Medic.h b/source/game/ai/AI_Medic.h new file mode 100644 index 0000000..b5c46d4 --- /dev/null +++ b/source/game/ai/AI_Medic.h @@ -0,0 +1,92 @@ +/* +================ + +AI_Medic.h + +================ +*/ +#include "AI_Tactical.h" + +#ifndef __AI_MEDIC__ +#define __AI_MEDIC__ + +class rvAIMedic : public rvAITactical { +public: + + CLASS_PROTOTYPE( rvAIMedic ); + + rvAIMedic ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Show ( void ); + + virtual void TalkTo ( idActor *actor ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool IsTethered ( void ) const; + + virtual void OnStateThreadClear ( const char *statename, int flags ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + bool isTech; + +protected: + virtual void OnStartMoving ( void ); + +private: + + idEntityPtr patient; + bool healing; + int lastPatientCheckTime; + bool emergencyOverride; + + bool noAutoHeal; + bool stationary; + bool silent; + bool healObeyTether; + int healAmt; + float patientRange; + float buddyRange; + float enemyRange; + + int curHealValue; + int maxHealValue; + int minHealValue; + int healedAmount; + int maxPatientValue; + + int healDebounceInterval; + int healDebounceTime; + + bool healDisabled; + bool wasAware; + bool wasIgnoreEnemies; + + void SetHealValues ( idPlayer* player ); + + void TakePatient ( idPlayer* pPatient ); + void DropPatient ( void ); + bool CheckTakePatient ( idPlayer* actor ); + bool SituationAllowsPatient ( void ); + bool AvailableToTakePatient ( void ); + + stateResult_t State_Medic ( const stateParms_t& parms ); + + void Event_EnableHeal ( void ); + void Event_DisableHeal ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + + rvScriptFuncUtility mPostHealScript; // script to run after completing a heal + + CLASS_STATES_PROTOTYPE ( rvAIMedic ); +}; + +#endif /* !__AI_MEDIC__ */ diff --git a/source/game/ai/AI_Move.cpp b/source/game/ai/AI_Move.cpp new file mode 100644 index 0000000..2a689b3 --- /dev/null +++ b/source/game/ai/AI_Move.cpp @@ -0,0 +1,4832 @@ +/* +=============================================================================== + +AI_Move.cpp + +This file has all movement related functions. It and its sister H file were +split from AI.h and AI.cpp in order to prevent merge conflicts and to make +further changes to the system possible. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + idMoveState + +=============================================================================== +*/ + +/* +===================== +idMoveState::idMoveState +===================== +*/ +idMoveState::idMoveState() { + moveType = MOVETYPE_ANIM; + moveCommand = MOVE_NONE; + moveStatus = MOVE_STATUS_DONE; + moveDest.Zero(); + moveDir.Set( 1.0f, 0.0f, 0.0f ); + goalEntity = NULL; + goalEntityOrigin.Zero(); + toAreaNum = 0; + startTime = 0; + duration = 0; + speed = 0.0f; + range = 0.0f; + wanderYaw = 0; + nextWanderTime = 0; + blockTime = 0; + obstacle = NULL; + lastMoveOrigin = vec3_origin; + lastMoveTime = 0; + anim = 0; + travelFlags = TFL_WALK|TFL_AIR; + kickForce = 2048.0f; + fl.ignoreObstacles = false; + fl.allowAnimMove = false; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + blockedRadius = 0.0f; + blockedMoveTime = 750; + blockedAttackTime = 750; + turnRate = 360.0f; + turnVel = 0.0f; + anim_turn_yaw = 0.0f; + anim_turn_amount = 0.0f; + anim_turn_angles = 0.0f; + fly_offset = 0; + fly_seek_scale = 1.0f; + fly_roll_scale = 0.0f; + fly_roll_max = 0.0f; + fly_roll = 0.0f; + fly_pitch_scale = 0.0f; + fly_pitch_max = 0.0f; + fly_pitch = 0.0f; + fly_speed = 0.0f; + fly_bob_strength = 0.0f; + fly_bob_vert = 0.0f; + fly_bob_horz = 0.0f; + currentDirection = MOVEDIR_FORWARD; + idealDirection = MOVEDIR_FORWARD; + current_yaw = 0.0f; + ideal_yaw = 0.0f; + flyTiltJoint = INVALID_JOINT; + addVelocity.Zero(); +} + + + +/* +===================== +idMoveState::Spawn +===================== +*/ +void idMoveState::Spawn( idDict &spawnArgs ) { + memset ( &fl, 0, sizeof(fl) ); + fl.allowAnimMove = true; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + fl.noRun = spawnArgs.GetBool ( "forceWalk", "0" ); + fl.noWalk = spawnArgs.GetBool ( "forceRun", "0" ); + fl.noTurn = spawnArgs.GetBool ( "noTurn", "0" ); + fl.noGravity = spawnArgs.GetBool ( "animate_z", "0" ); + fl.noRangedInterrupt = spawnArgs.GetBool ( "noRangedInterrupt", "0" ); + + fl.flyTurning = spawnArgs.GetBool ( "flyTurning", "0" ); + fl.allowDirectional = spawnArgs.GetBool ( "directionalMovement", "0" ); + fl.allowPushMovables = spawnArgs.GetBool( "af_push_moveables", "0" ); + fl.ignoreObstacles = spawnArgs.GetBool( "ignore_obstacles", "0" ); + fl.disabled = spawnArgs.GetBool ( "noMove", "0" ); + walkRange = spawnArgs.GetFloat ( "walkRange", "100" ); + walkTurn = spawnArgs.GetFloat ( "walkTurn", "45" ); + followRange = spawnArgs.GetVec2 ( "followRange", "70 250" ); + searchRange = spawnArgs.GetVec2 ( "searchRange", "0 1024" ); + attackPositionRange = spawnArgs.GetFloat ( "attackPositionRange", "0" ); + turnDelta = spawnArgs.GetFloat ( "turnDelta", "10" ); + blockTime = 0; + + spawnArgs.GetInt( "fly_offset", "100", fly_offset ); + spawnArgs.GetFloat( "fly_speed", "100", fly_speed ); + spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength ); + spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz ); + spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert ); + spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale ); + spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale ); + spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max ); + spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale ); + spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max ); + + spawnArgs.GetFloat( "turn_rate", "360", turnRate ); + spawnArgs.GetFloat( "kick_force", "4096", kickForce ); + spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius ); + spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime ); + spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime ); +} + + +/* +===================== +idMoveState::Save +===================== +*/ +void idMoveState::Save( idSaveGame *savefile ) const { + int i; + + savefile->Write ( &fl, sizeof(fl) ); + + savefile->WriteInt( (int)moveType ); + savefile->WriteInt( (int)moveCommand ); + savefile->WriteInt( (int)moveStatus ); + savefile->WriteVec3( moveDest ); + savefile->WriteVec3( moveDir ); + + savefile->WriteInt( toAreaNum ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( range ); + savefile->WriteFloat( wanderYaw ); + savefile->WriteInt( nextWanderTime ); + savefile->WriteInt( blockTime ); + obstacle.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( lastMoveTime ); + savefile->WriteInt( anim ); + + savefile->WriteInt( travelFlags ); + + savefile->WriteFloat ( kickForce ); + savefile->WriteFloat ( blockedRadius ); + savefile->WriteInt ( blockedMoveTime ); + savefile->WriteInt ( blockedAttackTime ); + + savefile->WriteFloat( ideal_yaw ); + savefile->WriteFloat( current_yaw ); + savefile->WriteFloat( turnRate ); + savefile->WriteFloat( turnVel ); + savefile->WriteFloat( anim_turn_yaw ); + savefile->WriteFloat( anim_turn_amount ); + savefile->WriteFloat( anim_turn_angles ); + + savefile->WriteJoint( flyTiltJoint ); + savefile->WriteFloat( fly_speed ); + savefile->WriteFloat( fly_bob_strength ); + savefile->WriteFloat( fly_bob_vert ); + savefile->WriteFloat( fly_bob_horz ); + savefile->WriteInt( fly_offset ); + savefile->WriteFloat( fly_seek_scale ); + savefile->WriteFloat( fly_roll_scale ); + savefile->WriteFloat( fly_roll_max ); + savefile->WriteFloat( fly_roll ); + savefile->WriteFloat( fly_pitch_scale ); + savefile->WriteFloat( fly_pitch_max ); + savefile->WriteFloat( fly_pitch ); + + savefile->WriteInt ( (int) currentDirection ); + savefile->WriteInt ( (int) idealDirection ); + savefile->WriteFloat( walkRange ); + savefile->WriteFloat( walkTurn ); + savefile->WriteVec2 ( followRange ); + savefile->WriteVec2 ( searchRange ); + savefile->WriteFloat( attackPositionRange ); + savefile->WriteFloat( turnDelta ); + + savefile->WriteVec3 ( goalPos ); + savefile->WriteInt ( goalArea ); + goalEntity.Save( savefile ); + savefile->WriteVec3( goalEntityOrigin ); + + savefile->WriteVec3( myPos ); // cnicholson: Added unsaved var + savefile->WriteInt( myArea ); // cnicholson: Added unsaved var + + savefile->WriteVec3 ( seekPos ); + + savefile->WriteInt( (int) MAX_PATH_LEN ); // cnicholson: Added unsaved vars + for (i=0; i< MAX_PATH_LEN; ++i) { + // TOSAVE: idReachability* reach; + savefile->WriteVec3( path[i].seekPos ); + } + + savefile->WriteInt( pathLen ); // cnicholson: Added unsaved var + savefile->WriteInt( pathArea ); // cnicholson: Added unsaved var + savefile->WriteInt( pathTime ); // cnicholson: Added unsaved var + + savefile->WriteVec3( addVelocity ); +} + +/* +===================== +idMoveState::Restore +===================== +*/ +void idMoveState::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->Read ( &fl, sizeof(fl) ); + + savefile->ReadInt( (int&)moveType ); + savefile->ReadInt( (int&)moveCommand ); + savefile->ReadInt( (int&)moveStatus ); + savefile->ReadVec3( moveDest ); + savefile->ReadVec3( moveDir ); + + savefile->ReadInt( toAreaNum ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( range ); + savefile->ReadFloat( wanderYaw ); + savefile->ReadInt( nextWanderTime ); + savefile->ReadInt( blockTime ); + obstacle.Restore ( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( lastMoveTime ); + savefile->ReadInt( anim ); + + savefile->ReadInt( travelFlags ); + + savefile->ReadFloat ( kickForce ); + savefile->ReadFloat ( blockedRadius ); + savefile->ReadInt ( blockedMoveTime ); + savefile->ReadInt ( blockedAttackTime ); + + savefile->ReadFloat( ideal_yaw ); + savefile->ReadFloat( current_yaw ); + savefile->ReadFloat( turnRate ); + savefile->ReadFloat( turnVel ); + savefile->ReadFloat( anim_turn_yaw ); + savefile->ReadFloat( anim_turn_amount ); + savefile->ReadFloat( anim_turn_angles ); + + savefile->ReadJoint( flyTiltJoint ); + savefile->ReadFloat( fly_speed ); + savefile->ReadFloat( fly_bob_strength ); + savefile->ReadFloat( fly_bob_vert ); + savefile->ReadFloat( fly_bob_horz ); + savefile->ReadInt( fly_offset ); + savefile->ReadFloat( fly_seek_scale ); + savefile->ReadFloat( fly_roll_scale ); + savefile->ReadFloat( fly_roll_max ); + savefile->ReadFloat( fly_roll ); + savefile->ReadFloat( fly_pitch_scale ); + savefile->ReadFloat( fly_pitch_max ); + savefile->ReadFloat( fly_pitch ); + + savefile->ReadInt ( (int&) currentDirection ); + savefile->ReadInt ( (int&) idealDirection ); + savefile->ReadFloat( walkRange ); + savefile->ReadFloat( walkTurn ); + savefile->ReadVec2 ( followRange ); + savefile->ReadVec2 ( searchRange ); + savefile->ReadFloat( attackPositionRange ); + savefile->ReadFloat( turnDelta ); + + savefile->ReadVec3 ( goalPos ); + savefile->ReadInt ( goalArea ); + goalEntity.Restore ( savefile ); + savefile->ReadVec3( goalEntityOrigin ); + + savefile->ReadVec3( myPos ); // cnicholson: Added unrestored var + savefile->ReadInt( myArea ); // cnicholson: Added unrestored var + + savefile->ReadVec3 ( seekPos ); + + savefile->ReadInt( num ); + for (i=0; i< num; ++i) { + // TOSAVE: idReachability* reach; + savefile->ReadVec3( path[i].seekPos ); + } + + savefile->ReadInt( pathLen ); // cnicholson: Added unrestored var + savefile->ReadInt( pathArea ); // cnicholson: Added unrestored var + savefile->ReadInt( pathTime ); // cnicholson: Added unrestored var + + savefile->ReadVec3( addVelocity ); +} + + +/* +============ +idAI::SetMoveType +============ +*/ +void idAI::SetMoveType ( moveType_t moveType ) { + if ( move.moveType == moveType ) { + return; + } + + StopSound ( SND_CHANNEL_HEART, false ); + + switch ( moveType ) { + case MOVETYPE_STATIC: + move.travelFlags = 0; + break; + + case MOVETYPE_FLY: + move.travelFlags = TFL_FLY|TFL_WALK|TFL_AIR; + StartSound ( "snd_fly", SND_CHANNEL_HEART, 0, false, NULL ); + break; + + case MOVETYPE_ANIM: + move.travelFlags = TFL_WALK|TFL_AIR; + break; + + case MOVETYPE_CUSTOM: + move.travelFlags = TFL_WALK|TFL_AIR|TFL_WALKOFFLEDGE; + break; + } + + move.moveType = moveType; +} + +/* +===================== +idAI::Event_SaveMove +===================== +*/ +void idAI::Event_SaveMove( void ) { + savedMove = move; +} + +/* +===================== +idAI::Event_RestoreMove +===================== +*/ +void idAI::Event_RestoreMove( void ) { + idVec3 dest; + + switch( savedMove.moveCommand ) { + case MOVE_NONE : + StopMove( savedMove.moveStatus ); + break; + + case MOVE_FACE_ENEMY : + FaceEnemy(); + break; + + case MOVE_FACE_ENTITY : + FaceEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_TO_ENEMY : + MoveToEnemy(); + break; + + case MOVE_TO_ENTITY : + MoveToEntity( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_OUT_OF_RANGE : + MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_TO_ATTACK: + MoveToAttack ( savedMove.goalEntity.GetEntity(), savedMove.anim ); + break; + + case MOVE_TO_COVER : + MoveToCover ( combat.attackRange[0], combat.attackRange[1], AITACTICAL_COVER ); + break; + + case MOVE_TO_POSITION : + MoveTo ( savedMove.moveDest, savedMove.range ); + break; + + case MOVE_SLIDE_TO_POSITION : + SlideToPosition( savedMove.moveDest, savedMove.duration ); + break; + + case MOVE_WANDER : + WanderAround(); + break; + } + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, dest ); + } +} + +/* +============ +idAI::KickObstacles +============ +*/ +void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) { + int i, numListedClipModels; + idBounds clipBounds; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + int clipmask; + idVec3 org; + idVec3 forceVec; + idVec3 delta; + idVec2 perpendicular; + + org = physicsObj.GetOrigin(); + + // find all possible obstacles + clipBounds = physicsObj.GetAbsBounds(); + clipBounds.TranslateSelf( dir * 32.0f ); + clipBounds.ExpandSelf( 8.0f ); + clipBounds.AddPoint( org ); + clipmask = physicsObj.GetClipMask(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( this, clipBounds, clipmask, clipModelList, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < numListedClipModels; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + if ( obEnt == alwaysKick ) { + // we'll kick this one outside the loop + continue; + } + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if (( obEnt->IsType( idMoveable::GetClassType() ) || obEnt->IsType( idAFAttachment::GetClassType() )) && obEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + delta = obEnt->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * obEnt->GetPhysics()->GetMass(); + obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec ); + } + } + + if ( alwaysKick ) { + delta = alwaysKick->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * alwaysKick->GetPhysics()->GetMass(); + alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec ); + } + +} + +/* +============ +ValidForBounds +============ +*/ +bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +===================== +idAI::SetAAS +===================== +*/ +void idAI::SetAAS( void ) { + idStr use_aas; + + spawnArgs.GetString( "use_aas", NULL, use_aas ); + if ( !use_aas || !use_aas[0] ) { + //don't intend to use AAS at all? + //no warning - lack of AAS intentional + aas = NULL; + return; + } + aas = gameLocal.GetAAS( use_aas ); + if ( aas ) { + const idAASSettings *settings = aas->GetSettings(); + if ( settings ) { + if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) { + gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() ); + } + float height = settings->maxStepHeight; + physicsObj.SetMaxStepHeight( height ); + return; + } else { + aas = NULL; + } + } + gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() ); +} + +/* +===================== +idAI::ReachedPos +===================== +*/ +bool idAI::ReachedPos( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range ) const { + // When moving towards the enemy just see if our bounding box touches the desination + if ( moveCommand == MOVE_TO_ENEMY ) { + if ( !enemy.ent || physicsObj.GetAbsBounds().IntersectsBounds( enemy.ent->GetPhysics()->GetAbsBounds().Expand( range ) ) ) { + return true; + } + return false; + } + + // Dont add vertical bias when using fly move + if ( move.moveType == MOVETYPE_FLY ) { + float offset; + if ( moveCommand == MOVE_TO_ENTITY ) { + offset = 0.0f; + } else { + offset = move.fly_offset; + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-range), idVec3(range,range,range+offset) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + + if ( moveCommand == MOVE_TO_TETHER ) + {//if you're not actually in the tether, we don't want to stop early (for compliance with ai_trigger condition_tether) + if ( !IsWithinTether() ) + { + return false; + } + } + + // Excluded z height when determining reached + if ( move.toAreaNum > 0 ) { + if ( PointReachableAreaNum( physicsObj.GetOrigin() ) == move.toAreaNum ) { + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-4096.0f), idVec3(range,range,4096.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-16.0f), idVec3(range,range,64.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); +} + +/* +===================== +idAI::PointReachableAreaNum +===================== +*/ +int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const { + int areaNum; + idVec3 size; + idBounds bounds; + + if ( !aas ) { + return 0; + } + + size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + if ( move.moveType == MOVETYPE_FLY ) { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ); + } else { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + } + + return areaNum; +} + +/* +===================== +idAI::PathToGoal +===================== +*/ +bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + idVec3 org; + idVec3 goal; + + if ( !aas ) { + return false; + } + + org = origin; + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !areaNum ) { + return false; + } + + goal = goalOrigin; + aas->PushPointIntoAreaNum( goalAreaNum, goal ); + if ( !goalAreaNum ) { + return false; + } + //push goal back up if flying to a point way above an area + if ( move.moveType == MOVETYPE_FLY ) { + if ( areaNum == goalAreaNum ) { + if ( goalOrigin.z > goal.z ) { + float areaTop = aas->AreaCeiling( goalAreaNum ) - GetPhysics()->GetBounds()[1].z; + if ( goalOrigin.z > areaTop ) { + //crap, cap it... + goal.z = areaTop; + } else { + goal.z = goalOrigin.z; + } + } + } + } + + + if ( move.moveType == MOVETYPE_FLY ) { + return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } else { + return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } +} + +/* +===================== +idAI::TravelDistance + +Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance. + +This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you +are from the goal, so try to break the goals up into shorter distances. +===================== +*/ +float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const { + int fromArea; + int toArea; + float dist; + idVec2 delta; + aasPath_t path; + + if ( !aas ) { + // no aas, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + fromArea = PointReachableAreaNum( start ); + toArea = PointReachableAreaNum( end ); + + if ( !fromArea || !toArea ) { + // can't seem to get there + return -1; + } + + if ( fromArea == toArea ) { + // same area, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + idReachability *reach; + int travelTime; + if ( !aas->RouteToGoalArea( fromArea, start, toArea, move.travelFlags, travelTime, &reach ) ) { + return -1; + } + + if ( DebugFilter(ai_debugMove) ) { // Travel Distance, Fly Path & Walk Path + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( start, toArea, end ); + } else { + aas->ShowWalkPath( start, toArea, end ); + } + } + + return travelTime; +} + +float idAI::TravelDistance ( idEntity *ent ) const { + return TravelDistance ( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( idEntity* start, idEntity* end ) const { + assert( start ); + assert( end ); + return TravelDistance( start->GetPhysics()->GetOrigin(), end->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( const idVec3 &pos ) const { + return TravelDistance( physicsObj.GetOrigin(), pos ); +} + +/* +============ +idAI::ScriptedPlaybackMove +============ +*/ +void idAI::ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + // Start the playback + if ( !mPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ) ) { + ScriptedEnd ( ); + return; + } + + move.goalEntity = NULL; + move.moveCommand = MOVE_RV_PLAYBACK; + move.moveType = MOVETYPE_PLAYBACK; + move.fl.done = false; + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +===================== +idAI::FaceEnemy + +Continually face the enemy's last known position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEnemy( void ) { + idEntity *enemyEnt = enemy.ent; + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + TurnToward( enemy.lastKnownPosition ); + move.goalEntity = enemyEnt; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENEMY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::FaceEntity + +Continually face the entity position. MoveDone will never be true in this case. +===================== +*/ +bool idAI::FaceEntity( idEntity *ent ) { + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + idVec3 entityOrg = ent->GetPhysics()->GetOrigin(); + TurnToward( entityOrg ); + move.goalEntity = ent; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENTITY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = false; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::StartMove + +Initialize a new movement by setting up the movement structure +===================== +*/ +bool idAI::StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ) { + // If we are already there then we are done + if ( ReachedPos( goalOrigin, command ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.lastMoveOrigin = physicsObj.GetOrigin ( ); + + move.seekPos = move.goalPos = move.moveDest = goalOrigin; + move.toAreaNum = goalArea; + move.goalEntity = goalEntity; + move.moveCommand = command; + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = move.fly_speed; + move.startTime = gameLocal.time; + move.range = range; + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = true; + + aasSensor->Reserve ( feature ); + + OnStartMoving ( ); + + return true; +} + +/* +===================== +idAI::StopMove +===================== +*/ +void idAI::StopMove( moveStatus_t status ) { + aiMoveCommand_t oldCommand = move.moveCommand; + float saveZ = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + move.fl.obstacleInPath = false; + move.fl.blocked = false; + + if ( move.moveType == MOVETYPE_FLY ) { + if ( move.moveCommand == MOVE_TO_ENEMY + || move.moveCommand == MOVE_TO_ATTACK ) { + saveZ = (move.moveDest.z-physicsObj.GetOrigin().z); + } + } + move.moveCommand = MOVE_NONE; + move.moveStatus = status; + move.toAreaNum = 0; + move.goalEntity = NULL; + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += saveZ; + move.startTime = gameLocal.time; + move.duration = 0; + move.range = 0.0f; + move.speed = 0.0f; + move.anim = 0; + + move.moveDir.Zero(); + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + + // Callback for handling stopping + if ( oldCommand != MOVE_NONE ) { + OnStopMoving ( oldCommand ); + } +} + +/* +===================== +idAI::MoveToTether +===================== +*/ +bool idAI::MoveToTether ( rvAITether* tether ) { + aasGoal_t goal; + + // find a goal using the currently active tether + if ( !aas || !tether ) { + return false; + } + + if ( !tether->FindGoal ( this, goal ) ) { + //This is extremely bad - if 2 guys both try to get to the center of a tether, they get hosed. + return MoveTo ( tether->GetPhysics()->GetOrigin ( ), tether->GetOriginReachedRange() ); + } + + if ( move.moveType == MOVETYPE_FLY ) { + //Hmm... we shouldn't have to clamp as the area wouldn't have been valid unless the tether was in its bounds and height, but...? + //float areaTop = aas->AreaCeiling( goal.areaNum ); + goal.origin.z = tether->GetPhysics()->GetOrigin().z; + } + + return StartMove ( MOVE_TO_TETHER, goal.origin, goal.areaNum, NULL, NULL, AI_TETHER_MINRANGE ); +} + +/* +===================== +idAI::MoveToAttack +===================== +*/ +bool idAI::MoveToAttack ( idEntity *ent, int attack_anim ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( ent ); + + // if we havent started a find yet or the current find is something other than a attack find then start a new one + if ( !aasFind || !dynamic_cast(aasFind) ) { + // Allocate the new aas find + delete aasFind; + aasFind = new rvAASFindGoalForAttack ( this ); + + // Find a goal that gives us a viable attack position on our enemy + aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, IsTethered()?0:move.searchRange[0], move.searchRange[1], &obstacle, 1, *aasFind ); + } + + assert ( aasFind ); + + // Test some more points with the existing find + rvAASFindGoalForAttack* aasFindAttack = static_cast(aasFind); + if ( !aasFindAttack->TestCachedGoals ( aifl.simpleThink ? 1 : 4, goal ) ) { + delete aasFind; + aasFind = NULL; + return false; + } + + // Havent found a goal yet but exhaused our trace count + if ( !goal.areaNum ) { + return false; + } + + // Dont need the find anymore + delete aasFind; + aasFind = NULL; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(goal.areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( pos.z > areaTop ) { + goal.origin.z = areaTop; + } else if ( pos.z > goal.origin.z+1.0f ) { + goal.origin.z = pos.z; + } else if ( move.fly_offset > 0 ) { + if ( (goal.origin.z+move.fly_offset) > areaTop ) { + goal.origin.z = areaTop; + } else { + goal.origin.z += move.fly_offset; + } + } + } + } + + return StartMove ( MOVE_TO_ATTACK, goal.origin, goal.areaNum, ent, NULL, move.attackPositionRange?move.attackPositionRange:8.0f ); +} + +/* +===================== +idAI::MoveToEnemy +===================== +*/ +bool idAI::MoveToEnemy( void ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !enemy.ent ) { + return false; + } + +// pos = LastKnownPosition ( enemy.ent ); + pos = enemy.ent->GetPhysics()->GetOrigin ( ); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent && move.goalEntityOrigin == pos ) { + return true; + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENEMY, 8.0f ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = pos; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( move.fly_offset > 0 ) { + if ( (pos.z+move.fly_offset) > areaTop ) { + pos.z = areaTop; + move.goalEntityOrigin.z = areaTop; + } else { + pos.z += move.fly_offset; + move.goalEntityOrigin.z += move.fly_offset; + } + } + } + } + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + move.moveDest = pos; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENEMY, pos, areaNum, enemy.ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveToEntity +===================== +*/ +bool idAI::MoveToEntity( idEntity *ent, float range ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !ent ) { + return false; + } + + // Where do we want to go? + pos = ent->GetPhysics()->GetOrigin(); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent && move.goalEntityOrigin == pos ) { + return true; + } + + // If we arent flying we should move to a position on the floor + if ( move.moveType != MOVETYPE_FLY ) { + ent->GetFloorPos( 64.0f, pos ); + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENTITY, range ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = ent->GetPhysics()->GetOrigin(); + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent ) { + move.moveDest = pos; + move.range = range <= 0.0f ? 8.0f : range; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENTITY, pos, areaNum, ent, NULL, range <= 0.0f ? 8.0f : range ); +} + +/* +===================== +idAI::MoveOutOfRange +===================== +*/ +bool idAI::MoveOutOfRange( idEntity *ent, float range, float minRange ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + int obstacles; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + // consider the entity the monster is getting close to as an obstacle + if ( ent != this ) { + obstacles = 1; + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + } else { + obstacles = 0; + } + + pos = LastKnownPosition ( ent ); + + // Find a goal out of range of where we are + rvAASFindGoalOutOfRange findGoal( this ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, minRange, range, &obstacle, obstacles, findGoal ) ) { + return false; + } + + return StartMove ( MOVE_OUT_OF_RANGE, goal.origin, goal.areaNum, ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveTo +===================== +*/ +bool idAI::MoveTo ( const idVec3 &pos, float range ) { + idVec3 org; + int areaNum; + aasPath_t path; + + if ( !aas ) { + return false; + } + + org = pos; + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // Can we get to where we want to go? + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), PointReachableAreaNum( physicsObj.GetOrigin() ), org ) ) { + return false; + } + + // Start moving + return StartMove ( MOVE_TO_POSITION, org, areaNum, NULL, NULL, range ); +} + +/* +===================== +idAI::MoveToCover +===================== +*/ +bool idAI::MoveToCover( float minRange, float maxRange, aiTactical_t coverType ) { + idVec3 org; + int areaNum; + aasPath_t path; + aasFeature_t* feature = 0; + idVec3 featureOrigin; + + if ( !aas ) { + return false; + } + + // Look for nearby cover + switch ( coverType ) { + case AITACTICAL_HIDE: aasSensor->SearchHide(); break; + case AITACTICAL_COVER_FLANK: aasSensor->SearchFlank(); break; + case AITACTICAL_COVER_ADVANCE: aasSensor->SearchAdvance(); break; + case AITACTICAL_COVER_RETREAT: aasSensor->SearchRetreat(); break; + case AITACTICAL_COVER_AMBUSH: aasSensor->SearchAmbush(); break; + case AITACTICAL_COVER: + default: + aasSensor->SearchCover(); + break; + } + + if ( !aasSensor->FeatureCount ( ) ) { + return false; + } + + feature = aasSensor->Feature ( 0 ); + featureOrigin = feature->Origin ( ); + org = featureOrigin; + areaNum = 0; + + // Find the aas area our cover point is in + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // See if there is a path to our goal or not + aas->PushPointIntoAreaNum( areaNum, org ); +/* + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, org ) ) { + return false; + } +*/ + + combat.coverValidTime = gameLocal.time; + + // Start the move + return StartMove ( MOVE_TO_COVER, org, areaNum, enemy.ent, feature, AI_COVER_MINRANGE / 2.0f ); +} + +/* +===================== +idAI::MoveToHide +===================== +*/ +bool idAI::MoveToHide ( void ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + // Need an enemy to hide from + if ( !aas || !enemy.ent ) { + return false; + } + + const idVec3& org = physicsObj.GetOrigin(); + obstacle.absBounds = enemy.ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( enemy.ent ); + + // Search aas for a suitable goal + rvAASFindGoalForHide findGoal( pos ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, 0.0f, 0.0f, &obstacle, 1, findGoal ) ) { + return false; + } + + // Start the movement + return StartMove ( MOVE_TO_HIDE, goal.origin, goal.areaNum, enemy.ent, NULL, 0.0f ); +} + +/* +===================== +idAI::SlideToPosition +===================== +*/ +bool idAI::SlideToPosition( const idVec3 &pos, float time ) { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_SLIDE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = false; + aifl.simpleThink = false; + + move.fl.allowAnimMove = false; + + if ( move.duration > 0 ) { + move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration ); + if ( move.moveType != MOVETYPE_FLY ) { + move.moveDir.z = 0.0f; + } + move.speed = move.moveDir.LengthFast(); + } + + return true; +} + +/* +===================== +idAI::WanderAround +===================== +*/ +bool idAI::WanderAround( void ) { + idVec3 dest; + + StopMove( MOVE_STATUS_DONE ); + + dest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + if ( !NewWanderDir( dest ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + return false; + } + + return StartMove ( MOVE_WANDER, dest, 0, NULL, NULL, 0.0f ); +} + +/* +================ +idAI::StepDirection +================ +*/ +bool idAI::StepDirection( float dir ) { + predictedPath_t path; + idVec3 org; + + move.wanderYaw = dir; + move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward(); + + org = physicsObj.GetOrigin(); + + idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) { + // don't report being blocked if we ran into our goal entity + return true; + } + + if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) { + float z; + + move.moveDir = path.endVelocity * 1.0f / 48.0f; + + // trace down to the floor and see if we can go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 floorPos = path.endPos; + idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = -1.0f; + return true; + } + + // trace up to see if we can go over something and go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 ceilingPos = path.endPos; + + for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) { + idVec3 start; + if ( z <= ceilingPos.z ) { + start.x = org.x; + start.y = org.y; + start.z = z; + } else { + start = ceilingPos; + } + idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = 1.0f; + return true; + } + } + return false; + } + + return ( path.endEvent == 0 ); +} + +/* +================ +idAI::NewWanderDir +================ +*/ +bool idAI::NewWanderDir( const idVec3 &dest ) { + float deltax, deltay; + float d[ 3 ]; + float tdir, olddir, turnaround; + + move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 ); + + olddir = idMath::AngleNormalize360( ( int )( move.current_yaw / 45 ) * 45 ); + turnaround = idMath::AngleNormalize360( olddir - 180 ); + + idVec3 org = physicsObj.GetOrigin(); + deltax = dest.x - org.x; + deltay = dest.y - org.y; + if ( deltax > 10 ) { + d[ 1 ]= 0; + } else if ( deltax < -10 ) { + d[ 1 ] = 180; + } else { + d[ 1 ] = DI_NODIR; + } + + if ( deltay < -10 ) { + d[ 2 ] = 270; + } else if ( deltay > 10 ) { + d[ 2 ] = 90; + } else { + d[ 2 ] = DI_NODIR; + } + + // try direct route + if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) { + if ( d[ 1 ] == 0 ) { + tdir = d[ 2 ] == 90 ? 45 : 315; + } else { + tdir = d[ 2 ] == 90 ? 135 : 215; + } + + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + + // try other directions + if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) { + tdir = d[ 1 ]; + d[ 1 ] = d[ 2 ]; + d[ 2 ] = tdir; + } + + if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) { + return true; + } + + if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) { + return true; + } + + // there is no direct path to the player, so pick another direction + if ( olddir != DI_NODIR && StepDirection( olddir ) ) { + return true; + } + + // randomly determine direction of search + if ( gameLocal.random.RandomInt() & 1 ) { + for( tdir = 0; tdir <= 315; tdir += 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } else { + for ( tdir = 315; tdir >= 0; tdir -= 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } + + if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { + return true; + } + + // can't move + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; +} + +/* +===================== +idAI::GetMovePos +===================== +*/ +bool idAI::GetMovePos( idVec3 &seekPos, idReachability** seekReach ) { + int areaNum; + aasPath_t path; + bool result; + idVec3 org; + + org = physicsObj.GetOrigin(); + seekPos = org; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = 0; + } + // RAVEN END + + switch( move.moveCommand ) { + case MOVE_NONE : + seekPos = move.moveDest; + return false; + + case MOVE_FACE_ENEMY : + case MOVE_FACE_ENTITY : + seekPos = move.moveDest; + return false; + + case MOVE_TO_POSITION_DIRECT : + seekPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + return false; + + case MOVE_SLIDE_TO_POSITION : + seekPos = org; + return false; + + case MOVE_TO_ENTITY: + MoveToEntity( move.goalEntity.GetEntity(), move.range ); + break; + + case MOVE_TO_ENEMY: + if ( !MoveToEnemy() && combat.tacticalCurrent == AITACTICAL_MELEE ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; + } + break; + } + + if ( move.moveType == MOVETYPE_FLY && move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + //flying + if ( DistanceTo( move.moveDest ) < 1024.0f ) { + //less than huge translation dist, which is actually 4096, but, just to be safe... + trace_t moveTrace; + gameLocal.Translation( this, moveTrace, org, move.moveDest, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this, move.goalEntity.GetEntity() ); + if ( moveTrace.fraction >= 1.0f ) { + //can head straight for it, so do it. + seekPos = move.moveDest; + return false; + } + } + } + + move.moveStatus = MOVE_STATUS_MOVING; + result = false; + + if ( move.moveCommand == MOVE_WANDER ) { + move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + } else { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + seekPos = org; + return false; + } + } + + if ( aas && move.toAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) { + seekPos = path.moveGoal; + if ( aas->GetFile() ) { + //we have AAS + if ( move.fly_offset > 0 ) { + if ( (seekPos-move.moveDest).LengthSqr() > 10.0f ) { + //not heading to final dest, that already has offset in it + float areaTop = aas->AreaCeiling(path.moveAreaNum)-GetPhysics()->GetBounds()[1][2]; + if ( (seekPos.z+move.fly_offset) > areaTop ) { + seekPos.z = areaTop; + } else { + seekPos.z += move.fly_offset; + } + } + } + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = (idReachability*)(path.reachability); + } + // RAVEN END + + result = true; + move.nextWanderTime = 0; + } else { + move.fl.goalUnreachable = true; + } + } + + + if ( !result ) { + // wander around + if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) { + result = NewWanderDir( move.moveDest ); + if ( !result ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + seekPos = org; + + return false; + } + } else { + result = true; + } + + seekPos = org + move.moveDir * 2048.0f; + + } else { + move.fl.goalUnreachable = false; + } + + if ( DebugFilter(ai_debugMove) ) { // YELLOW = seekPos + gameRenderWorld->DebugLine( colorYellow, physicsObj.GetOrigin(), seekPos ); + } + + return result; +} + + + +/* +===================== +idAI::BlockedFailSafe +===================== +*/ +void idAI::BlockedFailSafe( void ) { +/* move.fl.blocked = false; + + if ( !ai_blockedFailSafe.GetBool() || move.blockedRadius < 0.0f ) { + return; + } + if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL || + ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( move.blockedRadius ) ) { + move.lastMoveOrigin = physicsObj.GetOrigin(); + move.lastMoveTime = gameLocal.time; + } + if ( move.lastMoveTime < gameLocal.time - move.blockedMoveTime ) { + if ( lastAttackTime < gameLocal.time - move.blockedAttackTime ) { + move.fl.blocked = true; + move.lastMoveTime = gameLocal.time; + } + }*/ +} + +/*********************************************************************** + + turning + +***********************************************************************/ + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( float yaw ) { + move.ideal_yaw = idMath::AngleNormalize180( yaw ); + bool result = FacingIdeal(); + return result; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + dir = pos - physicsObj.GetOrigin(); + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.ent == NULL ) ) { + move.ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + + return FacingIdeal(); +} + +/* +===================== +idAI::TurnTowardLeader +===================== +*/ +bool idAI::TurnTowardLeader( bool faceLeaderByDefault ) { + if ( !leader.GetEntity() ) { + return false; + } + //see if there's a wall in the direction the player's looking + trace_t tr; + + idVec3 leaderLookDest = leader->GetPhysics()->GetOrigin() + (( (leader.GetEntity() && leader.GetEntity()->IsType( idPlayer::GetClassType() )) ? ((idPlayer*)leader.GetEntity())->intentDir:leader->viewAxis[0])*300.0f); + idVec3 myLookDir = leaderLookDest-GetPhysics()->GetOrigin(); + myLookDir.Normalize(); + idVec3 start = GetPhysics()->GetOrigin(); + start.z += EyeHeight(); + idVec3 end = start + (myLookDir*128.0f); + end.z = start.z; + idVec3 currentLookDir = viewAxis[0]; + currentLookDir.Normalize(); + + if ( !GetEnemy() && (!leader->IsType(idPlayer::GetClassType()) || !((idPlayer*)leader.GetEntity())->IsFlashlightOn()) ) { + //Not in combat and leader isn't looking around with flashlight + if ( myLookDir*currentLookDir > 0.666f ) { + //new dir isn't different enough from current dir for me to care + return true; + } + } + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + idEntity* traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //wall there - NOTE: okay to look at doors + if ( faceLeaderByDefault//want to face leader by default + || leader->viewAxis[0].ToYaw() == move.ideal_yaw ) {//a wall must have moved in front of us? + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + //just keep looking in the last valid dir + if ( FacingIdeal() ) { + //make sure it's still valid + idVec3 end = start + (viewAxis[0]*128.0f); + end.z = start.z; + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //a wall right in front of us - NOTE: okay to look at doors + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + } + return true; + } + //opening, face leader's dir + return TurnToward( myLookDir.ToYaw() ); +} + +/* +============ +idAI::DirectionalTurnToward + +Turn toward the given point using directional movement +============ +*/ +bool idAI::DirectionalTurnToward ( const idVec3 &pos ) { + static float moveDirOffset [ MOVEDIR_MAX ] = { + 0.0f, 180.0f, -90.0f, 90.0f + }; + + // Issue standard TurnToward if we are not currently eligible for directional movement + if ( combat.tacticalCurrent != AITACTICAL_MOVE_PLAYERPUSH ) { + //always move directionally when getting out of the player's way + if( !combat.fl.aware || focusType < AIFOCUS_USE_DIRECTIONAL_MOVE || !move.fl.moving || move.moveCommand == MOVE_TO_ENEMY || !move.fl.allowDirectional ) { + move.idealDirection = MOVEDIR_FORWARD; + return TurnToward ( pos ); + } + } + + // Turn towards our desination + float moveYaw; + TurnToward ( pos ); + moveYaw = move.ideal_yaw; + + // Turn towards the goal entity and determine the angle difference + TurnToward ( currentFocusPos ); + + // Check for a direction change only when we can no longer see + // where we need to look and where we are looking is greater than 3/4ths the maximum look + if ( (pos - GetPhysics()->GetOrigin()).LengthFast ( ) > 8.0f ) + if ( !FacingIdeal ( ) ) + if ( fabs ( idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ) >= fabs(lookMax[YAW]) || + fabs ( idMath::AngleNormalize180 ( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) - move.current_yaw ) ) >= 80.0f ) { + + float diffYaw; + diffYaw = idMath::AngleNormalize180 ( move.ideal_yaw - moveYaw ); + + if ( diffYaw > -45.0f && diffYaw < 45.0f ) { + move.idealDirection = MOVEDIR_FORWARD; + } else if ( diffYaw < -135.0f || diffYaw > 135.0f ) { + move.idealDirection = MOVEDIR_BACKWARD; + } else if ( diffYaw < 0.0f ) { + move.idealDirection = MOVEDIR_LEFT; + } else { + move.idealDirection = MOVEDIR_RIGHT; + } + } + + return TurnToward( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) ); +} + +/* +===================== +idAI::Turn +===================== +*/ +void idAI::Turn( void ) { + float diff; + float diff2; + float turnAmount; + animFlags_t animflags; + + // If cant turn or turning is disabled just bail + if ( !CanTurn ( ) ) { + return; + } + + // check if the animator has marked this anim as non-turning + if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) { + animflags = legsAnim.GetAnimFlags(); + } else { + animflags = torsoAnim.GetAnimFlags(); + } + if ( animflags.ai_no_turn ) { + return; + } + + if ( move.anim_turn_angles && animflags.anim_turn ) { + idMat3 rotateAxis; + + // set the blend between no turn and full turn + float frac = move.anim_turn_amount / move.anim_turn_angles; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac ); + + // get the total rotation from the start of the anim + animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis ); + move.current_yaw = idMath::AngleNormalize180( move.anim_turn_yaw + rotateAxis[ 0 ].ToYaw() ); + } else { + diff = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( move.currentDirection != move.idealDirection ) { + move.turnVel += (AI_TURN_SCALE * 2.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.running ) { + move.turnVel += (AI_TURN_SCALE * 3.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.moving ) { + move.turnVel += (AI_TURN_SCALE * 1.5f)* diff * MS2SEC( gameLocal.msec ); + } else { + move.turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec ); + } + + if ( move.turnVel > move.turnRate ) { + move.turnVel = move.turnRate; + } else if ( move.turnVel < -move.turnRate ) { + move.turnVel = -move.turnRate; + } + turnAmount = move.turnVel * MS2SEC( gameLocal.msec ); + if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } + move.current_yaw = idMath::AngleNormalize180( move.current_yaw + turnAmount ); + diff2 = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( idMath::Fabs( diff2 ) < 0.1f ) { + move.current_yaw = move.ideal_yaw; + } + } + + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + +// if ( DebugFilter(ai_debugMove) ) { // RED = ideal_yaw, GREEN = current_yaw, YELLOW = current+velocity +// const idVec3 &org = physicsObj.GetOrigin(); +// gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, move.ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, move.current_yaw, 0 ).ToForward() * 48, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, move.current_yaw + move.turnVel, 0 ).ToForward() * 32, gameLocal.msec ); +// if ( move.anim_turn_angles && animflags.anim_turn ) { +// gameRenderWorld->DebugLine( colorOrange, org, org + idAngles( 0, move.anim_turn_yaw, 0 ).ToForward() * 32, gameLocal.msec ); +// } +// } +} + +/* +===================== +idAI::FacingIdeal +===================== +*/ +bool idAI::FacingIdeal( void ) { + float diff; + + if ( !move.turnRate ) { + return true; + } + + diff = idMath::AngleDelta ( move.current_yaw, move.ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + move.current_yaw = move.ideal_yaw; + return true; + } + + return false; +} + +/* +================ +idAI::AnimTurn +================ +*/ +void idAI::AnimTurn ( float angles, bool force ) { + move.turnVel = 0.0f; + move.anim_turn_angles = angles; + if ( angles ) { + move.anim_turn_yaw = move.current_yaw; + + if ( force ) { + move.anim_turn_amount = angles; + } else { + move.anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( move.current_yaw - move.ideal_yaw ) ); + if ( move.anim_turn_amount > move.anim_turn_angles ) { + move.anim_turn_amount = move.anim_turn_angles; + } + } + } else { + move.anim_turn_amount = 0.0f; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); + } +} + +/*********************************************************************** + + Movement Helper Functions + +***********************************************************************/ + +/* +================ +idAI::ApplyImpulse +================ +*/ +void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + // FIXME: Jim take a look at this and see if this is a reasonable thing to do + // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 ) + // and we don't want him taking physics impulses as it can knock him off the path + if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) { + idActor::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +===================== +idAI::GetAnimMoveDelta +===================== +*/ +void idAI::GetAnimMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) { + idVec3 oldModelOrigin; + idVec3 modelOrigin; + + animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta = axis * delta; + + if ( modelOffset != vec3_zero ) { + // the pivot of the monster's model is around its origin, and not around the bounding + // box's origin, so we have to compensate for this when the model is offset so that + // the monster still appears to rotate around it's origin. + oldModelOrigin = modelOffset * oldaxis; + modelOrigin = modelOffset * axis; + delta += oldModelOrigin - modelOrigin; + } + + delta *= physicsObj.GetGravityAxis(); +} + + + +/* +===================== +TestTeammateCollisions +===================== +*/ + +void TestTeammateCollisions(idAI* owner) { + idActor* teammate; + idVec3 teammateDirection; + idVec3 teammateVelocity; + float teammateDirectionDot; + float teammateDistance; + float teammateSpeed; + const idVec3& myOrigin = owner->GetPhysics()->GetOrigin(); + const idBounds& myBounds = owner->GetPhysics()->GetBounds(); + idVec3 myVelocity = owner->GetPhysics()->GetLinearVelocity(); + float mySpeed = myVelocity.NormalizeFast(); + + // Don't Bother, We're Blocked Or Not Moving + //--------------------------------------------- + if (owner->move.blockTime>gameLocal.GetTime() || !owner->move.fl.moving || mySpeed<1.0f) { + return; + } + + + for (teammate = aiManager.GetAllyTeam((aiTeam_t)owner->team); teammate; teammate = teammate->teamNode.Next()) { + if (teammate->fl.hidden || teammate == owner || teammate->health <= 0) { + continue; + } + + // If On Same Floor + //------------------ + teammateDirection = teammate->GetPhysics()->GetOrigin() - myOrigin; + if (fabsf(teammateDirection[2])>myBounds.Size().z) { + continue; + } + + // If Close Enough + //----------------- + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance>128.0f) { + continue; + } + + // Completely Ignore Dudes Directly Behind Me + //-------------------------------------------- + teammateDirectionDot = teammateDirection*myVelocity; + if (teammateDirectionDot<-0.5f) { + continue; + } + + // Switch To Walk If I'm Heading For A Teammate + //---------------------------------------------- + if (teammateDirectionDot>0.85f) { + owner->move.fl.obstacleInPath = true; // make him slow to a walk + + if ( owner->DebugFilter(ai_debugMove) ) { // WHITE = Walk Teammate Near + gameRenderWorld->DebugArrow( colorWhite, myOrigin, teammate->GetPhysics()->GetOrigin(), 4, 250 ); + } + } + + if (teammateDistance<48.0f) { + + teammateVelocity = teammate->GetPhysics()->GetLinearVelocity(); + teammateSpeed = teammateVelocity.NormalizeFast(); + + if (teammateSpeed>50.0f) { + + // If I'm Following Him, And I'm RIGHT Behind Him, Stop And Let Him Go A Bit Farther Ahead + //------------------------------------------------------------------------------------------ + if (teammateDirectionDot>0.85f && teammateVelocity*myVelocity>0.4f) { + owner->move.blockTime = gameLocal.time + 1000; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1000); + } + + } + + // Stop moving if my leader or a guy with a higher entity number is headed straight for me + //----------------------------------------------------------------------------------------- + else if (teammate->entityNumberentityNumber && teammateDirection*teammateVelocity<0.5f) { + owner->move.blockTime = gameLocal.time + 1500; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1500); + } + } + } + } + } +} + + + + + + +/* +===================== +idAI::CheckObstacleAvoidance +===================== +*/ +void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &seekPos, idReachability* goalReach ) { + + move.fl.blocked = false; // Makes Character Stop Moving + move.fl.obstacleInPath = false; // Makes Character Walk + move.obstacle = NULL; + seekPos = goalPos; + + if (move.fl.ignoreObstacles) { + return; + } + if ( g_perfTest_aiNoObstacleAvoid.GetBool() ) { + return; + } + + // Test For Path Around Obstacles + //-------------------------------- + obstaclePath_t path; + move.fl.blocked = !FindPathAroundObstacles( &physicsObj, aas, move.moveCommand == MOVE_TO_ENEMY ? enemy.ent : NULL, physicsObj.GetOrigin(), goalPos, path ); + move.fl.obstacleInPath = (path.firstObstacle || path.seekPosObstacle || path.startPosObstacle); + move.obstacle = (path.firstObstacle)?(path.firstObstacle):(path.seekPosObstacle); + seekPos = path.seekPos; + + // Don't Worry About Obstacles Out Of Walk Range + //----------------------------------------------- + if (move.obstacle && DistanceTo(move.obstacle)>155.0f) { + move.fl.blocked = false; + move.fl.obstacleInPath = false; + move.obstacle = 0; + seekPos = goalPos; + } + + // cdr: Alternate Routes Bug + // If An Obstacle Remains, And The Seek Pos Is Fairly Farr Off Of The Straight Line Path, Then Mark The Reach As Blocked + //----------------------------------------------------------------------------------------------------------------------- + if (move.fl.obstacleInPath && goalReach && !(goalReach->travelType&TFL_INVALID) && seekPos.Dist2XY(goalPos)>100.0f) { + float scale; + float dist; + + dist = seekPos.DistToLineSeg(physicsObj.GetOrigin(), goalPos, scale); + if (scale<0.95f && dist>50.0f) { + aiManager.MarkReachBlocked(aas, goalReach, path.allObstacles); + } + } + + TestTeammateCollisions(this); + + + + if ( DebugFilter(ai_showObstacleAvoidance) ) { + gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec ); + gameRenderWorld->DebugLine( !move.fl.blocked ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec ); + } +} + +/* +============ +idAI::TestAnimMove +============ +*/ +bool idAI::TestAnimMove ( int animNum, idEntity *ignore, idVec3 *pMoveVec ) { + const idAnim* anim; + predictedPath_t path; + idVec3 moveVec; + + anim = GetAnimator()->GetAnim ( animNum ); + assert ( anim ); + +// moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.current_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec / MS2SEC( anim->Length() ), anim->Length(), 200, SE_BLOCKED | SE_ENTER_LEDGE_AREA, path, ignore ); + + if ( DebugFilter(ai_debugMove) ) { // TestAnimMove + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + } + if ( pMoveVec ) { + *pMoveVec = moveVec; + } + + return ( path.endEvent == 0 ); +} + +/* +===================== +Seek +===================== +*/ +idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) { + idVec3 predictedPos; + idVec3 goalDelta; + idVec3 seekVel; + + // predict our position + predictedPos = org + vel * prediction; + goalDelta = goal - predictedPos; +// goalDelta = goal - org; + seekVel = goalDelta * MS2SEC( gameLocal.msec ); + + return seekVel; +} + +/*********************************************************************** + + Movement + +***********************************************************************/ + +/* +===================== +idAI::DeadMove +===================== +*/ +void idAI::DeadMove( void ) { + idVec3 delta; + monsterMoveResult_t moveResult; + + DeathPush ( ); + + idVec3 org = physicsObj.GetOrigin(); + + GetAnimMoveDelta( viewAxis, viewAxis, delta ); + physicsObj.SetDelta( delta ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + move.fl.onGround = physicsObj.OnGround(); +} + +/* +===================== +idAI::AdjustFlyingAngles +===================== +*/ +void idAI::AdjustFlyingAngles( void ) { + idVec3 vel; + float speed; + float roll; + float pitch; + + vel = physicsObj.GetLinearVelocity(); + + speed = vel.Length(); + if ( speed < 5.0f ) { + roll = 0.0f; + pitch = 0.0f; + } else { + roll = vel * viewAxis[ 1 ] * -move.fly_roll_scale / move.fly_speed; + if ( roll > move.fly_roll_max ) { + roll = move.fly_roll_max; + } else if ( roll < -move.fly_roll_max ) { + roll = -move.fly_roll_max; + } + + pitch = vel * viewAxis[ 0 ] * -move.fly_pitch_scale / move.fly_speed; + if ( pitch > move.fly_pitch_max ) { + pitch = move.fly_pitch_max; + } else if ( pitch < -move.fly_pitch_max ) { + pitch = -move.fly_pitch_max; + } + } + + move.fly_roll = move.fly_roll * 0.95f + roll * 0.05f; + move.fly_pitch = move.fly_pitch * 0.95f + pitch * 0.05f; + + if ( move.flyTiltJoint != INVALID_JOINT ) { + animator.SetJointAxis( move.flyTiltJoint, JOINTMOD_WORLD, idAngles( move.fly_pitch, 0.0f, move.fly_roll ).ToMat3() ); + } else { + viewAxis = idAngles( move.fly_pitch, move.current_yaw, move.fly_roll ).ToMat3(); + } +} + +/* +===================== +idAI::AddFlyBob +===================== +*/ +void idAI::AddFlyBob( idVec3 &vel ) { + idVec3 fly_bob_add; + float t; + + if ( move.fly_bob_strength ) { + t = MS2SEC( gameLocal.time + entityNumber * 497 ); + fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * move.fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * move.fly_bob_vert ) ) * move.fly_bob_strength; + vel += fly_bob_add * MS2SEC( gameLocal.msec ); + if ( DebugFilter(ai_debugMove) ) { // FlyBob + const idVec3 &origin = physicsObj.GetOrigin(); + gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 ); + } + } +} + +/* +===================== +idAI::AdjustFlyHeight +===================== +*/ +void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) { + const idVec3 &origin = physicsObj.GetOrigin(); + predictedPath_t path; + idVec3 end; + idVec3 dest; + trace_t trace; + bool goLower; + + // make sure we're not flying too high to get through doors + // FIXME: with move to enemy this thinks we're going to hit + // the enemy and then says we need to go lower... but that's not quite right... + // if we're about to hit our enemy, then maintaining our height should be desirable..? + goLower = false; + if ( origin.z > goalPos.z ) { + dest = goalPos; + dest.z = origin.z + 128.0f; + idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path, move.goalEntity.GetEntity() ); + if ( path.endPos.z < origin.z ) { + + //Hmm, should we make sure the path.endPos is high enough off the ground? + if ( move.fly_offset && (move.moveCommand == MOVE_TO_ENEMY || move.moveCommand == MOVE_TO_ATTACK) ) { + int pathArea = PointReachableAreaNum( path.endPos ); + if ( aas && (aas->AreaFlags( pathArea )&AREA_FLOOR) ) { + path.endPos.z = aas->AreaBounds( pathArea )[0][2]; + float areaTop = aas->AreaCeiling( pathArea ) - GetPhysics()->GetBounds()[1].z; + if ( path.endPos.z + move.fly_offset > areaTop ) { + path.endPos.z = areaTop; + } else { + path.endPos.z += move.fly_offset; + } + } + } + + idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION ); + vel.z += addVel.z; + goLower = true; + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Height + gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + if ( !goLower ) { + // make sure we don't fly too low + end = origin; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + end.z = enemy.lastKnownPosition.z + move.fly_offset; + } else if ( move.moveCommand == MOVE_TO_ENTITY ) { + end.z = goalPos.z; + } else { + end.z = goalPos.z;// + move.fly_offset; + } + +// RAVEN BEGIN +// ddynerman: multiple collision world + idVec3 cappedEnd = (end-origin); + if ( cappedEnd.LengthFast() > 1024.0f ) { + //don't do translation predictions over 1024 + cappedEnd.Normalize(); + cappedEnd *= 1024.0f; + } + cappedEnd += origin; + gameLocal.Translation( this, trace, origin, cappedEnd, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this ); +// RAVEN END + vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION ); + } +} + +/* +===================== +idAI::FlySeekGoal +===================== +*/ +void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) { + idVec3 seekVel; + + // seek the goal position + seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION ); + seekVel *= move.fly_seek_scale; + //seekVel.Normalize(); + //vel = seekVel*move.speed; + vel += seekVel; +} + +/* +===================== +idAI::AdjustFlySpeed +===================== +*/ +void idAI::AdjustFlySpeed( idVec3 &vel ) { + float goalSpeed; + + // Slow down movespeed when we close to goal (this is similar to how AnimMove ai will + // switch to walking when they are close, it allows for more fine control of movement) + if ( move.walkRange > 0.0f ) { + float distSqr; + distSqr = (physicsObj.GetOrigin ( ) - move.moveDest).LengthSqr ( ); + goalSpeed = move.speed * idMath::ClampFloat ( 0.1f, 1.0f, distSqr / Square ( move.walkRange ) ); + } else { + goalSpeed = move.speed; + } + + // apply dampening as long as we arent within the walk range +// if ( goalSpeed != move.speed) { + float speed; + + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( goalSpeed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if (goalSpeed && ( speed > goalSpeed ) ) { + speed = goalSpeed; + } + + vel *= speed; +// } else { +// vel.Normalize ( ); +// vel *= goalSpeed; +// } +} + +/* +===================== +idAI::FlyTurn +===================== +*/ +void idAI::FlyTurn( void ) { + if ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( focusType != AIFOCUS_NONE && move.fl.allowDirectional ) { + DirectionalTurnToward ( currentFocusPos ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + + Turn(); +} + +/* +===================== +idAI::FlyMove +===================== +*/ +void idAI::FlyMove( void ) { + idVec3 oldorigin; + idVec3 newDest; + + move.fl.blocked = false; + if ( ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) && ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), aiMoveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, move.fly_speed ); + } + + // Dont move when movement is disabled + if ( !CanMove() || legsAnim.Disabled ( ) ) { + // Still allow turning though + FlyTurn ( ); + + if ( (aifl.action || aifl.scripted ) && legsAnim.Disabled () && move.fl.allowAnimMove ) { + idMat3 oldaxis = viewAxis; + idVec3 delta; + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + physicsObj.UseFlyMove( false ); + if ( spawnArgs.GetBool( "alwaysBob" ) ) { + AddFlyBob( delta ); + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + + RunPhysics(); + } else if ( spawnArgs.GetBool( "alwaysBob" ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + AddFlyBob( vel ); + physicsObj.SetLinearVelocity( vel ); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + } + + UpdateAnimationControllers ( ); + return; + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + move.seekPos.x = newDest.x; + move.seekPos.y = newDest.y; + } + + if ( move.speed ) { + FlySeekGoal( vel, move.seekPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, move.seekPos ); + } + + AdjustFlySpeed( vel ); + + vel += move.addVelocity; + move.addVelocity.Zero(); + + physicsObj.SetLinearVelocity( vel ); + } + + // turn + FlyTurn(); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + move.fl.blocked = true; + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); + gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorBlue, org, move.seekPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition ( ), GetEyePosition ( ) + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +============ +idAI::UpdatePlayback +============ +*/ +void idAI::UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ) { + rvDeclPlaybackData pbd; + bool atDest; + + // New playback stuff + if( !mPlayback.IsActive() ) { + return; + } + + atDest = mPlayback.UpdateFrame( this, pbd ); + + goalPos = pbd.GetPosition(); + SetOrigin( goalPos ); + viewAxis = pbd.GetAngles().ToMat3(); + + // Keep the yaw updated + idVec3 local_dir; + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + OnUpdatePlayback ( pbd ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::PlaybackMove( void ){ + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + move.obstacle = NULL; + + goalPos = oldorigin; + + UpdatePlayback( goalPos, delta, oldorigin, oldaxis ); + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else { + move.fl.blocked = true; + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::StaticMove +===================== +*/ +void idAI::StaticMove( void ) { + idEntity* enemyEnt = enemy.ent; + + if ( aifl.dead ) { + return; + } + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemyEnt ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + TurnToward( move.moveDest ); + } + Turn(); + + physicsObj.ForceDeltaMove( true ); // disable gravity + RunPhysics(); + + move.fl.onGround = false; + + if ( DebugFilter(ai_debugMove) ) { // Static Move + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + } +} + +/* +===================== +idAI::SlideMove +===================== +*/ +void idAI::SlideMove( void ) { + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.seekPos = move.moveDest; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.seekPos = move.moveDest; + } else if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + TurnToward( newDest ); + move.seekPos = newDest; + } + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.seekPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + } else { + move.seekPos = move.moveDest; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + float z = vel.z; + idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION; + + // seek the goal position + goalDelta = move.seekPos - predictedPos; + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + vel += goalDelta * MS2SEC( gameLocal.msec ); + + // cap our speed + vel.Truncate( move.fly_speed ); + vel.z = z; + physicsObj.SetLinearVelocity( vel ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); + + if ( DebugFilter(ai_debugMove) ) { // Slide Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // SlideMove + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::AnimMove +===================== +*/ +void idAI::AnimMove( void ) { + + if ( ai_useRVMasterMove.GetBool ( ) ) { + RVMasterMove(); + return; + } + + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idReachability* goalReach; + // RAVEN END + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + //move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( move.moveCommand == MOVE_FACE_ENEMY && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } else { + move.moveStatus = MOVE_STATUS_MOVING; + + // Otherwise, Update The Seek Pos + if ( !aifl.simpleThink && GetMovePos( move.goalPos, &goalReach ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( move.goalPos, move.seekPos, goalReach ); + } else { + move.seekPos = move.goalPos; + } + DirectionalTurnToward ( move.seekPos ); + } + } + } + + Turn(); + + goalDelta = move.seekPos - oldorigin; + goalDist = goalDelta.LengthFast(); + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = move.goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( move.fl.allowAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + } else if ( move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + float speed = delta.LengthFast(); + delta = goalDelta; + delta.Normalize(); + delta *= speed; + } else { + delta.Zero(); + } + + if ( move.moveCommand > NUM_NONMOVING_COMMANDS ) { + //actually *trying* to move to a goal + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + + RunPhysics(); + + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + const idVec3& org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // AnimMove : GREEN / RED Bounds & Move Dest + gameRenderWorld->DebugLine( colorCyan, oldorigin, org, 5000 ); + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), org, gameLocal.msec ); + if (!ReachedPos( move.moveDest, move.moveCommand, move.range )) { + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugArrow( (team==0)?(colorGreen):(colorRed), org, move.moveDest, 4, gameLocal.msec ); + } + DrawRoute(); + } +} + +/* +===================== +idAI::CustomMove +===================== +*/ +void idAI::CustomMove( void ) { + // derived class must implement this +} + + + +/* +====================================================================================== + NEW MOVE CODE BEGIN +====================================================================================== +*/ + +struct rvObstacle; + +const float REACHED_RADIUS = 4.0f; +const float REACHED_RADIUS_SQUARE = REACHED_RADIUS*REACHED_RADIUS; + + + + + + +/* +===================== +LineIntersection2D +===================== +*/ +bool LineIntersection2D(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& contactPoint) { + + // Test If Parallel + //------------------ + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)<0.01f) { + return false; + } + + // Test CD Edge + //-------------- + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f) { + return false; + } + if (s>1.0f) { + return false; + } + + // Test AB Edge + //-------------- + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r<0.0f) { + return false; + } + if (r>1.0f) { + return false; + } + + contactPoint = A + r*(B-A); + return true; +} + + +/* +===================== +rvWindingBox + +Clockwise winding with a simple line intersection test + +[1]---->[2] + ^ | + | verts | + | V +[0]<----[3] + +===================== +*/ +struct rvWindingBox { + idVec3 verts[4]; + aasArea_t* areas[4]; + rvObstacle* obstacles[4]; // CDR_TODO: Should be a list of obstacles for each vertex + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + for (int i=0; i<4; i++) { + verts[i] = vec3_zero; + areas[i] = NULL; + obstacles[i] = NULL; + } + } + + /* + ===================== + FromBounds + ===================== + */ + void FromBounds(const idBounds& b) { + verts[0].x = b[0].x; + verts[0].y = b[0].y; + verts[0].z = b[0].z; + + verts[1].x = b[0].x; + verts[1].y = b[1].y; + verts[1].z = b[0].z; + + verts[2].x = b[1].x; + verts[2].y = b[1].y; + verts[2].z = b[0].z; + + verts[3].x = b[1].x; + verts[3].y = b[0].y; + verts[3].z = b[0].z; + } + + /* + ===================== + LineIntersection + ===================== + */ + bool LineIntersection(const idVec3& start, const idVec3& end, idVec3& contactPoint, int& v1, int& v2) const { + for (int i=0; i<4; i++) { + v1 = i; + v2 = (i<3)?(i+1):(0); + + if (start.IsLeftOf(verts[v1], verts[v2]) && LineIntersection2D(start, end, verts[v1], verts[v2], contactPoint)) { + return true; + } + } + return false; + } + + /* + ===================== + PointInside + ===================== + */ + bool PointInside(const idVec3& point) const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + if (point.IsLeftOf(vert1, vert2)) { + return false; + } + } + return true; + } + + /* + ===================== + DrawDebugGraphics + ===================== + */ + bool DrawDebugGraphics() const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + gameRenderWorld->DebugLine(colorYellow, vert1, vert2, gameLocal.msec); + if (!areas[i] || obstacles[i]) { + gameRenderWorld->DebugLine(colorRed, vert1, vert1+idVec3(0.0f,0.0f,16.0f), gameLocal.msec); + } else if (areas[i]) { + // gameRenderWorld->DebugLine(colorYellow, vert1, areas[i]->center, gameLocal.msec); + } + } + return true; + } +}; + + + + +/* +===================== +rvMarker + +A marker represents an obstacle within +an area. Any single obstacle can have +any number of markers in any number of +areas that it touches +===================== +*/ +struct rvMarker { + rvObstacle* obstacle; + aasArea_t* area; + rvMarker* prev; + rvMarker* next; +}; +rvPool markerPool; + +/* +===================== +rvObstacle +===================== +*/ +struct rvObstacle { + static idAASFile* searchFile; + static idBounds searchBounds; + + entityPointer_t entity; + idVec3 origin; + idVec3 originFuture; + int lastTimeMoved; + bool pendingUpdate; + + rvWindingBox windings[3]; + idList markers; + idList areas; + + /* + ============ + Initialize + ============ + */ + void Initialize(idEntity* ent) { + entity = ent; + origin = vec3_zero; + originFuture = vec3_zero; + lastTimeMoved = 0; + pendingUpdate = true; + + for (int i=0; i<3; i++) { + windings[i].Initialize(); + } + markers.Clear(); + areas.Clear(); + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + if ((gameLocal.time - lastTimeMoved) < 1000) { + gameRenderWorld->DebugArrow(colorBrown, origin, originFuture, 3, gameLocal.msec); + } + + windings[0].DrawDebugGraphics(); + } + + /* + ============ + RemoveMarkers + ============ + */ + void RemoveMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==this); + + if (marker->area->firstMarker==marker) { + marker->area->firstMarker = marker->next; + } + if (marker->prev) { + marker->prev->next = marker->next; + } + if (marker->next) { + marker->next->prev = marker->prev; + } + + marker->obstacle = NULL; + + markerPool.free(marker); + } + markers.Clear(); + } + /* + ============ + SearchAreas_r + ============ + */ + void SearchAreas_r( int nodeNum ) { + int side; + const aasNode_t *node; + + while( nodeNum != 0 ) { + + // Negative nodeNum signifies a Leaf Area + if ( nodeNum < 0 ) { + if ( searchFile->GetArea(-nodeNum).flags&AREA_REACHABLE_WALK ) { + areas.AddUnique( &searchFile->GetArea(-nodeNum) ); + } + break; + } + node = &searchFile->GetNode( nodeNum ); + side = searchBounds.PlaneSide( searchFile->GetPlane( node->planeNum ) ); + if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + SearchAreas_r( node->children[1] ); + nodeNum = node->children[0]; + } + } + } + + /* + ============ + PointInsideArea + ============ + */ + bool PointInsideArea( const idVec3& point, aasArea_t* area ) { + int i, faceNum; + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = searchFile->GetFaceIndex(area->firstFace + i); + + const aasFace_t& face = searchFile->GetFace(abs( faceNum )); + if (!(face.flags & FACE_FLOOR)) { + const idPlane& plane = searchFile->GetPlane(face.planeNum ^ INTSIGNBITSET( faceNum )); + if (plane.Side(point) == PLANESIDE_BACK) { + return false; + } + } + } + return true; + } + + /* + ============ + AddMarkers + ============ + */ + void AddMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==NULL); + + // Setup The New Marker + //---------------------- + marker->obstacle = this; + marker->area = areas[i]; + marker->next = NULL; + marker->prev = NULL; + + + // Fixup Any Existing Linked List (First Marker) + //----------------------------------------------- + if (marker->area->firstMarker) { + marker->area->firstMarker->prev = marker; + marker->next = marker->area->firstMarker; + } + + // Add This Marker At THe Area's Head + //------------------------------------ + marker->area->firstMarker = marker; + + markers.Append(marker); + } + } + + + + /* + ============ + Update + ============ + */ + bool Update() { + static rvMarker* marker; + static rvObstacle* obstacle; + static aasArea_t* area; + static idVec3 expand; + static float speed; + static float distance; + static idVec3 direction; + static int aasFileNum, t, v, a; + static idList touched; + static rvWindingBox* myWinding; + + + pendingUpdate = false; + idEntity* ent = entity.GetEntity(); + if (!ent || ent->health<=0 || ent->fl.hidden || !ent->GetPhysics()) { + RemoveMarkers(); + return false; // Means This Obstacle Structure Should Be Retired + } + + + // Only Update If We've Moved Far Enough + //--------------------------------------- + idPhysics* physics = ent->GetPhysics(); + if (origin.Dist2XY(physics->GetOrigin())<100.0f) { // CDR_TODO: Scale This By Distance To Player, Other Obstacles Near... + return true; + } + + // Get The Obstacle's Seek Direction & Speed + //------------------------------------------- + if (ent->IsType(idAI::GetClassType())) { + speed = (((idAI*)ent)->move.fl.done)?(0.0f):(physics->GetLinearVelocity().LengthFast()) * 2.5f; + direction = (((idAI*)ent)->move.seekPos - physics->GetOrigin()); + distance = direction.NormalizeFast(); + + // Cap The Projection To The Seek Position + if (speed > distance) { + speed = distance; + } + } else { + direction = physics->GetLinearVelocity(); + speed = direction.NormalizeFast() * 2.5f; + } + + origin = physics->GetOrigin(); + originFuture = origin + (direction*speed); // 2.5 seconds into the future predicion + lastTimeMoved = gameLocal.time; + + + // Remove Old Markers + //-------------------- + RemoveMarkers(); + + + // Get The Entity's Bounds And Areas That These Bounds Cover + //----------------------------------------------------------- + for (aasFileNum=0; aasFileNumGetFile(); + searchBounds = physics->GetAbsBounds(); + expand = searchFile->GetSettings().boundingBoxes[0].Size(); + expand[0] *= 0.5f; + expand[1] *= 0.5f; + expand[2] = 0; + expand[0] += REACHED_RADIUS; + expand[1] += REACHED_RADIUS; + + searchBounds.ExpandSelf(expand); + SearchAreas_r(1); + + // Setup The Winding From The Bounds + //----------------------------------- + myWinding->FromBounds(searchBounds); + + // Setup Each Vertex On The Winding + //---------------------------------- + for (v=0; v<4; v++) { + const idVec3& myVertex = myWinding->verts[v]; + + myWinding->areas[v] = NULL; + for (a=0; aareas[v] = area; + + + // Search This Area For All Obstacles That May Contain This Vertex + //----------------------------------------------------------------- + obstacle = NULL; + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle->windings[aasFileNum].PointInside(myVertex)) { + obstacle = marker->obstacle; + break; + } + } + + // Update The Obstacle On The Winding + //------------------------------------ + if (myWinding->obstacles[v] && myWinding->obstacles[v]!=obstacle) { + touched.AddUnique(myWinding->obstacles[v]); + } + if (obstacle) { + touched.AddUnique(obstacle); + } + + myWinding->obstacles[v] = obstacle; + break; + } + } + } + + // Touched Obstacles Need To Test Their Verts Against This Winding + //----------------------------------------------------------------- + for (t=0; tPointInside(obstacle->windings[aasFileNum].verts[v])) { + obstacle->windings[aasFileNum].obstacles[v] = this; + } else if (obstacle->windings[aasFileNum].obstacles[v]==this) { + obstacle->windings[aasFileNum].obstacles[v] = NULL; + } + } + } + + // AddMarkers In Each Found Area + //------------------------------- + AddMarkers(); + } + + return true; + } + + /* + ===================== + VertexValid + ===================== + */ + static bool VertexValid(const rvWindingBox& bounds, int v, const aasArea_t* inArea, const idEntity* ignore) { + return (bounds.areas[v] && (bounds.obstacles[v]==NULL || bounds.obstacles[v]->entity.GetEntity()==ignore)); + } + + /* + ============ + MovedRecently + ============ + */ + bool MovedRecently(int time=800) { + if ((gameLocal.time - lastTimeMoved) < time) { + return true; + } + // idEntity* ent = entity.GetEntity(); + // if (ent && ent->IsType(idAI::GetClassType())) { + // return !((idAI*)ent)->move.fl.done; + // } + return false; + } +}; +rvIndexPool obstaclePool; + +idAASFile* rvObstacle::searchFile; +idBounds rvObstacle::searchBounds; + + + + + + +/* +===================== +rvObstacleFinder +===================== +*/ +struct rvObstacleFinder { + idList obstaclesPendingUpdate; + int obstaclesUpdateTime; + + + struct traceResult_t { + rvObstacle* obstacle; + idVec3 endPoint; + float distance; + int v1; + int v2; + bool v1Valid; + bool v2Valid; + }; + traceResult_t contact; + + + + + /* + ============ + Initialize + ============ + */ + void Initialize() { + markerPool.clear(); + obstaclePool.clear(); + + obstaclesPendingUpdate.Clear(); + obstaclesUpdateTime = 0; + memset(&contact, 0, sizeof(contact)); + + // Clear All The Marker Pointers In Any Areas + //-------------------------------------------- + for (int i=0; iGetFile(); + for (int a=0; aGetNumAreas(); a++) { + file->GetArea(a).firstMarker = NULL; + } + } + } + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + static int nextDrawTime=0; + static rvObstacle* obstacle; + if (nextDrawTime>=gameLocal.time) { + return; + } + nextDrawTime = gameLocal.time; + + for (int i=0; iDrawDebugGraphics(); + } + } + } + + /* + ============ + UpdateObstacles + ============ + */ + void UpdateObstacles() { + static rvObstacle* obstacle; + static float entityNum; + + if (obstaclesUpdateTime < gameLocal.time && obstaclesPendingUpdate.Num()) { + obstaclesUpdateTime = gameLocal.time + 50; + + // CDR_TODO: Priority Queue Of Updates? Track Last Update Time Perhaps? + while (obstaclesPendingUpdate.Num()) { + obstacle = obstaclesPendingUpdate.StackTop(); + obstaclesPendingUpdate.StackPop(); + + if (!obstacle->Update()) { + obstaclePool.free(obstacle->entity.GetEntityNum()); + } + } + } + } + + /* + ============ + MarkEntityForUpdate + ============ + */ + void MarkEntityForUpdate(idEntity* ent) { + + // Ignore Non Physics Entities + //----------------------------- + if (!ent || !ent->GetPhysics()) { + return; + } + + // Ignore Obstacles That Are Already Pending + //-------------------------------------------- + if (obstaclePool.valid(ent->entityNumber) && obstaclePool[ent->entityNumber]->pendingUpdate) { + return; + } + + if (!obstaclePool.valid(ent->entityNumber)) { + + // If No More Obstacles Are Available, Ignore This One + //----------------------------------------------------- + if (obstaclePool.full()) { + return; + } + + obstaclePool.alloc(ent->entityNumber)->Initialize(ent); + } + + + obstaclePool[ent->entityNumber]->pendingUpdate = true; + obstaclesPendingUpdate.Append(obstaclePool[ent->entityNumber]); + } + + + /* + ============ + RecordContact + ============ + */ + void RecordContact(float maxDistance, const idVec3& start, rvObstacle* obstacle, const idVec3& point, int vert1=-1, int vert2=-1) { + static float distance; + distance = point.DistXY(start); + if ((maxDistance==0.0f || distance distance || !contact.obstacle)) { + contact.obstacle = obstacle; + contact.endPoint = point; + contact.distance = distance; + contact.v1 = vert1; + contact.v2 = vert2; + } + } + + /* + ============ + RayTrace + ============ + */ + bool RayTrace(float maxDistance, const aasArea_t* area, const idVec3& start, const idVec3& stop, int aasNum, const idEntity* ignore1, const idEntity* ignore2=NULL, const idEntity* ignore3=NULL) { + static rvMarker* marker; + static idVec3 p; + static int v1; + static int v2; + static rvObstacle* ignoreA; + static rvObstacle* ignoreB; + static rvObstacle* ignoreC; + static idEntity* ent; + static idVec3 startBack; + static idVec3 direction; + static bool pulledBack; + + ignoreA = (ignore1 && obstaclePool.valid(ignore1->entityNumber))?(obstaclePool[ignore1->entityNumber]):(NULL); + ignoreB = (ignore2 && obstaclePool.valid(ignore2->entityNumber))?(obstaclePool[ignore2->entityNumber]):(NULL); + ignoreC = (ignore3 && obstaclePool.valid(ignore3->entityNumber))?(obstaclePool[ignore3->entityNumber]):(NULL); + + contact.obstacle = NULL; + pulledBack = false; + + + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle==NULL || marker->obstacle==ignoreA || marker->obstacle==ignoreB || marker->obstacle==ignoreC) { + continue; + } + + // Handle Moving Obstacles Differently + //------------------------------------- + if (marker->obstacle->MovedRecently()) { + + // Ignore Moving Actors With Higher Entity Numbers + //------------------------------------------------- + ent = marker->obstacle->entity.GetEntity(); + if (ent && ignore1 && ent->entityNumber>ignore1->entityNumber && ent->IsType(idActor::GetClassType())) { + continue; + } + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Moving Obstacle's Predicted Path + //---------------------------------------------------------- + if (LineIntersection2D(startBack, stop, marker->obstacle->origin, marker->obstacle->originFuture, p)) { + RecordContact(maxDistance, startBack, marker->obstacle, p); + } + + + // Stationary Obstacles + //---------------------- + } else { + + // CDR_TODO: Special Line Intersection Test For Player Forward Aim And On Same Team + //---------------------------------------------------------------------------------- + + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Stationary Obstacle's Position + //-------------------------------------------------------- + if (marker->obstacle->windings[aasNum].LineIntersection(startBack, stop, p, v1, v2)) { + RecordContact(maxDistance, startBack, marker->obstacle, p, v1, v2); + } + } + } + + if (contact.obstacle) { + if (contact.obstacle->MovedRecently()) { + contact.v1Valid = true; + contact.v2Valid = true; + } else { + contact.v1Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v1, area, ignore1); + contact.v2Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v2, area, ignore1); + } + + return true; + } + return false; + } +}; +rvObstacleFinder obstacleFinder; + + + + + +/* +===================== +rvPathFinder +===================== +*/ +class rvPathFinder { +public: + /* + ===================== + visitNode + ===================== + */ + struct visitNode { + float costToGoal; + float travelCost; + bool closed; + int vertexNum; + idReachability* reach; + visitNode* from; + + float cost() { + return travelCost + costToGoal; + } + }; + + static int visitSort( const void *a, const void *b ) { + return (int)( (*((visitNode**)b))->cost() - (*((visitNode**)a))->cost() ); + } + + + + enum { + MAX_VISITED = 512, + MAX_OPEN = 255, + MAX_PENDING = 60, + }; + + idAAS* myAAS; + float myRadius; + idMoveState* myMove; + int myTeam; + const idEntity* myIgnoreEntity; + const idEntity* myIgnoreEntity2; + bool drawVisitTree; + + visitNode* next; + + visitNode* open[MAX_OPEN]; + int openCount; + bool openListUpdate; + + visitNode* pending[MAX_PENDING]; + visitNode* pendingBest; + int pendingCount; + + visitNode visited[MAX_VISITED]; + int visitedCount; + idHashIndex visitedIndexReach; + idHashIndex visitedIndexVert; + + + + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + next = NULL; + } + + + /* + ===================== + Close + ===================== + */ + void Close(visitNode* node) { + node->closed = true; + } + + /* + ===================== + DrawVisitTree + ===================== + */ + void DrawVisitTree() { + for (int i=0; imyPos); + const idVec3& stop = (GetSeekPosition(&visited[i])); + const idVec4& color = (visited[i].closed)?(colorOrange):(colorYellow); + const int duration = (visited[i].closed)?(3000):(500); + + gameRenderWorld->DebugArrow(color, start, stop, 3, duration); + } + } + + /* + ===================== + XYLineIntersection + ===================== + */ + bool XYLineIntersection(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& P) { + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)>0.01f) { + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f || s>1.0f) { + return false; + } + + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r>1.0f) { + P = B; + return false; + } + if (r<0.0f) { + P = A; + return false; + } + + P = A + r*(B-A); + return true; + } + + // Lines Are Parallel, No Intersection + return false; + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(idReachability* reach, int vertexNum) { + return ((vertexNum)?(myAAS->GetFile()->GetVertex(vertexNum)):(reach->start)); + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(visitNode* node) { + return GetSeekPosition(node->reach, node->vertexNum); + } + + /* + ===================== + Success + ===================== + */ + bool Success(visitNode* node) { + static idVec3 edgeA; + static idVec3 edgeB; + static idVec3 intersect; + static idVec3 direction; + static idVec3 smoothedPos; + static int at; + static int count; + static rvMarker* marker; + static bool isCorner; + + // Always Add The Goal Pos At The End Of The Path + //------------------------------------------------ + myMove->path[myMove->pathLen].reach = NULL; + myMove->path[myMove->pathLen].seekPos = myMove->goalPos; + myMove->pathLen ++; + + // Build The Path + //---------------- + while (node && myMove->pathLenpath[myMove->pathLen].reach = node->reach; + myMove->path[myMove->pathLen].seekPos = GetSeekPosition(node); + + myMove->pathLen ++; + node = node->from; + } + + + // Additional Path Point Modifications + //------------------------------------- + count = myMove->pathLen-1; + for (at=count; at>0; at--) { + pathSeek_t& pathPrev = myMove->path[at+1]; + pathSeek_t& pathAt = myMove->path[at]; + pathSeek_t& pathNext = myMove->path[at-1]; + + myAAS->GetEdge(pathAt.reach->edgeNum, edgeA, edgeB); + + // Smooth The Path One Pass + //-------------------------- + if (pathAt.reach->travelType==TFL_WALK) { + const idVec3& walkA = (at==count) ? (myMove->myPos) :(pathPrev.seekPos); + const idVec3& walkB = (at==0) ? (myMove->goalPos) :(pathNext.seekPos); + + isCorner = !XYLineIntersection(edgeA, edgeB, walkA, walkB, smoothedPos); + + + // If The Smoothed Position Is Not Blocked By An Obstacle + //-------------------------------------------------------- + aasArea_t* area = &myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum); + for (marker=area->firstMarker; marker; marker=marker->next) { + if (!marker->obstacle || marker->obstacle->entity.GetEntity()==myIgnoreEntity || marker->obstacle->entity.GetEntity()==myIgnoreEntity2) { + continue; + } + if (marker->obstacle->windings[0/*CDR_TODO: use aasNum*/].PointInside(smoothedPos)) { + break; + } + } + if (!marker) { + pathAt.seekPos = smoothedPos; + + // Push Away From The Corner A Bit + //--------------------------------- + if (isCorner) { + smoothedPos.ProjectToLineSeg(walkA, walkB); + direction = pathAt.seekPos - smoothedPos; + direction.NormalizeFast(); + pathAt.seekPos += direction * (REACHED_RADIUS+1.0f); + } + } + } + } + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return true; + } + + /* + ===================== + Failure + ===================== + */ + bool Failure() { + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return false; + } + + + /* + ===================== + ErrorCondition + ===================== + */ + bool ErrorCondition() { + assert(0); + // Stats? + return false; + } + + /* + ===================== + SelectNextVisitedNode + ===================== + */ + visitNode* SelectNextVisitedNode() { + if (pendingBest && (!openCount || open[openCount-1]->cost() > pendingBest->cost())) { + next = pendingBest; + pendingBest = NULL; + openListUpdate = true; + return next; + } + + if (openCount) { + openCount--; + return open[openCount]; + } + return NULL; + } + + /* + ===================== + Open + ===================== + */ + void Open(visitNode* node) { + node->closed = false; + + // If This Node Is Cheaper Than The Existing Best Open Node, Add It To The End Of The Open List + //---------------------------------------------------------------------------------------------- + if (openCountcost() < open[openCount-1]->cost())) { + open[openCount] = node; + openCount++; + return; + } + } + + // Otherwise, Add It To The Pending List + //--------------------------------------- + if (pendingCountcost() < pendingBest->cost())) { + pendingBest = node; + } + } + } + + /* + ===================== + UpdateOpenList + ===================== + */ + void UpdateOpenList() { + + // List Is Updated Now, So Remove The Flag + //----------------------------------------- + openListUpdate = false; + + // Add All Pending Nodes To The Open List + //---------------------------------------- + for (int i=0; iclosed) { + open[openCount] = pending[i]; + openCount ++; + } + } + pendingCount = 0; + pendingBest = NULL; + + + // Sort The Open List + //-------------------- + qsort( (void*)open, (size_t)openCount, (size_t)sizeof(visitNode*), visitSort ); + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(idReachability* reach) { + for (int i=visitedIndexReach.First(abs(reach->edgeNum)); i>=0; i=visitedIndexReach.Next(i)) { + if (visited[i].reach==reach) { + return &visited[i]; + } + } + return NULL; + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(int vertexNum) { + for (int i=visitedIndexVert.First(vertexNum); i>=0; i=visitedIndexVert.Next(i)) { + if (visited[i].vertexNum==vertexNum) { + return &visited[i]; + } + } + return NULL; + } + + + /* + ===================== + TravelCost + ===================== + */ + float TravelCost(idReachability* reach, int vertexNum, visitNode* from) { + static float distance; + + const idVec3& start = (from)?(GetSeekPosition(from)):(myMove->myPos); + const idVec3& stop = GetSeekPosition(reach, vertexNum); + + + // Test For Any Obstacles In The Way + //----------------------------------- + const aasArea_t* area = &myAAS->GetFile()->GetArea((from)?(from->reach->toAreaNum):(myMove->myArea)); + if (obstacleFinder.RayTrace(0.0f, area, start, stop, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + + // If It Is Not Possible To Steer Around, Then This Edge Is Completely Invalid + //----------------------------------------------------------------------------- + if (!obstacleFinder.contact.v1Valid && !obstacleFinder.contact.v2Valid) { + return 0.0f; + } + + // Completely Disable Any Points Completely Covered By An Obstacle + //----------------------------------------------------------------- + if (obstacleFinder.contact.obstacle->windings[0/*CDR_TODO: Get myAASNum*/].PointInside(stop)) { + return 0.0f; + } + distance += 128.0f; + } + + + // Compute Standard Distance + //--------------------------- + distance = start.Dist(stop); + if (from) { + distance += from->travelCost + from->reach->travelTime; + } + + return distance; + } + + + /* + ===================== + Visit + ===================== + */ + void Visit(idReachability* reach, int vertexNum, visitNode* from) { + static visitNode* visit; + static float travelCost; + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Compute Travel Cost, And Test To See If This Edge Is Blocked + //-------------------------------------------------------------- + travelCost = TravelCost(reach, vertexNum, from); + if (travelCost==0.0f) { + return; + } + + // If The Visited Version Is Already Less Costly, Then Ignore This Reach + //----------------------------------------------------------------------- + visit = (vertexNum)?(WasVisited(vertexNum)):(WasVisited(reach)); + if (visit && visit->travelCost<=travelCost) { + return; + } + + + + // Reopen Any Nodes That Were Closed + //----------------------------------- + if (visit && visit->closed) { + Open(visit); + + // Otherwise, If The Node Is Already In The Open List, Just Change The Cost And Mark The List For Resorting + //---------------------------------------------------------------------------------------------------------- + } else if (visit) { + visit->from = from; + visit->travelCost = travelCost; + openListUpdate = true; + + // Must Never Have Visited This Node Before, So Make A Whole New One + //------------------------------------------------------------------- + } else { + + const idVec3& pos = GetSeekPosition(reach, vertexNum); + + // Constant Data (Will Never Change) + //----------------------------------- + visit = &visited[visitedCount]; + visit->reach = reach; + visit->vertexNum = vertexNum; + visit->costToGoal = pos.Dist(myMove->goalPos); + + // Temporary Data + //---------------- + visit->from = from; + visit->travelCost = travelCost; + visit->closed = false; + + // Add It To The Hash Table To Be Found Later + //-------------------------------------------- + if (!vertexNum) { + visitedIndexReach.Add(abs(reach->edgeNum), visitedCount); + } else { + visitedIndexVert.Add(vertexNum, visitedCount); + } + visitedCount++; + + // Mark It As Open + //----------------- + Open(visit); + } + } + + /* + ===================== + VisitReach + + This function first visits the center of the reachability, and then if + the edge is long enough and close enough to the start or end of the + path, it visits the verts as well + ===================== + */ + void VisitReach(idReachability *reach, visitNode* from) { + static int verts[2]; + static int vertexNum; + static idVec3 start; + static idVec3 stop; + + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Ignore Any Reach To The Area We Came From + //------------------------------------------- + if (from && from->reach->fromAreaNum==reach->toAreaNum) { + return; + } + + // Ignore Any Reach That Does Not Match Our Travel Flags + //------------------------------------------------------- + if (reach->travelType&TFL_INVALID || !(reach->travelType&myMove->travelFlags)) { + return; + } + + // Visit The Center Of The Reach + //------------------------------- + Visit(reach, 0, from); + + + // If Running Low On Visit Space, Stop Adding Verts + //-------------------------------------------------- + if (visitedCount>=(int)((float)MAX_VISITED * 0.85f)) { + return; + } + + // If Edge Is Far From Start and Goal, Don't Add Verts + //----------------------------------------------------- + if (!reach->fromAreaNum!=myMove->myArea && + !reach->toAreaNum!=myMove->myArea && + !reach->fromAreaNum!=myMove->goalArea && + !reach->toAreaNum!=myMove->goalArea && + reach->start.Dist2XY(myMove->myPos)>22500.0f/*(250*250)*/ && reach->start.Dist2XY(myMove->goalPos)>22500.0f/*(250*250)*/) { + return; + } + + // If This Edge Is Small Enough, Just Skip The Verts + //--------------------------------------------------- + myAAS->GetEdge(reach->edgeNum, start, stop); + if (start.Dist2XY(stop)<6400.0f/*(80*80)*/) { + return; + } + + // Ok, So Visit The Verts Too + //---------------------------- + myAAS->GetEdgeVertexNumbers(reach->edgeNum, verts); + for (int i=0; i<2 && visitedCountGetFile()->GetArea(areaNum); + + // If Visiting From Another Node, Close That Node Now + //---------------------------------------------------- + if (from) { + + // If Already Closed + //------------------- + if (from->closed) { + if (openListUpdate) { + UpdateOpenList(); // this is kind of hacky... + } + + // Don't Bother To Look At These Reaches + //--------------------------------------- + return; + + // Otherwise, Close It + //--------------------- + } else { + Close(from); + } + } + + // Ok, Iterate All Reachabilities Within This Area + //------------------------------------------------- + for (reach=area.reach; reach && visitedCountnext) { + VisitReach(reach, from); + } + + // Finally, If Necessary, Update The Open List Now + //------------------------------------------------- + if (openListUpdate) { + UpdateOpenList(); + } + } + + + /* + ===================== + FindPath + ===================== + */ + bool FindPath(idAAS* aas, idMoveState& move, float radius, bool inDebugMode, idEntity* ignoreEntity, idEntity* ignoreEntity2) { + myAAS = aas; + myMove = &move; + myRadius = radius; + drawVisitTree = inDebugMode; + myIgnoreEntity = ignoreEntity; + myIgnoreEntity2 = ignoreEntity2; + + myMove->pathArea = myMove->goalArea; + myMove->pathTime = gameLocal.GetTime(); + myMove->pathLen = 0; + + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + + + + if (!myMove->myArea) { + return ErrorCondition(); + } + const aasArea_t* myArea = &myAAS->GetFile()->GetArea(myMove->myArea); + const aasArea_t* goalArea = &myAAS->GetFile()->GetArea(myMove->goalArea); + + + // Special Case For Starting In The Goal Area + //-------------------------------------------- + if (myMove->myArea==myMove->goalArea) { + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, myArea, myMove->myPos, myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(NULL); + } + + // If There Is An Obstacle But We Think We Can Steer Around It, Then We've Still Succeeded + //----------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(NULL); + } + } + + + + // Start With My Area + //-------------------- + VisitArea(myMove->myArea); + + + // While Reachabilities Are Still Pending + //---------------------------------------- + while (openCount || pendingCount) { + + // Select Next Visited Node + //-------------------------- + next = SelectNextVisitedNode(); + if (!next) { + return ErrorCondition(); + } + + // If This Node Reaches Our Target Destination, We've Succeeded + //-------------------------------------------------------------- + if (next->reach->toAreaNum==myMove->goalArea) { + next->closed = true; + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, goalArea, GetSeekPosition(next), myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(next); + } + + // Or If There Is An Obstacle, But One Of The Verts Is Valid, Then This Is Still A Safe Course + //--------------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(next); + } + + // Visit All Reaches In The Next Area + //------------------------------------- + } else { + VisitArea(next->reach->toAreaNum, next); + } + } + + return Failure(); + } +}; + +rvPathFinder pathFinder; + + + +void AI_EntityMoved(idEntity* ent) { + obstacleFinder.MarkEntityForUpdate(ent); +} +void AI_MoveInitialize() { + pathFinder.Initialize(); + obstacleFinder.Initialize(); +} + + + + + + + +/* +===================== +idAI::NewCombinedMove +===================== +*/ +void idAI::RVMasterMove( void ) { + static idVec3 mySeekDelta; + static idVec3 mySeekDirection; + static float mySeekDistance; + + static float moveDistance; + static idVec3 moveDelta; + static idEntity* moveBlockEnt; + static monsterMoveResult_t moveResult; + + + //==================================================================== + // UPDATE DATA + // A simple first step for movement is to get data from the command + // and goal arguments and compute changes if necessary. + //==================================================================== + bool seekMove = CanMove() && move.moveCommand>=NUM_NONMOVING_COMMANDS; + bool seekTurn = CanTurn() && move.moveCommand> MOVE_NONE; + + idMat3 myAxis = viewAxis; + const idVec3& myPos = physicsObj.GetOrigin(); + const idBounds& myBounds = physicsObj.GetBounds(); + float myRadius = myBounds.Size().x / 2.0f; + idVec3 myPosOld = move.myPos; // only used for debug graphics + bool myPosMoved = false; // only used for debug graphics + + const idEntity* goalEntity = move.goalEntity.GetEntity(); + const idVec3& goalPos = (goalEntity)?(LastKnownPosition(goalEntity)):(move.moveDest); + + + + + // Update My Position And Area + //----------------------------- + if (move.myArea==0 || move.myPos.Dist2XY(myPos)>20.0f) { + move.myPos = myPos; + move.myArea = PointReachableAreaNum(move.myPos); + myPosMoved = true; + } + aasArea_t* myArea = &aas->GetFile()->GetArea(move.myArea); + + // Update Goal Position And Area + //------------------------------- + if ((seekMove || seekTurn) && move.goalPos.Dist2XY(goalPos)>20.0f) { + move.goalPos = goalPos; + move.goalArea = PointReachableAreaNum(move.goalPos); + } + + // If Reached The Goal Position, Then Stop Moving + //------------------------------------------------ + if (seekMove && ReachedPos( move.goalPos, move.moveCommand, move.range )) { + StopMove( MOVE_STATUS_DONE ); + seekMove = false; + move.pathLen = 0; + } + + // Update The Obstacle Markers + //----------------------------- + obstacleFinder.UpdateObstacles(); + + + + //==================================================================== + // PATH FINDING + // If the goal area is not the same as myArea, then we need to run + // a pathfinding search to get to the goal area. This operation + // will alter the seek position + //==================================================================== + if (seekMove) { + + // If No Path Exists, Find One + //----------------------------- + if (move.pathArea!=move.goalArea || (!move.pathLen && move.pathTime < (gameLocal.time-10000))) { + pathFinder.FindPath(aas, move, myRadius, DebugFilter(ai_debugMove), this, move.goalEntity.GetEntity()); + } + + + // Set The SeekPos + //----------------- + if (move.pathLen) { + move.seekPos = move.path[move.pathLen-1].seekPos; + + // If We've Reached The Next Path Pos, Pop It Off The List And Continue + //---------------------------------------------------------------------- + if (move.seekPos.Dist2XY(move.myPos)entity; + const rvWindingBox& bounds = tr.obstacle->windings[0/*CDR_TODO: Get actual aasNumber*/]; + + // Is The Obstacle Standing On Seek Position + //------------------------------------------- + if (bounds.PointInside(move.seekPos)) { + if (move.myArea!=move.goalArea) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1150; + move.pathArea = 0; // force a refind path next update + } else { + + // Otherwise, Stop And Wait For It To Move + //----------------------------------------- + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1050; + // CDR_TODO: Issue MoveDestInvalid() Callback Here + } + } + + // Is The Obstacle About To Cross My Path? + //----------------------------------------- + else if (tr.v1==-1) { // CDR_TODO: -1 is an obtouse way to detect this + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1500; + } + + // Ok, So Let's Try To Steer Around The Obstacle + //----------------------------------------------- + else { + + // If Neither Vertex Is Valid, Need To Refind Path + //------------------------------------------------- + if (!tr.v1Valid && !tr.v2Valid) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 3250; + move.pathArea = 0; // force a refind path next update + + } else { + + int vertex; + + // Otherwise, Choose The Best Valid Vertex + //----------------------------------------- + if (!tr.v2Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v1; + } else if (!tr.v1Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v2; + } else { + // CDRTODO: Record clockwise / counter clockwise and only test this once. + vertex = (tr.obstacle->origin.IsLeftOf(move.myPos, move.seekPos))?(tr.v2):(tr.v1); + } + + + // Get Close The Contact Point if The Choosen Vertex Is Not In This Area, Before Going Toward The Vertex + //------------------------------------------------------------------------------------------------------- + if (bounds.areas[vertex]!=myArea && move.myPos.Dist2XY(tr.endPoint)>400.0f /*20*20*/) { + move.seekPos = tr.endPoint; + } else { + move.seekPos = bounds.verts[vertex]; + } + + + // And Recompute The Seek Vectors + //-------------------------------- + mySeekDelta = move.seekPos - move.myPos; + mySeekDirection = mySeekDelta; + mySeekDistance = mySeekDirection.NormalizeFast(); + } + } + } + } + + + + + + //==================================================================== + // TURNING + // Having finialized our seek position, it is now time to turn + // toward it. + //==================================================================== + if (seekTurn) { + DirectionalTurnToward(move.seekPos); + } + Turn(); + + + + //==================================================================== + // VELOCITY + // Now we need to get the instantanious velocity vector, which will + // usually come right from the animation + //==================================================================== + if (move.moveType == MOVETYPE_ANIM) { + if ( move.fl.allowAnimMove || move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( myAxis, viewAxis, moveDelta ); + } else { + moveDelta.Zero(); + } + } else { + // CDR_TODO: Other movetypes here + } + + // If Doing Seek Move, Cap The Delta To Avoid Overshooting The Seek Position + //--------------------------------------------------------------------------- + if (seekMove && moveDelta!=vec3_zero) { + moveDistance = moveDelta.LengthFast(); + + if (mySeekDistance<0.5f) { + moveDelta = vec3_zero; + } else if (mySeekDistanceIsType( idMoveable::GetClassType() ) && moveBlockEnt->GetPhysics()->IsPushable()) { + KickObstacles( viewAxis[ 0 ], move.kickForce, moveBlockEnt ); + } + + // Touch Triggers + //---------------- + if (moveDelta!=vec3_zero ) { + if (moveResult!=MM_BLOCKED) { + TouchTriggers(); + } + } + + + + //==================================================================== + // DEBUG GRAPHICS + //==================================================================== + idVec3 origin = physicsObj.GetOrigin(); + if (DebugFilter(ai_debugMove)) { + static const idVec3 upPole(0.0f, 0.0f, 60.0f); + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + + gameRenderWorld->DebugBounds(colorMagenta, physicsObj.GetBounds(), origin, gameLocal.msec); // Bounds: MAGENTA + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + gameRenderWorld->DebugLine(colorPurple, move.goalPos, move.goalPos + upPole, gameLocal.msec); // Goal: PURPLE + + if (myPosMoved) { + gameRenderWorld->DebugLine(colorCyan, myPosOld, move.myPos, 800); // Trail: CYAN + } + + if (move.pathLen) { + for (int i=move.pathLen-1; i>=0; i--) { + gameRenderWorld->DebugLine(colorBlue, origin, move.path[i].seekPos, gameLocal.msec); // FoundPath: BLUE + origin = move.path[i].seekPos; + } + } + // PathVisited: ORANGE + // PathOpened: YELLOW + } + + if (DebugFilter(ai_showObstacleAvoidance)) { + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + const idVec3& obstaclePos = (move.obstacle.GetEntity())?(move.obstacle->GetPhysics()->GetOrigin()):(move.seekPos); + + if (!DebugFilter(ai_debugMove)) { + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + } + + if (move.blockTime>gameLocal.time) { + gameRenderWorld->DebugArrow(colorRed, move.myPos, obstaclePos, 3, gameLocal.msec); // Blocked Obstacle: RED + } else if (move.fl.obstacleInPath) { + gameRenderWorld->DebugArrow(colorWhite, move.myPos, obstaclePos, 3, gameLocal.msec); // Walk Obstacle: WHITE + } + // ObstacleBox: YELLOW + // VertexInvalid: RED + // FuturePosition: BROWN + obstacleFinder.DrawDebugGraphics(); + } +} + +/* +===================== +idAI::Move +===================== +*/ +void idAI::Move ( void ) { + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Start ( ); + } + + switch( move.moveType ) { + case MOVETYPE_DEAD: + DeadMove(); + break; + case MOVETYPE_FLY : + FlyMove(); + break; + case MOVETYPE_STATIC : + StaticMove(); + break; + case MOVETYPE_ANIM : + AnimMove(); + break; + case MOVETYPE_SLIDE : + SlideMove(); + break; + case MOVETYPE_PLAYBACK: + PlaybackMove(); + break; + case MOVETYPE_CUSTOM: + CustomMove(); + break; + } + + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Stop ( ); + } +} + diff --git a/source/game/ai/AI_Move.h b/source/game/ai/AI_Move.h new file mode 100644 index 0000000..6cec8a1 --- /dev/null +++ b/source/game/ai/AI_Move.h @@ -0,0 +1,295 @@ +/* +=============================================================================== + +AI_Move.h + +This file has all movement related typedefs, enums, flags, and structures. It +and its sister CPP file were split from AI.h and AI.cpp in order to prevent +merge conflicts and to make further changes to the system possible. + +=============================================================================== +*/ + +#ifndef __AI_MOVE_H__ +#define __AI_MOVE_H__ + + +typedef idEntityPtr entityPointer_t; + +#define MAX_PATH_LEN 48 + +/* +===================== +aiMoveDir_t - used by directional movement code +===================== +*/ +typedef enum { + MOVEDIR_FORWARD, + MOVEDIR_BACKWARD, + MOVEDIR_LEFT, + MOVEDIR_RIGHT, + MOVEDIR_MAX +} aiMoveDir_t; +extern const char* aiMoveDirectionString [ MOVEDIR_MAX ]; + +/* +===================== +moveType_t - determines which movement function to call +===================== +*/ +typedef enum { + MOVETYPE_DEAD, + MOVETYPE_ANIM, + MOVETYPE_SLIDE, + MOVETYPE_FLY, + MOVETYPE_STATIC, + MOVETYPE_PLAYBACK, + //twhitaker: added custom move type + MOVETYPE_CUSTOM, + NUM_MOVETYPES +} moveType_t; + +/* +===================== +aiMoveCommand_t - tells the AI how to get there +===================== +*/ +typedef enum { + MOVE_NONE, + MOVE_FACE_ENEMY, + MOVE_FACE_ENTITY, + + // commands < NUM_NONMOVING_COMMANDS don't cause a change in position + NUM_NONMOVING_COMMANDS, + + MOVE_TO_ENEMY = NUM_NONMOVING_COMMANDS, + MOVE_TO_ENTITY, + MOVE_TO_ATTACK, + MOVE_TO_HELPER, + MOVE_TO_TETHER, + MOVE_TO_COVER, + MOVE_TO_HIDE, + MOVE_TO_POSITION, + MOVE_TO_POSITION_DIRECT, + MOVE_OUT_OF_RANGE, + MOVE_SLIDE_TO_POSITION, + MOVE_WANDER, + MOVE_RV_PLAYBACK, + NUM_MOVE_COMMANDS + +} aiMoveCommand_t; + +/* +===================== +moveStatus_t - status results from move commands + +make sure to change script/doom_defs.script if you add any, or change their order +===================== +*/ +typedef enum { + MOVE_STATUS_DONE, + MOVE_STATUS_MOVING, + MOVE_STATUS_WAITING, + MOVE_STATUS_DEST_NOT_FOUND, + MOVE_STATUS_DEST_UNREACHABLE, + MOVE_STATUS_BLOCKED_BY_WALL, + MOVE_STATUS_BLOCKED_BY_OBJECT, + MOVE_STATUS_BLOCKED_BY_ENEMY, + MOVE_STATUS_BLOCKED_BY_MONSTER, + MOVE_STATUS_BLOCKED_BY_PLAYER, + MOVE_STATUS_DISABLED, + NUM_MOVE_STATUS, +} moveStatus_t; +extern const char* aiMoveStatusString[ NUM_MOVE_STATUS ]; + +/* +===================== +stopEvent_t - used by path prediction +===================== +*/ +typedef enum { + SE_BLOCKED = BIT(0), + SE_ENTER_LEDGE_AREA = BIT(1), + SE_ENTER_OBSTACLE = BIT(2), + SE_FALL = BIT(3), + SE_LAND = BIT(4) +} stopEvent_t; + +/* +===================== +obstaclePath_s +===================== +*/ +typedef struct obstaclePath_s { + idVec3 seekPos; // seek position avoiding obstacles + idEntity * firstObstacle; // if != NULL the first obstacle along the path + idVec3 startPosOutsideObstacles; // start position outside obstacles + idEntity * startPosObstacle; // if != NULL the obstacle containing the start position + idVec3 seekPosOutsideObstacles; // seek position outside obstacles + idEntity * seekPosObstacle; // if != NULL the obstacle containing the seek position + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idList allObstacles; + // RAVEN END +} obstaclePath_t; + +/* +===================== +predictedPath_s +===================== +*/ +typedef struct predictedPath_s { + idVec3 endPos; // final position + idVec3 endVelocity; // velocity at end position + idVec3 endNormal; // normal of blocking surface + int endTime; // time predicted + int endEvent; // event that stopped the prediction + const idEntity * blockingEntity; // entity that blocks the movement +} predictedPath_t; + + +/* +===================== +pathSeek_s +===================== +*/ +typedef struct pathSeek_s { + idReachability* reach; + idVec3 seekPos; +} pathSeek_t; + + + +/* +===================== +idMoveState +===================== +*/ +class idMoveState { +public: + idMoveState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Spawn( idDict &spawnArgs ); + + + struct movementFlags_s { + + // current state + bool done :1; + bool moving :1; + bool crouching :1; // is currently crouching? + bool running :1; // is currently running? + bool blocked :1; + bool obstacleInPath :1; + bool goalUnreachable :1; + bool onGround :1; + bool flyTurning :1; // lean into turns when flying + + // ideal state + bool idealRunning :1; // what we want running to be + + + // various enable / disable flags + bool disabled :1; // is movement disabled? (this includes turning) + bool ignoreObstacles :1; // don't check for obsticles in path + + bool allowDirectional :1; // allows directional movement (ie. running sideways, backwards) + bool allowAnimMove :1; // allows any animation movement + bool allowPrevAnimMove :1; // allows slide move if current animmove has no motion extraction on it (smooth transitions) + bool allowHiddenMove :1; // allows character to still move around while hidden + bool allowPushMovables :1; // allows the articulated figure to push moveable objects + bool allowSlideToGoal :1; // allows the AI to excactly slide to the goal position if close enough + + bool noRun :1; // force the actor to walk + bool noWalk :1; // force the actor to run + bool noTurn :1; // can the ai turn? + bool noGravity :1; // dont use gravity + bool noRangedInterrupt :1; // dont stop halfway to attack position just because you have a clear shot + } fl; + + + moveType_t moveType; + aiMoveCommand_t moveCommand; + moveStatus_t moveStatus; + idVec3 moveDest; + idVec3 moveDir; // used for wandering and slide moves + + int toAreaNum; + int startTime; + int duration; + float speed; // only used by flying creatures + float range; + float wanderYaw; + int nextWanderTime; + int blockTime; + idEntityPtr obstacle; + idVec3 lastMoveOrigin; + int lastMoveTime; + int anim; + + int travelFlags; + + float kickForce; + float blockedRadius; + int blockedMoveTime; + int blockedAttackTime; + + // turning + float ideal_yaw; + float current_yaw; + float turnRate; + float turnVel; + float anim_turn_yaw; + float anim_turn_amount; + float anim_turn_angles; + + // flying + jointHandle_t flyTiltJoint; + float fly_speed; + float fly_bob_strength; + float fly_bob_vert; + float fly_bob_horz; + int fly_offset; // prefered offset from player's view + float fly_seek_scale; + float fly_roll_scale; + float fly_roll_max; + float fly_roll; + float fly_pitch_scale; + float fly_pitch_max; + float fly_pitch; + + aiMoveDir_t currentDirection; // Direction currently moving in + aiMoveDir_t idealDirection; // Direction we want to be moving in + float walkRange; // Distance to target before starting to walk + float walkTurn; // Turn delta threshold for walking when turning + idVec2 followRange; // Min and max range for AI following their leader + idVec2 searchRange; // Min and max range to use when searching for a new place to move to + float attackPositionRange; // Override for how close you have to get to your attackPosition before stopping + float turnDelta; // Amount to turn when turning + + idVec3 goalPos; + int goalArea; + entityPointer_t goalEntity; + idVec3 goalEntityOrigin; // move to entity uses this to avoid checking the floor position every frame + + idVec3 myPos; + int myArea; + + idVec3 seekPos; + + pathSeek_t path[MAX_PATH_LEN]; + int pathLen; + int pathArea; + int pathTime; + + idVec3 addVelocity; +}; + +// cdr: Obstacle Avoidance +void AI_EntityMoved(idEntity* ent); +void AI_MoveInitialize(); + + +#endif /* !__AI_MOVE_H__ */ diff --git a/source/game/ai/AI_States.cpp b/source/game/ai/AI_States.cpp new file mode 100644 index 0000000..7bb6a20 --- /dev/null +++ b/source/game/ai/AI_States.cpp @@ -0,0 +1,2213 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +static const int TACTICALUPDATE_MELEEDELAY = 500; // Rate to update tactical state when rushing +static const int TACTICALUPDATE_RANGEDDELAY = 2000; // Rate to update tactical state when in ranged combat +static const int TACTICALUPDATE_COVERDELAY = 5000; // Rate to update tactical state when in cover +static const int TACTICALUPDATE_HIDEDELAY = 5000; // Rate to update tactical state when hiding +static const int TACTICALUPDATE_FOLLOWDELAY = 500; // Rate to update tactical state when following +static const int TACTICALUPDATE_MOVETETHERDELAY = 1000; // Rate to update tactical state when moving into tether range +static const int TACTICALUPDATE_PASSIVEDELAY = 500; // Rate to update tactical state when in passive mode +static const int TACTICALUPDATE_TURRETDELAY = 250; // Rate to update tactical state when in turret mode + +static const int RANGED_ENEMYDELAY = 2000; // Time to wait to move after losing sight of an enemy +static const int COVER_ENEMYDELAY = 5000; // Stay behind cover for 5 seconds after loosing sight of an enemy + +static const float COVER_TRIGGERRADIUS = 64.0f; + +CLASS_STATES_DECLARATION ( idAI ) + // Wait States + STATE ( "Wait_Activated", idAI::State_Wait_Activated ) + STATE ( "Wait_ScriptedDone", idAI::State_Wait_ScriptedDone ) + STATE ( "Wait_Action", idAI::State_Wait_Action ) + STATE ( "Wait_ActionNoPain", idAI::State_Wait_ActionNoPain ) + + // Global States + STATE ( "State_WakeUp", idAI::State_WakeUp ) + STATE ( "State_TriggerAnim", idAI::State_TriggerAnim ) + + // Passive states + STATE ( "State_Passive", idAI::State_Passive ) + + // Combat states + STATE ( "State_Combat", idAI::State_Combat ) + STATE ( "State_CombatCover", idAI::State_CombatCover ) + STATE ( "State_CombatMelee", idAI::State_CombatMelee ) + STATE ( "State_CombatRanged", idAI::State_CombatRanged ) + STATE ( "State_CombatTurret", idAI::State_CombatTurret ) + STATE ( "State_CombatHide", idAI::State_CombatHide ) + + STATE ( "State_Wander", idAI::State_Wander ) + STATE ( "State_MoveTether", idAI::State_MoveTether ) + STATE ( "State_MoveFollow", idAI::State_MoveFollow ) + STATE ( "State_MovePlayerPush", idAI::State_MovePlayerPush ) + STATE ( "State_Killed", idAI::State_Killed ) + STATE ( "State_Dead", idAI::State_Dead ) + STATE ( "State_LightningDeath", idAI::State_LightningDeath ) + STATE ( "State_Burn", idAI::State_Burn ) + STATE ( "State_Remove", idAI::State_Remove ) + STATE ( "State_ScriptedMove", idAI::State_ScriptedMove ) + STATE ( "State_ScriptedFace", idAI::State_ScriptedFace ) + STATE ( "State_ScriptedStop", idAI::State_ScriptedStop ) + STATE ( "State_ScriptedPlaybackMove", idAI::State_ScriptedPlaybackMove ) + STATE ( "State_ScriptedPlaybackAim", idAI::State_ScriptedPlaybackAim ) + STATE ( "State_ScriptedJumpDown", idAI::State_ScriptedJumpDown ) + + // Torso States + STATE ( "Torso_Idle", idAI::State_Torso_Idle ) + STATE ( "Torso_Sight", idAI::State_Torso_Sight ) + STATE ( "Torso_CustomCycle", idAI::State_Torso_CustomCycle ) + STATE ( "Torso_Action", idAI::State_Torso_Action ) + STATE ( "Torso_FinishAction", idAI::State_Torso_FinishAction ) + STATE ( "Torso_Pain", idAI::State_Torso_Pain ) + STATE ( "Torso_ScriptedAnim", idAI::State_Torso_ScriptedAnim ) + STATE ( "Torso_PassiveIdle", idAI::State_Torso_PassiveIdle ) + STATE ( "Torso_PassiveFidget", idAI::State_Torso_PassiveFidget ) + + // Leg States + STATE ( "Legs_Idle", idAI::State_Legs_Idle ) + STATE ( "Legs_Move", idAI::State_Legs_Move ) + STATE ( "Legs_MoveThink", idAI::State_Legs_MoveThink ) + STATE ( "Legs_TurnLeft", idAI::State_Legs_TurnLeft ) + STATE ( "Legs_TurnRight", idAI::State_Legs_TurnRight ) + STATE ( "Legs_ChangeDirection", idAI::State_Legs_ChangeDirection ) + + // Head States + STATE ( "Head_Idle", idAI::State_Head_Idle ) + +END_CLASS_STATES + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +/* +================ +idAI::State_TriggerAnim +================ +*/ +stateResult_t idAI::State_TriggerAnim ( const stateParms_t& parms ) { + const char* triggerAnim; + + // If we dont have the trigger anim, just skip it + triggerAnim = spawnArgs.GetString ( "trigger_anim" ); + if ( !*triggerAnim || !HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + gameLocal.Warning ( "Missing or invalid 'trigger_anim' ('%s') specified for entity '%s'", + triggerAnim, GetName() ); + return SRESULT_DONE; + } + + Show(); + ScriptedAnim ( triggerAnim, 4, false, true ); + + // FIXME: should do this a better way + // Alwasy let trigger anims play out + fl.neverDormant = true; + + return SRESULT_DONE; +} + +/* +================ +idAI::State_WakeUp +================ +*/ +stateResult_t idAI::State_WakeUp ( const stateParms_t& parms ) { + const char* triggerAnim; + + WakeUp ( ); + + // Start immeidately into a playback? + if( spawnArgs.FindKey( "playback_intro" ) ){ + ScriptedPlaybackMove ( "playback_intro", PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, 0 ); + // Start immeidately into a scripted anim? + } else if ( spawnArgs.GetString ( "trigger_anim", "", &triggerAnim) && *triggerAnim && HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + PostState ( "State_TriggerAnim" ); + return SRESULT_DONE; + } else { + // Either wait to be triggered or wait until they have an enemy + if ( spawnArgs.GetBool ( "trigger" ) ) { + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + + PostState ( "Wait_Activated" ); + } + } + + PostState ( "State_Combat" ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Passive +================ +*/ +stateResult_t idAI::State_Passive ( const stateParms_t& parms ) { + if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + if ( UpdateTactical ( TACTICALUPDATE_PASSIVEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Combat + +The base combat state is basically the clearing house for other combat states. By calling +UpdateTactical with a timer of zero it ensures a better tactical move can be found. +================ +*/ +stateResult_t idAI::State_Combat ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + combat.tacticalCurrent = AITACTICAL_NONE; + + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Update the tactical state using all available tactical abilities and reset + // the current tactical state since this is the generic state. + combat.tacticalCurrent = AITACTICAL_NONE; + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If we are here then there isnt a single combat state available, thats not good + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatCover + +Seek out a cover point that has a vantage point on the enemy. Stay there firing at the +enemy until the cover point is invalidated. +================ +*/ +stateResult_t idAI::State_CombatCover ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_ATTACK, + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done && aasSensor->Reserved ( ) ) { + StopMove ( MOVE_STATUS_DONE ); + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we dont have a cover point anymore then just bail out + if ( !aasSensor->Reserved ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // If we have moved off our cover point, move back + if ( DistanceTo ( aasSensor->ReservedOrigin ( ) ) > 8.0f ) { + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + } + // Dont do any cover checks until they are facing towards the wall + if ( !FacingIdeal ( ) ) { + return SRESULT_WAIT; + } + // Perform cover point actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +idAI::State_CombatMelee + +Head directly towards our enemy but allow ranged actions along the way +================ +*/ +stateResult_t idAI::State_CombatMelee ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the enemy + STAGE_ATTACK, // Keep attacking until no longer in melee range + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // If we can no longer get to our enemy, give up on this and do something else! + if ( move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we are out of melee range or lost sight of our enemy then start moving again + if ( !IsEnemyVisible ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Always face enemy when in melee range + TurnToward ( enemy.lastKnownPosition ); + + // Perform actions while standing still + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatRanged + +Move towards enemy until within firing range then continue to shoot at the enemy +================ +*/ +stateResult_t idAI::State_CombatRanged ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move to an attack position + STAGE_ATTACK, // Perform actions until the enemy is out of range or not visible + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // if we lost our enemy we are done here + if ( !enemy.ent ) { + ForceTacticalUpdate ( ); + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + // If done moving or within range of a visible enemy we can stop + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Stop early because we have a shot on our enemy? + if ( !move.fl.noRangedInterrupt && + !aifl.simpleThink && enemy.fl.visible + && gameLocal.time - enemy.lastVisibleChangeTime > 500 + && enemy.range >= combat.attackRange[0] + && enemy.range <= combat.attackRange[1] + && ( DistanceTo ( move.moveDest ) < DistanceTo ( move.lastMoveOrigin ) ) + && (!tether || tether->ValidateDestination ( this, physicsObj.GetOrigin( ) ) ) + && aiManager.ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If the enemy is visible but not in fov then rotate + if ( !enemy.fl.inFov && enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatTurret + +Stay put and shoot at the enemy when nearby +================ +*/ +stateResult_t idAI::State_CombatTurret ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) && !enemy.fl.inFov ) { + TurnToward ( enemy.lastKnownPosition ); + // Turn towards tether direction if we have no enemy + } else if ( !enemy.ent && tether ) { + TurnToward ( physicsObj.GetOrigin() + tether->GetPhysics()->GetAxis()[0] * 64.0f ); + } else if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If there are other available tactical states besides turret then try to + // go to one of them + if ( combat.tacticalMaskAvailable & ~(1<GetPhysics()->GetAxis()[0]; + TurnToward ( toPos ); + if ( !IsBehindCover() ) { + //Do a trace *once* to see if I can crouch-look in the direction of the tether at this point + trace_t tr; + idVec3 crouchEye = physicsObj.GetOrigin( ); + crouchEye.z += 32.0f; + gameLocal.TracePoint( this, tr, crouchEye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + combat.fl.crouchViewClear = true; + } + } + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } + // Update tactical state occasionally to see if there is something better to do + if ( UpdateTactical ( TACTICALUPDATE_MOVETETHERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MoveFollow +================ +*/ +stateResult_t idAI::State_MoveFollow ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + // If we lost our leader we are done + if ( !leader ) { + move.fl.done = true; + // Can we just stop here? + } else if ( DistanceTo ( leader ) < (move.followRange[0]+move.followRange[1])*0.5f && aiManager.ValidateDestination ( this, physicsObj.GetOrigin( ), false, leader ) ) { + move.fl.done = true; + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + move.fl.done = false; + } + */ + } + // Are we done moving? + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + if ( leader ) { + TurnToward ( leader->GetPhysics()->GetOrigin() ); + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else { + if ( UpdateTactical ( TACTICALUPDATE_FOLLOWDELAY ) ) { + return SRESULT_DONE_WAIT; + } + } + + // Perform actions on the way to the enemy + UpdateAction ( ); + + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MovePlayerPush + +Move out of the way when the player is pushing us +================ +*/ +stateResult_t idAI::State_MovePlayerPush ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + if ( move.fl.done ) { + StopMove(MOVE_STATUS_DONE); + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else if ( gameLocal.GetTime() - move.startTime > 2000 ) { + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Killed +================ +*/ +stateResult_t idAI::State_Killed ( const stateParms_t& parms ) { + disablePain = true; + + //quickburning subjects skip all this jazz + if( fl.quickBurn ) { + PostState ( "State_Dead" ); + return SRESULT_DONE; + } + + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + // Make sure all animations stop + animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; +} + + +/* +================ +idAI::State_Dead +================ +*/ +stateResult_t idAI::State_Dead ( const stateParms_t& parms ) { + if ( !fl.hidden ) { + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + if( fl.quickBurn ) { + StopRagdoll(); + PostState ( "State_Burn", SEC2MS(0.05f) ); + } else if ( spawnArgs.GetString( "fx_burn_lightning", NULL ) ) { + lightningNextTime = 0; + lightningEffects = 0; + PostState ( "State_LightningDeath", SEC2MS(burnDelay) ); + } else { + PostState ( "State_Burn", SEC2MS(burnDelay) ); + } + } + float removeDelay = SEC2MS ( spawnArgs.GetFloat ( "removeDelay" ) ); + if ( removeDelay >= 0.0f ) { + PostState ( "State_Remove", removeDelay ); + } + } else { + PostState ( "State_Remove" ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_LightningDeath +================ +*/ +stateResult_t idAI::State_LightningDeath ( const stateParms_t& parms ) { + if ( gameLocal.time > lightningNextTime ) { + if ( !lightningEffects ) + { + StartSound ( "snd_burn_lightning", SND_CHANNEL_BODY, 0, false, NULL ); + } + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( spawnArgs, "fx_burn_lightning" ), this, 100 ); + effect->Play ( gameLocal.time, false ); + lightningNextTime = gameLocal.time + 100; + lightningEffects++; + } + if ( lightningEffects < 10 ) + { + return SRESULT_WAIT; + } + + /* + if ( spawnArgs.GetString( "fx_burn_lightning" ) ) + { + for ( int i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + if ( !gameLocal.random.RandomInt(1) ) { + PlayEffect( "fx_burn_lightning", (jointHandle_t)i ); + } + } + } + } + */ + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + PostState ( "State_Burn", 0 ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Burn +================ +*/ +stateResult_t idAI::State_Burn ( const stateParms_t& parms ) { + if( fl.hidden ){ + return SRESULT_DONE; + } + + + renderEntity.noShadow = true; + + // Dont let the articulated figure be shot once they start burning away + SetCombatContents ( false ); + fl.takedamage = false; + + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + idEntity *head = GetHead(); + if ( head ) { + head->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + } + + UpdateVisuals(); + + if ( spawnArgs.GetString( "fx_burn_particles", NULL ) ) + { + int i = GetAnimator()->GetJointHandle( spawnArgs.GetString("joint_chestOffset","chest") ); + if ( i != INVALID_JOINT ) + { + PlayEffect( "fx_burn_particles_chest", (jointHandle_t)i ); + } + + for ( i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + PlayEffect( "fx_burn_particles", (jointHandle_t)i ); + } + } + //FIXME: head, too? + //FIXME: from joint to parent joint? + //FIXME: not small joints... + } + + StartSound ( "snd_burn", SND_CHANNEL_BODY, 0, false, NULL ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Remove +================ +*/ +stateResult_t idAI::State_Remove ( const stateParms_t& parms ) { + PostEventMS( &EV_Remove, 0 ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_ScriptedAnim +================ +*/ +stateResult_t idAI::State_Torso_ScriptedAnim ( const stateParms_t& parms ) { + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + aifl.scripted = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedMove +================ +*/ +stateResult_t idAI::State_ScriptedMove ( const stateParms_t& parms ) { + if ( !aifl.scripted || move.fl.done ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedFace +================ +*/ +stateResult_t idAI::State_ScriptedFace ( const stateParms_t& parms ) { + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedPlaybackMove +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + if ( mPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedPlaybackAim +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackAim ( const stateParms_t& parms ) { + if ( mLookPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedStop +================ +*/ +stateResult_t idAI::State_ScriptedStop ( const stateParms_t& parms ) { + ScriptedEnd ( ); + + // If ending in an idle animation move the legs back to idle and + // revert back to normal combat + if ( aifl.scriptedEndWithIdle ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + } + + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Torso States + +=============================================================================== +*/ + +/* +================ +idAI::State_Torso_Idle +================ +*/ +stateResult_t idAI::State_Torso_Idle ( const stateParms_t& parms ) { + // Custom passive idle? + if ( combat.tacticalCurrent == AITACTICAL_PASSIVE && passive.animIdlePrefix.Length() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the legs were disabled then get them back into idle again + if ( legsAnim.Disabled ( ) ) { + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + } + + // Start idle animation + const char* idleAnim = GetIdleAnimName (); + if ( !torsoAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_TORSO, idleAnim, parms.blendFrames ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_PassiveIdle +================ +*/ +stateResult_t idAI::State_Torso_PassiveIdle ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT, + STAGE_TALK_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + // Talk animation? + if ( passive.talkTime > gameLocal.time ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Find their talk animation + animName = spawnArgs.GetString ( va("%s_%s", passive.animTalkPrefix.c_str(), postfix.c_str() ) ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.GetString ( passive.animTalkPrefix ); + } + + if ( animName.Length ( ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_TALK_WAIT ); + } + } + + // If we have a fidget animation then see if its time to play it + if ( passive.animFidgetPrefix.Length() ) { + if ( passive.fidgetTime == 0 ) { + passive.fidgetTime = gameLocal.time + SEC2MS ( spawnArgs.GetInt ( "fidget_rate", "20" ) ); + } else if ( gameLocal.time > passive.fidgetTime ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveFidget", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", 4 ); + return SRESULT_DONE; + } + } + + // Do we have a custom idle animation? + passive.idleAnim = spawnArgs.RandomPrefix ( passive.animIdlePrefix, gameLocal.random ); + passive.idleAnimChangeTime = passive.fl.multipleIdles ? SEC2MS ( spawnArgs.GetFloat ( "idle_change_rate", "10" ) ) : 0; + + // Is there a start animation for the idle? + animName = va("%s_start", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, 0 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + // Talk animation interrupting? + if ( passive.animTalkPrefix.Length() && passive.talkTime > gameLocal.time ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If its time to play a new idle animation then do so + if ( passive.idleAnimChangeTime && gameLocal.time > passive.idleAnimChangeTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If its time to fidget then do so + if ( passive.fidgetTime && gameLocal.time > passive.fidgetTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // Loop the idle again + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va("%s_end", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_STAGE ( STAGE_START ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + + case STAGE_TALK_WAIT: + if ( gameLocal.time > passive.talkTime ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_PassiveFidget +================ +*/ +stateResult_t idAI::State_Torso_PassiveFidget ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_INIT: + passive.fidgetTime = 0; + animName = spawnArgs.RandomPrefix ( va("anim_%sfidget", passive.prefix.c_str() ), gameLocal.random ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.RandomPrefix ( "anim_fidget", gameLocal.random ); + } + if ( !animName.Length ( ) ) { + return SRESULT_DONE; + } + if ( !PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_ScriptedJumpDown + +Face edge, walk off +================ +*/ +stateResult_t idAI::State_ScriptedJumpDown( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FACE_WAIT, + STAGE_ANIM, + STAGE_JUMP_WAIT, + STAGE_INAIR, + STAGE_WAIT_LAND, + STAGE_ANIM_WAIT, + STAGE_FINISH + }; + + switch ( parms.stage ) { + case STAGE_INIT: + { + aifl.scripted = true; + Event_SaveMove(); + StopMove(MOVE_STATUS_DONE); + //move.current_yaw = move.ideal_yaw; + move.moveCommand = MOVE_NONE; + move.fl.allowDirectional = false; + } + if ( !move.fl.onGround ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_STAGE ( STAGE_FACE_WAIT ); + break; + case STAGE_FACE_WAIT: + if ( FacingIdeal() ) + { + return SRESULT_STAGE ( STAGE_ANIM ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM: + { + DisableAnimState ( ANIMCHANNEL_LEGS ); + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + if ( HasAnim( ANIMCHANNEL_TORSO, "jumpdown_start" ) ) { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_start", 4 ); + move.fl.allowAnimMove = true; + move.fl.noGravity = true; + return SRESULT_STAGE ( STAGE_JUMP_WAIT ); + } + return SRESULT_STAGE ( STAGE_INAIR ); + } + break; + case STAGE_JUMP_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_WAIT; + break; + case STAGE_INAIR: + { + idVec3 vel = viewAxis[0] * 200.0f; + vel.z = -50.0f; + physicsObj.SetLinearVelocity( vel ); + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_loop", 4 ); + move.fl.allowAnimMove = false; + move.fl.noGravity = false; + return SRESULT_STAGE ( STAGE_WAIT_LAND ); + } + break; + case STAGE_WAIT_LAND: + if ( physicsObj.OnGround() )//GetPhysics()->HasGroundContacts() ) + { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_end", 0 ); + move.fl.allowAnimMove = true; + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 ) ) + { + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + break; + case STAGE_FINISH: + Event_RestoreMove(); + SetState( "State_Combat" ); + aifl.scripted = false; + return SRESULT_DONE; + break; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_CustomCycle +================ +*/ +stateResult_t idAI::State_Torso_CustomCycle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Sight +================ +*/ +stateResult_t idAI::State_Torso_Sight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + // Dont let everyone on the team play their sight sounds at the same time + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT ) ) { + Speak ( "lipsync_sight", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT, 1000 ); + } + + // Execute sighted scripts + if( !enemy.fl.sighted ) { + enemy.fl.sighted = true; + ExecScriptFunction ( funcs.first_sight, this ); + } + ExecScriptFunction ( funcs.sight ); + + idStr animName = spawnArgs.GetString ( "anim_sight", spawnArgs.GetString ( "sight_anim" ) ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_DONE; + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_Action +================ +*/ +stateResult_t idAI::State_Torso_Action ( const stateParms_t& parms ) { + // Invalid animation so dont bother with running the action + if ( actionAnimNum == -1 ) { + return SRESULT_DONE_WAIT; + } + + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + + // Wait till animation is finished + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", parms.blendFrames ); + + return SRESULT_DONE_WAIT; +} + +/* +================ +idAI::State_Torso_FinishAction +================ +*/ +stateResult_t idAI::State_Torso_FinishAction ( const stateParms_t& parms ) { + + RestoreFlag ( AIFLAGOVERRIDE_DISABLEPAIN ); + RestoreFlag ( AIFLAGOVERRIDE_DAMAGE ); + RestoreFlag ( AIFLAGOVERRIDE_NOTURN ); + RestoreFlag ( AIFLAGOVERRIDE_NOGRAVITY ); + + enemy.fl.lockOrigin = false; + aifl.action = false; + + OnStopAction ( ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Pain +================ +*/ +stateResult_t idAI::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + pain.loopEndTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "pain_maxLoopTime", "1" ) ); + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + animName = va( "%s_start", painAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 1 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_TORSO, painAnim, 1 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + if ( gameLocal.time - pain.lastTakenTime > AI_PAIN_LOOP_DELAY ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( !pain.loopEndTime || gameLocal.time > pain.loopEndTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va( "%s_end", painAnim.c_str() ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, 1 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Head States + +=============================================================================== +*/ + +/* +================ +idAI::State_Head_Idle +================ +*/ +stateResult_t idAI::State_Head_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idAI::State_Legs_Idle +================ +*/ +stateResult_t idAI::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + const char* idleAnim; + move.fl.allowAnimMove = false; + idleAnim = GetIdleAnimName ( ); + if ( !legsAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_LEGS, idleAnim, parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + // Dont let the legs do anything from idle until they are done blending + // the animations they have (this is to prevent pops when an action finishes + // and blends to idle then immediately the legs move to walk) + if ( animator.IsBlending ( ANIMCHANNEL_LEGS, gameLocal.time ) ) { + return SRESULT_WAIT; + } + + if ( move.fl.moving && CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Legs_TurnLeft +================ +*/ +stateResult_t idAI::State_Legs_TurnLeft ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_TurnRight +================ +*/ +stateResult_t idAI::State_Legs_TurnRight ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_Move +================ +*/ +stateResult_t idAI::State_Legs_Move ( const stateParms_t& parms ) { + idStr animName; + + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // Movement direction changed? + if ( move.idealDirection != move.currentDirection ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_ChangeDirection", parms.blendFrames ); + } + + // Make sure run status is up to date when legs start moving + UpdateRunStatus ( ); + + // Run or walk? + animName = "run"; + if ( !move.fl.idealRunning && HasAnim ( ANIMCHANNEL_TORSO, "walk" ) ) { + animName = "walk"; + } + + // Append the run direction to the animation name + if ( move.idealDirection == MOVEDIR_LEFT ) { + animName = animName + "_left"; + } else if ( move.idealDirection == MOVEDIR_RIGHT ) { + animName = animName + "_right"; + } else if ( move.idealDirection == MOVEDIR_BACKWARD ) { + animName = animName + "_backwards"; + } else { +/* + // When moving to cover the walk animation is special + if ( move.moveCommand == MOVE_TO_COVER && !move.fl.idealRunning && move.fl.running ) { + animName = "run_slowdown"; + } +*/ + } + + // Update running flag with ideal state + move.fl.running = move.fl.idealRunning; + move.currentDirection = move.idealDirection; + + PlayCycle ( ANIMCHANNEL_LEGS, animName, parms.blendFrames ); + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_MoveThink", parms.blendFrames ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_MoveThink +================ +*/ +stateResult_t idAI::State_Legs_MoveThink ( const stateParms_t& parms ) { + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving anymore then go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the run state has changed restart the leg movement + if ( UpdateRunStatus ( ) || (move.idealDirection != move.currentDirection) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 4 ); + return SRESULT_DONE; + } + + // Just wait a frame before doing anything else + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Legs_ChangeDirection +================ +*/ +stateResult_t idAI::State_Legs_ChangeDirection ( const stateParms_t& parms ) { + if ( FacingIdeal ( ) || (move.idealDirection != move.currentDirection) ) { + return SRESULT_DONE; + } + + move.fl.allowAnimMove = false; + move.fl.allowPrevAnimMove = true; + return SRESULT_WAIT; +} + +/* +=============================================================================== + + State helpers + +=============================================================================== +*/ + +/* +================ +idAI::UpdateRunStatus +================ +*/ +bool idAI::UpdateRunStatus ( void ) { + // Get new run status + if ( !move.fl.moving || !CanMove() ) { + move.fl.idealRunning = false; + } else if ( move.walkRange && !ai_useRVMasterMove.GetBool() && move.moveCommand != MOVE_TO_ENEMY && (DistanceTo ( move.moveDest ) - move.range) < move.walkRange ) { + move.fl.idealRunning = false; + } else if ( IsTethered() && tether->IsRunForced ( ) ) { + move.fl.idealRunning = true; + } else if ( IsTethered() && tether->IsWalkForced ( ) ) { + move.fl.idealRunning = false; + } else if ( move.fl.noWalk ) { + move.fl.idealRunning = true; + } else if ( move.fl.noRun ) { + move.fl.idealRunning = false; + } else if ( move.walkRange ) { + if ( move.fl.obstacleInPath ) { + move.fl.idealRunning = false; + } else if ( InLookAtCoverMode() ) { + move.fl.idealRunning = false; + } else if ( !ai_useRVMasterMove.GetBool() && move.walkTurn && !move.fl.allowDirectional && fabs(GetTurnDelta ( )) > move.walkTurn ) { + move.fl.idealRunning = false; + } else { + move.fl.idealRunning = true; + } + } else { + move.fl.idealRunning = true; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +idAI::UpdateTactical + +This method is a recursive method that will determine the best tactical state to be in +from the given available states. +================ +*/ +bool idAI::UpdateTactical ( int delay ) { + // Update tactical cannot be called while performing an action and it must be called from the main state loop + assert ( !aifl.action ); + + // No movement updating on simple think (if the update is forced do it anyway) + if ( combat.tacticalUpdateTime && aifl.simpleThink && !combat.tacticalMaskUpdate ) { + return false; + } + // Don't let tactical updates pre-empt pain actions + if ( pain.takenThisFrame ) { + return false; + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Start ( ); + } + + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Stop ( ); + } + + // We have sighted an enemy so execute the sight state + if ( IsEnemyVisible() && !enemy.fl.sighted ) { + PerformAction ( "Torso_Sight", 4, true ); + return true; + } + + // continue with the last updatetactical if we still have bits left to check in our update mask + if ( !combat.tacticalMaskUpdate ) { + // Ignore the tactical delay if we are taking too much damage here. + // TODO: use skipcurrentdestination instead + if ( !combat.tacticalPainThreshold || combat.tacticalPainTaken < combat.tacticalPainThreshold ) { + // Delay tactical updating? + if ( combat.tacticalUpdateTime && move.moveStatus != MOVE_STATUS_BLOCKED_BY_ENEMY && delay && (gameLocal.time - combat.tacticalUpdateTime) < delay ) { + return false; + } + } + + // handle Auto break tethers + if ( IsTethered ( ) && tether->IsAutoBreak ( ) && IsWithinTether ( ) ) { + tether = NULL; + } + + // Filter the tactical + combat.tacticalMaskUpdate = FilterTactical ( combat.tacticalMaskAvailable ); + } else { + // Make sure all cached tactical updates are still valid + combat.tacticalMaskUpdate &= FilterTactical ( combat.tacticalMaskAvailable ); + if ( !combat.tacticalMaskUpdate ) { + return false; + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Start ( ); + } + + // Recursively look for a better tactical state + bool result = UpdateTactical_r ( ); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Stop ( ); + } + + return result; +} + +bool idAI::UpdateTactical_r ( void ) { + // Mapping of tactical types to combat states + static const char* tacticalState [ ] = { + "State_Combat", // AITACTICAL_NONE + "State_CombatMelee", // AITACTICAL_MELEE + "State_MoveFollow", // AITACTICAL_MOVE_FOLLOW + "State_MoveTether", // AITACTICAL_MOVE_TETHER + "State_MovePlayerPush", // AITACTICAL_MOVE_PLAYERPUSH + "State_CombatCover", // AITACTICAL_COVER + "State_CombatCover", // AITACTICAL_COVER_FLANK + "State_CombatCover", // AITACTICAL_COVER_ADVANCE + "State_CombatCover", // AITACTICAL_COVER_RETREAT + "State_CombatCover", // AITACTICAL_COVER_AMBUSH + "State_CombatRanged", // AITACTICAL_RANGED + "State_CombatTurret", // AITACTICAL_TURRET + "State_CombatHide", // AITACTICAL_HIDE + "State_Passive", // AITACTICAL_PASSIVE + }; + + if ( g_perfTest_aiStationary.GetBool() ) { + return false; + } + // Determine the new tactical state + aiTactical_t newTactical = AITACTICAL_TURRET; + + if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_PLAYERPUSH_BIT ) { + newTactical = AITACTICAL_MOVE_PLAYERPUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_FOLLOW_BIT ) { + newTactical = AITACTICAL_MOVE_FOLLOW; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_ADVANCE_BIT ) { + newTactical = AITACTICAL_COVER_ADVANCE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_FLANK_BIT ) { + newTactical = AITACTICAL_COVER_FLANK; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_RETREAT_BIT ) { + newTactical = AITACTICAL_COVER_RETREAT; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_AMBUSH_BIT ) { + newTactical = AITACTICAL_COVER_AMBUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_BIT ) { + newTactical = AITACTICAL_COVER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_RANGED_BIT ) { + newTactical = AITACTICAL_RANGED; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MELEE_BIT ) { + newTactical = AITACTICAL_MELEE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_TETHER_BIT ) { + newTactical = AITACTICAL_MOVE_TETHER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_HIDE_BIT ) { + newTactical = AITACTICAL_HIDE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_PASSIVE_BIT ) { + newTactical = AITACTICAL_PASSIVE; + } + + // Remove the new tactical from the available in case we have to recursively call ourself + combat.tacticalMaskUpdate &= ~(1<IsType ( idPlayer::GetClassType ( ) ) ) { + //no current pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, just continue it + availableTactical = AITACTICAL_NONE_BIT; + return availableTactical; + } + //stop pushing away + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } else { + //have a pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, only allow it to continue playerpush, but can re-calc, if needbe + availableTactical = AITACTICAL_MOVE_PLAYERPUSH_BIT; + return availableTactical; + } + if ( ai_playerPushAlways.GetBool() ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + case AITACTICAL_MOVE_FOLLOW: + case AITACTICAL_MOVE_TETHER: + case AITACTICAL_MOVE_PLAYERPUSH: + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + case AITACTICAL_RANGED: + case AITACTICAL_HIDE: + //okay to be pushed by players + break; + default: + if ( !leader ) { + //no leader and not in a moving tactical state, don't be pushed by players + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + break; + } + } else if ( !leader || (move.fl.moving&&move.goalEntity!=leader) ) { + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + } + } + + // No tether move if not tethered + if ( !IsTethered ( ) ) { + availableTactical &= ~(AITACTICAL_MOVE_TETHER_BIT); + + // Filter out any tactical states that require a leader + if ( !leader || (enemy.ent && IsEnemyVisible() && enemy.range < move.followRange[1] * 2.0f ) ) { + availableTactical &= ~(AITACTICAL_MOVE_FOLLOW_BIT); + } else if ( leader && !enemy.ent ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + // When tethered disable actions that dont adhear to the tether. + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_HIDE_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); + } + + // If we dont have an enemy we can filter out pure combat states + if ( !enemy.ent ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_MELEE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + + // If we arent aware either then we cant take cover + if ( !combat.fl.aware ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + } else { + // Dont advance or flank our enemy if we have seen them recently + if ( IsEnemyRecentlyVisible ( 0.5f ) ) { + availableTactical &= ~(AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT); + } + + // FIXME: need conditions for these cover states + availableTactical &= ~(AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_COVER_RETREAT_BIT); + } + + // Filter out all cover states if cover is disabled + if ( ai_disableCover.GetBool ( ) ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + + //if we need to fight in melee then fight in melee! + if( IsMeleeNeeded( ) ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + } + + return availableTactical; +} + +/* +================ +idAI::CheckTactical + +Returns 'AICHECKTACTICAL_MOVE' if we should use the given tactical state and execute a movement to do so +Returns 'AICHECKTACTICAL_SKIP' if the given tactical state should be skipped +Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but no movement is required +================ +*/ +aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) { + // Handle non movement tactical states first + if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) { + // If we are moving + if ( move.fl.moving ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + // If we are tethered always check that first + if ( IsTethered ( ) ) { + if ( !move.fl.moving || tactical != combat.tacticalCurrent ) { + // We stopped out of tether range so try a new move to get back in + if ( !tether->ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + return AICTRESULT_OK; + } + } else { + // Move is out of tether range, so look for another one + if ( !tether->ValidateDestination ( this, move.moveDest ) ) { + return AICTRESULT_OK; + } + } + } + + // Anything wrong with our current destination, if so pick a new move + if ( SkipCurrentDestination ( ) ) { + return AICTRESULT_OK; + } + + switch ( tactical ) { + case AITACTICAL_MOVE_FOLLOW: + // If already moving to our leader dont move again + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == leader ) { + return AICTRESULT_NOMOVE; + } + // If not moving and within follow range we can skip the follow state + if ( !move.fl.moving ) { + if ( DistanceTo ( leader ) < move.followRange[1] ) { + //unless the leader is on an elevator we should be standing on... + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + return AICTRESULT_OK; + } + */ + return AICTRESULT_SKIP; + } + } + return AICTRESULT_OK; + + case AITACTICAL_MOVE_TETHER: + // If not moving we dont want to idle in the move state so skip it + if ( !move.fl.moving ) { + return AICTRESULT_SKIP; + } + // We are still heading towards our tether so dont reissue a move + return AICTRESULT_NOMOVE; + + case AITACTICAL_RANGED: + // Currently issuing a find + if ( dynamic_cast(aasFind) ) { + return AICTRESULT_OK; + } + + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + // If the enemy is visible then no movement is required for ranged combat + if ( !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + if ( !move.fl.moving ) { + // If not within the attack range we need to move + if ( enemy.range > combat.attackRange[1] || enemy.range < combat.attackRange[0] ) { + return AICTRESULT_OK; + } + + // If we havent seen our enemy for a while and arent moving, then get moving! + if ( enemy.lastVisibleTime && gameLocal.time - enemy.lastVisibleTime > RANGED_ENEMYDELAY ) { + return AICTRESULT_OK; + } + } else { + // Is our destination out of range? + if ( (move.moveDest - enemy.lastKnownPosition).LengthSqr ( ) > Square ( combat.attackRange[1] ) ) { + return AICTRESULT_OK; + } + } + // Our position is fine so just stay in ranged combat + return AICTRESULT_NOMOVE; + + case AITACTICAL_MELEE: + // IF we are already moving towards our enemy then we dont need a state change + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + return AICTRESULT_NOMOVE; + } + return AICTRESULT_OK; + + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + if ( enemy.ent && !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + // No longer behind cover? + if ( !IsBehindCover ( ) ) { + return AICTRESULT_OK; + } + // Move if there have been too many close calls + if ( combat.tacticalFlinches && combat.tacticalFlinches > gameLocal.random.RandomInt(12) + 3 ) { + return AICTRESULT_OK; + } + // Move if cover destination isnt valid anymore + if ( !IsCoverValid() ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + return AICTRESULT_OK; +} + +/* +================ +idAI::WakeUpTargets +================ +*/ +void idAI::WakeUpTargets ( void ) { + const idKeyValue* kv; + + for ( kv = spawnArgs.MatchPrefix ( "wakeup_target" ); kv; kv = spawnArgs.MatchPrefix ( "wakeup_target", kv ) ) { + idEntity* ent; + ent = gameLocal.FindEntity ( kv->GetValue ( ) ); + if ( !ent ) { + gameLocal.Warning ( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), GetName() ); + } else { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + ent->TriggerGuis ( ); + } + } + + // Find all the tether entities we target + const char* target; + if ( spawnArgs.GetString ( "tether_target", "", &target ) && *target ) { + idEntity* ent; + ent = gameLocal.FindEntity ( target ); + if ( ent && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + ProcessEvent ( &EV_Activate, ent ); + } + } +} + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idAI::State_Wait_Activated + +Stop the state thread until the ai is activated +================ +*/ +stateResult_t idAI::State_Wait_Activated ( const stateParms_t& parms ) { + if ( (aifl.activated || aifl.pain ) && CanBecomeSolid ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_Action + +Stop the state thread as long as a torso action is running and allow a pain action to interrupt +================ +*/ +stateResult_t idAI::State_Wait_Action ( const stateParms_t& parms ) { + if ( CheckPainActions ( ) ) { + // Our current action is being interrupted by pain so make sure the action can be performed + // immediately after coming out of the pain. + actionTime = actionSkipTime; + return SRESULT_DONE; + } + + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ActionNoPain + +Stop the state thread as long as a torso action is running +================ +*/ +stateResult_t idAI::State_Wait_ActionNoPain ( const stateParms_t& parms ) { + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ScriptedDone + +Stop the state thread as a scripted sequence is active +================ +*/ +stateResult_t idAI::State_Wait_ScriptedDone ( const stateParms_t& parms ) { + if ( aifl.scripted ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} diff --git a/source/game/ai/AI_Tactical.cpp b/source/game/ai/AI_Tactical.cpp new file mode 100644 index 0000000..480cf65 --- /dev/null +++ b/source/game/ai/AI_Tactical.cpp @@ -0,0 +1,1054 @@ +// +// TODO: +// - unarmed posture +// - relaxed posture +// - turning in place too much, rely more on look angles? + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Tactical.h" + +const idEventDef AI_ForcePosture ( "forcePosture", "d" ); + +CLASS_DECLARATION( idAI, rvAITactical ) + EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture ) + EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn ) +END_CLASS + +static const char* aiPostureString[AIPOSTURE_MAX] = { + "stand", // AIPOSTURE_STAND, + "crouch", // AIPOSTURE_CROUCH, + "cover_left", // AIPOSTURE_STAND_COVER_LEFT, + "cover_right", // AIPOSTURE_STAND_COVER_RIGHT, + "crouch_cover", // AIPOSTURE_CROUCH_COVER + "crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT, + "crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT, + "relaxed", // AIPOSTURE_RELAXED + "unarmed", // AIPOSTURE_UNARMED + "at_attention", // AIPOSTURE_AT_ATTENTION +}; + +/* +================ +rvAITactical::rvAITactical +================ +*/ +rvAITactical::rvAITactical ( void ) { + shots = 0; + nextWallTraceTime = 0; +} + +void rvAITactical::InitSpawnArgsVariables ( void ) +{ + // Initialize the posture info + InitPostureInfo ( ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) ); + + healthRegen = spawnArgs.GetInt( "healthRegen", "2" ); + healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" ); +} + +/* +================ +rvAITactical::Spawn +================ +*/ +void rvAITactical::Spawn ( void ) { + InitSpawnArgsVariables(); + + // Force a posture? + const char* temp; + postureForce = AIPOSTURE_DEFAULT; + if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) { + for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) { + if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) { + break; + } + } + if ( postureForce >= AIPOSTURE_MAX ) { + postureForce = AIPOSTURE_DEFAULT; + } + } + + UpdatePosture ( ); + postureCurrent = postureIdeal; + + OnPostureChange ( ); + + ammo = spawnArgs.GetInt ( "ammo", "-1" ); + + // Initialize custom actions + actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK ); + actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK ); + + actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" ); + +// playerFocusTime = 0; +// playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" )); + + healthRegenNextTime = 0; + maxHealth = health; +} + +/* +================ +rvAITactical::Think +================ +*/ +void rvAITactical::Think ( void ) { + idAI::Think ( ); + + // If not simple thinking and not in an action, update the posture + if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) { + if ( UpdatePosture ( ) ) { + PerformAction ( "Torso_SetPosture", 4, true ); + } + } + +// FIXME: disabled for now, its annoying people + /* + idPlayer* localPlayer; + localPlayer = gameLocal.GetLocalPlayer(); + + // If the player has been standing in front of the marine and looking at him for too long he should say something + if ( !aifl.dead && playerFocusTime && playerAnnoyTime + && !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking() + && !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now + && DistanceTo( localPlayer ) < 64.0f ) { + idVec3 diff; + + + diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin(); + diff.NormalizeFast(); + + // Is the player looking at the marine? + if ( diff * localPlayer->viewAxis[0] > 0.7f ) { + // Say something every 5 seconds + if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) { + // Debounce it against other marines + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) { + Speak ( "lipsync_canihelpyou", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 ); + } + } + } else { + playerFocusTime = gameLocal.time; + } + } else { + playerFocusTime = gameLocal.time; + } + */ + + if ( health > 0 ) { + //alive + if ( healthRegenEnabled && healthRegen ) { + if ( gameLocal.GetTime() >= healthRegenNextTime ) { + health = idMath::ClampInt( 0, maxHealth, health+healthRegen ); + healthRegenNextTime = gameLocal.GetTime() + 1000; + } + } + } + + //crappy place to do this, just testing + bool clearPrefix = true; + bool facingWall = false; + if ( move.fl.moving && InCoverMode() && combat.fl.aware ) { + clearPrefix = false; + if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + facingWall = true; + } else if ( nextWallTraceTime < gameLocal.GetTime() ) { + //do an occasional check for solid architecture directly in front of us + nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750; + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + if ( neckJoint != INVALID_JOINT ) { + GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis ); + end = start + axis[0] * 32.0f; + } else { + start = GetEyePosition(); + start += viewAxis[0] * 8.0f;//still inside bbox + end = start + viewAxis[0] * 32.0f; + } + //trace against solid arcitecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this ); + if ( wallTrace.fraction < 1.0f ) { + facingWall = true; + } else { + clearPrefix = true; + } + } + } + + if ( facingWall ) { + if ( !animPrefix.Length() ) { + animPrefix = "nearcover"; + } + } else if ( clearPrefix && animPrefix == "nearcover" ) { + animPrefix = ""; + } +} + +/* +================ +rvAITactical::Save +================ +*/ +void rvAITactical::Save( idSaveGame *savefile ) const { + savefile->WriteSyncId(); + + savefile->WriteInt ( ammo ); + savefile->WriteInt ( shots ); + +// savefile->WriteInt ( playerFocusTime ); +// savefile->WriteInt ( playerAnnoyTime ); + + savefile->WriteInt ( (int&)postureIdeal ); + savefile->WriteInt ( (int&)postureCurrent ); + savefile->WriteInt ( (int&)postureForce ); + // TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson: + savefile->WriteInt ( healthRegenNextTime ); + savefile->WriteInt ( maxHealth ); + savefile->WriteInt ( nextWallTraceTime ); + + + actionElbowAttack.Save ( savefile ); + actionKillswitchAttack.Save ( savefile ); + + actionTimerPeek.Save ( savefile ); +} + +/* +================ +rvAITactical::Restore +================ +*/ +void rvAITactical::Restore( idRestoreGame *savefile ) { + InitSpawnArgsVariables ( ); + savefile->ReadSyncId( "rvAITactical" ); + + savefile->ReadInt ( ammo ); + savefile->ReadInt ( shots ); + +// savefile->ReadInt ( playerFocusTime ); +// savefile->ReadInt ( playerAnnoyTime ); + + savefile->ReadInt ( (int&)postureIdeal ); + savefile->ReadInt ( (int&)postureCurrent ); + savefile->ReadInt ( (int&)postureForce ); + // TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + savefile->ReadInt ( healthRegenNextTime ); + savefile->ReadInt ( maxHealth ); + savefile->ReadInt ( nextWallTraceTime ); + + actionElbowAttack.Restore ( savefile ); + actionKillswitchAttack.Restore ( savefile ); + + actionTimerPeek.Restore ( savefile ); + + UpdateAnimPrefix ( ); +} + +/* +================ +rvAITactical::CanTurn +================ +*/ +bool rvAITactical::CanTurn ( void ) const { + if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvAITactical::CanMove +================ +*/ +bool rvAITactical::CanMove ( void ) const { + if ( !postureInfo[postureCurrent].fl.canMove ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvAITactical::CheckAction_Reload +================ +*/ +bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( ammo == 0 ) { + return true; + } + return false; +} + +/* +================ +rvAITactical::CheckActions +================ +*/ +bool rvAITactical::CheckActions ( void ) { + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // If we are pressed, fight-- do not break melee combat until you or the enemy is dead. + if ( IsMeleeNeeded ( )) { + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + //take no actions other than fighting + return false; + } + + // Handle any posture changes + if ( postureIdeal != postureCurrent ) { + PerformAction ( "Torso_SetPosture", 4, true ); + return true; + } + + // Reload takes precedence + if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) { + PerformAction ( "Torso_Reload", 4, false ); + return true; + } + + if ( IsBehindCover ( ) ) { + // If we have no enemy try peeking + if ( !IsEnemyRecentlyVisible ( ) ) { + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) { + if ( actionTimerPeek.IsDone ( actionTime ) ) { + actionTimerPeek.Reset ( actionTime, 0.5f ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 ); + PerformAction ( "Torso_Cover_Peek", 4, false ); + return true; + } + } + } + + // Attacks from cover + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + // Kill switch attack from cover? + if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) { + if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) { + return true; + } + } + + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; + } + + // Standard attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + + // Ranged attack only if there is ammo + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +rvAITactical::CheckRelaxed + +Returns true if the marine should currently be in a relaxed state +================ +*/ +bool rvAITactical::CheckRelaxed ( void ) const { +// if ( forceRelaxed ) { +// return true; +// } + +/* + // If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed + if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) { + return true; + } +*/ + + // Alwasy relaxed when ignoring enemies + if ( !combat.fl.aware ) { + return true; + } + + if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) { + return false; + } + + if ( gameLocal.time >= focusTime ) { + return false; + } + + return true; +} + +/* +================ +rvAITactical::GetIdleAnimName +================ +*/ +const char* rvAITactical::GetIdleAnimName ( void ) { + return "idle"; +} + +/* +================ +rvAITactical::UpdateAnimPrefix +================ +*/ +void rvAITactical::UpdateAnimPrefix ( void ) { + if ( postureCurrent == AIPOSTURE_STAND ) { + animPrefix = ""; + } else { + animPrefix = aiPostureString[postureCurrent]; + } +} + +/* +================ +rvAITactical::InitPostureInfo +================ +*/ +void rvAITactical::InitPostureInfo ( void ) { + int posture; + for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) { + aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture]; + + postureCurrent = (aiPosture_t)posture; + UpdateAnimPrefix ( ); + + info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true ); + info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true ); + info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true ); + info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true ); + info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true ); + info.fl.canTurn = false; + } + + // FIXME: this should be based on the availablity of turn anims + postureInfo[AIPOSTURE_STAND].fl.canTurn = true; + postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true; + postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true; +} + +/* +================ +rvAITactical::UpdatePosture +================ +*/ +bool rvAITactical::UpdatePosture ( void ) { + // If the posture is being forced then use that until its no longer forced + if ( postureForce != AIPOSTURE_DEFAULT ) { + postureIdeal = postureForce; + // Not forcing posture, determine it from our current state + } else { + postureIdeal = AIPOSTURE_STAND; + + // Behind cover? + if ( IsBehindCover ( ) ) { + bool left; + if ( enemy.ent ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f; + } else if ( tether ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f; + } else { + left = false; + } + // Should be crouching behind cover? + if ( InCrouchCoverMode ( ) ) { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT; + } else { + postureIdeal = AIPOSTURE_CROUCH_COVER; + } + } else { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } + } + } else if ( combat.fl.aware //aggressive + && (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction + && ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here + //we crouch only if leader is + postureIdeal = AIPOSTURE_CROUCH; + } else if ( CheckRelaxed ( ) ) { + postureIdeal = AIPOSTURE_RELAXED; + } + + //never crouch in melee! + if( IsMeleeNeeded() ) { + postureIdeal = AIPOSTURE_STAND; + } + } + + // Default the posture if trying to move with one that doesnt support it + if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) { + postureIdeal = AIPOSTURE_STAND; + // Default the posture if trying to turn and we cant in the posture we chose + } else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) { + postureIdeal = AIPOSTURE_STAND; + } + + return (postureIdeal != postureCurrent); +} + +/* +================ +rvAITactical::OnPostureChange +================ +*/ +void rvAITactical::OnPostureChange ( void ) { + UpdateAnimPrefix ( ); +} + +/* +============ +rvAITactical::OnSetKey +============ +*/ +void rvAITactical::OnSetKey ( const char* key, const char* value ) { + idAI::OnSetKey ( key, value ); + +/* + if ( !idStr::Icmp ( key, "annoyed" ) ) { + playerAnnoyTime = SEC2MS( atof ( value ) ); + } +*/ +} + +/* +================ +rvAITactical::OnStopMoving +================ +*/ +void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + // Ensure the peek doesnt happen immedately every time we stop at a cover + if ( IsBehindCover ( ) ){ + actionTimerPeek.Clear ( actionTime ); + actionTimerPeek.Add ( 2000, 0.5f ); + + actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity ); + + // We should be looking fairly close to the right direction, so just snap it + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + move.current_yaw = move.ideal_yaw; + } + + idAI::OnStopMoving ( oldMoveCommand ); +} + +/* +================ +rvAITactical::CalculateShots +================ +*/ +void rvAITactical::CalculateShots ( const char* fireAnim ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + if ( shots > ammo ) { + shots = ammo; + } + + // Update the firing animation playback rate + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } +} + +/* +================ +rvAITactical::UseAmmo +================ +*/ +void rvAITactical::UseAmmo ( int amount ) { + if ( ammo <= 0 ) { + return; + } + + shots--; + ammo-=amount; + if ( ammo < 0 ) { + ammo = 0; + shots = 0; + } +} + +/* +================ +rvAITactical::GetDebugInfo +================ +*/ +void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData ); + proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData ); + proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData ); + proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData ); + proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData ); + proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData ); + + proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData ); + + + proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAITactical ) + STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack ) + + STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack ) + STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack ) + STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack ) + STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek ) + + STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload ) + + STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture ) + + STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek ) +END_CLASS_STATES + +/* +================ +rvAITactical::State_Torso_SetPosture +================ +*/ +stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_RELAXED, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: { + idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] ); + if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + + if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT + || postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT + || (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) ) + { + // FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION + move.fl.allowAnimMove = false; + } else { + //no need to play cover-to-stand/relaxed transition if: + //scripted... + //or we're moving already... + //or turning away from our old cover direction... + if ( aifl.scripted + /*|| (move.fl.moving&&!move.fl.blocked) + || (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames ); + if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT + && postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT + && postureIdeal == AIPOSTURE_RELAXED ) { + //we need to also play stand_to_relaxed at the end... + return SRESULT_STAGE ( STAGE_WAIT_RELAXED ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT_RELAXED: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_RangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + CalculateShots ( "range_attack" ); + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 ); + UseAmmo ( 1 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov ) { + return SRESULT_STAGE ( STAGE_END ); + } else if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots ( "range_attack_torso" ); + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 || !enemy.fl.inFov ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Reload +================ +*/ +stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + ammo = spawnArgs.GetInt ( "ammo" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanLeftAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanRightAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) { + enum { + STAGE_OUT, + STAGE_OUTWAIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_IN, + STAGE_INWAIT, + }; + switch ( parms.stage ) { + case STAGE_OUT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // The lean out animation cannot blend with any other animations since + // it is essential that the movement delta out match the one back in. Therefore + // we force the legs and torso to be stoped before playing any animations + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 ); + return SRESULT_STAGE ( STAGE_OUTWAIT ); + + case STAGE_OUTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Random number of shots + CalculateShots ( "lean_attack" ); + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 ); + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots > 0 ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_STAGE ( STAGE_IN ); + } + return SRESULT_WAIT; + + case STAGE_IN: + PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 ); + return SRESULT_STAGE ( STAGE_INWAIT ); + + case STAGE_INWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_Peek +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Frame_Peek +================ +*/ +stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) { + CheckForEnemy ( true, true ); + return SRESULT_OK; +} + +/* +================ +rvAITactical::Event_ForcePosture +================ +*/ +void rvAITactical::Event_ForcePosture ( int posture ) { + postureForce = (aiPosture_t)posture; +} + +/* +=================== +rvAITactical::IsCrouching +=================== +*/ +bool rvAITactical::IsCrouching( void ) const { + if ( postureCurrent == AIPOSTURE_CROUCH + || postureCurrent == AIPOSTURE_CROUCH_COVER + || postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT + || postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) { + return true; + } + return idAI::IsCrouching(); +} + +/* +================ +rvAITactical::Event_PostSpawn +================ +*/ +void rvAITactical::Event_PostSpawn( void ) { + idAI::Event_PostSpawn(); + if ( team == AITEAM_MARINE && healthRegenEnabled ) + {//regen-enabled buddy marine + if ( CheckDeathCausesMissionFailure() ) + {//who is important to a mission + if ( g_skill.GetInteger() > 2 ) + {//on impossible + health *= 1.5f; + healthRegen *= 1.5f; + } + else if ( g_skill.GetInteger() > 1 ) + {//on hard + health *= 1.2f; + healthRegen *= 1.25f; + } + } + } +} \ No newline at end of file diff --git a/source/game/ai/AI_Tactical.h b/source/game/ai/AI_Tactical.h new file mode 100644 index 0000000..e9faf9d --- /dev/null +++ b/source/game/ai/AI_Tactical.h @@ -0,0 +1,134 @@ +/* +================ + +AI_Tactical.h + +================ +*/ + +#ifndef __AI_TACTICAL__ +#define __AI_TACTICAL__ + +typedef enum { + AIPOSTURE_DEFAULT = -1, + AIPOSTURE_STAND, + AIPOSTURE_CROUCH, + AIPOSTURE_STAND_COVER_LEFT, + AIPOSTURE_STAND_COVER_RIGHT, + AIPOSTURE_CROUCH_COVER, + AIPOSTURE_CROUCH_COVER_LEFT, + AIPOSTURE_CROUCH_COVER_RIGHT, + AIPOSTURE_RELAXED, + AIPOSTURE_UNARMED, + AIPOSTURE_AT_ATTENTION, + AIPOSTURE_MAX +} aiPosture_t; + +typedef struct { + struct { + bool canMove; + bool canShoot; + bool canPeek; + bool canReload; + bool canTurn; + bool canKillswitch; + } fl; + +} aiPostureInfo_t; + +class rvAITactical : public idAI { +public: + + CLASS_PROTOTYPE( rvAITactical ); + + rvAITactical ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool CheckActions ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + + virtual bool IsCrouching ( void ) const; + +protected: + + virtual const char* GetIdleAnimName ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnPostureChange ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + bool CheckRelaxed ( void ) const; + + void InitPostureInfo ( void ); + bool UpdatePosture ( void ); + void CalculateShots ( const char* fireAnim ); + void UseAmmo ( int amount ); + void SetPosture ( aiPosture_t newPosture ); + void UpdateAnimPrefix ( void ); + + int ammo; + int maxShots; + int minShots; + int shots; + float fireRate; + + int playerFocusTime; + int playerAnnoyTime; + + aiPosture_t postureIdeal; + aiPosture_t postureCurrent; + aiPosture_t postureForce; + aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + + rvAIAction actionElbowAttack; + rvAIAction actionKillswitchAttack; + + rvAIActionTimer actionTimerPeek; + +private: + + int healthRegen; + bool healthRegenEnabled; + int healthRegenNextTime; + int maxHealth; + + int nextWallTraceTime; + + // Custom actions + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + bool CheckAction_Relax ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_SetPosture ( const stateParms_t& parms ); + + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + + stateResult_t State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_Peek ( const stateParms_t& parms ); + + // Frame Commands + stateResult_t State_Frame_Peek ( const stateParms_t& parms ); + + // Events + void Event_ForcePosture ( int posture ); + + virtual void Event_PostSpawn ( void ); + + CLASS_STATES_PROTOTYPE ( rvAITactical ); +}; + +extern const idEventDef AI_ForcePosture; + +#endif /* !__AI_TACTICAL__ */ diff --git a/source/game/ai/AI_Util.cpp b/source/game/ai/AI_Util.cpp new file mode 100644 index 0000000..fd31b1f --- /dev/null +++ b/source/game/ai/AI_Util.cpp @@ -0,0 +1,977 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIHelper ) + EVENT( EV_Activate, rvAIHelper::Event_Activate ) +END_CLASS + +/* +================ +rvAIHelper::rvAIHelper +================ +*/ +rvAIHelper::rvAIHelper ( void ) { + helperNode.SetOwner( this ); +} + +/* +================ +rvAIHelper::Spawn +================ +*/ +void rvAIHelper::Spawn ( void ) { + // Auto activate? + if ( spawnArgs.GetBool ( "start_on" ) ) { + PostEventMS ( &EV_Activate, 0, this ); + } +} + +/* +================ +rvAIHelper::IsCombat +================ +*/ +bool rvAIHelper::IsCombat ( void ) const { + return false; +} + + +/* +================ +rvAIHelper::OnActivate +================ +*/ +void rvAIHelper::OnActivate ( bool active ) { + if ( active ) { + ActivateTargets ( this ); + } +} + +/* +================ +rvAIHelper::Event_Activate +================ +*/ +void rvAIHelper::Event_Activate( idEntity *activator ) { + if ( !helperNode.InList ( ) ) { + aiManager.RegisterHelper ( this ); + OnActivate ( true ); + } else { + aiManager.UnregisterHelper ( this ); + OnActivate ( false ); + } +} + +/* +================ +rvAIHelper::GetDirection +================ +*/ +idVec3 rvAIHelper::GetDirection ( const idAI* ai ) const { + if ( ai->team == 0 ) { + return GetPhysics()->GetAxis()[0]; + } + return -GetPhysics()->GetAxis()[0]; +} + +/* +================ +rvAIHelper::ValidateDestination +================ +*/ +bool rvAIHelper::ValidateDestination ( const idAI* ent, const idVec3& dest ) const { + return true; +} + +/* +=============================================================================== + +rvAICombatHelper + +=============================================================================== +*/ + +class rvAICombatHelper : public rvAIHelper { +public: + CLASS_PROTOTYPE( rvAICombatHelper ); + + rvAICombatHelper ( void ); + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + +protected: + + virtual void OnActivate ( bool active ); + + idEntityPtr location; +}; + +CLASS_DECLARATION( rvAIHelper, rvAICombatHelper ) +END_CLASS + +/* +================ +rvAICombatHelper::rvAICombatHelper +================ +*/ +rvAICombatHelper::rvAICombatHelper ( void ) { +} + +/* +================ +rvAICombatHelper::Spawn +================ +*/ +void rvAICombatHelper::Spawn ( void ) { +} + +/* +================ +rvAICombatHelper::OnActivate +================ +*/ +void rvAICombatHelper::OnActivate ( bool active ) { + rvAIHelper::OnActivate ( active ); + + if ( active ) { + if ( spawnArgs.GetBool ( "tetherLocation", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() ); + } else { + location = NULL; + } + } +} + +/* +================ +rvAICombatHelper::IsCombat +================ +*/ +bool rvAICombatHelper::IsCombat ( void ) const { + return true; +} + +/* +================ +rvAICombatHelper::ValidateDestination +================ +*/ +bool rvAICombatHelper::ValidateDestination ( const idAI* ai, const idVec3& dest ) const { + // If tethering to a location then see if the location of the given points matches our tethered location + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + + // Is the destination on the wrong side of the helper? + idVec3 origin; + idVec3 dir; + + dir = GetDirection(ai); + if ( ai->enemy.ent ) { + origin = ai->enemy.lastKnownPosition; + origin -= (dir * ai->combat.attackRange[0]); + } else { + origin = GetPhysics()->GetOrigin(); + origin -= (dir * 32.0f); + } + + if ( dir * (origin-dest) < 0.0f ) { + return false; + } + + // Would this destination link us to the wrong helper? + if ( static_cast< const rvAIHelper * >( aiManager.FindClosestHelper( dest ) ) != this ) { + return false; + } + + return true; +} + +/* +=============================================================================== + + rvAIAvoid + +=============================================================================== +*/ + +class rvAIAvoid : public idEntity { +public: + CLASS_PROTOTYPE( rvAIAvoid ); + + rvAIAvoid ( void ); + + void Spawn ( void ); +}; + +CLASS_DECLARATION( idEntity, rvAIAvoid ) +END_CLASS + +/* +================ +rvAIAvoid::rvAIAvoid +================ +*/ +rvAIAvoid::rvAIAvoid ( void ) { +} + +/* +================ +rvAIAvoid::Spawn +================ +*/ +void rvAIAvoid::Spawn ( void ) { + int team = -1; + if ( !spawnArgs.GetInt ( "teamFilter", "-1", team ) ) { + //hmm, no "teamFilter" set, check "team" since many were set up like this + team = spawnArgs.GetInt ( "team", "-1" ); + } + aiManager.AddAvoid ( GetPhysics()->GetOrigin(), spawnArgs.GetFloat ( "radius", "64" ), team ); + PostEventMS ( &EV_Remove, 0 ); +} + +/* +=============================================================================== + + rvAITrigger + +=============================================================================== +*/ + +const idEventDef AI_AppendFromSpawner ( "", "ee" ); + +CLASS_DECLARATION( idEntity, rvAITrigger ) + EVENT( EV_Activate, rvAITrigger::Event_Activate ) + EVENT( AI_AppendFromSpawner, rvAITrigger::Event_AppendFromSpawner ) +END_CLASS + +/* +================ +rvAITrigger::rvAITrigger +================ +*/ +rvAITrigger::rvAITrigger( void ) { +} + +/* +================ +rvAITrigger::Spawn +================ +*/ +void rvAITrigger::Spawn ( void ) { + nextTriggerTime = 0; + wait = SEC2MS ( spawnArgs.GetFloat ( "wait", "-1" ) ); + + conditionDead = spawnArgs.GetBool ( "condition_dead", "0" ); + conditionTether = spawnArgs.GetBool ( "condition_tether", "0" ); + conditionStop = spawnArgs.GetBool ( "condition_stop", "0" ); + + percent = spawnArgs.GetFloat ( "percent", "1" ); + + // Start on by default? + nextTriggerTime = spawnArgs.GetBool ( "start_on", "0" ) ? 0 : -1; + if ( nextTriggerTime == 0 ) { + BecomeActive ( TH_THINK ); + } else { + BecomeInactive ( TH_THINK ); + } + + // If there are no conditions we are done + if ( !conditionDead && !conditionTether && !conditionStop ) { + gameLocal.Warning ( "No conditions specified on ai trigger entity '%s'", GetName ( ) ); + PostEventMS ( &EV_Remove, 0 ); + } +} + +/* +================ +rvAITrigger::Save +================ +*/ +void rvAITrigger::Save ( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( testAI.Num ( ) ); + for ( i = 0; i < testAI.Num(); i ++ ) { + testAI[i].Save ( savefile ); + } + + savefile->WriteInt ( testSpawner.Num ( ) ); + for ( i = 0; i < testSpawner.Num(); i ++ ) { + testSpawner[i].Save ( savefile ); + } + + savefile->WriteBool ( conditionDead ); + savefile->WriteBool ( conditionTether ); + savefile->WriteBool ( conditionStop ); + + savefile->WriteInt ( wait ); + savefile->WriteInt ( nextTriggerTime ); + + savefile->WriteFloat ( percent ); +} + +/* +================ +rvAITrigger::Restore +================ +*/ +void rvAITrigger::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + testAI.Clear ( ); + testAI.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testAI[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + testSpawner.Clear ( ); + testSpawner.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testSpawner[i].Restore ( savefile ); + } + + savefile->ReadBool ( conditionDead ); + savefile->ReadBool ( conditionTether ); + savefile->ReadBool ( conditionStop ); + + savefile->ReadInt ( wait ); + savefile->ReadInt ( nextTriggerTime ); + + savefile->ReadFloat ( percent ); +} + +/* +================ +rvAITrigger::Think +================ +*/ +void rvAITrigger::Think ( void ) { + int v; + + // Only trigger so often + if ( nextTriggerTime == -1 || gameLocal.time < nextTriggerTime ) { + return; + } + + // If we have any attached spawners then our condition cannot be met + if ( testSpawner.Num() ) { + for ( v = 0; v < testSpawner.Num(); v ++ ) { + rvSpawner* spawner = testSpawner[v]; + if ( !spawner ) { + testSpawner.RemoveIndex ( v ); + v--; + continue; + } + return; + } + } + + // If we have no AI we are tracking then wait until we do + if ( !testAI.Num ( ) ) { + return; + } + + int count = 0; + for ( v = 0; v < testAI.Num(); v ++ ) { + idAI* ai = testAI[v]; + if ( !ai ) { + testAI.RemoveIndex ( v ); + v--; + continue; + } + if ( !ai->aifl.dead ) { + if ( conditionDead ) { + continue; + } + if ( conditionTether && !ai->IsWithinTether ( ) ) { + continue; + } + if ( conditionStop && ai->move.fl.moving ) { + continue; + } + } + count++; + } + + // If result is true then we should fire our trigger now + if ( count >= (int) ((float)testAI.Num()*percent) ) { + ActivateTargets ( this ); + + // If only triggering once just remove ourselves now + if ( wait < 0 ) { + nextTriggerTime = -1; + } else { + nextTriggerTime = gameLocal.time + wait; + } + } +} + +/* +================ +rvAITrigger::FindTargets +================ +*/ +void rvAITrigger::FindTargets ( void ) { + int t; + + idEntity::FindTargets ( ); + + for ( t = 0; t < targets.Num(); t ++ ) { + idEntity* ent = targets[t]; + if ( !ent ) { + continue; + } + if ( ent->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + if ( ent->IsType ( rvSpawner::GetClassType ( ) ) ) { + static_cast(ent)->AddCallback ( this, &AI_AppendFromSpawner ); + testSpawner.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + } +} + +/* +================ +rvAITrigger::Event_Activate +================ +*/ +void rvAITrigger::Event_Activate( idEntity *activator ) { + + // Add spawners and ai to the list when they come in + if ( activator && activator->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(activator)) ); + return; + } + + if ( nextTriggerTime == -1 ) { + nextTriggerTime = 0; + BecomeActive ( TH_THINK ); + } else { + nextTriggerTime = -1; + BecomeInactive ( TH_THINK ); + } +} + +/* +================ +rvAITrigger::Event_AppendFromSpawner +================ +*/ +void rvAITrigger::Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ) { + // If its an ai entity being spawned then add it to our test list + if ( spawned && spawned->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(spawned)) ); + } +} + +/* +=============================================================================== + + rvAITether + +=============================================================================== +*/ +const idEventDef EV_TetherSetupLocation ( "tetherSetupLocation" ); +const idEventDef EV_TetherGetLocation ( "tetherGetLocation" ); +CLASS_DECLARATION( idEntity, rvAITether ) + EVENT( EV_Activate, rvAITether::Event_Activate ) + EVENT( EV_TetherGetLocation, rvAITether::Event_TetherGetLocation ) + EVENT( EV_TetherSetupLocation, rvAITether::Event_TetherSetupLocation ) +END_CLASS + +/* +================ +rvAITether::rvAITether +================ +*/ +rvAITether::rvAITether ( void ) { +} + +/* +================ +rvAITether::InitNonPersistentSpawnArgs +================ +*/ +void rvAITether::InitNonPersistentSpawnArgs ( void ) { + tfl.canBreak = spawnArgs.GetBool ( "allowBreak", "1" ); + tfl.autoBreak = spawnArgs.GetBool ( "autoBreak", "0" ); + tfl.forceRun = spawnArgs.GetBool ( "forceRun", "0" ); + tfl.forceWalk = spawnArgs.GetBool ( "forceWalk", "0" ); + tfl.becomeAggressive = spawnArgs.GetBool ( "becomeAggressive", "0" ); + tfl.becomePassive = spawnArgs.GetBool ( "becomePassive", "0" ); + + // Check for both being set + if ( tfl.forceRun && tfl.forceWalk ) { + gameLocal.Warning ( "both forceRun and forceWalk were specified for tether '%s', forceRun will take precedence" ); + tfl.forceWalk = false; + } + if ( tfl.becomeAggressive && tfl.becomePassive ) { + gameLocal.Warning ( "both becomePassive and becomeAggressive were specified for tether '%s', becomeAggressive will take precedence" ); + tfl.becomePassive = false; + } +} + +/* +================ +rvAITether::Spawn +================ +*/ +void rvAITether::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); + + PostEventMS( &EV_TetherSetupLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherSetupLocation +================ +*/ +void rvAITether::Event_TetherSetupLocation( void ) { + //NOTE: we now do this right after spawn so we don't stomp other tether's locations + // if we activate after them and are in the same room as them. + // Dynamically-spawned locations are very, very bad! + + // All pre-existing locations should be placed and spread by now + // Get the location entity we are attached to + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + if ( !location ) { + location = gameLocal.AddLocation ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f, "tether_location" ); + } + } else { + location = NULL; + } + PostEventMS( &EV_TetherGetLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherGetLocation +================ +*/ +void rvAITether::Event_TetherGetLocation( void ) { + //NOW: all locations should be made & spread, get our location (may not be the one + // we added, it could be be the same as another tether if it's in the same room as us) + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } else { + location = NULL; + } +} + +/* +================ +rvAITether::Save +================ +*/ +void rvAITether::Save ( idSaveGame *savefile ) const { + location.Save( savefile ); +} + +/* +================ +rvAITether::Restore +================ +*/ +void rvAITether::Restore ( idRestoreGame *savefile ) { + location.Restore( savefile ); + + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITether::ValidateAAS +================ +*/ +bool rvAITether::ValidateAAS ( idAI* ai ) { + if ( !ai->aas ) { + return false; + } + return true; +} + +/* +================ +rvAITether::ValidateDestination +================ +*/ +bool rvAITether::ValidateDestination ( idAI* ai, const idVec3& dest ) { + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + return true; +} + +/* +================ +rvAITether::ValidateBounds +================ +*/ +bool rvAITether::ValidateBounds ( const idBounds& bounds ) { + return true; +} + +/* +================ +rvAITether::FindGoal +================ +*/ +bool rvAITether::FindGoal ( idAI* ai, aasGoal_t& goal ) { + rvAASFindGoalForTether findGoal ( ai, this ); + if ( !ai->aas->FindNearestGoal( goal, + ai->PointReachableAreaNum( ai->GetPhysics()->GetOrigin() ), + ai->GetPhysics()->GetOrigin(), + GetPhysics()->GetOrigin(), + ai->move.travelFlags, + 0.0f, 0.0f, + NULL, 0, findGoal ) ) { + return false; + } + return true; +} + + +/* +================ +rvAITether::Event_Activate +================ +*/ +void rvAITether::Event_Activate( idEntity *activator ) { + int i; + + //WELL! Turns out designers are binding tethers to movers, so we have to get our location *again* on activation... + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } + + if ( activator && activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + + // Aggressive/Passive stance change? + if ( tfl.becomeAggressive ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } else if ( tfl.becomePassive ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, false ); + } + } + } +} + +/* +================ +rvAITether::DebugDraw +================ +*/ +void rvAITether::DebugDraw ( void ) { + const idBounds& bounds = GetPhysics()->GetAbsBounds(); + gameRenderWorld->DebugBounds ( colorYellow, bounds.Expand ( 8.0f ) ); + gameRenderWorld->DebugArrow ( colorWhite, bounds.GetCenter(), bounds.GetCenter() + GetPhysics()->GetAxis()[0] * 16.0f, 8.0f ); + gameRenderWorld->DrawText( name.c_str(), bounds.GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), bounds.GetCenter() - GetPhysics()->GetGravityNormal() * 5.0f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +} + +/* +=============================================================================== + + rvAITetherBehind + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherBehind ) +END_CLASS + +/* +================ +rvAITetherBehind::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherBehind::InitNonPersistentSpawnArgs ( void ) { + range = spawnArgs.GetFloat ( "range" ); +} + +/* +================ +rvAITetherBehind::Spawn +================ +*/ +void rvAITetherBehind::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::Restore +================ +*/ +void rvAITetherBehind::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::ValidateDestination +================ +*/ +bool rvAITetherBehind::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + + // Make sure we include the move range in the tether + idVec3 origin; + if ( range ) { + origin = GetPhysics()->GetOrigin ( ) + GetPhysics()->GetAxis()[0] * GetOriginReachedRange() - GetPhysics()->GetAxis()[0] * range; + if ( GetPhysics()->GetAxis()[0] * (origin-dest) > 0.0f ) { + return false; + } + } + + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis()[0] * GetOriginReachedRange(); + + // Are we on wrong side of tether? + return ( GetPhysics()->GetAxis()[0] * (origin-dest) ) >= 0.0f; +} + +/* +================ +rvAITetherBehind::ValidateBounds +================ +*/ +bool rvAITetherBehind::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + + idPlane plane; + int side; + plane.SetNormal ( GetPhysics()->GetAxis ( )[0] ); + plane.FitThroughPoint ( GetPhysics()->GetOrigin ( ) ); + side = bounds.PlaneSide ( plane ); + return ( side == PLANESIDE_CROSS || side == PLANESIDE_BACK ); +} + +/* +================ +rvAITetherBehind::DebugDraw +================ +*/ +void rvAITetherBehind::DebugDraw ( void ) { + idVec3 dir; + + rvAITether::DebugDraw ( ); + + dir = GetPhysics()->GetGravityNormal ( ).Cross ( GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorPink, + GetPhysics()->GetOrigin() - dir * 1024.0f, + GetPhysics()->GetOrigin() + dir * 1024.0f ); + + if ( range ) { + idVec3 origin; + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis ( )[0] * range; + gameRenderWorld->DebugArrow ( colorPink, GetPhysics()->GetOrigin(), origin, 5.0f ); + gameRenderWorld->DebugLine ( colorPink, + origin - dir * 1024.0f, + origin + dir * 1024.0f ); + } +} + +/* +=============================================================================== + + rvAITetherRadius + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherRadius ) +END_CLASS + +/* +================ +rvAITetherRadius::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherRadius::InitNonPersistentSpawnArgs ( void ) { + float radius; + if ( !spawnArgs.GetFloat ( "tetherRadius", "0", radius ) ) { + radius = spawnArgs.GetFloat ( "radius", "128" ); + } + radiusSqr = Square ( radius ); +} + +/* +================ +rvAITetherRadius::Spawn +================ +*/ +void rvAITetherRadius::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::Restore +================ +*/ +void rvAITetherRadius::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::ValidateDestination +================ +*/ +bool rvAITetherRadius::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + // Are we within tether radius? + return ((dest - GetPhysics()->GetOrigin()).LengthSqr ( ) < radiusSqr - Square ( ai->move.range ) ); +} + + +/* +================ +rvAITetherRadius::ValidateBounds +================ +*/ +bool rvAITetherRadius::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + return ( Square ( bounds.ShortestDistance ( GetPhysics()->GetOrigin ( ) ) ) < radiusSqr ); +} + +/* +================ +rvAITetherRadius::DebugDraw +================ +*/ +void rvAITetherRadius::DebugDraw ( void ) { + rvAITether::DebugDraw ( ); + gameRenderWorld->DebugCircle( colorPink, GetPhysics()->GetOrigin(), GetPhysics()->GetGravityNormal(), idMath::Sqrt(radiusSqr), 25 ); +} + + +/* +=============================================================================== + + rvAITetherClear + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherClear ) +END_CLASS + + +/* +=============================================================================== + + rvAIBecomePassive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomePassive ) + EVENT( EV_Activate, rvAIBecomePassive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomePassive::Event_Activate +================ +*/ +void rvAIBecomePassive::Event_Activate( idEntity *activator ) { + int i; + bool ignoreEnemies; + + ignoreEnemies = spawnArgs.GetBool ( "ignoreEnemies", "1" ); + + // All targeted AI will become passive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, ignoreEnemies ); + } + } +} + +/* +=============================================================================== + + rvAIBecomeAggressive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomeAggressive ) + EVENT( EV_Activate, rvAIBecomeAggressive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomeAggressive::Event_Activate +================ +*/ +void rvAIBecomeAggressive::Event_Activate( idEntity *activator ) { + int i; + + // All targetted AI will become aggressive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } + } +} diff --git a/source/game/ai/AI_Util.h b/source/game/ai/AI_Util.h new file mode 100644 index 0000000..0a7627b --- /dev/null +++ b/source/game/ai/AI_Util.h @@ -0,0 +1,232 @@ +/* +================ + +AI_Util.h + +================ +*/ + +#ifndef __AI_UTIL__ +#define __AI_UTIL__ + +const float AI_TETHER_MINRANGE = 8.0f; + +/* +=============================================================================== + rvAITrigger +=============================================================================== +*/ + +class rvAITrigger : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITrigger ); + + rvAITrigger ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void Think ( void ); + + virtual void FindTargets ( void ); + +protected: + + idList< idEntityPtr > testAI; + idList< idEntityPtr > testSpawner; + + bool conditionDead; + bool conditionTether; + bool conditionStop; + + int wait; + int nextTriggerTime; + + float percent; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_PostRestore ( void ); + + void Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ); +}; + +/* +=============================================================================== + rvAITether +=============================================================================== +*/ + +class rvAITether : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITether ); + + rvAITether ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateAAS ( idAI* ai ); + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + + virtual bool FindGoal ( idAI* ai, aasGoal_t& goal ); + virtual float GetOriginReachedRange ( void ) {return AI_TETHER_MINRANGE;} + + virtual void DebugDraw ( void ); + + bool CanBreak ( void ) const; + bool IsAutoBreak ( void ) const; + + idList areaNum; + + bool IsWalkForced ( void ) const; + bool IsRunForced ( void ) const; + +protected: + + idEntityPtr location; + + struct tetherFlags_s { + bool canBreak :1; // Temporarily break when enemy is within tether + bool autoBreak :1; // Break when the ai gets within the tether + bool forceRun :1; // Alwasy run when heading towards tether + bool forceWalk :1; // Alwasy walk when heading towards tether + bool becomeAggressive :1; // + bool becomePassive :1; + } tfl; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_TetherSetupLocation ( void ); + void Event_TetherGetLocation ( void ); +}; + +ID_INLINE bool rvAITether::CanBreak ( void ) const { + return tfl.canBreak; +} + +ID_INLINE bool rvAITether::IsWalkForced ( void ) const { + return tfl.forceWalk; +} + +ID_INLINE bool rvAITether::IsRunForced ( void ) const { + return tfl.forceRun; +} + +ID_INLINE bool rvAITether::IsAutoBreak ( void ) const { + return tfl.autoBreak; +} + +/* +=============================================================================== + rvAITetherBehind +=============================================================================== +*/ + +class rvAITetherBehind : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherBehind ); + + rvAITetherBehind( void ) { range = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + +protected: + + float range; +}; + +/* +=============================================================================== + rvAITetherRadius +=============================================================================== +*/ + +class rvAITetherRadius : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherRadius ); + + rvAITetherRadius( void ) { radiusSqr = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + + /* + virtual float GetOriginReachedRange ( void ) + { + float rad = sqrt(radiusSqr); + float halfRad = rad/2.0f; + if ( rad < AI_TETHER_MINRANGE ) + { + return rad; + } + return (halfRad" ); +const idEventDef AI_RealKill ( "" ); +const idEventDef AI_Kill ( "kill" ); +const idEventDef AI_RemoveUpdateSpawner ( "removeUpdateSpawner" ); +const idEventDef AI_AllowHiddenMovement ( "allowHiddenMovement", "d" ); +const idEventDef AI_Speak ( "speak", "s" ); +const idEventDef AI_SpeakRandom ( "speakRandom", "s" ); +const idEventDef AI_IsSpeaking ( "isSpeaking", NULL, 'f' ); +const idEventDef AI_IsTethered ( "isTethered", NULL, 'f' ); +const idEventDef AI_IsWithinTether ( "isWithinTether", NULL, 'f' ); +const idEventDef AI_LaunchMissile ( "launchMissile", "vv", 'e' ); +const idEventDef AI_AttackMelee ( "attackMelee", "s", 'd' ); +const idEventDef AI_DirectDamage ( "directDamage", "es" ); +const idEventDef AI_RadiusDamageFromJoint ( "radiusDamageFromJoint", "ss" ); +const idEventDef AI_MeleeAttackToJoint ( "meleeAttackToJoint", "ss", 'd' ); +const idEventDef AI_CanBecomeSolid ( "canBecomeSolid", NULL, 'f' ); +const idEventDef AI_BecomeSolid ( "becomeSolid" ); +const idEventDef AI_BecomeRagdoll ( "becomeRagdoll", NULL, 'd' ); +const idEventDef AI_BecomePassive ( "becomePassive", "d" ); +const idEventDef AI_BecomeAggressive ( "becomeAggressive" ); +const idEventDef AI_StopRagdoll ( "stopRagdoll" ); +const idEventDef AI_FaceEnemy ( "faceEnemy" ); +const idEventDef AI_FaceEntity ( "faceEntity", "E" ); + +//jshepard +const idEventDef AI_FindEnemy ( "findEnemy", "f", 'e'); + +void idAI::Event_Activate( idEntity *activator ) { Activate( activator );} +void idAI::Event_Touch( idEntity *other, trace_t *trace ) { OnTouch( other, trace ); } +void idAI::Event_SetEnemy( idEntity *ent ) { if ( !ent ) ClearEnemy(); else SetEnemy( ent );} +void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { DirectDamage( damageDefName, damageTarget ); } +void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { RadiusDamageFromJoint( jointname, damageDefName ); } +void idAI::Event_CanBecomeSolid( void ) { idThread::ReturnFloat( CanBecomeSolid() ); } +void idAI::Event_BecomeSolid( void ) { BecomeSolid(); } +void idAI::Event_BecomeNonSolid( void ) { BecomeNonSolid(); } +void idAI::Event_BecomeRagdoll( void ) { idThread::ReturnInt( StartRagdoll() ); } +void idAI::Event_StopRagdoll( void ) { StopRagdoll(); SetPhysics( &physicsObj ); } +void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) aifl.dead = false; else aifl.dead = true; } +void idAI::Event_FaceEnemy( void ) { FaceEnemy(); } +void idAI::Event_FaceEntity( idEntity *ent ) { FaceEntity( ent ); } +void idAI::Event_SetTalkState( int state ) { SetTalkState ( (talkState_t)state ); } +void idAI::Event_Speak( const char *speechDecl ) { Speak( speechDecl ); } +void idAI::Event_SpeakRandom( const char *speechDecl ) { Speak( speechDecl, true ); } +void idAI::Event_GetLeader( void ) { idThread::ReturnEntity( leader ); } +void idAI::Event_SetLeader( idEntity* ent ) { SetLeader ( ent ); } +void idAI::Event_GetEnemy( void ) { idThread::ReturnEntity( enemy.ent ); } +void idAI::Event_TakeDamage( float takeDamage ) { fl.takedamage = ( takeDamage ) ? true : false; } +void idAI::Event_SetUndying( float setUndying ) { aifl.undying = ( setUndying ) ? true : false; } + + +void idAI::Event_IsSpeaking ( void ) { idThread::ReturnFloat ( IsSpeaking ( ) ); } +void idAI::Event_IsTethered ( void ) { idThread::ReturnFloat ( IsTethered ( ) ); } +void idAI::Event_IsWithinTether ( void ) { idThread::ReturnFloat ( IsWithinTether ( ) ); } +void idAI::Event_IsMoving ( void ) { idThread::ReturnFloat ( move.fl.moving ); } + +CLASS_DECLARATION( idActor, idAI ) + EVENT( EV_Activate, idAI::Event_Activate ) + EVENT( EV_Touch, idAI::Event_Touch ) + + // Enable / Disable + EVENT( AI_EnableClip, idAI::Event_EnableClip ) + EVENT( AI_DisableClip, idAI::Event_DisableClip ) + EVENT( AI_EnableGravity, idAI::Event_EnableGravity ) + EVENT( AI_DisableGravity, idAI::Event_DisableGravity ) + EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush ) + EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush ) + EVENT( AI_EnableDamage, idAI::Event_EnableDamage ) + EVENT( AI_DisableDamage, idAI::Event_DisableDamage ) + EVENT( AI_EnablePain, idAI::Event_EnablePain ) + EVENT( AI_DisablePain, idAI::Event_DisablePain ) + EVENT( AI_EnableTarget, idAI::Event_EnableTarget ) + EVENT( AI_DisableTarget, idAI::Event_DisableTarget ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_SetUndying, idAI::Event_SetUndying ) + EVENT( AI_EnableAutoBlink, idAI::Event_EnableAutoBlink ) + EVENT( AI_DisableAutoBlink, idAI::Event_DisableAutoBlink ) + + // Scripted sequences + EVENT( AI_ScriptedMove, idAI::Event_ScriptedMove ) + EVENT( AI_ScriptedFace, idAI::Event_ScriptedFace ) + EVENT( AI_ScriptedAnim, idAI::Event_ScriptedAnim ) + EVENT( AI_ScriptedAction, idAI::Event_ScriptedAction ) + EVENT( AI_ScriptedPlaybackMove, idAI::Event_ScriptedPlaybackMove ) + EVENT( AI_ScriptedPlaybackAim, idAI::Event_ScriptedPlaybackAim ) + EVENT( AI_ScriptedDone, idAI::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, idAI::Event_ScriptedStop ) + EVENT( AI_ScriptedJumpDown, idAI::Event_ScriptedJumpDown ) + + // Get / Set + EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) + EVENT( AI_SetLeader, idAI::Event_SetLeader ) + EVENT( AI_GetLeader, idAI::Event_GetLeader ) + EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) + EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) + EVENT( EV_GetAngles, idAI::Event_GetAngles ) + EVENT( EV_SetAngles, idAI::Event_SetAngles ) + EVENT( AI_SetScript, idAI::Event_SetScript ) + EVENT( AI_SetMoveSpeed, idAI::Event_SetMoveSpeed ) + EVENT( AI_SetPassivePrefix, idAI::Event_SetPassivePrefix ) + + // Misc + EVENT( AI_Attack, idAI::Event_Attack ) + EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) + + EVENT( AI_LookAt, idAI::Event_LookAt ) + EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) + EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) + EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid ) + EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid ) + EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid ) + EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll ) + EVENT( AI_BecomePassive, idAI::Event_BecomePassive ) + EVENT( AI_BecomeAggressive, idAI::Event_BecomeAggressive ) + EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll ) + EVENT( AI_SetHealth, idAI::Event_SetHealth ) + EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) + EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) + EVENT( AI_StopThinking, idAI::Event_StopThinking ) + EVENT( AI_LockEnemyOrigin, idAI::Event_LockEnemyOrigin ) + EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) + EVENT( AI_RealKill, idAI::Event_RealKill ) + EVENT( AI_Kill, idAI::Event_Kill ) + EVENT( AI_RemoveUpdateSpawner, idAI::Event_RemoveUpdateSpawner ) + EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) + EVENT( AI_Speak, idAI::Event_Speak ) + EVENT( AI_SpeakRandom, idAI::Event_SpeakRandom ) + EVENT( AI_IsSpeaking, idAI::Event_IsSpeaking ) + EVENT( AI_IsTethered, idAI::Event_IsTethered ) + EVENT( AI_IsWithinTether, idAI::Event_IsWithinTether ) + EVENT( EV_IsMoving, idAI::Event_IsMoving ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) + EVENT( EV_SetKey, idAI::Event_SetKey ) + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + EVENT( EV_PostSpawn, idAI::Event_PostSpawn ) + // RAVEN END +END_CLASS + +/* +===================== +idAI::Event_PredictEnemyPos +===================== +*/ +void idAI::Event_PredictEnemyPos( float time ) { + predictedPath_t path; + idEntity* enemyEnt = enemy.ent; + + // if no enemy set + if ( !enemyEnt ) { + idThread::ReturnVector( physicsObj.GetOrigin() ); + return; + } + + // predict the enemy movement + idAI::PredictPath( enemyEnt, aas, enemy.lastKnownPosition, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnVector( path.endPos ); +} + +/* +===================== +idAI::Event_TestAnimMoveTowardEnemy +===================== +*/ +void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + float yaw; + idVec3 delta; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + yaw = delta.ToYaw(); + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestAnimMove +===================== +*/ +void idAI::Event_TestAnimMove( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + int animLen; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + animLen = animator.AnimLength( anim ); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMoveToPosition +===================== +*/ +void idAI::Event_TestMoveToPosition( const idVec3 &position ) { + predictedPath_t path; + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, gameLocal.msec ); + if ( path.endEvent ) { + gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMeleeAttack +===================== +*/ +void idAI::Event_TestMeleeAttack( void ) { + bool result = TestMelee(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_TestAnimAttack +===================== +*/ +void idAI::Event_TestAnimAttack( const char *animname ) { + int anim; + predictedPath_t path; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.ent ) ); +} + +/* +===================== +idAI::Event_LockEnemyOrigin +===================== +*/ +void idAI::Event_LockEnemyOrigin ( void ) { + enemy.fl.lockOrigin = true; +} + +/* +===================== +idAI::Event_StopThinking +===================== +*/ +void idAI::Event_StopThinking( void ) { + BecomeInactive( TH_THINK ); + idThread *thread = idThread::CurrentThread(); + if ( thread ) { + thread->DoneProcessing(); + } +} + +/* +===================== +idAI::Event_JumpFrame +===================== +*/ +void idAI::Event_JumpFrame( void ) { + aifl.jump = true; +} + +/* +===================== +idAI::Event_EnableClip +===================== +*/ +void idAI::Event_EnableClip( void ) { + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + Event_EnableGravity ( ); +} + +/* +===================== +idAI::Event_DisableClip +===================== +*/ +void idAI::Event_DisableClip( void ) { + physicsObj.SetClipMask( 0 ); + Event_DisableGravity ( ); +} + +/* +===================== +idAI::Event_EnableGravity +===================== +*/ +void idAI::Event_EnableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, false ); +} + +/* +===================== +idAI::Event_DisableGravity +===================== +*/ +void idAI::Event_DisableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, true ); +} + +/* +===================== +idAI::Event_EnableAFPush +===================== +*/ +void idAI::Event_EnableAFPush( void ) { + move.fl.allowPushMovables = true; +} + +/* +===================== +idAI::Event_DisableAFPush +===================== +*/ +void idAI::Event_DisableAFPush( void ) { + move.fl.allowPushMovables = false; +} + +/* +===================== +idAI::Event_EnableDamage +===================== +*/ +void idAI::Event_EnableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, true ); +} + +/* +===================== +idAI::Event_DisableDamage +===================== +*/ +void idAI::Event_DisableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, false ); +} + +/* +=============== +idAI::Event_DisablePain +=============== +*/ +void idAI::Event_DisablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, true ); +} + +/* +=============== +idAI::Event_EnablePain +=============== +*/ +void idAI::Event_EnablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, false ); +} + +/* +=============== +idAI::Event_EnableTarget +=============== +*/ +void idAI::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +=============== +idAI::Event_DisableTarget +=============== +*/ +void idAI::Event_DisableTarget ( void ) { + fl.notarget = true; +} + + +/* +===================== +idAI::Event_EnableAutoBlink +===================== +*/ +void idAI::Event_EnableAutoBlink( void ) { + fl.allowAutoBlink = true; +} + +/* +===================== +idAI::Event_DisableAutoBlink +===================== +*/ +void idAI::Event_DisableAutoBlink( void ) { + fl.allowAutoBlink = false; +} + +/* +===================== +idAI::Event_BecomeAggressive +===================== +*/ +void idAI::Event_BecomeAggressive ( void ) { + combat.fl.ignoreEnemies = false; + combat.fl.aware = true; + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_BecomePassive +===================== +*/ +void idAI::Event_BecomePassive ( int ignoreEnemies ) { + combat.fl.ignoreEnemies = (ignoreEnemies != 0); + combat.fl.aware = false; + SetEnemy ( NULL ); + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_LookAt +===================== +*/ +void idAI::Event_LookAt ( idEntity* lookAt ) { + lookTarget = lookAt; +} + +/* +===================== +idAI::LookAtEntity +===================== +*/ +void idAI::LookAtEntity( idEntity *ent, float duration ) { + if ( ent == this ) { + ent = NULL; + } + + if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = ent; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +================ +idAI::Event_ThrowMoveable +================ +*/ +void idAI::Event_ThrowMoveable( void ) { + idEntity *ent; + idEntity *moveable = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + moveable = ent; + break; + } + } + if ( moveable ) { + moveable->Unbind(); + moveable->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_ThrowAF +================ +*/ +void idAI::Event_ThrowAF( void ) { + idEntity *ent; + idEntity *af = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = ent; + break; + } + } + if ( af ) { + af->Unbind(); + af->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_SetAngles +================ +*/ +void idAI::Event_SetAngles( idAngles const &ang ) { + move.current_yaw = ang.yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); +} + +/* +================ +idAI::Event_GetAngles +================ +*/ +void idAI::Event_GetAngles( void ) { + idThread::ReturnVector( idVec3( 0.0f, move.current_yaw, 0.0f ) ); +} + +/* +================ +idAI::Event_RealKill +================ +*/ +void idAI::Event_RealKill( void ) { + health = 0; + + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + Killed( this, this, 0, vec3_zero, INVALID_JOINT ); +} + +/* +================ +idAI::Event_Kill +================ +*/ +void idAI::Event_Kill( void ) { + PostEventMS( &AI_RealKill, 0 ); +} + +/* +================ +idAI::Event_RemoveUpdateSpawner +================ +*/ +void idAI::Event_RemoveUpdateSpawner( void ) { + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + PostEventMS( &EV_Remove, 0 ); +} +/* +===================== +idAI::Event_FindActorsInBounds +===================== +*/ +void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedEntities = gameLocal.EntitiesTouchingBounds( this, idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++ ) { + ent = entityList[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_ClosestReachableEnemyOfEntity +===================== +*/ +void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) { + idActor *actor; + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + int areaNum; + int enemyAreaNum; + aasPath_t path; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !team_mate->IsType( idActor::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); + } + + actor = static_cast( team_mate ); + + const idVec3 &origin = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( origin ); + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - origin; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin(); + enemyAreaNum = PointReachableAreaNum( enemyPos ); + if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + } + + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_EntityInAttackCone +===================== +*/ +void idAI::Event_EntityInAttackCone( idEntity *ent ) { + float attack_cone; + idVec3 delta; + float yaw; + float relYaw; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + delta = ent->GetPhysics()->GetOrigin() - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + yaw = delta.ToYaw(); + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + relYaw = idMath::AngleNormalize180( move.ideal_yaw - yaw ); + if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idAI::Event_CanReachPosition +================ +*/ +void idAI::Event_CanReachPosition( const idVec3 &pos ) { + aasPath_t path; + int toAreaNum; + int areaNum; + + toAreaNum = PointReachableAreaNum( pos ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEntity +================ +*/ +void idAI::Event_CanReachEntity( idEntity *ent ) { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + toAreaNum = PointReachableAreaNum( pos ); + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEnemy +================ +*/ +void idAI::Event_CanReachEnemy( void ) { + aasPath_t path; + int toAreaNum = 0; + int areaNum; + idVec3 pos; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if( enemyEnt->IsType( idActor::GetClassType() ) ){ + idActor *enemyAct = static_cast( enemyEnt ); + if ( enemyAct->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + enemyAct->GetAASLocation( aas, pos, toAreaNum ); + } + } else { + pos = enemyEnt->GetPhysics()->GetOrigin(); + toAreaNum = PointReachableAreaNum( pos ); + } + + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_GetReachableEntityPosition +================ +*/ +void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { + int toAreaNum; + idVec3 pos; + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + if ( aas ) { + toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( toAreaNum, pos ); + } + + idThread::ReturnVector( pos ); +} + +/* +================ +idAI::Event_ScriptedMove +================ +*/ +void idAI::Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + ScriptedMove ( destEnt, minDist, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedFace +================ +*/ +void idAI::Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + ScriptedFace ( faceEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAnim +================ +*/ +void idAI::Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + ScriptedAnim ( animname, blendFrames, loop, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAction +================ +*/ +void idAI::Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + ScriptedAction ( actionEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedPlaybackMove +================ +*/ +void idAI::Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackMove ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedPlaybackAim +================ +*/ +void idAI::Event_ScriptedPlaybackAim( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackAim ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedDone +================ +*/ +void idAI::Event_ScriptedDone ( void ) { + idThread::ReturnFloat ( !aifl.scripted ); +} + +/* +================ +idAI::Event_ScriptedStop +================ +*/ +void idAI::Event_ScriptedStop ( void ) { + ScriptedStop ( ); +} + +/* +================ +idAI::Event_AllowHiddenMovement +================ +*/ +void idAI::Event_AllowHiddenMovement( int enable ) { + move.fl.allowHiddenMove = ( enable != 0 ); +} + +/* +================ +idAI::Event_SetScript +================ +*/ +void idAI::Event_SetScript ( const char* scriptName, const char* funcName ) { + SetScript ( scriptName, funcName ); +} + +/* +================ +idAI::Event_SetMoveSpeed +================ +*/ +void idAI::Event_SetMoveSpeed ( int speed ) { + switch ( speed ) { + case AIMOVESPEED_DEFAULT: + move.fl.noRun = false; + move.fl.noWalk = false; + break; + + case AIMOVESPEED_RUN: + move.fl.noRun = false; + move.fl.noWalk = true; + break; + + case AIMOVESPEED_WALK: + move.fl.noRun = true; + move.fl.noWalk = false; + break; + } +} + +/* +================ +idAI::Event_SetPassivePrefix +================ +*/ +void idAI::Event_SetPassivePrefix ( const char* prefix ) { + SetPassivePrefix ( prefix ); +} + +/* +================ +idAI::Event_Attack +================ +*/ +void idAI::Event_Attack ( const char* attackName, const char* jointName ) { + Attack ( attackName, animator.GetJointHandle ( jointName ), enemy.ent ); // , physicsObj.GetPushedLinearVelocity ( ) ); +} + +/* +================ +idAI::Event_AttackMelee +================ +*/ +void idAI::Event_AttackMelee( const char* meleeName ) { + const idDict* meleeDict; + meleeDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", meleeName ) ), false ); + if ( !meleeDict ) { + gameLocal.Error ( "missing meleeDef '%s' for ai entity '%s'", meleeName, GetName() ); + } + AttackMelee ( meleeName, meleeDict ); +} + +/* +================ +idAI::Event_ScriptedJumpDown +================ +*/ +void idAI::Event_ScriptedJumpDown( float yaw ) { + if ( animator.HasAnim( "jumpdown_start" ) ) + { + aifl.scripted = true; + move.ideal_yaw = yaw; + SetState( "State_ScriptedJumpDown" ); + } +} + +/* +================ +idAI::Event_FindEnemy +================ +*/ +void idAI::Event_FindEnemy( float distSqr ) { + idThread::ReturnEntity ( FindEnemy( false, 1, distSqr )); +} + +/* +================ +idAI::Event_SetKey +================ +*/ +void idAI::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); + + OnSetKey ( key, value ); +} + +/* +================ +idAI::Event_PostSpawn +================ +*/ +void idAI::Event_PostSpawn( void ) { + // RAVEN BEGIN + // twhitaker: difficulty levels + if ( team == TEAM_MARINE ) { + //health /= 1.0f + gameLocal.GetDifficultyModifier( ); + + //buddies are a little more healthy on hard & nightmare since the baddies deal so much more damage + switch ( g_skill.GetInteger() ) { + case 3: + health *= 1.4f; + break; + case 2: + health *= 1.2f; + break; + case 0: + health *= 1.2f; + break; + case 1: + default: + break; + } + } else { + health *= 1.0f + gameLocal.GetDifficultyModifier( ); + } + // RAVEN END +} diff --git a/source/game/ai/AI_pathing.cpp b/source/game/ai/AI_pathing.cpp new file mode 100644 index 0000000..2413a69 --- /dev/null +++ b/source/game/ai/AI_pathing.cpp @@ -0,0 +1,1626 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +// nmckenzie: +#include "AI.h" +// RAVEN END + +/* +=============================================================================== + + Dynamic Obstacle Avoidance + + - assumes the AI lives inside a bounding box aligned with the gravity direction + - obstacles in proximity of the AI are gathered + - if obstacles are found the AAS walls are also considered as obstacles + - every obstacle is represented by an oriented bounding box (OBB) + - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction + - the 2D windings of the projections are expanded for the AI bbox + - a path tree is build using clockwise and counter clockwise edge walks along the winding edges + - the path tree is pruned and optimized + - the shortest path is chosen for navigation + +=============================================================================== +*/ + +const float MAX_OBSTACLE_RADIUS = 256.0f; +const float PUSH_OUTSIDE_OBSTACLES = 0.5f; +const float CLIP_BOUNDS_EPSILON = 10.0f; +const int MAX_AAS_WALL_EDGES = 256; +const int MAX_OBSTACLES = 256; +const int MAX_PATH_NODES = 256; +const int MAX_OBSTACLE_PATH = 64; + +typedef struct obstacle_s { + idVec2 bounds[2]; + idWinding2D winding; + idEntity * entity; + bool fakePlayerForwardObstacle; +} obstacle_t; + +typedef struct pathNode_s { + int dir; + idVec2 pos; + idVec2 delta; + float dist; + int obstacle; + int edgeNum; + int numNodes; + struct pathNode_s * parent; + struct pathNode_s * children[2]; + struct pathNode_s * next; + void Init(); +} pathNode_t; + +void pathNode_s::Init() { + dir = 0; + pos.Zero(); + delta.Zero(); + obstacle = -1; + edgeNum = -1; + numNodes = 0; + parent = children[0] = children[1] = next = NULL; +} + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc pathNodeAllocator; +// RAVEN END + +/* +============ +LineIntersectsPath +============ +*/ +bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) { + float d0, d1, d2, d3; + idVec3 plane1, plane2; + + plane1 = idWinding2D::Plane2DFromPoints( start, end ); + d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z; + while( node->parent ) { + d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z; + if ( FLOATSIGNBITSET( d0 ) ^ FLOATSIGNBITSET( d1 ) ) { + plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos ); + d2 = plane2.x * start.x + plane2.y * start.y + plane2.z; + d3 = plane2.x * end.x + plane2.y * end.y + plane2.z; + if ( FLOATSIGNBITSET( d2 ) ^ FLOATSIGNBITSET( d3 ) ) { + return true; + } + } + d0 = d1; + node = node->parent; + } + return false; +} + +/* +============ +PointInsideObstacle +============ +*/ +int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point, bool skipFakePlayerForwardObstacles = false ) { + int i; + + for ( i = 0; i < numObstacles; i++ ) { + if ( skipFakePlayerForwardObstacles && obstacles[i].fakePlayerForwardObstacle ) { + //don't care if we're inside of these + continue; + } + + const idVec2 *bounds = obstacles[i].bounds; + if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) { + continue; + } + + if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) { + continue; + } + + return i; + } + + return -1; +} + +/* +============ +GetPointOutsideObstacles +============ +*/ +void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) { + int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2]; + float d, bestd, scale[2]; + idVec3 plane, bestPlane; + idVec2 newPoint, dir, bestPoint; + int *queue; + bool *obstacleVisited; + idWinding2D w1, w2; + + if ( obstacle ) { + *obstacle = -1; + } + if ( edgeNum ) { + *edgeNum = -1; + } + + bestObstacle = PointInsideObstacle( obstacles, numObstacles, point ); + if ( bestObstacle == -1 ) { + return; + } + + const idWinding2D &w = obstacles[bestObstacle].winding; + bestd = idMath::INFINITY; + bestEdgeNum = 0; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d < bestd ) { + bestd = d; + bestPlane = plane; + bestEdgeNum = i; + } + // if this is a wall always try to pop out at the first edge + if ( obstacles[bestObstacle].entity == NULL ) { + break; + } + } + + newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2(); + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + point = newPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + + queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) ); + obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) ); + + queueStart = 0; + queueEnd = 1; + queue[0] = bestObstacle; + + memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) ); + obstacleVisited[bestObstacle] = true; + + bestd = idMath::INFINITY; + for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) { + w1 = obstacles[i].winding; + w1.Expand( PUSH_OUTSIDE_OBSTACLES ); + + for ( j = 0; j < numObstacles; j++ ) { + // if the obstacle has been visited already + if ( obstacleVisited[j] ) { + continue; + } + // if the bounds do not intersect + if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y || + obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + + queue[queueEnd++] = j; + obstacleVisited[j] = true; + + w2 = obstacles[j].winding; + w2.Expand( 0.2f ); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + dir = w1[(k+1)%w1.GetNumPoints()] - w1[k]; + if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) { + continue; + } + for ( n = 0; n < 2; n++ ) { + newPoint = w1[k] + scale[n] * dir; + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + d = ( newPoint - point ).LengthSqr(); + if ( d < bestd ) { + bestd = d; + bestPoint = newPoint; + bestEdgeNum = edgeNums[n]; + bestObstacle = j; + } + } + } + } + } + + if ( bestd < idMath::INFINITY ) { + point = bestPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + } + gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); +} + +/* +============ +GetFirstBlockingObstacle +============ +*/ +bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) { + int i, edgeNums[2]; + float dist, scale1, scale2; + idVec2 bounds[2]; + + // get bounds for the current movement delta + bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(delta.x)].x += delta.x; + bounds[FLOATSIGNBITNOTSET(delta.y)].y += delta.y; + + // test for obstacles blocking the path + blockingScale = idMath::INFINITY; + dist = delta.Length(); + for ( i = 0; i < numObstacles; i++ ) { + if ( i == skipObstacle ) { + continue; + } + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) { + if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) { + blockingScale = scale1; + blockingObstacle = i; + blockingEdgeNum = edgeNums[0]; + } + } + } + return ( blockingScale < 1.0f ); +} + +/* +============ +GetObstacles +============ +*/ +int GetObstacles( const idActor* owner, const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) { + int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum, numIterations, extrude; + int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2]; + float stepHeight, headHeight, blockingScale, min, max; + idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd; + idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal; + idVec2 obDelta; + idPhysics *obPhys; + idBox box; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 extrudeVec; + idPlayer* player = gameLocal.GetLocalPlayer(); + bool extrudePlayer = false; + + if ( player && owner->team == player->team ) { + if ( player->HasEnemies() + || (player->weapon && player->weapon->lastAttack > gameLocal.GetTime() - 2000) + || (owner->IsType( idAI::GetClassType() ) && ((idAI*)owner)->GetEnemy()) ) { + + extrudeVec = player->firstPersonViewAxis[0]; + extrudeVec[2] = 0; + extrudeVec.Normalize(); + idVec3 myMoveDir = (seekPos-startPos); + /* + myMoveDir.z = 0; + if ( myMoveDir * extrudeVec > 200.0f ) + {//I'm already moving out away from him in the direction he's moving... (he's following me?) + } + else + {//we are heading into his line of sight, stop + */ + extrudePlayer = true; + extrudeVec *= (player->GetPhysics()->GetBounds()[1][0]-player->GetPhysics()->GetBounds()[0][0]); + //} + //FIXME: keep moving if already inside this cone? Otherwise, we'll stop in our tracks? + } + } + + numObstacles = 0; + + seekDelta = seekPos - startPos; + expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + + physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight ); + stepHeight += aas->GetSettings()->maxStepHeight; + + // clip bounds for the obstacle search space + clipBounds[0] = clipBounds[1] = startPos; + clipBounds.AddPoint( seekPos ); + clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS ); + clipMask = physics->GetClipMask(); + + // find all obstacles touching the clip bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( owner, clipBounds, clipMask, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( obEnt->IsType( idActor::GetClassType() ) ) { +// RAVEN END + obPhys = obEnt->GetPhysics(); + // ignore myself, my enemy, and dead bodies + if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) { + continue; + } + // if the actor is moving + // cdr: Alternate Routes Bug + // idVec3 v1 = obPhys->GetLinearVelocity(); + // if ( v1.LengthSqr() > Square( 10.0f ) ) { + // idVec3 v2 = physics->GetLinearVelocity(); + // if ( v2.LengthSqr() > Square( 10.0f ) ) { + // // if moving in about the same direction + // if ( v1 * v2 > 0.0f ) { + // continue; + // } + // } + // } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +// cdr: Ignore Pushable objects + } else if (obEnt->GetPhysics()->IsPushable()) { + continue; + } else if ( obEnt->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // moveables are considered obstacles + } else { + // ignore everything else + continue; + } + + // check if we can step over the object + clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max ); + if ( max < stepHeight || min > headHeight ) { + // can step over this one + continue; + } + + numIterations = 1; + if ( extrudePlayer && obEnt == player ) + {//do magic + numIterations = 3; + } + for ( extrude = 0; extrude < numIterations; extrude++ ) { + // project a box containing the obstacle onto the floor plane + box = idBox( clipModel->GetBounds(), extrude?clipModel->GetOrigin()+(extrude*extrudeVec):clipModel->GetOrigin(), clipModel->GetAxis() ); + numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts ); + + // create a 2D winding for the obstacle; + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + for ( j = 0; j < numVerts; j++ ) { + obstacle.winding.AddPoint( silVerts[j].ToVec2() ); + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( j = 0; j < numVerts; j++ ) { + silVerts[j].z = startPos.z; + } + for ( j = 0; j < numVerts; j++ ) { + gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 ); + } + } + + // expand the 2D winding for collision with a 2D box + obstacle.winding.ExpandForAxialBox( expBounds ); + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = obEnt; + if ( extrudePlayer && obEnt == player && extrude > 0 ) { + //don't care if we're inside one of these, because that would just make us stop where we are, anyway! + obstacle.fakePlayerForwardObstacle = true; + } else { + obstacle.fakePlayerForwardObstacle = false; + } + } + } + + // if there are no dynamic obstacles the path should be through valid AAS space + if ( numObstacles == 0 ) { + return 0; + } + + // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space + if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2(), true ) == -1 ) { + if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) { + return 0; + } + } + + // create obstacles for AAS walls + if ( aas ) { + float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f; + + numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES ); + aas->SortWallEdges( wallEdges, numWallEdges ); + + lastVerts[0] = lastVerts[1] = 0; + lastEdgeNormal.Zero(); + nextVerts[0] = nextVerts[1] = 0; + for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) { + aas->GetEdge( wallEdges[i], start, end ); + aas->GetEdgeVertexNumbers( wallEdges[i], verts ); + edgeDir = end.ToVec2() - start.ToVec2(); + edgeDir.Normalize(); + edgeNormal.x = edgeDir.y; + edgeNormal.y = -edgeDir.x; + if ( i < numWallEdges-1 ) { + aas->GetEdge( wallEdges[i+1], nextStart, nextEnd ); + aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts ); + nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2(); + nextEdgeDir.Normalize(); + nextEdgeNormal.x = nextEdgeDir.y; + nextEdgeNormal.y = -nextEdgeDir.x; + } + + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + obstacle.winding.AddPoint( end.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize ); + obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize ); + if ( lastVerts[1] == verts[0] ) { + obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[1] -= edgeDir; + } + if ( verts[1] == nextVerts[0] ) { + obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[0] += edgeDir; + } + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = NULL; + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( lastVerts, verts, sizeof( lastVerts ) ); +// RAVEN END + lastEdgeNormal = edgeNormal; + } + } + + // show obstacles + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( i = 0; i < numObstacles; i++ ) { + obstacle_t &obstacle = obstacles[i]; + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + silVerts[j].ToVec2() = obstacle.winding[j]; + silVerts[j].z = startPos.z; + } + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 ); + } + } + } + + return numObstacles; +} + +/* +============ +FreePathTree_r +============ +*/ +void FreePathTree_r( pathNode_t *node ) { + if ( node->children[0] ) { + FreePathTree_r( node->children[0] ); + } + if ( node->children[1] ) { + FreePathTree_r( node->children[1] ); + } + pathNodeAllocator.Free( node ); +} + +/* +============ +DrawPathTree +============ +*/ +void DrawPathTree( const pathNode_t *root, const float height ) { + int i; + idVec3 start, end; + const pathNode_t *node; + + for ( node = root; node; node = node->next ) { + for ( i = 0; i < 2; i++ ) { + if ( node->children[i] ) { + start.ToVec2() = node->pos; + start.z = height; + end.ToVec2() = node->children[i]->pos; + end.z = height; + gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 ); + break; + } + } + } +} + +/* +============ +GetPathNodeDelta +============ +*/ +bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) { + int numPoints, edgeNum; + bool facing; + idVec2 seekDelta, dir; + pathNode_t *n; + + numPoints = obstacles[node->obstacle].winding.GetNumPoints(); + + // get delta along the current edge + while( 1 ) { + edgeNum = ( node->edgeNum + node->dir ) % numPoints; + node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos; + if ( node->delta.LengthSqr() > 0.01f ) { + break; + } + node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints; + } + + // if not blocked + if ( !blocked ) { + + // test if the current edge faces the goal + seekDelta = seekPos - node->pos; + facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f; + + // if the current edge faces goal and the line from the current + // position to the goal does not intersect the current path + if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) { + node->delta = seekPos - node->pos; + node->edgeNum = -1; + } + } + + // if the delta is along the obstacle edge + if ( node->edgeNum != -1 ) { + // if the edge is found going from this node to the root node + for ( n = node->parent; n; n = n->parent ) { + + if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) { + continue; + } + + // test whether or not the edge segments actually overlap + if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) { + continue; + } + if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) { + continue; + } + + break; + } + if ( n ) { + return false; + } + } + return true; +} + +/* +============ +BuildPathTree +============ +*/ +pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) { + int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH; + float blockingScale; + pathNode_t *root, *node, *child; + + // gcc 4.0 + idQueueTemplate pathNodeQueue, treeQueue; + + root = pathNodeAllocator.Alloc(); + root->Init(); + root->pos = startPos; + + root->delta = seekPos - root->pos; + root->numNodes = 0; + pathNodeQueue.Add( root ); + + for ( node = pathNodeQueue.Get(); node && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) { + + treeQueue.Add( node ); + + // if this path has more than twice the number of nodes than the best path so far + if ( node->numNodes > bestNumNodes * 2 ) { + continue; + } + + // don't move outside of the clip bounds + idVec2 endPos = node->pos + node->delta; + if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x || + endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) { + continue; + } + + // if an obstacle is blocking the path + if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) { + + if ( path.firstObstacle == NULL ) { + path.firstObstacle = obstacles[blockingObstacle].entity; + } + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(obstacles[blockingObstacle].entity); + // RAVEN END + + + node->delta *= blockingScale; + + if ( node->edgeNum == -1 ) { + node->children[0] = pathNodeAllocator.Alloc(); + node->children[0]->Init(); + node->children[1] = pathNodeAllocator.Alloc(); + node->children[1]->Init(); + node->children[0]->dir = 0; + node->children[1]->dir = 1; + node->children[0]->parent = node->children[1]->parent = node; + node->children[0]->pos = node->children[1]->pos = node->pos + node->delta; + node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle; + node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum; + node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[0] ); + } + if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[1] ); + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->obstacle = blockingObstacle; + child->edgeNum = blockingEdgeNum; + child->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) { + pathNodeQueue.Add( child ); + } + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->numNodes = node->numNodes + 1; + + // there is a free path towards goal + if ( node->edgeNum == -1 ) { + if ( node->numNodes < bestNumNodes ) { + bestNumNodes = node->numNodes; + } + continue; + } + + child->obstacle = node->obstacle; + obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints(); + child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints; + + if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) { + pathNodeQueue.Add( child ); + } + } + } + + return root; +} + +/* +============ +PrunePathTree +============ +*/ +void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) { + int i; + float bestDist; + pathNode_t *node, *lastNode, *n, *bestNode; + + node = root; + while( node ) { + + node->dist = ( seekPos - node->pos ).LengthSqr(); + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + + // find the node closest to the goal along this path + bestDist = idMath::INFINITY; + bestNode = node; + for ( n = node; n; n = n->parent ) { + if ( n->children[0] && n->children[1] ) { + break; + } + if ( n->dist < bestDist ) { + bestDist = n->dist; + bestNode = n; + } + } + + // free tree down from the best node + for ( i = 0; i < 2; i++ ) { + if ( bestNode->children[i] ) { + FreePathTree_r( bestNode->children[i] ); + bestNode->children[i] = NULL; + } + } + + for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && ( node->children[1] != lastNode ) ) { + node = node->children[1]; + break; + } + } + } + } +} + +/* +============ +OptimizePath +============ +*/ +int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) { + int i, numPathPoints, edgeNums[2]; + const pathNode_t *curNode, *nextNode; + idVec2 curPos, curDelta, bounds[2]; + float scale1, scale2, curLength; + + optimizedPath[0] = root->pos; +// BDUBE: FIXME - Added this line because quite a few places in the code count on there being at least 2 items in that array + optimizedPath[1] = root->pos; + + numPathPoints = 1; + + for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) { + + for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) { + + // can only take shortcuts when going from one object to another + if ( nextNode->obstacle == curNode->obstacle ) { + continue; + } + + curPos = curNode->pos; + curDelta = nextNode->pos - curPos; + curLength = curDelta.Length(); + + // get bounds for the current movement delta + bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(curDelta.x)].x += curDelta.x; + bounds[FLOATSIGNBITNOTSET(curDelta.y)].y += curDelta.y; + + // test if the shortcut intersects with any obstacles + for ( i = 0; i < numObstacles; i++ ) { + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) { + if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) { + break; + } + if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) { + break; + } + } + } + if ( i >= numObstacles ) { + break; + } + } + + // store the next position along the optimized path + optimizedPath[numPathPoints++] = nextNode->pos; + } + + return numPathPoints; +} + +/* +============ +PathLength +============ +*/ +float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) { + int i; + float pathLength; + + // calculate the path length + pathLength = 0.0f; + for ( i = 0; i < numPathPoints-1; i++ ) { + pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast(); + } + + // add penalty if this path does not go in the current direction + if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) { + pathLength += 100.0f; + } + return pathLength; +} + +/* +============ +FindOptimalPath + + Returns true if there is a path all the way to the goal. +============ +*/ +bool FindOptimalPath( const idActor* owner, const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) { + int i, numPathPoints, bestNumPathPoints; + const pathNode_t *node, *lastNode, *bestNode; + idVec2 optimizedPath[MAX_OBSTACLE_PATH]; + float pathLength, bestPathLength; + bool pathToGoalExists, optimizedPathCalculated; + + seekPos.Zero(); + seekPos.z = height; + + pathToGoalExists = false; + optimizedPathCalculated = false; + + bestNode = root; + bestNumPathPoints = 0; + bestPathLength = idMath::INFINITY; + + node = root; + while( node ) { + + pathToGoalExists |= ( node->dist < 0.1f ); + + if ( node->dist <= bestNode->dist ) { + + if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) { + + if ( !optimizedPathCalculated ) { + bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() ); + seekPos.ToVec2() = optimizedPath[1]; + } + + numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath ); + pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() ); + + if ( pathLength < bestPathLength ) { + bestNode = node; + bestNumPathPoints = numPathPoints; + bestPathLength = pathLength; + seekPos.ToVec2() = optimizedPath[1]; + } + optimizedPathCalculated = true; + + } else { + + bestNode = node; + optimizedPathCalculated = false; + } + } + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && node->children[1] != lastNode ) { + node = node->children[1]; + break; + } + } + } + } + + if ( !pathToGoalExists ) { + if ( root->children[0] ) { + seekPos.ToVec2() = root->children[0]->pos; + } else if ( root->children[1] ) { + seekPos.ToVec2() = root->children[1]->pos; + } + } else if ( !optimizedPathCalculated ) { + OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + seekPos.ToVec2() = optimizedPath[1]; + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + idVec3 start, end; + start.z = end.z = height + 4.0f; + numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + for ( i = 0; i < numPathPoints-1; i++ ) { + start.ToVec2() = optimizedPath[i]; + end.ToVec2() = optimizedPath[i+1]; + gameRenderWorld->DebugArrow( colorCyan, start, end, 1 ); + } + } + + return pathToGoalExists; +} + +/* +============ +idAI::FindPathAroundObstacles + + Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks. +============ +*/ +bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) { + int numObstacles, areaNum, insideObstacle; + static obstacle_t obstacles[MAX_OBSTACLES]; + idBounds clipBounds; + idBounds bounds; + pathNode_t *root; + bool pathToGoalExists; + + path.seekPos = seekPos; + path.firstObstacle = NULL; + path.startPosOutsideObstacles = startPos; + path.startPosObstacle = NULL; + path.seekPosOutsideObstacles = seekPos; + path.seekPosObstacle = NULL; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.Clear(); + // RAVEN END + + if ( !aas ) { + return true; + } + idActor* owner = dynamic_cast(physics->GetSelf()); + + + bounds[1] = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -bounds[1]; + bounds[1].z = 32.0f; + + // get the AAS area number and a valid point inside that area + areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles ); + + // get all the nearby obstacles + numObstacles = GetObstacles( owner, physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds ); + + // get a source position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.startPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.startPosObstacle); + // RAVEN END + } + + // get a goal position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.seekPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.seekPosObstacle); + // RAVEN END + } + + // if start and destination are pushed to the same point, we don't have a path around the obstacle + if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) { + if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) { + return false; + } + } + + // build a path tree + root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path ); + + // draw the path tree + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + DrawPathTree( root, physics->GetOrigin().z ); + } + + // prune the tree + PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() ); + + // find the optimal path + pathToGoalExists = FindOptimalPath( owner, root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos ); + + // free the tree + FreePathTree_r( root ); + + return pathToGoalExists; +} + +/* +============ +idAI::FreeObstacleAvoidanceNodes +============ +*/ +void idAI::FreeObstacleAvoidanceNodes( void ) { + pathNodeAllocator.Shutdown(); +} + + +/* +=============================================================================== + + Path Prediction + + Uses the AAS to quickly and accurately predict a path for a certain + period of time based on an initial position and velocity. + +=============================================================================== +*/ + +const float OVERCLIP = 1.001f; +const int MAX_FRAME_SLIDE = 5; + +typedef struct pathTrace_s { + float fraction; + idVec3 endPos; + idVec3 normal; + const idEntity * blockingEntity; +} pathTrace_t; + +/* +============ +PathTrace + + Returns true if a stop event was triggered. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Adding ignoreent parm +bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path, const idEntity *ignore = NULL ) { +// RAVEN BEGIN + trace_t clipTrace; + aasTrace_t aasTrace; + + memset( &trace, 0, sizeof( trace ) ); + + if ( !aas || !aas->GetSettings() ) { +// RAVEN BEGIN +// nmckenzie: Added ignore ent +// ddynerman: multiple clip worlds + gameLocal.Translation( ent, clipTrace, start, end, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + // NOTE: could do (expensive) ledge detection here for when there is no AAS file + + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } else { + aasTrace.getOutOfSolid = true; + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + aasTrace.flags |= AREA_LEDGE; + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + aasTrace.travelFlags |= TFL_INVALID; + } + + aas->Trace( aasTrace, start, end ); + +// RAVEN BEGIN +// nmckenzie: Added ignore ent. + gameLocal.TranslationEntities( ent, clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + if ( clipTrace.fraction >= 1.0f ) { + + trace.fraction = aasTrace.fraction; + trace.endPos = aasTrace.endpos; + trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal(); + trace.blockingEntity = gameLocal.world; + + if ( aasTrace.fraction < 1.0f ) { + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_LEDGE_AREA; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { // PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_OBSTACLE; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) {// PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + } + } else { + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } + } + + if ( trace.fraction >= 1.0f ) { + trace.blockingEntity = NULL; + } + + return false; +} + +/* +============ +idAI::PredictPath + + Can also be used when there is no AAS file available however ledges are not detected. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Added ignore ent parm. +bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore ) { +// RAVEN END + int i, j, step, numFrames, curFrameTime; + idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart; + idVec3 gravity, gravityDir, invGravityDir; + float maxStepHeight, minFloorCos; + pathTrace_t trace; + + if ( aas && aas->GetSettings() ) { + gravity = aas->GetSettings()->gravity; + gravityDir = aas->GetSettings()->gravityDir; + invGravityDir = aas->GetSettings()->invGravityDir; + maxStepHeight = aas->GetSettings()->maxStepHeight; + minFloorCos = aas->GetSettings()->minFloorCos; + } else { + gravity = DEFAULT_GRAVITY_VEC3; + gravityDir = idVec3( 0, 0, -1 ); + invGravityDir = idVec3( 0, 0, 1 ); + maxStepHeight = 14.0f; + minFloorCos = 0.7f; + } + + path.endPos = start; + path.endVelocity = velocity; + path.endNormal.Zero(); + path.endEvent = 0; + path.endTime = 0; + path.blockingEntity = NULL; + + curStart = start; + curVelocity = velocity; + + numFrames = ( totalTime + frameTime - 1 ) / frameTime; + curFrameTime = frameTime; + for ( i = 0; i < numFrames; i++ ) { + + if ( i == numFrames-1 ) { + curFrameTime = totalTime - i * curFrameTime; + } + + delta = curVelocity * curFrameTime * 0.001f; + + path.endVelocity = curVelocity; + path.endTime = i * frameTime; + + // allow sliding along a few surfaces per frame + for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) { + + idVec3 lineStart = curStart; + + // allow stepping up three times per frame + for ( step = 0; step < 3; step++ ) { + + curEnd = curStart + delta; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path, ignore ) ) { +// RAVEN END + return true; + } + + if ( step ) { + + // step down at end point + tmpStart = trace.endPos; + curEnd = tmpStart - stepUp; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + + // if not moved any further than without stepping up, or if not on a floor surface + if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f || + ( trace.normal * invGravityDir ) < minFloorCos ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = lastEnd; + path.endEvent = SE_BLOCKED; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd ); + } + + return true; + } + + curStart = lastEnd; + break; + } + } + + path.endNormal = trace.normal; + path.blockingEntity = trace.blockingEntity; + + // if the trace is not blocked or blocked by a floor surface + if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) { + curStart = trace.endPos; + break; + } + + // save last result + lastEnd = trace.endPos; + + // step up + stepUp = invGravityDir * maxStepHeight; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + stepUp *= trace.fraction; + curStart = trace.endPos; + } + + if ( ai_debugMove.GetBool() ) {//PredictPath + gameRenderWorld->DebugLine( colorRed, lineStart, curStart ); + } + + if ( trace.fraction >= 1.0f ) { + break; + } + + delta.ProjectOntoPlane( trace.normal, OVERCLIP ); + curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP ); + + if ( stopEvent & SE_BLOCKED ) { + // if going backwards + if ( (curVelocity - gravityDir * curVelocity * gravityDir ) * + (velocity - gravityDir * velocity * gravityDir) < 0.0f ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + + return true; + } + } + } + + if ( j >= MAX_FRAME_SLIDE ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + return true; + } + } + + // add gravity + curVelocity += gravity * frameTime * 0.001f; + } + + path.endTime = totalTime; + path.endVelocity = curVelocity; + path.endPos = curStart; + path.endEvent = 0; + + return false; +} + + +/* +=============================================================================== + + Trajectory Prediction + + Finds the best collision free trajectory for a clip model based on an + initial position, target position and speed. + +=============================================================================== +*/ + +/* +===================== +Ballistics + + get the ideal aim pitch angle in order to hit the target + also get the time it takes for the projectile to arrive at the target +===================== +*/ +typedef struct ballistics_s { + float angle; // angle in degrees in the range [-180, 180] + float time; // time it takes before the projectile arrives +} ballistics_t; + +static int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) { + int n, i; + float x, y, a, b, c, d, sqrtd, inva, p[2]; + + x = ( end.ToVec2() - start.ToVec2() ).Length(); + y = end[2] - start[2]; + + a = 4.0f * y * y + 4.0f * x * x; + b = -4.0f * speed * speed - 4.0f * y * gravity; + c = gravity * gravity; + + d = b * b - 4.0f * a * c; + if ( d <= 0.0f || a == 0.0f ) { + return 0; + } + sqrtd = idMath::Sqrt( d ); + inva = 0.5f / a; + p[0] = ( - b + sqrtd ) * inva; + p[1] = ( - b - sqrtd ) * inva; + n = 0; + for ( i = 0; i < 2; i++ ) { + if ( p[i] <= 0.0f ) { + continue; + } + d = idMath::Sqrt( p[i] ); + bal[n].angle = idMath::ATan( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x ); + bal[n].time = x / ( idMath::Cos( bal[n].angle ) * speed ); + bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) ); + n++; + } + + return n; +} + +/* +===================== +HeightForTrajectory + +Returns the maximum hieght of a given trajectory +===================== +*/ +// RAVEN BEGIN +// nmckenzie: Removing static for this one. +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) { +// RAVEN END + float maxHeight, t; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + + return maxHeight; +} + +/* +===================== +idAI::TestTrajectory +===================== +*/ +bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) { + int i, numSegments; + float maxHeight, t, t2; + idVec3 points[5]; + trace_t trace; + bool result; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + // time it takes to fall from the top to the end height + t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) ); + + // start of parabolic + points[0] = start; + + if ( t < time ) { + numSegments = 4; + // point in the middle between top and start + t2 = ( time - t ) * 0.5f; + points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // top of parabolic + t2 = time - t; + points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // point in the middel between top and end + t2 = time - t * 0.5f; + points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } else { + numSegments = 2; + // point halfway through + t2 = time * 0.5f; + points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f; + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } + + // end of parabolic + points[numSegments] = end; + + if ( drawtime ) { + for ( i = 0; i < numSegments; i++ ) { + gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime ); + } + } + + // make sure projectile doesn't go higher than we want it to go + for ( i = 0; i < numSegments; i++ ) { + if ( points[i].z > max_height ) { + // goes higher than we want to allow + return false; + } + } + + result = true; + for ( i = 0; i < numSegments; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) { + result = true; + } else { + result = false; + } + break; + } + } + + if ( drawtime ) { + if ( clip ) { + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime ); + } else { + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + } + + return result; +} + +/* +===================== +idAI::PredictTrajectory + + returns true if there is a collision free trajectory for the clip model + aimDir is set to the ideal aim direction in order to hit the target +===================== +*/ +bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) { + int n, i, j; + float zVel, a, t, pitch, s, c; + trace_t trace; + ballistics_t ballistics[2]; + idVec3 dir[2]; + idVec3 velocity; + idVec3 lastPos, pos; + + assert( targetEntity ); + + // check if the projectile starts inside the target + if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) { + aimDir = target - firePos; + aimDir.Normalize(); + return true; + } + + // if no velocity or the projectile is not affected by gravity + if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) { + + aimDir = target - firePos; + aimDir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, firePos, target, clip, mat3_identity, clipmask, ignore ); +// RAVEN END + + if ( drawtime ) { + gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime ); + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + + return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ); + } + + n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics ); + if ( n == 0 ) { + // there is no valid trajectory + aimDir = target - firePos; + aimDir.Normalize(); + return false; + } + + // make sure the first angle is the smallest + if ( n == 2 ) { + if ( ballistics[1].angle < ballistics[0].angle ) { + a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a; + t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t; + } + } + + // test if there is a collision free trajectory + for ( i = 0; i < n; i++ ) { + pitch = DEG2RAD( ballistics[i].angle ); + idMath::SinCos( pitch, s, c ); + dir[i] = target - firePos; + dir[i].z = 0.0f; + dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() ); + dir[i].z = s; + + zVel = projectileSpeed * dir[i].z; + + if ( ai_debugTrajectory.GetBool() ) { + t = ballistics[i].time / 100.0f; + velocity = dir[i] * projectileSpeed; + lastPos = firePos; + pos = firePos; + for ( j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; +// RAVEN BEGIN +// nmckenzie: Added time parm to debugline so I can actually see it. + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, drawtime ); +// RAVEN END + lastPos = pos; + } + } + + if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) { + aimDir = dir[i]; + return true; + } + } + + aimDir = dir[0]; + + // there is no collision free trajectory + return false; +} diff --git a/source/game/ai/Monster_Berserker.cpp b/source/game/ai/Monster_Berserker.cpp new file mode 100644 index 0000000..b9e6007 --- /dev/null +++ b/source/game/ai/Monster_Berserker.cpp @@ -0,0 +1,465 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterBerserker : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBerserker ); + + rvMonsterBerserker ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + int FilterTactical ( int availableTactical ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + int standingMeleeNoAttackTime; + int painConsecutive; + + // Actions + rvAIAction actionPopupAttack; + rvAIAction actionChargeAttack; + + bool Berz_CanHitEnemyFromAnim ( int animNum ); + bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ChargeAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_ChargeGroundImpact ( const stateParms_t& parms ); + stateResult_t Frame_DoBlastAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterBerserker ); +}; + +CLASS_DECLARATION( idAI, rvMonsterBerserker ) +END_CLASS + +/* +================ +rvMonsterBerserker::rvMonsterBerserker +================ +*/ +rvMonsterBerserker::rvMonsterBerserker ( ) { + painConsecutive = 0; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterBerserker::Spawn +================ +*/ +void rvMonsterBerserker::Spawn ( void ) { + actionPopupAttack.Init ( spawnArgs, "action_popupAttack", NULL, AIACTIONF_ATTACK ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", "Torso_ChargeAttack", AIACTIONF_ATTACK ); + PlayEffect( "fx_ambient_electricity", animator.GetJointHandle( "r_Lowerarm_Real" ), true ); + PlayEffect( "fx_ambient_electricity_mace", animator.GetJointHandle( "chain9" ), true ); +} + +/* +================ +rvMonsterBerserker::Save +================ +*/ +void rvMonsterBerserker::Save ( idSaveGame *savefile ) const { + actionPopupAttack.Save ( savefile ); + actionChargeAttack.Save ( savefile ); + savefile->WriteInt( painConsecutive ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::Restore +================ +*/ +void rvMonsterBerserker::Restore ( idRestoreGame *savefile ) { + actionPopupAttack.Restore ( savefile ); + actionChargeAttack.Restore ( savefile ); + savefile->ReadInt( painConsecutive ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( GetEnemy() && GetEnemy()->GetPhysics()->GetOrigin().z > GetPhysics()->GetOrigin().z + 24.0f ) + {//this is a ground attack and enemy is above me, so don't even try it, stupid! + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +============ +rvMonsterBerserker::CanHitEnemyFromAnim +============ +*/ +bool rvMonsterBerserker::Berz_CanHitEnemyFromAnim( int animNum ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin(); + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +rvMonsterBerserker::CheckAction_RangedAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !Berz_CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterBerserker::CheckActions +================ +*/ +bool rvMonsterBerserker::CheckActions ( void ) { + // Pop-up attack is a forward moving melee attack that throws the enemy up in the air + if ( PerformAction ( &actionPopupAttack, (checkAction_t)&idAI::CheckAction_LeapAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + // Charge attack is where the berserker will charge up his spike and slam it in to the ground + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterBerserker::CheckAction_ChargeAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&rvMonsterBerserker::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} +/* +================ +rvMonsterBerserker::FilterTactical +================ +*/ +int rvMonsterBerserker::FilterTactical ( int availableTactical ) { + if ( move.moveCommand == MOVE_TO_ENEMY && move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else if ( combat.tacticalCurrent != AITACTICAL_RANGED + && combat.tacticalCurrent != AITACTICAL_MELEE + && (combat.tacticalMaskAvailable&AITACTICAL_RANGED_BIT) ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else { + availableTactical &= ~AITACTICAL_RANGED_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterBerserker::OnTacticalChange + +Enable/Disable the ranged attack based on whether the berzerker needs it +================ +*/ +void rvMonsterBerserker::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + //once you've gone into melee once, it's okay to try ranged attacks later + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BIT; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterBerserker::GetDebugInfo +===================== +*/ +void rvMonsterBerserker::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_popupAttack", aiActionStatusString[actionPopupAttack.status], userData ); + proc ( "idAI", "action_chargeAttack", aiActionStatusString[actionChargeAttack.status], userData ); +} + +/* +================ +rvMonsterBerserker::Pain +================ +*/ +bool rvMonsterBerserker::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterBerserker::CheckPainActions +================ +*/ +bool rvMonsterBerserker::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBerserker ) + STATE ( "State_Killed", rvMonsterBerserker::State_Killed ) + + STATE ( "Torso_ChargeAttack", rvMonsterBerserker::State_Torso_ChargeAttack ) + STATE ( "Torso_Pain", rvMonsterBerserker::State_Torso_Pain ) + + STATE ( "Frame_ChargeGroundImpact", rvMonsterBerserker::Frame_ChargeGroundImpact ) + STATE ( "Frame_DoBlastAttack", rvMonsterBerserker::Frame_DoBlastAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterBerserker::State_Torso_ChargeAttack +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_ChargeAttack ( const stateParms_t& parms ) { + enum { + TORSO_CHARGEATTACK_INIT, + TORSO_CHARGEATTACK_WAIT, + TORSO_CHARGEATTACK_RECOVER, + TORSO_CHARGEATTACK_RECOVERWAIT + }; + + switch ( parms.stage ) { + // Start the charge attack animation + case TORSO_CHARGEATTACK_INIT: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the ground strike + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_WAIT ); + + // Wait for charge attack animation to finish + case TORSO_CHARGEATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVER ); + } + return SRESULT_WAIT; + + // Play recover animation + case TORSO_CHARGEATTACK_RECOVER: + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike_recover", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVERWAIT ); + + // Wait for recover animation to finish + case TORSO_CHARGEATTACK_RECOVERWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBerserker::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_Pain ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterBerserker::State_Killed +================ +*/ +stateResult_t rvMonsterBerserker::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + StopEffect ( "fx_ambient_electricity" ); + StopEffect ( "fx_ambient_electricity_mace" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterBerserker::Frame_ChargeGroundImpact +================ +*/ +stateResult_t rvMonsterBerserker::Frame_ChargeGroundImpact ( const stateParms_t& parms ) { + idVec3 start; + idVec3 end; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform ( animator.GetJointHandle ( "R_lowerArm_Real" ), gameLocal.time, start, axis ); + + end = start; + end.z -= 128; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + + gameLocal.PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_ground_impact" ), tr.endpos, idVec3(0,0,1).ToMat3() ); + + return SRESULT_OK; +} + +/* +================ +rvMonsterBerserker::Frame_DoBlastAttack +================ +*/ +stateResult_t rvMonsterBerserker::Frame_DoBlastAttack ( const stateParms_t& parms ) { + float i; + idVec3 start; + idMat3 axis; + idAngles angles ( 0.0f, move.current_yaw, 0.0f ); + const idDict* blastDict; + + blastDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_spike" ), false ); + if ( !blastDict ) { + gameLocal.Error ( "missing projectile on spike attack for AI entity '%s'", GetName ( ) ) ; + return SRESULT_OK; + } + + GetJointWorldTransform ( animator.GetJointHandle ( "end_spike" ), gameLocal.time, start, axis ); + + for( i = 0; i < 32; i++ ) { + angles.yaw += (360.0f / 32.0f); + AttackProjectile ( blastDict, start, angles ); + } + + return SRESULT_OK; +} diff --git a/source/game/ai/Monster_BossBuddy.cpp b/source/game/ai/Monster_BossBuddy.cpp new file mode 100644 index 0000000..1f8e26c --- /dev/null +++ b/source/game/ai/Monster_BossBuddy.cpp @@ -0,0 +1,662 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +//------------------------------------------------------------ +class rvMonsterBossBuddy : public idAI +//------------------------------------------------------------ +{ +public: + + CLASS_PROTOTYPE( rvMonsterBossBuddy ); + + rvMonsterBossBuddy( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + void Think ( void ); + bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void OnWakeUp ( void ); + + // Add some dynamic externals for debugging + void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + bool CheckActions ( void ); +// void PerformAction ( const char* stateName, int blendFrames, bool noPain ); +// bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + void AdjustShieldState ( bool becomeShielded ); + void ReduceShields ( int amount ); + + int mShots; + int mShields; + int mMaxShields; + int mLastDamageTime; + int mShieldsLastFor; // read from def file, shouldn't need to be saved. + + bool mIsShielded; + bool mRequestedZoneMove; + bool mRequestedRecharge; + + //bool mCanIdle; + //bool mChaseMode; + +private: + + rvAIAction mActionRocketAttack; + rvAIAction mActionSlashMoveAttack; + rvAIAction mActionLightningAttack; + rvAIAction mActionDarkMatterAttack; + rvAIAction mActionMeleeMoveAttack; + rvAIAction mActionMeleeAttack; + + rvScriptFuncUtility mRequestRecharge; // script to run when a projectile is launched + rvScriptFuncUtility mRequestZoneMove; // script to run when he should move to the next zone + + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SlashAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + void Event_RechargeShields( float amount ); + + CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy ); +}; + +#define BOSS_BUDDY_MAX_SHIELDS 8500 +//------------------------------------------------------------ +// rvMonsterBossBuddy::rvMonsterBossBuddy +//------------------------------------------------------------ +rvMonsterBossBuddy::rvMonsterBossBuddy( void ) +{ + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; + mShields = mMaxShields; + mLastDamageTime = 0; + mIsShielded = false; + mRequestedZoneMove = false; + mRequestedRecharge = false; +// mCanIdle = false; +// mChaseMode = true; +} + +void rvMonsterBossBuddy::InitSpawnArgsVariables ( void ) +{ + mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f); + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Spawn +//------------------------------------------------------------ +void rvMonsterBossBuddy::Spawn( void ) +{ + mActionRocketAttack.Init( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + mActionLightningAttack.Init( spawnArgs,"action_lightningAttack", NULL, AIACTIONF_ATTACK ); + mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeMoveAttack.Init( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionSlashMoveAttack.Init( spawnArgs, "action_slashMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + mShields = mMaxShields; + + const char *func; + if ( spawnArgs.GetString( "requestRecharge", "", &func ) ) + { + mRequestRecharge.Init( func ); + } + if ( spawnArgs.GetString( "requestZoneMove", "", &func ) ) + { + mRequestZoneMove.Init( func ); + } + + HideSurface( "models/monsters/bossbuddy/forcefield" ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Save +//------------------------------------------------------------ +void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const +{ + savefile->WriteInt( mShots ); + savefile->WriteInt( mShields ); + savefile->WriteInt( mMaxShields ); + savefile->WriteInt( mLastDamageTime ); + savefile->WriteBool( mIsShielded ); + savefile->WriteBool( mRequestedZoneMove ); + savefile->WriteBool( mRequestedRecharge ); + + mActionRocketAttack.Save( savefile ); + mActionLightningAttack.Save( savefile ); + mActionDarkMatterAttack.Save( savefile ); + mActionMeleeMoveAttack.Save( savefile ); + mActionMeleeAttack.Save( savefile ); + mActionSlashMoveAttack.Save( savefile ); + + mRequestRecharge.Save( savefile ); + mRequestZoneMove.Save( savefile ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Restore +//------------------------------------------------------------ +void rvMonsterBossBuddy::Restore( idRestoreGame *savefile ) +{ + savefile->ReadInt( mShots ); + savefile->ReadInt( mShields ); + savefile->ReadInt( mMaxShields ); + savefile->ReadInt( mLastDamageTime ); + savefile->ReadBool( mIsShielded ); + savefile->ReadBool( mRequestedZoneMove ); + savefile->ReadBool( mRequestedRecharge ); + + mActionRocketAttack.Restore( savefile ); + mActionLightningAttack.Restore( savefile ); + mActionDarkMatterAttack.Restore( savefile ); + mActionMeleeMoveAttack.Restore( savefile ); + mActionMeleeAttack.Restore( savefile ); + mActionSlashMoveAttack.Restore( savefile ); + + mRequestRecharge.Restore( savefile ); + mRequestZoneMove.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +//------------------------------------------------------------ +// rvMonsterBerserker::GetDebugInfo +//------------------------------------------------------------ +void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData ) +{ + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_darkMatterAttack", aiActionStatusString[mActionDarkMatterAttack.status], userData ); + proc ( "idAI", "action_rocketAttack", aiActionStatusString[mActionRocketAttack.status], userData ); + proc ( "idAI", "action_meleeMoveAttack", aiActionStatusString[mActionMeleeMoveAttack.status], userData ); + proc ( "idAI", "action_lightningAttack", aiActionStatusString[mActionLightningAttack.status], userData ); +} + +//-------------------------------------------------------------- +// Custom Script Events +//-------------------------------------------------------------- +const idEventDef EV_RechargeShields( "rechargeShields", "f", 'f' ); + +CLASS_DECLARATION( idAI, rvMonsterBossBuddy ) + EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields ) +END_CLASS + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Event_RechargeShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::Event_RechargeShields( float amount ) +{ + mShields += (int)amount; + + if ( mShields >= mMaxShields ) + { + // charge is done + mShields = mMaxShields; + idThread::ReturnInt(0); + + // reset request states + mRequestedRecharge = false; + mRequestedZoneMove = false; + + // shield warning no longer neede for now + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn"); + } + else + { + // still charging + idThread::ReturnInt(1); + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::ReduceShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::ReduceShields( int amount ) +{ + mShields -= amount; + + // if no mShields left... or the last time we took damage was more than 8 seconds ago + if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time ) + { + //....remove the shielding + AdjustShieldState( false ); + } + + if (mShields < 1000) + { + if (!mRequestedRecharge ) + { + // entering a dangerous state! Get to the recharge station, fast! + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn"); + ExecScriptFunction( mRequestRecharge ); + mRequestedRecharge = true; + } + } + else if (mShields < 4000) + { + if ( !mRequestedZoneMove ) + { + // Getting low, so move him close to the next zone so he can be ready to recharge + ExecScriptFunction( mRequestZoneMove ); + mRequestedZoneMove = true; + } + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::AdjustShieldState +//------------------------------------------------------------ +void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded ) +{ + // only do the work for adjusting the state when it doesn't match our current state + if ( !mIsShielded && becomeShielded ) + { + // Activate Shields! + ShowSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" ); + } + else if ( mIsShielded && !becomeShielded ) + { + // Deactivate Shields! + HideSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL ); +// gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" ); + } + mIsShielded = becomeShielded; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Think() +{ + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) + { + // run simple shielding logic when we have them active + if ( mIsShielded ) + { + ReduceShields( 1 ); + + // if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields + if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time ) + { + AdjustShieldState( false ); + } + } + + // update shield bar + idUserInterface *hud = gameLocal.GetLocalPlayer()->hud; + if ( hud ) + { + float percent = ((float)mShields/mMaxShields); + + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } + } + + if ( move.obstacle.GetEntity() ) + { + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack ); + } + + idAI::Think(); +} +/* +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain ) +{ + // Allow movement in actions + move.fl.allowAnimMove = true; + + if ( mChaseMode ) + { + return; + } + + // Start the action + SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( mCanIdle ) + { + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState( "Wait_ActionNoPain" ); + OnStartAction( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) +{ + if ( mChaseMode ) + { + return false; + } + + return idAI::PerformAction( action, condition ,timer ); +} +*/ +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + // get damage amount so we can decay the shields and check for ignoreShields + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) + { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + // NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def, + // we don't run shielding logic + bool directDamage = damageDef->GetBool( "ignoreShields" ); + + int loc = location; + if ( directDamage ) + { + // Lame, I know, but hack the location + loc = INVALID_JOINT; + } + else if ( attacker == this ) + { + // can't damage self + return; + } + + float scale = 1; + + // Shields will activate for a set amount of time when damage is being taken + mLastDamageTime = gameLocal.time; + + // if shields are active, we should try to 'eat' them before directing damage to the BB + if ( mIsShielded && !directDamage ) + { + // BB is resistant to any kind of splash damage when the shields are up + if ( loc <= INVALID_JOINT ) + { + // damage must have been done by splash damage + return; + } + + int damage = damageDef->GetInt( "damage" ); + ReduceShields( damage * 8 ); + + // Shielding dramatically reduces actual damage done to BB + scale = 0.1f; + } + else if ( mShields > 0 ) // not currently shielded...does he have shields to use? + { + // Yep, so turn them on + AdjustShieldState( true ); + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Pain +//------------------------------------------------------------ +bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) +{ + // immune to small damage. Is this safe to do? + if ( damage > 5 ) + { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) ); + effect->Play( gameLocal.time, false ); + + return idAI::Pain( inflictor, attacker, damage, dir, location ); + } + return false; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CheckActions +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CheckActions( void ) +{ + // If not moving, try turning in place +/* if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnLeft90", 4, true ); + return true; + } + } +*/ + if ( PerformAction( &mActionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack )) + { + return true; + } + + if ( PerformAction( &mActionDarkMatterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionLightningAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack )) + { + return true; + } + + return idAI::CheckActions( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CanTurn +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CanTurn( void ) const +{ +/* if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +*/ + return idAI::CanTurn ( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::OnWakeUp +//------------------------------------------------------------ +void rvMonsterBossBuddy::OnWakeUp( void ) +{ + mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + idAI::OnWakeUp( ); +} + +//------------------------------------------------------------ +// States +//------------------------------------------------------------ + +CLASS_STATES_DECLARATION( rvMonsterBossBuddy ) + STATE( "Torso_RocketAttack", rvMonsterBossBuddy::State_Torso_RocketAttack ) + STATE( "Torso_SlashAttack", rvMonsterBossBuddy::State_Torso_SlashAttack ) + STATE( "Torso_TurnRight90", rvMonsterBossBuddy::State_Torso_TurnRight90 ) + STATE( "Torso_TurnLeft90", rvMonsterBossBuddy::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_RocketAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2start", parms.blendFrames ); + mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2loop2", 0 ); + return SRESULT_STAGE( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + if ( --mShots <= 0 || // exhausted mShots? .. or + (!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) || // ... player is no longer visible .. or + ( enemy.ent && DistanceTo(enemy.ent)<256 ) ) // ... player is so close, we prolly want to do a melee attack + { + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 0 ); + return SRESULT_STAGE( STAGE_WAITEND ); + } + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_SlashAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT_FIRST_SWIPE, + STAGE_WAIT_FINISH + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE ); + + case STAGE_WAIT_FIRST_SWIPE: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnRight90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnRight90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnLeft90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnLeft90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_BossMakron.cpp b/source/game/ai/Monster_BossMakron.cpp new file mode 100644 index 0000000..8af9b3f --- /dev/null +++ b/source/game/ai/Monster_BossMakron.cpp @@ -0,0 +1,2347 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterBossMakron : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBossMakron ); + + rvMonsterBossMakron ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + + bool CanTurn ( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BuildActionArray ( void ); + + //void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + +protected: + + bool CheckActions ( void ); + bool CheckTurnActions ( void ); + + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + //The Makron behaves differently, and we don't always need him sinking back into an idle after every attack + //This version is actually overridden + void PerformAction ( const char* stateName, int blendFrames, bool noPain ); + + //This just calls the parent version + bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + //This performs a patterned action + void PerformPatternedAction ( ); + + //For debug, may not need this. + void OnStopAction ( void ); + + //stops all effects + void StopAllEffects ( void ); + + //for the cannon fire + int shots; + + //determines if the Makron will enter idles between attacks + bool noIdle; + + //changes Makron from patterned, script controlled to normal AI + bool patternedMode; + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + float facingIdealYaw; + float facingTime; + float turnRate; + + //flag to indicate whether or not the Makron is in the corner of the map when he takes an action. + bool flagCornerState; + + //tallies up the number of consecutive actions the Makron has taken in the corner of the map. + //If the tally gets too high, teleport him to the map center. + int cornerCount; + + //in the state of teleporting + bool flagTeleporting; + + //for flying mode ---------------------------------------------------------------------------------------- + void BeginSeparation ( void ); + void CompleteSeparation ( void ); + + bool flagFlyingMode; + bool flagFakeDeath; + int flagUndying; + + rvClientEffectPtr effectHover; + jointHandle_t jointHoverEffect; + + //for the stomp attack ---------------------------------------------------------------------------------------- + float stompMaxRadius; + float stompWidth; + float stompSpeed; + float stompRadius; + + //for the lightning bolt sweep attacks ------------------------------------------------------------------- + void MaintainBoltSweep ( void ); + void InitBoltSweep ( idVec3 idealTarget ); + void LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ); + void StopAllBoltEffects ( void ); + + rvClientEffectPtr leftBoltEffect; + rvClientEffectPtr rightBoltEffect; + rvClientEffectPtr leftBoltImpact; + rvClientEffectPtr rightBoltImpact; + rvClientEffectPtr boltMuzzleFlash; + + idStr boltEffectName; + + idVec3 leftBoltVector; + idVec3 rightBoltVector; + idVec3 boltVectorMin; + idVec3 boltVectorMax; + idMat3 boltAimMatrix; + + bool flagSweepDone; + + //these are grabbed from the def file + float boltSweepTime; + float boltWaitTime; + + //used internally + float boltSweepStartTime; + float boltTime; + float boltNextStateTime; + + int stateBoltSweep; + + jointHandle_t jointLightningBolt; + + //end lightning bolt sweep attacks ------------------------------------------------------------------- + //changed by script event, allows for grenades to spawn baddies. + bool flagAllowSpawns; + + enum { MAKRON_ACTION_DMG, + MAKRON_ACTION_MELEE, + MAKRON_ACTION_CANNON, + MAKRON_ACTION_CANNON_SWEEP, + MAKRON_ACTION_GRENADE, + MAKRON_ACTION_LIGHTNING_1, + MAKRON_ACTION_LIGHTNING_2, + MAKRON_ACTION_STOMP, + MAKRON_ACTION_HEAL, + MAKRON_ACTION_CHARGE, + MAKRON_ACTION_KILLPLAYER, + MAKRON_ACTION_COUNT, }; + + rvAIAction actionDMGAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionCannonAttack; + rvAIAction actionCannonSweepAttack; + rvAIAction actionGrenadeAttack; + rvAIAction actionLightningPattern1Attack; + rvAIAction actionLightningPattern2Attack; + rvAIAction actionStompAttack; + rvAIAction actionHeal; + rvAIAction actionCharge; + rvAIAction actionKillPlayer; + + rvAIAction * actionArray[ MAKRON_ACTION_COUNT ]; + + int actionPatterned; + + //when the Makron is low on health, he'll use this. + rvScriptFuncUtility scriptRecharge; + rvScriptFuncUtility scriptTeleport; + + stateResult_t State_Torso_DMGAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_GrenadeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MeleeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonSweepAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning1Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning2Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_StompAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Recharge ( const stateParms_t& parms ); + stateResult_t State_Torso_Charge ( const stateParms_t& parms ); + stateResult_t State_Torso_KillPlayer ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + + //used when killed + stateResult_t State_Killed ( const stateParms_t& parms ); + + //walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + //flying turn anims + stateResult_t State_Torso_RotateToAngle ( const stateParms_t& parms ); + + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + + //like jesus, except with robot parts + stateResult_t State_Torso_FirstDeath ( const stateParms_t& parms ); + stateResult_t State_Torso_Resurrection ( const stateParms_t& parms ); + + //These two frames activate and deactivate the second lightning sweep + stateResult_t Frame_BeginLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_EndLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_StompAttack ( const stateParms_t& parms ); + stateResult_t Frame_Teleport ( const stateParms_t& parms ); + + + void Event_AllowMoreSpawns ( void ); + void Event_SetNextAction ( const char* actionString ); + void Event_EnablePatternMode ( void ); + void Event_DisablePatternMode ( void ); + void Event_StompAttack ( idVec3 &origin ); + void Event_Separate ( void ); + void Event_FlyingRotate ( idVec3 &vecOrg ); + void Event_ToggleCornerState ( float f ); + + + CLASS_STATES_PROTOTYPE ( rvMonsterBossMakron ); +}; + +const idEventDef EV_AllowMoreSpawns( "allowMoreSpawns" ); +const idEventDef EV_SetNextAction( "setNextAction", "s", 'f' ); +const idEventDef EV_EnablePatternMode( "enablePatternMode" ); +const idEventDef EV_DisablePatternMode( "disablePatternMode" ); +const idEventDef EV_StompAttack( "stompAttack", "v" ); +const idEventDef EV_Separate( "separate" ); +const idEventDef EV_FlyingRotate( "flyingRotate", "v"); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef EV_ToggleCornerState( "toggleCornerState", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterBossMakron ) + EVENT( EV_AllowMoreSpawns, rvMonsterBossMakron::Event_AllowMoreSpawns ) + EVENT( EV_SetNextAction, rvMonsterBossMakron::Event_SetNextAction ) + EVENT( EV_EnablePatternMode, rvMonsterBossMakron::Event_EnablePatternMode ) + EVENT( EV_DisablePatternMode, rvMonsterBossMakron::Event_DisablePatternMode ) + EVENT( EV_StompAttack, rvMonsterBossMakron::Event_StompAttack ) + EVENT( EV_Separate, rvMonsterBossMakron::Event_Separate ) + EVENT( EV_FlyingRotate, rvMonsterBossMakron::Event_FlyingRotate ) + EVENT( AI_ScriptedFace, rvMonsterBossMakron::ScriptedFace ) + EVENT( EV_ToggleCornerState, rvMonsterBossMakron::Event_ToggleCornerState ) +END_CLASS + +/* +================ +rvMonsterBossMakron::rvMonsterBossMakron +================ +*/ +rvMonsterBossMakron::rvMonsterBossMakron ( void ) { + + //set up this action array + + + +} + +/* +================ +rvMonsterBossMakron::BuildActionArray ( void ) +================ +*/ +void rvMonsterBossMakron::BuildActionArray ( void ) { + + actionArray[ MAKRON_ACTION_DMG ] = &actionDMGAttack; + actionArray[ MAKRON_ACTION_MELEE ] = &actionMeleeAttack; + actionArray[ MAKRON_ACTION_CANNON ] = &actionCannonAttack; + actionArray[ MAKRON_ACTION_CANNON_SWEEP ] = &actionCannonSweepAttack; + actionArray[ MAKRON_ACTION_GRENADE ] = &actionGrenadeAttack; + actionArray[ MAKRON_ACTION_LIGHTNING_1 ] = &actionLightningPattern1Attack; + actionArray[ MAKRON_ACTION_LIGHTNING_2 ] = &actionLightningPattern2Attack; + actionArray[ MAKRON_ACTION_STOMP ] = &actionStompAttack; + actionArray[ MAKRON_ACTION_HEAL ] = &actionHeal; + actionArray[ MAKRON_ACTION_CHARGE ] = &actionCharge; + actionArray[ MAKRON_ACTION_KILLPLAYER ] = &actionKillPlayer; + +} +/* +================ +rvMonsterBossMakron::Save +================ +*/ +void rvMonsterBossMakron::Save ( idSaveGame *savefile ) const { + + savefile->WriteInt( shots); + + savefile->WriteFloat( facingIdealYaw); + savefile->WriteFloat( facingTime); + + savefile->WriteBool( noIdle); + + savefile->WriteBool( patternedMode); + + savefile->WriteBool( flagFlyingMode); + savefile->WriteBool( flagFakeDeath); + savefile->WriteInt( flagUndying ); + + effectHover.Save( savefile ); + savefile->WriteJoint( jointHoverEffect ); // cnicholson: added unsaved var + + savefile->WriteFloat( stompRadius); + + leftBoltEffect.Save( savefile ); + rightBoltEffect.Save( savefile ); + leftBoltImpact.Save( savefile ); + rightBoltImpact.Save( savefile ); + boltMuzzleFlash.Save( savefile ); + + savefile->WriteString( boltEffectName); + + savefile->WriteVec3( leftBoltVector); + savefile->WriteVec3( rightBoltVector); + savefile->WriteVec3( boltVectorMin); + savefile->WriteVec3( boltVectorMax); + savefile->WriteMat3( boltAimMatrix); + + savefile->WriteBool( flagSweepDone); + + savefile->WriteFloat( boltSweepTime); + + savefile->WriteFloat( boltSweepStartTime); + savefile->WriteFloat( boltTime); + savefile->WriteFloat( boltNextStateTime); + + savefile->WriteInt( stateBoltSweep); + + savefile->WriteBool( flagAllowSpawns); + + savefile->WriteBool( flagCornerState ); + savefile->WriteInt( cornerCount ); + savefile->WriteBool( flagTeleporting ); + + + actionDMGAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionCannonAttack.Save( savefile ); + actionCannonSweepAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + actionLightningPattern1Attack.Save( savefile ); + actionLightningPattern2Attack.Save( savefile ); + actionStompAttack.Save( savefile ); + actionHeal.Save( savefile ); + actionCharge.Save( savefile ); + actionKillPlayer.Save( savefile ); + + savefile->WriteInt( actionPatterned); + + scriptRecharge.Save( savefile ); + scriptTeleport.Save( savefile ); + // cnicholson: No need to save actionArry, its rebuilt during restore +} + +/* +================ +rvMonsterBossMakron::Restore +================ +*/ +void rvMonsterBossMakron::Restore ( idRestoreGame *savefile ) { + + savefile->ReadInt( shots); + + savefile->ReadFloat( facingIdealYaw); + savefile->ReadFloat( facingTime); + + savefile->ReadBool( noIdle); + + savefile->ReadBool( patternedMode); + + savefile->ReadBool( flagFlyingMode); + savefile->ReadBool( flagFakeDeath); + savefile->ReadInt( flagUndying ); + + effectHover.Restore( savefile ); + savefile->ReadJoint( jointHoverEffect ); // cnicholson: added unrestoed var + + savefile->ReadFloat( stompRadius); + + leftBoltEffect.Restore( savefile ); + rightBoltEffect.Restore( savefile ); + leftBoltImpact.Restore( savefile ); + rightBoltImpact.Restore( savefile ); + boltMuzzleFlash.Restore( savefile ); + + savefile->ReadString( boltEffectName); + + savefile->ReadVec3( leftBoltVector); + savefile->ReadVec3( rightBoltVector); + savefile->ReadVec3( boltVectorMin); + savefile->ReadVec3( boltVectorMax); + savefile->ReadMat3( boltAimMatrix); + + savefile->ReadBool( flagSweepDone); + + savefile->ReadFloat( boltSweepTime); + + savefile->ReadFloat( boltSweepStartTime); + savefile->ReadFloat( boltTime); + savefile->ReadFloat( boltNextStateTime); + + savefile->ReadInt( stateBoltSweep); + + savefile->ReadBool( flagAllowSpawns); + + savefile->ReadBool( flagCornerState ); + savefile->ReadInt( cornerCount ); + savefile->ReadBool( flagTeleporting ); + + + actionDMGAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionCannonAttack.Restore( savefile ); + actionCannonSweepAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + actionLightningPattern1Attack.Restore( savefile ); + actionLightningPattern2Attack.Restore( savefile ); + actionStompAttack.Restore( savefile ); + actionHeal.Restore( savefile ); + actionCharge.Restore( savefile ); + actionKillPlayer.Restore( savefile ); + + + savefile->ReadInt( actionPatterned); + + scriptRecharge.Restore( savefile ); + scriptTeleport.Restore( savefile ); + //reload the action array, this is done in the init section but if we don't do it here our array is bunk. + BuildActionArray(); + InitSpawnArgsVariables(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + + +/* +================ +rvMonsterBossMakron::StopAllEffects +================ +*/ +void rvMonsterBossMakron::StopAllEffects ( void ) { + + StopAllBoltEffects(); + + //stop the over effect + if( effectHover ) { + effectHover.GetEntity()->Stop(); + effectHover = 0; + } + +} + +/* +================ +rvMonsterBossMakron::StopAllBoltEffects +================ +*/ +void rvMonsterBossMakron::StopAllBoltEffects ( void ) { + + if( leftBoltEffect ) { + leftBoltEffect.GetEntity()->Stop(); + leftBoltEffect = 0; + } + + if( rightBoltEffect ) { + rightBoltEffect.GetEntity()->Stop(); + rightBoltEffect = 0; + } + + if( leftBoltImpact ) { + leftBoltImpact.GetEntity()->Stop(); + leftBoltImpact = 0; + } + + if( rightBoltImpact ) { + rightBoltImpact.GetEntity()->Stop(); + rightBoltImpact = 0; + } + + if( boltMuzzleFlash ) { + boltMuzzleFlash .GetEntity()->Stop(); + boltMuzzleFlash = 0; + } + + +} + +void rvMonsterBossMakron::InitSpawnArgsVariables ( void ) { + //slick! + jointLightningBolt = animator.GetJointHandle ( spawnArgs.GetString ( "joint_lightningBolt", "claw_muzzle" ) ); + + stompMaxRadius = spawnArgs.GetFloat( "stomp_max_range", "1600"); + stompWidth = spawnArgs.GetFloat( "stomp_width", "32"); + stompSpeed = spawnArgs.GetFloat( "stomp_speed", "32"); + + boltWaitTime = spawnArgs.GetFloat( "lightingsweep_wait_time", "1"); + + turnRate = spawnArgs.GetFloat( "fly_turnRate", "180"); +} + +/* +================ +rvMonsterBossMakron::Spawn +================ +*/ +void rvMonsterBossMakron::Spawn ( void ) { + + if( spawnArgs.GetBool("passive")) { + flagFakeDeath = true; + } else { + flagFakeDeath = false; + } + + if( spawnArgs.GetBool("furniture") ) { + fl.takedamage = false; + } + + if( spawnArgs.GetBool("junior") ) { + flagUndying = 1; + } else { + flagUndying = 0; + } + + flagAllowSpawns = false; + flagFlyingMode = false; + patternedMode = false; + noIdle = false; + + flagCornerState = false; + cornerCount = 0; + flagTeleporting = false; + + //start clean + actionPatterned = -1; + + boltTime = 0; + boltSweepTime = spawnArgs.GetFloat( "lightningsweep_sweep_time", "3"); + + InitSpawnArgsVariables(); + + actionDMGAttack.Init ( spawnArgs, "action_DMGAttack", "Torso_DMGAttack", AIACTIONF_ATTACK ); + actionCannonAttack.Init ( spawnArgs, "action_cannonAttack", "Torso_CannonAttack", AIACTIONF_ATTACK ); + actionCannonSweepAttack.Init ( spawnArgs, "action_cannonsweepAttack", "Torso_CannonSweepAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", "Torso_GrenadeAttack", AIACTIONF_ATTACK ); + actionLightningPattern1Attack.Init( spawnArgs, "action_lightningPattern1Attack", "Torso_Lightning1Attack", AIACTIONF_ATTACK ); + actionLightningPattern2Attack.Init( spawnArgs, "action_lightningPattern2Attack", "Torso_Lightning2Attack", AIACTIONF_ATTACK ); + actionStompAttack.Init ( spawnArgs, "action_stompAttack", "Torso_StompAttack", AIACTIONF_ATTACK ); + actionHeal.Init( spawnArgs, "action_heal", "Torso_Recharge", AIACTIONF_ATTACK ); + actionCharge.Init( spawnArgs, "action_charge", "Torso_Charge", AIACTIONF_ATTACK ); + actionKillPlayer.Init( spawnArgs, "action_lightningPattern3Attack", "Torso_KillPlayer", AIACTIONF_ATTACK ); + + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", "Torso_MeleeAttack", AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_recharge", "", &func ) ) + { + scriptRecharge.Init( func ); + } + if ( spawnArgs.GetString( "script_teleport", "", &func ) ) + { + scriptTeleport.Init( func ); + } + + //build the action array + BuildActionArray(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + +/* +================ +rvMonsterBossMakron::CheckTurnActions +================ +*/ +bool rvMonsterBossMakron::CheckTurnActions ( void ) { + + if ( !flagFakeDeath && !move.fl.moving && !move.anim_turn_angles ) { //&& gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + ////gameLocal.Printf(" turn yaw == %f \n", turnYaw); + } + return false; +} +/* +================ +rvMonsterBossMakron::CheckActions +================ +*/ +bool rvMonsterBossMakron::CheckActions ( void ) { + + if( spawnArgs.GetFloat("furniture", "0")) { + return true; + } + + //if in the middle of teleporting, do nothing + if( flagTeleporting ) { + return true; + } + + //gameLocal.Printf("Begin CheckActions \n"); + + if( CheckTurnActions() ) { + //gameLocal.Printf("---CheckActions: TurnActions was true \n"); + return true; + } + + + //If we need more baddies, do it right now regardless of range or player FOV + if ( flagAllowSpawns ) { + PerformAction ( actionGrenadeAttack.state,actionGrenadeAttack.blendFrames, actionGrenadeAttack.fl.noPain ); + //gameLocal.Printf("---CheckActions: Spawned in more fellas \n"); + return true; + } + + //melee attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction ( &actionStompAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + //gameLocal.Printf("---CheckActions: Melee attacks \n"); + return true; + } + + //cannon sweep is good to check if we're up close. + //if ( PerformAction ( &actionCannonSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + // return true; + //} + + //If we're in total patterned mode, then don't relay on timers or ranges to perform actions. + if ( patternedMode && !flagFakeDeath ) { + //gameLocal.Printf("**CheckActions: PerformPatternedAction called \n"); + PerformPatternedAction ( ); + return true; + } + //gameLocal.Printf("---CheckActions: FlagFakeDeath: %d \n", flagFakeDeath ); + //gameLocal.Printf("---CheckActions: PatternedMode: %d \n", patternedMode ); + + //ranged attacks + if ( PerformAction ( &actionDMGAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningPattern1Attack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionCannonAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + //gameLocal.Printf("---CheckActions: Ranged attack called \n"); + return true; + } + //gameLocal.Printf("---CheckActions: Defaulting to AI CheckActions \n"); + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterMakron::PerformPatternedAction +================ +*/ +void rvMonsterBossMakron::PerformPatternedAction( ) { + + //gameLocal.Printf("PerformPatternedAction::Begin--\n"); + + //if we don't have an action to perform, don't. + if( actionPatterned == -1) { + //gameLocal.Printf("actionPatterned = -1\n"); + //gameLocal.Printf("PerformPatternedAction::End--\n"); + return; + } + + //if we're in a corner, right now, then add to the corner tally. + if( flagCornerState ) { + cornerCount++; + } else { + cornerCount = 0; + } + + //if this is our third consecutive action in the corner, maybe we should teleport? + if( cornerCount == 3 ) { + gameLocal.Warning("Makron is stuck in a corner!"); + cornerCount = 0; + SetState( "Torso_Teleport" ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + return; + + } + + rvAIAction* action; + rvAIActionTimer* timer; + + action = actionArray[ actionPatterned]; + timer = &actionTimerRangedAttack; + + //if the attack is variable length, then do all this jive... + float scale; + scale = 1.0f; + + //gameLocal.Printf("performing the action\n"); + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + //Clear this out so the next attack can be queued up. + actionPatterned = -1; + + // Restart the action timer using the length of the animation being played + // Even though we don't use the action timer for this instance, we need to keep it updated and fresh. + action->timer.Reset ( actionTime, action->diversity, scale ); + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + +} + + +/* +================ +rvMonsterMakron::CanTurn +================ +*/ +bool rvMonsterBossMakron::CanTurn ( void ) const { + if( !flagFlyingMode ) { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; + } else { + return idAI::CanTurn ( ); + } +} + + +/* +================ +rvMonsterMakron::InitBoltSweep +================ +*/ +void rvMonsterBossMakron::InitBoltSweep ( idVec3 idealTarget ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + idVec3 fwd; + idVec3 right; + idVec3 worldup(0,0,1); + + idVec3 targetPoint; + + //init all the lightning bolt stuff + boltTime = 0; + flagSweepDone = false; + stateBoltSweep = 0; + + //get the vector from makron's gun to the target point. + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + targetPoint = idealTarget; //enemy.lastVisibleChestPosition; + + fwd = targetPoint - origin; + + fwd.Normalize(); + right = fwd.Cross( worldup); + + boltAimMatrix[0] = fwd; + boltAimMatrix[1] = right; + boltAimMatrix[2] = worldup; + + targetPoint = origin + (fwd * 2400); + + //left + targetPoint = targetPoint - (right * 1800); + boltVectorMin = targetPoint - origin; + boltVectorMin.Normalize(); + leftBoltVector = boltVectorMin; + + //right + targetPoint = targetPoint + (right * 1800 * 2); + boltVectorMax = targetPoint - origin; + boltVectorMax.Normalize(); + rightBoltVector = boltVectorMax; + +} + +/* +================ +rvMonsterMakron::MaintainBoltSweep +================ +*/ +void rvMonsterBossMakron::MaintainBoltSweep ( void ) { + enum { + STATE_START, + STATE_WAIT1, + STATE_SWEEP1, + STATE_WAIT2, + STATE_SWEEP2, + STATE_END, + }; + + //advance the state when time is up. + + switch( stateBoltSweep ) { + + //init vars + case STATE_START: + flagSweepDone = false; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + StopAllBoltEffects(); + stateBoltSweep = STATE_WAIT1; + return; + + //blast at the waiting points, vectors do not move + case STATE_WAIT1: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + + case STATE_SWEEP1: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + } + + return; + case STATE_WAIT2: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + case STATE_SWEEP2: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + //min and max are reversed here. + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = 0; + StopAllBoltEffects(); + } + return; + case STATE_END: + flagSweepDone = 1; + return; + } + +} +/* +================ +rvMonsterBossMakron::LightningSweep +================ +*/ +void rvMonsterBossMakron::LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + + //trace out from origin along attackVector + attackVector.Normalize(); + gameLocal.TracePoint( this, tr, origin, origin + (attackVector * 25600), MASK_SHOT_RENDERMODEL, this); + //gameRenderWorld->DebugLine( colorRed, origin, tr.c.point, 100, true); + + if ( !boltEffect ) { + boltEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_fly" ), origin, attackVector.ToMat3(), true, tr.c.point); + } else { + boltEffect->SetOrigin ( origin ); + boltEffect->SetAxis ( attackVector.ToMat3() ); + boltEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_impact" ), tr.c.point, tr.c.normal.ToMat3(), true, tr.c.point); + } else { + impactEffect->SetOrigin ( tr.c.point ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + impactEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !boltMuzzleFlash ) { + boltMuzzleFlash = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_muzzle" ), origin, attackVector.ToMat3(), true, origin); + } else { + boltMuzzleFlash->SetOrigin ( origin ); + boltMuzzleFlash->SetAxis ( attackVector.ToMat3() ); + boltMuzzleFlash->SetEndOrigin ( origin ); + } + + //hurt anything in the way + idEntity* ent = gameLocal.entities[tr.c.entityNum]; + + if( ent) { + ent->Damage ( this, this, attackVector, spawnArgs.GetString ( "def_makron_sweep_damage" ), 1.0f, 0 ); + } + +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( !noIdle ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState ( "Wait_ActionNoPain" ); + + OnStartAction ( ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +bool rvMonsterBossMakron::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + return idAI::PerformAction( action, condition ,timer ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::OnStopAction( void ) { + ////gameLocal.Printf("\n\nAction stopped ----------- \n"); +} + +/* +================ +rvMonsterBossMakron::Damage +================ +*/ +void rvMonsterBossMakron::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + //Deal damage here, + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + //if the Makron has 0 or less health, make sure he's killed + ///if ( health <= 0 && !flagFlyingMode && !flagFakeDeath ) { + // Killed( this, this, 1, dir, location); + //} +} +/* +================ +rvMonsterBossMakron::Killed +================ +*/ +void rvMonsterBossMakron::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + //if this is the undying Makron Jr, don't worry about death. Stop what we're doing, + //Call the script function and let it ride. + if( flagUndying == 1) { + flagUndying = 2; + ExecScriptFunction( funcs.death ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + PerformAction ( actionKillPlayer.state, actionKillPlayer.blendFrames, actionKillPlayer.fl.noPain ); + return; + } + + if( flagUndying == 2) { + return; + } + + //stop all effects. + StopAllEffects(); + + //if the makron isn't in flying mode, it is now. + if( flagFlyingMode ) { + //play the falling animation, then die. + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + //SetState( "Torso_Death_Fall" ); + idAI::Killed( inflictor, attacker, damage, dir, location); + return; + } + + //the Makron is in undying mode until he finishes getting up. + //gameLocal.Warning("First form defeated!"); + + health = 1; + aifl.undying = true; + fl.takedamage = false; + SetMoveType ( MOVETYPE_DEAD ); + StopMove( MOVE_STATUS_DONE ); + //gameLocal.Printf("************\n************\n************\n************\n---flagFakeDeath set to TRUE! ************\n************\n************\n************\n" ); + flagFakeDeath = true; + + + + //play the death anim + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + //Call this anyway-- hopefully the script is prepared to handle multiple Makron deaths... + ExecScriptFunction( funcs.death ); + + //make sure he goes through deadness, but we need to post FinishAction afterwards +// SetState( "Torso_FirstDeath" ); +// aifl.action = true; + SetState( "Torso_FirstDeath" ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FirstDeath", 30, 0, SFLAG_ONCLEAR ); + +} + + +/* +================ +rvMonsterBossMakron::BeginSeparation( void ) +================ +*/ +void rvMonsterBossMakron::BeginSeparation( void ) { + + //spawn in a new Makron leg, + idDict args; + idEntity* newLegs; + + //We may need to do this at a later point + //SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_makron_legs" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &newLegs ); + + //store the name of the entity in the Makron's keys so we can burn it out as well. + spawnArgs.Set( "legs_name", newLegs->GetName() ); + + +} + +/* +================ +rvMonsterBossMakron::CompleteSeparation( void ) +================ +*/ +void rvMonsterBossMakron::CompleteSeparation( void ) { + + //flying mode now + flagFlyingMode = true; + SetMoveType ( MOVETYPE_FLY ); + move.fl.noGravity = true; + animPrefix = "fly"; + + //is there a cooler way to do this? Heal over time? + //health = spawnArgs.GetFloat("health_flying", "5000" ); + + ExecScriptFunction( funcs.init); + + //makron is once again pwnable. + aifl.undying = false; + fl.takedamage = true; + flagFakeDeath = false; + patternedMode = true; + + //make sure he cleans up. +// PostAnimState ( ANIMCHANNEL_ALL, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_LEGS, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + //gameLocal.Printf("*****\n*****\n*****\nPast 'PostAnimState' flagFakeDeath is: %d \n*****\n*****\n*****\n", flagFakeDeath); + + +// These four lines work! + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + + +} + +/* +================ +rvMonsterBossMakron::ScriptedFace( idEntity* faceEnt, bool endWithIdle ) +================ +*/ +/* +void rvMonsterBossMakron::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + + //set the ideal yaw, the change to the facing state. + FaceEntity( faceEnt ); + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + facingIdealYaw = move.ideal_yaw; + + //become scripted + aifl.scripted = true; + + //This will get us close to facing the entity correctly. + SetState( "State_ScriptedFace", SFLAG_ONCLEAR); + +} +*/ + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterBossMakron::Event_AllowMoreSpawns +================ +*/ +// this will allow Makron to spawn more baddies. +void rvMonsterBossMakron::Event_AllowMoreSpawns( void ) { + flagAllowSpawns = true; +} + +/* +================ +rvMonsterBossMakron::Event_EnablePatternMode +================ +*/ +// When set, the Makron will now only fight via scripted patterns +void rvMonsterBossMakron::Event_EnablePatternMode( void ) { + patternedMode = true; + noIdle = true; + flagTeleporting = false; +} + +/* +================ +rvMonsterBossMakron::Event_DisablePatternMode +================ +*/ +void rvMonsterBossMakron::Event_DisablePatternMode( void ) { + patternedMode = false; + noIdle = false; +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_Separate( void ) { + + //all we need to do here is post the separation state, right? + BeginSeparation(); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Resurrection", 0, 5000, SFLAG_ONCLEAR ); + +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_FlyingRotate( idVec3& vecOrg ) { + + //set move.ideal_yaw + TurnToward( vecOrg ); + + //copy it over + facingIdealYaw = move.ideal_yaw; + aifl.scripted = true; + + //set the state + SetState( "Torso_RotateToAngle"); + +} + +/* +================ +rvMonsterBossMakron::Event_SetNextAction +================ +*/ +void rvMonsterBossMakron::Event_SetNextAction( const char * actionString) { + + //if the next action is occupied, return false + if( actionPatterned != -1) { + idThread::ReturnFloat(0); + return; + } + + //otherwise, select the action from a list + if( !idStr::Cmp( actionString, "actionCannon")) { + actionPatterned = MAKRON_ACTION_CANNON; + } + else if( !idStr::Cmp( actionString, "actionCannonSweep")) { + actionPatterned = MAKRON_ACTION_CANNON_SWEEP; + } + else if( !idStr::Cmp( actionString, "actionDMG")) { + actionPatterned = MAKRON_ACTION_DMG; + } + else if( !idStr::Cmp( actionString, "actionDMGrenades")) { + actionPatterned = MAKRON_ACTION_GRENADE; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep1")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_1; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep2")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_2; + } + else if( !idStr::Cmp( actionString, "actionStomp")) { + actionPatterned = MAKRON_ACTION_STOMP; + } + else if( !idStr::Cmp( actionString, "actionHeal")) { + actionPatterned = MAKRON_ACTION_HEAL; + } + else if( !idStr::Cmp( actionString, "actionCharge")) { + actionPatterned = MAKRON_ACTION_CHARGE; + } + else if( !idStr::Cmp( actionString, "actionKillPlayer")) { + actionPatterned = MAKRON_ACTION_KILLPLAYER; + } + else if( !idStr::Cmp( actionString, "actionEndPattern")) { + actionPatterned = -1; + } + else { + gameLocal.Error(" Bad action %s passed into MonsterMakron::SetNextAction", actionString); + idThread::ReturnFloat(0); + return; + } + + idThread::ReturnFloat(1); + return; + + + +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBossMakron ) + STATE ( "Torso_DMGAttack", rvMonsterBossMakron::State_Torso_DMGAttack ) + STATE ( "Torso_MeleeAttack", rvMonsterBossMakron::State_Torso_MeleeAttack ) + STATE ( "Torso_CannonAttack", rvMonsterBossMakron::State_Torso_CannonAttack ) + STATE ( "Torso_GrenadeAttack", rvMonsterBossMakron::State_Torso_GrenadeAttack ) + STATE ( "Torso_CannonSweepAttack", rvMonsterBossMakron::State_Torso_CannonSweepAttack ) + STATE ( "Torso_Lightning1Attack", rvMonsterBossMakron::State_Torso_Lightning1Attack ) + STATE ( "Torso_Lightning2Attack", rvMonsterBossMakron::State_Torso_Lightning2Attack ) + STATE ( "Torso_StompAttack", rvMonsterBossMakron::State_Torso_StompAttack ) + STATE ( "Torso_Recharge", rvMonsterBossMakron::State_Torso_Recharge ) + STATE ( "Torso_Charge", rvMonsterBossMakron::State_Torso_Charge ) + STATE ( "Torso_KillPlayer", rvMonsterBossMakron::State_Torso_KillPlayer ) + STATE ( "Torso_FirstDeath", rvMonsterBossMakron::State_Torso_FirstDeath ) + STATE ( "Torso_Resurrection", rvMonsterBossMakron::State_Torso_Resurrection ) + STATE ( "State_Killed", rvMonsterBossMakron::State_Killed ) + STATE ( "Torso_Teleport", rvMonsterBossMakron::State_Torso_Teleport ) + + STATE ( "Torso_TurnRight90", rvMonsterBossMakron::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterBossMakron::State_Torso_TurnLeft90 ) + + STATE ( "Torso_RotateToAngle", rvMonsterBossMakron::State_Torso_RotateToAngle ) + + STATE ( "Frame_BeginLightningSweep2", rvMonsterBossMakron::Frame_BeginLightningSweep2 ) + STATE ( "Frame_EndLightningSweep2", rvMonsterBossMakron::Frame_EndLightningSweep2 ) + STATE ( "Frame_StompAttack", rvMonsterBossMakron::Frame_StompAttack ) + STATE ( "Frame_Teleport", rvMonsterBossMakron::Frame_Teleport ) + + STATE ( "State_ScriptedFace", rvMonsterBossMakron::State_ScriptedFace ) + + +END_CLASS_STATES + + +/* +================ +rvBossMakron::State_Torso_DMGAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_DMGAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron DMG Go!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_dmg", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_Torso_Charge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Charge ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "run", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_ScriptedFace +================ +*/ + +stateResult_t rvMonsterBossMakron::State_ScriptedFace ( const stateParms_t& parms ) { + + //note this uses the Makron's version of FacingIdeal, + if( !flagFlyingMode) { + if ( !aifl.scripted || (!CheckTurnActions( ) && (!move.anim_turn_angles))) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; + + } else { + + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +} + +/* + enum { + STAGE_INIT, + STAGE_WAIT + }; + + idStr turnAnim; + float turnYaw; + + switch( parms.stage ) { + case STAGE_INIT: + + DisableAnimState ( ANIMCHANNEL_LEGS ); + //which way do we need to face? + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + turnAnim = "turn_right_90"; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + turnAnim = "turn_left_90"; + } else { + //guess we don't need to turn? We're done. + aifl.scripted = false; + return SRESULT_DONE; + } + PlayAnim ( ANIMCHANNEL_TORSO, turnAnim, 4 ); + AnimTurn ( 90.0f, true ); + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + //back to the start to make sure we're facing the right way. + return SRESULT_STAGE ( STAGE_INIT ); + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; + +} +*/ + + +/* +================ +rvBossMakron::State_Torso_FirstDeath +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_FirstDeath ( const stateParms_t& parms ) { + + + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; +} + +/* +================ +rvBossMakron::State_Torso_Resurrection +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Resurrection ( const stateParms_t& parms ) { +/* enum { + STAGE_INIT, + STAGE_WAIT_FIRST, + STAGE_RISE, + STAGE_WAIT_SECOND + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT_FIRST); + + case STAGE_WAIT_FIRST: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_RISE); + } + return SRESULT_WAIT; + + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT_SECOND); + + case STAGE_WAIT_SECOND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +*/ + enum { + STAGE_RISE, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvBossMakron::State_Lightning2Attack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Lightning2Attack ( const stateParms_t& parms ) { + + idVec3 boltVector; + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + + //prep up for the bolt + //gameLocal.Warning("Prepping sweep 2"); + //init these values + StopAllBoltEffects(); + stateBoltSweep = 0; + flagSweepDone = false; + //set up bolt targeting at a little above the players chest-- make him duck this one. + boltVector = enemy.lastVisibleEyePosition; + boltVector.z += 8; + InitBoltSweep( boltVector ); + + //play the anim + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "claw_sweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + if( stateBoltSweep == 1) { + //fire the bolt out from the claw based on where the claw is pointing, sort of. + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact ); + } + else if( stateBoltSweep == 2) { + //gameLocal.Warning("Sweep 2 done."); + StopAllBoltEffects(); + stateBoltSweep = 0; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterBossMakron::State_Torso_StompAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_StompAttack( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //can't do the stomp attack while flying-- ain't got no legs!! + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shockwave_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvMonsterBossMakron::State_Torso_Teleport +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Teleport( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //No teleporting in flying mode. + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + //can't take damage in teleport anim, bad things happen! + aifl.undying = true; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + + //restore former state, which is killable unless some other effect renders Makron unkillable. + if( !flagUndying ) { + aifl.undying = false; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvBossMakron::State_Torso_GrenadeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_GrenadeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Grenades!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + //only allow spawns if the script tells us we can + if( !flagAllowSpawns) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_dm", parms.blendFrames ); + } + else { + flagAllowSpawns = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_spawn", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvBossMakron::State_Torso_Recharge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Recharge ( const stateParms_t& parms ) { + + ExecScriptFunction( scriptRecharge ); + return SRESULT_DONE; +} + +/* +================ +rvBossMakron::State_Torso_MeleeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_MeleeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //swing! + //gameLocal.Warning("Makron Melee Attack"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "melee_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + + +/* +================ +rvMonsterBossMakron::State_Torso_RotateToAngle +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_RotateToAngle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_LOOP, + }; + + float facingTimeDelta; + float turnYaw; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTime = MS2SEC( gameLocal.time); + aifl.scripted = true; + return SRESULT_STAGE ( STAGE_WAIT_LOOP ); + } + aifl.scripted = false; + return SRESULT_DONE; + case STAGE_WAIT_LOOP: + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTimeDelta = MS2SEC( gameLocal.time) - facingTime; + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + ang.yaw += ( turnRate * facingTimeDelta ); + SetAngles( ang); + move.current_yaw = ang.yaw; + return SRESULT_STAGE( STAGE_WAIT_LOOP); + } + aifl.scripted = false; + return SRESULT_DONE; + } + + return SRESULT_ERROR; +} +/* +================ +rvMonsterBossMakron::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_CannonAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + //once an anim starts with the legs disabled, the rest of the anims should match that. + static bool noLegs; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( !flagFlyingMode || ( flagFlyingMode && !move.fl.moving )) { + noLegs = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + } else { + noLegs = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_start", parms.blendFrames ); + + } + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + //if we're flying, and moving, fire fast! + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire_fast", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_fire_fast", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_end", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_Lightning1Attack +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_Lightning1Attack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_INIT_BOLT, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + idVec3 origin; + idVec3 targetPoint; + idMat3 axis; + trace_t tr; + + switch ( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning( "Lightningbolt!"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_INIT_BOLT ); + } + return SRESULT_WAIT; + + case STAGE_INIT_BOLT: + //aim a little below the player's chest + targetPoint = enemy.lastVisibleChestPosition; + targetPoint.z -= 24; + InitBoltSweep( targetPoint ); + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //sweep the blasts back and forth + MaintainBoltSweep(); + + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + if ( flagSweepDone ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_KillPlayer +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_KillPlayer ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = 8; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + shots--; + if ( shots < 1) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + + +/* +================ +rvBossMakron::State_Torso_CannonSweepAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonSweepAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron CannonSweep Go!"); + //sweep across with the cannon + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_cannonsweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + //if the flag is up, fire. + + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterBossMakron::State_Killed +================ +*/ +stateResult_t rvMonsterBossMakron::State_Killed ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + disablePain = true; + // At this point the Makron is killed! + // Make sure all animation stops + //StopAnimState ( ANIMCHANNEL_TORSO ); + //StopAnimState ( ANIMCHANNEL_LEGS ); + //if ( head ) { + // StopAnimState ( ANIMCHANNEL_HEAD ); + //} + + // Make sure all animations stop + //animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_BeginLightningSweep2 ( const stateParms_t& parms ) { + + //begin the sweep with this flag + stateBoltSweep = 1; + boltSweepStartTime = gameLocal.time; + boltSweepTime = 1; + return SRESULT_OK; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_EndLightningSweep2 ( const stateParms_t& parms ) { + + //end the sweep with this flag + stateBoltSweep = 2; + return SRESULT_OK; + +} + +/* +================ +rvMonsterBossMakron::Frame_Teleport +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_Teleport ( const stateParms_t& parms ) { + + //hide + Hide(); + + //turn on the do-nothing teleport flag + flagTeleporting = true; + + //call some script. + ExecScriptFunction( scriptTeleport ); + return SRESULT_DONE; + +} + + +/* +================ +rvMonsterBossMakron::Frame_StompAttack +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_StompAttack ( const stateParms_t& parms ) { + + idVec3 origin; + idVec3 worldUp(0, 0, 1); + + // Eminate from Makron origin + origin = this->GetPhysics()->GetOrigin(); + + //start radius at 256; + stompRadius = spawnArgs.GetFloat("stomp_start_size", "64"); + + //stomp + gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_stomp_wave" ), origin, worldUp.ToMat3(), false, origin); + + //bamf! + PostEventMS( &EV_StompAttack, 0, origin); + + return SRESULT_OK; + +} +/* +================ +rvMonsterBossMakron::Event_ToggleCornerState +================ +*/ +void rvMonsterBossMakron::Event_ToggleCornerState ( float f ) { + if( f == 1.0f) { + flagCornerState = true; + } else { + flagCornerState = false; + } +} + +/* +================ +rvMonsterBossMakron::Event_StompAttack +================ +*/ + +void rvMonsterBossMakron::Event_StompAttack (idVec3& origin) { + + idVec3 targetOrigin; + idVec3 worldUp; + idEntity* entities[ 1024 ]; + int count; + int i; + float stompZ; + idVec3 dir; + modelTrace_t result; + + + stompZ = origin.z; + + worldUp.x = 0; + worldUp.y = 0; + worldUp.z = 1; + + //if the radius is too big, stop. + if ( stompRadius > stompMaxRadius ) { + return; + } + + //get all enemies within radius. If they are: + // within radius, + // more than (radius - stompWidth) units away, + // Z valued within 16 of the the stomp Z + //they take stomp damage. + count = gameLocal.EntitiesWithinRadius ( origin, stompRadius, entities, 1024 ); + + //gameRenderWorld->DebugCircle( colorRed,origin,worldUp,stompRadius,24,20,false); + //gameRenderWorld->DebugCircle( colorBlue,origin,worldUp,stompRadius - stompWidth,24,20,false); + + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + + //don't stomp ourself, derp... + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Are they Z equal (about?) + targetOrigin = ent->GetPhysics()->GetOrigin(); + if( idMath::Abs( targetOrigin.z - origin.z) > 16) { + continue; + } + + // are they within the stomp width? + if( targetOrigin.Dist( origin) < ( stompRadius - stompWidth) || + targetOrigin.Dist( origin) > stompRadius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, ent->GetPhysics()->GetCenterMass()) ) { + continue; + } + + //ok, damage them + dir = targetOrigin - origin; + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_makron_stomp_damage" ), 1.0f, 0 ); + //gameRenderWorld->DebugArrow( colorYellow, origin, targetOrigin, 5, 1000); + + } + + //move the radius along + stompRadius += stompSpeed; + + //run it back + PostEventSec( &EV_StompAttack, stompSpeed / stompMaxRadius , origin ); + +} + + diff --git a/source/game/ai/Monster_ConvoyGround.cpp b/source/game/ai/Monster_ConvoyGround.cpp new file mode 100644 index 0000000..4f55c00 --- /dev/null +++ b/source/game/ai/Monster_ConvoyGround.cpp @@ -0,0 +1,603 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +// +class rvMonsterConvoyGround : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterConvoyGround ); + + rvMonsterConvoyGround ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Postthink ( void ); + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool CheckPainActions ( void ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AdjustHealthByDamage ( int inDamage ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + int shots; + int minShots; + int maxShots; + bool isOpen; + bool vehicleCollision; + float moveCurrentAnimRate; + float moveAnimRateMin; + float moveAnimRateRange; + float moveAccelRate; + bool onGround; + idVec3 oldOrigin; + idVec3 lastPainDir; + + rvAIAction actionBlasterAttack; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual const char* GetIdleAnimName ( void ); + + virtual void OnDeath ( void ); + +private: + + // General states + stateResult_t State_Fall ( const stateParms_t& parms ); + + // Legs States + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Open ( const stateParms_t& parms ); + stateResult_t State_Torso_Close ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyGround ); +}; + +CLASS_DECLARATION( idAI, rvMonsterConvoyGround ) +END_CLASS + +/* +================ +rvMonsterConvoyGround::rvMonsterConvoyGround +================ +*/ +rvMonsterConvoyGround::rvMonsterConvoyGround ( ) { + shots = 0; + isOpen = false; + vehicleCollision = false; + moveCurrentAnimRate = 1.0f; +} + +void rvMonsterConvoyGround::InitSpawnArgsVariables ( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); + moveAccelRate = spawnArgs.GetFloat ( "moveAccelRate", ".1" ); + moveAnimRateMin = spawnArgs.GetFloat ( "moveMinAnimRate", "1" ); + moveAnimRateRange = spawnArgs.GetFloat ( "moveMaxAnimRate", "10" ) - moveAnimRateMin; +} + +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +void rvMonsterConvoyGround::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + aifl.disableLook = true; + + onGround = true; + +} + +/* +================ +rvMonsterConvoyGround::Prethink +================ +*/ +void rvMonsterConvoyGround::Postthink ( void ) { +/* FIXME + if ( onGround && !physicsObj.HasGroundContacts ( ) ) { + onGround = false; + InterruptState ( "State_Fall" ); + } +*/ + + idAI::Postthink ( ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +bool rvMonsterConvoyGround::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + lastPainDir = dir; + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +void rvMonsterConvoyGround::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + //minShots and maxShots are set in the Restore + savefile->WriteBool ( isOpen ); + savefile->WriteBool ( vehicleCollision ); + savefile->WriteBool ( onGround ); + + savefile->WriteFloat ( moveCurrentAnimRate ); + savefile->WriteVec3 ( oldOrigin ); + savefile->WriteVec3 ( lastPainDir ); + + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterConvoyGround::Restore +================ +*/ +void rvMonsterConvoyGround::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadBool ( isOpen ); + savefile->ReadBool ( vehicleCollision ); + savefile->ReadBool ( onGround ); + + savefile->ReadFloat ( moveCurrentAnimRate ); + savefile->ReadVec3 ( oldOrigin ); + savefile->ReadVec3 ( lastPainDir ); + + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterConvoyGround::CanMove +================ +*/ +bool rvMonsterConvoyGround::CanMove ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvMonsterConvoyGround::CanTurn +================ +*/ +bool rvMonsterConvoyGround::CanTurn ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvMonsterConvoyGround::OnDeath +================ +*/ +void rvMonsterConvoyGround::OnDeath ( void ) { + idVec3 fxOrg; + idVec3 up; + idMat3 fxAxis; + + //center it + fxOrg = GetPhysics()->GetCenterMass(); + + //point it up + up.Set( 0, 0, 1 ); + fxAxis = up.ToMat3(); + + //if we can play it at the joint, do that + jointHandle_t axisJoint = animator.GetJointHandle ( "axis" ); + if ( axisJoint != INVALID_JOINT ) { + idMat3 junk; + animator.GetJointLocalTransform( axisJoint, gameLocal.GetTime(), fxOrg, junk ); + fxOrg = renderEntity.origin + (fxOrg*renderEntity.axis); + } + + gameLocal.PlayEffect ( spawnArgs, "fx_death", fxOrg, fxAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterConvoyGround::AdjustHealthByDamage +================ +*/ +void rvMonsterConvoyGround::AdjustHealthByDamage ( int damage ) { + if ( isOpen || vehicleCollision ) { + idAI::AdjustHealthByDamage ( damage ); + } else { + PlayEffect ( "fx_shieldHit", animator.GetJointHandle ( "axis" ) ); + } +} + +void rvMonsterConvoyGround::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + vehicleCollision = false; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + vehicleCollision = true; + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +bool rvMonsterConvoyGround::CheckActions ( void ) { + if ( isOpen ) { + if ( move.fl.moving ) { +/* + || !CheckAction_RangedAttack( &actionBlasterAttack, -1 ) + || enemy.range > actionBlasterAttack.maxRange + || enemy.range < actionBlasterAttack.minRange + || (!move.fl.moving && (gameLocal.GetTime()-move.startTime) > 3000 ) ) { +*/ + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Close", 4, true ); + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + // Open up if we have stopped and have an enemy + if ( !move.fl.moving && physicsObj.HasGroundContacts ( ) && enemy.ent && legsAnim.IsIdle ( ) && CheckTactical ( AITACTICAL_RANGED ) ) { + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Open", 4, true ); + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterConvoyGround::CheckPainActions +================ +*/ +bool rvMonsterConvoyGround::CheckPainActions ( void ) { + if ( isOpen ) { + return false; + } + + return idAI::CheckPainActions ( ); +} + +/* +================ +rvMonsterConvoyGround::GetIdleAnimName +================ +*/ +const char* rvMonsterConvoyGround::GetIdleAnimName ( void ) { + // Start idle animation + if ( isOpen ) { + return "idle_open"; + } + return "idle"; +} + +/* +================ +rvMonsterConvoyGround::FilterTactical +================ +*/ +int rvMonsterConvoyGround::FilterTactical ( int availableTactical ) { + return idAI::FilterTactical ( availableTactical ); +} + +/* +===================== +rvMonsterConvoyGround::GetDebugInfo +===================== +*/ +void rvMonsterConvoyGround::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + + proc ( "rvMonsterConvoyGround", "moveAnimRate", va("%g", moveCurrentAnimRate ), userData ); + proc ( "rvMonsterConvoyGround", "isOpen", isOpen ? "true" : "false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyGround ) + STATE ( "State_Fall", rvMonsterConvoyGround::State_Fall ) + STATE ( "Torso_Open", rvMonsterConvoyGround::State_Torso_Open ) + STATE ( "Torso_Close", rvMonsterConvoyGround::State_Torso_Close ) + STATE ( "Torso_BlasterAttack", rvMonsterConvoyGround::State_Torso_BlasterAttack ) + STATE ( "Torso_Pain", rvMonsterConvoyGround::State_Torso_Pain ) + + STATE ( "Legs_Move", rvMonsterConvoyGround::State_Legs_Move ) +END_CLASS_STATES + +/* +================ +rvMonsterConvoyGround::State_Fall +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact, switch to combat state + STAGE_WAITDONE, + STAGE_DONE + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StopAnimState ( ANIMCHANNEL_LEGS ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle", 0 ); + oldOrigin = physicsObj.GetOrigin ( ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( physicsObj.HasGroundContacts ( ) ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + + if ( (physicsObj.GetOrigin ( ) - oldOrigin).LengthSqr() > Square(128.0f) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "land", 0 ); + return SRESULT_STAGE ( STAGE_WAITDONE ); + } + return SRESULT_STAGE ( STAGE_DONE ); + + case STAGE_WAITDONE: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + onGround = true; + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Open +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Open ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "extend_legs", parms.blendFrames ); + isOpen = true; + aifl.disableLook = false; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Close +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Close ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "retract_legs", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + isOpen = false; + aifl.disableLook = true; + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_END + }; + + switch ( parms.stage ) { + case STAGE_START: + // Force the orientation to the direction we got hit from so the animation looks correct + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + TurnToward ( physicsObj.GetOrigin() - lastPainDir * 128.0f ); + move.current_yaw = move.ideal_yaw; + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END ); + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Legs_Move +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_MOVE + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + move.fl.running = true; + move.currentDirection = MOVEDIR_FORWARD; + // TODO: Looks like current anim rate never gets reset, so they do not correctly accelerate from a stop + // unfortunately, adding this change (with a decent acceleration factor) caused them to do lots of + // not-so-good looking short moves. +// moveCurrentAnimRate = 0; + + oldOrigin = physicsObj.GetOrigin ( ); + PlayCycle ( ANIMCHANNEL_LEGS, "run", 0 ); + StartSound( "snd_move", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_MOVE ); + + case STAGE_MOVE: + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + StopSound( SND_CHANNEL_BODY3, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + + // If on the ground update the animation rate based on the normal of the ground plane + if ( !ai_debugHelpers.GetBool ( ) && physicsObj.HasGroundContacts ( ) ) { + float rate; + idVec3 dir; + + dir = (physicsObj.GetOrigin ( ) - oldOrigin); + + if ( DistanceTo ( move.moveDest ) < move.walkRange ) { + rate = moveAnimRateMin; + } else if ( dir.Normalize ( ) > 0.0f ) { + rate = idMath::ClampFloat ( -0.7f, 0.7f, physicsObj.GetGravityNormal ( ) * dir ) / 0.7f; + rate = moveAnimRateMin + moveAnimRateRange * (1.0f + rate) / 2.0f; + } else { + rate = moveAnimRateMin + moveAnimRateRange * 0.5f; + } + moveCurrentAnimRate += ((rate - moveCurrentAnimRate) * moveAccelRate); + + animator.CurrentAnim ( ANIMCHANNEL_LEGS )->SetPlaybackRate ( gameLocal.time, moveCurrentAnimRate ); + } + + oldOrigin = physicsObj.GetOrigin ( ); + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + diff --git a/source/game/ai/Monster_ConvoyHover.cpp b/source/game/ai/Monster_ConvoyHover.cpp new file mode 100644 index 0000000..7186040 --- /dev/null +++ b/source/game/ai/Monster_ConvoyHover.cpp @@ -0,0 +1,630 @@ +//---------------------------------------------------------------- +// rvMonsterConvoyHover.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +class rvMonsterConvoyHover : public rvVehicleMonster { +public: + CLASS_PROTOTYPE ( rvMonsterConvoyHover ); + + rvMonsterConvoyHover ( void ); + ~rvMonsterConvoyHover ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + +private: + + void Think_Random ( void ); + void Think_Pathing ( void ); + + void AttackBlaster ( void ); + void AttackBeam ( void ); + //void AttackBomb ( void ); + + float angleYaw; // actually acos( angleYaw ) from player + float minYaw; + float maxYaw; + float desiredHeight; // actually acos( desiredHeight ) from player + float minHeight; + float maxHeight; + float distance; // distance to enemy + float minDistance; + float maxDistance; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + + int shotCount; + + idPhysics_RigidBody physicsObj; + + static const int DAMPEN_ANGLE_SAMPLES = 8; + idAngles hoverDampening[ DAMPEN_ANGLE_SAMPLES ]; + + void CalcDampening ( const idAngles & cur, idAngles & out ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_BeamAttack ( const stateParms_t& parms ); + //stateResult_t State_BombAttack ( const stateParms_t& parms ); + + struct { + bool pathing:1; + bool faceEnemy:1; + bool dead:1; + } myfl; + + virtual void OnDeath ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover ); +}; + +CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover ) +END_CLASS + +/* +================ +rvMonsterConvoyHover::rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) { +} + +/* +================ +rvMonsterConvoyHover::~rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvMonsterConvoyHover::Save +================ +*/ +void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( angleYaw ); + savefile->WriteFloat ( desiredHeight ); + savefile->WriteFloat ( distance ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Write( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var +} + +/* +================ +rvMonsterConvoyHover::Restore +================ +*/ +void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( angleYaw ); + savefile->ReadFloat ( desiredHeight ); + savefile->ReadFloat ( distance ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + savefile->ReadInt ( shotCount ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Read( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var + + InitSpawnArgsVariables(); +} + +void rvMonsterConvoyHover::InitSpawnArgsVariables ( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + minYaw = spawnArgs.GetFloat( "minYaw", "0" ); + maxYaw = spawnArgs.GetFloat( "maxYaw", "360" ); + minHeight = spawnArgs.GetFloat( "minHeight", "10" ); + maxHeight = spawnArgs.GetFloat( "maxHeight", "70" ); + minDistance = spawnArgs.GetFloat( "minDistance", "100" ); + maxDistance = spawnArgs.GetFloat( "maxDistance", "500" ); + + minDistance *= minDistance; + maxDistance *= maxDistance; + + if ( minYaw == 0.0f && maxYaw == 0.0f ) { + maxYaw = 360.0f; + } + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} +/* +================ +rvMonsterConvoyHover::Spawn +================ +*/ +void rvMonsterConvoyHover::Spawn ( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( physicsObj ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( vec3_origin ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + + InitSpawnArgsVariables(); + + shotCount = 0; + + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = 0.5f * ( maxHeight - minHeight ) + minHeight; + distance = 0.5f * ( maxDistance - minDistance ) + minDistance; + + lastAttackTime = 0; + + SetState( "Idle" ); + + myfl.pathing = false; + myfl.faceEnemy = true; + myfl.dead = false; + + for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) { + hoverDampening[ i ].Zero(); + } + + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvMonsterConvoyHover::Think +================ +*/ +void rvMonsterConvoyHover::Think ( void ) { + if ( !driver ) { + rvVehicleMonster::Think(); + return; + } + + if ( !driver->IsDriving() ) { + GetPhysics()->SetGravity( gameLocal.GetGravity() ); + } + + trace_t trace; + idVec3 dir = GetPhysics()->GetLinearVelocity(); + dir.Normalize(); + if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) { + if ( trace.c.contents == CONTENTS_FLYCLIP ) { + float maxHeight = this->maxHeight * 2.0f; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f ); + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f ); + } + } + + if ( !driver->CanSee( driver->enemy.ent, false ) ) { + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f ); + } + + if ( myfl.pathing ) { + Think_Pathing( ); + } else { + Think_Random( ); + } + + rvVehicleMonster::Think(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); +// vel += idVec3( idMath::Sin( gameLocal.time + vel.x ), +// idMath::Sin( gameLocal.time + vel.y ), +// idMath::Sin( gameLocal.time + vel.z ) ); + + + idAngles hover = idAngles( idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), + 0.0f, + idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) ); + + CalcDampening( hover, hover ); + + if ( myfl.faceEnemy ) { + LookAtEntity( gameLocal.GetLocalPlayer(), 0 ); + idAngles angles = GetPhysics()->GetAxis( ).ToAngles( ); + angles.pitch *= 0.35f; + GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() ); + } else { + GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Random +================ +*/ +void rvMonsterConvoyHover::Think_Random ( void ) { + const idVec3 xAxis( 1.0f, 0.0f, 0.0f ); + const idVec3 yAxis( 0.0f, 1.0f, 0.0f ); + const idVec3 zAxis( 0.0f, 0.0f, 1.0f ); + const float xSpeed = 16000.0f; + const float ySpeed = 9000.0f; + const float zSpeed = 9000.0f; + + idEntity * ent = driver->enemy.ent; + if ( !ent ) { + ent = gameLocal.GetLocalPlayer(); + } + + idVec3 toEnemyYaw = ent->GetPhysics()->GetOrigin() - GetOrigin(); + toEnemyYaw.z = 0.0f; + toEnemyYaw.Normalize(); + float yaw = toEnemyYaw.ToAngles().yaw; + float height = GetOrigin().z - ent->GetPhysics()->GetOrigin().z; + float dist = GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() ); + + // yaw + if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } else { + idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed ); + impulse *= GetPhysics()->GetAxis(); + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + + // 5% chance of choosing a new angle + if ( rvRandom::flrand() < 0.05f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } + } + + // pitch (changed to height for convenience) + if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) { + desiredHeight = rvRandom::flrand( minHeight, maxHeight ); + } else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed ); + } else { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed ); + } + + // distance + if ( idMath::Fabs( dist - distance ) > 20.0f ) { + idVec3 impulse = xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed ); + idMat3 axis = GetPhysics()->GetAxis(); + axis[ 1 ] = idVec3( 0.0f, 0.0f, 1.0f ); + axis[ 2 ] = axis[ 1 ].Cross( axis[ 0 ] ); + impulse *= axis; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + } else { + // just choose a random distance for now + distance = rvRandom::flrand( minDistance, maxDistance ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Pathing +================ +*/ +void rvMonsterConvoyHover::Think_Pathing ( void ) { +} + +/* +================ +rvMonsterConvoyHover::AttackBlaster +================ +*/ +void rvMonsterConvoyHover::AttackBlaster ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBeam +================ +*/ +void rvMonsterConvoyHover::AttackBeam ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBomb +================ +* +void rvMonsterConvoyHover::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + + PlayEffect ( "fx_bombflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_bomb") ); + if( proj ) { + proj->Create( this, offset, axis[0], NULL ); + proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() ); + } +} +*/ + + + +/* +================ +rvMonsterConvoyHover::OnDeath +================ +*/ +void rvMonsterConvoyHover::OnDeath ( void ) { + myfl.dead = true; + idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) ); + + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetFriction( 0.0f, 0.0f, 0.0f ); + GetPhysics()->SetAngularVelocity ( angular ); + + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); +} + +/* +================ +rvMonsterConvoyHover::CalcDampening +================ +*/ +void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) { + idAngles current = cur; // just incase cur == out + out = cur; + + for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) { + hoverDampening[ i - 1 ] = hoverDampening[ i ]; + out += hoverDampening[ i ]; + } + hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current; + + out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyHover ) + STATE( "Idle", rvMonsterConvoyHover::State_Idle ) + STATE( "BlasterAttack", rvMonsterConvoyHover::State_BlasterAttack ) + STATE( "BeamAttack", rvMonsterConvoyHover::State_BeamAttack ) +// STATE( "BombAttack", rvMonsterConvoyHover::State_BombAttack ) +END_CLASS_STATES + + +/* +================ +rvMonsterConvoyHover::State_Idle +================ +*/ +stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) { + if ( driver ) { + if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) { +// if ( rvRandom::irand( 0, 100 ) < 10 ) { +// distance = 0; +// PostState( "BeamAttack" ); +// } else { + PostState( "BlasterAttack" ); +// } + PostState( "Idle" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterConvoyHover::State_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BeamAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BEAM, + STAGE_BEAMWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BEAM ); + + case STAGE_BEAM: + lastAttackTime = gameLocal.time; + AttackBeam( ); + return SRESULT_STAGE ( STAGE_BEAMWAIT ); + + case STAGE_BEAMWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BEAM ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BombAttack +================ +* +stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ diff --git a/source/game/ai/Monster_FailedTransfer.cpp b/source/game/ai/Monster_FailedTransfer.cpp new file mode 100644 index 0000000..4685fe0 --- /dev/null +++ b/source/game/ai/Monster_FailedTransfer.cpp @@ -0,0 +1,122 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" + +class rvMonsterFailedTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFailedTransfer ); + + rvMonsterFailedTransfer ( void ); + + void Spawn ( void ); + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + bool allowSplit; + + virtual void OnDeath ( void ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterFailedTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFailedTransfer ) +END_CLASS + +/* +================ +rvMonsterFailedTransfer::rvMonsterFailedTransfer +================ +*/ +rvMonsterFailedTransfer::rvMonsterFailedTransfer ( ) { + allowSplit = false; +} + +/* +================ +rvMonsterFailedTransfer::Spawn +================ +*/ +void rvMonsterFailedTransfer::Spawn ( void ) { + LoadAF ( "ragdoll_legs", true ); + LoadAF ( NULL, true ); +} + +/* +================ +rvMonsterFailedTransfer::Save +================ +*/ +void rvMonsterFailedTransfer::Save( idSaveGame *savefile ) const { + savefile->WriteBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::Restore +================ +*/ +void rvMonsterFailedTransfer::Restore( idRestoreGame *savefile ) { + savefile->ReadBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::OnDeath +================ +*/ +void rvMonsterFailedTransfer::OnDeath ( void ) { + idAI::OnDeath ( ); + + if ( allowSplit ) { + idEntity* torso; + idDict args; + + LoadAF ( "ragdoll_legs", true ); + + PlayEffect ( "fx_bloodyburst", animator.GetJointHandle ( "chest" ) ); + SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_failed_transfer_torso" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -50.0f ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &torso ); + torso->fl.takedamage = false; + PostEventMS( &AI_TakeDamage, 100, 1.0f ); + } +} + +/* +================ +rvMonsterFailedTransfer::Killed +================ +*/ +void rvMonsterFailedTransfer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !idStr::Icmp ( GetDamageGroup( location ), "legs" ) && damage < 999 ) { + allowSplit = true; + } else { + allowSplit = false; + } + + idAI::Killed ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFailedTransfer ) +END_CLASS_STATES + diff --git a/source/game/ai/Monster_Fatty.cpp b/source/game/ai/Monster_Fatty.cpp new file mode 100644 index 0000000..8ddb329 --- /dev/null +++ b/source/game/ai/Monster_Fatty.cpp @@ -0,0 +1,419 @@ +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +typedef struct monsterFattyChain_s { + jointHandle_t orientationJoint; + jointHandle_t attackJoint; + bool out; + idEntityPtr projectile; +} monsterFattyChain_t; + +class rvMonsterFatty : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFatty ); + + rvMonsterFatty ( void ) {} + ~rvMonsterFatty ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool UpdateAnimationControllers ( void ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { return false; }; + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + rvAIAction actionWhipAttack; + + enum { + CHAIN_LEFT, + CHAIN_RIGHT, + CHAIN_MAX + }; + + monsterFattyChain_t chains[CHAIN_MAX]; + + float missHeight; + + virtual bool CheckActions ( void ); + + void PlayAttackAnim ( const idVec3& target, int blendFrames ); + + void ResetAllChains ( void ); + void ChainIn ( int chain ); + void ChainOut ( int chain ); + +private: + + // Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + + // Frame Commands + stateResult_t Frame_LeftChainOut ( const stateParms_t& parms ); + stateResult_t Frame_LeftChainIn ( const stateParms_t& parms ); + + stateResult_t Frame_RightChainOut ( const stateParms_t& parms ); + stateResult_t Frame_RightChainIn ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterFatty ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFatty ) +END_CLASS + +/* +================ +rvMonsterFatty::~rvMonsterFatty +================ +*/ +rvMonsterFatty::~rvMonsterFatty ( void ) { + ResetAllChains ( ); +} + +void rvMonsterFatty::InitSpawnArgsVariables ( void ) +{ + chains[CHAIN_LEFT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChain", "chainb1" ) ); + chains[CHAIN_LEFT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChainAttack", "hookb" ) ); + + chains[CHAIN_RIGHT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChain", "chaina1" ) ); + chains[CHAIN_RIGHT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChainAttack", "hooka" ) ); + + missHeight = spawnArgs.GetFloat ( "missHeight", "72" ); +} +/* +================ +rvMonsterFatty::Spawn +================ +*/ +void rvMonsterFatty::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + + // Cache joints + chains[CHAIN_LEFT].out = false; + chains[CHAIN_LEFT].projectile = NULL; + + chains[CHAIN_RIGHT].out = false; + chains[CHAIN_RIGHT].projectile = NULL; + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::Save +================ +*/ +void rvMonsterFatty::Save ( idSaveGame *savefile ) const { + int i; + + actionWhipAttack.Save( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->WriteBool( chains[i].out ); + chains[i].projectile.Save ( savefile ); + } +} + +/* +================ +rvMonsterFatty::Restore +================ +*/ +void rvMonsterFatty::Restore ( idRestoreGame *savefile ) { + int i; + + actionWhipAttack.Restore( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->ReadBool( chains[i].out ); + chains[i].projectile.Restore ( savefile ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::UpdateAnimationControllers +================ +*/ +bool rvMonsterFatty::UpdateAnimationControllers ( void ) { + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + if ( enemy.ent && CheckFOV ( enemy.lastKnownPosition ) ) { + int i; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( enemy.ent->IsType ( idActor::GetClassType ( ) ) ) { + target = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + target = enemy.ent->GetPhysics()->GetOrigin ( ); + } + + if ( !IsEnemyVisible ( ) ) { + target -= enemy.ent->GetPhysics()->GetGravityNormal() * missHeight; + } + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + if ( !chains[i].out ) { + continue; + } + + animator.ClearJoint ( chains[i].orientationJoint ); + GetJointWorldTransform ( chains[i].orientationJoint, gameLocal.time, origin, axis ); + dir = target - origin; + dir.Normalize ( ); + axis.ProjectVector ( dir, localDir ); + animator.SetJointAxis ( chains[i].orientationJoint, JOINTMOD_LOCAL, localDir.ToMat3() ); + } + } + + return true; +} + +/* +================ +rvMonsterFatty::AddDamageEffect +================ +*/ +void rvMonsterFatty::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + /* + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + */ +} + +/* +================ +rvMonsterFatty::CheckAction_WhipAttack +================ +*/ +bool rvMonsterFatty::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterFatty::CheckActions +================ +*/ +bool rvMonsterFatty::CheckActions ( void ) { + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterFatty::CheckAction_WhipAttack, NULL ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterFatty::PlayAttackAnim +================ +*/ +void rvMonsterFatty::PlayAttackAnim ( const idVec3& target, int blendFrames ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -45.0f ) { + animName = "attack4b"; + } else if ( yaw < -20.0f ) { + animName = "attack3b"; + } else if ( yaw < -5.0f ) { + animName = "attack5r"; + } else if ( yaw < 5.0f ) { + animName = "attack5"; + } else if ( yaw < 20.0f ) { + animName = "attack5l"; + } else if ( yaw < 45.0f ) { + animName = "attack2b"; + } else{ + animName = "attack1b"; + } + + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); +} + +/* +================ +rvMonsterFatty::ChainOut +================ +*/ +void rvMonsterFatty::ChainOut ( int chain ) { + idEntity* ent; + idProjectile* proj; + + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_hook" ) ), &ent, false ); + proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + return; + } + + chains[chain].out = true; + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + chains[chain].projectile = proj; + ent->BindToJoint ( this, chains[chain].attackJoint, false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); +} + +/* +================ +rvMonsterFatty::ChainIn +================ +*/ +void rvMonsterFatty::ChainIn ( int chain ) { + chains[chain].out = false; + if ( chains[chain].projectile ) { + delete chains[chain].projectile; + chains[chain].projectile = NULL; + } +} + +/* +================ +rvMonsterFatty::ResetChains +================ +*/ +void rvMonsterFatty::ResetAllChains ( void ) { + int i; + + animator.ClearAllJoints ( ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + ChainIn ( i ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFatty ) + STATE ( "Torso_WhipAttack", rvMonsterFatty::State_Torso_WhipAttack ) + + STATE ( "Frame_LeftChainOut", rvMonsterFatty::Frame_LeftChainOut ) + STATE ( "Frame_LeftChainIn", rvMonsterFatty::Frame_LeftChainIn ) + + STATE ( "Frame_RightChainOut", rvMonsterFatty::Frame_RightChainOut ) + STATE ( "Frame_RightChainIn", rvMonsterFatty::Frame_RightChainIn ) +END_CLASS_STATES + + +/* +================ +rvMonsterFatty::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterFatty::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + PlayAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ResetAllChains ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_RIGHT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_RIGHT ); + return SRESULT_OK; +} diff --git a/source/game/ai/Monster_Gladiator.cpp b/source/game/ai/Monster_Gladiator.cpp new file mode 100644 index 0000000..02f4806 --- /dev/null +++ b/source/game/ai/Monster_Gladiator.cpp @@ -0,0 +1,902 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class rvMonsterGladiator : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGladiator ); + + rvMonsterGladiator ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual int GetDamageForLocation( int damage, int location ); + +// virtual void SetTether ( rvAITether* newTether ); + +protected: + + // Actions + rvAIAction actionRailgunAttack; + + // Blaster attack + int maxShots; + int minShots; + int shots; + int lastShotTime; + + // Shield + bool usingShield; + idEntityPtr shield; + int shieldStartTime; + int shieldWaitTime; + int shieldHitDelay; + //int shieldInDelay; + //int shieldFov; + int shieldHealth; + int shieldConsecutiveHits; + int shieldLastHitTime; + + int railgunHealth; + int railgunDestroyedTime; + int nextTurnTime; + + virtual bool CheckActions ( void ); + void ShowShield ( void ); + void HideShield ( int hideTime=0 ); + void DestroyRailgun ( void ); + +private: + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso states + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldStart ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldEnd ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldFire ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after railgun is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterGladiator ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGladiator ) +END_CLASS + +/* +================ +rvMonsterGladiator::rvMonsterGladiator +================ +*/ +rvMonsterGladiator::rvMonsterGladiator ( ) { + usingShield = false; +} + +void rvMonsterGladiator::InitSpawnArgsVariables ( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + shieldHitDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldHitDelay", "1" ) ); +// shieldInDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldInDelay", "3" ) ); +// shieldFov = spawnArgs.GetInt ( "shieldfov", "90" ); +} +/* +================ +rvMonsterGladiator::Spawn +================ +*/ +void rvMonsterGladiator::Spawn ( void ) { + shieldWaitTime = 0; + shieldStartTime = 0; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + + InitSpawnArgsVariables(); + + shots = 0; + lastShotTime = 0; + + railgunHealth = spawnArgs.GetInt ( "railgunHealth", "100" ); + railgunDestroyedTime = 0; + + actionRailgunAttack.Init ( spawnArgs, "action_railgunAttack", "Torso_RailgunAttack", AIACTIONF_ATTACK ); + + // Disable range attack until using shield + //actionRangedAttack.fl.disabled = true; + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterGladiator::CheckActions + +Overriden to handle taking the shield out and putting it away. Will also ensure the gladiator +stays hidden behind his shield if getting shot at. +================ +*/ +bool rvMonsterGladiator::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f || (turnYaw > 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f || (turnYaw < 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + // Limited actions with shield out + if ( usingShield ) { + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + if ( move.moveCommand == MOVE_TO_ENEMY + && move.fl.moving ) + {//advancing on enemy with shield up + if ( gameLocal.GetTime() - lastShotTime > 1500 ) + {//been at least a second since the last time we fired while moving + if ( !gameLocal.random.RandomInt(2) ) + {//fire! + PerformAction ( "Torso_ShieldFire", 0, true ); + return true; + } + } + } + // Only ranged attack and melee attack are available when using shield + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + ( railgunHealth > 0 + && gameLocal.GetTime() - shieldStartTime > 2000 + && gameLocal.time - pain.lastTakenTime > 500 + && gameLocal.time - combat.shotAtTime > 300 + && gameLocal.GetTime() - shieldLastHitTime > 500 + && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) ) { + shieldWaitTime = 0; + return true; + } + + // see if it's safe to lower it? + if ( gameLocal.GetTime() - shieldStartTime > 2000 ) + {//shield's been up for at least 2 seconds + if ( !enemy.fl.visible || (gameLocal.time - combat.shotAtTime > 1000 && gameLocal.GetTime() - shieldLastHitTime > 1500) ) + { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) + { + PerformAction ( "Torso_ShieldEnd", 4, true ); + return true; + } + } + } + + return false; + } + else + {// Bring the shield out? + if ( combat.tacticalCurrent != AITACTICAL_MELEE || move.fl.done ) + {//not while rushing (NOTE: unless railgun was just destroyed?) + if ( enemy.fl.visible && enemy.fl.inFov ) + { + if ( combat.fl.aware && shieldWaitTime < gameLocal.GetTime() ) + { + if ( gameLocal.time - pain.lastTakenTime <= 1500 + || ( combat.shotAtAngle < 0 && gameLocal.time - combat.shotAtTime < 100 ) + || !gameLocal.random.RandomInt( 20 ) ) + { + if ( !gameLocal.random.RandomInt( 5 ) ) + { + PerformAction ( "Torso_ShieldStart", 4, true ); + return true; + } + } + } + } + } + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterGladiator::ShowShield +================ +*/ +void rvMonsterGladiator::ShowShield ( void ) { + // First time? + if ( !shield ) { + idEntity* ent; + idDict args; + const idDict *shieldDef = gameLocal.FindEntityDefDict( spawnArgs.GetString ( "def_shield" ), false ); + args.Set ( "classname", spawnArgs.GetString ( "def_shield" ) ); + if ( gameLocal.SpawnEntityDef( args, &ent ) ) { + shield = ent; + ent->GetPhysics()->SetClipMask ( 0 ); + ent->GetPhysics()->SetContents ( CONTENTS_RENDERMODEL ); + ent->GetPhysics()->GetClipModel ( )->SetOwner ( this ); + Attach ( ent ); + } + if ( !shield ) { + return; + } + if ( shieldDef && shield->IsType( idAFAttachment::GetClassType() ) ) + { + idAFAttachment* afShield = static_cast(shield.GetEntity()); + if ( afShield ) + { + jointHandle_t joint = animator.GetJointHandle( shieldDef->GetString( "joint" ) ); + afShield->SetBody ( this, shieldDef->GetString( "model" ), joint ); + } + } + } else if ( !shield || !shield->IsHidden() ) { + return; + } + + usingShield = true; + shieldWaitTime = 0; + animPrefix = "shield"; + shieldStartTime = gameLocal.time; +// actionRangedAttack.fl.disabled = false; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StartSound ( "snd_shield_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + + shield->Show ( ); + + SetShaderParm ( 6, gameLocal.time + 2000 ); +} + +/* +================ +rvMonsterGladiator::HideShield +================ +*/ +void rvMonsterGladiator::HideShield ( int hideTime ) { + if ( !shield || shield->IsHidden() ) { + return; + } + + usingShield = false; + animPrefix = ""; + shieldWaitTime = gameLocal.GetTime()+hideTime; +// actionRangedAttack.fl.disabled = true; + shieldHealth = 0; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StopSound ( SND_CHANNEL_ITEM, false ); + + shield->Hide ( ); +} + +/* +================ +rvMonsterGladiator::DestroyRailgun +================ +*/ +void rvMonsterGladiator::DestroyRailgun ( void ) { + HideSurface ( "models/monsters/gladiator/glad_railgun" ); + railgunHealth = -1; + + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_railgun_explode", "gun_main_jt" ) ); + GetJointWorldTransform ( joint, gameLocal.time, origin, axis ); + gameLocal.PlayEffect ( spawnArgs, "fx_railgun_explode", origin, axis ); + PlayEffect ( "fx_railgun_burn", joint, true ); + + GetAFPhysics()->GetBody ( "b_railgun" )->SetClipMask ( 0 ); + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "pain_big"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + railgunDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + actionRailgunAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + actionRangedAttack.chance = 0.25f; + actionRangedAttack.maxRange = 400; + minShots = 5; + maxShots = 15; + + combat.tacticalMaskAvailable &= ~AITACTICAL_HIDE_BIT; + + //temporarily disable this so we can charge and get mad + //FIXME: force MELEE + actionRangedAttack.timer.Add( 6000 ); + actionTimerRangedAttack.Add( 6000 ); + actionMeleeAttack.timer.Reset( actionTime ); + + //drop any tether since we need to advance + //SetTether(NULL); + //nevermind: let scripters handle it + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +================ +rvMonsterGladiator::UpdateRunStatus +================ +*/ +bool rvMonsterGladiator::UpdateRunStatus ( void ) { + // If rushing and moving forward, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.currentDirection == MOVEDIR_FORWARD ) { + move.fl.idealRunning = true; + return move.fl.running != move.fl.idealRunning; + } + + // Alwasy walk with shield out + if ( usingShield ) { + move.fl.idealRunning = false; + return move.fl.running != move.fl.idealRunning; + } + + return idAI::UpdateRunStatus ( ); +} + +/* +============ +rvMonsterGladiator::SetTether +============ +*/ +/* +void rvMonsterGladiator::SetTether ( rvAITether* newTether ) { + if ( railgunHealth <= 0 ) { + //don't allow any tethers! + idAI::SetTether(NULL); + } else { + idAI::SetTether(newTether); + } +} +*/ + +/* +================ +rvMonsterGladiator::FilterTactical +================ +*/ +int rvMonsterGladiator::FilterTactical ( int availableTactical ) { + if ( railgunHealth > 0 ) { // Only let the gladiator rush when he is really close to his enemy + if ( !enemy.range || enemy.range > combat.awareRange ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } else { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } else if ( gameLocal.GetTime() - railgunDestroyedTime < 6000 ) { + availableTactical = AITACTICAL_MELEE_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGladiator::AddDamageEffect +================ +*/ +void rvMonsterGladiator::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( collision.c.material != NULL && (collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ) ) { + // Delay putting shield away and shooting until the shield hasnt been hit for a while + actionRangedAttack.timer.Reset ( actionTime ); + actionRangedAttack.timer.Add ( shieldHitDelay ); + shieldStartTime = gameLocal.time; + return; + } + + return idAI::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +rvMonsterGladiator::GetDamageForLocation +===================== +*/ +int rvMonsterGladiator::GetDamageForLocation( int damage, int location ) { + // If the gun was hit only do damage to it + if ( idStr::Icmp ( GetDamageGroup ( location ), "gun" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( railgunHealth > 0 ){ + railgunHealth -= damage; + if ( railgunHealth <= 0 ) { + DestroyRailgun ( ); + } + } + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterGladiator::CanTurn +================ +*/ +bool rvMonsterGladiator::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterGladiator::Damage +================ +*/ +void rvMonsterGladiator::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef ) + { + if ( usingShield ) + {//shield up + if ( !damageDef->GetString( "filter_electricity", NULL ) ) + {//not by electricity + //If we get hit enough times with shield up, charge forward + if ( gameLocal.GetTime() - shieldLastHitTime > 1500 ) + { + shieldConsecutiveHits = 0; + } + shieldConsecutiveHits++; + shieldLastHitTime = gameLocal.GetTime(); + if ( shieldConsecutiveHits > 20 && combat.tacticalCurrent != AITACTICAL_MELEE && move.fl.done ) + {//really laying into us, move up + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } + + if ( idStr::Icmp ( GetDamageGroup ( location ), "shield" ) == 0 ) + {//Hit in shield + if ( damageDef->GetString( "filter_electricity", NULL ) ) + {//by electricity + shieldHealth -= damageDef->GetInt( "damage" ) * damageScale; + if ( shield ) + { + shield->SetShaderParm( SHADERPARM_MODE, gameLocal.GetTime() + gameLocal.random.RandomInt(1000) + 1000 ); + } + StartSound( "snd_shield_flicker", SND_CHANNEL_ANY, 0, false, NULL ); + if ( shieldHealth <= 0 ) + {//drop it + HideShield( gameLocal.random.RandomInt(3000)+2000 );//FIXME: when it does come back on, flicker back on? + painAnim = "pain_con"; + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + combat.shotAtTime = gameLocal.GetTime(); + } + return; + } + } + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( aifl.pain ) + {//hurt + if ( usingShield ) + {//shield up + //move in! + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } +} + +/* +================ +rvMonsterGladiator::Save +================ +*/ +void rvMonsterGladiator::Save( idSaveGame *savefile ) const { + actionRailgunAttack.Save ( savefile ) ; + + savefile->WriteInt ( shots ); + savefile->WriteInt ( lastShotTime ); + + savefile->WriteBool ( usingShield ); + shield.Save( savefile ); + savefile->WriteInt ( shieldStartTime ); + savefile->WriteInt ( shieldWaitTime ); + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shieldConsecutiveHits ); + savefile->WriteInt ( shieldLastHitTime ); + + savefile->WriteInt ( railgunHealth ); + savefile->WriteInt ( railgunDestroyedTime ); + savefile->WriteInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Save( savefile ); +} + +/* +================ +rvMonsterGladiator::Restore +================ +*/ +void rvMonsterGladiator::Restore( idRestoreGame *savefile ) { + actionRailgunAttack.Restore ( savefile ) ; + + savefile->ReadInt ( shots ); + savefile->ReadInt ( lastShotTime ); + savefile->ReadBool ( usingShield ); + shield.Restore( savefile ); + savefile->ReadInt ( shieldStartTime ); + savefile->ReadInt ( shieldWaitTime ); + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shieldConsecutiveHits ); + savefile->ReadInt ( shieldLastHitTime ); + + savefile->ReadInt ( railgunHealth ); + savefile->ReadInt ( railgunDestroyedTime ); + savefile->ReadInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGladiator::GetDebugInfo +================ +*/ +void rvMonsterGladiator::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_RailgunAttack", aiActionStatusString[actionRailgunAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGladiator ) + STATE ( "State_Killed", rvMonsterGladiator::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterGladiator::State_Torso_BlasterAttack ) + STATE ( "Torso_RailgunAttack", rvMonsterGladiator::State_Torso_RailgunAttack ) + STATE ( "Torso_ShieldStart", rvMonsterGladiator::State_Torso_ShieldStart ) + STATE ( "Torso_ShieldEnd", rvMonsterGladiator::State_Torso_ShieldEnd ) + + STATE ( "Torso_TurnRight90", rvMonsterGladiator::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterGladiator::State_Torso_TurnLeft90 ) + STATE ( "Torso_ShieldFire", rvMonsterGladiator::State_Torso_ShieldFire ) + +END_CLASS_STATES + +/* +================ +rvMonsterGladiator::State_Killed +================ +*/ +stateResult_t rvMonsterGladiator::State_Killed ( const stateParms_t& parms ) { + HideShield ( ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldStart +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldStart ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + ShowShield ( ); + PlayAnim ( ANIMCHANNEL_TORSO, "start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + SetShaderParm ( 6, 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldEnd +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldEnd ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) //anim done + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + HideShield ( 2000 ); + actionRailgunAttack.timer.Reset( actionTime ); + actionMeleeAttack.timer.Reset( actionTime ); + actionRangedAttack.timer.Reset( actionTime ); + actionTimerRangedAttack.Reset( actionTime ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_start", parms.blendFrames ); + //shots = 4; + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_start" ) ) {//anim changed + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + if ( --shots <= 0 || !enemy.fl.inFov || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGladiator::State_Torso_RailgunAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_RailgunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( usingShield ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ShieldEnd", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RailgunAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "railgun_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "railgun_attack" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_right" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_left" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldFire +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldFire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + PlayCycle( ANIMCHANNEL_TORSO, "walk_aim", 1 ); + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + Attack( "blaster", animator.GetJointHandle( "lft_wrist_jt"), GetEnemy() ); + PlayEffect( "fx_blaster_flash", animator.GetJointHandle("lft_wrist_jt") ); + lastShotTime = gameLocal.GetTime(); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( move.fl.done ) + { + return SRESULT_DONE; + } + if ( (gameLocal.GetTime()-lastShotTime) >= 250 ) { + shots--; + if ( GetEnemy() && shots > 0 ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } + PlayCycle( ANIMCHANNEL_TORSO, "walk", 1 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_Grunt.cpp b/source/game/ai/Monster_Grunt.cpp new file mode 100644 index 0000000..e72980f --- /dev/null +++ b/source/game/ai/Monster_Grunt.cpp @@ -0,0 +1,318 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGrunt : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGrunt ); + + rvMonsterGrunt ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void AdjustHealthByDamage ( int damage ); + +protected: + + rvAIAction actionMeleeMoveAttack; + rvAIAction actionChaingunAttack; + + virtual bool CheckActions ( void ); + + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnDeath ( void ); + +private: + + int standingMeleeNoAttackTime; + int rageThreshold; + + void RageStart ( void ); + void RageStop ( void ); + + // Torso States + stateResult_t State_Torso_Enrage ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_LeapAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGrunt ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGrunt ) +END_CLASS + +/* +================ +rvMonsterGrunt::rvMonsterGrunt +================ +*/ +rvMonsterGrunt::rvMonsterGrunt ( void ) { + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterGrunt::Spawn +================ +*/ +void rvMonsterGrunt::Spawn ( void ) { + rageThreshold = spawnArgs.GetInt ( "health_rageThreshold" ); + + // Custom actions + actionMeleeMoveAttack.Init ( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", NULL, AIACTIONF_ATTACK ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", "Torso_LeapAttack", AIACTIONF_ATTACK ); + + // Enraged to start? + if ( spawnArgs.GetBool ( "preinject" ) ) { + RageStart ( ); + } +} + +/* +================ +rvMonsterGrunt::Save +================ +*/ +void rvMonsterGrunt::Save ( idSaveGame *savefile ) const { + actionMeleeMoveAttack.Save( savefile ); + actionChaingunAttack.Save( savefile ); + + savefile->WriteInt( rageThreshold ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::Restore +================ +*/ +void rvMonsterGrunt::Restore ( idRestoreGame *savefile ) { + actionMeleeMoveAttack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + savefile->ReadInt( rageThreshold ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::RageStart +================ +*/ +void rvMonsterGrunt::RageStart ( void ) { + SetShaderParm ( 6, 1 ); + + // Disable non-rage actions + actionEvadeLeft.fl.disabled = true; + actionEvadeRight.fl.disabled = true; + + // Speed up animations + animator.SetPlaybackRate ( 1.25f ); + + // Disable pain + pain.threshold = 0; + + // Start over with health when enraged + health = spawnArgs.GetInt ( "health" ); + + // No more going to rage + rageThreshold = 0; +} + +/* +================ +rvMonsterGrunt::RageStop +================ +*/ +void rvMonsterGrunt::RageStop ( void ) { + SetShaderParm ( 6, 0 ); +} + +/* +================ +rvMonsterGrunt::CheckActions +================ +*/ +bool rvMonsterGrunt::CheckActions ( void ) { + // If our health is below the rage threshold then enrage + if ( health < rageThreshold ) { + PerformAction ( "Torso_Enrage", 4, true ); + return true; + } + + // Moving melee attack? + if ( PerformAction ( &actionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + + // Default actions + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + //allow ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} + +/* +================ +rvMonsterGrunt::OnDeath +================ +*/ +void rvMonsterGrunt::OnDeath ( void ) { + RageStop ( ); + return idAI::OnDeath ( ); +} + +/* +================ +rvMonsterGrunt::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGrunt::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterGrunt::AdjustHealthByDamage +===================== +*/ +void rvMonsterGrunt::AdjustHealthByDamage ( int damage ) { + // Take less damage during enrage process + if ( rageThreshold && health < rageThreshold ) { + health -= (damage * 0.25f); + return; + } + return idAI::AdjustHealthByDamage ( damage ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGrunt ) + STATE ( "Torso_Enrage", rvMonsterGrunt::State_Torso_Enrage ) + STATE ( "Torso_Pain", rvMonsterGrunt::State_Torso_Pain ) + STATE ( "Torso_LeapAttack", rvMonsterGrunt::State_Torso_LeapAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGrunt::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Pain ( const stateParms_t& parms ) { + // Stop streaming pain if its time to get angry + if ( pain.loopEndTime && health < rageThreshold ) { + pain.loopEndTime = 0; + } + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterGrunt::State_Torso_Enrage +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Enrage ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "anger", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + RageStart ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGrunt::State_Torso_LeapAttack +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_LeapAttack ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + lastAttackTime = 0; + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + // If we missed our leap attack get angry + if ( !lastAttackTime && rageThreshold ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Enrage", parms.blendFrames ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_Gunner.cpp b/source/game/ai/Monster_Gunner.cpp new file mode 100644 index 0000000..155e8a5 --- /dev/null +++ b/source/game/ai/Monster_Gunner.cpp @@ -0,0 +1,448 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGunner : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGunner ); + + rvMonsterGunner ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int shots; + int shotsFired; + idStr nailgunPrefix; + int nailgunMinShots; + int nailgunMaxShots; + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + // Actions + rvAIAction actionGrenadeAttack; + rvAIAction actionNailgunAttack; + + rvAIAction actionSideStepLeft; + rvAIAction actionSideStepRight; + rvAIActionTimer actionTimerSideStep; + + bool CheckAction_SideStepLeft ( rvAIAction* action, int animNum ); + bool CheckAction_SideStepRight ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_NailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGunner ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGunner ) +END_CLASS + +/* +================ +rvMonsterGunner::rvMonsterGunner +================ +*/ +rvMonsterGunner::rvMonsterGunner ( ) { + nextShootTime = 0; +} + + +void rvMonsterGunner::InitSpawnArgsVariables( void ) +{ + nailgunMinShots = spawnArgs.GetInt ( "action_nailgunAttack_minshots", "5" ); + nailgunMaxShots = spawnArgs.GetInt ( "action_nailgunAttack_maxshots", "20" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.3" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterGunner::Spawn +================ +*/ +void rvMonsterGunner::Spawn ( void ) { + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + actionNailgunAttack.Init ( spawnArgs, "action_nailgunAttack", "Torso_NailgunAttack", AIACTIONF_ATTACK ); + actionSideStepLeft.Init ( spawnArgs, "action_sideStepLeft", NULL, 0 ); + actionSideStepRight.Init ( spawnArgs, "action_sideStepRight", NULL, 0 ); + actionTimerSideStep.Init ( spawnArgs, "actionTimer_sideStep" ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGunner::Save +================ +*/ +void rvMonsterGunner::Save ( idSaveGame *savefile ) const { + actionGrenadeAttack.Save ( savefile ); + actionNailgunAttack.Save ( savefile ); + actionSideStepLeft.Save ( savefile ); + actionSideStepRight.Save ( savefile ); + actionTimerSideStep.Save ( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + savefile->WriteString ( nailgunPrefix ); + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterGunner::Restore +================ +*/ +void rvMonsterGunner::Restore ( idRestoreGame *savefile ) { + actionGrenadeAttack.Restore ( savefile ); + actionNailgunAttack.Restore ( savefile ); + actionSideStepLeft.Restore ( savefile ); + actionSideStepRight.Restore ( savefile ); + actionTimerSideStep.Restore ( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + savefile->ReadString ( nailgunPrefix ); + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterGunner::OnStopMoving +============ +*/ +void rvMonsterGunner::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + {//hiding + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE || enemy.range <= combat.meleeRange ) + {//in melee state or in melee range + actionMeleeAttack.timer.Clear( actionTime ); + } + else if ( (!actionNailgunAttack.timer.IsDone(actionTime) || !actionTimerRangedAttack.IsDone(actionTime)) + && (!actionGrenadeAttack.timer.IsDone(actionTime) || !actionTimerSpecialAttack.IsDone(actionTime)) ) + {//no attack is ready + //Ready at least one of them + if ( gameLocal.random.RandomInt(3) ) + { + actionNailgunAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + } + else + { + actionGrenadeAttack.timer.Clear( actionTime ); + actionTimerSpecialAttack.Clear( actionTime ); + } + } + } +} + +/* +================ +rvMonsterGunner::UpdateRunStatus +================ +*/ +bool rvMonsterGunner::UpdateRunStatus ( void ) { + move.fl.idealRunning = false; + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterGunner::FilterTactical +================ +*/ +int rvMonsterGunner::FilterTactical ( int availableTactical ) { + if ( !move.fl.moving && enemy.range > combat.meleeRange ) + {//keep moving! + if ( (!actionNailgunAttack.timer.IsDone(actionTime+500) || !actionTimerRangedAttack.IsDone(actionTime+500)) + && (!actionGrenadeAttack.timer.IsDone(actionTime+500) || !actionTimerSpecialAttack.IsDone(actionTime+500)) ) + {//won't be attacking in the next 1 second + combat.tacticalUpdateTime = 0; + availableTactical |= (AITACTICAL_MELEE_BIT); + if ( !gameLocal.random.RandomInt(2) ) + { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGunner::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGunner::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + //walk for at least 2 seconds (default update time of 500 is too short) + combat.tacticalUpdateTime = gameLocal.GetTime() + 2000 + gameLocal.random.RandomInt(1000); + break; + } +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepLeft +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepLeft ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the left? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepRight +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepRight ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove ( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the right? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckActions +================ +*/ +bool rvMonsterGunner::CheckActions ( void ) { + // Fire a grenade? + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) || + PerformAction ( &actionNailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + bool action = idAI::CheckActions( ); + if ( !action ) { + //try a strafe + if ( GetEnemy() && enemy.fl.visible && gameLocal.GetTime()-lastAttackTime > actionTimerRangedAttack.GetRate()+1000 ) { + //we can see our enemy but haven't been able to shoot him in a while... + if ( PerformAction ( &actionSideStepLeft, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepLeft, &actionTimerSideStep ) + || PerformAction ( &actionSideStepRight, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepRight, &actionTimerSideStep ) ) { + return true; + } + } + } + return action; +} + +/* +===================== +rvMonsterGunner::GetDebugInfo +===================== +*/ +void rvMonsterGunner::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_grenadeAttack", aiActionStatusString[actionGrenadeAttack.status], userData ); + proc ( "idAI", "action_nailgunAttack", aiActionStatusString[actionNailgunAttack.status], userData ); + proc ( "idAI", "actionSideStepLeft", aiActionStatusString[actionSideStepLeft.status], userData ); + proc ( "idAI", "actionSideStepRight", aiActionStatusString[actionSideStepRight.status], userData ); +} + +bool rvMonsterGunner::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + actionTimerRangedAttack.Clear( actionTime ); + actionNailgunAttack.timer.Clear( actionTime ); + return (idAI::Pain( inflictor, attacker, damage, dir, location )); +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGunner ) + STATE ( "Torso_NailgunAttack", rvMonsterGunner::State_Torso_NailgunAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterGunner::State_Torso_MovingRangedAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGunner::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "nail", attackJoint, GetEnemy() ); + PlayEffect( "fx_nail_flash", attackJoint ); + } + StartSound( "snd_nailgun_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= nailgunMinShots) || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGunner::State_Torso_NailgunAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_NailgunAttack ( const stateParms_t& parms ) { + static const char* nailgunAnims [ ] = { "nailgun_short", "nailgun_long" }; + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && !actionNailgunAttack.fl.overrideLegs && FacingIdeal() && !gameLocal.random.RandomInt(1) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + DisableAnimState ( ANIMCHANNEL_LEGS ); + shotsFired = 0; + nailgunPrefix = nailgunAnims[shots%2]; + if ( !CanHitEnemyFromAnim( GetAnim( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ) ) ) ) + {//this is hacky, but we really need to test the attack anim first since they're so different + //can't hit with this one, just use the other one... + nailgunPrefix = nailgunAnims[(shots+1)%2]; + } + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_start", nailgunPrefix.c_str() ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ), 0 ); + shotsFired++; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (shotsFired >= nailgunMinShots && !enemy.fl.inFov) || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_end", nailgunPrefix.c_str() ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_Harvester.cpp b/source/game/ai/Monster_Harvester.cpp new file mode 100644 index 0000000..dd6ccb7 --- /dev/null +++ b/source/game/ai/Monster_Harvester.cpp @@ -0,0 +1,1190 @@ +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +class rvMonsterHarvester : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvester ); + + rvMonsterHarvester ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + + virtual bool UpdateAnimationControllers ( void ); + bool CanTurn ( void ) const; + virtual int GetDamageForLocation ( int damage, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + enum { + WHIP_LEFT, + WHIP_CENTER, + WHIP_RIGHT, + WHIP_MAX + }; + + enum { + PART_ARM_R, + PART_ARM_L, + PART_LEG_FR, + PART_LEG_FL, + PART_LEG_BR, + PART_LEG_BL, + PART_TANK_R, + PART_TANK_L, + PARTS_MAX + }; + idStr partLocation[PARTS_MAX]; + idStr partSurf[PARTS_MAX]; + idStr partJoint[PARTS_MAX]; + int partHealth[PARTS_MAX]; + void DestroyPart ( int part ); + + rvAIAction actionWhipAttack; + rvAIAction actionSprayAttack; + rvAIAction actionRocketAttack; + rvAIAction actionGrenadeAttack; + + jointHandle_t whipJoints[WHIP_MAX]; + idEntityPtr whipProjectiles[WHIP_MAX]; + + jointHandle_t jointLeftMuzzle; + jointHandle_t jointRightMuzzle; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + const char* GetMeleeAttackAnim ( const idVec3& target ); + bool PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ); + const char* GetRangedAttackAnim ( const idVec3& target ); + bool PlayRangedAttackAnim ( const idVec3& target, int blendFrames ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + + void DropLeg ( int part ); +// Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RocketAttack( rvAIAction* action, int animNum ); + bool CheckAction_GrenadeAttack( rvAIAction* action, int animNum ); + + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ClawAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvester ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvester ) +END_CLASS + +/* +================ +rvMonsterHarvester::rvMonsterHarvester +================ +*/ +rvMonsterHarvester::rvMonsterHarvester ( void ) { +} + +void rvMonsterHarvester::InitSpawnArgsVariables( void ) +{ + whipJoints[WHIP_LEFT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_left" ) ); + whipJoints[WHIP_RIGHT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_right" ) ); + whipJoints[WHIP_CENTER] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_center" ) ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partLocation[part] = spawnArgs.GetString( va("part_%d_location",part), "" ); + partSurf[part] = spawnArgs.GetString( va("part_%d_surf",part), "" ); + partJoint[part] = spawnArgs.GetString( va("part_%d_joint",part), "" ); + } + + jointLeftMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_left_arm" ) ); + jointRightMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_right_arm" ) ); +} +/* +================ +rvMonsterHarvester::Spawn +================ +*/ +void rvMonsterHarvester::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + whipProjectiles[i] = NULL; + } + + InitSpawnArgsVariables(); + shots = 0; + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partHealth[part] = spawnArgs.GetInt( va("part_%d_health",part), "500" ); + } +} + +/* +================ +rvMonsterHarvester::Save +================ +*/ +void rvMonsterHarvester::Save ( idSaveGame *savefile ) const { + actionWhipAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->WriteObject( whipProjectiles[i] ); + } + + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->WriteInt( partHealth[part] ); + } +} + +/* +================ +rvMonsterHarvester::Restore +================ +*/ +void rvMonsterHarvester::Restore ( idRestoreGame *savefile ) { + actionWhipAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->ReadObject( reinterpret_cast( whipProjectiles[i] ) ); + } + + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->ReadInt( partHealth[part] ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterHarvester::UpdateAnimationControllers +================ +*/ +bool rvMonsterHarvester::UpdateAnimationControllers ( void ) { + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + return true; +} + +/* +===================== +rvMonsterHarvester::SkipImpulse +===================== +*/ +bool rvMonsterHarvester::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +void rvMonsterHarvester::DropLeg( int part ) +{ + jointHandle_t joint = INVALID_JOINT; + const char* legDef = NULL; + switch ( part ) + { + case PART_LEG_FR: + joint = animator.GetJointHandle( "r_toe_front" ); + legDef = "def_leg_part1"; + break; + case PART_LEG_FL: + joint = animator.GetJointHandle( "l_toe_front" ); + legDef = "def_leg_part2"; + break; + case PART_LEG_BR: + joint = animator.GetJointHandle( "r_toe_back" ); + legDef = "def_leg_part4"; + break; + case PART_LEG_BL: + joint = animator.GetJointHandle( "l_toe_back" ); + legDef = "def_leg_part3"; + break; + } + if ( joint != INVALID_JOINT ) + { + idEntity* leg = gameLocal.SpawnEntityDef( spawnArgs.GetString( legDef ) ); + if ( leg ) + { + idVec3 jointOrg; + idMat3 jointAxis; + animator.GetJointTransform( joint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + leg->GetPhysics()->SetOrigin( jointOrg ); + leg->GetPhysics()->SetAxis( jointAxis*renderEntity.axis ); + leg->PlayEffect( "fx_trail", vec3_origin, jointAxis, true, vec3_origin, true ); + if ( leg->IsType( idDamagable::GetClassType() ) ) + {//don't be destroyed for at least 5 seconds + ((idDamagable*)leg)->invincibleTime = gameLocal.GetTime() + 5000; + } + // push it + if ( jointOrg.z > GetPhysics()->GetOrigin().z+20.0f ) + {//leg was blown off while in the air... + jointHandle_t attachJoint = animator.GetJointHandle(partJoint[part].c_str()); + if ( attachJoint != INVALID_JOINT ) + { + animator.GetJointTransform( attachJoint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + + idVec3 impulse = leg->GetPhysics()->GetCenterMass() - jointOrg; + impulse.z = 0; + impulse.Normalize(); + impulse *= ((gameLocal.random.RandomFloat()*3.0f)+2.0f) * 1000;//away + impulse.z = (gameLocal.random.CRandomFloat()*500.0f)+1000.0f;//up! + leg->ApplyImpulse( this, 0, jointOrg, impulse ); + } + } + } + } +} + +void rvMonsterHarvester::DestroyPart( int part ) +{ + idStr explodeFX; + idStr trailFX; + switch ( part ) + { + case PART_ARM_R: + if ( partHealth[PART_ARM_L] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + case PART_ARM_L: + if ( partHealth[PART_ARM_R] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + //FIXME: spawn leg func_movables + case PART_LEG_FR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_frt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_FL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_flt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_brt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_blt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_TANK_R: + if ( partHealth[PART_TANK_L] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + case PART_TANK_L: + if ( partHealth[PART_TANK_R] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + } + HideSurface( partSurf[part].c_str() ); + PlayEffect( explodeFX, animator.GetJointHandle(partJoint[part].c_str()) ); + PlayEffect( trailFX, animator.GetJointHandle(partJoint[part].c_str()), true ); + //make sure it plays this pain + actionTimerPain.Reset ( actionTime ); +} + +/* +================ +rvMonsterHarvester::CanTurn +================ +*/ +bool rvMonsterHarvester::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); +} + +/* +===================== +rvMonsterHarvester::GetDamageForLocation +===================== +*/ +int rvMonsterHarvester::GetDamageForLocation( int damage, int location ) { + // If the part was hit only do damage to it + const char* dmgGroup = GetDamageGroup ( location ); + if ( dmgGroup ) + { + for ( int part = 0; part < PARTS_MAX; part++ ) + { + if ( idStr::Icmp ( dmgGroup, partLocation[part].c_str() ) == 0 ) + { + if ( partHealth[part] > 0 ) + { + partHealth[part] -= damage; + painAnim = "pain"; + if ( partHealth[part] <= 0 ) + { + if ( animPrefix.Length() + && (part == PART_LEG_FR + || part == PART_LEG_FL + || part == PART_LEG_BR + || part == PART_LEG_BL ) ) + {//just blew off a leg and already had one blown off... + DestroyPart( part ); + //we dead + health = 0; + return damage; + } + else + { + //FIXME: big pain? + DestroyPart( part ); + } + } + else + { + if ( !animPrefix.Length() ) + { + switch ( part ) + { + case PART_LEG_FR: + painAnim = "leg_pain_fr"; + break; + case PART_LEG_FL: + painAnim = "leg_pain_fl"; + break; + case PART_LEG_BR: + painAnim = "leg_pain_br"; + break; + case PART_LEG_BL: + painAnim = "leg_pain_bl"; + break; + } + } + } + + if ( pain.threshold < damage && health > 0 ) + //if ( move.anim_turn_angles == 0.0f ) + {//not in the middle of a turn + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + } + //pain.takenThisFrame = damage; + return 0; + } + } + } + + if ( health <= spawnArgs.GetInt( "death_damage_threshold" ) + && spawnArgs.GetInt( "death_damage_threshold" ) > damage ) + {//doesn't meet the minimum damage requirements to kill us + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterHarvester::Damage +================ +*/ +void rvMonsterHarvester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + //don't take damage from ourselves + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHarvester::CheckAction_SprayAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !CheckFOV( GetEnemy()->GetEyePosition(), 20.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RocketAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RocketAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_GrenadeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_GrenadeAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_WhipAttack +================:: +*/ +bool rvMonsterHarvester::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_MeleeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 90 ) ) { + return false; + } + if ( !GetMeleeAttackAnim( enemy.ent->GetEyePosition() ) ) + { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( idAI::CheckAction_RangedAttack(action,animNum) && enemy.ent && GetRangedAttackAnim( enemy.ent->GetEyePosition() ) ); +} + +/* +================ +rvMonsterHarvester::CheckActions +================ +*/ +bool rvMonsterHarvester::CheckActions ( void ) { + + // such a dirty hack... I'm not sure what is actually wrong, but somehow nextTurnTime is getting to be a rediculously high number. + // I have some more significant bugs that really need to be solved, so for now, this will have to do. + if ( nextTurnTime > gameLocal.time + 500 ) { + nextTurnTime = gameLocal.time-1; + } + + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.6f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.6f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_WhipAttack, NULL ) ) { + return true; + } + if ( PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_SprayAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_RocketAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_GrenadeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterHarvester::FilterTactical +================ +*/ +int rvMonsterHarvester::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT|AITACTICAL_RANGED_BITS|AITACTICAL_MELEE_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvester ) + STATE ( "State_Killed", rvMonsterHarvester::State_Killed ) + STATE ( "State_Dead", rvMonsterHarvester::State_Dead ) + + STATE ( "Torso_WhipAttack", rvMonsterHarvester::State_Torso_WhipAttack ) + STATE ( "Torso_ClawAttack", rvMonsterHarvester::State_Torso_ClawAttack ) + STATE ( "Torso_RangedAttack", rvMonsterHarvester::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvester::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvester::State_Torso_TurnLeft90 ) + STATE ( "Torso_SprayAttack", rvMonsterHarvester::State_Torso_SprayAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHarvester::State_Torso_RocketAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvester::State_Killed +================ +*/ +stateResult_t rvMonsterHarvester::State_Killed ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + int numLegsLost = 0; + for ( int i = PART_LEG_FR; i <= PART_LEG_BL; i++ ) { + if ( partHealth[i] <= 0 ) { + numLegsLost++; + } + } + if ( numLegsLost > 1 ) { + //dmg_death when 2 legs are blown off + PlayAnim( ANIMCHANNEL_TORSO, "dmg_death", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "death", 0 ); + } + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterHarvester::State_Dead +================ +*/ +stateResult_t rvMonsterHarvester::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterHarvester::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + idEntity* ent; + int i; + + for ( i = 0; i < WHIP_MAX; i ++ ) { + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_whip" ) ), &ent, false ); + idProjectile* proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + continue; + } + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + whipProjectiles[i] = proj; + ent->BindToJoint ( this, whipJoints[i], false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + delete whipProjectiles[i]; + whipProjectiles[i] = NULL; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvMonsterHarvester::Attack +===================== +*/ +bool rvMonsterHarvester::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + //NOTE: this stops spawning of projectile, but not muzzle flash... oh well... + if ( joint == jointLeftMuzzle && partHealth[PART_ARM_L] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + if ( joint == jointRightMuzzle && partHealth[PART_ARM_R] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + return idAI::Attack( attackName, joint, target, pushVelocity ); +} + +/* +================ +rvMonsterHarvester::GetMeleeAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -10.0f ) { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw_rt"; + } else if ( yaw > 10.0f ) { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw_lt"; + } else{ + if ( gameLocal.random.RandomFloat() < 0.5f || partHealth[PART_LEG_FR] <= 0 ) + { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw"; + } + else + { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw"; + } + } + return animName; +} +/* +================ +rvMonsterHarvester::PlayMeleeAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetMeleeAttackAnim( target ); + if ( animName ) + { + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::GetRangedAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetRangedAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName = NULL; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -20.0f ) { + if ( partHealth[PART_ARM_R] <= 0 ) + { + return NULL; + } + animName = "fire_right"; + } else if ( yaw > 20.0f ) { + if ( partHealth[PART_ARM_L] <= 0 ) + { + return NULL; + } + animName = "fire_left"; + } else{ + animName = "fire_forward"; + } + + return animName; +} + +/* +================ +rvMonsterHarvester::PlayRangedAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayRangedAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetRangedAttackAnim( target ); + if ( animName ) + { + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::State_Torso_ClawAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_ClawAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + if ( !PlayMeleeAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ) ) + { + return SRESULT_DONE; + } + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent || !PlayRangedAttackAnim ( enemy.ent->GetEyePosition(), 0 ) ) + { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) + { + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_STAGE ( STAGE_FIRE ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "missile_fire_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "missile_fire_start", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + if ( partHealth[PART_TANK_L] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("l_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("l_hopper_muzzle_flash"), enemy.ent ); + } + if ( partHealth[PART_TANK_R] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("r_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("r_hopper_muzzle_flash"), enemy.ent ); + } + + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_DONE; + } + + PlayAnim ( ANIMCHANNEL_TORSO, "attack_rocket", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "attack_rocket", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_HarvesterDispersal.cpp b/source/game/ai/Monster_HarvesterDispersal.cpp new file mode 100644 index 0000000..132ad41 --- /dev/null +++ b/source/game/ai/Monster_HarvesterDispersal.cpp @@ -0,0 +1,394 @@ +/* +================ +rvMonsterHarvesterDispersal.cpp + +AI for the Harvester on dispersal +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterHarvesterDispersal : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvesterDispersal ); + + rvMonsterHarvesterDispersal ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + rvAIAction actionSprayScream; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + +// Custom actions + virtual bool CheckAction_SprayScream( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_SprayScream ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvesterDispersal ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvesterDispersal ) +END_CLASS + +/* +================ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal +================ +*/ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal ( void ) { +} + +void rvMonsterHarvesterDispersal::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterHarvesterDispersal::Spawn +================ +*/ +void rvMonsterHarvesterDispersal::Spawn ( void ) { + actionSprayScream.Init ( spawnArgs, "action_sprayScream", "State_SprayScream", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterHarvesterDispersal::Save +================ +*/ +void rvMonsterHarvesterDispersal::Save ( idSaveGame *savefile ) const { + actionSprayScream.Save( savefile ); + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); +} + +/* +================ +rvMonsterHarvesterDispersal::Restore +================ +*/ +void rvMonsterHarvesterDispersal::Restore ( idRestoreGame *savefile ) { + actionSprayScream.Restore( savefile ); + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvMonsterHarvesterDispersal::SkipImpulse +===================== +*/ +bool rvMonsterHarvesterDispersal::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +/* +================ +rvMonsterHarvesterDispersal::CanTurn +================ +*/ +bool rvMonsterHarvesterDispersal::CanTurn ( void ) const { + return false; + /* + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); + */ +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( enemy.ent && idAI::CheckAction_RangedAttack( action, animNum ) && IsEnemyRecentlyVisible( ) ); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_SprayScream +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_SprayScream ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + return (!IsEnemyRecentlyVisible()); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckActions +================ +*/ +bool rvMonsterHarvesterDispersal::CheckActions ( void ) { + + // If not moving, try turning in place + /* + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.8f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.8f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + */ + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( idAI::CheckActions() ) { + return true; + } + + if ( PerformAction ( &actionSprayScream, (checkAction_t)&rvMonsterHarvesterDispersal::CheckAction_SprayScream ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterHarvesterDispersal::FilterTactical +================ +*/ +int rvMonsterHarvesterDispersal::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvesterDispersal ) + STATE ( "Torso_RangedAttack", rvMonsterHarvesterDispersal::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ) + STATE ( "State_SprayScream", rvMonsterHarvesterDispersal::State_SprayScream ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent ) + { + return SRESULT_DONE; + } + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_SprayScream ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH, + STAGE_SCREAM, + STAGE_FINISH_SCREAM + }; + if ( parms.stage < STAGE_SCREAM && IsEnemyRecentlyVisible() ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + lookTarget = GetEnemy(); + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) { + return SRESULT_STAGE ( STAGE_SWEEP ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam on + PlayAnim ( ANIMCHANNEL_TORSO, "dispersal_vent", 0 ); + return SRESULT_STAGE ( STAGE_FINISH_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_FINISH_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam off + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/game/ai/Monster_HeavyHoverTank.cpp b/source/game/ai/Monster_HeavyHoverTank.cpp new file mode 100644 index 0000000..6f2c6cb --- /dev/null +++ b/source/game/ai/Monster_HeavyHoverTank.cpp @@ -0,0 +1,534 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +class rvMonsterHeavyHoverTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHeavyHoverTank ); + + rvMonsterHeavyHoverTank ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + +protected: + + enum weaponState_t { + WEAPONSTATE_BLASTER, + WEAPONSTATE_ROCKET, + WEAPONSTATE_MAX + }; + + int blasterAnimIndex; + int shots; + weaponState_t weaponStateCurrent; + weaponState_t weaponStateIdeal; + + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionStrafe; + + jointHandle_t jointHoverEffect; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover; + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + +// virtual const char* GetIdleAnimName ( void ); + +private: + + float strafeSpeed; + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ChangeWeaponState ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHeavyHoverTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHeavyHoverTank ) +END_CLASS + +/* +================ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank +================ +*/ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank ( ) { + weaponStateIdeal = WEAPONSTATE_BLASTER; + weaponStateCurrent = weaponStateIdeal; + + effectDust = NULL; + effectHover = NULL; + + blasterAnimIndex = 0; + shots = 0; + + strafeSpeed = 0; +} + +void rvMonsterHeavyHoverTank::InitSpawnArgsVariables( void ) +{ + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover") ); + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "300" ); +} +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +void rvMonsterHeavyHoverTank::Spawn ( void ) { + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + + InitSpawnArgsVariables(); + + if ( jointHoverEffect != INVALID_JOINT ) { + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + } +} + +/* +================ +rvMonsterHeavyHoverTank::Think +================ +*/ +void rvMonsterHeavyHoverTank::Think ( void ) { + idAI::Think ( ); + + // If thinking we should play an effect on the ground under us + if ( jointHoverEffect != INVALID_JOINT ) { + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 128 units down from the hover effect joint + GetJointWorldTransform ( jointHoverEffect, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * 128.0f, CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + } +} + +/* +================ +rvMonsterHeavyHoverTank::Save +================ +*/ +void rvMonsterHeavyHoverTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( blasterAnimIndex ); + savefile->WriteInt ( shots ); + savefile->WriteInt ( (int)weaponStateCurrent ); + savefile->WriteInt ( (int)weaponStateIdeal ); + + actionRocketAttack.Save ( savefile ); + actionBlasterAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + + effectDust.Save ( savefile ); + effectHover.Save ( savefile ); +} + +/* +================ +rvMonsterHeavyHoverTank::Restore +================ +*/ +void rvMonsterHeavyHoverTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( blasterAnimIndex ); + savefile->ReadInt ( shots ); + savefile->ReadInt ( (int&)weaponStateCurrent ); + savefile->ReadInt ( (int&)weaponStateIdeal ); + + actionRocketAttack.Restore ( savefile ); + actionBlasterAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + + effectDust.Restore ( savefile ); + effectHover.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +void rvMonsterHeavyHoverTank::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + if ( damageScale > 0.0f ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + //push me hard! + float push = idMath::ClampFloat( 250.0f, 500.0f, damageScale*1000.0f ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += dir * push; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHeavyHoverTank::OnDeath +================ +*/ +void rvMonsterHeavyHoverTank::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + if ( effectHover ) { + effectHover->Stop ( ); + effectHover = NULL; + } + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterHeavyHoverTank::CheckAction_Strafe +================ +*/ +bool rvMonsterHeavyHoverTank::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +bool rvMonsterHeavyHoverTank::CheckActions ( void ) { + if ( weaponStateIdeal != weaponStateCurrent ) { + PerformAction ( "Torso_ChangeWeaponState", 4 ); + return true; + } + + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterHeavyHoverTank::CheckAction_Strafe ) ) + { + return true; + } + return false; +} + +/* +================ +rvMonsterHeavyHoverTank::OnEnemyChange +================ +*/ +void rvMonsterHeavyHoverTank::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } + + if ( enemy.ent->IsType ( rvVehicle::GetClassType() ) ) { + weaponStateIdeal = WEAPONSTATE_ROCKET; + } +} + +/* +================ +rvMonsterHeavyHoverTank::GetIdleAnimName +================ +*/ +/* +const char* rvMonsterHeavyHoverTank::GetIdleAnimName ( void ) { + if ( weaponStateCurrent == WEAPONSTATE_ROCKET ) { + return "rocket_idle"; + } + + return idAI::GetIdleAnimName ( ); +} +*/ + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHeavyHoverTank ) + STATE ( "Torso_BlasterAttack", rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHeavyHoverTank::State_Torso_RocketAttack ) + STATE ( "Torso_ChangeWeaponState", rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ) + STATE ( "Torso_EvadeLeft", rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterHeavyHoverTank::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterHeavyHoverTank::State_Torso_Strafe ) +END_CLASS_STATES + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_BLASTER; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BlasterAttack", parms.blendFrames ); + return SRESULT_DONE; + } + shots = gameLocal.random.RandomInt ( 8 ) + 4; + blasterAnimIndex = (blasterAnimIndex + 1) % 2; + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_preshoot", blasterAnimIndex + 1 ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_fire", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_postshoot", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_ROCKET; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RocketAttack", parms.blendFrames ); + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ( const stateParms_t& parms ) { + static const char* stateAnims [ WEAPONSTATE_MAX ] [ WEAPONSTATE_MAX ] = { + { NULL, "blaster_to_rocket" }, // WEAPONSTATE_BLASTER + { "rocket_to_blaster", NULL }, // WEAPONSTATE_ROCKET + }; + + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // No anim for that transition? + if ( stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ] == NULL ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ], parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + weaponStateCurrent = weaponStateIdeal; + switch ( weaponStateCurrent ) { + case WEAPONSTATE_ROCKET: + animPrefix = "rocket"; + break; + + default: + animPrefix = ""; + break; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeRight", parms.blendFrames ); + } else { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeLeft", parms.blendFrames ); + } + return SRESULT_DONE; +} diff --git a/source/game/ai/Monster_IronMaiden.cpp b/source/game/ai/Monster_IronMaiden.cpp new file mode 100644 index 0000000..9bdd613 --- /dev/null +++ b/source/game/ai/Monster_IronMaiden.cpp @@ -0,0 +1,531 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterIronMaiden : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterIronMaiden ); + + rvMonsterIronMaiden ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual int FilterTactical ( int availableTactical ); + +protected: + + int phaseTime; + jointHandle_t jointBansheeAttack; + int enemyStunTime; + + rvAIAction actionBansheeAttack; + + virtual bool CheckActions ( void ); + + void PhaseOut ( void ); + void PhaseIn ( void ); + + virtual void OnDeath ( void ); + +private: + + // Custom actions + bool CheckAction_BansheeAttack ( rvAIAction* action, int animNum ); + bool PerformAction_PhaseOut ( void ); + bool PerformAction_PhaseIn ( void ); + + // Global States + stateResult_t State_Phased ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_PhaseIn ( const stateParms_t& parms ); + stateResult_t State_Torso_PhaseOut ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttackDone ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_PhaseIn ( const stateParms_t& parms ); + stateResult_t Frame_PhaseOut ( const stateParms_t& parms ); + stateResult_t Frame_BansheeAttack ( const stateParms_t& parms ); + stateResult_t Frame_EndBansheeAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden ); +}; + +CLASS_DECLARATION( idAI, rvMonsterIronMaiden ) +END_CLASS + +/* +================ +rvMonsterIronMaiden::rvMonsterIronMaiden +================ +*/ +rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) { + enemyStunTime = 0; + phaseTime = 0; +} + +void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) { + // Cache the mouth joint + jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) ); +} +/* +================ +rvMonsterIronMaiden::Spawn +================ +*/ +void rvMonsterIronMaiden::Spawn ( void ) { + // Custom actions + actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true ); +} + +/* +================ +rvMonsterIronMaiden::Save +================ +*/ +void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( phaseTime ); + savefile->WriteInt ( enemyStunTime ); + + actionBansheeAttack.Save ( savefile ); +} + +/* +================ +rvMonsterIronMaiden::Restore +================ +*/ +void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( phaseTime ); + savefile->ReadInt ( enemyStunTime ); + + actionBansheeAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterIronMaiden::FilterTactical +================ +*/ +int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) { + // When hidden the iron maiden only uses ranged tactical + if ( fl.hidden ) { + availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT); + } + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterIronMaiden::CheckAction_BansheeAttack +================ +*/ +bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) { + return CheckAction_RangedAttack ( action, animNum ); +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseIn +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) { + if ( !phaseTime ) { + return false; + } + + // Must be out for at least 3 seconds + if ( gameLocal.time - phaseTime < 3000 ) { + return false; + } + + // Phase in after 10 seconds or our movement is done + if ( gameLocal.time - phaseTime > 10000 || move.fl.done ) { + // Make sure we arent in something + if ( CanBecomeSolid ( ) ) { + PerformAction ( "Torso_PhaseIn", 4, true ); + return true; + } + } + + return false; +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseOut +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) { + // Little randomization + if ( gameLocal.random.RandomFloat ( ) > 0.5f ) { + return false; + } + if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) { + return false; + } + + PerformAction ( "Torso_PhaseOut", 4, true ); + return true; +} + +/* +================ +rvMonsterIronMaiden::CheckActions +================ +*/ +bool rvMonsterIronMaiden::CheckActions ( void ) { + // When phased the only available action is phase in + if ( phaseTime ) { + if ( PerformAction_PhaseIn ( ) ) { + return true; + } + return false; + } + + if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction_PhaseOut ( ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterIronMaiden::PhaseOut +================ +*/ +void rvMonsterIronMaiden::PhaseOut ( void ) { + if ( phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + Hide ( ); + + move.fl.allowHiddenMove = true; + + ProcessEvent ( &EV_BecomeNonSolid ); + + StopMove ( MOVE_STATUS_DONE ); + SetState ( "State_Phased" ); + + // Move away from here, to anywhere + MoveOutOfRange ( this, 500.0f, 150.0f ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); + + phaseTime = gameLocal.time; +} + +/* +================ +rvMonsterIronMaiden::PhaseIn +================ +*/ +void rvMonsterIronMaiden::PhaseIn ( void ) { + if ( !phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + ProcessEvent ( &AI_BecomeSolid ); + + Show ( ); + +// PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true ); + + phaseTime = 0; + // Wait for the action that started the phase in to finish, then go back to combat + SetState ( "Wait_Action" ); + PostState ( "State_Combat" ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); +} + +/* +================ +rvMonsterIronMaiden::OnDeath +================ +*/ +void rvMonsterIronMaiden::OnDeath ( void ) { + StopSound ( SND_CHANNEL_ITEM, false ); + + // Stop looping effects + StopEffect ( "fx_banshee" ); + StopEffect ( "fx_dress" ); + + idAI::OnDeath( ); +} + +/* +================ +rvMonsterIronMaiden::GetDebugInfo +================ +*/ +void rvMonsterIronMaiden::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_BansheeAttack", aiActionStatusString[actionBansheeAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterIronMaiden ) + STATE ( "State_Phased", rvMonsterIronMaiden::State_Phased ) + + STATE ( "Torso_PhaseOut", rvMonsterIronMaiden::State_Torso_PhaseOut ) + STATE ( "Torso_PhaseIn", rvMonsterIronMaiden::State_Torso_PhaseIn ) + STATE ( "Torso_BansheeAttack", rvMonsterIronMaiden::State_Torso_BansheeAttack ) + STATE ( "Torso_BansheeAttackDone", rvMonsterIronMaiden::State_Torso_BansheeAttackDone ) + + STATE ( "Frame_PhaseIn", rvMonsterIronMaiden::Frame_PhaseIn ) + STATE ( "Frame_PhaseOut", rvMonsterIronMaiden::Frame_PhaseOut ) + STATE ( "Frame_BansheeAttack", rvMonsterIronMaiden::Frame_BansheeAttack ) + STATE ( "Frame_EndBansheeAttack", rvMonsterIronMaiden::Frame_EndBansheeAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterIronMaiden::State_Phased +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) { + // If done moving and cant become solid here, move again + if ( move.fl.done ) { + if ( !CanBecomeSolid ( ) ) { + MoveOutOfRange ( this, 300.0f, 150.0f ); + } + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Make sure we are checking actions if we have no tactical move + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + STAGE_PHASE, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttackDone + +To ensure the movement is enabled after the banshee attack and that the effect is stopped this state +will be posted to be run after the banshe attack finishes. +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) { + Frame_EndBansheeAttack ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterIronMaiden::Frame_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) { + PhaseIn ( ); + return SRESULT_OK; +} + + +/* +================ +rvMonsterIronMaiden::Frame_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) { + PhaseOut ( ); + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) { + idVec3 origin; + idMat3 axis; + idEntity* entities[ 1024 ]; + int count; + int i; + + // Get mouth origin + GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis ); + + // Find all entities within the banshee attacks radius + count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 ); + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Has to be within fov + if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) { + continue; + } + + // Do some damage + idVec3 dir; + dir = origin = ent->GetEyePosition ( ); + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 ); + + // Cache the last time we stunned our own enemy for the phase out + if ( ent == enemy.ent ) { + enemyStunTime = gameLocal.time; + } + } + + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_EndBansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_banshee" ); + return SRESULT_OK; +} diff --git a/source/game/ai/Monster_LightTank.cpp b/source/game/ai/Monster_LightTank.cpp new file mode 100644 index 0000000..d146d8a --- /dev/null +++ b/source/game/ai/Monster_LightTank.cpp @@ -0,0 +1,610 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterLightTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterLightTank ); + + rvMonsterLightTank ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual int GetDamageForLocation ( int damage, int location ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + int flamethrowerHealth; + int chargeDebounce; + void DestroyFlamethrower ( void ); + +private: + + int standingMeleeNoAttackTime; + bool damaged; +// bool damagedMove; + int powerUpStartTime; + + rvAIAction actionFlameThrower; + rvAIAction actionPowerUp; + rvAIAction actionChargeAttack; + + bool CheckAction_PowerUp ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms ); + stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_PowerUp ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterLightTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterLightTank ) +END_CLASS + +/* +================ +rvMonsterLightTank::rvMonsterLightTank +================ +*/ +rvMonsterLightTank::rvMonsterLightTank ( void ) { + damaged = false; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterLightTank::Spawn +================ +*/ +void rvMonsterLightTank::Spawn ( void ) { +// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" ); + flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" ); + chargeDebounce = 0; + + actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK ); + actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterLightTank::Save +================ +*/ +void rvMonsterLightTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( flamethrowerHealth ); + savefile->WriteInt( chargeDebounce ); + + savefile->WriteBool( damaged ); +// savefile->WriteBool( damagedMove ); + savefile->WriteInt( powerUpStartTime ); + + actionFlameThrower.Save( savefile ); + actionPowerUp.Save( savefile ); + actionChargeAttack.Save( savefile ); + mPostWeaponDestroyed.Save( savefile ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::Restore +================ +*/ +void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( flamethrowerHealth ); + savefile->ReadInt( chargeDebounce ); + + savefile->ReadBool( damaged ); +// savefile->ReadBool( damagedMove ); + savefile->ReadInt( powerUpStartTime ); + + actionFlameThrower.Restore( savefile ); + actionPowerUp.Restore( savefile ); + actionChargeAttack.Restore( savefile ); + mPostWeaponDestroyed.Restore( savefile ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::FilterTactical +================ +*/ +int rvMonsterLightTank::FilterTactical ( int availableTactical ) { + if ( flamethrowerHealth > 0 ) { + // Only let the light tank use ranged tactical when he is really far from his enemy + if ( !enemy.range || enemy.range < combat.attackRange[1] ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + if ( chargeDebounce > gameLocal.GetTime() ) + {//don't charge again any time soon + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterLightTank::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + break; + + default: + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +================ +rvMonsterLightTank::UpdateRunStatus +================ +*/ +bool rvMonsterLightTank::UpdateRunStatus ( void ) { + // If rushing, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + move.fl.idealRunning = true; + } + else + { + move.fl.idealRunning = false; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterLightTank::DestroyFlamethrower +================ +*/ +void rvMonsterLightTank::DestroyFlamethrower ( void ) { + + StopEffect ( "fx_flame_muzzle" ); + //HideSurface ( "models/monsters/light_tank/flamethrower" ); + //GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 ); + animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) ); + flamethrowerHealth = -1; + + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; +// flamethrowerDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + + PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") ); + PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true ); + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "damaged"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + chargeDebounce = 0; + damaged = true; + animPrefix = "damage"; + + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS); + + actionMeleeAttack.timer.Reset( actionTime ); + actionChargeAttack.timer.Reset( actionTime ); + + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +===================== +rvMonsterLightTank::Pain +===================== +*/ +bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( move.fl.moving && move.fl.running ) + { + painAnim = "pain_charge"; + } + return didPain; +} + /* +===================== +rvMonsterLightTank::GetDamageForLocation +===================== +*/ +int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) { + // If the flamethrower was hit only do damage to it + if( !damaged && !aifl.dead ) + { + if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( flamethrowerHealth > 0 ){ + flamethrowerHealth -= damage; + if ( flamethrowerHealth <= 0 ) { + DestroyFlamethrower(); + } + } + return 0; + } + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterLightTank::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( !damaged ) + { + if ( victim == GetEnemy() && inflictor == this ) + { + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//okay, get out of melee state for now + chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000; + } + } + } + + idAI::DamageFeedback( victim, inflictor, damage ); +} + +/* +============ +rvMonsterLightTank::OnStopMoving +============ +*/ +void rvMonsterLightTank::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionFlameThrower.timer.Clear( actionTime ); + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//so we don't stand there and look stupid + actionMeleeAttack.timer.Clear( actionTime ); + actionChargeAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterLightTank::CheckAction_PowerUp +================ +*/ +bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum ) +{ + if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeLeft( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeRight +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeRight( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckActions +================ +*/ +bool rvMonsterLightTank::CheckActions ( void ) { + if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterLightTank ) + STATE ( "State_Killed", rvMonsterLightTank::State_Killed ) + + STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower ) + STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink ) + STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain ) + STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack ) + STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp ) + +END_CLASS_STATES + +/* +================ +rvMonsterLightTank::State_Killed +================ +*/ +stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_destroy_arm_trail" ); + StopEffect ( "fx_flame_muzzle" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrower +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Flame effect + PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames ); + + // Delay start the flame thrower think to ensure he flames for a minimum time + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 ); + + return SRESULT_DONE; +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrowerThink +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) { + if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + StopEffect ( "fx_flame_muzzle" ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterLightTank::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) { + + StopEffect ( "fx_flame_muzzle" ); + + // Default pain animation + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( !move.fl.moving || !FacingIdeal() ) { + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames ); + } + + return SRESULT_STAGE ( STAGE_FINISH ); + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterLightTank::State_Torso_PowerUp +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + //fl.takedamage = false; + powerUpStartTime = gameLocal.GetTime(); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + health++; + if ( health >= spawnArgs.GetInt( "health" ) + || gameLocal.GetTime() - powerUpStartTime > 3875 ) + {//full health or been charging up for 3 full anim loops + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_NetworkGuardian.cpp b/source/game/ai/Monster_NetworkGuardian.cpp new file mode 100644 index 0000000..2e8a8c5 --- /dev/null +++ b/source/game/ai/Monster_NetworkGuardian.cpp @@ -0,0 +1,822 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterNetworkGuardian : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterNetworkGuardian ); + + rvMonsterNetworkGuardian ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + + int shots; + int landTime; + + int flagFlying; + + float strafeSpeed; + + //this flag allows the AI to control when it takes off and lands. + bool flagAutopilot; + + int battleStage; + + enum { + FLY_NONE = 0, + FLY_TRANSITION, + FLY_FLYING, + }; + +private: + + rvAIAction actionShotgunRocketAttack; + rvAIAction actionFlyingRangedAttack; + rvAIAction actionFlyingSweepAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionBlasterSweepGround; + rvAIAction actionBlasterAttack; + rvAIAction actionMIRVAttack; + + + + stateResult_t State_Wait_Flying ( const stateParms_t& parms ); + +// stateResult_t State_Dead ( const stateParms_t& parms ); + + // walking melee attacks + + // walking ranged attacks + stateResult_t State_Torso_ShotgunRocket ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterSweepGround ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MIRVAttack ( const stateParms_t& parms ); + + //flying evades + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + //flying ranged attacks + stateResult_t State_Torso_FlyingRanged ( const stateParms_t& parms ); + stateResult_t State_Torso_FlyingSweep ( const stateParms_t& parms ); + + // flying anims + stateResult_t State_Torso_LiftOff ( const stateParms_t& parms ); + stateResult_t State_Torso_Fall ( const stateParms_t& parms ); + // walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + + //force the NG into walking mode. This does not play the landing anim, and will make him fall from the sky. + void Event_ForceWalkMode( void ); + + //These commands change the NG's state, but do so through states and animations. + void Event_ForceLanding( void ); + void Event_ForceTakeoff( void ); + + //toggles the NG between AI controlled flight and script controlled flight + void Event_AllowAutopilot( float f); + + //for staged combat, sets the int value of the battle stage + void Event_SetBattleStage( float f); + + CLASS_STATES_PROTOTYPE ( rvMonsterNetworkGuardian ); +}; + +const idEventDef EV_ForceWalkMode( "forceWalkMode" ); +const idEventDef EV_ForceLanding( "forceLanding" ); +const idEventDef EV_ForceTakeoff( "forceTakeoff" ); +const idEventDef EV_AllowAutopilot( "allowAutopilot", "f" ); +const idEventDef EV_SetBattleStage( "setBattleStage", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterNetworkGuardian ) + EVENT( EV_ForceWalkMode, rvMonsterNetworkGuardian::Event_ForceWalkMode ) + EVENT( EV_ForceLanding, rvMonsterNetworkGuardian::Event_ForceLanding ) + EVENT( EV_ForceTakeoff, rvMonsterNetworkGuardian::Event_ForceTakeoff ) + EVENT( EV_AllowAutopilot, rvMonsterNetworkGuardian::Event_AllowAutopilot ) + EVENT( EV_SetBattleStage, rvMonsterNetworkGuardian::Event_SetBattleStage ) + +END_CLASS + +/* +================ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian +================ +*/ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian ( ) { + shots = 0; + landTime = 0; + flagFlying = FLY_NONE; + battleStage = 1; + strafeSpeed = 0; + +} + +/* +================ +rvMonsterNetworkGuardian::Spawn +================ +*/ +void rvMonsterNetworkGuardian::Spawn ( void ) { + + disablePain = true; + flagAutopilot = false; + + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "500" ); + + actionShotgunRocketAttack.Init ( spawnArgs, "action_ShotgunRocket", "Torso_ShotgunRocket_Attack", AIACTIONF_ATTACK ); + actionFlyingRangedAttack.Init( spawnArgs, "action_FlyingRangedAttack", "Torso_FlyingRanged_Attack", AIACTIONF_ATTACK ); + actionFlyingSweepAttack.Init( spawnArgs, "action_blasterSweepAirAttack", "Torso_FlyingSweep_Attack", AIACTIONF_ATTACK ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterSweepGround.Init( spawnArgs, "action_blasterSweepGroundAttack", "Torso_BlasterSweepGround_Attack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init( spawnArgs, "action_blasterAttack", "Torso_Blaster_Attack", AIACTIONF_ATTACK ); + actionMIRVAttack.Init( spawnArgs, "action_MIRVAttack", "Torso_MIRV_Attack", AIACTIONF_ATTACK ); + +} + + +/* +================ +rvMonsterNetworkGuardian::Save +================ +*/ +void rvMonsterNetworkGuardian::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( shots ); + savefile->WriteInt( landTime ); + savefile->WriteInt( flagFlying ); + savefile->WriteInt( battleStage ); + savefile->WriteBool( flagAutopilot ); + savefile->WriteFloat( strafeSpeed ); + + actionShotgunRocketAttack.Save( savefile ); + actionFlyingRangedAttack.Save( savefile ); + actionFlyingSweepAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionBlasterSweepGround.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionMIRVAttack.Save( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::Restore +================ +*/ +void rvMonsterNetworkGuardian::Restore ( idRestoreGame *savefile ) +{ + savefile->ReadInt( shots ); + savefile->ReadInt( landTime ); + savefile->ReadInt( flagFlying ); + savefile->ReadInt( battleStage ); + savefile->ReadBool( flagAutopilot ); + savefile->ReadFloat( strafeSpeed ); + + actionShotgunRocketAttack.Restore( savefile ); + actionFlyingRangedAttack.Restore( savefile ); + actionFlyingSweepAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionBlasterSweepGround.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionMIRVAttack.Restore( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::CheckActions +================ +*/ +bool rvMonsterNetworkGuardian::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + //if the flight is transitioning, do nothing + if( flagFlying == FLY_TRANSITION ) { + return false; + } + + //this is the autopilot section. + if( flagAutopilot ) { + // If he's been on the ground long enough, fly... + if ( move.moveType == MOVETYPE_ANIM && move.fl.onGround && !flagFlying && gameLocal.time > landTime ) + { + PostState ( "Wait_Flying" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + return true; + } + else if ( move.moveType == MOVETYPE_FLY && gameLocal.time > landTime ) + { + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + return true; + } + } + + //Normal actions here ----------- + + //if he's close enough for melee, use melee + if( flagFlying == FLY_NONE) { + if( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + //check for ranged attacks on the ground + if( flagFlying == FLY_NONE ) { + + switch (battleStage) { + + case 1: + if( PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + case 2: + if( PerformAction ( &actionMIRVAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + default: + gameLocal.Error("Bad battleStage '%d' set for Network Guardian.", battleStage); + break; + } + + } + //airborne attack actions + if( (flagFlying == FLY_FLYING) ) { + + if( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionFlyingRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionFlyingSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ){ + return true; + } + } + + //No. + //return idAI::CheckActions ( ); + return false; +} + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterNetworkGuardian::Event_ForceWalkMode +================ +*/ +// forces NG to obey gravity and immediately switch to walking mode. +void rvMonsterNetworkGuardian::Event_ForceWalkMode( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_NONE; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceLanding +================ +*/ +// forces NG play his landing animation. He will not just fall from the sky. +void rvMonsterNetworkGuardian::Event_ForceLanding( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + //PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceTakeoff +================ +*/ +// forces NG to take off and fly. +void rvMonsterNetworkGuardian::Event_ForceTakeoff( void ) { + + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + move.fl.allowDirectional = true; + SetState ( "Wait_Flying" ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_AllowAutoPilot +================ +*/ +// toggles the AI autoPilot for deciding when to fly and land. +void rvMonsterNetworkGuardian::Event_AllowAutopilot( float f ) { + + flagAutopilot = f ? true : false; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_SetBattleStage +================ +*/ +// sets the current battle stage. Each stage has different behaviors. +void rvMonsterNetworkGuardian::Event_SetBattleStage( float f ) { + + battleStage = f; + +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterNetworkGuardian ) + STATE ( "Wait_Flying", rvMonsterNetworkGuardian::State_Wait_Flying ) + STATE ( "State_Dead", rvMonsterNetworkGuardian::State_Dead ) + + STATE ( "Torso_ShotgunRocket_Attack", rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ) + STATE ( "Torso_BlasterSweepGround_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ) + STATE ( "Torso_Blaster_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterAttack ) + STATE ( "Torso_MIRV_Attack", rvMonsterNetworkGuardian::State_Torso_MIRVAttack) + + STATE ( "Torso_EvadeLeft", rvMonsterNetworkGuardian::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterNetworkGuardian::State_Torso_EvadeRight ) + + STATE ( "Torso_FlyingRanged_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingRanged ) + STATE ( "Torso_FlyingSweep_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingSweep ) + + STATE ( "Torso_LiftOff", rvMonsterNetworkGuardian::State_Torso_LiftOff ) + STATE ( "Torso_Fall", rvMonsterNetworkGuardian::State_Torso_Fall ) + + STATE ( "Torso_TurnRight90", rvMonsterNetworkGuardian::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ) + +END_CLASS_STATES + + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterNetworkGuardian::State_Torso_ShotgunRocket +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shotgunRocket_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_grd", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingSweep +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingSweep ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_air", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingRanged +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingRanged ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "flyingRanged_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_MIRVAttack +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_MIRVAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_vert", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterNetworkGuardian::State_Wait_Flying +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Wait_Flying ( const stateParms_t& parms ) { + if ( move.moveType == MOVETYPE_ANIM ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterNetworkGuardian::State_Dead +================ +*/ +/* +stateResult_t rvMonsterNetworkGuardian::State_Dead ( const stateParms_t& parms ) { + return SRESULT_DONE; +}*/ + +/* +================ +rvMonsterNetworkGuardian::State_Torso_LiftOff +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_LiftOff ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.noGravity = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "liftoff", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + SetMoveType ( MOVETYPE_FLY ); + landTime = gameLocal.time + DelayTime ( 5000, 10000 ); + animPrefix = "fly"; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + flagFlying = FLY_FLYING; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_Fall +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_Fall ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + // we've landed! determine the next fly time + landTime = gameLocal.time + DelayTime ( 10000, 15000 ); + flagFlying = FLY_NONE; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnRight90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnLeft90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_BlasterAttack +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( (flagFlying != FLY_NONE ) && move.fl.moving ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 12 ) + 8) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; + +} diff --git a/source/game/ai/Monster_RepairBot.cpp b/source/game/ai/Monster_RepairBot.cpp new file mode 100644 index 0000000..2f5f067 --- /dev/null +++ b/source/game/ai/Monster_RepairBot.cpp @@ -0,0 +1,376 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class repairBotArm_t { +public: + jointHandle_t joint; + int repairTime; + bool repairing; + rvClientEffectPtr effectRepair; + rvClientEffectPtr effectImpact; + + int periodicEndTime; + + repairBotArm_t () { + periodicEndTime = -1; + } + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvMonsterRepairBot : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterRepairBot ); + + rvMonsterRepairBot ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + int repairEndTime; + float repairEffectDist; + + repairBotArm_t armLeft; + repairBotArm_t armRight; + +private: + + void UpdateRepairs ( repairBotArm_t& arm ); + void StopRepairs ( repairBotArm_t& arm ); + + // Leg states + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_Repair ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_RepairDone ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterRepairBot ); +}; + +CLASS_DECLARATION( idAI, rvMonsterRepairBot ) +END_CLASS + +/* +================ +rvMonsterRepairBot::rvMonsterRepairBot +================ +*/ +rvMonsterRepairBot::rvMonsterRepairBot ( ) { +} + +void rvMonsterRepairBot::InitSpawnArgsVariables( void ) +{ + armLeft.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_left", "l_fx" ) ); + armRight.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_right", "r_fx" ) ); + repairEffectDist = spawnArgs.GetFloat( "repairEffectDist", "64" ); +} +/* +================ +rvMonsterRepairBot::Spawn +================ +*/ +void rvMonsterRepairBot::Spawn ( void ) { + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::CheckActions +================ +*/ +bool rvMonsterRepairBot::CheckActions ( void ) { + return false; +} + +/* +================ +rvMonsterRepairBot::OnDeath +================ +*/ +void rvMonsterRepairBot::OnDeath ( void ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), viewAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterRepairBot::Save +================ +*/ +void rvMonsterRepairBot::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( repairEndTime ); + + armLeft.Save ( savefile ); + armRight.Save ( savefile ); +} + +/* +================ +rvMonsterRepairBot::Restore +================ +*/ +void rvMonsterRepairBot::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( repairEndTime ); + + armLeft.Restore ( savefile ); + armRight.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::Think +================ +*/ +void rvMonsterRepairBot::Think ( void ) { + idAI::Think ( ); + + // Update repair effects? (dont worry about stopping them, the state ending will do so) + UpdateRepairs ( armLeft ); + UpdateRepairs ( armRight ); +} + +/* +================ +rvMonsterRepairBot::UpdateRepairs +================ +*/ +void rvMonsterRepairBot::UpdateRepairs ( repairBotArm_t& arm ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + if ( arm.joint == INVALID_JOINT ) { + return; + } + + if ( gameLocal.time > repairEndTime ) { + StopRepairs ( arm ) ; + return; + } + + // If the repair time has been crossed we need to start/stop the repairs + if ( gameLocal.time > arm.repairTime ) { + if ( arm.repairing ) { + StopRepairs ( arm ); + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 500 ); + arm.repairing = false; + } else { + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 2500 ); + arm.repairing = true; + } + } + + if ( !arm.repairing ) { + return; + } + + // Left repair effect + GetJointWorldTransform ( arm.joint, gameLocal.time, origin, axis ); + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * repairEffectDist, MASK_SHOT_RENDERMODEL, this ); + + if ( tr.fraction >= 1.0f ) { + StopRepairs ( arm ); + } else { + // Start the repair effect if not already started + if ( !arm.effectRepair ) { + arm.effectRepair = PlayEffect ( "fx_repair", arm.joint, true ); + } + // If the repair effect is running then set its end origin + if ( arm.effectRepair ) { + arm.effectRepair->SetEndOrigin ( tr.endpos ); + } + // Start the impact effect + if ( !arm.effectImpact ) { + arm.effectImpact = PlayEffect ( "fx_repair_impact", tr.endpos, tr.c.normal.ToMat3(), true ); + + } else { + // Calculate the local origin and axis from the given globals + idVec3 localOrigin = (tr.endpos - renderEntity.origin) * renderEntity.axis.Transpose ( ); + idMat3 localAxis = tr.c.normal.ToMat3 ();// * renderEntity.axis.Transpose(); + arm.effectImpact->SetOrigin ( localOrigin ); + arm.effectImpact->SetAxis ( localAxis ); + } + + if( gameLocal.GetTime() > arm.periodicEndTime ) {// FIXME: Seems dumb to keep banging on this if the fx isn't defined. + gameLocal.PlayEffect( spawnArgs, "fx_repair_impact_periodic", tr.endpos, tr.c.normal.ToMat3() ); + arm.periodicEndTime = gameLocal.GetTime() + SEC2MS( rvRandom::flrand(spawnArgs.GetVec2("impact_fx_delay_range", "1 1")) ); + } + } +} + +/* +================ +rvMonsterRepairBot::StopRepairs +================ +*/ +void rvMonsterRepairBot::StopRepairs ( repairBotArm_t& arm ) { + if ( arm.effectImpact ) { + arm.effectImpact->Stop ( ); + arm.effectImpact = NULL; + } + if ( arm.effectRepair ) { + arm.effectRepair->Stop ( ); + arm.effectRepair = NULL; + } + + arm.periodicEndTime = -1; +} + +/* +================ +rvMonsterRepairBot::GetDebugInfo +================ +*/ +void rvMonsterRepairBot::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterRepairBot ) + STATE ( "Legs_Move", rvMonsterRepairBot::State_Legs_Move ) + STATE ( "TorsoAction_Repair", rvMonsterRepairBot::State_TorsoAction_Repair ) + STATE ( "TorsoAction_RepairDone", rvMonsterRepairBot::State_TorsoAction_RepairDone ) +END_CLASS_STATES + +/* +================ +rvMonsterRepairBot::State_Legs_Move +================ +*/ +stateResult_t rvMonsterRepairBot::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_MOVE, + STAGE_MOVE_WAIT, + STAGE_STOP, + STAGE_STOP_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_LEGS, "idle_to_run", 4 ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + return SRESULT_STAGE ( STAGE_MOVE ); + } + return SRESULT_WAIT; + + case STAGE_MOVE: + PlayCycle ( ANIMCHANNEL_LEGS, "run", 4 ); + return SRESULT_STAGE ( STAGE_MOVE_WAIT ); + + case STAGE_MOVE_WAIT: + if ( !move.fl.moving || !CanMove() ) { + return SRESULT_STAGE ( STAGE_STOP ); + } + return SRESULT_WAIT; + + case STAGE_STOP: + PlayAnim ( ANIMCHANNEL_LEGS, "run_to_idle", 4 ); + return SRESULT_STAGE ( STAGE_STOP_WAIT ); + + case STAGE_STOP_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_Repair +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_Repair ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_REPAIR, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "repair", 4 ); + repairEndTime = gameLocal.time + SEC2MS(scriptedActionEnt->spawnArgs.GetInt ( "duration", "5" ) ); + PostAnimState ( ANIMCHANNEL_TORSO, "TorsoAction_RepairDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE(STAGE_REPAIR); + + case STAGE_REPAIR: + if ( gameLocal.time > repairEndTime ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_RepairDone +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_RepairDone ( const stateParms_t& parms ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + + return SRESULT_DONE; +} + +/* +================ +repairBotArm_t::Save +================ +*/ +void repairBotArm_t::Save ( idSaveGame* savefile ) const { + savefile->WriteInt ( repairTime ); + savefile->WriteBool ( repairing ); + effectRepair.Save ( savefile ); + effectImpact.Save ( savefile ); + + savefile->WriteInt ( periodicEndTime ); +} + +/* +================ +repairBotArm_t::Restore +================ +*/ +void repairBotArm_t::Restore ( idRestoreGame* savefile ) { + savefile->ReadInt ( repairTime ); + savefile->ReadBool ( repairing ); + effectRepair.Restore ( savefile ); + effectImpact.Restore ( savefile ); + + savefile->ReadInt ( periodicEndTime ); +} diff --git a/source/game/ai/Monster_Scientist.cpp b/source/game/ai/Monster_Scientist.cpp new file mode 100644 index 0000000..c403b00 --- /dev/null +++ b/source/game/ai/Monster_Scientist.cpp @@ -0,0 +1,76 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterScientist : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterScientist ); + + rvMonsterScientist ( void ); + + void Spawn ( void ); + + virtual void OnDeath ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterScientist ); +}; + +CLASS_DECLARATION( idAI, rvMonsterScientist ) +END_CLASS + +/* +================ +rvMonsterScientist::rvMonsterScientist +================ +*/ +rvMonsterScientist::rvMonsterScientist ( void ) { +} + +/* +================ +rvMonsterScientist::Spawn +================ +*/ +void rvMonsterScientist::Spawn ( void ) { + PlayEffect ( "fx_fly", animator.GetJointHandle ( "effects_bone" ), true ); +} + +/* +================ +rvMonsterScientist::OnDeath +================ +*/ +void rvMonsterScientist::OnDeath ( void ) { + StopEffect ( "fx_fly" ); + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterScientist::GetDebugInfo +================ +*/ +void rvMonsterScientist::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterScientist ) +END_CLASS_STATES diff --git a/source/game/ai/Monster_Sentry.cpp b/source/game/ai/Monster_Sentry.cpp new file mode 100644 index 0000000..fedb7a4 --- /dev/null +++ b/source/game/ai/Monster_Sentry.cpp @@ -0,0 +1,412 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSentry : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSentry ); + + rvMonsterSentry ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + rvAIAction actionBlasterAttack; + rvAIAction actionKamakaziAttack; + rvAIAction actionCircleStrafe; + + int shots; + int minShots; + int maxShots; + int kamakaziHealth; + int strafeTime; + bool strafeRight; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual void OnDeath ( void ); + + void Explode ( bool force = false ); + +private: + + int nextChatterTime; + + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_KamakaziAttack ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Action_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSentry ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSentry ) +END_CLASS + +/* +================ +rvMonsterSentry::rvMonsterSentry +================ +*/ +rvMonsterSentry::rvMonsterSentry ( void ) { + strafeTime = 0; + strafeRight = false; +} + +void rvMonsterSentry::InitSpawnArgsVariables( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); +} +/* +================ +rvMonsterSentry::Spawn +================ +*/ +void rvMonsterSentry::Spawn ( void ) { + // Custom actions + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionKamakaziAttack.Init ( spawnArgs, "action_kamakaziAttack", "Torso_KamakaziAttack", AIACTIONF_ATTACK ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "State_Action_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + kamakaziHealth = spawnArgs.GetInt ( "kamakazi_Health", va("%d", health / 2) ); + nextChatterTime = 0; +} + +/* +================ +rvMonsterSentry::Save +================ +*/ +void rvMonsterSentry::Save ( idSaveGame *savefile ) const { + actionBlasterAttack.Save( savefile ); + actionKamakaziAttack.Save( savefile ); + actionCircleStrafe.Save( savefile ); + + savefile->WriteInt( shots ); + savefile->WriteInt( kamakaziHealth ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteInt ( nextChatterTime ); +} + +/* +================ +rvMonsterSentry::Restore +================ +*/ +void rvMonsterSentry::Restore ( idRestoreGame *savefile ) { + actionBlasterAttack.Restore( savefile ); + actionKamakaziAttack.Restore( savefile ); + actionCircleStrafe.Restore( savefile ); + + savefile->ReadInt( shots ); + savefile->ReadInt( kamakaziHealth ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadInt ( nextChatterTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSentry::Pain +================ +*/ +bool rvMonsterSentry::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( kamakaziHealth > 0 && health < kamakaziHealth ) { + kamakaziHealth = 0; + move.speed = move.fly_speed = spawnArgs.GetFloat ( "kamakazi_fly_speed", va("%g", move.fly_speed ) ); + move.turnRate = spawnArgs.GetFloat ( "kamakazi_turn_rate", va("%g", move.turnRate ) ); + move.fly_pitch_max = spawnArgs.GetFloat ( "kamakazi_fly_pitch_max" ); + move.fly_pitch_scale = spawnArgs.GetFloat ( "kamakazi_fly_pitch_scale" ); + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterSentry::Think +================ +*/ +void rvMonsterSentry::Think ( void ) { + // Explode whenever we hit something + if ( kamakaziHealth == 0 && physicsObj.GetSlideMoveEntity () ) { + Explode ( ); + } else { + /* + if ( nextChatterTime < gameLocal.GetTime() ) { + if ( GetEnemy() ) { + Speak( "lipsync_chatter_combat" ); + nextChatterTime = speakTime + 2000 + gameLocal.random.RandomInt( 2000 ); + } else { + Speak( "lipsync_chatter_idle" ); + nextChatterTime = speakTime + 3000 + gameLocal.random.RandomInt( 3000 ); + } + } + */ + + idAI::Think ( ); + } +} + +/* +================ +rvMonsterSentry::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterSentry::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterSentry::CheckActions +================ +*/ +bool rvMonsterSentry::CheckActions ( void ) { + if ( kamakaziHealth != 0 ) { + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterSentry::CheckAction_CircleStrafe ) ) { + return true; + } + } else { + if ( PerformAction ( &actionKamakaziAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterSentry::FilterTactical +================ +*/ +int rvMonsterSentry::FilterTactical ( int availableTactical ) { + // Kamakazi Health will be set to 0 when its time to kamakazi + if ( !kamakaziHealth ) { + return availableTactical & (AITACTICAL_MELEE_BIT|AITACTICAL_TURRET_BIT); + // No rush when not in kamakazi mode + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterSentry::OnDeath +================ +*/ +void rvMonsterSentry::OnDeath ( void ) { + idAI::OnDeath ( ); + + Explode ( true ); +} + +/* +================ +rvMonsterSentry::Explode +================ +*/ +void rvMonsterSentry::Explode ( bool force ) { + if ( health > 0 || force ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString ( "def_kamakazi_damage" ), 1.0f ); + + // Kill ourselves + Damage ( this, this, viewAxis[0], "damage_gib", 1.0f, 0 ); + } +} + +/* +================ +rvMonsterSentry::GetDebugInfo +================ +*/ +void rvMonsterSentry::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_kamakaziAttack", aiActionStatusString[actionKamakaziAttack.status], userData ); + proc ( "idAI", "action_circleStrafe", aiActionStatusString[actionCircleStrafe.status], userData ); + proc ( "rvMonsterSentry", "strafeRight",strafeRight?"true":"false", userData ); + proc ( "rvMonsterSentry", "strafeTime", va("%d",strafeTime), userData ); + proc ( "rvMonsterSentry", "nextChatterTime", va("%d",nextChatterTime), userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSentry ) + STATE ( "Torso_BlasterAttack", rvMonsterSentry::State_Torso_BlasterAttack ) + STATE ( "Torso_KamakaziAttack", rvMonsterSentry::State_Torso_KamakaziAttack ) + STATE ( "State_CircleStrafe", rvMonsterSentry::State_CircleStrafe ) + STATE ( "State_Action_CircleStrafe",rvMonsterSentry::State_Action_CircleStrafe ) + STATE ( "Torso_EvadeLeft", rvMonsterSentry::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterSentry::State_Torso_EvadeRight ) +END_CLASS_STATES + +/* +================ +rvMonsterSentry::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSentry::State_Torso_KamakaziAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_KamakaziAttack ( const stateParms_t& parms ) { + Explode ( ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_CircleStrafe ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_CIRCLE + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( strafeTime ) { + //already strafing, just change dir? + strafeTime = (strafeTime0.5f); + } + return SRESULT_STAGE ( STAGE_CIRCLE ); + case STAGE_CIRCLE: + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) { + //FIXME: also stop if I bump into something + strafeTime = 0; + SetState( "State_Combat" ); + return SRESULT_DONE; + } + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (strafeRight?-200:200); + vel.Normalize(); + vel *= 200.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( enemy.lastKnownPosition ); + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterSentry::State_Action_CircleStrafe ( const stateParms_t& parms ) { + SetState( "State_CircleStrafe" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = false; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * 400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeRight ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = true; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} diff --git a/source/game/ai/Monster_SlimyTransfer.cpp b/source/game/ai/Monster_SlimyTransfer.cpp new file mode 100644 index 0000000..8e456b6 --- /dev/null +++ b/source/game/ai/Monster_SlimyTransfer.cpp @@ -0,0 +1,205 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSlimyTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSlimyTransfer ); + + rvMonsterSlimyTransfer ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + jointHandle_t jointVomitMuzzle; + + int vomitNextAttackTime; + int vomitAttackRate; + +private: + + rvAIAction actionVomitAttack; + + // Torso States + stateResult_t State_Torso_VomitAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishVomitAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t State_Frame_StartVomit ( const stateParms_t& parms ); + stateResult_t State_Frame_StopVomit ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSlimyTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSlimyTransfer ) +END_CLASS + +/* +================ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer +================ +*/ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer ( void ) { +} + +void rvMonsterSlimyTransfer::InitSpawnArgsVariables ( void ) { + jointVomitMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_vomitMuzzle", "puke_bone" ) ); + + vomitAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_vomit_rate", ".15" ) ); +} + +/* +================ +rvMonsterSlimyTransfer::Spawn +================ +*/ +void rvMonsterSlimyTransfer::Spawn ( void ) { + actionVomitAttack.Init ( spawnArgs, "action_vomitAttack", "Torso_VomitAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::Save +================ +*/ +void rvMonsterSlimyTransfer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( vomitNextAttackTime ); + + actionVomitAttack.Save( savefile ); +} + +/* +================ +rvMonsterSlimyTransfer::Restore +================ +*/ +void rvMonsterSlimyTransfer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( vomitNextAttackTime ); + + actionVomitAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::CheckActions +================ +*/ +bool rvMonsterSlimyTransfer::CheckActions ( void ) { + if ( PerformAction ( &actionVomitAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterSlimyTransfer::OnDeath +================ +*/ +void rvMonsterSlimyTransfer::OnDeath ( void ) { + StopEffect ( "fx_vomit_muzzle" ); + idAI::OnDeath ( ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSlimyTransfer ) + STATE ( "Torso_VomitAttack", rvMonsterSlimyTransfer::State_Torso_VomitAttack ) + STATE ( "Torso_FinishVomitAttack", rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ) + + STATE ( "Frame_StartVomit", rvMonsterSlimyTransfer::State_Frame_StartVomit ) + STATE ( "Frame_StopVomit", rvMonsterSlimyTransfer::State_Frame_StopVomit ) +END_CLASS_STATES + +/* +================ +rvMonsterSlimyTransfer::State_Torso_VomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_VomitAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + vomitNextAttackTime = 0; + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "vomit_attack", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishVomitAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + if ( vomitNextAttackTime && gameLocal.time >= vomitNextAttackTime ) { + Attack ( "vomit", jointVomitMuzzle, enemy.ent ); + vomitNextAttackTime = gameLocal.time + vomitAttackRate; + } + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ( const stateParms_t& parms ) { + State_Frame_StopVomit ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StartVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StartVomit ( const stateParms_t& parms ) { + PlayEffect ( "fx_vomit_muzzle", jointVomitMuzzle, true ); + vomitNextAttackTime = gameLocal.time; + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StopVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StopVomit ( const stateParms_t& parms ) { + StopEffect ( "fx_vomit_muzzle" ); + vomitNextAttackTime = 0; + return SRESULT_DONE; +} diff --git a/source/game/ai/Monster_StreamProtector.cpp b/source/game/ai/Monster_StreamProtector.cpp new file mode 100644 index 0000000..2b0fcad --- /dev/null +++ b/source/game/ai/Monster_StreamProtector.cpp @@ -0,0 +1,538 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterStreamProtector : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStreamProtector ); + + rvMonsterStreamProtector ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + virtual bool UpdateAnimationControllers ( void ); + + jointHandle_t jointPlasmaMuzzle; + + int attackEndTime; + int attackNextTime; + int plasmaAttackRate; + int shots; + +private: + + int painConsecutive; + + rvAIAction actionPlasmaAttack; + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionHeavyBlasterAttack; + rvAIAction actionLightningActtack; + rvAIAction actionChaingunAttack; + + // Torso States + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + stateResult_t State_Torso_PlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_LightningAttack ( const stateParms_t& parms ); + + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStreamProtector ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStreamProtector ) +END_CLASS + +/* +================ +rvMonsterStreamProtector::rvMonsterStreamProtector +================ +*/ +rvMonsterStreamProtector::rvMonsterStreamProtector ( void ) { + painConsecutive = 0; +} + +void rvMonsterStreamProtector::InitSpawnArgsVariables ( void ) { + jointPlasmaMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_plasmaMuzzle", "NM_muzzle" ) ); + + plasmaAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_plasma_rate", ".15" ) ); +} +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +void rvMonsterStreamProtector::Spawn ( void ) { + actionPlasmaAttack.Init ( spawnArgs, "action_plasmaAttack", "Torso_PlasmaAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", NULL, AIACTIONF_ATTACK ); + actionHeavyBlasterAttack.Init ( spawnArgs, "action_heavyBlasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionLightningActtack.Init ( spawnArgs, "action_lightningAttack", "Torso_LightningAttack", AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", "Torso_ChaingunAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Save +================ +*/ +void rvMonsterStreamProtector::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( attackEndTime ); + savefile->WriteInt( attackNextTime ); + savefile->WriteInt( shots ); + savefile->WriteInt( painConsecutive ); + + actionPlasmaAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionHeavyBlasterAttack.Save ( savefile ); + actionLightningActtack.Save( savefile ); + actionChaingunAttack.Save( savefile ); +} + +/* +================ +rvMonsterStreamProtector::Restore +================ +*/ +void rvMonsterStreamProtector::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( attackEndTime ); + savefile->ReadInt( attackNextTime ); + savefile->ReadInt( shots ); + savefile->ReadInt( painConsecutive ); + + actionPlasmaAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionHeavyBlasterAttack.Restore ( savefile ); + actionLightningActtack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +bool rvMonsterStreamProtector::UpdateAnimationControllers ( void ) { + // TODO: Target enemies behind us? (doesnt need to be the same enemy that we are targetting) + + return idAI::UpdateAnimationControllers ( ); +} + +/* +================ +rvMonsterStreamProtector::CheckPainActions +================ +*/ +bool rvMonsterStreamProtector::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +rvMonsterStreamProtector::CheckActions +================ +*/ +bool rvMonsterStreamProtector::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( PerformAction ( &actionPlasmaAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningActtack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionHeavyBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStreamProtector::CanTurn +================ +*/ +bool rvMonsterStreamProtector::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterStreamProtector::Pain +================ +*/ +bool rvMonsterStreamProtector::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterStreamProtector::Damage +================ +*/ +void rvMonsterStreamProtector::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker && attacker->IsType( rvMonsterStreamProtector::GetClassType() ) ) { + //don't take damage from ourselves or other stream protectors + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStreamProtector ) + STATE ( "State_Killed", rvMonsterStreamProtector::State_Killed ) + STATE ( "State_Dead", rvMonsterStreamProtector::State_Dead ) + + STATE ( "Torso_PlasmaAttack", rvMonsterStreamProtector::State_Torso_PlasmaAttack ) + STATE ( "Torso_FinishPlasmaAttack", rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ) + STATE ( "Torso_BlasterAttack", rvMonsterStreamProtector::State_Torso_BlasterAttack ) + STATE ( "Torso_LightningAttack", rvMonsterStreamProtector::State_Torso_LightningAttack ) + + STATE ( "Torso_TurnRight90", rvMonsterStreamProtector::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterStreamProtector::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +/* +================ +rvMonsterStreamProtector::State_Killed +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Killed ( const stateParms_t& parms ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death", parms.blendFrames ); + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Dead +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterStreamProtector::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_PlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_INITIALFIRE_WAIT, + STAGE_FIRE_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + attackEndTime = gameLocal.time + 500; + attackNextTime = gameLocal.time; + + // Flame effect + PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true ); + + PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 ); + + return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT ); + + case STAGE_INITIALFIRE_WAIT: + if ( gameLocal.time > attackEndTime ) { + attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f ); + return SRESULT_STAGE ( STAGE_FIRE_WAIT ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_FIRE_WAIT: + // If we have been using plasma too long or havent seen our enemy for at least half a second then + // stop now. + if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_STAGE ( STAGE_END ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_END: + // End animations + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_LightningAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + attackEndTime = gameLocal.time + 5000; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_StroggFlyer.cpp b/source/game/ai/Monster_StroggFlyer.cpp new file mode 100644 index 0000000..e16abf3 --- /dev/null +++ b/source/game/ai/Monster_StroggFlyer.cpp @@ -0,0 +1,363 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterStroggFlyer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggFlyer ); + + rvMonsterStroggFlyer ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnWakeUp ( void ); + + rvAIAction actionBombAttack; + rvAIAction actionBlasterAttack; + + idVec3 velocity; + + int shotCount; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + +private: + + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); + + void AttackBlaster ( void ); + void AttackBomb ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggFlyer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggFlyer ) +END_CLASS + +/* +================ +rvMonsterStroggFlyer::rvMonsterStroggFlyer +================ +*/ +rvMonsterStroggFlyer::rvMonsterStroggFlyer ( ) { + shotCount = 0; + lastAttackTime = 0; + attackStartTime = 0; +} + +void rvMonsterStroggFlyer::InitSpawnArgsVariables( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} + +/* +================ +rvMonsterStroggFlyer::Spawn +================ +*/ +void rvMonsterStroggFlyer::Spawn ( void ) { + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::OnUpdatePlayback +================ +*/ +void rvMonsterStroggFlyer::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + byte buttons; + byte changed; + byte impulse; + + velocity = pbd.GetVelocity ( ); + + buttons = pbd.GetButtons(); + changed = pbd.GetChanged(); + impulse = pbd.GetImpulse(); + + // Shoot the blaster if the attack button was pressed + if ( (changed & buttons) & BUTTON_ATTACK ){ + AttackBlaster ( ); + } + + if ( (changed & buttons) & BUTTON_ZOOM ){ + AttackBomb ( ); + } + + switch ( impulse ) { + case 40: + aifl.disableAttacks = true; + break; + + case 41: + aifl.disableAttacks = false; + break; + + case 42: + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } +} + +/* +================ +rvMonsterStroggFlyer::OnWakeUp +================ +*/ +void rvMonsterStroggFlyer::OnWakeUp ( void ) { + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); + + return idAI::OnWakeUp ( ); +} + + +/* +================ +rvMonsterStroggFlyer::AttackBlaster +================ +*/ +void rvMonsterStroggFlyer::AttackBlaster ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_muzzleflash", joint ); + Attack ( "blaster", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::AttackBomb +================ +*/ +void rvMonsterStroggFlyer::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( "fx_bombflash", joint ); + Attack ( "bomb", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::CheckActions +================ +*/ +bool rvMonsterStroggFlyer::CheckActions ( void ) { + if ( PerformAction ( &actionBombAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStroggFlyer::Save +================ +*/ +void rvMonsterStroggFlyer::Save( idSaveGame *savefile ) const { + actionBombAttack.Save ( savefile ) ; + actionBlasterAttack.Save ( savefile ); + + savefile->WriteVec3 ( velocity ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); +} + +/* +================ +rvMonsterStroggFlyer::Restore +================ +*/ +void rvMonsterStroggFlyer::Restore( idRestoreGame *savefile ) { + actionBombAttack.Restore ( savefile ) ; + actionBlasterAttack.Restore ( savefile ); + + savefile->ReadVec3 ( velocity ); + + savefile->ReadInt ( shotCount ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::GetDebugInfo +================ +*/ +void rvMonsterStroggFlyer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggFlyer ) + STATE ( "State_WakeUp", rvMonsterStroggFlyer::State_WakeUp ) + STATE ( "State_ScriptedPlaybackMove", rvMonsterStroggFlyer::State_ScriptedPlaybackMove ) + STATE ( "State_Killed", rvMonsterStroggFlyer::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterStroggFlyer::State_Torso_BlasterAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggFlyer::State_Torso_BombAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggFlyer::State_ScriptedPlaybackMove +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + // When the playback finishes cancel any running states + if ( !mPlayback.IsActive() ) { + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Perform actions + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterStroggFlyer::State_Killed +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Killed ( const stateParms_t& parms ) { + PlayEffect ( "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_StroggHover.cpp b/source/game/ai/Monster_StroggHover.cpp new file mode 100644 index 0000000..390ca58 --- /dev/null +++ b/source/game/ai/Monster_StroggHover.cpp @@ -0,0 +1,1448 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +#define MAX_MISSILE_JOINTS 4 +#define MAX_HOVER_JOINTS 4 +class rvMonsterStroggHover : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggHover ); + + rvMonsterStroggHover ( void ); + ~rvMonsterStroggHover ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + virtual void DeadMove ( void ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual int FilterTactical ( int availableTactical ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Hide( void ); + virtual void Show( void ); + + void StartHeadlight ( void ); + void StopHeadlight ( void ); + +protected: + +// rvAIAction actionRocketAttack; +// rvAIAction actionBlasterAttack; + rvAIAction actionMGunAttack; + rvAIAction actionMissileAttack; + rvAIAction actionBombAttack; + rvAIAction actionStrafe; + rvAIAction actionCircleStrafe; + + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnStartMoving ( void ); + + virtual const char* GetIdleAnimName ( void ); + +private: + + idEntityPtr marker; + idVec3 attackPosOffset; + bool inPursuit; + int holdPosTime; + + int strafeTime; + bool strafeRight; + bool circleStrafing; + float deathPitch; + float deathRoll; + float deathPitchRate; + float deathYawRate; + float deathRollRate; + float deathSpeed; + float deathGrav; + + int markerCheckTime; + + bool MarkerPosValid ( void ); + void TryStartPursuit ( void ); + void Pursue ( void ); + void CircleStrafe ( void ); + void Evade ( bool left ); + + int mGunFireRate; + int missileFireRate; + int bombFireRate; + int nextMGunFireTime; + int nextMissileFireTime; + int nextBombFireTime; + + int mGunMinShots; + int mGunMaxShots; + int missileMinShots; + int missileMaxShots; + int bombMinShots; + int bombMaxShots; + + int shots; + + int evadeDebounce; + int evadeDebounceRate; + float evadeChance; + float evadeSpeed; + float strafeSpeed; + float circleStrafeSpeed; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover[MAX_HOVER_JOINTS]; + rvClientEffectPtr effectHeadlight; + + jointHandle_t jointDust; + int numHoverJoints; + jointHandle_t jointHover[MAX_HOVER_JOINTS]; + jointHandle_t jointBomb; + jointHandle_t jointMGun; + int numMissileJoints; + jointHandle_t jointMissile[MAX_MISSILE_JOINTS]; + jointHandle_t jointHeadlight; + jointHandle_t jointHeadlightControl; + + renderLight_t renderLight; + int lightHandle; +// bool lightOn; + + void DoNamedAttack ( const char* attackName, jointHandle_t joint ); + + void UpdateLightDef ( void ); + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + bool CheckAction_BombAttack ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + +// stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_DeathSpiral ( const stateParms_t& parms ); + stateResult_t State_Pursue ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggHover ) +END_CLASS + +/* +================ +rvMonsterStroggHover::rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::rvMonsterStroggHover ( ) { + effectDust = NULL; + for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) { + effectHover[i] = NULL; + } + effectHeadlight = NULL; + + shots = 0; + strafeTime = 0; + strafeRight = false; + circleStrafing = false; + evadeDebounce = 0; + deathPitch = 0; + deathRoll = 0; + deathPitchRate = 0; + deathYawRate = 0; + deathRollRate = 0; + deathSpeed = 0; + deathGrav = 0; + + markerCheckTime = 0; + + marker = NULL; + attackPosOffset.Zero(); + inPursuit = false; + holdPosTime = 0; + + nextMGunFireTime = 0; + nextMissileFireTime = 0; + nextBombFireTime = 0; + + lightHandle = -1; +} + +/* +================ +rvMonsterStroggHover::~rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::~rvMonsterStroggHover ( ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +void rvMonsterStroggHover::InitSpawnArgsVariables( void ) +{ + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) ); + } + + jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) ); + + jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) ); + numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" )); + for ( int i = 0; i < numMissileJoints; i++ ) { + jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) ); + } + jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) ); + + mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" )); + missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" )); + bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" )); + + mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" ); + mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" ); + missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" ); + missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" ); + bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" ); + bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" ); + + evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" )); + evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" ); + evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" ); + strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" ); + circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" ); + + //LIGHT + jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) ); + jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) ); +} + +void rvMonsterStroggHover::Hide( void ) +{ + StopHeadlight(); + idAI::Hide(); +} + +void rvMonsterStroggHover::Show( void ) +{ + idAI::Show(); + StartHeadlight(); +} + +void rvMonsterStroggHover::StartHeadlight( void ) +{ + if ( jointHeadlight != INVALID_JOINT ) + { + lightHandle = -1; + if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) { + idVec3 color; + //const char* temp; + + const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + if ( headLightMaterial ) + { + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + spawnArgs.GetVector( "light_color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "light_target" ); + renderLight.up = spawnArgs.GetVector( "light_up" ); + renderLight.right = spawnArgs.GetVector( "light_right" ); + renderLight.end = spawnArgs.GetVector( "light_target" );; + } + + //lightOn = spawnArgs.GetBool( "start_on", "1" ); + + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } + // Hide flare surface if there is one + /* + temp = spawnArgs.GetString ( "light_flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + */ + + // Sounds shader when turning light + //spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + //spawnArgs.GetString ( "snd_off", "", soundOff); + + UpdateLightDef ( ); + } +} + +void rvMonsterStroggHover::StopHeadlight( void ) +{ + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( lightHandle ); + lightHandle = -1; + } + memset ( &renderLight, 0, sizeof(renderLight) ); +} +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +void rvMonsterStroggHover::Spawn ( void ) { +// actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); +// actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK ); + actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK ); + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + evadeDebounce = 0; + + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true ); + } + } + + if ( !marker ) { + marker = gameLocal.SpawnEntityDef( "target_null" ); + } + + //LIGHT + StopHeadlight(); + StartHeadlight(); + + if ( jointHeadlight != INVALID_JOINT ) + { + effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true ); + } +} + +/* +================ +rvMonsterStroggHover::GetDebugInfo +================ +*/ +void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData ); + proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData ); + + proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData ); + proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData ); + + proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData ); + + proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData ); + proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData ); + proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData ); + + proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData ); + proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData ); + proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData ); + proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData ); + proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData ); + proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData ); + proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData ); + proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData ); + + proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData ); + proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData ); + proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData ); + proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData ); + proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData ); + proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData ); +} + +/* +===================== +rvMonsterStroggHover::UpdateLightDef +===================== +*/ +void rvMonsterStroggHover::UpdateLightDef ( void ) { + if ( jointHeadlight != INVALID_JOINT ) + { + idVec3 origin; + idMat3 axis; + + if ( jointHeadlightControl != INVALID_JOINT ) { + idAngles jointAng; + jointAng.Zero(); + jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI ); + jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI ); + + animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + + GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis ); + + //origin += (localOffset * axis); + + // Include this part in the total bounds + // FIXME: bounds are local + //parent->AddToBounds ( worldOrigin ); + //UpdateOrigin ( ); + + if ( lightHandle != -1 ) { + renderLight.origin = origin; + renderLight.axis = axis; + + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); + } + } +} + +/* +================ +rvMonsterStroggHover::Think +================ +*/ +void rvMonsterStroggHover::Think ( void ) { + idAI::Think ( ); + + if ( !aifl.dead ) + { + // If thinking we should play an effect on the ground under us + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 80 units down from the bottom of our bbox + GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + /* + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + */ + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + //Try to circle strafe or pursue + if ( circleStrafing ) + { + CircleStrafe(); + } + else if ( !inPursuit ) + { + if ( !aifl.action && move.fl.done && !aifl.scripted ) + { + if ( GetEnemy() ) + { + if ( DistanceTo( GetEnemy() ) > 2000.0f + || (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f ) + {//enemy is far away or moving away from us at a pretty decent speed + TryStartPursuit(); + } + } + } + } + else + { + Pursue(); + } + + //Dodge + if ( !circleStrafing ) { + if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) { + if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) { + if ( gameLocal.random.RandomFloat() > evadeChance ) { + //40% chance of ignoring it - makes them dodge rockets less often but bullets more often? + combat.shotAtTime = 0; + } else if ( evadeDebounce < gameLocal.GetTime() ) { + //ramps down from 400 to 100 over 1 second + float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f))); + idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed; + evadeVel.z *= 0.5f; + move.addVelocity += evadeVel; + move.addVelocity.Normalize(); + move.addVelocity *= speed; + /* + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) { + //just need to do it once? + combat.shotAtTime = 0; + } + */ + if ( evadeDebounceRate > 1 ) + { + evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f)); + } + } + } + } + } + + //If using melee rush to nav to him, stop when we're close enough to attack + if ( combat.tacticalCurrent == AITACTICAL_MELEE + && move.moveCommand == MOVE_TO_ENEMY + && !move.fl.done + && nextBombFireTime < gameLocal.GetTime() - 3000 + && enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) { + StopMove( MOVE_STATUS_DONE ); + ForceTacticalUpdate(); + } else { + //whenever we're not in the middle of something, force an update of our tactical + if ( !aifl.action ) { + if ( !aasFind ) { + if ( move.fl.done ) { + if ( !inPursuit && !circleStrafing ) { + ForceTacticalUpdate(); + } + } + } + } + } + } + + //update light +// if ( lightOn ) { + UpdateLightDef ( ); +// } +} + +/* +============ +rvMonsterStroggHover::OnStartMoving +============ +*/ +void rvMonsterStroggHover::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( move.moveCommand == MOVE_TO_ENEMY ) { + move.range = combat.meleeRange; + } +} + +/* +================ +rvMonsterStroggHover::Save +================ +*/ +void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteBool ( circleStrafing ); + savefile->WriteFloat ( deathPitch ); + savefile->WriteFloat ( deathRoll ); + savefile->WriteFloat ( deathPitchRate ); + savefile->WriteFloat ( deathYawRate ); + savefile->WriteFloat ( deathRollRate ); + savefile->WriteFloat ( deathSpeed ); + savefile->WriteFloat ( deathGrav ); + savefile->WriteInt ( markerCheckTime ); + + savefile->WriteVec3( attackPosOffset ); + +// actionRocketAttack.Save ( savefile ); +// actionBlasterAttack.Save ( savefile ); + actionMGunAttack.Save ( savefile ); + actionMissileAttack.Save ( savefile ); + actionBombAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + actionCircleStrafe.Save ( savefile ); + + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Save ( savefile ); + } + + effectDust.Save ( savefile ); + effectHeadlight.Save ( savefile ); + + marker.Save( savefile ); + savefile->WriteBool ( inPursuit ); + savefile->WriteInt ( holdPosTime ); + savefile->WriteInt ( nextMGunFireTime ); + savefile->WriteInt ( nextMissileFireTime ); + savefile->WriteInt ( nextBombFireTime ); + + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + + savefile->WriteInt( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Restore +================ +*/ +void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadBool ( circleStrafing ); + savefile->ReadFloat ( deathPitch ); + savefile->ReadFloat ( deathRoll ); + savefile->ReadFloat ( deathPitchRate ); + savefile->ReadFloat ( deathYawRate ); + savefile->ReadFloat ( deathRollRate ); + savefile->ReadFloat ( deathSpeed ); + savefile->ReadFloat ( deathGrav ); + savefile->ReadInt ( markerCheckTime ); + + savefile->ReadVec3( attackPosOffset ); + +// actionRocketAttack.Restore ( savefile ); +// actionBlasterAttack.Restore ( savefile ); + actionMGunAttack.Restore ( savefile ); + actionMissileAttack.Restore ( savefile ); + actionBombAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + actionCircleStrafe.Restore ( savefile ); + + InitSpawnArgsVariables(); + //NOTE: if the def file changes the the number of numHoverJoints, this will be BAD... + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Restore ( savefile ); + } + + effectDust.Restore ( savefile ); + effectHeadlight.Restore ( savefile ); + + marker.Restore( savefile ); + savefile->ReadBool ( inPursuit ); + savefile->ReadInt ( holdPosTime ); + savefile->ReadInt ( nextMGunFireTime ); + savefile->ReadInt ( nextMissileFireTime ); + savefile->ReadInt ( nextBombFireTime ); + + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadInt ( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Collide +================ +*/ +bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( aifl.dead ) { + StopHeadlight(); + //stop headlight + if ( effectHeadlight ) { + effectHeadlight->Stop ( ); + effectHeadlight = NULL; + } + // Stop the crash & burn effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + SetState ( "State_Remove" ); + return false; + } + return idAI::Collide( collision, velocity ); +} + +/* +================ +rvMonsterStroggHover::Damage +================ +*/ +void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + bool wasDead = aifl.dead; + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( !wasDead && aifl.dead ) { + SetState( "State_DeathSpiral" ); + } +} +/* +================ +rvMonsterStroggHover::OnDeath +================ +*/ +void rvMonsterStroggHover::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + + idAI::OnDeath ( ); +} + +/* +===================== +rvMonsterStroggHover::DeadMove +===================== +*/ +void rvMonsterStroggHover::DeadMove( void ) { + DeathPush ( ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); +} + +/* +===================== +rvMonsterStroggHover::SkipImpulse +===================== +*/ +bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) { + return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK)); +} + +/* +================ +rvMonsterStroggHover::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( inPursuit && !holdPosTime ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + if ( evadeDebounce >= gameLocal.GetTime() ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( inPursuit ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) { + if ( !GetEnemy() || !enemy.fl.visible ) { + return false; + } + /* + if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) { + //not moving enough + return false; + } + */ + if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) { + //enemy is moving toward me, drop 'em! + return true; + } + return false; +} + +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +bool rvMonsterStroggHover::CheckActions ( void ) { + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) { + return true; + } + +/* + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } +*/ + if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterStroggHover::OnEnemyChange +================ +*/ +void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } +} + +/* +================ +rvMonsterStroggHover::GetIdleAnimName +================ +*/ +const char* rvMonsterStroggHover::GetIdleAnimName ( void ) { + /* + if ( move.moveType == MOVETYPE_FLY ) { + return "flying_idle"; + } + */ + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterStroggHover::DoNamedAttack +================ +*/ +void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) { + if ( joint != INVALID_JOINT ) { + StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( va("fx_%s_flash",attackName), joint ); + Attack ( attackName, joint, GetEnemy() ); + } +} + +/* +================ +rvMonsterStroggHover::FilterTactical +================ +*/ +int rvMonsterStroggHover::FilterTactical ( int availableTactical ) { + availableTactical = idAI::FilterTactical( availableTactical ); + if ( circleStrafing || inPursuit ) { + return 0; + } + if ( nextBombFireTime >= gameLocal.GetTime() ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } else if ( enemy.fl.visible ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } + return availableTactical; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggHover ) +// STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack ) +// STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack ) + STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack ) + STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack ) +// STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft ) +// STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe ) + STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe ) + STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe ) + STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral ) + STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggHover::State_Torso_MGunAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "mgun", jointMGun ); + nextMGunFireTime = gameLocal.GetTime() + mGunFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMGunFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_MissileAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] ); + nextMissileFireTime = gameLocal.GetTime() + missileFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMissileFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) { + //out of shots or enemy too close to safely keep launching rockets at + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( vel.z < 150.0f ) { + vel.z += 20.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + switch ( parms.stage ) { + case STAGE_INIT: + move.fly_offset = 800;//go up! + shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots; + if ( GetEnemy() ) { + //if I'm not above him, give me a quick boost first + float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z; + if ( zDiff < 150.0f ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel.z += 200.0f; + if ( zDiff < 0.0f ) { + //even more if I'm below him! + vel.z -= zDiff*2.0f; + } + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + if ( move.moveCommand == MOVE_TO_ATTACK ) { + StopMove( MOVE_STATUS_DONE ); + } + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + ForceTacticalUpdate(); + } + if ( move.moveCommand == MOVE_NONE + && !inPursuit && !circleStrafing ) { + MoveToEnemy(); + } + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "bomb", jointBomb ); + nextBombFireTime = gameLocal.GetTime() + bombFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextBombFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + move.fly_offset = spawnArgs.GetFloat("fly_offset","250"); + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BlasterAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( 8 ) + 4; + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ +/* +================ +rvMonsterStroggHover::State_Torso_RocketAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} +*/ + +void rvMonsterStroggHover::Evade ( bool left ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed); + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); +} + +stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + Evade( false ); + } else { + Evade( true ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) { + circleStrafing = true; + return SRESULT_DONE; +} + +bool rvMonsterStroggHover::MarkerPosValid ( void ) +{ + //debouncer ftw + if( markerCheckTime > gameLocal.GetTime() ) { + return true; + } + + markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500); + + trace_t trace; + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL ); + if ( !(trace.c.contents&GetPhysics()->GetClipMask()) ) + {//not in solid + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() ); + idActor* enemyAct = NULL; + rvVehicle* enemyVeh = NULL; + if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) { + enemyVeh = static_cast(GetEnemy()); + } else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) { + enemyAct = static_cast(GetEnemy()); + } + idEntity* hitEnt = gameLocal.entities[trace.c.entityNum]; + idActor* hitAct = NULL; + if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) { + hitAct = static_cast(hitEnt); + } + if ( trace.fraction >= 1.0f + || (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum]) + || (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) ) + {//have a clear LOS to enemy + if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) ) + {//valid AAS there... + return true; + } + } + } + return false; +} + +void rvMonsterStroggHover::TryStartPursuit ( void ) +{ + if ( GetEnemy() ) + { + inPursuit = false; + if ( !marker.GetEntity() ) { + //wtf?! + assert(0); + return; + } + attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f ); + if ( attackPosOffset.Length() < 150.0f ) + { + attackPosOffset.Normalize(); + attackPosOffset *= 150.0f; + } + attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset; + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( MarkerPosValid() ) + { + if ( MoveToEntity( marker ) ) + { + inPursuit = true; + holdPosTime = 0; + SetState( "State_Pursue" ); + } + } + } +} + +void rvMonsterStroggHover::Pursue ( void ) +{ + if ( marker.GetEntity() && GetEnemy() ) + { + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() ); + } + if ( MarkerPosValid() ) + { + bool breakOff = false; + if ( move.fl.done ) + {//even once get there, hold that position for a while... + if ( holdPosTime && holdPosTime > gameLocal.GetTime() ) + {//held this position long enough + breakOff = true; + } + else + { + if ( !holdPosTime ) + {//just got there, hold position for a bit + holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime(); + } + if ( !MoveToEntity( marker ) ) + { + breakOff = true; + } + } + } + if ( !breakOff ) + { + return; + } + } + } + if ( !move.fl.done ) + { + StopMove( MOVE_STATUS_DONE ); + } + inPursuit = false; +} + +stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) { + if ( inPursuit ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +void rvMonsterStroggHover::CircleStrafe ( void ) +{ + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) + { + //FIXME: also stop if I bump into something + circleStrafing = false; + strafeTime = 0; + SetState( "State_Combat" ); + return; + } + if ( !strafeTime ) + { + strafeTime = gameLocal.GetTime() + 8000; + //FIXME: try to see which side it clear? + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + } + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed); + strafeVel.z = 0.0f; + vel += strafeVel; + vel.Normalize(); + vel *= circleStrafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( GetEnemy()->GetPhysics()->GetOrigin() ); +} + +stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) { + if ( circleStrafing ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SPIRAL + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + disablePain = true; + + // Make sure all animation and attack states stop + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + + // Start the crash & burn effects + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + PlayEffect ( "fx_hurt", jointHover[i], false ); + effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true ); + } + } + deathPitch = viewAxis.ToAngles()[0]; + deathRoll = viewAxis.ToAngles()[2]; + + deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f; + deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f; + deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f; + deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f; + deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f; + + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + StopSound( SND_CHANNEL_HEART, false ); + StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL ); + } + return SRESULT_STAGE ( STAGE_SPIRAL ); + case STAGE_SPIRAL: + { + move.current_yaw += (strafeRight?-deathYawRate:deathYawRate); + deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate ); + deathRoll += (strafeRight?deathRollRate:-deathRollRate); + viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[0] * deathSpeed; + strafeVel.z = 0; + vel += strafeVel; + vel.Normalize(); + vel *= deathSpeed; + vel += GetPhysics()->GetGravity()/deathGrav; + + physicsObj.UseVelocityMove( true ); + + physicsObj.SetLinearVelocity( vel ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + refSound.parms.frequencyShift += 0.025f; + emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms ); + } + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_StroggMarine.cpp b/source/game/ai/Monster_StroggMarine.cpp new file mode 100644 index 0000000..f4effa8 --- /dev/null +++ b/source/game/ai/Monster_StroggMarine.cpp @@ -0,0 +1,753 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +//NOTE: actually a bit of a misnomer, as all Strogg Marine types use this class now... +class rvMonsterStroggMarine : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggMarine ); + + rvMonsterStroggMarine ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + + int maxShots; + int minShots; + int shots; + int shotsFired; + + int fireAnimNum; + bool spraySideRight; + int sweepCount; + + bool EnemyMovingToRight ( void ); + +private: + + void CalculateShots ( void ); + + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + // Actions + rvAIAction actionStrafe; + rvAIAction actionCrouchRangedAttack; + rvAIAction actionRollAttack; + rvAIAction actionSprayAttack; + rvAIAction actionAngry; + rvAIAction actionReload; + + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RollAttack ( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_Angry ( rvAIAction* action, int animNum ); + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_RollAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggMarine ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggMarine ) +END_CLASS + +/* +================ +rvMonsterStroggMarine::rvMonsterStroggMarine +================ +*/ +rvMonsterStroggMarine::rvMonsterStroggMarine ( ) { + nextShootTime = 0; +} + +void rvMonsterStroggMarine::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.2" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterStroggMarine::Spawn +================ +*/ +void rvMonsterStroggMarine::Spawn ( void ) { + actionStrafe.Init ( spawnArgs, "action_strafe", NULL, 0 ); + actionCrouchRangedAttack.Init ( spawnArgs, "action_crouchRangedAttack", NULL, AIACTIONF_ATTACK ); + actionRollAttack.Init ( spawnArgs, "action_rollAttack", NULL, AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionAngry.Init ( spawnArgs, "action_angry", NULL, 0 ); + actionReload.Init ( spawnArgs, "action_reload", NULL, 0 ); + + InitSpawnArgsVariables(); + + shots = 0; + shotsFired = 0; +} + +/* +================ +rvMonsterStroggMarine::Save +================ +*/ +void rvMonsterStroggMarine::Save ( idSaveGame *savefile ) const { + actionStrafe.Save ( savefile ); + actionCrouchRangedAttack.Save( savefile ); + actionRollAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionAngry.Save( savefile ); + actionReload.Save( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + + savefile->WriteInt ( fireAnimNum ); + savefile->WriteBool ( spraySideRight ); + savefile->WriteInt ( sweepCount ); + + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterStroggMarine::Restore +================ +*/ +void rvMonsterStroggMarine::Restore ( idRestoreGame *savefile ) { + actionStrafe.Restore ( savefile ); + actionCrouchRangedAttack.Restore( savefile ); + actionRollAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionAngry.Restore( savefile ); + actionReload.Restore( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + + savefile->ReadInt ( fireAnimNum ); + savefile->ReadBool ( spraySideRight ); + savefile->ReadInt ( sweepCount ); + + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterStroggMarine::OnStopMoving +============ +*/ +void rvMonsterStroggMarine::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + actionMeleeAttack.timer.Clear( actionTime ); + } + else + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionCrouchRangedAttack.timer.Clear( actionTime ); + actionRollAttack.timer.Clear( actionTime ); + actionSprayAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterStroggMarine::CheckAction_JumpBack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage && gameLocal.time - pain.lastTakenTime > 1500 ) { + return false; + } + + // enemy must be in front to jump backwards + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + } + // TODO: dont evade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeRight +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + return idAI::CheckAction_RangedAttack( action, animNum ); +} + +/* +================ +rvMonsterStroggMarine::CheckAction_CrouchRangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RollAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RollAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_SprayAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Angry +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Angry ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Reload +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckActions +================ +*/ +bool rvMonsterStroggMarine::CheckActions ( void ) { + + if ( idAI::CheckActions ( ) ) + { + return true; + } + if ( PerformAction ( &actionCrouchRangedAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_CrouchRangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRollAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_RollAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_SprayAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Strafe ) || + PerformAction ( &actionAngry, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Angry ) || + PerformAction ( &actionReload, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Reload ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggMarine ) + STATE ( "Torso_RollAttack", rvMonsterStroggMarine::State_Torso_RollAttack ) + STATE ( "Torso_RangedAttack", rvMonsterStroggMarine::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterStroggMarine::State_Torso_MovingRangedAttack ) + STATE ( "Torso_SprayAttack", rvMonsterStroggMarine::State_Torso_SprayAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggMarine::State_Torso_RollAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RollAttack ( const stateParms_t& parms ) { + enum { + TORSO_ROLLATTACK_ROLL, + TORSO_ROLLATTACK_FACE, + TORSO_ROLLATTACK_FIRE, + TORSO_ROLLATTACK_FINISH + }; + + TurnToward(enemy.lastKnownPosition); + + switch ( parms.stage ) { + // Start the roll attack animation + case TORSO_ROLLATTACK_ROLL: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the roll + PlayAnim ( ANIMCHANNEL_TORSO, "dive_turn", parms.blendFrames ); + move.fl.noTurn = false; + //FaceEnemy(); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FACE ); + + // Wait for roll animation to finish + case TORSO_ROLLATTACK_FACE: + if ( AnimDone ( ANIMCHANNEL_LEGS, 6 ) ) { + return SRESULT_STAGE ( TORSO_ROLLATTACK_FIRE ); + } + return SRESULT_WAIT; + + // Play fire animation + case TORSO_ROLLATTACK_FIRE: + if ( !enemy.ent || !enemy.fl.visible ) + {//whoops! rolled out of LOS + return SRESULT_DONE; + } + if ( enemy.fl.inFov ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "shotgun_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FINISH ); + } + return SRESULT_WAIT; + + // Wait for fire animation to finish + case TORSO_ROLLATTACK_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterStroggMarine::CalculateShots +================ +*/ +void rvMonsterStroggMarine::CalculateShots ( void ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + + // Update the firing animation playback rate + /* + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } + */ +} + +/* +================ +rvMonsterStroggMarine::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + //TurnToward(enemy.lastKnownPosition); + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && move.fl.running && !actionRangedAttack.fl.overrideLegs && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + fireAnimNum = gameLocal.random.RandomInt(2)+1; + CalculateShots(); + shotsFired = 0; + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_loop", fireAnimNum), 0 ); + shots--; + shotsFired++; + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov && shotsFired >= minShots ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots(); + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "base", attackJoint, GetEnemy() ); + PlayEffect( "fx_blaster_muzzleflash", attackJoint ); + } + StartSound( "snd_weapon_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= minShots) || !move.fl.running || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::EnemyMovingToRight +================ +*/ +bool rvMonsterStroggMarine::EnemyMovingToRight( void ) +{ + if ( !GetEnemy() ) + { + return false; + } + //use their movement direction + idVec3 dir = GetEnemy()->GetPhysics()->GetLinearVelocity(); + //flatten + dir.z = 0; + dir.Normalize(); + + idVec3 fwd = viewAxis[0]; + idVec3 lt = viewAxis[1]; + + float dot = 0.0f; + dot = DotProduct(dir, lt); + if ( dot > 0 ) + { + return false; + } + else + { + return true; + } +} + +/* +================ +rvMonsterStroggMarine::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + spraySideRight = EnemyMovingToRight(); + sweepCount = 0; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_start", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_start", 0 ); + } + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_sweep", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_sweep", 0 ); + } + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + bool curEnemyMovingRight = EnemyMovingToRight(); + if ( sweepCount < 3 + && (!gameLocal.random.RandomInt(2) + || (spraySideRight && !curEnemyMovingRight) + || (!spraySideRight && curEnemyMovingRight)) ) + { + spraySideRight = !spraySideRight; + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_end", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_end", 0 ); + } + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_TeleportDropper.cpp b/source/game/ai/Monster_TeleportDropper.cpp new file mode 100644 index 0000000..69b7bf9 --- /dev/null +++ b/source/game/ai/Monster_TeleportDropper.cpp @@ -0,0 +1,536 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +class rvMonsterTeleportDropper : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTeleportDropper ); + + rvMonsterTeleportDropper ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* projectileDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual const char* GetIdleAnimName ( void ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + +protected: + + idEntityPtr spawner; + + // Actions + rvAIAction actionDropSpawners; + + //bool dropAtGoalOnly; + +// virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); +// virtual bool MoveToEnemy ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + +private: + + bool leftSideBlocked; + bool rightSideBlocked; + jointHandle_t jointLeftFrontCannon; + jointHandle_t jointLeftRearCannon; + jointHandle_t jointRightFrontCannon; + jointHandle_t jointRightRearCannon; + + bool leapDidAttack; + + bool CheckAction_DropSpawners ( rvAIAction* action, int animNum ); + // Torso states + stateResult_t State_Torso_DropSpawners ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTeleportDropper ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTeleportDropper ) +END_CLASS + +/* +================ +rvMonsterTeleportDropper::rvMonsterTeleportDropper +================ +*/ +rvMonsterTeleportDropper::rvMonsterTeleportDropper ( void ) { + spawner = NULL; + leftSideBlocked = rightSideBlocked = false; + leapDidAttack = false; +// dropAtGoalOnly = false; +} + +void rvMonsterTeleportDropper::InitSpawnArgsVariables ( void ) +{ + jointLeftFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_front_cannon" ) ); + jointLeftRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_rear_cannon" ) ); + jointRightFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_front_cannon" ) ); + jointRightRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_rear_cannon" ) ); +} + +/* +================ +rvMonsterTeleportDropper::Spawn +================ +*/ +void rvMonsterTeleportDropper::Spawn ( void ) { + idEntity* ent; + idDict args; + + // Create the spawner entity + args.Clear ( ); + args.Set ( "classname", spawnArgs.GetString ( "def_spawner" ) ); + gameLocal.SpawnEntityDef ( args, &ent ); + + if ( ent ) { + spawner = static_cast(ent); + spawner->ProcessEvent ( &EV_Activate, this ); + } + + // Define actions + actionDropSpawners.Init ( spawnArgs, "action_dropSpawners", "Torso_DropSpawners", 0 ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Save +================ +*/ +void rvMonsterTeleportDropper::Save ( idSaveGame *savefile ) const { + spawner.Save( savefile ); + + actionDropSpawners.Save( savefile ); + savefile->WriteBool(leftSideBlocked); + savefile->WriteBool(rightSideBlocked); + savefile->WriteBool(leapDidAttack); +} + +/* +================ +rvMonsterTeleportDropper::Restore +================ +*/ +void rvMonsterTeleportDropper::Restore ( idRestoreGame *savefile ) { + spawner.Restore( savefile ); + + actionDropSpawners.Restore( savefile ); + savefile->ReadBool(leftSideBlocked); + savefile->ReadBool(rightSideBlocked); + savefile->ReadBool(leapDidAttack); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Think +================ +*/ +void rvMonsterTeleportDropper::Think ( void ) { + idAI::Think ( ); + if ( aifl.action && actionLeapAttack.status == rvAIAction::STATUS_OK && !leapDidAttack ) { + //in leap attack action + if ( GetEnemy() && enemy.fl.inFov && enemy.range < 64.0f ) + { + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_leap" ), false ); + AttackMelee( "leap", attackDict ); + StartSound( "snd_leap_hit", SND_CHANNEL_BODY, 0, 0, 0 ); + leapDidAttack = true; + } + } +} + +/* +================ +rvMonsterTeleportDropper::State_CombatHide +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_CombatHide ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) ) { + if ( !move.fl.moving ) { + TurnToward ( enemy.lastKnownPosition ); + } + } + + if ( !(FilterTactical( combat.tacticalMaskAvailable )&AITACTICAL_HIDE_BIT) ) + {//shouldn't hide anymore + ForceTacticalUpdate(); + } + + if ( UpdateTactical ( 5000 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; +} + +/* +void rvMonsterTeleportDropper::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + idAI::OnStopMoving(oldMoveCommand); + dropAtGoalOnly = false; +} + +bool rvMonsterTeleportDropper::MoveToEnemy ( void ) { + dropAtGoalOnly = false; + if ( !targets.Num() ) + { + return (idAI::MoveToEnemy()); + } + if ( !GetEnemy() ) + { + return false; + } + idEntity* goalEnt; + idEntity* bestGoal = NULL; + int areaNum = 0; + aasPath_t path; + idVec3 pos; + float dist; + float bestDist = actionDropSpawners.maxRange; + + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + if ( !aiManager.ValidateDestination(this,goalEnt->GetPhysics()->GetOrigin()) ) + { + continue; + } + dist = GetEnemy()->DistanceTo(pos); + if ( dist >= bestDist ) + { + continue; + } + // See if it's possible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + continue; + } + } + dist = bestDist; + bestGoal = goalEnt; + } + if ( bestGoal ) + { + if ( MoveToEntity( bestGoal, bestGoal->spawnArgs.GetFloat("range","64") ) ) + { + dropAtGoalOnly = true; + return true; + } + } + return (idAI::MoveToEnemy()); +} +*/ + +/* +================ +rvMonsterTeleportDropper::FilterTactical +================ +*/ +int rvMonsterTeleportDropper::FilterTactical ( int availableTactical ) { + //do normal filter + availableTactical = idAI::FilterTactical ( availableTactical ); + + //being tethered removes these - add them back in, we don't obey tethers! tethers are for chumps! + availableTactical |= (AITACTICAL_MELEE_BIT|AITACTICAL_HIDE_BIT); + + // Only allow hiding while the drop spawner action isnt ready + if ( !actionDropSpawners.timer.IsDone ( actionTime ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + // Hide while the spawner is still active + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + if ( leftSideBlocked && rightSideBlocked ) + {//both sides are blocked + if ( move.fl.done ) + {//not trying to move + //get out of here! + ForceTacticalUpdate(); + //try finding somewhere else to stand! + availableTactical |= AITACTICAL_RANGED_BIT; + } + } + return availableTactical; +} + +/* +================ +rvMonsterTeleportDropper::CheckAction_DropSpawners +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_DropSpawners ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + //Check to see if at least one side is open + leftSideBlocked = rightSideBlocked = false; + + //trace against solid architecture only, don't care about other entities + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + start = GetPhysics()->GetCenterMass(); + //NOTE: ASSUMPTION + int mask = (MASK_SOLID|CONTENTS_LARGESHOTCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_MONSTERCLIP); + //check left + end = start + (viewAxis[1] * 64.0f); + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //left side is blocked + leftSideBlocked = true; + } + //check right + end = start - (viewAxis[1] * 64.0f); + //trace against solid architecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //right side blocked + rightSideBlocked = true; + } + if ( leftSideBlocked && rightSideBlocked ) + { + return false; + } + + /* + if ( dropAtGoalOnly && !move.fl.done && move.moveCommand == MOVE_TO_ENTITY ) + {//FIXME: check ReachedPos? + return false; + } + //fuck it, just check them all + if ( targets.Num() ) + { + idEntity* goalEnt; + idVec3 pos; + float dist; + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + dist = DistanceTo(pos); + if ( dist <= goalEnt->spawnArgs.GetFloat("range","64") ) + { + return true; + } + } + return false; + } + */ + + return true; +} + +/* +================ +rvMonsterTeleportDropper::Collide +================ +*/ + +/* +================ +rvMonsterTeleportDropper::CheckAction_LeapAttack +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + if ( !move.fl.done + && !move.fl.blocked ) + {//still running away + return false; + } + //done running, okay to attack if in range + } + else + { + if ( !leftSideBlocked || !rightSideBlocked ) + {//clear to shoot on sides + return false; + } + } + + if ( idAI::CheckAction_LeapAttack( action, animNum ) ) + { + leapDidAttack = false; + return true; + } + return false; +} + +/* +================ +rvMonsterTeleportDropper::CheckActions +================ +*/ +bool rvMonsterTeleportDropper::CheckActions ( void ) { + if ( PerformAction ( &actionDropSpawners, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_DropSpawners ) ) { + return true; + } + if ( CheckPainActions ( ) ) { + return true; + } + if ( PerformAction ( &actionLeapAttack, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_LeapAttack ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterTeleportDropper::AttackMissileExt +================ +*/ +idProjectile* rvMonsterTeleportDropper::AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + idProjectile* proj; + + // Launch the projectile + if ( leftSideBlocked || rightSideBlocked ) + { + if ( idStr::Icmp( attackName, "dropSpawner" ) == 0 ) + { + if ( leftSideBlocked ) + { + if ( joint == jointLeftFrontCannon + || joint == jointLeftRearCannon ) + { + return NULL; + } + } + else if ( rightSideBlocked ) + { + if ( joint == jointRightFrontCannon + || joint == jointRightRearCannon ) + { + return NULL; + } + } + } + } + proj = idAI::AttackRanged ( attackName, attackDict, joint, target, pushVelocity ); + + if ( !proj ) { + return NULL; + } + + // If it was a spawner projectile set the spawer + if ( proj->IsType ( rvSpawnerProjectile::GetClassType() ) ) { + static_cast(proj)->SetSpawner ( spawner ); + } + + return proj; +} + +/* +================ +rvMonsterTeleportDropper::GetIdleAnimName +================ +*/ +const char* rvMonsterTeleportDropper::GetIdleAnimName ( void ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) { + return "idle_hide"; + } + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterTeleportDropper::GetDebugInfo +================ +*/ +void rvMonsterTeleportDropper::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterTeleportDropper", "action_dropSpawners", aiActionStatusString[actionDropSpawners.status], userData ); + //proc ( "rvMonsterTeleportDropper", "dropAtGoalOnly", dropAtGoalOnly?"true":"false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTeleportDropper ) + STATE ( "State_CombatHide", rvMonsterTeleportDropper::State_CombatHide ) + STATE ( "Torso_DropSpawners", rvMonsterTeleportDropper::State_Torso_DropSpawners ) +END_CLASS_STATES + +/* +================ +rvMonsterTeleportDropper::State_Torso_DropSpawners +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_Torso_DropSpawners ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "drop_spawners", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ForceTacticalUpdate ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/source/game/ai/Monster_Turret.cpp b/source/game/ai/Monster_Turret.cpp new file mode 100644 index 0000000..3074d9f --- /dev/null +++ b/source/game/ai/Monster_Turret.cpp @@ -0,0 +1,204 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurret : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurret ); + + rvMonsterTurret ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + virtual bool CheckActions ( void ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTurret ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurret ) +END_CLASS + +/* +================ +rvMonsterTurret::rvMonsterTurret +================ +*/ +rvMonsterTurret::rvMonsterTurret ( ) { + shieldHealth = 0; +} + +void rvMonsterTurret::InitSpawnArgsVariables ( void ) { + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurret::Spawn +================ +*/ +void rvMonsterTurret::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + health += shieldHealth; + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterTurret::Save +================ +*/ +void rvMonsterTurret::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterTurret::Restore +================ +*/ +void rvMonsterTurret::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTurret::CheckActions +================ +*/ +bool rvMonsterTurret::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterTurret::Pain +================ +*/ +bool rvMonsterTurret::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurret ) + STATE ( "State_Combat", rvMonsterTurret::State_Combat ) + STATE ( "State_Killed", rvMonsterTurret::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurret::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurret::State_Combat +================ +*/ +stateResult_t rvMonsterTurret::State_Combat ( const stateParms_t& parms ) { + // Aquire a new enemy if we dont have one + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + FaceEnemy ( ); + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurret::State_Killed +================ +*/ +stateResult_t rvMonsterTurret::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_death" ), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurret::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurret::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/Monster_TurretFlying.cpp b/source/game/ai/Monster_TurretFlying.cpp new file mode 100644 index 0000000..c46ad85 --- /dev/null +++ b/source/game/ai/Monster_TurretFlying.cpp @@ -0,0 +1,297 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurretFlying : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurretFlying ); + + rvMonsterTurretFlying ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual bool CheckActions ( void ); + +protected: + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Fall ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + CLASS_STATES_PROTOTYPE ( rvMonsterTurretFlying ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurretFlying ) +END_CLASS + +/* +================ +rvMonsterTurretFlying::rvMonsterTurretFlying +================ +*/ +rvMonsterTurretFlying::rvMonsterTurretFlying ( ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); +} + +void rvMonsterTurretFlying::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurretFlying::Spawn +================ +*/ +void rvMonsterTurretFlying::Spawn ( void ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + + InitSpawnArgsVariables(); + shots = 0; + + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + //don't take damage until we open up + fl.takedamage = false; +} + +/* +================ +rvMonsterTurretFlying::CheckActions +================ +*/ +bool rvMonsterTurretFlying::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + + +/* +================ +rvMonsterTurretFlying::Pain +================ +*/ +bool rvMonsterTurretFlying::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterTurretFlying::Save +================ +*/ +void rvMonsterTurretFlying::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ) ; +} + +/* +================ +rvMonsterTurretFlying::Restore +================ +*/ +void rvMonsterTurretFlying::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ) ; + + InitSpawnArgsVariables(); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurretFlying ) + STATE ( "State_Combat", rvMonsterTurretFlying::State_Combat ) + STATE ( "State_Fall", rvMonsterTurretFlying::State_Fall ) + STATE ( "State_Killed", rvMonsterTurretFlying::State_Killed ) + + STATE ( "Torso_Idle", rvMonsterTurretFlying::State_Torso_Idle ) + STATE ( "Legs_Idle", rvMonsterTurretFlying::State_Legs_Idle ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurretFlying::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurretFlying::State_Combat +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Combat ( const stateParms_t& parms ) { + // Special handling for not being on the ground + if ( !move.fl.onGround ) { + PostState ( "State_Fall", 0 ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurretFlying::State_Fall +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact + STAGE_WAITOPEN, // Wait for drop turret to open up + STAGE_OPENED, // Drop turret opened and ready for combat + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle_closed", 0 ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( move.fl.onGround ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + PlayAnim ( ANIMCHANNEL_TORSO, "open", 2 ); + return SRESULT_STAGE ( STAGE_WAITOPEN ); + + case STAGE_WAITOPEN: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_OPENED ); + } + return SRESULT_WAIT; + + case STAGE_OPENED: + // Activate shield + fl.takedamage = true; + health += shieldHealth; + PlayEffect ( "fx_shieldOpen", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + PostState ( "State_Combat" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterTurretFlying::State_Killed +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurretFlying::State_Torso_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_Idle ( const stateParms_t& parms ) { + if ( move.fl.onGround ) { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_open", parms.blendFrames ); + } else { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_closed", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Legs_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Legs_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, (shots&1)?"range_attack_top":"range_attack_bottom", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/ai/VehicleAI.cpp b/source/game/ai/VehicleAI.cpp new file mode 100644 index 0000000..734b898 --- /dev/null +++ b/source/game/ai/VehicleAI.cpp @@ -0,0 +1,242 @@ +//---------------------------------------------------------------- +// rvVehicleAI.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +CLASS_DECLARATION( idAI, rvVehicleAI ) + EVENT( VD_ChoosePathTarget, rvVehicleAI::Event_ChoosePathTarget ) +END_CLASS + +/* +================ +rvVehicleAI::rvVehicleAI +================ +*/ +rvVehicleAI::rvVehicleAI ( void ) { + flags = 0; +} + +/* +================ +rvVehicleAI::~rvVehicleAI +================ +*/ +rvVehicleAI::~rvVehicleAI ( void ) { +} + +/* +================ +rvVehicleAI::Spawn +================ +*/ +void rvVehicleAI::Spawn ( void ) { + driver = static_cast( gameLocal.SpawnEntityType( rvVehicleDriver::GetClassType() ) ); +} + +/* +================ +rvVehicleAI::Save +================ +*/ +void rvVehicleAI::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); + savefile->WriteInt ( flags ); +} + +/* +================ +rvVehicleAI::Restore +================ +*/ +void rvVehicleAI::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); + savefile->ReadInt ( flags ); +} + +/* +================ +rvVehicleAI::SetVehicle +================ +*/ +void rvVehicleAI::SetVehicle ( rvVehicleMonster * vehicle ) { + const idKeyValue * val = spawnArgs.MatchPrefix( "target", 0 ); + + if ( !driver ) { + return; + } + + driver->ProcessEvent ( &AI_EnterVehicle, vehicle ); + driver->SetPathingMode( rvVehicleDriver::VDPM_Custom, this ); + vehicle->ProcessEvent ( &EV_Door_Lock, true ); + vehicle->team = team; + driver->team = team; + + if ( val ) { + driver->ProcessEvent ( &AI_ScriptedMove, gameLocal.FindEntity( val->GetValue() ), 0.0f, 0 ); + } else { + driver->ProcessEvent ( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::Event_ChoosePathTarget +================ +*/ +void rvVehicleAI::Event_ChoosePathTarget ( idEntity * current ) { + if ( !current ) { + current = const_cast( FindClosestNode() ); + } + + if ( ( flags & VAIF_Freeze ) == 0 ) { + const idVec3 & playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + switch ( flags & 0x03 ) { + case 1 : + idThread::ReturnEntity( MoveCloserTo( playerOrigin, current ) ); + break; + + case 2 : + idThread::ReturnEntity( MoveAwayFrom( playerOrigin, current ) ); + break; + + default: + idThread::ReturnEntity( NULL ); + } + } + + //PostEventMS( &VD_ChoosePathTarget, gameLocal.random.RandomInt( 100 ) + 100, driver->lastPathTargetInfo.node.GetEntity() ); +} + +/* +================ +rvVehicleAI::OnWakeUp +================ +*/ +void rvVehicleAI::OnWakeUp ( void ) { + SetMoveType ( MOVETYPE_CUSTOM ); +} + +/* +================ +rvVehicleAI::CustomMove +================ +*/ +void rvVehicleAI::CustomMove ( void ) { + if ( !driver->pathTargetInfo.node && !(flags & VAIF_Freeze) ) { + driver->ProcessEvent( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::MoveCloserTo +================ +*/ +const idEntity * rvVehicleAI::MoveCloserTo ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float shortestDistance = FLT_MAX; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( shortestDistance > distance ) { + shortestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::MoveAwayFrom +================ +*/ +const idEntity * rvVehicleAI::MoveAwayFrom ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float longestDistance = FLT_MIN; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( longestDistance < distance ) { + longestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::FindClosestNode +================ +*/ +const idEntity * rvVehicleAI::FindClosestNode ( void ) const { + idEntity * current = 0; + const idEntity * best = 0; + float bestDistance = FLT_MAX; + + for (;;) { + current = gameLocal.FindEntityUsingDef( current, "target_vehicle_path" ); + + if ( !current ) { + break; + } + + float dist = ( current->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist < bestDistance ) { + bestDistance = dist; + best = current; + } + } + + return best; +} + +/* +================ +rvVehicleAI::Think +================ +*/ +void rvVehicleAI::Think ( void ) { + idAI::Think(); + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + //HACK: always choose player as the enemy... something is broken somewhere, + // and this is a quick way to get some gameplay out of this for now. Deadlines pwn. + if ( driver && driver->IsDriving() ) { + enemy.ent = gameLocal.GetLocalPlayer(); + enemy.range = ( enemy.ent->GetPhysics()->GetOrigin() - driver->vehicleController.GetVehicle()->GetPhysics()->GetOrigin() ).Length(); + } +} + + diff --git a/source/game/ai/VehicleAI.h b/source/game/ai/VehicleAI.h new file mode 100644 index 0000000..9d4a60e --- /dev/null +++ b/source/game/ai/VehicleAI.h @@ -0,0 +1,96 @@ +//---------------------------------------------------------------- +// rvVehicleAI.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEAI_H__ +#define __GAME_VEHICLEAI_H__ + +#ifndef __AI_H__ +#include "AI.h" +#endif +#ifndef __GAME_VEHICLEMONSTER_H__ +#include "../vehicle/VehicleMonster.h" +#endif + +enum VehicleAI_Flags { + VAIF_Chase = 1, + VAIF_Avoid = 2, + VAIF_Freeze = 4, +}; + +class rvVehicleAI : public idAI { + friend class rvVehicleMonster; + +public: + CLASS_PROTOTYPE( rvVehicleAI ); + + rvVehicleAI ( void ); + ~rvVehicleAI ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void SetVehicle ( rvVehicleMonster * vehicle ); + + void Random ( void ); + void StraightToEnemy ( void ); + void ChaseEnemy ( void ); + void AvoidEnemy ( void ); + void Stop ( void ); + void Start ( void ); + + int & GetFlags ( void ) { return flags; } + + bool IsDriving ( void ) { return driver && driver->IsDriving(); } + rvVehicleDriver* GetDriver ( void ) { return driver.GetEntity(); } + const rvVehicleDriver* GetDriver ( void ) const { return driver.GetEntity(); } + +private: + virtual void OnWakeUp ( void ); + virtual void CustomMove ( void ); + + const idEntity * MoveCloserTo ( const idVec3 & point, idEntity * current ); + const idEntity * MoveAwayFrom ( const idVec3 & point, idEntity * current ); + const idEntity * FindClosestNode ( void ) const; + + void Event_ChoosePathTarget ( idEntity * current ); + + idEntityPtr driver; + int flags; +}; + +ID_INLINE void rvVehicleAI::Random ( void ) { + flags = 0; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::StraightToEnemy ( void ) { + driver->ProcessEvent( &AI_ScriptedMove, enemy.ent.GetEntity(), 0.0f, 0 ); +} + +ID_INLINE void rvVehicleAI::ChaseEnemy ( void ) { + flags = VAIF_Chase; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::AvoidEnemy ( void ) { + flags = VAIF_Avoid; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::Stop ( void ) { + flags = ( flags & 0x03 ) | VAIF_Freeze; + driver->ProcessEvent( &AI_ScriptedStop ); +} + +ID_INLINE void rvVehicleAI::Start ( void ) { + flags = ( flags & 0x03 ) & ~VAIF_Freeze; + //driver->ProcessEvent( &EV_Activate, this ); + CustomMove(); +} + +#endif // __GAME_VEHICLEAI_H__ diff --git a/source/game/anim/Anim.cpp b/source/game/anim/Anim.cpp new file mode 100644 index 0000000..77430f7 --- /dev/null +++ b/source/game/anim/Anim.cpp @@ -0,0 +1,1182 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +bool idAnimManager::forceExport = false; + +/*********************************************************************** + + idMD5Anim + +***********************************************************************/ + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::idMD5Anim() { + ref_count = 0; + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + totaldelta.Zero(); +} + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::~idMD5Anim() { + Free(); +} + +/* +==================== +idMD5Anim::Free +==================== +*/ +void idMD5Anim::Free( void ) { + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + name = ""; + + totaldelta.Zero(); + + jointInfo.Clear(); + bounds.Clear(); + componentFrames.Clear(); +} + +/* +==================== +idMD5Anim::NumFrames +==================== +*/ +int idMD5Anim::NumFrames( void ) const { + return numFrames; +} + +/* +==================== +idMD5Anim::NumJoints +==================== +*/ +int idMD5Anim::NumJoints( void ) const { + return numJoints; +} + +/* +==================== +idMD5Anim::Length +==================== +*/ +int idMD5Anim::Length( void ) const { + return animLength; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const idVec3 &idMD5Anim::TotalMovementDelta( void ) const { + return totaldelta; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const char *idMD5Anim::Name( void ) const { + return name; +} + +/* +==================== +idMD5Anim::Reload +==================== +*/ +bool idMD5Anim::Reload( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idStr filename; + + filename = name; + Free(); + + return LoadAnim( filename ); +} + +/* +==================== +idMD5Anim::Allocated +==================== +*/ +size_t idMD5Anim::Allocated( void ) const { + size_t size = bounds.Allocated() + jointInfo.Allocated() + componentFrames.Allocated() + name.Allocated(); + return size; +} + +/* +==================== +idMD5Anim::LoadAnim +==================== +*/ +bool idMD5Anim::LoadAnim( const char *filename ) { + int version; +// RAVEN BEGIN +// jsinger: done this way to minimize amount of code change + idAutoPtr lexer( LexerFactory::MakeLexer(LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT) ); + Lexer &parser(*lexer); +// RAVEN END + idToken token; + int i, j; + int num; + + if ( !parser.LoadFile( filename ) ) { + return false; + } + + Free(); + + name = filename; + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + numJoints = parser.ParseInt(); + if ( numJoints <= 0 ) { + parser.Error( "Invalid number of joints: %d", numJoints ); + } + + // parse frame rate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate < 0 ) { + parser.Error( "Invalid frame rate: %d", frameRate ); + } + + // parse number of animated components + parser.ExpectTokenString( "numAnimatedComponents" ); + numAnimatedComponents = parser.ParseInt(); + if ( ( numAnimatedComponents < 0 ) || ( numAnimatedComponents > numJoints * 6 ) ) { + parser.Error( "Invalid number of animated components: %d", numAnimatedComponents ); + } + + // parse the hierarchy + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( numJoints ); + parser.ExpectTokenString( "hierarchy" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + parser.ReadToken( &token ); +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + jointInfo[ i ].nameIndex = animationLib->JointIndex( token ); +// RAVEN END + + // parse parent num + jointInfo[ i ].parentNum = parser.ParseInt(); + if ( jointInfo[ i ].parentNum >= i ) { + parser.Error( "Invalid parent num: %d", jointInfo[ i ].parentNum ); + } + + if ( ( i != 0 ) && ( jointInfo[ i ].parentNum < 0 ) ) { + parser.Error( "Animations may have only one root joint" ); + } + + // parse anim bits + jointInfo[ i ].animBits = parser.ParseInt(); + if ( jointInfo[ i ].animBits & ~63 ) { + parser.Error( "Invalid anim bits: %d", jointInfo[ i ].animBits ); + } + + // parse first component + jointInfo[ i ].firstComponent = parser.ParseInt(); + if ( ( numAnimatedComponents > 0 ) && ( ( jointInfo[ i ].firstComponent < 0 ) || ( jointInfo[ i ].firstComponent >= numAnimatedComponents ) ) ) { + parser.Error( "Invalid first component: %d", jointInfo[ i ].firstComponent ); + } + } + + parser.ExpectTokenString( "}" ); + + // parse bounds + parser.ExpectTokenString( "bounds" ); + parser.ExpectTokenString( "{" ); + bounds.SetGranularity( 1 ); + bounds.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, bounds[ i ][ 0 ].ToFloatPtr() ); + parser.Parse1DMatrix( 3, bounds[ i ][ 1 ].ToFloatPtr() ); + } + parser.ExpectTokenString( "}" ); + + // parse base frame + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( numJoints ); + parser.ExpectTokenString( "baseframe" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + idCQuat q; + parser.Parse1DMatrix( 3, baseFrame[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, q.ToFloatPtr() );//baseFrame[ i ].q.ToFloatPtr() ); + baseFrame[ i ].q = q.ToQuat();//.w = baseFrame[ i ].q.CalcW(); + } + parser.ExpectTokenString( "}" ); + + // parse frames + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( numAnimatedComponents * numFrames ); + + float *componentPtr = componentFrames.Ptr(); + for( i = 0; i < numFrames; i++ ) { + parser.ExpectTokenString( "frame" ); + num = parser.ParseInt(); + if ( num != i ) { + parser.Error( "Expected frame number %d", i ); + } + parser.ExpectTokenString( "{" ); + + for( j = 0; j < numAnimatedComponents; j++, componentPtr++ ) { + *componentPtr = parser.ParseFloat(); + } + + parser.ExpectTokenString( "}" ); + } + + // get total move delta + if ( !numAnimatedComponents ) { + totaldelta.Zero(); + } else { + componentPtr = &componentFrames[ jointInfo[ 0 ].firstComponent ]; + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.x; + } + totaldelta.x = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.x = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.y; + } + totaldelta.y = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.y = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.z; + } + totaldelta.z = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + } else { + totaldelta.z = 0.0f; + } + } + baseFrame[ 0 ].t.Zero(); + + // we don't count last frame because it would cause a 1 frame pause at the end + animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + // done + return true; +} + +/* +==================== +idMD5Anim::IncreaseRefs +==================== +*/ +void idMD5Anim::IncreaseRefs( void ) const { + ref_count++; +} + +/* +==================== +idMD5Anim::DecreaseRefs +==================== +*/ +void idMD5Anim::DecreaseRefs( void ) const { + ref_count--; +} + +/* +==================== +idMD5Anim::NumRefs +==================== +*/ +int idMD5Anim::NumRefs( void ) const { + return ref_count; +} + +/* +==================== +idMD5Anim::ConvertTimeToFrame +==================== +*/ +void idMD5Anim::ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const { + int frameTime; + int frameNum; + + if ( numFrames <= 1 ) { + frame.frame1 = 0; + frame.frame2 = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + if ( time <= 0 ) { + frame.frame1 = 0; + frame.frame2 = 1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + frameTime = time * frameRate; + frameNum = frameTime / 1000; + frame.cycleCount = frameNum / ( numFrames - 1 ); + + if ( ( cyclecount > 0 ) && ( frame.cycleCount >= cyclecount ) ) { + frame.cycleCount = cyclecount - 1; + frame.frame1 = numFrames - 1; + frame.frame2 = frame.frame1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + return; + } + + frame.frame1 = frameNum % ( numFrames - 1 ); + frame.frame2 = frame.frame1 + 1; + if ( frame.frame2 >= numFrames ) { + frame.frame2 = 0; + } + + frame.backlerp = ( frameTime % 1000 ) * 0.001f; + frame.frontlerp = 1.0f - frame.backlerp; +} + +// RAVEN BEGIN +// jscott: added block +/* +==================== +idMD5Anim::ConvertFrameToTime + +MD5_FRAMERATE is always 24 +==================== +*/ +int idMD5Anim::ConvertFrameToTime( frameBlend_t &frame ) const { + int time; + + // Adjust the time so the lerping doesn't break the reverse calc + time = ( ( frame.frame1 % (numFrames - 1)) * 1000 ) / frameRate; + time += 500 / frameRate; + + return( time ); +} +// RAVEN END + +/* +==================== +idMD5Anim::GetOrigin +==================== +*/ +void idMD5Anim::GetOrigin( idVec3 &offset, int time, int cyclecount ) const { + frameBlend_t frame; + + offset = baseFrame[ 0 ].t; + if ( !( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) ) { + // just use the baseframe + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + + if ( frame.cycleCount ) { + offset += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetOriginRotation +==================== +*/ +void idMD5Anim::GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const { + frameBlend_t frame; + int animBits; + + animBits = jointInfo[ 0 ].animBits; + if ( !( animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) ) { + // just use the baseframe + rotation = baseFrame[ 0 ].q; + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *jointframe1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *jointframe2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( animBits & ANIM_TX ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TY ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TZ ) { + jointframe1++; + jointframe2++; + } + + idQuat q1; + idQuat q2; + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case ANIM_QX: + q1.x = jointframe1[0]; + q2.x = jointframe2[0]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY: + q1.y = jointframe1[0]; + q2.y = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QZ: + q1.z = jointframe1[0]; + q2.z = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + q1.x = jointframe1[0]; + q1.z = jointframe1[1]; + q2.x = jointframe2[0]; + q2.z = jointframe2[1]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + q1.y = jointframe1[0]; + q1.z = jointframe1[1]; + q2.y = jointframe2[0]; + q2.z = jointframe2[1]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q1.z = jointframe1[2]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q2.z = jointframe2[2]; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + } + + rotation.Slerp( q1, q2, frame.backlerp ); +} + +/* +==================== +idMD5Anim::GetBounds +==================== +*/ +void idMD5Anim::GetBounds( idBounds &bnds, int time, int cyclecount ) const { + frameBlend_t frame; + idVec3 offset; + + ConvertTimeToFrame( time, cyclecount, frame ); + + bnds = bounds[ frame.frame1 ]; + bnds.AddBounds( bounds[ frame.frame2 ] ); + + // origin position + offset = baseFrame[ 0 ].t; + if ( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) { + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + } + + bnds[ 0 ] -= offset; + bnds[ 1 ] -= offset; +} + +/* +==================== +idMD5Anim::GetInterpolatedFrame +==================== +*/ +void idMD5Anim::GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const { + int i, numLerpJoints; + const float *frame1; + const float *frame2; + const float *jointframe1; + const float *jointframe2; + const jointAnimInfo_t *infoPtr; + int animBits; + idJointQuat *blendJoints; + idJointQuat *jointPtr; + idJointQuat *blendPtr; + int *lerpIndex; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return; + } +#endif + + if ( !numAnimatedComponents ) { + // just use the base frame + return; + } + + blendJoints = (idJointQuat *)_alloca16( baseFrame.Num() * sizeof( blendPtr[ 0 ] ) ); + lerpIndex = (int *)_alloca16( baseFrame.Num() * sizeof( lerpIndex[ 0 ] ) ); + numLerpJoints = 0; + + frame1 = &componentFrames[ frame.frame1 * numAnimatedComponents ]; + frame2 = &componentFrames[ frame.frame2 * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + blendPtr = &blendJoints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + lerpIndex[numLerpJoints++] = j; + + jointframe1 = frame1 + infoPtr->firstComponent; + jointframe2 = frame2 + infoPtr->firstComponent; + + switch( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + case 0: + blendPtr->t = jointPtr->t; + break; + case ANIM_TX: + jointPtr->t.x = jointframe1[0]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointPtr->t.y; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TY: + jointPtr->t.y = jointframe1[0]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TZ: + jointPtr->t.z = jointframe1[0]; + blendPtr->t.z = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.y = jointPtr->t.y; + jointframe1++; + jointframe2++; + break; + case ANIM_TX|ANIM_TY: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointPtr->t.z; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.y = jointPtr->t.y; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TY|ANIM_TZ: + jointPtr->t.y = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.x = jointPtr->t.x; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TY|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + jointPtr->t.z = jointframe1[2]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointframe2[2]; + jointframe1 += 3; + jointframe2 += 3; + break; + } + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case 0: + blendPtr->q = jointPtr->q; + break; + case ANIM_QX: + jointPtr->q.x = jointframe1[0]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointPtr->q.y; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY: + jointPtr->q.y = jointframe1[0]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QZ: + jointPtr->q.z = jointframe1[0]; + blendPtr->q.z = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + jointPtr->q.y = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.x = jointPtr->q.x; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + jointPtr->q.z = jointframe1[2]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointframe2[2]; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + } + } + } + + SIMDProcessor->BlendJoints( joints, blendJoints, frame.backlerp, lerpIndex, numLerpJoints ); + + if ( frame.cycleCount ) { + joints[ 0 ].t += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetSingleFrame +==================== +*/ +void idMD5Anim::GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const { + int i; + const float *frame; + const float *jointframe; + int animBits; + idJointQuat *jointPtr; + const jointAnimInfo_t *infoPtr; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( ( framenum == 0 ) || !numAnimatedComponents ) { + // just use the base frame + return; + } + + frame = &componentFrames[ framenum * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + jointframe = frame + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe++; + } + + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe++; + } + + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe++; + } + + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe++; + } + + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe; + } + + jointPtr->q.w = jointPtr->q.CalcW(); + } + } + } +} + +/* +==================== +idMD5Anim::CheckModelHierarchy +==================== +*/ +void idMD5Anim::CheckModelHierarchy( const idRenderModel *model ) const { + int i; + int jointNum; + int parent; + + if ( jointInfo.Num() != model->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Error( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), model->NumJoints(), name.c_str(), jointInfo.Num() ); +// scork: if we don't return here, we get dozens of other warnings generated by mismatching models below, one warning is sufficient... + if (common->DoingDeclValidation()) { + return; + } +// RAVEN END + } + + const idMD5Joint *modelJoints = model->GetJoints(); + for( i = 0; i < jointInfo.Num(); i++ ) { + jointNum = jointInfo[ i ].nameIndex; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + if ( modelJoints[ i ].name != animationLib->JointName( jointNum ) ) { +// RAVEN END + gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() ); + } + if ( modelJoints[ i ].parent ) { + parent = modelJoints[ i ].parent - modelJoints; + } else { + parent = -1; + } + if ( parent != jointInfo[ i ].parentNum ) { + gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() ); + } + } +} + +/*********************************************************************** + + idAnimManager + +***********************************************************************/ + +/* +==================== +idAnimManager::idAnimManager +==================== +*/ +idAnimManager::idAnimManager() { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + insideLevelLoad=false; +#endif +// RAVEN END +} + +/* +==================== +idAnimManager::~idAnimManager +==================== +*/ +idAnimManager::~idAnimManager() { + Shutdown(); +} + +/* +==================== +idAnimManager::Shutdown +==================== +*/ +void idAnimManager::Shutdown( void ) { + animations.DeleteContents(); + jointnames.Clear(); + jointnamesHash.Free(); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +==================== +idAnimManager::BeginLevelLoad +==================== +*/ +void idAnimManager::BeginLevelLoad( void ) +{ + insideLevelLoad = true; +} + +/* +==================== +idAnimManager::EndLevelLoad +==================== +*/ +void idAnimManager::EndLevelLoad( void ) +{ + insideLevelLoad = false; +} +#endif +// RAVEN END + +/* +==================== +idAnimManager::GetAnim +==================== +*/ +idMD5Anim *idAnimManager::GetAnim( const char *name ) { + idMD5Anim **animptrptr; + idMD5Anim *anim; + + // see if it has been asked for before + animptrptr = NULL; + if ( animations.Get( name, &animptrptr ) ) { + anim = *animptrptr; + } else { + idStr extension; + idStr filename = name; + + filename.ExtractFileExtension( extension ); + if ( extension != MD5_ANIM_EXT ) { +// RAVEN BEGIN +// nmckenzie: I'm not interested in debugging this blindly again. + gameLocal.Warning( "Animation '%s' doesn't have the correct extension for an animation file.", filename.c_str() ); +// RAVEN END + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(insideLevelLoad?RV_HEAP_ID_LEVEL:RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + anim = new idMD5Anim(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + if ( !anim->LoadAnim( filename ) ) { + gameLocal.Warning( "Couldn't load anim: '%s'", filename.c_str() ); + delete anim; + anim = NULL; + } + animations.Set( filename, anim ); + } + + return anim; +} + +/* +================ +idAnimManager::ReloadAnims +================ +*/ +void idAnimManager::ReloadAnims( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + ( *animptr )->Reload(); + } + } +} + +/* +================ +idAnimManager::JointIndex +================ +*/ +int idAnimManager::JointIndex( const char *name ) { + int i, hash; + + hash = jointnamesHash.GenerateKey( name ); + for ( i = jointnamesHash.First( hash ); i != -1; i = jointnamesHash.Next( i ) ) { + if ( jointnames[i].Cmp( name ) == 0 ) { + return i; + } + } + + i = jointnames.Append( name ); + jointnamesHash.Add( hash, i ); + return i; +} + +/* +================ +idAnimManager::JointName +================ +*/ +const char *idAnimManager::JointName( int index ) const { + return jointnames[ index ]; +} + +/* +================ +idAnimManager::ListAnims +================ +*/ +void idAnimManager::ListAnims( void ) const { + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t s; + size_t namesize; + int num; + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + s = anim->Size(); + gameLocal.Printf( "%8d bytes : %2d refs : %s\n", s, anim->NumRefs(), anim->Name() ); + size += s; + num++; + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + gameLocal.Printf( "\n%d memory used in %d anims\n", size, num ); + gameLocal.Printf( "%d memory used in %d joint names\n", namesize, jointnames.Num() ); +} + +// RAVEN BEGIN +/* +================ +idAnimManager::PrintMemInfo +================ +*/ +void idAnimManager::PrintMemInfo( MemInfo *mi ) { + + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t namesize; + int num; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_anim.txt" ); + if( !f ) { + return; + } + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + size += anim->Size(); + num++; + + f->Printf( "%8d: %s\n", anim->Size(), anim->Name() ); + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + mi->animsAssetsCount = num; + mi->animsAssetsTotal = namesize + size; + + f->Printf( "\nTotal anim bytes allocated: %s (%s items)\n", idStr::FormatNumber( mi->animsAssetsTotal ).c_str(), idStr::FormatNumber( mi->animsAssetsCount ).c_str() ); + fileSystem->CloseFile( f ); +} +// RAVEN END + +/* +================ +idAnimManager::FlushUnusedAnims +================ +*/ +void idAnimManager::FlushUnusedAnims( void ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + idList removeAnims; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + if ( ( *animptr )->NumRefs() <= 0 ) { + removeAnims.Append( *animptr ); + } + } + } + + for( i = 0; i < removeAnims.Num(); i++ ) { + animations.Remove( removeAnims[ i ]->Name() ); + delete removeAnims[ i ]; + } +} diff --git a/source/game/anim/Anim.h b/source/game/anim/Anim.h new file mode 100644 index 0000000..c72b45c --- /dev/null +++ b/source/game/anim/Anim.h @@ -0,0 +1,801 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __ANIM_H__ +#define __ANIM_H__ + +// +// animation channels +// these can be changed by modmakers and licensees to be whatever they need. +const int ANIM_NumAnimChannels = 5; +const int ANIM_MaxAnimsPerChannel = 3; +const int ANIM_MaxSyncedAnims = 3; + +// +// animation channels. make sure to change script/doom_defs.script if you add any channels, or change their order +// +const int ANIMCHANNEL_ALL = 0; +const int ANIMCHANNEL_TORSO = 1; +const int ANIMCHANNEL_LEGS = 2; +const int ANIMCHANNEL_HEAD = 3; +const int ANIMCHANNEL_EYELIDS = 4; + +// for converting from 24 frames per second to milliseconds +ID_INLINE int FRAME2MS( int framenum ) { + return ( framenum * 1000 ) / 24; +} + +class idRenderModel; +class idAnimator; +class idAnimBlend; +class function_t; +class idEntity; +class idSaveGame; +class idRestoreGame; + +typedef struct { + int cycleCount; // how many times the anim has wrapped to the begining (0 for clamped anims) + int frame1; + int frame2; + float frontlerp; + float backlerp; +} frameBlend_t; + +typedef struct { + int nameIndex; + int parentNum; + int animBits; + int firstComponent; +} jointAnimInfo_t; + +typedef struct { + jointHandle_t num; + jointHandle_t parentNum; + int channel; +} jointInfo_t; + +// +// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order. +// +typedef enum { + JOINTMOD_NONE, // no modification + JOINTMOD_LOCAL, // modifies the joint's position or orientation in joint local space + JOINTMOD_LOCAL_OVERRIDE, // sets the joint's position or orientation in joint local space + JOINTMOD_WORLD, // modifies joint's position or orientation in model space + JOINTMOD_WORLD_OVERRIDE, // sets the joint's position or orientation in model space + JOINTMOD_COLLAPSE +} jointModTransform_t; + +typedef struct { + jointHandle_t jointnum; + idMat3 mat; + idVec3 pos; + jointModTransform_t transform_pos; + jointModTransform_t transform_axis; + +// RAVEN BEGIN +// bdube: added more features to programmer controlled joints + idInterpolateAccelDecelLinear angularVelocity; + int lastTime; + + jointHandle_t collapseJoint; +// RAVEN END + +} jointMod_t; + +#define ANIM_TX BIT( 0 ) +#define ANIM_TY BIT( 1 ) +#define ANIM_TZ BIT( 2 ) +#define ANIM_QX BIT( 3 ) +#define ANIM_QY BIT( 4 ) +#define ANIM_QZ BIT( 5 ) + +typedef enum { + FC_SCRIPTFUNCTION, + FC_SCRIPTFUNCTIONOBJECT, + FC_EVENTFUNCTION, +// RAVEN BEGIN +// abahr: event call with parms + FC_EVENTFUNCTION_ARGS, +// RAVEN END + FC_SOUND, + FC_SOUND_VOICE, + FC_SOUND_VOICE2, + FC_SOUND_BODY, + FC_SOUND_BODY2, + FC_SOUND_BODY3, + FC_SOUND_WEAPON, + FC_SOUND_ITEM, + FC_SOUND_GLOBAL, + FC_SOUND_CHATTER, + FC_SKIN, + FC_TRIGGER, + FC_DIRECTDAMAGE, + FC_MUZZLEFLASH, + FC_FOOTSTEP, + FC_LEFTFOOT, + FC_RIGHTFOOT, + FC_ENABLE_EYE_FOCUS, + FC_DISABLE_EYE_FOCUS, + FC_FX, + FC_DISABLE_GRAVITY, + FC_ENABLE_GRAVITY, + FC_JUMP, + FC_ENABLE_CLIP, + FC_DISABLE_CLIP, + FC_ENABLE_WALK_IK, + FC_DISABLE_WALK_IK, + FC_ENABLE_LEG_IK, + FC_DISABLE_LEG_IK, + FC_RECORDDEMO, + FC_AVIGAME, + FC_GUIEVENT, + + FC_AI_ENABLE_PAIN, + FC_AI_DISABLE_PAIN, + FC_AI_ENABLE_DAMAGE, + FC_AI_DISABLE_DAMAGE, + FC_AI_LOCKENEMYORIGIN, + FC_AI_ATTACK, + FC_AI_ATTACK_MELEE, + + FC_AI_SPEAK, + FC_AI_SPEAK_RANDOM, +// MCG: for attachment managing + FC_ACT_ATTACH_HIDE, + FC_ACT_ATTACH_SHOW, + + FC_ENABLE_BLINKING, + FC_DISABLE_BLINKING, + FC_ENABLE_AUTOBLINK, + FC_DISABLE_AUTOBLINK, + + FC_COUNT + +} frameCommandType_t; + +// RAVEN BEGIN +// rjohnson: new camera frame commands +extern struct frameCommandInfo_t +{ + const char* name; + bool modview; + +} frameCommandInfo[FC_COUNT]; +// RAVEN END + +typedef struct { + int num; + int firstCommand; +} frameLookup_t; + +typedef struct { + frameCommandType_t type; + + idStr* string; + +// RAVEN BEGIN +// bdube: added joint + idStr* joint; + idStr* joint2; +// abahr: + idList* parmList; +// RAVEN END + + union { + const idSoundShader *soundShader; + const function_t *function; + const idDeclSkin *skin; + int index; +// RAVEN BEGIN +// bdube: effects + const idDecl *effect; + idStr* projectile; + idStr* melee; +// abahr: + const class idEventDef* event; +// RAVEN END + }; +} frameCommand_t; + +typedef struct { + bool prevent_idle_override : 1; + bool random_cycle_start : 1; + bool ai_no_turn : 1; + bool ai_no_look : 1; + bool ai_look_head_only : 1; + bool anim_turn : 1; + bool sync_cycle : 1; +} animFlags_t; + + +/* +============================================================================================== + + idModelExport + +============================================================================================== +*/ + +class idModelExport { +private: + void Reset( void ); + bool ParseOptions( idLexer &lex ); + int ParseExportSection( idParser &parser ); + + static bool CheckMayaInstall( void ); + static void LoadMayaDll( void ); + + bool ConvertMayaToMD5( void ); + static bool initialized; + +public: + idStr commandLine; + idStr src; + idStr dest; + bool force; + + idModelExport(); + + static void Shutdown( void ); + + int ExportDefFile( const char *filename ); + bool ExportModel( const char *model ); + bool ExportAnim( const char *anim ); + int ExportModels( const char *pathname, const char *extension ); +}; + +/* +============================================================================================== + + idMD5Anim + +============================================================================================== +*/ + +class idMD5Anim { +private: + int numFrames; + int frameRate; + int animLength; + int numJoints; + int numAnimatedComponents; + idList bounds; + idList jointInfo; + idList baseFrame; + idList componentFrames; + idStr name; + idVec3 totaldelta; + mutable int ref_count; + +public: + idMD5Anim(); + ~idMD5Anim(); + + void Free( void ); + bool Reload( void ); + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); }; + bool LoadAnim( const char *filename ); + + void IncreaseRefs( void ) const; + void DecreaseRefs( void ) const; + int NumRefs( void ) const; + + void CheckModelHierarchy( const idRenderModel *model ) const; + void GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const; + void GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumJoints( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + const char *Name( void ) const; + + void ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const; +// RAVEN BEGIN +// jscott: for modview + int ConvertFrameToTime( frameBlend_t &frame ) const; +// RAVEN END + + void GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const; + void GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const; + void GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const; +}; + +/* +============================================================================================== + + idAnim + +============================================================================================== +*/ + +class idAnim { +private: + const class idDeclModelDef *modelDef; + const idMD5Anim *anims[ ANIM_MaxSyncedAnims ]; + int numAnims; + idStr name; + idStr realname; + idList frameLookup; + idList frameCommands; + animFlags_t flags; + +// RAVEN BEGIN +// bdube: added anim speed + float rate; +// RAVEN END + +public: + idAnim(); + idAnim( const idDeclModelDef *modelDef, const idAnim *anim ); + ~idAnim(); + + void SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ); + const char *Name( void ) const; + const char *FullName( void ) const; + const idMD5Anim *MD5Anim( int num ) const; + const idDeclModelDef *ModelDef( void ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumAnims( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + bool GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const; + bool GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const; + bool GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const; +// RAVEN BEGIN +// bdube: frame command function that takes a list of frames + const char *AddFrameCommand( const class idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ); +// RAVEN END + void CallFrameCommands( idEntity *ent, int from, int to ) const; + bool HasFrameCommands( void ) const; + + // returns first frame (zero based) that command occurs. returns -1 if not found. + int FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const; + void SetAnimFlags( const animFlags_t &animflags ); + const animFlags_t &GetAnimFlags( void ) const; + +// RAVEN BEGIN +// bdube: added + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; + float GetPlaybackRate ( void ) const; + void SetPlaybackRate ( float rate ); +// jsinger: to support binary serialization/deserialization of idAnims +#ifdef RV_BINARYDECLS + idAnim( idDeclModelDef const *def, SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; +#endif +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added configurable playback rate +ID_INLINE float idAnim::GetPlaybackRate ( void ) const { + return rate; +} + +ID_INLINE void idAnim::SetPlaybackRate ( float _rate ) { + rate = _rate; +} + +// RAVEN END + +/* +============================================================================================== + + idDeclModelDef + +============================================================================================== +*/ + +// RAVEN BEGIN +// jsinger; allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclModelDef : public idDecl, public Serializable<'DMD '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idDeclModelDef( SerialInputStream &stream ); +private: + int mNumChannels; +#else +class idDeclModelDef : public idDecl { +#endif +// RAVEN END +public: + idDeclModelDef(); + ~idDeclModelDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + const idDeclSkin * GetDefaultSkin( void ) const; + const idJointQuat * GetDefaultPose( void ) const; + void SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const; + idRenderModel * ModelHandle( void ) const; + void GetJointList( const char *jointnames, idList &jointList ) const; + const jointInfo_t * FindJoint( const char *name ) const; + + int NumAnims( void ) const; + const idAnim * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + const idDeclSkin * GetSkin( void ) const; + const char * GetModelName( void ) const; + const idList & Joints( void ) const; + const int * JointParents( void ) const; + int NumJoints( void ) const; + const jointInfo_t * GetJoint( int jointHandle ) const; + const char * GetJointName( int jointHandle ) const; + int NumJointsOnChannel( int channel ) const; + const int * GetChannelJoints( int channel ) const; + + const idVec3 & GetVisualOffset( void ) const; + +private: + void CopyDecl( const idDeclModelDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idVec3 offset; + idList joints; + idList jointParents; + idList channelJoints[ ANIM_NumAnimChannels ]; + idRenderModel * modelHandle; + idList anims; + const idDeclSkin * skin; +}; + +ID_INLINE const idAnim *idDeclModelDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idAnimBlend + +============================================================================================== +*/ + +class idAnimBlend { +private: + const class idDeclModelDef *modelDef; + int starttime; + int endtime; + int timeOffset; + float rate; + + int blendStartTime; + int blendDuration; + float blendStartValue; + float blendEndValue; + + float animWeights[ ANIM_MaxSyncedAnims ]; + short cycle; + short animNum; + bool allowMove; + bool allowFrameCommands; + bool useFrameBlend; + + frameBlend_t frameBlend; + + friend class idAnimator; + + void Reset( const idDeclModelDef *_modelDef ); + void CallFrameCommands( idEntity *ent, int fromtime, int totime ) const; +// RAVEN BEGIN +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( const idDeclModelDef *modelDef, int animnum, const frameBlend_t & frameBlend ); +// jshepard: added rate parameter so we can speed up/slow down animations. + void CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); + void PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); +// RAVEN END + bool BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const; + void BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const; + void BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const; + void BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const; + bool AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const; + +public: + idAnimBlend(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ); + const char *AnimName( void ) const; + const char *AnimFullName( void ) const; + float GetWeight( int currenttime ) const; + float GetFinalWeight( void ) const; + void SetWeight( float newweight, int currenttime, int blendtime ); + int NumSyncedAnims( void ) const; + bool SetSyncedAnimWeight( int num, float weight ); + void Clear( int currentTime, int clearTime ); + bool IsDone( int currentTime ) const; + bool FrameHasChanged( int currentTime ) const; + int GetCycleCount( void ) const; + void SetCycleCount( int count ); + void SetPlaybackRate( int currentTime, float newRate ); + float GetPlaybackRate( void ) const; + void SetStartTime( int startTime ); + int GetStartTime( void ) const; + int GetEndTime( void ) const; + int GetFrameNumber( int currenttime ) const; + int AnimTime( int currenttime ) const; + int NumFrames( void ) const; + int Length( void ) const; + int PlayLength( void ) const; + void AllowMovement( bool allow ); + void AllowFrameCommands( bool allow ); + const idAnim *Anim( void ) const; + int AnimNum( void ) const; +}; + +ID_INLINE const idAnim *idAnimBlend::Anim( void ) const { + if ( !modelDef ) { + return NULL; + } + return modelDef->GetAnim( animNum ); +} + +/* +============================================================================================== + + idAFPoseJointMod + +============================================================================================== +*/ + +typedef enum { + AF_JOINTMOD_AXIS, + AF_JOINTMOD_ORIGIN, + AF_JOINTMOD_BOTH +} AFJointModType_t; + +class idAFPoseJointMod { +public: + idAFPoseJointMod( void ); + + AFJointModType_t mod; + idMat3 axis; + idVec3 origin; +}; + +ID_INLINE idAFPoseJointMod::idAFPoseJointMod( void ) { + mod = AF_JOINTMOD_AXIS; + axis.Identity(); + origin.Zero(); +} + +/* +============================================================================================== + + idAnimator + +============================================================================================== +*/ + +class idAnimator{ + public: + idAnimator(); + ~idAnimator(); + + size_t Allocated( void ) const; + size_t Size( void ) const; + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetEntity( idEntity *ent ); + idEntity *GetEntity( void ) const ; + void RemoveOriginOffset( bool remove ); + bool RemoveOrigin( void ) const; + + void GetJointList( const char *jointnames, idList &jointList ) const; + + int NumAnims( void ) const; + const idAnim *GetAnim( int index ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + + void ServiceAnims( int fromtime, int totime ); + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation + bool IsAnimating ( int currentTime, bool IgnoreAF = false ) const; + bool IsBlending ( int channelNum, int currentTime ) const; +// RAVEN END + + void GetJoints( int *numJoints, idJointMat **jointsPtr ); + int NumJoints( void ) const; + jointHandle_t GetFirstChild( jointHandle_t jointnum ) const; + jointHandle_t GetFirstChild( const char *name ) const; + + idRenderModel *SetModel( const char *modelname ); + idRenderModel *ModelHandle( void ) const; + const idDeclModelDef *ModelDef( void ) const; + + void ForceUpdate( void ); + void ClearForceUpdate( void ); + bool CreateFrame( int animtime, bool force ); + bool FrameHasChanged( int animtime ) const; + void GetDelta( int fromtime, int totime, idVec3 &delta ) const; + bool GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const; + void GetOrigin( int currentTime, idVec3 &pos ) const; + bool GetBounds( int currentTime, idBounds &bounds ); + + idAnimBlend *CurrentAnim( int channelNum ); + void Clear( int channelNum, int currentTime, int cleartime ); + +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( int channelNum, int animnum, const frameBlend_t & frameBlend ); + void CycleAnim( int channelNum, int animnum, int currenttime, int blendtime ); + void PlayAnim( int channelNum, int animnum, int currenttime, int blendTime ); + + // copies the current anim from fromChannelNum to channelNum. + // the copied anim will have frame commands disabled to avoid executing them twice. + void SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime ); + + void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void GetJointAxis( jointHandle_t jointnum, idMat3 &mat ); + void CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ); + void CollapseJoints ( const char* jointnames, jointHandle_t collapseJoint ); + void ClearJoint( jointHandle_t jointnum ); + void ClearAllJoints( void ); + +// RAVEN BEGIN +// bdube: more joint control functions + void AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int blendtime ); + void SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ); + idAngles GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ); + void ClearJointAngularVelocity ( jointHandle_t jointnum ); + +// jshepard: rate of playback change + void SetPlaybackRate(float multiplier); +// abahr: + void SetPlaybackRate( const char* animName, float rate ); + void SetPlaybackRate( int animHandle, float rate ); +// RAVEN END + + void InitAFPose( void ); + void SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ); + void FinishAFPose( int animnum, const idBounds &bounds, const int time ); + void SetAFPoseBlendWeight( float blendWeight ); + bool BlendAFPose( idJointQuat *blendFrame ) const; + void ClearAFPose( void ); + + void ClearAllAnims( int currentTime, int cleartime ); + + jointHandle_t GetJointHandle( const char *name ) const; + const char * GetJointName( jointHandle_t handle ) const; + int GetChannelForJoint( jointHandle_t joint ) const; + bool GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis ); + bool GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + + const animFlags_t GetAnimFlags( int animnum ) const; + int NumFrames( int animnum ) const; + int NumSyncedAnims( int animnum ) const; + const char *AnimName( int animnum ) const; + const char *AnimFullName( int animnum ) const; +// RAVEN BEGIN +// rjohnson: more output for animators + const char *AnimMD5Name( int animnum, int index ) const; +// RAVEN END + int AnimLength( int animnum ) const; + const idVec3 &TotalMovementDelta( int animnum ) const; + +// RAVEN BEGIN +// nrausch: get the nearest joint to a segment - ignores joints behind the origin +// you can pass it a null jointList in order to test against all joints ( use NumJoints() for the count ) + jointHandle_t GetNearestJoint( const idVec3 &start, const idVec3 &end, int time, jointHandle_t *jointList, int cnt ); +//MCG + jointMod_t * FindExistingJointMod( jointHandle_t jointnum, int *index ); +// RAVEN END + +private: +// RAVEN BEGIN +// bdube: added methods + jointMod_t * FindJointMod ( jointHandle_t jointnum ); +// RAVEN END + + void FreeData( void ); + void PushAnims( int channel, int currentTime, int blendTime ); + +private: + const idDeclModelDef * modelDef; + idEntity * entity; + + idAnimBlend channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ]; + idList jointMods; + int numJoints; + idJointMat * joints; + + mutable int lastTransformTime; // mutable because the value is updated in CreateFrame + mutable bool stoppedAnimatingUpdate; + bool removeOriginOffset; + bool forceUpdate; + + idBounds frameBounds; + + float AFPoseBlendWeight; + idList AFPoseJoints; + idList AFPoseJointMods; + idJointQuat * AFPoseJointFrame; + int AFPoseJointFrameSize; + idBounds AFPoseBounds; + int AFPoseTime; + +// RAVEN BEGIN +// jshepard: multiplier for the animation rate for all anims under this animator + float rateMultiplier; +// RAVEN END +}; + +ID_INLINE void idAnimator::SetPlaybackRate ( float _rate ) { + rateMultiplier = _rate; +} + +/* +============================================================================================== + + idAnimManager + +============================================================================================== +*/ + +class idAnimManager { +public: + idAnimManager(); + ~idAnimManager(); + + static bool forceExport; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void BeginLevelLoad( void ); + void EndLevelLoad( void ); +#endif +// RAVEN END + void Shutdown( void ); + idMD5Anim * GetAnim( const char *name ); + void ReloadAnims( void ); + void ListAnims( void ) const; + void PrintMemInfo( MemInfo *mi ); + int JointIndex( const char *name ); + const char * JointName( int index ) const; + + void ClearAnimsInUse( void ); + void FlushUnusedAnims( void ); + +private: + idHashTable animations; + idStrList jointnames; + idHashIndex jointnamesHash; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool insideLevelLoad; +#endif +// RAVEN END +}; + +#endif /* !__ANIM_H__ */ + +// RAVEN END diff --git a/source/game/anim/Anim_Blend.cpp b/source/game/anim/Anim_Blend.cpp new file mode 100644 index 0000000..da961a0 --- /dev/null +++ b/source/game/anim/Anim_Blend.cpp @@ -0,0 +1,6037 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../game/Projectile.h" +#include "../ai/AI.h" + +static const char *channelNames[ ANIM_NumAnimChannels ] = { + "all", "torso", "legs", "head", "eyelids" +}; + +/*********************************************************************** + + idAnim + +***********************************************************************/ + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim() { + modelDef = NULL; +// RAVEN BEGIN +// bdube: added speed + rate = 1.0f; +// RAVEN END + numAnims = 0; + memset( anims, 0, sizeof( anims ) ); + memset( &flags, 0, sizeof( flags ) ); +} + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim( const idDeclModelDef *modelDef, const idAnim *anim ) { + int i; + + this->modelDef = modelDef; + numAnims = anim->numAnims; + name = anim->name; + realname = anim->realname; + flags = anim->flags; +// RAVEN BEGIN +// bdube: copy speed info + rate = anim->rate; +// RAVEN END + + memset( anims, 0, sizeof( anims ) ); + for( i = 0; i < numAnims; i++ ) { + anims[ i ] = anim->anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + frameLookup.SetNum( anim->frameLookup.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( frameLookup.Ptr(), anim->frameLookup.Ptr(), frameLookup.MemoryUsed() ); +// RAVEN END + + frameCommands.SetNum( anim->frameCommands.Num() ); + for( i = 0; i < frameCommands.Num(); i++ ) { + frameCommands[ i ] = anim->frameCommands[ i ]; + if ( anim->frameCommands[ i ].string ) { + frameCommands[ i ].string = new idStr( *anim->frameCommands[ i ].string ); + } +// RAVEN BEGIN +// bdube: copy joint information + if ( anim->frameCommands[ i ].joint ) { + frameCommands[ i ].joint = new idStr ( *anim->frameCommands[i].joint ); + } + if ( anim->frameCommands[ i ].joint2 ) { + frameCommands[ i ].joint2 = new idStr ( *anim->frameCommands[i].joint2 ); + } +// abahr: + if ( anim->frameCommands[ i ].parmList ) { + frameCommands[ i ].parmList = new idList( *anim->frameCommands[i].parmList ); + } +// RAVEN END + } +} + +/* +===================== +idAnim::~idAnim +===================== +*/ +idAnim::~idAnim() { + int i; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + } + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joint support + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } +} + +/* +===================== +idAnim::SetAnim +===================== +*/ +void idAnim::SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ) { + int i; + + this->modelDef = modelDef; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + anims[ i ] = NULL; + } + + assert( ( num > 0 ) && ( num <= ANIM_MaxSyncedAnims ) ); + numAnims = num; + realname = sourcename; + name = animname; + + for( i = 0; i < num; i++ ) { + anims[ i ] = md5anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + memset( &flags, 0, sizeof( flags ) ); + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joints as their own string + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } + + frameLookup.Clear(); + frameCommands.Clear(); +} + +/* +===================== +idAnim::Name +===================== +*/ +const char *idAnim::Name( void ) const { + return name; +} + +/* +===================== +idAnim::FullName +===================== +*/ +const char *idAnim::FullName( void ) const { + return realname; +} + +/* +===================== +idAnim::MD5Anim + +index 0 will never be NULL. Any anim >= NumAnims will return NULL. +===================== +*/ +const idMD5Anim *idAnim::MD5Anim( int num ) const { + if ( anims == NULL || anims[0] == NULL ) { + return NULL; + } + return anims[ num ]; +} + +/* +===================== +idAnim::ModelDef +===================== +*/ +const idDeclModelDef *idAnim::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnim::Length +===================== +*/ +int idAnim::Length( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->Length(); +} + +/* +===================== +idAnim::NumFrames +===================== +*/ +int idAnim::NumFrames( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->NumFrames(); +} + +/* +===================== +idAnim::NumAnims +===================== +*/ +int idAnim::NumAnims( void ) const { + return numAnims; +} + +/* +===================== +idAnim::TotalMovementDelta +===================== +*/ +const idVec3 &idAnim::TotalMovementDelta( void ) const { + if ( !anims[ 0 ] ) { + return vec3_zero; + } + + return anims[ 0 ]->TotalMovementDelta(); +} + +/* +===================== +idAnim::GetOrigin +===================== +*/ +bool idAnim::GetOrigin( idVec3 &offset, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + offset.Zero(); + return false; + } + + anims[ animNum ]->GetOrigin( offset, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetOriginRotation +===================== +*/ +bool idAnim::GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + return false; + } + + anims[ animNum ]->GetOriginRotation( rotation, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetBounds +===================== +*/ +ID_INLINE bool idAnim::GetBounds( idBounds &bounds, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + return false; + } + + anims[ animNum ]->GetBounds( bounds, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *idAnim::AddFrameCommand( const idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "enableBlinking" ) { + fc.type = FC_ENABLE_BLINKING; + } else if ( token == "disableBlinking" ) { + fc.type = FC_DISABLE_BLINKING; + } else if ( token == "enableAutoBlink" ) { + fc.type = FC_ENABLE_AUTOBLINK; + } else if ( token == "disableAutoBlink" ) { + fc.type = FC_DISABLE_AUTOBLINK; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "speak_random" ) { + fc.type = FC_AI_SPEAK_RANDOM; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +//MCG - added attachment frame commands + } else if ( token == "attachment_hide" ) { + fc.type = FC_ACT_ATTACH_HIDE; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "attachment_show" ) { + fc.type = FC_ACT_ATTACH_SHOW; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( anims[ 0 ]->NumFrames() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > anims[ 0 ]->NumFrames() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, anims[ 0 ]->Name(), anims[ 0 ]->NumFrames() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +// RAVEN BEGIN +// bdube: added for debugging +struct frameCommandInfo_t frameCommandInfo[FC_COUNT] = { + { "call", false }, + { "object_call", false }, + { "event", false }, + { "eventArgs", false }, + + { "sound", true }, + { "sound_voice", true }, + { "sound_voice2", true }, + { "sound_body", true }, + { "sound_body2", true }, + { "sound_body3", true }, + { "sound_weapon", true }, + { "sound_item", true }, + { "sound_global", true }, + { "sound_chatter", true }, + + { "skin", true }, + { "trigger", false }, + { "direct_damage", false }, + { "muzzle_flash", false }, + { "footstep", false }, + { "leftfoot", false }, + { "rightfoot", false }, + { "enableEyeFocus", false }, + { "disableEyeFocus", false }, + { "effect", true }, + { "disable_gravity", false }, + { "enable_gravity", false }, + { "jump", false }, + { "enableClip", false }, + { "disableClip", false }, + { "enableWalkIK", false }, + { "disableWalkIK", false }, + { "enableLegIK", false }, + { "disableLegID", false }, + { "recordDemo", false }, + { "aviGame", false }, + { "guievent", false }, + + { "ai_enablePain", false }, + { "ai_disablePain", false }, + { "ai_enableDamage", false }, + { "ai_disableDamage", false }, + { "ai_lockEnemyOrigin", false }, + { "ai_attack", false }, + { "ai_attack_melee", false }, + { "speak", true }, +}; +// RAVEN END + +// RAVEN BEGIN +// bdube: added frame command methods +/* +===================== +idAnim::CallFrameCommandSound +===================== +*/ +void idAnim::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} +// RAVEN END + +/* +===================== +idAnim::CallFrameCommands +===================== +*/ +void idAnim::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + numframes = anims[ 0 ]->NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "framecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } + + if ( ( gameLocal.editors & EDITOR_MODVIEW ) && !frameCommandInfo[command.type].modview ) { + continue; + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { + + if ( gameLocal.localClientNum == -1 ) { + // early ret on dedicated server + break; + } +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_zero, mat3_identity ); + } else { + cent = gameLocal.PlayEffect( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? + if ( cent && command.joint2 && ent->IsType( idAnimatedEntity::GetClassType() ) ) { + cent->SetEndOrigin( ent->GetAnimator()->GetJointHandle( *command.joint2 ) ); + } + + // Error print should the effect fail to play + if ( !cent ) { + idStr error = "Failed to play effect"; + + if( command.string ) { + error += va( " \'%s\'", command.string->c_str() ); + } + if ( command.effect ) { + error += va( " \'%s\'", command.effect->GetName() ); + } + if( command.joint ) { + error += va( " on bone \'%s\'", command.joint->c_str() ); + } + common->Warning( error.c_str() ); + } +// RAVEN END + break; + } + case FC_SKIN: + ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { +// RAVEN BEGIN +// nmckenzie: We're not using this. +// ent->ProcessEvent( &AI_MuzzleFlash, command.string->c_str() ); +// RAVEN END + break; + } + case FC_FOOTSTEP : { + ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { + ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { + ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_ENABLE_BLINKING: { + ent->ProcessEvent( &AI_EnableBlink ); + break; + } + case FC_DISABLE_BLINKING: { + ent->ProcessEvent( &AI_DisableBlink ); + break; + } + case FC_ENABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_EnableAutoBlink ); + break; + } + case FC_DISABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_DisableAutoBlink ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { + ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { + ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { + ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { + ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: + ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: + ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: + ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: + ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: + ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: + ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: + ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_AI_SPEAK_RANDOM: + ent->ProcessEvent( &AI_SpeakRandom, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } + break; + + case FC_ACT_ATTACH_SHOW: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } + break; + + case FC_AI_ATTACK_MELEE: + ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + +/* +===================== +idAnim::FindFrameForFrameCommand +===================== +*/ +int idAnim::FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const { + int frame; + int index; + int numframes; + int end; + + if ( !frameCommands.Num() ) { + return -1; + } + + numframes = anims[ 0 ]->NumFrames(); + for( frame = 0; frame < numframes; frame++ ) { + end = frameLookup[ frame ].firstCommand + frameLookup[ frame ].num; + for( index = frameLookup[ frame ].firstCommand; index < end; index++ ) { + if ( frameCommands[ index ].type == framecommand ) { + if ( command ) { + *command = &frameCommands[ index ]; + } + return frame; + } + } + } + + if ( command ) { + *command = NULL; + } + + return -1; +} + +/* +===================== +idAnim::HasFrameCommands +===================== +*/ +bool idAnim::HasFrameCommands( void ) const { + if ( !frameCommands.Num() ) { + return false; + } + return true; +} + +/* +===================== +idAnim::SetAnimFlags +===================== +*/ +void idAnim::SetAnimFlags( const animFlags_t &animflags ) { + flags = animflags; +} + +/* +===================== +idAnim::GetAnimFlags +===================== +*/ +const animFlags_t &idAnim::GetAnimFlags( void ) const { + return flags; +} + +/*********************************************************************** + + idAnimBlend + +***********************************************************************/ + +/* +===================== +idAnimBlend::idAnimBlend +===================== +*/ +idAnimBlend::idAnimBlend( void ) { + Reset( NULL ); +} + +/* +===================== +idAnimBlend::Save + +archives object for save game file +===================== +*/ +void idAnimBlend::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( starttime ); + savefile->WriteInt( endtime ); + savefile->WriteInt( timeOffset ); + savefile->WriteFloat( rate ); + + savefile->WriteInt( blendStartTime ); + savefile->WriteInt( blendDuration ); + savefile->WriteFloat( blendStartValue ); + savefile->WriteFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->WriteFloat( animWeights[ i ] ); + } + savefile->WriteShort( cycle ); + savefile->WriteShort( animNum ); + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowFrameCommands ); + savefile->WriteBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Restore + +unarchives object from save game file +===================== +*/ +void idAnimBlend::Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ) { + int i; + + this->modelDef = modelDef; + + savefile->ReadInt( starttime ); + savefile->ReadInt( endtime ); + savefile->ReadInt( timeOffset ); + savefile->ReadFloat( rate ); + + savefile->ReadInt( blendStartTime ); + savefile->ReadInt( blendDuration ); + savefile->ReadFloat( blendStartValue ); + savefile->ReadFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->ReadFloat( animWeights[ i ] ); + } + savefile->ReadShort( cycle ); + savefile->ReadShort( animNum ); + if ( !modelDef ) { + animNum = 0; + } else if ( ( animNum < 0 ) || ( animNum > modelDef->NumAnims() ) ) { + gameLocal.Warning( "Anim number %d out of range for model '%s' during save game", animNum, modelDef->GetModelName() ); + animNum = 0; + } + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowFrameCommands ); + savefile->ReadBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Reset +===================== +*/ +void idAnimBlend::Reset( const idDeclModelDef *_modelDef ) { + modelDef = _modelDef; + cycle = 1; + starttime = 0; + endtime = 0; + timeOffset = 0; + rate = 1.0f; + allowMove = true; + allowFrameCommands = true; + animNum = 0; + + memset( animWeights, 0, sizeof( animWeights ) ); + + blendStartValue = 0.0f; + blendEndValue = 0.0f; + blendStartTime = 0; + blendDuration = 0; + useFrameBlend = false; + + memset( &frameBlend, 0, sizeof( frameBlend ) ); +} + +/* +===================== +idAnimBlend::FullName +===================== +*/ +const char *idAnimBlend::AnimFullName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} + +/* +===================== +idAnimBlend::AnimName +===================== +*/ +const char *idAnimBlend::AnimName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->Name(); +} + +/* +===================== +idAnimBlend::NumFrames +===================== +*/ +int idAnimBlend::NumFrames( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumFrames(); +} + +/* +===================== +idAnimBlend::Length +===================== +*/ +int idAnimBlend::Length( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->Length(); +} + +/* +===================== +idAnimBlend::GetWeight +===================== +*/ +float idAnimBlend::GetWeight( int currentTime ) const { + int timeDelta; + float frac; + float w; + + timeDelta = currentTime - blendStartTime; + if ( timeDelta <= 0 ) { + w = blendStartValue; + } else if ( timeDelta >= blendDuration ) { + w = blendEndValue; + } else { + frac = ( float )timeDelta / ( float )blendDuration; + w = blendStartValue + ( blendEndValue - blendStartValue ) * frac; + } + + return w; +} + +/* +===================== +idAnimBlend::GetFinalWeight +===================== +*/ +float idAnimBlend::GetFinalWeight( void ) const { + return blendEndValue; +} + +/* +===================== +idAnimBlend::SetWeight +===================== +*/ +void idAnimBlend::SetWeight( float newweight, int currentTime, int blendTime ) { + blendStartValue = GetWeight( currentTime ); + blendEndValue = newweight; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + + if ( !newweight ) { + endtime = currentTime + blendTime; + } +} + +/* +===================== +idAnimBlend::NumSyncedAnims +===================== +*/ +int idAnimBlend::NumSyncedAnims( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumAnims(); +} + +/* +===================== +idAnimBlend::SetSyncedAnimWeight +===================== +*/ +bool idAnimBlend::SetSyncedAnimWeight( int num, float weight ) { + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + if ( ( num < 0 ) || ( num > anim->NumAnims() ) ) { + return false; + } + + animWeights[ num ] = weight; + return true; +} + +/* +===================== +idAnimBlend::SetFrame +===================== +*/ +void idAnimBlend::SetFrame( const idDeclModelDef *modelDef, int _animNum, const frameBlend_t & _frameBlend ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + frameBlend = _frameBlend; + animNum = _animNum; + useFrameBlend = true; + + // a frame of 0 means it's not a single frame blend, so we set it to frame + 1 + if ( frameBlend.frame1 < 0 ) { + frameBlend.frame1 = 0; + } else if ( frameBlend.frame1 >= _anim->NumFrames() ) { + frameBlend.frame1 = _anim->NumFrames() - 1; + } + + if ( frameBlend.frame2 < 0 ) { + frameBlend.frame2 = 0; + } else if ( frameBlend.frame2 >= _anim->NumFrames() ) { + frameBlend.frame2 = _anim->NumFrames() - 1; + } +} + +/* +===================== +idAnimBlend::CycleAnim +===================== +*/ +void idAnimBlend::CycleAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + animWeights[ 0 ] = 1.0f; + endtime = -1; + cycle = -1; + if ( _anim->GetAnimFlags().random_cycle_start ) { + // start the animation at a random time so that characters don't walk in sync + starttime = currentTime - gameLocal.random.RandomFloat() * _anim->Length(); + } else { + starttime = currentTime; + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } +// RAVEN END +} + +/* +===================== +idAnimBlend::PlayAnim +===================== +*/ +void idAnimBlend::PlayAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = starttime + _anim->Length(); + cycle = 1; + animWeights[ 0 ] = 1.0f; + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } + +// RAVEN END +} + +/* +===================== +idAnimBlend::Clear +===================== +*/ +void idAnimBlend::Clear( int currentTime, int clearTime ) { + if ( !clearTime ) { + Reset( modelDef ); + } else { + SetWeight( 0.0f, currentTime, clearTime ); + } +} + +/* +===================== +idAnimBlend::IsDone +===================== +*/ +bool idAnimBlend::IsDone( int currentTime ) const { + if ( !useFrameBlend && ( endtime > 0 ) && ( currentTime >= endtime ) ) { + return true; + } + + if ( ( blendEndValue <= 0.0f ) && ( currentTime >= ( blendStartTime + blendDuration ) ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimBlend::FrameHasChanged +===================== +*/ +bool idAnimBlend::FrameHasChanged( int currentTime ) const { + // if we don't have an anim, no change + if ( !animNum ) { + return false; + } + + // if anim is done playing, no change + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + // if our blend weight changes, we need to update + if ( ( currentTime < ( blendStartTime + blendDuration ) && ( blendStartValue != blendEndValue ) ) ) { + return true; + } + + // if we're a single frame anim and this isn't the frame we started on, we don't need to update + if ( ( useFrameBlend || ( NumFrames() == 1 ) ) && ( currentTime != starttime ) ) { + return false; + } + + return true; +} + +/* +===================== +idAnimBlend::GetCycleCount +===================== +*/ +int idAnimBlend::GetCycleCount( void ) const { + return cycle; +} + +/* +===================== +idAnimBlend::SetCycleCount +===================== +*/ +void idAnimBlend::SetCycleCount( int count ) { + const idAnim *anim = Anim(); + + if ( !anim ) { + cycle = -1; + endtime = 0; + } else { + cycle = count; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + if ( count < 0 ) { +// RAVEN END + cycle = -1; + endtime = -1; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + } else if ( count == 0 ) { +// RAVEN END + cycle = 1; + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length(); + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + anim->Length() / rate; + } else { + endtime = -1; + } + } else { + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length() * cycle; + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + ( anim->Length() * cycle ) / rate; + } else { + endtime = -1; + } + } + } +} + +/* +===================== +idAnimBlend::SetPlaybackRate +===================== +*/ +void idAnimBlend::SetPlaybackRate( int currentTime, float newRate ) { + int animTime; + + if ( rate == newRate ) { + return; + } + + animTime = AnimTime( currentTime ); + if ( newRate == 1.0f ) { + timeOffset = animTime - ( currentTime - starttime ); + } else { + timeOffset = animTime - ( currentTime - starttime ) * newRate; + } + + rate = newRate; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetPlaybackRate +===================== +*/ +float idAnimBlend::GetPlaybackRate( void ) const { + return rate; +} + +/* +===================== +idAnimBlend::SetStartTime +===================== +*/ +void idAnimBlend::SetStartTime( int _startTime ) { + starttime = _startTime; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetStartTime +===================== +*/ +int idAnimBlend::GetStartTime( void ) const { + if ( !animNum ) { + return 0; + } + + return starttime; +} + +/* +===================== +idAnimBlend::GetEndTime +===================== +*/ +int idAnimBlend::GetEndTime( void ) const { + if ( !animNum ) { + return 0; + } + + return endtime; +} + +/* +===================== +idAnimBlend::PlayLength +===================== +*/ +int idAnimBlend::PlayLength( void ) const { + if ( !animNum ) { + return 0; + } + + if ( endtime < 0 ) { + return -1; + } + + return endtime - starttime + timeOffset; +} + +/* +===================== +idAnimBlend::AllowMovement +===================== +*/ +void idAnimBlend::AllowMovement( bool allow ) { + allowMove = allow; +} + +/* +===================== +idAnimBlend::AllowFrameCommands +===================== +*/ +void idAnimBlend::AllowFrameCommands( bool allow ) { + allowFrameCommands = allow; +} + +/* +===================== +idAnimBlend::AnimNum +===================== +*/ +int idAnimBlend::AnimNum( void ) const { + return animNum; +} + +/* +===================== +idAnimBlend::AnimTime +===================== +*/ +int idAnimBlend::AnimTime( int currentTime ) const { + int time; + int length; + const idAnim *anim = Anim(); + + if ( anim ) { + if ( useFrameBlend ) { + return FRAME2MS( frameBlend.frame1 ); + } + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + time = currentTime - starttime + timeOffset; + } else { + time = static_cast( ( currentTime - starttime ) * rate ) + timeOffset; + } + + // given enough time, we can easily wrap time around in our frame calculations, so + // keep cycling animations' time within the length of the anim. + length = anim->Length(); + if ( ( cycle < 0 ) && ( length > 0 ) ) { + time %= length; + + // time will wrap after 24 days (oh no!), resulting in negative results for the %. + // adding the length gives us the proper result. + if ( time < 0 ) { + time += length; + } + } + return time; + } else { + return 0; + } +} + +/* +===================== +idAnimBlend::GetFrameNumber +===================== +*/ +int idAnimBlend::GetFrameNumber( int currentTime ) const { + const idMD5Anim *md5anim; + frameBlend_t frameinfo; + int animTime; + + const idAnim *anim = Anim(); + if ( !anim ) { + return 1; + } + + if ( useFrameBlend ) { + return frameBlend.frame1; + } + + md5anim = anim->MD5Anim( 0 ); + animTime = AnimTime( currentTime ); + md5anim->ConvertTimeToFrame( animTime, cycle, frameinfo ); + + return frameinfo.frame1 + 1; +} + +/* +===================== +idAnimBlend::CallFrameCommands +===================== +*/ +void idAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + const idMD5Anim *md5anim; + frameBlend_t frame1; + frameBlend_t frame2; + int fromFrameTime; + int toFrameTime; + + if ( !allowFrameCommands || !ent || useFrameBlend || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->HasFrameCommands() ) { + return; + } + + if ( totime <= starttime ) { + // don't play until next frame or we'll play commands twice. + // this happens on the player sometimes. + return; + } + + fromFrameTime = AnimTime( fromtime ); + toFrameTime = AnimTime( totime ); + if ( toFrameTime < fromFrameTime ) { + toFrameTime += anim->Length(); + } + + md5anim = anim->MD5Anim( 0 ); + md5anim->ConvertTimeToFrame( fromFrameTime, cycle, frame1 ); + md5anim->ConvertTimeToFrame( toFrameTime, cycle, frame2 ); + + if ( fromFrameTime <= 0 ) { + // make sure first frame is called + anim->CallFrameCommands( ent, -1, frame2.frame1 ); + } else { + anim->CallFrameCommands( ent, frame1.frame1, frame2.frame1 ); + } +} + +/* +===================== +idAnimBlend::BlendAnim +===================== +*/ +bool idAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + int i; + float lerp; + float mixWeight; + const idMD5Anim *md5anim; + idJointQuat *ptr; + frameBlend_t frametime = {0}; + idJointQuat *jointFrame; + idJointQuat *mixFrame; + int numAnims; + int time; + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( blendWeight > 0.0f ) { + if ( ( endtime >= 0 ) && ( currentTime >= endtime ) ) { + return false; + } + if ( !weight ) { + return false; + } + if ( overrideBlend ) { + blendWeight = 1.0f - weight; + } + } + + if ( ( channel == ANIMCHANNEL_ALL ) && !blendWeight ) { + // we don't need a temporary buffer, so just store it directly in the blend frame + jointFrame = blendFrame; + } else { + // allocate a temporary buffer to copy the joints from + jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + } + + time = AnimTime( currentTime ); + + numAnims = anim->NumAnims(); + if ( numAnims == 1 ) { + md5anim = anim->MD5Anim( 0 ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->ConvertTimeToFrame( time, cycle, frametime ); + md5anim->GetInterpolatedFrame( frametime, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + } else { + // + // need to mix the multipoint anim together first + // + // allocate a temporary buffer to copy the joints to + mixFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + + if ( !useFrameBlend ) { + anim->MD5Anim( 0 )->ConvertTimeToFrame( time, cycle, frametime ); + } + + ptr = jointFrame; + mixWeight = 0.0f; + for( i = 0; i < numAnims; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + lerp = animWeights[ i ] / mixWeight; + md5anim = anim->MD5Anim( i ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->GetInterpolatedFrame( frametime, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + // only blend after the first anim is mixed in + if ( ptr != jointFrame ) { + SIMDProcessor->BlendJoints( jointFrame, ptr, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + ptr = mixFrame; + } + } + + if ( !mixWeight ) { + return false; + } + } + + if ( removeOriginOffset ) { + if ( allowMove ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + if ( anim->GetAnimFlags().anim_turn ) { + jointFrame[ 0 ].q.Set( -0.70710677f, 0.0f, 0.0f, 0.70710677f ); + } + } + + if ( !blendWeight ) { + blendWeight = weight; + if ( channel != ANIMCHANNEL_ALL ) { + const int *index = modelDef->GetChannelJoints( channel ); + const int num = modelDef->NumJointsOnChannel( channel ); + for( i = 0; i < num; i++ ) { + int j = index[i]; + blendFrame[j].t = jointFrame[j].t; + blendFrame[j].q = jointFrame[j].q; + } + } + } else { + blendWeight += weight; + lerp = weight / blendWeight; + SIMDProcessor->BlendJoints( blendFrame, jointFrame, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + if ( printInfo ) { + if ( useFrameBlend ) { + gameLocal.Printf( " %s: '%s', %d, %.2f%%\n", channelNames[ channel ], anim->FullName(), frameBlend.frame1, weight * 100.0f ); + } else { + gameLocal.Printf( " %s: '%s', %.3f, %.2f%%\n", channelNames[ channel ], anim->FullName(), ( float )frametime.frame1 + frametime.backlerp, weight * 100.0f ); + } + } + + return true; +} + +/* +===================== +idAnimBlend::BlendOrigin +===================== +*/ +void idAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + float lerp; + idVec3 animpos; + idVec3 pos; + int time; + int num; + int i; + + if ( useFrameBlend || ( ( endtime > 0 ) && ( currentTime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + if ( allowMove && removeOriginOffset ) { + return; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return; + } + + time = AnimTime( currentTime ); + + pos.Zero(); + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time, cycle ); + pos += animpos * animWeights[ i ]; + } + + if ( !blendWeight ) { + blendPos = pos; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendPos += lerp * ( pos - blendPos ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDelta +===================== +*/ +void idAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + idVec3 pos1; + idVec3 pos2; + idVec3 animpos; + idVec3 delta; + int time1; + int time2; + float lerp; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + num = anim->NumAnims(); + + pos1.Zero(); + pos2.Zero(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time1, cycle ); + pos1 += animpos * animWeights[ i ]; + + anim->GetOrigin( animpos, i, time2, cycle ); + pos2 += animpos * animWeights[ i ]; + } + + delta = pos2 - pos1; + if ( !blendWeight ) { + blendDelta = delta; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta += lerp * ( delta - blendDelta ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDeltaRotation +===================== +*/ +void idAnimBlend::BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const { + idQuat q1; + idQuat q2; + idQuat q3; + int time1; + int time2; + float lerp; + float mixWeight; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->GetAnimFlags().anim_turn ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + q1.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + q2.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + + mixWeight = 0.0f; + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + if ( animWeights[ i ] == mixWeight ) { + anim->GetOriginRotation( q1, i, time1, cycle ); + anim->GetOriginRotation( q2, i, time2, cycle ); + } else { + lerp = animWeights[ i ] / mixWeight; + anim->GetOriginRotation( q3, i, time1, cycle ); + q1.Slerp( q1, q3, lerp ); + + anim->GetOriginRotation( q3, i, time2, cycle ); + q2.Slerp( q1, q3, lerp ); + } + } + } + + q3 = q1.Inverse() * q2; + if ( !blendWeight ) { + blendDelta = q3; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta.Slerp( blendDelta, q3, lerp ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::AddBounds +===================== +*/ +bool idAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + int i; + int num; + idBounds b; + int time; + idVec3 pos; + bool addorigin; + + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return false; + } + + time = AnimTime( currentTime ); + num = anim->NumAnims(); + + addorigin = !allowMove || !removeOriginOffset; + for( i = 0; i < num; i++ ) { + if ( anim->GetBounds( b, i, time, cycle ) ) { + if ( addorigin ) { + anim->GetOrigin( pos, i, time, cycle ); + b.TranslateSelf( pos ); + } + bounds.AddBounds( b ); + } + } + + return true; +} + +/*********************************************************************** + + idDeclModelDef + +***********************************************************************/ + +/* +===================== +idDeclModelDef::idDeclModelDef +===================== +*/ +idDeclModelDef::idDeclModelDef() { + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels=0; +#endif +// RAVEN END +} + +/* +===================== +idDeclModelDef::~idDeclModelDef +===================== +*/ +idDeclModelDef::~idDeclModelDef() { + FreeData(); +} + +/* +================= +idDeclModelDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclModelDef::Size( void ) const { + + int i; + size_t size; + + size = sizeof( idDeclModelDef ); + size += joints.Allocated(); + size += jointParents.Allocated(); + size += anims.Allocated(); + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + + size += channelJoints[i].Allocated(); + } + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclModelDef::CopyDecl +===================== +*/ +void idDeclModelDef::CopyDecl( const idDeclModelDef *decl ) { + int i; + + FreeData(); + + offset = decl->offset; + modelHandle = decl->modelHandle; + skin = decl->skin; + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new idAnim( this, decl->anims[ i ] ); + } + + joints.SetNum( decl->joints.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( joints.Ptr(), decl->joints.Ptr(), decl->joints.Num() * sizeof( joints[0] ) ); +// RAVEN END + jointParents.SetNum( decl->jointParents.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( jointParents.Ptr(), decl->jointParents.Ptr(), decl->jointParents.Num() * sizeof( jointParents[0] ) ); +// RAVEN END + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i] = decl->channelJoints[i]; + } +} + +/* +===================== +idDeclModelDef::FreeData +===================== +*/ +void idDeclModelDef::FreeData( void ) { + anims.DeleteContents( true ); + joints.Clear(); + jointParents.Clear(); + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +================ +idDeclModelDef::DefaultDefinition +================ +*/ +const char *idDeclModelDef::DefaultDefinition( void ) const { + return "{ }"; +} + +/* +==================== +idDeclModelDef::FindJoint +==================== +*/ +const jointInfo_t *idDeclModelDef::FindJoint( const char *name ) const { + int i; + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + joint = modelHandle->GetJoints(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( !joint->name.Icmp( name ) ) { + return &joints[ i ]; + } + } + + return NULL; +} + +/* +===================== +idDeclModelDef::ModelHandle +===================== +*/ +idRenderModel *idDeclModelDef::ModelHandle( void ) const { + return ( idRenderModel * )modelHandle; +} + +/* +===================== +idDeclModelDef::GetJointList +===================== +*/ +void idDeclModelDef::GetJointList( const char *jointnames, idList &jointList ) const { + const char *pos; + idStr jointname; + const jointInfo_t *joint; + const jointInfo_t *child; + int i; + int num; + bool getChildren; + bool subtract; + + if ( !modelHandle ) { + return; + } + + jointList.Clear(); + + num = modelHandle->NumJoints(); + + // scan through list of joints and add each to the joint list + pos = jointnames; + while( *pos ) { + // skip over whitespace + while( ( *pos != 0 ) && isspace( *pos ) ) { + pos++; + } + + if ( !*pos ) { + // no more names + break; + } + + // copy joint name + jointname = ""; + + if ( *pos == '-' ) { + subtract = true; + pos++; + } else { + subtract = false; + } + + if ( *pos == '*' ) { + getChildren = true; + pos++; + } else { + getChildren = false; + } + + while( ( *pos != 0 ) && !isspace( *pos ) ) { + jointname += *pos; + pos++; + } + + joint = FindJoint( jointname ); + if ( !joint ) { + gameLocal.Warning( "Unknown joint '%s' in '%s' for model '%s'", jointname.c_str(), jointnames, GetName() ); + continue; + } + + if ( !subtract ) { + jointList.AddUnique( joint->num ); + } else { + jointList.Remove( joint->num ); + } + + if ( getChildren ) { + // include all joint's children + child = joint + 1; + for( i = joint->num + 1; i < num; i++, child++ ) { + // all children of the joint should follow it in the list. + // once we reach a joint without a parent or with a parent + // who is earlier in the list than the specified joint, then + // we've gone through all it's children. + if ( child->parentNum < joint->num ) { + break; + } + + if ( !subtract ) { + jointList.AddUnique( child->num ); + } else { + jointList.Remove( child->num ); + } + } + } + } +} + +/* +===================== +idDeclModelDef::Touch +===================== +*/ +void idDeclModelDef::Touch( void ) const { + if ( modelHandle ) { + renderModelManager->FindModel( modelHandle->Name() ); + } +} + +/* +===================== +idDeclModelDef::GetDefaultSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetDefaultSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetDefaultPose +===================== +*/ +const idJointQuat *idDeclModelDef::GetDefaultPose( void ) const { + return modelHandle->GetDefaultPose(); +} + +/* +===================== +idDeclModelDef::SetupJoints +===================== +*/ +void idDeclModelDef::SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const { + int num; + const idJointQuat *pose; + idJointMat *list; + + if ( !modelHandle || modelHandle->IsDefaultModel() ) { + Mem_Free16( (*jointList) ); + (*jointList) = NULL; + frameBounds.Clear(); + return; + } + + // get the number of joints + num = modelHandle->NumJoints(); + + if ( !num ) { + gameLocal.Error( "model '%s' has no joints", modelHandle->Name() ); + } + + // set up initial pose for model (with no pose, model is just a jumbled mess) +//RAVEN BEGIN +//amccarthy: Added allocation tag + list = (idJointMat *) Mem_Alloc16( num * sizeof( list[0] ), MA_ANIM ); +//RAVEN END + pose = GetDefaultPose(); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( list, pose, joints.Num() ); + + // check if we offset the model by the origin joint + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + list[ 0 ].SetTranslation( idVec3( offset.x, offset.y + pose[0].t.y, offset.z + pose[0].t.z ) ); +#else + list[ 0 ].SetTranslation( offset ); +#endif + } else { + list[ 0 ].SetTranslation( pose[0].t + offset ); + } + + // transform the joint hierarchy + SIMDProcessor->TransformJoints( list, jointParents.Ptr(), 1, joints.Num() - 1 ); + + *numJoints = num; + *jointList = list; + + // get the bounds of the default pose + frameBounds = modelHandle->Bounds( NULL ); +} + +/* +===================== +idDeclModelDef::ParseAnim +===================== +*/ +bool idDeclModelDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + idAnim *anim; + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + const idMD5Anim *md5anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + animFlags_t flags; + + numAnims = 0; + memset( md5anims, 0, sizeof( md5anims ) ); + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new idAnim(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + do { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + // lookup the animation +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( token ); +// RAVEN END + if ( !md5anim ) { + src.Warning( "Couldn't load anim '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + md5anim->CheckModelHierarchy( modelHandle ); + + if ( numAnims > 0 ) { + // make sure it's the same length as the other anims + if ( md5anim->Length() != md5anims[ 0 ]->Length() ) { + src.Warning( "Anim '%s' does not match length of anim '%s'", md5anim->Name(), md5anims[ 0 ]->Name() ); + MakeDefault(); + return false; + } + } + + if ( numAnims >= ANIM_MaxSyncedAnims ) { + src.Warning( "Exceeded max synced anims (%d)", ANIM_MaxSyncedAnims ); + MakeDefault(); + return false; + } + + // add it to our list + md5anims[ numAnims ] = md5anim; + numAnims++; + } while ( src.CheckTokenString( "," ) ); + + if ( !numAnims ) { + src.Warning( "No animation specified" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, numAnims, md5anims ); + memset( &flags, 0, sizeof( flags ) ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + }else if ( token == "prevent_idle_override" ) { + flags.prevent_idle_override = true; + } else if ( token == "random_cycle_start" ) { + flags.random_cycle_start = true; +// RAVEN BEGIN +// bdube: added speed + } else if ( token == "sync_cycle" ) { + flags.sync_cycle = true; + } else if ( token == "rate" ) { + anim->SetPlaybackRate ( src.ParseFloat ( ) ); + } else if ( token == "ai_no_look" ) { + flags.ai_no_look = true; + } else if ( token == "ai_look_head_only" ) { + flags.ai_look_head_only = true; +// RAVEN END + } else if ( token == "ai_no_turn" ) { + flags.ai_no_turn = true; + } else if ( token == "anim_turn" ) { + flags.anim_turn = true; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } + +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + // set the flags + anim->SetAnimFlags( flags ); + return true; +} + +/* +================ +idDeclModelDef::Parse +================ +*/ +bool idDeclModelDef::Parse( const char *text, const int textLength, bool noCaching ) { + int i; + int num; + idStr filename; + idStr extension; + const idMD5Joint *md5joint; + const idMD5Joint *md5joints; + idLexer src; + idToken token; + idToken token2; + idStr jointnames; + int channel; + jointHandle_t jointnum; + idList jointList; + int numDefaultAnims; +// RAVEN BEGIN +// bdube: attachments + idList attachJoints; +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( token == "inherit" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + const idDeclModelDef *copy = static_cast( declManager->FindType( DECL_MODELDEF, token2, false ) ); + if ( !copy ) { + gameLocal.Warning( "Unknown model definition '%s'", token2.c_str() ); + } else if ( copy->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "inherited model definition '%s' defaulted", token2.c_str() ); + MakeDefault(); + return false; + } else { + CopyDecl( copy ); + numDefaultAnims = anims.Num(); + } + } else if ( token == "skin" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + skin = declManager->FindSkin( token2 ); + if ( !skin ) { + src.Warning( "Skin '%s' not found", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "mesh" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + filename = token2; + filename.ExtractFileExtension( extension ); + if ( extension != MD5_MESH_EXT ) { + src.Warning( "Invalid model for MD5 mesh" ); + MakeDefault(); + return false; + } + modelHandle = renderModelManager->FindModel( filename ); + if ( !modelHandle ) { + src.Warning( "Model '%s' not found", filename.c_str() ); + MakeDefault(); + return false; + } + + if ( modelHandle->IsDefaultModel() ) { + src.Warning( "Model '%s' defaulted", filename.c_str() ); + MakeDefault(); + return false; + } + + // get the number of joints + num = modelHandle->NumJoints(); + if ( !num ) { + src.Warning( "Model '%s' has no joints", filename.c_str() ); + } + + // set up the joint hierarchy + joints.SetGranularity( 1 ); + joints.SetNum( num ); + jointParents.SetNum( num ); + channelJoints[0].SetNum( num ); + md5joints = modelHandle->GetJoints(); + md5joint = md5joints; + for( i = 0; i < num; i++, md5joint++ ) { + joints[i].channel = ANIMCHANNEL_ALL; + joints[i].num = static_cast( i ); + if ( md5joint->parent ) { + joints[i].parentNum = static_cast( md5joint->parent - md5joints ); + } else { + joints[i].parentNum = INVALID_JOINT; + } + jointParents[i] = joints[i].parentNum; + channelJoints[0][i] = i; + } + } else if ( token == "remove" ) { + // removes any anims whos name matches + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + num = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( ( token2 == anims[ i ]->Name() ) || ( token2 == anims[ i ]->FullName() ) ) { + delete anims[ i ]; + anims.RemoveIndex( i ); + if ( i >= numDefaultAnims ) { + src.Warning( "Anim '%s' was not inherited. Anim should be removed from the model def.", token2.c_str() ); + MakeDefault(); + return false; + } + i--; + numDefaultAnims--; + num++; + continue; + } + } + if ( !num ) { + src.Warning( "Couldn't find anim '%s' to remove", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "anim" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining anims" ); + MakeDefault(); + return false; + } + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else if ( token == "offset" ) { + if ( !src.Parse1DMatrix( 3, offset.ToFloatPtr() ) ) { + src.Warning( "Expected vector following 'offset'" ); + MakeDefault(); + return false; + } + } else if ( token == "channel" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining channels" ); + MakeDefault(); + return false; + } + + // set the channel for a group of joints + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( !src.CheckTokenString( "(" ) ) { + src.Warning( "Expected { after '%s'\n", token2.c_str() ); + MakeDefault(); + return false; + } + + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !stricmp( channelNames[ i ], token2 ) ) { + break; + } + } + + if ( i >= ANIM_NumAnimChannels ) { + src.Warning( "Unknown channel '%s'", token2.c_str() ); + MakeDefault(); + return false; + } + + channel = i; + jointnames = ""; + + while( !src.CheckTokenString( ")" ) ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + jointnames += token2; + if ( ( token2 != "*" ) && ( token2 != "-" ) ) { + jointnames += " "; + } + } + + GetJointList( jointnames, jointList ); + + channelJoints[ channel ].SetNum( jointList.Num() ); + for( num = i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + if ( joints[ jointnum ].channel != ANIMCHANNEL_ALL ) { + src.Warning( "Joint '%s' assigned to multiple channels", modelHandle->GetJointName( jointnum ) ); + continue; + } + joints[ jointnum ].channel = channel; + channelJoints[ channel ][ num++ ] = jointnum; + } + channelJoints[ channel ].SetNum( num ); +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels++; +#endif +// RAVEN END + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclModelDef::Validate +===================== +*/ +bool idDeclModelDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclModelDef *pSelf = (idDeclModelDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclModelDef::HasAnim +===================== +*/ +bool idDeclModelDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclModelDef::NumAnims +===================== +*/ +int idDeclModelDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclModelDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclModelDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +int idDeclModelDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + +/* +===================== +idDeclModelDef::GetSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetModelName +===================== +*/ +const char *idDeclModelDef::GetModelName( void ) const { + if ( modelHandle ) { + return modelHandle->Name(); + } else { + return ""; + } +} + +/* +===================== +idDeclModelDef::Joints +===================== +*/ +const idList &idDeclModelDef::Joints( void ) const { + return joints; +} + +/* +===================== +idDeclModelDef::JointParents +===================== +*/ +const int * idDeclModelDef::JointParents( void ) const { + return jointParents.Ptr(); +} + +/* +===================== +idDeclModelDef::NumJoints +===================== +*/ +int idDeclModelDef::NumJoints( void ) const { + return joints.Num(); +} + +/* +===================== +idDeclModelDef::GetJoint +===================== +*/ +const jointInfo_t *idDeclModelDef::GetJoint( int jointHandle ) const { + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJoint : joint handle out of range" ); + } + return &joints[ jointHandle ]; +} + +/* +==================== +idDeclModelDef::GetJointName +==================== +*/ +const char *idDeclModelDef::GetJointName( int jointHandle ) const { + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJointName : joint handle out of range" ); + } + + joint = modelHandle->GetJoints(); + return joint[ jointHandle ].name.c_str(); +} + +/* +===================== +idDeclModelDef::NumJointsOnChannel +===================== +*/ +int idDeclModelDef::NumJointsOnChannel( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::NumJointsOnChannel : channel out of range" ); + } + return channelJoints[ channel ].Num(); +} + +/* +===================== +idDeclModelDef::GetChannelJoints +===================== +*/ +const int * idDeclModelDef::GetChannelJoints( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::GetChannelJoints : channel out of range" ); + } + return channelJoints[ channel ].Ptr(); +} + +/* +===================== +idDeclModelDef::GetVisualOffset +===================== +*/ +const idVec3 &idDeclModelDef::GetVisualOffset( void ) const { + return offset; +} + +// RAVEN BEGIN +// jsinger: allow exporting of this decl type in a preparsed form +#ifdef RV_BINARYDECLS +void idDeclModelDef::Write( SerialOutputStream &stream ) const +{ + WriteValue(offset[0], stream); + WriteValue(offset[1], stream); + WriteValue(offset[2], stream); + WriteValue(joints.Num(), stream); + for(int i=0; iName()), stream); + WriteValue(anims.Num(), stream); + for(int i=0; iWrite(stream); + } + + if(skin) + { + WriteValue(idStr(skin->GetName()), stream); + } + else + { + WriteValue(idStr(""), stream); + } +} + +void idDeclModelDef::AddReferences() const +{ +} + +idDeclModelDef::idDeclModelDef( SerialInputStream &stream ) +{ + offset[0] = stream.ReadFloatValue(); + offset[1] = stream.ReadFloatValue(); + offset[2] = stream.ReadFloatValue(); + int numJoints = stream.ReadIntValue(); + joints.AssureSize(numJoints); + for(int i=0; iFindModel( modelName.c_str() ); + } + int numAnims = stream.ReadIntValue(); + anims.AssureSize(numAnims); + for(int i=0; iFindSkin(skinName.c_str()); + } + else + { + skin = NULL; + } +} +#endif +// RAVEN END +/*********************************************************************** + + idAnimator + +***********************************************************************/ + +/* +===================== +idAnimator::idAnimator +===================== +*/ +idAnimator::idAnimator() { + int i, j; + + modelDef = NULL; + entity = NULL; + numJoints = 0; + joints = NULL; + lastTransformTime = -1; + stoppedAnimatingUpdate = false; + removeOriginOffset = false; + forceUpdate = false; + +// RAVEN BEGIN +// jshepard: + rateMultiplier = 1; +// RAVEN END + + frameBounds.Clear(); + + AFPoseJoints.SetGranularity( 1 ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ClearAFPose(); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } +} + +/* +===================== +idAnimator::~idAnimator +===================== +*/ +idAnimator::~idAnimator() { + FreeData(); +} + +/* +===================== +idAnimator::Allocated +===================== +*/ +size_t idAnimator::Allocated( void ) const { + size_t size; + + size = jointMods.Allocated() + numJoints * sizeof( joints[0] ) + jointMods.Num() * sizeof( jointMods[ 0 ] ) + AFPoseJointMods.Allocated() + AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) + AFPoseJoints.Allocated(); + + return size; +} + +/* +===================== +idAnimator::Save + +archives object for save game file +===================== +*/ +void idAnimator::Save( idSaveGame *savefile ) const { + int i; + int j; + + savefile->WriteModelDef( modelDef ); + savefile->WriteObject( entity ); + + savefile->WriteInt( jointMods.Num() ); + for( i = 0; i < jointMods.Num(); i++ ) { + savefile->Write( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->WriteInt( numJoints ); + savefile->Write( joints, numJoints * sizeof( joints[0] ) ); + + savefile->WriteInt( lastTransformTime ); + savefile->WriteBool( stoppedAnimatingUpdate ); + savefile->WriteBool( forceUpdate ); + savefile->WriteBounds( frameBounds ); + + savefile->WriteFloat( AFPoseBlendWeight ); + + savefile->WriteInt( AFPoseJoints.Num() ); + savefile->Write( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointMods.Num() ); + savefile->Write( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointFrameSize ); + savefile->Write( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->WriteBounds( AFPoseBounds ); + savefile->WriteInt( AFPoseTime ); + + savefile->WriteBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Save( savefile ); + } + } +} + +/* +===================== +idAnimator::Restore + +unarchives object from save game file +===================== +*/ +void idAnimator::Restore( idRestoreGame *savefile ) { + int i; + int j; + int num; + + savefile->ReadModelDef( modelDef ); + savefile->ReadObject( reinterpret_cast( entity ) ); + + savefile->ReadInt( num ); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + savefile->Read( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->ReadInt( numJoints ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + joints = (idJointMat *) Mem_Alloc16( numJoints * sizeof( joints[0] ), MA_ANIM ); +//RAVEN END + savefile->Read( joints, numJoints * sizeof( joints[0] ) ); + + savefile->ReadInt( lastTransformTime ); + savefile->ReadBool( stoppedAnimatingUpdate ); + savefile->ReadBool( forceUpdate ); + savefile->ReadBounds( frameBounds ); + + savefile->ReadFloat( AFPoseBlendWeight ); + + savefile->ReadInt( num ); + AFPoseJoints.SetGranularity( 1 ); + AFPoseJoints.SetNum( num ); + savefile->Read( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->ReadInt( num ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointMods.SetNum( num ); + savefile->Read( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->ReadInt( AFPoseJointFrameSize ); + AFPoseJointFrame = (idJointQuat *)Mem_Alloc16( AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + savefile->Read( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->ReadBounds( AFPoseBounds ); + savefile->ReadInt( AFPoseTime ); + + savefile->ReadBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Restore( savefile, modelDef ); + } + } +} + +/* +===================== +idAnimator::FreeData +===================== +*/ +void idAnimator::FreeData( void ) { + int i, j; + + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + } + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } + + jointMods.DeleteContents( true ); + + Mem_Free16( joints ); + joints = NULL; + numJoints = 0; + + modelDef = NULL; + + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ForceUpdate(); +} + +/* +===================== +idAnimator::PushAnims +===================== +*/ +void idAnimator::PushAnims( int channelNum, int currentTime, int blendTime ) { + int i; + idAnimBlend *channel; + + channel = channels[ channelNum ]; + if ( !channel[ 0 ].GetWeight( currentTime ) || ( channel[ 0 ].starttime == currentTime ) ) { + return; + } + + for( i = ANIM_MaxAnimsPerChannel - 1; i > 0; i-- ) { + channel[ i ] = channel[ i - 1 ]; + } + + channel[ 0 ].Reset( modelDef ); + channel[ 1 ].Clear( currentTime, blendTime ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetModel +===================== +*/ +idRenderModel *idAnimator::SetModel( const char *modelname ) { + int i, j; + + FreeData(); + + // check if we're just clearing the model + if ( !modelname || !*modelname ) { + return NULL; + } + + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( !modelDef ) { + return NULL; + } + + idRenderModel *renderModel = modelDef->ModelHandle(); + if ( !renderModel ) { + modelDef = NULL; + return NULL; + } + + // make sure model hasn't been purged + modelDef->Touch(); + +// RAVEN BEGIN +// bdube: make sure models dont get purged + if ( modelDef->ModelHandle() ) { + modelDef->ModelHandle()->SetLevelLoadReferenced ( true ); + } +// RAVEN END + + modelDef->SetupJoints( &numJoints, &joints, frameBounds, removeOriginOffset ); + modelDef->ModelHandle()->Reset(); + + // set the modelDef on all channels + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( modelDef ); + } + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::Size +===================== +*/ +size_t idAnimator::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +===================== +idAnimator::SetEntity +===================== +*/ +void idAnimator::SetEntity( idEntity *ent ) { + entity = ent; +} + +/* +===================== +idAnimator::GetEntity +===================== +*/ +idEntity *idAnimator::GetEntity( void ) const { + return entity; +} + +/* +===================== +idAnimator::RemoveOriginOffset +===================== +*/ +void idAnimator::RemoveOriginOffset( bool remove ) { + removeOriginOffset = remove; +} + +/* +===================== +idAnimator::RemoveOrigin +===================== +*/ +bool idAnimator::RemoveOrigin( void ) const { + return removeOriginOffset; +} + +/* +===================== +idAnimator::GetJointList +===================== +*/ +void idAnimator::GetJointList( const char *jointnames, idList &jointList ) const { + if ( modelDef ) { + modelDef->GetJointList( jointnames, jointList ); + } +} + +/* +===================== +idAnimator::NumAnims +===================== +*/ +int idAnimator::NumAnims( void ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->NumAnims(); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +const idAnim *idAnimator::GetAnim( int index ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->GetAnim( index ); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +int idAnimator::GetAnim( const char *name ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->GetAnim( name ); +} + +/* +===================== +idAnimator::HasAnim +===================== +*/ +bool idAnimator::HasAnim( const char *name ) const { + if ( !modelDef ) { + return false; + } + + return modelDef->HasAnim( name ); +} + +/* +===================== +idAnimator::NumJoints +===================== +*/ +int idAnimator::NumJoints( void ) const { + return numJoints; +} + +/* +===================== +idAnimator::ModelHandle +===================== +*/ +idRenderModel *idAnimator::ModelHandle( void ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::ModelDef +===================== +*/ +const idDeclModelDef *idAnimator::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnimator::CurrentAnim +===================== +*/ +idAnimBlend *idAnimator::CurrentAnim( int channelNum ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CurrentAnim : channel out of range" ); + } + + return &channels[ channelNum ][ 0 ]; +} + +/* +===================== +idAnimator::Clear +===================== +*/ +void idAnimator::Clear( int channelNum, int currentTime, int cleartime ) { + int i; + idAnimBlend *blend; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::Clear : channel out of range" ); + } + + blend = channels[ channelNum ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->Clear( currentTime, cleartime ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetFrame +===================== +*/ +void idAnimator::SetFrame( int channelNum, int animNum, const frameBlend_t & frameBlend ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SetFrame : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, gameLocal.time, 0 ); + channels[ channelNum ][ 0 ].SetFrame( modelDef, animNum, frameBlend ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::CycleAnim +===================== +*/ +void idAnimator::CycleAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CycleAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].CycleAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + + // If the old animation and new animation both sync cycles then do so now + idAnimBlend* oldChannel = &channels[channelNum][1]; + idAnimBlend* newChannel = &channels[channelNum][0]; + if ( oldChannel->AnimNum ( ) && newChannel->AnimNum ( ) ) { + if ( oldChannel->Anim ( )->GetAnimFlags ( ).sync_cycle && newChannel->Anim ( )->GetAnimFlags ( ).sync_cycle ) { + // Calculate the percentage through the animation the old channel was + float f = (float)oldChannel->GetFrameNumber ( currentTime ) / (float)oldChannel->NumFrames ( ); + // Move the new channel back in time to start it mid cycle + newChannel->SetStartTime ( gameLocal.time - f * (newChannel->Length ( ) / newChannel->GetPlaybackRate()) ); + } + } +} + +/* +===================== +idAnimator::PlayAnim +===================== +*/ +void idAnimator::PlayAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::PlayAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].PlayAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SyncAnimChannels +===================== +*/ +void idAnimator::SyncAnimChannels( int channelNum, int fromChannelNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) || ( fromChannelNum < 0 ) || ( fromChannelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SyncToChannel : channel out of range" ); + } + + idAnimBlend &fromBlend = channels[ fromChannelNum ][ 0 ]; + idAnimBlend &toBlend = channels[ channelNum ][ 0 ]; + + float weight = fromBlend.blendEndValue; + if ( ( fromBlend.Anim() != toBlend.Anim() ) || ( fromBlend.GetStartTime() != toBlend.GetStartTime() ) || ( fromBlend.GetEndTime() != toBlend.GetEndTime() ) ) { + PushAnims( channelNum, currentTime, blendTime ); + toBlend = fromBlend; + toBlend.blendStartValue = 0.0f; + toBlend.blendEndValue = 0.0f; + } + toBlend.SetWeight( weight, currentTime - 1, blendTime ); + + // disable framecommands on the current channel so that commands aren't called twice + toBlend.AllowFrameCommands( false ); + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +// RAVEN BEGIN +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindExistingJointMod( jointHandle_t jointnum, int *index ) { + jointMod_t* jointMod; + int i; + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( index ) + { + *index = i; + } + return jointMod; +} + +// bdube: added methods +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindJointMod ( jointHandle_t jointnum ) { + jointMod_t* jointMod; + int i; + + jointMod = FindExistingJointMod( jointnum, &i ); + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMod->transform_pos = JOINTMOD_NONE; + jointMod->angularVelocity.Init ( 0, 0, 0, 0, idAngles(0,0,0), idAngles(0,0,0) ); + jointMod->lastTime = 0; + jointMods.Insert( jointMod, i ); + } + + return jointMod; +} + +// abahr: +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( const char* animName, float rate ) { + SetPlaybackRate( GetAnim(animName), rate ); +} + +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( int animHandle, float rate ) { + idAnim* anim = const_cast( GetAnim(animHandle) ); + if( anim ) { + anim->SetPlaybackRate( rate ); + } +} +// RAVEN END + +/* +===================== +idAnimator::SetJointPos +===================== +*/ +void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); + +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->pos = pos; + jointMod->transform_pos = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAxis +===================== +*/ +void idAnimator::SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// RAVEN END + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->pos.Zero(); + jointMod->transform_pos = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->mat = mat; + jointMod->transform_axis = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +// RAVEN BEGIN +// twhitaker: just added this for uniformity +/* +===================== +idAnimator::GetJointAxis +===================== +*/ +void idAnimator::GetJointAxis( jointHandle_t jointnum, idMat3 &mat ) { + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + mat = FindJointMod ( jointnum )->mat; +} +// RAVEN END + +/* +===================== +idAnimator::CollapseJoint + +Add a joint modification for the given joint to collapse it to another joint +===================== +*/ +void idAnimator::CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ) { + jointMod_t *jointMod; + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod ( jointnum ); + + jointMod->transform_pos = JOINTMOD_COLLAPSE; + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat.Zero ( ); + jointMod->collapseJoint = collapseTo; +} + +/* +===================== +idAnimator::CollapseJoints + +Add a collapse joint modification for each joint listed in "jointnames" to the given joint "coppaseJoint" +===================== +*/ +void idAnimator::CollapseJoints ( const char *jointnames, jointHandle_t collpaseJoint ) { + idList jointList; + int i; + + GetJointList( jointnames, jointList ); + + for ( i = 0; i < jointList.Num(); i ++ ) { + CollapseJoint ( jointList[i], collpaseJoint ); + } +} + +// RAVEN BEGIN +// bdube: added more programmer controlled joint methods +/* +===================== +idAnimator::AimJointAt +===================== +*/ +void idAnimator::AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int currentTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + idVec3 offset; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + + GetJointTransform ( jointnum, currentTime, offset, axis ); + + origin = entity->GetPhysics()->GetOrigin() + offset * entity->GetPhysics()->GetAxis(); + + dir = pos - origin; + dir.NormalizeFast ( ); + entity->GetPhysics()->GetAxis().ProjectVector ( dir, localDir ); + + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat = localDir.ToMat3(); + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAngularVelocity +===================== +*/ +void idAnimator::SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + if ( !jointMod->lastTime ) { + jointMod->lastTime = currentTime; + jointMod->mat = idAngles(0,0,0).ToMat3(); + } + + jointMod->transform_axis = JOINTMOD_LOCAL; + + if ( blendTime <= 0 ) { + jointMod->angularVelocity.Init ( currentTime, 0, 0, 1, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } else { + jointMod->angularVelocity.Init ( currentTime, blendTime/2, blendTime/2, blendTime, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +idAngles idAnimator::GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + return jointMod->angularVelocity.GetCurrentValue( currentTime ); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +void idAnimator::ClearJointAngularVelocity ( jointHandle_t jointnum ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + jointMod->angularVelocity.Init( 0, 0, 0, 0, jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue(), jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue() ); +} +// RAVEN END + +/* +===================== +idAnimator::ClearJoint +===================== +*/ +void idAnimator::ClearJoint( jointHandle_t jointnum ) { + int i; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + delete jointMods[ i ]; + jointMods.RemoveIndex( i ); + ForceUpdate(); + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } +} + +/* +===================== +idAnimator::ClearAllJoints +===================== +*/ +void idAnimator::ClearAllJoints( void ) { + if ( jointMods.Num() ) { + ForceUpdate(); + } + jointMods.DeleteContents( true ); +} + +/* +===================== +idAnimator::ClearAllAnims +===================== +*/ +void idAnimator::ClearAllAnims( int currentTime, int cleartime ) { + int i; + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + Clear( i, currentTime, cleartime ); + } + + ClearAFPose(); + ForceUpdate(); +} + +/* +==================== +idAnimator::GetDelta +==================== +*/ +void idAnimator::GetDelta( int fromtime, int totime, idVec3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Zero(); + return; + } + + delta.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + } +} + +/* +==================== +idAnimator::GetDeltaRotation +==================== +*/ +bool idAnimator::GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + idQuat q; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Identity(); + return false; + } + + q.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + } + + if ( blendWeight > 0.0f ) { + delta = q.ToMat3(); + return true; + } else { + delta.Identity(); + return false; + } +} + +/* +==================== +idAnimator::GetOrigin +==================== +*/ +void idAnimator::GetOrigin( int currentTime, idVec3 &pos ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() ) { + pos.Zero(); + return; + } + + pos.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + } + + pos += modelDef->GetVisualOffset(); +} + +/* +==================== +idAnimator::GetBounds +==================== +*/ +bool idAnimator::GetBounds( int currentTime, idBounds &bounds ) { + int i, j; + const idAnimBlend *blend; + int count; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( AFPoseJoints.Num() ) { + bounds = AFPoseBounds; + count = 1; + } else { + bounds.Clear(); + count = 0; + } + + blend = channels[ 0 ]; + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->AddBounds( currentTime, bounds, removeOriginOffset ) ) { + count++; + } + } + } + + if ( !count ) { + if ( !frameBounds.IsCleared() ) { + bounds = frameBounds; + return true; + } else { + bounds.Zero(); + return false; + } + } + + bounds.TranslateSelf( modelDef->GetVisualOffset() ); +// RAVEN BEGIN +// jscott: HACK! The bounds only get the range of the skeleton, so add in the distance between the highest bone and the top of the head + bounds[1][2] += 4.0f; +// RAVEN END + + if ( g_debugBounds.GetBool() ) { + if ( bounds[1][0] - bounds[0][0] > 2048 || bounds[1][1] - bounds[0][1] > 2048 ) { + if ( entity ) { + gameLocal.Warning( "big frameBounds on entity '%s' with model '%s': %f,%f", entity->name.c_str(), modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } else { + gameLocal.Warning( "big frameBounds on model '%s': %f,%f", modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } + } + } + + frameBounds = bounds; + + return true; +} + +/* +===================== +idAnimator::InitAFPose +===================== +*/ +void idAnimator::InitAFPose( void ) { + + if ( !modelDef ) { + return; + } + + AFPoseJoints.SetNum( modelDef->Joints().Num(), false ); + AFPoseJoints.SetNum( 0, false ); + AFPoseJointMods.SetNum( modelDef->Joints().Num(), false ); + + if ( AFPoseJointFrame == NULL || AFPoseJointFrameSize != modelDef->Joints().Num() ) { + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = (idJointQuat *) Mem_Alloc16( modelDef->Joints().Num() * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + AFPoseJointFrameSize = modelDef->Joints().Num(); + } +} + +/* +===================== +idAnimator::SetAFPoseJointMod +===================== +*/ +void idAnimator::SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ) { + AFPoseJointMods[jointNum].mod = mod; + AFPoseJointMods[jointNum].axis = axis; + AFPoseJointMods[jointNum].origin = origin; + + int index = idBinSearch_GreaterEqual( AFPoseJoints.Ptr(), AFPoseJoints.Num(), jointNum ); + if ( index >= AFPoseJoints.Num() || jointNum != AFPoseJoints[index] ) { + AFPoseJoints.Insert( jointNum, index ); + } +} + +/* +===================== +idAnimator::FinishAFPose +===================== +*/ +void idAnimator::FinishAFPose( int animNum, const idBounds &bounds, const int time ) { + int i, j; + int numJoints; + int parentNum; + int jointMod; + int jointNum; + const int * jointParent; + + if ( !modelDef ) { + return; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return; + } + + numJoints = modelDef->Joints().Num(); + if ( !numJoints ) { + return; + } + + idRenderModel *md5 = modelDef->ModelHandle(); + const idMD5Anim *md5anim = anim->MD5Anim( 0 ); + + if ( numJoints != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", md5->Name(), numJoints, md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + md5anim->GetSingleFrame( 0, jointFrame, modelDef->GetChannelJoints( ANIMCHANNEL_ALL ), modelDef->NumJointsOnChannel( ANIMCHANNEL_ALL ) ); + + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( *joints ) ); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( AFPoseJoints.Num() && AFPoseJoints[0] == 0 ) { + switch( AFPoseJointMods[0].mod ) { + case AF_JOINTMOD_AXIS: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + } + j = 1; + } else { + j = 0; + } + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // transform the child joints + for( i = 1; j < AFPoseJoints.Num(); j++, i++ ) { + jointMod = AFPoseJoints[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod - 1 ); + i = jointMod; + + parentNum = jointParent[i]; + + switch( AFPoseJointMods[jointMod].mod ) { + case AF_JOINTMOD_AXIS: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[i].SetRotation( joints[i].ToMat3() * joints[parentNum].ToMat3() ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + // untransform hierarchy + SIMDProcessor->UntransformJoints( joints, jointParent, 1, numJoints - 1 ); + + // convert joint matrices back to joint quaternions + SIMDProcessor->ConvertJointMatsToJointQuats( AFPoseJointFrame, joints, numJoints ); + + // find all modified joints and their parents + bool *blendJoints = (bool *) _alloca16( numJoints * sizeof( bool ) ); + memset( blendJoints, 0, numJoints * sizeof( bool ) ); + + // mark all modified joints and their parents + for( i = 0; i < AFPoseJoints.Num(); i++ ) { + for( jointNum = AFPoseJoints[i]; jointNum != INVALID_JOINT; jointNum = jointParent[jointNum] ) { + blendJoints[jointNum] = true; + } + } + + // lock all parents of modified joints + AFPoseJoints.SetNum( 0, false ); + for ( i = 0; i < numJoints; i++ ) { + if ( blendJoints[i] ) { + AFPoseJoints.Append( i ); + } + } + + AFPoseBounds = bounds; + AFPoseTime = time; + + ForceUpdate(); +} + +/* +===================== +idAnimator::SetAFPoseBlendWeight +===================== +*/ +void idAnimator::SetAFPoseBlendWeight( float blendWeight ) { + AFPoseBlendWeight = blendWeight; +} + +/* +===================== +idAnimator::BlendAFPose +===================== +*/ +bool idAnimator::BlendAFPose( idJointQuat *blendFrame ) const { + + if ( !AFPoseJoints.Num() ) { + return false; + } + + SIMDProcessor->BlendJoints( blendFrame, AFPoseJointFrame, AFPoseBlendWeight, AFPoseJoints.Ptr(), AFPoseJoints.Num() ); + + return true; +} + +/* +===================== +idAnimator::ClearAFPose +===================== +*/ +void idAnimator::ClearAFPose( void ) { + if ( AFPoseJoints.Num() ) { + ForceUpdate(); + } + AFPoseBlendWeight = 1.0f; + AFPoseJoints.SetNum( 0, false ); + AFPoseBounds.Clear(); + AFPoseTime = 0; +} + +/* +===================== +idAnimator::ServiceAnims +===================== +*/ +void idAnimator::ServiceAnims( int fromtime, int totime ) { + int i, j; + idAnimBlend *blend; + + if ( !modelDef ) { + return; + } + + if ( modelDef->ModelHandle() ) { + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + blend->CallFrameCommands( entity, fromtime, totime ); + } + } + } + + if ( !IsAnimating( totime ) ) { + stoppedAnimatingUpdate = true; + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + + // present one more time with stopped animations so the renderer can properly recreate interactions + entity->BecomeActive( TH_UPDATEVISUALS ); + } + } +} + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation +/* +===================== +idAnimator::IsAnimating +===================== +*/ +bool idAnimator::IsAnimating( int currentTime, bool IgnoreAF ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( !IgnoreAF && AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( !blend->IsDone( currentTime ) ) { + return true; + } + } + } + + return false; +} +// RAVEN END + +/* +===================== +idAnimator::FrameHasChanged +===================== +*/ +bool idAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->FrameHasChanged( currentTime ) ) { + return true; + } + } + } + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimator::CreateFrame +===================== +*/ +bool idAnimator::CreateFrame( int currentTime, bool force ) { + int i, j; + int numJoints; + int parentNum; + bool hasAnim; + bool debugInfo; + float baseBlend; + float blendWeight; + const idAnimBlend * blend; + const int * jointParent; +// RAVEN BEGIN +// bdube: removed const + jointMod_t * jointMod; +// RAVEN END + const idJointQuat * defaultPose; + + static idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only, 3 = draw joints only", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + +// RAVEN BEGIN +// rjohnson: always check the time transform except force. Even when skeleton is on + if ( !force ) { + if ( lastTransformTime == currentTime ) { + return false; + } + } + + if ( !force && !r_showSkel.GetInteger() ) { + if ( lastTransformTime != -1 && !stoppedAnimatingUpdate && !IsAnimating( currentTime ) ) { + return false; + } + } + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return false; + } +#endif +// RAVEN END + + lastTransformTime = currentTime; + stoppedAnimatingUpdate = false; + + if ( entity && ( ( g_debugAnim.GetInteger() == entity->entityNumber ) || ( g_debugAnim.GetInteger() == -2 ) ) ) { + debugInfo = true; + gameLocal.Printf( "---------------\n%d: entity '%s':\n", gameLocal.time, entity->GetName() ); + gameLocal.Printf( "model '%s':\n", modelDef->GetModelName() ); + } else { + debugInfo = false; + } + + // init the joint buffer + if ( AFPoseJoints.Num() ) { + // initialize with AF pose anim for the case where there are no other animations and no AF pose joint modifications + defaultPose = AFPoseJointFrame; + } else { + defaultPose = modelDef->GetDefaultPose(); + } + + if ( !defaultPose ) { + //gameLocal.Warning( "idAnimator::CreateFrame: no defaultPose on '%s'", modelDef->Name() ); + return false; + } + + numJoints = modelDef->Joints().Num(); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( jointFrame[0] ) ); + SIMDProcessor->Memcpy( jointFrame, defaultPose, numJoints * sizeof( jointFrame[0] ) ); + + hasAnim = false; + + // blend the all channel + baseBlend = 0.0f; + blend = channels[ ANIMCHANNEL_ALL ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_ALL, numJoints, jointFrame, baseBlend, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( baseBlend >= 1.0f ) { + break; + } + } + } + + // only blend other channels if there's enough space to blend into + if ( baseBlend < 1.0f ) { + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !modelDef->NumJointsOnChannel( i ) ) { + continue; + } + if ( i == ANIMCHANNEL_EYELIDS ) { + // eyelids blend over any previous anims, so skip it and blend it later + continue; + } + blendWeight = baseBlend; + blend = channels[ i ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, i, numJoints, jointFrame, blendWeight, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + + if ( debugInfo && !AFPoseJoints.Num() && !blendWeight ) { + gameLocal.Printf( "%d: %s using default pose in model '%s'\n", gameLocal.time, channelNames[ i ], modelDef->GetModelName() ); + } + } + } + + // blend in the eyelids + if ( modelDef->NumJointsOnChannel( ANIMCHANNEL_EYELIDS ) ) { + blend = channels[ ANIMCHANNEL_EYELIDS ]; + blendWeight = baseBlend; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_EYELIDS, numJoints, jointFrame, blendWeight, removeOriginOffset, true, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + } + + // blend the articulated figure pose + if ( BlendAFPose( jointFrame ) ) { + hasAnim = true; + } + + if ( !hasAnim && !jointMods.Num() ) { + // no animations were updated + return false; + } + + // convert the joint quaternions to rotation matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // check if we need to modify the origin + if ( jointMods.Num() && ( jointMods[0]->jointnum == 0 ) ) { + jointMod = jointMods[0]; + + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetRotation( jointMod->mat * joints[0].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[0].SetRotation( joints[0].ToMat3() * jointMod->mat ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetRotation( jointMod->mat ); + break; + } + + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetTranslation( joints[0].ToVec3() + jointMod->pos ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetTranslation( jointMod->pos ); + break; + } + j = 1; + } else { + j = 0; + } + + // add in the model offset + joints[0].SetTranslation( joints[0].ToVec3() + modelDef->GetVisualOffset() ); + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // add in any joint modifications + for( i = 1; j < jointMods.Num(); j++, i++ ) { + jointMod = jointMods[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod->jointnum - 1 ); + i = jointMod->jointnum; + + parentNum = jointParent[i]; + +// RAVEN BEGIN +// bdube: angular velocity on joints + if ( jointMod->angularVelocity.GetStartTime ( ) ) { + idAngles angles; + angles = jointMod->angularVelocity.GetCurrentValue ( currentTime ) * MS2SEC(currentTime-jointMod->lastTime); + angles.Normalize360 ( ); + jointMod->mat *= angles.ToMat3 ( ); + } +// RAVEN END + + // modify the axis + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + joints[i].SetRotation( joints[i].ToMat3() * joints[ parentNum ].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetRotation( jointMod->mat * ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetRotation( jointMod->mat * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetRotation( ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) * jointMod->mat ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetRotation( jointMod->mat ); + break; + } + + // modify the position + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetTranslation( joints[parentNum].ToVec3() + ( joints[i].ToVec3() + jointMod->pos ) * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + jointMod->pos * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() + jointMod->pos ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetTranslation( jointMod->pos ); + break; + + case JOINTMOD_COLLAPSE: + joints[i].SetTranslation( joints[jointMod->collapseJoint].ToVec3() ); + break; + } + +// RAVEN BEGIN +// bdube: more joint control + jointMod->lastTime = currentTime; +// RAVEN END + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + return true; +} + +/* +===================== +idAnimator::ForceUpdate +===================== +*/ +void idAnimator::ForceUpdate( void ) { + lastTransformTime = -1; + forceUpdate = true; +} + +/* +===================== +idAnimator::ClearForceUpdate +===================== +*/ +void idAnimator::ClearForceUpdate( void ) { + forceUpdate = false; +} + +/* +===================== +idAnimator::GetJointTransform + +===================== +*/ +bool idAnimator::GetJointTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef || ( jointHandle < 0 ) || ( jointHandle >= modelDef->NumJoints() ) ) { + return false; + } + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + CreateFrame( currentTime, false ); + + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + + return true; +} + +/* +===================== +idAnimator::GetJointLocalTransform +===================== +*/ +bool idAnimator::GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef ) { + return false; + } + + const idList &modelJoints = modelDef->Joints(); + + if ( ( jointHandle < 0 ) || ( jointHandle >= modelJoints.Num() ) ) { + return false; + } + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + // FIXME: overkill + CreateFrame( currentTime, false ); + + if ( jointHandle > 0 ) { + idJointMat m = joints[ jointHandle ]; + m /= joints[ modelJoints[ jointHandle ].parentNum ]; + offset = m.ToVec3(); + axis = m.ToMat3(); + } else { + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + } + + return true; +} + +/* +===================== +idAnimator::GetJointHandle +===================== +*/ +jointHandle_t idAnimator::GetJointHandle( const char *name ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return INVALID_JOINT; + } + + return modelDef->ModelHandle()->GetJointHandle( name ); +} + +/* +===================== +idAnimator::GetJointName +===================== +*/ +const char *idAnimator::GetJointName( jointHandle_t handle ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return ""; + } + + return modelDef->ModelHandle()->GetJointName( handle ); +} + +/* +===================== +idAnimator::GetChannelForJoint +===================== +*/ +int idAnimator::GetChannelForJoint( jointHandle_t joint ) const { + if ( !modelDef ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: NULL model" ); + } + + if ( ( joint < 0 ) || ( joint >= numJoints ) ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: invalid joint num (%d)", joint ); + } + + return modelDef->GetJoint( joint )->channel; +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( const char *name ) const { + return GetFirstChild( GetJointHandle( name ) ); +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( jointHandle_t jointnum ) const { + int i; + int num; + const jointInfo_t *joint; + + if ( !modelDef ) { + return INVALID_JOINT; + } + + num = modelDef->NumJoints(); + if ( !num ) { + return jointnum; + } + joint = modelDef->GetJoint( 0 ); + for( i = 0; i < num; i++, joint++ ) { + if ( joint->parentNum == jointnum ) { + return ( jointHandle_t )joint->num; + } + } + return jointnum; +} + +/* +===================== +idAnimator::GetJoints +===================== +*/ +void idAnimator::GetJoints( int *numJoints, idJointMat **jointsPtr ) { + *numJoints = this->numJoints; + *jointsPtr = this->joints; +} + +/* +===================== +idAnimator::GetAnimFlags +===================== +*/ +const animFlags_t idAnimator::GetAnimFlags( int animNum ) const { + animFlags_t result; + + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->GetAnimFlags(); + } + + memset( &result, 0, sizeof( result ) ); + return result; +} + +/* +===================== +idAnimator::IsBlending + +Returns true if the given channel number is currently blending more than one animation +===================== +*/ +bool idAnimator::IsBlending ( int channel, int currentTime ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::GetAnimCount : channel (%d) out of range", channel ); + } + + int i; + for( i = 1; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( !channels[ channel ][ i ].IsDone ( currentTime ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idAnimator::NumFrames +===================== +*/ +int idAnimator::NumFrames( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumFrames(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::NumSyncedAnims +===================== +*/ +int idAnimator::NumSyncedAnims( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumAnims(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::AnimName +===================== +*/ +const char *idAnimator::AnimName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Name(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimFullName +===================== +*/ +const char *idAnimator::AnimFullName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } else { + return ""; + } +} + +// RAVEN BEGIN +// rjohnson: more output for animators +/* +===================== +idAnimator::AnimMD5Name +===================== +*/ +const char *idAnimator::AnimMD5Name( int animNum, int index ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + const idMD5Anim *md5Anim = anim->MD5Anim( index ); + + if ( md5Anim ) { + return md5Anim->Name(); + } else { + return ""; + } + } else { + return ""; + } +} +// RAVEN END + +/* +===================== +idAnimator::AnimLength +===================== +*/ +int idAnimator::AnimLength( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Length(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::TotalMovementDelta +===================== +*/ +const idVec3 &idAnimator::TotalMovementDelta( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->TotalMovementDelta(); + } else { + return vec3_origin; + } +} + +// RAVEN BEGIN +// nrausch: added func +/* +===================== +idAnimator::GetNearestJoint +===================== +*/ +jointHandle_t idAnimator::GetNearestJoint( const idVec3 &start, const idVec3 &end, int currentTime, jointHandle_t *jointList, int cnt ) { + + int numJoints = 0; + idJointMat *joints = 0; + GetJoints( &numJoints, &joints ); + + jointHandle_t closestJoint = INVALID_JOINT; + float closest = 0.0f; + + idVec3 a = end - start; + a.Normalize(); + + for ( int i = 0; i < cnt; ++i ) { + + // If we specified a null jointlist, just run through each joint + jointHandle_t jointNum = (jointHandle_t)i; + if ( jointList ) { + jointNum = jointList[i]; + } + + if ( jointNum != INVALID_JOINT ) { + idVec3 jointPos; + idMat3 jointMat; + + GetJointTransform( jointNum, currentTime, jointPos, jointMat ); + + idVec3 b = jointPos - start; + b.Normalize(); + + float newDot = DotProduct(a, b); + + if ( newDot > closest ) { + closest = newDot; + closestJoint = jointNum; + } + } + } + + return closestJoint; +} +// RAVEN END + +/*********************************************************************** + + Util functions + +***********************************************************************/ + +/* +===================== +ANIM_GetModelDefFromEntityDef +===================== +*/ +const idDeclModelDef *ANIM_GetModelDefFromEntityDef( const idDict *args ) { + const idDeclModelDef *modelDef; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef && modelDef->ModelHandle() ) { + return modelDef; + } + + return NULL; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const idDict *args ) { + idRenderModel *model; + const idDeclModelDef *modelDef; + + model = NULL; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + + if ( !model ) { + model = renderModelManager->FindModel( name ); + } + + if ( model && model->IsDefaultModel() ) { + return NULL; + } + + return model; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const char *classname ) { + const idDict *args; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + return ANIM_GetModelFromEntityDef( args ); +} + +/* +===================== +idGameEdit::ANIM_GetModelOffsetFromEntityDef +===================== +*/ +const idVec3 &idGameEdit::ANIM_GetModelOffsetFromEntityDef( const char *classname ) { + const idDict *args; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return vec3_origin; + } + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( !modelDef ) { + return vec3_origin; + } + + return modelDef->GetVisualOffset(); +} + +/* +===================== +idGameEdit::ANIM_GetModelFromName +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromName( const char *modelName ) { + const idDeclModelDef *modelDef; + idRenderModel *model; + + model = NULL; + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + if ( !model ) { + model = renderModelManager->FindModel( modelName ); + } + return model; +} + +/* +===================== +idGameEdit::ANIM_GetAnimFromEntityDef +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ) { + const idDict *args; + const idMD5Anim *md5anim; + const idAnim *anim; + int animNum; + const char *modelname; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + md5anim = NULL; + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( animNum ) { + anim = modelDef->GetAnim( animNum ); + if ( anim ) { + md5anim = anim->MD5Anim( 0 ); + } + } + } + return md5anim; +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetAnimFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return NULL; + } + + return anim->MD5Anim(0); +} + +/* +===================== +idGameEdit::ANIM_GetAnimPlaybackRateFromEntity +===================== +*/ +float idGameEdit::ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return 1.0f; + } + + return anim->GetPlaybackRate(); +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const char* idGameEdit::ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} +// RAVEN END + +/* +===================== +idGameEdit::ANIM_GetNumAnimsFromEntityDef +===================== +*/ +int idGameEdit::ANIM_GetNumAnimsFromEntityDef( const idDict *args ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + return modelDef->NumAnims(); + } + return 0; +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntityDef +===================== +*/ +const char *idGameEdit::ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + const idAnim* anim = modelDef->GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } + } + return ""; +} + +/* +===================== +idGameEdit::ANIM_GetAnim +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnim( const char *fileName ) { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + return animationLib->GetAnim( fileName ); +// RAVEN END +} + +/* +===================== +idGameEdit::ANIM_GetLength +===================== +*/ +int idGameEdit::ANIM_GetLength( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->Length(); +} + +/* +===================== +idGameEdit::ANIM_GetNumFrames +===================== +*/ +int idGameEdit::ANIM_GetNumFrames( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->NumFrames(); +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetFilename +===================== +*/ +const char * idGameEdit::ANIM_GetFilename( const idMD5Anim* anim ) { + return anim->Name(); +} + +/* +===================== +idGameEdit::ANIM_ConvertFrameToTime +===================== +*/ +int idGameEdit::ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ) { + frameBlend_t fb; + fb.frame1 = frame; + fb.frame2 = frame; + return anim->ConvertFrameToTime ( fb ); +} + +/* +===================== +idGameEdit::ANIM_ConvertTimeToFrame +===================== +*/ +int idGameEdit::ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ) { + frameBlend_t fb; + anim->ConvertTimeToFrame ( time, 0, fb ); + return fb.frame1; +} + +// RAVEN END + +/* +===================== +idGameEdit::ANIM_CreateAnimFrame +===================== +*/ +void idGameEdit::ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *joints, int time, const idVec3 &offset, bool remove_origin_offset ) { + int i; + frameBlend_t frame; + const idMD5Joint *md5joints; + int *index; + + if ( !model || model->IsDefaultModel() || !anim ) { + return; + } + + if ( numJoints != model->NumJoints() ) { + gameLocal.Error( "ANIM_CreateAnimFrame: different # of joints in renderEntity_t (%d) than in model (%s)(%d)", model->NumJoints(), model->Name(), numJoints ); + } + + if ( !model->NumJoints() ) { + // FIXME: Print out a warning? + return; + } + + if ( !joints ) { + gameLocal.Error( "ANIM_CreateAnimFrame: NULL joint frame pointer on model (%s)", model->Name() ); + } + + if ( numJoints != anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), numJoints, anim->Name(), anim->NumJoints() ); +// RAVEN END + for( i = 0; i < numJoints; i++ ) { + joints[i].SetRotation( mat3_identity ); + joints[i].SetTranslation( offset ); + } + return; + } + + // create index for all joints + index = ( int * )_alloca16( numJoints * sizeof( int ) ); + for ( i = 0; i < numJoints; i++ ) { + index[i] = i; + } + + // create the frame + anim->ConvertTimeToFrame( time, 1, frame ); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + anim->GetInterpolatedFrame( frame, jointFrame, index, numJoints ); + + // convert joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( remove_origin_offset ) { + joints[0].SetTranslation( offset ); + } else { + joints[0].SetTranslation( joints[0].ToVec3() + offset ); + } + + // transform the children + md5joints = model->GetJoints(); + for( i = 1; i < numJoints; i++ ) { + joints[i] *= joints[ md5joints[i].parent - md5joints ]; + } +} + +/* +===================== +idGameEdit::ANIM_CreateMeshForAnim +===================== +*/ +idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ) { + renderEntity_t ent; + const idDict *args; + const char *temp; + idRenderModel *newmodel; + const idMD5Anim *md5anim; + idStr filename; + idStr extension; + const idAnim *anim; + int animNum; + idVec3 offset; + const idDeclModelDef *modelDef; + + if ( !model || model->IsDefaultModel() ) { + return NULL; + } + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + memset( &ent, 0, sizeof( ent ) ); + + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( !animNum ) { + return NULL; + } + anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + md5anim = anim->MD5Anim( 0 ); + ent.customSkin = modelDef->GetDefaultSkin(); + offset = modelDef->GetVisualOffset(); + } else { + filename = animname; + filename.ExtractFileExtension( extension ); + if ( !extension.Length() ) { + animname = args->GetString( va( "anim %s", animname ) ); + } + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( animname ); +// RAVEN END + offset.Zero(); + } + + if ( !md5anim ) { + return NULL; + } + + temp = args->GetString( "skin", "" ); + if ( temp[ 0 ] ) { + ent.customSkin = declManager->FindSkin( temp ); + } + + ent.numJoints = model->NumJoints(); +//RAVEN BEGIN +//amccarthy: Added allocation tag + ent.joints = ( idJointMat * )Mem_Alloc16( ent.numJoints * sizeof( *ent.joints ), MA_ANIM ); +//RAVEN END + + ANIM_CreateAnimFrame( model, md5anim, ent.numJoints, ent.joints, FRAME2MS( frame ), offset, remove_origin_offset ); + + newmodel = model->InstantiateDynamicModel( &ent, NULL, NULL, ~SURF_COLLISION ); + + Mem_Free16( ent.joints ); + ent.joints = NULL; + + return newmodel; +} + +// RAVEN BEGIN +// jsinger: allow for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +idAnim::idAnim( idDeclModelDef const *def, SerialInputStream &stream ) +{ + modelDef = def; + numAnims = stream.ReadIntValue(); + for(int i=0; i(); + for(int i=0; iAppend(stream.ReadStringValue()); + } + } + else + { + com.parmList = NULL; + } + } + flags.prevent_idle_override = stream.ReadBoolValue(); + flags.random_cycle_start = stream.ReadBoolValue(); + flags.ai_no_turn = stream.ReadBoolValue(); + flags.anim_turn = stream.ReadBoolValue(); + flags.sync_cycle = stream.ReadBoolValue(); + flags.ai_no_look = stream.ReadBoolValue(); + flags.ai_look_head_only = stream.ReadBoolValue(); + rate = stream.ReadFloatValue(); +} + +void idAnim::Write( SerialOutputStream &stream ) const +{ + stream.Write(numAnims); + for(int i=0; iName()); + } + stream.Write(name); + stream.Write(realname); + stream.Write(frameLookup.Num()); + for(int i=0; iNum()); + for(int j=0; jNum(); j++) + { + stream.Write((*frameCommands[i].parmList)[j]); + } + } + } + stream.Write((bool)flags.prevent_idle_override); + stream.Write((bool)flags.random_cycle_start); + stream.Write((bool)flags.ai_no_turn); + stream.Write((bool)flags.anim_turn); + stream.Write((bool)flags.sync_cycle); + stream.Write((bool)flags.ai_no_look); + stream.Write((bool)flags.ai_look_head_only); + stream.Write(rate); +} +#endif +// RAVEN END diff --git a/source/game/anim/Anim_Import.cpp b/source/game/anim/Anim_Import.cpp new file mode 100644 index 0000000..d9b0910 --- /dev/null +++ b/source/game/anim/Anim_Import.cpp @@ -0,0 +1,604 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "../../MayaImport/maya_main.h" + +/*********************************************************************** + + Maya conversion functions + +***********************************************************************/ + +static idStr Maya_Error; + +static exporterInterface_t Maya_ConvertModel = NULL; +static exporterShutdown_t Maya_Shutdown = NULL; +static int importDLL = 0; + +bool idModelExport::initialized = false; + + +// RAVEN BEGIN +// scork: now needed to cleanup after last Maya model conversion since I cache them for speed during "exportmodels" +bool g_bMayaConversionCleanerRunning = false; // so we can check all calls to Maya_ConvertModel have been updated to include this ;-) +struct MayaConversionCleaner +{ + MayaConversionCleaner() + { + g_bMayaConversionCleanerRunning = true; + } + ~MayaConversionCleaner() + { + if ( Maya_ConvertModel ) + { + Maya_ConvertModel( NULL, NULL, NULL ); + } + g_bMayaConversionCleanerRunning = false; + } +}; +// RAVEN END + + +/* +==================== +idModelExport::idModelExport +==================== +*/ +idModelExport::idModelExport() { + Reset(); +} + +/* +==================== +idModelExport::Shutdown +==================== +*/ +void idModelExport::Shutdown( void ) { + if ( Maya_Shutdown ) { + Maya_Shutdown(); + } + + if ( importDLL ) { + sys->DLL_Unload( importDLL ); + } + + importDLL = 0; + Maya_Shutdown = NULL; + Maya_ConvertModel = NULL; + Maya_Error.Clear(); + initialized = false; +} + +/* +===================== +idModelExport::CheckMayaInstall + +Determines if Maya is installed on the user's machine +===================== +*/ +bool idModelExport::CheckMayaInstall( void ) { +// RAVEN BEGIN +// jscott:revisit - this should go into the exe +//#ifdef _WIN32 +#ifndef _WINDOWS + return false; +#elif 0 + HKEY hKey; + long lres, lType; + + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya\\4.5\\Setup\\InstallPath", &hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + + lres = RegQueryValueEx( hKey, "MAYA_INSTALL_LOCATION", NULL, (unsigned long*)&lType, (unsigned char*)NULL, (unsigned long*)NULL ); + + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#else + HKEY hKey; + long lres; + + // only check the non-version specific key so that we only have to update the maya dll when new versions are released + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya", &hKey ); + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#endif +} + +/* +===================== +idModelExport::LoadMayaDll + +Checks to see if we can load the Maya export dll +===================== +*/ +void idModelExport::LoadMayaDll( void ) { + exporterDLLEntry_t dllEntry; + char dllPath[ MAX_OSPATH ]; + + fileSystem->FindDLL( "MayaImport", dllPath, false ); + if ( !dllPath[ 0 ] ) { + return; + } + importDLL = sys->DLL_Load( dllPath ); + if ( !importDLL ) { + return; + } + + // look up the dll interface functions + dllEntry = ( exporterDLLEntry_t )sys->DLL_GetProcAddress( importDLL, "dllEntry" ); + Maya_ConvertModel = ( exporterInterface_t )sys->DLL_GetProcAddress( importDLL, "Maya_ConvertModel" ); + Maya_Shutdown = ( exporterShutdown_t )sys->DLL_GetProcAddress( importDLL, "Maya_Shutdown" ); + if ( !Maya_ConvertModel || !dllEntry || !Maya_Shutdown ) { + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Invalid interface on export DLL." ); + return; + } + + // initialize the DLL +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to fix some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR + if ( !dllEntry( MD5_VERSION, common, sys, Memory::Allocate, Memory::Free, Memory::MSize ) ) { +#else + if ( !dllEntry( MD5_VERSION, common, sys ) ) { +#endif +// RAVEN END + // init failed + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Export DLL init failed." ); + return; + } +// RAVEN BEGIN +// jscott: maya needs the source control in the tools dll + common->LoadToolsDLL(); +// RAVEN END +} + +/* +===================== +idModelExport::ConvertMayaToMD5 + +Checks if a Maya model should be converted to an MD5, and converts if if the time/date or +version number has changed. +===================== +*/ +bool idModelExport::ConvertMayaToMD5( void ) { + unsigned sourceTime; + unsigned destTime; + int version; + idToken cmdLine; + idStr path; + + // check if our DLL got loaded + if ( initialized && !Maya_ConvertModel ) { + Maya_Error = "MayaImport dll not loaded."; + return false; + } + + // if idAnimManager::forceExport is set then we always reexport Maya models + if ( idAnimManager::forceExport ) { + force = true; + } + +// RAVEN BEGIN +// bdube: fs_import path + + // we need to make sure we have a full path, so convert the filename to an OS path + path.AppendPath( cvarSystem->GetCVarString ("fs_importpath")); + path.AppendPath(src); + + idFile* f = fileSystem->OpenExplicitFileRead ( path ); + if ( !f ) { + Maya_Error = "could not open source file"; + return false; + } + sourceTime = f->Timestamp ( ); + fileSystem->CloseFile ( f ); + +/* + // get the source file's time + if ( fileSystem->ReadFile( src, NULL, &sourceTime ) < 0 ) { + // source file doesn't exist + return true; + } +*/ + +// RAVEN END + + // get the destination file's time + if ( !force && ( fileSystem->ReadFile( dest, NULL, &destTime ) >= 0 ) ) { + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + + parser.LoadFile( dest ); + + // read the file version + if ( parser.CheckTokenString( MD5_VERSION_STRING ) ) { + version = parser.ParseInt(); + + // check the command line + if ( parser.CheckTokenString( "commandline" ) ) { + parser.ReadToken( &cmdLine ); + + // check the file time, scale, and version + if ( ( destTime >= sourceTime ) && ( version == MD5_VERSION ) && ( cmdLine == commandLine ) ) { + // don't convert it + return true; + } + } + } + } + + // if this is the first time we've been run, check if Maya is installed and load our DLL + if ( !initialized ) { + initialized = true; + + if ( !CheckMayaInstall() ) { + Maya_Error = "Maya not installed in registry."; + return false; + } + + LoadMayaDll(); + + // check if our DLL got loaded + if ( !Maya_ConvertModel ) { + Maya_Error = "Could not load MayaImport dll."; + return false; + } + } + + // we need to make sure we have a full path, so convert the filename to an OS path + src = fileSystem->RelativePathToOSPath( src ); + dest = fileSystem->RelativePathToOSPath( dest ); + + dest.ExtractFilePath( path ); + if ( path.Length() ) { + fileSystem->CreateOSPath( path ); + } + + // get the os path in case it needs to create one + path = fileSystem->RelativePathToOSPath( "" ); + + common->SetRefreshOnPrint( true ); +// RAVEN BEGIN +// scork: if this assert ever triggers it means there's a call to ConvertMayaToMD5() that doesn't have a 'MayaConversionCleaner' object above it. Go and put one in. Now. + assert( g_bMayaConversionCleanerRunning ); +// bdube: support src and dest ospath + Maya_Error = Maya_ConvertModel( cvarSystem->GetCVarString ( "fs_importpath" ), path, commandLine ); +// RAVEN END + common->SetRefreshOnPrint( false ); + if ( Maya_Error != "Ok" ) { + return false; + } + + // conversion succeded + return true; +} + +/* +==================== +idModelExport::Reset +==================== +*/ +void idModelExport::Reset( void ) { + force = false; + commandLine = ""; + src = ""; + dest = ""; +} + +/* +==================== +idModelExport::ExportModel +==================== +*/ +bool idModelExport::ExportModel( const char *model ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + const char *game = cvarSystem->GetCVarString( "fs_game" ); + + if ( strlen(game) == 0 ) { + game = BASE_GAMEDIR; + } + + Reset(); + src = model; + dest = model; + dest.SetFileExtension( MD5_MESH_EXT ); + + sprintf( commandLine, "mesh %s -dest %s -game %s", src.c_str(), dest.c_str(), game ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ExportAnim +==================== +*/ +bool idModelExport::ExportAnim( const char *anim ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + Reset(); + src = anim; + dest = anim; + dest.SetFileExtension( MD5_ANIM_EXT ); + + sprintf( commandLine, "anim %s -dest %s -game %s", src.c_str(), dest.c_str(), CD_BASEDIR ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ParseOptions +==================== +*/ +bool idModelExport::ParseOptions( idLexer &lex ) { + idToken token; + idStr destdir; + idStr sourcedir; + + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expected filename" ); + return false; + } + + src = token; + dest = token; + + while( lex.ReadToken( &token ) ) { + if ( token == "-" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expecting option" ); + return false; + } + if ( token == "sourcedir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -sourcedir" ); + return false; + } + sourcedir = token; + } else if ( token == "destdir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -destdir" ); + return false; + } + destdir = token; + } else if ( token == "dest" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing filename after -dest" ); + return false; + } + dest = token; + } else { + commandLine += va( " -%s", token.c_str() ); + } + } else { + commandLine += va( " %s", token.c_str() ); + } + } + + if ( sourcedir.Length() ) { + src.StripPath(); + sourcedir.BackSlashesToSlashes(); + sprintf( src, "%s/%s", sourcedir.c_str(), src.c_str() ); + } + + if ( destdir.Length() ) { + dest.StripPath(); + destdir.BackSlashesToSlashes(); + sprintf( dest, "%s/%s", destdir.c_str(), dest.c_str() ); + } + + return true; +} + +/* +==================== +idModelExport::ParseExportSection +==================== +*/ +int idModelExport::ParseExportSection( idParser &parser ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + idToken command; + idToken token; + idStr defaultCommands; + idLexer lex; + idStr temp; + idStr parms; + int count; + + // only export sections that match our export mask + if ( g_exportMask.GetString()[ 0 ] ) { + if ( parser.CheckTokenString( "{" ) ) { + parser.SkipBracedSection( false ); + return 0; + } + + parser.ReadToken( &token ); + if ( token.Icmp( g_exportMask.GetString() ) ) { + parser.SkipBracedSection(); + return 0; + } + parser.ExpectTokenString( "{" ); + } else if ( !parser.CheckTokenString( "{" ) ) { + // skip the export mask + parser.ReadToken( &token ); + parser.ExpectTokenString( "{" ); + } + + count = 0; + + lex.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + while( 1 ) { + + if ( !parser.ReadToken( &command ) ) { + parser.Error( "Unexpoected end-of-file" ); + break; + } + + if ( command == "}" ) { + break; + } + + if ( command == "options" ) { + parser.ParseRestOfLine( defaultCommands ); + } else if ( command == "addoptions" ) { + parser.ParseRestOfLine( temp ); + defaultCommands += " "; + defaultCommands += temp; + } else if ( ( command == "mesh" ) || ( command == "anim" ) || ( command == "camera" ) ) { + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Expected filename" ); + } + + temp = token; + parser.ParseRestOfLine( parms ); + + if ( defaultCommands.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), defaultCommands.c_str() ); + } + + if ( parms.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), parms.c_str() ); + } + + lex.LoadMemory( temp, temp.Length(), parser.GetFileName() ); + + Reset(); + if ( ParseOptions( lex ) ) { + if ( command == "mesh" ) { + dest.SetFileExtension( MD5_MESH_EXT ); + } else if ( command == "anim" ) { + dest.SetFileExtension( MD5_ANIM_EXT ); + } else if ( command == "camera" ) { + dest.SetFileExtension( MD5_CAMERA_EXT ); + } else { + dest.SetFileExtension( command ); + } + idStr back = commandLine; + sprintf( commandLine, "%s %s -dest %s -game %s%s", command.c_str(), src.c_str(), dest.c_str(), CD_BASEDIR, commandLine.c_str() ); + if ( ConvertMayaToMD5() ) { + count++; + } else { + parser.Warning( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + } + } + lex.FreeSource(); + } else { + parser.Error( "Unknown token: %s", command.c_str() ); + parser.SkipBracedSection( false ); + break; + } + } + + return count; +} + +/* +================ +idModelExport::ExportDefFile +================ +*/ +int idModelExport::ExportDefFile( const char *filename ) { + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + idToken token; + int count; + + count = 0; + + if ( !parser.LoadFile( filename ) ) { + gameLocal.Printf( "Could not load '%s'\n", filename ); + return 0; + } + + while( parser.ReadToken( &token ) ) { + if ( token == "export" ) { + count += ParseExportSection( parser ); + } else { + parser.ReadToken( &token ); + parser.SkipBracedSection(); + } + } + + return count; +} + +/* +================ +idModelExport::ExportModels +================ +*/ +int idModelExport::ExportModels( const char *pathname, const char *extension ) { + int count; + + count = 0; + + idFileList *files; + int i; + + if ( !CheckMayaInstall() ) { + // if Maya isn't installed, don't bother checking if we have anims to export + return 0; + } + + gameLocal.Printf( "--------- Exporting models --------\n" ); + if ( !g_exportMask.GetString()[ 0 ] ) { + gameLocal.Printf( " Export mask: '%s'\n", g_exportMask.GetString() ); + } + + count = 0; + + files = fileSystem->ListFiles( pathname, extension ); + for( i = 0; i < files->GetNumFiles(); i++ ) { + count += ExportDefFile( va( "%s/%s", pathname, files->GetFile( i ) ) ); + } + fileSystem->FreeFileList( files ); + + gameLocal.Printf( "...%d models exported.\n", count ); + gameLocal.Printf( "-----------------------------------\n" ); + + return count; +} diff --git a/source/game/anim/Anim_Testmodel.cpp b/source/game/anim/Anim_Testmodel.cpp new file mode 100644 index 0000000..8dc4deb --- /dev/null +++ b/source/game/anim/Anim_Testmodel.cpp @@ -0,0 +1,940 @@ +/* +============================================================================= + + MODEL TESTING + +Model viewing can begin with either "testmodel " + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Extension will default to ".ase" if not specified. + +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. + + g_testModelRotate + g_testModelAnimate + g_testModelBlend + +============================================================================= +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idAnimatedEntity, idTestModel ) + EVENT( EV_FootstepLeft, idTestModel::Event_Footstep ) + EVENT( EV_FootstepRight, idTestModel::Event_Footstep ) +END_CLASS + +/* +================ +idTestModel::idTestModel +================ +*/ +idTestModel::idTestModel() { + head = NULL; + headAnimator = NULL; + anim = 0; + headAnim = 0; + starttime = 0; + animtime = 0; + mode = 0; + frame = 0; +} + +/* +================ +idTestModel::Save +================ +*/ +void idTestModel::Save( idSaveGame *savefile ) { +} + +/* +================ +idTestModel::Restore +================ +*/ +void idTestModel::Restore( idRestoreGame *savefile ) { + // FIXME: one day we may actually want to save/restore test models, but for now we'll just delete them + delete this; +} + +/* +================ +idTestModel::Spawn +================ +*/ +void idTestModel::Spawn( void ) { + idVec3 size; + idBounds bounds; + const char *headModel; + jointHandle_t joint; + idStr jointName; + idVec3 origin, modelOffset; + idMat3 axis; + const idKeyValue *kv; + copyJoints_t copyJoint; +// RAVEN BEGIN +// ddynerman: new heads + idAFAttachment *headEnt; +// RAVEN END + + if ( renderEntity.hModel && renderEntity.hModel->IsDefaultModel() && !animator.ModelDef() ) { + gameLocal.Warning( "Unable to create testmodel for '%s' : model defaulted", spawnArgs.GetString( "model" ) ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + mode = g_testModelAnimate.GetInteger(); + animator.RemoveOriginOffset( g_testModelAnimate.GetInteger() == 1 ); + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) ) { + spawnArgs.GetVector( "maxs", NULL, bounds[1] ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } + + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + // add the head model if it has one + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { +// RAVEN BEGIN +// ddynerman: new heads + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found for 'joint_head'", jointName.c_str() ); + } else { + // copy any sounds in case we have frame commands on the head + idDict args; + const idKeyValue *sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + args.Set( "classname", headModel ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idTestModel::Spawn() - Unknown head model '%s'\n", headModel ); + return; + } + headEnt->spawnArgs.Set( "classname", headModel ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + + headEnt->BindToJoint( this, joint, true ); + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->InitCopyJoints ( ); + + head = headEnt; +// RAVEN END + + headAnimator = head.GetEntity()->GetAnimator(); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + jointName = kv->GetKey(); + + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s'", jointName.c_str() ); + continue; + } + + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head", jointName.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + } + + // start any shader effects based off of the spawn time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + SetPhysics( &physicsObj ); + + // always keep updating so we can see the skeleton for the default pose + fl.forcePhysicsUpdate = true; + + gameLocal.Printf( "Added testmodel at origin = '%s', angles = '%s'\n", GetPhysics()->GetOrigin().ToString(), GetPhysics()->GetAxis().ToAngles().ToString() ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTestModel::~idTestModel +================ +*/ +idTestModel::~idTestModel() { + StopSound( SND_CHANNEL_ANY, false ); + if ( renderEntity.hModel ) { + gameLocal.Printf( "Removing testmodel %s\n", renderEntity.hModel->Name() ); + } else { + gameLocal.Printf( "Removing testmodel\n" ); + } + + if ( gameLocal.testmodel == this ) { + gameLocal.testmodel = NULL; + } + if ( head.GetEntity() ) { +// RAVEN BEGIN +// ddynerman: allow instant respawning of head + head.GetEntity()->SetName( va( "%s_oldhead", head.GetEntity()->name.c_str() ) ); +// RAVEN END + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + SetPhysics( NULL ); +} + +/* +=============== +idTestModel::Event_Footstep +=============== +*/ +void idTestModel::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idTestModel::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idTestModel::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +idTestModel::Think +================ +*/ +void idTestModel::Think( void ) { + idVec3 pos; + idMat3 axis; + idAngles ang; + int i; + frameBlend_t frameBlend = { 0 }; + + if ( thinkFlags & TH_THINK ) { + if ( anim && ( gameLocal.testmodel == this ) && ( mode != g_testModelAnimate.GetInteger() ) ) { + StopSound( SND_CHANNEL_ANY, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + } + switch( g_testModelAnimate.GetInteger() ) { + default: + case 0: + // cycle anim with origin reset + if ( animator.NumFrames( anim ) <= 1 ) { + // single frame animations end immediately, so just cycle it since it's the same result + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + } else { + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + animator.RemoveOriginOffset( false ); + break; + + case 1: + // cycle anim with fixed origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 2: + // cycle anim with continuous origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 3: +// RAVEN BEGIN + // frame by frame with continuous origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + + case 4: + // play anim once + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 5: +// RAVEN BEGIN + // frame by frame with fixed origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + } + + mode = g_testModelAnimate.GetInteger(); + } + + if ( ( mode == 0 ) && ( gameLocal.time >= starttime + animtime ) ) { + starttime = gameLocal.time; + StopSound( SND_CHANNEL_ANY, false ); + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + + if ( headAnimator ) { + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + idMat3 mat = head.GetEntity()->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= head.GetEntity()->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } + } + + // update rotation + RunPhysics(); + + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, idAngles( 0, g_testModelRotate.GetFloat() * 360.0f / 60.0f, 0 ), ang_zero ); + + idClipModel *clip = physicsObj.GetClipModel(); + if ( clip && animator.ModelDef() ) { + idVec3 neworigin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle( "origin" ); + animator.GetJointTransform( joint, gameLocal.time, neworigin, axis ); + neworigin = ( ( neworigin - animator.ModelDef()->GetVisualOffset() ) * physicsObj.GetAxis() ) + GetPhysics()->GetOrigin(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, neworigin, clip->GetAxis() ); +// RAVEN END + } + } + + UpdateAnimation(); + Present(); + + if ( ( gameLocal.testmodel == this ) && g_showTestModelFrame.GetInteger() && anim ) { + gameLocal.Printf( "^5 Anim: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n", animator.AnimFullName( anim ), animator.CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + animator.CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - animator.CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + if ( headAnim ) { + gameLocal.Printf( "^5 Head: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n\n", headAnimator->AnimFullName( headAnim ), headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + } else { + gameLocal.Printf( "\n\n" ); + } + } +} + +/* +================ +idTestModel::NextAnim +================ +*/ +void idTestModel::NextAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim++; + if ( anim >= animator.NumAnims() ) { + // anim 0 is no anim + anim = 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::PrevAnim +================ +*/ +void idTestModel::PrevAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + headAnim = 0; + anim--; + if ( anim < 0 ) { + anim = animator.NumAnims() - 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::NextFrame +================ +*/ +void idTestModel::NextFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame++; + if ( frame > animator.NumFrames( anim ) ) { + frame = 1; + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::PrevFrame +================ +*/ +void idTestModel::PrevFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame--; + if ( frame < 1 ) { + frame = animator.NumFrames( anim ); + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::TestAnim +================ +*/ +void idTestModel::TestAnim( const idCmdArgs &args ) { + idStr name; + int animNum; + const idAnim *newanim; + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: testanim \n" ); + return; + } + + newanim = NULL; + + name = args.Argv( 1 ); +#if 0 + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + idModelExport exporter; + exporter.ExportAnim( name ); + name.SetFileExtension( MD5_ANIM_EXT ); + md5anims[ 0 ] = animationLib.GetAnim( name ); + if ( md5anims[ 0 ] ) { + customAnim.SetAnim( animator.ModelDef(), name, name, 1, md5anims ); + newanim = &customAnim; + } + } else { + animNum = animator.GetAnim( name ); + } +#else + animNum = animator.GetAnim( name ); +#endif + + if ( !animNum ) { + gameLocal.Printf( "Animation '%s' not found.\n", name.c_str() ); + return; + } + + anim = animNum; + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + if ( !headAnim ) { + gameLocal.Printf( "Missing 'idle' anim for head.\n" ); + } + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + animname = name; + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +===================== +idTestModel::BlendAnim +===================== +*/ +void idTestModel::BlendAnim( const idCmdArgs &args ) { + int anim1; + int anim2; + + if ( args.Argc() < 4 ) { + gameLocal.Printf( "usage: testblend \n" ); + return; + } + + anim1 = gameLocal.testmodel->animator.GetAnim( args.Argv( 1 ) ); + if ( !anim1 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 1 ) ); + return; + } + + anim2 = gameLocal.testmodel->animator.GetAnim( args.Argv( 2 ) ); + if ( !anim2 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 2 ) ); + return; + } + + animname = args.Argv( 2 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim1, gameLocal.time, 0 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, FRAME2MS( atoi( args.Argv( 3 ) ) ) ); + + anim = anim2; + headAnim = 0; +} + +/*********************************************************************** + + Testmodel console commands + +***********************************************************************/ + +/* +================= +idTestModel::KeepTestModel_f + +Makes the current test model permanent, allowing you to place +multiple test models +================= +*/ +void idTestModel::KeepTestModel_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel.\n" ); + return; + } + + gameLocal.Printf( "modelDef %i kept\n", gameLocal.testmodel->renderEntity.hModel ); + + gameLocal.testmodel = NULL; +} + +/* +================= +idTestModel::TestSkin_f + +Sets a skin on an existing testModel +================= +*/ +void idTestModel::TestSkin_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "removing testSkin.\n" ); + gameLocal.testmodel->SetSkin( NULL ); + return; + } + + name = args.Argv( 1 ); + gameLocal.testmodel->SetSkin( declManager->FindSkin( name ) ); +} + +/* +================= +idTestModel::TestShaderParm_f + +Sets a shaderParm on an existing testModel +================= +*/ +void idTestModel::TestShaderParm_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() != 3 ) { + gameLocal.Printf( "USAGE: testShaderParm \n" ); + return; + } + + int parm = atoi( args.Argv( 1 ) ); + if ( parm < 0 || parm >= MAX_ENTITY_SHADER_PARMS ) { + gameLocal.Printf( "parmNum %i out of range\n", parm ); + return; + } + + float value; + if ( !stricmp( args.Argv( 2 ), "time" ) ) { + value = gameLocal.time * -0.001; + } else { + value = atof( args.Argv( 2 ) ); + } + + gameLocal.testmodel->SetShaderParm( parm, value ); +} + +/* +================= +idTestModel::TestModel_f + +Creates a static modelDef in front of the current position, which +can then be moved around +================= +*/ +void idTestModel::TestModel_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + const idDict * entityDef; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testmodel ) { + delete gameLocal.testmodel; + gameLocal.testmodel = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + entityDef = gameLocal.FindEntityDefDict( name, false ); + if ( entityDef ) { + dict = *entityDef; + } else { + if ( declManager->FindType( DECL_MODELDEF, name, false ) ) { + dict.Set( "model", name ); + } else { + // allow map models with underscore prefixes to be tested during development + // without appending an ase + if ( name[ 0 ] != '_' ) { + name.DefaultFileExtension( ".ase" ); + } + + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + idModelExport exporter; + exporter.ExportModel( name ); + name.SetFileExtension( MD5_MESH_EXT ); + } + + if ( !renderModelManager->CheckModel( name ) ) { + gameLocal.Printf( "Can't register model\n" ); + return; + } + dict.Set( "model", name ); + } + } + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "angle", va( "%f", player->viewAngles.yaw + 180.0f ) ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + gameLocal.testmodel = ( idTestModel * )gameLocal.SpawnEntityType( idTestModel::GetClassType(), &dict ); +// RAVEN END + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); +} + +/* +===================== +idTestModel::ArgCompletion_TestModel +===================== +*/ +void idTestModel::ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i, num; + + num = declManager->GetNumDecls( DECL_ENTITYDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_ENTITYDEF, i , false )->GetName() ); + } + num = declManager->GetNumDecls( DECL_MODELDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_MODELDEF, i , false )->GetName() ); + } + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", ".mb", NULL ); +} + +/* +===================== +idTestModel::TestParticleStopTime_f +===================== +*/ +void idTestModel::TestParticleStopTime_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + gameLocal.testmodel->UpdateVisuals(); +} + +/* +===================== +idTestModel::TestAnim_f +===================== +*/ +void idTestModel::TestAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->TestAnim( args ); +} + + +/* +===================== +idTestModel::ArgCompletion_TestAnim +===================== +*/ +void idTestModel::ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ) { + if ( gameLocal.testmodel ) { + idAnimator *animator = gameLocal.testmodel->GetAnimator(); + for( int i = 0; i < animator->NumAnims(); i++ ) { + callback( va( "%s %s", args.Argv( 0 ), animator->AnimFullName( i ) ) ); + } + } +} + +/* +===================== +idTestModel::TestBlend_f +===================== +*/ +void idTestModel::TestBlend_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->BlendAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextAnim_f +===================== +*/ +void idTestModel::TestModelNextAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextAnim( args ); +} + +/* +===================== +idTestModel::TestModelPrevAnim_f +===================== +*/ +void idTestModel::TestModelPrevAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextFrame_f +===================== +*/ +void idTestModel::TestModelNextFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextFrame( args ); +} + +/* +===================== +idTestModel::TestModelPrevFrame_f +===================== +*/ +void idTestModel::TestModelPrevFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevFrame( args ); +} diff --git a/source/game/anim/Anim_Testmodel.h b/source/game/anim/Anim_Testmodel.h new file mode 100644 index 0000000..3a9f189 --- /dev/null +++ b/source/game/anim/Anim_Testmodel.h @@ -0,0 +1,71 @@ + +#ifndef __ANIM_TESTMODEL_H__ +#define __ANIM_TESTMODEL_H__ + +/* +============================================================================================== + + idTestModel + +============================================================================================== +*/ + +class idTestModel : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idTestModel ); + + idTestModel(); + ~idTestModel(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + + void NextAnim( const idCmdArgs &args ); + void PrevAnim( const idCmdArgs &args ); + void NextFrame( const idCmdArgs &args ); + void PrevFrame( const idCmdArgs &args ); + void TestAnim( const idCmdArgs &args ); + void BlendAnim( const idCmdArgs &args ); + + static void KeepTestModel_f( const idCmdArgs &args ); + static void TestModel_f( const idCmdArgs &args ); + static void ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestSkin_f( const idCmdArgs &args ); + static void TestShaderParm_f( const idCmdArgs &args ); + static void TestParticleStopTime_f( const idCmdArgs &args ); + static void TestAnim_f( const idCmdArgs &args ); + static void ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestBlend_f( const idCmdArgs &args ); + static void TestModelNextAnim_f( const idCmdArgs &args ); + static void TestModelPrevAnim_f( const idCmdArgs &args ); + static void TestModelNextFrame_f( const idCmdArgs &args ); + static void TestModelPrevFrame_f( const idCmdArgs &args ); + +private: +// RAVEN BEGIN +// ddynerman: new head system + idEntityPtr head; +// RAVEN END + idAnimator *headAnimator; + idAnim customAnim; + idPhysics_Parametric physicsObj; + idStr animname; + int anim; + int headAnim; + int mode; + int frame; + int starttime; + int animtime; + + idList copyJoints; + + virtual void Think( void ); + + void Event_Footstep( void ); +}; + +#endif /* !__ANIM_TESTMODEL_H__*/ diff --git a/source/game/client/ClientAFEntity.cpp b/source/game/client/ClientAFEntity.cpp new file mode 100644 index 0000000..f543971 --- /dev/null +++ b/source/game/client/ClientAFEntity.cpp @@ -0,0 +1,696 @@ +//---------------------------------------------------------------- +// ClientAFEntity.cpp +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + +rvClientAFEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAnimatedClientEntity, rvClientAFEntity ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +rvClientAFEntity::rvClientAFEntity +================ +*/ +rvClientAFEntity::rvClientAFEntity( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); +} + + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +rvClientAFEntity::~rvClientAFEntity( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +rvClientAFEntity::Spawn +================ +*/ +void rvClientAFEntity::Spawn( void ) { + spawnOrigin = worldOrigin; + spawnAxis = worldAxis; + nextSoundTime = 0; + + //if ( !LoadAF() ) { + // gameLocal.Error( "Couldn't load af file on entity %d", entityNumber ); + //} + + SetCombatModel(); + + //af.GetPhysics()->PutToRest(); + //if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + // af.GetPhysics()->Activate(); + //} +} + +/* +================ +rvClientAFEntity::LoadAF +================ +*/ +bool rvClientAFEntity::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + + idDict args = gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs; + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = spawnArgs; + + if ( !af.Load( gameLocal.entities[ ENTITYNUM_CLIENT ], fileName ) ) { + gameLocal.Error( "rvClientAFEntity::LoadAF: Couldn't load af file '%s' on client entity %d", fileName.c_str(), entityNumber ); + } + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = args; + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +rvClientAFEntity::Think +================ +*/ +void rvClientAFEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + + Present(); + LinkCombat(); +} + +/* +================ +rvClientAFEntity::BodyForClipModelId +================ +*/ +int rvClientAFEntity::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +rvClientAFEntity::SaveState +================ +*/ +void rvClientAFEntity::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +rvClientAFEntity::LoadState +================ +*/ +void rvClientAFEntity::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +rvClientAFEntity::AddBindConstraints +================ +*/ +void rvClientAFEntity::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +rvClientAFEntity::RemoveBindConstraints +================ +*/ +void rvClientAFEntity::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +rvClientAFEntity::GetImpactInfo +================ +*/ +void rvClientAFEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + rvClientEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFEntity::ApplyImpulse +================ +*/ +void rvClientAFEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + rvClientEntity::ApplyImpulse( ent, id, point, impulse, splash ); + } +} + +/* +================ +rvClientAFEntity::AddForce +================ +*/ +void rvClientAFEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + rvClientEntity::AddForce( ent, id, point, force ); + } +} + +bool rvClientAFEntity::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + // stubbed out + return true; +} + +/* +================ +rvClientAFEntity::Collide +================ +*/ +bool rvClientAFEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + // RAVEN BEGIN + // jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } + // RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +rvClientAFEntity::GetPhysicsToVisualTransform +================ +*/ +bool rvClientAFEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return rvClientModel::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +rvClientAFEntity::UpdateAnimationControllers +================ +*/ +bool rvClientAFEntity::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +rvClientAFEntity::SetCombatModel +================ +*/ +void rvClientAFEntity::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } +} + +/* +================ +rvClientAFEntity::GetCombatModel +================ +*/ +idClipModel *rvClientAFEntity::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFEntity::SetCombatContents +================ +*/ +void rvClientAFEntity::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +rvClientAFEntity::LinkCombat +================ +*/ +void rvClientAFEntity::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFEntity::UnlinkCombat +================ +*/ +void rvClientAFEntity::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +rvClientAFEntity::FreeEntityDef +================ +*/ +void rvClientAFEntity::FreeEntityDef( void ) { + UnlinkCombat(); + rvClientEntity::FreeEntityDef(); +} + +/* +================ +rvClientAFEntity::GetNoPlayerImpactFX +================ +*/ +bool rvClientAFEntity::GetNoPlayerImpactFX( void ) { + // stubbed out + return false; +} + + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientAFEntity, rvClientAFAttachment ) +END_CLASS + +/* +===================== +rvClientAFAttachment::rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::rvClientAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; +} + +/* +===================== +rvClientAFAttachment::~rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::~rvClientAFAttachment( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +===================== +rvClientAFAttachment::Spawn +===================== +*/ +void rvClientAFAttachment::Spawn( void ) { + idleAnim = animator.GetAnim( "idle" ); +} + +/* +===================== +rvClientAFAttachment::InitCopyJoints +===================== +*/ +void rvClientAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on client entity %d", jointName, entityNumber ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %d", kv->GetValue().c_str(), entityNumber ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +rvClientAFAttachment::CopyJointsFromBody +===================== +*/ +void rvClientAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +rvClientAFAttachment::SetBody +===================== +*/ +void rvClientAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +rvClientAFAttachment::ClearBody +===================== +*/ +void rvClientAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +rvClientAFAttachment::GetBody +===================== +*/ +idEntity *rvClientAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void rvClientAFAttachment::Hide( void ) { + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void rvClientAFAttachment::Show( void ) { + LinkCombat(); +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void rvClientAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +rvClientAFAttachment::GetImpactInfo +================ +*/ +void rvClientAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + rvClientAFAttachment::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFAttachment::CanPlayImpactEffect +================ +*/ +bool rvClientAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + return rvClientAFEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +rvClientAFAttachment::ApplyImpulse +================ +*/ +void rvClientAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + rvClientAFEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +rvClientAFAttachment::AddForce +================ +*/ +void rvClientAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + rvClientAFEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +rvClientAFAttachment::PlayIdleAnim +================ +*/ +void rvClientAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +} + +/* +================ +rvClientAFAttachment::Think +================ +*/ +void rvClientAFAttachment::Think( void ) { + rvClientAFEntity::Think(); +} + +/* +================ +rvClientAFAttachment::UpdateAnimationControllers +================ +*/ +bool rvClientAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return rvClientAFEntity::UpdateAnimationControllers( ); +} + +/* +================ +rvClientAFAttachment::SetCombatModel +================ +*/ +void rvClientAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } + combatModel->SetOwner( body ); +} + +/* +================ +rvClientAFAttachment::GetCombatModel +================ +*/ +idClipModel *rvClientAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFAttachment::LinkCombat +================ +*/ +void rvClientAFAttachment::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFAttachment::UnlinkCombat +================ +*/ +void rvClientAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} diff --git a/source/game/client/ClientAFEntity.h b/source/game/client/ClientAFEntity.h new file mode 100644 index 0000000..6894533 --- /dev/null +++ b/source/game/client/ClientAFEntity.h @@ -0,0 +1,126 @@ +//---------------------------------------------------------------- +// ClientAFEntity.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_AFENTITY_H__ +#define __GAME_CLIENT_AFENTITY_H__ + +/* +=============================================================================== + +rvClientAFEntity - a regular idAFEntity_Base spawned client-side + +=============================================================================== +*/ + +class rvClientAFEntity : public rvAnimatedClientEntity { +public: + CLASS_PROTOTYPE( rvClientAFEntity ); + + rvClientAFEntity( void ); + virtual ~rvClientAFEntity( void ); + + void Spawn( void ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeEntityDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound +}; + + +/* +=============================================================================== + +rvClientAFAttachment - a regular idAFAttachment spawned client-side - links to an + idAnimatedEntity rather than rvClientAnimatedEntity + +=============================================================================== +*/ +class rvClientAFAttachment : public rvClientAFEntity { +public: + CLASS_PROTOTYPE( rvClientAFAttachment ); + + rvClientAFAttachment( void ); + virtual ~rvClientAFAttachment( void ); + + void Spawn( void ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + +protected: + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + idList copyJoints; // copied from the body animation to the head model +}; + +#endif diff --git a/source/game/client/ClientEffect.cpp b/source/game/client/ClientEffect.cpp new file mode 100644 index 0000000..b734c13 --- /dev/null +++ b/source/game/client/ClientEffect.cpp @@ -0,0 +1,497 @@ +//---------------------------------------------------------------- +// ClientEffect.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientEffect.h" + +/* +=============================================================================== + +rvClientEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEntity, rvClientEffect ) +END_CLASS + +/* +================ +rvClientEffect::rvClientEffect +================ +*/ +rvClientEffect::rvClientEffect ( void ) { + Init( NULL ); + Spawn(); +} + +rvClientEffect::rvClientEffect ( const idDecl *effect ) { + Init( effect ); + Spawn(); +} + +void rvClientEffect::Init ( const idDecl *effect ) { + memset( &renderEffect, 0, sizeof( renderEffect ) ); + + renderEffect.declEffect = effect; + renderEffect.startTime = -1.0f; + renderEffect.referenceSoundHandle = -1; + effectDefHandle = -1; + endOriginJoint = INVALID_JOINT; +} + +/* +================ +rvClientEffect::~rvClientEffect +================ +*/ +rvClientEffect::~rvClientEffect( void ) { + FreeEffectDef( ); + // Prevent a double free of a SoundEmitter resulting in broken in-game sounds, when + // the second free releases a emitter that was reallocated to another sound. rvBSE caches + // this referenceSoundHandle and rvBSE::Destroy also frees the sound. rvBSE::Destroy + // is triggered by FreeEffectDef. Disable this free and let rvBSE do the releasing + + // Actually, the freeing should be done here and not in BSE. The client effect allocates and + // maintains the handle. Handling this here also allows emitters to be recycled for sparse + // looping effects. + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, renderEffect.referenceSoundHandle, true ); + renderEffect.referenceSoundHandle = -1; +} + +/* +================ +rvClientEffect::GetEffectIndex +================ +*/ +int rvClientEffect::GetEffectIndex( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->Index() ); + } + return( -1 ); +} + +/* +================ +rvClientEffect::GetEffectName +================ +*/ +const char *rvClientEffect::GetEffectName( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->GetName() ); + } + return( "unknown" ); +} + +/* +================ +rvClientEffect::FreeEffectDef +================ +*/ +void rvClientEffect::FreeEffectDef ( void ) { + if ( effectDefHandle != -1 && gameRenderWorld ) { + gameRenderWorld->FreeEffectDef( effectDefHandle ); + } + effectDefHandle = -1; +} + +/* +================ +rvClientEffect::UpdateBind +================ +*/ +void rvClientEffect::UpdateBind ( void ) { + rvClientEntity::UpdateBind ( ); + + renderEffect.origin = worldOrigin; + + if ( endOriginJoint != INVALID_JOINT && bindMaster ) { + idMat3 axis; + idVec3 endOrigin; + idVec3 dir; + + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( endOriginJoint, gameLocal.time, endOrigin, axis ); + SetEndOrigin ( endOrigin ); + + dir = (endOrigin - worldOrigin); + dir.Normalize (); + renderEffect.axis = dir.ToMat3 ( ); + } else { + renderEffect.axis = worldAxis; + } + + if ( bindMaster ) { + renderEffect.groupID = bindMaster->entityNumber + 1; + } else { + renderEffect.groupID = 0; + } +} + +/* +================ +rvClientEffect::Think +================ +*/ +void rvClientEffect::Think ( void ) { + // If we are bound to an entity that is now hidden we can just not render if looping, otherwise stop the effect + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + if ( renderEffect.loop ) { + return; + } + Stop ( ); + } + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_EFFECT); +// RAVEN END + // If there is a valid effect handle and we havent started playing + // and effect yet then see if its time + if( effectDefHandle < 0 && renderEffect.declEffect ) { + if( renderEffect.startTime >= 0.0f ) { + // Make sure our origins are all straight before starting the effect + UpdateBind(); + renderEffect.attenuation = 1.0f; + + // if the rendereffect needs sound give it an emitter. + if( renderEffect.referenceSoundHandle <= 0 ) { + if( gameRenderWorld->EffectDefHasSound( &renderEffect) ) + { + renderEffect.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } else { + renderEffect.referenceSoundHandle = -1; + } + } + // Add the render effect + effectDefHandle = gameRenderWorld->AddEffectDef( &renderEffect, gameLocal.time ); + if ( effectDefHandle < 0 ) { + PostEventMS( &EV_Remove, 0 ); + } + } + + return; + } + + // If we lost our effect def handle then just remove ourself + if( effectDefHandle < 0 ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Dont do anything else if its not a new client frame + if( !gameLocal.isNewFrame ) { + return; + } + + // Check to see if the player can possibly see the effect or not + renderEffect.inConnectedArea = true; + if( bindMaster ) { + renderEffect.inConnectedArea = gameLocal.InPlayerConnectedArea( bindMaster ); + } + + // Update the bind + UpdateBind(); + + // Update the actual render effect now + if( gameRenderWorld->UpdateEffectDef( effectDefHandle, &renderEffect, gameLocal.time ) ) { + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + return; + } +} + +/* +================ +rvClientEffect::Play +================ +*/ +bool rvClientEffect::Play ( int _startTime, bool _loop, const idVec3& endOrigin ) { + if ( !renderEffect.declEffect ) { + return false; + } + + // Initialize the render entity + if ( bindMaster ) { + renderEntity_t* renderEnt = bindMaster->GetRenderEntity ( ); + assert( renderEnt ); + + // Copy suppress values from parent entity + renderEffect.allowSurfaceInViewID = renderEnt->allowSurfaceInViewID; + renderEffect.suppressSurfaceInViewID = renderEnt->suppressSurfaceInViewID; + renderEffect.weaponDepthHackInViewID = renderEnt->weaponDepthHackInViewID; + } + + renderEffect.shaderParms[SHADERPARM_RED] = 1.0f; + renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f; + renderEffect.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + renderEffect.hasEndOrigin = ( endOrigin != vec3_origin ); + renderEffect.endOrigin = endOrigin; + renderEffect.loop = _loop; + + assert( effectDefHandle < 0 ); + + renderEffect.startTime = MS2SEC( _startTime ); + + return true; +} + +/* +================ +rvClientEffect::Stop +================ +*/ +void rvClientEffect::Stop ( bool destroyParticles ) { + if( effectDefHandle < 0 ) { + renderEffect.startTime = -1.0f; + renderEffect.declEffect = NULL; + return; + } + + if ( destroyParticles ) { + // Clear the effect index to make sure the effect isnt started again. This is + // an indirect way of making the effect not think + renderEffect.declEffect = NULL; + + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + gameRenderWorld->StopEffectDef( effectDefHandle ); + // this will ensure the effect doesn't re-up when loaded from a save. + renderEffect.startTime = -1.0f; + Unbind ( ); + } +} + +/* +================ +rvClientEffect::Restart +================ +*/ +void rvClientEffect::Restart ( void ) { + FreeEffectDef ( ); + + if ( renderEffect.loop ) { + Play ( gameLocal.time, true, renderEffect.endOrigin ); + } +} + +/* +================ +rvClientEffect::Attenuate +================ +*/ +void rvClientEffect::Attenuate ( float attenuation ) { + renderEffect.attenuation = attenuation; +} + +/* +================ +rvClientEffect::GetDuration +================ +*/ +float rvClientEffect::GetDuration( void ) const { + if( effectDefHandle < 0 ) { + return 0.0f; + } + + return bse->EffectDuration( gameRenderWorld->GetEffectDef( effectDefHandle ) ); +} + +/* +================ +rvClientEffect::DrawDebugInfo +================ +*/ +void rvClientEffect::DrawDebugInfo ( void ) const { + rvClientEntity::DrawDebugInfo ( ); + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + if( !renderEffect.declEffect ) { + return; + } + + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + gameRenderWorld->DrawText ( renderEffect.declEffect->GetName(), worldOrigin + up, 0.1f, colorWhite, axis, 1 ); +} + +/* +================ +rvClientEffect::Save +================ +*/ +void rvClientEffect::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEffect( renderEffect ); + savefile->WriteJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::Restore +================ +*/ +void rvClientEffect::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEffect( renderEffect ); + effectDefHandle = -1; + savefile->ReadJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::FreeEntityDef +================ +*/ +void rvClientEffect::FreeEntityDef( void ) { + FreeEffectDef(); +} + +/* +=============================================================================== + +rvClientCrawlEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEffect, rvClientCrawlEffect ) +END_CLASS + +/* +================ +rvClientCrawlEffect::rvClientCrawlEffect +================ +*/ +rvClientCrawlEffect::rvClientCrawlEffect ( void ) { +} + +rvClientCrawlEffect::rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int _crawlTime, idList* joints ) : rvClientEffect ( effect ) { + int i; + + // Crawl effects require an animated entity + crawlEnt = dynamic_cast(ent); + if ( !crawlEnt) { + return; + } + + // Specific joint list provided? + if ( joints && joints->Num () ) { + crawlJoints.Clear ( ); + for ( i = 0; i < joints->Num(); i ++ ) { + crawlJoints.Append ( (*joints)[i] ); + } + } else { + // Use only parent joints and skip joint zero which is presumed to be the origin + for ( i = ent->GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != ent->GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + crawlJoints.Append ( (jointHandle_t)i ); + } + } + } + + //no joints? abort! + if ( !crawlJoints.Num() ) { + return; + } + + jointStart = gameLocal.random.RandomInt ( crawlJoints.Num() ); + crawlDir = gameLocal.random.RandomInt ( 2 ) > 0 ? 1 : -1; + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + crawlTime = _crawlTime; + nextCrawl = gameLocal.time + crawlTime; +} + +/* +================ +rvClientCrawlEffect::Think +================ +*/ +void rvClientCrawlEffect::Think ( void ) { + + // If there is no crawl entity or no crawl joints then just free ourself + if ( !crawlEnt || !crawlJoints.Num() ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Move to the next joint if its time + if ( gameLocal.time > nextCrawl ) { + jointStart = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + nextCrawl = gameLocal.time + crawlTime; + } + + idVec3 offsetStart; + idVec3 offsetEnd; + idVec3 dir; + idMat3 axis; + + // Get the start origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointStart], gameLocal.time, offsetStart, axis ); + SetOrigin ( offsetStart ); + + // Get the end origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointEnd], gameLocal.time, offsetEnd, axis ); + SetEndOrigin ( offsetEnd ); + + // Update the axis to point at the bone + dir = offsetEnd - offsetStart; + dir.Normalize(); + SetAxis ( dir.ToMat3( ) ); + + rvClientEffect::Think ( ); +} + +/* +================ +rvClientCrawlEffect::Save +================ +*/ +void rvClientCrawlEffect::Save( idSaveGame *savefile ) const { + savefile->WriteInt( crawlJoints.Num() ); + for( int ix = 0; ix < crawlJoints.Num(); ++ix ) { + savefile->WriteJoint( crawlJoints[ix] ); + } + + savefile->WriteInt( crawlTime ); + savefile->WriteInt( nextCrawl ); + savefile->WriteInt( jointStart ); + savefile->WriteInt( jointEnd ); + savefile->WriteInt( crawlDir ); + crawlEnt.Save( savefile ); +} + +/* +================ +rvClientCrawlEffect::Restore +================ +*/ +void rvClientCrawlEffect::Restore( idRestoreGame *savefile ) { + int numJoints = 0; + savefile->ReadInt( numJoints ); + crawlJoints.SetNum( numJoints ); + for( int ix = 0; ix < numJoints; ++ix ) { + savefile->ReadJoint( crawlJoints[ix] ); + } + + savefile->ReadInt( crawlTime ); + savefile->ReadInt( nextCrawl ); + savefile->ReadInt( jointStart ); + savefile->ReadInt( jointEnd ); + savefile->ReadInt( crawlDir ); + crawlEnt.Restore( savefile ); +} diff --git a/source/game/client/ClientEffect.h b/source/game/client/ClientEffect.h new file mode 100644 index 0000000..58d600e --- /dev/null +++ b/source/game/client/ClientEffect.h @@ -0,0 +1,102 @@ +//---------------------------------------------------------------- +// ClientEffect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_EFFECT_H__ +#define __GAME_CLIENT_EFFECT_H__ + +class rvClientEffect : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientEffect ); + + rvClientEffect( void ); + rvClientEffect( const idDecl *effect ); + virtual ~rvClientEffect( void ); + + virtual void Think ( void ); + virtual void DrawDebugInfo ( void ) const; + virtual void FreeEntityDef ( void ); + virtual void UpdateBind ( void ); + + bool Play ( int startTime, bool loop = false, const idVec3& origin = vec3_origin ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + int GetEffectIndex ( void ); + const char * GetEffectName ( void ); + + void Attenuate ( float attenuation ); + + float GetDuration ( void ) const; + renderEffect_t* GetRenderEffect ( void ) { return &renderEffect; } + + void SetEndOrigin ( const idVec3& endOrigin ); + void SetEndOrigin ( jointHandle_t joint ) { endOriginJoint = joint; } + + void SetGravity ( const idVec3& gravity ) { renderEffect.gravity = gravity; } + + void SetColor ( const idVec4& color ); + void SetBrightness ( float brightness ) { renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = brightness; } + void SetAmbient ( bool in ) { renderEffect.ambient = in; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +protected: + + void Init ( const idDecl *effect ); + void FreeEffectDef ( void ); + + renderEffect_t renderEffect; + int effectDefHandle; + jointHandle_t endOriginJoint; +}; + +ID_INLINE void rvClientEffect::SetEndOrigin ( const idVec3& endOrigin ) { + renderEffect.endOrigin = endOrigin; + renderEffect.hasEndOrigin = !(endOrigin == vec3_origin); +} + +ID_INLINE void rvClientEffect::SetColor ( const idVec4& color ) { + renderEffect.shaderParms[SHADERPARM_RED] = color[0]; + renderEffect.shaderParms[SHADERPARM_GREEN] = color[1]; + renderEffect.shaderParms[SHADERPARM_BLUE] = color[2]; + renderEffect.shaderParms[SHADERPARM_ALPHA] = color[3]; +} + +//---------------------------------------------------------------- +// rvClientCrawlEffect +//---------------------------------------------------------------- + +class idAnimatedEntity; + +class rvClientCrawlEffect : public rvClientEffect { +public: + + CLASS_PROTOTYPE( rvClientCrawlEffect ); + + rvClientCrawlEffect ( void ); + rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int crawlTime, idList* joints = NULL ); + ~rvClientCrawlEffect ( void ) {} + + virtual void Think ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + idList crawlJoints; + int crawlTime; + int nextCrawl; + int jointStart; + int jointEnd; + int crawlDir; + idEntityPtr crawlEnt; +}; + +typedef rvClientEntityPtr rvClientEffectPtr; + +#endif // __GAME_CLIENT_EFFECT_H__ diff --git a/source/game/client/ClientEntity.cpp b/source/game/client/ClientEntity.cpp new file mode 100644 index 0000000..8929907 --- /dev/null +++ b/source/game/client/ClientEntity.cpp @@ -0,0 +1,651 @@ +//---------------------------------------------------------------- +// ClientEntity.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, rvClientEntity ) +END_CLASS + +/* +================ +rvClientEntity::rvClientEntity +================ +*/ +rvClientEntity::rvClientEntity( void ) { + bindMaster = NULL; + entityNumber = -1; + + bindOrigin.Zero(); + bindAxis.Identity(); + bindJoint = INVALID_JOINT; + bindOrientated = false; + + memset( &refSound, 0, sizeof(refSound) ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::~rvClientEntity +================ +*/ +rvClientEntity::~rvClientEntity( void ) { + Unbind(); + gameLocal.UnregisterClientEntity( this ); + + // Free sound emitter + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::Spawn +================ +*/ +void rvClientEntity::Spawn( void ) { + idVec3 origin; + idMat3 axis; + + gameLocal.RegisterClientEntity( this ); + + spawnNode.SetOwner( this ); + bindNode.SetOwner( this ); + + origin = spawnArgs.GetVector( "origin", "0 0 0" ); + axis = spawnArgs.GetMatrix( "axis", "1 0 0 0 1 0 0 0 1" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); +} + +/* +================ +rvClientEntity::Present +================ +*/ +void rvClientEntity::Present ( void ) { +} + +/* +================ +rvClientEntity::FreeEntityDef +================ +*/ +void rvClientEntity::FreeEntityDef ( void ) { +} + +/* +================ +rvClientEntity::Think +================ +*/ +void rvClientEntity::Think ( void ) { + UpdateBind(); + UpdateSound(); + Present(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Bind ( idEntity* master, jointHandle_t joint, bool isOrientated ) { + Unbind(); + + if ( joint != INVALID_JOINT && !dynamic_cast(master) ) { + gameLocal.Warning( "rvClientEntity::Bind: entity '%s' cannot support skeletal models.", master->GetName() ); + joint = INVALID_JOINT; + } + + bindMaster = master; + bindJoint = joint; + bindOrigin = worldOrigin; + bindAxis = worldAxis; + + bindNode.AddToEnd ( bindMaster->clientEntities ); + + bindOrientated = isOrientated; + if( physics ) { + physics->SetMaster( bindMaster, bindOrientated ); + } + + UpdateBind(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Unbind ( void ) { + if ( !bindMaster ) { + return; + } + + bindMaster = NULL; + bindNode.Remove ( ); +} + +/* +================ +rvClientEntity::SetOrigin +================ +*/ +void rvClientEntity::SetOrigin( const idVec3& origin ) { + if ( bindMaster ) { + bindOrigin = origin; + } else { + worldOrigin = origin; + } +} + +/* +================ +rvClientEntity::SetAxis +================ +*/ +void rvClientEntity::SetAxis( const idMat3& axis ) { + if ( bindMaster ) { + bindAxis = axis * bindMaster->GetRenderEntity()->axis.Transpose(); + } else { + worldAxis = axis; + } +} + +/* +================ +rvClientEntity::UpdateBind +================ +*/ +void rvClientEntity::UpdateBind ( void ) { + if ( !bindMaster ) { + return; + } + + if ( bindJoint != INVALID_JOINT ) { + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( bindJoint, gameLocal.time, worldOrigin, worldAxis ); + } else { + bindMaster->GetPosition( worldOrigin, worldAxis ); + //if ( !bindMaster->GetPhysicsToVisualTransform( worldOrigin, worldAxis ) ) { + // bindMaster->GetPosition( worldOrigin, worldAxis ); + //} + } + + worldOrigin += (bindOrigin * worldAxis); + worldAxis = bindAxis * worldAxis; +} + +/* +================ +rvClientEntity::IsClient +================ +*/ +bool rvClientEntity::IsClient ( void ) const { + return true; +} + +/* +================ +rvClientEntity::DrawDebugInfo +================ +*/ +void rvClientEntity::DrawDebugInfo ( void ) const { + idBounds bounds ( idVec3(-8,-8,-8), idVec3(8,8,8) ); + + gameRenderWorld->DebugBounds ( colorGreen, bounds, worldOrigin ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText ( GetClassname ( ), worldOrigin, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } +} + +/* +================ +rvClientEntity::UpdateSound +================ +*/ +void rvClientEntity::UpdateSound( void ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } +} + +/* +================ +rvClientEntity::SetSoundVolume +================ +*/ +void rvClientEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + + +/* +================ +rvClientEntity::StartSound +================ +*/ +bool rvClientEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags ); +} + + +/* +================ +rvClientEntity::StartSoundShader +================ +*/ +int rvClientEntity::StartSoundShader ( const idSoundShader* shader, const s_channelType channel, int soundShaderFlags ) { + if ( !shader ) { + return 0; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return( 0 ); + } + + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + return emitter->StartSound( shader, channel, gameLocal.random.RandomFloat(), soundShaderFlags ); +} + +/* +================ +rvClientEntity::Size + +Returns Returns memory size of an rvClientEntity. +================ +*/ + +size_t rvClientEntity::Size ( void ) const { + return sizeof( rvClientEntity ); +} + +/* +================ +rvClientEntity::Save +================ +*/ +void rvClientEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->WriteVec3( worldOrigin ); + savefile->WriteVec3( worldVelocity ); + savefile->WriteMat3( worldAxis ); + + bindMaster.Save( savefile ); + savefile->WriteVec3( bindOrigin ); + savefile->WriteMat3( bindAxis ); + savefile->WriteJoint( bindJoint ); + + savefile->WriteRefSound( refSound ); +} + +/* +================ +rvClientEntity::Restore +================ +*/ +void rvClientEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->ReadVec3( worldOrigin ); + savefile->ReadVec3( worldVelocity ); + savefile->ReadMat3( worldAxis ); + + bindMaster.Restore( savefile ); + savefile->ReadVec3( bindOrigin ); + savefile->ReadMat3( bindAxis ); + savefile->ReadJoint( bindJoint ); + + savefile->ReadRefSound( refSound ); +} + +/* +================ +rvClientEntity::RunPhysics +================ +*/ +void rvClientEntity::RunPhysics ( void ) { + idPhysics* physics = GetPhysics( ); + if( !physics ) { + return; + } + + rvClientPhysics* clientPhysics = (rvClientPhysics*)gameLocal.entities[ENTITYNUM_CLIENT]; + static_cast( clientPhysics )->currentEntityNumber = entityNumber; + + // order important: 1) set client physics bind master to client ent's bind master + // 2) set physics to client ent's physics, which sets physics + // master to client ent's master + // 3) set client physics origin to client ent origin, depends on + // proper bind master from 1 + clientPhysics->PushBindInfo( bindMaster, bindJoint, bindOrientated ); + clientPhysics->SetPhysics( physics ); + clientPhysics->PushOriginInfo( bindMaster ? bindOrigin : worldOrigin, bindMaster ? bindAxis : worldAxis ); + + physics->Evaluate ( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + worldOrigin = physics->GetOrigin(); + worldVelocity = physics->GetLinearVelocity(); + worldAxis = physics->GetAxis(); + + // order important: 1) restore previous bind master + // 2) reset physics with previous bind master + // 3) reset origin with previous bind master + clientPhysics->PopBindInfo(); + clientPhysics->SetPhysics( NULL ); + clientPhysics->PopOriginInfo(); + + UpdateAnimationControllers(); +} + +/* +================ +rvClientEntity::GetPhysics +================ +*/ +idPhysics* rvClientEntity::GetPhysics ( void ) const { + return physics; +} + +/* +================ +rvClientEntity::Collide +================ +*/ +bool rvClientEntity::Collide ( const trace_t &collision, const idVec3 &velocity ) { + return false; +} + +/* +================ +rvClientEntity::GetImpactInfo +================ +*/ +void rvClientEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +rvClientEntity::ApplyImpulse +================ +*/ +void rvClientEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +rvClientEntity::AddForce +================ +*/ +void rvClientEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +rvClientEntity::UpdateAnimationControllers +================ +*/ +bool rvClientEntity::UpdateAnimationControllers( void ) { + return false; +} + +/* +================ +rvClientEntity::InitDefaultPhysics +================ +*/ +void rvClientEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( temp ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on client entity '%d'", bounds[0].ToString(), bounds[1].ToString(), entityNumber ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on client entity '%d'", size.ToString(), entityNumber ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); + // RAVEN BEGIN + // bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); + // RAVEN END + } else { + trm.SetupBox( bounds ); + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( trm ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { + // RAVEN BEGIN + // jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( gameLocal.entities[ENTITYNUM_CLIENT] ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; + + // by default no collision + physics->SetContents( 0 ); +} + + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvClientPhysics ) +END_CLASS + +/* +===================== +rvClientPhysics::Spawn +===================== +*/ +void rvClientPhysics::Spawn( void ) { + pushedOrientated = false; +} + +/* +===================== +rvClientPhysics::Collide +===================== +*/ +bool rvClientPhysics::Collide( const trace_t &collision, const idVec3 &velocity ) { + assert ( currentEntityNumber >= 0 && currentEntityNumber < MAX_CENTITIES ); + + rvClientEntity* cent; + cent = gameLocal.clientEntities [ currentEntityNumber ]; + if ( cent ) { + return cent->Collide ( collision, velocity ); + } + + return false; +} + +/* +===================== +rvClientPhysics::PushBindInfo +===================== +*/ +void rvClientPhysics::PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ) { + pushedBindJoint = joint; + pushedBindMaster = master; + pushedOrientated = fl.bindOrientated; + + bindMaster = master; + bindJoint = joint; + fl.bindOrientated = orientated; +} + +/* +===================== +rvClientPhysics::PopBindInfo +===================== +*/ +void rvClientPhysics::PopBindInfo( void ) { + bindMaster = pushedBindMaster; + bindJoint = pushedBindJoint; + fl.bindOrientated = pushedOrientated; +} + +/* +===================== +rvClientPhysics::PushOriginInfo +===================== +*/ +void rvClientPhysics::PushOriginInfo( const idVec3& origin, const idMat3& axis ) { + if( !GetPhysics() ) { + return; + } + + pushedOrigin = GetPhysics()->GetOrigin(); + pushedAxis = GetPhysics()->GetAxis(); + + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( axis ); +} + +/* +===================== +rvClientPhysics::PopOriginInfo +===================== +*/ +void rvClientPhysics::PopOriginInfo( void ) { + if( !GetPhysics() ) { + return; + } + + GetPhysics()->SetOrigin( pushedOrigin ); + GetPhysics()->SetAxis( pushedAxis ); +} diff --git a/source/game/client/ClientEntity.h b/source/game/client/ClientEntity.h new file mode 100644 index 0000000..191b20d --- /dev/null +++ b/source/game/client/ClientEntity.h @@ -0,0 +1,222 @@ +//---------------------------------------------------------------- +// ClientEntity.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_ENTITY_H__ +#define __GAME_CLIENT_ENTITY_H__ + +class rvClientEntity : public idClass { +public: + + ABSTRACT_PROTOTYPE( rvClientEntity ); + + rvClientEntity ( void ); + + virtual ~rvClientEntity ( void ); + + void Spawn ( void ); + + virtual void Present ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); + const idVec3& GetOrigin ( void ); + const idMat3& GetAxis ( void ); + + void Bind ( idEntity* master, jointHandle_t joint = INVALID_JOINT, bool isOrientated = false ); + void Unbind ( void ); + + virtual bool IsClient ( void ) const; + virtual void DrawDebugInfo ( void ) const; + + void SetSoundVolume( float volume ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + int StartSoundShader ( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags ); + + // I'm guessing someone may decide to derive from rvClientEntity, so this virtual. + virtual size_t Size ( void ) const; + + virtual void FreeEntityDef ( void ); + + const idEntityPtr& GetBindMaster( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetImpactInfo ( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ); + virtual void AddForce ( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool UpdateAnimationControllers( void ); + + void InitDefaultPhysics ( const idVec3 &origin, const idMat3 &axis ); +protected: + + void RunPhysics ( void ); + + virtual void UpdateBind ( void ); + void UpdateSound ( void ); + +public: + + int entityNumber; + + idLinkList spawnNode; + idLinkList bindNode; + + idDict spawnArgs; + +protected: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics* physics; + + idVec3 worldOrigin; + idVec3 worldVelocity; + idMat3 worldAxis; + + idEntityPtr bindMaster; + idVec3 bindOrigin; + idMat3 bindAxis; + jointHandle_t bindJoint; + bool bindOrientated; + refSound_t refSound; +}; + +ID_INLINE const idVec3& rvClientEntity::GetOrigin ( void ) { + return worldOrigin; +} + +ID_INLINE const idMat3& rvClientEntity::GetAxis ( void ) { + return worldAxis; +} + +ID_INLINE const idEntityPtr& rvClientEntity::GetBindMaster( void ) const { + return bindMaster; +} + +//============================================================================ + +template< class type > +class rvClientEntityPtr { +public: + rvClientEntityPtr(); + + rvClientEntityPtr & operator=( type* cent ); + + int GetSpawnId ( void ) const { return spawnId; } + bool SetSpawnId ( int id ); + bool UpdateSpawnId ( void ); + + bool IsValid ( void ) const; + type * GetEntity ( void ) const; + int GetEntityNum ( void ) const; + + type * operator-> ( void ) const { return GetEntity ( ); } + operator type* ( void ) const { return GetEntity ( ); } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +private: + int spawnId; +}; + +template< class type > +ID_INLINE rvClientEntityPtr::rvClientEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE rvClientEntityPtr &rvClientEntityPtr::operator=( type *cent ) { + if ( cent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.clientSpawnIds[cent->entityNumber] << CENTITYNUM_BITS ) | cent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::SetSpawnId( int id ) { + if ( id == spawnId ) { + return false; + } + if ( ( id >> CENTITYNUM_BITS ) == gameLocal.clientSpawnIds[ id & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::IsValid( void ) const { + return ( gameLocal.clientSpawnIds[ spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ); +} + +template< class type > +ID_INLINE type *rvClientEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.clientSpawnIds[ entityNum ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ) ) { + return static_cast( gameLocal.clientEntities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int rvClientEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +class rvClientPhysics : public idEntity { +public: + + CLASS_PROTOTYPE( rvClientPhysics ); + + rvClientPhysics( void ) { currentEntityNumber = -1; } + + void Spawn( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ); + virtual void PopBindInfo( void ); + + virtual void PushOriginInfo( const idVec3& origin, const idMat3& axis ); + virtual void PopOriginInfo( void ); + + // client side entities. make sure the networking doesn't fuck around with this + virtual bool ClientStale( void ) { assert( false ); return false; } + virtual void ClientUnstale( void ) { assert( false ); } + + int currentEntityNumber; +private: + idEntity* pushedBindMaster; + jointHandle_t pushedBindJoint; + idVec3 pushedOrigin; + idMat3 pushedAxis; + bool pushedOrientated; +}; + +#endif // __GAME_CLIENT_ENTITY_H__ diff --git a/source/game/client/ClientModel.cpp b/source/game/client/ClientModel.cpp new file mode 100644 index 0000000..590e763 --- /dev/null +++ b/source/game/client/ClientModel.cpp @@ -0,0 +1,450 @@ +//---------------------------------------------------------------- +// ClientModel.cpp +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientModel.h" + +/* +=============================================================================== + +rvClientModel + +=============================================================================== +*/ +CLASS_DECLARATION( rvClientEntity, rvClientModel ) +END_CLASS + + +/* +================ +rvClientModel::rvClientModel +================ +*/ +rvClientModel::rvClientModel ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + worldAxis = mat3_identity; + entityDefHandle = -1; +} + +/* +================ +rvClientModel::~rvClientModel +================ +*/ +rvClientModel::~rvClientModel ( void ) { + FreeEntityDef ( ); +} + +/* +================ +rvClientModel::FreeEntityDef +================ +*/ +void rvClientModel::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +/* +================ +rvClientModel::Spawn +================ +*/ +void rvClientModel::Spawn ( void ) { + const char* spawnarg; + + spawnArgs.GetString ( "classname", "", classname ); + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + renderEntity.entityNum = entityNumber; + + spawnarg = spawnArgs.GetString( "model" ); + if ( spawnarg && *spawnarg ) { + SetModel( spawnarg ); + } +} + +/* +================ +rvClientModel::Think +================ +*/ +void rvClientModel::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + UpdateBind(); + Present(); +} + +/* +================ +rvClientModel::Present +================ +*/ +void rvClientModel::Present(void) { + // Hide client entities bound to a hidden entity + if ( bindMaster && (bindMaster->IsHidden ( ) || (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) ) { + return; + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis; + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientModel::SetCustomShader +================ +*/ +bool rvClientModel::SetCustomShader ( const char* shaderName ) { + if ( shaderName == NULL ) { + return false; + } + + const idMaterial* material = declManager->FindMaterial( shaderName ); + + if ( material == NULL ) { + return false; + } + + renderEntity.customShader = material; + + return true; +} + +/* +================ +rvClientModel::Save +================ +*/ +void rvClientModel::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + savefile->WriteString ( classname ); // cnicholson: Added unsaved var + +} + +/* +================ +rvClientModel::Restore +================ +*/ +void rvClientModel::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + savefile->ReadString ( classname ); // cnicholson: Added unrestored var + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } +} + +/* +================ +rvClientModel::SetModel +================ +*/ +void rvClientModel::SetModel( const char* modelname ) { + FreeEntityDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } +} + +/* +============== +rvClientModel::ProjectOverlay +============== +*/ +void rvClientModel::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( entityDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( entityDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +rvClientModel::UpdateRenderEntity +================ +*/ +bool rvClientModel::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +rvClientModel::ModelCallback + +NOTE: may not change the game state whatsoever! +================ +*/ +bool rvClientModel::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + rvClientEntity *cent; + + cent = gameLocal.clientEntities[ renderEntity->entityNum ]; + if ( !cent ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with NULL client entity '%d'", renderEntity->entityNum ); + return false; + } + + if( !cent->IsType( rvClientModel::GetClassType() ) ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with non-client model on client entity '%d'", renderEntity->entityNum ); + return false; + } + + return ((rvClientModel*)cent)->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +rvClientModel::GetPhysicsToVisualTransform +================ +*/ +bool rvClientModel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +rvClientModel::UpdateModelTransform +================ +*/ +void rvClientModel::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * worldAxis; + renderEntity.origin = worldOrigin + origin * renderEntity.axis; + } else { + renderEntity.axis = worldAxis; + renderEntity.origin = worldOrigin; + } +} + +/* +================ +rvClientModel::UpdateModel +================ +*/ +void rvClientModel::UpdateModel( void ) { + UpdateModelTransform(); + + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + } +} + +/* +================ +rvClientModel::UpdateVisuals +================ +*/ +void rvClientModel::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +rvClientModel::SetSkin +================ +*/ +void rvClientModel::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} + +/* +=============================================================================== + +rvAnimatedClientEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientModel, rvAnimatedClientEntity ) +END_CLASS + +/* +================ +rvAnimatedClientEntity::rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::~rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::~rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::Spawn +================ +*/ +void rvAnimatedClientEntity::Spawn( void ) { + SetModel( spawnArgs.GetString( "model" ) ); +} +/* +================ +rvAnimatedClientEntity::Think +================ +*/ +void rvAnimatedClientEntity::Think ( void ) { + UpdateAnimation(); + + rvClientEntity::Think(); +} + +/* +================ +rvAnimatedClientEntity::UpdateAnimation +================ +*/ +void rvAnimatedClientEntity::UpdateAnimation( void ) { + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + + // call any frame commands that have happened in the past frame + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() ) { + gameLocal.DPrintf( "rvAnimatedClientEntity %s %d: inside out bounds - %d\n", GetClassname(), entityNumber, gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + Present(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +rvAnimatedClientEntity::SetModel +================ +*/ +void rvAnimatedClientEntity::SetModel( const char *modelname ) { + FreeEntityDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + rvClientModel::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + //UpdateVisuals(); + Present(); +} diff --git a/source/game/client/ClientModel.h b/source/game/client/ClientModel.h new file mode 100644 index 0000000..96a0376 --- /dev/null +++ b/source/game/client/ClientModel.h @@ -0,0 +1,92 @@ +//---------------------------------------------------------------- +// ClientModel.h +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MODEL_H__ +#define __GAME_CLIENT_MODEL_H__ + +class rvClientModel : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientModel ); + + rvClientModel ( void ); + virtual ~rvClientModel ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + virtual renderEntity_t* GetRenderEntity ( void ); + const char* GetClassname ( void ) const; + + virtual bool SetCustomShader ( const char* shaderName ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void FreeEntityDef ( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return NULL; } + virtual idAnimator* GetAnimator( void ) { return NULL; } + + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + void UpdateVisuals( void ); + void UpdateModel( void ); + void UpdateModelTransform( void ); + + void SetSkin( const idDeclSkin* skin ); + int GetModelDefHandle( void ); +protected: + void Present( void ); + + renderEntity_t renderEntity; + int entityDefHandle; + + idStr classname; +}; + +ID_INLINE renderEntity_t* rvClientModel::GetRenderEntity ( void ) { + return &renderEntity; +} + +ID_INLINE const char* rvClientModel::GetClassname ( void ) const { + return classname.c_str(); +} + +ID_INLINE int rvClientModel::GetModelDefHandle( void ) { + return entityDefHandle; +} + +class rvAnimatedClientEntity : public rvClientModel { +public: + CLASS_PROTOTYPE( rvAnimatedClientEntity ); + + rvAnimatedClientEntity ( void ); + virtual ~rvAnimatedClientEntity ( void ); + + void Spawn( void ); + virtual void Think( void ); + virtual void UpdateAnimation( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return &animator; } + virtual idAnimator* GetAnimator( void ) { return &animator; } + +protected: + idAnimator animator; +}; + +#endif // __GAME_CLIENT_MODEL_H__ diff --git a/source/game/client/ClientMoveable.cpp b/source/game/client/ClientMoveable.cpp new file mode 100644 index 0000000..f492782 --- /dev/null +++ b/source/game/client/ClientMoveable.cpp @@ -0,0 +1,379 @@ +//---------------------------------------------------------------- +// ClientMoveable.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + +rvClientMoveable + +=============================================================================== +*/ +const idEventDef CL_FadeOut( "", "d" ); +const idEventDef CL_ClearDepthHack ( "" ); + +static const float BOUNCE_SOUND_MIN_VELOCITY = 100.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; +static const int BOUNCE_SOUND_DELAY = 200; + +CLASS_DECLARATION( rvClientEntity, rvClientMoveable ) + EVENT( CL_FadeOut, rvClientMoveable::Event_FadeOut ) + EVENT( CL_ClearDepthHack, rvClientMoveable::Event_ClearDepthHack ) +END_CLASS + + +/* +================ +rvClientMoveable::rvClientMoveable +================ +*/ +rvClientMoveable::rvClientMoveable ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + entityDefHandle = -1; + scale.Init( 0, 0, 1.0f, 1.0f ); +} + +/* +================ +rvClientMoveable::~rvClientMoveable +================ +*/ +rvClientMoveable::~rvClientMoveable ( void ) { + FreeEntityDef ( ); + + // Remove any trail effect if there is one + if ( trailEffect ) { + trailEffect->Stop ( ); + } +} + +/* +================ +rvClientMoveable::FreeEntityDef +================ +*/ +void rvClientMoveable::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +idVec3 simpleTri[3] = +{ + idVec3( -1.0, -1.0, 0.0 ), + idVec3( 0.0, 2.0, 0.0 ), + idVec3( 2.0, 0.0, 0.0 ) +}; + +/* +================ +rvClientMoveable::Spawn +================ +*/ +void rvClientMoveable::Spawn ( void ) { + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + idTraceModel trm; + int clipShrink; + idStr clipModelName; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName.Length () ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( clipModelName == SIMPLE_TRI_NAME ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvClientMoveable '%d': cannot load collision model %s", entityNumber, clipModelName.c_str() ); + return; + } + } + + // if the model should be shrunk + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + physicsObj.SetSelf ( gameLocal.entities[ENTITYNUM_CLIENT] ); + physicsObj.SetClipModel ( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "0.5" ), entityNumber ); + + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + physicsObj.SetBouncyness( spawnArgs.GetFloat( "bouncyness", "0.6" ) ); + physicsObj.SetFriction( spawnArgs.GetFloat("linear_friction", "0.6"), spawnArgs.GetFloat( "angular_friction", "0.6"), spawnArgs.GetFloat("friction", "0.05") ); + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + physicsObj.SetContents( 0 ); + // abahr: changed to MASK_SHOT_RENDERMODEL because brass was getting pinched between the player and the wall in some cases + // may want to try something cheaper. + physicsObj.SetClipMask( CONTENTS_OPAQUE ); // MASK_SHOT_RENDERMODEL | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + physicsObj.Activate ( ); + + trailEffect = gameLocal.PlayEffect ( spawnArgs, "fx_trail", physicsObj.GetCenterMass(), GetAxis(), true ); + trailAttenuateSpeed = spawnArgs.GetFloat ( "trailAttenuateSpeed", "200" ); + + bounceSoundShader = declManager->FindSound ( spawnArgs.GetString ( "snd_bounce" ), false ); + bounceSoundTime = 0; + mPlayBounceSoundOnce = spawnArgs.GetBool("bounce_sound_once"); + mHasBounced = false; + + scale.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("scale_reset_duration", "0.2")), Max(VECTOR_EPSILON, spawnArgs.GetFloat("scale", "1.0f")), 1.0f ); +} + +/* +================ +rvClientMoveable::SetOrigin +================ +*/ +void rvClientMoveable::SetOrigin( const idVec3& origin ) { + rvClientEntity::SetOrigin( origin ); + physicsObj.SetOrigin( origin ); +} + +/* +================ +rvClientMoveable::SetAxis +================ +*/ +void rvClientMoveable::SetAxis( const idMat3& axis ) { + rvClientEntity::SetAxis( axis ); + physicsObj.SetAxis( axis ); +} + +/* +================ +rvClientMoveable::SetOwner +================ +*/ +void rvClientMoveable::SetOwner( idEntity* owner ) { + physicsObj.GetClipModel()->SetOwner( owner ); + physicsObj.GetClipModel()->SetEntity( owner ); +} + +/* +================ +rvClientMoveable::Think +================ +*/ +void rvClientMoveable::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + + RunPhysics ( ); + + // Special case the sound update to use the center mass since the origin may be in an odd place + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = worldVelocity; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } + + // Keep the trail effect following + if ( trailEffect ) { + float speed; + speed = idMath::ClampFloat ( 0, trailAttenuateSpeed, worldVelocity.LengthFast ( ) ); + if ( physicsObj.IsAtRest ( ) ) { + trailEffect->Stop ( ); + trailEffect = NULL; + } else { + trailEffect->SetOrigin ( worldOrigin ); + trailEffect->SetAxis ( worldAxis ); + trailEffect->Attenuate ( speed / trailAttenuateSpeed ); + } + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis * scale.GetCurrentValue( gameLocal.GetTime() ); + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientMoveable::GetPhysics +================ +*/ +idPhysics* rvClientMoveable::GetPhysics ( void ) const { + return (idPhysics*)&physicsObj; +} + +/* +================ +rvClientMoveable::Collide +================ +*/ +bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { + if (mPlayBounceSoundOnce && mHasBounced) + { + return false; + } + if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { + float speed; + speed = velocity.LengthFast ( ); + if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { + StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 ); + bounceSoundTime = BOUNCE_SOUND_DELAY; + mHasBounced = true; + } + } + + return false; +} + +/* +================ +rvClientMoveable::Save +================ +*/ +void rvClientMoveable::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + trailEffect.Save( savefile ); + savefile->WriteFloat( trailAttenuateSpeed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( bounceSoundTime ); + savefile->WriteSoundShader( bounceSoundShader ); + + savefile->WriteBool(mPlayBounceSoundOnce); + savefile->WriteBool(mHasBounced); + + // TOSAVE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Restore +================ +*/ +void rvClientMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + trailEffect.Restore( savefile ); + savefile->ReadFloat( trailAttenuateSpeed ); + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadInt( bounceSoundTime ); + savefile->ReadSoundShader( bounceSoundShader ); + + savefile->ReadBool(mPlayBounceSoundOnce); + savefile->ReadBool(mHasBounced); + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + // TORESTORE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Event_FadeOut +================ +*/ +void rvClientMoveable::Event_FadeOut ( int duration ) { + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + PostEventMS ( &EV_Remove, duration ); +} + +/* +================ +rvClientMoveable::Event_ClearDepthHack +================ +*/ +void rvClientMoveable::Event_ClearDepthHack ( void ) { + renderEntity.weaponDepthHackInViewID = 0; +} + +/* +================ +rvClientMoveable::SpawnClientMoveables +================ +*/ +void rvClientMoveable::SpawnClientMoveables( idEntity* ent, const char *type, idList* list ) { + const idKeyValue *kv; + idVec3 origin; + idMat3 axis; + + if( list == NULL || type == NULL ) { + return; + } + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), NULL ); + while ( kv ) { + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( ent->IsType( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + idAnimatedEntity* animEnt = static_cast(ent); + jointHandle_t clientMoveableJoint; + + const char* clientMoveableJointName = ent->spawnArgs.GetString( va( "%s_joint", kv->GetKey().c_str() + 4 ) ); + + // use a joint if specified + if ( idStr::Icmp( clientMoveableJointName, "") ) { + clientMoveableJoint = animEnt->GetAnimator()->GetJointHandle( clientMoveableJointName ); + + if ( !animEnt->GetJointWorldTransform( clientMoveableJoint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", va( "%s_joint", kv->GetKey().c_str() + 4 ), clientMoveableJointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + } + } + + // spawn the entity + const idDict* entityDef = gameLocal.FindEntityDefDict ( kv->GetValue().c_str(), false ); + + if ( entityDef == NULL ) { + gameLocal.Warning( "%s refers to invalid entity def '%s' on entity '%s'\n", kv->GetKey().c_str(), kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + + rvClientMoveable* newModel = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientMoveable" ); + + if( !newModel ) { + gameLocal.Warning( "error spawning client moveable (invalid entity def '%s' on entity '%s')\n", kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + newModel->SetOrigin ( origin ); + newModel->SetAxis( axis ); + + list->Append( newModel ); + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), kv ); + } +} + diff --git a/source/game/client/ClientMoveable.h b/source/game/client/ClientMoveable.h new file mode 100644 index 0000000..0ec1fab --- /dev/null +++ b/source/game/client/ClientMoveable.h @@ -0,0 +1,70 @@ +//---------------------------------------------------------------- +// ClientMoveable.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MOVEABLE_H__ +#define __GAME_CLIENT_MOVEABLE_H__ + +class rvClientMoveable : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientMoveable ); + + rvClientMoveable ( void ); + virtual ~rvClientMoveable ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + renderEntity_t* GetRenderEntity ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + static void SpawnClientMoveables ( idEntity* ent, const char *type, idList* list ); + + virtual void FreeEntityDef ( void ); + + void SetOwner ( idEntity* ent ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); +protected: + renderEntity_t renderEntity; + int entityDefHandle; + + rvClientEffectPtr trailEffect; + float trailAttenuateSpeed; + + idPhysics_RigidBody physicsObj; + + int bounceSoundTime; + const idSoundShader* bounceSoundShader; + bool mPlayBounceSoundOnce; + bool mHasBounced; + + idInterpolate scale; + +private: + + void Event_FadeOut ( int duration ); + void Event_ClearDepthHack ( void ); +}; + +ID_INLINE renderEntity_t* rvClientMoveable::GetRenderEntity ( void ) { + return &renderEntity; +} + +extern const idEventDef CL_FadeOut; +extern const idEventDef CL_ClearDepthHack; + +#define SIMPLE_TRI_NAME "simpletri" + +extern idVec3 simpleTri[3]; + + +#endif // __GAME_CLIENT_MOVEABLE_H__ diff --git a/source/game/gamesys/Callbacks.cpp b/source/game/gamesys/Callbacks.cpp new file mode 100644 index 0000000..9c2663d --- /dev/null +++ b/source/game/gamesys/Callbacks.cpp @@ -0,0 +1,2600 @@ +// generated file - see CREATE_EVENT_CODE + + /******************************************************* + + 1 args + + *******************************************************/ + + case 512 : + typedef void ( idClass::*eventCallback_i_t )( const int ); + ( this->*( eventCallback_i_t )callback )( data[ 0 ] ); + break; + + case 513 : + typedef void ( idClass::*eventCallback_f_t )( const float ); + ( this->*( eventCallback_f_t )callback )( *( float * )&data[ 0 ] ); + break; + + /******************************************************* + + 2 args + + *******************************************************/ + + case 1024 : + typedef void ( idClass::*eventCallback_ii_t )( const int, const int ); + ( this->*( eventCallback_ii_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 1025 : + typedef void ( idClass::*eventCallback_fi_t )( const float, const int ); + ( this->*( eventCallback_fi_t )callback )( *( float * )&data[ 0 ], data[ 1 ] ); + break; + + case 1026 : + typedef void ( idClass::*eventCallback_if_t )( const int, const float ); + ( this->*( eventCallback_if_t )callback )( data[ 0 ], *( float * )&data[ 1 ] ); + break; + + case 1027 : + typedef void ( idClass::*eventCallback_ff_t )( const float, const float ); + ( this->*( eventCallback_ff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ] ); + break; + + /******************************************************* + + 3 args + + *******************************************************/ + + case 2048 : + typedef void ( idClass::*eventCallback_iii_t )( const int, const int, const int ); + ( this->*( eventCallback_iii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2049 : + typedef void ( idClass::*eventCallback_fii_t )( const float, const int, const int ); + ( this->*( eventCallback_fii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2050 : + typedef void ( idClass::*eventCallback_ifi_t )( const int, const float, const int ); + ( this->*( eventCallback_ifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2051 : + typedef void ( idClass::*eventCallback_ffi_t )( const float, const float, const int ); + ( this->*( eventCallback_ffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2052 : + typedef void ( idClass::*eventCallback_iif_t )( const int, const int, const float ); + ( this->*( eventCallback_iif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2053 : + typedef void ( idClass::*eventCallback_fif_t )( const float, const int, const float ); + ( this->*( eventCallback_fif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2054 : + typedef void ( idClass::*eventCallback_iff_t )( const int, const float, const float ); + ( this->*( eventCallback_iff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2055 : + typedef void ( idClass::*eventCallback_fff_t )( const float, const float, const float ); + ( this->*( eventCallback_fff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + /******************************************************* + + 4 args + + *******************************************************/ + + case 4096 : + typedef void ( idClass::*eventCallback_iiii_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_iiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4097 : + typedef void ( idClass::*eventCallback_fiii_t )( const float, const int, const int, const int ); + ( this->*( eventCallback_fiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4098 : + typedef void ( idClass::*eventCallback_ifii_t )( const int, const float, const int, const int ); + ( this->*( eventCallback_ifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4099 : + typedef void ( idClass::*eventCallback_ffii_t )( const float, const float, const int, const int ); + ( this->*( eventCallback_ffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4100 : + typedef void ( idClass::*eventCallback_iifi_t )( const int, const int, const float, const int ); + ( this->*( eventCallback_iifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4101 : + typedef void ( idClass::*eventCallback_fifi_t )( const float, const int, const float, const int ); + ( this->*( eventCallback_fifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4102 : + typedef void ( idClass::*eventCallback_iffi_t )( const int, const float, const float, const int ); + ( this->*( eventCallback_iffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4103 : + typedef void ( idClass::*eventCallback_fffi_t )( const float, const float, const float, const int ); + ( this->*( eventCallback_fffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4104 : + typedef void ( idClass::*eventCallback_iiif_t )( const int, const int, const int, const float ); + ( this->*( eventCallback_iiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4105 : + typedef void ( idClass::*eventCallback_fiif_t )( const float, const int, const int, const float ); + ( this->*( eventCallback_fiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4106 : + typedef void ( idClass::*eventCallback_ifif_t )( const int, const float, const int, const float ); + ( this->*( eventCallback_ifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4107 : + typedef void ( idClass::*eventCallback_ffif_t )( const float, const float, const int, const float ); + ( this->*( eventCallback_ffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4108 : + typedef void ( idClass::*eventCallback_iiff_t )( const int, const int, const float, const float ); + ( this->*( eventCallback_iiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4109 : + typedef void ( idClass::*eventCallback_fiff_t )( const float, const int, const float, const float ); + ( this->*( eventCallback_fiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4110 : + typedef void ( idClass::*eventCallback_ifff_t )( const int, const float, const float, const float ); + ( this->*( eventCallback_ifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4111 : + typedef void ( idClass::*eventCallback_ffff_t )( const float, const float, const float, const float ); + ( this->*( eventCallback_ffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + /******************************************************* + + 5 args + + *******************************************************/ + + case 8192 : + typedef void ( idClass::*eventCallback_iiiii_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8193 : + typedef void ( idClass::*eventCallback_fiiii_t )( const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8194 : + typedef void ( idClass::*eventCallback_ifiii_t )( const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8195 : + typedef void ( idClass::*eventCallback_ffiii_t )( const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8196 : + typedef void ( idClass::*eventCallback_iifii_t )( const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8197 : + typedef void ( idClass::*eventCallback_fifii_t )( const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8198 : + typedef void ( idClass::*eventCallback_iffii_t )( const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8199 : + typedef void ( idClass::*eventCallback_fffii_t )( const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8200 : + typedef void ( idClass::*eventCallback_iiifi_t )( const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8201 : + typedef void ( idClass::*eventCallback_fiifi_t )( const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8202 : + typedef void ( idClass::*eventCallback_ififi_t )( const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8203 : + typedef void ( idClass::*eventCallback_ffifi_t )( const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8204 : + typedef void ( idClass::*eventCallback_iiffi_t )( const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8205 : + typedef void ( idClass::*eventCallback_fiffi_t )( const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8206 : + typedef void ( idClass::*eventCallback_ifffi_t )( const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8207 : + typedef void ( idClass::*eventCallback_ffffi_t )( const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8208 : + typedef void ( idClass::*eventCallback_iiiif_t )( const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8209 : + typedef void ( idClass::*eventCallback_fiiif_t )( const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8210 : + typedef void ( idClass::*eventCallback_ifiif_t )( const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8211 : + typedef void ( idClass::*eventCallback_ffiif_t )( const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8212 : + typedef void ( idClass::*eventCallback_iifif_t )( const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8213 : + typedef void ( idClass::*eventCallback_fifif_t )( const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8214 : + typedef void ( idClass::*eventCallback_iffif_t )( const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8215 : + typedef void ( idClass::*eventCallback_fffif_t )( const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8216 : + typedef void ( idClass::*eventCallback_iiiff_t )( const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8217 : + typedef void ( idClass::*eventCallback_fiiff_t )( const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8218 : + typedef void ( idClass::*eventCallback_ififf_t )( const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8219 : + typedef void ( idClass::*eventCallback_ffiff_t )( const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8220 : + typedef void ( idClass::*eventCallback_iifff_t )( const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8221 : + typedef void ( idClass::*eventCallback_fifff_t )( const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8222 : + typedef void ( idClass::*eventCallback_iffff_t )( const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8223 : + typedef void ( idClass::*eventCallback_fffff_t )( const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + /******************************************************* + + 6 args + + *******************************************************/ + + case 16384 : + typedef void ( idClass::*eventCallback_iiiiii_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16385 : + typedef void ( idClass::*eventCallback_fiiiii_t )( const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16386 : + typedef void ( idClass::*eventCallback_ifiiii_t )( const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16387 : + typedef void ( idClass::*eventCallback_ffiiii_t )( const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16388 : + typedef void ( idClass::*eventCallback_iifiii_t )( const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iifiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16389 : + typedef void ( idClass::*eventCallback_fifiii_t )( const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16390 : + typedef void ( idClass::*eventCallback_iffiii_t )( const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16391 : + typedef void ( idClass::*eventCallback_fffiii_t )( const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16392 : + typedef void ( idClass::*eventCallback_iiifii_t )( const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16393 : + typedef void ( idClass::*eventCallback_fiifii_t )( const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16394 : + typedef void ( idClass::*eventCallback_ififii_t )( const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16395 : + typedef void ( idClass::*eventCallback_ffifii_t )( const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16396 : + typedef void ( idClass::*eventCallback_iiffii_t )( const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16397 : + typedef void ( idClass::*eventCallback_fiffii_t )( const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16398 : + typedef void ( idClass::*eventCallback_ifffii_t )( const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16399 : + typedef void ( idClass::*eventCallback_ffffii_t )( const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16400 : + typedef void ( idClass::*eventCallback_iiiifi_t )( const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16401 : + typedef void ( idClass::*eventCallback_fiiifi_t )( const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16402 : + typedef void ( idClass::*eventCallback_ifiifi_t )( const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16403 : + typedef void ( idClass::*eventCallback_ffiifi_t )( const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16404 : + typedef void ( idClass::*eventCallback_iififi_t )( const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16405 : + typedef void ( idClass::*eventCallback_fififi_t )( const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16406 : + typedef void ( idClass::*eventCallback_iffifi_t )( const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16407 : + typedef void ( idClass::*eventCallback_fffifi_t )( const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16408 : + typedef void ( idClass::*eventCallback_iiiffi_t )( const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16409 : + typedef void ( idClass::*eventCallback_fiiffi_t )( const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16410 : + typedef void ( idClass::*eventCallback_ififfi_t )( const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16411 : + typedef void ( idClass::*eventCallback_ffiffi_t )( const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16412 : + typedef void ( idClass::*eventCallback_iifffi_t )( const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16413 : + typedef void ( idClass::*eventCallback_fifffi_t )( const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16414 : + typedef void ( idClass::*eventCallback_iffffi_t )( const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16415 : + typedef void ( idClass::*eventCallback_fffffi_t )( const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16416 : + typedef void ( idClass::*eventCallback_iiiiif_t )( const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16417 : + typedef void ( idClass::*eventCallback_fiiiif_t )( const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16418 : + typedef void ( idClass::*eventCallback_ifiiif_t )( const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16419 : + typedef void ( idClass::*eventCallback_ffiiif_t )( const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16420 : + typedef void ( idClass::*eventCallback_iifiif_t )( const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iifiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16421 : + typedef void ( idClass::*eventCallback_fifiif_t )( const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16422 : + typedef void ( idClass::*eventCallback_iffiif_t )( const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16423 : + typedef void ( idClass::*eventCallback_fffiif_t )( const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16424 : + typedef void ( idClass::*eventCallback_iiifif_t )( const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16425 : + typedef void ( idClass::*eventCallback_fiifif_t )( const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16426 : + typedef void ( idClass::*eventCallback_ififif_t )( const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16427 : + typedef void ( idClass::*eventCallback_ffifif_t )( const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16428 : + typedef void ( idClass::*eventCallback_iiffif_t )( const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16429 : + typedef void ( idClass::*eventCallback_fiffif_t )( const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16430 : + typedef void ( idClass::*eventCallback_ifffif_t )( const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16431 : + typedef void ( idClass::*eventCallback_ffffif_t )( const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16432 : + typedef void ( idClass::*eventCallback_iiiiff_t )( const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16433 : + typedef void ( idClass::*eventCallback_fiiiff_t )( const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16434 : + typedef void ( idClass::*eventCallback_ifiiff_t )( const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16435 : + typedef void ( idClass::*eventCallback_ffiiff_t )( const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16436 : + typedef void ( idClass::*eventCallback_iififf_t )( const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16437 : + typedef void ( idClass::*eventCallback_fififf_t )( const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16438 : + typedef void ( idClass::*eventCallback_iffiff_t )( const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16439 : + typedef void ( idClass::*eventCallback_fffiff_t )( const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16440 : + typedef void ( idClass::*eventCallback_iiifff_t )( const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16441 : + typedef void ( idClass::*eventCallback_fiifff_t )( const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16442 : + typedef void ( idClass::*eventCallback_ififff_t )( const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16443 : + typedef void ( idClass::*eventCallback_ffifff_t )( const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16444 : + typedef void ( idClass::*eventCallback_iiffff_t )( const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16445 : + typedef void ( idClass::*eventCallback_fiffff_t )( const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16446 : + typedef void ( idClass::*eventCallback_ifffff_t )( const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16447 : + typedef void ( idClass::*eventCallback_ffffff_t )( const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + /******************************************************* + + 7 args + + *******************************************************/ + + case 32768 : + typedef void ( idClass::*eventCallback_iiiiiii_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32769 : + typedef void ( idClass::*eventCallback_fiiiiii_t )( const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32770 : + typedef void ( idClass::*eventCallback_ifiiiii_t )( const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32771 : + typedef void ( idClass::*eventCallback_ffiiiii_t )( const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32772 : + typedef void ( idClass::*eventCallback_iifiiii_t )( const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32773 : + typedef void ( idClass::*eventCallback_fifiiii_t )( const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32774 : + typedef void ( idClass::*eventCallback_iffiiii_t )( const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32775 : + typedef void ( idClass::*eventCallback_fffiiii_t )( const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32776 : + typedef void ( idClass::*eventCallback_iiifiii_t )( const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32777 : + typedef void ( idClass::*eventCallback_fiifiii_t )( const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32778 : + typedef void ( idClass::*eventCallback_ififiii_t )( const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ififiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32779 : + typedef void ( idClass::*eventCallback_ffifiii_t )( const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32780 : + typedef void ( idClass::*eventCallback_iiffiii_t )( const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32781 : + typedef void ( idClass::*eventCallback_fiffiii_t )( const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32782 : + typedef void ( idClass::*eventCallback_ifffiii_t )( const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ifffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32783 : + typedef void ( idClass::*eventCallback_ffffiii_t )( const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32784 : + typedef void ( idClass::*eventCallback_iiiifii_t )( const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32785 : + typedef void ( idClass::*eventCallback_fiiifii_t )( const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32786 : + typedef void ( idClass::*eventCallback_ifiifii_t )( const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32787 : + typedef void ( idClass::*eventCallback_ffiifii_t )( const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32788 : + typedef void ( idClass::*eventCallback_iififii_t )( const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iififii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32789 : + typedef void ( idClass::*eventCallback_fififii_t )( const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32790 : + typedef void ( idClass::*eventCallback_iffifii_t )( const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32791 : + typedef void ( idClass::*eventCallback_fffifii_t )( const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32792 : + typedef void ( idClass::*eventCallback_iiiffii_t )( const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32793 : + typedef void ( idClass::*eventCallback_fiiffii_t )( const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32794 : + typedef void ( idClass::*eventCallback_ififfii_t )( const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ififfii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32795 : + typedef void ( idClass::*eventCallback_ffiffii_t )( const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32796 : + typedef void ( idClass::*eventCallback_iifffii_t )( const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iifffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32797 : + typedef void ( idClass::*eventCallback_fifffii_t )( const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32798 : + typedef void ( idClass::*eventCallback_iffffii_t )( const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32799 : + typedef void ( idClass::*eventCallback_fffffii_t )( const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32800 : + typedef void ( idClass::*eventCallback_iiiiifi_t )( const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32801 : + typedef void ( idClass::*eventCallback_fiiiifi_t )( const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32802 : + typedef void ( idClass::*eventCallback_ifiiifi_t )( const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32803 : + typedef void ( idClass::*eventCallback_ffiiifi_t )( const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32804 : + typedef void ( idClass::*eventCallback_iifiifi_t )( const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32805 : + typedef void ( idClass::*eventCallback_fifiifi_t )( const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32806 : + typedef void ( idClass::*eventCallback_iffiifi_t )( const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32807 : + typedef void ( idClass::*eventCallback_fffiifi_t )( const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32808 : + typedef void ( idClass::*eventCallback_iiififi_t )( const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32809 : + typedef void ( idClass::*eventCallback_fiififi_t )( const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32810 : + typedef void ( idClass::*eventCallback_ifififi_t )( const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32811 : + typedef void ( idClass::*eventCallback_ffififi_t )( const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32812 : + typedef void ( idClass::*eventCallback_iiffifi_t )( const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32813 : + typedef void ( idClass::*eventCallback_fiffifi_t )( const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32814 : + typedef void ( idClass::*eventCallback_ifffifi_t )( const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ifffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32815 : + typedef void ( idClass::*eventCallback_ffffifi_t )( const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32816 : + typedef void ( idClass::*eventCallback_iiiiffi_t )( const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32817 : + typedef void ( idClass::*eventCallback_fiiiffi_t )( const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32818 : + typedef void ( idClass::*eventCallback_ifiiffi_t )( const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32819 : + typedef void ( idClass::*eventCallback_ffiiffi_t )( const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32820 : + typedef void ( idClass::*eventCallback_iififfi_t )( const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iififfi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32821 : + typedef void ( idClass::*eventCallback_fififfi_t )( const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32822 : + typedef void ( idClass::*eventCallback_iffiffi_t )( const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32823 : + typedef void ( idClass::*eventCallback_fffiffi_t )( const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32824 : + typedef void ( idClass::*eventCallback_iiifffi_t )( const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32825 : + typedef void ( idClass::*eventCallback_fiifffi_t )( const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32826 : + typedef void ( idClass::*eventCallback_ififffi_t )( const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ififffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32827 : + typedef void ( idClass::*eventCallback_ffifffi_t )( const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32828 : + typedef void ( idClass::*eventCallback_iiffffi_t )( const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32829 : + typedef void ( idClass::*eventCallback_fiffffi_t )( const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32830 : + typedef void ( idClass::*eventCallback_ifffffi_t )( const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32831 : + typedef void ( idClass::*eventCallback_ffffffi_t )( const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32832 : + typedef void ( idClass::*eventCallback_iiiiiif_t )( const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32833 : + typedef void ( idClass::*eventCallback_fiiiiif_t )( const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32834 : + typedef void ( idClass::*eventCallback_ifiiiif_t )( const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32835 : + typedef void ( idClass::*eventCallback_ffiiiif_t )( const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32836 : + typedef void ( idClass::*eventCallback_iifiiif_t )( const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32837 : + typedef void ( idClass::*eventCallback_fifiiif_t )( const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32838 : + typedef void ( idClass::*eventCallback_iffiiif_t )( const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32839 : + typedef void ( idClass::*eventCallback_fffiiif_t )( const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32840 : + typedef void ( idClass::*eventCallback_iiifiif_t )( const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32841 : + typedef void ( idClass::*eventCallback_fiifiif_t )( const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32842 : + typedef void ( idClass::*eventCallback_ififiif_t )( const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ififiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32843 : + typedef void ( idClass::*eventCallback_ffifiif_t )( const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32844 : + typedef void ( idClass::*eventCallback_iiffiif_t )( const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32845 : + typedef void ( idClass::*eventCallback_fiffiif_t )( const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32846 : + typedef void ( idClass::*eventCallback_ifffiif_t )( const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ifffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32847 : + typedef void ( idClass::*eventCallback_ffffiif_t )( const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32848 : + typedef void ( idClass::*eventCallback_iiiifif_t )( const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32849 : + typedef void ( idClass::*eventCallback_fiiifif_t )( const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32850 : + typedef void ( idClass::*eventCallback_ifiifif_t )( const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32851 : + typedef void ( idClass::*eventCallback_ffiifif_t )( const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32852 : + typedef void ( idClass::*eventCallback_iififif_t )( const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iififif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32853 : + typedef void ( idClass::*eventCallback_fififif_t )( const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32854 : + typedef void ( idClass::*eventCallback_iffifif_t )( const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32855 : + typedef void ( idClass::*eventCallback_fffifif_t )( const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32856 : + typedef void ( idClass::*eventCallback_iiiffif_t )( const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32857 : + typedef void ( idClass::*eventCallback_fiiffif_t )( const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32858 : + typedef void ( idClass::*eventCallback_ififfif_t )( const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ififfif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32859 : + typedef void ( idClass::*eventCallback_ffiffif_t )( const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32860 : + typedef void ( idClass::*eventCallback_iifffif_t )( const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iifffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32861 : + typedef void ( idClass::*eventCallback_fifffif_t )( const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32862 : + typedef void ( idClass::*eventCallback_iffffif_t )( const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32863 : + typedef void ( idClass::*eventCallback_fffffif_t )( const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32864 : + typedef void ( idClass::*eventCallback_iiiiiff_t )( const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32865 : + typedef void ( idClass::*eventCallback_fiiiiff_t )( const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32866 : + typedef void ( idClass::*eventCallback_ifiiiff_t )( const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32867 : + typedef void ( idClass::*eventCallback_ffiiiff_t )( const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32868 : + typedef void ( idClass::*eventCallback_iifiiff_t )( const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32869 : + typedef void ( idClass::*eventCallback_fifiiff_t )( const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32870 : + typedef void ( idClass::*eventCallback_iffiiff_t )( const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32871 : + typedef void ( idClass::*eventCallback_fffiiff_t )( const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32872 : + typedef void ( idClass::*eventCallback_iiififf_t )( const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32873 : + typedef void ( idClass::*eventCallback_fiififf_t )( const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32874 : + typedef void ( idClass::*eventCallback_ifififf_t )( const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32875 : + typedef void ( idClass::*eventCallback_ffififf_t )( const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32876 : + typedef void ( idClass::*eventCallback_iiffiff_t )( const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32877 : + typedef void ( idClass::*eventCallback_fiffiff_t )( const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32878 : + typedef void ( idClass::*eventCallback_ifffiff_t )( const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ifffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32879 : + typedef void ( idClass::*eventCallback_ffffiff_t )( const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32880 : + typedef void ( idClass::*eventCallback_iiiifff_t )( const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32881 : + typedef void ( idClass::*eventCallback_fiiifff_t )( const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32882 : + typedef void ( idClass::*eventCallback_ifiifff_t )( const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32883 : + typedef void ( idClass::*eventCallback_ffiifff_t )( const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32884 : + typedef void ( idClass::*eventCallback_iififff_t )( const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iififff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32885 : + typedef void ( idClass::*eventCallback_fififff_t )( const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32886 : + typedef void ( idClass::*eventCallback_iffifff_t )( const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32887 : + typedef void ( idClass::*eventCallback_fffifff_t )( const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32888 : + typedef void ( idClass::*eventCallback_iiiffff_t )( const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32889 : + typedef void ( idClass::*eventCallback_fiiffff_t )( const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32890 : + typedef void ( idClass::*eventCallback_ififfff_t )( const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ififfff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32891 : + typedef void ( idClass::*eventCallback_ffiffff_t )( const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32892 : + typedef void ( idClass::*eventCallback_iifffff_t )( const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iifffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32893 : + typedef void ( idClass::*eventCallback_fifffff_t )( const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32894 : + typedef void ( idClass::*eventCallback_iffffff_t )( const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32895 : + typedef void ( idClass::*eventCallback_fffffff_t )( const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + /******************************************************* + + 8 args + + *******************************************************/ + + case 65536 : + typedef void ( idClass::*eventCallback_iiiiiiii_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65537 : + typedef void ( idClass::*eventCallback_fiiiiiii_t )( const float, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65538 : + typedef void ( idClass::*eventCallback_ifiiiiii_t )( const int, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65539 : + typedef void ( idClass::*eventCallback_ffiiiiii_t )( const float, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65540 : + typedef void ( idClass::*eventCallback_iifiiiii_t )( const int, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65541 : + typedef void ( idClass::*eventCallback_fifiiiii_t )( const float, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65542 : + typedef void ( idClass::*eventCallback_iffiiiii_t )( const int, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65543 : + typedef void ( idClass::*eventCallback_fffiiiii_t )( const float, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65544 : + typedef void ( idClass::*eventCallback_iiifiiii_t )( const int, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiifiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65545 : + typedef void ( idClass::*eventCallback_fiifiiii_t )( const float, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65546 : + typedef void ( idClass::*eventCallback_ififiiii_t )( const int, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ififiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65547 : + typedef void ( idClass::*eventCallback_ffifiiii_t )( const float, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffifiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65548 : + typedef void ( idClass::*eventCallback_iiffiiii_t )( const int, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiffiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65549 : + typedef void ( idClass::*eventCallback_fiffiiii_t )( const float, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiffiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65550 : + typedef void ( idClass::*eventCallback_ifffiiii_t )( const int, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65551 : + typedef void ( idClass::*eventCallback_ffffiiii_t )( const float, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65552 : + typedef void ( idClass::*eventCallback_iiiifiii_t )( const int, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65553 : + typedef void ( idClass::*eventCallback_fiiifiii_t )( const float, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65554 : + typedef void ( idClass::*eventCallback_ifiifiii_t )( const int, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65555 : + typedef void ( idClass::*eventCallback_ffiifiii_t )( const float, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65556 : + typedef void ( idClass::*eventCallback_iififiii_t )( const int, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iififiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65557 : + typedef void ( idClass::*eventCallback_fififiii_t )( const float, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fififiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65558 : + typedef void ( idClass::*eventCallback_iffifiii_t )( const int, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iffifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65559 : + typedef void ( idClass::*eventCallback_fffifiii_t )( const float, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65560 : + typedef void ( idClass::*eventCallback_iiiffiii_t )( const int, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiffiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65561 : + typedef void ( idClass::*eventCallback_fiiffiii_t )( const float, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65562 : + typedef void ( idClass::*eventCallback_ififfiii_t )( const int, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ififfiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65563 : + typedef void ( idClass::*eventCallback_ffiffiii_t )( const float, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65564 : + typedef void ( idClass::*eventCallback_iifffiii_t )( const int, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iifffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65565 : + typedef void ( idClass::*eventCallback_fifffiii_t )( const float, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fifffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65566 : + typedef void ( idClass::*eventCallback_iffffiii_t )( const int, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65567 : + typedef void ( idClass::*eventCallback_fffffiii_t )( const float, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65568 : + typedef void ( idClass::*eventCallback_iiiiifii_t )( const int, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65569 : + typedef void ( idClass::*eventCallback_fiiiifii_t )( const float, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65570 : + typedef void ( idClass::*eventCallback_ifiiifii_t )( const int, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65571 : + typedef void ( idClass::*eventCallback_ffiiifii_t )( const float, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65572 : + typedef void ( idClass::*eventCallback_iifiifii_t )( const int, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifiifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65573 : + typedef void ( idClass::*eventCallback_fifiifii_t )( const float, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fifiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65574 : + typedef void ( idClass::*eventCallback_iffiifii_t )( const int, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iffiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65575 : + typedef void ( idClass::*eventCallback_fffiifii_t )( const float, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65576 : + typedef void ( idClass::*eventCallback_iiififii_t )( const int, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiififii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65577 : + typedef void ( idClass::*eventCallback_fiififii_t )( const float, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65578 : + typedef void ( idClass::*eventCallback_ifififii_t )( const int, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65579 : + typedef void ( idClass::*eventCallback_ffififii_t )( const float, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffififii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65580 : + typedef void ( idClass::*eventCallback_iiffifii_t )( const int, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiffifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65581 : + typedef void ( idClass::*eventCallback_fiffifii_t )( const float, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiffifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65582 : + typedef void ( idClass::*eventCallback_ifffifii_t )( const int, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65583 : + typedef void ( idClass::*eventCallback_ffffifii_t )( const float, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65584 : + typedef void ( idClass::*eventCallback_iiiiffii_t )( const int, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65585 : + typedef void ( idClass::*eventCallback_fiiiffii_t )( const float, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65586 : + typedef void ( idClass::*eventCallback_ifiiffii_t )( const int, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ifiiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65587 : + typedef void ( idClass::*eventCallback_ffiiffii_t )( const float, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65588 : + typedef void ( idClass::*eventCallback_iififfii_t )( const int, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iififfii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65589 : + typedef void ( idClass::*eventCallback_fififfii_t )( const float, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fififfii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65590 : + typedef void ( idClass::*eventCallback_iffiffii_t )( const int, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65591 : + typedef void ( idClass::*eventCallback_fffiffii_t )( const float, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65592 : + typedef void ( idClass::*eventCallback_iiifffii_t )( const int, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiifffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65593 : + typedef void ( idClass::*eventCallback_fiifffii_t )( const float, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65594 : + typedef void ( idClass::*eventCallback_ififffii_t )( const int, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ififffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65595 : + typedef void ( idClass::*eventCallback_ffifffii_t )( const float, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffifffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65596 : + typedef void ( idClass::*eventCallback_iiffffii_t )( const int, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65597 : + typedef void ( idClass::*eventCallback_fiffffii_t )( const float, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65598 : + typedef void ( idClass::*eventCallback_ifffffii_t )( const int, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65599 : + typedef void ( idClass::*eventCallback_ffffffii_t )( const float, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65600 : + typedef void ( idClass::*eventCallback_iiiiiifi_t )( const int, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65601 : + typedef void ( idClass::*eventCallback_fiiiiifi_t )( const float, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65602 : + typedef void ( idClass::*eventCallback_ifiiiifi_t )( const int, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65603 : + typedef void ( idClass::*eventCallback_ffiiiifi_t )( const float, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65604 : + typedef void ( idClass::*eventCallback_iifiiifi_t )( const int, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65605 : + typedef void ( idClass::*eventCallback_fifiiifi_t )( const float, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65606 : + typedef void ( idClass::*eventCallback_iffiiifi_t )( const int, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65607 : + typedef void ( idClass::*eventCallback_fffiiifi_t )( const float, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65608 : + typedef void ( idClass::*eventCallback_iiifiifi_t )( const int, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65609 : + typedef void ( idClass::*eventCallback_fiifiifi_t )( const float, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65610 : + typedef void ( idClass::*eventCallback_ififiifi_t )( const int, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ififiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65611 : + typedef void ( idClass::*eventCallback_ffifiifi_t )( const float, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffifiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65612 : + typedef void ( idClass::*eventCallback_iiffiifi_t )( const int, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiffiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65613 : + typedef void ( idClass::*eventCallback_fiffiifi_t )( const float, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiffiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65614 : + typedef void ( idClass::*eventCallback_ifffiifi_t )( const int, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65615 : + typedef void ( idClass::*eventCallback_ffffiifi_t )( const float, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65616 : + typedef void ( idClass::*eventCallback_iiiififi_t )( const int, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65617 : + typedef void ( idClass::*eventCallback_fiiififi_t )( const float, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65618 : + typedef void ( idClass::*eventCallback_ifiififi_t )( const int, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifiififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65619 : + typedef void ( idClass::*eventCallback_ffiififi_t )( const float, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65620 : + typedef void ( idClass::*eventCallback_iifififi_t )( const int, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iifififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65621 : + typedef void ( idClass::*eventCallback_fifififi_t )( const float, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fifififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65622 : + typedef void ( idClass::*eventCallback_iffififi_t )( const int, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iffififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65623 : + typedef void ( idClass::*eventCallback_fffififi_t )( const float, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65624 : + typedef void ( idClass::*eventCallback_iiiffifi_t )( const int, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiffifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65625 : + typedef void ( idClass::*eventCallback_fiiffifi_t )( const float, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65626 : + typedef void ( idClass::*eventCallback_ififfifi_t )( const int, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ififfifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65627 : + typedef void ( idClass::*eventCallback_ffiffifi_t )( const float, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65628 : + typedef void ( idClass::*eventCallback_iifffifi_t )( const int, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iifffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65629 : + typedef void ( idClass::*eventCallback_fifffifi_t )( const float, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fifffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65630 : + typedef void ( idClass::*eventCallback_iffffifi_t )( const int, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65631 : + typedef void ( idClass::*eventCallback_fffffifi_t )( const float, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65632 : + typedef void ( idClass::*eventCallback_iiiiiffi_t )( const int, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65633 : + typedef void ( idClass::*eventCallback_fiiiiffi_t )( const float, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65634 : + typedef void ( idClass::*eventCallback_ifiiiffi_t )( const int, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65635 : + typedef void ( idClass::*eventCallback_ffiiiffi_t )( const float, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65636 : + typedef void ( idClass::*eventCallback_iifiiffi_t )( const int, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iifiiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65637 : + typedef void ( idClass::*eventCallback_fifiiffi_t )( const float, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fifiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65638 : + typedef void ( idClass::*eventCallback_iffiiffi_t )( const int, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65639 : + typedef void ( idClass::*eventCallback_fffiiffi_t )( const float, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65640 : + typedef void ( idClass::*eventCallback_iiififfi_t )( const int, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiififfi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65641 : + typedef void ( idClass::*eventCallback_fiififfi_t )( const float, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65642 : + typedef void ( idClass::*eventCallback_ifififfi_t )( const int, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65643 : + typedef void ( idClass::*eventCallback_ffififfi_t )( const float, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffififfi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65644 : + typedef void ( idClass::*eventCallback_iiffiffi_t )( const int, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65645 : + typedef void ( idClass::*eventCallback_fiffiffi_t )( const float, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65646 : + typedef void ( idClass::*eventCallback_ifffiffi_t )( const int, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65647 : + typedef void ( idClass::*eventCallback_ffffiffi_t )( const float, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65648 : + typedef void ( idClass::*eventCallback_iiiifffi_t )( const int, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65649 : + typedef void ( idClass::*eventCallback_fiiifffi_t )( const float, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65650 : + typedef void ( idClass::*eventCallback_ifiifffi_t )( const int, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifiifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65651 : + typedef void ( idClass::*eventCallback_ffiifffi_t )( const float, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65652 : + typedef void ( idClass::*eventCallback_iififffi_t )( const int, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iififffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65653 : + typedef void ( idClass::*eventCallback_fififffi_t )( const float, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fififffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65654 : + typedef void ( idClass::*eventCallback_iffifffi_t )( const int, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iffifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65655 : + typedef void ( idClass::*eventCallback_fffifffi_t )( const float, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65656 : + typedef void ( idClass::*eventCallback_iiiffffi_t )( const int, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiffffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65657 : + typedef void ( idClass::*eventCallback_fiiffffi_t )( const float, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65658 : + typedef void ( idClass::*eventCallback_ififfffi_t )( const int, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ififfffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65659 : + typedef void ( idClass::*eventCallback_ffiffffi_t )( const float, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65660 : + typedef void ( idClass::*eventCallback_iifffffi_t )( const int, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65661 : + typedef void ( idClass::*eventCallback_fifffffi_t )( const float, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65662 : + typedef void ( idClass::*eventCallback_iffffffi_t )( const int, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65663 : + typedef void ( idClass::*eventCallback_fffffffi_t )( const float, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65664 : + typedef void ( idClass::*eventCallback_iiiiiiif_t )( const int, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65665 : + typedef void ( idClass::*eventCallback_fiiiiiif_t )( const float, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65666 : + typedef void ( idClass::*eventCallback_ifiiiiif_t )( const int, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65667 : + typedef void ( idClass::*eventCallback_ffiiiiif_t )( const float, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65668 : + typedef void ( idClass::*eventCallback_iifiiiif_t )( const int, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65669 : + typedef void ( idClass::*eventCallback_fifiiiif_t )( const float, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65670 : + typedef void ( idClass::*eventCallback_iffiiiif_t )( const int, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65671 : + typedef void ( idClass::*eventCallback_fffiiiif_t )( const float, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65672 : + typedef void ( idClass::*eventCallback_iiifiiif_t )( const int, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiifiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65673 : + typedef void ( idClass::*eventCallback_fiifiiif_t )( const float, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65674 : + typedef void ( idClass::*eventCallback_ififiiif_t )( const int, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ififiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65675 : + typedef void ( idClass::*eventCallback_ffifiiif_t )( const float, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffifiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65676 : + typedef void ( idClass::*eventCallback_iiffiiif_t )( const int, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiffiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65677 : + typedef void ( idClass::*eventCallback_fiffiiif_t )( const float, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiffiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65678 : + typedef void ( idClass::*eventCallback_ifffiiif_t )( const int, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65679 : + typedef void ( idClass::*eventCallback_ffffiiif_t )( const float, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65680 : + typedef void ( idClass::*eventCallback_iiiifiif_t )( const int, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65681 : + typedef void ( idClass::*eventCallback_fiiifiif_t )( const float, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65682 : + typedef void ( idClass::*eventCallback_ifiifiif_t )( const int, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65683 : + typedef void ( idClass::*eventCallback_ffiifiif_t )( const float, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65684 : + typedef void ( idClass::*eventCallback_iififiif_t )( const int, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iififiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65685 : + typedef void ( idClass::*eventCallback_fififiif_t )( const float, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fififiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65686 : + typedef void ( idClass::*eventCallback_iffifiif_t )( const int, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iffifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65687 : + typedef void ( idClass::*eventCallback_fffifiif_t )( const float, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65688 : + typedef void ( idClass::*eventCallback_iiiffiif_t )( const int, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiffiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65689 : + typedef void ( idClass::*eventCallback_fiiffiif_t )( const float, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65690 : + typedef void ( idClass::*eventCallback_ififfiif_t )( const int, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ififfiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65691 : + typedef void ( idClass::*eventCallback_ffiffiif_t )( const float, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65692 : + typedef void ( idClass::*eventCallback_iifffiif_t )( const int, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iifffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65693 : + typedef void ( idClass::*eventCallback_fifffiif_t )( const float, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fifffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65694 : + typedef void ( idClass::*eventCallback_iffffiif_t )( const int, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65695 : + typedef void ( idClass::*eventCallback_fffffiif_t )( const float, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65696 : + typedef void ( idClass::*eventCallback_iiiiifif_t )( const int, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65697 : + typedef void ( idClass::*eventCallback_fiiiifif_t )( const float, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65698 : + typedef void ( idClass::*eventCallback_ifiiifif_t )( const int, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65699 : + typedef void ( idClass::*eventCallback_ffiiifif_t )( const float, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65700 : + typedef void ( idClass::*eventCallback_iifiifif_t )( const int, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifiifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65701 : + typedef void ( idClass::*eventCallback_fifiifif_t )( const float, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fifiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65702 : + typedef void ( idClass::*eventCallback_iffiifif_t )( const int, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iffiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65703 : + typedef void ( idClass::*eventCallback_fffiifif_t )( const float, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65704 : + typedef void ( idClass::*eventCallback_iiififif_t )( const int, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiififif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65705 : + typedef void ( idClass::*eventCallback_fiififif_t )( const float, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65706 : + typedef void ( idClass::*eventCallback_ifififif_t )( const int, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65707 : + typedef void ( idClass::*eventCallback_ffififif_t )( const float, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffififif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65708 : + typedef void ( idClass::*eventCallback_iiffifif_t )( const int, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiffifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65709 : + typedef void ( idClass::*eventCallback_fiffifif_t )( const float, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiffifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65710 : + typedef void ( idClass::*eventCallback_ifffifif_t )( const int, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65711 : + typedef void ( idClass::*eventCallback_ffffifif_t )( const float, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65712 : + typedef void ( idClass::*eventCallback_iiiiffif_t )( const int, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65713 : + typedef void ( idClass::*eventCallback_fiiiffif_t )( const float, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65714 : + typedef void ( idClass::*eventCallback_ifiiffif_t )( const int, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ifiiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65715 : + typedef void ( idClass::*eventCallback_ffiiffif_t )( const float, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65716 : + typedef void ( idClass::*eventCallback_iififfif_t )( const int, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iififfif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65717 : + typedef void ( idClass::*eventCallback_fififfif_t )( const float, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fififfif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65718 : + typedef void ( idClass::*eventCallback_iffiffif_t )( const int, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65719 : + typedef void ( idClass::*eventCallback_fffiffif_t )( const float, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65720 : + typedef void ( idClass::*eventCallback_iiifffif_t )( const int, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiifffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65721 : + typedef void ( idClass::*eventCallback_fiifffif_t )( const float, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65722 : + typedef void ( idClass::*eventCallback_ififffif_t )( const int, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ififffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65723 : + typedef void ( idClass::*eventCallback_ffifffif_t )( const float, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffifffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65724 : + typedef void ( idClass::*eventCallback_iiffffif_t )( const int, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65725 : + typedef void ( idClass::*eventCallback_fiffffif_t )( const float, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65726 : + typedef void ( idClass::*eventCallback_ifffffif_t )( const int, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65727 : + typedef void ( idClass::*eventCallback_ffffffif_t )( const float, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65728 : + typedef void ( idClass::*eventCallback_iiiiiiff_t )( const int, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65729 : + typedef void ( idClass::*eventCallback_fiiiiiff_t )( const float, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65730 : + typedef void ( idClass::*eventCallback_ifiiiiff_t )( const int, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65731 : + typedef void ( idClass::*eventCallback_ffiiiiff_t )( const float, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65732 : + typedef void ( idClass::*eventCallback_iifiiiff_t )( const int, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65733 : + typedef void ( idClass::*eventCallback_fifiiiff_t )( const float, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65734 : + typedef void ( idClass::*eventCallback_iffiiiff_t )( const int, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65735 : + typedef void ( idClass::*eventCallback_fffiiiff_t )( const float, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65736 : + typedef void ( idClass::*eventCallback_iiifiiff_t )( const int, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiifiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65737 : + typedef void ( idClass::*eventCallback_fiifiiff_t )( const float, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65738 : + typedef void ( idClass::*eventCallback_ififiiff_t )( const int, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ififiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65739 : + typedef void ( idClass::*eventCallback_ffifiiff_t )( const float, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffifiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65740 : + typedef void ( idClass::*eventCallback_iiffiiff_t )( const int, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiffiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65741 : + typedef void ( idClass::*eventCallback_fiffiiff_t )( const float, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiffiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65742 : + typedef void ( idClass::*eventCallback_ifffiiff_t )( const int, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65743 : + typedef void ( idClass::*eventCallback_ffffiiff_t )( const float, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65744 : + typedef void ( idClass::*eventCallback_iiiififf_t )( const int, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65745 : + typedef void ( idClass::*eventCallback_fiiififf_t )( const float, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65746 : + typedef void ( idClass::*eventCallback_ifiififf_t )( const int, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifiififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65747 : + typedef void ( idClass::*eventCallback_ffiififf_t )( const float, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65748 : + typedef void ( idClass::*eventCallback_iifififf_t )( const int, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iifififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65749 : + typedef void ( idClass::*eventCallback_fifififf_t )( const float, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fifififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65750 : + typedef void ( idClass::*eventCallback_iffififf_t )( const int, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iffififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65751 : + typedef void ( idClass::*eventCallback_fffififf_t )( const float, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65752 : + typedef void ( idClass::*eventCallback_iiiffiff_t )( const int, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiffiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65753 : + typedef void ( idClass::*eventCallback_fiiffiff_t )( const float, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65754 : + typedef void ( idClass::*eventCallback_ififfiff_t )( const int, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ififfiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65755 : + typedef void ( idClass::*eventCallback_ffiffiff_t )( const float, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65756 : + typedef void ( idClass::*eventCallback_iifffiff_t )( const int, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iifffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65757 : + typedef void ( idClass::*eventCallback_fifffiff_t )( const float, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fifffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65758 : + typedef void ( idClass::*eventCallback_iffffiff_t )( const int, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65759 : + typedef void ( idClass::*eventCallback_fffffiff_t )( const float, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65760 : + typedef void ( idClass::*eventCallback_iiiiifff_t )( const int, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65761 : + typedef void ( idClass::*eventCallback_fiiiifff_t )( const float, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65762 : + typedef void ( idClass::*eventCallback_ifiiifff_t )( const int, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65763 : + typedef void ( idClass::*eventCallback_ffiiifff_t )( const float, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65764 : + typedef void ( idClass::*eventCallback_iifiifff_t )( const int, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifiifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65765 : + typedef void ( idClass::*eventCallback_fifiifff_t )( const float, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fifiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65766 : + typedef void ( idClass::*eventCallback_iffiifff_t )( const int, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iffiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65767 : + typedef void ( idClass::*eventCallback_fffiifff_t )( const float, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65768 : + typedef void ( idClass::*eventCallback_iiififff_t )( const int, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiififff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65769 : + typedef void ( idClass::*eventCallback_fiififff_t )( const float, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65770 : + typedef void ( idClass::*eventCallback_ifififff_t )( const int, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65771 : + typedef void ( idClass::*eventCallback_ffififff_t )( const float, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffififff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65772 : + typedef void ( idClass::*eventCallback_iiffifff_t )( const int, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiffifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65773 : + typedef void ( idClass::*eventCallback_fiffifff_t )( const float, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiffifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65774 : + typedef void ( idClass::*eventCallback_ifffifff_t )( const int, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65775 : + typedef void ( idClass::*eventCallback_ffffifff_t )( const float, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65776 : + typedef void ( idClass::*eventCallback_iiiiffff_t )( const int, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65777 : + typedef void ( idClass::*eventCallback_fiiiffff_t )( const float, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65778 : + typedef void ( idClass::*eventCallback_ifiiffff_t )( const int, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ifiiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65779 : + typedef void ( idClass::*eventCallback_ffiiffff_t )( const float, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65780 : + typedef void ( idClass::*eventCallback_iififfff_t )( const int, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iififfff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65781 : + typedef void ( idClass::*eventCallback_fififfff_t )( const float, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fififfff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65782 : + typedef void ( idClass::*eventCallback_iffiffff_t )( const int, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65783 : + typedef void ( idClass::*eventCallback_fffiffff_t )( const float, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65784 : + typedef void ( idClass::*eventCallback_iiifffff_t )( const int, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiifffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65785 : + typedef void ( idClass::*eventCallback_fiifffff_t )( const float, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65786 : + typedef void ( idClass::*eventCallback_ififffff_t )( const int, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ififffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65787 : + typedef void ( idClass::*eventCallback_ffifffff_t )( const float, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffifffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65788 : + typedef void ( idClass::*eventCallback_iiffffff_t )( const int, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65789 : + typedef void ( idClass::*eventCallback_fiffffff_t )( const float, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65790 : + typedef void ( idClass::*eventCallback_ifffffff_t )( const int, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65791 : + typedef void ( idClass::*eventCallback_ffffffff_t )( const float, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + diff --git a/source/game/gamesys/Class.cpp b/source/game/gamesys/Class.cpp new file mode 100644 index 0000000..51cae34 --- /dev/null +++ b/source/game/gamesys/Class.cpp @@ -0,0 +1,1434 @@ +/* + +Base class for all C++ objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +// this is the head of a singly linked list of all the idTypes +static idTypeInfo *typelist = NULL; +static idHierarchy classHierarchy; +static int eventCallbackMemory = 0; + +/* +================ +idTypeInfo::idClassType() + +Constructor for class. Should only be called from CLASS_DECLARATION macro. +Handles linking class definition into class hierarchy. This should only happen +at startup as idTypeInfos are statically defined. Since static variables can be +initialized in any order, the constructor must handle the case that subclasses +are initialized before superclasses. +================ +*/ +idTypeInfo::idTypeInfo( const char *classname, const char *superclass, idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), +// RAVEN BEGIN +// bdube: added states + void ( idClass::*Spawn )( void ), + rvStateFunc* stateCallbacks, + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ) { +// RAVEN END + + idTypeInfo *type; + idTypeInfo **insert; + + this->classname = classname; + this->superclass = superclass; + this->eventCallbacks = eventCallbacks; + this->eventMap = NULL; + this->Spawn = Spawn; + this->Save = Save; + this->Restore = Restore; + this->CreateInstance = CreateInstance; + this->super = idClass::GetClass( superclass ); + this->freeEventMap = false; + typeNum = 0; + lastChild = 0; + +// RAVEN BEGIN +// bdube: added states + this->stateCallbacks = stateCallbacks; +// RAVEN END + + // Check if any subclasses were initialized before their superclass + for( type = typelist; type != NULL; type = type->next ) { + if ( ( type->super == NULL ) && !idStr::Cmp( type->superclass, this->classname ) && + idStr::Cmp( type->classname, "idClass" ) ) { + type->super = this; + } + } + + // Insert sorted + for ( insert = &typelist; *insert; insert = &(*insert)->next ) { + assert( idStr::Cmp( classname, (*insert)->classname ) ); + if ( idStr::Cmp( classname, (*insert)->classname ) < 0 ) { + next = *insert; + *insert = this; + break; + } + } + if ( !*insert ) { + *insert = this; + next = NULL; + } +} + +/* +================ +idTypeInfo::~idTypeInfo +================ +*/ +idTypeInfo::~idTypeInfo() { + Shutdown(); +} + +/* +================ +idTypeInfo::Init + +Initializes the event callback table for the class. Creates a +table for fast lookups of event functions. Should only be called once. +================ +*/ +void idTypeInfo::Init( void ) { + idTypeInfo *c; + idEventFunc *def; + int ev; + int i; + bool *set; + int num; + + if ( eventMap ) { + // we've already been initialized by a subclass + return; + } + + // make sure our superclass is initialized first + if ( super && !super->eventMap ) { + super->Init(); + } + + // add to our node hierarchy + if ( super ) { + node.ParentTo( super->node ); + } else { + node.ParentTo( classHierarchy ); + } + node.SetOwner( this ); + + // keep track of the number of children below each class + for( c = super; c != NULL; c = c->super ) { + c->lastChild++; + } + + // if we're not adding any new event callbacks, we can just use our superclass's table + if ( ( !eventCallbacks || !eventCallbacks->event ) && super ) { + eventMap = super->eventMap; + return; + } + + // set a flag so we know to delete the eventMap table + freeEventMap = true; + + // Allocate our new table. It has to have as many entries as there + // are events. NOTE: could save some space by keeping track of the maximum + // event that the class responds to and doing range checking. + num = idEventDef::NumEventCommands(); + eventMap = new eventCallback_t[ num ]; + memset( eventMap, 0, sizeof( eventCallback_t ) * num ); + eventCallbackMemory += sizeof( eventCallback_t ) * num; + + // allocate temporary memory for flags so that the subclass's event callbacks + // override the superclass's event callback + set = new bool[ num ]; + memset( set, 0, sizeof( bool ) * num ); + + // go through the inheritence order and copies the event callback function into + // a list indexed by the event number. This allows fast lookups of + // event functions. + for( c = this; c != NULL; c = c->super ) { + def = c->eventCallbacks; + if ( !def ) { + continue; + } + + // go through each entry until we hit the NULL terminator + for( i = 0; def[ i ].event != NULL; i++ ) { + ev = def[ i ].event->GetEventNum(); + + if ( set[ ev ] ) { + continue; + } + set[ ev ] = true; + eventMap[ ev ] = def[ i ].function; + } + } + + delete[] set; +} + +/* +================ +idTypeInfo::Shutdown + +Should only be called when DLL or EXE is being shutdown. +Although it cleans up any allocated memory, it doesn't bother to remove itself +from the class list since the program is shutting down. +================ +*/ +void idTypeInfo::Shutdown() { + // free up the memory used for event lookups + if ( eventMap ) { + if ( freeEventMap ) { + delete[] eventMap; + } + eventMap = NULL; + } + typeNum = 0; + lastChild = 0; +} + + +/*********************************************************************** + + idClass + +***********************************************************************/ + +const idEventDef EV_PostRestore( "", NULL ); +const idEventDef EV_Remove( "", NULL ); +const idEventDef EV_SafeRemove( "remove", NULL ); + +ABSTRACT_DECLARATION( NULL, idClass ) +// RAVEN BEGIN + EVENT( EV_PostRestore, idClass::Event_PostRestore ) +// RAVEN END + EVENT( EV_Remove, idClass::Event_Remove ) + EVENT( EV_SafeRemove, idClass::Event_SafeRemove ) +END_CLASS + +CLASS_STATES_DECLARATION(idClass) +END_CLASS_STATES + +// alphabetical order +idList idClass::types; +// typenum order +idList idClass::typenums; + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; + +/* +================ +idClass::CallSpawn +================ +*/ +void idClass::CallSpawn( void ) { + idTypeInfo *type; + + type = GetType(); + CallSpawnFunc( type ); +} + +/* +================ +idClass::CallSpawnFunc +================ +*/ +classSpawnFunc_t idClass::CallSpawnFunc( idTypeInfo *cls ) { + classSpawnFunc_t func; + + if ( cls->super ) { + func = CallSpawnFunc( cls->super ); + if ( func == cls->Spawn ) { + // don't call the same function twice in a row. + // this can happen when subclasses don't have their own spawn function. + return func; + } + } + + // RAVEN BEGIN + // hmmm.... stompage of memory has occured + assert(cls->Spawn != 0); + // RAVEN END + + ( this->*cls->Spawn )(); + + return cls->Spawn; +} + +/* +================ +idClass::FindUninitializedMemory +================ +*/ +void idClass::FindUninitializedMemory( void ) { +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = ( ( unsigned long * )this ) - 1; + int size = *ptr; + assert( ( size & 3 ) == 0 ); + size >>= 2; + for ( int i = 0; i < size; i++ ) { + if ( ptr[i] == 0xcdcdcdcd ) { +// RAVEN BEGIN + gameLocal.Warning( "type '%s' has uninitialized variable (offset %d)", GetClassname(), i << 2 ); +// RAVEN END + } + } +#endif +} + +/* +================ +idClass::Spawn +================ +*/ +void idClass::Spawn( void ) { +} + +/* +================ +idClass::~idClass + +Destructor for object. Cancels any events that depend on this object. +================ +*/ +idClass::~idClass() { + idEvent::CancelEvents( this ); +} + +/* +================ +idClass::DisplayInfo_f +================ +*/ +void idClass::DisplayInfo_f( const idCmdArgs &args ) { + gameLocal.Printf( "Class memory status: %i bytes allocated in %i objects\n", memused, numobjects ); +} + +/* +================ +idClass::ListClasses_f +================ +*/ +void idClass::ListClasses_f( const idCmdArgs &args ) { + int i; + idTypeInfo *type; + + gameLocal.Printf( "%-24s %-24s %-6s %-6s\n", "Classname", "Superclass", "Type", "Subclasses" ); + gameLocal.Printf( "----------------------------------------------------------------------\n" ); + + for( i = 0; i < types.Num(); i++ ) { + type = types[ i ]; + gameLocal.Printf( "%-24s %-24s %6d %6d\n", type->classname, type->superclass, type->typeNum, type->lastChild - type->typeNum ); + } + + gameLocal.Printf( "...%d classes", types.Num() ); +} + +/* +================ +idClass::CreateInstance +================ +*/ +idClass *idClass::CreateInstance( const char *name ) { + const idTypeInfo *type; + idClass *obj; + + type = idClass::GetClass( name ); + if ( !type ) { + return NULL; + } + + obj = type->CreateInstance(); + return obj; +} + +/* +================ +idClass::Init + +Should be called after all idTypeInfos are initialized, so must be called +manually upon game code initialization. Tells all the idTypeInfos to initialize +their event callback table for the associated class. This should only be called +once during the execution of the program or DLL. +================ +*/ +void idClass::Init( void ) { + idTypeInfo *c; + int num; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CLASS); +// RAVEN END + + gameLocal.Printf( "Initializing class hierarchy\n" ); + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + return; + } + + // init the event callback tables for all the classes + for( c = typelist; c != NULL; c = c->next ) { +// RAVEN BEGIN +// jnewquist: Make sure the superclass was actually registered! + if ( c->super == NULL && (c->superclass && idStr::Cmp(c->superclass, "NULL")) ) { + common->Error("Superclass %s of %s was never registered!", c->superclass, c->classname); + } +// RAVEN END + c->Init(); + } + + // number the types according to the class hierarchy so we can quickly determine if a class + // is a subclass of another + num = 0; + for( c = classHierarchy.GetNext(); c != NULL; c = c->node.GetNext(), num++ ) { + c->typeNum = num; + c->lastChild += num; + } + + // number of bits needed to send types over network + typeNumBits = idMath::BitsForInteger( num ); + + // create a list of the types so we can do quick lookups + // one list in alphabetical order, one in typenum order + types.SetGranularity( 1 ); + types.SetNum( num ); + typenums.SetGranularity( 1 ); + typenums.SetNum( num ); + num = 0; + for( c = typelist; c != NULL; c = c->next, num++ ) { + types[ num ] = c; + typenums[ c->typeNum ] = c; + } + + initialized = true; + + gameLocal.Printf( "...%i classes, %i bytes for event callbacks\n", types.Num(), eventCallbackMemory ); +} + +/* +================ +idClass::Shutdown +================ +*/ +void idClass::Shutdown( void ) { + idTypeInfo *c; + + for( c = typelist; c != NULL; c = c->next ) { + c->Shutdown(); + } + types.Clear(); + typenums.Clear(); + + initialized = false; +} + +/* +================ +idClass::new +================ +*/ +#ifdef ID_DEBUG_MEMORY +#undef new +#endif + +void * idClass::operator new( size_t s ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +void * idClass::operator new( size_t s, int, int, char *, int ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +#ifdef ID_DEBUG_MEMORY +#define new ID_DEBUG_NEW +#endif + +/* +================ +idClass::delete +================ +*/ +void idClass::operator delete( void *ptr ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +void idClass::operator delete( void *ptr, int, int, char *, int ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +/* +================ +idClass::GetClass + +Returns the idTypeInfo for the name of the class passed in. This is a static function +so it must be called as idClass::GetClass( classname ) +================ +*/ +idTypeInfo *idClass::GetClass( const char *name ) { + idTypeInfo *c; + int order; + int mid; + int min; + int max; + + if ( !initialized ) { + // idClass::Init hasn't been called yet, so do a slow lookup + for( c = typelist; c != NULL; c = c->next ) { + if ( !idStr::Cmp( c->classname, name ) ) { + return c; + } + } + } else { + // do a binary search through the list of types + min = 0; + max = types.Num() - 1; + while( min <= max ) { + mid = ( min + max ) / 2; + c = types[ mid ]; + order = idStr::Cmp( c->classname, name ); + if ( !order ) { + return c; + } else if ( order > 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + } + + return NULL; +} + +/* +================ +idClass::GetType +================ +*/ +idTypeInfo *idClass::GetType( const int typeNum ) { + idTypeInfo *c; + + if ( !initialized ) { + for( c = typelist; c != NULL; c = c->next ) { + if ( c->typeNum == typeNum ) { + return c; + } + } + } else if ( ( typeNum >= 0 ) && ( typeNum < types.Num() ) ) { + return typenums[ typeNum ]; + } + + return NULL; +} + +/* +================ +idClass::GetClassname + +Returns the text classname of the object. +================ +*/ +const char *idClass::GetClassname( void ) const { + idTypeInfo *type; + + type = GetType(); + return type->classname; +} + +/* +================ +idClass::GetSuperclass + +Returns the text classname of the superclass. +================ +*/ +const char *idClass::GetSuperclass( void ) const { + idTypeInfo *cls; + + cls = GetType(); + return cls->superclass; +} + +/* +================ +idClass::CancelEvents +================ +*/ +void idClass::CancelEvents( const idEventDef *ev ) { + idEvent::CancelEvents( this, ev ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idClass::EventIsPosted +================ +*/ +bool idClass::EventIsPosted( const idEventDef *ev ) const { + return idEvent::EventIsPosted( this, ev ); +} +// RAVEN END + +/* +================ +idClass::PostEventArgs +================ +*/ +bool idClass::PostEventArgs( const idEventDef *ev, int time, int numargs, ... ) { + idTypeInfo *c; + idEvent *event; + va_list args; + + assert( ev ); + + if ( !idEvent::initialized ) { + return false; + } + + c = GetType(); + if ( !c->eventMap[ ev->GetEventNum() ] ) { + // we don't respond to this event, so ignore it + return false; + } + + // we service events on the client to avoid any bad code filling up the event pool + // we don't want them processed usually, unless when the map is (re)loading. + // we allow threads to run fine, though. +// RAVEN BEGIN +// bdube: added a check to see if this is a client entity +// jnewquist: Use accessor for static class type + bool isClient = !IsClient() && gameLocal.isClient; + +#ifdef _XENON + // nrausch: We want to allow selectWeapon to pass through, but that's all + if ( idStr::Cmp( ev->GetName(), "selectWeapon" ) == 0 ) { + isClient = false; + } +#endif + + if( isClient && ( gameLocal.GameState() != GAMESTATE_STARTUP ) && ( gameLocal.GameState() != GAMESTATE_RESTART ) && !IsType( idThread::GetClassType() ) ) { +// RAVEN END + return true; + } + + va_start( args, numargs ); + event = idEvent::Alloc( ev, numargs, args ); + va_end( args ); + + event->Schedule( this, c, time ); + + return true; +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time ) { + return PostEventArgs( ev, time, 0 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ) { + return PostEventArgs( ev, time, 1, &arg1 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, time, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, time, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, time, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, time, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, time, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, time, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, time, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time ) { + return PostEventArgs( ev, SEC2MS( time ), 0 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ) { + return PostEventArgs( ev, SEC2MS( time ), 1, &arg1 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, SEC2MS( time ), 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, SEC2MS( time ), 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, SEC2MS( time ), 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, SEC2MS( time ), 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, SEC2MS( time ), 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, SEC2MS( time ), 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, SEC2MS( time ), 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgs +================ +*/ +bool idClass::ProcessEventArgs( const idEventDef *ev, int numargs, ... ) { + idTypeInfo *c; + int num; + int data[ D_EVENT_MAXARGS ]; + va_list args; + + assert( ev ); + assert( idEvent::initialized ); + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + va_start( args, numargs ); + idEvent::CopyArgs( ev, numargs, args, data ); + va_end( args ); + + ProcessEventArgPtr( ev, data ); + + return true; +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev ) { + return ProcessEventArgs( ev, 0 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1 ) { + return ProcessEventArgs( ev, 1, &arg1 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ) { + return ProcessEventArgs( ev, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return ProcessEventArgs( ev, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return ProcessEventArgs( ev, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return ProcessEventArgs( ev, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return ProcessEventArgs( ev, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return ProcessEventArgs( ev, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return ProcessEventArgs( ev, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgPtr +================ +*/ +bool idClass::ProcessEventArgPtr( const idEventDef *ev, int *data ) { + idTypeInfo *c; + int num; + eventCallback_t callback; + + assert( ev ); + assert( idEvent::initialized ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( g_debugTriggers.GetBool() && ( ev == &EV_Activate ) && IsType( idEntity::GetClassType() ) ) { +// RAVEN END + const idEntity *ent = *reinterpret_cast( data ); + gameLocal.Printf( "%d: '%s' activated by '%s'\n", gameLocal.framenum, static_cast( this )->GetName(), ent ? ent->GetName() : "NULL" ); + } + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + callback = c->eventMap[ num ]; + +#if !CPU_EASYARGS + +/* +on ppc architecture, floats are passed in a seperate set of registers +the function prototypes must have matching float declaration + +http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_5.html +*/ + + switch( ev->GetFormatspecIndex() ) { + case 1 << D_EVENT_MAXARGS : + ( this->*callback )(); + break; + +// generated file - see CREATE_EVENT_CODE +#include "Callbacks.cpp" + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#else + + assert( D_EVENT_MAXARGS == 8 ); + + switch( ev->GetNumArgs() ) { + case 0 : + ( this->*callback )(); + break; + + case 1 : + typedef void ( idClass::*eventCallback_1_t )( const int ); + ( this->*( eventCallback_1_t )callback )( data[ 0 ] ); + break; + + case 2 : + typedef void ( idClass::*eventCallback_2_t )( const int, const int ); + ( this->*( eventCallback_2_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 3 : + typedef void ( idClass::*eventCallback_3_t )( const int, const int, const int ); + ( this->*( eventCallback_3_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 4 : + typedef void ( idClass::*eventCallback_4_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_4_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 5 : + typedef void ( idClass::*eventCallback_5_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_5_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 6 : + typedef void ( idClass::*eventCallback_6_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_6_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 7 : + typedef void ( idClass::*eventCallback_7_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_7_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 8 : + typedef void ( idClass::*eventCallback_8_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_8_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#endif + + return true; +} + +/* +================ +idClass::Event_Remove +================ +*/ +void idClass::Event_Remove( void ) { + delete this; +} + +/* +================ +idClass::Event_SafeRemove +================ +*/ +void idClass::Event_SafeRemove( void ) { + // Forces the remove to be done at a safe time + PostEventMS( &EV_Remove, 0 ); +} + +// RAVEN BEGIN +// bdube: client entities +/* +================ +idClass::IsClient +================ +*/ +bool idClass::IsClient ( void ) const { + return false; +} + +/* +================ +idClass::GetDebugInfo +================ +*/ +void idClass::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { +} + +/* +================ +idClass::ProcessState +================ +*/ +stateResult_t idClass::ProcessState ( const rvStateFunc* state, const stateParms_t& parms ) { + return (this->*(state->function)) ( parms ); +} + +stateResult_t idClass::ProcessState ( const char* name, const stateParms_t& parms ) { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return (this->*(cls->stateCallbacks[i].function)) ( parms ); + } + } + } + + return SRESULT_ERROR; +} + +/* +================ +idClass::FindState +================ +*/ +const rvStateFunc* idClass::FindState ( const char* name ) const { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return &cls->stateCallbacks[i]; + } + } + } + + return NULL; +} + +/* +================ +idClass::RegisterClasses +================ +*/ +void idClass::RegisterClasses( void ) +{ +// jnewquist: Register subclasses explicitly so they aren't dead-stripped +#define REGISTER(name) void Register_##name(void); Register_##name(); + REGISTER(idAFAttachment); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Base); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_ClawFourFingers); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Generic); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Gibbable); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_SteamPipe); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Vehicle); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleFourWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleSixWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_WithAttachedHead); // ..\..\code\game\AFEntity.cpp + REGISTER(idAI); // ..\..\code\game\ai\AI_events.cpp + REGISTER(idActivator); // ..\..\code\game\Misc.cpp + REGISTER(idActor); // ..\..\code\game\Actor.cpp + REGISTER(idAnimated); // ..\..\code\game\Misc.cpp + REGISTER(idAnimatedEntity); // ..\..\code\game\Entity.cpp + REGISTER(idBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idBeam); // ..\..\code\game\Misc.cpp + REGISTER(idBobber); // ..\..\code\game\Mover.cpp + REGISTER(idBrittleFracture); // ..\..\code\game\BrittleFracture.cpp + REGISTER(idCamera); // ..\..\code\game\Camera.cpp + REGISTER(idCameraAnim); // ..\..\code\game\Camera.cpp + REGISTER(idCameraView); // ..\..\code\game\Camera.cpp + REGISTER(idChain); // ..\..\code\game\AFEntity.cpp + REGISTER(idClass); // ..\..\code\game\gamesys\Class.cpp + REGISTER(idCursor3D); // ..\..\code\game\GameEdit.cpp + REGISTER(idDamagable); // ..\..\code\game\Misc.cpp + REGISTER(idDoor); // ..\..\code\game\Mover.cpp + REGISTER(idEarthQuake); // ..\..\code\game\Misc.cpp + REGISTER(idElevator); // ..\..\code\game\Mover.cpp + REGISTER(idEntity); // ..\..\code\game\Entity.cpp + REGISTER(idExplodable); // ..\..\code\game\Misc.cpp + REGISTER(idExplodingBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idForce); // ..\..\code\game\physics\Force.cpp + REGISTER(idForceField); // ..\..\code\game\Misc.cpp + REGISTER(idForce_Constant); // ..\..\code\game\physics\Force_Constant.cpp + REGISTER(idForce_Drag); // ..\..\code\game\physics\Force_Drag.cpp + REGISTER(idForce_Field); // ..\..\code\game\physics\Force_Field.cpp + REGISTER(idForce_Spring); // ..\..\code\game\physics\Force_Spring.cpp + REGISTER(idFuncAASObstacle); // ..\..\code\game\Misc.cpp + REGISTER(idFuncAASPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncEmitter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncRadioChatter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncSplat); // ..\..\code\game\Misc.cpp + REGISTER(idGuidedProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idItem); // ..\..\code\game\Item.cpp + REGISTER(idItemPowerup); // ..\..\code\game\Item.cpp + REGISTER(idItemRemover); // ..\..\code\game\Item.cpp + REGISTER(idLight); // ..\..\code\game\Light.cpp + REGISTER(idLiquid); // ..\..\code\game\Misc.cpp + REGISTER(idLocationEntity); // ..\..\code\game\Misc.cpp + REGISTER(idLocationSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idMoveable); // ..\..\code\game\Moveable.cpp + REGISTER(idMoveableItem); // ..\..\code\game\Item.cpp + REGISTER(idMover); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Binary); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Periodic); // ..\..\code\game\Mover.cpp + REGISTER(idMultiModelAF); // ..\..\code\game\AFEntity.cpp + REGISTER(idObjective); // ..\..\code\game\Item.cpp + REGISTER(idObjectiveComplete); // ..\..\code\game\Item.cpp + REGISTER(idPathCorner); // ..\..\code\game\Misc.cpp + REGISTER(idPendulum); // ..\..\code\game\Mover.cpp + REGISTER(idPhantomObjects); // ..\..\code\game\Misc.cpp + REGISTER(idPhysics); // ..\..\code\game\physics\Physics.cpp + REGISTER(idPhysics_AF); // ..\..\code\game\physics\Physics_AF.cpp + REGISTER(idPhysics_Actor); // ..\..\code\game\physics\Physics_Actor.cpp + REGISTER(idPhysics_Base); // ..\..\code\game\physics\Physics_Base.cpp + REGISTER(idPhysics_Monster); // ..\..\code\game\physics\Physics_Monster.cpp + REGISTER(idPhysics_Parametric); // ..\..\code\game\physics\Physics_Parametric.cpp + REGISTER(idPhysics_Player); // ..\..\code\game\physics\Physics_Player.cpp + REGISTER(idPhysics_RigidBody); // ..\..\code\game\physics\Physics_RigidBody.cpp + REGISTER(idPhysics_Static); // ..\..\code\game\physics\Physics_Static.cpp + REGISTER(idPhysics_StaticMulti); // ..\..\code\game\physics\Physics_StaticMulti.cpp + REGISTER(idPlat); // ..\..\code\game\Mover.cpp + REGISTER(idPlayer); // ..\..\code\game\Player.cpp + REGISTER(idPlayerStart); // ..\..\code\game\Misc.cpp + REGISTER(idProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idRiser); // ..\..\code\game\Mover.cpp + REGISTER(idRotater); // ..\..\code\game\Mover.cpp + REGISTER(idSecurityCamera); // ..\..\code\game\SecurityCamera.cpp + REGISTER(idShaking); // ..\..\code\game\Misc.cpp + REGISTER(idSound); // ..\..\code\game\Sound.cpp + REGISTER(idSpawnableEntity); // ..\..\code\game\Misc.cpp + REGISTER(idSplinePath); // ..\..\code\game\Mover.cpp + REGISTER(idSpring); // ..\..\code\game\Misc.cpp + REGISTER(idStaticEntity); // ..\..\code\game\Misc.cpp + REGISTER(idTarget); // ..\..\code\game\Target.cpp + REGISTER(idTarget_CallObjectFunction); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Damage); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableLevelWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableStamina); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\EndLevel.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeEntity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeSoundClass); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Give); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveEmail); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveSecurity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LevelTrigger); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeIn); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeOut); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LockDoor); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Remove); // ..\..\code\game\Target.cpp + REGISTER(idTarget_RemoveWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SessionCommand); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetFov); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetGlobalShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetInfluence); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetKeyVal); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetModel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetPrimaryObjective); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderParm); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Show); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Tip); // ..\..\code\game\Target.cpp + REGISTER(idTarget_WaitForButton); // ..\..\code\game\Target.cpp + REGISTER(idTestModel); // ..\..\code\game\anim\Anim_Testmodel.cpp + REGISTER(idTextEntity); // ..\..\code\game\Misc.cpp + REGISTER(idThread); // ..\..\code\game\script\Script_Thread.cpp + REGISTER(idTrigger); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Count); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_EntityName); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Fade); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Hurt); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Multi); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Timer); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Touch); // ..\..\code\game\Trigger.cpp + REGISTER(idVacuumEntity); // ..\..\code\game\Misc.cpp + REGISTER(idVacuumSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idWorldspawn); // ..\..\code\game\WorldSpawn.cpp + REGISTER(rvAFAttractor); // ..\..\code\game\AFEntity.cpp + REGISTER(rvAIAvoid); // ..\..\code\game\ai\AI_Helper.cpp + REGISTER(rvAITactical); // ..\..\code\game\ai\AI_Tactical.cpp + REGISTER(rvAIMedic); // ..\..\code\game\ai\AI_Medic.cpp + REGISTER(rvAITether); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherRadius); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherBehind); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherClear); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomePassive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomeAggressive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITrigger); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvCTFAssaultPlayerStart); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCTF_AssaultPoint); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCameraPlayback); // ..\..\code\game\Camera.cpp + REGISTER(rvCameraPortalSky); // ..\..\code\game\Camera.cpp + REGISTER(rvClientCrawlEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEntity); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvClientModel); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvAnimatedClientEntity); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvClientAFEntity); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientAFAttachment); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientMoveable); // ..\..\code\game\client\ClientMoveable.cpp + REGISTER(rvClientPhysics); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvConveyor); // ..\..\code\game\Mover.cpp + REGISTER(rvDarkMatterProjectile); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvDebugJumpPoint); // ..\..\code\game\Misc.cpp + REGISTER(rvDriftingProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvEffect); // ..\..\code\game\Effect.cpp + REGISTER(rvFuncSaveGame); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_Static); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_SurfaceNormal); // ..\..\code\game\Misc.cpp + REGISTER(rvGravitySeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(rvHealingStation); // ..\..\code\game\Healing_Station.cpp + REGISTER(rvItemCTFFlag); // ..\..\code\game\Item.cpp + REGISTER(rvJumpPad); // ..\..\code\game\Misc.cpp + REGISTER(rvMIRVProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvModviewModel); // ..\..\code\game\GameEdit.cpp + REGISTER(rvPusher); // ..\..\code\game\Mover.cpp +#ifndef _MPBETA + REGISTER(rvMonsterBerserker); // ..\..\code\game\ai\Monster_Berserker.cpp + REGISTER(rvMonsterBossBuddy); // ..\..\code\game\ai\Monster_BossBuddy.cpp + REGISTER(rvMonsterBossMakron); // ..\..\code\game\ai\Monster_BossMakron.cpp + REGISTER(rvMonsterConvoyGround); // ..\..\code\game\ai\Monster_ConvoyGround.cpp + REGISTER(rvMonsterConvoyHover); // ..\..\code\game\ai\Monster_ConvoyHover.cpp + REGISTER(rvMonsterFailedTransfer); // ..\..\code\game\ai\Monster_FailedTransfer.cpp + REGISTER(rvMonsterFatty); // ..\..\code\game\ai\Monster_Fatty.cpp + REGISTER(rvMonsterGladiator); // ..\..\code\game\ai\Monster_Gladiator.cpp + REGISTER(rvMonsterGrunt); // ..\..\code\game\ai\Monster_Grunt.cpp + REGISTER(rvMonsterGunner); // ..\..\code\game\ai\Monster_Gunner.cpp + REGISTER(rvMonsterHarvester); // ..\..\code\game\ai\Monster_Harvester.cpp + REGISTER(rvMonsterHarvesterDispersal); // ..\..\code\game\ai\Monster_HarvesterDispersal.cpp + REGISTER(rvMonsterHeavyHoverTank); // ..\..\code\game\ai\Monster_HeavyHoverTank.cpp + REGISTER(rvMonsterIronMaiden); // ..\..\code\game\ai\Monster_IronMaiden.cpp + REGISTER(rvMonsterLightTank); // ..\..\code\game\ai\Monster_LightTank.cpp + REGISTER(rvMonsterNetworkGuardian); // ..\..\code\game\ai\Monster_NetworkGuardian.cpp + REGISTER(rvMonsterRepairBot); // ..\..\code\game\ai\Monster_RepairBot.cpp + REGISTER(rvMonsterScientist); // ..\..\code\game\ai\Monster_Scientist.cpp + REGISTER(rvMonsterSentry); // ..\..\code\game\ai\Monster_Sentry.cpp + REGISTER(rvMonsterSlimyTransfer); // ..\..\code\game\ai\Monster_SlimyTransfer.cpp + REGISTER(rvMonsterStroggMarine);// ..\..\code\game\ai\Monster_StroggMarine.cpp + REGISTER(rvMonsterStreamProtector); // ..\..\code\game\ai\Monster_StreamProtector.cpp + REGISTER(rvMonsterStroggFlyer); // ..\..\code\game\ai\Monster_StroggFlyer.cpp + REGISTER(rvMonsterStroggHover); // ..\..\code\game\ai\Monster_StroggHover.cpp + REGISTER(rvMonsterTeleportDropper); // ..\..\code\game\ai\Monster_TeleportDropper.cpp + REGISTER(rvMonsterTurret); // ..\..\code\game\ai\Monster_Turret.cpp + REGISTER(rvMonsterTurretFlying); // ..\..\code\game\ai\Monster_TurretFlying.cpp +#endif // !_MPBETA + REGISTER(rvObjectiveFailed); // ..\..\code\game\Item.cpp + REGISTER(rvPhysics_Particle); // ..\..\code\game\physics\Physics_Particle.cpp + REGISTER(rvPhysics_VehicleMonster); // ..\..\code\game\physics\Physics_VehicleMonster.cpp + REGISTER(rvPhysics_Spline); // ..\..\code\game\SplineMover.cpp + REGISTER(rvSpawner); // ..\..\code\game\spawner.cpp + REGISTER(rvSpawnerProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvSplineMover); // ..\..\code\game\SplineMover.cpp + // twhitaker: removed database support + // REGISTER(rvTarget_AddDatabaseEntry); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_AmmoStash); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_BossBattle); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_ExitAreaAlert); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_LaunchProjectile); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_Nailable); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_SecretArea); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_TetherAI); // ..\..\code\game\Target.cpp + REGISTER(rvTramCar); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Marine); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Strogg); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramGate); // ..\..\code\game\TramGate.cpp + REGISTER(rvVehicle); // ..\..\code\game\vehicle\Vehicle.cpp + REGISTER(rvVehicleAI); // ..\..\code\game\ai\VehicleAI.cpp + REGISTER(rvVehicleAnimated); // ..\..\code\game\vehicle\VehicleAnimated.cpp + REGISTER(rvVehicleDriver); // ..\..\code\game\vehicle\VehicleDriver.cpp + REGISTER(rvVehicleDropPod); // ..\..\code\game\vehicle\Vehicle_DropPod.cpp + REGISTER(rvVehicleHoverpad); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleLight); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleMonster); // ..\..\code\game\vehicle\VehicleMonster.cpp + REGISTER(rvVehiclePart); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleRigid); // ..\..\code\game\vehicle\VehicleRigid.cpp + REGISTER(rvVehicleSound); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleSpline); // ..\..\code\game\vehicle\VehicleSpline.cpp + REGISTER(rvVehicleStatic); // ..\..\code\game\vehicle\VehicleStatic.cpp + REGISTER(rvVehicleThruster); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleTurret); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleUserAnimated); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleWalker); // ..\..\code\game\vehicle\Vehicle_Walker.cpp + REGISTER(rvVehicleWeapon); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvViewWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeaponBlaster); // ..\..\code\game\weapon\WeaponBlaster.cpp + REGISTER(rvWeaponDarkMatterGun); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvWeaponGauntlet); // ..\..\code\game\weapon\WeaponGauntlet.cpp + REGISTER(rvWeaponGrenadeLauncher); // ..\..\code\game\weapon\WeaponGrenadeLauncher.cpp + REGISTER(rvWeaponHyperblaster); // ..\..\code\game\weapon\WeaponHyperblaster.cpp + REGISTER(rvWeaponLightningGun); // ..\..\code\game\weapon\WeaponLightningGun.cpp + REGISTER(rvWeaponMachinegun); // ..\..\code\game\weapon\WeaponMachinegun.cpp + REGISTER(rvWeaponNailgun); // ..\..\code\game\weapon\WeaponNailgun.cpp + REGISTER(rvWeaponRailgun); // ..\..\code\game\weapon\WeaponRailgun.cpp + REGISTER(rvWeaponRocketLauncher); // ..\..\code\game\weapon\WeaponRocketLauncher.cpp + REGISTER(rvWeaponShotgun); // ..\..\code\game\weapon\WeaponShotgun.cpp +// RITUAL BEGIN + REGISTER(riDeadZonePowerup); // ..\..\code\game\Item.cpp + REGISTER(WeaponNapalmGun); // ..\..\code\game\weapon\WeaponNapalmGun.cpp +// RITUAL END +#undef REGISTER +} + +// RAVEN END + diff --git a/source/game/gamesys/Class.h b/source/game/gamesys/Class.h new file mode 100644 index 0000000..b551461 --- /dev/null +++ b/source/game/gamesys/Class.h @@ -0,0 +1,526 @@ +/* + +Base class for all game objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#ifndef __SYS_CLASS_H__ +#define __SYS_CLASS_H__ + +class idClass; +class idTypeInfo; + +// RAVEN BEGIN +extern const idEventDef EV_PostRestore; +// RAVEN END +extern const idEventDef EV_Remove; +extern const idEventDef EV_SafeRemove; + +typedef void ( idClass::*eventCallback_t )( void ); + +template< class Type > +struct idEventFunc { + const idEventDef *event; + eventCallback_t function; +}; + +// added & so gcc could compile this +#define EVENT( event, function ) { &( event ), ( void ( idClass::* )( void ) )( &function ) }, +#define END_CLASS { NULL, NULL } }; + +class idEventArg { +public: + int type; + int value; + + idEventArg() { type = D_EVENT_INTEGER; value = 0; }; + idEventArg( int data ) { type = D_EVENT_INTEGER; value = data; }; + idEventArg( float data ) { type = D_EVENT_FLOAT; value = *reinterpret_cast( &data ); }; + idEventArg( const idVec3 &data ) { type = D_EVENT_VECTOR; value = reinterpret_cast( &data ); }; + idEventArg( const idStr &data ) { type = D_EVENT_STRING; value = reinterpret_cast( data.c_str() ); }; + idEventArg( const char *data ) { type = D_EVENT_STRING; value = reinterpret_cast( data ); }; + idEventArg( const class idEntity *data ) { type = D_EVENT_ENTITY; value = reinterpret_cast( data ); }; + idEventArg( const trace_t *data ) { type = D_EVENT_TRACE; value = reinterpret_cast( data ); }; +}; + +class idAllocError : public idException { +public: + idAllocError( const char *text = "" ) : idException( text ) {} +}; + +/*********************************************************************** + + idClass + +***********************************************************************/ + +/* +================ +CLASS_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance concrete classes only. +================ +*/ +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +CLASS_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking and run-time instanciation. It also defines the list +of events that the class responds to. Take special care to ensure that the +proper superclass is indicated or the run-time type information will be +incorrect. Use this on concrete classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: Added states +// jnewquist: Use accessor for static class type +// mwhitlock: Dynamic memory consolidation +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + + +/* +================ +ABSTRACT_PROTOTYPE + +This macro must be included in the definition of any abstract subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +ABSTRACT_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking. It also defines the list of events that the class +responds to. Take special care to ensure that the proper superclass is +indicated or the run-time tyep information will be incorrect. Use this +on abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: added states +// jnewquist: Use accessor for static class type +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +typedef void ( idClass::*classSpawnFunc_t )( void ); + +class idSaveGame; +class idRestoreGame; + +class idClass { +public: + ABSTRACT_PROTOTYPE( idClass ); + +#ifdef ID_REDIRECT_NEWDELETE +#undef new +#endif + void * operator new( size_t ); + void * operator new( size_t s, int, int, char *, int ); + void operator delete( void * ); + void operator delete( void *, int, int, char *, int ); +#ifdef ID_REDIRECT_NEWDELETE +#define new ID_DEBUG_NEW +#endif + + virtual ~idClass(); + + void Spawn( void ); + void CallSpawn( void ); + bool IsType( const idTypeInfo &c ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *c ) const { return IsType(*c); } +// RAVEN END + const char * GetClassname( void ) const; + const char * GetSuperclass( void ) const; + void FindUninitializedMemory( void ); + + void Save( idSaveGame *savefile ) const {}; + void Restore( idRestoreGame *savefile ) {}; + + bool RespondsTo( const idEventDef &ev ) const; + +// RAVEN BEGIN +// bdube: states + stateResult_t ProcessState ( const rvStateFunc* state, const stateParms_t& parms ); + stateResult_t ProcessState ( const char* name, const stateParms_t& parms ); + const rvStateFunc* FindState ( const char* name ) const; + +// bdube: client entities + virtual bool IsClient ( void ) const; + +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + static void RegisterClasses( void ); +// RAVEN END + + bool PostEventMS( const idEventDef *ev, int time ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool PostEventSec( const idEventDef *ev, float time ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEvent( const idEventDef *ev ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEventArgPtr( const idEventDef *ev, int *data ); + void CancelEvents( const idEventDef *ev ); +// RAVEN BEGIN +// abahr: + bool EventIsPosted( const idEventDef *ev ) const; + + void Event_PostRestore( void ) {} +// RAVEN END + void Event_Remove( void ); + + // Static functions + static void Init( void ); + static void Shutdown( void ); + static idTypeInfo * GetClass( const char *name ); + static void DisplayInfo_f( const idCmdArgs &args ); + static void ListClasses_f( const idCmdArgs &args ); + static idClass * CreateInstance( const char *name ); + static int GetNumTypes( void ) { return types.Num(); } + static int GetTypeNumBits( void ) { return typeNumBits; } + static idTypeInfo * GetType( int num ); + +// RAVEN BEGIN +// jscott: for memory profiling + static size_t GetUsedMemory( void ) { return( memused ); } + +// bdube: debug info + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +private: + classSpawnFunc_t CallSpawnFunc( idTypeInfo *cls ); + + bool PostEventArgs( const idEventDef *ev, int time, int numargs, ... ); + bool ProcessEventArgs( const idEventDef *ev, int numargs, ... ); + + void Event_SafeRemove( void ); + + static bool initialized; + static idList types; + static idList typenums; + static int typeNumBits; + static int memused; + static int numobjects; + +// RAVEN BEGIN +// bdube: states + CLASS_STATES_PROTOTYPE(idClass); +// RAVEN END +}; + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +class idTypeInfo { +public: + const char * classname; + const char * superclass; + idClass * ( *CreateInstance )( void ); + void ( idClass::*Spawn )( void ); + void ( idClass::*Save )( idSaveGame *savefile ) const; + void ( idClass::*Restore )( idRestoreGame *savefile ); + +// RAVEN BEGIN +// bdube: added + rvStateFunc * stateCallbacks; +// RAVEN END + + idEventFunc * eventCallbacks; + eventCallback_t * eventMap; + idTypeInfo * super; + idTypeInfo * next; + bool freeEventMap; + int typeNum; + int lastChild; + + idHierarchy node; + + idTypeInfo( const char *classname, const char *superclass, + idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), void ( idClass::*Spawn )( void ), +// RAVEN BEGIN +// bdube: added + rvStateFunc *stateCallbacks, +// RAVEN END + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ); + ~idTypeInfo(); + + void Init( void ); + void Shutdown( void ); + + bool IsType( const idTypeInfo &superclass ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *superclass ) const { return IsType(*superclass); } +// RAVEN END + bool RespondsTo( const idEventDef &ev ) const; +}; + +/* +================ +idTypeInfo::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idTypeInfo::IsType( const idTypeInfo &type ) const { + return ( ( typeNum >= type.typeNum ) && ( typeNum <= type.lastChild ) ); +} + +/* +================ +idTypeInfo::RespondsTo +================ +*/ +ID_INLINE bool idTypeInfo::RespondsTo( const idEventDef &ev ) const { + assert( idEvent::initialized ); + if ( !eventMap[ ev.GetEventNum() ] ) { + // we don't respond to this event + return false; + } + + return true; +} + +/* +================ +idClass::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idClass::IsType( const idTypeInfo &superclass ) const { + idTypeInfo *subclass; + + subclass = GetType(); + return subclass->IsType( superclass ); +} + +/* +================ +idClass::RespondsTo +================ +*/ +ID_INLINE bool idClass::RespondsTo( const idEventDef &ev ) const { + const idTypeInfo *c; + + assert( idEvent::initialized ); + c = GetType(); + return c->RespondsTo( ev ); +} + +#endif /* !__SYS_CLASS_H__ */ diff --git a/source/game/gamesys/DebugGraph.cpp b/source/game/gamesys/DebugGraph.cpp new file mode 100644 index 0000000..8e2f8d2 --- /dev/null +++ b/source/game/gamesys/DebugGraph.cpp @@ -0,0 +1,68 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idDebugGraph::idDebugGraph +================ +*/ +idDebugGraph::idDebugGraph() { + index = 0; +} + +/* +================ +idDebugGraph::SetNumSamples +================ +*/ +void idDebugGraph::SetNumSamples( int num ) { + index = 0; + samples.Clear(); + samples.SetNum( num ); + memset( samples.Ptr(), 0, samples.MemoryUsed() ); +} + +/* +================ +idDebugGraph::AddValue +================ +*/ +void idDebugGraph::AddValue( float value ) { + samples[ index ] = value; + index++; + if ( index >= samples.Num() ) { + index = 0; + } +} + +/* +================ +idDebugGraph::Draw +================ +*/ +void idDebugGraph::Draw( const idVec4 &color, float scale ) const { + int i; + float value1; + float value2; + idVec3 vec1; + idVec3 vec2; + + const idMat3 &axis = gameLocal.GetLocalPlayer()->viewAxis; + const idVec3 pos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() + axis[ 1 ] * samples.Num() * 0.5f; + + value1 = samples[ index ] * scale; + for( i = 1; i < samples.Num(); i++ ) { + value2 = samples[ ( i + index ) % samples.Num() ] * scale; + + vec1 = pos + axis[ 2 ] * value1 - axis[ 1 ] * ( i - 1 ) + axis[ 0 ] * samples.Num(); + vec2 = pos + axis[ 2 ] * value2 - axis[ 1 ] * i + axis[ 0 ] * samples.Num(); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + gameRenderWorld->DebugLine( color, vec1, vec2, gameLocal.GetMSec ( ), false ); +// RAVEN END + value1 = value2; + } +} diff --git a/source/game/gamesys/DebugGraph.h b/source/game/gamesys/DebugGraph.h new file mode 100644 index 0000000..bafc30e --- /dev/null +++ b/source/game/gamesys/DebugGraph.h @@ -0,0 +1,13 @@ +// DebugGraph.h + +class idDebugGraph { +public: + idDebugGraph(); + void SetNumSamples( int num ); + void AddValue( float value ); + void Draw( const idVec4 &color, float scale ) const; + +private: + idList samples; + int index; +}; diff --git a/source/game/gamesys/Event.cpp b/source/game/gamesys/Event.cpp new file mode 100644 index 0000000..e063c46 --- /dev/null +++ b/source/game/gamesys/Event.cpp @@ -0,0 +1,813 @@ +/* +sys_event.cpp + +Event are used for scheduling tasks and for linking script commands. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_EVENTSPERFRAME 8192 // Upped from 4096 +//#define CREATE_EVENT_CODE + +/*********************************************************************** + + idEventDef + +***********************************************************************/ + +idEventDef *idEventDef::eventDefList[MAX_EVENTS]; +int idEventDef::numEventDefs = 0; + +static bool eventError = false; +static char eventErrorMsg[ 128 ]; + +/* +================ +idEventDef::idEventDef +================ +*/ +idEventDef::idEventDef( const char *command, const char *formatspec, char returnType ) { + idEventDef *ev; + int i; + unsigned int bits; + + assert( command ); + assert( !idEvent::initialized ); + + // Allow NULL to indicate no args, but always store it as "" + // so we don't have to check for it. + if ( !formatspec ) { + formatspec = ""; + } + + this->name = command; + this->formatspec = formatspec; + this->returnType = returnType; + + numargs = strlen( formatspec ); + assert( numargs <= D_EVENT_MAXARGS ); + if ( numargs > D_EVENT_MAXARGS ) { + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Too many args for '%s' event.", name ); + return; + } + + // make sure the format for the args is valid, calculate the formatspecindex, and the offsets for each arg + bits = 0; + argsize = 0; + memset( argOffset, 0, sizeof( argOffset ) ); + for( i = 0; i < numargs; i++ ) { + argOffset[ i ] = argsize; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + bits |= 1 << i; + argsize += sizeof( float ); + break; + + case D_EVENT_INTEGER : + argsize += sizeof( int ); + break; + + case D_EVENT_VECTOR : + argsize += sizeof( idVec3 ); + break; + + case D_EVENT_STRING : + argsize += MAX_STRING_LEN; + break; + + case D_EVENT_ENTITY : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_ENTITY_NULL : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_TRACE : + argsize += sizeof( trace_t ) + MAX_STRING_LEN + sizeof( bool ); + break; + + default : + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Invalid arg format '%s' string for '%s' event.", formatspec, name ); + return; + break; + } + } + + // calculate the formatspecindex + formatspecIndex = ( 1 << ( numargs + D_EVENT_MAXARGS ) ) | bits; + + // go through the list of defined events and check for duplicates + // and mismatched format strings + eventnum = numEventDefs; + for( i = 0; i < eventnum; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( command, ev->name ) == 0 ) { + if ( idStr::Cmp( formatspec, ev->formatspec ) != 0 ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing format strings ('%s'!='%s').", + command, formatspec, ev->formatspec ); + return; + } + + if ( ev->returnType != returnType ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing return types ('%c'!='%c').", + command, returnType, ev->returnType ); + return; + } + // Don't bother putting the duplicate event in list. + eventnum = ev->eventnum; + return; + } + } + + ev = this; + + if ( numEventDefs >= MAX_EVENTS ) { + eventError = true; + sprintf( eventErrorMsg, "numEventDefs >= MAX_EVENTS" ); + return; + } + eventDefList[numEventDefs] = ev; + numEventDefs++; +} + +/* +================ +idEventDef::NumEventCommands +================ +*/ +int idEventDef::NumEventCommands( void ) { + return numEventDefs; +} + +/* +================ +idEventDef::GetEventCommand +================ +*/ +const idEventDef *idEventDef::GetEventCommand( int eventnum ) { + return eventDefList[ eventnum ]; +} + +/* +================ +idEventDef::FindEvent +================ +*/ +const idEventDef *idEventDef::FindEvent( const char *name ) { + idEventDef *ev; + int num; + int i; + + assert( name ); + + num = numEventDefs; + for( i = 0; i < num; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( name, ev->name ) == 0 ) { + return ev; + } + } + + return NULL; +} + +/*********************************************************************** + + idEvent + +***********************************************************************/ + +static idLinkList FreeEvents; +static idLinkList EventQueue; +static idEvent EventPool[ MAX_EVENTS ]; + +bool idEvent::initialized = false; + +idDynamicBlockAlloc idEvent::eventDataAllocator; + +/* +================ +idEvent::~idEvent() +================ +*/ +idEvent::~idEvent() { + Free(); +} + + +void idEvent::WriteDebugInfo( void ) { + idEvent *event; + int count = 0; + + idFile *FH = fileSystem->OpenFileAppend( "idEvents.txt" ); + + FH->Printf( "Num Events = %d\n", EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + count++; + + FH->Printf( "%d. %d - %s - %s - %s\n", count, event->time, event->eventdef->GetName(), event->typeinfo->classname, event->object->GetClassname() ); + + event = event->eventNode.Next(); + } + + FH->Printf( "\n\n" ); + fileSystem->CloseFile( FH ); +} + +/* +================ +idEvent::Alloc +================ +*/ +idEvent *idEvent::Alloc( const idEventDef *evdef, int numargs, va_list args ) { + idEvent *ev; + size_t size; + const char *format; + idEventArg *arg; + byte *dataPtr; + int i; + const char *materialName; + + if ( FreeEvents.IsListEmpty() ) { + WriteDebugInfo( ); + gameLocal.Error( "idEvent::Alloc : No more free events for '%s' event.", evdef->GetName() ); + } + + ev = FreeEvents.Next(); + ev->eventNode.Remove(); + + ev->eventdef = evdef; + + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::Alloc : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + size = evdef->GetArgSize(); + if ( size ) { + ev->data = eventDataAllocator.Alloc( size ); + memset( ev->data, 0, size ); + } else { + ev->data = NULL; + } + + format = evdef->GetArgFormat(); + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) || ( format[ i ] == D_EVENT_TRACE ) ) { + // when NULL is passed in for an entity or trace, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + dataPtr = &ev->data[ evdef->GetArgOffset( i ) ]; + + switch( format[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + *reinterpret_cast( dataPtr ) = arg->value; + break; + + case D_EVENT_VECTOR : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = *reinterpret_cast( arg->value ); + } + break; + + case D_EVENT_STRING : + if ( arg->value ) { + idStr::Copynz( reinterpret_cast( dataPtr ), reinterpret_cast( arg->value ), MAX_STRING_LEN ); + } + break; + +// RAVEN BEGIN +// abahr: type checking change as per Jim D. +// jshepard: TODO FIXME HACK this never ever produces desired, positive results. Events should be built to prepare for null entities, especially when dealing with +// script events. This will throw a warning, and events should be prepared to deal with null entities. + case D_EVENT_ENTITY : + if ( reinterpret_cast( arg->value ) == NULL ) { + gameLocal.Warning( "idEvent::Alloc : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, evdef->GetName() ); + } + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; +//RAVEN END + + case D_EVENT_TRACE : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = true; + *reinterpret_cast( dataPtr + sizeof( bool ) ) = *reinterpret_cast( arg->value ); + + // save off the material as a string since the pointer won't be valid in save games. + // since we save off the entire trace_t structure, if the material is NULL here, + // it will be NULL when we process it, so we don't need to save off anything in that case. + if ( reinterpret_cast( arg->value )->c.material ) { + materialName = reinterpret_cast( arg->value )->c.material->GetName(); + idStr::Copynz( reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ), materialName, MAX_STRING_LEN ); + } + } else { + *reinterpret_cast( dataPtr ) = false; + } + break; + + default : + gameLocal.Error( "idEvent::Alloc : Invalid arg format '%s' string for '%s' event.", format, evdef->GetName() ); + break; + } + } + + return ev; +} + +/* +================ +idEvent::CopyArgs +================ +*/ +void idEvent::CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ) { + int i; + const char *format; + idEventArg *arg; + + format = evdef->GetArgFormat(); + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + data[ i ] = arg->value; + } +} + +/* +================ +idEvent::Free +================ +*/ +void idEvent::Free( void ) { + if ( data ) { + eventDataAllocator.Free( data ); + data = NULL; + } + + eventdef = NULL; + time = 0; + object = NULL; + typeinfo = NULL; + + eventNode.SetOwner( this ); + eventNode.AddToEnd( FreeEvents ); +} + +/* +================ +idEvent::Schedule +================ +*/ +void idEvent::Schedule( idClass *obj, const idTypeInfo *type, int time ) { + idEvent *event; + + assert( initialized ); + if ( !initialized ) { + return; + } + + object = obj; + typeinfo = type; + + // wraps after 24 days...like I care. ;) + this->time = gameLocal.time + time; + + eventNode.Remove(); + + event = EventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( EventQueue ); + } +} + +/* +================ +idEvent::CancelEvents +================ +*/ +void idEvent::CancelEvents( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEvent::EventIsPosted +================ +*/ +bool idEvent::EventIsPosted( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return false; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if( event->object == obj && evdef == event->eventdef ) { + return true; + } + } + + return false; +} +// RAVEN END + +/* +================ +idEvent::ClearEventList +================ +*/ +void idEvent::ClearEventList( void ) { + int i; + + // + // initialize lists + // + FreeEvents.Clear(); + EventQueue.Clear(); + + // + // add the events to the free list + // + for( i = 0; i < MAX_EVENTS; i++ ) { + EventPool[ i ].Free(); + } +} + +/* +================ +idEvent::ServiceEvents +================ +*/ +void idEvent::ServiceEvents( void ) { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !EventQueue.IsListEmpty() ) { + +#ifdef _XENON + session->PacifierUpdate(); +#endif + + event = EventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.time ) { + break; + } + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + case D_EVENT_ENTITY : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + if ( *reinterpret_cast( &args[ i ] ) == NULL ) { + gameLocal.Warning( "idEvent::ServiceEvents : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, ev->GetName() ); + } + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; +// RAVEN END + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + + // savegames can trash the object, so do this for safety + if ( event->object ) { + event->object->ProcessEventArgPtr( ev, args ); + } + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, ev->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::Init +================ +*/ +void idEvent::Init( void ) { + gameLocal.Printf( "Initializing event system\n" ); + + if ( eventError ) { + gameLocal.Error( "%s", eventErrorMsg ); + } + +#ifdef CREATE_EVENT_CODE + void CreateEventCallbackHandler(); + CreateEventCallbackHandler(); + gameLocal.Error( "Wrote event callback handler" ); +#endif + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + ClearEventList(); + return; + } + + ClearEventList(); + + eventDataAllocator.Init(); + + gameLocal.Printf( "...%i event definitions\n", idEventDef::NumEventCommands() ); + + // the event system has started + initialized = true; +} + +/* +================ +idEvent::Shutdown +================ +*/ +void idEvent::Shutdown( void ) { + gameLocal.Printf( "Shutdown event system\n" ); + + if ( !initialized ) { + gameLocal.Printf( "...not started\n" ); + return; + } + + ClearEventList(); + + eventDataAllocator.Shutdown(); + + // say it is now shutdown + initialized = false; +} + +/* +================ +idEvent::Save +================ +*/ +void idEvent::Save( idSaveGame *savefile ) { + idEvent *event; + + savefile->WriteInt( EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + savefile->Write( event->data, event->eventdef->GetArgSize() ); + + event = event->eventNode.Next(); + } +} + +/* +================ +idEvent::Restore +================ +*/ +void idEvent::Restore( idRestoreGame *savefile ) { + int i; + int num; + int argsize; + idStr name; + idEvent *event; + + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( EventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( !event->eventdef ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( !event->typeinfo ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + } + + savefile->ReadObject( event->object ); + assert( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + savefile->Read( event->data, argsize ); + } else { + event->data = NULL; + } + } +} + +#ifdef CREATE_EVENT_CODE +/* +================ +CreateEventCallbackHandler +================ +*/ +void CreateEventCallbackHandler( void ) { + int num; + int count; + int i, j, k; + char argString[ D_EVENT_MAXARGS + 1 ]; + idStr string1; + idStr string2; + idFile *file; + + file = fileSystem->OpenFileWrite( "Callbacks.cpp" ); + + file->Printf( "// generated file - see CREATE_EVENT_CODE\n\n" ); + + for( i = 1; i <= D_EVENT_MAXARGS; i++ ) { + + file->Printf( "\t/*******************************************************\n\n\t\t%d args\n\n\t*******************************************************/\n\n", i ); + + for ( j = 0; j < ( 1 << i ); j++ ) { + for ( k = 0; k < i; k++ ) { + argString[ k ] = j & ( 1 << k ) ? 'f' : 'i'; + } + argString[ i ] = '\0'; + + string1.Empty(); + string2.Empty(); + + for( k = 0; k < i; k++ ) { + if ( j & ( 1 << k ) ) { + string1 += "const float"; + string2 += va( "*( float * )&data[ %d ]", k ); + } else { + string1 += "const int"; + string2 += va( "data[ %d ]", k ); + } + + if ( k < i - 1 ) { + string1 += ", "; + string2 += ", "; + } + } + + file->Printf( "\tcase %d :\n\t\ttypedef void ( idClass::*eventCallback_%s_t )( %s );\n", ( 1 << ( i + D_EVENT_MAXARGS ) ) + j, argString, string1.c_str() ); + file->Printf( "\t\t( this->*( eventCallback_%s_t )callback )( %s );\n\t\tbreak;\n\n", argString, string2.c_str() ); + + } + } + + fileSystem->CloseFile( file ); +} + +#endif diff --git a/source/game/gamesys/Event.h b/source/game/gamesys/Event.h new file mode 100644 index 0000000..1bf6d7a --- /dev/null +++ b/source/game/gamesys/Event.h @@ -0,0 +1,184 @@ +/* +sys_event.h + +Event are used for scheduling tasks and for linking script commands. +*/ +#ifndef __SYS_EVENT_H__ +#define __SYS_EVENT_H__ + +#define D_EVENT_MAXARGS 8 // if changed, enable the CREATE_EVENT_CODE define in Event.cpp to generate switch statement for idClass::ProcessEventArgPtr. + // running the game will then generate c:\doom\base\events.txt, the contents of which should be copied into the switch statement. + +#define D_EVENT_VOID ( ( char )0 ) +#define D_EVENT_INTEGER 'd' +#define D_EVENT_FLOAT 'f' +#define D_EVENT_VECTOR 'v' +#define D_EVENT_STRING 's' +#define D_EVENT_ENTITY 'e' +#define D_EVENT_ENTITY_NULL 'E' // event can handle NULL entity pointers +#define D_EVENT_TRACE 't' + +#define MAX_EVENTS 8192 // Upped from 4096 + +class idClass; +class idTypeInfo; + +class idEventDef { +private: + const char *name; + const char *formatspec; + unsigned int formatspecIndex; + int returnType; + int numargs; + size_t argsize; + int argOffset[ D_EVENT_MAXARGS ]; + int eventnum; + const idEventDef * next; + + static idEventDef * eventDefList[MAX_EVENTS]; + static int numEventDefs; + +public: + idEventDef( const char *command, const char *formatspec = NULL, char returnType = 0 ); + + const char *GetName( void ) const; + const char *GetArgFormat( void ) const; + unsigned int GetFormatspecIndex( void ) const; + char GetReturnType( void ) const; + int GetEventNum( void ) const; + int GetNumArgs( void ) const; + size_t GetArgSize( void ) const; + int GetArgOffset( int arg ) const; + + static int NumEventCommands( void ); + static const idEventDef *GetEventCommand( int eventnum ); + static const idEventDef *FindEvent( const char *name ); +}; + +class idSaveGame; +class idRestoreGame; + +class idEvent { +private: + const idEventDef *eventdef; + byte *data; + int time; + idClass *object; + const idTypeInfo *typeinfo; + + idLinkList eventNode; + + static idDynamicBlockAlloc eventDataAllocator; + + +public: + static bool initialized; + + ~idEvent(); + + static void WriteDebugInfo( void ); + static idEvent *Alloc( const idEventDef *evdef, int numargs, va_list args ); + static void CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ); + + void Free( void ); + void Schedule( idClass *object, const idTypeInfo *cls, int time ); + byte *GetData( void ); + + static void CancelEvents( const idClass *obj, const idEventDef *evdef = NULL ); +// RAVEN BEGIN +// abahr: + static bool EventIsPosted( const idClass *obj, const idEventDef *evdef ); +// RAVEN END + static void ClearEventList( void ); + static void ServiceEvents( void ); + static void Init( void ); + static void Shutdown( void ); + + // save games + static void Save( idSaveGame *savefile ); // archives object for save game file + static void Restore( idRestoreGame *savefile ); // unarchives object from save game file +}; + +/* +================ +idEvent::GetData +================ +*/ +ID_INLINE byte *idEvent::GetData( void ) { + return data; +} + +/* +================ +idEventDef::GetName +================ +*/ +ID_INLINE const char *idEventDef::GetName( void ) const { + return name; +} + +/* +================ +idEventDef::GetArgFormat +================ +*/ +ID_INLINE const char *idEventDef::GetArgFormat( void ) const { + return formatspec; +} + +/* +================ +idEventDef::GetFormatspecIndex +================ +*/ +ID_INLINE unsigned int idEventDef::GetFormatspecIndex( void ) const { + return formatspecIndex; +} + +/* +================ +idEventDef::GetReturnType +================ +*/ +ID_INLINE char idEventDef::GetReturnType( void ) const { + return returnType; +} + +/* +================ +idEventDef::GetNumArgs +================ +*/ +ID_INLINE int idEventDef::GetNumArgs( void ) const { + return numargs; +} + +/* +================ +idEventDef::GetArgSize +================ +*/ +ID_INLINE size_t idEventDef::GetArgSize( void ) const { + return argsize; +} + +/* +================ +idEventDef::GetArgOffset +================ +*/ +ID_INLINE int idEventDef::GetArgOffset( int arg ) const { + assert( ( arg >= 0 ) && ( arg < D_EVENT_MAXARGS ) ); + return argOffset[ arg ]; +} + +/* +================ +idEventDef::GetEventNum +================ +*/ +ID_INLINE int idEventDef::GetEventNum( void ) const { + return eventnum; +} + +#endif /* !__SYS_EVENT_H__ */ diff --git a/source/game/gamesys/NoGameTypeInfo.h b/source/game/gamesys/NoGameTypeInfo.h new file mode 100644 index 0000000..532df85 --- /dev/null +++ b/source/game/gamesys/NoGameTypeInfo.h @@ -0,0 +1,56 @@ + +#ifndef __GAMETYPEINFO_H__ +#define __GAMETYPEINFO_H__ + +/* +=================================================================================== + + This file has been generated with the Type Info Generator v1.0 (c) 2004 id Software + +=================================================================================== +*/ + +typedef struct { + const char * name; + const char * type; + const char * value; +} constantInfo_t; + +typedef struct { + const char * name; + int value; +} enumValueInfo_t; + +typedef struct { + const char * typeName; + const enumValueInfo_t * values; +} enumTypeInfo_t; + +typedef struct { + const char * type; + const char * name; + int offset; + int size; +} classVariableInfo_t; + +typedef struct { + const char * typeName; + const char * superType; + int size; + const classVariableInfo_t * variables; +} classTypeInfo_t; + + +static constantInfo_t constantInfo[] = { + { NULL, NULL, NULL } +}; + +static enumTypeInfo_t enumTypeInfo[] = { + { NULL, NULL } +}; + +static classTypeInfo_t classTypeInfo[] = { + { NULL, NULL, 0, NULL } +}; + +#endif /* !__GAMETYPEINFO_H__ */ diff --git a/source/game/gamesys/SaveGame.cpp b/source/game/gamesys/SaveGame.cpp new file mode 100644 index 0000000..28d501d --- /dev/null +++ b/source/game/gamesys/SaveGame.cpp @@ -0,0 +1,2102 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +Save game related helper classes. + +Save games are implemented in two classes, idSaveGame and idRestoreGame, that implement write/read functions for +common types. They're passed in to each entity and object for them to archive themselves. Each class +implements save/restore functions for it's own data. When restoring, all the objects are instantiated, +then the restore function is called on each, superclass first, then subclasses. + +Pointers are restored by saving out an object index for each unique object pointer and adding them to a list of +objects that are to be saved. Restore instantiates all the objects in the list before calling the Restore function +on each object so that the pointers returned are valid. No object's restore function should rely on any other objects +being fully instantiated until after the restore process is complete. Post restore fixup should be done by posting +events with 0 delay. + +The savegame header will have the Game Name, Version, Map Name, and Player Persistent Info. + +Changes in version make savegames incompatible, and the game will start from the beginning of the level with +the player's persistent info. + +Changes to classes that don't need to break compatibilty can use the build number as the savegame version. +Later versions are responsible for restoring from previous versions by ignoring any unused data and initializing +variables that weren't in previous versions with safe information. + +At the head of the save game is enough information to restore the player to the beginning of the level should the +file be unloadable in some way (for example, due to script changes). +*/ + +// RAVEN BEGIN +// jscott: sanity length check for strings +#define MAX_PRINT_MSG 4096 +// RAVEN END + +/* +================ +idSaveGame::idSaveGame() +================ +*/ +idSaveGame::idSaveGame( idFile *savefile ) { + + file = savefile; + + // Put NULL at the start of the list so we can skip over it. + objects.Clear(); + objects.Append( NULL ); +} + +/* +================ +idSaveGame::~idSaveGame() +================ +*/ +idSaveGame::~idSaveGame( void ) { + if ( objects.Num() ) { + Close(); + } +} + +/* +================ +idSaveGame::Close +================ +*/ +void idSaveGame::Close( void ) { + int i; + + WriteSoundCommands(); + + // read trace models + idClipModel::SaveTraceModels( this ); + + for( i = 1; i < objects.Num(); i++ ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( objects[ i ]->GetType(), objects[ i ] ); + } + + objects.Clear(); + +#ifdef ID_DEBUG_MEMORY +// RAVEN BEGIN +// jscott: don't use type info +// idStr gameState = file->GetName(); +// gameState.StripFileExtension(); +// WriteGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ), false ) ); +// RAVEN END +#endif +} + +/* +================ +idSaveGame::WriteObjectList +================ +*/ +void idSaveGame::WriteObjectList( void ) { + int i; + + WriteInt( objects.Num() - 1 ); + for( i = 1; i < objects.Num(); i++ ) { + WriteString( objects[ i ]->GetClassname() ); + } +} + +/* +================ +idSaveGame::CallSave_r +================ +*/ +void idSaveGame::CallSave_r( const idTypeInfo *cls, const idClass *obj ) { + if ( cls->super ) { + CallSave_r( cls->super, obj ); + if ( cls->super->Save == cls->Save ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + WriteSyncId(); + ( obj->*cls->Save )( this ); + WriteSyncId(); +} + +/* +================ +idSaveGame::AddObject +================ +*/ +void idSaveGame::AddObject( const idClass *obj ) { + objects.AddUnique( obj ); +} + +/* +================ +idSaveGame::WriteSyncId +================ +*/ +void idSaveGame::WriteSyncId( void ) { + file->WriteSyncId(); +} + +/* +================ +idSaveGame::Write +================ +*/ +void idSaveGame::Write( const void *buffer, int len ) { + file->Write( buffer, len ); +} + +/* +================ +idSaveGame::WriteInt +================ +*/ +void idSaveGame::WriteInt( const int value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteJoint +================ +*/ +void idSaveGame::WriteJoint( const jointHandle_t value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteShort +================ +*/ +void idSaveGame::WriteShort( const short value ) { + file->WriteShort( value ); +} + +/* +================ +idSaveGame::WriteByte +================ +*/ +void idSaveGame::WriteByte( const byte value ) { + file->WriteUnsignedChar( value ); +} + +/* +================ +idSaveGame::WriteSignedChar +================ +*/ +void idSaveGame::WriteSignedChar( const signed char value ) { + file->WriteChar( value ); +} + +/* +================ +idSaveGame::WriteFloat +================ +*/ +void idSaveGame::WriteFloat( const float value ) { + file->WriteFloat( value ); +} + +/* +================ +idSaveGame::WriteBool +================ +*/ +void idSaveGame::WriteBool( const bool value ) { + file->WriteBool( value ); +} + +/* +================ +idSaveGame::WriteString +================ +*/ +void idSaveGame::WriteString( const char *string ) { + int len; + + len = strlen( string ); + +// RAVEN BEGIN +// jscott: added safety check for silly length strings + if( len < 0 || len >= MAX_PRINT_MSG ) { + + common->Error( "idSaveGame::WriteString invalid string length (%d)", len ); + } +// RAVEN END + + file->WriteString( string ); +} + +/* +================ +idSaveGame::WriteVec2 +================ +*/ +void idSaveGame::WriteVec2( const idVec2 &vec ) { + file->WriteVec2( vec ); +} + +/* +================ +idSaveGame::WriteVec3 +================ +*/ +void idSaveGame::WriteVec3( const idVec3 &vec ) { + file->WriteVec3( vec ); +} + +/* +================ +idSaveGame::WriteVec4 +================ +*/ +void idSaveGame::WriteVec4( const idVec4 &vec ) { + file->WriteVec4( vec ); +} + +/* +================ +idSaveGame::WriteVec5 +================ +*/ +void idSaveGame::WriteVec5( const idVec5 &vec ) { + file->WriteVec5( vec ); +} + +/* +================ +idSaveGame::WriteVec6 +================ +*/ +void idSaveGame::WriteVec6( const idVec6 &vec ) { + file->WriteVec6( vec ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteBounds( const idBounds &bounds ) { + file->Write( &bounds, sizeof( bounds ) ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteWinding( const idWinding &w ) +{ + int i, num; + num = w.GetNumPoints(); + WriteInt( num ); + for ( i = 0; i < num; i++ ) { + WriteVec5( w[i] ); + } +} + + +/* +================ +idSaveGame::WriteMat3 +================ +*/ +void idSaveGame::WriteMat3( const idMat3 &mat ) { + file->WriteMat3( mat ); +} + +/* +================ +idSaveGame::WriteAngles +================ +*/ +void idSaveGame::WriteAngles( const idAngles &angles ) { + file->Write( &angles, sizeof( angles ) ); +} + +/* +================ +idSaveGame::WriteObject +================ +*/ +void idSaveGame::WriteObject( const idClass *obj ) { + int index; + + index = objects.FindIndex( obj ); + if ( index < 0 ) { + gameLocal.DPrintf( "idSaveGame::WriteObject - WriteObject FindIndex failed\n" ); + + // Use the NULL index + index = 0; + } + + WriteInt( index ); +} + +/* +================ +idSaveGame::WriteStaticObject +================ +*/ +void idSaveGame::WriteStaticObject( const idClass &obj ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( obj.GetType(), &obj ); +} + +/* +================ +idSaveGame::WriteDict +================ +*/ +void idSaveGame::WriteDict( const idDict *dict ) { + int num; + int i; + const idKeyValue *kv; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !dict ) { + WriteInt( -1 ); + } else { + num = dict->GetNumKeyVals(); + WriteInt( num ); + for( i = 0; i < num; i++ ) { + kv = dict->GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + } + } +} + +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterial( const idMaterial *material ) { + if ( !material ) { + WriteString( "" ); + } else { + WriteString( material->GetName() ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterialType ( const rvDeclMatType* materialType ) { + if ( !materialType ) { + WriteString( "" ); + } else { + WriteString( materialType->GetName() ); + } +} + +/* +================ +idSaveGame::WriteTable +================ +*/ +void idSaveGame::WriteTable ( const idDeclTable* table ) { + if ( !table ) { + WriteString( "" ); + } else { + WriteString( table->GetName() ); + } +} +// RAVEN END + +/* +================ +idSaveGame::WriteSkin +================ +*/ +void idSaveGame::WriteSkin( const idDeclSkin *skin ) { + if ( !skin ) { + WriteString( "" ); + } else { + WriteString( skin->GetName() ); + } +} + +/* +================ +idSaveGame::WriteModelDef +================ +*/ +void idSaveGame::WriteModelDef( const idDeclModelDef *modelDef ) { + if ( !modelDef ) { + WriteString( "" ); + } else { + WriteString( modelDef->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSoundShader +================ +*/ +void idSaveGame::WriteSoundShader( const idSoundShader *shader ) { + const char *name; + + if ( !shader ) { + WriteString( "" ); + } else { + name = shader->GetName(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteModel +================ +*/ +void idSaveGame::WriteModel( const idRenderModel *model ) { + const char *name; + + if ( !model ) { + WriteString( "" ); + } else { + name = model->Name(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteUserInterface +================ +*/ +void idSaveGame::WriteUserInterface( const idUserInterface *ui, bool unique ) { + const char *name; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !ui ) { + WriteString( "" ); + } else { + name = ui->Name(); + WriteString( name ); + WriteBool( unique ); + if ( ui->WriteToSaveGame( file ) == false ) { + gameLocal.Error( "idSaveGame::WriteUserInterface: ui failed to write properly\n" ); + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteInt( extrap.GetStartValue() ); + WriteInt( extrap.GetBaseSpeed() ); + WriteInt( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteFloat( extrap.GetStartValue() ); + WriteFloat( extrap.GetBaseSpeed() ); + WriteFloat( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteVec3( extrap.GetStartValue() ); + WriteVec3( extrap.GetBaseSpeed() ); + WriteVec3( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteRenderEffect +================ +*/ +void idSaveGame::WriteRenderEffect( const renderEffect_t &renderEffect ) { + WriteSyncId(); + + WriteFloat( renderEffect.startTime ); + WriteInt( renderEffect.suppressSurfaceInViewID ); + WriteInt( renderEffect.allowSurfaceInViewID ); + WriteInt( renderEffect.groupID ); + + WriteVec3( renderEffect.origin ); + WriteMat3( renderEffect.axis ); + + WriteVec3( renderEffect.gravity ); + WriteVec3( renderEffect.endOrigin ); + + WriteFloat( renderEffect.attenuation ); + WriteBool( renderEffect.hasEndOrigin ); + WriteBool( renderEffect.loop ); + WriteBool( renderEffect.ambient ); + WriteBool( renderEffect.inConnectedArea ); + WriteInt( renderEffect.weaponDepthHackInViewID ); + WriteFloat( renderEffect.modelDepthHack ); + + WriteInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + WriteFloat( renderEffect.shaderParms[ ix ] ); + } + + if( renderEffect.declEffect ) { + WriteString( renderEffect.declEffect->GetName() ); + } else { + WriteString( "" ); + } +} + +/* +================ +idSaveGame::WriteFrustum +================ +*/ +void idSaveGame::WriteFrustum( const idFrustum& frustum ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteVec3( frustum.GetOrigin() ); + WriteMat3( frustum.GetAxis() ); + WriteFloat( frustum.GetNearDistance() ); + WriteFloat( frustum.GetFarDistance() ); + WriteFloat( frustum.GetLeft() ); + WriteFloat( frustum.GetUp() ); +} + +/* +================ +idSaveGame::WriteRenderEntity +================ +*/ +void idSaveGame::WriteRenderEntity( const renderEntity_t &renderEntity ) { + int i; + + WriteSyncId(); + + WriteModel( renderEntity.hModel ); + + WriteInt( renderEntity.entityNum ); + WriteInt( renderEntity.bodyId ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + WriteBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + + WriteInt( renderEntity.suppressSurfaceInViewID ); + WriteInt( renderEntity.suppressShadowInViewID ); + WriteInt( renderEntity.suppressShadowInLightID ); + WriteInt( renderEntity.allowSurfaceInViewID ); + + WriteInt( renderEntity.suppressSurfaceMask ); + + WriteVec3( renderEntity.origin ); + WriteMat3( renderEntity.axis ); + + WriteMaterial( renderEntity.customShader ); + WriteMaterial( renderEntity.referenceShader ); + WriteMaterial( renderEntity.overlayShader ); + WriteSkin( renderEntity.customSkin ); + + WriteInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + WriteUserInterface( renderEntity.gui[ i ], renderEntity.gui[ i ] ? renderEntity.gui[ i ]->IsUniqued() : false ); + } + + WriteFloat( renderEntity.modelDepthHack ); + + WriteBool( renderEntity.noSelfShadow ); + WriteBool( renderEntity.noShadow ); + WriteBool( renderEntity.noDynamicInteractions ); + WriteBool( renderEntity.forceUpdate ); + + WriteInt( renderEntity.weaponDepthHackInViewID ); + WriteFloat( renderEntity.shadowLODDistance ); + WriteInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idSaveGame::WriteRenderLight +================ +*/ +void idSaveGame::WriteRenderLight( const renderLight_t &renderLight ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteMat3( renderLight.axis ); + WriteVec3( renderLight.origin ); + + WriteInt( renderLight.suppressLightInViewID ); + WriteInt( renderLight.allowLightInViewID ); + WriteBool( renderLight.noShadows ); + WriteBool( renderLight.noSpecular ); + WriteBool( renderLight.noDynamicShadows ); + WriteBool( renderLight.pointLight ); + WriteBool( renderLight.parallel ); + WriteBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + WriteFloat( renderLight.detailLevel ); +// RAVEN END + + WriteVec3( renderLight.lightRadius ); + WriteVec3( renderLight.lightCenter ); + + WriteVec3( renderLight.target ); + WriteVec3( renderLight.right ); + WriteVec3( renderLight.up ); + WriteVec3( renderLight.start ); + WriteVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // WriteModel( renderLight.prelightModel ); + + WriteInt( renderLight.lightId ); + + WriteMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + WriteInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idSaveGame::WriteRefSound +================ +*/ +void idSaveGame::WriteRefSound( const refSound_t &refSound ) { +// RAVEN BEGIN + WriteSyncId(); + + WriteInt( refSound.referenceSoundHandle ); +// RAVEN END + WriteVec3( refSound.origin ); +// RAVEN BEGIN + WriteVec3( refSound.velocity ); +// RAVEN END + WriteInt( refSound.listenerId ); + WriteSoundShader( refSound.shader ); + WriteFloat( refSound.diversity ); + WriteBool( refSound.waitfortrigger ); + + WriteFloat( refSound.parms.minDistance ); + WriteFloat( refSound.parms.maxDistance ); + WriteFloat( refSound.parms.volume ); + WriteFloat( refSound.parms.shakes ); + WriteInt( refSound.parms.soundShaderFlags ); + WriteInt( refSound.parms.soundClass ); +} + +/* +================ +idSaveGame::WriteRenderView +================ +*/ +void idSaveGame::WriteRenderView( const renderView_t &view ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteInt( view.viewID ); + WriteInt( view.x ); + WriteInt( view.y ); + WriteInt( view.width ); + WriteInt( view.height ); + + WriteFloat( view.fov_x ); + WriteFloat( view.fov_y ); + WriteVec3( view.vieworg ); + WriteMat3( view.viewaxis ); + + WriteBool( view.cramZNear ); + + WriteInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + WriteFloat( view.shaderParms[ i ] ); + } +} + +/* +=================== +idSaveGame::WriteUsercmd +=================== +*/ +void idSaveGame::WriteUsercmd( const usercmd_t &usercmd ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteInt( usercmd.gameFrame ); + WriteInt( usercmd.gameTime ); + WriteInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + WriteShort( usercmd.buttons ); +// RAVEN END + WriteSignedChar( usercmd.forwardmove ); + WriteSignedChar( usercmd.rightmove ); + WriteSignedChar( usercmd.upmove ); + WriteShort( usercmd.angles[0] ); + WriteShort( usercmd.angles[1] ); + WriteShort( usercmd.angles[2] ); + WriteShort( usercmd.mx ); + WriteShort( usercmd.my ); + WriteSignedChar( usercmd.impulse ); + WriteByte( usercmd.flags ); + WriteInt( usercmd.sequence ); +} + +/* +=================== +idSaveGame::WriteContactInfo +=================== +*/ +void idSaveGame::WriteContactInfo( const contactInfo_t &contactInfo ) { + WriteInt( (int)contactInfo.type ); + WriteVec3( contactInfo.point ); + WriteVec3( contactInfo.normal ); + WriteFloat( contactInfo.dist ); + WriteInt( contactInfo.contents ); + WriteMaterial( contactInfo.material ); + WriteInt( contactInfo.modelFeature ); + WriteInt( contactInfo.trmFeature ); + WriteInt( contactInfo.entityNum ); + WriteInt( contactInfo.id ); + WriteMaterialType( contactInfo.materialType ); +} + +/* +=================== +idSaveGame::WriteTrace +=================== +*/ +void idSaveGame::WriteTrace( const trace_t &trace ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteFloat( trace.fraction ); + WriteVec3( trace.endpos ); + WriteMat3( trace.endAxis ); + WriteContactInfo( trace.c ); +} + +/* +=================== +idSaveGame::WriteClipModel +=================== +*/ +void idSaveGame::WriteClipModel( const idClipModel *clipModel ) { + if ( clipModel != NULL ) { + WriteBool( true ); + clipModel->Save( this ); + } else { + WriteBool( false ); + } +} + +/* +=================== +idSaveGame::WriteSoundCommands +=================== +*/ +void idSaveGame::WriteSoundCommands( void ) { + soundSystem->WriteToSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +====================== +idSaveGame::WriteBuildNumber +====================== +*/ +void idSaveGame::WriteBuildNumber( const int value ) { + WriteInt( value ); +} + + + + + + +/*********************************************************************** + + idRestoreGame + +***********************************************************************/ + +/* +================ +idRestoreGame::RestoreGame +================ +*/ +idRestoreGame::idRestoreGame( idFile *savefile ) { + file = savefile; +} + +/* +================ +idRestoreGame::~idRestoreGame() +================ +*/ +idRestoreGame::~idRestoreGame() { +} + +// RAVEN BEGIN +/* +================ +void idRestoreGame::CreateObjects +================ +*/ +void idRestoreGame::CreateObjects( void ) { + int i, num; + idStr classname; + idTypeInfo *type; + + ReadInt( num ); + + // create all the objects + objects.SetNum( num + 1 ); + memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); + + for ( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); + type = idClass::GetClass( classname ); + if ( !type ) { + Error( "idRestoreGame::CreateObjects: Unknown class '%s'", classname.c_str() ); + } + objects[ i ] = type->CreateInstance(); + assert( objects[ i ] != NULL ); + } +} + +/* +================ +void idRestoreGame::RestoreObjects +================ +*/ +void idRestoreGame::RestoreObjects( void ) { + int i; + + ReadSoundCommands(); + + // read trace models + idClipModel::RestoreTraceModels( this ); + + // restore all the objects + for( i = 1; i < objects.Num(); i++ ) { + file->ReadSyncId( "Restore objects", objects[ i ]->GetClassname() ); + CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); + } + + // regenerate render entities and render lights because are not saved + for( i = 1; i < objects.Num(); i++ ) { + if ( objects[ i ]->IsType( idEntity::GetClassType() ) ) { + idEntity *ent = static_cast( objects[ i ] ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} +// RAVEN END + +/* +==================== +void idRestoreGame::DeleteObjects +==================== +*/ +void idRestoreGame::DeleteObjects( void ) { + + // Remove the NULL object before deleting + objects.RemoveIndex( 0 ); + + objects.DeleteContents( true ); +} + +/* +================ +idRestoreGame::Error +================ +*/ +void idRestoreGame::Error( const char *fmt, ... ) { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN + // FIXME: this crashes. It now leaks, but that's better than crashing. + // The problem is that some entities delete attached ents that are also in this list. When this call gets to them + // it tries to delete an already deleted object +// objects.DeleteContents( true ); +// RAVEN END + + gameLocal.Error( "%s", text ); +} + +/* +================ +idRestoreGame::CallRestore_r +================ +*/ +void idRestoreGame::CallRestore_r( const idTypeInfo *cls, idClass *obj ) { + if ( cls->super ) { + CallRestore_r( cls->super, obj ); + if ( cls->super->Restore == cls->Restore ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + file->ReadSyncId( "Callrestore_r start ", cls->classname ); + ( obj->*cls->Restore )( this ); + file->ReadSyncId( "Callrestore_r end ", cls->classname ); +} + +/* +================ +idRestoreGame::Read +================ +*/ +void idRestoreGame::Read( void *buffer, int len ) { + file->Read( buffer, len ); +} + +/* +================ +idRestoreGame::ReadInt +================ +*/ +void idRestoreGame::ReadInt( int &value ) { + file->ReadInt( value ); +} + +/* +================ +idRestoreGame::ReadJoint +================ +*/ +void idRestoreGame::ReadJoint( jointHandle_t &value ) { + file->ReadInt( ( int &)value ); +} + +/* +================ +idRestoreGame::ReadShort +================ +*/ +void idRestoreGame::ReadShort( short &value ) { + file->ReadShort( value ); +} + +/* +================ +idRestoreGame::ReadByte +================ +*/ +void idRestoreGame::ReadByte( byte &value ) { + file->ReadUnsignedChar( value ); +} + +/* +================ +idRestoreGame::ReadSignedChar +================ +*/ +void idRestoreGame::ReadSignedChar( signed char &value ) { + file->ReadChar( ( char & )value ); +} + +/* +================ +idRestoreGame::ReadFloat +================ +*/ +void idRestoreGame::ReadFloat( float &value ) { + file->ReadFloat( value ); +} + +/* +================ +idRestoreGame::ReadBool +================ +*/ +void idRestoreGame::ReadBool( bool &value ) { + file->ReadBool( value ); +} + +/* +================ +idRestoreGame::ReadString +================ +*/ +void idRestoreGame::ReadString( idStr &string ) { +/* int len; + + ReadInt( len ); +// RAVEN BEGIN +// jscott: added max check - should be big enough + if ( len < 0 || len > MAX_PRINT_MSG ) { + Error( "idRestoreGame::ReadString: invalid length (%d)", len ); +// RAVEN END + } + + string.Fill( ' ', len );*/ + file->ReadString( string ); +} + +/* +================ +idRestoreGame::ReadVec2 +================ +*/ +void idRestoreGame::ReadVec2( idVec2 &vec ) { + file->ReadVec2( vec ); +} + +/* +================ +idRestoreGame::ReadVec3 +================ +*/ +void idRestoreGame::ReadVec3( idVec3 &vec ) { + file->ReadVec3( vec ); +} + +/* +================ +idRestoreGame::ReadVec4 +================ +*/ +void idRestoreGame::ReadVec4( idVec4 &vec ) { + file->ReadVec4( vec ); +} + +/* +================ +idRestoreGame::ReadVec5 +================ +*/ +void idRestoreGame::ReadVec5( idVec5 &vec ) { + file->ReadVec5( vec ); +} + +/* +================ +idRestoreGame::ReadVec6 +================ +*/ +void idRestoreGame::ReadVec6( idVec6 &vec ) { + file->ReadVec6( vec ); +} + +/* +================ +idRestoreGame::ReadBounds +================ +*/ +void idRestoreGame::ReadBounds( idBounds &bounds ) { + file->Read( &bounds, sizeof( bounds ) ); +} + +/* +================ +idRestoreGame::ReadWinding +================ +*/ +void idRestoreGame::ReadWinding( idWinding &w ) +{ + int i, num; + file->ReadInt( num ); + w.SetNumPoints( num ); + for ( i = 0; i < num; i++ ) { + file->ReadVec5( w[i] ); + } +} + +/* +================ +idRestoreGame::ReadMat3 +================ +*/ +void idRestoreGame::ReadMat3( idMat3 &mat ) { + file->ReadMat3( mat ); +} + +/* +================ +idRestoreGame::ReadAngles +================ +*/ +void idRestoreGame::ReadAngles( idAngles &angles ) { + file->Read( &angles, sizeof( angles ) ); +} + +/* +================ +idRestoreGame::ReadObject +================ +*/ +void idRestoreGame::ReadObject( idClass *&obj ) { + int index; + + ReadInt( index ); + if ( ( index < 0 ) || ( index >= objects.Num() ) ) { + Error( "idRestoreGame::ReadObject: invalid object index" ); + } + obj = objects[ index ]; +} + +/* +================ +idRestoreGame::ReadStaticObject +================ +*/ +void idRestoreGame::ReadStaticObject( idClass &obj ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadStaticObject", obj.GetClassname() ); +// RAVEN END + + CallRestore_r( obj.GetType(), &obj ); + +// RAVEN BEGIN + obj.PostEventMS( &EV_PostRestore, 0 ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadDict +================ +*/ +void idRestoreGame::ReadDict( idDict *dict ) { + int num; + int i; + idStr key; + idStr value; + +// RAVEN BEGIN + file->ReadSyncId( "ReadDict" ); +// RAVEN END + + ReadInt( num ); + + if ( num < 0 ) { + dict = NULL; + } else { + dict->Clear(); + for( i = 0; i < num; i++ ) { + ReadString( key ); + ReadString( value ); + dict->Set( key, value ); + } + } +} + +/* +================ +idRestoreGame::ReadMaterial +================ +*/ +void idRestoreGame::ReadMaterial( const idMaterial *&material ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + material = NULL; + } else { + material = declManager->FindMaterial( name ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idRestoreGame::ReadMaterialType +================ +*/ +void idRestoreGame::ReadMaterialType ( const rvDeclMatType* &materialType ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + materialType = NULL; + } else { + materialType = declManager->FindMaterialType ( name ); + } +} + +/* +================ +idRestoreGame::ReadTable +================ +*/ +void idRestoreGame::ReadTable ( const idDeclTable* &table ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + table = NULL; + } else { + table = declManager->FindTable( name ); + } +} + +// RAVEN END + +/* +================ +idRestoreGame::ReadSkin +================ +*/ +void idRestoreGame::ReadSkin( const idDeclSkin *&skin ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( name ); + } +} + +/* +================ +idRestoreGame::ReadSoundShader +================ +*/ +void idRestoreGame::ReadSoundShader( const idSoundShader *&shader ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + shader = NULL; + } else { + shader = declManager->FindSound( name ); + } +} + +/* +================ +idRestoreGame::ReadModelDef +================ +*/ +void idRestoreGame::ReadModelDef( const idDeclModelDef *&modelDef ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + modelDef = NULL; + } else { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + } +} + +/* +================ +idRestoreGame::ReadModel +================ +*/ +void idRestoreGame::ReadModel( idRenderModel *&model ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + model = NULL; + } else { + model = renderModelManager->FindModel( name ); + } +} + +/* +================ +idRestoreGame::ReadUserInterface +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadUserInterface( idUserInterface *&ui, const idDict *args ) { +// RAVEN END + idStr name; + +// RAVEN BEGIN + file->ReadSyncId( "ReadUserInterface" ); +// RAVEN END + + ReadString( name ); + if ( !name.Length() ) { + ui = NULL; + } else { + bool unique; + ReadBool( unique ); + ui = uiManager->FindGui( name, true, unique ); + if ( ui ) { + if ( ui->ReadFromSaveGame( file ) == false ) { + Error( "idSaveGame::ReadUserInterface: ui failed to read properly\n" ); + } else { +// RAVEN BEGIN + UpdateGuiParms( ui, args ); +// RAVEN END + } + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + int startValue; + int baseSpeed; + int speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( baseSpeed ); + ReadInt( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + float startValue; + float baseSpeed; + float speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( baseSpeed ); + ReadFloat( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + idVec3 startValue; + idVec3 baseSpeed; + idVec3 speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( baseSpeed ); + ReadVec3( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} +/* +================ +idRestoreGame::ReadRenderEffect +================ +*/ +void idRestoreGame::ReadRenderEffect( renderEffect_t &renderEffect ) { + idStr name; + + file->ReadSyncId( "ReadRenderEffect" ); + + renderEffect.declEffect = NULL; + + ReadFloat( renderEffect.startTime ); + ReadInt( renderEffect.suppressSurfaceInViewID ); + ReadInt( renderEffect.allowSurfaceInViewID ); + ReadInt( renderEffect.groupID ); + + ReadVec3( renderEffect.origin ); + ReadMat3( renderEffect.axis ); + + ReadVec3( renderEffect.gravity ); + ReadVec3( renderEffect.endOrigin ); + + ReadFloat( renderEffect.attenuation ); + ReadBool( renderEffect.hasEndOrigin ); + ReadBool( renderEffect.loop ); + ReadBool( renderEffect.ambient ); + ReadBool( renderEffect.inConnectedArea ); + ReadInt( renderEffect.weaponDepthHackInViewID ); + ReadFloat( renderEffect.modelDepthHack ); + + ReadInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + ReadFloat( renderEffect.shaderParms[ ix ] ); + } + + ReadString( name ); + if( name.Length() ) { + renderEffect.declEffect = declManager->FindType( DECL_EFFECT, name ); + } +} + +/* +================ +idRestoreGame::ReadFrustum +================ +*/ +void idRestoreGame::ReadFrustum( idFrustum& frustum ) { + idVec3 origin; + idMat3 axis; + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; +// RAVEN BEGIN + file->ReadSyncId( "ReadFrustum" ); +// RAVEN END + ReadVec3( origin ); + frustum.SetOrigin( origin ); + + ReadMat3( axis ); + frustum.SetAxis( axis ); + + ReadFloat( dNear ); + ReadFloat( dFar ); + ReadFloat( dLeft ); + ReadFloat( dUp ); + frustum.SetSize( dNear, dFar, dLeft, dUp ); +} + +/* +================ +idRestoreGame::ReadRenderEntity +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ) { +// RAVEN END + int i; + + file->ReadSyncId( "ReadRenderEntity" ); + + ReadModel( renderEntity.hModel ); + + ReadInt( renderEntity.entityNum ); + ReadInt( renderEntity.bodyId ); + + ReadBounds( renderEntity.bounds ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + // callback is set by class's Restore function + renderEntity.callback = NULL; + renderEntity.callbackData = NULL; + + ReadInt( renderEntity.suppressSurfaceInViewID ); + ReadInt( renderEntity.suppressShadowInViewID ); + ReadInt( renderEntity.suppressShadowInLightID ); + ReadInt( renderEntity.allowSurfaceInViewID ); + + ReadInt( renderEntity.suppressSurfaceMask ); + + ReadVec3( renderEntity.origin ); + ReadMat3( renderEntity.axis ); + + ReadMaterial( renderEntity.customShader ); + ReadMaterial( renderEntity.referenceShader ); + ReadMaterial( renderEntity.overlayShader ); + ReadSkin( renderEntity.customSkin ); + + ReadInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { +// RAVEN BEGIN + ReadUserInterface( renderEntity.gui[ i ], args ); +// RAVEN END + } + + // idEntity will restore "cameraTarget", which will be used in idEntity::Present to restore the remoteRenderView + renderEntity.remoteRenderView = NULL; + + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + + ReadFloat( renderEntity.modelDepthHack ); + + ReadBool( renderEntity.noSelfShadow ); + ReadBool( renderEntity.noShadow ); + ReadBool( renderEntity.noDynamicInteractions ); + ReadBool( renderEntity.forceUpdate ); + + ReadInt( renderEntity.weaponDepthHackInViewID ); + ReadFloat( renderEntity.shadowLODDistance ); + ReadInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idRestoreGame::ReadRenderLight +================ +*/ +void idRestoreGame::ReadRenderLight( renderLight_t &renderLight ) { + int i; + + file->ReadSyncId( "ReadRenderLight" ); + + ReadMat3( renderLight.axis ); + ReadVec3( renderLight.origin ); + + ReadInt( renderLight.suppressLightInViewID ); + ReadInt( renderLight.allowLightInViewID ); + ReadBool( renderLight.noShadows ); + ReadBool( renderLight.noSpecular ); + ReadBool( renderLight.noDynamicShadows ); + ReadBool( renderLight.pointLight ); + ReadBool( renderLight.parallel ); + ReadBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + ReadFloat( renderLight.detailLevel ); +// RAVEN END + + ReadVec3( renderLight.lightRadius ); + ReadVec3( renderLight.lightCenter ); + + ReadVec3( renderLight.target ); + ReadVec3( renderLight.right ); + ReadVec3( renderLight.up ); + ReadVec3( renderLight.start ); + ReadVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // ReadModel( renderLight.prelightModel ); + renderLight.prelightModel = NULL; + + ReadInt( renderLight.lightId ); + + ReadMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + ReadInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadRefSound +================ +*/ +void idRestoreGame::ReadRefSound( refSound_t &refSound ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadRefSound" ); +// RAVEN END + + ReadInt( refSound.referenceSoundHandle ); + ReadVec3( refSound.origin ); +// RAVEN BEGIN + ReadVec3( refSound.velocity ); +// RAVEN END + ReadInt( refSound.listenerId ); + ReadSoundShader( refSound.shader ); + ReadFloat( refSound.diversity ); + ReadBool( refSound.waitfortrigger ); + + ReadFloat( refSound.parms.minDistance ); + ReadFloat( refSound.parms.maxDistance ); + ReadFloat( refSound.parms.volume ); + ReadFloat( refSound.parms.shakes ); + ReadInt( refSound.parms.soundShaderFlags ); + ReadInt( refSound.parms.soundClass ); +} + +/* +================ +idRestoreGame::ReadRenderView +================ +*/ +void idRestoreGame::ReadRenderView( renderView_t &view ) { + int i; + +// RAVEN BEGIN + file->ReadSyncId( "ReadRenderView" ); +// RAVEN END + + ReadInt( view.viewID ); + ReadInt( view.x ); + ReadInt( view.y ); + ReadInt( view.width ); + ReadInt( view.height ); + + ReadFloat( view.fov_x ); + ReadFloat( view.fov_y ); + ReadVec3( view.vieworg ); + ReadMat3( view.viewaxis ); + + ReadBool( view.cramZNear ); + + ReadInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + ReadFloat( view.shaderParms[ i ] ); + } +} + +/* +================= +idRestoreGame::ReadUsercmd +================= +*/ +void idRestoreGame::ReadUsercmd( usercmd_t &usercmd ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadUsercmd" ); +// RAVEN END + ReadInt( usercmd.gameFrame ); + ReadInt( usercmd.gameTime ); + ReadInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + ReadShort( usercmd.buttons ); +// RAVEN END + ReadSignedChar( usercmd.forwardmove ); + ReadSignedChar( usercmd.rightmove ); + ReadSignedChar( usercmd.upmove ); + ReadShort( usercmd.angles[0] ); + ReadShort( usercmd.angles[1] ); + ReadShort( usercmd.angles[2] ); + ReadShort( usercmd.mx ); + ReadShort( usercmd.my ); + ReadSignedChar( usercmd.impulse ); + ReadByte( usercmd.flags ); + ReadInt( usercmd.sequence ); +} + +/* +=================== +idRestoreGame::ReadContactInfo +=================== +*/ +void idRestoreGame::ReadContactInfo( contactInfo_t &contactInfo ) { + ReadInt( (int &)contactInfo.type ); + ReadVec3( contactInfo.point ); + ReadVec3( contactInfo.normal ); + ReadFloat( contactInfo.dist ); + ReadInt( contactInfo.contents ); + ReadMaterial( contactInfo.material ); + ReadInt( contactInfo.modelFeature ); + ReadInt( contactInfo.trmFeature ); + ReadInt( contactInfo.entityNum ); + ReadInt( contactInfo.id ); + ReadMaterialType( contactInfo.materialType ); +} + +/* +=================== +idRestoreGame::ReadTrace +=================== +*/ +void idRestoreGame::ReadTrace( trace_t &trace ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadTrace" ); +// RAVEN END + ReadFloat( trace.fraction ); + ReadVec3( trace.endpos ); + ReadMat3( trace.endAxis ); + ReadContactInfo( trace.c ); +} + +/* +===================== +idRestoreGame::ReadClipModel +===================== +*/ +void idRestoreGame::ReadClipModel( idClipModel *&clipModel ) { + bool restoreClipModel; + + ReadBool( restoreClipModel ); + if ( restoreClipModel ) { + clipModel = new idClipModel(); + clipModel->Restore( this ); + } else { + clipModel = NULL; + } +} + +/* +===================== +idRestoreGame::ReadSoundCommands +===================== +*/ +void idRestoreGame::ReadSoundCommands( void ) { + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + soundSystem->ReadFromSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +===================== +idRestoreGame::ReadBuildNumber +===================== +*/ +void idRestoreGame::ReadBuildNumber( void ) { + ReadInt( buildNumber ); +} + +/* +===================== +idRestoreGame::GetBuildNumber +===================== +*/ +int idRestoreGame::GetBuildNumber( void ) { + return buildNumber; +} + + + +void Cmd_CheckSave_f( const idCmdArgs &args ) +{ + idPlayer *lp = gameLocal.GetLocalPlayer(); + idFile *mp = fileSystem->GetNewFileMemory(); + idSaveGame sg( mp ); + + sg.CallSave_r( lp->GetType(), lp ); + + + mp->Rewind(); + idPlayer test; + idRestoreGame rg( mp ); + + rg.CallRestore_r( test.GetType(), &test ); +} + diff --git a/source/game/gamesys/SaveGame.h b/source/game/gamesys/SaveGame.h new file mode 100644 index 0000000..4abd3c3 --- /dev/null +++ b/source/game/gamesys/SaveGame.h @@ -0,0 +1,183 @@ + +#ifndef __SAVEGAME_H__ +#define __SAVEGAME_H__ + +/* + +Save game related helper classes. + +*/ + +const int INITIAL_RELEASE_BUILD_NUMBER = 1262; + +class idSaveGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idSaveGame( idFile *savefile ); + ~idSaveGame(); + + void Close( void ); + + void AddObject( const idClass *obj ); + void WriteObjectList( void ); + + void Write( const void *buffer, int len ); + void WriteInt( const int value ); + void WriteJoint( const jointHandle_t value ); + void WriteShort( const short value ); + void WriteByte( const byte value ); + void WriteSignedChar( const signed char value ); + void WriteFloat( const float value ); + void WriteBool( const bool value ); + void WriteString( const char *string ); + void WriteVec2( const idVec2 &vec ); + void WriteVec3( const idVec3 &vec ); + void WriteVec4( const idVec4 &vec ); + void WriteVec5( const idVec5 &vec ); + void WriteVec6( const idVec6 &vec ); + void WriteWinding( const idWinding &winding ); + void WriteBounds( const idBounds &bounds ); + void WriteMat3( const idMat3 &mat ); + void WriteAngles( const idAngles &angles ); + void WriteObject( const idClass *obj ); + void WriteStaticObject( const idClass &obj ); + void WriteDict( const idDict *dict ); + void WriteMaterial( const idMaterial *material ); + void WriteSkin( const idDeclSkin *skin ); +// RAVEN BEGIN +// jscott: not using +// void WriteParticle( const idDeclParticle *particle ); +// void WriteFX( const idDeclFX *fx ); +// RAVEN END + void WriteSoundShader( const idSoundShader *shader ); + void WriteModelDef( const class idDeclModelDef *modelDef ); + void WriteModel( const idRenderModel *model ); +// RAVEN BEGIN +// bdube: material type + void WriteMaterialType ( const rvDeclMatType* matType ); + void WriteTable ( const idDeclTable* table ); +// abahr + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteRenderEffect( const renderEffect_t &renderEffect ); + void WriteFrustum( const idFrustum& frustum ); + void WriteSyncId( void ); +// RAVEN END + void WriteUserInterface( const idUserInterface *ui, bool unique ); + void WriteRenderEntity( const renderEntity_t &renderEntity ); + void WriteRenderLight( const renderLight_t &renderLight ); + void WriteRefSound( const refSound_t &refSound ); + void WriteRenderView( const renderView_t &view ); + void WriteUsercmd( const usercmd_t &usercmd ); + void WriteContactInfo( const contactInfo_t &contactInfo ); + void WriteTrace( const trace_t &trace ); + void WriteClipModel( const class idClipModel *clipModel ); + void WriteSoundCommands( void ); + + void WriteBuildNumber( const int value ); + +protected: + idFile * file; + + idList objects; + + void CallSave_r( const idTypeInfo *cls, const idClass *obj ); +}; + +class idRestoreGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idRestoreGame( idFile *savefile ); + ~idRestoreGame(); + + void CreateObjects( void ); + void RestoreObjects( void ); + void DeleteObjects( void ); + + void Error( const char *fmt, ... ); + + void Read( void *buffer, int len ); + void ReadInt( int &value ); + void ReadJoint( jointHandle_t &value ); + void ReadShort( short &value ); + void ReadByte( byte &value ); + void ReadSignedChar( signed char &value ); + void ReadFloat( float &value ); + void ReadBool( bool &value ); + void ReadString( idStr &string ); + void ReadVec2( idVec2 &vec ); + void ReadVec3( idVec3 &vec ); + void ReadVec4( idVec4 &vec ); + void ReadVec5( idVec5 &vec ); + void ReadVec6( idVec6 &vec ); + void ReadWinding( idWinding &winding ); + void ReadBounds( idBounds &bounds ); + void ReadMat3( idMat3 &mat ); + void ReadAngles( idAngles &angles ); + void ReadObject( idClass *&obj ); + void ReadStaticObject( idClass &obj ); + void ReadDict( idDict *dict ); + void ReadMaterial( const idMaterial *&material ); + void ReadSkin( const idDeclSkin *&skin ); +// RAVEN BEGIN +// bdube: not using +// void ReadParticle( const idDeclParticle *&particle ); +// void ReadFX( const idDeclFX *&fx ); +// RAVEN END + void ReadSoundShader( const idSoundShader *&shader ); + void ReadModelDef( const idDeclModelDef *&modelDef ); + void ReadModel( idRenderModel *&model ); +// RAVEN BEGIN + void ReadUserInterface( idUserInterface *&ui, const idDict *args ); +// bdube: material type + void ReadMaterialType ( const rvDeclMatType* &matType ); + void ReadTable ( const idDeclTable* &table ); +// abahr + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadRenderEffect( renderEffect_t &renderEffect ); + void ReadFrustum( idFrustum& frustum ); + void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) { file->ReadSyncId( detail, classname ); } + void ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ); +// RAVEN END + void ReadRenderLight( renderLight_t &renderLight ); + void ReadRefSound( refSound_t &refSound ); + void ReadRenderView( renderView_t &view ); + void ReadUsercmd( usercmd_t &usercmd ); + void ReadContactInfo( contactInfo_t &contactInfo ); + void ReadTrace( trace_t &trace ); + void ReadClipModel( idClipModel *&clipModel ); + void ReadSoundCommands( void ); + + void ReadBuildNumber( void ); + + // Used to retrieve the saved game buildNumber from within class Restore methods + int GetBuildNumber( void ); + +private: + int buildNumber; + + idFile * file; + + idList objects; + + void CallRestore_r( const idTypeInfo *cls, idClass *obj ); +}; + +#endif /* !__SAVEGAME_H__*/ diff --git a/source/game/gamesys/State.cpp b/source/game/gamesys/State.cpp new file mode 100644 index 0000000..e485203 --- /dev/null +++ b/source/game/gamesys/State.cpp @@ -0,0 +1,424 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const int HISTORY_COUNT = 50; + +/* +===================== +stateParms_t::Save +===================== +*/ +void stateParms_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( blendFrames ); + saveFile->WriteInt( time ); + saveFile->WriteInt( stage ); +} + +/* +===================== +stateParms_t::Restore +===================== +*/ +void stateParms_t::Restore( idRestoreGame *saveFile ) { + saveFile->ReadInt( blendFrames ); + saveFile->ReadInt( time ); + saveFile->ReadInt( stage ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( state->name ); + // TOSAVE: idLinkList node; + saveFile->WriteInt( flags ); + saveFile->WriteInt( delay ); + parms.Save( saveFile ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Restore( idRestoreGame *saveFile, const idClass* owner ) { + idStr name; + + saveFile->ReadString( name ); + state = owner->FindState( name ); + + saveFile->ReadInt( flags ); + saveFile->ReadInt( delay ); + parms.Restore( saveFile ); +} + +/* +===================== +rvStateThread::rvStateThread +===================== +*/ +rvStateThread::rvStateThread ( void ) { + owner = NULL; + insertAfter = NULL; + lastResult = SRESULT_DONE; + + states.Clear ( ); + interrupted.Clear ( ); + + memset ( &fl, 0, sizeof(fl) ); +} + +/* +===================== +rvStateThread::~rvStateThread +===================== +*/ +rvStateThread::~rvStateThread ( void ) { + Clear ( true ); +} + +/* +===================== +rvStateThread::SetOwner +===================== +*/ +void rvStateThread::SetOwner ( idClass* _owner ) { + owner = _owner; +} + +/* +===================== +rvStateThread::Post +===================== +*/ +stateResult_t rvStateThread::PostState ( const char* name, int blendFrames, int delay, int flags ) { + const rvStateFunc* func; + + // Make sure the state exists before queueing it + if ( NULL == (func = owner->FindState ( name ) ) ) { + return SRESULT_ERROR; + } + + stateCall_t* call; + call = new stateCall_t; + call->state = func; + call->delay = delay; + call->flags = flags; + call->parms.blendFrames = blendFrames; + call->parms.time = -1; + call->parms.stage = 0; + + call->node.SetOwner ( call ); + + if ( fl.executing && insertAfter ) { + call->node.InsertAfter ( insertAfter->node ); + } else { + call->node.AddToEnd ( states ); + } + + insertAfter = call; + + return SRESULT_OK; +} + +/* +===================== +rvStateThread::Set +===================== +*/ +stateResult_t rvStateThread::SetState ( const char* name, int blendFrames, int delay, int flags ) { + Clear ( ); + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::InterruptState +===================== +*/ +stateResult_t rvStateThread::InterruptState ( const char* name, int blendFrames, int delay, int flags ) { + stateCall_t* call; + + // Move all states to the front of the interrupted list in the same order + for ( call = states.Prev(); call; call = states.Prev() ) { + call->node.Remove ( ); + call->node.AddToFront ( interrupted ); + } + + // Nothing to insert after anymore + insertAfter = NULL; + fl.stateInterrupted = true; + + // Post the state now + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::CurrentStateIs +===================== +*/ +bool rvStateThread::CurrentStateIs( const char* name ) const { + return ( !IsIdle() ) ? owner->FindState(name) == GetState()->state : false; +} + +/* +===================== +rvStateThread::Clear +===================== +*/ +void rvStateThread::Clear ( bool ignoreStateCalls ) { + stateCall_t* call; + + // Clear all states from the main state list + for( call = states.Next(); call != NULL; call = states.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + // Clear all interrupted states + for( call = interrupted.Next(); call != NULL; call = interrupted.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + insertAfter = NULL; + fl.stateCleared = true; + + states.Clear ( ); + interrupted.Clear ( ); +} + +/* +===================== +rvStateThread::Execute +===================== +*/ +stateResult_t rvStateThread::Execute ( void ) { + stateCall_t* call = NULL; + int count; + const char* stateName; + int stateStage; + const char* historyState[HISTORY_COUNT]; + int historyStage[HISTORY_COUNT]; + int historyStart; + int historyEnd; + + // If our main state loop is empty copy over any states in the interrupted state + if ( !states.Next ( ) ) { + for ( call = interrupted.Next(); call; call = interrupted.Next() ) { + call->node.Remove ( ); + call->node.AddToEnd ( states ); + } + assert ( !interrupted.Next ( ) ); + } + + // State thread is idle if there are no states + if ( !states.Next() ) { + return SRESULT_IDLE; + } + + fl.executing = true; + + // Run through the states until there are no more or one of them tells us to wait + count = 0; + historyStart = 0; + historyEnd = 0; + + for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count ) { + insertAfter = call; + fl.stateCleared = false; + fl.stateInterrupted = false; + + // If this state is only called when being cleared then just skip it + if ( call->flags & SFLAG_ONCLEARONLY ) { + call->node.Remove ( ); + delete call; + continue; + } + + // If the call has a delay on it the time will be set to negative initially and then + // converted to game time. + if ( call->parms.time <= 0 ) { + call->parms.time = gameLocal.time; + } + + // Check for delayed states + if ( call->delay && gameLocal.time < call->parms.time + call->delay ) { + fl.executing = false; + return SRESULT_WAIT; + } + + // Debugging + if ( lastResult != SRESULT_WAIT ) { + if ( *g_debugState.GetString ( ) && (*g_debugState.GetString ( ) == '*' || !idStr::Icmp ( g_debugState.GetString ( ), name ) ) ) { + if ( call->parms.stage ) { + gameLocal.Printf ( "%s: %s (%d)\n", name.c_str(), call->state->name, call->parms.stage ); + } else { + gameLocal.Printf ( "%s: %s\n", name.c_str(), call->state->name ); + } + } + + // Keep a history of the called states so we can dump them on an overflow + historyState[historyEnd] = call->state->name; + historyStage[historyEnd] = call->parms.stage; + historyEnd = (historyEnd+1) % HISTORY_COUNT; + if ( historyEnd == historyStart ) { + historyStart = (historyEnd+1) % HISTORY_COUNT; + } + } + + // Cache name and stage for error messages + stateName = call->state->name; + stateStage = call->parms.stage; + + // Actually call the state function + lastResult = owner->ProcessState ( call->state, call->parms ); + switch ( lastResult ) { + case SRESULT_WAIT: + fl.executing = false; + return SRESULT_WAIT; + + case SRESULT_ERROR: + gameLocal.Error ( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage ); + fl.executing = false; + return SRESULT_ERROR; + } + + // Dont remove the node if it was interrupted or cleared in the last process + if ( !fl.stateCleared && !fl.stateInterrupted ) { + if( lastResult >= SRESULT_SETDELAY ) { + call->delay = lastResult - SRESULT_SETDELAY; + call->parms.time = gameLocal.GetTime(); + continue; + } else if ( lastResult >= SRESULT_SETSTAGE ) { + call->parms.stage = lastResult - SRESULT_SETSTAGE; + continue; + } + + // Done with state so remove it from list + call->node.Remove ( ); + delete call; + } + + // Finished the last state but wait a frame for next one + if ( lastResult == SRESULT_DONE_WAIT ) { + fl.executing = false; + return SRESULT_WAIT; + } + } + + // Runaway state loop? + if ( count >= HISTORY_COUNT ) { + idFile *file; + + fileSystem->RemoveFile ( "statedump.txt" ); + file = fileSystem->OpenFileWrite( "statedump.txt" ); + + for ( ; historyStart != historyEnd; historyStart = (historyStart + 1) % HISTORY_COUNT ) { + if ( historyStage[historyStart] ) { + gameLocal.Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + gameLocal.Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + if ( file ) { + if ( historyStage[historyStart] ) { + file->Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + file->Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + } + } + if ( file ) { + fileSystem->CloseFile( file ); + } + + gameLocal.Error ( "rvStateThread: run away state loop '%s'", name.c_str() ); + } + + insertAfter = NULL; + fl.executing = false; + + // Move interrupted states back into the main state list when the main state list is empty + if ( !states.Next() && interrupted.Next ( ) ) { + return Execute ( ); + } + + return lastResult; +} + +/* +===================== +rvStateThread::Save +===================== +*/ +void rvStateThread::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( name.c_str() ); + + // No need to save owner, its setup in restore + + saveFile->WriteInt( lastResult ); + saveFile->Write ( &fl, sizeof(fl) ); + + saveFile->WriteInt( states.Num() ); + for( idLinkList* node = states.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + saveFile->WriteInt( interrupted.Num() ); + for( idLinkList* node = interrupted.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + // TOSAVE: stateCall_t* insertAfter; + // TOSAVE: stateResult_t lastResult; +} + +/* +===================== +rvStateThread::Restore +===================== +*/ +void rvStateThread::Restore( idRestoreGame *saveFile, idClass* owner ) { + int numStates; + stateCall_t* call = NULL; + + saveFile->ReadString( name ); + + this->owner = owner; + + saveFile->ReadInt( (int&)lastResult ); + saveFile->Read ( &fl, sizeof(fl) ); + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( states ); + } + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( interrupted ); + } +} diff --git a/source/game/gamesys/State.h b/source/game/gamesys/State.h new file mode 100644 index 0000000..27ca629 --- /dev/null +++ b/source/game/gamesys/State.h @@ -0,0 +1,157 @@ +#ifndef __SYS_STATE_H__ +#define __SYS_STATE_H__ + +typedef enum { + SRESULT_OK, // Call was made successfully + SRESULT_ERROR, // An unrecoverable error occurred + SRESULT_DONE, // Done with current state, move to next + SRESULT_DONE_WAIT, // Done with current state, wait a frame then move to next + SRESULT_WAIT, // Wait a frame and re-run current state + SRESULT_IDLE, // State thread is currently idle (ie. no states) + SRESULT_SETSTAGE, // Sets the current stage of the current state and reruns the state + // NOTE: this has to be the last result becuase the stage is added to + // the result. + SRESULT_SETDELAY = SRESULT_SETSTAGE + 20 +} stateResult_t; + +#define MAX_STATE_CALLS 50 + +#define SRESULT_STAGE(x) ((stateResult_t)((int)SRESULT_SETSTAGE + (int)(x))) +#define SRESULT_DELAY(x) ((stateResult_t)((int)SRESULT_SETDELAY + (int)(x))) + +struct stateParms_t { + int blendFrames; + int time; + int stage; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); +}; + +typedef stateResult_t ( idClass::*stateCallback_t )( const stateParms_t& parms ); + +template< class Type > +struct rvStateFunc { + const char* name; + stateCallback_t function; +}; + +/* +================ +CLASS_STATES_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass that +wishes to have its own custom states. Its prototypes variables used in the process +of managing states. +================ +*/ +#define CLASS_STATES_PROTOTYPE(nameofclass) \ +protected: \ + static rvStateFunc stateCallbacks[] + +/* +================ +CLASS_STATES_DECLARATION + +This macro must be included in the code to properly initialize variables +used in state processing for a idClass dervied class +================ +*/ +#define CLASS_STATES_DECLARATION(nameofclass) \ +rvStateFunc nameofclass::stateCallbacks[] = { + +/* +================ +STATE + +This macro declares a single state. It must be surrounded by the CLASS_STATES_DECLARATION +and END_CLASS_STATES macros. +================ +*/ +#define STATE(statename,function) { statename, (stateCallback_t)( &function ) }, + +/* +================ +END_CLASS_STATES + +Terminates a state block +================ +*/ +#define END_CLASS_STATES { NULL, NULL } }; + +struct stateCall_t { + const rvStateFunc* state; + idLinkList node; + int flags; + int delay; + stateParms_t parms; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, const idClass* owner ); +}; + +class idClass; + +const int SFLAG_ONCLEAR = BIT(0); // Executes, even if the state queue is cleared +const int SFLAG_ONCLEARONLY = BIT(1); // Executes only if the state queue is cleared + +class rvStateThread { +public: + + rvStateThread ( void ); + ~rvStateThread ( void ); + + void SetName ( const char* name ); + void SetOwner ( idClass* owner ); + + bool Interrupt ( void ); + + stateResult_t InterruptState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t PostState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t SetState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateCall_t* GetState ( void ) const; + bool CurrentStateIs ( const char* name ) const; + + stateResult_t Execute ( void ); + + void Clear ( bool ignoreStateCalls = false ); + + bool IsIdle ( void ) const; + bool IsExecuting ( void ) const; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, idClass* owner ); + +protected: + + struct flags { + bool stateCleared :1; // State list was cleared + bool stateInterrupted :1; // State list was interrupted + bool executing :1; // Execute is currently processing states + } fl; + + idStr name; + idClass* owner; + idLinkList states; + idLinkList interrupted; + stateCall_t* insertAfter; + stateResult_t lastResult; +}; + +ID_INLINE void rvStateThread::SetName ( const char* _name ) { + name = _name; +} + +ID_INLINE stateCall_t* rvStateThread::GetState ( void ) const { + return states.Next(); +} + +ID_INLINE bool rvStateThread::IsIdle ( void ) const { + return !states.Next() && !interrupted.Next(); +} + +ID_INLINE bool rvStateThread::IsExecuting ( void ) const { + return fl.executing; +} + +#endif // __SYS_STATE_H__ diff --git a/source/game/gamesys/SysCmds.cpp b/source/game/gamesys/SysCmds.cpp new file mode 100644 index 0000000..ab92878 --- /dev/null +++ b/source/game/gamesys/SysCmds.cpp @@ -0,0 +1,3245 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +#include "../ai/AI.h" +#if !defined(__GAME_PROJECTILE_H__) + #include "../Projectile.h" +#endif +#if !defined(__GAME_WEAPON_H__) + #include "../Weapon.h" +#endif +#if !defined(__GAME_SPAWNER_H__) + #include "../spawner.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "../Vehicle/Vehicle.h" +#endif +#if !defined(__AI_MANAGER_H__) + #include "../ai/AI_Manager.h" +#endif +#if !defined(__INSTANCE_H__) + #include "../Instance.h" +#endif +// RAVEN END + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +================== +Cmd_GetFloatArg +================== +*/ +float Cmd_GetFloatArg( const idCmdArgs &args, int &argNum ) { + const char *value; + + value = args.Argv( argNum++ ); + return atof( value ); +} + +/* +=================== +Cmd_EntityList_f +=================== +*/ +void Cmd_EntityList_f( const idCmdArgs &args ) { + int e; + idEntity *check; + int count; + size_t size; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + size = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + + if ( !check ) { + continue; + } + + if ( !check->name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s\n", e, + check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + + count++; + size += check->spawnArgs.Allocated(); + } + + gameLocal.Printf( "...%d entities\n...%d bytes of spawnargs\n", count, size ); +} + +/* +=================== +Cmd_ClientEntityList_f +=================== +*/ +void Cmd_ClientEntityList_f( const idCmdArgs &args ) { + int e; + rvClientEntity *check; + int count; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + + gameLocal.Printf( "%-4s %-20s\n", " Num", "Classname" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_CENTITIES; e++ ) { + check = gameLocal.clientEntities[ e ]; + + idStr name( check->GetClassType().classname ); + + if ( !check ) { + continue; + } + + if ( !name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s\n", e, name.c_str() ); + + count++; + } + + gameLocal.Printf( "...%d entities\n", count ); +} + + +/* +=================== +Cmd_ActiveEntityList_f +=================== +*/ +void Cmd_ActiveEntityList_f( const idCmdArgs &args ) { + idEntity *check; + int count; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( check = gameLocal.activeEntities.Next(); check != NULL; check = check->activeNode.Next() ) { + char dormant = check->fl.isDormant ? '-' : ' '; + gameLocal.Printf( "%4i:%c%-20s %-20s %s\n", check->entityNumber, dormant, check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + count++; + } + + gameLocal.Printf( "...%d active entities\n", count ); +} + +/* +=================== +Cmd_ListSpawnArgs_f +=================== +*/ +void Cmd_ListSpawnArgs_f( const idCmdArgs &args ) { + int i; + idEntity *ent; + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + for ( i = 0; i < ent->spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = ent->spawnArgs.GetKeyVal( i ); + gameLocal.Printf( "\"%s\" "S_COLOR_WHITE"\"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +=================== +Cmd_ReloadScript_f +=================== +*/ +void Cmd_ReloadScript_f( const idCmdArgs &args ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + // error out so that the user can rerun the scripts + gameLocal.Error( "Exiting map to reload scripts" ); +} + +/* +=================== +Cmd_Script_f +=================== +*/ +void Cmd_Script_f( const idCmdArgs &args ) { + const char * script; + idStr text; + idStr funcname; + static int funccount = 0; + idThread * thread; + const function_t *func; + idEntity *ent; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + sprintf( funcname, "ConsoleFunction_%d", funccount++ ); + + script = args.Args(); +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + text = va( "void %s() {%s;}\n", funcname.c_str(), script ); +// RAVEN END + if ( gameLocal.program.CompileText( "console", text, true ) ) { + func = gameLocal.program.FindFunction( funcname ); + if ( func ) { + // set all the entity names in case the user named one in the script that wasn't referenced in the default script + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + gameLocal.program.SetEntity( ent->name, ent ); + } + + thread = new idThread( func ); + thread->Start(); + } + } +} + +// RAVEN BEGIN +// jscott: exports for tracking memory +/* +================== +idGameEdit::ScriptSummary +================== +*/ +size_t idGameEdit::ScriptSummary( const idCmdArgs &args ) const { + + return( gameLocal.program.ScriptSummary( args ) ); +} + +/* +================== +idGameEdit::ClassSummary +================== +*/ +size_t idGameEdit::ClassSummary( const idCmdArgs &args ) const { + + common->Printf( "Classes - %dK\n", idClass::GetUsedMemory() / 1024 ); + + return( idClass::GetUsedMemory() / 1024 ); +} + +/* +================== +idGameEdit::EntitySummary +================== +*/ + +size_t idGameEdit::EntitySummary( const idCmdArgs &args ) const { + + common->Printf( "CL & SV ents - %dK\n", gameLocal.GetEntityMemoryUsage () / 1024); + + return gameLocal.GetEntityMemoryUsage() / 1024; +} +// RAVEN END + +/* +================== +KillEntities + +Kills all the entities of the given class in a level. +================== +*/ +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { + idEntity *ent; + idStrList ignore; + const char *name; + int i; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( i = 1; i < args.Argc(); i++ ) { + name = args.Argv( i ); + ignore.Append( name ); + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( superClass ) ) { + for( i = 0; i < ignore.Num(); i++ ) { + if ( ignore[ i ] == ent->name ) { + break; + } + } + + if ( i >= ignore.Num() ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + } +} + +/* +================== +Cmd_KillMonsters_f + +Kills all the monsters in a level. +================== +*/ +void Cmd_KillMonsters_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAI::GetClassType() ); +// nmckenzie: rvSpawners + KillEntities( args, rvSpawner::GetClassType() ); + + // kill any projectiles as well since they have pointers to the monster that created them + KillEntities( args, idProjectile::GetClassType() ); +// RAVEN END +} + +/* +================== +Cmd_KillMovables_f + +Kills all the moveables in a level. +================== +*/ +void Cmd_KillMovables_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idMoveable::GetClassType() ); +// RAVEN END +} + +// RAVEN BEGIN +// bdube: vehicle code +/* +================== +Cmd_KillVehicles_f +================== +*/ +void Cmd_KillVehicles_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + rvVehicleController::KillVehicles ( ); +} + +void Cmd_KillMessage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + gameLocal.mpGame.SendDeathMessage( gameLocal.GetLocalPlayer(), gameLocal.GetLocalPlayer(), 2 ); +} + +void Cmd_APState_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for ( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + gameLocal.Printf ( "Assault point #%d: owner: %d\n", gameLocal.mpGame.assaultPoints[i]->GetIndex(), gameLocal.mpGame.assaultPoints[i]->GetOwner() ); + } +} +// RAVEN END + +/* +================== +Cmd_KillRagdolls_f + +Kills all the ragdolls in a level. +================== +*/ +void Cmd_KillRagdolls_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAFEntity_Generic::GetClassType() ); + KillEntities( args, idAFEntity_WithAttachedHead::GetClassType() ); +// RAVEN END +} + + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================== +GiveStuffToPlayer + +Used by the "give" and "buy" command line cmds +================== +*/ +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ) +{ + int i; + bool give_all; +// idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player || !name ) { + return; + } + + if( !value ) { + value = ""; + } + + if ( idStr::Icmp( name, "all" ) == 0 ) { + give_all = true; + } else { + give_all = false; + } + + if ( give_all || ( idStr::Cmpn( name, "weapon", 6 ) == 0 ) ) { + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", false ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, gameLocal.entities[ i ]->spawnArgs.GetString( "def_weapon1" ) ); + } + } + } + } + + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) || ( idStr::Icmp( name, "ammorefill" ) == 0 ) ) { + player->GiveItem( name ); + return; + } + + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { + player->health = player->inventory.maxHealth; + if ( player->IsInVehicle() ) { + player->GetVehicleController().Give ( "health", "9999" ); + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "weapons" ) == 0 ) { + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "ammo" ) == 0 ) { +// RAVEN BEGIN +// bdube: define changed + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, rvWeapon::GetAmmoNameForIndex( i ) ); +// RAVEN END + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "armor" ) == 0 ) { + player->inventory.armor = player->inventory.maxarmor; + if ( !give_all ) { + return; + } + } +// RAVEN BEGIN + if (idStr::Icmp(name, "quad") == 0) { + player->GivePowerUp( POWERUP_QUADDAMAGE, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "invis" ) == 0 ) { + player->GivePowerUp( POWERUP_INVISIBILITY, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "regen" ) == 0 ) { + player->GivePowerUp( POWERUP_REGENERATION, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "haste" ) == 0 ) { + player->GivePowerUp( POWERUP_HASTE, SEC2MS( 30.0f ) ); + return; + } + + if (idStr::Icmp(name, "ammoregen") == 0) { + player->GivePowerUp( POWERUP_AMMOREGEN, -1 ); + return; + } + + if (idStr::Icmp(name, "scout") == 0) { + player->GivePowerUp( POWERUP_SCOUT, -1 ); + return; + } + + if (idStr::Icmp(name, "doubler") == 0) { + player->GivePowerUp( POWERUP_DOUBLER, -1 ); + return; + } + + if (idStr::Icmp(name, "guard") == 0) { + player->GivePowerUp( POWERUP_GUARD, -1 ); + return; + } +// RAVEN END + + if ( !idStr::Icmp ( name, "wpmod_all" ) ) { + player->GiveWeaponMods ( 0xFFFFFFFF ); + return; + } else if ( !idStr::Cmpn( name, "wpmod_", 6 ) ) { + player->GiveWeaponMod(name); + return; + } + + if ( !idStr::Cmpn( name, "stroggmod_", 10 ) ) { + player->Give ( name, "" ); + return; + } + + if ( !give_all && !player->Give( name, value ) ) { + gameLocal.Printf( "unknown item\n" ); + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + GiveStuffToPlayer( player, args.Argv(1), args.Argv(2) ); +} +// RITUAL END + +/* +================== +Cmd_CenterView_f + +Centers the players pitch +================== +*/ +void Cmd_CenterView_f( const idCmdArgs &args ) { + idPlayer *player; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + ang = player->viewAngles; + ang.pitch = 0.0f; + player->SetViewAngles( ang ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->godmode ) { + player->godmode = false; + msg = "godmode OFF\n"; + } else { + player->godmode = true; + msg = "godmode ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Undying_f + +Sets client to undying + +argv(0) undying +================== +*/ +void Cmd_Undying_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->undying ) { + player->undying = false; + msg = "undying OFF\n"; + } else { + player->undying = true; + msg = "undying ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->fl.notarget ) { + player->fl.notarget = false; + msg = "notarget OFF\n"; + } else { + player->fl.notarget = true; + msg = "notarget ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + player->noclip = !player->noclip; + + gameLocal.Printf( "%s", msg ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_KILL ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "kill or kill \n" ); + return; + } + player->Kill( false, false ); +// RAVEN BEGIN +// rhummer: localized this string.. (killed client) + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say %s %d '%s^0'\n", common->GetLocalizedString( "#str_108022" ), player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + } else { + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + player->Kill( false, false ); + } +} + +// RAVEN BEGIN +// bdube: jump points +/* +================= +Cmd_DebugJump_f +================= +*/ +void Cmd_DebugJump_f( const idCmdArgs &args ) { + if (args.Argc() > 1) { + // going to a specific jump point as specified by second argument + gameDebug.JumpTo ( args.Argv( 1 ) ); + } else { + // just go to next jump point as specified + gameDebug.JumpNext ( ); + } +} + +/* +================= +Cmd_DebugNextJumpPoint_f +================= +*/ +void Cmd_DebugNextJumpPoint_f( const idCmdArgs &args ) { + // just go to next jump point as specified + gameDebug.JumpNext ( ); +} + +/* +================= +Cmd_DebugPrevJumpPoint_f +================= +*/ +void Cmd_DebugPrevJumpPoint_f( const idCmdArgs &args ) { + // just go to previous jump point as specified + gameDebug.JumpPrev ( ); +} + +/* +================= +Cmd_AASExtractTactical_f +================= +*/ +void Cmd_AASExtractTactical_f( const idCmdArgs &args ) { + if (gameLocal.GetLocalPlayer()) + { + gameLocal.GetLocalPlayer()->aasSensor->SearchDebug(); + } +} + +/* +================= +Cmd_CallScriptFunc_f +================= +*/ +void Cmd_CallScriptFunc_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: call FuncName ...\n" ); + return; + } + + idDict returnDict; + rvScriptFuncUtility util; + + if( !util.Init(args) ) { + return; + } + + util.CallFunc( &returnDict ); + + if( util.ReturnsAVal() && util.GetReturnKey() && util.GetReturnKey()[0] ) { + gameLocal.Printf( "%s: %s\n", util.GetReturnKey(), returnDict.GetString(util.GetReturnKey()) ); + } +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: setPlayerGravity 'x_magnitude y_magnitude z_magnitude\n" ); + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( !player ) { + return; + } + + idVec3 gravity; + sscanf( args.Argv(1), "%f %f %f", &gravity.x, &gravity.y, &gravity.z ); + player->GetPhysics()->SetGravity( gravity ); +} +// RAVEN END + +/* +================= +Cmd_PlayerModel_f +================= +*/ +void Cmd_PlayerModel_f( const idCmdArgs &args ) { + idPlayer *player; + const char *name; + idVec3 pos; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: playerModel \n" ); + return; + } + + name = args.Argv( 1 ); + player->spawnArgs.Set( "model", name ); + + pos = player->GetPhysics()->GetOrigin(); + ang = player->viewAngles; + player->SpawnToPoint( pos, ang ); +} + +/* +================== +Cmd_Say +================== +*/ +static void Cmd_Say( bool team, const idCmdArgs &args ) { + const char *name; + idStr text; + const char *cmd = team ? "sayTeam" : "say" ; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "%s can only be used in a multiplayer game\n", cmd ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text = args.Args(); +// RAVEN BEGIN +// bdube: make sure text was specified + if ( text.Length() == 0 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + +// asalmon: check to see if the text passes the live decency standard +#ifdef _XENON + if(!Sys_VerifyString(text.c_str())) + { + gameLocal.Printf( "Your message did not pass Xbox decency standards\n"); + return; + } +#endif + + +// ddynerman: team speak only in team games + if ( team && !gameLocal.IsTeamGame() ) { + team = false; + } +// RAVEN END + if ( text[ text.Length() - 1 ] == '\n' ) { + text[ text.Length() - 1 ] = '\0'; + } + name = "player"; + + idPlayer * player; + + // here we need to special case a listen server to use the real client name instead of "server" + // "server" will only appear on a dedicated server + if ( gameLocal.isClient || cvarSystem->GetCVarInteger( "net_serverDedicated" ) == 0 ) { + player = gameLocal.localClientNum >= 0 ? static_cast( gameLocal.entities[ gameLocal.localClientNum ] ) : NULL; + if ( player ) { + name = player->GetUserInfo()->GetString( "ui_name", "player" ); + +// RAVEN BEGIN +// mekberg: activate the mphud gui so the time is right before receiving the chat message + if ( player->mphud ) { + player->mphud->Activate( true, gameLocal.time ); + } + } +// RAVEN END + } else { + name = "server"; + } + + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( team ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( "" ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + gameLocal.mpGame.ProcessChatMessage( gameLocal.localClientNum, team, name, text, NULL ); + } +} + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( const idCmdArgs &args ) { + Cmd_Say( false, args ); +} + +/* +================== +Cmd_SayTeam_f +================== +*/ +static void Cmd_SayTeam_f( const idCmdArgs &args ) { + Cmd_Say( true, args ); +} + +/* +================== +Cmd_AddChatLine_f +================== +*/ +static void Cmd_AddChatLine_f( const idCmdArgs &args ) { + gameLocal.mpGame.AddChatLine( args.Argv( 1 ) ); +} + +/* +================== +Cmd_Kick_f +================== +*/ +static void Cmd_Kick_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "kick can only be used in a multiplayer game\n" ); + return; + } + + if ( gameLocal.isClient ) { + gameLocal.Printf( "You have no such power. This is a server command\n" ); + return; + } + + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "usage: kick or kick \n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say kicking out client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d\n", player->entityNumber ) ); +} + +/* +================== +Cmd_GetViewpos_f +================== +*/ +void Cmd_GetViewpos_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( view ) { + gameLocal.Printf( "(%s) %.1f\n", view->vieworg.ToString(), view->viewaxis[0].ToYaw() ); + } else { + player->GetViewPos( origin, axis ); + gameLocal.Printf( "(%s) %.1f\n", origin.ToString(), axis[0].ToYaw() ); + } +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + int i; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( ( args.Argc() != 4 ) && ( args.Argc() != 5 ) ) { + gameLocal.Printf( "usage: setviewpos \n" ); + return; + } + + angles.Zero(); + if ( args.Argc() == 5 ) { + angles.yaw = atof( args.Argv( 4 ) ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( args.Argv( i + 1 ) ); + } + origin.z -= pm_normalviewheight.GetFloat() - 0.25f; + + player->Teleport( origin, angles, NULL ); +} + +/* +================= +Cmd_Teleport_f +================= +*/ +void Cmd_Teleport_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: teleport \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + angles.Zero(); + angles.yaw = ent->GetPhysics()->GetAxis()[ 0 ].ToYaw(); + origin = ent->GetPhysics()->GetOrigin(); + + player->Teleport( origin, angles, ent ); +} + +/* +================= +Cmd_Trigger_f +================= +*/ +void Cmd_Trigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); +} + +/* +=================== +Cmd_Spawn_f +=================== +*/ +void Cmd_Spawn_f( const idCmdArgs &args ) { +#ifndef _MPBETA + const char *key, *value; + int i; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname [key/value pairs]\n" ); + return; + } + + yaw = player->viewAngles.yaw; + + value = args.Argv( 1 ); + dict.Set( "classname", value ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + +// RAVEN BEGIN +// kfuller: want to know the name of the entity I spawned + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } +// RAVEN END +#endif // !_MPBETA +} + +// RAVEN BEGIN +// ddynerman: MP spawning command for performance testing +/* +=================== +Cmd_EvaluateMPPerformance_f +=================== +*/ +void Cmd_EvaluateMPPerformance_f( const idCmdArgs &args ) { + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + int num = 15; + + if ( args.Argc() > 1 ) { + num = atoi( args.Argv( 1 ) ); + } + + float angleStep = 360.0f / num; + + const char* className = "char_marine"; + + yaw = player->viewAngles.yaw; + + for( int i = 0; i < num; i++ ) { + dict.Set( "classname", className ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw + (i * angleStep), 0 ).ToForward() * 120 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } + } +} +// RAVEN END + + +/* +================== +Cmd_Damage_f + +Damages the specified entity +================== +*/ +void Cmd_Damage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: damage \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); +} + + +/* +================== +Cmd_Flashlight_f + +Toggles flashlight on specified entity +================== +*/ +void Cmd_Flashlight_f( const idCmdArgs &args ) { + if ( gameLocal.IsMultiplayer() || !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: flashight <0 = off, 1 = on>\n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent || !ent->IsType( idActor::GetClassType() ) ) { + gameLocal.Printf( "entity not found or not an actor\n" ); + return; + } + ent->ProcessEvent( &AI_Flashlight, atoi( args.Argv( 2 ) ) ); +} + +/* +================== +Cmd_Remove_f + +Removes the specified entity +================== +*/ +void Cmd_Remove_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: remove \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + delete ent; +} + +/* +================== +Cmd_AI_DebugFilter_f + +Makes the targeted entity the only one ai_debugMove & ai_debugTactical cares about +================== +*/ +void Cmd_AI_DebugFilter_f( const idCmdArgs &args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + idEntity *ent = NULL; + if ( args.Argc() != 2 ) + { + //trace ahead + trace_t trace; + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAngles.ToForward() * 2048.0f; + gameLocal.TracePoint( player, trace, start, end, MASK_SHOT_RENDERMODEL, player ); + ent = gameLocal.GetTraceEntity( trace ); + } + else + { + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + } + + if ( !ent || !ent->IsType( idAI::GetClassType() ) ) { + ai_debugFilterString.SetString( "" ); + } else { + ai_debugFilterString.SetString( ent->GetName() ); + } +} + +/* +=================== +Cmd_TestLight_f +=================== +*/ +void Cmd_TestLight_f( const idCmdArgs &args ) { + int i; + idStr filename; + const char *key, *value, *name = NULL; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + renderView_t *rv = player->GetRenderView(); + + float fov = idMath::Tan( idMath::M_DEG2RAD * rv->fov_x / 2 ); + + dict.SetMatrix( "rotation", mat3_default ); + dict.SetVector( "origin", rv->vieworg ); + dict.SetVector( "light_target", rv->viewaxis[0] ); + dict.SetVector( "light_right", rv->viewaxis[1] * -fov ); + dict.SetVector( "light_up", rv->viewaxis[2] * fov ); + dict.SetVector( "light_start", rv->viewaxis[0] * 16 ); + dict.SetVector( "light_end", rv->viewaxis[0] * 1000 ); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + filename = args.Argv(1); + filename.DefaultFileExtension( ".tga" ); + dict.Set( "texture", filename ); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "spawned_light_%d", i ); // not just light_, or it might pick up a prelight shadow + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new light\n"); +} + +/* +=================== +Cmd_TestPointLight_f +=================== +*/ +void Cmd_TestPointLight_f( const idCmdArgs &args ) { + const char *key, *value, *name = NULL; + int i; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + dict.SetVector("origin", player->GetRenderView()->vieworg); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + dict.Set("light", value); + } else { + dict.Set("light", "300"); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "light_%d", i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new point light\n"); +} + +/* +================== +Cmd_PopLight_f +================== +*/ +void Cmd_PopLight_f( const idCmdArgs &args ) { + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idLight *lastLight; + int last; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + bool removeFromMap = ( args.Argc() > 1 ); + + lastLight = NULL; + last = -1; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( gameLocal.spawnIds[ ent->entityNumber ] > last ) { + last = gameLocal.spawnIds[ ent->entityNumber ]; + lastLight = static_cast( ent ); + } + } + + if ( lastLight ) { + // find map file entity + mapEnt = mapFile->FindEntity( lastLight->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + gameLocal.Printf( "Removing light %i\n", lastLight->GetLightDefHandle() ); + delete lastLight; + } else { + gameLocal.Printf( "No lights to clear.\n" ); + } + +} + +/* +==================== +Cmd_ClearLights_f +==================== +*/ +void Cmd_ClearLights_f( const idCmdArgs &args ) { + idEntity *ent; + idEntity *next; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + + bool removeFromMap = ( args.Argc() > 1 ); + + gameLocal.Printf( "Clearing all lights.\n" ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + light = static_cast( ent ); + mapEnt = mapFile->FindEntity( light->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + + delete light; + } +} + +// RAVEN BEGIN +// bdube: not using id effects +/* +================== +Cmd_TestFx_f +================== +void Cmd_TestFx_f( const idCmdArgs &args ) { + idVec3 offset; + const char *name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testFx ) { + delete gameLocal.testFx; + gameLocal.testFx = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "test", "1"); + dict.Set( "fx", name ); + gameLocal.testFx = ( idEntityFx * )gameLocal.SpawnEntityType( idEntityFx::Type, &dict ); +} +*/ +// RAVEN END + +#define MAX_DEBUGLINES 128 + +typedef struct { + bool used; + idVec3 start, end; + int color; + bool blink; + bool arrow; +} gameDebugLine_t; + +gameDebugLine_t debugLines[MAX_DEBUGLINES]; + +/* +================== +Cmd_AddDebugLine_f +================== +*/ +static void Cmd_AddDebugLine_f( const idCmdArgs &args ) { + int i, argNum; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 7 ) { + gameLocal.Printf( "usage: addline \n" ); + return; + } + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( !debugLines[i].used ) { + break; + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "no free debug lines\n" ); + return; + } + value = args.Argv( 0 ); + if ( !idStr::Icmp( value, "addarrow" ) ) { + debugLines[i].arrow = true; + } else { + debugLines[i].arrow = false; + } + debugLines[i].used = true; + debugLines[i].blink = false; + argNum = 1; + debugLines[i].start.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].color = Cmd_GetFloatArg( args, argNum ); +} + +/* +================== +Cmd_RemoveDebugLine_f +================== +*/ +static void Cmd_RemoveDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: removeline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi(value); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].used = false; +} + +/* +================== +Cmd_BlinkDebugLine_f +================== +*/ +static void Cmd_BlinkDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: blinkline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi( value ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].blink = !debugLines[i].blink; +} + +/* +================== +PrintFloat +================== +*/ +static void PrintFloat( float f ) { + char buf[128], i; + + for ( i = sprintf( buf, "%3.2f", f ); i < 7; i++ ) { + buf[i] = ' '; + } + buf[i] = '\0'; + gameLocal.Printf( buf ); +} + +/* +================== +Cmd_ListDebugLines_f +================== +*/ +static void Cmd_ListDebugLines_f( const idCmdArgs &args ) { + int i, num; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + num = 0; + gameLocal.Printf( "line num: x1 y1 z1 x2 y2 z2 c b a\n" ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + gameLocal.Printf( "line %3d: ", num ); + PrintFloat( debugLines[i].start.x ); + PrintFloat( debugLines[i].start.y ); + PrintFloat( debugLines[i].start.z ); + PrintFloat( debugLines[i].end.x ); + PrintFloat( debugLines[i].end.y ); + PrintFloat( debugLines[i].end.z ); + gameLocal.Printf( "%d %d %d\n", debugLines[i].color, debugLines[i].blink, debugLines[i].arrow ); + num++; + } + } + if ( !num ) { + gameLocal.Printf( "no debug lines\n" ); + } +} + +/* +================== +D_DrawDebugLines +================== +*/ +void D_DrawDebugLines( void ) { +// RAVEN BEGIN +// ddynerman: this eats about 5k us in release +#ifdef _DEBUG + int i; + idVec3 forward, right, up, p1, p2; + idVec4 color; + float l; + + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( !debugLines[i].blink || (gameLocal.time & (1<<9)) ) { + color = idVec4( debugLines[i].color&1, (debugLines[i].color>>1)&1, (debugLines[i].color>>2)&1, 1 ); + gameRenderWorld->DebugLine( color, debugLines[i].start, debugLines[i].end ); + // + if ( debugLines[i].arrow ) { + // draw a nice arrow + forward = debugLines[i].end - debugLines[i].start; + l = forward.Normalize() * 0.2f; + forward.NormalVectors( right, up); + + if ( l > 3.0f ) { + l = 3.0f; + } + p1 = debugLines[i].end - l * forward + (l * 0.4f) * right; + p2 = debugLines[i].end - l * forward - (l * 0.4f) * right; + gameRenderWorld->DebugLine( color, debugLines[i].end, p1 ); + gameRenderWorld->DebugLine( color, debugLines[i].end, p2 ); + gameRenderWorld->DebugLine( color, p1, p2 ); + } + } + } + } +#else + return; +#endif +// RAVEN END +} + +/* +================== +Cmd_ListCollisionModels_f +================== +*/ +static void Cmd_ListCollisionModels_f( const idCmdArgs &args ) { + if ( !gameLocal.CheatsOk() ) { + return; + } + + collisionModelManager->ListModels(); +} + +/* +================== +Cmd_CollisionModelInfo_f +================== +*/ +static void Cmd_CollisionModelInfo_f( const idCmdArgs &args ) { + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: collisionModelInfo \n" + "use 'all' instead of the model number for accumulated info\n" ); + return; + } + + value = args.Argv( 1 ); + if ( !idStr::Icmp( value, "all" ) ) { + collisionModelManager->ModelInfo( -1 ); + } else { + collisionModelManager->ModelInfo( atoi(value) ); + } +} + +/* +================== +Cmd_ExportModels_f +================== +*/ +static void Cmd_ExportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } +} + +/* +================== +Cmd_ReexportModels_f +================== +*/ +static void Cmd_ReexportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + idAnimManager::forceExport = true; + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } + idAnimManager::forceExport = false; +} + +/* +================== +Cmd_ReloadAnims_f +================== +*/ +static void Cmd_ReloadAnims_f( const idCmdArgs &args ) { + + // don't allow reloading anims when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + +// RAVEN BEGIN +// mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + +// jsinger: animationLib changed to a pointer + animationLib->ReloadAnims(); + +// mekberg: enable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( false ); +// RAVEN END +} + +/* +================== +Cmd_ListAnims_f +================== +*/ +static void Cmd_ListAnims_f( const idCmdArgs &args ) { + idEntity * ent; + int num; + size_t size; + size_t alloced; + idAnimator * animator; + const char * classname; + const idDict * dict; + int i; + + if ( args.Argc() > 1 ) { + idAnimator animator; + + classname = args.Argv( 1 ); + + dict = gameLocal.FindEntityDefDict( classname, false ); + if ( !dict ) { + gameLocal.Printf( "Entitydef '%s' not found\n", classname ); + return; + } + animator.SetModel( dict->GetString( "model" ) ); + + gameLocal.Printf( "----------------\n" ); + num = animator.NumAnims(); +// RAVEN BEGIN +// rjohnson: more output for animators + idFile *FH = fileSystem->OpenFileAppend( "animations.txt" ); + for( i = 0; i < num; i++ ) { + for( int j = 0; ; j++ ) { + const char *fileName = animator.AnimMD5Name( i, j ); + + if ( !fileName[0] ) { + break; + } + gameLocal.Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + if ( FH ) { + FH->Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + } + } + } + gameLocal.Printf( "%d anims\n", num ); + fileSystem->CloseFile( FH ); +// RAVEN END + } else { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->ListAnims(); +// RAVEN END + + size = 0; + num = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + animator = ent->GetAnimator(); + if ( animator ) { + alloced = animator->Allocated(); + size += alloced; + num++; + } + } + + gameLocal.Printf( "%d memory used in %d entity animators\n", size, num ); + } +} + +// RAVEN BEGIN +// jscott: export for memory tracking +/* +================ +idGameEdit::ListAnims +================ +*/ +void idGameEdit::PrintMemInfo( MemInfo *mi ) { + + int i, count, totalSize; + idAAS *aas; + + totalSize = 0; + count = 0; + for( i = 0; i < gameLocal.GetNumAAS(); i++ ) { + + aas = gameLocal.GetAAS( i ); + if( aas ) { + + totalSize += aas->StatsSummary(); + count++; + } + } + + mi->aasAssetsTotal = totalSize; + mi->aasAssetsCount = count; + + // jsinger: animationLib changed to a pointer + animationLib->PrintMemInfo( mi ); +} +// RAVEN END + +/* +================== +Cmd_AASStats_f +================== +*/ +static void Cmd_AASStats_f( const idCmdArgs &args ) { + int aasNum; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + aasNum = aas_test.GetInteger(); + idAAS *aas = gameLocal.GetAAS( aasNum ); + if ( !aas ) { + gameLocal.Printf( "No aas #%d loaded\n", aasNum ); + } else { + aas->Stats(); + } +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDamage_f( const idCmdArgs &args ) { + idPlayer *player; + const char *damageDefName; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + gameLocal.Printf( "usage: testDamage [angle]\n" ); + return; + } + + damageDefName = args.Argv( 1 ); + + idVec3 dir; + if ( args.Argc() == 3 ) { + float angle = atof( args.Argv( 2 ) ); + + idMath::SinCos( DEG2RAD( angle ), dir[1], dir[0] ); + dir[2] = 0; + } else { + dir.Zero(); + } + + // give the player full health before and after + // running the damage + player->health = player->inventory.maxHealth; + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->health = player->inventory.maxHealth; +} + +/* +================== +Cmd_TestBoneFx_f +================== +*/ +// RAVEN BEGIN +// bdube: not using +/* +static void Cmd_TestBoneFx_f( const idCmdArgs &args ) { + idPlayer *player; + const char *bone, *fx; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 3 || args.Argc() > 4 ) { + gameLocal.Printf( "usage: testBoneFx \n" ); + return; + } + + fx = args.Argv( 1 ); + bone = args.Argv( 2 ); + + player->StartFxOnBone( fx, bone ); +} +*/ +// RAVEN END + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDeath_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + idVec3 dir; + idMath::SinCos( DEG2RAD( 45.0f ), dir[1], dir[0] ); + dir[2] = 0; + + g_testDeath.SetBool( 1 ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + if ( args.Argc() >= 2) { + player->SpawnGibs( dir, "damage_triggerhurt_1000" ); + } + +} + +/* +================== +Cmd_WeaponSplat_f +================== +*/ +static void Cmd_WeaponSplat_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->weapon->BloodSplat( 2.0f ); +} + +/* +================== +Cmd_SaveSelected_f +================== +*/ +static void Cmd_SaveSelected_f( const idCmdArgs &args ) { + int i; + idPlayer *player; + idEntity *s; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + s = player->dragEntity.GetSelected(); + if ( !s ) { + gameLocal.Printf( "no entity selected, set g_dragShowSelection 1 to show the current selection\n" ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + // find map file entity + mapEnt = mapFile->FindEntity( s->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", s->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + s->name = name; + mapEnt->epairs.Set( "classname", s->GetEntityDefName() ); + mapEnt->epairs.Set( "name", s->name ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( s->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // save the moveable state + mapEnt->epairs.Set( "origin", s->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", s->GetPhysics()->GetAxis().ToString( 8 ) ); + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + else if ( s->IsType( idAFEntity_Generic::GetClassType() ) || s->IsType( idAFEntity_WithAttachedHead::GetClassType() ) ) { +// RAVEN END + // save the articulated figure state + dict.Clear(); + static_cast(s)->SaveState( dict ); + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_DeleteSelected_f +================== +*/ +static void Cmd_DeleteSelected_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.DeleteSelected(); + } +} + +/* +================== +Cmd_SaveMoveables_f +================== +*/ +static void Cmd_SaveMoveables_f( const idCmdArgs &args ) { + int e, i; + idMoveable *m; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m || !m->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + if ( !m->IsAtRest() ) { + break; + } + } + + if ( e < MAX_GENTITIES ) { + gameLocal.Warning( "map not saved because the moveable entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m ) + { + continue; + } +// jdischler: need to check for idMoveableItem as well + if ( !m->IsType( idMoveable::GetClassType()) && !m->IsType( idMoveableItem::GetClassType()) ) + { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + // find map file entity + mapEnt = mapFile->FindEntity( m->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", m->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + m->name = name; + mapEnt->epairs.Set( "classname", m->GetEntityDefName() ); + mapEnt->epairs.Set( "name", m->name ); + } + // save the moveable state + mapEnt->epairs.Set( "origin", m->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", m->GetPhysics()->GetAxis().ToString( 8 ) ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_SaveRagdolls_f +================== +*/ +static void Cmd_SaveRagdolls_f( const idCmdArgs &args ) { + int e, i; + idAFEntity_Base *af; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + af = static_cast(gameLocal.entities[ e ]); + + if ( !af ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af->IsType( idAFEntity_WithAttachedHead::GetClassType() ) && !af->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( af->IsBound() ) { + continue; + } + + if ( !af->IsAtRest() ) { + gameLocal.Warning( "the articulated figure for entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + } + + dict.Clear(); + af->SaveState( dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( af->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", af->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + af->name = name; + mapEnt->epairs.Set( "classname", af->GetEntityDefName() ); + mapEnt->epairs.Set( "name", af->name ); + } + // save the articulated figure state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_BindRagdoll_f +================== +*/ +static void Cmd_BindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.BindSelected(); + } +} + +/* +================== +Cmd_UnbindRagdoll_f +================== +*/ +static void Cmd_UnbindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.UnbindSelected(); + } +} + +/* +================== +Cmd_GameError_f +================== +*/ +static void Cmd_GameError_f( const idCmdArgs &args ) { + gameLocal.Error( "game error" ); +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +/* +================== +Cmd_ListEntityStats_f +================== +*/ +static void Cmd_ListEntityStats_f( const idCmdArgs &args ) { + gameLocal.ListEntityStats( args ); +} +// RAVEN END + +/* +================== +Cmd_SaveLights_f +================== +*/ +static void Cmd_SaveLights_f( const idCmdArgs &args ) { + int e, i; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + light = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !light || !light->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + dict.Clear(); + light->SaveState( &dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( light->name ); + // create new map file entity if there isn't one for this light + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", light->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + light->name = name; + mapEnt->epairs.Set( "classname", light->GetEntityDefName() ); + mapEnt->epairs.Set( "name", light->name ); + } + // save the light state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_SaveParticles_f +================== +*/ +static void Cmd_SaveParticles_f( const idCmdArgs &args ) { + int e; + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName, strModel; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + + ent = static_cast ( gameLocal.entities[ e ] ); + + if ( !ent ) { + continue; + } + + strModel = ent->spawnArgs.GetString( "model" ); + if ( strModel.Length() && strModel.Find( ".prt") > 0 ) { + dict.Clear(); + dict.Set( "model", ent->spawnArgs.GetString( "model" ) ); + dict.SetVector( "origin", ent->GetPhysics()->GetOrigin() ); + + // find map file entity + mapEnt = mapFile->FindEntity( ent->name ); + // create new map file entity if there isn't one for this entity + if ( !mapEnt ) { + continue; + } + // save the particle state + mapEnt->epairs.Copy( dict ); + } + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_DisasmScript_f +================== +*/ +static void Cmd_DisasmScript_f( const idCmdArgs &args ) { + gameLocal.program.Disassemble(); +} + +/* +================== +Cmd_TestSave_f +================== +*/ +static void Cmd_TestSave_f( const idCmdArgs &args ) { + idFile *f; + + f = fileSystem->OpenFileWrite( "test.sav" ); + gameLocal.SaveGame( f ); + fileSystem->CloseFile( f ); +} + +// RAVEN BEGIN +#if 0 +/* +================== +Cmd_RecordViewNotes_f +================== +*/ +static void Cmd_RecordViewNotes_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + if ( args.Argc() <= 3 ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + player->GetViewPos( origin, axis ); + + // Argv(1) = filename for map (viewnotes/mapname/person) + // Argv(2) = note number (person0001) + // Argv(3) = comments + + idStr str = args.Argv(1); + str.SetFileExtension( ".txt" ); + idFile *file = fileSystem->OpenFileAppend( str ); + if ( file ) { + file->WriteFloatString( "\"view\"\t( %s )\t( %s )\r\n", origin.ToString(), axis.ToString() ); + file->WriteFloatString( "\"comments\"\t\"%s: %s\"\r\n\r\n", args.Argv(2), args.Argv(3) ); + fileSystem->CloseFile( file ); + } + + idStr viewComments = args.Argv(1); + viewComments.StripLeading("viewnotes/"); + viewComments += " -- Loc: "; + viewComments += origin.ToString(); + viewComments += "\n"; + viewComments += args.Argv(3); + player->hud->SetStateString( "viewcomments", viewComments ); + player->hud->HandleNamedEvent( "showViewComments" ); +} + +/* +================== +Cmd_CloseViewNotes_f +================== +*/ +static void Cmd_CloseViewNotes_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); +} + +/* +================== +Cmd_ShowViewNotes_f +================== +*/ +static void Cmd_ShowViewNotes_f( const idCmdArgs &args ) { + static idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_NOFATALERRORS ); + idToken token; + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( !parser.IsLoaded() ) { + idStr str = "viewnotes/"; + str += gameLocal.GetMapName(); + str.StripFileExtension(); + str += "/"; + if ( args.Argc() > 1 ) { + str += args.Argv( 1 ); + } else { + str += "comments"; + } + str.SetFileExtension( ".txt" ); + if ( !parser.LoadFile( str ) ) { + gameLocal.Printf( "No view notes for %s\n", gameLocal.GetMapName() ); + return; + } + } + + if ( parser.ExpectTokenString( "view" ) && parser.Parse1DMatrix( 3, origin.ToFloatPtr() ) && + parser.Parse1DMatrix( 9, axis.ToFloatPtr() ) && parser.ExpectTokenString( "comments" ) && parser.ReadToken( &token ) ) { + player->hud->SetStateString( "viewcomments", token ); + player->hud->HandleNamedEvent( "showViewComments" ); + player->Teleport( origin, axis.ToAngles(), NULL ); + } else { + parser.FreeSource(); + player->hud->HandleNamedEvent( "hideViewComments" ); + return; + } +} +#endif +// RAVEN END + +/* +================= +FindEntityGUIs + +helper function for Cmd_NextGUI_f. Checks the passed entity to determine if it +has any valid gui surfaces. +================= +*/ +bool FindEntityGUIs( idEntity *ent, const modelSurface_t ** surfaces, int maxSurfs, int &guiSurfaces ) { + renderEntity_t *renderEnt; + idRenderModel *renderModel; + const modelSurface_t *surf; + const idMaterial *shader; + int i; + + assert( surfaces != NULL ); + assert( ent != NULL ); + + memset( surfaces, 0x00, sizeof( modelSurface_t *) * maxSurfs ); + guiSurfaces = 0; + + renderEnt = ent->GetRenderEntity(); + renderModel = renderEnt->hModel; + if ( renderModel == NULL ) { + return false; + } + + for( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf == NULL ) { + continue; + } + shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( shader->GetEntityGui() > 0 ) { + surfaces[ guiSurfaces++ ] = surf; + } + } + + return ( guiSurfaces != 0 ); +} + +/* +================= +Cmd_NextGUI_f +================= +*/ +void Cmd_NextGUI_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + int guiSurfaces; + bool newEnt; + renderEntity_t *renderEnt; + int surfIndex; + srfTriangles_t *geom; + idMat4 modelMatrix; + idVec3 normal; + idVec3 center; + const modelSurface_t *surfaces[ MAX_RENDERENTITY_GUI ]; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 1 ) { + gameLocal.Printf( "usage: nextgui\n" ); + return; + } + + // start at the last entity + ent = gameLocal.lastGUIEnt.GetEntity(); + + // see if we have any gui surfaces left to go to on the current entity. + guiSurfaces = 0; + newEnt = false; + if ( ent == NULL ) { + newEnt = true; + } else if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == true ) { + if ( gameLocal.lastGUI >= guiSurfaces ) { + newEnt = true; + } + } else { + // no actual gui surfaces on this ent, so skip it + newEnt = true; + } + + if ( newEnt == true ) { + // go ahead and skip to the next entity with a gui... + if ( ent == NULL ) { + ent = gameLocal.spawnedEntities.Next(); + } else { + ent = ent->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->spawnArgs.GetString( "gui", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui2", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui3", NULL ) != NULL ) { + break; + } + + // try the next entity + gameLocal.lastGUIEnt = ent; + } + + gameLocal.lastGUIEnt = ent; + gameLocal.lastGUI = 0; + + if ( !ent ) { + gameLocal.Printf( "No more gui entities. Starting over...\n" ); + return; + } + } + + if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == false ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces.\n", ent->name.c_str() ); + } + + if ( guiSurfaces == 0 ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces!\n", ent->name.c_str() ); + return; + } + + gameLocal.Printf( "Teleporting to gui entity \"%s\", gui #%d.\n" , ent->name.c_str (), gameLocal.lastGUI ); + + renderEnt = ent->GetRenderEntity(); + surfIndex = gameLocal.lastGUI++; + geom = surfaces[ surfIndex ]->geometry; + if ( geom == NULL ) { + gameLocal.Printf( "Entity \"%s\" has gui surface %d without geometry!\n", ent->name.c_str(), surfIndex ); + return; + } + + assert( geom->facePlanes != NULL ); + + modelMatrix = idMat4( renderEnt->axis, renderEnt->origin ); + normal = geom->facePlanes[ 0 ].Normal() * renderEnt->axis; + center = geom->bounds.GetCenter() * modelMatrix; + + origin = center + (normal * 32.0f); + origin.z -= player->EyeHeight(); + normal *= -1.0f; + angles = normal.ToAngles (); + + // make sure the player is in noclip + player->noclip = true; + player->Teleport( origin, angles, NULL ); +} + +static void ArgCompletion_DefFile( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "def/", true, ".def", NULL ); +} + +/* +=============== +Cmd_TestId_f +outputs a string from the string table for the specified id +=============== +*/ +void Cmd_TestId_f( const idCmdArgs &args ) { + idStr id; + int i; + if ( args.Argc() == 1 ) { + common->Printf( "usage: testid \n" ); + return; + } + + for ( i = 1; i < args.Argc(); i++ ) { + id += args.Argv( i ); + } + if ( idStr::Cmpn( id, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + id = STRTABLE_ID + id; + } + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( id ), "", "", "" ); +} + +// RAVEN BEGIN +// ddynerman: instance testing commands +void Cmd_SetInstance_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_SetInstance_f(): setInstance can only be run on a server\n" ); + return; + } + + if ( args.Argc() <= 2 ) { + common->Printf( "usage: setInstance \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + int instanceID = atoi( args.Argv( 2 ) ); + + if( client < 0 || client >= MAX_CLIENTS || !gameLocal.entities[ client ] ) { + gameLocal.Warning( "Cmd_SetInstance_f(): Invalid clientnum %d\n", client ); + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ client ]; + + gameLocal.Printf( "Cmd_SetInstance_f(): Switching %d: %s to instance %d\n", client, gameLocal.userInfo[ client ].GetString( "ui_name", "unknown" ), instanceID ); + + player->JoinInstance( instanceID ); +} + +void Cmd_ListInstances_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_ListInstances_f(): listInstances can only be run on a server\n" ); + return; + } + + for( int i = 0; i < gameLocal.GetNumInstances(); i++ ) { + gameLocal.Printf("Instance %d:\n", i ); + for( int j = 0; j < MAX_CLIENTS; j++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ j ]; + if( player && player->GetInstance() == i ) { + gameLocal.Printf( "\t%d: %s\n", j, player->GetUserInfo()->GetString( "ui_name" ) ); + } + } + } +} + +void Cmd_AddIcon_f( const idCmdArgs& args ) { + if ( args.Argc() <= 1 ) { + common->Printf( "usage: addIcon \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + + iconManager->AddIcon( client, "textures/mp/awards/capture" ); +} +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +void Cmd_ToggleBuyMenu_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->CanBuy() ) + { + gameLocal.mpGame.OpenLocalBuyMenu(); + } +} + +void Cmd_BuyItem_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + common->Printf( "ERROR: Cmd_BuyItem_f() failed, since GetLocalPlayer() was NULL.\n", player ); + return; + } + + player->GenerateImpulseForBuyAttempt( args.Argv(1) ); +} +// RITUAL END + +void Cmd_PlayerEmote_f( const idCmdArgs& args ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + gameLocal.Warning( "Cmd_Emote_f() - local player is NULL" ); + return; + } + + if ( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: emote \n" ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab_a'\n" ); + return; + } + + if( !idStr::Icmp( args.Argv( 1 ), "taunt" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_TAUNT ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_a" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_A ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_b" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_B ); + } else if( !idStr::Icmp( args.Argv( 1), "salute" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_SALUTE ); + } else if( !idStr::Icmp( args.Argv( 1), "cheer" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_CHEER ); + } else { + gameLocal.Printf( "Invalid emote '%s'\n", args.Argv( 1 ) ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab'\n" ); + } + +} + +// mekberg: added +void Cmd_SetPMCVars_f ( const idCmdArgs &args ) { + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( gameLocal.GetLocalPlayer( ) ) { + gameLocal.GetLocalPlayer( )->SetPMCVars( ); + } +} + +void Cmd_FadeSound_f( const idCmdArgs &args ) { + + if( args.Argc() < 2) { + return; + } + + float fadeDB = 0.0f; + float fadeTime = 0.0f; + + idStr _fadeDB = args.Argv( 1); + fadeDB = atof( _fadeDB ); + + idStr _fadeTime = args.Argv( 2); + fadeTime = atof( _fadeTime ); + + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, SOUND_CLASS_MUSICAL, 0.0f - fadeDB, fadeTime ); + +} + +void Cmd_TestClientModel_f( const idCmdArgs& args ) { + rvClientEntity* face; + const idDict* dict = gameLocal.FindEntityDefDict( "player_marine_client", false ); + + gameLocal.SpawnClientEntityDef( *dict, &face, false ); + +// face = new rvClientAFEntity( *dict ); +// face->Spawn( dict ); +// face->SetOrigin( vec3_zero ); +// face->SetAxis( mat3_identity ); +// face->SetModel( "model_player_marine" ); +} + + +// RAVEN END + +void Cmd_CheckSave_f( const idCmdArgs &args ); + +void Cmd_ShuffleTeams_f( const idCmdArgs& args ) { + gameLocal.mpGame.ShuffleTeams(); +} + +#ifndef _FINAL +void Cmd_ClientOverflowReliable_f( const idCmdArgs& args ) { + idBitMsg outMsg; + byte msgBuf[ 114688 ]; + + for( int i = 0; i < 10; i++ ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( -1 ); + for( int j = 0; j < 8190; j++ ) { + outMsg.WriteByte( j ); + } + networkSystem->ClientSendReliableMessage( outMsg ); + } +} +#endif + +/* +================= +idGameLocal::InitConsoleCommands + +Let the system know about all of our commands +so it can perform tab completion +================= +*/ +void idGameLocal::InitConsoleCommands( void ) { +// RAVEN BEGIN +// jscott: typeinfo gone - didn't work, it was unfinished +// cmdSystem->AddCommand( "listTypeInfo", ListTypeInfo_f, CMD_FL_GAME, "list type info" ); +// cmdSystem->AddCommand( "writeGameState", WriteGameState_f, CMD_FL_GAME, "write game state" ); +// cmdSystem->AddCommand( "testSaveGame", TestSaveGame_f, CMD_FL_GAME|CMD_FL_CHEAT, "test a save game for a level" ); +// RAVEN END + cmdSystem->AddCommand( "game_memory", idClass::DisplayInfo_f, CMD_FL_GAME, "displays game class info" ); + cmdSystem->AddCommand( "listClasses", idClass::ListClasses_f, CMD_FL_GAME, "lists game classes" ); + cmdSystem->AddCommand( "listThreads", idThread::ListThreads_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists script threads" ); + cmdSystem->AddCommand( "listEntities", Cmd_EntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists game entities" ); + cmdSystem->AddCommand( "listClientEntities", Cmd_ClientEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists client entities" ); + cmdSystem->AddCommand( "listActiveEntities", Cmd_ActiveEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists active game entities" ); + cmdSystem->AddCommand( "listMonsters", idAI::List_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists monsters" ); + cmdSystem->AddCommand( "listSpawnArgs", Cmd_ListSpawnArgs_f, CMD_FL_GAME|CMD_FL_CHEAT, "list the spawn args of an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "say", Cmd_Say_f, CMD_FL_GAME, "text chat" ); + cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); + cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); + cmdSystem->AddCommand( "gameKick", Cmd_Kick_f, CMD_FL_GAME, "same as kick, but recognizes player names" ); + cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "undying", Cmd_Undying_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables undying mode (take damage down to 1 health, but do not die)" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "kill", Cmd_Kill_f, CMD_FL_GAME, "kills the player" ); + cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); + cmdSystem->AddCommand( "teleport", Cmd_Teleport_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleports the player to an entity location", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "trigger", Cmd_Trigger_f, CMD_FL_GAME|CMD_FL_CHEAT, "triggers an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); + cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); + cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); + cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); + cmdSystem->AddCommand( "addarrow", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug arrow" ); + cmdSystem->AddCommand( "removeline", Cmd_RemoveDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes a debug line" ); + cmdSystem->AddCommand( "blinkline", Cmd_BlinkDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "blinks a debug line" ); + cmdSystem->AddCommand( "listLines", Cmd_ListDebugLines_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists all debug lines" ); + cmdSystem->AddCommand( "playerModel", Cmd_PlayerModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the given model on the player", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "flashlight", Cmd_Flashlight_f, CMD_FL_GAME|CMD_FL_CHEAT, "toggle actor's flashlight", idGameLocal::ArgCompletion_AIName ); + + cmdSystem->AddCommand( "shuffleTeams", Cmd_ShuffleTeams_f, CMD_FL_GAME, "shuffle teams" ); +// RAVEN BEGIN +// bdube: not using id effect system +// cmdSystem->AddCommand( "testFx", Cmd_TestFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system", idCmdSystem::ArgCompletion_Decl ); +// cmdSystem->AddCommand( "testBoneFx", Cmd_TestBoneFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system bound to a joint", idCmdSystem::ArgCompletion_Decl ); +// RAVEN END + cmdSystem->AddCommand( "testLight", Cmd_TestLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a light" ); + cmdSystem->AddCommand( "testPointLight", Cmd_TestPointLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a point light" ); + cmdSystem->AddCommand( "popLight", Cmd_PopLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes the last created light" ); + cmdSystem->AddCommand( "testDeath", Cmd_TestDeath_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests death" ); + cmdSystem->AddCommand( "testSave", Cmd_TestSave_f, CMD_FL_GAME|CMD_FL_CHEAT, "writes out a test savegame" ); + cmdSystem->AddCommand( "testModel", idTestModel::TestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a model", idTestModel::ArgCompletion_TestModel ); + cmdSystem->AddCommand( "testSkin", idTestModel::TestSkin_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a skin on an existing testModel", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testShaderParm", idTestModel::TestShaderParm_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets a shaderParm on an existing testModel" ); + cmdSystem->AddCommand( "keepTestModel", idTestModel::KeepTestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "keeps the last test model in the game" ); + cmdSystem->AddCommand( "testAnim", idTestModel::TestAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an animation", idTestModel::ArgCompletion_TestAnim ); + cmdSystem->AddCommand( "testParticleStopTime", idTestModel::TestParticleStopTime_f,CMD_FL_GAME|CMD_FL_CHEAT, "tests particle stop time on a test model" ); + cmdSystem->AddCommand( "nextAnim", idTestModel::TestModelNextAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation on test model" ); + cmdSystem->AddCommand( "prevAnim", idTestModel::TestModelPrevAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation on test model" ); + cmdSystem->AddCommand( "nextFrame", idTestModel::TestModelNextFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation frame on test model" ); + cmdSystem->AddCommand( "prevFrame", idTestModel::TestModelPrevFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation frame on test model" ); + cmdSystem->AddCommand( "testBlend", idTestModel::TestBlend_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests animation blending" ); + cmdSystem->AddCommand( "reloadScript", Cmd_ReloadScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads scripts" ); + cmdSystem->AddCommand( "script", Cmd_Script_f, CMD_FL_GAME|CMD_FL_CHEAT, "executes a line of script" ); + cmdSystem->AddCommand( "listCollisionModels", Cmd_ListCollisionModels_f, CMD_FL_GAME, "lists collision models" ); + cmdSystem->AddCommand( "collisionModelInfo", Cmd_CollisionModelInfo_f, CMD_FL_GAME, "shows collision model info" ); + cmdSystem->AddCommand( "reexportmodels", Cmd_ReexportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "reexports models", ArgCompletion_DefFile ); + cmdSystem->AddCommand( "reloadanims", Cmd_ReloadAnims_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads animations" ); + cmdSystem->AddCommand( "listAnims", Cmd_ListAnims_f, CMD_FL_GAME, "lists all animations" ); + cmdSystem->AddCommand( "aasStats", Cmd_AASStats_f, CMD_FL_GAME, "shows AAS stats" ); + cmdSystem->AddCommand( "testDamage", Cmd_TestDamage_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a damage def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "weaponSplat", Cmd_WeaponSplat_f, CMD_FL_GAME|CMD_FL_CHEAT, "projects a blood splat on the player weapon" ); + cmdSystem->AddCommand( "saveSelected", Cmd_SaveSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves the selected entity to the .map file" ); + cmdSystem->AddCommand( "deleteSelected", Cmd_DeleteSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "deletes selected entity" ); + cmdSystem->AddCommand( "saveMoveables", Cmd_SaveMoveables_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all moveables to the .map file" ); + cmdSystem->AddCommand( "saveRagdolls", Cmd_SaveRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all ragdoll poses to the .map file" ); + cmdSystem->AddCommand( "bindRagdoll", Cmd_BindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "binds ragdoll at the current drag position" ); + cmdSystem->AddCommand( "unbindRagdoll", Cmd_UnbindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "unbinds the selected ragdoll" ); + cmdSystem->AddCommand( "saveLights", Cmd_SaveLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "saveParticles", Cmd_SaveParticles_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "clearLights", Cmd_ClearLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "clears all lights" ); + cmdSystem->AddCommand( "gameError", Cmd_GameError_f, CMD_FL_GAME|CMD_FL_CHEAT, "causes a game error" ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + cmdSystem->AddCommand( "listEntityStats", Cmd_ListEntityStats_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists global entity stats" ); +// ddynerman: mp spawning test command + cmdSystem->AddCommand( "evaluateMPPerformance", Cmd_EvaluateMPPerformance_f,CMD_FL_GAME|CMD_FL_CHEAT, "spawns serveral player models", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "listMapEntities", idGameLocal::Cmd_PrintMapEntityNumbers_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); + cmdSystem->AddCommand( "listSpawnIds", idGameLocal::Cmd_PrintSpawnIds_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); +// RAVEN END + +#ifndef ID_DEMO_BUILD + cmdSystem->AddCommand( "disasmScript", Cmd_DisasmScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "disassembles script" ); +// RAVEN BEGIN +// rjohnson: removed old not taking system +/* + cmdSystem->AddCommand( "recordViewNotes", Cmd_RecordViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "record the current view position with notes" ); + cmdSystem->AddCommand( "showViewNotes", Cmd_ShowViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "show any view notes for the current map, successive calls will cycle to the next note" ); + cmdSystem->AddCommand( "closeViewNotes", Cmd_CloseViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "close the view showing any notes for this map" ); +*/ +// RAVEN END + cmdSystem->AddCommand( "exportmodels", Cmd_ExportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "exports models", ArgCompletion_DefFile ); + + // multiplayer client commands ( replaces old impulses stuff ) + //cmdSystem->AddCommand( "clientDropWeapon", idMultiplayerGame::DropWeapon_f, CMD_FL_GAME, "drop current weapon" ); + cmdSystem->AddCommand( "clientMessageMode", idMultiplayerGame::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); + // FIXME: implement + cmdSystem->AddCommand( "clientVote", idMultiplayerGame::Vote_f, CMD_FL_GAME, "cast your vote: clientVote yes | no" ); + cmdSystem->AddCommand( "clientCallVote", idMultiplayerGame::CallVote_f, CMD_FL_GAME, "call a vote: clientCallVote si_.. proposed_value" ); + cmdSystem->AddCommand( "clientVoiceChat", idMultiplayerGame::VoiceChat_f, CMD_FL_GAME, "voice chats: clientVoiceChat " ); + cmdSystem->AddCommand( "clientVoiceChatTeam", idMultiplayerGame::VoiceChatTeam_f, CMD_FL_GAME, "team voice chats: clientVoiceChat " ); +// RAVEN BEGIN + // jshepard + cmdSystem->AddCommand( "forceTeamChange", idMultiplayerGame::ForceTeamChange_f, CMD_FL_GAME, "force team change: forceTeamChange " ); + cmdSystem->AddCommand( "removeClientFromBanList", idMultiplayerGame::RemoveClientFromBanList_f, CMD_FL_GAME, "removes a client id from the ban list: removeClientFromBanList " ); + +#ifndef _XBOX +// shouchard: more voice chat stuff (non-XBOX) + cmdSystem->AddCommand( "clientvoicemute", idMultiplayerGame::VoiceMute_f, CMD_FL_GAME, "mute the specified player's incoming voicechat" ); + cmdSystem->AddCommand( "clientvoiceunmute", idMultiplayerGame::VoiceUnmute_f, CMD_FL_GAME, "unmute the specified player's incoming voicechat" ); +#endif // _XBOX +// RAVEN END + + // multiplayer server commands + cmdSystem->AddCommand( "verifyServerSettings", idGameLocal::VerifyServerSettings_f, CMD_FL_GAME, "verifies the game type can be played on the map" ); + cmdSystem->AddCommand( "serverMapRestart", idGameLocal::MapRestart_f, CMD_FL_GAME, "restart the current game" ); + cmdSystem->AddCommand( "serverForceReady", idMultiplayerGame::ForceReady_f,CMD_FL_GAME, "force all players ready" ); + cmdSystem->AddCommand( "serverNextMap", idGameLocal::NextMap_f, CMD_FL_GAME, "change to the next map" ); +#endif + + cmdSystem->AddCommand( "CheckTeamBalance", idMultiplayerGame::CheckTeamBalance_f, CMD_FL_GAME, "helper for team switching in the guis - " ); + + // localization help commands + cmdSystem->AddCommand( "nextGUI", Cmd_NextGUI_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleport the player to the next func_static with a gui" ); + cmdSystem->AddCommand( "testid", Cmd_TestId_f, CMD_FL_GAME|CMD_FL_CHEAT, "output the string for the specified id." ); + +// RAVEN BEGIN +// bdube: vehicle code + cmdSystem->AddCommand( "killVehicles", Cmd_KillVehicles_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all vehicles" ); + cmdSystem->AddCommand( "killMessage", Cmd_KillMessage_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints a fake death message" ); + cmdSystem->AddCommand( "apState", Cmd_APState_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints AP state" ); +// bdube: jump points + cmdSystem->AddCommand( "jump", Cmd_DebugJump_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to a specific debug jump point" ); + cmdSystem->AddCommand( "nextjumppoint", Cmd_DebugNextJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the next debug jump point " ); + cmdSystem->AddCommand( "prevjumppoint", Cmd_DebugPrevJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the previous debug jump point" ); +// cdr: Added Extract Tactical + cmdSystem->AddCommand( "extract_tactical", Cmd_AASExtractTactical_f, CMD_FL_GAME, "pulls tactical information for the current position." ); +// RAVEN END + +// RAVEN BEGIN +// abahr + cmdSystem->AddCommand( "call", Cmd_CallScriptFunc_f, CMD_FL_GAME|CMD_FL_CHEAT, "calls script function and prints out return val" ); + cmdSystem->AddCommand( "setPlayerGravity", Cmd_SetPlayerGravity_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets players local gravity" ); +// cdr + cmdSystem->AddCommand( "ai_debugFilter", Cmd_AI_DebugFilter_f, CMD_FL_GAME, "ai_debugMove and ai_debugTactical only work on the specified entity (if none, does one you're looking at)", idGameLocal::ArgCompletion_AIName ); +// ddynerman: multiple arena/CW stuff + cmdSystem->AddCommand( "setInstance", Cmd_SetInstance_f, CMD_FL_GAME, "sets a player's world instance" ); + cmdSystem->AddCommand( "addIcon", Cmd_AddIcon_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a test icon" ); + cmdSystem->AddCommand( "listInstances", Cmd_ListInstances_f, CMD_FL_GAME, "lists instances" ); +// ddynerman: emote anims + cmdSystem->AddCommand( "emote", Cmd_PlayerEmote_f, CMD_FL_GAME, "plays an emote" ); + + cmdSystem->AddCommand( "checkSave", Cmd_CheckSave_f, CMD_FL_GAME, "tests save system" ); + +// jshepard: fade music in / out + cmdSystem->AddCommand( "fadeSound", Cmd_FadeSound_f, CMD_FL_GAME|CMD_FL_CHEAT, "fades all sound by X decibles over Y seconds" ); + +// mekberg: added. + cmdSystem->AddCommand( "setPMCVars", Cmd_SetPMCVars_f, CMD_FL_GAME, "Resets player movement cvars" ); + + cmdSystem->AddCommand( "testClientModel", Cmd_TestClientModel_f, CMD_FL_GAME, "" ); +#ifndef _FINAL + cmdSystem->AddCommand( "clientOverflowReliable", Cmd_ClientOverflowReliable_f, CMD_FL_GAME, "" ); +#endif +// RAVEN END +// RITUAL START +// squirrel: Mode-agnostic buymenus + cmdSystem->AddCommand( "buyMenu", Cmd_ToggleBuyMenu_f, CMD_FL_GAME, "Toggle buy menu (if in a buy zone and the game type supports it)" ); + cmdSystem->AddCommand( "buy", Cmd_BuyItem_f, CMD_FL_GAME, "Buy an item (if in a buy zone and the game type supports it)" ); +// RITUAL END + +} + +/* +================= +idGameLocal::ShutdownConsoleCommands +================= +*/ +void idGameLocal::ShutdownConsoleCommands( void ) { + cmdSystem->RemoveFlaggedCommands( CMD_FL_GAME ); +} diff --git a/source/game/gamesys/SysCmds.h b/source/game/gamesys/SysCmds.h new file mode 100644 index 0000000..cb60f62 --- /dev/null +++ b/source/game/gamesys/SysCmds.h @@ -0,0 +1,11 @@ + +#ifndef __SYS_CMDS_H__ +#define __SYS_CMDS_H__ + +void D_DrawDebugLines( void ); + +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ); + +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ); + +#endif /* !__SYS_CMDS_H__ */ diff --git a/source/game/gamesys/SysCvar.cpp b/source/game/gamesys/SysCvar.cpp new file mode 100644 index 0000000..305ec3d --- /dev/null +++ b/source/game/gamesys/SysCvar.cpp @@ -0,0 +1,647 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "-release" +#endif + +/* + +All game cvars should be defined here. + +*/ + +// RAVEN BEGIN +// ddynerman: our gameplay modes +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +const char *si_gameTypeArgs[] = { "singleplayer", "DM", "Tourney", "Team DM", "CTF", "Arena CTF", "DeadZone", NULL }; +const int si_numGameTypeArgs = sizeof( si_gameTypeArgs ) / sizeof( si_gameTypeArgs[0] ); +// RITUAL END +// RAVEN END +const char *si_readyArgs[] = { "Not Ready", "Ready", NULL }; +const char *si_spectateArgs[] = { "Play", "Spectate", NULL }; + +// RAVEN BEGIN +// ddynerman: our teams +const char *ui_teamArgs[] = { "Marine", "Strogg", NULL }; +// RAVEN END + +struct gameVersion_s { + gameVersion_s( void ) { sprintf( string, "%s %s V%s %s %s", GAME_NAME, GAME_BUILD_TYPE, VERSION_STRING_DOTTED, BUILD_STRING, __DATE__ ); } + char string[256]; +} gameVersion; + +idCVar g_version( "g_version", gameVersion.string, CVAR_GAME | CVAR_ROM, "game version" ); + +// noset vars +idCVar gamename( "gamename", GAME_VERSION, CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "" ); +idCVar gamedate( "gamedate", __DATE__, CVAR_GAME | CVAR_ROM, "" ); + +// server info +idCVar si_name( "si_name", "Quake 4 Server", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "name of the server" ); +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +//idCVar sq_numRoundsPerMatch( "dz_numRoundsPerMatch", "5", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of rounds per match in DeadZone", 1, 999999 ); +//idCVar sq_buyFreezeSeconds( "dz_buyFreezeSeconds", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of seconds players are frozen at the start of each round in DeadZone", 0, 30 ); +//idCVar sq_buyTimeSeconds( "dz_buyTimeSeconds", "20", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of additional seconds after buy freeze that buy zones are active", 0, 999999 ); +// squirrel: Mode-agnostic buymenus +idCVar si_isBuyingEnabled( "si_isBuyingEnabled", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable buying in current mode" ); +idCVar si_dropWeaponsInBuyingModes( "si_dropWeaponsInBuyingModes", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "dead players drop weapons, even in Buying game modes" ); +// RITUAL END +// RAVEN BEGIN +// ddynerman: new gametype strings +idCVar si_gameType( "si_gameType", si_gameTypeArgs[ 0 ], CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "game type - singleplayer, DM, Tourney, Team DM, CTF, Arena CTF, or DeadZone", si_gameTypeArgs, idCmdSystem::ArgCompletion_String ); +idCVar si_map( "si_map", "mp/q4dm1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map to be played next on server", idCmdSystem::ArgCompletion_MapName ); +idCVar si_mapCycle( "si_mapCycle", "", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map cycle list semicolon delimited" ); +// bdube: raise player limit +idCVar si_maxPlayers( "si_maxPlayers", "12", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "max number of players allowed on the server", 1, 16 ); +// ddynerman: min players to start +idCVar si_minPlayers( "si_minPlayers", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "min number of players to start a game (only when warmup is enabled)", 1, 16 ); +// ddynerman: CTF +idCVar si_captureLimit( "si_captureLimit", "5", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "score limit for CTF", 1, MP_PLAYER_MAXFRAGS ); +// shouchard: for tourney +idCVar si_tourneyLimit( "si_tourneyLimit", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of times a tourney will be run before cycling maps", 1, MP_PLAYER_MAXFRAGS ); +idCVar si_useReady( "si_useReady", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "require players to ready before starting a match" ); +idCVar si_allowVoting( "si_allowVoting", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable or disable server option voting" ); +// ddynerman: disable hitscan tint +idCVar si_allowHitscanTint( "si_allowHitscanTint", "2", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "use hitscan tint (e.g. rail color) 0 - no tinting allowed, 1 - player hitscan tinting allowed in DM and NO hitscan tinting in team games, 2 - player hitscan tinting allowed in DM and use team-color hitscan tints in team games" ); +idCVar si_privatePlayers( "si_privatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "number of private player slots reserved on the server. subtracts from si_maxPlayers, so a server with si_maxPlayers 16 and 4 private player slots will only allow 12 public players to connect - see g_privatePassword, privatePassword", 0, 16 ); +idCVar g_privatePassword( "g_privatePassword", "", CVAR_GAME | PC_CVAR_ARCHIVE, "server-side password to access reserved client slots, clients set privatePassword" ); +idCVar privatePassword( "privatePassword", "", CVAR_GAME | CVAR_NOCHEAT, "client password used to access a servers private player slots" ); +idCVar si_numPrivatePlayers( "si_numPrivatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "number of private slots currently in use" ); +idCVar si_suddenDeathRestart( "si_suddenDeathRestart", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "toggles whether or not to respawn players/items when team games enter sudden death" ); +// RAVEN END +idCVar si_fragLimit( "si_fragLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "frag limit", 0, MP_PLAYER_MAXFRAGS ); +idCVar si_timeLimit( "si_timeLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "time limit in minutes", 0, 60 ); +idCVar si_teamDamage( "si_teamDamage", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable team damage" ); +idCVar si_warmup( "si_warmup", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "do pre-game warmup" ); +idCVar si_usePass( "si_usePass", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable client password checking" ); +#ifdef _MPBETA + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | CVAR_ROM, "server is pure and does not allow modified data" ); +#else + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "server is pure and does not allow modified data" ); +#endif // _MPBETA +idCVar si_spectators( "si_spectators", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "allow spectators or require all clients to play" ); +idCVar si_shuffle( "si_shuffle", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "shuffle teams after each round" ); +// shouchard: g_balanceTDM->si_autobalance so we can also use it for CTF +// asalmon: Changed to archive only on PC +idCVar si_autobalance( "si_autobalance", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | PC_CVAR_ARCHIVE, "maintain even teams" ); + +// RAVEN BEGIN +// jscott: added entity filtering +idCVar si_entityFilter( "si_entityFilter", "", CVAR_GAME | CVAR_SERVERINFO, "filter to use when spawning entities" ); +idCVar si_countDown( "si_countDown", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "pregame countdown in seconds", 4, 3600 ); +// MCG: added "weapon stay" option +idCVar si_weaponStay( "si_weaponStay", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "cannot pick up weapons you already have (get no ammo from them)" ); +// RAVEN END + +// RITUAL BEGIN +// DeadZone Mode and Buying related CVARS +idCVar si_deadZonePowerupTime( "si_deadZonePowerupTime", "45", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of time the dead zone powerup lasts" ); +idCVar si_buyModeStartingCredits( "si_buyModeStartingCredits", "1000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of credits players start with in buying enable games" ); +idCVar si_buyModeMaxCredits( "si_buyModeMaxCredits", "25000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Maximum amount of credits in buying enable games" ); +idCVar si_buyModeMinCredits( "si_buyModeMinCredits", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Minimum amount of credits in buying enable games" ); +idCVar si_controlTime( "si_controlTime", "120", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Time required to hold the dead zone", 1, 999 ); +// RITUAL END + + +// user info +idCVar ui_name( "ui_name", "Player", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player name" ); +idCVar ui_team( "ui_team", ui_teamArgs[ 0 ], CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player team", ui_teamArgs, idCmdSystem::ArgCompletion_String ); +// RAVEN BEGIN +// ddynerman: new UI cvars +idCVar ui_model( "ui_model", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model, blank uses default model" ); +idCVar ui_model_backup( "ui_model_backup", "", CVAR_GAME | CVAR_USERINFO, "player model backup" ); +idCVar ui_model_marine( "ui_model_marine", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on marine team in team games, blank uses default model" ); +idCVar ui_model_strogg( "ui_model_strogg", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on strogg team in team games, blank uses default model" ); +idCVar ui_clan( "ui_clan", "", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player clan" ); +idCVar ui_hitscanTint( "ui_hitscanTint", "120.0 0.6 1.0", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "a tint applied to select hitscan effects. Specified as a value in HSV color space. Hue [0.0-360.0] Saturation [0.0-1.0] Value [0.75-1.0]" ); +// RAVEN END +idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); +idCVar ui_autoReload( "ui_autoReload", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); +idCVar ui_showGun( "ui_showGun", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "show gun" ); +idCVar ui_ready( "ui_ready", si_readyArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "player is ready to start playing", idCmdSystem::ArgCompletion_String ); +idCVar ui_spectate( "ui_spectate", si_spectateArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "play or spectate", idCmdSystem::ArgCompletion_String ); +idCVar ui_chat( "ui_chat", "0", CVAR_GAME | CVAR_USERINFO | CVAR_BOOL | CVAR_ROM | CVAR_CHEAT, "player is chatting" ); + +// change anytime vars +idCVar developer( "developer", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_forceModel( "g_forceModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces all players to this model in non-team gameplay modes. See g_forceStroggModel, g_forceMarineModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModel ); +idCVar g_forceStroggModel( "g_forceStroggModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Strogg team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelStrogg ); +idCVar g_forceMarineModel( "g_forceMarineModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Marine team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelMarine ); + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +idCVar g_fixedHorizFOV( "r_fixedHorizFOV", "0", CVAR_RENDERER | CVAR_BOOL, "vertical stretch for letterboxed cinematics authored for 4:3 aspect" ); +idCVar g_cinematic( "g_cinematic", "1", CVAR_GAME | CVAR_BOOL, "skips updating entities that aren't marked 'cinematic' '1' during cinematics" ); +idCVar g_cinematicMaxSkipTime( "g_cinematicMaxSkipTime", "600", CVAR_GAME | CVAR_FLOAT, "# of seconds to allow game to run when skipping cinematic. prevents lock-up when cinematic doesn't end.", 0, 3600 ); + +idCVar g_muzzleFlash( "g_muzzleFlash", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show muzzle flashes" ); +idCVar g_projectileLights( "g_projectileLights", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show dynamic lights on projectiles" ); +idCVar g_doubleVision( "g_doubleVision", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show double vision when taking damage" ); +idCVar g_monsters( "g_monsters", "1", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_decals( "g_decals", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "show decals such as bullet holes" ); +idCVar g_knockback( "g_knockback", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_skill( "g_skill", "1", CVAR_GAME | CVAR_INTEGER, "difficulty level", 0, MAX_SKILL_LEVELS - 1 ); +idCVar g_nightmare( "g_nightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed" ); +idCVar g_gravity( "g_gravity", DEFAULT_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "singleplayer gravity" ); +idCVar g_mp_gravity( "g_mp_gravity", DEFAULT_MP_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "multiplayer gravity" ); +idCVar g_skipFX( "g_skipFX", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_skipParticles( "g_skipParticles", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_disasm( "g_disasm", "0", CVAR_GAME | CVAR_BOOL, "disassemble script into base/script/disasm.txt on the local drive when script is compiled" ); +idCVar g_debugBounds( "g_debugBounds", "0", CVAR_GAME | CVAR_BOOL, "checks for models with bounds > 2048" ); +idCVar g_debugAnim( "g_debugAnim", "-1", CVAR_GAME | CVAR_INTEGER, "displays information on which animations are playing on the specified entity number. set to -1 to disable." ); +idCVar g_debugMove( "g_debugMove", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugDamage( "g_debugDamage", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugWeapon( "g_debugWeapon", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugScript( "g_debugScript", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugMover( "g_debugMover", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME, "set to the name of the state you want to debug or * for all" ); +// RAVEN BEGIN +// bdube: added +idCVar g_debugState( "g_debugState", "0", CVAR_GAME, "" ); +idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); +//idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); +// RAVEN END +idCVar g_armorProtection( "g_armorProtection", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage" ); +idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); +idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how often to take health in nightmare mode" ); +idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how much health to take in nightmare mode" ); +idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); + + + +idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and thier targets. hidden entities are drawn grey." ); +idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and thier targets (green). disabled triggers are drawn grey." ); +idCVar g_showCollisionWorld( "g_showCollisionWorld", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionModels( "g_showCollisionModels", "0", CVAR_GAME | CVAR_INTEGER, "0 = off, 1 = draw collision models, 2 = only draw player collision models. g_maxShowDistance controls distance." ); +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces +idCVar g_showCollisionTraces( "g_showCollisionTraces", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +// ddynerman: SD's clip sector code +idCVar g_showClipSectors( "g_showClipSectors", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showClipSectorFilter( "g_showClipSectorFilter", "0", CVAR_GAME, "" ); +idCVar g_showAreaClipSectors( "g_showAreaClipSectors", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN END +idCVar g_maxShowDistance( "g_maxShowDistance", "128", CVAR_GAME | CVAR_FLOAT, "Distance at which to draw clipmodels and clipworld - Will significantly hurt performance at values above 512" ); +idCVar g_showEntityInfo( "g_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showviewpos( "g_showviewpos", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showcamerainfo( "g_showcamerainfo", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "displays the current frame # for the camera when playing cinematics" ); +idCVar g_showTestModelFrame( "g_showTestModelFrame", "0", CVAR_GAME | CVAR_BOOL, "displays the current animation and frame # for testmodels" ); +idCVar g_showActiveEntities( "g_showActiveEntities", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around thinking entities. dormant entities (outside of pvs) are drawn yellow. non-dormant are green." ); +idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around monsters that have targeted the the player" ); + +idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); +idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); + +// RAVEN BEGIN +// bdube: frame command debugging +idCVar g_showFrameCmds( "g_showFrameCmds", "0", CVAR_GAME | CVAR_BOOL, "displays frame commands as they are executed" ); +idCVar g_showGodDamage( "g_showGodDamage", "0", CVAR_GAME | CVAR_BOOL, "displays the amount of damage taken while in god mode on the hud" ); +idCVar g_debugVehicle( "g_debugVehicle", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +// RAVEN BEGIN +// twhitaker: for rvVehicleDriver +idCVar g_debugVehicleDriver( "g_debugVehicleDriver", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the func_vehicle_driver" ); +idCVar g_debugVehicleAI( "g_debugVehicleAI", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the vehicle ai system" ); +idCVar g_vehicleMode( "g_vehicleMode", "1", CVAR_GAME | CVAR_INTEGER, "enables the new vehicle control system for the GEV." ); +// RAVEN END +idCVar g_allowVehicleGunOverheat( "g_allowVehicleGunOverheat","1", CVAR_GAME | CVAR_BOOL, "allows disabling the gun overheating mechanism for vehicles that use it." ); + +idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); +idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); +idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); +idCVar ai_debugTactical( "ai_debugTactical", "0", CVAR_GAME, "draws tactical information for monsters" ); +idCVar ai_debugHelpers( "ai_debugHelpers", "0", CVAR_GAME, "draws ai helpers" ); +idCVar ai_debugFilterString( "ai_debugFilterString", "", CVAR_GAME, "see ai_debugFilter" ); +idCVar ai_testPredictPath( "ai_testPredictPath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCombatNodes( "ai_showCombatNodes", "0", CVAR_GAME | CVAR_BOOL, "draws attack cones for monsters" ); +idCVar ai_showPaths( "ai_showPaths", "0", CVAR_GAME | CVAR_BOOL, "draws path_* entities" ); +idCVar ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "0", CVAR_GAME | CVAR_INTEGER, "draws obstacle avoidance information for monsters. if 2, draws obstacles for player, as well", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar ai_blockedFailSafe( "ai_blockedFailSafe", "1", CVAR_GAME | CVAR_BOOL, "enable blocked fail safe handling" ); +idCVar ai_debugSquad( "ai_debugSquad", "0", CVAR_GAME | CVAR_BOOL, "draws squad info for allies" ); +idCVar ai_debugStealth( "ai_debugStealth", "0", CVAR_GAME | CVAR_INTEGER, "draws suspicion info for enemies" ); +idCVar ai_allowTacticalRush( "ai_allowTacticalRush", "1", CVAR_GAME | CVAR_BOOL, "allows tactical ai to rush an enemy when hurt" ); + + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +idCVar ai_speeds( "ai_speeds", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_freeze( "ai_freeze", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_animShow( "ai_animShow", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCover( "ai_showCover", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_showTacticalFeatures( "ai_showTacticalFeatures", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_disableEntTactical( "ai_disableEntTactical", "0", CVAR_GAME | CVAR_BOOL, "disables tactical points around entities" ); +idCVar ai_disableAttacks( "ai_disableAttacks", "0", CVAR_GAME | CVAR_BOOL, "disables attack decisions" ); +idCVar ai_disableSimpleThink( "ai_disableSimpleThink", "0", CVAR_GAME | CVAR_BOOL, "disables simple thinking in AI entities" ); +idCVar ai_disableCover( "ai_disableCover", "0", CVAR_GAME | CVAR_BOOL, "disables AI using cover points" ); +//cdr: use new master move functions +idCVar ai_useRVMasterMove( "ai_useRVMasterMove", "0", CVAR_GAME | CVAR_BOOL, "changes AI to use new master move function" ); +//jshepard: allow out of date AAS files to be used, for testing +idCVar ai_allowOldAAS( "ai_allowOldAAS", "0", CVAR_GAME | CVAR_BOOL, "allows AI to use most recent AAS file, even if it is not up-to-date. Enable only for testing."); +// twhitaker: debugging support for eye focus +idCVar ai_debugEyeFocus( "ai_debugEyeFocus", "0", CVAR_GAME | CVAR_BOOL, "draws eye focus info" ); +//mcg: always allow player to push buddies, unless scripted +idCVar ai_playerPushAlways( "ai_playerPushAlways", "1", CVAR_GAME | CVAR_BOOL, "always allow player to push buddies, unless scripted" ); +// RAVEN END + +idCVar g_dvTime( "g_dvTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvAmplitude( "g_dvAmplitude", "0.001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvFrequency( "g_dvFrequency", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_kickTime( "g_kickTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_kickAmplitude( "g_kickAmplitude", "0.0001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobTime( "g_blobTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobSize( "g_blobSize", "1", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_testHealthVision( "g_testHealthVision", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_editEntityMode( "g_editEntityMode", "0", CVAR_GAME | CVAR_INTEGER, "0 = off\n" + "1 = lights\n" + "2 = sounds\n" + "3 = articulated figures\n" + "4 = particle systems\n" + "5 = monsters\n" + "6 = entity names\n" +// RAVEN BEGIN +// bdube: extended + "7 = entity models\n" + "8 = effects", 0, 8, idCmdSystem::ArgCompletion_Integer<0,8> ); +// rhummer: Added archive flag. +idCVar g_editEntityDistance( "g_editEntityDistance", "512", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit" ); +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. Also added archive flag. +idCVar g_editEntityTextDistance( "g_editEntityTextDistance", "256", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit text information"); +idCVar g_testCTF( "g_testCTF", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "" ); +// rjohnson: entity usage stats +idCVar g_keepEntityStats( "g_keepEntityStats", "0", CVAR_GAME | CVAR_CHEAT |CVAR_BOOL, "keep track of entity usage stats" ); +// RAVEN END +idCVar g_dragEntity( "g_dragEntity", "0", CVAR_GAME | CVAR_BOOL, "allows dragging physics objects around by placing the crosshair over them and holding the fire button" ); +idCVar g_dragDamping( "g_dragDamping", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dragShowSelection( "g_dragShowSelection", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_dropItemRotation( "g_dropItemRotation", "", CVAR_GAME, "" ); + +idCVar g_vehicleVelocity( "g_vehicleVelocity", "1000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleForce( "g_vehicleForce", "50000", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar ik_enable( "ik_enable", "1", CVAR_GAME | CVAR_BOOL, "enable IK" ); +idCVar ik_debug( "ik_debug", "0", CVAR_GAME | CVAR_BOOL, "show IK debug lines" ); + +idCVar af_useLinearTime( "af_useLinearTime", "1", CVAR_GAME | CVAR_BOOL, "use linear time algorithm for tree-like structures" ); +idCVar af_useImpulseFriction( "af_useImpulseFriction", "0", CVAR_GAME | CVAR_BOOL, "use impulse based contact friction" ); +idCVar af_useJointImpulseFriction( "af_useJointImpulseFriction","0", CVAR_GAME | CVAR_BOOL, "use impulse based joint friction" ); +idCVar af_useSymmetry( "af_useSymmetry", "1", CVAR_GAME | CVAR_BOOL, "use constraint matrix symmetry" ); +idCVar af_skipSelfCollision( "af_skipSelfCollision", "0", CVAR_GAME | CVAR_BOOL, "skip self collision detection" ); +idCVar af_skipLimits( "af_skipLimits", "0", CVAR_GAME | CVAR_BOOL, "skip joint limits" ); +idCVar af_skipFriction( "af_skipFriction", "0", CVAR_GAME | CVAR_BOOL, "skip friction" ); +idCVar af_forceFriction( "af_forceFriction", "-1", CVAR_GAME | CVAR_FLOAT, "force the given friction value" ); +idCVar af_maxLinearVelocity( "af_maxLinearVelocity", "128", CVAR_GAME | CVAR_FLOAT, "maximum linear velocity" ); +idCVar af_maxAngularVelocity( "af_maxAngularVelocity", "1.57", CVAR_GAME | CVAR_FLOAT, "maximum angular velocity" ); +idCVar af_timeScale( "af_timeScale", "1", CVAR_GAME | CVAR_FLOAT, "scales the time" ); +idCVar af_jointFrictionScale( "af_jointFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the joint friction" ); +idCVar af_contactFrictionScale( "af_contactFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the contact friction" ); +idCVar af_highlightBody( "af_highlightBody", "", CVAR_GAME, "name of the body to highlight" ); +idCVar af_highlightConstraint( "af_highlightConstraint", "", CVAR_GAME, "name of the constraint to highlight" ); +idCVar af_showTimings( "af_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show articulated figure cpu usage" ); +idCVar af_showConstraints( "af_showConstraints", "0", CVAR_GAME | CVAR_BOOL, "show constraints" ); +idCVar af_showConstraintNames( "af_showConstraintNames", "0", CVAR_GAME | CVAR_BOOL, "show constraint names" ); +idCVar af_showConstrainedBodies( "af_showConstrainedBodies", "0", CVAR_GAME | CVAR_BOOL, "show the two bodies contrained by the highlighted constraint" ); +idCVar af_showPrimaryOnly( "af_showPrimaryOnly", "0", CVAR_GAME | CVAR_BOOL, "show primary constraints only" ); +idCVar af_showTrees( "af_showTrees", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures" ); +idCVar af_showLimits( "af_showLimits", "0", CVAR_GAME | CVAR_BOOL, "show joint limits" ); +idCVar af_showBodies( "af_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show bodies" ); +idCVar af_showBodyNames( "af_showBodyNames", "0", CVAR_GAME | CVAR_BOOL, "show body names" ); +idCVar af_showMass( "af_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each body" ); +idCVar af_showTotalMass( "af_showTotalMass", "0", CVAR_GAME | CVAR_BOOL, "show the total mass of each articulated figure" ); +idCVar af_showInertia( "af_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each body" ); +idCVar af_showVelocity( "af_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each body" ); +idCVar af_showActive( "af_showActive", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures of articulated figures not at rest" ); +idCVar af_testSolid( "af_testSolid", "1", CVAR_GAME | CVAR_BOOL, "test for bodies initially stuck in solid" ); + +idCVar rb_showTimings( "rb_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show rigid body cpu usage" ); +idCVar rb_showBodies( "rb_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies" ); +idCVar rb_showMass( "rb_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each rigid body" ); +idCVar rb_showInertia( "rb_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each rigid body" ); +idCVar rb_showVelocity( "rb_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each rigid body" ); +idCVar rb_showActive( "rb_showActive", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies that are not at rest" ); + +// RAVEN BEGIN +// bdube: more rigid body debug +idCVar rb_showContacts( "rb_showContacts", "0", CVAR_GAME | CVAR_BOOL, "show rigid body contacts" ); +// RAVEN END + +// The default values for player movement cvars are set in def/player.def +idCVar pm_jumpheight( "pm_jumpheight", "48", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "approximate hieght the player can jump" ); +idCVar pm_stepsize( "pm_stepsize", "16", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "maximum height the player can step up without jumping" ); +idCVar pm_crouchspeed( "pm_crouchspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while crouched" ); +// RAVEN BEGIN +idCVar pm_speed( "pm_speed", "160", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while running" ); +idCVar pm_walkspeed( "pm_walkspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while walking" ); +// RAVEN END +idCVar pm_noclipspeed( "pm_noclipspeed", "270", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while in noclip" ); +idCVar pm_spectatespeed( "pm_spectatespeed", "450", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while spectating" ); +idCVar pm_spectatebbox( "pm_spectatebbox", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "size of the spectator bounding box" ); +idCVar pm_usecylinder( "pm_usecylinder", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL | CVAR_NORESET, "use a cylinder approximation instead of a bounding box for player collision detection" ); +idCVar pm_minviewpitch( "pm_minviewpitch", "-89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look up (negative values are up)" ); +idCVar pm_maxviewpitch( "pm_maxviewpitch", "89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look down" ); +idCVar pm_stamina( "pm_stamina", "24", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "length of time player can run" ); +idCVar pm_staminathreshold( "pm_staminathreshold", "45", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "when stamina drops below this value, player gradually slows to a walk" ); +idCVar pm_staminarate( "pm_staminarate", "0.75", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "rate that player regains stamina. divide pm_stamina by this value to determine how long it takes to fully recharge." ); + +// ddynerman: adjusted bboxes to actual height +idCVar pm_normalheight( "pm_normalheight", "77", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while standing" ); +idCVar pm_crouchheight( "pm_crouchheight", "49", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while crouched" ); + +idCVar pm_crouchviewheight( "pm_crouchviewheight", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while crouched" ); +idCVar pm_normalviewheight( "pm_normalviewheight", "68", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while standing" ); + +idCVar pm_deadheight( "pm_deadheight", "20", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while dead" ); +idCVar pm_deadviewheight( "pm_deadviewheight", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while dead" ); +idCVar pm_crouchrate( "pm_crouchrate", "0.87", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "time it takes for player's view to change from standing to crouching" ); +idCVar pm_bboxwidth( "pm_bboxwidth", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "x/y size of player's bounding box" ); +idCVar pm_crouchbob( "pm_crouchbob", "0.5", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob much faster when crouched" ); +idCVar pm_walkbob( "pm_walkbob", "0.3", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob slowly when walking" ); +idCVar pm_runbob( "pm_runbob", "0.4", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob faster when running" ); +idCVar pm_runpitch( "pm_runpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_runroll( "pm_runroll", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobup( "pm_bobup", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobpitch( "pm_bobpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobroll( "pm_bobroll", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +idCVar pm_thirdPersonRange( "pm_thirdPersonRange", "80", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "camera distance from player in 3rd person" ); +idCVar pm_thirdPersonHeight( "pm_thirdPersonHeight", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "height of camera from normal view height in 3rd person" ); +idCVar pm_thirdPersonAngle( "pm_thirdPersonAngle", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "direction of camera from player in 3rd person in degrees (0 = behind player, 180 = in front)" ); +idCVar pm_thirdPersonClip( "pm_thirdPersonClip", "1", CVAR_GAME | CVAR_BOOL, "clip third person view into world space" ); +idCVar pm_thirdPerson( "pm_thirdPerson", "0", CVAR_GAME | CVAR_BOOL, "enables third person view" ); +idCVar pm_thirdPersonDeath( "pm_thirdPersonDeath", "0", CVAR_GAME | CVAR_BOOL, "enables third person view when player dies" ); +idCVar pm_modelView( "pm_modelView", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "draws camera from POV of player model (1 = always, 2 = when dead)", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar pm_airTics( "pm_air", "1800", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "how long in milliseconds the player can go without air before he starts taking damage" ); + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon - or a non-final pc build so Caryn can edit the guis +#if defined( _XBOX ) || !defined( _FINAL ) +idCVar pm_AimAssist( "pm_AimAssist", "2", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE , "Enable Xbox aim assistance. 1 to use change player view method. 2 to use change muzzle aim method.\n"); +idCVar pm_AimAssistDistance( "pm_AimAssistDistance", "1000", CVAR_GAME | CVAR_INTEGER, "The max aim assist distance.\n"); +idCVar pm_AimAssistThreshold( "pm_AimAssistThreshold", "1.0", CVAR_GAME | CVAR_FLOAT, "Threshold by which the projectile is aimed at the offending target.\n"); +idCVar pm_AimAssistFOV( "pm_AimAssistFOV", "10", CVAR_GAME | CVAR_INTEGER, "The field of view for aim assistance.\n"); +idCVar pm_AimAssistBump( "pm_AimAssistBump", "10", CVAR_GAME | CVAR_INTEGER, "The percentage of correction applied either to the view or the muzzle aim.\n"); +idCVar pm_showAimAssist( "pm_showAimAssist", "0", CVAR_GAME | CVAR_BOOL, "Draw aim assist frustum and bounding boxes.\n"); +idCVar pm_AimAssistSlow( "pm_AimAssistSlow", "50", CVAR_GAME | CVAR_INTEGER, "The percentage to slow the turning motion by when targeting an enemy.\n"); + +//asalmon: xenon controller config cvars +idCVar pm_ThumbstickConfig( "pm_ThumbstickConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the thumbstick config on Xenon. 0 right handed, 1 left handed.\n"); +idCVar pm_ButtonConfig( "pm_ButtonConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the button configuration for Xenon.\n"); +idCVar pm_Inversion( "pm_Inversion", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "invert look up and down\n"); + +idCVar pm_VLookSens( "pm_VLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HLookSens( "pm_HLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_VMoveSens( "pm_VMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HMoveSens( "pm_HMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); + +idCVar pm_voiceEnabled( "pm_voiceEnabled", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_GUI, "Enable/disable voice.\n"); + +//asalmon: Xenon leaderboard cvars +idCVar ui_LeaderboardView( "ui_LeaderboardView", "17", CVAR_INTEGER | CVAR_NOCHEAT, "Which leaderboard to show.\n"); +idCVar ui_LeaderboardSort( "ui_LeaderboardSort", "1", CVAR_INTEGER | CVAR_NOCHEAT, "How to sort the leaderboard. 0 for rating, 1 for ranking, 2 for friends, 3 find logged in player.\n"); + +//nrausch +idCVar pm_RocketJumpAutocenter( "pm_RocketJumpAutocenter", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Automatic autocentering following a rocket jump\n"); +idCVar pm_IAmACheater( "pm_IAmACheater", "0", CVAR_GAME | CVAR_BOOL, "Whomever is playing is a dirty, rotten cheater\n"); + +idCVar g_systemLinkMatch( "g_systemLinkMatch", "0", CVAR_INTEGER, "In a system link game\n"); + +//asalmon: cvars for Live teams. Teams will now be a post launch feature but this was left here in case it is of use on future projects +//idCVar ui_LiveClanName( "ui_LiveClanName", "My Clan", CVAR_GAME | CVAR_USERINFO, "The name of the live clan being created\n"); +//idCVar ui_LiveClanDesc( "ui_LiveClanDesc", "A Quake 4 clan", CVAR_GAME | CVAR_USERINFO, "The description of the live clan being created\n"); +//idCVar ui_LiveClanMotto( "ui_LiveClanMotto", "We love Quake 4", CVAR_GAME | CVAR_USERINFO, "The motto of the live clan being created\n"); +//idCVar ui_LiveClanUrl( "ui_LiveClanUrl", "www.ravensoft.com", CVAR_GAME | CVAR_USERINFO, "The url of the live clan being created\n"); +// +//idCVar ui_LiveRecruitName( "ui_LiveRecruitName", "Recruit Name Here", CVAR_GAME | CVAR_USERINFO, "name of the gamer you are trying recruit\n"); +//idCVar ui_LiveRecruitPDelete( "ui_LiveRecruitPDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit delete permissions\n"); +//idCVar ui_LiveRecruitPData( "ui_LiveRecruitPData", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit modify data permissions\n"); +//idCVar ui_LiveRecruitPMemberPermissions("ui_LiveRecruitPMemberPermissions", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member modify permissions\n"); +//idCVar ui_LiveRecruitPMemberDelete( "ui_LiveRecruitPMemberDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member delete permissions\n"); +//idCVar ui_LiveRecruitPMemberRecruit( "ui_LiveRecruitPMemberRecruit", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member recruit permissions\n"); +#endif + +idCVar pm_zoomedSlow( "pm_zoomedSlow", "100", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "Slow look speed while zoomed 0..100% of speed"); + +#ifndef _XENON +idCVar pm_isZoomed( "pm_isZoomed", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "if nonzero, is the slow speed"); +#endif + +// nmckenzie: added ability to try alternate accelerations. +idCVar pm_acceloverride( "pm_acceloverride", "0", CVAR_GAME | CVAR_FLOAT, "Adjust the player acceleration." ); +idCVar pm_frictionoverride( "pm_frictionoverride", "-1", CVAR_GAME | CVAR_FLOAT, "Adjust the player friciton." ); +idCVar pm_forcespectatormove( "pm_forcespectatormove", "0", CVAR_GAME | CVAR_FLOAT, "Force the player to move like a spectator (fly)." ); +// bdube: added vehicle cvars +idCVar pm_vehicleCameraSnap( "pm_vehicleCameraSnap", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraMinDist( "pm_vehicleCameraMinDist", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraSpeedScale( "pm_vehicleCameraSpeedScale", "0.5", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraScaleMax( "pm_vehicleCameraScaleMax", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleSoundLerpScale( "pm_vehicleSoundLerpScale", "10", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +// RAVEN END + +idCVar g_showPlayerShadow( "g_showPlayerShadow", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables shadow of player model" ); + +idCVar g_skipPlayerShadowsMP( "g_skipPlayerShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all player shadows in multiplayer" ); +idCVar g_skipItemShadowsMP( "g_skipItemShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all item shadows in multiplayer" ); +idCVar g_simpleItems( "g_simpleItems", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "render icon representations of items instead of the actual model" ); +idCVar g_showHud( "g_showHud", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar g_showProjectilePct( "g_showProjectilePct", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables display of player hit percentage" ); +// RAVEN BEGIN +// dluetscher: changed to g_brassTime +idCVar g_brassTime( "g_brassTime", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "amount of time brass should stay in the world before dissapearing, set to 0 to disable brass" ); +// RAVEN END +idCVar g_gun_x( "g_gunX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalX( "g_viewNodalX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalZ( "g_viewNodalZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN BEGIN +// jshepard: fov as a float for smoother transitions? +idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +// RAVEN END +idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); +idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); + +// RAVEN BEGIN +// bdube: crosshairs +// mekberg: custom size +idCVar g_crosshairSize( "g_crosshairSize", "32", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "crosshair size: 16,24,32,40,48", 16, 48 ); +//idCVar g_crosshairColor( "g_crosshairColor", "0.458 0.894 0.247 .75", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +idCVar g_crosshairColor( "g_crosshairColor", "1 1 1 1", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +// cnicholson: Custom crosshair +idCVar g_crosshairCustom( "g_crosshairCustom", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "sets the custom combat crosshair" ); +idCVar g_crosshairCustomFile( "g_crosshairCustomFile", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "stores the custom crosshair's filename" ); +idCVar g_crosshairCharInfoFar( "g_crosshairCharInfoFar", "1", CVAR_GAME | CVAR_BOOL, "instead of a green crosshair from far away, full character info always draws" ); +// bdube: database entries +idCVar g_showHudPopups( "g_showHudPopups", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "displays objective and database popups on the hud" ); +idCVar g_showRange( "g_showRange", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "shows the range from the player to the first collision under the players crosshair" ); +// bdube: debug hud +idCVar g_showDebugHud( "g_showDebugHud", "0", CVAR_GAME | CVAR_INTEGER, "displays the debug hud\n" + "0 = off\n" + "1 = player\n" + "2 = physics\n" + "3 = AI\n" + "4 = vehicle\n" + "5 = performance\n" + "6 = effects\n" + "7 = map information\n" + "8 = AI performance\n" + "9 = MP\n" + "10 = Sound\n" + "32 = scratch\n" ); +// bdube: cvar for messing with foreshortening and gun position +idCVar g_gun_pitch( "g_gunPitch", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_yaw( "g_gunYaw", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_roll( "g_gunRoll", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// abahr: +idCVar g_gunViewStyle( "g_gunViewStyle", "0", CVAR_GAME | CVAR_NOCHEAT | PC_CVAR_ARCHIVE | CVAR_INTEGER, "style presets\n" + "0 = Q3 style\n" + "1 = Shouldered style\n"); +// jscott: cvar for debugging playbacks +idCVar g_showPlayback( "g_showPlayback", "0", CVAR_GAME | CVAR_INTEGER, "show g_currentPlayback" ); +idCVar g_currentPlayback( "g_currentPlayback", "", CVAR_GAME, "name of playback shown by g_showPlayback" ); +// jscott: unused +//idCVar g_testParticle( "g_testParticle", "0", CVAR_GAME | CVAR_INTEGER, "test particle visualation, set by the particle editor" ); +//idCVar g_testParticleName( "g_testParticleName", "", CVAR_GAME, "name of the particle being tested by the particle editor" ); +// RAVEN END +idCVar g_testModelRotate( "g_testModelRotate", "0", CVAR_GAME, "test model rotation speed" ); +idCVar g_testPostProcess( "g_testPostProcess", "", CVAR_GAME, "name of material to draw over screen" ); +idCVar g_testModelAnimate( "g_testModelAnimate", "0", CVAR_GAME | CVAR_INTEGER, "test model animation,\n" + "0 = cycle anim with origin reset\n" + "1 = cycle anim with fixed origin\n" + "2 = cycle anim with continuous origin\n" + "3 = frame by frame with continuous origin\n" + "4 = play anim once\n" + "5 = frame by frame with fixed origin", 0, 5, idCmdSystem::ArgCompletion_Integer<0,5> ); +idCVar g_testModelBlend( "g_testModelBlend", "0", CVAR_GAME | CVAR_INTEGER, "number of frames to blend" ); +idCVar g_testDeath( "g_testDeath", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// bdube: added scoreboard testing +idCVar g_testScoreboard( "g_testScoreboard", "0", CVAR_GAME | CVAR_INTEGER, "number of clients to test in the scoreboard gui" ); +idCVar g_testPlayer( "g_testPlayer", "", CVAR_GAME, "test player classname" ); +// RAVEN END +idCVar g_exportMask( "g_exportMask", "", CVAR_GAME, "" ); +idCVar g_flushSave( "g_flushSave", "0", CVAR_GAME | CVAR_BOOL, "1 = don't buffer file writing for save games." ); + +idCVar aas_test( "aas_test", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreas( "aas_showAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreaBounds( "aas_showAreaBounds", "0", CVAR_GAME | CVAR_INTEGER, "When show areas is on, this draws the bounds of the areas, too..." ); +idCVar aas_showPath( "aas_showPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showFlyPath( "aas_showFlyPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showWallEdges( "aas_showWallEdges", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showHideArea( "aas_showHideArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_pullPlayer( "aas_pullPlayer", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_randomPullPlayer( "aas_randomPullPlayer", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_goalArea( "aas_goalArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showPushIntoArea( "aas_showPushIntoArea", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// rjohnson: added aas help +idCVar aas_showProblemAreas( "aas_showProblemAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// cdr: added rev reach +idCVar aas_showRevReach( "aas_showRevReach", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +idCVar g_password( "g_password", "", CVAR_GAME | PC_CVAR_ARCHIVE, "game password" ); +idCVar password( "password", "", CVAR_GAME | CVAR_NOCHEAT, "client password used when connecting" ); + +// RAVEN BEGIN +idCVar g_gameReviewPause( "g_gameReviewPause", "30", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | PC_CVAR_ARCHIVE, "scores review time in seconds (at end game)", 2, 3600 ); +// RAVEN END +idCVar net_clientPredictGUI( "net_clientPredictGUI", "1", CVAR_GAME | CVAR_BOOL, "test guis in networking without prediction" ); + +idCVar si_voteFlags( "si_voteFlags", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER | PC_CVAR_ARCHIVE, "vote flags. bit mask of votes not allowed on this server\n" + "bit 0 (+1) restart now\n" + "bit 1 (+2) min players\n" + "bit 2 (+4) auto balance teams\n" + "bit 3 (+8) shuffle teams\n" + "bit 4 (+16) kick player\n" + "bit 5 (+32) change map\n" + "bit 6 (+64) change gametype\n" + "bit 7 (+128) time limit\n" + "bit 8 (+256) tourney limit\n" + "bit 9 (+512) capture limit\n" + "bit 10 (+1024) frag limit" ); + +idCVar g_mapCycle( "g_mapCycle", "mapcycle", CVAR_GAME | CVAR_ARCHIVE, "map cycling script for multiplayer games - see mapcycle.scriptcfg" ); + +// RAVEN BEGIN +// bdube: client entitiy cvars +idCVar g_gamelog( "g_gamelog", "0", CVAR_GAME | CVAR_BOOL, "enables game logging" ); +idCVar cl_showEntityInfo( "cl_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +// ddynerman: announcer delay time +idCVar g_announcerDelay( "g_announcerDelay", "1000", CVAR_SOUND | PC_CVAR_ARCHIVE, "no more than one announcer sound will be played in this many ms" ); +// jnewquist: Option to force undying state +idCVar g_forceUndying( "g_forceUndying", "0", CVAR_GAME | CVAR_BOOL, "forces undying state" ); +// mcg: combat performance testing cvars +idCVar g_perfTest_weaponNoFX( "g_perfTest_weaponNoFX", "0", CVAR_GAME | CVAR_BOOL, "no muzzle flash, brass eject, muzzle fx, tracers, impact fx, blood decals or blood splats (whew!)" ); +idCVar g_perfTest_hitscanShort( "g_perfTest_hitscanShort", "0", CVAR_GAME | CVAR_BOOL, "all hitscans capped at 2048" ); +idCVar g_perfTest_hitscanBBox( "g_perfTest_hitscanBBox", "0", CVAR_GAME | CVAR_BOOL, "all hitscans vs bbox, not rendermodel" ); +idCVar g_perfTest_aiStationary( "g_perfTest_aiStationary", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no combat movement" ); +idCVar g_perfTest_aiNoDodge( "g_perfTest_aiNoDodge", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no dodging" ); +idCVar g_perfTest_aiNoRagdoll( "g_perfTest_aiNoRagdoll", "0", CVAR_GAME | CVAR_BOOL, "ai does not ragdoll" ); +idCVar g_perfTest_aiNoObstacleAvoid( "g_perfTest_aiNoObstacleAvoid", "0", CVAR_GAME | CVAR_BOOL, "ai does not attempt obstacle avoidance" ); +idCVar g_perfTest_aiUndying( "g_perfTest_aiUndying", "0", CVAR_GAME | CVAR_BOOL, "makes all AI undying" ); +idCVar g_perfTest_aiNoVisTrace( "g_perfTest_aiNoVisTrace", "0", CVAR_GAME | CVAR_BOOL, "ai does no vis traces" ); +idCVar g_perfTest_noJointTransform( "g_perfTest_noJointTransform", "0", CVAR_GAME | CVAR_BOOL, "all joint transforms return origin" ); +idCVar g_perfTest_noPlayerFocus( "g_perfTest_noPlayerFocus", "0", CVAR_GAME | CVAR_BOOL, "doesn't do player focus traces/logic" ); +idCVar g_perfTest_noProjectiles( "g_perfTest_noProjectiles", "0", CVAR_GAME | CVAR_BOOL, "all projectiles are removed instantly" ); + +idCVar g_clientProjectileCollision( "g_clientProjectileCollision", "1", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT, "allow the client to predict collisions" ); + +idCVar net_serverDownload( "net_serverDownload", "0", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "enable server download redirects. 0: off 1: client exits and opens si_serverURL in web browser 2: client downloads pak files from an URL and connects again. See net_serverDl* cvars for configuration" ); +idCVar net_serverDlBaseURL( "net_serverDlBaseURL", "", CVAR_GAME | CVAR_ARCHIVE, "base URL for the download redirection" ); +idCVar net_serverDlTable( "net_serverDlTable", "", CVAR_GAME | CVAR_ARCHIVE, "pak names for which download is provided, seperated by ; - use a * to mark all paks" ); + +idCVar si_serverURL( "si_serverURL", "", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "server information page" ); + + +idCVar net_warnStale( "net_warnStale", "1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "Warn stale entity occurences on network client - == 1: only on ClientStale call, > 1 all times" ); + +// RAVEN BEGIN +// bdube: cvar helps +static idCVarHelp help_g_showHud ( "g_showHud", "Show Player HUD", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showGun ( "g_showGun", "Show Player Weapon", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTargets ( "g_showTargets", "Show Targets", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTriggers ( "g_showTriggers", "Show Triggers", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showEntityInfo ( "g_showEntityInfo", "Show Entity Information", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_timeentities ( "g_timeentities", "Show Entity Times", "Off;0.5s;1.0s", "0;0.5;1.0", CVARHELP_GAME ); +static idCVarHelp help_g_showActiveEntities ( "g_showActiveEntities", "Show Active Entities", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_frametime ( "g_frametime", "Show Game Frame Times", "Off;On", "0;1", CVARHELP_GAME ); + +static idCVarHelp help_g_showCollisionWorld ( "g_showCollisionWorld", "Show Collision World", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionModels ( "g_showCollisionModels", "Show Collision Models", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionTraces ( "g_showCollisionTraces", "Show Collision Traces", "Off;Info;Lines", "0;1;2", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showActive ( "rb_showActive", "Show Active Rigid Bodies", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showTimings ( "rb_showTimings", "Show Rigid Body Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_af_showTimings ( "af_showTimings", "Show AF Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); + +// nmckenzie: ai cvar helps +static idCVarHelp help_g_aas_showAreas( "aas_showAreas", "Show AAS areas", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showProblemAreas( "aas_showProblemAreas", "Show AAS areas with Problems", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showPath( "aas_showPath", "Show AAS Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showFlyPath( "aas_showFlyPath", "Show AAS Flying Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showWallEdges( "aas_showWallEdges", "Show AAS Wall Edges", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showHideArea( "aas_showHideArea", "Show AAS Hide Areas", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_goalArea( "aas_goalArea", "Show AAS Goal Areas", "Off;On", "0;1", CVARHELP_AI ); + +static idCVarHelp help_g_ai_debugMove( "ai_debugMove", "Show Movement for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_debugTrajectory( "ai_debugTrajectory", "Show Grenade tests for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showCombatNodes( "ai_showCombatNodes", "Show attack cones for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showPaths( "ai_showPaths", "Show all path_* entities", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "Show obstacle avoidance", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_speeds( "ai_speeds", "Show performance load of AI", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_animShow( "ai_animShow", "List animations when used.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showTacticalFeatures( "ai_showTacticalFeatures", "Show player view tactical features.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_useRVMasterMove( "ai_useRVMasterMove", "Use new master move functions.", "Off;On", "0;1", CVARHELP_AI ); +// RAVEN END diff --git a/source/game/gamesys/SysCvar.h b/source/game/gamesys/SysCvar.h new file mode 100644 index 0000000..0ad8da7 --- /dev/null +++ b/source/game/gamesys/SysCvar.h @@ -0,0 +1,425 @@ + +#ifndef __SYS_CVAR_H__ +#define __SYS_CVAR_H__ + +extern idCVar developer; + +extern idCVar g_cinematic; +extern idCVar g_cinematicMaxSkipTime; + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +extern idCVar g_fixedHorizFOV; +// RAVEN END + +extern idCVar g_monsters; +extern idCVar g_decals; +extern idCVar g_knockback; +extern idCVar g_skill; +extern idCVar g_gravity; +extern idCVar g_mp_gravity; +extern idCVar g_skipFX; +extern idCVar g_skipParticles; +extern idCVar g_projectileLights; +extern idCVar g_doubleVision; +extern idCVar g_muzzleFlash; + +extern idCVar g_disasm; +extern idCVar g_debugBounds; +extern idCVar g_debugAnim; +extern idCVar g_debugMove; +extern idCVar g_debugDamage; +extern idCVar g_debugWeapon; +extern idCVar g_debugScript; +extern idCVar g_debugMover; +extern idCVar g_debugTriggers; +extern idCVar g_debugCinematic; +// RAVEN BEGIN +// bdube: added +extern idCVar g_debugState; +extern idCVar g_stopTime; +extern idCVar g_armorProtection; +extern idCVar g_armorProtectionMP; +//extern idCVar g_damageScale; +// jsinger: added to support binary read/write +extern idCVar com_BinaryRead; +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFile; +#endif +extern idCVar com_BinaryWrite; +// RAVEN END +extern idCVar g_useDynamicProtection; +extern idCVar g_healthTakeTime; +extern idCVar g_healthTakeAmt; +extern idCVar g_healthTakeLimit; + +extern idCVar g_showPVS; +extern idCVar g_showTargets; +extern idCVar g_showTriggers; +extern idCVar g_showCollisionWorld; +extern idCVar g_showCollisionModels; +extern idCVar g_showCollisionTraces; +// RAVEN BEGIN +// ddynerman: SD's clip sector code +extern idCVar g_showClipSectors; +extern idCVar g_showClipSectorFilter; +extern idCVar g_showAreaClipSectors; +// RAVEN END +extern idCVar g_maxShowDistance; +extern idCVar g_showEntityInfo; +extern idCVar g_showviewpos; +extern idCVar g_showcamerainfo; +extern idCVar g_showTestModelFrame; +extern idCVar g_showActiveEntities; +extern idCVar g_showEnemies; +extern idCVar g_frametime; +extern idCVar g_timeentities; + +// RAVEN BEGIN +// bdube: new debug cvar +extern idCVar g_debugVehicle; +extern idCVar g_showFrameCmds; +extern idCVar g_showGodDamage; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: debug cvars for rvVehicleDriver +extern idCVar g_debugVehicleDriver; +extern idCVar g_debugVehicleAI; +extern idCVar g_vehicleMode; +// RAVEN END +extern idCVar g_allowVehicleGunOverheat; + +extern idCVar ai_debugScript; +extern idCVar ai_debugMove; +extern idCVar ai_debugTrajectory; +extern idCVar ai_debugTactical; +extern idCVar ai_debugFilterString; +extern idCVar ai_testPredictPath; +extern idCVar ai_showCombatNodes; +extern idCVar ai_showPaths; +extern idCVar ai_showObstacleAvoidance; +extern idCVar ai_blockedFailSafe; +extern idCVar ai_debugSquad; +extern idCVar ai_debugStealth; +extern idCVar ai_allowTacticalRush; + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +extern idCVar ai_speeds; +extern idCVar ai_freeze; +extern idCVar ai_animShow; +extern idCVar ai_showCover; +extern idCVar ai_showTacticalFeatures; +extern idCVar ai_disableEntTactical; +extern idCVar ai_disableAttacks; +extern idCVar ai_disableSimpleThink; +extern idCVar ai_disableCover; +extern idCVar ai_debugHelpers; +// cdr: added new master move type +extern idCVar ai_useRVMasterMove; +//jshepard: allow old AAS files +extern idCVar ai_allowOldAAS; +// twhitaker: debugging support for eye focus +extern idCVar ai_debugEyeFocus; +//mcg: always allow player to push buddies, unless scripted +extern idCVar ai_playerPushAlways; +// RAVEN END + +extern idCVar g_dvTime; +extern idCVar g_dvAmplitude; +extern idCVar g_dvFrequency; + +extern idCVar g_kickTime; +extern idCVar g_kickAmplitude; +extern idCVar g_blobTime; +extern idCVar g_blobSize; + +extern idCVar g_testHealthVision; +extern idCVar g_editEntityMode; +// RAVEN BEGIN +extern idCVar g_editEntityDistance; +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. +extern idCVar g_editEntityTextDistance; +// rjohnson: entity usage stats +extern idCVar g_keepEntityStats; +// RAVEN END +extern idCVar g_dragEntity; +extern idCVar g_dragDamping; +extern idCVar g_dragShowSelection; +extern idCVar g_dropItemRotation; + +extern idCVar g_vehicleVelocity; +extern idCVar g_vehicleForce; + +extern idCVar ik_enable; +extern idCVar ik_debug; + +extern idCVar af_useLinearTime; +extern idCVar af_useImpulseFriction; +extern idCVar af_useJointImpulseFriction; +extern idCVar af_useSymmetry; +extern idCVar af_skipSelfCollision; +extern idCVar af_skipLimits; +extern idCVar af_skipFriction; +extern idCVar af_forceFriction; +extern idCVar af_maxLinearVelocity; +extern idCVar af_maxAngularVelocity; +extern idCVar af_timeScale; +extern idCVar af_jointFrictionScale; +extern idCVar af_contactFrictionScale; +extern idCVar af_highlightBody; +extern idCVar af_highlightConstraint; +extern idCVar af_showTimings; +extern idCVar af_showConstraints; +extern idCVar af_showConstraintNames; +extern idCVar af_showConstrainedBodies; +extern idCVar af_showPrimaryOnly; +extern idCVar af_showTrees; +extern idCVar af_showLimits; +extern idCVar af_showBodies; +extern idCVar af_showBodyNames; +extern idCVar af_showMass; +extern idCVar af_showTotalMass; +extern idCVar af_showInertia; +extern idCVar af_showVelocity; +extern idCVar af_showActive; +extern idCVar af_testSolid; + +extern idCVar rb_showTimings; +extern idCVar rb_showBodies; +extern idCVar rb_showMass; +extern idCVar rb_showInertia; +extern idCVar rb_showVelocity; +extern idCVar rb_showActive; + +extern idCVar pm_jumpheight; +extern idCVar pm_stepsize; +extern idCVar pm_crouchspeed; +// RAVEN BEGIN +extern idCVar pm_speed; +extern idCVar pm_walkspeed; +extern idCVar pm_zoomedSlow; +extern idCVar pm_isZoomed; +// RAVEN END +extern idCVar pm_noclipspeed; +extern idCVar pm_spectatespeed; +extern idCVar pm_spectatebbox; +extern idCVar pm_usecylinder; +extern idCVar pm_minviewpitch; +extern idCVar pm_maxviewpitch; +extern idCVar pm_stamina; +extern idCVar pm_staminathreshold; +extern idCVar pm_staminarate; +extern idCVar pm_crouchheight; +extern idCVar pm_crouchviewheight; +extern idCVar pm_normalheight; +extern idCVar pm_normalviewheight; +extern idCVar pm_deadheight; +extern idCVar pm_deadviewheight; +extern idCVar pm_crouchrate; +extern idCVar pm_bboxwidth; +extern idCVar pm_crouchbob; +extern idCVar pm_walkbob; +extern idCVar pm_runbob; +extern idCVar pm_runpitch; +extern idCVar pm_runroll; +extern idCVar pm_bobup; +extern idCVar pm_bobpitch; +extern idCVar pm_bobroll; +extern idCVar pm_thirdPersonRange; +extern idCVar pm_thirdPersonHeight; +extern idCVar pm_thirdPersonAngle; +extern idCVar pm_thirdPersonClip; +extern idCVar pm_thirdPerson; +extern idCVar pm_thirdPersonDeath; +extern idCVar pm_modelView; +extern idCVar pm_airTics; + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon +#ifdef _XBOX +extern idCVar pm_AimAssist; +extern idCVar pm_AimAssistDistance; +extern idCVar pm_AimAssistThreshold; +extern idCVar pm_AimAssistFOV; +extern idCVar pm_AimAssistBump; +extern idCVar pm_AimAssistShow; +extern idCVar pm_AimAssistSlow; + +extern idCVar pm_ThumbstickConfig; +extern idCVar pm_ButtonConfig; + +extern idCVar pm_RocketJumpAutocenter; +extern idCVar pm_IAmACheater; + + +#endif +// nmckenzie: added ability to try alternate accelerations. +extern idCVar pm_acceloverride; +extern idCVar pm_frictionoverride; +extern idCVar pm_forcespectatormove; +extern idCVar pm_thirdPersonTarget; +// bdube: vehicle +extern idCVar pm_vehicleLean; +extern idCVar pm_vehicleCameraSnap; +extern idCVar pm_vehicleCameraScaleMax; +extern idCVar pm_vehicleSoundLerpScale; +extern idCVar pm_vehicleCameraSpeedScale; +extern idCVar pm_vehicleCameraMinDist; +// RAVEN END + +extern idCVar g_showPlayerShadow; + +extern idCVar g_skipPlayerShadowsMP; +extern idCVar g_skipItemShadowsMP; + +extern idCVar g_simpleItems; +extern idCVar g_showHud; +// RAVEN BEGIN +extern idCVar g_crosshairColor; +// cnicholson: Custom Crosshair +extern idCVar g_crosshairCustom; +extern idCVar g_crosshairCustomFile; +extern idCVar g_crosshairCharInfoFar; +// bdube: hud popups +extern idCVar g_showHudPopups; +// bdube: range +extern idCVar g_showRange; +// bdube: debug hud +extern idCVar g_showDebugHud; +// RAVEN END +extern idCVar g_showProjectilePct; +// RAVEN BEGIN +// bdube: brass time +extern idCVar g_brassTime; +// RAVEN END +extern idCVar g_gun_x; +extern idCVar g_gun_y; +extern idCVar g_gun_z; +// RAVEN BEGIN +// bdube: cvar for messing with foreshortening +extern idCVar g_gun_pitch; +extern idCVar g_gun_yaw; +extern idCVar g_gun_roll; +// abahr: +extern idCVar g_gunViewStyle; +// jscott: for playbacks +extern idCVar g_showPlayback; +extern idCVar g_currentPlayback; +// RAVEN END +extern idCVar g_viewNodalX; +extern idCVar g_viewNodalZ; +extern idCVar g_fov; +extern idCVar g_testDeath; +extern idCVar g_skipViewEffects; +extern idCVar g_mpWeaponAngleScale; + +extern idCVar g_testParticle; +extern idCVar g_testParticleName; +// RAVEN BEGIN +// bdube: more rigid body debug +extern idCVar rb_showContacts; +// RAVEN END + +extern idCVar g_testPostProcess; + +extern idCVar g_testModelRotate; +extern idCVar g_testModelAnimate; +extern idCVar g_testModelBlend; + +extern idCVar g_forceModel; +extern idCVar g_forceStroggModel; +extern idCVar g_forceMarineModel; + +// RAVEN BEGIN +// bdube: test scoreboard +extern idCVar g_testScoreboard; +extern idCVar g_testPlayer; +// RAVEN END +extern idCVar g_exportMask; +extern idCVar g_flushSave; + +extern idCVar aas_test; +extern idCVar aas_showAreas; +extern idCVar aas_showAreaBounds; +extern idCVar aas_showPath; +extern idCVar aas_showFlyPath; +extern idCVar aas_showWallEdges; +extern idCVar aas_showHideArea; +extern idCVar aas_pullPlayer; +extern idCVar aas_randomPullPlayer; +extern idCVar aas_goalArea; +extern idCVar aas_showPushIntoArea; +// RAVEN BEGIN +// rjohnson: added aas help +extern idCVar aas_showProblemAreas; +// cdr: added rev reach +extern idCVar aas_showRevReach; +// RAVEN END + +extern idCVar net_clientPredictGUI; + +extern idCVar si_voteFlags; +extern idCVar g_mapCycle; +// RAVEN BEGIN +// shouchard: g_balanceTDM->g_balanceTeams so we can also use it for CTF +extern idCVar si_autobalance; +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +extern idCVar si_isBuyingEnabled; +extern idCVar si_dropWeaponsInBuyingModes; +extern idCVar si_controlTime; +// RITUAL END + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_gameType; +extern idCVar si_map; +extern idCVar si_mapCycle; +extern idCVar si_spectators; +extern idCVar si_minPlayers; +// RAVEN BEGIN +// shouchard: CTF +extern idCVar si_captureLimit; +// shouchard: Tourney +extern idCVar si_tourneyLimit; +// RAVEN END + +extern const char *si_gameTypeArgs[]; + +// RAVEN BEGIN +// bdube: client entities +extern idCVar g_gamelog; +extern idCVar cl_showEntityInfo; +// jnewquist: Option to force undying state +extern idCVar g_forceUndying; +// mcg: combat performance testing cvars +extern idCVar g_perfTest_weaponNoFX; +extern idCVar g_perfTest_hitscanShort; +extern idCVar g_perfTest_hitscanBBox; +extern idCVar g_perfTest_aiStationary; +extern idCVar g_perfTest_aiNoDodge; +extern idCVar g_perfTest_aiNoRagdoll; +extern idCVar g_perfTest_aiNoObstacleAvoid; +extern idCVar g_perfTest_aiUndying; +extern idCVar g_perfTest_aiNoVisTrace; +extern idCVar g_perfTest_noJointTransform; +extern idCVar g_perfTest_noPlayerFocus; +extern idCVar g_perfTest_noProjectiles; +// RAVEN END + +extern idCVar g_clientProjectileCollision; + +extern idCVar net_clientLagOMeter; + +extern idCVar net_warnStale; + +#endif /* !__SYS_CVAR_H__ */ diff --git a/source/game/mp/Buying.cpp b/source/game/mp/Buying.cpp new file mode 100644 index 0000000..fcec627 --- /dev/null +++ b/source/game/mp/Buying.cpp @@ -0,0 +1,68 @@ +//---------------------------------------------------------------- +// Buying.cpp +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.cpp and Player.cpp, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Buying.h" + + +riBuyingManager::riBuyingManager() : + _buyingGameBalanceConstants( NULL ), + opponentKillCashAward( 0 ), + opponentKillFragCount( -1 ) { } + +riBuyingManager::~riBuyingManager() { } + +int riBuyingManager::GetIntValueForKey( const char* keyName, int defaultValue ) { + if( !keyName ) + { + return defaultValue; + } + + if( !_buyingGameBalanceConstants ) + { + _buyingGameBalanceConstants = static_cast( declManager->FindType( DECL_ENTITYDEF, "BuyingGameBalanceConstants", false ) ); + + if( !_buyingGameBalanceConstants ) + { + return defaultValue; + } + } + + for( int i = 0; i < _buyingGameBalanceConstants->dict.GetNumKeyVals(); i++ ) + { + const idKeyValue* keyValuePair = _buyingGameBalanceConstants->dict.GetKeyVal( i ); + if( !keyValuePair->GetKey().Icmp( keyName ) ) + { + return atoi( keyValuePair->GetValue() ); + } + } + + return defaultValue; +} + +int riBuyingManager::GetOpponentKillCashAward( void ) { + int targetFragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + if ( opponentKillFragCount != targetFragCount ) { + opponentKillFragCount = targetFragCount; + if ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) && idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) ) { + // only do frag reward scaling in DM/TDM + opponentKillCashAward = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + } else { + targetFragCount = idMath::ClampInt( GetIntValueForKey( "killingOpponent_minFragAdjust", 10 ), GetIntValueForKey( "killingOpponent_maxFragAdjust",50 ), targetFragCount ); + int baseVal = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + int fragTarget = GetIntValueForKey( "killingOpponent_bestFragCount", 25 ); + opponentKillCashAward = ( baseVal * fragTarget ) / targetFragCount; + } + } + return opponentKillCashAward; +} diff --git a/source/game/mp/Buying.h b/source/game/mp/Buying.h new file mode 100644 index 0000000..66b2e44 --- /dev/null +++ b/source/game/mp/Buying.h @@ -0,0 +1,37 @@ +//---------------------------------------------------------------- +// Buying.h +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.h and Player.h, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#ifndef __BUYING_H__ +#define __BUYING_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + + +class riBuyingManager +{ +private: + const idDeclEntityDef* _buyingGameBalanceConstants; + int opponentKillCashAward; // latch + int opponentKillFragCount; + +public: + riBuyingManager(); + ~riBuyingManager(); + + int GetIntValueForKey( const char* keyName, int defaultValue ); + int GetOpponentKillCashAward( void ); + + void Reset( void ) { opponentKillFragCount = -1; } +}; + + +#endif // __BUYING_H__ diff --git a/source/game/mp/CTF.cpp b/source/game/mp/CTF.cpp new file mode 100644 index 0000000..7887f1f --- /dev/null +++ b/source/game/mp/CTF.cpp @@ -0,0 +1,308 @@ +//---------------------------------------------------------------- +// CTF.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "CTF.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ +CLASS_DECLARATION( idEntity, rvCTF_AssaultPoint ) + EVENT( EV_PostSpawn, rvCTF_AssaultPoint::Event_InitializeLinks ) + EVENT( EV_Touch, rvCTF_AssaultPoint::Event_Touch ) +END_CLASS + +rvCTF_AssaultPoint::rvCTF_AssaultPoint() { + trigger = NULL; + linked = false; + owner = AS_NEUTRAL; +} + +rvCTF_AssaultPoint::~rvCTF_AssaultPoint() { + delete trigger; + trigger = NULL; +} + +/* +================ +rvCTF_AssaultPoint::Spawn +================ +*/ +void rvCTF_AssaultPoint::Spawn( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvCTF_AssaultPoint::InitializeLinks +================ +*/ +void rvCTF_AssaultPoint::Event_InitializeLinks( void ) { + if( linked ) { + return; + } + + // pull in our targets + toStrogg = gameLocal.FindEntity( spawnArgs.GetString( "targetStroggAP" ) ); + toMarine = gameLocal.FindEntity( spawnArgs.GetString( "targetMarineAP" ) ); + + ResetIndices(); + + trigger = new idClipModel( idTraceModel( idBounds( vec3_origin ).Expand( 16.0f ) ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + trigger->SetContents ( CONTENTS_TRIGGER ); + + GetPhysics()->SetClipModel( NULL, 1.0f ); + + linked = true; +} + +/* +================ +rvCTF_AssaultPoint::ResetIndices +================ +*/ +void rvCTF_AssaultPoint::ResetIndices( void ) { + // find the closest AP to the marine flag, which is index 0, then work towards strogg flag + if ( !toMarine || !toMarine->IsType ( rvItemCTFFlag::GetClassType() ) ) { + return; + } + + idEntityPtr ptr; + ptr = this; + gameLocal.mpGame.assaultPoints.Append( ptr ); + int assignIndices = 0; + index = assignIndices++; + rvCTF_AssaultPoint* ap = this; + while( !ap->toStrogg->IsType ( rvItemCTFFlag::GetClassType() ) ) { + if( !ap->toStrogg->IsType ( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - non assault point targeted in assault point chain" ); + } + ap = static_cast(ap->toStrogg.GetEntity()); + ap->index = assignIndices++; + + ptr = ap; + gameLocal.mpGame.assaultPoints.Append( ptr ); + + if ( !ap->linked ) { + ap->Event_InitializeLinks(); + } + + if( !ap->toStrogg ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - break in assault point chain" ); + } + } +} + +/* +================ +rvCTF_AssaultPoint::Event_Activate +================ +*/ +void rvCTF_AssaultPoint::Event_Touch( idEntity *activator, trace_t *trace ) { + if( !activator->IsType( idPlayer::GetClassType() ) || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return; + } + + idPlayer* player = static_cast(activator); + int oldOwner = owner; + + if ( owner == player->team ) + return; + + int enemyPowerup = -1; + int friendlyPowerup = -1; + + if ( owner == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } else if ( owner == TEAM_STROGG ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + // neutral assault point + if ( player->team == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } + } + + if ( !player->PowerUpActive ( enemyPowerup ) ) { + return; + } + + + switch( player->team ) { + case TEAM_MARINE: { + if( !toMarine || owner == TEAM_MARINE ) { + break; + } + + if( toMarine->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } else if( toMarine->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toMarine.GetEntity())->owner == TEAM_MARINE ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } + } + break; + } + case TEAM_STROGG: { + if( !toStrogg || owner == TEAM_STROGG ) { + break; + } + + if( toStrogg->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } else if( toStrogg->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toStrogg.GetEntity())->owner == TEAM_STROGG ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } + } + break; + } + } + + + if( oldOwner != owner ) { + // we switched hands, reset forward spawns + gameLocal.ClearForwardSpawns(); + + if( oldOwner == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( oldOwner ); + } + } else if( oldOwner == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( oldOwner ); + } + } + + rvItemCTFFlag::ResetFlag ( enemyPowerup ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetAPOwner( index, owner ); + + ActivateTargets( this ); + + SetOwnerColor (); + } +} + +/* +================ +rvCTF_AssaultPoint::ResetSpawns +================ +*/ +void rvCTF_AssaultPoint::ResetSpawns( int team ) { + // check to see if team can spawn at me, if not pass down + if( owner == team ) { + ActivateTargets( this ); + } else { + if( team == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( team ); + } + } else if( team == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( team ); + } + } + } +} + +void rvCTF_AssaultPoint::SetOwner ( int newOwner ) { + if ( !gameLocal.isClient ) { + // server should set owner via EVENT_ACTIVATE, not here + return; + } + + owner = newOwner; + SetOwnerColor (); +} + +void rvCTF_AssaultPoint::Reset ( void ) { + owner = AS_NEUTRAL; + SetOwnerColor(); +} + +/* +================ +rvCTF_AssaultPoint::ChangeColor +================ +*/ +void rvCTF_AssaultPoint::SetOwnerColor ( void ) { + const idDeclSkin* skin = NULL; + if( owner == TEAM_MARINE ) { + skin = declManager->FindSkin( "skins/ddynerman/green_glow", false ); + } else if ( owner == TEAM_STROGG ) { + skin = declManager->FindSkin( "skins/ddynerman/orange_glow", false ); + } else { + skin = declManager->FindSkin( "skins/ddynerman/white", false ); + } + + if ( skin ) { + SetSkin( skin ); + } +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ + +CLASS_DECLARATION( idPlayerStart, rvCTFAssaultPlayerStart ) + EVENT( EV_Activate, rvCTFAssaultPlayerStart::Event_Activate ) +END_CLASS + +/* +================ +rvCTFAssaultPlayerStart::Spawn +================ +*/ +void rvCTFAssaultPlayerStart::Spawn(void) { + if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else { + gameLocal.Error("rvCTFAssaultPlayerStart::Spawn() - unknown team\n"); + team = -1; + } +} + +void rvCTFAssaultPlayerStart::Event_Activate( idEntity *activator ) { + if ( !activator->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Warning( "rvCTFAssaultPlayerStart::Event_Activate() - was activated by something other than an assault point\n" ); + return; + } + + rvCTF_AssaultPoint* ap = static_cast(activator); + + if( ap->GetOwner() == team ) { + gameLocal.UpdateForwardSpawns( this, team ); + } +} diff --git a/source/game/mp/CTF.h b/source/game/mp/CTF.h new file mode 100644 index 0000000..b0758b3 --- /dev/null +++ b/source/game/mp/CTF.h @@ -0,0 +1,84 @@ +//---------------------------------------------------------------- +// CTF.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __CTF_H__ +#define __CTF_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ + +class rvCTF_AssaultPoint : public idEntity { +public: + CLASS_PROTOTYPE( rvCTF_AssaultPoint ); + void Spawn( void ); + + rvCTF_AssaultPoint(); + ~rvCTF_AssaultPoint(); + + int GetOwner( void ); + int GetIndex( void ); + + void SetOwnerColor ( void ); + void SetOwner ( int newOwner ); + void Reset ( void ); + +private: + void Event_Touch( idEntity *activator, trace_t *trace ); + void Event_InitializeLinks( void ); + void ResetSpawns( int team ); + + void ResetIndices( void ); + + // these could be maintained as lists to allow multiple AP paths + // the assault point one step closer to the Strogg base + idEntityPtr toStrogg; + // the assault point one step closer to the Marine base + idEntityPtr toMarine; + + // who currently owns this assault point + int owner; + int index; + bool linked; + + idClipModel* trigger; +}; + +ID_INLINE int rvCTF_AssaultPoint::GetOwner( void ) { + return owner; +} + +ID_INLINE int rvCTF_AssaultPoint::GetIndex( void ) { + return index; +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ +class rvCTFAssaultPlayerStart : public idPlayerStart { +public: + CLASS_PROTOTYPE( rvCTFAssaultPlayerStart ); + + void Spawn( void ); + + int GetTeam( void ); +private: + void Event_Activate( idEntity *activator ); + int team; +}; + +#endif diff --git a/source/game/mp/GameState.cpp b/source/game/mp/GameState.cpp new file mode 100644 index 0000000..a569991 --- /dev/null +++ b/source/game/mp/GameState.cpp @@ -0,0 +1,3110 @@ +//---------------------------------------------------------------- +// GameState.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "GameState.h" + +/* +=============================================================================== + +rvGameState + +Game state info for deathmatch, team deathmatch + +=============================================================================== +*/ + +/* +================ +rvGameState::rvGameState +================ +*/ +rvGameState::rvGameState( bool allocPrevious ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvGameState::~rvGameState +================ +*/ +rvGameState::~rvGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +rvGameState::Clear +================ +*/ +void rvGameState::Clear( void ) { + currentState = INACTIVE; + nextState = INACTIVE; + nextStateTime = 0; + fragLimitTimeout = 0; +} + +/* +================ +rvGameState::SendState +================ +*/ +void rvGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious ); + + if( clientNum == -1 && (*this) == (*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvGameState::WriteState +=============== +*/ +void rvGameState::WriteState( idBitMsg &msg ) { + PackState( msg ); +} + +/* +================ +rvGameState::SendInitialState +================ +*/ +void rvGameState::SendInitialState( int clientNum ) { + rvGameState* previousState = previousGameState; + + rvGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvGameState::ReceiveState +================ +*/ +void rvGameState::ReceiveState( const idBitMsg& msg ) { + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (*previousGameState) = (*this); +} + +/* +================ +rvGameState::PackState +================ +*/ +void rvGameState::PackState( idBitMsg& outMsg ) { + // for now, we only transmit 3 bytes. If we need to sync more data, we should + // only transmit the diff + outMsg.WriteByte( currentState ); + outMsg.WriteByte( nextState ); + outMsg.WriteLong( nextStateTime ); +} + +/* +================ +rvGameState::UnpackState +================ +*/ +void rvGameState::UnpackState( const idBitMsg& inMsg ) { + currentState = (mpGameState_t)inMsg.ReadByte(); + nextState = (mpGameState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); +} + +/* +================ +rvGameState::GameStateChanged +================ +*/ +void rvGameState::GameStateChanged( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + // Check for a currentState change + if( currentState != previousGameState->currentState ) { + if( currentState == WARMUP ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld( true ); + + // reset stats on the client-side + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == COUNTDOWN ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld(true); + if( gameLocal.gameType != GAME_TOURNEY && previousGameState->currentState != INACTIVE ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, nextStateTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, nextStateTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, nextStateTime - 1000 ); + } + } else if( currentState == GAMEON ) { + if ( !player->vsMsgState ) { + player->GUIMainNotice( "" ); + player->GUIFragNotice( "" ); + } else { + player->vsMsgState = false; + } + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + } + //if ( gameLocal.gameType == GAME_DEADZONE ) { + // if ( player->team == TEAM_MARINE ) + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time ); + // else + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time ); + //} + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + soundSystem->SetActiveSoundWorld( true ); + + // tourney time announcements are scheduled as you join/leave arenas and at arena starts + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleTimeAnnouncements( ); + } + + + // clear stats on client + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == SUDDENDEATH ) { + soundSystem->SetActiveSoundWorld( true ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } else if( currentState == GAMEREVIEW ) { + // RITUAL BEGIN + gameLocal.mpGame.isBuyingAllowedRightNow = false; + // RITUAL END + gameLocal.mpGame.ShowStatSummary(); + } + } +} + +/* +================ +rvGameState::SpawnDeadZonePowerup +================ +*/ +void rvGameState::SpawnDeadZonePowerup( void ) { + idEntity *ent; + riDeadZonePowerup* spawnSpot = 0; + int count = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::GetClassType() ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != POWERUP_DEADZONE || flag->IsVisible() ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && !flag->IsVisible() ) { + flag->PostEventMS( &EV_Remove, 0 ); + + } else { + count++; + if ( !(rand()%(count)) ) { + spawnSpot = flag; + } + } + } + if ( spawnSpot ) { + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + spawnSpot->srvReady = 1; // Go ahead and set this, so the loop works properly. + } else { + gameLocal.Error("Couldn't find enough dead zone spawn spots for the number of dead zone artifacts specified in the map def!"); + } +} + +/* +================ +rvGameState::Run +================ +*/ +void rvGameState::Run( void ) { + if ( currentState == INACTIVE ) { + +#ifdef _XENON + if(Live()->RoundsPlayed() < cvarSystem->GetCVarInteger("si_matchRounds")) +#endif + { + NewState( WARMUP ); + } + } + + if ( nextState != INACTIVE && gameLocal.time > nextStateTime ) { + NewState( nextState ); + nextState = INACTIVE; + } + + switch( currentState ) { + case INACTIVE: + break; + + case GAMEREVIEW: { + if ( nextState == INACTIVE ) { + + statManager->SendAllStats(); + + nextState = NEXTGAME; + + // allow a little extra time in tourney since we have to display end brackets + if( gameLocal.gameType == GAME_TOURNEY ) { + nextStateTime = gameLocal.time + 5000 + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } else { + nextStateTime = gameLocal.time + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } + } + break; + } + case NEXTGAME: { + + // the core will force a restart at 12 hours max + // but it's nicer if we can wait for a game transition to perform the restart so the game is not interrupted + // perform a restart once we are past 8 hours + if ( networkSystem->ServerGetServerTime() > 8*60*60*1000 ) { + gameLocal.sessionCommand = "nextMap"; + return; + } + + if ( nextState == INACTIVE ) { + // game rotation, new map, gametype etc. + // only cycle in tourney if tourneylimit is higher than the specified value + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)this)->GetTourneyCount() >= gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + // whether we switch to the next map or not, reset the tourney count + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)this)->SetTourneyCount( 0 ); + } + + + if ( gameLocal.NextMap() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart\n" ); + return; + } + } + + NewState( WARMUP ); + + // put everyone back in from endgame spectate + for ( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->wantSpectate ) { + gameLocal.mpGame.CheckRespawns( static_cast( ent ) ); + } + } + } + } + break; + } + case WARMUP: { + // check to see if we actually want to do a warmup, or if we fall through + +//RAVEN BEGIN +//asalmon: Live has its own rules for ending warm up +#ifdef _XENON + if(!Live()->RollCall()) + { + break; + } + +#endif +//RAVEN END + if( !gameLocal.serverInfo.GetBool( "si_warmup" ) && gameLocal.gameType != GAME_TOURNEY ) { + // tourney always needs a warmup, to ensure that at least 2 players get seeded for the tournament. + NewState( GAMEON ); + } else if ( gameLocal.mpGame.AllPlayersReady() ) { + NewState( COUNTDOWN ); + nextState = GAMEON; + nextStateTime = gameLocal.time + 1000 * gameLocal.serverInfo.GetInt( "si_countDown" ); + } + break; + } + case COUNTDOWN: { + break; + } + } +} + +/* +================ +rvGameState::NewState +================ +*/ +void rvGameState::NewState( mpGameState_t newState ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + int i; + + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + // asalmon: start the stat manager as soon as the game starts + statManager->Init(); + statManager->BeginGame(); + + // if shuffle is on, shuffle the teams around + if( gameLocal.IsTeamGame() && gameLocal.serverInfo.GetBool( "si_shuffle" ) ) { + gameLocal.mpGame.ShuffleTeams(); + } + + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( false ); + + //asalmon: clear out lingering team scores. + gameLocal.mpGame.ClearTeamScores(); + + if( gameLocal.gameType != GAME_TOURNEY ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + ((idPlayer*)ent)->JoinInstance( 0 ); + } + } + + break; + } + case GAMEON: { + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart(); +// RITUAL BEGIN +// squirrel: Buying & Deadzone + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + // If the buy menu is up during a server restart, + // make sure to refresh it. + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) + gameLocal.mpGame.deadZonePowerupCount = mapDef->dict.GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iInit(); + statManager->BeginGame(); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + gameLocal.mpGame.SetMatchStartedTime( gameLocal.time ); + + fragLimitTimeout = 0; + + // write server initial reliable messages to give everyone new base + for( i = 0; i < MAX_CLIENTS; i++ ) { + // dont send this to server - we have all the data already and this will + // just trigger extra gamestate detection + if ( gameLocal.entities[ i ] && i != gameLocal.localClientNum ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( i ); + } + } + + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + gameLocal.mpGame.SetPlayerScore( p, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( p, 0 ); + + // in normal gameplay modes, spawn the player in. For tourney, the tourney manager handles spawning + if( gameLocal.gameType != GAME_TOURNEY ) { + p->JoinInstance( 0 ); + // in team games, let UserInfoChanged() spawn people on the right teams + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( static_cast(ent)->wantSpectate ); + } + } + } + + gameLocal.mpGame.ClearTeamScores(); + + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + gameLocal.mpGame.switchThrottle[ 1 ] = 0; // passby the throttle + break; + } + case GAMEREVIEW: { + statManager->EndGame(); + + //statManager->DebugPrint(); + nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } +// RITUAL BEGIN +// squirrel: support for Buying in multiplayer + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + player->forcedReady = false; + player->ServerSpectate( true ); + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); +// RITUAL END + } + break; + } + case SUDDENDEATH: { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_SUDDENDEATH ); + //unmark all leaders, so we make sure we only let the proper people respawn + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + } + + // only restart in team games if si_suddenDeathRestart is set. + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) { + gameLocal.LocalMapRestart(); + } + + // Mark everyone tied for the lead as leaders + i = 0; + idPlayer* leader = gameLocal.mpGame.GetRankedPlayer( i ); + if( leader ) { + int highScore = gameLocal.mpGame.GetScore( leader ); + while( leader ) { + if( gameLocal.mpGame.GetScore( leader ) < highScore ) { + break; + } + leader->SetLeader( true ); + leader = gameLocal.mpGame.GetRankedPlayer( ++i ); + } + } + +// RITUAL BEGIN +// squirrel: Buying & Deadzone + /// Reset players' cash and inventory if si_suddenDeathRestart is set + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) + { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + } + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) + gameLocal.mpGame.deadZonePowerupCount = mapDef->dict.GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iIsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } + if ( static_cast< idPlayer *>( ent )->IsLeader() ) { + static_cast(ent)->ServerSpectate( false ); + continue; + } + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); + } + } + + break; + } + default: { + break; + } + } + + currentState = newState; +} + +/* +================ +rvGameState::ClientDisconnect +================ +*/ +void rvGameState::ClientDisconnect( idPlayer* player ) { + return; +} + +/* +================ +rvGameState::Spectate +================ +*/ +void rvGameState::Spectate( idPlayer* player ) { + if( player->spectating && player->wantSpectate ) { + gameLocal.mpGame.ClearFrags( player->entityNumber ); + } + return; +} + +/* +================ +rvGameState::operator== +================ +*/ +bool rvGameState::operator==( const rvGameState& rhs ) const { + return ( + ( currentState == rhs.currentState ) && + ( nextState == rhs.nextState ) && + ( nextStateTime == rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator!= +================ +*/ +bool rvGameState::operator!=( const rvGameState& rhs ) const { + return ( + ( currentState != rhs.currentState ) || + ( nextState != rhs.nextState ) || + ( nextStateTime != rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator= +================ +*/ +rvGameState& rvGameState::operator=( const rvGameState& rhs ) { + currentState = rhs.currentState; + nextState = rhs.nextState; + nextStateTime = rhs.nextStateTime; + return (*this); +} + +/* +=============== +rvGameState::WriteNetworkInfo +=============== +*/ +void rvGameState::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteState( msg ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); +} + +/* +=============== +rvGameState::ReadNetworkInfo +=============== +*/ +void rvGameState::ReadNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, size ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ReceiveState( msg ); +} + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +rvDMGameState::rvDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvDMGameState::Run( void ) { + idPlayer* player = NULL; + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + player = gameLocal.mpGame.FragLimitHit(); + + bool tiedForFirst = false; + idPlayer* first = gameLocal.mpGame.GetRankedPlayer( 0 ); + idPlayer* second = gameLocal.mpGame.GetRankedPlayer( 1 ); + + if( player == NULL ) { + if( first && second && gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second ) ) { + tiedForFirst = true; + } + } + + if ( player ) { + // delay between detecting frag limit and ending game. let the death anims play + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( first && second && (gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second )) ) { + //this is a tie... + if( gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it must be tied at fraglimit, so sudden death. + NewState( SUDDENDEATH ); + } + } + //otherwise, just keep playing as normal. + fragLimitTimeout = 0; + + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + // check for the rare case that two players both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + break; + } + + case SUDDENDEATH: { + player = gameLocal.mpGame.FragLeader(); + + if ( player ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + + } +} + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +rvTeamDMGameState::rvTeamDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvTeamDMGameState::Run( void ) { + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) <= 0 ) { + // no fraglimit + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( tiedForFirst ) { + //this is a tie + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it's tied at the fraglimit. + NewState( SUDDENDEATH ); + } + //not a tie, game on. + fragLimitTimeout = 0; + } + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +/* +================ +rvCTFGameState::rvCTFGameState +================ +*/ +rvCTFGameState::rvCTFGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvCTFGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvCTFGameState::Clear +================ +*/ +void rvCTFGameState::Clear( void ) { + rvGameState::Clear(); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ].state = FS_AT_BASE; + flagStatus[ i ].clientNum = -1; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = AS_NEUTRAL; + } +} + +/* +================ +rvCTFGameState::SendState +================ +*/ +void rvCTFGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && IsType( rvCTFGameState::GetClassType() ) ); + + if( clientNum == -1 && (rvCTFGameState&)(*this) == (rvCTFGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvCTFGameState::WriteState +=============== +*/ +void rvCTFGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add CTF info + PackState( msg ); +} + +/* +================ +rvCTFGameState::ReceiveState +================ +*/ +void rvCTFGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvCTFGameState&)(*previousGameState) = (rvCTFGameState&)(*this); +} + +/* +================ +rvCTFGameState::PackState +================ +*/ +void rvCTFGameState::PackState( idBitMsg& outMsg ) { + // use indexing to pack in info + int index = 0; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( flagStatus[ i ].state ); + outMsg.WriteByte( flagStatus[ i ].clientNum ); + } + index++; + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != ((rvCTFGameState*)previousGameState)->apState[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( apState[ i ] ); + } + index++; + } +} + +/* +================ +rvCTFGameState::UnpackState +================ +*/ +void rvCTFGameState::UnpackState( const idBitMsg& inMsg ) { + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + if( index >= 0 && index < TEAM_MAX ) { + flagStatus[ index ].state = (flagState_t)inMsg.ReadByte(); + flagStatus[ index ].clientNum = inMsg.ReadByte(); + } else if( index >= TEAM_MAX && index < ( TEAM_MAX + MAX_AP ) ) { + apState[ index - TEAM_MAX ] = (apState_t)inMsg.ReadByte(); + } else { + gameLocal.Error( "rvCTFGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + } +} + +/* +================ +rvCTFGameState::SendInitialState +================ +*/ +void rvCTFGameState::SendInitialState( int clientNum ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvCTFGameState* previousState = (rvCTFGameState*)previousGameState; + + rvCTFGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvCTFGameState::GameStateChanged +================ +*/ +void rvCTFGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // CTF specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvCTFGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + bool noSounds = false; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] == ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + continue; + } + + // don't play flag messages when flag state changes as a result of the gamestate changing + if( currentState != ((rvCTFGameState*)previousGameState)->currentState && ((rvCTFGameState*)previousGameState)->currentState != INACTIVE ) { + continue; + } + + if ( ((rvCTFGameState*)previousGameState)->currentState == INACTIVE ) { + noSounds = true; + } + + // flagTeam - used to tell the HUD which flag to update + int flagTeam = i; + + // in one flag CTF flagTeam is set to whichever team has the flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if( flagStatus[ i ].state == FS_TAKEN_MARINE ) { + flagTeam = TEAM_MARINE; + } else if( flagStatus[ i ].state == FS_TAKEN_STROGG ) { + flagTeam = TEAM_STROGG; + } + } + + if( flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + if ( !player->spectating ) { + if( flagTeam == player->team ) { + // our flag was dropped, so the enemy dropped it + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_DROPS_FLAG, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_DROPS_FLAG, gameLocal.time, -1, true ); + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagDrop" ); + + if ( !player->spectating ) { + if ( flagTeam == player->team ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107723" ) ); + } else { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + } + + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else if( flagStatus[ i ].state == FS_AT_BASE ) { + if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_TAKEN && !noSounds ) { + // team scores + if ( !player->spectating ) { + if( flagTeam == player->team ) { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_ENEMY_SCORES, gameLocal.time ); + } + } else { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_YOU_SCORE, gameLocal.time ); + } + } + } + } else if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + // flag returned + if ( !player->spectating ) { + if( flagTeam == player->team ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_FLAG_RETURNED, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_RETURNS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagReturn" ); + } + } else if( flagStatus[ i ].state == FS_TAKEN || flagStatus[ i ].state == FS_TAKEN_STROGG || flagStatus[ i ].state == FS_TAKEN_MARINE ) { + // flag taken + if( flagTeam == player->team ) { + if ( !player->spectating ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_HAS_FLAG, gameLocal.time, -1, true ); + } + } + + if ( !player->spectating ) { + if ( player->mphud ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107722" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else { + if ( flagStatus[ i ].clientNum == gameLocal.localClientNum ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOU_HAVE_FLAG, gameLocal.time, -1, true ); + } + + if ( !player->spectating ) { + // shouchard: inform the GUI that you've taken the flag + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } else if ( !noSounds ) { + if ( !player->spectating ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_HAS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent ( "flagTaken" ); + } + } + } +} + +/* +================ +rvCTFGameState::Run +================ +*/ +void rvCTFGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_captureLimit" ) <= 0 ) { + // no capture limit games + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter capture limit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // OR the winner lost a point in the frag delay, and there's no tie, so no one wins, game on. + if( tiedForFirst && ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) )) { + NewState( SUDDENDEATH ); + } + fragLimitTimeout = 0; + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +================ +rvCTFGameState::SetFlagState +================ +*/ +void rvCTFGameState::SetFlagState( int flag, flagState_t newState ) { + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].state = newState; +} + +/* +================ +rvCTFGameState::SetFlagCarrier +================ +*/ +void rvCTFGameState::SetFlagCarrier( int flag, int clientNum ) { + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && (clientNum >= 0 && clientNum < MAX_CLIENTS) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].clientNum = clientNum; +} + +/* +================ +rvCTFGameState::operator== +================ +*/ +bool rvCTFGameState::operator==( const rvCTFGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != rhs.flagStatus[ i ] ) { + return false; + } + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != rhs.apState[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvCTFGameState::operator= +================ +*/ +rvCTFGameState& rvCTFGameState::operator=( const rvCTFGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ] = rhs.flagStatus[ i ]; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = rhs.apState[ i ]; + } + + return (*this); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +/* +================ +rvTourneyGameState::rvTourneyGameState +================ +*/ +rvTourneyGameState::rvTourneyGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvTourneyGameState( false ); + } else { + previousGameState = NULL; + } + + packTourneyHistory = false; + + trackPrevious = allocPrevious; +} + +/* +================ +rvTourneyGameState::Clear +================ +*/ +void rvTourneyGameState::Clear( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear(); + } + + rvGameState::Clear(); + + tourneyState = TS_INVALID; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + tourneyCount = 0; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Reset +================ +*/ +void rvTourneyGameState::Reset( void ) { + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Run +================ +*/ +void rvTourneyGameState::Run( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + // check to see if we need to advance to the next round + if( nextState == GAMEREVIEW ) { + return; + } + + bool roundDone = true; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_INACTIVE ) { + continue; + } + + arenas[ i ].UpdateState(); + + if( arenas[ i ].GetState() != AS_DONE ) { + // check to see if we're done + roundDone = false; + } + } + if( roundDone && !roundTimeout ) { + roundTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + if( roundDone && gameLocal.time > roundTimeout ) { + int pastRound = round - 1; //rounds are 1 indexed + idList > advancingPlayers; + + round++; + roundTimeout = 0; + + assert( round >= 2 ); + + // copy over tourney history for the previous round + UpdateTourneyHistory( pastRound ); + + // transmit history + forceTourneyHistory = true; + + // add who will advance to the next round + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() ) { + advancingPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + gameLocal.mpGame.AddPlayerWin( arenas[ i ].GetWinner(), 1 ); + } + } + + // when only one player advances, that player has won the tournament + if( advancingPlayers.Num() == 1 ) { + idPlayer* winner = advancingPlayers[ 0 ].First(); + assert( winner ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear(); + } + + gameLocal.Printf( "Round %d: player %d (%s) has won the tourney!\n", round - 1, winner->entityNumber, gameLocal.userInfo[ winner->entityNumber ].GetString( "ui_name" ) ); + // award 5 wins for winning the entire tournament + gameLocal.mpGame.AddPlayerWin( winner, 5 ); + + nextState = GAMEREVIEW; + nextStateTime = gameLocal.time + 5000; + + return; + } + + // see if we have to swap a player from the byeArena this round + bool swapBye = false; + int thisByeArena = -1; + + if( byeArena >= 0 ) { + // look at the bye arena from the round that just ended + thisByeArena = byeArena / (round - 1); + + // if the bye arena is playing in the next round, there's no need to switch players + if( !(thisByeArena % 2) ) { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena + 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena + 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } else { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena - 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena - 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } + } + + idPlayer* oldByePlayer = NULL; + idPlayer* newByePlayer = NULL; + + if( swapBye ) { + oldByePlayer = arenas[ thisByeArena ].GetWinner(); + + // pick a new bye player only from players who will be fighting next round + // i.e., don't swap the bye player with a player who will have a disconnect bye this upcoming round + idList > nextRoundPlayers; + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() && arenas[ i + 1 ].GetState() == AS_DONE && arenas[ i + 1 ].GetWinner() ) { + nextRoundPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + nextRoundPlayers.Append( rvPair( arenas[ i + 1 ].GetWinner(), i + 1 ) ); + } + } + + if ( nextRoundPlayers.Num() ) { + newByePlayer = nextRoundPlayers[ gameLocal.random.RandomInt( nextRoundPlayers.Num() ) ].First(); + } + } + + // assign arenas for the next round + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + idPlayer* advanceOne = arenas[ i ].GetWinner(); + idPlayer* advanceTwo = arenas[ i + 1 ].GetWinner(); + + // #13266 - bug: bystander from prev round is spectator in his match arena after round change + // we call rvTourneyInstance::Clear when setting up rounds (below), which sets it's players as spectator + // if the former bystander is placed in one of the new arenas and is still referenced in the bye arena then he'll get set spectating + // so just clear him up once identified + // #13631 - we used to call RemovePlayer before entering that loop, but that was messing up the selection of the next bystander + // so have to do it inside the loop now, just before it happens + if ( ( i == thisByeArena || i + 1 == thisByeArena ) && arenas[ thisByeArena ].HasPlayer( oldByePlayer ) ) { + arenas[ thisByeArena ].RemovePlayer( oldByePlayer ); + } + + arenas[ i ].Clear(); + arenas[ i + 1 ].Clear(); + + // assign new arenas, swapping oldByePlayer with newByePlayer + if ( newByePlayer && oldByePlayer ) { + if ( advanceOne == newByePlayer ) { + advanceOne = oldByePlayer; + } else if ( advanceTwo == newByePlayer ) { + advanceTwo = oldByePlayer; + } else if ( advanceOne == oldByePlayer ) { + advanceOne = newByePlayer; + } else if ( advanceTwo == oldByePlayer ) { + advanceTwo = newByePlayer; + } + } + + if ( advanceOne || advanceTwo ) { + arenas[ (i / 2) ].AddPlayers( advanceOne, advanceTwo ); + + if ( advanceOne ) { + advanceOne->JoinInstance( (i / 2) ); + advanceOne->ServerSpectate( false ); + } + + if ( advanceTwo ) { + advanceTwo->JoinInstance( (i / 2) ); + advanceTwo->ServerSpectate( false ); + } + + gameLocal.Printf( "Round %d: Arena %d is starting play with players %d (%s) and %d (%s)\n", round, (i / 2), advanceOne ? advanceOne->entityNumber : -1, advanceOne ? advanceOne->GetUserInfo()->GetString( "ui_name" ) : "NULL", advanceTwo ? advanceTwo->entityNumber : -1, advanceTwo ? advanceTwo->GetUserInfo()->GetString( "ui_name" ) : "NULL" ); + + arenas[ (i / 2) ].Ready(); + } + } + + // once the new round is setup, go through and make sure all the spectators are in valid arena + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + + // re-select our arena since round changed + if( p && p->spectating ) { + rvTourneyArena& thisArena = arenas[ p->GetArena() ]; + if( thisArena.GetState() != AS_WARMUP ) { + p->JoinInstance( GetNextActiveArena( p->GetArena() ) ); + } + } + } + } + + break; + } + } +} + +/* +================ +rvTourneyGameState::NewState +================ +*/ +void rvTourneyGameState::NewState( mpGameState_t newState ) { + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + Reset(); + // force everyone to spectate + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] ) { + ((idPlayer*)gameLocal.entities[ i ])->ServerSpectate( true ); + } + } + break; + } + case GAMEON: { + tourneyCount++; + SetupInitialBrackets(); + break; + } + } + + rvGameState::NewState( newState ); +} + +/* +================ +rvTourneyGameState::GameStateChanged +================ +*/ +void rvTourneyGameState::GameStateChanged( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + rvGameState::GameStateChanged(); + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + int oldRound = ((rvTourneyGameState*)previousGameState)->round; + + if( round != oldRound ) { + if ( round > maxRound && gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->ForceScoreboard( true, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_DONE, gameLocal.time ); + } + + // we're starting a new round + gameLocal.mpGame.tourneyGUI.RoundStart(); + + // skip announce if the round number doesn't make sense + if ( round >= 1 && round <= MAX_ROUNDS ) { + // play the sound a bit after round restart to let spawn sounds settle + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)( AS_TOURNEY_PRELIMS + round - 1 ), gameLocal.time + 1500); + } + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& thisArena = arenas[ i ]; + rvTourneyArena& previousArena = ((rvTourneyGameState*)previousGameState)->arenas[ i ]; + + if( ((thisArena.GetPlayers()[ 0 ] != previousArena.GetPlayers()[ 0 ]) || + (thisArena.GetPlayers()[ 1 ] != previousArena.GetPlayers()[ 1 ]) || + (round != ((rvTourneyGameState*)previousGameState)->round )) /*&& + !((thisArena.GetPlayers()[ 0 ] == NULL && thisArena.GetPlayers()[ 1 ] != NULL) || + (thisArena.GetPlayers()[ 0 ] != NULL && thisArena.GetPlayers()[ 1 ] == NULL) ) */) { + + // don't re-init arenas that have changed and been done for a while + if( thisArena.GetState() != AS_DONE || previousArena.GetState() != AS_DONE ) { + gameLocal.mpGame.tourneyGUI.ArenaStart( i ); + } + + if( localPlayer && thisArena.GetPlayers()[ 0 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_ONE ); + } + if( localPlayer && thisArena.GetPlayers()[ 1 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_TWO ); + } + } + + if( localPlayer->GetArena() == thisArena.GetArenaID() && thisArena.GetState() == AS_SUDDEN_DEATH && thisArena.GetState() != previousArena.GetState() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time, thisArena.GetArenaID() ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } + + if( thisArena.GetState() == AS_DONE && thisArena.GetState() != previousArena.GetState() ) { + if( thisArena.GetWinner() != previousArena.GetWinner() && thisArena.GetWinner() == gameLocal.GetLocalPlayer() ) { + if( round >= maxRound ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_WON, gameLocal.time ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ADVANCE, gameLocal.time ); + } + } else if( thisArena.GetLoser() != previousArena.GetLoser() && thisArena.GetLoser() == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ELIMINATED, gameLocal.time ); + } + + if( previousArena.GetWinner() == NULL && thisArena.GetWinner() != NULL ) { + gameLocal.mpGame.tourneyGUI.ArenaDone( i ); + } + + // on the client, add this result to our tourneyHistory + //if( gameLocal.isClient && thisArena.GetPlayers()[ 0 ] && thisArena.GetPlayers()[ 1 ] && (round - 1 ) >= 0 && (round - 1) < MAX_ROUNDS ) { + // tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 0 ] ); + // tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 1 ] ); + //} + } + + if( localPlayer && (thisArena.GetState() == AS_WARMUP) && (thisArena.GetState() != previousArena.GetState()) && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + int warmupEndTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countdown" ) * 1000 ) + 5000; + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, warmupEndTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, warmupEndTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, warmupEndTime - 1000 ); + + localPlayer->vsMsgState = true; + localPlayer->GUIMainNotice( va( "%s^0 vs. %s^0", thisArena.GetPlayerName( 0 ), thisArena.GetPlayerName( 1 ) ), true ); + } + + if( (thisArena.GetState() == AS_ROUND) && (thisArena.GetState() != previousArena.GetState()) ) { + if( localPlayer && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleTimeAnnouncements(); + } + } + } + + if( ((rvTourneyGameState*)previousGameState)->currentState != currentState ) { + if( currentState == WARMUP ) { + gameLocal.mpGame.tourneyGUI.PreTourney(); + } else if( currentState == COUNTDOWN ) { + if( currentState == COUNTDOWN && ((rvTourneyGameState*)previousGameState)->currentState != INACTIVE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_START, gameLocal.time); + } + } else if( currentState == GAMEON ) { + gameLocal.mpGame.tourneyGUI.TourneyStart(); + } + } + + if( packTourneyHistory ) { + // apply history + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); + packTourneyHistory = false; + } + + if( localPlayer && localPlayer->spectating ) { + + rvTourneyArena& thisArena = arenas[ localPlayer->GetArena() ]; + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL || (localPlayer->spectating && localPlayer->spectator != thisArena.GetPlayers()[ 0 ]->entityNumber && localPlayer->spectator != thisArena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_BRACKET ); + } else if ( thisArena.GetPlayers()[ 0 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 0 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_ONE ); + } else if ( thisArena.GetPlayers()[ 1 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 1 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_TWO ); + } + } + +} + +/* +================ +msgTourney_t +Identifiers for transmitted state +================ +*/ +typedef enum { + MSG_TOURNEY_TOURNEYSTATE, + MSG_TOURNEY_STARTINGROUND, + MSG_TOURNEY_ROUND, + MSG_TOURNEY_MAXROUND, + MSG_TOURNEY_HISTORY, + MSG_TOURNEY_TOURNEYCOUNT, + MSG_TOURNEY_ARENAINFO +} msgTourney_t; + +/* +================ +rvTourneyGameState::PackState +================ +*/ +void rvTourneyGameState::PackState( idBitMsg& outMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( tourneyState != ((rvTourneyGameState*)previousGameState)->tourneyState ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYSTATE ); + outMsg.WriteByte( tourneyState ); + } + + if( startingRound != ((rvTourneyGameState*)previousGameState)->startingRound ) { + outMsg.WriteByte( MSG_TOURNEY_STARTINGROUND ); + outMsg.WriteByte( startingRound ); + } + + if( round != ((rvTourneyGameState*)previousGameState)->round ) { + outMsg.WriteByte( MSG_TOURNEY_ROUND ); + outMsg.WriteByte( round ); + } + + if( maxRound != ((rvTourneyGameState*)previousGameState)->maxRound ) { + outMsg.WriteByte( MSG_TOURNEY_MAXROUND ); + outMsg.WriteByte( maxRound ); + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != ((rvTourneyGameState*)previousGameState)->arenas[ i ] ) { + outMsg.WriteByte( MSG_TOURNEY_ARENAINFO + i ); + arenas[ i ].PackState( outMsg ); + } + } + + if( packTourneyHistory || forceTourneyHistory ) { + if( forceTourneyHistory ) { + common->Warning("forcing down tourney history\n"); + } + // don't write out uninitialized tourney history + if( startingRound > 0 && round > 1 ) { + outMsg.WriteByte( MSG_TOURNEY_HISTORY ); + + // client might not yet have startingRound or round + outMsg.WriteByte( startingRound ); + outMsg.WriteByte( round ); + outMsg.WriteByte( maxRound ); + + for( int i = startingRound - 1; i <= Min( (round - 1), (maxRound - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + outMsg.WriteString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerOneScore ) ); + outMsg.WriteString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerTwoScore ) ); + outMsg.WriteByte( tourneyHistory[ i ][ j ].playerOneNum ); + outMsg.WriteByte( tourneyHistory[ i ][ j ].playerTwoNum ); + } + } + packTourneyHistory = false; + } + } + + if( tourneyCount != ((rvTourneyGameState*)previousGameState)->tourneyCount ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYCOUNT ); + outMsg.WriteByte( tourneyCount ); + } +} + +/* +================ +rvTourneyGameState::UnpackState +================ +*/ +void rvTourneyGameState::UnpackState( const idBitMsg& inMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + switch( index ) { + case MSG_TOURNEY_TOURNEYSTATE: { + tourneyState = (tourneyState_t)inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_STARTINGROUND: { + startingRound = inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_ROUND: { + round = inMsg.ReadByte(); + // not a great way of doing this, should clamp the values + if( round == 255 ) { + round = -1; + } + break; + } + case MSG_TOURNEY_MAXROUND: { + maxRound = inMsg.ReadByte(); + if( maxRound == 255 ) { + maxRound = -1; + } + break; + } + case MSG_TOURNEY_HISTORY: { + int startRound = inMsg.ReadByte(); + int rnd = inMsg.ReadByte(); + int maxr = inMsg.ReadByte(); + + assert( rnd >= 1 ); // something is uninitialized + + for( int i = startRound - 1; i <= Min( (rnd - 1), (maxr - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + inMsg.ReadString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneScore = inMsg.ReadChar(); + inMsg.ReadString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoScore = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerOneNum = inMsg.ReadByte(); + tourneyHistory[ i ][ j ].playerTwoNum = inMsg.ReadByte(); + + if( tourneyHistory[ i ][ j ].playerOneNum < 0 || tourneyHistory[ i ][ j ].playerOneNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerOneNum = -1; + } + + if( tourneyHistory[ i ][ j ].playerTwoNum < 0 || tourneyHistory[ i ][ j ].playerTwoNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // client side (and listen server) no reason to check for history change all the time + packTourneyHistory = true; + break; + } + case MSG_TOURNEY_TOURNEYCOUNT: { + tourneyCount = inMsg.ReadByte(); + break; + } + default: { + if( index >= MSG_TOURNEY_ARENAINFO && index < MSG_TOURNEY_ARENAINFO + MAX_ARENAS ) { + arenas[ index - MSG_TOURNEY_ARENAINFO ].UnpackState( inMsg ); + } else { + gameLocal.Error( "rvTourneyGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + break; + } + } + + } +} + +/* +================ +rvTourneyGameState::SendState +================ +*/ +void rvTourneyGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && IsType( rvTourneyGameState::GetClassType() ) ); + + if ( clientNum == -1 && (rvTourneyGameState&)(*this) == (rvTourneyGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + if( clientNum == -1 && forceTourneyHistory ) { + forceTourneyHistory = false; + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvTourneyGameState::WriteState +=============== +*/ +void rvTourneyGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add Tourney info + PackState( msg ); +} + +/* +================ +rvTourneyGameState::ReceiveState +================ +*/ +void rvTourneyGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvTourneyGameState&)(*previousGameState) = (rvTourneyGameState&)(*this); +} + +/* +================ +rvTourneyGameState::SendInitialState +================ +*/ +void rvTourneyGameState::SendInitialState( int clientNum ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvTourneyGameState* previousState = (rvTourneyGameState*)previousGameState; + + rvTourneyGameState invalidState; + + previousGameState = &invalidState; + + + // if the tourney has been going on, transmit the tourney history + if( round > 0 ) { + // comparing the tourney history for all state changes is wasteful when we really just want to send it to new clients + packTourneyHistory = true; + } + + SendState( clientNum ); + + previousGameState = previousState; +} + + +/* +================ +rvTourneyGameState::operator== +================ +*/ +bool rvTourneyGameState::operator==( const rvTourneyGameState& rhs ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + if( round != rhs.round || startingRound != rhs.startingRound || maxRound != rhs.maxRound || tourneyState != rhs.tourneyState ) { + return false; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != rhs.arenas[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvTourneyGameState::operator= +================ +*/ +rvTourneyGameState& rvTourneyGameState::operator=( const rvTourneyGameState& rhs ) { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + (rvGameState&)(*this) = (rvGameState&)rhs; + + round = rhs.round; + startingRound = rhs.startingRound; + maxRound = rhs.maxRound; + tourneyState = rhs.tourneyState; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ] = rhs.arenas[ i ]; + } + + return (*this); +} + +/* +================ +rvTourneyGameState::GetNumArenas +Returns number of active arenas +================ +*/ +int rvTourneyGameState::GetNumArenas( void ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + int num = 0; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + num++; + } + } + + return num; +} + +/* +================ +rvTourneyGameState::SetupInitialBrackets + +Sets up the brackets for a new match. fragCount in playerstate is the player's +persistant frag count over an entire level. In teamFragCount we store this +rounds score. + +Initial bracket seeds are random +================ +*/ +void rvTourneyGameState::SetupInitialBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + idList unseededPlayers; + int numRankedPlayers = gameLocal.mpGame.GetNumRankedPlayers(); + + // all this crazy math does is figure out: + // 8 arenas to 4 rounds ( round 1: 8 arenas, round 2: 4 arenas, round 3: 2 arenas, round 4: 1 arena ) + // 16 arenas to 5 rounds ( round 1: 16 arenas, round 2: 8 arenas, round 3: 4 arenas, round 4: 2 arenas, round 5: 1 arena ) + // etc + int newMaxRound = idMath::ILog2( Max( idMath::CeilPowerOfTwo( MAX_ARENAS * 2 ), 2 ) ); + + // we start at a round appropriate for our # of people + // If you have 1-2 players, start in maxRound, if you have 3-4 players start in maxRound - 1, if you have 5-8 players + // start in maxRound - 2, if you have 9 - 16 players start in maxRound - 3, etc. + int newRound = newMaxRound - idMath::ILog2( Max( idMath::CeilPowerOfTwo( gameLocal.mpGame.GetNumRankedPlayers() ), 2 ) ) + 1; + + round = newRound; + maxRound = newMaxRound; + startingRound = round; + + for( int i = 0; i < numRankedPlayers; i++ ) { + unseededPlayers.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + int numArenas = 0; + + while ( unseededPlayers.Num() > 1 ) { + idPlayer* playerOne = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerOne ); + + idPlayer* playerTwo = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerTwo ); + + rvTourneyArena& arena = arenas[ numArenas ]; + + arena.Clear(); + arena.AddPlayers( playerOne, playerTwo ); + + // place the players in the correct instance + playerOne->JoinInstance( numArenas ); + playerTwo->JoinInstance( numArenas ); + + playerOne->ServerSpectate( false ); + playerTwo->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( playerOne, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( playerTwo, 0 ); + + gameLocal.Printf( "rvTourneyGameState::SetupInitialBrackets() - %s will face %s in arena %d\n", playerOne->GetUserInfo()->GetString( "ui_name" ), playerTwo->GetUserInfo()->GetString( "ui_name" ), numArenas ); + + // this arena is ready to play + arena.Ready(); + + numArenas++; + } + + assert( unseededPlayers.Num() == 0 || unseededPlayers.Num() == 1 ); + + if( unseededPlayers.Num() ) { + assert( unseededPlayers[ 0 ] ); + // the mid player gets a bye + unseededPlayers[ 0 ]->SetTourneyStatus( PTS_UNKNOWN ); + unseededPlayers[ 0 ]->ServerSpectate( true ); + + arenas[ numArenas ].AddPlayers( unseededPlayers[ 0 ], NULL ); + arenas[ numArenas ].Ready(); + + // mark the ancestor arena of the bye player + byeArena = numArenas * startingRound; + } else { + byeArena = -1; + } +} + +/* +================ +rvTourneyGameState::ClientDisconnect +A player has disconnected from the server +================ +*/ +void rvTourneyGameState::ClientDisconnect( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + if( gameLocal.isClient ) { + return; + } + + // go through the tourney history and copy over the disconnecting player's name + if( startingRound > 0 && round <= MAX_ROUNDS ) { + for( int i = startingRound - 1; i < round - 1; i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + if( tourneyHistory[ i ][ j ].playerOneNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneNum = -1; + } else if( tourneyHistory[ i ][ j ].playerTwoNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // copy over the current rounds info if the player is playing + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetPlayers()[ 0 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( player ); + } else if( arenas[ i ].GetPlayers()[ 1 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( player ); + } + } + + // retransmit tourney history to everyone + packTourneyHistory = true; + } + + RemovePlayer( player ); + + // give RemovePlayer() a chance to update tourney history if needed + if( packTourneyHistory ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if( i == player->entityNumber ) { + continue; + } + + if( gameLocal.entities[ i ] ) { + packTourneyHistory = true; + SendState( i ); + } + } + packTourneyHistory = false; + } +} + +/* +================ +rvTourneyGameState::Spectate +================ +*/ +void rvTourneyGameState::Spectate( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + if( player->spectating && player->wantSpectate ) { + RemovePlayer( player ); + } +} + +/* +================ +rvTourneyGameState::RemovePlayer +Removes the specified player from the arena +================ +*/ +void rvTourneyGameState::RemovePlayer( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( round < 1 || round > maxRound ) { + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + idPlayer** players = GetArenaPlayers( i ); + + if( players[ 0 ] == player || players[ 1 ] == player ) { + rvTourneyArena& arena = arenas[ i ]; + + idPlayer* remainingPlayer = players[ 0 ] == player ? players[ 1 ] : players[ 0 ]; + + bool arenaInProgress = arena.GetState() == AS_ROUND || arena.GetState() == AS_WARMUP || arena.GetState() == AS_SUDDEN_DEATH; + bool remainingIsWinner = (remainingPlayer == arena.GetWinner()); + int remainingIndex = (remainingPlayer == arena.GetPlayers()[ 0 ]) ? 0 : 1; + + arena.RemovePlayer( player ); + + // both players have disconnected from this arena, or the winner has disconnected, just return + if( (!arena.GetPlayers()[ 0 ] && !arena.GetPlayers()[ 1 ]) || (!arenaInProgress && remainingPlayer && !remainingIsWinner) ) { + arena.Clear(); + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + return; + } + + assert( remainingPlayer ); + + // if this arena is in progress, try restarting + // ATVI DevTrack #13196 - do not put the bye player into a game with the abandonned player anymore + // if ( arenaInProgress && byeArena >= 0 && arenas[ byeArena / round ].GetWinner() ) { + if ( 0 ) { + idPlayer* byePlayer = arenas[ byeArena / round ].GetWinner(); + + // reset history for this new round + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + + arena.Clear(); + arenas[ byeArena / round ].Clear(); + + arena.AddPlayers( remainingPlayer, byePlayer ); + + // place the players in the correct instance + remainingPlayer->JoinInstance( i ); + byePlayer->JoinInstance( i ); + + remainingPlayer->ServerSpectate( false ); + byePlayer->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( remainingPlayer, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( byePlayer, 0 ); + + gameLocal.Printf( "rvTourneyManager::RemovePlayer() - %s will face %s in arena %d\n", remainingPlayer->GetUserInfo()->GetString( "ui_name" ), byePlayer->GetUserInfo()->GetString( "ui_name" ), i ); + + byeArena = -1; + // this arena is ready to play + arena.Ready(); + } else { + // if the arena was in progress and didn't get a bye player to step in, this becomes a bye + // arena - clear the tourney history + if( arenaInProgress ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = -1; + + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = -1; + } else { + // since the player is disconnecting, write the history for this round + if( remainingIndex == 0 ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } else { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } + } + } + + return; + } + } +} + +int rvTourneyGameState::GetNextActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena + 1; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = 0; i < arena; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +int rvTourneyGameState::GetPrevActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena - 1; i >= 0; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = MAX_ARENAS - 1; i > arena; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +void rvTourneyGameState::SpectateCycleNext( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = players[ 1 ]->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetNextActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + player->spectator = player->entityNumber; + } + } + + // this is where the listen server updates it's gui spectating elements + if ( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::SpectateCyclePrev( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetPrevActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + + rvTourneyArena& newSpectatingArena = arenas[ player->GetArena() ]; + + idPlayer** newPlayers = newSpectatingArena.GetPlayers(); + + if( !newPlayers[ 0 ] || !newPlayers[ 1 ] || newPlayers[ 0 ]->spectating || newPlayers[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + player->spectator = newPlayers[ 1 ]->entityNumber; + } + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = player->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } + + // this is where the listen server updates it gui spectating elements + if( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::UpdateTourneyBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); +} + +void rvTourneyGameState::UpdateTourneyHistory( int round ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( round >= MAX_ROUNDS ) { + assert( false ); + gameLocal.Error( "rvTourneyGameState::UpdateTourneyHistory: MAX_ROUNDS exceeded" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + // sometimes tourney history might have been updated for the current + // round, so don't clobber any data + + if( (tourneyHistory[ round ][ i ].playerOne[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerOneNum != -1) || + (tourneyHistory[ round ][ i ].playerTwo[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerTwoNum != -1) ) { + continue; + } + + tourneyHistory[ round ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneNum = -1; + tourneyHistory[ round ][ i ].playerOneScore = 0; + + tourneyHistory[ round ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoNum = -1; + tourneyHistory[ round ][ i ].playerTwoScore = 0; + + if( arenas[ i ].GetState() == AS_DONE ) { + idPlayer* playerOne = arenas[ i ].GetPlayers()[ 0 ]; + idPlayer* playerTwo = arenas[ i ].GetPlayers()[ 1 ]; + + if( playerOne ) { + tourneyHistory[ round ][ i ].playerOneNum = playerOne->entityNumber; + tourneyHistory[ round ][ i ].playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( playerOne ); + } + + if( playerTwo ) { + tourneyHistory[ round ][ i ].playerTwoNum = playerTwo->entityNumber; + tourneyHistory[ round ][ i ].playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( playerTwo ); + } + } + } +} + + +/* +================ +rvTourneyGameState::FirstAvailableArena +Returns the first non-WARMUP arena available for use in the next round +================ +*/ +int rvTourneyGameState::FirstAvailableArena( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_WARMUP ) { + return i; + } + } + + // no available arenas + assert( 0 ); + return 0; +} + +arenaOutcome_t* rvTourneyGameState::GetArenaOutcome( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( arenas[ arena ].GetState() != AS_DONE || (arenas[ arena ].GetPlayers()[ 0 ] && arenas[ arena ].GetPlayers()[ 1 ]) ) { + return NULL; + } + + return &(tourneyHistory[ round - 1 ][ arena ]); +} + +bool rvTourneyGameState::HasBye( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( player == NULL || player->GetArena() < 0 || player->GetArena() >= MAX_ARENAS ) { + return false; + } + + // if we're one of the players in the arena we're in, and the other player is NULL, we have a bye + if( (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == player || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == player) && + (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == NULL || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == NULL) ) { + return true; + } + + return false; +} + +// simple RTTI +gameStateType_t rvGameState::type = GS_BASE; +gameStateType_t rvDMGameState::type = GS_DM; +gameStateType_t rvTeamDMGameState::type = GS_TEAMDM; +gameStateType_t rvCTFGameState::type = GS_CTF; +gameStateType_t rvTourneyGameState::type = GS_TOURNEY; + +bool rvGameState::IsType( gameStateType_t type ) const { + return ( type == rvGameState::type ); +} + +bool rvDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvDMGameState::type ); +} + +bool rvTeamDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvTeamDMGameState::type ); +} + +bool rvCTFGameState::IsType( gameStateType_t type ) const { + return ( type == rvCTFGameState::type ); +} + +bool rvTourneyGameState::IsType( gameStateType_t type ) const { + return ( type == rvTourneyGameState::type ); +} + +gameStateType_t rvGameState::GetClassType( void ) { + return rvGameState::type; +} + +gameStateType_t rvDMGameState::GetClassType( void ) { + return rvDMGameState::type; +} + +gameStateType_t rvTeamDMGameState::GetClassType( void ) { + return rvTeamDMGameState::type; +} + +gameStateType_t rvCTFGameState::GetClassType( void ) { + return rvCTFGameState::type; +} + +gameStateType_t rvTourneyGameState::GetClassType( void ) { + return rvTourneyGameState::type; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +/* +================ +riDZGameState::riDZGameState +================ +*/ +riDZGameState::riDZGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new riDZGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; + + type = GS_DZ; +} + +/* +================ +riDZGameState::~riDZGameState +================ +*/ +riDZGameState::~riDZGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +riDZGameState::Clear +================ +*/ +void riDZGameState::Clear( void ) { + rvGameState::Clear(); + + if ( previousGameState ) { + riDZGameState* prevState = (riDZGameState*)previousGameState; + prevState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + + dzTriggerEnt = -1; +} + +/* +================ +riDZGameState::SendState +================ +*/ +void riDZGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && type == GS_DZ ); + + if( clientNum == -1 && (riDZGameState&)(*this) == (riDZGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +riDZGameState::WriteState +=============== +*/ +void riDZGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add DZ info + PackState( msg ); +} + +/* +================ +riDZGameState::ReceiveState +================ +*/ +void riDZGameState::ReceiveState( const idBitMsg& msg ) { + assert( type == GS_DZ ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (riDZGameState&)(*previousGameState) = (riDZGameState&)(*this); +} + +/* +================ +riDZGameState::PackState +================ +*/ +void riDZGameState::PackState( idBitMsg& outMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + outMsg.WriteByte( dzStatus[ i ].state ); + outMsg.WriteBits( dzStatus[ i ].clientNum, -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + + outMsg.WriteLong(dzTriggerEnt); + outMsg.WriteLong(dzShaderParm); +} + +/* +================ +riDZGameState::UnpackState +================ +*/ +void riDZGameState::UnpackState( const idBitMsg& inMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = (dzState_t)inMsg.ReadByte(); + dzStatus[ i ].clientNum = inMsg.ReadBits( -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + dzTriggerEnt = inMsg.ReadLong(); + dzShaderParm = inMsg.ReadLong(); +} + +/* +================ +riDZGameState::SendInitialState +================ +*/ +void riDZGameState::SendInitialState( int clientNum ) { + assert( type == GS_DZ ); + + riDZGameState* previousState = (riDZGameState*)previousGameState; + + riDZGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + + +/* +================ +riDZGameState::ControlZoneStateChanged +================ +*/ +void riDZGameState::ControlZoneStateChanged( int team ) { + if( !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + return; + } + + if ( dzTriggerEnt < 0 ) + return; + + idEntity* ent = gameLocal.FindEntity(dzTriggerEnt); + if ( ent ) { + ent->SetShaderParm(7, dzShaderParm); + } + + dzTriggerEnt = -1; +} + + +/* +================ +riDZGameState::GameStateChanged +================ +*/ +void riDZGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // DZ specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + //gameLocal.Error( "riDZGameState::GameStateChanged() - NULL local player\n" ); + return; + } + + if( currentState == GAMEREVIEW ) + { + // Need to clear the deadzone state at this point. + ((riDZGameState*)previousGameState)->Clear(); + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] == ((riDZGameState*)previousGameState)->dzStatus[ i ] ) { + continue; + } + + ControlZoneStateChanged(i); + } +} + +/* +================ +riDZGameState::Run +================ +*/ +void riDZGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: + { + /// Check if we're frozen (in buy mode, etc.) + if( gameLocal.GetIsFrozen() ) + { + /// Check if it's time for freeze to wear off + int unFreezeTime = gameLocal.GetUnFreezeTime(); + if( gameLocal.time >= unFreezeTime ) + { + gameLocal.SetIsFrozen( false ); + // FIXME: say "fight" + } + } + else + { + /// Check if time limit has been reached + if ( gameLocal.mpGame.TimeLimitHit() ) + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + else + { + /// Teams are tied and time limit was hit - go to sudden death! + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } + } + } + + break; + } + + case SUDDENDEATH: + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + + break; + } + } +} + +/* +================ +riDZGameState::SetDZState +================ +*/ +void riDZGameState::SetDZState( int dz, dzState_t newState ) { + assert( gameLocal.isServer && ( dz >= 0 && dz < TEAM_MAX ) && type == GS_DZ ); + + dzStatus[ dz ].state = newState; +} + +/* +================ +riDZGameState::operator== +================ +*/ +bool riDZGameState::operator==( const riDZGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] != rhs.dzStatus[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +riDZGameState::operator= +================ +*/ +riDZGameState& riDZGameState::operator=( const riDZGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ] = rhs.dzStatus[ i ]; + } + + return (*this); +} + diff --git a/source/game/mp/GameState.h b/source/game/mp/GameState.h new file mode 100644 index 0000000..95b4ed1 --- /dev/null +++ b/source/game/mp/GameState.h @@ -0,0 +1,483 @@ +//---------------------------------------------------------------- +// GameState.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAMESTATE_H__ +#define __GAMESTATE_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" +#include "Tourney.h" + +/* +=============================================================================== + +rvGameState + +Game state info common for all gametypes + +=============================================================================== +*/ + +typedef enum { + GS_BASE, + GS_DM, + GS_TEAMDM, + GS_CTF, + GS_TOURNEY, + GS_DZ +} gameStateType_t; + +typedef enum { + INACTIVE = 0, // not running + WARMUP, // warming up + COUNTDOWN, // post warmup pre-game + GAMEON, // game is on + SUDDENDEATH, // game is on but in sudden death, first frag wins + GAMEREVIEW, // game is over, scoreboard is up. we wait si_gameReviewPause seconds (which has a min value) + NEXTGAME, + STATE_COUNT +} mpGameState_t; + +class rvGameState { +public: + + rvGameState( bool allocPrevious = true ); + virtual ~rvGameState( void ); + + // clientNum == -1 except for SendInitialState + // when clientNum >= 0, always send the state + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + + virtual void Run( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + virtual void Clear( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + + mpGameState_t GetMPGameState( void ) { return currentState; } + + mpGameState_t GetNextMPGameState( void ) { return nextState; } + int GetNextMPGameStateTime( void ) { return nextStateTime; } + void SetNextMPGameState( mpGameState_t newState ) { nextState = newState; } + void SetNextMPGameStateTime( int time ) { nextStateTime = time; } + + bool operator==( const rvGameState& rhs ) const; + bool operator!=( const rvGameState& rhs ) const; + rvGameState& operator=( const rvGameState& rhs ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile *file, int clientNum ); + + void SpawnDeadZonePowerup(); +protected: + static gameStateType_t type; + rvGameState* previousGameState; + bool trackPrevious; + + mpGameState_t currentState; + mpGameState_t nextState; + int nextStateTime; + + int fragLimitTimeout; +}; + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +class rvDMGameState : public rvGameState { +public: + rvDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +class rvTeamDMGameState : public rvGameState { +public: + rvTeamDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +// assault point state for CTF +enum apState_t { + AS_MARINE, + AS_STROGG, + AS_NEUTRAL +}; + +// current flag state for CTF +enum flagState_t { + FS_AT_BASE = 0, + FS_DROPPED, + FS_TAKEN, + // for one flag CTF + FS_TAKEN_STROGG, // taken by strogg team + FS_TAKEN_MARINE // taken by marine team +}; + +struct flagStatus_t { + flagState_t state; + int clientNum; +}; + +class rvCTFGameState : public rvGameState { +public: + rvCTFGameState( bool allocPrevious = true ); + virtual void Clear( void ); + + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetAPOwner( int ap, int owner ); + int GetAPOwner( int ap ); + + void SetFlagState( int flag, flagState_t newState ); + flagState_t GetFlagState( int flag ); + int GetFlagCarrier( int flag ); + + void SetFlagCarrier( int flag, int clientNum ); + + bool operator==( const rvCTFGameState& rhs ) const; + rvCTFGameState& operator=( const rvCTFGameState& rhs ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + flagStatus_t flagStatus[ TEAM_MAX ]; + apState_t apState[ MAX_AP ]; + + static gameStateType_t type; +}; + +ID_INLINE void rvCTFGameState::SetAPOwner( int ap, int owner ) { + assert( (ap >= 0 && ap < MAX_AP) && (owner >= 0 && owner < TEAM_MAX) ); + + apState[ ap ] = (apState_t)owner; +} + +ID_INLINE int rvCTFGameState::GetAPOwner( int ap ) { + return apState[ ap ]; +} + +ID_INLINE flagState_t rvCTFGameState::GetFlagState( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].state; +} + +ID_INLINE int rvCTFGameState::GetFlagCarrier( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].clientNum; +} + +ID_INLINE bool operator==( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +enum tourneyState_t { + TS_INVALID = 0, + TS_PREMATCH, + TS_MATCH, + TS_END_GAME +}; + +class rvTourneyGameState : public rvGameState { +public: + rvTourneyGameState( bool allocPrevious = true ); + + virtual void Clear( void ); + + virtual void Run( void ); + + virtual void Reset( void ); + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + void RemovePlayer( idPlayer* player ); + + bool operator==( const rvTourneyGameState& rhs ) const; + rvTourneyGameState& operator=( const rvTourneyGameState& rhs ); + + int GetNumArenas( void ) const; + bool HasBye( idPlayer* player ); + + int GetMaxRound( void ) const; + int GetRound( void ) const; + int GetStartingRound( void ) const; + + idPlayer** GetArenaPlayers( int arena ); + + rvTourneyArena& GetArena( int arena ); + + const char* GetRoundDescription( void ); + int GetNextActiveArena( int arena ); + int GetPrevActiveArena( int arena ); + + void SpectateCycleNext( idPlayer* player ); + void SpectateCyclePrev( idPlayer* player ); + int GetTourneyCount( void ); + void SetTourneyCount( int count ) { tourneyCount = count; } + + void UpdateTourneyBrackets( void ); + + void UpdateTourneyHistory( int round ); + int FirstAvailableArena( void ); + + arenaOutcome_t* GetArenaOutcome( int arena ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + void SetupInitialBrackets( void ); + + tourneyState_t tourneyState; + + // startingRound - The starting round, with the current # of players + int startingRound; + // round - current round ( 1-indexed ) + int round; + // maxRound - the most rounds we'll run (based on MAX_ARENAS) + int maxRound; + // arenas - current brackets, an extra arena to serve as the waiting room + rvTourneyArena arenas[ MAX_ARENAS + 1 ]; + // tourneyHistory - past winners + arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ]; + // byeArena - the arena that natural (non disconnection) bye players go in + int byeArena; + // packTourneyHistory - whether or not we should transmit tourney history to one client through SendState + bool packTourneyHistory; + // forceTourneyHistory - if true will sync down tourney history for all clients + bool forceTourneyHistory; + // tourneyCount - how many tourneys to run per map + int tourneyCount; + // roundTimeout - a delay between all arenas finishing and starting the next round + int roundTimeout; + + static gameStateType_t type; +}; + +ID_INLINE int rvTourneyGameState::GetMaxRound( void ) const { + assert( type == GS_TOURNEY ); + return maxRound; +} + +ID_INLINE int rvTourneyGameState::GetRound( void ) const { + assert( type == GS_TOURNEY ); + return round; +} + +ID_INLINE int rvTourneyGameState::GetStartingRound( void ) const { + assert( type == GS_TOURNEY ); + return startingRound; +} + +ID_INLINE idPlayer** rvTourneyGameState::GetArenaPlayers( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ].GetPlayers(); +} + +ID_INLINE rvTourneyArena& rvTourneyGameState::GetArena( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ]; +} + +ID_INLINE const char* rvTourneyGameState::GetRoundDescription( void ) { + assert( type == GS_TOURNEY ); + + if( round == maxRound ) { + return common->GetLocalizedString( "#str_107720" ); + } else if( round == maxRound - 1 ) { + return common->GetLocalizedString( "#str_107719" ); + } else if( round == maxRound - 2 ) { + return common->GetLocalizedString( "#str_107718" ); + } else if( round == maxRound - 3 ) { + return common->GetLocalizedString( "#str_107717" ); + } else { + // shouldn't happen in production + return va( "ROUND %d", round ); + } +} + +ID_INLINE int rvTourneyGameState::GetTourneyCount( void ) { + assert( type == GS_TOURNEY ); + return tourneyCount; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +// current control zone state +enum dzState_t { + DZ_NONE = 0, + DZ_TAKEN, + DZ_LOST, + DZ_DEADLOCK, + DZ_MARINES_TAKEN, + DZ_MARINES_LOST, + DZ_STROGG_TAKEN, + DZ_STROGG_LOST, + DZ_MARINE_TO_STROGG, + DZ_STROGG_TO_MARINE, + DZ_MARINE_DEADLOCK, + DZ_STROGG_DEADLOCK, + DZ_MARINE_REGAIN, + DZ_STROGG_REGAIN +}; + +struct dzStatus_t { + dzState_t state; + int clientNum; +}; + +class riDZGameState : public rvGameState { +public: + riDZGameState( bool allocPrevious = true ); + ~riDZGameState( void ); + virtual void Clear( void ); + + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetDZState( int dz, dzState_t newState ); + dzState_t GetDZState( int dz ); + + bool operator==( const riDZGameState& rhs ) const; + riDZGameState& operator=( const riDZGameState& rhs ); + + int dzTriggerEnt; + int dzShaderParm; + +private: + dzStatus_t dzStatus[ TEAM_MAX ]; + + void ControlZoneStateChanged( int team ); +}; + + +ID_INLINE dzState_t riDZGameState::GetDZState( int dz ) { + assert( dz >= 0 && dz < TEAM_MAX ); + + return dzStatus[ dz ].state; +} + +ID_INLINE bool operator==( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} +#endif diff --git a/source/game/mp/Tourney.cpp b/source/game/mp/Tourney.cpp new file mode 100644 index 0000000..9006939 --- /dev/null +++ b/source/game/mp/Tourney.cpp @@ -0,0 +1,1021 @@ +//---------------------------------------------------------------- +// Tourney.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Tourney.h" + + +/* +=============================================================================== + +rvTourneyArena + +=============================================================================== +*/ + +/* +================ +rvTourneyArena::rvTourneyArena +================ +*/ +rvTourneyArena::rvTourneyArena() { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; +} + +/* +================ +rvTourneyArena::AddPlayer +================ +*/ +void rvTourneyArena::AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ) { + players[ 0 ] = playerOne; + players[ 1 ] = playerTwo; + if( playerOne ) { + playerOne->SetTourneyStatus( PTS_PLAYING ); + } + + if( playerTwo ) { + playerTwo->SetTourneyStatus( PTS_PLAYING ); + } +} + +/* +================ +rvTourneyArena::ClearPlayers +Clears player references if clearPlayer is NULL (client-side) +Clears specific player if clearPlayer is not NULL (server-side) +================ +*/ +void rvTourneyArena::ClearPlayers( idPlayer* clearPlayer /* = NULL */ ) { + if( gameLocal.isServer ) { + if( clearPlayer ) { + assert( clearPlayer == players[ 0 ] || clearPlayer == players[ 1 ] ); + if( clearPlayer == players[ 0 ] ) { + players[ 0 ] = NULL; + } else { + players[ 1 ] = NULL; + } + } + return; + } else { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + } +} + +/* +================ +rvTourneyArena::Ready +================ +*/ +void rvTourneyArena::Ready( void ) { + arenaState = AS_WARMUP; + nextStateTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countDown" ) * 1000 ) + 5000; + + matchStartTime = 0; + + if( !players[ 0 ] || !players[ 1 ] ) { + // if we don't have both players available, bye this arena + NewState( AS_DONE ); + } +} + +/* +================ +rvTourneyArena::Clear +Clears player list and state, but not round # or arena ID +================ +*/ +void rvTourneyArena::Clear( bool respawnPlayers ) { + if( gameLocal.isServer && respawnPlayers ) { + if( players[ 0 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 0 ]->ServerSpectate( true ); + } + + if( players[ 1 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 1 ]->ServerSpectate( true ); + } + + // This arena is being cleared so we must also clear out any spectators + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + } + + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; + SetState( AS_INACTIVE ); +} + +/* +================ +rvTourneyArena::GetLeader +Returns winning player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLeader( void ) { + if( players[ 0 ] == NULL || players[ 1 ] == NULL ) { + if( players[ 0 ] ) { + return players[ 0 ]; + } else if( players[ 1 ] ) { + return players[ 1 ]; + } + + return NULL; + } + + int playerOneScore = gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ); + int playerTwoScore = gameLocal.mpGame.GetTeamScore( players[ 1 ]->entityNumber ); + + if ( playerOneScore == playerTwoScore ) { + return NULL; + } + + return ( playerOneScore > playerTwoScore ? players[ 0 ] : players[ 1 ] ); +} + +/* +================ +rvTourneyArena::GetLoser +Returns losing player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLoser( void ) { + if( winner == NULL ) { + return NULL; + } else { + return ( winner == players[ 0 ] ? players[ 1 ] : players[ 0 ] ); + } +} + +/* +================ +rvTourneyArena::UpdateState +Updates this arena's state +================ +*/ +void rvTourneyArena::UpdateState( void ) { + if( (players[ 0 ] == NULL || players[ 1 ] == NULL) && arenaState != AS_DONE ) { + gameLocal.Warning( "rvTourneyArena::UpdateState() - UpdateState() called on non-full and non-done arena\n" ); + NewState( AS_DONE ); + return; + } + + switch( arenaState ) { + case AS_DONE: { + // not both players will neccesarily be valid here (if a player wins the arena then his opponent disconnects) + + break; + } + case AS_WARMUP: { + if( gameLocal.time > nextStateTime ) { + SetState( AS_ROUND ); + + // allow damage in warmup + //players[ 0 ]->fl.takedamage = true; + //players[ 1 ]->fl.takedamage = true; + // respawn the players + //players[ 0 ]->ServerSpectate( false ); + //players[ 1 ]->ServerSpectate( false ); + // respawn items + gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart( arenaID ); + + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + matchStartTime = gameLocal.time; + } + + break; + } + + case AS_ROUND: { + if( GetLeader() && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetTeamScore( GetLeader()->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + if( fragLimitTimeout && fragLimitTimeout < gameLocal.time ) { + NewState( AS_DONE ); + } else if ( !fragLimitTimeout ) { + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + } else if( fragLimitTimeout ) { + if( !GetLeader() && gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + // players tied at fraglimit + NewState( AS_SUDDEN_DEATH ); + } else { + // player hit fraglimit, but then killed himself and dropped below fraglimit, return to normal play + fragLimitTimeout = 0; + } + + //back to normal play? + } else if ( TimeLimitHit() ) { + // only send to clients in this arena, the times may be getting out of sync between the arenas during a round ( #13196 ) + int i; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if ( !player ) { + continue; + } + if ( player->GetArena() == arenaID ) { + gameLocal.mpGame.PrintMessageEvent( i, MSG_TIMELIMIT ); + } + } + if( GetLeader() == NULL ) { + // if tied at timelimit hit, goto sudden death + NewState( AS_SUDDEN_DEATH ); + } else { + // or just end the game + NewState( AS_DONE ); + } + } + break; + } + case AS_SUDDEN_DEATH: { + if ( GetLeader() ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", GetLeader()->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( AS_DONE ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, GetLeader()->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + } +} + +/* +================ +rvTourneyArena::NewState +================ +*/ +void rvTourneyArena::NewState( arenaState_t newState ) { + switch( newState ) { + case AS_DONE: { + winner = GetLeader(); + assert( winner ); + gameLocal.Printf( "rvTourneyArena::UpdateState() - %s has won this arena\n", GetLeader()->GetUserInfo()->GetString( "ui_name" ) ); + + winner->SetTourneyStatus( PTS_ADVANCED ); + + if( GetLoser() ) { + GetLoser()->SetTourneyStatus( PTS_ELIMINATED ); + } + + + if( players[ 0 ] ) { + players[ 0 ]->ServerSpectate( true ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + if( players[ 1 ] ) { + players[ 1 ]->ServerSpectate( true ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + // when we're done, put anyone who was spectating into another arena + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + + matchStartTime = 0; + break; + } + case AS_SUDDEN_DEATH: { + fragLimitTimeout = 0; + + // respawn the arena + players[ 0 ]->ServerSpectate( false ); + players[ 1 ]->ServerSpectate( false ); + gameLocal.LocalMapRestart( arenaID ); + break; + } + default: { + gameLocal.Error( "rvTourneyArena::NewState() - Unknown state '%d'\n", newState ); + return; + } + } + SetState( newState ); +} + +/* +================ +rvTourneyArena::RemovePlayer +The specified client is being removed from the server +================ +*/ +void rvTourneyArena::RemovePlayer( idPlayer* player ) { + // when we call Clear() the arena will clean up (set player instances to 0, spectate them, etc) + // we don't want it to do this for the disconnecting player, since he may not be entirely valid + // anymore. If we NULL out the disconnecting player, Clear() will properly reset the remaining player + // but will leave the volatile disconnecting player's data lone. + + bool playerInArena = false; + if( player == players[ 0 ] ) { + players[ 0 ] = NULL; + playerInArena = true; + } + + if( player == players[ 1 ] ) { + players[ 1 ] = NULL; + playerInArena = true; + } + + if( player == winner ) { + winner = NULL; + } + + if( !playerInArena ) { + // occasionally happens if a client has dropped ( the bystander maybe ) + common->Warning( "rvTourneyArena::RemovePlayer() - Called on player who is not in arena '%d'\n", arenaID ); + } +} + +/* +================ +rvTourneyArena::PackState +================ +*/ +void rvTourneyArena::PackState( idBitMsg& outMsg ) { + if( players[ 0 ] ) { + outMsg.WriteByte( players[ 0 ]->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + if( players[ 1 ] ) { + outMsg.WriteByte( players[ 1 ]->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + if( winner ) { + outMsg.WriteByte( winner->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + outMsg.WriteByte( arenaState ); + outMsg.WriteLong( nextStateTime ); + outMsg.WriteLong( fragLimitTimeout ); + outMsg.WriteLong( matchStartTime ); +} + +/* +================ +rvTourneyArena::UnpackState +================ +*/ +void rvTourneyArena::UnpackState( const idBitMsg& inMsg ) { + int playerOneNum = inMsg.ReadByte(); + int playerTwoNum = inMsg.ReadByte(); + int winnerNum = inMsg.ReadByte(); + + if( playerOneNum >= 0 && playerOneNum < MAX_CLIENTS ) { + players[ 0 ] = (idPlayer*)gameLocal.entities[ playerOneNum ]; + } else { + players[ 0 ] = NULL; + } + + if( playerTwoNum >= 0 && playerTwoNum < MAX_CLIENTS ) { + players[ 1 ] = (idPlayer*)gameLocal.entities[ playerTwoNum ]; + } else { + players[ 1 ] = NULL; + } + + if( winnerNum >= 0 && winnerNum < MAX_CLIENTS ) { + winner = (idPlayer*)gameLocal.entities[ winnerNum ]; + } else { + winner = NULL; + } + + arenaState = (arenaState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + fragLimitTimeout = inMsg.ReadLong(); + matchStartTime = inMsg.ReadLong(); +} + +/* +================ +rvTourneyArena::SetState +Set's this arena's state - client side only based on UpdateState() results from server +================ +*/ +void rvTourneyArena::SetState( arenaState_t newState ) { + arenaState = newState; +} + +/* +================ +rvTourneyArena::SetNextStateTime +================ +*/ +void rvTourneyArena::SetNextStateTime( int time ) { + nextStateTime = time; +} + +/* +================ +rvTourneyArena::GetNextStateTime +================ +*/ +int rvTourneyArena::GetNextStateTime( void ) { + return nextStateTime; +} + + +/* +================ +rvTourneyArena::GetState +================ +*/ +arenaState_t rvTourneyArena::GetState( void ) const { + return arenaState; +} + +/* +================ +rvTourneyArena::GetPlayerName +================ +*/ +const char* rvTourneyArena::GetPlayerName( int player ) { + assert( player >= 0 && player < 2 ); + + if( players[ player ] ) { + return players[ player ]->GetUserInfo()->GetString( "ui_name" ); + } else { + return NULL; + } +} + +/* +================ +rvTourneyArena::GetPlayerScore +================ +*/ +int rvTourneyArena::GetPlayerScore( int player ) { + assert( player >= 0 && player < 2 ); + + + if( players[ player ] ) { + return gameLocal.mpGame.GetTeamScore( players[ player ] ); + } else { + return 0; + } +} + +/* +================ +rvTourneyArena::TimeLimitHit +================ +*/ +bool rvTourneyArena::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +=============================================================================== + +rvTourneyGUI + +=============================================================================== +*/ + +rvTourneyGUI::rvTourneyGUI() { + tourneyGUI = NULL; + tourneyScoreboard = NULL; +} + +void rvTourneyGUI::SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ) { + tourneyGUI = newTourneyGUI; + tourneyScoreboard = newTourneyScoreboard; +} + +void rvTourneyGUI::RoundStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "roundTransitionIn" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "roundTransitionIn" ); + + // set bye player for new round - the actual byePlayer may not have changed, so the gamestate won't move him over +// UpdateByePlayer(); +} + +void rvTourneyGUI::ArenaStart( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyGUI" ); + return; + } + + //arenaInit sets names to white and scores to orange. needs values "gui::round" , "gui::bracket" and "gui::empty" + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( arena ); + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyGUI->SetStateBool( "empty", true ); + } else { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +} + +void rvTourneyGUI::ArenaDone( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& tourneyArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + if( tourneyArena.GetPlayers()[ 0 ] == NULL || tourneyArena.GetPlayers()[ 1 ] == NULL ) { + // we don't transition bye arenas to done + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + idPlayer* winner = tourneyArena.GetWinner(); + idPlayer** players = tourneyArena.GetPlayers(); + + if( winner == NULL ) { + common->Error( "rvTourneyGUI::ArenaDone() - Called on arena '%d' which is not done!\n", arena ); + return; + } + + if( winner != players[ 0 ] && winner != players[ 1 ] ) { + common->Error( "rvTourneyGUI::ArenaDone() - Arena '%d' is reporting a winner that is not in the arena!\n", arena ); + return; + } + + tourneyGUI->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaDone" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyScoreboard" ); + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + tourneyScoreboard->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); +} + +void rvTourneyGUI::ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& thisArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + // don't select empty arenas + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL ) { + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + tourneyGUI->SetStateInt( "sel", (int)highlightType ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaFocus" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyScoreboard" ); + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + tourneyScoreboard->SetStateInt( "sel", (int)highlightType ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaFocus" ); +} + +void rvTourneyGUI::UpdateScores( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + if( round < 0 ) { + // not now + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyGUI->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyGUI->SetStateBool( "empty", false ); + } + } + + tourneyGUI->StateChanged( gameLocal.time ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyScoreboard->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyScoreboard->SetStateBool( "empty", false ); + } + } + + tourneyScoreboard->StateChanged( gameLocal.time ); +} + +void rvTourneyGUI::PreTourney( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::PreTourney() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->HandleNamedEvent( "warmupTransitionIn" ); +} + +void rvTourneyGUI::TourneyStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + int maxRound = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetMaxRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "warmupTransitionOut" ); + + // setup and clear the scoreboard + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 1; i <= maxRound; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyScoreboard->SetStateInt( "round", i ); + tourneyScoreboard->SetStateInt( "bracket", j + 1 ); + + if( i < round ) { + // we aren't using these brackets + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } else if ( i == round ) { + // this is our initial round + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( j ); + + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + } else { + // this is our future bracket + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + } + } + + // we might have overwritten a bye player, so update it again +// UpdateByePlayer(); +} + +/*void rvTourneyGUI::UpdateByePlayer( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyGUI" ); + return; + } + + int arena; + for( arena = 0; arena < MAX_ARENAS; arena++ ) { + if( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ).GetState() == AS_INACTIVE ) { + break; + } + } + + if( arena >= MAX_ARENAS ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Bye player with no inactive arenas!" ); + return; + } + + idPlayer* byePlayer = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetByePlayer(); + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyGUI->SetStateBool( "empty", true ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +}*/ + +void rvTourneyGUI::SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ) { + if ( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::SetupTourneyHistory() - Invalid tourneyScoreboard" ); + return; + } + + if( startHistory <= 0 ) { + return; + } + + for ( int round = startHistory; round <= endHistory; round++ ) { + tourneyScoreboard->SetStateInt( "round", round ); + + for ( int arena = 0; arena < MAX_ARENAS / round; arena++ ) { + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + // whether we want to send arenaDone to this arena + // don't send for empty brackets or bye rounds + bool arenaNotDone = tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne) && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo); + + if( arenaNotDone ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + + bool firstSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne)); + bool secondSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo)); + + assert( !(firstSlotBye && secondSlotBye) ); + + if( firstSlotBye || secondSlotBye ) { + // this was a bye round + + if( secondSlotBye ) { + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + } + + arenaNotDone = true; + } else { + // regular round + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOneScore ); + + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwoScore ); + + tourneyScoreboard->SetStateInt( "winner", tourneyHistory[ round - 1 ][ arena ].playerOneScore > tourneyHistory[ round - 1 ][ arena ].playerTwoScore ? 1 : 2 ); + } + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + if( !arenaNotDone ) { + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); + } + } + } + + UpdateScores(); +} diff --git a/source/game/mp/Tourney.h b/source/game/mp/Tourney.h new file mode 100644 index 0000000..7ef571d --- /dev/null +++ b/source/game/mp/Tourney.h @@ -0,0 +1,156 @@ +//---------------------------------------------------------------- +// Tourney.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __TOURNEY_H__ +#define __TOURNEY_H__ + +#include "../Game_local.h" + +enum arenaState_t { + AS_INACTIVE = 0, + AS_WARMUP, + AS_ROUND, + AS_SUDDEN_DEATH, + AS_DONE +}; + +#define MAX_TOURNEY_HISTORY_NAME_LEN 32 + +typedef struct arenaOutcome_s { + // for clients that have disconnected, copy their names for history purposes + char playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + char playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + + // for currently connected clients, use clientnum to get current name + int playerOneNum; + int playerTwoNum; + + int playerOneScore; + int playerTwoScore; +} arenaOutcome_t; + +// shouldn't exceed MAX_INSTANCES from idMultiplayerGame +const int MAX_ARENAS = 8; + +const int MAX_ROUNDS = 4; + +class rvTourneyArena { +public: + rvTourneyArena(); + + void AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ); + void ClearPlayers( idPlayer* clearPlayer = NULL ); + void Clear( bool respawnPlayers = true ); + void Ready( void ); + + idPlayer* GetLeader( void ); + idPlayer* GetLoser( void ); + idPlayer* GetWinner( void ) { return winner; } + void UpdateState( void ); + void NewState( arenaState_t newState ); + + idPlayer** GetPlayers( void ); + + void SetArenaID( int id ); + int GetArenaID( void ) const; + + arenaState_t GetState( void ) const; + void SetState( arenaState_t newState ); + void SetNextStateTime( int time ); + int GetNextStateTime( void ); + int GetMatchStartTime( void ) { return matchStartTime; } + + void PackState( idBitMsg& outMsg ); + void UnpackState( const idBitMsg& inMsg ); + + void RemovePlayer( idPlayer* player ); + bool TimeLimitHit( void ); + bool IsPlaying( idPlayer* player ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE && ( player == players[ 0 ] || player == players[ 1 ] ) ); } + bool HasPlayer( idPlayer* player ) { return ( player == players[0] || player == players[1] ); } + bool IsPlaying( void ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE ); } + + const char* GetPlayerName( int player ); + int GetPlayerScore( int player ); + int GetFraglimitTimeout( void ) { return fragLimitTimeout; } + bool operator==( const rvTourneyArena& rhs ) const; + bool operator!=( const rvTourneyArena& rhs ) const; + rvTourneyArena& operator=( const rvTourneyArena& rhs ); + +private: + // players - players in arena + idPlayer* players[ 2 ]; + // arenaID - this arena's ID + int arenaID; + // arenaState - state of the arena + arenaState_t arenaState; + // nextStateTime - transition time to next state + int nextStateTime; + // winner - the winner of the arena + idPlayer* winner; + // fragLimitTimeout - timeout to let death anims play + int fragLimitTimeout; + // matchStartTime - time arena started + int matchStartTime; +}; + +ID_INLINE idPlayer** rvTourneyArena::GetPlayers( void ) { + return players; +} + +ID_INLINE void rvTourneyArena::SetArenaID( int id ) { + arenaID = id; +} + +ID_INLINE int rvTourneyArena::GetArenaID( void ) const { + return arenaID; +} + +ID_INLINE bool rvTourneyArena::operator==( const rvTourneyArena& rhs ) const { + return ( arenaState == rhs.arenaState && players[ 0 ] == rhs.players[ 0 ] && players[ 1 ] == rhs.players[ 1 ] && nextStateTime == rhs.nextStateTime && arenaID == rhs.arenaID && fragLimitTimeout == rhs.fragLimitTimeout && matchStartTime == rhs.matchStartTime ); +} + +ID_INLINE bool rvTourneyArena::operator!=( const rvTourneyArena& rhs ) const { + return ( arenaState != rhs.arenaState || players[ 0 ] != rhs.players[ 0 ] || players[ 1 ] != rhs.players[ 1 ] || nextStateTime != rhs.nextStateTime || arenaID != rhs.arenaID || fragLimitTimeout != rhs.fragLimitTimeout || matchStartTime != rhs.matchStartTime ); +} + +ID_INLINE rvTourneyArena& rvTourneyArena::operator=( const rvTourneyArena& rhs ) { + players[ 0 ] = rhs.players[ 0 ]; + players[ 1 ] = rhs.players[ 1 ]; + arenaState = rhs.arenaState; + nextStateTime = rhs.nextStateTime; + arenaID = rhs.arenaID; + fragLimitTimeout = rhs.fragLimitTimeout; + matchStartTime = rhs.matchStartTime; + return (*this); +} + +typedef enum { + TGH_BRACKET, + TGH_PLAYER_ONE, + TGH_PLAYER_TWO +} tourneyGUIHighlight_t; + +class rvTourneyGUI { +public: + rvTourneyGUI(); + void SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ); + + void RoundStart( void ); + void ArenaStart( int arena ); + void ArenaDone( int arena ); + void ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ); + void UpdateScores( void ); + void PreTourney( void ); + void TourneyStart( void ); + //void UpdateByePlayer( void ); + void SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ); + +private: + idUserInterface* tourneyGUI; + idUserInterface* tourneyScoreboard; +}; + +#endif diff --git a/source/game/mp/VoiceComms.cpp b/source/game/mp/VoiceComms.cpp new file mode 100644 index 0000000..41565f1 --- /dev/null +++ b/source/game/mp/VoiceComms.cpp @@ -0,0 +1,196 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop +#include "../Game_local.h" + +// This equates to about 1 second +#define MAX_VOICE_PACKET_SIZE 382 + +idCVar si_voiceChat( "si_voiceChat", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_INFO, "enables or disables voice chat through the server" ); +idCVar g_voiceChatDebug( "g_voiceChatDebug", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "display info on voicechat net traffic" ); + +void idMultiplayerGame::ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + + idBitMsg outMsg; + int i; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 2]; + idPlayer * from; + + from = ( idPlayer * )gameLocal.entities[clientNum]; + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) || !from ) { + return; + } + + // Create a new packet with forwarded data + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteData( inMsg.GetReadData(), inMsg.GetRemainingData() ); + + if( g_voiceChatDebug.GetInteger() & 2 ) { + common->Printf( "VC: Received %d bytes, forwarding...\n", inMsg.GetRemainingData() ); + } + + // Forward to appropriate parties + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* to = ( idPlayer * )gameLocal.entities[i]; + if( to && to->GetUserInfo() && to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( i != gameLocal.localClientNum && CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to client %d\n", to->entityNumber ); + } + } + else + { + if( g_voiceChatDebug.GetInteger() ) + { + common->Printf( " ... suppressed packet to client %d\n", to->entityNumber ); + } + } + } + } + } + } + +#ifdef _USE_VOICECHAT + // Listen servers need to manually call the receive function + if ( gameLocal.isListenServer ) { + // Skip over control byte + outMsg.ReadByte(); + + idPlayer* to = gameLocal.GetLocalPlayer(); + if( to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + ReceiveAndPlayVoiceData( outMsg ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to local client %d\n", gameLocal.localClientNum ); + } + ReceiveAndPlayVoiceData( outMsg ); + } + } + } + } + } +#endif +} + +bool idMultiplayerGame::CanTalk( idPlayer *from, idPlayer *to, bool echo ) { + if ( !to ) { + return false; + } + + if ( !from ) { + return false; + } + + if ( from->spectating != to->spectating ) { + return false; + } + + if ( to->IsPlayerMuted( from ) ) { + return false; + } + + if ( to->GetArena() != from->GetArena() ) { + return false; + } + + if ( gameLocal.IsTeamGame() && from->team != to->team ) { + return false; + } + + if ( from == to ) { + return echo; + } + + return true; +} + +#ifdef _USE_VOICECHAT + +void idMultiplayerGame::XmitVoiceData( void ) +{ + idBitMsg outMsg; + int count; + byte work; + byte buffer[MAX_VOICE_PACKET_SIZE]; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 1]; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + + // Grab any new input and send up to the server + count = soundSystem->GetVoiceData( buffer, MAX_VOICE_PACKET_SIZE ); + if( count ) + { + outMsg.BeginWriting(); + outMsg.BeginReading(); + + work = GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT + cvarSystem->GetCVarBool( "s_voiceChatEcho" ) + ( cvarSystem->GetCVarInteger( "s_voiceChatTest" ) * 2 ); + outMsg.WriteByte( work ); + outMsg.WriteData( buffer, count ); + + // FIXME!!! FIXME!!! This should be unreliable - this is very bad + networkSystem->ClientSendReliableMessage( outMsg ); + + if( g_voiceChatDebug.GetInteger() & 1 ) + { + common->Printf( "VC: sent %d bytes at %d\n", count, gameLocal.time ); + } + } +} + +void idMultiplayerGame::ReceiveAndPlayVoiceData( const idBitMsg &inMsg ) +{ + int clientNum; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + clientNum = inMsg.ReadByte(); + soundSystem->PlayVoiceData( clientNum, inMsg.GetReadData(), inMsg.GetRemainingData() ); + if( g_voiceChatDebug.GetInteger() & 4 ) + { + common->Printf( "VC: Playing %d bytes\n", inMsg.GetRemainingData() ); + } +} + +#endif + +// end diff --git a/source/game/mp/stats/StatEvent.cpp b/source/game/mp/stats/StatEvent.cpp new file mode 100644 index 0000000..867bc83 --- /dev/null +++ b/source/game/mp/stats/StatEvent.cpp @@ -0,0 +1,331 @@ +//---------------------------------------------------------------- +// StatEvent.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + + +rvStatHit::rvStatHit( int t, int p, int v, int w, bool countForAccuracy ) : rvStat( t ) { + playerClientNum = p; + victimClientNum = v; + weapon = w; + trackAccuracy = countForAccuracy; + type = ST_HIT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatHit::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS && weapon >= 0 && weapon < MAX_WEAPONS && trackAccuracy ) { + stats[ playerClientNum ].weaponHits[ weapon ]++; + } + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_railgun" ) ) { + + // Apparently it does the rail hit event before the rail fired event, so this is backwards for a reason + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_HIT ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_RAIL_HIT; + } + + if ( rvStatManager::lastRailShot[ playerClientNum ] < stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } else { + if ( rvStatManager::lastRailShot[ playerClientNum ] >= stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + if ( (rvStatManager::lastRailShotHits[ playerClientNum ] % 2) == 0 ) { + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + ++rvStatManager::lastRailShotHits[ playerClientNum ]; + } else { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } + } + +/* + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 8000, 2, (rvStat**)lastHits ); + if( lastHits[0] ) { + if( !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[0])->weapon ) ), "weapon_railgun" ) ) { + + //Check to make sure we don't chain impressive awards. + if(lastHits[1] + && !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[1])->weapon ) ), "weapon_railgun" ) + && (lastHits[1]->GetTimeStamp() - lastHits[0]->GetTimeStamp()) < 4000) { + return; + + } + + if(gameLocal.time - lastHits[0]->GetTimeStamp() < 4000){ + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + } + } +*/ + } else if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_rocketlauncher" ) ) { + + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_FIRED ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_ROCKET_HIT; + } + + } +} + +rvStatKill::rvStatKill( int t, int p, int v, bool g, int mod ) : rvStat( t ) { + type = ST_KILL; + playerClientNum = p; + victimClientNum = v; + gibbed = g; + methodOfDeath = mod; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatKill::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum < 0 || playerClientNum >= MAX_CLIENTS || victimClientNum < 0 || victimClientNum >= MAX_CLIENTS ) { + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + idPlayer* victim = (idPlayer*)gameLocal.entities[ victimClientNum ]; + + // no award processing for suicides + if( victimClientNum == playerClientNum ) { + stats[ playerClientNum ].suicides++; + return; + } + + bool teamKill = false; + + // don't track team kills + if( !gameLocal.IsTeamGame() || player->team != victim->team ) { + stats[ playerClientNum ].kills++; + + if( methodOfDeath >= 0 && methodOfDeath < MAX_WEAPONS ) { + stats[ playerClientNum ].weaponKills[ methodOfDeath ]++; + } + } + + if( gameLocal.IsTeamGame() && player->team == victim->team ) { + teamKill = true; + } + + + // check for humiliation award + if( !teamKill && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_gauntlet" ) ) { + statManager->GiveInGameAward( IGA_HUMILIATION, playerClientNum ); + } + + rvStatKill* lastKills[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_KILL, gameLocal.time - 5000, 2, (rvStat**)lastKills ); + + // check for excellent award + if( !teamKill && lastKills[ 0 ] && lastKills[ 0 ]->GetTimeStamp() >= (gameLocal.time - 2000) && lastKills[ 0 ]->victimClientNum != playerClientNum && victimClientNum != playerClientNum ) { + statManager->GiveInGameAward( IGA_EXCELLENT, playerClientNum ); + } + + // check for rampage award + if( !teamKill && ( gibbed && victimClientNum != playerClientNum ) && + ( lastKills[ 0 ] && lastKills[ 0 ]->gibbed && lastKills[ 0 ]->victimClientNum != playerClientNum ) && + ( lastKills[ 1 ] && lastKills[ 1 ]->gibbed && lastKills[ 1 ]->victimClientNum != playerClientNum ) ) { + statManager->GiveInGameAward( IGA_RAMPAGE, playerClientNum ); + } + + + // check for combo kill award + if( victimClientNum != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_railgun" ) ) { + // the rail killing shot is the last hit, so look for the one past that + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 3000, 2, (rvStat**)lastHits ); + + if( lastHits[ 1 ] && lastHits[ 1 ]->GetVictimClientNum() != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", lastHits[ 1 ]->GetWeapon() ) ), "weapon_rocketlauncher" ) ) { + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_RAIL_HIT ) { + statManager->GiveInGameAward( IGA_COMBO_KILL, playerClientNum ); + } + } + } + + rvStatManager::comboKillState[ playerClientNum ] = CKS_NONE; + + + // check for defense award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + // defense is given for two conditions +// assert( gameLocal.mpGame.GetFlagEntity( TEAM_STROGG ) && gameLocal.mpGame.GetFlagEntity( TEAM_MARINE ) ); + assert( player->team >= 0 && player->team < TEAM_MAX ); + assert( gameLocal.mpGame.GetGameState()->IsType( rvCTFGameState::GetClassType() ) ); + + if ( gameLocal.mpGame.GetFlagEntity( player->team ) ) { + // defending your flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_AT_BASE || ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_DROPPED ) { + if( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if you're close to flag and you kill an enemy + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if ( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if enemy is close to flag and you kill them + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + + // defending your teammate carrying the enemy flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( gameLocal.mpGame.OpposingTeam( player->team ) ) == FS_TAKEN ) { + int clientNum = ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagCarrier( gameLocal.mpGame.OpposingTeam( player->team ) ); + + idPlayer* flagCarrier = NULL; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + flagCarrier = (idPlayer*)gameLocal.entities[ clientNum ]; + } + + if( flagCarrier ) { + if( (flagCarrier->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed enemy while close to the flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if( (flagCarrier->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed an enemy who was close to teh flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + } + + // check for holy shit award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + if( (player->team == TEAM_MARINE && victim->PowerUpActive( POWERUP_CTF_MARINEFLAG )) || + (player->team == TEAM_STROGG && victim->PowerUpActive( POWERUP_CTF_STROGGFLAG )) ) { + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( victim->team ) == FS_AT_BASE ) { + + // fixme: something is broken with the mpgame state + if ( gameLocal.mpGame.GetFlagEntity( victim->team ) ) { + if( (gameLocal.mpGame.GetFlagEntity( victim->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 40000 ) { + statManager->GiveInGameAward( IGA_HOLY_SHIT, playerClientNum ); + } + } + } + } + } +} + +/* +================ +rvStatDeath + +A player died +================ +*/ +rvStatDeath::rvStatDeath( int t, int p, int mod ) : rvStat( t ) { + type = ST_DEATH; + playerClientNum = p; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDeath::RegisterInGame( rvPlayerStat* stats ) { + stats[ playerClientNum ].deaths++; +} + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +rvStatDamageDealt::rvStatDamageDealt( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_DEALT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageDealt::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageGiven += damage; + } +} + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +rvStatDamageTaken::rvStatDamageTaken( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_TAKEN; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageTaken::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageTaken += damage; + } +} + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +rvStatFlagDrop::rvStatFlagDrop( int t, int p, int a, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + attacker = a; + type = ST_CTF_FLAG_DROP; +} + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +rvStatFlagReturn::rvStatFlagReturn( int t, int p, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + type = ST_CTF_FLAG_RETURN; +} + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +rvStatFlagCapture::rvStatFlagCapture( int t, int p, int f, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + flagTeam = f; + type = ST_CTF_FLAG_CAPTURE; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatFlagCapture::RegisterInGame( rvPlayerStat* stats ) { + statManager->GiveInGameAward( IGA_CAPTURE, playerClientNum ); + // figure out if we need to give an assist + + // see if someone carried the flag a bit, lost it, but it was capped + rvStatFlagDrop* flagDrop = (rvStatFlagDrop*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_DROP, gameLocal.time - 10000 ); + if( flagDrop && flagDrop->GetTeam() == team ) { + // only give the award if there was no flag return between the flag drop and the flag capture + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( flagTeam, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn == NULL ) { + statManager->GiveInGameAward( IGA_ASSIST, flagDrop->GetPlayerClientNum() ); + } + } + + // see if someone returned the flag, allowing a cap + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn ) { + statManager->GiveInGameAward( IGA_ASSIST, flagReturn->GetPlayerClientNum() ); + } +} diff --git a/source/game/mp/stats/StatEvent.h b/source/game/mp/stats/StatEvent.h new file mode 100644 index 0000000..b2cdaa2 --- /dev/null +++ b/source/game/mp/stats/StatEvent.h @@ -0,0 +1,361 @@ +//---------------------------------------------------------------- +// StatEvent.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATEVENT_H__ +#define __STATEVENT_H__ + +/* +=============================================================================== + +Multiplayer statistics events + +This file contains statistic event definitions. rvStatManager exposes +game-related functions (i.e. 'Kill', 'FlagCaptured') which create the +appropriate statistic event and add it to the statQueue in rvStatManager. + +The statQueue contains a complete picture of an MP game. It can be parsed to +calculate accuracies, award end-game awards, etc. + +Statistic events also have functionality to register in-game information for +on-the-fly statistics. + +=============================================================================== +*/ + +class rvPlayerStat; + +/* +================ +statType_t + +Type identifiers for RTTI of stat events, rvStatTeam-derived events must go +after ST_STAT_TEAM +================ +*/ +enum statType_t { + // not an actual event, undefined marker + ST_NONE = 0, + // rvStat derived + ST_BEGIN_GAME, + ST_END_GAME, + ST_CLIENT_CONNECT, + ST_HIT, + ST_KILL, + ST_DEATH, + ST_DAMAGE_DEALT, + ST_DAMAGE_TAKEN, + // not an actual event, team marker + ST_STAT_TEAM, + // rvStatTeam derived + ST_CTF_FLAG_CAPTURE, + ST_CTF_FLAG_DROP, + ST_CTF_FLAG_RETURN, + + ST_COUNT +}; + +/* +================ +rvStat + +An individual event we want to know about for stats. +================ +*/ +class rvStat { + + friend class rvStatAllocator; + +public: + + statType_t GetType( void ) const { return type; }; + int GetTimeStamp( void ) const { return timeStamp; }; + byte GetPlayerClientNum( void ) const { return playerClientNum; }; + +protected: + + // moved to protected to prevent allocating these on the normal heap. + // these should only be allocatd by rvStatAllocator. + rvStat( int t ) { timeStamp = t; playerClientNum = -1; type = ST_NONE; }; + virtual ~rvStat( void ) {}; + + virtual void RegisterInGame( rvPlayerStat* stats ) {}; + + // the entity number of the player associated with this statistic + byte playerClientNum; + + statType_t type; + int timeStamp; + +private: + // because of the way memory is handled for these we want the compiler + // on our side to find abuses. note that we aren't defining these. + // this change is propagated to all derived classes. + rvStat(); + rvStat( const rvStat &rhs ); + const rvStat &operator=( const rvStat &rhs ); +}; + +/* +================ +rvStatTeam + +A team-related rvStat +================ +*/ +class rvStatTeam : public rvStat { + + friend class rvStatAllocator; + +public: + virtual ~rvStatTeam( void ) {}; + + int GetTeam( void ) { return team; }; +protected: + + // see comment in rvStat + rvStatTeam( int t, int tm ) : rvStat( t ) { team = tm; }; + + byte team; + +private: + // see comment in rvStat + rvStatTeam(); + rvStatTeam( const rvStatTeam &rhs ); + const rvStatTeam &operator=( const rvStat &rhs ); + +}; + +/* +=============================================================================== + +rvStat/rvStatTeam-derived classes + +These are the individual events that get stored in the +statManager's stat queue + +=============================================================================== +*/ + +/* +================ +rvStatBeginGame + +A game has begun +================ +*/ +class rvStatBeginGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatBeginGame( int t ) : rvStat( t ) { type = ST_BEGIN_GAME; }; +}; + +/* +================ +rvStatEndGame + +The current game has ended +================ +*/ +class rvStatEndGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatEndGame( int t ) : rvStat( t ) { type = ST_END_GAME; }; +}; + +/* +================ +rvStatClientConnect + +A player has connected +================ +*/ +class rvStatClientConnect : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatClientConnect( int t, int p ) : rvStat( t ) { type = ST_CLIENT_CONNECT; playerClientNum = p; }; +}; + +/* +================ +rvStatHit + +A player hit another player +================ +*/ +class rvStatHit : public rvStat { + + friend class rvStatAllocator; + +public: + int GetVictimClientNum() const { return victimClientNum; } + int GetWeapon() const { return weapon; } + +protected: + rvStatHit( int t, int p, int v, int w, bool countForAccuracy ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + byte victimClientNum; + bool trackAccuracy; +}; + +/* +================ +rvStatKill + +A player killed another player +================ +*/ +class rvStatKill : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatKill( int t, int p, int v, bool g, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; + byte victimClientNum; + bool gibbed; +}; + +/* +================ +rvStatDeath + +A player died +================ +*/ +class rvStatDeath : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatDeath( int t, int p, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; +}; + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +class rvStatDamageDealt : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageDealt( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +class rvStatDamageTaken : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageTaken( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +class rvStatFlagDrop : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagDrop( int t, int p, int a, int tm ); + + byte attacker; // enemy who caused the flag drop + +private: + // see comment in rvStat + rvStatFlagDrop(); + rvStatFlagDrop( const rvStatFlagDrop &rhs ); + const rvStatFlagDrop &operator=( const rvStatFlagDrop &rhs ); +}; + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +class rvStatFlagReturn : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagReturn( int t, int p, int tm ); + +private: + // see comment in rvStat + rvStatFlagReturn(); + rvStatFlagReturn( const rvStatFlagReturn &rhs ); + const rvStatFlagReturn &operator=( const rvStatFlagReturn &rhs ); +}; + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +class rvStatFlagCapture : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagCapture( int t, int p, int f, int tm ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte flagTeam; // team of flag was captured + +private: + // see comment in rvStat + rvStatFlagCapture(); + rvStatFlagCapture( const rvStatFlagCapture &rhs ); + const rvStatFlagCapture &operator=( const rvStatFlagCapture &rhs ); +}; + +#endif diff --git a/source/game/mp/stats/StatManager.cpp b/source/game/mp/stats/StatManager.cpp new file mode 100644 index 0000000..75e6996 --- /dev/null +++ b/source/game/mp/stats/StatManager.cpp @@ -0,0 +1,1345 @@ +//---------------------------------------------------------------- +// StatManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + +// TTimo - *????????* +#include + +rvStatManager statManagerLocal; +rvStatManager* statManager = &statManagerLocal; + +comboKillState_t rvStatManager::comboKillState[ MAX_CLIENTS ] = { CKS_NONE }; +int rvStatManager::lastRailShot[ MAX_CLIENTS ] = { -2 }; +int rvStatManager::lastRailShotHits[ MAX_CLIENTS ] = { 0 }; + +inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ] = { + //IGA_INVALID + { + NULL + }, + //IGA_CAPTURE + { + "capture" + }, + //IGA_HUMILIATION + { + "humiliation" + }, + //IGA_IMPRESSIVE + { + "impressive" + }, + //IGA_EXCELLENT + { + "excellent" + }, + //IGA_ASSIST + { + "assist" + }, + //IGA_DEFENSE + { + "defense" + }, + //IGA_COMBO_KILL + { + "combo_kill" + }, + //IGA_RAMPAGE + { + "rampage" + }, + //IGA_HOLY_SHIT + { + "holy_shit" + } +}; + +// RAVEN BEGIN +// rhummer: localized these strings. +endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ] = { + //EGA_INVALID + { + NULL + }, + //EGA_LEMMING + { + "#str_107260" + }, + //EGA_RAIL_MASTER + { + "#str_107261" + }, + //EGA_ROCKET_SAUCE + { + "#str_107262" + }, + //EGA_BRAWLER + { + "#str_107263" + }, + //EGA_SNIPER + { + "#str_107264" + }, + //EGA_CRITICAL_FAILURE + { + "#str_107265" + }, + //EGA_TEAM_PLAYER + { + "#str_107266" + }, + //EGA_ACCURACY + { + "#str_107267" + }, + //EGA_FRAGS + { + "#str_107268" + }, + //EGA_PERFECT + { + "#str_107269" + } +}; +// RAVEN END + +void showStats_f( const idCmdArgs &args ) { + + statManager->DebugPrint(); +} + + +/* +=============================================================================== + + rvStatAllocator + + Handles allocating stat events + +=============================================================================== +*/ + +void *rvStatAllocator::GetBlock( size_t blockSize, int* blockNumOut /* = NULL */ ) { + // if not enough bytes for this allocation, get a new block + if ( GetBytesLeftInBlock() < blockSize ) { + // recycle & reuse a block + currentBlock++; + placeInBlock = 0; + if( currentBlock >= MAX_BLOCKS ) { + currentBlock = 0; + if( statManager->GetStat( 0 ) && gameLocal.isMultiplayer ) { +// int delta = gameLocal.time - statManager->GetStat( 0 )->GetTimeStamp(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Tracked %g seconds of stats before recycle\n", delta / 1000.0f ); + } + } +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Using block %d\n", currentBlock ); +// statManager->DebugPrint(); +// int numFreed = + statManager->FreeEvents( currentBlock ); +// statManager->DebugPrint(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - stat manager freed %d events which used block %d\n", numFreed, currentBlock ); + } + + // if this fires our objects are too large or our alloc size is too small + assert( GetBytesLeftInBlock() >= blockSize ); + + byte *newBlock = blocks + ( BLOCK_SIZE * currentBlock ) + placeInBlock; + placeInBlock += blockSize; + totalAllocations++; + totalBytesUsed += blockSize; + if( blockNumOut ) { + // return the current block number if requested + (*blockNumOut) = currentBlock; + } + return newBlock; +} + +void rvStatAllocator::Reset() { + currentBlock = 0; + placeInBlock = 0; + + totalBytesUsed = 0; + totalAllocations = 0; + totalBytesAllocated = 0; + for ( int i = 0; i < ST_COUNT; i++ ) { + allocationsByType[ i ] = 0; + } +} + +rvStatAllocator::rvStatAllocator() { + Reset(); +} + +void rvStatAllocator::Report() +{ + // shouchard: for debugging and tuning only + common->Printf( "rvStatAllocator: dump of usage stats\n" ); + common->Printf( "\t%d total bytes handed out in %d requests\n", GetTotalBytesUsed(), GetTotalAllocations() ); + common->Printf( "\tbegin game: %3d; end game: %3d\n", + GetAllocationsByType( ST_BEGIN_GAME ), + GetAllocationsByType( ST_END_GAME ) ); + common->Printf( "\tplayer hit: %3d; player kill: %3d\n", + GetAllocationsByType( ST_HIT ), + GetAllocationsByType( ST_KILL ) ); + common->Printf( "\tplayer death: %3d;\n", + GetAllocationsByType( ST_DEATH ) ); + common->Printf( "\tdamage dealt: %3d; damage taken: %3d\n", + GetAllocationsByType( ST_DAMAGE_DEALT ), + GetAllocationsByType( ST_DAMAGE_TAKEN ) ); + common->Printf( "\tstat team: %3d\n", + GetAllocationsByType( ST_STAT_TEAM ) ); + common->Printf( "\tflag capture: %3d;\n", + GetAllocationsByType( ST_CTF_FLAG_CAPTURE ) ), + common->Printf( "\tflag drop: %3d; flag return: %3d\n", + GetAllocationsByType( ST_CTF_FLAG_DROP ), + GetAllocationsByType( ST_CTF_FLAG_RETURN ) ); +} + +// object allocators + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new new +#endif // _INLINEDEBUGMEMORY + +rvStatBeginGame *rvStatAllocator::AllocStatBeginGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatBeginGame ), blockNumOut ); + assert( newBlock ); + rvStatBeginGame *stat = new( newBlock ) rvStatBeginGame( t ); + allocationsByType[ ST_BEGIN_GAME ]++; + return stat; +} + +rvStatEndGame *rvStatAllocator::AllocStatEndGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatEndGame ), blockNumOut ); + assert( newBlock ); + rvStatEndGame *stat = new( newBlock ) rvStatEndGame( t ); + allocationsByType[ ST_END_GAME ]++; + return stat; +} + +rvStatClientConnect *rvStatAllocator::AllocStatClientConnect( int t, int client, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatClientConnect ), blockNumOut ); + assert( newBlock ); + rvStatClientConnect *stat = new( newBlock ) rvStatClientConnect( t, client ); + allocationsByType[ ST_CLIENT_CONNECT ]++; + return stat; +} + +rvStatHit *rvStatAllocator::AllocStatHit( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatHit ), blockNumOut ); + assert( newBlock ); + rvStatHit *stat = new( newBlock ) rvStatHit( t, p, v, w, countForAccuracy ); + allocationsByType[ ST_HIT ]++; + return stat; +} + +rvStatKill *rvStatAllocator::AllocStatKill( int t, int p, int v, bool g, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatKill ), blockNumOut ); + assert( newBlock ); + rvStatKill *stat = new( newBlock ) rvStatKill( t, p, v, g, mod ); + allocationsByType[ ST_KILL ]++; + return stat; +} + +rvStatDeath *rvStatAllocator::AllocStatDeath( int t, int p, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDeath ), blockNumOut ); + assert( newBlock ); + rvStatDeath *stat = new( newBlock ) rvStatDeath( t, p, mod ); + allocationsByType[ ST_DEATH ]++; + return stat; +} + +rvStatDamageDealt *rvStatAllocator::AllocStatDamageDealt( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageDealt ), blockNumOut ); + assert( newBlock ); + rvStatDamageDealt *stat = new( newBlock ) rvStatDamageDealt( t, p, w, d ); + allocationsByType[ ST_DAMAGE_DEALT ]++; + return stat; +} + +rvStatDamageTaken *rvStatAllocator::AllocStatDamageTaken( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageTaken ), blockNumOut ); + assert( newBlock ); + rvStatDamageTaken *stat = new( newBlock ) rvStatDamageTaken( t, p, w, d ); + allocationsByType[ ST_DAMAGE_TAKEN ]++; + return stat; +} + +rvStatFlagDrop *rvStatAllocator::AllocStatFlagDrop( int t, int p, int a, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagDrop ), blockNumOut ); + assert( newBlock ); + rvStatFlagDrop *stat = new( newBlock ) rvStatFlagDrop( t, p, a, tm ); + allocationsByType[ ST_CTF_FLAG_DROP ]++; + return stat; +} + +rvStatFlagReturn *rvStatAllocator::AllocStatFlagReturn( int t, int p, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagReturn ), blockNumOut ); + assert( newBlock ); + rvStatFlagReturn *stat = new( newBlock ) rvStatFlagReturn( t, p, tm ); + allocationsByType[ ST_CTF_FLAG_CAPTURE ]++; + return stat; +} + +rvStatFlagCapture *rvStatAllocator::AllocStatFlagCapture( int t, int p, int f, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagCapture ), blockNumOut ); + assert( newBlock ); + rvStatFlagCapture *stat = new( newBlock ) rvStatFlagCapture( t, p, f, tm ); + allocationsByType[ ST_CTF_FLAG_RETURN ]++; + return stat; +} + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new ID_DEBUG_NEW +#endif // _INLINEDEBUGMEMORY + +/* +=============================================================================== + + rvStatManager + + Stores game statistic events in statQueue + +=============================================================================== +*/ + +// shouchard: stat manager start with 1 meg; we'll tune as we get better data +rvStatManager::rvStatManager() { + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Init( void ) { + Shutdown(); + statQueue.Clear(); + awardQueue.Clear(); + statQueue.SetGranularity( 1024 ); + endGameSetup = false; + cmdSystem->AddCommand( "ShowInGameStats", showStats_f, CMD_FL_SYSTEM, "show in game stats." ); + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Shutdown( void ) { + statAllocator.Report(); + statAllocator.Reset(); + statQueue.Clear(); + awardQueue.Clear(); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::BeginGame( void ) { + int blockNum; + rvStatBeginGame* stat = statAllocator.AllocStatBeginGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + endGameSetup = false; +#if ID_TRAFFICSTATS + startTime = gameLocal.time; + networkSystem->GetTrafficStats( startSent, startPacketsSent, startReceived, startPacketsReceived ); +#endif + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::EndGame( void ) { + int blockNum; + rvStatEndGame* stat = statAllocator.AllocStatEndGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + CalculateEndGameStats(); + awardQueue.Clear(); +#if ID_TRAFFICSTATS + int sent, packetsSent, received, packetsReceived, time; + networkSystem->GetTrafficStats( sent, packetsSent, received, packetsReceived ); + sent -= startSent; + packetsSent -= startPacketsSent; + received -= startReceived; + packetsReceived -= startPacketsReceived; + time = gameLocal.time - startTime; + common->Printf( "EndGame. bytes sent: %d packets sent: %d bytes received: %d packets received: %d\n", sent, packetsSent, received, packetsReceived ); + // compute averages, including packet overhead + // adjust the UDP overhead, may depend on your TCP stack implementation ( 42 comes from ethereal analysis of the traffic ) + sent += packetsSent * 42; + received += packetsReceived * 42; + float sentBps, recvBps; + sentBps = (float)( sent ) * 1000.0f / time; + recvBps = (float)( received ) * 1000.0f / time; + common->Printf( "avg sent %g B/s, received %g B/s\n", sentBps, recvBps ); +#endif +} + +void rvStatManager::ClientConnect( int clientNum ) { + // push a client connected event into the queue so we don't get confused with old + // events detailing the previous owner of this clientNum + int blockNum; + rvStatClientConnect* stat = statAllocator.AllocStatClientConnect( gameLocal.time, clientNum, &blockNum ); + statQueue.Append( rvPair( (rvStat*)stat, blockNum ) ); +} + +void rvStatManager::ClientDisconnect( int clientNum ) { + // re-init player stats + playerStats[ clientNum ] = rvPlayerStat(); +} + +void rvStatManager::Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ) { + int deathBlock, killBlock; + rvStatDeath* statDeath = statAllocator.AllocStatDeath( gameLocal.time, victim->entityNumber, methodOfDeath, &deathBlock ); + + statQueue.Append( rvPair( (rvStat*)statDeath, deathBlock ) ); + + if( killer && killer->IsType( idPlayer::GetClassType() ) ) { + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, killer->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } else if( !killer ) { + // basically a suicide + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, victim->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } +} + +void rvStatManager::FlagCaptured( const idPlayer* player, int flagTeam ) { + int blockNum; + rvStatFlagCapture* stat = statAllocator.AllocStatFlagCapture( gameLocal.time, player->entityNumber, flagTeam, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::WeaponFired( const idPlayer* player, int weapon, int num ) { + playerStats[ player->entityNumber ].weaponShots[ weapon ] += num; + lastRailShotHits[ player->entityNumber ] = 0; + + comboKillState_t cks = comboKillState[ player->entityNumber ]; + comboKillState[ player->entityNumber ] = CKS_NONE; + if ( player->GetWeaponIndex( "weapon_rocketlauncher" ) == weapon ) { + if ( cks == CKS_NONE ) { + comboKillState[ player->entityNumber ] = CKS_ROCKET_FIRED; + } + } else if ( player->GetWeaponIndex( "weapon_railgun" ) == weapon ) { + // apparently it processes hits before it does the fire.... + if ( cks == CKS_RAIL_HIT ) { + comboKillState[ player->entityNumber ] = CKS_RAIL_FIRED; + } + } + +} + +void rvStatManager::WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if( victim && victim != attacker ) { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + // if attacker was a player, track hit and damage dealt + int hitBlock; + rvStatHit* statHit = statAllocator.AllocStatHit( gameLocal.time, attacker->entityNumber, victim->entityNumber, weapon, countForAccuracy, &hitBlock ); + + statQueue.Append( rvPair( (rvStat*)(statHit), hitBlock ) ); + } + } +} + +//asalmon modified to work for single player stats on Xenon +void rvStatManager::Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if(attacker) + { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + int damageBlock; + rvStatDamageDealt* statDamage = statAllocator.AllocStatDamageDealt( gameLocal.time, attacker->entityNumber, weapon, damage, &damageBlock ); + statQueue.Append( rvPair( (rvStat*)(statDamage), damageBlock ) ); + } + } + + if(victim) + { + if( victim->IsType( idPlayer::GetClassType() ) ) { + // if victim was a player, track damage taken + int blockNum; + rvStatDamageTaken* stat = statAllocator.AllocStatDamageTaken( gameLocal.time, victim->entityNumber, weapon, damage, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + } + } +} + +void rvStatManager::FlagDropped( const idPlayer* player, const idEntity* attacker ) { + int blockNum; + rvStatFlagDrop* stat = statAllocator.AllocStatFlagDrop( gameLocal.time, player->entityNumber, attacker->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} +void rvStatManager::FlagReturned( const idPlayer* player ) { + int blockNum; + rvStatFlagReturn* stat = statAllocator.AllocStatFlagReturn( gameLocal.time, player->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::DebugPrint( void ) { + if( !gameLocal.isMultiplayer ) { + return; + } + //gameLocal.Printf( "Begin statistics debug dump:\n" ); + + //gameLocal.Printf( "Statistics queue:\n" ); + for( int i = 0; i < Min( statQueue.Num(), 50 ); i++ ) { + gameLocal.Printf( "{%d, %d} ", statQueue[i].First()->GetType(), statQueue[i].First()->GetTimeStamp() ); + } + gameLocal.Printf("\n"); + + //gameLocal.Printf( "In-game statistics\n\n" ); + //for( int i = 0; i < inGameStats.Num(); i++ ) { + // gameLocal.Printf( "\t%d - %s:\n", i, statIndex->index[ i ].GetName().c_str() ); + // gameLocal.Printf( "\t\tKills: %d\n", inGameStats[i].kills ); + // gameLocal.Printf( "\t\tDeaths: %d\n", inGameStats[i].deaths ); + // gameLocal.Printf( "\t\tFlag Caps: %d\n", inGameStats[i].flagCaptures ); + + // for( int j = 0; j < MAX_WEAPONS; j++ ) { + // gameLocal.Printf( "\t\tWeapon %d hits: %d\n\t\tWeapon %d shots: %d\n", j, inGameStats[i].weaponHits[j], j, inGameStats[i].weaponShots[j] ); + // } + //} +} + +int rvStatManager::FreeEvents( int blockNum ) { + int blockStart = -1; + int blockEnd = -1; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( blockStart == -1 && statQueue[ i ].Second() == blockNum ) { + blockStart = i; + } else if( blockStart != -1 && statQueue[ i ].Second() != blockNum ) { + blockEnd = i; + break; + } + } + + if( blockStart == -1 || blockEnd == -1 ) { +// rjohnson: commented out warning - I take it this is not a bad message? +// gameLocal.Warning( "rvStatManager::FreeEvents() - Could not find events with block num '%d'\n", blockNum ); + return 0; + } + + statQueue.RemoveRange( blockStart, blockEnd - 1 ); + + return (blockEnd - blockStart); +} + +void rvStatManager::SendInGameAward( inGameAward_t award, int clientNum ) { + assert( gameLocal.isServer ); + + idBitMsg msg; + byte msgBuf[1024]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_RELIABLE_MESSAGE_INGAMEAWARD ); + + msg.WriteByte( award ); + msg.WriteByte( clientNum ); + + networkSystem->ServerSendReliableMessage( -1, msg ); + + if( gameLocal.isListenServer ) { + msg.ReadByte(); + ReceiveInGameAward( msg ); + } +} + + +void rvStatManager::ReceiveInGameAward( const idBitMsg& msg ) { + assert( gameLocal.isClient || gameLocal.isListenServer ); + int numAwards = 0; + + inGameAward_t award = (inGameAward_t)msg.ReadByte(); + int client = msg.ReadByte(); + + // display award on hud + idPlayer* player = gameLocal.GetLocalPlayer(); + idPlayer* remote = gameLocal.GetClientByNum(client); + bool justSound = false; + if( client != gameLocal.localClientNum ) { + justSound = true; + } + + if ( ( gameLocal.time - inGameAwardHudTime ) < 3000 || awardQueue.Num() > 0 ) { + if ( gameLocal.GetDemoFollowClient() == client || ( player && player->GetInstance() == remote->GetInstance() ) ) { + rvPair awardPair(award, justSound); + awardQueue.StackAdd(awardPair); + return; + } + } + + if( client == gameLocal.localClientNum ) { + // don't count awards during warmup + + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + localInGameAwards[ award ]++; + numAwards = localInGameAwards[ award ]; + } else { + numAwards = 1; + } + + if( player && player->mphud ) { + player->mphud->HandleNamedEvent( "clearIGA" ); + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, numAwards ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if( numAwards < 10 ) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, numAwards ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + } else { + player->mphud->SetStateInt( "ig_award_num", numAwards ); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + //inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + + if ( player ) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + else if ( player && remote && (player->GetInstance() == remote->GetInstance()) && (award == IGA_HOLY_SHIT || award == IGA_CAPTURE)) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + inGameAwardHudTime = gameLocal.time; + + idPlayer* awardee = gameLocal.GetClientByNum( client ); + if ( awardee ) { + if ( player && player->GetInstance() == awardee->GetInstance() ) { + iconManager->AddIcon( client, va( "mtr_award_%s", inGameAwardInfo[ award ].name ) ); + } + } +} + +void rvStatManager::CheckAwardQueue() { + + if(((gameLocal.time - inGameAwardHudTime) < 3000 || awardQueue.Num() == 0)) + { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int award = awardQueue.StackTop().First(); + bool justSound = awardQueue.StackTop().Second(); + awardQueue.StackPop(); + + if ( player ) { + if(!justSound || award == IGA_HOLY_SHIT || award == IGA_CAPTURE) + { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + if( player && player->mphud && !justSound) { + + player->mphud->HandleNamedEvent( "clearIGA" ); + localInGameAwards[ award ]++; + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, localInGameAwards[ award ] ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if(localInGameAwards[ award ] < 10) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, localInGameAwards[ award ] ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + + } + else { + player->mphud->SetStateInt( "ig_award_num", localInGameAwards[ award ]); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + +} + + +void rvStatManager::GivePlayerCashForAward( idPlayer* player, inGameAward_t award ) +{ + if( !player ) + return; + + if( !gameLocal.isMultiplayer ) + return; + + if( !gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + return; + + mpGameState_t mpGameState = gameLocal.mpGame.GetGameState()->GetMPGameState(); + if( mpGameState != GAMEON && mpGameState != SUDDENDEATH ) + return; + + const char* awardCashValueName = NULL; + switch( award ) + { + case IGA_CAPTURE: awardCashValueName = "playerCashAward_mpAward_capture"; break; + case IGA_HUMILIATION: awardCashValueName = "playerCashAward_mpAward_humiliation"; break; + case IGA_IMPRESSIVE: awardCashValueName = "playerCashAward_mpAward_impressive"; break; + case IGA_EXCELLENT: awardCashValueName = "playerCashAward_mpAward_excellent"; break; + case IGA_ASSIST: awardCashValueName = "playerCashAward_mpAward_assist"; break; + case IGA_DEFENSE: awardCashValueName = "playerCashAward_mpAward_defense"; break; + case IGA_COMBO_KILL: awardCashValueName = "playerCashAward_mpAward_combo_kill"; break; + case IGA_RAMPAGE: awardCashValueName = "playerCashAward_mpAward_rampage"; break; + case IGA_HOLY_SHIT: awardCashValueName = "playerCashAward_mpAward_holy_shit"; break; + } + + if( awardCashValueName ) + { + player->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( awardCashValueName, 0 ) ); + } +} + + +void rvStatManager::GiveInGameAward( inGameAward_t award, int clientNum ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ clientNum ]; + + if( gameLocal.isMultiplayer ) { + // show in-game awards during warmup, but don't actually let players accumulate them + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + playerStats[ clientNum ].inGameAwards[ award ]++; + GivePlayerCashForAward( player, award ); + } + SendInGameAward( award, clientNum ); + } +} + +void rvStatManager::SetupStatWindow( idUserInterface* statHud ) { + statWindow.SetupStatWindow( statHud ); +} + +void rvStatManager::SelectStatWindow( int selectionIndex, int selectionTeam ) { + statWindow.SelectPlayer( statWindow.ClientNumFromSelection( selectionIndex, selectionTeam ) ); +} + +int rvStatManager::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + return statWindow.GetSelectedClientNum( selectionIndexOut, selectionTeamOut ); +} + +void rvStatManager::UpdateInGameHud( idUserInterface* statHud, bool visible ) { + idPlayer* player = NULL; + + if( gameLocal.GetLocalPlayer() ) { + player = gameLocal.GetLocalPlayer(); + player->GetHud()->SetStateInt( "stat_visible", visible? 1 : 0); + } + + if( !visible ) { + statHud->SetStateInt( "stat_visible", 0 ); + return; + } else { + statHud->SetStateInt( "stat_visible", 1 ); + } + + if( player ) { + statWindow.SetupStatWindow( statHud, player->spectating ); + } +} + +void rvStatManager::SendStat( int toClient, int statClient ) { + if( statClient < 0 || statClient >= MAX_CLIENTS ) { + gameLocal.Warning( "rvStatManager::SendStat() - Stats requested for invalid client num '%d'\n", statClient ); + return; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( statClient ); + + playerStats[ statClient ].PackStats( outMsg ); + + networkSystem->ServerSendReliableMessage( toClient, outMsg ); +} + +void rvStatManager::ReceiveStat( const idBitMsg& msg ) { + //asalmon: added because this is used to restore single player stats from saves on Xbox 360 + if(gameLocal.IsMultiplayer()) + { + assert( gameLocal.isClient ); + } + + int client = msg.ReadByte(); + + playerStats[ client ].UnpackStats( msg ); + playerStats[ client ].lastUpdateTime = gameLocal.time; + + // display the updated stat + if(gameLocal.IsMultiplayer()) + { + statWindow.SelectPlayer( client ); + } +} + +void rvStatManager::SendAllStats( int clientNum, bool full ) { + + assert( gameLocal.isServer ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ALL_STATS ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = 0; + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( gameLocal.entities[ i ] ) { + sentClients |= 1 << i; + } + } + + outMsg.WriteBits( sentClients, MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].PackStats( outMsg ); + } + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + //common->Printf("SENT ALL STATS %i\n", Sys_Milliseconds()); + +} + + +void rvStatManager::ReceiveAllStats( const idBitMsg& msg ) { + assert( gameLocal.isClient ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = msg.ReadBits( MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].UnpackStats( msg ); + } else { + playerStats[ i ].Clear(); + } + + playerStats[ i ].lastUpdateTime = gameLocal.time; + } + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == GAMEREVIEW ) { + gameLocal.mpGame.ShowStatSummary(); + } +} + + +void rvStatManager::ClearStats( void ) { + // clear connected stats + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +#ifdef _XENON + lastFullUpdate = -50000; +#endif +} + +void rvStatManager::CalculateEndGameStats( void ) { + int maxKills = idMath::INT_MIN; + int maxKillPlayer = -1; + + int maxSuicides = idMath::INT_MIN; + int maxSuicidesPlayer = -1; + + int maxGauntletKills = idMath::INT_MIN; + int maxGauntletKillsPlayer = -1; + + float maxDamageKillsRatio = 0.0; + int maxDamageKillsRatioPlayer = -1; + +//Dump the stats to a log file + idFile *log = NULL; + if(cvarSystem->GetCVarBool("com_logMPStats")) + { + log = fileSystem->OpenFileAppend("StatisticsLog.txt"); + } + idStr toFile; + if ( !log ) { +// common->Warning("Statistics log will not be written\n"); + } + else + { + struct tm *newtime; + time_t aclock; + time( &aclock ); + newtime = localtime( &aclock ); + toFile = va("Match on map %s played on %s\n", gameLocal.GetMapName(), asctime( newtime )); + log->Write(toFile.c_str(), toFile.Length()); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( !gameLocal.entities[ i ] ) { + continue; + } + + if(log) + { + toFile = va("Statistics for %s\n", gameLocal.userInfo[ i ].GetString("ui_name")); + log->Write(toFile.c_str(), toFile.Length()); + toFile = va("Kills: %i Deaths: %i Suicides: %i\n", playerStats[ i ].kills, playerStats[ i ].deaths, playerStats[ i ].suicides); + log->Write(toFile.c_str(), toFile.Length()); + } + + gameLocal.Printf( "Calculating stats for client %d (%s)\n", i, gameLocal.userInfo[ i ].GetString("ui_name") ); + // overall accuracy award and sniper accuracy award + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + int railgunIndex = player->GetWeaponIndex( "weapon_railgun" ); + int rocketIndex = player->GetWeaponIndex( "weapon_rocketlauncher" ); + + float accuracyAverage = 0.0f; + int numAccuracies = 0; + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats[ i ].weaponShots[ j ] == 0 ) { + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s not used\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name"))); + log->Write(toFile.c_str(), toFile.Length()); + } + } + continue; + } + + float weaponAccuracy = (float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]; + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s: %i%%\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name")), (int)(((float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]) * 100.0f)); + log->Write(toFile.c_str(), toFile.Length()); + } + } + if( j == railgunIndex ) { + // sniper award + if( weaponAccuracy >= 0.9f && playerStats[ i ].weaponShots[ railgunIndex ] >= 10 ) { + playerStats[ i ].endGameAwards.Append( EGA_SNIPER ); + } + } + + accuracyAverage += weaponAccuracy; + numAccuracies++; + } + + + if ( numAccuracies && ( accuracyAverage / (float)numAccuracies >= 0.5f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ACCURACY ); + } + + + // rail master award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ railgunIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_RAIL_MASTER ); + } + + // rocket sauce award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ rocketIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ROCKET_SAUCE ); + } + + // critical failure award + if( playerStats[ i ].kills == 0 ) { + playerStats[ i ].endGameAwards.Append( EGA_CRITICAL_FAILURE ); + } + + // frags award +//asalmon: Made the limit more reasonable for the shorter time limits and less players of Xenon +#ifdef _XENON + if( playerStats[ i ].kills >= 50 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#else + if( playerStats[ i ].kills >= 100 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#endif + + + if( playerStats[ i ].kills > maxKills ) { + maxKills = playerStats[ i ].kills; + maxKillPlayer = i; + } + + if( playerStats[ i ].suicides > maxSuicides ) { + maxSuicides = playerStats[ i ].suicides; + maxSuicidesPlayer = i; + } + + if( playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ] > maxGauntletKills ) { + maxGauntletKills = playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ]; + maxGauntletKillsPlayer = i; + } + +//asalmon: Calculate the damage ratio: + if(playerStats[i].kills > 0) + { + playerStats[i].damageRatio = playerStats[i].damageGiven / playerStats[i].kills; + } + else + { + playerStats[i].damageRatio = playerStats[i].damageGiven; + } + + if( playerStats[ i ].damageRatio > maxDamageKillsRatio ) { + maxDamageKillsRatio = playerStats[ i ].damageRatio; + maxDamageKillsRatioPlayer = i; + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + } + + +//asalmon: hack to test certain achievement awards. +#ifdef _XENON + if(cvarSystem->GetCVarInteger("si_overrideFrags")) + { + common->Printf("Overriding Frags to: %i for player %i\n", cvarSystem->GetCVarInteger("si_overrideFrags"), i); + playerStats[i].kills = cvarSystem->GetCVarInteger("si_overrideFrags"); + } +#endif + + } + + + + // Perfect award + if( maxKillPlayer >= 0 && playerStats[ maxKillPlayer ].deaths == 0 ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ maxKillPlayer ]; + if( !gameLocal.IsTeamGame() || ( gameLocal.IsTeamGame() && player && gameLocal.mpGame.TeamLeader() == player->team ) ) { + playerStats[ maxKillPlayer ].endGameAwards.Append( EGA_PERFECT ); + } + } + + // Lemming award + if( maxSuicidesPlayer >= 0 && maxSuicides >= 5 ) { + playerStats[ maxSuicidesPlayer ].endGameAwards.Append( EGA_LEMMING ); + } + + // Brawler award + if( maxGauntletKillsPlayer >= 0 && maxGauntletKills >= 3 ) { + playerStats[ maxGauntletKillsPlayer ].endGameAwards.Append( EGA_BRAWLER ); + } + + // Team player award + if( maxDamageKillsRatioPlayer >= 0 && maxDamageKillsRatio > 500 ) { + playerStats[ maxDamageKillsRatioPlayer ].endGameAwards.Append( EGA_TEAM_PLAYER ); + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + log->Flush(); + fileSystem->CloseFile(log); + } +} + +void rvStatManager::GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ) { + memset( accuracyLeaders, -1, sizeof( int ) * MAX_WEAPONS ); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] == NULL ) { + continue; + } + + rvPlayerStat* playerStats = GetPlayerStat( i ); + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats->weaponShots[ j ] == 0 ) { + continue; + } + + float playerAccuracy = (float)playerStats->weaponHits[ j ] / (float)playerStats->weaponShots[ j ]; + float leaderAccuracy = -1.0f; + if( accuracyLeaders[ j ] != -1 ) { + rvPlayerStat* leaderStats = GetPlayerStat( accuracyLeaders[ j ] ); + if( leaderStats->weaponShots[ j ] != 0 ) { + leaderAccuracy = (float)leaderStats->weaponHits[ j ] / (float)leaderStats->weaponShots[ j ]; + } + } + if( playerAccuracy > leaderAccuracy ) { + accuracyLeaders[ j ] = i; + } + } + } +} + +int rvStatManager::DamageGiven( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_DEALT ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && (statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +int rvStatManager::DamageTaken( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_TAKEN ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && ( statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound ) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +rvStat* rvStatManager::GetLastClientStat( int clientNum, statType_t type, int time ) { + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + return statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ) { + int numFound = 0; + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + results[ numFound++ ] = statQueue[ i ].First(); + if( numFound >= num ) { + return; + } + } + } + } + + return; +} + + +rvStatTeam* rvStatManager::GetLastTeamStat( int team, statType_t type, int time ) { + assert( type > ST_STAT_TEAM ); + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( ((rvStatTeam*)statQueue[ i ].First())->GetTeam() == team ) { + return (rvStatTeam*)statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::SetupEndGameHud( idUserInterface* statHud ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = gameLocal.mpGame.GetRankedPlayer( i ); + + if( p && gameLocal.mpGame.IsInGame( p->entityNumber ) ) { + statHud->SetStateString( va( "player%d_name", i + 1 ), va( "%d. %s", i + 1, gameLocal.userInfo[ p->entityNumber ].GetString( "ui_name" ) ) ); + statHud->SetStateString( va( "player%d_score", i + 1 ), va( "%d", gameLocal.mpGame.GetScore( i ) ) ); + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 1 ); + statHud->SetStateInt( va( "player%d_team", i + 1 ), gameLocal.IsTeamGame() ? p->team : 0 ); + } else { + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 0 ); + } + } + statHud->HandleNamedEvent( "Setup" ); +} + +rvPlayerStat* rvStatManager::GetPlayerStat( int clientNum ) { + return &playerStats[ clientNum ]; +} + +void rvStatManager::UpdateEndGameHud( idUserInterface* statHud, int clientNum ) { + /*if( !endGameSetup ) { + // no info yet + SetupEndGameHud( statHud ); + endGameSetup = true; + } + + rvPlayerStat* clientStat = &(playerStats[ clientNum ]); + + statHud->HandleNamedEvent( "clear" ); + statHud->SetStateString( "stat_name", gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ); + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", (int)(clientStat->weaponAccuracy[ i ] * 100) ) ); + } + + // in-game awards + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < clientStat->inGameAwards.Num(); i++ ) { + igAwardCount[ clientStat->inGameAwards[ i ] ]++; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < clientStat->endGameAwards.Num(); i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); + statHud->SetStateString( va( "eg_award%d_text", i ), endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ); + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );*/ +} + +/* +=============================================================================== + + rvStatSummary + + Stores one player's summary information. Transmitted to clients for + intermission summary screen. + +=============================================================================== +*/ +rvPlayerStat::rvPlayerStat() { + Clear(); +} + +void rvPlayerStat::Clear( void ) { + memset( weaponShots, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponHits, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponKills, 0, sizeof(int) * MAX_WEAPONS ); + memset( inGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + + kills = deaths = suicides = lastUpdateTime = damageTaken = damageGiven = 0; + damageRatio = 0.0f; +} + +void rvPlayerStat::PackStats( idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponShots[ i ] ); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponHits[ i ] ); + } + + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + msg.WriteByte( inGameAwards[ i ] ); + } + + msg.WriteByte( endGameAwards.Num() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + msg.WriteByte( endGameAwards[ i ] ); + } + + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXDEATHS, deaths ), ASYNC_PLAYER_DEATH_BITS ); + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXKILLS, kills ), ASYNC_PLAYER_KILL_BITS ); +} + +void rvPlayerStat::UnpackStats( const idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponShots[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponHits[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + inGameAwards[ i ] = msg.ReadByte(); + } + + endGameAwards.SetNum( msg.ReadByte() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + endGameAwards[ i ] = (endGameAward_t)msg.ReadByte(); + } + + deaths = msg.ReadBits( ASYNC_PLAYER_DEATH_BITS ); + kills = msg.ReadBits( ASYNC_PLAYER_KILL_BITS ); +} diff --git a/source/game/mp/stats/StatManager.h b/source/game/mp/stats/StatManager.h new file mode 100644 index 0000000..35ede7a --- /dev/null +++ b/source/game/mp/stats/StatManager.h @@ -0,0 +1,349 @@ +//---------------------------------------------------------------- +// StatManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATMANAGER_H__ +#define __STATMANAGER_H__ + +#include "StatEvent.h" +#include "StatWindow.h" + +#ifndef ID_TRAFFICSTATS + #define ID_TRAFFICSTATS 0 +#endif + +/* +=============================================================================== + + Multiplayer game statistics + +=============================================================================== +*/ + +/* +================ +endGameAward_t + +End-game award IDs +================ +*/ +enum endGameAward_t { + EGA_INVALID = 0, + EGA_LEMMING, + EGA_RAIL_MASTER, + EGA_ROCKET_SAUCE, + EGA_BRAWLER, + EGA_SNIPER, + EGA_CRITICAL_FAILURE, + EGA_TEAM_PLAYER, + EGA_ACCURACY, + EGA_FRAGS, + EGA_PERFECT, + EGA_NUM_AWARDS +}; + +/* +================ +endGameAwardInfo_t + +end-game award info +================ +*/ +struct endGameAwardInfo_t { + char* name; +}; + +/* +================ +inGameAward_t + +in-game award IDs +================ +*/ +enum inGameAward_t { + IGA_INVALID = 0, + IGA_CAPTURE, + IGA_HUMILIATION, + IGA_IMPRESSIVE, + IGA_EXCELLENT, + IGA_ASSIST, + IGA_DEFENSE, + IGA_COMBO_KILL, + IGA_RAMPAGE, + IGA_HOLY_SHIT, + IGA_NUM_AWARDS +}; + +enum comboKillState_t { + CKS_NONE, + CKS_ROCKET_FIRED, + CKS_ROCKET_HIT, + CKS_RAIL_FIRED, + CKS_RAIL_HIT, +}; + +/* +================ +inGameAwardInfo_t + +in-game award info +================ +*/ +struct inGameAwardInfo_t { + char* name; +}; + +/* +================ +rvStatAllocator + +A simple block allocator for different stat objects. +Note: this isn't a real heap; it has no delete or tracking of free blocks. +It matches the memory usage of the rvStatManager which dumps its entire +memory all at once at very specific times. If that ever changes, this will +need to change. +================ +*/ + +const int BLOCK_SIZE = 1024; +const int MAX_BLOCKS = 128; + +class rvStatAllocator { + protected: + byte blocks[ BLOCK_SIZE * MAX_BLOCKS ]; + short currentBlock; // current block + size_t placeInBlock; // current position, offset from start of block + size_t totalBytesUsed; // total bytes handed out from the allocator + size_t totalAllocations; // total blocks handed out from the allocator + size_t totalBytesAllocated; // total bytes in all allocated blocks + size_t allocationsByType[ST_COUNT]; // one spare at [0]; allocations per type of event + + void *GetBlock( size_t blockSize, int* blockNumOut = NULL ); + + public: + rvStatAllocator(); + void Report(); + void Reset(); + + // object allocators + rvStatBeginGame *AllocStatBeginGame ( int t, int* blockNumOut = NULL ); + rvStatEndGame *AllocStatEndGame ( int t, int* blockNumOut = NULL ); + rvStatClientConnect *AllocStatClientConnect ( int t, int client, int* blockNumOut = NULL ); + rvStatHit *AllocStatHit ( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut = NULL ); + rvStatKill *AllocStatKill ( int t, int p, int v, bool g, int mod, int* blockNumOut = NULL ); + rvStatDeath *AllocStatDeath ( int t, int p, int mod, int* blockNumOut = NULL ); + rvStatDamageDealt *AllocStatDamageDealt ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatDamageTaken *AllocStatDamageTaken ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatFlagDrop *AllocStatFlagDrop ( int t, int p, int a, int tm, int* blockNumOut = NULL ); + rvStatFlagReturn *AllocStatFlagReturn ( int t, int p, int tm, int* blockNumOut = NULL ); + rvStatFlagCapture *AllocStatFlagCapture ( int t, int p, int f, int tm, int* blockNumOut = NULL ); + + // bookkeeping readers + ID_INLINE size_t GetBytesLeftInBlock() const { + assert( placeInBlock <= BLOCK_SIZE ); + return ( size_t )( BLOCK_SIZE - placeInBlock ); + } + + ID_INLINE size_t GetPlaceInBlock() const { + return placeInBlock; + } + + ID_INLINE size_t GetTotalBytesUsed() const { + return totalBytesUsed; + } + + ID_INLINE size_t GetTotalAllocations() const { + return totalAllocations; + } + + ID_INLINE size_t GetAllocationsByType( statType_t type ) const { + return allocationsByType[ type ]; + } + + ID_INLINE size_t GetTotalBytesAllocated() const { + return totalBytesAllocated; + } +}; + + +/* +================ +rvPlayerStat + +Stores one player's stat information. +================ +*/ +class rvPlayerStat { +public: + rvPlayerStat(); + + void Clear( void ); + void CalculateStats( int p, const idList& playerStats ); + + void PackStats( idBitMsg& msg ); + void UnpackStats( const idBitMsg& msg ); + + int weaponShots[ MAX_WEAPONS ]; + int weaponHits[ MAX_WEAPONS ]; + + int weaponKills[ MAX_WEAPONS ]; + + int kills; + int deaths; + int suicides; + float damageRatio; + +// asalmon: added for Xenon +// ddynerman: also used on PC + int damageGiven; + int damageTaken; + + idList endGameAwards; +// asalmon: changed this to just an array of counts + int inGameAwards[IGA_NUM_AWARDS]; + + int lastUpdateTime; +}; + +/* +================ +rvStatManager + +The statistics management interface. This is the entrypoint into +statistics from the game. + +The game calls into rvStatManager at appropriate times (i.e. 'FlagCaptured') + +rvStatManager creates stat events (see StatEvent.h) with the appropriate +information and append them to the statQueue. + +The statQueue is parsed at the end of a game to tabulate statistics, give +end-game awards. + +================ +*/ +class rvStatManager { +public: + rvStatManager(); + + void Init( void ); + void Shutdown( void ); + + // generic events + void BeginGame( void ); + void EndGame( void ); + void ClientConnect( int clientNum ); + void ClientDisconnect( int clientNum ); + void WeaponFired( const idPlayer* player, int weapon, int num ); + void WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy = true ); + void Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ); + + + // ctf-specific events + void FlagCaptured( const idPlayer* player, int flagTeam ); + void FlagDropped( const idPlayer* player, const idEntity* attacker ); + void FlagReturned( const idPlayer* player ); + + + // shared events + void Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ); + + void DebugPrint( void ); + + rvPlayerStat* GetPlayerStats( void ) { return playerStats; }; + rvPlayerStat* GetPlayerStat( int clientNum ); + + void CalculateEndGameStats( void ); + + void GivePlayerCashForAward( idPlayer* player, inGameAward_t award ); + void GiveInGameAward( inGameAward_t award, int clientNum ); + + // network transmission + void SendStat( int toClient, int statClient ); + void ReceiveStat( const idBitMsg& msg ); + + void SendInGameAward( inGameAward_t award, int clientNum ); + void ReceiveInGameAward( const idBitMsg& msg ); + void CheckAwardQueue(); + + void ClearStats( void ); + + int DamageGiven( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + int DamageTaken( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + + void UpdateInGameHud( idUserInterface* statHud, bool visible ); + void UpdateEndGameHud( idUserInterface* statHud, int clientNum ); + void SetupEndGameHud( idUserInterface* statHud ); + + rvStat* GetLastClientStat( int clientNum, statType_t type, int time ); + void GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ); + rvStatTeam* GetLastTeamStat( int team, statType_t type, int time ); + rvStat* GetStat( int i ); + + void GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ); + + void SetupStatWindow( idUserInterface* statHud ); + void SelectStatWindow( int selectionIndex, int selectionTeam ); + int GetSelectedClientNum( int* selectionIndexOut = NULL, int* selectionTeamOut = NULL ); + + //asalmon: Sends all stats to all clients. For Xenon periodic update of stats. + void SendAllStats( int clientNum = -1, bool full = true ); + void ReceiveAllStats( const idBitMsg& msg ); + + void SetDamageGiven(int client, int damage) { playerStats[client].damageGiven = damage; } + void SetDamageTaken(int client, int damage) { playerStats[client].damageTaken = damage; } + + int FreeEvents( int blockNum ); + + static comboKillState_t comboKillState[ MAX_CLIENTS ]; + static int lastRailShot[ MAX_CLIENTS ]; + static int lastRailShotHits[ MAX_CLIENTS ]; + +private: + idList > statQueue; + idList > awardQueue; +// idList inGameStats; +// rvStatInGame previousInGameStats[ MAX_CLIENTS ]; +// rvStatInGame localClientInGameStats; + + // local hud support + int localInGameAwards[ IGA_NUM_AWARDS ]; + int inGameAwardHudTime; + + rvPlayerStat playerStats[ MAX_CLIENTS ]; + + bool endGameSetup; + + rvStatWindow statWindow; + + // shouchard: stat allocator to avoid lots of little news + rvStatAllocator statAllocator; + +#ifdef _XENON + int lastFullUpdate; +#endif + +#if ID_TRAFFICSTATS + int startSent; + int startPacketsSent; + int startReceived; + int startPacketsReceived; + int startTime; +#endif +}; + +ID_INLINE rvStat* rvStatManager::GetStat( int i ) { + if( i < 0 || i >= statQueue.Num() ) { + return NULL; + } + + return statQueue[ i ].First(); +} + +extern rvStatManager* statManager; +extern inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ]; +extern endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ]; + +#endif /* !__STATMANAGER_H__ */ diff --git a/source/game/mp/stats/StatWindow.cpp b/source/game/mp/stats/StatWindow.cpp new file mode 100644 index 0000000..e7fce29 --- /dev/null +++ b/source/game/mp/stats/StatWindow.cpp @@ -0,0 +1,462 @@ +//---------------------------------------------------------------- +// StatWindow.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" + +#include "StatWindow.h" +#include "StatManager.h" + +/* +================ +rvStatWindow::rvStatWindow() +================ +*/ +rvStatWindow::rvStatWindow() { + statHud = NULL; +} + +/* +================ +rvStatWindow::SetupStatWindow() + +Sets up a selectable window of current stats +================ +*/ +void rvStatWindow::SetupStatWindow( idUserInterface* hud, bool useSpectator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + assert( player ); + if ( !player ) { + return; + } + +// mekberg: added + idList marineScores; + idList stroggScores; + + int selectionIndex = -1; + int selectionTeam = -1; + + statHud = hud; + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + stroggPlayers.Clear(); + marinePlayers.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + idPlayer* player = gameLocal.mpGame.GetRankedPlayer( i ); + + if( player->team == TEAM_MARINE ) { + marinePlayers.Append( player ); + marineScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } else if ( player->team == TEAM_STROGG ) { + stroggPlayers.Append( player ); + stroggScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < marinePlayers.Num() ) { + statHud->SetStateString( va( "team_1_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( marinePlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( marinePlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + marinePlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + marineScores[ i ] ) ); + + if( useSpectator ) { + if( marinePlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } else { + if( marinePlayers[ i ] == player ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_1_names_item_%d", i ), "" ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < stroggPlayers.Num() ) { + statHud->SetStateString( va( "team_2_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( stroggPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( stroggPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + stroggPlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + stroggScores[ i ] ) ); + if( useSpectator ) { + if( stroggPlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } else { + if( stroggPlayers[ i ] == player ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_2_names_item_%d", i ), "" ); + } + } + + statHud->SetStateInt ( "num_strogg_players", stroggPlayers.Num() ); + statHud->SetStateInt ( "num_marine_players", marinePlayers.Num() ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + + players.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + players.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < players.Num() ) { + statHud->SetStateString( va( "dm_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( players[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( players[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + players[ i ]->GetUserInfo()->GetString( "ui_name"), + + gameLocal.mpGame.GetScore( players[ i ] ) ) ); + if( useSpectator ) { + if( players[ i ]->entityNumber == player->spectator ) { + selectionTeam = 0; + selectionIndex = i; + } + } else { + if( players[ i ] == player ) { + selectionTeam = 0; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "dm_names_item_%d", i ), "" ); + } + } + statHud->SetStateInt ( "num_players", players.Num() ); + } + + // spectators + spectators.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumUnrankedPlayers(); i++ ) { + spectators.Append( gameLocal.mpGame.GetUnrankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < spectators.Num() ) { + statHud->SetStateString( va( "spec_names_item_%d", i ), + va( "%s\t%s\t%s\t", ( player->IsFriend( spectators[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( spectators[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + spectators[ i ]->GetUserInfo()->GetString( "ui_name") ) ); + + if( useSpectator ) { + if( spectators[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } else { + if( spectators[ i ] == player ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "spec_names_item_%d", i ), "" ); + + } + } + + statHud->SetStateInt( "gametype", gameLocal.gameType ); + statHud->SetStateInt( "playerteam", gameLocal.GetLocalPlayer()->team ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); + + // we shouldn't ever draw a hud unless we're in-game + assert( selectionIndex >= 0 && selectionTeam >= 0 ); + + + statManager->SelectStatWindow( selectionIndex, selectionTeam ); +} + +/* +================ +rvStatWindow::ClearWindow() + +Clears the stat part of the stat window, but not the player lists boxes +================ +*/ +void rvStatWindow::ClearWindow( void ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), "" ); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, "" ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 0 ); + statHud->SetStateString( va( "eg_award%d_text", i ), "" ); + } + + // kills + statHud->SetStateString( "stat_frags", "" ); + + // deaths + statHud->SetStateString( "stat_deaths", "" ); + + // score + statHud->SetStateString( "stat_score", "" ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + + +/* +================ +rvStatWindow::SelectPlayer() + +Selects the specified player +================ +*/ +void rvStatWindow::SelectPlayer( int clientNum ) { + if( statHud == NULL ) { + return; + } + + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + ClearWindow(); + return; + } + + assert( gameLocal.GetLocalPlayer() ); + + rvPlayerStat* clientStat = statManager->GetPlayerStat( clientNum ); + + if( gameLocal.isClient && ( clientStat == NULL || ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) ) { + // get new stats + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + int weaponAccuracy = 0; + if( clientStat->weaponShots[ i ] != 0 ) { + weaponAccuracy = (int)(((float)clientStat->weaponHits[ i ] / (float)clientStat->weaponShots[ i ]) * 100.0f); + } + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", weaponAccuracy ) ); + } + + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + igAwardCount[i] = clientStat->inGameAwards[i]; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + if( i < clientStat->endGameAwards.Num() ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); +// RAVEN BEGIN +// rhummer: Localized the award strings.. + statHud->SetStateString( va( "eg_award%d_text", i ), common->GetLocalizedString( endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ) ); +// RAVEN END + } else { + statHud->SetStateInt( va( "eg_award%d", i), 0 ); + } + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) ); + + // score + int score = gameLocal.IsTeamGame() ? (gameLocal.mpGame.GetTeamScore( clientNum ) + gameLocal.mpGame.GetScore( clientNum )): gameLocal.mpGame.GetScore( clientNum ); + statHud->SetStateString( "stat_score", va( "%d", score ) ); + + if( gameLocal.GetLocalPlayer()->IsFriend( clientNum ) ) { + // remove friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200249" ) ); + } else { + // add friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200248" ) ); + } + + if( gameLocal.GetLocalPlayer()->IsPlayerMuted( clientNum ) ) { + // unmute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200251" ) ); + } else { + // mute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200250" ) ); + } + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + +/* +================ +rvStatWindow::ClientNumFromSelection() + +Parses a selection index and team into a clientNum +================ +*/ +int rvStatWindow::ClientNumFromSelection( int selectionIndex, int selectionTeam ) { + int clientNum = -1; + + if( gameLocal.IsTeamGame() ) { + if( selectionTeam == TEAM_MARINE ) { + if( selectionIndex < 0 || selectionIndex >= marinePlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = marinePlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_1_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else if( selectionTeam == TEAM_STROGG ) { + if( selectionIndex < 0 || selectionIndex >= stroggPlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = stroggPlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_2_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } else { + if( selectionTeam == TEAM_MAX ) { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= players.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = players[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "dm_names_sel_0", selectionIndex ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } + + return clientNum; +} + +/* +================ +rvStatWindow::GetSelectedClientNum() + +Queries the gui dict to figure out the currently selected selectionIndex/selectionTeam. +Uses the selectionIndex/selectionTeam to return the selected client num. +================ +*/ +int rvStatWindow::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + if( statHud == NULL ) { + return -1; + } + int selectionIndex = -1; + int selectionTeam = -1; + + // StatWindow update code should assure that only one selection of all these listDefs is is valid + // Find the valid one + if( statHud->State().GetInt( "spec_names_sel_0", "-1" ) >= 0 ) { + selectionIndex = statHud->State().GetInt( "spec_names_sel_0", "-1" ); + selectionTeam = TEAM_MAX; + } + + if ( statHud->State().GetInt( "dm_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "dm_names_sel_0", "-1" ); + selectionTeam = 0; + } + + if ( statHud->State().GetInt( "team_1_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_1_names_sel_0", "-1" ); + selectionTeam = TEAM_MARINE; + } + + if ( statHud->State().GetInt( "team_2_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_2_names_sel_0", "-1" ); + selectionTeam = TEAM_STROGG; + } + + // return the selection index & team + if( selectionIndexOut ) { + (*selectionIndexOut) = selectionIndex; + } + if( selectionTeamOut ) { + (*selectionTeamOut) = selectionTeam; + } + + return ClientNumFromSelection( selectionIndex, selectionTeam ); +} diff --git a/source/game/mp/stats/StatWindow.h b/source/game/mp/stats/StatWindow.h new file mode 100644 index 0000000..f4e9263 --- /dev/null +++ b/source/game/mp/stats/StatWindow.h @@ -0,0 +1,36 @@ +//---------------------------------------------------------------- +// StatWindow.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATWINDOW_H__ +#define __STATWINDOW_H__ + +/* +=============================================================================== + +Stat selection window + +=============================================================================== +*/ + +class rvStatWindow { +public: + rvStatWindow(); + void SetupStatWindow( idUserInterface* statHud, bool useSpectator = false ); + void SelectPlayer( int clientNum ); + int ClientNumFromSelection( int selectionIndex, int selectionTeam ); + void ClearWindow( void ); + int GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ); +private: + idList stroggPlayers; + idList marinePlayers; + idList players; + idList spectators; + + idUserInterface* statHud; +}; + + +#endif diff --git a/source/game/physics/Clip.cpp b/source/game/physics/Clip.cpp new file mode 100644 index 0000000..072bddf --- /dev/null +++ b/source/game/physics/Clip.cpp @@ -0,0 +1,2167 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_SECTOR_DEPTH 12 +#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1) + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +typedef struct clipSector_s { + int contents; + int dynamicContents; + struct clipLink_s * clipLinks; +} clipSector_t; +// RAVEN END + +typedef struct clipLink_s { + idClipModel * clipModel; + struct clipSector_s * sector; + struct clipLink_s * prevInSector; + struct clipLink_s * nextInSector; + struct clipLink_s * nextLink; +} clipLink_t; + +typedef struct trmCache_s { + idTraceModel trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; + const idMaterial * material; + idCollisionModel * collisionModel; + int hash; // only used to identify non-hashed trm's in a save. +} trmCache_t; + +idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON ); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc clipLinkAllocator; +// RAVEN END + +typedef enum { + CPT_NONE = -1, + CPT_TRANSLATION, + CPT_CONTACTS, + CPT_CONTENTS, + + CPT_MAX_TYPES +} CLIP_PROFILE_TYPES; + +#if 0 + +static idTimer clipProfileTimer[ CPT_MAX_TYPES ]; +static CLIP_PROFILE_TYPES currentProfileType = CPT_NONE; +static int clipProfileDepth = 0; + +static void BeginClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth++; + + if ( clipProfileDepth == 1 ) { + clipProfileTimer[ type ].Start(); + currentProfileType = type; + } +} + +static void EndClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth--; + + if ( clipProfileDepth == 0 ) { + clipProfileTimer[ currentProfileType ].Stop(); + currentProfileType = CPT_NONE; + } +} + +void ClearClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + clipProfileTimer[ i ].Clear(); + } +} + +void DisplayClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + common->Printf( "%d:%d ", i, ( int )clipProfileTimer[ i ].Milliseconds() ); + } + common->Printf( "\n" ); +} +#else + +#define BeginClipProfile( type ) + +#define EndClipProfile( type ) + +#endif + + +/* +=============================================================== + + idClipModel trace model cache + +=============================================================== +*/ + +static idList traceModelCache; +static idHashIndex traceModelHash; + +/* +=============== +idClipModel::ClearTraceModelCache +=============== +*/ +void idClipModel::ClearTraceModelCache( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + collisionModelManager->FreeModel( traceModelCache[i]->collisionModel ); + traceModelCache[i]->collisionModel = NULL; + } + traceModelCache.DeleteContents( true ); + traceModelHash.Free(); +} + +/* +=============== +idClipModel::CacheCollisionModels +=============== +*/ +void idClipModel::CacheCollisionModels( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + if ( traceModelCache[i]->collisionModel == NULL ) { + traceModelCache[i]->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", i ), traceModelCache[i]->trm, traceModelCache[i]->material ); + } + } +} + +/* +=============== +idClipModel::TraceModelCacheSize +=============== +*/ +int idClipModel::TraceModelCacheSize( void ) { + return traceModelCache.Num() * sizeof( idTraceModel ); +} + +/* +=============== +idClipModel::AllocTraceModel +=============== +*/ +int idClipModel::AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + int i, hashKey, traceModelIndex; + trmCache_t *entry; + + if ( notHashed ) { + hashKey = 0xffffffff; + } else { + hashKey = GetTraceModelHashKey( trm ); + + for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) { + if ( traceModelCache[i]->trm == trm ) { + traceModelCache[i]->refCount++; + return i; + } + } + } + + + entry = new trmCache_t; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->material = material; + entry->hash = hashKey; + traceModelIndex = traceModelCache.Append( entry ); + + if ( !notHashed ) { + traceModelHash.Add( hashKey, traceModelIndex ); + } + + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", traceModelIndex ), trm, material ); + + return traceModelIndex; +} + +/* +=============== +idClipModel::Replace +=============== +*/ +void idClipModel::ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed ) { + common->Error( "ReplaceTraceModel was misused. Replace can only be used on non-hashed models right now.\n" ); + return; + } + + trmCache_t *entry = traceModelCache[ index ]; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->hash = 0xffffffff; + entry->material = material; + + if(entry->collisionModel) + { + collisionModelManager->FreeModel( entry->collisionModel ); + } + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", index ), trm, material ); +} + +/* +=============== +idClipModel::FreeTraceModel +=============== +*/ +void idClipModel::FreeTraceModel( int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache[traceModelIndex]->refCount--; +} + +/* +=============== +idClipModel::CopyTraceModel +=============== +*/ +int idClipModel::CopyTraceModel( const int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::CopyTraceModel: tried to copy an uncached trace model" ); + return -1; + } + traceModelCache[traceModelIndex]->refCount++; + return traceModelIndex; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) { + return &traceModelCache[traceModelIndex]->trm; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idCollisionModel *idClipModel::GetCachedCollisionModel( int traceModelIndex ) { + return traceModelCache[traceModelIndex]->collisionModel; +} + +/* +=============== +idClipModel::GetTraceModelHashKey +=============== +*/ +int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) { + const idVec3 &v = trm.bounds[0]; + return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() ); +} + +/* +=============== +idClipModel::SaveTraceModels +=============== +*/ +void idClipModel::SaveTraceModels( idSaveGame *savefile ) { + int i; + + savefile->WriteInt( traceModelCache.Num() ); + for ( i = 0; i < traceModelCache.Num(); i++ ) { + trmCache_t *entry = traceModelCache[i]; + + savefile->Write( &entry->trm, sizeof( entry->trm ) ); + savefile->WriteFloat( entry->volume ); + savefile->WriteVec3( entry->centerOfMass ); + savefile->WriteMat3( entry->inertiaTensor ); + savefile->WriteMaterial( entry->material ); + savefile->WriteInt( entry->hash ); + } +} + +/* +=============== +idClipModel::RestoreTraceModels +=============== +*/ +void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) { + int i, num; + + ClearTraceModelCache(); + + savefile->ReadInt( num ); + traceModelCache.SetNum( num ); + for ( i = 0; i < num; i++ ) { + trmCache_t *entry = new trmCache_t; + + savefile->Read( &entry->trm, sizeof( entry->trm ) ); + savefile->ReadFloat( entry->volume ); + savefile->ReadVec3( entry->centerOfMass ); + savefile->ReadMat3( entry->inertiaTensor ); + savefile->ReadMaterial( entry->material ); + savefile->ReadInt( entry->hash ); + + entry->refCount = 0; + entry->collisionModel = NULL; + + traceModelCache[i] = entry; + if ( entry->hash != 0xffffffff ) { + traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i ); + } + } + + CacheCollisionModels(); +} + + +/* +=============================================================== + + idClipModel + +=============================================================== +*/ + +/* +================ +idClipModel::FreeModel +================ +*/ +void idClipModel::FreeModel( void ) { + + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } + + if ( collisionModel != NULL ) { + collisionModelManager->FreeModel( collisionModel ); + collisionModel = NULL; + } + + renderModelHandle = -1; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +================ +idClipModel::UpdateDynamicContents +================ +*/ +void idClipModel::UpdateDynamicContents( void ) { + idClip::UpdateDynamicContents( this ); +} +// RAVEN END + +/* +================ +idClipModel::LoadModel +================ +*/ +bool idClipModel::LoadModel( const char *name ) { + FreeModel(); + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), name ); + if ( collisionModel != NULL ) { + collisionModel->GetBounds( bounds ); + collisionModel->GetContents( contents ); + return true; + } else { + bounds.Zero(); + return false; + } +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed || traceModelIndex == -1 ) { + FreeModel(); + traceModelIndex = AllocTraceModel( trm, material, notHashed ); + } else { + ReplaceTraceModel( traceModelIndex, trm, material, notHashed ); + } + + bounds = trm.bounds; +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const int renderModelHandle ) { + FreeModel(); + this->renderModelHandle = renderModelHandle; + if ( renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + bounds = renderEntity->bounds; + } + } +} + +/* +================ +idClipModel::Init +================ +*/ +void idClipModel::Init( void ) { + enabled = true; + entity = NULL; + id = 0; + owner = NULL; + origin.Zero(); + axis.Identity(); + bounds.Zero(); + absBounds.Zero(); + contents = CONTENTS_BODY; + collisionModel = NULL; + renderModelHandle = -1; + traceModelIndex = -1; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( void ) { + Init(); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const char *name ) { + Init(); + LoadModel( name ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm, const idMaterial *material ) { + Init(); + LoadModel( trm, material ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const int renderModelHandle ) { + Init(); + contents = CONTENTS_RENDERMODEL; + LoadModel( renderModelHandle ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idClipModel *model ) { + enabled = model->enabled; + entity = model->entity; + id = model->id; + owner = model->owner; + origin = model->origin; + axis = model->axis; + bounds = model->bounds; + absBounds = model->absBounds; + contents = model->contents; + collisionModel = NULL; + if ( model->collisionModel != NULL ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), model->collisionModel->GetName() ); + } + traceModelIndex = -1; + if ( model->traceModelIndex != -1 ) { + traceModelIndex = CopyTraceModel( model->traceModelIndex ); + } + renderModelHandle = model->renderModelHandle; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::~idClipModel +================ +*/ +idClipModel::~idClipModel( void ) { + // make sure the clip model is no longer linked + Unlink(); + FreeModel(); +} + +/* +================ +idClipModel::Save +================ +*/ +void idClipModel::Save( idSaveGame *savefile ) const { + savefile->WriteBool( enabled ); + savefile->WriteObject( entity ); + savefile->WriteInt( id ); + savefile->WriteObject( owner ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); + savefile->WriteBounds( bounds ); + savefile->WriteBounds( absBounds ); + savefile->WriteInt( contents ); + if ( collisionModel != NULL ) { + savefile->WriteString( collisionModel->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteInt( traceModelIndex ); + savefile->WriteInt( renderModelHandle ); + savefile->WriteBool( clipLinks != NULL ); + savefile->WriteInt( touchCount ); + + savefile->WriteBool ( checked ); // cnicholson: Added unsaved var +} + +/* +================ +idClipModel::Restore +================ +*/ +void idClipModel::Restore( idRestoreGame *savefile ) { + idStr collisionModelName; + bool linked; + + savefile->ReadBool( enabled ); + savefile->ReadObject( reinterpret_cast( entity ) ); + savefile->ReadInt( id ); + savefile->ReadObject( reinterpret_cast( owner ) ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadBounds( bounds ); + savefile->ReadBounds( absBounds ); + savefile->ReadInt( contents ); + savefile->ReadString( collisionModelName ); + if ( collisionModelName.Length() ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), collisionModelName ); + } else { + collisionModel = NULL; + } + savefile->ReadInt( traceModelIndex ); + if ( traceModelIndex >= 0 ) { + traceModelCache[traceModelIndex]->refCount++; + } + savefile->ReadInt( renderModelHandle ); + savefile->ReadBool( linked ); + savefile->ReadInt( touchCount ); + + savefile->ReadBool ( checked ); // cnicholson: Added unrestored var + + // the render model will be set when the clip model is linked + renderModelHandle = -1; + clipLinks = NULL; + touchCount = -1; + + if ( linked ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Link( entity, id, origin, axis, renderModelHandle ); +// RAVEN END + } +} + +/* +================ +idClipModel::SetPosition +================ +*/ +void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) { + if ( clipLinks ) { + Unlink(); // unlink from old position + } + origin = newOrigin; + axis = newAxis; +} + +/* +================ +idClipModel::GetCollisionModel +================ +*/ +idCollisionModel * idClipModel::GetCollisionModel( void ) const { + assert( renderModelHandle == -1 ); + if ( collisionModel != NULL ) { + return collisionModel; + } else if ( traceModelIndex != -1 ) { + return GetCachedCollisionModel( traceModelIndex ); + } else { + // this happens in multiplayer on the combat models + if ( entity ) { + gameLocal.Warning( "idClipModel::GetCollisionModel: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber ); + } + return 0; + } +} + +/* +================ +idClipModel::GetMassProperties +================ +*/ +void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + if ( traceModelIndex == -1 ) { + gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() ); + } + + trmCache_t *entry = traceModelCache[traceModelIndex]; + mass = entry->volume * density; + centerOfMass = entry->centerOfMass; + inertiaTensor = density * entry->inertiaTensor; +} + +/* +=============== +idClipModel::Unlink +=============== +*/ +void idClipModel::Unlink( void ) { + clipLink_t *link; + + for ( link = clipLinks; link; link = clipLinks ) { + clipLinks = link->nextLink; + if ( link->prevInSector ) { + link->prevInSector->nextInSector = link->nextInSector; + } else { + link->sector->clipLinks = link->nextInSector; + } + if ( link->nextInSector ) { + link->nextInSector->prevInSector = link->prevInSector; + } +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idClip::UpdateDynamicContents( link->sector ); +// RAVEN END + + clipLinkAllocator.Free( link ); + } +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( void ) { + + assert( idClipModel::entity ); + if ( !idClipModel::entity ) { + return; + } + + idClip* clp = gameLocal.GetEntityClipWorld( idClipModel::entity ); + + if ( clipLinks ) { + Unlink(); // unlink from old position + } + + if ( bounds.IsCleared() ) { + return; + } + + // set the abs box + if ( axis.IsRotated() ) { + // expand for rotation + absBounds.FromTransformedBounds( bounds, origin, axis ); + } else { + // normal + absBounds[0] = bounds[0] + origin; + absBounds[1] = bounds[1] + origin; + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + absBounds[0] -= vec3_boxEpsilon; + absBounds[1] += vec3_boxEpsilon; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + clp->CoordsForBounds( coords, absBounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clp->clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + sector->dynamicContents |= GetContents(); + + clipLink_t* link = clipLinkAllocator.Alloc(); + link->clipModel = this; + link->sector = sector; + link->nextInSector = sector->clipLinks; + link->prevInSector = NULL; + if ( sector->clipLinks ) { + sector->clipLinks->prevInSector = link; + } + sector->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; + } + } +// RAVEN END +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) { + this->entity = ent; + this->id = newId; + this->origin = newOrigin; + this->axis = newAxis; + if ( renderModelHandle != -1 ) { + this->renderModelHandle = renderModelHandle; + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + this->bounds = renderEntity->bounds; + } + } + this->Link(); +} +// RAVEN END + + +/* +=============================================================== + + idClip + +=============================================================== +*/ + +// RAVEN BEGIN +// ddynerman: change to static +idClipModel idClip::defaultClipModel; + +idClipModel *idClip::DefaultClipModel( void ) { + // initialize a default clip model + if( defaultClipModel.traceModelIndex == -1 ) { + defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ), NULL ); + } + + return &defaultClipModel; +} + +void idClip::FreeDefaultClipModel( void ) { + if ( defaultClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex ); + defaultClipModel.traceModelIndex = -1; + } +} +// RAVEN END + +/* +=============== +idClip::idClip +=============== +*/ +idClip::idClip( void ) { + clipSectors = NULL; + world = NULL; + worldBounds.Zero(); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::CreateClipSectors_r + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) { +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if( clipSectors ) { + delete[] clipSectors; + clipSectors = NULL; + } + nodeOffsetVisual = bounds[ 0 ]; + + int i; + for( i = 0; i < 3; i++ ) { +//jshepard: this crashes too often +#ifdef _DEBUG + if( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ) { + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + } else { + gameLocal.Error("zero size bounds while creating clipsectors"); + nodeScale[ i ] = depth; + } + if( nodeScale[ i ] ) { + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + } else { + gameLocal.Error("zero size nodeScale while creating clipsectors"); + nodeOffset[ i] = nodeOffset[ i] + 0.5f; + } +#else + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + +#endif + } + + clipSectors = new clipSector_t[ Square( depth ) ]; + memset( clipSectors, 0, Square( depth ) * sizeof( clipSector_t ) ); + return clipSectors; +// RAVEN END +} + +/* +=============== +idClip::Init +=============== +*/ +void idClip::Init( void ) { + idVec3 size, maxSector = vec3_origin; + + // get world map bounds + world = collisionModelManager->LoadModel( gameLocal.GetMapName(), WORLD_MODEL_NAME ); + world->GetBounds( worldBounds ); + + // create world sectors +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// ddynerman: SD's clip sector code + CreateClipSectors_r( CLIPSECTOR_WIDTH, worldBounds, maxSector ); + GetClipSectorsStaticContents(); +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + size = worldBounds[1] - worldBounds[0]; + gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] ); + gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] ); + + // set counters to zero + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::Shutdown +=============== +*/ +void idClip::Shutdown( void ) { + delete[] clipSectors; + clipSectors = NULL; + + // free the trace model used for the temporaryClipModel + if ( temporaryClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex ); + temporaryClipModel.traceModelIndex = -1; + } + + clipLinkAllocator.Shutdown(); +} + +/* +==================== +idClip::ClipModelsTouchingBounds_r +==================== +*/ +typedef struct listParms_s { + idBounds bounds; + int contentMask; + idClipModel ** list; + int count; + int maxCount; +} listParms_t; + + +/* +================ +idClip::ClipModelsTouchingBounds +================ +*/ +int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + listParms_t parms; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int clipCount = 0; + static idClipModel* clipModels[ MAX_GENTITIES ]; + + assert( maxCount <= MAX_GENTITIES ); + if( maxCount > MAX_GENTITIES ) { + maxCount = MAX_GENTITIES; + } +// RAVEN END + + if ( bounds[0][0] > bounds[1][0] || + bounds[0][1] > bounds[1][1] || + bounds[0][2] > bounds[1][2] ) { + // we should not go through the tree for degenerate or backwards bounds + assert( false ); + return 0; + } + + parms.bounds[0] = bounds[0] - vec3_boxEpsilon; + parms.bounds[1] = bounds[1] + vec3_boxEpsilon; + parms.contentMask = contentMask; + parms.list = clipModelList; + parms.count = 0; + parms.maxCount = maxCount; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + CoordsForBounds( coords, parms.bounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + if( !( sector->dynamicContents & contentMask ) ) { + continue; + } + + for ( clipLink_t* link = sector->clipLinks; link && clipCount < MAX_GENTITIES; link = link->nextInSector ) { + idClipModel* model = link->clipModel; + + if( model->checked || !model->enabled || !( model->GetContents() & contentMask ) ) { + continue; + } + + model->checked = true; + clipModels[ clipCount++ ] = model; + } + } + } + + for( x = 0; x < clipCount; x++ ) { + clipModels[ x ]->checked = false; + } + + for( x = 0; x < clipCount; x++ ) { + idClipModel* model = clipModels[ x ]; + + // if the bounds really do overlap + if ( model->absBounds[0].x > parms.bounds[1].x || + model->absBounds[1].x < parms.bounds[0].x || + model->absBounds[0].y > parms.bounds[1].y || + model->absBounds[1].y < parms.bounds[0].y || + model->absBounds[0].z > parms.bounds[1].z || + model->absBounds[1].z < parms.bounds[0].z ) { + continue; + } + + if( parms.count >= parms.maxCount ) { +// gameLocal.Warning( "idClip::ClipModelsTouchingBounds Max Count Hit\n" ); + break; + } + + parms.list[ parms.count++ ] = model; + } +// RAVEN END + + return parms.count; +} + +/* +================ +idClip::EntitiesTouchingBounds +================ +*/ +int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, entCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + entCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < entCount; j++ ) { + if ( entityList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= entCount ) { + if ( entCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return entCount; + } + entityList[entCount] = clipModelList[i]->entity; + entCount++; + } + } + + return entCount; +} + +// RAVEN BEGIN +// ddynerman: MP helper function +/* +================ +idClip::PlayersTouchingBounds +================ +*/ +int idClip::PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **playerList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, playerCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + playerCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < playerCount; j++ ) { + if ( playerList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= playerCount ) { + if ( playerCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return playerCount; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( clipModelList[i]->entity->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerList[playerCount] = static_cast(clipModelList[i]->entity); + playerCount++; + } + } + } + + return playerCount; +} +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + +/* +==================== +idClip::DrawAreaClipSectors +==================== +*/ +void idClip::DrawAreaClipSectors( float range ) const { + idClipModel* clipModels[ MAX_GENTITIES ]; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idBounds bounds; + bounds[0] = player->GetPhysics()->GetOrigin() - idVec3( range, range, range ); + bounds[1] = player->GetPhysics()->GetOrigin() + idVec3( range, range, range ); + + int count = ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + + int i; + for ( i = 0; i < count; i++ ) { + idEntity* owner = clipModels[ i ]->GetEntity(); + + const idVec3& org = clipModels[ i ]->GetOrigin(); + const idBounds& bounds = clipModels[ i ]->GetBounds(); + + gameRenderWorld->DebugBounds( colorCyan, bounds, org ); + gameRenderWorld->DrawText( owner->GetClassname(), org, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + } +} + +/* +==================== +idClip::DrawClipSectors +==================== +*/ +void idClip::DrawClipSectors( void ) const { + idBounds bounds; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + int i; + idVec3 inverseNodeScale; + for( i = 0; i < 3; i++ ) { + inverseNodeScale[ i ] = 1 / nodeScale[ i ]; + } + + const char* filter = g_showClipSectorFilter.GetString(); + idTypeInfo* type = idClass::GetClass( filter ); + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { +// idWinding w( 4 ); + + bounds[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffsetVisual.x + 1; + bounds[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffsetVisual.y + 1; + bounds[ 0 ].z = player->GetPhysics()->GetBounds()[0].z; + + bounds[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffsetVisual.x - 1; + bounds[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffsetVisual.y - 1; + bounds[ 1 ].z = player->GetPhysics()->GetBounds()[1].z; + + idVec3 point; + point.x = ( bounds[ 0 ].x + bounds[ 1 ].x ) * 0.5f; + point.y = ( bounds[ 0 ].y + bounds[ 1 ].y ) * 0.5f; + point.z = 0.f; + +/* point.x = bounds[ 0 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point ); + + point.x = bounds[ 0 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point );*/ + + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + clipLink_t* link = sector->clipLinks; + while ( link ) { + if ( type && !link->clipModel->GetEntity()->IsType( *type ) ) { + link = link->nextInSector; + } else { + break; + } + } + + if( link ) { + + gameRenderWorld->DrawText( link->clipModel->GetEntity()->GetClassname(), point, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DebugBounds( colorMagenta, bounds ); + gameRenderWorld->DebugBounds( colorYellow, link->clipModel->GetBounds(), link->clipModel->GetOrigin() ); + + } else { + +// gameRenderWorld->DrawText( sector->clipLinks->clipModel->GetEntity()->GetClassname(), point, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + + } + } + } +} + +/* +==================== +idClip::GetClipSectorsStaticContents +==================== +*/ +void idClip::GetClipSectorsStaticContents( void ) { + idBounds bounds; + + bounds[ 0 ].x = 0; + bounds[ 0 ].y = 0; + bounds[ 0 ].z = worldBounds[ 0 ].z; + + bounds[ 1 ].x = 1 / nodeScale.x; + bounds[ 1 ].y = 1 / nodeScale.y; + bounds[ 1 ].z = worldBounds[ 1 ].z; + + idTraceModel* trm = new idTraceModel( bounds ); + + idVec3 org; + org.z = 0; + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { + org.x = ( x / nodeScale.x ) + nodeOffset.x; + org.y = ( y / nodeScale.y ) + nodeOffset.y; + + int contents = collisionModelManager->Contents( org, trm, mat3_identity, -1, world, vec3_origin, mat3_default ); + clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ].contents = contents; + } + } + // mwhitlock: Fix leak in SD's code. + delete trm; +} + +// RAVEN END + +/* +==================== +idClip::GetTraceClipModels + + an ent will be excluded from testing if: + cm->entity == passEntity ( don't clip against the pass entity ) + cm->entity == passOwner ( missiles don't clip with owner ) + cm->owner == passEntity ( don't interact with your own missiles ) + cm->owner == passOwner ( don't interact with other missiles from same owner ) +==================== +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 ) const { +// RAVEN END + int i, num; + idClipModel *cm; + idEntity *passOwner; + + num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + + if ( !passEntity ) { + return num; + } + + if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) { + passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner(); + } else { + passOwner = NULL; + } + + for ( i = 0; i < num; i++ ) { + + cm = clipModelList[i]; + + // check if we should ignore this entity + if ( cm->entity == passEntity ) { + clipModelList[i] = NULL; // don't clip against the pass entity + } +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + else if ( cm->entity == passEntity2 ){ + clipModelList[i] = NULL; +// RAVEN END + } else if ( cm->entity == passOwner ) { + clipModelList[i] = NULL; // missiles don't clip with their owner + } else if ( cm->owner ) { + if ( cm->owner == passEntity ) { + clipModelList[i] = NULL; // don't clip against own missiles + } else if ( cm->owner == passOwner ) { + clipModelList[i] = NULL; // don't clip against other missiles from same owner + } + } + } + + return num; +} + +/* +============ +idClip::TraceRenderModel +============ +*/ +void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { + trace.fraction = 1.0f; + + // if the trace is passing through the bounds + if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { + modelTrace_t modelTrace; + + // test with exact render model and modify trace_t structure accordingly + if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { + trace.fraction = modelTrace.fraction; + trace.endAxis = axis; + trace.endpos = modelTrace.point; + trace.c.normal = modelTrace.normal; + trace.c.dist = modelTrace.point * modelTrace.normal; + trace.c.point = modelTrace.point; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.modelFeature = 0; + trace.c.trmFeature = 0; + trace.c.contents = modelTrace.material->GetContentFlags(); + trace.c.material = modelTrace.material; + +// RAVEN BEGIN +// jscott: for material types + trace.c.materialType = modelTrace.materialType; +// RAVEN END + + // NOTE: trace.c.id will be the joint number + touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); + } + } +} + +/* +============ +idClip::TraceModelForClipModel +============ +*/ +const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const { + if ( !mdl ) { + return NULL; + } else { + if ( !mdl->IsTraceModel() ) { + if ( mdl->GetEntity() ) { + gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() ); + } else { + gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() ); + } + } + return idClipModel::GetCachedTraceModel( mdl->traceModelIndex ); + } +} + +/* +============ +idClip::TestHugeTranslation +============ +*/ +ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { + if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + assert( 0 ); + + results.fraction = 0.0f; + results.endpos = start; + results.endAxis = trmAxis; + memset( &results.c, 0, sizeof( results.c ) ); + results.c.point = start; + results.c.entityNum = ENTITYNUM_WORLD; + + if ( mdl->GetEntity() ) { + gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + } else { + gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + } + return true; + } + return false; +} + +/* +============ +idClip::TranslationEntities +============ +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return; + } + + trm = TraceModelForClipModel( mdl ); + + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + + if ( !trm ) { + traceBounds.FromPointTranslation( start, end - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } +} + +/* +============ +idClip::Translation +============ +*/ +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation +bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + BeginClipProfile( CPT_TRANSLATION ); + + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces + if ( g_showCollisionTraces.GetInteger() >= 2 && !g_stopTime.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, start, end, 1000 ); + } +// RAVEN END + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + EndClipProfile( CPT_TRANSLATION ); + return true; + } + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + EndClipProfile( CPT_TRANSLATION ); + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + } + + if ( !trm ) { + traceBounds.FromPointTranslation( start, results.endpos - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + +// RAVEN BEGIN +// jscott: for material types + results.c.materialType = trace.c.materialType; + +// mekberg: copy contents + if ( touch->IsTraceModel( ) ) { + results.c.contents = touch->GetContents( ); + } +// RAVEN END + + if ( results.fraction == 0.0f ) { + break; + } + } + } + + EndClipProfile( CPT_TRANSLATION ); + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Rotation +============ +*/ +bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + trace_t trace; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( !trm ) { + traceBounds.FromPointRotation( start, rotation ); + } else { + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Motion +============ +*/ +bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idVec3 dir, endPosition; + idBounds traceBounds; + float radius; + trace_t translationalTrace, rotationalTrace, trace; + idRotation endRotation; + const idTraceModel *trm; + + assert( rotation.GetOrigin() == start ); + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) { + // if no translation + if ( start == end ) { + // pure rotation + return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + } else if ( start != end ) { + // pure translation + return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity ); + } else { + // no motion + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis; + return false; + } + + trm = TraceModelForClipModel( mdl ); + + radius = trm->bounds.GetRadius(); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // translational collision with world + idClip::numTranslations++; + collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &translationalTrace, 0, sizeof( translationalTrace ) ); + translationalTrace.fraction = 1.0f; + translationalTrace.endpos = end; + translationalTrace.endAxis = trmAxis; + } + + if ( translationalTrace.fraction != 0.0f ) { + + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + dir = translationalTrace.endpos - start; + for ( i = 0; i < 3; i++ ) { + if ( dir[i] < 0.0f ) { + traceBounds[0][i] += dir[i]; + } + else { + traceBounds[1][i] += dir[i]; + } + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < translationalTrace.fraction ) { + translationalTrace = trace; + translationalTrace.c.entityNum = touch->entity->entityNumber; + translationalTrace.c.id = touch->id; + if ( translationalTrace.fraction == 0.0f ) { + break; + } + } + } + } else { + num = -1; + } + + endPosition = translationalTrace.endpos; + endRotation = rotation; + endRotation.SetOrigin( endPosition ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // rotational collision with world + idClip::numRotations++; + collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &rotationalTrace, 0, sizeof( rotationalTrace ) ); + rotationalTrace.fraction = 1.0f; + rotationalTrace.endpos = endPosition; + rotationalTrace.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( rotationalTrace.fraction != 0.0f ) { + + if ( num == -1 ) { + traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation ); + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + } + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision detection with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < rotationalTrace.fraction ) { + rotationalTrace = trace; + rotationalTrace.c.entityNum = touch->entity->entityNumber; + rotationalTrace.c.id = touch->id; + if ( rotationalTrace.fraction == 0.0f ) { + break; + } + } + } + } + + if ( rotationalTrace.fraction < 1.0f ) { + results = rotationalTrace; + } else { + results = translationalTrace; + results.endAxis = rotationalTrace.endAxis; + } + + results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction ); + + return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f ); +} + +/* +============ +idClip::Contacts +============ +*/ +int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + BeginClipProfile( CPT_CONTACTS ); + + int i, j, num, n, numContacts; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContacts++; + numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + numContacts = 0; + } + + for ( i = 0; i < numContacts; i++ ) { + contacts[i].entityNum = ENTITYNUM_WORLD; + contacts[i].id = 0; + } + + if ( numContacts >= maxContacts ) { + EndClipProfile( CPT_CONTACTS ); + return numContacts; + } + + if ( !trm ) { + traceBounds = idBounds( start ).Expand( depth ); + } else { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + traceBounds.ExpandSelf( depth ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contacts with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numContacts++; + n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts, + start, dir, depth, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + for ( j = 0; j < n; j++ ) { + contacts[numContacts].entityNum = touch->entity->entityNumber; + contacts[numContacts].id = touch->id; + numContacts++; + } + + if ( numContacts >= maxContacts ) { + break; + } + } + + EndClipProfile( CPT_CONTACTS ); + + return numContacts; +} + +/* +============ +idClip::Contents +============ +*/ +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. +int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { +// RAVEN END + BeginClipProfile( CPT_CONTENTS ); + + int i, num, contents; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContents++; + contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + contents = 0; + } + + if ( !trm ) { + traceBounds[0] = start; + traceBounds[1] = start; + } else if ( trmAxis.IsRotated() ) { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + } else { + traceBounds[0] = trm->bounds[0] + start; + traceBounds[1] = trm->bounds[1] + start; + } + + num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contents test with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + // if the entity does not have any contents we are looking for + if ( ( touch->contents & contentMask ) == 0 ) { + continue; + } + + // if the entity has no new contents flags + if ( ( touch->contents & contents ) == touch->contents ) { + continue; + } + + idClip::numContents++; + if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->GetCollisionModel(), touch->origin, touch->axis ) ) { + contents |= ( touch->contents & contentMask ); + } + +// RAVEN BEGIN + // Only sends back one entity for now. Ahh well, if this is a problem, have it send back a list... + if ( touchedEntity ) + { + *touchedEntity = touch->GetEntity(); + } +// RAVEN END + } + + EndClipProfile( CPT_CONTENTS ); + + return contents; +} + +/* +============ +idClip::TranslationModel +============ +*/ +void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::RotationModel +============ +*/ +void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContactsModel +============ +*/ +int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContacts++; + return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContentsModel +============ +*/ +int idClip::ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContents++; + return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::GetModelContactFeature +============ +*/ +bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + int i; + idCollisionModel *model; + idVec3 start, end; + + model = NULL; + winding.Clear(); + + if ( clipModel == NULL ) { + model = NULL; + } else { + if ( clipModel->renderModelHandle != -1 ) { + winding += contact.point; + return true; + } else if ( clipModel->traceModelIndex != -1 ) { + model = idClipModel::GetCachedCollisionModel( clipModel->traceModelIndex ); + } else { + model = clipModel->collisionModel; + } + } + + // if contact with a collision model + if ( model != NULL ) { + switch( contact.type ) { + case CONTACT_EDGE: { + // the model contact feature is a collision model edge + model->GetEdge( contact.modelFeature, start, end ); + winding += start; + winding += end; + break; + } + case CONTACT_MODELVERTEX: { + // the model contact feature is a collision model vertex + model->GetVertex( contact.modelFeature, start ); + winding += start; + break; + } + case CONTACT_TRMVERTEX: { + // the model contact feature is a collision model polygon + model->GetPolygon( contact.modelFeature, winding ); + break; + } + } + } + + // transform the winding to world space + if ( clipModel ) { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() *= clipModel->axis; + winding[i].ToVec3() += clipModel->origin; + } + } + + return true; +} + +/* +============ +idClip::PrintStatistics +============ +*/ +void idClip::PrintStatistics( void ) { +// RAVEN BEGIN +// rjohnson: added trace model cache size + gameLocal.Printf( "t=%-3d, r=%-3d, m=%-3d, render=%-3d, contents=%-3d, contacts=%-3d, cache=%d\n", + numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts, traceModelCache.Num() * sizeof( idTraceModel ) ); +// RAVEN END +} + +/* +============ +idClip::DrawClipModels +============ +*/ +void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type ) { + int i, num; + idBounds bounds; + idClipModel *clipModelList[MAX_GENTITIES]; + idClipModel *clipModel; + + bounds = idBounds( eye ).Expand( radius ); + + num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < num; i++ ) { + clipModel = clipModelList[i]; + if ( clipModel->GetEntity() == passEntity ) { + continue; + } + if ( type && !clipModel->GetEntity()->IsType( *type ) ) { + continue; + } + if ( clipModel->renderModelHandle != -1 ) { + gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() ); + continue; + } + + idCollisionModel *model = clipModel->GetCollisionModel(); + if ( model != NULL ) { + collisionModelManager->DrawModel( model, clipModel->GetOrigin(), clipModel->GetAxis(), eye, mat3_identity, radius ); + } + } +} + +/* +============ +idClip::DrawModelContactFeature +============ +*/ +bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const { + int i; + idMat3 axis; + idFixedWinding winding; + + if ( !GetModelContactFeature( contact, clipModel, winding ) ) { + return false; + } + + axis = contact.normal.ToMat3(); + + if ( winding.GetNumPoints() == 1 ) { + gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime ); + } else { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime ); + } + } + + axis[0] = -axis[0]; + axis[2] = -axis[2]; + gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added debug hud support + +void idClip::DebugHudStatistics( void ) +{ + gameDebug.SetInt( "physics_translations", numTranslations ); + gameDebug.SetInt( "physics_rotations", numRotations ); + gameDebug.SetInt( "physics_motions", numMotions ); + gameDebug.SetInt( "physics_render_model_traces", numRenderModelTraces ); + gameDebug.SetInt( "physics_contents", numContents ); + gameDebug.SetInt( "physics_contacts", numContacts ); +} + +void idClip::ClearStatistics( void ) +{ + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( clipSector_t* sector ) { + sector->dynamicContents = 0; + + clipLink_t* link; + for( link = sector->clipLinks; link; link = link->nextInSector ) { + sector->dynamicContents |= link->clipModel->GetContents(); + } +} + +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( idClipModel* clipModel ) { + clipLink_s* link = clipModel->clipLinks; + while ( link ) { + idClip::UpdateDynamicContents( link->sector ); + link = link->nextLink; + } +} +// RAVEN END diff --git a/source/game/physics/Clip.h b/source/game/physics/Clip.h new file mode 100644 index 0000000..b7bf837 --- /dev/null +++ b/source/game/physics/Clip.h @@ -0,0 +1,407 @@ + +#ifndef __CLIP_H__ +#define __CLIP_H__ + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +const int CLIPSECTOR_DEPTH = 6; +const int CLIPSECTOR_WIDTH = 1 << CLIPSECTOR_DEPTH; +// RAVEN END + +/* +=============================================================================== + + Handles collision detection with the world and between physics objects. + +=============================================================================== +*/ + +#define CLIPMODEL_ID_TO_JOINT_HANDLE( id ) ( ( id ) >= 0 ? INVALID_JOINT : ((jointHandle_t) ( -1 - id )) ) +#define JOINT_HANDLE_TO_CLIPMODEL_ID( id ) ( -1 - id ) + +class idClip; +class idClipModel; +class idEntity; + + +//=============================================================== +// +// idClipModel +// +//=============================================================== + +class idClipModel { + + friend class idClip; + +public: + idClipModel( void ); + explicit idClipModel( const char *name ); + explicit idClipModel( const idTraceModel &trm, const idMaterial *material = NULL ); + explicit idClipModel( const int renderModelHandle ); + explicit idClipModel( const idClipModel *model ); + ~idClipModel( void ); +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void UpdateDynamicContents( void ); +// RAVEN END + + bool LoadModel( const char *name ); + void LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + void LoadModel( const int renderModelHandle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void Link( void ); // must have been linked with an entity and id before + void Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); +// RAVEN END + void Unlink( void ); // unlink from sectors + void SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ); // unlinks the clip model + void Translate( const idVec3 &translation ); // unlinks the clip model + void Rotate( const idRotation &rotation ); // unlinks the clip model + void Enable( void ); // enable for clipping + void Disable( void ); // keep linked but disable for clipping + void SetContents( int newContents ); // override contents + int GetContents( void ) const; + void SetEntity( idEntity *newEntity ); + idEntity * GetEntity( void ) const; + void SetId( int newId ); + int GetId( void ) const; + void SetOwner( idEntity *newOwner ); + idEntity * GetOwner( void ) const; + const idBounds & GetBounds( void ) const; + const idBounds & GetAbsBounds( void ) const; + const idVec3 & GetOrigin( void ) const; + const idMat3 & GetAxis( void ) const; + bool IsTraceModel( void ) const; // returns true if this is a trace model + bool IsRenderModel( void ) const; // returns true if this is a render model + bool IsLinked( void ) const; // returns true if the clip model is linked + bool IsEnabled( void ) const; // returns true if enabled for collision detection + bool IsEqual( const idTraceModel &trm ) const; + idCollisionModel * GetCollisionModel( void ) const; // returns handle used to collide vs this model + const idTraceModel * GetTraceModel( void ) const; + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + + static void ClearTraceModelCache( void ); + static int TraceModelCacheSize( void ); + static void SaveTraceModels( idSaveGame *savefile ); + static void RestoreTraceModels( idRestoreGame *savefile ); + +private: + bool enabled; // true if this clip model is used for clipping +// RAVEN BEGIN + bool checked; // Splash's clip model code +// RAVEN END + idEntity * entity; // entity using this clip model + int id; // id for entities that use multiple clip models + idEntity * owner; // owner of the entity that owns this clip model + idVec3 origin; // origin of clip model + idMat3 axis; // orientation of clip model + idBounds bounds; // bounds + idBounds absBounds; // absolute bounds + int contents; // all contents ored together + idCollisionModel * collisionModel; // handle to collision model + int traceModelIndex; // trace model used for collision detection + int renderModelHandle; // render model def handle + + struct clipLink_s * clipLinks; // links into sectors + int touchCount; + + void Init( void ); // initialize + void FreeModel( void ); + void Link_r( struct clipSector_s *node ); + + static void CacheCollisionModels( void ); + static int AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void FreeTraceModel( int traceModelIndex ); + static int CopyTraceModel( const int traceModelIndex ); + static idTraceModel * GetCachedTraceModel( int traceModelIndex ); + static idCollisionModel*GetCachedCollisionModel( int traceModelIndex ); + static int GetTraceModelHashKey( const idTraceModel &trm ); +}; + + +ID_INLINE void idClipModel::Translate( const idVec3 &translation ) { + Unlink(); + origin += translation; +} + +ID_INLINE void idClipModel::Rotate( const idRotation &rotation ) { + Unlink(); + origin *= rotation; + axis *= rotation.ToMat3(); +} + +ID_INLINE void idClipModel::Enable( void ) { + enabled = true; +} + +ID_INLINE void idClipModel::Disable( void ) { + enabled = false; +} + +ID_INLINE void idClipModel::SetContents( int newContents ) { + contents = newContents; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + UpdateDynamicContents(); +// RAVEN END +} + +ID_INLINE int idClipModel::GetContents( void ) const { + return contents; +} + +ID_INLINE void idClipModel::SetEntity( idEntity *newEntity ) { + entity = newEntity; +} + +ID_INLINE idEntity *idClipModel::GetEntity( void ) const { + return entity; +} + +ID_INLINE void idClipModel::SetId( int newId ) { + id = newId; +} + +ID_INLINE int idClipModel::GetId( void ) const { + return id; +} + +ID_INLINE void idClipModel::SetOwner( idEntity *newOwner ) { + owner = newOwner; +} + +ID_INLINE idEntity *idClipModel::GetOwner( void ) const { + return owner; +} + +ID_INLINE const idBounds &idClipModel::GetBounds( void ) const { + return bounds; +} + +ID_INLINE const idBounds &idClipModel::GetAbsBounds( void ) const { + return absBounds; +} + +ID_INLINE const idVec3 &idClipModel::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idClipModel::GetAxis( void ) const { + return axis; +} + +ID_INLINE bool idClipModel::IsRenderModel( void ) const { + return ( renderModelHandle != -1 ); +} + +ID_INLINE bool idClipModel::IsTraceModel( void ) const { + return ( traceModelIndex != -1 ); +} + +ID_INLINE bool idClipModel::IsLinked( void ) const { + return ( clipLinks != NULL ); +} + +ID_INLINE bool idClipModel::IsEnabled( void ) const { + return enabled; +} + +ID_INLINE bool idClipModel::IsEqual( const idTraceModel &trm ) const { + return ( traceModelIndex != -1 && *GetCachedTraceModel( traceModelIndex ) == trm ); +} + +ID_INLINE const idTraceModel *idClipModel::GetTraceModel( void ) const { + if ( !IsTraceModel() ) { + return NULL; + } + return idClipModel::GetCachedTraceModel( traceModelIndex ); +} + + +//=============================================================== +// +// idClip +// +//=============================================================== + +class idClip { + + friend class idClipModel; + +public: + idClip( void ); + + void Init( void ); + void Shutdown( void ); + + // clip versus the rest of the world +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + bool Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + bool Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. + int Contents( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); +// RAVEN END + + // special case translations versus the rest of the world + bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, + int contentMask, const idEntity *passEntity ); + bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, + int contentMask, const idEntity *passEntity ); + + // clip versus a specific model + void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // clip versus all entities but not the world +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + void TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + + // get a contact feature + bool GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + +// RAVEN BEGIN +// ddynerman: another helper function, useful in MP + int PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; +// RAVEN END + + const idBounds & GetWorldBounds( void ) const; + idCollisionModel * GetWorldCollisionModel( void ) const { return world; } + +// RAVEN BEGIN +// ddynerman: change to static + static idClipModel * DefaultClipModel( void ); + static void FreeDefaultClipModel( void ); +// RAVEN END + + // stats and debug drawing + void PrintStatistics( void ); + void DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type = NULL ); + bool DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const; + +// RAVEN BEGIN +// rjohnson: added debug hud support + void DebugHudStatistics( void ); + void ClearStatistics( void ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void CoordsForBounds( int* coords, idBounds& bounds ) const; + void DrawClipSectors( void ) const; + void DrawAreaClipSectors( float range ) const; + static void UpdateDynamicContents( struct clipSector_s* sector ); + static void UpdateDynamicContents( idClipModel* clipModel ); +// RAVEN END + +private: +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idVec3 nodeScale; + idVec3 nodeOffset; + idVec3 nodeOffsetVisual; +// ddynerman: change to static + static idClipModel defaultClipModel; +// RAVEN END + struct clipSector_s * clipSectors; + idCollisionModel * world; + idBounds worldBounds; + idClipModel temporaryClipModel; + + mutable int touchCount; + // statistics + int numTranslations; + int numRotations; + int numMotions; + int numRenderModelTraces; + int numContents; + int numContacts; + +private: + struct clipSector_s * CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ); + void ClipModelsTouchingBounds_r( const struct clipSector_s *node, struct listParms_s &parms ) const; + const idTraceModel * TraceModelForClipModel( const idClipModel *mdl ) const; +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + int GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 = 0 ) const; +// RAVEN END + void TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void GetClipSectorsStaticContents( void ); +// RAVEN END +}; + + +ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + Translation( results, start, end, NULL, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + temporaryClipModel.LoadModel( idTraceModel( bounds ), NULL, true ); + Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE const idBounds & idClip::GetWorldBounds( void ) const { + return worldBounds; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +ID_INLINE void idClip::CoordsForBounds( int* coords, idBounds& bounds ) const { + float fCoords[ 4 ]; + + fCoords[ 0 ] = ( bounds[ 0 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 1 ] = ( bounds[ 0 ].y - nodeOffset.y ) * nodeScale.y; + fCoords[ 2 ] = ( bounds[ 1 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 3 ] = ( bounds[ 1 ].y - nodeOffset.y ) * nodeScale.y; + + int i; + for( i = 0; i < 4; i++ ) { + + coords[ i ] = idMath::FtoiFast( fCoords[ i ] ); + + if( coords[ i ] < 0 ) { + coords[ i ] = 0; + } else if( coords[ i ] > CLIPSECTOR_WIDTH - 1 ) { + coords[ i ] = CLIPSECTOR_WIDTH - 1; + } + } + coords[ 2 ]++; coords[ 3 ]++; +} +// RAVEN END + +#endif /* !__CLIP_H__ */ diff --git a/source/game/physics/Force.cpp b/source/game/physics/Force.cpp new file mode 100644 index 0000000..9925a4f --- /dev/null +++ b/source/game/physics/Force.cpp @@ -0,0 +1,66 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idClass, idForce ) +END_CLASS + +idList idForce::forceList; + +/* +================ +idForce::idForce +================ +*/ +idForce::idForce( void ) { + forceList.Append( this ); +} + +/* +================ +idForce::~idForce +================ +*/ +idForce::~idForce( void ) { + forceList.Remove( this ); +} + +/* +================ +idForce::DeletePhysics +================ +*/ +void idForce::DeletePhysics( const idPhysics *phys ) { + int i; + + for ( i = 0; i < forceList.Num(); i++ ) { + forceList[i]->RemovePhysics( phys ); + } +} + +/* +================ +idForce::ClearForceList +================ +*/ +void idForce::ClearForceList( void ) { + forceList.Clear(); +} + +/* +================ +idForce::Evaluate +================ +*/ +void idForce::Evaluate( int time ) { +} + +/* +================ +idForce::RemovePhysics +================ +*/ +void idForce::RemovePhysics( const idPhysics *phys ) { +} diff --git a/source/game/physics/Force.h b/source/game/physics/Force.h new file mode 100644 index 0000000..4bd3b2d --- /dev/null +++ b/source/game/physics/Force.h @@ -0,0 +1,39 @@ + +#ifndef __FORCE_H__ +#define __FORCE_H__ + +/* +=============================================================================== + + Force base class + + A force object applies a force to a physics object. + +=============================================================================== +*/ + +class idEntity; +class idPhysics; + +class idForce : public idClass { + +public: + CLASS_PROTOTYPE( idForce ); + + idForce( void ); + virtual ~idForce( void ); + static void DeletePhysics( const idPhysics *phys ); + static void ClearForceList( void ); + +public: // common force interface + // evalulate the force up to the given time + virtual void Evaluate( int time ); + // removes any pointers to the physics object + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + static idList forceList; +}; + +#endif /* !__FORCE_H__ */ diff --git a/source/game/physics/Force_Constant.cpp b/source/game/physics/Force_Constant.cpp new file mode 100644 index 0000000..50ca1f3 --- /dev/null +++ b/source/game/physics/Force_Constant.cpp @@ -0,0 +1,109 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Constant ) +END_CLASS + +/* +================ +idForce_Constant::idForce_Constant +================ +*/ +idForce_Constant::idForce_Constant( void ) { + force = vec3_zero; + physics = NULL; + id = 0; + point = vec3_zero; +} + +/* +================ +idForce_Constant::~idForce_Constant +================ +*/ +idForce_Constant::~idForce_Constant( void ) { +} + +/* +================ +idForce_Constant::Save +================ +*/ +void idForce_Constant::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( force ); + // TOSAVE: idPhysics * physics + savefile->WriteInt( id ); + savefile->WriteVec3( point ); +} + +/* +================ +idForce_Constant::Restore +================ +*/ +void idForce_Constant::Restore( idRestoreGame *savefile ) { + // Owner needs to call SetPhysics!! + savefile->ReadVec3( force ); + savefile->ReadInt( id ); + savefile->ReadVec3( point ); +} + +/* +================ +idForce_Constant::SetPosition +================ +*/ +void idForce_Constant::SetPosition( idPhysics *physics, int id, const idVec3 &point ) { + this->physics = physics; + this->id = id; + this->point = point; +} + +/* +================ +idForce_Constant::SetForce +================ +*/ +void idForce_Constant::SetForce( const idVec3 &force ) { + this->force = force; +} + +/* +================ +idForce_Constant::SetPhysics +================ +*/ +void idForce_Constant::SetPhysics( idPhysics *physics ) { + this->physics = physics; +} + +/* +================ +idForce_Constant::Evaluate +================ +*/ +void idForce_Constant::Evaluate( int time ) { + idVec3 p; + + if ( !physics ) { + return; + } + + p = physics->GetOrigin( id ) + point * physics->GetAxis( id ); + + physics->AddForce( id, p, force ); +} + +/* +================ +idForce_Constant::RemovePhysics +================ +*/ +void idForce_Constant::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/source/game/physics/Force_Constant.h b/source/game/physics/Force_Constant.h new file mode 100644 index 0000000..3aa5d43 --- /dev/null +++ b/source/game/physics/Force_Constant.h @@ -0,0 +1,44 @@ + +#ifndef __FORCE_CONSTANT_H__ +#define __FORCE_CONSTANT_H__ + +/* +=============================================================================== + + Constant force + +=============================================================================== +*/ + +class idForce_Constant : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Constant ); + + idForce_Constant( void ); + virtual ~idForce_Constant( void ); + + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // constant force + void SetForce( const idVec3 &force ); + // set force position + void SetPosition( idPhysics *physics, int id, const idVec3 &point ); + + void SetPhysics( idPhysics *physics ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + // force properties + idVec3 force; + idPhysics * physics; + int id; + idVec3 point; +}; + +#endif /* !__FORCE_CONSTANT_H__ */ diff --git a/source/game/physics/Force_Drag.cpp b/source/game/physics/Force_Drag.cpp new file mode 100644 index 0000000..9e21f39 --- /dev/null +++ b/source/game/physics/Force_Drag.cpp @@ -0,0 +1,131 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Drag ) +END_CLASS + +/* +================ +idForce_Drag::idForce_Drag +================ +*/ +idForce_Drag::idForce_Drag( void ) { + damping = 0.5f; + dragPosition = vec3_zero; + physics = NULL; + id = 0; + p = vec3_zero; + dragPosition = vec3_zero; +} + +/* +================ +idForce_Drag::~idForce_Drag +================ +*/ +idForce_Drag::~idForce_Drag( void ) { +} + +/* +================ +idForce_Drag::Init +================ +*/ +void idForce_Drag::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Drag::SetPhysics +================ +*/ +void idForce_Drag::SetPhysics( idPhysics *phys, int id, const idVec3 &p ) { + this->physics = phys; + this->id = id; + this->p = p; +} + +/* +================ +idForce_Drag::SetDragPosition +================ +*/ +void idForce_Drag::SetDragPosition( const idVec3 &pos ) { + this->dragPosition = pos; +} + +/* +================ +idForce_Drag::GetDragPosition +================ +*/ +const idVec3 &idForce_Drag::GetDragPosition( void ) const { + return this->dragPosition; +} + +/* +================ +idForce_Drag::GetDraggedPosition +================ +*/ +const idVec3 idForce_Drag::GetDraggedPosition( void ) const { + return ( physics->GetOrigin( id ) + p * physics->GetAxis( id ) ); +} + +/* +================ +idForce_Drag::Evaluate +================ +*/ +void idForce_Drag::Evaluate( int time ) { + float l1, l2, mass; + idVec3 dragOrigin, dir1, dir2, velocity, centerOfMass; + idMat3 inertiaTensor; + idRotation rotation; + idClipModel *clipModel; + + if ( !physics ) { + return; + } + + clipModel = physics->GetClipModel( id ); + if ( clipModel != NULL && clipModel->IsTraceModel() ) { + clipModel->GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + } else { + centerOfMass.Zero(); + } + + centerOfMass = physics->GetOrigin( id ) + centerOfMass * physics->GetAxis( id ); + dragOrigin = physics->GetOrigin( id ) + p * physics->GetAxis( id ); + + dir1 = dragPosition - centerOfMass; + dir2 = dragOrigin - centerOfMass; + l1 = dir1.Normalize(); + l2 = dir2.Normalize(); + + rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.GetMSec() ), id ); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.GetMSec ( ) ) ); +// RAVEN END + physics->SetLinearVelocity( velocity, id ); +} + +/* +================ +idForce_Drag::RemovePhysics +================ +*/ +void idForce_Drag::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/source/game/physics/Force_Drag.h b/source/game/physics/Force_Drag.h new file mode 100644 index 0000000..e829fab --- /dev/null +++ b/source/game/physics/Force_Drag.h @@ -0,0 +1,47 @@ + +#ifndef __FORCE_DRAG_H__ +#define __FORCE_DRAG_H__ + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Drag : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Drag ); + + idForce_Drag( void ); + virtual ~idForce_Drag( void ); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &p ); + // set position to drag towards + void SetDragPosition( const idVec3 &pos ); + // get the position dragged towards + const idVec3 & GetDragPosition( void ) const; + // get the position on the dragged physics object + const idVec3 GetDraggedPosition( void ) const; + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // properties + float damping; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object + idVec3 p; // position on clip model + idVec3 dragPosition; // drag towards this position +}; + +#endif /* !__FORCE_DRAG_H__ */ diff --git a/source/game/physics/Force_Field.cpp b/source/game/physics/Force_Field.cpp new file mode 100644 index 0000000..77e6a90 --- /dev/null +++ b/source/game/physics/Force_Field.cpp @@ -0,0 +1,272 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Field ) +END_CLASS + +/* +================ +idForce_Field::idForce_Field +================ +*/ +idForce_Field::idForce_Field( void ) { + type = FORCEFIELD_UNIFORM; + applyType = FORCEFIELD_APPLY_FORCE; + magnitude = 0.0f; + dir.Set( 0, 0, 1 ); + randomTorque = 0.0f; + playerOnly = false; + monsterOnly = false; + clipModel = NULL; +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = -1; + owner = NULL; +// RAVEN END +} + +/* +================ +idForce_Field::~idForce_Field +================ +*/ +idForce_Field::~idForce_Field( void ) { + if ( this->clipModel ) { + delete this->clipModel; + } +} + +/* +================ +idForce_Field::Save +================ +*/ +void idForce_Field::Save( idSaveGame *savefile ) const { + savefile->WriteInt( type ); + savefile->WriteInt( applyType); + savefile->WriteFloat( magnitude ); + savefile->WriteVec3( dir ); + savefile->WriteFloat( randomTorque ); + savefile->WriteBool( playerOnly ); + savefile->WriteBool( monsterOnly ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteInt ( lastApplyTime ); // cnicholson: Added unsaved var + // TOSAVE: idEntity* owner; +} + +/* +================ +idForce_Field::Restore +================ +*/ +void idForce_Field::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)type ); + savefile->ReadInt( (int &)applyType); + savefile->ReadFloat( magnitude ); + savefile->ReadVec3( dir ); + savefile->ReadFloat( randomTorque ); + savefile->ReadBool( playerOnly ); + savefile->ReadBool( monsterOnly ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadInt ( lastApplyTime ); // cnicholson: Added unrestored var +} + +/* +================ +idForce_Field::SetClipModel +================ +*/ +void idForce_Field::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && clipModel != this->clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idForce_Field::Uniform +================ +*/ +void idForce_Field::Uniform( const idVec3 &force ) { + dir = force; + magnitude = dir.Normalize(); + type = FORCEFIELD_UNIFORM; +} + +/* +================ +idForce_Field::Explosion +================ +*/ +void idForce_Field::Explosion( float force ) { + magnitude = force; + type = FORCEFIELD_EXPLOSION; +} + +/* +================ +idForce_Field::Implosion +================ +*/ +void idForce_Field::Implosion( float force ) { + magnitude = force; + type = FORCEFIELD_IMPLOSION; +} + +/* +================ +idForce_Field::RandomTorque +================ +*/ +void idForce_Field::RandomTorque( float force ) { + randomTorque = force; +} + +/* +================ +idForce_Field::Evaluate +================ +*/ +void idForce_Field::Evaluate( int time ) { + int numClipModels, i; + idBounds bounds; + idVec3 force, torque, angularVelocity; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + assert( clipModel ); + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( owner, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + idPhysics *physics = entity->GetPhysics(); + + if ( playerOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Player::GetClassType() ) ) { +// RAVEN END + continue; + } + } else if ( monsterOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Monster::GetClassType() ) ) { + + continue; + } + } + +// nrausch: It was undesireable to have gibs and discarded weapons bouncing on jump pads + if ( gameLocal.isMultiplayer ) { + if ( entity->IsType( idItem::GetClassType() ) ) { + continue; + } + if ( entity->IsType( rvClientPhysics::GetClassType() ) ) { + continue; + } + if ( entity->IsType( idPlayer::GetClassType() ) ) { + if ( ((idPlayer*)entity)->health <= 0 ) { + continue; + } + } + + } + +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( owner, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + switch( type ) { + case FORCEFIELD_UNIFORM: { + force = dir; + break; + } + case FORCEFIELD_EXPLOSION: { + force = cm->GetOrigin() - clipModel->GetOrigin(); + force.Normalize(); + break; + } + case FORCEFIELD_IMPLOSION: { + force = clipModel->GetOrigin() - cm->GetOrigin(); + force.Normalize(); + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid type" ); + break; + } + } + + if ( randomTorque != 0.0f ) { + torque[0] = gameLocal.random.CRandomFloat(); + torque[1] = gameLocal.random.CRandomFloat(); + torque[2] = gameLocal.random.CRandomFloat(); + if ( torque.Normalize() == 0.0f ) { + torque[2] = 1.0f; + } + } + + switch( applyType ) { + case FORCEFIELD_APPLY_FORCE: { + if ( randomTorque != 0.0f ) { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + case FORCEFIELD_APPLY_VELOCITY: { + physics->SetLinearVelocity( force * magnitude, cm->GetId() ); + if ( randomTorque != 0.0f ) { + angularVelocity = physics->GetAngularVelocity( cm->GetId() ); + physics->SetAngularVelocity( 0.5f * (angularVelocity + torque * randomTorque), cm->GetId() ); + } + break; + } + case FORCEFIELD_APPLY_IMPULSE: { + if ( randomTorque != 0.0f ) { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid apply type" ); + break; + } + } + +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = time; +// RAVEN END + } +} diff --git a/source/game/physics/Force_Field.h b/source/game/physics/Force_Field.h new file mode 100644 index 0000000..c9ec802 --- /dev/null +++ b/source/game/physics/Force_Field.h @@ -0,0 +1,92 @@ + +#ifndef __FORCE_FIELD_H__ +#define __FORCE_FIELD_H__ + +/* +=============================================================================== + + Force field + +=============================================================================== +*/ + +enum forceFieldType { + FORCEFIELD_UNIFORM, + FORCEFIELD_EXPLOSION, + FORCEFIELD_IMPLOSION +}; + +enum forceFieldApplyType { + FORCEFIELD_APPLY_FORCE, + FORCEFIELD_APPLY_VELOCITY, + FORCEFIELD_APPLY_IMPULSE +}; + +class idForce_Field : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Field ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Field( void ); + virtual ~idForce_Field( void ); + // uniform constant force + void Uniform( const idVec3 &force ); + // explosion from clip model origin + void Explosion( float force ); + // implosion towards clip model origin + void Implosion( float force ); + // add random torque + void RandomTorque( float force ); + // should the force field apply a force, velocity or impulse + void SetApplyType( const forceFieldApplyType type ) { applyType = type; } + // make the force field only push players + void SetPlayerOnly( bool set ) { playerOnly = set; } + // make the force field only push monsters + void SetMonsterOnly( bool set ) { monsterOnly = set; } + // clip model describing the extents of the force field + void SetClipModel( idClipModel *clipModel ); + +// RAVEN BEGIN +// ddynerman: owner information + void SetOwner( idEntity* ent ); +// bdube: added last apply time + int GetLastApplyTime( void ) const; +// RAVEN END + +public: // common force interface + virtual void Evaluate( int time ); + +private: + // force properties + forceFieldType type; + forceFieldApplyType applyType; + float magnitude; + idVec3 dir; + float randomTorque; + bool playerOnly; + bool monsterOnly; + idClipModel * clipModel; + +// RAVEN BEGIN +// bdube: added last apply time + int lastApplyTime; + idEntity* owner; +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added last apply time +ID_INLINE int idForce_Field::GetLastApplyTime ( void ) const { + return lastApplyTime; +} + +// ddynerman: owner information +ID_INLINE void idForce_Field::SetOwner( idEntity* ent ) { + owner = ent; +} +// RAVEN END + +#endif /* !__FORCE_FIELD_H__ */ diff --git a/source/game/physics/Force_Spring.cpp b/source/game/physics/Force_Spring.cpp new file mode 100644 index 0000000..dfc8f4b --- /dev/null +++ b/source/game/physics/Force_Spring.cpp @@ -0,0 +1,163 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Spring ) +END_CLASS + +/* +================ +idForce_Spring::idForce_Spring +================ +*/ +idForce_Spring::idForce_Spring( void ) { + Kstretch = 100.0f; + Kcompress = 100.0f; + damping = 0.0f; + restLength = 0.0f; + physics1 = NULL; + id1 = 0; + p1 = vec3_zero; + physics2 = NULL; + id2 = 0; + p2 = vec3_zero; +} + +/* +================ +idForce_Spring::~idForce_Spring +================ +*/ +idForce_Spring::~idForce_Spring( void ) { +} + +/* +================ +idForce_Spring::InitSpring +================ +*/ +void idForce_Spring::InitSpring( float Kstretch, float Kcompress, float damping, float restLength ) { + this->Kstretch = Kstretch; + this->Kcompress = Kcompress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idForce_Spring::SetPosition +================ +*/ +void idForce_Spring::SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, idPhysics *physics2, int id2, const idVec3 &p2 ) { + this->physics1 = physics1; + this->id1 = id1; + this->p1 = p1; + this->physics2 = physics2; + this->id2 = id2; + this->p2 = p2; +} + +/* +================ +idForce_Spring::Evaluate +================ +*/ +void idForce_Spring::Evaluate( int time ) { + float length; + idMat3 axis; + idVec3 pos1, pos2, velocity1, velocity2, force, dampingForce; + impactInfo_t info; + + pos1 = p1; + pos2 = p2; + velocity1 = velocity2 = vec3_origin; + + if ( physics1 ) { + axis = physics1->GetAxis( id1 ); + pos1 = physics1->GetOrigin( id1 ); + pos1 += p1 * axis; + if ( damping > 0.0f ) { + physics1->GetImpactInfo( id1, pos1, &info ); + velocity1 = info.velocity; + } + } + + if ( physics2 ) { + axis = physics2->GetAxis( id2 ); + pos2 = physics2->GetOrigin( id2 ); + pos2 += p2 * axis; + if ( damping > 0.0f ) { + physics2->GetImpactInfo( id2, pos2, &info ); + velocity2 = info.velocity; + } + } + + force = pos2 - pos1; + dampingForce = ( damping * ( ((velocity2 - velocity1) * force) / (force * force) ) ) * force; + length = force.Normalize(); + + // if the spring is stretched + if ( length > restLength ) { + if ( Kstretch > 0.0f ) { + force = ( Square( length - restLength ) * Kstretch ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, -force ); + } + } + } + else { + if ( Kcompress > 0.0f ) { + force = ( Square( length - restLength ) * Kcompress ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, -force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, force ); + } + } + } +} + +/* +================ +idForce_Spring::RemovePhysics +================ +*/ +void idForce_Spring::RemovePhysics( const idPhysics *phys ) { + if ( physics1 == phys ) { + physics1 = NULL; + } + if ( physics2 == phys ) { + physics2 = NULL; + } +} + +/* +================ +idForce_Spring::Save +================ +*/ +void idForce_Spring::Save( idSaveGame *savefile ) const { + savefile->WriteFloat ( Kstretch ); + savefile->WriteFloat ( Kcompress ); + savefile->WriteFloat ( damping ); + savefile->WriteFloat ( restLength ); +} + +/* +================ +idForce_Spring::Restore +================ +*/ +void idForce_Spring::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat ( Kstretch ); + savefile->ReadFloat ( Kcompress ); + savefile->ReadFloat ( damping ); + savefile->ReadFloat ( restLength ); +} + diff --git a/source/game/physics/Force_Spring.h b/source/game/physics/Force_Spring.h new file mode 100644 index 0000000..a0518b2 --- /dev/null +++ b/source/game/physics/Force_Spring.h @@ -0,0 +1,51 @@ + +#ifndef __FORCE_SPRING_H__ +#define __FORCE_SPRING_H__ + +/* +=============================================================================== + + Spring force + +=============================================================================== +*/ + +class idForce_Spring : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Spring ); + + idForce_Spring( void ); + virtual ~idForce_Spring( void ); + // initialize the spring + void InitSpring( float Kstretch, float Kcompress, float damping, float restLength ); + // set the entities and positions on these entities the spring is attached to + void SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, + idPhysics *physics2, int id2, const idVec3 &p2 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // spring properties + float Kstretch; + float Kcompress; + float damping; + float restLength; + + // positioning + idPhysics * physics1; // first physics object + int id1; // clip model id of first physics object + idVec3 p1; // position on clip model + idPhysics * physics2; // second physics object + int id2; // clip model id of second physics object + idVec3 p2; // position on clip model + +}; + +#endif /* !__FORCE_SPRING_H__ */ diff --git a/source/game/physics/Physics.cpp b/source/game/physics/Physics.cpp new file mode 100644 index 0000000..a772643 --- /dev/null +++ b/source/game/physics/Physics.cpp @@ -0,0 +1,56 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, idPhysics ) +END_CLASS + + +/* +================ +idPhysics::~idPhysics +================ +*/ +idPhysics::~idPhysics( void ) { +} + +/* +================ +idPhysics::Save +================ +*/ +void idPhysics::Save( idSaveGame *savefile ) const { +} + +/* +================ +idPhysics::Restore +================ +*/ +void idPhysics::Restore( idRestoreGame *savefile ) { +} + +/* +================ +idPhysics::SetClipBox +================ +*/ +void idPhysics::SetClipBox( const idBounds &bounds, float density ) { + SetClipModel( new idClipModel( idTraceModel( bounds ) ), density ); +} + +/* +================ +idPhysics::SnapTimeToPhysicsFrame +================ +*/ +int idPhysics::SnapTimeToPhysicsFrame( int t ) { + int s; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + s = t + gameLocal.GetMSec() - 1; + return ( s - s % gameLocal.GetMSec() ); +// RAVEN END +} diff --git a/source/game/physics/Physics.h b/source/game/physics/Physics.h new file mode 100644 index 0000000..bc5f740 --- /dev/null +++ b/source/game/physics/Physics.h @@ -0,0 +1,179 @@ + +#ifndef __PHYSICS_H__ +#define __PHYSICS_H__ + +/* +=============================================================================== + + Physics abstract class + + A physics object is a tool to manipulate the position and orientation of + an entity. The physics object is a container for idClipModels used for + collision detection. The physics deals with moving these collision models + through the world according to the laws of physics or other rules. + + The mass of a clip model is the volume of the clip model times the density. + An arbitrary mass can however be set for specific clip models or the + whole physics object. The contents of a clip model is a set of bit flags + that define the contents. The clip mask defines the contents a clip model + collides with. + + The linear velocity of a physics object is a vector that defines the + translation of the center of mass in units per second. The angular velocity + of a physics object is a vector that passes through the center of mass. The + direction of this vector defines the axis of rotation and the magnitude + defines the rate of rotation about the axis in radians per second. + The gravity is the change in velocity per second due to gravitational force. + + Entities update their visual position and orientation from the physics + using GetOrigin() and GetAxis(). Direct origin and axis changes of + entities should go through the physics. In other words the physics origin + and axis are updated first and the entity updates it's visual position + from the physics. + +=============================================================================== +*/ + +#define CONTACT_EPSILON 0.25f // maximum contact seperation distance + +class idEntity; + +typedef struct impactInfo_s { + float invMass; // inverse mass + idMat3 invInertiaTensor; // inverse inertia tensor + idVec3 position; // impact position relative to center of mass + idVec3 velocity; // velocity at the impact position +} impactInfo_t; + + +class idPhysics : public idClass { + +public: + ABSTRACT_PROTOTYPE( idPhysics ); + + virtual ~idPhysics( void ); + static int SnapTimeToPhysicsFrame( int t ); + + // Must not be virtual + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + // set pointer to entity using physics + virtual void SetSelf( idEntity *e ) = 0; + // clip models + virtual void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ) = 0; + virtual void SetClipBox( const idBounds &bounds, float density ); + virtual idClipModel * GetClipModel( int id = 0 ) const = 0; + virtual int GetNumClipModels( void ) const = 0; + // get/set the mass of a specific clip model or the whole physics object + virtual void SetMass( float mass, int id = -1 ) = 0; + virtual float GetMass( int id = -1 ) const = 0; + +// RAVEN BEGIN +// bdube: added center mass + // get the center of mass origin + virtual idVec3 GetCenterMass ( int id = -1 ) const = 0; +// RAVEN END + + // get/set the contents of a specific clip model or the whole physics object + virtual void SetContents( int contents, int id = -1 ) = 0; + virtual int GetContents( int id = -1 ) const = 0; + // get/set the contents a specific clip model or the whole physics object collides with + virtual void SetClipMask( int mask, int id = -1 ) = 0; + virtual int GetClipMask( int id = -1 ) const = 0; + // get the bounds of a specific clip model or the whole physics object + virtual const idBounds & GetBounds( int id = -1 ) const = 0; + virtual const idBounds & GetAbsBounds( int id = -1 ) const = 0; + // evaluate the physics with the given time step, returns true if the object moved + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ) = 0; + // update the time without moving + virtual void UpdateTime( int endTimeMSec ) = 0; + // get the last physics update time + virtual int GetTime( void ) const = 0; + // collision interaction between different physics objects + virtual void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const = 0; + virtual void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) = 0; + virtual void AddForce( const int id, const idVec3 &point, const idVec3 &force ) = 0; + virtual void Activate( void ) = 0; + virtual void PutToRest( void ) = 0; + virtual bool IsAtRest( void ) const = 0; + virtual int GetRestStartTime( void ) const = 0; + virtual bool IsPushable( void ) const = 0; +// RAVEN BEGIN +// bdube: water interraction + virtual bool IsInWater ( void ) const = 0; +// RAVEN END + // save and restore the physics state + virtual void SaveState( void ) = 0; + virtual void RestoreState( void ) = 0; + // set the position and orientation in master space or world space if no master set + virtual void SetOrigin( const idVec3 &newOrigin, int id = -1 ) = 0; + virtual void SetAxis( const idMat3 &newAxis, int id = -1 ) = 0; + // translate or rotate the physics object in world space + virtual void Translate( const idVec3 &translation, int id = -1 ) = 0; + virtual void Rotate( const idRotation &rotation, int id = -1 ) = 0; + // get the position and orientation in world space + virtual const idVec3 & GetOrigin( int id = 0 ) const = 0; + virtual const idMat3 & GetAxis( int id = 0 ) const = 0; + // set linear and angular velocity + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ) = 0; + virtual void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ) = 0; + // get linear and angular velocity + virtual const idVec3 & GetLinearVelocity( int id = 0 ) const = 0; + virtual const idVec3 & GetAngularVelocity( int id = 0 ) const = 0; + // gravity + virtual void SetGravity( const idVec3 &newGravity ) = 0; + virtual const idVec3 & GetGravity( void ) const = 0; + virtual const idVec3 & GetGravityNormal( void ) const = 0; + // get first collision when translating or rotating this physics object + virtual void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const = 0; + virtual void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const = 0; + virtual int ClipContents( const idClipModel *model ) const = 0; + // disable/enable the clip models contained by this physics object + virtual void DisableClip( void ) = 0; + virtual void EnableClip( void ) = 0; + // link/unlink the clip models contained by this physics object + virtual void UnlinkClip( void ) = 0; + virtual void LinkClip( void ) = 0; + // contacts + virtual bool EvaluateContacts( void ) = 0; + virtual int GetNumContacts( void ) const = 0; + virtual const contactInfo_t &GetContact( int num ) const = 0; + virtual void ClearContacts( void ) = 0; + virtual void AddContactEntity( idEntity *e ) = 0; + virtual void RemoveContactEntity( idEntity *e ) = 0; + // ground contacts + virtual bool HasGroundContacts( void ) const = 0; + virtual bool IsGroundEntity( int entityNum ) const = 0; + virtual bool IsGroundClipModel( int entityNum, int id ) const = 0; +// RAVEN BEGIN +// abahr + virtual const idVec3 GetContactNormal() const { return vec3_zero; } + virtual const idVec3 GetGroundContactNormal() const { return vec3_zero; } + virtual idEntity* GetSelf() const = 0; +// RAVEN END + // set the master entity for objects bound to a master + virtual void SetMaster( idEntity *master, const bool orientated = true ) = 0; + // set pushed state + virtual void SetPushed( int deltaTime ) = 0; + virtual const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const = 0; + virtual const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const = 0; + // get blocking info, returns NULL if the object is not blocked + virtual const trace_t * GetBlockingInfo( void ) const = 0; + virtual idEntity * GetBlockingEntity( void ) const = 0; + // movement end times in msec for reached events at the end of predefined motion + virtual int GetLinearEndTime( void ) const = 0; + virtual int GetAngularEndTime( void ) const = 0; + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const = 0; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ) = 0; + +// RAVEN BEGIN +// kfuller: we want to debug draw the bbox + virtual void DebugDraw() {} +// RAVEN END + +}; + +#endif /* !__PHYSICS_H__ */ diff --git a/source/game/physics/Physics_AF.cpp b/source/game/physics/Physics_AF.cpp new file mode 100644 index 0000000..f34f741 --- /dev/null +++ b/source/game/physics/Physics_AF.cpp @@ -0,0 +1,8079 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_AF ) +END_CLASS + +const float ERROR_REDUCTION = 0.5f; +const float ERROR_REDUCTION_MAX = 256.0f; +const float LIMIT_ERROR_REDUCTION = 0.3f; +const float LCP_EPSILON = 1e-7f; +const float LIMIT_LCP_EPSILON = 1e-4f; +const float CONTACT_LCP_EPSILON = 1e-6f; +const float CENTER_OF_MASS_EPSILON = 1e-4f; +const float NO_MOVE_TIME = 1.0f; +const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f; +const float NO_MOVE_ROTATION_TOLERANCE = 10.0f; +const float MIN_MOVE_TIME = -1.0f; +const float MAX_MOVE_TIME = -1.0f; +const float IMPULSE_THRESHOLD = 500.0f; +const float SUSPEND_LINEAR_VELOCITY = 10.0f; +const float SUSPEND_ANGULAR_VELOCITY = 15.0f; +const float SUSPEND_LINEAR_ACCELERATION = 20.0f; +const float SUSPEND_ANGULAR_ACCELERATION = 30.0f; +const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON, + LCP_EPSILON, LCP_EPSILON, LCP_EPSILON ); + +#define AF_TIMINGS + +#ifdef AF_TIMINGS +static int lastTimerReset = 0; +static int numArticulatedFigures = 0; +static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp; +#endif + + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +/* +================ +idAFConstraint::idAFConstraint +================ +*/ +idAFConstraint::idAFConstraint( void ) { + type = CONSTRAINT_INVALID; + name = "noname"; + body1 = NULL; + body2 = NULL; + physics = NULL; + + lo.Zero( 6 ); + lo.SubVec6(0) = -vec6_infinity; + hi.Zero( 6 ); + hi.SubVec6(0) = vec6_infinity; + e.SetSize( 6 ); + e.SubVec6(0) = vec6_lcp_epsilon; + + boxConstraint = NULL; + boxIndex[0] = -1; + boxIndex[1] = -1; + boxIndex[2] = -1; + boxIndex[3] = -1; + boxIndex[4] = -1; + boxIndex[5] = -1; + + firstIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); +} + +/* +================ +idAFConstraint::~idAFConstraint +================ +*/ +idAFConstraint::~idAFConstraint( void ) { +} + +/* +================ +idAFConstraint::SetBody1 +================ +*/ +void idAFConstraint::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::SetBody2 +================ +*/ +void idAFConstraint::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::GetMultiplier +================ +*/ +const idVecX &idAFConstraint::GetMultiplier( void ) { + return lm; +} + +/* +================ +idAFConstraint::Evaluate +================ +*/ +void idAFConstraint::Evaluate( float invTimeStep ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::ApplyFriction +================ +*/ +void idAFConstraint::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint::GetForce +================ +*/ +void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) { + idVecX v; + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + if ( body == body1 ) { + J1.TransposeMultiply( v, lm ); + } + else if ( body == body2 ) { + J2.TransposeMultiply( v, lm ); + } + else { + v.Zero(); + } + force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5]; +} + +/* +================ +idAFConstraint::Translate +================ +*/ +void idAFConstraint::Translate( const idVec3 &translation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::Rotate +================ +*/ +void idAFConstraint::Rotate( const idRotation &rotation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::GetCenter +================ +*/ +void idAFConstraint::GetCenter( idVec3 ¢er ) { + center.Zero(); +} + +/* +================ +idAFConstraint::DebugDraw +================ +*/ +void idAFConstraint::DebugDraw( void ) { +} + +/* +================ +idAFConstraint::InitSize +================ +*/ +void idAFConstraint::InitSize( int size ) { + J1.Zero( size, 6 ); + J2.Zero( size, 6 ); + c1.Zero( size ); + c2.Zero( size ); + s.Zero( size ); + lm.Zero( size ); +} + +/* +================ +idAFConstraint::Save +================ +*/ +void idAFConstraint::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( type ); + saveFile->WriteString ( name ); // cnicholson: Added unsaved var + // TOSAVE: idAFBody * body1; + // TOSAVE: idAFBody * body2; + // TOSAVE: idPhysics_AF * physics; + + // TOSAVE: idMatX J1, J2; + // TOSAVE: idVecX c1, c2; + // TOSAVE: idVecX lo, hi, e; + // TOSAVE: idAFConstraint * boxConstraint; + for (int i=0; i<6; ++i) { // cnicholson: Added unsaved var + saveFile->WriteInt ( boxIndex[i] ); + } + + // TOSAVE: idMatX invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX lm; + saveFile->WriteInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ));// cnicholson: Added unsaved var +} + +/* +================ +idAFConstraint::Restore +================ +*/ +void idAFConstraint::Restore( idRestoreGame *saveFile ) { + constraintType_t t; + saveFile->ReadInt( (int &)t ); + assert( t == type ); + + saveFile->ReadString ( name ); // cnicholson: Added unrestored var + + for (int i=0; i<6; ++i) { + saveFile->ReadInt ( boxIndex[i] ); + } + + saveFile->ReadInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Read( &fl, sizeof( fl )); // cnicholson: Added unsaved var +} + + +//=============================================================== +// +// idAFConstraint_Fixed +// +//=============================================================== + +/* +================ +idAFConstraint_Fixed::idAFConstraint_Fixed +================ +*/ +idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_FIXED; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 6 ); + fl.allowPrimary = true; + fl.noCollision = true; + + InitOffset(); +} + +/* +================ +idAFConstraint_Fixed::InitOffset +================ +*/ +void idAFConstraint_Fixed::InitOffset( void ) { + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Fixed::SetBody1 +================ +*/ +void idAFConstraint_Fixed::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::SetBody2 +================ +*/ +void idAFConstraint_Fixed::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::Evaluate +================ +*/ +void idAFConstraint_Fixed::Evaluate( float invTimeStep ) { + idVec3 ofs, a2; + idMat3 ax; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + a2 = offset * master->GetWorldAxis(); + ofs = a2 + master->GetWorldOrigin(); + ax = relAxis * master->GetWorldAxis(); + } + else { + a2.Zero(); + ofs = offset; + ax = relAxis; + } + + J1.Set( mat3_identity, mat3_zero, + mat3_zero, mat3_identity ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, -mat3_identity ); + } + else { + J2.Zero( 6, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() ); + r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation(); + c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Fixed::ApplyFriction +================ +*/ +void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Fixed::Translate +================ +*/ +void idAFConstraint_Fixed::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Fixed::Rotate +================ +*/ +void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + relAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Fixed::GetCenter +================ +*/ +void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin(); +} + +/* +================ +idAFConstraint_Fixed::DebugDraw +================ +*/ +void idAFConstraint_Fixed::DebugDraw( void ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() ); + } + else { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin ); + } +} + +/* +================ +idAFConstraint_Fixed::Save +================ +*/ +void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Fixed::Restore +================ +*/ +void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJoint +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_BALLANDSOCKETJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 3 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetAnchor +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetNoLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetConeLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetFriction +================ +*/ +float idAFConstraint_BallAndSocketJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + J1.Set( mat3_identity, -SkewSymmetric( a1 ) ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ) ); + } + else { + J2.Zero( 3, 6 ); + } + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_BallAndSocketJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetForce +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Translate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetCenter +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::DebugDraw +================ +*/ +void idAFConstraint_BallAndSocketJoint::DebugDraw( void ) { + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Save +================ +*/ +void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( friction ); +// cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); // cnicholson: Bad way to save and restore + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_BallAndSocketJointFriction *fc; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Restore +================ +*/ +void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( friction ); + + // cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_BallAndSocketJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction +================ +*/ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "ballAndSocketJointFriction"; + InitSize( 3 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Setup +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) { + this->joint = bsj; + body1 = bsj->GetBody1(); + body2 = bsj->GetBody2(); +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Add +================ +*/ +bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = lo[2] = -f; + hi[0] = hi[1] = hi[2] = f; + + J1.Zero( 3, 6 ); + J1[0][3] = J1[1][4] = J1[2][5] = 1.0f; + + if ( body2 ) { + + J2.Zero( 3, 6 ); + J2[0][3] = J2[1][4] = J2[2][5] = 1.0f; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Translate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_UniversalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_UNIVERSALJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 4 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetAnchor +================ +*/ +void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_UniversalJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_UniversalJoint::SetShafts +================ +*/ +void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) { + idVec3 cardanAxis; + float l; + + shaft1 = cardanShaft1; + l = shaft1.Normalize(); + assert( l != 0.0f ); + shaft2 = cardanShaft2; + l = shaft2.Normalize(); + assert( l != 0.0f ); + + // the cardan axis is a vector orthogonal to both cardan shafts + cardanAxis = shaft1.Cross( shaft2 ); + if ( cardanAxis.Normalize() == 0.0f ) { + idVec3 vecY; + shaft1.OrthogonalBasis( cardanAxis, vecY ); + cardanAxis.Normalize(); + } + + shaft1 *= body1->GetWorldAxis().Transpose(); + axis1 = cardanAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + shaft2 *= body2->GetWorldAxis().Transpose(); + axis2 = cardanAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = cardanAxis; + } + + if ( coneLimit ) { + coneLimit->SetBody1Axis( shaft1 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetBody1Axis( shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetNoLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetConeLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetFriction +================ +*/ +float idAFConstraint_UniversalJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_UniversalJoint::Evaluate + + NOTE: this joint is homokinetic +================ +*/ +void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = s1.Cross( axis1 * body1->GetWorldAxis() ); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( s1[0], s1[1], s1[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 4, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( s2[0], s2[1], s2[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 4, 6 ); + } + else { + J2.Zero( 4, 6 ); + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_UniversalJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetForce +================ +*/ +void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_UniversalJoint::Translate +================ +*/ +void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Rotate +================ +*/ +void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + shaft2 *= rotation.ToMat3(); + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetCenter +================ +*/ +void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_UniversalJoint::DebugDraw +================ +*/ +void idAFConstraint_UniversalJoint::DebugDraw( void ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = axis1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f ); + gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f ); + gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f ); + gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_UniversalJoint::Save +================ +*/ +void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( shaft1 ); + saveFile->WriteVec3( shaft2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteFloat( friction ); + + // cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_UniversalJointFriction *fc; +} + +/* +================ +idAFConstraint_UniversalJoint::Restore +================ +*/ +void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( shaft1 ); + saveFile->ReadVec3( shaft2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadFloat( friction ); + +// cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_UniversalJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_UniversalJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction +================ +*/ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "universalJointFriction"; + InitSize( 2 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Setup +================ +*/ +void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) { + this->joint = uj; + body1 = uj->GetBody1(); + body2 = uj->GetBody2(); +} + +/* +================ +idAFConstraint_UniversalJointFriction::Evaluate +================ +*/ +void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::Add +================ +*/ +bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 s1, s2, dir1, dir2; + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = -f; + hi[0] = hi[1] = f; + + joint->GetShafts( s1, s2 ); + + s1 *= body1->GetWorldAxis(); + s1.NormalVectors( dir1, dir2 ); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = dir1; + J1.SubVec6(1).SubVec3(0).Zero(); + J1.SubVec6(1).SubVec3(1) = dir2; + + if ( body2 ) { + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -dir1; + J2.SubVec6(1).SubVec3(0).Zero(); + J2.SubVec6(1).SubVec3(1) = -dir2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Translate +================ +*/ +void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_UniversalJointFriction::Rotate +================ +*/ +void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_CylindricalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint +================ +*/ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Evaluate +================ +*/ +void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::ApplyFriction +================ +*/ +void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Translate +================ +*/ +void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Rotate +================ +*/ +void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::DebugDraw +================ +*/ +void idAFConstraint_CylindricalJoint::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Hinge +// +//=============================================================== + +/* +================ +idAFConstraint_Hinge::idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_HINGE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + coneLimit = NULL; + steering = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; + initialAxis = body1->GetWorldAxis(); + if ( body2 ) { + initialAxis *= body2->GetWorldAxis().Transpose(); + } +} + +/* +================ +idAFConstraint_Hinge::~idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::~idAFConstraint_Hinge( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( fc ) { + delete fc; + } + if ( steering ) { + delete steering; + } +} + +/* +================ +idAFConstraint_Hinge::SetAnchor +================ +*/ +void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_Hinge::GetAnchor +================ +*/ +idVec3 idAFConstraint_Hinge::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_Hinge::SetAxis +================ +*/ +void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) { + idVec3 normAxis; + + normAxis = axis; + normAxis.Normalize(); + + // get axis relative to body1 + axis1 = normAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get axis relative to body2 + axis2 = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = normAxis; + } +} + +/* +================ +idAFConstraint_Hinge::GetAxis +================ +*/ +idVec3 idAFConstraint_Hinge::GetAxis( void ) const { + if ( body2 ) { + return axis2 * body2->GetWorldAxis(); + } + return axis2; +} + +/* +================ +idAFConstraint_Hinge::SetNoLimit +================ +*/ +void idAFConstraint_Hinge::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } +} + +/* +================ +idAFConstraint_Hinge::SetLimit +================ +*/ +void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_Hinge::SetLimitEpsilon +================ +*/ +void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_Hinge::GetFriction +================ +*/ +float idAFConstraint_Hinge::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_Hinge::GetAngle +================ +*/ +float idAFConstraint_Hinge::GetAngle( void ) const { + idMat3 axis; + idRotation rotation; + float angle; + + axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose(); + rotation = axis.ToRotation(); + angle = rotation.GetAngle(); + if ( rotation.GetVec() * axis1 < 0.0f ) { + return -angle; + } + return angle; +} + +/* +================ +idAFConstraint_Hinge::SetSteerAngle +================ +*/ +void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !steering ) { + steering = new idAFConstraint_HingeSteering(); + steering->Setup( this ); + } + steering->SetSteerAngle( degrees ); +} + +/* +================ +idAFConstraint_Hinge::SetSteerSpeed +================ +*/ +void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) { + if ( steering ) { + steering->SetSteerSpeed( speed ); + } +} + +/* +================ +idAFConstraint_Hinge::Evaluate +================ +*/ +void idAFConstraint_Hinge::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idVec3 x1, x2, cross; + idVec3 vecX, vecY; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space + x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space + + a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); // anchor in master space + x2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + x2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( vecX[0], vecX[1], vecX[2], + vecY[0], vecY[1], vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2], + -vecY[0], -vecY[1], -vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + cross = x1.Cross( x2 ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( steering ) { + steering->Add( physics, invTimeStep ); + } + else if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::ApplyFriction +================ +*/ +void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::GetForce +================ +*/ +void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_Hinge::Translate +================ +*/ +void idAFConstraint_Hinge::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_Hinge::Rotate +================ +*/ +void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_Hinge::GetCenter +================ +*/ +void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Hinge::DebugDraw +================ +*/ +void idAFConstraint_Hinge::DebugDraw( void ) { + idVec3 vecX, vecY; + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + idVec3 x1 = axis1 * body1->GetWorldAxis(); + x1.OrthogonalBasis( vecX, vecY ); + + gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_Hinge::Save +================ +*/ +void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteMat3( initialAxis ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + saveFile->WriteBool( true ); + coneLimit->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( steering ) { + saveFile->WriteBool( true ); + steering->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( fc ) { + saveFile->WriteBool( true ); + fc->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } +} + +/* +================ +idAFConstraint_Hinge::Restore +================ +*/ +void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) { + bool b; + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadMat3( initialAxis ); + saveFile->ReadFloat( friction ); + + saveFile->ReadBool( b ); + if ( b ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + } + coneLimit->SetPhysics( physics ); + coneLimit->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !steering ) { + steering = new idAFConstraint_HingeSteering; + } + steering->Setup( this ); + steering->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + } + fc->Setup( this ); + fc->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_HingeFriction +// +//=============================================================== + +/* +================ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction +================ +*/ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_HingeFriction::Setup +================ +*/ +void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeFriction::Evaluate +================ +*/ +void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::ApplyFriction +================ +*/ +void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::Add +================ +*/ +bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 a1, a2; + float f; + + physics = phys; + + f = hinge->GetFriction() * hinge->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = -f; + hi[0] = f; + + hinge->GetAxis( a1, a2 ); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeFriction::Translate +================ +*/ +void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeFriction::Rotate +================ +*/ +void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_HingeSteering +// +//=============================================================== + +/* +================ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering +================ +*/ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering( void ) { + type = CONSTRAINT_HINGESTEERING; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; + steerSpeed = 0.0f; + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_HingeSteering::Save +================ +*/ +void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const { + + saveFile->WriteFloat(steerAngle); + saveFile->WriteFloat(steerSpeed); + saveFile->WriteFloat(epsilon); + + //if ( hinge ) { // cnicholson: Added unsaved var (doesnt work) + // saveFile->WriteBool( true ); + // hinge->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Restore +================ +*/ +void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadFloat(steerAngle); + saveFile->ReadFloat(steerSpeed); + saveFile->ReadFloat(epsilon); + +// cnicholson: Added unrestored var (doesnt work) + //bool b; + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !hinge ) { + // hinge = new idAFConstraint_Hinge; + // } + // hinge->SetSteerAngle( steerAngle ); + // hinge->SetSteerSpeed( steerSpeed ); + // hinge->SetLimitEpsilon( epsilon ); + // hinge->Restore( saveFile ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Setup +================ +*/ +void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeSteering::Evaluate +================ +*/ +void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::ApplyFriction +================ +*/ +void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::Add +================ +*/ +bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) { + float angle, speed; + idVec3 a1, a2; + + physics = phys; + + hinge->GetAxis( a1, a2 ); + angle = hinge->GetAngle(); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + speed = steerAngle - angle; + if ( steerSpeed != 0.0f ) { + if ( speed > steerSpeed ) { + speed = steerSpeed; + } + else if ( speed < -steerSpeed ) { + speed = -steerSpeed; + } + } + + c1[0] = DEG2RAD( speed ) * invTimeStep; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeSteering::Translate +================ +*/ +void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeSteering::Rotate +================ +*/ +void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_Slider +// +//=============================================================== + +/* +================ +idAFConstraint_Slider::idAFConstraint_Slider +================ +*/ +idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SLIDER; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + fl.allowPrimary = true; + fl.noCollision = true; + + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Slider::SetAxis +================ +*/ +void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) { + idVec3 normAxis; + + // get normalized axis relative to body1 + normAxis = ax; + normAxis.Normalize(); + if ( body2 ) { + axis = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis = normAxis; + } +} + +/* +================ +idAFConstraint_Slider::Evaluate +================ +*/ +void idAFConstraint_Slider::Evaluate( float invTimeStep ) { + idVec3 vecX, vecY, ofs; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + (axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY ); + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation(); + } + else { + axis.OrthogonalBasis( vecX, vecY ); + ofs = offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation(); + } + + J1.Set( mat3_zero, mat3_identity, + idMat3( vecX, vecY, vec3_origin ), mat3_zero ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + + J2.Set( mat3_zero, -mat3_identity, + idMat3( -vecX, -vecY, vec3_origin ), mat3_zero ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Slider::ApplyFriction +================ +*/ +void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Slider::Translate +================ +*/ +void idAFConstraint_Slider::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Slider::Rotate +================ +*/ +void idAFConstraint_Slider::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + } +} + +/* +================ +idAFConstraint_Slider::GetCenter +================ +*/ +void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + center = offset - body1->GetWorldOrigin(); + } +} + +/* +================ +idAFConstraint_Slider::DebugDraw +================ +*/ +void idAFConstraint_Slider::DebugDraw( void ) { + idVec3 ofs; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + ofs = offset - body1->GetWorldOrigin(); + } + gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() ); +} + +/* +================ +idAFConstraint_Slider::Save +================ +*/ +void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( axis ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Slider::Restore +================ +*/ +void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( axis ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_Line +// +//=============================================================== + +/* +================ +idAFConstraint_Line::idAFConstraint_Line +================ +*/ +idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Evaluate +================ +*/ +void idAFConstraint_Line::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::ApplyFriction +================ +*/ +void idAFConstraint_Line::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Translate +================ +*/ +void idAFConstraint_Line::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Rotate +================ +*/ +void idAFConstraint_Line::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::DebugDraw +================ +*/ +void idAFConstraint_Line::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Plane +// +//=============================================================== + +/* +================ +idAFConstraint_Plane::idAFConstraint_Plane +================ +*/ +idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_PLANE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_Plane::SetPlane +================ +*/ +void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) { + // get anchor relative to center of mass of body1 + anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + planeNormal = normal * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = anchor; + planeNormal = normal; + } +} + +/* +================ +idAFConstraint_Plane::Evaluate +================ +*/ +void idAFConstraint_Plane::Evaluate( float invTimeStep ) { + idVec3 a1, a2, normal, p; + idVec6 v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + normal = planeNormal * master->GetWorldAxis(); + } + else { + a2 = anchor2; + normal = planeNormal; + } + + p = a1 - body1->GetWorldOrigin(); + v.SubVec3(0) = normal; + v.SubVec3(1) = p.Cross( normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + + if ( body2 ) { + p = a1 - body2->GetWorldOrigin(); + v.SubVec3(0) = -normal; + v.SubVec3(1) = p.Cross( -normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + } + + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Plane::ApplyFriction +================ +*/ +void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Plane::Translate +================ +*/ +void idAFConstraint_Plane::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Plane::Rotate +================ +*/ +void idAFConstraint_Plane::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + planeNormal *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Plane::DebugDraw +================ +*/ +void idAFConstraint_Plane::DebugDraw( void ) { + idVec3 a1, normal, right, up; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + normal = planeNormal * master->GetWorldAxis(); + } + else { + normal = planeNormal; + } + normal.NormalVectors( right, up ); + normal *= 4.0f; + right *= 4.0f; + up *= 4.0f; + + gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right ); + gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up ); + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 ); +} + +/* +================ +idAFConstraint_Plane::Save +================ +*/ +void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( planeNormal ); +} + +/* +================ +idAFConstraint_Plane::Restore +================ +*/ +void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( planeNormal ); +} + + +//=============================================================== +// +// idAFConstraint_Spring +// +//=============================================================== + +/* +================ +idAFConstraint_Spring::idAFConstraint_Spring +================ +*/ +idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SPRING; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = false; + kstretch = kcompress = damping = 1.0f; + minLength = maxLength = restLength = 0.0f; +} + +/* +================ +idAFConstraint_Spring::SetAnchor +================ +*/ +void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldAnchor2; + } +} + +/* +================ +idAFConstraint_Spring::SetSpring +================ +*/ +void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) { + assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f ); + this->kstretch = stretch; + this->kcompress = compress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idAFConstraint_Spring::SetLimit +================ +*/ +void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) { + assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength ); + this->minLength = minLength; + this->maxLength = maxLength; +} + +/* +================ +idAFConstraint_Spring::Evaluate +================ +*/ +void idAFConstraint_Spring::Evaluate( float invTimeStep ) { + idVec3 a1, a2, velocity1, velocity2, force; + idVec6 v1, v2; + float d, dampingForce, length, error; + bool limit; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + velocity1 = body1->GetPointVelocity( a1 ); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + velocity2 = master->GetPointVelocity( a2 ); + } + else { + a2 = anchor2; + velocity2.Zero(); + } + + force = a2 - a1; + d = force * force; + if ( d != 0.0f ) { + dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d; + } + else { + dampingForce = 0.0f; + } + length = force.Normalize(); + + if ( length > restLength ) { + if ( kstretch > 0.0f ) { + idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + else { + if ( kcompress > 0.0f ) { + idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + + // check for spring limits + if ( length < minLength ) { + force = -force; + error = minLength - length; + limit = true; + } + else if ( maxLength > 0.0f && length > maxLength ) { + error = length - maxLength; + limit = true; + } + else { + error = 0.0f; + limit = false; + } + + if ( limit ) { + a1 -= body1->GetWorldOrigin(); + v1.SubVec3(0) = force; + v1.SubVec3(1) = a1.Cross( force ); + J1.Set( 1, 6, v1.ToFloatPtr() ); + if ( body2 ) { + a2 -= body2->GetWorldOrigin(); + v2.SubVec3(0) = -force; + v2.SubVec3(1) = a2.Cross( -force ); + J2.Set( 1, 6, v2.ToFloatPtr() ); + } + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error; + lo[0] = 0.0f; + } + else { + J1.Zero( 0, 0 ); + J2.Zero( 0, 0 ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Spring::ApplyFriction +================ +*/ +void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Spring::Translate +================ +*/ +void idAFConstraint_Spring::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Spring::Rotate +================ +*/ +void idAFConstraint_Spring::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } +} + +/* +================ +idAFConstraint_Spring::GetCenter +================ +*/ +void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) { + idAFBody *master; + idVec3 a1, a2; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + center = ( a1 + a2 ) * 0.5f; +} + +/* +================ +idAFConstraint_Spring::DebugDraw +================ +*/ +void idAFConstraint_Spring::DebugDraw( void ) { + idAFBody *master; + float length; + idVec3 a1, a2, dir, mid, p; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + dir = a2 - a1; + mid = a1 + 0.5f * dir; + length = dir.Normalize(); + + // draw spring + gameRenderWorld->DebugLine( colorGreen, a1, a2 ); + + // draw rest length + p = restLength * 0.5f * dir; + gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 ); + gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 ); + if ( restLength > length ) { + gameRenderWorld->DebugLine( colorWhite, a2, mid + p ); + gameRenderWorld->DebugLine( colorWhite, a1, mid - p ); + } + + if ( minLength > 0.0f ) { + // draw min length + gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 ); + } + + if ( maxLength > 0.0f ) { + // draw max length + gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 ); + } +} + +/* +================ +idAFConstraint_Spring::Save +================ +*/ +void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( kstretch ); + saveFile->WriteFloat( kcompress ); + saveFile->WriteFloat( damping ); + saveFile->WriteFloat( restLength ); + saveFile->WriteFloat( minLength ); + saveFile->WriteFloat( maxLength ); +} + +/* +================ +idAFConstraint_Spring::Restore +================ +*/ +void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( kstretch ); + saveFile->ReadFloat( kcompress ); + saveFile->ReadFloat( damping ); + saveFile->ReadFloat( restLength ); + saveFile->ReadFloat( minLength ); + saveFile->ReadFloat( maxLength ); +} + + +//=============================================================== +// +// idAFConstraint_Contact +// +//=============================================================== + +/* +================ +idAFConstraint_Contact::idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::idAFConstraint_Contact( void ) { + name = "contact"; + type = CONSTRAINT_CONTACT; + InitSize( 1 ); + fc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_Contact::~idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::~idAFConstraint_Contact( void ) { + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_Contact::Setup +================ +*/ +void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) { + idVec3 p; + idVec6 v; + float vel; + + assert( b1 ); + + body1 = b1; + body2 = b2; + contact = c; + + p = c.point - body1->GetWorldOrigin(); + v.SubVec3(0) = c.normal; + v.SubVec3(1) = p.Cross( c.normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + p = c.point - body2->GetWorldOrigin(); + v.SubVec3(0) = -c.normal; + v.SubVec3(1) = p.Cross( -c.normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity(); + c2[0] = 0.0f; + } + + if ( body1->GetBouncyness() > 0.0f ) { + c1[0] = body1->GetBouncyness() * -vel; + } + else { + c1[0] = 0.0f; + } + + e[0] = CONTACT_LCP_EPSILON; + lo[0] = 0.0f; + hi[0] = idMath::INFINITY; + boxConstraint = NULL; + boxIndex[0] = -1; +} + +/* +================ +idAFConstraint_Contact::Evaluate +================ +*/ +void idAFConstraint_Contact::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Contact::ApplyFriction +================ +*/ +void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) { + idVec3 r, velocity, normal, dir1, dir2; + float friction, magnitude, forceNumerator, forceDenominator; + idVecX impulse, dv; + + friction = body1->GetContactFriction(); + if ( body2 && body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + + friction *= physics->GetContactFrictionScale(); + + if ( friction <= 0.0f ) { + return; + } + + // seperate friction per contact is silly but it's fast and often looks close enough + if ( af_useImpulseFriction.GetBool() ) { + + impulse.SetData( 6, VECX_ALLOCA( 6 ) ); + dv.SetData( 6, VECX_ALLOCA( 6 ) ); + + // calculate velocity in the contact plane + r = contact.point - body1->GetWorldOrigin(); + velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r ); + velocity -= contact.normal * velocity * contact.normal; + + // get normalized direction of friction and magnitude of velocity + normal = -velocity; + magnitude = normal.Normalize(); + + forceNumerator = friction * magnitude; + forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal; + impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) ); + body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() ); + + // modify velocity with friction force + body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) ); + body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) ); + } + else { + + if ( !fc ) { + fc = new idAFConstraint_ContactFriction; + } + // call setup each frame because contact constraints are re-used for different bodies + fc->Setup( this ); + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Contact::Translate +================ +*/ +void idAFConstraint_Contact::Translate( const idVec3 &translation ) { + assert( 0 ); // contact should never be translated +} + +/* +================ +idAFConstraint_Contact::Rotate +================ +*/ +void idAFConstraint_Contact::Rotate( const idRotation &rotation ) { + assert( 0 ); // contact should never be rotated +} + +/* +================ +idAFConstraint_Contact::GetCenter +================ +*/ +void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) { + center = contact.point; +} + +/* +================ +idAFConstraint_Contact::DebugDraw +================ +*/ +void idAFConstraint_Contact::DebugDraw( void ) { + idVec3 x, y; + contact.normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y ); +} + + +//=============================================================== +// +// idAFConstraint_ContactFriction +// +//=============================================================== + +/* +================ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction +================ +*/ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "contactFriction"; + InitSize( 2 ); + cc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ContactFriction::Setup +================ +*/ +void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) { + this->cc = cc; + body1 = cc->GetBody1(); + body2 = cc->GetBody2(); +} + +/* +================ +idAFConstraint_ContactFriction::Evaluate +================ +*/ +void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::ApplyFriction +================ +*/ +void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::Add +================ +*/ +bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 r, dir1, dir2; + float friction; + int newRow; + + physics = phys; + + friction = body1->GetContactFriction() * physics->GetContactFrictionScale(); + + // if the body only has friction in one direction + if ( body1->GetFrictionDirection( dir1 ) ) { + // project the friction direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + c1.SetSize( 1 ); + c1[0] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + c2.SetSize( 1 ); + c2[0] = 0.0f; + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + } + else { + // get two friction directions orthogonal to contact normal + cc->GetContact().normal.NormalVectors( dir1, dir2 ); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + J1.SubVec6(1).SubVec3(0) = dir2; + J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 ); + c1.SetSize( 2 ); + c1[0] = c1[1] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + J2.SubVec6(1).SubVec3(0) = -dir2; + J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 ); + c2.SetSize( 2 ); + c2[0] = c2[1] = 0.0f; + + if ( body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + lo[1] = -friction; + hi[1] = friction; + boxIndex[1] = 0; + } + + if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) { + // project the motor force direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + newRow = J1.GetNumRows(); + J1.ChangeSize( newRow+1, J1.GetNumColumns() ); + J1.SubVec6(newRow).SubVec3(0) = -dir1; + J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c1.ChangeSize( newRow+1 ); + c1[newRow] = body1->GetContactMotorVelocity(); + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.ChangeSize( newRow+1, J2.GetNumColumns() ); + J2.SubVec6(newRow).SubVec3(0) = -dir1; + J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c2.ChangeSize( newRow+1 ); + c2[newRow] = 0.0f; + } + + lo[newRow] = -body1->GetContactMotorForce(); + hi[newRow] = body1->GetContactMotorForce(); + boxIndex[newRow] = -1; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ContactFriction::Translate +================ +*/ +void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_ContactFriction::Rotate +================ +*/ +void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_ContactFriction::DebugDraw +================ +*/ +void idAFConstraint_ContactFriction::DebugDraw( void ) { +} + + +//=============================================================== +// +// idAFConstraint_ConeLimit +// +//=============================================================== + +/* +================ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit +================ +*/ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit( void ) { + type = CONSTRAINT_CONELIMIT; + name = "coneLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ConeLimit::Setup + + the coneAnchor is the top of the cone in body2 space + the coneAxis is the axis of the cone in body2 space + the coneAngle is the angle the cone hull makes at the top + the body1Axis is the axis in body1 space that should stay within the cone +================ +*/ +void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + this->body1 = b1; + this->body2 = b2; + this->coneAxis = coneAxis; + this->coneAxis.Normalize(); + this->coneAnchor = coneAnchor; + this->body1Axis = body1Axis; + this->body1Axis.Normalize(); + this->cosAngle = idMath::Cos( DEG2RAD( coneAngle * 0.5f ) ); + this->sinHalfAngle = idMath::Sin( DEG2RAD( coneAngle * 0.25f ) ); + this->cosHalfAngle = idMath::Cos( DEG2RAD( coneAngle * 0.25f ) ); +} + +/* +================ +idAFConstraint_ConeLimit::SetAnchor +================ +*/ +void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) { + this->coneAnchor = coneAnchor; +} + +/* +================ +idAFConstraint_ConeLimit::SetBody1Axis +================ +*/ +void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_ConeLimit::Evaluate +================ +*/ +void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ConeLimit::ApplyFriction +================ +*/ +void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_ConeLimit::Add +================ +*/ +bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + float a; + idVec6 J1row, J2row; + idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + a = ax * body1ax; + + // if the body1 axis is inside the cone + if ( a > cosAngle ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward cone normal for the position the body1 axis went outside the cone + normal = body1ax.Cross( ax ); + normal.Normalize(); + q.x = normal.x * sinHalfAngle; + q.y = normal.y * sinHalfAngle; + q.z = normal.z * sinHalfAngle; + q.w = cosHalfAngle; + coneVector = ax * q.ToMat3(); + normal = coneVector.Cross( ax ).Cross( coneVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ConeLimit::Translate +================ +*/ +void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + coneAnchor += translation; + } +} + +/* +================ +idAFConstraint_ConeLimit::Rotate +================ +*/ +void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + coneAnchor *= rotation; + coneAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_ConeLimit::DebugDraw +================ +*/ +void idAFConstraint_ConeLimit::DebugDraw( void ) { + idVec3 ax, anchor, x, y, z, start, end; + float sinAngle, a, size = 10.0f; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw cone + ax.NormalVectors( x, y ); + sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle ); + x *= size * sinAngle; + y *= size * sinAngle; + z = anchor + ax * size * cosAngle; + start = x + z; + for ( a = 0.0f; a < 360.0f; a += 45.0f ) { + end = x * idMath::Cos( DEG2RAD(a + 45.0f) ) + y * idMath::Sin( DEG2RAD(a + 45.0f) ) + z; + gameRenderWorld->DebugLine( colorMagenta, anchor, start ); + gameRenderWorld->DebugLine( colorMagenta, start, end ); + start = end; + } +} + +/* +================ +idAFConstraint_ConeLimit::Save +================ +*/ +void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( coneAnchor ); + saveFile->WriteVec3( coneAxis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle ); + saveFile->WriteFloat( sinHalfAngle ); + saveFile->WriteFloat( cosHalfAngle ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_ConeLimit::Restore +================ +*/ +void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( coneAnchor ); + saveFile->ReadVec3( coneAxis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle ); + saveFile->ReadFloat( sinHalfAngle ); + saveFile->ReadFloat( cosHalfAngle ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_PyramidLimit +// +//=============================================================== + +/* +================ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit +================ +*/ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit( void ) { + type = CONSTRAINT_PYRAMIDLIMIT; + name = "pyramidLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_PyramidLimit::Setup +================ +*/ +void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) { + body1 = b1; + body2 = b2; + // setup the base and make sure the basis is orthonormal + pyramidBasis[2] = pyramidAxis; + pyramidBasis[2].Normalize(); + pyramidBasis[0] = baseAxis; + pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2]; + pyramidBasis[0].Normalize(); + pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] ); + // pyramid top + this->pyramidAnchor = pyramidAnchor; + // angles + cosAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.5f ) ); + cosAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.5f ) ); + sinHalfAngle[0] = idMath::Sin( DEG2RAD( pyramidAngle1 * 0.25f ) ); + sinHalfAngle[1] = idMath::Sin( DEG2RAD( pyramidAngle2 * 0.25f ) ); + cosHalfAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.25f ) ); + cosHalfAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.25f ) ); + + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::SetAnchor +================ +*/ +void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) { + this->pyramidAnchor = pyramidAnchor; +} + +/* +================ +idAFConstraint_PyramidLimit::SetBody1Axis +================ +*/ +void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::Evaluate +================ +*/ +void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_PyramidLimit::ApplyFriction +================ +*/ +void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_PyramidLimit::Add +================ +*/ +bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + int i; + float a[2]; + idVec6 J1row, J2row; + idMat3 worldBase; + idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + for ( i = 0; i < 2; i++ ) { + ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i]; + ax[i].Normalize(); + a[i] = worldBase[2] * ax[i]; + } + + // if the body1 axis is inside the pyramid + if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward pyramid normal for the position the body1 axis went outside the pyramid + pyramidVector = worldBase[2]; + for ( i = 0; i < 2; i++ ) { + if ( a[i] <= cosAngle[i] ) { + v = ax[i].Cross( worldBase[2] ); + v.Normalize(); + q.x = v.x * sinHalfAngle[i]; + q.y = v.y * sinHalfAngle[i]; + q.z = v.z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + pyramidVector *= q.ToMat3(); + } + } + normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_PyramidLimit::Translate +================ +*/ +void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + pyramidAnchor += translation; + } +} + +/* +================ +idAFConstraint_PyramidLimit::Rotate +================ +*/ +void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + pyramidAnchor *= rotation; + pyramidBasis[0] *= rotation.ToMat3(); + pyramidBasis[1] *= rotation.ToMat3(); + pyramidBasis[2] *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_PyramidLimit::DebugDraw +================ +*/ +void idAFConstraint_PyramidLimit::DebugDraw( void ) { + int i; + float size = 10.0f; + idVec3 anchor, dir, p[4]; + idMat3 worldBase, m[2]; + idQuat q; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw the pyramid + for ( i = 0; i < 2; i++ ) { + q.x = worldBase[!i].x * sinHalfAngle[i]; + q.y = worldBase[!i].y * sinHalfAngle[i]; + q.z = worldBase[!i].z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + m[i] = q.ToMat3(); + } + + dir = worldBase[2] * size; + p[0] = anchor + m[0] * (m[1] * dir); + p[1] = anchor + m[0] * (m[1].Transpose() * dir); + p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir); + p[3] = anchor + m[0].Transpose() * (m[1] * dir); + + for ( i = 0; i < 4; i++ ) { + gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] ); + gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] ); + } +} + +/* +================ +idAFConstraint_PyramidLimit::Save +================ +*/ +void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( pyramidAnchor ); + saveFile->WriteMat3( pyramidBasis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle[0] ); + saveFile->WriteFloat( cosAngle[1] ); + saveFile->WriteFloat( sinHalfAngle[0] ); + saveFile->WriteFloat( sinHalfAngle[1] ); + saveFile->WriteFloat( cosHalfAngle[0] ); + saveFile->WriteFloat( cosHalfAngle[1] ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_PyramidLimit::Restore +================ +*/ +void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( pyramidAnchor ); + saveFile->ReadMat3( pyramidBasis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle[0] ); + saveFile->ReadFloat( cosAngle[1] ); + saveFile->ReadFloat( sinHalfAngle[0] ); + saveFile->ReadFloat( sinHalfAngle[1] ); + saveFile->ReadFloat( cosHalfAngle[0] ); + saveFile->ReadFloat( cosHalfAngle[1] ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( void ) { + Init(); +} + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) { + + assert( clipModel ); + assert( clipModel->IsTraceModel() ); + + Init(); + + this->name = name; + this->clipModel = NULL; + + SetClipModel( clipModel ); + SetDensity( density ); + + current->worldOrigin = clipModel->GetOrigin(); + current->worldAxis = clipModel->GetAxis(); + *next = *current; + +} + +/* +================ +idAFBody::~idAFBody +================ +*/ +idAFBody::~idAFBody( void ) { + delete clipModel; +} + +/* +================ +idAFBody::Init +================ +*/ +void idAFBody::Init( void ) { + name = "noname"; + parent = NULL; + clipModel = NULL; + primaryConstraint = NULL; + tree = NULL; + + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + clipMask = 0; + + frictionDir = vec3_zero; + contactMotorDir = vec3_zero; + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + + mass = 1.0f; + invMass = 1.0f; + centerOfMass = vec3_zero; + inertiaTensor = mat3_identity; + inverseInertiaTensor = mat3_identity; + + current = &state[0]; + next = &state[1]; + current->worldOrigin = vec3_zero; + current->worldAxis = mat3_identity; + current->spatialVelocity = vec6_zero; + current->externalForce = vec6_zero; + *next = *current; + saved = *current; + atRestOrigin = vec3_zero; + atRestAxis = mat3_identity; + + s.Zero( 6 ); + totalForce.Zero( 6 ); + auxForce.Zero( 6 ); + acceleration.Zero( 6 ); + + response = NULL; + responseIndex = NULL; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); + + fl.selfCollision = true; + fl.isZero = true; +} + +/* +================ +idAFBody::SetClipModel +================ +*/ +void idAFBody::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && this->clipModel != clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idAFBody::SetFriction +================ +*/ +void idAFBody::SetFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f ) { + gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact ); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idAFBody::SetBouncyness +================ +*/ +void idAFBody::SetBouncyness( float bounce ) { + if ( bounce < 0.0f || bounce > 1.0f ) { + gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce ); + return; + } + bouncyness = bounce; +} + +/* +================ +idAFBody::SetDensity +================ +*/ +void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) { + + // get the body mass properties + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // make sure we have a valid mass + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // make sure the center of mass is at the body origin + if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() ); + } + centerOfMass.Zero(); + + // calculate the inverse mass and inverse inertia tensor + invMass = 1.0f / mass; + if ( inertiaScale != mat3_identity ) { + inertiaTensor *= inertiaScale; + } + if ( inertiaTensor.IsDiagonal( 1e-3f ) ) { + inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f; + inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f; + inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f; + inverseInertiaTensor.Identity(); + inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0]; + inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1]; + inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2]; + } + else { + inverseInertiaTensor = inertiaTensor.Inverse(); + } +} + +/* +================ +idAFBody::SetFrictionDirection +================ +*/ +void idAFBody::SetFrictionDirection( const idVec3 &dir ) { + frictionDir = dir * current->worldAxis.Transpose(); + fl.useFrictionDir = true; +} + +/* +================ +idAFBody::GetFrictionDirection +================ +*/ +bool idAFBody::GetFrictionDirection( idVec3 &dir ) const { + if ( fl.useFrictionDir ) { + dir = frictionDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::SetContactMotorDirection +================ +*/ +void idAFBody::SetContactMotorDirection( const idVec3 &dir ) { + contactMotorDir = dir * current->worldAxis.Transpose(); + fl.useContactMotorDir = true; +} + +/* +================ +idAFBody::GetContactMotorDirection +================ +*/ +bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const { + if ( fl.useContactMotorDir ) { + dir = contactMotorDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::GetPointVelocity +================ +*/ +idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const { + idVec3 r = point - current->worldOrigin; + return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r ); +} + +/* +================ +idAFBody::AddForce +================ +*/ +void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) { + current->externalForce.SubVec3(0) += force; + current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force ); +} + +/* +================ +idAFBody::InverseWorldSpatialInertiaMultiply + + dst = this->inverseWorldSpatialInertia * v; +================ +*/ +ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const { + const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr(); + const float *vPtr = v; + float *dstPtr = dst.ToFloatPtr(); + + if ( fl.spatialInertiaSparse ) { + dstPtr[0] = mPtr[0*6+0] * vPtr[0]; + dstPtr[1] = mPtr[1*6+1] * vPtr[1]; + dstPtr[2] = mPtr[2*6+2] * vPtr[2]; + dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5]; + dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5]; + dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5]; + } else { + gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() ); + } +} + +/* +================ +idAFBody::Save +================ +*/ +void idAFBody::Save( idSaveGame *saveFile ) { + + saveFile->WriteString( name ); // cniciholson: Added Unsaved Var + //if ( parent ) { + // saveFile->WriteBool( true ); + // parent->Save( savefile ); // cniciholson: Added Unsaved Var + //} + //else { + // saveFile->WriteBool( false ); + //} + // TOSAVE: idList children + // TOSAVE: idClipModel * clipModel + // TOSAVE: idAFConstraint * primaryConstraint + // TOSAVE: idList constraints + // TOSAVE: idAFTree * tree; + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteInt( clipMask ); + saveFile->WriteVec3( frictionDir ); + saveFile->WriteVec3( contactMotorDir ); + saveFile->WriteFloat( contactMotorVelocity ); + saveFile->WriteFloat( contactMotorForce ); + + saveFile->WriteFloat( mass ); + saveFile->WriteFloat( invMass ); + saveFile->WriteVec3( centerOfMass ); + saveFile->WriteMat3( inertiaTensor ); + saveFile->WriteMat3( inverseInertiaTensor ); + + //saveFile->WriteVec3( state[0].worldOrigin ); // cnicholson: Added unsaved var state[0] + //saveFile->WriteMat3( state[0].worldAxis ); + //saveFile->WriteVec6( state[0].spatialVelocity ); + //saveFile->WriteVec6( state[0].externalForce ); + // + //saveFile->WriteVec3( state[1].worldOrigin ); // cnicholson: Added unsaved var state[1] + //saveFile->WriteMat3( state[1].worldAxis ); + //saveFile->WriteVec6( state[1].spatialVelocity ); + //saveFile->WriteVec6( state[1].externalForce ); + + saveFile->WriteVec3( current->worldOrigin ); + saveFile->WriteMat3( current->worldAxis ); + saveFile->WriteVec6( current->spatialVelocity ); + saveFile->WriteVec6( current->externalForce ); + + //saveFile->WriteVec3( next->worldOrigin ); // cnicholson: Added unsaved var next-> + //saveFile->WriteMat3( next->worldAxis ); + //saveFile->WriteVec6( next->spatialVelocity ); + //saveFile->WriteVec6( next->externalForce ); + // + //saveFile->WriteVec3( saved.worldOrigin ); // cnicholson: Added unsaved var saved + //saveFile->WriteMat3( saved.worldAxis ); + //saveFile->WriteVec6( saved.spatialVelocity ); + //saveFile->WriteVec6( saved.externalForce ); + + saveFile->WriteVec3( atRestOrigin ); + saveFile->WriteMat3( atRestAxis ); + + // TOSAVE: idMatX inverseWorldSpatialInerti + // TOSAVE: idMatX I, invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX totalForce; + // TOSAVE: idVecX auxForce; + // TOSAVE: idVecX acceleration; + // TOSAVE: float * response; + // TOSAVE: int * responseIndex; + saveFile->WriteInt( numResponses ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxAuxiliaryIndex ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ) ); // cnicholson: Added unsaved var +} + +/* +================ +idAFBody::Restore +================ +*/ +void idAFBody::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadString( name ); // cniciholson: Added Unrestored Var + // TORESTORE: idList parent + // TORESTORE: idList children + // TORESTORE: idClipModel * clipModel + // TORESTORE: idAFConstraint * primaryConstraint + // TORESTORE: idList constraints + // TORESTORE: idAFTree * tree; + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadInt( clipMask ); + saveFile->ReadVec3( frictionDir ); + saveFile->ReadVec3( contactMotorDir ); + saveFile->ReadFloat( contactMotorVelocity ); + saveFile->ReadFloat( contactMotorForce ); + + saveFile->ReadFloat( mass ); + saveFile->ReadFloat( invMass ); + saveFile->ReadVec3( centerOfMass ); + saveFile->ReadMat3( inertiaTensor ); + saveFile->ReadMat3( inverseInertiaTensor ); + + //saveFile->ReadVec3( state[0].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[0].worldAxis ); + //saveFile->ReadVec6( state[0].spatialVelocity ); + //saveFile->ReadVec6( state[0].externalForce ); + + //saveFile->ReadVec3( state[1].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[1].worldAxis ); + //saveFile->ReadVec6( state[1].spatialVelocity ); + //saveFile->ReadVec6( state[1].externalForce ); + + saveFile->ReadVec3( current->worldOrigin ); + saveFile->ReadMat3( current->worldAxis ); + saveFile->ReadVec6( current->spatialVelocity ); + saveFile->ReadVec6( current->externalForce ); + + //saveFile->ReadVec3( next->worldOrigin ); // cnicholson: Added unrestored var next-> + //saveFile->ReadMat3( next->worldAxis ); + //saveFile->ReadVec6( next->spatialVelocity ); + //saveFile->ReadVec6( next->externalForce ); + + //saveFile->ReadVec3( saved.worldOrigin ); // cnicholson: Added unrestored var saved + //saveFile->ReadMat3( saved.worldAxis ); + //saveFile->ReadVec6( saved.spatialVelocity ); + //saveFile->ReadVec6( saved.externalForce ); + + saveFile->ReadVec3( atRestOrigin ); + saveFile->ReadMat3( atRestAxis ); + + // TORESTORE: idMatX inverseWorldSpatialInerti + // TORESTORE: idMatX I, invI; + // TORESTORE: idMatX J; + // TORESTORE: idVecX s; + // TORESTORE: idVecX totalForce; + // TORESTORE: idVecX auxForce; + // TORESTORE: idVecX acceleration; + // TORESTORE: float * response; + // TORESTORE: int * responseIndex; + saveFile->ReadInt( numResponses ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxAuxiliaryIndex ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unrestored var + + saveFile->Read( &fl, sizeof( fl ) ); // cnicholson: Added unrestored var +} + + +//=============================================================== +// M +// idAFTree MrE +// E +//=============================================================== + +/* +================ +idAFTree::Factor + + factor matrix for the primary constraints in the tree +================ +*/ +void idAFTree::Factor( void ) const { + int i, j; + idAFBody *body; + idAFConstraint *child; + idMatX childI; + + childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) ); + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + if ( body->children.Num() ) { + +// RAVEN BEGIN +// jscott: fixed warning + child = NULL; +// RAVEN END + + for ( j = 0; j < body->children.Num(); j++ ) { + + child = body->children[j]->primaryConstraint; + + // child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J; + childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() ); + child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J ); + childI.Negate(); + + child->invI = childI; + if ( !child->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'", + child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() ); + } + child->J = child->invI * child->J; + + body->I -= child->J.TransposeMultiply( childI ) * child->J; + } + + body->invI = body->I; + if ( !body->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s", + child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() ); + } + if ( body->primaryConstraint ) { + body->J = body->invI * body->J; + } + } + else if ( body->primaryConstraint ) { + body->J = body->inverseWorldSpatialInertia * body->J; + } + } +} + +/* +================ +idAFTree::Solve + + solve for primary constraints in the tree +================ +*/ +void idAFTree::Solve( int auxiliaryIndex ) const { + int i, j; + idAFBody *body, *child; + idAFConstraint *primaryConstraint; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + primaryConstraint = child->primaryConstraint; + + if ( !child->fl.isZero ) { + child->J.TransposeMultiplySub( primaryConstraint->s, child->s ); + primaryConstraint->fl.isZero = false; + } + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s ); + body->fl.isZero = false; + } + } + } + + bool useSymmetry = af_useSymmetry.GetBool(); + + // from the root down towards the leaves + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + + if ( primaryConstraint ) { + + if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s; + } + primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s ); + + primaryConstraint->lm = primaryConstraint->s; + + if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( body->children.Num() ) { + if ( !body->fl.isZero ) { + body->s = body->invI * body->s; + } + body->J.MultiplySub( body->s, primaryConstraint->s ); + } + } else if ( body->children.Num() ) { + body->s = body->invI * body->s; + } + } +} + +/* +================ +idAFTree::Response + + calculate body forces in the tree in response to a constraint force +================ +*/ +void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const { + int i, j; + idAFBody *body; + idAFConstraint *child, *primaryConstraint; + idVecX v; + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + body = constraint->body1; + if ( body->tree == this ) { + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + else { + body = constraint->body2; + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + return; + } + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + + // initialize right hand side to zero + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->s.Zero(); + primaryConstraint->fl.isZero = true; + } + body->s.Zero(); + body->fl.isZero = true; + body->GetResponseForce( body->numResponses ).Zero(); + } + + // set right hand side for first constrained body + body = constraint->body1; + if ( body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.Multiply( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.Multiply( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + } + + // set right hand side for second constrained body + body = constraint->body2; + if ( body && body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.MultiplyAdd( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + } + + + // solve for primary constraints + Solve( auxiliaryIndex ); + + bool useSymmetry = af_useSymmetry.GetBool(); + + // store body forces in response to the constraint force + idVecX force; + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + force.SetData( 6, body->response + body->numResponses * 8 ); + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( force, child->lm ); + } + + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } +} + +/* +================ +idAFTree::CalculateForces + + calculate forces on the bodies in the tree +================ +*/ +void idAFTree::CalculateForces( float timeStep ) const { + int i, j; + float invStep; + idAFBody *body; + idAFConstraint *child, *c, *primaryConstraint; + + // forces on bodies + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0); + } + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + return; + } + + invStep = 1.0f / timeStep; + + // initialize right hand side + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + // b = ( J * acc + c ) + c = primaryConstraint; + c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 ); + c->fl.isZero = false; + } + body->s.Zero(); + body->fl.isZero = true; + } + + // solve for primary constraints + Solve(); + + // calculate forces on bodies after applying primary constraints + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( body->totalForce, child->lm ); + } + } +} + +/* +================ +idAFTree::SetMaxSubTreeAuxiliaryIndex +================ +*/ +void idAFTree::SetMaxSubTreeAuxiliaryIndex( void ) { + int i, j; + idAFBody *body, *child; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) { + body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex; + } + } + } +} + +/* +================ +idAFTree::SortBodies_r +================ +*/ +void idAFTree::SortBodies_r( idList&sortedList, idAFBody *body ) { + int i; + + for ( i = 0; i < body->children.Num(); i++ ) { + sortedList.Append( body->children[i] ); + } + for ( i = 0; i < body->children.Num(); i++ ) { + SortBodies_r( sortedList, body->children[i] ); + } +} + +/* +================ +idAFTree::SortBodies + + sort body list to make sure parents come first +================ +*/ +void idAFTree::SortBodies( void ) { + int i; + idAFBody *body; + + // find the root + for ( i = 0; i < sortedBodies.Num(); i++ ) { + if ( !sortedBodies[i]->parent ) { + break; + } + } + + if ( i >= sortedBodies.Num() ) { + gameLocal.Error( "Articulated figure tree has no root." ); + } + + body = sortedBodies[i]; + sortedBodies.Clear(); + sortedBodies.Append( body ); + SortBodies_r( sortedBodies, body ); +} + +/* +================ +idAFTree::DebugDraw +================ +*/ +void idAFTree::DebugDraw( const idVec4 &color ) const { + int i; + idAFBody *body; + + for ( i = 1; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 ); + } +} + + +//=============================================================== +// M +// idPhysics_AF MrE +// E +//=============================================================== + +/* +================ +idPhysics_AF::EvaluateConstraints +================ +*/ +void idPhysics_AF::EvaluateConstraints( float timeStep ) { + int i; + float invTimeStep; + idAFBody *body; + idAFConstraint *c; + + invTimeStep = 1.0f / timeStep; + + // setup the constraint equations for the current position and orientation of the bodies + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + c = primaryConstraints[i]; + c->Evaluate( invTimeStep ); + c->J = c->J2; + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->Evaluate( invTimeStep ); + } + + // add contact constraints to the list with frame constraints + for ( i = 0; i < contactConstraints.Num(); i++ ) { + AddFrameConstraint( contactConstraints[i] ); + } + + // setup body primary constraint matrix + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->primaryConstraint ) { + body->J = body->primaryConstraint->J1.Transpose(); + } + } +} + +/* +================ +idPhysics_AF::EvaluateBodies +================ +*/ +void idPhysics_AF::EvaluateBodies( float timeStep ) { + int i; + idAFBody *body; + idMat3 axis; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // we transpose the axis before using it because idMat3 is column-major + axis = body->current->worldAxis.Transpose(); + + // if the center of mass is at the body point of reference + if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() ); + + body->fl.spatialInertiaSparse = true; + } + else { + idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass ); + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, massMoment, + massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia = body->I.InverseFast(); + + body->fl.spatialInertiaSparse = false; + } + + // initialize auxiliary constraint force to zero + body->auxForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddFrameConstraints +================ +*/ +void idPhysics_AF::AddFrameConstraints( void ) { + int i; + + // add frame constraints to auxiliary constraints + for ( i = 0; i < frameConstraints.Num(); i++ ) { + auxiliaryConstraints.Append( frameConstraints[i] ); + } +} + +/* +================ +idPhysics_AF::RemoveFrameConstraints +================ +*/ +void idPhysics_AF::RemoveFrameConstraints( void ) { + // remove all the frame constraints from the auxiliary constraints + auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num(), false ); + frameConstraints.SetNum( 0, false ); +} + +/* +================ +idPhysics_AF::ApplyFriction +================ +*/ +void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) { + int i; + float invTimeStep; + + if ( af_skipFriction.GetBool() ) { + return; + } + + if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f; + if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime; + } else { + jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime; + } + } else { + jointFrictionDentScale = 0.0f; + } + + if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f; + if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime; + } else { + contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime; + } + } else { + contactFrictionDentScale = 0.0f; + } + + invTimeStep = 1.0f / timeStep; + + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + primaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < frameConstraints.Num(); i++ ) { + frameConstraints[i]->ApplyFriction( invTimeStep ); + } +} + +/* +================ +idPhysics_AF::PrimaryFactor +================ +*/ +void idPhysics_AF::PrimaryFactor( void ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->Factor(); + } +} + +/* +================ +idPhysics_AF::PrimaryForces +================ +*/ +void idPhysics_AF::PrimaryForces( float timeStep ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->CalculateForces( timeStep ); + } +} + +/* +================ +idPhysics_AF::AuxiliaryForces +================ +*/ +void idPhysics_AF::AuxiliaryForces( float timeStep ) { + int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex; + float *ptr, *j1, *j2, *dstPtr, *forcePtr; + float invStep, u; + idAFBody *body; + idAFConstraint *constraint; + idVecX tmp; + idMatX jmk; + idVecX rhs, w, lm, lo, hi; + + // get the number of one dimensional auxiliary constraints + for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + if ( numAuxConstraints == 0 ) { + return; + } + + // allocate memory to store the body response to auxiliary constraint forces + forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) ); + index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) ); + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = forcePtr; + body->responseIndex = index; + body->numResponses = 0; + body->maxAuxiliaryIndex = 0; + forcePtr += numAuxConstraints * 8; + index += numAuxConstraints; + } + + // set on each body the largest index of an auxiliary constraint constraining the body + if ( af_useSymmetry.GetBool() ) { + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + if ( k > constraint->body1->maxAuxiliaryIndex ) { + constraint->body1->maxAuxiliaryIndex = k; + } + if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) { + constraint->body2->maxAuxiliaryIndex = k; + } + } + } + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SetMaxSubTreeAuxiliaryIndex(); + } + } + + // calculate forces of primary constraints in response to the auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + // calculate body forces in the tree in response to the constraint force + constraint->body1->tree->Response( constraint, j, k ); + // if there is a second body which is part of a different tree + if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) { + // calculate body forces in the second tree in response to the constraint force + constraint->body2->tree->Response( constraint, j, k ); + } + } + } + + // NOTE: the rows are 16 byte padded + jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) ); + tmp.SetData( 6, VECX_ALLOCA( 6 ) ); + + // create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] ); + j1 = tmp.ToFloatPtr(); + ptr = constraint->body1->response; + index = constraint->body1->responseIndex; + dstPtr = jmk[k]; + s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints; + for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) { + while( l < m ) { + dstPtr[l++] = 0.0f; + } + dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + ptr += 8; + } + + while( l < s ) { + dstPtr[l++] = 0.0f; + } + + if ( constraint->body2 ) { + constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] ); + j2 = tmp.ToFloatPtr(); + ptr = constraint->body2->response; + index = constraint->body2->responseIndex; + for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) { + dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + ptr += 8; + } + } + } + } + + if ( af_useSymmetry.GetBool() ) { + n = jmk.GetNumColumns(); + for ( i = 0; i < numAuxConstraints; i++ ) { + ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i; + dstPtr = jmk.ToFloatPtr() + i * n + i + 1; + for ( j = i+1; j < numAuxConstraints; j++ ) { + *dstPtr++ = *ptr; + ptr += n; + } + } + } + + invStep = 1.0f / timeStep; + + // calculate body acceleration + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + } + + rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) ); + + // set first index for special box constrained variables + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->firstIndex = k; + k += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + // initialize right hand side and low and high bounds for auxiliary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + n = k; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + j1 = constraint->J1[j]; + ptr = constraint->body1->acceleration.ToFloatPtr(); + rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + rhs[k] += constraint->c1[j] * invStep; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->acceleration.ToFloatPtr(); + rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + rhs[k] += constraint->c2[j] * invStep; + } + + rhs[k] = -rhs[k]; + lo[k] = constraint->lo[j]; + hi[k] = constraint->hi[j]; + + if ( constraint->boxIndex[j] >= 0 ) { + if ( constraint->boxConstraint->fl.isPrimary ) { + gameLocal.Error( "cannot reference primary constraints for the box index" ); + } + boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j]; + } + else { + boxIndex[k] = -1; + } + jmk[k][k] += constraint->e[j] * invStep; + } + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Start(); + } +#endif + + // calculate lagrange multipliers for auxiliary constraints + if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) { + return; // bad monkey! + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Stop(); + } +#endif + + // calculate auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->lm[j] = u = lm[k]; + + j1 = constraint->J1[j]; + ptr = constraint->body1->auxForce.ToFloatPtr(); + ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u; + ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->auxForce.ToFloatPtr(); + ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u; + ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u; + } + } + } + + // recalculate primary constraint forces in response to auxiliary constraint forces + PrimaryForces( timeStep ); + + // clear pointers pointing to stack space so tools don't get confused + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = NULL; + body->responseIndex = NULL; + } +} + +/* +================ +idPhysics_AF::VerifyContactConstraints +================ +*/ +void idPhysics_AF::VerifyContactConstraints( void ) { +#if 0 + int i; + float impulseNumerator, impulseDenominator; + idVec3 r, velocity, normalVelocity, normal, impulse; + idAFBody *body; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + + r = contact.point - body->GetCenterOfMass(); + + // calculate velocity at contact point + velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r ); + + // velocity along normal vector + normalVelocity = ( velocity * contact.normal ) * contact.normal; + + // if moving towards the surface at the contact point + if ( normalVelocity * contact.normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f; + + // apply impulse + body->SetLinearVelocity( body->GetLinearVelocity() + impulse ); + body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) ); + } + } +#else + int i; + idAFBody *body; + idVec3 normal; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + normal = contactConstraints[i]->GetContact().normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + body = contactConstraints[i]->body2; + if ( !body ) { + continue; + } + normal = -normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + } +#endif +} + +/* +================ +idPhysics_AF::Evolve +================ +*/ +void idPhysics_AF::Evolve( float timeStep ) { + int i; + float angle; + idVec3 vec; + idAFBody *body; + idVec6 force; + idRotation rotation; + float vSqr, maxLinearVelocity, maxAngularVelocity; + + maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep; + maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // calculate the spatial velocity for the next physics state + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0); + + if ( maxLinearVelocity > 0.0f ) { + // cap the linear velocity + vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr(); + if ( vSqr > Square( maxLinearVelocity ) ) { + body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity; + } + } + + if ( maxAngularVelocity > 0.0f ) { + // cap the angular velocity + vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr(); + if ( vSqr > Square( maxAngularVelocity ) ) { + body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity; + } + } + } + + // make absolutely sure all contact constraints are satisfied + VerifyContactConstraints(); + + // calculate the position of the bodies for the next physics state + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // translate world origin + body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 ); + + // convert angular velocity to a rotation matrix + vec = body->next->spatialVelocity.SubVec3( 1 ); + angle = -timeStep * (float) RAD2DEG( vec.Normalize() ); + rotation = idRotation( vec3_origin, vec, angle ); + rotation.Normalize180(); + + // rotate world axis + body->next->worldAxis = body->current->worldAxis * rotation.ToMat3(); + body->next->worldAxis.OrthoNormalizeSelf(); + + // linear and angular friction + body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0); + body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1); + } +} + +/* +================ +idPhysics_AF::CollisionImpulse + + apply impulse to the colliding bodies + the current state of the body should be set to the moment of impact + this is silly as it doesn't take the AF structure into account +================ +*/ +bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) { + idVec3 r, velocity, impulse; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator; + impactInfo_t info; + idEntity *ent; + + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent == self || !ent ) { + return false; + } + + // get info from other entity involved + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + // collision point relative to the body center of mass + r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis); + // the velocity at the collision point + velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + // never stick + if ( velocity * collision.c.normal > 0.0f ) { + velocity = collision.c.normal; + } + inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis; + impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal ); + impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + + // callback to self to let the entity know about the impact + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_AF::ApplyCollisions +================ +*/ +bool idPhysics_AF::ApplyCollisions( float timeStep ) { + int i; + + for ( i = 0; i < collisions.Num(); i++ ) { + if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_AF::SetupCollisionForBody +================ +*/ +idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const { + int i; + idAFBody *b; + idEntity *passEntity; + + passEntity = NULL; + + if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) { + + // disable all bodies + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } + + // don't collide with world collision model if attached to the world + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } + } + + } else { + + // enable all bodies that have self collision + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->fl.selfCollision ) { + bodies[i]->clipModel->Enable(); + } else { + bodies[i]->clipModel->Disable(); + } + } + + // don't let the body collide with itself + body->clipModel->Disable(); + + // disable any bodies attached with constraints + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } else { + if ( body->constraints[i]->body1 == body ) { + b = body->constraints[i]->body2; + } else if ( body->constraints[i]->body2 == body ) { + b = body->constraints[i]->body1; + } else { + continue; + } + // don't collide with this body + b->clipModel->Disable(); + } + } + } + + return passEntity; +} + +/* +================ +idPhysics_AF::CheckForCollisions + + check for collisions between the current and next state + if there is a collision the next state is set to the state at the moment of impact + assumes all bodies are linked for collision detection and relinks all bodies after moving them +================ +*/ +void idPhysics_AF::CheckForCollisions( float timeStep ) { +// #define TEST_COLLISION_DETECTION + int i, index; + idAFBody *body; + idMat3 axis; + idRotation rotation; + trace_t collision; + idEntity *passEntity; + + // clear list with collisions + collisions.SetNum( 0, false ); + + if ( !enableCollision ) { + return; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask != 0 && !fastEval ) { +// RAVEN END + + passEntity = SetupCollisionForBody( body ); + +#ifdef TEST_COLLISION_DETECTION + bool startsolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->current->worldOrigin, body->clipModel, + body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( body->current->worldOrigin ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, body->current->worldOrigin, body->next->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + body->next->worldOrigin = collision.endpos; + body->next->worldAxis = collision.endAxis; + + // add collision to the list + index = collisions.Num(); + collisions.SetNum( index + 1, false ); + collisions[index].trace = collision; + collisions[index].body = body; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->next->worldOrigin, body->clipModel, + body->next->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::EvaluateContacts +================ +*/ +bool idPhysics_AF::EvaluateContacts( void ) { + int i, j, k, numContacts, numBodyContacts; + idAFBody *body; + contactInfo_t contactInfo[10]; + idEntity *passEntity; + idVecX dir( 6, VECX_ALLOCA( 6 ) ); + + // evaluate bodies + EvaluateBodies( current.lastTimeStep ); + + // remove all existing contacts + ClearContacts(); + + contactBodies.SetNum( 0, false ); + + if ( !enableCollision ) { + return false; + } + + // find all the contacts + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask == 0 || fastEval ) { +// RAVEN END + continue; + } + + passEntity = SetupCollisionForBody( body ); + + body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() ); + dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0); + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numContacts = gameLocal.Contacts( self, contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ); +// RAVEN END +#if 1 + // merge nearby contacts between the same bodies + // and assure there are at most three planar contacts between any pair of bodies + for ( j = 0; j < numContacts; j++ ) { + + numBodyContacts = 0; + for ( k = 0; k < contacts.Num(); k++ ) { + if ( contacts[k].entityNum == contactInfo[j].entityNum ) { + if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) || + ( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) { + + if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) { + break; + } + if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) { + numBodyContacts++; + } + } + } + } + + if ( k >= contacts.Num() && numBodyContacts < 3 ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } + } + +#else + + for ( j = 0; j < numContacts; j++ ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } +#endif + + } + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_AF::SetupContactConstraints +================ +*/ +void idPhysics_AF::SetupContactConstraints( void ) { + int i; + + // make sure enough contact constraints are allocated + contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement ); + contactConstraints.SetNum( contacts.Num(), false ); + + // setup contact constraints + for ( i = 0; i < contacts.Num(); i++ ) { + // add contact constraint + contactConstraints[i]->physics = this; + if ( contacts[i].entityNum == self->entityNumber ) { + contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] ); + } + else { + contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] ); + } + } +} + +/* +================ +idPhysics_AF::ApplyContactForces +================ +*/ +void idPhysics_AF::ApplyContactForces( void ) { +#if 0 + int i; + idEntity *ent; + idVec3 force; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + if ( contactConstraints[i]->body2 != NULL ) { + continue; + } + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + ent = gameLocal.entities[contact.entityNum]; + if ( !ent ) { + continue; + } + force.Zero(); + ent->AddForce( self, contact.id, contact.point, force ); + } +#endif +} + +/* +================ +idPhysics_AF::ClearExternalForce +================ +*/ +void idPhysics_AF::ClearExternalForce( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // clear external force + body->current->externalForce.Zero(); + body->next->externalForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddGravity +================ +*/ +void idPhysics_AF::AddGravity( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + // add gravitational force + body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector; + } +} + +/* +================ +idPhysics_AF::SwapStates +================ +*/ +void idPhysics_AF::SwapStates( void ) { + int i; + idAFBody *body; + AFBodyPState_t *swap; + + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + + // swap the current and next state for next simulation step + swap = body->current; + body->current = body->next; + body->next = swap; + } +} + +/* +================ +idPhysics_AF::UpdateClipModels +================ +*/ +void idPhysics_AF::UpdateClipModels( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::SetSuspendSpeed +================ +*/ +void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) { + this->suspendVelocity = velocity; + this->suspendAcceleration = acceleration; +} + +/* +================ +idPhysics_AF::SetSuspendTime +================ +*/ +void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) { + this->minMoveTime = minTime; + this->maxMoveTime = maxTime; +} + +/* +================ +idPhysics_AF::SetSuspendTolerance +================ +*/ +void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) { + this->noMoveTime = noMoveTime; + this->noMoveTranslation = noMoveTranslation; + this->noMoveRotation = noMoveRotation; +} + +/* +================ +idPhysics_AF::SetTimeScaleRamp +================ +*/ +void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) { + timeScaleRampStart = start; + timeScaleRampEnd = end; +} + +/* +================ +idPhysics_AF::SetJointFrictionDent +================ +*/ +void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) { + jointFrictionDent = dent; + jointFrictionDentStart = start; + jointFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetJointFrictionScale +================ +*/ +float idPhysics_AF::GetJointFrictionScale( void ) const { + if ( jointFrictionDentScale > 0.0f ) { + return jointFrictionDentScale; + } else if ( jointFrictionScale > 0.0f ) { + return jointFrictionScale; + } else if ( af_jointFrictionScale.GetFloat() > 0.0f ) { + return af_jointFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::SetContactFrictionDent +================ +*/ +void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) { + contactFrictionDent = dent; + contactFrictionDentStart = start; + contactFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetContactFrictionScale +================ +*/ +float idPhysics_AF::GetContactFrictionScale( void ) const { + if ( contactFrictionDentScale > 0.0f ) { + return contactFrictionDentScale; + } else if ( contactFrictionScale > 0.0f ) { + return contactFrictionScale; + } else if ( af_contactFrictionScale.GetFloat() > 0.0f ) { + return af_contactFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::TestIfAtRest +================ +*/ +bool idPhysics_AF::TestIfAtRest( float timeStep ) { + int i; + float translationSqr, maxTranslationSqr, rotation, maxRotation; + idAFBody *body; + + if ( current.atRest >= 0 ) { + return true; + } + + current.activateTime += timeStep; + + // if the simulation should never be suspended before a certaint amount of time passed + if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) { + return false; + } + + // if the simulation should always be suspended after a certain amount time passed + if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) { + return true; + } + + // test if all bodies hardly moved over a period of time + if ( current.noMoveTime == 0.0f ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->atRestOrigin = body->current->worldOrigin; + body->atRestAxis = body->current->worldAxis; + } + current.noMoveTime += timeStep; + } + else if ( current.noMoveTime > noMoveTime ) { + current.noMoveTime = 0.0f; + maxTranslationSqr = 0.0f; + maxRotation = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr(); + if ( translationSqr > maxTranslationSqr ) { + maxTranslationSqr = translationSqr; + } + rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle(); + if ( rotation > maxRotation ) { + maxRotation = rotation; + } + } + + if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) { + // hardly moved over a period of time so the articulated figure may come to rest + return true; + } + } else { + current.noMoveTime += timeStep; + } + + // test if the velocity or acceleration of any body is still too large to come to rest + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) { + return false; + } + if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) { + return false; + } + if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) { + return false; + } + if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) { + return false; + } + } + + // all bodies have a velocity and acceleration small enough to come to rest + return true; +} + +/* +================ +idPhysics_AF::Rest +================ +*/ +void idPhysics_AF::Rest( void ) { + int i; + + current.atRest = gameLocal.time; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity.Zero(); + bodies[i]->current->externalForce.Zero(); + } + + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::Activate +================ +*/ +void idPhysics_AF::Activate( void ) { + // if the articulated figure was at rest + if ( current.atRest >= 0 ) { + // normally gravity is added at the end of a simulation frame + // if the figure was at rest add gravity here so it is applied this simulation frame + AddGravity(); + // reset the active time for the max move time + current.activateTime = 0.0f; + } + current.atRest = -1; + current.noMoveTime = 0.0f; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_AF::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_AF::EnableImpact +================ +*/ +void idPhysics_AF::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_AF::DisableImpact +================ +*/ +void idPhysics_AF::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_AF::AddPushVelocity +================ +*/ +void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) { + int i; + + if ( pushVelocity != vec6_origin ) { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity += pushVelocity; + } + } +} + +/* +================ +idPhysics_AF::SetClipModel +================ +*/ +void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_AF::GetClipModel +================ +*/ +idClipModel *idPhysics_AF::GetClipModel( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel(); + } + return NULL; +} + +/* +================ +idPhysics_AF::GetNumClipModels +================ +*/ +int idPhysics_AF::GetNumClipModels( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::SetMass +================ +*/ +void idPhysics_AF::SetMass( float mass, int id ) { + if ( id >= 0 && id < bodies.Num() ) { + } + else { + forceTotalMass = mass; + } + SetChanged(); +} + +/* +================ +idPhysics_AF::GetMass +================ +*/ +float idPhysics_AF::GetMass( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->mass; + } + return totalMass; +} + +/* +================ +idPhysics_AF::SetContents +================ +*/ +void idPhysics_AF::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < bodies.Num() ) { + bodies[id]->GetClipModel()->SetContents( contents ); + } + else { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->GetClipModel()->SetContents( contents ); + } + } +} + +/* +================ +idPhysics_AF::GetContents +================ +*/ +int idPhysics_AF::GetContents( int id ) const { + int i, contents; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetContents(); + } + else { + contents = 0; + for ( i = 0; i < bodies.Num(); i++ ) { + contents |= bodies[i]->GetClipModel()->GetContents(); + } + return contents; + } +} + +/* +================ +idPhysics_AF::GetBounds +================ +*/ +const idBounds &idPhysics_AF::GetBounds( int id ) const { + int i; + static idBounds relBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetBounds(); + } + else if ( !bodies.Num() ) { + relBounds.Zero(); + return relBounds; + } + else { + relBounds = bodies[0]->GetClipModel()->GetBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + idBounds bounds; + idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose(); + idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose(); + bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis ); + relBounds += bounds; + } + return relBounds; + } +} + +/* +================ +idPhysics_AF::GetAbsBounds +================ +*/ +const idBounds &idPhysics_AF::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetAbsBounds(); + } + else if ( !bodies.Num() ) { + absBounds.Zero(); + return absBounds; + } + else { + absBounds = bodies[0]->GetClipModel()->GetAbsBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + absBounds += bodies[i]->GetClipModel()->GetAbsBounds(); + } + return absBounds; + } +} + +/* +================ +idPhysics_AF::Evaluate +================ +*/ +bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { + float timeStep; + + if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) { + timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart ); + } else if ( af_timeScale.GetFloat() != 1.0f ) { + timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat(); + } else { + timeStep = MS2SEC( timeStepMSec ) * timeScale; + } + current.lastTimeStep = timeStep; + + + // if the articulated figure changed + if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) { + BuildTrees(); + changedAF = false; + linearTime = af_useLinearTime.GetBool(); + } + + // get the new master position + if ( masterBody ) { + idVec3 masterOrigin; + idMat3 masterAxis; + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) { + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + + // if the simulation is suspended because the figure is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // move the af velocity into the frame of a pusher + AddPushVelocity( -current.pushVelocity ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Start(); + timer_collision.Start(); + } +#endif + + // evaluate contacts + EvaluateContacts(); + + // setup contact constraints + SetupContactConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // evaluate constraint equations + EvaluateConstraints( timeStep ); + + // apply friction + ApplyFriction( timeStep, endTimeMSec ); + + // add frame constraints + AddFrameConstraints(); + +#ifdef AF_TIMINGS + int numPrimary = 0, numAuxiliary = 0; + if ( af_showTimings.GetInteger() != 0 ) { + int i; + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + numPrimary += primaryConstraints[i]->J1.GetNumRows(); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows(); + } + timer_pc.Start(); + } +#endif + + // factor matrices for primary constraints + PrimaryFactor(); + + // calculate forces on bodies after applying primary constraints + PrimaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_pc.Stop(); + timer_ac.Start(); + } +#endif + + // calculate and apply auxiliary constraint forces + AuxiliaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_ac.Stop(); + } +#endif + + // evolve current state to next state + Evolve( timeStep ); + + // debug graphics + DebugDraw(); + + // clear external forces on all bodies + ClearExternalForce(); + + // apply contact force to other entities + ApplyContactForces(); + + // remove all frame constraints + RemoveFrameConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions between current and next state + CheckForCollisions( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // swap the current and next state + SwapStates(); + + // make sure all clip models are disabled in case they were enabled for self collision + if ( selfCollision && !af_skipSelfCollision.GetBool() ) { + DisableClip(); + } + + // apply collision impulses + if ( ApplyCollisions( timeStep ) ) { + current.atRest = gameLocal.time; + comeToRest = true; + } + + // test if the simulation can be suspended because the whole figure is at rest + if ( comeToRest && TestIfAtRest( timeStep ) ) { + Rest(); + } else { + ActivateContactEntities(); + } + + // add gravitational force + AddGravity(); + + // move the af velocity back into the world frame + AddPushVelocity( current.pushVelocity ); + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { +// RAVEN BEGIN +// kfuller: warnings shouldn't crash the game + if ( bodies.Num() && bodies[0] && bodies[0]->current && self ) { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); + } else { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' -- no body", + self->name.c_str(), self->GetType()->classname ); + } +// RAVEN END + Rest(); + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Stop(); + + if ( af_showTimings.GetInteger() == 1 ) { + gameLocal.Printf( "%12s: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + else if ( af_showTimings.GetInteger() == 2 ) { + numArticulatedFigures++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "af %d: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + numArticulatedFigures, + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + } + + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numArticulatedFigures = 0; + timer_total.Clear(); + timer_pc.Clear(); + timer_ac.Clear(); + timer_collision.Clear(); + timer_lcp.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_AF::UpdateTime +================ +*/ +void idPhysics_AF::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_AF::GetTime +================ +*/ +int idPhysics_AF::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +DrawTraceModelSilhouette +================ +*/ +void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) { + int i, numSilEdges; + int silEdges[MAX_TRACEMODEL_EDGES]; + idVec3 v1, v2; + const idTraceModel *trm = clipModel->GetTraceModel(); + const idVec3 &origin = clipModel->GetOrigin(); + const idMat3 &axis = clipModel->GetAxis(); + + numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges ); + for ( i = 0; i < numSilEdges; i++ ) { + v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITSET( silEdges[i] ) ] ]; + v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITNOTSET( silEdges[i] ) ] ]; + gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 ); + } +} + +/* +================ +idPhysics_AF::DebugDraw +================ +*/ +void idPhysics_AF::DebugDraw( void ) { + int i; + idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL; + idAFConstraint *constraint; + idVec3 center; + idMat3 axis; + + if ( af_highlightConstraint.GetString()[0] ) { + constraint = GetConstraint( af_highlightConstraint.GetString() ); + if ( constraint ) { + constraint->GetCenter( center ); + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 ); + + if ( af_showConstrainedBodies.GetBool() ) { + cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) ); + constrainedBody1 = constraint->body1; + if ( constrainedBody1 ) { + collisionModelManager->DrawModel( constrainedBody1->clipModel->GetCollisionModel(), constrainedBody1->clipModel->GetOrigin(), + constrainedBody1->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) ); + constrainedBody2 = constraint->body2; + if ( constrainedBody2 ) { + collisionModelManager->DrawModel( constrainedBody2->clipModel->GetCollisionModel(), constrainedBody2->clipModel->GetOrigin(), + constrainedBody2->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + } + + if ( af_highlightBody.GetString()[0] ) { + highlightBody = GetBody( af_highlightBody.GetString() ); + if ( highlightBody ) { + cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) ); + collisionModelManager->DrawModel( highlightBody->clipModel->GetCollisionModel(), highlightBody->clipModel->GetOrigin(), + highlightBody->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + + if ( af_showBodies.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + if ( body == constrainedBody1 || body == constrainedBody2 ) { + continue; + } + if ( body == highlightBody ) { + continue; + } + collisionModelManager->DrawModel( body->clipModel->GetCollisionModel(), body->clipModel->GetOrigin(), + body->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + //DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel ); + } + } + + if ( af_showBodyNames.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showMass.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showTotalMass.GetBool() ) { + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 ); + } + + if ( af_showInertia.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + idMat3 &I = body->inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showVelocity.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f ); + } + } + + if ( af_showConstraints.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->DebugDraw(); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->DebugDraw(); + } + } + } + + if ( af_showConstraintNames.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + } + + if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) { + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) ); + } + } +} + +/* +================ +idPhysics_AF::idPhysics_AF +================ +*/ +idPhysics_AF::idPhysics_AF( void ) { + trees.Clear(); + bodies.Clear(); + constraints.Clear(); + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + frameConstraints.Clear(); + contacts.Clear(); + collisions.Clear(); + changedAF = true; + masterBody = NULL; + + lcp = idLCP::AllocSymmetric(); + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + saved = current; + + linearFriction = 0.005f; + angularFriction = 0.005f; + contactFriction = 0.8f; + bouncyness = 0.4f; + totalMass = 0.0f; + forceTotalMass = -1.0f; + + suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY ); + suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION ); + noMoveTime = NO_MOVE_TIME; + noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE; + noMoveRotation = NO_MOVE_ROTATION_TOLERANCE; + minMoveTime = MIN_MOVE_TIME; + maxMoveTime = MAX_MOVE_TIME; + impulseThreshold = IMPULSE_THRESHOLD; + + timeScale = 1.0f; + timeScaleRampStart = 0.0f; + timeScaleRampEnd = 0.0f; + + jointFrictionScale = 0.0f; + jointFrictionDent = 0.0f; + jointFrictionDentStart = 0.0f; + jointFrictionDentEnd = 0.0f; + jointFrictionDentScale = 0.0f; + + contactFrictionScale = 0.0f; + contactFrictionDent = 0.0f; + contactFrictionDentStart = 0.0f; + contactFrictionDentEnd = 0.0f; + contactFrictionDentScale = 0.0f; + + enableCollision = true; + selfCollision = true; + comeToRest = true; + linearTime = true; + noImpact = false; + worldConstraintsLocked = false; + forcePushable = false; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + fastEval = false; +// RAVEN END + +#ifdef AF_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_AF::~idPhysics_AF +================ +*/ +idPhysics_AF::~idPhysics_AF( void ) { + int i; + + trees.DeleteContents( true ); + + for ( i = 0; i < bodies.Num(); i++ ) { + delete bodies[i]; + } + + for ( i = 0; i < constraints.Num(); i++ ) { + delete constraints[i]; + } + + contactConstraints.SetNum( contactConstraints.NumAllocated(), false ); + for ( i = 0; i < contactConstraints.NumAllocated(); i++ ) { + delete contactConstraints[i]; + } + + delete lcp; + + if ( masterBody ) { + delete masterBody; + } +} + +/* +================ +idPhysics_AF_SavePState +================ +*/ +void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) { + saveFile->WriteInt( state.atRest ); + saveFile->WriteFloat( state.noMoveTime ); + saveFile->WriteFloat( state.activateTime ); + saveFile->WriteFloat( state.lastTimeStep ); + saveFile->WriteVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF_RestorePState +================ +*/ +void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) { + saveFile->ReadInt( state.atRest ); + saveFile->ReadFloat( state.noMoveTime ); + saveFile->ReadFloat( state.activateTime ); + saveFile->ReadFloat( state.lastTimeStep ); + saveFile->ReadVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF::Save +================ +*/ +void idPhysics_AF::Save( idSaveGame *saveFile ) const { + int i; + + // the articulated figure structure is handled by the owner + + // TOSAVE: idList trees; + saveFile->WriteInt( bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Save( saveFile ); + } + if ( masterBody ) { + saveFile->WriteBool( true ); + masterBody->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + + saveFile->WriteInt( constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Save( saveFile ); + } + + // TOSAVE: idListprimaryConstraints; + // TOSAVE: idListauxiliaryConstraints; + // TOSAVE: idListframeConstraints; + // TOSAVE: idListcontactConstraints; + // TOSAVE: idList contactBodies; + // TOSAVE: idList collisions; + + saveFile->WriteBool( changedAF ); + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteFloat( totalMass ); + saveFile->WriteFloat( forceTotalMass ); + + saveFile->WriteVec2( suspendVelocity ); + saveFile->WriteVec2( suspendAcceleration ); + saveFile->WriteFloat( noMoveTime ); + saveFile->WriteFloat( noMoveTranslation ); + saveFile->WriteFloat( noMoveRotation ); + saveFile->WriteFloat( minMoveTime ); + saveFile->WriteFloat( maxMoveTime ); + saveFile->WriteFloat( impulseThreshold ); + + saveFile->WriteFloat( timeScale ); + saveFile->WriteFloat( timeScaleRampStart ); + saveFile->WriteFloat( timeScaleRampEnd ); + + saveFile->WriteFloat( jointFrictionScale ); + saveFile->WriteFloat( jointFrictionDent ); + saveFile->WriteFloat( jointFrictionDentStart ); + saveFile->WriteFloat( jointFrictionDentEnd ); + saveFile->WriteFloat( jointFrictionDentScale ); + + saveFile->WriteFloat( contactFrictionScale ); + saveFile->WriteFloat( contactFrictionDent ); + saveFile->WriteFloat( contactFrictionDentStart ); + saveFile->WriteFloat( contactFrictionDentEnd ); + saveFile->WriteFloat( contactFrictionDentScale ); + + saveFile->WriteBool( enableCollision ); + saveFile->WriteBool( selfCollision ); + saveFile->WriteBool( comeToRest ); + saveFile->WriteBool( linearTime ); + saveFile->WriteBool( noImpact ); + saveFile->WriteBool( worldConstraintsLocked ); + saveFile->WriteBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->WriteBool( fastEval ); +// RAVEN END + + idPhysics_AF_SavePState( saveFile, current ); + idPhysics_AF_SavePState( saveFile, saved ); + + // TOSAVE: idAFBody * masterBody; + // TOSAVE: idLCP * lcp; +} + +/* +================ +idPhysics_AF::Restore +================ +*/ +void idPhysics_AF::Restore( idRestoreGame *saveFile ) { + int i, num; + bool hasMaster; + + // the articulated figure structure should have already been restored + + saveFile->ReadInt( num ); + assert( num == bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Restore( saveFile ); + } + saveFile->ReadBool( hasMaster ); + if ( hasMaster ) { + masterBody = new idAFBody(); + masterBody->Restore( saveFile ); + } + + saveFile->ReadInt( num ); + assert( num == constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Restore( saveFile ); + } + + saveFile->ReadBool( changedAF ); + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadFloat( totalMass ); + saveFile->ReadFloat( forceTotalMass ); + + saveFile->ReadVec2( suspendVelocity ); + saveFile->ReadVec2( suspendAcceleration ); + saveFile->ReadFloat( noMoveTime ); + saveFile->ReadFloat( noMoveTranslation ); + saveFile->ReadFloat( noMoveRotation ); + saveFile->ReadFloat( minMoveTime ); + saveFile->ReadFloat( maxMoveTime ); + saveFile->ReadFloat( impulseThreshold ); + + saveFile->ReadFloat( timeScale ); + saveFile->ReadFloat( timeScaleRampStart ); + saveFile->ReadFloat( timeScaleRampEnd ); + + saveFile->ReadFloat( jointFrictionScale ); + saveFile->ReadFloat( jointFrictionDent ); + saveFile->ReadFloat( jointFrictionDentStart ); + saveFile->ReadFloat( jointFrictionDentEnd ); + saveFile->ReadFloat( jointFrictionDentScale ); + + saveFile->ReadFloat( contactFrictionScale ); + saveFile->ReadFloat( contactFrictionDent ); + saveFile->ReadFloat( contactFrictionDentStart ); + saveFile->ReadFloat( contactFrictionDentEnd ); + saveFile->ReadFloat( contactFrictionDentScale ); + + saveFile->ReadBool( enableCollision ); + saveFile->ReadBool( selfCollision ); + saveFile->ReadBool( comeToRest ); + saveFile->ReadBool( linearTime ); + saveFile->ReadBool( noImpact ); + saveFile->ReadBool( worldConstraintsLocked ); + saveFile->ReadBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->ReadBool( fastEval ); +// RAVEN END + + idPhysics_AF_RestorePState( saveFile, current ); + idPhysics_AF_RestorePState( saveFile, saved ); + + changedAF = true; + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::IsClosedLoop +================ +*/ +bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const { + const idAFBody *b1, *b2; + + for ( b1 = body1; b1->parent; b1 = b1->parent ) { + } + for ( b2 = body2; b2->parent; b2 = b2->parent ) { + } + return ( b1 == b2 ); +} + +/* +================ +idPhysics_AF::BuildTrees +================ +*/ +void idPhysics_AF::BuildTrees( void ) { + int i; + float scale; + idAFBody *b; + idAFConstraint *c; + idAFTree *tree; + + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + trees.DeleteContents( true ); + + totalMass = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->parent = NULL; + b->primaryConstraint = NULL; + b->constraints.SetNum( 0, false ); + b->children.Clear(); + b->tree = NULL; + totalMass += b->mass; + } + + if ( forceTotalMass > 0.0f ) { + scale = forceTotalMass / totalMass; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->mass *= scale; + b->invMass = 1.0f / b->mass; + b->inertiaTensor *= scale; + b->inverseInertiaTensor = b->inertiaTensor.Inverse(); + } + totalMass = forceTotalMass; + } + + if ( af_useLinearTime.GetBool() ) { + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + // only bilateral constraints between two non-world bodies that do not + // create loops can be used as primary constraints + if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) { + c->body1->primaryConstraint = c; + c->body1->parent = c->body2; + c->body2->children.Append( c->body1 ); + c->fl.isPrimary = true; + c->firstIndex = 0; + primaryConstraints.Append( c ); + } else { + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } + + // create trees for all parent bodies + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->parent ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + } + + // add each child body to the appropriate tree + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->parent ) { + for ( b = bodies[i]->parent; !b->tree; b = b->parent ) { + } + b->tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = b->tree; + } + } + + if ( trees.Num() > 1 ) { + gameLocal.Warning( "Articulated figure has multiple seperate tree structures for entity '%s' type '%s'.", + self->name.c_str(), self->GetType()->classname ); + } + + // sort bodies in each tree to make sure parents come first + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SortBodies(); + } + + } else { + + // create a tree for each body + for ( i = 0; i < bodies.Num(); i++ ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } +} + +/* +================ +idPhysics_AF::AddBody + + bodies get an id in the order they are added starting at zero + as such the first body added will get id zero +================ +*/ +int idPhysics_AF::AddBody( idAFBody *body ) { + int id = 0; + + if ( !body->clipModel ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() ); + } + + if ( bodies.Find( body ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() ); + } + + if ( GetBody( body->name ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() ); + } + + id = bodies.Num(); + body->clipModel->SetId( id ); + if ( body->linearFriction < 0.0f ) { + body->linearFriction = linearFriction; + body->angularFriction = angularFriction; + body->contactFriction = contactFriction; + } + if ( body->bouncyness < 0.0f ) { + body->bouncyness = bouncyness; + } + if ( !body->fl.clipMaskSet ) { + body->clipMask = clipMask; + } + + bodies.Append( body ); + + changedAF = true; + + return id; +} + +/* +================ +idPhysics_AF::AddConstraint +================ +*/ +void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) { + + if ( constraints.Find( constraint ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() ); + } + if ( GetConstraint( constraint->name ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() ); + } + if ( !constraint->body1 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() ); + } + if ( !bodies.Find( constraint->body1 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body1 == constraint->body2 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() ); + } + + constraints.Append( constraint ); + constraint->physics = this; + + changedAF = true; +} + +/* +================ +idPhysics_AF::AddFrameConstraint +================ +*/ +void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) { + frameConstraints.Append( constraint ); + constraint->physics = this; +} + +/* +================ +idPhysics_AF::ForceBodyId +================ +*/ +void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 ) { + gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + if ( id != newId ) { + idAFBody *b = bodies[newId]; + bodies[newId] = bodies[id]; + bodies[id] = b; + changedAF = true; + } +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( idAFBody *body ) const { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 && body ) { + gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return i; + } + } + gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName ); + return 0; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const { + int id; + + id = constraints.FindIndex( constraint ); + if ( id == -1 && constraint ) { + gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return i; + } + } + gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName ); + return 0; +} + +/* +================ +idPhysics_AF::GetNumBodies +================ +*/ +int idPhysics_AF::GetNumBodies( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::GetNumConstraints +================ +*/ +int idPhysics_AF::GetNumConstraints( void ) const { + return constraints.Num(); +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return bodies[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + gameLocal.Error( "GetBody: no body with id %d exists\n", id ); + return NULL; + } + return bodies[id]; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return constraints[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const { + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id ); + return NULL; + } + return constraints[id]; +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const char *bodyName ) { + int i; + + // find the body with the given name + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + break; + } + } + + if ( i >= bodies.Num() ) { + gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + bodyName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteBody( i ); +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const int id ) { + int j; + + if ( id < 0 || id > bodies.Num() ) { + gameLocal.Error( "DeleteBody: no body with id %d.", id ); + return; + } + + // remove any constraints attached to this body + for ( j = 0; j < constraints.Num(); j++ ) { + if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) { + delete constraints[j]; + constraints.RemoveIndex( j ); + j--; + } + } + + // remove the body + delete bodies[id]; + bodies.RemoveIndex( id ); + + // set new body ids + for ( j = 0; j < bodies.Num(); j++ ) { + bodies[j]->clipModel->SetId( j ); + } + + changedAF = true; +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const char *constraintName ) { + int i; + + // find the constraint with the given name + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !constraints[i]->name.Icmp( constraintName ) ) { + break; + } + } + + if ( i >= constraints.Num() ) { + gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + constraintName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteConstraint( i ); +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const int id ) { + + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id ); + return; + } + + // remove the constraint + delete constraints[id]; + constraints.RemoveIndex( id ); + + changedAF = true; +} + +/* +================ +idPhysics_AF::GetBodyContactConstraints +================ +*/ +int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const { + int i, numContacts; + idAFBody *body; + idAFConstraint_Contact *contact; + + if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) { + return 0; + } + + numContacts = 0; + body = bodies[id]; + for ( i = 0; i < contactConstraints.Num(); i++ ) { + contact = contactConstraints[i]; + if ( contact->body1 == body || contact->body2 == body ) { + contacts[numContacts++] = contact; + if ( numContacts >= maxContacts ) { + return numContacts; + } + } + } + return numContacts; +} + +/* +================ +idPhysics_AF::SetDefaultFriction +================ +*/ +void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_AF::GetImpactInfo +================ +*/ +void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + if ( id < 0 || id >= bodies.Num() ) { + memset( info, 0, sizeof( *info ) ); + return; + } + info->invMass = 1.0f / bodies[id]->mass; + info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + info->position = point - bodies[id]->current->worldOrigin; + info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position ); +} + +/* +================ +idPhysics_AF::ApplyImpulse +================ +*/ +void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) { + return; + } + idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse; + bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_AF::AddForce +================ +*/ +void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->externalForce.SubVec3( 0 ) += force; + bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_AF::IsAtRest +================ +*/ +bool idPhysics_AF::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_AF::GetRestStartTime +================ +*/ +int idPhysics_AF::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_AF::IsPushable +================ +*/ +bool idPhysics_AF::IsPushable( void ) const { + return ( !noImpact && ( masterBody == NULL || forcePushable ) ); +} + +/* +================ +idPhysics_AF::SaveState +================ +*/ +void idPhysics_AF::SaveState( void ) { + int i; + + saved = current; + + for ( i = 0; i < bodies.Num(); i++ ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::RestoreState +================ +*/ +void idPhysics_AF::RestoreState( void ) { + int i; + + current = saved; + + for ( i = 0; i < bodies.Num(); i++ ) { + *(bodies[i]->current) = bodies[i]->saved; + } + + EvaluateContacts(); +} + +/* +================ +idPhysics_AF::SetOrigin +================ +*/ +void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) { + if ( masterBody ) { + Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin ); + } else { + Translate( newOrigin - bodies[0]->current->worldOrigin ); + } +} + +/* +================ +idPhysics_AF::SetAxis +================ +*/ +void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) { + idMat3 axis; + idRotation rotation; + + if ( masterBody ) { + axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis ); + } else { + axis = bodies[0]->current->worldAxis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( bodies[0]->current->worldOrigin ); + + Rotate( rotation ); +} + +/* +================ +idPhysics_AF::Translate +================ +*/ +void idPhysics_AF::Translate( const idVec3 &translation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // translate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Translate( translation ); + } + } + + // translate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + body->current->worldOrigin += translation; + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::Rotate +================ +*/ +void idPhysics_AF::Rotate( const idRotation &rotation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // rotate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Rotate( rotation ); + } + } + + // rotate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + body->current->worldOrigin *= rotation; + body->current->worldAxis *= rotation.ToMat3(); + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::GetOrigin +================ +*/ +const idVec3 &idPhysics_AF::GetOrigin( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->worldOrigin; + } +} + +/* +================ +idPhysics_AF::GetAxis +================ +*/ +const idMat3 &idPhysics_AF::GetAxis( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return mat3_identity; + } + else { + return bodies[id]->current->worldAxis; + } +} + +/* +================ +idPhysics_AF::SetLinearVelocity +================ +*/ +void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::SetAngularVelocity +================ +*/ +void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 0 ); + } +} + +/* +================ +idPhysics_AF::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 1 ); + } +} + +/* +================ +idPhysics_AF::ClipTranslation +================ +*/ +void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TranslationModel( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation; + results.endAxis = bodies[0]->current->worldAxis; +} + +/* +================ +idPhysics_AF::ClipRotation +================ +*/ +void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + idRotation partialRotation; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + partialRotation = rotation * results.fraction; + results.endpos = bodies[0]->current->worldOrigin * partialRotation; + results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3(); +} + +/* +================ +idPhysics_AF::ClipContents +================ +*/ +int idPhysics_AF::ClipContents( const idClipModel *model ) const { + int i, contents; + idAFBody *body; + + contents = 0; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + contents |= gameLocal.Contents( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, NULL ); +// RAVEN END + } + } + } + + return contents; +} + +/* +================ +idPhysics_AF::DisableClip +================ +*/ +void idPhysics_AF::DisableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } +} + +/* +================ +idPhysics_AF::EnableClip +================ +*/ +void idPhysics_AF::EnableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Enable(); + } +} + +/* +================ +idPhysics_AF::UnlinkClip +================ +*/ +void idPhysics_AF::UnlinkClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Unlink(); + } +} + +/* +================ +idPhysics_AF::LinkClip +================ +*/ +void idPhysics_AF::LinkClip( void ) { + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::SetPushed +================ +*/ +void idPhysics_AF::SetPushed( int deltaTime ) { + idAFBody *body; + idRotation rotation; + + if ( bodies.Num() ) { + body = bodies[0]; + rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); + } +} + +/* +================ +idPhysics_AF::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_AF::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_AF::SetMaster + + the binding is orientated based on the constraints being used +================ +*/ +void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + idRotation rotation; + + if ( master ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( !masterBody ) { + masterBody = new idAFBody(); + // translate and rotate all the constraints with body2 == NULL from world space to master space + rotation = masterAxis.Transpose().ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Translate( -masterOrigin ); + constraints[i]->Rotate( rotation ); + } + } + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + else { + if ( masterBody ) { + // translate and rotate all the constraints with body2 == NULL from master space to world space + rotation = masterBody->current->worldAxis.ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Rotate( rotation ); + constraints[i]->Translate( masterBody->current->worldOrigin ); + } + } + delete masterBody; + masterBody = NULL; + Activate(); + } + } +} + + +const float AF_VELOCITY_MAX = 16000; +const int AF_VELOCITY_TOTAL_BITS = 16; +const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1; +const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS; +const float AF_FORCE_MAX = 1e20f; +const int AF_FORCE_TOTAL_BITS = 16; +const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1; +const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_AF::WriteToSnapshot +================ +*/ +void idPhysics_AF::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat; + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.noMoveTime ); + msg.WriteFloat( current.activateTime ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + msg.WriteByte( bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + quat = state->worldAxis.ToCQuat(); + + msg.WriteFloat( state->worldOrigin[0] ); + msg.WriteFloat( state->worldOrigin[1] ); + msg.WriteFloat( state->worldOrigin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + } +} + +/* +================ +idPhysics_AF::ReadFromSnapshot +================ +*/ +void idPhysics_AF::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat; + + // TODO: Check that this conditional write to delta message is OK + current.atRest = msg.ReadLong(); + current.noMoveTime = msg.ReadFloat(); + current.activateTime = msg.ReadFloat(); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + num = msg.ReadByte(); + assert( num == bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + + state->worldOrigin[0] = msg.ReadFloat(); + state->worldOrigin[1] = msg.ReadFloat(); + state->worldOrigin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + state->worldAxis = quat.ToMat3(); + } + + UpdateClipModels(); +} diff --git a/source/game/physics/Physics_AF.h b/source/game/physics/Physics_AF.h new file mode 100644 index 0000000..2998972 --- /dev/null +++ b/source/game/physics/Physics_AF.h @@ -0,0 +1,988 @@ + +#ifndef __PHYSICS_AF_H__ +#define __PHYSICS_AF_H__ + +/* +=================================================================================== + + Articulated Figure physics + + Employs a constraint force based dynamic simulation using a lagrangian + multiplier method to solve for the constraint forces. + +=================================================================================== +*/ + +class idAFConstraint; +class idAFConstraint_Fixed; +class idAFConstraint_BallAndSocketJoint; +class idAFConstraint_BallAndSocketJointFriction; +class idAFConstraint_UniversalJoint; +class idAFConstraint_UniversalJointFriction; +class idAFConstraint_CylindricalJoint; +class idAFConstraint_Hinge; +class idAFConstraint_HingeFriction; +class idAFConstraint_HingeSteering; +class idAFConstraint_Slider; +class idAFConstraint_Line; +class idAFConstraint_Plane; +class idAFConstraint_Spring; +class idAFConstraint_Contact; +class idAFConstraint_ContactFriction; +class idAFConstraint_ConeLimit; +class idAFConstraint_PyramidLimit; +class idAFBody; +class idAFTree; +class idPhysics_AF; + +typedef enum { + CONSTRAINT_INVALID, + CONSTRAINT_FIXED, + CONSTRAINT_BALLANDSOCKETJOINT, + CONSTRAINT_UNIVERSALJOINT, + CONSTRAINT_HINGE, + CONSTRAINT_HINGESTEERING, + CONSTRAINT_SLIDER, + CONSTRAINT_CYLINDRICALJOINT, + CONSTRAINT_LINE, + CONSTRAINT_PLANE, + CONSTRAINT_SPRING, + CONSTRAINT_CONTACT, + CONSTRAINT_FRICTION, + CONSTRAINT_CONELIMIT, + CONSTRAINT_PYRAMIDLIMIT +} constraintType_t; + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +// base class for all constraints +class idAFConstraint { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFConstraint( void ); + virtual ~idAFConstraint( void ); + constraintType_t GetType( void ) const { return type; } + const idStr & GetName( void ) const { return name; } + idAFBody * GetBody1( void ) const { return body1; } + idAFBody * GetBody2( void ) const { return body2; } + void SetPhysics( idPhysics_AF *p ) { physics = p; } + const idVecX & GetMultiplier( void ); + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + constraintType_t type; // constraint type + idStr name; // name of constraint + idAFBody * body1; // first constrained body + idAFBody * body2; // second constrained body, NULL for world + idPhysics_AF * physics; // for adding additional constraints like limits + + // simulation variables set by Evaluate + idMatX J1, J2; // matrix with left hand side of constraint equations + idVecX c1, c2; // right hand side of constraint equations + idVecX lo, hi, e; // low and high bounds and lcp epsilon + idAFConstraint * boxConstraint; // constraint the boxIndex refers to + int boxIndex[6]; // indexes for special box constrained variables + + // simulation variables used during calculations + idMatX invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX lm; // lagrange multipliers + int firstIndex; // index of the first constraint row in the lcp matrix + + struct constraintFlags_s { + bool allowPrimary : 1; // true if the constraint can be used as a primary constraint + bool frameConstraint : 1; // true if this constraint is added to the frame constraints + bool noCollision : 1; // true if body1 and body2 never collide with each other + bool isPrimary : 1; // true if this is a primary constraint + bool isZero : 1; // true if 's' is zero during calculations + } fl; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitSize( int size ); +}; + +// fixed or rigid joint which allows zero degrees of freedom +// constrains body1 to have a fixed position and orientation relative to body2 +class idAFConstraint_Fixed : public idAFConstraint { + +public: + idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetRelativeOrigin( const idVec3 &origin ) { this->offset = origin; } + void SetRelativeAxis( const idMat3 &axis ) { this->relAxis = axis; } + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 offset; // offset of body1 relative to body2 in body2 space + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitOffset( void ); +}; + +// ball and socket or spherical joint which allows 3 degrees of freedom +// constrains body1 relative to body2 with a ball and socket joint +class idAFConstraint_BallAndSocketJoint : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_BallAndSocketJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_BallAndSocketJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// ball and socket joint friction +class idAFConstraint_BallAndSocketJointFriction : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJointFriction( void ); + void Setup( idAFConstraint_BallAndSocketJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_BallAndSocketJoint *joint; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal, Cardan or Hooke joint which allows 2 degrees of freedom +// like a ball and socket joint but also constrains the rotation about the cardan shafts +class idAFConstraint_UniversalJoint : public idAFConstraint { + +public: + idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_UniversalJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ); + void GetShafts( idVec3 &cardanShaft1, idVec3 &cardanShaft2 ) { cardanShaft1 = shaft1; cardanShaft2 = shaft2; } + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 shaft1; // body1 cardan shaft in body1 space + idVec3 shaft2; // body2 cardan shaft in body2 space + idVec3 axis1; // cardan axis in body1 space + idVec3 axis2; // cardan axis in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_UniversalJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal joint friction +class idAFConstraint_UniversalJointFriction : public idAFConstraint { + +public: + idAFConstraint_UniversalJointFriction( void ); + void Setup( idAFConstraint_UniversalJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_UniversalJoint *joint; // universal joint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// cylindrical joint which allows 2 degrees of freedom +// constrains body1 to lie on a line relative to body2 and allows only translation along and rotation about the line +class idAFConstraint_CylindricalJoint : public idAFConstraint { + +public: + idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge, revolute or pin joint which allows 1 degree of freedom +// constrains all motion of body1 relative to body2 except the rotation about the hinge axis +class idAFConstraint_Hinge : public idAFConstraint { + +public: + idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_Hinge( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetAxis( const idVec3 &axis ); + void GetAxis( idVec3 &a1, idVec3 &a2 ) const { a1 = axis1; a2 = axis2; } + idVec3 GetAxis( void ) const; + void SetNoLimit( void ); + void SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + float GetAngle( void ) const; + void SetSteerAngle( const float degrees ); + void SetSteerSpeed( const float speed ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 axis1; // axis in body1 space + idVec3 axis2; // axis in body2 space + idMat3 initialAxis; // initial axis of body1 relative to body2 + float friction; // hinge friction + idAFConstraint_ConeLimit *coneLimit; // cone limit + idAFConstraint_HingeSteering *steering; // steering + idAFConstraint_HingeFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge joint friction +class idAFConstraint_HingeFriction : public idAFConstraint { + +public: + idAFConstraint_HingeFriction( void ); + void Setup( idAFConstraint_Hinge *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains two bodies attached to each other with a hinge to get a specified relative orientation +class idAFConstraint_HingeSteering : public idAFConstraint { + +public: + idAFConstraint_HingeSteering( void ); + void Setup( idAFConstraint_Hinge *cc ); + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void SetSteerSpeed( const float speed ) { steerSpeed = speed; } + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + float steerAngle; // desired steer angle in degrees + float steerSpeed; // steer speed + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// slider, prismatic or translational constraint which allows 1 degree of freedom +// constrains body1 to lie on a line relative to body2, the orientation is also fixed relative to body2 +class idAFConstraint_Slider : public idAFConstraint { + +public: + idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAxis( const idVec3 &ax ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 axis; // axis along which body1 slides in body2 space + idVec3 offset; // offset of body1 relative to body2 + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// line constraint which allows 4 degrees of freedom +// constrains body1 to lie on a line relative to body2, does not constrain the orientation. +class idAFConstraint_Line : public idAFConstraint { + +public: + idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// plane constraint which allows 5 degrees of freedom +// constrains body1 to lie in a plane relative to body2, does not constrain the orientation. +class idAFConstraint_Plane : public idAFConstraint { + +public: + idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetPlane( const idVec3 &normal, const idVec3 &anchor ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 planeNormal; // plane normal in body2 space + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// spring constraint which allows 6 or 5 degrees of freedom based on the spring limits +// constrains body1 relative to body2 with a spring +class idAFConstraint_Spring : public idAFConstraint { + +public: + idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ); + void SetSpring( const float stretch, const float compress, const float damping, const float restLength ); + void SetLimit( const float minLength, const float maxLength ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float kstretch; // spring constant when stretched + float kcompress; // spring constant when compressed + float damping; // spring damping + float restLength; // rest length of spring + float minLength; // minimum spring length + float maxLength; // maximum spring length + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains body1 to either be in contact with or move away from body2 +class idAFConstraint_Contact : public idAFConstraint { + +public: + idAFConstraint_Contact( void ); + ~idAFConstraint_Contact( void ); + void Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ); + const contactInfo_t & GetContact( void ) const { return contact; } + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + +protected: + contactInfo_t contact; // contact information + idAFConstraint_ContactFriction *fc; // contact friction + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// contact friction +class idAFConstraint_ContactFriction : public idAFConstraint { + +public: + idAFConstraint_ContactFriction( void ); + void Setup( idAFConstraint_Contact *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Contact *cc; // contact constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a cone relative to body2 +class idAFConstraint_ConeLimit : public idAFConstraint { + +public: + idAFConstraint_ConeLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, + const float coneAngle, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &coneAnchor ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 coneAnchor; // top of the cone in body2 space + idVec3 coneAxis; // cone axis in body2 space + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle; // cos( coneAngle / 2 ) + float sinHalfAngle; // sin( coneAngle / 4 ) + float cosHalfAngle; // cos( coneAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a pyramid relative to body2 +class idAFConstraint_PyramidLimit : public idAFConstraint { + +public: + idAFConstraint_PyramidLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &pyramidAxis ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 pyramidAnchor; // top of the pyramid in body2 space + idMat3 pyramidBasis; // pyramid basis in body2 space with base[2] being the pyramid axis + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle[2]; // cos( pyramidAngle / 2 ) + float sinHalfAngle[2]; // sin( pyramidAngle / 4 ) + float cosHalfAngle[2]; // cos( pyramidAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +typedef struct AFBodyPState_s { + idVec3 worldOrigin; // position in world space + idMat3 worldAxis; // axis at worldOrigin + idVec6 spatialVelocity; // linear and rotational velocity of body + idVec6 externalForce; // external force and torque applied to body +} AFBodyPState_t; + + +class idAFBody { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFBody( void ); + idAFBody( const idStr &name, idClipModel *clipModel, float density ); + ~idAFBody( void ); + + void Init( void ); + const idStr & GetName( void ) const { return name; } + const idVec3 & GetWorldOrigin( void ) const { return current->worldOrigin; } + const idMat3 & GetWorldAxis( void ) const { return current->worldAxis; } + const idVec3 & GetLinearVelocity( void ) const { return current->spatialVelocity.SubVec3(0); } + const idVec3 & GetAngularVelocity( void ) const { return current->spatialVelocity.SubVec3(1); } + idVec3 GetPointVelocity( const idVec3 &point ) const; + const idVec3 & GetCenterOfMass( void ) const { return centerOfMass; } + void SetClipModel( idClipModel *clipModel ); + idClipModel * GetClipModel( void ) const { return clipModel; } + void SetClipMask( const int mask ) { clipMask = mask; fl.clipMaskSet = true; } + int GetClipMask( void ) const { return clipMask; } + void SetSelfCollision( const bool enable ) { fl.selfCollision = enable; } + void SetWorldOrigin( const idVec3 &origin ) { current->worldOrigin = origin; } + void SetWorldAxis( const idMat3 &axis ) { current->worldAxis = axis; } + void SetLinearVelocity( const idVec3 &linear ) const { current->spatialVelocity.SubVec3(0) = linear; } + void SetAngularVelocity( const idVec3 &angular ) const { current->spatialVelocity.SubVec3(1) = angular; } + void SetFriction( float linear, float angular, float contact ); + float GetContactFriction( void ) const { return contactFriction; } + void SetBouncyness( float bounce ); + float GetBouncyness( void ) const { return bouncyness; } + void SetDensity( float density, const idMat3 &inertiaScale = mat3_identity ); + float GetInverseMass( void ) const { return invMass; } + idMat3 GetInverseWorldInertia( void ) const { return current->worldAxis.Transpose() * inverseInertiaTensor * current->worldAxis; } + + void SetFrictionDirection( const idVec3 &dir ); + bool GetFrictionDirection( idVec3 &dir ) const; + + void SetContactMotorDirection( const idVec3 &dir ); + bool GetContactMotorDirection( idVec3 &dir ) const; + void SetContactMotorVelocity( float vel ) { contactMotorVelocity = vel; } + float GetContactMotorVelocity( void ) const { return contactMotorVelocity; } + void SetContactMotorForce( float force ) { contactMotorForce = force; } + float GetContactMotorForce( void ) const { return contactMotorForce; } + + void AddForce( const idVec3 &point, const idVec3 &force ); + void InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const; + idVec6 & GetResponseForce( int index ) { return reinterpret_cast(response[ index * 8 ]); } + + void Save( idSaveGame *saveFile ); + void Restore( idRestoreGame *saveFile ); + +private: + // properties + idStr name; // name of body + idAFBody * parent; // parent of this body + idList children; // children of this body + idClipModel * clipModel; // model used for collision detection + idAFConstraint * primaryConstraint; // primary constraint (this->constraint->body1 = this) + idListconstraints; // all constraints attached to this body + idAFTree * tree; // tree structure this body is part of + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bounce + int clipMask; // contents this body collides with + idVec3 frictionDir; // specifies a single direction of friction in body space + idVec3 contactMotorDir; // contact motor direction + float contactMotorVelocity; // contact motor velocity + float contactMotorForce; // maximum force applied to reach the motor velocity + + // derived properties + float mass; // mass of body + float invMass; // inverse mass + idVec3 centerOfMass; // center of mass of body + idMat3 inertiaTensor; // inertia tensor + idMat3 inverseInertiaTensor; // inverse inertia tensor + + // physics state + AFBodyPState_t state[2]; + AFBodyPState_t * current; // current physics state + AFBodyPState_t * next; // next physics state + AFBodyPState_t saved; // saved physics state + idVec3 atRestOrigin; // origin at rest + idMat3 atRestAxis; // axis at rest + + // simulation variables used during calculations + idMatX inverseWorldSpatialInertia; // inverse spatial inertia in world space + idMatX I, invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX totalForce; // total force acting on body + idVecX auxForce; // force from auxiliary constraints + idVecX acceleration; // acceleration + float * response; // forces on body in response to auxiliary constraint forces + int * responseIndex; // index to response forces + int numResponses; // number of response forces + int maxAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body + int maxSubTreeAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body or one of it's children + + struct bodyFlags_s { + bool clipMaskSet : 1; // true if this body has a clip mask set + bool selfCollision : 1; // true if this body can collide with other bodies of this AF + bool spatialInertiaSparse: 1; // true if the spatial inertia matrix is sparse + bool useFrictionDir : 1; // true if a single friction direction should be used + bool useContactMotorDir : 1; // true if a contact motor should be used + bool isZero : 1; // true if 's' is zero during calculations + } fl; +}; + + +//=============================================================== +// +// idAFTree +// +//=============================================================== + +class idAFTree { + friend class idPhysics_AF; + +public: + void Factor( void ) const; + void Solve( int auxiliaryIndex = 0 ) const; + void Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const; + void CalculateForces( float timeStep ) const; + void SetMaxSubTreeAuxiliaryIndex( void ); + void SortBodies( void ); + void SortBodies_r( idList&sortedList, idAFBody *body ); + void DebugDraw( const idVec4 &color ) const; + +private: + idList sortedBodies; +}; + + +//=============================================================== +// +// idPhysics_AF +// +//=============================================================== + +typedef struct AFPState_s { + int atRest; // >= 0 if articulated figure is at rest + float noMoveTime; // time the articulated figure is hardly moving + float activateTime; // time since last activation + float lastTimeStep; // last time step + idVec6 pushVelocity; // velocity with which the af is pushed +} AFPState_t; + +typedef struct AFCollision_s { + trace_t trace; + idAFBody * body; +} AFCollision_t; + +class idPhysics_AF : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_AF ); + + idPhysics_AF( void ); + ~idPhysics_AF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + int AddBody( idAFBody *body ); // returns body id + void AddConstraint( idAFConstraint *constraint ); + void AddFrameConstraint( idAFConstraint *constraint ); + // force a body to have a certain id + void ForceBodyId( idAFBody *body, int newId ); + // get body or constraint id + int GetBodyId( idAFBody *body ) const; + int GetBodyId( const char *bodyName ) const; + int GetConstraintId( idAFConstraint *constraint ) const; + int GetConstraintId( const char *constraintName ) const; + // number of bodies and constraints + int GetNumBodies( void ) const; + int GetNumConstraints( void ) const; + // retrieve body or constraint + idAFBody * GetBody( const char *bodyName ) const; + idAFBody * GetBody( const int id ) const; + idAFBody * GetMasterBody( void ) const { return masterBody; } + idAFConstraint * GetConstraint( const char *constraintName ) const; + idAFConstraint * GetConstraint( const int id ) const; + // delete body or constraint + void DeleteBody( const char *bodyName ); + void DeleteBody( const int id ); + void DeleteConstraint( const char *constraintName ); + void DeleteConstraint( const int id ); + + // get all the contact constraints acting on the body + int GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const; + // set the default friction for bodies + void SetDefaultFriction( float linear, float angular, float contact ); + // suspend settings + void SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ); + // set the time and tolerances used to determine if the simulation can be suspended when the figure hardly moves for a while + void SetSuspendTolerance( const float noMoveTime, const float translationTolerance, const float rotationTolerance ); + // set minimum and maximum simulation time in seconds + void SetSuspendTime( const float minTime, const float maxTime ); + // set the time scale value + void SetTimeScale( const float ts ) { timeScale = ts; } + // set time scale ramp + void SetTimeScaleRamp( const float start, const float end ); + // set the joint friction scale + void SetJointFrictionScale( const float scale ) { jointFrictionScale = scale; } + // set joint friction dent + void SetJointFrictionDent( const float dent, const float start, const float end ); + // get the current joint friction scale + float GetJointFrictionScale( void ) const; + // set the contact friction scale + void SetContactFrictionScale( const float scale ) { contactFrictionScale = scale; } + // set contact friction dent + void SetContactFrictionDent( const float dent, const float start, const float end ); + // get the current contact friction scale + float GetContactFrictionScale( void ) const; + // enable or disable collision detection + void SetCollision( const bool enable ) { enableCollision = enable; } + // enable or disable self collision + void SetSelfCollision( const bool enable ) { selfCollision = enable; } + // enable or disable coming to a dead stop + void SetComeToRest( bool enable ) { comeToRest = enable; } + // call when structure of articulated figure changes + void SetChanged( void ) { changedAF = true; } +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + // enable or disable fast evaluation + void SetFastEval( const bool enable ) { fastEval = enable; } +// RAVEN END + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + // lock of unlock the world constraints + void LockWorldConstraints( const bool lock ) { worldConstraintsLocked = lock; } + // set force pushable + void SetForcePushable( const bool enable ) { forcePushable = enable; } + // update the clip model positions + void UpdateClipModels( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + //MCG: added SetImpulseThreshold + void SetImpulseThreshold( float newIT ) { impulseThreshold = newIT; }; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + void Activate( void ); + void PutToRest( void ); + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // articulated figure + idList trees; // tree structures + idList bodies; // all bodies + idListconstraints; // all frame independent constraints + idListprimaryConstraints; // list with primary constraints + idListauxiliaryConstraints; // list with auxiliary constraints + idListframeConstraints; // constraints that only live one frame + idListcontactConstraints; // contact constraints + idList contactBodies; // body id for each contact + idList collisions; // collisions + bool changedAF; // true when the articulated figure just changed + + // properties + float linearFriction; // default translational friction + float angularFriction; // default rotational friction + float contactFriction; // default friction with contact surfaces + float bouncyness; // default bouncyness + float totalMass; // total mass of articulated figure + float forceTotalMass; // force this total mass + + idVec2 suspendVelocity; // simulation may not be suspended if a body has more velocity + idVec2 suspendAcceleration; // simulation may not be suspended if a body has more acceleration + float noMoveTime; // suspend simulation if hardly any movement for this many seconds + float noMoveTranslation; // maximum translation considered no movement + float noMoveRotation; // maximum rotation considered no movement + float minMoveTime; // if > 0 the simulation is never suspended before running this many seconds + float maxMoveTime; // if > 0 the simulation is always suspeded after running this many seconds + float impulseThreshold; // threshold below which impulses are ignored to avoid continuous activation + + float timeScale; // the time is scaled with this value for slow motion effects + float timeScaleRampStart; // start of time scale change + float timeScaleRampEnd; // end of time scale change + + float jointFrictionScale; // joint friction scale + float jointFrictionDent; // joint friction dives from 1 to this value and goes up again + float jointFrictionDentStart; // start time of joint friction dent + float jointFrictionDentEnd; // end time of joint friction dent + float jointFrictionDentScale; // dent scale + + float contactFrictionScale; // contact friction scale + float contactFrictionDent; // contact friction dives from 1 to this value and goes up again + float contactFrictionDentStart; // start time of contact friction dent + float contactFrictionDentEnd; // end time of contact friction dent + float contactFrictionDentScale; // dent scale + + bool enableCollision; // if true collision detection is enabled + bool selfCollision; // if true the self collision is allowed + bool comeToRest; // if true the figure can come to rest + bool linearTime; // if true use the linear time algorithm + bool noImpact; // if true do not activate when another object collides + bool worldConstraintsLocked; // if true world constraints cannot be moved + bool forcePushable; // if true can be pushed even when bound to a master +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; // if true the fast eval is on +// RAVEN END + + // physics state + AFPState_t current; + AFPState_t saved; + + idAFBody * masterBody; // master body + idLCP * lcp; // linear complementarity problem solver + +private: + void BuildTrees( void ); + bool IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const; + void PrimaryFactor( void ); + void EvaluateBodies( float timeStep ); + void EvaluateConstraints( float timeStep ); + void AddFrameConstraints( void ); + void RemoveFrameConstraints( void ); + void ApplyFriction( float timeStep, float endTimeMSec ); + void PrimaryForces( float timeStep ); + void AuxiliaryForces( float timeStep ); + void VerifyContactConstraints( void ); + void SetupContactConstraints( void ); + void ApplyContactForces( void ); + void Evolve( float timeStep ); + idEntity * SetupCollisionForBody( idAFBody *body ) const; + bool CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ); + bool ApplyCollisions( float timeStep ); + void CheckForCollisions( float timeStep ); + void ClearExternalForce( void ); + void AddGravity( void ); + void SwapStates( void ); + bool TestIfAtRest( float timeStep ); + void Rest( void ); + void AddPushVelocity( const idVec6 &pushVelocity ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_AF_H__ */ diff --git a/source/game/physics/Physics_Actor.cpp b/source/game/physics/Physics_Actor.cpp new file mode 100644 index 0000000..7e956a8 --- /dev/null +++ b/source/game/physics/Physics_Actor.cpp @@ -0,0 +1,373 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Actor ) +END_CLASS + +/* +================ +idPhysics_Actor::idPhysics_Actor +================ +*/ +idPhysics_Actor::idPhysics_Actor( void ) { + clipModel = NULL; + SetClipModelAxis(); + mass = 100.0f; + invMass = 1.0f / mass; + masterEntity = NULL; + masterYaw = 0.0f; + masterDeltaYaw = 0.0f; + groundEntityPtr = NULL; +} + +/* +================ +idPhysics_Actor::~idPhysics_Actor +================ +*/ +idPhysics_Actor::~idPhysics_Actor( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +idPhysics_Actor::Save +================ +*/ +void idPhysics_Actor::Save( idSaveGame *savefile ) const { + + savefile->WriteClipModel( clipModel ); + savefile->WriteMat3( clipModelAxis ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( invMass ); + + savefile->WriteObject( masterEntity ); + savefile->WriteFloat( masterYaw ); + savefile->WriteFloat( masterDeltaYaw ); + + groundEntityPtr.Save( savefile ); +} + +/* +================ +idPhysics_Actor::Restore +================ +*/ +void idPhysics_Actor::Restore( idRestoreGame *savefile ) { + + savefile->ReadClipModel( clipModel ); + savefile->ReadMat3( clipModelAxis ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( invMass ); + + savefile->ReadObject( reinterpret_cast( masterEntity ) ); + savefile->ReadFloat( masterYaw ); + savefile->ReadFloat( masterDeltaYaw ); + + groundEntityPtr.Restore( savefile ); +} + +/* +================ +idPhysics_Actor::SetClipModelAxis +================ +*/ +void idPhysics_Actor::SetClipModelAxis( void ) { + // align clip model to gravity direction + if ( ( gravityNormal[2] == -1.0f ) || ( gravityNormal == vec3_zero ) ) { + clipModelAxis.Identity(); + } + else { + clipModelAxis[2] = -gravityNormal; + clipModelAxis[2].NormalVectors( clipModelAxis[0], clipModelAxis[1] ); + clipModelAxis[1] = -clipModelAxis[1]; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::GetGravityAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetGravityAxis( void ) const { + return clipModelAxis; +} + +/* +================ +idPhysics_Actor::GetMasterDeltaYaw +================ +*/ +float idPhysics_Actor::GetMasterDeltaYaw( void ) const { + return masterDeltaYaw; +} + +/* +================ +idPhysics_Actor::GetGroundEntity +================ +*/ +idEntity *idPhysics_Actor::GetGroundEntity( void ) const { + return groundEntityPtr.GetEntity(); +} + +/* +================ +idPhysics_Actor::SetClipModel +================ +*/ +void idPhysics_Actor::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + assert( self ); + assert( model ); // a clip model is required + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::GetClipModel +================ +*/ +idClipModel *idPhysics_Actor::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Actor::GetNumClipModels +================ +*/ +int idPhysics_Actor::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_Actor::SetMass +================ +*/ +void idPhysics_Actor::SetMass( float _mass, int id ) { + assert( _mass > 0.0f ); + mass = _mass; + invMass = 1.0f / _mass; +} + +/* +================ +idPhysics_Actor::GetMass +================ +*/ +float idPhysics_Actor::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_Actor::SetContents +================ +*/ +void idPhysics_Actor::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_Actor::GetContents +================ +*/ +int idPhysics_Actor::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_Actor::GetBounds +================ +*/ +const idBounds &idPhysics_Actor::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_Actor::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Actor::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_Actor::IsPushable +================ +*/ +bool idPhysics_Actor::IsPushable( void ) const { + return ( masterEntity == NULL ); +} + +/* +================ +idPhysics_Actor::GetOrigin +================ +*/ +const idVec3 &idPhysics_Actor::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +idPhysics_Player::GetAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetAxis( int id ) const { + return clipModel->GetAxis(); +} + +/* +================ +idPhysics_Actor::SetGravity +================ +*/ +void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { + if ( newGravity != gravityVector ) { + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + } +} + +/* +================ +idPhysics_Actor::ClipTranslation +================ +*/ +void idPhysics_Actor::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::ClipRotation +================ +*/ +void idPhysics_Actor::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::ClipContents +================ +*/ +int idPhysics_Actor::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip models + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::DisableClip +================ +*/ +void idPhysics_Actor::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_Actor::EnableClip +================ +*/ +void idPhysics_Actor::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_Actor::UnlinkClip +================ +*/ +void idPhysics_Actor::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_Actor::LinkClip +================ +*/ +void idPhysics_Actor::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::EvaluateContacts +================ +*/ +bool idPhysics_Actor::EvaluateContacts( void ) { + + // get all the ground contacts + ClearContacts(); + AddGroundContacts( clipModel ); + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} diff --git a/source/game/physics/Physics_Actor.h b/source/game/physics/Physics_Actor.h new file mode 100644 index 0000000..748fb06 --- /dev/null +++ b/source/game/physics/Physics_Actor.h @@ -0,0 +1,89 @@ + +#ifndef __PHYSICS_ACTOR_H__ +#define __PHYSICS_ACTOR_H__ + +/* +=================================================================================== + + Actor physics base class + + An actor typically uses one collision model which is aligned with the gravity + direction. The collision model is usually a simple box with the origin at the + bottom center. + +=================================================================================== +*/ + +class idPhysics_Actor : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Actor ); + + idPhysics_Actor( void ); + ~idPhysics_Actor( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // get delta yaw of master + float GetMasterDeltaYaw( void ) const; + // returns the ground entity + idEntity * GetGroundEntity( void ) const; + // align the clip model with the gravity direction + void SetClipModelAxis( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool IsPushable( void ) const; + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); +// RAVEN BEGIN +// abahr: made virtual + virtual const idMat3& GetGravityAxis( void ) const; +// RAVEN END + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + +protected: + idClipModel * clipModel; // clip model used for collision detection + idMat3 clipModelAxis; // axis of clip model aligned with gravity direction + + // derived properties + float mass; + float invMass; + + // master + idEntity * masterEntity; + float masterYaw; + float masterDeltaYaw; + + // results of last evaluate + idEntityPtr groundEntityPtr; +}; + +#endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/source/game/physics/Physics_Base.cpp b/source/game/physics/Physics_Base.cpp new file mode 100644 index 0000000..1cacbf3 --- /dev/null +++ b/source/game/physics/Physics_Base.cpp @@ -0,0 +1,872 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Base ) +END_CLASS + +/* +================ +idPhysics_Base::idPhysics_Base +================ +*/ +idPhysics_Base::idPhysics_Base( void ) { + self = NULL; + clipMask = 0; + SetGravity( gameLocal.GetGravity() ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::~idPhysics_Base +================ +*/ +idPhysics_Base::~idPhysics_Base( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::Save +================ +*/ +void idPhysics_Base::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + savefile->WriteInt( clipMask ); + savefile->WriteVec3( gravityVector ); + savefile->WriteVec3( gravityNormal ); + + savefile->WriteInt( contacts.Num() ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->WriteContactInfo( contacts[i] ); + } + + savefile->WriteInt( contactEntities.Num() ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Save( savefile ); + } +} + +/* +================ +idPhysics_Base::Restore +================ +*/ +void idPhysics_Base::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadInt( clipMask ); + savefile->ReadVec3( gravityVector ); + savefile->ReadVec3( gravityNormal ); + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Restore( savefile ); + } +} + +/* +================ +idPhysics_Base::SetSelf +================ +*/ +void idPhysics_Base::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Base::SetClipModel +================ +*/ +void idPhysics_Base::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_Base::GetClipModel +================ +*/ +idClipModel *idPhysics_Base::GetClipModel( int id ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetNumClipModels +================ +*/ +int idPhysics_Base::GetNumClipModels( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetMass +================ +*/ +void idPhysics_Base::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Base::GetMass +================ +*/ +float idPhysics_Base::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Base::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Base::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Base::SetContents +================ +*/ +void idPhysics_Base::SetContents( int contents, int id ) { +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +int idPhysics_Base::GetContents( int id ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +void idPhysics_Base::SetClipMask( int mask, int id ) { + clipMask = mask; +} + +/* +================ +idPhysics_Base::GetClipMask +================ +*/ +int idPhysics_Base::GetClipMask( int id ) const { + return clipMask; +} + +/* +================ +idPhysics_Base::GetBounds +================ +*/ +const idBounds &idPhysics_Base::GetBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Base::GetAbsBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::Evaluate +================ +*/ +bool idPhysics_Base::Evaluate( int timeStepMSec, int endTimeMSec ) { + return false; +} + +/* +================ +idPhysics_Base::UpdateTime +================ +*/ +void idPhysics_Base::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Base::GetTime +================ +*/ +int idPhysics_Base::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetImpactInfo +================ +*/ +void idPhysics_Base::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Base::ApplyImpulse +================ +*/ +void idPhysics_Base::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Base::AddForce +================ +*/ +void idPhysics_Base::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Base::Activate +================ +*/ +void idPhysics_Base::Activate( void ) { +} + +/* +================ +idPhysics_Base::PutToRest +================ +*/ +void idPhysics_Base::PutToRest( void ) { +} + +/* +================ +idPhysics_Base::IsAtRest +================ +*/ +bool idPhysics_Base::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Base::GetRestStartTime +================ +*/ +int idPhysics_Base::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::IsPushable +================ +*/ +bool idPhysics_Base::IsPushable( void ) const { + return true; +} + +// RAVEN BEGIN +// bdube: water interraction +bool idPhysics_Base::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Base::SaveState +================ +*/ +void idPhysics_Base::SaveState( void ) { +} + +/* +================ +idPhysics_Base::RestoreState +================ +*/ +void idPhysics_Base::RestoreState( void ) { +} + +/* +================ +idPhysics_Base::SetOrigin +================ +*/ +void idPhysics_Base::SetOrigin( const idVec3 &newOrigin, int id ) { +} + +/* +================ +idPhysics_Base::SetAxis +================ +*/ +void idPhysics_Base::SetAxis( const idMat3 &newAxis, int id ) { +} + +/* +================ +idPhysics_Base::Translate +================ +*/ +void idPhysics_Base::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Base::Rotate +================ +*/ +void idPhysics_Base::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Base::GetOrigin +================ +*/ +const idVec3 &idPhysics_Base::GetOrigin( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAxis +================ +*/ +const idMat3 &idPhysics_Base::GetAxis( int id ) const { + return mat3_identity; +} + +/* +================ +idPhysics_Base::SetLinearVelocity +================ +*/ +void idPhysics_Base::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Base::SetAngularVelocity +================ +*/ +void idPhysics_Base::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Base::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetGravity +================ +*/ +void idPhysics_Base::SetGravity( const idVec3 &newGravity ) { + gravityVector = newGravity; + gravityNormal = newGravity; + gravityNormal.Normalize(); +} + +/* +================ +idPhysics_Base::GetGravity +================ +*/ +const idVec3 &idPhysics_Base::GetGravity( void ) const { + return gravityVector; +} + +/* +================ +idPhysics_Base::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Base::GetGravityNormal( void ) const { + return gravityNormal; +} + +/* +================ +idPhysics_Base::ClipTranslation +================ +*/ +void idPhysics_Base::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipRotation +================ +*/ +void idPhysics_Base::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipContents +================ +*/ +int idPhysics_Base::ClipContents( const idClipModel *model ) const { + return 0; +} + +/* +================ +idPhysics_Base::DisableClip +================ +*/ +void idPhysics_Base::DisableClip( void ) { +} + +/* +================ +idPhysics_Base::EnableClip +================ +*/ +void idPhysics_Base::EnableClip( void ) { +} + +/* +================ +idPhysics_Base::UnlinkClip +================ +*/ +void idPhysics_Base::UnlinkClip( void ) { +} + +/* +================ +idPhysics_Base::LinkClip +================ +*/ +void idPhysics_Base::LinkClip( void ) { +} + +/* +================ +idPhysics_Base::EvaluateContacts +================ +*/ +bool idPhysics_Base::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Base::GetNumContacts +================ +*/ +int idPhysics_Base::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +idPhysics_Base::GetContact +================ +*/ +const contactInfo_t &idPhysics_Base::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +idPhysics_Base::ClearContacts +================ +*/ +void idPhysics_Base::ClearContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent ) { + ent->RemoveContactEntity( self ); + } + } + contacts.SetNum( 0, false ); +} + +/* +================ +idPhysics_Base::AddContactEntity +================ +*/ +void idPhysics_Base::AddContactEntity( idEntity *e ) { + int i; + idEntity *ent; + bool found = false; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent == NULL ) { + contactEntities.RemoveIndex( i-- ); + } + if ( ent == e ) { + found = true; + } + } + if ( !found ) { + contactEntities.Alloc() = e; + } +} + +/* +================ +idPhysics_Base::RemoveContactEntity +================ +*/ +void idPhysics_Base::RemoveContactEntity( idEntity *e ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( !ent ) { + contactEntities.RemoveIndex( i-- ); + continue; + } + if ( ent == e ) { + contactEntities.RemoveIndex( i-- ); + return; + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + normal += GetContact( ix ).normal; + } + + return normal.ToNormal(); +} + +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetGroundContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + if ( GetContact(ix).normal * -gravityNormal > 0.0f ) { + normal += GetContact( ix ).normal; + } + } + + return normal.ToNormal(); +} +// RAVEN END + +/* +================ +idPhysics_Base::HasGroundContacts +================ +*/ +bool idPhysics_Base::HasGroundContacts( void ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundEntity +================ +*/ +bool idPhysics_Base::IsGroundEntity( int entityNum ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundClipModel +================ +*/ +bool idPhysics_Base::IsGroundClipModel( int entityNum, int id ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && contacts[i].id == id && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::SetPushed +================ +*/ +void idPhysics_Base::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Base::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetMaster +================ +*/ +void idPhysics_Base::SetMaster( idEntity *master, const bool orientated ) { +} + +/* +================ +idPhysics_Base::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Base::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Base::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetLinearEndTime +================ +*/ +int idPhysics_Base::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetAngularEndTime +================ +*/ +int idPhysics_Base::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::AddGroundContacts +================ +*/ +void idPhysics_Base::AddGroundContacts( const idClipModel *clipModel ) { + idVec6 dir; + int index, num; + + index = contacts.Num(); + contacts.SetNum( index + 10, false ); + + dir.SubVec3(0) = gravityNormal; + dir.SubVec3(1) = vec3_origin; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[index], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( index + num, false ); +} + +/* +================ +idPhysics_Base::AddContactEntitiesForContacts +================ +*/ +void idPhysics_Base::AddContactEntitiesForContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent && ent != self ) { + ent->AddContactEntity( self ); + } + } +} + +/* +================ +idPhysics_Base::ActivateContactEntities +================ +*/ +void idPhysics_Base::ActivateContactEntities( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent ) { + ent->ActivatePhysics( self ); + } else { + contactEntities.RemoveIndex( i-- ); + } + } +} + +/* +================ +idPhysics_Base::IsOutsideWorld +================ +*/ +bool idPhysics_Base::IsOutsideWorld( void ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.GetWorldBounds( self ).Expand( 128.0f ).IntersectsBounds( GetAbsBounds() ) ) { +// RAVEN END + return true; + } + return false; +} + +/* +================ +idPhysics_Base::DrawVelocity +================ +*/ +void idPhysics_Base::DrawVelocity( int id, float linearScale, float angularScale ) const { + idVec3 dir, org, vec, start, end; + idMat3 axis; + float length, a; + + dir = GetLinearVelocity( id ); + dir *= linearScale; + if ( dir.LengthSqr() > Square( 0.1f ) ) { + dir.Truncate( 10.0f ); + org = GetOrigin( id ); + gameRenderWorld->DebugArrow( colorRed, org, org + dir, 1 ); + } + + dir = GetAngularVelocity( id ); + length = dir.Normalize(); + length *= angularScale; + if ( length > 0.1f ) { + if ( length < 60.0f ) { + length = 60.0f; + } + else if ( length > 360.0f ) { + length = 360.0f; + } + axis = GetAxis( id ); + vec = axis[2]; + if ( idMath::Fabs( dir * vec ) > 0.99f ) { + vec = axis[0]; + } + vec -= vec * dir * vec; + vec.Normalize(); + vec *= 4.0f; + start = org + vec; + for ( a = 20.0f; a < length; a += 20.0f ) { + end = org + idRotation( vec3_origin, dir, -a ).ToMat3() * vec; + gameRenderWorld->DebugLine( colorBlue, start, end, 1 ); + start = end; + } + end = org + idRotation( vec3_origin, dir, -length ).ToMat3() * vec; + gameRenderWorld->DebugArrow( colorBlue, start, end, 1 ); + } +} + +/* +================ +idPhysics_Base::WriteToSnapshot +================ +*/ +void idPhysics_Base::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Base::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} diff --git a/source/game/physics/Physics_Base.h b/source/game/physics/Physics_Base.h new file mode 100644 index 0000000..18d1437 --- /dev/null +++ b/source/game/physics/Physics_Base.h @@ -0,0 +1,160 @@ + +#ifndef __PHYSICS_BASE_H__ +#define __PHYSICS_BASE_H__ + +/* +=============================================================================== + + Physics base for a moving object using one or more collision models. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Changed from #define to typedef to fix compiler issues +typedef idEntityPtr contactEntity_t; +// RAVEN END + +class idPhysics_Base : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Base ); + + idPhysics_Base( void ); + ~idPhysics_Base( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: added center mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + +// RAVEN BEGIN +// abahr: helper function used in projectiles + virtual const idVec3 GetContactNormal() const; + virtual const idVec3 GetGroundContactNormal() const; +// RAVEN END + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object + int clipMask; // contents the physics object collides with + idVec3 gravityVector; // direction and magnitude of gravity + idVec3 gravityNormal; // normalized direction of gravity + idList contacts; // contacts with other physics objects + idList contactEntities; // entities touching this physics object + +protected: + // add ground contacts for the clip model + void AddGroundContacts( const idClipModel *clipModel ); + // add contact entity links to contact entities + void AddContactEntitiesForContacts( void ); + // active all contact entities + void ActivateContactEntities( void ); + // returns true if the whole physics object is outside the world bounds + bool IsOutsideWorld( void ) const; + // draw linear and angular velocity + void DrawVelocity( int id, float linearScale, float angularScale ) const; +}; + +#endif /* !__PHYSICS_BASE_H__ */ diff --git a/source/game/physics/Physics_Monster.cpp b/source/game/physics/Physics_Monster.cpp new file mode 100644 index 0000000..751edb8 --- /dev/null +++ b/source/game/physics/Physics_Monster.cpp @@ -0,0 +1,838 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster ) +END_CLASS + +const float OVERCLIP = 1.001f; + +/* +===================== +idPhysics_Monster::CheckGround +===================== +*/ +void idPhysics_Monster::CheckGround( monsterPState_t &state ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + down = state.origin + gravityNormal * CONTACT_EPSILON; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( groundTrace.fraction == 1.0f ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) { + state.onGround = false; + return; + } + + state.onGround = true; + + // let the entity know about the collision + self->Collide( groundTrace, state.velocity ); + + // apply impact to a non world floor entity + if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +===================== +idPhysics_Monster::SlideMove +===================== +*/ +monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + + if ( tr.c.entityNum != ENTITYNUM_NONE ) { + blockingEntity = gameLocal.entities[ tr.c.entityNum ]; + } + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + + return MM_BLOCKED; +} + +/* +===================== +idPhysics_Monster::StepMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel; + monsterMoveResult_t result1, result2; + float stepdist; + float nostepdist; + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta ); + if ( result1 == MM_OK ) { + velocity = noStepVel; +// RAVEN BEGIN +// bdube: dont step when there is no gravity + if ( gravityNormal == vec3_zero || forceDeltaMove ) { +// RAVEN END + start = noStepPos; + return MM_OK; + } + + // try to step down so that we walk down slopes and stairs at a normal rate + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction < 1.0f ) { + start = tr.endpos; + return MM_STEPPED; + } else { + start = noStepPos; + return MM_OK; + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockingEntity && blockingEntity->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // try to step down in case walking into an actor while going down steps + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = tr.endpos; + velocity = noStepVel; + return MM_BLOCKED; + } + + if ( gravityNormal == vec3_zero ) { + return result1; + } + + // try to step up + up = start - gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction == 0.0f ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta ); + if ( result2 == MM_BLOCKED ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // step down again + down = stepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + stepPos = tr.endpos; + + // if the move is further without stepping up, or the slope is too steap, don't step up + nostepdist = ( noStepPos - start ).LengthSqr(); + stepdist = ( stepPos - start ).LengthSqr(); + if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) { + start = noStepPos; + velocity = noStepVel; + return MM_SLIDING; + } + + start = stepPos; + velocity = stepVel; + + return MM_STEPPED; +} + +/* +================ +idPhysics_Monster::Activate +================ +*/ +void idPhysics_Monster::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::Rest +================ +*/ +void idPhysics_Monster::Rest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::PutToRest +================ +*/ +void idPhysics_Monster::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_Monster::idPhysics_Monster +================ +*/ +idPhysics_Monster::idPhysics_Monster( void ) { + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + saved = current; + + delta.Zero(); + maxStepHeight = 18.0f; + minFloorCosine = 0.7f; + moveResult = MM_OK; + forceDeltaMove = false; + fly = false; + useVelocityMove = false; + noImpact = false; + blockingEntity = NULL; +} + +/* +================ +idPhysics_Monster_SavePState +================ +*/ +void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.onGround ); + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Monster_RestorePState +================ +*/ +void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.onGround ); + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson: Added unrestored var +} + +/* +================ +idPhysics_Monster::Save +================ +*/ +void idPhysics_Monster::Save( idSaveGame *savefile ) const { + + idPhysics_Monster_SavePState( savefile, current ); + idPhysics_Monster_SavePState( savefile, saved ); + + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( minFloorCosine ); + savefile->WriteVec3( delta ); + + savefile->WriteBool( forceDeltaMove ); + savefile->WriteBool( fly ); + savefile->WriteBool( useVelocityMove ); + savefile->WriteBool( noImpact ); + + savefile->WriteInt( (int)moveResult ); + savefile->WriteObject( blockingEntity ); +} + +/* +================ +idPhysics_Monster::Restore +================ +*/ +void idPhysics_Monster::Restore( idRestoreGame *savefile ) { + + idPhysics_Monster_RestorePState( savefile, current ); + idPhysics_Monster_RestorePState( savefile, saved ); + + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( minFloorCosine ); + savefile->ReadVec3( delta ); + + savefile->ReadBool( forceDeltaMove ); + savefile->ReadBool( fly ); + savefile->ReadBool( useVelocityMove ); + savefile->ReadBool( noImpact ); + + savefile->ReadInt( (int &)moveResult ); + savefile->ReadObject( reinterpret_cast( blockingEntity ) ); +} + +/* +================ +idPhysics_Monster::SetDelta +================ +*/ +void idPhysics_Monster::SetDelta( const idVec3 &d ) { + delta = d; + if ( delta != vec3_origin ) { + Activate(); + } +} + +/* +================ +idPhysics_Monster::SetMaxStepHeight +================ +*/ +void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Monster::GetMaxStepHeight +================ +*/ +float idPhysics_Monster::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Monster::OnGround +================ +*/ +bool idPhysics_Monster::OnGround( void ) const { + return current.onGround; +} + +/* +================ +idPhysics_Monster::GetSlideMoveEntity +================ +*/ +idEntity *idPhysics_Monster::GetSlideMoveEntity( void ) const { + return blockingEntity; +} + +/* +================ +idPhysics_Monster::GetMoveResult +================ +*/ +monsterMoveResult_t idPhysics_Monster::GetMoveResult( void ) const { + return moveResult; +} + +/* +================ +idPhysics_Monster::ForceDeltaMove +================ +*/ +void idPhysics_Monster::ForceDeltaMove( bool force ) { + forceDeltaMove = force; +} + +/* +================ +idPhysics_Monster::UseFlyMove +================ +*/ +void idPhysics_Monster::UseFlyMove( bool force ) { + fly = force; +} + +/* +================ +idPhysics_Monster::UseVelocityMove +================ +*/ +void idPhysics_Monster::UseVelocityMove( bool force ) { + useVelocityMove = force; +} + +/* +================ +idPhysics_Monster::EnableImpact +================ +*/ +void idPhysics_Monster::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_Monster::DisableImpact +================ +*/ +void idPhysics_Monster::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_Monster::Evaluate +================ +*/ +bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return true; + } + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + idPhysics_Monster::CheckGround( current ); + + // if not on the ground or moving upwards + float upspeed; + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) { + if ( upspeed < 0.0f ) { + moveResult = MM_FALLING; + } + else { + current.onGround = false; + moveResult = MM_OK; + } + delta = current.velocity * timeStep; + if ( delta != vec3_origin ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly ) { + current.velocity += gravityVector * timeStep; + } + } else { + if ( useVelocityMove ) { + delta = current.velocity * timeStep; + } else { + current.velocity = delta / timeStep; + } + + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + + if ( delta == vec3_origin ) { + Rest(); + } else { + // try moving into the desired direction +// RAVEN BEGIN +// jshepard: flying creatures, even if not using fly move, shouldn't use step move + if( self->IsType( idAI::GetClassType() ) && ((idAI*)self)->move.moveType == MOVETYPE_FLY ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + } else { + moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + } + delta.Zero(); +// RAVEN END + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + Rest(); + } + +// RAVEN BEGIN +// bdube: determine if we moved this frame + return true; +// RAVEN END +} + +/* +================ +idPhysics_Monster::UpdateTime +================ +*/ +void idPhysics_Monster::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Monster::GetTime +================ +*/ +int idPhysics_Monster::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Monster::GetImpactInfo +================ +*/ +void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Monster::ApplyImpulse +================ +*/ +void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.velocity += impulse * invMass; + Activate(); +} + +/* +================ +idPhysics_Monster::IsAtRest +================ +*/ +bool idPhysics_Monster::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Monster::GetRestStartTime +================ +*/ +int idPhysics_Monster::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Monster::SaveState +================ +*/ +void idPhysics_Monster::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Monster::RestoreState +================ +*/ +void idPhysics_Monster::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Translate +================ +*/ +void idPhysics_Monster::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Rotate +================ +*/ +void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::SetLinearVelocity +================ +*/ +void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_Monster::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Monster::SetPushed +================ +*/ +void idPhysics_Monster::SetPushed( int deltaTime ) { + // velocity with which the monster is pushed + current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_Monster::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Monster::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +const float MONSTER_VELOCITY_MAX = 4000; +const int MONSTER_VELOCITY_TOTAL_BITS = 16; +const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1; +const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS; + +/* +================ +idPhysics_Monster::WriteToSnapshot +================ +*/ +void idPhysics_Monster::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteLong( current.atRest ); + msg.WriteBits( current.onGround, 1 ); +} + +/* +================ +idPhysics_Monster::ReadFromSnapshot +================ +*/ +void idPhysics_Monster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.atRest = msg.ReadLong(); + current.onGround = msg.ReadBits( 1 ) != 0; +} diff --git a/source/game/physics/Physics_Monster.h b/source/game/physics/Physics_Monster.h new file mode 100644 index 0000000..f35b7cc --- /dev/null +++ b/source/game/physics/Physics_Monster.h @@ -0,0 +1,129 @@ + +#ifndef __PHYSICS_MONSTER_H__ +#define __PHYSICS_MONSTER_H__ + +/* +=================================================================================== + + Monster physics + + Simulates the motion of a monster through the environment. The monster motion + is typically driven by animations. + +=================================================================================== +*/ + +typedef enum { + MM_OK, + MM_SLIDING, + MM_BLOCKED, + MM_STEPPED, + MM_FALLING +} monsterMoveResult_t; + +typedef struct monsterPState_s { + int atRest; + bool onGround; + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END +} monsterPState_t; + +class idPhysics_Monster : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Monster ); + + idPhysics_Monster( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // maximum step up the monster can take, default 18 units + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + // minimum cosine of floor angle to be able to stand on the floor + void SetMinFloorCosine( const float newMinFloorCosine ); + // set delta for next move + void SetDelta( const idVec3 &d ); + // returns true if monster is standing on the ground + bool OnGround( void ) const; + // returns the movement result + monsterMoveResult_t GetMoveResult( void ) const; + // overrides any velocity for pure delta movement + void ForceDeltaMove( bool force ); + // whether velocity should be affected by gravity + void UseFlyMove( bool force ); + // don't use delta movement + void UseVelocityMove( bool force ); + // get entity blocking the move + idEntity * GetSlideMoveEntity( void ) const; + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // monster physics state + monsterPState_t current; + monsterPState_t saved; + + // properties + float maxStepHeight; // maximum step height + float minFloorCosine; // minimum cosine of floor angle + idVec3 delta; // delta for next move + + bool forceDeltaMove; + bool fly; + bool useVelocityMove; + bool noImpact; // if true do not activate when another object collides + + // results of last evaluate + monsterMoveResult_t moveResult; + idEntity * blockingEntity; + +private: + void CheckGround( monsterPState_t &state ); + monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + monsterMoveResult_t StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + void Rest( void ); +}; + +#endif /* !__PHYSICS_MONSTER_H__ */ diff --git a/source/game/physics/Physics_Parametric.cpp b/source/game/physics/Physics_Parametric.cpp new file mode 100644 index 0000000..854cb81 --- /dev/null +++ b/source/game/physics/Physics_Parametric.cpp @@ -0,0 +1,1191 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Parametric ) +END_CLASS + + +/* +================ +idPhysics_Parametric::Activate +================ +*/ +void idPhysics_Parametric::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::TestIfAtRest +================ +*/ +bool idPhysics_Parametric::TestIfAtRest( void ) const { + + if ( ( current.linearExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + ( current.angularExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + current.linearInterpolation.GetDuration() == 0 && + current.angularInterpolation.GetDuration() == 0 && + current.spline == NULL ) { + return true; + } + + if ( !current.linearExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.linearInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( current.spline != NULL && !current.spline->IsDone( current.time ) ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_Parametric::Rest +================ +*/ +void idPhysics_Parametric::Rest( void ) { + current.atRest = gameLocal.time; + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::idPhysics_Parametric +================ +*/ +idPhysics_Parametric::idPhysics_Parametric( void ) { + + current.time = gameLocal.time; + current.atRest = -1; + current.useSplineAngles = false; + current.origin.Zero(); + current.angles.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAngles.Zero(); + current.linearExtrapolation.Init( 0, 0, vec3_zero, vec3_zero, vec3_zero, EXTRAPOLATION_NONE ); + current.angularExtrapolation.Init( 0, 0, ang_zero, ang_zero, ang_zero, EXTRAPOLATION_NONE ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + current.spline = NULL; + current.splineInterpolate.Init( 0, 1, 1, 2, 0, 0 ); + + saved = current; + + isPusher = false; + pushFlags = 0; + clipModel = NULL; + isBlocked = false; + memset( &pushResults, 0, sizeof( pushResults ) ); + + hasMaster = false; + isOrientated = false; + +// RAVEN BEGIN +// abahr: + useAxisOffset = false; + axisOffset.Identity(); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::~idPhysics_Parametric +================ +*/ +idPhysics_Parametric::~idPhysics_Parametric( void ) { + if ( clipModel != NULL ) { + delete clipModel; + clipModel = NULL; + } + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } +} + +/* +================ +idPhysics_Parametric_SavePState +================ +*/ +void idPhysics_Parametric_SavePState( idSaveGame *savefile, const parametricPState_t &state ) { + savefile->WriteInt( state.time ); + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.useSplineAngles ); + savefile->WriteVec3( state.origin ); + savefile->WriteAngles( state.angles ); + savefile->WriteMat3( state.axis ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteAngles( state.localAngles ); + + savefile->WriteInt( (int)state.linearExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.linearExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.linearExtrapolation.GetDuration() ); + savefile->WriteVec3( state.linearExtrapolation.GetStartValue() ); + savefile->WriteVec3( state.linearExtrapolation.GetBaseSpeed() ); + savefile->WriteVec3( state.linearExtrapolation.GetSpeed() ); + + savefile->WriteInt( (int)state.angularExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.angularExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.angularExtrapolation.GetDuration() ); + savefile->WriteAngles( state.angularExtrapolation.GetStartValue() ); + savefile->WriteAngles( state.angularExtrapolation.GetBaseSpeed() ); + savefile->WriteAngles( state.angularExtrapolation.GetSpeed() ); + + savefile->WriteFloat( state.linearInterpolation.GetStartTime() ); + savefile->WriteFloat( state.linearInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDuration() ); + savefile->WriteVec3( state.linearInterpolation.GetStartValue() ); + savefile->WriteVec3( state.linearInterpolation.GetEndValue() ); + + savefile->WriteFloat( state.angularInterpolation.GetStartTime() ); + savefile->WriteFloat( state.angularInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDuration() ); + savefile->WriteAngles( state.angularInterpolation.GetStartValue() ); + savefile->WriteAngles( state.angularInterpolation.GetEndValue() ); + + // spline is handled by owner + + savefile->WriteFloat( state.splineInterpolate.GetStartTime() ); + savefile->WriteFloat( state.splineInterpolate.GetAcceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetDuration() ); + savefile->WriteFloat( state.splineInterpolate.GetDeceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetStartValue() ); + savefile->WriteFloat( state.splineInterpolate.GetEndValue() ); +} + +/* +================ +idPhysics_Parametric_RestorePState +================ +*/ +void idPhysics_Parametric_RestorePState( idRestoreGame *savefile, parametricPState_t &state ) { + extrapolation_t etype; + float startTime, duration, accelTime, decelTime, startValue, endValue; + idVec3 linearStartValue, linearBaseSpeed, linearSpeed, startPos, endPos; + idAngles angularStartValue, angularBaseSpeed, angularSpeed, startAng, endAng; + + savefile->ReadInt( state.time ); + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.useSplineAngles ); + savefile->ReadVec3( state.origin ); + savefile->ReadAngles( state.angles ); + savefile->ReadMat3( state.axis ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadAngles( state.localAngles ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( linearStartValue ); + savefile->ReadVec3( linearBaseSpeed ); + savefile->ReadVec3( linearSpeed ); + + state.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, etype ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( angularStartValue ); + savefile->ReadAngles( angularBaseSpeed ); + savefile->ReadAngles( angularSpeed ); + + state.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, etype ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( startPos ); + savefile->ReadVec3( endPos ); + + state.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( startAng ); + savefile->ReadAngles( endAng ); + + state.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + // spline is handled by owner + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( startValue ); + savefile->ReadFloat( endValue ); + + state.splineInterpolate.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idPhysics_Parametric::Save +================ +*/ +void idPhysics_Parametric::Save( idSaveGame *savefile ) const { + + idPhysics_Parametric_SavePState( savefile, current ); + idPhysics_Parametric_SavePState( savefile, saved ); + + savefile->WriteBool( isPusher ); + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( pushFlags ); + + savefile->WriteTrace( pushResults ); + savefile->WriteBool( isBlocked ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); + + savefile->WriteBool ( useAxisOffset ); // cnicholson: Added unsaved var + savefile->WriteMat3 ( axisOffset ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Parametric::Restore +================ +*/ +void idPhysics_Parametric::Restore( idRestoreGame *savefile ) { + + idPhysics_Parametric_RestorePState( savefile, current ); + idPhysics_Parametric_RestorePState( savefile, saved ); + + savefile->ReadBool( isPusher ); + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( pushFlags ); + + savefile->ReadTrace( pushResults ); + savefile->ReadBool( isBlocked ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); + + savefile->ReadBool ( useAxisOffset ); // cnicholson: Added unrestored var + savefile->ReadMat3 ( axisOffset ); // cnicholson: Added unrestored var + +} + +/* +================ +idPhysics_Parametric::SetPusher +================ +*/ +void idPhysics_Parametric::SetPusher( int flags ) { + assert( clipModel ); + isPusher = true; + pushFlags = flags; +} + +/* +================ +idPhysics_Parametric::IsPusher +================ +*/ +bool idPhysics_Parametric::IsPusher( void ) const { + return isPusher; +} + +/* +================ +idPhysics_Parametric::SetLinearExtrapolation +================ +*/ +void idPhysics_Parametric::SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ) { + current.time = gameLocal.time; + current.linearExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localOrigin = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularExtrapolation +================ +*/ +void idPhysics_Parametric::SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ) { + current.time = gameLocal.time; + current.angularExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localAngles = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetLinearExtrapolationType( void ) const { + return current.linearExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::GetAngularExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetAngularExtrapolationType( void ) const { + return current.angularExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::SetLinearInterpolation +================ +*/ +void idPhysics_Parametric::SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ) { + current.time = gameLocal.time; + current.linearInterpolation.Init( time, accelTime, decelTime, duration, startPos, endPos ); + current.localOrigin = startPos; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularInterpolation +================ +*/ +void idPhysics_Parametric::SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ) { + current.time = gameLocal.time; + current.angularInterpolation.Init( time, accelTime, decelTime, duration, startAng, endAng ); + current.localAngles = startAng; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetSpline +================ +*/ +void idPhysics_Parametric::SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ) { + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } + current.spline = spline; + if ( current.spline != NULL ) { + float startTime = current.spline->GetTime( 0 ); + float endTime = current.spline->GetTime( current.spline->GetNumValues() - 1 ); + float length = current.spline->GetLengthForTime( endTime ); + current.splineInterpolate.Init( startTime, accelTime, decelTime, endTime - startTime, 0.0f, length ); + } + current.useSplineAngles = useSplineAngles; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetSpline +================ +*/ +idCurve_Spline *idPhysics_Parametric::GetSpline( void ) const { + return current.spline; +} + +/* +================ +idPhysics_Parametric::GetSplineAcceleration +================ +*/ +int idPhysics_Parametric::GetSplineAcceleration( void ) const { + return current.splineInterpolate.GetAcceleration(); +} + +/* +================ +idPhysics_Parametric::GetSplineDeceleration +================ +*/ +int idPhysics_Parametric::GetSplineDeceleration( void ) const { + return current.splineInterpolate.GetDeceleration(); +} + +/* +================ +idPhysics_Parametric::UsingSplineAngles +================ +*/ +bool idPhysics_Parametric::UsingSplineAngles( void ) const { + return current.useSplineAngles; +} + +/* +================ +idPhysics_Parametric::GetLocalOrigin +================ +*/ +void idPhysics_Parametric::GetLocalOrigin( idVec3 &curOrigin ) const { + curOrigin = current.localOrigin; +} + +/* +================ +idPhysics_Parametric::GetLocalAngles +================ +*/ +void idPhysics_Parametric::GetLocalAngles( idAngles &curAngles ) const { + curAngles = current.localAngles; +} + +/* +================ +idPhysics_Parametric::SetClipModel +================ +*/ +void idPhysics_Parametric::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::GetClipModel +================ +*/ +idClipModel *idPhysics_Parametric::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Parametric::GetNumClipModels +================ +*/ +int idPhysics_Parametric::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Parametric::SetMass +================ +*/ +void idPhysics_Parametric::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Parametric::GetMass +================ +*/ +float idPhysics_Parametric::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +void idPhysics_Parametric::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +int idPhysics_Parametric::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Parametric::GetBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return idPhysics_Base::GetBounds(); +} + +/* +================ +idPhysics_Parametric::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetAbsBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + return idPhysics_Base::GetAbsBounds(); +} + +/* +================ +idPhysics_Parametric::Evaluate +================ +*/ +bool idPhysics_Parametric::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 oldLocalOrigin, oldOrigin, masterOrigin; + idAngles oldLocalAngles, oldAngles; + idMat3 oldAxis, masterAxis; + + isBlocked = false; + oldLocalOrigin = current.localOrigin; + oldOrigin = current.origin; + oldLocalAngles = current.localAngles; + oldAngles = current.angles; + oldAxis = current.axis; + + current.localOrigin.Zero(); + current.localAngles.Zero(); + + if ( current.spline != NULL ) { + float length = current.splineInterpolate.GetCurrentValue( endTimeMSec ); + float t = current.spline->GetTimeForLength( length, 0.01f ); + current.localOrigin = current.spline->GetCurrentValue( t ); + if ( current.useSplineAngles ) { + current.localAngles = current.spline->GetCurrentFirstDerivative( t ).ToAngles(); + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + current.localOrigin += current.linearInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localOrigin += current.linearExtrapolation.GetCurrentValue( endTimeMSec ); + } + + if ( current.angularInterpolation.GetDuration() != 0 ) { + current.localAngles += current.angularInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localAngles += current.angularExtrapolation.GetCurrentValue( endTimeMSec ); + } + + current.localAngles.Normalize360(); + current.origin = current.localOrigin; + current.angles = current.localAngles; + current.axis = current.localAngles.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( masterAxis.IsRotated() ) { + current.origin = current.origin * masterAxis + masterOrigin; + if ( isOrientated ) { + current.axis *= masterAxis; + current.angles = current.axis.ToAngles(); + } + } + else { + current.origin += masterOrigin; + } + } + + if ( isPusher ) { + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + if ( pushResults.fraction < 1.0f ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, oldOrigin, oldAxis ); +// RAVEN END + current.localOrigin = oldLocalOrigin; + current.origin = oldOrigin; + current.localAngles = oldLocalAngles; + current.angles = oldAngles; + current.axis = oldAxis; + isBlocked = true; + return false; + } + + current.angles = current.axis.ToAngles(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// abahr: a hack way of hiding gimble lock from movers. + clipModel->Link( self, 0, current.origin, UseAxisOffset() ? GetAxisOffset() * current.axis : current.axis ); +// RAVEN END + } + + current.time = endTimeMSec; + + if ( TestIfAtRest() ) { + Rest(); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); +} + +/* +================ +idPhysics_Parametric::UpdateTime +================ +*/ +void idPhysics_Parametric::UpdateTime( int endTimeMSec ) { + int timeLeap = endTimeMSec - current.time; + + current.time = endTimeMSec; + // move the trajectory start times to sync the trajectory with the current endTime + current.linearExtrapolation.SetStartTime( current.linearExtrapolation.GetStartTime() + timeLeap ); + current.angularExtrapolation.SetStartTime( current.angularExtrapolation.GetStartTime() + timeLeap ); + current.linearInterpolation.SetStartTime( current.linearInterpolation.GetStartTime() + timeLeap ); + current.angularInterpolation.SetStartTime( current.angularInterpolation.GetStartTime() + timeLeap ); + if ( current.spline != NULL ) { + current.spline->ShiftTime( timeLeap ); + current.splineInterpolate.SetStartTime( current.splineInterpolate.GetStartTime() + timeLeap ); + } +} + +/* +================ +idPhysics_Parametric::GetTime +================ +*/ +int idPhysics_Parametric::GetTime( void ) const { + return current.time; +} + +/* +================ +idPhysics_Parametric::IsAtRest +================ +*/ +bool idPhysics_Parametric::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Parametric::GetRestStartTime +================ +*/ +int idPhysics_Parametric::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Parametric::IsPushable +================ +*/ +bool idPhysics_Parametric::IsPushable( void ) const { + return false; +} + +/* +================ +idPhysics_Parametric::SaveState +================ +*/ +void idPhysics_Parametric::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Parametric::RestoreState +================ +*/ +void idPhysics_Parametric::RestoreState( void ) { + + current = saved; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::SetOrigin +================ +*/ +void idPhysics_Parametric::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.linearExtrapolation.SetStartValue( newOrigin ); + current.linearInterpolation.SetStartValue( newOrigin ); + + current.localOrigin = current.linearExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAxis +================ +*/ +void idPhysics_Parametric::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAngles = newAxis.ToAngles(); + + current.angularExtrapolation.SetStartValue( current.localAngles ); + current.angularInterpolation.SetStartValue( current.localAngles ); + + current.localAngles = current.angularExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAngles.ToMat3() * masterAxis; + current.angles = current.axis.ToAngles(); + } + else { + current.axis = current.localAngles.ToMat3(); + current.angles = current.localAngles; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::Move +================ +*/ +void idPhysics_Parametric::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Parametric::Rotate +================ +*/ +void idPhysics_Parametric::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Parametric::GetOrigin +================ +*/ +const idVec3 &idPhysics_Parametric::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Parametric::GetAxis +================ +*/ +const idMat3 &idPhysics_Parametric::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Parametric::GetAngles +================ +*/ +void idPhysics_Parametric::GetAngles( idAngles &curAngles ) const { + curAngles = current.angles; +} + +/* +================ +idPhysics_Parametric::SetLinearVelocity +================ +*/ +void idPhysics_Parametric::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.origin, newLinearVelocity, vec3_origin ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularVelocity +================ +*/ +void idPhysics_Parametric::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + idRotation rotation; + idVec3 vec; + float angle; + + vec = newAngularVelocity; + angle = vec.Normalize(); + rotation.Set( vec3_origin, vec, (float) RAD2DEG( angle ) ); + + SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.angles, rotation.ToAngles(), ang_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + + curLinearVelocity = current.linearExtrapolation.GetCurrentSpeed( gameLocal.time ); + return curLinearVelocity; +} + +/* +================ +idPhysics_Parametric::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idAngles angles; + + angles = current.angularExtrapolation.GetCurrentSpeed( gameLocal.time ); + curAngularVelocity = angles.ToAngularVelocity(); + return curAngularVelocity; +} + +/* +================ +idPhysics_Parametric::DisableClip +================ +*/ +void idPhysics_Parametric::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Parametric::EnableClip +================ +*/ +void idPhysics_Parametric::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Parametric::UnlinkClip +================ +*/ +void idPhysics_Parametric::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Parametric::LinkClip +================ +*/ +void idPhysics_Parametric::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Parametric::GetBlockingInfo( void ) const { + return ( isBlocked ? &pushResults : NULL ); +} + +/* +================ +idPhysics_Parametric::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Parametric::GetBlockingEntity( void ) const { + if ( isBlocked ) { + return gameLocal.entities[ pushResults.c.entityNum ]; + } + return NULL; +} + +/* +================ +idPhysics_Parametric::SetMaster +================ +*/ +void idPhysics_Parametric::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAngles = ( current.axis * masterAxis.Transpose() ).ToAngles(); + } + else { + current.localAngles = current.axis.ToAngles(); + } + + current.linearExtrapolation.SetStartValue( current.localOrigin ); + current.angularExtrapolation.SetStartValue( current.localAngles ); + hasMaster = true; + isOrientated = orientated; + } + } + else { + if ( hasMaster ) { + // transform from master space to world space + current.localOrigin = current.origin; + current.localAngles = current.angles; + SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.origin, vec3_origin, vec3_origin ); + SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.angles, ang_zero, ang_zero ); + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Parametric::GetLinearEndTime +================ +*/ +int idPhysics_Parametric::GetLinearEndTime( void ) const { + if ( current.spline != NULL ) { + if ( current.spline->GetBoundaryType() != idCurve_Spline::BT_CLOSED ) { + return current.spline->GetTime( current.spline->GetNumValues() - 1 ); + } else { + return 0; + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + return current.linearInterpolation.GetEndTime(); + } else { + return current.linearExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::GetAngularEndTime +================ +*/ +int idPhysics_Parametric::GetAngularEndTime( void ) const { + if ( current.angularInterpolation.GetDuration() != 0 ) { + return current.angularInterpolation.GetEndTime(); + } else { + return current.angularExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::WriteToSnapshot +================ +*/ +void idPhysics_Parametric::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.time ); + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.angles[0] ); + msg.WriteFloat( current.angles[1] ); + msg.WriteFloat( current.angles[2] ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( current.angles[0], current.localAngles[0] ); + msg.WriteDeltaFloat( current.angles[1], current.localAngles[1] ); + msg.WriteDeltaFloat( current.angles[2], current.localAngles[2] ); + + msg.WriteBits( current.linearExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteBits( current.angularExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[2] ); +} + +/* +================ +idPhysics_Parametric::ReadFromSnapshot +================ +*/ +void idPhysics_Parametric::ReadFromSnapshot( const idBitMsgDelta &msg ) { + extrapolation_t linearType, angularType; + float startTime, duration, accelTime, decelTime; + idVec3 linearStartValue, linearSpeed, linearBaseSpeed, startPos, endPos; + idAngles angularStartValue, angularSpeed, angularBaseSpeed, startAng, endAng; + + current.time = msg.ReadLong(); + current.atRest = msg.ReadLong(); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.angles[0] = msg.ReadFloat(); + current.angles[1] = msg.ReadFloat(); + current.angles[2] = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAngles[0] = msg.ReadDeltaFloat( current.angles[0] ); + current.localAngles[1] = msg.ReadDeltaFloat( current.angles[1] ); + current.localAngles[2] = msg.ReadDeltaFloat( current.angles[2] ); + + linearType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, linearType ); + + angularType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, angularType ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startPos[0] = msg.ReadDeltaFloat( 0.0f ); + startPos[1] = msg.ReadDeltaFloat( 0.0f ); + startPos[2] = msg.ReadDeltaFloat( 0.0f ); + endPos[0] = msg.ReadDeltaFloat( 0.0f ); + endPos[1] = msg.ReadDeltaFloat( 0.0f ); + endPos[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startAng[0] = msg.ReadDeltaFloat( 0.0f ); + startAng[1] = msg.ReadDeltaFloat( 0.0f ); + startAng[2] = msg.ReadDeltaFloat( 0.0f ); + endAng[0] = msg.ReadDeltaFloat( 0.0f ); + endAng[1] = msg.ReadDeltaFloat( 0.0f ); + endAng[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + current.axis = current.angles.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} diff --git a/source/game/physics/Physics_Parametric.h b/source/game/physics/Physics_Parametric.h new file mode 100644 index 0000000..15f286f --- /dev/null +++ b/source/game/physics/Physics_Parametric.h @@ -0,0 +1,163 @@ + +#ifndef __PHYSICS_PARAMETRIC_H__ +#define __PHYSICS_PARAMETRIC_H__ + +/* +=================================================================================== + + Parametric physics + + Used for predefined or scripted motion. The motion of an object is completely + parametrized. By adjusting the parameters an object is forced to follow a + predefined path. The parametric physics is typically used for doors, bridges, + rotating fans etc. + +=================================================================================== +*/ + +typedef struct parametricPState_s { + int time; // physics time + int atRest; // set when simulation is suspended + idVec3 origin; // world origin + idAngles angles; // world angles + idMat3 axis; // world axis + idVec3 localOrigin; // local origin + idAngles localAngles; // local angles + idExtrapolate linearExtrapolation; // extrapolation based description of the position over time + idExtrapolate angularExtrapolation; // extrapolation based description of the orientation over time + idInterpolateAccelDecelLinear linearInterpolation; // interpolation based description of the position over time + idInterpolateAccelDecelLinear angularInterpolation; // interpolation based description of the orientation over time + idCurve_Spline * spline; // spline based description of the position over time + idInterpolateAccelDecelLinear splineInterpolate; // position along the spline over time + bool useSplineAngles; // set the orientation using the spline +} parametricPState_t; + +class idPhysics_Parametric : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Parametric ); + + idPhysics_Parametric( void ); + ~idPhysics_Parametric( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPusher( int flags ); + bool IsPusher( void ) const; + + void SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ); + void SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ); + extrapolation_t GetLinearExtrapolationType( void ) const; + extrapolation_t GetAngularExtrapolationType( void ) const; + + void SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ); + void SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ); + + void SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ); + idCurve_Spline *GetSpline( void ) const; + int GetSplineAcceleration( void ) const; + int GetSplineDeceleration( void ) const; + bool UsingSplineAngles( void ) const; + + void GetLocalOrigin( idVec3 &curOrigin ) const; + void GetLocalAngles( idAngles &curAngles ) const; + + void GetAngles( idAngles &curAngles ) const; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + void SetAxisOffset( const idMat3& offset ) { axisOffset = offset; useAxisOffset = true; } + const idMat3& GetAxisOffset() const { return axisOffset; } + idMat3& GetAxisOffset() { return axisOffset; } + bool UseAxisOffset() const { return useAxisOffset; } +// RAVEN END + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // parametric physics state + parametricPState_t current; + parametricPState_t saved; + + // pusher + bool isPusher; + idClipModel * clipModel; + int pushFlags; + + // results of last evaluate + trace_t pushResults; + bool isBlocked; + + // master + bool hasMaster; + bool isOrientated; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + bool useAxisOffset; + idMat3 axisOffset; +// RAVEN END + +private: + bool TestIfAtRest( void ) const; + void Rest( void ); +}; + +#endif /* !__PHYSICS_PARAMETRIC_H__ */ diff --git a/source/game/physics/Physics_Particle.cpp b/source/game/physics/Physics_Particle.cpp new file mode 100644 index 0000000..f8073d2 --- /dev/null +++ b/source/game/physics/Physics_Particle.cpp @@ -0,0 +1,1022 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Particle ) +END_CLASS + +const float PRT_OVERCLIP = 1.001f; +const float PRT_BOUNCESTOP = 10.0f; + +/* +================ +rvPhysics_Particle::DropToFloorAndRest + +Drops the object straight down to the floor +================ +*/ +void rvPhysics_Particle::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + testSolid = false; + if ( gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ) ) { + gameLocal.Warning( "entity in solid '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.origin + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + current.origin = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, clipModel->GetAxis() ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + PutToRest(); + EvaluateContacts();//Do a final contact check. Items that drop to floor never do this check otherwise + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "entity outside world bounds '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + } +} + +/* +================ +rvPhysics_Particle::DebugDraw +================ +*/ +void rvPhysics_Particle::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +} + +/* +================ +rvPhysics_Particle::rvPhysics_Particle +================ +*/ +rvPhysics_Particle::rvPhysics_Particle( void ) { + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f, true ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + current.origin.Zero(); + saved = current; + + dropToFloor = false; + testSolid = false; + hasMaster = false; + + SetFriction( 0.6f, 0.6f, 0.0f ); + SetBouncyness ( 0.5f, true ); + + gravityNormal.Zero(); +} + +/* +================ +rvPhysics_Particle::~rvPhysics_Particle +================ +*/ +rvPhysics_Particle::~rvPhysics_Particle( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +rvPhysics_Particle::Save +================ +*/ +void rvPhysics_Particle::Save( idSaveGame *savefile ) const { + savefile->WriteInt( current.atRest ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteVec3( current.pushVelocity ); + savefile->WriteVec3( current.origin ); + savefile->WriteVec3( current.velocity ); + savefile->WriteBool( current.onGround ); + savefile->WriteBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->WriteInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->WriteVec3( saved.localOrigin ); + savefile->WriteMat3( saved.localAxis ); + savefile->WriteVec3( saved.pushVelocity ); + savefile->WriteVec3( saved.origin ); + savefile->WriteVec3( saved.velocity ); + savefile->WriteBool( saved.onGround ); + savefile->WriteBool( saved.inWater ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteBool ( allowBounce ); + savefile->WriteBounds ( clipModel->GetBounds ( ) ); // cnicholson: Added unsaved var + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); // cnicholson: Added unsaved var + extraPassEntity.Save( savefile ); + + savefile->WriteClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::Restore +================ +*/ +void rvPhysics_Particle::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( current.atRest ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadVec3( current.pushVelocity ); + savefile->ReadVec3( current.origin ); + savefile->ReadVec3( current.velocity ); + savefile->ReadBool( current.onGround ); + savefile->ReadBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->ReadInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->ReadVec3( saved.localOrigin ); + savefile->ReadMat3( saved.localAxis ); + savefile->ReadVec3( saved.pushVelocity ); + savefile->ReadVec3( saved.origin ); + savefile->ReadVec3( saved.velocity ); + savefile->ReadBool( saved.onGround ); + savefile->ReadBool( saved.inWater ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadBool ( allowBounce ); + + idBounds bounds; // cnicholson: Added unrestored var + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); // cnicholson: Added unrestored var + extraPassEntity.Restore( savefile ); + + savefile->ReadClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::SetClipModel +================ +*/ +void rvPhysics_Particle::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::GetClipModel +================ +*/ +idClipModel *rvPhysics_Particle::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Particle::GetNumClipModels +================ +*/ +int rvPhysics_Particle::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +rvPhysics_Particle::SetBouncyness +================ +*/ +void rvPhysics_Particle::SetBouncyness( const float b, bool _allowBounce ) { + allowBounce = _allowBounce; + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +rvPhysics_Particle::SetFriction +================ +*/ +void rvPhysics_Particle::SetFriction( const float linear, const float angular, const float contact ) { + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + + +/* +================ +rvPhysics_Particle::PutToRest +================ +*/ +void rvPhysics_Particle::PutToRest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::DropToFloor +================ +*/ +void rvPhysics_Particle::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +rvPhysics_Particle::Activate +================ +*/ +void rvPhysics_Particle::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::EvaluateContacts +================ +*/ +bool rvPhysics_Particle::EvaluateContacts( void ) { + ClearContacts(); + AddGroundContacts( clipModel ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +rvPhysics_Particle::SetContents +================ +*/ +void rvPhysics_Particle::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Particle::GetContents +================ +*/ +int rvPhysics_Particle::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Particle::GetBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Particle::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Particle::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_Particle::Evaluate( int timeStepMSec, int endTimeMSec ) { + particlePState_t next; + float timeStep; + float upspeed; + + timeStep = MS2SEC( timeStepMSec ); + + // if bound to a master + if ( hasMaster ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 oldOrigin; + + oldOrigin = current.origin; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, current.localAxis * masterAxis ); +// RAVEN END + + trace_t tr; + gameLocal.Translation( self, tr, oldOrigin, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.fraction < 1.0f ) { + self->Collide ( tr, current.origin - oldOrigin ); + } + + DebugDraw(); + + return true; + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + return true; + } + + clipModel->Unlink(); + + // Determine if currently on the ground + CheckGround ( ); + + // Determine the current upward velocity + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + + // If not on the ground, or moving upwards, or bouncing and moving toward gravity then do a straight + // forward slide move and gravity. + if ( !current.onGround || upspeed > 1.0f || (bouncyness > 0.0f && upspeed < -PRT_BOUNCESTOP && !current.inWater) ) { + // Force ground off when moving upward + if ( upspeed > 0.0f ) { + current.onGround = false; + } + SlideMove( current.origin, current.velocity, current.velocity * timeStep ); + if ( current.onGround && upspeed < PRT_BOUNCESTOP ) { + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } else { + current.velocity += (gravityVector * timeStep); + } + } else { + idVec3 delta; + + // Slow down due to friction + ApplyFriction ( timeStep ); + + delta = current.velocity * timeStep; + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + if ( delta == vec3_origin ) { + PutToRest( ); + } else { + SlideMove( current.origin, current.velocity, delta ); + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + DebugDraw(); + + // get all the ground contacts + EvaluateContacts(); + + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + PutToRest(); + } + + return true; +} + +/* +================ +rvPhysics_Particle::UpdateTime +================ +*/ +void rvPhysics_Particle::UpdateTime( int endTimeMSec ) { +} + +/* +================ +rvPhysics_Particle::GetTime +================ +*/ +int rvPhysics_Particle::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +rvPhysics_Particle::IsAtRest +================ +*/ +bool rvPhysics_Particle::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +rvPhysics_Particle::GetRestStartTime +================ +*/ +int rvPhysics_Particle::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +rvPhysics_Particle::IsPushable +================ +*/ +bool rvPhysics_Particle::IsPushable( void ) const { + return ( !hasMaster ); +} + +/* +================ +rvPhysics_Particle::SaveState +================ +*/ +void rvPhysics_Particle::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Particle::RestoreState +================ +*/ +void rvPhysics_Particle::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Particle::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Particle::SetAxis( const idMat3 &newAxis, int id ) { + current.localAxis = newAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::Translate +================ +*/ +void rvPhysics_Particle::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +rvPhysics_Particle::Rotate( +================ +*/ +void rvPhysics_Particle::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Particle::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +rvPhysics_Particle::GetAxis +================ +*/ +const idMat3 &rvPhysics_Particle::GetAxis( int id ) const { + if ( !clipModel ) { + return idPhysics_Base::GetAxis ( id ); + } + return clipModel->GetAxis(); +} + +/* +================ +rvPhysics_Particle::SetLinearVelocity +================ +*/ +void rvPhysics_Particle::SetLinearVelocity( const idVec3 &velocity, int id ) { + current.velocity = velocity; + Activate(); +} + +/* +================ +rvPhysics_Particle::GetLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +rvPhysics_Particle::ClipTranslation +================ +*/ +void rvPhysics_Particle::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipRotation +================ +*/ +void rvPhysics_Particle::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipContents +================ +*/ +int rvPhysics_Particle::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +rvPhysics_Particle::DisableClip +================ +*/ +void rvPhysics_Particle::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +rvPhysics_Particle::EnableClip +================ +*/ +void rvPhysics_Particle::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +rvPhysics_Particle::UnlinkClip +================ +*/ +void rvPhysics_Particle::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +rvPhysics_Particle::LinkClip +================ +*/ +void rvPhysics_Particle::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::SetPushed +================ +*/ +void rvPhysics_Particle::SetPushed( int deltaTime ) { + // velocity with which the particle is pushed + current.pushVelocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +rvPhysics_Particle::GetPushedLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +rvPhysics_Particle::SetMaster +================ +*/ +void rvPhysics_Particle::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + hasMaster = true; + } + ClearContacts(); + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +/* +===================== +rvPhysics_Particle::CheckGround +===================== +*/ +void rvPhysics_Particle::CheckGround( void ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + current.onGround = false; + return; + } + + down = current.origin + gravityNormal * CONTACT_EPSILON; + gameLocal.Translation( self, groundTrace, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( groundTrace.fraction == 1.0f ) { + current.onGround = false; + return; + } + + if ( ( groundTrace.c.normal * -gravityNormal ) < 0.7f ) { + current.onGround = false; + return; + } + + current.onGround = true; +} + +/* +================ +rvPhysics_Particle::ApplyFriction +================ +*/ +void rvPhysics_Particle::ApplyFriction( float timeStep ) { + idVec3 vel; + float speed; + float newspeed; + + // ignore slope movement, remove all velocity in gravity direction + vel = current.velocity + (current.velocity * gravityNormal) * gravityNormal; + + speed = vel.Length(); + if ( speed < 1.0f ) { + // remove all movement orthogonal to gravity, allows for sinking underwater + current.velocity = (current.velocity * gravityNormal) * gravityNormal; + return; + } + + // scale the velocity + if ( current.onGround ) { + newspeed = speed - ((speed * contactFriction) * timeStep); + } else { + newspeed = speed - ((speed * linearFriction) * timeStep); + } + + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= ( newspeed / speed ); +} + +/* +================ +rvPhysics_Particle::SlideMove +================ +*/ +bool rvPhysics_Particle::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + bool collide, rtnValue = false; + + move = delta; + for( i = 0; i < 3; i++ ) { // be sure if you change this upper value in the for() to update the exit condition below!!!!! + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self, extraPassEntity ); + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return false; + } + return true; + } + + bool hitTeleporter = false; + + // let the entity know about the collision + collide = self->Collide( tr, current.velocity, hitTeleporter ); + + idEntity* ent; + ent = gameLocal.entities[tr.c.entityNum]; + assert ( ent ); + + // If we hit water just clip the move for now and keep on going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + // Make sure we dont collide with water again + clipMask &= ~CONTENTS_WATER; + + current.inWater = true; + + // Allow the loop to go one more round to push us through the water + i--; + + velocity *= 0.4f; + + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + continue; + // bounce the projectile + } else if ( !current.inWater && allowBounce && bouncyness ) { + if ( !hitTeleporter ) { + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ) * bouncyness; + } + return true; +//RAVEN BEGIN +//jshepard: tr.c.material can (did) crash here if null + } else if ( allowBounce && tr.c.material && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) ) { +//RAVEN END + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ); + return true; + } +// RAVEN BEGIN +// dluetscher: removed redundant trace calls + else { + i = 4; + rtnValue = true; + } +// RAVEN END + + // clip the movement delta and velocity + if( collide ) { + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + } + } + + return rtnValue; +} + +const float PRT_VELOCITY_MAX = 16000; +const int PRT_VELOCITY_TOTAL_BITS = 16; +const int PRT_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PRT_VELOCITY_MAX ) ) + 1; +const int PRT_VELOCITY_MANTISSA_BITS = PRT_VELOCITY_TOTAL_BITS - 1 - PRT_VELOCITY_EXPONENT_BITS; + +/* +================ +rvPhysics_Particle::WriteToSnapshot +================ +*/ +void rvPhysics_Particle::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.atRest ); + msg.WriteBits ( current.onGround, 1 ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + // TODO: Check that this conditional write to delta message is OK + if ( hasMaster ) { + idCQuat localQuat; + localQuat = current.localAxis.ToCQuat(); + + msg.WriteBits ( 1, 1 ); + msg.WriteFloat( localQuat.x ); + msg.WriteFloat( localQuat.y ); + msg.WriteFloat( localQuat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + } else { + msg.WriteBits ( 0, 1 ); + } +} + +/* +================ +rvPhysics_Particle::ReadFromSnapshot +================ +*/ +void rvPhysics_Particle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.atRest = msg.ReadLong(); + current.onGround = ( msg.ReadBits( 1 ) != 0 ); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); +// current.velocity[0] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + if ( msg.ReadBits ( 1 ) ) { + idCQuat localQuat; + localQuat.x = msg.ReadFloat( ); + localQuat.y = msg.ReadFloat( ); + localQuat.z = msg.ReadFloat( ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAxis = localQuat.ToMat3(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + } +} diff --git a/source/game/physics/Physics_Particle.h b/source/game/physics/Physics_Particle.h new file mode 100644 index 0000000..a440db3 --- /dev/null +++ b/source/game/physics/Physics_Particle.h @@ -0,0 +1,152 @@ +#ifndef __PHYSICS_PARTICLE_H__ +#define __PHYSICS_PARTICLE_H__ + +/* +=================================================================================== + + Particle Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct particlePState_s { + int atRest; // set when simulation is suspended + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec3 pushVelocity; // push velocity + idVec3 origin; + idVec3 velocity; + bool onGround; + bool inWater; +} particlePState_t; + +class rvPhysics_Particle : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Particle ); + + rvPhysics_Particle( void ); + ~rvPhysics_Particle( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b, bool allowBounce ); + + // put to rest untill something collides with this physics object + void PutToRest( void ); + // same as above but drop to the floor first + void DropToFloor( void ); + + // returns true if touching the ground + bool IsOnGround ( void ) const; + bool IsInWater ( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + bool CanBounce ( void ) const; + + bool EvaluateContacts( void ); + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idEntityPtr extraPassEntity; + +private: + // state of the particle + particlePState_t current; + particlePState_t saved; + + // particle properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + bool allowBounce; // Allowed to bounce at all? + idClipModel * clipModel; // clip model used for collision detection + + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for inside solid during a drop + + // master + bool hasMaster; + bool isOrientated; + +private: + + void DropToFloorAndRest ( void ); + bool SlideMove ( idVec3& start, idVec3& velocity, const idVec3& delta ); + void CheckGround ( void ); + void ApplyFriction ( float timeStep ); + void DebugDraw ( void ); +}; + +ID_INLINE bool rvPhysics_Particle::IsOnGround ( void ) const { + return current.onGround; +} + +ID_INLINE bool rvPhysics_Particle::IsInWater ( void ) const { + return current.inWater; +} + +ID_INLINE bool rvPhysics_Particle::CanBounce ( void ) const { + return allowBounce; +} + +#endif /* !__PHYSICS_PARTICLE_H__ */ diff --git a/source/game/physics/Physics_Player.cpp b/source/game/physics/Physics_Player.cpp new file mode 100644 index 0000000..665b1d6 --- /dev/null +++ b/source/game/physics/Physics_Player.cpp @@ -0,0 +1,2309 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player ) +END_CLASS + +// movement parameters +const float PM_STOPSPEED = 100.0f; +const float PM_SWIMSCALE = 0.5f; +const float PM_LADDERSPEED = 100.0f; +const float PM_STEPSCALE = 1.0f; + +const float PM_ACCELERATE_SP = 10.0f; +const float PM_AIRACCELERATE_SP = 1.0f; +const float PM_ACCELERATE_MP = 15.0f; +const float PM_AIRACCELERATE_MP = 1.18f; +const float PM_WATERACCELERATE = 4.0f; +const float PM_FLYACCELERATE = 8.0f; + +const float PM_FRICTION = 6.0f; +const float PM_AIRFRICTION = 0.0f; +const float PM_WATERFRICTION = 2.0f; +const float PM_FLYFRICTION = 3.0f; +const float PM_NOCLIPFRICTION = 12.0f; +// RAVEN BEGIN +// bdube: sliding +const float PM_SLIDEFRICTION = 0.5f; +// RAVEN END + +const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes +const float OVERCLIP = 1.001f; + +// movementFlags +const int PMF_DUCKED = 1; // set when ducking +const int PMF_JUMPED = 2; // set when the player jumped this frame +const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame +const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame +const int PMF_JUMP_HELD = 16; // set when jump button is held down +const int PMF_TIME_LAND = 32; // movementTime is time before rejump +const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time +const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump +const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK); + +int c_pmove = 0; + +float idPhysics_Player::Pm_Accelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_ACCELERATE_MP : PM_ACCELERATE_SP; +} + +float idPhysics_Player::Pm_AirAccelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_AIRACCELERATE_MP : PM_AIRACCELERATE_SP; +} + +/* +============ +idPhysics_Player::CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const { + int max; + float total; + float scale; + int forwardmove; + int rightmove; + int upmove; + + forwardmove = cmd.forwardmove; + rightmove = cmd.rightmove; + + // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground + // otherwise crouch speed will be lower than specified + if ( walking ) { + upmove = 0; + } else { + upmove = cmd.upmove; + } + + max = abs( forwardmove ); + if ( abs( rightmove ) > max ) { + max = abs( rightmove ); + } + if ( abs( upmove ) > max ) { + max = abs( upmove ); + } + + if ( !max ) { + return 0.0f; + } + + total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); + scale = (float) playerSpeed * max / ( 127.0f * total ); + + return scale; +} + +/* +============== +idPhysics_Player::Accelerate + +Handles user intended acceleration +============== +*/ +void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) { +#if 1 + // q2 style + float addspeed, accelspeed, currentspeed; + + currentspeed = current.velocity * wishdir; + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } +// RAVEN BEGIN +// nmckenzie: added ability to try alternate accelerations. + if ( pm_acceloverride.GetFloat() > 0.0f ) { + accelspeed = pm_acceloverride.GetFloat() * frametime * wishspeed; + } else { + accelspeed = accel * frametime * wishspeed; + } +// RAVEN END + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + current.velocity += accelspeed * wishdir; + +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + idVec3 wishVelocity; + idVec3 pushDir; + float pushLen; + float canPush; + + wishVelocity = wishdir * wishspeed; + pushDir = wishVelocity - current.velocity; + pushLen = pushDir.Normalize(); + + canPush = accel * frametime * wishspeed; + if ( canPush > pushLen ) { + canPush = pushLen; + } + + current.velocity += canPush * pushDir; +#endif +} + +/* +================== +idPhysics_Player::SlideMove + +Returns true if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) { + int i, j, k, pushFlags; + int bumpcount, numbumps, numplanes; + float d, time_left, into, totalMass; + idVec3 dir, planes[MAX_CLIP_PLANES]; + idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity; + trace_t trace, stepTrace, downTrace; + bool nearGround, stepped, pushed; + + numbumps = 4; + + primal_velocity = current.velocity; + + if ( gravity ) { + endVelocity = current.velocity + gravityVector * frametime; + current.velocity = ( current.velocity + endVelocity ) * 0.5f; + primal_velocity = endVelocity; + if ( groundPlane ) { + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + } + else { + endVelocity = current.velocity; + } + + time_left = frametime; + + // never turn against the ground plane + if ( groundPlane ) { + numplanes = 1; + planes[0] = groundTrace.c.normal; + } else { + numplanes = 0; + } + + // never turn against original velocity + planes[numplanes] = current.velocity; + planes[numplanes].Normalize(); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { + + // calculate position we are trying to move to + end = current.origin + time_left * current.velocity; + + // see if we can make it there +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + time_left -= time_left * trace.fraction; + current.origin = trace.endpos; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + + stepped = pushed = false; + + // if we are allowed to step up + if ( stepUp && ( trace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + nearGround = groundPlane | ladder; + + if ( !nearGround ) { + // trace down to see if the player is near the ground + // step checking when near the ground allows the player to move up stairs smoothly while jumping + stepEnd = current.origin + maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ); + } + + // may only step up if near the ground or on a ladder + if ( nearGround ) { + + // step up + stepEnd = current.origin - maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // trace along velocity + stepEnd = downTrace.endpos + time_left * current.velocity; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // step down + stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) { + // if moved the entire distance + if ( stepTrace.fraction >= 1.0f ) { + time_left = 0; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + break; + } + + // if the move is further when stepping up + if ( stepTrace.fraction > trace.fraction ) { + time_left -= time_left * stepTrace.fraction; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + trace = stepTrace; + stepped = true; + } + } + } + } + + // if we can push other entities and not blocked by the world + if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) { + + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + // clip movement, only push idMoveables, don't push entities the player is standing on + // apply impact to pushed objects + pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f ); + pushed = true; + } + + current.origin = trace.endpos; + time_left -= time_left * trace.fraction; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + } + + if ( !stepped && self ) { + // let the entity know about the collision + self->Collide( trace, current.velocity ); + } + + if ( numplanes >= MAX_CLIP_PLANES ) { + // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris + // where it may hit the max clip planes + current.velocity = vec3_origin; + return true; + } + + // + // if this is the same plane we hit before, nudge velocity out along it, + // which fixes some epsilon issues with non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( ( trace.c.normal * planes[i] ) > 0.999f ) { + // clip into the trace normal just in case this normal is almost but not exactly the same as the groundTrace normal + current.velocity.ProjectOntoPlane( trace.c.normal, OVERCLIP ); + // also add the normal to nudge the velocity out + current.velocity += trace.c.normal; + break; + } + } + if ( i < numplanes ) { + continue; + } + planes[numplanes] = trace.c.normal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = current.velocity * planes[i]; + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // slide along the plane + clipVelocity = current.velocity; + clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // slide along the plane + endClipVelocity = endVelocity; + endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( ( clipVelocity * planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + + // see if it goes back into the first clip plane + if ( ( clipVelocity * planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * current.velocity; + clipVelocity = d * dir; + + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * endVelocity; + endClipVelocity = d * dir; + + // see if there is a third plane the the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( ( clipVelocity * planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + current.velocity = vec3_origin; + return true; + } + } + + // if we have fixed all interactions, try another move + current.velocity = clipVelocity; + endVelocity = endClipVelocity; + break; + } + } + + // step down + if ( stepDown && groundPlane ) { + stepEnd = current.origin + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) { + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_DOWN; + current.velocity *= PM_STEPSCALE; + } + } + + if ( gravity ) { + current.velocity = endVelocity; + } + + // come to a dead stop when the velocity orthogonal to the gravity flipped + clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal; + endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal; + if ( clipVelocity * endClipVelocity < 0.0f ) { + current.velocity = gravityNormal * current.velocity * gravityNormal; + } + + return ( bumpcount == 0 ); +} + +/* +================== +idPhysics_Player::Friction + +Handles both ground friction and water friction +================== +*/ +void idPhysics_Player::Friction( void ) { + idVec3 vel; + float speed, newspeed, control; + float drop; + + vel = current.velocity; + if ( walking ) { + // ignore slope movement, remove all velocity in gravity direction + vel += (vel * gravityNormal) * gravityNormal; + } + + speed = vel.Length(); + if ( speed < 1.0f ) { + current.velocity.Zero(); + return; + } + + drop = 0; + + // spectator friction +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat() > -1 ) { + drop += speed * pm_frictionoverride.GetFloat() * frametime; + } else if ( current.movementType == PM_SPECTATOR ) { +// RAVEN END + drop += speed * PM_FLYFRICTION * frametime; + } + // apply ground friction + else if ( walking && waterLevel <= WATERLEVEL_FEET ) { + // no friction on slick surfaces + if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) { + control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed; +// RAVEN BEGIN +// bdube: crouch slide + if ( current.crouchSlideTime > 0 ) { + drop += control * PM_SLIDEFRICTION * frametime; + } else { + drop += control * PM_FRICTION * frametime; + } +// RAVEN END + } + } + } + // apply water friction even if just wading + else if ( waterLevel ) { + drop += speed * PM_WATERFRICTION * waterLevel * frametime; + } + // apply air friction + else { + drop += speed * PM_AIRFRICTION * frametime; + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + current.velocity *= ( newspeed / speed ); + + // TTimo - snap to avoid denormals + if ( fabs( current.velocity.x ) < 1.0e-5f ) { + current.velocity.x = 0.0f; + } + if ( fabs( current.velocity.y ) < 1.0e-5f ) { + current.velocity.y = 0.0f; + } + if ( fabs( current.velocity.z ) < 1.0e-5f ) { + current.velocity.z = 0.0f; + } +} + +/* +=================== +idPhysics_Player::WaterJumpMove + +Flying out of the water +=================== +*/ +void idPhysics_Player::WaterJumpMove( void ) { + + // waterjump has no control, but falls + idPhysics_Player::SlideMove( true, true, false, false ); + + // add gravity + current.velocity += gravityNormal * frametime; + // if falling down + if ( current.velocity * gravityNormal > 0.0f ) { + // cancel as soon as we are falling down again + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } +} + +/* +=================== +idPhysics_Player::WaterMove +=================== +*/ +void idPhysics_Player::WaterMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + float vel; + + if ( idPhysics_Player::CheckWaterJump() ) { + idPhysics_Player::WaterJumpMove(); + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // user intentions + if ( !scale ) { + wishvel = gravityNormal * 60; // sink towards bottom + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + if ( wishspeed > playerSpeed * PM_SWIMSCALE ) { + wishspeed = playerSpeed * PM_SWIMSCALE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE ); + + // make sure we can go up slopes easily under water + if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) { + vel = current.velocity.Length(); + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + current.velocity.Normalize(); + current.velocity *= vel; + } + + idPhysics_Player::SlideMove( false, true, false, false ); +} + +/* +=================== +idPhysics_Player::FlyMove +=================== +*/ +void idPhysics_Player::FlyMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +=================== +idPhysics_Player::AirMove +=================== +*/ +void idPhysics_Player::AirMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + +// RAVEN BEGIN +// bdube: crouch time + // if the player isnt pressing crouch and heading down then accumulate slide time + if ( command.upmove >= 0 && current.velocity * gravityNormal > 0 ) { + current.crouchSlideTime += framemsec * 2; + if ( current.crouchSlideTime > 2000 ) { + current.crouchSlideTime = 2000; + } + } +// RAVEN END + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishvel -= (wishvel * gravityNormal) * gravityNormal; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // not on ground, so little effect on velocity + idPhysics_Player::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 ( groundPlane ) { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + + // NOTE: enable stair checking while moving through the air in multiplayer to allow bunny hopping onto stairs + idPhysics_Player::SlideMove( true, gameLocal.isMultiplayer, false, false ); +} + +/* +=================== +idPhysics_Player::WalkMove +=================== +*/ +void idPhysics_Player::WalkMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + float accelerate; + idVec3 oldVelocity, vel; + float oldVel, newVel; + + if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) { + // begin swimming + idPhysics_Player::WaterMove(); + return; + } + + if ( idPhysics_Player::CheckJump() ) { + // jumped away + if ( waterLevel > WATERLEVEL_FEET ) { + idPhysics_Player::WaterMove(); + } + else { + idPhysics_Player::AirMove(); + } + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + + // project the forward and right directions onto the ground plane + viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + // + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( waterLevel ) { + float waterScale; + + waterScale = waterLevel / 3.0f; + waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale; + if ( wishspeed > playerSpeed * waterScale ) { + wishspeed = playerSpeed * waterScale; + } + } + + // when a player gets hit, they temporarily lose full control, which allows them to be moved a bit + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + accelerate = Pm_AirAccelerate(); + } + else { + accelerate = Pm_Accelerate(); + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate ); + + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + current.velocity += gravityVector * frametime; + } + + oldVelocity = current.velocity; + + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + // if not clipped into the opposite direction + if ( oldVelocity * current.velocity > 0.0f ) { + newVel = current.velocity.LengthSqr(); + if ( newVel > 1.0f ) { + oldVel = oldVelocity.LengthSqr(); + if ( oldVel > 1.0f ) { + // don't decrease velocity when going up or down a slope + current.velocity *= idMath::Sqrt( oldVel / newVel ); + } + } + } + + // don't do anything if standing still + vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal; + if ( vel.IsZero() ) { + return; + } + + gameLocal.push.InitSavingPushedEntityPositions(); + + idPhysics_Player::SlideMove( false, true, true, !gameLocal.isMultiplayer ); +} + +/* +============== +idPhysics_Player::DeadMove +============== +*/ +void idPhysics_Player::DeadMove( void ) { + float forward; + + if ( !walking ) { + return; + } + + // extra friction + forward = current.velocity.Length(); + forward -= 20; + if ( forward <= 0 ) { + current.velocity = vec3_origin; + } + else { + current.velocity.Normalize(); + current.velocity *= forward; + } +} + +/* +=============== +idPhysics_Player::NoclipMove +=============== +*/ +void idPhysics_Player::NoclipMove( void ) { + float speed, drop, friction, newspeed, stopspeed; + float scale, wishspeed; + idVec3 wishdir; + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat ( ) > -1 ) { + idPhysics_Player::Friction(); + } else { +// RAVEN END + + // friction + speed = current.velocity.Length(); + if ( speed < 20.0f ) { + current.velocity = vec3_origin; + } + else { + stopspeed = playerSpeed * 0.3f; + if ( speed < stopspeed ) { + speed = stopspeed; + } + friction = PM_NOCLIPFRICTION; + drop = speed * friction * frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= newspeed / speed; + } + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + } +// RAVEN END + + // accelerate + scale = idPhysics_Player::CmdScale( command ); + + wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishdir -= scale * gravityNormal * command.upmove; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_Accelerate() ); + + // move + current.origin += frametime * current.velocity; +} + +/* +=============== +idPhysics_Player::SpectatorMove +=============== +*/ +void idPhysics_Player::SpectatorMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + trace_t trace; + idVec3 end; + + // fly movement + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +============ +idPhysics_Player::LadderMove +============ +*/ +void idPhysics_Player::LadderMove( void ) { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * ladderNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (ladderNormal * right) * ladderNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( ladderNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.upmove ) { + wishvel += -0.5f * gravityNormal * scale * (float) command.upmove; + } + + // do strafe friction + idPhysics_Player::Friction(); + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, Pm_Accelerate() ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + +/* +============= +idPhysics_Player::CorrectAllSolid +============= +*/ +void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:allsolid\n", c_pmove ); + } + + // FIXME: jitter around to find a free spot ? + + if ( trace.fraction >= 1.0f ) { + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = current.origin; + trace.endAxis = clipModelAxis; + trace.fraction = 0.0f; + trace.c.dist = current.origin.z; + trace.c.normal.Set( 0, 0, 1 ); + trace.c.point = current.origin; + trace.c.entityNum = ENTITYNUM_WORLD; + trace.c.id = 0; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.material = NULL; + trace.c.contents = contents; + } +} + +/* +============= +idPhysics_Player::CheckGround +============= +*/ +// RAVEN BEGIN +// MrE: check stuck +void idPhysics_Player::CheckGround( bool checkStuck ) { +// RAVEN END + int i, contents; + idVec3 point; + bool hadGroundContacts; + + hadGroundContacts = HasGroundContacts(); + + // set the clip model origin before getting the contacts + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); + + // setup a ground trace from the contacts + groundTrace.endpos = current.origin; + groundTrace.endAxis = clipModel->GetAxis(); + if ( contacts.Num() ) { + groundTrace.fraction = 0.0f; + groundTrace.c = contacts[0]; + for ( i = 1; i < contacts.Num(); i++ ) { + groundTrace.c.normal += contacts[i].normal; + } + groundTrace.c.normal.Normalize(); + } else { + groundTrace.fraction = 1.0f; + } + +// RAVEN BEGIN +// ddynerman: multiple collision worlds +// MrE: check stuck + if ( checkStuck ) { + contents = gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), -1, self ); + if ( contents & MASK_SOLID ) { + // do something corrective if stuck in solid + idPhysics_Player::CorrectAllSolid( groundTrace, contents ); + } + } +// RAVEN END + + // if the trace didn't hit anything, we are in free fall + if ( groundTrace.fraction == 1.0f ) { + groundPlane = false; + walking = false; + groundEntityPtr = NULL; + return; + } + + groundMaterial = groundTrace.c.material; + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + // check if getting thrown off the ground + if ( (current.velocity * -gravityNormal) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 10.0f ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:kickoff\n", c_pmove ); + } + + groundPlane = false; + walking = false; + return; + } + + // slopes that are too steep will not be considered onground + if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:steep\n", c_pmove ); + } + + // FIXME: if they can't slide down the slope, let them walk (sharp crevices) + + if( gameLocal.isMultiplayer ) { + // in multiplayer, instead of sliding push the player out from the normal for some free fall + current.origin += groundTrace.c.normal; + + groundPlane = false; + walking = false; + } else { + // make sure we don't die from sliding down a steep slope + if ( current.velocity * gravityNormal > 150.0f ) { + current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal; + } + groundPlane = true; + walking = false; + } + + return; + } + + groundPlane = true; + walking = true; + + // hitting solid ground will end a waterjump + if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + current.movementTime = 0; + } + + // if the player didn't have ground contacts the previous frame + if ( !hadGroundContacts ) { + // don't do landing time if we were just going down a slope + if ( (current.velocity * -gravityNormal) < -200.0f ) { + // don't allow another jump for a little while + current.movementFlags |= PMF_TIME_LAND; + current.movementTime = 250; + } + } + + // let the entity know about the collision + if ( self ) { + self->Collide( groundTrace, current.velocity ); + } + + if ( groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +============== +idPhysics_Player::CheckDuck + +Sets clip model size +============== +*/ +void idPhysics_Player::CheckDuck( void ) { + trace_t trace; + idVec3 end; + idBounds bounds; + float maxZ; + + if ( current.movementType == PM_DEAD ) { + maxZ = pm_deadheight.GetFloat(); + } else { + // stand up when up against a ladder + if ( command.upmove < 0 && !ladder ) { + // duck + current.movementFlags |= PMF_DUCKED; + } else { + // stand up if possible + if ( current.movementFlags & PMF_DUCKED ) { + // try to stand up + end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( trace.fraction >= 1.0f ) { + current.movementFlags &= ~PMF_DUCKED; + } + } + } + + if ( current.movementFlags & PMF_DUCKED ) { +// RAVEN BEGIN +// bdube: crouch slide + if ( !current.crouchSlideTime ) { + playerSpeed = crouchSpeed; + } +// RAVEN END + maxZ = pm_crouchheight.GetFloat(); + } else { + maxZ = pm_normalheight.GetFloat(); +// RAVEN BEGIN +// bdube: crouch slide + if ( groundPlane && current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } +// RAVEN END + } + } + // if the clipModel height should change + if ( clipModel->GetBounds()[1][2] != maxZ ) { + + bounds = clipModel->GetBounds(); + bounds[1][2] = maxZ; + if ( pm_usecylinder.GetBool() ) { + clipModel->LoadModel( idTraceModel( bounds, 8 ), NULL ); + } else { + clipModel->LoadModel( idTraceModel( bounds ), NULL ); + } + } +} + +/* +================ +idPhysics_Player::CheckLadder +================ +*/ +void idPhysics_Player::CheckLadder( void ) { + idVec3 forward, start, end; + trace_t trace; + float tracedist; + + if ( current.movementTime ) { + return; + } + + // if on the ground moving backwards + if ( walking && command.forwardmove <= 0 ) { + return; + } + + // forward vector orthogonal to gravity + forward = viewForward - (gravityNormal * viewForward) * gravityNormal; + forward.Normalize(); + + if ( walking ) { + // don't want to get sucked towards the ladder when still walking + tracedist = 1.0f; + } else { + tracedist = 48.0f; + } + + end = current.origin + tracedist * forward; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + // if near a surface + if ( trace.fraction < 1.0f ) { + + // if a ladder surface + if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) { + + // check a step height higher + end = current.origin - gravityNormal * ( maxStepHeight * 0.75f ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = trace.endpos; + end = start + tracedist * forward; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // if also near a surface a step height higher + if ( trace.fraction < 1.0f ) { + + // if it also is a ladder surface + if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) { + ladder = true; + ladderNormal = trace.c.normal; + } + } + } + } +} + +/* +============= +idPhysics_Player::CheckJump +============= +*/ +bool idPhysics_Player::CheckJump( void ) { + idVec3 addVelocity; + + if ( command.upmove < 10 ) { + // not holding jump + return false; + } + + // must wait for jump to be released + if ( current.movementFlags & PMF_JUMP_HELD ) { + return false; + } + + // don't jump if we can't stand up + if ( current.movementFlags & PMF_DUCKED ) { + return false; + } + + groundPlane = false; // jumping away + walking = false; + current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED; + + addVelocity = 2.0f * maxJumpHeight * -gravityVector; + addVelocity *= idMath::Sqrt( addVelocity.Normalize() ); + current.velocity += addVelocity; + +// RAVEN BEGIN +// bdube: crouch slide, nick maggoire is awesome + current.crouchSlideTime = 0; +// RAVEN END + + return true; +} + +/* +============= +idPhysics_Player::CheckWaterJump +============= +*/ +bool idPhysics_Player::CheckWaterJump( void ) { + idVec3 spot; + int cont; + idVec3 flatforward; + + if ( current.movementTime ) { + return false; + } + + // check for water jump + if ( waterLevel != WATERLEVEL_WAIST ) { + return false; + } + + flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal; + flatforward.Normalize(); + + spot = current.origin + 30.0f * flatforward; + spot -= 4.0f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( !(cont & CONTENTS_SOLID) ) { + return false; + } + + spot -= 16.0f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( cont ) { + return false; + } + + // jump out of water + current.velocity = 200.0f * viewForward - 350.0f * gravityNormal; + current.movementFlags |= PMF_TIME_WATERJUMP; + current.movementTime = 2000; + + return true; +} + +/* +============= +idPhysics_Player::SetWaterLevel +============= +*/ +void idPhysics_Player::SetWaterLevel( void ) { + idVec3 point; + idBounds bounds; + int contents; + + // + // get waterlevel, accounting for ducking + // + waterLevel = WATERLEVEL_NONE; + waterType = 0; + + bounds = clipModel->GetBounds(); + +// RAVEN BEGIN +// AReis: Get back the water entity (if there is one), so we can grab his density +// then apply some force to the fluid since we're moving through it. + idEntity *other = NULL; + + // check at feet level + point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self, &other ); +// RAVEN END + if ( contents & MASK_WATER ) { + + waterType = contents; + waterLevel = WATERLEVEL_FEET; + + // check at waist level + point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( contents & MASK_WATER ) { + + waterLevel = WATERLEVEL_WAIST; + + // check at head level + point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( contents & MASK_WATER ) { + waterLevel = WATERLEVEL_HEAD; + } + } + } +} + +/* +================ +idPhysics_Player::DropTimers +================ +*/ +void idPhysics_Player::DropTimers( void ) { + // drop misc timing counter + if ( current.movementTime ) { + if ( framemsec >= current.movementTime ) { + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } + else { + current.movementTime -= framemsec; + } + } + +// RAVEN BEGIN +// bdube: crouch slide + if ( groundPlane && current.crouchSlideTime ) { + if ( framemsec >= current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } else { + current.crouchSlideTime -= framemsec; + } + } +// RAVEN END +} + +/* +================ +idPhysics_Player::MovePlayer +================ +*/ +void idPhysics_Player::MovePlayer( int msec ) { + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint for the previous frame + c_pmove++; + + walking = false; + groundPlane = false; + ladder = false; + + // determine the time + framemsec = msec; + frametime = framemsec * 0.001f; + + // default speed + playerSpeed = walkSpeed; + + // remove jumped and stepped up flag + current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN); + current.stepUp = 0.0f; + + if ( command.upmove < 10 ) { + // not holding jump + current.movementFlags &= ~PMF_JUMP_HELD; + } + + // if no movement at all + if ( current.movementType == PM_FREEZE ) { + return; + } + + // move the player velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + // view vectors + viewAngles.ToVectors( &viewForward, NULL, NULL ); + viewForward *= clipModelAxis; + viewRight = gravityNormal.Cross( viewForward ); + viewRight.Normalize(); + + // fly in spectator mode +// RAVEN BEGIN +// nmckenzie: Allowing ways to force spectator movement. + if ( current.movementType == PM_SPECTATOR || pm_forcespectatormove.GetBool() ) { +// RAVEN END + SpectatorMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // special no clip mode + if ( current.movementType == PM_NOCLIP ) { + idPhysics_Player::NoclipMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // no control when dead + if ( current.movementType == PM_DEAD ) { + command.forwardmove = 0; + command.rightmove = 0; + command.upmove = 0; + } + + // set watertype and waterlevel +// RAVEN BEGIN +// ddynerman: water disabled in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + // check for ground + idPhysics_Player::CheckGround( true ); + + // check if up against a ladder +// RAVEN BEGIN +// MrE: no ladders in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::CheckLadder(); + } +// RAVEN END + + // set clip model size + idPhysics_Player::CheckDuck(); + + // handle timers + idPhysics_Player::DropTimers(); + + // move + if ( current.movementType == PM_DEAD ) { + // dead + idPhysics_Player::DeadMove(); + } + else if ( ladder ) { + // going up or down a ladder + idPhysics_Player::LadderMove(); + } +// RAVEN BEGIN +// ddynerman: water disabled in MP + else if ( !gameLocal.isMultiplayer && current.movementFlags & PMF_TIME_WATERJUMP ) { + + // jumping out of water + idPhysics_Player::WaterJumpMove(); + } + else if ( !gameLocal.isMultiplayer && waterLevel > 1 ) { +// RAVEN END + // swimming + idPhysics_Player::WaterMove(); + } + else if ( walking ) { + // walking on ground + idPhysics_Player::WalkMove(); + } + else { + // airborne + idPhysics_Player::AirMove(); + } + +// RAVEN BEGIN +// ddynerman: water disabled in MP + if( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + idPhysics_Player::CheckGround( false ); + + // move the player velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); +} + + +/* +================ +idPhysics_Player::GetWaterLevel +================ +*/ +waterLevel_t idPhysics_Player::GetWaterLevel( void ) const { + return waterLevel; +} + +/* +================ +idPhysics_Player::GetWaterType +================ +*/ +int idPhysics_Player::GetWaterType( void ) const { + return waterType; +} + +/* +================ +idPhysics_Player::HasJumped +================ +*/ +bool idPhysics_Player::HasJumped( void ) const { + return ( ( current.movementFlags & PMF_JUMPED ) != 0 ); +} + +/* +================ +idPhysics_Player::HasSteppedUp +================ +*/ +bool idPhysics_Player::HasSteppedUp( void ) const { + return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 ); +} + +/* +================ +idPhysics_Player::GetStepUp +================ +*/ +float idPhysics_Player::GetStepUp( void ) const { + return current.stepUp; +} + +/* +================ +idPhysics_Player::IsCrouching +================ +*/ +bool idPhysics_Player::IsCrouching( void ) const { + //MCG: if bound, never think we're crouched + return ( !masterEntity&&( current.movementFlags & PMF_DUCKED ) != 0 ); +} + +/* +================ +idPhysics_Player::OnLadder +================ +*/ +bool idPhysics_Player::OnLadder( void ) const { + return ladder; +} + +/* +================ +idPhysics_Player::idPhysics_Player +================ +*/ +idPhysics_Player::idPhysics_Player( void ) { + debugLevel = false; + clipModel = NULL; + clipMask = 0; + memset( ¤t, 0, sizeof( current ) ); + saved = current; + walkSpeed = 0; + crouchSpeed = 0; + maxStepHeight = 0; + maxJumpHeight = 0; + memset( &command, 0, sizeof( command ) ); + viewAngles.Zero(); + framemsec = 0; + frametime = 0; + playerSpeed = 0; + viewForward.Zero(); + viewRight.Zero(); + walking = false; + groundPlane = false; + memset( &groundTrace, 0, sizeof( groundTrace ) ); + groundMaterial = NULL; + ladder = false; + ladderNormal.Zero(); + waterLevel = WATERLEVEL_NONE; + waterType = 0; +} + +/* +================ +idPhysics_Player_SavePState +================ +*/ +void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson Added unsaved var + + savefile->WriteFloat( state.stepUp ); + savefile->WriteInt( state.movementType ); + savefile->WriteInt( state.movementFlags ); + savefile->WriteInt( state.movementTime ); + + savefile->WriteInt( state.crouchSlideTime ); // cnicholson Added unsaved var +} + +/* +================ +idPhysics_Player_RestorePState +================ +*/ +void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson Added unrestored var + + savefile->ReadFloat( state.stepUp ); + savefile->ReadInt( state.movementType ); + savefile->ReadInt( state.movementFlags ); + savefile->ReadInt( state.movementTime ); + + savefile->ReadInt( state.crouchSlideTime ); // cnicholson Added unrestored var +} + +/* +================ +idPhysics_Player::Save +================ +*/ +void idPhysics_Player::Save( idSaveGame *savefile ) const { + + idPhysics_Player_SavePState( savefile, current ); + idPhysics_Player_SavePState( savefile, saved ); + + savefile->WriteFloat( walkSpeed ); + savefile->WriteFloat( crouchSpeed ); + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( maxJumpHeight ); + savefile->WriteInt( debugLevel ); + + savefile->WriteUsercmd( command ); + savefile->WriteAngles( viewAngles ); + + savefile->WriteInt( framemsec ); + savefile->WriteFloat( frametime ); + savefile->WriteFloat( playerSpeed ); + savefile->WriteVec3( viewForward ); + savefile->WriteVec3( viewRight ); + + savefile->WriteBool( walking ); + savefile->WriteBool( groundPlane ); + savefile->WriteTrace( groundTrace ); + savefile->WriteMaterial( groundMaterial ); + + savefile->WriteBool( ladder ); + savefile->WriteVec3( ladderNormal ); + + savefile->WriteInt( (int)waterLevel ); + savefile->WriteInt( waterType ); +} + +/* +================ +idPhysics_Player::Restore +================ +*/ +void idPhysics_Player::Restore( idRestoreGame *savefile ) { + + idPhysics_Player_RestorePState( savefile, current ); + idPhysics_Player_RestorePState( savefile, saved ); + + savefile->ReadFloat( walkSpeed ); + savefile->ReadFloat( crouchSpeed ); + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( maxJumpHeight ); + savefile->ReadInt( debugLevel ); + + savefile->ReadUsercmd( command ); + savefile->ReadAngles( viewAngles ); + + savefile->ReadInt( framemsec ); + savefile->ReadFloat( frametime ); + savefile->ReadFloat( playerSpeed ); + savefile->ReadVec3( viewForward ); + savefile->ReadVec3( viewRight ); + + savefile->ReadBool( walking ); + savefile->ReadBool( groundPlane ); + savefile->ReadTrace( groundTrace ); + savefile->ReadMaterial( groundMaterial ); + + savefile->ReadBool( ladder ); + savefile->ReadVec3( ladderNormal ); + + savefile->ReadInt( (int &)waterLevel ); + savefile->ReadInt( waterType ); +} + +/* +================ +idPhysics_Player::SetPlayerInput +================ +*/ +void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) { + command = cmd; + viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles +} + +/* +================ +idPhysics_Player::SetSpeed +================ +*/ +void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) { + walkSpeed = newWalkSpeed; + crouchSpeed = newCrouchSpeed; +} + +/* +================ +idPhysics_Player::SetMaxStepHeight +================ +*/ +void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Player::GetMaxStepHeight +================ +*/ +float idPhysics_Player::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Player::SetMaxJumpHeight +================ +*/ +void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) { + maxJumpHeight = newMaxJumpHeight; +} + +/* +================ +idPhysics_Player::SetMovementType +================ +*/ +void idPhysics_Player::SetMovementType( const pmtype_t type ) { + current.movementType = type; +} + +/* +================ +idPhysics_Player::SetKnockBack +================ +*/ +void idPhysics_Player::SetKnockBack( const int knockBackTime ) { + if ( current.movementTime ) { + return; + } + current.movementFlags |= PMF_TIME_KNOCKBACK; + current.movementTime = knockBackTime; +} + +/* +================ +idPhysics_Player::SetDebugLevel +================ +*/ +void idPhysics_Player::SetDebugLevel( bool set ) { + debugLevel = set; +} + +/* +================ +idPhysics_Player::Evaluate +================ +*/ +bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + + waterLevel = WATERLEVEL_NONE; + waterType = 0; + oldOrigin = current.origin; + + clipModel->Unlink(); + + // if bound to a master + if ( masterEntity ) { + assert( self ); + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f ); + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + ActivateContactEntities(); + + idPhysics_Player::MovePlayer( timeStepMSec ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + + // IsOutsideWorld uses self, so it needs to be non null + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self ? "NULL" : self->name.c_str(), current.origin.ToString(0) ); + } + } +// RAVEN END + + return true; //( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Player::UpdateTime +================ +*/ +void idPhysics_Player::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Player::GetTime +================ +*/ +int idPhysics_Player::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Player::GetImpactInfo +================ +*/ +void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Player::ApplyImpulse +================ +*/ +void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( current.movementType != PM_NOCLIP ) { + current.velocity += impulse * invMass; + } +} + +/* +================ +idPhysics_Player::IsAtRest +================ +*/ +bool idPhysics_Player::IsAtRest( void ) const { + return false; +} + +/* +================ +idPhysics_Player::GetRestStartTime +================ +*/ +int idPhysics_Player::GetRestStartTime( void ) const { + return -1; +} + +/* +================ +idPhysics_Player::SaveState +================ +*/ +void idPhysics_Player::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Player::RestoreState +================ +*/ +void idPhysics_Player::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + assert( self ); + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); + } +// RAVEN END +} + +/* +================ +idPhysics_Player::GetOrigin +================ +*/ +const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const { + return current.origin; +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Translate +================ +*/ +void idPhysics_Player::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Rotate +================ +*/ +void idPhysics_Player::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::SetLinearVelocity +================ +*/ +void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; +} + +/* +================ +idPhysics_Player::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushed( int deltaTime ) { + idVec3 velocity; + float d; + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Player::ClearPushedVelocity +================ +*/ +void idPhysics_Player::ClearPushedVelocity( void ) { + current.pushVelocity.Zero(); + current.lastPushVelocity.Zero ( ); +} + +/* +================ +idPhysics_Player::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + } + } +} + +const float PLAYER_VELOCITY_MAX = 4000; +const int PLAYER_VELOCITY_TOTAL_BITS = 16; +const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1; +const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS; +const int PLAYER_MOVEMENT_TYPE_BITS = 3; +const int PLAYER_MOVEMENT_FLAGS_BITS = 8; + +/* +================ +idPhysics_Player::WriteToSnapshot +================ +*/ +void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); + + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + msg.WriteDeltaFloat( 0.0f, current.stepUp ); + msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS ); + msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS ); + msg.WriteDeltaLong( 0, current.movementTime ); +// RAVEN BEGIN +// bdube: crouch slide + msg.WriteDeltaLong( 0, current.crouchSlideTime ); +// abahr: gravity + //msg.WriteQuat( GetAxis().ToQuat() ); + //msg.WriteVec3( GetGravity() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::ReadFromSnapshot +================ +*/ +void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) { + + idVec3 oldOrigin = current.origin; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + + GAMELOG_SET( "origin_delta_x", (current.origin - oldOrigin).x ); + GAMELOG_SET( "origin_delta_y", (current.origin - oldOrigin).y ); + GAMELOG_SET( "origin_delta_z", (current.origin - oldOrigin).z ); + +// current.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + + idVec3 oldVelocity = current.velocity; + + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); + + GAMELOG_SET( "velocity_delta_x", (current.velocity - oldVelocity).x ); + GAMELOG_SET( "velocity_delta_y", (current.velocity - oldVelocity).y ); + GAMELOG_SET( "velocity_delta_z", (current.velocity - oldVelocity).z ); + + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + current.stepUp = msg.ReadDeltaFloat( 0.0f ); + current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS ); + + current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS ); + if( !( saved.movementFlags & PMF_JUMP_HELD ) && current.movementFlags & PMF_JUMP_HELD ) { + assert( self && self->IsType( idPlayer::GetClassType() ) ); + ((idPlayer*)self)->jumpDuringHitch = true; + } + + current.movementTime = msg.ReadDeltaLong( 0 ); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + } +// RAVEN BEGIN +// bdube: crouch slide + current.crouchSlideTime = msg.ReadDeltaLong( 0 ); +// RAVEN END +} + +/* +=============== +idPhysics_Player::SetClipModelNoLink +=============== +*/ +void idPhysics_Player::SetClipModelNoLink( idClipModel *model ) { + assert( model ); + assert( model->IsTraceModel() ); + + if ( clipModel && clipModel != model ) { + delete clipModel; + } + clipModel = model; +} diff --git a/source/game/physics/Physics_Player.h b/source/game/physics/Physics_Player.h new file mode 100644 index 0000000..60498fd --- /dev/null +++ b/source/game/physics/Physics_Player.h @@ -0,0 +1,198 @@ + +#ifndef __PHYSICS_PLAYER_H__ +#define __PHYSICS_PLAYER_H__ + +/* +=================================================================================== + + Player physics + + Simulates the motion of a player through the environment. Input from the + player is used to allow a certain degree of control over the motion. + +=================================================================================== +*/ + +// movementType +typedef enum { + PM_NORMAL, // normal physics + PM_DEAD, // no acceleration or turning, but free falling + PM_SPECTATOR, // flying without gravity but with collision detection + PM_FREEZE, // stuck in place without control + PM_NOCLIP // flying without collision detection nor gravity +} pmtype_t; + +typedef enum { + WATERLEVEL_NONE, + WATERLEVEL_FEET, + WATERLEVEL_WAIST, + WATERLEVEL_HEAD +} waterLevel_t; + +#define MAXTOUCH 32 + +typedef struct playerPState_s { + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END + float stepUp; + int movementType; + int movementFlags; + int movementTime; +// RAVEN BEGIN +// bdube: crouch slide + int crouchSlideTime; +// RAVEN END +} playerPState_t; + +class idPhysics_Player : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Player ); + + idPhysics_Player( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ); + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + void SetMaxJumpHeight( const float newMaxJumpHeight ); + void SetMovementType( const pmtype_t type ); + void SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ); + void SetKnockBack( const int knockBackTime ); + void SetDebugLevel( bool set ); + // feed back from last physics frame + waterLevel_t GetWaterLevel( void ) const; + int GetWaterType( void ) const; + bool HasJumped( void ) const; + bool HasSteppedUp( void ) const; + float GetStepUp( void ) const; + bool IsCrouching( void ) const; + bool OnLadder( void ) const; + const idVec3 & PlayerGetOrigin( void ) const; // != GetOrigin + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + void ClearPushedVelocity( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +// RAVEN BEGIN +// kfuller: Added + bool IsNoclip( void ) const; + bool IsDead( void ) const; +// RAVEN END + + void SetClipModelNoLink( idClipModel *clip ); + +private: + // player physics state + playerPState_t current; + playerPState_t saved; + + // properties + float walkSpeed; + float crouchSpeed; + float maxStepHeight; + float maxJumpHeight; + int debugLevel; // if set, diagnostic output will be printed + + // player input + usercmd_t command; + idAngles viewAngles; + + // run-time variables + int framemsec; + float frametime; + float playerSpeed; + idVec3 viewForward; + idVec3 viewRight; + + // walk movement + bool walking; + bool groundPlane; + trace_t groundTrace; + const idMaterial * groundMaterial; + + // ladder movement + bool ladder; + idVec3 ladderNormal; + + // results of last evaluate + waterLevel_t waterLevel; + int waterType; + +private: + float CmdScale( const usercmd_t &cmd ) const; + void Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ); + bool SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ); + void Friction( void ); + void WaterJumpMove( void ); + void WaterMove( void ); + void FlyMove( void ); + void AirMove( void ); + void WalkMove( void ); + void DeadMove( void ); + void NoclipMove( void ); + void SpectatorMove( void ); + void LadderMove( void ); + void CorrectAllSolid( trace_t &trace, int contents ); +// RAVEN BEGIN +// MrE: check stuck + void CheckGround( bool checkStuck ); +// RAVEN END + void CheckDuck( void ); + void CheckLadder( void ); + bool CheckJump( void ); + bool CheckWaterJump( void ); + void SetWaterLevel( void ); + void DropTimers( void ); + void MovePlayer( int msec ); + + float Pm_Accelerate( void ); + float Pm_AirAccelerate( void ); +}; + +ID_INLINE bool idPhysics_Player::IsNoclip( void ) const { + return current.movementType == PM_NOCLIP; +} + +ID_INLINE bool idPhysics_Player::IsDead( void ) const { + return current.movementType == PM_DEAD; +} + +#endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/source/game/physics/Physics_RigidBody.cpp b/source/game/physics/Physics_RigidBody.cpp new file mode 100644 index 0000000..1c28d6b --- /dev/null +++ b/source/game/physics/Physics_RigidBody.cpp @@ -0,0 +1,1623 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_RigidBody ) +END_CLASS + +const float STOP_SPEED = 10.0f; + + +#undef RB_TIMINGS + +#ifdef RB_TIMINGS +static int lastTimerReset = 0; +static int numRigidBodies = 0; +static idTimer timer_total, timer_collision; +#endif + + +/* +================ +RigidBodyDerivatives +================ +*/ +void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const idPhysics_RigidBody *p = (idPhysics_RigidBody *) clientData; + rigidBodyIState_t *s = (rigidBodyIState_t *) state; + // NOTE: this struct should be build conform rigidBodyIState_t + struct rigidBodyDerivatives_s { + idVec3 linearVelocity; + idMat3 angularMatrix; + idVec3 force; + idVec3 torque; + } *d = (struct rigidBodyDerivatives_s *) derivatives; + idVec3 angularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = s->orientation * p->inverseInertiaTensor * s->orientation.Transpose(); + angularVelocity = inverseWorldInertiaTensor * s->angularMomentum; + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix = SkewSymmetric( angularVelocity ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque = - p->angularFriction * s->angularMomentum + p->current.externalTorque; +} + +/* +================ +idPhysics_RigidBody::Integrate + + Calculate next state from the current state using an integrator. +================ +*/ +void idPhysics_RigidBody::Integrate( float deltaTime, rigidBodyPState_t &next ) { + idVec3 position; + + position = current.i.position; + current.i.position += centerOfMass * current.i.orientation; + + current.i.orientation.TransposeSelf(); + + integrator->Evaluate( (float *) ¤t.i, (float *) &next.i, 0, deltaTime ); + next.i.orientation.OrthoNormalizeSelf(); + + // apply gravity + next.i.linearMomentum += deltaTime * gravityVector * mass; + + current.i.orientation.TransposeSelf(); + next.i.orientation.TransposeSelf(); + + current.i.position = position; + next.i.position -= centerOfMass * next.i.orientation; + + next.atRest = current.atRest; +} + +/* +================ +idPhysics_RigidBody::CollisionImpulse + + Calculates the collision impulse using the velocity relative to the collision object. + The current state should be set to the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CollisionImpulse( const trace_t &collision, idVec3 &impulse ) { + idVec3 r, linearVelocity, angularVelocity, velocity; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator, vel; + impactInfo_t info; + idEntity *ent; + + // get info from other entity involved + ent = gameLocal.entities[collision.c.entityNum]; + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + +// RAVEN BEGIN +// bdube: once in water take out the water flag and increase friction + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + clipMask &= ~CONTENTS_WATER; + linearFriction *= 20.0f; + angularFriction *= 20.0f; + } +// RAVEN END + + // collision point relative to the body center of mass + r = collision.c.point - (current.i.position + centerOfMass * current.i.orientation); + + // the velocity at the collision point + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + + // velocity in normal direction + vel = velocity * collision.c.normal; + + if ( vel > -STOP_SPEED ) { + impulseNumerator = STOP_SPEED; + } + else { + impulseNumerator = -( 1.0f + bouncyness ) * vel; + } + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // update linear and angular momentum with impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if no movement at all don't blow up + if ( collision.fraction < 0.0001f ) { + current.i.linearMomentum *= 0.5f; + current.i.angularMomentum *= 0.5f; + } + + // callback to self to let the entity know about the collision + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_RigidBody::CheckForCollisions + + Check for collisions between the current and next state. + If there is a collision the next state is set to the state at the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { +//#define TEST_COLLISION_DETECTION + idMat3 axis; + idRotation rotation; + bool collided = false; + +#ifdef TEST_COLLISION_DETECTION + bool startsolid; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( current.i.orientation, next.i.orientation, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( current.i.position ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, current.i.position, next.i.position, rotation, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + next.i.linearMomentum = current.i.linearMomentum; + next.i.angularMomentum = current.i.angularMomentum; + collided = true; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, next.i.position, clipModel, next.i.orientation, clipMask, self ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return collided; +} + +/* +================ +idPhysics_RigidBody::ContactFriction + + Does not solve friction for multiple simultaneous contacts but applies contact friction in isolation. + Uses absolute velocity at the contact points instead of the velocity relative to the contact object. +================ +*/ +void idPhysics_RigidBody::ContactFriction( float deltaTime ) { + int i; + float magnitude, impulseNumerator, impulseDenominator; + idMat3 inverseWorldInertiaTensor; + idVec3 linearVelocity, angularVelocity; + idVec3 massCenter, r, velocity, normal, impulse, normalVelocity; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + + massCenter = current.i.position + centerOfMass * current.i.orientation; + + for ( i = 0; i < contacts.Num(); i++ ) { + + r = contacts[i].point - massCenter; + + // calculate velocity at contact point + linearVelocity = inverseMass * current.i.linearMomentum; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + + // velocity along normal vector + normalVelocity = ( velocity * contacts[i].normal ) * contacts[i].normal; + + // calculate friction impulse + normal = -( velocity - normalVelocity ); + magnitude = normal.Normalize(); + impulseNumerator = contactFriction * magnitude; + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply friction impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if moving towards the surface at the contact point + if ( normalVelocity * contacts[i].normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross( impulse ); + } + } +} + +/* +================ +idPhysics_RigidBody::TestIfAtRest + + Returns true if the body is considered at rest. + Does not catch all cases where the body is at rest but is generally good enough. +================ +*/ +bool idPhysics_RigidBody::TestIfAtRest( void ) const { + int i; + float gv; + idVec3 v, av, normal, point; + idMat3 inverseWorldInertiaTensor; + idFixedWinding contactWinding; + + if ( current.atRest >= 0 ) { + return true; + } + + // need at least 3 contact points to come to rest + if ( contacts.Num() < 3 ) { + return false; + } + + // get average contact plane normal + normal.Zero(); + for ( i = 0; i < contacts.Num(); i++ ) { + normal += contacts[i].normal; + } + normal /= (float) contacts.Num(); + normal.Normalize(); + + // if on a too steep surface + if ( (normal * gravityNormal) > -0.7f ) { + return false; + } + + // create bounds for contact points + contactWinding.Clear(); + for ( i = 0; i < contacts.Num(); i++ ) { + // project point onto plane through origin orthogonal to the gravity + point = contacts[i].point - (contacts[i].point * gravityNormal) * gravityNormal; + contactWinding.AddToConvexHull( point, gravityNormal ); + } + + // need at least 3 contact points to come to rest + if ( contactWinding.GetNumPoints() < 3 ) { + return false; + } + + // center of mass in world space + point = current.i.position + centerOfMass * current.i.orientation; + point -= (point * gravityNormal) * gravityNormal; + + // if the point is not inside the winding + if ( !contactWinding.PointInside( gravityNormal, point, 0 ) ) { + return false; + } + + // linear velocity of body + v = inverseMass * current.i.linearMomentum; + // linear velocity in gravity direction + gv = v * gravityNormal; + // linear velocity orthogonal to gravity direction + v -= gv * gravityNormal; + + // if too much velocity orthogonal to gravity direction + if ( v.Length() > STOP_SPEED ) { + return false; + } + // if too much velocity in gravity direction + if ( gv > 2.0f * STOP_SPEED || gv < -2.0f * STOP_SPEED ) { + return false; + } + + // calculate rotational velocity + inverseWorldInertiaTensor = current.i.orientation * inverseInertiaTensor * current.i.orientation.Transpose(); + av = inverseWorldInertiaTensor * current.i.angularMomentum; + + // if too much rotational velocity + if ( av.LengthSqr() > STOP_SPEED ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_RigidBody::DropToFloorAndRest + + Drops the object straight down to the floor and verifies if the object is at rest on the floor. +================ +*/ +void idPhysics_RigidBody::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + + testSolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.i.position + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.i.position, down, clipModel, current.i.orientation, clipMask, self ); +// RAVEN END + current.i.position = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, current.i.orientation ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + // test if we are really at rest + EvaluateContacts(); + if ( !TestIfAtRest() ) { + gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + } + Rest(); + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + } +} + +/* +================ +idPhysics_RigidBody::DebugDraw +================ +*/ +void idPhysics_RigidBody::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showMass.GetBool() ) { +// RAVEN BEGIN +// bdube: draw center of mass at the center of mass + gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.i.position + centerOfMass * current.i.orientation, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +// RAVEN END + } + + if ( rb_showInertia.GetBool() ) { + idMat3 &I = inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + current.i.position, 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showVelocity.GetBool() ) { + DrawVelocity( clipModel->GetId(), 0.1f, 4.0f ); + } + +// RAVEN BEGIN +// bdube: added more debug info + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::idPhysics_RigidBody( void ) { + + // set default rigid body properties + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f ); + SetFriction( 0.6f, 0.6f, 0.0f ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + + current.i.position.Zero(); + current.i.orientation.Identity(); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + + saved = current; + + mass = 1.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + inverseInertiaTensor.Identity(); + + // use the least expensive euler integrator + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), RigidBodyDerivatives, this ); + + dropToFloor = false; + noImpact = false; + noContact = false; + + hasMaster = false; + isOrientated = false; + +#ifdef RB_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_RigidBody::~idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::~idPhysics_RigidBody( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } + delete integrator; +} + +/* +================ +idPhysics_RigidBody_SavePState +================ +*/ +void idPhysics_RigidBody_SavePState( idSaveGame *savefile, const rigidBodyPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteFloat( state.lastTimeStep ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteMat3( state.localAxis ); + savefile->Write( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->WriteVec3( state.externalForce ); + savefile->WriteVec3( state.externalTorque ); + + savefile->WriteVec3( state.i.position ); + savefile->WriteMat3( state.i.orientation ); + savefile->WriteVec3( state.i.linearMomentum ); + savefile->WriteVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody_RestorePState +================ +*/ +void idPhysics_RigidBody_RestorePState( idRestoreGame *savefile, rigidBodyPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadFloat( state.lastTimeStep ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadMat3( state.localAxis ); + savefile->Read( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->ReadVec3( state.externalForce ); + savefile->ReadVec3( state.externalTorque ); + + savefile->ReadVec3( state.i.position ); + savefile->ReadMat3( state.i.orientation ); + savefile->ReadVec3( state.i.linearMomentum ); + savefile->ReadVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody::Save +================ +*/ +void idPhysics_RigidBody::Save( idSaveGame *savefile ) const { + + idPhysics_RigidBody_SavePState( savefile, current ); + idPhysics_RigidBody_SavePState( savefile, saved ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( inverseMass ); + savefile->WriteVec3( centerOfMass ); + savefile->WriteMat3( inertiaTensor ); + savefile->WriteMat3( inverseInertiaTensor ); + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + savefile->WriteBool( noImpact ); + savefile->WriteBool( noContact ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::Restore +================ +*/ +void idPhysics_RigidBody::Restore( idRestoreGame *savefile ) { + + idPhysics_RigidBody_RestorePState( savefile, current ); + idPhysics_RigidBody_RestorePState( savefile, saved ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( inverseMass ); + savefile->ReadVec3( centerOfMass ); + savefile->ReadMat3( inertiaTensor ); + savefile->ReadMat3( inverseInertiaTensor ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + savefile->ReadBool( noImpact ); + savefile->ReadBool( noContact ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::SetClipModel +================ +*/ +#define MAX_INERTIA_SCALE 10.0f + +void idPhysics_RigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + int minIndex; + idMat3 inertiaScale; + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.i.position, current.i.orientation ); +// RAVEN END + + // get mass properties from the trace model + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // check whether or not the clip model has valid mass properties + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: invalid mass for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // check whether or not the inertia tensor is balanced + minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] ); + inertiaScale.Identity(); + inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex]; + inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex]; + inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex]; + + if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) { + gameLocal.DWarning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + float min = inertiaTensor[minIndex][minIndex] * MAX_INERTIA_SCALE; + inertiaScale[(minIndex+1)%3][(minIndex+1)%3] = min / inertiaTensor[(minIndex+1)%3][(minIndex+1)%3]; + inertiaScale[(minIndex+2)%3][(minIndex+2)%3] = min / inertiaTensor[(minIndex+2)%3][(minIndex+2)%3]; + inertiaTensor *= inertiaScale; + } + + inverseMass = 1.0f / mass; + inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f ); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); +} + +/* +================ +idPhysics_RigidBody::GetClipModel +================ +*/ +idClipModel *idPhysics_RigidBody::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_RigidBody::GetNumClipModels +================ +*/ +int idPhysics_RigidBody::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_RigidBody::SetMass +================ +*/ +void idPhysics_RigidBody::SetMass( float mass, int id ) { + assert( mass > 0.0f ); + inertiaTensor *= mass / this->mass; + inverseInertiaTensor = inertiaTensor.Inverse() * (1.0f / 6.0f); + this->mass = mass; + inverseMass = 1.0f / mass; +} + +/* +================ +idPhysics_RigidBody::GetMass +================ +*/ +float idPhysics_RigidBody::GetMass( int id ) const { + return mass; +} + +// RAVEN BEGIN +// bdube: means of getting center of mass +/* +================ +idPhysics_RigidBody::GetCenterMass +================ +*/ +idVec3 idPhysics_RigidBody::GetCenterMass( int id ) const +{ + return (current.i.position + centerOfMass * current.i.orientation); +} +// RAVEN END + +/* +================ +idPhysics_RigidBody::SetFriction +================ +*/ +void idPhysics_RigidBody::SetFriction( const float linear, const float angular, const float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_RigidBody::SetBouncyness +================ +*/ +void idPhysics_RigidBody::SetBouncyness( const float b ) { + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +idPhysics_RigidBody::Rest +================ +*/ +void idPhysics_RigidBody::Rest( void ) { + current.atRest = gameLocal.time; + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::DropToFloor +================ +*/ +void idPhysics_RigidBody::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +idPhysics_RigidBody::NoContact +================ +*/ +void idPhysics_RigidBody::NoContact( void ) { + noContact = true; +} + +/* +================ +idPhysics_RigidBody::Activate +================ +*/ +void idPhysics_RigidBody::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_RigidBody::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_RigidBody::EnableImpact +================ +*/ +void idPhysics_RigidBody::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_RigidBody::DisableImpact +================ +*/ +void idPhysics_RigidBody::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_RigidBody::SetContents +================ +*/ +void idPhysics_RigidBody::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_RigidBody::GetContents +================ +*/ +int idPhysics_RigidBody::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_RigidBody::GetBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_RigidBody::GetAbsBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_RigidBody::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { + rigidBodyPState_t next; + idAngles angles; + trace_t collision; + idVec3 impulse; + idEntity *ent; + idVec3 oldOrigin, masterOrigin; + idMat3 oldAxis, masterAxis; + float timeStep; + bool collided, cameToRest = false; + + timeStep = MS2SEC( timeStepMSec ); + current.lastTimeStep = timeStep; + + if ( hasMaster ) { + oldOrigin = current.i.position; + oldAxis = current.i.orientation; + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.i.orientation = current.localAxis * masterAxis; + } + else { + current.i.orientation = current.localAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + current.i.linearMomentum = mass * ( ( current.i.position - oldOrigin ) / timeStep ); + current.i.angularMomentum = inertiaTensor * ( ( current.i.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep ); + current.externalForce.Zero(); + current.externalTorque.Zero(); + + return ( current.i.position != oldOrigin || current.i.orientation != oldAxis ); + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + current.externalForce.Zero(); + current.externalTorque.Zero(); + return true; + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Start(); + } +#endif + + // move the rigid body velocity into the frame of a pusher +// current.i.linearMomentum -= current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum -= current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + + clipModel->Unlink(); + + next = current; + + // calculate next position and orientation + Integrate( timeStep, next ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions from the current to the next state + collided = CheckForCollisions( timeStep, next, collision ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // set the new state + current = next; + +// trace_t pushResults; +// gameLocal.push.ClipPush( pushResults, self, PUSHFL_CRUSH | PUSHFL_CLIP, saved.localOrigin, saved.localAxis, current.localOrigin, current.localAxis ); + + if ( collided ) { + // apply collision impulse + if ( CollisionImpulse( collision, impulse ) ) { + current.atRest = gameLocal.time; + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + DebugDraw(); + + if ( !noContact ) { + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + // get contacts + EvaluateContacts(); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // check if the body has come to rest + if ( TestIfAtRest() ) { + // put to rest + Rest(); + cameToRest = true; + } else { + // apply contact friction + ContactFriction( timeStep ); + } + } + + if ( current.atRest < 0 ) { + ActivateContactEntities(); + } + + if ( collided ) { + // if the rigid body didn't come to rest or the other entity is not at rest + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ( !cameToRest || !ent->IsAtRest() ) ) { + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + } + } + + // move the rigid body velocity back into the world frame +// current.i.linearMomentum += current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum += current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + current.pushVelocity.Zero(); + + current.lastTimeStep = timeStep; + current.externalForce.Zero(); + current.externalTorque.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Stop(); + + if ( rb_showTimings->integer == 1 ) { + gameLocal.Printf( "%12s: t %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + lastTimerReset = 0; + } + else if ( rb_showTimings->integer == 2 ) { + numRigidBodies++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "rb %d: t %1.4f cd %1.4f\n", + numRigidBodies, + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + } + } + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numRigidBodies = 0; + timer_total.Clear(); + timer_collision.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_RigidBody::UpdateTime +================ +*/ +void idPhysics_RigidBody::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_RigidBody::GetTime +================ +*/ +int idPhysics_RigidBody::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_RigidBody::GetImpactInfo +================ +*/ +void idPhysics_RigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + idVec3 linearVelocity, angularVelocity; + idMat3 inverseWorldInertiaTensor; + + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + + info->invMass = inverseMass; + info->invInertiaTensor = inverseWorldInertiaTensor; + info->position = point - ( current.i.position + centerOfMass * current.i.orientation ); + info->velocity = linearVelocity + angularVelocity.Cross( info->position ); +} + +/* +================ +idPhysics_RigidBody::ApplyImpulse +================ +*/ +void idPhysics_RigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.i.linearMomentum += impulse; + current.i.angularMomentum += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::AddForce +================ +*/ +void idPhysics_RigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + current.externalForce += force; + current.externalTorque += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::IsAtRest +================ +*/ +bool idPhysics_RigidBody::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_RigidBody::GetRestStartTime +================ +*/ +int idPhysics_RigidBody::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_RigidBody::IsPushable +================ +*/ +bool idPhysics_RigidBody::IsPushable( void ) const { + return ( !noImpact && !hasMaster ); +} + +/* +================ +idPhysics_RigidBody::SaveState +================ +*/ +void idPhysics_RigidBody::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_RigidBody::RestoreState +================ +*/ +void idPhysics_RigidBody::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void idPhysics_RigidBody::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + newOrigin * masterAxis; + } + else { + current.i.position = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void idPhysics_RigidBody::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.orientation = newAxis * masterAxis; + } + else { + current.i.orientation = newAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), clipModel->GetOrigin(), current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Move +================ +*/ +void idPhysics_RigidBody::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.i.position += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Rotate +================ +*/ +void idPhysics_RigidBody::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.i.orientation *= rotation.ToMat3(); + current.i.position *= rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + current.localOrigin = current.i.position; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetOrigin +================ +*/ +const idVec3 &idPhysics_RigidBody::GetOrigin( int id ) const { + return current.i.position; +} + +/* +================ +idPhysics_RigidBody::GetAxis +================ +*/ +const idMat3 &idPhysics_RigidBody::GetAxis( int id ) const { + return current.i.orientation; +} + +/* +================ +idPhysics_RigidBody::SetLinearVelocity +================ +*/ +void idPhysics_RigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.i.linearMomentum = newLinearVelocity * mass; + Activate(); +} + +/* +================ +idPhysics_RigidBody::SetAngularVelocity +================ +*/ +void idPhysics_RigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + current.i.angularMomentum = newAngularVelocity * inertiaTensor; + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + curLinearVelocity = current.i.linearMomentum * inverseMass; + return curLinearVelocity; +} + +/* +================ +idPhysics_RigidBody::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + curAngularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + return curAngularVelocity; +} + +/* +================ +idPhysics_RigidBody::ClipTranslation +================ +*/ +void idPhysics_RigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipRotation +================ +*/ +void idPhysics_RigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipContents +================ +*/ +int idPhysics_RigidBody::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::DisableClip +================ +*/ +void idPhysics_RigidBody::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_RigidBody::EnableClip +================ +*/ +void idPhysics_RigidBody::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_RigidBody::UnlinkClip +================ +*/ +void idPhysics_RigidBody::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_RigidBody::LinkClip +================ +*/ +void idPhysics_RigidBody::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::EvaluateContacts +================ +*/ +bool idPhysics_RigidBody::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0) = current.i.linearMomentum + current.lastTimeStep * gravityVector * mass; + dir.SubVec3(1) = current.i.angularMomentum; + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_RigidBody::SetPushed +================ +*/ +void idPhysics_RigidBody::SetPushed( int deltaTime ) { + idRotation rotation; + + rotation = ( saved.i.orientation * current.i.orientation ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( current.i.position - saved.i.position ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_RigidBody::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_RigidBody::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_RigidBody::SetMaster +================ +*/ +void idPhysics_RigidBody::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.i.orientation * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + } + hasMaster = true; + isOrientated = orientated; + ClearContacts(); + } + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +const float RB_VELOCITY_MAX = 16000; +const int RB_VELOCITY_TOTAL_BITS = 16; +const int RB_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_VELOCITY_MAX ) ) + 1; +const int RB_VELOCITY_MANTISSA_BITS = RB_VELOCITY_TOTAL_BITS - 1 - RB_VELOCITY_EXPONENT_BITS; +const float RB_MOMENTUM_MAX = 1e20f; +const int RB_MOMENTUM_TOTAL_BITS = 16; +const int RB_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_MOMENTUM_MAX ) ) + 1; +const int RB_MOMENTUM_MANTISSA_BITS = RB_MOMENTUM_TOTAL_BITS - 1 - RB_MOMENTUM_EXPONENT_BITS; +const float RB_FORCE_MAX = 1e20f; +const int RB_FORCE_TOTAL_BITS = 16; +const int RB_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_FORCE_MAX ) ) + 1; +const int RB_FORCE_MANTISSA_BITS = RB_FORCE_TOTAL_BITS - 1 - RB_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_RigidBody::WriteToSnapshot +================ +*/ +void idPhysics_RigidBody::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.i.position[0] ); + msg.WriteFloat( current.i.position[1] ); + msg.WriteFloat( current.i.position[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( current.i.linearMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.i.position[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.i.position[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.i.position[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); +} + +/* +================ +idPhysics_RigidBody::ReadFromSnapshot +================ +*/ +void idPhysics_RigidBody::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.atRest = msg.ReadLong(); + current.i.position[0] = msg.ReadFloat(); + current.i.position[1] = msg.ReadFloat(); + current.i.position[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.i.linearMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.i.position[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.i.position[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.i.position[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + } +} diff --git a/source/game/physics/Physics_RigidBody.h b/source/game/physics/Physics_RigidBody.h new file mode 100644 index 0000000..24d7587 --- /dev/null +++ b/source/game/physics/Physics_RigidBody.h @@ -0,0 +1,172 @@ + +#ifndef __PHYSICS_RIGIDBODY_H__ +#define __PHYSICS_RIGIDBODY_H__ + +/* +=================================================================================== + + Rigid body physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct rididBodyIState_s { + idVec3 position; // position of trace model + idMat3 orientation; // orientation of trace model + idVec3 linearMomentum; // translational momentum relative to center of mass + idVec3 angularMomentum; // rotational momentum relative to center of mass +} rigidBodyIState_t; + +typedef struct rigidBodyPState_s { + int atRest; // set when simulation is suspended + float lastTimeStep; // length of last time step + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec6 pushVelocity; // push velocity + idVec3 externalForce; // external force relative to center of mass + idVec3 externalTorque; // external torque relative to center of mass + rigidBodyIState_t i; // state used for integration +} rigidBodyPState_t; + +class idPhysics_RigidBody : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( idPhysics_RigidBody ); + + idPhysics_RigidBody( void ); + ~idPhysics_RigidBody( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b ); + // same as above but drop to the floor first + void DropToFloor( void ); + // no contact determination and contact friction + void NoContact( void ); + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + // state of the rigid body + rigidBodyPState_t current; + rigidBodyPState_t saved; + +private: + + // rigid body properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + idClipModel * clipModel; // clip model used for collision detection + + // derived properties + float mass; // mass of body + float inverseMass; // 1 / mass + idVec3 centerOfMass; // center of mass of trace model + idMat3 inertiaTensor; // mass distribution + idMat3 inverseInertiaTensor; // inverse inertia tensor + + idODE * integrator; // integrator + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for solid when dropping to the floor + bool noImpact; // if true do not activate when another object collides + bool noContact; // if true do not determine contacts and no contact friction + + // master + bool hasMaster; + bool isOrientated; + +private: + friend void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); + void Integrate( const float deltaTime, rigidBodyPState_t &next ); + bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + bool CollisionImpulse( const trace_t &collision, idVec3 &impulse ); + void ContactFriction( float deltaTime ); + void DropToFloorAndRest( void ); + bool TestIfAtRest( void ) const; + void Rest( void ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_RIGIDBODY_H__ */ diff --git a/source/game/physics/Physics_Static.cpp b/source/game/physics/Physics_Static.cpp new file mode 100644 index 0000000..fdc3505 --- /dev/null +++ b/source/game/physics/Physics_Static.cpp @@ -0,0 +1,882 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Static ) +END_CLASS + +/* +================ +idPhysics_Static::idPhysics_Static +================ +*/ +idPhysics_Static::idPhysics_Static( void ) { + self = NULL; + clipModel = NULL; + current.origin.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAxis.Identity(); + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Static::~idPhysics_Static +================ +*/ +idPhysics_Static::~idPhysics_Static( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + if ( clipModel ) { + delete clipModel; + } +} + +/* +================ +idPhysics_Static::Save +================ +*/ +void idPhysics_Static::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + + savefile->WriteVec3( current.origin ); + savefile->WriteMat3( current.axis ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Static::Restore +================ +*/ +void idPhysics_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadVec3( current.origin ); + savefile->ReadMat3( current.axis ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Static::SetSelf +================ +*/ +void idPhysics_Static::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Static::SetClipModel +================ +*/ +void idPhysics_Static::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + assert( self ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetClipModel +================ +*/ +idClipModel *idPhysics_Static::GetClipModel( int id ) const { + if ( clipModel ) { + return clipModel; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_Static::GetNumClipModels +================ +*/ +int idPhysics_Static::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Static::SetMass +================ +*/ +void idPhysics_Static::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Static::GetMass +================ +*/ +float idPhysics_Static::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Static::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Static::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Static::SetContents +================ +*/ +void idPhysics_Static::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Static::GetContents +================ +*/ +int idPhysics_Static::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Static::SetClipMask +================ +*/ +void idPhysics_Static::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_Static::GetClipMask +================ +*/ +int idPhysics_Static::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetBounds +================ +*/ +const idBounds &idPhysics_Static::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return bounds_zero; +} + +/* +================ +idPhysics_Static::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Static::GetAbsBounds( int id ) const { + static idBounds absBounds; + + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + absBounds[0] = absBounds[1] = current.origin; + return absBounds; +} + +/* +================ +idPhysics_Static::Evaluate +================ +*/ +bool idPhysics_Static::Evaluate( int timeStepMSec, int endTimeMSec ) { +// RAVEN BEGIN +// bdube: draw bbox + if ( hasMaster ) { + idVec3 masterOrigin; + idVec3 oldOrigin; + idMat3 masterAxis; + idMat3 oldAxis; +// RAVEN END + + oldOrigin = current.origin; + oldAxis = current.axis; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.axis = current.localAxis * masterAxis; + } else { + current.axis = current.localAxis; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); + } + return false; +} + +/* +================ +idPhysics_Static::UpdateTime +================ +*/ +void idPhysics_Static::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Static::GetTime +================ +*/ +int idPhysics_Static::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetImpactInfo +================ +*/ +void idPhysics_Static::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Static::ApplyImpulse +================ +*/ +void idPhysics_Static::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Static::AddForce +================ +*/ +void idPhysics_Static::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Static::Activate +================ +*/ +void idPhysics_Static::Activate( void ) { +} + +/* +================ +idPhysics_Static::PutToRest +================ +*/ +void idPhysics_Static::PutToRest( void ) { +} + +/* +================ +idPhysics_Static::IsAtRest +================ +*/ +bool idPhysics_Static::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Static::GetRestStartTime +================ +*/ +int idPhysics_Static::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::IsPushable +================ +*/ +bool idPhysics_Static::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_Static::IsInWater +================ +*/ +bool idPhysics_Static::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Static::SaveState +================ +*/ +void idPhysics_Static::SaveState( void ) { +} + +/* +================ +idPhysics_Static::RestoreState +================ +*/ +void idPhysics_Static::RestoreState( void ) { +} + +/* +================ +idPhysics_Static::SetOrigin +================ +*/ +void idPhysics_Static::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } else { + current.origin = newOrigin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::SetAxis +================ +*/ +void idPhysics_Static::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } else { + current.axis = newAxis; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Translate +================ +*/ +void idPhysics_Static::Translate( const idVec3 &translation, int id ) { + current.localOrigin += translation; + current.origin += translation; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Rotate +================ +*/ +void idPhysics_Static::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + current.axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + current.localOrigin = current.origin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetOrigin +================ +*/ +const idVec3 &idPhysics_Static::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Static::GetAxis +================ +*/ +const idMat3 &idPhysics_Static::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Static::SetLinearVelocity +================ +*/ +void idPhysics_Static::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Static::SetAngularVelocity +================ +*/ +void idPhysics_Static::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Static::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetGravity +================ +*/ +void idPhysics_Static::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_Static::GetGravity +================ +*/ +const idVec3 &idPhysics_Static::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_Static::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Static::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_Static::ClipTranslation +================ +*/ +void idPhysics_Static::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Translation( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::ClipRotation +================ +*/ +void idPhysics_Static::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, current.origin, rotation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Rotation( self, results, current.origin, rotation, clipModel, current.axis, MASK_SOLID, self ); + } +// RAVEN END +} + +/* +================ +idPhysics_Static::ClipContents +================ +*/ +int idPhysics_Static::ClipContents( const idClipModel *model ) const { + if ( clipModel ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } + } + return 0; +} + +/* +================ +idPhysics_Static::DisableClip +================ +*/ +void idPhysics_Static::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Static::EnableClip +================ +*/ +void idPhysics_Static::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Static::UnlinkClip +================ +*/ +void idPhysics_Static::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Static::LinkClip +================ +*/ +void idPhysics_Static::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::EvaluateContacts +================ +*/ +bool idPhysics_Static::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Static::GetNumContacts +================ +*/ +int idPhysics_Static::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetContact +================ +*/ +const contactInfo_t &idPhysics_Static::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_Static::ClearContacts +================ +*/ +void idPhysics_Static::ClearContacts( void ) { +} + +/* +================ +idPhysics_Static::AddContactEntity +================ +*/ +void idPhysics_Static::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::RemoveContactEntity +================ +*/ +void idPhysics_Static::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::HasGroundContacts +================ +*/ +bool idPhysics_Static::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundEntity +================ +*/ +bool idPhysics_Static::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundClipModel +================ +*/ +bool idPhysics_Static::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_Static::SetPushed +================ +*/ +void idPhysics_Static::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Static::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetMaster +================ +*/ +void idPhysics_Static::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.axis * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Static::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Static::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Static::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetLinearEndTime +================ +*/ +int idPhysics_Static::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetAngularEndTime +================ +*/ +int idPhysics_Static::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::WriteToSnapshot +================ +*/ +void idPhysics_Static::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.axis.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Static::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current.axis = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); +} diff --git a/source/game/physics/Physics_Static.h b/source/game/physics/Physics_Static.h new file mode 100644 index 0000000..0f92daf --- /dev/null +++ b/source/game/physics/Physics_Static.h @@ -0,0 +1,155 @@ + +#ifndef __PHYSICS_STATIC_H__ +#define __PHYSICS_STATIC_H__ + +/* +=============================================================================== + + Physics for a non moving object using at most one collision model. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +class idStaticPState { + +public: + idVec3 origin; + idMat3 axis; + idVec3 localOrigin; + idMat3 localAxis; + + idStaticPState( void ) { } +}; +// RAVEN END + +class idPhysics_Static : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Static ); + + idPhysics_Static( void ); + ~idPhysics_Static( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idStaticPState current; // physics state +// RAVEN END + idClipModel * clipModel; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATIC_H__ */ diff --git a/source/game/physics/Physics_StaticMulti.cpp b/source/game/physics/Physics_StaticMulti.cpp new file mode 100644 index 0000000..7611b91 --- /dev/null +++ b/source/game/physics/Physics_StaticMulti.cpp @@ -0,0 +1,1088 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_StaticMulti ) +END_CLASS + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +idStaticPState defaultState; +// RAVEN END + +/* +================ +idPhysics_StaticMulti::idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::idPhysics_StaticMulti( void ) { + self = NULL; + hasMaster = false; + isOrientated = false; + + defaultState.origin.Zero(); + defaultState.axis.Identity(); + defaultState.localOrigin.Zero(); + defaultState.localAxis.Identity(); + + current.SetNum( 1 ); + current[0] = defaultState; + clipModels.SetNum( 1 ); + clipModels[0] = NULL; +} + +/* +================ +idPhysics_StaticMulti::~idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::~idPhysics_StaticMulti( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + for ( int i = 0; i < clipModels.Num(); i++ ) { + delete clipModels[i]; + } +} + +/* +================ +idPhysics_StaticMulti::Save +================ +*/ +void idPhysics_StaticMulti::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + + savefile->WriteInt(current.Num()); + for ( i = 0; i < current.Num(); i++ ) { + savefile->WriteVec3( current[i].origin ); + savefile->WriteMat3( current[i].axis ); + savefile->WriteVec3( current[i].localOrigin ); + savefile->WriteMat3( current[i].localAxis ); + } + + savefile->WriteInt( clipModels.Num() ); + for ( i = 0; i < clipModels.Num(); i++ ) { + savefile->WriteClipModel( clipModels[i] ); + } + + savefile->WriteBool(hasMaster); + savefile->WriteBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::Restore +================ +*/ +void idPhysics_StaticMulti::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadInt(num); + current.AssureSize( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadVec3( current[i].origin ); + savefile->ReadMat3( current[i].axis ); + savefile->ReadVec3( current[i].localOrigin ); + savefile->ReadMat3( current[i].localAxis ); + } + + savefile->ReadInt(num); + clipModels.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadClipModel( clipModels[i] ); + } + + savefile->ReadBool(hasMaster); + savefile->ReadBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::SetSelf +================ +*/ +void idPhysics_StaticMulti::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_StaticMulti::RemoveIndex +================ +*/ +void idPhysics_StaticMulti::RemoveIndex( int id, bool freeClipModel ) { + if ( id < 0 || id >= clipModels.Num() ) { + return; + } + if ( clipModels[id] && freeClipModel ) { + delete clipModels[id]; + clipModels[id] = NULL; + } + clipModels.RemoveIndex( id ); + current.RemoveIndex( id ); +} + +/* +================ +idPhysics_StaticMulti::SetClipModel +================ +*/ +void idPhysics_StaticMulti::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + int i; + + assert( self ); + + if ( id >= clipModels.Num() ) { + current.AssureSize( id+1, defaultState ); + clipModels.AssureSize( id+1, NULL ); + } + + if ( clipModels[id] && clipModels[id] != model && freeOld ) { + delete clipModels[id]; + } + clipModels[id] = model; + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + + for ( i = clipModels.Num() - 1; i >= 1; i-- ) { + if ( clipModels[i] ) { + break; + } + } + current.SetNum( i+1, false ); + clipModels.SetNum( i+1, false ); +} + +/* +================ +idPhysics_StaticMulti::GetClipModel +================ +*/ +idClipModel *idPhysics_StaticMulti::GetClipModel( int id ) const { + if ( id >= 0 && id < clipModels.Num() && clipModels[id] ) { + return clipModels[id]; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_StaticMulti::GetNumClipModels +================ +*/ +int idPhysics_StaticMulti::GetNumClipModels( void ) const { + return clipModels.Num(); +} + +/* +================ +idPhysics_StaticMulti::SetMass +================ +*/ +void idPhysics_StaticMulti::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetMass +================ +*/ +float idPhysics_StaticMulti::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_StaticMulti::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_StaticMulti::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SetContents +================ +*/ +void idPhysics_StaticMulti::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + clipModels[id]->SetContents( contents ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->SetContents( contents ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetContents +================ +*/ +int idPhysics_StaticMulti::GetContents( int id ) const { + int i, contents = 0; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + contents = clipModels[id]->GetContents(); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + contents |= clipModels[i]->GetContents(); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::SetClipMask +================ +*/ +void idPhysics_StaticMulti::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetClipMask +================ +*/ +int idPhysics_StaticMulti::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetBounds( int id ) const { + int i; + static idBounds bounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetBounds(); + } + } + if ( id == -1 ) { + bounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds[0] -= clipModels[i]->GetOrigin(); + bounds[1] -= clipModels[i]->GetOrigin(); + break; + } + } + return bounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::GetAbsBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetAbsBounds(); + } + } + if ( id == -1 ) { + absBounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + absBounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + return absBounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::Evaluate +================ +*/ +bool idPhysics_StaticMulti::Evaluate( int timeStepMSec, int endTimeMSec ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin = masterOrigin + current[i].localOrigin * masterAxis; + if ( isOrientated ) { + current[i].axis = current[i].localAxis * masterAxis; + } else { + current[i].axis = current[i].localAxis; + } + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + + // FIXME: return false if master did not move + return true; + } + return false; +} + +/* +================ +idPhysics_StaticMulti::UpdateTime +================ +*/ +void idPhysics_StaticMulti::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_StaticMulti::GetTime +================ +*/ +int idPhysics_StaticMulti::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetImpactInfo +================ +*/ +void idPhysics_StaticMulti::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_StaticMulti::ApplyImpulse +================ +*/ +void idPhysics_StaticMulti::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_StaticMulti::AddForce +================ +*/ +void idPhysics_StaticMulti::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_StaticMulti::Activate +================ +*/ +void idPhysics_StaticMulti::Activate( void ) { +} + +/* +================ +idPhysics_StaticMulti::PutToRest +================ +*/ +void idPhysics_StaticMulti::PutToRest( void ) { +} + +/* +================ +idPhysics_StaticMulti::IsAtRest +================ +*/ +bool idPhysics_StaticMulti::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_StaticMulti::GetRestStartTime +================ +*/ +int idPhysics_StaticMulti::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::IsPushable +================ +*/ +bool idPhysics_StaticMulti::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_StaticMulti::IsInWater +================ +*/ +bool idPhysics_StaticMulti::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SaveState +================ +*/ +void idPhysics_StaticMulti::SaveState( void ) { +} + +/* +================ +idPhysics_StaticMulti::RestoreState +================ +*/ +void idPhysics_StaticMulti::RestoreState( void ) { +} + +/* +================ +idPhysics_StaticMulti::SetOrigin +================ +*/ +void idPhysics_StaticMulti::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].origin = masterOrigin + newOrigin * masterAxis; + } else { + current[id].origin = newOrigin; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + Translate( masterOrigin + masterAxis * newOrigin - current[0].origin ); + } else { + Translate( newOrigin - current[0].origin ); + } + } +} + +/* +================ +idPhysics_StaticMulti::SetAxis +================ +*/ +void idPhysics_StaticMulti::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].axis = newAxis * masterAxis; + } else { + current[id].axis = newAxis; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + idMat3 axis; + idRotation rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + axis = current[0].axis.Transpose() * ( newAxis * masterAxis ); + } else { + axis = current[0].axis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( current[0].origin ); + + Rotate( rotation ); + } +} + +/* +================ +idPhysics_StaticMulti::Translate +================ +*/ +void idPhysics_StaticMulti::Translate( const idVec3 &translation, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin += translation; + current[id].origin += translation; + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin += translation; + current[i].origin += translation; + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::Rotate +================ +*/ +void idPhysics_StaticMulti::Rotate( const idRotation &rotation, int id ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].origin *= rotation; + current[id].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].localAxis *= rotation.ToMat3(); + current[id].localOrigin = ( current[id].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[id].localAxis = current[id].axis; + current[id].localOrigin = current[id].origin; + } + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin *= rotation; + current[i].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[i].localAxis *= rotation.ToMat3(); + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + current[i].localOrigin = current[i].origin; + } + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetOrigin +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetOrigin( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].origin; + } + if ( clipModels.Num() ) { + return current[0].origin; + } else { + return vec3_origin; + } +} + +/* +================ +idPhysics_StaticMulti::GetAxis +================ +*/ +const idMat3 &idPhysics_StaticMulti::GetAxis( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].axis; + } + if ( clipModels.Num() ) { + return current[0].axis; + } else { + return mat3_identity; + } +} + +/* +================ +idPhysics_StaticMulti::SetLinearVelocity +================ +*/ +void idPhysics_StaticMulti::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::SetAngularVelocity +================ +*/ +void idPhysics_StaticMulti::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetGravity +================ +*/ +void idPhysics_StaticMulti::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_StaticMulti::GetGravity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_StaticMulti::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::ClipTranslation +================ +*/ +void idPhysics_StaticMulti::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipTranslation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipRotation +================ +*/ +void idPhysics_StaticMulti::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipRotation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipContents +================ +*/ +int idPhysics_StaticMulti::ClipContents( const idClipModel *model ) const { + int i, contents; + + contents = 0; + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + contents |= gameLocal.Contents( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, NULL ); +// RAVEN END + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::DisableClip +================ +*/ +void idPhysics_StaticMulti::DisableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Disable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::EnableClip +================ +*/ +void idPhysics_StaticMulti::EnableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Enable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::UnlinkClip +================ +*/ +void idPhysics_StaticMulti::UnlinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Unlink(); + } + } +} + +/* +================ +idPhysics_StaticMulti::LinkClip +================ +*/ +void idPhysics_StaticMulti::LinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } +} + +/* +================ +idPhysics_StaticMulti::EvaluateContacts +================ +*/ +bool idPhysics_StaticMulti::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_StaticMulti::GetNumContacts +================ +*/ +int idPhysics_StaticMulti::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetContact +================ +*/ +const contactInfo_t &idPhysics_StaticMulti::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_StaticMulti::ClearContacts +================ +*/ +void idPhysics_StaticMulti::ClearContacts( void ) { +} + +/* +================ +idPhysics_StaticMulti::AddContactEntity +================ +*/ +void idPhysics_StaticMulti::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::RemoveContactEntity +================ +*/ +void idPhysics_StaticMulti::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::HasGroundContacts +================ +*/ +bool idPhysics_StaticMulti::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundEntity +================ +*/ +bool idPhysics_StaticMulti::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundClipModel +================ +*/ +bool idPhysics_StaticMulti::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SetPushed +================ +*/ +void idPhysics_StaticMulti::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_StaticMulti::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetMaster +================ +*/ +void idPhysics_StaticMulti::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current[i].localAxis = current[i].axis * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + } + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_StaticMulti::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_StaticMulti::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetBlockingEntity +================ +*/ +idEntity *idPhysics_StaticMulti::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetLinearEndTime +================ +*/ +int idPhysics_StaticMulti::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetAngularEndTime +================ +*/ +int idPhysics_StaticMulti::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::WriteToSnapshot +================ +*/ +void idPhysics_StaticMulti::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat, localQuat; + + // TODO: Check that this conditional write to delta message is OK + msg.WriteByte( current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + quat = current[i].axis.ToCQuat(); + localQuat = current[i].localAxis.ToCQuat(); + + msg.WriteFloat( current[i].origin[0] ); + msg.WriteFloat( current[i].origin[1] ); + msg.WriteFloat( current[i].origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current[i].origin[0], current[i].localOrigin[0] ); + msg.WriteDeltaFloat( current[i].origin[1], current[i].localOrigin[1] ); + msg.WriteDeltaFloat( current[i].origin[2], current[i].localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + } +} + +/* +================ +idPhysics_StaticMulti::ReadFromSnapshot +================ +*/ +void idPhysics_StaticMulti::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat, localQuat; + + num = msg.ReadByte(); + assert( num == current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + current[i].origin[0] = msg.ReadFloat(); + current[i].origin[1] = msg.ReadFloat(); + current[i].origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current[i].localOrigin[0] = msg.ReadDeltaFloat( current[i].origin[0] ); + current[i].localOrigin[1] = msg.ReadDeltaFloat( current[i].origin[1] ); + current[i].localOrigin[2] = msg.ReadDeltaFloat( current[i].origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current[i].axis = quat.ToMat3(); + current[i].localAxis = localQuat.ToMat3(); + } +} diff --git a/source/game/physics/Physics_StaticMulti.h b/source/game/physics/Physics_StaticMulti.h new file mode 100644 index 0000000..26a12fa --- /dev/null +++ b/source/game/physics/Physics_StaticMulti.h @@ -0,0 +1,143 @@ + +#ifndef __PHYSICS_STATICMULTI_H__ +#define __PHYSICS_STATICMULTI_H__ + +/* +=============================================================================== + + Physics for a non moving object using no or multiple collision models. + +=============================================================================== +*/ + +class idPhysics_StaticMulti : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_StaticMulti ); + + idPhysics_StaticMulti( void ); + ~idPhysics_StaticMulti( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void RemoveIndex( int id = 0, bool freeClipModel = true ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idList current; // physics state +// RAVEN END + idList clipModels; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATICMULTI_H__ */ diff --git a/source/game/physics/Physics_VehicleMonster.cpp b/source/game/physics/Physics_VehicleMonster.cpp new file mode 100644 index 0000000..df444fa --- /dev/null +++ b/source/game/physics/Physics_VehicleMonster.cpp @@ -0,0 +1,37 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, rvPhysics_VehicleMonster ) +END_CLASS + +/* +================ +rvPhysics_VehicleMonster::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_VehicleMonster::Evaluate( int timeStepMSec, int endTimeMSec ) { + if ( idPhysics_RigidBody::Evaluate( timeStepMSec, endTimeMSec ) ) { + + idAngles euler = current.i.orientation.ToAngles(); + euler.pitch = 0.0f; + euler.roll = 0.0f; + current.i.orientation = euler.ToMat3(); + + return true; + } + + return false; +} + +void rvPhysics_VehicleMonster::SetGravity ( const idVec3 & v ) { + gravityVector = v; + gravityNormal = gameLocal.GetGravity( ); + gravityNormal.Normalize(); +} diff --git a/source/game/physics/Physics_VehicleMonster.h b/source/game/physics/Physics_VehicleMonster.h new file mode 100644 index 0000000..a74a0ae --- /dev/null +++ b/source/game/physics/Physics_VehicleMonster.h @@ -0,0 +1,25 @@ + +#ifndef __PHYSICS_VEHICLE_MONSTER_H__ +#define __PHYSICS_VEHICLE_MONSTER_H__ + +/* +=================================================================================== + + Vehicle Monster Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + Extents particle physics with the ability to apply impulses. + +=================================================================================== +*/ + +class rvPhysics_VehicleMonster : public idPhysics_RigidBody { +public: + CLASS_PROTOTYPE( rvPhysics_VehicleMonster ); + + bool Evaluate ( int timeStepMSec, int endTimeMSec ); + void SetGravity ( const idVec3 & v ); +}; + +#endif /* !__PHYSICS_VEHICLE_MONSTER_H__ */ diff --git a/source/game/physics/Push.cpp b/source/game/physics/Push.cpp new file mode 100644 index 0000000..3908959 --- /dev/null +++ b/source/game/physics/Push.cpp @@ -0,0 +1,1503 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +// RAVEN BEGIN +// bdube: projectile +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif +// RAVEN END + +/* +============ +idPush::InitSavingPushedEntityPositions +============ +*/ +void idPush::InitSavingPushedEntityPositions( void ) { + numPushed = 0; +} + +/* +============ +idPush::SaveEntityPosition +============ +*/ +void idPush::SaveEntityPosition( idEntity *ent ) { + int i; + + // if already saved the physics state for this entity + for ( i = 0; i < numPushed; i++ ) { + if ( pushed[i].ent == ent ) { + return; + } + } + + // don't overflow + if ( numPushed >= MAX_GENTITIES ) { + gameLocal.Error( "more than MAX_GENTITIES pushed entities" ); + return; + } + + pushed[numPushed].ent = ent; + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // save the delta view angles + pushed[numPushed].deltaViewAngles = static_cast(ent)->GetDeltaViewAngles(); + } + + // save the physics state + ent->GetPhysics()->SaveState(); + + numPushed++; +} + +/* +============ +idPush::RestorePushedEntityPositions +============ +*/ +void idPush::RestorePushedEntityPositions( void ) { + int i; + + for ( i = 0; i < numPushed; i++ ) { + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( pushed[i].ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // set back the delta view angles + static_cast(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles ); + } + + // restore the physics state + pushed[i].ent->GetPhysics()->RestoreState(); + } +} + +/* +============ +idPush::RotateEntityToAxial +============ +*/ +bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) { + int i; + trace_t trace; + idRotation rotation; + idMat3 axis; + idPhysics *physics; + + physics = ent->GetPhysics(); + axis = physics->GetAxis(); + if ( !axis.IsRotated() ) { + return true; + } + +// RAVEN BEGIN +// abahr: caching current upVector to reduce visual effects of fp error + idVec3 upVector = -physics->GetGravityNormal(); +// RAVEN END + + // try to rotate the bbox back to axial with at most four rotations + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + axis = physics->GetAxis()[2].ToMat3( 2 ); +// RAVEN END + rotation = axis.ToRotation(); + rotation.Scale( -1 ); + rotation.SetOrigin( rotationPoint ); + // tiny float numbers in the clip axis, this can get the entity stuck + if ( rotation.GetAngle() == 0.0f ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // + ent->GetPhysics()->ClipRotation( trace, rotation, NULL ); + // if the full rotation is possible + if ( trace.fraction >= 1.0f ) { + // set bbox in final axial position + physics->SetOrigin( trace.endpos ); +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // if partial rotation was possible + else if ( trace.fraction > 0.0f ) { + // partial rotation + physics->SetOrigin( trace.endpos ); + physics->SetAxis( trace.endAxis ); + } + // next rotate around collision point + rotationPoint = trace.c.point; + } + return false; +} + +#ifdef NEW_PUSH + +/* +============ +idPush::CanPushEntity +============ +*/ +bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) { + + // if the physics object is not pushable + if ( !ent->GetPhysics()->IsPushable() ) { + return false; + } + + // if the entity doesn't clip with this pusher + if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + return false; + } + + // don't push players in noclip mode + if ( ent->client && ent->client->noclip ) { + return false; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) { + return false; + } + + // if we shouldn't push entities the original pusher rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) { + return false; + } + } + + return true; +} + +/* +============ +idPush::AddEntityToPushedGroup +============ +*/ +void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) { + int i, j; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + if ( fraction > pushedGroup[i].fraction ) { + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact &= groundContact; + pushedGroup[i].test = true; + } + return; + } + else if ( fraction > pushedGroup[i].fraction ) { + for ( j = pushedGroupSize; j > i; j-- ) { + pushedGroup[j] = pushedGroup[j-1]; + } + break; + } + } + + // put the entity in the group + pushedGroupSize++; + pushedGroup[i].ent = ent; + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact = groundContact; + pushedGroup[i].test = true; + + // remove any further occurances of the same entity in the group + for ( i++; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + for ( j = i+1; j < pushedGroupSize; j++ ) { + pushedGroup[j-1] = pushedGroup[j]; + } + pushedGroupSize--; + break; + } + } +} + +/* +============ +idPush::IsFullyPushed +============ +*/ +bool idPush::IsFullyPushed( idEntity *ent ) { + int i; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( pushedGroup[i].fraction < 1.0f ) { + return false; + } + if ( ent == pushedGroup[i].ent ) { + return true; + } + } + return false; +} + +/* +============ +idPush::ClipTranslationAgainstPusher +============ +*/ +bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForTranslation +============ +*/ +int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( this, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by translating the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idVec3 realTranslation, partialTranslation; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = pusher->GetPhysics()->GetAxis(); + memset( results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip translation + pusher->GetPhysics()->ClipTranslation( results, translation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realTranslation = results.fraction * translation; + } + else { + realTranslation = translation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 0; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // translate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // translate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + } + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationAgainstPusher +============ +*/ +bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForRotation +============ +*/ +int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by rotating the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idRotation realRotation, partialRotation; + idMat3 oldAxis; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = newAxis; + memset( results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip rotation + pusher->GetPhysics()->ClipRotation( results, rotation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realRotation = results.fraction * rotation; + } + else { + realRotation = rotation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipRotation( results, realRotation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // rotate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // rotate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + pusher->GetPhysics()->GetClipModel(i)->Enable(); + } + // rotate any actors back to axial + for ( i = 1; i < pushedGroupSize; i++ ) { + // if the entity is using actor physics + if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) { + // don't allow rotation if the bbox is no longer axial + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + } + } + + return totalMass; +} + +#else /* !NEW_PUSH */ + +enum { + PUSH_NO, // not pushed + PUSH_OK, // pushed ok + PUSH_BLOCKED // blocked +}; + +/* +============ +idPush::ClipEntityRotation +============ +*/ +void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipRotation( trace, rotation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::ClipEntityTranslation +============ +*/ +void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipTranslation( trace, translation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::TryRotatePushEntity +============ +*/ +#ifdef _DEBUG +// #define ROTATIONAL_PUSH_DEBUG +#endif + +int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + trace_t trace; + idVec3 rotationPoint; + idRotation newRotation; + float checkAngle; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef ROTATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate the entity colliding with all other entities except the pusher itself + ClipEntityRotation( trace, check, NULL, clipModel, rotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle() * trace.fraction; + // test if the entity can stay at it's partly pushed position by rotating + // the entity in reverse only colliding with pusher + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) ); + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle(); + } + // point to rotate entity bbox around back to axial + rotationPoint = physics->GetOrigin(); + } + else { + // rotate entity in reverse only colliding with pusher + newRotation = rotation; + newRotation.Scale( -1 ); + // + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { +#ifdef ROTATIONAL_PUSH_DEBUG + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return PUSH_NO; + } + // get point to rotate bbox around back to axial + rotationPoint = results.c.point; + // angle along which the entity will be pushed + checkAngle = rotation.GetAngle() * (1.0f - results.fraction); + // rotate the entity colliding with all other entities except the pusher itself + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + ClipEntityRotation( trace, check, NULL, clipModel, newRotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + + SaveEntityPosition( check ); + + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + // NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in + // release builds moving less floats than it should + static float shit = checkAngle; + + newRotation.RotatePoint( rotationPoint ); + + // rotate the entity + physics->Rotate( newRotation ); + + // set pusher into final position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity uses actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( check, rotationPoint ) ) { + // don't allow rotation if the bbox is no longer axial + return PUSH_BLOCKED; + } + } + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity is an actor using actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idActor::GetClassType() ) && physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // if the entity is standing ontop of the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate actor view + idActor *actor = static_cast(check); + idAngles delta = actor->GetDeltaViewAngles(); + delta.yaw += newRotation.ToMat3()[0].ToYaw(); + actor->SetDeltaViewAngles( delta ); + } + } + + return PUSH_OK; +} + +/* +============ +idPush::TryTranslatePushEntity +============ +*/ +#ifdef _DEBUG +// #define TRANSLATIONAL_PUSH_DEBUG +#endif + +int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ) { + trace_t trace; + idVec3 checkMove; + idVec3 oldOrigin; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, move ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // vector along which the entity is pushed + checkMove = move * trace.fraction; + // test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // vector along which the entity is pushed + checkMove = move; + } + } + else { + // move entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -move ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { + return PUSH_NO; + } + // vector along which the entity is pushed + checkMove = move * (1.0f - results.fraction); + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, checkMove ); + // if there is a collisions + if ( trace.fraction < 1.0f ) { + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // FIXME: try to push the blocking entity as well ? + // FIXME: handle sliding along more than one collision plane ? + // FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map" + +/* + oldOrigin = physics->GetOrigin(); + + // movement still remaining + checkMove *= (1.0f - trace.fraction); + + // project the movement along the collision plane + if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) { + return PUSH_BLOCKED; + } + checkMove *= 1.001f; + + // move entity from collision point along the collision plane + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, NULL, NULL, checkMove ); + + if ( trace.fraction < 1.0f ) { + physics->SetOrigin( oldOrigin ); + return PUSH_BLOCKED; + } + + checkMove = trace.endpos - oldOrigin; + + // move entity in reverse only colliding with pusher + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, clipModel, NULL, -move ); + + physics->SetOrigin( oldOrigin ); +*/ + if ( trace.fraction < 1.0f ) { + return PUSH_BLOCKED; + } + } + } + + SaveEntityPosition( check ); + + // translate the entity + physics->Translate( checkMove ); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + return PUSH_OK; +} + +/* +============ +idPush::DiscardEntities +============ +*/ +int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) { + int i, num; + idEntity *check; + + // remove all entities we cannot or should not push from the list + for ( num = i = 0; i < numEntities; i++ ) { + check = entityList[ i ]; + + // if the physics object is not pushable + if ( !check->GetPhysics()->IsPushable() ) { + continue; + } + + // if the entity doesn't clip with this pusher + if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + continue; + } + + // don't push players in noclip mode +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idPlayer::GetClassType() ) && static_cast(check)->noclip ) { +// RAVEN END + continue; + } + + // if we should only push idMoveable entities +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + // if we shouldn't push entities the clip model rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) { + continue; + } + } + + // keep entity in list + entityList[ num++ ] = entityList[i]; + } + + return num; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + dir = translation; + dir.Normalize(); + dir.z += 1.0f; + dir *= 10.0f; + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( pusher, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipMove = results.endpos - clipModel->GetOrigin(); + clipOrigin = results.endpos; + + } + else { + + clipMove = translation; + clipOrigin = newOrigin; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldOrigin = clipModel->GetOrigin(); + + // try to push the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the translated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() ); +// RAVEN END + + // wake up this object + if ( flags & PUSHFL_APPLYIMPULSE ) { + impulse = physics->GetMass() * dir; + } else { + impulse.Zero(); + } + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // if the entity is a moveable item and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idMoveableItem::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idRotation clipRotation; + idMat3 clipAxis, oldAxis; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Rotation( pusher, results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipRotation = rotation * results.fraction; + clipAxis = results.endAxis; + } + else { + + clipRotation = rotation; + clipAxis = newAxis; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldAxis = clipModel->GetAxis(); + + // try to push all the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the rotated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis ); +// RAVEN END + + // wake up this object + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +#endif /* !NEW_PUSH */ + + +/* +============ +idPush::ClipPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ) { + idVec3 translation; + idRotation rotation; + float mass; + + mass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // translational push + translation = newOrigin - oldOrigin; + + // if the pusher translates + if ( translation != vec3_origin ) { + + mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newOrigin = oldOrigin; + } + + // rotational push + rotation = ( oldAxis.Transpose() * newAxis ).ToRotation(); + rotation.SetOrigin( newOrigin ); + rotation.Normalize180(); + rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors + + // if the pusher rotates + if ( rotation.GetAngle() != 0.0f ) { + + // recalculate new axis to avoid floating point rounding problems + newAxis = oldAxis * rotation.ToMat3(); + newAxis.OrthoNormalizeSelf(); + newAxis.FixDenormals(); + newAxis.FixDegeneracies(); + + pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis ); + + mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newAxis = oldAxis; + } + + return mass; +} diff --git a/source/game/physics/Push.h b/source/game/physics/Push.h new file mode 100644 index 0000000..b303089 --- /dev/null +++ b/source/game/physics/Push.h @@ -0,0 +1,86 @@ + +#ifndef __PUSH_H__ +#define __PUSH_H__ + +/* +=============================================================================== + + Allows physics objects to be pushed geometrically. + +=============================================================================== +*/ + +#define PUSHFL_ONLYMOVEABLE 1 // only push moveable entities +#define PUSHFL_NOGROUNDENTITIES 2 // don't push entities the clip model rests upon +#define PUSHFL_CLIP 4 // also clip against all non-moveable entities +#define PUSHFL_CRUSH 8 // kill blocking entities +#define PUSHFL_APPLYIMPULSE 16 // apply impulse to pushed entities + +//#define NEW_PUSH + +class idPush { +public: + // Try to push other entities by moving the given entity. + // If results.fraction < 1.0 the move was blocked by results.c.entityNum + // Returns total mass of all pushed entities. + float ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + + float ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + + float ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ); + + // initialize saving the positions of entities being pushed + void InitSavingPushedEntityPositions( void ); + // move all pushed entities back to their previous position + void RestorePushedEntityPositions( void ); + // returns the number of pushed entities + int GetNumPushedEntities( void ) const { return numPushed; } + // get the ith pushed entity + idEntity * GetPushedEntity( int i ) const { assert( i >= 0 && i < numPushed ); return pushed[i].ent; } + +private: + struct pushed_s { + idEntity * ent; // pushed entity + idAngles deltaViewAngles; // actor delta view angles + } pushed[MAX_GENTITIES]; // pushed entities + int numPushed; // number of pushed entities + + struct pushedGroup_s { + idEntity * ent; + float fraction; + bool groundContact; + bool test; + } pushedGroup[MAX_GENTITIES]; + int pushedGroupSize; + +private: + void SaveEntityPosition( idEntity *ent ); + bool RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ); +#ifdef NEW_PUSH + bool CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ); + void AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ); + bool IsFullyPushed( idEntity *ent ); + bool ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ); + int GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ); + bool ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ); + int GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ); +#else + void ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idRotation &rotation ); + void ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idVec3 &translation ); + int TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + int TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + int DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ); +#endif +}; + +#endif /* !__PUSH_H__ */ diff --git a/source/game/script/ScriptFuncUtility.cpp b/source/game/script/ScriptFuncUtility.cpp new file mode 100644 index 0000000..15d5d2a --- /dev/null +++ b/source/game/script/ScriptFuncUtility.cpp @@ -0,0 +1,469 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility* sfu ) { + Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility& sfu ) { + Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const char* source ) { + Init( source ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const idCmdArgs& args ) { + Init( args ); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const char* source ) { + Clear(); + + if( !source || !source[0] ) { + return SFU_NOFUNC; + } + + idStr::Split( source, parms, ' ' ); + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const idCmdArgs& args ) { + Clear(); + + //Start at index 1 so we ignore 'call' + for( int ix = 1; ix < args.Argc(); ++ix ) { + InsertString( args.Argv(ix), ix ); + } + + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init() { + assert( parms.Num() ); + + func = FindFunction( GetParm(0) ); + if( func ) { + RemoveIndex( 0 );// remove Function name + returnKey.Clear(); + } else if( parms.Num() >= 2 ) { + returnKey = GetParm( 0 ); + RemoveIndex( 0 );// remove key name + func = FindFunction( GetParm(0) ); + RemoveIndex( 0 );// remove Function name + } else { + gameLocal.Warning( "Unable to find function %s in rvScriptFuncUtility::Init\n", parms[0].c_str() ); + } + + return func != NULL ? SFU_OK : SFU_ERROR; +} + +/* +================ +rvScriptFuncUtility::Clear +================ +*/ +void rvScriptFuncUtility::Clear() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::Save +================ +*/ +void rvScriptFuncUtility::Save( idSaveGame *savefile ) const { + bool validFunc = GetFunc() != NULL; + savefile->WriteBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->WriteString( GetReturnKey() ); + + savefile->WriteString( GetFunc()->Name() ); + + savefile->WriteInt( NumParms() ); + for( int ix = 0; ix < NumParms(); ++ix ) { + savefile->WriteString( func->Name() ); + } +} + +/* +================ +rvScriptFuncUtility::Restore +================ +*/ +void rvScriptFuncUtility::Restore( idRestoreGame *savefile ) { + idStr value; + idStr element; + int numParms = 0; + bool validFunc = false; + + savefile->ReadBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadInt( numParms ); + for( int ix = 0; ix < numParms; ++ix ) { + savefile->ReadString( element ); + value += element; + value += ' '; + } + + value.StripTrailing( ' ' ); + sfuReturnType status = Init(value.c_str()); + if ( status != SFU_OK ) { + assert( 0 ); + } +} + +/* +================ +rvScriptFuncUtility::NumParms +================ +*/ +int rvScriptFuncUtility::NumParms() const { + return (func && func->type) ? func->type->NumParameters() : 0; +} + +/* +================ +rvScriptFuncUtility::ReturnsAVal +================ +*/ +bool rvScriptFuncUtility::ReturnsAVal() const { + return GetReturnType() != &type_void; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +idTypeDef* rvScriptFuncUtility::GetParmType( int index ) const { + return (func && func->type) ? func->type->GetParmType( index ) : NULL; +} + +/* +================ +rvScriptFuncUtility::GetReturnType +================ +*/ +idTypeDef* rvScriptFuncUtility::GetReturnType() const { + return (func && func->type) ? func->type->ReturnType() : &type_void; +} + +/* +================ +rvScriptFuncUtility::SetFunction +================ +*/ +void rvScriptFuncUtility::SetFunction( const function_t* func ) { + this->func = func; +} + +/* +================ +rvScriptFuncUtility::SetParms +================ +*/ +void rvScriptFuncUtility::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +const char* rvScriptFuncUtility::GetParm( int index ) const { + return parms[ index ].c_str(); +} + +/* +================ +rvScriptFuncUtility::GetFuncName +================ +*/ +const char* rvScriptFuncUtility::GetFuncName() const { + if( !func ) { + return NULL; + } + + return func->Name(); +} + +/* +================ +rvScriptFuncUtility::InsertInt +================ +*/ +void rvScriptFuncUtility::InsertInt( int parm, int index ) { + InsertString( va("%d", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertFloat +================ +*/ +void rvScriptFuncUtility::InsertFloat( float parm, int index ) { + InsertString( va("%f", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertVec3 +================ +*/ +void rvScriptFuncUtility::InsertVec3( const idVec3& parm, int index ) { + InsertString( parm.ToString(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertEntity +================ +*/ +void rvScriptFuncUtility::InsertEntity( const idEntity* parm, int index ) { + assert( parm ); + + InsertString( parm->GetName(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertString +================ +*/ +void rvScriptFuncUtility::InsertString( const char* parm, int index ) { + assert( parm ); + + parms.Insert( parm, index ); +} + +/* +================ +rvScriptFuncUtility::InsertBool +================ +*/ +void rvScriptFuncUtility::InsertBool( bool parm, int index ) { + InsertInt( (int)parm, index ); +} + +/* +================ +rvScriptFuncUtility::RemoveIndex +================ +*/ +void rvScriptFuncUtility::RemoveIndex( int index ) { + parms.RemoveIndex( index ); +} + +/* +================ +rvScriptFuncUtility::FindFunction +================ +*/ +const function_t* rvScriptFuncUtility::FindFunction( const char* name ) const { + idTypeDef* type = gameLocal.program.FindType( name ); + if( type ) {//Find based on type + return gameLocal.program.FindFunction( name, type ); + } + + //Find based on scope + return gameLocal.program.FindFunction( name ); +} + +/* +================ +rvScriptFuncUtility::CallFunc +================ +*/ +void rvScriptFuncUtility::CallFunc( idDict* returnDict ) const { + idTypeDef* type = NULL; + + if( !Valid() ) { + return; + } + + idThread* thread = new idThread(); + if( !thread ) { + return; + } + + thread->ClearStack(); + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + type->PushOntoStack( thread, GetParm(ix) ); + } + + thread->CallFunction( func, false ); + + if( thread->Execute() && returnDict && ReturnsAVal() ) { + returnDict->Set( GetReturnKey(), GetReturnType()->GetReturnedValAsString(gameLocal.program) ); + } +} + +/* +================ +rvScriptFuncUtility::Valid +================ +*/ +bool rvScriptFuncUtility::Valid() const { +//#if _DEBUG + idTypeDef* type = NULL; + + if( !GetFunc() ) { + return false; + } + + if( GetFunc()->eventdef ) { + gameLocal.Warning( "Function, %s, is an event\n", GetFunc()->Name() ); + return false; + } + + // FIXME: designers call functions with no parms even though parms are pushed on stack + //if( NumParms() != parms.Num() ) { + // gameLocal.Warning( "Number of parms doesn't equal the num required by function %s\n", func->Name() ); + // return false; + //} + + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + if( !type->IsValid( GetParm(ix) ) ) { + gameLocal.Warning( "(Func: %s) Parm '%s' doesn't match expected type '%s'\n", GetFunc()->Name(), GetParm(ix), type->Name() ); + return false; + } + } + + if( returnKey.Length() && !ReturnsAVal() ) { + gameLocal.Warning( "Expecting return val from function %s which doesn't return one\n", func->Name() ); + return false; + } +//#endif + + return true; +} + +/* +================ +rvScriptFuncUtility::Assign +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::Assign( const rvScriptFuncUtility* sfu ) { + assert( sfu ); + func = sfu->func; + parms = sfu->parms; + returnKey = sfu->returnKey; + + return *this; +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility* sfu ) { + return Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility& sfu ) { + return Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::IsEqualTo +================ +*/ +bool rvScriptFuncUtility::IsEqualTo( const rvScriptFuncUtility* sfu ) const { + assert( sfu ); + return GetFunc() == sfu->GetFunc(); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility* sfu ) const { + return IsEqualTo( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility& sfu ) const { + return IsEqualTo( &sfu ); +} diff --git a/source/game/script/ScriptFuncUtility.h b/source/game/script/ScriptFuncUtility.h new file mode 100644 index 0000000..0e89ebe --- /dev/null +++ b/source/game/script/ScriptFuncUtility.h @@ -0,0 +1,71 @@ +#ifndef __RV_SCRIPT_FUNC_UTILITY_H +#define __RV_SCRIPT_FUNC_UTILITY_H + +class idThread; + +enum sfuReturnType { + SFU_NOFUNC = -1, + SFU_ERROR = 0, + SFU_OK = 1 +}; + +class rvScriptFuncUtility { +public: + rvScriptFuncUtility(); + explicit rvScriptFuncUtility( const rvScriptFuncUtility* sfu ); + explicit rvScriptFuncUtility( const rvScriptFuncUtility& sfu ); + explicit rvScriptFuncUtility( const char* source ); + explicit rvScriptFuncUtility( const idCmdArgs& args ); + + sfuReturnType Init( const char* source ); + sfuReturnType Init( const idCmdArgs& args ); + void Clear(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idTypeDef* GetParmType( int index ) const; + idTypeDef* GetReturnType() const ; + int NumParms() const; + bool ReturnsAVal() const; + + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ) { returnKey = key; } + + const char* GetFuncName() const; + const function_t* GetFunc() const { return func; } + const char* GetParm( int index ) const; + const char* GetReturnKey() const { return returnKey.c_str(); } + + void InsertInt( int parm, int index ); + void InsertFloat( float parm, int index ); + void InsertVec3( const idVec3& parm, int index ); + void InsertEntity( const idEntity* parm, int index ); + void InsertString( const char* parm, int index ); + void InsertBool( bool parm, int index ); + void RemoveIndex( int index ); + + const function_t* FindFunction( const char* name ) const; + void CallFunc( idDict* returnDict ) const; + + bool Valid() const; + + rvScriptFuncUtility& Assign( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility& sfu ); + + bool IsEqualTo( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility& sfu ) const; + +private: + sfuReturnType Init(); + +protected: + const function_t* func; + idList parms; + idStr returnKey; +}; + +#endif diff --git a/source/game/script/Script_Compiler.cpp b/source/game/script/Script_Compiler.cpp new file mode 100644 index 0000000..87b1389 --- /dev/null +++ b/source/game/script/Script_Compiler.cpp @@ -0,0 +1,2643 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define FUNCTION_PRIORITY 2 +#define INT_PRIORITY 2 +#define NOT_PRIORITY 5 +#define TILDE_PRIORITY 5 +#define TOP_PRIORITY 7 + +bool idCompiler::punctuationValid[ 256 ]; +char *idCompiler::punctuation[] = { + "+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", "::", ";", ",", + "~", "!", "*", "/", "%", "(", ")", "-", "+", + "=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL +}; + +opcode_t idCompiler::opcodes[] = { + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float }, + + { "~", "COMP_F", -1, false, &def_float, &def_void, &def_float }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + { "%", "MOD_F", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float }, + { "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float }, + { "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float }, + { "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float }, + { "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float }, + { ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector }, + { ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string }, + { ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity }, + { ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean }, + { ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object }, + + { ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer }, + + { ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void }, + { ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void }, + { ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean }, + { "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object }, + { "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object }, + { "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + { "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string }, + { "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string }, + { "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean }, + { "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float }, + + { "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float }, + { "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector }, + { "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string }, + { "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity }, + { "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field }, + { "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean }, + { "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object }, + { "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object }, + + { "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string }, + { "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string }, + { "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string }, + { "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean }, + { "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void }, + { "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float }, + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + + { "", "NEG_F", -1, false, &def_float, &def_void, &def_float }, + { "", "NEG_V", -1, false, &def_vector, &def_void, &def_vector }, + + { "int", "INT_F", -1, false, &def_float, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_jumpoffset, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_argsize, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_argsize, &def_void }, + { "", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_float, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_string, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void }, + { "", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void }, + { "", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void }, + { "", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void }, + { "", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void }, + { "", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void }, + { "", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void }, + + { "", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { "", "BREAK", -1, false, &def_float, &def_void, &def_void }, + { "", "CONTINUE", -1, false, &def_float, &def_void, &def_void }, + + { NULL } +}; + +/* +================ +idCompiler::idCompiler() +================ +*/ +idCompiler::idCompiler() { + char **ptr; + int id; + + // make sure we have the right # of opcodes in the table + assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) ); + + eof = true; + parserPtr = &parser; + + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + basetype = NULL; + currentLineNumber = 0; + currentFileNumber = 0; + errorCount = 0; + console = false; + scope = &def_namespace; + + memset( &immediate, 0, sizeof( immediate ) ); + memset( punctuationValid, 0, sizeof( punctuationValid ) ); + for( ptr = punctuation; *ptr != NULL; ptr++ ) { + id = parserPtr->GetPunctuationId( *ptr ); + if ( ( id >= 0 ) && ( id < 256 ) ) { + punctuationValid[ id ] = true; + } + } +} + +/* +============ +idCompiler::Error + +Aborts the current file load +============ +*/ +void idCompiler::Error( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + throw idCompileError( string ); +} + +/* +============ +idCompiler::Warning + +Prints a warning about the current line +============ +*/ +void idCompiler::Warning( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + parserPtr->Warning( "%s", string ); +} + +/* +============ +idCompiler::VirtualFunctionConstant + +Creates a def for an index into a virtual function table +============ +*/ +ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr ); + if ( eval._int < 0 ) { +// RAVEN BEGIN +// bdube: added scope to print + if( !func->scope || !func->scope->initialized ) { + Error( "Function '%s' not found in uninitialized scope. Make sure function is an object method.", func->Name() ); + } else { + Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() ); + } +// RAVEN END + } + + return GetImmediate( &type_virtualfunction, &eval, "" ); +} + +/* +============ +idCompiler::SizeConstant + +Creates a def for a size constant +============ +*/ +ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = size; + return GetImmediate( &type_argsize, &eval, "" ); +} + +/* +============ +idCompiler::JumpConstant + +Creates a def for a jump constant +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = value; + return GetImmediate( &type_jumpoffset, &eval, "" ); +} + +/* +============ +idCompiler::JumpDef + +Creates a def for a relative jump from one code location to another +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) { + return JumpConstant( jumpto - jumpfrom ); +} + +/* +============ +idCompiler::JumpTo + +Creates a def for a relative jump from current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) { + return JumpDef( gameLocal.program.NumStatements(), jumpto ); +} + +/* +============ +idCompiler::JumpFrom + +Creates a def for a relative jump from code location to current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) { + return JumpDef( jumpfrom, gameLocal.program.NumStatements() ); +} + +/* +============ +idCompiler::Divide +============ +*/ +ID_INLINE float idCompiler::Divide( float numerator, float denominator ) { + if ( denominator == 0 ) { + Error( "Divide by zero" ); + return 0; + } + + return numerator / denominator; +} + +/* +============ +idCompiler::FindImmediate + +tries to find an existing immediate with the same value +============ +*/ +idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const { + idVarDef *def; + etype_t etype; + + etype = type->Type(); + + // check for a constant with the same value + for( def = gameLocal.program.GetDefList( "" ); def != NULL; def = def->Next() ) { + if ( def->TypeDef() != type ) { + continue; + } + + switch( etype ) { + case ev_field : + if ( *def->value.intPtr == eval->_int ) { + return def; + } + break; + + case ev_argsize : + if ( def->value.argSize == eval->_int ) { + return def; + } + break; + + case ev_jumpoffset : + if ( def->value.jumpOffset == eval->_int ) { + return def; + } + break; + + case ev_entity : + if ( *def->value.intPtr == eval->entity ) { + return def; + } + break; + + case ev_string : + if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) { + return def; + } + break; + + case ev_float : + if ( *def->value.floatPtr == eval->_float ) { + return def; + } + break; + + case ev_virtualfunction : + if ( def->value.virtualFunction == eval->_int ) { + return def; + } + break; + + + case ev_vector : + if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) && + ( def->value.vectorPtr->y == eval->vector[ 1 ] ) && + ( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) { + return def; + } + break; + + default : + Error( "weird immediate type" ); + break; + } + } + + return NULL; +} + +/* +============ +idCompiler::GetImmediate + +returns an existing immediate with the same value, or allocates a new one +============ +*/ +idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) { + idVarDef *def; + + def = FindImmediate( type, eval, string ); + if ( def ) { + def->numUsers++; + } else { + // allocate a new def + def = gameLocal.program.AllocDef( type, "", &def_namespace, true ); + if ( type->Type() == ev_string ) { + def->SetString( string, true ); + } else { + def->SetValue( *eval, true ); + } + } + + return def; +} + +/* +============ +idCompiler::OptimizeOpcode + +try to optimize when the operator works on constants only +============ +*/ +idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + eval_t c; + idTypeDef *type; + + if ( var_a && var_a->initialized != idVarDef::initializedConstant ) { + return NULL; + } + if ( var_b && var_b->initialized != idVarDef::initializedConstant ) { + return NULL; + } + + idVec3 &vec_c = *reinterpret_cast( &c.vector[ 0 ] ); + + memset( &c, 0, sizeof( c ) ); + switch( op - opcodes ) { + case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break; + case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break; + case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break; + case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break; + case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break; + case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break; + case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break; + case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break; + case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break; + case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break; + case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break; + case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break; + case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break; + case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break; + case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break; + case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break; + case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break; + case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break; + case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break; + case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break; + case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break; + case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break; + case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break; + case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break; + case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break; + case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break; + case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break; + case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break; + case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break; + case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break; + case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break; + case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break; + default: type = NULL; break; + } + + if ( !type ) { + return NULL; + } + + if ( var_a ) { + var_a->numUsers--; + if ( var_a->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_a, NULL ); + } + } + if ( var_b ) { + var_b->numUsers--; + if ( var_b->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_b, NULL ); + } + } + + return GetImmediate( type, &c, "" ); +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + statement_t *statement; + idVarDef *var_c; + + var_c = OptimizeOpcode( op, var_a, var_b ); + if ( var_c ) { + return var_c; + } + + if ( var_a && !idStr::Cmp( var_a->Name(), RESULT_STRING ) ) { + var_a->numUsers++; + } + if ( var_b && !idStr::Cmp( var_b->Name(), RESULT_STRING ) ) { + var_b->numUsers++; + } + + statement = gameLocal.program.AllocStatement(); + statement->linenumber = currentLineNumber; + statement->file = currentFileNumber; + + if ( ( op->type_c == &def_void ) || op->rightAssociative ) { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + } else { + // allocate result space + // try to reuse result defs as much as possible + var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b ); + // set user count back to 1, a result def needs to be used twice before it can be reused + var_c->numUsers = 1; + } + + statement->op = op - opcodes; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + + if ( op->rightAssociative ) { + return var_a; + } + + return var_c; +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) { + return EmitOpcode( &opcodes[ op ], var_a, var_b ); +} + +/* +============ +idCompiler::EmitPush + +Emits an opcode to push the variable onto the stack. +============ +*/ +bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) { + opcode_t *op; + opcode_t *out; + + out = NULL; + for( op = &opcodes[ OP_PUSH_F ]; op->name && !idStr::Cmp( op->name, "" ); op++ ) { + if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) { + out = op; + break; + } + } + + if ( !out ) { + if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) { + return false; + } + + out = &opcodes[ OP_PUSH_ENT ]; + } + + EmitOpcode( out, expression, 0 ); + + return true; +} + +/* +============== +idCompiler::NextToken + +Sets token, immediateType, and possibly immediate +============== +*/ +void idCompiler::NextToken( void ) { + int i; + + // reset our type + immediateType = NULL; + memset( &immediate, 0, sizeof( immediate ) ); + + // Save the token's line number and filename since when we emit opcodes the current + // token is always the next one to be read + currentLineNumber = token.line; + currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() ); + + if ( !parserPtr->ReadToken( &token ) ) { + eof = true; + return; + } + + if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) { + if ( ( braceDepth > 0 ) && ( token != "}" ) ) { + // missing a closing brace. try to give as much info as possible. + if ( scope->Type() == ev_function ) { + Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_object ) { + Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_namespace ) { + Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() ); + } else { + Error( "Unexpected end of file inside braced section" ); + } + } + } + + switch( token.type ) { + case TT_STRING: + // handle quoted strings as a unit + immediateType = &type_string; + return; + + case TT_LITERAL: { + // handle quoted vectors as a unit + immediateType = &type_vector; + idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS ); + idToken token2; + for( i = 0; i < 3; i++ ) { + if ( !lex.ReadToken( &token2 ) ) { + Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() ); + } + if ( token2.type == TT_PUNCTUATION && token2 == "-" ) { + if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) { + Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() ); + } + immediate.vector[ i ] = -token2.GetFloatValue(); + } else if ( token2.type == TT_NUMBER ) { + immediate.vector[ i ] = token2.GetFloatValue(); + } else { + Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() ); + } + } + return; + } + + case TT_NUMBER: + immediateType = &type_float; + immediate._float = token.GetFloatValue(); + return; + + case TT_PUNCTUATION: + // entity names + if ( token == "$" ) { + immediateType = &type_entity; + parserPtr->ReadToken( &token ); + return; + } + + if ( token == "{" ) { + braceDepth++; + return; + } + + if ( token == "}" ) { + braceDepth--; + return; + } + + if ( punctuationValid[ token.subtype ] ) { + return; + } + + Error( "Unknown punctuation '%s'", token.c_str() ); + break; + + case TT_NAME: + return; + + default: + Error( "Unknown token '%s'", token.c_str() ); + } +} + +/* +============= +idCompiler::ExpectToken + +Issues an Error if the current token isn't equal to string +Gets the next token +============= +*/ +void idCompiler::ExpectToken( const char *string ) { + if ( token != string ) { + Error( "expected '%s', found '%s'", string, token.c_str() ); + } + + NextToken(); +} + +/* +============= +idCompiler::CheckToken + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool idCompiler::CheckToken( const char *string ) { + if ( token != string ) { + return false; + } + + NextToken(); + + return true; +} + +/* +============ +idCompiler::ParseName + +Checks to see if the current token is a valid name +============ +*/ +void idCompiler::ParseName( idStr &name ) { + if ( token.type != TT_NAME ) { + Error( "'%s' is not a name", token.c_str() ); + } + + name = token; + NextToken(); +} + +/* +============ +idCompiler::SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void idCompiler::SkipOutOfFunction( void ) { + while( braceDepth ) { + parserPtr->SkipBracedSection( false ); + braceDepth--; + } + NextToken(); +} + +/* +============ +idCompiler::SkipToSemicolon + +For error recovery +============ +*/ +void idCompiler::SkipToSemicolon( void ) { + do { + if ( CheckToken( ";" ) ) { + return; + } + + NextToken(); + } while( !eof ); +} + +/* +============ +idCompiler::CheckType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::CheckType( void ) { + idTypeDef *type; + + if ( token == "float" ) { + type = &type_float; + } else if ( token == "vector" ) { + type = &type_vector; + } else if ( token == "entity" ) { + type = &type_entity; + } else if ( token == "string" ) { + type = &type_string; + } else if ( token == "void" ) { + type = &type_void; + } else if ( token == "object" ) { + type = &type_object; + } else if ( token == "boolean" ) { + type = &type_boolean; + } else if ( token == "namespace" ) { + type = &type_namespace; + } else if ( token == "scriptEvent" ) { + type = &type_scriptevent; + } else { + type = gameLocal.program.FindType( token.c_str() ); + if ( type && !type->Inherits( &type_object ) ) { + type = NULL; + } + } + + return type; +} + +/* +============ +idCompiler::ParseType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::ParseType( void ) { + idTypeDef *type; + + type = CheckType(); + if ( !type ) { + Error( "\"%s\" is not a type", token.c_str() ); + } + + if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) { + Error( "scriptEvents can only defined in the global namespace" ); + } + + if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) { + Error( "A namespace may only be defined globally, or within another namespace" ); + } + + NextToken(); + + return type; +} + +/* +============ +idCompiler::ParseImmediate + +Looks for a preexisting constant +============ +*/ +idVarDef *idCompiler::ParseImmediate( void ) { + idVarDef *def; + + def = GetImmediate( immediateType, &immediate, token.c_str() ); + NextToken(); + + return def; +} + +/* +============ +idCompiler::EmitFunctionParms +============ +*/ +idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) { + idVarDef *e; + const idTypeDef *type; + const idTypeDef *funcArg; + idVarDef *returnDef; + idTypeDef *returnType; + int arg; + int size; + int resultOp; + + type = func->TypeDef(); + if ( func->Type() != ev_function ) { + Error( "'%s' is not a function", func->Name() ); + } + + // copy the parameters to the global parameter variables + arg = startarg; + size = startsize; + if ( !CheckToken( ")" ) ) { + do { + if ( arg >= type->NumParameters() ) { + Error( "too many parameters" ); + } + + e = GetExpression( TOP_PRIORITY ); + + funcArg = type->GetParmType( arg ); + if ( !EmitPush( e, funcArg ) ) { + Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() ); + } + + if ( funcArg->Type() == ev_object ) { + size += type_object.Size(); + } else { + size += funcArg->Size(); + } + + arg++; + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + if ( arg < type->NumParameters() ) { + Error( "too few parameters for function '%s'", func->Name() ); + } + + if ( op == OP_CALL ) { + EmitOpcode( op, func, 0 ); + } else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) { + EmitOpcode( op, object, VirtualFunctionConstant( func ) ); + + // need arg size seperate since script object may be NULL + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = SizeConstant( func->value.functionPtr->parmTotal ); + } else { + EmitOpcode( op, func, SizeConstant( size ) ); + } + + // we need to copy off the result into a temporary result location, so figure out the opcode + returnType = type->ReturnType(); + if ( returnType->Type() == ev_string ) { + resultOp = OP_STORE_S; + returnDef = gameLocal.program.returnStringDef; + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + returnDef = gameLocal.program.returnDef; + + switch( returnType->Type() ) { + case ev_void : + resultOp = OP_STORE_F; + break; + + case ev_boolean : + resultOp = OP_STORE_BOOL; + break; + + case ev_float : + resultOp = OP_STORE_F; + break; + + case ev_vector : + resultOp = OP_STORE_V; + break; + + case ev_entity : + resultOp = OP_STORE_ENT; + break; + + case ev_object : + resultOp = OP_STORE_OBJ; + break; + + default : + Error( "Invalid return type for function '%s'", func->Name() ); + // shut up compiler + resultOp = OP_STORE_OBJ; + break; + } + } + + if ( returnType->Type() == ev_void ) { + // don't need result space since there's no result, so just return the normal result def. + return returnDef; + } + + // allocate result space + // try to reuse result defs as much as possible + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b ); + // set user count back to 0, a result def needs to be used twice before it can be reused + resultDef->numUsers = 0; + + EmitOpcode( resultOp, returnDef, resultDef ); + + return resultDef; +} + +/* +============ +idCompiler::ParseFunctionCall +============ +*/ +idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) { + assert( funcDef ); + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->initialized == idVarDef::uninitialized ) { + Error( "Function '%s' has not been defined yet", funcDef->GlobalName() ); + } + + assert( funcDef->value.functionPtr ); + if ( callthread ) { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + Error( "Built-in functions cannot be called as threads" ); + } + callthread = false; + return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL ); + } else { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + if ( !thisdef ) { + Error( "No 'self' within scope" ); + } + + return ParseEventCall( thisdef, funcDef ); + } else { + Error( "Built-in functions cannot be called without an object" ); + } + } + + return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL ); + } +} + +/* +============ +idCompiler::ParseObjectCall +============ +*/ +idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) { + EmitPush( object, object->TypeDef() ); + if ( callthread ) { + callthread = false; + return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object ); + } else { + return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object ); + } +} + +/* +============ +idCompiler::ParseEventCall +============ +*/ +idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + if ( object->Type() == ev_object ) { + EmitPush( object, &type_entity ); + } else { + EmitPush( object, object->TypeDef() ); + } + + return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL ); +} + +/* +============ +idCompiler::ParseSysObjectCall +============ +*/ +idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !idThread::GetClassType().RespondsTo( *funcDef->value.functionPtr->eventdef ) ) { +// RAVEN END + Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() ); + } + + return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL ); +} + +/* +============ +idCompiler::LookupDef +============ +*/ +idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) { + idVarDef *def; + idVarDef *field; + etype_t type_b; + etype_t type_c; + opcode_t *op; + + // check if we're accessing a field + if ( baseobj && ( baseobj->Type() == ev_object ) ) { + const idVarDef *tdef; + + def = NULL; + for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = gameLocal.program.GetDef( NULL, name, tdef ); + if ( def ) { + break; + } + } + } else { + // first look through the defs in our scope + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + // if we're in a member function, check types local to the object + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + + field = LookupDef( name, scope->scope->TypeDef()->def ); + if ( !field ) { + Error( "Unknown value \"%s\"", name ); + } + + // type check + type_b = field->Type(); + if ( field->Type() == ev_function ) { + type_c = field->TypeDef()->ReturnType()->Type(); + } else { + type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field + if ( CheckToken( "++" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for ++" ); + } + def = EmitOpcode( OP_UINCP_F, thisdef, field ); + return def; + } else if ( CheckToken( "--" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for --" ); + } + def = EmitOpcode( OP_UDECP_F, thisdef, field ); + return def; + } + } + + op = &opcodes[ OP_INDIRECT_F ]; + while( ( op->type_a->Type() != ev_object ) + || ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) && + ( type_c != op->type_c->Type() ) ) { + // catches object calls that return a value + break; + } + op++; + if ( !op->name || idStr::Cmp( op->name, "." ) ) { + Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() ); + } + } + + if ( ( op - opcodes ) == OP_OBJECTCALL ) { + ExpectToken( "(" ); + def = ParseObjectCall( thisdef, field ); + } else { + // emit the conversion opcode + def = EmitOpcode( op, thisdef, field ); + + // field access gets type from field + def->SetTypeDef( field->TypeDef()->FieldType() ); + } + } + } + } + + return def; +} + +/* +============ +idCompiler::ParseValue + +Returns the def for the current token +============ +*/ +idVarDef *idCompiler::ParseValue( void ) { + idVarDef *def; + idVarDef *namespaceDef; + idStr name; + + if ( immediateType == &type_entity ) { + // if an immediate entity ($-prefaced name) then create or lookup a def for it. + // when entities are spawned, they'll lookup the def and point it to them. + def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace ); + if ( !def ) { + def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true ); + } + NextToken(); + return def; + } else if ( immediateType ) { + // if the token is an immediate, allocate a constant for it + return ParseImmediate(); + } + + ParseName( name ); + def = LookupDef( name, basetype ); + if ( !def ) { + if ( basetype ) { + Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + // if namespace, then look up the variable in that namespace + } else if ( def->Type() == ev_namespace ) { + while( def->Type() == ev_namespace ) { + ExpectToken( "::" ); + ParseName( name ); + namespaceDef = def; + def = gameLocal.program.GetDef( NULL, name, namespaceDef ); + if ( !def ) { + Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() ); + } + } + //def = LookupDef( name, basetype ); + } + + return def; +} + +/* +============ +idCompiler::GetTerm +============ +*/ +idVarDef *idCompiler::GetTerm( void ) { + idVarDef *e; + int op; + + if ( !immediateType && CheckToken( "~" ) ) { + e = GetExpression( TILDE_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_COMP_F; + break; + + default : + Error( "type mismatch for ~" ); + + // shut up compiler + op = OP_COMP_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + if ( !immediateType && CheckToken( "!" ) ) { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_boolean : + op = OP_NOT_BOOL; + break; + + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_function : + Error( "Invalid type for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + + case ev_object : + op = OP_NOT_ENT; + break; + + default : + Error( "type mismatch for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + // check for negation operator + if ( !immediateType && CheckToken( "-" ) ) { + // constants are directly negated without an instruction + if ( immediateType == &type_float ) { + immediate._float = -immediate._float; + return ParseImmediate(); + } else if ( immediateType == &type_vector ) { + immediate.vector[0] = -immediate.vector[0]; + immediate.vector[1] = -immediate.vector[1]; + immediate.vector[2] = -immediate.vector[2]; + return ParseImmediate(); + } else { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_NEG_F; + break; + + case ev_vector : + op = OP_NEG_V; + break; + default : + Error( "type mismatch for -" ); + + // shut up compiler + op = OP_NEG_F; + break; + } + return EmitOpcode( &opcodes[ op ], e, 0 ); + } + } + + if ( CheckToken( "int" ) ) { + ExpectToken( "(" ); + + e = GetExpression( INT_PRIORITY ); + if ( e->Type() != ev_float ) { + Error( "type mismatch for int()" ); + } + + ExpectToken( ")" ); + + return EmitOpcode( OP_INT_F, e, 0 ); + } + + if ( CheckToken( "thread" ) ) { + callthread = true; + e = GetExpression( FUNCTION_PRIORITY ); + + if ( callthread ) { + Error( "Invalid thread call" ); + } + + // threads return the thread number + gameLocal.program.returnDef->SetTypeDef( &type_float ); + return gameLocal.program.returnDef; + } + + if ( !immediateType && CheckToken( "(" ) ) { + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +idCompiler::TypeMatches +============== +*/ +bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const { + if ( type1 == type2 ) { + return true; + } + + //if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) { + // return true; + //} + + //if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) { + // return true; + //} + + return false; +} + +/* +============== +idCompiler::GetExpression +============== +*/ +idVarDef *idCompiler::GetExpression( int priority ) { + opcode_t *op; + opcode_t *oldop; + idVarDef *e; + idVarDef *e2; + const idVarDef *oldtype; + etype_t type_a; + etype_t type_b; + etype_t type_c; + + if ( priority == 0 ) { + return GetTerm(); + } + + e = GetExpression( priority - 1 ); + if ( token == ";" ) { + // save us from searching through the opcodes unneccesarily + return e; + } + + while( 1 ) { + if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) { + return ParseFunctionCall( e ); + } + + // has to be a punctuation + if ( immediateType ) { + break; + } + + for( op = opcodes; op->name; op++ ) { + if ( ( op->priority == priority ) && CheckToken( op->name ) ) { + break; + } + } + + if ( !op->name ) { + // next token isn't at this priority level + break; + } + + // unary operators act only on the left operand + if ( op->type_b == &def_void ) { + e = EmitOpcode( op, e, 0 ); + return e; + } + + // preserve our base type + oldtype = basetype; + + // field access needs scope from object + if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) { + // save off what type this field is part of + basetype = e->TypeDef()->def; + } + + if ( op->rightAssociative ) { + // if last statement is an indirect, change it to an address of + if ( gameLocal.program.NumStatements() > 0 ) { + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) { + statement.op = OP_ADDRESS; + type_pointer.SetPointerType( e->TypeDef() ); + e->SetTypeDef( &type_pointer ); + } + } + + e2 = GetExpression( priority ); + } else { + e2 = GetExpression( priority - 1 ); + } + + // restore type + basetype = oldtype; + + // type check + type_a = e->Type(); + type_b = e2->Type(); + + // field access gets type from field + if ( op->name[ 0 ] == '.' ) { + if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) { + type_c = e2->TypeDef()->ReturnType()->Type(); + } else if ( e2->TypeDef()->FieldType() ) { + type_c = e2->TypeDef()->FieldType()->Type(); + } else { + // not a field + type_c = ev_error; + } + } else { + type_c = ev_void; + } + + oldop = op; + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) || + ( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) { + break; + } + + op++; + if ( !op->name || idStr::Cmp( op->name, oldop->name ) ) { + Error( "type mismatch for '%s'", oldop->name ); + } + } + + switch( op - opcodes ) { + case OP_SYSCALL : + ExpectToken( "(" ); + e = ParseSysObjectCall( e2 ); + break; + + case OP_OBJECTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + case OP_EVENTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + default: + if ( callthread ) { + Error( "Expecting function call after 'thread'" ); + } + + if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) { + // FIXME: need to make a general case for this + if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) { + // copy from float to boolean pointer + op = &opcodes[ OP_STOREP_FTOBOOL ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) { + // copy from boolean to float pointer + op = &opcodes[ OP_STOREP_BOOLTOF ]; + } else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from float to string pointer + op = &opcodes[ OP_STOREP_FTOS ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from boolean to string pointer + op = &opcodes[ OP_STOREP_BTOS ]; + } else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from vector to string pointer + op = &opcodes[ OP_STOREP_VTOS ]; + } else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) { + // store an entity into an object pointer + op = &opcodes[ OP_STOREP_OBJENT ]; + } else { + Error( "type mismatch for '%s'", op->name ); + } + } + + if ( op->rightAssociative ) { + e = EmitOpcode( op, e2, e ); + } else { + e = EmitOpcode( op, e, e2 ); + } + + if ( op - opcodes == OP_STOREP_OBJENT ) { + // statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = type_pointer.PointerType()->def; + } + + // field access gets type from field + if ( type_c != ev_void ) { + e->SetTypeDef( e2->TypeDef()->FieldType() ); + } + break; + } + } + + return e; +} + +/* +================ +idCompiler::PatchLoop +================ +*/ +void idCompiler::PatchLoop( int start, int continuePos ) { + int i; + statement_t *pos; + + pos = &gameLocal.program.GetStatement( start ); + for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_BREAK ) { + pos->op = OP_GOTO; + pos->a = JumpFrom( i ); + } else if ( pos->op == OP_CONTINUE ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, continuePos ); + } + } +} + +/* +================ +idCompiler::ParseReturnStatement +================ +*/ +void idCompiler::ParseReturnStatement( void ) { + idVarDef *e; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( CheckToken( ";" ) ) { + if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) { + Error( "expecting return value" ); + } + + EmitOpcode( OP_RETURN, 0, 0 ); + return; + } + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + type_a = e->Type(); + type_b = scope->TypeDef()->ReturnType()->Type(); + + if ( TypeMatches( type_a, type_b ) ) { + EmitOpcode( OP_RETURN, e, 0 ); + return; + } + + for( op = opcodes; op->name; op++ ) { + if ( !idStr::Cmp( op->name, "=" ) ) { + break; + } + } + + assert( op->name ); + + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) { + op++; + if ( !op->name || idStr::Cmp( op->name, "=" ) ) { + Error( "type mismatch for return value" ); + } + } + + idTypeDef *returnType = scope->TypeDef()->ReturnType(); + if ( returnType->Type() == ev_string ) { + EmitOpcode( op, e, gameLocal.program.returnStringDef ); + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + EmitOpcode( op, e, gameLocal.program.returnDef ); + } + EmitOpcode( OP_RETURN, 0, 0 ); +} + +/* +================ +idCompiler::ParseWhileStatement +================ +*/ +void idCompiler::ParseWhileStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + loopDepth++; + + ExpectToken( "(" ); + + patch2 = gameLocal.program.NumStatements(); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) { + //FIXME: we can completely skip generation of this code in the opposite case + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + } else { + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } + + // fixup breaks and continues + PatchLoop( patch2, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseForStatement + +Form of for statement with a counter: + + a = 0; +start: << patch4 + if ( !( a < 10 ) ) { + goto end; << patch1 + } else { + goto process; << patch3 + } + +increment: << patch2 + a = a + 1; + goto start; << goto patch4 + +process: + statements; + goto increment; << goto patch2 + +end: + +Form of for statement without a counter: + + a = 0; +start: << patch2 + if ( !( a < 10 ) ) { + goto end; << patch1 + } + +process: + statements; + goto start; << goto patch2 + +end: +================ +*/ +void idCompiler::ParseForStatement( void ) { + idVarDef *e; + int start; + int patch1; + int patch2; + int patch3; + int patch4; + + loopDepth++; + + start = gameLocal.program.NumStatements(); + + ExpectToken( "(" ); + + // init + if ( !CheckToken( ";" ) ) { + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ";" ); + } + + // condition + patch2 = gameLocal.program.NumStatements(); + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + // counter + if ( !CheckToken( ")" ) ) { + patch3 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IF, e, 0 ); + + patch4 = patch2; + patch2 = gameLocal.program.NumStatements(); + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + + // goto patch4 + EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 ); + + // fixup patch3 + gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 ); + } + + ParseStatement(); + + // goto patch2 + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + + // fixup patch1 + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + + // fixup breaks and continues + PatchLoop( start, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseDoWhileStatement +================ +*/ +void idCompiler::ParseDoWhileStatement( void ) { + idVarDef *e; + int patch1; + + loopDepth++; + + patch1 = gameLocal.program.NumStatements(); + ParseStatement(); + ExpectToken( "while" ); + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + ExpectToken( ";" ); + + EmitOpcode( OP_IF, e, JumpTo( patch1 ) ); + + // fixup breaks and continues + PatchLoop( patch1, patch1 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseIfStatement +================ +*/ +void idCompiler::ParseIfStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + ParseStatement(); + + if ( CheckToken( "else" ) ) { + patch2 = gameLocal.program.NumStatements(); + EmitOpcode( OP_GOTO, 0, 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + ParseStatement(); + gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 ); + } else { + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } +} + +/* +============ +idCompiler::ParseStatement +============ +*/ +void idCompiler::ParseStatement( void ) { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + if ( CheckToken( "{" ) ) { + do { + ParseStatement(); + } while( !CheckToken( "}" ) ); + + return; + } + + if ( CheckToken( "return" ) ) { + ParseReturnStatement(); + return; + } + + if ( CheckToken( "while" ) ) { + ParseWhileStatement(); + return; + } + + if ( CheckToken( "for" ) ) { + ParseForStatement(); + return; + } + + if ( CheckToken( "do" ) ) { + ParseDoWhileStatement(); + return; + } + + if ( CheckToken( "break" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot break outside of a loop" ); + } + EmitOpcode( OP_BREAK, 0, 0 ); + return; + } + + if ( CheckToken( "continue" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot contine outside of a loop" ); + } + EmitOpcode( OP_CONTINUE, 0, 0 ); + return; + } + + if ( CheckType() != NULL ) { + ParseDefs(); + return; + } + + if ( CheckToken( "if" ) ) { + ParseIfStatement(); + return; + } + + GetExpression( TOP_PRIORITY ); + ExpectToken(";"); +} + +/* +================ +idCompiler::ParseObjectDef +================ +*/ +void idCompiler::ParseObjectDef( const char *objname ) { + idTypeDef *objtype; + idTypeDef *type; + idTypeDef *parentType; + idTypeDef *fieldtype; + idStr name; + const char *fieldname; + idTypeDef newtype( ev_field, NULL, "", 0, NULL ); + idVarDef *oldscope; + int num; + int i; + + oldscope = scope; + if ( scope->Type() != ev_namespace ) { + Error( "Objects cannot be defined within functions or other objects" ); + } + + // make sure it doesn't exist before we create it + if ( gameLocal.program.FindType( objname ) != NULL ) { + Error( "'%s' : redefinition; different basic types", objname ); + } + + // base type + if ( !CheckToken( ":" ) ) { + parentType = &type_object; + } else { + parentType = ParseType(); + if ( !parentType->Inherits( &type_object ) ) { + Error( "Objects may only inherit from objects." ); + } + } + + objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType ); + objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true ); + scope = objtype->def; + + // inherit all the functions + num = parentType->NumFunctions(); + for( i = 0; i < parentType->NumFunctions(); i++ ) { + const function_t *func = parentType->GetFunction( i ); + objtype->AddFunction( func ); + } + + ExpectToken( "{" ); + + do { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + continue; + } + + fieldtype = ParseType(); + newtype.SetFieldType( fieldtype ); + + fieldname = va( "%s field", fieldtype->Name() ); + newtype.SetName( fieldname ); + + ParseName( name ); + + // check for a function prototype or declaraction + if ( CheckToken( "(" ) ) { + ParseFunctionDef( newtype.FieldType(), name ); + } else { + type = gameLocal.program.GetType( newtype, true ); + assert( !type->def ); + gameLocal.program.AllocDef( type, name, scope, true ); + objtype->AddField( type, name ); + ExpectToken( ";" ); + } + } while( !CheckToken( "}" ) ); + + scope = oldscope; + + ExpectToken( ";" ); +} + +/* +============ +idCompiler::ParseFunction + +parse a function type +============ +*/ +idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) { + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + idTypeDef *type; + + if ( scope->Type() != ev_namespace ) { + // create self pointer + newtype.AddFunctionParm( scope->TypeDef(), "self" ); + } + + if ( !CheckToken( ")" ) ) { + idStr parmName; + do { + type = ParseType(); + ParseName( parmName ); + newtype.AddFunctionParm( type, parmName ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + return gameLocal.program.GetType( newtype, true ); +} + +/* +================ +idCompiler::ParseFunctionDef +================ +*/ +void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) { + idTypeDef *type; + idVarDef *def; + const idVarDef *parm; + idVarDef *oldscope; + int i; + int numParms; + const idTypeDef *parmType; + function_t *func; + statement_t *pos; + + if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) { + Error( "Functions may not be defined within other functions" ); + } + + type = ParseFunction( returnType, name ); + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + type->def = def; + + func = &gameLocal.program.AllocFunction( def ); + if ( scope->TypeDef()->Inherits( &type_object ) ) { + scope->TypeDef()->AddFunction( func ); + } + } else { + func = def->value.functionPtr; + assert( func ); + if ( func->firstStatement ) { + Error( "%s redeclared", def->GlobalName() ); + } + } + +// RAVEN BEGIN +// abahr: moved to below parm size calc as per Jim D +// RAVEN END + + // calculate stack space used by parms + numParms = type->NumParameters(); +// RAVEN BEGIN +// abahr + func->parmTotal = 0; + func->parmSize.Clear(); +// RAVEN END + func->parmSize.SetNum( numParms ); + for( i = 0; i < numParms; i++ ) { + parmType = type->GetParmType( i ); + if ( parmType->Inherits( &type_object ) ) { + func->parmSize[ i ] = type_object.Size(); + } else { + func->parmSize[ i ] = parmType->Size(); + } + func->parmTotal += func->parmSize[ i ]; + } + +// RAVEN BEGIN +// abahr: moved here so prototypes calculate parm sizes + // check if this is a prototype or declaration + if ( !CheckToken( "{" ) ) { + // it's just a prototype, so get the ; and move on + ExpectToken( ";" ); + return; + } +// RAVEN END + + // define the parms + for( i = 0; i < numParms; i++ ) { + if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) { + Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) ); + } + parm = gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false ); + } + + oldscope = scope; + scope = def; + + func->firstStatement = gameLocal.program.NumStatements(); + + // check if we should call the super class constructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "init" ) ) { + idTypeDef *superClass; + function_t *constructorFunc = NULL; + + // find the superclass constructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) ); + if ( constructorFunc ) { + break; + } + } + + // emit the call to the constructor + if ( constructorFunc ) { + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 ); + } + } + + // parse regular statements + while( !CheckToken( "}" ) ) { + ParseStatement(); + } + + // check if we should call the super class destructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "destroy" ) ) { + idTypeDef *superClass; + function_t *destructorFunc = NULL; + + // find the superclass destructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) ); + if ( destructorFunc ) { + break; + } + } + + if ( destructorFunc ) { + // change all returns to point to the call to the destructor + pos = &gameLocal.program.GetStatement( func->firstStatement ); + for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_RETURN ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, gameLocal.program.NumStatements() ); + } + } + + // emit the call to the destructor + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 ); + } + } + +// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }" +#if 0 + // don't bother adding a return opcode if the "return" statement was used. + if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) { + // emit an end of statements opcode + EmitOpcode( OP_RETURN, 0, 0 ); + } +#else + // always emit the return opcode + EmitOpcode( OP_RETURN, 0, 0 ); +#endif + + // record the number of statements in the function + func->numStatements = gameLocal.program.NumStatements() - func->firstStatement; + + scope = oldscope; +} + +/* +================ +idCompiler::ParseVariableDef +================ +*/ +void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) { + idVarDef *def, *def2; + bool negate; + + def = gameLocal.program.GetDef( type, name, scope ); + if ( def ) { + Error( "%s redeclared", name ); + } + + def = gameLocal.program.AllocDef( type, name, scope, false ); + + // check for an initialization + if ( CheckToken( "=" ) ) { + // if a local variable in a function then write out interpreter code to initialize variable + if ( scope->Type() == ev_function ) { + def2 = GetExpression( TOP_PRIORITY ); + if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_F, def2, def ); + } else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_V, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) { + EmitOpcode( OP_STORE_S, def2, def ); + } else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) { + EmitOpcode( OP_STORE_ENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) { + EmitOpcode( OP_STORE_OBJENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) { + EmitOpcode( OP_STORE_OBJ, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOL, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_VTOS, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOBOOL, def2, def ); + } else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOLTOF, def2, def ); + } else { + Error( "bad initialization for '%s'", name ); + } + } else { + // global variables can only be initialized with immediate values + negate = false; + if ( token.type == TT_PUNCTUATION && token == "-" ) { + negate = true; + NextToken(); + if ( immediateType != &type_float ) { + Error( "wrong immediate type for '-' on variable '%s'", name ); + } + } + + if ( immediateType != type ) { + Error( "wrong immediate type for '%s'", name ); + } + + // global variables are initialized at start up + if ( type == &type_string ) { + def->SetString( token, false ); + } else { + if ( negate ) { + immediate._float = -immediate._float; + } + def->SetValue( immediate, false ); + } + NextToken(); + } + } else if ( type == &type_string ) { + // local strings on the stack are initialized in the interpreter + if ( scope->Type() != ev_function ) { + def->SetString( "", false ); + } + } else if ( type->Inherits( &type_object ) ) { + if ( scope->Type() != ev_function ) { + def->SetObject( NULL ); + } + } +} + +/* +================ +idCompiler::GetTypeForEventArg +================ +*/ +idTypeDef *idCompiler::GetTypeForEventArg( char argType ) { + idTypeDef *type; + + switch( argType ) { + case D_EVENT_INTEGER : + // this will get converted to int by the interpreter + type = &type_float; + break; + + case D_EVENT_FLOAT : + type = &type_float; + break; + + case D_EVENT_VECTOR : + type = &type_vector; + break; + + case D_EVENT_STRING : + type = &type_string; + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + type = &type_entity; + break; + + case D_EVENT_VOID : + type = &type_void; + break; + + case D_EVENT_TRACE : + // This data type isn't available from script + type = NULL; + break; + + default: + // probably a typo + type = NULL; + break; + } + + return type; +} + +/* +================ +idCompiler::ParseEventDef +================ +*/ +void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) { + const idTypeDef *expectedType; + idTypeDef *argType; + idTypeDef *type; + int i; + int num; + const char *format; + const idEventDef *ev; + idStr parmName; + + ev = idEventDef::FindEvent( name ); + if ( !ev ) { + Error( "Unknown event '%s'", name ); + } + + // set the return type + expectedType = GetTypeForEventArg( ev->GetReturnType() ); + if ( !expectedType ) { + Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name ); + } + if ( returnType != expectedType ) { + Error( "Return type doesn't match internal return type '%s'", expectedType->Name() ); + } + + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + + ExpectToken( "(" ); + + format = ev->GetArgFormat(); + num = strlen( format ); + for( i = 0; i < num; i++ ) { + expectedType = GetTypeForEventArg( format[ i ] ); + if ( !expectedType || ( expectedType == &type_void ) ) { + Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name ); + } + + argType = ParseType(); + ParseName( parmName ); + if ( argType != expectedType ) { + Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.", + i + 1, parmName.c_str(), expectedType->Name(), name ); + } + + newtype.AddFunctionParm( argType, "" ); + + if ( i < num - 1 ) { + if ( CheckToken( ")" ) ) { + Error( "Too few parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( "," ); + } + } + if ( !CheckToken( ")" ) ) { + Error( "Too many parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( ";" ); + + type = gameLocal.program.FindType( name ); + if ( type ) { + if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) { + Error( "Type mismatch on redefinition of '%s'", name ); + } + } else { + type = gameLocal.program.AllocType( newtype ); + type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true ); + + function_t &func = gameLocal.program.AllocFunction( type->def ); + func.eventdef = ev; + func.parmSize.SetNum( num ); + for( i = 0; i < num; i++ ) { + argType = newtype.GetParmType( i ); + func.parmTotal += argType->Size(); + func.parmSize[ i ] = argType->Size(); + } + + // mark the parms as local + func.locals = func.parmTotal; + } +} + +/* +================ +idCompiler::ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void idCompiler::ParseDefs( void ) { + idStr name; + idTypeDef *type; + idVarDef *def; + idVarDef *oldscope; + + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + type = ParseType(); + if ( type == &type_scriptevent ) { + type = ParseType(); + ParseName( name ); + ParseEventDef( type, name ); + return; + } + + ParseName( name ); + + if ( type == &type_namespace ) { + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + } + ParseNamespace( def ); + } else if ( CheckToken( "::" ) ) { + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + Error( "Unknown object name '%s'", name.c_str() ); + } + ParseName( name ); + oldscope = scope; + scope = def; + + ExpectToken( "(" ); + ParseFunctionDef( type, name.c_str() ); + scope = oldscope; + } else if ( type == &type_object ) { + ParseObjectDef( name.c_str() ); + } else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction + ParseFunctionDef( type, name.c_str() ); + } else { + ParseVariableDef( type, name.c_str() ); + while( CheckToken( "," ) ) { + ParseName( name ); + ParseVariableDef( type, name.c_str() ); + } + ExpectToken( ";" ); + } +} + +/* +================ +idCompiler::ParseNamespace + +Parses anything within a namespace definition +================ +*/ +void idCompiler::ParseNamespace( idVarDef *newScope ) { + idVarDef *oldscope; + + oldscope = scope; + if ( newScope != &def_namespace ) { + ExpectToken( "{" ); + } + + while( !eof ) { + scope = newScope; + callthread = false; + + if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) { + break; + } + + ParseDefs(); + } + + scope = oldscope; +} + +/* +============ +idCompiler::CompileFile + +compiles the 0 terminated text, adding definitions to the program structure +============ +*/ +void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) { + idStr orig = filename; + idTimer compile_time; + bool error; + + compile_time.Start(); + + scope = &def_namespace; + basetype = NULL; + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + currentLineNumber = 0; + console = toConsole; + + memset( &immediate, 0, sizeof( immediate ) ); + + parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS ); + parser.LoadMemory( text, strlen( text ), filename ); + parserPtr = &parser; + + // unread tokens to include script defines + token = SCRIPT_DEFAULTDEFS; + token.type = TT_STRING; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "include"; + token.type = TT_NAME; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "#"; + token.type = TT_PUNCTUATION; + token.subtype = P_PRECOMP; + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + // init the current token line to be the first line so that currentLineNumber is set correctly in NextToken + token.line = 1; + + error = false; + try { + // read first token + NextToken(); + while( !eof && !error ) { + // parse from global namespace + ParseNamespace( &def_namespace ); + } + } + + catch( idCompileError &err ) { + idStr error; + + if ( console ) { + // don't print line number of an error if were calling script from the console using the "script" command + sprintf( error, "Error: %s\n", err.error ); + } else { + sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.error ); + } + + parser.FreeSource(); + + throw idCompileError( error ); + } + + parser.FreeSource(); + + compile_time.Stop(); + if ( !toConsole ) { + gameLocal.Printf( "Compiled '%s': %.1f ms\n", orig.c_str(), compile_time.Milliseconds() ); + } +} diff --git a/source/game/script/Script_Compiler.h b/source/game/script/Script_Compiler.h new file mode 100644 index 0000000..3e12ee3 --- /dev/null +++ b/source/game/script/Script_Compiler.h @@ -0,0 +1,251 @@ +#ifndef __SCRIPT_COMPILER_H__ +#define __SCRIPT_COMPILER_H__ + +const char * const RESULT_STRING = ""; + +typedef struct opcode_s { + char *name; + char *opname; + int priority; + bool rightAssociative; + idVarDef *type_a; + idVarDef *type_b; + idVarDef *type_c; +} opcode_t; + +// These opcodes are no longer necessary: +// OP_PUSH_OBJ: +// OP_PUSH_OBJENT: + +enum { + OP_RETURN, + + OP_UINC_F, + OP_UINCP_F, + OP_UDEC_F, + OP_UDECP_F, + OP_COMP_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_MOD_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_EO, + OP_EQ_OE, + OP_EQ_OO, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_EO, + OP_NE_OE, + OP_NE_OO, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_INDIRECT_F, + OP_INDIRECT_V, + OP_INDIRECT_S, + OP_INDIRECT_ENT, + OP_INDIRECT_BOOL, + OP_INDIRECT_OBJ, + + OP_ADDRESS, + + OP_EVENTCALL, + OP_OBJECTCALL, + OP_SYSCALL, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_BOOL, + OP_STORE_OBJENT, + OP_STORE_OBJ, + OP_STORE_ENTOBJ, + + OP_STORE_FTOS, + OP_STORE_BTOS, + OP_STORE_VTOS, + OP_STORE_FTOBOOL, + OP_STORE_BOOLTOF, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_BOOL, + OP_STOREP_OBJ, + OP_STOREP_OBJENT, + + OP_STOREP_FTOS, + OP_STOREP_BTOS, + OP_STOREP_VTOS, + OP_STOREP_FTOBOOL, + OP_STOREP_BOOLTOF, + + OP_UMUL_F, + OP_UMUL_V, + OP_UDIV_F, + OP_UDIV_V, + OP_UMOD_F, + OP_UADD_F, + OP_UADD_V, + OP_USUB_F, + OP_USUB_V, + OP_UAND_F, + OP_UOR_F, + + OP_NOT_BOOL, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + + OP_NEG_F, + OP_NEG_V, + + OP_INT_F, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_THREAD, + OP_OBJTHREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_OBJ, + OP_PUSH_OBJENT, + OP_PUSH_FTOS, + OP_PUSH_BTOF, + OP_PUSH_FTOB, + OP_PUSH_VTOS, + OP_PUSH_BTOS, + + OP_GOTO, + + OP_AND, + OP_AND_BOOLF, + OP_AND_FBOOL, + OP_AND_BOOLBOOL, + OP_OR, + OP_OR_BOOLF, + OP_OR_FBOOL, + OP_OR_BOOLBOOL, + + OP_BITAND, + OP_BITOR, + + OP_BREAK, // placeholder op. not used in final code + OP_CONTINUE, // placeholder op. not used in final code + + NUM_OPCODES +}; + +class idCompiler { +private: + static bool punctuationValid[ 256 ]; + static char *punctuation[]; + + idParser parser; + idParser *parserPtr; + idToken token; + + idTypeDef *immediateType; + eval_t immediate; + + bool eof; + bool console; + bool callthread; + int braceDepth; + int loopDepth; + int currentLineNumber; + int currentFileNumber; + int errorCount; + + idVarDef *scope; // the function being parsed, or NULL + const idVarDef *basetype; // for accessing fields + + float Divide( float numerator, float denominator ); + void Error( const char *error, ... ) const; + void Warning( const char *message, ... ) const; + idVarDef *OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ); + bool EmitPush( idVarDef *expression, const idTypeDef *funcArg ); + void NextToken( void ); + void ExpectToken( const char *string ); + bool CheckToken( const char *string ); + void ParseName( idStr &name ); + void SkipOutOfFunction( void ); + void SkipToSemicolon( void ); + idTypeDef *CheckType( void ); + idTypeDef *ParseType( void ); + idVarDef *FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const; + idVarDef *GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ); + idVarDef *VirtualFunctionConstant( idVarDef *func ); + idVarDef *SizeConstant( int size ); + idVarDef *JumpConstant( int value ); + idVarDef *JumpDef( int jumpfrom, int jumpto ); + idVarDef *JumpTo( int jumpto ); + idVarDef *JumpFrom( int jumpfrom ); + idVarDef *ParseImmediate( void ); + idVarDef *EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ); + idVarDef *ParseFunctionCall( idVarDef *func ); + idVarDef *ParseObjectCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseEventCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseSysObjectCall( idVarDef *func ); + idVarDef *LookupDef( const char *name, const idVarDef *baseobj ); + idVarDef *ParseValue( void ); + idVarDef *GetTerm( void ); + bool TypeMatches( etype_t type1, etype_t type2 ) const; + idVarDef *GetExpression( int priority ); + idTypeDef *GetTypeForEventArg( char argType ); + void PatchLoop( int start, int continuePos ); + void ParseReturnStatement( void ); + void ParseWhileStatement( void ); + void ParseForStatement( void ); + void ParseDoWhileStatement( void ); + void ParseIfStatement( void ); + void ParseStatement( void ); + void ParseObjectDef( const char *objname ); + idTypeDef *ParseFunction( idTypeDef *returnType, const char *name ); + void ParseFunctionDef( idTypeDef *returnType, const char *name ); + void ParseVariableDef( idTypeDef *type, const char *name ); + void ParseEventDef( idTypeDef *type, const char *name ); + void ParseDefs( void ); + void ParseNamespace( idVarDef *newScope ); + +public : + static opcode_t opcodes[]; + + idCompiler(); + void CompileFile( const char *text, const char *filename, bool console ); +}; + +#endif /* !__SCRIPT_COMPILER_H__ */ diff --git a/source/game/script/Script_Interpreter.cpp b/source/game/script/Script_Interpreter.cpp new file mode 100644 index 0000000..456fea8 --- /dev/null +++ b/source/game/script/Script_Interpreter.cpp @@ -0,0 +1,1891 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idInterpreter::idInterpreter() +================ +*/ +idInterpreter::idInterpreter() { + localstackUsed = 0; + terminateOnExit = true; + debug = 0; + LastScriptVariable = 0; + memset( localstack, 0, sizeof( localstack ) ); + memset( callStack, 0, sizeof( callStack ) ); + Reset(); +} + +/* +================ +idInterpreter::Save +================ +*/ +void idInterpreter::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->WriteInt( callStack[i].s ); + if ( callStack[i].f ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( callStack[i].f ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( callStack[i].stackbase ); + } + savefile->WriteInt( maxStackDepth ); + + savefile->WriteInt( localstackUsed ); + savefile->Write( &localstack, localstackUsed ); + + savefile->WriteInt( localstackBase ); + savefile->WriteInt( maxLocalstackUsed ); + + if ( currentFunction ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( currentFunction ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( instructionPointer ); + + savefile->WriteInt( popParms ); + + // TOSAVE: idVarDef *LastScriptVariable; + if ( multiFrameEvent ) { + savefile->WriteString( multiFrameEvent->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteObject( eventEntity ); + + savefile->WriteObject( thread ); + + savefile->WriteBool( doneProcessing ); + savefile->WriteBool( threadDying ); + savefile->WriteBool( terminateOnExit ); + savefile->WriteBool( debug ); +} + +/* +================ +idInterpreter::Restore +================ +*/ +void idInterpreter::Restore( idRestoreGame *savefile ) { + int i; + idStr funcname; + int func_index; + + savefile->ReadInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->ReadInt( callStack[i].s ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + callStack[i].f = gameLocal.program.GetFunction( func_index ); + } else { + callStack[i].f = NULL; + } + + savefile->ReadInt( callStack[i].stackbase ); + } + savefile->ReadInt( maxStackDepth ); + + savefile->ReadInt( localstackUsed ); + savefile->Read( &localstack, localstackUsed ); + + savefile->ReadInt( localstackBase ); + savefile->ReadInt( maxLocalstackUsed ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + currentFunction = gameLocal.program.GetFunction( func_index ); + } else { + currentFunction = NULL; + } + savefile->ReadInt( instructionPointer ); + + savefile->ReadInt( popParms ); + + // TORESTORE: idVarDef *LastScriptVariable; + + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + multiFrameEvent = idEventDef::FindEvent( funcname ); + } + + savefile->ReadObject( reinterpret_cast( eventEntity ) ); + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadBool( doneProcessing ); + savefile->ReadBool( threadDying ); + savefile->ReadBool( terminateOnExit ); + savefile->ReadBool( debug ); +} + +/* +================ +idInterpreter::Reset +================ +*/ +void idInterpreter::Reset( void ) { + callStackDepth = 0; + localstackUsed = 0; + localstackBase = 0; + + maxLocalstackUsed = 0; + maxStackDepth = 0; + + popParms = 0; + multiFrameEvent = NULL; + eventEntity = NULL; + + currentFunction = 0; + NextInstruction( 0 ); + + threadDying = false; + doneProcessing = true; +} + +/* +================ +idInterpreter::GetRegisterValue + +Returns a string representation of the value of the register. This is +used primarily for the debugger and debugging + +//FIXME: This is pretty much wrong. won't access data in most situations. +================ +*/ +// RAVEN BEGIN +// bdube: took this over again +bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { + varEval_t reg; + idVarDef *d; + char funcObject[ 1024 ]; + char *funcName; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; + const idTypeDef *field; + const function_t *func; + + out.Empty(); + + if ( scopeDepth == -1 ) { + scopeDepth = callStackDepth; + } + + if ( scopeDepth == callStackDepth ) { + func = currentFunction; + } else { + func = callStack[ scopeDepth ].f; + } + if ( !func ) { + return false; + } + + idStr::Copynz( funcObject, func->Name(), sizeof( funcObject ) ); + funcName = strstr( funcObject, "::" ); + if ( funcName ) { + *funcName = '\0'; + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } + } else { + funcName = funcObject; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; + } + + if ( !scope ) + { + return false; + } + + d = gameLocal.program.GetDef( NULL, name, scope ); + + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; + } + } + } + + if ( !d ) + { + out = "???"; + return false; + } + + reg = GetVariable( d ); + switch( d->Type() ) { + case ev_float: + if ( reg.floatPtr ) { + out = va("%g", *reg.floatPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_vector: + if ( reg.vectorPtr ) { + out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); + } else { + out = "0,0,0"; + } + return true; + break; + + case ev_boolean: + if ( reg.intPtr ) { + out = va( "%d", *reg.intPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_field: + { + idEntity* entity; + idScriptObject* obj; + + if ( scope == &def_namespace ) { + // should never happen, but handle it safely anyway + return false; + } + + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { + return false; + } + + obj = &entity->scriptObject; + if ( !obj ) { + return false; + } + + switch ( field->Type() ) { + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } + + default: + return false; + } + + break; + } + + case ev_string: + if ( reg.stringPtr ) { + out = "\""; + out += reg.stringPtr; + out += "\""; + } else { + out = "\"\""; + } + return true; + + default: + return false; + } +} +// RAVEN END + +/* +================ +idInterpreter::GetCallstackDepth +================ +*/ +int idInterpreter::GetCallstackDepth( void ) const { + return callStackDepth; +} + +/* +================ +idInterpreter::GetCallstack +================ +*/ +const prstack_t *idInterpreter::GetCallstack( void ) const { + return &callStack[ 0 ]; +} + +/* +================ +idInterpreter::GetCurrentFunction +================ +*/ +const function_t *idInterpreter::GetCurrentFunction( void ) const { + return currentFunction; +} + +/* +================ +idInterpreter::GetThread +================ +*/ +idThread *idInterpreter::GetThread( void ) const { + return thread; +} + + +/* +================ +idInterpreter::SetThread +================ +*/ +void idInterpreter::SetThread( idThread *pThread ) { + thread = pThread; +} + +/* +================ +idInterpreter::CurrentLine +================ +*/ +int idInterpreter::CurrentLine( void ) const { + if ( instructionPointer < 0 ) { + return 0; + } + return gameLocal.program.GetLineNumberForStatement( instructionPointer ); +} + +/* +================ +idInterpreter::CurrentFile +================ +*/ +const char *idInterpreter::CurrentFile( void ) const { + if ( instructionPointer < 0 ) { + return ""; + } + return gameLocal.program.GetFilenameForStatement( instructionPointer ); +} + +/* +============ +idInterpreter::StackTrace +============ +*/ +void idInterpreter::StackTrace( void ) const { + const function_t *f; + int i; + int top; + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + return; + } + + top = callStackDepth; + if ( top >= MAX_STACK_DEPTH ) { + top = MAX_STACK_DEPTH - 1; + } + + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = top; i >= 0; i-- ) { + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } +} + +/* +============ +idInterpreter::Error + +Aborts the currently executing function +============ +*/ +void idInterpreter::Error( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + StackTrace(); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Error( "%s(%d): Thread '%s': %s\n", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Error( "Thread '%s': %s\n", thread->GetThreadName(), text ); + } +} + +/* +============ +idInterpreter::Warning + +Prints file and line number information with warning. +============ +*/ +void idInterpreter::Warning( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Warning( "%s(%d): Thread '%s': %s", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Warning( "Thread '%s' : %s", thread->GetThreadName(), text ); + } +} + +/* +================ +idInterpreter::DisplayInfo +================ +*/ +void idInterpreter::DisplayInfo( void ) const { + const function_t *f; + int i; + + gameLocal.Printf( " Stack depth: %d bytes, %d max\n", localstackUsed, maxLocalstackUsed ); + gameLocal.Printf( " Call depth: %d, %d max\n", callStackDepth, maxStackDepth ); + gameLocal.Printf( " Call Stack: " ); + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + } else { + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = callStackDepth; i > 0; i-- ) { + gameLocal.Printf( " " ); + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } + } +} + +/* +==================== +idInterpreter::ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void idInterpreter::ThreadCall( idInterpreter *source, const function_t *func, int args ) { + Reset(); + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( localstack, &source->localstack[ source->localstackUsed - args ], args ); +// RAVEN END + + localstackUsed = args; + localstackBase = 0; + + maxLocalstackUsed = localstackUsed; + EnterFunction( func, false ); + + thread->SetThreadName( currentFunction->Name() ); +} + +/* +================ +idInterpreter::EnterObjectFunction + +Calls a function on a script object. + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +================ +*/ +void idInterpreter::EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ) { + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + Push( self->entityNumber + 1 ); + EnterFunction( func, false ); +} + +/* +==================== +idInterpreter::EnterFunction + +Returns the new program statement counter + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +==================== +*/ +void idInterpreter::EnterFunction( const function_t *func, bool clearStack ) { + int c; + prstack_t *stack; + + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + + if ( callStackDepth >= MAX_STACK_DEPTH ) { + Error( "call stack overflow" ); + } + + stack = &callStack[ callStackDepth ]; + + stack->s = instructionPointer + 1; // point to the next instruction to execute + stack->f = currentFunction; + stack->stackbase = localstackBase; + + callStackDepth++; + if ( callStackDepth > maxStackDepth ) { + maxStackDepth = callStackDepth; + } + + if ( !func ) { + Error( "NULL function" ); + } + + if ( debug ) { + if ( currentFunction ) { + gameLocal.Printf( "%d: call '%s' from '%s'(line %d)%s\n", gameLocal.time, func->Name(), currentFunction->Name(), + gameLocal.program.GetStatement( instructionPointer ).linenumber, clearStack ? " clear stack" : "" ); + } else { + gameLocal.Printf( "%d: call '%s'%s\n", gameLocal.time, func->Name(), clearStack ? " clear stack" : "" ); + } + } + + currentFunction = func; + assert( !func->eventdef ); + NextInstruction( func->firstStatement ); + + // allocate space on the stack for locals + // parms are already on stack + c = func->locals - func->parmTotal; + assert( c >= 0 ); + + if ( localstackUsed + c > LOCALSTACK_SIZE ) { + Error( "EnterFuncton: locals stack overflow\n" ); + } + + // initialize local stack variables to zero + memset( &localstack[ localstackUsed ], 0, c ); + + localstackUsed += c; + localstackBase = localstackUsed - func->locals; + + if ( localstackUsed > maxLocalstackUsed ) { + maxLocalstackUsed = localstackUsed ; + } +} + +/* +==================== +idInterpreter::LeaveFunction +==================== +*/ +void idInterpreter::LeaveFunction( idVarDef *returnDef ) { + prstack_t *stack; + varEval_t ret; + + if ( callStackDepth <= 0 ) { + Error( "prog stack underflow" ); + } + + // return value + if ( returnDef ) { + switch( returnDef->Type() ) { + case ev_string : + gameLocal.program.ReturnString( GetString( returnDef ) ); + break; + + case ev_vector : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnVector( *ret.vectorPtr ); + break; + + default : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnInteger( *ret.intPtr ); + } + } + + // remove locals from the stack + PopParms( currentFunction->locals ); + assert( localstackUsed == localstackBase ); + + if ( debug ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + gameLocal.Printf( "%d: %s(%d): exit %s", gameLocal.time, gameLocal.program.GetFilename( line.file ), line.linenumber, currentFunction->Name() ); + if ( callStackDepth > 1 ) { + gameLocal.Printf( " return to %s(line %d)\n", callStack[ callStackDepth - 1 ].f->Name(), gameLocal.program.GetStatement( callStack[ callStackDepth - 1 ].s ).linenumber ); + } else { + gameLocal.Printf( " done\n" ); + } + } + + // up stack + callStackDepth--; + stack = &callStack[ callStackDepth ]; + currentFunction = stack->f; + localstackBase = stack->stackbase; + NextInstruction( stack->s ); + + if ( !callStackDepth ) { + // all done + doneProcessing = true; + threadDying = true; + currentFunction = 0; + } +} + +/* +================ +idInterpreter::CallEvent +================ +*/ +void idInterpreter::CallEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t var; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + var.intPtr = ( int * )&localstack[ start ]; + eventEntity = GetEntity( *var.entityNumberPtr ); + + if ( !eventEntity || !eventEntity->RespondsTo( *evdef ) ) { +// RAVEN BEGIN +// jshepard: added catch for entities that don't exist + if ( eventEntity && developer.GetBool() ) { + // give a warning in developer mode + Warning( "Function '%s' not supported on entity '%s'", evdef->GetName(), eventEntity->name.c_str() ); + } else if( !eventEntity ) { + //check the last script variable. + if(!!LastScriptVariable) { + //if it's $null_entity, then we should ignore this. + if(idStr::Cmp( "$null_entity", LastScriptVariable->Name()) ) { + Warning( "Entity '%s' is a null entity", LastScriptVariable->Name() ); + } + } else { + Warning( "Null entity referenced -- check entity name spelling."); + } + } +// RAVEN END + // always return a safe value when an object doesn't exist + switch( evdef->GetReturnType() ) { + case D_EVENT_INTEGER : + gameLocal.program.ReturnInteger( 0 ); + break; + + case D_EVENT_FLOAT : + gameLocal.program.ReturnFloat( 0 ); + break; + + case D_EVENT_VECTOR : + gameLocal.program.ReturnVector( vec3_zero ); + break; + + case D_EVENT_STRING : + gameLocal.program.ReturnString( "" ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + gameLocal.program.ReturnEntity( ( idEntity * )NULL ); + break; + + case D_EVENT_TRACE : + default: + // unsupported data type + break; + } + + PopParms( argsize ); + eventEntity = NULL; + return; + } + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = type_object.Size(); ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + var.intPtr = ( int * )&localstack[ start + pos ]; + data[ i ] = int( *var.floatPtr ); + break; + + case D_EVENT_FLOAT : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( float * )&data[ i ] ) = *var.floatPtr; + break; + + case D_EVENT_VECTOR : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idVec3 ** )&data[ i ] ) = var.vectorPtr; + break; + + case D_EVENT_STRING : + ( *( const char ** )&data[ i ] ) = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + if ( !( *( idEntity ** )&data[ i ] ) ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + eventEntity->ProcessEventArgPtr( evdef, data ); + if ( !multiFrameEvent ) { + if ( popParms ) { + PopParms( popParms ); + } + eventEntity = NULL; + } else { + doneProcessing = true; + } + popParms = 0; +} + +/* +================ +idInterpreter::BeginMultiFrameEvent +================ +*/ +bool idInterpreter::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( eventEntity != ent ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong entity" ); + } + if ( multiFrameEvent ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong event" ); + } + return false; + } + + multiFrameEvent = event; + return true; +} + +/* +================ +idInterpreter::EndMultiFrameEvent +================ +*/ +void idInterpreter::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::EndMultiFrameEvent called with wrong event" ); + } + + multiFrameEvent = NULL; +} + +/* +================ +idInterpreter::MultiFrameEventInProgress +================ +*/ +bool idInterpreter::MultiFrameEventInProgress( void ) const { + return multiFrameEvent != NULL; +} + +/* +================ +idInterpreter::CallSysEvent +================ +*/ +void idInterpreter::CallSysEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t source; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = 0; ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( int * )&data[ i ] = int( *source.floatPtr ); + break; + + case D_EVENT_FLOAT : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( float * )&data[ i ] = *source.floatPtr; + break; + + case D_EVENT_VECTOR : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idVec3 ** )&data[ i ] = source.vectorPtr; + break; + + case D_EVENT_STRING : + *( const char ** )&data[ i ] = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + if ( !*( idEntity ** )&data[ i ] ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + thread->ProcessEventArgPtr( evdef, data ); + if ( popParms ) { + PopParms( popParms ); + } + popParms = 0; +} + +/* +==================== +idInterpreter::Execute +==================== +*/ +bool idInterpreter::Execute( void ) { + varEval_t var_a; + varEval_t var_b; + varEval_t var_c; + varEval_t var; + statement_t *st; + int runaway; + idThread *newThread; + float floatVal; + idScriptObject *obj; + const function_t *func; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_SCRIPT); +// RAVEN END + + if ( threadDying || !currentFunction ) { + return true; + } + + if ( multiFrameEvent ) { + // move to previous instruction and call it again + instructionPointer--; + } + + runaway = 5000000; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) { + instructionPointer++; + + if ( !--runaway ) { + Error( "runaway loop error" ); + } + + // next statement + st = &gameLocal.program.GetStatement( instructionPointer ); + +// RAVEN BEGIN +// bdube: if the debugger is running then we need to check to see if any breakpoints have beeng hit + if ( gameLocal.editors & EDITOR_DEBUGGER ) { + common->DebuggerCheckBreakpoint ( this, &gameLocal.program, instructionPointer ); + } else if ( g_debugScript.GetBool ( ) ) { + static int lastLineNumber = -1; + if ( lastLineNumber != gameLocal.program.GetStatement ( instructionPointer ).linenumber ) { + gameLocal.Printf ( "%s (%d)\n", + gameLocal.program.GetFilename ( gameLocal.program.GetStatement ( instructionPointer ).file ), + gameLocal.program.GetStatement ( instructionPointer ).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement ( instructionPointer ).linenumber; + } + } +// RAVEN END + + switch( st->op ) { + case OP_RETURN: + LeaveFunction( st->a ); + break; + + case OP_THREAD: + newThread = new idThread( this, st->a->value.functionPtr, st->b->value.argSize ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + PopParms( st->b->value.argSize ); + break; + + case OP_OBJTHREAD: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + assert( st->c->value.argSize == func->parmTotal ); + newThread = new idThread( this, GetEntity( *var_a.entityNumberPtr ), func, func->parmTotal ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + } else { + // return a null thread to the script + gameLocal.program.ReturnFloat( 0.0f ); + } + PopParms( st->c->value.argSize ); + break; + + case OP_CALL: + EnterFunction( st->a->value.functionPtr, false ); + break; + + case OP_EVENTCALL: + CallEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_OBJECTCALL: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + EnterFunction( func, false ); + } else { + // return a 'safe' value + gameLocal.program.ReturnVector( vec3_zero ); + gameLocal.program.ReturnString( "" ); + PopParms( st->c->value.argSize ); + } + break; + + case OP_SYSCALL: + CallSysEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_IFNOT: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr == 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_IF: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr != 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_GOTO: + NextInstruction( instructionPointer + st->a->value.jumpOffset ); + break; + + case OP_ADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr + *var_b.floatPtr; + break; + + case OP_ADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr + *var_b.vectorPtr; + break; + + case OP_ADD_S: + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_FS: + var_a = GetVariable( st->a ); + SetString( st->c, FloatToString( *var_a.floatPtr ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SF: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, FloatToString( *var_b.floatPtr ) ); + break; + + case OP_ADD_VS: + var_a = GetVariable( st->a ); + SetString( st->c, var_a.vectorPtr->ToString() ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SV: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, var_b.vectorPtr->ToString() ); + break; + + case OP_SUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr - *var_b.floatPtr; + break; + + case OP_SUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr - *var_b.vectorPtr; + break; + + case OP_MUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr * *var_b.floatPtr; + break; + + case OP_MUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.vectorPtr * *var_b.vectorPtr; + break; + + case OP_MUL_FV: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.floatPtr * *var_b.vectorPtr; + break; + + case OP_MUL_VF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr * *var_b.floatPtr; + break; + + case OP_DIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = idMath::INFINITY; + } else { + *var_c.floatPtr = *var_a.floatPtr / *var_b.floatPtr; + } + break; + + case OP_MOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable ( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = *var_a.floatPtr; + } else { + *var_c.floatPtr = static_cast( *var_a.floatPtr ) % static_cast( *var_b.floatPtr ); + } + break; + + case OP_BITAND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) & static_cast( *var_b.floatPtr ); + break; + + case OP_BITOR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) | static_cast( *var_b.floatPtr ); + break; + + case OP_GE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr >= *var_b.floatPtr ); + break; + + case OP_LE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr <= *var_b.floatPtr ); + break; + + case OP_GT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr > *var_b.floatPtr ); + break; + + case OP_LT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr < *var_b.floatPtr ); + break; + + case OP_AND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.intPtr != 0 ); + break; + + case OP_AND_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.intPtr != 0 ); + break; + + case OP_OR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.intPtr != 0 ); + break; + + case OP_OR_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.intPtr != 0 ); + break; + + case OP_NOT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr == 0 ); + break; + + case OP_NOT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == 0.0f ); + break; + + case OP_NOT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == vec3_zero ); + break; + + case OP_NOT_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( strlen( GetString( st->a ) ) == 0 ); + break; + + case OP_NOT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( GetEntity( *var_a.entityNumberPtr ) == NULL ); + break; + + case OP_NEG_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = -*var_a.floatPtr; + break; + + case OP_NEG_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = -*var_a.vectorPtr; + break; + + case OP_INT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ); + break; + + case OP_EQ_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == *var_b.floatPtr ); + break; + + case OP_EQ_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == *var_b.vectorPtr ); + break; + + case OP_EQ_S: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) == 0 ); + break; + + case OP_EQ_E: + case OP_EQ_EO: + case OP_EQ_OE: + case OP_EQ_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr == *var_b.entityNumberPtr ); + break; + + case OP_NE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != *var_b.floatPtr ); + break; + + case OP_NE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr != *var_b.vectorPtr ); + break; + + case OP_NE_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) != 0 ); + break; + + case OP_NE_E: + case OP_NE_EO: + case OP_NE_OE: + case OP_NE_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr != *var_b.entityNumberPtr ); + break; + + case OP_UADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr += *var_a.floatPtr; + break; + + case OP_UADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr += *var_a.vectorPtr; + break; + + case OP_USUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr -= *var_a.floatPtr; + break; + + case OP_USUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr -= *var_a.vectorPtr; + break; + + case OP_UMUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr *= *var_a.floatPtr; + break; + + case OP_UMUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr *= *var_a.floatPtr; + break; + + case OP_UDIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = idMath::INFINITY; + } else { + *var_b.floatPtr = *var_b.floatPtr / *var_a.floatPtr; + } + break; + + case OP_UDIV_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + var_b.vectorPtr->Set( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + } else { + *var_b.vectorPtr = *var_b.vectorPtr / *var_a.floatPtr; + } + break; + + case OP_UMOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = *var_a.floatPtr; + } else { + *var_b.floatPtr = static_cast( *var_b.floatPtr ) % static_cast( *var_a.floatPtr ); + } + break; + + case OP_UOR_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) | static_cast( *var_a.floatPtr ); + break; + + case OP_UAND_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) & static_cast( *var_a.floatPtr ); + break; + + case OP_UINC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )++; + break; + + case OP_UINCP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )++; + } + break; + + case OP_UDEC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )--; + break; + + case OP_UDECP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )--; + } + break; + + case OP_COMP_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ~static_cast( *var_a.floatPtr ); + break; + + case OP_STORE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = *var_a.floatPtr; + break; + + case OP_STORE_ENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_BOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.intPtr = *var_a.intPtr; + break; + + case OP_STORE_OBJENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.entityNumberPtr = 0; + } else if ( !obj->GetTypeDef()->Inherits( st->b->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->b->TypeDef()->Name() ); + *var_b.entityNumberPtr = 0; + } else { + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STORE_OBJ: + case OP_STORE_ENTOBJ: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_S: + SetString( st->b, GetString( st->a ) ); + break; + + case OP_STORE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr = *var_a.vectorPtr; + break; + + case OP_STORE_FTOS: + var_a = GetVariable( st->a ); + SetString( st->b, FloatToString( *var_a.floatPtr ) ); + break; + + case OP_STORE_BTOS: + var_a = GetVariable( st->a ); + SetString( st->b, *var_a.intPtr ? "true" : "false" ); + break; + + case OP_STORE_VTOS: + var_a = GetVariable( st->a ); + SetString( st->b, var_a.vectorPtr->ToString() ); + break; + + case OP_STORE_FTOBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.intPtr = 1; + } else { + *var_b.intPtr = 0; + } + break; + + case OP_STORE_BOOLTOF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_a.intPtr ); + break; + + case OP_STOREP_F: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = *var_a.floatPtr; + } + break; + + case OP_STOREP_ENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_FLD: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_BOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_S: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + idStr::Copynz( var_b.evalPtr->stringPtr, GetString( st->a ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_V: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->vectorPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->vectorPtr = *var_a.vectorPtr; + } + break; + + case OP_STOREP_FTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, FloatToString( *var_a.floatPtr ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_BTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + idStr::Copynz( var_b.evalPtr->stringPtr, "true", MAX_STRING_LEN ); + } else { + idStr::Copynz( var_b.evalPtr->stringPtr, "false", MAX_STRING_LEN ); + } + } + break; + + case OP_STOREP_VTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, var_a.vectorPtr->ToString(), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_FTOBOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.evalPtr->intPtr = 1; + } else { + *var_b.evalPtr->intPtr = 0; + } + } + break; + + case OP_STOREP_BOOLTOF: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = static_cast( *var_a.intPtr ); + } + break; + + case OP_STOREP_OBJ: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_OBJENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.evalPtr->entityNumberPtr = 0; + + // st->b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in st->c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + } else if ( !obj->GetTypeDef()->Inherits( st->c->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->c->TypeDef()->Name() ); + *var_b.evalPtr->entityNumberPtr = 0; + } else { + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + } + break; + + case OP_ADDRESS: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var_c.evalPtr->bytePtr = &obj->data[ st->b->value.ptrOffset ]; + } else { + var_c.evalPtr->bytePtr = NULL; + } + break; + + case OP_INDIRECT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.floatPtr = *var.floatPtr; + } else { + *var_c.floatPtr = 0.0f; + } + break; + + case OP_INDIRECT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } else { + *var_c.entityNumberPtr = 0; + } + break; + + case OP_INDIRECT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.intPtr = *var.intPtr; + } else { + *var_c.intPtr = 0; + } + break; + + case OP_INDIRECT_S: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + SetString( st->c, var.stringPtr ); + } else { + SetString( st->c, "" ); + } + break; + + case OP_INDIRECT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.vectorPtr = *var.vectorPtr; + } else { + var_c.vectorPtr->Zero(); + } + break; + + case OP_INDIRECT_OBJ: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_c.entityNumberPtr = 0; + } else { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } + break; + + case OP_PUSH_F: + var_a = GetVariable( st->a ); + Push( *var_a.intPtr ); + break; + + case OP_PUSH_FTOS: + var_a = GetVariable( st->a ); + PushString( FloatToString( *var_a.floatPtr ) ); + break; + + case OP_PUSH_BTOF: + var_a = GetVariable( st->a ); + floatVal = *var_a.intPtr; + Push( *reinterpret_cast( &floatVal ) ); + break; + + case OP_PUSH_FTOB: + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + Push( 1 ); + } else { + Push( 0 ); + } + break; + + case OP_PUSH_VTOS: + var_a = GetVariable( st->a ); + PushString( var_a.vectorPtr->ToString() ); + break; + + case OP_PUSH_BTOS: + var_a = GetVariable( st->a ); + PushString( *var_a.intPtr ? "true" : "false" ); + break; + + case OP_PUSH_ENT: + var_a = GetVariable( st->a ); +// RAVEN BEGIN +// jshepard: keep tabs on this guy, he's the last referenced script variable. + assert(st->a); + LastScriptVariable = st->a; + //This line leads to memory corruption, I need to come up with a better way of keeping track. + //LastScriptVariable->initialized = idVarDef::stackVariable; +// RAVEN END + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_S: + PushString( GetString( st->a ) ); + break; + + case OP_PUSH_V: + var_a = GetVariable( st->a ); + Push( *reinterpret_cast( &var_a.vectorPtr->x ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->y ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->z ) ); + break; + + case OP_PUSH_OBJ: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_OBJENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_BREAK: + case OP_CONTINUE: + default: + Error( "Bad opcode %i", st->op ); + break; + } + } + + return threadDying; +} diff --git a/source/game/script/Script_Interpreter.h b/source/game/script/Script_Interpreter.h new file mode 100644 index 0000000..80a65e2 --- /dev/null +++ b/source/game/script/Script_Interpreter.h @@ -0,0 +1,254 @@ + +#ifndef __SCRIPT_INTERPRETER_H__ +#define __SCRIPT_INTERPRETER_H__ + +#define MAX_STACK_DEPTH 64 +#define LOCALSTACK_SIZE 6144 + +typedef struct prstack_s { + int s; + const function_t *f; + int stackbase; +} prstack_t; + +class idInterpreter { +private: + prstack_t callStack[ MAX_STACK_DEPTH ]; + int callStackDepth; + int maxStackDepth; + + byte localstack[ LOCALSTACK_SIZE ]; + int localstackUsed; + int localstackBase; + int maxLocalstackUsed; + + const function_t *currentFunction; + int instructionPointer; + + int popParms; + const idEventDef *multiFrameEvent; + idEntity *eventEntity; + + idThread *thread; + + void PopParms( int numParms ); +// RAVEN BEGIN +// abahr: making Push public to allow parms to be put on stack +public: + void PushString( const char *string ); + void Push( int value ); +private: +// RAVEN END + const char *FloatToString( float value ); + void AppendString( idVarDef *def, const char *from ); + void SetString( idVarDef *def, const char *from ); + const char *GetString( idVarDef *def ); + varEval_t GetVariable( idVarDef *def ); + idEntity *GetEntity( int entnum ) const; + idScriptObject *GetScriptObject( int entnum ) const; + void NextInstruction( int position ); + + void LeaveFunction( idVarDef *returnDef ); + void CallEvent( const function_t *func, int argsize ); + void CallSysEvent( const function_t *func, int argsize ); +// RAVEN BEGIN +// jshepard: last variable referenced in the script-- keep tabs on it so we can print it for warnings. + idVarDef *LastScriptVariable; +// RAVEN END + + +public: + bool doneProcessing; + bool threadDying; + bool terminateOnExit; + bool debug; + + idInterpreter(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetThread( idThread *pThread ); + + void StackTrace( void ) const; + + int CurrentLine( void ) const; + const char *CurrentFile( void ) const; + + void Error( char *fmt, ... ) const; + void Warning( char *fmt, ... ) const; + void DisplayInfo( void ) const; + + bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + bool MultiFrameEventInProgress( void ) const; + + void ThreadCall( idInterpreter *source, const function_t *func, int args ); + void EnterFunction( const function_t *func, bool clearStack ); + void EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ); + + bool Execute( void ); + void Reset( void ); + + bool GetRegisterValue( const char *name, idStr &out, int scopeDepth ); + int GetCallstackDepth( void ) const; + const prstack_t *GetCallstack( void ) const; + const function_t *GetCurrentFunction( void ) const; + idThread *GetThread( void ) const; + +}; + +/* +==================== +idInterpreter::PopParms +==================== +*/ +ID_INLINE void idInterpreter::PopParms( int numParms ) { + // pop our parms off the stack + if ( localstackUsed < numParms ) { + Error( "locals stack underflow\n" ); + } + + localstackUsed -= numParms; +} + +/* +==================== +idInterpreter::Push +==================== +*/ +ID_INLINE void idInterpreter::Push( int value ) { + if ( localstackUsed + sizeof( int ) > LOCALSTACK_SIZE ) { + Error( "Push: locals stack overflow\n" ); + } + *( int * )&localstack[ localstackUsed ] = value; + localstackUsed += sizeof( int ); +} + +/* +==================== +idInterpreter::PushString +==================== +*/ +ID_INLINE void idInterpreter::PushString( const char *string ) { + if ( localstackUsed + MAX_STRING_LEN > LOCALSTACK_SIZE ) { + Error( "PushString: locals stack overflow\n" ); + } + idStr::Copynz( ( char * )&localstack[ localstackUsed ], string, MAX_STRING_LEN ); + localstackUsed += MAX_STRING_LEN; +} + +/* +==================== +idInterpreter::FloatToString +==================== +*/ +ID_INLINE const char *idInterpreter::FloatToString( float value ) { + static char text[ 32 ]; + + if ( value == ( float )( int )value ) { + sprintf( text, "%d", ( int )value ); + } else { + sprintf( text, "%f", value ); + } + return text; +} + +/* +==================== +idInterpreter::AppendString +==================== +*/ +ID_INLINE void idInterpreter::AppendString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Append( ( char * )&localstack[ localstackBase + def->value.stackOffset ], MAX_STRING_LEN, from ); + } else { + idStr::Append( def->value.stringPtr, MAX_STRING_LEN, from ); + } +} + +/* +==================== +idInterpreter::SetString +==================== +*/ +ID_INLINE void idInterpreter::SetString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Copynz( ( char * )&localstack[ localstackBase + def->value.stackOffset ], from, MAX_STRING_LEN ); + } else { + idStr::Copynz( def->value.stringPtr, from, MAX_STRING_LEN ); + } +} + +/* +==================== +idInterpreter::GetString +==================== +*/ +ID_INLINE const char *idInterpreter::GetString( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + return ( char * )&localstack[ localstackBase + def->value.stackOffset ]; + } else { + return def->value.stringPtr; + } +} + +/* +==================== +idInterpreter::GetVariable +==================== +*/ +ID_INLINE varEval_t idInterpreter::GetVariable( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + varEval_t val; + val.intPtr = ( int * )&localstack[ localstackBase + def->value.stackOffset ]; + return val; + } else { + return def->value; + } +} + +/* +================ +idInterpreter::GetEntity +================ +*/ +ID_INLINE idEntity *idInterpreter::GetEntity( int entnum ) const{ + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + return gameLocal.entities[ entnum - 1 ]; + } + return NULL; +} + +/* +================ +idInterpreter::GetScriptObject +================ +*/ +ID_INLINE idScriptObject *idInterpreter::GetScriptObject( int entnum ) const { + idEntity *ent; + + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + ent = gameLocal.entities[ entnum - 1 ]; + if ( ent && ent->scriptObject.data ) { + return &ent->scriptObject; + } + } + return NULL; +} + +/* +==================== +idInterpreter::NextInstruction +==================== +*/ +ID_INLINE void idInterpreter::NextInstruction( int position ) { + // Before we execute an instruction, we increment instructionPointer, + // therefore we need to compensate for that here. + instructionPointer = position - 1; +} + +#endif /* !__SCRIPT_INTERPRETER_H__ */ diff --git a/source/game/script/Script_Program.cpp b/source/game/script/Script_Program.cpp new file mode 100644 index 0000000..bdb37ab --- /dev/null +++ b/source/game/script/Script_Program.cpp @@ -0,0 +1,2514 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +// simple types. function types are dynamically allocated +idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); +idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( void * ), NULL ); +idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( void * ), NULL ); +// RAVEN BEGIN +// abahr +rvTypeDefString type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); +rvTypeDefFloat type_float( ev_float, &def_float, "float", sizeof( float ), NULL ); +rvTypeDefVec3 type_vector( ev_vector, &def_vector, "vector", sizeof( idVec3 ), NULL ); +rvTypeDefEntity type_entity( ev_entity, &def_entity, "entity", sizeof( int * ), NULL ); // stored as entity number pointer +// RAVEN END +idTypeDef type_field( ev_field, &def_field, "field", sizeof( void * ), NULL ); +idTypeDef type_function( ev_function, &def_function, "function", sizeof( void * ), &type_void ); +idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( int ), NULL ); +idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( void * ), NULL ); +idTypeDef type_object( ev_object, &def_object, "object", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( int ), NULL ); // only used for jump opcodes +idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( int ), NULL ); // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +rvTypeDefBool type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( int ), NULL ); +// RAVEN END + +idVarDef def_void( &type_void ); +idVarDef def_scriptevent( &type_scriptevent ); +idVarDef def_namespace( &type_namespace ); +idVarDef def_string( &type_string ); +idVarDef def_float( &type_float ); +idVarDef def_vector( &type_vector ); +idVarDef def_entity( &type_entity ); +idVarDef def_field( &type_field ); +idVarDef def_function( &type_function ); +idVarDef def_virtualfunction( &type_virtualfunction ); +idVarDef def_pointer( &type_pointer ); +idVarDef def_object( &type_object ); +idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes +idVarDef def_argsize( &type_argsize ); +idVarDef def_boolean( &type_boolean ); + +/*********************************************************************** + + function_t + +***********************************************************************/ + +/* +================ +function_t::function_t +================ +*/ +function_t::function_t() { + Clear(); +} + +/* +================ +function_t::Allocated +================ +*/ +size_t function_t::Allocated( void ) const { + return name.Allocated() + parmSize.Allocated(); +} + +/* +================ +function_t::SetName +================ +*/ +void function_t::SetName( const char *name ) { + this->name = name; +} + +/* +================ +function_t::Name +================ +*/ +const char *function_t::Name( void ) const { + return name; +} + +/* +================ +function_t::Clear +================ +*/ +void function_t::Clear( void ) { + eventdef = NULL; + def = NULL; + type = NULL; + firstStatement = 0; + numStatements = 0; + parmTotal = 0; + locals = 0; + filenum = 0; + name.Clear(); + parmSize.Clear(); +} + +/*********************************************************************** + + idTypeDef + +***********************************************************************/ + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + name = ename; + type = etype; + def = edef; + size = esize; + auxType = aux; + + parmTypes.SetGranularity( 1 ); + parmNames.SetGranularity( 1 ); + functions.SetGranularity( 1 ); +} + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( const idTypeDef &other ) { + *this = other; +} + +/* +================ +idTypeDef::operator= +================ +*/ +void idTypeDef::operator=( const idTypeDef& other ) { + type = other.type; + def = other.def; + name = other.name; + size = other.size; + auxType = other.auxType; + parmTypes = other.parmTypes; + parmNames = other.parmNames; + functions = other.functions; +} + +/* +================ +idTypeDef::Allocated +================ +*/ +size_t idTypeDef::Allocated( void ) const { + size_t memsize; + int i; + + memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); + for( i = 0; i < parmTypes.Num(); i++ ) { + memsize += parmNames[ i ].Allocated(); + } + + return memsize; +} + +/* +================ +idTypeDef::Inherits + +Returns true if basetype is an ancestor of this type. +================ +*/ +bool idTypeDef::Inherits( const idTypeDef *basetype ) const { + idTypeDef *superType; + + if ( type != ev_object ) { + return false; + } + + if ( this == basetype ) { + return true; + } + for( superType = auxType; superType != NULL; superType = superType->auxType ) { + if ( superType == basetype ) { + return true; + } + } + + return false; +} + +/* +================ +idTypeDef::MatchesType + +Returns true if both types' base types and parameters match +================ +*/ +bool idTypeDef::MatchesType( const idTypeDef &matchtype ) const { + int i; + + if ( this == &matchtype ) { + return true; + } + + if ( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchtype.parmTypes.Num() ) { + return false; + } + + for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::MatchesVirtualFunction + +Returns true if both functions' base types and parameters match +================ +*/ +bool idTypeDef::MatchesVirtualFunction( const idTypeDef &matchfunc ) const { + int i; + + if ( this == &matchfunc ) { + return true; + } + + if ( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchfunc.parmTypes.Num() ) { + return false; + } + + if ( parmTypes.Num() > 0 ) { + if ( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { + return false; + } + } + + for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::AddFunctionParm + +Adds a new parameter for a function type. +================ +*/ +void idTypeDef::AddFunctionParm( idTypeDef *parmtype, const char *name ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); + } + + parmTypes.Append( parmtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; +} + +/* +================ +idTypeDef::AddField + +Adds a new field to an object type. +================ +*/ +void idTypeDef::AddField( idTypeDef *fieldtype, const char *name ) { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); + } + + parmTypes.Append( fieldtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; + + if ( fieldtype->FieldType()->Inherits( &type_object ) ) { + size += type_object.Size(); + } else { + size += fieldtype->FieldType()->Size(); + } +} + +/* +================ +idTypeDef::SetName +================ +*/ +void idTypeDef::SetName( const char *newname ) { + name = newname; +} + +/* +================ +idTypeDef::Name +================ +*/ +const char *idTypeDef::Name( void ) const { + return name; +} + +/* +================ +idTypeDef::Type +================ +*/ +etype_t idTypeDef::Type( void ) const { + return type; +} + +/* +================ +idTypeDef::Size +================ +*/ +int idTypeDef::Size( void ) const { + return size; +} + +/* +================ +idTypeDef::SuperClass + +If type is an object, then returns the object's superclass +================ +*/ +idTypeDef *idTypeDef::SuperClass( void ) const { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::ReturnType + +If type is a function, then returns the function's return type +================ +*/ +idTypeDef *idTypeDef::ReturnType( void ) const { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetReturnType + +If type is a function, then sets the function's return type +================ +*/ +void idTypeDef::SetReturnType( idTypeDef *returntype ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); + } + + auxType = returntype; +} + +/* +================ +idTypeDef::FieldType + +If type is a field, then returns it's type +================ +*/ +idTypeDef *idTypeDef::FieldType( void ) const { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetFieldType + +If type is a field, then sets the function's return type +================ +*/ +void idTypeDef::SetFieldType( idTypeDef *fieldtype ) { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); + } + + auxType = fieldtype; +} + +/* +================ +idTypeDef::PointerType + +If type is a pointer, then returns the type it points to +================ +*/ +idTypeDef *idTypeDef::PointerType( void ) const { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetPointerType + +If type is a pointer, then sets the pointer's type +================ +*/ +void idTypeDef::SetPointerType( idTypeDef *pointertype ) { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); + } + + auxType = pointertype; +} + +/* +================ +idTypeDef::NumParameters +================ +*/ +int idTypeDef::NumParameters( void ) const { + return parmTypes.Num(); +} + +/* +================ +idTypeDef::GetParmType +================ +*/ +idTypeDef *idTypeDef::GetParmType( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmTypes[ parmNumber ]; +} + +/* +================ +idTypeDef::GetParmName +================ +*/ +const char *idTypeDef::GetParmName( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmNames[ parmNumber ]; +} + +/* +================ +idTypeDef::NumFunctions +================ +*/ +int idTypeDef::NumFunctions( void ) const { + return functions.Num(); +} + +/* +================ +idTypeDef::GetFunctionNumber +================ +*/ +int idTypeDef::GetFunctionNumber( const function_t *func ) const { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( functions[ i ] == func ) { + return i; + } + } + return -1; +} + +/* +================ +idTypeDef::GetFunction +================ +*/ +const function_t *idTypeDef::GetFunction( int funcNumber ) const { + assert( funcNumber >= 0 ); + assert( funcNumber < functions.Num() ); + return functions[ funcNumber ]; +} + +/* +================ +idTypeDef::AddFunction +================ +*/ +void idTypeDef::AddFunction( const function_t *func ) { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( !idStr::Cmp( functions[ i ]->def->Name(), func->def->Name() ) ) { + if ( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { + functions[ i ] = func; + return; + } + } + } + functions.Append( func ); +} + +// RAVEN BEGIN +// abahr +/* +================ +rvTypeDefInt::Parse +================ +*/ +int rvTypeDefInt::Parse( const char* source ) const { + int i; + + sscanf( source, Format(), &i ); + + return i; +} + +/* +================ +rvTypeDefInt::GetReturnedValAsString +================ +*/ +const char* rvTypeDefInt::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedInteger() ); +} + +/* +================ +rvTypeDefInt::PushOntoStack +================ +*/ +void rvTypeDefInt::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushInt( Parse(source) ); +} + +/* +================ +rvTypeDefInt::IsValid +================ +*/ +bool rvTypeDefInt::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ + rvTypeDefFloat::Parse +================ +*/ +float rvTypeDefFloat::Parse( const char* source ) const { + float f; + + sscanf( source, Format(), &f ); + + return f; +} + +/* +================ +rvTypeDefFloat::GetReturnedValAsString +================ +*/ +const char* rvTypeDefFloat::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedFloat() ); +} + +/* +================ +rvTypeDefFloat::PushOntoStack +================ +*/ +void rvTypeDefFloat::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushFloat( Parse(source) ); +} + +/* +================ +rvTypeDefFloat::IsValid +================ +*/ +bool rvTypeDefFloat::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ +rvTypeDefVec3::Parse +================ +*/ +idVec3 rvTypeDefVec3::Parse( const char* source ) const { + idVec3 v; + + sscanf( source, Format(), &v[0], &v[1], &v[2] ); + + return v; +} + +/* +================ +rvTypeDefVec3::GetReturnedValAsString +================ +*/ +const char* rvTypeDefVec3::GetReturnedValAsString( idProgram& program ) { + idVec3 v( program.GetReturnedVec3() ); + return va( Format(), v[0], v[1], v[2] ); +} + +/* +================ +rvTypeDefVec3::PushOntoStack +================ +*/ +void rvTypeDefVec3::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushVec3( Parse(source) ); +} + +/* +================ +rvTypeDefVec3::IsValid +================ +*/ +bool rvTypeDefVec3::IsValid( const char* source ) const { + //Looking for two ' ' + return idStr::FindChar(source, ' ', idStr::FindChar(source, ' ') ) != -1; +} + +/* +================ +rvTypeDefEntity::Parse +================ +*/ +idEntity* rvTypeDefEntity::Parse( const char* source ) const { + return gameLocal.FindEntity( source ); +} + +/* +================ +rvTypeDefEntity::GetReturnedValAsString +================ +*/ + +const char* rvTypeDefEntity::GetReturnedValAsString( idProgram& program ) { + idEntity* entity = program.GetReturnedEntity(); + return (entity) ? entity->GetName() : ""; +} + +/* +================ +rvTypeDefEntity::PushOntoStack +================ +*/ +void rvTypeDefEntity::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushEntity( Parse(source) ); +} + +/* +================ +rvTypeDefEntity::IsValid +================ +*/ +bool rvTypeDefEntity::IsValid( const char* source ) const { + return Parse( source ) != NULL; +} + +/* +================ +rvTypeDefString::Parse +================ +*/ +const char* rvTypeDefString::Parse( const char* source ) const { + return source; +} + +/* +================ +rvTypeDefString::GetReturnedValAsString +================ +*/ +const char* rvTypeDefString::GetReturnedValAsString( idProgram& program ) { + return program.GetReturnedString(); +} + +/* +================ +rvTypeDefString::PushOntoStack +================ +*/ +void rvTypeDefString::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushString( Parse(source) ); +} + +/* +================ +vTypeDefString::IsValid +================ +*/ +bool rvTypeDefString::IsValid( const char* source ) const { + return true; +} + +/* +================ +rvTypeDefBool::Parse +================ +*/ +bool rvTypeDefBool::Parse( const char* source ) const { + unsigned int b; + + sscanf( source, Format(), &b ); + + return !!b; +} + +/* +================ +rvTypeDefBool::GetReturnedValAsString +================ +*/ +const char* rvTypeDefBool::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedBool() ); +} + +/* +================ +rvTypeDefBool::PushOntoStack +================ +*/ +void rvTypeDefBool::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushBool( Parse(source) ); +} + +/* +================ +rvTypeDefBool::IsValid +================ +*/ +bool rvTypeDefBool::IsValid( const char* source ) const { + return !idStr::Icmp(source, "true") || !idStr::Icmp(source, "false") || !idStr::Icmp(source, "1") || !idStr::Icmp(source, "0"); +} + +/* +================ +idProgram::GetReturnedEntity +================ +*/ +idEntity* idProgram::GetReturnedEntity() { + //This is here because gameLocal isn't known about yet in the header + int entityNumber = *returnDef->value.entityNumberPtr; + if( !entityNumber ) { + return NULL; + } + + return gameLocal.entities[ entityNumber - 1 ]; +} +// RAVEN END + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +================ +idVarDef::idVarDef() +================ +*/ +idVarDef::idVarDef( idTypeDef *typeptr ) { + typeDef = typeptr; + num = 0; + scope = NULL; + numUsers = 0; + initialized = idVarDef::uninitialized; + memset( &value, 0, sizeof( value ) ); + name = NULL; + next = NULL; +} + +/* +============ +idVarDef::~idVarDef +============ +*/ +idVarDef::~idVarDef() { + if ( name ) { + name->RemoveDef( this ); + } +} + +/* +============ +idVarDef::Name +============ +*/ +const char *idVarDef::Name( void ) const { + return name->Name(); +} + +/* +============ +idVarDef::GlobalName +============ +*/ +const char *idVarDef::GlobalName( void ) const { + if ( scope != &def_namespace ) { + return va( "%s::%s", scope->GlobalName(), name->Name() ); + } else { + return name->Name(); + } +} + +/* +============ +idVarDef::DepthOfScope +============ +*/ +int idVarDef::DepthOfScope( const idVarDef *otherScope ) const { + const idVarDef *def; + int depth; + + depth = 1; + for( def = otherScope; def != NULL; def = def->scope ) { + if ( def == scope ) { + return depth; + } + depth++; + } + + return 0; +} + +/* +============ +idVarDef::SetFunction +============ +*/ +void idVarDef::SetFunction( function_t *func ) { + assert( typeDef ); + initialized = initializedConstant; + assert( typeDef->Type() == ev_function ); + value.functionPtr = func; +} + +/* +============ +idVarDef::SetObject +============ +*/ +void idVarDef::SetObject( idScriptObject *object ) { + assert( typeDef ); + initialized = initialized; + assert( typeDef->Inherits( &type_object ) ); + *value.objectPtrPtr = object; +} + +/* +============ +idVarDef::SetValue +============ +*/ +void idVarDef::SetValue( const eval_t &_value, bool constant ) { + assert( typeDef ); + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + switch( typeDef->Type() ) { + case ev_pointer : + case ev_boolean : + case ev_field : + *value.intPtr = _value._int; + break; + + case ev_jumpoffset : + value.jumpOffset = _value._int; + break; + + case ev_argsize : + value.argSize = _value._int; + break; + + case ev_entity : + *value.entityNumberPtr = _value.entity; + break; + + case ev_string : + idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); + break; + + case ev_float : + *value.floatPtr = _value._float; + break; + + case ev_vector : + value.vectorPtr->x = _value.vector[ 0 ]; + value.vectorPtr->y = _value.vector[ 1 ]; + value.vectorPtr->z = _value.vector[ 2 ]; + break; + + case ev_function : + value.functionPtr = _value.function; + break; + + case ev_virtualfunction : + value.virtualFunction = _value._int; + break; + + case ev_object : + *value.entityNumberPtr = _value.entity; + break; + + default : + throw idCompileError( va( "weird type on '%s'", Name() ) ); + break; + } +} + +/* +============ +idVarDef::SetString +============ +*/ +void idVarDef::SetString( const char *string, bool constant ) { + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + assert( typeDef && ( typeDef->Type() == ev_string ) ); + idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +============ +idVarDef::PrintInfo +============ +*/ +void idVarDef::PrintInfo( idFile *file, int instructionPointer ) const { + statement_t *jumpst; + int jumpto; + etype_t etype; + int i; + int len; + const char *ch; + + if ( initialized == initializedConstant ) { + file->Printf( "const " ); + } + + etype = typeDef->Type(); + switch( etype ) { + case ev_jumpoffset : + jumpto = instructionPointer + value.jumpOffset; + jumpst = &gameLocal.program.GetStatement( jumpto ); + file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); + break; + + case ev_function : + if ( value.functionPtr->eventdef ) { + file->Printf( "event %s", GlobalName() ); + } else { + file->Printf( "function %s", GlobalName() ); + } + break; + + case ev_field : + file->Printf( "field %d", value.ptrOffset ); + break; + + case ev_argsize: + file->Printf( "args %d", value.argSize ); + break; + + default: + file->Printf( "%s ", typeDef->Name() ); + if ( initialized == initializedConstant ) { + switch( etype ) { + case ev_string : + file->Printf( "\"" ); + len = strlen( value.stringPtr ); + ch = value.stringPtr; + for( i = 0; i < len; i++, ch++ ) { + if ( idStr::CharIsPrintable( *ch ) ) { + file->Printf( "%c", *ch ); + } else if ( *ch == '\n' ) { + file->Printf( "\\n" ); + } else { + file->Printf( "\\x%.2x", static_cast( *ch ) ); + } + } + file->Printf( "\"" ); + break; + + case ev_vector : + file->Printf( "'%s'", value.vectorPtr->ToString() ); + break; + + case ev_float : + file->Printf( "%f", *value.floatPtr ); + break; + + case ev_virtualfunction : + file->Printf( "vtable[ %d ]", value.virtualFunction ); + break; + + default : + file->Printf( "%d", *value.intPtr ); + break; + } + } else if ( initialized == stackVariable ) { + file->Printf( "stack[%d]", value.stackOffset ); + } else { + file->Printf( "global[%d]", num ); + } + break; + } +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +============ +idVarDefName::AddDef +============ +*/ +void idVarDefName::AddDef( idVarDef *def ) { + assert( def->next == NULL ); + def->name = this; + def->next = defs; + defs = def; +} + +/* +============ +idVarDefName::RemoveDef +============ +*/ +void idVarDefName::RemoveDef( idVarDef *def ) { + if ( defs == def ) { + defs = def->next; + } else { + for ( idVarDef *d = defs; d->next != NULL; d = d->next ) { + if ( d->next == def ) { + d->next = def->next; + break; + } + } + } + def->next = NULL; + def->name = NULL; +} + +/*********************************************************************** + + idScriptObject + +***********************************************************************/ + +/* +============ +idScriptObject::idScriptObject +============ +*/ +idScriptObject::idScriptObject() { + data = NULL; + type = &type_object; +} + +/* +============ +idScriptObject::~idScriptObject +============ +*/ +idScriptObject::~idScriptObject() { + Free(); +} + +/* +============ +idScriptObject::Free +============ +*/ +void idScriptObject::Free( void ) { + if ( data ) { + Mem_Free( data ); + } + + data = NULL; + type = &type_object; +} + +/* +================ +idScriptObject::Save +================ +*/ +void idScriptObject::Save( idSaveGame *savefile ) const { + size_t size; + + if ( type == &type_object && data == NULL ) { + // Write empty string for uninitialized object + savefile->WriteString( "" ); + } else { + savefile->WriteString( type->Name() ); + size = type->Size(); + savefile->WriteInt( size ); + savefile->Write( data, size ); + } +} + +/* +================ +idScriptObject::Restore +================ +*/ +void idScriptObject::Restore( idRestoreGame *savefile ) { + idStr typeName; + size_t size; + + savefile->ReadString( typeName ); + + // Empty string signals uninitialized object + if ( typeName.Length() == 0 ) { + return; + } + + if ( !SetType( typeName ) ) { + savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); + } + + savefile->ReadInt( (int &)size ); + if ( size != type->Size() ) { + savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); + } + + savefile->Read( data, size ); +} + +/* +============ +idScriptObject::SetType + +Allocates an object and initializes memory. +============ +*/ +bool idScriptObject::SetType( const char *typeName ) { + size_t size; + idTypeDef *newtype; + + // lookup the type + newtype = gameLocal.program.FindType( typeName ); + + // only allocate memory if the object type changes + if ( newtype != type ) { + Free(); + if ( !newtype ) { + gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); + return false; + } + + if ( !newtype->Inherits( &type_object ) ) { + gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); + return false; + } + + // set the type + type = newtype; + + // allocate the memory + size = type->Size(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + data = ( byte * )Mem_Alloc( size, MA_SCRIPT ); +//RAVEN END + } + + // init object memory + ClearObject(); + + return true; +} + +/* +============ +idScriptObject::ClearObject + +Resets the memory for the script object without changing its type. +============ +*/ +void idScriptObject::ClearObject( void ) { + size_t size; + + if ( type != &type_object ) { + // init object memory + size = type->Size(); + memset( data, 0, size ); + } +} + +/* +============ +idScriptObject::HasObject +============ +*/ +bool idScriptObject::HasObject( void ) const { + return ( type != &type_object ); +} + +/* +============ +idScriptObject::GetTypeDef +============ +*/ +idTypeDef *idScriptObject::GetTypeDef( void ) const { + return type; +} + +/* +============ +idScriptObject::GetTypeName +============ +*/ +const char *idScriptObject::GetTypeName( void ) const { + return type->Name(); +} + +/* +============ +idScriptObject::GetConstructor +============ +*/ +const function_t *idScriptObject::GetConstructor( void ) const { + const function_t *func; + + func = GetFunction( "init" ); + return func; +} + +/* +============ +idScriptObject::GetDestructor +============ +*/ +const function_t *idScriptObject::GetDestructor( void ) const { + const function_t *func; + + func = GetFunction( "destroy" ); + return func; +} + +/* +============ +idScriptObject::GetFunction +============ +*/ +const function_t *idScriptObject::GetFunction( const char *name ) const { + const function_t *func; + + if ( type == &type_object ) { + return NULL; + } + + func = gameLocal.program.FindFunction( name, type ); + return func; +} + +/* +============ +idScriptObject::GetVariable +============ +*/ +byte *idScriptObject::GetVariable( const char *name, etype_t etype ) const { + int i; + int pos; + const idTypeDef *t; + const idTypeDef *parm; + + if ( type == &type_object ) { + return NULL; + } + + t = type; + do { + if ( t->SuperClass() != &type_object ) { + pos = t->SuperClass()->Size(); + } else { + pos = 0; + } + for( i = 0; i < t->NumParameters(); i++ ) { + parm = t->GetParmType( i ); + if ( !idStr::Cmp( t->GetParmName( i ), name ) ) { + if ( etype != parm->FieldType()->Type() ) { + return NULL; + } + return &data[ pos ]; + } + + if ( parm->FieldType()->Inherits( &type_object ) ) { + pos += type_object.Size(); + } else { + pos += parm->FieldType()->Size(); + } + } + t = t->SuperClass(); + } while( t && ( t != &type_object ) ); + + return NULL; +} + +/*********************************************************************** + + idProgram + +***********************************************************************/ + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( idTypeDef &type ) { + idTypeDef *newtype; + + newtype = new idTypeDef( type ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + idTypeDef *newtype; + + newtype = new idTypeDef( etype, edef, ename, esize, aux ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::GetType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +idTypeDef *idProgram::GetType( idTypeDef &type, bool allocate ) { + int i; + + //FIXME: linear search == slow + for( i = types.Num() - 1; i >= 0; i-- ) { + if ( types[ i ]->MatchesType( type ) && !idStr::Cmp( types[ i ]->Name(), type.Name() ) ) { + return types[ i ]; + } + } + + if ( !allocate ) { + return NULL; + } + + // allocate a new one + return AllocType( type ); +} + +/* +============ +idProgram::FindType + +Returns a preexisting complex type that matches the name, or returns NULL if not found +============ +*/ +idTypeDef *idProgram::FindType( const char *name ) { + idTypeDef *check; + int i; + + for( i = types.Num() - 1; i >= 0; i-- ) { + check = types[ i ]; + if ( !idStr::Cmp( check->Name(), name ) ) { + return check; + } + } + + return NULL; +} + +/* +============ +idProgram::GetDefList +============ +*/ +idVarDef *idProgram::GetDefList( const char *name ) const { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + return varDefNames[i]->GetDefs(); + } + } + return NULL; +} + +/* +============ +idProgram::AddDefToNameList +============ +*/ +void idProgram::AddDefToNameList( idVarDef *def, const char *name ) { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + break; + } + } + if ( i == -1 ) { + i = varDefNames.Append( new idVarDefName( name ) ); + varDefNameHash.Add( hash, i ); + } + varDefNames[i]->AddDef( def ); +} + +/* +============ +idProgram::AllocDef +============ +*/ +idVarDef *idProgram::AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ) { + idVarDef *def; + idStr element; + idVarDef *def_x; + idVarDef *def_y; + idVarDef *def_z; + + // allocate a new def + def = new idVarDef( type ); + def->scope = scope; + def->numUsers = 1; + def->num = varDefs.Append( def ); + + // add the def to the list with defs with this name and set the name pointer + AddDefToNameList( def, name ); + + if ( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { + // + // vector + // + if ( !idStr::Cmp( name, RESULT_STRING ) ) { + // vector defs don't need the _x, _y and _z components + assert( scope->Type() == ev_function ); + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + scope->value.functionPtr->locals += type->Size(); + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); + idTypeDef *type = GetType( newtype, true ); + + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( type, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( type, element, scope, constant ); + def_y->value.ptrOffset = def_x->value.ptrOffset + type_float.Size(); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( type, element, scope, constant ); + def_z->value.ptrOffset = def_y->value.ptrOffset + type_float.Size(); + } else { + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( &type_float, element, scope, constant ); + + // point the vector def to the x coordinate + def->value = def_x->value; + def->initialized = def_x->initialized; + } + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + // + // object variable + // + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + } else if ( scope->Type() == ev_function ) { + // + // stack variable + // + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + + if ( type->Inherits( &type_object ) ) { + // objects only have their entity number on the stack, not the entire object + scope->value.functionPtr->locals += type_object.Size(); + } else { + scope->value.functionPtr->locals += type->Size(); + } + } else { + // + // global variable + // + def->value.bytePtr = &variables[ numVariables ]; + numVariables += def->TypeDef()->Size(); + if ( numVariables > sizeof( variables ) ) { + throw idCompileError( va( "Exceeded global memory size (%d bytes)", sizeof( variables ) ) ); + } + + memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); + } + + return def; +} + +/* +============ +idProgram::GetDef + +If type is NULL, it will match any type +============ +*/ +idVarDef *idProgram::GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const { + idVarDef *def; + idVarDef *bestDef; + int bestDepth; + int depth; + + bestDepth = 0; + bestDef = NULL; + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def->scope->Type() == ev_namespace ) { + depth = def->DepthOfScope( scope ); + if ( !depth ) { + // not in the same namespace + continue; + } + } else if ( def->scope != scope ) { + // in a different function + continue; + } else { + depth = 1; + } + + if ( !bestDef || ( depth < bestDepth ) ) { + bestDepth = depth; + bestDef = def; + } + } + + // see if the name is already in use for another type + if ( bestDef && type && ( bestDef->TypeDef() != type ) ) { + throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); + } + + return bestDef; +} + +/* +============ +idProgram::FreeDef +============ +*/ +void idProgram::FreeDef( idVarDef *def, const idVarDef *scope ) { + idVarDef *e; + int i; + + if ( def->Type() == ev_vector ) { + idStr name; + + sprintf( name, "%s_x", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_y", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_z", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + } + + varDefs.RemoveIndex( def->num ); + for( i = def->num; i < varDefs.Num(); i++ ) { + varDefs[ i ]->num = i; + } + + delete def; +} + +/* +============ +idProgram::FindFreeResultDef +============ +*/ +idVarDef *idProgram::FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ) { + idVarDef *def; + + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def == a || def == b ) { + continue; + } + if ( def->TypeDef() != type ) { + continue; + } + if ( def->scope != scope ) { + continue; + } + if ( def->numUsers <= 1 ) { + continue; + } + return def; + } + + return AllocDef( type, name, scope, false ); +} + +/* +================ +idProgram::FindFunction + +Searches for the specified function in the currently loaded script. A full namespace should be +specified if not in the global namespace. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name ) const { + int start; + int pos; + idVarDef *namespaceDef; + idVarDef *def; + + assert( name ); + + idStr fullname = name; + start = 0; + namespaceDef = &def_namespace; + do { + pos = fullname.Find( "::", true, start ); + if ( pos < 0 ) { + break; + } + + idStr namespaceName = fullname.Mid( start, pos - start ); + def = GetDef( NULL, namespaceName, namespaceDef ); + if ( !def ) { + // couldn't find namespace + return NULL; + } + namespaceDef = def; + + // skip past the :: + start = pos + 2; + } while( def->Type() == ev_namespace ); + + idStr funcName = fullname.Right( fullname.Length() - start ); + def = GetDef( NULL, funcName, namespaceDef ); + if ( !def ) { + // couldn't find function + return NULL; + } + + if ( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { + return def->value.functionPtr; + } + + // is not a function, or is an eventdef + return NULL; +} + +// RAVEN BEGIN +// bgeisler: list functions +/* +================ +idProgram::ListFunctions +================ +*/ +void idProgram::ListStates( void ) +{ + gameLocal.Printf( "Script States: \n"); + // function 0 is a NULL function + for( int i = 1; i < functions.Num(); i++ ) + { + gameLocal.Printf( "%s \n", functions[ i ].Name() ); + } + +} +// RAVEN END + +/* +================ +idProgram::FindFunction + +Searches for the specified object function in the currently loaded script. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name, const idTypeDef *type ) const { + const idVarDef *tdef; + const idVarDef *def; + + // look for the function + def = NULL; + for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = GetDef( NULL, name, tdef ); + if ( def ) { + return def->value.functionPtr; + } + } + + return NULL; +} + +/* +================ +idProgram::AllocFunction +================ +*/ +function_t &idProgram::AllocFunction( idVarDef *def ) { + if ( functions.Num() >= functions.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); + } + + // fill in the dfunction + function_t &func = *functions.Alloc(); + func.eventdef = NULL; + func.def = def; + func.type = def->TypeDef(); + func.firstStatement = 0; + func.numStatements = 0; + func.parmTotal = 0; + func.locals = 0; + func.filenum = filenum; + func.parmSize.SetGranularity( 1 ); + func.SetName( def->GlobalName() ); + + def->SetFunction( &func ); + + return func; +} + +/* +================ +idProgram::SetEntity +================ +*/ +void idProgram::SetEntity( const char *name, idEntity *ent ) { + idVarDef *def; + idStr defName( "$" ); + + defName += name; + + def = GetDef( &type_entity, defName, &def_namespace ); + if ( def && ( def->initialized != idVarDef::stackVariable ) ) { + // 0 is reserved for NULL entity + if ( !ent ) { + *def->value.entityNumberPtr = 0; + } else { + *def->value.entityNumberPtr = ent->entityNumber + 1; + } + } +} + +/* +================ +idProgram::AllocStatement +================ +*/ +statement_t *idProgram::AllocStatement( void ) { + if ( statements.Num() >= statements.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); + } + return statements.Alloc(); +} + +/* +============== +idProgram::BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void idProgram::BeginCompilation( void ) { + statement_t *statement; + + FreeData(); + + try { + // make the first statement a return for a "NULL" function + statement = AllocStatement(); + statement->linenumber = 0; + statement->file = 0; + statement->op = OP_RETURN; + statement->a = NULL; + statement->b = NULL; + statement->c = NULL; + + // define NULL + //AllocDef( &type_void, "", &def_namespace, true ); + + // define the return def + returnDef = AllocDef( &type_vector, "", &def_namespace, false ); + + // define the return def for strings + returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); + + // define the sys object + sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); + } + + catch( idCompileError &err ) { + gameLocal.Error( "%s", err.error ); + } +} + +/* +============== +idProgram::DisassembleStatement +============== +*/ +void idProgram::DisassembleStatement( idFile *file, int instructionPointer ) const { + opcode_t *op; + const statement_t *statement; + + statement = &statements[ instructionPointer ]; + op = &idCompiler::opcodes[ statement->op ]; + file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); + + if ( statement->a ) { + file->Printf( "\ta: " ); + statement->a->PrintInfo( file, instructionPointer ); + } + + if ( statement->b ) { + file->Printf( "\tb: " ); + statement->b->PrintInfo( file, instructionPointer ); + } + + if ( statement->c ) { + file->Printf( "\tc: " ); + statement->c->PrintInfo( file, instructionPointer ); + } + + file->Printf( "\n" ); +} + +/* +============== +idProgram::Disassemble +============== +*/ +void idProgram::Disassemble( void ) const { + int i; + int instructionPointer; + const function_t *func; + idFile *file; + + file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); + + for( i = 0; i < functions.Num(); i++ ) { + func = &functions[ i ]; + if ( func->eventdef ) { + // skip eventdefs + continue; + } + + file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); + + for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { + DisassembleStatement( file, func->firstStatement + instructionPointer ); + } + + file->Printf( "}\n" ); + } + + fileSystem->CloseFile( file ); +} + +/* +============== +idProgram::FinishCompilation + +Called after all files are compiled to check for errors +============== +*/ +void idProgram::FinishCompilation( void ) { + int i; + + top_functions = functions.Num(); + top_statements = statements.Num(); + top_types = types.Num(); + top_defs = varDefs.Num(); + top_files = fileList.Num(); + + variableDefaults.Clear(); + variableDefaults.SetNum( numVariables ); + + for( i = 0; i < numVariables; i++ ) { + variableDefaults[ i ] = variables[ i ]; + } +} + +/* +============== +idProgram::CompileStats + +called after all files are compiled to report memory usage. +============== +*/ +void idProgram::CompileStats( void ) { + int memused; + int memallocated; + int numdefs; + int stringspace; + int funcMem; + int i; + + gameLocal.Printf( "-------------- Compile stats ----------------\n" ); + gameLocal.DPrintf( "Files loaded:\n" ); + + stringspace = 0; + for( i = 0; i < fileList.Num(); i++ ) { + gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); + stringspace += fileList[ i ].Allocated(); + } + stringspace += fileList.Size(); + + numdefs = varDefs.Num(); + memused = varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + memused += stringspace; + + for( i = 0; i < types.Num(); i++ ) { + memused += types[ i ]->Allocated(); + } + + funcMem = functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + funcMem += functions[ i ].Allocated(); + } + + memallocated = funcMem + memused + sizeof( idProgram ); + + memused += statements.MemoryUsed(); + memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them + memused += sizeof( variables ); + + gameLocal.Printf( "\nMemory usage:\n" ); + gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); + gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); + gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); + gameLocal.Printf( " Variables: %d bytes\n", numVariables ); + gameLocal.Printf( " Mem used: %d bytes\n", memused ); + gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); + gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); + gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); +} + +// RAVEN BEGIN +// jscott: summary of script memory usage +/* +================ +idProgram::ScriptSummary +================ +*/ +size_t idProgram::ScriptSummary( const idCmdArgs &args ) { + + int memused; + int i; + + memused = 0; + for( i = 0; i < fileList.Num(); i++ ) { + + memused += fileList[i].Allocated(); + } + memused += fileList.Size(); + + memused += varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + + for( i = 0; i < types.Num(); i++ ) { + + memused += types[i]->Allocated(); + } + + memused += functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + + memused += functions[i].Allocated(); + } + + memused += statements.MemoryUsed(); + memused += sizeof( variables ); + memused += sizeof( idProgram ); + memused += sizeof( idThread ); + + common->Printf( "Scripts - %dK\n", memused >> 10 ); + + return( memused >> 10 ); +} +// RAVEN END + +/* +================ +idProgram::CompileText +================ +*/ +bool idProgram::CompileText( const char *source, const char *text, bool console ) { + idCompiler compiler; + int i; + idVarDef *def; + idStr ospath; + +// RAVEN BEGIN +// bdube: Make sure the file hasnt already been loaded + if ( -1 != fileList.FindIndex ( idStr(source) ) ) { + return true; + } +// RAVEN END + + // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser + ospath = fileSystem->RelativePathToOSPath( source ); + filenum = GetFilenum( ospath ); + + try { + compiler.CompileFile( text, filename, console ); + + // check to make sure all functions prototyped have code + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + if ( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { + if ( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { + throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); + } + } + } + } + + catch( idCompileError &err ) { + if ( console ) { + gameLocal.Printf( "%s\n", err.error ); + return false; + } else { + gameLocal.Error( "%s\n", err.error ); + } + }; + + if ( !console ) { + CompileStats(); + } + + return true; +} + +/* +================ +idProgram::CompileFunction +================ +*/ +const function_t *idProgram::CompileFunction( const char *functionName, const char *text ) { + bool result; + + result = CompileText( functionName, text, false ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed." ); + } + + return FindFunction( functionName ); +} + +/* +================ +idProgram::CompileFile +================ +*/ +void idProgram::CompileFile( const char *filename ) { + char *src; + bool result; + + if ( fileSystem->ReadFile( filename, ( void ** )&src, NULL ) < 0 ) { + gameLocal.Error( "Couldn't load %s\n", filename ); + } + + result = CompileText( filename, src, false ); + + fileSystem->FreeFile( src ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed in file %s.", filename ); + } +} + +/* +================ +idProgram::FreeData +================ +*/ +void idProgram::FreeData( void ) { + int i; + + // free the defs + varDefs.DeleteContents( true ); + varDefNames.DeleteContents( true ); + varDefNameHash.Free(); + + returnDef = NULL; + returnStringDef = NULL; + sysDef = NULL; + + // free any special types we've created + types.DeleteContents( true ); + + filenum = 0; + + numVariables = 0; + memset( variables, 0, sizeof( variables ) ); + + // clear all the strings in the functions so that it doesn't look like we're leaking memory. + for( i = 0; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + + filename.Clear(); + fileList.Clear(); + statements.Clear(); + functions.Clear(); + + top_functions = 0; + top_statements = 0; + top_types = 0; + top_defs = 0; + top_files = 0; + + filename = ""; +} + +/* +================ +idProgram::Startup +================ +*/ +void idProgram::Startup( const char *defaultScript ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag, MA_SCRIPT); +// RAVEN END + gameLocal.Printf( "Initializing scripts\n" ); + + // make sure all data is freed up + idThread::Restart(); + + // get ready for loading scripts + BeginCompilation(); + + // load the default script + if ( defaultScript && *defaultScript ) { + CompileFile( defaultScript ); + } + + FinishCompilation(); +} + +/* +================ +idProgram::Save +================ +*/ +void idProgram::Save( idSaveGame *savefile ) const { + int i; + int currentFileNum = top_files; + + savefile->WriteInt( (fileList.Num() - currentFileNum) ); + while ( currentFileNum < fileList.Num() ) { + savefile->WriteString( fileList[ currentFileNum ] ); + currentFileNum++; + } + savefile->WriteString( filename ); // cnicholson: Added unsaved var + savefile->WriteInt ( filenum ); // cnicholson: Added unsaved var + + for ( i = 0; i < variableDefaults.Num(); i++ ) { + if ( variables[i] != variableDefaults[i] ) { + savefile->WriteInt( i ); + savefile->WriteByte( variables[i] ); + } + } + // Mark the end of the diff with default variables with -1 + savefile->WriteInt( -1 ); + + savefile->WriteInt( numVariables ); + for ( i = variableDefaults.Num(); i < numVariables; i++ ) { + savefile->WriteByte( variables[i] ); + } + + savefile->WriteInt ( top_functions ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_statements ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_types ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_defs ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_files ); // cnicholson: Added unsaved var + + int checksum = CalculateChecksum(); + savefile->WriteInt( checksum ); +} + +/* +================ +idProgram::Restore +================ +*/ +bool idProgram::Restore( idRestoreGame *savefile ) { + int i, num, index; + bool result = true; + idStr scriptname; + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( scriptname ); + CompileFile( scriptname ); + } + + savefile->ReadString( filename ); // cnicholson: Added unrestored var + savefile->ReadInt ( filenum ); // cnicholson: Added unrestored var + + savefile->ReadInt( index ); + while( index >= 0 ) { + savefile->ReadByte( variables[index] ); + savefile->ReadInt( index ); + } + + savefile->ReadInt( num ); + for ( i = variableDefaults.Num(); i < num; i++ ) { + savefile->ReadByte( variables[i] ); + } + + savefile->ReadInt ( top_functions ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_statements ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_types ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_defs ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_files ); // cnicholson: Added unrestored var + + int saved_checksum, checksum; + + savefile->ReadInt( saved_checksum ); + checksum = CalculateChecksum(); + + if ( saved_checksum != checksum ) { + result = false; + } + + return result; +} + +/* +================ +idProgram::CalculateChecksum +================ +*/ +int idProgram::CalculateChecksum( void ) const { + int i, result; + + typedef struct { + unsigned short op; + int a; + int b; + int c; + unsigned short linenumber; + unsigned short file; + } statementBlock_t; + + statementBlock_t *statementList = new statementBlock_t[ statements.Num() ]; + + memset( statementList, 0, ( sizeof(statementBlock_t) * statements.Num() ) ); + + // Copy info into new list, using the variable numbers instead of a pointer to the variable + for( i = 0; i < statements.Num(); i++ ) { + statementList[i].op = statements[i].op; + + if ( statements[i].a ) { + statementList[i].a = statements[i].a->num; + } else { + statementList[i].a = -1; + } + if ( statements[i].b ) { + statementList[i].b = statements[i].b->num; + } else { + statementList[i].b = -1; + } + if ( statements[i].c ) { + statementList[i].c = statements[i].c->num; + } else { + statementList[i].c = -1; + } + + statementList[i].linenumber = statements[i].linenumber; + statementList[i].file = statements[i].file; + } + + result = MD4_BlockChecksum( statementList, ( sizeof(statementBlock_t) * statements.Num() ) ); + + delete [] statementList; + + return result; +} + +/* +============== +idProgram::Restart + +Restores all variables to their initial value +============== +*/ +void idProgram::Restart( void ) { + int i; + + idThread::Restart(); + + // + // since there may have been a script loaded by the map or the user may + // have typed "script" from the console, free up any types and vardefs that + // have been allocated after the initial startup + // + for( i = top_types; i < types.Num(); i++ ) { + delete types[ i ]; + } + types.SetNum( top_types, false ); + + for( i = top_defs; i < varDefs.Num(); i++ ) { + delete varDefs[ i ]; + } + varDefs.SetNum( top_defs, false ); + + for( i = top_functions; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + functions.SetNum( top_functions ); + + statements.SetNum( top_statements ); + fileList.SetNum( top_files, false ); + filename.Clear(); + + // reset the variables to their default values + numVariables = variableDefaults.Num(); + for( i = 0; i < numVariables; i++ ) { + variables[ i ] = variableDefaults[ i ]; + } +} + +/* +================ +idProgram::GetFilenum +================ +*/ +int idProgram::GetFilenum( const char *name ) { + if ( filename == name ) { + return filenum; + } + + idStr strippedName; + strippedName = fileSystem->OSPathToRelativePath( name ); + if ( !strippedName.Length() ) { + // not off the base path so just use the full path + filenum = fileList.AddUnique( name ); + } else { + filenum = fileList.AddUnique( strippedName ); + } + + // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum + filename = name; + + return filenum; +} + +/* +================ +idProgram::idProgram +================ +*/ +idProgram::idProgram() { + FreeData(); +} + +/* +================ +idProgram::~idProgram +================ +*/ +idProgram::~idProgram() { + FreeData(); +} + +// RAVEN BEGIN +// jscott: for debug with inlines and memory log +/* +================ +idProgram::Shutdown +================ +*/ +void idProgram::Shutdown( void ) +{ + FreeData(); +} +// RAVEN END + +/* +================ +idProgram::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idProgram::ReturnEntity( const idEntity *ent ) { + if ( ent ) { + *returnDef->value.entityNumberPtr = ent->entityNumber + 1; + } else { + *returnDef->value.entityNumberPtr = 0; + } +} + diff --git a/source/game/script/Script_Program.h b/source/game/script/Script_Program.h new file mode 100644 index 0000000..b050f36 --- /dev/null +++ b/source/game/script/Script_Program.h @@ -0,0 +1,797 @@ + +#ifndef __SCRIPT_PROGRAM_H__ +#define __SCRIPT_PROGRAM_H__ + +class idScriptObject; +class idEventDef; +class idVarDef; +class idTypeDef; +class idEntity; +class idThread; +class idSaveGame; +class idRestoreGame; + +#define MAX_STRING_LEN 128 +// RAVEN BEGIN +// ddynerman: higher max limit, initially 196608 +// jshepard: ... then 393216 +#define MAX_GLOBALS 589824 // in bytes + +// jshepard: raise the limits. Formerly 1024, 3072 and 81920. +#define MAX_STRINGS 2048 +#define MAX_FUNCS 6144 +#define MAX_STATEMENTS 163840 // statement_t - 18 bytes last I checked +// RAVEN END +typedef enum { + ev_error = -1, ev_void, ev_scriptevent, ev_namespace, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_virtualfunction, ev_pointer, ev_object, ev_jumpoffset, ev_argsize, ev_boolean +} etype_t; + +class function_t { +public: + function_t(); + + size_t Allocated( void ) const; + void SetName( const char *name ); + const char *Name( void ) const; + void Clear( void ); + +private: + idStr name; +public: + const idEventDef *eventdef; + idVarDef *def; + const idTypeDef *type; + int firstStatement; + int numStatements; + int parmTotal; + int locals; // total ints of parms + locals + int filenum; // source file defined in + idList parmSize; +}; + +typedef union eval_s { + const char *stringPtr; + float _float; + float vector[ 3 ]; + function_t *function; + int _int; + int entity; +} eval_t; + +/*********************************************************************** + +idTypeDef + +Contains type information for variables and functions. + +***********************************************************************/ + +class idTypeDef { +private: + etype_t type; + idStr name; + int size; + + // function types are more complex + idTypeDef *auxType; // return type + idList parmTypes; + idStrList parmNames; + idList functions; + +public: + idVarDef *def; // a def that points to this type + + idTypeDef( const idTypeDef &other ); + idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + virtual ~idTypeDef( void ) { } + void operator=( const idTypeDef& other ); + size_t Allocated( void ) const; + + bool Inherits( const idTypeDef *basetype ) const; + bool MatchesType( const idTypeDef &matchtype ) const; + bool MatchesVirtualFunction( const idTypeDef &matchfunc ) const; + void AddFunctionParm( idTypeDef *parmtype, const char *name ); + void AddField( idTypeDef *fieldtype, const char *name ); + + void SetName( const char *newname ); + const char *Name( void ) const; + + etype_t Type( void ) const; + int Size( void ) const; + + idTypeDef *SuperClass( void ) const; + + idTypeDef *ReturnType( void ) const; + void SetReturnType( idTypeDef *type ); + + idTypeDef *FieldType( void ) const; + void SetFieldType( idTypeDef *type ); + + idTypeDef *PointerType( void ) const; + void SetPointerType( idTypeDef *type ); + + int NumParameters( void ) const; + idTypeDef *GetParmType( int parmNumber ) const; + const char *GetParmName( int parmNumber ) const; + + int NumFunctions( void ) const; + int GetFunctionNumber( const function_t *func ) const; + const function_t *GetFunction( int funcNumber ) const; + void AddFunction( const function_t *func ); + +// RAVEN BEGIN + // abahr + virtual const char* GetReturnedValAsString( idProgram& program ) { return ""; } + virtual void PushOntoStack( idThread* thread, const char* source ) { assert(0); } + virtual bool IsValid( const char* source ) const { return true; } + virtual const char* Format() const { return ""; } +// RAVEN END +}; + +// RAVEN BEGIN +// abahr: subclasses for overwritting helper functions + +/*********************************************************************** + +rvTypeDefInt + +***********************************************************************/ +class rvTypeDefInt : public idTypeDef { +public: + rvTypeDefInt( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefInt( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%d"; } + int Parse( const char* source ) const; + +}; + +/*********************************************************************** + +rvTypeDefFloat + +***********************************************************************/ +class rvTypeDefFloat : public idTypeDef { +public: + rvTypeDefFloat( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefFloat( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f"; } + float Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefVec3 + +***********************************************************************/ +class rvTypeDefVec3 : public idTypeDef { +public: + rvTypeDefVec3( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefVec3( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f %f %f"; } + idVec3 Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefEntity + +***********************************************************************/ +class rvTypeDefEntity : public idTypeDef { +public: + rvTypeDefEntity( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefEntity( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + idEntity* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefString + +***********************************************************************/ +class rvTypeDefString : public idTypeDef { +public: + rvTypeDefString( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefString( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefBool + +***********************************************************************/ +class rvTypeDefBool : public idTypeDef { +public: + rvTypeDefBool( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefBool( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%u"; } + bool Parse( const char* source ) const; +}; +// RAVEN END + +/*********************************************************************** + +idScriptObject + +In-game representation of objects in scripts. Use the idScriptVariable template +(below) to access variables. + +***********************************************************************/ + +class idScriptObject { +private: + idTypeDef *type; + +public: + byte *data; + + idScriptObject(); + ~idScriptObject(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Free( void ); + bool SetType( const char *typeName ); + void ClearObject( void ); + bool HasObject( void ) const; + idTypeDef *GetTypeDef( void ) const; + const char *GetTypeName( void ) const; + const function_t *GetConstructor( void ) const; + const function_t *GetDestructor( void ) const; + const function_t *GetFunction( const char *name ) const; + + byte *GetVariable( const char *name, etype_t etype ) const; +}; + +/*********************************************************************** + +idScriptVariable + +Helper template that handles looking up script variables stored in objects. +If the specified variable doesn't exist, or is the wrong data type, idScriptVariable +will cause an error. + +***********************************************************************/ + +template +class idScriptVariable { +private: + type *data; + +public: + idScriptVariable(); + bool IsLinked( void ) const; + void Unlink( void ); + void LinkTo( idScriptObject &obj, const char *name ); + idScriptVariable &operator=( const returnType &value ); + operator returnType() const; +}; + +template +ID_INLINE idScriptVariable::idScriptVariable() { + data = NULL; +} + +template +ID_INLINE bool idScriptVariable::IsLinked( void ) const { + return ( data != NULL ); +} + +template +ID_INLINE void idScriptVariable::Unlink( void ) { + data = NULL; +} + +template +ID_INLINE void idScriptVariable::LinkTo( idScriptObject &obj, const char *name ) { + data = ( type * )obj.GetVariable( name, etype ); + if ( !data ) { + gameError( "Missing '%s' field in script object '%s'", name, obj.GetTypeName() ); + } +} + +template +ID_INLINE idScriptVariable &idScriptVariable::operator=( const returnType &value ) { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + *data = ( type )value; + } + return *this; +} + +template +ID_INLINE idScriptVariable::operator returnType() const { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + return ( const returnType )*data; + } else { + // reasonably safe value + return ( const returnType )0; + } +} + +/*********************************************************************** + +Script object variable access template instantiations + +These objects will automatically handle looking up of the current value +of a variable in a script object. They can be stored as part of a class +for up-to-date values of the variable, or can be used in functions to +sample the data for non-dynamic values. + +***********************************************************************/ + +typedef idScriptVariable idScriptBool; +typedef idScriptVariable idScriptFloat; +typedef idScriptVariable idScriptInt; +typedef idScriptVariable idScriptVector; +typedef idScriptVariable idScriptString; + +/*********************************************************************** + +idCompileError + +Causes the compiler to exit out of compiling the current function and +display an error message with line and file info. + +***********************************************************************/ + +class idCompileError : public idException { +public: + idCompileError( const char *text ) : idException( text ) {} +}; + +/*********************************************************************** + +idVarDef + +Define the name, type, and location of variables, functions, and objects +defined in script. + +***********************************************************************/ + +typedef union varEval_s { + idScriptObject **objectPtrPtr; + char *stringPtr; + float *floatPtr; + idVec3 *vectorPtr; + function_t *functionPtr; + int *intPtr; + byte *bytePtr; + int *entityNumberPtr; + int virtualFunction; + int jumpOffset; + int stackOffset; // offset in stack for local variables + int argSize; + varEval_s *evalPtr; + int ptrOffset; +} varEval_t; + +class idVarDefName; + +class idVarDef { + friend class idVarDefName; + +public: + int num; + varEval_t value; + idVarDef * scope; // function, namespace, or object the var was defined in + int numUsers; // number of users if this is a constant + + typedef enum { + uninitialized, initializedVariable, initializedConstant, stackVariable + } initialized_t; + + initialized_t initialized; + +public: + idVarDef( idTypeDef *typeptr = NULL ); + ~idVarDef(); + + const char * Name( void ) const; + const char * GlobalName( void ) const; + + void SetTypeDef( idTypeDef *_type ) { typeDef = _type; } + idTypeDef * TypeDef( void ) const { return typeDef; } + etype_t Type( void ) const { return ( typeDef != NULL ) ? typeDef->Type() : ev_void; } + + int DepthOfScope( const idVarDef *otherScope ) const; + + void SetFunction( function_t *func ); + void SetObject( idScriptObject *object ); + void SetValue( const eval_t &value, bool constant ); + void SetString( const char *string, bool constant ); + + idVarDef * Next( void ) const { return next; } // next var def with same name + + void PrintInfo( idFile *file, int instructionPointer ) const; + +private: + idTypeDef * typeDef; + idVarDefName * name; // name of this var + idVarDef * next; // next var with the same name +}; + +/*********************************************************************** + + idVarDefName + +***********************************************************************/ + +class idVarDefName { +public: + idVarDefName( void ) { defs = NULL; } + idVarDefName( const char *n ) { name = n; defs = NULL; } + + const char * Name( void ) const { return name; } + idVarDef * GetDefs( void ) const { return defs; } + + void AddDef( idVarDef *def ); + void RemoveDef( idVarDef *def ); + +private: + idStr name; + idVarDef * defs; +}; + +/*********************************************************************** + + Variable and type defintions + +***********************************************************************/ + +extern idTypeDef type_void; +extern idTypeDef type_scriptevent; +extern idTypeDef type_namespace; +// RAVEN BEGIN +// abahr +extern rvTypeDefString type_string; +extern rvTypeDefFloat type_float; +extern rvTypeDefVec3 type_vector; +extern rvTypeDefEntity type_entity; +// RAVEN END +extern idTypeDef type_field; +extern idTypeDef type_function; +extern idTypeDef type_virtualfunction; +extern idTypeDef type_pointer; +extern idTypeDef type_object; +extern idTypeDef type_jumpoffset; // only used for jump opcodes +extern idTypeDef type_argsize; // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +extern rvTypeDefBool type_boolean; +// RAVEN END + +extern idVarDef def_void; +extern idVarDef def_scriptevent; +extern idVarDef def_namespace; +extern idVarDef def_string; +extern idVarDef def_float; +extern idVarDef def_vector; +extern idVarDef def_entity; +extern idVarDef def_field; +extern idVarDef def_function; +extern idVarDef def_virtualfunction; +extern idVarDef def_pointer; +extern idVarDef def_object; +extern idVarDef def_jumpoffset; // only used for jump opcodes +extern idVarDef def_argsize; // only used for function call and thread opcodes +extern idVarDef def_boolean; + +typedef struct statement_s { + unsigned short op; + idVarDef *a; + idVarDef *b; + idVarDef *c; + unsigned short linenumber; + unsigned short file; +} statement_t; + +/*********************************************************************** + +idProgram + +Handles compiling and storage of script data. Multiple idProgram objects +would represent seperate programs with no knowledge of each other. Scripts +meant to access shared data and functions should all be compiled by a +single idProgram. + +***********************************************************************/ + +class idProgram { +private: + idStrList fileList; + idStr filename; + int filenum; + + int numVariables; + byte variables[ MAX_GLOBALS ]; + idStaticList variableDefaults; + idStaticList functions; + idStaticList statements; + idList types; + idList varDefNames; + idHashIndex varDefNameHash; + idList varDefs; + + idVarDef *sysDef; + + int top_functions; + int top_statements; + int top_types; + int top_defs; + int top_files; + + void CompileStats( void ); + +public: + idVarDef *returnDef; + idVarDef *returnStringDef; + + idProgram(); + ~idProgram(); + + // save games + void Save( idSaveGame *savefile ) const; + bool Restore( idRestoreGame *savefile ); + int CalculateChecksum( void ) const; // Used to insure program code has not + // changed between savegames + + void Startup( const char *defaultScript ); + void Restart( void ); + bool CompileText( const char *source, const char *text, bool console ); + const function_t *CompileFunction( const char *functionName, const char *text ); + void CompileFile( const char *filename ); + void BeginCompilation( void ); + void FinishCompilation( void ); + void DisassembleStatement( idFile *file, int instructionPointer ) const; + void Disassemble( void ) const; + void FreeData( void ); + + const char *GetFilename( int num ); + int GetFilenum( const char *name ); + int GetLineNumberForStatement( int index ); + const char *GetFilenameForStatement( int index ); + + idTypeDef *AllocType( idTypeDef &type ); + idTypeDef *AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + idTypeDef *GetType( idTypeDef &type, bool allocate ); + idTypeDef *FindType( const char *name ); + + idVarDef *AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ); + idVarDef *GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const; + void FreeDef( idVarDef *d, const idVarDef *scope ); + idVarDef *FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ); + idVarDef *GetDefList( const char *name ) const; + void AddDefToNameList( idVarDef *def, const char *name ); + + function_t *FindFunction( const char *name ) const; // returns NULL if function not found + function_t *FindFunction( const char *name, const idTypeDef *type ) const; // returns NULL if function not found + function_t &AllocFunction( idVarDef *def ); + function_t *GetFunction( int index ); + int GetFunctionIndex( const function_t *func ); + + void SetEntity( const char *name, idEntity *ent ); + + statement_t *AllocStatement( void ); + statement_t &GetStatement( int index ); + int NumStatements( void ) { return statements.Num(); } + + int GetReturnedInteger( void ); + +// RAVEN BEGIN +// abahr: adding helper functions for other types + float GetReturnedFloat(); + idVec3 GetReturnedVec3(); + idEntity* GetReturnedEntity(); + const char* GetReturnedString(); + bool GetReturnedBool(); +// RAVEN END + + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( idVec3 const &vec ); + void ReturnString( const char *string ); +// RAVEN BEGIN +// abahr: added const + void ReturnEntity( const idEntity *ent ); +// RAVEN END + + int NumFilenames( void ) { return fileList.Num( ); } + +// RAVEN BEGIN +// bdube: added + void ListStates( void ); +// jscott: added for debug with inlines and memory log + void Shutdown( void ); +// jscott: added for stats + size_t ScriptSummary( const idCmdArgs &args ); + size_t ClassSummary( const idCmdArgs &args ); +// RAVEN END +}; + +/* +================ +idProgram::GetStatement +================ +*/ +ID_INLINE statement_t &idProgram::GetStatement( int index ) { + return statements[ index ]; +} + +/* +================ +idProgram::GetFunction +================ +*/ +ID_INLINE function_t *idProgram::GetFunction( int index ) { + return &functions[ index ]; +} + +/* +================ +idProgram::GetFunctionIndex +================ +*/ +ID_INLINE int idProgram::GetFunctionIndex( const function_t *func ) { + return func - &functions[0]; +} + +/* +================ +idProgram::GetReturnedInteger +================ +*/ +ID_INLINE int idProgram::GetReturnedInteger( void ) { +// RAVEN BEGIN +// abahr: because we only return floats in idThread we need to only get floats + return (int)GetReturnedFloat(); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: adding helper functions for other types +/* +================ +idProgram::GetReturnedFloat +================ +*/ +ID_INLINE float idProgram::GetReturnedFloat() { + return *returnDef->value.floatPtr; +} + +/* +================ +idProgram::GetReturnedVec3 +================ +*/ +ID_INLINE idVec3 idProgram::GetReturnedVec3() { + return *returnDef->value.vectorPtr; +} + +/* +================ +idProgram::GetReturnedString +================ +*/ +ID_INLINE const char* idProgram::GetReturnedString() { + return returnDef->value.stringPtr; +} + +/* +================ +idProgram::GetReturnedBool +================ +*/ +ID_INLINE bool idProgram::GetReturnedBool() { + return GetReturnedInteger() != 0; +} +// RAVEN END + +/* +================ +idProgram::ReturnFloat +================ +*/ +ID_INLINE void idProgram::ReturnFloat( float value ) { + *returnDef->value.floatPtr = value; +} + +/* +================ +idProgram::ReturnInteger +================ +*/ +ID_INLINE void idProgram::ReturnInteger( int value ) { + *returnDef->value.intPtr = value; +} + +/* +================ +idProgram::ReturnVector +================ +*/ +ID_INLINE void idProgram::ReturnVector( idVec3 const &vec ) { + *returnDef->value.vectorPtr = vec; +} + +/* +================ +idProgram::ReturnString +================ +*/ +ID_INLINE void idProgram::ReturnString( const char *string ) { + idStr::Copynz( returnStringDef->value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +================ +idProgram::GetFilename +================ +*/ +ID_INLINE const char *idProgram::GetFilename( int num ) { + return fileList[ num ]; +} + +/* +================ +idProgram::GetLineNumberForStatement +================ +*/ +ID_INLINE int idProgram::GetLineNumberForStatement( int index ) { + return statements[ index ].linenumber; +} + +/* +================ +idProgram::GetFilenameForStatement +================ +*/ +ID_INLINE const char *idProgram::GetFilenameForStatement( int index ) { + return GetFilename( statements[ index ].file ); +} + +#endif /* !__SCRIPT_PROGRAM_H__ */ diff --git a/source/game/script/Script_Thread.cpp b/source/game/script/Script_Thread.cpp new file mode 100644 index 0000000..5a1f9d9 --- /dev/null +++ b/source/game/script/Script_Thread.cpp @@ -0,0 +1,2309 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const idEventDef EV_Thread_Execute( "", NULL ); +const idEventDef EV_Thread_SetCallback( "", NULL ); + +// script callable events +const idEventDef EV_Thread_TerminateThread( "terminate", "d" ); +const idEventDef EV_Thread_Pause( "pause", NULL ); +const idEventDef EV_Thread_Wait( "wait", "f" ); +const idEventDef EV_Thread_WaitFrame( "waitFrame" ); +const idEventDef EV_Thread_WaitFor( "waitFor", "e" ); +const idEventDef EV_Thread_WaitForThread( "waitForThread", "d" ); +const idEventDef EV_Thread_Print( "print", "s" ); +const idEventDef EV_Thread_PrintLn( "println", "s" ); +const idEventDef EV_Thread_Say( "say", "s" ); +const idEventDef EV_Thread_Assert( "assert", "f" ); +const idEventDef EV_Thread_Trigger( "trigger", "e" ); +const idEventDef EV_Thread_SetCvar( "setcvar", "ss" ); +const idEventDef EV_Thread_GetCvar( "getcvar", "s", 's' ); +const idEventDef EV_Thread_Random( "random", "f", 'f' ); +const idEventDef EV_Thread_GetTime( "getTime", NULL, 'f' ); +const idEventDef EV_Thread_KillThread( "killthread", "s" ); +const idEventDef EV_Thread_SetThreadName( "threadname", "s" ); +const idEventDef EV_Thread_GetEntity( "getEntity", "s", 'e' ); +const idEventDef EV_Thread_Spawn( "spawn", "s", 'e' ); +const idEventDef EV_Thread_CopySpawnArgs( "copySpawnArgs", "e" ); +const idEventDef EV_Thread_SetSpawnArg( "setSpawnArg", "ss" ); +const idEventDef EV_Thread_SpawnString( "SpawnString", "ss", 's' ); +const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); +const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); +const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); +const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); +const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); +const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); +const idEventDef EV_Thread_AngToForward( "angToForward", "v", 'v' ); +const idEventDef EV_Thread_AngToRight( "angToRight", "v", 'v' ); +const idEventDef EV_Thread_AngToUp( "angToUp", "v", 'v' ); +const idEventDef EV_Thread_Sine( "sin", "f", 'f' ); +const idEventDef EV_Thread_Cosine( "cos", "f", 'f' ); +const idEventDef EV_Thread_SquareRoot( "sqrt", "f", 'f' ); +const idEventDef EV_Thread_Normalize( "vecNormalize", "v", 'v' ); +const idEventDef EV_Thread_VecLength( "vecLength", "v", 'f' ); +const idEventDef EV_Thread_VecDotProduct( "DotProduct", "vv", 'f' ); +const idEventDef EV_Thread_VecCrossProduct( "CrossProduct", "vv", 'v' ); +const idEventDef EV_Thread_VecToAngles( "VecToAngles", "v", 'v' ); +const idEventDef EV_Thread_OnSignal( "onSignal", "des" ); +const idEventDef EV_Thread_ClearSignal( "clearSignalThread", "de" ); +const idEventDef EV_Thread_SetCamera( "setCamera", "e" ); +const idEventDef EV_Thread_FirstPerson( "firstPerson", NULL ); +const idEventDef EV_Thread_Trace( "trace", "vvvvde", 'f' ); +const idEventDef EV_Thread_TracePoint( "tracePoint", "vvde", 'f' ); +const idEventDef EV_Thread_GetTraceFraction( "getTraceFraction", NULL, 'f' ); +const idEventDef EV_Thread_GetTraceEndPos( "getTraceEndPos", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceNormal( "getTraceNormal", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceEntity( "getTraceEntity", NULL, 'e' ); +const idEventDef EV_Thread_GetTraceJoint( "getTraceJoint", NULL, 's' ); +const idEventDef EV_Thread_GetTraceBody( "getTraceBody", NULL, 's' ); +const idEventDef EV_Thread_FadeIn( "fadeIn", "vf" ); +const idEventDef EV_Thread_FadeOut( "fadeOut", "vf" ); +const idEventDef EV_Thread_FadeTo( "fadeTo", "vff" ); +const idEventDef EV_Thread_StartMusic( "music", "s" ); +const idEventDef EV_Thread_Error( "error", "s" ); +const idEventDef EV_Thread_Warning( "warning", "s" ); +const idEventDef EV_Thread_StrLen( "strLength", "s", 'd' ); +const idEventDef EV_Thread_StrLeft( "strLeft", "sd", 's' ); +const idEventDef EV_Thread_StrRight( "strRight", "sd", 's' ); +const idEventDef EV_Thread_StrSkip( "strSkip", "sd", 's' ); +const idEventDef EV_Thread_StrMid( "strMid", "sdd", 's' ); +const idEventDef EV_Thread_StrToFloat( "strToFloat", "s", 'f' ); +const idEventDef EV_Thread_RadiusDamage( "radiusDamage", "vEEEsf" ); +const idEventDef EV_Thread_IsClient( "isClient", NULL, 'f' ); +const idEventDef EV_Thread_IsMultiplayer( "isMultiplayer", NULL, 'f' ); +const idEventDef EV_Thread_GetFrameTime( "getFrameTime", NULL, 'f' ); +const idEventDef EV_Thread_GetTicsPerSecond( "getTicsPerSecond", NULL, 'f' ); +const idEventDef EV_Thread_DebugLine( "debugLine", "vvvf" ); +const idEventDef EV_Thread_DebugArrow( "debugArrow", "vvvdf" ); +const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); +const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); +const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); +const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); + +// RAVEN BEGIN +// kfuller: added everything below the above block +const idEventDef EV_Thread_SetSpawnVector( "setSpawnVector", "sv" ); +const idEventDef EV_Thread_ArcSine( "asin", "f", 'f' ); +const idEventDef EV_Thread_ArcCosine( "acos", "f", 'f' ); +const idEventDef EV_Thread_VecRotate( "vecRotate", "vv", 'v'); +const idEventDef EV_Thread_ClearSignalAllThreads( "clearSignalAllThreads", "de" ); +// bgeisler: added everything below the added block that was added by keith +const idEventDef EV_Thread_PlayWorldEffect( "playWorldEffect", "svv"); +// abahr: +const idEventDef EV_Thread_ReferenceScriptObjectProxy( "refProxy", "s", 'e' ); +const idEventDef EV_Thread_ReleaseScriptObjectProxy( "releaseProxy", "s" ); +const idEventDef EV_Thread_ClampFloat( "clampFloat", "fff", 'f' ); +const idEventDef EV_Thread_MinFloat( "minFloat", "ff", 'f' ); +const idEventDef EV_Thread_MaxFloat( "maxFloat", "ff", 'f' ); +const idEventDef EV_Thread_StrFind( "strFind", "ss", 'f' ); +const idEventDef EV_Thread_RandomInt( "randomInt", "f", 'f' ); +// rjohnson: new blur special effect +const idEventDef EV_Thread_SetSpecialEffect( "setSpecialEffect", "dd" ); +const idEventDef EV_Thread_SetSpecialEffectParm( "setSpecialEffectParm", "ddf" ); +// asalmon: award achievement +const idEventDef EV_Thread_AwardAchievement( "awardAchievement", "s" ); +// twhitaker: ceil, floor and intVal +const idEventDef EV_Thread_Ceil( "ceil", "f", 'f' ); +const idEventDef EV_Thread_Floor( "floor", "f", 'f' ); +const idEventDef EV_Thread_ToInt( "intVal", "f", 'f' ); +// jdischler: send named event string to specified gui +const idEventDef EV_Thread_SendNamedEvent( "sendNamedEvent", "ds" ); +// material streaming +const idEventDef EV_Thread_BeginManualStreaming( "beginManualStreaming" ); +const idEventDef EV_Thread_EndManualStreaming( "endManualStreaming" ); + +// nrausch: change material sort order on the fly +const idEventDef EV_Thread_SetMatSort( "setMatSort", "ss", 0 ); +// RAVEN END + +CLASS_DECLARATION( idClass, idThread ) + EVENT( EV_Thread_Execute, idThread::Event_Execute ) + EVENT( EV_Thread_TerminateThread, idThread::Event_TerminateThread ) + EVENT( EV_Thread_Pause, idThread::Event_Pause ) + EVENT( EV_Thread_Wait, idThread::Event_Wait ) + EVENT( EV_Thread_WaitFrame, idThread::Event_WaitFrame ) + EVENT( EV_Thread_WaitFor, idThread::Event_WaitFor ) + EVENT( EV_Thread_WaitForThread, idThread::Event_WaitForThread ) + EVENT( EV_Thread_Print, idThread::Event_Print ) + EVENT( EV_Thread_PrintLn, idThread::Event_PrintLn ) + EVENT( EV_Thread_Say, idThread::Event_Say ) + EVENT( EV_Thread_Assert, idThread::Event_Assert ) + EVENT( EV_Thread_Trigger, idThread::Event_Trigger ) + EVENT( EV_Thread_SetCvar, idThread::Event_SetCvar ) + EVENT( EV_Thread_GetCvar, idThread::Event_GetCvar ) + EVENT( EV_Thread_Random, idThread::Event_Random ) + EVENT( EV_Thread_GetTime, idThread::Event_GetTime ) + EVENT( EV_Thread_KillThread, idThread::Event_KillThread ) + EVENT( EV_Thread_SetThreadName, idThread::Event_SetThreadName ) + EVENT( EV_Thread_GetEntity, idThread::Event_GetEntity ) + EVENT( EV_Thread_Spawn, idThread::Event_Spawn ) + EVENT( EV_Thread_CopySpawnArgs, idThread::Event_CopySpawnArgs ) + EVENT( EV_Thread_SetSpawnArg, idThread::Event_SetSpawnArg ) + EVENT( EV_Thread_SpawnString, idThread::Event_SpawnString ) + EVENT( EV_Thread_SpawnFloat, idThread::Event_SpawnFloat ) + EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) + EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) + EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) + EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) + EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) + EVENT( EV_Thread_AngToForward, idThread::Event_AngToForward ) + EVENT( EV_Thread_AngToRight, idThread::Event_AngToRight ) + EVENT( EV_Thread_AngToUp, idThread::Event_AngToUp ) + EVENT( EV_Thread_Sine, idThread::Event_GetSine ) + EVENT( EV_Thread_Cosine, idThread::Event_GetCosine ) + EVENT( EV_Thread_SquareRoot, idThread::Event_GetSquareRoot ) + EVENT( EV_Thread_Normalize, idThread::Event_VecNormalize ) + EVENT( EV_Thread_VecLength, idThread::Event_VecLength ) + EVENT( EV_Thread_VecDotProduct, idThread::Event_VecDotProduct ) + EVENT( EV_Thread_VecCrossProduct, idThread::Event_VecCrossProduct ) + EVENT( EV_Thread_VecToAngles, idThread::Event_VecToAngles ) + EVENT( EV_Thread_OnSignal, idThread::Event_OnSignal ) + EVENT( EV_Thread_ClearSignal, idThread::Event_ClearSignalThread ) + EVENT( EV_Thread_SetCamera, idThread::Event_SetCamera ) + EVENT( EV_Thread_FirstPerson, idThread::Event_FirstPerson ) + EVENT( EV_Thread_Trace, idThread::Event_Trace ) + EVENT( EV_Thread_TracePoint, idThread::Event_TracePoint ) + EVENT( EV_Thread_GetTraceFraction, idThread::Event_GetTraceFraction ) + EVENT( EV_Thread_GetTraceEndPos, idThread::Event_GetTraceEndPos ) + EVENT( EV_Thread_GetTraceNormal, idThread::Event_GetTraceNormal ) + EVENT( EV_Thread_GetTraceEntity, idThread::Event_GetTraceEntity ) + EVENT( EV_Thread_GetTraceJoint, idThread::Event_GetTraceJoint ) + EVENT( EV_Thread_GetTraceBody, idThread::Event_GetTraceBody ) + EVENT( EV_Thread_FadeIn, idThread::Event_FadeIn ) + EVENT( EV_Thread_FadeOut, idThread::Event_FadeOut ) + EVENT( EV_Thread_FadeTo, idThread::Event_FadeTo ) + EVENT( EV_SetShaderParm, idThread::Event_SetShaderParm ) + EVENT( EV_Thread_StartMusic, idThread::Event_StartMusic ) + EVENT( EV_Thread_Warning, idThread::Event_Warning ) + EVENT( EV_Thread_Error, idThread::Event_Error ) + EVENT( EV_Thread_StrLen, idThread::Event_StrLen ) + EVENT( EV_Thread_StrLeft, idThread::Event_StrLeft ) + EVENT( EV_Thread_StrRight, idThread::Event_StrRight ) + EVENT( EV_Thread_StrSkip, idThread::Event_StrSkip ) + EVENT( EV_Thread_StrMid, idThread::Event_StrMid ) + EVENT( EV_Thread_StrToFloat, idThread::Event_StrToFloat ) + EVENT( EV_Thread_RadiusDamage, idThread::Event_RadiusDamage ) + EVENT( EV_Thread_IsClient, idThread::Event_IsClient ) + EVENT( EV_Thread_IsMultiplayer, idThread::Event_IsMultiplayer ) + EVENT( EV_Thread_GetFrameTime, idThread::Event_GetFrameTime ) + EVENT( EV_Thread_GetTicsPerSecond, idThread::Event_GetTicsPerSecond ) + EVENT( EV_CacheSoundShader, idThread::Event_CacheSoundShader ) + EVENT( EV_Thread_DebugLine, idThread::Event_DebugLine ) + EVENT( EV_Thread_DebugArrow, idThread::Event_DebugArrow ) + EVENT( EV_Thread_DebugCircle, idThread::Event_DebugCircle ) + EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) + EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) + EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) + +// RAVEN BEGIN +// kfuller: added events + EVENT( EV_Thread_SetSpawnVector, idThread::Event_SetSpawnVector ) + EVENT( EV_Thread_ArcSine, idThread::Event_GetArcSine ) + EVENT( EV_Thread_ArcCosine, idThread::Event_GetArcCosine ) + EVENT( EV_Thread_VecRotate, idThread::Event_VecRotate ) + EVENT( EV_Thread_ClearSignalAllThreads, idThread::Event_ClearSignalAllThreads ) +// nmckenzie: added signal strings + EVENT( EV_Thread_PlayWorldEffect, idThread::Event_PlayWorldEffect ) +// abahr: + EVENT( EV_Thread_ReferenceScriptObjectProxy, idThread::Event_ReferenceScriptObjectProxy ) + EVENT( EV_Thread_ReleaseScriptObjectProxy, idThread::Event_ReleaseScriptObjectProxy ) + EVENT( EV_Thread_ClampFloat, idThread::Event_ClampFloat ) + EVENT( EV_Thread_MinFloat, idThread::Event_MinFloat ) + EVENT( EV_Thread_MaxFloat, idThread::Event_MaxFloat ) + EVENT( EV_Thread_StrFind, idThread::Event_StrFind ) + EVENT( EV_Thread_RandomInt, idThread::Event_RandomInt ) +// rjohnson: new blur special effect + EVENT( EV_Thread_SetSpecialEffect, idThread::Event_SetSpecialEffect ) + EVENT( EV_Thread_SetSpecialEffectParm, idThread::Event_SetSpecialEffectParm ) +// asalmon: award a Xenon achievement + EVENT( EV_Thread_AwardAchievement, idThread::Event_AwardAchievement ) +// twhitaker: ceil and floor + EVENT( EV_Thread_Ceil, idThread::Event_GetCeil ) + EVENT( EV_Thread_Floor, idThread::Event_GetFloor ) + EVENT( EV_Thread_ToInt, idThread::Event_ToInt ) +// jdischler: send named event string to specified gui + EVENT( EV_Thread_SendNamedEvent, idThread::Event_SendNamedEvent ) +// manual streaming + EVENT( EV_Thread_BeginManualStreaming, idThread::Event_BeginManualStreaming ) + EVENT( EV_Thread_EndManualStreaming, idThread::Event_EndManualStreaming ) + EVENT( EV_Thread_SetMatSort, idThread::Event_SetMatSort ) +// RAVEN END +END_CLASS + +idThread *idThread::currentThread = NULL; +int idThread::threadIndex = 0; +idList idThread::threadList; +trace_t idThread::trace; + +/* +================ +idThread::CurrentThread +================ +*/ +idThread *idThread::CurrentThread( void ) { + return currentThread; +} + +/* +================ +idThread::CurrentThreadNum +================ +*/ +int idThread::CurrentThreadNum( void ) { + if ( currentThread ) { + return currentThread->GetThreadNum(); + } else { + return 0; + } +} + +/* +================ +idThread::BeginMultiFrameEvent +================ +*/ +bool idThread::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::BeginMultiFrameEvent called without a current thread" ); + } + return currentThread->interpreter.BeginMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::EndMultiFrameEvent +================ +*/ +void idThread::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::EndMultiFrameEvent called without a current thread" ); + } + currentThread->interpreter.EndMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread() { + Init(); + SetThreadName( va( "thread_%d", threadIndex ) ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idEntity *self, const function_t *func ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.EnterObjectFunction( self, func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( const function_t *func ) { + assert( func ); + + Init(); + SetThreadName( func->Name() ); + interpreter.EnterFunction( func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, const function_t *func, int args ) { + Init(); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::~idThread +================ +*/ +idThread::~idThread() { + idThread *thread; + int i; + int n; + + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: end thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } + threadList.Remove( this ); + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->WaitingOnThread() == this ) { + thread->ThreadCallback( this ); + } + } + + if ( currentThread == this ) { + currentThread = NULL; + } +} + +/* +================ +idThread::ManualDelete +================ +*/ +void idThread::ManualDelete( void ) { + interpreter.terminateOnExit = false; +} + +/* +================ +idThread::Save +================ +*/ +void idThread::Save( idSaveGame *savefile ) const { + savefile->WriteObject( currentThread ); + + savefile->WriteObject( waitingForThread ); + savefile->WriteInt( waitingFor ); + savefile->WriteInt( waitingUntil ); + interpreter.Save( savefile ); + + savefile->WriteDict( &spawnArgs ); + + savefile->WriteInt( threadNum ); + savefile->WriteString( threadName ); + + savefile->WriteInt( lastExecuteTime ); + savefile->WriteInt( creationTime ); + + savefile->WriteBool( manualControl ); + + savefile->WriteInt( threadIndex ); +} + +/* +================ +idThread::Restore +================ +*/ +void idThread::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( currentThread ) ); + + savefile->ReadObject( reinterpret_cast( waitingForThread ) ); + savefile->ReadInt( waitingFor ); + savefile->ReadInt( waitingUntil ); + interpreter.Restore( savefile ); + + savefile->ReadDict( &spawnArgs ); + + savefile->ReadInt( threadNum ); + savefile->ReadString( threadName ); + + savefile->ReadInt( lastExecuteTime ); + savefile->ReadInt( creationTime ); + + savefile->ReadBool( manualControl ); + + savefile->ReadInt( threadIndex ); +} + +/* +================ +idThread::Init +================ +*/ +void idThread::Init( void ) { + // create a unique threadNum + do { + threadIndex++; + if ( threadIndex == 0 ) { + threadIndex = 1; + } + } while( GetThread( threadIndex ) ); + + threadNum = threadIndex; + threadList.Append( this ); + + creationTime = gameLocal.time; + lastExecuteTime = 0; + manualControl = false; + + ClearWaitFor(); + + interpreter.SetThread( this ); +} + +/* +================ +idThread::GetThread +================ +*/ +idThread *idThread::GetThread( int num ) { + int i; + int n; + idThread *thread; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->GetThreadNum() == num ) { + return thread; + } + } + + return NULL; +} + +/* +================ +idThread::DisplayInfo +================ +*/ +void idThread::DisplayInfo( void ) { + gameLocal.Printf( + "%12i: '%s'\n" + " File: %s(%d)\n" + " Created: %d (%d ms ago)\n" + " Status: ", + threadNum, threadName.c_str(), + interpreter.CurrentFile(), interpreter.CurrentLine(), + creationTime, gameLocal.time - creationTime ); + + if ( interpreter.threadDying ) { + gameLocal.Printf( "Dying\n" ); + } else if ( interpreter.doneProcessing ) { + gameLocal.Printf( + "Paused since %d (%d ms)\n" + " Reason: ", lastExecuteTime, gameLocal.time - lastExecuteTime ); + if ( waitingForThread ) { + gameLocal.Printf( "Waiting for thread #%3i '%s'\n", waitingForThread->GetThreadNum(), waitingForThread->GetThreadName() ); + } else if ( ( waitingFor != ENTITYNUM_NONE ) && ( gameLocal.entities[ waitingFor ] ) ) { + gameLocal.Printf( "Waiting for entity #%3i '%s'\n", waitingFor, gameLocal.entities[ waitingFor ]->name.c_str() ); + } else if ( waitingUntil ) { + gameLocal.Printf( "Waiting until %d (%d ms total wait time)\n", waitingUntil, waitingUntil - lastExecuteTime ); + } else { + gameLocal.Printf( "None\n" ); + } + } else { + gameLocal.Printf( "Processing\n" ); + } + + interpreter.DisplayInfo(); + + gameLocal.Printf( "\n" ); +} + +/* +================ +idThread::ListThreads_f +================ +*/ +void idThread::ListThreads_f( const idCmdArgs &args ) { + int i; + int n; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + //threadList[ i ]->DisplayInfo(); + gameLocal.Printf( "%3i: %-20s : %s(%d)\n", threadList[ i ]->threadNum, threadList[ i ]->threadName.c_str(), threadList[ i ]->interpreter.CurrentFile(), threadList[ i ]->interpreter.CurrentLine() ); + } + gameLocal.Printf( "%d active threads\n\n", n ); +} + +/* +================ +idThread::Restart +================ +*/ +void idThread::Restart( void ) { + int i; + int n; + + // reset the threadIndex + threadIndex = 0; + + currentThread = NULL; + n = threadList.Num(); + for( i = n - 1; i >= 0; i-- ) { + delete threadList[ i ]; + } + threadList.Clear(); + + memset( &trace, 0, sizeof( trace ) ); + trace.c.entityNum = ENTITYNUM_NONE; +} + +/* +================ +idThread::DelayedStart +================ +*/ +void idThread::DelayedStart( int delay ) { + CancelEvents( &EV_Thread_Execute ); + if ( gameLocal.time <= 0 ) { + delay++; + } + PostEventMS( &EV_Thread_Execute, delay ); +} + +/* +================ +idThread::Start +================ +*/ +bool idThread::Start( void ) { + bool result; + + CancelEvents( &EV_Thread_Execute ); + result = Execute(); + + return result; +} + +/* +================ +idThread::SetThreadName +================ +*/ +void idThread::SetThreadName( const char *name ) { + threadName = name; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( int threadnum, idEntity *obj ) { + idThread *thread; + + if ( !threadnum ) { + return; + } + + thread = GetThread( threadnum ); + if ( thread ) { + thread->ObjectMoveDone( obj ); + } +} + +/* +================ +idThread::End +================ +*/ +void idThread::End( void ) { + // Tell thread to die. It will exit on its own. + Pause(); + interpreter.threadDying = true; +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( const char *name ) { + int i; + int num; + int len; + const char *ptr; + idThread *thread; + + // see if the name uses a wild card + ptr = strchr( name, '*' ); + if ( ptr ) { + len = ptr - name; + } else { + len = strlen( name ); + } + + // kill only those threads whose name matches name + num = threadList.Num(); + for( i = 0; i < num; i++ ) { + thread = threadList[ i ]; + if ( !idStr::Cmpn( thread->GetThreadName(), name, len ) ) { + thread->End(); + } + } +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( thread ) { + // Tell thread to die. It will delete itself on it's own. + thread->End(); + } +} + +/* +================ +idThread::Execute +================ +*/ +bool idThread::Execute( void ) { + idThread *oldThread; + bool done; + + if ( manualControl && ( waitingUntil > gameLocal.time ) ) { + return false; + } + + oldThread = currentThread; + currentThread = this; + + lastExecuteTime = gameLocal.time; + ClearWaitFor(); + done = interpreter.Execute(); + if ( done ) { + End(); + if ( interpreter.terminateOnExit ) { + PostEventMS( &EV_Remove, 0 ); + } + } else if ( !manualControl ) { + if ( waitingUntil > lastExecuteTime ) { + PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); + } else if ( interpreter.MultiFrameEventInProgress() ) { + PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + } + } + + currentThread = oldThread; + + return done; +} + +/* +================ +idThread::IsWaiting + +Checks if thread is still waiting for some event to occur. +================ +*/ +bool idThread::IsWaiting( void ) { + if ( waitingForThread || ( waitingFor != ENTITYNUM_NONE ) ) { + return true; + } + + if ( waitingUntil && ( waitingUntil > gameLocal.time ) ) { + return true; + } + + return false; +} + +// RAVEN BEGIN +// bgeisler: +/* +================ +idThread::ListFunctions +================ +*/ +void idThread::ListStates(void) +{ + gameLocal.program.ListStates(); +} + +// abahr: added helper functions +/* +================ +idThread::ClearStack +================ +*/ +void idThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +idThread::PushInt +================ +*/ +void idThread::PushInt( int value ) { + interpreter.Push( value ); +} + +/* +================ +idThread::PushFloat +================ +*/ +void idThread::PushFloat( float value ) { + interpreter.Push( *(int*)&value ); +} + +/* +================ +idThread::PushVec3 +================ +*/ +void idThread::PushVec3( const idVec3& value ) { + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + PushFloat( value[ix] ); + } +} + +/* +================ +idThread::PushEntity +================ +*/ +void idThread::PushEntity( const idEntity* ent ) { + assert( ent ); + + PushInt( ent->entityNumber + 1 ); +} + +/* +================ +idThread::PushString +================ +*/ +void idThread::PushString( const char* string ) { + interpreter.PushString( string ); +} + +/* +================ +idThread::PushBool +================ +*/ +void idThread::PushBool( bool value ) { + PushInt( (int)value ); +} +// RAVEN END + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( const function_t *func, bool clearStack ) { + ClearWaitFor(); + interpreter.EnterFunction( func, clearStack ); +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( idEntity *self, const function_t *func, bool clearStack ) { + assert( self ); + ClearWaitFor(); + interpreter.EnterObjectFunction( self, func, clearStack ); +} + +/* +================ +idThread::ClearWaitFor +================ +*/ +void idThread::ClearWaitFor( void ) { + waitingFor = ENTITYNUM_NONE; + waitingForThread = NULL; + waitingUntil = 0; +} + +/* +================ +idThread::IsWaitingFor +================ +*/ +bool idThread::IsWaitingFor( idEntity *obj ) { + assert( obj ); + return waitingFor == obj->entityNumber; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( idEntity *obj ) { + assert( obj ); + + if ( IsWaitingFor( obj ) ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::ThreadCallback +================ +*/ +void idThread::ThreadCallback( idThread *thread ) { + if ( interpreter.threadDying ) { + return; + } + + if ( thread == waitingForThread ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::Event_SetThreadName +================ +*/ +void idThread::Event_SetThreadName( const char *name ) { + SetThreadName( name ); +} + +/* +================ +idThread::Error +================ +*/ +void idThread::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Error( text ); +} + +/* +================ +idThread::Warning +================ +*/ +void idThread::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Warning( text ); +} + +/* +================ +idThread::ReturnString +================ +*/ +void idThread::ReturnString( const char *text ) { + gameLocal.program.ReturnString( text ); +} + +/* +================ +idThread::ReturnFloat +================ +*/ +void idThread::ReturnFloat( float value ) { + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnInt +================ +*/ +void idThread::ReturnInt( int value ) { + // true integers aren't supported in the compiler, + // so int values are stored as floats + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnVector +================ +*/ +void idThread::ReturnVector( idVec3 const &vec ) { + gameLocal.program.ReturnVector( vec ); +} + +/* +================ +idThread::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idThread::ReturnEntity( const idEntity *ent ) { + gameLocal.program.ReturnEntity( ent ); +} + +/* +================ +idThread::Event_Execute +================ +*/ +void idThread::Event_Execute( void ) { + Execute(); +} + +/* +================ +idThread::Pause +================ +*/ +void idThread::Pause( void ) { + ClearWaitFor(); + interpreter.doneProcessing = true; +} + +/* +================ +idThread::WaitMS +================ +*/ +void idThread::WaitMS( int time ) { + Pause(); +// RAVEN BEGIN +// bdube: add 1 ms to ensure a time of zero still works + waitingUntil = gameLocal.time + time + 1; +// RAVEN END +} + +/* +================ +idThread::WaitSec +================ +*/ +void idThread::WaitSec( float time ) { + WaitMS( SEC2MS( time ) ); +} + +/* +================ +idThread::WaitFrame +================ +*/ +void idThread::WaitFrame( void ) { + Pause(); + + // manual control threads don't set waitingUntil so that they can be run again + // that frame if necessary. + if ( !manualControl ) { + waitingUntil = gameLocal.time + gameLocal.msec; + } +} + +/*********************************************************************** + + Script callable events + +***********************************************************************/ + +/* +================ +idThread::Event_TerminateThread +================ +*/ +void idThread::Event_TerminateThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + KillThread( num ); +} + +/* +================ +idThread::Event_Pause +================ +*/ +void idThread::Event_Pause( void ) { + Pause(); +} + +/* +================ +idThread::Event_Wait +================ +*/ +void idThread::Event_Wait( float time ) { + WaitSec( time ); +} + +/* +================ +idThread::Event_WaitFrame +================ +*/ +void idThread::Event_WaitFrame( void ) { + WaitFrame(); +} + +/* +================ +idThread::Event_WaitFor +================ +*/ +void idThread::Event_WaitFor( idEntity *ent ) { + if ( ent && ent->RespondsTo( EV_Thread_SetCallback ) ) { + ent->ProcessEvent( &EV_Thread_SetCallback ); + if ( gameLocal.program.GetReturnedInteger() ) { + Pause(); + waitingFor = ent->entityNumber; + } + } +} + +/* +================ +idThread::Event_WaitForThread +================ +*/ +void idThread::Event_WaitForThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( !thread ) { + if ( g_debugScript.GetBool() ) { + // just print a warning and continue executing + Warning( "Thread %d not running", num ); + } + } else { + Pause(); + waitingForThread = thread; + } +} + +/* +================ +idThread::Event_Print +================ +*/ +void idThread::Event_Print( const char *text ) { + gameLocal.Printf( "%s", text ); +} + +/* +================ +idThread::Event_PrintLn +================ +*/ +void idThread::Event_PrintLn( const char *text ) { + gameLocal.Printf( "%s\n", text ); +} + +/* +================ +idThread::Event_Say +================ +*/ +void idThread::Event_Say( const char *text ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); +} + +/* +================ +idThread::Event_Assert +================ +*/ +void idThread::Event_Assert( float value ) { +// RAVEN BEGIN +// jnewquist: assert with a useful callstack, since this is script +#ifdef _DEBUG + if ( !value ) { + Error("Script assert fired"); + } +#endif +// RAVEN END +} + +/* +================ +idThread::Event_Trigger +================ +*/ +void idThread::Event_Trigger( idEntity *ent ) { + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + ent->TriggerGuis(); + } +} + +/* +================ +idThread::Event_SetCvar +================ +*/ +void idThread::Event_SetCvar( const char *name, const char *value ) const { + cvarSystem->SetCVarString( name, value ); +} + +/* +================ +idThread::Event_GetCvar +================ +*/ +void idThread::Event_GetCvar( const char *name ) const { + ReturnString( cvarSystem->GetCVarString( name ) ); +} + +/* +================ +idThread::Event_Random +================ +*/ +void idThread::Event_Random( float range ) const { + float result; + + result = gameLocal.random.RandomFloat(); + ReturnFloat( range * result ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idThread::Event_RandomInt +================ +*/ +void idThread::Event_RandomInt( float range ) const { + ReturnFloat( rvRandom::irand(0, range) ); +} + + +// rjohnson: new blur special effect +void idThread::Event_SetSpecialEffect( int Effect, int Enabled ) { + renderSystem->SetSpecialEffect( (ESpecialEffectType)Effect, !!Enabled ); +} + +void idThread::Event_SetSpecialEffectParm( int Effect, int Parm, float Value ) { + renderSystem->SetSpecialEffectParm( (ESpecialEffectType)Effect, Parm, Value ); +} + +// RAVEN END + +/* +================ +idThread::Event_GetTime +================ +*/ +void idThread::Event_GetTime( void ) { + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); +} + +/* +================ +idThread::Event_KillThread +================ +*/ +void idThread::Event_KillThread( const char *name ) { + KillThread( name ); +} + +/* +================ +idThread::Event_GetEntity +================ +*/ +void idThread::Event_GetEntity( const char *name ) { + int entnum; + idEntity *ent; + + assert( name ); + + if ( name[ 0 ] == '*' ) { + entnum = atoi( &name[ 1 ] ); + if ( ( entnum < 0 ) || ( entnum >= MAX_GENTITIES ) ) { + Error( "Entity number in string out of range." ); + } + ReturnEntity( gameLocal.entities[ entnum ] ); + } else { + ent = gameLocal.FindEntity( name ); + ReturnEntity( ent ); + } +} + +/* +================ +idThread::Event_Spawn +================ +*/ +void idThread::Event_Spawn( const char *classname ) { + idEntity *ent; + + spawnArgs.Set( "classname", classname ); + gameLocal.SpawnEntityDef( spawnArgs, &ent ); + ReturnEntity( ent ); + spawnArgs.Clear(); +} + +/* +================ +idThread::Event_CopySpawnArgs +================ +*/ +void idThread::Event_CopySpawnArgs( idEntity *ent ) { + spawnArgs.Copy( ent->spawnArgs ); +} + +/* +================ +idThread::Event_SetSpawnArg +================ +*/ +void idThread::Event_SetSpawnArg( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +// RAVEN BEGIN +// kfuller: added events + +/* +================ +idThread::Event_SetSpawnVector +================ +*/ +void idThread::Event_SetSpawnVector( const char *key, idVec3 &vec) { + spawnArgs.SetVector(key, vec); +} +// RAVEN END + +/* +================ +idThread::Event_SpawnString +================ +*/ +void idThread::Event_SpawnString( const char *key, const char *defaultvalue ) { + const char *result; + + spawnArgs.GetString( key, defaultvalue, &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_SpawnFloat +================ +*/ +void idThread::Event_SpawnFloat( const char *key, float defaultvalue ) { + float result; + + spawnArgs.GetFloat( key, va( "%f", defaultvalue ), result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_SpawnVector +================ +*/ +void idThread::Event_SpawnVector( const char *key, idVec3 &defaultvalue ) { + idVec3 result; + + spawnArgs.GetVector( key, va( "%f %f %f", defaultvalue.x, defaultvalue.y, defaultvalue.z ), result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_ClearPersistantArgs +================ +*/ +void idThread::Event_ClearPersistantArgs( void ) { + gameLocal.persistentLevelInfo.Clear(); +} + + +/* +================ +idThread::Event_SetPersistantArg +================ +*/ +void idThread::Event_SetPersistantArg( const char *key, const char *value ) { + gameLocal.persistentLevelInfo.Set( key, value ); +} + +/* +================ +idThread::Event_GetPersistantString +================ +*/ +void idThread::Event_GetPersistantString( const char *key ) { + const char *result; + + gameLocal.persistentLevelInfo.GetString( key, "", &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_GetPersistantFloat +================ +*/ +void idThread::Event_GetPersistantFloat( const char *key ) { + float result; + + gameLocal.persistentLevelInfo.GetFloat( key, "0", result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_GetPersistantVector +================ +*/ +void idThread::Event_GetPersistantVector( const char *key ) { + idVec3 result; + + gameLocal.persistentLevelInfo.GetVector( key, "0 0 0", result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_AngToForward +================ +*/ +void idThread::Event_AngToForward( idAngles &ang ) { + ReturnVector( ang.ToForward() ); +} + +/* +================ +idThread::Event_AngToRight +================ +*/ +void idThread::Event_AngToRight( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_AngToUp +================ +*/ +void idThread::Event_AngToUp( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, NULL, &vec ); + ReturnVector( vec ); +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_GetArcSine +================ +*/ +void idThread::Event_GetArcSine( float sinValue ) { + ReturnFloat( asinf( sinValue ) ); +} + +/* +================ +idThread::Event_GetArcCosine +================ +*/ +void idThread::Event_GetArcCosine( float cosValue ) { + ReturnFloat( acosf( cosValue ) ); +} +// RAVEN END + +/* +================ +idThread::Event_GetSine +================ +*/ +void idThread::Event_GetSine( float angle ) { + ReturnFloat( idMath::Sin( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetCosine +================ +*/ +void idThread::Event_GetCosine( float angle ) { + ReturnFloat( idMath::Cos( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetSquareRoot +================ +*/ +void idThread::Event_GetSquareRoot( float theSquare ) { + ReturnFloat( idMath::Sqrt( theSquare ) ); +} + +/* +================ +idThread::Event_VecNormalize +================ +*/ +void idThread::Event_VecNormalize( idVec3 &vec ) { + idVec3 n; + + n = vec; + n.Normalize(); + ReturnVector( n ); +} + +/* +================ +idThread::Event_VecLength +================ +*/ +void idThread::Event_VecLength( idVec3 &vec ) { + ReturnFloat( vec.Length() ); +} + +/* +================ +idThread::Event_VecDotProduct +================ +*/ +void idThread::Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnFloat( vec1 * vec2 ); +} + +/* +================ +idThread::Event_VecCrossProduct +================ +*/ +void idThread::Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnVector( vec1.Cross( vec2 ) ); +} + +/* +================ +idThread::Event_VecToAngles +================ +*/ +void idThread::Event_VecToAngles( idVec3 &vec ) { + idAngles ang = vec.ToAngles(); + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +// RAVEN BEGIN +// kef 12/2/02 -- made these into events + +void idThread::Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch) +{ + idVec3 vecResult; + +// VectorRotate3(vecToBeRotated, rotateHowMuch, vecResult); + gameLocal.Printf("'vecRotate' doesn't work -- I guess Keith should find a replacement for VectorRotate3 now...\n"); + ReturnVector(vecResult); +} + +// RAVEN END + +/* +================ +idThread::Event_OnSignal +================ +*/ +void idThread::Event_OnSignal( int signal, idEntity *ent, const char *func ) { + const function_t *function; + + assert( func ); + + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + function = gameLocal.program.FindFunction( func ); + if ( !function ) { + Error( "Function '%s' not found", func ); + } + + ent->SetSignal( ( signalNum_t )signal, this, function ); +} + +/* +================ +idThread::Event_ClearSignalThread +================ +*/ +void idThread::Event_ClearSignalThread( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignalThread( ( signalNum_t )signal, this ); +} + +// RAVEN BEGIN +// kfuller: added +/* +================ +idThread::Event_ClearSignalAllThreads +================ +*/ +void idThread::Event_ClearSignalAllThreads( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignal(this, ( signalNum_t )signal); +} +// RAVEN END + +/* +================ +idThread::Event_SetCamera +================ +*/ +void idThread::Event_SetCamera( idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idCamera::GetClassType() ) ) { +// RAVEN END + Error( "Entity is not a camera" ); + return; + } + + gameLocal.SetCamera( ( idCamera * )ent ); +} + +/* +================ +idThread::Event_FirstPerson +================ +*/ +void idThread::Event_FirstPerson( void ) { + gameLocal.SetCamera( NULL ); +} + +/* +================ +idThread::Event_Trace +================ +*/ +void idThread::Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ) { + if ( mins == vec3_origin && maxs == vec3_origin ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); + } else { + gameLocal.TraceBounds( NULL, trace, start, end, idBounds( mins, maxs ), contents_mask, passEntity ); +// RAVEN END + } + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_TracePoint +================ +*/ +void idThread::Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); +// RAVEN END + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceFraction +================ +*/ +void idThread::Event_GetTraceFraction( void ) { + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceEndPos +================ +*/ +void idThread::Event_GetTraceEndPos( void ) { + ReturnVector( trace.endpos ); +} + +/* +================ +idThread::Event_GetTraceNormal +================ +*/ +void idThread::Event_GetTraceNormal( void ) { + if ( trace.fraction < 1.0f ) { + ReturnVector( trace.c.normal ); + } else { + ReturnVector( vec3_origin ); + } +} + +/* +================ +idThread::Event_GetTraceEntity +================ +*/ +void idThread::Event_GetTraceEntity( void ) { + if ( trace.fraction < 1.0f ) { + ReturnEntity( gameLocal.entities[ trace.c.entityNum ] ); + } else { + ReturnEntity( ( idEntity * )NULL ); + } +} + +/* +================ +idThread::Event_GetTraceJoint +================ +*/ +void idThread::Event_GetTraceJoint( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + ReturnString( af->GetAnimator()->GetJointName( CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ) ) ); + return; + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_GetTraceBody +================ +*/ +void idThread::Event_GetTraceBody( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + int bodyId = af->BodyForClipModelId( trace.c.id ); + idAFBody *body = af->GetAFPhysics()->GetBody( bodyId ); + if ( body ) { + ReturnString( body->GetName() ); + return; + } + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_FadeIn +================ +*/ +void idThread::Event_FadeIn( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 0.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeOut +================ +*/ +void idThread::Event_FadeOut( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 1.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeTo +================ +*/ +void idThread::Event_FadeTo( idVec3 &color, float alpha, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], alpha ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_SetShaderParm +================ +*/ +void idThread::Event_SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_GLOBAL_SHADER_PARMS ) ) { + Error( "shader parm index (%d) out of range", parmnum ); + } + + gameLocal.globalShaderParms[ parmnum ] = value; +} + +/* +================ +idThread::Event_StartMusic +================ +*/ +void idThread::Event_StartMusic( const char *text ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, text ); +} + +/* +================ +idThread::Event_Warning +================ +*/ +void idThread::Event_Warning( const char *text ) { + Warning( "%s", text ); +} + +/* +================ +idThread::Event_Error +================ +*/ +void idThread::Event_Error( const char *text ) { + Error( "%s", text ); +} + +/* +================ +idThread::Event_StrLen +================ +*/ +void idThread::Event_StrLen( const char *string ) { + int len; + + len = strlen( string ); + idThread::ReturnInt( len ); +} + +/* +================ +idThread::Event_StrLeft +================ +*/ +void idThread::Event_StrLeft( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idStr result( string, 0, num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrRight +================ +*/ +void idThread::Event_StrRight( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idThread::ReturnString( string + len - num ); +} + +/* +================ +idThread::Event_StrSkip +================ +*/ +void idThread::Event_StrSkip( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( string ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( string + num ); +} + +/* +================ +idThread::Event_StrMid +================ +*/ +void idThread::Event_StrMid( const char *string, int start, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + if ( start < 0 ) { + start = 0; + } + len = strlen( string ); + if ( start > len ) { + start = len; + } + + if ( start + num > len ) { + num = len - start; + } + + idStr result( string, start, start + num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrToFloat( const char *string ) +================ +*/ +void idThread::Event_StrToFloat( const char *string ) { + float result; + + result = atof( string ); + idThread::ReturnFloat( result ); +} + +/* +================ +idThread::Event_RadiusDamage +================ +*/ +void idThread::Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ) { + gameLocal.RadiusDamage( origin, inflictor, attacker, ignore, ignore, damageDefName, dmgPower ); +} + +/* +================ +idThread::Event_IsClient +================ +*/ +void idThread::Event_IsClient( void ) { + idThread::ReturnFloat( gameLocal.isClient ); +} + +/* +================ +idThread::Event_IsMultiplayer +================ +*/ +void idThread::Event_IsMultiplayer( void ) { + idThread::ReturnFloat( gameLocal.isMultiplayer ); +} + +/* +================ +idThread::Event_GetFrameTime +================ +*/ +void idThread::Event_GetFrameTime( void ) { + idThread::ReturnFloat( MS2SEC( gameLocal.msec ) ); +} + +/* +================ +idThread::Event_GetTicsPerSecond +================ +*/ +void idThread::Event_GetTicsPerSecond( void ) { + idThread::ReturnFloat( gameLocal.GetMHz() ); +} + +/* +================ +idThread::Event_CacheSoundShader +================ +*/ +void idThread::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idThread::Event_DebugLine +================ +*/ +void idThread::Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ) { + gameRenderWorld->DebugLine( idVec4( color.x, color.y, color.z, 0.0f ), start, end, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugArrow +================ +*/ +void idThread::Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ) { + gameRenderWorld->DebugArrow( idVec4( color.x, color.y, color.z, 0.0f ), start, end, size, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugCircle +================ +*/ +void idThread::Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ) { + gameRenderWorld->DebugCircle( idVec4( color.x, color.y, color.z, 0.0f ), origin, dir, radius, numSteps, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugBounds +================ +*/ +void idThread::Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ) { + gameRenderWorld->DebugBounds( idVec4( color.x, color.y, color.z, 0.0f ), idBounds( mins, maxs ), vec3_origin, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DrawText +================ +*/ +void idThread::Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ) { + gameRenderWorld->DrawText( text, origin, scale, idVec4( color.x, color.y, color.z, 0.0f ), gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), align, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_InfluenceActive +================ +*/ +void idThread::Event_InfluenceActive( void ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player && player->GetInfluenceLevel() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_PlayWorldEffect +================ +*/ +void idThread::Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ) { + gameLocal.PlayEffect ( ( const idDecl * )declManager->FindEffect( effectName ), org, angle.ToMat3() ); +} + +// abahr: +/* +================ +idThread::Event_ReferenceScriptObjectProxy +================ +*/ +void idThread::Event_ReferenceScriptObjectProxy( const char* scriptObjectName ) { + ReturnEntity( gameLocal.ReferenceScriptObjectProxy(scriptObjectName) ); + Event_WaitFrame();// Needed because the constructor call is delayed by one frame +} + +/* +================ +idThread::Event_ReleaseScriptObjectProxy +================ +*/ +void idThread::Event_ReleaseScriptObjectProxy( const char* proxyName ) { + gameLocal.ReleaseScriptObjectProxy( proxyName ); + Event_WaitFrame();// Needed because the destructor call is delayed by one frame +} + +/* +================ +idThread::Event_ClampFloat +================ +*/ +void idThread::Event_ClampFloat( float min, float max, float val ) { + ReturnFloat( idMath::ClampFloat(min, max, val) ); +} + +/* +================ +idThread::Event_MinFloat +================ +*/ +void idThread::Event_MinFloat( float val1, float val2 ) { + ReturnFloat( Min(val1, val2) ); +} + +/* +================ +idThread::Event_MaxFloat +================ +*/ +void idThread::Event_MaxFloat( float val1, float val2 ) { + ReturnFloat( Max(val1, val2) ); +} + +/* +================ +idThread::Event_StrFind +================ +*/ +void idThread::Event_StrFind( idStr& sourceStr, idStr& subStr ) { + idThread::ReturnInt( sourceStr.Find(subStr.c_str()) ); +} + +// asalmon: award achievement +/* +================ +idThread::Event_AwardAchievement +================ +*/ +void idThread::Event_AwardAchievement( const char *name ) { +#ifdef _XENON + Live()->AwardAchievement(name); +#endif +} +// twhitaker: ceil, floor and intVal +/* +================ +idThread::Event_GetCeil +================ +*/ +void idThread::Event_GetCeil( float val ) { + ReturnFloat( idMath::Ceil( val ) ); +} + +/* +================ +idThread::Event_GetFloor +================ +*/ +void idThread::Event_GetFloor( float val ) { + ReturnFloat( idMath::Floor( val ) ); +} + +/* +================ +idThread::Event_ToInt +================ +*/ +void idThread::Event_ToInt( float val ) { + ReturnFloat( idMath::Ftoi( val ) ); +// ReturnFloat( idMath::FtoiFast( val ) ); +} + +// jdischler: send named event string to specified gui +/* +=============================== +idThread::Event_SendNamedEvent +=============================== +*/ +void idThread::Event_SendNamedEvent( int guiEnum, const char *namedEvent ) { + + typedef enum { + SNE_PLAYERHUD = 0, + SNE_CINEMATICHUD, + SNE_VEHICLECHUD, + SNE_CURSORHUD, + }; + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + switch( guiEnum ) { + case SNE_PLAYERHUD: + if ( player->hud ) { + player->hud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_CINEMATICHUD: + if ( player->cinematicHud ) { + player->cinematicHud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_VEHICLECHUD: + // twhitaker: I'd really just rather use player->GetHud() ... which returns the vehicle hud if the player is in a vehicle + // but this way the scripter will be able to strictly enforce which hud the named event gets played on. See idPlayer::GetHud() + if ( player->vehicleController.GetVehicle() ) { + idUserInterface * hud = player->vehicleController.GetVehicle()->GetHud(); + + if ( hud ) { + hud->HandleNamedEvent( namedEvent ); + } + } + break; + case SNE_CURSORHUD: + if ( player->GetCursorGUI() ) { + player->GetCursorGUI()->HandleNamedEvent( namedEvent ); + } + break; + } +} + +/* +================ +idThread::Event_SetMatSort +================ +*/ +void idThread::Event_SetMatSort( const char *name, const char *val ) const { + const idMaterial *mat = declManager->FindMaterial( name ); + if ( mat ) { + int srt = SS_DECAL; + if ( idStr::Icmp( val, "SS_MIN" ) == 0 ) { + srt = SS_MIN; + } else if ( idStr::Icmp( val, "SS_SUBVIEW" ) == 0 ) { + srt = SS_SUBVIEW; + } else if ( idStr::Icmp( val, "SS_PREGUI" ) == 0 ) { + srt = SS_PREGUI; + } else if ( idStr::Icmp( val, "SS_GUI" ) == 0 ) { + srt = SS_GUI; + } else if ( idStr::Icmp( val, "SS_BAD" ) == 0 ) { + srt = SS_BAD; + } else if ( idStr::Icmp( val, "SS_OPAQUE" ) == 0 ) { + srt = SS_OPAQUE; + } else if ( idStr::Icmp( val, "SS_PORTAL_SKY" ) == 0 ) { + srt = SS_PORTAL_SKY; + } else if ( idStr::Icmp( val, "SS_DECAL" ) == 0 ) { + srt = SS_DECAL; + } else if ( idStr::Icmp( val, "SS_FAR" ) == 0 ) { + srt = SS_FAR; + } else if ( idStr::Icmp( val, "SS_MEDIUM" ) == 0 ) { + srt = SS_MEDIUM; + } else if ( idStr::Icmp( val, "SS_CLOSE" ) == 0 ) { + srt = SS_CLOSE; + } else if ( idStr::Icmp( val, "SS_ALMOST_NEAREST" ) == 0 ) { + srt = SS_ALMOST_NEAREST; + } else if ( idStr::Icmp( val, "SS_NEAREST" ) == 0 ) { + srt = SS_NEAREST; + } else if ( idStr::Icmp( val, "SS_POST_PROCESS" ) == 0 ) { + srt = SS_POST_PROCESS; + } + + mat->SetSort( srt ); + } +} + +// jdischler: Adding ability to create texture streams from scripts +/* +================ +idThread::Event_BeginManualStreaming +================ +*/ +void idThread::Event_BeginManualStreaming() +{ +} + +/* +================ +idThread::Event_EndManualStreaming +================ +*/ +void idThread::Event_EndManualStreaming() +{ +} +// RAVEN END diff --git a/source/game/script/Script_Thread.h b/source/game/script/Script_Thread.h new file mode 100644 index 0000000..8ed76c0 --- /dev/null +++ b/source/game/script/Script_Thread.h @@ -0,0 +1,365 @@ + +#ifndef __SCRIPT_THREAD_H__ +#define __SCRIPT_THREAD_H__ + +extern const idEventDef EV_Thread_Execute; +extern const idEventDef EV_Thread_SetCallback; +extern const idEventDef EV_Thread_TerminateThread; +extern const idEventDef EV_Thread_Pause; +extern const idEventDef EV_Thread_Wait; +extern const idEventDef EV_Thread_WaitFrame; +extern const idEventDef EV_Thread_WaitFor; +extern const idEventDef EV_Thread_WaitForThread; +extern const idEventDef EV_Thread_Print; +extern const idEventDef EV_Thread_PrintLn; +extern const idEventDef EV_Thread_Say; +extern const idEventDef EV_Thread_Assert; +extern const idEventDef EV_Thread_Trigger; +extern const idEventDef EV_Thread_SetCvar; +extern const idEventDef EV_Thread_GetCvar; +extern const idEventDef EV_Thread_Random; +extern const idEventDef EV_Thread_GetTime; +extern const idEventDef EV_Thread_KillThread; +extern const idEventDef EV_Thread_SetThreadName; +extern const idEventDef EV_Thread_GetEntity; +extern const idEventDef EV_Thread_Spawn; +extern const idEventDef EV_Thread_SetSpawnArg; +extern const idEventDef EV_Thread_SpawnString; +extern const idEventDef EV_Thread_SpawnFloat; +extern const idEventDef EV_Thread_SpawnVector; +extern const idEventDef EV_Thread_AngToForward; +extern const idEventDef EV_Thread_AngToRight; +extern const idEventDef EV_Thread_AngToUp; +extern const idEventDef EV_Thread_Sine; +extern const idEventDef EV_Thread_Cosine; +extern const idEventDef EV_Thread_Normalize; +extern const idEventDef EV_Thread_VecLength; +extern const idEventDef EV_Thread_VecDotProduct; +extern const idEventDef EV_Thread_VecCrossProduct; +extern const idEventDef EV_Thread_OnSignal; +extern const idEventDef EV_Thread_ClearSignal; +extern const idEventDef EV_Thread_SetCamera; +extern const idEventDef EV_Thread_FirstPerson; +extern const idEventDef EV_Thread_TraceFraction; +extern const idEventDef EV_Thread_TracePos; +extern const idEventDef EV_Thread_FadeIn; +extern const idEventDef EV_Thread_FadeOut; +extern const idEventDef EV_Thread_FadeTo; +extern const idEventDef EV_Thread_Restart; +extern const idEventDef EV_Thread_SetMatSort; + +// RAVEN BEGIN +// rjohnson: new blur special effect +extern const idEventDef EV_Thread_SetSpecialEffect; +extern const idEventDef EV_Thread_SetSpecialEffectParm; +// RAVEN END + +class idThread : public idClass { +private: + static idThread *currentThread; + + idThread *waitingForThread; + int waitingFor; + int waitingUntil; + idInterpreter interpreter; + + idDict spawnArgs; + + int threadNum; + idStr threadName; + + int lastExecuteTime; + int creationTime; + + bool manualControl; + + static int threadIndex; + static idList threadList; + + static trace_t trace; + + void Init( void ); + void Pause( void ); + + void Event_Execute( void ); + void Event_SetThreadName( const char *name ); + + // + // script callable Events + // + void Event_TerminateThread( int num ); + void Event_Pause( void ); + void Event_Wait( float time ); + void Event_WaitFrame( void ); + void Event_WaitFor( idEntity *ent ); + void Event_WaitForThread( int num ); + void Event_Print( const char *text ); + void Event_PrintLn( const char *text ); + void Event_Say( const char *text ); + void Event_Assert( float value ); + void Event_Trigger( idEntity *ent ); + void Event_SetCvar( const char *name, const char *value ) const; + void Event_GetCvar( const char *name ) const; + void Event_Random( float range ) const; + void Event_GetTime( void ); + void Event_KillThread( const char *name ); + void Event_GetEntity( const char *name ); + void Event_Spawn( const char *classname ); + void Event_CopySpawnArgs( idEntity *ent ); + void Event_SetSpawnArg( const char *key, const char *value ); + void Event_SpawnString( const char *key, const char *defaultvalue ); + void Event_SpawnFloat( const char *key, float defaultvalue ); + void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); + void Event_ClearPersistantArgs( void ); + void Event_SetPersistantArg( const char *key, const char *value ); + void Event_GetPersistantString( const char *key ); + void Event_GetPersistantFloat( const char *key ); + void Event_GetPersistantVector( const char *key ); + void Event_AngToForward( idAngles &ang ); + void Event_AngToRight( idAngles &ang ); + void Event_AngToUp( idAngles &ang ); + void Event_GetSine( float angle ); + void Event_GetCosine( float angle ); + void Event_GetSquareRoot( float theSquare ); + void Event_VecNormalize( idVec3 &vec ); + void Event_VecLength( idVec3 &vec ); + void Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecToAngles( idVec3 &vec ); + void Event_OnSignal( int signal, idEntity *ent, const char *func ); + void Event_ClearSignalThread( int signal, idEntity *ent ); + void Event_SetCamera( idEntity *ent ); + void Event_FirstPerson( void ); + void Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ); + void Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ); + void Event_GetTraceFraction( void ); + void Event_GetTraceEndPos( void ); + void Event_GetTraceNormal( void ); + void Event_GetTraceEntity( void ); + void Event_GetTraceJoint( void ); + void Event_GetTraceBody( void ); + void Event_FadeIn( idVec3 &color, float time ); + void Event_FadeOut( idVec3 &color, float time ); + void Event_FadeTo( idVec3 &color, float alpha, float time ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_StartMusic( const char *name ); + void Event_Warning( const char *text ); + void Event_Error( const char *text ); + void Event_StrLen( const char *string ); + void Event_StrLeft( const char *string, int num ); + void Event_StrRight( const char *string, int num ); + void Event_StrSkip( const char *string, int num ); + void Event_StrMid( const char *string, int start, int num ); + void Event_StrToFloat( const char *string ); + void Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ); + void Event_IsClient( void ); + void Event_IsMultiplayer( void ); + void Event_GetFrameTime( void ); + void Event_GetTicsPerSecond( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ); + void Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ); + void Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ); + void Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ); + void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); + void Event_InfluenceActive( void ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetSpawnVector( const char *key, idVec3 &vec ); + void Event_GetArcSine( float sinValue ); + void Event_GetArcCosine( float cosValue ); + void Event_ClearSignalAllThreads( int signal, idEntity *ent ); + void Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch); + void Event_IsStringEmpty( const char *checkString ); + void Event_AnnounceToAI( const char *announcement); + void Event_ChangeCrossings(const char *originalType, const char *newType); + +// abahr: so we can fake having pure script objects + void Event_ReferenceScriptObjectProxy( const char* scriptObjectName ); + void Event_ReleaseScriptObjectProxy( const char* proxyName ); + void Event_ClampFloat( float min, float max, float val ); + void Event_MinFloat( float val1, float val2 ); + void Event_MaxFloat( float val1, float val2 ); + void Event_StrFind( idStr& sourceStr, idStr& subStr ); + void Event_RandomInt( float range ) const; + +// rjohnson: new blur special effect + void Event_SetSpecialEffect( int Effect, int Enabled ); + void Event_SetSpecialEffectParm( int Effect, int Parm, float Value ); + +// nmckenzie: string signaling + void Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ); +// asalmon: achievements for Xenon + void Event_AwardAchievement( const char *name); +// twhitaker: ceil, floor and intVal + void Event_GetCeil( float val ); + void Event_GetFloor( float val ); + void Event_ToInt( float val ); +// jdischler: send named event string to specified gui + void Event_SendNamedEvent( int guiEnum, const char *namedEvent ); + void Event_BeginManualStreaming( void ); + void Event_EndManualStreaming( void ); + void Event_SetMatSort( const char *name, const char *val ) const; +// RAVEN END + +public: + CLASS_PROTOTYPE( idThread ); + + idThread(); + idThread( idEntity *self, const function_t *func ); + idThread( const function_t *func ); + idThread( idInterpreter *source, const function_t *func, int args ); + idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ); + + virtual ~idThread(); + + // tells the thread manager not to delete this thread when it ends + void ManualDelete( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void EnableDebugInfo( void ) { interpreter.debug = true; }; + void DisableDebugInfo( void ) { interpreter.debug = false; }; + + void WaitMS( int time ); + void WaitSec( float time ); + void WaitFrame( void ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( const function_t *func, bool clearStack ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( idEntity *obj, const function_t *func, bool clearStack ); + +// RAVEN BEGIN +// bgeisler: added way to list functions + void ListStates( void ); + +// abahr: added helper functions for pushing parms onto stack + void ClearStack( void ); + void PushInt( int value ); + void PushFloat( float value ); + void PushVec3( const idVec3& value ); + void PushEntity( const idEntity* ent ); + void PushString( const char* str ); + void PushBool( bool value ); +// RAVEN END + + void DisplayInfo( void ); + static idThread *GetThread( int num ); + static void ListThreads_f( const idCmdArgs &args ); + static void Restart( void ); + static void ObjectMoveDone( int threadnum, idEntity *obj ); + + static idList& GetThreads( void ); + + bool IsDoneProcessing( void ); + bool IsDying ( void ); + + void End( void ); + static void KillThread( const char *name ); + static void KillThread( int num ); + bool Execute( void ); + void ManualControl( void ) { manualControl = true; CancelEvents( &EV_Thread_Execute ); }; + void DoneProcessing( void ) { interpreter.doneProcessing = true; }; + void ContinueProcessing( void ) { interpreter.doneProcessing = false; }; + bool ThreadDying( void ) { return interpreter.threadDying; }; + void EndThread( void ) { interpreter.threadDying = true; }; + bool IsWaiting( void ); + void ClearWaitFor( void ); + bool IsWaitingFor( idEntity *obj ); + void ObjectMoveDone( idEntity *obj ); + void ThreadCallback( idThread *thread ); + void DelayedStart( int delay ); + bool Start( void ); + idThread *WaitingOnThread( void ); + void SetThreadNum( int num ); + int GetThreadNum( void ); + void SetThreadName( const char *name ); + const char *GetThreadName( void ); + + void Error( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + + static idThread *CurrentThread( void ); + static int CurrentThreadNum( void ); + static bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + static void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + + static void ReturnString( const char *text ); + static void ReturnFloat( float value ); + static void ReturnInt( int value ); + static void ReturnVector( idVec3 const &vec ); +// RAVEN BEGIN +// abahr: added const + static void ReturnEntity( const idEntity *ent ); +// RAVEN END +}; + +/* +================ +idThread::WaitingOnThread +================ +*/ +ID_INLINE idThread *idThread::WaitingOnThread( void ) { + return waitingForThread; +} + +/* +================ +idThread::SetThreadNum +================ +*/ +ID_INLINE void idThread::SetThreadNum( int num ) { + threadNum = num; +} + +/* +================ +idThread::GetThreadNum +================ +*/ +ID_INLINE int idThread::GetThreadNum( void ) { + return threadNum; +} + +/* +================ +idThread::GetThreadName +================ +*/ +ID_INLINE const char *idThread::GetThreadName( void ) { + return threadName.c_str(); +} + +/* +================ +idThread::GetThreads +================ +*/ +ID_INLINE idList& idThread::GetThreads ( void ) { + return threadList; +} + +/* +================ +idThread::IsDoneProcessing +================ +*/ +ID_INLINE bool idThread::IsDoneProcessing ( void ) { + return interpreter.doneProcessing; +} + +/* +================ +idThread::IsDying +================ +*/ +ID_INLINE bool idThread::IsDying ( void ) { + return interpreter.threadDying; +} + +#endif /* !__SCRIPT_THREAD_H__ */ diff --git a/source/game/spawner.cpp b/source/game/spawner.cpp new file mode 100644 index 0000000..807ee6c --- /dev/null +++ b/source/game/spawner.cpp @@ -0,0 +1,600 @@ +/* +================ + +Spawner.cpp + +================ +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" +#include "vehicle/Vehicle.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AI_Util.h" + +const idEventDef EV_Spawner_RemoveNullActiveEntities( "removeNullActiveEntities" ); +const idEventDef EV_Spawner_NumActiveEntities( "numActiveEntities", "", 'd' ); +const idEventDef EV_Spawner_GetActiveEntity( "getActiveEntity", "d", 'e' ); + +CLASS_DECLARATION( idEntity, rvSpawner ) + EVENT( EV_Activate, rvSpawner::Event_Activate ) + EVENT( EV_Spawner_RemoveNullActiveEntities, rvSpawner::Event_RemoveNullActiveEntities ) + EVENT( EV_Spawner_NumActiveEntities, rvSpawner::Event_NumActiveEntities ) + EVENT( EV_Spawner_GetActiveEntity, rvSpawner::Event_GetActiveEntity ) +END_CLASS + +/* +============== +rvSpawner::Spawn +============== +*/ +void rvSpawner::Spawn( void ){ + GetPhysics()->SetContents( 0 ); + + // TEMP: read max_team_test until we can get it out of all the current maps + if ( !spawnArgs.GetInt ( "max_active", "4", maxActive ) ) { + if ( spawnArgs.GetInt ( "max_team_test", "4", maxActive ) ) { + gameLocal.Warning ( "spawner '%s' using outdated 'max_team_test', please change to 'max_active'", GetName() ); + } + } + + maxToSpawn = spawnArgs.GetInt( "count", "-1" ); + skipVisible = spawnArgs.GetBool ( "skipvisible", "1" ); + spawnWaves = spawnArgs.GetInt( "waves", "1" ); + spawnDelay = SEC2MS( spawnArgs.GetFloat( "delay", "2" ) ); + numSpawned = 0; + nextSpawnTime = 0; + + // Spawn waves has to be less than max active + if ( spawnWaves > maxActive ) { + spawnWaves = maxActive; + } + + FindSpawnTypes ( ); +} + +/* +============== +rvSpawner::Save +============== +*/ +void rvSpawner::Save ( idSaveGame *savefile ) const{ + savefile->WriteInt( numSpawned ); + savefile->WriteInt( maxToSpawn ); + savefile->WriteFloat( nextSpawnTime ); + savefile->WriteInt( maxActive ); + + int i; + savefile->WriteInt( currentActive.Num() ); + for( i = 0; i < currentActive.Num(); i++ ) { + currentActive[i].Save ( savefile ); + } + + savefile->WriteInt( spawnWaves ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( skipVisible ); + + savefile->WriteInt( spawnPoints.Num() ); + for ( i = 0; i < spawnPoints.Num(); i++ ) { + spawnPoints[ i ].Save ( savefile ); + } + + savefile->WriteInt ( callbacks.Num() ); + for ( i = 0; i < callbacks.Num(); i ++ ) { + callbacks[i].ent.Save ( savefile ); + savefile->WriteString ( callbacks[i].event ); + } +} + +/* +============== +rvSpawner::Restore +============== +*/ +void rvSpawner::Restore ( idRestoreGame *savefile ){ + int num; + int i; + + savefile->ReadInt( numSpawned ); + savefile->ReadInt( maxToSpawn ); + savefile->ReadFloat( nextSpawnTime ); + savefile->ReadInt( maxActive ); + + savefile->ReadInt( num ); + currentActive.Clear ( ); + currentActive.SetNum( num ); + for( i = 0; i < num; i++ ) { + currentActive[i].Restore ( savefile ); + } + + savefile->ReadInt( spawnWaves ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( skipVisible ); + + savefile->ReadInt( num ); + spawnPoints.SetNum( num); + for( i = 0; i < num; i ++ ) { + spawnPoints[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + callbacks.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + callbacks[i].ent.Restore ( savefile ); + savefile->ReadString ( callbacks[i].event ); + } + + FindSpawnTypes (); +} + +/* +============== +rvSpawner::FindSpawnTypes + +Generate the list of classnames to spawn from the spawnArgs. Anything matching the +prefix "def_spawn" will be included in the list. +============== +*/ +void rvSpawner::FindSpawnTypes ( void ){ + const idKeyValue *kv; + for ( kv = spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + spawnTypes.Append ( kv->GetValue ( ) ); + + // precache decls + declManager->FindType( DECL_AF, kv->GetValue(), true, false ); + } +} + +/* +============== +rvSpawner::FindTargets +============== +*/ +void rvSpawner::FindTargets ( void ) { + int i; + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + trace_t tr; + + idEntity::FindTargets ( ); + + // Copy the relevant targets to the spawn point list (right now only target_null entities) + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = ent; + + if( !spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + gameLocal.TraceBounds( this, tr, ent->GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin(), bounds, MASK_MONSTERSOLID, NULL ); + if ( gameLocal.entities[tr.c.entityNum] && !gameLocal.entities[tr.c.entityNum]->IsType ( idActor::GetClassType ( ) ) ) { + //drop a console warning here + gameLocal.Warning ( "Spawner '%s' can't spawn at point '%s', the monster won't fit.", GetName(), ent->GetName() ); + } + } + } +} + +/* +============== +rvSpawner::ValidateSpawnPoint +============== +*/ +bool rvSpawner::ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ){ + trace_t tr; + if( spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + return true; + } + + gameLocal.TraceBounds( this, tr, origin, origin, bounds, MASK_MONSTERSOLID, NULL ); + return tr.fraction >= 1.0f; +} + +/* +============== +rvSpawner::AddSpawnPoint +============== +*/ +void rvSpawner::AddSpawnPoint ( idEntity* point ) { + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = point; + + // If there were no spawnPoints then start with the delay + if ( spawnPoints.Num () == 1 ) { + nextSpawnTime = gameLocal.time + spawnDelay; + } +} + +/* +============== +rvSpawner::RemoveSpawnPoint +============== +*/ +void rvSpawner::RemoveSpawnPoint ( idEntity* point ) { + int i; + for ( i = spawnPoints.Num()-1; i >= 0; i -- ) { + if ( spawnPoints[i] == point ) { + spawnPoints.RemoveIndex ( i ); + break; + } + } +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +void rvSpawner::AddCallback ( idEntity* owner, const idEventDef* ev ) { + spawnerCallback_t& callback = callbacks.Alloc ( ); + callback.event = ev->GetName ( ); + callback.ent = owner; +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +idEntity *rvSpawner::GetSpawnPoint ( void ) { + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + idList< idEntityPtr > spawns; + int spawnIndex; + idEntity* spawnEnt; + + // Run through all spawnPoints and choose a random one. Each time a spawn point is excluded + // it will be removed from the list until there are no more items in the list. + for ( spawns = spawnPoints ; spawns.Num(); spawns.RemoveIndex ( spawnIndex ) ) { + spawnIndex = gameLocal.random.RandomInt ( spawns.Num() ); + spawnEnt = spawns[spawnIndex]; + + if ( !spawnEnt || !spawnEnt->GetPhysics() ) { + continue; + } + + // Check to see if something is in the way at this spawn point + if ( !ValidateSpawnPoint ( spawnEnt->GetPhysics()->GetOrigin(), bounds ) ) { + continue; + } + + // Skip the spawn point because its currently visible? + if ( skipVisible && gameLocal.GetLocalPlayer()->CanSee ( spawnEnt, true ) ) { + continue; + } + + // Found one! + return spawnEnt; + } + + return NULL; +} + +/* +============== +rvSpawner::GetSpawnType +============== +*/ +const char* rvSpawner::GetSpawnType ( idEntity* spawnPoint ) { + const idKeyValue* kv; + + if ( spawnPoint ) { + // If the spawn point has any "def_spawn" keys then they override the normal spawn keys + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", NULL ); + if ( kv ) { + const char* types [ MAX_SPAWN_TYPES ]; + int typeCount; + + for ( typeCount = 0; + typeCount < MAX_SPAWN_TYPES && kv; + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", kv ) ) { + types [ typeCount++ ] = kv->GetValue ( ).c_str(); + } + + return types[ gameLocal.random.RandomInt( typeCount ) ]; + } + } + + // No spawn types? + if ( !spawnTypes.Num ( ) ) { + return ""; + } + + // Return from the spawners list of types + return spawnTypes[ gameLocal.random.RandomInt( spawnTypes.Num() ) ]; +} + +/* +============== +rvSpawner::CopyPrefixedSpawnArgs +============== +*/ +void rvSpawner::CopyPrefixedSpawnArgs( idEntity *src, const char *prefix, idDict &args ){ + const idKeyValue *kv = src->spawnArgs.MatchPrefix( prefix, NULL ); + while( kv ) { + args.Set( kv->GetKey().c_str() + idStr::Length( prefix ), kv->GetValue() ); + kv = src->spawnArgs.MatchPrefix( prefix, kv ); + } +} + +/* +============== +rvSpawner::SpawnEnt +============== +*/ +bool rvSpawner::SpawnEnt( void ){ + idDict args; + idEntity* spawnPoint; + idEntity* spawnedEnt; + const char* temp; + + // Find a spawn point to spawn the entity + spawnPoint = GetSpawnPoint ( ); + if( !spawnPoint ){ + return false; + } + + // No valid spawn types for this point + temp = GetSpawnType ( spawnPoint ); + if ( !temp || !*temp ) { + gameLocal.Warning ( "Spawner '%s' could not find any valid spawn types for spawn point '%s'", GetName(), spawnPoint->GetName() ); + return false; + } + + // Build the spawn parameters for the entity about to be spawned + args.Set ( "origin", spawnPoint->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", spawnPoint->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", temp ); + args.SetBool ( "forceEnemy", spawnArgs.GetBool ( "auto_target", "1" ) ); + args.SetBool ( "faceEnemy", spawnArgs.GetBool ( "faceEnemy", "0" ) ); + + // Copy all keywords prefixed with "spawn_" to the entity being spawned. + CopyPrefixedSpawnArgs( this, "spawn_", args ); + if( spawnPoint != this ) { + CopyPrefixedSpawnArgs( spawnPoint, "spawn_", args ); + } + + // Spawn the entity + if ( !gameLocal.SpawnEntityDef ( args, &spawnedEnt ) ) { + return false; + } + + // Activate the spawned entity + spawnedEnt->ProcessEvent( &EV_Activate, this ); + + // Play a spawning effect if it has one - do we possibly want some script hooks in here? + gameLocal.PlayEffect ( spawnArgs, "fx_spawning", spawnPoint->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3() ); + + // script function for spawning guys + if( spawnArgs.GetString( "call", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + + // script function for the guy being spawned + if ( spawnArgs.GetString( "call_spawned", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( spawnedEnt, temp ); + } + + // Call all of our callbacks + int c; + for ( c = callbacks.Num() - 1; c >= 0; c-- ) { + if ( callbacks[c].ent ) { + callbacks[c].ent->ProcessEvent ( idEventDef::FindEvent ( callbacks[c].event ), this, spawnedEnt ); + } + } + + // Activate the spawn point entity when an enemy is spawned there and all of its targets + if( spawnPoint != this ){ + spawnPoint->ProcessEvent( &EV_Activate, spawnPoint ); + spawnPoint->ActivateTargets( spawnedEnt ); + + // One time use on this target? + if ( spawnPoint->spawnArgs.GetBool ( "remove" ) ) { + spawnPoint->PostEventMS ( &EV_Remove, 0 ); + } + } + + // Increment the total number spawned + numSpawned++; + + return true; +} + +/* +============== +rvSpawner::Think +============== +*/ +void rvSpawner::Think( void ){ + if ( thinkFlags & TH_THINK ) { + if( ActiveListChanged() ) {// If an entity has been removed and we have not been informed via Detach + nextSpawnTime = gameLocal.GetTime() + spawnDelay; + } + + CheckSpawn ( ); + } +} + +/* +============== +rvSpawner::CheckSpawn +============== +*/ +void rvSpawner::CheckSpawn ( void ) { + int count; + + // Any spawn points? + if ( !spawnPoints.Num ( ) ) { + return; + } + + // Is it time to spawn yet? + if ( nextSpawnTime == 0 || gameLocal.time < nextSpawnTime ) { + return; + } + + // Any left to spawn? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ){ + return; + } + + // Spawn in waves? + for ( count = 0; count < spawnWaves; count ++ ) { + // Too many active? + if( currentActive.Num() >= maxActive ) { + return; + } + + // Spawn a new entity + SpawnEnt ( ); + + // Are we at the limit now? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ) { + CallScriptEvents( "script_used_up", this ); + PostEventMS ( &EV_Remove, 0 ); + break; + } + } + + // Dont spawn again until after the delay + nextSpawnTime = gameLocal.time + spawnDelay; +} + +/* +============== +rvSpawner::CallScriptEvents +============== +*/ +void rvSpawner::CallScriptEvents( const char* prefixKey, idEntity* parm ) { + if( !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spawnArgs.MatchPrefix(prefixKey); kv; kv = spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( parm, 0 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +============== +rvSpawner::ActiveListChanged +============== +*/ +bool rvSpawner::ActiveListChanged() { + int previousCount = currentActive.Num(); + + currentActive.RemoveNull(); + + return previousCount > currentActive.Num(); +} + +/* +============== +rvSpawner::Attach + +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Attach ( idEntity* ent ) { + currentActive.AddUnique( ent ); +} + +/* +============== +rvSpawner::Detach + +Detaches the given AI from the spawner which will free up an active spot for spawning. +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Detach ( idEntity* ent ){ + currentActive.Remove( ent ); + + nextSpawnTime = gameLocal.GetTime() + spawnDelay; +} + +/* +============== +rvSpawner::Event_Activate +============== +*/ +void rvSpawner::Event_Activate ( idEntity *activator ) { + + // "trigger_only" spawners will attempt to spawn when triggered + if ( spawnArgs.GetBool ( "trigger_only" ) ) { + // Update next spawn time to follo CheckSpawn into thinking its time to spawn again + nextSpawnTime = gameLocal.time; + CheckSpawn ( ); + return; + } + + // If nextSpawnTime is zero then the spawner is currently deactivated + if ( nextSpawnTime == 0 ) { + // Start thinking + BecomeActive( TH_THINK ); + + // Allow immediate spawn + nextSpawnTime = gameLocal.time; + + // Spawn any ai targets and add them to the current count + ActivateTargets ( this ); + } else { + nextSpawnTime = 0; + BecomeInactive( TH_THINK ); + + // Remove the spawner if need be + if ( spawnArgs.GetBool ( "remove", "1" ) ) { + PostEventMS ( &EV_Remove, 0 ); + } + } +} + +/* +============== +rvSpawner::Event_RemoveNullActiveEntities +============== +*/ +void rvSpawner::Event_RemoveNullActiveEntities( void ) { + for( int ix = currentActive.Num() - 1; ix >= 0; --ix ) { + if( !currentActive[ix].IsValid() ) { + currentActive.RemoveIndex( ix ); + } + } +} + +/* +============== +rvSpawner::Event_NumActiveEntities +============== +*/ +void rvSpawner::Event_NumActiveEntities( void ) { + idThread::ReturnInt( currentActive.Num() ); +} + +/* +============== +rvSpawner::Event_GetActiveEntity +============== +*/ +void rvSpawner::Event_GetActiveEntity( int index ) { + idThread::ReturnEntity( (index < 0 || index >= currentActive.Num()) ? NULL : currentActive[index] ); +} + diff --git a/source/game/spawner.h b/source/game/spawner.h new file mode 100644 index 0000000..22595aa --- /dev/null +++ b/source/game/spawner.h @@ -0,0 +1,120 @@ +/* +================ + +Spawner.h + +================ +*/ + +#ifndef __GAME_SPAWNER_H__ +#define __GAME_SPAWNER_H__ + +const int MAX_SPAWN_TYPES = 32; + +class rvSpawner; + +typedef void (*spawnerCallbackProc_t) ( rvSpawner* spawner, idEntity* spawned, int userdata ); + +typedef struct { + idEntityPtr ent; + idStr event; +} spawnerCallback_t; + +/* +=============================================================================== + + rvSpawner + +=============================================================================== +*/ +class rvSpawner : public idEntity { +public: + CLASS_PROTOTYPE( rvSpawner ); + + void Spawn ( void ); + void Think ( void ); + + void Attach ( idEntity* ent ); + void Detach ( idEntity* ent ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void AddSpawnPoint ( idEntity* point ); + void RemoveSpawnPoint ( idEntity* point ); + + int GetNumSpawnPoints ( void ) const; + int GetNumActive ( void ) const; + int GetMaxActive ( void ) const; + idEntity* GetSpawnPoint ( int index ); + + virtual void FindTargets ( void ); + bool ActiveListChanged ( void ); + + void CallScriptEvents ( const char* prefixKey, idEntity* parm ); + + void AddCallback ( idEntity* owner, const idEventDef* ev ); + +protected: + + int numSpawned; + int maxToSpawn; + float nextSpawnTime; + int maxActive; + idList< idEntityPtr > currentActive; + int spawnWaves; + int spawnDelay; + bool skipVisible; + idStrList spawnTypes; + + idList< idEntityPtr > spawnPoints; + + idList< spawnerCallback_t > callbacks; + + // Check to see if its time to spawn + void CheckSpawn ( void ); + + // Spawn a new entity + bool SpawnEnt ( void ); + + // Populate the spawnType list with the available spawn types + void FindSpawnTypes ( void ); + + // Get a random spawnpoint to spawn at + idEntity* GetSpawnPoint ( void ); + + // Get a random spawn type + const char* GetSpawnType ( idEntity* spawnPoint ); + + // Validate the given spawn point for spawning + bool ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ); + + // Copy key/values from the given entity to the given dictionary using the specified prefix + void CopyPrefixedSpawnArgs ( idEntity *src, const char *prefix, idDict &args ); + +private: + + void Event_Activate ( idEntity *activator ); + void Event_RemoveNullActiveEntities( void ); + void Event_NumActiveEntities ( void ); + void Event_GetActiveEntity ( int index ); +}; + + +ID_INLINE int rvSpawner::GetNumSpawnPoints( void ) const { + return spawnPoints.Num ( ); +} + +ID_INLINE idEntity* rvSpawner::GetSpawnPoint( int index ) { + return spawnPoints[index]; +} + +ID_INLINE int rvSpawner::GetNumActive( void ) const { + return currentActive.Num(); +} + +ID_INLINE int rvSpawner::GetMaxActive( void ) const { + return maxActive; +} + +#endif // __GAME_SPAWNER_H__ diff --git a/source/game/vehicle/Vehicle.cpp b/source/game/vehicle/Vehicle.cpp new file mode 100644 index 0000000..9661193 --- /dev/null +++ b/source/game/vehicle/Vehicle.cpp @@ -0,0 +1,1682 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Effect.h" +#include "../ai/AI_Manager.h" +#include "../Projectile.h" + +#define VEHICLE_CRASH_DELAY 500 +#define VEHICLE_HAZARD_TIMEOUT 5000 +#define VEHICLE_LOCK_TIMEOUT 5000 + +const idEventDef EV_LaunchProjectiles( "launchProjectiles", "d" ); +const idEventDef EV_HUDShockWarningOff( ""); +const idEventDef EV_StalledRestart( "", "dd" ); +const idEventDef EV_GetViewAngles( "getViewAngles", NULL, 'v' ); + +CLASS_DECLARATION( idActor, rvVehicle ) + EVENT( EV_Door_Lock, rvVehicle::Event_Lock ) + EVENT( EV_Door_IsLocked, rvVehicle::Event_IsLocked ) + EVENT( AI_EnableMovement, rvVehicle::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvVehicle::Event_DisableMovement ) + EVENT( EV_Player_EnableWeapon, rvVehicle::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, rvVehicle::Event_DisableWeapon ) + EVENT( AI_EnableClip, rvVehicle::Event_EnableClip ) + EVENT( AI_DisableClip, rvVehicle::Event_DisableClip ) + EVENT( EV_Activate, rvVehicle::Event_Activate ) + EVENT( EV_LaunchProjectiles, rvVehicle::Event_LaunchProjectiles ) + EVENT( AI_SetScript, rvVehicle::Event_SetScript ) + EVENT( AI_SetHealth, rvVehicle::Event_SetHealth ) + EVENT( EV_HUDShockWarningOff, rvVehicle::Event_HUDShockWarningOff ) + EVENT( EV_StalledRestart, rvVehicle::Event_StalledRestart ) + EVENT( EV_GetViewAngles, rvVehicle::Event_GetViewAngles ) +END_CLASS + +/* +===================== +rvVehicle::rvVehicle +===================== +*/ +rvVehicle::rvVehicle ( void ) { + autoRight = false; + hud = NULL; + shieldModel = NULL; + shieldMaxHealth = 0; + hazardWarningTime = 0; + lockWarningTime = 0; + godModeDamage = 0; + drivers = 0; + + autoCorrectionBegin = 0; + + crashEffect = 0; + crashTime = 0; + crashNextSound = 0; + + fl.networkSync = true; +} + +rvVehicle::~rvVehicle ( void ) { + int i; + + // Force all the drivers out + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver ( ); + if ( !driver ) { + continue; + } + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + } + + positions.Clear ( ); +} + +/* +================ +rvVehicle::Spawn +================ +*/ +void rvVehicle::Spawn( void ) { + const char* temp; + + memset ( &vfl, 0, sizeof(vfl) ); + + SetPositions ( ); + + healthMax = health; + healthLow = spawnArgs.GetInt ( "lowhealth", va("%d", health / 10 ) ); + damageStaticChance = spawnArgs.GetFloat ( "damageStaticChance", "0" ); + crashSpeedSmall = spawnArgs.GetFloat ( "crashSpeedSmall", "50" ); + crashSpeedMedium = spawnArgs.GetFloat ( "crashSpeedMedium", "125" ); + crashSpeedLarge = spawnArgs.GetFloat ( "crashSpeedLarge", "200" ); + crashDamage = spawnArgs.GetString ( "def_crashDamage" ); + + healthRegenDelay = SEC2MS(spawnArgs.GetFloat( "healthRegenDelay", "0" )); + healthRegenRate = spawnArgs.GetInt( "healthRegenRate", "0" ); + healthRegenAmount.Init( gameLocal.time, 0, healthMax, healthMax ); + + vfl.disableMovement = spawnArgs.GetBool ( "disableMovement", "0" ); + vfl.disableWeapons = spawnArgs.GetBool ( "disableWeapon", "0" ); + vfl.scripted = 0; + vfl.flipEject = spawnArgs.GetBool( "allowFlipEject", "1" ); + + health = spawnArgs.GetInt ( "health", "100" ); + fl.takedamage = ( health > 0 ); + + // Load the HUD + if ( NULL != ( temp = spawnArgs.GetString( "gui_hud", "" ) ) ) { + hud = uiManager->FindGui( temp, true, false, false ); + if ( hud ) { + hud->SetStateInt ( "vehicle_id", spawnArgs.GetInt ( "hudid" ) ); + hud->Activate( true, gameLocal.time ); + } + } + + // Get shield parameters + shieldMaxHealth = spawnArgs.GetInt ( "shieldHealth", "0" ); + shieldRegenTime = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenTime", "0" ) ); + shieldRegenDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenDelay", "0" ) ); + shieldHitTime = 0; + shieldHealth.Init ( gameLocal.time, 0, shieldMaxHealth, shieldMaxHealth ); + + SetCombatModel(); + + cachedContents = GetPhysics()->GetContents(); + + funcs.enter.Init( spawnArgs.GetString( "enter_script" ) ); + funcs.exit.Init( spawnArgs.GetString( "exit_script" ) ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::Save +================ +*/ +void rvVehicle::Save ( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt ( positions.Num ( ) ); + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].Save ( savefile ); + } + savefile->WriteInt ( drivers ); + + savefile->WriteUserInterface( hud, false ); + + savefile->WriteFloat ( crashSpeedSmall ); + savefile->WriteFloat ( crashSpeedMedium ); + savefile->WriteFloat ( crashSpeedLarge ); + savefile->WriteString ( crashDamage ); + crashEffect.Save ( savefile ); + savefile->WriteInt ( crashNextSound ); + savefile->WriteInt ( crashTime ); + + savefile->WriteFloat ( autoRightDir ); + savefile->WriteBool ( autoRight ); + + savefile->WriteInt( autoCorrectionBegin ); + + savefile->Write( &vfl, sizeof( vfl ) ); + + savefile->WriteFloat ( damageStaticChance ); + + savefile->WriteFloat ( shieldMaxHealth ); + savefile->WriteInterpolate( shieldHealth ); + savefile->WriteInt ( shieldHitTime ); + savefile->WriteFloat ( shieldRegenTime ); + savefile->WriteInt ( shieldRegenDelay ); +// TOSAVE: idClipModel* shieldModel; + + savefile->WriteInt( healthRegenDelay ); + savefile->WriteInt( healthRegenRate ); + savefile->WriteInterpolate( healthRegenAmount ); + + savefile->WriteInt ( hazardWarningTime ); + savefile->WriteInt ( lockWarningTime ); + savefile->WriteInt ( healthMax ); + savefile->WriteInt ( healthLow ); + savefile->WriteInt ( godModeDamage ); + + savefile->WriteInt ( cachedContents ); + + funcs.enter.Save( savefile ); + funcs.exit.Save ( savefile ); + +// cnicholson: Don't save crash Velocities, they are assigned in Restore +// savefile->WriteFloat( crashVelocitySmall ); // cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityMedium );// cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityLarge ); // cnicholson: Added unsaved var + +// TOSAVE: idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; + +} + +/* +================ +rvVehicle::Restore +================ +*/ +void rvVehicle::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + positions.Clear(); + positions.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + positions[i].Restore ( savefile ); + } + savefile->ReadInt ( drivers ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + + savefile->ReadFloat ( crashSpeedSmall ); + savefile->ReadFloat ( crashSpeedMedium ); + savefile->ReadFloat ( crashSpeedLarge ); + savefile->ReadString ( crashDamage ); + crashEffect.Restore ( savefile ); + savefile->ReadInt ( crashNextSound ); + savefile->ReadInt ( crashTime ); + + savefile->ReadFloat ( autoRightDir ); + savefile->ReadBool ( autoRight ); + + savefile->ReadInt( (int&)autoCorrectionBegin ); + + savefile->Read( &vfl, sizeof( vfl ) ); + + savefile->ReadFloat ( damageStaticChance ); + + savefile->ReadFloat ( shieldMaxHealth ); + savefile->ReadInterpolate( shieldHealth ); + savefile->ReadInt ( shieldHitTime ); + savefile->ReadFloat ( shieldRegenTime ); + savefile->ReadInt ( shieldRegenDelay ); +// TORESTORE: idClipModel* shieldModel; + + savefile->ReadInt( healthRegenDelay ); + savefile->ReadInt( healthRegenRate ); + savefile->ReadInterpolate( healthRegenAmount ); + + savefile->ReadInt ( hazardWarningTime ); + savefile->ReadInt ( lockWarningTime ); + savefile->ReadInt ( healthMax ); + savefile->ReadInt ( healthLow ); + savefile->ReadInt ( godModeDamage ); + + savefile->ReadInt ( cachedContents ); + + funcs.enter.Restore ( savefile ); + funcs.exit.Restore ( savefile ); + + SetCombatModel ( ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::SetPositions +================ +*/ +void rvVehicle::SetPositions ( void ) { + int positionCount; + int index; + const idKeyValue* kv; + + // Count the positions first so we can allocate them all at once + positionCount = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + positionCount++; + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } + + // Every vehicle needs a def_position in it's def file + if ( positionCount == 0) { + //gameLocal.Error ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + gameLocal.Warning ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + positionCount = 1; + spawnArgs.Set( "def_position", "vehicle_ai_null_position" ); + } + + // Initialize the positions + positions.SetNum ( positionCount ); + + // Initialize all of the positions + index = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + const idDict* dict; + + // Get the position dictionary + dict = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Initialize the position + positions[index++].Init ( this, *dict ); + + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } +} + +/* +================ +rvVehicle::SetCombatModel +================ +*/ +void rvVehicle::SetCombatModel ( void ) { + idActor::SetCombatModel ( ); + + if ( shieldMaxHealth ) { + idBounds bounds; + bounds.Clear ( ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMins", "0 0 0" ) ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMaxs", "0 0 0" ) ); + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + shieldModel = NULL; + } + + //twhitaker: dodecahedron support + idStr shieldModelName; + if ( spawnArgs.GetString( "shieldModel", "", shieldModelName ) ) { + if ( shieldModelName.Length() && !shieldModelName.Icmp( "dodecahedron" ) ) { + idTraceModel trm; + trm.SetupDodecahedron ( GetPhysics()->GetBounds() ); + shieldModel = new idClipModel ( trm ); + } + } + //twhitaker: end + + if ( !shieldModel ) { + shieldModel = new idClipModel ( idTraceModel ( bounds, spawnArgs.GetInt ( "shieldSides", "6" ) ) ); + } + + shieldModel->SetOwner ( this ); + shieldModel->SetContents ( CONTENTS_SOLID ); + } else { + shieldModel = NULL; + } +} + +/* +================ +rvVehicle::LinkCombat +================ +*/ +void rvVehicle::LinkCombat ( void ) { + if ( fl.hidden ) { + return; + } + + if ( g_debugVehicle.GetInteger() == 1 && shieldModel ) { + collisionModelManager->DrawModel( shieldModel->GetCollisionModel(), renderEntity.origin, renderEntity.axis, vec3_origin, mat3_identity, 0.0f ); + } + + if ( shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 && HasDrivers ( ) ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + shieldModel->Link( this, 0, renderEntity.origin, renderEntity.axis ); +// RAVEN END + + if ( combatModel ) { + combatModel->Unlink ( ); + } + } else { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + + if ( shieldModel ) { + shieldModel->Unlink ( ); + } + } +} + +/* +================ +rvVehicle::ClientPredictionThink +================ +*/ +void rvVehicle::ClientPredictionThink ( void ) { + Think ( ); +} + +/* +================ +rvVehicle::WriteToSnapshot +================ +*/ +void rvVehicle::WriteToSnapshot ( idBitMsgDelta &msg ) const { + int i; + // TODO: Check that this conditional write to delta message is OK + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].WriteToSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::ReadFromSnapshot +================ +*/ +void rvVehicle::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + int i; + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].ReadFromSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::UpdateState +================ +*/ +void rvVehicle::UpdateState ( void ) { + rvVehiclePosition* pos; + pos = &positions[0]; + + vfl.forward = (pos->IsOccupied() && pos->mInputCmd.forwardmove > 0); + vfl.backward = (pos->IsOccupied() && pos->mInputCmd.forwardmove < 0); + vfl.right = (pos->IsOccupied() && pos->mInputCmd.rightmove > 0); + vfl.left = (pos->IsOccupied() && pos->mInputCmd.rightmove < 0); + vfl.driver = pos->IsOccupied(); + vfl.strafe = (pos->IsOccupied() && pos->mInputCmd.buttons & BUTTON_STRAFE ); +} + + +/* +================ +rvVehicle::Think +================ +*/ +void rvVehicle::Think ( void ) { + UpdateState(); + UpdateAnimState ( ); + UpdateIncomingProjectiles(); + + // If we are the current debug entity then output some info to the hud + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, this ) ) { + gameDebug.SetInt ( "vehicle", 1 ); + gameDebug.SetInt ( "shields", shieldHealth.GetCurrentValue(gameLocal.time) ); + gameDebug.SetInt ( "positions", positions.Num() ); + gameDebug.SetInt ( "drivers", drivers ); + } + + if ( g_debugVehicle.GetInteger() == 1 ) { + idMat3 flatAxis = idAngles(0,GetPhysics()->GetAxis().ToAngles().yaw,0).ToMat3(); + gameRenderWorld->DebugLine ( colorOrange, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[0] ); + gameRenderWorld->DebugLine ( colorYellow, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[1] ); + gameRenderWorld->DebugLine ( colorCyan, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() - 50.0f * flatAxis[2] ); + + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + +/* VEH_FIXME: where to put this? + // For engine glow + renderEntity.shaderParms[7] = mEngineStatus.GetCurrentValue ( gameLocal.time ); +*/ + + if( thinkFlags & TH_THINK ) { + int i; + + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPrePhysics( ); + } + + if( autoRight ) { + if( idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) < 30.0f ) { + GetPhysics()->SetLinearVelocity( vec3_origin ); + autoRight = false; + } else { + idVec3 upSpeed = -GetPhysics()->GetGravityNormal() * 72.0f; + idMat3 axis = GetPhysics()->GetAxis(); + + // Rotate around the forward axis + axis.RotateRelative ( 0, (renderEntity.axis.ToAngles().roll<0?180:-180) * MS2SEC(gameLocal.GetMSec()) * 1.5f ); + GetPhysics()->SetAxis ( axis ); + + // Move up to make room for the rotation + GetPhysics()->SetLinearVelocity( upSpeed ); + } + } + + // twhitaker: nothing special here + RunPrePhysics(); + + // Run the physics + RunPhysics(); + + // twhitaker: nothing special here + RunPostPhysics(); + + // Give each position a chance to think after physics has been run + vfl.godmode = false; + fl.notarget = false; + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPostPhysics( ); + + // Include the eye origin into the bounds to ensure the driver + // is inside the render bounds when the vehicle is rendered. + if ( positions[i].IsOccupied ( ) ) { + AddToBounds ( positions[i].GetEyeOrigin ( ) ); + + // Transfer godmode from driving players + if ( positions[i].mDriver && positions[i].mDriver->IsType ( idPlayer::GetClassType() ) ) { + if ( static_cast(positions[i].mDriver.GetEntity())->godmode ) { + vfl.godmode = true; + } + } + + // Inherit notarget if our driver has it on + if ( positions[i].mDriver && positions[i].mDriver->fl.notarget ) { + fl.notarget = true; + } + } + } + + // If the vehicle is flipped then kick out all drivers + if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) { + if ( spawnArgs.GetBool( "locked_flip_death", false ) ) { + Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 ); + } else if ( vfl.flipEject ) { + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver (); + if ( driver ) { + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + } + } + } + } + + // Regenerate the shield + if ( shieldMaxHealth && shieldHealth.GetCurrentValue(gameLocal.time) < shieldMaxHealth ) { + if ( gameLocal.time > shieldHitTime + shieldRegenDelay && shieldHealth.IsDone ( gameLocal.time ) ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldRecharge", SND_CHANNEL_BODY2, 0, false, NULL ); + + float regenTime = shieldHealth.GetCurrentValue(gameLocal.time); + regenTime /= (float)shieldMaxHealth; + regenTime = 1.0f - regenTime; + regenTime *= (float)shieldRegenTime; + shieldHealth.Init ( gameLocal.time, regenTime, shieldHealth.GetCurrentValue(gameLocal.time), shieldMaxHealth ); + } + } + + // Regenerate health, if needed + // do nothing if the vehicle is at full hit points + if (health < healthMax) + { + // also do nothing if we've been damaged less than healthRegenDelay seconds ago + if ( healthRegenRate && shieldHitTime + healthRegenDelay <= gameLocal.time ) + { + // do we need to start the interpolation? + if ( healthRegenAmount.IsDone( gameLocal.time )) + { + healthRegenAmount.Init( gameLocal.time, SEC2MS((float)(healthMax - health)/healthRegenRate), + health, healthMax ); + } + else // get our interpolated health value for the current time. + { + health = healthRegenAmount.GetCurrentValue( gameLocal.time ); + } + } + } + + // Check hazards + if ( hazardWarningTime && gameLocal.time > hazardWarningTime + VEHICLE_HAZARD_TIMEOUT ) { + hazardWarningTime = 0; + StartSound ( "snd_voiceSafe", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "info_safe" ); + } + } + + // Stop the crash effect if no collide happened this frame + if ( crashEffect && crashTime != gameLocal.time ) { + crashEffect->Stop ( ); + crashEffect = NULL; + } + + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } +} + +/* +================ +rvVehicle::UpdateDrivers +================ +*/ +void rvVehicle::UpdateDrivers ( int delta ) { + int oldDrivers = drivers; + + drivers = idMath::ClampInt ( 0, positions.Num(), drivers + delta ); + + if ( drivers && !oldDrivers ) { + if ( fl.takedamage ) { + aiManager.AddTeammate ( this ); + } + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_enter", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } else if ( !drivers ) { + aiManager.RemoveTeammate ( this ); + + refSound.listenerId = entityNumber + 1; + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_exit", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } +} + +/* +================ +rvVehicle::GetAxis +================ +*/ +const idMat3& rvVehicle::GetAxis( int id ) const { + return GetPhysics()->GetAxis( id ); +} + +/* +================ +rvVehicle::GetOrigin +================ +*/ +const idVec3& rvVehicle::GetOrigin( int id ) const { + return GetPhysics()->GetOrigin(); +} + +/* +================ +rvVehicle::GetEyePosition +================ +*/ +void rvVehicle::GetEyePosition ( int pos, idVec3& origin, idMat3& axis ) { + axis = positions[pos].GetEyeAxis ( ); + origin = positions[pos].GetEyeOrigin ( ); +} + +/* +================ +rvVehicle::GetDriverPosition +================ +*/ +void rvVehicle::GetDriverPosition ( int pos, idVec3& origin, idMat3& axis ) { + const rvVehiclePosition *position = GetPosition( pos ); + + position->GetDriverPosition( origin, axis ); +} + +/* +===================== +rvVehicle::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicle::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + idMat3 identity; + identity.Identity( ); + + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + + // mekberg: vehicle bounds are resized based on rotation but not rotated with the axis. Get transformed bounds. + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, identity ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +================ +rvVehicle::AddDriver + +Add a driver to the vehicle at the given position. Its possible that the given position is +occupied, in that case the driver will be assigned the closest valid position. If no position +can be found then (-1) will be returned, otherwise the position the driver is now driving will +be returned. +================ +*/ +int rvVehicle::AddDriver ( int position, idActor* driver ) { + int wraparound; + + // Ensure the given position is valid + if ( position < 0 || position >= positions.Num() ) { + gameLocal.Warning ( "position %d is invalid for vehicle '%s'", position, name.c_str() ); + return -1; + } + + wraparound = position; + + do { + // If the position isnt occupied then set the driver + if ( !positions[position].IsOccupied ( ) ) { + positions[position].SetDriver ( driver ); + + // Vehicle is on same team as driver + team = driver->team; + + UpdateDrivers ( 1 ); + + // The local player will hear all private sounds of the vehicle + if ( driver == gameLocal.GetLocalPlayer ( ) ) { + refSound.listenerId = driver->GetListenerId ( ); + } + + return position; + } + + // Check the next position, wrap around if necessary + position = (position + 1) % positions.Num(); + + } while ( wraparound != position ); + + return -1; +} + +/* +================ +rvVehicle::RemoveDriver + +Removes the given driver from the vehicle. This includes physically taking the driver out +of the vehicle and placing them back into the world and will follow all constraints that limit +the driver from exiting the vehicle. +================ +*/ +bool rvVehicle::RemoveDriver ( int position, bool force ) { + if ( !positions[position].EjectDriver ( force ) ) { + return false; + } + + UpdateDrivers ( -1 ); + return true; +} + +/* +================ +rvVehicle::EjectAllDrivers +================ +*/ +void rvVehicle::EjectAllDrivers( bool force ) { + for( int ix = positions.Num() - 1; ix >= 0; --ix ) { + if( !GetPosition(ix)->GetDriver() ) { + continue; + } + + GetPosition(ix)->GetDriver()->ProcessEvent( &AI_ExitVehicle, force ); + } +} + +/* +============ +rvVehicle::Damage +============ +*/ +void rvVehicle::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, int location ) { + int damage; + + if ( !fl.takedamage ) { + return; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->GetBool( "ignore_vehicles", "0" ) ) { + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damage *= damageScale; + + int save = HasDrivers() ? shieldHealth.GetCurrentValue ( gameLocal.time ) : 0; + + // If one of the drivers is the player, do the player HUD effects + for ( int i = 0; i < positions.Num(); i++ ) { + rvVehiclePosition & pos = positions[ i ]; + + if ( pos.mDriver.IsValid() && pos.mDriver->IsType( idPlayer::GetClassType() ) ) { + idPlayer & driver = static_cast< idPlayer & >( *pos.mDriver.GetEntity() ); + + if ( driver.GetHud() ) { + driver.GetHud()->HandleNamedEvent( "vehicleHit" ); + } + + if ( !stricmp( damageDef->GetString( "filter" ), "electrical" ) ) { + driver.GetHud()->HandleNamedEvent( "electricWarningOn" ); + PostEventMS( &EV_HUDShockWarningOff, spawnArgs.GetInt( "hud_shock_warning_time", "2500" ) ); + + if ( damage >= spawnArgs.GetInt( "electric_stall_damage", "20" ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect( spawnArgs , "fx_electrical_stall" ), this, SEC2MS( spawnArgs.GetFloat ( "hud_shock_warning_time", "2.5" ) ) ); + effect->Play ( gameLocal.time, false ); + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall" ); + } + + vfl.stalled = true; + PostEventMS( &EV_StalledRestart, spawnArgs.GetInt( "electric_stall_delay", "3500" ), save, damage ); + } + } + } + } + + + shieldHitTime = gameLocal.time; + + // Shields off when there is no controller + if ( !damageDef->GetBool( "noShields", "0" ) ) + { + if ( save > 0 ) { + int shield; + save = Min( save, damage ); + damage -= save; + + shield = shieldHealth.GetCurrentValue ( gameLocal.time ) - save; + shieldHealth.Init ( gameLocal.time, 0, shield, shield ); + // always stop the sound because we may be playing the recharge just now. + StopSound ( SND_CHANNEL_BODY2, false ); + + // Looping warning sound for shield + if ( shield <= 0 && !vfl.stalled ) { + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + } + } + + // God Mode? + if ( vfl.godmode && !damageDef->GetBool( "noGod" ) ) { + godModeDamage += damage; + damage = 0; + } + + if ( !damage ) { + return; + } + + // Static on the gui when hit + if ( renderEntity.gui[0] && gameLocal.random.RandomFloat() < damageStaticChance ) { + renderEntity.gui[0]->HandleNamedEvent ( "shot_static" ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + health -= damage; + if ( health < 1 && damageDef->GetBool( "nonLethal" ) ) { + health = 1; + } + if ( health <= 0 ) { + // keep the driver from being killed if we're forcing undying state + if ( g_forceUndying.GetBool() && HasDrivers() ) { + idActor * driver = NULL; + for ( int i = 0; i < positions.Num(); i++ ) { + idActor * currentDriver = positions[ i ].GetDriver(); + if ( currentDriver && currentDriver->IsType( idPlayer::GetClassType() ) ) { + driver = currentDriver; + break; + } + } + + if ( driver ) { + health = 1; + Pain( inflictor, attacker, damage, dir, 0 ); + return; + } + } + + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, 0 ); + } +} + +/* +================ +rvVehicle::Killed +================ +*/ +void rvVehicle::Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int i; + + lockWarningTime = 0; + hazardWarningTime = 0; + vfl.locked = false; + vfl.dead = true; + + // Try removing the vehicle from all lists it could be part of. + aiManager.RemoveTeammate ( this ); + + // Remove all drivers from the vehicle and kill them + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver(); + if ( !driver ) { + continue; + } + + // Dump the driver out of the vehicle and kill them + driver->health = 0; + driver->ProcessEvent ( &AI_ExitVehicle, true ); + driver->Killed( inflictor, attacker, damage, dir, location ); + } + + OnDeath(); + CheckDeathObjectives(); + + if ( spawnArgs.GetBool( "remove_on_death", "1" ) ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + + Hide ( ); + + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +rvVehicle::AddDamageEffect +================ +*/ +void rvVehicle::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + if ( HasDrivers() && shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 ) { + jointHandle_t joint = animator.GetJointHandle( spawnArgs.GetString( "fx_shield_joint", "" ) ); + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + dir.NormalizeFast ( ); + if ( INVALID_JOINT == joint ) { + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + } else { + PlayEffect ( "fx_shield", joint, false, vec3_origin, true ); + } + } +} + +/* +================ +rvVehicle::Collide +================ +*/ +bool rvVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir; + float speed; + float collisionSelfDamage = 0.0f; + + dir = velocity; + speed = dir.Normalize(); + + // No collision effect when hitting a no impact surfac +// if ( collision.c.material && collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) { +// return false; +// } + + if ( vfl.dead ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + Hide ( ); + PostEventMS( &EV_Remove, 0 ); + } + + if ( !alwaysImpactDamage ) { + if ( speed < crashSpeedSmall ) { + return false; + } + } + + // When colliding with the world play a collision effect + if ( collision.c.entityNum == ENTITYNUM_WORLD ) { + // TODO: MAterial types + if ( !crashEffect ) { + crashEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_crash" ), collision.c.point, collision.c.normal.ToMat3(), true ); + } + if ( crashEffect ) { + crashEffect->SetOrigin ( collision.c.point ); + crashEffect->SetAxis ( collision.c.normal.ToMat3() ); + crashEffect->Attenuate ( idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ) ); + } + } + + idStr collDmgDef = "damage_gev_collision_self"; + if ( gameLocal.time > crashNextSound ) { + + float dot = dir * collision.c.normal; + if( dot < -0.5f ) { + + // Crash impact sounds + if ( speed > crashSpeedLarge ) { + collisionSelfDamage += 50.0f; + StartSound ( "snd_crash_large", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedMedium ) { + collisionSelfDamage += 25.0f; + StartSound ( "snd_crash_medium", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedSmall ) { + StartSound ( "snd_crash_small", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + } + } + } + + crashTime = gameLocal.time; + float vel = GetPhysics()->GetLinearVelocity().Length(); + + if ( vel > crashVelocitySmall && crashDamage.Length ( ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + + if ( ent ) { + float f = vel > crashVelocityLarge ? 1.0f : idMath::Sqrt( vel - crashVelocityMedium ) * ( 1.0f / idMath::Sqrt( crashVelocityLarge - crashVelocityMedium ) ); + + // Now hurt him + if ( !( team != gameLocal.GetLocalPlayer()->team && ent->IsType( idPlayer::GetClassType() ) ) ) { + + const idDict* damageDef = gameLocal.FindEntityDefDict ( crashDamage, false ); + //MCG NOTE: This used to call vehicleController.GetDriver(), which was always NULL... + idActor* theDriver = positions[0].GetDriver(); + + if ( ent->fl.takedamage + && ent->health >= 0 + && !ent->spawnArgs.GetBool( "ignore_vehicle_damage" ) + && (vel > 100.0f || !ent->IsType(idPlayer::GetClassType())) ) { + //MCG NOTE: now that theDriver is being set correctly, this damage will get credited to the driver (as the attacker), unlike before + float dScale = f * ent->spawnArgs.GetFloat( "vehicle_damage_scale", "1" ); + ent->Damage( this, theDriver, dir, crashDamage, dScale, INVALID_JOINT ); + if ( vel > 100.0f ) { + //NOTE: we PURPOSELY override this here, so you don't take damage from slamming into damageable things, only invulnerable things... + collisionSelfDamage = ent->spawnArgs.GetFloat( "vehicle_impact_damage", "0" ); + collDmgDef = "damage_gev_collision"; + } + } + + // ApplyImpulse doesn't like it when you give it a null driver + // MCG: okay, now ApplyImpulse actually is getting called, + // but I'm only going to allow it on idMoveables since ApplyImpulse is + // rejected by idActors if it comes from an idActor and this code was + // never being called before. + if ( theDriver && damageDef && !ent->spawnArgs.GetBool("ignore_vehicle_push") && ent->IsType( idMoveable::GetClassType() ) ) { + float push = damageDef->GetFloat( "push" ) * speed / idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ); + idVec3 impulse = -push * f * collision.c.normal; + impulse[2] = push * f * 0.75f; + + // Send him flying + ent->ApplyImpulse( theDriver, collision.c.id, collision.c.point, impulse ); + + if ( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugArrow ( colorGreen, collision.c.point, collision.c.point + impulse, 3, 10000 ); + } + } + } + } + } + if ( collisionSelfDamage ) { + idVec3 dmgDir = GetPhysics()->GetLinearVelocity()*-1.0f; + Damage( this, this, dmgDir, collDmgDef.c_str(), collisionSelfDamage, 0 ); + } + return false; +} + +/* +=============== +rvVehicle::Give +=============== +*/ +bool rvVehicle::Give( const char *statname, const char *value ) { + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= healthMax ) { + return false; + } + health = Min ( atoi( value ) + health, healthMax ); + } + + return true; +} + +/* +================ +rvVehicle::AutoRight +================ +*/ +void rvVehicle::AutoRight( idEntity* activator ) { + if( autoRight ) { + return; + } + + autoRight = true; + +/* + idVec3 vec = renderEntity.origin - activator->renderEntity.origin; + vec.Normalize(); + + float dot = DotProduct( vec, renderEntity.axis[ 1 ] ); + + if( dot < 0 ) + { + autoRightDir = -1.0f; + } + else + { + autoRightDir = 1.0f; + } +*/ +} + +/* +================ +rvVehicle::GetPositionIndex +================ +*/ +int rvVehicle::GetPositionIndex ( const rvVehiclePosition* position ) const { + int i; + for ( i = positions.Num() - 1; i >= 0; i -- ) { + if ( &positions[i] == position ) { + return i; + } + } + return -1; +} + +/* +================ +rvVehicle::UpdateHUD +================ +*/ +void rvVehicle::UpdateHUD ( int position, idUserInterface* gui ) { +// VEH_FIXME: godmode ? +/* + if ( mController && mController->GetDriver() && mController->GetDriver()->IsType ( idPlayer::Type ) ) + { + idPlayer* player = static_cast(mController->GetDriver()); + gui->State()->SetInt ( "vehicle_god", player->godmode && g_showGodDamage->integer ); + gui->State()->SetInt ( "vehicle_god_damage", godModeDamage ); + } + else + { + gui->State()->SetInt ( "vehicle_god", 0 ); + } +*/ + gui->SetStateFloat( "vehicle_health", health / spawnArgs.GetFloat( "health", "1" ) ); + gui->SetStateInt ( "vehicle_armor", 0 ); + gui->SetStateInt ( "vehicle_position", position ); + gui->SetStateFloat ( "vehicle_shield", (float)shieldHealth.GetCurrentValue(gameLocal.time) / (float)shieldMaxHealth ); + gui->SetStateInt ( "vehicle_haz", hazardWarningTime != 0 ); + gui->SetStateInt ( "vehicle_locked", IsLocked ( ) ); + + // Update position specific information + positions[position].UpdateHUD ( gui ); +} + +/* +================ +rvVehicle::UpdateCursorGUI +================ +*/ +void rvVehicle::UpdateCursorGUI ( int position, idUserInterface* gui ) { + positions[position].UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehicle::DrawHUD +================ +*/ +void rvVehicle::DrawHUD ( int position ) { + if ( !hud || gameLocal.GetLocalPlayer()->IsObjectiveUp() || gameLocal.GetLocalPlayer()->objectiveSystemOpen ) { + return; + } + + UpdateHUD ( position, hud ); + + hud->Redraw ( gameLocal.time ); +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueHazardWarning ( void ) { + if ( !hazardWarningTime ) { + StartSound ( "snd_voiceHazard", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_hazard" ); + } + } + + hazardWarningTime = gameLocal.time; +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueLockedWarning ( void ) { + if ( gameLocal.time > lockWarningTime + VEHICLE_LOCK_TIMEOUT ) { + StartSound ( "snd_voiceNoExit", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_locked" ); + } + + lockWarningTime = gameLocal.time; + } +} + +/* +================== +rvVehicle::Hide +================== +*/ +void rvVehicle::Hide ( void ) { + idActor::Hide ( ); + + GetPhysics()->SetContents( 0 ); + GetPhysics()->GetClipModel()->Unlink(); +} + +/* +================== +rvVehicle::Show +================== +*/ +void rvVehicle::Show ( void ) { + idActor::Show ( ); + + GetPhysics()->SetContents ( CONTENTS_BODY ); + GetPhysics()->GetClipModel()->Link(); +} + +/* +================== +rvVehicle::Lock +================== +*/ +void rvVehicle::Lock( void ) { + Event_Lock( true ); +} + +/* +================== +rvVehicle::Unlock +================== +*/ +void rvVehicle::Unlock( void ) { + Event_Lock( false ); +} + +/* +================== +rvVehicle::GuidedProjectileLocked +================== +*/ +void rvVehicle::GuidedProjectileIncoming( idGuidedProjectile * projectile ) { + if ( projectile ) { + incomingProjectiles.Insert( projectile ); + } +} + +/* +================== +rvVehicle::UpdateIncomingProjectiles +================== +*/ +void rvVehicle::UpdateIncomingProjectiles( void ) { + idGuidedProjectile * proj = NULL; + float dist = 0.0f; + + for( int i = incomingProjectiles.Num() - 1; i >= 0; i-- ) { + if ( !incomingProjectiles[ i ].IsValid() ) { + incomingProjectiles.RemoveIndex( i ); + continue; + } + + if ( proj ) { + float d = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist > d ) { + proj = incomingProjectiles[ i ]; + dist = d; + } + } else { + proj = incomingProjectiles[ i ]; + dist = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + } + } + + if ( proj ) { + idVec3 localDir; + int length; + + GetPosition( 0 )->mEyeAxis.ProjectVector( proj->GetPhysics()->GetOrigin() - GetOrigin(), localDir ); + GetHud()->SetStateFloat ( "missiledir", localDir.ToAngles()[YAW] ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + + if ( !vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatUp" ); + } + + if ( emitter ) { + StartSound( "snd_incomingProjectile", SND_CHANNEL_BODY3, 0, false, &length ); + } + + vfl.missileWarningOn = true; + } + + if ( emitter ) { + //float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist / 100000.0f ) ); + //refSound.parms.volume = lerp * 0.6f + 0.5f; + //refSound.parms.frequencyShift = lerp * 0.4f + 0.8f; + //emitter->ModifySound ( SND_CHANNEL_ANY, &refSound.parms ); + } + } else if ( vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatDown" ); + } + + StopSound( SND_CHANNEL_BODY3, false ); + + vfl.missileWarningOn = false; + } +} + +/* +================== +rvVehicle::Event_Lock +================== +*/ +void rvVehicle::Event_Lock ( bool lock ) { + vfl.locked = lock; +} + +/* +================== +rvVehicle::Event_IsLocked +================== +*/ +void rvVehicle::Event_IsLocked ( void ) { + idThread::ReturnFloat( (float)IsLocked() ); +} + +/* +================== +rvVehicle::Event_EnableWeapon +================== +*/ +void rvVehicle::Event_EnableWeapon ( void ) { + vfl.disableWeapons = false; +} + +/* +================== +rvVehicle::Event_DisableWeapon +================== +*/ +void rvVehicle::Event_DisableWeapon ( void ) { + vfl.disableWeapons = true; +} + + +/* +================== +rvVehicle::Event_EnableMovement +================== +*/ +void rvVehicle::Event_EnableMovement( void ) { + vfl.disableMovement = false; +} + +/* +================== +rvVehicle::Event_DisableMovement +================== +*/ +void rvVehicle::Event_DisableMovement ( void ) { + vfl.disableMovement = true; +} + +/* +================== +rvVehicle::Event_EnableClip +================== +*/ +void rvVehicle::Event_EnableClip( void ) { + GetPhysics()->SetContents( cachedContents ); +} + +/* +================== +rvVehicle::Event_DisableClip +================== +*/ +void rvVehicle::Event_DisableClip( void ) { + cachedContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); +} + +/* +================== +rvVehicle::Event_Activate +================== +*/ +void rvVehicle::Event_Activate( idEntity* activator ) { + RemoveNullTargets(); + + if( !targets.Num() && activator && activator->IsType(idPlayer::GetClassType()) ) { + static_cast( activator )->EnterVehicle( this ); + } +} + +/* +================== +rvVehicle::Event_LaunchProjectiles +================== +*/ +void rvVehicle::Event_LaunchProjectiles( const idList* parms ) { + int pos = -1; + + assert( parms && parms->Num() ); + + sscanf( (*parms)[0].c_str(), "%d", &pos ); + GetPosition( pos )->FireWeapon(); +} + +/* +================== +rvVehicle::Event_SetScript +================== +*/ +void rvVehicle::Event_SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "enter" ) ) { + funcs.enter.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "exit" ) ) { + funcs.exit.Init( funcName ); + } else { + gameLocal.Warning ( "unknown script '%s' specified on vehicle '%s'", scriptName, name.c_str() ); + } +} + +/* +================ +rvVehicle::Event_SetHealth +================ +*/ +void rvVehicle::Event_SetHealth ( float health ) { + this->health = health; +} + +/* +================ +rvVehicle::Event_HUDShockWarningOff +================ +*/ +void rvVehicle::Event_HUDShockWarningOff ( ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "electricWarningOff" ); + } +} + +/* +================ +rvVehicle::Event_StalledRestart +================ +*/ +void rvVehicle::Event_StalledRestart ( float shield, float damage ) { + vfl.stalled = false; + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + // Looping warning sound for shield + if ( shield <= 0 ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +/* +================ +rvVehicle::Event_GetViewAngles +================ +*/ +void rvVehicle::Event_GetViewAngles ( ) { + + idThread::ReturnVector( GetPosition( 0)->GetEyeAxis()[0]); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicle ) + STATE ( "Wait_Driver", rvVehicle::State_Wait_Driver ) +END_CLASS_STATES + +/* +================ +rvVehicle::State_Wait_Driver +================ +*/ +stateResult_t rvVehicle::State_Wait_Driver ( int blendFrames ) { + if ( !vfl.driver || vfl.stalled ) { + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/source/game/vehicle/Vehicle.h b/source/game/vehicle/Vehicle.h new file mode 100644 index 0000000..5945624 --- /dev/null +++ b/source/game/vehicle/Vehicle.h @@ -0,0 +1,416 @@ +//---------------------------------------------------------------- +// Vehicle.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +class rvVehiclePosition { +public: + + rvVehiclePosition( void ); + virtual ~rvVehiclePosition( void ); + + usercmd_t mInputCmd; + idAngles mInputAngles; + usercmd_t mOldInputCmd; + idAngles mOldInputAngles; + int mOldInputFlags; + + int mCurrentWeapon; + + idEntityPtr mDriver; + idEntityPtr mParent; + + rvVehiclePartList_t mParts; + rvVehiclePartList_t mWeapons; + + idVec3 mEyeOrigin; + idMat3 mEyeAxis; + + jointHandle_t mEyeJoint; + idVec3 mEyeOffset; + idMat3 mEyeJointTransform; + idAngles mDeltaEyeAxisScale; + int mEyeJointAxisMap[3]; + int mEyeJointDirMap[3]; + + idMat3 mAxisOffset; + + jointHandle_t mDriverJoint; + idVec3 mDriverOffset; + idMat3 mDriverJointTransform; + idAngles mDeltaDriverAxisScale; + int mDriverJointAxisMap[3]; + int mDriverJointDirMap[3]; + + idVec3 mExitPosOffset; + idMat3 mExitAxisOffset; + + idStr mDriverAnim; + + idStr mInternalSurface; + + struct positionFlags_s { + bool inputValid :1; + bool engine :1; + bool driverVisible :1; + bool depthHack :1; + bool internalView :1; + bool bindDriver :1; + bool stalled :1; + } fl; + + void Init ( rvVehicle* parent, const idDict& args ); + + int AddPart ( const idTypeInfo &classdef, const idDict& args ); + rvVehiclePart* GetPart ( int partIndex ); + + bool IsOccupied ( void ) const; + bool IsEngine ( void ) const; + + bool SetDriver ( idActor* driver ); + void SetInput ( const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( usercmd_t& cmd, idAngles& newAngles ) const; + + bool EjectDriver ( bool force = false ); + + void RunPrePhysics ( void ); + void RunPostPhysics ( void ); + + void SelectWeapon ( int weapon ); + + void UpdateHUD ( idUserInterface* gui ); + void UpdateCursorGUI ( idUserInterface* gui ); + void UpdateInternalView ( bool force = false ); + + virtual idMat3 GetAxis ( void ) const; + virtual idVec3 GetOrigin ( const idVec3& offset = vec3_zero ) const; + + const idVec3& GetEyeOrigin ( void ) const; + const idMat3& GetEyeAxis ( void ) const; + rvVehicle* GetParent ( void ) const; + idActor* GetDriver ( void ) const; + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + void GetPosition ( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const; + + void FireWeapon ( void ); + + rvVehicleWeapon * GetWeapon ( int weaponIndex ); + rvVehicleWeapon * GetActiveWeapon ( void ); + +private: + + void SetParts ( const idDict& args ); + void ActivateParts ( bool active ); + + int mSoundPart; + float mSoundMaxSpeed; +}; + +typedef struct rvVehicleFuncs_s { + rvScriptFuncUtility enter; // script to run when the vehicle becomes occupied + rvScriptFuncUtility exit; // script to run when the vehicle becomes unoccupied +} rvVehicleFuncs_t; + +class rvVehicle : public idActor { +public: + + CLASS_PROTOTYPE( rvVehicle ); + + rvVehicle ( void ); + ~rvVehicle ( void ); + + void Spawn ( void ); + void Think ( void ); + bool Give ( const char *statname, const char *value ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { return false; } + + virtual const idMat3& GetAxis ( int id = 0 ) const; + virtual const idVec3& GetOrigin ( int id = 0 ) const; + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + virtual void EjectAllDrivers ( bool force = false ); + rvVehiclePosition* GetPosition ( int index ); + const rvVehiclePosition* GetPosition ( int index ) const; + int GetPositionIndex ( const rvVehiclePosition* position ) const; + + int GetNumPositions ( void ) const; + int GetNumDrivers ( void ) const; + bool HasOpenPositions ( void ) const; + + bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + virtual void UpdateState (); + + float GetEngineOffTime ( void ) const; + float GetEngineOnTime ( void ) const; + + bool IsFlipped ( void ) const; + bool IsFrozen ( void ) const; + bool IsLocked ( void ) const; + bool HasDrivers ( void ) const; + + bool IsShootingEnabled ( void ) const; + bool IsMovementEnabled ( void ) const; + + void Lock ( void ); + void Unlock ( void ); + + void AutoRight ( idEntity* activator ); + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( int position, idVec3& origin, idMat3& axis ); + virtual void GetEyePosition ( int position, idVec3& origin, idMat3& axis ); + void DrawHUD ( int position ); + idUserInterface* GetHud ( void ); + void UpdateCursorGUI ( int position, idUserInterface* ui ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( int position, usercmd_t& cmd, idAngles& newAngles ) const; + + void IssueHazardWarning ( void ); + void IssueLockedWarning ( void ); + + void AddToBounds ( const idVec3& vec ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ) {} + + float FocusLength ( void ) const { return spawnArgs.GetFloat("focusLength_enter", "60"); } + + bool IsAutoCorrecting ( void ) const { return autoCorrectionBegin != 0; } + bool IsStalled ( void ) const { return vfl.stalled; } + + void OnEnter ( void ) { funcs.enter.CallFunc( NULL ); fl.exitedVehicle=false; }; + void OnExit ( void ) { if ( !fl.exitedVehicle ) { fl.exitedVehicle=true; funcs.exit.CallFunc( NULL );} }; + + bool IsStrafing ( void ) const { return vfl.strafe; } +// void PlayAnim ( int channel, const char *name, int blendFrames ); + + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); +protected: + + void SelectWeapon ( int weapon ); + + void UpdateDrivers ( int delta ); + virtual void UpdateHUD ( int position, idUserInterface* gui ); + + void SetPositions ( void ); + + void SetCombatModel ( void ); + void LinkCombat ( void ); + + // twhitaker: + friend class rvVehicleDriver; + virtual void RunPrePhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + + idList positions; + int drivers; + + idUserInterface * hud; + + float crashSpeedSmall; + float crashSpeedMedium; + float crashSpeedLarge; + idStr crashDamage; + rvClientEffectPtr crashEffect; + int crashNextSound; + int crashTime; + + float autoRightDir; + bool autoRight; + + // twhitaker: for rvVehicleDriver, to avoid getting stuck. + unsigned autoCorrectionBegin; // time when obstacle avoidance state began (0 if not correcting) + + struct vehicleFlags_s { + bool forward :1; + bool backward :1; + bool left :1; + bool right :1; + bool driver :1; + bool locked :1; + bool godmode :1; + bool frozen :1; + bool disableWeapons :1; + bool disableMovement :1; + bool scripted :1; + bool endWithIdle :1; + bool flipEject :1; + bool dead :1; + bool strafe :1; + bool missileWarningOn :1; + bool stalled :1; + } vfl; + + float damageStaticChance; + + // Shields + float shieldMaxHealth; + idInterpolate shieldHealth; + int shieldHitTime; + float shieldRegenTime; + int shieldRegenDelay; + idClipModel* shieldModel; + + int healthRegenDelay; + int healthRegenRate; + idInterpolate healthRegenAmount; + + int hazardWarningTime; + int lockWarningTime; + int healthMax; + int healthLow; + int godModeDamage; + + int cachedContents; + + rvVehicleFuncs_t funcs; + + float crashVelocitySmall; + float crashVelocityMedium; + float crashVelocityLarge; + + bool alwaysImpactDamage; + + void UpdateIncomingProjectiles( void ); + +public: + idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; +private: + + void Event_Lock ( bool locked ); + void Event_IsLocked ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + void Event_EnableClip ( void ); + void Event_DisableClip ( void ); + void Event_Activate ( idEntity* activator ); + void Event_LaunchProjectiles ( const idList* parms ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetHealth ( float health ); + void Event_HUDShockWarningOff( void ); + void Event_StalledRestart ( float shield, float damage ); + void Event_GetViewAngles ( void ); + + virtual void OnDeath ( void ) { } + + stateResult_t State_Wait_Driver ( int blendFrames ); + + CLASS_STATES_PROTOTYPE( rvVehicle ); +}; + +/* +=============== +rvVehiclePosition inlines +=============== +*/ + +ID_INLINE bool rvVehiclePosition::IsOccupied ( void ) const { return mDriver.IsValid();} +ID_INLINE bool rvVehiclePosition::IsEngine ( void ) const { return fl.engine; } +ID_INLINE const idVec3& rvVehiclePosition::GetEyeOrigin ( void ) const { return mEyeOrigin; } +ID_INLINE const idMat3& rvVehiclePosition::GetEyeAxis ( void ) const { return mEyeAxis; } +ID_INLINE rvVehicle* rvVehiclePosition::GetParent ( void ) const { return mParent; } +ID_INLINE idActor* rvVehiclePosition::GetDriver ( void ) const { return mDriver; } +ID_INLINE rvVehiclePart* rvVehiclePosition::GetPart ( int partIndex ) { return mParts[partIndex]; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetWeapon ( int weaponIndex ){ return ( weaponIndex >= 0 && weaponIndex < mWeapons.Num() ) ? static_cast(mWeapons[weaponIndex]) : 0; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetActiveWeapon ( void ) { return GetWeapon( mCurrentWeapon ); } + +/* +=============== +rvVehicle inlines +=============== +*/ + +ID_INLINE bool rvVehicle::IsFlipped( void ) const { + return (( !vfl.flipEject ) ? false : + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) > 60.f || + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().pitch ) > 60.0f )); +} + +ID_INLINE bool rvVehicle::IsFrozen( void ) const { + return vfl.frozen; +} + +ID_INLINE idUserInterface* rvVehicle::GetHud ( void ) { + return hud; +} + +ID_INLINE bool rvVehicle::IsLocked ( void ) const { + return vfl.locked || hazardWarningTime != 0; +} + +ID_INLINE rvVehiclePosition* rvVehicle::GetPosition ( int index ) { + return &positions[index]; +} + +ID_INLINE const rvVehiclePosition* rvVehicle::GetPosition ( int index ) const { + return &positions[index]; +} + +ID_INLINE void rvVehicle::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +ID_INLINE void rvVehicle::GetInput( int position, usercmd_t& cmd, idAngles& newAngles ) const { + GetPosition(position)->GetInput( cmd, newAngles ); +} + +ID_INLINE bool rvVehicle::HasDrivers ( void ) const { + return drivers > 0; +} + +ID_INLINE void rvVehicle::AddToBounds ( const idVec3& vec ) { + renderEntity.bounds.AddPoint ( (vec-renderEntity.origin) * GetPhysics()->GetAxis().Transpose() ); +} + +ID_INLINE bool rvVehicle::IsShootingEnabled ( void ) const { + return !vfl.disableWeapons; +} + +ID_INLINE bool rvVehicle::IsMovementEnabled ( void ) const { + return !vfl.disableMovement; +} + +ID_INLINE int rvVehicle::GetNumPositions ( void ) const { + return positions.Num(); +} + +ID_INLINE int rvVehicle::GetNumDrivers ( void ) const { + return drivers; +} + +ID_INLINE bool rvVehicle::HasOpenPositions ( void ) const { + return GetNumPositions() > GetNumDrivers(); +} + +#endif // __GAME_VEHICLE_H__ diff --git a/source/game/vehicle/VehicleAnimated.cpp b/source/game/vehicle/VehicleAnimated.cpp new file mode 100644 index 0000000..380e4f8 --- /dev/null +++ b/source/game/vehicle/VehicleAnimated.cpp @@ -0,0 +1,237 @@ + +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleAnimated ) +END_CLASS + +rvVehicleAnimated::rvVehicleAnimated ( void ) { +} + +rvVehicleAnimated::~rvVehicleAnimated ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleAnimated::Spawn +================ +*/ +void rvVehicleAnimated::Spawn( void ) { + + turnRate = spawnArgs.GetFloat ( "turnRate", "90" ); + viewAngles = GetPhysics()->GetAxis ( ).ToAngles ( ); + viewAxis = viewAngles.ToMat3(); + + // Initialize the physics object + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetMaxStepHeight( spawnArgs.GetFloat ( "stepheight", "14" ) ); + + // Start just a tad above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetDelta( vec3_origin ); + + // Gravity + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat ( ); + physicsObj.SetGravity( gravity ); + SetPhysics( &physicsObj ); + + animator.RemoveOriginOffset( true ); + + additionalDelta.Zero(); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleAnimated::ClientPredictionThink +================ +*/ +void rvVehicleAnimated::ClientPredictionThink ( void ) { + Think(); +} + +/* +================ +rvVehicleAnimated::Think +================ +*/ +void rvVehicleAnimated::Think ( void ) { + + rvVehicle::Think(); + + float rate = 0.0f; + usercmd_t& cmd = positions[0].mInputCmd; + idVec3 delta; + + if( positions[0].IsOccupied() && !IsFrozen() && IsMovementEnabled() ) { + + if (( g_vehicleMode.GetInteger() == 0 && !( cmd.buttons & BUTTON_STRAFE )) || // If we're in the old driving mode and we aren't strafing or... + ( g_vehicleMode.GetInteger() != 0 && ( cmd.buttons & BUTTON_STRAFE ))) // If we're in the new driving mode and we are strafing + { + rate = SignZero(cmd.forwardmove) * turnRate; + rate *= idMath::MidPointLerp( 0.0f, 0.9f, 1.0f, idMath::Fabs(cmd.rightmove) / 127.0f ); + viewAngles.yaw += Sign(cmd.rightmove) * rate * MS2SEC(gameLocal.GetMSec()); + } + } + + viewAngles.Normalize360(); + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + delta += additionalDelta; + + idStr alignmentJoint; + if ( g_vehicleMode.GetInteger() != 0 && spawnArgs.GetString( "alignment_joint", 0, alignmentJoint ) ) { + idVec3 offset; + idMat3 axis; + GetJointWorldTransform( animator.GetJointHandle( alignmentJoint ), gameLocal.time, offset, axis ); + delta *= axis; + } else { + viewAxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + delta *= viewAxis; + } + + physicsObj.SetDelta( delta ); + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetAxis +================ +*/ +const idMat3& rvVehicleAnimated::GetAxis( int id ) const { + return viewAxis; +} + +/* +================ +rvVehicleAnimated::WriteToSnapshot +================ +*/ +void rvVehicleAnimated::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot ( msg ); + + physicsObj.WriteToSnapshot( msg ); + msg.WriteFloat( viewAngles[0] ); + msg.WriteFloat( viewAngles[1] ); + msg.WriteFloat( viewAngles[2] ); +} + +/* +================ +rvVehicleAnimated::ReadFromSnapshot +================ +*/ +void rvVehicleAnimated::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot ( msg ); + + physicsObj.ReadFromSnapshot( msg ); + viewAngles[0] = msg.ReadFloat( ); + viewAngles[1] = msg.ReadFloat( ); + viewAngles[2] = msg.ReadFloat( ); +} + +/* +================ +rvVehicleAnimated::Save +================ +*/ +void rvVehicleAnimated::Save ( idSaveGame *savefile ) const { + savefile->WriteVec3( storedPosition ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->WriteAngles ( viewAngles ); + savefile->WriteFloat ( turnRate ); + + // why are we writing out the viewAxis here??? + savefile->WriteMat3 ( viewAxis ); + + // TEMP + animator.Save( savefile ); +} + +/* +================ +rvVehicleAnimated::Restore +================ +*/ +void rvVehicleAnimated::Restore ( idRestoreGame *savefile ) { + savefile->ReadVec3( storedPosition ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadAngles ( viewAngles ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadMat3 ( viewAxis ); + + // TEMP - restore animator, because it was cleared when loading the AF + animator.Restore( savefile ); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + // TEMP + + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleAnimated::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +rvVehicleAnimated::RunPrePhysics +================ +*/ +void rvVehicleAnimated::RunPrePhysics ( void ) { + storedPosition = physicsObj.GetOrigin(); +} + +/* +================ +rvVehicleAnimated::RunPostPhysics +================ +*/ +void rvVehicleAnimated::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 3000 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + idVec3 delta; + idVec3 actualDelta = physicsObj.GetOrigin() - storedPosition; + + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( !autoCorrectionBegin && delta.LengthSqr() * 0.2f > actualDelta.LengthSqr() ) { + autoCorrectionBegin = gameLocal.time; + } + } +} + + diff --git a/source/game/vehicle/VehicleAnimated.h b/source/game/vehicle/VehicleAnimated.h new file mode 100644 index 0000000..c06251d --- /dev/null +++ b/source/game/vehicle/VehicleAnimated.h @@ -0,0 +1,49 @@ +//---------------------------------------------------------------- +// VehicleAnimated.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEANIMATED_H__ +#define __GAME_VEHICLEANIMATED_H__ + +#include "Vehicle.h" +#include "../physics/Physics_Monster.h" + +class rvVehicleAnimated : public rvVehicle { +public: + + CLASS_PROTOTYPE( rvVehicleAnimated ); + + rvVehicleAnimated ( void ); + ~rvVehicleAnimated ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual const idMat3& GetAxis ( int id = 0 ) const; + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +protected: + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedPosition; + // end twhitaker: + + idPhysics_Monster physicsObj; + + idAngles viewAngles; + float turnRate; + idVec3 additionalDelta; +}; + +#endif // __GAME_VEHICLEANIMATED_H__ diff --git a/source/game/vehicle/VehicleController.cpp b/source/game/vehicle/VehicleController.cpp new file mode 100644 index 0000000..48a9724 --- /dev/null +++ b/source/game/vehicle/VehicleController.cpp @@ -0,0 +1,301 @@ +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleController.h" +#include "Vehicle.h" + +/* +===================== +rvVehicleController::rvVehicleController +===================== +*/ +rvVehicleController::rvVehicleController ( void ) { + mVehicle = NULL; + //mDriver = NULL; + mPosition = 0; +} + +/* +===================== +rvVehicleController::Save +===================== +*/ +void rvVehicleController::Save ( idSaveGame *savefile ) const { + mVehicle.Save ( savefile ); + savefile->WriteInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Restore +===================== +*/ +void rvVehicleController::Restore ( idRestoreGame *savefile ) { + mVehicle.Restore ( savefile ); + savefile->ReadInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Drive +===================== +*/ +bool rvVehicleController::Drive ( rvVehicle* vehicle, idActor* driver ) { + assert ( vehicle && driver ); + + // Flip the vehicle back over? + if ( vehicle->IsFlipped ( ) ) { + vehicle->AutoRight ( vehicle ); + return false; + } + + // Add the driver to the vehicle and cache the position it was added. + if ( -1 == (mPosition = vehicle->AddDriver ( 0, driver ) ) ) { + return false; + } + + mVehicle = vehicle; + + //twhitaker: for scripted callback events + vehicle->OnEnter(); + + if ( driver->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( driver ); + + if ( player->GetHud() ) { + GetHud()->Activate( true, gameLocal.time ); + GetHud()->HandleNamedEvent( "showExitmessage" ); + } + } + + return true; +} + +/* +===================== +rvVehicleController::Eject +===================== +*/ +bool rvVehicleController::Eject ( bool force ) { + if ( !GetVehicle() ) { + return true; + } + + if ( GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( GetDriver() ); + + if ( player->GetHud() ) { + GetHud()->HandleNamedEvent( "hideExitmessage" ); + } + } + + if ( mVehicle->RemoveDriver ( GetPosition(), force ) ) { + mVehicle->OnExit(); + mVehicle = NULL; + + return true; + } + + return false; +} + +/* +===================== +rvVehicleController::FindClearExitPoint +===================== +*/ +bool rvVehicleController::FindClearExitPoint( idVec3& origin, idMat3& axis ) const { + return GetVehicle()->FindClearExitPoint( mPosition, origin, axis ); +} + +/* +===================== +rvVehicleController::KillVehicles +===================== +*/ +void rvVehicleController::KillVehicles ( void ) { + KillEntities( idCmdArgs(), rvVehicle::GetClassType() ); +} + +/* +===================== +rvVehicleController::DrawHUD +===================== +*/ +void rvVehicleController::DrawHUD ( void ) { + assert ( mVehicle ); + + if ( GetDriver() && GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast( GetDriver() ); + rvVehicleWeapon * weapon = mVehicle->GetPosition( mPosition )->GetActiveWeapon(); + if ( weapon ) { + if ( weapon->CanZoom() && player->IsZoomed() && player == gameLocal.GetLocalPlayer() ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } +// if ( mVehicle->GetHud() ) { +// mVehicle->GetHud()->SetStateFloat( "tram_heatpct", weapon->GetOverheatPercent()); +// mVehicle->GetHud()->SetStateFloat( "tram_overheat", weapon->GetOverheatState()); +// } + } + } + + mVehicle->DrawHUD ( mPosition ); +} + +/* +===================== +rvVehicleController::StartRadioChatter +===================== +*/ +void rvVehicleController::StartRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterUp" ); + } +} + +/* +===================== +rvVehicleController::StopRadioChatter +===================== +*/ +void rvVehicleController::StopRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterDown" ); + } +} + +/* +===================== +rvVehicleController::Give +===================== +*/ +void rvVehicleController::Give ( const char* statname, const char* value ) { + if( mVehicle.IsValid() ) { + mVehicle->Give ( statname, value ); + } +} + +/* +===================== +rvVehicleController::GetEyePosition +===================== +*/ +void rvVehicleController::GetEyePosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetEyePosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetDriverPosition +===================== +*/ +void rvVehicleController::GetDriverPosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetDriverPosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetVehicle +===================== +*/ +rvVehicle* rvVehicleController::GetVehicle ( void ) const { + return mVehicle; +} + +idActor* rvVehicleController::GetDriver ( void ) const { + return (GetVehicle()) ? GetVehicle()->GetPosition(GetPosition())->GetDriver() : NULL; +} + +/* +===================== +rvVehicleController::SetInput +===================== +*/ +void rvVehicleController::SetInput ( const usercmd_t& cmd, const idAngles &angles ) { + if( mVehicle.IsValid() ) { + mVehicle->SetInput ( mPosition, cmd, angles ); + } +} + +/* +===================== +rvVehicleController::GetInput +===================== +*/ +void rvVehicleController::GetInput( usercmd_t& cmd, idAngles &newAngles ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetInput( mPosition, cmd, newAngles ); + } +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +idUserInterface* rvVehicleController::GetHud( void ) { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +const idUserInterface* rvVehicleController::GetHud( void ) const { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::WriteToSnapshot +================ +*/ +void rvVehicleController::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteLong ( mPosition ); + msg.WriteLong ( mVehicle.GetSpawnId ( ) ); +} + +/* +================ +rvVehicleController::ReadFromSnapshot +================ +*/ +void rvVehicleController::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mPosition = msg.ReadLong ( ); + mVehicle.SetSpawnId ( msg.ReadLong ( ) ); +} + +/* +================ +rvVehicleController::UpdateCursorGUI +================ +*/ +void rvVehicleController::UpdateCursorGUI ( idUserInterface* ui ) { + assert ( mVehicle ); + mVehicle->UpdateCursorGUI ( mPosition, ui ); +} + +/* +================ +rvVehicleController::SelectWeapon +================ +*/ +void rvVehicleController::SelectWeapon ( int weapon ) { + if( mVehicle.IsValid() ) { + mVehicle->GetPosition( mPosition )->SelectWeapon( weapon ); + } +} diff --git a/source/game/vehicle/VehicleController.h b/source/game/vehicle/VehicleController.h new file mode 100644 index 0000000..bf2c4c5 --- /dev/null +++ b/source/game/vehicle/VehicleController.h @@ -0,0 +1,69 @@ +//---------------------------------------------------------------- +// VehicleController.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLECONTROLLER_H__ +#define __GAME_VEHICLECONTROLLER_H__ + +class rvVehicle; +class rvVehiclePosition; + +class rvVehicleController { +public: + + rvVehicleController ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Drive ( rvVehicle* vehicle, idActor* driver ); + bool Eject ( bool force = false ); + + bool FindClearExitPoint ( idVec3& origin, idMat3& axis ) const; + + bool IsDriving ( void ) const; + + rvVehicle* GetVehicle ( void ) const; + idActor* GetDriver ( void ) const; + int GetPosition ( void ) const; + + void SetInput ( const usercmd_t& cmd, const idAngles &newAngles ); + void GetInput ( usercmd_t& cmd, idAngles &newAngles ) const; + + idUserInterface* GetHud ( void ); + const idUserInterface* GetHud ( void ) const; + void DrawHUD ( void ); + void UpdateCursorGUI ( idUserInterface* ui ); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void Give ( const char* statname, const char* value ); + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + + static void KillVehicles ( void ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void SelectWeapon ( int weapon ); + +protected: + + idEntityPtr mVehicle; + int mPosition; +}; + +ID_INLINE bool rvVehicleController::IsDriving ( void ) const { + return mVehicle.IsValid ( ); +} + +ID_INLINE int rvVehicleController::GetPosition ( void ) const { + return mPosition; +} + +#endif // __GAME_VEHICLECONTROLLER_H__ + diff --git a/source/game/vehicle/VehicleDriver.cpp b/source/game/vehicle/VehicleDriver.cpp new file mode 100644 index 0000000..2e5e315 --- /dev/null +++ b/source/game/vehicle/VehicleDriver.cpp @@ -0,0 +1,836 @@ + +// VehicleDriver.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include + +const idEventDef VD_ChoosePathTarget( "choosePathTarget", "e", 'e' ); +const idEventDef EV_FollowOffset( "followOffset", "v" ); +const idEventDef EV_FireWeapon ( "fireWeapon", "ff" ); + +CLASS_DECLARATION( idActor, rvVehicleDriver ) + EVENT( EV_PostSpawn , rvVehicleDriver::Event_PostSpawn ) + EVENT( AI_EnterVehicle , rvVehicleDriver::Event_EnterVehicle ) + EVENT( AI_ExitVehicle , rvVehicleDriver::Event_ExitVehicle ) + EVENT( AI_ScriptedMove , rvVehicleDriver::Event_ScriptedMove ) + EVENT( AI_ScriptedDone , rvVehicleDriver::Event_ScriptedDone ) + EVENT( AI_ScriptedStop , rvVehicleDriver::Event_ScriptedStop ) + EVENT( EV_Activate , rvVehicleDriver::Event_Trigger ) + EVENT( EV_Speed , rvVehicleDriver::Event_SetSpeed ) + EVENT( EV_FireWeapon , rvVehicleDriver::Event_FireWeapon ) + EVENT( AI_FaceEntity , rvVehicleDriver::Event_FaceEntity ) + EVENT( AI_LookAt , rvVehicleDriver::Event_LookAt ) + EVENT( AI_SetLeader , rvVehicleDriver::Event_SetLeader ) + +//twhitaker: remove - begin + EVENT( EV_FollowOffset , rvVehicleDriver::Event_SetFollowOffset ) +//twhitaker: remove - end +END_CLASS + +/* +================ +rvVehicleDriver::rvVehicleDriver +================ +*/ +rvVehicleDriver::rvVehicleDriver ( void ) { +} + +/* +================ +rvVehicleDriver::Spawn +================ +*/ +void rvVehicleDriver::Spawn ( void ) { + currentThrottle = 1.0f; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; + leaderFlags = 0; + decelDistance = 0.0f; + minDistance = 0.0f; + fireEndTime = 0.0f; + isMoving = false; + avoidingLeader = false; + pathingMode = VDPM_Random; + pathingOrigin = vec3_origin; + pathingEntity = NULL; + + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + SIMDProcessor->Memset( &lastPathTargetInfo, 0, sizeof( PathTargetInfo ) ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleDriver::Think +================ +*/ +void rvVehicleDriver::Think ( void ) { + if ( !IsDriving() ) { + if ( leader ) { + leader->SetLeaderHint( VDLH_Continue ); + leader = NULL; + } + return; + } + + if ( pathTargetInfo.node ) { + float targetDistance = 0.0f; + float dotForward = 0.0f; + float dotRight = 0.0f; + float desiredThrottle = 1.0f; + idEntity* currentPathTarget = pathTargetInfo.node; + rvVehicle* vehicle = vehicleController.GetVehicle(); + idMat3 vehicleAxis = vehicle->GetAxis(); + idVec3 targetOrigin; + idVec3 dirToTarget; + usercmd_t cmd; + idAngles ang; + + // We may want to hack the auto correction variable based on what the state of the driver and the driver's leader. + UpdateAutoCorrection(); + + if ( vehicle->IsAutoCorrecting( ) ) { + if ( lastPathTargetInfo.node ) { + currentPathTarget = lastPathTargetInfo.node; + } + + if ( g_debugVehicleDriver.GetInteger() != 0 ) { + gameRenderWorld->DebugBounds( colorCyan, vehicle->GetPhysics()->GetAbsBounds().Expand( 30 ) ); + } + } + + // Touch all the triggers that the vehicle touches + vehicle->TouchTriggers(); + + GetTargetInfo( currentPathTarget, &targetOrigin, &dirToTarget, &dotForward, &dotRight, &targetDistance ); + + // The primary purpose of the following portions of code is to set the desiredThrottle variable. + if ( IsMoving() ) { + + // if we're slowing down lerp throttle. + if ( pathTargetInfo.throttle < lastPathTargetInfo.throttle ) { + desiredThrottle = idMath::Lerp( lastPathTargetInfo.throttle, pathTargetInfo.throttle, targetDistance / pathTargetInfo.initialDistance ); + } else { + // otherwise accelerate rapidly or maintain throttle. + desiredThrottle = pathTargetInfo.throttle; + } + + // we could potentially be a leader, so check the leader flags + if ( leaderFlags & VDLH_SlowDown ) { + desiredThrottle = 0.1f; + } else if ( leaderFlags & VDLH_Wait ) { + desiredThrottle = 0.0f; + } + + // if we're following a someone ... + if ( leader ) { + bool canSeeLeader = vehicle->CanSee( leader->vehicleController.GetVehicle(), false ); + float distanceToLeader = ( leader->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + avoidingLeader = false; + + // update the leaders flags based on distance and visibility + if ( !canSeeLeader ) { + // if we can't see the leader, tell him to stop + leader->SetLeaderHint( VDLH_Wait ); + + } else if ( distanceToLeader > decelDistance ) { + // if we're too far from the leader, tell him to slow down + leader->SetLeaderHint( VDLH_SlowDown ); + + } else { + // we're within range of the leader so let him move along at a normal rate + leader->SetLeaderHint( VDLH_Continue ); + + // if we're too close to the leader, we need to slow ourself down + if ( distanceToLeader < minDistance ) { + desiredThrottle = -0.2f; + avoidingLeader = true; + } + } + } + + // the desiredThrottle variable should be set at this point, the only other thing that could change it + // would be in SimulateKeys, if the dotForward < 0. + } + + vehicleController.GetInput( cmd, ang ); + + // Simulate Input + SimulateKeys( cmd, dotForward, dotRight, desiredThrottle * currentThrottle, targetDistance ); + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + + // Node Transition + if( (!vehicle->IsAutoCorrecting() || !IsValidPathNode( currentPathTarget )) && isMoving ) { + idVec3 point = targetOrigin - dirToTarget * pathTargetInfo.minDistance; + + if( vehicle->GetPhysics()->GetAbsBounds().ContainsPoint( point ) ) { + int numTargets = NumValidTargets( currentPathTarget ); + lastPathTargetInfo = pathTargetInfo; + //TODO: ponder - should I be setting lastPathTargetInfo.node to pathTargetInfo.node or currentPathTarget??? + + if( numTargets ) { + Event_ScriptedMove( ChooseNextNode( currentPathTarget ), 0, 0 ); + } + + if( lastPathTargetInfo.exitVehicle ) { + Event_ExitVehicle( true ); + } else if( lastPathTargetInfo.throttle == 0 || !numTargets ) { + Event_ScriptedStop(); + + if( !numTargets ) { + pathTargetInfo.node = NULL; + } + } + + // Debug Output + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-5, -5, -5), idVec3(5, 5, 5)), point ); + } + } + } + } else { // no path + float dotForward; + float dotRight; + usercmd_t cmd; + idAngles ang; + + vehicleController.GetInput( cmd, ang ); + + if ( GetTargetInfo( faceTarget, NULL, NULL, &dotForward, &dotRight, NULL) ) { + if( ( 1.0f - dotForward ) < VECTOR_EPSILON ) { + Event_ScriptedStop(); + } else { + SimulateKeys( cmd, dotForward, dotRight, 0.0f ); + } + } + + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + } +} + +/* +================ +rvVehicleDriver::GetTargetInfo +================ +*/ +bool rvVehicleDriver::GetTargetInfo( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, float* dotForward, float* dotRight, float* distance ) const { + if( !target || !IsDriving() ) { + return false; + } + + idMat3 vehicleAxis = vehicleController.GetVehicle()->GetAxis(); + idVec3 vehicleOrigin = vehicleController.GetVehicle()->GetOrigin(); + idVec3 forward = vehicleAxis[ 0 ]; + idVec3 localTargetOrigin = target->GetPhysics()->GetOrigin(); + localTargetOrigin.z = vehicleOrigin.z;// Find way to include vehicleAxis here + idVec3 vectorToTarget = localTargetOrigin - vehicleOrigin; + idVec3 localDirToTarget = vectorToTarget; + float distToTarget = localDirToTarget.Normalize(); + + if( targetOrigin ) { + *targetOrigin = localTargetOrigin; + } + + if( dirToTarget ) { + *dirToTarget = localDirToTarget; + } + + if( distance ) { + *distance = distToTarget; + } + + if( dotForward ) { + *dotForward = localDirToTarget * forward; + } + + if( dotRight ) { + *dotRight = localDirToTarget * vehicleAxis[ 1 ]; + } + + // Debug help + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + idStr temp; + const idVec3 & origin = GetPhysics()->GetOrigin(); + gameRenderWorld->DebugLine( colorBlue, origin, target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DebugBounds( colorBlue, GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DebugBounds( colorBlue, target->GetPhysics()->GetBounds(), target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DrawText( target->GetName(), vehicleOrigin + vectorToTarget + vehicleAxis[2] * 5.0f, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + gameRenderWorld->DrawText( idStr( "State: " ) + LeaderHintsString( leaderFlags, temp ), origin, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + + if ( vehicleController.GetVehicle()->IsAutoCorrecting() ) { + gameRenderWorld->DebugBounds( colorPurple, vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DrawText( "Auto-Correcting", vehicleOrigin, 1.0f, colorPurple, gameLocal.GetLocalPlayer()->viewAxis ); + } + + if ( pathTargetInfo.node && g_debugVehicleDriver.GetInteger( ) == 2 ) { + gameRenderWorld->DebugBounds( colorOrange, pathTargetInfo.node->GetPhysics()->GetBounds() ); + + for ( int ix = 0; ix < pathTargetInfo.node->targets.Num(); ix++ ) { + gameRenderWorld->DebugLine( colorOrange, pathTargetInfo.node->GetPhysics()->GetOrigin(), pathTargetInfo.node->targets[ ix ]->GetPhysics()->GetOrigin(), 0, true ); + } + } + if ( leader ) { + idVec4 & color = colorGreen; + + if ( leader->GetLeaderHint() & VDLH_SlowDown ) { + color = colorYellow; + } else if ( leader->GetLeaderHint() & VDLH_Wait ) { + color = colorRed; + } + + idStr str = idStr( "decel_distance: " ) + idStr( decelDistance ) + idStr( "\nmin_distance: " ) + idStr( minDistance ); + + gameRenderWorld->DrawText( str, leader->GetPhysics()->GetOrigin(), 1.0f, color, gameLocal.GetLocalPlayer()->viewAxis, 1, 0, true ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), leader->GetPhysics()->GetOrigin(), 0, true ); + gameRenderWorld->DebugBounds( color, leader->vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds(), vec3_origin, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), decelDistance, 10, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), minDistance, 10, 0, true ); + } + } + + return true; +} + +/* +================ +rvVehicleDriver::ChooseNextNode +================ +*/ +idEntity* rvVehicleDriver::ChooseNextNode( idEntity* target ) { + if( !target ) { + return NULL; + } + + if( func.Init( target->spawnArgs.GetString( "call_nextNode" )) <= 0 ) { + idEntity * best = NULL; + float bestDist; + + switch ( pathingMode ) { + case VDPM_MoveTo: + bestDist = FLT_MAX; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist > distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + break; + + case VDPM_MoveAway: + bestDist = FLT_MIN; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist < distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + case VDPM_Custom: + if ( pathingEntity ) { +// best = pathingCallback( target ); + pathingEntity->ProcessEvent( &VD_ChoosePathTarget, target ); + best = gameLocal.program.GetReturnedEntity(); + } + } + + return ( best ) ? best : RandomValidTarget( target ); + } + + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + + return ( func.ReturnsAVal()) ? gameLocal.FindEntity( spawnArgs.GetString( func.GetReturnKey() )) : const_cast( target ); +} + +/* +================ +rvVehicleDriver::SimulateButtons +================ +*/ +void rvVehicleDriver::SimulateButtons( usercmd_t& cmd ) { + cmd.buttons = (fireEndTime >= gameLocal.time) ? BUTTON_ATTACK : 0; +} + +/* +================ +rvVehicleDriver::SimulateMouseMove +================ +*/ +void rvVehicleDriver::SimulateMouseMove( usercmd_t& cmd ) { + idVec3 origin; + idMat3 axis; + + idVec3 vectorToTarget; + idAngles anglesToTarget; + idAngles turretAngles; + idAngles deltaAngles; + + if( !lookTarget ) { + for( int ix = 0; ix < 3; ++ix ) { + cmd.angles[ix] = 0; + } + return; + } + + vehicleController.GetEyePosition( origin, axis ); + vectorToTarget = (lookTarget->GetPhysics()->GetOrigin() - origin).ToNormal(); + anglesToTarget = vectorToTarget.ToAngles().Normalize360(); + turretAngles = (axis[0]).ToAngles().Normalize360(); + deltaAngles = (anglesToTarget - turretAngles).Normalize180(); + + for( int ix = 0; ix < deltaAngles.GetDimension(); ++ix ) { + cmd.angles[ix] += ANGLE2SHORT( deltaAngles[ix] ); + } + + // Debug Output + if( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugLine( colorGreen, origin, origin + anglesToTarget.ToForward() * 100.0f, 17, true ); + gameRenderWorld->DebugLine( colorYellow, origin, origin + turretAngles.ToForward() * 100.0f, 17, true ); + } +} + +/* +================ +rvVehicleDriver::SimulateKeys +================ +*/ +void rvVehicleDriver::SimulateKeys( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + if( !vehicle ) { + cmd.forwardmove = 0; + cmd.rightmove = 0; + return; + } + + cmd.forwardmove = static_cast< signed char >( (!vehicle->IsAutoCorrecting() ? 127.0f : forwardThrustScale) * dotForward * speed ); + cmd.rightmove = static_cast< signed char >( ( ( dotForward < 0.0f ) ? rightThrustScale : -rightThrustScale ) * dotRight ); +} + + +/* +================ +rvVehicleDriver::SortValid +================ +*/ +int rvVehicleDriver::SortValid( const void* a, const void* b ) { + idEntityPtr A = *(idEntityPtr*)a; + idEntityPtr B = *(idEntityPtr*)b; + + return rvVehicleDriver::IsValidTarget( B ) - rvVehicleDriver::IsValidTarget( A ); +} + +/* +================ +rvVehicleDriver::SortTargetList +================ +*/ +int rvVehicleDriver::SortTargetList( idEntity* ent ) const { + if( !ent ) { + return 0; + } + + int numValidEntities = 0; + idEntity * target; + + ent->RemoveNullTargets(); + qsort( ent->targets.Ptr(), NumTargets( ent ), ent->targets.TypeSize(), rvVehicleDriver::SortValid ); + + for( int ix = NumTargets( ent ) - 1; ix >= 0; --ix ) { + target = GetTarget( ent, ix ); + + if( IsValidTarget( target ) ) { + ++numValidEntities; + } + } + + return numValidEntities; +} + +/* +================ +rvVehicleDriver::RandomValidTarget +================ +*/ +idEntity* rvVehicleDriver::RandomValidTarget( idEntity* ent ) const { + int numValid = NumValidTargets( ent ); + return (!numValid) ? NULL : ent->targets[ rvRandom::irand(0, numValid - 1) ]; +} + +/* +================ +rvVehicleDriver::NumValidTargets +================ +*/ +int rvVehicleDriver::NumValidTargets( idEntity* ent ) const { + return SortTargetList(ent); +} + +/* +================ +rvVehicleDriver::NumTargets +================ +*/ +int rvVehicleDriver::NumTargets( const idEntity* ent ) const { + return (ent) ? ent->targets.Num() : 0; +} + +/* +================ +rvVehicleDriver::GetTarget +================ +*/ +idEntity* rvVehicleDriver::GetTarget( const idEntity* ent, int index ) const { + return (ent) ? ent->targets[index] : NULL; +} + +/* +================ +rvVehicleDriver::IsValidPathNode +================ +*/ +bool rvVehicleDriver::IsValidPathNode( const idEntity* ent ) { + if( !ent ) { + return false; + } + + return ent->IsType( idTarget::GetClassType() ); +} + +/* +================ +rvVehicleDriver::IsValidTarget +================ +*/ +bool rvVehicleDriver::IsValidTarget( const idEntity* ent ) { + return IsValidPathNode( ent ) || ent->IsType( rvVehicle::GetClassType() ) || ent->IsType( idPlayer::GetClassType() ); +} + +/* +================ +rvVehicleDriver::SetLeader +================ +*/ +bool rvVehicleDriver::SetLeader( idEntity* ent ) { + if ( !ent || !ent->IsType( rvVehicleDriver::GetClassType() ) ) { + return false; + } + + leader = static_cast( ent ); + minDistance = leader->spawnArgs.GetFloat( "min_distance", "500" ); + idStr decel_distance( minDistance * 3.0f ); + decelDistance = leader->spawnArgs.GetFloat( "decel_distance", decel_distance ); + + return true; +} + +/* +================ +rvVehicleDriver::Event_PostSpawn +================ +*/ +void rvVehicleDriver::Event_PostSpawn ( void ) { + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + Event_EnterVehicle( ent ); + } + } + + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( idTarget::GetClassType() ) ) { + Event_ScriptedMove( ent, 0, 0 ); + } + if ( ent->IsType( rvVehicleDriver::GetClassType() ) ) { + SetLeader( ent ); + } + } +} + +/* +================ +rvVehicleDriver::Event_EnterVehicle +================ +*/ +void rvVehicleDriver::Event_EnterVehicle ( idEntity * vehicle ) { + if ( vehicle ) { + forwardThrustScale = vehicle->spawnArgs.GetFloat( "driver_forward_thrust", ".75" ) * 127.0f; + rightThrustScale = vehicle->spawnArgs.GetFloat( "driver_right_thrust", "1" ) * 127.0f; + + EnterVehicle( vehicle ); + } +} + +/* +================ +rvVehicleDriver::Event_ExitVehicle +================ +*/ +void rvVehicleDriver::Event_ExitVehicle( bool force ) { + Event_ScriptedStop(); + + if( vehicleController.GetVehicle() && force && !ExitVehicle(force) ) { + vehicleController.GetVehicle()->RemoveDriver( vehicleController.GetPosition(), force ); + } + + pathTargetInfo.node = NULL; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; +} + +/* +================ +rvVehicleDriver::Event_ScriptedMove +================ +*/ +void rvVehicleDriver::Event_ScriptedMove( idEntity *target, float minDist, bool exitVehicle ) { + isMoving = false; + + if( !target ) { + return; + } + + if( IsValidTarget( target ) ) { + isMoving = true; + faceTarget = NULL; + pathTargetInfo.node = target; + pathTargetInfo.initialDistance = ( target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if ( !target->IsType( idPlayer::GetClassType() ) ) { + pathTargetInfo.minDistance = target->spawnArgs.GetFloat( "min_distance", va("%f", Max(200.0f, minDist)) ); + pathTargetInfo.throttle = idMath::ClampFloat( 0.0f, 1.0f, target->spawnArgs.GetFloat( "throttle", "1" ) ); + pathTargetInfo.exitVehicle = target->spawnArgs.GetBool( "exit_vehicle", exitVehicle ? "1" : "0" ); + + if( pathTargetInfo.exitVehicle ) { + pathTargetInfo.throttle = 0.0f; + } + } else { + pathTargetInfo.minDistance = minDist; + pathTargetInfo.throttle = 1.0f; + pathTargetInfo.exitVehicle = false; + } + + } else { + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + } +} + +/* +================ +rvVehicleDriver::Event_ScriptedDone +================ +*/ +void rvVehicleDriver::Event_ScriptedDone( void ) { + idThread::ReturnFloat( !IsMoving() ); +} + +/* +================ +rvVehicleDriver::Event_ScriptedStop +================ +*/ +void rvVehicleDriver::Event_ScriptedStop( void ) { + if( IsDriving() ) { + usercmd_t cmd = { 0 }; + + vehicleController.SetInput( cmd, ang_zero ); + + if( pathTargetInfo.node ) { + if( func.Init( pathTargetInfo.node->spawnArgs.GetString( "call_doneMoving" )) > 0 ) { + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + } + + pathTargetInfo.node->ActivateTargets(this); + pathTargetInfo.node = NULL; + } + + isMoving = false; + } +} + +/* +================ +rvVehicleDriver::Event_Trigger +================ +*/ +void rvVehicleDriver::Event_Trigger( idEntity *activator ) { + if( IsDriving() && pathTargetInfo.node ) { + isMoving = true; + } +} + +/* +================ +rvVehicleDriver::Event_SetSpeed +================ +*/ +void rvVehicleDriver::Event_SetSpeed( float speed ) { + currentThrottle = speed; +} + +//twhitaker: remove - begin +/* +================ +rvVehicleDriver::Event_SetFollowOffset +================ +*/ +void rvVehicleDriver::Event_SetFollowOffset( const idVec3 &offset ) { + gameLocal.Warning( "Script Event \"followOffset\" is deprecated, please remove it immediately to avoid errors." ); +} +//twhitaker: remove - end + +/* +================ +rvVehicleDriver::Event_FireWeapon +================ +*/ +void rvVehicleDriver::Event_FireWeapon( float weapon_index, float time ) { + if( IsDriving() ) { + fireEndTime = gameLocal.GetTime() + SEC2MS( time ); + vehicleController.SelectWeapon( weapon_index ); + } +} + +/* +================ +rvVehicleDriver::Event_FaceEntity +================ +*/ +void rvVehicleDriver::Event_FaceEntity( const idEntity* entity ) { + if( IsMoving() ) { + return; + } + + faceTarget = entity; + isMoving = true; +} + +/* +================ +rvVehicleDriver::Event_LookAt +================ +*/ +void rvVehicleDriver::Event_LookAt( const idEntity* entity ) { + lookTarget = entity; +} + +/* +================ +rvVehicleDriver::Event_SetLeader +================ +*/ +void rvVehicleDriver::Event_SetLeader( idEntity* newLeader ) { + SetLeader( newLeader ); +} + +/* +================ +rvVehicleDriver::UpdateAutoCorrection +================ +*/ +void rvVehicleDriver::UpdateAutoCorrection ( void ) { + if ( IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + // Disregard your autocorrection state if we're slowing down to avoid collision with the leader. + if ( vehicle->IsAutoCorrecting() ) { + if ( avoidingLeader == true ) { + vehicle->autoCorrectionBegin = 0; + } + } + } +} + +/* +================ +rvVehicleDriver::Save +================ +*/ +void rvVehicleDriver::Save ( idSaveGame *savefile ) const { + savefile->Write ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Write ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->WriteFloat ( currentThrottle ); + + faceTarget.Save ( savefile ); + lookTarget.Save ( savefile ); + + leader.Save ( savefile ); + savefile->WriteInt ( leaderFlags ); + savefile->WriteFloat ( decelDistance ); + savefile->WriteFloat ( minDistance ); + savefile->WriteBool ( avoidingLeader ); + + savefile->WriteFloat ( fireEndTime ); + + func.Save ( savefile ); + + savefile->WriteBool ( isMoving ); + + savefile->WriteInt ( (int&)pathingMode ); + pathingEntity.Save ( savefile ); + savefile->WriteVec3 ( pathingOrigin ); + + savefile->WriteFloat( forwardThrustScale ); // cnicholson: Added unsaved var + savefile->WriteFloat( rightThrustScale ); // cnicholson: Added unsaved var + +} + +/* +================ +rvVehicleDriver::Restore +================ +*/ +void rvVehicleDriver::Restore ( idRestoreGame *savefile ) { + savefile->Read ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Read ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->ReadFloat ( currentThrottle ); + + faceTarget.Restore ( savefile ); + lookTarget.Restore ( savefile ); + + leader.Restore ( savefile ); + savefile->ReadInt ( leaderFlags ); + savefile->ReadFloat ( decelDistance ); + savefile->ReadFloat ( minDistance ); + savefile->ReadBool ( avoidingLeader ); + + savefile->ReadFloat ( fireEndTime ); + + func.Restore ( savefile ); + + savefile->ReadBool ( isMoving ); + + savefile->ReadInt ( (int&)pathingMode ); + pathingEntity.Restore ( savefile ); + savefile->ReadVec3 ( pathingOrigin ); + + savefile->ReadFloat( forwardThrustScale ); // cnicholson: Added unrestored var + savefile->ReadFloat( rightThrustScale ); // cnicholson: Added unrestored var +} diff --git a/source/game/vehicle/VehicleDriver.h b/source/game/vehicle/VehicleDriver.h new file mode 100644 index 0000000..95258e6 --- /dev/null +++ b/source/game/vehicle/VehicleDriver.h @@ -0,0 +1,185 @@ +//---------------------------------------------------------------- +// VehicleDriver.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +extern const idEventDef VD_ChoosePathTarget; + +class rvVehicleDriver : public idActor { + friend class rvVehicleAI; + friend class rvVehicleMonster; +public: + + CLASS_PROTOTYPE( rvVehicleDriver ); + + rvVehicleDriver ( void ); + + void Think ( void ); + void Spawn ( void ); + + static bool IsValidTarget ( const idEntity* ent ); // valid entity for looking or facing + static bool IsValidPathNode ( const idEntity* ent ); // valid entity for pathing + static bool IsValidLeader ( const idEntity* ent ); // valid entity to set as a leader + + static int SortValid ( const void* a, const void* b ); + + virtual void Present ( void ) { } + +protected: + void SimulateKeys ( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance = 0 ); + void SimulateButtons ( usercmd_t& cmd ); + void SimulateMouseMove ( usercmd_t& cmd ); + + bool GetTargetInfo ( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, + float* dotForward, float* dotRight, float* distance ) const; + idEntity* ChooseNextNode ( idEntity* target ); + + bool IsMoving ( void ) const { return isMoving && IsDriving(); } + bool IsDriving ( void ) const { return vehicleController.IsDriving(); } + + bool SetLeader ( idEntity* newLeader ); + const idEntity* GetLeader ( void ) const { return leader; } + void SetLeaderHint ( int flags ) { leaderFlags = flags; } + int GetLeaderHint ( void ) const { return leaderFlags; } + + int SortTargetList ( idEntity* ent ) const; + idEntity* RandomValidTarget ( idEntity* ent ) const; + int NumValidTargets ( idEntity* ent ) const; + int NumTargets ( const idEntity* ent ) const; + idEntity* GetTarget ( const idEntity* ent, int index ) const; + + void UpdateAutoCorrection ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum VD_PathingMode { + VDPM_Random = 0, + VDPM_MoveTo = 1, + VDPM_MoveAway = 2, + VDPM_Custom = 4, + }; + + + void SetPathingMode ( VD_PathingMode mode, const idVec3 & origin ); +// CustomPathingFunc SetPathingMode ( VD_PathingMode mode, CustomPathingFunc cb ); + idEntity * SetPathingMode ( VD_PathingMode mode, idEntity * ); + VD_PathingMode GetPathingMode ( void ) const; + const idVec3 & GetPathingOrigin ( void ) const; +// CustomPathingFunc GetPathingCustomCallback ( void ) const; + idEntity * GetPathingEntity ( void ) const; + +protected: + void Event_PostSpawn ( void ); + void Event_EnterVehicle ( idEntity * vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_ScriptedMove ( idEntity *target, float minDist, bool exitVehicle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_Trigger ( idEntity *activator ); + void Event_SetSpeed ( float speed ); + void Event_FireWeapon ( float weapon_index, float time ); + void Event_FaceEntity ( const idEntity* entity ); + void Event_LookAt ( const idEntity* entity ); + void Event_SetLeader ( idEntity* newLeader ); + +//twhitaker: remove - begin + void Event_SetFollowOffset ( const idVec3 &offset ); +//twhitaker: remove - end + + // Leader hints : a driver will send the following driving hints to it's leader + enum VD_LeaderHints { + VDLH_SlowDown = 1, // Set when distance is too great + VDLH_Wait = 2, // Set when there is no line of site + VDLH_Continue = 0, // Default following behavior + }; + + idStr & LeaderHintsString( int hints, idStr & out ) const { + switch ( hints ) { + case VDLH_SlowDown: out = "SlowDown"; break; + case VDLH_Wait: out = "Wait"; break; + default: out = "Continue"; break; + } + return out; + } + + // Stores information about a path target + struct PathTargetInfo { + idEntityPtr node; // the target entity + float initialDistance; // the distance to this entity when we first decide to move there + float minDistance; // the range that specifies when this entity has been reached + float throttle; // 0 -> 1 speed setting + bool exitVehicle; // set to true to exit a vehicle when we reach this target + }; + + struct PathNavigationData { + float dotForward; + float dotRight; + }; + + // standard movement + PathTargetInfo pathTargetInfo; + PathTargetInfo lastPathTargetInfo; + float currentThrottle; + + // facing + idEntityPtr faceTarget; + idEntityPtr lookTarget; + + // following + idEntityPtr leader; //TODO: twhitaker: see if I can make this an rvVehicle (so entities can follow player) + int leaderFlags; + float decelDistance; + float minDistance; + bool avoidingLeader; + + // firing + float fireEndTime; + + // event callbacks + rvScriptFuncUtility func; + + // state + bool isMoving; + + VD_PathingMode pathingMode; +// idEntity * (* pathingCallback)( idEntity * ); + idEntityPtr pathingEntity; + idVec3 pathingOrigin; + + float forwardThrustScale; + float rightThrustScale; +}; + + +ID_INLINE void rvVehicleDriver::SetPathingMode( VD_PathingMode mode, const idVec3 & origin ) { + if ( mode != VDPM_Custom ) { + pathingMode = mode; + pathingOrigin = origin; + } +} + +ID_INLINE idEntity * rvVehicleDriver::SetPathingMode( VD_PathingMode mode, idEntity * entity ) { + if ( mode != VDPM_Custom ) { + return NULL; + } + + pathingMode = mode; + idEntity * prev = pathingEntity; + pathingEntity = entity; + return prev; +} + +ID_INLINE rvVehicleDriver::VD_PathingMode rvVehicleDriver::GetPathingMode ( void ) const { + return pathingMode; +} + +ID_INLINE const idVec3 & rvVehicleDriver::GetPathingOrigin ( void ) const { + return pathingOrigin; +} + +ID_INLINE idEntity * rvVehicleDriver::GetPathingEntity ( void ) const { + return pathingEntity; +} + diff --git a/source/game/vehicle/VehicleMonster.cpp b/source/game/vehicle/VehicleMonster.cpp new file mode 100644 index 0000000..01a97f5 --- /dev/null +++ b/source/game/vehicle/VehicleMonster.cpp @@ -0,0 +1,188 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleMonster.h" +#include "../ai/VehicleAI.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleMonster ) +END_CLASS + +rvVehicleMonster::rvVehicleMonster ( void ) { +} + +rvVehicleMonster::~rvVehicleMonster ( void ) { +} + +/* +================ +rvVehicleMonster::Spawn +================ +*/ +void rvVehicleMonster::Spawn( void ) { + idDict dict; + int count = 0; + const idKeyValue * val = NULL; + + for (;;) { + val = spawnArgs.MatchPrefix( "target", val ); + + if ( !val ) + break; + + dict.Set( "target" + (( count ) ? idStr( count ) : idStr( "" ) ), val->GetValue() ); + count++; + } + + dict.Set( "bind", name ); + dict.SetBool( "hide", true ); + + driver = static_cast( gameLocal.SpawnEntityDef( "ai_vehicle_driver", &dict ) ); + + if ( driver ) { + driver->SetVehicle( this ); + driver->GetPhysics()->SetOrigin( vec3_zero ); + } else { + gameLocal.Warning( "Unable to find \"entityDef ai_vehicle_driver\"." ); + } +} + +/* +================ +rvVehicleMonster::SetClipModel +================ +*/ +void rvVehicleMonster::SetClipModel ( idPhysics & physicsObj ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleMonster '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + +/* +================ +rvVehicleMonster::Save +================ +*/ +void rvVehicleMonster::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); +} + +/* +================ +rvVehicleMonster::Restore +================ +*/ +void rvVehicleMonster::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); +} + +/* +================ +rvVehicleMonster::Think +================ +*/ +void rvVehicleMonster::Think ( void ) { + if ( !driver ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + stateThread.Execute(); + rvVehicle::Think(); +} + +/* +================ +rvVehicleMonster::GetTargetOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetTargetOrigin ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToTarget +================ +*/ +idVec3 rvVehicleMonster::GetVectorToTarget ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin() - GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetEnemyOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetEnemyOrigin ( void ) { + if ( driver && driver->enemy.ent ) { + return driver->enemy.ent->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToEnemy +================ +*/ +idVec3 rvVehicleMonster::GetVectorToEnemy ( void ) { + if ( driver && driver->enemy.ent ) { + //HACK: this is hideous, I'm ashamed. + return ( driver->enemy.ent->GetPhysics()->GetOrigin() + idVec3( 0, 0, 36 ) )- GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::LookAtEntity +================ +*/ +void rvVehicleMonster::LookAtEntity ( idEntity *ent, float duration ) { + const idVec3 entOrigin = ent->GetPhysics()->GetOrigin(); + const idVec3 origin = GetOrigin(); + idVec3 toEnt = entOrigin - origin; + toEnt.Normalize(); + GetPhysics()->SetAxis( toEnt.ToMat3() ); +} + +/* +================ +rvVehicleMonster::SkipImpulse +================ +*/ +bool rvVehicleMonster::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/source/game/vehicle/VehicleMonster.h b/source/game/vehicle/VehicleMonster.h new file mode 100644 index 0000000..f8e1e7c --- /dev/null +++ b/source/game/vehicle/VehicleMonster.h @@ -0,0 +1,45 @@ +//---------------------------------------------------------------- +// VehicleMonster.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEMONSTER_H__ +#define __GAME_VEHICLEMONSTER_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleAI; + +class rvVehicleMonster : public rvVehicle { + friend class rvVehicleAI; +public: + + CLASS_PROTOTYPE( rvVehicleMonster ); + + rvVehicleMonster ( void ); + ~rvVehicleMonster ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( idPhysics & physicsObj ); + + const idVec3 & GetTargetOrigin ( void ); + idVec3 GetVectorToTarget ( void ); + const idVec3 & GetEnemyOrigin ( void ); + idVec3 GetVectorToEnemy ( void ); + void LookAtEntity ( idEntity *ent, float duration ); + + idEntityPtr driver; +}; + +#endif // __GAME_VEHICLEMONSTER_H__ diff --git a/source/game/vehicle/VehicleParts.cpp b/source/game/vehicle/VehicleParts.cpp new file mode 100644 index 0000000..ae41c38 --- /dev/null +++ b/source/game/vehicle/VehicleParts.cpp @@ -0,0 +1,2386 @@ +//---------------------------------------------------------------- +// VehicleParts.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "../Effect.h" +#include "Vehicle.h" +#include "VehicleParts.h" +#include "../ai/AI_Manager.h" + +/*********************************************************************** + + rvVehiclePart + +***********************************************************************/ + +ABSTRACT_DECLARATION( idClass, rvVehiclePart ) +END_CLASS + +/* +===================== +rvVehiclePart::Init +===================== +*/ +bool rvVehiclePart::Init ( rvVehiclePosition* _position, const idDict& args, s_channelType _soundChannel ) { + spawnArgs.Copy ( args ); + + soundChannel = _soundChannel; + position = _position; + parent = position->GetParent(); + fl.active = false; + fl.useCenterMass = false; + + return true; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehiclePart::Spawn ( void ) { + // Get joint for orienting the part + joint = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint", "" ) ); + + // More position information + fl.useCenterMass = spawnArgs.GetBool ( "useCenterMass", "0" ); + spawnArgs.GetVector ( "offset", "0 0 0", localOffset ); + + fl.active = false; + + fl.useViewAxis = spawnArgs.GetBool( "useViewAxis", "0" ); + + // Initialize the origin so we can determine which side of the vehicle we are on + UpdateOrigin ( ); + + // Determine if this part is on the left and/or front side of the vehicle + idVec3 localOrigin; + localOrigin = (worldOrigin - parent->GetPhysics()->GetCenterMass()); + fl.front = (localOrigin * parent->GetPhysics()->GetAxis()[0] < 0.0f); + fl.left = (localOrigin * parent->GetPhysics()->GetAxis()[1] < 0.0f); +} + +/* +================ +rvVehiclePart::UpdateOrigin +================ +*/ +void rvVehiclePart::UpdateOrigin ( void ) { + if ( joint != INVALID_JOINT ) { + parent->GetJointWorldTransform ( joint, gameLocal.time, worldOrigin, worldAxis ); + } else { + if ( fl.useViewAxis ) { + worldAxis = parent->viewAxis; + } else { + worldAxis = parent->GetPhysics()->GetAxis(); + } + worldOrigin = fl.useCenterMass ? parent->GetPhysics()->GetCenterMass() : parent->GetPhysics()->GetOrigin(); + } + + worldOrigin += (localOffset * worldAxis); + + // Include this part in the total bounds + // FIXME: bounds are local + parent->AddToBounds ( worldOrigin ); +} + +/* +================ +rvVehiclePart::Save +================ +*/ +void rvVehiclePart::Save ( idSaveGame* savefile ) const { + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteDict ( &spawnArgs ); + parent.Save ( savefile ); + savefile->WriteJoint ( joint ); + savefile->WriteInt ( soundChannel ); + savefile->WriteInt ( parent->GetPositionIndex ( position ) ); + + savefile->WriteVec3 ( worldOrigin ); + savefile->WriteMat3 ( worldAxis ); + savefile->WriteVec3 ( localOffset ); +} + +/* +================ +rvVehiclePart::Restore +================ +*/ +void rvVehiclePart::Restore ( idRestoreGame* savefile ) { + int temp; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadDict ( &spawnArgs ); + parent.Restore ( savefile ); + savefile->ReadJoint ( joint ); + savefile->ReadInt ( soundChannel ); + + savefile->ReadInt ( temp ); + position = parent->GetPosition ( temp ); + + savefile->ReadVec3 ( worldOrigin ); + savefile->ReadMat3 ( worldAxis ); + savefile->ReadVec3 ( localOffset ); +} + +/*********************************************************************** + + rvVehicleSound + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleSound ) +END_CLASS + +/* +===================== +rvVehicleSound::rvVehicleSound +===================== +*/ +rvVehicleSound::rvVehicleSound ( void ) { + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + fade = false; + autoActivate = true; +} + +/* +===================== +rvVehicleSound::~rvVehicleSound +===================== +*/ +rvVehicleSound::~rvVehicleSound ( void ) { + Stop(); + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehicleSound::Spawn ( void ) { + soundName = spawnArgs.GetString ( "snd_loop" ); + + spawnArgs.GetVec2 ( "freqShift", "1 1", freqShift ); + spawnArgs.GetVec2 ( "volume", "0 0", volume ); + + // Temp fix for volume + volume[0] = idMath::dBToScale( volume[0] ); + volume[1] = idMath::dBToScale( volume[1] ); + + declManager->FindSound ( soundName )->GetParms ( &refSound.parms ); +} + +/* +===================== +rvVehicleSound::RunPostPhysics +===================== +*/ +void rvVehicleSound::RunPostPhysics ( void ) { + Update ( ); +} + +/* +===================== +rvVehicleSound::Activate +===================== +*/ +void rvVehicleSound::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( autoActivate ) { + if ( active ) { + Play ( ); + } else { + Stop ( ); + } + } +} + +/* +===================== +rvVehicleSound::Play +===================== +*/ +void rvVehicleSound::Play ( void ) { + if ( !soundName.Length ( ) ) { + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + Attenuate ( 0.0f, 0.0f ); + + Update ( true ); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound ( declManager->FindSound( soundName ), soundChannel, 0, 0 ); + } +} + +/* +===================== +rvVehicleSound::Stop +===================== +*/ +void rvVehicleSound::Stop ( void ) { + if ( IsPlaying ( ) ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->StopSound( soundChannel ); + } + } +} + +/* +===================== +rvVehicleSound::Update +===================== +*/ +void rvVehicleSound::Update ( bool force ) { + if ( !force && !IsPlaying ( ) ) { + return; + } + + if ( fade && currentVolume.IsDone ( gameLocal.time ) ) { + Stop ( ); + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return; + } + + UpdateOrigin ( ); + + refSound.parms.volume = currentVolume.GetCurrentValue ( gameLocal.time ); + refSound.parms.frequencyShift = currentFreqShift.GetCurrentValue ( gameLocal.time ); + emitter->ModifySound ( soundChannel, &refSound.parms ); + + refSound.origin = worldOrigin; + // bdube: please put something sensible here if you want doppler from the sound system + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, parent->entityNumber + 1, &refSound.parms ); +} + +/* +===================== +rvVehicleSound::Attenuate +===================== +*/ +void rvVehicleSound::Attenuate ( float volumeAttenuate, float freqAttenuate ) { + float f; + + fade = false; + + f = volume[0] + (volume[1]-volume[0]) * volumeAttenuate; + currentVolume.Init ( gameLocal.time, 100, currentVolume.GetCurrentValue(gameLocal.time), f ); + + f = freqShift[0] + (freqShift[1]-freqShift[0]) * freqAttenuate; + currentFreqShift.Init ( gameLocal.time, 100, currentFreqShift.GetCurrentValue(gameLocal.time), f ); +} + +/* +===================== +rvVehicleSound::Fade +===================== +*/ +void rvVehicleSound::Fade ( int duration, float toVolume, float toFreq ) { + currentVolume.Init ( gameLocal.time, duration, currentVolume.GetCurrentValue(gameLocal.time), toVolume ); + currentFreqShift.Init ( gameLocal.time, duration, currentFreqShift.GetCurrentValue(gameLocal.time), toFreq ); +} + +/* +===================== +rvVehicleSound::Save +===================== +*/ +void rvVehicleSound::Save ( idSaveGame* savefile ) const { + savefile->WriteVec2 ( volume ); + savefile->WriteVec2 ( freqShift ); + savefile->WriteString ( soundName ); + savefile->WriteRefSound ( refSound ); + + savefile->WriteInterpolate ( currentVolume ); + savefile->WriteInterpolate ( currentFreqShift ); + + savefile->WriteBool ( fade ); + savefile->WriteBool ( autoActivate ); +} + +/* +===================== +rvVehicleSound::Restore +===================== +*/ +void rvVehicleSound::Restore ( idRestoreGame* savefile ) { + savefile->ReadVec2 ( volume ); + savefile->ReadVec2 ( freqShift ); + savefile->ReadString ( soundName ); + savefile->ReadRefSound ( refSound ); + + savefile->ReadInterpolate ( currentVolume ); + savefile->ReadInterpolate ( currentFreqShift ); + + savefile->ReadBool ( fade ); + savefile->ReadBool ( autoActivate ); +} + +//---------------------------------------------------------------- +// +// rvVehicleLight +// +//---------------------------------------------------------------- + +CLASS_DECLARATION( rvVehiclePart, rvVehicleLight ) +END_CLASS + +/* +===================== +rvVehicleLight::rvVehicleLight +===================== +*/ +rvVehicleLight::rvVehicleLight ( void ) { + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::~rvVehicleLight +===================== +*/ +rvVehicleLight::~rvVehicleLight ( void ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +/* +===================== +rvVehicleLight::Spawn +===================== +*/ +void rvVehicleLight::Spawn ( void ) { + idVec3 color; + const char* temp; + + memset ( &renderLight, 0, sizeof(renderLight) ); + + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "pointlight", "1" ); + + spawnArgs.GetVector( "color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "target" ); + renderLight.up = spawnArgs.GetVector( "up" ); + renderLight.right = spawnArgs.GetVector( "right" ); + renderLight.end = spawnArgs.GetVector( "target" );; + } + + // Hide flare surface if there is one + temp = spawnArgs.GetString ( "flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + + // Sounds shader when turning light + spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + spawnArgs.GetString ( "snd_off", "", soundOff); + + lightOn = spawnArgs.GetBool( "start_on", "1" ); + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::RunPostPhysics +===================== +*/ +void rvVehicleLight::RunPostPhysics ( void ) { + if ( lightHandle == -1 || !lightOn ) { + return; + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::UpdateLightDef +===================== +*/ +void rvVehicleLight::UpdateLightDef ( void ) { + UpdateOrigin ( ); + + renderLight.origin = worldOrigin; + renderLight.axis = worldAxis; + + if ( lightHandle == -1 ) { + return; + } + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); +} + +/* +===================== +rvVehicleLight::Activate +===================== +*/ +void rvVehicleLight::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active && lightOn ) { + TurnOn ( ); + } else { + TurnOff ( ); + } +} + +/* +===================== +rvVehicleLight::TurnOff +===================== +*/ +void rvVehicleLight::TurnOff ( void ) { + const char* surface; + + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + + // Play off sound + if ( soundOff.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOff ), soundChannel, 0, false, NULL ); + } + } + + // Hide flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_HideSurface, surface ); + } +} + +/* +===================== +rvVehicleLight::TurnOn +===================== +*/ +void rvVehicleLight::TurnOn ( void ) { + const char* surface; + + if ( lightHandle == -1 ) { + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + // Play off sound + if ( soundOn.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOn ), soundChannel, 0, false, NULL ); + } + + // Show flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_ShowSurface, surface ); + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::Impulse +===================== +*/ +void rvVehicleLight::Impulse ( int impulse ) { + switch ( impulse ) { + case IMPULSE_50: + if ( lightOn ) { + lightOn = false; + TurnOff ( ); + } else { + lightOn = true; + TurnOn ( ); + } + break; + } +} + +/* +===================== +rvVehicleLight::Save +===================== +*/ +void rvVehicleLight::Save ( idSaveGame* savefile ) const { + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + savefile->WriteBool ( lightOn ); + savefile->WriteString ( soundOn ); + savefile->WriteString ( soundOff ); +} + +/* +===================== +rvVehicleLight::Restore +===================== +*/ +void rvVehicleLight::Restore ( idRestoreGame* savefile ) { + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadBool ( lightOn ); + savefile->ReadString ( soundOn ); + savefile->ReadString ( soundOff ); +} + +/*********************************************************************** + + rvVehicleWeapon + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleWeapon ) +END_CLASS + +#define WEAPON_DELAY_FIRE 500 + +/* +===================== +rvVehicleWeapon::rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::rvVehicleWeapon ( void ) { +#ifdef _XENON + bestEnemy = 0; +#endif + nextFireTime = 0; + fireDelay = 0; + hitScanDef = NULL; + projectileDef = NULL; + animNum = 0; +} + +/* +===================== +rvVehicleWeapon::~rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::~rvVehicleWeapon ( void ) { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + StopTargetEffect( ); +} + +/* +===================== +rvVehicleWeapon::Spawn +===================== +*/ +void rvVehicleWeapon::Spawn ( void ) { + int i; + idStr temp; + idVec3 color; + +#ifdef _XENON + bestEnemy = 0; +#endif + + launchFromJoint = spawnArgs.GetBool ( "launchFromJoint", "0" ); + lockScanning = spawnArgs.GetBool ( "lockScanning", "0" ); + lastLockTime = 0; + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + fireDelay = SEC2MS ( spawnArgs.GetFloat ( "firedelay" ) ); + spread = spawnArgs.GetFloat ( "spread" ); + jointIndex = 0; + count = spawnArgs.GetInt ( "count", "1" ); + lockRange = spawnArgs.GetFloat ( "lockrange", "0" ); + ammoPerCharge = spawnArgs.GetInt ( "ammopercharge", "-1" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargetime" ) ); + currentAmmo = ammoPerCharge; + muzzleFlashHandle = -1; + + if( spawnArgs.GetString("anim", "", temp) && *temp ) { + animNum = parent->GetAnimator()->GetAnim( temp ); + animChannel = spawnArgs.GetInt( "animChannel" ); + } + + spawnArgs.GetVector ( "force", "0 0 0", force ); + + // Vehicle guns can have multiple joints to fire from. They will be cycled through + // when firing. + joints.Append ( joint ); + for ( i = 2; ; i ++ ) { + jointHandle_t joint2; + joint2 = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint%d", i ) ) ); + if ( joint2 == INVALID_JOINT ) { + break; + } + + joints.Append ( joint2 ); + } + + // Muzzle Flash + memset ( &muzzleFlash, 0, sizeof(muzzleFlash) ); + + spawnArgs.GetVector ( "flashOffset", "0 0 0", muzzleFlashOffset ); + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_flashShader", "lights/muzzleflash" ), false ); + muzzleFlash.pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + muzzleFlash.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + spawnArgs.GetVector( "flashColor", "0 0 0", color ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = color[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = color[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = color[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + muzzleFlash.lightRadius[0] = muzzleFlash.lightRadius[1] = muzzleFlash.lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); + + if ( !muzzleFlash.pointLight ) { + muzzleFlash.target = spawnArgs.GetVector( "flashTarget" ); + muzzleFlash.up = spawnArgs.GetVector( "flashUp" ); + muzzleFlash.right = spawnArgs.GetVector( "flashRight" ); + muzzleFlash.end = spawnArgs.GetVector( "flashTarget" ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // get the brass def + idStr name; + brassDict = NULL; + if ( spawnArgs.GetString( "def_ejectBrass", "", name ) && *name ) { + brassDict = gameLocal.FindEntityDefDict( name, false ); + + if ( !brassDict ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon on vehicle '%s'", name.c_str(), position->mParent->name.c_str() ); + } + } + + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); +// wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); +} + +/* +===================== +rvVehicleWeapon::Activate +===================== +*/ +void rvVehicleWeapon::Activate ( bool activate ) { + rvVehiclePart::Activate ( activate ); + + nextFireTime = gameLocal.time + WEAPON_DELAY_FIRE; + +#ifdef _XENON + bestEnemy = 0; +#endif +} + +/* +===================== +rvVehicleWeapon::StopTargetEffect +===================== +*/ +void rvVehicleWeapon::StopTargetEffect ( void ) { + if ( targetEffect ) { + targetEffect->Stop( ); + targetEffect = NULL; + } +} + +/* +===================== +rvVehicleWeapon::UpdateLock +===================== +*/ +void rvVehicleWeapon::UpdateLock ( void ) { + +#ifdef _XENON + + bestEnemy = 0; + + // Handle auto-aim if it's enabled + if ( cvarSystem->GetCVarBool("pm_AimAssist") ) { + + const float testDist = 2000.0f; // huge because we're outdoors + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + + idVec3 start = position->GetEyeOrigin(); + idVec3 end = start + (position->GetEyeAxis().ToAngles().ToForward() * testDist); + + idBounds bounds( start ); + bounds.AddPoint( end ); + + idClipModel *clipModelList[ MAX_GENTITIES ]; + int numClipModels = gameLocal.ClipModelsTouchingBounds( player, bounds, -1, clipModelList, MAX_GENTITIES ); + + float bDist = testDist; + + float fovX, fovY; + gameLocal.CalcFov( player->CalcFov( true ), fovX, fovY ); + float dNear = cvarSystem->GetCVarFloat( "r_znear" ); + float dFar = testDist; + float size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * float(cvarSystem->GetCVarInteger("pm_AimAssistFOV")) / 100.0f; + + idFrustum aimArea; + aimArea.SetOrigin( start ); + aimArea.SetAxis( position->GetEyeAxis() ); + aimArea.SetSize( dNear, dFar, size, size ); + + for ( int i = 0; i < numClipModels; i++ ) { + + idClipModel *clip = clipModelList[ i ]; + idEntity *ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + if ( static_cast( ent )->team == player->team ) { + continue; + } + } else { + continue; + } + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + + const idBounds &b = ent->GetPhysics()->GetAbsBounds(); + bool inside = aimArea.IntersectsBounds(b); + + if ( inside ) { + float dist = b.ShortestDistance(start); + if ( bDist > dist ) { + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } +#endif + + if ( !position->GetDriver() || parent->health <= 0 || !lockScanning) { + StopTargetEffect( ); + } else if ( lockScanning && position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //always update locking info + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + // Update the guide effect + if ( targetEnt && targetEnt.IsValid() && targetEnt->health > 0 && targetEnt->IsType( idActor::GetClassType() ) ) { + idVec3 eyePos; + if ( targetJoint != INVALID_JOINT ) { + idMat3 jointAxis; + targetEnt->GetAnimator()->GetJointTransform( targetJoint, gameLocal.GetTime(), eyePos, jointAxis ); + eyePos = targetEnt->GetRenderEntity()->origin + (eyePos*targetEnt->GetRenderEntity()->axis); + if ( !targetPos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * targetEnt->GetRenderEntity()->axis; + eyePos += jointAxis[0]*targetPos[0]; + eyePos += jointAxis[1]*targetPos[1]; + eyePos += jointAxis[2]*targetPos[2]; + } + } else { + eyePos = static_cast(targetEnt.GetEntity())->GetEyePosition(); + eyePos += targetEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + eyePos *= 0.5f; + } + if ( targetEffect ) { + targetEffect->SetOrigin ( eyePos ); + targetEffect->SetAxis ( player->firstPersonViewAxis.Transpose() ); + } else { + targetEffect = gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_guide" ), eyePos, player->firstPersonViewAxis.Transpose(), true, vec3_origin, false ); + if ( targetEffect ) { + targetEffect->GetRenderEffect()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + targetEffect->GetRenderEffect()->allowSurfaceInViewID = position->GetDriver()->entityNumber + 1; + } + } + } else { + StopTargetEffect( ); + } + } + } +} + +/* +===================== +rvVehicleWeapon::RunPostPhysics +===================== +*/ +void rvVehicleWeapon::RunPostPhysics ( void ) { + if ( !IsActive() || !parent->IsShootingEnabled ( ) ) { + return; + } + + if ( currentAmmo <= 0 && currentCharge.IsDone( gameLocal.GetTime() ) ) { + currentAmmo = ammoPerCharge; + } + + UpdateLock(); + + if ( !(position->mInputCmd.buttons & BUTTON_ATTACK ) ) { + return; + } + + if ( gameLocal.time <= nextFireTime || !currentCharge.IsDone(gameLocal.GetTime()) ) { + return; + } + + // Gun animation? + if ( animNum ) { + parent->GetAnimator()->PlayAnim ( animChannel, animNum, gameLocal.time, 0 ); + } + + if( !spawnArgs.GetBool("launchOnFrameCommand") ) { + if (!Fire()) + { + return; + } + } + + nextFireTime = gameLocal.time + fireDelay; +} + +/* +================ +rvVehicleWeapon::WeaponFeedback +================ +*/ +void rvVehicleWeapon::WeaponFeedback( const idDict* dict ) { + if( !dict || !GetPosition() || !GetPosition()->IsOccupied() ) { + return; + } + + idActor* actor = GetPosition()->GetDriver(); + if( !actor || !actor->IsType( idPlayer::GetClassType() ) ) { + return; + } + + //abahr: This feels like a hack. I hate using def files for logic but it just seems the easiest way to do it + idPlayer* player = static_cast( actor ); + if( dict->GetInt("recoilTime") ) { + player->playerView.WeaponFireFeedback( dict ); + } + if( dict->GetInt("shakeTime") ) { + player->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + dict->GetInt("shakeTime")), dict->GetFloat("shakeMagnitude") ); + } + EjectBrass(); +} + +/* +================ +rvVehicleWeapon::MuzzleFlashLight +================ +*/ +void rvVehicleWeapon::MuzzleFlashLight( const idVec3& origin, const idMat3& axis ) { + if ( !muzzleFlash.lightRadius[0] ) { + return; + } + + muzzleFlash.origin = origin; + muzzleFlash.axis = axis; + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = parent->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +===================== +rvVehicleWeapon::UpdateCursorGUI +===================== +*/ +void rvVehicleWeapon::UpdateCursorGUI ( idUserInterface* gui ) const { + // cnicholson: I do't see a reason why this if statement is here. Its ALWAYS false and so this block never executed, + // thus if the player was in a vehicle, the crosshair was always the player's last held weapon, not the crosshair defined in the vehicle .def. + // So I commented the if part out. + //if ( spawnArgs.GetBool( "hide_crosshair", "0" ) ) { + gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString ( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + gui->SetStateString ( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt ( "crossOffsetX", spawnArgs.GetInt ( "crosshairOffsetX", "0" ) ); + gui->SetStateInt ( "crossOffsetY", spawnArgs.GetInt ( "crosshairOffsetY", "0" ) ); + gui->StateChanged ( gameLocal.time ); + //} +} + +/* +===================== +rvVehicleWeapon::Save +===================== +*/ +void rvVehicleWeapon::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteInt ( nextFireTime ); + savefile->WriteInt ( fireDelay ); + savefile->WriteInt ( count ); + // TOSAVE: const idDict* projectileDef; + // TOSVAE: const idDict* hitScanDef; + + savefile->WriteFloat ( spread ); + savefile->WriteBool ( launchFromJoint ); + savefile->WriteBool ( lockScanning ); + savefile->WriteInt ( lastLockTime ); + savefile->WriteFloat ( lockRange ); + + savefile->WriteInt ( joints.Num() ); + for ( i = 0; i < joints.Num(); i ++ ) { + savefile->WriteJoint ( joints[i] ); + } + savefile->WriteInt ( jointIndex ); + + savefile->WriteVec3 ( force ); + + savefile->WriteInt ( ammoPerCharge ); + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( currentAmmo ); + savefile->WriteInterpolate ( currentCharge ); + + savefile->WriteRenderLight ( muzzleFlash ); + savefile->WriteInt ( muzzleFlashHandle ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashOffset ); + + savefile->WriteInt ( animNum ); + savefile->WriteInt ( animChannel ); + + targetEnt.Save( savefile ); + savefile->WriteJoint( targetJoint ); + savefile->WriteVec3( targetPos ); + targetEffect.Save( savefile ); + // Don't save shaderFire or shaderReload, they are setup in Restore + + savefile->WriteInt( zoomFov ); + savefile->WriteUserInterface( zoomGui, true ); + savefile->WriteFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +void rvVehicleWeapon::Restore ( idRestoreGame* savefile ) { + int i; + int num; + idStr temp; + +#ifdef _XENON + bestEnemy = 0; +#endif + + savefile->ReadInt ( nextFireTime ); + savefile->ReadInt ( fireDelay ); + savefile->ReadInt ( count ); + savefile->ReadFloat ( spread ); + savefile->ReadBool ( launchFromJoint ); + savefile->ReadBool ( lockScanning ); + savefile->ReadInt ( lastLockTime ); + savefile->ReadFloat ( lockRange ); + + savefile->ReadInt ( num ); + joints.Clear ( ); + joints.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadJoint ( joints[i] ); + } + savefile->ReadInt ( jointIndex ); + + savefile->ReadVec3 ( force ); + + savefile->ReadInt ( ammoPerCharge ); + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( currentAmmo ); + savefile->ReadInterpolate( currentCharge ); + + savefile->ReadRenderLight ( muzzleFlash ); + savefile->ReadInt ( muzzleFlashHandle ); + if ( muzzleFlashHandle != -1 ) { + //get the handle again as it's out of date after a restore! + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashOffset ); + + savefile->ReadInt ( animNum ); + savefile->ReadInt ( animChannel ); + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // Brass Def + brassDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_ejectBrass" ), false ); + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt.Restore( savefile ); + savefile->ReadJoint( targetJoint ); + savefile->ReadVec3( targetPos ); + targetEffect.Restore( savefile ); + + savefile->ReadInt( zoomFov ); + savefile->ReadUserInterface( zoomGui, &spawnArgs ); + savefile->ReadFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +bool rvVehicleWeapon::Fire() { + if ( muzzleFlashHandle != -1 && gameLocal.time >= muzzleFlashEnd ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + +// twhitaker: moved to rvVehicleWeapon::RunPostPhysics so that the HUD would update without having to fire the gun +// if ( currentAmmo == 0 ) { +// currentAmmo = ammoPerCharge; +// } + + LaunchProjectiles(); + + if ( currentAmmo == -1 ) { + currentCharge.Init ( gameLocal.time, fireDelay, 0.0, 1.0f ); + } else { + currentAmmo--; + if ( currentAmmo <= 0 ) { + currentAmmo = 0; + if ( chargeTime ) { + parent->StartSoundShader( shaderReload, SND_CHANNEL_ANY, 0, false, NULL ); + currentCharge.Init ( gameLocal.time, chargeTime, 0.0f, 1.0f ); + } + } + } + return true; +} + +#ifdef _XENON +/* +===================== +rvVehicleWeapon::AutoAim +===================== +*/ +void rvVehicleWeapon::AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ) { + + if ( player ) { + + // If we have a enemy selected, handle aim correction + if ( bestEnemy ) { + + idVec3 dif = bestEnemy->GetRenderEntity()->origin - origin; + idVec3 muzzleDest = origin + (dir * dif.Length() ); + + // Transform start and end into the enemy's body space + idVec3 localStart = bestEnemy->GetRenderEntity()->axis / (origin - bestEnemy->GetRenderEntity()->origin); + idVec3 localEnd = bestEnemy->GetRenderEntity()->axis / (muzzleDest - bestEnemy->GetRenderEntity()->origin); + + if ( bestEnemy->GetAnimator() ) { + int numJoints = bestEnemy->GetAnimator()->NumJoints(); + + if ( numJoints ) { + + jointHandle_t nearJoint = bestEnemy->GetAnimator()->GetNearestJoint( localStart, localEnd, gameLocal.time, 0, numJoints ); + + // If we found a valid joint to snap to... + if ( nearJoint > 0 ) { + idMat3 dummy; + bestEnemy->GetJointWorldTransform( nearJoint, gameLocal.time, muzzleDest, dummy ); + dir = muzzleDest - origin; + dir.Normalize(); + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + } + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + + } + } +} +#endif + +/* +===================== +rvVehicleWeapon::LaunchHitScan +===================== +*/ +void rvVehicleWeapon::LaunchHitScan( const idVec3& origin, const idVec3& _dir, const idVec3& jointOrigin ) { + idPlayer* player = 0; + + idVec3 dir = _dir; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + gameLocal.HitScan ( *hitScanDef, origin, dir, jointOrigin, position->GetDriver(), false, 1.0f, parent ); +} + +/* +===================== +rvVehicleWeapon::LaunchProjectile +===================== +*/ +void rvVehicleWeapon::LaunchProjectile( const idVec3& origin, const idVec3& _dir, const idVec3& pushVelocity ) { + + idPlayer *player = 0; + idEntity* ent; + idProjectile* projectile; + + idVec3 dir = _dir; + + gameLocal.SpawnEntityDef ( *projectileDef, &ent ); + if ( !ent ) { + return; + } + + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + + projectile = ( idProjectile * )ent; + projectile->Create( position->GetDriver(), origin, dir, parent ); + projectile->Launch( origin, dir, pushVelocity, 0.0f, 1.0f ); + + if ( projectile->IsType ( idGuidedProjectile::GetClassType() ) ) { + if ( spawnArgs.GetBool("guideTowardsDir") && (!targetEnt || targetJoint == INVALID_JOINT) ) { +#ifndef _XENON + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); +#else + if ( bestEnemy ) { + static_cast(projectile)->GuideTo ( origin, dir ); + } else { + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); + } +#endif + + } else if ( targetEnt ) { + static_cast(projectile)->GuideTo ( targetEnt, targetJoint, targetPos ); + } else { + static_cast(projectile)->GuideTo ( targetPos ); + } + } +} + +/* +===================== +rvVehicleWeapon::LaunchProjectiles +===================== +*/ +void rvVehicleWeapon::LaunchProjectiles() { + idVec3 jointOrigin; + idMat3 jointAxis; + idVec3 origin; + idMat3 axis; + + float spreadRad = DEG2RAD( spread ); + idVec3 dir; + float ang = 0.0f; + float spin = 0.0f; + + parent->GetJointWorldTransform ( joints[jointIndex], gameLocal.time, jointOrigin, jointAxis ); + MuzzleFlashLight ( jointOrigin + muzzleFlashOffset * jointAxis, jointAxis ); + + if( launchFromJoint ) { + origin = jointOrigin; + axis = jointAxis; + } else { + origin = position->GetEyeOrigin(); + axis = position->GetEyeAxis(); + } + + if ( !lockScanning || !targetEnt || !position->GetDriver() || !position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //don't do this continuously, so have to do it here + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + } + + parent->StartSoundShader( shaderFire, SND_CHANNEL_WEAPON, 0, false, NULL ); + + parent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_muzzleflash" ), joints[jointIndex], vec3_origin, mat3_identity ); + + gameLocal.AlertAI( parent ); + + for( int i = 0; i < count; i ++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[0] + axis[ 2 ] * ( ang * idMath::Sin( spin ) ) - axis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine ( colorBlue, origin, origin + dir * 10000.0f, 10000 ); + } + + if ( hitScanDef ) { + LaunchHitScan( origin, dir, jointOrigin ); + } else { + LaunchProjectile( origin, dir, parent->GetPhysics()->GetPushedLinearVelocity() ); + } + } + + jointIndex = (jointIndex+1) % joints.Num(); + + parent->GetPhysics()->ApplyImpulse ( 0, origin, force * axis ); + + WeaponFeedback( &spawnArgs ); +} + +/* +===================== +rvVehicleWeapon::GetLockInfo +===================== +*/ +void rvVehicleWeapon::GetLockInfo( const idVec3& eyeOrigin, const idMat3& eyeAxis ) { + if ( lastLockTime < gameLocal.GetTime() - 2000 + || (targetEnt && (!targetEnt.IsValid() || targetEnt.GetEntity()->health <= 0)) ) { + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + } + + if ( lockRange > 0.0f ) { + idVec3 end; + trace_t tr; + bool lockFound = false; + idEntity* newTargetEnt = NULL; + jointHandle_t newTargetJoint = INVALID_JOINT; + + end = eyeOrigin + eyeAxis[0] * lockRange; +// gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_BOUNDINGBOX, NULL ); + gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_RENDERMODEL, NULL ); + + if ( tr.fraction < 1.0 ) { + newTargetEnt = gameLocal.entities[ tr.c.entityNum ]; + lockFound = true; + + // Make sure the target is an actor and is alive + if ( !(newTargetEnt->IsType ( idActor::GetClassType() ) || newTargetEnt->IsType ( idProjectile::GetClassType() )) || newTargetEnt->health <= 0 ) { + newTargetEnt = NULL; + lockFound = false; + } else { + //see if there's a joint to lock onto + if ( newTargetEnt->spawnArgs.MatchPrefix ( "lockJoint" ) ) { + jointHandle_t testJointHandle; + idVec3 testJointOffset; + idStr lockJointName; + idVec3 jointOrg; + idMat3 jointAxis; + + int lockJointNum = 1; + float bestDist = 100.0f; + float dist = 0.0f; + + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",lockJointNum), NULL ); + while ( lockJointName.Length() ) { + testJointHandle = newTargetEnt->GetAnimator()->GetJointHandle( lockJointName ); + if ( testJointHandle != INVALID_JOINT ) { + newTargetEnt->GetAnimator()->GetJointTransform( testJointHandle, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = newTargetEnt->GetRenderEntity()->origin + (jointOrg*newTargetEnt->GetRenderEntity()->axis); + jointAxis = jointAxis * newTargetEnt->GetRenderEntity()->axis; + testJointOffset = newTargetEnt->spawnArgs.GetVector( va("lockJointOffset%d",lockJointNum), "0 0 0" ); + jointOrg += jointAxis*testJointOffset; + dist = (jointOrg - tr.endpos).Length(); + if ( dist < bestDist ) { + bestDist = dist; + newTargetJoint = testJointHandle; + targetPos = testJointOffset; + } + } + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",++lockJointNum), NULL ); + } + } + } + if ( spawnArgs.GetBool("guideTowardsDir") ) { + if ( newTargetJoint == INVALID_JOINT ) { + newTargetEnt = NULL; + lockFound = false; + } + } + } else if ( spawnArgs.GetBool( "guideTowardsDir" ) && !targetEnt ) { + targetPos = tr.endpos; + } + if ( lockFound ) { + targetEnt = newTargetEnt; + targetJoint = newTargetJoint; + lastLockTime = gameLocal.GetTime(); + } + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, end, 3, 10000 ); + gameRenderWorld->DebugArrow ( colorWhite, eyeOrigin, tr.endpos, 4, 10000 ); + } + } +} + +/* +===================== +rvVehicleWeapon::EjectBrass +===================== +*/ +void rvVehicleWeapon::EjectBrass ( void ) { + if ( !brassDict || gameLocal.time > brassEjectNext || g_brassTime.GetFloat() <= 0.0f || gameLocal.isMultiplayer || brassEjectJoint == INVALID_JOINT || !brassDict->GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !parent->GetJointWorldTransform( brassEjectJoint, gameLocal.time, origin, axis ) ) { + return; + } + + brassEjectNext += brassEjectDelay; + + // Spawn the client side moveable for the brass + idVec3 offset; + idMat3 playerViewAxis; + gameLocal.GetPlayerView( offset, playerViewAxis ); + rvClientMoveable* cent = NULL; + gameLocal.SpawnClientEntityDef( *brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOrigin ( origin + axis * brassEjectOffset ); + cent->SetAxis ( playerViewAxis ); + cent->SetOwner ( position->GetDriver() ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 6.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict->GetVector("linear_velocity").Random( brassDict->GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( position->GetDriver()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict->GetAngles("angular_velocity").Random( brassDict->GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/*********************************************************************** + + rvVehicleTurret + +***********************************************************************/ + +#define TURRET_MOVESOUND_FADE 200 + +CLASS_DECLARATION( rvVehiclePart, rvVehicleTurret ) +END_CLASS + +/* +===================== +rvVehicleTurret::rvVehicleTurret +===================== +*/ +rvVehicleTurret::rvVehicleTurret ( void ) { + moveTime = 0; + soundPart = -1; +} + +/* +===================== +rvVehicleTurret::Spawn +===================== +*/ +void rvVehicleTurret::Spawn ( void ) { + angles[0][PITCH] = spawnArgs.GetFloat ( "minpitch", "0" ); + angles[1][PITCH] = spawnArgs.GetFloat ( "maxpitch", "0" ); + axisMap[PITCH] = spawnArgs.GetInt ( "pitch", "-1" ); + invert[PITCH] = spawnArgs.GetBool ( "pitchinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][ROLL] = spawnArgs.GetFloat ( "minroll", "0" ); + angles[1][ROLL] = spawnArgs.GetFloat ( "maxroll", "0" ); + axisMap[ROLL] = spawnArgs.GetInt ( "roll", "-1" ); + invert[ROLL] = spawnArgs.GetBool ( "rollinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][YAW] = spawnArgs.GetFloat ( "minyaw", "0" ); + angles[1][YAW] = spawnArgs.GetFloat ( "maxyaw", "0" ); + axisMap[YAW] = spawnArgs.GetInt ( "yaw", "-1" ); + invert[YAW] = spawnArgs.GetBool ( "yawinvert", "0" ) ? -1.0f : 1.0f; + + turnRate = spawnArgs.GetFloat ( "turnrate", "360" ); + + currentAngles.Zero ( ); + + //the parent is *not* stuck on spawn. + parentStuck = false; + + + // Find the vehicle part for the turret sound + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } +} + +/* +===================== +rvVehicleTurret::Activate +===================== +*/ +void rvVehicleTurret::Activate ( bool active ) { + rvVehiclePart::Activate( active ); + + if ( !IsActive() && soundPart >= 0 ) { + static_cast(position->GetPart(soundPart))->Stop ( ); + } + +} + +/* +===================== +rvVehicleTurret::RunPostPhysics +===================== +*/ +void rvVehicleTurret::RunPostPhysics ( void ) { + int i; + idAngles inputAngles; + idAngles lastInputAngles; + idMat3 mat[3]; + idAngles oldAngles; + + if ( IsActive ( ) ) { + for ( i = 0; i < 3; i++ ) { + inputAngles[i] = SHORT2ANGLE( position->mInputCmd.angles[i] ); + lastInputAngles[i] = SHORT2ANGLE( position->mOldInputCmd.angles[i] ); + } + } else { + inputAngles.Zero ( ); + lastInputAngles.Zero ( ); + } + + oldAngles = currentAngles; + + mat[PITCH].Identity(); + mat[YAW].Identity(); + mat[ROLL].Identity(); + for ( i = 0; i < 3; i ++ ) { + if ( axisMap[i] != -1 ) { + float diff = (invert[i] * idMath::AngleDelta ( inputAngles[i], lastInputAngles[i] )); + + diff = SignZero( diff ) * idMath::ClampFloat ( 0.0f, turnRate * MS2SEC(gameLocal.GetMSec()), idMath::Fabs ( diff ) ); + if ( angles[0][i] == angles[1][i] ) { + currentAngles[axisMap[i]] = idMath::AngleNormalize360( currentAngles[axisMap[i]] + diff ); + } else { + currentAngles[axisMap[i]] = idMath::ClampFloat ( angles[0][i], angles[1][i], currentAngles[axisMap[i]] + diff ); + } + + idAngles angles; + angles.Zero(); + angles[axisMap[i]] = currentAngles[axisMap[i]]; + mat[axisMap[i]] = angles.ToMat3(); + } + } + + // Update the turret moving sound + if ( soundPart >= 0 ) { + float speed; + speed = idMath::Fabs( idMath::AngleDelta( oldAngles[YAW], currentAngles[YAW] ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[PITCH], currentAngles[PITCH] ) ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[ROLL], currentAngles[ROLL] ) ) ); + + if ( speed ) { + if ( !moveTime ) { + static_cast(position->GetPart(soundPart))->Play ( ); + } + moveTime = gameLocal.time + TURRET_MOVESOUND_FADE; + } else if ( moveTime && gameLocal.time > moveTime ) { + static_cast(position->GetPart(soundPart))->Fade ( TURRET_MOVESOUND_FADE, 0.0f, 1.0f ); + moveTime = 0; + } + + // Update the volume of the turret move sound using the current speed of the + // turret as well as how long it has been since it has last moved. + if ( moveTime ) { + float f; + f = idMath::ClampFloat ( 0.0f, 1.0f, fabs(speed) / (turnRate * MS2SEC(gameLocal.GetMSec())) ); + static_cast(position->GetPart(soundPart))->Attenuate ( f, f ); + } + } + + // Rotate the turret with the mouse + parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_LOCAL, mat[YAW] * mat[PITCH] * mat[ROLL] ); + + if ( g_vehicleMode.GetInteger() != 0 && joint != INVALID_JOINT && parent->GetAnimator()->GetJointHandle( spawnArgs.GetString( "alignment_joint" ) ) == joint ) { + idMat3 turretAxis; + idVec3 temp; + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + const idMat3 & vehicleAxis = parent->GetPhysics()->GetAxis(); + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + float dotRight = vehicleAxis[1] * turretAxis[alignmentAxis]; + float dotForward = vehicleAxis[0] * turretAxis[alignmentAxis]; + float acosForward = idMath::ACos( dotForward ); + + idAngles oldAngles = currentAngles; + idMat3 oldAxis = vehicleAxis; + + if ( idMath::Fabs(1.0f - dotForward) > VECTOR_EPSILON ) { + float sinus = idMath::Cos( dotForward ); + float power = idMath::Pow( sinus, spawnArgs.GetFloat( "alignment_power", "5" ) ); + float deg = idMath::ClampFloat( 0.0f, spawnArgs.GetFloat( "alignment_delta", "2.4" ), RAD2DEG( acosForward ) ) * power; + idAngles delta( 0, deg * SignZero(dotRight), 0 ); + + //move the tank + parent->GetPhysics()->SetAxis( (delta).ToMat3() * vehicleAxis ); + currentAngles -= delta; + trace_t tr; + + //trace the tank against the world. + idBounds traceBounds; + idVec3 raiseVector( 0, 0, 80); + idVec3 reduceVector( 0, 0, -16); + idVec3 parentOrigin = parent->GetPhysics()->GetOrigin(); + idVec3 parentVecForward = parent->GetPhysics()->GetAxis()[0]; + + traceBounds.Zero(); + traceBounds = parent->GetPhysics()->GetAbsBounds(); + traceBounds.ExpandSelf( reduceVector ); + + //zero the tracebounds-- abs bounds is the right size but not the right location :) + traceBounds.TranslateSelf( traceBounds.GetCenter() * -1 ); + + //raise the bounds up so the hover tank can hover. + traceBounds.TranslateSelf( raiseVector ); + + gameLocal.TraceBounds( parent, tr, parentOrigin, parentOrigin, traceBounds, MASK_SOLID | CONTENTS_VEHICLECLIP ,parent); + + //draw the debug turning bounds if we need to. It will be redder if there's collision. + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec4 vecColor(1.0f - ( tr.fraction) , 0.25f, (0.5f * tr.fraction), 1.0f); + gameRenderWorld->DebugBounds( vecColor, traceBounds, parentOrigin, 10, true ); + } + + //roll it back if it's colliding. + if( tr.fraction < 1.0f) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + gameLocal.Warning("Turret move caused vehicle block %d distance %f", tr.c.entityNum, tr.fraction ); + } + parent->GetPhysics()->SetAxis( oldAxis ); + currentAngles = oldAngles; + + //apply a push to the parent based on the direction of the collision? + parentStuck = true; + } + } + + } +} +/* +===================== +rvVehicleTurret::RunPrePhysics +===================== +*/ +void rvVehicleTurret::RunPrePhysics ( void ) { + + //in case we're stuck, apply an impulse to the parent based on the direction the turret is facing. This will help us pull out in the right direction. + if( parentStuck ) { + + idVec3 impulseForce( 0,0,0); + idMat3 turretAxis; + idVec3 temp; + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + impulseForce += ( turretAxis[alignmentAxis] * 50 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass()); + //impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * 25 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + + // Apply the impulse to the parent entity + + parent->GetPhysics()->ApplyImpulse( 0, parent->GetPhysics()->GetOrigin(), impulseForce); + + parentStuck = false; + } + + +} + + +/* +===================== +rvVehicleTurret::Save +===================== +*/ +void rvVehicleTurret::Save ( idSaveGame* savefile ) const { + savefile->WriteBounds ( angles ); + savefile->WriteInt ( axisMap[0] ); + savefile->WriteInt ( axisMap[1] ); + savefile->WriteInt ( axisMap[2] ); + + savefile->WriteFloat ( invert[0] ); + savefile->WriteFloat ( invert[1] ); + savefile->WriteFloat ( invert[2] ); + + savefile->WriteAngles ( currentAngles ); + + savefile->WriteInt ( moveTime ); + savefile->WriteFloat ( turnRate ); + + savefile->WriteInt ( soundPart ); +} + +/* +===================== +rvVehicleTurret::Restore +===================== +*/ +void rvVehicleTurret::Restore ( idRestoreGame* savefile ) { + savefile->ReadBounds ( angles ); + savefile->ReadInt ( axisMap[0] ); + savefile->ReadInt ( axisMap[1] ); + savefile->ReadInt ( axisMap[2] ); + + savefile->ReadFloat ( invert[0] ); + savefile->ReadFloat ( invert[1] ); + savefile->ReadFloat ( invert[2] ); + + savefile->ReadAngles ( currentAngles ); + + savefile->ReadInt ( moveTime ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadInt ( soundPart ); +} + +/*********************************************************************** + + rvVehicleHoverpad + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleHoverpad ) +END_CLASS + +rvVehicleHoverpad::rvVehicleHoverpad ( void ) { + clipModel = NULL; + effectDust = NULL; + soundPart = -1; + effectDustMaterialType = NULL; +} + +rvVehicleHoverpad::~rvVehicleHoverpad ( void ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + delete clipModel; +} + +/* +================ +rvVehicleHoverpad::Spawn +================ +*/ +void rvVehicleHoverpad::Spawn ( void ) { + float size; + + spawnArgs.GetFloat ( "size", "10", size ); + spawnArgs.GetFloat ( "height", "70", height ); + spawnArgs.GetFloat ( "dampen", "10", dampen ); + spawnArgs.GetFloat ( "forceRandom", "10", forceRandom ); + spawnArgs.GetFloat ( "thrustForward", "0", thrustForward ); + spawnArgs.GetFloat ( "thrustLeft", "0", thrustLeft ); + + maxRestAngle = idMath::Cos ( DEG2RAD(spawnArgs.GetFloat ( "maxRestAngle", "20" ) ) ); + + fadeTime = SEC2MS(spawnArgs.GetFloat ( "fadetime", ".5" )); + + effectDust = NULL; + atRest = true; + + delete clipModel; + clipModel = new idClipModel ( idTraceModel ( idBounds ( spawnArgs.GetVector("mins"),spawnArgs.GetVector("maxs")) ) ); + + force = spawnArgs.GetFloat ( "force", "1066" ); + forceUpTime = SEC2MS ( spawnArgs.GetFloat ( "forceUpTime", "1" ) ); + forceDownTime = SEC2MS ( spawnArgs.GetFloat ( "forceDownTime", "1" ) ); + forceTable = declManager->FindTable( spawnArgs.GetString ( "forceTable", "linear" ), false ); + + currentForce.Init ( 0, 0, 0, 0 ); + + // Is a sound part specified? + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } + + Activate ( false ); +} + +/* +================ +rvVehicleHoverpad::Activate +================ +*/ +void rvVehicleHoverpad::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active ) { + currentForce.Init ( gameLocal.time, forceUpTime, currentForce.GetCurrentValue( gameLocal.time ), force ); + atRest = false; + } else { + currentForce.Init ( gameLocal.time, forceDownTime, currentForce.GetCurrentValue( gameLocal.time ), 0 ); + } +} + +/* +================ +rvVehicleHoverpad::RunPrePhysics + +Called before RunPhysics. Since impact velocities are altered by calls to ApplyImpulse +we need to cache them to ensure other hoverpads are not altering the data. The start +position of the hoverpad is also cached here for efficiency. +================ +*/ +void rvVehicleHoverpad::RunPrePhysics ( void ) { + impactInfo_t info; + + UpdateOrigin ( ); + + // Cache velocity at start point + parent->GetPhysics()->GetImpactInfo( 0, worldOrigin, &info ); + velocity = info.velocity; +} + +/* +================ +rvVehicleHoverpad::RunPhysics +================ +*/ +void rvVehicleHoverpad::RunPhysics ( void ) { + idVec3 end; + idMat3 axis; + trace_t tr; + idVec3 impulseForce; + idVec3 dampingForce; + float hoverForce; + float curlength; + + // Disable pad? + if ( !IsActive() && !atRest ) { + if ( parent->IsAtRest ( ) || currentForce.GetCurrentValue ( gameLocal.time ) <= 0.0f || parent->IsFlipped( ) || parent->IsStalled() ) { + if ( soundPart >= 0 ) { + float fadeVolume; + fadeVolume = idMath::dBToScale ( spawnArgs.GetFloat ( "fadevolume", "0" ) ); + static_cast(position->GetPart(soundPart))->Fade ( fadeTime, fadeVolume, spawnArgs.GetFloat ( "fadefreqshift", "0.1" ) ); + } + + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + atRest = true; + } + } + + // If the vehicle isnt activate then kill the effect on it and dont think + if ( atRest ) { + return; + } + + // Prepare for trace + axis[0] = -parent->GetPhysics()->GetAxis()[2]; + axis[1] = parent->GetPhysics()->GetAxis()[0]; + axis[2] = parent->GetPhysics()->GetAxis()[1]; + + // Always point the hover jets towards gravity + end = worldOrigin + (parent->GetPhysics()->GetGravityNormal ( ) * height); + + // Determine how far away the hoverpad is from the ground + gameLocal.Translation ( parent.GetEntity(), tr, worldOrigin, end, clipModel, axis, MASK_SOLID|CONTENTS_VEHICLECLIP, parent ); + if ( tr.fraction >= 1.0f && tr.endpos == end ) { + UpdateDustEffect ( worldOrigin, axis, 0.0f, NULL ); + return; + } + + // Determine spring properties from trace + tr.c.point = worldOrigin + (end-worldOrigin) * (tr.fraction + 0.001f); + curlength = height - (worldOrigin - tr.c.point).Length ( ); + + // Dampening + dampingForce = tr.c.point - worldOrigin; + dampingForce = ( dampen * ( (velocity * dampingForce) / (dampingForce * dampingForce) ) ) * dampingForce; + + // Random swap forces + float rnd = 0.0f; + if ( !position->mInputCmd.forwardmove && !position->mInputCmd.rightmove ) { + float angle = DEG2RAD( ( (float)( gameLocal.time % 5000 ) / 5000.0f ) * 360.f ); + if ( fl.left ) { + angle += 90; + } + if ( fl.front ) { + angle += 180; + } + rnd = forceRandom * idMath::Sin( angle ); + } + + // Determine how much force should be applied at the center of the hover spring + hoverForce = (currentForce.GetCurrentValue(gameLocal.time) + rnd) * parent->GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()); + + // If the slope under the hoverpad is < 30 degress then use the gravity vector to allow the hovertank to + // stop on slopes, otherwise use the vehicles axis which will cause it to slide down steep slopes + float f = (-parent->GetPhysics()->GetGravityNormal ( ) * tr.c.normal); + if ( f < maxRestAngle || (thrustForward && position->mInputCmd.forwardmove != 0 ) ) { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -axis[0] - dampingForce; + } else { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -parent->GetPhysics()->GetGravityNormal ( ) - dampingForce; + } + + // No thrust if movement is disabled + if ( parent->IsMovementEnabled ( ) ) { + idMat3 axis; + idVec3 offset; + jointHandle_t axisJoint = joint; + if ( axisJoint == INVALID_JOINT ) { + axisJoint = parent->GetAnimator()->GetJointHandle( "gun_pivot" ); + } + parent->GetJointWorldTransform( axisJoint, gameLocal.time, offset, axis ); + + // Add forward/backward thrust + if ( thrustForward ) { + if ( g_vehicleMode.GetInteger() == 2 ) { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * axis[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } + } + + // Add right/left thrust. The front pads will add force to the right if the right button is pressed and the rear will do the opposite. If moving backward the directions are flipped again + if ( thrustLeft ) { + if ( !parent->IsStrafing() && g_vehicleMode.GetInteger() != 0 ) { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * -axis[2] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * (position->mInputCmd.forwardmove<0?-1:1) * parent->GetPhysics()->GetAxis()[1] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass () * (fl.front ? 1 : -1); + } + } + } + + // Apply the impulse to the parent entity + parent->GetPhysics()->ApplyImpulse( 0, worldOrigin, impulseForce); + + // Update the position and intensity of the dust effect + UpdateDustEffect ( tr.c.point, tr.c.normal.ToMat3( ), (curlength / height), tr.c.materialType ); + + // Debug information + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec3 offset; + offset = worldOrigin - parent->GetPhysics()->GetOrigin(); + offset *= parent->GetPhysics()->GetAxis().Transpose(); + gameDebug.AppendList ( "hover", va("%c%c %d\t%0.1f\t%0.1f\t%d\t", (offset[0]>0?'F':'B'),(offset[1]<0?'R':'L'), (int)impulseForce.Length(), curlength, dampingForce.Length(), (int)(velocity*axis[0] ) ) ); + } + + // Draw debug information + if ( g_debugVehicle.GetInteger() == 2 ) { + collisionModelManager->DrawModel ( clipModel->GetCollisionModel(), tr.c.point, axis, vec3_origin, mat3_identity, 0.0f ); + gameRenderWorld->DebugCircle ( colorGreen, tr.c.point, axis[0], 5, 10 ); + gameRenderWorld->DebugLine ( colorMagenta, worldOrigin, worldOrigin + idVec3(0,0,-height)); + gameRenderWorld->DebugLine ( colorWhite, worldOrigin, end ); + gameRenderWorld->DebugCircle ( colorBlue, worldOrigin, axis[0], 5, 10 ); + gameRenderWorld->DebugCircle ( colorBlue, end, axis[0], 5, 10 ); + + if ( thrustForward && position->mInputCmd.forwardmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[1] * 50.0f * (fl.front ? 1 : -1) * ( position->mInputCmd.rightmove / 127.0f ), 10); + } + + if ( thrustLeft && position->mInputCmd.rightmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[0] * 50.0f * ( position->mInputCmd.forwardmove / 127.0f ), 10); + } + } +} + +/* +================ +rvVehicleHoverpad::UpdateDustEffect +================ +*/ +void rvVehicleHoverpad::UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ) { + if ( !effectDust || (mtype && effectDustMaterialType != mtype) ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust", mtype ), origin, axis, true ); + effectDustMaterialType = mtype; + } + + if ( effectDust ) { + effectDust->Attenuate ( attenuation ); + effectDust->SetOrigin ( origin ); + effectDust->SetAxis ( axis ); + } + + // If there is a sound playing update it as well + if ( soundPart >= 0 ) { + if ( static_cast(position->GetPart(soundPart))->IsPlaying ( ) ) { + static_cast(position->GetPart(soundPart))->Attenuate ( attenuation, attenuation ); + } else { + static_cast(position->GetPart(soundPart))->Play ( ); + } + } +} + +/* +===================== +rvVehicleHoverpad::Save +===================== +*/ +void rvVehicleHoverpad::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( height ); + savefile->WriteFloat ( dampen ); + savefile->WriteBool ( atRest ); + + savefile->WriteBounds ( clipModel->GetBounds ( ) ); + + savefile->WriteInterpolate ( currentForce ); + savefile->WriteFloat ( force ); + savefile->WriteTable ( forceTable ); + savefile->WriteFloat ( forceRandom ); + + savefile->WriteFloat ( fadeTime ); + savefile->WriteVec3 ( velocity ); + + savefile->WriteFloat ( thrustForward ); + savefile->WriteFloat ( thrustLeft ); + + savefile->WriteFloat ( maxRestAngle ); + + savefile->WriteInt ( soundPart ); + + savefile->WriteInt ( forceUpTime ); + savefile->WriteInt ( forceDownTime ); + + effectDust.Save ( savefile ); + savefile->WriteMaterialType ( effectDustMaterialType ); +} + +/* +===================== +rvVehicleHoverpad::Restore +===================== +*/ +void rvVehicleHoverpad::Restore ( idRestoreGame* savefile ) { + idBounds bounds; + + savefile->ReadFloat ( height ); + savefile->ReadFloat ( dampen ); + savefile->ReadBool ( atRest ); + + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadInterpolate ( currentForce ); + savefile->ReadFloat ( force ); + savefile->ReadTable ( forceTable ); + savefile->ReadFloat ( forceRandom ); + + savefile->ReadFloat ( fadeTime ); + savefile->ReadVec3 ( velocity ); + + savefile->ReadFloat ( thrustForward ); + savefile->ReadFloat ( thrustLeft ); + + savefile->ReadFloat ( maxRestAngle ); + + savefile->ReadInt ( soundPart ); + + savefile->ReadInt ( forceUpTime ); + savefile->ReadInt ( forceDownTime ); + + effectDust.Restore ( savefile ); + savefile->ReadMaterialType ( effectDustMaterialType ); +} + +/*********************************************************************** + + rvVehicleThruster + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleThruster ) +END_CLASS + +rvVehicleThruster::rvVehicleThruster ( void ) { +} + +rvVehicleThruster::~rvVehicleThruster ( void ) { +} + +/* +===================== +rvVehicleThruster::Save +===================== +*/ +void rvVehicleThruster::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( force ); + savefile->WriteInt ( forceAxis ); + savefile->WriteInt ( key ); +} + +/* +===================== +rvVehicleThruster::Restore +===================== +*/ +void rvVehicleThruster::Restore ( idRestoreGame* savefile ) { + savefile->ReadFloat ( force ); + savefile->ReadInt ( forceAxis ); + savefile->ReadInt ( (int&)key ); +} + +/* +================ +rvVehicleThruster::Spawn +================ +*/ +void rvVehicleThruster::Spawn ( void ) { + idStr keyName; + + force = spawnArgs.GetFloat ( "force", "0" ); + forceAxis = spawnArgs.GetInt ( "forceAxis", "0" ); + + // Determine which key controls the thruster + spawnArgs.GetString ( "key", "", keyName ); + if ( !keyName.Icmp ( "forward" ) ) { + key = KEY_FORWARD; + } else if ( !keyName.Icmp ( "right" ) ) { + key = KEY_RIGHT; + } else if ( !keyName.Icmp ( "up" ) ) { + key = KEY_UP; + } +} + +/* +================ +rvVehicleThruster::RunPhysics +================ +*/ +void rvVehicleThruster::RunPhysics ( void ) { + float mult; + + if ( !IsActive ( ) ) { + return; + } + + // Determine the force multiplier from the key being pressed + mult = 0.0f; + switch ( key ) { + case KEY_FORWARD: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.forwardmove ); + break; + + case KEY_RIGHT: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.rightmove ); + break; + + case KEY_UP: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.upmove ); + break; + } + + // No multiplier, no move + if ( mult == 0.0f ) { + return; + } + + UpdateOrigin ( ); + + // Apply the force + parent->GetPhysics()->ApplyImpulse ( 0, worldOrigin, worldAxis[forceAxis] * force * mult ); + + // Debug Information + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugBounds ( colorCyan, idBounds(idVec3(-4,-4,-4), idVec3(4,4,4) ), worldOrigin ); + gameRenderWorld->DebugArrow ( colorCyan, worldOrigin, worldOrigin + worldAxis[forceAxis] * 100.0f * mult * (force < 0 ? -1 : 1), 10 ); + } +} + +/*********************************************************************** + + rvVehicleUserAnimated + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleUserAnimated ) + EVENT( EV_PostSpawn, rvVehicleUserAnimated::Event_PostSpawn ) +END_CLASS + +rvVehicleUserAnimated::rvVehicleUserAnimated ( void ) { +} + +/* +===================== +rvVehicleUserAnimated::Save +===================== +*/ +void rvVehicleUserAnimated::Save ( idSaveGame* savefile ) const { +} + +/* +===================== +rvVehicleUserAnimated::Restore +===================== +*/ +void rvVehicleUserAnimated::Restore ( idRestoreGame* savefile ) { + Event_PostSpawn(); +} + +/* +================ +rvVehicleUserAnimated::Spawn +================ +*/ +void rvVehicleUserAnimated::Spawn ( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleUserAnimated::Event_PostSpawn +================ +*/ +void rvVehicleUserAnimated::Event_PostSpawn ( void ) { + InitAnim( "forward", anims[ VUAA_Forward ] ); + InitAnim( "strafe", anims[ VUAA_Strafe ] ); + InitAnim( "crouch", anims[ VUAA_Crouch ] ); + InitAnim( "attack", anims[ VUAA_Attack ] ); + + InitFunc( "forward", funcs[ VUAF_Forward ] ); + InitFunc( "strafe", funcs[ VUAF_Strafe ] ); + InitFunc( "crouch", funcs[ VUAF_Crouch ] ); + InitFunc( "attack", funcs[ VUAF_Attack ] ); +} + + +/* +================ +rvVehicleUserAnimated::RunPrePhysics +================ +*/ +void rvVehicleUserAnimated::RunPrePhysics ( void ) { + if ( !IsActive ( ) ) { + return; + } + + rvVehicle *vehicle = position->GetParent(); + const rvVehiclePosition *position = vehicle->GetPosition( 0 ); + const usercmd_t & input = position->mInputCmd; + + if ( position->IsOccupied() ) { + LateralMove( input.forwardmove, VUAA_Forward, VUAF_Forward ); + LateralMove( input.rightmove, VUAA_Strafe, VUAF_Strafe ); + LateralMove( input.upmove, VUAA_Crouch, VUAF_Crouch ); + + // cheat and use LateralMove for the attack + LateralMove( input.buttons & BUTTON_ATTACK, VUAA_Attack, VUAF_Attack ); + } +} + +/* +================ +rvVehicleUserAnimated::InitAnim +================ +*/ +void rvVehicleUserAnimated::InitAnim( const char * action, rvUserAnimatedAnim_t & anim ) { + idStr prefix( action ); + rvVehicle * vehicle = position->GetParent(); + + anim.index = vehicle->GetAnimator()->GetAnim( spawnArgs.GetString( prefix + "_anim" ) ); + anim.rate = spawnArgs.GetFloat( prefix + "_rate", "1" ); + anim.frame = spawnArgs.GetInt( prefix + "_frame" ) * anim.rate; + anim.channel = spawnArgs.GetInt( prefix + "_channel" ); + anim.loop = spawnArgs.GetBool( prefix + "_loop" ); + anim.length = vehicle->GetAnimator()->NumFrames( anim.index ); + + // NOTE: I may want to add the suffix "_play" in the InitAnim() function + // this would specify that the animation should be played rather than stepped through + // which could be useful especially for the attack command +} + +/* +================ +rvVehicleUserAnimated::InitFunc +================ +*/ +void rvVehicleUserAnimated::InitFunc( const char * action, rvScriptFuncUtility & func ) { + idStr temp = idStr( action ) + "_script"; + + if ( spawnArgs.GetString( temp, NULL, temp ) ) { + func.Init( temp ); + } +} + +/* +================ +rvVehicleUserAnimated::SetFrame +================ +*/ +void rvVehicleUserAnimated::SetFrame( const rvUserAnimatedAnim_t & anim ) { + float lerp = anim.frame - int( anim.frame ); + frameBlend_t frameBlend = { 0, anim.frame, anim.frame + 1.0f, 1.0f - lerp, lerp }; + position->GetParent()->GetAnimator()->SetFrame( anim.channel, anim.index, frameBlend ); +} + +/* +================ +rvVehicleUserAnimated::LateralMove +================ +*/ +void rvVehicleUserAnimated::LateralMove( signed char input, int anim, int func ) { + if ( !input ) { + return; + } + + rvUserAnimatedAnim_t & theAnim = anims[ anim ]; + + if ( theAnim.index ) { + + float sign = ( ( input > 0 ) ? 1.0f : -1.0f ); + theAnim.frame += theAnim.rate * sign; + + if ( theAnim.frame < 1 ) { + theAnim.frame = ( ( theAnim.loop ) ? theAnim.length : 1 ); + } else if ( theAnim.frame > theAnim.length ) { + theAnim.frame = ( ( theAnim.loop ) ? 1 : theAnim.length ); + } + + SetFrame( theAnim ); + } + + if ( funcs[ func ].Valid( ) ) + funcs[ func ].CallFunc( &spawnArgs ); +} diff --git a/source/game/vehicle/VehicleParts.h b/source/game/vehicle/VehicleParts.h new file mode 100644 index 0000000..9744189 --- /dev/null +++ b/source/game/vehicle/VehicleParts.h @@ -0,0 +1,444 @@ +//---------------------------------------------------------------- +// VehicleParts.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEPARTS_H__ +#define __GAME_VEHICLEPARTS_H__ + +class idWeapon; +class rvVehicle; +class rvVehiclePosition; +class rvEffect; + +//---------------------------------------------------------------- +// Base Part +//---------------------------------------------------------------- + +class rvVehiclePart : public idClass +{ +public: + + ABSTRACT_PROTOTYPE( rvVehiclePart ); + + virtual ~rvVehiclePart( void ) { } + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual bool Init ( rvVehiclePosition* position, const idDict& args, s_channelType soundChannel ); + + virtual void RunPrePhysics ( void ) { } + virtual void RunPhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + bool IsLeft ( void ) const; + bool IsFront ( void ) const; + bool IsUsingCenterMass ( void ) const; + bool IsActive ( void ) const; + + virtual void Activate ( bool activate ) { fl.active = activate; } + + virtual void Impulse ( int impulse ) { } + + virtual rvVehiclePosition* GetPosition ( void ) const; + const idVec3& GetOrigin ( void ) const; + const idMat3& GetAxis ( void ) const; + +protected: + + void UpdateOrigin ( void ); + + typedef struct partFlags_s { + bool active : 1; // Is part active + bool left : 1; // Is part on left side of vehicle + bool front : 1; // Is part on right side of vehicle + bool useCenterMass : 1; // Use center of mass for origin + bool useViewAxis : 1; // Uses parent->viewAxis instead of the physics axis (::UpdateOrigin()) + } partFlags_t; + + partFlags_t fl; + + idDict spawnArgs; + idEntityPtr parent; + jointHandle_t joint; + s_channelType soundChannel; + rvVehiclePosition* position; + + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 localOffset; +}; + +ID_INLINE rvVehiclePosition* rvVehiclePart::GetPosition ( void ) const { return position; } +ID_INLINE const idVec3& rvVehiclePart::GetOrigin ( void ) const { return worldOrigin; } +ID_INLINE const idMat3& rvVehiclePart::GetAxis ( void ) const { return worldAxis; } + +ID_INLINE bool rvVehiclePart::IsLeft ( void ) const { return fl.left; } +ID_INLINE bool rvVehiclePart::IsFront ( void ) const { return fl.front; } +ID_INLINE bool rvVehiclePart::IsUsingCenterMass ( void ) const { return fl.useCenterMass; } +ID_INLINE bool rvVehiclePart::IsActive ( void ) const { return fl.active; } + +//---------------------------------------------------------------- +// Sound +//---------------------------------------------------------------- + +class rvVehicleSound : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleSound ); + + rvVehicleSound ( void ); + ~rvVehicleSound ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + bool IsPlaying ( void ) const; + + void Play ( void ); + void Stop ( void ); + void Update ( bool force = false ); + + void Fade ( int time, float toVolume, float toFreq ); + void Attenuate ( float volumeAttenuate, float freqAttenuate ); + + void SetAutoActivate ( bool activate ); + +protected: + + idVec2 volume; + idVec2 freqShift; + idStr soundName; + refSound_t refSound; + idInterpolate currentVolume; + idInterpolate currentFreqShift; + bool fade; + bool autoActivate; +}; + +ID_INLINE bool rvVehicleSound::IsPlaying ( void ) const { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return( false ); +} + +ID_INLINE void rvVehicleSound::SetAutoActivate ( bool activate ){ + autoActivate = activate; +} + +//---------------------------------------------------------------- +// Hoverpad +//---------------------------------------------------------------- + +class rvVehicleHoverpad : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleHoverpad ); + + rvVehicleHoverpad ( void ); + ~rvVehicleHoverpad ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + void UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ); + + float height; + float dampen; + bool atRest; + + idClipModel* clipModel; + + idInterpolate currentForce; + float force; + const idDeclTable* forceTable; + float forceRandom; + + float fadeTime; + idVec3 velocity; + + float thrustForward; + float thrustLeft; + + float maxRestAngle; + + int soundPart; + + int forceUpTime; + int forceDownTime; + + // Dust effects + rvClientEntityPtr effectDust; + const rvDeclMatType* effectDustMaterialType; +}; + +//---------------------------------------------------------------- +// Thruster +//---------------------------------------------------------------- + +class rvVehicleThruster : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleThruster ); + + rvVehicleThruster ( void ); + ~rvVehicleThruster ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPhysics ( void ); + +protected: + + enum EThrusterKey + { + KEY_FORWARD, + KEY_RIGHT, + KEY_UP + }; + + float force; + int forceAxis; + EThrusterKey key; +}; + +//---------------------------------------------------------------- +// Light +//---------------------------------------------------------------- + +class rvVehicleLight : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleLight ); + + rvVehicleLight ( void ); + ~rvVehicleLight ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + virtual void Impulse ( int impulse ); + +protected: + + void TurnOff ( void ); + void TurnOn ( void ); + + void UpdateLightDef ( void ); + + renderLight_t renderLight; + int lightHandle; + bool lightOn; + + idStr soundOn; + idStr soundOff; +}; + +//---------------------------------------------------------------- +// Weapon +//---------------------------------------------------------------- +class rvVehicleWeapon : public rvVehiclePart { +public: + CLASS_PROTOTYPE( rvVehicleWeapon ); + + rvVehicleWeapon ( void ); + ~rvVehicleWeapon ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool activate ); + + int GetCurrentAmmo ( void ) const; + float GetCurrentCharge ( void ) const; + + void UpdateCursorGUI ( idUserInterface* gui ) const; + + bool Fire (); + + int GetZoomFov ( void ) const; + idUserInterface * GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + bool CanZoom ( void ) const; + + void StopTargetEffect ( void ); + +protected: +#ifdef _XENON + idActor* bestEnemy; + void AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ); +#endif + void LaunchHitScan ( const idVec3& origin, const idVec3& dir, const idVec3& jointOrigin ); + void LaunchProjectile ( const idVec3& origin, const idVec3& dir, const idVec3& pushVelocity ); + void LaunchProjectiles (); + + void UpdateLock ( void ); + void GetLockInfo ( const idVec3& eyeOrigin, const idMat3& eyeAxis ); + + void EjectBrass ( void ); + + void MuzzleFlashLight ( const idVec3& origin, const idMat3& axis ); + void WeaponFeedback ( const idDict* dict ); + + int nextFireTime; + int fireDelay; + int count; + const idDict* projectileDef; + const idDict* hitScanDef; + float spread; + bool launchFromJoint; + bool lockScanning; + int lastLockTime; + float lockRange; + + idEntityPtr targetEnt; + jointHandle_t targetJoint; + idVec3 targetPos; + rvClientEntityPtr targetEffect; + + idList joints; + int jointIndex; + + idVec3 force; + + int ammoPerCharge; + int chargeTime; + int currentAmmo; + idInterpolate currentCharge; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashOffset; + + int animNum; + int animChannel; + + const idSoundShader* shaderFire; + const idSoundShader* shaderReload; + + const idDict* brassDict; + jointHandle_t brassEjectJoint; + idVec3 brassEjectOffset; + int brassEjectNext; + int brassEjectDelay; + + int zoomFov; + idUserInterface * zoomGui; + float zoomTime; +}; + +ID_INLINE int rvVehicleWeapon::GetCurrentAmmo ( void ) const { return currentAmmo; } +ID_INLINE float rvVehicleWeapon::GetCurrentCharge ( void ) const { return currentCharge.IsDone(gameLocal.time) ? 1.0f : currentCharge.GetCurrentValue(gameLocal.time); } +ID_INLINE int rvVehicleWeapon::GetZoomFov ( void ) const { return zoomFov; } +ID_INLINE idUserInterface* rvVehicleWeapon::GetZoomGui ( void ) const { return zoomGui; } +ID_INLINE float rvVehicleWeapon::GetZoomTime ( void ) const { return zoomTime; } +ID_INLINE bool rvVehicleWeapon::CanZoom ( void ) const { return zoomFov != -1; } + + +//---------------------------------------------------------------- +// Turret +//---------------------------------------------------------------- + +class rvVehicleTurret : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleTurret ); + + rvVehicleTurret ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + idBounds angles; + int axisMap[3]; + + float invert[3]; + + idAngles currentAngles; + + int moveTime; + float turnRate; + + int soundPart; + + bool parentStuck; +}; + +//---------------------------------------------------------------- +// Animated Vehicle Part +//---------------------------------------------------------------- + +class rvVehicleUserAnimated : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleUserAnimated ); + + rvVehicleUserAnimated ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + + void Event_PostSpawn ( void ); + +protected: + + typedef struct rvUserAnimatedAnim_s { + int index; + float frame; + short length; + short channel; + float rate; + bool loop; + } rvUserAnimatedAnim_t; + + enum { VUAA_Forward, VUAA_Strafe, VUAA_Crouch, VUAA_Attack, VUAA_Count }; + enum { VUAF_Forward, VUAF_Strafe, VUAF_Crouch, VUAF_Attack, VUAF_Count }; + + rvUserAnimatedAnim_t anims[ VUAA_Count ]; + rvScriptFuncUtility funcs[ VUAF_Count ]; + + void InitAnim( const char * action, rvUserAnimatedAnim_t & anim ); + void InitFunc( const char * action, rvScriptFuncUtility & func ); + void SetFrame( const rvUserAnimatedAnim_t & anim ); + void LateralMove( signed char input, int anim, int func ); +}; + +typedef idList rvVehiclePartList_t; + +#endif // __GAME_VEHICLEPARTS_H__ diff --git a/source/game/vehicle/VehiclePosition.cpp b/source/game/vehicle/VehiclePosition.cpp new file mode 100644 index 0000000..3f8a0b0 --- /dev/null +++ b/source/game/vehicle/VehiclePosition.cpp @@ -0,0 +1,845 @@ +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "Vehicle.h" + +/* +================ +rvVehiclePosition::rvVehiclePosition +================ +*/ +rvVehiclePosition::rvVehiclePosition ( void ) { + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + + memset ( &fl, 0, sizeof(fl) ); + + mCurrentWeapon = -1; + mSoundPart = -1; + + mDriver = NULL; + mParent = NULL; + + mEyeOrigin.Zero(); + mEyeAxis.Identity(); + + mEyeOffset.Zero(); + mEyeJoint = INVALID_JOINT; + + mDriverOffset.Zero ( ); + mDriverJoint = INVALID_JOINT; +} + +/* +================ +rvVehiclePosition::~rvVehiclePosition +================ +*/ +rvVehiclePosition::~rvVehiclePosition ( void ) { + mParts.DeleteContents ( true ); + mWeapons.DeleteContents ( true ); +} + +void InitIntArray( const idVec3& vec, int array[] ) { + int ix; + for( ix = 0; ix < vec.GetDimension(); ++ix ) { + array[ix] = (int)vec[ix]; + } +} + +/* +================ +rvVehiclePosition::Init +================ +*/ +void rvVehiclePosition::Init ( rvVehicle* parent, const idDict& args ) { + idVec3 garbage; + + mParent = parent; + + mEyeJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "eyeJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mEyeJoint, gameLocal.GetTime(), garbage, mEyeJointTransform) ) { + mEyeJointTransform.Identity(); + //} + //mEyeJointTransform.TransposeSelf(); + mEyeOffset = args.GetVector ( "eyeOffset", "0 0 0" ); + mDeltaEyeAxisScale = args.GetAngles( "deltaEyeAxisScale", "1 1 0" ); + mDeltaEyeAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("eyeJointAxisMap", "0 1 2"), mEyeJointAxisMap ); + InitIntArray( args.GetVector("eyeJointDirMap", "1 1 1"), mEyeJointDirMap ); + + mAxisOffset = args.GetAngles( "angles_offset" ).ToMat3(); + + mDriverJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "driverJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mDriverJoint, gameLocal.GetTime(), garbage, mDriverJointTransform) ) { + mDriverJointTransform.Identity(); + //} + //mDriverJointTransform.TransposeSelf( ); + mDriverOffset = args.GetVector ( "driverOffset", "0 0 0" ); + mDeltaDriverAxisScale = args.GetAngles( "deltaDriverAxisScale", "1 1 0" ); + mDeltaDriverAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("driverJointAxisMap", "0 1 2"), mDriverJointAxisMap ); + InitIntArray( args.GetVector("driverJointDirMap", "1 1 1"), mDriverJointDirMap ); + + mExitPosOffset = args.GetVector( "exitPosOffset" ); + mExitAxisOffset = args.GetAngles( "exitAxisOffset" ).ToMat3(); + + mDriverAnim = args.GetString ( "driverAnim", "driver" ); + + fl.driverVisible = args.GetBool ( "driverVisible", "0" ); + fl.engine = args.GetBool ( "engine", "0" ); + fl.depthHack = args.GetBool ( "depthHack", "1" ); + fl.bindDriver = args.GetBool ( "bindDriver", "1" ); + + args.GetString ( "internalSurface", "", mInternalSurface ); + + SetParts ( args ); + + mSoundMaxSpeed = args.GetFloat ( "maxsoundspeed", "0" ); + + // Looping sound when occupied? + if ( *args.GetString ( "snd_loop", "" ) ) { + mSoundPart = AddPart ( rvVehicleSound::GetClassType(), args ); + static_cast(mParts[mSoundPart])->SetAutoActivate ( false ); + } + + SelectWeapon ( 0 ); + + UpdateInternalView ( true ); +} + +/* +================ +rvVehiclePosition::SetParts +================ +*/ +void rvVehiclePosition::SetParts ( const idDict& args ) { + const idKeyValue* kv; + + // Spawn all parts + kv = args.MatchPrefix( "def_part", NULL ); + while ( kv ) { + const idDict* dict; + idTypeInfo* typeInfo; + + // Skip empty strings + if ( !kv->GetValue().Length() ) { + kv = args.MatchPrefix( "def_part", kv ); + continue; + } + + // Get the dictionary for the part + dict = gameLocal.FindEntityDefDict ( kv->GetValue() ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Determine the part type + typeInfo = idClass::GetClass ( dict->GetString ( "spawnclass" ) ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) { + gameLocal.Error ( "Class '%s' is not a vehicle part", dict->GetString ( "spawnclass" ) ); + } + + // Add the new part + AddPart ( *typeInfo, *dict ); + + kv = args.MatchPrefix( "def_part", kv ); + } +} + +/* +================ +rvVehiclePosition::AddPart +================ +*/ +int rvVehiclePosition::AddPart ( const idTypeInfo &classdef, const idDict& args ) { + rvVehiclePart* part; + int soundChannel; + + // Get a sound channel + soundChannel = SND_CHANNEL_CUSTOM; + soundChannel += mParts.Num(); + + // Allocate the new part + part = static_cast(classdef.CreateInstance ( )); + part->Init ( this, args, (s_channelType)soundChannel ); + part->CallSpawn ( ); + + // Weapons go into their own list since only one can be active + // and any given point in time. + if ( part->IsType ( rvVehicleWeapon::GetClassType() ) ) { + return mWeapons.Append ( part ); + } + + return mParts.Append ( part ); +} + +/* +================ +rvVehiclePosition::SetDriver +================ +*/ +bool rvVehiclePosition::SetDriver ( idActor* driver ) { + if ( mDriver == driver ) { + return false; + } + + fl.inputValid = false; + + if ( driver ) { + mDriver = driver; + + // Keep the driver visible if the position is exposed + if ( fl.driverVisible ) { + mDriver->Show ( ); + } else { + mDriver->Hide ( ); + + // Dont let the player take damage when inside the if not visible + mDriver->fl.takedamage = false; + } + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Play ( ); + } + + // Bind the driver to a joint or to the vehicles origin? + if( fl.bindDriver ) { + if ( INVALID_JOINT != mDriverJoint ) { + mDriver->GetPhysics()->SetAxis ( mParent->GetAxis( ) );// Not sure if this is needed + mDriver->BindToJoint ( mParent, mDriverJoint, true ); + mDriver->GetPhysics()->SetOrigin ( vec3_origin ); + } else { + mDriver->Bind ( mParent, true ); + mDriver->GetPhysics()->SetOrigin( mDriverOffset ); + } + } else {// If not bound put the vehicle first in the active list + mParent->activeNode.Remove(); + mParent->activeNode.AddToFront( gameLocal.activeEntities ); + } + + // Play a certain animation on the driver + if ( mDriverAnim.Length() ) { + mDriver->GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, mDriver->GetAnimator()->GetAnim( mDriverAnim ), gameLocal.time, 0 ); + } + } else { + if ( !fl.driverVisible ) { + mDriver->Show ( ); + + // Take damage again + mDriver->fl.takedamage = true; + } + + // Driver is no longer bound to the vehicle + mDriver->Unbind(); + + mDriver = driver; + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Stop ( ); + } + + // Clear out the input commands so the guns dont keep firing and what not when + // the player gets out + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + } + + return true; +} + +/* +================ +rvVehiclePosition::GetAxis +================ +*/ +idMat3 rvVehiclePosition::GetAxis() const { + return mAxisOffset * GetParent()->GetPhysics()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetOrigin +================ +*/ +idVec3 rvVehiclePosition::GetOrigin( const idVec3& offset ) const { + return GetParent()->GetPhysics()->GetOrigin() + (mDriverOffset + offset) * GetAxis(); +} + +/* +================ +rvVehiclePosition::ActivateParts +================ +*/ +void rvVehiclePosition::ActivateParts ( bool active ) { + int i; + + // Activate or deactive the parts based on whether there is a driver + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + mParts[i]->Activate ( active ); + } + + for ( i = mWeapons.Num() - 1; i >= 0; i -- ) { + mWeapons[i]->Activate ( active ); + } +} + +/* +================ +rvVehiclePosition::EjectDriver +================ +*/ +bool rvVehiclePosition::EjectDriver ( bool force ) { + if ( !IsOccupied ( ) ) { + return false; + } + + // Physically eject the actor from the position + if ( !force && mParent->IsLocked ( ) ) { + mParent->IssueLockedWarning ( ); + return false; + } + + // Remove the driver and if successful disable all parts for + // this position + if ( SetDriver ( NULL ) ) { + ActivateParts ( false ); + return true; + } + + return false; +} + +/* +================ +rvVehiclePosition::SetInput +================ +*/ +void rvVehiclePosition::SetInput ( const usercmd_t& cmd, const idAngles& newAngles ) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE ) ) { + if ( mDriver == gameLocal.GetLocalPlayer ( ) ) { + gameDebug.SetFocusEntity ( mParent ); + } + } + + if ( fl.inputValid || !mDriver ) { + mOldInputCmd = mInputCmd; + mOldInputAngles = mInputAngles; + } else { + mOldInputCmd = cmd; + mInputCmd = cmd; + mInputAngles = newAngles; + + // We have valid input now and there is a driver so activate all of the parts + if ( !fl.inputValid && mDriver ) { + ActivateParts ( true ); + } + } + + fl.inputValid = true; + mInputCmd = cmd; + mInputAngles = newAngles; +} + +/* +================ +rvVehiclePosition::GetInput +================ +*/ +void rvVehiclePosition::GetInput( usercmd_t& cmd, idAngles& newAngles ) const { + cmd = mInputCmd; + newAngles = mInputAngles; +} + +/* +================ +rvVehiclePosition::UpdateHUD +================ +*/ +void rvVehiclePosition::UpdateHUD ( idUserInterface* gui ) { + + // HACK: twhitaker: this is ugly but since the GEV and the Walker now have a unified HUD, it is a necessary evil + int guiWeaponID; + + if ( !stricmp( mParent->spawnArgs.GetString( "classname" ), "vehicle_walker" ) ) { + guiWeaponID = mCurrentWeapon + 2; + } else { + guiWeaponID = mCurrentWeapon; + } + + gui->SetStateInt ( "vehicle_weapon", guiWeaponID ); + // HACK: twhitaker end + + gui->SetStateInt ( "vehicle_weaponcount", mWeapons.Num() ); + if ( mCurrentWeapon >= 0 && mCurrentWeapon < mWeapons.Num() ) { + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + gui->SetStateFloat ( "vehicle_weaponcharge", weapon->GetCurrentCharge() ); + gui->SetStateInt ( "vehicle_weaponammo", weapon->GetCurrentAmmo() ); + } + + // Calculate the rotation of the view in relation to the vehicle itself + gui->SetStateFloat ( "vehicle_rotate", -idMath::AngleDelta ( mEyeAxis.ToAngles()[YAW], mParent->GetAxis ( ).ToAngles()[YAW] ) ); + + if( GetParent() ) { + GetParent()->UpdateHUD( GetDriver(), gui ); + } +} + +/* +================ +rvVehiclePosition::UpdateCursorGUI +================ +*/ +void rvVehiclePosition::UpdateCursorGUI ( idUserInterface* gui ) { + if ( mCurrentWeapon < 0 || mCurrentWeapon >= mWeapons.Num() ) { + return; + } + + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + assert ( weapon ); + weapon->UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehiclePosition::UpdateInternalView +================ +*/ +void rvVehiclePosition::UpdateInternalView ( bool force ) { + bool internal = false; + + // If the local player is driving and not in third person then use internal + internal = ( mDriver == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetInteger() ); + + // force the update? + if ( !force && internal == fl.internalView ) { + return; + } + + fl.internalView = internal; + + if ( fl.depthHack ) { + mParent->GetRenderEntity()->weaponDepthHackInViewID = (IsOccupied() && fl.internalView) ? GetDriver()->entityNumber + 1 : 0; + } + + // Show and hide the internal surface + if ( mInternalSurface.Length() ) { + mParent->ProcessEvent ( fl.internalView ? &EV_ShowSurface : &EV_HideSurface, mInternalSurface.c_str() ); + } +} + +/* +================ +rvVehiclePosition::RunPrePhysics +================ +*/ +void rvVehiclePosition::RunPrePhysics ( void ) { + int i; + + UpdateInternalView ( ); + + if ( mParent->IsStalled() ) { + if ( !fl.stalled ) { + // we just stalled + fl.stalled = true; + ActivateParts( false ); + } + return; + } else if ( IsOccupied() && !mParent->IsStalled() && fl.stalled ) { + // we just restarted + fl.stalled = false; + ActivateParts( true ); + } + + // Give the parts a chance to set up for physics + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + mParts[i]->RunPrePhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPrePhysics ( ); + } + + // Run physics for each part + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + + mParts[i]->RunPhysics ( ); + } + + // Attenuate the attached sound if speed based + if ( mSoundPart != -1 && mSoundMaxSpeed > 0.0f ) { + float f; + float speed; + idVec3 vel; + + // Only interested in forward or backward velocity + vel = mParent->GetPhysics()->GetLinearVelocity ( ) * mParent->GetPhysics()->GetAxis ( ); + speed = idMath::ClampFloat ( 0.0f, mSoundMaxSpeed, vel.Normalize ( ) ); + f = speed / mSoundMaxSpeed; + + static_cast(mParts[mSoundPart])->Attenuate ( f, f ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPhysics ( ); + } +} + +/* +================ +rvVehiclePosition::RunPostPhysics +================ +*/ +void rvVehiclePosition::RunPostPhysics ( void ) { + int i; + for ( i = 0; i < mParts.Num(); i ++ ) { + assert ( mParts[i] ); + mParts[i]->RunPostPhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPostPhysics ( ); + } + + if ( !IsOccupied ( ) ) { + return; + } + + if ( fl.inputValid ) { + if ( mInputCmd.upmove > 0 && !mParent->IsLocked() ) { + // inform the driver that its time to get out of the vehicle. + if( !mDriver->EventIsPosted(&AI_ExitVehicle) ) { + mDriver->PostEventMS( &AI_ExitVehicle, 250, false );// To remove jump when exiting + } + + // If the position isnt occupied anymore then the vehicle exit was successful and there + // is nothing else to do + if ( !IsOccupied ( ) ) { + return; + } + } else if ( (mInputCmd.flags & UCF_IMPULSE_SEQUENCE) != (mOldInputFlags & UCF_IMPULSE_SEQUENCE) ) { + int i; + + if ( mInputCmd.impulse >= IMPULSE_0 && mInputCmd.impulse <= IMPULSE_12 ) { + SelectWeapon( mInputCmd.impulse - IMPULSE_0 ); + } + + if ( mWeapons.Num () ) { + switch ( mInputCmd.impulse ) { + case IMPULSE_14: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() + 1) % mWeapons.Num() ); + break; + + case IMPULSE_15: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() - 1) % mWeapons.Num() ); + break; + } + } + + for( i = 0; i < mParts.Num(); i++ ) { + mParts[ i ]->Impulse ( mInputCmd.impulse ); + } + + mOldInputFlags = mInputCmd.flags; + } + } + + // Update the eye origin and axis + GetEyePosition( mEyeOrigin, mEyeAxis ); + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow( colorMagenta, mEyeOrigin, mEyeOrigin + mEyeAxis[0] * 30.0f, 3 ); + } +} + +/* +================ +rvVehiclePosition::GetEyePosition +================ +*/ +void rvVehiclePosition::GetEyePosition( idVec3& origin, idMat3& axis ) const { + GetPosition( mEyeJoint, mEyeOffset, mEyeJointTransform, mDeltaEyeAxisScale, mEyeJointAxisMap, mEyeJointDirMap, origin, axis ); + axis *= GetParent()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetDriverPosition +================ +*/ +void rvVehiclePosition::GetDriverPosition( idVec3& origin, idMat3& axis ) const { + if( fl.bindDriver ) { + GetPosition( mDriverJoint, mDriverOffset, mDriverJointTransform, mDeltaDriverAxisScale, mDriverJointAxisMap, mDriverJointDirMap, origin, axis ); + } else { + origin = GetDriver()->GetPhysics()->GetOrigin(); + axis = GetDriver()->viewAxis; + } +} + +/* +================ +rvVehiclePosition::GetPosition +================ +*/ +void rvVehiclePosition::GetPosition( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const { + if( GetParent()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.GetTime(), origin, axis) ) { + axis *= jointTransform; + idAngles ang( axis.ToAngles().Remap(axisMap, dirMap).Scale(scale) ); + axis = ang.Normalize360().ToMat3() * mAxisOffset; + + origin = GetParent()->GetOrigin() + (origin + offset * axis) * GetParent()->GetAxis(); + } else { + origin = GetOrigin( mEyeOffset ); + axis = GetAxis(); + } +} + +/* +================ +rvVehiclePosition::FireWeapon +================ +*/ +void rvVehiclePosition::FireWeapon( void ) { + if ( mCurrentWeapon >= 0 ) { + static_cast( mWeapons[mCurrentWeapon] )->Fire(); + } +} + +/* +================ +rvVehiclePosition::SelectWeapon +================ +*/ +void rvVehiclePosition::SelectWeapon ( int weapon ) { + if ( weapon < 0 || weapon >= mWeapons.Num ( ) ) { + return; + } + +// mekberg: clear effect + if ( mCurrentWeapon != -1 ) { + static_cast ( mWeapons[ mCurrentWeapon ] )->StopTargetEffect( ); + } + + mCurrentWeapon = weapon; +} + +/* +================ +rvVehiclePosition::WriteToSnapshot +================ +*/ +void rvVehiclePosition::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteBits ( mCurrentWeapon, 3 ); +} + +/* +================ +rvVehiclePosition::ReadFromSnapshot +================ +*/ +void rvVehiclePosition::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mCurrentWeapon = msg.ReadBits ( 3 ); +} + +/* +================ +rvVehiclePosition::Save +================ +*/ +void rvVehiclePosition::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteUsercmd( mInputCmd ); + savefile->WriteAngles ( mInputAngles ); + savefile->WriteUsercmd ( mOldInputCmd ); + savefile->WriteAngles ( mOldInputAngles ); + savefile->WriteInt ( mOldInputFlags ); + + savefile->WriteInt ( mCurrentWeapon ); + + mDriver.Save ( savefile ); + mParent.Save ( savefile ); + + savefile->WriteInt ( mParts.Num ( ) ); + for ( i = 0; i < mParts.Num(); i ++ ) { + savefile->WriteString ( mParts[i]->GetClassname() ); + savefile->WriteStaticObject ( *mParts[i] ); + } + + savefile->WriteInt ( mWeapons.Num ( ) ); + for ( i = 0; i < mWeapons.Num(); i ++ ) { + savefile->WriteString ( mWeapons[i]->GetClassname() ); + savefile->WriteStaticObject ( *mWeapons[i] ); + } + + savefile->WriteVec3 ( mEyeOrigin ); + savefile->WriteMat3 ( mEyeAxis ); + + savefile->WriteJoint ( mEyeJoint ); + savefile->WriteVec3 ( mEyeOffset ); + savefile->WriteMat3( mEyeJointTransform ); + savefile->WriteAngles( mDeltaEyeAxisScale ); + savefile->Write ( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Write ( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->WriteMat3 ( mAxisOffset ); + + savefile->WriteJoint ( mDriverJoint ); + savefile->WriteVec3 ( mDriverOffset ); + savefile->WriteMat3 ( mDriverJointTransform ); + savefile->WriteAngles( mDeltaDriverAxisScale ); + savefile->Write ( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Write ( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->WriteVec3 ( mExitPosOffset ); + savefile->WriteMat3 ( mExitAxisOffset ); + + savefile->WriteString ( mDriverAnim ); + + savefile->WriteString ( mInternalSurface ); + + savefile->Write ( &fl, sizeof ( fl ) ); + + savefile->WriteInt ( mSoundPart ); + savefile->WriteFloat ( mSoundMaxSpeed ); +} + +/* +================ +rvVehiclePosition::Restore +================ +*/ +void rvVehiclePosition::Restore ( idRestoreGame* savefile ) { + int i; + int num; + + savefile->ReadUsercmd( mInputCmd ); + savefile->ReadAngles ( mInputAngles ); + savefile->ReadUsercmd ( mOldInputCmd ); + savefile->ReadAngles ( mOldInputAngles ); + savefile->ReadInt ( mOldInputFlags ); + + savefile->ReadInt ( mCurrentWeapon ); + + mDriver.Restore ( savefile ); + mParent.Restore ( savefile ); + + savefile->ReadInt ( num ); + mParts.Clear ( ); + mParts.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mParts[i] = part; + } + + savefile->ReadInt ( num ); + mWeapons.Clear ( ); + mWeapons.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mWeapons[i] = part; + } + + savefile->ReadVec3 ( mEyeOrigin ); + savefile->ReadMat3 ( mEyeAxis ); + + savefile->ReadJoint ( mEyeJoint ); + savefile->ReadVec3 ( mEyeOffset ); + savefile->ReadMat3 ( mEyeJointTransform ); + savefile->ReadAngles( mDeltaEyeAxisScale ); + savefile->Read( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Read( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->ReadMat3 ( mAxisOffset ); + + savefile->ReadJoint ( mDriverJoint ); + savefile->ReadVec3 ( mDriverOffset ); + savefile->ReadMat3 ( mDriverJointTransform ); + savefile->ReadAngles( mDeltaDriverAxisScale ); + savefile->Read( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Read( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->ReadVec3 ( mExitPosOffset ); + savefile->ReadMat3 ( mExitAxisOffset ); + + savefile->ReadString ( mDriverAnim ); + + savefile->ReadString ( mInternalSurface ); + + savefile->Read ( &fl, sizeof(fl) ); + savefile->ReadInt ( mSoundPart ); + savefile->ReadFloat ( mSoundMaxSpeed ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/game/vehicle/VehicleRigid.cpp b/source/game/vehicle/VehicleRigid.cpp new file mode 100644 index 0000000..68c7188 --- /dev/null +++ b/source/game/vehicle/VehicleRigid.cpp @@ -0,0 +1,163 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleRigid.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleRigid ) +END_CLASS + +rvVehicleRigid::rvVehicleRigid ( void ) { +} + +rvVehicleRigid::~rvVehicleRigid ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleRigid::Spawn +================ +*/ +void rvVehicleRigid::Spawn( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleRigid::SetClipModel +================ +*/ +void rvVehicleRigid::SetClipModel ( void ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleRigid '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + + +/* +================ +rvVehicleRigid::RunPrePhysics +================ +*/ +void rvVehicleRigid::RunPrePhysics ( void ) { + storedVelocity = physicsObj.GetLinearVelocity(); +} + +/* +================ +rvVehicleRigid::RunPostPhysics +================ +*/ +void rvVehicleRigid::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 1250 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + float lengthSq = physicsObj.GetLinearVelocity().LengthSqr(); + if ( !autoCorrectionBegin && ( ( storedVelocity * 0.4f ).LengthSqr() >= lengthSq ) || ( lengthSq < 0.01f ) ) { + autoCorrectionBegin = gameLocal.time; + } + } + + if ( g_debugVehicle.GetInteger() == 10 ) { + gameLocal.Printf( "Speed: %f\n", physicsObj.GetLinearVelocity().Length() ); + } +} + +/* +================ +rvVehicleRigid::WriteToSnapshot +================ +*/ +void rvVehicleRigid::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot( msg ); + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::ReadFromSnapshot +================ +*/ +void rvVehicleRigid::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot( msg ); + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::Save +================ +*/ +void rvVehicleRigid::Save ( idSaveGame *savefile ) const { + + savefile->WriteVec3 ( storedVelocity ); // cnicholson: Added unsaved var + savefile->WriteStaticObject ( physicsObj ); +} + +/* +================ +rvVehicleRigid::Restore +================ +*/ +void rvVehicleRigid::Restore ( idRestoreGame *savefile ) { + + savefile->ReadVec3 ( storedVelocity ); // cnicholson: Added unrestored var + + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +===================== +rvVehicleRigid::SkipImpulse +===================== +*/ +bool rvVehicleRigid::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/source/game/vehicle/VehicleRigid.h b/source/game/vehicle/VehicleRigid.h new file mode 100644 index 0000000..eba3ea5 --- /dev/null +++ b/source/game/vehicle/VehicleRigid.h @@ -0,0 +1,45 @@ +//---------------------------------------------------------------- +// VehicleRigid.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLERIGID_H__ +#define __GAME_VEHICLERIGID_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleRigid : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleRigid ); + + rvVehicleRigid ( void ); + ~rvVehicleRigid ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( void ); + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedVelocity; + // end twhitaker: + + idPhysics_RigidBody physicsObj; // physics object +}; + +#endif // __GAME_VEHICLERIGID_H__ diff --git a/source/game/vehicle/VehicleSpline.cpp b/source/game/vehicle/VehicleSpline.cpp new file mode 100644 index 0000000..169e3c2 --- /dev/null +++ b/source/game/vehicle/VehicleSpline.cpp @@ -0,0 +1,123 @@ +//---------------------------------------------------------------- +// VehicleSplineCoupling.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleSpline.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleSpline ) + EVENT( EV_PostSpawn, rvVehicleSpline::Event_PostSpawn ) + EVENT( EV_SetSpline, rvVehicleSpline::Event_SetSpline ) + EVENT( EV_DoneMoving, rvVehicleSpline::Event_DoneMoving ) +END_CLASS + +rvVehicleSpline::rvVehicleSpline ( void ) { +} + +rvVehicleSpline::~rvVehicleSpline ( void ) { + SetPhysics( NULL ); +} + +void rvVehicleSpline::Spawn ( void ) { + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat( "accel", "200" ) ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat( "decel", "200" ) ); + + viewAxis = idAngles( 0, spawnArgs.GetFloat( "angle" ) , 0 ).ToMat3(); + + physicsObj.SetAxis( GetPhysics()->GetAxis() * viewAxis ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + BecomeActive( TH_THINK ); + + accelWithStrafe = Sign( spawnArgs.GetFloat("accel_strafe") ); + + idealSpeed = spawnArgs.GetFloat( "speed", "200" ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleSpline::Save +================ +*/ +void rvVehicleSpline::Save ( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteMat3( angleOffset ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( accelWithStrafe ); +} + +/* +================ +rvVehicleSpline::Restore +================ +*/ +void rvVehicleSpline::Restore ( idRestoreGame *savefile ) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadMat3( angleOffset ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( accelWithStrafe ); +} + +void rvVehicleSpline::Think( void ) { + float moveAmount = 0.0f; + + if ( positions[0].IsOccupied ( ) && !IsFrozen () && IsMovementEnabled ( ) ) { + + if ( accelWithStrafe != 0.0f ) { + moveAmount = positions[0].mInputCmd.rightmove * accelWithStrafe; + } else { + moveAmount = positions[0].mInputCmd.forwardmove; + } + + moveAmount = Sign( moveAmount ); + } + + physicsObj.SetSpeed( idealSpeed * moveAmount ); + + rvVehicle::Think( ); +} + +void rvVehicleSpline::Event_PostSpawn( void ) { + idStr splinePath; + + if ( spawnArgs.GetString( "spline_path", "", splinePath ) ) { + Event_SetSpline( gameLocal.FindEntity( splinePath.c_str() ) ); + } else { + gameLocal.Warning( "\"spline_path\" key not found on %s (rvVehicleSpline)", this->GetName() ); + } +} + +void rvVehicleSpline::Event_SetSpline ( idEntity * spline ) { + if ( spline && spline->IsType( idSplinePath::GetClassType() ) ) { + physicsObj.SetSplineEntity( static_cast< idSplinePath * >( spline ) ); + //HACK: force an intitial physics update so that the object orients itself with the spline + //TEMP: this should be fixed after the build on friday (Jan 28, 2005) + physicsObj.SetSpeed( 0.1f ); + RunPhysics(); + //END HACK + } +} + +void rvVehicleSpline::Event_DoneMoving() { + idThread::ReturnInt( true ); +} diff --git a/source/game/vehicle/VehicleSpline.h b/source/game/vehicle/VehicleSpline.h new file mode 100644 index 0000000..2ab006b --- /dev/null +++ b/source/game/vehicle/VehicleSpline.h @@ -0,0 +1,34 @@ +//---------------------------------------------------------------- +// VehicleSpline.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESPLINECOUPLING_H__ +#define __GAME_VEHICLESPLINECOUPLING_H__ + +class rvVehicleSpline : public rvVehicle { +public: + CLASS_PROTOTYPE( rvVehicleSpline ); + + rvVehicleSpline ( void ); + ~rvVehicleSpline ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Think ( void ); + + void Event_PostSpawn ( void ); + void Event_SetSpline ( idEntity * spline ); + void Event_DoneMoving ( void ); + +protected: + rvPhysics_Spline physicsObj; + idMat3 angleOffset; + float idealSpeed; + float accelWithStrafe; +}; + +#endif // __GAME_VEHICLESPLINECOUPLING_H__ diff --git a/source/game/vehicle/VehicleStatic.cpp b/source/game/vehicle/VehicleStatic.cpp new file mode 100644 index 0000000..0ccb901 --- /dev/null +++ b/source/game/vehicle/VehicleStatic.cpp @@ -0,0 +1,118 @@ +//---------------------------------------------------------------- +// VehicleStatic.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleStatic ) + EVENT( AI_ScriptedAnim, rvVehicleStatic::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleStatic::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleStatic::Event_ScriptedStop ) +END_CLASS + +rvVehicleStatic::rvVehicleStatic ( void ) { +} + +rvVehicleStatic::~rvVehicleStatic ( void ) { +} + +/* +================ +rvVehicleStatic::Spawn +================ +*/ +void rvVehicleStatic::Spawn( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleStatic::AddDriver +================ +*/ +int rvVehicleStatic::AddDriver ( int position, idActor* driver ) { + int pos = rvVehicle::AddDriver( position, driver ); + + if( pos < 0 ) { + return pos; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "hideGunInfo" ); + } + + return pos; +} + +/* +================ +rvVehicleStatic::RemoveDriver +================ +*/ +bool rvVehicleStatic::RemoveDriver ( int position, bool force ) { + bool result = rvVehicle::RemoveDriver( position, force ); + + if( !result ) { + return result; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "showGunInfo" ); + } + + return result; +} + +/* +================ +rvVehicleStatic::UpdateHUD +================ +*/ +void rvVehicleStatic::UpdateHUD( idActor* driver, idUserInterface* gui ) { + if( driver && driver->IsType( idPlayer::GetClassType() ) ) { + static_cast(driver)->UpdateHudStats( gui ); + } +} + +/* +================ +rvVehicleStatic::Event_ScriptedAnim +================ +*/ +void rvVehicleStatic::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + vfl.scripted = true; +} + +/* +================ +rvVehicleStatic::Event_ScriptedDone +================ +*/ +void rvVehicleStatic::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleStatic::Event_ScriptedStop +================ +*/ +void rvVehicleStatic::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + PlayCycle( ANIMCHANNEL_TORSO, spawnArgs.GetString( "idle" ), 2 ); + } +} diff --git a/source/game/vehicle/VehicleStatic.h b/source/game/vehicle/VehicleStatic.h new file mode 100644 index 0000000..7a0d861 --- /dev/null +++ b/source/game/vehicle/VehicleStatic.h @@ -0,0 +1,35 @@ +//---------------------------------------------------------------- +// VehicleStatic.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESTATIC_H__ +#define __GAME_VEHICLESTATIC_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleStatic : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleStatic ); + + rvVehicleStatic ( void ); + ~rvVehicleStatic ( void ); + + void Spawn ( void ); + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); +}; + +#endif // __GAME_VEHICLESTATIC_H__ diff --git a/source/game/vehicle/Vehicle_DropPod.cpp b/source/game/vehicle/Vehicle_DropPod.cpp new file mode 100644 index 0000000..bc72e9e --- /dev/null +++ b/source/game/vehicle/Vehicle_DropPod.cpp @@ -0,0 +1,177 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +class rvVehicleDropPod : public rvVehicleStatic { +public: + + CLASS_PROTOTYPE( rvVehicleDropPod ); + + rvVehicleDropPod ( void ); + + void Spawn ( void ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleDropPod ); +}; + +CLASS_DECLARATION( rvVehicleStatic, rvVehicleDropPod ) + EVENT( AI_ScriptedAnim, rvVehicleDropPod::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleDropPod::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleDropPod::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleDropPod::rvVehicleDropPod +================ +*/ +rvVehicleDropPod::rvVehicleDropPod ( void ) { +} + +/* +================ +rvVehicleDropPod::Spawn +================ +*/ +void rvVehicleDropPod::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleDropPod ) + STATE ( "State_Idle", rvVehicleDropPod::State_Idle ) + STATE ( "State_IdleThink", rvVehicleDropPod::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleDropPod::State_IdleOffline ) + + STATE ( "State_ScriptedAnim", rvVehicleDropPod::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleDropPod::State_IdleOffline +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 0 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_Idle +================ +*/ +stateResult_t rvVehicleDropPod::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_IdleThink +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", parms.blendFrames ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleDropPod::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleDropPod::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedAnim +================ +*/ +void rvVehicleDropPod::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedDone +================ +*/ +void rvVehicleDropPod::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleDropPod::Event_ScriptedStop +================ +*/ +void rvVehicleDropPod::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 1 ); + } +} + +/* +================ +rvVehicleDropPod::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleDropPod::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { +// if ( GetBindMaster() ) { +// axis = viewAxis; +// origin.Zero(); +// return true; +// } + return false; +} + diff --git a/source/game/vehicle/Vehicle_Walker.cpp b/source/game/vehicle/Vehicle_Walker.cpp new file mode 100644 index 0000000..66192ff --- /dev/null +++ b/source/game/vehicle/Vehicle_Walker.cpp @@ -0,0 +1,593 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +class rvVehicleWalker : public rvVehicleAnimated { +public: + + CLASS_PROTOTYPE( rvVehicleWalker ); + + rvVehicleWalker ( void ); + + void Think ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void UpdateState ( void ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + + const char* stopAnimName; + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + +private: + void HandleStrafing ( void ); + + + stateResult_t Frame_ForwardRight ( int ); + stateResult_t Frame_ForwardLeft ( int ); + stateResult_t Frame_BackwardRight ( int ); + stateResult_t Frame_BackwardLeft ( int ); + + stateResult_t State_Wait_OnlineAnim ( const stateParms_t& parms ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_Offline ( const stateParms_t& parms ); + stateResult_t State_Online ( const stateParms_t& parms ); + + stateResult_t State_ForwardStart ( const stateParms_t& parms ); + stateResult_t State_Forward ( const stateParms_t& parms ); + stateResult_t State_BackwardStart ( const stateParms_t& parms ); + stateResult_t State_Backward ( const stateParms_t& parms ); + stateResult_t State_Stop ( const stateParms_t& parms ); + stateResult_t State_Turn ( const stateParms_t& parms ); + stateResult_t State_TurnThink ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleWalker ); +}; + +CLASS_DECLARATION( rvVehicleAnimated, rvVehicleWalker ) + EVENT( AI_ScriptedAnim, rvVehicleWalker::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleWalker::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleWalker::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleWalker::rvVehicleWalker +================ +*/ +rvVehicleWalker::rvVehicleWalker ( void ) { + stopAnimName = ""; +} + +/* +================ +rvVehicleWalker::Think +================ +*/ +void rvVehicleWalker::Think ( void ) { + rvVehicleAnimated::Think(); + + if ( !HasDrivers() || IsStalled() ) { + return; + } + + idVec3 delta; + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( delta.LengthSqr() > 0.1f ) { + gameLocal.RadiusDamage( GetOrigin(), this, this, this, this, spawnArgs.GetString( "def_stompDamage", "damage_Smallexplosion" ) ); + } +} + +/* +================ +rvVehicleWalker::Spawn +================ +*/ +void rvVehicleWalker::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +================ +rvVehicleWalker::Save +================ +*/ +void rvVehicleWalker::Save ( idSaveGame *savefile ) const { + savefile->WriteString( stopAnimName ); +} + +/* +================ +rvVehicleWalker::Restore +================ +*/ +void rvVehicleWalker::Restore ( idRestoreGame *savefile ) { + //twhitaker: I just happened to see this, while going through this code which I originally wrote (at 3am or so). + //TODO: fix this. Make stopAnimName an idStr? + idStr str; + savefile->ReadString( str ); + stopAnimName = str; +} + +/* +================ +rvVehicleWalker::UpdateState +================ +*/ +void rvVehicleWalker::UpdateState ( void ) { + rvVehiclePosition& pos = positions[0]; + usercmd_t& cmd = pos.mInputCmd; + + vfl.driver = pos.IsOccupied(); + vfl.forward = (vfl.driver && cmd.forwardmove > 0); + vfl.backward = (vfl.driver && cmd.forwardmove < 0); + vfl.right = (vfl.driver && cmd.rightmove < 0); + vfl.left = (vfl.driver && cmd.rightmove > 0); + vfl.strafe = (vfl.driver && cmd.buttons & BUTTON_STRAFE ); + + if ( g_vehicleMode.GetInteger() != 0 ) { + vfl.strafe = !vfl.strafe; + } +} + +/* +================ +rvVehicleWalker::SetInput +================ +*/ +void rvVehicleWalker::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + usercmd_t* pcmd = const_cast( &cmd ); + pcmd->rightmove *= -1; + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +/* +================ +rvVehicleWalker::HandleStrafing +================ +*/ +void rvVehicleWalker::HandleStrafing ( void ) { + if ( vfl.right ) { + additionalDelta -= spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } + if ( vfl.left ) { + additionalDelta += spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } +} + +// mekberg: overloaded this because physics bounds code is significantly different +/* +===================== +rvVehicleWalker::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicleWalker::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, GetPhysics()->GetAxis() ); + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleWalker ) + STATE ( "ForwardLeft", rvVehicleWalker::Frame_ForwardLeft ) + STATE ( "ForwardRight", rvVehicleWalker::Frame_ForwardRight ) + STATE ( "BackwardLeft", rvVehicleWalker::Frame_BackwardLeft ) + STATE ( "BackwardRight", rvVehicleWalker::Frame_BackwardRight ) + + STATE ( "Wait_OnlineAnim", rvVehicleWalker::State_Wait_OnlineAnim ) + + STATE ( "State_Idle", rvVehicleWalker::State_Idle ) + STATE ( "State_IdleThink", rvVehicleWalker::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleWalker::State_IdleOffline ) + STATE ( "State_Offline", rvVehicleWalker::State_Offline ) + STATE ( "State_Online", rvVehicleWalker::State_Online ) + + STATE ( "State_ForwardStart", rvVehicleWalker::State_ForwardStart ) + STATE ( "State_Forward", rvVehicleWalker::State_Forward ) + STATE ( "State_BackwardStart", rvVehicleWalker::State_BackwardStart ) + STATE ( "State_Backward", rvVehicleWalker::State_Backward ) + STATE ( "State_Stop", rvVehicleWalker::State_Stop ) + STATE ( "State_Turn", rvVehicleWalker::State_Turn ) + STATE ( "State_TurnThink", rvVehicleWalker::State_TurnThink ) + STATE ( "State_ScriptedAnim", rvVehicleWalker::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleWalker::State_IdleOffline +================ +*/ +stateResult_t rvVehicleWalker::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle_offline", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Online", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Online +================ +*/ +stateResult_t rvVehicleWalker::State_Online ( const stateParms_t& parms ) { + vfl.frozen = false; + + PlayAnim ( ANIMCHANNEL_LEGS, "start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_OnlineAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 4 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Offline +================ +*/ +stateResult_t rvVehicleWalker::State_Offline ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "stop", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 4 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleThink", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver || vfl.stalled ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Offline", parms.blendFrames ); + return SRESULT_DONE; + } + + if ( IsMovementEnabled ( ) ) { + if ( vfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_ForwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_BackwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.right || vfl.left ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 2 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_ForwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_ForwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "forward_start", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Forward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Forward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.forward ) { + stopAnimName = "forward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 4 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "forward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_BackwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_BackwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "backward_start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Backward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Backward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.backward ) { + stopAnimName = "backward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "backward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_Stop +================ +*/ +stateResult_t rvVehicleWalker::State_Stop ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, stopAnimName, parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Turn +================ +*/ +stateResult_t rvVehicleWalker::State_Turn ( const stateParms_t& parms ) { + if ( vfl.left ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_left", parms.blendFrames ); + } else if ( vfl.right ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_right", parms.blendFrames ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "State_TurnThink", 16 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_TurnThink +================ +*/ +stateResult_t rvVehicleWalker::State_TurnThink ( const stateParms_t& parms ) { + // If moving again bail on the turn, reguardless of whether its in mid animation + if ( vfl.forward || vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + // If the animation is done then repeat + if ( AnimDone ( ANIMCHANNEL_LEGS, 8 ) ){ + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 8 ); + return SRESULT_DONE; + } + + HandleStrafing(); + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::Frame_ForwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardLeft ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_ForwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardRight ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + + +/* +================ +rvVehicleWalker::Frame_BackwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardLeft ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_BackwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardRight ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::State_Wait_OnlineAnim +================ +*/ +stateResult_t rvVehicleWalker::State_Wait_OnlineAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) && vfl.driver ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleWalker::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::Event_ScriptedAnim +================ +*/ +void rvVehicleWalker::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleWalker::Event_ScriptedDone +================ +*/ +void rvVehicleWalker::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleWalker::Event_ScriptedStop +================ +*/ +void rvVehicleWalker::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + } +} diff --git a/source/game/weapon/WeaponBlaster.cpp b/source/game/weapon/WeaponBlaster.cpp new file mode 100644 index 0000000..e6c182b --- /dev/null +++ b/source/game/weapon/WeaponBlaster.cpp @@ -0,0 +1,487 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +#define BLASTER_SPARM_CHARGEGLOW 6 + +class rvWeaponBlaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponBlaster ); + + rvWeaponBlaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + bool UpdateAttack ( void ); + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + int chargeTime; + int chargeDelay; + idVec2 chargeGlow; + bool fireForced; + int fireHeldTime; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Charge ( const stateParms_t& parms ); + stateResult_t State_Charged ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponBlaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponBlaster ) +END_CLASS + +/* +================ +rvWeaponBlaster::rvWeaponBlaster +================ +*/ +rvWeaponBlaster::rvWeaponBlaster ( void ) { +} + +/* +================ +rvWeaponBlaster::UpdateFlashlight +================ +*/ +bool rvWeaponBlaster::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponBlaster::Flashlight +================ +*/ +void rvWeaponBlaster::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +================ +rvWeaponBlaster::UpdateAttack +================ +*/ +bool rvWeaponBlaster::UpdateAttack ( void ) { + // Clear fire forced + if ( fireForced ) { + if ( !wsfl.attack ) { + fireForced = false; + } else { + return false; + } + } + + // If the player is pressing the fire button and they have enough ammo for a shot + // then start the shooting process. + if ( wsfl.attack && gameLocal.time >= nextAttackTime ) { + // Save the time which the fire button was pressed + if ( fireHeldTime == 0 ) { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + fireHeldTime = gameLocal.time; + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + } + } + + // If they have the charge mod and they have overcome the initial charge + // delay then transition to the charge state. + if ( fireHeldTime != 0 ) { + if ( gameLocal.time - fireHeldTime > chargeDelay ) { + SetState ( "Charge", 4 ); + return true; + } + + // If the fire button was let go but was pressed at one point then + // release the shot. + if ( !wsfl.attack ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if( player ) { + + if( player->GuiActive()) { + //make sure the player isn't looking at a gui first + SetState ( "Lower", 0 ); + } else { + SetState ( "Fire", 0 ); + } + } + return true; + } + } + + return false; +} + +/* +================ +rvWeaponBlaster::Spawn +================ +*/ +void rvWeaponBlaster::Spawn ( void ) { + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + SetState ( "Raise", 0 ); + + chargeGlow = spawnArgs.GetVec2 ( "chargeGlow" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargeTime" ) ); + chargeDelay = SEC2MS ( spawnArgs.GetFloat ( "chargeDelay" ) ); + + fireHeldTime = 0; + fireForced = false; + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponBlaster::Save +================ +*/ +void rvWeaponBlaster::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( chargeDelay ); + savefile->WriteVec2 ( chargeGlow ); + savefile->WriteBool ( fireForced ); + savefile->WriteInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::Restore +================ +*/ +void rvWeaponBlaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( chargeDelay ); + savefile->ReadVec2 ( chargeGlow ); + savefile->ReadBool ( fireForced ); + savefile->ReadInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::PreSave +================ +*/ +void rvWeaponBlaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponBlaster::PostSave +================ +*/ +void rvWeaponBlaster::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponBlaster ) + STATE ( "Raise", rvWeaponBlaster::State_Raise ) + STATE ( "Lower", rvWeaponBlaster::State_Lower ) + STATE ( "Idle", rvWeaponBlaster::State_Idle) + STATE ( "Charge", rvWeaponBlaster::State_Charge ) + STATE ( "Charged", rvWeaponBlaster::State_Charged ) + STATE ( "Fire", rvWeaponBlaster::State_Fire ) + STATE ( "Flashlight", rvWeaponBlaster::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponBlaster::State_Raise +================ +*/ +stateResult_t rvWeaponBlaster::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Lower +================ +*/ +stateResult_t rvWeaponBlaster::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Idle +================ +*/ +stateResult_t rvWeaponBlaster::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + if ( UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charge +================ +*/ +stateResult_t rvWeaponBlaster::State_Charge ( const stateParms_t& parms ) { + enum { + CHARGE_INIT, + CHARGE_WAIT, + }; + switch ( parms.stage ) { + case CHARGE_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + StartSound ( "snd_charge", SND_CHANNEL_ITEM, 0, false, NULL ); + PlayCycle( ANIMCHANNEL_ALL, "charging", parms.blendFrames ); + return SRESULT_STAGE ( CHARGE_WAIT ); + + case CHARGE_WAIT: + if ( gameLocal.time - fireHeldTime < chargeTime ) { + float f; + f = (float)(gameLocal.time - fireHeldTime) / (float)chargeTime; + f = chargeGlow[0] + f * (chargeGlow[1] - chargeGlow[0]); + f = idMath::ClampFloat ( chargeGlow[0], chargeGlow[1], f ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, f ); + + if ( !wsfl.attack ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + SetState ( "Charged", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charged +================ +*/ +stateResult_t rvWeaponBlaster::State_Charged ( const stateParms_t& parms ) { + enum { + CHARGED_INIT, + CHARGED_WAIT, + }; + switch ( parms.stage ) { + case CHARGED_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 1.0f ); + + StopSound ( SND_CHANNEL_ITEM, false ); + StartSound ( "snd_charge_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound ( "snd_charge_click", SND_CHANNEL_BODY, 0, false, NULL ); + return SRESULT_STAGE(CHARGED_WAIT); + + case CHARGED_WAIT: + if ( !wsfl.attack ) { + fireForced = true; + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Fire +================ +*/ +stateResult_t rvWeaponBlaster::State_Fire ( const stateParms_t& parms ) { + enum { + FIRE_INIT, + FIRE_WAIT, + }; + switch ( parms.stage ) { + case FIRE_INIT: + + StopSound ( SND_CHANNEL_ITEM, false ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + //don't fire if we're targeting a gui. + idPlayer* player; + player = gameLocal.GetLocalPlayer(); + + //make sure the player isn't looking at a gui first + if( player && player->GuiActive() ) { + fireHeldTime = 0; + SetState ( "Lower", 0 ); + return SRESULT_DONE; + } + + if( player && !player->CanFire() ) { + fireHeldTime = 0; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + + + + if ( gameLocal.time - fireHeldTime > chargeTime ) { + Attack ( true, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_chargedflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "chargedfire", parms.blendFrames ); + } else { + Attack ( false, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_normalflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + fireHeldTime = 0; + + return SRESULT_STAGE(FIRE_WAIT); + + case FIRE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) || UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Flashlight +================ +*/ +stateResult_t rvWeaponBlaster::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/source/game/weapon/WeaponDarkMatterGun.cpp b/source/game/weapon/WeaponDarkMatterGun.cpp new file mode 100644 index 0000000..39fd58b --- /dev/null +++ b/source/game/weapon/WeaponDarkMatterGun.cpp @@ -0,0 +1,470 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../Projectile.h" + +class rvWeaponDarkMatterGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponDarkMatterGun ); + + rvWeaponDarkMatterGun ( void ); + ~rvWeaponDarkMatterGun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + enum darkMatterRing_t { + RING_OUTER, + RING_MIDDLE, + RING_INNER, + RING_MAX + }; + + struct rings_s { + idAngles angularVelocity; + jointHandle_t joint; + }; + rings_s rings[ RING_MAX ]; + + int nextRotateTime; + int ringStartTime; + int chargeDuration; + bool clientReload; + rvClientEffectPtr coreEffect; + rvClientEffectPtr coreStartEffect; + jointHandle_t jointCore; + + void InitRing ( darkMatterRing_t ring, const char* name ); + void StartRings ( bool chargeUp ); + void StopRings ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponDarkMatterGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponDarkMatterGun ) +END_CLASS + +/* +================ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun ( void ) { + coreStartEffect = NULL; + coreEffect = NULL; + ringStartTime = -1; + clientReload = false; +} + +/* +================ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun ( void ) { + StopRings ( ); +} + +/* +================ +rvWeaponDarkMatterGun::Spawn +================ +*/ +void rvWeaponDarkMatterGun::Spawn ( void ) { + SetState ( "Raise", 0 ); + + InitRing ( RING_OUTER, "outer" ); + InitRing ( RING_INNER, "inner" ); + InitRing ( RING_MIDDLE, "middle" ); + + nextRotateTime = 0; + + chargeDuration = SEC2MS ( spawnArgs.GetFloat ( "chargeDuration", ".5" ) ); + + jointCore = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_core" ) ); +} + +/* +================ +rvWeaponDarkMatterGun::Save +================ +*/ +void rvWeaponDarkMatterGun::Save ( idSaveGame *savefile ) const { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->WriteAngles ( rings[ i ].angularVelocity ); + savefile->WriteJoint ( rings[ i ].joint ); + } + savefile->WriteInt ( nextRotateTime ); + savefile->WriteInt ( ringStartTime ); + savefile->WriteInt ( chargeDuration ); + savefile->WriteObject( coreEffect.GetEntity() ); + savefile->WriteObject( coreStartEffect.GetEntity() ); + savefile->WriteJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::Restore +================ +*/ +void rvWeaponDarkMatterGun::Restore ( idRestoreGame *savefile ) { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->ReadAngles ( rings[ i ].angularVelocity ); + savefile->ReadJoint ( rings[ i ].joint ); + } + savefile->ReadInt ( nextRotateTime ); + savefile->ReadInt ( ringStartTime ); + savefile->ReadInt ( chargeDuration ); + savefile->ReadObject( reinterpret_cast( coreEffect ) ); + savefile->ReadObject( reinterpret_cast( coreStartEffect ) ); + savefile->ReadJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::PreSave +================ +*/ +void rvWeaponDarkMatterGun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + +} + +/* +================ +rvWeaponDarkMatterGun::PostSave +================ +*/ +void rvWeaponDarkMatterGun::PostSave ( void ) { + + //start the ring sounds + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); +} + + +/* +================ +rvWeaponDarkMatterGun::InitRing +================ +*/ +void rvWeaponDarkMatterGun::InitRing ( darkMatterRing_t ring, const char* name ) { + rings[ring].angularVelocity = spawnArgs.GetAngles ( va("ring_%s_velocity", name ) ); + rings[ring].joint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("ring_%s_joint", name ) ) ); +} + +/* +================ +rvWeaponDarkMatterGun::StartRings +================ +*/ +void rvWeaponDarkMatterGun::StartRings ( bool chargeUp ) { + int i; + + if ( ringStartTime == -1 ) { + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); + ringStartTime = gameLocal.time; + } + + if ( chargeUp ) { + coreStartEffect = viewModel->PlayEffect( "fx_core_start", jointCore ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, chargeDuration / 2 ); + } + } else if ( !coreEffect ) { + coreEffect = viewModel->PlayEffect( "fx_core", jointCore, true ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, 0 ); + } + } +} + +/* +================ +rvWeaponDarkMatterGun::StopRings +================ +*/ +void rvWeaponDarkMatterGun::StopRings ( void ) { + int i; + + if ( !viewModel ) { + return; + } + + viewModel->StopSound ( SND_CHANNEL_VOICE, false ); + + if ( coreEffect ) { + coreEffect->Stop ( ); + coreEffect = NULL; + } + + if ( coreStartEffect ) { + coreStartEffect->Stop ( ); + coreStartEffect = NULL; + } + + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->ClearJoint ( rings[i].joint ); + } + + ringStartTime = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponDarkMatterGun ) + STATE ( "Idle", rvWeaponDarkMatterGun::State_Idle) + STATE ( "Fire", rvWeaponDarkMatterGun::State_Fire ) + STATE ( "Reload", rvWeaponDarkMatterGun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponDarkMatterGun::State_Idle +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + // Auto reload? + if ( !AmmoInClip ( ) && AmmoAvailable () && !clientReload ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + clientReload = false; + + StartRings ( false ); + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload ) { + if ( owner->entityNumber != gameLocal.localClientNum ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } else { + wsfl.netReload = false; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Fire +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopRings ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 2 ) || (gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Reload +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + StartRings ( true ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + clientReload = true; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + AddToClip ( ClipSize() ); + SetStatus ( WP_READY ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + rvDarkMatterProjectile + +=============================================================================== +*/ + +class rvDarkMatterProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDarkMatterProjectile ); + + rvDarkMatterProjectile ( void ); + ~rvDarkMatterProjectile ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + +protected: + + int nextDamageTime; + const idDict* radiusDamageDef; +}; + +CLASS_DECLARATION( idProjectile, rvDarkMatterProjectile ) +END_CLASS + +/* +================ +rvDarkMatterProjectile::rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::rvDarkMatterProjectile ( void ) { + radiusDamageDef = NULL; +} + +/* +================ +rvDarkMatterProjectile::~rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::~rvDarkMatterProjectile ( void ) { +} + +/* +================ +rvDarkMatterProjectile::Spawn +================ +*/ +void rvDarkMatterProjectile::Spawn ( void ) { + nextDamageTime = 0; + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Save +================ +*/ +void rvDarkMatterProjectile::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( nextDamageTime ); +} + +/* +================ +rvDarkMatterProjectile::Restore +================ +*/ +void rvDarkMatterProjectile::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( nextDamageTime ); + + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Think +================ +*/ +void rvDarkMatterProjectile::Think ( void ) { + physicsObj.SetClipMask( MASK_DMGSOLID ); + idProjectile::Think ( ); + + if ( gameLocal.time > nextDamageTime ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, owner, owner, NULL, spawnArgs.GetString( "def_radius_damage" ), 1.0f, &hitCount ); + nextDamageTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "damageRate", ".05" ) ); + } +} + + diff --git a/source/game/weapon/WeaponGauntlet.cpp b/source/game/weapon/WeaponGauntlet.cpp new file mode 100644 index 0000000..239739f --- /dev/null +++ b/source/game/weapon/WeaponGauntlet.cpp @@ -0,0 +1,514 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGauntlet : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGauntlet ); + + rvWeaponGauntlet( void ); + ~rvWeaponGauntlet( void ); + + virtual void Spawn ( void ); + virtual void CleanupWeapon ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idAngles bladeSpinFast; + idAngles bladeSpinSlow; + jointHandle_t bladeJoint; + jointHandle_t bladeJoint_world; + int bladeAccel; + + float range; + + rvClientEffectPtr impactEffect; + int impactMaterial; + + void Attack ( void ); + void StartBlade ( void ); + void StopBlade ( void ); + +private: + + void PlayLoopSound ( int sndType ); + int loopSound; + enum { + LOOP_NONE, + LOOP_WALL, + LOOP_FLESH + }; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponGauntlet ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGauntlet ) +END_CLASS + +/* +================ +rvWeaponGauntlet::rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::rvWeaponGauntlet( void ) { + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::~rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::~rvWeaponGauntlet(void) +{ + if ( viewModel ) { + StopSound( SND_CHANNEL_WEAPON, false ); + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + +} +/* +================ +rvWeaponGauntlet::Spawn +================ +*/ +void rvWeaponGauntlet::Spawn ( void ) { + SetState ( "Raise", 0 ); + + bladeJoint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + bladeJoint_world= GetWorldModel()->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + + bladeSpinFast = spawnArgs.GetAngles ( "blade_spinfast" ); + bladeSpinSlow = spawnArgs.GetAngles ( "blade_spinslow" ); + bladeAccel = SEC2MS ( spawnArgs.GetFloat ( "blade_accel", ".25" ) ); + + range = spawnArgs.GetFloat ( "range", "32" ); + + impactMaterial = -1; + impactEffect = NULL; + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::Save +================ +*/ +void rvWeaponGauntlet::Save ( idSaveGame *savefile ) const { + savefile->WriteAngles ( bladeSpinFast ); + savefile->WriteAngles ( bladeSpinSlow ); + savefile->WriteJoint ( bladeJoint ); + savefile->WriteJoint ( bladeJoint_world ); + + savefile->WriteInt ( bladeAccel ); + + savefile->WriteFloat ( range ); + + savefile->WriteObject ( impactEffect ); + savefile->WriteInt ( impactMaterial ); + savefile->WriteInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::Restore +================ +*/ +void rvWeaponGauntlet::Restore ( idRestoreGame *savefile ) { + savefile->ReadAngles ( bladeSpinFast ); + savefile->ReadAngles ( bladeSpinSlow ); + savefile->ReadJoint ( bladeJoint ); + savefile->ReadJoint ( bladeJoint_world ); + + savefile->ReadInt ( bladeAccel ); + + savefile->ReadFloat ( range ); + + savefile->ReadObject ( reinterpret_cast( impactEffect ) ); + savefile->ReadInt ( impactMaterial ); + savefile->ReadInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::PreSave +================ +*/ +void rvWeaponGauntlet::PreSave ( void ) { + +} + +/* +================ +rvWeaponGauntlet::PostSave +================ +*/ +void rvWeaponGauntlet::PostSave( void ) { +} + +void rvWeaponGauntlet::PlayLoopSound( int sndType ) { + if ( loopSound == sndType ) { + return; + } + const char *loopSoundString = NULL; + switch ( sndType ) { + case LOOP_NONE: + default: + loopSoundString = "snd_spin_loop"; + break; + case LOOP_WALL: + loopSoundString = "snd_spin_wall"; + break; + case LOOP_FLESH: + loopSoundString = "snd_spin_flesh"; + break; + } + if ( loopSoundString ) { + loopSound = sndType; + StartSound( loopSoundString, SND_CHANNEL_WEAPON, 0, false, 0 ); + } +} + +/* +================ +rvWeaponGauntlet::CleanupWeapon +================ +*/ +void rvWeaponGauntlet::CleanupWeapon( void ) { + + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); +} + +/* +================ +rvWeaponGauntlet::Attack +================ +*/ +void rvWeaponGauntlet::Attack ( void ) { + trace_t tr; + idEntity* ent; + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + owner->WeaponFireFeedback( &weaponDef->dict ); + + if ( tr.fraction >= 1.0f ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); + return; + } + + // Entity we hit? + ent = gameLocal.entities[tr.c.entityNum]; + + // If the impact material changed then stop the impact effect + if ( (tr.c.materialType && tr.c.materialType->Index ( ) != impactMaterial) || + (!tr.c.materialType && impactMaterial != -1) ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + } + + // In singleplayer-- the gauntlet never effects marine AI + if( !gameLocal.isMultiplayer ) { + idActor* actor_ent = 0; + + //ignore both the body and the head. + if (ent->IsType( idActor::GetClassType()) ) { + actor_ent = static_cast(ent); + } else if (ent->IsType ( idAFAttachment::GetClassType()) ) { + actor_ent = static_cast(ent->GetBindMaster()); + } + + if ( actor_ent && actor_ent->team == gameLocal.GetLocalPlayer()->team ) { + PlayLoopSound( LOOP_NONE ); + return; + } + } + + //multiplayer-- don't gauntlet dead stuff + if( gameLocal.isMultiplayer ) { + idPlayer * player; + if ( ent->IsType( idPlayer::GetClassType() )) { + player = static_cast< idPlayer* >(ent); + if (player->health <= 0) { + return; + } + } + + } + + if ( !impactEffect ) { + impactMaterial = tr.c.materialType ? tr.c.materialType->Index() : -1; + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_impact", tr.c.materialType ), tr.endpos, tr.c.normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( tr.endpos ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + } + + // Do damage? + if ( gameLocal.time > nextAttackTime ) { + if ( ent ) { + if ( ent->fl.takedamage ) { + float dmgScale = 1.0f; + dmgScale *= owner->PowerUpModifier( PMOD_MELEE_DAMAGE ); + ent->Damage ( owner, owner, playerViewAxis[0], spawnArgs.GetString ( "def_damage" ), dmgScale, 0 ); + StartSound( "snd_hit", SND_CHANNEL_ANY, 0, false, NULL ); + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + PlayLoopSound( LOOP_FLESH ); + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_NONE ); + } + nextAttackTime = gameLocal.time + fireRate; + } +} + +/* +================ +rvWeaponGauntlet::StartBlade +================ +*/ +void rvWeaponGauntlet::StartBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_ITEM, false ); +// StartSound ( "snd_blade_fast", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_spin_up", SND_CHANNEL_ITEM, 0, false, 0 ); +} + +/* +================ +rvWeaponGauntlet::StopBlade +================ +*/ +void rvWeaponGauntlet::StopBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_WEAPON, false ); +// StartSound ( "snd_blade_slow", SND_CHANNEL_ITEM, 0, false, NULL ); + + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGauntlet ) + STATE ( "Raise", rvWeaponGauntlet::State_Raise ) + STATE ( "Lower", rvWeaponGauntlet::State_Lower ) + STATE ( "Idle", rvWeaponGauntlet::State_Idle) + STATE ( "Fire", rvWeaponGauntlet::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponGauntlet::State_Raise +================ +*/ +stateResult_t rvWeaponGauntlet::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Lower +================ +*/ +stateResult_t rvWeaponGauntlet::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "lower", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Idle +================ +*/ +stateResult_t rvWeaponGauntlet::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + StopBlade ( ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Fire +================ +*/ +stateResult_t rvWeaponGauntlet::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_ALL, "attack_start", parms.blendFrames ); + StartBlade ( ); + loopSound = LOOP_NONE; + return SRESULT_STAGE(STAGE_START_WAIT); + + case STAGE_START_WAIT: + if ( !wsfl.attack ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_ALL, "attack_loop", parms.blendFrames ); + StartSound( "snd_spin_loop", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE(STAGE_LOOP_WAIT); + + case STAGE_LOOP_WAIT: + if ( !wsfl.attack || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_END ); + } + Attack ( ); + return SRESULT_WAIT; + + case STAGE_END: + PlayAnim ( ANIMCHANNEL_ALL, "attack_end", parms.blendFrames ); + StopBlade ( ); + StartSound( "snd_spin_down", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( wsfl.attack || AnimDone ( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/game/weapon/WeaponGrenadeLauncher.cpp b/source/game/weapon/WeaponGrenadeLauncher.cpp new file mode 100644 index 0000000..c3ae03c --- /dev/null +++ b/source/game/weapon/WeaponGrenadeLauncher.cpp @@ -0,0 +1,202 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGrenadeLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGrenadeLauncher ); + + rvWeaponGrenadeLauncher ( void ); + + virtual void Spawn ( void ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + const char* GetFireAnim() const { return (!AmmoInClip()) ? "fire_empty" : "fire"; } + const char* GetIdleAnim() const { return (!AmmoInClip()) ? "idle_empty" : "idle"; } + + CLASS_STATES_PROTOTYPE ( rvWeaponGrenadeLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGrenadeLauncher ) +END_CLASS + +/* +================ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher +================ +*/ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::Spawn +================ +*/ +void rvWeaponGrenadeLauncher::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponGrenadeLauncher::PreSave +================ +*/ +void rvWeaponGrenadeLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::PostSave +================ +*/ +void rvWeaponGrenadeLauncher::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGrenadeLauncher ) + STATE ( "Idle", rvWeaponGrenadeLauncher::State_Idle) + STATE ( "Fire", rvWeaponGrenadeLauncher::State_Fire ) + STATE ( "Reload", rvWeaponGrenadeLauncher::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponGrenadeLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, GetIdleAnim(), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, GetFireAnim(), 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/game/weapon/WeaponHyperblaster.cpp b/source/game/weapon/WeaponHyperblaster.cpp new file mode 100644 index 0000000..661b6d2 --- /dev/null +++ b/source/game/weapon/WeaponHyperblaster.cpp @@ -0,0 +1,295 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int HYPERBLASTER_SPARM_BATTERY = 6; +const int HYPERBLASTER_SPIN_SPEED = 300; + +class rvWeaponHyperblaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponHyperblaster ); + + rvWeaponHyperblaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + jointHandle_t jointBatteryView; + bool spinning; + + void SpinUp ( void ); + void SpinDown ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponHyperblaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponHyperblaster ) +END_CLASS + +/* +================ +rvWeaponHyperblaster::rvWeaponHyperblaster +================ +*/ +rvWeaponHyperblaster::rvWeaponHyperblaster ( void ) { +} + +/* +================ +rvWeaponHyperblaster::Spawn +================ +*/ +void rvWeaponHyperblaster::Spawn ( void ) { + jointBatteryView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_battery" ) ); + spinning = false; + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponHyperblaster::Save +================ +*/ +void rvWeaponHyperblaster::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint ( jointBatteryView ); + savefile->WriteBool ( spinning ); +} + +/* +================ +rvWeaponHyperblaster::Restore +================ +*/ +void rvWeaponHyperblaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint ( jointBatteryView ); + savefile->ReadBool ( spinning ); +} + +/* +================ +rvWeaponHyperBlaster::PreSave +================ +*/ +void rvWeaponHyperblaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, false ); + StopSound( SND_CHANNEL_BODY, false ); + StopSound( SND_CHANNEL_ITEM, false ); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponHyperBlaster::PostSave +================ +*/ +void rvWeaponHyperblaster::PostSave ( void ) { +} + +/* +================ +rvWeaponHyperblaster::SpinUp +================ +*/ +void rvWeaponHyperblaster::SpinUp ( void ) { + if ( spinning ) { + return; + } + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,HYPERBLASTER_SPIN_SPEED,0), gameLocal.time, 50 ); + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spin", SND_CHANNEL_BODY2, 0, false, NULL ); + spinning = true; +} + +/* +================ +rvWeaponHyperblaster::SpinDown +================ +*/ +void rvWeaponHyperblaster::SpinDown ( void ) { + if ( !spinning ) { + return; + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spindown", SND_CHANNEL_BODY2, 0, false, NULL ); + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,0,0), gameLocal.time, 500 ); + } + + spinning = false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponHyperblaster ) + STATE ( "Idle", rvWeaponHyperblaster::State_Idle) + STATE ( "Fire", rvWeaponHyperblaster::State_Fire ) + STATE ( "Reload", rvWeaponHyperblaster::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponHyperblaster::State_Idle +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + SpinDown ( ); + + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Fire +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SpinUp ( ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( (!wsfl.attack || !AmmoInClip() || wsfl.lowerWeapon) && AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Reload +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SpinDown ( ); + + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 0 ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/game/weapon/WeaponLightningGun.cpp b/source/game/weapon/WeaponLightningGun.cpp new file mode 100644 index 0000000..f96c0f3 --- /dev/null +++ b/source/game/weapon/WeaponLightningGun.cpp @@ -0,0 +1,973 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" +#include "../ai/AI_Manager.h" + +const int LIGHTNINGGUN_NUM_TUBES = 3; +const int LIGHTNINGGUN_MAX_PATHS = 3; + +const idEventDef EV_Lightninggun_RestoreHum( "", "" ); + +class rvLightningPath { +public: + idEntityPtr target; + idVec3 origin; + idVec3 normal; + rvClientEffectPtr trailEffect; + rvClientEffectPtr impactEffect; + + void StopEffects ( void ); + void UpdateEffects ( const idVec3& from, const idDict& dict ); + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvWeaponLightningGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponLightningGun ); + + rvWeaponLightningGun( void ); + ~rvWeaponLightningGun( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + virtual void ClientStale ( void ); + + void PreSave ( void ); + void PostSave ( void ); + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + bool NoFireWhileSwitching( void ) const { return true; } + +protected: + + void UpdateTubes ( void ); + + // Tube effects + rvClientEntityPtr tubeEffects[LIGHTNINGGUN_NUM_TUBES]; + idInterpolate tubeOffsets[LIGHTNINGGUN_NUM_TUBES]; + jointHandle_t tubeJoints[LIGHTNINGGUN_NUM_TUBES]; + float tubeMaxOffset; + float tubeThreshold; + int tubeTime; + + rvClientEntityPtr trailEffectView; + + int nextCrawlTime; + + float range; + jointHandle_t spireJointView; + jointHandle_t chestJointView; + + + rvLightningPath currentPath; + + // Chain lightning mod + idList chainLightning; + idVec3 chainLightningRange; + +private: + + void Attack ( idEntity* ent, const idVec3& dir, float power = 1.0f ); + + void UpdateChainLightning ( void ); + void StopChainLightning ( void ); + void UpdateEffects ( const idVec3& origin ); + void UpdateTrailEffect ( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view = false ); + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponLightningGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponLightningGun ) +EVENT( EV_Lightninggun_RestoreHum, rvWeaponLightningGun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponLightningGun::rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::rvWeaponLightningGun( void ) { +} + +/* +================ +rvWeaponLightningGun::~rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::~rvWeaponLightningGun( void ) { + int i; + + if ( trailEffectView ) { + trailEffectView->Stop( ); + } + currentPath.StopEffects( ); + StopChainLightning( ); + + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop( ); + } + } +} + +/* +================ +rvWeaponLightningGun::Spawn +================ +*/ +void rvWeaponLightningGun::Spawn( void ) { + int i; + + trailEffectView = NULL; + nextCrawlTime = 0; + + chainLightning.Clear( ); + + // get hitscan range for our firing + range = weaponDef->dict.GetFloat( "range", "10000" ); + + // Initialize tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeJoints[i] = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint_tube_%d",i), "" ) ); + tubeOffsets[i].Init ( gameLocal.time, 0, 0, 0 ); + } + + // Cache the max ammo for the weapon and the max tube offset + tubeMaxOffset = spawnArgs.GetFloat ( "tubeoffset" ); + tubeThreshold = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ) / (float)LIGHTNINGGUN_NUM_TUBES; + tubeTime = SEC2MS ( spawnArgs.GetFloat ( "tubeTime", ".25" ) ); + + spireJointView = viewModel->GetAnimator ( )->GetJointHandle ( "spire_1" ); + + if( gameLocal.GetLocalPlayer()) { + chestJointView = gameLocal.GetLocalPlayer()->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_hideGun_flash" ) ); + } else { + chestJointView = spireJointView; + } + + chainLightningRange = spawnArgs.GetVec2( "chainLightningRange", "150 300" ); + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponLightningGun::Save +================ +*/ +void rvWeaponLightningGun::Save ( idSaveGame* savefile ) const { + int i; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Save ( savefile ); + savefile->WriteInterpolate ( tubeOffsets[i] ); + savefile->WriteJoint ( tubeJoints[i] ); + } + savefile->WriteFloat ( tubeMaxOffset ); + savefile->WriteFloat ( tubeThreshold ); + savefile->WriteInt ( tubeTime ); + + // General + trailEffectView.Save ( savefile ); + savefile->WriteInt ( nextCrawlTime ); + savefile->WriteFloat ( range ); + savefile->WriteJoint ( spireJointView ); + savefile->WriteJoint ( chestJointView ); + + currentPath.Save ( savefile ); + + // Chain Lightning mod + savefile->WriteInt ( chainLightning.Num() ); + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].Save ( savefile ); + } + savefile->WriteVec3 ( chainLightningRange ); +} + +/* +================ +rvWeaponLightningGun::Restore +================ +*/ +void rvWeaponLightningGun::Restore ( idRestoreGame* savefile ) { + int i; + int num; + float f; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Restore ( savefile ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartTime ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetDuration ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartValue ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetEndValue ( f ); + savefile->ReadJoint ( tubeJoints[i] ); + } + savefile->ReadFloat ( tubeMaxOffset ); + savefile->ReadFloat ( tubeThreshold ); + savefile->ReadInt ( tubeTime ); + + // General + trailEffectView.Restore ( savefile ); + + savefile->ReadInt ( nextCrawlTime ); + savefile->ReadFloat ( range ); + savefile->ReadJoint ( spireJointView ); + savefile->ReadJoint ( chestJointView ); + + currentPath.Restore ( savefile ); + + // Chain lightning mod + savefile->ReadInt ( num ); + chainLightning.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + chainLightning[i].Restore ( savefile ); + } + savefile->ReadVec3 ( chainLightningRange ); + + +} + +/* +================ +rvWeaponLightningGun::Think +================ +*/ +void rvWeaponLightningGun::Think ( void ) { + trace_t tr; + + rvWeapon::Think(); + + UpdateTubes(); + + // If no longer firing or out of ammo then nothing to do in the think + if ( !wsfl.attack || !IsReady() || !AmmoAvailable() ) { + if ( trailEffectView ) { + trailEffectView->Stop ( ); + trailEffectView = NULL; + } + + currentPath.StopEffects(); + StopChainLightning(); + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// jshepard: allow projectile hits + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + (MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE), owner ); +// RAVEN END + // Calculate the direction of the lightning effect using the barrel joint of the weapon + // and the end point of the trace + idVec3 origin; + idMat3 axis; + + //fire from chest if show gun models is off. + if( !cvarSystem->GetCVarBool("ui_showGun")) { + GetGlobalJointTransform( true, chestJointView, origin, axis ); + } else { + GetGlobalJointTransform( true, barrelJointView, origin, axis ); + } + + // Cache the target we are hitting + currentPath.origin = tr.endpos; + currentPath.normal = tr.c.normal; + currentPath.target = gameLocal.entities[tr.c.entityNum]; + + UpdateChainLightning(); + + UpdateEffects( origin ); + + MuzzleFlash(); + + // Inflict damage on all targets being attacked + if ( !gameLocal.isClient && gameLocal.time >= nextAttackTime ) { + int i; + float power = 1.0f; + idVec3 dir; + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + + dir = tr.endpos - origin; + dir.Normalize ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( currentPath.target, dir, power ); + for ( i = 0; i < chainLightning.Num(); i ++, power *= 0.75f ) { + Attack ( chainLightning[i].target, chainLightning[i].normal, power ); + } + + statManager->WeaponFired( owner, owner->GetCurrentWeapon(), chainLightning.Num() + 1 ); + } + + // Play the lightning crawl effect every so often when doing damage + if ( gameLocal.time > nextCrawlTime ) { + nextCrawlTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat ( "crawlDelay", ".3" )); + } +} + +/* +================ +rvWeaponLightningGun::Attack +================ +*/ +void rvWeaponLightningGun::Attack ( idEntity* ent, const idVec3& dir, float power ) { + // Double check + if ( !ent || !ent->fl.takedamage ) { + return; + } + + // Start a lightning crawl effect every so often + // we don't synchronize it, so let's not show it in multiplayer for a listen host. also fixes seeing it on the host from other instances + if ( !gameLocal.isMultiplayer && gameLocal.time > nextCrawlTime ) { + if ( ent->IsType( idActor::GetClassType() ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( weaponDef->dict, "fx_crawl" ), ent, SEC2MS( spawnArgs.GetFloat ( "crawlTime", ".2" ) ) ); + effect->Play( gameLocal.time, false ); + } + } + +// RAVEN BEGIN +// mekberg: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, owner->GetCurrentWeapon() ); + } +// RAVEN END + ent->Damage( owner, owner, dir, spawnArgs.GetString ( "def_damage" ), power * owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ), 0 ); +} + +/* +================ +rvWeaponLightningGun::UpdateChainLightning +================ +*/ +void rvWeaponLightningGun::UpdateChainLightning ( void ) { + int i; + rvLightningPath* parent; + rvLightningPath* path; + idActor* target; + + // Chain lightning not enabled + if ( !chainLightningRange[0] ) { + return; + } + + // Need to have a primary target that is not on the same team for chain lightning to work + path = ¤tPath; + target = dynamic_cast(path->target.GetEntity()); + if ( !target || !target->health || target->team == owner->team ) { + StopChainLightning ( ); + return; + } + + currentPath.target->fl.takedamage = false; + + // Look through the chain lightning list and remove any paths that are no longer valid due + // to their range or visibility + for ( i = 0; i < chainLightning.Num(); i ++ ) { + parent = path; + path = &chainLightning[i]; + target = dynamic_cast(path->target.GetEntity()); + + // If the entity isnt valid anymore or is dead then remove it from the list + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Choose a destination origin the chain lightning path target + path->origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + path->origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); + path->normal = path->origin - parent->origin; + path->normal.Normalize(); + + // Make sure the entity is still within range of its parent + if ( (path->origin - parent->origin).LengthSqr ( ) > Square ( chainLightningRange[1] ) ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Trace to make sure we can still hit them + trace_t tr; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, parent->origin, path->origin, MASK_SHOT_RENDERMODEL, parent->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + path->origin = tr.endpos; + + // Temporarily disable taking damage to flag this entity is used + target->fl.takedamage = false; + } + + // Start path at the end of the current path + if ( chainLightning.Num () ) { + path = &chainLightning[chainLightning.Num()-1]; + } else { + path = ¤tPath; + } + + // Cap the number of chain lightning jumps + while ( chainLightning.Num ( ) + 1 < LIGHTNINGGUN_MAX_PATHS ) { + for ( target = aiManager.GetEnemyTeam ( (aiTeam_t)owner->team ); target; target = target->teamNode.Next() ) { + // Must be a valid entity that takes damage to chain lightning too + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + continue; + } + // Must be within starting chain path range + if ( (target->GetPhysics()->GetOrigin() - path->target->GetPhysics()->GetOrigin()).LengthSqr() > Square ( chainLightningRange[0] ) ) { + continue; + } + + // Make sure we can trace to the target from the current path + trace_t tr; + idVec3 origin; + origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, path->origin, origin, MASK_SHOT_RENDERMODEL, path->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + continue; + } + + path = &chainLightning.Alloc ( ); + path->target = target; + path->normal = tr.endpos - path->origin; + path->normal.Normalize(); + path->origin = tr.endpos; + + // Flag this entity to ensure it is skipped + target->fl.takedamage = false; + break; + } + // Found nothing? just break out early + if ( !target ) { + break; + } + } + + // Reset the take damage flag + currentPath.target->fl.takedamage = true; + for ( i = chainLightning.Num() - 1; i >= 0; i -- ) { + chainLightning[i].target->fl.takedamage = true; + } +} + +/* +================ +rvWeaponLightningGun::UpdateEffects +================ +*/ +void rvWeaponLightningGun::UpdateEffects( const idVec3& origin ) { + int i; + rvLightningPath* parent; + idVec3 dir; + + // Main path (world effects) + currentPath.UpdateEffects ( origin, weaponDef->dict ); + if ( currentPath.trailEffect ) { + currentPath.trailEffect->GetRenderEffect()->suppressSurfaceInViewID = owner->entityNumber + 1; + } + + // In view trail effect + dir = currentPath.origin - origin; + dir.Normalize(); + if ( !trailEffectView ) { + trailEffectView = gameLocal.PlayEffect ( gameLocal.GetEffect ( weaponDef->dict, "fx_trail" ), origin, dir.ToMat3(), true, currentPath.origin ); + } else { + trailEffectView->SetOrigin( origin ); + trailEffectView->SetAxis( dir.ToMat3() ); + trailEffectView->SetEndOrigin( currentPath.origin ); + } + if ( trailEffectView ) { + trailEffectView->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;; + } + + if ( !currentPath.target ) { + return; + } + + // Chain lightning effects + parent = ¤tPath; + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].UpdateEffects( parent->origin, weaponDef->dict ); + parent = &chainLightning[i]; + } +} + +/* +================ +rvWeaponLightningGun::UpdateTrailEffect +================ +*/ +void rvWeaponLightningGun::UpdateTrailEffect( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view ) { + idVec3 dir; + dir = end - start; + dir.Normalize(); + + if ( !effect ) { + effect = gameLocal.PlayEffect( gameLocal.GetEffect( weaponDef->dict, view ? "fx_trail" : "fx_trail_world" ), start, dir.ToMat3(), true, end ); + } else { + effect->SetOrigin( start ); + effect->SetAxis( dir.ToMat3() ); + effect->SetEndOrigin( end ); + } +} + +/* +================ +rvWeaponLightningGun::StopChainLightning +================ +*/ +void rvWeaponLightningGun::StopChainLightning( void ) { + int i; + + if ( !chainLightning.Num( ) ) { + return; + } + + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].StopEffects( ); + } + + chainLightning.Clear( ); +} + +/* +================ +rvWeaponLightningGun::ClientStale +================ +*/ +void rvWeaponLightningGun::ClientStale( void ) { + rvWeapon::ClientStale( ); + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + + currentPath.StopEffects( ); +} + +/* +================ +rvWeaponLightningGun::PreSave +================ +*/ +void rvWeaponLightningGun::PreSave( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_BODY2, 0); + StopSound( SND_CHANNEL_BODY3, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + + viewModel->StopSound( SND_CHANNEL_ANY, false ); + viewModel->StopSound( SND_CHANNEL_BODY, 0); + + worldModel->StopSound( SND_CHANNEL_ANY, false ); + worldModel->StopSound( SND_CHANNEL_BODY, 0); + + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + for ( int i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Event_Remove(); + } + } + currentPath.StopEffects( ); + +} + +/* +================ +rvWeaponLightningGun::PostSave +================ +*/ +void rvWeaponLightningGun::PostSave( void ) { + //restore the humming + PostEventMS( &EV_Lightninggun_RestoreHum, 10 ); +} + +/* +================ +rvWeaponLightningGun::UpdateTubes +================ +*/ +void rvWeaponLightningGun::UpdateTubes( void ) { + idAnimator* animator; + animator = viewModel->GetAnimator ( ); + assert ( animator ); + + int i; + float ammo; + + ammo = AmmoAvailable ( ); + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + float offset; + if ( ammo > tubeThreshold * i ) { + offset = tubeMaxOffset; + + if ( !tubeEffects[i] ) { + tubeEffects[i] = viewModel->PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_tube" ), tubeJoints[i], vec3_origin, mat3_identity, true ); + if( tubeEffects[i] ) { + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } else { + offset = 0; + + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop ( ); + tubeEffects[i] = NULL; + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + // Attenuate the tube effect to how much ammo is left in the tube + if ( tubeEffects[i] ) { + tubeEffects[i]->Attenuate ( (ammo - tubeThreshold * (float)i) / tubeThreshold ); + } + + if ( offset > tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (1.0f - (current/tubeMaxOffset)) * (float)tubeTime, current, offset ); + } else if ( offset < tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (current/tubeMaxOffset) * (float)tubeTime, current, offset ); + } + + animator->SetJointPos ( tubeJoints[i], JOINTMOD_LOCAL, idVec3(tubeOffsets[i].GetCurrentValue(gameLocal.time),0,0) ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponLightningGun ) + STATE ( "Raise", rvWeaponLightningGun::State_Raise ) + STATE ( "Lower", rvWeaponLightningGun::State_Lower ) + STATE ( "Idle", rvWeaponLightningGun::State_Idle) + STATE ( "Fire", rvWeaponLightningGun::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponLightningGun::State_Raise +================ +*/ +stateResult_t rvWeaponLightningGun::State_Raise( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus( WP_RISING ); + viewModel->SetShaderParm( 6, 1 ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + // Wait for the weapon to finish raising and allow it to be cancelled by + // lowering the weapon + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Lower +================ +*/ +stateResult_t rvWeaponLightningGun::State_Lower( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Idle +================ +*/ +stateResult_t rvWeaponLightningGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time > nextAttackTime && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Fire +================ +*/ +stateResult_t rvWeaponLightningGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACKLOOP, + STAGE_DONE, + STAGE_DONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + StartSound( "snd_fire_stereo", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_fire_loop", SND_CHANNEL_BODY2, 0, false, NULL ); + + viewModel->SetShaderParm( 6, 0 ); + + viewModel->PlayEffect( "fx_spire", spireJointView, true ); + viewModel->PlayEffect( "fx_flash", barrelJointView, true ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict,"fx_flash_world"), flashJointWorld, vec3_origin, mat3_identity, true ); + } + + PlayAnim( ANIMCHANNEL_ALL, "shoot_start", parms.blendFrames ); + return SRESULT_STAGE( STAGE_ATTACKLOOP ); + + case STAGE_ATTACKLOOP: + if ( !wsfl.attack || wsfl.lowerWeapon || !AmmoAvailable ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + PlayCycle( ANIMCHANNEL_ALL, "shoot_loop", 0 ); + if ( !gameLocal.isMultiplayer + && owner == gameLocal.GetLocalPlayer() ) { + owner->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + 500), 2.0f ); + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + StopSound( SND_CHANNEL_BODY2, false ); + + viewModel->StopEffect( "fx_spire" ); + viewModel->StopEffect( "fx_flash" ); + if ( worldModel ) { + worldModel->StopEffect( gameLocal.GetEffect( weaponDef->dict, "fx_flash_world" ) ); + } + viewModel->SetShaderParm( 6, 1 ); + + PlayAnim( ANIMCHANNEL_ALL, "shoot_end", 0 ); + return SRESULT_STAGE( STAGE_DONEWAIT ); + + case STAGE_DONEWAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( !wsfl.lowerWeapon && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponLightningGun::Event_RestoreHum +================ +*/ +void rvWeaponLightningGun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); +} + +/* +=============================================================================== + + rvLightningPath + +=============================================================================== +*/ + +/* +================ +rvLightningPath::StopEffects +================ +*/ +void rvLightningPath::StopEffects( void ) { + if ( trailEffect ) { + trailEffect->Stop( ); + trailEffect->Event_Remove( ); + trailEffect = NULL; + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } +} + +/* +================ +rvLightningPath::UpdateEffects +================ +*/ +void rvLightningPath::UpdateEffects ( const idVec3& from, const idDict& dict ) { + idVec3 dir; + dir = origin - from; + dir.Normalize(); + + // Trail effect + if ( !trailEffect ) { + trailEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_trail_world" ), from, dir.ToMat3(), true, origin ); + } else { + trailEffect->SetOrigin ( from ); + trailEffect->SetAxis ( dir.ToMat3() ); + trailEffect->SetEndOrigin ( origin ); + } + + // Impact effect + if ( !target || target.GetEntityNum ( ) == ENTITYNUM_NONE ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } + } else { + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_impact" ), origin, normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( origin ); + impactEffect->SetAxis ( normal.ToMat3 ( ) ); + } + } +} + +/* +================ +rvLightningPath::Save +================ +*/ +void rvLightningPath::Save( idSaveGame* savefile ) const { + target.Save( savefile ); + savefile->WriteVec3( origin ); + savefile->WriteVec3( normal ); + trailEffect.Save( savefile ); + impactEffect.Save( savefile ); +} + +/* +================ +rvLightningPath::Restore +================ +*/ +void rvLightningPath::Restore( idRestoreGame* savefile ) { + target.Restore( savefile ); + savefile->ReadVec3( origin ); + savefile->ReadVec3( normal ); + trailEffect.Restore( savefile ); + impactEffect.Restore( savefile ); +} diff --git a/source/game/weapon/WeaponMachinegun.cpp b/source/game/weapon/WeaponMachinegun.cpp new file mode 100644 index 0000000..c9d453e --- /dev/null +++ b/source/game/weapon/WeaponMachinegun.cpp @@ -0,0 +1,327 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponMachinegun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponMachinegun ); + + rvWeaponMachinegun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + float spreadZoom; + bool fireHeld; + + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponMachinegun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponMachinegun ) +END_CLASS + +/* +================ +rvWeaponMachinegun::rvWeaponMachinegun +================ +*/ +rvWeaponMachinegun::rvWeaponMachinegun ( void ) { +} + +/* +================ +rvWeaponMachinegun::Spawn +================ +*/ +void rvWeaponMachinegun::Spawn ( void ) { + spreadZoom = spawnArgs.GetFloat ( "spreadZoom" ); + fireHeld = false; + + SetState ( "Raise", 0 ); + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponMachinegun::Save +================ +*/ +void rvWeaponMachinegun::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( spreadZoom ); + savefile->WriteBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::Restore +================ +*/ +void rvWeaponMachinegun::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( spreadZoom ); + savefile->ReadBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::PreSave +================ +*/ +void rvWeaponMachinegun::PreSave ( void ) { +} + +/* +================ +rvWeaponMachinegun::PostSave +================ +*/ +void rvWeaponMachinegun::PostSave ( void ) { +} + + +/* +================ +rvWeaponMachinegun::Think +================ +*/ +void rvWeaponMachinegun::Think() +{ + rvWeapon::Think(); + if ( zoomGui && owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat( "playerYaw", playerViewAxis.ToAngles().yaw ); + } +} + +/* +================ +rvWeaponMachinegun::UpdateFlashlight +================ +*/ +bool rvWeaponMachinegun::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponMachinegun::Flashlight +================ +*/ +void rvWeaponMachinegun::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponMachinegun ) + STATE ( "Idle", rvWeaponMachinegun::State_Idle) + STATE ( "Fire", rvWeaponMachinegun::State_Fire ) + STATE ( "Reload", rvWeaponMachinegun::State_Reload ) + STATE ( "Flashlight", rvWeaponMachinegun::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponMachinegun::State_Idle +================ +*/ +stateResult_t rvWeaponMachinegun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + + if ( fireHeld && !wsfl.attack ) { + fireHeld = false; + } + if ( !clipSize ) { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Fire +================ +*/ +stateResult_t rvWeaponMachinegun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spreadZoom, 0, 1.0f ); + fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !fireHeld && wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Reload +================ +*/ +stateResult_t rvWeaponMachinegun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponMachinegun::State_Flashlight +================ +*/ +stateResult_t rvWeaponMachinegun::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/source/game/weapon/WeaponNailgun.cpp b/source/game/weapon/WeaponNailgun.cpp new file mode 100644 index 0000000..06a5f57 --- /dev/null +++ b/source/game/weapon/WeaponNailgun.cpp @@ -0,0 +1,929 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" + +// Available drum speeds +const int NAILGUN_DRUMSPEED_STOPPED = 0; +const int NAILGUN_DRUMSPEED_SLOW = 1; +const int NAILGUN_DRUMSPEED_FAST = 2; + +// Spinup and spindown times +const int NAILGUN_SPINDOWN_TIME = 1000; +const int NAILGUN_SPINUP_TIME = 1000; + +// Nailgun shader parms +const int NAILGUN_SPARM_PLAYLEADIN = 7; + +// Nailgun weapon modifications +const int NAILGUN_MOD_ROF_AMMO = BIT(0); // power mod combines Rate of fire and ammoo +const int NAILGUN_MOD_SEEK = BIT(1); +//const int NAILGUN_MOD_AMMO = BIT(2); + +const int NAILGUN_SPIN_SNDCHANNEL = SND_CHANNEL_BODY2; + +class rvWeaponNailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponNailgun ); + + rvWeaponNailgun ( void ); + ~rvWeaponNailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idEntityPtr guideEnt; + int guideTime; + int guideStartTime; + rvClientEntityPtr guideEffect; + bool guideLocked; + float guideRange; + int guideHoldTime; + int guideAquireTime; + + jointHandle_t jointDrumView; + jointHandle_t jointPinsView; + jointHandle_t jointSteamRightView; + jointHandle_t jointSteamLeftView; + + jointHandle_t jointGuideEnt; + + int drumSpeed; + int drumSpeedIdeal; + float drumMultiplier; + + virtual void OnLaunchProjectile ( idProjectile* proj ); + +private: + + void UpdateGuideStatus ( float range = 0.0f ); + void CancelGuide ( void ); + bool DrumSpin ( int speed, int blendFrames ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_DrumSpinDown ( const stateParms_t& parms ); + stateResult_t State_DrumSpinUp ( const stateParms_t& parms ); + + stateResult_t Frame_ClaspOpen ( const stateParms_t& parms ); + stateResult_t Frame_ClaspClose ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponNailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponNailgun ) +END_CLASS + +/* +================ +rvWeaponNailgun::rvWeaponNailgun +================ +*/ +rvWeaponNailgun::rvWeaponNailgun ( void ) { +} + +/* +================ +rvWeaponNailgun::~rvWeaponNailgun +================ +*/ +rvWeaponNailgun::~rvWeaponNailgun ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } + if( viewModel ) { + viewModel->StopSound( NAILGUN_SPIN_SNDCHANNEL, false ); + } +} + + +/* +================ +rvWeaponNailgun::Spawn +================ +*/ +void rvWeaponNailgun::Spawn ( void ) { + spawnArgs.GetFloat ( "lockRange", "1000", guideRange ); + guideHoldTime = SEC2MS ( spawnArgs.GetFloat ( "lockHoldTime", "10" ) ); + guideAquireTime = SEC2MS ( spawnArgs.GetFloat ( "lockAquireTime", ".1" ) ); + + jointDrumView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_drum" ) ); + jointPinsView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_pins" ) ); + jointSteamRightView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamRight" ) ); + jointSteamLeftView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamLeft" ) ); + + jointGuideEnt = INVALID_JOINT; + + drumSpeed = NAILGUN_DRUMSPEED_STOPPED; + drumSpeedIdeal = drumSpeed; + drumMultiplier = spawnArgs.GetFloat ( "drumSpeed" ); + + ExecuteState ( "ClaspClose" ); + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponNailgun::Save +================ +*/ +void rvWeaponNailgun::Save ( idSaveGame *savefile ) const { + guideEnt.Save( savefile ); + savefile->WriteInt( guideTime ); + savefile->WriteInt( guideStartTime ); + guideEffect.Save( savefile ); + savefile->WriteBool ( guideLocked ); + savefile->WriteFloat ( guideRange ); + savefile->WriteInt ( guideHoldTime ); + savefile->WriteInt ( guideAquireTime ); + + savefile->WriteJoint ( jointDrumView ); + savefile->WriteJoint ( jointPinsView ); + savefile->WriteJoint ( jointSteamRightView ); + savefile->WriteJoint ( jointSteamLeftView ); + savefile->WriteJoint ( jointGuideEnt ); + + savefile->WriteInt ( drumSpeed ); + savefile->WriteInt ( drumSpeedIdeal ); + savefile->WriteFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::Restore +================ +*/ +void rvWeaponNailgun::Restore ( idRestoreGame *savefile ) { + guideEnt.Restore( savefile ); + savefile->ReadInt( guideTime ); + savefile->ReadInt( guideStartTime ); + guideEffect.Restore( savefile ); + savefile->ReadBool ( guideLocked ); + savefile->ReadFloat ( guideRange ); + savefile->ReadInt ( guideHoldTime ); + savefile->ReadInt ( guideAquireTime ); + + savefile->ReadJoint ( jointDrumView ); + savefile->ReadJoint ( jointPinsView ); + savefile->ReadJoint ( jointSteamRightView ); + savefile->ReadJoint ( jointSteamLeftView ); + savefile->ReadJoint ( jointGuideEnt ); + + + savefile->ReadInt ( drumSpeed ); + savefile->ReadInt ( drumSpeedIdeal ); + savefile->ReadFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::PreSave +================ +*/ +void rvWeaponNailgun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + + //remove the guide gui cursor if there is one. + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect->Event_Remove(); + guideEffect = NULL; + } +} + +/* +================ +rvWeaponNailgun::PostSave +================ +*/ +void rvWeaponNailgun::PostSave ( void ) { + + //restore sounds-- but which one? + if( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } else if( drumSpeed == NAILGUN_DRUMSPEED_SLOW ) { + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } + + //the guide gui effect will restore itself naturally +} + +/* +================ +rvWeaponNailgun::CancelGuide +================ +*/ +void rvWeaponNailgun::CancelGuide ( void ) { + if ( zoomGui && guideEnt ) { + zoomGui->HandleNamedEvent ( "lockStop" ); + } + + guideEnt = NULL; + guideLocked = false; + jointGuideEnt = INVALID_JOINT; + UpdateGuideStatus ( ); +} + +/* +================ +rvWeaponNailgun::UpdateGuideStatus +================ +*/ +void rvWeaponNailgun::UpdateGuideStatus ( float range ) { + // Update the zoom GUI variables + if ( zoomGui ) { + float lockStatus; + if ( guideEnt ) { + if ( guideLocked ) { + lockStatus = 1.0f; + } else { + lockStatus = Min((float)(gameLocal.time - guideStartTime)/(float)guideTime, 1.0f); + } + } else { + lockStatus = 0.0f; + } + + if ( owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat ( "lockStatus", lockStatus ); + zoomGui->SetStateFloat ( "playerYaw", playerViewAxis.ToAngles().yaw ); + } + + if ( guideEnt ) { + idVec3 diff; + diff = guideEnt->GetPhysics()->GetOrigin ( ) - playerViewOrigin; + diff.NormalizeFast ( ); + zoomGui->SetStateFloat ( "lockYaw", idMath::AngleDelta ( diff.ToAngles ( ).yaw, playerViewAxis.ToAngles().yaw ) ); + zoomGui->SetStateString ( "lockRange", va("%4.2f", range) ); + zoomGui->SetStateString ( "lockTime", va("%02d", (int)MS2SEC(guideStartTime + guideTime - gameLocal.time) + 1) ); + } + } + + // Update the guide effect + if ( guideEnt ) { + idVec3 eyePos = static_cast(guideEnt.GetEntity())->GetEyePosition(); + if( jointGuideEnt == INVALID_JOINT ) { + eyePos += guideEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + } else { + idMat3 jointAxis; + idVec3 jointLoc; + static_cast(guideEnt.GetEntity())->GetJointWorldTransform( jointGuideEnt, gameLocal.GetTime(),jointLoc,jointAxis ); + eyePos += jointLoc; + } + eyePos *= 0.5f; + if ( guideEffect ) { + guideEffect->SetOrigin ( eyePos ); + guideEffect->SetAxis ( playerViewAxis.Transpose() ); + } else { + guideEffect = gameLocal.PlayEffect ( + gameLocal.GetEffect( spawnArgs, guideLocked ? "fx_guide" : "fx_guidestart" ), + eyePos, playerViewAxis.Transpose(), true, vec3_origin, false ); + if ( guideEffect ) { + guideEffect->GetRenderEffect()->weaponDepthHackInViewID = owner->entityNumber + 1; + guideEffect->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1; + } + } + } else { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + } +} + +/* +================ +rvWeaponNailgun::Think +================ +*/ +void rvWeaponNailgun::Think ( void ) { + idEntity* ent; + trace_t tr; + + // Let the real weapon think first + rvWeapon::Think ( ); + + // If no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + // If the zoom button isnt down then dont update the lock + if ( !wsfl.zoom || IsReloading ( ) || IsHolstered ( ) ) { + CancelGuide ( ); + return; + } + + // Dont update the target if the current target is still alive, unhidden and we have already locked + if ( guideEnt && guideEnt->health > 0 && !guideEnt->IsHidden() && guideLocked ) { + float range; + range = (guideEnt->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast(); + if ( range > guideRange || gameLocal.time > guideStartTime + guideTime ) { + CancelGuide ( ); + } else { + UpdateGuideStatus ( range ); + return; + } + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_BOUNDINGBOX, owner ); +// RAVEN END + + if ( tr.fraction >= 1.0f ) { + CancelGuide( ); + return; + } + + ent = gameLocal.entities[tr.c.entityNum]; + + //if we're using a target nailable... + if( ent->IsType ( rvTarget_Nailable::GetClassType() ) ) { + const char* jointName = ent->spawnArgs.GetString("lock_joint"); + ent = ent->targets[ 0 ].GetEntity(); + if( !ent) { + CancelGuide( ); + return; + } + + if( ent->GetAnimator() ) { + jointGuideEnt = ent->GetAnimator()->GetJointHandle( jointName ); + } + } + + + if ( !ent->IsType ( idActor::GetClassType() ) ) { + CancelGuide( ); + return; + } + if ( gameLocal.GetLocalPlayer() && static_cast(ent)->team == gameLocal.GetLocalPlayer()->team ) { + CancelGuide( ); + return; + } + + if ( guideEnt != ent ) { + guideStartTime = gameLocal.time; + guideTime = guideAquireTime; + guideEnt = ent; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockStart" ); + } + } else if ( gameLocal.time > guideStartTime + guideTime ) { + // Stop the guide effect since it was just the guide_Start effect + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + guideLocked = true; + guideTime = guideHoldTime; + guideStartTime = gameLocal.time; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockAquired" ); + } + } + + UpdateGuideStatus ( (ent->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast() ); +} + +/* +================ +rvWeaponNailgun::OnLaunchProjectile +================ +*/ +void rvWeaponNailgun::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + idGuidedProjectile* guided; + guided = dynamic_cast(proj); + if ( guided ) { + guided->GuideTo ( guideEnt, jointGuideEnt ); + } +} + +/* +================ +rvWeaponNailgun::DrumSpin + +Set the drum spin speed +================ +*/ +bool rvWeaponNailgun::DrumSpin ( int speed, int blendFrames ) { + // Dont bother if the drum is already spinning at the desired speed + if ( drumSpeedIdeal == speed ) { + return false; + } + + drumSpeedIdeal = speed; + + switch ( speed ) { + case NAILGUN_DRUMSPEED_STOPPED: + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,0), gameLocal.time, 250 ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,0), gameLocal.time, 250 ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_SLOW: + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,45), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-35), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_FAST: + // Start the barrel spinning faster + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,360.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-300.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + + PostState ( "DrumSpinUp", blendFrames ); + return true; + } + + drumSpeed = drumSpeedIdeal; + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponNailgun ) + STATE ( "Raise", rvWeaponNailgun::State_Raise ) + STATE ( "Lower", rvWeaponNailgun::State_Lower ) + STATE ( "Idle", rvWeaponNailgun::State_Idle) + STATE ( "Fire", rvWeaponNailgun::State_Fire ) + STATE ( "Reload", rvWeaponNailgun::State_Reload ) + STATE ( "DrumSpinDown", rvWeaponNailgun::State_DrumSpinDown ) + STATE ( "DrumSpinUp", rvWeaponNailgun::State_DrumSpinUp ) + + STATE ( "ClaspOpen", rvWeaponNailgun::Frame_ClaspOpen ) + STATE ( "ClaspClose", rvWeaponNailgun::Frame_ClaspClose ) +END_CLASS_STATES + +/* +================ +rvWeaponNailgun::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + // Stop any looping sounds + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Idle + +Manage the idle state of the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoInClip ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + // Do we need to spin the drum down? + if ( DrumSpin ( NAILGUN_DRUMSPEED_SLOW, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Fire + +Fire the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_DONE, + STAGE_SPINEMPTY, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !wsfl.attack ) { + SetState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + if ( DrumSpin ( NAILGUN_DRUMSPEED_FAST, 2 ) ) { + PostState ( "Fire", 2 ); + return SRESULT_DONE; + } + nextAttackTime = gameLocal.time; + + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_fast", 4 ); + } else { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_slow", 4 ); + } + + if ( wsfl.zoom ) { + Attack ( true, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } else { + Attack ( false, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } + + // Play the exhaust effects + viewModel->PlayEffect ( "fx_exhaust", jointSteamRightView, false ); + viewModel->PlayEffect ( "fx_exhaust", jointSteamLeftView, false ); + + viewModel->StartSound ( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + viewModel->StartSound ( "snd_fireStereo", SND_CHANNEL_ITEM, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( gameLocal.time >= nextAttackTime ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( clipSize && wsfl.attack && !wsfl.lowerWeapon && !wsfl.reload ) { + PlayCycle ( ANIMCHANNEL_LEGS, "spinempty", 4 ); + return SRESULT_STAGE ( STAGE_SPINEMPTY ); + } + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 4 ); + if ( !wsfl.attack && !AmmoInClip() && AmmoAvailable() && AutoReload ( ) && !wsfl.lowerWeapon ) { + PostState ( "Reload", 4 ); + } else { + PostState ( "Idle", 4 ); + } + return SRESULT_DONE; + + case STAGE_SPINEMPTY: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Reload +================ +*/ +stateResult_t rvWeaponNailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_RELOAD, + STAGE_RELOADWAIT, + STAGE_RELOADLEFT, + STAGE_RELOADLEFTWAIT, + STAGE_RELOADRIGHT, + STAGE_RELOADRIGHTWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( DrumSpin ( NAILGUN_DRUMSPEED_STOPPED, parms.blendFrames ) ) { + PostState ( "reload", parms.blendFrames ); + return SRESULT_DONE; + } + + SetStatus ( WP_RELOAD ); + + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + return SRESULT_STAGE( STAGE_RELOADLEFT ); + } + return SRESULT_STAGE( STAGE_RELOAD ); + + case STAGE_RELOAD: + PlayAnim( ANIMCHANNEL_LEGS, "reload", parms.blendFrames ); + return SRESULT_STAGE( STAGE_RELOADWAIT ); + + case STAGE_RELOADWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize ( ) ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLEFT: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1hold", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADLEFTWAIT ); + + case STAGE_RELOADLEFTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + AddToClip ( ClipSize() / 2 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHT ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADRIGHT: + if ( wsfl.attack || AmmoInClip() >= ClipSize() || !AmmoAvailable() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip2", 0 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHTWAIT ); + + case STAGE_RELOADRIGHTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize() / 2 ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1finish", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinUp + +Spin the drum from a slow speed to a fast speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinUp ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spinup", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL); + PlayAnim ( ANIMCHANNEL_LEGS, "spinup", 4 ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( !wsfl.attack ) { + int oldSpeed = drumSpeed; + drumSpeed = drumSpeedIdeal; + DrumSpin ( oldSpeed, 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinDown + +Spin the drum down from a faster speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinDown ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spindown", SND_CHANNEL_ANY, 0, false, 0 ); + + // Spin down animation + PlayAnim( ANIMCHANNEL_LEGS, "spindown", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoAvailable() > AmmoInClip() && AmmoInClip() < ClipSize()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Open the clasp that holds in the clips +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspOpen ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, "clasp_open", 0 ); + return SRESULT_OK; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Close the clasp that holds in the clips and make sure to use the +correct positioning depending on whether you have one or two clips +in the gun. +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspClose ( const stateParms_t& parms ) { + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_2clip", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_1clip", 0 ); + } + return SRESULT_OK; +} diff --git a/source/game/weapon/WeaponNapalmGun.cpp b/source/game/weapon/WeaponNapalmGun.cpp new file mode 100644 index 0000000..ccec34b --- /dev/null +++ b/source/game/weapon/WeaponNapalmGun.cpp @@ -0,0 +1,443 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +const int NAPALM_GUN_NUM_CYLINDERS = 5; + +class WeaponNapalmGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( WeaponNapalmGun ); + + WeaponNapalmGun ( void ); + ~WeaponNapalmGun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + + virtual void SpectatorCycle ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + +protected: + + void UpdateCylinders(void); + + typedef enum {CYLINDER_RESET_POSITION,CYLINDER_MOVE_POSITION, CYLINDER_UPDATE_POSITION } CylinderState; + CylinderState cylinderState; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_EmptyReload ( const stateParms_t& parms ); + + stateResult_t Frame_MoveCylinder ( const stateParms_t& parms ); + stateResult_t Frame_ResetCylinder ( const stateParms_t& parms ); + + + float cylinderMaxOffsets[NAPALM_GUN_NUM_CYLINDERS]; + idInterpolate cylinderOffsets[NAPALM_GUN_NUM_CYLINDERS]; + jointHandle_t cylinderJoints[NAPALM_GUN_NUM_CYLINDERS]; + + + int cylinderMoveTime; + int previousAmmo; + bool zoomed; + + CLASS_STATES_PROTOTYPE ( WeaponNapalmGun ); +}; + +CLASS_DECLARATION( rvWeapon, WeaponNapalmGun ) +END_CLASS + +/* +================ +WeaponNapalmGun::WeaponNapalmGun +================ +*/ +WeaponNapalmGun::WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::~WeaponNapalmGun +================ +*/ +WeaponNapalmGun::~WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::Spawn +================ +*/ +void WeaponNapalmGun::Spawn( void ) { + assert(viewModel); + idAnimator* animator = viewModel->GetAnimator(); + assert(animator); + + SetState( "Raise", 0 ); + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + idStr argName = "cylinder_offset"; + argName += i; + cylinderMaxOffsets[i] = spawnArgs.GetFloat(argName, "0.0"); + + argName = "cylinder_joint"; + argName += i; + cylinderJoints[i] = animator->GetJointHandle( spawnArgs.GetString( argName, "" ) ); + + cylinderOffsets[i].Init( gameLocal.time, 0.0f, 0, 0); + } + + previousAmmo = AmmoInClip(); + cylinderMoveTime = spawnArgs.GetFloat( "cylinderMoveTime", "500" ); + cylinderState = CYLINDER_RESET_POSITION; + zoomed = false; +} + +/* +================ +WeaponNapalmGun::Think +================ +*/ +void WeaponNapalmGun::Think( void ) { + + rvWeapon::Think(); + + //Check to see if the ammo level has changed. + //This is to account for ammo pickups. + if ( previousAmmo != AmmoInClip() ) { + // don't do this in MP, the weap script doesn't sync the canisters anyway + if ( !gameLocal.isMultiplayer ) { + //change the cylinder state to reflect the new change in ammo. + cylinderState = CYLINDER_MOVE_POSITION; + } + previousAmmo = AmmoInClip(); + } + + UpdateCylinders(); +} + +/* +=============== +WeaponNapalmGun::MuzzleRise +=============== +*/ +void WeaponNapalmGun::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if ( wsfl.zoom ) + return; + + rvWeapon::MuzzleRise( origin, axis ); +} + +/* +=============== +WeaponNapalmGun::UpdateCylinders +=============== +*/ +void WeaponNapalmGun::UpdateCylinders(void) +{ + idAnimator* animator; + animator = viewModel->GetAnimator(); + assert( animator ); + + float ammoInClip = AmmoInClip(); + float clipSize = ClipSize(); + if ( clipSize <= idMath::FLOAT_EPSILON ) { + clipSize = maxAmmo; + } + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + // move the local position of the joint along the x-axis. + float currentOffset = cylinderOffsets[i].GetCurrentValue(gameLocal.time); + + switch(cylinderState) + { + case CYLINDER_MOVE_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, cylinderMoveTime, currentOffset, endValue ); + } + break; + + case CYLINDER_RESET_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, 0, endValue, endValue ); + } + break; + } + + + animator->SetJointPos( cylinderJoints[i], JOINTMOD_LOCAL, idVec3( currentOffset, 0.0f, 0.0f ) ); + } + + cylinderState = CYLINDER_UPDATE_POSITION; +} + + +/* +===================== +WeaponNapalmGun::Save +===================== +*/ +void WeaponNapalmGun::Save( idSaveGame *saveFile ) const +{ + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->WriteFloat(cylinderMaxOffsets[i]); + saveFile->WriteInterpolate(cylinderOffsets[i]); + saveFile->WriteJoint(cylinderJoints[i]); + } + + saveFile->WriteInt(cylinderMoveTime); + saveFile->WriteInt(previousAmmo); +} + +/* +===================== +WeaponNapalmGun::Restore +===================== +*/ +void WeaponNapalmGun::Restore( idRestoreGame *saveFile ) { + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->ReadFloat(cylinderMaxOffsets[i]); + saveFile->ReadInterpolate(cylinderOffsets[i]); + saveFile->ReadJoint(cylinderJoints[i]); + } + + saveFile->ReadInt(cylinderMoveTime); + saveFile->ReadInt(previousAmmo); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( WeaponNapalmGun ) + STATE ( "Idle", WeaponNapalmGun::State_Idle) + STATE ( "Fire", WeaponNapalmGun::State_Fire ) + STATE ( "Reload", WeaponNapalmGun::State_Reload ) + STATE ( "EmptyReload", WeaponNapalmGun::State_EmptyReload ) + STATE ( "MoveCylinder", WeaponNapalmGun::Frame_MoveCylinder ) + STATE ( "ResetCylinder", WeaponNapalmGun::Frame_ResetCylinder) +END_CLASS_STATES + + + +stateResult_t WeaponNapalmGun::State_Reload( const stateParms_t& parms) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t WeaponNapalmGun::State_EmptyReload( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_empty", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip ( ClipSize() ); + + cylinderState = CYLINDER_MOVE_POSITION; + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Idle +================ +*/ +stateResult_t WeaponNapalmGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + if ( wsfl.zoom ) + PlayCycle( ANIMCHANNEL_LEGS, "altidle", parms.blendFrames ); + else + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.zoom && !zoomed ) { + SetState ( "Idle", 4 ); + zoomed = true; + return SRESULT_DONE; + } + + if ( !wsfl.zoom && zoomed ) { + SetState ( "Idle", 4 ); + zoomed = false; + return SRESULT_DONE; + } + + if(!clipSize) + { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } + else + { + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Fire +================ +*/ +stateResult_t WeaponNapalmGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + //fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + + int animNum = viewModel->GetAnimator()->GetAnim ( "fire" ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim ( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + + previousAmmo = AmmoInClip(); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + if ( !wsfl.zoom ) + SetState ( "Reload", 4 ); + else + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t WeaponNapalmGun::Frame_MoveCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_MOVE_POSITION; + return SRESULT_OK; +} + +stateResult_t WeaponNapalmGun::Frame_ResetCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_RESET_POSITION; + return SRESULT_OK; +} + +void WeaponNapalmGun::SpectatorCycle( void ) { + cylinderState = CYLINDER_RESET_POSITION; +} diff --git a/source/game/weapon/WeaponRailgun.cpp b/source/game/weapon/WeaponRailgun.cpp new file mode 100644 index 0000000..92a8ef9 --- /dev/null +++ b/source/game/weapon/WeaponRailgun.cpp @@ -0,0 +1,270 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const idEventDef EV_Railgun_RestoreHum( "", "" ); + +class rvWeaponRailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRailgun ); + + rvWeaponRailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + void ClientUnstale ( void ); + +protected: + jointHandle_t jointBatteryView; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRailgun ) + EVENT( EV_Railgun_RestoreHum, rvWeaponRailgun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponRailgun::rvWeaponRailgun +================ +*/ +rvWeaponRailgun::rvWeaponRailgun ( void ) { +} + +/* +================ +rvWeaponRailgun::Spawn +================ +*/ +void rvWeaponRailgun::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponRailgun::Save +================ +*/ +void rvWeaponRailgun::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::Restore +================ +*/ +void rvWeaponRailgun::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::PreSave +================ +*/ +void rvWeaponRailgun::PreSave ( void ) { + + //this should shoosh the humming but not the shooting sound. + StopSound( SND_CHANNEL_BODY2, 0); +} + +/* +================ +rvWeaponRailgun::PostSave +================ +*/ +void rvWeaponRailgun::PostSave ( void ) { + + //restore the humming + PostEventMS( &EV_Railgun_RestoreHum, 10); +} + +/* +================ +rvWeaponRailgun::Think +================ +*/ +void rvWeaponRailgun::Think ( void ) { + + // Let the real weapon think first + rvWeapon::Think ( ); + + if ( zoomGui && wsfl.zoom && !gameLocal.isMultiplayer ) { + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + zoomGui->SetStateInt( "player_ammo", ammo ); + } + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRailgun ) + STATE ( "Idle", rvWeaponRailgun::State_Idle) + STATE ( "Fire", rvWeaponRailgun::State_Fire ) + STATE ( "Reload", rvWeaponRailgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponRailgun::State_Idle +================ +*/ +stateResult_t rvWeaponRailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); + SetStatus ( WP_READY ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + // Auto reload? + if ( AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRailgun::State_Fire +================ +*/ +stateResult_t rvWeaponRailgun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( ( gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime ) || + ( !gameLocal.isMultiplayer && ( AnimDone ( ANIMCHANNEL_ALL, 2 ) ) ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +stateResult_t rvWeaponRailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Event + +=============================================================================== +*/ + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +void rvWeaponRailgun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); +} + +/* +================ +rvWeaponRailgun::ClientUnStale +================ +*/ +void rvWeaponRailgun::ClientUnstale( void ) { + Event_RestoreHum(); +} + diff --git a/source/game/weapon/WeaponRocketLauncher.cpp b/source/game/weapon/WeaponRocketLauncher.cpp new file mode 100644 index 0000000..bbd3623 --- /dev/null +++ b/source/game/weapon/WeaponRocketLauncher.cpp @@ -0,0 +1,583 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +class rvWeaponRocketLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRocketLauncher ); + + rvWeaponRocketLauncher ( void ); + ~rvWeaponRocketLauncher ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + void PreSave ( void ); + void PostSave ( void ); + + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetRocketState ( const char* state, int blendFrames ); + + rvClientEntityPtr guideEffect; + idList< idEntityPtr > guideEnts; + float guideSpeedSlow; + float guideSpeedFast; + float guideRange; + float guideAccelTime; + + rvStateThread rocketThread; + + float reloadRate; + + bool idleEmpty; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_Rocket_Idle ( const stateParms_t& parms ); + stateResult_t State_Rocket_Reload ( const stateParms_t& parms ); + + stateResult_t Frame_AddToClip ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRocketLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRocketLauncher ) +END_CLASS + +/* +================ +rvWeaponRocketLauncher::rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::rvWeaponRocketLauncher ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } +} + +/* +================ +rvWeaponRocketLauncher::Spawn +================ +*/ +void rvWeaponRocketLauncher::Spawn ( void ) { + float f; + + idleEmpty = false; + + spawnArgs.GetFloat ( "lockRange", "0", guideRange ); + + spawnArgs.GetFloat ( "lockSlowdown", ".25", f ); + attackDict.GetFloat ( "speed", "0", guideSpeedFast ); + guideSpeedSlow = guideSpeedFast * f; + + reloadRate = SEC2MS ( spawnArgs.GetFloat ( "reloadRate", ".8" ) ); + + guideAccelTime = SEC2MS ( spawnArgs.GetFloat ( "lockAccelTime", ".25" ) ); + + // Start rocket thread + rocketThread.SetName ( viewModel->GetName ( ) ); + rocketThread.SetOwner ( this ); + + // Adjust reload animations to match the fire rate + idAnim* anim; + int animNum; + float rate; + animNum = viewModel->GetAnimator()->GetAnim ( "reload" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + animNum = viewModel->GetAnimator()->GetAnim ( "reload_empty" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + SetState ( "Raise", 0 ); + SetRocketState ( "Rocket_Idle", 0 ); +} + +/* +================ +rvWeaponRocketLauncher::Think +================ +*/ +void rvWeaponRocketLauncher::Think ( void ) { + trace_t tr; + int i; + + rocketThread.Execute ( ); + + // Let the real weapon think first + rvWeapon::Think ( ); + + // IF no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + if ( !wsfl.zoom ) { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden ( ) ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket is still guiding then stop the guide and slow it down + if ( proj->GetGuideType ( ) != idGuidedProjectile::GUIDE_NONE ) { + proj->CancelGuide ( ); + proj->SetSpeed ( guideSpeedFast, (1.0f - (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow)) * guideAccelTime ); + } + } + + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden() ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket isnt guiding yet then adjust its speed back to normal + if ( proj->GetGuideType ( ) == idGuidedProjectile::GUIDE_NONE ) { + proj->SetSpeed ( guideSpeedSlow, (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow) * guideAccelTime ); + } + proj->GuideTo ( tr.endpos ); + } + + if ( !guideEffect ) { + guideEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_guide" ), tr.endpos, tr.c.normal.ToMat3(), true, vec3_origin, true ); + } else { + guideEffect->SetOrigin ( tr.endpos ); + guideEffect->SetAxis ( tr.c.normal.ToMat3() ); + } +} + +/* +================ +rvWeaponRocketLauncher::OnLaunchProjectile +================ +*/ +void rvWeaponRocketLauncher::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + // Double check that its actually a guided projectile + if ( !proj || !proj->IsType ( idGuidedProjectile::GetClassType() ) ) { + return; + } + + // Launch the projectile + idEntityPtr ptr; + ptr = proj; + guideEnts.Append ( ptr ); +} + +/* +================ +rvWeaponRocketLauncher::SetRocketState +================ +*/ +void rvWeaponRocketLauncher::SetRocketState ( const char* state, int blendFrames ) { + rocketThread.SetState ( state, blendFrames ); +} + +/* +===================== +rvWeaponRocketLauncher::Save +===================== +*/ +void rvWeaponRocketLauncher::Save( idSaveGame *saveFile ) const { + saveFile->WriteObject( guideEffect ); + + idEntity* ent = NULL; + saveFile->WriteInt( guideEnts.Num() ); + for( int ix = 0; ix < guideEnts.Num(); ++ix ) { + ent = guideEnts[ ix ].GetEntity(); + if( ent ) { + saveFile->WriteObject( ent ); + } + } + + saveFile->WriteFloat( guideSpeedSlow ); + saveFile->WriteFloat( guideSpeedFast ); + saveFile->WriteFloat( guideRange ); + saveFile->WriteFloat( guideAccelTime ); + + saveFile->WriteFloat ( reloadRate ); + + rocketThread.Save( saveFile ); +} + +/* +===================== +rvWeaponRocketLauncher::Restore +===================== +*/ +void rvWeaponRocketLauncher::Restore( idRestoreGame *saveFile ) { + int numEnts = 0; + idEntity* ent = NULL; + rvClientEffect* clientEffect = NULL; + + saveFile->ReadObject( reinterpret_cast(clientEffect) ); + guideEffect = clientEffect; + + saveFile->ReadInt( numEnts ); + guideEnts.Clear(); + guideEnts.SetNum( numEnts ); + for( int ix = 0; ix < numEnts; ++ix ) { + saveFile->ReadObject( reinterpret_cast(ent) ); + guideEnts[ ix ] = ent; + } + + saveFile->ReadFloat( guideSpeedSlow ); + saveFile->ReadFloat( guideSpeedFast ); + saveFile->ReadFloat( guideRange ); + saveFile->ReadFloat( guideAccelTime ); + + saveFile->ReadFloat ( reloadRate ); + + rocketThread.Restore( saveFile, this ); +} + +/* +================ +rvWeaponRocketLauncher::PreSave +================ +*/ +void rvWeaponRocketLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::PostSave +================ +*/ +void rvWeaponRocketLauncher::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRocketLauncher ) + STATE ( "Idle", rvWeaponRocketLauncher::State_Idle) + STATE ( "Fire", rvWeaponRocketLauncher::State_Fire ) + STATE ( "Raise", rvWeaponRocketLauncher::State_Raise ) + STATE ( "Lower", rvWeaponRocketLauncher::State_Lower ) + + STATE ( "Rocket_Idle", rvWeaponRocketLauncher::State_Rocket_Idle ) + STATE ( "Rocket_Reload", rvWeaponRocketLauncher::State_Rocket_Reload ) + + STATE ( "AddToClip", rvWeaponRocketLauncher::Frame_AddToClip ) +END_CLASS_STATES + + +/* +================ +rvWeaponRocketLauncher::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && ( gameLocal.isClient || AmmoInClip ( ) ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_LEGS, "fire", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && ( gameLocal.isClient || AmmoInClip ( ) ) && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITEMPTY, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) <= AmmoInClip() ) { + PlayAnim( ANIMCHANNEL_TORSO, "idle_empty", parms.blendFrames ); + idleEmpty = true; + } else { + PlayAnim( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AmmoAvailable ( ) > AmmoInClip() ) { + if ( idleEmpty ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } else if ( ClipSize ( ) > 1 ) { + if ( gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) ) { + if ( !AmmoInClip() || !wsfl.attack ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } else { + if ( AmmoInClip ( ) == 0 ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Reload +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: { + const char* animName; + int animNum; + + if ( idleEmpty ) { + animName = "ammo_pickup"; + idleEmpty = false; + } else if ( AmmoAvailable ( ) == AmmoInClip( ) + 1 ) { + animName = "reload_empty"; + } else { + animName = "reload"; + } + + animNum = viewModel->GetAnimator()->GetAnim ( animName ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (reloadRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( !wsfl.attack && gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) && AmmoAvailable() > AmmoInClip() ) { + SetRocketState ( "Rocket_Reload", 0 ); + } else { + SetRocketState ( "Rocket_Idle", 0 ); + } + return SRESULT_DONE; + } + /* + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + if ( AmmoInClip ( ) == 0 ) + { + AddToClip ( ClipSize() ); + } + SetRocketState ( "Rocket_Idle", 0 ); + return SRESULT_DONE; + } + */ + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::Frame_AddToClip +================ +*/ +stateResult_t rvWeaponRocketLauncher::Frame_AddToClip ( const stateParms_t& parms ) { + AddToClip ( 1 ); + return SRESULT_OK; +} + diff --git a/source/game/weapon/WeaponShotgun.cpp b/source/game/weapon/WeaponShotgun.cpp new file mode 100644 index 0000000..c904afa --- /dev/null +++ b/source/game/weapon/WeaponShotgun.cpp @@ -0,0 +1,289 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int SHOTGUN_MOD_AMMO = BIT(0); + +class rvWeaponShotgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponShotgun ); + + rvWeaponShotgun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + int hitscans; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( rvWeaponShotgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponShotgun ) +END_CLASS + +/* +================ +rvWeaponShotgun::rvWeaponShotgun +================ +*/ +rvWeaponShotgun::rvWeaponShotgun( void ) { +} + +/* +================ +rvWeaponShotgun::Spawn +================ +*/ +void rvWeaponShotgun::Spawn( void ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); + + SetState( "Raise", 0 ); +} + +/* +================ +rvWeaponShotgun::Save +================ +*/ +void rvWeaponShotgun::Save( idSaveGame *savefile ) const { +} + +/* +================ +rvWeaponShotgun::Restore +================ +*/ +void rvWeaponShotgun::Restore( idRestoreGame *savefile ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); +} + +/* +================ +rvWeaponShotgun::PreSave +================ +*/ +void rvWeaponShotgun::PreSave ( void ) { +} + +/* +================ +rvWeaponShotgun::PostSave +================ +*/ +void rvWeaponShotgun::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION( rvWeaponShotgun ) + STATE( "Idle", rvWeaponShotgun::State_Idle) + STATE( "Fire", rvWeaponShotgun::State_Fire ) + STATE( "Reload", rvWeaponShotgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponShotgun::State_Idle +================ +*/ +stateResult_t rvWeaponShotgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable( ) ) { + SetStatus( WP_OUTOFAMMO ); + } else { + SetStatus( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Fire +================ +*/ +stateResult_t rvWeaponShotgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack( false, hitscans, spread, 0, 1.0f ); + PlayAnim( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( (!gameLocal.isMultiplayer && (wsfl.lowerWeapon || AnimDone( ANIMCHANNEL_ALL, 0 )) ) || AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetState( "Idle", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( clipSize ) { + if ( (wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip())) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Reload +================ +*/ +stateResult_t rvWeaponShotgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_RELOADSTARTWAIT, + STAGE_RELOADLOOP, + STAGE_RELOADLOOPWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + + if ( mods & SHOTGUN_MOD_AMMO ) { + PlayAnim ( ANIMCHANNEL_ALL, "reload_clip", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_ALL, "reload_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADSTARTWAIT ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADSTARTWAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLOOP: + if ( (wsfl.attack && AmmoInClip() ) || AmmoAvailable ( ) <= AmmoInClip ( ) || AmmoInClip() == ClipSize() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_ALL, "reload_loop", 0 ); + return SRESULT_STAGE ( STAGE_RELOADLOOPWAIT ); + + case STAGE_RELOADLOOPWAIT: + if ( (wsfl.attack && AmmoInClip() ) || wsfl.netEndReload ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip( 1 ); + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + NetEndReload ( ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_end", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) && gameLocal.time > nextAttackTime ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/idlib.vcproj b/source/idlib.vcproj new file mode 100644 index 0000000..aa1f584 --- /dev/null +++ b/source/idlib.vcproj @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/idlib/AutoPtr.h b/source/idlib/AutoPtr.h new file mode 100644 index 0000000..44176ef --- /dev/null +++ b/source/idlib/AutoPtr.h @@ -0,0 +1,64 @@ +#ifndef __AUTOPTR_H__ +#define __AUTOPTR_H__ + + +// this class is NOT safe for array new's. It will not properly call +// the destructor for each element and you will silently leak memory. +// it does work for classes requiring no destructor however(base types) +template +class idAutoPtr +{ +public: + explicit idAutoPtr(type *ptr = 0) + : mPtr(ptr) + { + } + + ~idAutoPtr() + { + delete mPtr; + } + + type &operator*() const + { + return *mPtr; + } + + type *operator->() const + { + return &**this; + } + + type *get() const + { + return mPtr; + } + + type *release() + { + type *ptr = mPtr; + mPtr = NULL; + return ptr; + } + + void reset(type *ptr = NULL) + { + if (ptr != mPtr) + delete mPtr; + mPtr = ptr; + } + + operator type*() + { + return get(); + } + +private: + // disallow copies + idAutoPtr &operator=(idAutoPtr& ptr); + idAutoPtr(idAutoPtr& ptr); + + type *mPtr; +}; + +#endif diff --git a/source/idlib/Base64.cpp b/source/idlib/Base64.cpp new file mode 100644 index 0000000..1274e3d --- /dev/null +++ b/source/idlib/Base64.cpp @@ -0,0 +1,233 @@ + +#include "precompiled.h" +#pragma hdrstop + +/* +Copyright (c) 1996 Lars Wirzenius. All rights reserved. + +June 14 2003: TTimo + modified + endian bug fixes + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=197039 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +============ +idBase64::Encode +============ +*/ +static const char sixtet_to_base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void idBase64::Encode( const byte *from, int size ) { + int i, j; + unsigned long w; + byte *to; + + EnsureAlloced( 4*(size+3)/3 + 2 ); // ratio and padding + trailing \0 + to = data; + + w = 0; + i = 0; + while (size > 0) { + w |= *from << i*8; + ++from; + --size; + ++i; + if (size == 0 || i == 3) { + byte out[4]; + SixtetsForInt( out, w ); + for (j = 0; j*6 < i*8; ++j) { + *to++ = sixtet_to_base64[ out[j] ]; + } + if (size == 0) { + for (j = i; j < 3; ++j) { + *to++ = '='; + } + } + w = 0; + i = 0; + } + } + + *to++ = '\0'; + len = to - data; +} + +/* +============ +idBase64::DecodeLength +returns the minimum size in bytes of the target buffer for decoding +4 base64 digits <-> 3 bytes +============ +*/ +int idBase64::DecodeLength( void ) const { + return 3*len/4; +} + +/* +============ +idBase64::Decode +============ +*/ +int idBase64::Decode( byte *to ) const { + unsigned long w; + int i, j; + size_t n; + static char base64_to_sixtet[256]; + static int tab_init = 0; + byte *from = data; + + if (!tab_init) { + memset( base64_to_sixtet, 0, 256 ); + for (i = 0; (j = sixtet_to_base64[i]) != '\0'; ++i) { + base64_to_sixtet[j] = i; + } + tab_init = 1; + } + + w = 0; + i = 0; + n = 0; + byte in[4] = {0,0,0,0}; + while (*from != '\0' && *from != '=' ) { + if (*from == ' ' || *from == '\n') { + ++from; + continue; + } + in[i] = base64_to_sixtet[* (unsigned char *) from]; + ++i; + ++from; + if (*from == '\0' || *from == '=' || i == 4) { + w = IntForSixtets( in ); + for (j = 0; j*8 < i*6; ++j) { + *to++ = w & 0xff; + ++n; + w >>= 8; + } + i = 0; + w = 0; + } + } + return n; +} + +/* +============ +idBase64::Encode +============ +*/ +void idBase64::Encode( const idStr &src ) { + Encode( (const byte *)src.c_str(), src.Length() ); +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idStr &dest ) const { + byte *buf = new byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + buf[out] = '\0'; + dest = (const char *)buf; + delete[] buf; +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idFile *dest ) const { + byte *buf = new byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + dest->Write( buf, out ); + delete[] buf; +} + +#if 0 + +void idBase64_TestBase64() { + + idStr src; + idBase64 dest; + src = "Encode me in base64"; + dest.Encode( src ); + idLib::common->Printf( "%s -> %s\n", src.c_str(), dest.c_str() ); + dest.Decode( src ); + idLib::common->Printf( "%s -> %s\n", dest.c_str(), src.c_str() ); + + idDict src_dict; + src_dict.SetFloat("float", 0.5f); + src_dict.SetBool("bool", true); + src_dict.Set("value", "foo"); + idFile_Memory src_fmem("serialize_dict"); + src_dict.WriteToFileHandle( &src_fmem ); + dest.Encode( (const byte *)src_fmem.GetDataPtr(), src_fmem.Length() ); + idLib::common->Printf( "idDict encoded to %s\n", dest.c_str()); + + // now decode to another stream and build back + idFile_Memory dest_fmem( "build_back" ); + dest.Decode( &dest_fmem ); + dest_fmem.MakeReadOnly(); + idDict dest_dict; + dest_dict.ReadFromFileHandle( &dest_fmem ); + idLib::common->Printf( "idDict reconstructed after base64 decode\n"); + dest_dict.Print(); + + // test idDict read from file - from python generated files, see idDict.py + idFile *file = idLib::fileSystem->OpenFileRead("idDict.test"); + if (file) { + idDict test_dict; + test_dict.ReadFromFileHandle( file ); + // + idLib::common->Printf( "read idDict.test:\n"); + test_dict.Print(); + idLib::fileSystem->CloseFile(file); + file = NULL; + } else { + idLib::common->Printf( "idDict.test not found\n" ); + } + + idBase64 base64_src; + void *buffer; + if ( idLib::fileSystem->ReadFile( "idDict.base64.test", &buffer ) != -1 ) { + idFile_Memory mem_src( "dict" ); + idLib::common->Printf( "read: %d %s\n", idStr::Length( (char*)buffer ), buffer ); + base64_src = (char *)buffer; + base64_src.Decode( &mem_src ); + mem_src.MakeReadOnly(); + idDict test_dict; + test_dict.ReadFromFileHandle( &mem_src ); + idLib::common->Printf( "read idDict.base64.test:\n"); + test_dict.Print(); + idLib::fileSystem->FreeFile( buffer ); + } else { + idLib::common->Printf( "idDict.base64.test not found\n" ); + } +} + +#endif diff --git a/source/idlib/Base64.h b/source/idlib/Base64.h new file mode 100644 index 0000000..53cf0aa --- /dev/null +++ b/source/idlib/Base64.h @@ -0,0 +1,84 @@ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +/* +=============================================================================== + + base64 + +=============================================================================== +*/ + +class idBase64 { +public: + idBase64( void ); + idBase64( const idStr &s ); + ~idBase64( void ); + + void Encode( const byte *from, int size ); + void Encode( const idStr &src ); + int DecodeLength( void ) const; // minimum size in bytes of destination buffer for decoding + int Decode( byte *to ) const; // does not append a \0 - needs a DecodeLength() bytes buffer + void Decode( idStr &dest ) const; // decodes the binary content to an idStr (a bit dodgy, \0 and other non-ascii are possible in the decoded content) + void Decode( idFile *dest ) const; + + const char *c_str() const; + + void operator=( const idStr &s ); + +private: + byte * data; + int len; + int alloced; + + void Init( void ); + void Release( void ); + void EnsureAlloced( int size ); +}; + +ID_INLINE idBase64::idBase64( void ) { + Init(); +} + +ID_INLINE idBase64::idBase64( const idStr &s ) { + Init(); + *this = s; +} + +ID_INLINE idBase64::~idBase64( void ) { + Release(); +} + +ID_INLINE const char *idBase64::c_str( void ) const { + return (const char *)data; +} + +ID_INLINE void idBase64::Init( void ) { + len = 0; + alloced = 0; + data = NULL; +} + +ID_INLINE void idBase64::Release( void ) { + if ( data ) { + delete[] data; + } + Init(); +} + +ID_INLINE void idBase64::EnsureAlloced( int size ) { + if ( size > alloced ) { + Release(); + } + data = new byte[size]; + alloced = size; +} + +ID_INLINE void idBase64::operator=( const idStr &s ) { + EnsureAlloced( s.Length()+1 ); // trailing \0 - beware, this does a Release + strcpy( (char *)data, s.c_str() ); + len = s.Length(); +} + +#endif /* !__BASE64_H__ */ diff --git a/source/idlib/BitMsg.cpp b/source/idlib/BitMsg.cpp new file mode 100644 index 0000000..22e36b1 --- /dev/null +++ b/source/idlib/BitMsg.cpp @@ -0,0 +1,1459 @@ +#include "precompiled.h" +#pragma hdrstop + + +/* +============================================================================== + + idBitMsg + +============================================================================== +*/ + +/* +================ +idBitMsg::idBitMsg +================ +*/ +idBitMsg::idBitMsg() { + writeData = NULL; + readData = NULL; + maxSize = 0; + curSize = 0; + writeBit = 0; + readCount = 0; + readBit = 0; + allowOverflow = false; + overflowed = false; +} + +/* +================ +idBitMsg::CheckOverflow +================ +*/ +bool idBitMsg::CheckOverflow( int numBits ) { + assert( numBits >= 0 ); + if ( numBits > GetRemainingWriteBits() ) { + if ( !allowOverflow ) { + idLib::common->FatalError( va( "idBitMsg: overflow without allowOverflow set: %d > %d*8", GetNumBitsWritten()+numBits, GetMaxSize() ) ); + } + if ( numBits > ( maxSize << 3 ) ) { + idLib::common->FatalError( "idBitMsg: %i bits is > full message size", numBits ); + } + idLib::common->Printf( "idBitMsg: overflow\n" ); + BeginWriting(); + overflowed = true; + return true; + } + return false; +} + +/* +================ +idBitMsg::GetByteSpace +================ +*/ +byte *idBitMsg::GetByteSpace( int length ) { + byte *ptr; + + if ( !writeData ) { + idLib::common->FatalError( "idBitMsg::GetByteSpace: cannot write to message" ); + } + + // round up to the next byte + WriteByteAlign(); + + // check for overflow + CheckOverflow( length << 3 ); + + ptr = writeData + curSize; + curSize += length; + return ptr; +} + +/* +================ +idBitMsg::WriteBits + + If the number of bits is negative a sign is included. +================ +*/ +void idBitMsg::WriteBits( int value, int numBits ) { + int put; + int fraction; + + if ( !writeData ) { + idLib::common->Error( "idBitMsg::WriteBits: cannot write to message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::common->Error( "idBitMsg::WriteBits: bad numBits %i", numBits ); + } + + // check for value overflows + if ( numBits != 32 ) { + if ( numBits > 0 ) { + if ( value > ( 1 << numBits ) - 1 ) { + idLib::common->Warning( "idBitMsg::WriteBits: value overflow %d %d", value, numBits ); + } else if ( value < 0 ) { + idLib::common->Warning( "idBitMsg::WriteBits: value overflow %d %d", value, numBits ); + } + } else { + int r = 1 << ( - 1 - numBits ); + if ( value > r - 1 ) { + idLib::common->Warning( "idBitMsg::WriteBits: value overflow %d %d", value, numBits ); + } else if ( value < -r ) { + idLib::common->Warning( "idBitMsg::WriteBits: value overflow %d %d", value, numBits ); + } + } + } + + if ( numBits < 0 ) { + numBits = -numBits; + } + + // check for msg overflow + if ( CheckOverflow( numBits ) ) { + return; + } + + // write the bits + while( numBits ) { + if ( writeBit == 0 ) { + writeData[curSize] = 0; + curSize++; + } + put = 8 - writeBit; + if ( put > numBits ) { + put = numBits; + } + fraction = value & ( ( 1 << put ) - 1 ); + writeData[curSize - 1] |= fraction << writeBit; + numBits -= put; + value >>= put; + writeBit = ( writeBit + put ) & 7; + } +} + +/* +================ +idBitMsg::WriteString +================ +*/ +void idBitMsg::WriteString( const char *s, int maxLength ) { + if ( !s ) { + WriteData( "", 1 ); + } else { + int l; + byte *dataPtr; + + l = idStr::Length( s ); + if ( maxLength >= 0 && l >= maxLength ) { + l = maxLength - 1; + } + dataPtr = GetByteSpace( l + 1 ); + memcpy( dataPtr, s, l ); + dataPtr[l] = '\0'; + } +} + +/* +================ +idBitMsg::WriteData +================ +*/ +void idBitMsg::WriteData( const void *data, int length ) { + memcpy( GetByteSpace( length ), data, length ); +} + +/* +================ +idBitMsg::WriteNetadr +================ +*/ +void idBitMsg::WriteNetadr( const netadr_t adr ) { + byte *dataPtr; + dataPtr = GetByteSpace( 4 ); + memcpy( dataPtr, adr.ip, 4 ); + WriteUShort( adr.port ); +} + +/* +================ +idBitMsg::WriteDelta +================ +*/ +void idBitMsg::WriteDelta( int oldValue, int newValue, int numBits ) { + if ( oldValue == newValue ) { + WriteBits( 0, 1 ); + return; + } + WriteBits( 1, 1 ); + WriteBits( newValue, numBits ); +} + +/* +================ +idBitMsg::WriteDeltaByteCounter +================ +*/ +void idBitMsg::WriteDeltaByteCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 7; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 3 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================ +idBitMsg::WriteDeltaShortCounter +================ +*/ +void idBitMsg::WriteDeltaShortCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 15; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 4 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================ +idBitMsg::WriteDeltaLongCounter +================ +*/ +void idBitMsg::WriteDeltaLongCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 31; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 5 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================== +idBitMsg::WriteDeltaDict +================== +*/ +bool idBitMsg::WriteDeltaDict( const idDict &dict, const idDict *base ) { + int i; + const idKeyValue *kv, *basekv; + bool changed = false; + + if ( base != NULL ) { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + basekv = base->FindKey( kv->GetKey() ); + if ( basekv == NULL || basekv->GetValue().Cmp( kv->GetValue() ) != 0 ) { + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + } + + WriteString( "" ); + + for ( i = 0; i < base->GetNumKeyVals(); i++ ) { + basekv = base->GetKeyVal( i ); + kv = dict.FindKey( basekv->GetKey() ); + if ( kv == NULL ) { + WriteString( basekv->GetKey() ); + changed = true; + } + } + + WriteString( "" ); + + } else { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + WriteString( "" ); + + WriteString( "" ); + + } + + return changed; +} + +/* +================ +idBitMsg::ReadBits + + If the number of bits is negative a sign is included. +================ +*/ +int idBitMsg::ReadBits( int numBits ) const { + int value; + int valueBits; + int get; + int fraction; + bool sgn; + + if ( !readData ) { + idLib::common->FatalError( "idBitMsg::ReadBits: cannot read from message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::common->FatalError( "idBitMsg::ReadBits: bad numBits %i", numBits ); + } + + value = 0; + valueBits = 0; + + if ( numBits < 0 ) { + numBits = -numBits; + sgn = true; + } else { + sgn = false; + } + + // check for overflow + if ( numBits > GetRemainingReadBits() ) { + return -1; + } + + while ( valueBits < numBits ) { + if ( readBit == 0 ) { + readCount++; + } + get = 8 - readBit; + if ( get > (numBits - valueBits) ) { + get = numBits - valueBits; + } + fraction = readData[readCount - 1]; + fraction >>= readBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + + valueBits += get; + readBit = ( readBit + get ) & 7; + } + + if ( sgn ) { + if ( value & ( 1 << ( numBits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << numBits ) - 1 ); + } + } + + return value; +} + +/* +================ +idBitMsg::ReadString +================ +*/ +int idBitMsg::ReadString( char *buffer, int bufferSize ) const { + int l, c; + + ReadByteAlign(); + l = 0; + while( 1 ) { + c = ReadByte(); + if ( c <= 0 || c >= 255 ) { + break; + } + // translate all fmt spec to avoid crash bugs in string routines + if ( c == '%' ) { + c = '.'; + } + + // we will read past any excessively long string, so + // the following data can be read, but the string will + // be truncated + if ( l < bufferSize - 1 ) { + buffer[l] = c; + l++; + } + } + + buffer[l] = 0; + return l; +} + +/* +================ +idBitMsg::ReadData +================ +*/ +int idBitMsg::ReadData( void *data, int length ) const { + int cnt; + + ReadByteAlign(); + cnt = readCount; + + if ( readCount + length > curSize ) { + if ( data ) { + memcpy( data, readData + readCount, GetRemainingData() ); + } + readCount = curSize; + } else { + if ( data ) { + memcpy( data, readData + readCount, length ); + } + readCount += length; + } + + return ( readCount - cnt ); +} + +/* +================ +idBitMsg::ReadNetadr +================ +*/ +void idBitMsg::ReadNetadr( netadr_t *adr ) const { + int i; + + adr->type = NA_IP; + for ( i = 0; i < 4; i++ ) { + adr->ip[ i ] = ReadByte(); + } + adr->port = ReadUShort(); +} + +/* +================ +idBitMsg::ReadDelta +================ +*/ +int idBitMsg::ReadDelta( int oldValue, int numBits ) const { + if ( ReadBits( 1 ) ) { + return ReadBits( numBits ); + } + return oldValue; +} + +/* +================ +idBitMsg::ReadDeltaByteCounter +================ +*/ +int idBitMsg::ReadDeltaByteCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 3 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================ +idBitMsg::ReadDeltaShortCounter +================ +*/ +int idBitMsg::ReadDeltaShortCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 4 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================ +idBitMsg::ReadDeltaLongCounter +================ +*/ +int idBitMsg::ReadDeltaLongCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 5 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================== +idBitMsg::ReadDeltaDict +================== +*/ +bool idBitMsg::ReadDeltaDict( idDict &dict, const idDict *base ) const { + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + bool changed = false; + + if ( base != NULL ) { + dict = *base; + } else { + dict.Clear(); + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + ReadString( value, sizeof( value ) ); + dict.Set( key, value ); + changed = true; + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + dict.Delete( key ); + changed = true; + } + + return changed; +} + +/* +================ +idBitMsg::DirToBits +================ +*/ +int idBitMsg::DirToBits( const idVec3 &dir, int numBits ) { + int max, bits; + float bias; + + assert( numBits >= 6 && numBits <= 32 ); + assert( dir.LengthSqr() - 1.0f < 0.01f ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + bias = 0.5f / max; + + bits = FLOATSIGNBITSET( dir.x ) << ( numBits * 3 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.x ) + bias ) * max ) ) << ( numBits * 2 ); + bits |= FLOATSIGNBITSET( dir.y ) << ( numBits * 2 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.y ) + bias ) * max ) ) << ( numBits * 1 ); + bits |= FLOATSIGNBITSET( dir.z ) << ( numBits * 1 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.z ) + bias ) * max ) ) << ( numBits * 0 ); + return bits; +} + +/* +================ +idBitMsg::BitsToDir +================ +*/ +idVec3 idBitMsg::BitsToDir( int bits, int numBits ) { + static float sign[2] = { 1.0f, -1.0f }; + int max; + float invMax; + idVec3 dir; + + assert( numBits >= 6 && numBits <= 32 ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + invMax = 1.0f / max; + + dir.x = sign[( bits >> ( numBits * 3 - 1 ) ) & 1] * ( ( bits >> ( numBits * 2 ) ) & max ) * invMax; + dir.y = sign[( bits >> ( numBits * 2 - 1 ) ) & 1] * ( ( bits >> ( numBits * 1 ) ) & max ) * invMax; + dir.z = sign[( bits >> ( numBits * 1 - 1 ) ) & 1] * ( ( bits >> ( numBits * 0 ) ) & max ) * invMax; + dir.NormalizeFast(); + return dir; +} + + +/* +============================================================================== + + idBitMsgDelta + +============================================================================== +*/ + +const int MAX_DATA_BUFFER = 1024; + +/* +================ +idBitMsgDelta::WriteBits +================ +*/ +void idBitMsgDelta::WriteBits( int value, int numBits ) { + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + + if ( !base ) { + writeDelta->WriteBits( value, numBits ); + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( baseValue == value ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( value, numBits ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDelta +================ +*/ +void idBitMsgDelta::WriteDelta( int oldValue, int newValue, int numBits ) { + if ( newBase ) { + newBase->WriteBits( newValue, numBits ); + } + + if ( !base ) { + if ( oldValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( newValue, numBits ); + } + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + if ( oldValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + changed = true; + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( newValue, numBits ); + changed = true; + } + } + } +} + +/* +================ +idBitMsgDelta::ReadBits +================ +*/ +int idBitMsgDelta::ReadBits( int numBits ) const { + int value; + + if ( !base ) { + value = readDelta->ReadBits( numBits ); + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadBits( numBits ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDelta +================ +*/ +int idBitMsgDelta::ReadDelta( int oldValue, int numBits ) const { + int value; + + if ( !base ) { + if ( readDelta->ReadBits( 1 ) == 0 ) { + value = oldValue; + } else { + value = readDelta->ReadBits( numBits ); + } + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else if ( readDelta->ReadBits( 1 ) == 0 ) { + value = oldValue; + changed = true; + } else { + value = readDelta->ReadBits( numBits ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + return value; +} + +/* +================ +idBitMsgDelta::WriteString +================ +*/ +void idBitMsgDelta::WriteString( const char *s, int maxLength ) { + if ( newBase ) { + newBase->WriteString( s, maxLength ); + } + + if ( !base ) { + writeDelta->WriteString( s, maxLength ); + changed = true; + } else { + char baseString[MAX_DATA_BUFFER]; + base->ReadString( baseString, sizeof( baseString ) ); + if ( idStr::Cmp( s, baseString ) == 0 ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteString( s, maxLength ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteData +================ +*/ +void idBitMsgDelta::WriteData( const void *data, int length ) { + if ( newBase ) { + newBase->WriteData( data, length ); + } + + if ( !base ) { + writeDelta->WriteData( data, length ); + changed = true; + } else { + byte baseData[MAX_DATA_BUFFER]; + assert( length < sizeof( baseData ) ); + base->ReadData( baseData, length ); + if ( memcmp( data, baseData, length ) == 0 ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteData( data, length ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDict +================ +*/ +void idBitMsgDelta::WriteDict( const idDict &dict ) { + if ( newBase ) { + newBase->WriteDeltaDict( dict, NULL ); + } + + if ( !base ) { + writeDelta->WriteDeltaDict( dict, NULL ); + changed = true; + } else { + idDict baseDict; + base->ReadDeltaDict( baseDict, NULL ); + changed = writeDelta->WriteDeltaDict( dict, &baseDict ); + } +} + +/* +================ +idBitMsgDelta::WriteDeltaByteCounter +================ +*/ +void idBitMsgDelta::WriteDeltaByteCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 8 ); + } + + if ( !base ) { + writeDelta->WriteDeltaByteCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 8 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaByteCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDeltaShortCounter +================ +*/ +void idBitMsgDelta::WriteDeltaShortCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 16 ); + } + + if ( !base ) { + writeDelta->WriteDeltaShortCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 16 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaShortCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDeltaLongCounter +================ +*/ +void idBitMsgDelta::WriteDeltaLongCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 32 ); + } + + if ( !base ) { + writeDelta->WriteDeltaLongCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 32 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaLongCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::ReadString +================ +*/ +void idBitMsgDelta::ReadString( char *buffer, int bufferSize ) const { + if ( !base ) { + readDelta->ReadString( buffer, bufferSize ); + changed = true; + } else { + char baseString[MAX_DATA_BUFFER]; + base->ReadString( baseString, sizeof( baseString ) ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + idStr::Copynz( buffer, baseString, bufferSize ); + } else { + readDelta->ReadString( buffer, bufferSize ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteString( buffer ); + } +} + +/* +================ +idBitMsgDelta::ReadData +================ +*/ +void idBitMsgDelta::ReadData( void *data, int length ) const { + if ( !base ) { + readDelta->ReadData( data, length ); + changed = true; + } else { + char baseData[MAX_DATA_BUFFER]; + assert( length < sizeof( baseData ) ); + base->ReadData( baseData, length ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + memcpy( data, baseData, length ); + } else { + readDelta->ReadData( data, length ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteData( data, length ); + } +} + +/* +================ +idBitMsgDelta::ReadDict +================ +*/ +void idBitMsgDelta::ReadDict( idDict &dict ) { + if ( !base ) { + readDelta->ReadDeltaDict( dict, NULL ); + changed = true; + } else { + idDict baseDict; + base->ReadDeltaDict( baseDict, NULL ); + if ( !readDelta ) { + dict = baseDict; + } else { + changed = readDelta->ReadDeltaDict( dict, &baseDict ); + } + } + + if ( newBase ) { + newBase->WriteDeltaDict( dict, NULL ); + } +} + +/* +================ +idBitMsgDelta::ReadDeltaByteCounter +================ +*/ +int idBitMsgDelta::ReadDeltaByteCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaByteCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 8 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaByteCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 8 ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDeltaShortCounter +================ +*/ +int idBitMsgDelta::ReadDeltaShortCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaShortCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 16 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaShortCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 16 ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDeltaLongCounter +================ +*/ +int idBitMsgDelta::ReadDeltaLongCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaLongCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 32 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaLongCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 32 ); + } + return value; +} + +/* +=========================================================================== +idMsgQueue +=========================================================================== +*/ + +/* +=============== +idMsgQueue::idMsgQueue +=============== +*/ +idMsgQueue::idMsgQueue( void ) { + Init( 0 ); +} + +/* +=============== +idMsgQueue::Init +=============== +*/ +void idMsgQueue::Init( int sequence ) { + first = last = sequence; + startIndex = endIndex = 0; +} + +/* +=============== +idMsgQueue::Add +=============== +*/ +bool idMsgQueue::Add( const byte *data, const int size, bool sequencing ) { + if ( GetSpaceLeft() < size + 8 ) { + return false; + } + + assert( size ); + + WriteUShort( size ); + if ( sequencing ) { + WriteLong( last ); + } + last++; + WriteData( data, size ); + return true; +} + +/* +=============== +idMsgQueue::AddConcat +=============== +*/ +bool idMsgQueue::AddConcat( const byte *data1, const int size1, const byte *data2, const int size2, bool sequencing ) { + if ( GetSpaceLeft() < size1 + size2 + 8 ) { + return false; + } + + assert( size1 && size2 ); + + WriteUShort( size1 + size2 ); + if ( sequencing ) { + WriteLong( last ); + } + last++; + WriteData( data1, size1 ); + WriteData( data2, size2 ); + return true; +} + +/* +=============== +idMsgQueue::Get +=============== +*/ +bool idMsgQueue::Get( byte *data, int dataSize, int &size, bool sequencing ) { + if ( sequencing ? ( first == last ) : ( startIndex == endIndex ) ) { + size = 0; + return false; + } + int sequence; + size = ReadUShort(); + if ( data && size > dataSize ) { + common->Error( "idMsgQueue::Get buffer size of %d < get size of %d", dataSize, size ); + } + + if ( sequencing ) { + sequence = ReadLong(); + assert( sequence == first ); + } + ReadData( data, size ); + first++; + return true; +} + +/* +=============== +idMsgQueue::GetTotalSize +=============== +*/ +int idMsgQueue::GetTotalSize( void ) const { + if ( startIndex <= endIndex ) { + return ( endIndex - startIndex ); + } else { + return ( sizeof( buffer ) - startIndex + endIndex ); + } +} + +/* +=============== +idMsgQueue::GetSpaceLeft +=============== +*/ +int idMsgQueue::GetSpaceLeft( void ) const { + if ( startIndex <= endIndex ) { + return sizeof( buffer ) - ( endIndex - startIndex ) - 1; + } else { + return ( startIndex - endIndex ) - 1; + } +} + +/* +=============== +idMsgQueue::CopyToBuffer +=============== +*/ +void idMsgQueue::CopyToBuffer( byte *buf ) const { + if ( startIndex <= endIndex ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( buf, buffer + startIndex, endIndex - startIndex ); +// RAVEN END + } else { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( buf, buffer + startIndex, sizeof( buffer ) - startIndex ); + SIMDProcessor->Memcpy( buf + sizeof( buffer ) - startIndex, buffer, endIndex ); +// RAVEN END + } +} + +/* +=============== +idMsgQueue::WriteByte +=============== +*/ +void idMsgQueue::WriteByte( byte b ) { + buffer[endIndex] = b; + endIndex = ( endIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); +} + +/* +=============== +idMsgQueue::ReadByte +=============== +*/ +byte idMsgQueue::ReadByte( void ) { + byte b = buffer[startIndex]; + startIndex = ( startIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); + return b; +} + +/* +=============== +idMsgQueue::WriteShort +=============== +*/ +void idMsgQueue::WriteShort( int s ) { + WriteByte( ( s >> 0 ) & 255 ); + WriteByte( ( s >> 8 ) & 255 ); +} + +/* +=============== +idMsgQueue::WriteUShort +=============== +*/ +void idMsgQueue::WriteUShort( int s ) { + WriteByte( ( s >> 0 ) & 255 ); + WriteByte( ( s >> 8 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadShort +=============== +*/ +int idMsgQueue::ReadShort( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte l = ReadByte(); + byte h = ReadByte(); + return (short)(l | ( h << 8 )); // sign extend +// RAVEN LOW +} + +/* +=============== +idMsgQueue::ReadUShort +=============== +*/ +int idMsgQueue::ReadUShort( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte l = ReadByte(); + byte h = ReadByte(); + return l | ( h << 8 ); +// RAVEN LOW +} + +/* +=============== +idMsgQueue::WriteLong +=============== +*/ +void idMsgQueue::WriteLong( int l ) { + WriteByte( ( l >> 0 ) & 255 ); + WriteByte( ( l >> 8 ) & 255 ); + WriteByte( ( l >> 16 ) & 255 ); + WriteByte( ( l >> 24 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadLong +=============== +*/ +int idMsgQueue::ReadLong( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte ll = ReadByte(); + byte lh = ReadByte(); + byte hl = ReadByte(); + byte hh = ReadByte(); + return ll | ( lh << 8 ) | ( hl << 16 ) | ( hh << 24 ); +// RAVEN END +} + +/* +=============== +idMsgQueue::WriteData +=============== +*/ +void idMsgQueue::WriteData( const byte *data, const int size ) { + for ( int i = 0; i < size; i++ ) { + WriteByte( data[i] ); + } +} + +/* +=============== +idMsgQueue::ReadData +=============== +*/ +void idMsgQueue::ReadData( byte *data, const int size ) { + if ( data ) { + for ( int i = 0; i < size; i++ ) { + data[i] = ReadByte(); + } + } else { + for ( int i = 0; i < size; i++ ) { + ReadByte(); + } + } +} + +/* +=============== +idMsgQueue::WriteTo +=============== +*/ +void idMsgQueue::WriteTo( idBitMsg &msg ) { + msg.WriteUShort( GetTotalSize() ); + assert( startIndex == 0 ); + msg.WriteData( buffer + startIndex, endIndex - startIndex ); +} + +/* +=============== +idMsgQueue::FlushTo +=============== +*/ +void idMsgQueue::FlushTo( idBitMsg &msg ) { + WriteTo( msg ); + Init( 0 ); +} + +/* +=============== +idMsgQueue::ReadFrom +=============== +*/ +void idMsgQueue::ReadFrom( const idBitMsg &msg ) { + Init( 0 ); + endIndex = msg.ReadUShort(); + msg.ReadData( buffer, endIndex ); +} + +/* +=============== +idMsgQueue::Save +=============== +*/ +void idMsgQueue::Save( idFile *file ) const { + file->WriteInt( first ); + file->WriteInt( last ); + file->WriteInt( startIndex ); + file->WriteInt( endIndex ); + file->Write( buffer + startIndex, endIndex - startIndex ); +} +/* +=============== +idMsgQueue::Restore +=============== +*/ +void idMsgQueue::Restore( idFile *file ) { + file->ReadInt( first ); + file->ReadInt( last ); + file->ReadInt( startIndex ); + file->ReadInt( endIndex ); + file->Read( buffer + startIndex, endIndex - startIndex ); +} + +/* +=============== +idBitMsgQueue::idBitMsgQueue +=============== +*/ +idBitMsgQueue::idBitMsgQueue( void ) { + Init(); +} + +/* +=============== +idBitMsgQueue::Init +=============== +*/ +void idBitMsgQueue::Init( void ) { + readTimestamp = false; + writeList.Clear(); + readList.Clear(); +} + +/* +=============== +idBitMsgQueue::Add +=============== +*/ +void idBitMsgQueue::Add( const idBitMsg &msg, const int timestamp ) { + while ( writeList.NextNode() && writeList.Next()->GetSpaceLeft() < ( msg.GetSize() + 8+4 ) ) { + writeList.NextNode()->AddToEnd( readList ); + } + + if ( !writeList.NextNode() ) { + idLinkList< idMsgQueue > *node = new idLinkList< idMsgQueue >; + node->SetOwner( new idMsgQueue ); + node->AddToEnd( writeList ); + } + + writeList.Next()->WriteLong( timestamp ); + if ( !writeList.Next()->Add( msg.GetData(), msg.GetSize(), false ) ) { + assert( false ); + } +} + +/* +=============== +idBitMsgQueue::Get +=============== +*/ +bool idBitMsgQueue::Get( idBitMsg &msg, int ×tamp ) { + while ( readList.NextNode() && !readList.Next()->GetTotalSize() ) { + readList.Next()->Init( 0 ); + readList.NextNode()->AddToEnd( writeList ); + } + + if ( readList.NextNode() ) { + int size; + + timestamp = readTimestamp ? nextTimestamp : readList.Next()->ReadLong(); + readTimestamp = true; + if ( readList.Next()->Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + readTimestamp = false; + return true; + } + + assert( false ); + } else if ( writeList.NextNode() && writeList.Next()->GetTotalSize() ) { + int size; + + timestamp = readTimestamp ? nextTimestamp : writeList.Next()->ReadLong(); + readTimestamp = true; + if ( writeList.Next()->Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + readTimestamp = false; + return true; + } + + assert( false ); + } + + msg.SetSize( 0 ); + return false; +} + +/* +=============== +idBitMsgQueue::GetTimestamp +=============== +*/ +bool idBitMsgQueue::GetTimestamp( int ×tamp ) { + if ( readTimestamp ) { + timestamp = nextTimestamp; + return true; + } + + while ( readList.NextNode() && !readList.Next()->GetTotalSize() ) { + readList.Next()->Init( 0 ); + readList.NextNode()->AddToEnd( writeList ); + } + + if ( readList.NextNode() ) { + timestamp = nextTimestamp = readList.Next()->ReadLong(); + readTimestamp = true; + return true; + } else if ( writeList.NextNode() && writeList.Next()->GetTotalSize() ) { + timestamp = nextTimestamp = writeList.Next()->ReadLong(); + readTimestamp = true; + return true; + } + + return false; +} diff --git a/source/idlib/BitMsg.h b/source/idlib/BitMsg.h new file mode 100644 index 0000000..bbb37d8 --- /dev/null +++ b/source/idlib/BitMsg.h @@ -0,0 +1,865 @@ + +#ifndef __BITMSG_H__ +#define __BITMSG_H__ + +/* +=============================================================================== + + idBitMsg + + Handles byte ordering and avoids alignment errors. + Allows concurrent writing and reading. + The data set with Init is never freed. + +=============================================================================== +*/ + +class idBitMsg { +public: + idBitMsg(); + ~idBitMsg() {} + + void Init( byte *data, int length ); + void Init( const byte *data, int length ); + byte * GetData( void ); // get data for writing + const byte * GetData( void ) const; // get data for reading + const byte * GetReadData( void ) const; + int GetMaxSize( void ) const; // get the maximum message size + void SetAllowOverflow( bool set ); // generate error if not set and message is overflowed + bool IsOverflowed( void ) const; // returns true if the message was overflowed + + int GetSize( void ) const; // size of the message in bytes + void SetSize( int size ); // set the message size + int GetWriteBit( void ) const; // get current write bit + void SetWriteBit( int bit ); // set current write bit + int GetNumBitsWritten( void ) const; // returns number of bits written + int GetRemainingSpace( void ) const; // space left in bytes for writing + int GetRemainingWriteBits( void ) const; // space left in bits for writing + void SaveWriteState( int &s, int &b ) const; // save the write state + void RestoreWriteState( int s, int b ); // restore the write state + + int GetReadCount( void ) const; // bytes read so far + void SetReadCount( int bytes ); // set the number of bytes and bits read + int GetReadBit( void ) const; // get current read bit + void SetReadBit( int bit ); // set current read bit + int GetNumBitsRead( void ) const; // returns number of bits read + int GetRemainingData( void ) const; // number of bytes left to read + int GetRemainingReadBits( void ) const; // number of bits left to read + void SaveReadState( int &c, int &b ) const; // save the read state + void RestoreReadState( int c, int b ) const; // restore the read state + + void BeginWriting( void ); // begin writing + void WriteByteAlign( void ); // write up to the next byte boundary + void WriteBits( int value, int numBits ); // write the specified number of bits + void WriteChar( int c ); + void WriteByte( int c ); + void WriteShort( int c ); + void WriteUShort( int c ); + void WriteLong( int c ); + void WriteFloat( float f ); + void WriteFloat( float f, int exponentBits, int mantissaBits ); + void WriteAngle8( float f ); + void WriteAngle16( float f ); + void WriteDir( const idVec3 &dir, int numBits ); + void WriteString( const char *s, int maxLength = -1 ); + void WriteData( const void *data, int length ); + void WriteNetadr( const netadr_t adr ); + + void WriteDeltaChar( int oldValue, int newValue ); + void WriteDeltaByte( int oldValue, int newValue ); + void WriteDeltaShort( int oldValue, int newValue ); + void WriteDeltaLong( int oldValue, int newValue ); + void WriteDeltaFloat( float oldValue, float newValue ); + void WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ); + void WriteDeltaByteCounter( int oldValue, int newValue ); + void WriteDeltaShortCounter( int oldValue, int newValue ); + void WriteDeltaLongCounter( int oldValue, int newValue ); + bool WriteDeltaDict( const idDict &dict, const idDict *base ); + + void BeginReading( void ) const; // begin reading. + void ReadByteAlign( void ) const; // read up to the next byte boundary + int ReadBits( int numBits ) const; // read the specified number of bits + int ReadChar( void ) const; + int ReadByte( void ) const; + int ReadShort( void ) const; + int ReadUShort( void ) const; + int ReadLong( void ) const; + float ReadFloat( void ) const; + float ReadFloat( int exponentBits, int mantissaBits ) const; + float ReadAngle8( void ) const; + float ReadAngle16( void ) const; + idVec3 ReadDir( int numBits ) const; + int ReadString( char *buffer, int bufferSize ) const; + int ReadData( void *data, int length ) const; + void ReadNetadr( netadr_t *adr ) const; + + int ReadDeltaChar( int oldValue ) const; + int ReadDeltaByte( int oldValue ) const; + int ReadDeltaShort( int oldValue ) const; + int ReadDeltaLong( int oldValue ) const; + float ReadDeltaFloat( float oldValue ) const; + float ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const; + int ReadDeltaByteCounter( int oldValue ) const; + int ReadDeltaShortCounter( int oldValue ) const; + int ReadDeltaLongCounter( int oldValue ) const; + bool ReadDeltaDict( idDict &dict, const idDict *base ) const; + + static int DirToBits( const idVec3 &dir, int numBits ); + static idVec3 BitsToDir( int bits, int numBits ); + +private: + byte * writeData; // pointer to data for writing + const byte * readData; // pointer to data for reading + int maxSize; // maximum size of message in bytes + int curSize; // current size of message in bytes + int writeBit; // number of bits written to the last written byte + mutable int readCount; // number of bytes read so far + mutable int readBit; // number of bits read from the last read byte + bool allowOverflow; // if false, generate an error when the message is overflowed + bool overflowed; // set to true if the buffer size failed (with allowOverflow set) + +private: + bool CheckOverflow( int numBits ); + byte * GetByteSpace( int length ); + void WriteDelta( int oldValue, int newValue, int numBits ); + int ReadDelta( int oldValue, int numBits ) const; +}; + + +ID_INLINE void idBitMsg::Init( byte *data, int length ) { + writeData = data; + readData = data; + maxSize = length; +} + +ID_INLINE void idBitMsg::Init( const byte *data, int length ) { + writeData = NULL; + readData = data; + maxSize = length; +} + +ID_INLINE byte *idBitMsg::GetData( void ) { + return writeData; +} + +ID_INLINE const byte *idBitMsg::GetData( void ) const { + return readData; +} + +ID_INLINE const byte *idBitMsg::GetReadData( void ) const { + return ( readData + readCount ); +} + +ID_INLINE int idBitMsg::GetMaxSize( void ) const { + return maxSize; +} + +ID_INLINE void idBitMsg::SetAllowOverflow( bool set ) { + allowOverflow = set; +} + +ID_INLINE bool idBitMsg::IsOverflowed( void ) const { + return overflowed; +} + +ID_INLINE int idBitMsg::GetSize( void ) const { + return curSize; +} + +ID_INLINE void idBitMsg::SetSize( int size ) { + if ( size > maxSize ) { + curSize = maxSize; + } else { + curSize = size; + } +} + +ID_INLINE int idBitMsg::GetWriteBit( void ) const { + return writeBit; +} + +ID_INLINE void idBitMsg::SetWriteBit( int bit ) { + writeBit = bit & 7; + if ( writeBit ) { + writeData[curSize - 1] &= ( 1 << writeBit ) - 1; + } +} + +ID_INLINE int idBitMsg::GetNumBitsWritten( void ) const { +// return ( ( curSize << 3 ) - ( writeBit ? 8 - writeBit : 0 ) ); + return ( ( curSize << 3 ) - ( ( 8 - writeBit ) & 7 ) ); +} + +ID_INLINE int idBitMsg::GetRemainingSpace( void ) const { + return maxSize - curSize; +} + +ID_INLINE int idBitMsg::GetRemainingWriteBits( void ) const { + return ( maxSize << 3 ) - GetNumBitsWritten(); +} + +ID_INLINE void idBitMsg::SaveWriteState( int &s, int &b ) const { + s = curSize; + b = writeBit; +} + +ID_INLINE void idBitMsg::RestoreWriteState( int s, int b ) { + curSize = s; + writeBit = b & 7; + if ( writeBit ) { + writeData[curSize - 1] &= ( 1 << writeBit ) - 1; + } +} + +ID_INLINE int idBitMsg::GetReadCount( void ) const { + return readCount; +} + +ID_INLINE void idBitMsg::SetReadCount( int bytes ) { + readCount = bytes; +} + +ID_INLINE int idBitMsg::GetReadBit( void ) const { + return readBit; +} + +ID_INLINE void idBitMsg::SetReadBit( int bit ) { + readBit = bit & 7; +} + +ID_INLINE int idBitMsg::GetNumBitsRead( void ) const { +// return ( ( readCount << 3 ) - ( readBit ? 8 - readBit : 0 ) ); + return ( ( readCount << 3 ) - ( ( 8 - readBit ) & 7 ) ); +} + +ID_INLINE int idBitMsg::GetRemainingData( void ) const { + return curSize - readCount; +} + +ID_INLINE int idBitMsg::GetRemainingReadBits( void ) const { + return ( curSize << 3 ) - GetNumBitsRead(); +} + +ID_INLINE void idBitMsg::SaveReadState( int &c, int &b ) const { + c = readCount; + b = readBit; +} + +ID_INLINE void idBitMsg::RestoreReadState( int c, int b ) const { + readCount = c; + readBit = b & 7; +} + +ID_INLINE void idBitMsg::BeginWriting( void ) { + curSize = 0; + overflowed = false; + writeBit = 0; +} + +ID_INLINE void idBitMsg::WriteByteAlign( void ) { + writeBit = 0; +} + +ID_INLINE void idBitMsg::WriteChar( int c ) { + WriteBits( c, -8 ); +} + +ID_INLINE void idBitMsg::WriteByte( int c ) { + WriteBits( c, 8 ); +} + +ID_INLINE void idBitMsg::WriteShort( int c ) { + WriteBits( c, -16 ); +} + +ID_INLINE void idBitMsg::WriteUShort( int c ) { + WriteBits( c, 16 ); +} + +ID_INLINE void idBitMsg::WriteLong( int c ) { + WriteBits( c, 32 ); +} + +ID_INLINE void idBitMsg::WriteFloat( float f ) { + WriteBits( *reinterpret_cast(&f), 32 ); +} + +ID_INLINE void idBitMsg::WriteFloat( float f, int exponentBits, int mantissaBits ) { + int bits = idMath::FloatToBits( f, exponentBits, mantissaBits ); + WriteBits( bits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE void idBitMsg::WriteAngle8( float f ) { + WriteByte( ANGLE2BYTE( f ) ); +} + +ID_INLINE void idBitMsg::WriteAngle16( float f ) { + WriteShort( ANGLE2SHORT(f) ); +} + +ID_INLINE void idBitMsg::WriteDir( const idVec3 &dir, int numBits ) { + WriteBits( DirToBits( dir, numBits ), numBits ); +} + +ID_INLINE void idBitMsg::WriteDeltaChar( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -8 ); +} + +ID_INLINE void idBitMsg::WriteDeltaByte( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 8 ); +} + +ID_INLINE void idBitMsg::WriteDeltaShort( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -16 ); +} + +ID_INLINE void idBitMsg::WriteDeltaLong( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 32 ); +} + +ID_INLINE void idBitMsg::WriteDeltaFloat( float oldValue, float newValue ) { + WriteDelta( *reinterpret_cast(&oldValue), *reinterpret_cast(&newValue), 32 ); +} + +ID_INLINE void idBitMsg::WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ) { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = idMath::FloatToBits( newValue, exponentBits, mantissaBits ); + WriteDelta( oldBits, newBits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE void idBitMsg::BeginReading( void ) const { + readCount = 0; + readBit = 0; +} + +ID_INLINE void idBitMsg::ReadByteAlign( void ) const { + readBit = 0; +} + +ID_INLINE int idBitMsg::ReadChar( void ) const { + return (signed char)ReadBits( -8 ); +} + +ID_INLINE int idBitMsg::ReadByte( void ) const { + return (unsigned char)ReadBits( 8 ); +} + +ID_INLINE int idBitMsg::ReadShort( void ) const { + return (short)ReadBits( -16 ); +} + +ID_INLINE int idBitMsg::ReadUShort( void ) const { + return (unsigned short)ReadBits( 16 ); +} + +ID_INLINE int idBitMsg::ReadLong( void ) const { + return ReadBits( 32 ); +} + +ID_INLINE float idBitMsg::ReadFloat( void ) const { + float value; + *reinterpret_cast(&value) = ReadBits( 32 ); + return value; +} + +ID_INLINE float idBitMsg::ReadFloat( int exponentBits, int mantissaBits ) const { + int bits = ReadBits( 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( bits, exponentBits, mantissaBits ); +} + +ID_INLINE float idBitMsg::ReadAngle8( void ) const { + return BYTE2ANGLE( ReadByte() ); +} + +ID_INLINE float idBitMsg::ReadAngle16( void ) const { + return SHORT2ANGLE( ReadShort() ); +} + +ID_INLINE idVec3 idBitMsg::ReadDir( int numBits ) const { + return BitsToDir( ReadBits( numBits ), numBits ); +} + +ID_INLINE int idBitMsg::ReadDeltaChar( int oldValue ) const { + return (signed char)ReadDelta( oldValue, -8 ); +} + +ID_INLINE int idBitMsg::ReadDeltaByte( int oldValue ) const { + return (unsigned char)ReadDelta( oldValue, 8 ); +} + +ID_INLINE int idBitMsg::ReadDeltaShort( int oldValue ) const { + return (short)ReadDelta( oldValue, -16 ); +} + +ID_INLINE int idBitMsg::ReadDeltaLong( int oldValue ) const { + return ReadDelta( oldValue, 32 ); +} + +ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue ) const { + float value; + *reinterpret_cast(&value) = ReadDelta( *reinterpret_cast(&oldValue), 32 ); + return value; +} + +ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = ReadDelta( oldBits, 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( newBits, exponentBits, mantissaBits ); +} + + +/* +=============================================================================== + + idBitMsgDelta + +=============================================================================== +*/ + +class idBitMsgDelta { +public: + idBitMsgDelta(); + ~idBitMsgDelta() {} + + void InitWriting( const idBitMsg *base, idBitMsg *newBase, idBitMsg *delta ); + void InitReading( const idBitMsg *base, idBitMsg *newBase, const idBitMsg *delta ); + bool HasChanged( void ) const; + + void WriteBits( int value, int numBits ); + void WriteChar( int c ); + void WriteByte( int c ); + void WriteShort( int c ); + void WriteUShort( int c ); + void WriteLong( int c ); + void WriteFloat( float f ); + void WriteFloat( float f, int exponentBits, int mantissaBits ); +// RAVEN BEGIN +// abahr: + void WriteVec3( const idVec3& v ); + void WriteDeltaVec3( const idVec3& oldValue, const idVec3& newValue ); + void WriteVec4( const idVec4& v ); + void WriteDeltaVec4( const idVec4& oldValue, const idVec4& newValue ); + void WriteQuat( const idQuat& q ); + void WriteDeltaQuat( const idQuat& oldValue, const idQuat& newValue ); + void WriteMat3( const idMat3& m ); + void WriteDeltaMat3( const idMat3& oldValue, const idMat3& newValue ); +// RAVEN END + void WriteAngle8( float f ); + void WriteAngle16( float f ); + void WriteDir( const idVec3 &dir, int numBits ); + void WriteString( const char *s, int maxLength = -1 ); + void WriteData( const void *data, int length ); + void WriteDict( const idDict &dict ); + + void WriteDeltaChar( int oldValue, int newValue ); + void WriteDeltaByte( int oldValue, int newValue ); + void WriteDeltaShort( int oldValue, int newValue ); + void WriteDeltaLong( int oldValue, int newValue ); + void WriteDeltaFloat( float oldValue, float newValue ); + void WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ); + void WriteDeltaByteCounter( int oldValue, int newValue ); + void WriteDeltaShortCounter( int oldValue, int newValue ); + void WriteDeltaLongCounter( int oldValue, int newValue ); + + int ReadBits( int numBits ) const; + int ReadChar( void ) const; + int ReadByte( void ) const; + int ReadShort( void ) const; + int ReadUShort( void ) const; + int ReadLong( void ) const; + float ReadFloat( void ) const; + float ReadFloat( int exponentBits, int mantissaBits ) const; +// RAVEN BEGIN +// abahr + idVec3 ReadVec3( void ) const; + idVec3 ReadDeltaVec3( const idVec3& oldValue ) const; + idVec4 ReadVec4( void ) const; + idVec4 ReadDeltaVec4( const idVec4& oldValue ) const; + idQuat ReadQuat( void ) const; + idQuat ReadDeltaQuat( const idQuat& oldValue ) const; + idMat3 ReadMat3( void ) const; + idMat3 ReadDeltaMat3( const idMat3& oldValue ) const; +// RAVEN END + float ReadAngle8( void ) const; + float ReadAngle16( void ) const; + idVec3 ReadDir( int numBits ) const; + void ReadString( char *buffer, int bufferSize ) const; + void ReadData( void *data, int length ) const; + void ReadDict( idDict &dict ); + + int ReadDeltaChar( int oldValue ) const; + int ReadDeltaByte( int oldValue ) const; + int ReadDeltaShort( int oldValue ) const; + int ReadDeltaLong( int oldValue ) const; + float ReadDeltaFloat( float oldValue ) const; + float ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const; + int ReadDeltaByteCounter( int oldValue ) const; + int ReadDeltaShortCounter( int oldValue ) const; + int ReadDeltaLongCounter( int oldValue ) const; + +private: + const idBitMsg *base; // base + idBitMsg * newBase; // new base + idBitMsg * writeDelta; // delta from base to new base for writing + const idBitMsg *readDelta; // delta from base to new base for reading + mutable bool changed; // true if the new base is different from the base + +private: + void WriteDelta( int oldValue, int newValue, int numBits ); + int ReadDelta( int oldValue, int numBits ) const; +}; + +ID_INLINE idBitMsgDelta::idBitMsgDelta() { + base = NULL; + newBase = NULL; + writeDelta = NULL; + readDelta = NULL; + changed = false; +} + +ID_INLINE void idBitMsgDelta::InitWriting( const idBitMsg *base, idBitMsg *newBase, idBitMsg *delta ) { + this->base = base; + this->newBase = newBase; + this->writeDelta = delta; + this->readDelta = delta; + this->changed = false; +} + +ID_INLINE void idBitMsgDelta::InitReading( const idBitMsg *base, idBitMsg *newBase, const idBitMsg *delta ) { + this->base = base; + this->newBase = newBase; + this->writeDelta = NULL; + this->readDelta = delta; + this->changed = false; +} + +ID_INLINE bool idBitMsgDelta::HasChanged( void ) const { + return changed; +} + +ID_INLINE void idBitMsgDelta::WriteChar( int c ) { + WriteBits( c, -8 ); +} + +ID_INLINE void idBitMsgDelta::WriteByte( int c ) { + WriteBits( c, 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteShort( int c ) { + WriteBits( c, -16 ); +} + +ID_INLINE void idBitMsgDelta::WriteUShort( int c ) { + WriteBits( c, 16 ); +} + +ID_INLINE void idBitMsgDelta::WriteLong( int c ) { + WriteBits( c, 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteFloat( float f ) { + WriteBits( *reinterpret_cast(&f), 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteFloat( float f, int exponentBits, int mantissaBits ) { + int bits = idMath::FloatToBits( f, exponentBits, mantissaBits ); + WriteBits( bits, 1 + exponentBits + mantissaBits ); +} + +// RAVEN BEGIN +// abahr +ID_INLINE void idBitMsgDelta::WriteVec3( const idVec3& v ) { + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + WriteFloat( v[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaVec3( const idVec3& oldValue, const idVec3& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteVec4( const idVec4& v ) { + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + WriteFloat( v[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaVec4( const idVec4& oldValue, const idVec4& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteQuat( const idQuat& q ) { + for( int ix = 0; ix < q.GetDimension(); ++ix ) { + WriteFloat( q[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaQuat( const idQuat& oldValue, const idQuat& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteMat3( const idMat3& m ) { + for( int ix = 0; ix < m.GetVec3Dimension(); ++ix ) { + WriteVec3( m[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaMat3( const idMat3& oldValue, const idMat3& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaVec3( oldValue[ix], newValue[ix] ); + } +} +// RAVEN END + +ID_INLINE void idBitMsgDelta::WriteAngle8( float f ) { + WriteBits( ANGLE2BYTE( f ), 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteAngle16( float f ) { + WriteBits( ANGLE2SHORT(f), 16 ); +} + +ID_INLINE void idBitMsgDelta::WriteDir( const idVec3 &dir, int numBits ) { + WriteBits( idBitMsg::DirToBits( dir, numBits ), numBits ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaChar( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -8 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaByte( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaShort( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -16 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaLong( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaFloat( float oldValue, float newValue ) { + WriteDelta( *reinterpret_cast(&oldValue), *reinterpret_cast(&newValue), 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ) { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = idMath::FloatToBits( newValue, exponentBits, mantissaBits ); + WriteDelta( oldBits, newBits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE int idBitMsgDelta::ReadChar( void ) const { + return (signed char)ReadBits( -8 ); +} + +ID_INLINE int idBitMsgDelta::ReadByte( void ) const { + return (unsigned char)ReadBits( 8 ); +} + +ID_INLINE int idBitMsgDelta::ReadShort( void ) const { + return (short)ReadBits( -16 ); +} + +ID_INLINE int idBitMsgDelta::ReadUShort( void ) const { + return (unsigned short)ReadBits( 16 ); +} + +ID_INLINE int idBitMsgDelta::ReadLong( void ) const { + return ReadBits( 32 ); +} + +ID_INLINE float idBitMsgDelta::ReadFloat( void ) const { + float value; + *reinterpret_cast(&value) = ReadBits( 32 ); + return value; +} + +ID_INLINE float idBitMsgDelta::ReadFloat( int exponentBits, int mantissaBits ) const { + int bits = ReadBits( 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( bits, exponentBits, mantissaBits ); +} + +// RAVEN BEGIN +// abahr +ID_INLINE idVec3 idBitMsgDelta::ReadVec3() const { + idVec3 v; + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + v[ix] = ReadFloat(); + } + + return v; +} + +ID_INLINE idVec3 idBitMsgDelta::ReadDeltaVec3( const idVec3& oldValue ) const { + idVec3 value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idVec4 idBitMsgDelta::ReadVec4( void ) const { + idVec4 v; + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + v[ix] = ReadFloat(); + } + + return v; +} + +ID_INLINE idVec4 idBitMsgDelta::ReadDeltaVec4( const idVec4& oldValue ) const { + idVec4 value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idQuat idBitMsgDelta::ReadQuat( void ) const { + idQuat q; + for( int ix = 0; ix < q.GetDimension(); ++ix ) { + q[ix] = ReadFloat(); + } + + return q; +} + +ID_INLINE idQuat idBitMsgDelta::ReadDeltaQuat( const idQuat& oldValue ) const { + idQuat value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idMat3 idBitMsgDelta::ReadMat3() const { + idMat3 m; + + for( int ix = 0; ix < m.GetVec3Dimension(); ++ix ) { + m[ix] = ReadVec3(); + } + + return m; +} + +ID_INLINE idMat3 idBitMsgDelta::ReadDeltaMat3( const idMat3& oldValue ) const { + idMat3 value; + + for( int ix = 0; ix < value.GetVec3Dimension(); ++ix ) { + value[ix] = ReadDeltaVec3( oldValue[ix] ); + } + + return value; +} +// RAVEN END + +ID_INLINE float idBitMsgDelta::ReadAngle8( void ) const { + return BYTE2ANGLE( ReadByte() ); +} + +ID_INLINE float idBitMsgDelta::ReadAngle16( void ) const { + return SHORT2ANGLE( ReadShort() ); +} + +ID_INLINE idVec3 idBitMsgDelta::ReadDir( int numBits ) const { + return idBitMsg::BitsToDir( ReadBits( numBits ), numBits ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaChar( int oldValue ) const { + return (signed char)ReadDelta( oldValue, -8 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaByte( int oldValue ) const { + return (unsigned char)ReadDelta( oldValue, 8 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaShort( int oldValue ) const { + return (short)ReadDelta( oldValue, -16 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaLong( int oldValue ) const { + return ReadDelta( oldValue, 32 ); +} + +ID_INLINE float idBitMsgDelta::ReadDeltaFloat( float oldValue ) const { + float value; + *reinterpret_cast(&value) = ReadDelta( *reinterpret_cast(&oldValue), 32 ); + return value; +} + +ID_INLINE float idBitMsgDelta::ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = ReadDelta( oldBits, 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( newBits, exponentBits, mantissaBits ); +} + +#define MAX_MSG_QUEUE_SIZE 16384 // must be a power of 2 + +class idMsgQueue { +public: + idMsgQueue(); + + void Init( int sequence ); + + bool Add( const byte *data, const int size, bool sequencing ); + // prepend to a message without the need to a memcopy and readjust + bool AddConcat( const byte *data1, const int size1, const byte *data2, const int size2, bool sequencing ); + bool Get( byte *data, int dataSize, int &size, bool sequencing ); + int GetTotalSize( void ) const; + int GetSpaceLeft( void ) const; + int GetFirst( void ) const { return first; } + int GetLast( void ) const { return last; } + void CopyToBuffer( byte *buf ) const; + + void WriteTo( idBitMsg &msg ); + void FlushTo( idBitMsg &msg ); + void ReadFrom( const idBitMsg &msg ); + + void Save( idFile *file ) const; + void Restore( idFile *file ); + +private: + byte buffer[MAX_MSG_QUEUE_SIZE]; + int first; // sequence number of first message in queue + int last; // sequence number of last message in queue + int startIndex; // index pointing to the first byte of the first message + int endIndex; // index pointing to the first byte after the last message + +public: + void WriteByte( byte b ); + byte ReadByte( void ); + void WriteShort( int s ); + void WriteUShort( int s ); + int ReadShort( void ); + int ReadUShort( void ); + void WriteLong( int l ); + int ReadLong( void ); + void WriteData( const byte *data, const int size ); + void ReadData( byte *data, const int size ); +}; + +class idBitMsgQueue { +public: + idBitMsgQueue(); + + void Init( void ); + + void Add( const idBitMsg &msg, const int timestamp ); + bool Get( idBitMsg &msg, int ×tamp ); + bool Get( idBitMsg &msg ) { int dummy; return Get( msg, dummy); } + bool GetTimestamp( int ×tamp ); + +private: + int nextTimestamp; + bool readTimestamp; + idLinkList writeList, readList; +}; + +#endif /* !__BITMSG_H__ */ diff --git a/source/idlib/CmdArgs.cpp b/source/idlib/CmdArgs.cpp new file mode 100644 index 0000000..4ad3c7d --- /dev/null +++ b/source/idlib/CmdArgs.cpp @@ -0,0 +1,170 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +============ +idCmdArgs::operator= +============ +*/ +void idCmdArgs::operator=( const idCmdArgs &args ) { + int i; + + argc = args.argc; + memcpy( tokenized, args.tokenized, MAX_COMMAND_STRING ); + for ( i = 0; i < argc; i++ ) { + argv[ i ] = tokenized + ( args.argv[ i ] - args.tokenized ); + } +} + +/* +============ +idCmdArgs::Args +============ +*/ +const char *idCmdArgs::Args( int start, int end, bool escapeArgs ) const { + static char cmd_args[MAX_COMMAND_STRING]; + int i; + + if ( end < 0 ) { + end = argc - 1; + } else if ( end >= argc ) { + end = argc - 1; + } + cmd_args[0] = '\0'; + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + for ( i = start; i <= end; i++ ) { + if ( i > start ) { + if ( escapeArgs ) { + strcat( cmd_args, "\" \"" ); + } else { + strcat( cmd_args, " " ); + } + } + if ( escapeArgs && strchr( argv[i], '\\' ) ) { + char *p = argv[i]; + while ( *p != '\0' ) { + if ( *p == '\\' ) { + strcat( cmd_args, "\\\\" ); + } else { + int l = strlen( cmd_args ); + cmd_args[ l ] = *p; + cmd_args[ l+1 ] = '\0'; + } + p++; + } + } else { + strcat( cmd_args, argv[i] ); + } + } + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + + return cmd_args; +} + +/* +============ +idCmdArgs::TokenizeString + +Parses the given string into command line tokens. +The text is copied to a separate buffer and 0 characters +are inserted in the appropriate place. The argv array +will point into this temporary buffer. +============ +*/ +void idCmdArgs::TokenizeString( const char *text, bool keepAsStrings ) { + idLexer lex; + idToken token, number; + int len, totalLen; + + // clear previous args + argc = 0; + + if ( !text ) { + return; + } + + lex.LoadMemory( text, strlen( text ), "idCmdSystemLocal::TokenizeString" ); + lex.SetFlags( LEXFL_NOERRORS + | LEXFL_NOWARNINGS + | LEXFL_NOSTRINGCONCAT + | LEXFL_ALLOWPATHNAMES + | LEXFL_NOSTRINGESCAPECHARS + | LEXFL_ALLOWIPADDRESSES | ( keepAsStrings ? LEXFL_ONLYSTRINGS : 0 ) ); + + totalLen = 0; + + while ( 1 ) { + if ( argc == MAX_COMMAND_ARGS ) { + return; // this is usually something malicious + } + + if ( !lex.ReadToken( &token ) ) { + return; + } + + // check for negative numbers + if ( !keepAsStrings && ( token == "-" ) ) { + if ( lex.CheckTokenType( TT_NUMBER, 0, &number ) ) { + token = "-" + number; + } + } + + // check for cvar expansion + if ( token == "$" ) { + if ( !lex.ReadToken( &token ) ) { + return; + } + if ( idLib::cvarSystem ) { + token = idLib::cvarSystem->GetCVarString( token.c_str() ); + } else { + token = ""; + } + } + + len = token.Length(); + + if ( totalLen + len + 1 > sizeof( tokenized ) ) { + return; // this is usually something malicious + } + + // regular token + argv[argc] = tokenized + totalLen; + argc++; + + idStr::Copynz( tokenized + totalLen, token.c_str(), sizeof( tokenized ) - totalLen ); + + totalLen += len + 1; + } +} +/* +============ +idCmdArgs::AppendArg +============ +*/ +void idCmdArgs::AppendArg( const char *text ) { + if ( !argc ) { + argc = 1; + argv[ 0 ] = tokenized; + idStr::Copynz( tokenized, text, sizeof( tokenized ) ); + } else { + argv[ argc ] = argv[ argc-1 ] + strlen( argv[ argc-1 ] ) + 1; + idStr::Copynz( argv[ argc ], text, sizeof( tokenized ) - ( argv[ argc ] - tokenized ) ); + argc++; + } +} + +/* +============ +idCmdArgs::GetArgs +============ +*/ +const char **idCmdArgs::GetArgs( int *_argc ) { + *_argc = argc; + return (const char **)&argv[0]; +} + diff --git a/source/idlib/CmdArgs.h b/source/idlib/CmdArgs.h new file mode 100644 index 0000000..017af07 --- /dev/null +++ b/source/idlib/CmdArgs.h @@ -0,0 +1,46 @@ + +#ifndef __CMDARGS_H__ +#define __CMDARGS_H__ + +/* +=============================================================================== + + Command arguments. + +=============================================================================== +*/ + +class idCmdArgs { +public: + idCmdArgs( void ) { argc = 0; } + idCmdArgs( const char *text, bool keepAsStrings ) { TokenizeString( text, keepAsStrings ); } + + void operator=( const idCmdArgs &args ); + + // The functions that execute commands get their parameters with these functions. + int Argc( void ) const { return argc; } + // Argv() will return an empty string, not NULL if arg >= argc. + const char * Argv( int arg ) const { return ( arg >= 0 && arg < argc ) ? argv[arg] : ""; } + // Returns a single string containing argv(start) to argv(end) + // escapeArgs is a fugly way to put the string back into a state ready to tokenize again + const char * Args( int start = 1, int end = -1, bool escapeArgs = false ) const; + + // Takes a null terminated string and breaks the string up into arg tokens. + // Does not need to be /n terminated. + // Set keepAsStrings to true to only seperate tokens from whitespace and comments, ignoring punctuation + void TokenizeString( const char *text, bool keepAsStrings ); + + void AppendArg( const char *text ); + void Clear( void ) { argc = 0; } + const char ** GetArgs( int *argc ); + +private: + static const int MAX_COMMAND_ARGS = 64; + static const int MAX_COMMAND_STRING = 2 * MAX_STRING_CHARS; + + int argc; // number of arguments + char * argv[MAX_COMMAND_ARGS]; // points into tokenized + char tokenized[MAX_COMMAND_STRING]; // will have 0 bytes inserted +}; + +#endif /* !__CMDARGS_H__ */ diff --git a/source/idlib/Dict.cpp b/source/idlib/Dict.cpp new file mode 100644 index 0000000..0975c7d --- /dev/null +++ b/source/idlib/Dict.cpp @@ -0,0 +1,821 @@ + +#include "precompiled.h" +#pragma hdrstop + +idStrPool idDict::globalKeys; +idStrPool idDict::globalValues; + +/* +================ +idDict::operator= + + clear existing key/value pairs and copy all key/value pairs from other +================ +*/ +idDict &idDict::operator=( const idDict &other ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + // check for assignment to self + if ( this == &other ) { + return *this; + } + + Clear(); + + args = other.args; + argHash = other.argHash; + + for ( i = 0; i < args.Num(); i++ ) { + args[i].key = globalKeys.CopyString( args[i].key ); + args[i].value = globalValues.CopyString( args[i].value ); + } + + return *this; +} + +/* +================ +idDict::Copy + + copy all key value pairs without removing existing key/value pairs not present in the other dict +================ +*/ +void idDict::Copy( const idDict &other ) { + int i, n, *found; + idKeyValue kv; + + // check for assignment to self + if ( this == &other ) { + return; + } + + n = other.args.Num(); + + if ( args.Num() ) { + found = (int *) _alloca16( other.args.Num() * sizeof( int ) ); + for ( i = 0; i < n; i++ ) { + found[i] = FindKeyIndex( other.args[i].GetKey() ); + } + } else { + found = NULL; + } + + for ( i = 0; i < n; i++ ) { + if ( found && found[i] != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[found[i]].value; + args[found[i]].value = globalValues.CopyString( other.args[i].value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.CopyString( other.args[i].key ); + kv.value = globalValues.CopyString( other.args[i].value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } + } +} + +/* +================ +idDict::TransferKeyValues + + clear existing key/value pairs and transfer key/value pairs from other +================ +*/ +void idDict::TransferKeyValues( idDict &other ) { + int i, n; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + if ( this == &other ) { + return; + } + + if ( other.args.Num() && other.args[0].key->GetPool() != &globalKeys ) { + common->FatalError( "idDict::TransferKeyValues: can't transfer values across a DLL boundary" ); + return; + } + + Clear(); + + n = other.args.Num(); + args.SetNum( n ); + for ( i = 0; i < n; i++ ) { + args[i].key = other.args[i].key; + args[i].value = other.args[i].value; + } + argHash = other.argHash; + + other.args.Clear(); + other.argHash.Free(); +} + +/* +================ +idDict::Parse +================ +*/ +bool idDict::Parse( idParser &parser ) { + idToken token; + idToken token2; + bool errors; + + errors = false; + + parser.ExpectTokenString( "{" ); + parser.ReadToken( &token ); + while( ( token.type != TT_PUNCTUATION ) || ( token != "}" ) ) { + if ( token.type != TT_STRING ) { + parser.Error( "Expected quoted string, but found '%s'", token.c_str() ); + } + + if ( !parser.ReadToken( &token2 ) ) { + parser.Error( "Unexpected end of file" ); + } + + if ( FindKey( token ) ) { + parser.Warning( "'%s' already defined", token.c_str() ); + errors = true; + } + Set( token, token2 ); + + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Unexpected end of file" ); + } + } + + return !errors; +} + +/* +================ +idDict::SetDefaults +================ +*/ +void idDict::SetDefaults( const idDict *dict ) { + int i, n; + const idKeyValue *kv, *def; + idKeyValue newkv; + + n = dict->args.Num(); + for( i = 0; i < n; i++ ) { + def = &dict->args[i]; + kv = FindKey( def->GetKey() ); + if ( !kv ) { + newkv.key = globalKeys.CopyString( def->key ); + newkv.value = globalValues.CopyString( def->value ); + argHash.Add( argHash.GenerateKey( newkv.GetKey(), false ), args.Append( newkv ) ); + } + } +} + +/* +================ +idDict::Clear +================ +*/ +void idDict::Clear( void ) { + int i; + + for( i = 0; i < args.Num(); i++ ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + } + + args.Clear(); + argHash.Free(); +} + +/* +================ +idDict::Print +================ +*/ +void idDict::Print() const { + int i; + int n; + + n = args.Num(); + for( i = 0; i < n; i++ ) { + idLib::common->Printf( "%s = %s\n", args[i].GetKey().c_str(), args[i].GetValue().c_str() ); + } +} + +int KeyCompare( const idKeyValue *a, const idKeyValue *b ) { + return idStr::Cmp( a->GetKey(), b->GetKey() ); +} + +/* +================ +idDict::Checksum +================ +*/ +int idDict::Checksum( void ) const { + unsigned long ret; + int i, n; + + idList sorted = args; + sorted.Sort( KeyCompare ); + n = sorted.Num(); + CRC32_InitChecksum( ret ); + for( i = 0; i < n; i++ ) { + CRC32_UpdateChecksum( ret, sorted[i].GetKey().c_str(), sorted[i].GetKey().Length() ); + CRC32_UpdateChecksum( ret, sorted[i].GetValue().c_str(), sorted[i].GetValue().Length() ); + } + CRC32_FinishChecksum( ret ); + return ret; +} + +/* +================ +idDict::Allocated +================ +*/ +size_t idDict::Allocated( void ) const { + int i; + size_t size; + + size = args.Allocated() + argHash.Allocated(); + for( i = 0; i < args.Num(); i++ ) { + size += args[i].Size(); + } + + return size; +} + +/* +================ +idDict::Set +================ +*/ +void idDict::Set( const char *key, const char *value ) { + int i; + idKeyValue kv; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + if ( key == NULL || key[0] == '\0' ) { + return; + } + + i = FindKeyIndex( key ); + if ( i != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[i].value; + args[i].value = globalValues.AllocString( value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.AllocString( key ); + kv.value = globalValues.AllocString( value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } +} + +/* +================ +idDict::GetFloat +================ +*/ +bool idDict::GetFloat( const char *key, const char *defaultString, float &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atof( s ); + return found; +} + +/* +================ +idDict::GetInt +================ +*/ +bool idDict::GetInt( const char *key, const char *defaultString, int &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atoi( s ); + return found; +} + +/* +================ +idDict::GetBool +================ +*/ +bool idDict::GetBool( const char *key, const char *defaultString, bool &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = ( atoi( s ) != 0 ); + return found; +} + +/* +================ +idDict::GetAngles +================ +*/ +bool idDict::GetAngles( const char *key, const char *defaultString, idAngles &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.pitch, &out.yaw, &out.roll ); + return found; +} + +/* +================ +idDict::GetVector +================ +*/ +bool idDict::GetVector( const char *key, const char *defaultString, idVec3 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.x, &out.y, &out.z ); + return found; +} + +/* +================ +idDict::GetVec2 +================ +*/ +bool idDict::GetVec2( const char *key, const char *defaultString, idVec2 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f", &out.x, &out.y ); + return found; +} + +/* +================ +idDict::GetVec4 +================ +*/ +bool idDict::GetVec4( const char *key, const char *defaultString, idVec4 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f %f", &out.x, &out.y, &out.z, &out.w ); + return found; +} + +/* +================ +idDict::GetMatrix +================ +*/ +bool idDict::GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const { + const char *s; + bool found; + + if ( !defaultString ) { + defaultString = "1 0 0 0 1 0 0 0 1"; + } + + found = GetString( key, defaultString, &s ); + out.Identity(); // sccanf has a bug in it on Mac OS 9. Sigh. + sscanf( s, "%f %f %f %f %f %f %f %f %f", &out[0].x, &out[0].y, &out[0].z, &out[1].x, &out[1].y, &out[1].z, &out[2].x, &out[2].y, &out[2].z ); + return found; +} + +/* +================ +WriteString +================ +*/ +static void WriteString( const char *s, idFile *f ) { + int len = strlen( s ); + if ( len >= MAX_STRING_CHARS-1 ) { + idLib::common->Error( "idDict::WriteToFileHandle: bad string" ); + } + f->Write( s, strlen(s) + 1 ); +} + +/* +================ +idDict::FindKey +================ +*/ +const idKeyValue *idDict::FindKey( const char *key ) const { + int i, hash; + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKey: empty key" ); + return NULL; + } + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return &args[i]; + } + } + + return NULL; +} + +/* +================ +idDict::FindKeyIndex +================ +*/ +int idDict::FindKeyIndex( const char *key ) const { + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKeyIndex: empty key" ); + return NULL; + } + + int hash = argHash.GenerateKey( key, false ); + for ( int i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return i; + } + } + + return -1; +} + +/* +================ +idDict::Delete +================ +*/ +void idDict::Delete( const char *key ) { + int hash, i; + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + args.RemoveIndex( i ); + argHash.RemoveIndex( hash, i ); + break; + } + } + +#if 0 + // make sure all keys can still be found in the hash index + for ( i = 0; i < args.Num(); i++ ) { + assert( FindKey( args[i].GetKey() ) != NULL ); + } +#endif +} + +/* +================ +idDict::MatchPrefix +================ +*/ +const idKeyValue *idDict::MatchPrefix( const char *prefix, const idKeyValue *lastMatch ) const { + int i; + int len; + int start; + + assert( prefix ); + len = strlen( prefix ); + + start = -1; + if ( lastMatch ) { + start = args.FindIndex( *lastMatch ); + assert( start >= 0 ); + if ( start < 1 ) { + start = 0; + } + } + + for( i = start + 1; i < args.Num(); i++ ) { + if ( !args[i].GetKey().Icmpn( prefix, len ) ) { + return &args[i]; + } + } + return NULL; +} + +/* +================ +idDict::RandomPrefix +================ +*/ +// RAVEN BEGIN +// abahr: added default value param +const char *idDict::RandomPrefix( const char *prefix, idRandom &random, const char* defaultValue ) const { + int count; + const int MAX_RANDOM_KEYS = 2048; + const char *list[MAX_RANDOM_KEYS]; + const idKeyValue *kv; + +// RAVEN BEGIN +// abahr: added defaultValue param + list[0] = defaultValue; +// RAVEN END + for ( count = 0, kv = MatchPrefix( prefix ); kv && count < MAX_RANDOM_KEYS; kv = MatchPrefix( prefix, kv ) ) { + list[count++] = kv->GetValue().c_str(); + } + return list[random.RandomInt( count )]; +} + +/* +================ +idDict::WriteToFileHandle +================ +*/ +void idDict::WriteToFileHandle( idFile *f ) const { + int c = LittleLong( args.Num() ); + f->Write( &c, sizeof( c ) ); +// RAVEN BEGIN +// jnewquist: For loop was looking at c, which got swapped on Xenon! + for ( int i = 0; i < args.Num(); i++ ) { +// RAVEN END + WriteString( args[i].GetKey().c_str(), f ); + WriteString( args[i].GetValue().c_str(), f ); + } +} + +/* +================ +ReadString +================ +*/ +static idStr ReadString( idFile *f ) { + char str[MAX_STRING_CHARS]; + int len; + + for ( len = 0; len < MAX_STRING_CHARS; len++ ) { + f->Read( (void *)&str[len], 1 ); + if ( str[len] == 0 ) { + break; + } + } + if ( len == MAX_STRING_CHARS ) { + idLib::common->Error( "idDict::ReadFromFileHandle: bad string" ); + } + + return idStr( str ); +} + +/* +================ +idDict::ReadFromFileHandle +================ +*/ +void idDict::ReadFromFileHandle( idFile *f ) { + int c; + idStr key, val; + + Clear(); + + f->Read( &c, sizeof( c ) ); + c = LittleLong( c ); + for ( int i = 0; i < c; i++ ) { + key = ReadString( f ); + val = ReadString( f ); + Set( key, val ); + } +} + +// RAVEN BEGIN +/* +================ +idDict::WriteToMemory +================ +*/ +int idDict::WriteToMemory( void *mem, int maxSize ) const { + int bytesWritten = 0; + char *out = (char*)mem; + if ( out ) { + *((int*)out) = args.Num(); + out+=sizeof(int); + } + bytesWritten+=sizeof(int); + + int l; + for ( int i = 0; i < args.Num(); i++ ) { + l = args[i].GetKey().Length(); + ++l; + + if ( bytesWritten + l > maxSize ) { + assert( 0 ); + return bytesWritten; + } + + if ( out ) { + memcpy( out, args[i].GetKey().c_str(), l ); + } + if ( out ) { + out+=l; + } + bytesWritten+=l; + + l = args[i].GetValue().Length(); + ++l; + + if ( bytesWritten + l > maxSize ) { + assert( 0 ); + return bytesWritten; + } + + if ( out ) { + memcpy( out, args[i].GetValue().c_str(), l ); + } + if ( out ) { + out+=l; + } + bytesWritten+=l; + } + + return bytesWritten; +} + +/* +================ +idDict::ReadFromMemory +================ +*/ +void idDict::ReadFromMemory( void *mem, int size ) { + int bytesRead = 0; + + char *in = (char*)mem; + int c = *((int*)in); + in+=sizeof(int); + + idStr key, val; + + Clear(); + + int readLen; + for ( int i = 0; i < c; i++ ) { + key = in; + readLen = key.Length() + 1; + in+=readLen; + bytesRead+=readLen; + if ( bytesRead >= size ) { + assert( 0 ); + return; + } + + val = in; + readLen = val.Length() + 1; + in+=readLen; + bytesRead+=readLen; + if ( bytesRead >= size ) { + assert( 0 ); + return; + } + + Set( key, val ); + } +} +// RAVEN END + +/* +================ +idDict::Init +================ +*/ +void idDict::Init( void ) { + globalKeys.SetCaseSensitive( false ); + globalValues.SetCaseSensitive( true ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + globalKeys.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_PERMANENT)); + globalValues.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_PERMANENT)); +#endif +// RAVEN END +} + +/* +================ +idDict::Shutdown +================ +*/ +void idDict::Shutdown( void ) { + globalKeys.Clear(); + globalValues.Clear(); +} + +/* +================ +idDict::ShowMemoryUsage_f +================ +*/ +void idDict::ShowMemoryUsage_f( const idCmdArgs &args ) { + idLib::common->Printf( "%5d KB in %d keys\n", globalKeys.Size() >> 10, globalKeys.Num() ); + idLib::common->Printf( "%5d KB in %d values\n", globalValues.Size() >> 10, globalValues.Num() ); +} + +/* +================ +idDictStringSortCmp +================ +*/ +// NOTE: the const wonkyness is required to make msvc happy +template<> +ID_INLINE int idListSortCompare( const idPoolStr * const *a, const idPoolStr * const *b ) { + return (*a)->Icmp( **b ); +} + +/* +================ +idDict::ListKeys_f +================ +*/ +void idDict::ListKeys_f( const idCmdArgs &args ) { + int i; + idList keyStrings; + + for ( i = 0; i < globalKeys.Num(); i++ ) { + keyStrings.Append( globalKeys[i] ); + } + keyStrings.Sort(); + for ( i = 0; i < keyStrings.Num(); i++ ) { + idLib::common->Printf( "%s\n", keyStrings[i]->c_str() ); + } + idLib::common->Printf( "%5d keys\n", keyStrings.Num() ); +} + +/* +================ +idDict::ListValues_f +================ +*/ +void idDict::ListValues_f( const idCmdArgs &args ) { + int i; + idList valueStrings; + + for ( i = 0; i < globalValues.Num(); i++ ) { + valueStrings.Append( globalValues[i] ); + } + valueStrings.Sort(); + for ( i = 0; i < valueStrings.Num(); i++ ) { + idLib::common->Printf( "%s\n", valueStrings[i]->c_str() ); + } + idLib::common->Printf( "%5d values\n", valueStrings.Num() ); +} + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +void idDict::Write( SerialOutputStream &stream ) const +{ + stream.Write(GetNumKeyVals()); + for(int i=0;iGetKey()); + stream.Write(keyVal->GetValue()); + } +} + +idDict::idDict( SerialInputStream &stream ) +{ + int numKeyVals = stream.ReadIntValue(); + for(int i=0; iAllocated() + value->Allocated(); } + size_t Size( void ) const { return sizeof( *this ) + key->Size() + value->Size(); } + + bool operator==( const idKeyValue &kv ) const { return ( key == kv.key && value == kv.value ); } + +private: + const idPoolStr * key; + const idPoolStr * value; +}; + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +#include "../serialization/SerialOutputStream.h" +#include "../serialization/SerialInputStream.h" +#endif +// RAVEN END +class idDict { +public: + idDict( void ); + idDict( const idDict &other ); // allow declaration with assignment + ~idDict( void ); + + // set the granularity for the index + void SetGranularity( int granularity ); + // set hash size + void SetHashSize( int hashSize ); + // clear existing key/value pairs and copy all key/value pairs from other + idDict & operator=( const idDict &other ); + // copy from other while leaving existing key/value pairs in place + void Copy( const idDict &other ); + // clear existing key/value pairs and transfer key/value pairs from other + void TransferKeyValues( idDict &other ); + // parse dict from parser + bool Parse( idParser &parser ); + // copy key/value pairs from other dict not present in this dict + void SetDefaults( const idDict *dict ); + // clear dict freeing up memory + void Clear( void ); + // print the dict + void Print() const; + + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Set( const char *key, const char *value ); + void SetFloat( const char *key, float val ); + void SetInt( const char *key, int val ); + void SetBool( const char *key, bool val ); + void SetVector( const char *key, const idVec3 &val ); + void SetVec2( const char *key, const idVec2 &val ); + void SetVec4( const char *key, const idVec4 &val ); + void SetAngles( const char *key, const idAngles &val ); + void SetMatrix( const char *key, const idMat3 &val ); + + // these return default values of 0.0, 0 and false + const char * GetString( const char *key, const char *defaultString = "" ) const; + float GetFloat( const char *key, const char *defaultString = "0" ) const; + int GetInt( const char *key, const char *defaultString = "0" ) const; + bool GetBool( const char *key, const char *defaultString = "0" ) const; + idVec3 GetVector( const char *key, const char *defaultString = NULL ) const; + idVec2 GetVec2( const char *key, const char *defaultString = NULL ) const; + idVec4 GetVec4( const char *key, const char *defaultString = NULL ) const; + idAngles GetAngles( const char *key, const char *defaultString = NULL ) const; + idMat3 GetMatrix( const char *key, const char *defaultString = NULL ) const; + + bool GetString( const char *key, const char *defaultString, const char **out ) const; + bool GetString( const char *key, const char *defaultString, idStr &out ) const; + bool GetFloat( const char *key, const char *defaultString, float &out ) const; + bool GetInt( const char *key, const char *defaultString, int &out ) const; + bool GetBool( const char *key, const char *defaultString, bool &out ) const; + bool GetVector( const char *key, const char *defaultString, idVec3 &out ) const; + bool GetVec2( const char *key, const char *defaultString, idVec2 &out ) const; + bool GetVec4( const char *key, const char *defaultString, idVec4 &out ) const; + bool GetAngles( const char *key, const char *defaultString, idAngles &out ) const; + bool GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const; + + int GetNumKeyVals( void ) const; + const idKeyValue * GetKeyVal( int index ) const; + // returns the key/value pair with the given key + // returns NULL if the key/value pair does not exist + const idKeyValue * FindKey( const char *key ) const; + // returns the index to the key/value pair with the given key + // returns -1 if the key/value pair does not exist + int FindKeyIndex( const char *key ) const; + // delete the key/value pair with the given key + void Delete( const char *key ); + // finds the next key/value pair with the given key prefix. + // lastMatch can be used to do additional searches past the first match. + const idKeyValue * MatchPrefix( const char *prefix, const idKeyValue *lastMatch = NULL ) const; + // randomly chooses one of the key/value pairs with the given key prefix and returns it's value +// RAVEN BEGIN +// abahr: added default value param + const char * RandomPrefix( const char *prefix, idRandom &random, const char* defaultValue = "" ) const; +// RAVEN END + + void WriteToFileHandle( idFile *f ) const; + void ReadFromFileHandle( idFile *f ); + +// RAVEN BEGIN +// nrausch: write/read to memory + int WriteToMemory( void *mem, int maxSize ) const; + void ReadFromMemory( void *mem, int size ); +// RAVEN END + + // returns a unique checksum for this dictionary's content + int Checksum( void ) const; + + static void Init( void ); + static void Shutdown( void ); + + static void ShowMemoryUsage_f( const idCmdArgs &args ); + static void ListKeys_f( const idCmdArgs &args ); + static void ListValues_f( const idCmdArgs &args ); + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + void Write( SerialOutputStream &stream ) const; + idDict( SerialInputStream &stream ); +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + // Set the heap used for all allocations + void SetAllocatorHeap ( rvHeap* heap ) + { + args.SetAllocatorHeap(heap); + argHash.SetAllocatorHeap(heap); + } +#endif +// RAVEN END + +private: + idList args; + idHashIndex argHash; + static idStrPool globalKeys; + static idStrPool globalValues; +}; + + +ID_INLINE idDict::idDict( void ) { + args.SetGranularity( 16 ); + argHash.SetGranularity( 16 ); + argHash.Clear( 128, 16 ); +} + +ID_INLINE idDict::idDict( const idDict &other ) { + *this = other; +} + +ID_INLINE idDict::~idDict( void ) { + Clear(); +} + +ID_INLINE void idDict::SetGranularity( int granularity ) { + args.SetGranularity( granularity ); + argHash.SetGranularity( granularity ); +} + +ID_INLINE void idDict::SetHashSize( int hashSize ) { + if ( args.Num() == 0 ) { + argHash.Clear( hashSize, 16 ); + } +} + +ID_INLINE void idDict::SetFloat( const char *key, float val ) { + Set( key, va( "%f", val ) ); +} + +ID_INLINE void idDict::SetInt( const char *key, int val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetBool( const char *key, bool val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetVector( const char *key, const idVec3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec4( const char *key, const idVec4 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec2( const char *key, const idVec2 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetAngles( const char *key, const idAngles &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetMatrix( const char *key, const idMat3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, const char **out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + *out = kv->GetValue(); + return true; + } + *out = defaultString; + return false; +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, idStr &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = kv->GetValue(); + return true; + } + out = defaultString; + return false; +} + +ID_INLINE const char *idDict::GetString( const char *key, const char *defaultString ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return kv->GetValue(); + } + return defaultString; +} + +ID_INLINE float idDict::GetFloat( const char *key, const char *defaultString ) const { + return atof( GetString( key, defaultString ) ); +} + +ID_INLINE int idDict::GetInt( const char *key, const char *defaultString ) const { + return atoi( GetString( key, defaultString ) ); +} + +ID_INLINE bool idDict::GetBool( const char *key, const char *defaultString ) const { + return ( atoi( GetString( key, defaultString ) ) != 0 ); +} + +ID_INLINE idVec3 idDict::GetVector( const char *key, const char *defaultString ) const { + idVec3 out; + GetVector( key, defaultString, out ); + return out; +} + +ID_INLINE idVec2 idDict::GetVec2( const char *key, const char *defaultString ) const { + idVec2 out; + GetVec2( key, defaultString, out ); + return out; +} + +ID_INLINE idVec4 idDict::GetVec4( const char *key, const char *defaultString ) const { + idVec4 out; + GetVec4( key, defaultString, out ); + return out; +} + +ID_INLINE idAngles idDict::GetAngles( const char *key, const char *defaultString ) const { + idAngles out; + GetAngles( key, defaultString, out ); + return out; +} + +ID_INLINE idMat3 idDict::GetMatrix( const char *key, const char *defaultString ) const { + idMat3 out; + GetMatrix( key, defaultString, out ); + return out; +} + +ID_INLINE int idDict::GetNumKeyVals( void ) const { + return args.Num(); +} + +ID_INLINE const idKeyValue *idDict::GetKeyVal( int index ) const { + if ( index >= 0 && index < args.Num() ) { + return &args[ index ]; + } + return NULL; +} + +#endif /* !__DICT_H__ */ diff --git a/source/idlib/Heap.cpp b/source/idlib/Heap.cpp new file mode 100644 index 0000000..c07c222 --- /dev/null +++ b/source/idlib/Heap.cpp @@ -0,0 +1,2315 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +void *(*Memory::mAllocator)(size_t size); +void (*Memory::mDeallocator)(void *ptr); +size_t (*Memory::mMSize)(void *ptr); +bool Memory::sOK = true; + +void Memory::Error(const char *errStr) +{ + // If you land here it's because you allocated dynamic memory in a DLL + // before the Memory system was initialized and this will lead to instability + // later. We can't assert here since that will cause the DLL to fail loading + // so set a break point on the BreakHere=0xdeadbeef; line in order to debug + int BreakHere; + BreakHere=0xdeadbeef; + + if(common != NULL) + { + // this can't be an error or the game will fail to load the DLL and crash. + // While a crash is the desired behavior, a crash without any meaningful + // message to the user indicating what went wrong is not desired. + common->Warning(errStr); + } + else + { + // if common isn't initialized then we set a flag so that we can notify once it + // gets initialized + sOK = false; + } +} +#endif +// RAVEN END +#ifndef _RV_MEM_SYS_SUPPORT + +#ifndef USE_LIBC_MALLOC + #ifdef _XBOX + #define USE_LIBC_MALLOC 1 + #else +// RAVEN BEGIN +// scork: changed this to a 1, since 0 will crash Radiant if you BSP large maps ~3 times because of high watermark-mem that never gets freed. + #define USE_LIBC_MALLOC 1 +// RAVEN END + #endif +#endif + +#ifndef CRASH_ON_STATIC_ALLOCATION +// #define CRASH_ON_STATIC_ALLOCATION +#endif + +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon +inline +void *local_malloc(size_t size) +{ +#ifndef _XENON +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::Allocate(size); + #else + void *addr = malloc(size); + if( !addr && size ) { + common->FatalError( "Out of memory" ); + } + return( addr ); + #endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#else +#endif +} + +inline +void local_free(void *ptr) +{ +#ifndef _XENON +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + Memory::Free(ptr); + #else + return free(ptr); + #endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#else +#endif +} +// RAVEN END + +//=============================================================== +// +// idHeap +// +//=============================================================== + +// RAVEN BEGIN +// amccarthy: Added space in headers in debug for allocation tag storage +#ifdef _DEBUG +#define SMALL_HEADER_SIZE ( (int) ( sizeof( byte ) + sizeof( byte )+ sizeof( byte ) ) ) +#define MEDIUM_HEADER_SIZE ( (int) ( sizeof( mediumHeapEntry_s ) + sizeof( byte )+ sizeof( byte ) ) ) +#define LARGE_HEADER_SIZE ( (int) ( sizeof( dword * ) + sizeof( byte )+ sizeof( byte ) ) ) + +#define MALLOC_HEADER_SIZE ( (int) ( 11*sizeof( byte ) + sizeof( byte ) + sizeof( dword ))) +#else +#define SMALL_HEADER_SIZE ( (int) ( sizeof( byte ) + sizeof( byte ) ) ) +#define MEDIUM_HEADER_SIZE ( (int) ( sizeof( mediumHeapEntry_s ) + sizeof( byte ) ) ) +#define LARGE_HEADER_SIZE ( (int) ( sizeof( dword * ) + sizeof( byte ) ) ) +#endif +// RAVEN END + +#define ALIGN_SIZE( bytes ) ( ( (bytes) + ALIGN - 1 ) & ~(ALIGN - 1) ) +#define SMALL_ALIGN( bytes ) ( ALIGN_SIZE( (bytes) + SMALL_HEADER_SIZE ) - SMALL_HEADER_SIZE ) +#define MEDIUM_SMALLEST_SIZE ( ALIGN_SIZE( 256 ) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ) ) + +// RAVEN BEGIN +// amccarthy: Allocation tag tracking and reporting +#ifdef _DEBUG +int OutstandingXMallocSize=0; +int OutstandingXMallocTagSize[MA_MAX]; +int PeakXMallocTagSize[MA_MAX]; +int CurrNumAllocations[MA_MAX]; + + +// Descriptions that go with each tag. When updating the tag enum in Heap.h please +// update this list as well. +// (also update the list in rvHeap.cpp) +char *TagNames[] = { + "none", + "New operation", + "default", + "Lexer", + "Parser", + "AAS routing", + "Class", + "Script program", + "Collision Model", + "CVar", + "Decl System", + "File System", + "Images", + "Materials", + "Models", + "Fonts", + "Main renderer", + "Vertex data", + "Sound", + "Window", + "Event loop", + "Math - Matrices and vectors", + "Animation", + "Dynamic Blocks", + "Strings", + "GUI", + "Effects", + "Entities", + "Physics", + "AI", + "Network", + "Not Used" +}; + +// jnewquist: Detect when the tag descriptions are out of sync with the enums. +template +class TagTableCheck +{ +private: + TagTableCheck(); +}; + +template<> +class TagTableCheck<1> +{ +}; + +// An error here means you need to synchronize TagNames and Mem_Alloc_Types_t +TagTableCheck TagTableCheckedHere; +// An error here means there are too many tags. No more than 32! +TagTableCheck TagMaxCheckedHere; + +void PrintOutstandingMemAlloc() +{ + + int i; + unsigned long totalOutstanding = 0; + for (i=0;iPrintf("%-30s peak %9d curr %9d\n",TagNames[i],PeakXMallocTagSize[i],OutstandingXMallocTagSize[i]); + totalOutstanding += OutstandingXMallocTagSize[i]; + } + } + idLib::common->Printf("Mem_Alloc Outstanding: %d\n",totalOutstanding); + +} + +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak) +{ + num = CurrNumAllocations[tag]; + size = OutstandingXMallocTagSize[tag]; + peak = PeakXMallocTagSize[tag]; + return TagNames[tag]; +} +#endif + + +/* +================= +Mem_ShowMemAlloc_f +================= +*/ +// amccarthy: print out outstanding mem_allocs. +void Mem_ShowMemAlloc_f( const idCmdArgs &args ) { + +#ifdef _DEBUG + PrintOutstandingMemAlloc(); +#endif +} + +// jnewquist: memory tag stack for new/delete +#if defined(_DEBUG) && !defined(ENABLE_INTEL_SMP) +MemScopedTag* MemScopedTag::mTop = NULL; +#endif + +//RAVEN END + +class idHeap { + +public: + idHeap( void ); + ~idHeap( void ); // frees all associated data + void Init( void ); // initialize +//RAVEN BEGIN +//amccarthy: Added allocation tag + void * Allocate( const dword bytes, byte tag ); // allocate memory +//RAVEN END + void Free( void *p ); // free memory +//RAVEN BEGIN +//amccarthy: Added allocation tag + void * Allocate16( const dword bytes, byte tag );// allocate 16 byte aligned memory +//RAVEN END + void Free16( void *p ); // free 16 byte aligned memory + dword Msize( void *p ); // return size of data block + void Dump( void ); + + void AllocDefragBlock( void ); // hack for huge renderbumps + +private: + + enum { + ALIGN = 8 // memory alignment in bytes + }; + + enum { + INVALID_ALLOC = 0xdd, + SMALL_ALLOC = 0xaa, // small allocation + MEDIUM_ALLOC = 0xbb, // medium allocaction + LARGE_ALLOC = 0xcc // large allocaction + }; + + struct page_s { // allocation page + void * data; // data pointer to allocated memory + dword dataSize; // number of bytes of memory 'data' points to + page_s * next; // next free page in same page manager + page_s * prev; // used only when allocated + dword largestFree; // this data used by the medium-size heap manager + void * firstFree; // pointer to first free entry + }; + + struct mediumHeapEntry_s { + page_s * page; // pointer to page + dword size; // size of block + mediumHeapEntry_s * prev; // previous block + mediumHeapEntry_s * next; // next block + mediumHeapEntry_s * prevFree; // previous free block + mediumHeapEntry_s * nextFree; // next free block + dword freeBlock; // non-zero if free block + }; + + // variables + void * smallFirstFree[256/ALIGN+1]; // small heap allocator lists (for allocs of 1-255 bytes) + page_s * smallCurPage; // current page for small allocations + dword smallCurPageOffset; // byte offset in current page + page_s * smallFirstUsedPage; // first used page of the small heap manager + + page_s * mediumFirstFreePage; // first partially free page + page_s * mediumLastFreePage; // last partially free page + page_s * mediumFirstUsedPage; // completely used page + + page_s * largeFirstUsedPage; // first page used by the large heap manager + + page_s * swapPage; + + dword pagesAllocated; // number of pages currently allocated + dword pageSize; // size of one alloc page in bytes + + dword pageRequests; // page requests + dword OSAllocs; // number of allocs made to the OS + + int c_heapAllocRunningCount; + + void *defragBlock; // a single huge block that can be allocated + // at startup, then freed when needed + + // methods + page_s * AllocatePage( dword bytes ); // allocate page from the OS + void FreePage( idHeap::page_s *p ); // free an OS allocated page + +//RAVEN BEGIN +//amccarthy: Added allocation tags + void * SmallAllocate( dword bytes, byte tag ); // allocate memory (1-255 bytes) from small heap manager + void SmallFree( void *ptr ); // free memory allocated by small heap manager + + void * MediumAllocateFromPage( idHeap::page_s *p, dword sizeNeeded, byte tag ); + void * MediumAllocate( dword bytes, byte tag ); // allocate memory (256-32768 bytes) from medium heap manager + void MediumFree( void *ptr ); // free memory allocated by medium heap manager + + void * LargeAllocate( dword bytes, byte tag ); // allocate large block from OS directly + void LargeFree( void *ptr ); // free memory allocated by large heap manager +//RAVEN END + void ReleaseSwappedPages( void ); + void FreePageReal( idHeap::page_s *p ); +}; + + +/* +================ +idHeap::Init +================ +*/ +void idHeap::Init () { + OSAllocs = 0; + pageRequests = 0; + pageSize = 65536 - sizeof( idHeap::page_s ); + pagesAllocated = 0; // reset page allocation counter + + largeFirstUsedPage = NULL; // init large heap manager + swapPage = NULL; + + memset( smallFirstFree, 0, sizeof(smallFirstFree) ); // init small heap manager + smallFirstUsedPage = NULL; + smallCurPage = AllocatePage( pageSize ); + assert( smallCurPage ); + smallCurPageOffset = SMALL_ALIGN( 0 ); + + defragBlock = NULL; + + mediumFirstFreePage = NULL; // init medium heap manager + mediumLastFreePage = NULL; + mediumFirstUsedPage = NULL; + + c_heapAllocRunningCount = 0; + +//RAVEN BEGIN +//amccarthy: initalize allocation tracking +#ifdef _DEBUG + OutstandingXMallocSize=0; + int i; + for (i=0;inext; + FreePage( p ); + p= next; + } + + p = largeFirstUsedPage; // free large-heap allocated pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + p = mediumFirstFreePage; // free medium-heap allocated pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + p = mediumFirstUsedPage; // free medium-heap allocated completely used pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + ReleaseSwappedPages(); + + if ( defragBlock ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + } + + assert( pagesAllocated == 0 ); +} + +/* +================ +idHeap::AllocDefragBlock +================ +*/ +void idHeap::AllocDefragBlock( void ) { + int size = 0x40000000; + + if ( defragBlock ) { + return; + } + while( 1 ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + defragBlock = local_malloc( size ); +// RAVEN END + if ( defragBlock ) { + break; + } + size >>= 1; + } + idLib::common->Printf( "Allocated a %i mb defrag block\n", size / (1024*1024) ); +} + +/* +================ +idHeap::Allocate +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::Allocate( const dword bytes, byte tag ) { +//RAVEN END + if ( !bytes ) { + return NULL; + } + c_heapAllocRunningCount++; + +#if USE_LIBC_MALLOC + +//RAVEN BEGIN +//amccarthy: Added allocation tags for malloc +#ifdef _DEBUG + byte *p = (byte *)local_malloc( bytes + MALLOC_HEADER_SIZE ); + OutstandingXMallocSize += bytes; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += bytes; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + dword *d = (dword *)p; + d[0] = bytes; + byte *ret = p+MALLOC_HEADER_SIZE; + ret[-1] = tag; + return ret; +#else + return local_malloc( bytes); +#endif + +//RAVEN END + + +#else +//RAVEN BEGIN +//amccarthy: Added allocation tag + if ( !(bytes & ~255) ) { + return SmallAllocate( bytes, tag ); + } + if ( !(bytes & ~32767) ) { + return MediumAllocate( bytes, tag ); + } + return LargeAllocate( bytes, tag ); + +//RAVEN END +#endif +} + +/* +================ +idHeap::Free +================ +*/ +void idHeap::Free( void *p ) { + if ( !p ) { + return; + } + c_heapAllocRunningCount--; + +#if USE_LIBC_MALLOC +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte *ptr = ((byte *)p) - MALLOC_HEADER_SIZE; + dword size = ((dword*)(ptr))[0]; + byte tag = ((byte *)(p))[-1]; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; + local_free( ptr ); +#else + local_free( p ); +#endif +//RAVEN END +#else + switch( ((byte *)(p))[-1] ) { + case SMALL_ALLOC: { + SmallFree( p ); + break; + } + case MEDIUM_ALLOC: { + MediumFree( p ); + break; + } + case LARGE_ALLOC: { + LargeFree( p ); + break; + } + default: { + idLib::common->FatalError( "idHeap::Free: invalid memory block (%s)", idLib::sys->GetCallStackCurStr( 4 ) ); + break; + } + } + +#endif +} + +/* +================ +idHeap::Allocate16 +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::Allocate16( const dword bytes, byte tag ) { +//RAVEN END + byte *ptr, *alignedPtr; + +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + ptr = (byte *) Allocate(bytes+16, tag); + alignedPtr = (byte *) ( ( (int) ptr ) + 15 & ~15 ); + int padSize = alignedPtr - ptr; + if ( padSize == 0 ) { + alignedPtr += 16; + padSize += 16; + } + *((byte *)(alignedPtr - 1)) = (byte) padSize; + + assert( ( unsigned int )alignedPtr < 0xff000000 ); + + return (void *) alignedPtr; +#else +//RAVEN END +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ptr = (byte *) local_malloc( bytes + 16 + 4 ); +// RAVEN END + if ( !ptr ) { + if ( defragBlock ) { + idLib::common->Printf( "Freeing defragBlock on alloc of %i.\n", bytes ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + defragBlock = NULL; +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ptr = (byte *) local_malloc( bytes + 16 + 4 ); +// RAVEN END + AllocDefragBlock(); + } + if ( !ptr ) { + common->FatalError( "malloc failure for %i", bytes ); + } + } + + alignedPtr = (byte *) ( ( (int) ptr ) + 15 & ~15 ); + if ( alignedPtr - ptr < 4 ) { + alignedPtr += 16; + + } + *((int *)(alignedPtr - 4)) = (int) ptr; + assert( ( unsigned int )alignedPtr < 0xff000000 ); + return (void *) alignedPtr; + +//RAVEN BEGIN +//amccarthy: allocation tracking added for debug xbox only. +#endif +//RAVEN END + +} + +/* +================ +idHeap::Free16 +================ +*/ +void idHeap::Free16( void *p ) { +//RAVEN BEGIN +//amccarthy: allocation tag +#ifdef _DEBUG + byte* ptr = (byte*)p; + int padSize = *(ptr-1); + ptr -= padSize; + Free( ptr ); +#else + local_free( (void *) *((int *) (( (byte *) p ) - 4)) ); +#endif +//RAVEN END +} + +/* +================ +idHeap::Msize + + returns size of allocated memory block + p = pointer to memory block + Notes: size may not be the same as the size in the original + allocation request (due to block alignment reasons). +================ +*/ +dword idHeap::Msize( void *p ) { + + if ( !p ) { + return 0; + } + +#if USE_LIBC_MALLOC + #ifdef _WINDOWS +//RAVEN BEGIN +//amccarthy: allocation tracking + #ifdef _DEBUG + byte *ptr = ((byte *)p) - MALLOC_HEADER_SIZE; +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::MSize(ptr); + #else + return _msize(ptr); + #endif // RV_UNIFIED_ALLOCATOR + #else + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::MSize(p); + #else + return _msize(p); + #endif // RV_UNIFIED_ALLOCATOR + #endif +//RAVEN END + #else + return 0; + #endif +#else + switch( ((byte *)(p))[-1] ) { + case SMALL_ALLOC: { + return SMALL_ALIGN( ((byte *)(p))[-SMALL_HEADER_SIZE] * ALIGN ); + } + case MEDIUM_ALLOC: { + return ((mediumHeapEntry_s *)(((byte *)(p)) - ALIGN_SIZE( MEDIUM_HEADER_SIZE )))->size - ALIGN_SIZE( MEDIUM_HEADER_SIZE ); + } + case LARGE_ALLOC: { + return ((idHeap::page_s*)(*((dword *)(((byte *)p) - ALIGN_SIZE( LARGE_HEADER_SIZE )))))->dataSize - ALIGN_SIZE( LARGE_HEADER_SIZE ); + } + default: { + idLib::common->FatalError( "idHeap::Msize: invalid memory block (%s)", idLib::sys->GetCallStackCurStr( 4 ) ); + return 0; + } + } +#endif +} + +/* +================ +idHeap::Dump + + dump contents of the heap +================ +*/ +void idHeap::Dump( void ) { + idHeap::page_s *pg; + + for ( pg = smallFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (in use by small heap)\n", pg->data, pg->dataSize); + } + + if ( smallCurPage ) { + pg = smallCurPage; + idLib::common->Printf( "%p bytes %-8d (small heap active page)\n", pg->data, pg->dataSize ); + } + + for ( pg = mediumFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (completely used by medium heap)\n", pg->data, pg->dataSize ); + } + + for ( pg = mediumFirstFreePage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (partially used by medium heap)\n", pg->data, pg->dataSize ); + } + + for ( pg = largeFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (fully used by large heap)\n", pg->data, pg->dataSize ); + } + + idLib::common->Printf( "pages allocated : %d\n", pagesAllocated ); +} + +/* +================ +idHeap::FreePageReal + + frees page to be used by the OS + p = page to free +================ +*/ +void idHeap::FreePageReal( idHeap::page_s *p ) { + assert( p ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ::local_free( p ); +// RAVEN END +} + +/* +================ +idHeap::ReleaseSwappedPages + + releases the swap page to OS +================ +*/ +void idHeap::ReleaseSwappedPages () { + if ( swapPage ) { + FreePageReal( swapPage ); + } + swapPage = NULL; +} + +/* +================ +idHeap::AllocatePage + + allocates memory from the OS + bytes = page size in bytes + returns pointer to page +================ +*/ +idHeap::page_s* idHeap::AllocatePage( dword bytes ) { + idHeap::page_s* p; + + pageRequests++; + + if ( swapPage && swapPage->dataSize == bytes ) { // if we've got a swap page somewhere + p = swapPage; + swapPage = NULL; + } + else { + dword size; + + size = bytes + sizeof(idHeap::page_s); + +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + p = (idHeap::page_s *) ::local_malloc( size + ALIGN - 1 ); +// RAVEN END + if ( !p ) { + if ( defragBlock ) { + idLib::common->Printf( "Freeing defragBlock on alloc of %i.\n", size + ALIGN - 1 ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + defragBlock = NULL; +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + p = (idHeap::page_s *) ::local_malloc( size + ALIGN - 1 ); +// RAVEN END + AllocDefragBlock(); + } + if ( !p ) { + common->FatalError( "malloc failure for %i", bytes ); + } + } + + p->data = (void *) ALIGN_SIZE( (int)((byte *)(p)) + sizeof( idHeap::page_s ) ); + p->dataSize = size - sizeof(idHeap::page_s); + p->firstFree = NULL; + p->largestFree = 0; + OSAllocs++; + } + + p->prev = NULL; + p->next = NULL; + + pagesAllocated++; + + return p; +} + +/* +================ +idHeap::FreePage + + frees a page back to the operating system + p = pointer to page +================ +*/ +void idHeap::FreePage( idHeap::page_s *p ) { + assert( p ); + + if ( p->dataSize == pageSize && !swapPage ) { // add to swap list? + swapPage = p; + } + else { + FreePageReal( p ); + } + + pagesAllocated--; +} + +//=============================================================== +// +// small heap code +// +//=============================================================== + +/* +================ +idHeap::SmallAllocate + + allocate memory (1-255 bytes) from the small heap manager + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::SmallAllocate( dword bytes, byte tag ) { +//RAVEN END + // we need the at least sizeof( dword ) bytes for the free list + if ( bytes < sizeof( dword ) ) { + bytes = sizeof( dword ); + } + + // increase the number of bytes if necessary to make sure the next small allocation is aligned + bytes = SMALL_ALIGN( bytes ); + + byte *smallBlock = (byte *)(smallFirstFree[bytes / ALIGN]); + if ( smallBlock ) { + dword *link = (dword *)(smallBlock + SMALL_HEADER_SIZE); +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += smallBlock[0]; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += smallBlock[0]; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + smallBlock[1] = tag; + smallBlock[2] = SMALL_ALLOC; // allocation identifier + +#else + + smallBlock[1] = SMALL_ALLOC; // allocation identifier +#endif + +//RAVEN END + smallFirstFree[bytes / ALIGN] = (void *)(*link); + return (void *)(link); + } + + dword bytesLeft = (long)(pageSize) - smallCurPageOffset; + // if we need to allocate a new page + if ( bytes >= bytesLeft ) { + + smallCurPage->next = smallFirstUsedPage; + smallFirstUsedPage = smallCurPage; + smallCurPage = AllocatePage( pageSize ); + if ( !smallCurPage ) { + return NULL; + } + // make sure the first allocation is aligned + smallCurPageOffset = SMALL_ALIGN( 0 ); + } + + smallBlock = ((byte *)smallCurPage->data) + smallCurPageOffset; + smallBlock[0] = (byte)(bytes / ALIGN); // write # of bytes/ALIGN +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += smallBlock[0]; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += smallBlock[0]; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + smallBlock[1] = tag; + smallBlock[2] = SMALL_ALLOC; // allocation identifier + +#else + + smallBlock[1] = SMALL_ALLOC; // allocation identifier +#endif +//RAVEN END + smallCurPageOffset += bytes + SMALL_HEADER_SIZE; // increase the offset on the current page + return ( smallBlock + SMALL_HEADER_SIZE ); // skip the first two bytes +} + +/* +================ +idHeap::SmallFree + + frees a block of memory allocated by SmallAllocate() call + data = pointer to block of memory +================ +*/ +void idHeap::SmallFree( void *ptr ) { + ((byte *)(ptr))[-1] = INVALID_ALLOC; + +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + byte size = ((byte *)(ptr))[-3]; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + byte *d = ( (byte *)ptr ) - SMALL_HEADER_SIZE; + dword *dt = (dword *)ptr; + // index into the table with free small memory blocks + dword ix = *d; + + // check if the index is correct + if ( ix > (256 / ALIGN) ) { + idLib::common->FatalError( "SmallFree: invalid memory block" ); + } + + *dt = (dword)smallFirstFree[ix]; // write next index + smallFirstFree[ix] = (void *)d; // link +} + +//=============================================================== +// +// medium heap code +// +// Medium-heap allocated pages not returned to OS until heap destructor +// called (re-used instead on subsequent medium-size malloc requests). +// +//=============================================================== + +/* +================ +idHeap::MediumAllocateFromPage + + performs allocation using the medium heap manager from a given page + p = page + sizeNeeded = # of bytes needed + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::MediumAllocateFromPage( idHeap::page_s *p, dword sizeNeeded, byte tag ) { +//RAVEN END + + mediumHeapEntry_s *best,*nw = NULL; + byte *ret; + + best = (mediumHeapEntry_s *)(p->firstFree); // first block is largest + + assert( best ); + assert( best->size == p->largestFree ); + assert( best->size >= sizeNeeded ); + + // if we can allocate another block from this page after allocating sizeNeeded bytes + if ( best->size >= (dword)( sizeNeeded + MEDIUM_SMALLEST_SIZE ) ) { + nw = (mediumHeapEntry_s *)((byte *)best + best->size - sizeNeeded); + nw->page = p; + nw->prev = best; + nw->next = best->next; + nw->prevFree = NULL; + nw->nextFree = NULL; + nw->size = sizeNeeded; + nw->freeBlock = 0; // used block + if ( best->next ) { + best->next->prev = nw; + } + best->next = nw; + best->size -= sizeNeeded; + + p->largestFree = best->size; + } + else { + if ( best->prevFree ) { + best->prevFree->nextFree = best->nextFree; + } + else { + p->firstFree = (void *)best->nextFree; + } + if ( best->nextFree ) { + best->nextFree->prevFree = best->prevFree; + } + + best->prevFree = NULL; + best->nextFree = NULL; + best->freeBlock = 0; // used block + nw = best; + + p->largestFree = 0; + } + + ret = (byte *)(nw) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ); +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += nw->size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += nw->size; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + ret[-2] = tag; +#endif +//RAVEN END + ret[-1] = MEDIUM_ALLOC; // allocation identifier + + return (void *)(ret); +} + +/* +================ +idHeap::MediumAllocate + + allocate memory (256-32768 bytes) from medium heap manager + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::MediumAllocate( dword bytes, byte tag ) { +//RAVEN END + idHeap::page_s *p; + void *data; + + dword sizeNeeded = ALIGN_SIZE( bytes ) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ); + + // find first page with enough space + for ( p = mediumFirstFreePage; p; p = p->next ) { + if ( p->largestFree >= sizeNeeded ) { + break; + } + } + + if ( !p ) { // need to allocate new page? + p = AllocatePage( pageSize ); + if ( !p ) { + return NULL; // malloc failure! + } + p->prev = NULL; + p->next = mediumFirstFreePage; + if (p->next) { + p->next->prev = p; + } + else { + mediumLastFreePage = p; + } + + mediumFirstFreePage = p; + + p->largestFree = pageSize; + p->firstFree = (void *)p->data; + + mediumHeapEntry_s *e; + e = (mediumHeapEntry_s *)(p->firstFree); + e->page = p; + // make sure ((byte *)e + e->size) is aligned + e->size = pageSize & ~(ALIGN - 1); + e->prev = NULL; + e->next = NULL; + e->prevFree = NULL; + e->nextFree = NULL; + e->freeBlock = 1; + } + +//RAVEN BEGIN +//amccarthy: Added allocation tag + data = MediumAllocateFromPage( p, sizeNeeded, tag ); // allocate data from page +//RAVEN END + + // if the page can no longer serve memory, move it away from free list + // (so that it won't slow down the later alloc queries) + // this modification speeds up the pageWalk from O(N) to O(sqrt(N)) + // a call to free may swap this page back to the free list + + if ( p->largestFree < MEDIUM_SMALLEST_SIZE ) { + if ( p == mediumLastFreePage ) { + mediumLastFreePage = p->prev; + } + + if ( p == mediumFirstFreePage ) { + mediumFirstFreePage = p->next; + } + + if ( p->prev ) { + p->prev->next = p->next; + } + if ( p->next ) { + p->next->prev = p->prev; + } + + // link to "completely used" list + p->prev = NULL; + p->next = mediumFirstUsedPage; + if ( p->next ) { + p->next->prev = p; + } + mediumFirstUsedPage = p; + return data; + } + + // re-order linked list (so that next malloc query starts from current + // matching block) -- this speeds up both the page walks and block walks + + if ( p != mediumFirstFreePage ) { + assert( mediumLastFreePage ); + assert( mediumFirstFreePage ); + assert( p->prev); + + mediumLastFreePage->next = mediumFirstFreePage; + mediumFirstFreePage->prev = mediumLastFreePage; + mediumLastFreePage = p->prev; + p->prev->next = NULL; + p->prev = NULL; + mediumFirstFreePage = p; + } + + return data; +} + +/* +================ +idHeap::MediumFree + + frees a block allocated by the medium heap manager + ptr = pointer to data block +================ +*/ +void idHeap::MediumFree( void *ptr ) { + ((byte *)(ptr))[-1] = INVALID_ALLOC; + + mediumHeapEntry_s *e = (mediumHeapEntry_s *)((byte *)ptr - ALIGN_SIZE( MEDIUM_HEADER_SIZE )); + idHeap::page_s *p = e->page; + bool isInFreeList; + + isInFreeList = p->largestFree >= MEDIUM_SMALLEST_SIZE; + + assert( e->size ); + assert( e->freeBlock == 0 ); + +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + dword size = e->size; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + mediumHeapEntry_s *prev = e->prev; + + // if the previous block is free we can merge + if ( prev && prev->freeBlock ) { + prev->size += e->size; + prev->next = e->next; + if ( e->next ) { + e->next->prev = prev; + } + e = prev; + } + else { + e->prevFree = NULL; // link to beginning of free list + e->nextFree = (mediumHeapEntry_s *)p->firstFree; + if ( e->nextFree ) { + assert( !(e->nextFree->prevFree) ); + e->nextFree->prevFree = e; + } + + p->firstFree = e; + p->largestFree = e->size; + e->freeBlock = 1; // mark block as free + } + + mediumHeapEntry_s *next = e->next; + + // if the next block is free we can merge + if ( next && next->freeBlock ) { + e->size += next->size; + e->next = next->next; + + if ( next->next ) { + next->next->prev = e; + } + + if ( next->prevFree ) { + next->prevFree->nextFree = next->nextFree; + } + else { + assert( next == p->firstFree ); + p->firstFree = next->nextFree; + } + + if ( next->nextFree ) { + next->nextFree->prevFree = next->prevFree; + } + } + + if ( p->firstFree ) { + p->largestFree = ((mediumHeapEntry_s *)(p->firstFree))->size; + } + else { + p->largestFree = 0; + } + + // did e become the largest block of the page ? + + if ( e->size > p->largestFree ) { + assert( e != p->firstFree ); + p->largestFree = e->size; + + if ( e->prevFree ) { + e->prevFree->nextFree = e->nextFree; + } + if ( e->nextFree ) { + e->nextFree->prevFree = e->prevFree; + } + + e->nextFree = (mediumHeapEntry_s *)p->firstFree; + e->prevFree = NULL; + if ( e->nextFree ) { + e->nextFree->prevFree = e; + } + p->firstFree = e; + } + + // if page wasn't in free list (because it was near-full), move it back there + if ( !isInFreeList ) { + + // remove from "completely used" list + if ( p->prev ) { + p->prev->next = p->next; + } + if ( p->next ) { + p->next->prev = p->prev; + } + if ( p == mediumFirstUsedPage ) { + mediumFirstUsedPage = p->next; + } + + p->next = NULL; + p->prev = mediumLastFreePage; + + if ( mediumLastFreePage ) { + mediumLastFreePage->next = p; + } + mediumLastFreePage = p; + if ( !mediumFirstFreePage ) { + mediumFirstFreePage = p; + } + } +} + +//=============================================================== +// +// large heap code +// +//=============================================================== + +/* +================ +idHeap::LargeAllocate + + allocates a block of memory from the operating system + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::LargeAllocate( dword bytes, byte tag ) { +//RAVEN END + idHeap::page_s *p = AllocatePage( bytes + ALIGN_SIZE( LARGE_HEADER_SIZE ) ); + + assert( p ); + + if ( !p ) { + return NULL; + } + + byte * d = (byte*)(p->data) + ALIGN_SIZE( LARGE_HEADER_SIZE ); + dword * dw = (dword*)(d - ALIGN_SIZE( LARGE_HEADER_SIZE )); + dw[0] = (dword)p; // write pointer back to page table +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += p->dataSize; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += p->dataSize; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + d[-2] = tag; +#endif +//RAVEN END + d[-1] = LARGE_ALLOC; // allocation identifier + + // link to 'large used page list' + p->prev = NULL; + p->next = largeFirstUsedPage; + if ( p->next ) { + p->next->prev = p; + } + largeFirstUsedPage = p; + + return (void *)(d); +} + +/* +================ +idHeap::LargeFree + + frees a block of memory allocated by the 'large memory allocator' + p = pointer to allocated memory +================ +*/ +void idHeap::LargeFree( void *ptr) { + idHeap::page_s* pg; + + ((byte *)(ptr))[-1] = INVALID_ALLOC; + + // get page pointer + pg = (idHeap::page_s *)(*((dword *)(((byte *)ptr) - ALIGN_SIZE( LARGE_HEADER_SIZE )))); + + //RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + dword size = pg->dataSize; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + // unlink from doubly linked list + if ( pg->prev ) { + pg->prev->next = pg->next; + } + if ( pg->next ) { + pg->next->prev = pg->prev; + } + if ( pg == largeFirstUsedPage ) { + largeFirstUsedPage = pg->next; + } + pg->next = pg->prev = NULL; + + FreePage(pg); +} + +//=============================================================== +// +// memory allocation all in one place +// +//=============================================================== + +#undef new + +static idHeap * mem_heap = NULL; +static memoryStats_t mem_total_allocs = { 0, 0x0fffffff, -1, 0 }; +static memoryStats_t mem_frame_allocs; +static memoryStats_t mem_frame_frees; + +/* +================== +Mem_ClearFrameStats +================== +*/ +void Mem_ClearFrameStats( void ) { + mem_frame_allocs.num = mem_frame_frees.num = 0; + mem_frame_allocs.minSize = mem_frame_frees.minSize = 0x0fffffff; + mem_frame_allocs.maxSize = mem_frame_frees.maxSize = -1; + mem_frame_allocs.totalSize = mem_frame_frees.totalSize = 0; +} + +/* +================== +Mem_GetFrameStats +================== +*/ +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ) { + allocs = mem_frame_allocs; + frees = mem_frame_frees; +} + +/* +================== +Mem_GetStats +================== +*/ +void Mem_GetStats( memoryStats_t &stats ) { + stats = mem_total_allocs; +} + +/* +================== +Mem_UpdateStats +================== +*/ +void Mem_UpdateStats( memoryStats_t &stats, int size ) { + stats.num++; + if ( size < stats.minSize ) { + stats.minSize = size; + } + if ( size > stats.maxSize ) { + stats.maxSize = size; + } + stats.totalSize += size; +} + +/* +================== +Mem_UpdateAllocStats +================== +*/ +void Mem_UpdateAllocStats( int size ) { + Mem_UpdateStats( mem_frame_allocs, size ); + Mem_UpdateStats( mem_total_allocs, size ); +} + +/* +================== +Mem_UpdateFreeStats +================== +*/ +void Mem_UpdateFreeStats( int size ) { + Mem_UpdateStats( mem_frame_frees, size ); + mem_total_allocs.num--; + mem_total_allocs.totalSize -= size; +} + + +#ifndef ID_DEBUG_MEMORY + +/* +================== +Mem_Alloc +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_Alloc( const int size, byte tag ) { + if ( !size ) { + return NULL; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); + } +// amccarthy: Added allocation tag + void *mem = mem_heap->Allocate( size, tag ); + Mem_UpdateAllocStats( mem_heap->Msize( mem ) ); + return mem; +} +// RAVEN END + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr ) { + if ( !ptr ) { + return; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( ptr ); +// RAVEN END + return; + } + Mem_UpdateFreeStats( mem_heap->Msize( ptr ) ); + mem_heap->Free( ptr ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_Alloc16( const int size, byte tag ) { + if ( !size ) { + return NULL; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); + } + +// amccarthy: Added allocation tag + void *mem = mem_heap->Allocate16( size, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} +// RAVEN END + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr ) { + if ( !ptr ) { + return; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( ptr ); +// RAVEN END + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + mem_heap->Free16( ptr ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_ClearedAlloc( const int size, byte tag ) { + void *mem = Mem_Alloc( size, tag ); +// RAVEN END + SIMDProcessor->Memset( mem, 0, size ); + return mem; +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void Mem_AllocDefragBlock( void ) { + mem_heap->AllocDefragBlock(); +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in ) { + char *out; + +// RAVEN BEGIN +// amccarthy: Added allocation tag + out = (char *)Mem_Alloc( strlen(in) + 1, MA_STRING ); +// RAVEN END + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) { +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) { +} + +/* +================== +Mem_Init +================== +*/ +void Mem_Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + mem_heap = new idHeap; + Mem_ClearFrameStats(); +} + +/* +================== +Mem_Shutdown +================== +*/ +void Mem_Shutdown( void ) { + idHeap *m = mem_heap; + mem_heap = NULL; + delete m; +} + +/* +================== +Mem_EnableLeakTest +================== +*/ +void Mem_EnableLeakTest( const char *name ) { +} + + +#else /* !ID_DEBUG_MEMORY */ + +#undef Mem_Alloc +#undef Mem_ClearedAlloc +#undef Com_ClearedReAlloc +#undef Mem_Free +#undef Mem_CopyString +#undef Mem_Alloc16 +#undef Mem_Free16 + +#define MAX_CALLSTACK_DEPTH 6 + +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation +// size of this struct must be a multiple of 16 bytes +typedef struct debugMemory_s { + unsigned short signature; + unsigned short lineNumber; + const char * fileName; + int frameNumber; + int size; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct debugMemory_s * prev; + struct debugMemory_s * next; +} debugMemory_t; +// RAVEN END + +static debugMemory_t * mem_debugMemory = NULL; +static char mem_leakName[256] = ""; + +/* +================== +Mem_CleanupFileName +================== +*/ +const char *Mem_CleanupFileName( const char *fileName ) { + int i1, i2; + idStr newFileName; + static char newFileNames[4][MAX_STRING_CHARS]; + static int index; + + newFileName = fileName; + newFileName.BackSlashesToSlashes(); + i1 = newFileName.Find( "neo", false ); + if ( i1 >= 0 ) { + i1 = newFileName.Find( "/", false, i1 ); + newFileName = newFileName.Right( newFileName.Length() - ( i1 + 1 ) ); + } + while( 1 ) { + i1 = newFileName.Find( "/../" ); + if ( i1 <= 0 ) { + break; + } + i2 = i1 - 1; + while( i2 > 1 && newFileName[i2-1] != '/' ) { + i2--; + } + newFileName = newFileName.Left( i2 - 1 ) + newFileName.Right( newFileName.Length() - ( i1 + 4 ) ); + } + index = ( index + 1 ) & 3; + strncpy( newFileNames[index], newFileName.c_str(), sizeof( newFileNames[index] ) ); + return newFileNames[index]; +} + +/* +================== +Mem_Dump +================== +*/ +void Mem_Dump( const char *fileName ) { + int i, numBlocks, totalSize; + char dump[32], *ptr; + debugMemory_t *b; + idStr module, funcName; + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + totalSize = 0; + for ( numBlocks = 0, b = mem_debugMemory; b; b = b->next, numBlocks++ ) { + ptr = ((char *) b) + sizeof(debugMemory_t); + totalSize += b->size; + for ( i = 0; i < (sizeof(dump)-1) && i < b->size; i++) { + if ( ptr[i] >= 32 && ptr[i] < 127 ) { + dump[i] = ptr[i]; + } else { + dump[i] = '_'; + } + } + dump[i] = '\0'; + if ( ( b->size >> 10 ) != 0 ) { + fprintf( f, "size: %6d KB: %s, line: %d [%s], call stack: %s\r\n", ( b->size >> 10 ), Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else { + fprintf( f, "size: %7d B: %s, line: %d [%s], call stack: %s\r\n", b->size, Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +} + + + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) { + const char *fileName; + + if ( args.Argc() >= 2 ) { + fileName = args.Argv( 1 ); + } + else { + fileName = "memorydump.txt"; + } + Mem_Dump( fileName ); +} + +/* +================== +Mem_DumpCompressed +================== +*/ + +typedef struct allocInfo_s { + const char * fileName; + int lineNumber; + int size; + int numAllocs; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct allocInfo_s * next; +} allocInfo_t; + +typedef enum { + MEMSORT_SIZE, + MEMSORT_LOCATION, + MEMSORT_NUMALLOCS, + MEMSORT_CALLSTACK +} memorySortType_t; + +void Mem_DumpCompressed( const char *fileName, memorySortType_t memSort, int sortCallStack, int numFrames ) { + int numBlocks, totalSize, r, j; + debugMemory_t *b; + allocInfo_t *a, *nexta, *allocInfo = NULL, *sortedAllocInfo = NULL, *prevSorted, *nextSorted; + idStr module, funcName; + + // build list with memory allocations + totalSize = 0; + numBlocks = 0; +// RAVEN BEGIN + nextSorted = NULL; +// RAVEN END + + for ( b = mem_debugMemory; b; b = b->next ) { + + if ( numFrames && b->frameNumber < idLib::frameNumber - numFrames ) { + continue; + } + + numBlocks++; + totalSize += b->size; + + // search for an allocation from the same source location + for ( a = allocInfo; a; a = a->next ) { + if ( a->lineNumber != b->lineNumber ) { + continue; + } +// RAVEN BEGIN +// dluetscher: removed the call stack info for better consolidation of info and speed of dump +#ifndef _XENON + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + if ( a->callStack[j] != b->callStack[j] ) { + break; + } + } + if ( j < MAX_CALLSTACK_DEPTH ) { + continue; + } +#endif +// RAVEN END + if ( idStr::Cmp( a->fileName, b->fileName ) != 0 ) { + continue; + } + a->numAllocs++; + a->size += b->size; + break; + } + + // if this is an allocation from a new source location + if ( !a ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + a = (allocInfo_t *) ::local_malloc( sizeof( allocInfo_t ) ); +// RAVEN END + a->fileName = b->fileName; + a->lineNumber = b->lineNumber; + a->size = b->size; + a->numAllocs = 1; + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + a->callStack[j] = b->callStack[j]; + } + a->next = allocInfo; + allocInfo = a; + } + } + + // sort list + for ( a = allocInfo; a; a = nexta ) { + nexta = a->next; + + prevSorted = NULL; + switch( memSort ) { + // sort on size + case MEMSORT_SIZE: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->size > nextSorted->size ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on file name and line number + case MEMSORT_LOCATION: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + r = idStr::Cmp( Mem_CleanupFileName( a->fileName ), Mem_CleanupFileName( nextSorted->fileName ) ); + if ( r < 0 || ( r == 0 && a->lineNumber < nextSorted->lineNumber ) ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on the number of allocations + case MEMSORT_NUMALLOCS: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->numAllocs > nextSorted->numAllocs ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on call stack + case MEMSORT_CALLSTACK: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->callStack[sortCallStack] < nextSorted->callStack[sortCallStack] ) { + break; + } + prevSorted = nextSorted; + } + break; + } + } + if ( !prevSorted ) { + a->next = sortedAllocInfo; + sortedAllocInfo = a; + } + else { + prevSorted->next = a; + a->next = nextSorted; + } + } + +// RAVEN BEGIN +// dluetscher: changed xenon version to output anything above 1K to the console +#ifdef _XENON + // write list to debug output and console + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + if ( (a->size >> 10) > 0 ) { + + idLib::common->Printf("size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); + + } + ::local_free( a ); + } + + idLib::sys->ShutdownSymbols(); + + idLib::common->Printf("%8d total memory blocks allocated\r\n", numBlocks ); + idLib::common->Printf("%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); +#else +// RAVEN END + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + // write list to file + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + fprintf( f, "size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ::local_free( a ); +// RAVEN END + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +#endif +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) { + int argNum; + const char *arg, *fileName; + memorySortType_t memSort = MEMSORT_LOCATION; + int sortCallStack = 0, numFrames = 0; + + // get cmd-line options + argNum = 1; + arg = args.Argv( argNum ); + while( arg[0] == '-' ) { + arg = args.Argv( ++argNum ); + if ( idStr::Icmp( arg, "s" ) == 0 ) { + memSort = MEMSORT_SIZE; + } else if ( idStr::Icmp( arg, "l" ) == 0 ) { + memSort = MEMSORT_LOCATION; + } else if ( idStr::Icmp( arg, "a" ) == 0 ) { + memSort = MEMSORT_NUMALLOCS; + } else if ( idStr::Icmp( arg, "cs1" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 2; + } else if ( idStr::Icmp( arg, "cs2" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 1; + } else if ( idStr::Icmp( arg, "cs3" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 0; + } else if ( arg[0] == 'f' ) { + numFrames = atoi( arg + 1 ); + } else { + idLib::common->Printf( "memoryDumpCompressed [options] [filename]\n" + "options:\n" + " -s sort on size\n" + " -l sort on location\n" + " -a sort on the number of allocations\n" + " -cs1 sort on first function on call stack\n" + " -cs2 sort on second function on call stack\n" + " -cs3 sort on third function on call stack\n" + " -f only report allocations the last X frames\n" + "By default the memory allocations are sorted on location.\n" + "By default a 'memorydump.txt' is written if no file name is specified.\n" ); + return; + } + arg = args.Argv( ++argNum ); + } + if ( argNum >= args.Argc() ) { + fileName = "memorydump.txt"; + } else { + fileName = arg; + } + Mem_DumpCompressed( fileName, memSort, sortCallStack, numFrames ); +} + +/* +================== +Mem_AllocDebugMemory +================== +*/ +// RAVEN BEGIN +void *Mem_AllocDebugMemory( const int size, const char *fileName, const int lineNumber, const bool align16, byte tag ) { +// RAVEN END + void *p; + debugMemory_t *m; + + if ( !size ) { + return NULL; + } + + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif + // NOTE: set a breakpoint here to find memory allocations before mem_heap is initialized +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); +// RAVEN END + } + + if ( align16 ) { +// RAVEN BEGIN + p = mem_heap->Allocate16( size + sizeof( debugMemory_t ), tag ); + } + else { + p = mem_heap->Allocate( size + sizeof( debugMemory_t ), tag ); +// RAVEN END + } + + Mem_UpdateAllocStats( size ); + + m = (debugMemory_t *) p; +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation + m->signature = 0xf00d; +// RAVEN END + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = size; + m->next = mem_debugMemory; + m->prev = NULL; + if ( mem_debugMemory ) { + mem_debugMemory->prev = m; + } + mem_debugMemory = m; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + return ( ( (byte *) p ) + sizeof( debugMemory_t ) ); +} + +/* +================== +Mem_FreeDebugMemory +================== +*/ +void Mem_FreeDebugMemory( void *p, const char *fileName, const int lineNumber, const bool align16 ) { + debugMemory_t *m; + + if ( !p ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation + m = (debugMemory_t *) ( ( (byte *) p ) - sizeof( debugMemory_t ) ); + + if ( !mem_heap || m->signature != 0xf00d ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif + // NOTE: set a breakpoint here to find memory being freed before mem_heap is initialized +// jnewquist: send all allocations through one place on the Xenon + local_free( p ); + return; + } + +// RAVEN END + if ( m->size < 0 ) { + idLib::common->FatalError( "memory freed twice, first from %s, now from %s", idLib::sys->GetCallStackStr( m->callStack, MAX_CALLSTACK_DEPTH ), idLib::sys->GetCallStackCurStr( MAX_CALLSTACK_DEPTH ) ); + } + + Mem_UpdateFreeStats( m->size ); + + if ( m->next ) { + m->next->prev = m->prev; + } + if ( m->prev ) { + m->prev->next = m->next; + } + else { + mem_debugMemory = m->next; + } + + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = -m->size; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + if ( align16 ) { + mem_heap->Free16( m ); + } + else { + mem_heap->Free( m ); + } +} + +/* +================== +Mem_Alloc +================== +*/ +void *Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag ) { + if ( !size ) { + return NULL; + } + return Mem_AllocDebugMemory( size, fileName, lineNumber, false, tag ); +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ) { + if ( !ptr ) { + return; + } + Mem_FreeDebugMemory( ptr, fileName, lineNumber, false ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +void *Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag ) { + if ( !size ) { + return NULL; + } + void *mem = Mem_AllocDebugMemory( size, fileName, lineNumber, true, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ) { + if ( !ptr ) { + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + Mem_FreeDebugMemory( ptr, fileName, lineNumber, true ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void *Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag ) { + void *mem = Mem_Alloc( size, fileName, lineNumber, tag ); + SIMDProcessor->Memset( mem, 0, size ); + return mem; +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in, const char *fileName, const int lineNumber ) { + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, fileName, lineNumber ); + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Init +================== +*/ +void Mem_Init( void ) { + mem_heap = new idHeap; +} + +/* +================== +Mem_Shutdown +================== +*/ +void Mem_Shutdown( void ) { + + if ( mem_leakName[0] != '\0' ) { + Mem_DumpCompressed( va( "%s_leak_size.txt", mem_leakName ), MEMSORT_SIZE, 0, 0 ); + Mem_DumpCompressed( va( "%s_leak_location.txt", mem_leakName ), MEMSORT_LOCATION, 0, 0 ); + Mem_DumpCompressed( va( "%s_leak_cs1.txt", mem_leakName ), MEMSORT_CALLSTACK, 2, 0 ); + } + + idHeap *m = mem_heap; + mem_heap = NULL; + delete m; +} + +/* +================== +Mem_EnableLeakTest +================== +*/ +void Mem_EnableLeakTest( const char *name ) { + idStr::Copynz( mem_leakName, name, sizeof( mem_leakName ) ); +} + +// RAVEN BEGIN +// jnewquist: Add Mem_Size to query memory allocation size +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) { + return mem_heap->Msize(ptr); +} +// RAVEN END + +#endif /* !ID_DEBUG_MEMORY */ + +#endif // #ifndef _RV_MEM_SYS_SUPPORT diff --git a/source/idlib/Heap.h b/source/idlib/Heap.h new file mode 100644 index 0000000..08d00c3 --- /dev/null +++ b/source/idlib/Heap.h @@ -0,0 +1,1168 @@ + +#ifndef __HEAP_H__ +#define __HEAP_H__ + +/* +=============================================================================== + + Memory Management + + This is a replacement for the compiler heap code (i.e. "C" malloc() and + free() calls). On average 2.5-3.0 times faster than MSVC malloc()/free(). + Worst case performance is 1.65 times faster and best case > 70 times. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +class Memory +{ +public: + static void *Allocate(size_t size) + { + if(mAllocator) + { + return mAllocator(size); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return malloc(size); + } + } + + static void Free(void *ptr) + { + if(mDeallocator) + { + mDeallocator(ptr); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return free(ptr); + } + } + + static size_t MSize(void *ptr) + { + if(mMSize) + { + return mMSize(ptr); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return _msize(ptr); + } + } + + static void InitAllocator(void *(*allocator)(size_t size), void (*deallocator)(void *), size_t (*msize)(void *ptr)) + { + // check for errors prior to initialization + if(!sOK) + { + Error("Unprotected allocation in DLL detected prior to initialization of memory system"); + } + + mAllocator = allocator; + mDeallocator = deallocator; + mMSize = msize; + } + + static void DeinitAllocator() + { + mAllocator = NULL; + mDeallocator = NULL; + mMSize = NULL; + } + + static void Error(const char *errStr); + +private: + static void *(*mAllocator)(size_t size); + static void (*mDeallocator)(void *ptr); + static size_t (*mMSize)(void *ptr); + static bool sOK; +}; +#endif +// RAVEN END + +typedef struct { + int num; + int minSize; + int maxSize; + int totalSize; +} memoryStats_t; + +// RAVEN BEGIN +// amccarthy: tags for memory allocation tracking. When updating this list please update the +// list of discriptions in Heap.cpp as well. +typedef enum { + MA_NONE = 0, + + MA_OPNEW, + MA_DEFAULT, + MA_LEXER, + MA_PARSER, + MA_AAS, + MA_CLASS, + MA_SCRIPT, + MA_CM, + MA_CVAR, + MA_DECL, + MA_FILESYS, + MA_IMAGES, + MA_MATERIAL, + MA_MODEL, + MA_FONT, + MA_RENDER, + MA_VERTEX, + MA_SOUND, + MA_WINDOW, + MA_EVENT, + MA_MATH, + MA_ANIM, + MA_DYNAMICBLOCK, + MA_STRING, + MA_GUI, + MA_EFFECT, + MA_ENTITY, + MA_PHYSICS, + MA_AI, + MA_NETWORK, + + MA_DO_NOT_USE, // neither of the two remaining enumerated values should be used (no use of MA_DO_NOT_USE prevents the second dword in a memory block from getting the value 0xFFFFFFFF) + MA_MAX // <- this enumerated value is a count and cannot exceed 32 (5 bits are used to encode tag within memory block with rvHeap.cpp) +} Mem_Alloc_Types_t; + +#if defined(_DEBUG) || defined(_RV_MEM_SYS_SUPPORT) +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak); +#endif +// RAVEN END + +void Mem_Init( void ); +void Mem_Shutdown( void ); +void Mem_EnableLeakTest( const char *name ); +void Mem_ClearFrameStats( void ); +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ); +void Mem_GetStats( memoryStats_t &stats ); +void Mem_Dump_f( const class idCmdArgs &args ); +void Mem_DumpCompressed_f( const class idCmdArgs &args ); +void Mem_AllocDefragBlock( void ); + +// RAVEN BEGIN +// amccarthy command for printing tagged mem_alloc info +void Mem_ShowMemAlloc_f( const idCmdArgs &args ); + +// jnewquist: Add Mem_Size to query memory allocation size +int Mem_Size( void *ptr ); + +// jnewquist: memory tag stack for new/delete +#if (defined(_DEBUG) || defined(_RV_MEM_SYS_SUPPORT)) && !defined(ENABLE_INTEL_SMP) +class MemScopedTag { + byte mTag; + MemScopedTag *mPrev; + static MemScopedTag *mTop; +public: + MemScopedTag( byte tag ) { + mTag = tag; + mPrev = mTop; + mTop = this; + } + ~MemScopedTag() { + assert( mTop != NULL ); + mTop = mTop->mPrev; + } + void SetTag( byte tag ) { + mTag = tag; + } + static byte GetTopTag( void ) { + if ( mTop ) { + return mTop->mTag; + } else { + return MA_OPNEW; + } + } +}; +#define MemScopedTag_GetTopTag() MemScopedTag::GetTopTag() +#define MEM_SCOPED_TAG(var, tag) MemScopedTag var(tag) +#define MEM_SCOPED_TAG_SET(var, tag) var.SetTag(tag) +#else +#define MemScopedTag_GetTopTag() MA_OPNEW +#define MEM_SCOPED_TAG(var, tag) +#define MEM_SCOPED_TAG_SET(var, tag) +#endif + +// RAVEN END + +#ifndef ID_DEBUG_MEMORY + +// RAVEN BEGIN +// amccarthy: added tags from memory allocation tracking. +void * Mem_Alloc( const int size, byte tag = MA_DEFAULT ); +void * Mem_ClearedAlloc( const int size, byte tag = MA_DEFAULT ); +void Mem_Free( void *ptr ); +char * Mem_CopyString( const char *in ); +void * Mem_Alloc16( const int size, byte tag=MA_DEFAULT ); +void Mem_Free16( void *ptr ); + +// jscott: standardised stack allocation +inline void *Mem_StackAlloc( const int size ) { return( _alloca( size ) ); } +inline void *Mem_StackAlloc16( const int size ) { + byte *addr = ( byte * )_alloca( size + 15 ); + addr = ( byte * )( ( int )( addr + 15 ) & 0xfffffff0 ); + return( ( void * )addr ); +} + +// dluetscher: moved the inline new/delete operators to sys_local.cpp and Game_local.cpp so that +// Tools.dll will link. +#if defined(_XBOX) || defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) + +void *operator new( size_t s ); +void operator delete( void *p ); +void *operator new[]( size_t s ); +void operator delete[]( void *p ); + +#endif +// RAVEN END + +#else /* ID_DEBUG_MEMORY */ + +// RAVEN BEGIN +// amccarthy: added tags from memory allocation tracking. +void * Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT ); +void * Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT ); +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ); +char * Mem_CopyString( const char *in, const char *fileName, const int lineNumber ); +void * Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT); +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ); + + +// jscott: standardised stack allocation +inline void *Mem_StackAlloc( const int size ) { return( _alloca( size ) ); } +inline void *Mem_StackAlloc16( const int size ) { + byte *addr = ( byte * )_alloca( size + 15 ); + addr = ( byte * )( ( int )( addr + 15 ) & 0xfffffff0 ); + return( ( void * )addr ); +} + +// dluetscher: moved the inline new/delete operators to sys_local.cpp and Game_local.cpp so that +// the Tools.dll will link. +#if defined(_XBOX) || defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) + +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ); +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ); +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ); +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ); + +void *operator new( size_t s ); +void operator delete( void *p ); +void *operator new[]( size_t s ); +void operator delete[]( void *p ); +// RAVEN END + +#define ID_DEBUG_NEW new( 0, 0, __FILE__, __LINE__ ) +#undef new +#define new ID_DEBUG_NEW + +#endif + +#define Mem_Alloc( size, tag ) Mem_Alloc( size, __FILE__, __LINE__ ) +#define Mem_ClearedAlloc( size, tag ) Mem_ClearedAlloc( size, __FILE__, __LINE__ ) +#define Mem_Free( ptr ) Mem_Free( ptr, __FILE__, __LINE__ ) +#define Mem_CopyString( s ) Mem_CopyString( s, __FILE__, __LINE__ ) +#define Mem_Alloc16( size, tag ) Mem_Alloc16( size, __FILE__, __LINE__ ) +#define Mem_Free16( ptr ) Mem_Free16( ptr, __FILE__, __LINE__ ) +// RAVEN END +#endif /* ID_DEBUG_MEMORY */ + + +/* +=============================================================================== + + Block based allocator for fixed size objects. + + All objects of the 'type' are properly constructed. + However, the constructor is not called for re-used objects. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +template +class idBlockAlloc { +public: + idBlockAlloc( void ); + ~idBlockAlloc( void ); + + void Shutdown( void ); + + type * Alloc( void ); + void Free( type *element ); + + int GetTotalCount( void ) const { return total; } + int GetAllocCount( void ) const { return active; } + int GetFreeCount( void ) const { return total - active; } + +// RAVEN BEGIN +// jscott: get the amount of memory used + size_t Allocated( void ) const { return( total * sizeof( type ) ); } +// RAVEN END + +private: + typedef struct element_s { + struct element_s * next; + type t; + } element_t; + typedef struct block_s { + element_t elements[blockSize]; + struct block_s * next; + } block_t; + + block_t * blocks; + element_t * free; + int total; + int active; +}; + +template +idBlockAlloc::idBlockAlloc( void ) { + blocks = NULL; + free = NULL; + total = active = 0; +} + +template +idBlockAlloc::~idBlockAlloc( void ) { + Shutdown(); +} + +template +type *idBlockAlloc::Alloc( void ) { + if ( !free ) { + MEM_SCOPED_TAG(tag, memoryTag); + block_t *block = new block_t; + block->next = blocks; + blocks = block; + for ( int i = 0; i < blockSize; i++ ) { + block->elements[i].next = free; + free = &block->elements[i]; + } + total += blockSize; + } + active++; + element_t *element = free; + free = free->next; + element->next = NULL; + return &element->t; +} + +template +void idBlockAlloc::Free( type *t ) { + element_t *element = (element_t *)( ( (unsigned char *) t ) - ( (int) &((element_t *)0)->t ) ); + element->next = free; + free = element; + active--; +} + +template +void idBlockAlloc::Shutdown( void ) { + while( blocks ) { + block_t *block = blocks; + blocks = blocks->next; + delete block; + } + blocks = NULL; + free = NULL; + total = active = 0; +} +// RAVEN END + +/* +============================================================================== + + Dynamic allocator, simple wrapper for normal allocations which can + be interchanged with idDynamicBlockAlloc. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +template +class idDynamicAlloc { +public: + idDynamicAlloc( void ); + ~idDynamicAlloc( void ); + + void Init( void ); + void Shutdown( void ); + void SetFixedBlocks( int numBlocks ) {} + void SetLockMemory( bool lock ) {} + void FreeEmptyBaseBlocks( void ) {} + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks( void ) const { return 0; } + int GetBaseBlockMemory( void ) const { return 0; } + int GetNumUsedBlocks( void ) const { return numUsedBlocks; } + int GetUsedBlockMemory( void ) const { return usedBlockMemory; } + int GetNumFreeBlocks( void ) const { return 0; } + int GetFreeBlockMemory( void ) const { return 0; } + int GetNumEmptyBaseBlocks( void ) const { return 0; } + +private: + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + + int numAllocs; + int numResizes; + int numFrees; + + void Clear( void ); +}; + +template +idDynamicAlloc::idDynamicAlloc( void ) { + Clear(); +} + +template +idDynamicAlloc::~idDynamicAlloc( void ) { + Shutdown(); +} + +template +void idDynamicAlloc::Init( void ) { +} + +template +void idDynamicAlloc::Shutdown( void ) { + Clear(); +} + +template +type *idDynamicAlloc::Alloc( const int num ) { + numAllocs++; + if ( num <= 0 ) { + return NULL; + } + numUsedBlocks++; + usedBlockMemory += num * sizeof( type ); +// RAVEN BEGIN +// jscott: to make it build +// mwhitlock: to make it build on Xenon + return (type *) ( (byte *) Mem_Alloc16( num * sizeof( type ), memoryTag ) ); +// RAVEN BEGIN +} + +#include "math/Simd.h" + +template +type *idDynamicAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + +// RAVEN BEGIN +// jnewquist: provide a real implementation of resize + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + type *newptr = Alloc( num ); + + if ( ptr != NULL ) { + const int oldSize = Mem_Size(ptr); + const int newSize = num*sizeof(type); + SIMDProcessor->Memcpy( newptr, ptr, (newSize +void idDynamicAlloc::Free( type *ptr ) { + numFrees++; + if ( ptr == NULL ) { + return; + } + Mem_Free16( ptr ); +} + +template +const char *idDynamicAlloc::CheckMemory( const type *ptr ) const { + return NULL; +} + +template +void idDynamicAlloc::Clear( void ) { + numUsedBlocks = 0; + usedBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; +} + + +/* +============================================================================== + + Fast dynamic block allocator. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +#include "containers/BTree.h" + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +//#ifdef _DEBUG +//#define DYNAMIC_BLOCK_ALLOC_CHECK +#define DYNAMIC_BLOCK_ALLOC_FASTCHECK +#define DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL +//#endif +// RAVEN END + +template +class idDynamicBlock { +public: + type * GetMemory( void ) const { return (type *)( ( (byte *) this ) + sizeof( idDynamicBlock ) ); } + int GetSize( void ) const { return abs( size ); } + void SetSize( int s, bool isBaseBlock ) { size = isBaseBlock ? -s : s; } + bool IsBaseBlock( void ) const { return ( size < 0 ); } + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + int identifier[3]; + void * allocator; +#endif + + int size; // size in bytes of the block + idDynamicBlock * prev; // previous memory block + idDynamicBlock * next; // next memory block + idBTreeNode,int> *node; // node in the B-Tree with free blocks +}; + +template +class idDynamicBlockAlloc { +public: + idDynamicBlockAlloc( void ); + ~idDynamicBlockAlloc( void ); + + void Init( void ); + void Shutdown( void ); + void SetFixedBlocks( int numBlocks ); + void SetLockMemory( bool lock ); + void FreeEmptyBaseBlocks( void ); + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks( void ) const { return numBaseBlocks; } + int GetBaseBlockMemory( void ) const { return baseBlockMemory; } + int GetNumUsedBlocks( void ) const { return numUsedBlocks; } + int GetUsedBlockMemory( void ) const { return usedBlockMemory; } + int GetNumFreeBlocks( void ) const { return numFreeBlocks; } + int GetFreeBlockMemory( void ) const { return freeBlockMemory; } + int GetNumEmptyBaseBlocks( void ) const; + +private: + idDynamicBlock * firstBlock; // first block in list in order of increasing address + idDynamicBlock * lastBlock; // last block in list in order of increasing address + idBTree,int,4>freeTree; // B-Tree with free memory blocks + bool allowAllocs; // allow base block allocations + bool lockMemory; // lock memory so it cannot get swapped out + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + int blockId[3]; +#endif + + int numBaseBlocks; // number of base blocks + int baseBlockMemory; // total memory in base blocks + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + int numFreeBlocks; // number of free blocks + int freeBlockMemory; // total memory in free blocks + + int numAllocs; + int numResizes; + int numFrees; + + void Clear( void ); + idDynamicBlock * AllocInternal( const int num ); + idDynamicBlock * ResizeInternal( idDynamicBlock *block, const int num ); + void FreeInternal( idDynamicBlock *block ); + void LinkFreeInternal( idDynamicBlock *block ); + void UnlinkFreeInternal( idDynamicBlock *block ); + void CheckMemory( void ) const; +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc + const char * CheckMemory( const idDynamicBlock *block ) const; +// RAVEN END +}; + +template +idDynamicBlockAlloc::idDynamicBlockAlloc( void ) { + Clear(); +} + +template +idDynamicBlockAlloc::~idDynamicBlockAlloc( void ) { + Shutdown(); +} + +template +void idDynamicBlockAlloc::Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,memoryTag); +// RAVEN END + freeTree.Init(); +} + +template +void idDynamicBlockAlloc::Shutdown( void ) { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->node == NULL ) { + FreeInternal( block ); + } + } + + for ( block = firstBlock; block != NULL; block = firstBlock ) { + firstBlock = block->next; + assert( block->IsBaseBlock() ); + if ( lockMemory ) { + idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + Mem_Free16( block ); + } + + freeTree.Shutdown(); + + Clear(); +} + +template +void idDynamicBlockAlloc::SetFixedBlocks( int numBlocks ) { + int i; + idDynamicBlock *block; + + for ( i = numBaseBlocks; i < numBlocks; i++ ) { +//RAVEN BEGIN +//amccarthy: Added allocation tag + block = ( idDynamicBlock * ) Mem_Alloc16( baseBlockSize, memoryTag ); +//RAVEN END + if ( lockMemory ) { + idLib::sys->LockMemory( block, baseBlockSize ); + } +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( block->identifier, blockId, sizeof( block->identifier ) ); + block->allocator = (void*)this; +#endif + block->SetSize( baseBlockSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + FreeInternal( block ); + + numBaseBlocks++; + baseBlockMemory += baseBlockSize; + } + + allowAllocs = false; +} + +template +void idDynamicBlockAlloc::SetLockMemory( bool lock ) { + lockMemory = lock; +} + +template +void idDynamicBlockAlloc::FreeEmptyBaseBlocks( void ) { + idDynamicBlock *block, *next; + + for ( block = firstBlock; block != NULL; block = next ) { + next = block->next; + + if ( block->IsBaseBlock() && block->node != NULL && ( next == NULL || next->IsBaseBlock() ) ) { + UnlinkFreeInternal( block ); + if ( block->prev ) { + block->prev->next = block->next; + } else { + firstBlock = block->next; + } + if ( block->next ) { + block->next->prev = block->prev; + } else { + lastBlock = block->prev; + } + if ( lockMemory ) { + idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + numBaseBlocks--; + baseBlockMemory -= block->GetSize() + (int)sizeof( idDynamicBlock ); + Mem_Free16( block ); + } + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +template +int idDynamicBlockAlloc::GetNumEmptyBaseBlocks( void ) const { + int numEmptyBaseBlocks; + idDynamicBlock *block; + + numEmptyBaseBlocks = 0; + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->IsBaseBlock() && block->node != NULL && ( block->next == NULL || block->next->IsBaseBlock() ) ) { + numEmptyBaseBlocks++; + } + } + return numEmptyBaseBlocks; +} + +template +type *idDynamicBlockAlloc::Alloc( const int num ) { + idDynamicBlock *block; + + numAllocs++; + + if ( num <= 0 ) { + return NULL; + } + + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + numUsedBlocks++; + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +type *idDynamicBlockAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + + if ( ptr == NULL ) { + return Alloc( num ); + } + + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + usedBlockMemory -= block->GetSize(); + + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +void idDynamicBlockAlloc::Free( type *ptr ) { + + numFrees++; + + if ( ptr == NULL ) { + return; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + numUsedBlocks--; + usedBlockMemory -= block->GetSize(); + + FreeInternal( block ); + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +template +const char *idDynamicBlockAlloc::CheckMemory( const idDynamicBlock *block ) const { + if ( block->node != NULL ) { + return "memory has been freed"; + } + +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + if ( block->identifier[0] != 0x11111111 || block->identifier[1] != 0x22222222 || block->identifier[2] != 0x33333333 ) { + return "memory has invalid identifier"; + } +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifndef RV_UNIFIED_ALLOCATOR + if ( block->allocator != (void*)this ) { + return "memory was allocated with different allocator"; + } +#endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#endif + + /* base blocks can be larger than baseBlockSize which can cause this code to fail + idDynamicBlock *base; + for ( base = firstBlock; base != NULL; base = base->next ) { + if ( base->IsBaseBlock() ) { + if ( ((int)block) >= ((int)base) && ((int)block) < ((int)base) + baseBlockSize ) { + break; + } + } + } + if ( base == NULL ) { + return "no base block found for memory"; + } + */ + + return NULL; +} + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +template +const char *idDynamicBlockAlloc::CheckMemory( const type *ptr ) const { + idDynamicBlock *block; + + if ( ptr == NULL ) { + return NULL; + } + + block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + return CheckMemory( block ); +} +// RAVEN END + +template +void idDynamicBlockAlloc::Clear( void ) { + firstBlock = lastBlock = NULL; + allowAllocs = true; + lockMemory = false; + numBaseBlocks = 0; + baseBlockMemory = 0; + numUsedBlocks = 0; + usedBlockMemory = 0; + numFreeBlocks = 0; + freeBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + blockId[0] = 0x11111111; + blockId[1] = 0x22222222; + blockId[2] = 0x33333333; +#endif +} + +template +idDynamicBlock *idDynamicBlockAlloc::AllocInternal( const int num ) { + idDynamicBlock *block; + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + + block = freeTree.FindSmallestLargerEqual( alignedBytes ); + if ( block != NULL ) { + UnlinkFreeInternal( block ); + } else if ( allowAllocs ) { + int allocSize = Max( baseBlockSize, alignedBytes + (int)sizeof( idDynamicBlock ) ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + block = ( idDynamicBlock * ) Mem_Alloc16( allocSize, memoryTag ); +//RAVEN END + if ( lockMemory ) { + idLib::sys->LockMemory( block, baseBlockSize ); + } +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( block->identifier, blockId, sizeof( block->identifier ) ); + block->allocator = (void*)this; +#endif + block->SetSize( allocSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + numBaseBlocks++; + baseBlockMemory += allocSize; + } + + return block; +} + +template +idDynamicBlock *idDynamicBlockAlloc::ResizeInternal( idDynamicBlock *block, const int num ) { + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333); // && block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif +// RAVEN END +#endif + + // if the new size is larger + if ( alignedBytes > block->GetSize() ) { + + idDynamicBlock *nextBlock = block->next; + + // try to annexate the next block if it's free + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL && + block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize() >= alignedBytes ) { + + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } else { + // allocate a new block and copy + idDynamicBlock *oldBlock = block; + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + memcpy( block->GetMemory(), oldBlock->GetMemory(), oldBlock->GetSize() ); + FreeInternal( oldBlock ); + } + } + + // if the unused space at the end of this block is large enough to hold a block with at least one element + if ( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ) < Max( minBlockSize, (int)sizeof( type ) ) ) { + return block; + } + + idDynamicBlock *newBlock; + + newBlock = ( idDynamicBlock * ) ( ( (byte *) block ) + (int)sizeof( idDynamicBlock ) + alignedBytes ); +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( newBlock->identifier, blockId, sizeof( newBlock->identifier ) ); + newBlock->allocator = (void*)this; +#endif + newBlock->SetSize( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ), false ); + newBlock->next = block->next; + newBlock->prev = block; + if ( newBlock->next ) { + newBlock->next->prev = newBlock; + } else { + lastBlock = newBlock; + } + newBlock->node = NULL; + block->next = newBlock; + block->SetSize( alignedBytes, block->IsBaseBlock() ); + + FreeInternal( newBlock ); + + return block; +} + +template +void idDynamicBlockAlloc::FreeInternal( idDynamicBlock *block ) { + + assert( block->node == NULL ); + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 );//&& block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#endif + + // try to merge with a next free block + idDynamicBlock *nextBlock = block->next; + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL ) { + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } + + // try to merge with a previous free block + idDynamicBlock *prevBlock = block->prev; + if ( prevBlock && !block->IsBaseBlock() && prevBlock->node != NULL ) { + UnlinkFreeInternal( prevBlock ); + prevBlock->SetSize( prevBlock->GetSize() + (int)sizeof( idDynamicBlock ) + block->GetSize(), prevBlock->IsBaseBlock() ); + prevBlock->next = block->next; + if ( block->next ) { + block->next->prev = prevBlock; + } else { + lastBlock = prevBlock; + } + LinkFreeInternal( prevBlock ); + } else { + LinkFreeInternal( block ); + } +} + +template +ID_INLINE void idDynamicBlockAlloc::LinkFreeInternal( idDynamicBlock *block ) { + block->node = freeTree.Add( block, block->GetSize() ); + numFreeBlocks++; + freeBlockMemory += block->GetSize(); +} + +template +ID_INLINE void idDynamicBlockAlloc::UnlinkFreeInternal( idDynamicBlock *block ) { + freeTree.Remove( block->node ); + block->node = NULL; + numFreeBlocks--; + freeBlockMemory -= block->GetSize(); +} + +template +void idDynamicBlockAlloc::CheckMemory( void ) const { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333); // && block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif +// RAVEN END +#endif + + // make sure the block is properly linked + if ( block->prev == NULL ) { + assert( firstBlock == block ); + } else { + assert( block->prev->next == block ); + } + if ( block->next == NULL ) { + assert( lastBlock == block ); + } else { + assert( block->next->prev == block ); + } + } +} + +#endif /* !__HEAP_H__ */ diff --git a/source/idlib/LangDict.cpp b/source/idlib/LangDict.cpp new file mode 100644 index 0000000..1e3347d --- /dev/null +++ b/source/idlib/LangDict.cpp @@ -0,0 +1,290 @@ + +#include "precompiled.h" +#pragma hdrstop + + +/* +============ +idLangDict::idLangDict +============ +*/ +idLangDict::idLangDict( void ) { + args.SetGranularity( 256 ); + hash.SetGranularity( 256 ); + hash.Clear( 4096, 8192 ); + baseID = 0; +} + +/* +============ +idLangDict::~idLangDict +============ +*/ +idLangDict::~idLangDict( void ) { + Clear(); +} + +/* +============ +idLangDict::Clear +============ +*/ +void idLangDict::Clear( void ) { + args.Clear(); + hash.Clear(); +} + +/* +============ +idLangDict::Load +============ +*/ +bool idLangDict::Load( const char *fileName, bool clear ) { + if ( clear ) { + Clear(); + } + + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + int len = idLib::fileSystem->ReadFile( fileName, (void**)&buffer ); + if ( len <= 0 ) { + // let whoever called us deal with the failure (so sys_lang can be reset) + return false; + } + src.LoadMemory( buffer, strlen( buffer ), fileName ); + if ( !src.IsLoaded() ) { + return false; + } + + idToken tok, tok2; + src.ExpectTokenString( "{" ); + while ( src.ReadToken( &tok ) ) { + if ( tok == "}" ) { + break; + } + + if ( src.ReadToken( &tok2 ) ) { + if ( tok2 == "}" ) { + break; + } + idLangKeyValue kv; + kv.key = tok; + kv.value = tok2; +// RAVEN BEGIN + if( kv.key.CmpPrefix( STRTABLE_ID ) ) { + common->Warning( "Invalid token id \'%s\' in \'%s\' line %d", kv.key.c_str(), fileName, src.GetLineNum() ); + } else { + hash.Add( GetHashKey( kv.key ), args.Append( kv ) ); + } +// RAVEN END + } + } + idLib::common->Printf( "%i strings read from %s\n", args.Num(), fileName ); + idLib::fileSystem->FreeFile( (void*)buffer ); + + return true; +} + +/* +============ +idLangDict::Save +============ +*/ +void idLangDict::Save( const char *fileName ) { + idFile *outFile = idLib::fileSystem->OpenFileWrite( fileName ); +// RAVEN BEGIN + if( !outFile ) { + common->Printf( "Could not open file \'%s\'for writing\n", fileName ); + return; + } +// RAVEN END + outFile->WriteFloatString( "// string table" NEWLINE "// english" NEWLINE "//" NEWLINE NEWLINE "{" NEWLINE ); + for ( int j = 0; j < args.Num(); j++ ) { + outFile->WriteFloatString( "\t\"%s\"\t\"", args[j].key.c_str() ); + int l = args[j].value.Length(); + char slash = '\\'; + char tab = 't'; + char nl = 'n'; + for ( int k = 0; k < l; k++ ) { + char ch = args[j].value[k]; + if ( ch == '\t' ) { + outFile->Write( &slash, 1 ); + outFile->Write( &tab, 1 ); + } else if ( ch == '\n' || ch == '\r' ) { + outFile->Write( &slash, 1 ); + outFile->Write( &nl, 1 ); + } else { + outFile->Write( &ch, 1 ); + } + } + outFile->WriteFloatString( "\"" NEWLINE ); + } + outFile->WriteFloatString( NEWLINE "}" NEWLINE ); + idLib::fileSystem->CloseFile( outFile ); +} + +/* +============ +idLangDict::GetString +============ +*/ +const char *idLangDict::GetString( const char *str ) const { + + if ( str == NULL || str[0] == '\0' ) { + return ""; + } + + if ( idStr::Cmpn( str, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + return str; + } + + int hashKey = GetHashKey( str ); + for ( int i = hash.First( hashKey ); i != -1; i = hash.Next( i ) ) { + if ( args[i].key.Cmp( str ) == 0 ) { + return args[i].value; + } + } + + idLib::common->Warning( "Unknown string id %s", str ); + return str; +} + +/* +============ +idLangDict::AddString +============ +*/ +const char *idLangDict::AddString( const char *str ) { + + if ( ExcludeString( str ) ) { + return str; + } + + int c = args.Num(); + for ( int j = 0; j < c; j++ ) { + if ( idStr::Cmp( args[j].value, str ) == 0 ){ + return args[j].key; + } + } + + int id = GetNextId(); + idLangKeyValue kv; + kv.key = va( "#str_%06i", id ); + kv.value = str; + c = args.Append( kv ); + assert( kv.key.CmpPrefix( STRTABLE_ID ) == 0 ); + hash.Add( GetHashKey( kv.key ), c ); + return args[c].key; +} + +/* +============ +idLangDict::GetNumKeyVals +============ +*/ +int idLangDict::GetNumKeyVals( void ) const { + return args.Num(); +} + +/* +============ +idLangDict::GetKeyVal +============ +*/ +const idLangKeyValue * idLangDict::GetKeyVal( int i ) const { + return &args[i]; +} + +/* +============ +idLangDict::AddKeyVal +============ +*/ +void idLangDict::AddKeyVal( const char *key, const char *val ) { + idLangKeyValue kv; + kv.key = key; + kv.value = val; + assert( kv.key.CmpPrefix( STRTABLE_ID ) == 0 ); + hash.Add( GetHashKey( kv.key ), args.Append( kv ) ); +} + +/* +============ +idLangDict::ExcludeString +============ +*/ +bool idLangDict::ExcludeString( const char *str ) const { + if ( str == NULL ) { + return true; + } + + int c = strlen( str ); + if ( c <= 1 ) { + return true; + } + + if ( idStr::Cmpn( str, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + return true; + } + + if ( idStr::Icmpn( str, "gui::", strlen( "gui::" ) ) == 0 ) { + return true; + } + + if ( str[0] == '$' ) { + return true; + } + + int i; + for ( i = 0; i < c; i++ ) { + if ( isalpha( str[i] ) ) { + break; + } + } + if ( i == c ) { + return true; + } + + return false; +} + +/* +============ +idLangDict::GetNextId +============ +*/ +int idLangDict::GetNextId( void ) const { + int c = args.Num(); + //Let and external user supply the base id for this dictionary + int id = baseID; + + if ( c == 0 ) { + return id; + } + + idStr work; + for ( int j = 0; j < c; j++ ) { + work = args[j].key; + work.StripLeading( STRTABLE_ID ); + int test = atoi( work ); + if ( test > id ) { + id = test; + } + } + return id + 1; +} + +/* +============ +idLangDict::GetHashKey +============ +*/ +int idLangDict::GetHashKey( const char *str ) const { + int hashKey = 0; + for ( str += STRTABLE_ID_LENGTH; str[0] != '\0'; str++ ) { + assert( str[0] >= '0' && str[0] <= '9' ); + hashKey = hashKey * 10 + str[0] - '0'; + } + return hashKey; +} diff --git a/source/idlib/LangDict.h b/source/idlib/LangDict.h new file mode 100644 index 0000000..1f224a4 --- /dev/null +++ b/source/idlib/LangDict.h @@ -0,0 +1,50 @@ + +#ifndef __LANGDICT_H__ +#define __LANGDICT_H__ + +/* +=============================================================================== + + Simple dictionary specifically for the localized string tables. + +=============================================================================== +*/ + +class idLangKeyValue { +public: + idStr key; + idStr value; +}; + +class idLangDict { +public: + idLangDict( void ); + ~idLangDict( void ); + + void Clear( void ); + bool Load( const char *fileName, bool clear = true ); + void Save( const char *fileName ); + + const char * AddString( const char *str ); + const char * GetString( const char *str ) const; + + // adds the value and key as passed (doesn't generate a "#str_xxxxxx" key or ensure the key/value pair is unique) + void AddKeyVal( const char *key, const char *val ); + + int GetNumKeyVals( void ) const; + const idLangKeyValue * GetKeyVal( int i ) const; + + void SetBaseID(int id) { baseID = id; }; + +private: + idList args; + idHashIndex hash; + + bool ExcludeString( const char *str ) const; + int GetNextId( void ) const; + int GetHashKey( const char *str ) const; + + int baseID; +}; + +#endif /* !__LANGDICT_H__ */ diff --git a/source/idlib/Lexer.cpp b/source/idlib/Lexer.cpp new file mode 100644 index 0000000..f4c19b6 --- /dev/null +++ b/source/idlib/Lexer.cpp @@ -0,0 +1,3944 @@ + +#include "precompiled.h" +#pragma hdrstop + +#ifdef LEXER_READ_AHEAD + +namespace { + + #define IO_BUFFER_CHUNK_SIZE (1024 * 64) + + enum { + IOJobPending = 0, + IOJobQuit, + IOJobComplete, + IOJobCount + }; + static HANDLE mIOJobs[IOJobCount]; + + static DWORD mThreadId = 0; + static HANDLE mThreadHandle = 0; + + + struct IOJobData { + idFile *mFile; + int mAmtToRead; + byte mBuffer[IO_BUFFER_CHUNK_SIZE]; + }; + + IOJobData gIOJobData; + + void SetPendingJob(idFile *f, int size) { + gIOJobData.mFile = f; + gIOJobData.mAmtToRead = size; + + if ( mThreadHandle ) { + SetEvent( mIOJobs[IOJobPending] ); + } + } + + IOJobData *GetPendingJob() { + return &gIOJobData; + } + + void GetPendingJobResults(byte *dest, int &size) { + if ( mThreadHandle ) { + if ( WAIT_OBJECT_0 == WaitForSingleObject( mIOJobs[IOJobComplete], INFINITE ) ) { + //ResetEvent( mIOJobs[IOJobComplete] ); + size = gIOJobData.mAmtToRead; + memcpy(dest, gIOJobData.mBuffer, size ); + } else { + assert( 0 ); + } + } else { + gIOJobData.mFile->Read( dest, gIOJobData.mAmtToRead ); + size = gIOJobData.mAmtToRead; + } + } + + static LONG IOThread( void * ) { + while ( 1 ) { + DWORD res = WaitForMultipleObjects( 2, mIOJobs, FALSE, INFINITE ); + if ( res == WAIT_OBJECT_0 ) { + //ResetEvent( mIOJobs[IOJobPending] ); + IOJobData *j = GetPendingJob(); + j->mFile->Read( j->mBuffer, j->mAmtToRead ); + SetEvent( mIOJobs[IOJobComplete] ); + } else if ( res == (WAIT_OBJECT_0 + 1) ) { + ExitThread(0); + } else { + assert( 0 ); + } + } + ExitThread(0); + } + + void InitIO() { + for ( int i = 0; i < IOJobCount; ++i ) { + //mIOJobs[i] = CreateEvent( 0, TRUE, FALSE, 0 ); + mIOJobs[i] = CreateEvent( 0, FALSE, FALSE, 0 ); + } + mThreadHandle = CreateThread( NULL, 1024 * 512, ( LPTHREAD_START_ROUTINE )IOThread, 0, CREATE_SUSPENDED, &mThreadId ); + ResumeThread( mThreadHandle ); + } + + void ShutdownIO() { + + SetEvent( mIOJobs[IOJobQuit] ); + + DWORD ecode = 0; + while ( !GetExitCodeThread( mThreadHandle, &ecode ) ) { + Sleep( 1 ); + } + CloseHandle( mThreadHandle ); + + for ( int i = 0; i < IOJobCount; ++i ) { + CloseHandle( mIOJobs[i] ); + } + + mThreadHandle = 0; + } + +} + +namespace ChunkBuffer { + + idFile *mFile; + int mFileSize; + int mBytesRead; + + int mFileBytesRead; + + byte mChunkMem[IO_BUFFER_CHUNK_SIZE]; + int mCurrentChunkBytes; + int mCurrentChunkBytesRead = 0; + + /* + ================ + ChunkBuffer::Length + ================ + */ + int Length() { + return mFileSize; + } + + /* + ================ + ChunkBuffer::BytesRead + ================ + */ + int BytesRead() { + return mBytesRead; + } + + /* + ================ + ChunkBuffer::Begin + ================ + */ + void Begin( idFile *f ) { + mFile = f; + mFileSize = f->Length(); + mBytesRead = 0; + + int initialRead = (mFileSize < IO_BUFFER_CHUNK_SIZE) ? mFileSize : IO_BUFFER_CHUNK_SIZE; + mFile->Read( mChunkMem, initialRead ); + mFileBytesRead = initialRead; + mCurrentChunkBytesRead = 0; + mCurrentChunkBytes = initialRead; + + int bytesRemaining = mFileSize - mFileBytesRead; + if ( bytesRemaining > 0 ) { + SetPendingJob( mFile, (bytesRemaining > IO_BUFFER_CHUNK_SIZE) ? IO_BUFFER_CHUNK_SIZE : bytesRemaining ); + } + } + + /* + ================ + ChunkBuffer::End + ================ + */ + void End() { + } + + /* + ================ + ChunkBuffer::Advance + ================ + */ + int Advance( byte *chunkMem ) { + int readAmt = 0; + GetPendingJobResults( chunkMem, readAmt ); + mFileBytesRead+=readAmt; + + int bytesRemaining = mFileSize - mFileBytesRead; + if ( bytesRemaining > 0 ) { + SetPendingJob( mFile, (bytesRemaining > IO_BUFFER_CHUNK_SIZE) ? IO_BUFFER_CHUNK_SIZE : bytesRemaining ); + } + + return readAmt; + } + + /* + ================ + ChunkBuffer::Read + ================ + */ + int Read( byte *dest, int size ) { + int bytesRem = mFileSize - mBytesRead; + + if ( size > bytesRem ) { + size = bytesRem; + } + + if ( !size ) { + return 0; + } + + byte *writePos = dest; + int cnt = size; + + while ( cnt ) { + if ( mCurrentChunkBytesRead >= mCurrentChunkBytes ) { + mCurrentChunkBytes = Advance( mChunkMem ); + mCurrentChunkBytesRead = 0; + } + + int chunkBytesRemaining = mCurrentChunkBytes - mCurrentChunkBytesRead; + int toRead = (cnt > chunkBytesRemaining) ? chunkBytesRemaining : cnt; + + byte *readPos = &mChunkMem[mCurrentChunkBytesRead]; + + memcpy( writePos, readPos, toRead ); + + cnt-=toRead; + mCurrentChunkBytesRead+=toRead; + writePos+=toRead; + } + + mBytesRead+=size; + return size; + } +} + +/* +================ +LexerIOWrapper::LexerIOWrapper +================ +*/ +LexerIOWrapper::LexerIOWrapper() { + file = 0; + offset = 0; + size = 0; + memory = 0; +} + +/* +================ +LexerIOWrapper::InitFromMemory +================ +*/ +void LexerIOWrapper::InitFromMemory(char const * const ptr, int length) { + file = 0; + offset = 0; + size = length; + memory = (char *)ptr; +} + +/* +================ +LexerIOWrapper::OpenFile +================ +*/ +bool LexerIOWrapper::OpenFile(char const *filename, bool OSPath) { + if ( OSPath ) { + file = idLib::fileSystem->OpenExplicitFileRead( filename ); + } else { + file = idLib::fileSystem->OpenFileRead( filename ); + } + + if ( !file ) { + return false; + } + + ChunkBuffer::Begin( file ); + + return true; +} + +/* +================ +LexerIOWrapper::Length +================ +*/ +int LexerIOWrapper::Length() { + if ( file ) { + return ChunkBuffer::Length(); + } + return size; +} + +/* +================ +LexerIOWrapper::Tell +================ +*/ +int LexerIOWrapper::Tell() { + if ( file ) { + return ChunkBuffer::BytesRead(); + } + return offset; +} + +/* +================ +LexerIOWrapper::Seek +================ +*/ +void LexerIOWrapper::Seek( int loc, int mode ) { + if ( file ) { + if ( loc != 0 || mode != FS_SEEK_SET ) { + common->Error( "lol\n" ); + } + file->Seek( 0, FS_SEEK_SET ); + ChunkBuffer::Begin( file ); + return; + } + if ( mode == FS_SEEK_SET ) { + offset = loc; + } else { + common->Error( "lol" ); + } +} + +/* +================ +LexerIOWrapper::Read +================ +*/ +void LexerIOWrapper::Read( void *dest, int s ) { + if ( file ) { + ChunkBuffer::Read( (byte*)dest, s ); + return; + } + char *d = (char *)dest; + char *dEnd = d + (((s + offset) > size) ? (size - offset) : (s)); + while ( d < dEnd ) { + *d = memory[offset]; + ++d; + ++offset; + } +} + +/* +================ +LexerIOWrapper::Close +================ +*/ +void LexerIOWrapper::Close() { + if ( file ) { + ChunkBuffer::End(); + idLib::fileSystem->CloseFile(file); + return; + } +} + +/* +================ +LexerIOWrapper::IsLoaded +================ +*/ +int LexerIOWrapper::IsLoaded() { + if ( file || memory ) { + return 1; + } + return 0; +} + +/* +================ +Lexer::BeginLevelLoad +================ +*/ +void Lexer::BeginLevelLoad() { + InitIO(); +} + +/* +================ +Lexer::EndLevelLoad +================ +*/ +void Lexer::EndLevelLoad() { + ShutdownIO(); +} + +/* +================ +ReadValue +================ +*/ +template inline type ReadValue(LexerIOWrapper *in) +{ + type ret; + + in->Read(&ret, sizeof(type)); + return ret; +} + +/* +================ +ReadValue +specialization read for idStr's +================ +*/ +template <> inline idStr ReadValue(LexerIOWrapper *in) +{ + char c; + idStr str; + + in->Read(&c, 1); + while(c != '\0') + { + str.Append(c); + in->Read(&c, 1); + } + + str.Append(c); + + return str; +} + +#endif + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = { + //binary operators + {">>=",P_RSHIFT_ASSIGN}, + {"<<=",P_LSHIFT_ASSIGN}, + // + {"...",P_PARMS}, + //define merge operator + {"##",P_PRECOMPMERGE}, // pre-compiler + //logic operators + {"&&",P_LOGIC_AND}, // pre-compiler + {"||",P_LOGIC_OR}, // pre-compiler + {">=",P_LOGIC_GEQ}, // pre-compiler + {"<=",P_LOGIC_LEQ}, // pre-compiler + {"==",P_LOGIC_EQ}, // pre-compiler + {"!=",P_LOGIC_UNEQ}, // pre-compiler + //arithmatic operators + {"*=",P_MUL_ASSIGN}, + {"/=",P_DIV_ASSIGN}, + {"%=",P_MOD_ASSIGN}, + {"+=",P_ADD_ASSIGN}, + {"-=",P_SUB_ASSIGN}, + {"++",P_INC}, + {"--",P_DEC}, + //binary operators + {"&=",P_BIN_AND_ASSIGN}, + {"|=",P_BIN_OR_ASSIGN}, + {"^=",P_BIN_XOR_ASSIGN}, + {">>",P_RSHIFT}, // pre-compiler + {"<<",P_LSHIFT}, // pre-compiler + //reference operators + {"->",P_POINTERREF}, + //C++ + {"::",P_CPP1}, + {".*",P_CPP2}, + //arithmatic operators + {"*",P_MUL}, // pre-compiler + {"/",P_DIV}, // pre-compiler + {"%",P_MOD}, // pre-compiler + {"+",P_ADD}, // pre-compiler + {"-",P_SUB}, // pre-compiler + {"=",P_ASSIGN}, + //binary operators + {"&",P_BIN_AND}, // pre-compiler + {"|",P_BIN_OR}, // pre-compiler + {"^",P_BIN_XOR}, // pre-compiler + {"~",P_BIN_NOT}, // pre-compiler + //logic operators + {"!",P_LOGIC_NOT}, // pre-compiler + {">",P_LOGIC_GREATER}, // pre-compiler + {"<",P_LOGIC_LESS}, // pre-compiler + //reference operator + {".",P_REF}, + //seperators + {",",P_COMMA}, // pre-compiler + {";",P_SEMICOLON}, + //label indication + {":",P_COLON}, // pre-compiler + //if statement + {"?",P_QUESTIONMARK}, // pre-compiler + //embracements + {"(",P_PARENTHESESOPEN}, // pre-compiler + {")",P_PARENTHESESCLOSE}, // pre-compiler + {"{",P_BRACEOPEN}, // pre-compiler + {"}",P_BRACECLOSE}, // pre-compiler + {"[",P_SQBRACKETOPEN}, + {"]",P_SQBRACKETCLOSE}, + // + {"\\",P_BACKSLASH}, + //precompiler operator + {"#",P_PRECOMP}, // pre-compiler + {"$",P_DOLLAR}, +// RAVEN BEGIN + {"ˇ",P_INVERTED_PLING}, + {"ż",P_INVERTED_QUERY}, +// RAVEN END + {NULL, 0} +}; + +int default_punctuationtable[256]; +int default_nextpunctuation[sizeof(default_punctuations) / sizeof(punctuation_t)]; +int default_setup; + +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +char Lexer::baseFolder[ 256 ]; + +// Added this to allow easy changing of the suffix that signifies a binary file +idStr const Lexer::sCompiledFileSuffix("c"); +// RAVEN END + +/* +================ +idLexer::CreatePunctuationTable +================ +*/ +void idLexer::CreatePunctuationTable( const punctuation_t *punctuations ) { + int i, n, lastp; + const punctuation_t *p, *newp; + + //get memory for the table + if ( punctuations == default_punctuations ) { + idLexer::punctuationtable = default_punctuationtable; + idLexer::nextpunctuation = default_nextpunctuation; + if ( default_setup ) { + return; + } + default_setup = true; + i = sizeof(default_punctuations) / sizeof(punctuation_t); + } + else { + if ( !idLexer::punctuationtable || idLexer::punctuationtable == default_punctuationtable ) { +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idLexer::punctuationtable = (int *) Mem_Alloc(256 * sizeof(int),MA_LEXER); +//RAVEN END + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( idLexer::nextpunctuation ); + } + for (i = 0; punctuations[i].p; i++) { + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idLexer::nextpunctuation = (int *) Mem_Alloc(i * sizeof(int),MA_LEXER); +//RAVEN END + } + memset(idLexer::punctuationtable, 0xFF, 256 * sizeof(int)); + memset(idLexer::nextpunctuation, 0xFF, i * sizeof(int)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) { + newp = &punctuations[i]; + lastp = -1; + //sort the punctuations in this table entry on length (longer punctuations first) + for (n = idLexer::punctuationtable[(unsigned int) newp->p[0]]; n >= 0; n = idLexer::nextpunctuation[n] ) { + p = &punctuations[n]; + if (strlen(p->p) < strlen(newp->p)) { + idLexer::nextpunctuation[i] = n; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + break; + } + lastp = n; + } + if (n < 0) { + idLexer::nextpunctuation[i] = -1; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + } + } +} + +/* +================ +idLexer::GetPunctuationFromId +================ +*/ +const char *idLexer::GetPunctuationFromId( int id ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( idLexer::punctuations[i].n == id ) { + return idLexer::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idLexer::GetPunctuationId +================ +*/ +int idLexer::GetPunctuationId( const char *p ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( !idStr::Cmp(idLexer::punctuations[i].p, p) ) { + return idLexer::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idLexer::Error +================ +*/ + +void idLexer::Error( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + hadError = true; + + if ( idLexer::flags & LEXFL_NOERRORS ) { + return; + } + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + + if ( idLexer::flags & LEXFL_NOFATALERRORS ) { + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } else { + idLib::common->Error( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } +} + +/* +================ +idLexer::Warning +================ +*/ +void idLexer::Warning( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + if ( idLexer::flags & LEXFL_NOWARNINGS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); +} + +/* +================ +idLexer::SetPunctuations +================ +*/ +void idLexer::SetPunctuations( const punctuation_t *p ) { +#ifdef PUNCTABLE + if (p) { + idLexer::CreatePunctuationTable( p ); + } + else { + idLexer::CreatePunctuationTable( default_punctuations ); + } +#endif //PUNCTABLE + if (p) { + idLexer::punctuations = p; + } + else { + idLexer::punctuations = default_punctuations; + } +} + +/* +================ +idLexer::ReadWhiteSpace + +Reads spaces, tabs, C-like comments etc. +When a newline character is found the scripts line counter is increased. +================ +*/ +int idLexer::ReadWhiteSpace( void ) { + while(1) { + // skip white space +// RAVEN BEGIN + while(( byte )*idLexer::script_p <= ' ') { +// RAVEN END + if (!*idLexer::script_p) { + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::line++; + } + idLexer::script_p++; + } + // skip comments + if (*idLexer::script_p == '/') { + // comments // + if (*(idLexer::script_p+1) == '/') { + idLexer::script_p++; + do { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + } + while( *idLexer::script_p != '\n' ); + idLexer::line++; + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + // comments /* */ + else if (*(idLexer::script_p+1) == '*') { + idLexer::script_p++; + while( 1 ) { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + if ( *idLexer::script_p == '\n' ) { + idLexer::line++; + } + else if ( *idLexer::script_p == '/' ) { + if ( *(idLexer::script_p-1) == '*' ) { + break; + } + if ( *(idLexer::script_p+1) == '*' ) { + idLexer::Warning( "nested comment" ); + } + } + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + } + break; + } + return 1; +} + +/* +================ +idLexer::ReadEscapeCharacter +================ +*/ +int idLexer::ReadEscapeCharacter( char *ch ) { + int c, val, i; + + // step over the leading '\\' + idLexer::script_p++; + // determine the escape character + switch(*idLexer::script_p) { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + idLexer::script_p++; + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'Z') + c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + c = c - 'a' + 10; + else + break; + val = (val << 4) + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + default: //NOTE: decimal ASCII code, NOT octal + { + if (*idLexer::script_p < '0' || *idLexer::script_p > '9') { + idLexer::Error("unknown escape char"); + } + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else + break; + val = val * 10 + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + } + // step over the escape character or the last digit of the number + idLexer::script_p++; + // store the escape character + *ch = c; + // succesfully read escape character + return 1; +} + +/* +================ +idLexer::ReadString + +Escape characters are interpretted. +Reads two strings with only a white space between them as one string. +================ +*/ +int idLexer::ReadString( idToken *token, int quote ) { + int tmpline; + const char *tmpscript_p; + char ch; + + if ( quote == '\"' ) { + token->type = TT_STRING; + } else { + token->type = TT_LITERAL; + } + + // leading quote + idLexer::script_p++; + + while(1) { + // if there is an escape character and escape characters are allowed + if (*idLexer::script_p == '\\' && !(idLexer::flags & LEXFL_NOSTRINGESCAPECHARS)) { + if ( !idLexer::ReadEscapeCharacter( &ch ) ) { + return 0; + } + token->AppendDirty( ch ); + } + // if a trailing quote + else if (*idLexer::script_p == quote) { + // step over the quote + idLexer::script_p++; + // if consecutive strings should not be concatenated + if ( (idLexer::flags & LEXFL_NOSTRINGCONCAT) && + (!(idLexer::flags & LEXFL_ALLOWBACKSLASHSTRINGCONCAT) || (quote != '\"')) ) { + break; + } + + tmpscript_p = idLexer::script_p; + tmpline = idLexer::line; + // read white space between possible two consecutive strings + if ( !idLexer::ReadWhiteSpace() ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + + if ( idLexer::flags & LEXFL_NOSTRINGCONCAT ) { + if ( *idLexer::script_p != '\\' ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the '\\' + idLexer::script_p++; + if ( !idLexer::ReadWhiteSpace() || ( *idLexer::script_p != quote ) ) { + idLexer::Error( "expecting string after '\' terminated line" ); + return 0; + } + } + + // if there's no leading qoute + if ( *idLexer::script_p != quote ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the new leading quote + idLexer::script_p++; + } + else { + if (*idLexer::script_p == '\0') { + idLexer::Error( "missing trailing quote" ); + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::Error( "newline inside string" ); + return 0; + } + token->AppendDirty( *idLexer::script_p++ ); + } + } + token->data[token->len] = '\0'; + + if ( token->type == TT_LITERAL ) { + if ( !(idLexer::flags & LEXFL_ALLOWMULTICHARLITERALS) ) { + if ( token->Length() != 1 ) { + idLexer::Warning( "literal is not one character long" ); + } + } + token->subtype = (*token)[0]; + } + else { + // the sub type is the length of the string + token->subtype = token->Length(); + } + return 1; +} + +/* +================ +idLexer::ReadName +================ +*/ +int idLexer::ReadName( idToken *token ) { + char c; + + token->type = TT_NAME; + do { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; +// RAVEN BEGIN + } while ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' || +// RAVEN EBD + // if treating all tokens as strings, don't parse '-' as a seperate token + ((idLexer::flags & LEXFL_ONLYSTRINGS) && (c == '-')) || + // if special path name characters are allowed + ((idLexer::flags & LEXFL_ALLOWPATHNAMES) && (c == '/' || c == '\\' || c == ':' || c == '.')) ); + token->data[token->len] = '\0'; + //the sub type is the length of the name + token->subtype = token->Length(); + return 1; +} + +/* +================ +idLexer::CheckString +================ +*/ +ID_INLINE int idLexer::CheckString( const char *str ) const { + int i; + + for ( i = 0; str[i]; i++ ) { + if ( idLexer::script_p[i] != str[i] ) { + return false; + } + } + return true; +} + +/* +================ +idLexer::ReadNumber +================ +*/ +int idLexer::ReadNumber( idToken *token ) { + int i; + int dot; + char c, c2; + + token->type = TT_NUMBER; + token->subtype = 0; + token->intvalue = 0; + token->floatvalue = 0; + + c = *idLexer::script_p; + c2 = *(idLexer::script_p + 1); + + if ( c == '0' && c2 != '.' ) { + // check for a hexadecimal number + if ( c2 == 'x' || c2 == 'X' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_HEX | TT_INTEGER; + } + // check for a binary number + else if ( c2 == 'b' || c2 == 'B' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c == '0' || c == '1' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_BINARY | TT_INTEGER; + } + // its an octal number + else { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c >= '0' && c <= '7' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_OCTAL | TT_INTEGER; + } + } + else { + // decimal integer or floating point number or ip address + dot = 0; + while( 1 ) { + if ( c >= '0' && c <= '9' ) { + } + else if ( c == '.' ) { + dot++; + } + else { + break; + } + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if( c == 'e' && dot == 0) { + //We have scientific notation without a decimal point + dot++; + } + // if a floating point number + if ( dot == 1 ) { + token->subtype = TT_DECIMAL | TT_FLOAT; + // check for floating point exponent + if ( c == 'e' ) { + //Append the e so that GetFloatValue code works + token->AppendDirty( c ); + c = *(++idLexer::script_p); + if ( c == '-' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + else if ( c == '+' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + } + // check for floating point exception infinite 1.#INF or indefinite 1.#IND or NaN + else if ( c == '#' ) { + c2 = 4; + if ( CheckString( "INF" ) ) { + token->subtype |= TT_INFINITE; + } + else if ( CheckString( "IND" ) ) { + token->subtype |= TT_INDEFINITE; + } + else if ( CheckString( "NAN" ) ) { + token->subtype |= TT_NAN; + } + else if ( CheckString( "QNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + else if ( CheckString( "SNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + for ( i = 0; i < c2; i++ ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if ( !(idLexer::flags & LEXFL_ALLOWFLOATEXCEPTIONS) ) { + token->AppendDirty( 0 ); // zero terminate for c_str + idLexer::Error( "parsed %s", token->c_str() ); + } + } + } + else if ( dot > 1 ) { + if ( !( idLexer::flags & LEXFL_ALLOWIPADDRESSES ) ) { + idLexer::Error( "more than one dot in number" ); + return 0; + } + if ( dot != 3 ) { + idLexer::Error( "ip address should have three dots" ); + return 0; + } + token->subtype = TT_IPADDRESS; + } + else { + token->subtype = TT_DECIMAL | TT_INTEGER; + } + } + + if ( token->subtype & TT_FLOAT ) { + if ( c > ' ' ) { + // single-precision: float + if ( c == 'f' || c == 'F' ) { + token->subtype |= TT_SINGLE_PRECISION; + idLexer::script_p++; + } + // extended-precision: long double + else if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_EXTENDED_PRECISION; + idLexer::script_p++; + } + // default is double-precision: double + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else if ( token->subtype & TT_INTEGER ) { + if ( c > ' ' ) { + // default: signed long + for ( i = 0; i < 2; i++ ) { + // long integer + if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_LONG; + } + // unsigned integer + else if ( c == 'u' || c == 'U' ) { + token->subtype |= TT_UNSIGNED; + } + else { + break; + } + c = *(++idLexer::script_p); + } + } + } + else if ( token->subtype & TT_IPADDRESS ) { + if ( c == ':' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype |= TT_IPPORT; + } + } + token->data[token->len] = '\0'; + return 1; +} + +/* +================ +idLexer::ReadPunctuation +================ +*/ +int idLexer::ReadPunctuation( idToken *token ) { + int l, n, i; + char *p; + const punctuation_t *punc; + +#ifdef PUNCTABLE + for (n = idLexer::punctuationtable[(unsigned int)*(idLexer::script_p)]; n >= 0; n = idLexer::nextpunctuation[n]) + { + punc = &(idLexer::punctuations[n]); +#else + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + punc = &idLexer::punctuations[i]; +#endif + p = punc->p; + // check for this punctuation in the script + for ( l = 0; p[l] && idLexer::script_p[l]; l++ ) { + if ( idLexer::script_p[l] != p[l] ) { + break; + } + } + if ( !p[l] ) { + // + token->EnsureAlloced( l+1, false ); + for ( i = 0; i <= l; i++ ) { + token->data[i] = p[i]; + } + token->len = l; + // + idLexer::script_p += l; + token->type = TT_PUNCTUATION; + // sub type is the punctuation id + token->subtype = punc->n; + return 1; + } + } + return 0; +} + +/* +================ +idLexer::ReadToken +================ +*/ +int idLexer::ReadToken( idToken *token ) { + int c; + + if ( !loaded ) { +// RAVEN BEGIN +#ifndef _XENON +// nrausch: should not be an error on xenon since it prevents the precache stuff from working + idLib::common->Error( "idLexer::ReadToken: no file loaded" ); +#else + idLib::common->Warning( "idLexer::ReadToken: no file loaded" ); +#endif +// RAVEN END + return 0; + } + + // if there is a token available (from unreadToken) + if ( tokenavailable ) { + tokenavailable = 0; + *token = idLexer::token; + return 1; + } + // save script pointer + lastScript_p = script_p; + // save line counter + lastline = line; + // clear the token stuff + token->data[0] = '\0'; + token->len = 0; + // start of the white space + whiteSpaceStart_p = script_p; + token->whiteSpaceStart_p = script_p; + // read white space before token + if ( !ReadWhiteSpace() ) { + return 0; + } + // end of the white space + idLexer::whiteSpaceEnd_p = script_p; + token->whiteSpaceEnd_p = script_p; + // line the token is on + token->line = line; + // number of lines crossed before token + token->linesCrossed = line - lastline; + // clear token flags + token->flags = 0; + + c = *idLexer::script_p; + + // if we're keeping everything as whitespace deliminated strings + if ( idLexer::flags & LEXFL_ONLYSTRINGS ) { + // if there is a leading quote + if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } else if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // if there is a number + else if ( (c >= '0' && c <= '9') || + (c == '.' && (*(idLexer::script_p + 1) >= '0' && *(idLexer::script_p + 1) <= '9')) ) { + if ( !idLexer::ReadNumber( token ) ) { + return 0; + } + // if names are allowed to start with a number + if ( idLexer::flags & LEXFL_ALLOWNUMBERNAMES ) { + c = *idLexer::script_p; + if ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + } + } + // if there is a leading quote + else if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } + // if there is a name +// RAVEN BEGIN + else if ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' ) { +// RAVEN END + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // names may also start with a slash when pathnames are allowed + else if ( ( idLexer::flags & LEXFL_ALLOWPATHNAMES ) && ( (c == '/' || c == '\\') || c == '.' ) ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // check for punctuations + else if ( !idLexer::ReadPunctuation( token ) ) { + idLexer::Error( "unknown punctuation %c", c ); + return 0; + } +// RAVEN BEGIN +// jsinger: added to write out the binary version of this token when binary writes have been turned on + WriteBinaryToken(token); + +// RAVEN END + // succesfully read a token + return 1; +} + +/* +================ +idLexer::ExpectTokenString +================ +*/ +int idLexer::ExpectTokenString( const char *string ) { + idToken token; + + if (!idLexer::ReadToken( &token )) { + idLexer::Error( "couldn't find expected '%s'", string ); + return 0; + } + if ( token != string ) { + idLexer::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; +} + +/* +================ +idLexer::ExpectTokenType +================ +*/ +int idLexer::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idLexer::ReadToken( token ) ) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idLexer::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idLexer::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idLexer::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idLexer::Error( "expected '%s' but found '%s'", GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idLexer::ExpectAnyToken +================ +*/ +int idLexer::ExpectAnyToken( idToken *token ) { + if (!idLexer::ReadToken( token )) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + else { + return 1; + } +} + +/* +================ +idLexer::CheckTokenString +================ +*/ +int idLexer::CheckTokenString( const char *string ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + + // if the token is available + if ( tok == string ) { + return 1; + } + // token not available + UnreadToken( &tok ); + return 0; +} + +/* +================ +idLexer::CheckTokenType +================ +*/ +int idLexer::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + // if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return 1; + } + // token is not available + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return 0; +} + +/* +================ +idLexer::SkipUntilString +================ +*/ +int idLexer::SkipUntilString( const char *string ) { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token == string ) { + return 1; + } + } + return 0; +} + +/* +================ +idLexer::SkipRestOfLine +================ +*/ +int idLexer::SkipRestOfLine( void ) { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return 1; + } + } + return 0; +} + +/* +================= +idLexer::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idLexer::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if ( token.type == TT_PUNCTUATION ) { + if ( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================ +idLexer::UnreadToken +================ +*/ +void idLexer::UnreadToken( const idToken *token ) { + if ( idLexer::tokenavailable ) { + idLib::common->FatalError( "idLexer::unreadToken, unread token twice\n" ); + } + idLexer::token = *token; + idLexer::tokenavailable = 1; +} + +/* +================ +idLexer::ReadTokenOnLine +================ +*/ +int idLexer::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // restore our position + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + token->Clear(); + return false; +} + +/* +================ +idLexer::ReadRestOfLine +================ +*/ +const char* idLexer::ReadRestOfLine(idStr& out) { + while(1) { + + if(*idLexer::script_p == '\n') { + idLexer::line++; + break; + } + + if(!*idLexer::script_p) { + break; + } + + if(*idLexer::script_p <= ' ') { + out += " "; + } else { + out += *idLexer::script_p; + } + idLexer::script_p++; + + } + + out.Strip(' '); + return out.c_str(); +} + +/* +================ +idLexer::ParseInt +================ +*/ +int idLexer::ParseInt( void ) { + idToken token; + + if ( !idLexer::ReadToken( &token ) ) { + idLexer::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idLexer::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idLexer::ParseBool +================ +*/ +bool idLexer::ParseBool( void ) { + idToken token; + + if ( !idLexer::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idLexer::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idLexer::ParseFloat +================ +*/ +float idLexer::ParseFloat( bool *errorFlag ) { + idToken token; + + if ( errorFlag ) { + *errorFlag = false; + } + + if ( !idLexer::ReadToken( &token ) ) { + if ( errorFlag ) { + idLexer::Warning( "couldn't read expected floating point number" ); + *errorFlag = true; + } else { + idLexer::Error( "couldn't read expected floating point number" ); + } + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + if ( errorFlag ) { + idLexer::Warning( "expected float value, found '%s'", token.c_str() ); + *errorFlag = true; + } else { + idLexer::Error( "expected float value, found '%s'", token.c_str() ); + } + } + return token.GetFloatValue(); +} + +/* +================ +idLexer::Parse1DMatrix +================ +*/ +int idLexer::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idLexer::ParseFloat(); + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +// RAVEN BEGIN +// rjohnson: added vertex color support to proc files. assume a default RGBA of 0x000000ff +/* +================ +idLexer::Parse1DMatrixOpenEnded +================ +*/ +int idLexer::Parse1DMatrixOpenEnded( int MaxCount, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return 0; + } + + for ( i = 0; i < MaxCount; i++ ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + + if ( tok == ")" ) { + return i; + } + + idLexer::UnreadToken( &tok ); + + m[i] = idLexer::ParseFloat(); + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return 0; + } + + return i; +} +// RAVEN END + +/* +================ +idLexer::Parse2DMatrix +================ +*/ +int idLexer::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idLexer::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::Parse3DMatrix +================ +*/ +int idLexer::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idLexer::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::ParseNumericStructArray +================ +*/ +void idLexer::ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ) +{ + int arrayOffset, curElement; + + for ( arrayOffset = 0; arrayOffset < arrayCount; arrayOffset++ ) + { + for ( curElement = 0; curElement < numStructElements; curElement++ ) + { + if ( tokenSubTypeStructElements[curElement] & TT_FLOAT ) + { + *(float*)arrayStorage = idLexer::ParseFloat(); + arrayStorage += sizeof(float); + } + else + { + *(int*)arrayStorage = idLexer::ParseInt(); + arrayStorage += sizeof(int); + } + } + } +} + +/* +================= +idParser::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains exact characters between braces. + + FIXME: this should use ReadToken and replace the token white space with correct indents and newlines +================= +*/ +const char *idLexer::ParseBracedSectionExact( idStr &out, int tabs ) { + int depth; + bool doTabs; + bool skipWhite; + + out.Empty(); + + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str( ); + } + + out = "{"; + depth = 1; + skipWhite = false; + doTabs = tabs >= 0; + + while( depth && *idLexer::script_p ) { + char c = *(idLexer::script_p++); + + switch ( c ) { + case '\t': + case ' ': { + if ( skipWhite ) { + continue; + } + break; + } + case '\n': { +// RAVEN BEGIN +// jscott: now gives correct line number in error reports + line++; +// RAVEN END + if ( doTabs ) { + skipWhite = true; + out += c; + continue; + } + break; + } + case '{': { + depth++; + tabs++; + break; + } + case '}': { + depth--; + tabs--; + break; + } + } + + if ( skipWhite ) { + int i = tabs; + if ( c == '{' ) { + i--; + } + skipWhite = false; + for ( ; i > 0; i-- ) { + out += '\t'; + } + } + out += c; + } + return out.c_str(); +} + +/* +================= +idLexer::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +const char *idLexer::ParseBracedSection( idStr &out ) { + idToken token; + int i, depth; + + out.Empty(); + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str(); + } + out = "{"; + depth = 1; + do { + if ( !idLexer::ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if ( token.type == TT_PUNCTUATION ) { + if ( token[0] == '{' ) { + depth++; + } + else if ( token[0] == '}' ) { + depth--; + } + } + + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } + else { + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idLexer::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idLexer::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +================ +idLexer::GetLastWhiteSpace +================ +*/ +int idLexer::GetLastWhiteSpace( idStr &whiteSpace ) const { + whiteSpace.Clear(); + for ( const char *p = whiteSpaceStart_p; p < whiteSpaceEnd_p; p++ ) { + whiteSpace.Append( *p ); + } + return whiteSpace.Length(); +} + +/* +================ +idLexer::GetLastWhiteSpaceStart +================ +*/ +int idLexer::GetLastWhiteSpaceStart( void ) const { + return whiteSpaceStart_p - buffer; +} + +/* +================ +idLexer::GetLastWhiteSpaceEnd +================ +*/ +int idLexer::GetLastWhiteSpaceEnd( void ) const { + return whiteSpaceEnd_p - buffer; +} + +/* +================ +idLexer::Reset +================ +*/ +void idLexer::Reset( void ) { + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // begin of white space + idLexer::whiteSpaceStart_p = NULL; + // end of white space + idLexer::whiteSpaceEnd_p = NULL; + // set if there's a token available in idLexer::token + idLexer::tokenavailable = 0; + + idLexer::line = 1; + idLexer::lastline = 1; + // clear the saved token + idLexer::token = ""; +} + +/* +================ +idLexer::EndOfFile +================ +*/ +int idLexer::EndOfFile( void ) { + return idLexer::script_p >= idLexer::end_p; +} + +/* +================ +idLexer::NumLinesCrossed +================ +*/ +int idLexer::NumLinesCrossed( void ) { + return idLexer::line - idLexer::lastline; +} + +/* +================ +idLexer::LoadFile +================ +*/ +int idLexer::LoadFile( const char *filename, bool OSPath ) { + idFile *fp; + idStr pathname; + int length; + char *buf; + + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadFile: another script already loaded"); + return false; + } + + if ( !OSPath && ( baseFolder[0] != '\0' ) ) { + pathname = va( "%s/%s", baseFolder, filename ); + } else { + pathname = filename; + } + if ( OSPath ) { + fp = idLib::fileSystem->OpenExplicitFileRead( pathname ); + } else { + fp = idLib::fileSystem->OpenFileRead( pathname ); + } + if ( !fp ) { + return false; + } + length = fp->Length(); +// RAVEN BEGIN +// amccarthy: Added memory allocation tag + buf = (char *) Mem_Alloc( length + 1, MA_LEXER ); + if( !buf ) { + common->FatalError( "Memory system failure : out of memory" ); + } +// RAVEN END + buf[length] = '\0'; + fp->Read( buf, length ); + idLexer::fileTime = fp->Timestamp(); + idLexer::filename = fp->GetFullPath(); + idLib::fileSystem->CloseFile( fp ); + + idLexer::buffer = buf; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = 1; + idLexer::lastline = 1; + idLexer::allocated = true; + idLexer::loaded = true; + +// RAVEN BEGIN +// jsinger: initialize compiled file + if(flags & LEXFL_WRITEBINARY) + { + pathname.Append(Lexer::sCompiledFileSuffix); + if ( OSPath ) { + mBinaryFile = idLib::fileSystem->OpenExplicitFileWrite( pathname ); + } else { + mBinaryFile = idLib::fileSystem->OpenFileWrite( pathname ); + } + } +// RAVEN END + + return true; +} + +/* +================ +idLexer::LoadMemory +================ +*/ +int idLexer::LoadMemory( const char *ptr, int length, const char *name, int startLine ) { + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadMemory: another script already loaded"); + return false; + } + idLexer::filename = name; + idLexer::buffer = ptr; + idLexer::fileTime = 0; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = startLine; + idLexer::lastline = startLine; + idLexer::allocated = false; + idLexer::loaded = true; + + return true; +} + +/* +================ +idLexer::FreeSource +================ +*/ +void idLexer::FreeSource( void ) { +#ifdef PUNCTABLE + if ( idLexer::punctuationtable && idLexer::punctuationtable != default_punctuationtable ) { + Mem_Free( (void *) idLexer::punctuationtable ); + idLexer::punctuationtable = NULL; + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( (void *) idLexer::nextpunctuation ); + idLexer::nextpunctuation = NULL; + } +#endif //PUNCTABLE + if ( idLexer::allocated ) { + Mem_Free( (void *) idLexer::buffer ); + idLexer::buffer = NULL; + idLexer::allocated = false; + } + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::loaded = false; +// RAVEN BEGIN +// jsinger: close compile file if it exists + if(flags & LEXFL_WRITEBINARY) + { + if(mBinaryFile) + { + idLib::fileSystem->CloseFile(mBinaryFile); + mBinaryFile = NULL; + } + } +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( void ) { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = 0; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( int flags ) { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *filename, int flags, bool OSPath ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END + idLexer::LoadFile( filename, OSPath ); +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *ptr, int length, const char *name, int flags ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END + idLexer::LoadMemory( ptr, length, name ); +} + +/* +================ +idLexer::~idLexer +================ +*/ +idLexer::~idLexer( void ) { + idLexer::FreeSource(); +} + +// RAVEN BEGIN +// jsinger: SetBaseFolder was moved to the Lexer base class to unify its functionality across all +// derived classes +/* +================ +idLexer::SetBaseFolder +================ + +void idLexer::SetBaseFolder( const char *path ) { + idStr::Copynz( baseFolder, path, sizeof( baseFolder ) ); +} +*/ +// RAVEN END +/* +================ +idLexer::HadError +================ +*/ +bool idLexer::HadError( void ) const { + return hadError; +} + + +// RAVEN BEGIN +// jsinger: This method can write out a binary representation of a token in a format +// suitable to be read by the Lexer +/* +================ +idLexer::WriteBinaryToken + Writes a binary representation of the token value to the compiled file + when compiling is turned on +================ +*/ +void idLexer::WriteBinaryToken(idToken *tok) +{ + bool swapBytes = (flags & LEXFL_BYTESWAP) == LEXFL_BYTESWAP; + + if(flags & LEXFL_WRITEBINARY) + { + unsigned char prefix; + + switch(tok->type) + { + case TT_NUMBER: + if(tok->subtype & TT_INTEGER) + { + if(tok->subtype & TT_UNSIGNED) + { + unsigned long val = tok->GetUnsignedLongValue(); + unsigned char byteVal = (unsigned char)val; + unsigned short shortVal = (unsigned short)val; + + if(byteVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + long val = tok->GetIntValue(); + char byteVal = (char)val; + short shortVal = (short)val; + + if(byteVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + } + else if(tok->subtype & TT_FLOAT) + { + if(tok->subtype & TT_SINGLE_PRECISION) + { + float val = tok->GetFloatValue(); + int intval = tok->GetIntValue(); + if(((float)intval) == val) // integral check + { + char byteVal = (char)intval; + short shortVal = (short)intval; + + if(byteVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else if(tok->subtype & TT_DOUBLE_PRECISION) + { + // we can write out doubles as floats because the text file doesn't have double precision anyway + float val = tok->GetDoubleValue(); + int intval = tok->GetIntValue(); + if(((float)intval) == val) // integral check + { + char byteVal = (char)intval; + short shortVal = (short)intval; + + if(byteVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_DOUBLE, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_DOUBLE, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + } + break; + case TT_STRING: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_STRING, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_LITERAL: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_LITERAL, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_NAME: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_NAME, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_PUNCTUATION: + if(*tok == "&&") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LOGICALAND), mBinaryFile, swapBytes); + } + else if (*tok == "&") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_AMPERSAND), mBinaryFile, swapBytes); + } + else if(*tok == "=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "==") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EQUALEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "!=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_NOTEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "!") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EXCLAMATION), mBinaryFile, swapBytes); + } + else if(*tok == "<") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LESSTHAN), mBinaryFile, swapBytes); + } + else if(*tok == "<=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LESSOREQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "<<") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SHIFTLEFT), mBinaryFile, swapBytes); + } + else if(*tok == ">") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_GREATERTHAN), mBinaryFile, swapBytes); + } + else if(*tok == ">=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_GREATEROREQUAL), mBinaryFile, swapBytes); + } + else if(*tok == ">>") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SHIFTRIGHT), mBinaryFile, swapBytes); + } + else if(*tok == "%") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PERCENT), mBinaryFile, swapBytes); + } + else if(*tok == "[") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTBRACKET), mBinaryFile, swapBytes); + } + else if(*tok == "]") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTBRACKET), mBinaryFile, swapBytes); + } + else if(*tok == "-") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUS), mBinaryFile, swapBytes); + } + else if(*tok == "--") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUSMINUS), mBinaryFile, swapBytes); + } + else if(*tok == "-=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUSEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "+") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUS), mBinaryFile, swapBytes); + } + else if(*tok == "+=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUSEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "++") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUSPLUS), mBinaryFile, swapBytes); + } + else if(*tok == "(") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTPAREN), mBinaryFile, swapBytes); + } + else if(*tok == ")") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTPAREN), mBinaryFile, swapBytes); + } + else if(*tok == "{") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTBRACE), mBinaryFile, swapBytes); + } + else if(*tok == "}") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTBRACE), mBinaryFile, swapBytes); + } + else if(*tok == ",") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_COMMA), mBinaryFile, swapBytes); + } + else if(*tok == "::") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLECOLON), mBinaryFile, swapBytes); + } + else if(*tok == "#") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_HASH), mBinaryFile, swapBytes); + } + else if(*tok == "##") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLEHASH), mBinaryFile, swapBytes); + } + else if(*tok == "/") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_FORWARDSLASH), mBinaryFile, swapBytes); + } + else if(*tok == "\\") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_BACKSLASH), mBinaryFile, swapBytes); + } + else if(*tok == ";") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SEMICOLON), mBinaryFile, swapBytes); + } + else if(*tok == ".") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PERIOD), mBinaryFile, swapBytes); + } + else if(*tok == "$") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOLLARSIGN), mBinaryFile, swapBytes); + } + else if(*tok == "~") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_TILDE), mBinaryFile, swapBytes); + } + else if(*tok == "|") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PIPE), mBinaryFile, swapBytes); + } + else if(*tok == "||") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLEPIPE), mBinaryFile, swapBytes); + } + else if(*tok == "*") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_ASTERISK), mBinaryFile, swapBytes); + } + else if(*tok == "*=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_TIMESEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "ˇ") + { + TextCompiler::WriteValue(BTT_PUNC_INVERTEDPLING, mBinaryFile, swapBytes); + } + else if(*tok == "ż") + { + TextCompiler::WriteValue(BTT_PUNC_INVERTEDQUERY, mBinaryFile, swapBytes); + } + else + { + common->Warning("Unsupported punctuation: '%s'", tok->c_str()); + + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_ASNULLTERMINATED), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + } + break; + default: + // wtf? + assert(false); + } + } +} + +// jsinger: this method takes an existing file and causes a binary tokenized +// version of the file to be generated +void idLexer::WriteBinaryFile(char const * const filename) +{ + if(!cvarSystem->GetCVarBool("com_BinaryRead")) + { + unsigned int swap=0; + + switch(cvarSystem->GetCVarInteger("com_BinaryWrite")) + { + case 1: + swap = 0; + break; + case 2: + swap = LEXFL_BYTESWAP; + break; + } + + idLexer src(filename, LEXFL_WRITEBINARY | swap); + idToken token; + if(src.IsLoaded()) + { + while(src.ReadToken(&token)) + { + // we don't need to do anything, because just reading the file + // causes it to write a tokenized version + } + } + } +} + +// jsinger: implementation for a class that can load binary tokenized files +using namespace TextCompiler; +Lexer::Lexer(int flags) +{ +#ifndef LEXER_READ_AHEAD + mFile = NULL; +#endif + offset = 0; + tokenAvailable = false; + mFlags = flags; + if(flags & LEXFL_READBINARY) + { + mDelegate = NULL; + } + else + { + mDelegate = new idLexer(flags); + } +} + +Lexer::Lexer(char const * const ptr, int length, char const * const name, int flags) +{ + if(flags & LEXFL_READBINARY) + { +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.InitFromMemory(ptr, length); +#else + assert(false); +#endif + mDelegate = NULL; + } + else + { + mDelegate = new idLexer(ptr, length, name, flags); + } +} + +Lexer::Lexer(char const * const filename, int flags, bool OSPath) +{ +#ifndef LEXER_READ_AHEAD + mFile = NULL; +#endif + offset = 0; + tokenAvailable = false; + mFlags = flags; + if(flags & LEXFL_READBINARY) + { + mDelegate = NULL; + LoadFile(filename, OSPath); + } + else + { + mDelegate = new idLexer(filename, flags, OSPath); + } +} + +Lexer::~Lexer() +{ + FreeSource(); +} + +char const * Lexer::GetFileName() +{ + if(mDelegate) + { + return mDelegate->GetFileName(); + } + else + { + assert(false); + return ""; + } +} + +bool Lexer::HadError() const +{ + if(mDelegate) + { + return mDelegate->HadError(); + } + else + { + assert(false); + return false; + } +} + +void Lexer::Warning(char const *str, ...) +{ + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + idLib::common->Warning( "Lexer: '%s'", text ); +} + +void Lexer::Error(char const *str, ...) +{ + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + + assert(false); + + idLib::common->Error( "Lexer: offset: %d '%s'", offset, text ); +} + +int const Lexer::GetLineNum() +{ + if(mDelegate) + { + return mDelegate->GetLineNum(); + } + else + { + return 1; + } +} + +unsigned int const Lexer::GetFileTime() +{ + if(mDelegate) + { + return mDelegate->GetFileTime(); + } + else + { + return 0; + } +} + +int const Lexer::GetFileOffset() +{ + if(mDelegate) + { + return mDelegate->GetFileOffset(); + } + { + assert(false); + return 0; + } +} + +int Lexer::EndOfFile() +{ + if(mDelegate) + { + return mDelegate->EndOfFile(); + } + else + { +#ifdef LEXER_READ_AHEAD + return mLexerIOWrapper.Tell()>= mLexerIOWrapper.Length(); +#else + return mFile->Tell()>= mFile->Length(); +#endif + } +} + +void Lexer::Reset() +{ + if(mDelegate) + { + mDelegate->Reset(); + } + else + { +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.Seek(0, FS_SEEK_SET); +#else + mFile->Seek(0, FS_SEEK_SET); +#endif + offset = 0; + tokenAvailable = false; + } +} + +int Lexer::GetFlags() +{ + if(mDelegate) + { + return mDelegate->GetFlags(); + } + else + { + return mFlags; + } +} + +void Lexer::SetFlags(int flags) +{ + if(mDelegate) + { + mDelegate->SetFlags(flags); + } + else + { + mFlags = flags; + } +} + +int Lexer::GetPunctuationId(char const *str) +{ + if(mDelegate) + { + return mDelegate->GetPunctuationId(str); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +void Lexer::SetPunctuations(struct punctuation_s const *punc) +{ + if(mDelegate) + { + mDelegate->SetPunctuations(punc); + } + else + { + // this method is not supported + assert(false); + } +} + +int Lexer::GetLastWhiteSpaceEnd() const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpaceEnd(); + } + else + { + return 0; + } +} + +char const *Lexer::GetPunctuationFromId(int val) +{ + if(mDelegate) + { + return mDelegate->GetPunctuationFromId(val); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +int Lexer::GetLastWhiteSpaceStart() const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpaceStart(); + } + else + { + return 0; + } +} + +int Lexer::GetLastWhiteSpace(idStr &str) const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpace(str); + } + else + { + return 0; + } +} + +char const *Lexer::ParseRestOfLine(idStr &str) +{ + if(mDelegate) + { + return mDelegate->ParseRestOfLine(str); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +char const *Lexer::ParseBracedSectionExact(idStr &str, int val) +{ + if(mDelegate) + { + return mDelegate->ParseBracedSectionExact(str, val); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +char const *Lexer::ParseBracedSection(idStr &str) +{ + if(mDelegate) + { + return mDelegate->ParseBracedSection(str); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +int Lexer::Parse3DMatrix(int z, int y, int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse3DMatrix(z, y, x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +int Lexer::Parse2DMatrix(int y, int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse2DMatrix(y, x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +int Lexer::Parse1DMatrixOpenEnded(int MaxCount, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse1DMatrixOpenEnded(MaxCount, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return 0; + } + + for ( i = 0; i < MaxCount; i++ ) { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + + if ( tok == ")" ) { + return i; + } + + UnreadToken( &tok ); + + m[i] = ParseFloat(); + } + + if ( !ExpectTokenString( ")" ) ) { + return 0; + } + + return i; + } +} + +int Lexer::Parse1DMatrix(int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse1DMatrix(x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = ParseFloat(); + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +float Lexer::ParseFloat(bool *errorFlag) +{ + if(mDelegate) + { + return mDelegate->ParseFloat(errorFlag); + } + else + { + idToken token; + + if(EndOfFile()) + { + return 0; + } + + if ( !ReadToken( &token ) ) { + Warning( "couldn't read expected floating point number" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + Warning( "expected float value, found '%s'", token.c_str() ); + } + return token.GetFloatValue(); + } +} + +bool Lexer::ParseBool() +{ + if(mDelegate) + { + return mDelegate->ParseBool(); + } + else + { + idToken token; + + if ( !ExpectTokenType( TT_NUMBER, 0, &token ) ) { + Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); + } +} + +int Lexer::ParseInt() +{ + if(mDelegate) + { + return mDelegate->ParseInt(); + } + else + { + idToken token; + + if ( !ReadToken( &token ) ) { + Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); + } +} + +int Lexer::ReadTokenOnLine(idToken *token) +{ + if(mDelegate) + { + return mDelegate->ReadTokenOnLine(token); + } + else + { + // lines are meaningless in the binary format + return ReadToken(token); + } +} + +void Lexer::UnreadToken(idToken const *token) +{ + if(mDelegate) + { + mDelegate->UnreadToken(token); + } + else + { + if ( tokenAvailable ) + { + idLib::common->FatalError( "Lexer::unreadToken, unread token twice\n" ); + } + unreadToken = *token; + tokenAvailable = true; + offset -= unreadSize+1; // the +1 is for the prefix + } +} + +int Lexer::SkipBracedSection(bool parseFirstBrace) +{ + if(mDelegate) + { + return mDelegate->SkipBracedSection(parseFirstBrace); + } + else + { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if ( token.type == TT_PUNCTUATION ) { + if ( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; + } +} + +int Lexer::SkipRestOfLine() +{ + if(mDelegate) + { + return mDelegate->SkipRestOfLine(); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +int Lexer::SkipUntilString(char const *str) +{ + if(mDelegate) + { + return mDelegate->SkipUntilString(str); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +int Lexer::CheckTokenType(int type, int subtype, idToken *token) +{ + if(mDelegate) + { + return mDelegate->CheckTokenType(type, subtype, token); + } + else + { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + // if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return 1; + } + return 0; + } +} + +int Lexer::CheckTokenString(char const *string) +{ + if(mDelegate) + { + return mDelegate->CheckTokenString(string); + } + else + { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + + // if the token is available + if ( tok == string ) { + return 1; + } + + UnreadToken( &tok ); + return 0; + } +} + +int Lexer::ExpectAnyToken(idToken *token) +{ + if(mDelegate) + { + return mDelegate->ExpectAnyToken(token); + } + else + { + if (!ReadToken( token )) { + Error( "couldn't read expected token" ); + return 0; + } + else { + return 1; + } + } +} + +int Lexer::ExpectTokenType(int type, int subtype, idToken *token) +{ + if(mDelegate) + { + return mDelegate->ExpectTokenType(type, subtype, token); + } + else + { + idStr str; + + if ( !ReadToken( token ) ) { + Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + Error( "expected '%s' but found '%s'", GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; + } +} + +int Lexer::ExpectTokenString(char const *string) +{ + if(mDelegate) + { + return mDelegate->ExpectTokenString(string); + } + else + { + idToken token; + + if(!ReadToken( &token )) + { + Error( "couldn't find expected '%s'", string ); + return 0; + } + if( token != string ) + { + Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; + } +} + +int Lexer::ReadToken(idToken *token) +{ + +#ifdef LEXER_READ_AHEAD +#define OBJ &mLexerIOWrapper +#else +#define OBJ mFile +#endif + + if(mDelegate) + { + return mDelegate->ReadToken(token); + } + else + { + // if there are any unread tokens, use them first + if(tokenAvailable) + { + *token = unreadToken; + tokenAvailable = false; + } + else + { +#ifndef LEXER_READ_AHEAD + assert(mFile); +#endif + if(EndOfFile()) + return 0; + unsigned char prefix = ReadValue(OBJ); + token->Clear(); + + // BTT types are equivalent to the TT types except they are one less, so we add one to get the token type + if(BTT_GET_TYPE(prefix) == BTT_PUNCTUATION2) + token->type = TT_PUNCTUATION; + else + token->type = BTT_GET_TYPE(prefix)+1; + + // this is used only to determine if the punctuation was encoded in the prefix or came afterwards as a null terminated string + bool encoded = true; + + switch(token->type) + { + case TT_STRING: + case TT_NAME: + case TT_LITERAL: + if(BTT_GET_STRING_LENGTH(prefix) != 0) + { + // short string + int length = BTT_GET_STRING_LENGTH(prefix); + if ( length == 0 ) { + assert( false ); + } + int i; + for( i = 0; i < length; i++ ) + { + char c = ReadValue(OBJ); + token->Append(c); + } + // short strings have no null, so we must add one + //token->Append('\0'); + + token->subtype = token->Length(); + unreadSize = token->Length(); + } + else + { + // null terminated string + *token = ReadValue(OBJ); + token->subtype = token->Length(); + unreadSize = token->Length()+1; + } + break; + case TT_PUNCTUATION: + switch(BTT_GET_PUNCTUATION(prefix)) + { + case BTT_PUNC_ASNULLTERMINATED: + // null terminated string + *token = ReadValue(OBJ); + token->subtype = token->Length(); + unreadSize = token->Length()+1; + encoded = false; + break; + case BTT_PUNC_RIGHTPAREN: + *token = ")"; + break; + case BTT_PUNC_LEFTBRACE: + *token = "{"; + break; + case BTT_PUNC_RIGHTBRACE: + *token = "}"; + break; + case BTT_PUNC_MINUS: + *token = "-"; + break; + case BTT_PUNC_PLUS: + *token = "+"; + break; + case BTT_PUNC_COMMA: + *token = ","; + break; + case BTT_PUNC_PLUSPLUS: + *token = "++"; + break; + case BTT_PUNC_LEFTBRACKET: + *token = "["; + break; + case BTT_PUNC_RIGHTBRACKET: + *token = "]"; + break; + case BTT_PUNC_EQUAL: + *token = "="; + break; + case BTT_PUNC_EQUALEQUAL: + *token = "=="; + break; + case BTT_PUNC_NOTEQUAL: + *token = "!="; + break; + case BTT_PUNC_PERCENT: + *token = "%"; + break; + case BTT_PUNC_LESSTHAN: + *token = "<"; + break; + case BTT_PUNC_GREATERTHAN: + *token = ">"; + break; + case BTT_PUNC_LOGICALAND: + *token = "&&"; + break; + case BTT_PUNC_AMPERSAND: + *token = "&"; + break; + case BTT_PUNC_MINUSMINUS: + *token = "--"; + break; + case BTT_PUNC_HASH: + *token = "#"; + break; + case BTT_PUNC_LESSOREQUAL: + *token = "<="; + break; + case BTT_PUNC_GREATEROREQUAL: + *token = ">="; + break; + case BTT_PUNC_FORWARDSLASH: + *token = "/"; + break; + case BTT_PUNC_SHIFTLEFT: + *token = "<<"; + break; + case BTT_PUNC_SHIFTRIGHT: + *token = ">>"; + break; + case BTT_PUNC_LEFTPAREN: + *token = "("; + break; + case BTT_PUNC_SEMICOLON: + *token = ";"; + break; + case BTT_PUNC_ASTERISK: + *token = "*"; + break; + case BTT_PUNC_PERIOD: + *token = "."; + break; + case BTT_PUNC_DOLLARSIGN: + *token = "$"; + break; + case BTT_PUNC_PLUSEQUAL: + *token = "+="; + break; + case BTT_PUNC_MINUSEQUAL: + *token = "-="; + break; + case BTT_PUNC_TILDE: + *token = "~"; + break; + case BTT_PUNC_EXCLAMATION: + *token = "!"; + break; + case BTT_PUNC_PIPE: + *token = "|"; + break; + case BTT_PUNC_BACKSLASH: + *token = "\\"; + break; + case BTT_PUNC_DOUBLEHASH: + *token = "##"; + break; + case BTT_PUNC_TIMESEQUAL: + *token = "*="; + break; + case BTT_PUNC_DOUBLEPIPE: + *token = "||"; + break; + case BTT_PUNC_INVERTEDPLING: + *token = "ˇ"; + break; + case BTT_PUNC_INVERTEDQUERY: + *token = "ż"; + break; + default: + assert(false); // unrecognized punctuation + } + if(encoded) + unreadSize = 0; // punctuation encoded in prefix + break; + case TT_NUMBER: + { + // number of bytes actually in the stream used to represent this value + unsigned int size = BTT_GET_STORED_SIZE(prefix); + const int buffersize = 100; + char buffer[buffersize]; // big enough buffer for the int to string conversion routines + + switch(BTT_GET_SUBTYPE(prefix)) + { + case BTT_SUBTYPE_INT: + switch(size) + { + case BTT_STORED_1BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(char); + break; + case BTT_STORED_2BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(short); + break; + case BTT_STORED_4BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(int); + break; + default: + // invalid stored size for an integer + assert(false); + } + + token->floatvalue = token->intvalue; + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + + // This conversion to a string assumes that long and int are the same + assert(sizeof(long) == sizeof(int)); + + //ltoa(token->intvalue, buffer, 10); + idStr::snPrintf( buffer, buffersize, "%ld", token->intvalue ); + assert(token->intvalue == atol(buffer)); + *token = buffer; + token->subtype = TT_INTEGER | TT_DECIMAL | TT_VALUESVALID; + break; + + case BTT_SUBTYPE_UNSIGNEDINT: + switch(size) + { + case BTT_STORED_1BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned char); + break; + case BTT_STORED_2BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned short); + break; + case BTT_STORED_4BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned int); + break; + default: + // invalid stored size for an unsigned integer + assert(false); + } + token->floatvalue = token->intvalue; + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + + // This conversion to a string assumes that long and int are the same + assert(sizeof(long) == sizeof(int)); + + //ultoa(token->intvalue, buffer, 10); + idStr::snPrintf( buffer, buffersize, "%lu", token->intvalue ); + assert(token->intvalue == ((unsigned long)atol(buffer))); + *token = buffer; + token->subtype = TT_INTEGER | TT_UNSIGNED | TT_DECIMAL | TT_VALUESVALID; + break; + + case BTT_SUBTYPE_FLOAT: + // invalid stored size for a float + assert(sizeof(float) == 4); + + switch(size) + { + case BTT_STORED_4BYTE: + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(float); + break; + case BTT_STORED_2BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(short); + break; + case BTT_STORED_1BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(char); + break; + default: + assert(false); + } + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + idStr::snPrintf( buffer, buffersize, "%f", token->floatvalue ); + *token = buffer; + token->subtype = TT_FLOAT | TT_DECIMAL | TT_SINGLE_PRECISION | TT_VALUESVALID; + + unreadSize = sizeof(float); + break; + + case BTT_SUBTYPE_DOUBLE: + // invalid stored size for a double + assert(sizeof(double) == 8); + + // doubles are stored as floats since the original text file never uses full double precision anyway + //assert(size == BTT_STORED_4BYTE); + + switch(size) + { + case BTT_STORED_4BYTE: + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(float); + break; + case BTT_STORED_2BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(short); + break; + case BTT_STORED_1BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(char); + break; + default: + assert(false); + } + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + idStr::snPrintf( buffer, buffersize, "%f", token->floatvalue ); + *token = buffer; + token->subtype = TT_FLOAT | TT_DECIMAL | TT_DOUBLE_PRECISION | TT_VALUESVALID; + break; + } + } + break; + + default: + // unsupported binary type + assert(false); + } + } + + //common->Warning("Read Token: '%s (int: %d)(float: %f)'\n", token->c_str(), token->GetIntValue(), token->GetFloatValue()); + //common->Printf("Read Token: %s\n", token->c_str()); + offset += unreadSize+1; // the +1 is for the prefix + return 1; + } +} + +int Lexer::IsLoaded() +{ + if(mDelegate) + { + return mDelegate->IsLoaded(); + } + else + { +#ifdef LEXER_READ_AHEAD + return mLexerIOWrapper.IsLoaded(); +#else + return mFile != NULL; +#endif + } +} + +void Lexer::FreeSource() +{ +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.Close(); +#else + if(mFile) + { + idLib::fileSystem->CloseFile(mFile); + mFile = NULL; + } +#endif + + if(mDelegate) + { + delete mDelegate; + mDelegate = NULL; + } +} + +int Lexer::LoadMemory(char const *ptr, int length, char const *name, int startLine) +{ + if(mDelegate) + { + return mDelegate->LoadMemory(ptr, length, name, startLine); + } + else + { + // this is essentially a no op + return true; + } +} + +int Lexer::LoadFile(char const *filename, bool OSPath) +{ + if(mDelegate) + { + return mDelegate->LoadFile(filename, OSPath); + } + else + { + idStr fullName; + FreeSource(); + + fullName = filename; + fullName.Append(sCompiledFileSuffix); + bool binaryFound = OpenFile(fullName, OSPath); + if(binaryFound) + { + return binaryFound; + } + else + { + mDelegate = new idLexer(filename, mFlags, OSPath); + int isLoaded = mDelegate->IsLoaded(); + if(isLoaded) + { + // don't do this right now until I clean up the Lexer class + if(mFlags & LEXFL_READBINARY) + { + Warning("%s not found, loading ascii version", fullName.c_str()); + } + } + else + { + // didn't find the ascii version either, make sure and clean up in case this lexer is reused + delete mDelegate; + mDelegate = NULL; + } + + return isLoaded; + } + } +} + +void Lexer::WriteBinaryToken(idToken *tok) +{ + if(mDelegate) + { + mDelegate->WriteBinaryToken(tok); + } + else + { + // can't write out an already binary file + assert(false); + } +} + +bool Lexer::OpenFile(char const *filename, bool OSPath) +{ + idStr pathname; + + if ( !OSPath && ( baseFolder[0] != '\0' ) ) { + pathname = va( "%s/%s", baseFolder, filename ); + } else { + pathname = filename; + } + +#ifdef LEXER_READ_AHEAD + if ( !mLexerIOWrapper.OpenFile( pathname, OSPath ) ) { + return false; + } +#else + if ( OSPath ) { + mFile = idLib::fileSystem->OpenExplicitFileRead( pathname ); + } else { + mFile = idLib::fileSystem->OpenFileRead( pathname ); + } + if ( !mFile ) { + return false; + } +#endif + + return true; +} + +void Lexer::SetBaseFolder( const char *path ) { + idStr::Copynz( baseFolder, path, sizeof( baseFolder ) ); +} + + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage +void Lexer::ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ) +{ + if(mDelegate) + { + mDelegate->ParseNumericStructArray( numStructElements, tokenSubTypeStructElements, arrayCount, arrayStorage ); + } + else + { + int arrayOffset, curElement; + + for ( arrayOffset = 0; arrayOffset < arrayCount; arrayOffset++ ) + { + for ( curElement = 0; curElement < numStructElements; curElement++ ) + { + if ( tokenSubTypeStructElements[curElement] & TT_FLOAT ) + { + *(float*)arrayStorage = Lexer::ParseFloat(); + arrayStorage += sizeof(float); + } + else + { + *(int*)arrayStorage = Lexer::ParseInt(); + arrayStorage += sizeof(int); + } + } + } + } +} +// RAVEN END + + +char idLexer::baseFolder[256]; +// RAVEN END diff --git a/source/idlib/Lexer.h b/source/idlib/Lexer.h new file mode 100644 index 0000000..e761429 --- /dev/null +++ b/source/idlib/Lexer.h @@ -0,0 +1,466 @@ + +#ifndef __LEXER_H__ +#define __LEXER_H__ + +/* +=============================================================================== + + Lexicographical parser + + Does not use memory allocation during parsing. The lexer uses no + memory allocation if a source is loaded with LoadMemory(). + However, idToken may still allocate memory for large strings. + + A number directly following the escape character '\' in a string is + assumed to be in decimal format instead of octal. Binary numbers of + the form 0b.. or 0B.. can also be used. + +=============================================================================== +*/ + +// lexer flags +typedef enum { + LEXFL_NOERRORS = BIT(0), // don't print any errors + LEXFL_NOWARNINGS = BIT(1), // don't print any warnings + LEXFL_NOFATALERRORS = BIT(2), // errors aren't fatal + LEXFL_NOSTRINGCONCAT = BIT(3), // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS = BIT(4), // no escape characters inside strings + LEXFL_NODOLLARPRECOMPILE = BIT(5), // don't use the $ sign for precompilation + LEXFL_NOBASEINCLUDES = BIT(6), // don't include files embraced with < > + LEXFL_ALLOWPATHNAMES = BIT(7), // allow path seperators in names + LEXFL_ALLOWNUMBERNAMES = BIT(8), // allow names to start with a number + LEXFL_ALLOWIPADDRESSES = BIT(9), // allow ip addresses to be parsed as numbers + LEXFL_ALLOWFLOATEXCEPTIONS = BIT(10), // allow float exceptions like 1.#INF or 1.#IND to be parsed + LEXFL_ALLOWMULTICHARLITERALS = BIT(11), // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT = BIT(12), // allow multiple strings seperated by '\' to be concatenated + LEXFL_ONLYSTRINGS = BIT(13), // parse as whitespace deliminated strings (quoted strings keep quotes) +// RAVEN BEGIN +// jsinger: added to support binary writing from the lexer in either byte swapped or non-byte swapped formats + LEXFL_READBINARY = BIT(29), // read a byte code compiled version of the file + LEXFL_BYTESWAP = BIT(30), // when writing the byte code compiled file, byte swap numbers + LEXFL_WRITEBINARY = BIT(31) // write out a byte code compiled version of the file +// RAVEN END +} lexerFlags_t; + +// punctuation ids +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 + +// RAVEN BEGIN +#define P_INVERTED_PLING 53 +#define P_INVERTED_QUERY 54 +// RAVEN END + +// punctuation +typedef struct punctuation_s +{ + char *p; // punctuation character(s) + int n; // punctuation id +} punctuation_t; + +// RAVEN BEGIN + +#ifdef LEXER_READ_AHEAD +class LexerIOWrapper { + idFile *file; + + int offset; + int size; + char *memory; + +public: + LexerIOWrapper(); + void InitFromMemory(char const * const ptr, int length); + bool OpenFile(char const *filename, bool OSPath); + int Length(); + int Tell(); + void Seek( int loc, int mode ); + void Read( void *dest, int s ); + void Close(); + int IsLoaded(); +}; +#endif + +class idLexer { + friend class idParser; + +public: +// RAVEN END + // constructor + idLexer(); + idLexer( int flags ); + idLexer( const char *filename, int flags = 0, bool OSPath = false ); + idLexer( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idLexer(); + // load a script from the given file at the given offset with the given length + int LoadFile( const char *filename, bool OSPath = false ); + // load a script from the given memory with the given length and a specified line offset, + // so source strings extracted from a file can still refer to proper line numbers in the file + // NOTE: the buffer is expect to be a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name, int startLine = 1 ); + // free the script + void FreeSource( void ); + // returns true if a script is loaded + int IsLoaded( void ) { return idLexer::loaded; } + // read a token + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true when the token is available + int CheckTokenString( const char *string ); + // returns true an reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // unread the given token + void UnreadToken( const idToken *token ); + // read a token only if on the same line + int ReadTokenOnLine( idToken *token ); + //Returns the rest of the current line + const char* ReadRestOfLine(idStr& out); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number. If errorFlag is NULL, a non-numeric token will + // issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true + float ParseFloat( bool *errorFlag = NULL ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); +// RAVEN BEGIN +// rjohnson: added vertex color support to proc files. assume a default RGBA of 0x000000ff + int Parse1DMatrixOpenEnded( int MaxCount, float *m ); +// RAVEN END + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact ( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // retrieves the white space characters before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // returns start index into text buffer of last white space + int GetLastWhiteSpaceStart( void ) const; + // returns end index into text buffer of last white space + int GetLastWhiteSpaceEnd( void ) const; + // set an array with punctuations, NULL restores default C/C++ set, see default_punctuations for an example + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ); + // reset the lexer + void Reset( void ); + // returns true if at the end of the file + int EndOfFile( void ); + // returns the current filename + const char * GetFileName( void ); + // get offset in script + const int GetFileOffset( void ); + // get file time + const unsigned int GetFileTime( void ); + // returns the current line number + const int GetLineNum( void ); + // print an error message + void Error( const char *str, ... ) id_attribute((format(printf,2,3))); + // print a warning message + void Warning( const char *str, ... ) id_attribute((format(printf,2,3))); + // returns true if Error() was called with LEXFL_NOFATALERRORS or LEXFL_NOERRORS set + bool HadError( void ) const; + + static void SetBaseFolder(char const * const); + +// RAVEN BEGIN + // write a binary representation of a token + void WriteBinaryToken(idToken *tok); + static void WriteBinaryFile(char const * const filename); +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage + void ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ); +// RAVEN END + +private: + int loaded; // set when a script file is loaded from file or memory + idStr filename; // file name of the script + bool allocated; // true if buffer memory was allocated + const char * buffer; // buffer containing the script + const char * script_p; // current pointer in the script + const char * end_p; // pointer to the end of the script + const char * lastScript_p; // script pointer before reading token + const char * whiteSpaceStart_p; // start of last white space + const char * whiteSpaceEnd_p; // end of last white space + unsigned int fileTime; // file time + int length; // length of the script in bytes + int line; // current line in script + int lastline; // line before reading token + int tokenavailable; // set by unreadToken + int flags; // several script flags + const punctuation_t *punctuations; // the punctuations used in the script + int * punctuationtable; // ASCII table with punctuations + int * nextpunctuation; // next punctuation in chain + idToken token; // available token + idLexer* next; // next script in a chain + bool hadError; // set by idLexer::Error, even if the error is supressed + +protected: + static char baseFolder[256]; + + +private: + void CreatePunctuationTable( const punctuation_t *punctuations ); + int ReadWhiteSpace( void ); + int ReadEscapeCharacter( char *ch ); + int ReadString( idToken *token, int quote ); + int ReadName( idToken *token ); + int ReadNumber( idToken *token ); + int ReadPunctuation( idToken *token ); + int ReadPrimitive( idToken *token ); + int CheckString( const char *str ) const; + int NumLinesCrossed( void ); + +// RAVEN BEGIN +// jsinger: This is the file that WriteBinaryToken will write to when the proper flags are set + idFile *mBinaryFile; +// RAVEN END +}; + +// this class is only necessary to parse binary files +class Lexer { + friend class idParser; + +public: + +#ifdef LEXER_READ_AHEAD + static void BeginLevelLoad(); + static void EndLevelLoad(); +#endif + + // constructors + Lexer( const char *filename, int flags = 0, bool OSPath = false ); + Lexer(int flags=0); + Lexer( char const * const ptr, int length, char const * const name, int flags = 0); + + //destructor + ~Lexer(); + // load a script from the given file at the given offset with the given length + int LoadFile( const char *filename, bool OSPath = false ); + // load a script from the given memory with the given length and a specified line offset, + // so source strings extracted from a file can still refer to proper line numbers in the file + // NOTE: the buffer is expect to be a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name, int startLine = 1 ); + // free the script + void FreeSource( void ); + // returns true if a script is loaded + int IsLoaded( void ); + // read a token + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true when the token is available + int CheckTokenString( const char *string ); + // returns true an reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // unread the given token + void UnreadToken( const idToken *token ); + // read a token only if on the same line + int ReadTokenOnLine( idToken *token ); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number. If errorFlag is NULL, a non-numeric token will + // issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true + float ParseFloat( bool *errorFlag = NULL ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse1DMatrixOpenEnded( int MaxCount, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact ( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // retrieves the white space characters before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // returns start index into text buffer of last white space + int GetLastWhiteSpaceStart( void ) const; + // returns end index into text buffer of last white space + int GetLastWhiteSpaceEnd( void ) const; + // set an array with punctuations, NULL restores default C/C++ set, see default_punctuations for an example + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ); + // reset the lexer + void Reset( void ); + // returns true if at the end of the file + int EndOfFile( void ); + // returns the current filename + const char * GetFileName( void ); + // get offset in script + const int GetFileOffset( void ); + // get file time + const unsigned int GetFileTime( void ); + // returns the current line number + const int GetLineNum( void ); + // print an error message + void Error( const char *str, ... ); + // print a warning message + void Warning( const char *str, ... ); + // returns true if Error() was called with LEXFL_NOFATALERRORS or LEXFL_NOERRORS set + bool HadError( void ) const; + + // write a binary representation of a token + void WriteBinaryToken(idToken *tok); + + static void SetBaseFolder(char const * const); + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage + void ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ); +// RAVEN END + + // suffix that should be appended to the normal file extension to signify that the file is binary + static idStr const sCompiledFileSuffix; + +protected: + static char baseFolder[256]; + +private: + bool OpenFile(char const *filename, bool OSPath); + +#ifdef LEXER_READ_AHEAD + LexerIOWrapper mLexerIOWrapper; +#else + idFile *mFile; +#endif + + unsigned int offset; + idToken unreadToken; + unsigned int unreadSize; + bool tokenAvailable; + idLexer *mDelegate; + int mFlags; +}; + + + +ID_INLINE const char *idLexer::GetFileName( void ) { + return idLexer::filename; +} + +ID_INLINE const int idLexer::GetFileOffset( void ) { + return idLexer::script_p - idLexer::buffer; +} + +ID_INLINE const unsigned int idLexer::GetFileTime( void ) { + return idLexer::fileTime; +} + +ID_INLINE const int idLexer::GetLineNum( void ) { + return idLexer::line; +} + +ID_INLINE void idLexer::SetFlags( int flags ) { + idLexer::flags = flags; +} + +ID_INLINE int idLexer::GetFlags( void ) { + return idLexer::flags; +} + +#endif /* !__LEXER_H__ */ + diff --git a/source/idlib/LexerFactory.cpp b/source/idlib/LexerFactory.cpp new file mode 100644 index 0000000..534f912 --- /dev/null +++ b/source/idlib/LexerFactory.cpp @@ -0,0 +1,53 @@ +#include "precompiled.h" +#pragma hdrstop +#include "LexerFactory.h" + +LexerFactory::~LexerFactory() +{ +} + +Lexer *LexerFactory::MakeLexer(char const * const filename, int flags, bool OSPath) +{ + return new Lexer(filename, flags | GetReadBinary() | GetWriteBinary(), OSPath); +} + +Lexer *LexerFactory::MakeLexer(int flags) +{ + return new Lexer(flags | GetReadBinary() | GetWriteBinary()); +} + +Lexer *LexerFactory::MakeLexer( char const * const ptr, int length, char const * const name, int flags) +{ + return new Lexer(ptr, length, name, flags | GetWriteBinary() | GetReadBinary()); +} + +int LexerFactory::GetReadBinary() +{ + if(cvarSystem->GetCVarBool("com_binaryRead")) + { + return LEXFL_READBINARY; + } + else + { + return 0; + } +} + +int LexerFactory::GetWriteBinary() +{ + int ret=0; + int writeBinary = cvarSystem->GetCVarInteger("com_binaryWrite"); + switch(writeBinary) + { + case 0: + break; + case 1: + ret = LEXFL_WRITEBINARY; + break; + case 2: + ret = LEXFL_WRITEBINARY | LEXFL_BYTESWAP; + break; + } + + return ret; +} diff --git a/source/idlib/LexerFactory.h b/source/idlib/LexerFactory.h new file mode 100644 index 0000000..021887f --- /dev/null +++ b/source/idlib/LexerFactory.h @@ -0,0 +1,21 @@ +#ifndef __LEXERFACTORY_H__ +#define __LEXERFACTORY_H__ + +class LexerFactory +{ +public: + + static Lexer *MakeLexer( int flags ); + static Lexer *MakeLexer( char const * const filename, int flags = 0, bool OSPath = false ); + static Lexer *MakeLexer( char const * const ptr, int length, char const * const name, int flags = 0 ); + +private: + static int GetReadBinary(); + static int GetWriteBinary(); + + // disallow default constructor + LexerFactory(); + ~LexerFactory(); +}; + +#endif diff --git a/source/idlib/Lib.cpp b/source/idlib/Lib.cpp new file mode 100644 index 0000000..61c6433 --- /dev/null +++ b/source/idlib/Lib.cpp @@ -0,0 +1,561 @@ + +#include "precompiled.h" +#pragma hdrstop + +#if defined( MACOS_X ) +#include +#include +#include +#endif + +/* +=============================================================================== + + idLib + +=============================================================================== +*/ + +idSys * idLib::sys = NULL; +idCommon * idLib::common = NULL; +idCVarSystem * idLib::cvarSystem = NULL; +idFileSystem * idLib::fileSystem = NULL; +int idLib::frameNumber = 0; + +/* +================ +idLib::Init +================ +*/ +void idLib::Init( void ) { + + // initialize little/big endian conversion + Swap_Init(); + + // initialize memory manager + Mem_Init(); + +// RAVEN BEGIN +// dluetscher: added the following code to initialize each of the memory heaps immediately +// following Mem_Init() +#ifdef _RV_MEM_SYS_SUPPORT + // initialize each of the memory heaps + common->InitHeaps(); +#endif +// RAVEN END + + // init string memory allocator + idStr::InitMemory(); + + // initialize generic SIMD implementation + idSIMD::Init(); + + // initialize math + idMath::Init(); + +// RAVEN BEGIN +// jsinger: There is no reason for us to be doing this on the Xenon +#ifndef _XENON + // test idMatX + //idMatX::Test(); + + // test idPolynomial + idPolynomial::Test(); +#endif +// RAVEN END + + // initialize the dictionary string pools + idDict::Init(); +} + +/* +================ +idLib::ShutDown +================ +*/ +void idLib::ShutDown( void ) { + + // shut down the dictionary string pools + idDict::Shutdown(); + + // shut down the string memory allocator + idStr::ShutdownMemory(); + + // shut down the SIMD engine + idSIMD::Shutdown(); + +// RAVEN BEGIN +// dluetscher: added the following code to shutdown each of the memory heaps immediately +// before Mem_Shutdown() +#ifdef _RV_MEM_SYS_SUPPORT + // shutdown each of the memory heaps + common->ShutdownHeaps(); +#endif +// RAVEN END + + // shut down the memory manager + Mem_Shutdown(); +} + + +/* +=============================================================================== + + Colors + +=============================================================================== +*/ + +idVec4 colorBlack = idVec4( 0.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorWhite = idVec4( 1.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorRed = idVec4( 1.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorGreen = idVec4( 0.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorBlue = idVec4( 0.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorYellow = idVec4( 1.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorMagenta= idVec4( 1.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorCyan = idVec4( 0.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorOrange = idVec4( 1.00f, 0.50f, 0.00f, 1.00f ); +idVec4 colorPurple = idVec4( 0.60f, 0.00f, 0.60f, 1.00f ); +idVec4 colorPink = idVec4( 0.73f, 0.40f, 0.48f, 1.00f ); +idVec4 colorBrown = idVec4( 0.40f, 0.35f, 0.08f, 1.00f ); +idVec4 colorLtGrey = idVec4( 0.75f, 0.75f, 0.75f, 1.00f ); +idVec4 colorMdGrey = idVec4( 0.50f, 0.50f, 0.50f, 1.00f ); +idVec4 colorDkGrey = idVec4( 0.25f, 0.25f, 0.25f, 1.00f ); + +static dword colorMask[2] = { 255, 0 }; + +/* +================ +ColorFloatToByte +================ +*/ +ID_INLINE static byte ColorFloatToByte( float c ) { + return (byte) ( ( (dword) ( c * 255.0f ) ) & colorMask[FLOATSIGNBITSET(c)] ); +} + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec4 &color ) { + dword dw, dx, dy, dz; + + dx = ColorFloatToByte( color.x ); + dy = ColorFloatToByte( color.y ); + dz = ColorFloatToByte( color.z ); + dw = ColorFloatToByte( color.w ); + +// RAVEN BEGIN +// jnewquist: Big endian support +#ifdef _LITTLE_ENDIAN + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ) | ( dw << 24 ); +#else + return ( dx << 24 ) | ( dy << 16 ) | ( dz << 8 ) | ( dw << 0 ); +#endif +// RAVEN END +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec4 &unpackedColor ) { +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 24 ) & 255 ) * ( 1.0f / 255.0f ) ); +#else + unpackedColor.Set( ( ( color >> 24 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ) ); +#endif +// RAVEN END +} + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec3 &color ) { + dword dx, dy, dz; + + dx = ColorFloatToByte( color.x ); + dy = ColorFloatToByte( color.y ); + dz = ColorFloatToByte( color.z ); + +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ); +#else + return ( dy << 16 ) | ( dz << 8 ) | ( dx << 0 ); +#endif +// RAVEN END +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec3 &unpackedColor ) { +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ) ); +#else + unpackedColor.Set( ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ) ); +#endif +// RAVEN END +} + +/* +=============== +idLib::Error +=============== +*/ +void idLib::Error( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Error( "%s", text ); +} + +/* +=============== +idLib::Warning +=============== +*/ +void idLib::Warning( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +=============================================================================== + + Byte order functions + +=============================================================================== +*/ + +// can't just use function pointers, or dll linkage can mess up +static short (*_BigShort)( short l ); +static short (*_LittleShort)( short l ); +static int (*_BigLong)( int l ); +static int (*_LittleLong)( int l ); +static float (*_BigFloat)( float l ); +static float (*_LittleFloat)( float l ); +static void (*_BigRevBytes)( void *bp, int elsize, int elcount ); +static void (*_LittleRevBytes)( void *bp, int elsize, int elcount ); +static void (*_SixtetsForInt)( byte *out, int src ); +static int (*_IntForSixtets)( byte *in ); + +#ifdef _LITTLE_ENDIAN + +short LittleShort( short l ) { return l; } +int LittleLong( int l ) { return l; } +float LittleFloat( float l ) { return l; } +void LittleRevBytes( void *bp, int elsize, int elcount ) {} + +short BigShort( short l ) { return _BigShort( l ); } +int BigLong( int l ) { return _BigLong( l ); } +float BigFloat( float l ) { return _BigFloat( l ); } +void BigRevBytes( void *bp, int elsize, int elcount ) { _BigRevBytes( bp, elsize, elcount ); } + +#else + +short LittleShort( short l ) { return _LittleShort( l ); } +int LittleLong( int l ) { return _LittleLong( l ); } +float LittleFloat( float l ) { return _LittleFloat( l ); } +void LittleRevBytes( void *bp, int elsize, int elcount ) { _LittleRevBytes( bp, elsize, elcount ); } + +short BigShort( short l ) { return l; } +int BigLong( int l ) { return l; } +float BigFloat( float l ) { return l; } +void BigRevBytes( void *bp, int elsize, int elcount ) {} + +#endif + + +void SixtetsForInt( byte *out, int src) { _SixtetsForInt( out, src ); } +int IntForSixtets( byte *in ) { return _IntForSixtets( in ); } + +/* +================ +ShortSwap +================ +*/ +short ShortSwap( short l ) { + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +/* +================ +ShortNoSwap +================ +*/ +short ShortNoSwap( short l ) { + return l; +} + +/* +================ +LongSwap +================ +*/ +int LongSwap ( int l ) { + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +/* +================ +LongNoSwap +================ +*/ +int LongNoSwap( int l ) { + return l; +} + +/* +================ +FloatSwap +================ +*/ +float FloatSwap( float f ) { + union { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +/* +================ +FloatNoSwap +================ +*/ +float FloatNoSwap( float f ) { + return f; +} + +/* +===================================================================== +RevBytesSwap + +Reverses byte order in place. + +INPUTS + bp bytes to reverse + elsize size of the underlying data type + elcount number of elements to swap + +RESULTS + Reverses the byte order in each of elcount elements. +===================================================================== */ +void RevBytesSwap( void *bp, int elsize, int elcount ) { + register unsigned char *p, *q; + + p = ( unsigned char * ) bp; + + if ( elsize == 2 ) { + q = p + 1; + while ( elcount-- ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + p += 2; + q += 2; + } + return; + } + + while ( elcount-- ) { + q = p + elsize - 1; + while ( p < q ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + ++p; + --q; + } + p += elsize >> 1; + } +} + +/* +================ +RevBytesNoSwap +================ +*/ +void RevBytesNoSwap( void *bp, int elsize, int elcount ) { + return; +} + +/* +================ +SixtetsForIntLittle +================ +*/ +void SixtetsForIntLittle( byte *out, int src) { + byte *b = (byte *)&src; + out[0] = ( b[0] & 0xfc ) >> 2; + out[1] = ( ( b[0] & 0x3 ) << 4 ) + ( ( b[1] & 0xf0 ) >> 4 ); + out[2] = ( ( b[1] & 0xf ) << 2 ) + ( ( b[2] & 0xc0 ) >> 6 ); + out[3] = b[2] & 0x3f; +} + +/* +================ +SixtetsForIntBig +TTimo: untested - that's the version from initial base64 encode +================ +*/ +void SixtetsForIntBig( byte *out, int src) { + for( int i = 0 ; i < 4 ; i++ ) { + out[i] = src & 0x3f; + src >>= 6; + } +} + +/* +================ +IntForSixtetsLittle +================ +*/ +int IntForSixtetsLittle( byte *in ) { + int ret = 0; + byte *b = (byte *)&ret; + b[0] |= in[0] << 2; + b[0] |= ( in[1] & 0x30 ) >> 4; + b[1] |= ( in[1] & 0xf ) << 4; + b[1] |= ( in[2] & 0x3c ) >> 2; + b[2] |= ( in[2] & 0x3 ) << 6; + b[2] |= in[3]; + return ret; +} + +/* +================ +IntForSixtetsBig +TTimo: untested - that's the version from initial base64 decode +================ +*/ +int IntForSixtetsBig( byte *in ) { + int ret = 0; + ret |= in[0]; + ret |= in[1] << 6; + ret |= in[2] << 2*6; + ret |= in[3] << 3*6; + return ret; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init( void ) { + byte swaptest[2] = {1,0}; + + // set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) { + // little endian ex: x86 + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + _BigRevBytes = RevBytesSwap; + _LittleRevBytes = RevBytesNoSwap; + _SixtetsForInt = SixtetsForIntLittle; + _IntForSixtets = IntForSixtetsLittle; + } else { + // big endian ex: ppc + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + _BigRevBytes = RevBytesNoSwap; + _LittleRevBytes = RevBytesSwap; + _SixtetsForInt = SixtetsForIntBig; + _IntForSixtets = IntForSixtetsBig; + } +} + +/* +========== +Swap_IsBigEndian +========== +*/ +bool Swap_IsBigEndian( void ) { + byte swaptest[2] = {1,0}; + return *(short *)swaptest != 1; +} + +/* +=============================================================================== + + Assertion + +=============================================================================== +*/ + +void AssertFailed( const char *file, int line, const char *expression ) { + if ( idLib::sys ) { + idLib::sys->DebugPrintf( "\n\nASSERTION FAILED!\n%s(%d): '%s'\n", file, line, expression ); + } +#ifdef _WIN32 +// RAVEN BEGIN +// jnewquist: Visual Studio platform independent breakpoint + __debugbreak(); +// RAVEN END +#elif defined( __linux__ ) + __asm__ __volatile__ ("int $0x03"); +#elif defined( MACOS_X ) + kill( getpid(), SIGINT ); +#endif +} diff --git a/source/idlib/Lib.h b/source/idlib/Lib.h new file mode 100644 index 0000000..dcec788 --- /dev/null +++ b/source/idlib/Lib.h @@ -0,0 +1,330 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + + +/* +=============================================================================== + + idLib contains stateless support classes and concrete types. Some classes + do have static variables, but such variables are initialized once and + read-only after initialization (they do not maintain a modifiable state). + + The interface pointers idSys, idCommon, idCVarSystem and idFileSystem + should be set before using idLib. The pointers stored here should not + be used by any part of the engine except for idLib. + + The frameNumber should be continuously set to the number of the current + frame if frame base memory logging is required. + +=============================================================================== +*/ + +class idLib { +public: + static class idSys * sys; + static class idCommon * common; + static class idCVarSystem * cvarSystem; + static class idFileSystem * fileSystem; + static int frameNumber; + + static void Init( void ); + static void ShutDown( void ); + + // wrapper to idCommon functions + static void Error( const char *fmt, ... ); + static void Warning( const char *fmt, ... ); +}; + + +/* +=============================================================================== + + Asserts and Exceptions + +=============================================================================== +*/ + +/* +The verify(x) macro just returns true or false in release mode, but breaks +in debug mode. That way the code can take a non-fatal path in release mode +if something that's not supposed to happen happens. + +if ( !verify(game) ) { + // This should never happen! + return; +} +*/ + +#ifdef _DEBUG + + #define ID_CONDITIONAL_ASSERT + + void AssertFailed( const char *file, int line, const char *expression ); + #undef assert + #ifdef ID_CONDITIONAL_ASSERT + // lets you disable an assertion at runtime when needed + // could extend this to count and produce an assert log - useful for 'release with asserts' builds + #define assert( x ) \ + { \ + volatile static bool assert_enabled = true; \ + if ( assert_enabled ) { \ + if ( x ) { } else AssertFailed( __FILE__, __LINE__, #x ); \ + } \ + } + #define verify( x ) \ + ( \ + ( ( x ) ? true : ( \ + ( { \ + volatile static bool assert_enabled = true; \ + if ( assert_enabled ) { AssertFailed( __FILE__, __LINE__, #x ); } \ + } ) \ + , false ) ) \ + ) + #else + #define assert( x ) if ( x ) { } else AssertFailed( __FILE__, __LINE__, #x ) + #define verify( x ) ( ( x ) ? true : ( AssertFailed( __FILE__, __LINE__, #x ), false ) ) + #endif + +#else + + #undef assert + #define assert( x ) + #define verify( x ) ( ( x ) ? true : false ) + +#endif + +#define assert_8_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&7) == 0 ); +#define assert_16_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&15) == 0 ); +#define assert_32_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&31) == 0 ); + +#ifndef __TYPE_INFO_GEN__ +#define compile_time_assert( x ) { typedef int compile_time_assert_failed[(x) ? 1 : -1]; } +#define file_scoped_compile_time_assert( x ) extern int compile_time_assert_failed[(x) ? 1 : -1] +#define assert_sizeof( type, size ) file_scoped_compile_time_assert( sizeof( type ) == size ) +#define assert_offsetof( type, field, offset ) file_scoped_compile_time_assert( offsetof( type, field ) == offset ) +#define assert_sizeof_8_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 8 ) == 0 ) +#define assert_sizeof_16_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 15 ) == 0 ) +#define assert_sizeof_32_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 31 ) == 0 ) +#else +#define compile_time_assert( x ) +#define file_scoped_compile_time_assert( x ) +#define assert_sizeof( type, size ) +#define assert_offsetof( type, field, offset ) +#define assert_sizeof_16_byte_multiple( type ) +#endif + +class idException { +public: + char error[2048]; + + idException( const char *text = "" ) { strcpy( error, text ); } +}; + + +/* +=============================================================================== + + Types and defines used throughout the engine. + +=============================================================================== +*/ + +typedef unsigned char byte; // 8 bits +typedef unsigned short word; // 16 bits +typedef unsigned int dword; // 32 bits +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef int qhandle_t; + +class idFile; +class idVec3; +#ifdef _XENON +#define ID_VEC4_ALIGN __declspec(align(16)) +#else +#define ID_VEC4_ALIGN +#endif +class ID_VEC4_ALIGN idVec4; + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef BIT +#define BIT( num ) ( 1 << ( num ) ) +#endif + +#define MAX_STRING_CHARS 1024 // max length of a string + +// maximum world size +#define MAX_WORLD_COORD ( 128 * 1024 ) +#define MIN_WORLD_COORD ( -128 * 1024 ) +#define MAX_WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +// basic colors +extern ID_VEC4_ALIGN idVec4 colorBlack; +extern ID_VEC4_ALIGN idVec4 colorWhite; +extern ID_VEC4_ALIGN idVec4 colorRed; +extern ID_VEC4_ALIGN idVec4 colorGreen; +extern ID_VEC4_ALIGN idVec4 colorBlue; +extern ID_VEC4_ALIGN idVec4 colorYellow; +extern ID_VEC4_ALIGN idVec4 colorMagenta; +extern ID_VEC4_ALIGN idVec4 colorCyan; +extern ID_VEC4_ALIGN idVec4 colorOrange; +extern ID_VEC4_ALIGN idVec4 colorPurple; +extern ID_VEC4_ALIGN idVec4 colorPink; +extern ID_VEC4_ALIGN idVec4 colorBrown; +extern ID_VEC4_ALIGN idVec4 colorLtGrey; +extern ID_VEC4_ALIGN idVec4 colorMdGrey; +extern ID_VEC4_ALIGN idVec4 colorDkGrey; + +// packs color floats in the range [0,1] into an integer +dword PackColor( const idVec3 &color ); +void UnpackColor( const dword color, idVec3 &unpackedColor ); +dword PackColor( const idVec4 &color ); +void UnpackColor( const dword color, idVec4 &unpackedColor ); + +// little/big endian conversion +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); +void BigRevBytes( void *bp, int elsize, int elcount ); +void LittleRevBytes( void *bp, int elsize, int elcount ); +void Swap_Init( void ); + +bool Swap_IsBigEndian( void ); + +// for base64 +void SixtetsForInt( byte *out, int src); +int IntForSixtets( byte *in ); + + +/* +=============================================================================== + + idLib headers. + +=============================================================================== +*/ + +#include "math/Math.h" + +// memory management and arrays +#include "Heap.h" + +// RAVEN BEGIN +// dluetscher: added includes for new heap/memory management system +#ifdef _RV_MEM_SYS_SUPPORT +#include "rvHeapArena.h" +#include "rvHeap.h" +#include "rvMemSys.h" +#endif +// RAVEN END + +#include "containers/List.h" + +// math +#include "math/Simd.h" +#include "math/Random.h" +#include "math/Complex.h" +#include "math/Vector.h" +#include "math/Matrix.h" +#include "math/Mat3x4.h" +#include "math/Angles.h" +#include "math/Quat.h" +#include "math/Rotation.h" +#include "math/Plane.h" +#include "math/Pluecker.h" +#include "math/Polynomial.h" +#include "math/Extrapolate.h" +#include "math/Interpolate.h" +#include "math/Curve.h" +#include "math/Ode.h" +#include "math/Lcp.h" +// RAVEN BEGIN +#include "math/Radians.h" +#include "math/FFT.h" +// RAVEN END + +// bounding volumes +#include "bv/Sphere.h" +#include "bv/Bounds.h" +#include "bv/Box.h" +#include "bv/Frustum.h" + +// geometry +#include "geometry/DrawVert.h" +#include "geometry/JointTransform.h" +#include "geometry/Winding.h" +#include "geometry/Winding2D.h" +#include "geometry/Surface.h" +#include "geometry/Surface_Patch.h" +#include "geometry/Surface_Polytope.h" +#include "geometry/Surface_SweptSpline.h" +#include "geometry/TraceModel.h" + +// RAVEN BEGIN +// dluetscher: added some headers for new vertex formats +#ifdef _MD5R_SUPPORT +#include "geometry/rvVertex.h" +#endif +// RAVEN END + +// text manipulation +#include "Str.h" +#include "Token.h" +#include "Lexer.h" +#include "Parser.h" +#include "Base64.h" +#include "CmdArgs.h" + +// containers +#include "containers/BTree.h" +#include "containers/BinSearch.h" +#include "containers/HashIndex.h" +#include "containers/HashTable.h" +#include "containers/StaticList.h" +#include "containers/LinkList.h" +#include "containers/Hierarchy.h" +#include "containers/Queue.h" +#include "containers/Stack.h" +#include "containers/StrList.h" +#include "containers/StrPool.h" +#include "containers/VectorSet.h" +#include "containers/PlaneSet.h" +#include "containers/rvBlockPool.h" +// RAVEN BEGIN +// ddynerman: a pair +#include "containers/Pair.h" +// ddynerman: algorithms +#include "algorithms/MultifieldSort.h" +// RAVEN END + +// hashing +#include "hashing/CRC8.h" +#include "hashing/CRC16.h" +#include "hashing/CRC32.h" +#include "hashing/Honeyman.h" +#include "hashing/MD4.h" +#include "hashing/MD5.h" + +// misc +#include "Dict.h" +#include "LangDict.h" +#include "BitMsg.h" +#include "MapFile.h" +#include "Timer.h" +// RAVEN BEGIN +// jsinger: xenon needs these because the simd class is no longer virtual +#ifdef _XENON +#include "math/Simd_generic.h" +#include "math/Simd_Xenon.h" +#include "Threads/ThreadEventManager.h" +#include "Threads/WorkerThreadManager.h" +#endif +// RAVEN END + +#endif /* !__LIB_H__ */ diff --git a/source/idlib/MapFile.h b/source/idlib/MapFile.h new file mode 100644 index 0000000..336d589 --- /dev/null +++ b/source/idlib/MapFile.h @@ -0,0 +1,267 @@ + +#ifndef __MAPFILE_H__ +#define __MAPFILE_H__ + +/* +=============================================================================== + + Reads or writes the contents of .map files into a standard internal + format, which can then be moved into private formats for collision + detection, map processing, or editor use. + + No validation (duplicate planes, null area brushes, etc) is performed. + There are no limits to the number of any of the elements in maps. + The order of entities, brushes, and sides is maintained. + +=============================================================================== +*/ + +const int OLD_MAP_VERSION = 1; +const int CURRENT_MAP_VERSION = 3; +const int DEFAULT_CURVE_SUBDIVISION = 4; +const float DEFAULT_CURVE_MAX_ERROR = 4.0f; +const float DEFAULT_CURVE_MAX_ERROR_CD = 24.0f; +const float DEFAULT_CURVE_MAX_LENGTH = -1.0f; +const float DEFAULT_CURVE_MAX_LENGTH_CD = -1.0f; + + +class idMapPrimitive { +public: + enum { TYPE_INVALID = -1, TYPE_BRUSH, TYPE_PATCH }; + + idDict epairs; + + idMapPrimitive( void ) { type = TYPE_INVALID; } + virtual ~idMapPrimitive( void ) { } + int GetType( void ) const { return type; } + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ) { } +// RAVEN END + +protected: + int type; +}; + + +class idMapBrushSide { + friend class idMapBrush; + +public: + idMapBrushSide( void ); + ~idMapBrushSide( void ) { } + const char * GetMaterial( void ) const { return material; } + void SetMaterial( const char *p ) { material = p; } + const idPlane & GetPlane( void ) const { return plane; } + void SetPlane( const idPlane &p ) { plane = p; } + void SetTextureMatrix( const idVec3 mat[2] ) { texMat[0] = mat[0]; texMat[1] = mat[1]; } + void GetTextureMatrix( idVec3 &mat1, idVec3 &mat2 ) { mat1 = texMat[0]; mat2 = texMat[1]; } + void GetTextureVectors( idVec4 v[2] ) const; + +protected: + idStr material; + idPlane plane; + idVec3 texMat[2]; + idVec3 origin; +}; + +ID_INLINE idMapBrushSide::idMapBrushSide( void ) { + plane.Zero(); + texMat[0].Zero(); + texMat[1].Zero(); + origin.Zero(); +} + + +class idMapBrush : public idMapPrimitive { +public: + idMapBrush( void ) { type = TYPE_BRUSH; sides.Resize( 8, 4 ); } + ~idMapBrush( void ) { sides.DeleteContents( true ); } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapBrush * Parse( Lexer &src, const idVec3 &origin, bool newFormat = true, int version = CURRENT_MAP_VERSION ); + static idMapBrush * ParseQ3( Lexer &src, const idVec3 &origin ); +// RAVEN END + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + int GetNumSides( void ) const { return sides.Num(); } + int AddSide( idMapBrushSide *side ) { return sides.Append( side ); } + idMapBrushSide * GetSide( int i ) const { return sides[i]; } + unsigned int GetGeometryCRC( void ) const; + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ); +// RAVEN END + +protected: + idList sides; +}; + + +class idMapPatch : public idMapPrimitive, public idSurface_Patch { +public: + idMapPatch( void ); + idMapPatch( int maxPatchWidth, int maxPatchHeight ); + ~idMapPatch( void ) { } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapPatch * Parse( Lexer &src, const idVec3 &origin, bool patchDef3 = true, int version = CURRENT_MAP_VERSION ); +// RAVEN END + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + const char * GetMaterial( void ) const { return material; } + void SetMaterial( const char *p ) { material = p; } + int GetHorzSubdivisions( void ) const { return horzSubdivisions; } + int GetVertSubdivisions( void ) const { return vertSubdivisions; } + bool GetExplicitlySubdivided( void ) const { return explicitSubdivisions; } + void SetHorzSubdivisions( int n ) { horzSubdivisions = n; } + void SetVertSubdivisions( int n ) { vertSubdivisions = n; } + void SetExplicitlySubdivided( bool b ) { explicitSubdivisions = b; } + unsigned int GetGeometryCRC( void ) const; + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ); +// RAVEN END + +protected: + idStr material; + int horzSubdivisions; + int vertSubdivisions; + bool explicitSubdivisions; +}; + +ID_INLINE idMapPatch::idMapPatch( void ) { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxHeight = 0; + expanded = false; +} + +ID_INLINE idMapPatch::idMapPatch( int maxPatchWidth, int maxPatchHeight ) { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + + +class idMapEntity { + friend class idMapFile; + +public: + idDict epairs; + +public: + idMapEntity( void ) { epairs.SetHashSize( 64 ); } + ~idMapEntity( void ) { primitives.DeleteContents( true ); } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapEntity * Parse( Lexer &src, bool worldSpawn = false, int version = CURRENT_MAP_VERSION ); +// RAVEN END + bool Write( idFile *fp, int entityNum ) const; + int GetNumPrimitives( void ) const { return primitives.Num(); } + idMapPrimitive * GetPrimitive( int i ) const { return primitives[i]; } + void AddPrimitive( idMapPrimitive *p ) { primitives.Append( p ); } + unsigned int GetGeometryCRC( void ) const; + void RemovePrimitiveData(); + +protected: + idList primitives; +}; + + +class idMapFile { +public: + idMapFile( void ); +// RAVEN BEGIN +// rhummer: moved body to mapfile.cpp to help debug some odd issues with func_groups + ~idMapFile( void ); /*{ entities.DeleteContents( true ); }*/ +// RAVEN END + + // filename does not require an extension + // normally this will use a .reg file instead of a .map file if it exists, + // which is what the game and dmap want, but the editor will want to always + // load a .map file + bool Parse( const char *filename, bool ignoreRegion = false, bool osPath = false ); + +// RAVEN BEGIN +// rjohnson: added resolve + void Resolve( void ); +// rhummer: added boolean to dictate if the Resolve function has been run on this map. + bool HasBeenResloved() { return mHasBeenResolved; } +// rjohnson: added export + bool Write( const char *fileName, const char *ext, bool fromBasePath = true, bool exportOnly = false ); +// RAVEN END + + // get the number of entities in the map + int GetNumEntities( void ) const { return entities.Num(); } + // get the specified entity + idMapEntity * GetEntity( int i ) const { return entities[i]; } + // get the name without file extension + const char * GetName( void ) const { return name; } + // get the file time + unsigned int GetFileTime( void ) const { return fileTime; } + // get CRC for the map geometry + // texture coordinates and entity key/value pairs are not taken into account + unsigned int GetGeometryCRC( void ) const { return geometryCRC; } + // returns true if the file on disk changed + bool NeedsReload(); + + int AddEntity( idMapEntity *mapentity ); + idMapEntity * FindEntity( const char *name ); + void RemoveEntity( idMapEntity *mapEnt ); + void RemoveEntities( const char *classname ); + void RemoveAllEntities(); + void RemovePrimitiveData(); + bool HasPrimitiveData() { return hasPrimitiveData; } + +// RAVEN BEGIN +// rjohnson: added export + bool WriteExport( const char *fileName, bool fromBasePath = true ); + bool ParseExport( const char *filename, bool osPath = false ); + + bool HasExportEntities(void) { return mHasExportEntities; } +// RAVEN END + +protected: + int version; + unsigned int fileTime; + unsigned int geometryCRC; + idList entities; + idStr name; + bool hasPrimitiveData; + +// RAVEN BEGIN +// rjohnson: added export + idList mExportEntities; + bool mHasExportEntities; +// rhummer: Added to inform if func_groups some how disappeared between the loading and saving of the map file. + bool mHasFuncGroups; +// rhummer: Added to notify that this map has been resloved, so func_groups have been removed. + bool mHasBeenResolved; +// RAVEN END + +private: + void SetGeometryCRC( void ); +}; + +ID_INLINE idMapFile::idMapFile( void ) { + version = CURRENT_MAP_VERSION; + fileTime = 0; + geometryCRC = 0; + entities.Resize( 1024, 256 ); + hasPrimitiveData = false; +// RAVEN BEGIN +// rhummer: Used to make sure func_groups don't disappear. + mHasFuncGroups = false; +// RAVEN END +} + +#endif /* !__MAPFILE_H__ */ diff --git a/source/idlib/Parser.cpp b/source/idlib/Parser.cpp new file mode 100644 index 0000000..4686eef --- /dev/null +++ b/source/idlib/Parser.cpp @@ -0,0 +1,3220 @@ + +#include "precompiled.h" +#pragma hdrstop + +//#define DEBUG_EVAL +#define MAX_DEFINEPARMS 128 +#define DEFINEHASHSIZE 2048 + +#define TOKEN_FL_RECURSIVE_DEFINE 1 + +define_t * idParser::globaldefines; + +/* +================ +idParser::SetBaseFolder +================ +*/ +void idParser::SetBaseFolder( const char *path) { +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + Lexer::SetBaseFolder(path); +// RAVEN END +} + +/* +================ +idParser::AddGlobalDefine +================ +*/ +int idParser::AddGlobalDefine( const char *string ) { + define_t *define; + + define = idParser::DefineFromString(string); + if (!define) { + return false; + } + define->next = globaldefines; + globaldefines = define; + return true; +} + +/* +================ +idParser::RemoveGlobalDefine +================ +*/ +int idParser::RemoveGlobalDefine( const char *name ) { + define_t *d, *prev; + + for ( prev = NULL, d = idParser::globaldefines; d; prev = d, d = d->next ) { + if ( !idStr::Cmp( d->name, name ) ) { + break; + } + } + if ( d ) { + if ( prev ) { + prev->next = d->next; + } + else { + idParser::globaldefines = d->next; + } + idParser::FreeDefine( d ); + return true; + } + return false; +} + +/* +================ +idParser::RemoveAllGlobalDefines +================ +*/ +void idParser::RemoveAllGlobalDefines( void ) { + define_t *define; + + for ( define = globaldefines; define; define = globaldefines ) { + globaldefines = globaldefines->next; + idParser::FreeDefine(define); + } +} + + +/* +=============================================================================== + +idParser + +=============================================================================== +*/ + +/* +================ +idParser::PrintDefine +================ +*/ +void idParser::PrintDefine( define_t *define ) { + idLib::common->Printf("define->name = %s\n", define->name); + idLib::common->Printf("define->flags = %d\n", define->flags); + idLib::common->Printf("define->builtin = %d\n", define->builtin); + idLib::common->Printf("define->numparms = %d\n", define->numparms); +} + +/* +================ +PC_PrintDefineHashTable +================ +* / +static void PC_PrintDefineHashTable(define_t **definehash) { + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) { + Log_Write(" %s", d->name); + } + Log_Write("\n"); + } +} +*/ + +/* +================ +PC_NameHash +================ +*/ +ID_INLINE int PC_NameHash( const char *name ) { + int hash, i; + + hash = 0; + for ( i = 0; name[i] != '\0'; i++ ) { + hash += name[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} + +/* +================ +idParser::AddDefineToHash +================ +*/ +void idParser::AddDefineToHash( define_t *define, define_t **definehash ) { + int hash; + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} + +/* +================ +FindHashedDefine +================ +*/ +define_t *idParser::FindHashedDefine( define_t **definehash, const char *name ) { + define_t *d; + int hash; + + hash = PC_NameHash(name); + for ( d = definehash[hash]; d; d = d->hashnext ) { + if ( !idStr::Cmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefine +================ +*/ +define_t *idParser::FindDefine( define_t *defines, const char *name ) { + define_t *d; + + for ( d = defines; d; d = d->next ) { + if ( !idStr::Cmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefineParm +================ +*/ +int idParser::FindDefineParm( define_t *define, const char *name ) { + idToken *p; + int i; + + i = 0; + for ( p = define->parms; p; p = p->next ) { + if ( (*p) == name ) { + return i; + } + i++; + } + return -1; +} + +/* +================ +idParser::CopyDefine +================ +*/ +define_t *idParser::CopyDefine( define_t *define ) { + define_t *newdefine; + idToken *token, *newtoken, *lasttoken; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + newdefine = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(define->name) + 1, MA_PARSER); +//RAVEN END + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) { + newtoken = new idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) { + newtoken = new idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } + return newdefine; +} + +/* +================ +idParser::FreeDefine +================ +*/ +void idParser::FreeDefine( define_t *define ) { + idToken *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) { + next = t->next; + delete t; + } + //free the define tokens + for (t = define->tokens; t; t = next) { + next = t->next; + delete t; + } + //free the define + Mem_Free( define ); +} + +/* +================ +idParser::DefineFromString +================ +*/ +define_t *idParser::DefineFromString( const char *string ) { + idParser src; + define_t *def; + + if ( !src.LoadMemory(string, strlen(string), "*defineString") ) { + return NULL; + } + // create a define from the source + if ( !src.Directive_define() ) { + src.FreeSource(); + return NULL; + } + def = src.CopyFirstDefine(); + src.FreeSource(); + //if the define was created succesfully + return def; +} + +/* +================ +idParser::Error +================ +*/ +void idParser::Error( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Error( text ); + } +} + +/* +================ +idParser::Warning +================ +*/ +void idParser::Warning( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Warning( text ); + } +} + +/* +================ +idParser::PushIndent +================ +*/ +void idParser::PushIndent( int type, int skip ) { + indent_t *indent; + +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + indent = (indent_t *) Mem_Alloc(sizeof(indent_t),MA_PARSER); +//RAVEN END + indent->type = type; + indent->script = idParser::scriptstack; + indent->skip = (skip != 0); + idParser::skip += indent->skip; + indent->next = idParser::indentstack; + idParser::indentstack = indent; +} + +/* +================ +idParser::PopIndent +================ +*/ +void idParser::PopIndent( int *type, int *skip ) { + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = idParser::indentstack; + if (!indent) return; + + // must be an indent from the current script + if (idParser::indentstack->script != idParser::scriptstack) { + return; + } + + *type = indent->type; + *skip = indent->skip; + idParser::indentstack = idParser::indentstack->next; + idParser::skip -= indent->skip; + Mem_Free( indent ); +} + +/* +================ +idParser::PushScript +================ +*/ +void idParser::PushScript( idLexer *script ) { + idLexer *s; + + for ( s = idParser::scriptstack; s; s = s->next ) { + if ( !idStr::Icmp(s->GetFileName(), script->GetFileName()) ) { + idParser::Warning( "'%s' recursively included", script->GetFileName() ); + return; + } + } + //push the script on the script stack + script->next = idParser::scriptstack; + idParser::scriptstack = script; +} + +/* +================ +idParser::ReadSourceToken +================ +*/ +int idParser::ReadSourceToken( idToken *token ) { + idToken *t; + idLexer *script; + int type, skip, changedScript; + + if ( !idParser::scriptstack ) { + idLib::common->FatalError( "idParser::ReadSourceToken: not loaded" ); + return false; + } + changedScript = 0; + // if there's no token already available + while( !idParser::tokens ) { + // if there's a token to read from the script + if ( idParser::scriptstack->ReadToken( token ) ) { + token->linesCrossed += changedScript; + + // set the marker based on the start of the token read in + if ( !marker_p ) { + marker_p = token->whiteSpaceEnd_p; + } + return true; + } + // if at the end of the script + if ( idParser::scriptstack->EndOfFile() ) { + // remove all indents of the script + while( idParser::indentstack && idParser::indentstack->script == idParser::scriptstack ) { + idParser::Warning( "missing #endif" ); + idParser::PopIndent( &type, &skip ); + } + changedScript = 1; + } + // if this was the initial script + if ( !idParser::scriptstack->next ) { + return false; + } + // remove the script and return to the previous one + script = idParser::scriptstack; + idParser::scriptstack = idParser::scriptstack->next; + delete script; + } + // copy the already available token + *token = idParser::tokens; + // remove the token from the source + t = idParser::tokens; + idParser::tokens = idParser::tokens->next; + delete t; + return true; +} + +/* +================ +idParser::UnreadSourceToken +================ +*/ +int idParser::UnreadSourceToken( idToken *token ) { + idToken *t; + + t = new idToken(token); + t->next = idParser::tokens; + idParser::tokens = t; + return true; +} + +/* +================ +idParser::ReadDefineParms +================ +*/ +int idParser::ReadDefineParms( define_t *define, idToken **parms, int maxparms ) { + define_t *newdefine; + idToken token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + + if ( define->numparms > maxparms ) { + idParser::Error( "define with more than %d parameters", maxparms ); + return false; + } + + for ( i = 0; i < define->numparms; i++ ) { + parms[i] = NULL; + } + // if no leading "(" + if ( token != "(" ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + // read the define parameters + for ( done = 0, numparms = 0, indent = 1; !done; ) { + if ( numparms >= maxparms ) { + idParser::Error( "define '%s' with too many parameters", define->name ); + return false; + } + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while( !done ) { + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' incomplete", define->name ); + return false; + } + + if ( token == "," ) { + if ( indent <= 1 ) { + if ( lastcomma ) { + idParser::Warning( "too many comma's" ); + } + if ( numparms >= define->numparms ) { + idParser::Warning( "too many define parameters" ); + } + lastcomma = 1; + break; + } + } + else if ( token == "(" ) { + indent++; + } + else if ( token == ")" ) { + indent--; + if ( indent <= 0 ) { + if ( !parms[define->numparms-1] ) { + idParser::Warning( "too few define parameters" ); + } + done = 1; + break; + } + } + else if ( token.type == TT_NAME ) { + newdefine = FindHashedDefine( idParser::definehash, token.c_str() ); + if ( newdefine ) { + if ( !idParser::ExpandDefineIntoSource( &token, newdefine ) ) { + return false; + } + continue; + } + } + + lastcomma = 0; + + if ( numparms < define->numparms ) { + + t = new idToken( token ); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } + } + numparms++; + } + return true; +} + +/* +================ +idParser::StringizeTokens +================ +*/ +int idParser::StringizeTokens( idToken *tokens, idToken *token ) { + idToken *t; + + token->type = TT_STRING; + token->whiteSpaceStart_p = NULL; + token->whiteSpaceEnd_p = NULL; + (*token) = ""; + for ( t = tokens; t; t = t->next ) { + token->Append( t->c_str() ); + } + return true; +} + +/* +================ +idParser::MergeTokens +================ +*/ +int idParser::MergeTokens( idToken *t1, idToken *t2 ) { + // merging of a name with a name or number + if ( t1->type == TT_NAME && (t2->type == TT_NAME || (t2->type == TT_NUMBER && !(t2->subtype & TT_FLOAT))) ) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two numbers + if ( t1->type == TT_NUMBER && t2->type == TT_NUMBER && + !(t1->subtype & (TT_HEX|TT_BINARY)) && !(t2->subtype & (TT_HEX|TT_BINARY)) && + (!(t1->subtype & TT_FLOAT) || !(t2->subtype & TT_FLOAT)) ) { + t1->Append( t2->c_str() ); + return true; + } + + return false; +} + +/* +================ +idParser::AddBuiltinDefines +================ +*/ +void idParser::AddBuiltinDefines( void ) { + int i; + define_t *define; + struct builtin + { + char *string; + int id; + } builtin[] = { + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, + { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) { +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + define = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(builtin[i].string) + 1,MA_PARSER); +//RAVEN END + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, builtin[i].string); + define->flags = DEFINE_FIXED; + define->builtin = builtin[i].id; + define->numparms = 0; + define->parms = NULL; + define->tokens = NULL; + // add the define to the source + AddDefineToHash(define, idParser::definehash); + } +} + +/* +================ +idParser::CopyFirstDefine +================ +*/ +define_t *idParser::CopyFirstDefine( void ) { + int i; + + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + if ( idParser::definehash[i] ) { + return CopyDefine(idParser::definehash[i]); + } + } + return NULL; +} + +/* +================ +idParser::ExpandBuiltinDefine +================ +*/ +int idParser::ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *token; + time_t t; + char *curtime; + char buf[MAX_STRING_CHARS]; + + token = new idToken(deftoken); + switch( define->builtin ) { + case BUILTIN_LINE: { + sprintf( buf, "%d", deftoken->line ); + (*token) = buf; + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER | TT_VALUESVALID; + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_FILE: { + (*token) = idParser::scriptstack->GetFileName(); + token->type = TT_NAME; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_DATE: { + t = time(NULL); + curtime = ctime(&t); + (*token) = "\""; + token->Append( curtime+4 ); + token[7] = '\0'; + token->Append( curtime+20 ); + token[10] = '\0'; + token->Append( "\"" ); + free(curtime); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_TIME: { + t = time(NULL); + curtime = ctime(&t); + (*token) = "\""; + token->Append( curtime+11 ); + token[8] = '\0'; + token->Append( "\"" ); + free(curtime); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_STDC: { + idParser::Warning( "__STDC__ not supported\n" ); + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + default: { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + } + return true; +} + +/* +================ +idParser::ExpandDefine +================ +*/ +int idParser::ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + idToken *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + // if it is a builtin define + if ( define->builtin ) { + return idParser::ExpandBuiltinDefine( deftoken, define, firsttoken, lasttoken ); + } + // if the define has parameters + if ( define->numparms ) { + if ( !idParser::ReadDefineParms( define, parms, MAX_DEFINEPARMS ) ) { + return false; + } +#ifdef DEBUG_EVAL + for ( i = 0; i < define->numparms; i++ ) { + Log_Write("define parms %d:", i); + for ( pt = parms[i]; pt; pt = pt->next ) { + Log_Write( "%s", pt->c_str() ); + } + } +#endif //DEBUG_EVAL + } + // empty list at first + first = NULL; + last = NULL; + // create a list with tokens of the expanded define + for ( dt = define->tokens; dt; dt = dt->next ) { + parmnum = -1; + // if the token is a name, it could be a define parameter + if ( dt->type == TT_NAME ) { + parmnum = FindDefineParm( define, dt->c_str() ); + } + // if it is a define parameter + if ( parmnum >= 0 ) { + for ( pt = parms[parmnum]; pt; pt = pt->next ) { + t = new idToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + else { + // if stringizing operator + if ( (*dt) == "#" ) { + // the stringizing operator must be followed by a define parameter + if ( dt->next ) { + parmnum = FindDefineParm( define, dt->next->c_str() ); + } + else { + parmnum = -1; + } + + if ( parmnum >= 0 ) { + // step over the stringizing operator + dt = dt->next; + // stringize the define parameter tokens + if ( !idParser::StringizeTokens( parms[parmnum], &token ) ) { + idParser::Error( "can't stringize tokens" ); + return false; + } + t = new idToken(token); + t->line = deftoken->line; + } + else { + idParser::Warning( "stringizing operator without define parameter" ); + continue; + } + } + else { + t = new idToken(dt); + t->line = deftoken->line; + } + // add the token to the list + t->next = NULL; +// the token being read from the define list should use the line number of +// the original file, not the header file + t->line = deftoken->line; + + if ( last ) last->next = t; + else first = t; + last = t; + } + } + // check for the merging operator + for ( t = first; t; ) { + if ( t->next ) { + // if the merging operator + if ( (*t->next) == "##" ) { + t1 = t; + t2 = t->next->next; + if ( t2 ) { + if ( !idParser::MergeTokens( t1, t2 ) ) { + idParser::Error( "can't merge '%s' with '%s'", t1->c_str(), t2->c_str() ); + return false; + } + delete t1->next; + t1->next = t2->next; + if ( t2 == last ) last = t1; + delete t2; + continue; + } + } + } + t = t->next; + } + // store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + // free all the parameter tokens + for ( i = 0; i < define->numparms; i++ ) { + for ( pt = parms[i]; pt; pt = nextpt ) { + nextpt = pt->next; + delete pt; + } + } + + return true; +} + +/* +================ +idParser::ExpandDefineIntoSource +================ +*/ +int idParser::ExpandDefineIntoSource( idToken *deftoken, define_t *define ) { + idToken *firsttoken, *lasttoken; + + if ( !idParser::ExpandDefine( deftoken, define, &firsttoken, &lasttoken ) ) { + return false; + } + // if the define is not empty + if ( firsttoken && lasttoken ) { + firsttoken->linesCrossed += deftoken->linesCrossed; + lasttoken->next = idParser::tokens; + idParser::tokens = firsttoken; + } + return true; +} + +/* +================ +idParser::ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is found +================ +*/ +int idParser::ReadLine( idToken *token ) { + int crossline; + + crossline = 0; + do { + if (!idParser::ReadSourceToken( token )) { + return false; + } + + if (token->linesCrossed > crossline) { + idParser::UnreadSourceToken( token ); + return false; + } + crossline = 1; + } while( (*token) == "\\" ); + return true; +} + +/* +================ +idParser::Directive_include +================ +*/ +int idParser::Directive_include( void ) { + idLexer *script; + idToken token; + idStr path; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.linesCrossed > 0 ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.type == TT_STRING ) { + script = new idLexer; + // try relative to the current file + path = scriptstack->GetFileName(); + path.StripFilename(); +// RAVEN BEGIN +// jscott: avoid the leading slash + if( path.Length() ) + { + path += "/"; + } +// RAVEN END + path += token; + if ( !script->LoadFile( path, OSPath ) ) { + // try absolute path + path = token; + if ( !script->LoadFile( path, OSPath ) ) { + // try from the include path + path = includepath + token; + if ( !script->LoadFile( path, OSPath ) ) { + delete script; + script = NULL; + } + } + } + } + else if ( token.type == TT_PUNCTUATION && token == "<" ) { + path = idParser::includepath; + while( idParser::ReadSourceToken( &token ) ) { + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( token.type == TT_PUNCTUATION && token == ">" ) { + break; + } + path += token; + } + if ( token != ">" ) { + idParser::Warning( "#include missing trailing >" ); + } + if ( !path.Length() ) { + idParser::Error( "#include without file name between < >" ); + return false; + } + if ( idParser::flags & LEXFL_NOBASEINCLUDES ) { + return true; + } + script = new idLexer; + if ( !script->LoadFile( includepath + path, OSPath ) ) { + delete script; + script = NULL; + } + } + else { + idParser::Error( "#include without file name" ); + return false; + } + if (!script) { + idParser::Error( "file '%s' not found", path.c_str() ); + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + idParser::PushScript( script ); + return true; +} + +/* +================ +idParser::Directive_undef +================ +*/ +int idParser::Directive_undef( void ) { + idToken token; + define_t *define, *lastdefine; + int hash; + + // + if (!idParser::ReadLine( &token )) { + idParser::Error( "undef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name but found '%s'", token.c_str() ); + return false; + } + + hash = PC_NameHash( token.c_str() ); + for (lastdefine = NULL, define = idParser::definehash[hash]; define; define = define->hashnext) { + if (!idStr::Cmp(define->name, token.c_str())) + { + if (define->flags & DEFINE_FIXED) { + idParser::Warning( "can't undef '%s'", token.c_str() ); + } + else { + if (lastdefine) { + lastdefine->hashnext = define->hashnext; + } + else { + idParser::definehash[hash] = define->hashnext; + } + FreeDefine(define); + } + break; + } + lastdefine = define; + } + return true; +} + +/* +================ +idParser::Directive_define +================ +*/ +int idParser::Directive_define( void ) { + idToken token, *t, *last; + define_t *define; + + if (!idParser::ReadLine( &token )) { + idParser::Error( "#define without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #define, found '%s'", token.c_str() ); + return false; + } + // check if the define already exists + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (define) { + if (define->flags & DEFINE_FIXED) { + idParser::Error( "can't redefine '%s'", token.c_str() ); + return false; + } + idParser::Warning( "redefinition of '%s'", token.c_str() ); + // unread the define name before executing the #undef directive + idParser::UnreadSourceToken( &token ); + if (!idParser::Directive_undef()) + return false; + // if the define was not removed (define->flags & DEFINE_FIXED) + define = FindHashedDefine(idParser::definehash, token.c_str()); + } + // allocate define +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + define = (define_t *) Mem_ClearedAlloc(sizeof(define_t) + token.Length() + 1, MA_PARSER); +//RAVEN END + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.c_str()); + // add the define to the source + AddDefineToHash(define, idParser::definehash); + // if nothing is defined, just return + if ( !idParser::ReadLine( &token ) ) { + return true; + } + // if it is a define with parameters + if ( token.WhiteSpaceBeforeToken() == 0 && token == "(" ) { + // read the define parameters + last = NULL; + if ( !idParser::CheckTokenString(")") ) { + while(1) { + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "expected define parameter" ); + return false; + } + // if it isn't a name + if (token.type != TT_NAME) { + idParser::Error( "invalid define parameter" ); + return false; + } + + if (FindDefineParm(define, token.c_str()) >= 0) { + idParser::Error( "two the same define parameters" ); + return false; + } + // add the define parm + t = new idToken(token); + t->ClearTokenWhiteSpace(); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + // read next token + if (!idParser::ReadLine( &token )) { + idParser::Error( "define parameters not terminated" ); + return false; + } + + if ( token == ")" ) { + break; + } + // then it must be a comma + if ( token != "," ) { + idParser::Error( "define not terminated" ); + return false; + } + } + } + if ( !idParser::ReadLine( &token ) ) { + return true; + } + } + // read the defined stuff + last = NULL; + do + { + t = new idToken(token); + if ( t->type == TT_NAME && !idStr::Cmp( t->c_str(), define->name ) ) { + t->flags |= TOKEN_FL_RECURSIVE_DEFINE; + idParser::Warning( "recursive define (removed recursion)" ); + } + t->ClearTokenWhiteSpace(); + t->next = NULL; + if ( last ) last->next = t; + else define->tokens = t; + last = t; + } while( idParser::ReadLine( &token ) ); + + if ( last ) { + // check for merge operators at the beginning or end + if ( (*define->tokens) == "##" || (*last) == "##" ) { + idParser::Error( "define with misplaced ##" ); + return false; + } + } + return true; +} + +/* +================ +idParser::AddDefine +================ +*/ +int idParser::AddDefine( const char *string ) { + define_t *define; + + define = DefineFromString( string ); + if (!define) { + return false; + } + AddDefineToHash(define, idParser::definehash); + return true; +} + +/* +================ +idParser::AddGlobalDefinesToSource +================ +*/ +void idParser::AddGlobalDefinesToSource( void ) { + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) { + newdefine = CopyDefine( define ); + AddDefineToHash(newdefine, idParser::definehash); + } +} + +/* +================ +idParser::Directive_if_def +================ +*/ +int idParser::Directive_if_def( int type ) { + idToken token; + define_t *d; + int skip; + + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "#ifdef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #ifdef, found '%s'", token.c_str() ); + return false; + } + d = FindHashedDefine(idParser::definehash, token.c_str()); + skip = (type == INDENT_IFDEF) == (d == NULL); + idParser::PushIndent( type, skip ); + return true; +} + +/* +================ +idParser::Directive_ifdef +================ +*/ +int idParser::Directive_ifdef( void ) { + return idParser::Directive_if_def( INDENT_IFDEF ); +} + +/* +================ +idParser::Directive_ifndef +================ +*/ +int idParser::Directive_ifndef( void ) { + return idParser::Directive_if_def( INDENT_IFNDEF ); +} + +/* +================ +idParser::Directive_else +================ +*/ +int idParser::Directive_else( void ) { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #else" ); + return false; + } + if (type == INDENT_ELSE) { + idParser::Error( "#else after #else" ); + return false; + } + idParser::PushIndent( INDENT_ELSE, !skip ); + return true; +} + +/* +================ +idParser::Directive_endif +================ +*/ +int idParser::Directive_endif( void ) { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #endif" ); + return false; + } + return true; +} + +/* +================ +idParser::EvaluateTokens +================ +*/ +typedef struct operator_s +{ + int op; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) { + switch(op) { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } + return false; +} + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 + +#define AllocValue(val) \ + if ( numvalues >= MAX_VALUES ) { \ + idParser::Error( "out of value space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + val = &value_heap[numvalues++]; \ + } + +#define FreeValue(val) + +#define AllocOperator(op) \ + if ( numoperators >= MAX_OPERATORS ) { \ + idParser::Error( "out of operator space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + op = &operator_heap[numoperators++]; \ + } + +#define FreeOperator(op) + +int idParser::EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ) { + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + idToken *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = false; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for ( t = tokens; t; t = t->next ) { + switch( t->type ) { + case TT_NAME: + { + if ( lastwasvalue || negativevalue ) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + if ( (*t) != "defined" ) { + idParser::Error( "undefined name '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + t = t->next; + if ( (*t) == "(" ) { + brace = true; + t = t->next; + } + if (!t || t->type != TT_NAME) { + idParser::Error( "defined() without name in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (FindHashedDefine(idParser::definehash, t->c_str())) { + v->intvalue = 1; + v->floatvalue = 1; + } + else { + v->intvalue = 0; + v->floatvalue = 0; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) { + t = t->next; + if (!t || (*t) != ")" ) { + idParser::Error( "defined missing ) in #if/#elif" ); + error = 1; + break; + } + } + brace = false; + // defined() creates a value + lastwasvalue = 1; + break; + } + case TT_NUMBER: + { + if (lastwasvalue) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) { + v->intvalue = - t->GetIntValue(); + v->floatvalue = - t->GetFloatValue(); + } + else { + v->intvalue = t->GetIntValue(); + v->floatvalue = t->GetFloatValue(); + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } + case TT_PUNCTUATION: + { + if (negativevalue) { + idParser::Error( "misplaced minus sign in #if/#elif" ); + error = 1; + break; + } + if (t->subtype == P_PARENTHESESOPEN) { + parentheses++; + break; + } + else if (t->subtype == P_PARENTHESESCLOSE) { + parentheses--; + if (parentheses < 0) { + idParser::Error( "too many ) in #if/#elsif" ); + error = 1; + } + break; + } + //check for invalid operators on floating point values + if ( !integer ) { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) { + idParser::Error( "illigal operator '%s' on floating point operands\n", t->c_str() ); + error = 1; + break; + } + } + switch( t->subtype ) { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) { + idParser::Error( "! or ~ after value in #if/#elif" ); + error = 1; + break; + } + break; + } + case P_INC: + case P_DEC: + { + idParser::Error( "++ or -- used in #if/#elif" ); + break; + } + case P_SUB: + { + if (!lastwasvalue) { + negativevalue = 1; + break; + } + } + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) { + idParser::Error( "operator '%s' after operator in #if/#elif", t->c_str() ); + error = 1; + break; + } + break; + } + default: + { + idParser::Error( "invalid operator '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (!error && !negativevalue) { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->op = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } + break; + } + default: + { + idParser::Error( "unknown '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (error) { + break; + } + } + if (!error) { + if (!lastwasvalue) { + idParser::Error( "trailing operator in #if/#elif" ); + error = 1; + } + else if (parentheses) { + idParser::Error( "too many ( in #if/#elif" ); + error = 1; + } + } + // + gotquestmarkvalue = false; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while( !error && firstoperator ) { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) { + break; + } + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) { + break; + } + } + //if the arity of the operator isn't equal to 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + v = v->next; + } + //if there's no value or no next value + if (!v) { + idParser::Error( "mising values in #if/#elif" ); + error = 1; + break; + } + } + if (error) { + break; + } + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) { + Log_Write("operator %s, value1 = %d", idParser::scriptstack->getPunctuationFromId(o->op), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } + else { + Log_Write("operator %s, value1 = %f", idParser::scriptstack->getPunctuationFromId(o->op), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } +#endif //DEBUG_EVAL + switch(o->op) { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) { + idParser::Error( ": without ? in #if/#elif" ); + error = 1; + break; + } + if (integer) { + if (!questmarkintvalue) + v1->intvalue = v2->intvalue; + } + else { + if (!questmarkfloatvalue) + v1->floatvalue = v2->floatvalue; + } + gotquestmarkvalue = false; + break; + } + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) { + idParser::Error( "? after ? in #if/#elif" ); + error = 1; + break; + } + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = true; + break; + } + } +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) + break; + lastoperatortype = o->op; + //if not an operator with arity 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + //remove the second value if not question mark operator + if (o->op != P_QUESTIONMARK) { + v = v->next; + } + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } + if (firstvalue) { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } + for (o = firstoperator; o; o = lastoperator) { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } + for (v = firstvalue; v; v = lastvalue) { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } + if (!error) { + return true; + } + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + return false; +} + +/* +================ +idParser::Evaluate +================ +*/ +int idParser::Evaluate( signed long int *intvalue, double *floatvalue, int integer ) { + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + int defined = false; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "no value after #if/#elif" ); + return false; + } + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Error( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadLine( &token )); + // + if ( !idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer ) ) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::DollarEvaluate +================ +*/ +int idParser::DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer) { + int indent, defined = false; + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "no leading ( after $evalint/$evalfloat" ); + return false; + } + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "nothing to Evaluate" ); + return false; + } + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Warning( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + if ( token[0] == '(' ) indent++; + else if ( token[0] == ')' ) indent--; + if (indent <= 0) { + break; + } + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadSourceToken( &token )); + // + if (!idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer)) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::Directive_elif +================ +*/ +int idParser::Directive_elif( void ) { + signed long int value; + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type || type == INDENT_ELSE) { + idParser::Error( "misplaced #elif" ); + return false; + } + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_ELIF, skip ); + return true; +} + +/* +================ +idParser::Directive_if +================ +*/ +int idParser::Directive_if( void ) { + signed long int value; + int skip; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_IF, skip ); + return true; +} + +/* +================ +idParser::Directive_line +================ +*/ +int idParser::Directive_line( void ) { + idToken token; + + idParser::Error( "#line directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::Directive_error +================ +*/ +int idParser::Directive_error( void ) { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Error( "#error without string" ); + return false; + } + idParser::Error( "#error: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_warning +================ +*/ +int idParser::Directive_warning( void ) { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Warning( "#warning without string" ); + return false; + } + idParser::Warning( "#warning: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_pragma +================ +*/ +int idParser::Directive_pragma( void ) { + idToken token; + + idParser::Warning( "#pragma directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::UnreadSignToken +================ +*/ +void idParser::UnreadSignToken( void ) { + idToken token; + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + token = "-"; + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + idParser::UnreadSourceToken( &token ); +} + +/* +================ +idParser::Directive_eval +================ +*/ +int idParser::Directive_eval( void ) { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%d", abs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::Directive_evalfloat +================ +*/ +int idParser::Directive_evalfloat( void ) { + double value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%1.2f", idMath::Fabs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if (value < 0) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDirective +================ +*/ +int idParser::ReadDirective( void ) { + idToken token; + + //read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '#' without name" ); + return false; + } + //directive name must be on the same line + if (token.linesCrossed > 0) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '#' at end of line" ); + return false; + } + //if if is a name + if (token.type == TT_NAME) { + if ( token == "if" ) { + return idParser::Directive_if(); + } + else if ( token == "ifdef" ) { + return idParser::Directive_ifdef(); + } + else if ( token == "ifndef" ) { + return idParser::Directive_ifndef(); + } + else if ( token == "elif" ) { + return idParser::Directive_elif(); + } + else if ( token == "else" ) { + return idParser::Directive_else(); + } + else if ( token == "endif" ) { + return idParser::Directive_endif(); + } + else if (idParser::skip > 0) { + // skip the rest of the line + while( idParser::ReadLine( &token ) ) { + } + return true; + } + else { + if ( token == "include" ) { + return idParser::Directive_include(); + } + else if ( token == "define" ) { + return idParser::Directive_define(); + } + else if ( token == "undef" ) { + return idParser::Directive_undef(); + } + else if ( token == "line" ) { + return idParser::Directive_line(); + } + else if ( token == "error" ) { + return idParser::Directive_error(); + } + else if ( token == "warning" ) { + return idParser::Directive_warning(); + } + else if ( token == "pragma" ) { + return idParser::Directive_pragma(); + } + else if ( token == "eval" ) { + return idParser::Directive_eval(); + } + else if ( token == "evalfloat" ) { + return idParser::Directive_evalfloat(); + } + } + } + idParser::Error( "unknown precompiler directive '%s'", token.c_str() ); + return false; +} + +/* +================ +idParser::DollarDirective_evalint +================ +*/ +int idParser::DollarDirective_evalint( void ) { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%d", abs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = abs( value ); + token.floatvalue = abs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::DollarDirective_evalfloat +================ +*/ +int idParser::DollarDirective_evalfloat( void ) { + double value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%1.2f", fabs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = (unsigned long) fabs( value ); + token.floatvalue = fabs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDollarDirective +================ +*/ +int idParser::ReadDollarDirective( void ) { + idToken token; + + // read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '$' without name" ); + return false; + } + // directive name must be on the same line + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '$' at end of line" ); + return false; + } + // if if is a name + if (token.type == TT_NAME) { + if ( token == "evalint" ) { + return idParser::DollarDirective_evalint(); + } + else if ( token == "evalfloat" ) { + return idParser::DollarDirective_evalfloat(); + } + } + idParser::UnreadSourceToken( &token ); + return false; +} + +/* +================ +idParser::ReadToken +================ +*/ +int idParser::ReadToken( idToken *token ) { + define_t *define; + + while(1) { + if ( !idParser::ReadSourceToken( token ) ) { + return false; + } + // check for precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '#' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( !idParser::ReadDirective() ) { + return false; + } + continue; + } + // if skipping source because of conditional compilation + if ( idParser::skip ) { + continue; + } + // recursively concatenate strings that are behind each other still resolving defines + if ( token->type == TT_STRING && !(idParser::scriptstack->GetFlags() & LEXFL_NOSTRINGCONCAT) ) { + idToken newtoken; + if ( idParser::ReadToken( &newtoken ) ) { + if ( newtoken.type == TT_STRING ) { + token->Append( newtoken.c_str() ); + } + else { + idParser::UnreadSourceToken( &newtoken ); + } + } + } + // + if ( !(idParser::scriptstack->GetFlags() & LEXFL_NODOLLARPRECOMPILE) ) { + // check for special precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '$' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( idParser::ReadDollarDirective() ) { + continue; + } + } + } + // if the token is a name + if ( token->type == TT_NAME && !( token->flags & TOKEN_FL_RECURSIVE_DEFINE ) ) { + // check if the name is a define macro + define = FindHashedDefine( idParser::definehash, token->c_str() ); + // if it is a define macro + if ( define ) { + // expand the defined macro + if ( !idParser::ExpandDefineIntoSource( token, define ) ) { + return false; + } + continue; + } + } + // found a token + return true; + } +} + +/* +================ +idParser::ExpectTokenString +================ +*/ +int idParser::ExpectTokenString( const char *string ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't find expected '%s'", string ); + return false; + } + + if ( token != string ) { + idParser::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return false; + } + return true; +} + +/* +================ +idParser::ExpectTokenType +================ +*/ +int idParser::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idParser::ReadToken( token ) ) { + idParser::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idParser::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idParser::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idParser::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idParser::Error( "expected '%s' but found '%s'", scriptstack->GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idParser::ExpectAnyToken +================ +*/ +int idParser::ExpectAnyToken( idToken *token ) { + if (!idParser::ReadToken( token )) { + idParser::Error( "couldn't read expected token" ); + return false; + } + else { + return true; + } +} + +/* +================ +idParser::CheckTokenString +================ +*/ +int idParser::CheckTokenString( const char *string ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + //if the token is available + if ( tok == string ) { + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::CheckTokenType +================ +*/ +int idParser::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + //if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::SkipUntilString +================ +*/ +int idParser::SkipUntilString( const char *string ) { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token == string ) { + return true; + } + } + return false; +} + +/* +================ +idParser::SkipRestOfLine +================ +*/ +int idParser::SkipRestOfLine( void ) { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + return true; + } + } + return false; +} + +/* +================= +idParser::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idParser::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if( token.type == TT_PUNCTUATION ) { + if( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================= +idParser::ParseBracedSectionExact + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains the exact formating of the braced section + + FIXME: what about precompilation ? +================= +*/ +const char *idParser::ParseBracedSectionExact( idStr &out, int tabs ) { + return scriptstack->ParseBracedSectionExact( out, tabs ); +} + +/* +================= +idParser::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +const char *idParser::ParseBracedSection( idStr &out, int tabs ) { + idToken token; + int i, depth; + bool doTabs = false; + if (tabs >= 0) { + doTabs = true; + } + + out.Empty(); + if ( !idParser::ExpectTokenString( "{" ) ) { + return out.c_str(); + } + out = "{"; + depth = 1; + do { + if ( !idParser::ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if (doTabs && token.linesCrossed) { + i = tabs; + if (token[0] == '}' && i > 0) { + i--; + } + while (i-- > 0) { + out += "\t"; + } + } + if ( token.type == TT_PUNCTUATION ) { + if ( token[0] == '{' ) { + depth++; + if (doTabs) { + tabs++; + } + } + else if ( token[0] == '}' ) { + depth--; + if (doTabs) { + tabs--; + } + } + } + + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } + else { + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idParser::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idParser::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +================ +idParser::UnreadToken +================ +*/ +void idParser::UnreadToken( idToken *token ) { + idParser::UnreadSourceToken( token ); +} + +/* +================ +idParser::ReadTokenOnLine +================ +*/ +int idParser::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::ParseInt +================ +*/ +int idParser::ParseInt( void ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idParser::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idParser::ParseBool +================ +*/ +bool idParser::ParseBool( void ) { + idToken token; + + if ( !idParser::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idParser::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idParser::ParseFloat +================ +*/ +float idParser::ParseFloat( void ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected floating point number" ); + return 0.0f; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + idParser::Error( "expected float value, found '%s'", token.c_str() ); + } + return token.GetFloatValue(); +} + +/* +================ +idParser::Parse1DMatrix +================ +*/ +int idParser::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idParser::ParseFloat(); + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse2DMatrix +================ +*/ +int idParser::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idParser::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse3DMatrix +================ +*/ +int idParser::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idParser::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::GetLastWhiteSpace +================ +*/ +int idParser::GetLastWhiteSpace( idStr &whiteSpace ) const { + if ( scriptstack ) { + scriptstack->GetLastWhiteSpace( whiteSpace ); + } else { + whiteSpace.Clear(); + } + return whiteSpace.Length(); +} + +/* +================ +idParser::SetMarker +================ +*/ +void idParser::SetMarker( void ) { + marker_p = NULL; +} + +/* +================ +idParser::GetStringFromMarker + + FIXME: this is very bad code, the script isn't even garrenteed to still be around +================ +*/ +void idParser::GetStringFromMarker( idStr& out, bool clean ) { + char* p; + char save; + + if ( marker_p == NULL ) { + marker_p = scriptstack->buffer; + } + + if ( tokens ) { + p = (char*)tokens->whiteSpaceStart_p; + } else { + p = (char*)scriptstack->script_p; + } + + // Set the end character to NULL to give us a complete string + save = *p; + *p = 0; + + // If cleaning then reparse + if ( clean ) { + idParser temp( marker_p, strlen( marker_p ), "temp", flags ); + idToken token; + while ( temp.ReadToken ( &token ) ) { + out += token; + } + } else { + out = marker_p; + } + + // restore the character we set to NULL + *p = save; +} + +/* +================ +idParser::SetIncludePath +================ +*/ +void idParser::SetIncludePath( const char *path ) { + idParser::includepath = path; + // add trailing path seperator + if (idParser::includepath[idParser::includepath.Length()-1] != '\\' && + idParser::includepath[idParser::includepath.Length()-1] != '/') { +// RAVEN BEGIN + idParser::includepath += "/"; +// RAVEN END + } +} + +/* +================ +idParser::SetPunctuations +================ +*/ +void idParser::SetPunctuations( const punctuation_t *p ) { + idParser::punctuations = p; +} + +/* +================ +idParser::SetFlags +================ +*/ +void idParser::SetFlags( int flags ) { + idLexer *s; + + idParser::flags = flags; + for ( s = idParser::scriptstack; s; s = s->next ) { + s->SetFlags( flags ); + } +} + +/* +================ +idParser::GetFlags +================ +*/ +int idParser::GetFlags( void ) const { + return idParser::flags; +} + +/* +================ +idParser::LoadFile +================ +*/ +int idParser::LoadFile( const char *filename, bool OSPath ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadFile: another source already loaded"); + return false; + } + script = new idLexer( filename, 0, OSPath ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::OSPath = OSPath; + idParser::filename = filename; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), MA_PARSER ); +//RAVEN END + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::LoadMemory +================ +*/ +int idParser::LoadMemory(const char *ptr, int length, const char *name ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadMemory: another source already loaded"); + return false; + } + script = new idLexer( ptr, length, name ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::filename = name; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), MA_PARSER ); +//RAVEN END + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::FreeSource +================ +*/ +void idParser::FreeSource( bool keepDefines ) { + idLexer *script; + idToken *token; + define_t *define; + indent_t *indent; + int i; + + // free all the scripts + while(idParser::scriptstack) { + script = idParser::scriptstack; + idParser::scriptstack = idParser::scriptstack->next; + delete script; + } + // free all the tokens + while(idParser::tokens) { + token = idParser::tokens; + idParser::tokens = idParser::tokens->next; + delete token; + } + // free all indents + while(idParser::indentstack) { + indent = idParser::indentstack; + idParser::indentstack = idParser::indentstack->next; + Mem_Free( indent ); + } + if ( !keepDefines ) { + // free hash table + if ( idParser::definehash ) { + // free defines + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + while( idParser::definehash[i] ) { + define = idParser::definehash[i]; + idParser::definehash[i] = idParser::definehash[i]->hashnext; + FreeDefine(define); + } + } + idParser::defines = NULL; + Mem_Free( idParser::definehash ); + idParser::definehash = NULL; + } + } + idParser::loaded = false; +} + +/* +================ +idParser::GetPunctuationFromId +================ +*/ +const char *idParser::GetPunctuationFromId( int id ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationFromId( id ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( idParser::punctuations[i].n == id ) { + return idParser::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idParser::GetPunctuationId +================ +*/ +int idParser::GetPunctuationId( const char *p ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationId( p ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( !idStr::Cmp(idParser::punctuations[i].p, p) ) { + return idParser::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser() { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = 0; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + +// RAVEN BEGIN +// bdube: added members + marker_p = NULL; +// RAVEN END +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + +// RAVEN BEGIN +// bdube: added members + marker_p = NULL; +// RAVEN END +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *filename, int flags, bool OSPath ) { + this->loaded = false; + this->OSPath = true; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadFile( filename, OSPath ); +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *ptr, int length, const char *name, int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadMemory( ptr, length, name ); +} + +/* +================ +idParser::~idParser +================ +*/ +idParser::~idParser( void ) { + idParser::FreeSource( false ); +} + diff --git a/source/idlib/Parser.h b/source/idlib/Parser.h new file mode 100644 index 0000000..297c4cd --- /dev/null +++ b/source/idlib/Parser.h @@ -0,0 +1,254 @@ + +#ifndef __PARSER_H__ +#define __PARSER_H__ + +/* +=============================================================================== + + C/C++ compatible pre-compiler + +=============================================================================== +*/ + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +// macro definitions +typedef struct define_s { + char * name; // define name + int flags; // define flags + int builtin; // > 0 if builtin define + int numparms; // number of define parameters + idToken * parms; // define parameters + idToken * tokens; // macro tokens (possibly containing parm tokens) + struct define_s *next; // next defined macro in a list + struct define_s *hashnext; // next define in the hash chain +} define_t; + +// indents used for conditional compilation directives: +// #if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s { + int type; // indent type + int skip; // true if skipping current indent + idLexer * script; // script the indent was in + struct indent_s *next; // next indent on the indent stack +} indent_t; + + +class idParser { + +public: + // constructor + idParser(); + idParser( int flags ); + idParser( const char *filename, int flags = 0, bool OSPath = false ); + idParser( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idParser(); + // load a source file + int LoadFile( const char *filename, bool OSPath = false ); + // load a source from memory + int LoadMemory( const char *ptr, int length, const char *name ); + // free the current source + void FreeSource( bool keepDefines = false ); + // returns true if a source is loaded + int IsLoaded( void ) const { return idParser::loaded; }; + // read a token from the source + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true and reads the token when it is available + int CheckTokenString( const char *string ); + // returns true and reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out, int tabs = -1 ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // unread the given token + void UnreadToken( idToken *token ); + // read a token only if on the current line + int ReadTokenOnLine( idToken *token ); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number + float ParseFloat( void ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // get the white space before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // Set a marker in the source file (there is only one marker) + void SetMarker( void ); + // Get the string from the marker to the current position + void GetStringFromMarker( idStr& out, bool clean = false ); + // add a define to the source + int AddDefine( const char *string ); + // add builtin defines + void AddBuiltinDefines( void ); + // set the source include path + void SetIncludePath( const char *path ); + // set the punctuation set + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ) const; + // returns the current filename + const char * GetFileName( void ) const; + // get current offset in current script + const int GetFileOffset( void ) const; + // get file time for current script + const unsigned int GetFileTime( void ) const; + // returns the current line number + const int GetLineNum( void ) const; + // print an error message + void Error( const char *str, ... ) const id_attribute((format(printf,2,3))); + // print a warning message + void Warning( const char *str, ... ) const id_attribute((format(printf,2,3))); + + // add a global define that will be added to all opened sources + static int AddGlobalDefine( const char *string ); + // remove the given global define + static int RemoveGlobalDefine( const char *name ); + // remove all global defines + static void RemoveAllGlobalDefines( void ); + // set the base folder to load files from + static void SetBaseFolder( const char *path ); + +private: + + int loaded; // set when a source file is loaded from file or memory + idStr filename; // file name of the script + idStr includepath; // path to include files + bool OSPath; // true if the file was loaded from an OS path + const punctuation_t *punctuations; // punctuations to use + int flags; // flags used for script parsing + idLexer * scriptstack; // stack with scripts of the source + idToken * tokens; // tokens to read first + define_t * defines; // list with macro definitions + define_t ** definehash; // hash chain with defines + indent_t * indentstack; // stack with indents + int skip; // > 0 if skipping conditional code + idToken token; // last read token + const char* marker_p; + + static define_t *globaldefines; // list with global defines added to every source loaded + +private: + void PushIndent( int type, int skip ); + void PopIndent( int *type, int *skip ); + void PushScript( idLexer *script ); + int ReadSourceToken( idToken *token ); + int ReadLine( idToken *token ); + int UnreadSourceToken( idToken *token ); + int ReadDefineParms( define_t *define, idToken **parms, int maxparms ); + int StringizeTokens( idToken *tokens, idToken *token ); + int MergeTokens( idToken *t1, idToken *t2 ); + int ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefineIntoSource( idToken *deftoken, define_t *define ); + void AddGlobalDefinesToSource( void ); + define_t * CopyDefine( define_t *define ); + define_t * FindHashedDefine(define_t **definehash, const char *name); + int FindDefineParm( define_t *define, const char *name ); + void AddDefineToHash(define_t *define, define_t **definehash); + static void PrintDefine( define_t *define ); + static void FreeDefine( define_t *define ); + static define_t *FindDefine( define_t *defines, const char *name ); + static define_t *DefineFromString( const char *string); + define_t * CopyFirstDefine( void ); + int Directive_include( void ); + int Directive_undef( void ); + int Directive_if_def( int type ); + int Directive_ifdef( void ); + int Directive_ifndef( void ); + int Directive_else( void ); + int Directive_endif( void ); + int EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ); + int Evaluate( signed long int *intvalue, double *floatvalue, int integer ); + int DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer); + int Directive_define( void ); + int Directive_elif( void ); + int Directive_if( void ); + int Directive_line( void ); + int Directive_error( void ); + int Directive_warning( void ); + int Directive_pragma( void ); + void UnreadSignToken( void ); + int Directive_eval( void ); + int Directive_evalfloat( void ); + int ReadDirective( void ); + int DollarDirective_evalint( void ); + int DollarDirective_evalfloat( void ); + int ReadDollarDirective( void ); +}; + +ID_INLINE const char *idParser::GetFileName( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileName(); + } + else { + return ""; + } +} + +ID_INLINE const int idParser::GetFileOffset( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileOffset(); + } + else { + return 0; + } +} + +ID_INLINE const unsigned int idParser::GetFileTime( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileTime(); + } + else { + return 0; + } +} + +ID_INLINE const int idParser::GetLineNum( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetLineNum(); + } + else { + return 0; + } +} + +#endif /* !__PARSER_H__ */ + diff --git a/source/idlib/Str.cpp b/source/idlib/Str.cpp new file mode 100644 index 0000000..32ede88 --- /dev/null +++ b/source/idlib/Str.cpp @@ -0,0 +1,2232 @@ + +#include "precompiled.h" +#pragma hdrstop + +// TTimo - don't do anything funky if you're on a real OS ;-) +#if defined(_WINDOWS) || defined(_XENON) + +// RAVEN BEGIN +// jsinger: attempt at removing the DLL cross boundary issue +// mwhitlock: Dynamic memory consolidation - may want to consier making a totally separate string allocator +#if ( !defined(ID_REDIRECT_NEWDELETE) && !defined(RV_UNIFIED_ALLOCATOR) ) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END + #define USE_STRING_DATA_ALLOCATOR +#endif + +#endif + +#ifdef USE_STRING_DATA_ALLOCATOR +static idDynamicBlockAlloc stringDataAllocator; +#endif + +idVec4 g_color_table[16] = +{ + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED + idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN + idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW + idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE + idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN + idVec4(1.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_MAGENTA + idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE + idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY + idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK +// RAVEN BEGIN +// bdube: console color + idVec4(0.94f, 0.62f, 0.05f, 1.0f), // S_COLOR_CONSOLE +// RAVEN END + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), +}; + +const char *units[2][4] = +{ + { "B", "KB", "MB", "GB" }, + { "B/s", "KB/s", "MB/s", "GB/s" } +}; + +// P means the character is Polish +// C means the character is Czech + +const bool idStr::printableCharacter[256] = +{ +// + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, +// + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, +// ! " # $ & % ' ( ) * + , - . / + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// @ A B C D E F G H I J K L M N O + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// P Q R S T U V W X Y Z [ \ ] ^ _ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// ` a b c d e f g h i j k l m n o + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// p q r s t u v w x y z { | } ~ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, +// € X S C Ś P T C Z C Z P + true, true, false, false, false, false, false, false, false, false, true, false, true, true, true, true, +// ™ s C ś P t C z C z P + false, false, false, false, false, false, false, false, false, true, true, false, true, true, true, true, +// ˇ Ł P ¤ Ą P § © « ® P + false, true, false, true, true, true, false, true, false, true, false, true, false, false, true, true, +// ° Ł P ´ µ · P » ż P + true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, true, +// Ŕ Á C Â Ă Ä Ĺ Ć P Ç Č C É C Ę P Ë Ě C Í C Î Ď C + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// Đ Ń P Ň C Ó PC Ô Ő Ö × Ř C Ů C Ú C Ű Ü Ý C Ţ ß + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// ŕ á C â ă ä ĺ ć P ç č C é C ę P ë ě C í C î ď C + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// đ ń P ň C ó PC ô ő ö ÷ ř C ů C ú C ű ü ý C ţ ˙ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +}; + +const char idStr::upperCaseCharacter[256] = +{ +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// ! " # $ & % ' ( ) * + , - . / + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// @ A B C D E F G H I J K L M N O + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +// P Q R S T U V W X Y Z [ \ ] ^ _ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0, +// ` a b c d e f g h i j k l m n o + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +// p q r s t u v w x y z { | } ~ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0, +// € X S Ś T Z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Ś', 'Ť', 'Ž', 'Ž', +// TM s ś t z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Ś', 'Ť', 'Ž', 'Ź', +// ˇ Ł ¤ Ą § © « ® + 0, 0, 0, 'Ł', 0, 'Ą', 0, 0, 0, 0, 0, 0, 0, 0, 0, 'ż', +// ° ´ µ · » ż + 0, 0, 0, 'Ł', 0, 0, 0, 0, 0, 'Ą', 0, 0, 0, 0, 0, 'ż', +// Ŕ Á Â Ă Ä Ĺ Ć Ç Č É Ę Ë Ě Í Î Ď + 'Ŕ', 'Á', 'Â', 'Ă', 'Ä', 'Ĺ', 'Ć', 'Ç', 'Č', 'É', 'Ę', 'Ë', 'Ě', 'Í', 'Î', 'Ď', +// Đ Ń Ň Ó Ô Ő Ö × Ř Ů Ú Ű Ü Ý Ţ ß + 'Đ', 'Ń', 'Ň', 'Ó', 'Ô', 'Ő', 'Ö', 0, 'Ř', 'Ů', 'Ú', 'Ű', 'Ü', 'Ý', 'Ţ', 'ß', +// ŕ á â ă ä ĺ ć ç č é ę ë ě í î ď + 'Ŕ', 'Á', 'Â', 'Ă', 'Ä', 'Ĺ', 'Ć', 'Ç', 'Č', 'É', 'Ę', 'Ë', 'Ě', 'Í', 'Î', 'Ď', +// đ ń ň ó ô ő ö ÷ ř ů ú ű ü ý ţ ˙ + 'Đ', 'Ń', 'Ň', 'Ó', 'Ô', 'Ő', 'Ö', 0, 'Ř', 'Ů', 'Ú', 'Ű', 'Ü', 'Ý', 'Ţ', 'ß', +}; + +const char idStr::lowerCaseCharacter[256] = +{ +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// ! " # $ & % ' ( ) * + , - . / + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// @ A B C D E F G H I J K L M N O + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +// P Q R S T U V W X Y Z [ \ ] ^ _ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0, +// ` a b c d e f g h i j k l m n o + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +// p q r s t u v w x y z { | } ~ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0, +// € X S Ś T Z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'ś', 'ť', 'ž', 'ź', +// TM s ś t z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'ś', 'ť', 'ž', 'ź', +// ˇ Ł ¤ Ą § © « ® + 0, 0, 0, 'ł', 0, 'ą', 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Ż', +// ° ´ µ · » ż + 0, 0, 0, 'ł', 0, 0, 0, 0, 0, 'ą', 0, 0, 0, 0, 0, 'Ż', +// Ŕ Á Â Ă Ä Ĺ Ć Ç Č É Ę Ë Ě Í Î Ď + 'ŕ', 'á', 'â', 'ă', 'ä', 'ĺ', 'ć', 'ç', 'č', 'é', 'ę', 'ë', 'ě', 'í', 'î', 'ď', +// Đ Ń Ň Ó Ô Ő Ö × Ř Ů Ú Ű Ü Ý Ţ ß + 'đ', 'ń', 'ň', 'ó', 'ô', 'ő', 'ö', 0, 'ř', 'ů', 'ú', 'ű', 'ü', 'ý', 'ţ', 'ß', +// ŕ á â ă ä ĺ ć ç č é ę ë ě í î ď + 'ŕ', 'á', 'â', 'ă', 'ä', 'ĺ', 'ć', 'ç', 'č', 'é', 'ę', 'ë', 'ě', 'í', 'î', 'ď', +// đ ń ň ó ô ő ö ÷ ř ů ú ű ü ý ţ ˙ + 'đ', 'ń', 'ň', 'ó', 'ô', 'ő', 'ö', 0, 'ř', 'ů', 'ú', 'ű', 'ü', 'ý', 'ţ', 'ß', +}; +// RAVEN END + +/* +============ +idStr::ColorForIndex +============ +*/ +idVec4 & idStr::ColorForIndex( int i ) { + return g_color_table[ i & 15 ]; +} + +/* +============ +idStr::ReAllocate +============ +*/ +void idStr::ReAllocate( int amount, bool keepold ) { + char *newbuffer; + int newsize; + int mod; + + //assert( data ); + assert( amount > 0 ); + + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } + else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + alloced = newsize; + +#ifdef USE_STRING_DATA_ALLOCATOR + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + + newbuffer = stringDataAllocator.Alloc( alloced ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + +#else + newbuffer = new char[ alloced ]; +#endif + if ( keepold && data ) { + data[ len ] = '\0'; + strcpy( newbuffer, data ); + } + + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Free( data ); +#else + delete [] data; +#endif + } + + data = newbuffer; +} + +/* +============ +idStr::FreeData +============ +*/ +void idStr::FreeData( void ) { + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR +// RAVEN BEGIN +// jnewquist: Ignore free request if allocator is empty, probably shutdown + if ( stringDataAllocator.GetNumUsedBlocks() > 0 ) { + stringDataAllocator.Free( data ); + } +// RAVEN END +#else + delete[] data; +#endif + +// RAVEN BEGIN +// jsinger: was exhibiting a buffer overrun when an idStr was contained in an idList +// due to having the wrong alloced value. This corrects that + Init(); +// RAVEN END + } +} + +/* +============ +idStr::operator= +============ +*/ +void idStr::operator=( const char *text ) { + int l; + int diff; + int i; + + if ( !text ) { + // safe behaviour if NULL + EnsureAlloced( 1, false ); + data[ 0 ] = '\0'; + len = 0; + return; + } + + if ( text == data ) { + return; // copying same thing + } + + // check if we're aliasing + if ( text >= data && text <= data + len ) { + diff = text - data; + + assert( strlen( text ) < (unsigned)len ); + + for ( i = 0; text[ i ]; i++ ) { + data[ i ] = text[ i ]; + } + + data[ i ] = '\0'; + + len -= diff; + + return; + } + + l = strlen( text ); + EnsureAlloced( l + 1, false ); + strcpy( data, text ); + len = l; +} + +/* +============ +idStr::FindChar + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::FindChar( const char *str, const char c, int start, int end ) { + int i; + + if ( end == -1 ) { + end = strlen( str ) - 1; + } + for ( i = start; i <= end; i++ ) { + if ( str[i] == c ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::FindText + +returns -1 if not found otherwise the index of the text +============ +*/ +int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) { + int l, i, j; + + if ( end == -1 ) { + end = strlen( str ); + } + l = end - strlen( text ); + for ( i = start; i <= l; i++ ) { + if ( casesensitive ) { + for ( j = 0; text[j]; j++ ) { + if ( str[i+j] != text[j] ) { + break; + } + } + } else { + for ( j = 0; text[j]; j++ ) { + if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) { + break; + } + } + } + if ( !text[j] ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::Filter + +Returns true if the string conforms the given filter. +Several metacharacter may be used in the filter. + +* match any string of zero or more characters +? match any single character +[abc...] match any of the enclosed characters; a hyphen can + be used to specify a range (e.g. a-z, A-Z, 0-9) + +============ +*/ +bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) { + idStr buf; + int i, found, index; + + while(*filter) { + if (*filter == '*') { + filter++; + buf.Empty(); + for (i = 0; *filter; i++) { + if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) { + break; + } + buf += *filter; + if ( *filter == '[' ) { + filter++; + } + filter++; + } + if ( buf.Length() ) { + index = idStr(name).Find( buf.c_str(), casesensitive ); + if ( index == -1 ) { + return false; + } + name += index + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[') { + if ( *(filter+1) == '[' ) { + if ( *name != '[' ) { + return false; + } + filter += 2; + name++; + } + else { + filter++; + found = false; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') { + break; + } + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) { + found = true; + } + } + else { + if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) { + found = true; + } + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) { + found = true; + } + } + else { + if ( ::toupper(*filter) == ::toupper(*name) ) { + found = true; + } + } + filter++; + } + } + if (!found) { + return false; + } + while(*filter) { + if ( *filter == ']' && *(filter+1) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } + } + else { + if (casesensitive) { + if (*filter != *name) { + return false; + } + } + else { + if ( ::toupper(*filter) != ::toupper(*name) ) { + return false; + } + } + filter++; + name++; + } + } + return true; +} + +/* +============= +idStr::StripMediaName + + makes the string lower case, replaces backslashes with forward slashes, and removes extension +============= +*/ +void idStr::StripMediaName( const char *name, idStr &mediaName ) { + char c; + + mediaName.Empty(); + + for ( c = *name; c; c = *(++name) ) { + // truncate at an extension + if ( c == '.' ) { + break; + } + // convert backslashes to forward slashes + if ( c == '\\' ) { + mediaName.Append( '/' ); + } else { + mediaName.Append( idStr::ToLower( c ) ); + } + } +} + +/* +============= +idStr::CheckExtension +============= +*/ +bool idStr::CheckExtension( const char *name, const char *ext ) { + const char *s1 = name + Length( name ) - 1; + const char *s2 = ext + Length( ext ) - 1; + int c1, c2, d; + + do { + c1 = *s1--; + c2 = *s2--; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return false; + } + } while( s1 > name && s2 > ext ); + + return ( s1 >= name ); +} + +/* +============= +idStr::FloatArrayToString +============= +*/ +const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) { + static int index = 0; + static char str[4][16384]; // in case called by nested functions + int i, n; + char format[16], *s; + + // use an array of string so that multiple calls won't collide + s = str[ index ]; + index = (index + 1) & 3; + + idStr::snPrintf( format, sizeof( format ), "%%.%df", precision ); + n = idStr::snPrintf( s, sizeof( str[0] ), format, array[0] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + idStr::snPrintf( format, sizeof( format ), " %%.%df", precision ); + for ( i = 1; i < length; i++ ) { + n += idStr::snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + } + return s; +} + +/* +============ +idStr::Last + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::Last( const char c ) const { + int i; + + for( i = Length(); i > 0; i-- ) { + if ( data[ i - 1 ] == c ) { + return i - 1; + } + } + + return -1; +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char c ) { + while( data[ 0 ] == c ) { + memmove( &data[ 0 ], &data[ 1 ], len ); + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + } + } +} + +/* +============ +idStr::StripLeadingOnce +============ +*/ +bool idStr::StripLeadingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + return true; + } + return false; +} + +/* +============ +idStr::StripTrailing +============ +*/ +void idStr::StripTrailing( const char c ) { + int i; + + for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripTrailing( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + } + } +} + +/* +============ +idStr::StripTrailingOnce +============ +*/ +bool idStr::StripTrailingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + return true; + } + return false; +} + +/* +============ +idStr::Replace +============ +*/ +// RAVEN BEGIN +// scork: this needs to return an int of the replacement count, like MS CString etc, otherwise you can't do things like this: +// +// idStr str; +// while (str.Replace("\n\n","\n")) {}; +// +// ... to remove blank lines from an output string if you have 3 blank lines in a row. Without the while(), you'd just be guessing about +// converting (eg) 3 blank lines to 2, then 2 to 1, depending on how many times you straight-line the Replace() call. +// +int idStr::Replace( const char *old, const char *nw ) { + int iReplaced = 0; + int oldLen, newLen, i, j, count; + idStr oldString( data ); + + oldLen = strlen( old ); + newLen = strlen( nw ); + + // Work out how big the new string will be + count = 0; + for( i = 0; i < oldString.Length(); i++ ) { + if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) { + count++; + i += oldLen - 1; + } + } + + if( count ) { + EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false ); + + // Replace the old data with the new data + for( i = 0, j = 0; i < oldString.Length(); i++ ) { + if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) { + memcpy( data + j, nw, newLen ); + i += oldLen - 1; + j += newLen; + iReplaced++; + } else { + data[j] = oldString[i]; + j++; + } + } + data[j] = 0; + len = strlen( data ); + } + return iReplaced; +} +// RAVEN END + +/* +============ +idStr::Mid +============ +*/ +const char *idStr::Mid( int start, int len, idStr &result ) const { + int i; + + result.Empty(); + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return NULL; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::Mid +============ +*/ +idStr idStr::Mid( int start, int len ) const { + int i; + idStr result; + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return result; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::StripTrailingWhitespace +============ +*/ +void idStr::StripTrailingWhitespace( void ) { + int i; + + // cast to unsigned char to prevent stripping off high-ASCII characters + for( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +// RAVEN BEGIN +/* +============ +idStr::StripUntil +============ +*/ +void idStr::StripUntil( const char c ) +{ + while( data[ 0 ] != c && len ) { + memmove( &data[ 0 ], &data[ 1 ], len ); + len--; + } +} +// RAVEN END + +/* +===================================================================== + + info strings + +===================================================================== +*/ + +/* +============ +idStr::StripQuotes + +Removes the quotes from the beginning and end of the string +============ +*/ +idStr& idStr::StripQuotes ( void ) +{ + if ( data[0] != '\"' ) + { + return *this; + } + + // Remove the trailing quote first + if ( data[len-1] == '\"' ) + { + data[len-1] = '\0'; + len--; + } + + // Strip the leading quote now + len--; + memmove( &data[ 0 ], &data[ 1 ], len ); + data[len] = '\0'; + + return *this; +} + +/* +===================================================================== + + filename methods + +===================================================================== +*/ + +/* +============ +idStr::FileNameHash +============ +*/ +int idStr::FileNameHash( void ) const { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while( data[i] != '\0' ) { + letter = idStr::ToLower( data[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter =='\\' ) { + letter = '/'; + } + hash += (long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +============ +idStr::BackSlashesToSlashes +============ +*/ +idStr &idStr::BackSlashesToSlashes( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '\\' ) { + data[ i ] = '/'; + } + } + return *this; +} + +// RAVEN BEGIN +// jscott: for file systems that require backslashes +/* +============ +idStr::SlashesToBackSlashes +============ +*/ +idStr &idStr::SlashesToBackSlashes( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '/' ) { + data[ i ] = '\\'; + } + } + return *this; +} + +// nmckenzie: char replacing routine + +/* +============ +idStr::SlashesToBackSlashes +============ +*/ + +bool idStr::HasChar( const char check ){ + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == check ) { + return true; + } + } + return false; +} + +/* +============ +idStr::HasChars +============ +*/ + +bool idStr::HasChars( const char *check ){ + int i; + + while( *check ){ + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == *check ) { + return true; + } + } + check++; + } + return false; +} + +/* +============ +idStr::ReplaceChar +============ +*/ + +idStr &idStr::ReplaceChar( const char from, const char to ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == from ) { + data[ i ] = to; + } + } + return *this; +} + +/* +============ +idStr::ReplaceChars +============ +*/ + +idStr &idStr::ReplaceChars( const char *from, const char to ) { + int i; + + while( *from ){ + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == *from ) { + data[ i ] = to; + } + } + from++; + } + return *this; +} + +// RAVEN END + +/* +============ +idStr::SetFileExtension +============ +*/ +idStr &idStr::SetFileExtension( const char *extension ) { + StripFileExtension(); + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +============ +idStr::StripFileExtension +============ +*/ +idStr &idStr::StripFileExtension( void ) { + int i; + + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + return *this; +} + +/* +============ +idStr::StripAbsoluteFileExtension +============ +*/ +idStr &idStr::StripAbsoluteFileExtension( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + + return *this; +} + +/* +================== +idStr::DefaultFileExtension +================== +*/ +idStr &idStr::DefaultFileExtension( const char *extension ) { + int i; + + // do nothing if the string already has an extension + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + return *this; + } + } + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +================== +idStr::DefaultPath +================== +*/ +idStr &idStr::DefaultPath( const char *basepath ) { + if ( ( ( *this )[ 0 ] == '/' ) || ( ( *this )[ 0 ] == '\\' ) ) { + // absolute path location + return *this; + } + + *this = basepath + *this; + return *this; +} + +/* +==================== +idStr::AppendPath +==================== +*/ +void idStr::AppendPath( const char *text ) { + int pos; + int i = 0; + + if ( text && text[i] ) { + pos = len; + EnsureAlloced( len + strlen( text ) + 2 ); + + if ( pos ) { + if ( data[ pos-1 ] != '/' ) { + data[ pos++ ] = '/'; + } + } + if ( text[i] == '/' ) { + i++; + } + + for ( ; text[ i ]; i++ ) { + if ( text[ i ] == '\\' ) { + data[ pos++ ] = '/'; + } else { + data[ pos++ ] = text[ i ]; + } + } + len = pos; + data[ pos ] = '\0'; + } +} + +/* +================== +idStr::StripFilename +================== +*/ +idStr &idStr::StripFilename( void ) { + int pos; + + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos ] != '/' ) && ( ( *this )[ pos ] != '\\' ) ) { + pos--; + } + + if ( pos < 0 ) { + pos = 0; + } + + CapLength( pos ); + return *this; +} + +/* +================== +idStr::StripPath +================== +*/ +idStr &idStr::StripPath( void ) { + int pos; + + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + *this = Right( Length() - pos ); + return *this; +} + +/* +==================== +idStr::ExtractFilePath +==================== +*/ +void idStr::ExtractFilePath( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Left( pos, dest ); +} + +/* +==================== +idStr::ExtractFileName +==================== +*/ +void idStr::ExtractFileName( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Right( Length() - pos, dest ); +} + +/* +==================== +idStr::ExtractFileBase +==================== +*/ +void idStr::ExtractFileBase( idStr &dest ) const { + int pos; + int start; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + start = pos; +// RAVEN BEGIN +// jscott: for getting the file base out of addnormals() style filenames + while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) && ( ( *this )[ pos ] != ',' ) ) { +// RAVEN END + pos++; + } + + Mid( start, pos - start, dest ); +} + +/* +==================== +idStr::ExtractFileExtension +==================== +*/ +void idStr::ExtractFileExtension( idStr &dest ) const { + int pos; + + // + // back up until a . or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) { + pos--; + } + + if ( !pos ) { + // no extension + dest.Empty(); + } else { + Right( Length() - pos, dest ); + } +} + +// RAVEN BEGIN +// twhitaker: Turns a bad file name into a good one or your money back +void idStr::ScrubFileName( void ) +{ + int i; + + RemoveEscapes(); + StripFileExtension(); + + for ( i = 0; i < len; i++ ) { + if( !idStr::upperCaseCharacter[data[i]] && !isdigit(data[i]) ) { + data[i] = '_'; + } + } +} + +// jscott: like the declManager version, but globally accessable +void idStr::MakeNameCanonical( void ) +{ + ToLower(); + BackSlashesToSlashes(); + StripFileExtension(); +} + +void idStr::EnsurePrintable( void ) { + + int i; + + for( i = 0; i < len; i++ ) { + + if( !CharIsPrintable( data[i] ) ) { + + memmove( &data[i], &data[i + 1], len - i ); + len--; + } + } +} +// RAVEN END + +/* +===================================================================== + + char * methods to replace library functions + +===================================================================== +*/ + +/* +============ +idStr::IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool idStr::IsNumeric( const char *s ) { + int i; + bool dot; + + if ( *s == '-' ) { + s++; + } + + dot = false; + for ( i = 0; s[i]; i++ ) { + if ( !isdigit( ( byte )s[i] ) ) { + if ( ( s[ i ] == '.' ) && !dot ) { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +/* +============ +idStr::HasLower + +Checks if a string has any lowercase chars +============ +*/ +bool idStr::HasLower( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsLower( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +============ +idStr::HasUpper + +Checks if a string has any uppercase chars +============ +*/ +bool idStr::HasUpper( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsUpper( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +================ +idStr::Cmp +================ +*/ +int idStr::Cmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + if ( d ) { + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Cmpn +================ +*/ +int idStr::Cmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + if ( d ) { + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +int idStr::Icmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmpn +================ +*/ +int idStr::Icmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +// RAVEN BEGIN +// bdube: escape codes +int idStr::IcmpNoEscape ( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + for ( d = idStr::IsEscape( s1 ); d; d = idStr::IsEscape( s1 ) ) { + s1 += d; + } + for ( d = idStr::IsEscape( s2 ); d; d = idStr::IsEscape( s2 ) ) { + s2 += d; + } +// RAVEN END + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::IcmpPath +================ +*/ +int idStr::IcmpPath( const char *s1, const char *s2 ) { + int c1, c2, d; + +#if 0 +//#if !defined( _WIN32 ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +================ +idStr::IcmpnPath +================ +*/ +int idStr::IcmpnPath( const char *s1, const char *s2, int n ) { + int c1, c2, d; + +#if 0 +//#if !defined( _WIN32 ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +============= +idStr::Copynz + +Safe strncpy that ensures a trailing zero +============= +*/ +void idStr::Copynz( char *dest, const char *src, int destsize ) { + if ( !src ) { + idLib::common->Warning( "idStr::Copynz: NULL src" ); + return; + } + if ( destsize < 1 ) { + idLib::common->Warning( "idStr::Copynz: destsize < 1" ); + return; + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +/* +================ +idStr::Append + + never goes past bounds or leaves without a terminating 0 +================ +*/ +void idStr::Append( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + idLib::common->Error( "idStr::Append: already overflowed" ); + } + idStr::Copynz( dest + l1, src, size - l1 ); +} + +// bdube: escape codes +/* +================ +idStr::LengthWithoutEscapes +================ +*/ +int idStr::LengthWithoutEscapes( const char *s ) { + int len; + const char *p; + + if ( !s ) { + return 0; + } + + len = 0; + p = s; + while( *p ) { + int esc; + esc = idStr::IsEscape ( p ); + if ( esc ) { + p += esc; + continue; + } + p++; + len++; + } + + return len; +} + +/* +================ +idStr::RemoveEscapes +================ +*/ +char *idStr::RemoveEscapes( char *string, int escapes ) { + char *d; + char *s; + int c; + + s = string; + d = string; + while( (c = *s) != 0 ) { + int esc; + int type; + esc = idStr::IsEscape( s, &type ); + if ( esc && (type & escapes) ) { + s += esc; + continue; + } + else { + *d++ = c; + if ( c == C_COLOR_ESCAPE && *(s+1) ) { + s++; + } + } + s++; + } + *d = '\0'; + + return string; +} + +/* +================ +idStr::IsEscape +================ +*/ +int idStr::IsEscape( const char *s, int* type ) { + if ( !s || *s != C_COLOR_ESCAPE || *(s+1) == C_COLOR_ESCAPE ) { + return 0; + } + if ( type ) { + *type = S_ESCAPE_UNKNOWN; + } + switch ( *(s+1) ) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': + if ( type ) { + *type = S_ESCAPE_COLORINDEX; + } + return 2; + + case '-': case '+': + if ( type ) { + *type = S_ESCAPE_COLOR; + } + return 2; + + case 'r': case 'R': + if ( type ) { + *type = S_ESCAPE_COMMAND; + } + return 2; + + case 'c': case 'C': + if ( *(s+2) ) { + if ( *(s+3) ) { + if ( *(s+4) ) { + if ( type ) { + *type = S_ESCAPE_COLOR; + } + return 5; + } + } + } + return 0; + + case 'n': case 'N': + if ( type ) { + *type = S_ESCAPE_COMMAND; + } + if ( *(s+2) ) { + return 3; + } + return 0; + + case 'i': case 'I': + if ( *(s+2) && *(s+3) && *(s+4) ) { + if ( type ) { + *type = S_ESCAPE_ICON; + } + return 5; + } + return 0; + } + return 0; +} + +// RAVEN END + +/* +================ +idStr::snPrintf +================ +*/ +int idStr::snPrintf( char *dest, int size, const char *fmt, ...) { + int len; + va_list argptr; + char buffer[32000]; // big, but small enough to fit in PPC stack + + va_start( argptr, fmt ); + len = vsprintf( buffer, fmt, argptr ); + va_end( argptr ); + if ( len >= sizeof( buffer ) ) { + idLib::common->Error( "idStr::snPrintf: overflowed buffer" ); + } + if ( len >= size ) { + idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size ); + len = size; + } + idStr::Copynz( dest, buffer, size ); + return len; +} + +/* +============ +idStr::vsnPrintf + +vsnprintf portability: + +C99 standard: vsnprintf returns the number of characters (excluding the trailing +'\0') which would have been written to the final string if enough space had been available +snprintf and vsnprintf do not write more than size bytes (including the trailing '\0') + +win32: _vsnprintf returns the number of characters written, not including the terminating null character, +or a negative value if an output error occurs. If the number of characters to write exceeds count, then count +characters are written and -1 is returned and no trailing '\0' is added. + +idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0) +or returns -1 on failure or if the buffer would be overflowed. +============ +*/ +int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) { + int ret; + +#ifdef _WIN32 +#undef _vsnprintf + ret = _vsnprintf( dest, size-1, fmt, argptr ); +#define _vsnprintf use_idStr_vsnPrintf +#else +#undef vsnprintf + ret = vsnprintf( dest, size, fmt, argptr ); +#define vsnprintf use_idStr_vsnPrintf +#endif + dest[size-1] = '\0'; + if ( ret < 0 || ret >= size ) { + return -1; + } + return ret; +} + +/* +============ +sprintf + +Sets the value of the string using a printf interface. +============ +*/ +int sprintf( idStr &string, const char *fmt, ... ) { + int l; + va_list argptr; + char buffer[32000]; + + va_start( argptr, fmt ); + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + va_end( argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +vsprintf + +Sets the value of the string using a vprintf interface. +============ +*/ +int vsprintf( idStr &string, const char *fmt, va_list argptr ) { + int l; + char buffer[32000]; + + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +va + +does a varargs printf into a temp buffer +NOTE: not thread safe +============ +*/ +char *va( const char *fmt, ... ) { + va_list argptr; + static int index = 0; +// RAVEN BEGIN +// scork: tweaked from 4 to 8, since one of my funcs uses it twice. Better safe than sorry + static char string[VA_NUM_BUFS][VA_BUF_LEN]; // in case called by nested functions + char *buf; + + buf = string[index]; + index = (index + 1) & 7; +// RAVEN END + + va_start( argptr, fmt ); + vsprintf( buf, fmt, argptr ); + va_end( argptr ); + + return buf; +} + +#ifdef _WIN32 +/* +============ +fe + +Used for formatting windows errors +NOTE: not thread safe +============ +*/ +// RAVEN BEGIN +// abahr +char *fe( int errorId ) { + static int index = 0; + static char string[4][256]; // in case called by nested functions + char *buf; + + buf = string[index]; + index = (index + 1) & 3; +#ifndef _XBOX + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorId, + 0, // Default language + (LPTSTR) buf, + 256, + NULL + ); +#endif + return buf; +} +#endif +// RAVEN END + +/* +============ +idStr::BestUnit +============ +*/ +int idStr::BestUnit( const char *format, float value, Measure_t measure ) { + int unit = 1; + while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) { + unit++; + } + unit--; + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; + return unit; +} + +/* +============ +idStr::SetUnit +============ +*/ +void idStr::SetUnit( const char *format, float value, int unit, Measure_t measure ) { + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; +} + +/* +================ +idStr::InitMemory +================ +*/ +void idStr::InitMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + stringDataAllocator.Init(); +#endif +} + +/* +================ +idStr::ShutdownMemory +================ +*/ +void idStr::ShutdownMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Shutdown(); +#endif +} + +/* +================ +idStr::PurgeMemory +================ +*/ +void idStr::PurgeMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.FreeEmptyBaseBlocks(); +#endif +} + +/* +================ +idStr::ShowMemoryUsage_f +================ +*/ +void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) { +#ifdef USE_STRING_DATA_ALLOCATOR + idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n", + stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10, + stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks() ); +#endif +} + +/* +================ +idStr::FormatNumber +================ +*/ +struct formatList_t { + int gran; + int count; +}; + +// elements of list need to decend in size +formatList_t formatList[] = { + { 1000000000, 0 }, + { 1000000, 0 }, + { 1000, 0 } +}; + +int numFormatList = sizeof(formatList) / sizeof( formatList[0] ); + + +idStr idStr::FormatNumber( int number ) { + idStr string; + bool hit; + + // reset + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + li->count = 0; + } + + // main loop + do { + hit = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( number >= li->gran ) { + li->count++; + number -= li->gran; + hit = true; + break; + } + } + } while ( hit ); + + // print out + bool found = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( li->count ) { + if ( !found ) { + string += va( "%i,", li->count ); + } else { + string += va( "%3.3i,", li->count ); + } + found = true; + } + else if ( found ) { + string += va( "%3.3i,", li->count ); + } + } + + if ( found ) { + string += va( "%3.3i", number ); + } + else { + string += va( "%i", number ); + } + + // pad to proper size + int count = 11 - string.Length(); + + for ( int i = 0; i < count; i++ ) { + string.Insert( " ", 0 ); + } + + return string; +} + +// RAVEN BEGIN +// abahr +/* +================ +idStr::Split +================ +*/ +void idStr::Split( const char* source, idList& list, const char delimiter, const char groupDelimiter ) { + const idStr localSource( source ); + int sourceLength = localSource.Length(); + idStr element; + int startIndex = 0; + int endIndex = -1; + char currentChar = '\0'; + + list.Clear(); + while( startIndex < sourceLength ) { + currentChar = localSource[ startIndex ]; + if( currentChar == groupDelimiter ) { + endIndex = localSource.Find( groupDelimiter, ++startIndex ); + if( endIndex == -1 ) { + common->Error( "Couldn't find expected char %c in idStr::Split\n", groupDelimiter ); + } + element = localSource.Mid( startIndex, endIndex ); + element.Strip( groupDelimiter ); + list.Append( element ); + element.Clear(); + startIndex = endIndex + 1; + continue; + } else if( currentChar == delimiter ) { + element += '\0'; + list.Append( element ); + element.Clear(); + endIndex = ++startIndex; + continue; + } + + startIndex++; + element += currentChar; + } + + if( element.Length() ) { + element += '\0'; + list.Append( element ); + } +} + +/* +================ +idStr::Split +================ +*/ +void idStr::Split( idList& list, const char delimiter, const char groupDelimiter ) { + Split( c_str(), list, delimiter, groupDelimiter ); +} +// RAVEN END + +idStr idStr::GetLastColorCode( void ) const { + for ( int i = Length(); i > 0; i-- ) { + int escapeType = 0; + int escapeLength = idStr::IsEscape( &data[i-1], &escapeType ); + + if ( escapeLength && ( escapeType == S_ESCAPE_COLORINDEX || S_ESCAPE_COLOR ) ) { + idStr result = ""; + result.Append( &data[i-1], escapeLength ); + return result; + } + } + + return ""; +} diff --git a/source/idlib/Str.h b/source/idlib/Str.h new file mode 100644 index 0000000..23b741a --- /dev/null +++ b/source/idlib/Str.h @@ -0,0 +1,1100 @@ + +#ifndef __STR_H__ +#define __STR_H__ + +/* +=============================================================================== + + Character string + +=============================================================================== +*/ + +// these library functions should not be used for cross platform compatibility +#define strcmp idStr::Cmp // use_idStr_Cmp +#define strncmp use_idStr_Cmpn +#define StrCmpN use_idStr_Cmpn +#define strcmpi use_idStr_Icmp +#define StrCmpI use_idStr_Icmp +#define stricmp idStr::Icmp // use_idStr_Icmp +#define _stricmp use_idStr_Icmp +#define strcasecmp use_idStr_Icmp +#define strnicmp use_idStr_Icmpn +#define _strnicmp use_idStr_Icmpn +#define _memicmp use_idStr_Icmpn +#define StrCmpNI use_idStr_Icmpn +#define snprintf use_idStr_snPrintf +#define _snprintf use_idStr_snPrintf +#define vsnprintf use_idStr_vsnPrintf +#define _vsnprintf use_idStr_vsnPrintf + +#ifdef _XENON +class __declspec(align(16)) idVec4; +#else +class idVec4; +#endif + +#ifndef FILE_HASH_SIZE +#define FILE_HASH_SIZE 1024 +#endif + +// color escape character +const int C_COLOR_ESCAPE = '^'; +const int C_COLOR_DEFAULT = '0'; +const int C_COLOR_RED = '1'; +const int C_COLOR_GREEN = '2'; +const int C_COLOR_YELLOW = '3'; +const int C_COLOR_BLUE = '4'; +const int C_COLOR_CYAN = '5'; +const int C_COLOR_MAGENTA = '6'; +const int C_COLOR_WHITE = '7'; +const int C_COLOR_GRAY = '8'; +const int C_COLOR_BLACK = '9'; +// RAVEN BEGIN +// bdube: added +const int C_COLOR_CONSOLE = ':'; +// RAVEN END + +// color escape string +#define S_COLOR_DEFAULT "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" +#define S_COLOR_GRAY "^8" +#define S_COLOR_BLACK "^9" +// RAVEN BEGIN +// bdube: added +#define S_COLOR_CONSOLE "^:" +// ddynerman: team colors +#define S_COLOR_MARINE "^c683" +#define S_COLOR_STROGG "^c950" +#define S_COLOR_ALERT "^c920" +// ddynerman: MP icons +#define I_VOICE_ENABLED "^ivce" +#define I_VOICE_DISABLED "^ivcd" +#define I_FRIEND_ENABLED "^ifde" +#define I_FRIEND_DISABLED "^ifdd" +#define I_FLAG_MARINE "^iflm" +#define I_FLAG_STROGG "^ifls" +#define I_READY "^iyrd" +#define I_NOT_READY "^inrd" +// shouchard: server browser stuff +#define I_SERVER_DEDICATED "^ids0" +#define I_SERVER_DEDICATED_PB "^idsp" +#define I_SERVER_LOCKED "^isl0" +#define I_SERVER_FAVORITE "^isf0" +const int VA_BUF_LEN = 16384; +const int VA_NUM_BUFS = 8; +// RAVEN END + +// make idStr a multiple of 16 bytes long +// don't make too large to keep memory requirements to a minimum +const int STR_ALLOC_BASE = 20; +const int STR_ALLOC_GRAN = 32; + +typedef enum { + MEASURE_SIZE = 0, + MEASURE_BANDWIDTH +} Measure_t; + +// RAVEN BEGIN +// bdube: string escape codes +#define S_ESCAPE_UNKNOWN BIT(0) +#define S_ESCAPE_COLOR BIT(1) +#define S_ESCAPE_COLORINDEX BIT(2) +#define S_ESCAPE_ICON BIT(3) +#define S_ESCAPE_COMMAND BIT(4) +#define S_ESCAPE_ALL ( S_ESCAPE_COLOR | S_ESCAPE_COLORINDEX | S_ESCAPE_ICON | S_ESCAPE_COMMAND ) +// RAVEN END + +class idStr { + +public: + idStr( void ); + idStr( const idStr &text ); + idStr( const idStr &text, int start, int end ); + idStr( const char *text ); + idStr( const char *text, int start, int end ); + explicit idStr( const bool b ); + explicit idStr( const char c ); + explicit idStr( const int i ); + explicit idStr( const unsigned u ); + explicit idStr( const float f ); + ~idStr( void ); + + size_t Size( void ) const; + const char * c_str( void ) const; + operator const char *( void ) const; + operator const char *( void ); + + char operator[]( int index ) const; + char & operator[]( int index ); + + void operator=( const idStr &text ); + void operator=( const char *text ); + + friend idStr operator+( const idStr &a, const idStr &b ); + friend idStr operator+( const idStr &a, const char *b ); + friend idStr operator+( const char *a, const idStr &b ); + + friend idStr operator+( const idStr &a, const float b ); + friend idStr operator+( const idStr &a, const int b ); + friend idStr operator+( const idStr &a, const unsigned b ); + friend idStr operator+( const idStr &a, const bool b ); + friend idStr operator+( const idStr &a, const char b ); + + idStr & operator+=( const idStr &a ); + idStr & operator+=( const char *a ); + idStr & operator+=( const float a ); + idStr & operator+=( const char a ); + idStr & operator+=( const int a ); + idStr & operator+=( const unsigned a ); + idStr & operator+=( const bool a ); + + // case sensitive compare + friend bool operator==( const idStr &a, const idStr &b ); + friend bool operator==( const idStr &a, const char *b ); + friend bool operator==( const char *a, const idStr &b ); + + // case sensitive compare + friend bool operator!=( const idStr &a, const idStr &b ); + friend bool operator!=( const idStr &a, const char *b ); + friend bool operator!=( const char *a, const idStr &b ); + + // case sensitive compare + int Cmp( const char *text ) const; + int Cmpn( const char *text, int n ) const; + int CmpPrefix( const char *text ) const; + + // case insensitive compare + int Icmp( const char *text ) const; + int Icmpn( const char *text, int n ) const; + int IcmpPrefix( const char *text ) const; + + // case insensitive compare ignoring color +// RAVEN BEGIN +// bdube: changed to escapes + int IcmpNoEscape( const char *text ) const; +// RAVEN END + + // compares paths and makes sure folders come first + int IcmpPath( const char *text ) const; + int IcmpnPath( const char *text, int n ) const; + int IcmpPrefixPath( const char *text ) const; + + int Length( void ) const; + int Allocated( void ) const; + void Empty( void ); + bool IsEmpty( void ) const; + void Clear( void ); + void Append( const char a ); + void Append( const idStr &text ); + void Append( const char *text ); + void Append( const char *text, int len ); + void Insert( const char a, int index ); + void Insert( const char *text, int index ); + void ToLower( void ); + void ToUpper( void ); + bool IsNumeric( void ) const; + bool HasLower( void ) const; + bool HasUpper( void ) const; +// RAVEN BEGIN +// bdube: escapes + int IsEscape( int* type = NULL ) const; + int LengthWithoutEscapes ( void ) const; + idStr & RemoveEscapes ( int escapes = S_ESCAPE_ALL ); +// RAVEN END + void CapLength( int ); + void Fill( const char ch, int newlen ); + int Find( const char c, int start = 0, int end = -1 ) const; + int Find( const char *text, bool casesensitive = true, int start = 0, int end = -1 ) const; + bool Filter( const char *filter, bool casesensitive = true ) const; + int Last( const char c ) const; // return the index to the last occurance of 'c', returns -1 if not found + const char * Left( int len, idStr &result ) const; // store the leftmost 'len' characters in the result + const char * Right( int len, idStr &result ) const; // store the rightmost 'len' characters in the result + const char * Mid( int start, int len, idStr &result ) const; // store 'len' characters starting at 'start' in result + idStr Left( int len ) const; // return the leftmost 'len' characters + idStr Right( int len ) const; // return the rightmost 'len' characters + idStr Mid( int start, int len ) const; // return 'len' characters starting at 'start' + void StripLeading( const char c ); // strip char from front as many times as the char occurs + void StripLeading( const char *string ); // strip string from front as many times as the string occurs + bool StripLeadingOnce( const char *string ); // strip string from front just once if it occurs + void StripTrailing( const char c ); // strip char from end as many times as the char occurs + void StripTrailing( const char *string ); // strip string from end as many times as the string occurs + bool StripTrailingOnce( const char *string ); // strip string from end just once if it occurs + void Strip( const char c ); // strip char from front and end as many times as the char occurs + void Strip( const char *string ); // strip string from front and end as many times as the string occurs + void StripTrailingWhitespace( void ); // strip trailing white space characters +// RAVEN BEGIN + void StripUntil( const char c ); // strip until this character is reached or there is no string left +// RAVEN END + idStr & StripQuotes( void ); // strip quotes around string +// RAVEN BEGIN +// scork: added replacement-count return + int Replace( const char *old, const char *nw ); +// RAVEN END + + + // file name methods + int FileNameHash( void ) const; // hash key for the filename (skips extension) + idStr & BackSlashesToSlashes( void ); // convert slashes +// RAVEN BEGIN +// jscott: + idStr & SlashesToBackSlashes( void ); // convert slashes +// nmckenzie: char swapping routine + bool HasChar( const char check ); + bool HasChars( const char *check ); + idStr & ReplaceChar( const char from, const char to ); + idStr & ReplaceChars( const char *from, const char to ); +// RAVEN END + idStr & SetFileExtension( const char *extension ); // set the given file extension + idStr & StripFileExtension( void ); // remove any file extension + idStr & StripAbsoluteFileExtension( void ); // remove any file extension looking from front (useful if there are multiple .'s) + idStr & DefaultFileExtension( const char *extension ); // if there's no file extension use the default + idStr & DefaultPath( const char *basepath ); // if there's no path use the default + void AppendPath( const char *text ); // append a partial path + idStr & StripFilename( void ); // remove the filename from a path + idStr & StripPath( void ); // remove the path from the filename + void ExtractFilePath( idStr &dest ) const; // copy the file path to another string + void ExtractFileName( idStr &dest ) const; // copy the filename to another string + void ExtractFileBase( idStr &dest ) const; // copy the filename minus the extension to another string + void ExtractFileExtension( idStr &dest ) const; // copy the file extension to another string + bool CheckExtension( const char *ext ); + void ScrubFileName( void ); // Turns a bad file name into a good one or your money back + +// RAVEN BEGIN +// jscott: like the declManager version, but globally accessable + void MakeNameCanonical( void ); + void EnsurePrintable( void ); +// RAVEN END + +// RAVEN BEGIN +// abahr + void Split( idList& list, const char delimiter = ',', const char groupDelimiter = '\'' ); +// RAVEN END + + // char * methods to replace library functions + static int Length( const char *s ); + static char * ToLower( char *s ); + static char * ToUpper( char *s ); + static bool IsNumeric( const char *s ); + static bool HasLower( const char *s ); + static bool HasUpper( const char *s ); +// RAVEN BEGIN +// bdube: escape codes + static int IsEscape( const char *s, int* type = NULL ); + static int LengthWithoutEscapes( const char *s ); + static char * RemoveEscapes( char *s, int escapes = S_ESCAPE_ALL ); +// RAVEN END + static int Cmp( const char *s1, const char *s2 ); + static int Cmpn( const char *s1, const char *s2, int n ); + static int Icmp( const char *s1, const char *s2 ); + static int Icmpn( const char *s1, const char *s2, int n ); +// RAVEN BEGIN +// bdube: escapes + static int IcmpNoEscape( const char *s1, const char *s2 ); +// RAVEN END + static int IcmpPath( const char *s1, const char *s2 ); // compares paths and makes sure folders come first + static int IcmpnPath( const char *s1, const char *s2, int n ); // compares paths and makes sure folders come first + static void Append( char *dest, int size, const char *src ); + static void Copynz( char *dest, const char *src, int destsize ); + static int snPrintf( char *dest, int size, const char *fmt, ... ) id_attribute((format(printf,3,4))); + static int vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ); + static int FindChar( const char *str, const char c, int start = 0, int end = -1 ); + static int FindText( const char *str, const char *text, bool casesensitive = true, int start = 0, int end = -1 ); + static bool Filter( const char *filter, const char *name, bool casesensitive ); + static void StripMediaName( const char *name, idStr &mediaName ); + static bool CheckExtension( const char *name, const char *ext ); + static const char * FloatArrayToString( const float *array, const int length, const int precision ); + + // hash keys + static int Hash( const char *string ); + static int Hash( const char *string, int length ); + static int IHash( const char *string ); // case insensitive + static int IHash( const char *string, int length ); // case insensitive + + // character methods + static char ToLower( byte c ); + static char ToUpper( byte c ); + static bool CharIsPrintable( byte c ); + static bool CharIsLower( byte c ); + static bool CharIsUpper( byte c ); + static bool CharIsAlpha( byte c ); + static bool CharIsNumeric( byte c ); + static bool CharIsNewLine( byte c ); + static bool CharIsTab( byte c ); + static int ColorIndex( int c ); + static idVec4 & ColorForIndex( int i ); + + friend int sprintf( idStr &dest, const char *fmt, ... ); + friend int vsprintf( idStr &dest, const char *fmt, va_list ap ); + + void ReAllocate( int amount, bool keepold ); // reallocate string data buffer + void FreeData( void ); // free allocated string memory + + // format value in the given measurement with the best unit, returns the best unit + int BestUnit( const char *format, float value, Measure_t measure ); + // format value in the requested unit and measurement + void SetUnit( const char *format, float value, int unit, Measure_t measure ); + + static void InitMemory( void ); + static void ShutdownMemory( void ); + static void PurgeMemory( void ); + static void ShowMemoryUsage_f( const idCmdArgs &args ); + + int DynamicMemoryUsed() const; + static idStr FormatNumber( int number ); + + static void Split( const char* source, idList& list, const char delimiter = ',', const char groupDelimiter = '\'' ); + + idStr GetLastColorCode( void ) const; + +protected: + int len; + char * data; + int alloced; + char baseBuffer[ STR_ALLOC_BASE ]; + + void Init( void ); // initialize string using base buffer + void EnsureAlloced( int amount, bool keepold = true ); // ensure string data buffer is large anough + +// RAVEN BEGIN +public: + static const bool printableCharacter[256]; + static const char upperCaseCharacter[256]; + static const char lowerCaseCharacter[256]; +// RAVEN END +}; + +char * va( const char *fmt, ... ) id_attribute((format(printf,1,2))); + +// RAVEN BEGIN +// abahr +char* fe( int errorId ); +// RAVEN END + +ID_INLINE void idStr::EnsureAlloced( int amount, bool keepold ) { + if ( amount > alloced ) { + ReAllocate( amount, keepold ); + } +} + +ID_INLINE void idStr::Init( void ) { + len = 0; + alloced = STR_ALLOC_BASE; + data = baseBuffer; + data[ 0 ] = '\0'; +#ifdef ID_DEBUG_MEMORY + memset( baseBuffer, 0, sizeof( baseBuffer ) ); +#endif +} + +ID_INLINE idStr::idStr( void ) { + Init(); +} + +ID_INLINE idStr::idStr( const idStr &text ) { + int l; + + Init(); + l = text.Length(); + EnsureAlloced( l + 1 ); + strcpy( data, text.data ); + len = l; +} + +ID_INLINE idStr::idStr( const idStr &text, int start, int end ) { + int i; + int l; + + Init(); + if ( end > text.Length() ) { + end = text.Length(); + } + if ( start > text.Length() ) { + start = text.Length(); + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const char *text ) { + int l; + + Init(); + if ( text ) { + l = strlen( text ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; + } +} + +ID_INLINE idStr::idStr( const char *text, int start, int end ) { + int i; + int l = strlen( text ); + + Init(); + if ( end > l ) { + end = l; + } + if ( start > l ) { + start = l; + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const bool b ) { + Init(); + EnsureAlloced( 2 ); + data[ 0 ] = b ? '1' : '0'; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const char c ) { + Init(); + EnsureAlloced( 2 ); + data[ 0 ] = c; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const int i ) { + char text[ 64 ]; + int l; + + Init(); + l = sprintf( text, "%d", i ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const unsigned u ) { + char text[ 64 ]; + int l; + + Init(); + l = sprintf( text, "%u", u ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const float f ) { + char text[ 64 ]; + int l; + + Init(); + l = idStr::snPrintf( text, sizeof( text ), "%f", f ); + while( l > 0 && text[l-1] == '0' ) text[--l] = '\0'; + while( l > 0 && text[l-1] == '.' ) text[--l] = '\0'; + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::~idStr( void ) { + FreeData(); +} + +ID_INLINE size_t idStr::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +ID_INLINE const char *idStr::c_str( void ) const { + return data; +} + +ID_INLINE idStr::operator const char *( void ) { + return c_str(); +} + +ID_INLINE idStr::operator const char *( void ) const { + return c_str(); +} + +ID_INLINE char idStr::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE char &idStr::operator[]( int index ) { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE void idStr::operator=( const idStr &text ) { + int l; + + l = text.Length(); + EnsureAlloced( l + 1, false ); + memcpy( data, text.data, l ); + data[l] = '\0'; + len = l; +} + +ID_INLINE idStr operator+( const idStr &a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char *b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const char *a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const bool b ) { + idStr result( a ); + result.Append( b ? "true" : "false" ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const float b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%f", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const int b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%d", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const unsigned b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%u", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr &idStr::operator+=( const float a ) { + char text[ 64 ]; + + sprintf( text, "%f", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const int a ) { + char text[ 64 ]; + + sprintf( text, "%d", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const unsigned a ) { + char text[ 64 ]; + + sprintf( text, "%u", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const idStr &a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char *a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const bool a ) { + Append( a ? "true" : "false" ); + return *this; +} + +ID_INLINE bool operator==( const idStr &a, const idStr &b ) { + return ( !idStr::Cmp( a.data, b.data ) ); +} + +ID_INLINE bool operator==( const idStr &a, const char *b ) { + assert( b ); + return ( !idStr::Cmp( a.data, b ) ); +} + +ID_INLINE bool operator==( const char *a, const idStr &b ) { + assert( a ); + return ( !idStr::Cmp( a, b.data ) ); +} + +ID_INLINE bool operator!=( const idStr &a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const idStr &a, const char *b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const char *a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE int idStr::Cmp( const char *text ) const { + assert( text ); + return idStr::Cmp( data, text ); +} + +ID_INLINE int idStr::Cmpn( const char *text, int n ) const { + assert( text ); + return idStr::Cmpn( data, text, n ); +} + +ID_INLINE int idStr::CmpPrefix( const char *text ) const { + assert( text ); + return idStr::Cmpn( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Icmp( const char *text ) const { + assert( text ); + return idStr::Icmp( data, text ); +} + +ID_INLINE int idStr::Icmpn( const char *text, int n ) const { + assert( text ); + return idStr::Icmpn( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefix( const char *text ) const { + assert( text ); + return idStr::Icmpn( data, text, strlen( text ) ); +} + +// RAVEN BEGIN +// bdube: escapes +ID_INLINE int idStr::IcmpNoEscape( const char *text ) const { + assert( text ); + return idStr::IcmpNoEscape( data, text ); +} +// RAVEN END + +ID_INLINE int idStr::IcmpPath( const char *text ) const { + assert( text ); + return idStr::IcmpPath( data, text ); +} + +ID_INLINE int idStr::IcmpnPath( const char *text, int n ) const { + assert( text ); + return idStr::IcmpnPath( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefixPath( const char *text ) const { + assert( text ); + return idStr::IcmpnPath( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Length( void ) const { + return len; +} + +ID_INLINE int idStr::Allocated( void ) const { + if ( data != baseBuffer ) { + return alloced; + } else { + return 0; + } +} + +ID_INLINE void idStr::Empty( void ) { + EnsureAlloced( 1 ); + data[ 0 ] = '\0'; + len = 0; +} + +ID_INLINE bool idStr::IsEmpty( void ) const { + return ( idStr::Cmp( data, "" ) == 0 ); +} + +ID_INLINE void idStr::Clear( void ) { + FreeData(); + Init(); +} + +ID_INLINE void idStr::Append( const char a ) { + EnsureAlloced( len + 2 ); + data[ len ] = a; + len++; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const idStr &text ) { + int newLen; + int i; + + newLen = len + text.Length(); + EnsureAlloced( newLen + 1 ); + for ( i = 0; i < text.len; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const char *text ) { + int newLen; + int i; + + if ( text ) { + newLen = len + strlen( text ); + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ]; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Append( const char *text, int l ) { + int newLen; + int i; + + if ( text && l ) { + newLen = len + l; + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ] && i < l; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Insert( const char a, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = 1; + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + data[index] = a; + len++; +} + +ID_INLINE void idStr::Insert( const char *text, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = strlen( text ); + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + for ( i = 0; i < l; i++ ) { + data[index+i] = text[i]; + } + len += l; +} + +ID_INLINE void idStr::ToLower( void ) { + for (int i = 0; data[i]; i++ ) { +// RAVEN BEGIN + data[i] = ToLower( data[i] ); +// RAVEN END + } +} + +ID_INLINE void idStr::ToUpper( void ) { + for (int i = 0; data[i]; i++ ) { +// RAVEN BEGIN + data[i] = ToUpper( data[i] ); +// RAVEN END + } +} + +ID_INLINE bool idStr::IsNumeric( void ) const { + return idStr::IsNumeric( data ); +} + +ID_INLINE bool idStr::HasLower( void ) const { + return idStr::HasLower( data ); +} + +ID_INLINE bool idStr::HasUpper( void ) const { + return idStr::HasUpper( data ); +} + +ID_INLINE int idStr::IsEscape( int* type ) const { + return idStr::IsEscape( data, type ); +} +ID_INLINE idStr &idStr::RemoveEscapes ( int escapes ) { + idStr::RemoveEscapes( data, escapes ); + len = Length( c_str() ); + return *this; +} +ID_INLINE int idStr::LengthWithoutEscapes( void ) const { + return idStr::LengthWithoutEscapes( data ); +} +// RAVEN END + +ID_INLINE void idStr::CapLength( int newlen ) { + if ( len <= newlen ) { + return; + } + data[ newlen ] = 0; + len = newlen; +} + +ID_INLINE void idStr::Fill( const char ch, int newlen ) { + EnsureAlloced( newlen + 1 ); + len = newlen; + memset( data, ch, len ); + data[ len ] = 0; +} + +ID_INLINE int idStr::Find( const char c, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindChar( data, c, start, end ); +} + +ID_INLINE int idStr::Find( const char *text, bool casesensitive, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindText( data, text, casesensitive, start, end ); +} + +ID_INLINE bool idStr::Filter( const char *filter, bool casesensitive ) const { + return idStr::Filter( filter, data, casesensitive ); +} + +ID_INLINE const char *idStr::Left( int len, idStr &result ) const { + return Mid( 0, len, result ); +} + +ID_INLINE const char *idStr::Right( int len, idStr &result ) const { + if ( len >= Length() ) { + result = *this; + return result; + } + return Mid( Length() - len, len, result ); +} + +ID_INLINE idStr idStr::Left( int len ) const { + return Mid( 0, len ); +} + +ID_INLINE idStr idStr::Right( int len ) const { + if ( len >= Length() ) { + return *this; + } + return Mid( Length() - len, len ); +} + +ID_INLINE void idStr::Strip( const char c ) { + StripLeading( c ); + StripTrailing( c ); +} + +ID_INLINE void idStr::Strip( const char *string ) { + StripLeading( string ); + StripTrailing( string ); +} + +ID_INLINE bool idStr::CheckExtension( const char *ext ) { + return idStr::CheckExtension( data, ext ); +} + +ID_INLINE int idStr::Length( const char *s ) { + int i; + for ( i = 0; s[i]; i++ ) {} + return i; +} + +ID_INLINE char *idStr::ToLower( char *s ) { + for ( int i = 0; s[i]; i++ ) { +// RAVEN BEGIN + s[i] = ToLower( s[i] ); +// RAVEN END + } + return s; +} + +ID_INLINE char *idStr::ToUpper( char *s ) { + for ( int i = 0; s[i]; i++ ) { +// RAVEN BEGIN + s[i] = ToUpper( s[i] ); +// RAVEN END + } + return s; +} + +ID_INLINE int idStr::Hash( const char *string ) { + int i, hash = 0; + for ( i = 0; *string != '\0'; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::Hash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string ) { + int i, hash = 0; + for( i = 0; *string != '\0'; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE char idStr::ToLower( byte c ) { +// RAVEN BEGIN + if( lowerCaseCharacter[c] ) { + return lowerCaseCharacter[c]; + } +// RAVEN END + return c; +} + +ID_INLINE char idStr::ToUpper( byte c ) { +// RAVEN BEGIN + if( upperCaseCharacter[c] ) { + return upperCaseCharacter[c]; + } +// RAVEN END + return c; +} + +ID_INLINE bool idStr::CharIsPrintable( byte c ) { +// RAVEN BEGIN + return printableCharacter[c]; +// RAVEN END +} + +ID_INLINE bool idStr::CharIsLower( byte c ) { +// RAVEN BEGIN + return !!lowerCaseCharacter[c] && ( lowerCaseCharacter[c] == c ); +// RAVEN END +} + +ID_INLINE bool idStr::CharIsUpper( byte c ) { +// RAVEN BEGIN + return !!upperCaseCharacter[c] && ( upperCaseCharacter[c] == c ); +// RAVEN END +} + +ID_INLINE bool idStr::CharIsAlpha( byte c ) { + // test for regular ascii and western European high-ascii chars + return !!upperCaseCharacter[c]; +} + +ID_INLINE bool idStr::CharIsNumeric( byte c ) { + return ( c <= '9' && c >= '0' ); +} + +ID_INLINE bool idStr::CharIsNewLine( byte c ) { + return ( c == '\n' || c == '\r' || c == '\v' ); +} + +ID_INLINE bool idStr::CharIsTab( byte c ) { + return ( c == '\t' ); +} + +ID_INLINE int idStr::ColorIndex( int c ) { + return ( c & 15 ); +} + +ID_INLINE int idStr::DynamicMemoryUsed() const { + return ( data == baseBuffer ) ? 0 : alloced; +} + +#endif /* !__STR_H__ */ diff --git a/source/idlib/TextCompiler.cpp b/source/idlib/TextCompiler.cpp new file mode 100644 index 0000000..f8934c6 --- /dev/null +++ b/source/idlib/TextCompiler.cpp @@ -0,0 +1,2 @@ +#include "precompiled.h" +#pragma hdrstop diff --git a/source/idlib/TextCompiler.h b/source/idlib/TextCompiler.h new file mode 100644 index 0000000..9e9fd11 --- /dev/null +++ b/source/idlib/TextCompiler.h @@ -0,0 +1,138 @@ +#ifndef __TEXTCOMPILER_H__ +#define __TEXTCOMPILER_H__ + +namespace TextCompiler +{ + // Write an indirect value + template inline void WriteValue(type const * const ptr, idFile *out, bool byteSwap=false) + { + if(out != NULL) + { + if(!byteSwap) + { + out->Write(ptr, sizeof(type)); + } + else + { + byte const * const p = (byte *)ptr; + + for(int i=sizeof(type)-1;i>=0;i--) + { + out->Write(&(p[i]), 1); + } + } + + } + } + + // Write a direct value + template inline void WriteValue(type const val, idFile *out, bool byteSwap=false) + { + if(out != NULL) + { + if(!byteSwap) + { + out->Write(&val, sizeof(type)); + } + else + { + byte const * const p = (byte *)&val; + + for(int i=sizeof(type)-1;i>=0;i--) + { + out->Write(&(p[i]), 1); + } + } + } + } + + template inline type ReadValue(idFile *in) + { + type ret; + + in->Read(&ret, sizeof(type)); + return ret; + } + + // specialization write for idStr's + template <> inline void WriteValue(idStr const * const ptr, idFile *out, bool byteSwap) + { + if(out != NULL) + { + // if less than 32, then we don't need a trailing null + // this is because 5 bits can be used to represent string length in the token header + // zero length strings end up needing to be null terminated + if((ptr->Length() < 32) && (ptr->Length() != 0)) + { + out->Write(ptr->c_str(), ptr->Length()); + } + else + { + out->Write(ptr->c_str(), ptr->Length()+1); + } + } + } + + // specialization read for idStr's + template <> inline idStr ReadValue(idFile *in) + { + char c; + idStr str; + + in->Read(&c, 1); + while(c != '\0') + { + str.Append(c); + in->Read(&c, 1); + } + + str.Append(c); + + return str; + } + + template inline void WriteArray(type const * const ptr, unsigned int count, idFile *out) + { + WriteValue(&count, out); + for(unsigned int i=0;i(&(ptr[i]), out); + } + } + + template inline type *ReadArray(idFile *in, unsigned int *count=NULL) + { + unsigned int len; + len = ReadValue(in); + type *buffer = (type *)malloc(len*sizeof(type)); + type *ptr = buffer; + for(unsigned int i=0;i(in); + ptr++; + } + if(count != NULL) + *count = len; + return buffer; + } + + template inline void WriteIdList(idList const * const ptr, idFile *out) + { + WriteArray(ptr->Ptr(), ptr->Num(), out); + } + + template inline idList ReadIdList(idFile *in) + { + idList ret; + ret.Clear(); + unsigned int count; + idAutoPtr array(ReadArray(in, &count)); + + for(unsigned int i=0;iClear(); + } +} + +/* +================= +idTimerReport::AddTime +================= +*/ +void idTimerReport::AddTime( const char *name, idTimer *time ) { + assert ( timers.Num() == names.Num() ); + int i; + for ( i = 0; i < names.Num(); i++ ) { + if ( names[i].Icmp( name ) == 0 ) { + *timers[i] += *time; + break; + } + } + if ( i == names.Num() ) { + int index = AddReport( name ); + if ( index >= 0 ) { + timers[index]->Clear(); + *timers[index] += *time; + } + } +} + +/* +================= +idTimerReport::PrintReport +================= +*/ +void idTimerReport::PrintReport() { + assert( timers.Num() == names.Num() ); + idLib::common->Printf( "Timing Report for %s\n", reportName.c_str() ); + idLib::common->Printf( "-------------------------------\n" ); + float total = 0.0f; + for ( int i = 0; i < names.Num(); i++ ) { + idLib::common->Printf( "%s consumed %5.2f seconds\n", names[i].c_str(), timers[i]->Milliseconds() * 0.001f ); + total += timers[i]->Milliseconds(); + } + idLib::common->Printf( "Total time for report %s was %5.2f\n\n", reportName.c_str(), total * 0.001f ); +} diff --git a/source/idlib/Timer.h b/source/idlib/Timer.h new file mode 100644 index 0000000..2cb5e46 --- /dev/null +++ b/source/idlib/Timer.h @@ -0,0 +1,196 @@ + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +/* +=============================================================================== + + Clock tick counter. Should only be used for profiling. + +=============================================================================== +*/ + +class idTimer { +public: + idTimer( void ); + idTimer( double clockTicks ); + ~idTimer( void ); + + idTimer operator+( const idTimer &t ) const; + idTimer operator-( const idTimer &t ) const; + idTimer & operator+=( const idTimer &t ); + idTimer & operator-=( const idTimer &t ); + + void Start( void ); + void Stop( void ); + void Clear( void ); + double ClockTicks( void ) const; + double Milliseconds( void ) const; + +private: + static double base; + enum { + TS_STARTED, + TS_STOPPED + } state; + double start; + double clockTicks; + + void InitBaseClockTicks( void ) const; +}; + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer( void ) { + state = TS_STOPPED; + clockTicks = 0.0; +} + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer( double _clockTicks ) { + state = TS_STOPPED; + clockTicks = _clockTicks; +} + +/* +================= +idTimer::~idTimer +================= +*/ +ID_INLINE idTimer::~idTimer( void ) { +} + +/* +================= +idTimer::operator+ +================= +*/ +ID_INLINE idTimer idTimer::operator+( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks + t.clockTicks ); +} + +/* +================= +idTimer::operator- +================= +*/ +ID_INLINE idTimer idTimer::operator-( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks - t.clockTicks ); +} + +/* +================= +idTimer::operator+= +================= +*/ +ID_INLINE idTimer &idTimer::operator+=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks += t.clockTicks; + return *this; +} + +/* +================= +idTimer::operator-= +================= +*/ +ID_INLINE idTimer &idTimer::operator-=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks -= t.clockTicks; + return *this; +} + +/* +================= +idTimer::Start +================= +*/ +ID_INLINE void idTimer::Start( void ) { + assert( state == TS_STOPPED ); + state = TS_STARTED; + start = idLib::sys->GetClockTicks(); +} + +/* +================= +idTimer::Stop +================= +*/ +ID_INLINE void idTimer::Stop( void ) { + assert( state == TS_STARTED ); + clockTicks += idLib::sys->GetClockTicks() - start; + if ( base < 0.0 ) { + InitBaseClockTicks(); + } + if ( clockTicks > base ) { + clockTicks -= base; + } + state = TS_STOPPED; +} + +/* +================= +idTimer::Clear +================= +*/ +ID_INLINE void idTimer::Clear( void ) { + clockTicks = 0.0; +} + +/* +================= +idTimer::ClockTicks +================= +*/ +ID_INLINE double idTimer::ClockTicks( void ) const { + assert( state == TS_STOPPED ); + return clockTicks; +} + +/* +================= +idTimer::Milliseconds +================= +*/ +ID_INLINE double idTimer::Milliseconds( void ) const { + assert( state == TS_STOPPED ); + return clockTicks / ( idLib::sys->ClockTicksPerSecond() * 0.001 ); +} + + +/* +=============================================================================== + + Report of multiple named timers. + +=============================================================================== +*/ + +class idTimerReport { +public: + idTimerReport( void ); + ~idTimerReport( void ); + + void SetReportName( const char *name ); + int AddReport( const char *name ); + void Clear( void ); + void Reset( void ); + void PrintReport( void ); + void AddTime( const char *name, idTimer *time ); + +private: + idListtimers; + idStrList names; + idStr reportName; +}; + +#endif /* !__TIMER_H__ */ diff --git a/source/idlib/TimingCollection.cpp b/source/idlib/TimingCollection.cpp new file mode 100644 index 0000000..713cf47 --- /dev/null +++ b/source/idlib/TimingCollection.cpp @@ -0,0 +1,398 @@ + +#include "precompiled.h" +#pragma hdrstop + +#if !defined(__TIMINGCOLLECTION_H_) + #include "TimingCollection.h" +#endif + +#ifdef RV_TIMERCOLLECTION + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::Clear +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::Clear( void ) +{ + mTotalUpdates = 0; + mCurValue = 0; + mTotalValue = 0; + mPeakValue = 0; + mLimit = 0; + mLimitExceeded = 0; + mLimitExceededTimesFive = 0; + mDisplayLevel = 0; + mName = ""; + mParentName = ""; + + mStartFile = ""; + mStartLine = 0; + mEndFile = ""; + mEndLine = 0; +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::rvSingleTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming::rvSingleTiming() +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::rvSingleTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming::rvSingleTiming( idStr &newName ) +{ + Clear(); + mName = newName; +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::OutputDataToFile +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::OutputDataToFile( idFile *file, int framesRecorded ) +{ + char buffer[1024]; + idStr outputName = ""; + + for( int i = 0; i < mDisplayLevel; i++ ) + { + outputName += "**"; + } + outputName += mName; + + sprintf(buffer, "%-36s %-9.3f %-9.3f %-9.3f %-9.3f %-9i %-9i %-9.3f\n", outputName.c_str(), + (float)mTotalValue, (float)( mTotalValue / (float)framesRecorded), (float)mPeakValue, + (float)mLimit, mLimitExceeded, mLimitExceededTimesFive, (float)(mTotalUpdates / (float)framesRecorded) ); + file->Write ( buffer, strlen( buffer ) ); +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::OutputInfoToFile +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::OutputInfoToFile( idFile *file ) +{ + char buffer[1024]; + + sprintf(buffer, "Name: %s\nParent: %s\n", mName.c_str(), mParentName.c_str() ); + file->Write ( buffer, strlen( buffer ) ); + + sprintf(buffer, "Starting at %s(%d)\nEnding at %s(%d)\n\n", mStartFile.c_str(), mStartLine, mEndFile.c_str(), mEndLine ); + file->Write ( buffer, strlen( buffer ) ); +} + + + + + + + + + + + + + + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::Clear +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::Clear( void ) +{ + mUpdates = 0; + mCurTimer = 0; + mFramesRecorded = 0; + mCurrentlyUpdating = 0; + mTimings.Clear(); + mTimingsIndex.Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::rvTimingCollection +// +//----------------------------------------------------------------------------- + +rvTimingCollection::rvTimingCollection() +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::GetTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming *rvTimingCollection::GetTiming( idStr &timingName ) +{ + int *handle = NULL; + + if( mTimingsIndex.Get( timingName, &handle ) ) + { + return( &mTimings[*handle] ); + } + + rvSingleTiming newTiming( timingName ); + int index = mTimings.Num(); + + mTimingsIndex.Set( timingName, index ); + mTimings.Append( newTiming ); + return &mTimings[index]; +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::DisplayTimingValues +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::DisplayTimingValues( void ) { + // Fixme: the display resulting from this is really, really a mess - quite unreadable. + common->Printf ( "Timer: \n" ); + for( int i = 0; i < mTimings.Num(); i++ ) { + if ( (int)(mTimings[i].mCurValue) ) { + common->Printf( "\t%s %3i\n", mTimings[i].mName.c_str(), (int)(mTimings[i].mCurValue) ); + } + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::OutputToFile +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::OutputToFile( void ) +{ + idFile *file = NULL; + idStr name; + char buffer[1024]; + + // Fixme: Do we have any good information for building a better file name here? + + name = "Timings/output.txt"; + + file = fileSystem->OpenFileWrite ( name ); + if ( !file ) + { + return; + } + + sprintf( buffer, "Total frames = %d\n\n", mFramesRecorded ); + file->Write( buffer, strlen( buffer ) ); + + sprintf( buffer, "%-36s %-9s %-9s %-9s %-9s %-9s %-9s %-9s\n\n", + "Name", "Total", "Average", "Peak", "Limit", "Exceeded", "Exceed*5", "Calls" ); + file->Write ( buffer, strlen( buffer ) ); + + for( int i = 0; i < mTimings.Num(); i++ ) + { + mTimings[i].OutputDataToFile( file, mFramesRecorded ); + if( !( ( i + 1 )%3) ) // break up the prints into groups of 3 to make scanning visually easier. + { + sprintf( buffer, "\n" ); + file->Write( buffer, strlen( buffer ) ); + } + } + + sprintf(buffer, "\n\nInformation about categories\n\n" ); + file->Write ( buffer, strlen( buffer ) ); + + for( int i = 0; i < mTimings.Num(); i++ ) + { + mTimings[i].OutputInfoToFile( file ); + } + + fileSystem->CloseFile ( file ); + file = NULL; +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::AppendToDict +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::AppendToDict( idDict *dict ) +{ + if( !mCurrentlyUpdating ) + { + return; + } + + for( int i = 0; i < mTimings.Num(); i++ ) + { + dict->Set ( mTimings[i].mName.c_str(), va( "%0.3g\t%0.3g\t", mTimings[i].mCurValue, mTimings[i].mTotalValue ) ); + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::InitFrame +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !inUse ) + { + if( mCurrentlyUpdating ) + { + if( outputFileWhenDone ) + { + OutputToFile(); + } + mCurrentlyUpdating = 0; + } + return; + } + + mCurrentlyUpdating = 1; + mUpdates++; + mFramesRecorded++; + + if( mUpdates >= 10 && displayText ) + { + // ?? If we display output every single frame, we get REALLY bogged down. It is bad. + + DisplayTimingValues(); + + mUpdates = 0; + + return; + } + + // Clear only the curValues and record information. + + for( int i = 0; i < mTimings.Num(); i++ ) + { + if( mTimings[i].mCurValue > mTimings[i].mPeakValue ) + { + mTimings[i].mPeakValue = mTimings[i].mCurValue; + } + + if( mTimings[i].mLimit > 0.0 && mTimings[i].mCurValue > mTimings[i].mLimit ) + { + mTimings[i].mLimitExceeded++; + } + + if( mTimings[i].mLimit > 0.0 && mTimings[i].mCurValue > mTimings[i].mLimit * 5 ) + { + mTimings[i].mLimitExceededTimesFive++; + } + + mTimings[i].mCurValue = 0.0f; + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::TimingStart +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::_TimingStart( const char *timingName, const char *fileName, const int lineNum ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !mCurrentlyUpdating ) + { + return; + } + + // Go up the timer list. + + mCurTimer++; + assert( mCurTimer < MAX_TIMERS ); + + // Keep track of the current function being timed in case we nest and need to know our parent. + + mTimerName[mCurTimer] = timingName; + + // Set the information about this timer. + + rvSingleTiming *curTiming = GetTiming( mTimerName[mCurTimer] ); + + if( mCurTimer == 0 ) + { + curTiming->mParentName = "base"; + } + else + { + curTiming->mParentName = mTimerName[mCurTimer - 1]; + } + curTiming->mStartFile = fileName; + curTiming->mStartLine = lineNum; + curTiming->mDisplayLevel = mCurTimer - 1; + + // Start the timer; do it last to avoid timing the rest of this function. + + mTimer[mCurTimer].Clear(); + mTimer[mCurTimer].Start(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::TimingEnd +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::_TimingStop( double msecLimit, const char *fileName, const int lineNum ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !mCurrentlyUpdating ) + { + return; + } + + // Stop our timer first so that we don't log time from the rest of this function. + + mTimer[mCurTimer].Stop(); + + // Update incidental information on the timer. + + rvSingleTiming *curTiming = GetTiming( mTimerName[mCurTimer] ); + + curTiming->mEndFile = fileName; + curTiming->mEndLine = lineNum; + + curTiming->mLimit = msecLimit; + + // Add the actual timing values to the rvSingleTiming + + double frameTime = mTimer[mCurTimer].Milliseconds(); + + curTiming->mCurValue += frameTime; + curTiming->mTotalValue += frameTime; + + curTiming->mTotalUpdates++; + + // Go back down the timer list. + + mCurTimer--; + assert( mCurTimer >= 0 ); +} + +#endif diff --git a/source/idlib/TimingCollection.h b/source/idlib/TimingCollection.h new file mode 100644 index 0000000..88fca50 --- /dev/null +++ b/source/idlib/TimingCollection.h @@ -0,0 +1,100 @@ +#ifndef __TIMINGCOLLECTION_H_ +#define __TIMINGCOLLECTION_H_ + +// Fixme! Move this. It doesn't belong in AI at all, obviously. +#include "../idlib/Str.h" +#include "../idlib/containers/List.h" +#include "../idlib/containers/HashTable.h" +#include "../framework/File.h" +#include "Timer.h" + +//----------------------------------------------------------------------------- +// +// rvTimingCollection +// +//----------------------------------------------------------------------------- + +#define RV_TIMERCOLLECTION + +#define START_PROFILING(a, b) (a)._TimingStart(b, __FILE__, __LINE__) +#define STOP_PROFILING(a, b) (a)._TimingStop(b, __FILE__, __LINE__) + +// Easy and quick way to remove all of this from the codebase +#ifdef RV_TIMERCOLLECTION + +#define MAX_TIMERS 16 + +class rvSingleTiming +{ +public: + int mTotalUpdates; + double mCurValue; + double mTotalValue; + double mPeakValue; + double mLimit; + int mLimitExceeded; + int mLimitExceededTimesFive; + int mDisplayLevel; + + idStr mName; + idStr mParentName; + + idStr mStartFile; + int mStartLine; + idStr mEndFile; + int mEndLine; + + rvSingleTiming(); + rvSingleTiming( idStr &newName ); + + void Clear(); + void OutputDataToFile( idFile *file, int framesRecorded ); + void OutputInfoToFile( idFile *file ); +}; + +class rvTimingCollection +{ +protected: + idTimer mTimer[MAX_TIMERS]; + idStr mTimerName[MAX_TIMERS]; + + idList mTimings; + idHashTable mTimingsIndex; + + int mCurTimer; + int mUpdates; + int mCurrentlyUpdating; + int mFramesRecorded; + + rvSingleTiming *GetTiming( idStr &timingName ); + void DisplayTimingValues( void ); + void OutputToFile( void ); + +public: + rvTimingCollection(); + + void InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ); + void _TimingStart( const char *timingName, const char *fileName, const int lineNum ); + void _TimingStop( double msecLimit, const char *fileName, const int lineNum ); + void AppendToDict( idDict* dict ); + void Clear( void ); +}; + +#else + +class rvTimingCollection +{ +protected: +public: + rvTimingCollection(){} + + void InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ){} + void _TimingStart( const char *timingName, const char *fileName, const int lineNum ){} + void _TimingStop( double msecLimit, const char *fileName, const int lineNum ){} + void AppendToDict( idDict* dict ){} + void Clear( void ){} +}; + +#endif + +#endif diff --git a/source/idlib/Token.cpp b/source/idlib/Token.cpp new file mode 100644 index 0000000..fdb0a6c --- /dev/null +++ b/source/idlib/Token.cpp @@ -0,0 +1,152 @@ + +#include "precompiled.h" +#pragma hdrstop + + +/* +================ +idToken::NumberValue +================ +*/ +void idToken::NumberValue( void ) { + int i, pow, div, c; + const char *p; + double m; + + assert( type == TT_NUMBER ); + p = c_str(); + floatvalue = 0; + intvalue = 0; + // floating point number + if ( subtype & TT_FLOAT ) { + if ( subtype & ( TT_INFINITE | TT_INDEFINITE | TT_NAN ) ) { + if ( subtype & TT_INFINITE ) { // 1.#INF + unsigned int inf = 0x7f800000; + floatvalue = (double) *(float*)&inf; + } + else if ( subtype & TT_INDEFINITE ) { // 1.#IND + unsigned int ind = 0xffc00000; + floatvalue = (double) *(float*)&ind; + } + else if ( subtype & TT_NAN ) { // 1.#QNAN + unsigned int nan = 0x7fc00000; + floatvalue = (double) *(float*)&nan; + } + } + else { + while( *p && *p != '.' && *p != 'e' ) { + floatvalue = floatvalue * 10.0 + (double) (*p - '0'); + p++; + } + if ( *p == '.' ) { + p++; + for( m = 0.1; *p && *p != 'e'; p++ ) { + floatvalue = floatvalue + (double) (*p - '0') * m; + m *= 0.1; + } + } + if ( *p == 'e' ) { + p++; + if ( *p == '-' ) { + div = true; + p++; + } + else if ( *p == '+' ) { + div = false; + p++; + } + else { + div = false; + } + pow = 0; + for ( pow = 0; *p; p++ ) { + pow = pow * 10 + (int) (*p - '0'); + } + for ( m = 1.0, i = 0; i < pow; i++ ) { + m *= 10.0; + } + if ( div ) { + floatvalue /= m; + } + else { + floatvalue *= m; + } + } + } + intvalue = idMath::Ftoi( floatvalue ); + } + else if ( subtype & TT_DECIMAL ) { + while( *p ) { + intvalue = intvalue * 10 + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_IPADDRESS ) { + c = 0; + while( *p && *p != ':' ) { + if ( *p == '.' ) { + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + c = 0; + } + else { + intvalue = intvalue * 10 + (*p - '0'); + c++; + } + p++; + } + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_OCTAL ) { + // step over the first zero + p += 1; + while( *p ) { + intvalue = (intvalue << 3) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_HEX ) { + // step over the leading 0x or 0X + p += 2; + while( *p ) { + intvalue <<= 4; + if (*p >= 'a' && *p <= 'f') + intvalue += *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + intvalue += *p - 'A' + 10; + else + intvalue += *p - '0'; + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_BINARY ) { + // step over the leading 0b or 0B + p += 2; + while( *p ) { + intvalue = (intvalue << 1) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + subtype |= TT_VALUESVALID; +} + +/* +================ +idToken::ClearTokenWhiteSpace +================ +*/ +void idToken::ClearTokenWhiteSpace( void ) { + whiteSpaceStart_p = NULL; + whiteSpaceEnd_p = NULL; + linesCrossed = 0; +} diff --git a/source/idlib/Token.h b/source/idlib/Token.h new file mode 100644 index 0000000..b46237a --- /dev/null +++ b/source/idlib/Token.h @@ -0,0 +1,239 @@ + +#ifndef __TOKEN_H__ +#define __TOKEN_H__ + +/* +=============================================================================== + + idToken is a token read from a file or memory with idLexer or idParser + +=============================================================================== +*/ +// RAVEN BEGIN +// jsinger: defines used by the binary lexer and WriteBinaryToken method of idLexer + +// binary token types +// low order 3 bits form the type +#define BTT_STRING 0 +#define BTT_LITERAL 1 +#define BTT_NUMBER 2 +#define BTT_NAME 3 +#define BTT_PUNCTUATION 4 +#define BTT_PUNCTUATION2 5 // when punctuation, least significant bit is used as part of the punctuation type +// 3 bits +#define BTT_SUBTYPE_INT 0 +#define BTT_SUBTYPE_UNSIGNEDINT 1 +#define BTT_SUBTYPE_LONG 2 +#define BTT_SUBTYPE_UNSIGNEDLONG 3 +#define BTT_SUBTYPE_FLOAT 4 +#define BTT_SUBTYPE_DOUBLE 5 +#define BTT_SUBTYPE_IPADDRESS 6 +#define BTT_SUBTYPE_IPPORT 7 + +// 2 bits +#define BTT_STORED_1BYTE 0 +#define BTT_STORED_2BYTE 1 +#define BTT_STORED_4BYTE 2 +#define BTT_STORED_8BYTE 3 + +// retrieval macros +#define BTT_GET_TYPE(x) ((x) & 0x07) +#define BTT_GET_SUBTYPE(x) (((x) & 0x38)>>3) +#define BTT_GET_STORED_SIZE(x) (((x) & 0xC0)>>6) +#define BTT_GET_STRING_LENGTH(x) (((x) & 0xFC)>>3) +#define BTT_GET_PUNCTUATION(x) (((x)&1)|(((x)>>2)&~1)) + +// punctuation types +#define BTT_PUNC_ASNULLTERMINATED 0 +#define BTT_PUNC_RIGHTPAREN 1 +#define BTT_PUNC_LEFTBRACE 2 +#define BTT_PUNC_RIGHTBRACE 3 +#define BTT_PUNC_MINUS 4 +#define BTT_PUNC_PLUS 5 +#define BTT_PUNC_COMMA 6 +#define BTT_PUNC_PLUSPLUS 7 +#define BTT_PUNC_LEFTBRACKET 8 +#define BTT_PUNC_RIGHTBRACKET 9 +#define BTT_PUNC_EQUAL 10 +#define BTT_PUNC_EQUALEQUAL 11 +#define BTT_PUNC_NOTEQUAL 12 +#define BTT_PUNC_PERCENT 13 +#define BTT_PUNC_LESSTHAN 14 +#define BTT_PUNC_GREATERTHAN 15 +#define BTT_PUNC_LOGICALAND 16 +#define BTT_PUNC_AMPERSAND 17 +#define BTT_PUNC_MINUSMINUS 18 +#define BTT_PUNC_HASH 19 +#define BTT_PUNC_LESSOREQUAL 20 +#define BTT_PUNC_GREATEROREQUAL 21 +#define BTT_PUNC_FORWARDSLASH 22 +#define BTT_PUNC_SHIFTLEFT 23 +#define BTT_PUNC_SHIFTRIGHT 24 +#define BTT_PUNC_LEFTPAREN 25 +#define BTT_PUNC_SEMICOLON 26 +#define BTT_PUNC_ASTERISK 27 +#define BTT_PUNC_PERIOD 28 +#define BTT_PUNC_DOLLARSIGN 29 +#define BTT_PUNC_PLUSEQUAL 30 +#define BTT_PUNC_MINUSEQUAL 31 +#define BTT_PUNC_TILDE 32 +#define BTT_PUNC_EXCLAMATION 33 +#define BTT_PUNC_PIPE 34 +#define BTT_PUNC_BACKSLASH 35 +#define BTT_PUNC_DOUBLEHASH 36 +#define BTT_PUNC_DOUBLECOLON 37 +#define BTT_PUNC_TIMESEQUAL 38 +#define BTT_PUNC_DOUBLEPIPE 39 +#define BTT_PUNC_INVERTEDPLING 40 +#define BTT_PUNC_INVERTEDQUERY 41 + +// set macro +#define BTT_MAKENUMBER_PREFIX(type, subtype, storedsize) ((unsigned char)((type)|(subtype<<3)|(storedsize<<6))) +#define BTT_MAKESTRING_PREFIX(type, length) ((unsigned char)((type)|((((length)<32)?(length):0)<<3))) +#define BTT_MAKEPUNCTUATION_PREFIX(punc) (((punc<<2)&~7)|(punc&1))|BTT_PUNCTUATION +// RAVEN END + +// token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +// number sub types +#define TT_INTEGER 0x00001 // integer +#define TT_DECIMAL 0x00002 // decimal number +#define TT_HEX 0x00004 // hexadecimal number +#define TT_OCTAL 0x00008 // octal number +#define TT_BINARY 0x00010 // binary number +#define TT_LONG 0x00020 // long int +#define TT_UNSIGNED 0x00040 // unsigned int +#define TT_FLOAT 0x00080 // floating point number +#define TT_SINGLE_PRECISION 0x00100 // float +#define TT_DOUBLE_PRECISION 0x00200 // double +#define TT_EXTENDED_PRECISION 0x00400 // long double +#define TT_INFINITE 0x00800 // infinite 1.#INF +#define TT_INDEFINITE 0x01000 // indefinite 1.#IND +#define TT_NAN 0x02000 // NaN +#define TT_IPADDRESS 0x04000 // ip address +#define TT_IPPORT 0x08000 // ip port +#define TT_VALUESVALID 0x10000 // set if intvalue and floatvalue are valid + +// string sub type is the length of the string +// literal sub type is the ASCII code +// punctuation sub type is the punctuation id +// name sub type is the length of the name + +class idToken : public idStr { + + friend class idParser; + friend class idLexer; +// RAVEN BEGIN +// jsinger: added to allow Lexer direct access to token internals as well + friend class Lexer; +// RAVEN END + +public: + int type; // token type + int subtype; // token sub type + int line; // line in script the token was on + int linesCrossed; // number of lines crossed in white space before token + int flags; // token flags, used for recursive defines + +public: + idToken( void ); + idToken( const idToken *token ); + ~idToken( void ); + + void operator=( const idStr& text ); + void operator=( const char *text ); + + double GetDoubleValue( void ); // double value of TT_NUMBER + float GetFloatValue( void ); // float value of TT_NUMBER + unsigned long GetUnsignedLongValue( void ); // unsigned long value of TT_NUMBER + int GetIntValue( void ); // int value of TT_NUMBER + int WhiteSpaceBeforeToken( void ) const;// returns length of whitespace before token + void ClearTokenWhiteSpace( void ); // forget whitespace before token + + void NumberValue( void ); // calculate values for a TT_NUMBER + +private: + unsigned long intvalue; // integer value + double floatvalue; // floating point value + const char * whiteSpaceStart_p; // start of white space before token, only used by idLexer + const char * whiteSpaceEnd_p; // end of white space before token, only used by idLexer + idToken * next; // next token in chain, only used by idParser + + void AppendDirty( const char a ); // append character without adding trailing zero +}; + +// RAVEN BEGIN +// rjohnson: initialized floatvalue to prevent fpu exceptin + +ID_INLINE idToken::idToken( void ) : + floatvalue(0.0) +{ +} + +// RAVEN END + +ID_INLINE idToken::idToken( const idToken *token ) { + *this = *token; +} + +ID_INLINE idToken::~idToken( void ) { +} + +ID_INLINE void idToken::operator=( const char *text) { + *static_cast(this) = text; +} + +ID_INLINE void idToken::operator=( const idStr& text ) { + *static_cast(this) = text; +} + +ID_INLINE double idToken::GetDoubleValue( void ) { + if ( type != TT_NUMBER ) { + return 0.0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return floatvalue; +} + +ID_INLINE float idToken::GetFloatValue( void ) { + return (float) GetDoubleValue(); +} + +ID_INLINE unsigned long idToken::GetUnsignedLongValue( void ) { + if ( type != TT_NUMBER ) { + return 0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return intvalue; +} + +ID_INLINE int idToken::GetIntValue( void ) { + return (int) GetUnsignedLongValue(); +} + +ID_INLINE int idToken::WhiteSpaceBeforeToken( void ) const { + return ( whiteSpaceEnd_p > whiteSpaceStart_p ); +} + +ID_INLINE void idToken::AppendDirty( const char a ) { + EnsureAlloced( len + 2, true ); +// RAVEN BEGIN +// jscott: I hate slashes nearly as much as KRABS + if( a == '\\' ) { + data[len++] = '/'; + } else { + data[len++] = a; + } +// RAVEN END +} + +#endif /* !__TOKEN_H__ */ diff --git a/source/idlib/algorithms/MultifieldSort.h b/source/idlib/algorithms/MultifieldSort.h new file mode 100644 index 0000000..0127baa --- /dev/null +++ b/source/idlib/algorithms/MultifieldSort.h @@ -0,0 +1,200 @@ +//---------------------------------------------------------------- +// MultifieldSort.cpp +// +// Routines for performing multi-field sorts on idList's +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __MULTIFIELD_SORT_H__ +#define __MULTIFIELD_SORT_H__ + +template< class type > +class rvMultifieldSort { +public: + void Clear( void ); + void AddCompareFunction( typename idList::cmp_t* fn ); + void AddFilterFunction( typename idList::filter_t* fn ); + + void Sort( idList& list ); + +private: + void QSort_Iterative( idList& list, int low, int n ); + int Median3( type* list, int a, int b, int c ); + int Compare( const type* lhs, const type* rhs ); + + // sortingFields - pointers to sorting functions, in order to be sorted + idList::cmp_t*> sortingFields; + // filterFields - pointers to filter functions, in order to be filtered + idList::filter_t*> filterFields; +}; + +template< class type > +void rvMultifieldSort< type >::Clear( void ) { + sortingFields.Clear(); + filterFields.Clear(); +} + +template< class type > +int rvMultifieldSort< type >::Compare( const type* lhs, const type* rhs ) { + for( int i = 0; i < sortingFields.Num(); i++ ) { + int result = (*sortingFields[ i ])( lhs, rhs ); + if( result != 0 ) { + return result; + } + } + + // items are equal on all fields + return 0; +} + +template< class type > +ID_INLINE void rvMultifieldSort< type >::AddCompareFunction( typename idList::cmp_t* fn ) { + sortingFields.Append( fn ); +} + +template< class type > +ID_INLINE void rvMultifieldSort< type >::AddFilterFunction( typename idList::filter_t* fn ) { + filterFields.Append( fn ); +} + +template< class type > +void rvMultifieldSort< type >::Sort( idList& list ) { + int i, j; + + if ( filterFields.Num() ) { + for ( i = 0; i < list.Num(); i++ ) { + for ( j = 0; j < filterFields.Num(); j++ ) { + if( (*filterFields[ j ])( (const type*)(&list[ i ]) ) ) { + list.RemoveIndex( i ); + i--; + break; + } + } + } + } + QSort_Iterative( list, 0, list.Num() ); +} + +template< class type > +ID_INLINE int rvMultifieldSort< type >::Median3( type* list, int a, int b, int c ) { + return Compare( &list[ a ], &list[ b ] ) < 0 ? + ( Compare( &list[ b ], &list[ c ] ) < 0 ? b : ( Compare( &list[ a ], &list[ c ] ) < 0 ? c : a ) ) + : ( Compare( &list[ b ], &list[ c ] ) > 0 ? b : ( Compare( &list[ a ], &list[ c ] ) < 0 ? a : c )); +} + +template< class type > +void rvMultifieldSort< type >::QSort_Iterative( idList& list, int low, int n ) { + int swap_cnt, pivot, lowBound, highBound; + +loop: + swap_cnt = 0; + + // insertion sort on small arrays + if( n < 7 ) { + for( int i = low + 1; i < low + n; i++ ) { + for( int j = i; j > low && Compare( &list[ j - 1 ], &list[ j ] ) > 0; j-- ) { + type t = list[ j - 1 ]; + list[ j - 1 ] = list[ j ]; + list[ j ] = t; + } + } + return; + } + + // pivot selection + pivot = n / 2 + low; + if( n > 7 ) { + lowBound = low; + highBound = low + n - 1; + if (n > 40) { + int d = (n / 8); + lowBound = Median3( list.Ptr(), lowBound, lowBound + d, lowBound + 2 * d ); + pivot = Median3( list.Ptr(), pivot - d, pivot, pivot + d ); + highBound = Median3( list.Ptr(), highBound - 2 * d, highBound - d, highBound ); + } + pivot = Median3( list.Ptr(), lowBound, pivot, highBound ); + } + type t = list[ low ]; + list[ low ] = list[ pivot ]; + list[ pivot ] = t; + + // qsort + int leftA, leftB, rightC, rightD, r; + leftA = leftB = low + 1; + rightC = rightD = low + n - 1; + for (;;) { + while ( leftB <= rightC && (r = Compare( &list[ leftB ], &list[ low ] )) <= 0) { + if (r == 0) { + swap_cnt = 1; + type t = list[ leftA ]; + list[ leftA ] = list[ leftB ]; + list[ leftB ] = t; + leftA++; + } + leftB++; + } + while (leftB <= rightC && (r = Compare( &list[ rightC ], &list[ low ] )) >= 0) { + if (r == 0) { + swap_cnt = 1; + type t = list[ rightC ]; + list[ rightC ] = list[ rightD ]; + list[ rightD ] = t; + rightD--; + } + rightC--; + } + if( leftB > rightC ) { + break; + } + type t = list[ leftB ]; + list[ leftB ] = list[ rightC ]; + list[ rightC ] = t; + swap_cnt = 1; + leftB++; + rightC--; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for( int i = low + 1; i < low + n; i++ ) { + for( int j = i; j > low && Compare( &list[ j - 1 ], &list[ j ] ) > 0; j-- ) { + type t = list[ j ]; + list[ j ] = list[ j - 1 ]; + list[ j - 1 ] = t; + } + } + return; + } + + highBound = low + n; + r = Min( leftA - low, leftB - leftA ); + for( int i = 0; i < r; i++ ) { + type t = list[ low + i ]; + list[ low + i ] = list[ leftB - r + i ]; + list[ leftB - r + i ] = t; + } + + r = Min( rightD - rightC, highBound - rightD - 1 ); + for( int i = 0; i < r; i++ ) { + type t = list[ leftB + i ]; + list[ leftB + i ] = list[ highBound - r + i ]; + list[ highBound - r + i ] = t; + } + + if ( (r = leftB - leftA) > 1 ) { + QSort_Iterative( list, low, r ); + } + if ((r = rightD - rightC) > 1) { + /* Iterate rather than recurse to save stack space */ + low = highBound - r; + n = r; + goto loop; + } + /* qsort(pn - r, r / es, es, cmp);*/ +} + + + +///////// + +#endif + diff --git a/source/idlib/bv/Bounds.cpp b/source/idlib/bv/Bounds.cpp new file mode 100644 index 0000000..b7025b4 --- /dev/null +++ b/source/idlib/bv/Bounds.cpp @@ -0,0 +1,468 @@ + +#include "../precompiled.h" +#pragma hdrstop + +idBounds bounds_zero( vec3_zero, vec3_zero ); + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius( void ) const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius( const idVec3 ¢er ) const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( center[i] - b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] - center[i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +================ +idBounds::PlaneDistance +================ +*/ +float idBounds::PlaneDistance( const idPlane &plane ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +// RAVEN BEGIN +// jscott: for BSE attenuation +/* +================ +idBounds::ShortestDistance +================ +*/ +float idBounds::ShortestDistance( const idVec3 &point ) const { + + int i; + float delta, distance; + + if( ContainsPoint( point ) ) { + + return( 0.0f ); + } + + distance = 0.0f; + for( i = 0; i < 3; i++ ) { + + if( point[i] < b[0][i] ) { + + delta = b[0][i] - point[i]; + distance += delta * delta; + + } else if ( point[i] > b[1][i] ) { + + delta = point[i] - b[1][i]; + distance += delta * delta; + } + } + + return( idMath::Sqrt( distance ) ); +} + +// abahr: +idVec3 idBounds::FindEdgePoint( const idVec3& dir ) const { + return FindEdgePoint( GetCenter(), dir ); +} + +idVec3 idBounds::FindEdgePoint( const idVec3& start, const idVec3& dir ) const { + idVec3 center( GetCenter() ); + float radius = GetRadius( center ); + idVec3 point( start + dir * radius ); + float scale = 0.0f; + + RayIntersection( point, -dir, scale ); + + idVec3 p = point + -dir * scale; + return p; +} + +idVec3 idBounds::FindVectorToEdge( const idVec3& dir ) const { + return idVec3( FindVectorToEdge(GetCenter(), dir) ); +} + +idVec3 idBounds::FindVectorToEdge( const idVec3& start, const idVec3& dir ) const { + return idVec3( FindEdgePoint(start, dir) - start ); +} +// RAVEN END + +/* +================ +idBounds::PlaneSide +================ +*/ +int idBounds::PlaneSide( const idPlane &plane, const float epsilon ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBounds::LineIntersection + + Returns true if the line intersects the bounds between the start and end point. +============ +*/ +bool idBounds::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float ld[3]; + idVec3 center = ( b[0] + b[1] ) * 0.5f; + idVec3 extents = b[1] - center; + idVec3 lineDir = 0.5f * ( end - start ); + idVec3 lineCenter = start + lineDir; + idVec3 dir = lineCenter - center; + + ld[0] = idMath::Fabs( lineDir[0] ); + if ( idMath::Fabs( dir[0] ) > extents[0] + ld[0] ) { + return false; + } + + ld[1] = idMath::Fabs( lineDir[1] ); + if ( idMath::Fabs( dir[1] ) > extents[1] + ld[1] ) { + return false; + } + + ld[2] = idMath::Fabs( lineDir[2] ); + if ( idMath::Fabs( dir[2] ) > extents[2] + ld[2] ) { + return false; + } + + idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross[0] ) > extents[1] * ld[2] + extents[2] * ld[1] ) { + return false; + } + + if ( idMath::Fabs( cross[1] ) > extents[0] * ld[2] + extents[2] * ld[0] ) { + return false; + } + + if ( idMath::Fabs( cross[2] ) > extents[0] * ld[1] + extents[1] * ld[0] ) { + return false; + } + + return true; +} + +/* +============ +idBounds::RayIntersection + + Returns true if the ray intersects the bounds. + The ray can intersect the bounds in both directions from the start point. + If start is inside the bounds it is considered an intersection with scale = 0 +============ +*/ +bool idBounds::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + int i, ax0, ax1, ax2, side, inside; + float f; + idVec3 hit; + + ax0 = -1; + inside = 0; + for ( i = 0; i < 3; i++ ) { + if ( start[i] < b[0][i] ) { + side = 0; + } + else if ( start[i] > b[1][i] ) { + side = 1; + } + else { + inside++; + continue; + } + if ( dir[i] == 0.0f ) { + continue; + } + f = ( start[i] - b[side][i] ); + if ( ax0 < 0 || idMath::Fabs( f ) > idMath::Fabs( scale * dir[i] ) ) { + scale = - ( f / dir[i] ); + ax0 = i; + } + } + + if ( ax0 < 0 ) { + scale = 0.0f; + // return true if the start point is inside the bounds + return ( inside == 3 ); + } + + ax1 = (ax0+1)%3; + ax2 = (ax0+2)%3; + hit[ax1] = start[ax1] + scale * dir[ax1]; + hit[ax2] = start[ax2] + scale * dir[ax2]; + + return ( hit[ax1] >= b[0][ax1] && hit[ax1] <= b[1][ax1] && + hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] ); +} + +/* +============ +idBounds::FromTransformedBounds +============ +*/ +void idBounds::FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + int i; + idVec3 center, extents, rotatedExtents; + + center = (bounds[0] + bounds[1]) * 0.5f; + extents = bounds[1] - center; + + for ( i = 0; i < 3; i++ ) { + rotatedExtents[i] = idMath::Fabs( extents[0] * axis[0][i] ) + + idMath::Fabs( extents[1] * axis[1][i] ) + + idMath::Fabs( extents[2] * axis[2][i] ); + } + + center = origin + center * axis; + b[0] = center - rotatedExtents; + b[1] = center + rotatedExtents; +} + +/* +============ +idBounds::FromPoints + + Most tight bounds for a point set. +============ +*/ +void idBounds::FromPoints( const idVec3 *points, const int numPoints ) { + SIMDProcessor->MinMax( b[0], b[1], points, numPoints ); +} + +/* +============ +idBounds::FromPointTranslation + + Most tight bounds for the translational movement of the given point. +============ +*/ +void idBounds::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] = point[i] + translation[i]; + b[1][i] = point[i]; + } + else { + b[0][i] = point[i]; + b[1][i] = point[i] + translation[i]; + } + } +} + +/* +============ +idBounds::FromBoundsTranslation + + Most tight bounds for the translational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ) { + int i; + + if ( axis.IsRotated() ) { + FromTransformedBounds( bounds, origin, axis ); + } + else { + b[0] = bounds[0] + origin; + b[1] = bounds[1] + origin; + } + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] += translation[i]; + } + else { + b[1][i] += translation[i]; + } + } +} + +/* +================ +BoundsForPointRotation + + only for rotations < 180 degrees +================ +*/ +idBounds BoundsForPointRotation( const idVec3 &start, const idRotation &rotation ) { + int i; + float radiusSqr; + idVec3 v1, v2; + idVec3 origin, axis, end; + idBounds bounds; + + end = start * rotation; + axis = rotation.GetVec(); + origin = rotation.GetOrigin() + axis * ( axis * ( start - rotation.GetOrigin() ) ); + radiusSqr = ( start - origin ).LengthSqr(); + v1 = ( start - origin ).Cross( axis ); + v2 = ( end - origin ).Cross( axis ); + + for ( i = 0; i < 3; i++ ) { + // if the derivative changes sign along this axis during the rotation from start to end + if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) { + if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) { + bounds[0][i] = Min( start[i], end[i] ); +// RAVEN BEGIN + bounds[1][i] = origin[i]; + if( axis[i] * axis[i] < 1.0f ) { + bounds[1][i] += idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } +// RAVEN END + } + else { +// RAVEN BEGIN + bounds[0][i] = origin[i]; + if( axis[i] * axis[i] < 1.0f ) { + bounds[0][i] -= idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } +// RAVEN END + bounds[1][i] = Max( start[i], end[i] ); + } + } + else if ( start[i] > end[i] ) { + bounds[0][i] = end[i]; + bounds[1][i] = start[i]; + } + else { + bounds[0][i] = start[i]; + bounds[1][i] = end[i]; + } + } + + return bounds; +} + +/* +============ +idBounds::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBounds::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + float radius; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + (*this) = BoundsForPointRotation( point, rotation ); + } + else { + + radius = ( point - rotation.GetOrigin() ).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::FromBoundsRotation + + Most tight bounds for the rotational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ) { + int i; + float radius; + idVec3 point; + idBounds rBounds; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + + (*this) = BoundsForPointRotation( bounds[0] * axis + origin, rotation ); + for ( i = 1; i < 8; i++ ) { + point[0] = bounds[(i^(i>>1))&1][0]; + point[1] = bounds[(i>>1)&1][1]; + point[2] = bounds[(i>>2)&1][2]; + (*this) += BoundsForPointRotation( point * axis + origin, rotation ); + } + } + else { + + point = (bounds[1] - bounds[0]) * 0.5f; + radius = (bounds[1] - point).Length() + (point - rotation.GetOrigin()).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::ToPoints +============ +*/ +void idBounds::ToPoints( idVec3 points[8] ) const { + for ( int i = 0; i < 8; i++ ) { + points[i][0] = b[(i^(i>>1))&1][0]; + points[i][1] = b[(i>>1)&1][1]; + points[i][2] = b[(i>>2)&1][2]; + } +} diff --git a/source/idlib/bv/Bounds.h b/source/idlib/bv/Bounds.h new file mode 100644 index 0000000..39b0ac5 --- /dev/null +++ b/source/idlib/bv/Bounds.h @@ -0,0 +1,449 @@ + +#ifndef __BV_BOUNDS_H__ +#define __BV_BOUNDS_H__ + +/* +=============================================================================== + + Axis Aligned Bounding Box + +=============================================================================== +*/ + +class idBounds { +public: + idBounds( void ); + explicit idBounds( const idVec3 &mins, const idVec3 &maxs ); + explicit idBounds( const idVec3 &point ); + + const idVec3 & operator[]( const int index ) const; + idVec3 & operator[]( const int index ); + idBounds operator+( const idVec3 &t ) const; // returns translated bounds + idBounds & operator+=( const idVec3 &t ); // translate the bounds + idBounds operator*( const idMat3 &r ) const; // returns rotated bounds + idBounds & operator*=( const idMat3 &r ); // rotate the bounds + idBounds operator+( const idBounds &a ) const; + idBounds & operator+=( const idBounds &a ); + idBounds operator-( const idBounds &a ) const; + idBounds & operator-=( const idBounds &a ); + + bool Compare( const idBounds &a ) const; // exact compare, no epsilon + bool Compare( const idBounds &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBounds &a ) const; // exact compare, no epsilon + bool operator!=( const idBounds &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out bounds + void Zero( void ); // single point at origin + + idVec3 GetCenter( void ) const; // returns center of bounds + float GetRadius( void ) const; // returns the radius relative to the bounds origin + float GetRadius( const idVec3 ¢er ) const; // returns the radius relative to the given center + float GetVolume( void ) const; // returns the volume of the bounds + bool IsCleared( void ) const; // returns true if bounds are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the bounds expanded + bool AddBounds( const idBounds &a ); // add the bounds, returns true if the bounds expanded + idBounds Intersect( const idBounds &a ) const; // return intersection of this bounds with the given bounds + idBounds & IntersectSelf( const idBounds &a ); // intersect this bounds with the given bounds + idBounds Expand( const float d ) const; // return bounds expanded in all directions with the given value + idBounds & ExpandSelf( const float d ); // expand bounds in all directions with the given value + +// RAVEN BEGIN +// jscott: for BSE + idBounds & ExpandSelf( const idVec3 &d ); // expand bounds in all directions by the given vector + const idVec3 Size( void ) const; + float ShortestDistance( const idVec3 &point ) const; + bool Contains( const idBounds &a ) const; +// jscott: for material types + int GetLargestAxis( void ) const; +// abahr: can be used to get width of bounds + idVec3 FindEdgePoint( const idVec3& dir ) const; + idVec3 FindEdgePoint( const idVec3& start, const idVec3& dir ) const; + idVec3 FindVectorToEdge( const idVec3& dir ) const; + idVec3 FindVectorToEdge( const idVec3& start, const idVec3& dir ) const; +// RAVEN END + + idBounds Translate( const idVec3 &translation ) const; // return translated bounds + idBounds & TranslateSelf( const idVec3 &translation ); // translate this bounds + idBounds Rotate( const idMat3 &rotation ) const; // return rotated bounds + idBounds & RotateSelf( const idMat3 &rotation ); // rotate this bounds + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBounds( const idBounds &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + + // most tight bounds for the given transformed bounds + void FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + // most tight bounds for a point set + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight bounds for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ); + // most tight bounds for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere( void ) const; + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const; + + idVec3 b[2]; +}; + +extern idBounds bounds_zero; + +ID_INLINE idBounds::idBounds( void ) { +} + +ID_INLINE idBounds::idBounds( const idVec3 &mins, const idVec3 &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idBounds::idBounds( const idVec3 &point ) { + b[0] = point; + b[1] = point; +} + +ID_INLINE const idVec3 &idBounds::operator[]( const int index ) const { + return b[index]; +} + +ID_INLINE idVec3 &idBounds::operator[]( const int index ) { + return b[index]; +} + +ID_INLINE idBounds idBounds::operator+( const idVec3 &t ) const { + return idBounds( b[0] + t, b[1] + t ); +} + +ID_INLINE idBounds &idBounds::operator+=( const idVec3 &t ) { + b[0] += t; + b[1] += t; + return *this; +} + +ID_INLINE idBounds idBounds::operator*( const idMat3 &r ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, r ); + return bounds; +} + +ID_INLINE idBounds &idBounds::operator*=( const idMat3 &r ) { + this->FromTransformedBounds( *this, vec3_origin, r ); + return *this; +} + +ID_INLINE idBounds idBounds::operator+( const idBounds &a ) const { + idBounds newBounds; + newBounds = *this; + newBounds.AddBounds( a ); + return newBounds; +} + +ID_INLINE idBounds &idBounds::operator+=( const idBounds &a ) { + idBounds::AddBounds( a ); + return *this; +} + +ID_INLINE idBounds idBounds::operator-( const idBounds &a ) const { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + return idBounds( idVec3( b[0][0] + a.b[1][0], b[0][1] + a.b[1][1], b[0][2] + a.b[1][2] ), + idVec3( b[1][0] + a.b[0][0], b[1][1] + a.b[0][1], b[1][2] + a.b[0][2] ) ); +} + +ID_INLINE idBounds &idBounds::operator-=( const idBounds &a ) { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + b[0] += a.b[1]; + b[1] += a.b[0]; + return *this; +} + +ID_INLINE bool idBounds::Compare( const idBounds &a ) const { + return ( b[0].Compare( a.b[0] ) && b[1].Compare( a.b[1] ) ); +} + +ID_INLINE bool idBounds::Compare( const idBounds &a, const float epsilon ) const { + return ( b[0].Compare( a.b[0], epsilon ) && b[1].Compare( a.b[1], epsilon ) ); +} + +ID_INLINE bool idBounds::operator==( const idBounds &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBounds::operator!=( const idBounds &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBounds::Clear( void ) { + b[0][0] = b[0][1] = b[0][2] = idMath::INFINITY; + b[1][0] = b[1][1] = b[1][2] = -idMath::INFINITY; +} + +ID_INLINE void idBounds::Zero( void ) { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE idVec3 idBounds::GetCenter( void ) const { + return idVec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE float idBounds::GetVolume( void ) const { + if ( b[0][0] >= b[1][0] || b[0][1] >= b[1][1] || b[0][2] >= b[1][2] ) { + return 0.0f; + } + return ( ( b[1][0] - b[0][0] ) * ( b[1][1] - b[0][1] ) * ( b[1][2] - b[0][2] ) ); +} + +ID_INLINE bool idBounds::IsCleared( void ) const { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool idBounds::AddPoint( const idVec3 &v ) { + bool expanded = false; + if ( v[0] < b[0][0]) { + b[0][0] = v[0]; + expanded = true; + } + if ( v[0] > b[1][0]) { + b[1][0] = v[0]; + expanded = true; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + expanded = true; + } + if ( v[1] > b[1][1]) { + b[1][1] = v[1]; + expanded = true; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + expanded = true; + } + if ( v[2] > b[1][2]) { + b[1][2] = v[2]; + expanded = true; + } + return expanded; +} + +ID_INLINE bool idBounds::AddBounds( const idBounds &a ) { + bool expanded = false; + if ( a.b[0][0] < b[0][0] ) { + b[0][0] = a.b[0][0]; + expanded = true; + } + if ( a.b[0][1] < b[0][1] ) { + b[0][1] = a.b[0][1]; + expanded = true; + } + if ( a.b[0][2] < b[0][2] ) { + b[0][2] = a.b[0][2]; + expanded = true; + } + if ( a.b[1][0] > b[1][0] ) { + b[1][0] = a.b[1][0]; + expanded = true; + } + if ( a.b[1][1] > b[1][1] ) { + b[1][1] = a.b[1][1]; + expanded = true; + } + if ( a.b[1][2] > b[1][2] ) { + b[1][2] = a.b[1][2]; + expanded = true; + } + return expanded; +} + +ID_INLINE idBounds idBounds::Intersect( const idBounds &a ) const { + idBounds n; + n.b[0][0] = ( a.b[0][0] > b[0][0] ) ? a.b[0][0] : b[0][0]; + n.b[0][1] = ( a.b[0][1] > b[0][1] ) ? a.b[0][1] : b[0][1]; + n.b[0][2] = ( a.b[0][2] > b[0][2] ) ? a.b[0][2] : b[0][2]; + n.b[1][0] = ( a.b[1][0] < b[1][0] ) ? a.b[1][0] : b[1][0]; + n.b[1][1] = ( a.b[1][1] < b[1][1] ) ? a.b[1][1] : b[1][1]; + n.b[1][2] = ( a.b[1][2] < b[1][2] ) ? a.b[1][2] : b[1][2]; + return n; +} + +ID_INLINE idBounds &idBounds::IntersectSelf( const idBounds &a ) { + if ( a.b[0][0] > b[0][0] ) { + b[0][0] = a.b[0][0]; + } + if ( a.b[0][1] > b[0][1] ) { + b[0][1] = a.b[0][1]; + } + if ( a.b[0][2] > b[0][2] ) { + b[0][2] = a.b[0][2]; + } + if ( a.b[1][0] < b[1][0] ) { + b[1][0] = a.b[1][0]; + } + if ( a.b[1][1] < b[1][1] ) { + b[1][1] = a.b[1][1]; + } + if ( a.b[1][2] < b[1][2] ) { + b[1][2] = a.b[1][2]; + } + return *this; +} + +ID_INLINE idBounds idBounds::Expand( const float d ) const { + return idBounds( idVec3( b[0][0] - d, b[0][1] - d, b[0][2] - d ), + idVec3( b[1][0] + d, b[1][1] + d, b[1][2] + d ) ); +} + +ID_INLINE idBounds &idBounds::ExpandSelf( const float d ) { + b[0][0] -= d; + b[0][1] -= d; + b[0][2] -= d; + b[1][0] += d; + b[1][1] += d; + b[1][2] += d; + return *this; +} + +// RAVEN BEGIN +// jscott: for BSE +ID_INLINE idBounds &idBounds::ExpandSelf( const idVec3 &d ) +{ + b[0][0] -= d[0]; + b[0][1] -= d[1]; + b[0][2] -= d[2]; + b[1][0] += d[0]; + b[1][1] += d[1]; + b[1][2] += d[2]; + return( *this ); +} + +ID_INLINE const idVec3 idBounds::Size( void ) const +{ + return( b[1] - b[0] ); +} + +ID_INLINE bool idBounds::Contains( const idBounds &a ) const +{ + int i; + + for( i = 0; i < 3; i++ ) { + if( a[1][i] > b[1][i] ) { + return( false ); + } + if( a[0][i] < b[0][i] ) { + return( false ); + } + } + + return( true ); +} + +// jscott: for material types +ID_INLINE int idBounds::GetLargestAxis( void ) const +{ + idVec3 work = b[1] - b[0]; + int axis = 0; + + if( work[1] > work[0] ) + { + axis = 1; + } + if( work[2] > work[axis] ) + { + axis = 2; + } + return( axis ); +} +// RAVEN END + +ID_INLINE idBounds idBounds::Translate( const idVec3 &translation ) const { + return idBounds( b[0] + translation, b[1] + translation ); +} + +ID_INLINE idBounds &idBounds::TranslateSelf( const idVec3 &translation ) { + b[0] += translation; + b[1] += translation; + return *this; +} + +ID_INLINE idBounds idBounds::Rotate( const idMat3 &rotation ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, rotation ); + return bounds; +} + +ID_INLINE idBounds &idBounds::RotateSelf( const idMat3 &rotation ) { + FromTransformedBounds( *this, vec3_origin, rotation ); + return *this; +} + +ID_INLINE bool idBounds::ContainsPoint( const idVec3 &p ) const { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool idBounds::IntersectsBounds( const idBounds &a ) const { + if ( a.b[1][0] < b[0][0] || a.b[1][1] < b[0][1] || a.b[1][2] < b[0][2] + || a.b[0][0] > b[1][0] || a.b[0][1] > b[1][1] || a.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBounds::ToSphere( void ) const { + idSphere sphere; + sphere.SetOrigin( ( b[0] + b[1] ) * 0.5f ); + sphere.SetRadius( ( b[1] - sphere.GetOrigin() ).Length() ); + return sphere; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * dir[0] ) + + idMath::Fabs( extents[1] * dir[1] ) + + idMath::Fabs( extents[2] * dir[2] ); + + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + center = origin + center * axis; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + + min = d1 - d2; + max = d1 + d2; +} + +#endif /* !__BV_BOUNDS_H__ */ diff --git a/source/idlib/bv/Box.cpp b/source/idlib/bv/Box.cpp new file mode 100644 index 0000000..85bbdd9 --- /dev/null +++ b/source/idlib/bv/Box.cpp @@ -0,0 +1,820 @@ + +#include "../precompiled.h" +#pragma hdrstop + +idBox box_zero( vec3_zero, vec3_zero, mat3_identity ); + +/* + 4---{4}---5 + + /| /| + Z {7} {8} {5} | + - / | / {9} + 7--{6}----6 | + | | | | + {11} 0---|-{0}-1 + | / | / - + | {3} {10} {1} Y + |/ |/ + + 3---{2}---2 + + - X + + + plane bits: + 0 = min x + 1 = max x + 2 = min y + 3 = max y + 4 = min z + 5 = max z + +*/ + +static int boxVertPlanes[8] = { + ( (1<<0) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<3) | (1<<5) ), + ( (1<<0) | (1<<3) | (1<<5) ) +}; + +static int boxVertEdges[8][3] = { + // bottom + { 3, 0, 8 }, + { 0, 1, 9 }, + { 1, 2, 10 }, + { 2, 3, 11 }, + // top + { 7, 4, 8 }, + { 4, 5, 9 }, + { 5, 6, 10 }, + { 6, 7, 11 } +}; + +static int boxEdgePlanes[12][2] = { + // bottom + { 4, 2 }, + { 4, 1 }, + { 4, 3 }, + { 4, 0 }, + // top + { 5, 2 }, + { 5, 1 }, + { 5, 3 }, + { 5, 0 }, + // sides + { 0, 2 }, + { 2, 1 }, + { 1, 3 }, + { 3, 0 } +}; + +static int boxEdgeVerts[12][2] = { + // bottom + { 0, 1 }, + { 1, 2 }, + { 2, 3 }, + { 3, 0 }, + // top + { 4, 5 }, + { 5, 6 }, + { 6, 7 }, + { 7, 4 }, + // sides + { 0, 4 }, + { 1, 5 }, + { 2, 6 }, + { 3, 7 } +}; + +static int boxPlaneBitsSilVerts[64][7] = { + { 0, 0, 0, 0, 0, 0, 0 }, // 000000 = 0 + { 4, 7, 4, 0, 3, 0, 0 }, // 000001 = 1 + { 4, 5, 6, 2, 1, 0, 0 }, // 000010 = 2 + { 0, 0, 0, 0, 0, 0, 0 }, // 000011 = 3 + { 4, 4, 5, 1, 0, 0, 0 }, // 000100 = 4 + { 6, 3, 7, 4, 5, 1, 0 }, // 000101 = 5 + { 6, 4, 5, 6, 2, 1, 0 }, // 000110 = 6 + { 0, 0, 0, 0, 0, 0, 0 }, // 000111 = 7 + { 4, 6, 7, 3, 2, 0, 0 }, // 001000 = 8 + { 6, 6, 7, 4, 0, 3, 2 }, // 001001 = 9 + { 6, 5, 6, 7, 3, 2, 1 }, // 001010 = 10 + { 0, 0, 0, 0, 0, 0, 0 }, // 001011 = 11 + { 0, 0, 0, 0, 0, 0, 0 }, // 001100 = 12 + { 0, 0, 0, 0, 0, 0, 0 }, // 001101 = 13 + { 0, 0, 0, 0, 0, 0, 0 }, // 001110 = 14 + { 0, 0, 0, 0, 0, 0, 0 }, // 001111 = 15 + { 4, 0, 1, 2, 3, 0, 0 }, // 010000 = 16 + { 6, 0, 1, 2, 3, 7, 4 }, // 010001 = 17 + { 6, 3, 2, 6, 5, 1, 0 }, // 010010 = 18 + { 0, 0, 0, 0, 0, 0, 0 }, // 010011 = 19 + { 6, 1, 2, 3, 0, 4, 5 }, // 010100 = 20 + { 6, 1, 2, 3, 7, 4, 5 }, // 010101 = 21 + { 6, 2, 3, 0, 4, 5, 6 }, // 010110 = 22 + { 0, 0, 0, 0, 0, 0, 0 }, // 010111 = 23 + { 6, 0, 1, 2, 6, 7, 3 }, // 011000 = 24 + { 6, 0, 1, 2, 6, 7, 4 }, // 011001 = 25 + { 6, 0, 1, 5, 6, 7, 3 }, // 011010 = 26 + { 0, 0, 0, 0, 0, 0, 0 }, // 011011 = 27 + { 0, 0, 0, 0, 0, 0, 0 }, // 011100 = 28 + { 0, 0, 0, 0, 0, 0, 0 }, // 011101 = 29 + { 0, 0, 0, 0, 0, 0, 0 }, // 011110 = 30 + { 0, 0, 0, 0, 0, 0, 0 }, // 011111 = 31 + { 4, 7, 6, 5, 4, 0, 0 }, // 100000 = 32 + { 6, 7, 6, 5, 4, 0, 3 }, // 100001 = 33 + { 6, 5, 4, 7, 6, 2, 1 }, // 100010 = 34 + { 0, 0, 0, 0, 0, 0, 0 }, // 100011 = 35 + { 6, 4, 7, 6, 5, 1, 0 }, // 100100 = 36 + { 6, 3, 7, 6, 5, 1, 0 }, // 100101 = 37 + { 6, 4, 7, 6, 2, 1, 0 }, // 100110 = 38 + { 0, 0, 0, 0, 0, 0, 0 }, // 100111 = 39 + { 6, 6, 5, 4, 7, 3, 2 }, // 101000 = 40 + { 6, 6, 5, 4, 0, 3, 2 }, // 101001 = 41 + { 6, 5, 4, 7, 3, 2, 1 }, // 101010 = 42 + { 0, 0, 0, 0, 0, 0, 0 }, // 101011 = 43 + { 0, 0, 0, 0, 0, 0, 0 }, // 101100 = 44 + { 0, 0, 0, 0, 0, 0, 0 }, // 101101 = 45 + { 0, 0, 0, 0, 0, 0, 0 }, // 101110 = 46 + { 0, 0, 0, 0, 0, 0, 0 }, // 101111 = 47 + { 0, 0, 0, 0, 0, 0, 0 }, // 110000 = 48 + { 0, 0, 0, 0, 0, 0, 0 }, // 110001 = 49 + { 0, 0, 0, 0, 0, 0, 0 }, // 110010 = 50 + { 0, 0, 0, 0, 0, 0, 0 }, // 110011 = 51 + { 0, 0, 0, 0, 0, 0, 0 }, // 110100 = 52 + { 0, 0, 0, 0, 0, 0, 0 }, // 110101 = 53 + { 0, 0, 0, 0, 0, 0, 0 }, // 110110 = 54 + { 0, 0, 0, 0, 0, 0, 0 }, // 110111 = 55 + { 0, 0, 0, 0, 0, 0, 0 }, // 111000 = 56 + { 0, 0, 0, 0, 0, 0, 0 }, // 111001 = 57 + { 0, 0, 0, 0, 0, 0, 0 }, // 111010 = 58 + { 0, 0, 0, 0, 0, 0, 0 }, // 111011 = 59 + { 0, 0, 0, 0, 0, 0, 0 }, // 111100 = 60 + { 0, 0, 0, 0, 0, 0, 0 }, // 111101 = 61 + { 0, 0, 0, 0, 0, 0, 0 }, // 111110 = 62 + { 0, 0, 0, 0, 0, 0, 0 }, // 111111 = 63 +}; + + +/* +============ +idBox::AddPoint +============ +*/ +bool idBox::AddPoint( const idVec3 &v ) { + idMat3 axis2; + idBounds bounds1, bounds2; + + if ( extents[0] < 0.0f ) { + extents.Zero(); + center = v; + axis.Identity(); + return true; + } + + bounds1[0][0] = bounds1[1][0] = center * axis[0]; + bounds1[0][1] = bounds1[1][1] = center * axis[1]; + bounds1[0][2] = bounds1[1][2] = center * axis[2]; + bounds1[0] -= extents; + bounds1[1] += extents; + if ( !bounds1.AddPoint( idVec3( v * axis[0], v * axis[1], v * axis[2] ) ) ) { + // point is contained in the box + return false; + } + + axis2[0] = v - center; + axis2[0].Normalize(); + axis2[1] = axis[ Min3Index( axis2[0] * axis[0], axis2[0] * axis[1], axis2[0] * axis[2] ) ]; + axis2[1] = axis2[1] - ( axis2[1] * axis2[0] ) * axis2[0]; + axis2[1].Normalize(); + axis2[2].Cross( axis2[0], axis2[1] ); + + AxisProjection( axis2, bounds2 ); + bounds2.AddPoint( idVec3( v * axis2[0], v * axis2[1], v * axis2[2] ) ); + + // create new box based on the smallest bounds + if ( bounds1.GetVolume() < bounds2.GetVolume() ) { + center = ( bounds1[0] + bounds1[1] ) * 0.5f; + extents = bounds1[1] - center; + center *= axis; + } + else { + center = ( bounds2[0] + bounds2[1] ) * 0.5f; + extents = bounds2[1] - center; + center *= axis2; + axis = axis2; + } + return true; +} + +/* +============ +idBox::AddBox +============ +*/ +bool idBox::AddBox( const idBox &a ) { + int i, besti; + float v, bestv; + idVec3 dir; + idMat3 ax[4]; + idBounds bounds[4], b; + + if ( a.extents[0] < 0.0f ) { + return false; + } + + if ( extents[0] < 0.0f ) { + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axis of this box + ax[0] = axis; + bounds[0][0][0] = bounds[0][1][0] = center * ax[0][0]; + bounds[0][0][1] = bounds[0][1][1] = center * ax[0][1]; + bounds[0][0][2] = bounds[0][1][2] = center * ax[0][2]; + bounds[0][0] -= extents; + bounds[0][1] += extents; + a.AxisProjection( ax[0], b ); + if ( !bounds[0].AddBounds( b ) ) { + // the other box is contained in this box + return false; + } + + // test axis of other box + ax[1] = a.axis; + bounds[1][0][0] = bounds[1][1][0] = a.center * ax[1][0]; + bounds[1][0][1] = bounds[1][1][1] = a.center * ax[1][1]; + bounds[1][0][2] = bounds[1][1][2] = a.center * ax[1][2]; + bounds[1][0] -= a.extents; + bounds[1][1] += a.extents; + AxisProjection( ax[1], b ); + if ( !bounds[1].AddBounds( b ) ) { + // this box is contained in the other box + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axes aligned with the vector between the box centers and one of the box axis + dir = a.center - center; + dir.Normalize(); + for ( i = 2; i < 4; i++ ) { + ax[i][0] = dir; + ax[i][1] = ax[i-2][ Min3Index( dir * ax[i-2][0], dir * ax[i-2][1], dir * ax[i-2][2] ) ]; + ax[i][1] = ax[i][1] - ( ax[i][1] * dir ) * dir; + ax[i][1].Normalize(); + ax[i][2].Cross( dir, ax[i][1] ); + + AxisProjection( ax[i], bounds[i] ); + a.AxisProjection( ax[i], b ); + bounds[i].AddBounds( b ); + } + + // get the bounds with the smallest volume + bestv = idMath::INFINITY; + besti = 0; + for ( i = 0; i < 4; i++ ) { + v = bounds[i].GetVolume(); + if ( v < bestv ) { + bestv = v; + besti = i; + } + } + + // create a box from the smallest bounds axis pair + center = ( bounds[besti][0] + bounds[besti][1] ) * 0.5f; + extents = bounds[besti][1] - center; + center *= ax[besti]; + axis = ax[besti]; + + return false; +} + +/* +================ +idBox::PlaneDistance +================ +*/ +float idBox::PlaneDistance( const idPlane &plane ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * ( plane.Normal() * axis[0] ) ) + + idMath::Fabs( extents[1] * ( plane.Normal() * axis[1] ) ) + + idMath::Fabs( extents[2] * ( plane.Normal() * axis[2] ) ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +/* +================ +idBox::PlaneSide +================ +*/ +int idBox::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * ( plane.Normal() * axis[0] ) ) + + idMath::Fabs( extents[1] * ( plane.Normal() * axis[1] ) ) + + idMath::Fabs( extents[2] * ( plane.Normal() * axis[2] ) ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBox::IntersectsBox +============ +*/ +bool idBox::IntersectsBox( const idBox &a ) const { + idVec3 dir; // vector between centers + float c[3][3]; // matrix c = axis.Transpose() * a.axis + float ac[3][3]; // absolute values of c + float axisdir[3]; // axis[i] * dir + float d, e0, e1; // distance between centers and projected extents + + dir = a.center - center; + + // axis C0 + t * A0 + c[0][0] = axis[0] * a.axis[0]; + c[0][1] = axis[0] * a.axis[1]; + c[0][2] = axis[0] * a.axis[2]; + axisdir[0] = axis[0] * dir; + ac[0][0] = idMath::Fabs( c[0][0] ); + ac[0][1] = idMath::Fabs( c[0][1] ); + ac[0][2] = idMath::Fabs( c[0][2] ); + + d = idMath::Fabs( axisdir[0] ); + e0 = extents[0]; + e1 = a.extents[0] * ac[0][0] + a.extents[1] * ac[0][1] + a.extents[2] * ac[0][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1 + c[1][0] = axis[1] * a.axis[0]; + c[1][1] = axis[1] * a.axis[1]; + c[1][2] = axis[1] * a.axis[2]; + axisdir[1] = axis[1] * dir; + ac[1][0] = idMath::Fabs( c[1][0] ); + ac[1][1] = idMath::Fabs( c[1][1] ); + ac[1][2] = idMath::Fabs( c[1][2] ); + + d = idMath::Fabs( axisdir[1] ); + e0 = extents[1]; + e1 = a.extents[0] * ac[1][0] + a.extents[1] * ac[1][1] + a.extents[2] * ac[1][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2 + c[2][0] = axis[2] * a.axis[0]; + c[2][1] = axis[2] * a.axis[1]; + c[2][2] = axis[2] * a.axis[2]; + axisdir[2] = axis[2] * dir; + ac[2][0] = idMath::Fabs( c[2][0] ); + ac[2][1] = idMath::Fabs( c[2][1] ); + ac[2][2] = idMath::Fabs( c[2][2] ); + + d = idMath::Fabs( axisdir[2] ); + e0 = extents[2]; + e1 = a.extents[0] * ac[2][0] + a.extents[1] * ac[2][1] + a.extents[2] * ac[2][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B0 + d = idMath::Fabs( a.axis[0] * dir ); + e0 = extents[0] * ac[0][0] + extents[1] * ac[1][0] + extents[2] * ac[2][0]; + e1 = a.extents[0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B1 + d = idMath::Fabs( a.axis[1] * dir ); + e0 = extents[0] * ac[0][1] + extents[1] * ac[1][1] + extents[2] * ac[2][1]; + e1 = a.extents[1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B2 + d = idMath::Fabs( a.axis[2] * dir ); + e0 = extents[0] * ac[0][2] + extents[1] * ac[1][2] + extents[2] * ac[2][2]; + e1 = a.extents[2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB0 + d = idMath::Fabs( axisdir[2] * c[1][0] - axisdir[1] * c[2][0] ); + e0 = extents[1] * ac[2][0] + extents[2] * ac[1][0]; + e1 = a.extents[1] * ac[0][2] + a.extents[2] * ac[0][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB1 + d = idMath::Fabs( axisdir[2] * c[1][1] - axisdir[1] * c[2][1] ); + e0 = extents[1] * ac[2][1] + extents[2] * ac[1][1]; + e1 = a.extents[0] * ac[0][2] + a.extents[2] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB2 + d = idMath::Fabs( axisdir[2] * c[1][2] - axisdir[1] * c[2][2] ); + e0 = extents[1] * ac[2][2] + extents[2] * ac[1][2]; + e1 = a.extents[0] * ac[0][1] + a.extents[1] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB0 + d = idMath::Fabs( axisdir[0] * c[2][0] - axisdir[2] * c[0][0] ); + e0 = extents[0] * ac[2][0] + extents[2] * ac[0][0]; + e1 = a.extents[1] * ac[1][2] + a.extents[2] * ac[1][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB1 + d = idMath::Fabs( axisdir[0] * c[2][1] - axisdir[2] * c[0][1] ); + e0 = extents[0] * ac[2][1] + extents[2] * ac[0][1]; + e1 = a.extents[0] * ac[1][2] + a.extents[2] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB2 + d = idMath::Fabs( axisdir[0] * c[2][2] - axisdir[2] * c[0][2] ); + e0 = extents[0] * ac[2][2] + extents[2] * ac[0][2]; + e1 = a.extents[0] * ac[1][1] + a.extents[1] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB0 + d = idMath::Fabs( axisdir[1] * c[0][0] - axisdir[0] * c[1][0] ); + e0 = extents[0] * ac[1][0] + extents[1] * ac[0][0]; + e1 = a.extents[1] * ac[2][2] + a.extents[2] * ac[2][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB1 + d = idMath::Fabs( axisdir[1] * c[0][1] - axisdir[0] * c[1][1] ); + e0 = extents[0] * ac[1][1] + extents[1] * ac[0][1]; + e1 = a.extents[0] * ac[2][2] + a.extents[2] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB2 + d = idMath::Fabs( axisdir[1] * c[0][2] - axisdir[0] * c[1][2] ); + e0 = extents[0] * ac[1][2] + extents[1] * ac[0][2]; + e1 = a.extents[0] * ac[2][1] + a.extents[1] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + return true; +} + +/* +============ +idBox::LineIntersection + + Returns true if the line intersects the box between the start and end point. +============ +*/ +bool idBox::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float ld[3]; + idVec3 lineDir = 0.5f * ( end - start ); + idVec3 lineCenter = start + lineDir; + idVec3 dir = lineCenter - center; + + ld[0] = idMath::Fabs( lineDir * axis[0] ); + if ( idMath::Fabs( dir * axis[0] ) > extents[0] + ld[0] ) { + return false; + } + + ld[1] = idMath::Fabs( lineDir * axis[1] ); + if ( idMath::Fabs( dir * axis[1] ) > extents[1] + ld[1] ) { + return false; + } + + ld[2] = idMath::Fabs( lineDir * axis[2] ); + if ( idMath::Fabs( dir * axis[2] ) > extents[2] + ld[2] ) { + return false; + } + + idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross * axis[0] ) > extents[1] * ld[2] + extents[2] * ld[1] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[1] ) > extents[0] * ld[2] + extents[2] * ld[0] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[2] ) > extents[0] * ld[1] + extents[1] * ld[0] ) { + return false; + } + + return true; +} + +/* +============ +BoxPlaneClip +============ +*/ +static bool BoxPlaneClip( const float denom, const float numer, float &scale0, float &scale1 ) { + if ( denom > 0.0f ) { + if ( numer > denom * scale1 ) { + return false; + } + if ( numer > denom * scale0 ) { + scale0 = numer / denom; + } + return true; + } + else if ( denom < 0.0f ) { + if ( numer > denom * scale0 ) { + return false; + } + if ( numer > denom * scale1 ) { + scale1 = numer / denom; + } + return true; + } + else { + return ( numer <= 0.0f ); + } +} + +/* +============ +idBox::RayIntersection + + Returns true if the ray intersects the box. + The ray can intersect the box in both directions from the start point. + If start is inside the box then scale1 < 0 and scale2 > 0. +============ +*/ +bool idBox::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 localStart, localDir; + + localStart = ( start - center ) * axis.Transpose(); + localDir = dir * axis.Transpose(); + + scale1 = -idMath::INFINITY; + scale2 = idMath::INFINITY; + return BoxPlaneClip( localDir.x, -localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( -localDir.x, localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( localDir.y, -localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( -localDir.y, localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( localDir.z, -localStart.z - extents[2], scale1, scale2 ) && + BoxPlaneClip( -localDir.z, localStart.z - extents[2], scale1, scale2 ); +} + +/* +============ +idBox::FromPoints + + Tight box for a collection of points. +============ +*/ +void idBox::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float invNumPoints, sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ; + idVec3 dir; + idBounds bounds; + idMatX eigenVectors; + idVecX eigenValues; + + // compute mean of points + center = points[0]; + for ( i = 1; i < numPoints; i++ ) { + center += points[i]; + } + invNumPoints = 1.0f / numPoints; + center *= invNumPoints; + + // compute covariances of points + sumXX = 0.0f; sumXY = 0.0f; sumXZ = 0.0f; + sumYY = 0.0f; sumYZ = 0.0f; sumZZ = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - center; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + sumZZ += dir.z * dir.z; + } + sumXX *= invNumPoints; + sumXY *= invNumPoints; + sumXZ *= invNumPoints; + sumYY *= invNumPoints; + sumYZ *= invNumPoints; + sumZZ *= invNumPoints; + + // compute eigenvectors for covariance matrix + eigenValues.SetData( 3, VECX_ALLOCA( 3 ) ); + eigenVectors.SetData( 3, 3, MATX_ALLOCA( 3 * 3 ) ); + + eigenVectors[0][0] = sumXX; + eigenVectors[0][1] = sumXY; + eigenVectors[0][2] = sumXZ; + eigenVectors[1][0] = sumXY; + eigenVectors[1][1] = sumYY; + eigenVectors[1][2] = sumYZ; + eigenVectors[2][0] = sumXZ; + eigenVectors[2][1] = sumYZ; + eigenVectors[2][2] = sumZZ; + eigenVectors.Eigen_SolveSymmetric( eigenValues ); + eigenVectors.Eigen_SortIncreasing( eigenValues ); + + axis[0][0] = eigenVectors[0][0]; + axis[0][1] = eigenVectors[0][1]; + axis[0][2] = eigenVectors[0][2]; + axis[1][0] = eigenVectors[1][0]; + axis[1][1] = eigenVectors[1][1]; + axis[1][2] = eigenVectors[1][2]; + axis[2][0] = eigenVectors[2][0]; + axis[2][1] = eigenVectors[2][1]; + axis[2][2] = eigenVectors[2][2]; + + extents[0] = eigenValues[0]; + extents[1] = eigenValues[0]; + extents[2] = eigenValues[0]; + + // refine by calculating the bounds of the points projected onto the axis and adjusting the center and extents + bounds.Clear(); + for ( i = 0; i < numPoints; i++ ) { + bounds.AddPoint( idVec3( points[i] * axis[0], points[i] * axis[1], points[i] * axis[2] ) ); + } + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + center *= axis; +} + +/* +============ +idBox::FromPointTranslation + + Most tight box for the translational movement of the given point. +============ +*/ +void idBox::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxTranslation + + Most tight box for the translational movement of the given box. +============ +*/ +void idBox::FromBoxTranslation( const idBox &box, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBox::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxRotation + + Most tight box for the rotational movement of the given box. +============ +*/ +void idBox::FromBoxRotation( const idBox &box, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::ToPoints +============ +*/ +void idBox::ToPoints( idVec3 points[8] ) const { + idMat3 ax; + idVec3 temp[4]; + + ax[0] = extents[0] * axis[0]; + ax[1] = extents[1] * axis[1]; + ax[2] = extents[2] * axis[2]; + temp[0] = center - ax[0]; + temp[1] = center + ax[0]; + temp[2] = ax[1] - ax[2]; + temp[3] = ax[1] + ax[2]; + points[0] = temp[0] - temp[3]; + points[1] = temp[1] - temp[3]; + points[2] = temp[1] + temp[2]; + points[3] = temp[0] + temp[2]; + points[4] = temp[0] - temp[2]; + points[5] = temp[1] - temp[2]; + points[6] = temp[1] + temp[3]; + points[7] = temp[0] + temp[3]; +} + +/* +============ +idBox::GetProjectionSilhouetteVerts +============ +*/ +int idBox::GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8], dir1, dir2; + + ToPoints( points ); + + dir1 = points[0] - projectionOrigin; + dir2 = points[6] - projectionOrigin; + f = dir1 * axis[0]; + planeBits = FLOATSIGNBITNOTSET( f ); + f = dir2 * axis[0]; + planeBits |= FLOATSIGNBITSET( f ) << 1; + f = dir1 * axis[1]; + planeBits |= FLOATSIGNBITNOTSET( f ) << 2; + f = dir2 * axis[1]; + planeBits |= FLOATSIGNBITSET( f ) << 3; + f = dir1 * axis[2]; + planeBits |= FLOATSIGNBITNOTSET( f ) << 4; + f = dir2 * axis[2]; + planeBits |= FLOATSIGNBITSET( f ) << 5; + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} + +/* +============ +idBox::GetParallelProjectionSilhouetteVerts +============ +*/ +int idBox::GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8]; + + ToPoints( points ); + + planeBits = 0; + f = projectionDir * axis[0]; + if ( FLOATNOTZERO( f ) ) { + planeBits = 1 << FLOATSIGNBITSET( f ); + } + f = projectionDir * axis[1]; + if ( FLOATNOTZERO( f ) ) { + planeBits |= 4 << FLOATSIGNBITSET( f ); + } + f = projectionDir * axis[2]; + if ( FLOATNOTZERO( f ) ) { + planeBits |= 16 << FLOATSIGNBITSET( f ); + } + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} diff --git a/source/idlib/bv/Box.h b/source/idlib/bv/Box.h new file mode 100644 index 0000000..9cb7ebb --- /dev/null +++ b/source/idlib/bv/Box.h @@ -0,0 +1,270 @@ + +#ifndef __BV_BOX_H__ +#define __BV_BOX_H__ + +/* +=============================================================================== + + Oriented Bounding Box + +=============================================================================== +*/ + +class idBox { +public: + idBox( void ); + explicit idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ); + explicit idBox( const idVec3 &point ); + explicit idBox( const idBounds &bounds ); + explicit idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + + idBox operator+( const idVec3 &t ) const; // returns translated box + idBox & operator+=( const idVec3 &t ); // translate the box + idBox operator*( const idMat3 &r ) const; // returns rotated box + idBox & operator*=( const idMat3 &r ); // rotate the box + idBox operator+( const idBox &a ) const; + idBox & operator+=( const idBox &a ); + idBox operator-( const idBox &a ) const; + idBox & operator-=( const idBox &a ); + + bool Compare( const idBox &a ) const; // exact compare, no epsilon + bool Compare( const idBox &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBox &a ) const; // exact compare, no epsilon + bool operator!=( const idBox &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out box + void Zero( void ); // single point at origin + + const idVec3 & GetCenter( void ) const; // returns center of the box + const idVec3 & GetExtents( void ) const; // returns extents of the box + const idMat3 & GetAxis( void ) const; // returns the axis of the box + float GetVolume( void ) const; // returns the volume of the box + bool IsCleared( void ) const; // returns true if box are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the box expanded + bool AddBox( const idBox &a ); // add the box, returns true if the box expanded + idBox Expand( const float d ) const; // return box expanded in all directions with the given value + idBox & ExpandSelf( const float d ); // expand box in all directions with the given value + idBox Translate( const idVec3 &translation ) const; // return translated box + idBox & TranslateSelf( const idVec3 &translation ); // translate this box + idBox Rotate( const idMat3 &rotation ) const; // return rotated box + idBox & RotateSelf( const idMat3 &rotation ); // rotate this box + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBox( const idBox &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // tight box for a collection of points + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight box for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoxTranslation( const idBox &box, const idVec3 &translation ); + // most tight box for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoxRotation( const idBox &box, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere( void ) const; + + // calculates the projection of this box onto the given axis + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idMat3 &ax, idBounds &bounds ) const; + + // calculates the silhouette of the box + int GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const; + int GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const; + +private: + idVec3 center; + idVec3 extents; + idMat3 axis; +}; + +extern idBox box_zero; + +ID_INLINE idBox::idBox( void ) { +} + +ID_INLINE idBox::idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ) { + this->center = center; + this->extents = extents; + this->axis = axis; +} + +ID_INLINE idBox::idBox( const idVec3 &point ) { + this->center = point; + this->extents.Zero(); + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->center = origin + this->center * axis; + this->axis = axis; +} + +ID_INLINE idBox idBox::operator+( const idVec3 &t ) const { + return idBox( center + t, extents, axis ); +} + +ID_INLINE idBox &idBox::operator+=( const idVec3 &t ) { + center += t; + return *this; +} + +ID_INLINE idBox idBox::operator*( const idMat3 &r ) const { + return idBox( center * r, extents, axis * r ); +} + +ID_INLINE idBox &idBox::operator*=( const idMat3 &r ) { + center *= r; + axis *= r; + return *this; +} + +ID_INLINE idBox idBox::operator+( const idBox &a ) const { + idBox newBox; + newBox = *this; + newBox.AddBox( a ); + return newBox; +} + +ID_INLINE idBox &idBox::operator+=( const idBox &a ) { + idBox::AddBox( a ); + return *this; +} + +ID_INLINE idBox idBox::operator-( const idBox &a ) const { + return idBox( center, extents - a.extents, axis ); +} + +ID_INLINE idBox &idBox::operator-=( const idBox &a ) { + extents -= a.extents; + return *this; +} + +ID_INLINE bool idBox::Compare( const idBox &a ) const { + return ( center.Compare( a.center ) && extents.Compare( a.extents ) && axis.Compare( a.axis ) ); +} + +ID_INLINE bool idBox::Compare( const idBox &a, const float epsilon ) const { + return ( center.Compare( a.center, epsilon ) && extents.Compare( a.extents, epsilon ) && axis.Compare( a.axis, epsilon ) ); +} + +ID_INLINE bool idBox::operator==( const idBox &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBox::operator!=( const idBox &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBox::Clear( void ) { + center.Zero(); + extents[0] = extents[1] = extents[2] = -idMath::INFINITY; + axis.Identity(); +} + +ID_INLINE void idBox::Zero( void ) { + center.Zero(); + extents.Zero(); + axis.Identity(); +} + +ID_INLINE const idVec3 &idBox::GetCenter( void ) const { + return center; +} + +ID_INLINE const idVec3 &idBox::GetExtents( void ) const { + return extents; +} + +ID_INLINE const idMat3 &idBox::GetAxis( void ) const { + return axis; +} + +ID_INLINE float idBox::GetVolume( void ) const { + return ( extents * 2.0f ).LengthSqr(); +} + +ID_INLINE bool idBox::IsCleared( void ) const { + return extents[0] < 0.0f; +} + +ID_INLINE idBox idBox::Expand( const float d ) const { + return idBox( center, extents + idVec3( d, d, d ), axis ); +} + +ID_INLINE idBox &idBox::ExpandSelf( const float d ) { + extents[0] += d; + extents[1] += d; + extents[2] += d; + return *this; +} + +ID_INLINE idBox idBox::Translate( const idVec3 &translation ) const { + return idBox( center + translation, extents, axis ); +} + +ID_INLINE idBox &idBox::TranslateSelf( const idVec3 &translation ) { + center += translation; + return *this; +} + +ID_INLINE idBox idBox::Rotate( const idMat3 &rotation ) const { + return idBox( center * rotation, extents, axis * rotation ); +} + +ID_INLINE idBox &idBox::RotateSelf( const idMat3 &rotation ) { + center *= rotation; + axis *= rotation; + return *this; +} + +ID_INLINE bool idBox::ContainsPoint( const idVec3 &p ) const { + idVec3 lp = p - center; + if ( idMath::Fabs( lp * axis[0] ) > extents[0] || + idMath::Fabs( lp * axis[1] ) > extents[1] || + idMath::Fabs( lp * axis[2] ) > extents[2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBox::ToSphere( void ) const { + return idSphere( center, extents.Length() ); +} + +ID_INLINE void idBox::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1 = dir * center; + float d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBox::AxisProjection( const idMat3 &ax, idBounds &bounds ) const { + for ( int i = 0; i < 3; i++ ) { + float d1 = ax[i] * center; + float d2 = idMath::Fabs( extents[0] * ( ax[i] * axis[0] ) ) + + idMath::Fabs( extents[1] * ( ax[i] * axis[1] ) ) + + idMath::Fabs( extents[2] * ( ax[i] * axis[2] ) ); + bounds[0][i] = d1 - d2; + bounds[1][i] = d1 + d2; + } +} + +#endif /* !__BV_BOX_H__ */ diff --git a/source/idlib/bv/Frustum.cpp b/source/idlib/bv/Frustum.cpp new file mode 100644 index 0000000..c2cefa2 --- /dev/null +++ b/source/idlib/bv/Frustum.cpp @@ -0,0 +1,2826 @@ + +#include "../precompiled.h" +#pragma hdrstop + +//#define FRUSTUM_DEBUG + +/* + bit 0 = min x + bit 1 = max x + bit 2 = min y + bit 3 = max y + bit 4 = min z + bit 5 = max z +*/ +static int boxVertPlanes[8] = { + ( (1<<0) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<3) | (1<<5) ), + ( (1<<0) | (1<<3) | (1<<5) ), +}; + +/* +============ +BoxToPoints +============ +*/ +void BoxToPoints( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis, idVec3 points[8] ) { + idMat3 ax; + idVec3 temp[4]; + + ax[0] = extents[0] * axis[0]; + ax[1] = extents[1] * axis[1]; + ax[2] = extents[2] * axis[2]; + temp[0] = center - ax[0]; + temp[1] = center + ax[0]; + temp[2] = ax[1] - ax[2]; + temp[3] = ax[1] + ax[2]; + points[0] = temp[0] - temp[3]; + points[1] = temp[1] - temp[3]; + points[2] = temp[1] + temp[2]; + points[3] = temp[0] + temp[2]; + points[4] = temp[0] - temp[2]; + points[5] = temp[1] - temp[2]; + points[6] = temp[1] + temp[3]; + points[7] = temp[0] + temp[3]; +} + +/* +================ +idFrustum::PlaneDistance +================ +*/ +float idFrustum::PlaneDistance( const idPlane &plane ) const { + float min, max; + + AxisProjection( plane.Normal(), min, max ); + if ( min + plane[3] > 0.0f ) { + return min + plane[3]; + } + if ( max + plane[3] < 0.0f ) { + return max + plane[3]; + } + return 0.0f; +} + +/* +================ +idFrustum::PlaneSide +================ +*/ +int idFrustum::PlaneSide( const idPlane &plane, const float epsilon ) const { + float min, max; + + AxisProjection( plane.Normal(), min, max ); + if ( min + plane[3] > epsilon ) { + return PLANESIDE_FRONT; + } + if ( max + plane[3] < epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idFrustum::CullPoint +============ +*/ +bool idFrustum::CullPoint( const idVec3 &point ) const { + idVec3 p; + float scale; + + // transform point to frustum space + p = ( point - origin ) * axis.Transpose(); + // test whether or not the point is within the frustum + if ( p.x < dNear || p.y > dFar ) { + return true; + } + scale = p.x * invFar; + if ( idMath::Fabs( p.y ) > dLeft * scale ) { + return true; + } + if ( idMath::Fabs( p.z ) > dUp * scale ) { + return true; + } + return false; +} + +/* +============ +idFrustum::CullLocalBox + + Tests if any of the planes of the frustum can be used as a separating plane. + + 3 muls best case + 25 muls worst case +============ +*/ +bool idFrustum::CullLocalBox( const idVec3 &localOrigin, const idVec3 &extents, const idMat3 &localAxis ) const { + float d1, d2; + idVec3 testOrigin; + idMat3 testAxis; + + // near plane + d1 = dNear - localOrigin.x; + d2 = idMath::Fabs( extents[0] * localAxis[0][0] ) + + idMath::Fabs( extents[1] * localAxis[1][0] ) + + idMath::Fabs( extents[2] * localAxis[2][0] ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + // far plane + d1 = localOrigin.x - dFar; + if ( d1 - d2 > 0.0f ) { + return true; + } + + testOrigin = localOrigin; + testAxis = localAxis; + + if ( testOrigin.y < 0.0f ) { + testOrigin.y = -testOrigin.y; + testAxis[0][1] = -testAxis[0][1]; + testAxis[1][1] = -testAxis[1][1]; + testAxis[2][1] = -testAxis[2][1]; + } + + // test left/right planes + d1 = dFar * testOrigin.y - dLeft * testOrigin.x; + d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][1] - dLeft * testAxis[0][0] ) ) + + idMath::Fabs( extents[1] * ( dFar * testAxis[1][1] - dLeft * testAxis[1][0] ) ) + + idMath::Fabs( extents[2] * ( dFar * testAxis[2][1] - dLeft * testAxis[2][0] ) ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + if ( testOrigin.z < 0.0f ) { + testOrigin.z = -testOrigin.z; + testAxis[0][2] = -testAxis[0][2]; + testAxis[1][2] = -testAxis[1][2]; + testAxis[2][2] = -testAxis[2][2]; + } + + // test up/down planes + d1 = dFar * testOrigin.z - dUp * testOrigin.x; + d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][2] - dUp * testAxis[0][0] ) ) + + idMath::Fabs( extents[1] * ( dFar * testAxis[1][2] - dUp * testAxis[1][0] ) ) + + idMath::Fabs( extents[2] * ( dFar * testAxis[2][2] - dUp * testAxis[2][0] ) ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullBounds + + Tests if any of the planes of the frustum can be used as a separating plane. + + 24 muls best case + 37 muls worst case +============ +*/ +bool idFrustum::CullBounds( const idBounds &bounds ) const { + idVec3 localOrigin, center, extents; + idMat3 localAxis; + + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + + // transform the bounds into the space of this frustum + localOrigin = ( center - origin ) * axis.Transpose(); + localAxis = axis.Transpose(); + + return CullLocalBox( localOrigin, extents, localAxis ); +} + +/* +============ +idFrustum::CullBounds + + Tests if any of the planes of the frustum can be used as a separating plane. + + 39 muls best case + 61 muls worst case +============ +*/ +bool idFrustum::CullBox( const idBox &box ) const { + idVec3 localOrigin; + idMat3 localAxis; + + // transform the box into the space of this frustum + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + + return CullLocalBox( localOrigin, box.GetExtents(), localAxis ); +} + +/* +============ +idFrustum::CullSphere + + Tests if any of the planes of the frustum can be used as a separating plane. + + 9 muls best case + 21 muls worst case +============ +*/ +bool idFrustum::CullSphere( const idSphere &sphere ) const { + float d, r, rs, sFar; + idVec3 center; + + center = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + r = sphere.GetRadius(); + + // test near plane + if ( dNear - center.x > r ) { + return true; + } + + // test far plane + if ( center.x - dFar > r ) { + return true; + } + + rs = r * r; + sFar = dFar * dFar; + + // test left/right planes + d = dFar * idMath::Fabs( center.y ) - dLeft * center.x; + if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) { + return true; + } + + // test up/down planes + d = dFar * idMath::Fabs( center.z ) - dUp * center.x; + if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullLocalFrustum + + Tests if any of the planes of this frustum can be used as a separating plane. + + 0 muls best case + 30 muls worst case +============ +*/ +bool idFrustum::CullLocalFrustum( const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const { + int index; + float dx, dy, dz, leftScale, upScale; + + // test near plane + dy = -localFrustum.axis[1].x; + dz = -localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x < dNear ) { + return true; + } + + // test far plane + dy = localFrustum.axis[1].x; + dz = localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x > dFar ) { + return true; + } + + leftScale = dLeft * invFar; + + // test left plane + dy = dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x; + dz = dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y > indexPoints[index].x * leftScale ) { + return true; + } + + // test right plane + dy = -dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x; + dz = -dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y < -indexPoints[index].x * leftScale ) { + return true; + } + + upScale = dUp * invFar; + + // test up plane + dy = dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x; + dz = dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dFar * cornerVecs[index].z - dUp * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z > indexPoints[index].x * upScale ) { + return true; + } + + // test down plane + dy = -dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x; + dz = -dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -dFar * cornerVecs[index].z - dUp * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z < -indexPoints[index].x * upScale ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullFrustum + + Tests if any of the planes of this frustum can be used as a separating plane. + + 58 muls best case + 88 muls worst case +============ +*/ +bool idFrustum::CullFrustum( const idFrustum &frustum ) const { + idFrustum localFrustum; + idVec3 indexPoints[8], cornerVecs[4]; + + // transform the given frustum into the space of this frustum + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum.axis = frustum.axis * axis.Transpose(); + + localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + return CullLocalFrustum( localFrustum, indexPoints, cornerVecs ); +} + +/* +============ +idFrustum::CullLocalWinding +============ +*/ +bool idFrustum::CullLocalWinding( const idVec3 *points, const int numPoints, int *pointCull ) const { + int i, pCull, culled; + float leftScale, upScale; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + + culled = -1; + for ( i = 0; i < numPoints; i++ ) { + const idVec3 &p = points[i]; + pCull = 0; + if ( p.x < dNear ) { + pCull = 1; + } + else if ( p.x > dFar ) { + pCull = 2; + } + if ( idMath::Fabs( p.y ) > p.x * leftScale ) { + pCull |= 4 << FLOATSIGNBITSET( p.y ); + } + if ( idMath::Fabs( p.z ) > p.x * upScale ) { + pCull |= 16 << FLOATSIGNBITSET( p.z ); + } + culled &= pCull; + pointCull[i] = pCull; + } + + return ( culled != 0 ); +} + +/* +============ +idFrustum::CullWinding +============ +*/ +bool idFrustum::CullWinding( const idWinding &winding ) const { + int i, *pointCull; + idVec3 *localPoints; + idMat3 transpose; + + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + return CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull ); +} + +/* +============ +idFrustum::BoundsCullLocalFrustum + + Tests if any of the bounding box planes can be used as a separating plane. +============ +*/ +bool idFrustum::BoundsCullLocalFrustum( const idBounds &bounds, const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const { + int index; + float dx, dy, dz; + + dy = -localFrustum.axis[1].x; + dz = -localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x < bounds[0].x ) { + return true; + } + + dy = localFrustum.axis[1].x; + dz = localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x > bounds[1].x ) { + return true; + } + + dy = -localFrustum.axis[1].y; + dz = -localFrustum.axis[2].y; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].y; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y < bounds[0].y ) { + return true; + } + + dy = localFrustum.axis[1].y; + dz = localFrustum.axis[2].y; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].y; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y > bounds[1].y ) { + return true; + } + + dy = -localFrustum.axis[1].z; + dz = -localFrustum.axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z < bounds[0].z ) { + return true; + } + + dy = localFrustum.axis[1].z; + dz = localFrustum.axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z > bounds[1].z ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::LocalLineIntersection + + 7 divs + 30 muls +============ +*/ +bool idFrustum::LocalLineIntersection( const idVec3 &start, const idVec3 &end ) const { + idVec3 dir; + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + int startInside = 1; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + // test near plane + if ( dNear > 0.0f ) { + d1 = dNear - start.x; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = dNear - end.x; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) { + return true; + } + } + } + } + } + + // test far plane + d1 = start.x - dFar; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = end.x - dFar; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) { + return true; + } + } + } + } + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + return true; + } + } + } + } + + // test right plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = -fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + return true; + } + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + return true; + } + } + } + } + + // test down plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = -fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + return true; + } + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::LocalRayIntersection + + Returns true if the ray starts inside the frustum. + If there was an intersection scale1 <= scale2 +============ +*/ +bool idFrustum::LocalRayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 end; + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + int startInside = 1; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + end = start + dir; + + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + // test near plane + if ( dNear > 0.0f ) { + d1 = dNear - start.x; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = dNear - end.x; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + } + + // test far plane + d1 = start.x - dFar; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = end.x - dFar; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + // test right plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = -fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + // test down plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = -fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::ContainsPoint +============ +*/ +bool idFrustum::ContainsPoint( const idVec3 &point ) const { + return !CullPoint( point ); +} + +/* +============ +idFrustum::LocalFrustumIntersectsFrustum +============ +*/ +bool idFrustum::LocalFrustumIntersectsFrustum( const idVec3 points[8], const bool testFirstSide ) const { + int i; + + // test if any edges of the other frustum intersect this frustum + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[i], points[4+i] ) ) { + return true; + } + } + if ( testFirstSide ) { + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[i], points[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[4+i], points[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::LocalFrustumIntersectsBounds +============ +*/ +bool idFrustum::LocalFrustumIntersectsBounds( const idVec3 points[8], const idBounds &bounds ) const { + int i; + + // test if any edges of the other frustum intersect this frustum + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[i], points[4+i] ) ) { + return true; + } + } + if ( dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[i], points[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[4+i], points[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::IntersectsBounds +============ +*/ +bool idFrustum::IntersectsBounds( const idBounds &bounds ) const { + idVec3 localOrigin, center, extents; + idMat3 localAxis; + + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + + localOrigin = ( center - origin ) * axis.Transpose(); + localAxis = axis.Transpose(); + + if ( CullLocalBox( localOrigin, extents, localAxis ) ) { + return false; + } + + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + if ( BoundsCullLocalFrustum( bounds, *this, indexPoints, cornerVecs ) ) { + return false; + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + if ( LocalFrustumIntersectsBounds( indexPoints, bounds ) ) { + return true; + } + + BoxToPoints( localOrigin, extents, localAxis, indexPoints ); + + if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsBox +============ +*/ +bool idFrustum::IntersectsBox( const idBox &box ) const { + idVec3 localOrigin; + idMat3 localAxis; + + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + + if ( CullLocalBox( localOrigin, box.GetExtents(), localAxis ) ) { + return false; + } + + idVec3 indexPoints[8], cornerVecs[4]; + idFrustum localFrustum; + + localFrustum = *this; + localFrustum.origin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose(); + localFrustum.axis = axis * box.GetAxis().Transpose(); + localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + if ( BoundsCullLocalFrustum( idBounds( -box.GetExtents(), box.GetExtents() ), localFrustum, indexPoints, cornerVecs ) ) { + return false; + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + if ( LocalFrustumIntersectsBounds( indexPoints, idBounds( -box.GetExtents(), box.GetExtents() ) ) ) { + return true; + } + + BoxToPoints( localOrigin, box.GetExtents(), localAxis, indexPoints ); + + if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsSphere + + FIXME: test this +============ +*/ +#define VORONOI_INDEX( x, y, z ) ( x + y * 3 + z * 9 ) + +bool idFrustum::IntersectsSphere( const idSphere &sphere ) const { + int index, x, y, z; + float scale, r, d; + idVec3 p, dir, points[8]; + + if ( CullSphere( sphere ) ) { + return false; + } + + x = y = z = 0; + dir.Zero(); + + p = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + + if ( p.x <= dNear ) { + scale = dNear * invFar; + dir.y = idMath::Fabs( p.y ) - dLeft * scale; + dir.z = idMath::Fabs( p.z ) - dUp * scale; + } + else if ( p.x >= dFar ) { + dir.y = idMath::Fabs( p.y ) - dLeft; + dir.z = idMath::Fabs( p.z ) - dUp; + } + else { + scale = p.x * invFar; + dir.y = idMath::Fabs( p.y ) - dLeft * scale; + dir.z = idMath::Fabs( p.z ) - dUp * scale; + } + if ( dir.y > 0.0f ) { + y = ( 1 + FLOATSIGNBITNOTSET( p.y ) ); + } + if ( dir.z > 0.0f ) { + z = ( 1 + FLOATSIGNBITNOTSET( p.z ) ); + } + if ( p.x < dNear ) { + scale = dLeft * dNear * invFar; + if ( p.x < dNear + ( scale - p.y ) * scale * invFar ) { + scale = dUp * dNear * invFar; + if ( p.x < dNear + ( scale - p.z ) * scale * invFar ) { + x = 1; + } + } + } + else { + if ( p.x > dFar ) { + x = 2; + } + else if ( p.x > dFar + ( dLeft - p.y ) * dLeft * invFar ) { + x = 2; + } + else if ( p.x > dFar + ( dUp - p.z ) * dUp * invFar ) { + x = 2; + } + } + + r = sphere.GetRadius(); + index = VORONOI_INDEX( x, y, z ); + switch( index ) { + case VORONOI_INDEX( 0, 0, 0 ): return true; + case VORONOI_INDEX( 1, 0, 0 ): return ( dNear - p.x < r ); + case VORONOI_INDEX( 2, 0, 0 ): return ( p.x - dFar < r ); + case VORONOI_INDEX( 0, 1, 0 ): d = dFar * p.y - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) ); + case VORONOI_INDEX( 0, 2, 0 ): d = -dFar * p.z - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) ); + case VORONOI_INDEX( 0, 0, 1 ): d = dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) ); + case VORONOI_INDEX( 0, 0, 2 ): d = -dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) ); + default: { + ToIndexPoints( points ); + switch( index ) { + case VORONOI_INDEX( 1, 1, 1 ): return sphere.ContainsPoint( points[0] ); + case VORONOI_INDEX( 2, 1, 1 ): return sphere.ContainsPoint( points[4] ); + case VORONOI_INDEX( 1, 2, 1 ): return sphere.ContainsPoint( points[1] ); + case VORONOI_INDEX( 2, 2, 1 ): return sphere.ContainsPoint( points[5] ); + case VORONOI_INDEX( 1, 1, 2 ): return sphere.ContainsPoint( points[2] ); + case VORONOI_INDEX( 2, 1, 2 ): return sphere.ContainsPoint( points[6] ); + case VORONOI_INDEX( 1, 2, 2 ): return sphere.ContainsPoint( points[3] ); + case VORONOI_INDEX( 2, 2, 2 ): return sphere.ContainsPoint( points[7] ); + case VORONOI_INDEX( 1, 1, 0 ): return sphere.LineIntersection( points[0], points[2] ); + case VORONOI_INDEX( 2, 1, 0 ): return sphere.LineIntersection( points[4], points[6] ); + case VORONOI_INDEX( 1, 2, 0 ): return sphere.LineIntersection( points[1], points[3] ); + case VORONOI_INDEX( 2, 2, 0 ): return sphere.LineIntersection( points[5], points[7] ); + case VORONOI_INDEX( 1, 0, 1 ): return sphere.LineIntersection( points[0], points[1] ); + case VORONOI_INDEX( 2, 0, 1 ): return sphere.LineIntersection( points[4], points[5] ); + case VORONOI_INDEX( 0, 1, 1 ): return sphere.LineIntersection( points[0], points[4] ); + case VORONOI_INDEX( 0, 2, 1 ): return sphere.LineIntersection( points[1], points[5] ); + case VORONOI_INDEX( 1, 0, 2 ): return sphere.LineIntersection( points[2], points[3] ); + case VORONOI_INDEX( 2, 0, 2 ): return sphere.LineIntersection( points[6], points[7] ); + case VORONOI_INDEX( 0, 1, 2 ): return sphere.LineIntersection( points[2], points[6] ); + case VORONOI_INDEX( 0, 2, 2 ): return sphere.LineIntersection( points[3], points[7] ); + } + break; + } + } + return false; +} + +/* +============ +idFrustum::IntersectsFrustum +============ +*/ +bool idFrustum::IntersectsFrustum( const idFrustum &frustum ) const { + idVec3 indexPoints2[8], cornerVecs2[4]; + idFrustum localFrustum2; + + localFrustum2 = frustum; + localFrustum2.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum2.axis = frustum.axis * axis.Transpose(); + localFrustum2.ToIndexPointsAndCornerVecs( indexPoints2, cornerVecs2 ); + + if ( CullLocalFrustum( localFrustum2, indexPoints2, cornerVecs2 ) ) { + return false; + } + + idVec3 indexPoints1[8], cornerVecs1[4]; + idFrustum localFrustum1; + + localFrustum1 = *this; + localFrustum1.origin = ( origin - frustum.origin ) * frustum.axis.Transpose(); + localFrustum1.axis = axis * frustum.axis.Transpose(); + localFrustum1.ToIndexPointsAndCornerVecs( indexPoints1, cornerVecs1 ); + + if ( frustum.CullLocalFrustum( localFrustum1, indexPoints1, cornerVecs1 ) ) { + return false; + } + + idSwap( indexPoints2[2], indexPoints2[3] ); + idSwap( indexPoints2[6], indexPoints2[7] ); + + if ( LocalFrustumIntersectsFrustum( indexPoints2, ( localFrustum2.dNear > 0.0f ) ) ) { + return true; + } + + idSwap( indexPoints1[2], indexPoints1[3] ); + idSwap( indexPoints1[6], indexPoints1[7] ); + + if ( frustum.LocalFrustumIntersectsFrustum( indexPoints1, ( localFrustum1.dNear > 0.0f ) ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsWinding +============ +*/ +bool idFrustum::IntersectsWinding( const idWinding &winding ) const { + int i, j, *pointCull; + float min, max; + idVec3 *localPoints, indexPoints[8], cornerVecs[4]; + idMat3 transpose; + idPlane plane; + + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + // if the winding is culled + if ( CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull ) ) { + return false; + } + + winding.GetPlane( plane ); + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, plane.Normal(), min, max ); + + // if the frustum does not cross the winding plane + if ( min + plane[3] > 0.0f || max + plane[3] < 0.0f ) { + return false; + } + + // test if any of the winding edges goes through the frustum + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + j = (i+1)%winding.GetNumPoints(); + if ( !( pointCull[i] & pointCull[j] ) ) { + if ( LocalLineIntersection( localPoints[i], localPoints[j] ) ) { + return true; + } + } + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + // test if any edges of the frustum intersect the winding + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[4+i] ) ) { + return true; + } + } + if ( dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[4+i], indexPoints[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::LineIntersection + + Returns true if the line intersects the box between the start and end point. +============ +*/ +bool idFrustum::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + return LocalLineIntersection( ( start - origin ) * axis.Transpose(), ( end - origin ) * axis.Transpose() ); +} + +/* +============ +idFrustum::RayIntersection + + Returns true if the ray intersects the bounds. + The ray can intersect the bounds in both directions from the start point. + If start is inside the frustum then scale1 < 0 and scale2 > 0. +============ +*/ +bool idFrustum::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + if ( LocalRayIntersection( ( start - origin ) * axis.Transpose(), dir * axis.Transpose(), scale1, scale2 ) ) { + return true; + } + if ( scale1 <= scale2 ) { + return true; + } + return false; +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the bounds. +============ +*/ +bool idFrustum::FromProjection( const idBounds &bounds, const idVec3 &projectionOrigin, const float dFar ) { + return FromProjection( idBox( bounds, vec3_origin, mat3_identity ), projectionOrigin, dFar ); +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the box. +============ +*/ +bool idFrustum::FromProjection( const idBox &box, const idVec3 &projectionOrigin, const float dFar ) { + int i, bestAxis; + float value, bestValue; + idVec3 dir; + + assert( dFar > 0.0f ); + + this->dNear = this->dFar = this->invFar = 0.0f; + + dir = box.GetCenter() - projectionOrigin; + if ( dir.Normalize() == 0.0f ) { + return false; + } + + bestAxis = 0; + bestValue = idMath::Fabs( box.GetAxis()[0] * dir ); + for ( i = 1; i < 3; i++ ) { + value = idMath::Fabs( box.GetAxis()[i] * dir ); + if ( value * box.GetExtents()[bestAxis] * box.GetExtents()[bestAxis] < bestValue * box.GetExtents()[i] * box.GetExtents()[i] ) { + bestValue = value; + bestAxis = i; + } + } + +#if 1 + + int j, minX, minY, maxY, minZ, maxZ; + idVec3 points[8]; + + minX = minY = maxY = minZ = maxZ = 0; + + for ( j = 0; j < 2; j++ ) { + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points ); + + if ( points[0].x <= 1.0f ) { + return false; + } + + minX = minY = maxY = minZ = maxZ = 0; + for ( i = 1; i < 8; i++ ) { + if ( points[i].x <= 1.0f ) { + return false; + } + if ( points[i].x < points[minX].x ) { + minX = i; + } + if ( points[minY].x * points[i].y < points[i].x * points[minY].y ) { + minY = i; + } else if ( points[maxY].x * points[i].y > points[i].x * points[maxY].y ) { + maxY = i; + } + if ( points[minZ].x * points[i].z < points[i].x * points[minZ].z ) { + minZ = i; + } else if ( points[maxZ].x * points[i].z > points[i].x * points[maxZ].z ) { + maxZ = i; + } + } + + if ( j == 0 ) { + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minY].y, points[minY].x ) + idMath::ATan16( points[maxY].y, points[maxY].x ) ) ) * axis[1]; + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minZ].z, points[minZ].x ) + idMath::ATan16( points[maxZ].z, points[maxZ].x ) ) ) * axis[2]; + dir.Normalize(); + } + } + + this->origin = projectionOrigin; + this->dNear = points[minX].x; + this->dFar = dFar; + this->dLeft = Max( idMath::Fabs( points[minY].y / points[minY].x ), idMath::Fabs( points[maxY].y / points[maxY].x ) ) * dFar; + this->dUp = Max( idMath::Fabs( points[minZ].z / points[minZ].x ), idMath::Fabs( points[maxZ].z / points[maxZ].x ) ) * dFar; + this->invFar = 1.0f / dFar; + +#elif 1 + + int j; + float f, x; + idBounds b; + idVec3 points[8]; + + for ( j = 0; j < 2; j++ ) { + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points ); + + b.Clear(); + for ( i = 0; i < 8; i++ ) { + x = points[i].x; + if ( x <= 1.0f ) { + return false; + } + f = 1.0f / x; + points[i].y *= f; + points[i].z *= f; + b.AddPoint( points[i] ); + } + + if ( j == 0 ) { + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][1] ) + idMath::ATan16( b[0][1] ) ) ) * axis[1]; + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][2] ) + idMath::ATan16( b[0][2] ) ) ) * axis[2]; + dir.Normalize(); + } + } + + this->origin = projectionOrigin; + this->dNear = b[0][0]; + this->dFar = dFar; + this->dLeft = Max( idMath::Fabs( b[0][1] ), idMath::Fabs( b[1][1] ) ) * dFar; + this->dUp = Max( idMath::Fabs( b[0][2] ), idMath::Fabs( b[1][2] ) ) * dFar; + this->invFar = 1.0f / dFar; + +#else + + float dist; + idVec3 org; + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + for ( i = 0; i < 3; i++ ) { + dist[i] = idMath::Fabs( box.GetExtents()[0] * ( axis[i] * box.GetAxis()[0] ) ) + + idMath::Fabs( box.GetExtents()[1] * ( axis[i] * box.GetAxis()[1] ) ) + + idMath::Fabs( box.GetExtents()[2] * ( axis[i] * box.GetAxis()[2] ) ); + } + + dist[0] = axis[0] * ( box.GetCenter() - projectionOrigin ) - dist[0]; + if ( dist[0] <= 1.0f ) { + return false; + } + float invDist = 1.0f / dist[0]; + + this->origin = projectionOrigin; + this->dNear = dist[0]; + this->dFar = dFar; + this->dLeft = dist[1] * invDist * dFar; + this->dUp = dist[2] * invDist * dFar; + this->invFar = 1.0f / dFar; + +#endif + + return true; +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the sphere. +============ +*/ +bool idFrustum::FromProjection( const idSphere &sphere, const idVec3 &projectionOrigin, const float dFar ) { + idVec3 dir; + float d, r, s, x, y; + + assert( dFar > 0.0f ); + + dir = sphere.GetOrigin() - projectionOrigin; + d = dir.Normalize(); + r = sphere.GetRadius(); + + if ( d <= r + 1.0f ) { + this->dNear = this->dFar = this->invFar = 0.0f; + return false; + } + + origin = projectionOrigin; + axis = dir.ToMat3(); + + s = idMath::Sqrt( d * d - r * r ); + x = r / d * s; + y = idMath::Sqrt( s * s - x * x ); + + this->dNear = d - r; + this->dFar = dFar; + this->dLeft = x / y * dFar; + this->dUp = dLeft; + this->invFar = 1.0f / dFar; + + return true; +} + +/* +============ +idFrustum::ConstrainToBounds + + Returns false if no part of the bounds extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToBounds( const idBounds &bounds ) { + float min, max, newdFar; + + bounds.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToBox + + Returns false if no part of the box extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToBox( const idBox &box ) { + float min, max, newdFar; + + box.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToSphere + + Returns false if no part of the sphere extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToSphere( const idSphere &sphere ) { + float min, max, newdFar; + + sphere.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToFrustum + + Returns false if no part of the frustum extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToFrustum( const idFrustum &frustum ) { + float min, max, newdFar; + + frustum.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ToPlanes + + planes point outwards +============ +*/ +void idFrustum::ToPlanes( idPlane planes[6] ) const { + int i; + idVec3 scaled[2]; + idVec3 points[4]; + + planes[0].Normal() = -axis[0]; + planes[0].SetDist( -dNear ); + planes[1].Normal() = axis[0]; + planes[1].SetDist( dFar ); + + scaled[0] = axis[1] * dLeft; + scaled[1] = axis[2] * dUp; + points[0] = scaled[0] + scaled[1]; + points[1] = -scaled[0] + scaled[1]; + points[2] = -scaled[0] - scaled[1]; + points[3] = scaled[0] - scaled[1]; + + for ( i = 0; i < 4; i++ ) { + planes[i+2].Normal() = points[i].Cross( points[(i+1)&3] - points[i] ); + planes[i+2].Normalize(); + planes[i+2].FitThroughPoint( points[i] ); + } +} + +/* +============ +idFrustum::ToPoints +============ +*/ +void idFrustum::ToPoints( idVec3 points[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + points[0] = scaled[0] + scaled[1]; + points[1] = scaled[0] - scaled[1]; + points[2] = points[1] - scaled[2]; + points[3] = points[0] - scaled[2]; + points[0] += scaled[2]; + points[1] += scaled[2]; + + scaled[0] = origin + axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + points[4] = scaled[0] + scaled[1]; + points[5] = scaled[0] - scaled[1]; + points[6] = points[5] - scaled[2]; + points[7] = points[4] - scaled[2]; + points[4] += scaled[2]; + points[5] += scaled[2]; +} + +/* +============ +idFrustum::ToClippedPoints +============ +*/ +void idFrustum::ToClippedPoints( const float fractions[4], idVec3 points[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + points[0] = scaled[0] + scaled[1]; + points[1] = scaled[0] - scaled[1]; + points[2] = points[1] - scaled[2]; + points[3] = points[0] - scaled[2]; + points[0] += scaled[2]; + points[1] += scaled[2]; + + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + points[4] = scaled[0] + scaled[1]; + points[5] = scaled[0] - scaled[1]; + points[6] = points[5] - scaled[2]; + points[7] = points[4] - scaled[2]; + points[4] += scaled[2]; + points[5] += scaled[2]; + + points[4] = origin + fractions[0] * points[4]; + points[5] = origin + fractions[1] * points[5]; + points[6] = origin + fractions[2] * points[6]; + points[7] = origin + fractions[3] * points[7]; +} + +/* +============ +idFrustum::ToIndexPoints +============ +*/ +void idFrustum::ToIndexPoints( idVec3 indexPoints[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + indexPoints[0] = scaled[0] - scaled[1]; + indexPoints[2] = scaled[0] + scaled[1]; + indexPoints[1] = indexPoints[0] + scaled[2]; + indexPoints[3] = indexPoints[2] + scaled[2]; + indexPoints[0] -= scaled[2]; + indexPoints[2] -= scaled[2]; + + scaled[0] = origin + axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + indexPoints[4] = scaled[0] - scaled[1]; + indexPoints[6] = scaled[0] + scaled[1]; + indexPoints[5] = indexPoints[4] + scaled[2]; + indexPoints[7] = indexPoints[6] + scaled[2]; + indexPoints[4] -= scaled[2]; + indexPoints[6] -= scaled[2]; +} + +/* +============ +idFrustum::ToIndexPointsAndCornerVecs + + 22 muls +============ +*/ +void idFrustum::ToIndexPointsAndCornerVecs( idVec3 indexPoints[8], idVec3 cornerVecs[4] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + indexPoints[0] = scaled[0] - scaled[1]; + indexPoints[2] = scaled[0] + scaled[1]; + indexPoints[1] = indexPoints[0] + scaled[2]; + indexPoints[3] = indexPoints[2] + scaled[2]; + indexPoints[0] -= scaled[2]; + indexPoints[2] -= scaled[2]; + + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + cornerVecs[0] = scaled[0] - scaled[1]; + cornerVecs[2] = scaled[0] + scaled[1]; + cornerVecs[1] = cornerVecs[0] + scaled[2]; + cornerVecs[3] = cornerVecs[2] + scaled[2]; + cornerVecs[0] -= scaled[2]; + cornerVecs[2] -= scaled[2]; + + indexPoints[4] = cornerVecs[0] + origin; + indexPoints[5] = cornerVecs[1] + origin; + indexPoints[6] = cornerVecs[2] + origin; + indexPoints[7] = cornerVecs[3] + origin; +} + +/* +============ +idFrustum::AxisProjection + + 18 muls +============ +*/ +void idFrustum::AxisProjection( const idVec3 indexPoints[8], const idVec3 cornerVecs[4], const idVec3 &dir, float &min, float &max ) const { + float dx, dy, dz; + int index; + + dy = dir.x * axis[1].x + dir.y * axis[1].y + dir.z * axis[1].z; + dz = dir.x * axis[2].x + dir.y * axis[2].y + dir.z * axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dir.x * cornerVecs[index].x + dir.y * cornerVecs[index].y + dir.z * cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + min = indexPoints[index] * dir; + index = ~index & 3; + dx = -dir.x * cornerVecs[index].x - dir.y * cornerVecs[index].y - dir.z * cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + max = indexPoints[index] * dir; +} + +/* +============ +idFrustum::AxisProjection + + 40 muls +============ +*/ +void idFrustum::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, dir, min, max ); +} + +/* +============ +idFrustum::AxisProjection + + 76 muls +============ +*/ +void idFrustum::AxisProjection( const idMat3 &ax, idBounds &bounds ) const { + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, ax[0], bounds[0][0], bounds[1][0] ); + AxisProjection( indexPoints, cornerVecs, ax[1], bounds[0][1], bounds[1][1] ); + AxisProjection( indexPoints, cornerVecs, ax[2], bounds[0][2], bounds[1][2] ); +} + +/* +============ +idFrustum::AddLocalLineToProjectionBoundsSetCull +============ +*/ +void idFrustum::AddLocalLineToProjectionBoundsSetCull( const idVec3 &start, const idVec3 &end, int &startCull, int &endCull, idBounds &bounds ) const { + idVec3 dir, p; + float d1, d2, fstart, fend, lstart, lend, f; + float leftScale, upScale; + int cull1, cull2; + +#ifdef FRUSTUM_DEBUG + static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + if ( r_showInteractionScissors.GetInteger() > 1 ) { + session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis ); + } +#endif + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + cull1 = FLOATSIGNBITSET( d1 ); + cull2 = FLOATSIGNBITSET( d2 ); + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = 1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 1; + cull2 |= FLOATSIGNBITSET( d2 ) << 1; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = -1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 2; + cull2 |= FLOATSIGNBITSET( d2 ) << 2; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = 1.0f; + bounds.AddPoint( p ); + } + } + } + } + + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 3; + cull2 |= FLOATSIGNBITSET( d2 ) << 3; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = -1.0f; + bounds.AddPoint( p ); + } + } + } + } + + if ( cull1 == 0 && start.x > 0.0f ) { + // add start point to projection bounds + p.x = start.x; + p.y = start.y * dFar / ( start.x * dLeft ); + p.z = start.z * dFar / ( start.x * dUp ); + bounds.AddPoint( p ); + } + + if ( cull2 == 0 && end.x > 0.0f ) { + // add end point to projection bounds + p.x = end.x; + p.y = end.y * dFar / ( end.x * dLeft ); + p.z = end.z * dFar / ( end.x * dUp ); + bounds.AddPoint( p ); + } + + if ( start.x < bounds[0].x ) { + bounds[0].x = start.x < 0.0f ? 0.0f : start.x; + } + if ( end.x < bounds[0].x ) { + bounds[0].x = end.x < 0.0f ? 0.0f : end.x; + } + + startCull = cull1; + endCull = cull2; +} + +/* +============ +idFrustum::AddLocalLineToProjectionBoundsUseCull +============ +*/ +void idFrustum::AddLocalLineToProjectionBoundsUseCull( const idVec3 &start, const idVec3 &end, int startCull, int endCull, idBounds &bounds ) const { + idVec3 dir, p; + float d1, d2, fstart, fend, lstart, lend, f; + float leftScale, upScale; + int clip; + + clip = startCull ^ endCull; + if ( !clip ) { + return; + } + +#ifdef FRUSTUM_DEBUG + static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + if ( r_showInteractionScissors.GetInteger() > 1 ) { + session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis ); + } +#endif + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + if ( clip & (1|2) ) { + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + if ( clip & 1 ) { + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = 1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + } + + if ( clip & 2 ) { + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = -1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + } + } + + if ( clip & (4|8) ) { + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + if ( clip & 4 ) { + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = 1.0f; + bounds.AddPoint( p ); + } + } + } + } + } + + if ( clip & 8 ) { + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = -1.0f; + bounds.AddPoint( p ); + } + } + } + } + } + } +} + +/* +============ +idFrustum::BoundsRayIntersection + + Returns true if the ray starts inside the bounds. + If there was an intersection scale1 <= scale2 +============ +*/ +bool idFrustum::BoundsRayIntersection( const idBounds &bounds, const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 end, p; + float d1, d2, f; + int i, startInside = 1; + + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + end = start + dir; + + for ( i = 0; i < 2; i++ ) { + d1 = start.x - bounds[i].x; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.x - bounds[i].x; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.y = start.y + f * dir.y; + if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) { + p.z = start.z + f * dir.z; + if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + d1 = start.y - bounds[i].y; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.y - bounds[i].y; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) { + p.z = start.z + f * dir.z; + if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + d1 = start.z - bounds[i].z; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.z - bounds[i].z; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) { + p.y = start.y + f * dir.y; + if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idBounds &bounds, idBounds &projectionBounds ) const { + return ProjectionBounds( idBox( bounds, vec3_origin, mat3_identity ), projectionBounds ); +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idBox &box, idBounds &projectionBounds ) const { + int i, p1, p2, pointCull[8], culled, outside; + float scale1, scale2; + idFrustum localFrustum; + idVec3 points[8], localOrigin; + idMat3 localAxis, localScaled; + idBounds bounds( -box.GetExtents(), box.GetExtents() ); + + // if the frustum origin is inside the bounds + if ( bounds.ContainsPoint( ( origin - box.GetCenter() ) * box.GetAxis().Transpose() ) ) { + // bounds that cover the whole frustum + float boxMin, boxMax, base; + + base = origin * axis[0]; + box.AxisProjection( axis[0], boxMin, boxMax ); + + projectionBounds[0].x = boxMin - base; + projectionBounds[1].x = boxMax - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // transform the bounds into the space of this frustum + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + BoxToPoints( localOrigin, box.GetExtents(), localAxis, points ); + + // test outer four edges of the bounds + culled = -1; + outside = 0; + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if the bounds are completely outside this frustum + if ( culled ) { + return false; + } + + // if the bounds are completely inside this frustum + if ( !outside ) { + return true; + } + + // test the remaining edges of the bounds + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the bounds extend beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + localOrigin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose(); + localScaled = axis * box.GetAxis().Transpose(); + localScaled[0] *= dFar; + localScaled[1] *= dLeft; + localScaled[2] *= dUp; + + // test the outer edges of this frustum for intersection with the bounds + if ( (outside & 2) && (outside & 8) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idSphere &sphere, idBounds &projectionBounds ) const { + float d, r, rs, sFar; + idVec3 center; + + projectionBounds.Clear(); + + center = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + r = sphere.GetRadius(); + rs = r * r; + sFar = dFar * dFar; + + // test left/right planes + d = dFar * idMath::Fabs( center.y ) - dLeft * center.x; + if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) { + return false; + } + + // test up/down planes + d = dFar * idMath::Fabs( center.z ) - dUp * center.x; + if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) { + return false; + } + + // FIXME: implement + + // bounds that cover the whole frustum + projectionBounds[0].x = 0.0f; + projectionBounds[1].x = dFar; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idFrustum &frustum, idBounds &projectionBounds ) const { + int i, p1, p2, pointCull[8], culled, outside; + float scale1, scale2; + idFrustum localFrustum; + idVec3 points[8], localOrigin; + idMat3 localScaled; + + // if the frustum origin is inside the other frustum + if ( frustum.ContainsPoint( origin ) ) { + // bounds that cover the whole frustum + float frustumMin, frustumMax, base; + + base = origin * axis[0]; + frustum.AxisProjection( axis[0], frustumMin, frustumMax ); + + projectionBounds[0].x = frustumMin - base; + projectionBounds[1].x = frustumMax - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // transform the given frustum into the space of this frustum + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum.axis = frustum.axis * axis.Transpose(); + localFrustum.ToPoints( points ); + + // test outer four edges of the other frustum + culled = -1; + outside = 0; + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if the other frustum is completely outside this frustum + if ( culled ) { + return false; + } + + // if the other frustum is completely inside this frustum + if ( !outside ) { + return true; + } + + // test the remaining edges of the other frustum + if ( localFrustum.dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the other frustum extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + localOrigin = ( origin - frustum.origin ) * frustum.axis.Transpose(); + localScaled = axis * frustum.axis.Transpose(); + localScaled[0] *= dFar; + localScaled[1] *= dLeft; + localScaled[2] *= dUp; + + // test the outer edges of this frustum for intersection with the other frustum + if ( (outside & 2) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] - localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] - localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] + localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] + localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idWinding &winding, idBounds &projectionBounds ) const { + int i, p1, p2, *pointCull, culled, outside; + float scale; + idVec3 *localPoints; + idMat3 transpose, scaled; + idPlane plane; + + projectionBounds.Clear(); + + // transform the winding points into the space of this frustum + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + // test the winding edges + culled = -1; + outside = 0; + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + for ( i = 0; i < winding.GetNumPoints(); i += 2 ) { + p1 = i; + p2 = (i+1)%winding.GetNumPoints(); + AddLocalLineToProjectionBoundsSetCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if completely culled + if ( culled ) { + return false; + } + + // if completely inside + if ( !outside ) { + return true; + } + + // test remaining winding edges + for ( i = 1; i < winding.GetNumPoints(); i += 2 ) { + p1 = i; + p2 = (i+1)%winding.GetNumPoints(); + AddLocalLineToProjectionBoundsUseCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the winding extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + winding.GetPlane( plane ); + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + // test the outer edges of this frustum for intersection with the winding + if ( (outside & 2) && (outside & 8) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] - scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] + scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] - scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] + scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ClipFrustumToBox + + Clips the frustum far extents to the box. +============ +*/ +void idFrustum::ClipFrustumToBox( const idBox &box, float clipFractions[4], int clipPlanes[4] ) const { + int i, index; + float f, minf; + idMat3 scaled, localAxis, transpose; + idVec3 localOrigin, cornerVecs[4]; + idBounds bounds; + + transpose = box.GetAxis(); + transpose.TransposeSelf(); + localOrigin = ( origin - box.GetCenter() ) * transpose; + localAxis = axis * transpose; + + scaled[0] = localAxis[0] * dFar; + scaled[1] = localAxis[1] * dLeft; + scaled[2] = localAxis[2] * dUp; + cornerVecs[0] = scaled[0] + scaled[1]; + cornerVecs[1] = scaled[0] - scaled[1]; + cornerVecs[2] = cornerVecs[1] - scaled[2]; + cornerVecs[3] = cornerVecs[0] - scaled[2]; + cornerVecs[0] += scaled[2]; + cornerVecs[1] += scaled[2]; + + bounds[0] = -box.GetExtents(); + bounds[1] = box.GetExtents(); + + minf = ( dNear + 1.0f ) * invFar; + + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// jscott: made safe + clipFractions[i] = MAX_WORLD_COORD; + clipPlanes[i] = 1; + + if( cornerVecs[i].x != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].x ); + f = ( bounds[index].x - localOrigin.x ) / cornerVecs[i].x; + clipFractions[i] = f; + clipPlanes[i] = 1 << index; + } + + if( cornerVecs[i].y != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].y ); + f = ( bounds[index].y - localOrigin.y ) / cornerVecs[i].y; + if ( f < clipFractions[i] ) { + clipFractions[i] = f; + clipPlanes[i] = 4 << index; + } + } + + if( cornerVecs[i].z != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].z ); + f = ( bounds[index].z - localOrigin.z ) / cornerVecs[i].z; + if ( f < clipFractions[i] ) { + clipFractions[i] = f; + clipPlanes[i] = 16 << index; + } + } +// RAVEN END + // make sure the frustum is not clipped between the frustum origin and the near plane + if ( clipFractions[i] < minf ) { + clipFractions[i] = minf; + } + } +} + +/* +============ +idFrustum::ClipLine + + Returns true if part of the line is inside the frustum. + Does not clip to the near and far plane. +============ +*/ +bool idFrustum::ClipLine( const idVec3 localPoints[8], const idVec3 points[8], int startIndex, int endIndex, idVec3 &start, idVec3 &end, int &startClip, int &endClip ) const { + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + float scale1, scale2; + int startCull, endCull; + idVec3 localStart, localEnd, localDir; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + + localStart = localPoints[startIndex]; + localEnd = localPoints[endIndex]; + localDir = localEnd - localStart; + + startClip = endClip = -1; + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + fstart = dFar * localStart.y; + fend = dFar * localEnd.y; + lstart = dLeft * localStart.x; + lend = dLeft * localEnd.x; + + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + startCull = FLOATSIGNBITSET( d1 ); + endCull = FLOATSIGNBITSET( d2 ); + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 0; } + if ( f > scale2 ) { scale2 = f; endClip = 0; } + } + } + } + } + + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 1; + endCull |= FLOATSIGNBITSET( d2 ) << 1; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 1; } + if ( f > scale2 ) { scale2 = f; endClip = 1; } + } + } + } + } + + fstart = dFar * localStart.z; + fend = dFar * localEnd.z; + lstart = dUp * localStart.x; + lend = dUp * localEnd.x; + + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 2; + endCull |= FLOATSIGNBITSET( d2 ) << 2; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 2; } + if ( f > scale2 ) { scale2 = f; endClip = 2; } + } + } + } + } + + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 3; + endCull |= FLOATSIGNBITSET( d2 ) << 3; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 3; } + if ( f > scale2 ) { scale2 = f; endClip = 3; } + } + } + } + } + + // if completely inside + if ( !( startCull | endCull ) ) { + start = points[startIndex]; + end = points[endIndex]; + return true; + } + else if ( scale1 <= scale2 ) { + if ( !startCull ) { + start = points[startIndex]; + startClip = -1; + } + else { + start = points[startIndex] + scale1 * ( points[endIndex] - points[startIndex] ); + } + if ( !endCull ) { + end = points[endIndex]; + endClip = -1; + } + else { + end = points[startIndex] + scale2 * ( points[endIndex] - points[startIndex] ); + } + return true; + } + return false; +} + +/* +============ +idFrustum::AddLocalCapsToProjectionBounds +============ +*/ +static int capPointIndex[4][2] = { + { 0, 3 }, + { 1, 2 }, + { 0, 1 }, + { 2, 3 } +}; + +ID_INLINE bool idFrustum::AddLocalCapsToProjectionBounds( const idVec3 endPoints[4], const int endPointCull[4], const idVec3 &point, int pointCull, int pointClip, idBounds &projectionBounds ) const { + int *p; + + if ( pointClip < 0 ) { + return false; + } + p = capPointIndex[pointClip]; + AddLocalLineToProjectionBoundsUseCull( endPoints[p[0]], point, endPointCull[p[0]], pointCull, projectionBounds ); + AddLocalLineToProjectionBoundsUseCull( endPoints[p[1]], point, endPointCull[p[1]], pointCull, projectionBounds ); + return true; +} + +/* +============ +idFrustum::ClippedProjectionBounds +============ +*/ +bool idFrustum::ClippedProjectionBounds( const idFrustum &frustum, const idBox &clipBox, idBounds &projectionBounds ) const { + int i, p1, p2, clipPointCull[8], clipPlanes[4], usedClipPlanes, nearCull, farCull, outside; + int pointCull[2], startClip, endClip, boxPointCull[8]; + float clipFractions[4], s1, s2, t1, t2, leftScale, upScale; + idFrustum localFrustum; + idVec3 clipPoints[8], localPoints1[8], localPoints2[8], localOrigin1, localOrigin2, start, end; + idMat3 localAxis1, localAxis2, transpose; + idBounds clipBounds; + + // if the frustum origin is inside the other frustum + if ( frustum.ContainsPoint( origin ) ) { + // bounds that cover the whole frustum + float clipBoxMin, clipBoxMax, frustumMin, frustumMax, base; + + base = origin * axis[0]; + clipBox.AxisProjection( axis[0], clipBoxMin, clipBoxMax ); + frustum.AxisProjection( axis[0], frustumMin, frustumMax ); + + projectionBounds[0].x = Max( clipBoxMin, frustumMin ) - base; + projectionBounds[1].x = Min( clipBoxMax, frustumMax ) - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // clip the outer edges of the given frustum to the clip bounds + frustum.ClipFrustumToBox( clipBox, clipFractions, clipPlanes ); + usedClipPlanes = clipPlanes[0] | clipPlanes[1] | clipPlanes[2] | clipPlanes[3]; + + // transform the clipped frustum to the space of this frustum + transpose = axis; + transpose.TransposeSelf(); + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * transpose; + localFrustum.axis = frustum.axis * transpose; + localFrustum.ToClippedPoints( clipFractions, clipPoints ); + + // test outer four edges of the clipped frustum + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + + // get cull bits for the clipped frustum + outside = clipPointCull[0] | clipPointCull[1] | clipPointCull[2] | clipPointCull[3] | + clipPointCull[4] | clipPointCull[5] | clipPointCull[6] | clipPointCull[7]; + nearCull = clipPointCull[0] & clipPointCull[1] & clipPointCull[2] & clipPointCull[3]; + farCull = clipPointCull[4] & clipPointCull[5] & clipPointCull[6] & clipPointCull[7]; + + // if the clipped frustum is not completely inside this frustum + if ( outside ) { + + // test the remaining edges of the clipped frustum + if ( !nearCull && localFrustum.dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + } + + if ( !farCull ) { + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + } + } + + // if the clipped frustum far end points are inside this frustum + if ( !( farCull && !( nearCull & farCull ) ) && + // if the clipped frustum is not clipped to a single plane of the clip bounds + ( clipPlanes[0] != clipPlanes[1] || clipPlanes[1] != clipPlanes[2] || clipPlanes[2] != clipPlanes[3] ) ) { + + // transform the clip box into the space of the other frustum + transpose = frustum.axis; + transpose.TransposeSelf(); + localOrigin1 = ( clipBox.GetCenter() - frustum.origin ) * transpose; + localAxis1 = clipBox.GetAxis() * transpose; + BoxToPoints( localOrigin1, clipBox.GetExtents(), localAxis1, localPoints1 ); + + // cull the box corners with the other frustum + leftScale = frustum.dLeft * frustum.invFar; + upScale = frustum.dUp * frustum.invFar; + for ( i = 0; i < 8; i++ ) { + idVec3 &p = localPoints1[i]; + if ( !( boxVertPlanes[i] & usedClipPlanes ) || p.x <= 0.0f ) { + boxPointCull[i] = 1|2|4|8; + } + else { + boxPointCull[i] = 0; + if ( idMath::Fabs( p.y ) > p.x * leftScale ) { + boxPointCull[i] |= 1 << FLOATSIGNBITSET( p.y ); + } + if ( idMath::Fabs( p.z ) > p.x * upScale ) { + boxPointCull[i] |= 4 << FLOATSIGNBITSET( p.z ); + } + } + } + + // transform the clip box into the space of this frustum + transpose = axis; + transpose.TransposeSelf(); + localOrigin2 = ( clipBox.GetCenter() - origin ) * transpose; + localAxis2 = clipBox.GetAxis() * transpose; + BoxToPoints( localOrigin2, clipBox.GetExtents(), localAxis2, localPoints2 ); + + // clip the edges of the clip bounds to the other frustum and add the clipped edges to the projection bounds + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + } + + // if the clipped frustum extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + // transform this frustum into the space of the other frustum + transpose = frustum.axis; + transpose.TransposeSelf(); + localOrigin1 = ( origin - frustum.origin ) * transpose; + localAxis1 = axis * transpose; + localAxis1[0] *= dFar; + localAxis1[1] *= dLeft; + localAxis1[2] *= dUp; + + // transform this frustum into the space of the clip bounds + transpose = clipBox.GetAxis(); + transpose.TransposeSelf(); + localOrigin2 = ( origin - clipBox.GetCenter() ) * transpose; + localAxis2 = axis * transpose; + localAxis2[0] *= dFar; + localAxis2[1] *= dLeft; + localAxis2[2] *= dUp; + + clipBounds[0] = -clipBox.GetExtents(); + clipBounds[1] = clipBox.GetExtents(); + + // test the outer edges of this frustum for intersection with both the other frustum and the clip bounds + if ( (outside & 2) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] - localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] - localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, -1.0f ) ); + } + } + } + if ( (outside & 2) && (outside & 4) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] + localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] + localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, 1.0f ) ); + } + } + } + if ( (outside & 1) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] - localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] - localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, -1.0f ) ); + } + } + } + if ( (outside & 1) && (outside & 2) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] + localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] + localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, 1.0f ) ); + } + } + } + } + + return true; +} diff --git a/source/idlib/bv/Frustum.h b/source/idlib/bv/Frustum.h new file mode 100644 index 0000000..17d70e7 --- /dev/null +++ b/source/idlib/bv/Frustum.h @@ -0,0 +1,238 @@ + +#ifndef __BV_FRUSTUM_H__ +#define __BV_FRUSTUM_H__ + +/* +=============================================================================== + + Orthogonal Frustum + +=============================================================================== +*/ + +class idFrustum { +public: + idFrustum( void ); + + void SetOrigin( const idVec3 &origin ); + void SetAxis( const idMat3 &axis ); + void SetSize( float dNear, float dFar, float dLeft, float dUp ); + void SetPyramid( float dNear, float dFar ); + void MoveNearDistance( float dNear ); + void MoveFarDistance( float dFar ); + + const idVec3 & GetOrigin( void ) const; // returns frustum origin + const idMat3 & GetAxis( void ) const; // returns frustum orientation + idVec3 GetCenter( void ) const; // returns center of frustum + + bool IsValid( void ) const; // returns true if the frustum is valid + float GetNearDistance( void ) const; // returns distance to near plane + float GetFarDistance( void ) const; // returns distance to far plane + float GetLeft( void ) const; // returns left vector length + float GetUp( void ) const; // returns up vector length + + idFrustum Expand( const float d ) const; // returns frustum expanded in all directions with the given value + idFrustum & ExpandSelf( const float d ); // expands frustum in all directions with the given value + idFrustum Translate( const idVec3 &translation ) const; // returns translated frustum + idFrustum & TranslateSelf( const idVec3 &translation ); // translates frustum + idFrustum Rotate( const idMat3 &rotation ) const; // returns rotated frustum + idFrustum & RotateSelf( const idMat3 &rotation ); // rotates frustum + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + // fast culling but might not cull everything outside the frustum + bool CullPoint( const idVec3 &point ) const; + bool CullBounds( const idBounds &bounds ) const; + bool CullBox( const idBox &box ) const; + bool CullSphere( const idSphere &sphere ) const; + bool CullFrustum( const idFrustum &frustum ) const; + bool CullWinding( const class idWinding &winding ) const; + + // exact intersection tests + bool ContainsPoint( const idVec3 &point ) const; + bool IntersectsBounds( const idBounds &bounds ) const; + bool IntersectsBox( const idBox &box ) const; + bool IntersectsSphere( const idSphere &sphere ) const; + bool IntersectsFrustum( const idFrustum &frustum ) const; + bool IntersectsWinding( const idWinding &winding ) const; + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // returns true if the projection origin is far enough away from the bounding volume to create a valid frustum + bool FromProjection( const idBounds &bounds, const idVec3 &projectionOrigin, const float dFar ); + bool FromProjection( const idBox &box, const idVec3 &projectionOrigin, const float dFar ); + bool FromProjection( const idSphere &sphere, const idVec3 &projectionOrigin, const float dFar ); + + // moves the far plane so it extends just beyond the bounding volume + bool ConstrainToBounds( const idBounds &bounds ); + bool ConstrainToBox( const idBox &box ); + bool ConstrainToSphere( const idSphere &sphere ); + bool ConstrainToFrustum( const idFrustum &frustum ); + + void ToPlanes( idPlane planes[6] ) const; // planes point outwards + void ToPoints( idVec3 points[8] ) const; // 8 corners of the frustum + + // calculates the projection of this frustum onto the given axis + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idMat3 &ax, idBounds &bounds ) const; + + // calculates the bounds for the projection in this frustum + bool ProjectionBounds( const idBounds &bounds, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idBox &box, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idSphere &sphere, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idFrustum &frustum, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idWinding &winding, idBounds &projectionBounds ) const; + + // calculates the bounds for the projection in this frustum of the given frustum clipped to the given box + bool ClippedProjectionBounds( const idFrustum &frustum, const idBox &clipBox, idBounds &projectionBounds ) const; + +private: + idVec3 origin; // frustum origin + idMat3 axis; // frustum orientation + float dNear; // distance of near plane, dNear >= 0.0f + float dFar; // distance of far plane, dFar > dNear + float dLeft; // half the width at the far plane + float dUp; // half the height at the far plane + float invFar; // 1.0f / dFar + +private: + bool CullLocalBox( const idVec3 &localOrigin, const idVec3 &extents, const idMat3 &localAxis ) const; + bool CullLocalFrustum( const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const; + bool CullLocalWinding( const idVec3 *points, const int numPoints, int *pointCull ) const; + bool BoundsCullLocalFrustum( const idBounds &bounds, const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const; + bool LocalLineIntersection( const idVec3 &start, const idVec3 &end ) const; + bool LocalRayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + bool LocalFrustumIntersectsFrustum( const idVec3 points[8], const bool testFirstSide ) const; + bool LocalFrustumIntersectsBounds( const idVec3 points[8], const idBounds &bounds ) const; + void ToClippedPoints( const float fractions[4], idVec3 points[8] ) const; + void ToIndexPoints( idVec3 indexPoints[8] ) const; + void ToIndexPointsAndCornerVecs( idVec3 indexPoints[8], idVec3 cornerVecs[4] ) const; + void AxisProjection( const idVec3 indexPoints[8], const idVec3 cornerVecs[4], const idVec3 &dir, float &min, float &max ) const; + void AddLocalLineToProjectionBoundsSetCull( const idVec3 &start, const idVec3 &end, int &startCull, int &endCull, idBounds &bounds ) const; + void AddLocalLineToProjectionBoundsUseCull( const idVec3 &start, const idVec3 &end, int startCull, int endCull, idBounds &bounds ) const; + bool AddLocalCapsToProjectionBounds( const idVec3 endPoints[4], const int endPointCull[4], const idVec3 &point, int pointCull, int pointClip, idBounds &projectionBounds ) const; + bool BoundsRayIntersection( const idBounds &bounds, const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + void ClipFrustumToBox( const idBox &box, float clipFractions[4], int clipPlanes[4] ) const; + bool ClipLine( const idVec3 localPoints[8], const idVec3 points[8], int startIndex, int endIndex, idVec3 &start, idVec3 &end, int &startClip, int &endClip ) const; +}; + + +ID_INLINE idFrustum::idFrustum( void ) { + dNear = dFar = 0.0f; +} + +ID_INLINE void idFrustum::SetOrigin( const idVec3 &origin ) { + this->origin = origin; +} + +ID_INLINE void idFrustum::SetAxis( const idMat3 &axis ) { + this->axis = axis; +} + +ID_INLINE void idFrustum::SetSize( float dNear, float dFar, float dLeft, float dUp ) { + assert( dNear >= 0.0f && dFar > dNear && dLeft > 0.0f && dUp > 0.0f ); + this->dNear = dNear; + this->dFar = dFar; + this->dLeft = dLeft; + this->dUp = dUp; + this->invFar = 1.0f / dFar; +} + +ID_INLINE void idFrustum::SetPyramid( float dNear, float dFar ) { + assert( dNear >= 0.0f && dFar > dNear ); + this->dNear = dNear; + this->dFar = dFar; + this->dLeft = dFar; + this->dUp = dFar; + this->invFar = 1.0f / dFar; +} + +ID_INLINE void idFrustum::MoveNearDistance( float dNear ) { + assert( dNear >= 0.0f ); + this->dNear = dNear; +} + +ID_INLINE void idFrustum::MoveFarDistance( float dFar ) { + assert( dFar > this->dNear ); + float scale = dFar / this->dFar; + this->dFar = dFar; + this->dLeft *= scale; + this->dUp *= scale; + this->invFar = 1.0f / dFar; +} + +ID_INLINE const idVec3 &idFrustum::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idFrustum::GetAxis( void ) const { + return axis; +} + +ID_INLINE idVec3 idFrustum::GetCenter( void ) const { + return ( origin + axis[0] * ( ( dFar - dNear ) * 0.5f ) ); +} + +ID_INLINE bool idFrustum::IsValid( void ) const { + return ( dFar > dNear ); +} + +ID_INLINE float idFrustum::GetNearDistance( void ) const { + return dNear; +} + +ID_INLINE float idFrustum::GetFarDistance( void ) const { + return dFar; +} + +ID_INLINE float idFrustum::GetLeft( void ) const { + return dLeft; +} + +ID_INLINE float idFrustum::GetUp( void ) const { + return dUp; +} + +ID_INLINE idFrustum idFrustum::Expand( const float d ) const { + idFrustum f = *this; + f.origin -= d * f.axis[0]; + f.dFar += 2.0f * d; + f.dLeft = f.dFar * dLeft * invFar; + f.dUp = f.dFar * dUp * invFar; + f.invFar = 1.0f / dFar; + return f; +} + +ID_INLINE idFrustum &idFrustum::ExpandSelf( const float d ) { + origin -= d * axis[0]; + dFar += 2.0f * d; + dLeft = dFar * dLeft * invFar; + dUp = dFar * dUp * invFar; + invFar = 1.0f / dFar; + return *this; +} + +ID_INLINE idFrustum idFrustum::Translate( const idVec3 &translation ) const { + idFrustum f = *this; + f.origin += translation; + return f; +} + +ID_INLINE idFrustum &idFrustum::TranslateSelf( const idVec3 &translation ) { + origin += translation; + return *this; +} + +ID_INLINE idFrustum idFrustum::Rotate( const idMat3 &rotation ) const { + idFrustum f = *this; + f.axis *= rotation; + return f; +} + +ID_INLINE idFrustum &idFrustum::RotateSelf( const idMat3 &rotation ) { + axis *= rotation; + return *this; +} + +#endif /* !__BV_FRUSTUM_H__ */ diff --git a/source/idlib/bv/Sphere.cpp b/source/idlib/bv/Sphere.cpp new file mode 100644 index 0000000..9218ba6 --- /dev/null +++ b/source/idlib/bv/Sphere.cpp @@ -0,0 +1,128 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +idSphere sphere_zero( vec3_zero, 0.0f ); + + +/* +================ +idSphere::PlaneDistance +================ +*/ +float idSphere::PlaneDistance( const idPlane &plane ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius ) { + return d - radius; + } + if ( d < -radius ) { + return d + radius; + } + return 0.0f; +} + +/* +================ +idSphere::PlaneSide +================ +*/ +int idSphere::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius + epsilon ) { + return PLANESIDE_FRONT; + } + if ( d < -radius - epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idSphere::LineIntersection + + Returns true if the line intersects the sphere between the start and end point. +============ +*/ +bool idSphere::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + idVec3 r, s, e; + float a; + + s = start - origin; + e = end - origin; + r = e - s; + a = -s * r; + if ( a <= 0 ) { + return ( s * s < radius * radius ); + } + else if ( a >= r * r ) { + return ( e * e < radius * radius ); + } + else { + r = s + ( a / ( r * r ) ) * r; + return ( r * r < radius * radius ); + } +} + +/* +============ +idSphere::RayIntersection + + Returns true if the ray intersects the sphere. + The ray can intersect the sphere in both directions from the start point. + If start is inside the sphere then scale1 < 0 and scale2 > 0. +============ +*/ +bool idSphere::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + double a, b, c, d, sqrtd; + idVec3 p; + + p = start - origin; + a = dir * dir; + b = dir * p; + c = p * p - radius * radius; + d = b * b - c * a; + + if ( d < 0.0f ) { + return false; + } + + sqrtd = idMath::Sqrt( d ); + a = 1.0f / a; + + scale1 = ( -b + sqrtd ) * a; + scale2 = ( -b - sqrtd ) * a; + + return true; +} + +/* +============ +idSphere::FromPoints + + Tight sphere for a point set. +============ +*/ +void idSphere::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float radiusSqr, dist; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, points, numPoints ); + + origin = ( mins + maxs ) * 0.5f; + + radiusSqr = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dist = ( points[i] - origin ).LengthSqr(); + if ( dist > radiusSqr ) { + radiusSqr = dist; + } + } + radius = idMath::Sqrt( radiusSqr ); +} diff --git a/source/idlib/bv/Sphere.h b/source/idlib/bv/Sphere.h new file mode 100644 index 0000000..42ba56b --- /dev/null +++ b/source/idlib/bv/Sphere.h @@ -0,0 +1,248 @@ + +#ifndef __BV_SPHERE_H__ +#define __BV_SPHERE_H__ + +/* +=============================================================================== + + Sphere + +=============================================================================== +*/ + +class idSphere { +public: + idSphere( void ); + explicit idSphere( const idVec3 &point ); + explicit idSphere( const idVec3 &point, const float r ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idSphere operator+( const idVec3 &t ) const; // returns tranlated sphere + idSphere & operator+=( const idVec3 &t ); // translate the sphere + idSphere operator+( const idSphere &s ) const; + idSphere & operator+=( const idSphere &s ); + + bool Compare( const idSphere &a ) const; // exact compare, no epsilon + bool Compare( const idSphere &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idSphere &a ) const; // exact compare, no epsilon + bool operator!=( const idSphere &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out sphere + void Zero( void ); // single point at origin + void SetOrigin( const idVec3 &o ); // set origin of sphere + void SetRadius( const float r ); // set square radius + + const idVec3 & GetOrigin( void ) const; // returns origin of sphere + float GetRadius( void ) const; // returns sphere radius + bool IsCleared( void ) const; // returns true if sphere is inside out + + bool AddPoint( const idVec3 &p ); // add the point, returns true if the sphere expanded + bool AddSphere( const idSphere &s ); // add the sphere, returns true if the sphere expanded + idSphere Expand( const float d ) const; // return bounds expanded in all directions with the given value + idSphere & ExpandSelf( const float d ); // expand bounds in all directions with the given value + idSphere Translate( const idVec3 &translation ) const; + idSphere & TranslateSelf( const idVec3 &translation ); + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsSphere( const idSphere &s ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // Tight sphere for a point set. + void FromPoints( const idVec3 *points, const int numPoints ); + // Most tight sphere for a translation. + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ); + // Most tight sphere for a rotation. + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ); + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + +private: + idVec3 origin; + float radius; +}; + +extern idSphere sphere_zero; + +ID_INLINE idSphere::idSphere( void ) { +} + +ID_INLINE idSphere::idSphere( const idVec3 &point ) { + origin = point; + radius = 0.0f; +} + +ID_INLINE idSphere::idSphere( const idVec3 &point, const float r ) { + origin = point; + radius = r; +} + +ID_INLINE float idSphere::operator[]( const int index ) const { + return ((float *) &origin)[index]; +} + +ID_INLINE float &idSphere::operator[]( const int index ) { + return ((float *) &origin)[index]; +} + +ID_INLINE idSphere idSphere::operator+( const idVec3 &t ) const { + return idSphere( origin + t, radius ); +} + +ID_INLINE idSphere &idSphere::operator+=( const idVec3 &t ) { + origin += t; + return *this; +} + +ID_INLINE bool idSphere::Compare( const idSphere &a ) const { + return ( origin.Compare( a.origin ) && radius == a.radius ); +} + +ID_INLINE bool idSphere::Compare( const idSphere &a, const float epsilon ) const { + return ( origin.Compare( a.origin, epsilon ) && idMath::Fabs( radius - a.radius ) <= epsilon ); +} + +ID_INLINE bool idSphere::operator==( const idSphere &a ) const { + return Compare( a ); +} + +ID_INLINE bool idSphere::operator!=( const idSphere &a ) const { + return !Compare( a ); +} + +ID_INLINE void idSphere::Clear( void ) { + origin.Zero(); + radius = -1.0f; +} + +ID_INLINE void idSphere::Zero( void ) { + origin.Zero(); + radius = 0.0f; +} + +ID_INLINE void idSphere::SetOrigin( const idVec3 &o ) { + origin = o; +} + +ID_INLINE void idSphere::SetRadius( const float r ) { + radius = r; +} + +ID_INLINE const idVec3 &idSphere::GetOrigin( void ) const { + return origin; +} + +ID_INLINE float idSphere::GetRadius( void ) const { + return radius; +} + +ID_INLINE bool idSphere::IsCleared( void ) const { + return ( radius < 0.0f ); +} + +ID_INLINE bool idSphere::AddPoint( const idVec3 &p ) { + if ( radius < 0.0f ) { + origin = p; + radius = 0.0f; + return true; + } + else { + float r = ( p - origin ).LengthSqr(); + if ( r > radius * radius ) { + r = idMath::Sqrt( r ); + origin += ( p - origin ) * 0.5f * (1.0f - radius / r ); + radius += 0.5f * ( r - radius ); + return true; + } + return false; + } +} + +ID_INLINE bool idSphere::AddSphere( const idSphere &s ) { + if ( radius < 0.0f ) { + origin = s.origin; + radius = s.radius; + return true; + } + else { + float r = ( s.origin - origin ).LengthSqr(); + if ( r > ( radius + s.radius ) * ( radius + s.radius ) ) { + r = idMath::Sqrt( r ); + origin += ( s.origin - origin ) * 0.5f * (1.0f - radius / ( r + s.radius ) ); + radius += 0.5f * ( ( r + s.radius ) - radius ); + return true; + } + return false; + } +} + +ID_INLINE idSphere idSphere::Expand( const float d ) const { + return idSphere( origin, radius + d ); +} + +ID_INLINE idSphere &idSphere::ExpandSelf( const float d ) { + radius += d; + return *this; +} + +ID_INLINE idSphere idSphere::Translate( const idVec3 &translation ) const { + return idSphere( origin + translation, radius ); +} + +ID_INLINE idSphere &idSphere::TranslateSelf( const idVec3 &translation ) { + origin += translation; + return *this; +} + +ID_INLINE bool idSphere::ContainsPoint( const idVec3 &p ) const { + if ( ( p - origin ).LengthSqr() > radius * radius ) { + return false; + } + return true; +} + +ID_INLINE bool idSphere::IntersectsSphere( const idSphere &s ) const { + float r = s.radius + radius; + if ( ( s.origin - origin ).LengthSqr() > r * r ) { + return false; + } + return true; +} + +ID_INLINE void idSphere::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + origin = point + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ) { + origin = start + sphere.origin + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + idVec3 end = rotation * point; + origin = ( point + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - point ).LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ) { + idVec3 end = rotation * sphere.origin; + origin = start + ( sphere.origin + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - sphere.origin ).LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d; + d = dir * origin; + min = d - radius; + max = d + radius; +} + +#endif /* !__BV_SPHERE_H__ */ diff --git a/source/idlib/containers/BTree.h b/source/idlib/containers/BTree.h new file mode 100644 index 0000000..ab2d1e0 --- /dev/null +++ b/source/idlib/containers/BTree.h @@ -0,0 +1,496 @@ + +#ifndef __BTREE_H__ +#define __BTREE_H__ + +/* +=============================================================================== + + Balanced Search Tree + +=============================================================================== +*/ + +//#define BTREE_CHECK + +template< class objType, class keyType > +class idBTreeNode { +public: + keyType key; // key used for sorting + objType * object; // if != NULL pointer to object stored in leaf node + idBTreeNode * parent; // parent node + idBTreeNode * next; // next sibling + idBTreeNode * prev; // prev sibling + int numChildren; // number of children + idBTreeNode * firstChild; // first child + idBTreeNode * lastChild; // last child +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +class idBTree { +public: + idBTree( void ); + ~idBTree( void ); + + void Init( void ); + void Shutdown( void ); + + idBTreeNode * Add( objType *object, keyType key ); // add an object to the tree + void Remove( idBTreeNode *node ); // remove an object node from the tree + + objType * Find( keyType key ) const; // find an object using the given key + objType * FindSmallestLargerEqual( keyType key ) const; // find an object with the smallest key larger equal the given key + objType * FindLargestSmallerEqual( keyType key ) const; // find an object with the largest key smaller equal the given key + + idBTreeNode * GetRoot( void ) const; // returns the root node of the tree + int GetNodeCount( void ) const; // returns the total number of nodes in the tree + idBTreeNode * GetNext( idBTreeNode *node ) const; // goes through all nodes of the tree + idBTreeNode * GetNextLeaf( idBTreeNode *node ) const; // goes through all leaf nodes of the tree + +private: + idBTreeNode * root; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc,128,MA_DEFAULT> nodeAllocator; +// RAVEN END + + idBTreeNode * AllocNode( void ); + void FreeNode( idBTreeNode *node ); + void SplitNode( idBTreeNode *node ); + idBTreeNode * MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ); + + void CheckTree_r( idBTreeNode *node, int &numNodes ) const; + void CheckTree( void ) const; +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::idBTree( void ) { + assert( maxChildrenPerNode >= 4 ); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::~idBTree( void ) { + Shutdown(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Init( void ) { + root = AllocNode(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Shutdown( void ) { + nodeAllocator.Shutdown(); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::Add( objType *object, keyType key ) { + idBTreeNode *node, *child, *newNode; + + if ( root->numChildren >= maxChildrenPerNode ) { + newNode = AllocNode(); + newNode->key = root->key; + newNode->firstChild = root; + newNode->lastChild = root; + newNode->numChildren = 1; + root->parent = newNode; + SplitNode( root ); + root = newNode; + } + + newNode = AllocNode(); + newNode->key = key; + newNode->object = object; + + for ( node = root; node->firstChild != NULL; node = child ) { + + if ( key > node->key ) { + node->key = key; + } + + // find the first child with a key larger equal to the key of the new node + for( child = node->firstChild; child->next; child = child->next ) { + if ( key <= child->key ) { + break; + } + } + + if ( child->object ) { + + if ( key <= child->key ) { + // insert new node before child + if ( child->prev ) { + child->prev->next = newNode; + } else { + node->firstChild = newNode; + } + newNode->prev = child->prev; + newNode->next = child; + child->prev = newNode; + } else { + // insert new node after child + if ( child->next ) { + child->next->prev = newNode; + } else { + node->lastChild = newNode; + } + newNode->prev = child; + newNode->next = child->next; + child->next = newNode; + } + + newNode->parent = node; + node->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; + } + + // make sure the child has room to store another node + if ( child->numChildren >= maxChildrenPerNode ) { + SplitNode( child ); + if ( key <= child->prev->key ) { + child = child->prev; + } + } + } + + // we only end up here if the root node is empty + newNode->parent = root; + root->key = key; + root->firstChild = newNode; + root->lastChild = newNode; + root->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Remove( idBTreeNode *node ) { + idBTreeNode *parent; + + assert( node->object != NULL ); + + // unlink the node from it's parent + if ( node->prev ) { + node->prev->next = node->next; + } else { + node->parent->firstChild = node->next; + } + if ( node->next ) { + node->next->prev = node->prev; + } else { + node->parent->lastChild = node->prev; + } + node->parent->numChildren--; + + // make sure there are no parent nodes with a single child + for ( parent = node->parent; parent != root && parent->numChildren <= 1; parent = parent->parent ) { + + if ( parent->next ) { + parent = MergeNodes( parent, parent->next ); + } else if ( parent->prev ) { + parent = MergeNodes( parent->prev, parent ); + } + + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + + if ( parent->numChildren > maxChildrenPerNode ) { + SplitNode( parent ); + break; + } + } + for ( ; parent != NULL && parent->lastChild != NULL; parent = parent->parent ) { + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + } + + // free the node + FreeNode( node ); + + // remove the root node if it has a single internal node as child + if ( root->numChildren == 1 && root->firstChild->object == NULL ) { + idBTreeNode *oldRoot = root; + root->firstChild->parent = NULL; + root = root->firstChild; + FreeNode( oldRoot ); + } + +#ifdef BTREE_CHECK + CheckTree(); +#endif +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::Find( keyType key ) const { + idBTreeNode *node; + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key == key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindSmallestLargerEqual( keyType key ) const { + idBTreeNode *node; + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key >= key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindLargestSmallerEqual( keyType key ) const { + idBTreeNode *node; + + for ( node = root->lastChild; node != NULL; node = node->lastChild ) { + while( node->prev ) { + if ( node->key <= key ) { + break; + } + node = node->prev; + } + if ( node->object ) { + if ( node->key <= key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetRoot( void ) const { + return root; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE int idBTree::GetNodeCount( void ) const { + return nodeAllocator.GetAllocCount(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNext( idBTreeNode *node ) const { + if ( node->firstChild ) { + return node->firstChild; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + return node; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNextLeaf( idBTreeNode *node ) const { + if ( node->firstChild ) { + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->next; + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + return NULL; + } + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::AllocNode( void ) { + idBTreeNode *node = nodeAllocator.Alloc(); + node->key = 0; + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->numChildren = 0; + node->firstChild = NULL; + node->lastChild = NULL; + node->object = NULL; + return node; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::FreeNode( idBTreeNode *node ) { + nodeAllocator.Free( node ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::SplitNode( idBTreeNode *node ) { + int i; + idBTreeNode *child, *newNode; + + // allocate a new node + newNode = AllocNode(); + newNode->parent = node->parent; + + // divide the children over the two nodes + child = node->firstChild; + child->parent = newNode; + for ( i = 3; i < node->numChildren; i += 2 ) { + child = child->next; + child->parent = newNode; + } + + newNode->key = child->key; + newNode->numChildren = node->numChildren / 2; + newNode->firstChild = node->firstChild; + newNode->lastChild = child; + + node->numChildren -= newNode->numChildren; + node->firstChild = child->next; + + child->next->prev = NULL; + child->next = NULL; + + // add the new child to the parent before the split node + assert( node->parent->numChildren < maxChildrenPerNode ); + + if ( node->prev ) { + node->prev->next = newNode; + } else { + node->parent->firstChild = newNode; + } + newNode->prev = node->prev; + newNode->next = node; + node->prev = newNode; + + node->parent->numChildren++; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ) { + idBTreeNode *child; + + assert( node1->parent == node2->parent ); + assert( node1->next == node2 && node2->prev == node1 ); + assert( node1->object == NULL && node2->object == NULL ); + assert( node1->numChildren >= 1 && node2->numChildren >= 1 ); + + for ( child = node1->firstChild; child->next; child = child->next ) { + child->parent = node2; + } + child->parent = node2; + child->next = node2->firstChild; + node2->firstChild->prev = child; + node2->firstChild = node1->firstChild; + node2->numChildren += node1->numChildren; + + // unlink the first node from the parent + if ( node1->prev ) { + node1->prev->next = node2; + } else { + node1->parent->firstChild = node2; + } + node2->prev = node1->prev; + node2->parent->numChildren--; + + FreeNode( node1 ); + + return node2; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree_r( idBTreeNode *node, int &numNodes ) const { + int numChildren; + idBTreeNode *child; + + numNodes++; + + // the root node may have zero children and leaf nodes always have zero children, all other nodes should have at least 2 and at most maxChildrenPerNode children + assert( ( node == root ) || ( node->object != NULL && node->numChildren == 0 ) || ( node->numChildren >= 2 && node->numChildren <= maxChildrenPerNode ) ); + // the key of a node may never be larger than the key of it's last child + assert( ( node->lastChild == NULL ) || ( node->key <= node->lastChild->key ) ); + + numChildren = 0; + for ( child = node->firstChild; child; child = child->next ) { + numChildren++; + // make sure the children are properly linked + if ( child->prev == NULL ) { + assert( node->firstChild == child ); + } else { + assert( child->prev->next == child ); + } + if ( child->next == NULL ) { + assert( node->lastChild == child ); + } else { + assert( child->next->prev == child ); + } + // recurse down the tree + CheckTree_r( child, numNodes ); + } + // the number of children should equal the number of linked children + assert( numChildren == node->numChildren ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree( void ) const { + int numNodes = 0; + idBTreeNode *node, *lastNode; + + CheckTree_r( root, numNodes ); + + // the number of nodes in the tree should equal the number of allocated nodes + assert( numNodes == nodeAllocator.GetAllocCount() ); + + // all the leaf nodes should be ordered + lastNode = GetNextLeaf( GetRoot() ); + if ( lastNode ) { + for ( node = GetNextLeaf( lastNode ); node; lastNode = node, node = GetNextLeaf( node ) ) { + assert( lastNode->key <= node->key ); + } + } +} + +#endif /* !__BTREE_H__ */ diff --git a/source/idlib/containers/BinSearch.h b/source/idlib/containers/BinSearch.h new file mode 100644 index 0000000..2c5ce7d --- /dev/null +++ b/source/idlib/containers/BinSearch.h @@ -0,0 +1,111 @@ + +#ifndef __BINSEARCH_H__ +#define __BINSEARCH_H__ + +/* +=============================================================================== + + Binary Search templates + + The array elements have to be ordered in increasing order. + +=============================================================================== +*/ + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Less( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] < value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_LessEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] <= value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_Greater + + Finds the first array element which is greater than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Greater( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] > value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the first array element which is greater than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_GreaterEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] >= value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +#endif /* !__BINSEARCH_H__ */ diff --git a/source/idlib/containers/HashIndex.cpp b/source/idlib/containers/HashIndex.cpp new file mode 100644 index 0000000..b2f969c --- /dev/null +++ b/source/idlib/containers/HashIndex.cpp @@ -0,0 +1,210 @@ + +#include "../precompiled.h" +#pragma hdrstop + +int idHashIndex::INVALID_INDEX[1] = { -1 }; + +/* +================ +idHashIndex::Init +================ +*/ +void idHashIndex::Init( const int initialHashSize, const int initialIndexSize ) { + assert( idMath::IsPowerOfTwo( initialHashSize ) ); + + hashSize = initialHashSize; + hash = INVALID_INDEX; + indexSize = initialIndexSize; + indexChain = INVALID_INDEX; + granularity = DEFAULT_HASH_GRANULARITY; + hashMask = hashSize - 1; + lookupMask = 0; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + allocatorHeap = 0; +#endif +// RAVEN END +} + +/* +================ +idHashIndex::Allocate +================ +*/ +void idHashIndex::Allocate( const int newHashSize, const int newIndexSize ) { + assert( idMath::IsPowerOfTwo( newHashSize ) ); + + Free(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + hashSize = newHashSize; + hash = new int[hashSize]; + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + indexSize = newIndexSize; + indexChain = new int[indexSize]; + memset( indexChain, 0xff, indexSize * sizeof( indexChain[0] ) ); + hashMask = hashSize - 1; + lookupMask = -1; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashIndex::Free +================ +*/ +void idHashIndex::Free( void ) { + if ( hash != INVALID_INDEX ) { + delete[] hash; + hash = INVALID_INDEX; + } + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + indexChain = INVALID_INDEX; + } + lookupMask = 0; +} + +/* +================ +idHashIndex::ResizeIndex +================ +*/ +void idHashIndex::ResizeIndex( const int newIndexSize ) { + int *oldIndexChain, mod, newSize; + + if ( newIndexSize <= indexSize ) { + return; + } + + mod = newIndexSize % granularity; + if ( !mod ) { + newSize = newIndexSize; + } else { + newSize = newIndexSize + granularity - mod; + } + + if ( indexChain == INVALID_INDEX ) { + indexSize = newSize; + return; + } + + oldIndexChain = indexChain; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + indexChain = new int[newSize]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memcpy( indexChain, oldIndexChain, indexSize * sizeof(int) ); + memset( indexChain + indexSize, 0xff, (newSize - indexSize) * sizeof(int) ); + delete[] oldIndexChain; + indexSize = newSize; +} + +/* +================ +idHashIndex::GetSpread +================ +*/ +int idHashIndex::GetSpread( void ) const { + int i, index, totalItems, *numHashItems, average, error, e; + + if ( hash == INVALID_INDEX ) { + return 100; + } + + totalItems = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + numHashItems = new int[hashSize]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + for ( i = 0; i < hashSize; i++ ) { + numHashItems[i] = 0; + for ( index = hash[i]; index >= 0; index = indexChain[index] ) { + numHashItems[i]++; + } + totalItems += numHashItems[i]; + } + // if no items in hash + if ( totalItems <= 1 ) { + delete[] numHashItems; + return 100; + } + average = totalItems / hashSize; + error = 0; + for ( i = 0; i < hashSize; i++ ) { + e = abs( numHashItems[i] - average ); + if ( e > 1 ) { + error += e - 1; + } + } + delete[] numHashItems; + return 100 - (error * 100 / totalItems); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +void idHashIndex::SetAllocatorHeap ( rvHeap* heap ) +{ + assert(heap); + allocatorHeap = heap; +} +#endif +// RAVEN END diff --git a/source/idlib/containers/HashIndex.h b/source/idlib/containers/HashIndex.h new file mode 100644 index 0000000..003fd2a --- /dev/null +++ b/source/idlib/containers/HashIndex.h @@ -0,0 +1,416 @@ + +#ifndef __HASHINDEX_H__ +#define __HASHINDEX_H__ + +/* +=============================================================================== + + Fast hash table for indexes and arrays. + Does not allocate memory until the first key/index pair is added. + +=============================================================================== +*/ + +#define DEFAULT_HASH_SIZE 1024 +#define DEFAULT_HASH_GRANULARITY 1024 + +class idHashIndex { +public: + idHashIndex( void ); + idHashIndex( const int initialHashSize, const int initialIndexSize ); + ~idHashIndex( void ); + + // returns total size of allocated memory + size_t Allocated( void ) const; + // returns total size of allocated memory including size of hash index type + size_t Size( void ) const; + + idHashIndex & operator=( const idHashIndex &other ); + // add an index to the hash, assumes the index has not yet been added to the hash + void Add( const int key, const int index ); + // remove an index from the hash + void Remove( const int key, const int index ); + // get the first index from the hash, returns -1 if empty hash entry + int First( const int key ) const; + // get the next index from the hash, returns -1 if at the end of the hash chain + int Next( const int index ) const; + // insert an entry into the index and add it to the hash, increasing all indexes >= index + void InsertIndex( const int key, const int index ); + // remove an entry from the index and remove it from the hash, decreasing all indexes >= index + void RemoveIndex( const int key, const int index ); + // clear the hash + void Clear( void ); + // clear and resize + void Clear( const int newHashSize, const int newIndexSize ); + // free allocated memory + void Free( void ); + // get size of hash table + int GetHashSize( void ) const; + // get size of the index + int GetIndexSize( void ) const; + // set granularity + void SetGranularity( const int newGranularity ); + // force resizing the index, current hash table stays intact + void ResizeIndex( const int newIndexSize ); + // returns number in the range [0-100] representing the spread over the hash table + int GetSpread( void ) const; + // returns a key for a string + int GenerateKey( const char *string, bool caseSensitive = true ) const; + // returns a key for a vector + int GenerateKey( const idVec3 &v ) const; + // returns a key for two integers + int GenerateKey( const int n1, const int n2 ) const; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ); // set the heap used for all allocations +#endif +// RAVEN END + +private: + int hashSize; + int * hash; + int indexSize; + int * indexChain; + int granularity; + int hashMask; + int lookupMask; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rvHeap* allocatorHeap; +#endif +// RAVEN END + + static int INVALID_INDEX[1]; + + void Init( const int initialHashSize, const int initialIndexSize ); + void Allocate( const int newHashSize, const int newIndexSize ); +}; + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex( void ) { + Init( DEFAULT_HASH_SIZE, DEFAULT_HASH_SIZE ); +} + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex( const int initialHashSize, const int initialIndexSize ) { + Init( initialHashSize, initialIndexSize ); +} + +/* +================ +idHashIndex::~idHashIndex +================ +*/ +ID_INLINE idHashIndex::~idHashIndex( void ) { + Free(); +} + +/* +================ +idHashIndex::Allocated +================ +*/ +ID_INLINE size_t idHashIndex::Allocated( void ) const { + return hashSize * sizeof( int ) + indexSize * sizeof( int ); +} + +/* +================ +idHashIndex::Size +================ +*/ +ID_INLINE size_t idHashIndex::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +================ +idHashIndex::operator= +================ +*/ +ID_INLINE idHashIndex &idHashIndex::operator=( const idHashIndex &other ) { + granularity = other.granularity; + hashMask = other.hashMask; + lookupMask = other.lookupMask; + + if ( other.lookupMask == 0 ) { + hashSize = other.hashSize; + indexSize = other.indexSize; + Free(); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + if ( other.hashSize != hashSize || hash == INVALID_INDEX ) { + if ( hash != INVALID_INDEX ) { + delete[] hash; + } + hashSize = other.hashSize; + hash = new int[hashSize]; + } + if ( other.indexSize != indexSize || indexChain == INVALID_INDEX ) { + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + } + indexSize = other.indexSize; + indexChain = new int[indexSize]; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memcpy( hash, other.hash, hashSize * sizeof( hash[0] ) ); + memcpy( indexChain, other.indexChain, indexSize * sizeof( indexChain[0] ) ); + } + + return *this; +} + +/* +================ +idHashIndex::Add +================ +*/ +ID_INLINE void idHashIndex::Add( const int key, const int index ) { + int h; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + + assert( index >= 0 ); + if ( hash == INVALID_INDEX ) { + Allocate( hashSize, index >= indexSize ? index + 1 : indexSize ); + } + else if ( index >= indexSize ) { + ResizeIndex( index + 1 ); + } + h = key & hashMask; + indexChain[index] = hash[h]; + hash[h] = index; +} + +/* +================ +idHashIndex::Remove +================ +*/ +ID_INLINE void idHashIndex::Remove( const int key, const int index ) { + int k = key & hashMask; + + if ( hash == INVALID_INDEX ) { + return; + } + if ( hash[k] == index ) { + hash[k] = indexChain[index]; + } + else { + for ( int i = hash[k]; i != -1; i = indexChain[i] ) { + if ( indexChain[i] == index ) { + indexChain[i] = indexChain[index]; + break; + } + } + } + indexChain[index] = -1; +} + +/* +================ +idHashIndex::First +================ +*/ +ID_INLINE int idHashIndex::First( const int key ) const { + return hash[key & hashMask & lookupMask]; +} + +/* +================ +idHashIndex::Next +================ +*/ +ID_INLINE int idHashIndex::Next( const int index ) const { + assert( index >= 0 && index < indexSize ); + return indexChain[index & lookupMask]; +} + +/* +================ +idHashIndex::InsertIndex +================ +*/ +ID_INLINE void idHashIndex::InsertIndex( const int key, const int index ) { + int i, max; + + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + hash[i]++; + if ( hash[i] > max ) { + max = hash[i]; + } + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + indexChain[i]++; + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + } + } + if ( max >= indexSize ) { + ResizeIndex( max + 1 ); + } + for ( i = max; i > index; i-- ) { + indexChain[i] = indexChain[i-1]; + } + indexChain[index] = -1; + } + Add( key, index ); +} + +/* +================ +idHashIndex::RemoveIndex +================ +*/ +ID_INLINE void idHashIndex::RemoveIndex( const int key, const int index ) { + int i, max; + + Remove( key, index ); + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + if ( hash[i] > max ) { + max = hash[i]; + } + hash[i]--; + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + indexChain[i]--; + } + } + for ( i = index; i < max; i++ ) { + indexChain[i] = indexChain[i+1]; + } + indexChain[max] = -1; + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear( void ) { + // only clear the hash table because clearing the indexChain is not really needed + if ( hash != INVALID_INDEX ) { + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear( const int newHashSize, const int newIndexSize ) { + Free(); + hashSize = newHashSize; + indexSize = newIndexSize; +} + +/* +================ +idHashIndex::GetHashSize +================ +*/ +ID_INLINE int idHashIndex::GetHashSize( void ) const { + return hashSize; +} + +/* +================ +idHashIndex::GetIndexSize +================ +*/ +ID_INLINE int idHashIndex::GetIndexSize( void ) const { + return indexSize; +} + +/* +================ +idHashIndex::SetGranularity +================ +*/ +ID_INLINE void idHashIndex::SetGranularity( const int newGranularity ) { + assert( newGranularity > 0 ); + granularity = newGranularity; +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const char *string, bool caseSensitive ) const { + if ( caseSensitive ) { + return ( idStr::Hash( string ) & hashMask ); + } else { + return ( idStr::IHash( string ) & hashMask ); + } +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const idVec3 &v ) const { + return ( (((int) v[0]) + ((int) v[1]) + ((int) v[2])) & hashMask ); +// return ( ( idMath::FtoiFast( v[0] ) + idMath::FtoiFast( v[1] ) + idMath::FtoiFast( v[2] ) ) & hashMask ); +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const int n1, const int n2 ) const { + return ( ( n1 + n2 ) & hashMask ); +} + +#endif /* !__HASHINDEX_H__ */ diff --git a/source/idlib/containers/HashTable.h b/source/idlib/containers/HashTable.h new file mode 100644 index 0000000..04807c2 --- /dev/null +++ b/source/idlib/containers/HashTable.h @@ -0,0 +1,421 @@ + +#ifndef __HASHTABLE_H__ +#define __HASHTABLE_H__ + +/* +=============================================================================== + + General hash table. Slower than idHashIndex but it can also be used for + linked lists and other data structures than just indexes or arrays. + +=============================================================================== +*/ + +template< class Type > +class idHashTable { +public: + idHashTable( int newtablesize = 256 ); + idHashTable( const idHashTable &map ); + ~idHashTable( void ); + + // returns total size of allocated memory + size_t Allocated( void ) const; + // returns total size of allocated memory including size of hash table type + size_t Size( void ) const; + + void Set( const char *key, Type &value ); + bool Get( const char *key, Type **value = NULL ) const; + bool Remove( const char *key ); + + void Clear( void ); + void DeleteContents( void ); + + // the entire contents can be itterated over, but note that the + // exact index for a given element may change when new elements are added + int Num( void ) const; + Type * GetIndex( int index ) const; + + int GetSpread( void ) const; + +private: + struct hashnode_s { + idStr key; + Type value; + hashnode_s *next; + + hashnode_s( const idStr &k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + hashnode_s( const char *k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + }; + + hashnode_s ** heads; + + int tablesize; + int numentries; + int tablesizemask; + + int GetHash( const char *key ) const; +}; + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( int newtablesize ) { + + assert( idMath::IsPowerOfTwo( newtablesize ) ); + + tablesize = newtablesize; + assert( tablesize > 0 ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + heads = new hashnode_s *[ tablesize ]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memset( heads, 0, sizeof( *heads ) * tablesize ); + + numentries = 0; + + tablesizemask = tablesize - 1; +} + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( const idHashTable &map ) { + int i; + hashnode_s *node; + hashnode_s **prev; + + assert( map.tablesize > 0 ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + tablesize = map.tablesize; + heads = new hashnode_s *[ tablesize ]; + numentries = map.numentries; + tablesizemask = map.tablesizemask; + + for( i = 0; i < tablesize; i++ ) { + if ( !map.heads[ i ] ) { + heads[ i ] = NULL; + continue; + } + + prev = &heads[ i ]; + for( node = map.heads[ i ]; node != NULL; node = node->next ) { + *prev = new hashnode_s( node->key, node->value, NULL ); + prev = &( *prev )->next; + } + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashTable::~idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::~idHashTable( void ) { + Clear(); + delete[] heads; +} + +/* +================ +idHashTable::Allocated +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Allocated( void ) const { + return sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::Size +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Size( void ) const { + return sizeof( idHashTable ) + sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::GetHash +================ +*/ +template< class Type > +ID_INLINE int idHashTable::GetHash( const char *key ) const { + return ( idStr::Hash( key ) & tablesizemask ); +} + +/* +================ +idHashTable::Set +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Set( const char *key, Type &value ) { + hashnode_s *node, **nextPtr; + int hash, s; + + hash = GetHash( key ); + for( nextPtr = &(heads[hash]), node = *nextPtr; node != NULL; nextPtr = &(node->next), node = *nextPtr ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + node->value = value; + return; + } + if ( s > 0 ) { + break; + } + } + + numentries++; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + *nextPtr = new hashnode_s( key, value, heads[ hash ] ); + (*nextPtr)->next = node; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashTable::Get +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Get( const char *key, Type **value ) const { + hashnode_s *node; + int hash, s; + + hash = GetHash( key ); + for( node = heads[ hash ]; node != NULL; node = node->next ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + if ( value ) { + *value = &node->value; + } + return true; + } + if ( s > 0 ) { + break; + } + } + + if ( value ) { + *value = NULL; + } + + return false; +} + +/* +================ +idHashTable::GetIndex + +the entire contents can be itterated over, but note that the +exact index for a given element may change when new elements are added +================ +*/ +template< class Type > +ID_INLINE Type *idHashTable::GetIndex( int index ) const { + hashnode_s *node; + int count; + int i; + + if ( ( index < 0 ) || ( index > numentries ) ) { + assert( 0 ); + return NULL; + } + + count = 0; + for( i = 0; i < tablesize; i++ ) { + for( node = heads[ i ]; node != NULL; node = node->next ) { + if ( count == index ) { + return &node->value; + } + count++; + } + } + + return NULL; +} + +/* +================ +idHashTable::Remove +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Remove( const char *key ) { + hashnode_s **head; + hashnode_s *node; + hashnode_s *prev; + int hash; + + hash = GetHash( key ); + head = &heads[ hash ]; + if ( *head ) { + for( prev = NULL, node = *head; node != NULL; prev = node, node = node->next ) { + if ( node->key == key ) { + if ( prev ) { + prev->next = node->next; + } else { + *head = node->next; + } + + delete node; + numentries--; + return true; + } + } + } + + return false; +} + +/* +================ +idHashTable::Clear +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Clear( void ) { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::DeleteContents +================ +*/ +template< class Type > +ID_INLINE void idHashTable::DeleteContents( void ) { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node->value; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::Num +================ +*/ +template< class Type > +ID_INLINE int idHashTable::Num( void ) const { + return numentries; +} + +#if !defined(__GNUC__) || __GNUC__ < 4 +/* +================ +idHashTable::GetSpread +================ +*/ +template< class Type > +int idHashTable::GetSpread( void ) const { + int i, average, error, e; + hashnode_s *node; + + // if no items in hash + if ( !numentries ) { + return 100; + } + average = numentries / tablesize; + error = 0; + for ( i = 0; i < tablesize; i++ ) { + numItems = 0; + for( node = heads[ i ]; node != NULL; node = node->next ) { + numItems++; + } + e = abs( numItems - average ); + if ( e > 1 ) { + error += e - 1; + } + } + return 100 - (error * 100 / numentries); +} +#endif + +#endif /* !__HASHTABLE_H__ */ diff --git a/source/idlib/containers/Hierarchy.h b/source/idlib/containers/Hierarchy.h new file mode 100644 index 0000000..6524c2c --- /dev/null +++ b/source/idlib/containers/Hierarchy.h @@ -0,0 +1,335 @@ + +#ifndef __HIERARCHY_H__ +#define __HIERARCHY_H__ + +/* +============================================================================== + + idHierarchy + +============================================================================== +*/ + +template< class type > +class idHierarchy { +public: + + idHierarchy(); + ~idHierarchy(); + + void SetOwner( type *object ); + type * Owner( void ) const; + void ParentTo( idHierarchy &node ); + void MakeSiblingAfter( idHierarchy &node ); + bool ParentedBy( const idHierarchy &node ) const; + void RemoveFromParent( void ); + void RemoveFromHierarchy( void ); + + type * GetParent( void ) const; // parent of this node + type * GetChild( void ) const; // first child of this node + type * GetSibling( void ) const; // next node with the same parent + type * GetPriorSibling( void ) const; // previous node with the same parent + type * GetNext( void ) const; // goes through all nodes of the hierarchy + type * GetNextLeaf( void ) const; // goes through all leaf nodes of the hierarchy + +private: + idHierarchy * parent; + idHierarchy * sibling; + idHierarchy * child; + type * owner; + + idHierarchy *GetPriorSiblingNode( void ) const; // previous node with the same parent +}; + +/* +================ +idHierarchy::idHierarchy +================ +*/ +template< class type > +idHierarchy::idHierarchy() { + owner = NULL; + parent = NULL; + sibling = NULL; + child = NULL; +} + +/* +================ +idHierarchy::~idHierarchy +================ +*/ +template< class type > +idHierarchy::~idHierarchy() { + RemoveFromHierarchy(); +} + +/* +================ +idHierarchy::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idHierarchy::Owner( void ) const { + return owner; +} + +/* +================ +idHierarchy::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idHierarchy::SetOwner( type *object ) { + owner = object; +} + +/* +================ +idHierarchy::ParentedBy +================ +*/ +template< class type > +bool idHierarchy::ParentedBy( const idHierarchy &node ) const { + if ( parent == &node ) { + return true; + } else if ( parent ) { + return parent->ParentedBy( node ); + } + return false; +} + +/* +================ +idHierarchy::ParentTo + +Makes the given node the parent. +================ +*/ +template< class type > +void idHierarchy::ParentTo( idHierarchy &node ) { + RemoveFromParent(); + + parent = &node; + sibling = node.child; + node.child = this; +} + +/* +================ +idHierarchy::MakeSiblingAfter + +Makes the given node a sibling after the passed in node. +================ +*/ +template< class type > +void idHierarchy::MakeSiblingAfter( idHierarchy &node ) { + RemoveFromParent(); + parent = node.parent; + sibling = node.sibling; + node.sibling = this; +} + +/* +================ +idHierarchy::RemoveFromParent +================ +*/ +template< class type > +void idHierarchy::RemoveFromParent( void ) { + idHierarchy *prev; + + if ( parent ) { + prev = GetPriorSiblingNode(); + if ( prev ) { + prev->sibling = sibling; + } else { + parent->child = sibling; + } + } + + parent = NULL; + sibling = NULL; +} + +/* +================ +idHierarchy::RemoveFromHierarchy + +Removes the node from the hierarchy and adds it's children to the parent. +================ +*/ +template< class type > +void idHierarchy::RemoveFromHierarchy( void ) { + idHierarchy *parentNode; + idHierarchy *node; + + parentNode = parent; + RemoveFromParent(); + + if ( parentNode ) { + while( child ) { + node = child; + node->RemoveFromParent(); + node->ParentTo( *parentNode ); + } + } else { + while( child ) { + child->RemoveFromParent(); + } + } +} + +/* +================ +idHierarchy::GetParent +================ +*/ +template< class type > +type *idHierarchy::GetParent( void ) const { + if ( parent ) { + return parent->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetChild +================ +*/ +template< class type > +type *idHierarchy::GetChild( void ) const { + if ( child ) { + return child->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetSibling +================ +*/ +template< class type > +type *idHierarchy::GetSibling( void ) const { + if ( sibling ) { + return sibling->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetPriorSiblingNode + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +idHierarchy *idHierarchy::GetPriorSiblingNode( void ) const { + if ( !parent || ( parent->child == this ) ) { + return NULL; + } + + idHierarchy *prev; + idHierarchy *node; + + node = parent->child; + prev = NULL; + while( ( node != this ) && ( node != NULL ) ) { + prev = node; + node = node->sibling; + } + + if ( node != this ) { + idLib::Error( "idHierarchy::GetPriorSibling: could not find node in parent's list of children" ); + } + + return prev; +} + +/* +================ +idHierarchy::GetPriorSibling + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +type *idHierarchy::GetPriorSibling( void ) const { + idHierarchy *prior; + + prior = GetPriorSiblingNode(); + if ( prior ) { + return prior->owner; + } + + return NULL; +} + +/* +================ +idHierarchy::GetNext + +Goes through all nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNext( void ) const { + const idHierarchy *node; + + if ( child ) { + return child->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + return node->sibling->owner; + } else { + return NULL; + } + } +} + +/* +================ +idHierarchy::GetNextLeaf + +Goes through all leaf nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNextLeaf( void ) const { + const idHierarchy *node; + + if ( child ) { + node = child; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->sibling; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + return NULL; + } + } +} + +#endif /* !__HIERARCHY_H__ */ diff --git a/source/idlib/containers/LinkList.h b/source/idlib/containers/LinkList.h new file mode 100644 index 0000000..59b145a --- /dev/null +++ b/source/idlib/containers/LinkList.h @@ -0,0 +1,334 @@ + +#ifndef __LINKLIST_H__ +#define __LINKLIST_H__ + +/* +============================================================================== + +idLinkList + +Circular linked list template + +============================================================================== +*/ + +template< class type > +class idLinkList { +public: + idLinkList(); + ~idLinkList(); + + bool IsListEmpty( void ) const; + bool InList( void ) const; + int Num( void ) const; + void Clear( void ); + + void InsertBefore( idLinkList &node ); + void InsertAfter( idLinkList &node ); + void AddToEnd( idLinkList &node ); + void AddToFront( idLinkList &node ); + + void Remove( void ); + + type * Next( void ) const; + type * Prev( void ) const; + + type * Owner( void ) const; + void SetOwner( type *object ); + + idLinkList * ListHead( void ) const; + idLinkList * NextNode( void ) const; + idLinkList * PrevNode( void ) const; + +private: + idLinkList * head; + idLinkList * next; + idLinkList * prev; + type * owner; +}; + +/* +================ +idLinkList::idLinkList + +Node is initialized to be the head of an empty list +================ +*/ +template< class type > +idLinkList::idLinkList() { + owner = NULL; + head = this; + next = this; + prev = this; +} + +/* +================ +idLinkList::~idLinkList + +Removes the node from the list, or if it's the head of a list, removes +all the nodes from the list. +================ +*/ +template< class type > +idLinkList::~idLinkList() { + Clear(); +} + +/* +================ +idLinkList::IsListEmpty + +Returns true if the list is empty. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE bool idLinkList::IsListEmpty( void ) const { +// RAVEN END + return head->next == head; +} + +/* +================ +idLinkList::InList + +Returns true if the node is in a list. If called on the head of a list, will always return false. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE bool idLinkList::InList( void ) const { +// RAVEN END + return head != this; +} + +/* +================ +idLinkList::Num + +Returns the number of nodes in the list. +================ +*/ +template< class type > +int idLinkList::Num( void ) const { + idLinkList *node; + int num; + + num = 0; + for( node = head->next; node != head; node = node->next ) { + num++; + } + + return num; +} + +/* +================ +idLinkList::Clear + +If node is the head of the list, clears the list. Otherwise it just removes the node from the list. +================ +*/ +template< class type > +void idLinkList::Clear( void ) { + if ( head == this ) { + while( next != this ) { + next->Remove(); + } + } else { + Remove(); + } +} + +/* +================ +idLinkList::Remove + +Removes node from list +================ +*/ +template< class type > +void idLinkList::Remove( void ) { + prev->next = next; + next->prev = prev; + + next = this; + prev = this; + head = this; +} + +/* +================ +idLinkList::InsertBefore + +Places the node before the existing node in the list. If the existing node is the head, +then the new node is placed at the end of the list. +================ +*/ +template< class type > +void idLinkList::InsertBefore( idLinkList &node ) { + Remove(); + + next = &node; + prev = node.prev; + node.prev = this; + prev->next = this; + head = node.head; +} + +/* +================ +idLinkList::InsertAfter + +Places the node after the existing node in the list. If the existing node is the head, +then the new node is placed at the beginning of the list. +================ +*/ +template< class type > +void idLinkList::InsertAfter( idLinkList &node ) { + Remove(); + + prev = &node; + next = node.next; + node.next = this; + next->prev = this; + head = node.head; +} + +/* +================ +idLinkList::AddToEnd + +Adds node at the end of the list +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE void idLinkList::AddToEnd( idLinkList &node ) { +// RAVEN END + InsertBefore( *node.head ); +} + +/* +================ +idLinkList::AddToFront + +Adds node at the beginning of the list +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE void idLinkList::AddToFront( idLinkList &node ) { +// RAVEN END + InsertAfter( *node.head ); +} + +/* +================ +idLinkList::ListHead + +Returns the head of the list. If the node isn't in a list, it returns +a pointer to itself. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE idLinkList *idLinkList::ListHead( void ) const { +// RAVEN END + return head; +} + +/* +================ +idLinkList::Next + +Returns the next object in the list, or NULL if at the end. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE type *idLinkList::Next( void ) const { +// RAVEN END + if ( !next || ( next == head ) ) { + return NULL; + } + return next->owner; +} + +/* +================ +idLinkList::Prev + +Returns the previous object in the list, or NULL if at the beginning. +================ +*/ +template< class type > +type *idLinkList::Prev( void ) const { + if ( !prev || ( prev == head ) ) { + return NULL; + } + return prev->owner; +} + +/* +================ +idLinkList::NextNode + +Returns the next node in the list, or NULL if at the end. +================ +*/ +template< class type > +idLinkList *idLinkList::NextNode( void ) const { + if ( next == head ) { + return NULL; + } + return next; +} + +/* +================ +idLinkList::PrevNode + +Returns the previous node in the list, or NULL if at the beginning. +================ +*/ +template< class type > +idLinkList *idLinkList::PrevNode( void ) const { + if ( prev == head ) { + return NULL; + } + return prev; +} + +/* +================ +idLinkList::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idLinkList::Owner( void ) const { + return owner; +} + +/* +================ +idLinkList::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idLinkList::SetOwner( type *object ) { + owner = object; +} + +#endif /* !__LINKLIST_H__ */ diff --git a/source/idlib/containers/List.h b/source/idlib/containers/List.h new file mode 100644 index 0000000..591b7f4 --- /dev/null +++ b/source/idlib/containers/List.h @@ -0,0 +1,1232 @@ + +#ifndef __LIST_H__ +#define __LIST_H__ + +/* +=============================================================================== + + List template + Does not allocate memory until the first item is added. + +=============================================================================== +*/ + + +/* +================ +idListSortCompare +================ +*/ +#ifdef __INTEL_COMPILER +// the intel compiler doesn't do the right thing here +template< class type > +ID_INLINE int idListSortCompare( const type *a, const type *b ) { + assert( 0 ); + return 0; +} +#else +template< class type > +ID_INLINE int idListSortCompare( const type *a, const type *b ) { + return *a - *b; +} +#endif + +/* +================ +idListNewElement +================ +*/ +template< class type > +ID_INLINE type *idListNewElement( void ) { + return new type; +} + +/* +================ +idSwap +================ +*/ +template< class type > +ID_INLINE void idSwap( type &a, type &b ) { + type c = a; + a = b; + b = c; +} + +template< class type > +class idList { +public: + + typedef int cmp_t( const type *, const type * ); + typedef int filter_t( const type * ); + + typedef type new_t( void ); + + idList( int newgranularity = 16 ); + idList( const idList &other ); + ~idList( void ); + + void Clear( void ); // clear the list + int Num( void ) const; // returns number of elements in list + int NumAllocated( void ) const; // returns number of elements allocated for + void SetGranularity( int newgranularity ); // set new granularity + int GetGranularity( void ) const; // get the current granularity +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ); // set the heap used for all allocations +#endif +// RAVEN END + + size_t Allocated( void ) const; // returns total size of allocated memory + size_t Size( void ) const; // returns total size of allocated memory including size of list type + size_t MemoryUsed( void ) const; // returns size of the used elements in the list + + idList & operator=( const idList &other ); + const type & operator[]( int index ) const; + type & operator[]( int index ); + + void Condense( void ); // resizes list to exactly the number of elements it contains + void Resize( int newsize ); // resizes list to the given number of elements + void Resize( int newsize, int newgranularity ); // resizes list and sets new granularity + void SetNum( int newnum, bool resize = true ); // set number of elements in list and resize to exactly this number if necessary + void AssureSize( int newSize); // assure list has given number of elements, but leave them uninitialized + void AssureSize( int newSize, const type &initValue ); // assure list has given number of elements and initialize any new elements + void AssureSizeAlloc( int newSize, new_t *allocator ); // assure the pointer list has the given number of elements and allocate any new elements + + type * Ptr( void ); // returns a pointer to the list + const type * Ptr( void ) const; // returns a pointer to the list + type & Alloc( void ); // returns reference to a new data element at the end of the list + int Append( const type & obj ); // append element + int Append( const idList &other ); // append list + int AddUnique( const type & obj ); // add unique element + int Insert( const type & obj, int index = 0 ); // insert the element at the given index + int FindIndex( const type & obj ) const; // find the index for the given element + type * Find( type const & obj ) const; // find pointer to the given element + int FindNull( void ) const; // find the index for the first NULL pointer in the list + int IndexOf( const type *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + bool Remove( const type & obj ); // remove the element + void Sort( cmp_t *compare = ( cmp_t * )&idListSortCompare ); + void SortSubSection( int startIndex, int endIndex, cmp_t *compare = ( cmp_t * )&idListSortCompare ); + void Swap( idList &other ); // swap the contents of the lists + void DeleteContents( bool clear ); // delete the contents of the list + +//RAVENBEGIN +// cdr : added Heap & Stack & Sort functionality + int FindBinary ( const type & key, cmp_t *compare = ( cmp_t * )&idListSortCompare ) const; + void StackAdd( const type & obj ); // add to the stack + void StackPop( void ); // remove from the stack + type & StackTop( void ); // get the top element of the stack + void HeapAdd( const type & obj ); // add to the heap, and resort + void HeapPop( void ); // pop off the top of the heap & resort +// abahr: + int TypeSize() const { return sizeof(type); } + void RemoveNull(); + // gcc 4.0: see ListGame.h + void RemoveContents( bool clear ); +// ddynerman: range remove + bool RemoveRange( int low, int high ); + void RemoveDuplicates( void ); +//RAVEN END + +private: + int num; + int size; + int granularity; + type * list; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rvHeap* allocatorHeap; +#endif +// RAVEN END +}; + +/* +================ +idList::idList( int ) +================ +*/ +template< class type > +ID_INLINE idList::idList( int newgranularity ) { + assert( newgranularity > 0 ); + + list = NULL; + granularity = newgranularity; + Clear(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + allocatorHeap = 0; +#endif +// RAVEN END +} + +/* +================ +idList::idList( const idList &other ) +================ +*/ +template< class type > +ID_INLINE idList::idList( const idList &other ) { + list = NULL; + *this = other; +} + +/* +================ +idList::~idList +================ +*/ +template< class type > +ID_INLINE idList::~idList( void ) { + Clear(); +} + +/* +================ +idList::Clear + +Frees up the memory allocated by the list. Assumes that type automatically handles freeing up memory. +================ +*/ +template< class type > +ID_INLINE void idList::Clear( void ) { + if ( list ) { + delete[] list; + } + + list = NULL; + num = 0; + size = 0; +} + +/* +================ +idList::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template< class type > +ID_INLINE void idList::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < num; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, size * sizeof( type ) ); + } +} + +/* +================ +idList::Allocated + +return total memory allocated for the list in bytes, but doesn't take into account additional memory allocated by type +================ +*/ +template< class type > +ID_INLINE size_t idList::Allocated( void ) const { + return size * sizeof( type ); +} + +/* +================ +idList::Size + +return total size of list in bytes, but doesn't take into account additional memory allocated by type +================ +*/ +template< class type > +ID_INLINE size_t idList::Size( void ) const { + return sizeof( idList ) + Allocated(); +} + +/* +================ +idList::MemoryUsed +================ +*/ +template< class type > +ID_INLINE size_t idList::MemoryUsed( void ) const { + return num * sizeof( *list ); +} + +/* +================ +idList::Num + +Returns the number of elements currently contained in the list. +Note that this is NOT an indication of the memory allocated. +================ +*/ +template< class type > +ID_INLINE int idList::Num( void ) const { + return num; +} + +/* +================ +idList::NumAllocated + +Returns the number of elements currently allocated for. +================ +*/ +template< class type > +ID_INLINE int idList::NumAllocated( void ) const { + return size; +} + +/* +================ +idList::SetNum + +Resize to the exact size specified irregardless of granularity +================ +*/ +template< class type > +ID_INLINE void idList::SetNum( int newnum, bool resize ) { + assert( newnum >= 0 ); + if ( resize || newnum > size ) { + Resize( newnum ); + } + num = newnum; +} + +/* +================ +idList::SetGranularity + +Sets the base size of the array and resizes the array to match. +================ +*/ +template< class type > +ID_INLINE void idList::SetGranularity( int newgranularity ) { + int newsize; + + assert( newgranularity > 0 ); + granularity = newgranularity; + + if ( list ) { + // resize it to the closest level of granularity + newsize = num + granularity - 1; + newsize -= newsize % granularity; + if ( newsize != size ) { + Resize( newsize ); + } + } +} + +/* +================ +idList::GetGranularity + +Get the current granularity. +================ +*/ +template< class type > +ID_INLINE int idList::GetGranularity( void ) const { + return granularity; +} + +/* +================ +idList::Condense + +Resizes the array to exactly the number of elements it contains or frees up memory if empty. +================ +*/ +template< class type > +ID_INLINE void idList::Condense( void ) { + if ( list ) { + if ( num ) { + Resize( num ); + } else { + Clear(); + } + } +} + +/* +================ +idList::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< class type > +ID_INLINE void idList::Resize( int newsize ) { + type *temp; + int i; + + assert( newsize >= 0 ); + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + if ( newsize == size ) { + // not changing the size, so just exit + return; + } + + temp = list; + size = newsize; + if ( size < num ) { + num = size; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=false; + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + ok=true; + } + else + { + ok=rvPushHeapContainingMemory(this); + } +#endif +// RAVEN END + + // copy the old list into our new one + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = temp[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + // delete the old list if it exists + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< class type > +ID_INLINE void idList::Resize( int newsize, int newgranularity ) { + type *temp; + int i; + + assert( newsize >= 0 ); + + assert( newgranularity > 0 ); + granularity = newgranularity; + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + temp = list; + size = newsize; + if ( size < num ) { + num = size; + } + + // copy the old list into our new one + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = temp[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + // delete the old list if it exists + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::AssureSize + +Makes sure the list has at least the given number of elements. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSize( int newSize ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + Resize( newSize ); + } + + num = newNum; +} + +/* +================ +idList::AssureSize + +Makes sure the list has at least the given number of elements and initialize any elements not yet initialized. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSize( int newSize, const type &initValue ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = initValue; + } + } + + num = newNum; +} + +/* +================ +idList::AssureSizeAlloc + +Makes sure the list has at least the given number of elements and allocates any elements using the allocator. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSizeAlloc( int newSize, new_t *allocator ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = (*allocator)(); + } + } + + num = newNum; +} + +/* +================ +idList::operator= + +Copies the contents and size attributes of another list. +================ +*/ +template< class type > +ID_INLINE idList &idList::operator=( const idList &other ) { + int i; + + Clear(); + + num = other.num; + size = other.size; + granularity = other.granularity; + + if ( size ) { + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = other.list[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + } + return *this; +} + +/* +================ +idList::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< class type > +ID_INLINE const type &idList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< class type > +ID_INLINE type &idList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< class type > +ID_INLINE type *idList::Ptr( void ) { + return list; +} + +/* +================ +idList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< class type > +const ID_INLINE type *idList::Ptr( void ) const { + return list; +} + +/* +================ +idList::Alloc + +Returns a reference to a new data element at the end of the list. +================ +*/ +template< class type > +ID_INLINE type &idList::Alloc( void ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + Resize( size + granularity ); + } + + return list[ num++ ]; +} + +/* +================ +idList::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< class type > +ID_INLINE int idList::Append( type const & obj ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + list[ num ] = obj; + num++; + + return num - 1; +} + + +/* +================ +idList::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< class type > +ID_INLINE int idList::Insert( type const & obj, int index ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + if ( index < 0 ) { + index = 0; + } + else if ( index > num ) { + index = num; + } + for ( int i = num; i > index; --i ) { + list[i] = list[i-1]; + } + num++; + list[index] = obj; + return index; +} + +/* +================ +idList::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template< class type > +ID_INLINE int idList::Append( const idList &other ) { + if ( !list ) { + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + Resize( granularity ); + } + + int n = other.Num(); + for (int i = 0; i < n; i++) { + Append(other[i]); + } + + return Num(); +} + +/* +================ +idList::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template< class type > +ID_INLINE int idList::AddUnique( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idList::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template< class type > +ID_INLINE int idList::FindIndex( type const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template< class type > +ID_INLINE type *idList::Find( type const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return &list[ i ]; + } + + return NULL; +} + +/* +================ +idList::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< class type > +ID_INLINE int idList::FindNull( void ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template< class type > +ID_INLINE int idList::IndexOf( type const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idList::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< class type > +ID_INLINE bool idList::RemoveIndex( int index ) { + int i; + + assert( list != NULL ); + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +================ +idList::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< class type > +ID_INLINE bool idList::Remove( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idList::Sort + +Performs a qsort on the list using the supplied comparison function. Note that the data is merely moved around the +list, so any pointers to data within the list may no longer be valid. +================ +*/ +template< class type > +ID_INLINE void idList::Sort( cmp_t *compare ) { + if ( !list ) { + return; + } + typedef int cmp_c(const void *, const void *); + + cmp_c *vCompare = (cmp_c *)compare; + qsort( ( void * )list, ( size_t )num, sizeof( type ), vCompare ); +} + +/* +================ +idList::SortSubSection + +Sorts a subsection of the list. +================ +*/ +template< class type > +ID_INLINE void idList::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { + if ( !list ) { + return; + } + if ( startIndex < 0 ) { + startIndex = 0; + } + if ( endIndex >= num ) { + endIndex = num - 1; + } + if ( startIndex >= endIndex ) { + return; + } + typedef int cmp_c(const void *, const void *); + + cmp_c *vCompare = (cmp_c *)compare; + qsort( ( void * )( &list[startIndex] ), ( size_t )( endIndex - startIndex + 1 ), sizeof( type ), vCompare ); +} + +/* +================ +idList::Swap + +Swaps the contents of two lists +================ +*/ +template< class type > +ID_INLINE void idList::Swap( idList &other ) { + idSwap( num, other.num ); + idSwap( size, other.size ); + idSwap( granularity, other.granularity ); + idSwap( list, other.list ); +} + + + +//RAVENBEGIN +// cdr : added Heap & Stack & Binary Search functionality + +/* +================ +idList::FindBinary + +Assumes the list is sorted and does a binary search for the given key + +================ +*/ +template< class type > +ID_INLINE int idList::FindBinary ( const type & key, cmp_t *compare ) const { + + typedef int cmp_c(const void *, const void *); + cmp_c *vCompare = (cmp_c *)compare; + + type* found = (type*) bsearch( ( void * )( &key ), ( void * )list, ( size_t )num, sizeof( type ), vCompare ); + if (found) + { + return IndexOf(found); + } + return -1; +} + + +/* +================ +idList::StackAdd + +Adds a value to the list as if the list was a stack + +================ +*/ +template< class type > +ID_INLINE void idList::StackAdd( const type & obj ) { + Append( obj ); +} + +/* +================ +idList::StackPop + +Removes a value to the list as if the list was a stack + +================ +*/ +template< class type > +ID_INLINE void idList::StackPop( void ) { +#if 0 + assert(num>0); + if (num<=0) + { + return; + } + +// RAVEN BEGIN +// jsinger: Without this, this container will leak any dynamically allocated members from +// contained class instances +// TTimo: that would cause a double destructor when the list is deleted in Clear() + list[num].~type(); +// RAVEN END + num--; +#else + RemoveIndex( num - 1 ); +#endif +} + +template< class type > +ID_INLINE type& idList::StackTop( void ){ + assert( num > 0 ); + return list[ num-1 ]; +} + +/* +================ +idList::HeapAdd + +Adds a value to the list as if the list was a heap by doing a bubble swap sort +First it appends the object, then swaps up the heap at successive parent positions +as needed + +Complexity: O[n log n] +================ +*/ +template< class type > +ID_INLINE void idList::HeapAdd( const type & obj ) { + + int pos = Append( obj ); + + while (pos && list[((pos-1)/2)]::HeapPop + +Removes the top element from the heap and +First swaps the top element of the heap with the lowest +element, destroys the lowest element (wich was the top), +and then sorts the new top element down the heap as +needed + +Complexity: O[n log n] +================ +*/ +template< class type > +ID_INLINE void idList::HeapPop( void ) { + + assert(num>0); + if (num<=0) + { + return; + } + + idSwap(list[0], list[num-1]); + num--; + + int pos = 0; + + int largestChild = pos; + if (((2*pos)+1)::RemoveNull +================ +*/ +template< class type > +ID_INLINE void idList::RemoveNull() { + for( int ix = Num() - 1; ix >= 0; --ix ) { + if( !list[ix] ) { + RemoveIndex( ix ); + } + } +} + +/* +================ +idList::RemoveRange + +Removes the specified range of elements [low, high] +Only copies down the array once. +================ +*/ +template< class type > +ID_INLINE bool idList::RemoveRange( int low, int high ) { + int i; + + assert( list != NULL ); + assert( low >= 0 ); + assert( high < num ); + assert( low <= high ); + + if ( ( low < 0 ) || ( high >= num ) || ( low > high ) ) { + return false; + } + + int range = (high - low) + 1; + num -= range; + for( i = low; i < num; i++ ) { + list[ i ] = list[ i + range ]; + } + + return true; +} +//RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +template< class type > +void idList::SetAllocatorHeap ( rvHeap* heap ) +{ + assert(heap); + allocatorHeap = heap; +} +#endif +// RAVEN END + +#endif /* !__LIST_H__ */ diff --git a/source/idlib/containers/ListGame.h b/source/idlib/containers/ListGame.h new file mode 100644 index 0000000..426f8f8 --- /dev/null +++ b/source/idlib/containers/ListGame.h @@ -0,0 +1,31 @@ + +#ifndef __LIST_GAME_H__ +#define __LIST_GAME_H__ + +// GCC 4 compiles templates when they are encountered in a source file, +// not when they are used. Therefore all references, must be resolved. +// RemoveContents() from idLib\List.h references global variables only +// available in the GAME DLL. + +/* +================ +idList::RemoveContents +================ +*/ +template< class type > +ID_INLINE void idList::RemoveContents( bool clear ) { + RemoveNull(); + + for( int ix = Num() - 1; ix >= 0; --ix ) { + list[ ix ]->PostEventMS( &EV_Remove, 0 ); + list[ ix ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, Allocated() ); + } +} + +#endif // __LIST_GAME_H__ diff --git a/source/idlib/containers/Pair.h b/source/idlib/containers/Pair.h new file mode 100644 index 0000000..d95f817 --- /dev/null +++ b/source/idlib/containers/Pair.h @@ -0,0 +1,58 @@ +//---------------------------------------------------------------- +// Pair.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __PAIR_H__ +#define __PAIR_H__ + +template< class type1, class type2 > +class rvPair { +public: + rvPair() {}; + rvPair( const type1& T1, const type2& T2 ) { first = T1; second = T2; }; + + const type1& First( void ) const { return first; }; + const type2& Second( void ) const { return second; }; + + static int rvPairFirstCompare( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + return b->First() - a->First(); + } + + static int rvPairSecondCompare( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + return b->Second() - a->Second(); + } + + static int rvPairFirstCompareDirect( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + if( b->First() - a->First() < 0.001f || b->First() - a->First() < -0.001f ) { + return 0; + } + + if( a->First() > b->First() ) { + return -1; + } else if( a->First() < b->First() ) { + return 1; + } + return 0; + } + + static int rvPairSecondCompareDirect( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + if( b->Second() - a->Second() < 0.001f || b->Second() - a->Second() < -0.001f ) { + return 0; + } + + if( a->Second() > b->Second() ) { + return -1; + } else if( a->Second() < b->Second() ) { + return 1; + } + return 0; + } + +private: + type1 first; + type2 second; +}; + +#endif diff --git a/source/idlib/containers/PlaneSet.h b/source/idlib/containers/PlaneSet.h new file mode 100644 index 0000000..14ad70c --- /dev/null +++ b/source/idlib/containers/PlaneSet.h @@ -0,0 +1,54 @@ + +#ifndef __PLANESET_H__ +#define __PLANESET_H__ + +/* +=============================================================================== + + Plane Set + +=============================================================================== +*/ + +class idPlaneSet : public idList { +public: + + void Clear( void ) { idList::Clear(); hash.Free(); } + + int FindPlane( const idPlane &plane, const float normalEps, const float distEps ); + +private: + idHashIndex hash; +}; + +ID_INLINE int idPlaneSet::FindPlane( const idPlane &plane, const float normalEps, const float distEps ) { + int i, border, hashKey; + + assert( distEps <= 0.125f ); + + hashKey = (int)( idMath::Fabs( plane.Dist() ) * 0.125f ); + for ( border = -1; border <= 1; border++ ) { + for ( i = hash.First( hashKey + border ); i >= 0; i = hash.Next( i ) ) { + if ( (*this)[i].Compare( plane, normalEps, distEps ) ) { + return i; + } + } + } + + if ( plane.Type() >= PLANETYPE_NEGX && plane.Type() < PLANETYPE_TRUEAXIAL ) { + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + Append( plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 1 ); + } + else { + Append( plane ); + hash.Add( hashKey, Num()-1 ); + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 2 ); + } +} + +#endif /* !__PLANESET_H__ */ diff --git a/source/idlib/containers/Queue.h b/source/idlib/containers/Queue.h new file mode 100644 index 0000000..7665d0c --- /dev/null +++ b/source/idlib/containers/Queue.h @@ -0,0 +1,173 @@ + +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +/* +=============================================================================== + + Queue template + +=============================================================================== +*/ + +template< class type, int nextOffset > +class idQueueTemplate { +public: + idQueueTemplate( void ); + + void Add( type *element ); + type * Get( void ); + +private: + type * first; + type * last; +}; + +#define QUEUE_NEXT_PTR( element ) (*((type**)(((byte*)element)+nextOffset))) + +template< class type, int nextOffset > +idQueueTemplate::idQueueTemplate( void ) { + first = last = NULL; +} + +template< class type, int nextOffset > +void idQueueTemplate::Add( type *element ) { + QUEUE_NEXT_PTR(element) = NULL; + if ( last ) { + QUEUE_NEXT_PTR(last) = element; + } else { + first = element; + } + last = element; +} + +template< class type, int nextOffset > +type *idQueueTemplate::Get( void ) { + type *element; + + element = first; + if ( element ) { + first = QUEUE_NEXT_PTR(first); + if ( last == element ) { + last = NULL; + } + QUEUE_NEXT_PTR(element) = NULL; + } + return element; +} + +/* +=============================================================================== + + Raven Queue template + +=============================================================================== +*/ +template +class rvQueue +{ + TYPE mData[CAPACITY]; // Total capacity of the queue + int mNewest; // Head + int mOldest; // Tail + int mSize; // Size... yes, this could be calculated... + +public: + rvQueue() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + int begin() {return (full())?(mOldest+1):(mOldest);} + int end() {return mNewest-1;} + void clear() {mNewest = mOldest = mSize = 0;} + bool valid(int i) {return ((mNewest>=mOldest)?(i>=mOldest && i=mNewest && i=(CAPACITY-1))?(0):(i+1);} + TYPE& first() {assert(!empty()); return mData[mOldest];} + TYPE& last() {assert(!empty()); return mData[mNewest-1];} + TYPE& operator[] (int i) {assert(valid(i)); return mData[i];} + int push(const TYPE& value) {assert(!full()); mData[mNewest]=value; inc(mNewest); return (mSize++);} + int pop() {assert(!empty()); inc(mOldest); return (mSize--);} +}; + +/* +=============================================================================== + + Raven Bit Vector Template + +=============================================================================== +*/ +template +class rvBits +{ + enum + { + BITS_SHIFT = 5, // 5. Such A Nice Number + BITS_INT_SIZE = 32, // Size Of A Single Word + BITS_AND = (BITS_INT_SIZE - 1), // Used For And Operation + ARRAY_SIZE = ((CAPACITY + BITS_AND)/(BITS_INT_SIZE)), // Num Words Used + DATA_BYTE_SIZE = (ARRAY_SIZE*sizeof(unsigned int)), // Num Bytes Used + }; + + unsigned int mData[ARRAY_SIZE]; + +public: + rvBits() {clear();} + void clear() {memset(mData, 0, (size_t)(ARRAY_SIZE * sizeof( unsigned int )));} + bool valid(const int i) {return (i>=0 && i>BITS_SHIFT] &= ~(1<<(i&BITS_AND));} + void set(const int i) {assert(valid(i)); mData[i>>BITS_SHIFT] |= (1<<(i&BITS_AND));} + bool operator[](const int i) {assert(valid(i)); return (mData[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0;} +}; + +/* +=============================================================================== + + Raven Pool + +=============================================================================== +*/ +template +class rvPool +{ + TYPE mData[CAPACITY]; + TYPE* mFree[CAPACITY]; + int mSize; + +public: + rvPool() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + void clear() {mSize=0; for (int i=0; i +class rvIndexPool +{ + TYPE mData[CAPACITY]; + TYPE* mFree[CAPACITY]; + TYPE* mIndx[INDEXNUM]; + int mSize; + +public: + rvIndexPool() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + void clear() {mSize=0; for (int i=0; i=0 && inext)> + +template< class type, int nextOffset > +class idStackTemplate { +public: + idStackTemplate( void ); + + void Add( type *element ); + type * Get( void ); + +private: + type * top; + type * bottom; +}; + +#define STACK_NEXT_PTR( element ) (*(type**)(((byte*)element)+nextOffset)) + +template< class type, int nextOffset > +idStackTemplate::idStackTemplate( void ) { + top = bottom = NULL; +} + +template< class type, int nextOffset > +void idStackTemplate::Add( type *element ) { + STACK_NEXT_PTR(element) = top; + top = element; + if ( !bottom ) { + bottom = element; + } +} + +template< class type, int nextOffset > +type *idStackTemplate::Get( void ) { + type *element; + + element = top; + if ( element ) { + top = STACK_NEXT_PTR(top); + if ( bottom == element ) { + bottom = NULL; + } + STACK_NEXT_PTR(element) = NULL; + } + return element; +} + +#endif /* !__STACK_H__ */ diff --git a/source/idlib/containers/StaticList.h b/source/idlib/containers/StaticList.h new file mode 100644 index 0000000..699b877 --- /dev/null +++ b/source/idlib/containers/StaticList.h @@ -0,0 +1,521 @@ + +#ifndef __STATICLIST_H__ +#define __STATICLIST_H__ + +/* +=============================================================================== + + Static list template + A non-growing, memset-able list using no memory allocation. + +=============================================================================== +*/ + +template +class idStaticList { +public: + + idStaticList(); + idStaticList( const idStaticList &other ); + ~idStaticList( void ); + + void Clear( void ); // marks the list as empty. does not deallocate or intialize data. + int Num( void ) const; // returns number of elements in list + int Max( void ) const; // returns the maximum number of elements in the list + void SetNum( int newnum ); // set number of elements in list + + size_t Allocated( void ) const; // returns total size of allocated memory + size_t Size( void ) const; // returns total size of allocated memory including size of list type + size_t MemoryUsed( void ) const; // returns size of the used elements in the list + + const type & operator[]( int index ) const; + type & operator[]( int index ); + + type * Ptr( void ); // returns a pointer to the list + const type * Ptr( void ) const; // returns a pointer to the list + type * Alloc( void ); // returns reference to a new data element at the end of the list. returns NULL when full. + int Append( const type & obj ); // append element + int Append( const idStaticList &other ); // append list + int AddUnique( const type & obj ); // add unique element + int Insert( const type & obj, int index ); // insert the element at the given index + int FindIndex( const type & obj ) const; // find the index for the given element + type * Find( type const & obj ) const; // find pointer to the given element + int FindNull( void ) const; // find the index for the first NULL pointer in the list + int IndexOf( const type *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + bool Remove( const type & obj ); // remove the element + void Swap( idStaticList &other ); // swap the contents of the lists + void DeleteContents( bool clear ); // delete the contents of the list + +private: + int num; + type list[ size ]; +}; + +/* +================ +idStaticList::idStaticList() +================ +*/ +template +ID_INLINE idStaticList::idStaticList() { + num = 0; +} + +/* +================ +idStaticList::idStaticList( const idStaticList &other ) +================ +*/ +template +ID_INLINE idStaticList::idStaticList( const idStaticList &other ) { + *this = other; +} + +/* +================ +idStaticList::~idStaticList +================ +*/ +template +ID_INLINE idStaticList::~idStaticList( void ) { +} + +/* +================ +idStaticList::Clear + +Sets the number of elements in the list to 0. Assumes that type automatically handles freeing up memory. +================ +*/ +template +ID_INLINE void idStaticList::Clear( void ) { + num = 0; +} + +/* +================ +idStaticList::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template +ID_INLINE void idStaticList::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < size; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, sizeof( list ) ); + } +} + +/* +================ +idStaticList::Num + +Returns the number of elements currently contained in the list. +================ +*/ +template +ID_INLINE int idStaticList::Num( void ) const { + return num; +} + +/* +================ +idStaticList::Num + +Returns the maximum number of elements in the list. +================ +*/ +template +ID_INLINE int idStaticList::Max( void ) const { + return size; +} + +/* +================ +idStaticList::Allocated +================ +*/ +template +ID_INLINE size_t idStaticList::Allocated( void ) const { + return size * sizeof( type ); +} + +/* +================ +idStaticList::Size +================ +*/ +template +ID_INLINE size_t idStaticList::Size( void ) const { + return sizeof( idStaticList ) + Allocated(); +} + +/* +================ +idStaticList::Num +================ +*/ +template +ID_INLINE size_t idStaticList::MemoryUsed( void ) const { + return num * sizeof( list[ 0 ] ); +} + +/* +================ +idStaticList::SetNum + +Set number of elements in list. +================ +*/ +template +ID_INLINE void idStaticList::SetNum( int newnum ) { + assert( newnum >= 0 ); + assert( newnum <= size ); + num = newnum; +} + +/* +================ +idStaticList::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE const type &idStaticList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE type &idStaticList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE type *idStaticList::Ptr( void ) { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE const type *idStaticList::Ptr( void ) const { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Alloc + +Returns a pointer to a new data element at the end of the list. +================ +*/ +template +ID_INLINE type *idStaticList::Alloc( void ) { + if ( num >= size ) { + return NULL; + } + + return &list[ num++ ]; +} + +/* +================ +idStaticList::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Append( type const & obj ) { + assert( num < size ); + if ( num < size ) { + list[ num ] = obj; + num++; + return num - 1; + } + + return -1; +} + + +/* +================ +idStaticList::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Insert( type const & obj, int index ) { + int i; + + assert( num < size ); + if ( num >= size ) { + return -1; + } + + assert( index >= 0 ); + if ( index < 0 ) { + index = 0; + } else if ( index > num ) { + index = num; + } + + for( i = num; i > index; --i ) { + list[i] = list[i-1]; + } + + num++; + list[index] = obj; + return index; +} + +/* +================ +idStaticList::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template +ID_INLINE int idStaticList::Append( const idStaticList &other ) { + int i; + int n = other.Num(); + + if ( num + n > size ) { + n = size - num; + } + for( i = 0; i < n; i++ ) { + list[i + num] = other.list[i]; + } + num += n; + return Num(); +} + +/* +================ +idStaticList::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template +ID_INLINE int idStaticList::AddUnique( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idStaticList::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template +ID_INLINE int idStaticList::FindIndex( type const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template +ID_INLINE type *idStaticList::Find( type const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return &list[ i ]; + } + + return NULL; +} + +/* +================ +idStaticList::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template +ID_INLINE int idStaticList::FindNull( void ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template +ID_INLINE int idStaticList::IndexOf( type const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idStaticList::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::RemoveIndex( int index ) { + int i; + + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +================ +idStaticList::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::Remove( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idStaticList::Swap + +Swaps the contents of two lists +================ +*/ +template +ID_INLINE void idStaticList::Swap( idStaticList &other ) { + idStaticList temp = *this; + *this = other; + other = temp; +} + +#endif /* !__STATICLIST_H__ */ diff --git a/source/idlib/containers/StrList.h b/source/idlib/containers/StrList.h new file mode 100644 index 0000000..db0b849 --- /dev/null +++ b/source/idlib/containers/StrList.h @@ -0,0 +1,196 @@ + +#ifndef __STRLIST_H__ +#define __STRLIST_H__ + +/* +=============================================================================== + + idStrList + +=============================================================================== +*/ + +typedef idList idStrList; +typedef idList idStrPtrList; +typedef idStr *idStrPtr; + +/* +================ +idListSortCompare + +Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +================ +*/ +template<> +ID_INLINE int idListSortCompare( const idStrPtr *a, const idStrPtr *b ) { + return ( *a )->Icmp( **b ); +} + +/* +================ +idStrList::Sort + +Sorts the list of strings alphabetically. Creates a list of pointers to the actual strings and sorts the +pointer list. Then copies the strings into another list using the ordered list of pointers. +================ +*/ +template<> +ID_INLINE void idStrList::Sort( cmp_t *compare ) { + int i; + + if ( !num ) { + return; + } + + idList other; + idList pointerList; + + pointerList.SetNum( num ); + for( i = 0; i < num; i++ ) { + pointerList[ i ] = &( *this )[ i ]; + } + + pointerList.Sort(); + + other.SetNum( num ); + other.SetGranularity( granularity ); + for( i = 0; i < other.Num(); i++ ) { + other[ i ] = *pointerList[ i ]; + } + + this->Swap( other ); +} + +/* +================ +idStrList::SortSubSection + +Sorts a subsection of the list of strings alphabetically. +================ +*/ +template<> +ID_INLINE void idStrList::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { + int i, s; + + if ( !num ) { + return; + } + if ( startIndex < 0 ) { + startIndex = 0; + } + if ( endIndex >= num ) { + endIndex = num - 1; + } + if ( startIndex >= endIndex ) { + return; + } + + idList other; + idList pointerList; + + s = endIndex - startIndex + 1; + other.SetNum( s ); + pointerList.SetNum( s ); + for( i = 0; i < s; i++ ) { + other[ i ] = ( *this )[ startIndex + i ]; + pointerList[ i ] = &other[ i ]; + } + + pointerList.Sort(); + + for( i = 0; i < s; i++ ) { + (*this)[ startIndex + i ] = *pointerList[ i ]; + } +} + +/* +================ +idStrList::Size +================ +*/ +template<> +ID_INLINE size_t idStrList::Size( void ) const { + size_t s; + int i; + + s = sizeof( *this ); + for( i = 0; i < Num(); i++ ) { + s += ( *this )[ i ].Size(); + } + + return s; +} + +/* +================ +idStrList::RemoveDuplicates +================ +*/ +template<> +ID_INLINE void idStrList::RemoveDuplicates( void ) { + int i, j; + + for( i = 0; i < Num() - 1; i++ ) { + for( j = i + 1; j < Num(); ) { + if( ( *this )[i] == ( *this )[j] ) { + RemoveIndex( j ); + } else { + j++; + } + } + } +} +/* +=============================================================================== + + idStrList path sorting + +=============================================================================== +*/ + +/* +================ +idListSortComparePaths + +Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +================ +*/ +template +ID_INLINE int idListSortComparePaths( const idStrPtr *a, const idStrPtr *b ) { + return ( *a )->IcmpPath( **b ); +} + +/* +================ +idStrListSortPaths + +Sorts the list of path strings alphabetically and makes sure folders come first. +================ +*/ +ID_INLINE void idStrListSortPaths( idStrList &list ) { + int i; + + if ( !list.Num() ) { + return; + } + + idList other; + idList pointerList; + + pointerList.SetNum( list.Num() ); + for( i = 0; i < list.Num(); i++ ) { + pointerList[ i ] = &list[ i ]; + } + + pointerList.Sort( idListSortComparePaths ); + + other.SetNum( list.Num() ); + other.SetGranularity( list.GetGranularity() ); + for( i = 0; i < other.Num(); i++ ) { + other[ i ] = *pointerList[ i ]; + } + + list.Swap( other ); +} + +#endif /* !__STRLIST_H__ */ diff --git a/source/idlib/containers/StrPool.h b/source/idlib/containers/StrPool.h new file mode 100644 index 0000000..81013ff --- /dev/null +++ b/source/idlib/containers/StrPool.h @@ -0,0 +1,225 @@ + +#ifndef __STRPOOL_H__ +#define __STRPOOL_H__ + +/* +=============================================================================== + + idStrPool + +=============================================================================== +*/ + +class idStrPool; + +class idPoolStr : public idStr { + friend class idStrPool; + +public: + idPoolStr() { numUsers = 0; } + ~idPoolStr() { assert( numUsers == 0 ); } + + // returns total size of allocated memory + size_t Allocated( void ) const { return idStr::Allocated(); } + // returns total size of allocated memory including size of string pool type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + // returns a pointer to the pool this string was allocated from + const idStrPool * GetPool( void ) const { return pool; } + +private: + idStrPool * pool; + mutable int numUsers; +}; + +class idStrPool { +public: + idStrPool() { caseSensitive = true; } + + void SetCaseSensitive( bool caseSensitive ); + + int Num( void ) const { return pool.Num(); } + size_t Allocated( void ) const; + size_t Size( void ) const; + + const idPoolStr * operator[]( int index ) const { return pool[index]; } + + const idPoolStr * AllocString( const char *string ); + void FreeString( const idPoolStr *poolStr ); + const idPoolStr * CopyString( const idPoolStr *poolStr ); + void Clear( void ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ) + { + assert(heap); + pool.SetAllocatorHeap(heap); + poolHash.SetAllocatorHeap(heap); + } +#endif +// RAVEN END + +private: + bool caseSensitive; + idList pool; + idHashIndex poolHash; +}; + +/* +================ +idStrPool::SetCaseSensitive +================ +*/ +ID_INLINE void idStrPool::SetCaseSensitive( bool caseSensitive ) { + this->caseSensitive = caseSensitive; +} + +/* +================ +idStrPool::AllocString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::AllocString( const char *string ) { + int i, hash; + idPoolStr *poolStr; + + hash = poolHash.GenerateKey( string, caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + poolStr = new idPoolStr; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + *static_cast(poolStr) = string; + poolStr->pool = this; + poolStr->numUsers = 1; + poolHash.Add( hash, pool.Append( poolStr ) ); + return poolStr; +} + +/* +================ +idStrPool::FreeString +================ +*/ +ID_INLINE void idStrPool::FreeString( const idPoolStr *poolStr ) { + int i, hash; + + assert( poolStr->numUsers >= 1 ); + assert( poolStr->pool == this ); + + poolStr->numUsers--; + if ( poolStr->numUsers <= 0 ) { + hash = poolHash.GenerateKey( poolStr->c_str(), caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } + assert( i != -1 ); + assert( pool[i] == poolStr ); + delete pool[i]; + pool.RemoveIndex( i ); + poolHash.RemoveIndex( hash, i ); + } +} + +/* +================ +idStrPool::CopyString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::CopyString( const idPoolStr *poolStr ) { + + assert( poolStr->numUsers >= 1 ); + + if ( poolStr->pool == this ) { + // the string is from this pool so just increase the user count + poolStr->numUsers++; + return poolStr; + } else { + // the string is from another pool so it needs to be re-allocated from this pool. + return AllocString( poolStr->c_str() ); + } +} + +/* +================ +idStrPool::Clear +================ +*/ +ID_INLINE void idStrPool::Clear( void ) { + int i; + + for ( i = 0; i < pool.Num(); i++ ) { + pool[i]->numUsers = 0; + } + pool.DeleteContents( true ); + poolHash.Free(); +} + +/* +================ +idStrPool::Allocated +================ +*/ +ID_INLINE size_t idStrPool::Allocated( void ) const { + int i; + size_t size; + + size = pool.Allocated() + poolHash.Allocated(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Allocated(); + } + return size; +} + +/* +================ +idStrPool::Size +================ +*/ +ID_INLINE size_t idStrPool::Size( void ) const { + int i; + size_t size; + + size = pool.Size() + poolHash.Size(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Size(); + } + return size; +} + +#endif /* !__STRPOOL_H__ */ diff --git a/source/idlib/containers/VectorSet.h b/source/idlib/containers/VectorSet.h new file mode 100644 index 0000000..84cc6a2 --- /dev/null +++ b/source/idlib/containers/VectorSet.h @@ -0,0 +1,243 @@ + +#ifndef __VECTORSET_H__ +#define __VECTORSET_H__ + +/* +=============================================================================== + + Vector Set + + Creates a set of vectors without duplicates. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSet : public idList { +public: + idVectorSet( void ); + idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated( void ) const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void ResizeIndex( const int newSize ); + void Clear( void ); + + int FindVector( const type &v, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet( void ) { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + idList::AssureSize( initialSize ); + idList::SetNum( 0, false ); + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::ResizeIndex( const int newSize ) { + idList::Resize( newSize ); + hash.ResizeIndex( newSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Clear( void ) { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSet::FindVector( const type &v, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = (*this)[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, idList::Num() ); + Append( v ); + return idList::Num()-1; +} + + +/* +=============================================================================== + + Vector Subset + + Creates a subset without duplicates from an existing list with vectors. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSubset { +public: + idVectorSubset( void ); + idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated( void ) const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void Clear( void ); + + // returns either vectorNum or an index to a previously found vector + int FindVector( const type *vectorList, const int vectorNum, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset( void ) { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Clear( void ) { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSubset::FindVector( const type *vectorList, const int vectorNum, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + const type &v = vectorList[vectorNum]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = vectorList[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, vectorNum ); + return vectorNum; +} + +#endif /* !__VECTORSET_H__ */ diff --git a/source/idlib/containers/rvBlockPool.h b/source/idlib/containers/rvBlockPool.h new file mode 100644 index 0000000..7b7554f --- /dev/null +++ b/source/idlib/containers/rvBlockPool.h @@ -0,0 +1,107 @@ +#ifndef __BLOCKPOOL_H__ +#define __BLOCKPOOL_H__ + +#ifdef _RV_MEM_SYS_SUPPORT + +/* +=============================================================================== + + Block based allocator for fixed size objects. + + All objects of the 'type' are properly constructed. + However, the constructor is not called for re-used objects. + + This is essentially the same as idBlockAlloc found in heap.h. The difference + is that rvBlockPool is aware of which system heap it should allocate into. + +=============================================================================== +*/ + +template +class rvBlockPool { +public: + rvBlockPool( void ); + ~rvBlockPool( void ); + + void Shutdown( void ); + + type * Alloc( void ); + void Free( type *element ); + + int GetTotalCount( void ) const { return total; } + int GetAllocCount( void ) const { return active; } + int GetFreeCount( void ) const { return total - active; } + + size_t Allocated( void ) const { return( total * sizeof( type ) ); } + +private: + typedef struct element_s { + struct element_s * next; + type t; + } element_t; + typedef struct block_s { + element_t elements[blockSize]; + struct block_s * next; + } block_t; + + block_t * blocks; + element_t * free; + int total; + int active; +}; + +template +rvBlockPool::rvBlockPool( void ) { + blocks = NULL; + free = NULL; + total = active = 0; +} + +template +rvBlockPool::~rvBlockPool( void ) { + Shutdown(); +} + +template +type *rvBlockPool::Alloc( void ) { + if ( !free ) { + RV_PUSH_SYS_HEAP_ID(heapID); + block_t *block = new block_t; + RV_POP_HEAP(); + block->next = blocks; + blocks = block; + for ( int i = 0; i < blockSize; i++ ) { + block->elements[i].next = free; + free = &block->elements[i]; + } + total += blockSize; + } + active++; + element_t *element = free; + free = free->next; + element->next = NULL; + return &element->t; +} + +template +void rvBlockPool::Free( type *t ) { + element_t *element = (element_t *)( ( (unsigned char *) t ) - ( (int) &((element_t *)0)->t ) ); + element->next = free; + free = element; + active--; +} + +template +void rvBlockPool::Shutdown( void ) { + while( blocks ) { + block_t *block = blocks; + blocks = blocks->next; + delete block; + } + blocks = NULL; + free = NULL; + total = active = 0; +} + +#endif // _RV_MEM_SYS_SUPPORT +#endif // __BLOCKPOOL_H__ diff --git a/source/idlib/geometry/DrawVert.h b/source/idlib/geometry/DrawVert.h new file mode 100644 index 0000000..a7cac88 --- /dev/null +++ b/source/idlib/geometry/DrawVert.h @@ -0,0 +1,153 @@ + +#ifndef __DRAWVERT_H__ +#define __DRAWVERT_H__ + +/* +=============================================================================== + + Draw Vertex. + +=============================================================================== +*/ + +class idDrawVert { +public: + idVec3 xyz; + byte color[4]; + idVec3 normal; + byte color2[4]; + idVec3 tangents[2]; + idVec2 st; + + float operator[]( const int index ) const; + float & operator[]( const int index ); + + void Clear( void ); + + const idVec3 & GetNormal( void ) const; + void SetNormal( float x, float y, float z ); + void SetNormal( const idVec3 &n ); + + const idVec3 & GetTangent( void ) const; + void SetTangent( float x, float y, float z ); + void SetTangent( const idVec3 &t ); + + const idVec3 & GetBiTangent( void ) const; // derived from normal, tangent, and tangent flag + void SetBiTangent( float x, float y, float z ); + void SetBiTangent( const idVec3 &t ); + void SetBiTangentSign( float sign ); // either 1.0f or -1.0f + + void Lerp( const idDrawVert &a, const idDrawVert &b, const float f ); + void LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ); + + void Normalize( void ); + + void SetColor( dword color ); + dword GetColor( void ) const; +}; + +// offsets for SIMD code +#define DRAWVERT_SIZE 64 // sizeof( idDrawVert ) +#define DRAWVERT_SIZE_SHIFT 6 // log2( sizeof( idDrawVert ) ) +#define DRAWVERT_XYZ_OFFSET (0*4) // offsetof( idDrawVert, xyz ) +#define DRAWVERT_COLOR_OFFSET (3*4) // offsetof( idDrawVert, color ) +#define DRAWVERT_NORMAL_OFFSET (4*4) // offsetof( idDrawVert, normal ) +#define DRAWVERT_COLOR2_OFFSET (7*4) // offsetof( idDrawVert, color2 ) +#define DRAWVERT_TANGENT0_OFFSET (8*4) // offsetof( idDrawVert, tangents[0] ) +#define DRAWVERT_TANGENT1_OFFSET (11*4) // offsetof( idDrawVert, tangents[1] ) +#define DRAWVERT_ST_OFFSET (14*4) // offsetof( idDrawVert, st ) + +assert_sizeof( idDrawVert, DRAWVERT_SIZE ); +assert_sizeof( idDrawVert, (1<= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} +ID_INLINE float &idDrawVert::operator[]( const int index ) { + assert( index >= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} + +ID_INLINE void idDrawVert::Clear( void ) { + xyz.Zero(); + st.Zero(); + normal.Zero(); + tangents[0].Zero(); + tangents[1].Zero(); + color[0] = color[1] = color[2] = color[3] = 0; +} + +ID_INLINE const idVec3 &idDrawVert::GetNormal( void ) const { + return normal; +} + +ID_INLINE void idDrawVert::SetNormal( const idVec3 &n ) { + normal = n; +} + +ID_INLINE void idDrawVert::SetNormal( float x, float y, float z ) { + normal.Set( x, y, z ); +} + +ID_INLINE const idVec3 &idDrawVert::GetTangent( void ) const { + return tangents[0]; +} + +ID_INLINE void idDrawVert::SetTangent( float x, float y, float z ) { + tangents[0].Set( x, y, z ); +} + +ID_INLINE void idDrawVert::SetTangent( const idVec3 &t ) { + tangents[0] = t; +} + +ID_INLINE const idVec3 &idDrawVert::GetBiTangent( void ) const { + return tangents[1]; +} + +ID_INLINE void idDrawVert::SetBiTangent( float x, float y, float z ) { + tangents[1].Set( x, y, z ); +} + +ID_INLINE void idDrawVert::SetBiTangent( const idVec3 &t ) { + tangents[1] = t; +} + +ID_INLINE void idDrawVert::SetBiTangentSign( float sign ) { +// tangents[0][3] = sign; +} + +ID_INLINE void idDrawVert::Lerp( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = a.xyz + f * ( b.xyz - a.xyz ); + st = a.st + f * ( b.st - a.st ); +} + +ID_INLINE void idDrawVert::LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = a.xyz + f * ( b.xyz - a.xyz ); + st = a.st + f * ( b.st - a.st ); + normal = a.normal + f * ( b.normal - a.normal ); + tangents[0] = a.tangents[0] + f * ( b.tangents[0] - a.tangents[0] ); + tangents[1] = a.tangents[1] + f * ( b.tangents[1] - a.tangents[1] ); + color[0] = (byte)( a.color[0] + f * ( b.color[0] - a.color[0] ) ); + color[1] = (byte)( a.color[1] + f * ( b.color[1] - a.color[1] ) ); + color[2] = (byte)( a.color[2] + f * ( b.color[2] - a.color[2] ) ); + color[3] = (byte)( a.color[3] + f * ( b.color[3] - a.color[3] ) ); +} + +ID_INLINE void idDrawVert::SetColor( dword color ) { + *reinterpret_cast(this->color) = color; +} + +ID_INLINE dword idDrawVert::GetColor( void ) const { + return *reinterpret_cast(this->color); +} + +#endif /* !__DRAWVERT_H__ */ diff --git a/source/idlib/geometry/JointTransform.cpp b/source/idlib/geometry/JointTransform.cpp new file mode 100644 index 0000000..3a343aa --- /dev/null +++ b/source/idlib/geometry/JointTransform.cpp @@ -0,0 +1,60 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +============= +idJointMat::ToJointQuat +============= +*/ +idJointQuat idJointMat::ToJointQuat( void ) const { + idJointQuat jq; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[3] = s * t; + jq.q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + jq.q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + jq.q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[i] = s * t; + jq.q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + jq.q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + jq.q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + jq.t[0] = mat[0 * 4 + 3]; + jq.t[1] = mat[1 * 4 + 3]; + jq.t[2] = mat[2 * 4 + 3]; + + return jq; +} diff --git a/source/idlib/geometry/JointTransform.h b/source/idlib/geometry/JointTransform.h new file mode 100644 index 0000000..5fcfa23 --- /dev/null +++ b/source/idlib/geometry/JointTransform.h @@ -0,0 +1,340 @@ + +#ifndef __JOINTTRANSFORM_H__ +#define __JOINTTRANSFORM_H__ + +/* +=============================================================================== + + Joint Quaternion + +=============================================================================== +*/ + +class idJointQuat { +public: + + idQuat q; + idVec3 t; + float w; + }; + +// offsets for SIMD code +#define JOINTQUAT_SIZE (8*4) // sizeof( idJointQuat ) +#define JOINTQUAT_SIZE_SHIFT 5 // log2( sizeof( idJointQuat ) ) +#define JOINTQUAT_Q_OFFSET (0*4) // offsetof( idJointQuat, q ) +#define JOINTQUAT_T_OFFSET (4*4) // offsetof( idJointQuat, t ) + +assert_sizeof( idJointQuat, JOINTQUAT_SIZE ); +assert_sizeof( idJointQuat, (1< epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idJointMat::operator==( const idJointMat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idJointMat::operator!=( const idJointMat &a ) const { + return !Compare( a ); +} + +ID_INLINE idMat3 idJointMat::ToMat3( void ) const { + return idMat3( mat[0 * 4 + 0], mat[1 * 4 + 0], mat[2 * 4 + 0], + mat[0 * 4 + 1], mat[1 * 4 + 1], mat[2 * 4 + 1], + mat[0 * 4 + 2], mat[1 * 4 + 2], mat[2 * 4 + 2] ); +} + +ID_INLINE idVec3 idJointMat::ToVec3( void ) const { + return idVec3( mat[0 * 4 + 3], mat[1 * 4 + 3], mat[2 * 4 + 3] ); +} + +ID_INLINE const float *idJointMat::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idJointMat::ToFloatPtr( void ) { + return mat; +} + +ID_INLINE void idJointMat::Mul( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] = s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] = s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] = s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] = s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] = s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] = s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] = s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] = s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] = s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = s * mat.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::Mad( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] += s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] += s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] += s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] += s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] += s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] += s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] += s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] += s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] += s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] += s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] += s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] += s * mat.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::Multiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[0 * 4 + 3]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[1 * 4 + 3]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::InverseMultiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + float dst[3]; + + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + + dst[0] = - ( m2.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 0] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 0] * m2.mat[2 * 4 + 3] ); + dst[1] = - ( m2.mat[0 * 4 + 1] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 1] * m2.mat[2 * 4 + 3] ); + dst[2] = - ( m2.mat[0 * 4 + 2] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 2] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] ); + + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * dst[0] + m1.mat[0 * 4 + 1] * dst[1] + m1.mat[0 * 4 + 2] * dst[2] + m1.mat[0 * 4 + 3]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * dst[0] + m1.mat[1 * 4 + 1] * dst[1] + m1.mat[1 * 4 + 2] * dst[2] + m1.mat[1 * 4 + 3]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * dst[0] + m1.mat[2 * 4 + 1] * dst[1] + m1.mat[2 * 4 + 2] * dst[2] + m1.mat[2 * 4 + 3]; +} + +// RAVEN BEGIN +// dluetscher: added function to quickly invert an idJointMat - assumes rotation-translation only + +ID_INLINE void idJointMat::Invert( ) { + float m01, m02, m12, tx, ty; + + tx = mat[0 * 4 + 3]; + ty = mat[1 * 4 + 3]; + mat[0 * 4 + 3] = -(tx*mat[0 * 4 + 0] + ty*mat[1 * 4 + 0] + mat[2 * 4 + 3]*mat[2 * 4 + 0]); + mat[1 * 4 + 3] = -(tx*mat[0 * 4 + 1] + ty*mat[1 * 4 + 1] + mat[2 * 4 + 3]*mat[2 * 4 + 1]); + mat[2 * 4 + 3] = -(tx*mat[0 * 4 + 2] + ty*mat[1 * 4 + 2] + mat[2 * 4 + 3]*mat[2 * 4 + 2]); + + m01 = mat[0 * 4 + 1]; + m02 = mat[0 * 4 + 2]; + m12 = mat[1 * 4 + 2]; + mat[0 * 4 + 1] = mat[1 * 4 + 0]; + mat[0 * 4 + 2] = mat[2 * 4 + 0]; + mat[1 * 4 + 0] = m01; + mat[1 * 4 + 2] = mat[2 * 4 + 1]; + mat[2 * 4 + 0] = m02; + mat[2 * 4 + 1] = m12; +} + +// RAVEN END +#endif /* !__JOINTTRANSFORM_H__ */ diff --git a/source/idlib/geometry/Surface.cpp b/source/idlib/geometry/Surface.cpp new file mode 100644 index 0000000..764e7aa --- /dev/null +++ b/source/idlib/geometry/Surface.cpp @@ -0,0 +1,903 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +================= +UpdateVertexIndex +================= +*/ +ID_INLINE int UpdateVertexIndex( int vertexIndexNum[2], int *vertexRemap, int *vertexCopyIndex, int vertNum ) { + int s = INTSIGNBITSET( vertexRemap[vertNum] ); + vertexIndexNum[0] = vertexRemap[vertNum]; + vertexRemap[vertNum] = vertexIndexNum[s]; + vertexIndexNum[1] += s; + vertexCopyIndex[vertexRemap[vertNum]] = vertNum; + return vertexRemap[vertNum]; +} + +/* +================= +idSurface::Split +================= +*/ +int idSurface::Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges, int *backOnPlaneEdges ) const { + float * dists; + float f; + byte * sides; + int counts[3]; + int * edgeSplitVertex; + int numEdgeSplitVertexes; + int * vertexRemap[2]; + int vertexIndexNum[2][2]; + int * vertexCopyIndex[2]; + int * indexPtr[2]; + int indexNum[2]; + int * index; + int * onPlaneEdges[2]; + int numOnPlaneEdges[2]; + int maxOnPlaneEdges; + int i; + idSurface * surface[2]; + idDrawVert v; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + *back = new idSurface( *this ); + return SIDE_BACK; + } else { + *front = new idSurface( *this ); + return SIDE_FRONT; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = new idSurface( *this ); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = new idSurface( *this ); + return SIDE_FRONT; + } + + // allocate front and back surface + *front = surface[0] = new idSurface(); + *back = surface[1] = new idSurface(); + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + maxOnPlaneEdges = 4 * counts[SIDE_ON]; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + int sidesOr = ( sides[v0] | sides[v1] ); + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( sidesOr & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[sidesOr & SIDE_BACK]++; + counts[SIDE_ON] += ( sidesOr & SIDE_ON ) >> 1; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + surface[0]->verts.Append( v ); + surface[1]->verts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be more indexes than twice the number of edges + surface[0]->indexes.Resize( ( ( counts[SIDE_FRONT] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + surface[1]->indexes.Resize( ( ( counts[SIDE_BACK] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap[0] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[0], -1, verts.Num() * sizeof( int ) ); + vertexRemap[1] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[1], -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex[0] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + vertexCopyIndex[1] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0][0] = vertexIndexNum[1][0] = 0; + vertexIndexNum[0][1] = vertexIndexNum[1][1] = numEdgeSplitVertexes; + + indexPtr[0] = surface[0]->indexes.Ptr(); + indexPtr[1] = surface[1]->indexes.Ptr(); + indexNum[0] = surface[0]->indexes.Num(); + indexNum[1] = surface[1]->indexes.Num(); + + maxOnPlaneEdges += 4 * numEdgeSplitVertexes; + // allocate one more in case no triangles are actually split which may happen for a disconnected surface + onPlaneEdges[0] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + numOnPlaneEdges[0] = numOnPlaneEdges[1] = 0; + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2, s, n; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INTSIGNBITSET( edgeSplitVertex[e0] ) | ( INTSIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INTSIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + s = FLOATSIGNBITSET( f ); + } else { + s = ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK; + } + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]] = n; + numOnPlaneEdges[s] += ( sides[v0] & sides[v1] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+1; + numOnPlaneEdges[s] += ( sides[v1] & sides[v2] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+2; + numOnPlaneEdges[s] += ( sides[v2] & sides[v0] ) >> 1; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 1: { // first edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + break; + } + case 2: { // second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 3: { // first and second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 4: { // third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 5: { // first and third edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + case 6: { // second and third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + } + } + + surface[0]->indexes.SetNum( indexNum[0], false ); + surface[1]->indexes.SetNum( indexNum[1], false ); + + // copy vertexes + surface[0]->verts.SetNum( vertexIndexNum[0][1], false ); + index = vertexCopyIndex[0]; + for ( i = numEdgeSplitVertexes; i < surface[0]->verts.Num(); i++ ) { + surface[0]->verts[i] = verts[index[i]]; + } + surface[1]->verts.SetNum( vertexIndexNum[1][1], false ); + index = vertexCopyIndex[1]; + for ( i = numEdgeSplitVertexes; i < surface[1]->verts.Num(); i++ ) { + surface[1]->verts[i] = verts[index[i]]; + } + + // generate edge indexes + surface[0]->GenerateEdgeIndexes(); + surface[1]->GenerateEdgeIndexes(); + + if ( frontOnPlaneEdges ) { + memcpy( frontOnPlaneEdges, onPlaneEdges[0], numOnPlaneEdges[0] * sizeof( int ) ); + frontOnPlaneEdges[numOnPlaneEdges[0]] = -1; + } + + if ( backOnPlaneEdges ) { + memcpy( backOnPlaneEdges, onPlaneEdges[1], numOnPlaneEdges[1] * sizeof( int ) ); + backOnPlaneEdges[numOnPlaneEdges[1]] = -1; + } + + return SIDE_CROSS; +} + +/* +================= +idSurface::ClipInPlace +================= +*/ +bool idSurface::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + float f; + byte * sides; + int counts[3]; + int i; + int * edgeSplitVertex; + int * vertexRemap; + int vertexIndexNum[2]; + int * vertexCopyIndex; + int * indexPtr; + int indexNum; + int numEdgeSplitVertexes; + idDrawVert v; + idList newVerts; + idList newIndexes; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + Clear(); + return false; + } else { + return true; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + Clear(); + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( ( sides[v0] | sides[v1] ) & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[(sides[v0]|sides[v1]) & SIDE_BACK]++; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + newVerts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be + // more indexes than twice the number of edges + newIndexes.Resize( ( counts[SIDE_FRONT] << 1 ) + ( numEdgeSplitVertexes << 2 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap, -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0] = 0; + vertexIndexNum[1] = numEdgeSplitVertexes; + + indexPtr = newIndexes.Ptr(); + indexNum = newIndexes.Num(); + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INTSIGNBITSET( edgeSplitVertex[e0] ) | ( INTSIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INTSIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK ) { + break; + } + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + if ( !keepOn ) { + break; + } + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + break; + } + } + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + break; + } + case 1: { // first edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } + break; + } + case 2: { // second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 3: { // first and second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e0]; + } else { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 4: { // third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } + break; + } + case 5: { // first and third edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + case 6: { // second and third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + } + } + + newIndexes.SetNum( indexNum, false ); + + // copy vertexes + newVerts.SetNum( vertexIndexNum[1], false ); + for ( i = numEdgeSplitVertexes; i < newVerts.Num(); i++ ) { + newVerts[i] = verts[vertexCopyIndex[i]]; + } + + // copy back to this surface + indexes = newIndexes; + verts = newVerts; + + GenerateEdgeIndexes(); + + return true; +} + +/* +============= +idSurface::IsConnected +============= +*/ +bool idSurface::IsConnected( void ) const { + int i, j, numIslands, numTris; + int queueStart, queueEnd; + int *queue, *islandNum; + int curTri, nextTri, edgeNum; + const int *index; + + numIslands = 0; + numTris = indexes.Num() / 3; + islandNum = (int *) _alloca16( numTris * sizeof( int ) ); + memset( islandNum, -1, numTris * sizeof( int ) ); + queue = (int *) _alloca16( numTris * sizeof( int ) ); + + for ( i = 0; i < numTris; i++ ) { + + if ( islandNum[i] != -1 ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = i; + islandNum[i] = numIslands; + + for ( curTri = queue[queueStart]; queueStart < queueEnd; curTri = queue[++queueStart] ) { + + index = &edgeIndexes[curTri * 3]; + + for ( j = 0; j < 3; j++ ) { + + edgeNum = index[j]; + nextTri = edges[abs(edgeNum)].tris[INTSIGNBITNOTSET(edgeNum)]; + + if ( nextTri == -1 ) { + continue; + } + + nextTri /= 3; + + if ( islandNum[nextTri] != -1 ) { + continue; + } + + queue[queueEnd++] = nextTri; + islandNum[nextTri] = numIslands; + } + } + numIslands++; + } + + return ( numIslands == 1 ); +} + +/* +================= +idSurface::IsClosed +================= +*/ +bool idSurface::IsClosed( void ) const { + for ( int i = 0; i < edges.Num(); i++ ) { + if ( edges[i].tris[0] < 0 || edges[i].tris[1] < 0 ) { + return false; + } + } + return true; +} + +/* +============= +idSurface::IsPolytope +============= +*/ +bool idSurface::IsPolytope( const float epsilon ) const { + int i, j; + idPlane plane; + + if ( !IsClosed() ) { + return false; + } + + for ( i = 0; i < indexes.Num(); i += 3 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + + for ( j = 0; j < verts.Num(); j++ ) { + if ( plane.Side( verts[j].xyz, epsilon ) == SIDE_FRONT ) { + return false; + } + } + } + return true; +} + +/* +============= +idSurface::PlaneDistance +============= +*/ +float idSurface::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idSurface::PlaneSide +============= +*/ +int idSurface::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +================= +idSurface::LineIntersection +================= +*/ +bool idSurface::LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float scale; + + RayIntersection( start, end - start, scale, false ); + return ( scale >= 0.0f && scale <= 1.0f ); +} + +/* +================= +idSurface::RayIntersection +================= +*/ +bool idSurface::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i, i0, i1, i2, s0, s1, s2; + float d, s; + byte *sidedness; + idPluecker rayPl, pl; + idPlane plane; + + sidedness = (byte *)_alloca( edges.Num() * sizeof(byte) ); + scale = idMath::INFINITY; + + rayPl.FromRay( start, dir ); + + // ray sidedness for edges + for ( i = 0; i < edges.Num(); i++ ) { + pl.FromLine( verts[ edges[i].verts[1] ].xyz, verts[ edges[i].verts[0] ].xyz ); + d = pl.PermutedInnerProduct( rayPl ); + sidedness[ i ] = FLOATSIGNBITSET( d ); + } + + // test triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + i0 = edgeIndexes[i+0]; + i1 = edgeIndexes[i+1]; + i2 = edgeIndexes[i+2]; + s0 = sidedness[abs(i0)] ^ INTSIGNBITSET( i0 ); + s1 = sidedness[abs(i1)] ^ INTSIGNBITSET( i1 ); + s2 = sidedness[abs(i2)] ^ INTSIGNBITSET( i2 ); + + if ( s0 & s1 & s2 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } else if ( !backFaceCull && !(s0 | s1 | s2) ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } + } + + if ( idMath::Fabs( scale ) < idMath::INFINITY ) { + return true; + } + return false; +} + +/* +================= +idSurface::GenerateEdgeIndexes + + Assumes each edge is shared by at most two triangles. +================= +*/ +void idSurface::GenerateEdgeIndexes( void ) { + int i, j, i0, i1, i2, s, v0, v1, edgeNum; + int *index, *vertexEdges, *edgeChain; + surfaceEdge_t e[3]; + + vertexEdges = (int *) _alloca16( verts.Num() * sizeof( int ) ); + memset( vertexEdges, -1, verts.Num() * sizeof( int ) ); + edgeChain = (int *) _alloca16( indexes.Num() * sizeof( int ) ); + + edgeIndexes.SetNum( indexes.Num(), true ); + + edges.Clear(); + + // the first edge is a dummy + e[0].verts[0] = e[0].verts[1] = e[0].tris[0] = e[0].tris[1] = 0; + edges.Append( e[0] ); + + for ( i = 0; i < indexes.Num(); i += 3 ) { + index = indexes.Ptr() + i; + // vertex numbers + i0 = index[0]; + i1 = index[1]; + i2 = index[2]; + // setup edges each with smallest vertex number first + s = INTSIGNBITSET(i1 - i0); + e[0].verts[0] = index[s]; + e[0].verts[1] = index[s^1]; + s = INTSIGNBITSET(i2 - i1) + 1; + e[1].verts[0] = index[s]; + e[1].verts[1] = index[s^3]; + s = INTSIGNBITSET(i2 - i0) << 1; + e[2].verts[0] = index[s]; + e[2].verts[1] = index[s^2]; + // get edges + for ( j = 0; j < 3; j++ ) { + v0 = e[j].verts[0]; + v1 = e[j].verts[1]; + for ( edgeNum = vertexEdges[v0]; edgeNum >= 0; edgeNum = edgeChain[edgeNum] ) { + if ( edges[edgeNum].verts[1] == v1 ) { + break; + } + } + // if the edge does not yet exist + if ( edgeNum < 0 ) { + e[j].tris[0] = e[j].tris[1] = -1; + edgeNum = edges.Append( e[j] ); + edgeChain[edgeNum] = vertexEdges[v0]; + vertexEdges[v0] = edgeNum; + } + // update edge index and edge tri references + if ( index[j] == v0 ) { + assert( edges[edgeNum].tris[0] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[0] = i; + edgeIndexes[i+j] = edgeNum; + } else { + assert( edges[edgeNum].tris[1] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[1] = i; + edgeIndexes[i+j] = -edgeNum; + } + } + } +} + +/* +================= +idSurface::FindEdge +================= +*/ +int idSurface::FindEdge( int v1, int v2 ) const { + int i, firstVert, secondVert; + + if ( v1 < v2 ) { + firstVert = v1; + secondVert = v2; + } else { + firstVert = v2; + secondVert = v1; + } + for ( i = 1; i < edges.Num(); i++ ) { + if ( edges[i].verts[0] == firstVert ) { + if ( edges[i].verts[1] == secondVert ) { + break; + } + } + } + if ( i < edges.Num() ) { + return v1 < v2 ? i : -i; + } + return 0; +} diff --git a/source/idlib/geometry/Surface.h b/source/idlib/geometry/Surface.h new file mode 100644 index 0000000..7cb0891 --- /dev/null +++ b/source/idlib/geometry/Surface.h @@ -0,0 +1,206 @@ + +#ifndef __SURFACE_H__ +#define __SURFACE_H__ + +/* +=============================================================================== + + Surface base class. + + A surface is tesselated to a triangle mesh with each edge shared by + at most two triangles. + +=============================================================================== +*/ + +typedef struct surfaceEdge_s { + int verts[2]; // edge vertices always with ( verts[0] < verts[1] ) + int tris[2]; // edge triangles +} surfaceEdge_t; + + +class idSurface { +public: + idSurface( void ); + explicit idSurface( const idSurface &surf ); + explicit idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + ~idSurface( void ); + + const idDrawVert & operator[]( const int index ) const; + idDrawVert & operator[]( const int index ); + idSurface & operator+=( const idSurface &surf ); + + int GetNumIndexes( void ) const { return indexes.Num(); } + const int * GetIndexes( void ) const { return indexes.Ptr(); } + int GetNumVertices( void ) const { return verts.Num(); } + const idDrawVert * GetVertices( void ) const { return verts.Ptr(); } + const int * GetEdgeIndexes( void ) const { return edgeIndexes.Ptr(); } + const surfaceEdge_t * GetEdges( void ) const { return edges.Ptr(); } + + void Clear( void ); + void SwapTriangles( idSurface &surf ); + void TranslateSelf( const idVec3 &translation ); + void RotateSelf( const idMat3 &rotation ); + + // splits the surface into a front and back surface, the surface itself stays unchanged + // frontOnPlaneEdges and backOnPlaneEdges optionally store the indexes to the edges that lay on the split plane + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges = NULL, int *backOnPlaneEdges = NULL ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns true if each triangle can be reached from any other triangle by a traversal + bool IsConnected( void ) const; + // returns true if the surface is closed + bool IsClosed( void ) const; + // returns true if the surface is a convex hull + bool IsPolytope( const float epsilon = 0.1f ) const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + // returns true if the line intersects one of the surface triangles + bool LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + +protected: + idList verts; // vertices + idList indexes; // 3 references to vertices for each triangle + idList edges; // edges + idList edgeIndexes; // 3 references to edges for each triangle, may be negative for reversed edge + +protected: + void GenerateEdgeIndexes( void ); + int FindEdge( int v1, int v2 ) const; +}; + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface( void ) { +} + +/* +================= +idSurface::idSurface +================= +*/ +ID_INLINE idSurface::idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + assert( verts != NULL && indexes != NULL && numVerts > 0 && numIndexes > 0 ); + this->verts.SetNum( numVerts ); + memcpy( this->verts.Ptr(), verts, numVerts * sizeof( verts[0] ) ); + this->indexes.SetNum( numIndexes ); + memcpy( this->indexes.Ptr(), indexes, numIndexes * sizeof( indexes[0] ) ); + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface( const idSurface &surf ) { + this->verts = surf.verts; + this->indexes = surf.indexes; + this->edges = surf.edges; + this->edgeIndexes = surf.edgeIndexes; +} + +/* +==================== +idSurface::~idSurface +==================== +*/ +ID_INLINE idSurface::~idSurface( void ) { +} + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE const idDrawVert &idSurface::operator[]( const int index ) const { + return verts[ index ]; +}; + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE idDrawVert &idSurface::operator[]( const int index ) { + return verts[ index ]; +}; + +/* +================= +idSurface::operator+= +================= +*/ +ID_INLINE idSurface &idSurface::operator+=( const idSurface &surf ) { + int i, m, n; + n = verts.Num(); + m = indexes.Num(); + verts.Append( surf.verts ); // merge verts where possible ? + indexes.Append( surf.indexes ); + for ( i = m; i < indexes.Num(); i++ ) { + indexes[i] += n; + } + GenerateEdgeIndexes(); + return *this; +} + +/* +================= +idSurface::Clear +================= +*/ +ID_INLINE void idSurface::Clear( void ) { + verts.Clear(); + indexes.Clear(); + edges.Clear(); + edgeIndexes.Clear(); +} + +/* +================= +idSurface::SwapTriangles +================= +*/ +ID_INLINE void idSurface::SwapTriangles( idSurface &surf ) { + verts.Swap( surf.verts ); + indexes.Swap( surf.indexes ); + edges.Swap( surf.edges ); + edgeIndexes.Swap( surf.edgeIndexes ); +} + +/* +================= +idSurface::TranslateSelf +================= +*/ +ID_INLINE void idSurface::TranslateSelf( const idVec3 &translation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz += translation; + } +} + +/* +================= +idSurface::RotateSelf +================= +*/ +ID_INLINE void idSurface::RotateSelf( const idMat3 &rotation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz *= rotation; + verts[i].normal *= rotation; + verts[i].tangents[0] *= rotation; + verts[i].tangents[1] *= rotation; + } +} + +#endif /* !__SURFACE_H__ */ diff --git a/source/idlib/geometry/Surface_Patch.cpp b/source/idlib/geometry/Surface_Patch.cpp new file mode 100644 index 0000000..39314e1 --- /dev/null +++ b/source/idlib/geometry/Surface_Patch.cpp @@ -0,0 +1,664 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +================= +idSurface_Patch::SetSize +================= +*/ +void idSurface_Patch::SetSize( int patchWidth, int patchHeight ) { + if ( patchWidth < 1 || patchWidth > maxWidth ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchWidth"); + } + if ( patchHeight < 1 || patchHeight > maxHeight ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchHeight"); + } + width = patchWidth; + height = patchHeight; + verts.SetNum( width * height, false ); +} + +/* +================= +idSurface_Patch::PutOnCurve + +Expects an expanded patch. +================= +*/ +void idSurface_Patch::PutOnCurve( void ) { + int i, j; + idDrawVert prev, next; + + assert( expanded == true ); + // put all the approximating points on the curve + for ( i = 0; i < width; i++ ) { + for ( j = 1; j < height; j += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[(j+1)*maxWidth+i], prev ); + LerpVert( verts[j*maxWidth+i], verts[(j-1)*maxWidth+i], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } + + for ( j = 0; j < height; j++ ) { + for ( i = 1; i < width; i += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i+1], prev ); + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i-1], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } +} + +/* +================ +idSurface_Patch::ProjectPointOntoVector +================ +*/ +void idSurface_Patch::ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ) { + idVec3 pVec, vec; + + pVec = point - vStart; + vec = vEnd - vStart; + vec.Normalize(); + // project onto the directional vector for this segment + vProj = vStart + (pVec * vec) * vec; +} + +/* +================ +idSurface_Patch::RemoveLinearColumnsRows + +Expects an expanded patch. +================ +*/ +void idSurface_Patch::RemoveLinearColumnsRows( void ) { + int i, j, k; + float len, maxLength; + idVec3 proj, dir; + + assert( expanded == true ); + for ( j = 1; j < width - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < height; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[i*maxWidth + j].xyz, + verts[i*maxWidth + j-1].xyz, verts[i*maxWidth + j+1].xyz, proj); + dir = verts[i*maxWidth + j].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + width--; + for ( i = 0; i < height; i++ ) { + for ( k = j; k < width; k++ ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k+1]; + } + } + j--; + } + } + for ( j = 1; j < height - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < width; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[j*maxWidth + i].xyz, + verts[(j-1)*maxWidth + i].xyz, verts[(j+1)*maxWidth + i].xyz, proj); + dir = verts[j*maxWidth + i].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + height--; + for ( i = 0; i < width; i++ ) { + for ( k = j; k < height; k++ ) { + verts[k*maxWidth + i] = verts[(k+1)*maxWidth + i]; + } + } + j--; + } + } +} + +/* +================ +idSurface_Patch::ResizeExpanded +================ +*/ +void idSurface_Patch::ResizeExpanded( int newHeight, int newWidth ) { + int i, j; + + assert( expanded == true ); + if ( newHeight <= maxHeight && newWidth <= maxWidth ) { + return; + } + if ( newHeight * newWidth > maxHeight * maxWidth ) { + verts.SetNum( newHeight * newWidth ); + } + // space out verts for new height and width + for ( j = maxHeight-1; j >= 0; j-- ) { + for ( i = maxWidth-1; i >= 0; i-- ) { + verts[j*newWidth + i] = verts[j*maxWidth + i]; + } + } + maxHeight = newHeight; + maxWidth = newWidth; +} + +/* +================ +idSurface_Patch::Collapse +================ +*/ +void idSurface_Patch::Collapse( void ) { + int i, j; + + if ( !expanded ) { + idLib::common->FatalError("idSurface_Patch::Collapse: patch not expanded"); + } + expanded = false; + if ( width != maxWidth ) { + for ( j = 0; j < height; j++ ) { + for ( i = 0; i < width; i++ ) { + verts[j*width + i] = verts[j*maxWidth + i]; + } + } + } + verts.SetNum( width * height, false ); +} + +/* +================ +idSurface_Patch::Expand +================ +*/ +void idSurface_Patch::Expand( void ) { + int i, j; + + if ( expanded ) { + idLib::common->FatalError("idSurface_Patch::Expand: patch alread expanded"); + } + expanded = true; + verts.SetNum( maxWidth * maxHeight, false ); + if ( width != maxWidth ) { + for ( j = height-1; j >= 0; j-- ) { + for ( i = width-1; i >= 0; i-- ) { + verts[j*maxWidth + i] = verts[j*width + i]; + } + } + } +} + +/* +============ +idSurface_Patch::LerpVert +============ +*/ +void idSurface_Patch::LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const { + out.xyz[0] = 0.5f * ( a.xyz[0] + b.xyz[0] ); + out.xyz[1] = 0.5f * ( a.xyz[1] + b.xyz[1] ); + out.xyz[2] = 0.5f * ( a.xyz[2] + b.xyz[2] ); + out.normal[0] = 0.5f * ( a.normal[0] + b.normal[0] ); + out.normal[1] = 0.5f * ( a.normal[1] + b.normal[1] ); + out.normal[2] = 0.5f * ( a.normal[2] + b.normal[2] ); + out.st[0] = 0.5f * ( a.st[0] + b.st[0] ); + out.st[1] = 0.5f * ( a.st[1] + b.st[1] ); +} + +/* +================= +idSurface_Patch::GenerateNormals + +Handles all the complicated wrapping and degenerate cases +Expects a Not expanded patch. +================= +*/ +#define COPLANAR_EPSILON 0.1f + +void idSurface_Patch::GenerateNormals( void ) { + int i, j, k, dist; + idVec3 norm; + idVec3 sum; + int count; + idVec3 base; + idVec3 delta; + int x, y; + idVec3 around[8], temp; + bool good[8]; + bool wrapWidth, wrapHeight; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + assert( expanded == false ); + + // + // if all points are coplanar, set all normals to that plane + // + idVec3 extent[3]; + float offset; + + extent[0] = verts[width - 1].xyz - verts[0].xyz; + extent[1] = verts[(height-1) * width + width - 1].xyz - verts[0].xyz; + extent[2] = verts[(height-1) * width].xyz - verts[0].xyz; + + norm = extent[0].Cross( extent[1] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[0].Cross( extent[2] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[1].Cross( extent[2] ); + } + } + + // wrapped patched may not get a valid normal here + if ( norm.Normalize() != 0.0f ) { + + offset = verts[0].xyz * norm; + for ( i = 1; i < width * height; i++ ) { + float d = verts[i].xyz * norm; + if ( idMath::Fabs( d - offset ) > COPLANAR_EPSILON ) { + break; + } + } + + if ( i == width * height ) { + // all are coplanar + for ( i = 0; i < width * height; i++ ) { + verts[i].normal = norm; + } + return; + } + } + + // check for wrapped edge cases, which should smooth across themselves + wrapWidth = false; + for ( i = 0; i < height; i++ ) { + delta = verts[i * width].xyz - verts[i * width + width-1].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == height ) { + wrapWidth = true; + } + + wrapHeight = false; + for ( i = 0; i < width; i++ ) { + delta = verts[i].xyz - verts[(height-1) * width + i].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == width ) { + wrapHeight = true; + } + + for ( i = 0; i < width; i++ ) { + for ( j = 0; j < height; j++ ) { + count = 0; + base = verts[j * width + i].xyz; + for ( k = 0; k < 8; k++ ) { + around[k] = vec3_origin; + good[k] = false; + + for ( dist = 1; dist <= 3; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + temp = verts[y * width + x].xyz - base; + if ( temp.Normalize() == 0.0f ) { + continue; // degenerate edge, get more dist + } else { + good[k] = true; + around[k] = temp; + break; // good edge + } + } + } + + sum = vec3_origin; + for ( k = 0; k < 8; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + norm = around[(k+1)&7].Cross( around[k] ); + if ( norm.Normalize() == 0.0f ) { + continue; + } + sum += norm; + count++; + } + if ( count == 0 ) { + //idLib::common->Printf("bad normal\n"); + count = 1; + } + verts[j * width + i].normal = sum; + verts[j * width + i].normal.Normalize(); + } + } +} + +/* +================= +idSurface_Patch::GenerateIndexes +================= +*/ +void idSurface_Patch::GenerateIndexes( void ) { + int i, j, v1, v2, v3, v4, index; + + indexes.SetNum( (width-1) * (height-1) * 2 * 3, false ); + index = 0; + for ( i = 0; i < width - 1; i++ ) { + for ( j = 0; j < height - 1; j++ ) { + v1 = j * width + i; + v2 = v1 + 1; + v3 = v1 + width + 1; + v4 = v1 + width; + indexes[index++] = v1; + indexes[index++] = v3; + indexes[index++] = v2; + indexes[index++] = v1; + indexes[index++] = v4; + indexes[index++] = v3; + } + } + + GenerateEdgeIndexes(); +} + +/* +=============== +idSurface_Patch::SampleSinglePatchPoint +=============== +*/ +void idSurface_Patch::SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const { + float vCtrl[3][8]; + int vPoint; + int axis; + + // find the control points for the v coordinate + for ( vPoint = 0; vPoint < 3; vPoint++ ) { + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + if ( axis < 3 ) { + a = ctrl[0][vPoint].xyz[axis]; + b = ctrl[1][vPoint].xyz[axis]; + c = ctrl[2][vPoint].xyz[axis]; + } else if ( axis < 6 ) { + a = ctrl[0][vPoint].normal[axis-3]; + b = ctrl[1][vPoint].normal[axis-3]; + c = ctrl[2][vPoint].normal[axis-3]; + } else { + a = ctrl[0][vPoint].st[axis-6]; + b = ctrl[1][vPoint].st[axis-6]; + c = ctrl[2][vPoint].st[axis-6]; + } + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + vCtrl[vPoint][axis] = qA * u * u + qB * u + qC; + } + } + + // interpolate the v value + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + + a = vCtrl[0][axis]; + b = vCtrl[1][axis]; + c = vCtrl[2][axis]; + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + + if ( axis < 3 ) { + out->xyz[axis] = qA * v * v + qB * v + qC; + } else if ( axis < 6 ) { + out->normal[axis-3] = qA * v * v + qB * v + qC; + } else { + out->st[axis-6] = qA * v * v + qB * v + qC; + } + } +} + +/* +=================== +idSurface_Patch::SampleSinglePatch +=================== +*/ +void idSurface_Patch::SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const { + int i, j; + float u, v; + + horzSub++; + vertSub++; + for ( i = 0; i < horzSub; i++ ) { + for ( j = 0; j < vertSub; j++ ) { + u = (float) i / ( horzSub - 1 ); + v = (float) j / ( vertSub - 1 ); + SampleSinglePatchPoint( ctrl, u, v, &outVerts[((baseRow + j) * width) + i + baseCol] ); + } + } +} + +/* +================= +idSurface_Patch::SubdivideExplicit +================= +*/ +void idSurface_Patch::SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear ) { + int i, j, k, l; + idDrawVert sample[3][3]; + int outWidth = ((width - 1) / 2 * horzSubdivisions) + 1; + int outHeight = ((height - 1) / 2 * vertSubdivisions) + 1; + idDrawVert *dv = new idDrawVert[ outWidth * outHeight ]; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + int baseCol = 0; + for ( i = 0; i + 2 < width; i += 2 ) { + int baseRow = 0; + for ( j = 0; j + 2 < height; j += 2 ) { + for ( k = 0; k < 3; k++ ) { + for ( l = 0; l < 3; l++ ) { + sample[k][l] = verts[ ((j + l) * width) + i + k ]; + } + } + SampleSinglePatch( sample, baseCol, baseRow, outWidth, horzSubdivisions, vertSubdivisions, dv ); + baseRow += vertSubdivisions; + } + baseCol += horzSubdivisions; + } + verts.SetNum( outWidth * outHeight ); + for ( i = 0; i < outWidth * outHeight; i++ ) { + verts[i] = dv[i]; + } + + delete[] dv; + + width = maxWidth = outWidth; + height = maxHeight = outHeight; + expanded = false; + + if ( removeLinear ) { + Expand(); + RemoveLinearColumnsRows(); + Collapse(); + } + + // normalize all the lerped normals + if ( genNormals ) { + for ( i = 0; i < width * height; i++ ) { + verts[i].normal.Normalize(); + } + } + + GenerateIndexes(); +} + +/* +================= +idSurface_Patch::Subdivide +================= +*/ +void idSurface_Patch::Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals ) { + int i, j, k, l; + idDrawVert prev, next, mid; + idVec3 prevxyz, nextxyz, midxyz; + idVec3 delta; + float maxHorizontalErrorSqr, maxVerticalErrorSqr, maxLengthSqr; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + maxHorizontalErrorSqr = Square( maxHorizontalError ); + maxVerticalErrorSqr = Square( maxVerticalError ); + maxLengthSqr = Square( maxLength ); + + Expand(); + + // horizontal subdivisions + for ( j = 0; j + 2 < width; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < height; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[i*maxWidth + j+1].xyz[l] - verts[i*maxWidth + j ].xyz[l]; + nextxyz[l] = verts[i*maxWidth + j+2].xyz[l] - verts[i*maxWidth + j+1].xyz[l]; + midxyz[l] = (verts[i*maxWidth + j ].xyz[l] + verts[i*maxWidth + j+1].xyz[l] * 2.0f + + verts[i*maxWidth + j+2].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[i*maxWidth + j+1].xyz - midxyz; + if ( delta.LengthSqr() > maxHorizontalErrorSqr ) { + break; + } + } + + if ( i == height ) { + continue; // didn't need subdivision + } + + if ( width + 2 >= maxWidth ) { + ResizeExpanded( maxHeight, maxWidth + 4 ); + } + + // insert two columns and replace the peak + width += 2; + + for ( i = 0; i < height; i++ ) { + idSurface_Patch::LerpVert( verts[i*maxWidth + j ], verts[i*maxWidth + j+1], prev ); + idSurface_Patch::LerpVert( verts[i*maxWidth + j+1], verts[i*maxWidth + j+2], next ); + idSurface_Patch::LerpVert( prev, next, mid ); + + for ( k = width - 1; k > j + 3; k-- ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k-2]; + } + verts[i*maxWidth + j+1] = prev; + verts[i*maxWidth + j+2] = mid; + verts[i*maxWidth + j+3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + // vertical subdivisions + for ( j = 0; j + 2 < height; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < width; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[(j+1)*maxWidth + i].xyz[l] - verts[j*maxWidth + i].xyz[l]; + nextxyz[l] = verts[(j+2)*maxWidth + i].xyz[l] - verts[(j+1)*maxWidth + i].xyz[l]; + midxyz[l] = (verts[j*maxWidth + i].xyz[l] + verts[(j+1)*maxWidth + i].xyz[l] * 2.0f + + verts[(j+2)*maxWidth + i].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[(j+1)*maxWidth + i].xyz - midxyz; + if ( delta.LengthSqr() > maxVerticalErrorSqr ) { + break; + } + } + + if ( i == width ) { + continue; // didn't need subdivision + } + + if ( height + 2 >= maxHeight ) { + ResizeExpanded( maxHeight + 4, maxWidth ); + } + + // insert two columns and replace the peak + height += 2; + + for ( i = 0; i < width; i++ ) { + LerpVert( verts[j*maxWidth + i], verts[(j+1)*maxWidth + i], prev ); + LerpVert( verts[(j+1)*maxWidth + i], verts[(j+2)*maxWidth + i], next ); + LerpVert( prev, next, mid ); + + for ( k = height - 1; k > j + 3; k-- ) { + verts[k*maxWidth + i] = verts[(k-2)*maxWidth + i]; + } + verts[(j+1)*maxWidth + i] = prev; + verts[(j+2)*maxWidth + i] = mid; + verts[(j+3)*maxWidth + i] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + PutOnCurve(); + + RemoveLinearColumnsRows(); + + Collapse(); + + // normalize all the lerped normals + if ( genNormals ) { + for ( i = 0; i < width * height; i++ ) { + verts[i].normal.Normalize(); + } + } + + GenerateIndexes(); +} diff --git a/source/idlib/geometry/Surface_Patch.h b/source/idlib/geometry/Surface_Patch.h new file mode 100644 index 0000000..78e70df --- /dev/null +++ b/source/idlib/geometry/Surface_Patch.h @@ -0,0 +1,119 @@ + +#ifndef __SURFACE_PATCH_H__ +#define __SURFACE_PATCH_H__ + +/* +=============================================================================== + + Bezier patch surface. + +=============================================================================== +*/ + +class idSurface_Patch : public idSurface { + +public: + idSurface_Patch( void ); + idSurface_Patch( int maxPatchWidth, int maxPatchHeight ); + idSurface_Patch( const idSurface_Patch &patch ); + ~idSurface_Patch( void ); + + void SetSize( int patchWidth, int patchHeight ); + int GetWidth( void ) const; + int GetHeight( void ) const; + + // subdivide the patch mesh based on error + void Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals = false ); + // subdivide the patch up to an explicit number of horizontal and vertical subdivisions + void SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear = false ); + +protected: + int width; // width of patch + int height; // height of patch + int maxWidth; // maximum width allocated for + int maxHeight; // maximum height allocated for + bool expanded; // true if vertices are spaced out + +private: + // put the approximation points on the curve + void PutOnCurve( void ); + // remove columns and rows with all points on one line + void RemoveLinearColumnsRows( void ); + // resize verts buffer + void ResizeExpanded( int height, int width ); + // space points out over maxWidth * maxHeight buffer + void Expand( void ); + // move all points to the start of the verts buffer + void Collapse( void ); + // project a point onto a vector to calculate maximum curve error + void ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ); + // generate normals + void GenerateNormals( void ); + // generate triangle indexes + void GenerateIndexes( void ); + // lerp point from two patch point + void LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const; + // sample a single 3x3 patch + void SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const; + void SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const; +}; + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( void ) { + height = width = maxHeight = maxWidth = 0; + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( int maxPatchWidth, int maxPatchHeight ) { + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( const idSurface_Patch &patch ) { + (*this) = patch; +} + +/* +================= +idSurface_Patch::~idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::~idSurface_Patch() { +} + +/* +================= +idSurface_Patch::GetWidth +================= +*/ +ID_INLINE int idSurface_Patch::GetWidth( void ) const { + return width; +} + +/* +================= +idSurface_Patch::GetHeight +================= +*/ +ID_INLINE int idSurface_Patch::GetHeight( void ) const { + return height; +} + +#endif /* !__SURFACE_PATCH_H__ */ diff --git a/source/idlib/geometry/Surface_Polytope.cpp b/source/idlib/geometry/Surface_Polytope.cpp new file mode 100644 index 0000000..6d3a306 --- /dev/null +++ b/source/idlib/geometry/Surface_Polytope.cpp @@ -0,0 +1,309 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#define POLYTOPE_VERTEX_EPSILON 0.1f + + +/* +==================== +idSurface_Polytope::FromPlanes +==================== +*/ +void idSurface_Polytope::FromPlanes( const idPlane *planes, const int numPlanes ) { + int i, j, k, *windingVerts; + idFixedWinding w; + idDrawVert newVert; + + windingVerts = (int *) _alloca( MAX_POINTS_ON_WINDING * sizeof( int ) ); + memset( &newVert, 0, sizeof( newVert ) ); + + for ( i = 0; i < numPlanes; i++ ) { + + w.BaseForPlane( planes[i] ); + + for ( j = 0; j < numPlanes; j++ ) { + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( -planes[j], ON_EPSILON, true ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; + } + + for ( j = 0; j < w.GetNumPoints(); j++ ) { + for ( k = 0; k < verts.Num(); j++ ) { + if ( verts[k].xyz.Compare( w[j].ToVec3(), POLYTOPE_VERTEX_EPSILON ) ) { + break; + } + } + if ( k >= verts.Num() ) { + newVert.xyz = w[j].ToVec3(); + k = verts.Append( newVert ); + } + windingVerts[j] = k; + } + + for ( j = 2; j < w.GetNumPoints(); j++ ) { + indexes.Append( windingVerts[0] ); + indexes.Append( windingVerts[j-1] ); + indexes.Append( windingVerts[j] ); + } + } + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupTetrahedron +==================== +*/ +void idSurface_Polytope::SetupTetrahedron( const idBounds &bounds ) { + idVec3 center, scale; + float c1, c2, c3; + + c1 = 0.4714045207f; + c2 = 0.8164965809f; + c3 = -0.3333333333f; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 4 ); + verts[0].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[1].xyz = center + idVec3( 2.0f * c1 * scale.x, 0.0f, c3 * scale.z ); + verts[2].xyz = center + idVec3( -c1 * scale.x, c2 * scale.y, c3 * scale.z ); + verts[3].xyz = center + idVec3( -c1 * scale.x, -c2 * scale.y, c3 * scale.z ); + + indexes.SetNum( 4*3 ); + indexes[0*3+0] = 0; + indexes[0*3+1] = 1; + indexes[0*3+2] = 2; + indexes[1*3+0] = 0; + indexes[1*3+1] = 2; + indexes[1*3+2] = 3; + indexes[2*3+0] = 0; + indexes[2*3+1] = 3; + indexes[2*3+2] = 1; + indexes[3*3+0] = 1; + indexes[3*3+1] = 3; + indexes[3*3+2] = 2; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupHexahedron +==================== +*/ +void idSurface_Polytope::SetupHexahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 8 ); + verts[0].xyz = center + idVec3( -scale.x, -scale.y, -scale.z ); + verts[1].xyz = center + idVec3( scale.x, -scale.y, -scale.z ); + verts[2].xyz = center + idVec3( scale.x, scale.y, -scale.z ); + verts[3].xyz = center + idVec3( -scale.x, scale.y, -scale.z ); + verts[4].xyz = center + idVec3( -scale.x, -scale.y, scale.z ); + verts[5].xyz = center + idVec3( scale.x, -scale.y, scale.z ); + verts[6].xyz = center + idVec3( scale.x, scale.y, scale.z ); + verts[7].xyz = center + idVec3( -scale.x, scale.y, scale.z ); + + indexes.SetNum( 12*3 ); + indexes[ 0*3+0] = 0; + indexes[ 0*3+1] = 3; + indexes[ 0*3+2] = 2; + indexes[ 1*3+0] = 0; + indexes[ 1*3+1] = 2; + indexes[ 1*3+2] = 1; + indexes[ 2*3+0] = 0; + indexes[ 2*3+1] = 1; + indexes[ 2*3+2] = 5; + indexes[ 3*3+0] = 0; + indexes[ 3*3+1] = 5; + indexes[ 3*3+2] = 4; + indexes[ 4*3+0] = 0; + indexes[ 4*3+1] = 4; + indexes[ 4*3+2] = 7; + indexes[ 5*3+0] = 0; + indexes[ 5*3+1] = 7; + indexes[ 5*3+2] = 3; + indexes[ 6*3+0] = 6; + indexes[ 6*3+1] = 5; + indexes[ 6*3+2] = 1; + indexes[ 7*3+0] = 6; + indexes[ 7*3+1] = 1; + indexes[ 7*3+2] = 2; + indexes[ 8*3+0] = 6; + indexes[ 8*3+1] = 2; + indexes[ 8*3+2] = 3; + indexes[ 9*3+0] = 6; + indexes[ 9*3+1] = 3; + indexes[ 9*3+2] = 7; + indexes[10*3+0] = 6; + indexes[10*3+1] = 7; + indexes[10*3+2] = 4; + indexes[11*3+0] = 6; + indexes[11*3+1] = 4; + indexes[11*3+2] = 5; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupOctahedron +==================== +*/ +void idSurface_Polytope::SetupOctahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 6 ); + verts[0].xyz = center + idVec3( scale.x, 0.0f, 0.0f ); + verts[1].xyz = center + idVec3( -scale.x, 0.0f, 0.0f ); + verts[2].xyz = center + idVec3( 0.0f, scale.y, 0.0f ); + verts[3].xyz = center + idVec3( 0.0f, -scale.y, 0.0f ); + verts[4].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[5].xyz = center + idVec3( 0.0f, 0.0f, -scale.z ); + + indexes.SetNum( 8*3 ); + indexes[0*3+0] = 4; + indexes[0*3+1] = 0; + indexes[0*3+2] = 2; + indexes[1*3+0] = 4; + indexes[1*3+1] = 2; + indexes[1*3+2] = 1; + indexes[2*3+0] = 4; + indexes[2*3+1] = 1; + indexes[2*3+2] = 3; + indexes[3*3+0] = 4; + indexes[3*3+1] = 3; + indexes[3*3+2] = 0; + indexes[4*3+0] = 5; + indexes[4*3+1] = 2; + indexes[4*3+2] = 0; + indexes[5*3+0] = 5; + indexes[5*3+1] = 1; + indexes[5*3+2] = 2; + indexes[6*3+0] = 5; + indexes[6*3+1] = 3; + indexes[6*3+2] = 1; + indexes[7*3+0] = 5; + indexes[7*3+1] = 0; + indexes[7*3+2] = 3; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupDodecahedron +==================== +*/ +void idSurface_Polytope::SetupDodecahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupIcosahedron +==================== +*/ +void idSurface_Polytope::SetupIcosahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupCylinder +==================== +*/ +void idSurface_Polytope::SetupCylinder( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SetupCone +==================== +*/ +void idSurface_Polytope::SetupCone( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SplitPolytope +==================== +*/ +int idSurface_Polytope::SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const { + int side, i, j, s, v0, v1, v2, edgeNum; + idSurface *surface[2]; + idSurface_Polytope *polytopeSurfaces[2], *surf; + int *onPlaneEdges[2]; + + onPlaneEdges[0] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + + side = Split( plane, epsilon, &surface[0], &surface[1], onPlaneEdges[0], onPlaneEdges[1] ); + + *front = polytopeSurfaces[0] = new idSurface_Polytope; + *back = polytopeSurfaces[1] = new idSurface_Polytope; + + for ( s = 0; s < 2; s++ ) { + if ( surface[s] ) { + polytopeSurfaces[s] = new idSurface_Polytope; + polytopeSurfaces[s]->SwapTriangles( *surface[s] ); + delete surface[s]; + surface[s] = NULL; + } + } + + *front = polytopeSurfaces[0]; + *back = polytopeSurfaces[1]; + + if ( side != SIDE_CROSS ) { + return side; + } + + // add triangles to close off the front and back polytope + for ( s = 0; s < 2; s++ ) { + + surf = polytopeSurfaces[s]; + + edgeNum = surf->edgeIndexes[onPlaneEdges[s][0]]; + v0 = surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)]; + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + + for ( i = 1; onPlaneEdges[s][i] >= 0; i++ ) { + for ( j = i+1; onPlaneEdges[s][j] >= 0; j++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][j]]; + if ( v1 == surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)] ) { + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + idSwap( onPlaneEdges[s][i], onPlaneEdges[s][j] ); + break; + } + } + } + + for ( i = 2; onPlaneEdges[s][i] >= 0; i++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][i]]; + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + v2 = surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)]; + surf->indexes.Append( v0 ); + surf->indexes.Append( v1 ); + surf->indexes.Append( v2 ); + } + + surf->GenerateEdgeIndexes(); + } + + return side; +} diff --git a/source/idlib/geometry/Surface_Polytope.h b/source/idlib/geometry/Surface_Polytope.h new file mode 100644 index 0000000..ba93fdc --- /dev/null +++ b/source/idlib/geometry/Surface_Polytope.h @@ -0,0 +1,43 @@ + +#ifndef __SURFACE_POLYTOPE_H__ +#define __SURFACE_POLYTOPE_H__ + +/* +=============================================================================== + + Polytope surface. + + NOTE: vertexes are not duplicated for texture coordinates. + +=============================================================================== +*/ + +class idSurface_Polytope : public idSurface { +public: + idSurface_Polytope( void ); + + void FromPlanes( const idPlane *planes, const int numPlanes ); + + void SetupTetrahedron( const idBounds &bounds ); + void SetupHexahedron( const idBounds &bounds ); + void SetupOctahedron( const idBounds &bounds ); + void SetupDodecahedron( const idBounds &bounds ); + void SetupIcosahedron( const idBounds &bounds ); + void SetupCylinder( const idBounds &bounds, const int numSides ); + void SetupCone( const idBounds &bounds, const int numSides ); + + int SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const; + +protected: + +}; + +/* +==================== +idSurface_Polytope::idSurface_Polytope +==================== +*/ +ID_INLINE idSurface_Polytope::idSurface_Polytope( void ) { +} + +#endif /* !__SURFACE_POLYTOPE_H__ */ diff --git a/source/idlib/geometry/Surface_SweptSpline.cpp b/source/idlib/geometry/Surface_SweptSpline.cpp new file mode 100644 index 0000000..397c390 --- /dev/null +++ b/source/idlib/geometry/Surface_SweptSpline.cpp @@ -0,0 +1,196 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +==================== +idSurface_SweptSpline::SetSpline +==================== +*/ +void idSurface_SweptSpline::SetSpline( idCurve_Spline *spline ) { + if ( this->spline ) { + delete this->spline; + } + this->spline = spline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptSpline +==================== +*/ +void idSurface_SweptSpline::SetSweptSpline( idCurve_Spline *sweptSpline ) { + if ( this->sweptSpline ) { + delete this->sweptSpline; + } + this->sweptSpline = sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptCircle + + Sets the swept spline to a NURBS circle. +==================== +*/ +void idSurface_SweptSpline::SetSweptCircle( const float radius ) { + idCurve_NURBS *nurbs = new idCurve_NURBS(); + nurbs->Clear(); + nurbs->AddValue( 0.0f, idVec4( radius, radius, 0.0f, 0.00f ) ); + nurbs->AddValue( 100.0f, idVec4( -radius, radius, 0.0f, 0.25f ) ); + nurbs->AddValue( 200.0f, idVec4( -radius, -radius, 0.0f, 0.50f ) ); + nurbs->AddValue( 300.0f, idVec4( radius, -radius, 0.0f, 0.75f ) ); + nurbs->SetBoundaryType( idCurve_NURBS::BT_CLOSED ); + nurbs->SetCloseTime( 100.0f ); + if ( sweptSpline ) { + delete sweptSpline; + } + sweptSpline = nurbs; +} + +/* +==================== +idSurface_SweptSpline::GetFrame +==================== +*/ +void idSurface_SweptSpline::GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + idVec3 d, v; + idMat3 axis; + + d = dir; + d.Normalize(); + v = d.Cross( previousFrame[2] ); + v.Normalize(); + + a = idMath::ACos( previousFrame[2] * d ) * 0.5f; + c = idMath::Cos( a ); + s = idMath::Sqrt( 1.0f - c * c ); + + x = v[0] * s; + y = v[1] * s; + z = v[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + xx = x * x2; + xy = x * y2; + xz = x * z2; + yy = y * y2; + yz = y * z2; + zz = z * z2; + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[0][0] = 1.0f - ( yy + zz ); + axis[0][1] = xy - wz; + axis[0][2] = xz + wy; + axis[1][0] = xy + wz; + axis[1][1] = 1.0f - ( xx + zz ); + axis[1][2] = yz - wx; + axis[2][0] = xz - wy; + axis[2][1] = yz + wx; + axis[2][2] = 1.0f - ( xx + yy ); + + newFrame = previousFrame * axis; + + newFrame[2] = dir; + newFrame[2].Normalize(); + newFrame[1].Cross( newFrame[ 2 ], newFrame[ 0 ] ); + newFrame[1].Normalize(); + newFrame[0].Cross( newFrame[ 1 ], newFrame[ 2 ] ); + newFrame[0].Normalize(); +} + +/* +==================== +idSurface_SweptSpline::Tessellate + + tesselate the surface +==================== +*/ +void idSurface_SweptSpline::Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ) { + int i, j, offset, baseOffset, splineDiv, sweptSplineDiv; + int i0, i1, j0, j1; + float totalTime, t; + idVec4 splinePos, splineD1; + idMat3 splineMat; + + if ( !spline || !sweptSpline ) { + idSurface::Clear(); + return; + } + + verts.SetNum( splineSubdivisions * sweptSplineSubdivisions, false ); + + // calculate the points and first derivatives for the swept spline + totalTime = sweptSpline->GetTime( sweptSpline->GetNumValues() - 1 ) - sweptSpline->GetTime( 0 ) + sweptSpline->GetCloseTime(); + sweptSplineDiv = sweptSpline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? sweptSplineSubdivisions : sweptSplineSubdivisions - 1; + baseOffset = (splineSubdivisions-1) * sweptSplineSubdivisions; + for ( i = 0; i < sweptSplineSubdivisions; i++ ) { + t = totalTime * i / sweptSplineDiv; + splinePos = sweptSpline->GetCurrentValue( t ); + splineD1 = sweptSpline->GetCurrentFirstDerivative( t ); + verts[baseOffset+i].xyz = splinePos.ToVec3(); + verts[baseOffset+i].st[0] = splinePos.w; + verts[baseOffset+i].tangents[0] = splineD1.ToVec3(); + } + + // sweep the spline + totalTime = spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) + spline->GetCloseTime(); + splineDiv = spline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? splineSubdivisions : splineSubdivisions - 1; + splineMat.Identity(); + for ( i = 0; i < splineSubdivisions; i++ ) { + t = totalTime * i / splineDiv; + + splinePos = spline->GetCurrentValue( t ); + splineD1 = spline->GetCurrentFirstDerivative( t ); + + GetFrame( splineMat, splineD1.ToVec3(), splineMat ); + + offset = i * sweptSplineSubdivisions; + for ( j = 0; j < sweptSplineSubdivisions; j++ ) { + idDrawVert *v = &verts[offset+j]; + v->xyz = splinePos.ToVec3() + verts[baseOffset+j].xyz * splineMat; + v->st[0] = verts[baseOffset+j].st[0]; + v->st[1] = splinePos.w; + v->tangents[0] = verts[baseOffset+j].tangents[0] * splineMat; + v->tangents[1] = splineD1.ToVec3(); + v->normal = v->tangents[1].Cross( v->tangents[0] ); + v->normal.Normalize(); + v->color[0] = v->color[1] = v->color[2] = v->color[3] = 0; + } + } + + indexes.SetNum( splineDiv * sweptSplineDiv * 2 * 3, false ); + + // create indexes for the triangles + for ( offset = i = 0; i < splineDiv; i++ ) { + + i0 = (i+0) * sweptSplineSubdivisions; + i1 = (i+1) % splineSubdivisions * sweptSplineSubdivisions; + + for ( j = 0; j < sweptSplineDiv; j++ ) { + + j0 = (j+0); + j1 = (j+1) % sweptSplineSubdivisions; + + indexes[offset++] = i0 + j0; + indexes[offset++] = i0 + j1; + indexes[offset++] = i1 + j1; + + indexes[offset++] = i1 + j1; + indexes[offset++] = i1 + j0; + indexes[offset++] = i0 + j0; + } + } + + GenerateEdgeIndexes(); +} diff --git a/source/idlib/geometry/Surface_SweptSpline.h b/source/idlib/geometry/Surface_SweptSpline.h new file mode 100644 index 0000000..e2e06b8 --- /dev/null +++ b/source/idlib/geometry/Surface_SweptSpline.h @@ -0,0 +1,66 @@ + +#ifndef __SURFACE_SWEPTSPLINE_H__ +#define __SURFACE_SWEPTSPLINE_H__ + +/* +=============================================================================== + + Swept Spline surface. + +=============================================================================== +*/ + +class idSurface_SweptSpline : public idSurface { +public: + idSurface_SweptSpline( void ); + ~idSurface_SweptSpline( void ); + + void SetSpline( idCurve_Spline *spline ); + void SetSweptSpline( idCurve_Spline *sweptSpline ); + void SetSweptCircle( const float radius ); + + void Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ); + + void Clear( void ); + +protected: + idCurve_Spline *spline; + idCurve_Spline *sweptSpline; + + void GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ); +}; + +/* +==================== +idSurface_SweptSpline::idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::idSurface_SweptSpline( void ) { + spline = NULL; + sweptSpline = NULL; +} + +/* +==================== +idSurface_SweptSpline::~idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::~idSurface_SweptSpline( void ) { + delete spline; + delete sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::Clear +==================== +*/ +ID_INLINE void idSurface_SweptSpline::Clear( void ) { + idSurface::Clear(); + delete spline; + spline = NULL; + delete sweptSpline; + sweptSpline = NULL; +} + +#endif /* !__SURFACE_SWEPTSPLINE_H__ */ diff --git a/source/idlib/geometry/TraceModel.cpp b/source/idlib/geometry/TraceModel.cpp new file mode 100644 index 0000000..456a7aa --- /dev/null +++ b/source/idlib/geometry/TraceModel.cpp @@ -0,0 +1,1634 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "TraceModel.h" + + +/* +============ +idTraceModel::SetupBox +============ +*/ +void idTraceModel::SetupBox( const idBounds &boxBounds ) { + int i; + + if ( type != TRM_BOX ) { + InitBox(); + } + // offset to center + offset = ( boxBounds[0] + boxBounds[1] ) * 0.5f; + // set box vertices + for ( i = 0; i < 8; i++ ) { + verts[i][0] = boxBounds[(i^(i>>1))&1][0]; + verts[i][1] = boxBounds[(i>>1)&1][1]; + verts[i][2] = boxBounds[(i>>2)&1][2]; + } + // set polygon plane distances + polys[0].dist = -boxBounds[0][2]; + polys[1].dist = boxBounds[1][2]; + polys[2].dist = -boxBounds[0][1]; + polys[3].dist = boxBounds[1][0]; + polys[4].dist = boxBounds[1][1]; + polys[5].dist = -boxBounds[0][0]; + // set polygon bounds + for ( i = 0; i < 6; i++ ) { + polys[i].bounds = boxBounds; + } + polys[0].bounds[1][2] = boxBounds[0][2]; + polys[1].bounds[0][2] = boxBounds[1][2]; + polys[2].bounds[1][1] = boxBounds[0][1]; + polys[3].bounds[0][0] = boxBounds[1][0]; + polys[4].bounds[0][1] = boxBounds[1][1]; + polys[5].bounds[1][0] = boxBounds[0][0]; + + bounds = boxBounds; +} + +/* +============ +idTraceModel::SetupBox + + The origin is placed at the center of the cube. +============ +*/ +void idTraceModel::SetupBox( const float size ) { + idBounds boxBounds; + float halfSize; + + halfSize = size * 0.5f; + boxBounds[0].Set( -halfSize, -halfSize, -halfSize ); + boxBounds[1].Set( halfSize, halfSize, halfSize ); + SetupBox( boxBounds ); +} + +/* +============ +idTraceModel::InitBox + + Initialize size independent box. +============ +*/ +void idTraceModel::InitBox( void ) { + int i; + + type = TRM_BOX; + numVerts = 8; + numEdges = 12; + numPolys = 6; + + // set box edges + for ( i = 0; i < 4; i++ ) { + edges[ i + 1 ].v[0] = i; + edges[ i + 1 ].v[1] = (i + 1) & 3; + edges[ i + 5 ].v[0] = 4 + i; + edges[ i + 5 ].v[1] = 4 + ((i + 1) & 3); + edges[ i + 9 ].v[0] = i; + edges[ i + 9 ].v[1] = 4 + i; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 4; + polys[0].edges[0] = -4; + polys[0].edges[1] = -3; + polys[0].edges[2] = -2; + polys[0].edges[3] = -1; + polys[0].normal.Set( 0.0f, 0.0f, -1.0f ); + + polys[1].numEdges = 4; + polys[1].edges[0] = 5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].normal.Set( 0.0f, 0.0f, 1.0f ); + + polys[2].numEdges = 4; + polys[2].edges[0] = 1; + polys[2].edges[1] = 10; + polys[2].edges[2] = -5; + polys[2].edges[3] = -9; + polys[2].normal.Set( 0.0f, -1.0f, 0.0f ); + + polys[3].numEdges = 4; + polys[3].edges[0] = 2; + polys[3].edges[1] = 11; + polys[3].edges[2] = -6; + polys[3].edges[3] = -10; + polys[3].normal.Set( 1.0f, 0.0f, 0.0f ); + + polys[4].numEdges = 4; + polys[4].edges[0] = 3; + polys[4].edges[1] = 12; + polys[4].edges[2] = -7; + polys[4].edges[3] = -11; + polys[4].normal.Set( 0.0f, 1.0f, 0.0f ); + + polys[5].numEdges = 4; + polys[5].edges[0] = 4; + polys[5].edges[1] = 9; + polys[5].edges[2] = -8; + polys[5].edges[3] = -12; + polys[5].normal.Set( -1.0f, 0.0f, 0.0f ); + + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron +============ +*/ +void idTraceModel::SetupOctahedron( const idBounds &octBounds ) { + int i, e0, e1, v0, v1, v2; + idVec3 v; + + if ( type != TRM_OCTAHEDRON ) { + InitOctahedron(); + } + + offset = ( octBounds[0] + octBounds[1] ) * 0.5f; + v[0] = octBounds[1][0] - offset[0]; + v[1] = octBounds[1][1] - offset[1]; + v[2] = octBounds[1][2] - offset[2]; + + // set vertices + verts[0].Set( offset.x + v[0], offset.y, offset.z ); + verts[1].Set( offset.x - v[0], offset.y, offset.z ); + verts[2].Set( offset.x, offset.y + v[1], offset.z ); + verts[3].Set( offset.x, offset.y - v[1], offset.z ); + verts[4].Set( offset.x, offset.y, offset.z + v[2] ); + verts[5].Set( offset.x, offset.y, offset.z - v[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + v0 = edges[abs(e0)].v[INTSIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INTSIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INTSIGNBITNOTSET(e1)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + } + + // trm bounds + bounds = octBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupOctahedron( const float size ) { + idBounds octBounds; + float halfSize; + + halfSize = size * 0.5f; + octBounds[0].Set( -halfSize, -halfSize, -halfSize ); + octBounds[1].Set( halfSize, halfSize, halfSize ); + SetupOctahedron( octBounds ); +} + +/* +============ +idTraceModel::InitOctahedron + + Initialize size independent octahedron. +============ +*/ +void idTraceModel::InitOctahedron( void ) { + + type = TRM_OCTAHEDRON; + numVerts = 6; + numEdges = 12; + numPolys = 8; + + // set edges + edges[ 1].v[0] = 4; edges[ 1].v[1] = 0; + edges[ 2].v[0] = 0; edges[ 2].v[1] = 2; + edges[ 3].v[0] = 2; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 2; edges[ 4].v[1] = 1; + edges[ 5].v[0] = 1; edges[ 5].v[1] = 4; + edges[ 6].v[0] = 1; edges[ 6].v[1] = 3; + edges[ 7].v[0] = 3; edges[ 7].v[1] = 4; + edges[ 8].v[0] = 3; edges[ 8].v[1] = 0; + edges[ 9].v[0] = 5; edges[ 9].v[1] = 2; + edges[10].v[0] = 0; edges[10].v[1] = 5; + edges[11].v[0] = 5; edges[11].v[1] = 1; + edges[12].v[0] = 5; edges[12].v[1] = 3; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + + polys[1].numEdges = 3; + polys[1].edges[0] = -3; + polys[1].edges[1] = 4; + polys[1].edges[2] = 5; + + polys[2].numEdges = 3; + polys[2].edges[0] = -5; + polys[2].edges[1] = 6; + polys[2].edges[2] = 7; + + polys[3].numEdges = 3; + polys[3].edges[0] = -7; + polys[3].edges[1] = 8; + polys[3].edges[2] = -1; + + polys[4].numEdges = 3; + polys[4].edges[0] = 9; + polys[4].edges[1] = -2; + polys[4].edges[2] = 10; + + polys[5].numEdges = 3; + polys[5].edges[0] = 11; + polys[5].edges[1] = -4; + polys[5].edges[2] = -9; + + polys[6].numEdges = 3; + polys[6].edges[0] = 12; + polys[6].edges[1] = -6; + polys[6].edges[2] = -11; + + polys[7].numEdges = 3; + polys[7].edges[0] = -10; + polys[7].edges[1] = -8; + polys[7].edges[2] = -12; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupDodecahedron +============ +*/ +void idTraceModel::SetupDodecahedron( const idBounds &dodBounds ) { + int i, e0, e1, e2, e3, v0, v1, v2, v3, v4; + float s, d; + idVec3 a, b, c; + + if ( type != TRM_DODECAHEDRON ) { + InitDodecahedron(); + } + + a[0] = a[1] = a[2] = 0.5773502691896257f; // 1.0f / ( 3.0f ) ^ 0.5f; + b[0] = b[1] = b[2] = 0.3568220897730899f; // ( ( 3.0f - ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + c[0] = c[1] = c[2] = 0.9341723589627156f; // ( ( 3.0f + ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + d = 0.5f / c[0]; + s = ( dodBounds[1][0] - dodBounds[0][0] ) * d; + a[0] *= s; + b[0] *= s; + c[0] *= s; + s = ( dodBounds[1][1] - dodBounds[0][1] ) * d; + a[1] *= s; + b[1] *= s; + c[1] *= s; + s = ( dodBounds[1][2] - dodBounds[0][2] ) * d; + a[2] *= s; + b[2] *= s; + c[2] *= s; + + offset = ( dodBounds[0] + dodBounds[1] ) * 0.5f; + + // set vertices + verts[ 0].Set( offset.x + a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 1].Set( offset.x + a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 2].Set( offset.x + a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 3].Set( offset.x + a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 4].Set( offset.x - a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 5].Set( offset.x - a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 6].Set( offset.x - a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 7].Set( offset.x - a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 8].Set( offset.x + b[0], offset.y + c[1], offset.z ); + verts[ 9].Set( offset.x - b[0], offset.y + c[1], offset.z ); + verts[10].Set( offset.x + b[0], offset.y - c[1], offset.z ); + verts[11].Set( offset.x - b[0], offset.y - c[1], offset.z ); + verts[12].Set( offset.x + c[0], offset.y , offset.z + b[2] ); + verts[13].Set( offset.x + c[0], offset.y , offset.z - b[2] ); + verts[14].Set( offset.x - c[0], offset.y , offset.z + b[2] ); + verts[15].Set( offset.x - c[0], offset.y , offset.z - b[2] ); + verts[16].Set( offset.x , offset.y + b[1], offset.z + c[2] ); + verts[17].Set( offset.x , offset.y - b[1], offset.z + c[2] ); + verts[18].Set( offset.x , offset.y + b[1], offset.z - c[2] ); + verts[19].Set( offset.x , offset.y - b[1], offset.z - c[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + e2 = polys[i].edges[2]; + e3 = polys[i].edges[3]; + v0 = edges[abs(e0)].v[INTSIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INTSIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INTSIGNBITNOTSET(e1)]; + v3 = edges[abs(e2)].v[INTSIGNBITNOTSET(e2)]; + v4 = edges[abs(e3)].v[INTSIGNBITNOTSET(e3)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + polys[i].bounds.AddPoint( verts[v3] ); + polys[i].bounds.AddPoint( verts[v4] ); + } + + // trm bounds + bounds = dodBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupDodecahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupDodecahedron( const float size ) { + idBounds dodBounds; + float halfSize; + + halfSize = size * 0.5f; + dodBounds[0].Set( -halfSize, -halfSize, -halfSize ); + dodBounds[1].Set( halfSize, halfSize, halfSize ); + SetupDodecahedron( dodBounds ); +} + +/* +============ +idTraceModel::InitDodecahedron + + Initialize size independent dodecahedron. +============ +*/ +void idTraceModel::InitDodecahedron( void ) { + + type = TRM_DODECAHEDRON; + numVerts = 20; + numEdges = 30; + numPolys = 12; + + // set edges + edges[ 1].v[0] = 0; edges[ 1].v[1] = 8; + edges[ 2].v[0] = 8; edges[ 2].v[1] = 9; + edges[ 3].v[0] = 9; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 4; edges[ 4].v[1] = 16; + edges[ 5].v[0] = 16; edges[ 5].v[1] = 0; + edges[ 6].v[0] = 16; edges[ 6].v[1] = 17; + edges[ 7].v[0] = 17; edges[ 7].v[1] = 2; + edges[ 8].v[0] = 2; edges[ 8].v[1] = 12; + edges[ 9].v[0] = 12; edges[ 9].v[1] = 0; + edges[10].v[0] = 2; edges[10].v[1] = 10; + edges[11].v[0] = 10; edges[11].v[1] = 3; + edges[12].v[0] = 3; edges[12].v[1] = 13; + edges[13].v[0] = 13; edges[13].v[1] = 12; + edges[14].v[0] = 9; edges[14].v[1] = 5; + edges[15].v[0] = 5; edges[15].v[1] = 15; + edges[16].v[0] = 15; edges[16].v[1] = 14; + edges[17].v[0] = 14; edges[17].v[1] = 4; + edges[18].v[0] = 3; edges[18].v[1] = 19; + edges[19].v[0] = 19; edges[19].v[1] = 18; + edges[20].v[0] = 18; edges[20].v[1] = 1; + edges[21].v[0] = 1; edges[21].v[1] = 13; + edges[22].v[0] = 7; edges[22].v[1] = 11; + edges[23].v[0] = 11; edges[23].v[1] = 6; + edges[24].v[0] = 6; edges[24].v[1] = 14; + edges[25].v[0] = 15; edges[25].v[1] = 7; + edges[26].v[0] = 1; edges[26].v[1] = 8; + edges[27].v[0] = 18; edges[27].v[1] = 5; + edges[28].v[0] = 6; edges[28].v[1] = 17; + edges[29].v[0] = 11; edges[29].v[1] = 10; + edges[30].v[0] = 19; edges[30].v[1] = 7; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 5; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + polys[0].edges[3] = 4; + polys[0].edges[4] = 5; + + polys[1].numEdges = 5; + polys[1].edges[0] = -5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].edges[4] = 9; + + polys[2].numEdges = 5; + polys[2].edges[0] = -8; + polys[2].edges[1] = 10; + polys[2].edges[2] = 11; + polys[2].edges[3] = 12; + polys[2].edges[4] = 13; + + polys[3].numEdges = 5; + polys[3].edges[0] = 14; + polys[3].edges[1] = 15; + polys[3].edges[2] = 16; + polys[3].edges[3] = 17; + polys[3].edges[4] = -3; + + polys[4].numEdges = 5; + polys[4].edges[0] = 18; + polys[4].edges[1] = 19; + polys[4].edges[2] = 20; + polys[4].edges[3] = 21; + polys[4].edges[4] = -12; + + polys[5].numEdges = 5; + polys[5].edges[0] = 22; + polys[5].edges[1] = 23; + polys[5].edges[2] = 24; + polys[5].edges[3] = -16; + polys[5].edges[4] = 25; + + polys[6].numEdges = 5; + polys[6].edges[0] = -9; + polys[6].edges[1] = -13; + polys[6].edges[2] = -21; + polys[6].edges[3] = 26; + polys[6].edges[4] = -1; + + polys[7].numEdges = 5; + polys[7].edges[0] = -26; + polys[7].edges[1] = -20; + polys[7].edges[2] = 27; + polys[7].edges[3] = -14; + polys[7].edges[4] = -2; + + polys[8].numEdges = 5; + polys[8].edges[0] = -4; + polys[8].edges[1] = -17; + polys[8].edges[2] = -24; + polys[8].edges[3] = 28; + polys[8].edges[4] = -6; + + polys[9].numEdges = 5; + polys[9].edges[0] = -23; + polys[9].edges[1] = 29; + polys[9].edges[2] = -10; + polys[9].edges[3] = -7; + polys[9].edges[4] = -28; + + polys[10].numEdges = 5; + polys[10].edges[0] = -25; + polys[10].edges[1] = -15; + polys[10].edges[2] = -27; + polys[10].edges[3] = -19; + polys[10].edges[4] = 30; + + polys[11].numEdges = 5; + polys[11].edges[0] = -30; + polys[11].edges[1] = -18; + polys[11].edges[2] = -11; + polys[11].edges[3] = -29; + polys[11].edges[4] = -22; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupCylinder +alt_alignment makes even-sided cylinders align faces with the x/y axes +============ +*/ +void idTraceModel::SetupCylinder( const idBounds &cylBounds, const int numSides, const bool alt_alignment ) { + int i, n, ii, n2; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 3 ) { + n = 3; + } + if ( n * 2 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS / 2; + } + if ( n * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many sides\n" ); + n = MAX_TRACEMODEL_EDGES / 3; + } + if ( n + 2 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 2; + } + + float angle_bias = alt_alignment ? (idMath::PI / n) : 0.0f; + + type = TRM_CYLINDER; + numVerts = n * 2; + numEdges = n * 3; + numPolys = n + 2; + offset = ( cylBounds[0] + cylBounds[1] ) * 0.5f; + halfSize = cylBounds[1] - offset; + for ( i = 0; i < n; i++ ) { + // verts + angle = (idMath::TWO_PI * i / n) + angle_bias; + verts[i].x = idMath::Cos( angle ) * halfSize.x + offset.x; + verts[i].y = idMath::Sin( angle ) * halfSize.y + offset.y; + verts[i].z = -halfSize.z + offset.z; + verts[n+i].x = verts[i].x; + verts[n+i].y = verts[i].y; + verts[n+i].z = halfSize.z + offset.z; + // edges + ii = i + 1; + n2 = n << 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = edges[ii].v[0] + n; + edges[n+ii].v[1] = edges[ii].v[1] + n; + edges[n2+ii].v[0] = i; + edges[n2+ii].v[1] = n + i; + // vertical polygon edges + polys[i].numEdges = 4; + polys[i].edges[0] = ii; + polys[i].edges[1] = n2 + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + polys[i].edges[3] = -(n2 + ii); + // bottom and top polygon edges + polys[n].edges[i] = -(n - i); + polys[n+1].edges[i] = n + ii; + } + // bottom and top polygon numEdges + polys[n].numEdges = n; + polys[n+1].numEdges = n; + // polygons + for ( i = 0; i < n; i++ ) { + // vertical polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n+i] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // vertical polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds[0][2] = -halfSize.z + offset.z; + polys[i].bounds[1][2] = halfSize.z + offset.z; + } + // bottom and top polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -cylBounds[0][2]; + polys[n+1].normal.Set( 0.0f, 0.0f, 1.0f ); + polys[n+1].dist = cylBounds[1][2]; + // trm bounds + bounds = cylBounds; + // bottom and top polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + polys[n+1].bounds = bounds; + polys[n+1].bounds[0][2] = bounds[1][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCylinder + + The origin is placed at the center of the cylinder. +============ +*/ +void idTraceModel::SetupCylinder( const float height, const float width, const int numSides ) { + idBounds cylBounds; + float halfHeight, halfWidth; + + halfHeight = height * 0.5f; + halfWidth = width * 0.5f; + cylBounds[0].Set( -halfWidth, -halfWidth, -halfHeight ); + cylBounds[1].Set( halfWidth, halfWidth, halfHeight ); + SetupCylinder( cylBounds, numSides ); +} + +/* +============ +idTraceModel::SetupCone +============ +*/ +void idTraceModel::SetupCone( const idBounds &coneBounds, const int numSides ) { + int i, n, ii; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 2 ) { + n = 3; + } + if ( n + 1 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS - 1; + } + if ( n * 2 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many edges\n" ); + n = MAX_TRACEMODEL_EDGES / 2; + } + if ( n + 1 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 1; + } + + type = TRM_CONE; + numVerts = n + 1; + numEdges = n * 2; + numPolys = n + 1; + offset = ( coneBounds[0] + coneBounds[1] ) * 0.5f; + halfSize = coneBounds[1] - offset; + verts[n].Set( 0.0f, 0.0f, halfSize.z + offset.z ); + for ( i = 0; i < n; i++ ) { + // verts + angle = idMath::TWO_PI * i / n; +// RAVEN BEGIN + verts[i].x = idMath::Cos( angle ) * halfSize.x + offset.x; + verts[i].y = idMath::Sin( angle ) * halfSize.y + offset.y; +// RAVEN END + verts[i].z = -halfSize.z + offset.z; + // edges + ii = i + 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = i; + edges[n+ii].v[1] = n; + // vertical polygon edges + polys[i].numEdges = 3; + polys[i].edges[0] = ii; + polys[i].edges[1] = n + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + // bottom polygon edges + polys[n].edges[i] = -(n - i); + } + // bottom polygon numEdges + polys[n].numEdges = n; + + // polygons + for ( i = 0; i < n; i++ ) { + // polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds.AddPoint( verts[n] ); + } + // bottom polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -coneBounds[0][2]; + // trm bounds + bounds = coneBounds; + // bottom polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCone + + The origin is placed at the apex of the cone. +============ +*/ +void idTraceModel::SetupCone( const float height, const float width, const int numSides ) { + idBounds coneBounds; + float halfWidth; + + halfWidth = width * 0.5f; + coneBounds[0].Set( -halfWidth, -halfWidth, -height ); + coneBounds[1].Set( halfWidth, halfWidth, 0.0f ); + SetupCone( coneBounds, numSides ); +} + +/* +============ +idTraceModel::SetupBone + + The origin is placed at the center of the bone. +============ +*/ +void idTraceModel::SetupBone( const float length, const float width ) { + int i, j, edgeNum; + float halfLength = length * 0.5f; + + if ( type != TRM_BONE ) { + InitBone(); + } + // offset to center + offset.Set( 0.0f, 0.0f, 0.0f ); + // set vertices + verts[0].Set( 0.0f, 0.0f, -halfLength ); + verts[1].Set( 0.0f, width * -0.5f, 0.0f ); + verts[2].Set( width * 0.5f, width * 0.25f, 0.0f ); + verts[3].Set( width * -0.5f, width * 0.25f, 0.0f ); + verts[4].Set( 0.0f, 0.0f, halfLength ); + // set bounds + bounds[0].Set( width * -0.5f, width * -0.5f, -halfLength ); + bounds[1].Set( width * 0.5f, width * 0.25f, halfLength ); + // poly plane normals + polys[0].normal = ( verts[2] - verts[0] ).Cross( verts[1] - verts[0] ); + polys[0].normal.Normalize(); + polys[2].normal.Set( -polys[0].normal[0], polys[0].normal[1], polys[0].normal[2] ); + polys[3].normal.Set( polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[5].normal.Set( -polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[1].normal = (verts[3] - verts[0]).Cross(verts[2] - verts[0]); + polys[1].normal.Normalize(); + polys[4].normal.Set( polys[1].normal[0], polys[1].normal[1], -polys[1].normal[2] ); + // poly plane distances + for ( i = 0; i < 6; i++ ) { + polys[i].dist = polys[i].normal * verts[ edges[ abs(polys[i].edges[0]) ].v[0] ]; + polys[i].bounds.Clear(); + for ( j = 0; j < 3; j++ ) { + edgeNum = polys[i].edges[ j ]; + polys[i].bounds.AddPoint( verts[ edges[abs(edgeNum)].v[edgeNum < 0] ] ); + } + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::InitBone + + Initialize size independent bone. +============ +*/ +void idTraceModel::InitBone( void ) { + int i; + + type = TRM_BONE; + numVerts = 5; + numEdges = 9; + numPolys = 6; + + // set bone edges + for ( i = 0; i < 3; i++ ) { + edges[ i + 1 ].v[0] = 0; + edges[ i + 1 ].v[1] = i + 1; + edges[ i + 4 ].v[0] = 1 + i; + edges[ i + 4 ].v[1] = 1 + ((i + 1) % 3); + edges[ i + 7 ].v[0] = i + 1; + edges[ i + 7 ].v[1] = 4; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 2; + polys[0].edges[1] = -4; + polys[0].edges[2] = -1; + + polys[1].numEdges = 3; + polys[1].edges[0] = 3; + polys[1].edges[1] = -5; + polys[1].edges[2] = -2; + + polys[2].numEdges = 3; + polys[2].edges[0] = 1; + polys[2].edges[1] = -6; + polys[2].edges[2] = -3; + + polys[3].numEdges = 3; + polys[3].edges[0] = 4; + polys[3].edges[1] = 8; + polys[3].edges[2] = -7; + + polys[4].numEdges = 3; + polys[4].edges[0] = 5; + polys[4].edges[1] = 9; + polys[4].edges[2] = -8; + + polys[5].numEdges = 3; + polys[5].edges[0] = 6; + polys[5].edges[1] = 7; + polys[5].edges[2] = -9; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idVec3 *v, const int count ) { + int i, j; + idVec3 mid; + + type = TRM_POLYGON; + numVerts = count; + // times three because we need to be able to turn the polygon into a volume + if ( numVerts * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupPolygon: too many vertices\n" ); + numVerts = MAX_TRACEMODEL_EDGES / 3; + } + + numEdges = numVerts; + numPolys = 2; + // set polygon planes + polys[0].numEdges = numEdges; + polys[0].normal = ( v[1] - v[0] ).Cross( v[2] - v[0] ); + polys[0].normal.Normalize(); + polys[0].dist = polys[0].normal * v[0]; + polys[1].numEdges = numEdges; + polys[1].normal = -polys[0].normal; + polys[1].dist = -polys[0].dist; + // setup verts, edges and polygons + polys[0].bounds.Clear(); + mid = vec3_origin; + for ( i = 0, j = 1; i < numVerts; i++, j++ ) { + if ( j >= numVerts ) { + j = 0; + } + verts[i] = v[i]; + edges[i+1].v[0] = i; + edges[i+1].v[1] = j; + edges[i+1].normal = polys[0].normal.Cross( v[i] - v[j] ); + edges[i+1].normal.Normalize(); + polys[0].edges[i] = i + 1; + polys[1].edges[i] = -(numVerts - i); + polys[0].bounds.AddPoint( verts[i] ); + mid += v[i]; + } + polys[1].bounds = polys[0].bounds; + // offset to center + offset = mid * (1.0f / numVerts); + // total bounds + bounds = polys[0].bounds; + // considered non convex because the model has no volume + isConvex = false; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idWinding &w ) { + int i; + idVec3 *verts; + + verts = (idVec3 *) _alloca16( w.GetNumPoints() * sizeof( idVec3 ) ); + for ( i = 0; i < w.GetNumPoints(); i++ ) { + verts[i] = w[i].ToVec3(); + } + SetupPolygon( verts, w.GetNumPoints() ); +} + +/* +============ +idTraceModel::VolumeFromPolygon +============ +*/ +void idTraceModel::VolumeFromPolygon( idTraceModel &trm, float thickness ) const { + int i; + + trm = *this; + trm.type = TRM_POLYGONVOLUME; + trm.numVerts = numVerts * 2; + trm.numEdges = numEdges * 3; + trm.numPolys = numEdges + 2; + for ( i = 0; i < numEdges; i++ ) { + trm.verts[ numVerts + i ] = verts[i] - thickness * polys[0].normal; + trm.edges[ numEdges + i + 1 ].v[0] = numVerts + i; + trm.edges[ numEdges + i + 1 ].v[1] = numVerts + (i+1) % numVerts; + trm.edges[ numEdges * 2 + i + 1 ].v[0] = i; + trm.edges[ numEdges * 2 + i + 1 ].v[1] = numVerts + i; + trm.polys[1].edges[i] = -(numEdges + i + 1); + trm.polys[2+i].numEdges = 4; + trm.polys[2+i].edges[0] = -(i + 1); + trm.polys[2+i].edges[1] = numEdges*2 + i + 1; + trm.polys[2+i].edges[2] = numEdges + i + 1; + trm.polys[2+i].edges[3] = -(numEdges*2 + (i+1) % numEdges + 1); + trm.polys[2+i].normal = (verts[(i + 1) % numVerts] - verts[i]).Cross( polys[0].normal ); + trm.polys[2+i].normal.Normalize(); + trm.polys[2+i].dist = trm.polys[2+i].normal * verts[i]; + } + trm.polys[1].dist = trm.polys[1].normal * trm.verts[ numEdges ]; + + trm.GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::GenerateEdgeNormals +============ +*/ +#define SHARP_EDGE_DOT -0.7f + +int idTraceModel::GenerateEdgeNormals( void ) { + int i, j, edgeNum, numSharpEdges; + float dot; + idVec3 dir; + traceModelPoly_t *poly; + traceModelEdge_t *edge; + + for ( i = 0; i <= numEdges; i++ ) { + edges[i].normal.Zero(); + } + + numSharpEdges = 0; + + for ( i = 0; i < numPolys; i++ ) { + poly = polys + i; + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = edges + abs( edgeNum ); + if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) { + edge->normal = poly->normal; + } else { + dot = edge->normal * poly->normal; + // if the two planes make a very sharp edge + if ( dot < SHARP_EDGE_DOT ) { + // max length normal pointing outside both polygons + dir = verts[ edge->v[edgeNum > 0]] - verts[ edge->v[edgeNum < 0]]; + edge->normal = edge->normal.Cross( dir ) + poly->normal.Cross( -dir ); + edge->normal *= ( 1.0f / ( 1.0f + SHARP_EDGE_DOT ) ) / edge->normal.Length(); + numSharpEdges++; + } else { + edge->normal = ( 1.0f / ( 1.0f + dot ) ) * ( edge->normal + poly->normal ); + } + } + } + } + + return numSharpEdges; +} + +/* +============ +idTraceModel::TestConvexity +============ +*/ +void idTraceModel::TestConvexity( void ) { + int i, j; + + // assume convex + isConvex = true; + // check if really convex + for ( i = 0; i < numPolys; i++ ) { + // to be convex no vertices should be in front of any polygon plane + for ( j = 0; j < numVerts; j++ ) { + if ( polys[ i ].normal * verts[ j ] - polys[ i ].dist > 0.01f ) { + isConvex = false; + break; + } + } + if ( j < numVerts ) { + break; + } + } +} + +/* +============ +idTraceModel::Translate +============ +*/ +void idTraceModel::Translate( const idVec3 &translation ) { + int i; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] += translation; + } + for ( i = 0; i < numPolys; i++ ) { + polys[i].dist += polys[i].normal * translation; + polys[i].bounds[0] += translation; + polys[i].bounds[1] += translation; + } + offset += translation; + bounds[0] += translation; + bounds[1] += translation; +} + +/* +============ +idTraceModel::Rotate +============ +*/ +void idTraceModel::Rotate( const idMat3 &rotation ) { + int i, j, edgeNum; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] *= rotation; + } + + bounds.Clear(); + for ( i = 0; i < numPolys; i++ ) { + polys[i].normal *= rotation; + polys[i].bounds.Clear(); + edgeNum = 0; + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = polys[i].edges[j]; + polys[i].bounds.AddPoint( verts[edges[abs(edgeNum)].v[INTSIGNBITSET(edgeNum)]] ); + } + polys[i].dist = polys[i].normal * verts[edges[abs(edgeNum)].v[INTSIGNBITSET(edgeNum)]]; + bounds += polys[i].bounds; + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::Shrink +============ +*/ +void idTraceModel::Shrink( const float m ) { + int i, j, edgeNum, vertexNum; + float d, bestd, n, invDet, f0, f1; + traceModelPoly_t *poly, *poly1, *poly2; + traceModelEdge_t *edge; + idVec3 start, dir; + int vertexPolys[MAX_TRACEMODEL_VERTS][MAX_TRACEMODEL_POLYS]; + int vertexNumPolys[MAX_TRACEMODEL_VERTS]; + + // special case for single polygon + if ( type == TRM_POLYGON ) { + for ( i = 0; i < numEdges; i++ ) { + edgeNum = polys[0].edges[i]; + edge = &edges[abs(edgeNum)]; + dir = verts[ edge->v[ INTSIGNBITSET(edgeNum) ] ] - verts[ edge->v[ INTSIGNBITNOTSET(edgeNum) ] ]; + if ( dir.Normalize() < 2.0f * m ) { + continue; + } + dir *= m; + verts[ edge->v[ 0 ] ] -= dir; + verts[ edge->v[ 1 ] ] += dir; + } + return; + } + + // the trace model should be a closed surface + assert( IsClosedSurface() ); + + memset( vertexPolys, 0, sizeof( vertexPolys ) ); + memset( vertexNumPolys, 0, sizeof( vertexNumPolys ) ); + + // move polygon planes and find the vertex polygons + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + poly->dist -= m; + + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = &edges[abs(edgeNum)]; + vertexNum = edge->v[ INTSIGNBITSET( edgeNum ) ]; + + vertexPolys[vertexNum][vertexNumPolys[vertexNum]] = i; + vertexNumPolys[vertexNum]++; + } + } + + // move vertices + for ( i = 0; i < numVerts; i++ ) { + + assert( vertexNumPolys[i] >= 3 ); + + poly1 = &polys[vertexPolys[i][0]]; + poly2 = NULL; + + // find the polygon that is most orthogonal to the first polygon + bestd = 1.0f; + for ( j = 1; j < vertexNumPolys[i]; j++ ) { + d = fabs( poly1->normal * polys[vertexPolys[i][j]].normal ); + if ( d < bestd ) { + bestd = d; + poly2 = &polys[vertexPolys[i][j]]; + } + } + + // calculate intersection line between planes + n = poly1->normal * poly2->normal; + invDet = 1.0f / ( 1.0f - n * n ); + f0 = ( poly1->dist - n * poly2->dist ) * invDet; + f1 = ( poly2->dist - n * poly1->dist ) * invDet; + start = f0 * poly1->normal + f1 * poly2->normal; + dir = poly1->normal.Cross( poly2->normal ); + + // find the polygon that is most orthogonal to the plane intersection ray + bestd = 0.0f; + for ( j = 1; j < vertexNumPolys[i]; j++ ) { + d = fabs( dir * polys[vertexPolys[i][j]].normal ); + if ( d > bestd ) { + bestd = d; + poly2 = &polys[vertexPolys[i][j]]; + } + } + + // calculate intersection with plane intersection ray + f0 = poly2->normal * start - poly2->dist; + f1 = poly2->normal * dir; + verts[i] = start - dir * ( f0 / f1 ); + } + + Verify(); +} + +/* +============ +idTraceModel::ClearUnused +============ +*/ +void idTraceModel::ClearUnused( void ) { + int i, j; + + for ( i = numVerts; i < MAX_TRACEMODEL_VERTS; i++ ) { + verts[i].Zero(); + } + memset( &edges[0], 0, sizeof( edges[0] ) ); + for ( i = numEdges+1; i < MAX_TRACEMODEL_EDGES+1; i++ ) { + memset( &edges[i], 0, sizeof( edges[i] ) ); + } + for ( i = 0; i < numPolys; i++ ) { + for ( j = polys[i].numEdges; j < MAX_TRACEMODEL_POLYEDGES; j++ ) { + polys[i].edges[j] = 0; + } + } + for ( i = numPolys; i < MAX_TRACEMODEL_POLYS; i++ ) { + memset( &polys[i], 0, sizeof( polys[i] ) ); + } +} + +/* +============ +idTraceModel::Verify +============ +*/ +bool idTraceModel::Verify( void ) { + int i, j, edgeNum, vertexNum; + traceModelPoly_t *poly; + traceModelEdge_t *edge; + + // test whether or not the vertices are on the polygon planes + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = &edges[abs(edgeNum)]; + vertexNum = edge->v[ INTSIGNBITSET( edgeNum ) ]; + float d = poly->normal * verts[vertexNum] - poly->dist; + if ( fabs( d ) > 1e-4f ) { + return false; + } + } + } + + return true; +} + +/* +============ +idTraceModel::Compare +============ +*/ +bool idTraceModel::Compare( const idTraceModel &trm ) const { + int i; + + if ( type != trm.type || numVerts != trm.numVerts || + numEdges != trm.numEdges || numPolys != trm.numPolys ) { + return false; + } + if ( bounds != trm.bounds || offset != trm.offset ) { + return false; + } + + switch( type ) { + case TRM_INVALID: + case TRM_BOX: + case TRM_OCTAHEDRON: + case TRM_DODECAHEDRON: + case TRM_CYLINDER: + case TRM_CONE: + break; + case TRM_BONE: + case TRM_POLYGON: + case TRM_POLYGONVOLUME: + case TRM_CUSTOM: + for ( i = 0; i < trm.numVerts; i++ ) { + if ( verts[i] != trm.verts[i] ) { + return false; + } + } + break; + } + return true; +} + +/* +============ +idTraceModel::IsClosedSurface +============ +*/ +bool idTraceModel::IsClosedSurface( void ) const { + int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1]; + + // each edge should be used exactly twice + memset( numEdgeUsers, 0, sizeof(numEdgeUsers) ); + for ( i = 0; i < numPolys; i++ ) { + for ( j = 0; j < polys[i].numEdges; j++ ) { + numEdgeUsers[ abs( polys[i].edges[j] ) ]++; + } + } + for ( i = 1; i <= numEdges; i++ ) { + if ( numEdgeUsers[i] != 2 ) { + return false; + } + } + return true; +} + +/* +============ +idTraceModel::GetPolygonArea +============ +*/ +float idTraceModel::GetPolygonArea( int polyNum ) const { + int i; + idVec3 base, v1, v2, cross; + float total; + const traceModelPoly_t *poly; + + if ( polyNum < 0 || polyNum >= numPolys ) { + return 0.0f; + } + poly = &polys[polyNum]; + total = 0.0f; + base = verts[ edges[ abs(poly->edges[0]) ].v[ INTSIGNBITSET( poly->edges[0] ) ] ]; + for ( i = 0; i < poly->numEdges; i++ ) { + v1 = verts[ edges[ abs(poly->edges[i]) ].v[ INTSIGNBITSET( poly->edges[i] ) ] ] - base; + v2 = verts[ edges[ abs(poly->edges[i]) ].v[ INTSIGNBITNOTSET( poly->edges[i] ) ] ] - base; + cross = v1.Cross( v2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============ +idTraceModel::GetOrderedSilhouetteEdges +============ +*/ +int idTraceModel::GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum, numSilEdges, nextSilVert; + int unsortedSilEdges[MAX_TRACEMODEL_EDGES+1]; + + numSilEdges = 0; + for ( i = 1; i <= numEdges; i++ ) { + if ( edgeIsSilEdge[i] ) { + unsortedSilEdges[numSilEdges++] = i; + } + } + + silEdges[0] = unsortedSilEdges[0]; + unsortedSilEdges[0] = -1; + nextSilVert = edges[silEdges[0]].v[0]; + for ( i = 1; i < numSilEdges; i++ ) { + for ( j = 1; j < numSilEdges; j++ ) { + edgeNum = unsortedSilEdges[j]; + if ( edgeNum >= 0 ) { + if ( edges[edgeNum].v[0] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[1]; + silEdges[i] = edgeNum; + break; + } + if ( edges[edgeNum].v[1] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[0]; + silEdges[i] = -edgeNum; + break; + } + } + } + if ( j >= numSilEdges ) { + silEdges[i] = 1; // shouldn't happen + } + unsortedSilEdges[j] = -1; + } + return numSilEdges; +} + +/* +============ +idTraceModel::GetProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + idVec3 dir; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + edgeNum = poly->edges[0]; + dir = verts[ edges[abs(edgeNum)].v[ INTSIGNBITSET(edgeNum) ] ] - projectionOrigin; + if ( dir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + +/* +============ +idTraceModel::GetParallelProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + if ( projectionDir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + + +/* + + credits to Brian Mirtich for his paper "Fast and Accurate Computation of Polyhedral Mass Properties" + +*/ + +struct projectionIntegrals_t { + float P1; + float Pa, Pb; + float Paa, Pab, Pbb; + float Paaa, Paab, Pabb, Pbbb; +}; + +/* +============ +idTraceModel::ProjectionIntegrals +============ +*/ +void idTraceModel::ProjectionIntegrals( int polyNum, int a, int b, projectionIntegrals_t &integrals ) const { + const traceModelPoly_t *poly; + int i, edgeNum; + idVec3 v1, v2; + float a0, a1, da; + float b0, b1, db; + float a0_2, a0_3, a0_4, b0_2, b0_3, b0_4; + float a1_2, a1_3, b1_2, b1_3; + float C1, Ca, Caa, Caaa, Cb, Cbb, Cbbb; + float Cab, Kab, Caab, Kaab, Cabb, Kabb; + + memset(&integrals, 0, sizeof(projectionIntegrals_t)); + poly = &polys[polyNum]; + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + v1 = verts[ edges[ abs(edgeNum) ].v[ edgeNum < 0 ] ]; + v2 = verts[ edges[ abs(edgeNum) ].v[ edgeNum > 0 ] ]; + a0 = v1[a]; + b0 = v1[b]; + a1 = v2[a]; + b1 = v2[b]; + da = a1 - a0; + db = b1 - b0; + a0_2 = a0 * a0; + a0_3 = a0_2 * a0; + a0_4 = a0_3 * a0; + b0_2 = b0 * b0; + b0_3 = b0_2 * b0; + b0_4 = b0_3 * b0; + a1_2 = a1 * a1; + a1_3 = a1_2 * a1; + b1_2 = b1 * b1; + b1_3 = b1_2 * b1; + + C1 = a1 + a0; + Ca = a1 * C1 + a0_2; + Caa = a1 * Ca + a0_3; + Caaa = a1 * Caa + a0_4; + Cb = b1 * (b1 + b0) + b0_2; + Cbb = b1 * Cb + b0_3; + Cbbb = b1 * Cbb + b0_4; + Cab = 3 * a1_2 + 2 * a1 * a0 + a0_2; + Kab = a1_2 + 2 * a1 * a0 + 3 * a0_2; + Caab = a0 * Cab + 4 * a1_3; + Kaab = a1 * Kab + 4 * a0_3; + Cabb = 4 * b1_3 + 3 * b1_2 * b0 + 2 * b1 * b0_2 + b0_3; + Kabb = b1_3 + 2 * b1_2 * b0 + 3 * b1 * b0_2 + 4 * b0_3; + + integrals.P1 += db * C1; + integrals.Pa += db * Ca; + integrals.Paa += db * Caa; + integrals.Paaa += db * Caaa; + integrals.Pb += da * Cb; + integrals.Pbb += da * Cbb; + integrals.Pbbb += da * Cbbb; + integrals.Pab += db * (b1 * Cab + b0 * Kab); + integrals.Paab += db * (b1 * Caab + b0 * Kaab); + integrals.Pabb += da * (a1 * Cabb + a0 * Kabb); + } + + integrals.P1 *= (1.0f / 2.0f); + integrals.Pa *= (1.0f / 6.0f); + integrals.Paa *= (1.0f / 12.0f); + integrals.Paaa *= (1.0f / 20.0f); + integrals.Pb *= (1.0f / -6.0f); + integrals.Pbb *= (1.0f / -12.0f); + integrals.Pbbb *= (1.0f / -20.0f); + integrals.Pab *= (1.0f / 24.0f); + integrals.Paab *= (1.0f / 60.0f); + integrals.Pabb *= (1.0f / -60.0f); +} + +struct polygonIntegrals_t { + float Fa, Fb, Fc; + float Faa, Fbb, Fcc; + float Faaa, Fbbb, Fccc; + float Faab, Fbbc, Fcca; +}; + +/* +============ +idTraceModel::PolygonIntegrals +============ +*/ +void idTraceModel::PolygonIntegrals( int polyNum, int a, int b, int c, polygonIntegrals_t &integrals ) const { + projectionIntegrals_t pi; + idVec3 n; + float w; + float k1, k2, k3, k4; + + ProjectionIntegrals( polyNum, a, b, pi ); + + n = polys[polyNum].normal; + w = -polys[polyNum].dist; + k1 = 1 / n[c]; + k2 = k1 * k1; + k3 = k2 * k1; + k4 = k3 * k1; + + integrals.Fa = k1 * pi.Pa; + integrals.Fb = k1 * pi.Pb; + integrals.Fc = -k2 * (n[a] * pi.Pa + n[b] * pi.Pb + w * pi.P1); + + integrals.Faa = k1 * pi.Paa; + integrals.Fbb = k1 * pi.Pbb; + integrals.Fcc = k3 * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb + + w * (2 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faaa = k1 * pi.Paaa; + integrals.Fbbb = k1 * pi.Pbbb; + integrals.Fccc = -k4 * (Cube(n[a]) * pi.Paaa + 3 * Square(n[a]) * n[b] * pi.Paab + + 3 * n[a] * Square(n[b]) * pi.Pabb + Cube(n[b]) * pi.Pbbb + + 3 * w * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb) + + w * w * (3 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faab = k1 * pi.Paab; + integrals.Fbbc = -k2 * (n[a] * pi.Pabb + n[b] * pi.Pbbb + w * pi.Pbb); + integrals.Fcca = k3 * (Square(n[a]) * pi.Paaa + 2 * n[a] * n[b] * pi.Paab + Square(n[b]) * pi.Pabb + + w * (2 * (n[a] * pi.Paa + n[b] * pi.Pab) + w * pi.Pa)); +} + +struct volumeIntegrals_t { + float T0; + idVec3 T1; + idVec3 T2; + idVec3 TP; +}; + +/* +============ +idTraceModel::VolumeIntegrals +============ +*/ +void idTraceModel::VolumeIntegrals( volumeIntegrals_t &integrals ) const { + const traceModelPoly_t *poly; + polygonIntegrals_t pi; + int i, a, b, c; + float nx, ny, nz; + + memset( &integrals, 0, sizeof(volumeIntegrals_t) ); + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + nx = idMath::Fabs( poly->normal[0] ); + ny = idMath::Fabs( poly->normal[1] ); + nz = idMath::Fabs( poly->normal[2] ); + if ( nx > ny && nx > nz ) { + c = 0; + } + else { + c = (ny > nz) ? 1 : 2; + } + a = (c + 1) % 3; + b = (a + 1) % 3; + + PolygonIntegrals( i, a, b, c, pi ); + + integrals.T0 += poly->normal[0] * ((a == 0) ? pi.Fa : ((b == 0) ? pi.Fb : pi.Fc)); + + integrals.T1[a] += poly->normal[a] * pi.Faa; + integrals.T1[b] += poly->normal[b] * pi.Fbb; + integrals.T1[c] += poly->normal[c] * pi.Fcc; + integrals.T2[a] += poly->normal[a] * pi.Faaa; + integrals.T2[b] += poly->normal[b] * pi.Fbbb; + integrals.T2[c] += poly->normal[c] * pi.Fccc; + integrals.TP[a] += poly->normal[a] * pi.Faab; + integrals.TP[b] += poly->normal[b] * pi.Fbbc; + integrals.TP[c] += poly->normal[c] * pi.Fcca; + } + + integrals.T1 *= 0.5f; + integrals.T2 *= (1.0f / 3.0f); + integrals.TP *= 0.5f; +} + +/* +============ +idTraceModel::GetMassProperties +============ +*/ +void idTraceModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + volumeIntegrals_t integrals; + + // if polygon trace model + if ( type == TRM_POLYGON ) { + idTraceModel trm; + + VolumeFromPolygon( trm, 1.0f ); + trm.GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + return; + } + + VolumeIntegrals( integrals ); + + // if no volume + if ( integrals.T0 == 0.0f ) { + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + return; + } + + // mass of model + mass = density * integrals.T0; + // center of mass + centerOfMass = integrals.T1 / integrals.T0; + // compute inertia tensor + inertiaTensor[0][0] = density * (integrals.T2[1] + integrals.T2[2]); + inertiaTensor[1][1] = density * (integrals.T2[2] + integrals.T2[0]); + inertiaTensor[2][2] = density * (integrals.T2[0] + integrals.T2[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] = - density * integrals.TP[0]; + inertiaTensor[1][2] = inertiaTensor[2][1] = - density * integrals.TP[1]; + inertiaTensor[2][0] = inertiaTensor[0][2] = - density * integrals.TP[2]; + // translate inertia tensor to center of mass + inertiaTensor[0][0] -= mass * (centerOfMass[1]*centerOfMass[1] + centerOfMass[2]*centerOfMass[2]); + inertiaTensor[1][1] -= mass * (centerOfMass[2]*centerOfMass[2] + centerOfMass[0]*centerOfMass[0]); + inertiaTensor[2][2] -= mass * (centerOfMass[0]*centerOfMass[0] + centerOfMass[1]*centerOfMass[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] += mass * centerOfMass[0] * centerOfMass[1]; + inertiaTensor[1][2] = inertiaTensor[2][1] += mass * centerOfMass[1] * centerOfMass[2]; + inertiaTensor[2][0] = inertiaTensor[0][2] += mass * centerOfMass[2] * centerOfMass[0]; +} diff --git a/source/idlib/geometry/TraceModel.h b/source/idlib/geometry/TraceModel.h new file mode 100644 index 0000000..1793f59 --- /dev/null +++ b/source/idlib/geometry/TraceModel.h @@ -0,0 +1,174 @@ + +#ifndef __TRACEMODEL_H__ +#define __TRACEMODEL_H__ + +/* +=============================================================================== + + A trace model is an arbitrary polygonal model which is used by the + collision detection system to find collisions, contacts or the contents + of a volume. For collision detection speed reasons the number of vertices + and edges are limited. The trace model can have any shape. However convex + models are usually preferred. + +=============================================================================== +*/ + +class idVec3; +class idMat3; +class idBounds; + +// trace model type +enum traceModel_t { + TRM_INVALID, // invalid trm + TRM_BOX, // box + TRM_OCTAHEDRON, // octahedron + TRM_DODECAHEDRON, // dodecahedron + TRM_CYLINDER, // cylinder approximation + TRM_CONE, // cone approximation + TRM_BONE, // two tetrahedrons attached to each other + TRM_POLYGON, // arbitrary convex polygon + TRM_POLYGONVOLUME, // volume for arbitrary convex polygon + TRM_CUSTOM // loaded from map model or ASE/LWO +}; + +// these are bit cache limits +#define MAX_TRACEMODEL_VERTS 32 +#define MAX_TRACEMODEL_EDGES 32 +#define MAX_TRACEMODEL_POLYS 16 +#define MAX_TRACEMODEL_POLYEDGES 16 + +typedef idVec3 traceModelVert_t; + +struct traceModelEdge_t { + int v[2]; + idVec3 normal; +}; + +struct traceModelPoly_t { + idVec3 normal; + float dist; + idBounds bounds; + int numEdges; + int edges[MAX_TRACEMODEL_POLYEDGES]; +}; + +class idTraceModel { + +public: + traceModel_t type; + int numVerts; + traceModelVert_t verts[MAX_TRACEMODEL_VERTS]; + int numEdges; + traceModelEdge_t edges[MAX_TRACEMODEL_EDGES+1]; // edges[0] is unused because edge index signs are used for direction + int numPolys; + traceModelPoly_t polys[MAX_TRACEMODEL_POLYS]; + idVec3 offset; // offset to center of model + idBounds bounds; // bounds of model + bool isConvex; // true when model is convex + +public: + idTraceModel( void ); + // axial bounding box + idTraceModel( const idBounds &boxBounds ); + // cylinder approximation + idTraceModel( const idBounds &cylBounds, const int numSides, const bool alt_alignment = false ); + // bone + idTraceModel( const float length, const float width ); + + // axial box + void SetupBox( const idBounds &boxBounds ); + void SetupBox( const float size ); + // octahedron + void SetupOctahedron( const idBounds &octBounds ); + void SetupOctahedron( const float size ); + // dodecahedron + void SetupDodecahedron( const idBounds &dodBounds ); + void SetupDodecahedron( const float size ); + // cylinder approximation + void SetupCylinder( const idBounds &cylBounds, const int numSides, const bool alt_alignment = false ); + void SetupCylinder( const float height, const float width, const int numSides ); + // cone approximation + void SetupCone( const idBounds &coneBounds, const int numSides ); + void SetupCone( const float height, const float width, const int numSides ); + // two tetrahedrons attached to each other + void SetupBone( const float length, const float width ); + // arbitrary convex polygon + void SetupPolygon( const idVec3 *v, const int count ); + void SetupPolygon( const idWinding &w ); + + // generate edge normals + int GenerateEdgeNormals( void ); + // test whether or not the model is convex and set isConvex accordingly + void TestConvexity( void ); + // translate the trm + void Translate( const idVec3 &translation ); + // rotate the trm + void Rotate( const idMat3 &rotation ); + // shrink the model m units on all sides + void Shrink( const float m ); + // clear unused spots in the arrays + void ClearUnused( void ); + // make sure the trace model is well formed + bool Verify( void ); + + // compare + bool Compare( const idTraceModel &trm ) const; + bool operator==( const idTraceModel &trm ) const; + bool operator!=( const idTraceModel &trm ) const; + + // returns true of the model is a closed surface + bool IsClosedSurface( void ) const; + + // get the area of one of the polygons + float GetPolygonArea( int polyNum ) const; + // get the silhouette edges + int GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + int GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + // calculate mass properties assuming an uniform density + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + +private: + void InitBox( void ); + void InitOctahedron( void ); + void InitDodecahedron( void ); + void InitBone( void ); + + void ProjectionIntegrals( int polyNum, int a, int b, struct projectionIntegrals_t &integrals ) const; + void PolygonIntegrals( int polyNum, int a, int b, int c, struct polygonIntegrals_t &integrals ) const; + void VolumeIntegrals( struct volumeIntegrals_t &integrals ) const; + void VolumeFromPolygon( idTraceModel &trm, float thickness ) const; + int GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const; +}; + + +ID_INLINE idTraceModel::idTraceModel( void ) { + type = TRM_INVALID; + numVerts = numEdges = numPolys = 0; + bounds.Zero(); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &boxBounds ) { + InitBox(); + SetupBox( boxBounds ); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &cylBounds, const int numSides, const bool alt_alignment ) { + SetupCylinder( cylBounds, numSides, alt_alignment ); +} + +ID_INLINE idTraceModel::idTraceModel( const float length, const float width ) { + InitBone(); + SetupBone( length, width ); +} + +ID_INLINE bool idTraceModel::operator==( const idTraceModel &trm ) const { + return Compare( trm ); +} + +ID_INLINE bool idTraceModel::operator!=( const idTraceModel &trm ) const { + return !Compare( trm ); +} + +#endif /* !__TRACEMODEL_H__ */ + diff --git a/source/idlib/geometry/Winding.cpp b/source/idlib/geometry/Winding.cpp new file mode 100644 index 0000000..d552613 --- /dev/null +++ b/source/idlib/geometry/Winding.cpp @@ -0,0 +1,1624 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +//=============================================================== +// +// idWinding +// +//=============================================================== + +/* +============= +idWinding::ReAllocate +============= +*/ +bool idWinding::ReAllocate( int n, bool keep ) { + idVec5 *oldP; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + oldP = p; + n = (n+3) & ~3; // align up to multiple of four + p = new idVec5[n]; + if ( oldP ) { + if ( keep ) { + memcpy( p, oldP, numPoints * sizeof(p[0]) ); + } + delete[] oldP; + } + allocedSize = n; + + return true; +} + +/* +============= +idWinding::BaseForPlane +============= +*/ +void idWinding::BaseForPlane( const idVec3 &normal, const float dist ) { + idVec3 org, vright, vup; + + org = normal * dist; + + normal.NormalVectors( vup, vright ); + vup *= MAX_WORLD_SIZE; + vright *= MAX_WORLD_SIZE; + + EnsureAlloced( 4 ); + numPoints = 4; + p[0].ToVec3() = org - vright + vup; + p[0].s = p[0].t = 0.0f; + p[1].ToVec3() = org + vright + vup; + p[1].s = p[1].t = 0.0f; + p[2].ToVec3() = org + vright - vup; + p[2].s = p[2].t = 0.0f; + p[3].ToVec3() = org - vright - vup; + p[3].s = p[3].t = 0.0f; +} + +/* +============= +idWinding::Split +============= +*/ +int idWinding::Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const { + float * dists; + byte * sides; + int counts[3]; + float dot; + int i, j; + const idVec5 * p1, *p2; + idVec5 mid; + idWinding * f, *b; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + idPlane windingPlane; + + GetPlane( windingPlane ); + if ( windingPlane.Normal() * plane.Normal() > 0.0f ) { + *front = Copy(); + return SIDE_FRONT; + } else { + *back = Copy(); + return SIDE_BACK; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + *front = f = new idWinding(maxpts); + *back = b = new idWinding(maxpts); + + for (i = 0; i < numPoints; i++) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + mid.s = p2->s + dot * ( p1->s - p2->s ); + mid.t = p2->t + dot * ( p1->t - p2->t ); + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + if ( f->numPoints > maxpts || b->numPoints > maxpts ) { + idLib::common->FatalError( "idWinding::Split: points exceeded estimate." ); + } + + return SIDE_CROSS; +} + +/* +============= +idWinding::Clip +============= +*/ +idWinding *idWinding::Clip( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return this; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + delete this; + return NULL; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return this; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return this; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return this; +} + +/* +============= +idWinding::ClipInPlace +============= +*/ +bool idWinding::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float* dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return true; +} + +/* +============= +idWinding::Copy +============= +*/ +idWinding *idWinding::Copy( void ) const { + idWinding *w; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + w = new idWinding( numPoints ); + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof(p[0]) ); + return w; +} + +/* +============= +idWinding::Reverse +============= +*/ +idWinding *idWinding::Reverse( void ) const { + idWinding *w; + int i; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + w = new idWinding( numPoints ); + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============= +idWinding::ReverseSelf +============= +*/ +void idWinding::ReverseSelf( void ) { + idVec5 v; + int i; + + for ( i = 0; i < (numPoints>>1); i++ ) { + v = p[i]; + p[i] = p[numPoints - i - 1]; + p[numPoints - i - 1] = v; + } +} + +/* +============= +idWinding::Check +============= +*/ +bool idWinding::Check( bool print ) const { + int i, j; + float d, edgedist; + idVec3 dir, edgenormal; + float area; + idPlane plane; + + if ( numPoints < 3 ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: only %i points.", numPoints ); + } + return false; + } + + area = GetArea(); + if ( area < 1.0f ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: tiny area: %f", area ); + } + return false; + } + + GetPlane( plane ); + + for ( i = 0; i < numPoints; i++ ) { + const idVec3 &p1 = p[i].ToVec3(); + + // check if the winding is huge + for ( j = 0; j < 3; j++ ) { + if ( p1[j] >= MAX_WORLD_COORD || p1[j] <= MIN_WORLD_COORD ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d outside world %c-axis: %f", i, 'X'+j, p1[j] ); + } + return false; + } + } + + j = i + 1 == numPoints ? 0 : i + 1; + + // check if the point is on the face plane + d = p1 * plane.Normal() + plane[3]; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d off plane.", i ); + } + return false; + } + + // check if the edge isn't degenerate + const idVec3 &p2 = p[j].ToVec3(); + dir = p2 - p1; + + if ( dir.Length() < ON_EPSILON) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: edge %d is degenerate.", i ); + } + return false; + } + + // check if the winding is convex + edgenormal = plane.Normal().Cross( dir ); + edgenormal.Normalize(); + edgedist = p1 * edgenormal; + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0; j < numPoints; j++ ) { + if ( j == i ) { + continue; + } + d = p[j].ToVec3() * edgenormal; + if ( d > edgedist ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: non-convex." ); + } + return false; + } + } + } + return true; +} + +/* +============= +idWinding::GetArea +============= +*/ +float idWinding::GetArea( void ) const { + int i; + idVec3 d1, d2, cross; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1].ToVec3() - p[0].ToVec3(); + d2 = p[i].ToVec3() - p[0].ToVec3(); + cross = d1.Cross( d2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============= +idWinding::GetRadius +============= +*/ +float idWinding::GetRadius( const idVec3 ¢er ) const { + int i; + float radius, r; + idVec3 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i].ToVec3() - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============= +idWinding::GetCenter +============= +*/ +idVec3 idWinding::GetCenter( void ) const { + int i; + idVec3 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i].ToVec3(); + } + center *= ( 1.0f / numPoints ); + return center; +} + +// RAVEN BEGIN +// scork: Splash Damage's light-resize code +/* +============= +idWinding::GetNormal +============= +*/ +idVec3 idWinding::GetNormal( void ) const { + idVec3 v1, v2, center, normal; + + if ( numPoints < 3 ) { + normal.Zero(); + return normal; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + normal = v2.Cross( v1 ); + normal.Normalize(); + return normal; +} +// RAVEN END + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idVec3 &normal, float &dist ) const { + idVec3 v1, v2, center; + + if ( numPoints < 3 ) { + normal.Zero(); + dist = 0.0f; + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + normal = v2.Cross( v1 ); + normal.Normalize(); + dist = p[0].ToVec3() * normal; +} + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idPlane &plane ) const { + idVec3 v1, v2; + idVec3 center; + + if ( numPoints < 3 ) { + plane.Zero(); + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + plane.SetNormal( v2.Cross( v1 ) ); + plane.Normalize(); + plane.FitThroughPoint( p[0].ToVec3() ); +} + +/* +============= +idWinding::GetBounds +============= +*/ +void idWinding::GetBounds( idBounds &bounds ) const { + int i; + + if ( !numPoints ) { + bounds.Clear(); + return; + } + + bounds[0] = bounds[1] = p[0].ToVec3(); + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + if ( p[i].z < bounds[0].z ) { + bounds[0].z = p[i].z; + } else if ( p[i].z > bounds[1].z ) { + bounds[1].z = p[i].z; + } + } +} + +/* +============= +idWinding::RemoveEqualPoints +============= +*/ +void idWinding::RemoveEqualPoints( const float epsilon ) { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + if ( (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).LengthSqr() >= Square( epsilon ) ) { + continue; + } + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::RemoveColinearPoints +============= +*/ +void idWinding::RemoveColinearPoints( const idVec3 &normal, const float epsilon ) { + int i, j; + idVec3 edgeNormal; + float dist; + + if ( numPoints <= 3 ) { + return; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + edgeNormal = (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).Cross( normal ); + edgeNormal.Normalize(); + dist = edgeNormal * p[i].ToVec3(); + + if ( idMath::Fabs( edgeNormal * p[(i+1)%numPoints].ToVec3() - dist ) > epsilon ) { + continue; + } + + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::AddToConvexHull + + Adds the given winding to the convex hull. + Assumes the current winding already is a convex hull with three or more points. +============= +*/ +void idWinding::AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon ) { + int i, j, k; + idVec3 dir; + float d; + int maxPts; + idVec3 * hullDirs; + bool * hullSide; + bool outside; + int numNewHullPoints; + idVec5 * newHullPoints; + + if ( !winding ) { + return; + } + + maxPts = this->numPoints + winding->numPoints; + + if ( !this->EnsureAlloced( maxPts, true ) ) { + return; + } + + newHullPoints = (idVec5 *) _alloca( maxPts * sizeof( idVec5 ) ); + hullDirs = (idVec3 *) _alloca( maxPts * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( maxPts * sizeof( bool ) ); + + for ( i = 0; i < winding->numPoints; i++ ) { + const idVec5 &p1 = winding->p[i]; + + // calculate hull edge vectors + for ( j = 0; j < this->numPoints; j++ ) { + dir = this->p[ (j + 1) % this->numPoints ].ToVec3() - this->p[ j ].ToVec3(); + dir.Normalize(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < this->numPoints; j++ ) { + dir = p1.ToVec3() - this->p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0; j < this->numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % this->numPoints ] ) { + break; + } + } + if ( j >= this->numPoints ) { + continue; + } + + // insert the point here + newHullPoints[0] = p1; + numNewHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % this->numPoints; + for ( k = 0; k < this->numPoints; k++ ) { + if ( hullSide[ (j+k) % this->numPoints ] && hullSide[ (j+k+1) % this->numPoints ] ) { + continue; + } + newHullPoints[numNewHullPoints] = this->p[ (j+k+1) % this->numPoints ]; + numNewHullPoints++; + } + + this->numPoints = numNewHullPoints; + memcpy( this->p, newHullPoints, numNewHullPoints * sizeof(idVec5) ); + } +} + +/* +============= +idWinding::AddToConvexHull + + Add a point to the convex hull. + The current winding must be convex but may be degenerate and can have less than three points. +============= +*/ +void idWinding::AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon ) { + int j, k, numHullPoints; + idVec3 dir; + float d; + idVec3 * hullDirs; + bool * hullSide; + idVec5 * hullPoints; + bool outside; + + switch( numPoints ) { + case 0: { + p[0] = point; + numPoints++; + return; + } + case 1: { + // don't add the same point second + if ( p[0].ToVec3().Compare( point, epsilon ) ) { + return; + } + p[1].ToVec3() = point; + numPoints++; + return; + } + case 2: { + // don't add a point if it already exists + if ( p[0].ToVec3().Compare( point, epsilon ) || p[1].ToVec3().Compare( point, epsilon ) ) { + return; + } + // if only two points make sure we have the right ordering according to the normal + dir = point - p[0].ToVec3(); + dir = dir.Cross( p[1].ToVec3() - p[0].ToVec3() ); + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + // points don't make a plane + return; + } + if ( dir * normal > 0.0f ) { + p[2].ToVec3() = point; + } + else { + p[2] = p[1]; + p[1].ToVec3() = point; + } + numPoints++; + return; + } + } + + hullDirs = (idVec3 *) _alloca( numPoints * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( numPoints * sizeof( bool ) ); + + // calculate hull edge vectors + for ( j = 0; j < numPoints; j++ ) { + dir = p[(j + 1) % numPoints].ToVec3() - p[j].ToVec3(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < numPoints; j++ ) { + dir = point - p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + return; + } + + // find the back side to front side transition + for ( j = 0; j < numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % numPoints ] ) { + break; + } + } + if ( j >= numPoints ) { + return; + } + + hullPoints = (idVec5 *) _alloca( (numPoints+1) * sizeof( idVec5 ) ); + + // insert the point here + hullPoints[0] = point; + numHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % numPoints; + for ( k = 0; k < numPoints; k++ ) { + if ( hullSide[ (j+k) % numPoints ] && hullSide[ (j+k+1) % numPoints ] ) { + continue; + } + hullPoints[numHullPoints] = p[ (j+k+1) % numPoints ]; + numHullPoints++; + } + + if ( !EnsureAlloced( numHullPoints, false ) ) { + return; + } + numPoints = numHullPoints; + memcpy( p, hullPoints, numHullPoints * sizeof(idVec5) ); +} + +/* +============= +idWinding::TryMerge +============= +*/ +#define CONTINUOUS_EPSILON 0.005f + +idWinding *idWinding::TryMerge( const idWinding &w, const idVec3 &planenormal, int keep ) const { + idVec3 *p1, *p2, *p3, *p4, *back; + idWinding *newf; + const idWinding *f1, *f2; + int i, j, k, l; + idVec3 normal, delta; + float dot; + bool keep1, keep2; + + f1 = this; + f2 = &w; + // + // find a idLib::common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; + + for ( i = 0; i < f1->numPoints; i++ ) { + p1 = &f1->p[i].ToVec3(); + p2 = &f1->p[(i+1) % f1->numPoints].ToVec3(); + for ( j = 0; j < f2->numPoints; j++ ) { + p3 = &f2->p[j].ToVec3(); + p4 = &f2->p[(j+1) % f2->numPoints].ToVec3(); + for (k = 0; k < 3; k++ ) { + if ( idMath::Fabs((*p1)[k] - (*p4)[k]) > 0.1f ) { + break; + } + if ( idMath::Fabs((*p2)[k] - (*p3)[k]) > 0.1f ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j < f2->numPoints ) { + break; + } + } + + if ( i == f1->numPoints ) { + return NULL; // no matching edges + } + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numPoints-1)%f1->numPoints].ToVec3(); + delta = (*p1) - (*back); + normal = planenormal.Cross(delta); + normal.Normalize(); + + back = &f2->p[(j+2)%f2->numPoints].ToVec3(); + delta = (*back) - (*p1); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep1 = (bool)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numPoints].ToVec3(); + delta = (*back) - (*p2); + normal = planenormal.Cross( delta ); + normal.Normalize(); + + back = &f2->p[(j+f2->numPoints-1)%f2->numPoints].ToVec3(); + delta = (*back) - (*p2); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep2 = (bool)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + newf = new idWinding( f1->numPoints + f2->numPoints ); + + // copy first polygon + for ( k = (i+1) % f1->numPoints; k != i; k = (k+1) % f1->numPoints ) { + if ( !keep && k == (i+1) % f1->numPoints && !keep2 ) { + continue; + } + + newf->p[newf->numPoints] = f1->p[k]; + newf->numPoints++; + } + + // copy second polygon + for ( l = (j+1) % f2->numPoints; l != j; l = (l+1) % f2->numPoints ) { + if ( !keep && l == (j+1) % f2->numPoints && !keep1 ) { + continue; + } + newf->p[newf->numPoints] = f2->p[l]; + newf->numPoints++; + } + + return newf; +} + +/* +============= +idWinding::RemovePoint +============= +*/ +void idWinding::RemovePoint( int point ) { + if ( point < 0 || point >= numPoints ) { + idLib::common->FatalError( "idWinding::removePoint: point out of range" ); + } + if ( point < numPoints - 1) { + memmove(&p[point], &p[point+1], (numPoints - point - 1) * sizeof(p[0]) ); + } + numPoints--; +} + +/* +============= +idWinding::InsertPoint +============= +*/ +void idWinding::InsertPoint( const idVec3 &point, int spot ) { + int i; + + if ( spot > numPoints ) { + idLib::common->FatalError( "idWinding::insertPoint: spot > numPoints" ); + } + + if ( spot < 0 ) { + idLib::common->FatalError( "idWinding::insertPoint: spot < 0" ); + } + + EnsureAlloced( numPoints+1, true ); + for ( i = numPoints; i > spot; i-- ) { + p[i] = p[i-1]; + } + p[spot] = point; + numPoints++; +} + +/* +============= +idWinding::InsertPointIfOnEdge +============= +*/ +bool idWinding::InsertPointIfOnEdge( const idVec3 &point, const idPlane &plane, const float epsilon ) { + int i; + float dist, dot; + idVec3 normal; + + // point may not be too far from the winding plane + if ( idMath::Fabs( plane.Distance( point ) ) > epsilon ) { + return false; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + normal = (p[(i+1)%numPoints].ToVec3() - p[i].ToVec3()).Cross( plane.Normal() ); + normal.Normalize(); + dist = normal * p[i].ToVec3(); + + if ( idMath::Fabs( normal * point - dist ) > epsilon ) { + continue; + } + + normal = plane.Normal().Cross( normal ); + dot = normal * point; + + dist = dot - normal * p[i].ToVec3(); + + if ( dist < epsilon ) { + // if the winding already has the point + if ( dist > -epsilon ) { + return false; + } + continue; + } + + dist = dot - normal * p[(i+1)%numPoints].ToVec3(); + + if ( dist > -epsilon ) { + // if the winding already has the point + if ( dist < epsilon ) { + return false; + } + continue; + } + + InsertPoint( point, i+1 ); + return true; + } + return false; +} + +/* +============= +idWinding::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding::IsTiny( void ) const { + int i; + float len; + idVec3 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints].ToVec3() - p[i].ToVec3(); + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding::IsHuge +============= +*/ +bool idWinding::IsHuge( void ) const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding::Print +============= +*/ +void idWinding::Print( void ) const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f, %5.1f)\n", p[i][0], p[i][1], p[i][2] ); + } +} + +/* +============= +idWinding::PlaneDistance +============= +*/ +float idWinding::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding::PlaneSide +============= +*/ +int idWinding::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============= +idWinding::PlanesConcave +============= +*/ +#define WCONVEX_EPSILON 0.2f + +bool idWinding::PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const { + int i; + + // check if one of the points of winding 1 is at the back of the plane of winding 2 + for ( i = 0; i < numPoints; i++ ) { + if ( normal2 * p[i].ToVec3() - dist2 > WCONVEX_EPSILON ) { + return true; + } + } + // check if one of the points of winding 2 is at the back of the plane of winding 1 + for ( i = 0; i < w2.numPoints; i++ ) { + if ( normal1 * w2.p[i].ToVec3() - dist1 > WCONVEX_EPSILON ) { + return true; + } + } + + return false; +} + +/* +============= +idWinding::PointInside +============= +*/ +bool idWinding::PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const { + int i; + idVec3 dir, n, pointvec; + + for ( i = 0; i < numPoints; i++ ) { + dir = p[(i+1) % numPoints].ToVec3() - p[i].ToVec3(); + pointvec = point - p[i].ToVec3(); + + n = dir.Cross( normal ); + + if ( pointvec * n < -epsilon ) { + return false; + } + } + return true; +} + +/* +============= +idWinding::LineIntersection +============= +*/ +bool idWinding::LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float front, back, frac; + idVec3 mid; + + front = windingPlane.Distance( start ); + back = windingPlane.Distance( end ); + + // if both points at the same side of the plane + if ( front < 0.0f && back < 0.0f ) { + return false; + } + + if ( front > 0.0f && back > 0.0f ) { + return false; + } + + // if back face culled + if ( backFaceCull && front < 0.0f ) { + return false; + } + + // get point of intersection with winding plane + if ( idMath::Fabs(front - back) < 0.0001f ) { + mid = end; + } + else { + frac = front / (front - back); + mid[0] = start[0] + (end[0] - start[0]) * frac; + mid[1] = start[1] + (end[1] - start[1]) * frac; + mid[2] = start[2] + (end[2] - start[2]) * frac; + } + + return PointInside( windingPlane.Normal(), mid, 0.0f ); +} + +/* +============= +idWinding::RayIntersection +============= +*/ +bool idWinding::RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i; + bool side, lastside = false; + idPluecker pl1, pl2; + + scale = 0.0f; + pl1.FromRay( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + pl2.FromLine( p[i].ToVec3(), p[(i+1)%numPoints].ToVec3() ); + side = pl1.PermutedInnerProduct( pl2 ) > 0.0f; + if ( i && side != lastside ) { + return false; + } + lastside = side; + } + if ( !backFaceCull || lastside ) { + windingPlane.RayIntersection( start, dir, scale ); + return true; + } + return false; +} + +/* +================= +idWinding::TriangleArea +================= +*/ +float idWinding::TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ) { + idVec3 v1, v2; + idVec3 cross; + + v1 = b - a; + v2 = c - a; + cross = v1.Cross( v2 ); + return 0.5f * cross.Length(); +} + + +//=============================================================== +// +// idFixedWinding +// +//=============================================================== + +/* +============= +idFixedWinding::ReAllocate +============= +*/ +bool idFixedWinding::ReAllocate( int n, bool keep ) { + + assert( n <= MAX_POINTS_ON_WINDING ); + + if ( n > MAX_POINTS_ON_WINDING ) { + idLib::common->Printf("WARNING: idFixedWinding -> MAX_POINTS_ON_WINDING overflowed\n"); + return false; + } + return true; +} + +/* +============= +idFixedWinding::Split +============= +*/ +int idFixedWinding::Split( idFixedWinding *back, const idPlane &plane, const float epsilon ) { + int counts[3]; + float dists[MAX_POINTS_ON_WINDING+4]; + byte sides[MAX_POINTS_ON_WINDING+4]; + float dot; + int i, j; + idVec5 *p1, *p2; + idVec5 mid; + idFixedWinding out; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if ( !counts[SIDE_BACK] ) { + if ( !counts[SIDE_FRONT] ) { + return SIDE_ON; + } + else { + return SIDE_FRONT; + } + } + + if ( !counts[SIDE_FRONT] ) { + return SIDE_BACK; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + out.numPoints = 0; + back->numPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + back->p[back->numPoints] = *p1; + back->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + } + if ( sides[i] == SIDE_BACK ) { + back->p[back->numPoints] = *p1; + back->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + // generate a split point + j = i + 1; + if ( j >= numPoints ) { + p2 = &p[0]; + } + else { + p2 = &p[j]; + } + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + out.p[out.numPoints] = mid; + out.numPoints++; + back->p[back->numPoints] = mid; + back->numPoints++; + } + for ( i = 0; i < out.numPoints; i++ ) { + p[i] = out.p[i]; + } + numPoints = out.numPoints; + + return SIDE_CROSS; +} diff --git a/source/idlib/geometry/Winding.h b/source/idlib/geometry/Winding.h new file mode 100644 index 0000000..f9b4a55 --- /dev/null +++ b/source/idlib/geometry/Winding.h @@ -0,0 +1,397 @@ + +#ifndef __WINDING_H__ +#define __WINDING_H__ + +/* +=============================================================================== + + A winding is an arbitrary convex polygon defined by an array of points. + +=============================================================================== +*/ + +class idWinding { + +public: + idWinding( void ); + explicit idWinding( const int n ); // allocate for n points + explicit idWinding( const idVec3 *verts, const int n ); // winding from points + explicit idWinding( const idVec3 &normal, const float dist ); // base winding for plane + explicit idWinding( const idPlane &plane ); // base winding for plane + explicit idWinding( const idWinding &winding ); + virtual ~idWinding( void ); + + idWinding & operator=( const idWinding &winding ); + const idVec5 & operator[]( const int index ) const; + idVec5 & operator[]( const int index ); + + // add a point to the end of the winding point array + idWinding & operator+=( const idVec3 &v ); + idWinding & operator+=( const idVec5 &v ); +// RAVEN BEGIN + idWinding & operator+=( const idDrawVert &dv ); +// RAVEN END + void AddPoint( const idVec3 &v ); + void AddPoint( const idVec5 &v ); + + // number of points on winding + int GetNumPoints( void ) const; + void SetNumPoints( int n ); + virtual void Clear( void ); + + // huge winding for plane, the points go counter clockwise when facing the front of the plane + void BaseForPlane( const idVec3 &normal, const float dist ); + void BaseForPlane( const idPlane &plane ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const; + // returns the winding fragment at the front of the clipping plane, + // if there is nothing at the front the winding itself is destroyed and NULL is returned + idWinding * Clip( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns a copy of the winding + idWinding * Copy( void ) const; + idWinding * Reverse( void ) const; + void ReverseSelf( void ); + void RemoveEqualPoints( const float epsilon = ON_EPSILON ); + void RemoveColinearPoints( const idVec3 &normal, const float epsilon = ON_EPSILON ); + void RemovePoint( int point ); + void InsertPoint( const idVec3 &point, int spot ); + bool InsertPointIfOnEdge( const idVec3 &point, const idPlane &plane, const float epsilon = ON_EPSILON ); + // add a winding to the convex hull + void AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // add a point to the convex hull + void AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // tries to merge 'this' with the given winding, returns NULL if merge fails, both 'this' and 'w' stay intact + // 'keep' tells if the contacting points should stay even if they create colinear edges + idWinding * TryMerge( const idWinding &w, const idVec3 &normal, int keep = false ) const; + // check whether the winding is valid or not + bool Check( bool print = true ) const; + + float GetArea( void ) const; + idVec3 GetCenter( void ) const; + +// RAVEN BEGIN +// scork: Splash Damage's light-resize code + idVec3 GetNormal( void ) const; +// RAVEN END + float GetRadius( const idVec3 ¢er ) const; + void GetPlane( idVec3 &normal, float &dist ) const; + void GetPlane( idPlane &plane ) const; + void GetBounds( idBounds &bounds ) const; + + bool IsTiny( void ) const; + bool IsHuge( void ) const; // base winding for a plane is typically huge + void Print( void ) const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const; + + bool PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const; + // returns true if the line or ray intersects the winding + bool LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + + static float TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ); + +protected: + int numPoints; // number of points + idVec5 * p; // pointer to point data + int allocedSize; + + bool EnsureAlloced( int n, bool keep = false ); + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idWinding::idWinding( void ) { + numPoints = allocedSize = 0; + p = NULL; +} + +ID_INLINE idWinding::idWinding( int n ) { + numPoints = allocedSize = 0; + p = NULL; + EnsureAlloced( n ); +} + +ID_INLINE idWinding::idWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = allocedSize = 0; + p = NULL; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0.0f; + } + numPoints = n; +} + +ID_INLINE idWinding::idWinding( const idVec3 &normal, const float dist ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( normal, dist ); +} + +ID_INLINE idWinding::idWinding( const idPlane &plane ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( plane ); +} + +ID_INLINE idWinding::idWinding( const idWinding &winding ) { + int i; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idWinding::~idWinding( void ) { + delete[] p; + p = NULL; +} + +ID_INLINE idWinding &idWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.numPoints ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec5 &idWinding::operator[]( const int index ) const { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idVec5 &idWinding::operator[]( const int index ) { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec3 &v ) { + AddPoint( v ); + return *this; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec5 &v ) { + AddPoint( v ); + return *this; +} + +// RAVEN BEGIN +ID_INLINE idWinding &idWinding::operator+=( const idDrawVert &dv ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return *this; + } + p[numPoints] = idVec5( dv.xyz, dv.st ); + numPoints++; + return *this; +} +// RAVEN END + +ID_INLINE void idWinding::AddPoint( const idVec3 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE void idWinding::AddPoint( const idVec5 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE int idWinding::GetNumPoints( void ) const { + return numPoints; +} + +ID_INLINE void idWinding::SetNumPoints( int n ) { + if ( !EnsureAlloced( n, true ) ) { + return; + } + numPoints = n; +} + +ID_INLINE void idWinding::Clear( void ) { + numPoints = 0; + // RAVENBEGIN + // cdr: need to clear the allocated size too, or we wont' be able to readd points + allocedSize = 0; + // RAVENEND + delete[] p; + p = NULL; +} + +ID_INLINE void idWinding::BaseForPlane( const idPlane &plane ) { + BaseForPlane( plane.Normal(), plane.Dist() ); +} + +ID_INLINE bool idWinding::EnsureAlloced( int n, bool keep ) { + if ( n > allocedSize ) { + return ReAllocate( n, keep ); + } + return true; +} + + +/* +=============================================================================== + + idFixedWinding is a fixed buffer size winding not using + memory allocations. + + When an operation would overflow the fixed buffer a warning + is printed and the operation is safely cancelled. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING 64 + +class idFixedWinding : public idWinding { + +public: + idFixedWinding( void ); + explicit idFixedWinding( const int n ); + explicit idFixedWinding( const idVec3 *verts, const int n ); + explicit idFixedWinding( const idVec3 &normal, const float dist ); + explicit idFixedWinding( const idPlane &plane ); + explicit idFixedWinding( const idWinding &winding ); + explicit idFixedWinding( const idFixedWinding &winding ); + virtual ~idFixedWinding( void ); + + idFixedWinding &operator=( const idWinding &winding ); + + virtual void Clear( void ); + + // splits the winding in a back and front part, 'this' becomes the front part + // returns a SIDE_? + int Split( idFixedWinding *back, const idPlane &plane, const float epsilon = ON_EPSILON ); + +protected: + idVec5 data[MAX_POINTS_ON_WINDING]; // point data + + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idFixedWinding::idFixedWinding( void ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( int n ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0; + } + numPoints = n; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 &normal, const float dist ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( normal, dist ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idPlane &plane ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( plane ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idFixedWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::~idFixedWinding( void ) { + p = NULL; // otherwise it tries to free the fixed buffer +} + +ID_INLINE idFixedWinding &idFixedWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); + return *this; +} + +ID_INLINE void idFixedWinding::Clear( void ) { + numPoints = 0; +} +#endif /* !__WINDING_H__ */ diff --git a/source/idlib/geometry/Winding2D.cpp b/source/idlib/geometry/Winding2D.cpp new file mode 100644 index 0000000..2fbbd38 --- /dev/null +++ b/source/idlib/geometry/Winding2D.cpp @@ -0,0 +1,727 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Winding2D.h" + + +/* +============ +GetAxialBevel +============ +*/ +bool GetAxialBevel( const idVec3 &plane1, const idVec3 &plane2, const idVec2 &point, idVec3 &bevel ) { + if ( FLOATSIGNBITSET( plane1.x ) ^ FLOATSIGNBITSET( plane2.x ) ) { + if ( idMath::Fabs( plane1.x ) > 0.1f && idMath::Fabs( plane2.x ) > 0.1f ) { + bevel.x = 0.0f; + if ( FLOATSIGNBITSET( plane1.y ) ) { + bevel.y = -1.0f; + } + else { + bevel.y = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + if ( FLOATSIGNBITSET( plane1.y ) ^ FLOATSIGNBITSET( plane2.y ) ) { + if ( idMath::Fabs( plane1.y ) > 0.1f && idMath::Fabs( plane2.y ) > 0.1f ) { + bevel.y = 0.0f; + if ( FLOATSIGNBITSET( plane1.x ) ) { + bevel.x = -1.0f; + } + else { + bevel.x = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + return false; +} + +/* +============ +idWinding2D::ExpandForAxialBox +============ +*/ +void idWinding2D::ExpandForAxialBox( const idVec2 bounds[2] ) { + int i, j, numPlanes; + idVec2 v; + idVec3 planes[MAX_POINTS_ON_WINDING_2D], plane, bevel; + + // get planes for the edges and add bevels + for ( numPlanes = i = 0; i < numPoints; i++ ) { + j = (i+1) % numPoints; + if ( ( p[j] - p[i] ).LengthSqr() < 0.01f ) { + continue; + } + plane = Plane2DFromPoints( p[i], p[j], true ); + if ( i ) { + if ( GetAxialBevel( planes[numPlanes-1], plane, p[i], bevel ) ) { + planes[numPlanes++] = bevel; + } + } + assert( numPlanes < MAX_POINTS_ON_WINDING_2D ); + planes[numPlanes++] = plane; + } + if ( GetAxialBevel( planes[numPlanes-1], planes[0], p[0], bevel ) ) { + planes[numPlanes++] = bevel; + } + + // expand the planes + for ( i = 0; i < numPlanes; i++ ) { + v.x = bounds[ FLOATSIGNBITSET( planes[i].x ) ].x; + v.y = bounds[ FLOATSIGNBITSET( planes[i].y ) ].y; + planes[i].z += v.x * planes[i].x + v.y * planes[i].y; + } + + // get intersection points of the planes + for ( numPoints = i = 0; i < numPlanes; i++ ) { + if ( Plane2DIntersection( planes[(i+numPlanes-1) % numPlanes], planes[i], p[numPoints] ) ) { + numPoints++; + } + } +} + +/* +============ +idWinding2D::Expand +============ +*/ +void idWinding2D::Expand( const float d ) { + int i; + idVec2 edgeNormals[MAX_POINTS_ON_WINDING_2D]; + + for ( i = 0; i < numPoints; i++ ) { + idVec2 &start = p[i]; + idVec2 &end = p[(i+1)%numPoints]; + edgeNormals[i].x = start.y - end.y; + edgeNormals[i].y = end.x - start.x; + edgeNormals[i].Normalize(); + edgeNormals[i] *= d; + } + + for ( i = 0; i < numPoints; i++ ) { + p[i] += edgeNormals[i] + edgeNormals[(i+numPoints-1)%numPoints]; + } +} + +/* +============= +idWinding2D::Split +============= +*/ +int idWinding2D::Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const { + float dists[MAX_POINTS_ON_WINDING_2D]; + byte sides[MAX_POINTS_ON_WINDING_2D]; + int counts[3]; + float dot; + int i, j; + const idVec2 * p1, *p2; + idVec2 mid; + idWinding2D * f; + idWinding2D * b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + + *front = f = new idWinding2D; + *back = b = new idWinding2D; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + return SIDE_CROSS; +} + +/* +============ +idWinding2D::ClipInPlace +============ +*/ +bool idWinding2D::ClipInPlace( const idVec3 &plane, const float epsilon, const bool keepOn ) { + int i, j, maxpts, newNumPoints; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float dot, dists[MAX_POINTS_ON_WINDING_2D+1]; + idVec2 *p1, *p2, mid, newPoints[MAX_POINTS_ON_WINDING_2D+4]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( newNumPoints >= MAX_POINTS_ON_WINDING_2D ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec2) ); + + return true; +} + +/* +============= +idWinding2D::Copy +============= +*/ +idWinding2D *idWinding2D::Copy( void ) const { + idWinding2D *w; + + w = new idWinding2D; + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof( p[0] ) ); + return w; +} + +/* +============= +idWinding2D::Reverse +============= +*/ +idWinding2D *idWinding2D::Reverse( void ) const { + idWinding2D *w; + int i; + + w = new idWinding2D; + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============ +idWinding2D::GetArea +============ +*/ +float idWinding2D::GetArea( void ) const { + int i; + idVec2 d1, d2; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1] - p[0]; + d2 = p[i] - p[0]; + total += d1.x * d2.y - d1.y * d2.x; + } + return total * 0.5f; +} + +/* +============ +idWinding2D::GetCenter +============ +*/ +idVec2 idWinding2D::GetCenter( void ) const { + int i; + idVec2 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i]; + } + center *= ( 1.0f / numPoints ); + return center; +} + +/* +============ +idWinding2D::GetRadius +============ +*/ +float idWinding2D::GetRadius( const idVec2 ¢er ) const { + int i; + float radius, r; + idVec2 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i] - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============ +idWinding2D::GetBounds +============ +*/ +void idWinding2D::GetBounds( idVec2 bounds[2] ) const { + int i; + + if ( !numPoints ) { + bounds[0].x = bounds[0].y = idMath::INFINITY; + bounds[1].x = bounds[1].y = -idMath::INFINITY; + return; + } + bounds[0] = bounds[1] = p[0]; + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + } +} + +/* +============= +idWinding2D::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding2D::IsTiny( void ) const { + int i; + float len; + idVec2 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints] - p[i]; + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding2D::IsHuge +============= +*/ +bool idWinding2D::IsHuge( void ) const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 2; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding2D::Print +============= +*/ +void idWinding2D::Print( void ) const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f)\n", p[i][0], p[i][1] ); + } +} + +/* +============= +idWinding2D::PlaneDistance +============= +*/ +float idWinding2D::PlaneDistance( const idVec3 &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding2D::PlaneSide +============= +*/ +int idWinding2D::PlaneSide( const idVec3 &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============ +idWinding2D::PointInside +============ +*/ +bool idWinding2D::PointInside( const idVec2 &point, const float epsilon ) const { + int i; + float d; + idVec3 plane; + + for ( i = 0; i < numPoints; i++ ) { + plane = Plane2DFromPoints( p[i], p[(i+1) % numPoints] ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idWinding2D::LineIntersection +============ +*/ +bool idWinding2D::LineIntersection( const idVec2 &start, const idVec2 &end ) const { + int i, numEdges; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromPoints( start, end ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = edges[0].x * end.x + edges[0].y * end.y + edges[0].z; + if ( FLOATSIGNBITNOTSET( d1 ) & FLOATSIGNBITNOTSET( d2 ) ) { + return false; + } + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = edges[1].x * end.x + edges[1].y * end.y + edges[1].z; + if ( FLOATSIGNBITNOTSET( d1 ) & FLOATSIGNBITNOTSET( d2 ) ) { + return false; + } + return true; +} + +/* +============ +idWinding2D::RayIntersection +============ +*/ +bool idWinding2D::RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums ) const { + int i, numEdges, localEdgeNums[2]; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + scale1 = scale2 = 0.0f; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromVecs( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + localEdgeNums[numEdges] = i; + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = - ( edges[0].x * dir.x + edges[0].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale1 = d1 / d2; + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = - ( edges[1].x * dir.x + edges[1].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale2 = d1 / d2; + + if ( idMath::Fabs( scale1 ) > idMath::Fabs( scale2 ) ) { + idSwap( scale1, scale2 ); + idSwap( localEdgeNums[0], localEdgeNums[1] ); + } + + if ( edgeNums ) { + edgeNums[0] = localEdgeNums[0]; + edgeNums[1] = localEdgeNums[1]; + } + return true; +} diff --git a/source/idlib/geometry/Winding2D.h b/source/idlib/geometry/Winding2D.h new file mode 100644 index 0000000..7eee6f4 --- /dev/null +++ b/source/idlib/geometry/Winding2D.h @@ -0,0 +1,142 @@ + +#ifndef __WINDING2D_H__ +#define __WINDING2D_H__ + +/* +=============================================================================== + + A 2D winding is an arbitrary convex 2D polygon defined by an array of points. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING_2D 16 + + +class idWinding2D { +public: + idWinding2D( void ); + + idWinding2D & operator=( const idWinding2D &winding ); + const idVec2 & operator[]( const int index ) const; + idVec2 & operator[]( const int index ); + + void Clear( void ); + void AddPoint( const idVec2 &point ); + int GetNumPoints( void ) const; + + void Expand( const float d ); + void ExpandForAxialBox( const idVec2 bounds[2] ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idVec3 &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + idWinding2D * Copy( void ) const; + idWinding2D * Reverse( void ) const; + + float GetArea( void ) const; + idVec2 GetCenter( void ) const; + float GetRadius( const idVec2 ¢er ) const; + void GetBounds( idVec2 bounds[2] ) const; + + bool IsTiny( void ) const; + bool IsHuge( void ) const; // base winding for a plane is typically huge + void Print( void ) const; + + float PlaneDistance( const idVec3 &plane ) const; + int PlaneSide( const idVec3 &plane, const float epsilon = ON_EPSILON ) const; + + bool PointInside( const idVec2 &point, const float epsilon ) const; + bool LineIntersection( const idVec2 &start, const idVec2 &end ) const; + bool RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums = NULL ) const; + + static idVec3 Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize = false ); + static idVec3 Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize = false ); + static bool Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ); + +private: + int numPoints; + idVec2 p[MAX_POINTS_ON_WINDING_2D]; +}; + +ID_INLINE idWinding2D::idWinding2D( void ) { + numPoints = 0; +} + +ID_INLINE idWinding2D &idWinding2D::operator=( const idWinding2D &winding ) { + int i; + + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec2 &idWinding2D::operator[]( const int index ) const { + return p[ index ]; +} + +ID_INLINE idVec2 &idWinding2D::operator[]( const int index ) { + return p[ index ]; +} + +ID_INLINE void idWinding2D::Clear( void ) { + numPoints = 0; +} + +ID_INLINE void idWinding2D::AddPoint( const idVec2 &point ) { + p[numPoints++] = point; +} + +ID_INLINE int idWinding2D::GetNumPoints( void ) const { + return numPoints; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize ) { + idVec3 plane; + plane.x = start.y - end.y; + plane.y = end.x - start.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize ) { + idVec3 plane; + plane.x = -dir.y; + plane.y = dir.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE bool idWinding2D::Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ) { + float n00, n01, n11, det, invDet, f0, f1; + + n00 = plane1.x * plane1.x + plane1.y * plane1.y; + n01 = plane1.x * plane2.x + plane1.y * plane2.y; + n11 = plane2.x * plane2.x + plane2.y * plane2.y; + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane2.z - n11 * plane1.z ) * invDet; + f1 = ( n01 * plane1.z - n00 * plane2.z ) * invDet; + point.x = f0 * plane1.x + f1 * plane2.x; + point.y = f0 * plane1.y + f1 * plane2.y; + return true; +} + +#endif /* !__WINDING2D_H__ */ diff --git a/source/idlib/geometry/rvVertex.h b/source/idlib/geometry/rvVertex.h new file mode 100644 index 0000000..b55d8b9 --- /dev/null +++ b/source/idlib/geometry/rvVertex.h @@ -0,0 +1,81 @@ +// +// rvVertex.h - Describes some commonly used vertices (alternatives to the idDrawVert) +// Date: 1/10/05 +// Created by: Dwight Luetscher - Raven Software +// + +#ifndef __RV_VERTEX_H__ +#define __RV_VERTEX_H__ + +// +// rvBlend4DrawVert +// +// a vertex that is used to communicate data to drawing vertices stored in vertex buffers +// +class rvBlend4DrawVert { +public: + idVec3 xyz; + int blendIndex[4]; + float blendWeight[4]; // NOTE: the vertex stored in the actual buffer that is actually used for drawing may leave out the last weight (implied 1 - sum of other weights) + idVec3 normal; + idVec3 tangent; + idVec3 binormal; + byte color[4]; // diffuse color, [0] red, [1] green, [2] blue, [3] alpha + idVec2 st; +}; + +// +// rvSilTraceVertT +// +// a transformed vert that typically resides in system-memory and is used for operations +// like silhouette determination and trace testing +// +class rvSilTraceVertT { +public: + idVec4 xyzw; + + float operator[]( const int index ) const; + float & operator[]( const int index ); + + void Clear( void ); + + void Lerp( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ); + void LerpAll( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ); +}; + +#define SILTRACEVERT_SIZE_SHIFT 4 +#define SILTRACEVERT_SIZE (1 << SILTRACEVERT_SIZE_SHIFT) +#define SILTRACEVERT_XYZW_OFFSET 0 + +assert_sizeof( rvSilTraceVertT, SILTRACEVERT_SIZE ); +assert_sizeof( rvSilTraceVertT, (1<= 0 && index < 4 ); + return ((float *)(&xyzw))[index]; +} + +ID_INLINE float &rvSilTraceVertT::operator[]( const int index ) +{ + assert( index >= 0 && index < 4 ); + return ((float *)(&xyzw))[index]; +} + +ID_INLINE void rvSilTraceVertT::Clear( void ) +{ + xyzw.Zero(); +} + +ID_INLINE void rvSilTraceVertT::Lerp( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ) +{ + xyzw = a.xyzw + f * ( b.xyzw - a.xyzw ); +} + +ID_INLINE void rvSilTraceVertT::LerpAll( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ) +{ + xyzw = a.xyzw + f * ( b.xyzw - a.xyzw ); +} + +#endif // #ifndef __RV_VERTEX_H__ diff --git a/source/idlib/hashing/CRC16.cpp b/source/idlib/hashing/CRC16.cpp new file mode 100644 index 0000000..a19ecb7 --- /dev/null +++ b/source/idlib/hashing/CRC16.cpp @@ -0,0 +1,118 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-16 + + 16 bit, non-reflected CRC using the polynomial 0x1021 + and the initial and final xor values shown below. + in other words, the CCITT standard CRC-16 +*/ + +#define CRC16_INIT_VALUE 0xffff +#define CRC16_XOR_VALUE 0x0000 + +#ifdef CREATE_CRC_TABLE + +static unsigned short crctable[256]; + +/* + Generate a table for a byte-wise 16-bit CRC calculation on the polynomial: + x^16 + x^12 + x^5 + x^0 +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long poly, c; + /* terms of polynomial defining this crc (except x^16): */ + static const byte p[] = {0,5,12}; + + /* make exclusive-or pattern from polynomial (0x1021) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << p[i]; + } + + for ( i = 0; i < 256; i++ ) { + c = i << 8; + for ( j = 0; j < 8; j++ ) { + c = ( c & 0x8000 ) ? poly ^ ( c << 1 ) : ( c << 1 ); + } + crctable[i] = (unsigned short) c; + } +} + +#else + +/* + Table of CRC-16's of all single-byte values (made by make_crc_table) +*/ +static unsigned short crctable[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +#endif + +void CRC16_InitChecksum( unsigned short &crcvalue ) { + crcvalue = CRC16_INIT_VALUE; +} + +void CRC16_Update( unsigned short &crcvalue, const byte data ) { + crcvalue = ( crcvalue << 8 ) ^ crctable[ ( crcvalue >> 8 ) ^ data ]; +} + +void CRC16_UpdateChecksum( unsigned short &crcvalue, const void *data, int length ) { + unsigned short crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = ( crc << 8 ) ^ crctable[ ( crc >> 8 ) ^ *buf++ ]; + } + crcvalue = crc; +} + +void CRC16_FinishChecksum( unsigned short &crcvalue ) { + crcvalue ^= CRC16_XOR_VALUE; +} + +unsigned short CRC16_BlockChecksum( const void *data, int length ) { + unsigned short crc; + + CRC16_InitChecksum( crc ); + CRC16_UpdateChecksum( crc, data, length ); + CRC16_FinishChecksum( crc ); + return crc; +} diff --git a/source/idlib/hashing/CRC16.h b/source/idlib/hashing/CRC16.h new file mode 100644 index 0000000..52f1b45 --- /dev/null +++ b/source/idlib/hashing/CRC16.h @@ -0,0 +1,19 @@ + +#ifndef __CRC16_H__ +#define __CRC16_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CCITT standard CRC-16. + +=============================================================================== +*/ + +void CRC16_InitChecksum( unsigned short &crcvalue ); +void CRC16_UpdateChecksum( unsigned short &crcvalue, const void *data, int length ); +void CRC16_FinishChecksum( unsigned short &crcvalue ); +unsigned short CRC16_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC16_H__ */ diff --git a/source/idlib/hashing/CRC32.cpp b/source/idlib/hashing/CRC32.cpp new file mode 100644 index 0000000..c8c4026 --- /dev/null +++ b/source/idlib/hashing/CRC32.cpp @@ -0,0 +1,167 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-32 + Copyright (C) 1995-1998 Mark Adler +*/ + +#define CRC32_INIT_VALUE 0xffffffffL +#define CRC32_XOR_VALUE 0xffffffffL + +#ifdef CREATE_CRC_TABLE + +static unsigned long crctable[256]; + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x^1+x^0), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+x^0), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long c, poly; + /* terms of polynomial defining this crc (except x^32): */ + static const byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << ( 31 - p[i] ); + } + + for ( i = 0; i < 256; i++ ) { + c = (unsigned long)i; + for ( j = 0; j < 8; j++ ) { + c = ( c & 1 ) ? poly ^ ( c >> 1 ) : ( c >> 1 ); + } + crctable[i] = c; + } +} + +#else + +/* + Table of CRC-32's of all single-byte values (made by make_crc_table) +*/ +static unsigned long crctable[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +#endif + +void CRC32_InitChecksum( unsigned long &crcvalue ) { + crcvalue = CRC32_INIT_VALUE; +} + +void CRC32_Update( unsigned long &crcvalue, const byte data ) { + crcvalue = crctable[ ( crcvalue ^ data ) & 0xff ] ^ ( crcvalue >> 8 ); +} + +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ) { + unsigned long crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = crctable[ ( crc ^ ( *buf++ ) ) & 0xff ] ^ ( crc >> 8 ); + } + crcvalue = crc; +} + +void CRC32_FinishChecksum( unsigned long &crcvalue ) { + crcvalue ^= CRC32_XOR_VALUE; +} + +unsigned long CRC32_BlockChecksum( const void *data, int length ) { + unsigned long crc; + + CRC32_InitChecksum( crc ); + CRC32_UpdateChecksum( crc, data, length ); + CRC32_FinishChecksum( crc ); + return crc; +} diff --git a/source/idlib/hashing/CRC32.h b/source/idlib/hashing/CRC32.h new file mode 100644 index 0000000..1d91006 --- /dev/null +++ b/source/idlib/hashing/CRC32.h @@ -0,0 +1,19 @@ + +#ifndef __CRC32_H__ +#define __CRC32_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CRC-32. + +=============================================================================== +*/ + +void CRC32_InitChecksum( unsigned long &crcvalue ); +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ); +void CRC32_FinishChecksum( unsigned long &crcvalue ); +unsigned long CRC32_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC32_H__ */ diff --git a/source/idlib/hashing/CRC8.cpp b/source/idlib/hashing/CRC8.cpp new file mode 100644 index 0000000..da67ffe --- /dev/null +++ b/source/idlib/hashing/CRC8.cpp @@ -0,0 +1,114 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-8 +*/ + +#define CRC8_INIT_VALUE 0x0000 +#define CRC8_XOR_VALUE 0x0000 + +#ifdef CREATE_CRC_TABLE + +static byte crctable[256]; + +/* + Generate a table for a byte-wise 8-bit CRC calculation on the polynomial: + x^8 + x^2 + x^1 + x^0 +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long poly, c; + /* terms of polynomial defining this crc (except x^8): */ + static const byte p[] = {0,1,2}; + + /* make exclusive-or pattern from polynomial (0x07) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << p[i]; + } + + for ( i = 0; i < 256; i++ ) { + c = i; + for ( j = 0; j < 8; j++ ) { + c = ( c & 0x80 ) ? poly ^ ( c << 1 ) : ( c << 1 ); + } + crctable[i] = (byte) c; + } +} + +#else + +/* + Table of CRC-8's of all single-byte values (made by make_crc_table) +*/ +static byte crctable[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +#endif + +void CRC8_InitChecksum( unsigned char &crcvalue ) { + crcvalue = CRC8_INIT_VALUE; +} + +void CRC8_Update( unsigned char &crcvalue, const byte data ) { + crcvalue = crctable[crcvalue ^ data]; +} + +void CRC8_UpdateChecksum( unsigned char &crcvalue, const void *data, int length ) { + unsigned char crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = crctable[crc ^ *buf++]; + } + crcvalue = crc; +} + +void CRC8_FinishChecksum( unsigned char &crcvalue ) { + crcvalue ^= CRC8_XOR_VALUE; +} + +unsigned char CRC8_BlockChecksum( const void *data, int length ) { + unsigned char crc; + + CRC8_InitChecksum( crc ); + CRC8_UpdateChecksum( crc, data, length ); + CRC8_FinishChecksum( crc ); + return crc; +} diff --git a/source/idlib/hashing/CRC8.h b/source/idlib/hashing/CRC8.h new file mode 100644 index 0000000..07c772b --- /dev/null +++ b/source/idlib/hashing/CRC8.h @@ -0,0 +1,19 @@ + +#ifndef __CRC8_H__ +#define __CRC8_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CRC-8. + +=============================================================================== +*/ + +void CRC8_InitChecksum( unsigned char &crcvalue ); +void CRC8_UpdateChecksum( unsigned char &crcvalue, const void *data, int length ); +void CRC8_FinishChecksum( unsigned char &crcvalue ); +unsigned char CRC8_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC8_H__ */ diff --git a/source/idlib/hashing/Honeyman.cpp b/source/idlib/hashing/Honeyman.cpp new file mode 100644 index 0000000..3bf1c12 --- /dev/null +++ b/source/idlib/hashing/Honeyman.cpp @@ -0,0 +1,116 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#define HONEYMAN_INIT_VALUE 0x00000000L +#define HONEYMAN_XOR_VALUE 0x00000000L + +#ifdef CREATE_CRC_TABLE + +static unsigned long crctable[256]; + +/* + Create the CRC table for the simplified version of the pathalias hashing function. + Thanks to Steve Belovin and Peter Honeyman + + This fast table calculation works only if POLY is a prime polynomial + in the field of integers modulo 2. Since the coefficients of a + 32-bit polynomial won't fit in a 32-bit word, the high-order bit is + implicit. IT MUST ALSO BE THE CASE that the coefficients of orders + 31 down to 25 are zero. Happily, we have candidates, from + E. J. Watson, "Primitive Polynomials (Mod 2)", Math. Comp. 16 (1962): + x^32 + x^7 + x^5 + x^3 + x^2 + x^1 + x^0 + x^31 + x^3 + x^0 + + We reverse the bits to get: + 111101010000000000000000000000001 but drop the last 1 + f 5 0 0 0 0 0 0 + 010010000000000000000000000000001 ditto, for 31-bit crc + 4 8 0 0 0 0 0 0 +*/ + +void make_crc_table( void ) { + + #define POLY 0x48000000L /* 31-bit polynomial (avoids sign problems) */ + + for ( int i = 0; i < 128; i++ ) { + int sum = 0; + for ( int j = 7 - 1; j >= 0; --j ) { + if ( i & ( 1 << j ) ) { + sum ^= POLY >> j; + } + } + crctable[i] = sum; + } +} + +#else + +static unsigned long crctable[256] = { + 0x00000000L, 0x48000000L, 0x24000000L, 0x6c000000L, + 0x12000000L, 0x5a000000L, 0x36000000L, 0x7e000000L, + 0x09000000L, 0x41000000L, 0x2d000000L, 0x65000000L, + 0x1b000000L, 0x53000000L, 0x3f000000L, 0x77000000L, + 0x04800000L, 0x4c800000L, 0x20800000L, 0x68800000L, + 0x16800000L, 0x5e800000L, 0x32800000L, 0x7a800000L, + 0x0d800000L, 0x45800000L, 0x29800000L, 0x61800000L, + 0x1f800000L, 0x57800000L, 0x3b800000L, 0x73800000L, + 0x02400000L, 0x4a400000L, 0x26400000L, 0x6e400000L, + 0x10400000L, 0x58400000L, 0x34400000L, 0x7c400000L, + 0x0b400000L, 0x43400000L, 0x2f400000L, 0x67400000L, + 0x19400000L, 0x51400000L, 0x3d400000L, 0x75400000L, + 0x06c00000L, 0x4ec00000L, 0x22c00000L, 0x6ac00000L, + 0x14c00000L, 0x5cc00000L, 0x30c00000L, 0x78c00000L, + 0x0fc00000L, 0x47c00000L, 0x2bc00000L, 0x63c00000L, + 0x1dc00000L, 0x55c00000L, 0x39c00000L, 0x71c00000L, + 0x01200000L, 0x49200000L, 0x25200000L, 0x6d200000L, + 0x13200000L, 0x5b200000L, 0x37200000L, 0x7f200000L, + 0x08200000L, 0x40200000L, 0x2c200000L, 0x64200000L, + 0x1a200000L, 0x52200000L, 0x3e200000L, 0x76200000L, + 0x05a00000L, 0x4da00000L, 0x21a00000L, 0x69a00000L, + 0x17a00000L, 0x5fa00000L, 0x33a00000L, 0x7ba00000L, + 0x0ca00000L, 0x44a00000L, 0x28a00000L, 0x60a00000L, + 0x1ea00000L, 0x56a00000L, 0x3aa00000L, 0x72a00000L, + 0x03600000L, 0x4b600000L, 0x27600000L, 0x6f600000L, + 0x11600000L, 0x59600000L, 0x35600000L, 0x7d600000L, + 0x0a600000L, 0x42600000L, 0x2e600000L, 0x66600000L, + 0x18600000L, 0x50600000L, 0x3c600000L, 0x74600000L, + 0x07e00000L, 0x4fe00000L, 0x23e00000L, 0x6be00000L, + 0x15e00000L, 0x5de00000L, 0x31e00000L, 0x79e00000L, + 0x0ee00000L, 0x46e00000L, 0x2ae00000L, 0x62e00000L, + 0x1ce00000L, 0x54e00000L, 0x38e00000L, 0x70e00000L +}; + +#endif + +void Honeyman_InitChecksum( unsigned long &crcvalue ) { + crcvalue = HONEYMAN_INIT_VALUE; +} + +void Honeyman_Update( unsigned long &crcvalue, const byte data ) { + crcvalue = ( ( crcvalue >> 7 ) ^ crctable[ ( crcvalue ^ data ) & 0x7f ] ); +} + +void Honeyman_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ) { + unsigned long crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = ( ( crc >> 7 ) ^ crctable[ ( crc ^ *buf++ ) & 0x7f ] ); + } + crcvalue = crc; +} + +void Honeyman_FinishChecksum( unsigned long &crcvalue ) { + crcvalue ^= HONEYMAN_XOR_VALUE; +} + +unsigned long Honeyman_BlockChecksum( const void *data, int length ) { + unsigned long crc; + + Honeyman_InitChecksum( crc ); + Honeyman_UpdateChecksum( crc, data, length ); + Honeyman_FinishChecksum( crc ); + return crc; +} diff --git a/source/idlib/hashing/Honeyman.h b/source/idlib/hashing/Honeyman.h new file mode 100644 index 0000000..3acb618 --- /dev/null +++ b/source/idlib/hashing/Honeyman.h @@ -0,0 +1,20 @@ + +#ifndef __HONEYMAN_H__ +#define __HONEYMAN_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the simplified version of the pathalias hashing + function by Steve Belovin and Peter Honeyman. + +=============================================================================== +*/ + +void Honeyman_InitChecksum( unsigned long &crcvalue ); +void Honeyman_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ); +void Honeyman_FinishChecksum( unsigned long &crcvalue ); +unsigned long Honeyman_BlockChecksum( const void *data, int length ); + +#endif /* !__HONEYMAN_H__ */ diff --git a/source/idlib/hashing/MD4.cpp b/source/idlib/hashing/MD4.cpp new file mode 100644 index 0000000..f2139e6 --- /dev/null +++ b/source/idlib/hashing/MD4.cpp @@ -0,0 +1,259 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* + RSA Data Security, Inc., MD4 message-digest algorithm. (RFC1320) +*/ + +/* + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode( unsigned char *output, UINT4 *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode( UINT4 *output, const unsigned char *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); + } +} + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4_Transform( UINT4 state[4], const unsigned char block[64] ) { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11); /* 1 */ + FF (d, a, b, c, x[ 1], S12); /* 2 */ + FF (c, d, a, b, x[ 2], S13); /* 3 */ + FF (b, c, d, a, x[ 3], S14); /* 4 */ + FF (a, b, c, d, x[ 4], S11); /* 5 */ + FF (d, a, b, c, x[ 5], S12); /* 6 */ + FF (c, d, a, b, x[ 6], S13); /* 7 */ + FF (b, c, d, a, x[ 7], S14); /* 8 */ + FF (a, b, c, d, x[ 8], S11); /* 9 */ + FF (d, a, b, c, x[ 9], S12); /* 10 */ + FF (c, d, a, b, x[10], S13); /* 11 */ + FF (b, c, d, a, x[11], S14); /* 12 */ + FF (a, b, c, d, x[12], S11); /* 13 */ + FF (d, a, b, c, x[13], S12); /* 14 */ + FF (c, d, a, b, x[14], S13); /* 15 */ + FF (b, c, d, a, x[15], S14); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 0], S21); /* 17 */ + GG (d, a, b, c, x[ 4], S22); /* 18 */ + GG (c, d, a, b, x[ 8], S23); /* 19 */ + GG (b, c, d, a, x[12], S24); /* 20 */ + GG (a, b, c, d, x[ 1], S21); /* 21 */ + GG (d, a, b, c, x[ 5], S22); /* 22 */ + GG (c, d, a, b, x[ 9], S23); /* 23 */ + GG (b, c, d, a, x[13], S24); /* 24 */ + GG (a, b, c, d, x[ 2], S21); /* 25 */ + GG (d, a, b, c, x[ 6], S22); /* 26 */ + GG (c, d, a, b, x[10], S23); /* 27 */ + GG (b, c, d, a, x[14], S24); /* 28 */ + GG (a, b, c, d, x[ 3], S21); /* 29 */ + GG (d, a, b, c, x[ 7], S22); /* 30 */ + GG (c, d, a, b, x[11], S23); /* 31 */ + GG (b, c, d, a, x[15], S24); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 0], S31); /* 33 */ + HH (d, a, b, c, x[ 8], S32); /* 34 */ + HH (c, d, a, b, x[ 4], S33); /* 35 */ + HH (b, c, d, a, x[12], S34); /* 36 */ + HH (a, b, c, d, x[ 2], S31); /* 37 */ + HH (d, a, b, c, x[10], S32); /* 38 */ + HH (c, d, a, b, x[ 6], S33); /* 39 */ + HH (b, c, d, a, x[14], S34); /* 40 */ + HH (a, b, c, d, x[ 1], S31); /* 41 */ + HH (d, a, b, c, x[ 9], S32); /* 42 */ + HH (c, d, a, b, x[ 5], S33); /* 43 */ + HH (b, c, d, a, x[13], S34); /* 44 */ + HH (a, b, c, d, x[ 3], S31); /* 45 */ + HH (d, a, b, c, x[11], S32); /* 46 */ + HH (c, d, a, b, x[ 7], S33); /* 47 */ + HH (b, c, d, a, x[15], S34); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4_Init( MD4_CTX *context ) { + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants.*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4_Update( MD4_CTX *context, const unsigned char *input, unsigned int inputLen ) { + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) { + context->count[1]++; + } + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if ( inputLen >= partLen ) { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4_Transform (context->state, context->buffer); + + for ( i = partLen; i + 63 < inputLen; i += 64 ) { + MD4_Transform (context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the message digest and zeroizing the context. */ +void MD4_Final( MD4_CTX *context, unsigned char digest[16] ) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode( bits, context->count, 8 ); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4_Update( context, bits, 8 ); + + /* Store state in digest */ + Encode( digest, context->state, 16 ); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + +/* +=============== +MD4_BlockChecksum +=============== +*/ +unsigned long MD4_BlockChecksum( const void *data, int length ) { + unsigned long digest[4]; + unsigned long val; + MD4_CTX ctx; + + MD4_Init( &ctx ); + MD4_Update( &ctx, (unsigned char *)data, length ); + MD4_Final( &ctx, (unsigned char *)digest ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/source/idlib/hashing/MD4.h b/source/idlib/hashing/MD4.h new file mode 100644 index 0000000..d1f3f73 --- /dev/null +++ b/source/idlib/hashing/MD4.h @@ -0,0 +1,16 @@ + +#ifndef __MD4_H__ +#define __MD4_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD4 message-digest algorithm. + +=============================================================================== +*/ + +unsigned long MD4_BlockChecksum( const void *data, int length ); + +#endif /* !__MD4_H__ */ diff --git a/source/idlib/hashing/MD5.cpp b/source/idlib/hashing/MD5.cpp new file mode 100644 index 0000000..1206d17 --- /dev/null +++ b/source/idlib/hashing/MD5.cpp @@ -0,0 +1,272 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* + MD5 Message Digest Algorithm. (RFC1321) +*/ + +/* + +This code implements the MD5 message-digest algorithm. +The algorithm is due to Ron Rivest. This code was +written by Colin Plumb in 1993, no copyright is claimed. +This code is in the public domain; do with it what you wish. + +Equivalent code is available from RSA Data Security, Inc. +This code has been tested against that, and is equivalent, +except that you don't need to include two pages of legalese +with every copy. + +To compute the message digest of a chunk of bytes, declare an +MD5Context structure, pass it to MD5Init, call MD5Update as +needed on buffers full of bytes, and then call MD5Final, which +will fill a supplied 16-byte array with the digest. + +*/ + +/* MD5 context. */ +typedef struct +{ + unsigned int state[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5_CTX; + +/* The four core functions - F1 is optimized somewhat */ +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* +================= +MD5_Transform + +The core of the MD5 algorithm, this alters an existing MD5 hash to +reflect the addition of 16 longwords of new data. MD5Update blocks +the data and converts bytes into longwords for this routine. +================= +*/ +void MD5_Transform( unsigned int state[4], unsigned int const in[16] ) { + register unsigned int a, b, c, d; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + LittleRevBytes( const_cast< unsigned int * >( in ), sizeof(unsigned int), 16 ); + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + LittleRevBytes( const_cast< unsigned int * >( in ), sizeof(unsigned int), 16 ); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +/* +================== +MD5_Init + +MD5 initialization. Begins an MD5 operation, writing a new context. +================== +*/ +void MD5_Init( MD5_CTX *ctx ) { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* +=================== +MD5_Update + +MD5 block update operation. Continues an MD5 message-digest operation, +processing another message block, and updating the context. +=================== +*/ +void MD5_Update( MD5_CTX *ctx, unsigned char const *buf, unsigned int len ) { + unsigned int t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ( ( ctx->bits[0] = t + ( (unsigned int) len << 3 ) ) < t ) { + ctx->bits[1]++; /* Carry from low to high */ + } + ctx->bits[1] += len >> 29; + + t = ( t >> 3 ) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if ( len < t ) { + memcpy( p, buf, len ); + return; + } + memcpy( p, buf, t ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while( len >= 64 ) { + memcpy( ctx->in, buf, 64 ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy( ctx->in, buf, len ); +} + +/* +=============== +MD5_Final + +MD5 finalization. Ends an MD5 message-digest operation, +writing the message digest and zeroizing the context. +=============== +*/ +void MD5_Final( MD5_CTX *ctx, unsigned char digest[16] ) { + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = ( ctx->bits[0] >> 3 ) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if ( count < 8 ) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset( p, 0, count ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + + /* Now fill the next block with 56 bytes */ + memset( ctx->in, 0, 56 ); + } else { + /* Pad block to 56 bytes */ + memset( p, 0, count - 8 ); + } + + /* Append length in bits and transform */ + unsigned int val0 = ctx->bits[0]; + unsigned int val1 = ctx->bits[1]; + + ((unsigned int *) ctx->in)[14] = LittleLong( val0 ); + ((unsigned int *) ctx->in)[15] = LittleLong( val1 ); + + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + memcpy( digest, ctx->state, 16 ); + memset( ctx, 0, sizeof( ctx ) ); /* In case it's sensitive */ +} + +/* +=============== +MD5_BlockChecksum +=============== +*/ +unsigned long MD5_BlockChecksum( const void *data, int length ) { + unsigned long digest[4]; + unsigned long val; + MD5_CTX ctx; + + MD5_Init( &ctx ); + MD5_Update( &ctx, (unsigned char *)data, length ); + MD5_Final( &ctx, (unsigned char *)digest ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/source/idlib/hashing/MD5.h b/source/idlib/hashing/MD5.h new file mode 100644 index 0000000..7dca238 --- /dev/null +++ b/source/idlib/hashing/MD5.h @@ -0,0 +1,16 @@ + +#ifndef __MD5_H__ +#define __MD5_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD5 message-digest algorithm. + +=============================================================================== +*/ + +unsigned long MD5_BlockChecksum( const void *data, int length ); + +#endif /* !__MD5_H__ */ diff --git a/source/idlib/mapfile.cpp b/source/idlib/mapfile.cpp new file mode 100644 index 0000000..0506d00 --- /dev/null +++ b/source/idlib/mapfile.cpp @@ -0,0 +1,1366 @@ + +#include "precompiled.h" +#pragma hdrstop + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + + +/* +=============== +FloatCRC +=============== +*/ +ID_INLINE unsigned int FloatCRC( float f ) { + return *(unsigned int *)&f; +} + +/* +=============== +StringCRC +=============== +*/ +ID_INLINE unsigned int StringCRC( const char *str ) { + unsigned int i, crc; + const unsigned char *ptr; + + crc = 0; + ptr = reinterpret_cast(str); + for ( i = 0; str[i]; i++ ) { + crc ^= str[i] << (i & 3); + } + return crc; +} + +/* +================= +ComputeAxisBase + +WARNING : special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere when x == 0 +rotation by (0,RotY,RotZ) assigns X to normal +================= +*/ +static void ComputeAxisBase( const idVec3 &normal, idVec3 &texS, idVec3 &texT ) { + float RotY, RotZ; + idVec3 n; + + // do some cleaning + n[0] = ( idMath::Fabs( normal[0] ) < 1e-6f ) ? 0.0f : normal[0]; + n[1] = ( idMath::Fabs( normal[1] ) < 1e-6f ) ? 0.0f : normal[1]; + n[2] = ( idMath::Fabs( normal[2] ) < 1e-6f ) ? 0.0f : normal[2]; + + RotY = -idMath::ATan( n[2], idMath::Sqrt( n[1] * n[1] + n[0] * n[0]) ); + RotZ = idMath::ATan( n[1], n[0] ); + // rotate (0,1,0) and (0,0,1) to compute texS and texT +// RAVEN BEGIN +// jscott: routed thru idMath + texS[0] = -idMath::Sin( RotZ ); + texS[1] = idMath::Cos( RotZ ); + texS[2] = 0; + // the texT vector is along -Z ( T texture coorinates axis ) + texT[0] = -idMath::Sin( RotY ) * idMath::Cos( RotZ ); + texT[1] = -idMath::Sin( RotY ) * idMath::Sin( RotZ ); + texT[2] = -idMath::Cos( RotY ); +// RAVEN END +} + +// RAVEN BEGIN +// rhummer: Trying to debug why func_groups disappear when editing a map when they shouldn't. +idMapFile::~idMapFile( void ) +{ + entities.DeleteContents( true ); + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2done with .map file ^0%s\n", name.c_str() ); + } +} +// RAVEN END + +/* +================= +idMapBrushSide::GetTextureVectors +================= +*/ +void idMapBrushSide::GetTextureVectors( idVec4 v[2] ) const { + int i; + idVec3 texX, texY; + + ComputeAxisBase( plane.Normal(), texX, texY ); + for ( i = 0; i < 2; i++ ) { + v[i][0] = texX[0] * texMat[i][0] + texY[0] * texMat[i][1]; + v[i][1] = texX[1] * texMat[i][0] + texY[1] * texMat[i][1]; + v[i][2] = texX[2] * texMat[i][0] + texY[2] * texMat[i][1]; + v[i][3] = texMat[i][2] + ( origin * v[i].ToVec3() ); + } +} + +/* +================= +idMapPatch::Parse +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapPatch *idMapPatch::Parse( Lexer &src, const idVec3 &origin, bool patchDef3, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + float info[7]; + idDrawVert *vert; + idToken token; + int i, j; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + // read the material (we had an implicit 'textures/' in the old format...) + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapPatch::Parse: unexpected EOF" ); + return NULL; + } + + // Parse it + if (patchDef3) { + if ( !src.Parse1DMatrix( 7, info ) ) { + src.Error( "idMapPatch::Parse: unable to Parse patchDef3 info" ); + return NULL; + } + } else { + if ( !src.Parse1DMatrix( 5, info ) ) { + src.Error( "idMapPatch::Parse: unable to parse patchDef2 info" ); + return NULL; + } + } + + idMapPatch *patch = new idMapPatch( info[0], info[1] ); + patch->SetSize( info[0], info[1] ); + if ( version < 2 ) { + patch->SetMaterial( "textures/" + token ); + } else { + patch->SetMaterial( token ); + } + + if ( patchDef3 ) { + patch->SetHorzSubdivisions( info[2] ); + patch->SetVertSubdivisions( info[3] ); + patch->SetExplicitlySubdivided( true ); + } + + if ( patch->GetWidth() < 0 || patch->GetHeight() < 0 ) { + src.Error( "idMapPatch::Parse: bad size" ); + delete patch; + return NULL; + } + + // these were written out in the wrong order, IMHO + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad patch vertex data" ); + delete patch; + return NULL; + } + for ( j = 0; j < patch->GetWidth(); j++ ) { + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad vertex row data" ); + delete patch; + return NULL; + } + for ( i = 0; i < patch->GetHeight(); i++ ) { + float v[5]; + + if ( !src.Parse1DMatrix( 5, v ) ) { + src.Error( "idMapPatch::Parse: bad vertex column data" ); + delete patch; + return NULL; + } + + vert = &((*patch)[i * patch->GetWidth() + j]); + vert->xyz[0] = v[0] - origin[0]; + vert->xyz[1] = v[1] - origin[1]; + vert->xyz[2] = v[2] - origin[2]; + vert->st[0] = v[3]; + vert->st[1] = v[4]; + } + if ( !src.ExpectTokenString( ")" ) ) { + delete patch; + src.Error( "idMapPatch::Parse: unable to parse patch control points" ); + return NULL; + } + } + if ( !src.ExpectTokenString( ")" ) || + !src.ExpectTokenString( "}" ) || + !src.ExpectTokenString( "}" ) ) { + src.Error( "idMapPatch::Parse: unable to parse patch control points, no closure" ); + delete patch; + return NULL; + } + + return patch; +} + +/* +============ +idMapPatch::Write +============ +*/ +bool idMapPatch::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i, j; + const idDrawVert *v; + + if ( GetExplicitlySubdivided() ) { + fp->WriteFloatString( "// primitive %d\n{\n patchDef3\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight(), GetHorzSubdivisions(), GetVertSubdivisions()); + } else { + fp->WriteFloatString( "// primitive %d\n{\n patchDef2\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight()); + } + + fp->WriteFloatString( " (\n" ); + for ( i = 0; i < GetWidth(); i++ ) { + fp->WriteFloatString( " ( " ); + for ( j = 0; j < GetHeight(); j++ ) { + v = &verts[ j * GetWidth() + i ]; + fp->WriteFloatString( " ( %f %f %f %f %f )", v->xyz[0] + origin[0], + v->xyz[1] + origin[1], v->xyz[2] + origin[2], v->st[0], v->st[1] ); + } + fp->WriteFloatString( " )\n" ); + } + fp->WriteFloatString( " )\n }\n}\n" ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data +/* +=============== +idMapPatch::AdjustOrigin +=============== +*/ +void idMapPatch::AdjustOrigin( idVec3 &delta ) { + TranslateSelf( delta ); +} +// RAVEN END + +/* +=============== +idMapPatch::GetGeometryCRC +=============== +*/ +unsigned int idMapPatch::GetGeometryCRC( void ) const { + int i, j; + unsigned int crc; + + crc = GetHorzSubdivisions() ^ GetVertSubdivisions(); + for ( i = 0; i < GetWidth(); i++ ) { + for ( j = 0; j < GetHeight(); j++ ) { + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.x ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.y ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.z ); + } + } + + crc ^= StringCRC( GetMaterial() ); + + return crc; +} + +/* +================= +idMapBrush::Parse +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapBrush *idMapBrush::Parse( Lexer &src, const idVec3 &origin, bool newFormat, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + do { + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + if ( token == "}" ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is a brace + if ( token == "(" ) { + break; + } + // the token should be a key string for a key/value pair + if ( token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: unexpected %s, expected ( or epair key string", token.c_str() ); + sides.DeleteContents( true ); + return NULL; + } + + idStr key = token; + + if ( !src.ReadTokenOnLine( &token ) || token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: expected epair value string not found" ); + sides.DeleteContents( true ); + return NULL; + } + + epairs.Set( key, token ); + + // try to read the next key + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + } while (1); + + src.UnreadToken( &token ); + + side = new idMapBrushSide(); + sides.Append(side); + + if ( newFormat ) { + if ( !src.Parse1DMatrix( 4, side->plane.ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + } else { + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + } + + // read the texture matrix + // this is odd, because the texmat is 2D relative to default planar texture axis + if ( !src.Parse2DMatrix( 2, 3, side->texMat[0].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side texture matrix" ); + sides.DeleteContents( true ); + return NULL; + } + side->origin = origin; + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we had an implicit 'textures/' in the old format... + if ( version < 2 ) { + side->material = "textures/" + token; + } else { + side->material = token; + } + +// RAVEN BEGIN +// jscott: make sure the material is properly parsed + declManager->FindMaterial( token ); +// RAVEN END + +// RAVEN BEGIN +// jscott: removed these in later versions + if( version < 3 ) { +// RAVEN END + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } + } while( 1 ); + + if ( !src.ExpectTokenString( "}" ) ) { + sides.DeleteContents( true ); + return NULL; + } + + idMapBrush *brush = new idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +================= +idMapBrush::ParseQ3 +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapBrush *idMapBrush::ParseQ3( Lexer &src, const idVec3 &origin ) { +// RAVEN END + int i, shift[2], rotate; + float scale[2]; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + do { + if ( src.CheckTokenString( "}" ) ) { + break; + } + + side = new idMapBrushSide(); + sides.Append( side ); + + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we have an implicit 'textures/' in the old format + side->material = "textures/" + token; + + // read the texture shift, rotate and scale + shift[0] = src.ParseInt(); + shift[1] = src.ParseInt(); + rotate = src.ParseInt(); + scale[0] = src.ParseFloat(); + scale[1] = src.ParseFloat(); + side->texMat[0] = idVec3( 0.03125f, 0.0f, 0.0f ); + side->texMat[1] = idVec3( 0.0f, 0.03125f, 0.0f ); + side->origin = origin; + + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } while( 1 ); + + idMapBrush *brush = new idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +============ +idMapBrush::Write +============ +*/ +bool idMapBrush::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i; + idMapBrushSide *side; + + fp->WriteFloatString( "// primitive %d\n{\n brushDef3\n {\n", primitiveNum ); + + // write brush epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( " \"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + // write brush sides + for ( i = 0; i < GetNumSides(); i++ ) { + side = GetSide( i ); + fp->WriteFloatString( " ( %f %f %f %f ) ", side->plane[0], side->plane[1], side->plane[2], side->plane[3] ); +// RAVEN BEGIN + fp->WriteFloatString( "( ( %f %f %f ) ( %f %f %f ) ) \"%s\"\n", +// RAVEN END + side->texMat[0][0], side->texMat[0][1], side->texMat[0][2], + side->texMat[1][0], side->texMat[1][1], side->texMat[1][2], + side->material.c_str() ); + } + + fp->WriteFloatString( " }\n}\n" ); + + return true; +} + +/* +=============== +idMapBrush::GetGeometryCRC +=============== +*/ +unsigned int idMapBrush::GetGeometryCRC( void ) const { + int i, j; + idMapBrushSide *mapSide; + unsigned int crc; + + crc = 0; + for ( i = 0; i < GetNumSides(); i++ ) { + mapSide = GetSide(i); + for ( j = 0; j < 4; j++ ) { + crc ^= FloatCRC( mapSide->GetPlane()[j] ); + } + crc ^= StringCRC( mapSide->GetMaterial() ); + } + + return crc; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + +// This is taken from tools/radiant/EditorBrushPrimit.cpp +float SarrusDet(idVec3 a, idVec3 b, idVec3 c) { + return (float)a[0] * (float)b[1] * (float)c[2] + (float)b[0] * (float)c[1] * (float)a[2] + (float)c[0] * (float)a[1] * (float)b[2] - (float)c[0] * (float)b[1] * (float)a[2] - (float)a[1] * (float)b[0] * (float)c[2] - (float)a[0] * (float)b[2] * (float)c[1]; +} + +/* +=============== +idMapBrush::AdjustOrigin +=============== +*/ +void idMapBrush::AdjustOrigin( idVec3 &delta ) { + int i; + idMapBrushSide *mapSide; + + for ( i = 0; i < GetNumSides(); i++ ) { + mapSide = GetSide(i); + + mapSide->SetPlane( mapSide->GetPlane().Translate( delta ) ); + + // This is taken from Face_MoveTexture_BrushPrimit() in tools/radiant/EditorBrushPrimit.cpp + idVec3 texS, texT; + float tx, ty; + idVec3 M[3]; // columns of the matrix .. easier that way + float det; + idVec3 D[2]; + + // compute plane axis base ( doesn't change with translation ) + ComputeAxisBase( mapSide->GetPlane().Normal(), texS, texT); + + // compute translation vector in plane axis base + tx = DotProduct(delta, texS); + ty = DotProduct(delta, texT); + + // fill the data vectors + M[0][0] = tx; + M[0][1] = 1.0f + tx; + M[0][2] = tx; + M[1][0] = ty; + M[1][1] = ty; + M[1][2] = 1.0f + ty; + M[2][0] = 1.0f; + M[2][1] = 1.0f; + M[2][2] = 1.0f; + + idVec3 tm[2]; + mapSide->GetTextureMatrix( tm[0], tm[1] ); + + D[0][0] = tm[0][2]; + D[0][1] = tm[0][0] + tm[0][2]; + D[0][2] = tm[0][1] + tm[0][2]; + D[1][0] = tm[1][2]; + D[1][1] = tm[1][0] + tm[1][2]; + D[1][2] = tm[1][1] + tm[1][2]; + + // solve + det = SarrusDet(M[0], M[1], M[2]); + if ( det != 0. ) { + tm[0][0] = SarrusDet(D[0], M[1], M[2]) / det; + tm[0][1] = SarrusDet(M[0], D[0], M[2]) / det; + tm[0][2] = SarrusDet(M[0], M[1], D[0]) / det; + tm[1][0] = SarrusDet(D[1], M[1], M[2]) / det; + tm[1][1] = SarrusDet(M[0], D[1], M[2]) / det; + tm[1][2] = SarrusDet(M[0], M[1], D[1]) / det; + mapSide->SetTextureMatrix(tm); + } + } +} +// RAVEN END + +/* +================ +idMapEntity::Parse +================ +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapEntity *idMapEntity::Parse( Lexer &src, bool worldSpawn, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + idToken token; + idMapEntity *mapEnt; + idMapPatch *mapPatch; + idMapBrush *mapBrush; + bool worldent; + idVec3 origin; + double v1, v2, v3; + + if ( !src.ReadToken(&token) ) { + return NULL; + } + + if ( token != "{" ) { + src.Error( "idMapEntity::Parse: { not found, found %s", token.c_str() ); + return NULL; + } + + mapEnt = new idMapEntity(); + + if ( worldSpawn ) { + mapEnt->primitives.Resize( 1024, 256 ); + } + + origin.Zero(); + worldent = false; + do { + if ( !src.ReadToken(&token) ) { + src.Error( "idMapEntity::Parse: EOF without closing brace" ); + return NULL; + } + if ( token == "}" ) { + break; + } + + if ( token == "{" ) { + // parse a brush or patch + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapEntity::Parse: unexpected EOF" ); + return NULL; + } + + if ( worldent ) { + origin.Zero(); + } + + // if is it a brush: brush, brushDef, brushDef2, brushDef3 + if ( token.Icmpn( "brush", 5 ) == 0 ) { + mapBrush = idMapBrush::Parse( src, origin, ( !token.Icmp( "brushDef2" ) || !token.Icmp( "brushDef3" ) ), version ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + // if is it a patch: patchDef2, patchDef3 + else if ( token.Icmpn( "patch", 5 ) == 0 ) { + mapPatch = idMapPatch::Parse( src, origin, !token.Icmp( "patchDef3" ), version ); + if ( !mapPatch ) { + return NULL; + } + mapEnt->AddPrimitive( mapPatch ); + } + // assume it's a brush in Q3 or older style + else { + src.UnreadToken( &token ); + mapBrush = idMapBrush::ParseQ3( src, origin ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + } else { + idStr key, value; + + // parse a key / value pair + key = token; + src.ReadTokenOnLine( &token ); + value = token; + + // strip trailing spaces that sometimes get accidentally + // added in the editor + value.StripTrailingWhitespace(); + key.StripTrailingWhitespace(); + + mapEnt->epairs.Set( key, value ); + + if ( !idStr::Icmp( key, "origin" ) ) { + // scanf into doubles, then assign, so it is idVec size independent + v1 = v2 = v3 = 0; + sscanf( value, "%lf %lf %lf", &v1, &v2, &v3 ); + origin.x = v1; + origin.y = v2; + origin.z = v3; + } + else if ( !idStr::Icmp( key, "classname" ) && !idStr::Icmp( value, "worldspawn" ) ) { + worldent = true; + } + } + } while( 1 ); + + return mapEnt; +} + +/* +============ +idMapEntity::Write +============ +*/ +bool idMapEntity::Write( idFile *fp, int entityNum ) const { + int i; + idMapPrimitive *mapPrim; + idVec3 origin; + + fp->WriteFloatString( "// entity %d\n{\n", entityNum ); + + // write entity epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( "\"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + epairs.GetVector( "origin", "0 0 0", origin ); + + // write pritimives + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + case idMapPrimitive::TYPE_PATCH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + } + } + + fp->WriteFloatString( "}\n" ); + + return true; +} + +/* +=============== +idMapEntity::RemovePrimitiveData +=============== +*/ +void idMapEntity::RemovePrimitiveData() { + primitives.DeleteContents(true); +} + +/* +=============== +idMapEntity::GetGeometryCRC +=============== +*/ +unsigned int idMapEntity::GetGeometryCRC( void ) const { + int i; + unsigned int crc; + idMapPrimitive *mapPrim; + + crc = 0; + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + if ( epairs.GetString( "model" ) ) { + crc ^= StringCRC( epairs.GetString( "model" ) ); + } + break; + case idMapPrimitive::TYPE_PATCH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + if ( epairs.GetString( "model" ) ) { + crc ^= StringCRC( epairs.GetString( "model" ) ); + } + break; + } + } + + return crc; +} + +/* +=============== +idMapFile::Parse +=============== +*/ +bool idMapFile::Parse( const char *filename, bool ignoreRegion, bool osPath ) { + // no string concatenation for epairs and allow path names for materials +// RAVEN BEGIN +// jsinger: Done this way to reduce the amount of code change. The auto pointer will +// delete the lexer when this method exits + idAutoPtr lexer(LexerFactory::MakeLexer( LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES )); + Lexer &src(*lexer); +// RAVEN END + idToken token; + idStr fullName; + idMapEntity *mapEnt; + + TIME_THIS_SCOPE( __FUNCLINE__); + + name = filename; + name.StripFileExtension(); + fullName = name; + hasPrimitiveData = false; + + if ( !ignoreRegion ) { + // try loading a .reg file first + fullName.SetFileExtension( "reg" ); + src.LoadFile( fullName, osPath ); + } + + if ( !src.IsLoaded() ) { + + PACIFIER_UPDATE; + + // now try a .map file + fullName.SetFileExtension( "map" ); + src.LoadFile( fullName, osPath ); + + if ( !src.IsLoaded() ) { + // didn't get anything at all + return false; + } + } + + version = OLD_MAP_VERSION; + fileTime = src.GetFileTime(); + entities.DeleteContents( true ); + + if ( src.CheckTokenString( "Version" ) ) { + src.ReadTokenOnLine( &token ); + version = token.GetIntValue(); + } + + while( 1 ) { + PACIFIER_UPDATE; + mapEnt = idMapEntity::Parse( src, ( entities.Num() == 0 ), version ); + if ( !mapEnt ) { + break; + } +// RAVEN BEGIN +// rhummer: Check to see if there are func_groups in the map file. + if ( !mHasFuncGroups && !idStr::Icmp( mapEnt->epairs.GetString( "classname" ), "func_group" ) ) { + mHasFuncGroups = true; + } +// RAVEN END + entities.Append( mapEnt ); + } + + PACIFIER_UPDATE; + ParseExport(filename, osPath); + + SetGeometryCRC(); +// RAVEN BEGIN +// rhummer: Trying to debug why func_groups disappear when editing a map when they shouldn't. + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2loaded .map file ^0%s\n", filename ); + } + mHasBeenResolved = false; +// RAVEN END + + hasPrimitiveData = true; + return true; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data +void idMapFile::Resolve( void ) +{ + int i, j, k; + idMapEntity *mapEnt; + + if ( !hasPrimitiveData ) { + return; + } + + // if the map has a worldspawn + if ( entities.Num() ) { + + // "removeEntities" "classname" can be set in the worldspawn to remove all entities with the given classname + const idKeyValue *removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", NULL ); + while ( removeEntities ) { + RemoveEntities( removeEntities->GetValue() ); + removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", removeEntities ); + } + + // "overrideMaterial" "material" can be set in the worldspawn to reset all materials + idStr material; + if ( entities[0]->epairs.GetString( "overrideMaterial", "", material ) ) { + for ( i = 0; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { + idMapPrimitive *mapPrimitive = mapEnt->GetPrimitive( j ); + switch( mapPrimitive->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: { + idMapBrush *mapBrush = static_cast(mapPrimitive); + for ( k = 0; k < mapBrush->GetNumSides(); k++ ) { + mapBrush->GetSide( k )->SetMaterial( material ); + } + break; + } + case idMapPrimitive::TYPE_PATCH: { + static_cast(mapPrimitive)->SetMaterial( material ); + break; + } + } + } + } + } + + // force all entities to have a name key/value pair + if ( entities[0]->epairs.GetBool( "forceEntityNames" ) ) { + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( !mapEnt->epairs.FindKey( "name" ) ) { + mapEnt->epairs.Set( "name", va( "%s%d", mapEnt->epairs.GetString( "classname", "forcedName" ), i ) ); + } + } + } + + // move the primitives of any func_group entities to the worldspawn + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( idStr::Icmp( mapEnt->epairs.GetString( "classname" ), "func_group" ) == 0 ) { + idVec3 delta; + + mapEnt->epairs.GetVector( "origin", "0 0 0", delta ); + + for( j = 0; j < mapEnt->primitives.Num(); j++ ) { + idMapPrimitive *mapPrim = mapEnt->primitives[j]; + + mapPrim->AdjustOrigin( delta ); + } + entities[0]->primitives.Append( mapEnt->primitives ); + mapEnt->primitives.Clear(); + } + } + RemoveEntities( "func_group" ); + } +// rhummer: + mHasBeenResolved = true; + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2idMapFile::Resolve has been run on ^0%s^2 so no func_groups exist.\n", name.c_str() ); + } +} + +// rjohnson: added export +/* +============ +idMapFile::Write +============ +*/ +bool idMapFile::Write( const char *fileName, const char *ext, bool fromBasePath, bool exportOnly ) { + int i; + idStr qpath; + idFile *fp; +// RAVEN BEGIN +// rhummer: Used to verify func_groups didn't disappear somehow. + bool funcGroupFound = false; +// RAVEN END + + qpath = fileName; + qpath.SetFileExtension( ext ); + + idLib::common->Printf( "writing %s...\n", qpath.c_str() ); + + if ( fromBasePath ) { + fp = idLib::fileSystem->OpenFileWrite( qpath, "fs_devpath" ); + } + else { + fp = idLib::fileSystem->OpenExplicitFileWrite( qpath ); + } + + if ( !fp ) { + idLib::common->Warning( "Couldn't open %s\n", qpath.c_str() ); + return false; + } + + fp->WriteFloatString( "Version %d\n", CURRENT_MAP_VERSION ); + + if (exportOnly) + { + for ( i = 0; i < entities.Num(); i++ ) + { + if (entities[i]->epairs.GetInt("export")) + { + entities[i]->Write( fp, i ); + } + } + } + else + { + for ( i = 0; i < entities.Num(); i++ ) { + entities[i]->Write( fp, i ); +// RAVEN BEGIN +// rhummer: See if there are func_groups, there should be if there was during loading.. + if ( !idStr::Icmp( entities[i]->epairs.GetString( "classname" ), "func_group" ) ) { + funcGroupFound = true; + } +// RAVEN END + } + } + +// RAVEN BEGIN +// rhummer: If this is true..something bad happened to cause all func_groups to go away between loading and saving. + if ( mHasFuncGroups && !funcGroupFound && cvarSystem->GetCVarBool( "developer" ) ) { + common->Warning( "^5Did not find any ^2func_groups^0 during save, but there were ^2func_groups^0 found during loading. Was this intended?\n"); + } +// RAVEN END + + idLib::fileSystem->CloseFile( fp ); + + return true; +} +// RAVEN END + +/* +=============== +idMapFile::SetGeometryCRC +=============== +*/ +void idMapFile::SetGeometryCRC( void ) { + int i; + + geometryCRC = 0; + for ( i = 0; i < entities.Num(); i++ ) { + geometryCRC ^= entities[i]->GetGeometryCRC(); + } +} + +/* +=============== +idMapFile::AddEntity +=============== +*/ +int idMapFile::AddEntity( idMapEntity *mapEnt ) { + int ret = entities.Append( mapEnt ); + return ret; +} + +/* +=============== +idMapFile::FindEntity +=============== +*/ +idMapEntity *idMapFile::FindEntity( const char *name ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "name" ), name ) == 0 ) { + return ent; + } + } + return NULL; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntity( idMapEntity *mapEnt ) { + entities.Remove( mapEnt ); + delete mapEnt; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntities( const char *classname ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "classname" ), classname ) == 0 ) { + delete entities[i]; + entities.RemoveIndex( i ); + i--; + } + } +} + +/* +=============== +idMapFile::RemoveAllEntities +=============== +*/ +void idMapFile::RemoveAllEntities() { + entities.DeleteContents( true ); + hasPrimitiveData = false; +} + +/* +=============== +idMapFile::RemovePrimitiveData +=============== +*/ +void idMapFile::RemovePrimitiveData() { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + ent->RemovePrimitiveData(); + } + hasPrimitiveData = false; +// RAVEN BEGIN +// rhummer: debugging stuff.. + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2Removed all primitive data from ^0%s\n", name.c_str() ); + } +// RAVEN END +} + +/* +=============== +idMapFile::NeedsReload +=============== +*/ +bool idMapFile::NeedsReload() { + if ( name.Length() ) { + unsigned int time = (unsigned int)-1; + if ( idLib::fileSystem->ReadFile( name, NULL, &time ) > 0 ) { + return ( time > fileTime ); + } + } + return true; +} + +bool idMapFile::WriteExport( const char *fileName, bool fromBasePath ) +{ + int i, j; + idDict newPairs; + + for ( i = 0; i < entities.Num(); i++ ) + { + idMapEntity *ent = entities[i]; + + if ( ent->epairs.GetInt("export") == 1 ) + { +/* for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + const idKeyValue *kv = ent->epairs.GetKeyVal(j); + + if (kv->key.CmpPrefix("export_") == 0) + { + ent->epairs.Delete(kv->key.c_str()); + j--; + continue; + } + } +*/ + newPairs.Clear(); + + for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + const idKeyValue *kv = ent->epairs.GetKeyVal(j); + + if (kv->GetKey().CmpPrefix("export_") != 0) + { + char newName[1024]; + + sprintf(newName, "export_%s", kv->GetKey().c_str()); + + if (!ent->epairs.GetString(newName, 0)) + { // don't overwrite an existing export + newPairs.Set (newName, kv->GetValue().c_str()); + } + } + } + + ent->epairs.Copy(newPairs); + } + } + + return Write(fileName, "export", fromBasePath, true); +} + +bool idMapFile::ParseExport( const char *filename, bool osPath ) +{ + // no string concatenation for epairs and allow path names for materials +// RAVEN BEGIN +// jsinger: Done this way to reduce the amount of code to change. +// The auto pointer will delete the lexer when this method exits. + idAutoPtr lexer(LexerFactory::MakeLexer( LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ) ); + Lexer &src(*lexer); +// RAVEN END + idToken token; + idStr fullName; + idMapEntity *mapEnt; + int i, j; + int numMerged, numNotMerged, numAdded, numRemoved; + + numMerged = numNotMerged = numAdded = numRemoved = 0; + mHasExportEntities = false; + + fullName = filename; + fullName.StripFileExtension(); + fullName.SetFileExtension( "export" ); + src.LoadFile( fullName, osPath ); + if ( !src.IsLoaded() ) { + // didn't get anything at all + return false; + } + + version = OLD_MAP_VERSION; + fileTime = src.GetFileTime(); + mExportEntities.DeleteContents( true ); + + if ( src.CheckTokenString( "Version" ) ) { + src.ReadTokenOnLine( &token ); + version = token.GetIntValue(); + } + + while( 1 ) { + mapEnt = idMapEntity::Parse( src, false, version ); + if ( !mapEnt ) { + break; + } + mExportEntities.Append( mapEnt ); + } + + if (mExportEntities.Num()) + { + mHasExportEntities = true; + } + + for(i=0;iepairs.GetString("name"); + mapEnt = FindEntity(name); + + if (!mapEnt) + { // check to see if we have an export_name + name = ent->epairs.GetString("export_name", 0); + if (!name || ent->epairs.GetInt("original") ) + { // this is a new entity + ent->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields + ent->epairs.SetInt("merged", 1); + ent->epairs.SetInt("original", 0); // 0 = now the entity in map is always treated as master + entities.Append(ent); + mExportEntities.RemoveIndex(i); + i--; + numAdded++; + continue; + } + else + { + mapEnt = FindEntity(name); + } + } + + if (!mapEnt) + { // this export entity has been removed from the original map + numNotMerged++; + } + else + { // exists in both, do a merge! + mapEnt->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields + mapEnt->epairs.SetInt("merged", 1); + for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + char origName[1024]; + const idKeyValue *exportKV = ent->epairs.GetKeyVal(j); + const idKeyValue *origKV, *mapKV; + + if (exportKV->GetKey().CmpPrefix("export_") != 0) + { + sprintf(origName, "export_%s", exportKV->GetKey().c_str()); + origKV = ent->epairs.FindKey(origName); + mapKV = mapEnt->epairs.FindKey(exportKV->GetKey().c_str()); + + if (origKV && mapKV) + { // keys exist in all spots, compare + if (origKV->GetValue() == exportKV->GetValue()) + { // export hasn't change + } + else if (mapKV->GetValue() == origKV->GetValue()) + { // map is same as the orig, yet export has changed, so bring it over + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + else if (origKV) + { // map has removed this key, so we shouldn't merge it + } + else if (mapKV) + { // map has added this key, so we'll ignore the exported value, which has also been added + } + else + { // this was added to the export, so merge it in + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + else + { + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + numMerged++; + } + } + + // check for any entities that have been marked as exported in the original map but have been removed from the export file + for(i=0;iepairs.GetInt("export")) + { + if (!ent->epairs.GetInt("merged")) + { + entities.RemoveIndex(i); + numRemoved++; + i--; + } + else + { + ent->epairs.Delete("merged"); + } + } + } + + common->Printf("Export Merge: %d added, %d merged, %d not merged, %d removed\n", numAdded, numMerged, numNotMerged, numRemoved); + + mExportEntities.DeleteContents( true ); + + return true; +} diff --git a/source/idlib/math/Angles.cpp b/source/idlib/math/Angles.cpp new file mode 100644 index 0000000..cb894bc --- /dev/null +++ b/source/idlib/math/Angles.cpp @@ -0,0 +1,229 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include + +idAngles ang_zero( 0.0f, 0.0f, 0.0f ); + + +/* +================= +idAngles::Normalize360 + +returns angles normalized to the range [0 <= angle < 360] +================= +*/ +idAngles& idAngles::Normalize360( void ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( ( (*this)[i] >= 360.0f ) || ( (*this)[i] < 0.0f ) ) { + (*this)[i] -= floor( (*this)[i] / 360.0f ) * 360.0f; + + if ( (*this)[i] >= 360.0f ) { + (*this)[i] -= 360.0f; + } + if ( (*this)[i] < 0.0f ) { + (*this)[i] += 360.0f; + } + } + } + + return *this; +} + +/* +================= +idAngles::Normalize180 + +returns angles normalized to the range [-180 < angle <= 180] +================= +*/ +idAngles& idAngles::Normalize180( void ) { + Normalize360(); + + if ( pitch > 180.0f ) { + pitch -= 360.0f; + } + + if ( yaw > 180.0f ) { + yaw -= 360.0f; + } + + if ( roll > 180.0f ) { + roll -= 360.0f; + } + return *this; +} + +/* +================= +idAngles::ToVectors +================= +*/ +void idAngles::ToVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) const { + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + if ( forward ) { + forward->Set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->Set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if ( up ) { + up->Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +/* +================= +idAngles::ToForward +================= +*/ +idVec3 idAngles::ToForward( void ) const { + float sp, sy, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + + return idVec3( cp * cy, cp * sy, -sp ); +} + +/* +================= +idAngles::ToQuat +================= +*/ +idQuat idAngles::ToQuat( void ) const { + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + return idQuat( cxsy*sz - sxcy*cz, -cxsy*cz - sxcy*sz, sxsy*cz - cxcy*sz, cxcy*cz + sxsy*sz ); +} + +/* +================= +idAngles::ToRotation +================= +*/ +idRotation idAngles::ToRotation( void ) const { + idVec3 vec; + float angle, w; + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + if ( pitch == 0.0f ) { + if ( yaw == 0.0f ) { + return idRotation( vec3_origin, idVec3( -1.0f, 0.0f, 0.0f ), roll ); + } + if ( roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, 0.0f, -1.0f ), yaw ); + } + } else if ( yaw == 0.0f && roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, -1.0f, 0.0f ), pitch ); + } + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + vec.x = cxsy * sz - sxcy * cz; + vec.y = -cxsy * cz - sxcy * sz; + vec.z = sxsy * cz - cxcy * sz; + w = cxcy * cz + sxsy * sz; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +================= +idAngles::ToMat3 +================= +*/ +idMat3 idAngles::ToMat3( void ) const { + idMat3 mat; + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + mat[ 0 ].Set( cp * cy, cp * sy, -sp ); + mat[ 1 ].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[ 2 ].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return mat; +} + +// RAVEN BEGIN +// jscott: slightly quicker version without the copy +idMat3 &idAngles::ToMat3( idMat3 &mat ) const { + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + mat[0].Set( cp * cy, cp * sy, -sp ); + mat[1].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[2].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return mat; +} +// RAVEN END + +/* +================= +idAngles::ToMat4 +================= +*/ +idMat4 idAngles::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +================= +idAngles::ToAngularVelocity +================= +*/ +idVec3 idAngles::ToAngularVelocity( void ) const { + idRotation rotation = idAngles::ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============= +idAngles::ToString +============= +*/ +const char *idAngles::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Angles.h b/source/idlib/math/Angles.h new file mode 100644 index 0000000..d223b2b --- /dev/null +++ b/source/idlib/math/Angles.h @@ -0,0 +1,306 @@ + +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +/* +=============================================================================== + + Euler angles + +=============================================================================== +*/ + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#ifndef M_PI +# define M_PI (3.1415926536f) +#endif + +class idVec3; +class idQuat; +class idRotation; +class idMat3; +class idMat4; + +class idAngles { +public: + float pitch; + float yaw; + float roll; + + idAngles( void ); + idAngles( float pitch, float yaw, float roll ); + explicit idAngles( const idVec3 &v ); + + void Set( float pitch, float yaw, float roll ); + idAngles & Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idAngles operator-() const; // negate angles, in general not the inverse rotation + idAngles & operator=( const idAngles &a ); + idAngles operator+( const idAngles &a ) const; + idAngles & operator+=( const idAngles &a ); + idAngles operator-( const idAngles &a ) const; + idAngles & operator-=( const idAngles &a ); + idAngles operator*( const float a ) const; + idAngles & operator*=( const float a ); + idAngles operator/( const float a ) const; + idAngles & operator/=( const float a ); + + friend idAngles operator*( const float a, const idAngles &b ); + + bool Compare( const idAngles &a ) const; // exact compare, no epsilon + bool Compare( const idAngles &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idAngles &a ) const; // exact compare, no epsilon + bool operator!=( const idAngles &a ) const; // exact compare, no epsilon + + idAngles & Normalize360( void ); // normalizes 'this' + idAngles & Normalize180( void ); // normalizes 'this' + + float Length( void ) const; + float LengthSqr( void ) const; + void Clamp( const idAngles &min, const idAngles &max ); + + int GetDimension( void ) const; + + void ToVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ) const; + idVec3 ToForward( void ) const; + idQuat ToQuat( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + +// RAVEN BEGIN + idMat3 &ToMat3( idMat3 &mat ) const; +// abahr + idAngles Random( const idVec3& range, idRandom& random ) const; + idAngles& Scale( const idAngles& scalar ); + idAngles& Remap( const int map[], const int dirMap[] ); +// RAVEN END + + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + bool FixDenormals( void ); +}; + +extern idAngles ang_zero; + +ID_INLINE idAngles::idAngles( void ) { +} + +ID_INLINE idAngles::idAngles( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles::idAngles( const idVec3 &v ) { + this->pitch = v[0]; + this->yaw = v[1]; + this->roll = v[2]; +} + +ID_INLINE void idAngles::Set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles &idAngles::Zero( void ) { + pitch = yaw = roll = 0.0f; + return *this; +} + +ID_INLINE float idAngles::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE float &idAngles::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE idAngles idAngles::operator-() const { + return idAngles( -pitch, -yaw, -roll ); +} + +ID_INLINE idAngles &idAngles::operator=( const idAngles &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; + return *this; +} + +ID_INLINE idAngles idAngles::operator+( const idAngles &a ) const { + return idAngles( pitch + a.pitch, yaw + a.yaw, roll + a.roll ); +} + +ID_INLINE idAngles& idAngles::operator+=( const idAngles &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator-( const idAngles &a ) const { + return idAngles( pitch - a.pitch, yaw - a.yaw, roll - a.roll ); +} + +ID_INLINE idAngles& idAngles::operator-=( const idAngles &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator*( const float a ) const { + return idAngles( pitch * a, yaw * a, roll * a ); +} + +ID_INLINE idAngles& idAngles::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + return *this; +} + +ID_INLINE idAngles idAngles::operator/( const float a ) const { + float inva = 1.0f / a; + return idAngles( pitch * inva, yaw * inva, roll * inva ); +} + +ID_INLINE idAngles& idAngles::operator/=( float a ) { + float inva = 1.0f / a; + pitch *= inva; + yaw *= inva; + roll *= inva; + return *this; +} + +ID_INLINE idAngles operator*( const float a, const idAngles &b ) { + return idAngles( a * b.pitch, a * b.yaw, a * b.roll ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a ) const { + return ( ( a.pitch == pitch ) && ( a.yaw == yaw ) && ( a.roll == roll ) ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a, const float epsilon ) const { + if ( idMath::Fabs( pitch - a.pitch ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( yaw - a.yaw ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( roll - a.roll ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idAngles::operator==( const idAngles &a ) const { + return Compare( a ); +} + +ID_INLINE bool idAngles::operator!=( const idAngles &a ) const { + return !Compare( a ); +} + +ID_INLINE float idAngles::Length( void ) const { + return idMath::Sqrt( yaw * yaw + pitch * pitch + roll * roll ); +} + +ID_INLINE float idAngles::LengthSqr( void ) const { + return ( yaw * yaw + pitch * pitch + roll * roll ); +} + +ID_INLINE void idAngles::Clamp( const idAngles &min, const idAngles &max ) { + if ( pitch < min.pitch ) { + pitch = min.pitch; + } else if ( pitch > max.pitch ) { + pitch = max.pitch; + } + if ( yaw < min.yaw ) { + yaw = min.yaw; + } else if ( yaw > max.yaw ) { + yaw = max.yaw; + } + if ( roll < min.roll ) { + roll = min.roll; + } else if ( roll > max.roll ) { + roll = max.roll; + } +} + +ID_INLINE int idAngles::GetDimension( void ) const { + return 3; +} + +ID_INLINE const float *idAngles::ToFloatPtr( void ) const { + return &pitch; +} + +ID_INLINE float *idAngles::ToFloatPtr( void ) { + return &pitch; +} + +// RAVEN BEGIN +// abahr +ID_INLINE idAngles idAngles::Random( const idVec3& range, idRandom& random ) const { + idAngles a( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + a[ ix ] += a[ ix ] * range[ix] * random.CRandomFloat(); + } + return a; +} + +ID_INLINE idAngles& idAngles::Scale( const idAngles& scalar ) { + for( int ix = 0; ix < GetDimension(); ++ix ) { + (*this)[ix] *= scalar[ix]; + } + + return *this; +} + +ID_INLINE idAngles& idAngles::Remap( const int map[], const int dirMap[] ) { + idAngles ang( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + (*this)[ ix ] = SignZero(dirMap[ix]) * ang[ abs(map[ix]) ]; + } + + return *this; +} +// RAVEN END + +ID_INLINE bool idAngles::FixDenormals( void ) { + bool denormal = false; + if ( fabs( yaw ) < 1e-30f ) { + yaw = 0.0f; + denormal = true; + } + if ( fabs( pitch ) < 1e-30f ) { + pitch = 0.0f; + denormal = true; + } + if ( fabs( roll ) < 1e-30f ) { + roll = 0.0f; + denormal = true; + } + return denormal; + +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/source/idlib/math/Complex.cpp b/source/idlib/math/Complex.cpp new file mode 100644 index 0000000..9e41ede --- /dev/null +++ b/source/idlib/math/Complex.cpp @@ -0,0 +1,14 @@ + +#include "../precompiled.h" +#pragma hdrstop + +idComplex complex_origin( 0.0f, 0.0f ); + +/* +============= +idComplex::ToString +============= +*/ +const char *idComplex::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Complex.h b/source/idlib/math/Complex.h new file mode 100644 index 0000000..1108726 --- /dev/null +++ b/source/idlib/math/Complex.h @@ -0,0 +1,321 @@ + +#ifndef __MATH_COMPLEX_H__ +#define __MATH_COMPLEX_H__ + +/* +=============================================================================== + + Complex number + +=============================================================================== +*/ + +class idComplex { +public: + float r; // real part + float i; // imaginary part + + idComplex( void ); + idComplex( const float r, const float i ); + + void Set( const float r, const float i ); + void Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idComplex operator-() const; + idComplex & operator=( const idComplex &a ); + + idComplex operator*( const idComplex &a ) const; + idComplex operator/( const idComplex &a ) const; + idComplex operator+( const idComplex &a ) const; + idComplex operator-( const idComplex &a ) const; + + idComplex & operator*=( const idComplex &a ); + idComplex & operator/=( const idComplex &a ); + idComplex & operator+=( const idComplex &a ); + idComplex & operator-=( const idComplex &a ); + + idComplex operator*( const float a ) const; + idComplex operator/( const float a ) const; + idComplex operator+( const float a ) const; + idComplex operator-( const float a ) const; + + idComplex & operator*=( const float a ); + idComplex & operator/=( const float a ); + idComplex & operator+=( const float a ); + idComplex & operator-=( const float a ); + + friend idComplex operator*( const float a, const idComplex &b ); + friend idComplex operator/( const float a, const idComplex &b ); + friend idComplex operator+( const float a, const idComplex &b ); + friend idComplex operator-( const float a, const idComplex &b ); + + bool Compare( const idComplex &a ) const; // exact compare, no epsilon + bool Compare( const idComplex &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idComplex &a ) const; // exact compare, no epsilon + bool operator!=( const idComplex &a ) const; // exact compare, no epsilon + + idComplex Reciprocal( void ) const; + idComplex Sqrt( void ) const; + float Abs( void ) const; + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; +}; + +extern idComplex complex_origin; +#define complex_zero complex_origin + +ID_INLINE idComplex::idComplex( void ) { +} + +ID_INLINE idComplex::idComplex( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Set( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Zero( void ) { + r = i = 0.0f; +} + +ID_INLINE float idComplex::operator[]( int index ) const { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE float& idComplex::operator[]( int index ) { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE idComplex idComplex::operator-() const { + return idComplex( -r, -i ); +} + +ID_INLINE idComplex &idComplex::operator=( const idComplex &a ) { + r = a.r; + i = a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const idComplex &a ) const { + return idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); +} + +ID_INLINE idComplex idComplex::operator/( const idComplex &a ) const { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + return idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + return idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } +} + +ID_INLINE idComplex idComplex::operator+( const idComplex &a ) const { + return idComplex( r + a.r, i + a.i ); +} + +ID_INLINE idComplex idComplex::operator-( const idComplex &a ) const { + return idComplex( r - a.r, i - a.i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const idComplex &a ) { + *this = idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const idComplex &a ) { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + *this = idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + *this = idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const idComplex &a ) { + r += a.r; + i += a.i; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const idComplex &a ) { + r -= a.r; + i -= a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const float a ) const { + return idComplex( r * a, i * a ); +} + +ID_INLINE idComplex idComplex::operator/( const float a ) const { + float s = 1.0f / a; + return idComplex( r * s, i * s ); +} + +ID_INLINE idComplex idComplex::operator+( const float a ) const { + return idComplex( r + a, i ); +} + +ID_INLINE idComplex idComplex::operator-( const float a ) const { + return idComplex( r - a, i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const float a ) { + r *= a; + i *= a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const float a ) { + float s = 1.0f / a; + r *= s; + i *= s; + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const float a ) { + r += a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const float a ) { + r -= a; + return *this; +} + +ID_INLINE idComplex operator*( const float a, const idComplex &b ) { + return idComplex( a * b.r, a * b.i ); +} + +ID_INLINE idComplex operator/( const float a, const idComplex &b ) { + float s, t; + if ( idMath::Fabs( b.r ) >= idMath::Fabs( b.i ) ) { + s = b.i / b.r; + t = a / ( b.r + s * b.i ); + return idComplex( t, - s * t ); + } else { + s = b.r / b.i; + t = a / ( s * b.r + b.i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex operator+( const float a, const idComplex &b ) { + return idComplex( a + b.r, b.i ); +} + +ID_INLINE idComplex operator-( const float a, const idComplex &b ) { + return idComplex( a - b.r, -b.i ); +} + +ID_INLINE idComplex idComplex::Reciprocal( void ) const { + float s, t; + if ( idMath::Fabs( r ) >= idMath::Fabs( i ) ) { + s = i / r; + t = 1.0f / ( r + s * i ); + return idComplex( t, - s * t ); + } else { + s = r / i; + t = 1.0f / ( s * r + i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex idComplex::Sqrt( void ) const { + float x, y, w; + + if ( r == 0.0f && i == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x >= y ) { + w = y / x; + w = idMath::Sqrt( x ) * idMath::Sqrt( 0.5f * ( 1.0f + idMath::Sqrt( 1.0f + w * w ) ) ); + } else { + w = x / y; + w = idMath::Sqrt( y ) * idMath::Sqrt( 0.5f * ( w + idMath::Sqrt( 1.0f + w * w ) ) ); + } + if ( w == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + if ( r >= 0.0f ) { + return idComplex( w, 0.5f * i / w ); + } else { + return idComplex( 0.5f * y / w, ( i >= 0.0f ) ? w : -w ); + } +} + +ID_INLINE float idComplex::Abs( void ) const { + float x, y, t; + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x == 0.0f ) { + return y; + } else if ( y == 0.0f ) { + return x; + } else if ( x > y ) { + t = y / x; + return x * idMath::Sqrt( 1.0f + t * t ); + } else { + t = x / y; + return y * idMath::Sqrt( 1.0f + t * t ); + } +} + +ID_INLINE bool idComplex::Compare( const idComplex &a ) const { + return ( ( r == a.r ) && ( i == a.i ) ); +} + +ID_INLINE bool idComplex::Compare( const idComplex &a, const float epsilon ) const { + if ( idMath::Fabs( r - a.r ) > epsilon ) { + return false; + } + if ( idMath::Fabs( i - a.i ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idComplex::operator==( const idComplex &a ) const { + return Compare( a ); +} + +ID_INLINE bool idComplex::operator!=( const idComplex &a ) const { + return !Compare( a ); +} + +ID_INLINE int idComplex::GetDimension( void ) const { + return 2; +} + +ID_INLINE const float *idComplex::ToFloatPtr( void ) const { + return &r; +} + +ID_INLINE float *idComplex::ToFloatPtr( void ) { + return &r; +} + +#endif /* !__MATH_COMPLEX_H__ */ diff --git a/source/idlib/math/Curve.h b/source/idlib/math/Curve.h new file mode 100644 index 0000000..ea57233 --- /dev/null +++ b/source/idlib/math/Curve.h @@ -0,0 +1,2572 @@ + +#ifndef __MATH_CURVE_H__ +#define __MATH_CURVE_H__ + +/* +=============================================================================== + + Curve base template. + +=============================================================================== +*/ + +template< class type > +class idCurve { +public: + idCurve( void ); + virtual ~idCurve( void ); + + virtual int AddValue( const float time, const type &value ); + virtual void RemoveIndex( const int index ) { values.RemoveIndex(index); times.RemoveIndex(index); changed = true; } + virtual void Clear( void ) { values.Clear(); times.Clear(); currentIndex = -1; changed = true; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + + virtual bool IsDone( const float time ) const; + +// RAVEN BEGIN +// jscott: added + void SetGranularity( int gran ) { times.SetGranularity( gran ); values.SetGranularity( gran ); } +// RAVEN END + + int GetNumValues( void ) const { return values.Num(); } + void SetValue( const int index, const type &value ) { values[index] = value; changed = true; } + type GetValue( const int index ) const { return values[index]; } + type * GetValueAddress( const int index ) { return &values[index]; } + float GetTime( const int index ) const { return times[index]; } + + float GetLengthForTime( const float time ) const; + float GetTimeForLength( const float length, const float epsilon = 0.1f ) const; + float GetLengthBetweenKnots( const int i0, const int i1 ) const; + + void MakeUniform( const float totalTime ); + void SetConstantSpeed( const float totalTime ); + void ShiftTime( const float deltaTime ); + void Translate( const type &translation ); + +// RAVEN BEGIN +// ddynerman: spline joining + virtual bool Weld( idCurve* c ) const; +// RAVEN END + +protected: + idList times; // knots + idList values; // knot values + mutable int currentIndex; // cached index for fast lookup + mutable bool changed; // set whenever the curve changes + + int IndexForTime( const float time ) const; + float TimeForIndex( const int index ) const; + type ValueForIndex( const int index ) const; + + float GetSpeed( const float time ) const; + float RombergIntegral( const float t0, const float t1, const int order ) const; +}; + +/* +==================== +idCurve::idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::idCurve( void ) { + currentIndex = -1; + changed = false; +} + +/* +==================== +idCurve::~idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::~idCurve( void ) { +} + +/* +==================== +idCurve::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve::AddValue( const float time, const type &value ) { + int i; + + i = IndexForTime( time ); + times.Insert( time, i ); + values.Insert( value, i ); + changed = true; + return i; +} + +/* +==================== +idCurve::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentValue( const float time ) const { + int i; + + i = IndexForTime( time ); + if ( i >= values.Num() ) { + return values[values.Num() - 1]; + } else { + return values[i]; + } +} + +/* +==================== +idCurve::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentFirstDerivative( const float time ) const { + return ( values[0] - values[0] ); +} + +/* +==================== +idCurve::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentSecondDerivative( const float time ) const { + return ( values[0] - values[0] ); +} + +/* +==================== +idCurve::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve::IsDone( const float time ) const { + return ( time >= times[ times.Num() - 1 ] ); +} + +/* +==================== +idCurve::GetSpeed +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetSpeed( const float time ) const { + int i; + float speed; + type value; + + value = GetCurrentFirstDerivative( time ); + for ( speed = 0.0f, i = 0; i < value.GetDimension(); i++ ) { + speed += value[i] * value[i]; + } + return idMath::Sqrt( speed ); +} + +/* +==================== +idCurve::RombergIntegral +==================== +*/ +template< class type > +ID_INLINE float idCurve::RombergIntegral( const float t0, const float t1, const int order ) const { + int i, j, k, m, n; + float sum, delta; + float *temp[2]; + + temp[0] = (float *) _alloca16( order * sizeof( float ) ); + temp[1] = (float *) _alloca16( order * sizeof( float ) ); + + delta = t1 - t0; + temp[0][0] = 0.5f * delta * ( GetSpeed( t0 ) + GetSpeed( t1 ) ); + + for ( i = 2, m = 1; i <= order; i++, m *= 2, delta *= 0.5f ) { + + // approximate using the trapezoid rule + sum = 0.0f; + for ( j = 1; j <= m; j++ ) { + sum += GetSpeed( t0 + delta * ( j - 0.5f ) ); + } + + // Richardson extrapolation + temp[1][0] = 0.5f * ( temp[0][0] + delta * sum ); + for ( k = 1, n = 4; k < i; k++, n *= 4 ) { + temp[1][k] = ( n * temp[1][k-1] - temp[0][k-1] ) / ( n - 1 ); + } + + for ( j = 0; j < i; j++ ) { + temp[0][j] = temp[1][j]; + } + } + return temp[0][order-1]; +} + +/* +==================== +idCurve::GetLengthBetweenKnots +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthBetweenKnots( const int i0, const int i1 ) const { + float length = 0.0f; + for ( int i = i0; i < i1; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + return length; +} + +/* +==================== +idCurve::GetLengthForTime +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthForTime( const float time ) const { + float length = 0.0f; + int index = IndexForTime( time ); + for ( int i = 0; i < index; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + length += RombergIntegral( times[index], time, 5 ); + return length; +} + +/* +==================== +idCurve::GetTimeForLength +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetTimeForLength( const float length, const float epsilon ) const { + int i, index; + float *accumLength, totalLength, len0, len1, t, diff; + + if ( length <= 0.0f ) { + return times[0]; + } + + accumLength = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( index = 0; index < values.Num() - 1; index++ ) { + totalLength += GetLengthBetweenKnots( index, index + 1 ); + accumLength[index] = totalLength; + if ( length < accumLength[index] ) { + break; + } + } + + if ( index >= values.Num() - 1 ) { + return times[times.Num() - 1]; + } + + if ( index == 0 ) { + len0 = length; + len1 = accumLength[0]; + } else { + len0 = length - accumLength[index-1]; + len1 = accumLength[index] - accumLength[index-1]; + } + + // invert the arc length integral using Newton's method + t = ( times[index+1] - times[index] ) * len0 / len1; + for ( i = 0; i < 32; i++ ) { + diff = RombergIntegral( times[index], times[index] + t, 5 ) - len0; + if ( idMath::Fabs( diff ) <= epsilon ) { + return times[index] + t; + } + t -= diff / GetSpeed( times[index] + t ); + } + return times[index] + t; +} + +/* +==================== +idCurve::MakeUniform +==================== +*/ +template< class type > +ID_INLINE void idCurve::MakeUniform( const float totalTime ) { + int i, n; + + n = times.Num() - 1; + for ( i = 0; i <= n; i++ ) { + times[i] = i * totalTime / n; + } + changed = true; +} + +/* +==================== +idCurve::SetConstantSpeed +==================== +*/ +template< class type > +ID_INLINE void idCurve::SetConstantSpeed( const float totalTime ) { +// RAVEN BEGIN +// bdube: fixed warning + int i; +// RAVEN END + float *length, totalLength, scale, t; + + length = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( i = 0; i < values.Num() - 1; i++ ) { + length[i] = GetLengthBetweenKnots( i, i + 1 ); + totalLength += length[i]; + } + scale = totalTime / totalLength; + for ( t = 0.0f, i = 0; i < times.Num() - 1; i++ ) { + times[i] = t; + t += scale * length[i]; + } + times[times.Num() - 1] = totalTime; + changed = true; +} + +/* +==================== +idCurve::ShiftTime +==================== +*/ +template< class type > +ID_INLINE void idCurve::ShiftTime( const float deltaTime ) { + for ( int i = 0; i < times.Num(); i++ ) { + times[i] += deltaTime; + } + changed = true; +} + +/* +==================== +idCurve::Translate +==================== +*/ +template< class type > +ID_INLINE void idCurve::Translate( const type &translation ) { + for ( int i = 0; i < values.Num(); i++ ) { + values[i] += translation; + } + changed = true; +} + +/* +==================== +idCurve::IndexForTime + + find the index for the first time greater than or equal to the given time +==================== +*/ +template< class type > +ID_INLINE int idCurve::IndexForTime( const float time ) const { + int len, mid, offset, res; + + if ( currentIndex >= 0 && currentIndex <= times.Num() ) { + // use the cached index if it is still valid + if ( currentIndex == 0 ) { + if ( time <= times[currentIndex] ) { + return currentIndex; + } + } else if ( currentIndex == times.Num() ) { + if ( time > times[currentIndex-1] ) { + return currentIndex; + } + } else if ( time > times[currentIndex-1] && time <= times[currentIndex] ) { + return currentIndex; + } else if ( time > times[currentIndex] && ( currentIndex+1 == times.Num() || time <= times[currentIndex+1] ) ) { + // use the next index + currentIndex++; + return currentIndex; + } + } + + // use binary search to find the index for the given time + len = times.Num(); + mid = len; + offset = 0; + res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( time == times[offset+mid] ) { + return offset+mid; + } else if ( time > times[offset+mid] ) { + offset += mid; + len -= mid; + res = 1; + } else { + len -= mid; + res = 0; + } + } + currentIndex = offset+res; + return currentIndex; +} + +/* +==================== +idCurve::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::ValueForIndex( const int index ) const { + int n = values.Num()-1; + + if ( index < 0 ) { + return values[0] + index * ( values[1] - values[0] ); + } else if ( index > n ) { + return values[n] + ( index - n ) * ( values[n] - values[n-1] ); + } + return values[index]; +} + +/* +==================== +idCurve::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve::TimeForIndex( const int index ) const { + int n = times.Num()-1; + + if ( index < 0 ) { + return times[0] + index * ( times[1] - times[0] ); + } else if ( index > n ) { + return times[n] + ( index - n ) * ( times[n] - times[n-1] ); + } + return times[index]; +} + +// RAVEN BEGIN +// ddynerman: spline welding +/* +==================== +idCurve::Weld + + Weld splines, implementation specific - parent version does nothing +==================== +*/ +template< class type > +ID_INLINE bool idCurve::Weld( idCurve* c ) const { + return false; +} +// RAVEN END + + +/* +=============================================================================== + + Bezier Curve template. + The degree of the polynomial equals the number of knots minus one. + +=============================================================================== +*/ + +template< class type > +class idCurve_Bezier : public idCurve { +public: + idCurve_Bezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_Bezier::idCurve_Bezier +==================== +*/ +template< class type > +ID_INLINE idCurve_Bezier::idCurve_Bezier( void ) { +} + +/* +==================== +idCurve_Bezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentValue( const float time ) const { + int i; + float *bvals; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + Basis( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + return v; +} + +/* +==================== +idCurve_Bezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentFirstDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisFirstDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-1) / d ) * v; +} + +/* +==================== +idCurve_Bezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentSecondDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisSecondDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-2) * (this->values.Num()-1) / ( d * d ) ) * v; +} + +/* +==================== +idCurve_Bezier::Basis + + bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::Basis( const int order, const float t, float *bvals ) const { + int i, j, d; + float *c, c1, c2, s, o, ps, po; + + bvals[0] = 1.0f; + d = order - 1; + if ( d <= 0 ) { + return; + } + + c = (float *) _alloca16( (d+1) * sizeof( float ) ); + s = (float) ( t - this->times[0] ) / ( this->times[this->times.Num()-1] - this->times[0] ); + o = 1.0f - s; + ps = s; + po = o; + + for ( i = 1; i < d; i++ ) { + c[i] = 1.0f; + } + for ( i = 1; i < d; i++ ) { + c[i-1] = 0.0f; + c1 = c[i]; + c[i] = 1.0f; + for ( j = i+1; j <= d; j++ ) { + c2 = c[j]; + c[j] = c1 + c[j-1]; + c1 = c2; + } + bvals[i] = c[d] * ps; + ps *= s; + } + for ( i = d-1; i >= 0; i-- ) { + bvals[i] *= po; + po *= o; + } + bvals[d] = ps; +} + +/* +==================== +idCurve_Bezier::BasisFirstDerivative + + first derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisFirstDerivative( const int order, const float t, float *bvals ) const { + int i; + + Basis( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + +/* +==================== +idCurve_Bezier::BasisSecondDerivative + + second derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisSecondDerivative( const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + + +/* +=============================================================================== + + Quadratic Bezier Curve template. + Should always have exactly three knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_QuadraticBezier : public idCurve { +public: + idCurve_QuadraticBezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_QuadraticBezier::idCurve_QuadraticBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_QuadraticBezier::idCurve_QuadraticBezier( void ) { +} + + +/* +==================== +idCurve_QuadraticBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentValue( const float time ) const { + float bvals[3]; + assert( this->values.Num() == 3 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ); +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / d; +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / ( d * d ); +} + +/* +==================== +idCurve_QuadraticBezier::Basis + + quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = s2 - 2.0f * s1 + 1.0f; + bvals[1] = -2.0f * s2 + 2.0f * s1; + bvals[2] = s2; +} + +/* +==================== +idCurve_QuadraticBezier::BasisFirstDerivative + + first derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f * s1 - 2.0f; + bvals[1] = -4.0f * s1 + 2.0f; + bvals[2] = 2.0f * s1; +} + +/* +==================== +idCurve_QuadraticBezier::BasisSecondDerivative + + second derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f; + bvals[1] = -4.0f; + bvals[2] = 2.0f; +} + + +/* +=============================================================================== + + Cubic Bezier Curve template. + Should always have exactly four knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CubicBezier : public idCurve { +public: + idCurve_CubicBezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CubicBezier::idCurve_CubicBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_CubicBezier::idCurve_CubicBezier( void ) { +} + + +/* +==================== +idCurve_CubicBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentValue( const float time ) const { + float bvals[4]; + assert( this->values.Num() == 4 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ); +} + +/* +==================== +idCurve_CubicBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / d; +} + +/* +==================== +idCurve_CubicBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / ( d * d ); +} + +/* +==================== +idCurve_CubicBezier::Basis + + cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + float s3 = s2 * s1; + bvals[0] = -s3 + 3.0f * s2 - 3.0f * s1 + 1.0f; + bvals[1] = 3.0f * s3 - 6.0f * s2 + 3.0f * s1; + bvals[2] = -3.0f * s3 + 3.0f * s2; + bvals[3] = s3; +} + +/* +==================== +idCurve_CubicBezier::BasisFirstDerivative + + first derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = -3.0f * s2 + 6.0f * s1 - 3.0f; + bvals[1] = 9.0f * s2 - 12.0f * s1 + 3.0f; + bvals[2] = -9.0f * s2 + 6.0f * s1; + bvals[3] = 3.0f * s2; +} + +/* +==================== +idCurve_CubicBezier::BasisSecondDerivative + + second derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + bvals[0] = -6.0f * s1 + 6.0f; + bvals[1] = 18.0f * s1 - 12.0f; + bvals[2] = -18.0f * s1 + 6.0f; + bvals[3] = 6.0f * s1; +} + + +/* +=============================================================================== + + Spline base template. + +=============================================================================== +*/ + +template< class type > +class idCurve_Spline : public idCurve { +public: + enum boundary_t { BT_FREE, BT_CLAMPED, BT_CLOSED }; + + idCurve_Spline( void ); + + virtual bool IsDone( const float time ) const; + + virtual void SetBoundaryType( const boundary_t bt ) { boundaryType = bt; this->changed = true; } + virtual boundary_t GetBoundaryType( void ) const { return boundaryType; } + + virtual void SetCloseTime( const float t ) { closeTime = t; this->changed = true; } +// RAVEN BEGIN +// jsinger: this->changed to be const so that we can call it during binary serialization + virtual float GetCloseTime( void ) const { return boundaryType == BT_CLOSED ? closeTime : 0.0f; } +// RAVEN END + +protected: + boundary_t boundaryType; + float closeTime; + + type ValueForIndex( const int index ) const; + float TimeForIndex( const int index ) const; + float ClampedTime( const float t ) const; +}; + +/* +==================== +idCurve_Spline::idCurve_Spline +==================== +*/ +template< class type > +ID_INLINE idCurve_Spline::idCurve_Spline( void ) { + boundaryType = BT_FREE; + closeTime = 0.0f; +} + +/* +==================== +idCurve_Spline::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Spline::ValueForIndex( const int index ) const { + int n = this->values.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ this->values.Num() + index % this->values.Num() ]; + } + else { + return this->values[0] + (float)index * ( this->values[1] - this->values[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ index % this->values.Num() ]; + } + else { + return this->values[n] + (float)( index - n ) * ( this->values[n] - this->values[n-1] ); + } + } + return this->values[index]; +} + +/* +==================== +idCurve_Spline::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::TimeForIndex( const int index ) const { + int n = this->times.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) - ( this->times[n] + closeTime - this->times[this->times.Num() + index % this->times.Num()] ); + } + else { + return this->times[0] + index * ( this->times[1] - this->times[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) + this->times[index % this->times.Num()]; + } + else { + return this->times[n] + ( index - n ) * ( this->times[n] - this->times[n-1] ); + } + } + return this->times[index]; +} + +/* +==================== +idCurve_Spline::ClampedTime + + return the clamped time based on the boundary type +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::ClampedTime( const float t ) const { + if ( boundaryType == BT_CLAMPED ) { + if ( t < this->times[0] ) { + return this->times[0]; + } + else if ( t >= this->times[this->times.Num()-1] ) { + return this->times[this->times.Num()-1]; + } + } + return t; +} + +/* +==================== +idCurve_Spline::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve_Spline::IsDone( const float time ) const { + return ( boundaryType != BT_CLOSED && time >= this->times[ this->times.Num() - 1 ] ); +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_NaturalCubicSpline : public idCurve_Spline { +public: + idCurve_NaturalCubicSpline( void ); + + virtual void Clear( void ) { idCurve_Spline::Clear(); this->values.Clear(); b.Clear(); c.Clear(); d.Clear(); } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + mutable idListb; + mutable idListc; + mutable idListd; + + void Setup( void ) const; + void SetupFree( void ) const; + void SetupClamped( void ) const; + void SetupClosed( void ) const; +}; + +/* +==================== +idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline( void ) { +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentValue( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( this->values[i] + s * ( b[i] + s * ( c[i] + s * d[i] ) ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentFirstDerivative( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( b[i] + s * ( 2.0f * c[i] + 3.0f * s * d[i] ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentSecondDerivative( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( 2.0f * c[i] + 6.0f * s * d[i] ); +} + +/* +==================== +idCurve_NaturalCubicSpline::Setup +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::Setup( void ) const { + if ( this->changed ) { + switch( this->boundaryType ) { + case idCurve_Spline::BT_FREE: SetupFree(); break; + case idCurve_Spline::BT_CLAMPED: SetupClamped(); break; + case idCurve_Spline::BT_CLOSED: SetupClosed(); break; + } + this->changed = false; + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupFree +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupFree( void ) const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 1.0f; + gamma[0] = 0.0f; + delta[0] = this->values[0] - this->values[0]; + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + beta[this->values.Num() - 1] = 1.0f; + delta[this->values.Num() - 1] = this->values[0] - this->values[0]; + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = this->values[0] - this->values[0]; + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i] * ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClamped +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClamped( void ) const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + inv = 1.0f / d0[0]; + alpha[0] = 3.0f * ( inv - 1.0f ) * ( this->values[1] - this->values[0] ); + inv = 1.0f / d0[this->values.Num() - 2]; + alpha[this->values.Num() - 1] = 3.0f * ( 1.0f - inv ) * ( this->values[this->values.Num() - 1] - this->values[this->values.Num() - 2] ); + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 2.0f * d0[0]; + gamma[0] = 0.5f; + inv = 1.0f / beta[0]; + delta[0] = inv * alpha[0]; + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + + beta[this->values.Num() - 1] = d0[this->values.Num() - 2] * ( 2.0f - gamma[this->values.Num() - 2] ); + inv = 1.0f / beta[this->values.Num() - 1]; + delta[this->values.Num() - 1] = inv * ( alpha[this->values.Num() - 1] - d0[this->values.Num() - 2] * delta[this->values.Num() - 2] ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = delta[this->values.Num() - 1]; + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i]* ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClosed +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClosed( void ) const { + int i, j; + float c0, c1; + float *d0; + idMatX mat; + idVecX x; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + x.SetData( this->values.Num(), VECX_ALLOCA( this->values.Num() ) ); + mat.SetData( this->values.Num(), this->values.Num(), MATX_ALLOCA( this->values.Num() * this->values.Num() ) ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + // matrix of system + mat[0][0] = 1.0f; + mat[0][this->values.Num() - 1] = -1.0f; + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + mat[i][i-1] = d0[i-1]; + mat[i][i ] = 2.0f * ( d0[i-1] + d0[i] ); + mat[i][i+1] = d0[i]; + } + mat[this->values.Num() - 1][this->values.Num() - 2] = d0[this->values.Num() - 2]; + mat[this->values.Num() - 1][0] = 2.0f * ( d0[this->values.Num() - 2] + d0[0] ); + mat[this->values.Num() - 1][1] = d0[0]; + + // right-hand side + c[0].Zero(); + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + c0 = 1.0f / d0[i]; + c1 = 1.0f / d0[i-1]; + c[i] = 3.0f * ( c0 * ( this->values[i + 1] - this->values[i] ) - c1 * ( this->values[i] - this->values[i - 1] ) ); + } + c0 = 1.0f / d0[0]; + c1 = 1.0f / d0[this->values.Num() - 2]; + c[this->values.Num() - 1] = 3.0f * ( c0 * ( this->values[1] - this->values[0] ) - c1 * ( this->values[0] - this->values[this->values.Num() - 2] ) ); + + // solve system for each dimension + mat.LU_Factor( NULL ); + for ( i = 0; i < this->values[0].GetDimension(); i++ ) { + for ( j = 0; j < this->values.Num(); j++ ) { + x[j] = c[j][i]; + } + mat.LU_Solve( x, x, NULL ); + for ( j = 0; j < this->values.Num(); j++ ) { + c[j][i] = x[j]; + } + } + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + c0 = 1.0f / d0[i]; + b[i] = c0 * ( this->values[i + 1] - this->values[i] ) - ( 1.0f / 3.0f ) * ( c[i+1] + 2.0f * c[i] ) * d0[i]; + d[i] = ( 1.0f / 3.0f ) * c0 * ( c[i + 1] - c[i] ); + } +} + + +/* +=============================================================================== + + Uniform Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CatmullRomSpline : public idCurve_Spline { +public: + idCurve_CatmullRomSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CatmullRomSpline::idCurve_CatmullRomSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_CatmullRomSpline::idCurve_CatmullRomSpline( void ) { +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_CatmullRomSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( -s + 2.0f ) * s - 1.0f ) * s * 0.5f; // -0.5f s * s * s + s * s - 0.5f * s + bvals[1] = ( ( ( 3.0f * s - 5.0f ) * s ) * s + 2.0f ) * 0.5f; // 1.5f * s * s * s - 2.5f * s * s + 1.0f + bvals[2] = ( ( -3.0f * s + 4.0f ) * s + 1.0f ) * s * 0.5f; // -1.5f * s * s * s - 2.0f * s * s + 0.5f s + bvals[3] = ( ( s - 1.0f ) * s * s ) * 0.5f; // 0.5f * s * s * s - 0.5f * s * s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( -1.5f * s + 2.0f ) * s - 0.5f; // -1.5f * s * s + 2.0f * s - 0.5f + bvals[1] = ( 4.5f * s - 5.0f ) * s; // 4.5f * s * s - 5.0f * s + bvals[2] = ( -4.5 * s + 4.0f ) * s + 0.5f; // -4.5 * s * s + 4.0f * s + 0.5f + bvals[3] = 1.5f * s * s - s; // 1.5f * s * s - s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -3.0f * s + 2.0f; + bvals[1] = 9.0f * s - 5.0f; + bvals[2] = -9.0f * s + 4.0f; + bvals[3] = 3.0f * s - 1.0f; +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + The curve becomes the Catmull-Rom spline if the tension, + continuity and bias are all set to zero. + +=============================================================================== +*/ + +template< class type > +class idCurve_KochanekBartelsSpline : public idCurve_Spline { +public: + idCurve_KochanekBartelsSpline( void ); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); tension.RemoveIndex(index); continuity.RemoveIndex(index); bias.RemoveIndex(index); } + virtual void Clear( void ) { this->values.Clear(); this->times.Clear(); tension.Clear(); continuity.Clear(); bias.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList tension; + idList continuity; + idList bias; + + void TangentsForIndex( const int index, type &t0, type &t1 ) const; + + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline( void ) { +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + tension.Insert( 0.0f, i ); + continuity.Insert( 0.0f, i ); + bias.Insert( 0.0f, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + this->tension.Insert( tension, i ); + this->continuity.Insert( continuity, i ); + this->bias.Insert( bias, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentValue( const float time ) const { + int i; + float bvals[4], clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + Basis( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + return v; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisFirstDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisSecondDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_KochanekBartelsSpline::TangentsForIndex +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::TangentsForIndex( const int index, type &t0, type &t1 ) const { + float dt, omt, omc, opc, omb, opb, adj, s0, s1; + type delta; + + delta = idCurve_Spline::ValueForIndex( index + 1 ) - idCurve_Spline::ValueForIndex( index ); + dt = idCurve_Spline::TimeForIndex( index + 1 ) - idCurve_Spline::TimeForIndex( index ); + + omt = 1.0f - tension[index]; + omc = 1.0f - continuity[index]; + opc = 1.0f + continuity[index]; + omb = 1.0f - bias[index]; + opb = 1.0f + bias[index]; + adj = 2.0f * dt / ( idCurve_Spline::TimeForIndex( index + 1 ) - idCurve_Spline::TimeForIndex( index - 1 ) ); + s0 = 0.5f * adj * omt * opc * opb; + s1 = 0.5f * adj * omt * omc * omb; + + // outgoing tangent at first point + t0 = s1 * delta + s0 * ( idCurve_Spline::ValueForIndex( index ) - idCurve_Spline::ValueForIndex( index - 1 ) ); + + omt = 1.0f - tension[index + 1]; + omc = 1.0f - continuity[index + 1]; + opc = 1.0f + continuity[index + 1]; + omb = 1.0f - bias[index + 1]; + opb = 1.0f + bias[index + 1]; + adj = 2.0f * dt / ( idCurve_Spline::TimeForIndex( index + 2 ) - idCurve_Spline::TimeForIndex( index ) ); + s0 = 0.5f * adj * omt * omc * opb; + s1 = 0.5f * adj * omt * opc * omb; + + // incoming tangent at second point + t1 = s1 * ( idCurve_Spline::ValueForIndex( index + 2 ) - idCurve_Spline::ValueForIndex( index + 1 ) ) + s0 * delta; +} + +/* +==================== +idCurve_KochanekBartelsSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( 2.0f * s - 3.0f ) * s ) * s + 1.0f; // 2.0f * s * s * s - 3.0f * s * s + 1.0f + bvals[1] = ( ( -2.0f * s + 3.0f ) * s ) * s; // -2.0f * s * s * s + 3.0f * s * s + bvals[2] = ( ( s - 2.0f ) * s ) * s + s; // s * s * s - 2.0f * s * s + s + bvals[3] = ( ( s - 1.0f ) * s ) * s; // s * s * s - s * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( 6.0f * s - 6.0f ) * s; // 6.0f * s * s - 6.0f * s + bvals[1] = ( -6.0f * s + 6.0f ) * s; // -6.0f * s * s + 6.0f * s + bvals[2] = ( 3.0f * s - 4.0f ) * s + 1.0f; // 3.0f * s * s - 4.0f * s + 1.0f + bvals[3] = ( 3.0f * s - 2.0f ) * s; // 3.0f * s * s - 2.0f * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = 12.0f * s - 6.0f; + bvals[1] = -12.0f * s + 6.0f; + bvals[2] = 6.0f * s - 4.0f; + bvals[3] = 6.0f * s - 2.0f; +} + + +/* +=============================================================================== + + B-Spline base template. Uses recursive definition and is slow. + Use idCurve_UniformCubicBSpline or idCurve_NonUniformBSpline instead. + +=============================================================================== +*/ + +template< class type > +class idCurve_BSpline : public idCurve_Spline { +public: + idCurve_BSpline( void ); + + virtual int GetOrder( void ) const { return order; } + virtual void SetOrder( const int i ) { assert( i > 0 && i < 10 ); order = i; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + int order; + + float Basis( const int index, const int order, const float t ) const; + float BasisFirstDerivative( const int index, const int order, const float t ) const; + float BasisSecondDerivative( const int index, const int order, const float t ) const; +}; + +/* +==================== +idCurve_BSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_BSpline::idCurve_BSpline( void ) { + order = 4; // default to cubic +} + +/* +==================== +idCurve_BSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += Basis( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisFirstDerivative( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisSecondDerivative( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::Basis + + spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::Basis( const int index, const int order, const float t ) const { + if ( order <= 1 ) { + if ( idCurve_Spline::TimeForIndex( index ) < t && t <= idCurve_Spline::TimeForIndex( index + 1 ) ) { + return 1.0f; + } else { + return 0.0f; + } + } else { + float sum = 0.0f; + float d1 = idCurve_Spline::TimeForIndex( index+order-1 ) - idCurve_Spline::TimeForIndex( index ); + if ( d1 != 0.0f ) { + sum += (float) ( t - idCurve_Spline::TimeForIndex( index ) ) * Basis( index, order-1, t ) / d1; + } + + float d2 = idCurve_Spline::TimeForIndex( index+order ) - idCurve_Spline::TimeForIndex( index+1 ); + if ( d2 != 0.0f ) { + sum += (float) ( idCurve_Spline::TimeForIndex( index+order ) - t ) * Basis( index+1, order-1, t ) / d2; + } + return sum; + } +} + +/* +==================== +idCurve_BSpline::BasisFirstDerivative + + first derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisFirstDerivative( const int index, const int order, const float t ) const { + return ( Basis( index, order-1, t ) - Basis( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( idCurve_Spline::TimeForIndex( index + ( order - 1 ) - 2 ) - idCurve_Spline::TimeForIndex( index - 2 ) ); +} + +/* +==================== +idCurve_BSpline::BasisSecondDerivative + + second derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisSecondDerivative( const int index, const int order, const float t ) const { + return ( BasisFirstDerivative( index, order-1, t ) - BasisFirstDerivative( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( idCurve_Spline::TimeForIndex( index + ( order - 1 ) - 2 ) - idCurve_Spline::TimeForIndex( index - 2 ) ); +} + + +/* +=============================================================================== + + Uniform Non-Rational Cubic B-Spline template. + +=============================================================================== +*/ + +template< class type > +class idCurve_UniformCubicBSpline : public idCurve_BSpline { +public: + idCurve_UniformCubicBSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline( void ) { + this->order = 4; // always cubic +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_UniformCubicBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( ( -s + 3.0f ) * s - 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[1] = ( ( ( 3.0f * s - 6.0f ) * s ) * s + 4.0f ) * ( 1.0f / 6.0f ); + bvals[2] = ( ( ( -3.0f * s + 3.0f ) * s + 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[3] = ( s * s * s ) * ( 1.0f / 6.0f ); +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -0.5f * s * s + s - 0.5f; + bvals[1] = 1.5f * s * s - 2.0f * s; + bvals[2] = -1.5f * s * s + s + 0.5f; + bvals[3] = 0.5f * s * s; +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -s + 1.0f; + bvals[1] = 3.0f * s - 2.0f; + bvals[2] = -3.0f * s + 1.0f; + bvals[3] = s; +} + + +/* +=============================================================================== + + Non-Uniform Non-Rational B-Spline (NUBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NonUniformBSpline : public idCurve_BSpline { +public: + idCurve_NonUniformBSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; +// RAVEN BEGIN +// ddynerman: spline welding + virtual bool Weld( idCurve* c ) const; +// RAVEN END + +protected: + void Basis( const int index, const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_NonUniformBSpline::idCurve_NonUniformBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NonUniformBSpline::idCurve_NonUniformBSpline( void ) { +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::Basis( const int index, const int order, const float t, float *bvals ) const { + int r, s, i; + float omega; + + bvals[order-1] = 1.0f; + for ( r = 2; r <= order; r++ ) { + i = index - r + 1; + bvals[order - r] = 0.0f; + for ( s = order - r + 1; s < order; s++ ) { + i++; + omega = (float) ( t - idCurve_Spline::TimeForIndex( i ) ) / ( idCurve_Spline::TimeForIndex( i + r - 1 ) - idCurve_Spline::TimeForIndex( i ) ); + bvals[s - 1] += ( 1.0f - omega ) * bvals[s]; + bvals[s] *= omega; + } + } +} + +/* +==================== +idCurve_NonUniformBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + Basis( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); +} + +/* +==================== +idCurve_NonUniformBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); +} + +// RAVEN BEGIN +// ddynerman: spline welding +/* +==================== +idCurve_NonUniformBSpline::Weld + + Attach two B-Splines together +==================== +*/ +template< class type > +ID_INLINE bool idCurve_NonUniformBSpline::Weld( idCurve* c ) const { + idCurve_NonUniformBSpline* spline = dynamic_cast*>(c); + + if( spline == NULL ) { + return false; + } + + type refLine = this->values[ this->values.Num() - 1 ] - this->values[ this->values.Num() - 2 ]; + float length = (spline->values[ 0 ] - spline->values[ 1 ]).Length(); + + bool valuesChanged = spline->values[ 0 ] != this->values[ this->values.Num() - 1 ]; + spline->values[ 0 ] = this->values[ this->values.Num() - 1 ]; + + type deltaPos = ~refLine * length; + type newValue = spline->values[ 0 ] + deltaPos; + + valuesChanged = (spline->values[ 1 ] != newValue) || valuesChanged; + + spline->values[ 1 ] = newValue; + + return valuesChanged; +} +// RAVEN END + +/* +=============================================================================== + + Non-Uniform Rational B-Spline (NURBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NURBS : public idCurve_NonUniformBSpline { +public: + idCurve_NURBS( void ); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float weight ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); weights.RemoveIndex(index); } + virtual void Clear( void ) { this->values.Clear(); this->times.Clear(); weights.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList weights; + + float WeightForIndex( const int index ) const; +}; + +/* +==================== +idCurve_NURBS::idCurve_NURBS +==================== +*/ +template< class type > +ID_INLINE idCurve_NURBS::idCurve_NURBS( void ) { +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( 1.0f, i ); + return i; +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value, const float weight ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( weight, i ); + return i; +} + +/* +==================== +idCurve_NURBS::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentValue( const float time ) const { + int i, j, k; + float w, b, *bvals, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + w = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + b = bvals[j] * WeightForIndex( k ); + w += b; + v += b * idCurve_Spline::ValueForIndex( k ); + } + return v / w; +} + +/* +==================== +idCurve_NURBS::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, b, d1, *bvals, *d1vals, clampedTime; + type v, vb, vd1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + vb = vd1 = this->values[0] - this->values[0]; + wb = wd1 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + wb += b; + wd1 += d1; + v = idCurve_Spline::ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + } + return ( wb * vd1 - vb * wd1 ) / ( wb * wb ); +} + +/* +==================== +idCurve_NURBS::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, wd2, b, d1, d2, *bvals, *d1vals, *d2vals, clampedTime; + type v, vb, vd1, vd2; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + d2vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + BasisSecondDerivative( i-1, this->order, clampedTime, d2vals ); + vb = vd1 = vd2 = this->values[0] - this->values[0]; + wb = wd1 = wd2 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + d2 = d2vals[j] * w; + wb += b; + wd1 += d1; + wd2 += d2; + v = idCurve_Spline::ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + vd2 += d2 * v; + } + return ( ( wb * wb ) * ( wb * vd2 - vb * wd2 ) - ( wb * vd1 - vb * wd1 ) * 2.0f * wb * wd1 ) / ( wb * wb * wb * wb ); +} + +/* +==================== +idCurve_NURBS::WeightForIndex + + get the weight for the given index +==================== +*/ +template< class type > +ID_INLINE float idCurve_NURBS::WeightForIndex( const int index ) const { + int n = weights.Num()-1; + + if ( index < 0 ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ weights.Num() + index % weights.Num() ]; + } else { + return weights[0] + index * ( weights[1] - weights[0] ); + } + } else if ( index > n ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ index % weights.Num() ]; + } else { + return weights[n] + ( index - n ) * ( weights[n] - weights[n-1] ); + } + } + return weights[index]; +} + +#endif /* !__MATH_CURVE_H__ */ diff --git a/source/idlib/math/Extrapolate.h b/source/idlib/math/Extrapolate.h new file mode 100644 index 0000000..1cca85b --- /dev/null +++ b/source/idlib/math/Extrapolate.h @@ -0,0 +1,214 @@ + +#ifndef __MATH_EXTRAPOLATE_H__ +#define __MATH_EXTRAPOLATE_H__ + +/* +============================================================================================== + + Extrapolation + +============================================================================================== +*/ + +typedef enum { + EXTRAPOLATION_NONE = 0x01, // no extrapolation, covered distance = duration * 0.001 * ( baseSpeed ) + EXTRAPOLATION_LINEAR = 0x02, // linear extrapolation, covered distance = duration * 0.001 * ( baseSpeed + speed ) + EXTRAPOLATION_ACCELLINEAR = 0x04, // linear acceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_DECELLINEAR = 0x08, // linear deceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_ACCELSINE = 0x10, // sinusoidal acceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_DECELSINE = 0x20, // sinusoidal deceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_NOSTOP = 0x40 // do not stop at startTime + duration +} extrapolation_t; + +template< class type > +class idExtrapolate { +public: + idExtrapolate(); + + void Init( const float startTime, const float duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ); + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && time >= startTime + duration ); } + void SetStartTime( float time ) { startTime = time; currentTime = -1; } + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && duration > 0 ) ? startTime + duration : 0; } + float GetDuration( void ) const { return duration; } + void SetStartValue( const type &value ) { startValue = value; currentTime = -1; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetBaseSpeed( void ) const { return baseSpeed; } + const type & GetSpeed( void ) const { return speed; } + extrapolation_t GetExtrapolationType( void ) const { return extrapolationType; } + +private: + extrapolation_t extrapolationType; + float startTime; + float duration; + type startValue; + type baseSpeed; + type speed; + mutable float currentTime; + mutable type currentValue; +}; + +/* +==================== +idExtrapolate::idExtrapolate +==================== +*/ +template< class type > +ID_INLINE idExtrapolate::idExtrapolate() { + extrapolationType = EXTRAPOLATION_NONE; + startTime = duration = 0.0f; + memset( &startValue, 0, sizeof( startValue ) ); + memset( &baseSpeed, 0, sizeof( baseSpeed ) ); + memset( &speed, 0, sizeof( speed ) ); + currentTime = -1; + currentValue = startValue; +} + +/* +==================== +idExtrapolate::Init +==================== +*/ +template< class type > +ID_INLINE void idExtrapolate::Init( const float startTime, const float duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ) { + this->extrapolationType = extrapolationType; + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->baseSpeed = baseSpeed; + this->speed = speed; + currentTime = -1; + currentValue = startValue; +} + +/* +==================== +idExtrapolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentValue( float time ) const { + float deltaTime, s; + + if ( time == currentTime ) { + return currentValue; + } + + currentTime = time; + + if ( time < startTime ) { + return startValue; + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + time = startTime + duration; + } + + switch( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + deltaTime = ( time - startTime ) * 0.001f; + currentValue = startValue + deltaTime * baseSpeed; + break; + } + case EXTRAPOLATION_LINEAR: { + deltaTime = ( time - startTime ) * 0.001f; + currentValue = startValue + deltaTime * ( baseSpeed + speed ); + break; + } + case EXTRAPOLATION_ACCELLINEAR: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( 0.5f * deltaTime * deltaTime ) * ( duration * 0.001f ); + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_DECELLINEAR: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( deltaTime - ( 0.5f * deltaTime * deltaTime ) ) * ( duration * 0.001f ); + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_ACCELSINE: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( 1.0f - idMath::Cos( deltaTime * idMath::HALF_PI ) ) * duration * 0.001f * idMath::SQRT_1OVER2; + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_DECELSINE: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = idMath::Sin( deltaTime * idMath::HALF_PI ) * duration * 0.001f * idMath::SQRT_1OVER2; + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + } + return currentValue; +} + +/* +==================== +idExtrapolate::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentSpeed( float time ) const { + float deltaTime, s; + + if ( time < startTime || !duration ) { + return ( startValue - startValue ); + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + return ( startValue - startValue ); + } + + switch( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + return baseSpeed; + } + case EXTRAPOLATION_LINEAR: { + return baseSpeed + speed; + } + case EXTRAPOLATION_ACCELLINEAR: { + deltaTime = ( time - startTime ) / duration; + s = deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELLINEAR: { + deltaTime = ( time - startTime ) / duration; + s = 1.0f - deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_ACCELSINE: { + deltaTime = ( time - startTime ) / duration; + s = idMath::Sin( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELSINE: { + deltaTime = ( time - startTime ) / duration; + s = idMath::Cos( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + default: { + return baseSpeed; + } + } +} + +#endif /* !__MATH_EXTRAPOLATE_H__ */ diff --git a/source/idlib/math/FFT.cpp b/source/idlib/math/FFT.cpp new file mode 100644 index 0000000..ad530ba --- /dev/null +++ b/source/idlib/math/FFT.cpp @@ -0,0 +1,146 @@ + +#include "../precompiled.h" +#pragma hdrstop + +typedef struct { + double re; + double im; +} cpxDouble_t; + +/* +=================== + + "A New Principle for Fast Fourier Transformation", Charles M. Rader and N.M. Brenner, I.E.E.E. Transactions on Acoustics + "Programs for Digital Signal Processing", published by I.E.E.E. Press, chapter 1, section 1.1, 1979. + +=================== +*/ +// RAVEN BEGIN +// jscott: added stride +void idFFT::FFT1D( cpxFloat_t *data, int N, int ISI, int stride ) +{ +// RAVEN END + int i, j, m, mmax, istep; + cpxFloat_t cfTemp; + cpxDouble_t cdTemp1, cdTemp2; + double theta, dTemp; + const double pi = idMath::PI; + + // first operation puts data in bit-reversed order + j = 0; + for(i = 0; i < N; i++) { + if(i < j) { +// RAVEN BEGIN + cfTemp.re = data[j * stride].re; + cfTemp.im = data[j * stride].im; + + data[j * stride].re = data[i * stride].re; + data[j * stride].im = data[i * stride].im; + + data[i * stride].re = cfTemp.re; + data[i * stride].im = cfTemp.im; +// RAVEN END + } + m = N / 2; + while(j >= m) { + j = j - m; + m = m / 2; + if(m == 0){ + break; + } + } + j = j + m; + } + + // second operation computes the butterflies + mmax = 1; + while (mmax < N) { + istep = 2 * mmax; + theta = pi * ISI / mmax; + dTemp = idMath::Sin(theta / 2.0); + cdTemp2.re = -2.0 * dTemp * dTemp; + cdTemp2.im = idMath::Sin(theta); + cdTemp1.re = 1.0; + cdTemp1.im = 0.0; + for(m = 0; m < mmax; m++) { + for(i = m; i < N; i += istep) { + j = i + mmax; +// RAVEN BEGIN + cfTemp.re = (float)(cdTemp1.re * data[j * stride].re - cdTemp1.im * data[j * stride].im); + cfTemp.im = (float)(cdTemp1.re * data[j * stride].im + cdTemp1.im * data[j * stride].re); + data[j * stride].re = data[i * stride].re - cfTemp.re; + data[j * stride].im = data[i * stride].im - cfTemp.im; + data[i * stride].re += cfTemp.re; + data[i * stride].im += cfTemp.im; +// RAVEN END + } + dTemp = cdTemp1.re; + cdTemp1.re = cdTemp1.re * cdTemp2.re - cdTemp1.im * cdTemp2.im + cdTemp1.re; + cdTemp1.im = cdTemp1.im * cdTemp2.re + dTemp * cdTemp2.im + cdTemp1.im; + } + mmax = istep; + } +} + +// RAVEN BEGIN +// jscott: added 2d Fourier transform - cost 2N +void idFFT::FFT2D( cpxFloat_t *data, int N, int ISI ) +{ + cpxFloat_t *orig; + int i; + + orig = data; + + // 1D horizontal transform + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, 1 ); + data += N; + } + + // 1D vertical transform + data = orig; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, N ); + data++; + } +} + +// jscott: added 3d Fourier transform - cost 3N^2 +void idFFT::FFT3D( cpxFloat_t *data, int N, int ISI ) +{ + cpxFloat_t *orig; + int i, j; + + orig = data; + + // Transform each slice + for( j = 0; j < N; j++ ) + { + // 1D transform + data = orig + j * N * N; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, 1 ); + data += N; + } + + // 1D transform + data = orig + j * N * N; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, N ); + data++; + } + } + + // Transform the volume + data = orig; + for( j = 0; j < N * N; j++ ) + { + FFT1D( data, N, ISI, N * N ); + data++; + } +} +// RAVEN END diff --git a/source/idlib/math/FFT.h b/source/idlib/math/FFT.h new file mode 100644 index 0000000..49d2ed0 --- /dev/null +++ b/source/idlib/math/FFT.h @@ -0,0 +1,29 @@ + +#ifndef __MATH_FFT_H__ +#define __MATH_FFT_H__ + +/* +=============================================================================== + + Fast Fourier Transform + +=============================================================================== +*/ + +// complex number +typedef struct { + float re; + float im; +} cpxFloat_t; + +class idFFT { +public: +// RAVEN BEGIN +// jscott: added stride to 1D, created 2D + static void FFT1D( cpxFloat_t *data, int N, int ISI, int stride = 1 ); + static void FFT2D( cpxFloat_t *data, int N, int ISI ); + static void FFT3D( cpxFloat_t *data, int N, int ISI ); +// RAVEN END +}; + +#endif /* !__MATH_FFT_H__ */ diff --git a/source/idlib/math/Interpolate.h b/source/idlib/math/Interpolate.h new file mode 100644 index 0000000..cb675b7 --- /dev/null +++ b/source/idlib/math/Interpolate.h @@ -0,0 +1,467 @@ + +#ifndef __MATH_INTERPOLATE_H__ +#define __MATH_INTERPOLATE_H__ + +/* +============================================================================================== + + Linear interpolation. + +============================================================================================== +*/ + +template< class type > +class idInterpolate { +public: + idInterpolate(); + virtual ~idInterpolate() { } + + void Init( const float startTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { this->startTime = time; } + void SetDuration( float duration ) { this->duration = duration; } + void SetStartValue( const type &startValue ) { this->startValue = startValue; } + void SetEndValue( const type &endValue ) { this->endValue = endValue; } + +// RAVEN BEGIN +// abahr: made virtual + virtual type GetCurrentValue( float time ) const; + virtual type GetDeltaValue( float startTime, float endTime ) const; +// RAVEN END + bool IsDone( float time ) const { return ( time >= startTime + duration ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + duration; } + float GetDuration( void ) const { return duration; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +// RAVEN BEGIN +// abahr: changed to protected +protected: +// RAVEN END + float startTime; + float duration; + type startValue; + type endValue; + mutable float currentTime; + mutable type currentValue; +}; + +/* +==================== +idInterpolate::idInterpolate +==================== +*/ +template< class type > +ID_INLINE idInterpolate::idInterpolate() { + currentTime = startTime = duration = 0; + memset( ¤tValue, 0, sizeof( currentValue ) ); + startValue = endValue = currentValue; +} + +/* +==================== +idInterpolate::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolate::Init( const float startTime, const float duration, const type &startValue, const type &endValue ) { + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->endValue = endValue; + this->currentTime = startTime - 1; + this->currentValue = startValue; +} + +/* +==================== +idInterpolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolate::GetCurrentValue( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( time != currentTime ) { + currentTime = time; + if ( deltaTime <= 0 ) { + currentValue = startValue; + } else if ( deltaTime >= duration ) { + currentValue = endValue; + } else { + currentValue = startValue + ( endValue - startValue ) * ( (float) deltaTime / duration ); + } + } + return currentValue; +} + +// RAVEN BEGIN +// abahr +/* +==================== +idInterpolate::GetDeltaValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolate::GetDeltaValue( float startTime, float endTime ) const { + return GetCurrentValue(endTime) - GetCurrentValue(startTime); +} + +ID_INLINE float Linear( float frac ) { + return frac; +} + +ID_INLINE float SinusoidalMidPoint( float frac ) { + return idMath::Sin( DEG2RAD(idMath::MidPointLerp(0.0f, 60.0f, 90.0f, frac)) ); +} + +/* +============================================================================================== + + Spherical interpolation. + +============================================================================================== +*/ +typedef float (*TimeManipFunc) ( float ); +class rvSphericalInterpolate : public idInterpolate { +public: + rvSphericalInterpolate(); + virtual idQuat GetCurrentValue( float time ) const; + + void SetTimeFunction( TimeManipFunc func ) { timeFunc = func; } + +protected: + TimeManipFunc timeFunc; +}; + +/* +==================== +rvSphericalInterpolate::rvSphericalInterpolate +==================== +*/ +ID_INLINE rvSphericalInterpolate::rvSphericalInterpolate() : + idInterpolate() { + SetTimeFunction( SinusoidalMidPoint ); +} + +/* +==================== +rvSphericalInterpolate::GetCurrentValue +==================== +*/ +ID_INLINE idQuat rvSphericalInterpolate::GetCurrentValue( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( time != currentTime ) { + currentTime = time; + if( duration == 0.0f ) { + currentValue = endValue; + } else { + currentValue.Slerp( startValue, endValue, timeFunc((float)deltaTime / duration) ); + } + } + return currentValue; +} +// RAVEN END + +/* +============================================================================================== + + Continuous interpolation with linear acceleration and deceleration phase. + The velocity is continuous but the acceleration is not. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelLinear { +public: + idInterpolateAccelDecelLinear(); + + void Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + accelTime + linearTime + decelTime; } + float GetDuration( void ) const { return accelTime + linearTime + decelTime; } + float GetAcceleration( void ) const { return accelTime; } + float GetDeceleration( void ) const { return decelTime; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +private: + float startTime; + float accelTime; + float linearTime; + float decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate( void ); + void SetPhase( float time ) const; +}; + +/* +==================== +idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + endValue = startValue; +} + +/* +==================== +idInterpolateAccelDecelLinear::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ) { + type speed; + + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0.0f ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * 0.5f ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELLINEAR ); + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELLINEAR ); + } +} + +/* +==================== +idInterpolateAccelDecelLinear::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Invalidate( void ) { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelLinear::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::SetPhase( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELLINEAR ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELLINEAR ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * 0.5f ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELLINEAR ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * 0.5f ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELLINEAR ); + } + } +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentValue( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentSpeed( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + + +/* +============================================================================================== + + Continuous interpolation with sinusoidal acceleration and deceleration phase. + Both the velocity and acceleration are continuous. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelSine { +public: + idInterpolateAccelDecelSine(); + + void Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + accelTime + linearTime + decelTime; } + float GetDuration( void ) const { return accelTime + linearTime + decelTime; } + float GetAcceleration( void ) const { return accelTime; } + float GetDeceleration( void ) const { return decelTime; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +private: + float startTime; + float accelTime; + float linearTime; + float decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate( void ); + void SetPhase( float time ) const; +}; + +/* +==================== +idInterpolateAccelDecelSine::idInterpolateAccelDecelSine +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelSine::idInterpolateAccelDecelSine() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + endValue = startValue; +} + +/* +==================== +idInterpolateAccelDecelSine::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ) { + type speed; + + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0.0f ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * idMath::SQRT_1OVER2 ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELSINE ); + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELSINE ); + } +} + +/* +==================== +idInterpolateAccelDecelSine::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Invalidate( void ) { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelSine::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::SetPhase( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELSINE ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELSINE ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * idMath::SQRT_1OVER2 ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELSINE ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * idMath::SQRT_1OVER2 ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELSINE ); + } + } +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentValue( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentSpeed( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + +#endif /* !__MATH_INTERPOLATE_H__ */ diff --git a/source/idlib/math/Lcp.cpp b/source/idlib/math/Lcp.cpp new file mode 100644 index 0000000..b035a8e --- /dev/null +++ b/source/idlib/math/Lcp.cpp @@ -0,0 +1,1617 @@ + +#include "../precompiled.h" +#pragma hdrstop + +static idCVar lcp_showFailures( "lcp_showFailures", "0", CVAR_SYSTEM | CVAR_BOOL, "show LCP solver failures" ); + +const float LCP_BOUND_EPSILON = 1e-5f; +const float LCP_ACCEL_EPSILON = 1e-5f; +const float LCP_DELTA_ACCEL_EPSILON = 1e-9f; +const float LCP_DELTA_FORCE_EPSILON = 1e-9f; + +#define IGNORE_UNSATISFIABLE_VARIABLES + +//=============================================================== +// M +// idLCP_Square MrE +// E +//=============================================================== + +class idLCP_Square : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LU factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of U of the LU factored sub matrix for clamped variables + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + +private: + bool FactorClamped( void ); + void SolveClamped( idVecX &x, const float *b ); + void Swap( int i, int j ); + void AddClamped( int r ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); + void GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const; +}; + +/* +============ +idLCP_Square::FactorClamped +============ +*/ +bool idLCP_Square::FactorClamped( void ) { + int i, j, k; + float s, d; + + for ( i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + + for ( i = 0; i < numClamped; i++ ) { + + s = idMath::Fabs( clamped[i][i] ); + + if ( s == 0.0f ) { + return false; + } + + diagonal[i] = d = 1.0f / clamped[i][i]; + for ( j = i + 1; j < numClamped; j++ ) { + clamped[j][i] *= d; + } + + for ( j = i + 1; j < numClamped; j++ ) { + d = clamped[j][i]; + for ( k = i + 1; k < numClamped; k++ ) { + clamped[j][k] -= d * clamped[i][k]; + } + } + } + + return true; +} + +/* +============ +idLCP_Square::SolveClamped +============ +*/ +void idLCP_Square::SolveClamped( idVecX &x, const float *b ) { + int i, j; + float sum; + + // solve L + for ( i = 0; i < numClamped; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[i][j] * x[j]; + } + x[i] = sum; + } + + // solve U + for ( i = numClamped - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numClamped; j++ ) { + sum -= clamped[i][j] * x[j]; + } + x[i] = sum * diagonal[i]; + } +} + +/* +============ +idLCP_Square::Swap +============ +*/ +void idLCP_Square::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + idSwap( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex ) { + idSwap( boxIndex[i], boxIndex[j] ); + } + idSwap( side[i], side[j] ); + idSwap( permuted[i], permuted[j] ); +} + +/* +============ +idLCP_Square::AddClamped +============ +*/ +void idLCP_Square::AddClamped( int r ) { + int i, j; + float sum; + + assert( r >= numClamped ); + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // add row to L + for ( i = 0; i < numClamped; i++ ) { + sum = rowPtrs[numClamped][i]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[numClamped][j] * clamped[j][i]; + } + clamped[numClamped][i] = sum * diagonal[i]; + } + + // add column to U + for ( i = 0; i <= numClamped; i++ ) { + sum = rowPtrs[i][numClamped]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[i][j] * clamped[j][numClamped]; + } + clamped[i][numClamped] = sum; + } + + diagonal[numClamped] = 1.0f / clamped[numClamped][numClamped]; + + numClamped++; +} + +/* +============ +idLCP_Square::RemoveClamped +============ +*/ +void idLCP_Square::RemoveClamped( int r ) { + int i, j; + float *y0, *y1, *z0, *z1; + double diag, beta0, beta1, p0, p1, q0, q1, d; + + assert( r < numClamped ); + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + y0 = (float *) _alloca16( numClamped * sizeof( float ) ); + z0 = (float *) _alloca16( numClamped * sizeof( float ) ); + y1 = (float *) _alloca16( numClamped * sizeof( float ) ); + z1 = (float *) _alloca16( numClamped * sizeof( float ) ); + + // the row/column need to be subtracted from the factorization + for ( i = 0; i < numClamped; i++ ) { + y0[i] = -rowPtrs[i][r]; + } + + memset( y1, 0, numClamped * sizeof( float ) ); + y1[r] = 1.0f; + + memset( z0, 0, numClamped * sizeof( float ) ); + z0[r] = 1.0f; + + for ( i = 0; i < numClamped; i++ ) { + z1[i] = -rowPtrs[r][i]; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // the swapped last row/column need to be added to the factorization + for ( i = 0; i < numClamped; i++ ) { + y0[i] += rowPtrs[i][r]; + } + + for ( i = 0; i < numClamped; i++ ) { + z1[i] += rowPtrs[r][i]; + } + z1[r] = 0.0f; + + // update the beginning of the to be updated row and column + for ( i = 0; i < r; i++ ) { + p0 = y0[i]; + beta1 = z1[i] * diagonal[i]; + + clamped[i][r] += p0; + for ( j = i+1; j < numClamped; j++ ) { + z1[j] -= beta1 * clamped[i][j]; + } + for ( j = i+1; j < numClamped; j++ ) { + y0[j] -= p0 * clamped[j][i]; + } + clamped[r][i] += beta1; + } + + // update the lower right corner starting at r,r + for ( i = r; i < numClamped; i++ ) { + diag = clamped[i][i]; + + p0 = y0[i]; + p1 = z0[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + return; + } + + beta0 = p1 / diag; + + q0 = y1[i]; + q1 = z1[i]; + diag += q0 * q1; + + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + return; + } + + d = 1.0f / diag; + beta1 = q1 * d; + + clamped[i][i] = diag; + diagonal[i] = d; + + for ( j = i+1; j < numClamped; j++ ) { + + d = clamped[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + clamped[i][j] = d; + } + + for ( j = i+1; j < numClamped; j++ ) { + + d = clamped[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + clamped[j][i] = d; + } + } + return; +} + +/* +============ +idLCP_Square::CalcForceDelta + + modifies this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::CalcForceDelta( int d, float dir ) { + int i; + float *ptr; + + delta_f[d] = dir; + + if ( numClamped == 0 ) { + return; + } + + // get column d of matrix + ptr = (float *) _alloca16( numClamped * sizeof( float ) ); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = rowPtrs[i][d]; + } + + // solve force delta + SolveClamped( delta_f, ptr ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + ptr = delta_f.ToFloatPtr(); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +============ +idLCP_Square::CalcAccelDelta + + modifies this->delta_a and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::CalcAccelDelta( int d ) { + int j; + float dot; + + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + SIMDProcessor->Dot( dot, rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +============ +idLCP_Square::ChangeForce + + modifies this->f and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + SIMDProcessor->MulAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +============ +idLCP_Square::ChangeAccel + + modifies this->a and uses this->delta_a +============ +*/ +ID_INLINE void idLCP_Square::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + SIMDProcessor->MulAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +============ +idLCP_Square::GetMaxStep +============ +*/ +void idLCP_Square::GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const { + int i; + float s; + + // default to a full step for the current variable + if ( idMath::Fabs( delta_a[d] ) > LCP_DELTA_ACCEL_EPSILON ) { + maxStep = -a[d] / delta_a[d]; + } else { + maxStep = 0.0f; + } + limit = d; + limitSide = 0; + + // test the current variable + if ( dir < 0.0f ) { + if ( lo[d] != -idMath::INFINITY ) { + s = ( lo[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = -1; + } + } + } else { + if ( hi[d] != idMath::INFINITY ) { + s = ( hi[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = 1; + } + } + } + + // test the clamped bounded variables + for ( i = numUnbounded; i < numClamped; i++ ) { + if ( delta_f[i] < -LCP_DELTA_FORCE_EPSILON ) { + // if there is a low boundary + if ( lo[i] != -idMath::INFINITY ) { + s = ( lo[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = -1; + } + } + } else if ( delta_f[i] > LCP_DELTA_FORCE_EPSILON ) { + // if there is a high boundary + if ( hi[i] != idMath::INFINITY ) { + s = ( hi[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 1; + } + } + } + } + + // test the not clamped bounded variables + for ( i = numClamped; i < d; i++ ) { + if ( side[i] == -1 ) { + if ( delta_a[i] >= -LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else if ( side[i] == 1 ) { + if ( delta_a[i] <= LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else { + continue; + } + // ignore variables for which the force is not allowed to take any substantial value + if ( lo[i] >= -LCP_BOUND_EPSILON && hi[i] <= LCP_BOUND_EPSILON ) { + continue; + } + s = -a[i] / delta_a[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 0; + } + } +} + +/* +============ +idLCP_Square::Solve +============ +*/ +bool idLCP_Square::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + int i, j, n, limit, limitSide, boxStartIndex; + float dir, maxStep, dot, s; + char *failed; + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast(o_m[0]) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + boxStartIndex = m.GetNumRows(); + if ( boxIndex ) { + for ( i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 && ( lo[i] != -idMath::INFINITY || hi[i] != idMath::INFINITY ) ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetData( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA( m.GetNumRows() * m.GetNumColumns() ) ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::common->Printf( "idLCP_Square::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + int numIgnored = 0; +#endif + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + failed = NULL; + for ( i = numUnbounded; i < m.GetNumRows(); i++ ) { + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( j = boxStartIndex; j < m.GetNumRows(); j++ ) { + s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + SIMDProcessor->Dot( dot, rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i ); + continue; + } + + // drive the current variable into a valid region + for ( n = 0; n < maxIterations; n++ ) { + + // direction to move + if ( a[i] <= 0.0f ) { + dir = 1.0f; + } else { + dir = -1.0f; + } + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + // maximum step we can take + GetMaxStep( i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed = va( "invalid step size %.4f", maxStep ); +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + switch( limitSide ) { + case 0: { + a[limit] = 0.0f; + AddClamped( limit ); + break; + } + case -1: { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + case 1: { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed = va( "max iterations %d", maxIterations ); + break; + } + + if ( failed ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Square::Solve: %s (%d of %d bounded variables ignored)\n", failed, m.GetNumRows() - i, m.GetNumRows() - numUnbounded ); + } + for ( j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + } + +#if defined(_DEBUG) && 0 + if ( !failed ) { + // test whether or not the solution satisfies the complementarity conditions + for ( i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + // unpermute original matrix + for ( i = 0; i < m.GetNumRows(); i++ ) { + for ( j = 0; j < m.GetNumRows(); j++ ) { + if ( permuted[j] == i ) { + break; + } + } + if ( i != j ) { + m.SwapColumns( i, j ); + idSwap( permuted[i], permuted[j] ); + } + } + + return true; +} + + +//=============================================================== +// M +// idLCP_Symmetric MrE +// E +//=============================================================== + +class idLCP_Symmetric : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LDLt factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of LDLt factored sub matrix for clamped variables + idVecX solveCache1; // intermediate result cached in SolveClamped + idVecX solveCache2; // " + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + int clampedChangeStart; // lowest row/column changed in the clamped matrix during an iteration + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + +private: + bool FactorClamped( void ); + void SolveClamped( idVecX &x, const float *b ); + void Swap( int i, int j ); + void AddClamped( int r, bool useSolveCache ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); + void GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const; +}; + +/* +============ +idLCP_Symmetric::FactorClamped +============ +*/ +bool idLCP_Symmetric::FactorClamped( void ) { + + clampedChangeStart = 0; + + for ( int i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + return SIMDProcessor->MatX_LDLTFactor( clamped, diagonal, numClamped ); +} + +/* +============ +idLCP_Symmetric::SolveClamped +============ +*/ +void idLCP_Symmetric::SolveClamped( idVecX &x, const float *b ) { + + // solve L + SIMDProcessor->MatX_LowerTriangularSolve( clamped, solveCache1.ToFloatPtr(), b, numClamped, clampedChangeStart ); + + // solve D + SIMDProcessor->Mul( solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), diagonal.ToFloatPtr(), numClamped ); + + // solve Lt + SIMDProcessor->MatX_LowerTriangularSolveTranspose( clamped, x.ToFloatPtr(), solveCache2.ToFloatPtr(), numClamped ); + + clampedChangeStart = numClamped; +} + +/* +============ +idLCP_Symmetric::Swap +============ +*/ +void idLCP_Symmetric::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + idSwap( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex ) { + idSwap( boxIndex[i], boxIndex[j] ); + } + idSwap( side[i], side[j] ); + idSwap( permuted[i], permuted[j] ); +} + +/* +============ +idLCP_Symmetric::AddClamped +============ +*/ +void idLCP_Symmetric::AddClamped( int r, bool useSolveCache ) { + float d, dot; + + assert( r >= numClamped ); + + if ( numClamped < clampedChangeStart ) { + clampedChangeStart = numClamped; + } + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // solve for v in L * v = rowPtr[numClamped] + if ( useSolveCache ) { + + // the lower triangular solve was cached in SolveClamped called by CalcForceDelta + memcpy( clamped[numClamped], solveCache2.ToFloatPtr(), numClamped * sizeof( float ) ); + // calculate row dot product + SIMDProcessor->Dot( dot, solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), numClamped ); + + } else { + + float *v = (float *) _alloca16( numClamped * sizeof( float ) ); + + SIMDProcessor->MatX_LowerTriangularSolve( clamped, v, rowPtrs[numClamped], numClamped ); + // add bottom row to L + SIMDProcessor->Mul( clamped[numClamped], v, diagonal.ToFloatPtr(), numClamped ); + // calculate row dot product + SIMDProcessor->Dot( dot, clamped[numClamped], v, numClamped ); + } + + // update diagonal[numClamped] + d = rowPtrs[numClamped][numClamped] - dot; + + if ( d == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::AddClamped: updating factorization failed\n" ); + numClamped++; + return; + } + + clamped[numClamped][numClamped] = d; + diagonal[numClamped] = 1.0f / d; + + numClamped++; +} + +/* +============ +idLCP_Symmetric::RemoveClamped +============ +*/ +void idLCP_Symmetric::RemoveClamped( int r ) { + int i, j, n; + float *addSub, *original, *v, *ptr, *v1, *v2, dot; + double sum, diag, newDiag, invNewDiag, p1, p2, alpha1, alpha2, beta1, beta2; + + assert( r < numClamped ); + + if ( r < clampedChangeStart ) { + clampedChangeStart = r; + } + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // update the factored matrix + addSub = (float *) _alloca16( numClamped * sizeof( float ) ); + + if ( r == 0 ) { + + if ( numClamped == 1 ) { + diag = rowPtrs[0][0]; + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + clamped[0][0] = diag; + diagonal[0] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + original = rowPtrs[numClamped]; + ptr = rowPtrs[r]; + addSub[0] = ptr[0] - original[numClamped]; + for ( i = 1; i < numClamped; i++ ) { + addSub[i] = ptr[i] - original[i]; + } + + } else { + + v = (float *) _alloca16( numClamped * sizeof( float ) ); + + // solve for v in L * v = rowPtr[r] + SIMDProcessor->MatX_LowerTriangularSolve( clamped, v, rowPtrs[r], r ); + + // update removed row + SIMDProcessor->Mul( clamped[r], v, diagonal.ToFloatPtr(), r ); + + // if the last row/column of the matrix is updated + if ( r == numClamped - 1 ) { + // only calculate new diagonal + SIMDProcessor->Dot( dot, clamped[r], v, r ); + diag = rowPtrs[r][r] - dot; + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + clamped[r][r] = diag; + diagonal[r] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = 0; i < r; i++ ) { + v[i] = clamped[r][i] * clamped[i][i]; + } + for ( i = r; i < numClamped; i++ ) { + if ( i == r ) { + sum = clamped[r][r]; + } else { + sum = clamped[r][r] * clamped[i][r]; + } + ptr = clamped[i]; + for ( j = 0; j < r; j++ ) { + sum += ptr[j] * v[j]; + } + addSub[i] = rowPtrs[r][i] - sum; + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + + v1 = (float *) _alloca16( numClamped * sizeof( float ) ); + v2 = (float *) _alloca16( numClamped * sizeof( float ) ); + + diag = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * diag; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * diag; + for ( i = r+1; i < numClamped; i++ ) { + v1[i] = v2[i] = addSub[i] * diag; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + n = clamped.GetNumColumns(); + for ( i = r; i < numClamped; i++ ) { + + diag = clamped[i][i]; + p1 = v1[i]; + newDiag = diag + alpha1 * p1 * p1; + + if ( newDiag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + + alpha1 /= newDiag; + beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( newDiag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + + clamped[i][i] = newDiag; + diagonal[i] = invNewDiag = 1.0f / newDiag; + + alpha2 *= invNewDiag; + beta2 = p2 * alpha2; + alpha2 *= diag; + + // update column below diagonal (i,i) + ptr = clamped.ToFloatPtr() + i; + + for ( j = i+1; j < numClamped - 1; j += 2 ) { + + float sum0 = ptr[(j+0)*n]; + float sum1 = ptr[(j+1)*n]; + + v1[j+0] -= p1 * sum0; + v1[j+1] -= p1 * sum1; + + sum0 += beta1 * v1[j+0]; + sum1 += beta1 * v1[j+1]; + + v2[j+0] -= p2 * sum0; + v2[j+1] -= p2 * sum1; + + sum0 += beta2 * v2[j+0]; + sum1 += beta2 * v2[j+1]; + + ptr[(j+0)*n] = sum0; + ptr[(j+1)*n] = sum1; + } + + for ( ; j < numClamped; j++ ) { + + sum = ptr[j*n]; + + v1[j] -= p1 * sum; + sum += beta1 * v1[j]; + + v2[j] -= p2 * sum; + sum += beta2 * v2[j]; + + ptr[j*n] = sum; + } + } +} + +/* +============ +idLCP_Symmetric::CalcForceDelta + + modifies this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::CalcForceDelta( int d, float dir ) { + int i; + float *ptr; + + delta_f[d] = dir; + + if ( numClamped == 0 ) { + return; + } + + // solve force delta + SolveClamped( delta_f, rowPtrs[d] ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + ptr = delta_f.ToFloatPtr(); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +============ +idLCP_Symmetric::CalcAccelDelta + + modifies this->delta_a and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::CalcAccelDelta( int d ) { + int j; + float dot; + + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + SIMDProcessor->Dot( dot, rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +============ +idLCP_Symmetric::ChangeForce + + modifies this->f and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + SIMDProcessor->MulAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +============ +idLCP_Symmetric::ChangeAccel + + modifies this->a and uses this->delta_a +============ +*/ +ID_INLINE void idLCP_Symmetric::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + SIMDProcessor->MulAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +============ +idLCP_Symmetric::GetMaxStep +============ +*/ +void idLCP_Symmetric::GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const { + int i; + float s; + + // default to a full step for the current variable + if ( idMath::Fabs( delta_a[d] ) > LCP_DELTA_ACCEL_EPSILON ) { + maxStep = -a[d] / delta_a[d]; + } else { + maxStep = 0.0f; + } + limit = d; + limitSide = 0; + + // test the current variable + if ( dir < 0.0f ) { + if ( lo[d] != -idMath::INFINITY ) { + s = ( lo[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = -1; + } + } + } else { + if ( hi[d] != idMath::INFINITY ) { + s = ( hi[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = 1; + } + } + } + + // test the clamped bounded variables + for ( i = numUnbounded; i < numClamped; i++ ) { + if ( delta_f[i] < -LCP_DELTA_FORCE_EPSILON ) { + // if there is a low boundary + if ( lo[i] != -idMath::INFINITY ) { + s = ( lo[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = -1; + } + } + } else if ( delta_f[i] > LCP_DELTA_FORCE_EPSILON ) { + // if there is a high boundary + if ( hi[i] != idMath::INFINITY ) { + s = ( hi[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 1; + } + } + } + } + + // test the not clamped bounded variables + for ( i = numClamped; i < d; i++ ) { + if ( side[i] == -1 ) { + if ( delta_a[i] >= -LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else if ( side[i] == 1 ) { + if ( delta_a[i] <= LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else { + continue; + } + // ignore variables for which the force is not allowed to take any substantial value + if ( lo[i] >= -LCP_BOUND_EPSILON && hi[i] <= LCP_BOUND_EPSILON ) { + continue; + } + s = -a[i] / delta_a[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 0; + } + } +} + +/* +============ +idLCP_Symmetric::Solve +============ +*/ +bool idLCP_Symmetric::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + int i, j, n, limit, limitSide, boxStartIndex; + float dir, maxStep, dot, s; + char *failed; + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast(o_m[0]) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + boxStartIndex = m.GetNumRows(); + if ( boxIndex ) { + for ( i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 && ( lo[i] != -idMath::INFINITY || hi[i] != idMath::INFINITY ) ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetData( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA( m.GetNumRows() * m.GetNumColumns() ) ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache1.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache2.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + int numIgnored = 0; +#endif + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + failed = NULL; + for ( i = numUnbounded; i < m.GetNumRows(); i++ ) { + + clampedChangeStart = 0; + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( j = boxStartIndex; j < m.GetNumRows(); j++ ) { + s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + SIMDProcessor->Dot( dot, rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i, false ); + continue; + } + + // drive the current variable into a valid region + for ( n = 0; n < maxIterations; n++ ) { + + // direction to move + if ( a[i] <= 0.0f ) { + dir = 1.0f; + } else { + dir = -1.0f; + } + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + // maximum step we can take + GetMaxStep( i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed = va( "invalid step size %.4f", maxStep ); +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + switch( limitSide ) { + case 0: { + a[limit] = 0.0f; + AddClamped( limit, ( limit == i ) ); + break; + } + case -1: { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + case 1: { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed = va( "max iterations %d", maxIterations ); + break; + } + + if ( failed ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %s (%d of %d bounded variables ignored)\n", failed, m.GetNumRows() - i, m.GetNumRows() - numUnbounded ); + } + for ( j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + } + +#if defined(_DEBUG) && 0 + if ( !failed ) { + // test whether or not the solution satisfies the complementarity conditions + for ( i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + // unpermute original matrix + for ( i = 0; i < m.GetNumRows(); i++ ) { + for ( j = 0; j < m.GetNumRows(); j++ ) { + if ( permuted[j] == i ) { + break; + } + } + if ( i != j ) { + m.SwapColumns( i, j ); + idSwap( permuted[i], permuted[j] ); + } + } + + return true; +} + + +//=============================================================== +// +// idLCP +// +//=============================================================== + +/* +============ +idLCP::AllocSquare +============ +*/ +idLCP *idLCP::AllocSquare( void ) { + idLCP *lcp = new idLCP_Square; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +============ +idLCP::AllocSymmetric +============ +*/ +idLCP *idLCP::AllocSymmetric( void ) { + idLCP *lcp = new idLCP_Symmetric; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +============ +idLCP::~idLCP +============ +*/ +idLCP::~idLCP( void ) { +} + +/* +============ +idLCP::SetMaxIterations +============ +*/ +void idLCP::SetMaxIterations( int max ) { + maxIterations = max; +} + +/* +============ +idLCP::GetMaxIterations +============ +*/ +int idLCP::GetMaxIterations( void ) { + return maxIterations; +} diff --git a/source/idlib/math/Lcp.h b/source/idlib/math/Lcp.h new file mode 100644 index 0000000..896a034 --- /dev/null +++ b/source/idlib/math/Lcp.h @@ -0,0 +1,50 @@ + +#ifndef __MATH_LCP_H__ +#define __MATH_LCP_H__ + +/* +=============================================================================== + + Box Constrained Mixed Linear Complementarity Problem solver + + A is a matrix of dimension n*n and x, b, lo, hi are vectors of dimension n + + Solve: Ax = b + t, where t is a vector of dimension n, with + complementarity condition: (x[i] - lo[i]) * (x[i] - hi[i]) * t[i] = 0 + such that for each 0 <= i < n one of the following holds: + + 1. lo[i] < x[i] < hi[i], t[i] == 0 + 2. x[i] == lo[i], t[i] >= 0 + 3. x[i] == hi[i], t[i] <= 0 + + Partly bounded or unbounded variables can have lo[i] and/or hi[i] + set to negative/positive idMath::INFITITY respectively. + + If boxIndex != NULL and boxIndex[i] != -1 then + + lo[i] = - fabs( lo[i] * x[boxIndex[i]] ) + hi[i] = fabs( hi[i] * x[boxIndex[i]] ) + boxIndex[boxIndex[i]] must be -1 + + Before calculating any of the bounded x[i] with boxIndex[i] != -1 the + solver calculates all unbounded x[i] and all x[i] with boxIndex[i] == -1. + +=============================================================================== +*/ + +class idLCP { +public: + static idLCP * AllocSquare( void ); // A must be a square matrix + static idLCP * AllocSymmetric( void ); // A must be a symmetric matrix + + virtual ~idLCP( void ); + + virtual bool Solve( const idMatX &A, idVecX &x, const idVecX &b, const idVecX &lo, const idVecX &hi, const int *boxIndex = NULL ) = 0; + virtual void SetMaxIterations( int max ); + virtual int GetMaxIterations( void ); + +protected: + int maxIterations; +}; + +#endif /* !__MATH_LCP_H__ */ diff --git a/source/idlib/math/Mat3x4.h b/source/idlib/math/Mat3x4.h new file mode 100644 index 0000000..06bf98e --- /dev/null +++ b/source/idlib/math/Mat3x4.h @@ -0,0 +1,286 @@ + +#ifndef __MAT3X4_H__ +#define __MAT3X4_H__ + +/* +=============================================================================== + + row-major 3x4 matrix + + idMat3 m; + idVec3 t; + + m[0][0], m[1][0], m[2][0], t[0] + m[0][1], m[1][1], m[2][1], t[1] + m[0][2], m[1][2], m[2][2], t[2] + +=============================================================================== +*/ + +class idMat3x4 { +public: + + void SetRotation( const idMat3 &m ); + void SetTranslation( const idVec3 &t ); + + bool Compare( const idMat3x4 &a ) const; // exact compare, no epsilon + bool Compare( const idMat3x4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat3x4 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat3x4 &a ) const; // exact compare, no epsilon + + void Identity( void ); + void Invert( void ); + + void LeftMultiply( const idMat3x4 &m ); + void LeftMultiply( const idMat3 &m ); + void RightMultiply( const idMat3x4 &m ); + void RightMultiply( const idMat3 &m ); + + void Transform( idVec3 &result, const idVec3 &v ) const; + void Rotate( idVec3 &result, const idVec3 &v ) const; + + idMat3 ToMat3( void ) const; + idVec3 ToVec3( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float mat[3*4]; +}; + + +ID_INLINE void idMat3x4::SetRotation( const idMat3 &m ) { + // NOTE: idMat3 is transposed because it is column-major + mat[0 * 4 + 0] = m[0][0]; + mat[0 * 4 + 1] = m[1][0]; + mat[0 * 4 + 2] = m[2][0]; + mat[1 * 4 + 0] = m[0][1]; + mat[1 * 4 + 1] = m[1][1]; + mat[1 * 4 + 2] = m[2][1]; + mat[2 * 4 + 0] = m[0][2]; + mat[2 * 4 + 1] = m[1][2]; + mat[2 * 4 + 2] = m[2][2]; +} + +ID_INLINE void idMat3x4::SetTranslation( const idVec3 &t ) { + mat[0 * 4 + 3] = t[0]; + mat[1 * 4 + 3] = t[1]; + mat[2 * 4 + 3] = t[2]; +} + +ID_INLINE bool idMat3x4::Compare( const idMat3x4 &a ) const { + int i; + + for ( i = 0; i < 12; i++ ) { + if ( mat[i] != a.mat[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat3x4::Compare( const idMat3x4 &a, const float epsilon ) const { + int i; + + for ( i = 0; i < 12; i++ ) { + if ( idMath::Fabs( mat[i] - a.mat[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat3x4::operator==( const idMat3x4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat3x4::operator!=( const idMat3x4 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat3x4::Identity( void ) { + mat[0 * 4 + 0] = 1.0f; mat[0 * 4 + 1] = 0.0f; mat[0 * 4 + 2] = 0.0f; mat[0 * 4 + 3] = 0.0f; + mat[0 * 4 + 0] = 0.0f; mat[0 * 4 + 1] = 1.0f; mat[0 * 4 + 2] = 0.0f; mat[0 * 4 + 3] = 0.0f; + mat[0 * 4 + 0] = 0.0f; mat[0 * 4 + 1] = 0.0f; mat[0 * 4 + 2] = 1.0f; mat[0 * 4 + 3] = 0.0f; +} + +ID_INLINE void idMat3x4::Invert( void ) { + float tmp[3]; + + // negate inverse rotated translation part + tmp[0] = mat[0 * 4 + 0] * mat[0 * 4 + 3] + mat[1 * 4 + 0] * mat[1 * 4 + 3] + mat[2 * 4 + 0] * mat[2 * 4 + 3]; + tmp[1] = mat[0 * 4 + 1] * mat[0 * 4 + 3] + mat[1 * 4 + 1] * mat[1 * 4 + 3] + mat[2 * 4 + 1] * mat[2 * 4 + 3]; + tmp[2] = mat[0 * 4 + 2] * mat[0 * 4 + 3] + mat[1 * 4 + 2] * mat[1 * 4 + 3] + mat[2 * 4 + 2] * mat[2 * 4 + 3]; + mat[0 * 4 + 3] = -tmp[0]; + mat[1 * 4 + 3] = -tmp[1]; + mat[2 * 4 + 3] = -tmp[2]; + + // transpose rotation part + tmp[0] = mat[0 * 4 + 1]; + mat[0 * 4 + 1] = mat[1 * 4 + 0]; + mat[1 * 4 + 0] = tmp[0]; + tmp[1] = mat[0 * 4 + 2]; + mat[0 * 4 + 2] = mat[2 * 4 + 0]; + mat[2 * 4 + 0] = tmp[1]; + tmp[2] = mat[1 * 4 + 2]; + mat[1 * 4 + 2] = mat[2 * 4 + 1]; + mat[2 * 4 + 1] = tmp[2]; +} + +ID_INLINE void idMat3x4::LeftMultiply( const idMat3x4 &m ) { + float t0, t1; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 0] + m.mat[0 * 4 + 1] * mat[1 * 4 + 0] + m.mat[0 * 4 + 2] * mat[2 * 4 + 0]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 0] + m.mat[1 * 4 + 1] * mat[1 * 4 + 0] + m.mat[1 * 4 + 2] * mat[2 * 4 + 0]; + mat[2 * 4 + 0] = m.mat[2 * 4 + 0] * mat[0 * 4 + 0] + m.mat[2 * 4 + 1] * mat[1 * 4 + 0] + m.mat[2 * 4 + 2] * mat[2 * 4 + 0]; + + mat[1 * 4 + 0] = t1; + mat[0 * 4 + 0] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 1] + m.mat[0 * 4 + 1] * mat[1 * 4 + 1] + m.mat[0 * 4 + 2] * mat[2 * 4 + 1]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 1] + m.mat[1 * 4 + 1] * mat[1 * 4 + 1] + m.mat[1 * 4 + 2] * mat[2 * 4 + 1]; + mat[2 * 4 + 1] = m.mat[2 * 4 + 0] * mat[0 * 4 + 1] + m.mat[2 * 4 + 1] * mat[1 * 4 + 1] + m.mat[2 * 4 + 2] * mat[2 * 4 + 1]; + + mat[1 * 4 + 1] = t1; + mat[0 * 4 + 1] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 2] + m.mat[0 * 4 + 1] * mat[1 * 4 + 2] + m.mat[0 * 4 + 2] * mat[2 * 4 + 2]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 2] + m.mat[1 * 4 + 1] * mat[1 * 4 + 2] + m.mat[1 * 4 + 2] * mat[2 * 4 + 2]; + mat[2 * 4 + 2] = m.mat[2 * 4 + 0] * mat[0 * 4 + 2] + m.mat[2 * 4 + 1] * mat[1 * 4 + 2] + m.mat[2 * 4 + 2] * mat[2 * 4 + 2]; + + mat[1 * 4 + 2] = t1; + mat[0 * 4 + 2] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 3] + m.mat[0 * 4 + 1] * mat[1 * 4 + 3] + m.mat[0 * 4 + 2] * mat[2 * 4 + 3] + m.mat[0 * 4 + 3]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 3] + m.mat[1 * 4 + 1] * mat[1 * 4 + 3] + m.mat[1 * 4 + 2] * mat[2 * 4 + 3] + m.mat[1 * 4 + 3]; + mat[2 * 4 + 3] = m.mat[2 * 4 + 0] * mat[0 * 4 + 3] + m.mat[2 * 4 + 1] * mat[1 * 4 + 3] + m.mat[2 * 4 + 2] * mat[2 * 4 + 3] + m.mat[2 * 4 + 3]; + + mat[1 * 4 + 3] = t1; + mat[0 * 4 + 3] = t0; +} + +ID_INLINE void idMat3x4::LeftMultiply( const idMat3 &m ) { + float t0, t1; + + // NOTE: idMat3 is column-major + t0 = m[0][0] * mat[0 * 4 + 0] + m[1][0] * mat[1 * 4 + 0] + m[2][0] * mat[2 * 4 + 0]; + t1 = m[0][1] * mat[0 * 4 + 0] + m[1][1] * mat[1 * 4 + 0] + m[2][1] * mat[2 * 4 + 0]; + mat[2 * 4 + 0] = m[0][2] * mat[0 * 4 + 0] + m[1][2] * mat[1 * 4 + 0] + m[2][2] * mat[2 * 4 + 0]; + + mat[1 * 4 + 0] = t1; + mat[0 * 4 + 0] = t0; + + t0 = m[0][0] * mat[0 * 4 + 1] + m[1][0] * mat[1 * 4 + 1] + m[2][0] * mat[2 * 4 + 1]; + t1 = m[0][1] * mat[0 * 4 + 1] + m[1][1] * mat[1 * 4 + 1] + m[2][1] * mat[2 * 4 + 1]; + mat[2 * 4 + 1] = m[0][2] * mat[0 * 4 + 1] + m[1][2] * mat[1 * 4 + 1] + m[2][2] * mat[2 * 4 + 1]; + + mat[1 * 4 + 1] = t1; + mat[0 * 4 + 1] = t0; + + t0 = m[0][0] * mat[0 * 4 + 2] + m[1][0] * mat[1 * 4 + 2] + m[2][0] * mat[2 * 4 + 2]; + t1 = m[0][1] * mat[0 * 4 + 2] + m[1][1] * mat[1 * 4 + 2] + m[2][1] * mat[2 * 4 + 2]; + mat[2 * 4 + 2] = m[0][2] * mat[0 * 4 + 2] + m[1][2] * mat[1 * 4 + 2] + m[2][2] * mat[2 * 4 + 2]; + + mat[1 * 4 + 2] = t1; + mat[0 * 4 + 2] = t0; + + t0 = m[0][0] * mat[0 * 4 + 3] + m[1][0] * mat[1 * 4 + 3] + m[2][0] * mat[2 * 4 + 3]; + t1 = m[0][1] * mat[0 * 4 + 3] + m[1][1] * mat[1 * 4 + 3] + m[2][1] * mat[2 * 4 + 3]; + mat[2 * 4 + 3] = m[0][2] * mat[0 * 4 + 3] + m[1][2] * mat[1 * 4 + 3] + m[2][2] * mat[2 * 4 + 3]; + + mat[1 * 4 + 3] = t1; + mat[0 * 4 + 3] = t0; +} + +ID_INLINE void idMat3x4::RightMultiply( const idMat3x4 &m ) { + float t0, t1, t2; + + t0 = mat[0 * 4 + 0] * m.mat[0 * 4 + 0] + mat[0 * 4 + 1] * m.mat[1 * 4 + 0] + mat[0 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[0 * 4 + 0] * m.mat[0 * 4 + 1] + mat[0 * 4 + 1] * m.mat[1 * 4 + 1] + mat[0 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[0 * 4 + 0] * m.mat[0 * 4 + 2] + mat[0 * 4 + 1] * m.mat[1 * 4 + 2] + mat[0 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[0 * 4 + 3] = mat[0 * 4 + 0] * m.mat[0 * 4 + 3] + mat[0 * 4 + 1] * m.mat[1 * 4 + 3] + mat[0 * 4 + 2] * m.mat[2 * 4 + 3] + mat[0 * 4 + 3]; + + mat[0 * 4 + 0] = t0; + mat[0 * 4 + 1] = t1; + mat[0 * 4 + 2] = t2; + + t0 = mat[1 * 4 + 0] * m.mat[0 * 4 + 0] + mat[1 * 4 + 1] * m.mat[1 * 4 + 0] + mat[1 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[1 * 4 + 0] * m.mat[0 * 4 + 1] + mat[1 * 4 + 1] * m.mat[1 * 4 + 1] + mat[1 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[1 * 4 + 0] * m.mat[0 * 4 + 2] + mat[1 * 4 + 1] * m.mat[1 * 4 + 2] + mat[1 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[1 * 4 + 3] = mat[1 * 4 + 0] * m.mat[0 * 4 + 3] + mat[1 * 4 + 1] * m.mat[1 * 4 + 3] + mat[1 * 4 + 2] * m.mat[2 * 4 + 3] + mat[1 * 4 + 3]; + + mat[1 * 4 + 0] = t0; + mat[1 * 4 + 1] = t1; + mat[1 * 4 + 2] = t2; + + t0 = mat[2 * 4 + 0] * m.mat[0 * 4 + 0] + mat[2 * 4 + 1] * m.mat[1 * 4 + 0] + mat[2 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[2 * 4 + 0] * m.mat[0 * 4 + 1] + mat[2 * 4 + 1] * m.mat[1 * 4 + 1] + mat[2 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[2 * 4 + 0] * m.mat[0 * 4 + 2] + mat[2 * 4 + 1] * m.mat[1 * 4 + 2] + mat[2 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[2 * 4 + 3] = mat[2 * 4 + 0] * m.mat[0 * 4 + 3] + mat[2 * 4 + 1] * m.mat[1 * 4 + 3] + mat[2 * 4 + 2] * m.mat[2 * 4 + 3] + mat[2 * 4 + 3]; + + mat[2 * 4 + 0] = t0; + mat[2 * 4 + 1] = t1; + mat[2 * 4 + 2] = t2; +} + +ID_INLINE void idMat3x4::RightMultiply( const idMat3 &m ) { + float t0, t1, t2; + + // NOTE: idMat3 is column-major + t0 = mat[0 * 4 + 0] * m[0][0] + mat[0 * 4 + 1] * m[0][1] + mat[0 * 4 + 2] * m[0][2]; + t1 = mat[0 * 4 + 0] * m[1][0] + mat[0 * 4 + 1] * m[1][1] + mat[0 * 4 + 2] * m[1][2]; + t2 = mat[0 * 4 + 0] * m[2][0] + mat[0 * 4 + 1] * m[2][1] + mat[0 * 4 + 2] * m[2][2]; + + mat[0 * 4 + 0] = t0; + mat[0 * 4 + 1] = t1; + mat[0 * 4 + 2] = t2; + + t0 = mat[1 * 4 + 0] * m[0][0] + mat[1 * 4 + 1] * m[0][1] + mat[1 * 4 + 2] * m[0][2]; + t1 = mat[1 * 4 + 0] * m[1][0] + mat[1 * 4 + 1] * m[1][1] + mat[1 * 4 + 2] * m[1][2]; + t2 = mat[1 * 4 + 0] * m[2][0] + mat[1 * 4 + 1] * m[2][1] + mat[1 * 4 + 2] * m[2][2]; + + mat[1 * 4 + 0] = t0; + mat[1 * 4 + 1] = t1; + mat[1 * 4 + 2] = t2; + + t0 = mat[2 * 4 + 0] * m[0][0] + mat[2 * 4 + 1] * m[0][1] + mat[2 * 4 + 2] * m[0][2]; + t1 = mat[2 * 4 + 0] * m[1][0] + mat[2 * 4 + 1] * m[1][1] + mat[2 * 4 + 2] * m[1][2]; + t2 = mat[2 * 4 + 0] * m[2][0] + mat[2 * 4 + 1] * m[2][1] + mat[2 * 4 + 2] * m[2][2]; + + mat[2 * 4 + 0] = t0; + mat[2 * 4 + 1] = t1; + mat[2 * 4 + 2] = t2; +} + +ID_INLINE void idMat3x4::Transform( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z + mat[0 * 4 + 3]; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z + mat[1 * 4 + 3]; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z + mat[2 * 4 + 3]; +} + +ID_INLINE void idMat3x4::Rotate( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z; +} + +ID_INLINE idMat3 idMat3x4::ToMat3( void ) const { + return idMat3( mat[0 * 4 + 0], mat[1 * 4 + 0], mat[2 * 4 + 0], + mat[0 * 4 + 1], mat[1 * 4 + 1], mat[2 * 4 + 1], + mat[0 * 4 + 2], mat[1 * 4 + 2], mat[2 * 4 + 2] ); +} + +ID_INLINE idVec3 idMat3x4::ToVec3( void ) const { + return idVec3( mat[0 * 4 + 3], mat[1 * 4 + 3], mat[2 * 4 + 3] ); +} + +ID_INLINE const float *idMat3x4::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idMat3x4::ToFloatPtr( void ) { + return mat; +} + +#endif /* !__MAT3X4_H__ */ diff --git a/source/idlib/math/Math.cpp b/source/idlib/math/Math.cpp new file mode 100644 index 0000000..89ee867 --- /dev/null +++ b/source/idlib/math/Math.cpp @@ -0,0 +1,273 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +const float idMath::PI = 3.14159265358979323846f; +const float idMath::TWO_PI = 2.0f * PI; +const float idMath::HALF_PI = 0.5f * PI; +const float idMath::ONEFOURTH_PI = 0.25f * PI; +const float idMath::E = 2.71828182845904523536f; +const float idMath::SQRT_TWO = 1.41421356237309504880f; +const float idMath::SQRT_THREE = 1.73205080756887729352f; +// RAVEN BEGIN +const float idMath::THREEFOURTHS_PI = 0.75f * PI; +// RAVEN END +const float idMath::SQRT_1OVER2 = 0.70710678118654752440f; +const float idMath::SQRT_1OVER3 = 0.57735026918962576450f; +const float idMath::M_DEG2RAD = PI / 180.0f; +const float idMath::M_RAD2DEG = 180.0f / PI; +const float idMath::M_SEC2MS = 1000.0f; +const float idMath::M_MS2SEC = 0.001f; +const float idMath::INFINITY = 1e30f; +// RAVEN BEGIN +// jscott: renamed to prevent name clash +const float idMath::FLOAT_EPSILON = 1.192092896e-07f; +// ddynerman: added, from limits.h +const int idMath::INT_MIN = (-2147483647 - 1); +const int idMath::INT_MAX = 2147483647; +// RAVEN END + +bool idMath::initialized = false; +#ifdef _FAST_MATH +dword idMath::iSqrt[SQRT_TABLE_SIZE]; // inverse square root lookup table +#endif + +#ifdef ID_WIN_X86_SSE +const float idMath::SSE_FLOAT_ZERO = 0.0f; +const float idMath::SSE_FLOAT_255 = 255.0f; +#endif + +/* +=============== +idMath::Init +=============== +*/ +void idMath::Init( void ) { +#ifdef _FAST_MATH + union _flint fi, fo; + + for ( int i = 0; i < SQRT_TABLE_SIZE; i++ ) { + fi.i = ((EXP_BIAS-1) << EXP_POS) | (i << LOOKUP_POS); + fo.f = (float)( 1.0 / sqrt( fi.f ) ); + iSqrt[i] = ((dword)(((fo.i + (1<<(SEED_POS-2))) >> SEED_POS) & 0xFF))<= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + int maxBits = ( ( ( 1 << ( exponentBits - 1 ) ) - 1 ) << mantissaBits ) | ( ( 1 << mantissaBits ) - 1 ); + int minBits = ( ( ( 1 << exponentBits ) - 2 ) << mantissaBits ) | 1; + + float max = BitsToFloat( maxBits, exponentBits, mantissaBits ); + float min = BitsToFloat( minBits, exponentBits, mantissaBits ); + + if ( f >= 0.0f ) { + if ( f >= max ) { + return maxBits; + } else if ( f <= min ) { + return minBits; + } + } else { + if ( f <= -max ) { + return ( maxBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } else if ( f >= -min ) { + return ( minBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } + } + + exponentBits--; + i = *reinterpret_cast(&f); + sign = ( i >> IEEE_FLT_SIGN_BIT ) & 1; + exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + mantissa = i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ); + value = sign << ( 1 + exponentBits + mantissaBits ); + value |= ( ( INTSIGNBITSET( exponent ) << exponentBits ) | ( abs( exponent ) & ( ( 1 << exponentBits ) - 1 ) ) ) << mantissaBits; + value |= mantissa >> ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + return value; +} + +/* +================ +idMath::BitsToFloat +================ +*/ +float idMath::BitsToFloat( int i, int exponentBits, int mantissaBits ) { + static int exponentSign[2] = { 1, -1 }; + int sign, exponent, mantissa, value; + assert( exponentBits >= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + exponentBits--; + sign = i >> ( 1 + exponentBits + mantissaBits ); + exponent = ( ( i >> mantissaBits ) & ( ( 1 << exponentBits ) - 1 ) ) * exponentSign[( i >> ( exponentBits + mantissaBits ) ) & 1]; + mantissa = ( i & ( ( 1 << mantissaBits ) - 1 ) ) << ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + value = sign << IEEE_FLT_SIGN_BIT | ( exponent + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS | mantissa; + return *reinterpret_cast(&value); +} + +// RAVEN BEGIN +// bdube: added block + +void idMath::ArtesianFromPolar( idVec3 &result, idVec3 view ) +{ + float s1, c1, s2, c2; + + idMath::SinCos( view[1], s1, c1 ); + idMath::SinCos( view[2], s2, c2 ); + + result[0] = c1 * s2 * view[0]; + result[1] = s1 * s2 * view[0]; + result[2] = c2 * view[0]; +} + +void idMath::PolarFromArtesian( idVec3 &view, idVec3 artesian ) +{ + float length; + view[0] = artesian.Length(); + view[1] = idMath::ATan( artesian[1], artesian[0] ); + length = sqrtf( ( artesian[0] * artesian[0] ) + ( artesian[1] * artesian[1] ) ); + view[2] = idMath::ATan( length, artesian[2] ); +} + +// ================================================================================================ +// jscott: fast and reliable random routines +// ================================================================================================ + +unsigned long rvRandom::mSeed; + +float rvRandom::flrand( float min, float max ) +{ + float result; + + mSeed = ( mSeed * 214013L ) + 2531011; + // Note: the shift and divide cannot be combined as this breaks the routine + result = ( float )( mSeed >> 17 ); // 0 - 32767 range + result = ( ( result * ( max - min ) ) * ( 1.0f / 32768.0f ) ) + min; + return( result ); +} + +float rvRandom::flrand() { + return flrand( 0.0f, 1.0f ); +} + +float rvRandom::flrand( const idVec2& v ) { + return flrand( v[0], v[1] ); +} + +int rvRandom::irand( int min, int max ) +{ + int result; + + max++; + mSeed = ( mSeed * 214013L ) + 2531011; + result = mSeed >> 17; + result = ( ( result * ( max - min ) ) >> 15 ) + min; + return( result ); +} + +// Try to get a seed independent of the random number system + +int rvRandom::Init( void ) +{ + mSeed *= ( unsigned long )sys->Milliseconds(); + + return( mSeed ); +} + +// ================================================================================================ +// Barycentric texture coordinate functions +// Get the *SIGNED* area of a triangle required for barycentric +// ================================================================================================ +float idMath::BarycentricTriangleArea( const idVec3 &normal, const idVec3 &a, const idVec3 &b, const idVec3 &c ) +{ + idVec3 v1, v2; + idVec3 cross; + float area; + + v1 = b - a; + v2 = c - a; + cross = v1.Cross( v2 ); + area = 0.5f * DotProduct( cross, normal ); + + return( area ); +} + +void idMath::BarycentricEvaluate( idVec2 &result, const idVec3 &point, const idVec3 &normal, const float area, const idVec3 t[3], const idVec2 tc[3] ) +{ + float b1, b2, b3; + + b1 = idMath::BarycentricTriangleArea( normal, point, t[1], t[2] ) / area; + b2 = idMath::BarycentricTriangleArea( normal, t[0], point, t[2] ) / area; + b3 = idMath::BarycentricTriangleArea( normal, t[0], t[1], point ) / area; + + result[0] = ( b1 * tc[0][0] ) + ( b2 * tc[1][0] ) + ( b3 * tc[2][0] ); + result[1] = ( b1 * tc[0][1] ) + ( b2 * tc[1][1] ) + ( b3 * tc[2][1] ); +} + +// abahr: +float idMath::Lerp( const idVec2& range, float frac ) { + return Lerp( range[0], range[1], frac ); +} + +// abahr: +float idMath::Lerp( float start, float end, float frac ) { + if( frac >= 1.0f ) { + return end; + } + + if( frac <= 0.0f ) { + return start; + } + + return start + (end - start) * frac; +} + +// abahr: +float idMath::MidPointLerp( float start, float mid, float end, float frac ) { + if( frac < 0.5f ) { + return Lerp( start, mid, 2.0f * frac ); + } + + return Lerp( mid, end, 2.0f * (frac - 0.5f) ); +} + +float idMath::dBToScale( float db ) { + + if( db < -60.0f ) { + + return( 0.0f ); + + } else { + + return( powf( 2.0f, db * ( 1.0f / 6.0f ) ) ); + } +} + +float idMath::ScaleToDb( float scale ) { + + if( scale <= 0.0f ) { + + return( -60.0f ); + + } else { + + return( 6.0f * idMath::Log( scale ) / idMath::Log( 2 ) ); + } +} + +// RAVEN END diff --git a/source/idlib/math/Math.h b/source/idlib/math/Math.h new file mode 100644 index 0000000..e71cd10 --- /dev/null +++ b/source/idlib/math/Math.h @@ -0,0 +1,1046 @@ + +#ifndef __MATH_MATH_H__ +#define __MATH_MATH_H__ + +/* +=============================================================================== + + Math + +=============================================================================== +*/ + +#ifdef INFINITY +#undef INFINITY +#endif + +// RAVEN BEGIN +// jscott: renamed to prevent name clash +#ifdef FLOAT_EPSILON +#undef FLOAT_EPSILON +#endif +// ddynerman: name clash prevention +#ifdef INT_MIN +#undef INT_MIN +#endif + +#ifdef INT_MAX +#undef INT_MAX +#endif + +// jscott: uncomment this to use id's sqrt and trig approximations +#if defined( __linux__ ) || defined( MACOS_X ) + // TTimo - enabling for OSes I'm covering + // (14:34:20) mrelusive: in the general case we don't use those functions + // (14:34:28) mrelusive: they are only for specific cases where they are faster + #define _FAST_MATH +#endif +// RAVEN END + +#define DEG2RAD(a) ( (a) * idMath::M_DEG2RAD ) +#define RAD2DEG(a) ( (a) * idMath::M_RAD2DEG ) + +#define SEC2MS(t) ( idMath::FtoiFast( (t) * idMath::M_SEC2MS ) ) +#define MS2SEC(t) ( (t) * idMath::M_MS2SEC ) + +#define ANGLE2SHORT(x) ( idMath::FtoiFast( (x) * 65536.0f / 360.0f ) & 65535 ) +#define SHORT2ANGLE(x) ( (x) * ( 360.0f / 65536.0f ) ) + +#define ANGLE2BYTE(x) ( idMath::FtoiFast( (x) * 256.0f / 360.0f ) & 255 ) +#define BYTE2ANGLE(x) ( (x) * ( 360.0f / 256.0f ) ) + +#define FLOATSIGNBITSET(f) ((*(const unsigned long *)&(f)) >> 31) +#define FLOATSIGNBITNOTSET(f) ((~(*(const unsigned long *)&(f))) >> 31) +#define FLOATNOTZERO(f) ((*(const unsigned long *)&(f)) & ~(1<<31) ) +#define INTSIGNBITSET(i) (((const unsigned long)(i)) >> 31) +#define INTSIGNBITNOTSET(i) ((~((const unsigned long)(i))) >> 31) + +#define FLOAT_IS_NAN(x) (((*(const unsigned long *)&x) & 0x7f800000) == 0x7f800000) +#define FLOAT_IS_INF(x) (((*(const unsigned long *)&x) & 0x7fffffff) == 0x7f800000) +#define FLOAT_IS_IND(x) ((*(const unsigned long *)&x) == 0xffc00000) +#define FLOAT_IS_DENORMAL(x) (((*(const unsigned long *)&x) & 0x7f800000) == 0x00000000 && \ + ((*(const unsigned long *)&x) & 0x007fffff) != 0x00000000 ) + +#define IEEE_FLT_MANTISSA_BITS 23 +#define IEEE_FLT_EXPONENT_BITS 8 +#define IEEE_FLT_EXPONENT_BIAS 127 +#define IEEE_FLT_SIGN_BIT 31 + +#define IEEE_DBL_MANTISSA_BITS 52 +#define IEEE_DBL_EXPONENT_BITS 11 +#define IEEE_DBL_EXPONENT_BIAS 1023 +#define IEEE_DBL_SIGN_BIT 63 + +#define IEEE_DBLE_MANTISSA_BITS 63 +#define IEEE_DBLE_EXPONENT_BITS 15 +#define IEEE_DBLE_EXPONENT_BIAS 0 +#define IEEE_DBLE_SIGN_BIT 79 + +template ID_INLINE T Max( T x, T y ) { return ( x > y ) ? x : y; } +template ID_INLINE T Min( T x, T y ) { return ( x < y ) ? x : y; } +template ID_INLINE int MaxIndex( T x, T y ) { return ( x > y ) ? 0 : 1; } +template ID_INLINE int MinIndex( T x, T y ) { return ( x < y ) ? 0 : 1; } + +template ID_INLINE T Max3( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? x : z ) : ( ( y > z ) ? y : z ); } +template ID_INLINE T Min3( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? x : z ) : ( ( y < z ) ? y : z ); } +template ID_INLINE int Max3Index( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? 0 : 2 ) : ( ( y > z ) ? 1 : 2 ); } +template ID_INLINE int Min3Index( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? 0 : 2 ) : ( ( y < z ) ? 1 : 2 ); } +template ID_INLINE T Sign( T f ) { return ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ); } +// RAVEN BEGIN +// abahr: I know its not correct but return 1 if zero +template ID_INLINE T SignZero( T f ) { return ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 1 ); } +// RAVEN END +template ID_INLINE T Square( T x ) { return x * x; } +template ID_INLINE T Cube( T x ) { return x * x * x; } + +class idVec2; +class idVec3; + +class idMath { +public: + + static void Init( void ); + + static float RSqrt( float x ); // reciprocal square root, returns huge number when x == 0.0 + +#ifdef _FAST_MATH + static float InvSqrt( float x ); // inverse square root with 32 bits precision, returns huge number when x == 0.0 + static float InvSqrt16( float x ); // inverse square root with 16 bits precision, returns huge number when x == 0.0 + static double InvSqrt64( float x ); // inverse square root with 64 bits precision, returns huge number when x == 0.0 + + static float Sqrt( float x ); // square root with 32 bits precision + static float Sqrt16( float x ); // square root with 16 bits precision + static double Sqrt64( float x ); // square root with 64 bits precision + + static float Sin( float a ); // sine with 32 bits precision + static float Sin16( float a ); // sine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Sin64( float a ); // sine with 64 bits precision + + static float Cos( float a ); // cosine with 32 bits precision + static float Cos16( float a ); // cosine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Cos64( float a ); // cosine with 64 bits precision + + static float Tan( float a ); // tangent with 32 bits precision + static float Tan16( float a ); // tangent with 16 bits precision, maximum absolute error is 1.8897e-08 + static double Tan64( float a ); // tangent with 64 bits precision + + static float ATan( float a ); // arc tangent with 32 bits precision + static float ATan16( float a ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float a ); // arc tangent with 64 bits precision + + static float ATan( float y, float x ); // arc tangent with 32 bits precision + static float ATan16( float y, float x ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float y, float x ); // arc tangent with 64 bits precision +#else + static float InvSqrt( float x ) { assert( x > 0.0f ); return( 1.0f / sqrtf( x ) ); } // inverse square root with 32 bits precision, returns huge number when x == 0.0 + static float InvSqrt16( float x ) { assert( x > 0.0f ); return( 1.0f / sqrt( x ) ); } // inverse square root with 16 bits precision, returns huge number when x == 0.0 + static double InvSqrt64( float x ) { assert( x > 0.0f ); return( 1.0f / sqrt( x ) ); } // inverse square root with 64 bits precision, returns huge number when x == 0.0 + + static float Sqrt( float x ) { assert( x >= 0.0f ); return( sqrtf( x ) ); } // square root with 32 bits precision + static float Sqrt16( float x ) { assert( x >= 0.0f ); return( sqrtf( x ) ); } // square root with 16 bits precision + static double Sqrt64( float x ) { assert( x >= 0.0f ); return( sqrt( x ) ); } // square root with 64 bits precision + + static float Sin( float a ) { return( sinf( a ) ); } // sine with 32 bits precision + static float Sin16( float a ) { return( sinf( a ) ); } // sine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Sin64( float a ) { return( sin( a ) ); } // sine with 64 bits precision + + static float Cos( float a ) { return( cosf( a ) ); } // cosine with 32 bits precision + static float Cos16( float a ) { return( cosf( a ) ); } // cosine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Cos64( float a ) { return( cos( a ) ); } // cosine with 64 bits precision + + static float Tan( float a ) { return( tanf( a ) ); } // tangent with 32 bits precision + static float Tan16( float a ) { return( tanf( a ) ); } // tangent with 16 bits precision, maximum absolute error is 1.8897e-08 + static double Tan64( float a ) { return( tan( a ) ); } // tangent with 64 bits precision + + static float ATan( float a ) { return( atanf( a ) ); } // arc tangent with 32 bits precision + static float ATan16( float a ) { return( atanf( a ) ); } // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float a ) { return( atan( a ) ); } // arc tangent with 64 bits precision + + static float ATan( float y, float x ) { return( atan2f( y, x ) ); } // arc tangent with 32 bits precision + static float ATan16( float y, float x ) { return( atan2f( y, x ) ); } // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float y, float x ) { return( atan2( y, x ) ); } // arc tangent with 64 bits precision +#endif + + static void SinCos( float a, float &s, float &c ); // sine and cosine with 32 bits precision + static void SinCos16( float a, float &s, float &c ); // sine and cosine with 16 bits precision + static void SinCos64( float a, double &s, double &c ); // sine and cosine with 64 bits precision + + static float ASin( float a ); // arc sine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ASin16( float a ); // arc sine with 16 bits precision, maximum absolute error is 6.7626e-05 + static double ASin64( float a ); // arc sine with 64 bits precision + + static float ACos( float a ); // arc cosine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ACos16( float a ); // arc cosine with 16 bits precision, maximum absolute error is 6.7626e-05 + static double ACos64( float a ); // arc cosine with 64 bits precision + + static float Pow( float x, float y ); // x raised to the power y with 32 bits precision + static float Pow16( float x, float y ); // x raised to the power y with 16 bits precision + static double Pow64( float x, float y ); // x raised to the power y with 64 bits precision + + static float Exp( float f ); // e raised to the power f with 32 bits precision + static float Exp16( float f ); // e raised to the power f with 16 bits precision + static double Exp64( float f ); // e raised to the power f with 64 bits precision + + static float Log( float f ); // natural logarithm with 32 bits precision + static float Log16( float f ); // natural logarithm with 16 bits precision + static double Log64( float f ); // natural logarithm with 64 bits precision + + static int IPow( int x, int y ); // integral x raised to the power y + static int ILog2( float f ); // integral base-2 logarithm of the floating point value + static int ILog2( int i ); // integral base-2 logarithm of the integer value + + static int BitsForFloat( float f ); // minumum number of bits required to represent ceil( f ) + static int BitsForInteger( int i ); // minumum number of bits required to represent i + static int MaskForFloatSign( float f );// returns 0x00000000 if x >= 0.0f and returns 0xFFFFFFFF if x <= -0.0f + static int MaskForIntegerSign( int i );// returns 0x00000000 if x >= 0 and returns 0xFFFFFFFF if x < 0 + static int FloorPowerOfTwo( int x ); // round x down to the nearest power of 2 + static int CeilPowerOfTwo( int x ); // round x up to the nearest power of 2 + static bool IsPowerOfTwo( int x ); // returns true if x is a power of 2 + static int BitCount( int x ); // returns the number of 1 bits in x + static int BitReverse( int x ); // returns the bit reverse of x + + static int Abs( int x ); // returns the absolute value of the integer value (for reference only) + static float Fabs( float f ); // returns the absolute value of the floating point value + static float Floor( float f ); // returns the largest integer that is less than or equal to the given value + static float Ceil( float f ); // returns the smallest integer that is greater than or equal to the given value + static float Rint( float f ); // returns the nearest integer + static int Ftoi( float f ); // float to int conversion + static int FtoiFast( float f ); // fast float to int conversion but uses current FPU round mode (default round nearest) + static byte Ftob( float f ); // float to byte conversion, the result is clamped to the range [0-255] + + static signed char ClampChar( int i ); +// RAVEN BEGIN + static byte ClampByte( int i ); +// RAVEN END + static signed short ClampShort( int i ); + static int ClampInt( int min, int max, int value ); + static float ClampFloat( float min, float max, float value ); + + static float AngleNormalize360( float angle ); + static float AngleNormalize180( float angle ); + static float AngleDelta( float angle1, float angle2 ); + + static int FloatToBits( float f, int exponentBits, int mantissaBits ); + static float BitsToFloat( int i, int exponentBits, int mantissaBits ); + + static int FloatHash( const float *array, const int numFloats ); + + static const float PI; // pi + static const float TWO_PI; // pi * 2 + static const float HALF_PI; // pi / 2 + static const float ONEFOURTH_PI; // pi / 4 + static const float E; // e + static const float SQRT_TWO; // sqrt( 2 ) + static const float SQRT_THREE; // sqrt( 3 ) +// RAVEN BEGIN + static const float THREEFOURTHS_PI; // 3 * pi / 4 +// RAVEN END + static const float SQRT_1OVER2; // sqrt( 1 / 2 ) + static const float SQRT_1OVER3; // sqrt( 1 / 3 ) + static const float M_DEG2RAD; // degrees to radians multiplier + static const float M_RAD2DEG; // radians to degrees multiplier + static const float M_SEC2MS; // seconds to milliseconds multiplier + static const float M_MS2SEC; // milliseconds to seconds multiplier + static const float INFINITY; // huge number which should be larger than any valid number used +// RAVEN BEGIN +// jscott: renamed to prevent name clash + static const float FLOAT_EPSILON; // smallest positive number such that 1.0+FLT_EPSILON != 1.0 +// ddynerman: added from limits.h + static const int INT_MIN; + static const int INT_MAX; + +// bdube: moved here from modview + static void ArtesianFromPolar( idVec3 &result, idVec3 view ); + static void PolarFromArtesian( idVec3 &view, idVec3 artesian ); +// jscott: for material type collision + static float BarycentricTriangleArea( const idVec3 &normal, const idVec3 &a, const idVec3 &b, const idVec3 &c ); + static void BarycentricEvaluate( idVec2 &result, const idVec3 &point, const idVec3 &normal, const float area, const idVec3 t[3], const idVec2 tc[3] ); +// abahr + static float Lerp( const idVec2& range, float frac ); + static float Lerp( float start, float end, float frac ); + static float MidPointLerp( float start, float mid, float end, float frac ); +// jscott: for sound system + static float dBToScale( float db ); + static float ScaleToDb( float scale ); +// RAVEN END + + +private: + enum { + LOOKUP_BITS = 8, + EXP_POS = 23, + EXP_BIAS = 127, + LOOKUP_POS = (EXP_POS-LOOKUP_BITS), + SEED_POS = (EXP_POS-8), + SQRT_TABLE_SIZE = (2<( &x ); + i = 0x5f3759df - ( i >> 1 ); + r = *reinterpret_cast( &i ); + r = r * ( 1.5f - r * r * y ); + return r; +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::InvSqrt16( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + return (float) r; +} + +ID_INLINE float idMath::InvSqrt( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + return (float) r; +} + +ID_INLINE double idMath::InvSqrt64( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + return r; +} + +ID_INLINE float idMath::Sqrt16( float x ) { + return x * InvSqrt16( x ); +} + +ID_INLINE float idMath::Sqrt( float x ) { + return x * InvSqrt( x ); +} + +ID_INLINE double idMath::Sqrt64( float x ) { + return x * InvSqrt64( x ); +} + +ID_INLINE float idMath::Sin( float a ) { + return sinf( a ); +} + +ID_INLINE float idMath::Sin16( float a ) { + float s; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a / TWO_PI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + } else { + a = PI - a; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + } +#endif + s = a * a; + return a * ( ( ( ( ( -2.39e-08f * s + 2.7526e-06f ) * s - 1.98409e-04f ) * s + 8.3333315e-03f ) * s - 1.666666664e-01f ) * s + 1.0f ); +} + +ID_INLINE double idMath::Sin64( float a ) { + return sin( a ); +} + +ID_INLINE float idMath::Cos( float a ) { + return cosf( a ); +} + +ID_INLINE float idMath::Cos16( float a ) { + float s, d; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a / TWO_PI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + s = a * a; + return d * ( ( ( ( ( -2.605e-07f * s + 2.47609e-05f ) * s - 1.3888397e-03f ) * s + 4.16666418e-02f ) * s - 4.999999963e-01f ) * s + 1.0f ); +} + +ID_INLINE double idMath::Cos64( float a ) { + return cos( a ); +} +#endif + +ID_INLINE void idMath::SinCos( float a, float &s, float &c ) { +#ifdef ID_WIN_X86_ASM + _asm { + fld a + fsincos + mov ecx, c + mov edx, s + fstp dword ptr [ecx] + fstp dword ptr [edx] + } +#else + s = sinf( a ); + c = cosf( a ); +#endif +} + +ID_INLINE void idMath::SinCos16( float a, float &s, float &c ) { + float t, d; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + t = a * a; + s = a * ( ( ( ( ( -2.39e-08f * t + 2.7526e-06f ) * t - 1.98409e-04f ) * t + 8.3333315e-03f ) * t - 1.666666664e-01f ) * t + 1.0f ); + c = d * ( ( ( ( ( -2.605e-07f * t + 2.47609e-05f ) * t - 1.3888397e-03f ) * t + 4.16666418e-02f ) * t - 4.999999963e-01f ) * t + 1.0f ); +} + +ID_INLINE void idMath::SinCos64( float a, double &s, double &c ) { +#ifdef ID_WIN_X86_ASM + _asm { + fld a + fsincos + mov ecx, c + mov edx, s + fstp qword ptr [ecx] + fstp qword ptr [edx] + } +#else + s = sin( a ); + c = cos( a ); +#endif +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::Tan( float a ) { + return tanf( a ); +} + +ID_INLINE float idMath::Tan16( float a ) { + float s; + bool reciprocal; + + if ( ( a < 0.0f ) || ( a >= PI ) ) { + a -= floorf( a / PI ) * PI; + } +#if 1 + if ( a < HALF_PI ) { + if ( a > ONEFOURTH_PI ) { + a = HALF_PI - a; + reciprocal = true; + } else { + reciprocal = false; + } + } else { + if ( a > HALF_PI + ONEFOURTH_PI ) { + a = a - PI; + reciprocal = false; + } else { + a = HALF_PI - a; + reciprocal = true; + } + } +#else + a = HALF_PI - a; + if ( fabs( a ) >= ONEFOURTH_PI ) { + a = ( ( a < 0.0f ) ? -HALF_PI : HALF_PI ) - a; + reciprocal = false; + } else { + reciprocal = true; + } +#endif + s = a * a; + s = a * ( ( ( ( ( ( 9.5168091e-03f * s + 2.900525e-03f ) * s + 2.45650893e-02f ) * s + 5.33740603e-02f ) * s + 1.333923995e-01f ) * s + 3.333314036e-01f ) * s + 1.0f ); + if ( reciprocal ) { + return 1.0f / s; + } else { + return s; + } +} + +ID_INLINE double idMath::Tan64( float a ) { + return tan( a ); +} +#endif + +ID_INLINE float idMath::ASin( float a ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + if ( a >= 1.0f ) { + return HALF_PI; + } + return asinf( a ); +} + +ID_INLINE float idMath::ASin16( float a ) { + if ( FLOATSIGNBITSET( a ) ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + a = fabs( a ); + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ) - HALF_PI; + } else { + if ( a >= 1.0f ) { + return HALF_PI; + } + return HALF_PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } +} + +ID_INLINE double idMath::ASin64( float a ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + if ( a >= 1.0f ) { + return HALF_PI; + } + return asin( a ); +} + +ID_INLINE float idMath::ACos( float a ) { + if ( a <= -1.0f ) { + return PI; + } + if ( a >= 1.0f ) { + return 0.0f; + } + return acosf( a ); +} + +ID_INLINE float idMath::ACos16( float a ) { + if ( FLOATSIGNBITSET( a ) ) { + if ( a <= -1.0f ) { + return PI; + } + a = fabs( a ); + return PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } else { + if ( a >= 1.0f ) { + return 0.0f; + } + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } +} + +ID_INLINE double idMath::ACos64( float a ) { + if ( a <= -1.0f ) { + return PI; + } + if ( a >= 1.0f ) { + return 0.0f; + } + return acos( a ); +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::ATan( float a ) { + return atanf( a ); +} + +ID_INLINE float idMath::ATan16( float a ) { + float s; + + if ( fabs( a ) > 1.0f ) { + a = 1.0f / a; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( FLOATSIGNBITSET( a ) ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +ID_INLINE double idMath::ATan64( float a ) { + return atan( a ); +} + +ID_INLINE float idMath::ATan( float y, float x ) { + return atan2f( y, x ); +} + +ID_INLINE float idMath::ATan16( float y, float x ) { + float a, s; + + if ( fabs( y ) > fabs( x ) ) { + a = x / y; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( FLOATSIGNBITSET( a ) ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + a = y / x; + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +ID_INLINE double idMath::ATan64( float y, float x ) { + return atan2( y, x ); +} +#endif + +ID_INLINE float idMath::Pow( float x, float y ) { + return powf( x, y ); +} + +ID_INLINE float idMath::Pow16( float x, float y ) { + return Exp16( y * Log16( x ) ); +} + +ID_INLINE double idMath::Pow64( float x, float y ) { + return pow( x, y ); +} + +ID_INLINE float idMath::Exp( float f ) { + return expf( f ); +} + +ID_INLINE float idMath::Exp16( float f ) { + int i, s, e, m, exponent; + float x, x2, y, p, q; + + x = f * 1.44269504088896340f; // multiply with ( 1 / log( 2 ) ) +#if 1 + i = *reinterpret_cast( &x ); + s = ( i >> IEEE_FLT_SIGN_BIT ); + e = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + m = ( i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ) ) | ( 1 << IEEE_FLT_MANTISSA_BITS ); + i = ( ( m >> ( IEEE_FLT_MANTISSA_BITS - e ) ) & ~( e >> 31 ) ) ^ s; +#else + i = (int) x; + if ( x < 0.0f ) { + i--; + } +#endif + exponent = ( i + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS; + y = *reinterpret_cast( &exponent ); + x -= (float) i; + if ( x >= 0.5f ) { + x -= 0.5f; + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + } + x2 = x * x; + p = x * ( 7.2152891511493f + x2 * 0.0576900723731f ); + q = 20.8189237930062f + x2; + x = y * ( q + p ) / ( q - p ); + return x; +} + +ID_INLINE double idMath::Exp64( float f ) { + return exp( f ); +} + +ID_INLINE float idMath::Log( float f ) { + return logf( f ); +} + +ID_INLINE float idMath::Log16( float f ) { + int i, exponent; + float y, y2; + + i = *reinterpret_cast( &f ); + exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + i -= ( exponent + 1 ) << IEEE_FLT_MANTISSA_BITS; // get value in the range [.5, 1> + y = *reinterpret_cast( &i ); + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + y = ( y - 1.0f ) / ( y + 1.0f ); + y2 = y * y; + y = y * ( 2.000000000046727f + y2 * ( 0.666666635059382f + y2 * ( 0.4000059794795f + y2 * ( 0.28525381498f + y2 * 0.2376245609f ) ) ) ); + y += 0.693147180559945f * ( (float)exponent + 0.5f ); + return y; +} + +ID_INLINE double idMath::Log64( float f ) { + return log( f ); +} + +ID_INLINE int idMath::IPow( int x, int y ) { + int r; for( r = x; y > 1; y-- ) { r *= x; } return r; +} + +ID_INLINE int idMath::ILog2( float f ) { + return ( ( ( *reinterpret_cast( &f ) ) >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; +} + +ID_INLINE int idMath::ILog2( int i ) { + return ILog2( (float)i ); +} + +ID_INLINE int idMath::BitsForFloat( float f ) { + return ILog2( f ) + 1; +} + +ID_INLINE int idMath::BitsForInteger( int i ) { + return ILog2( (float)i ) + 1; +} + +ID_INLINE int idMath::MaskForFloatSign( float f ) { + return ( ( *reinterpret_cast( &f ) ) >> 31 ); +} + +ID_INLINE int idMath::MaskForIntegerSign( int i ) { + return ( i >> 31 ); +} + +ID_INLINE int idMath::FloorPowerOfTwo( int x ) { + return CeilPowerOfTwo( x ) >> 1; +} + +ID_INLINE int idMath::CeilPowerOfTwo( int x ) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x; +} + +ID_INLINE bool idMath::IsPowerOfTwo( int x ) { + return ( x & ( x - 1 ) ) == 0 && x > 0; +} + +ID_INLINE int idMath::BitCount( int x ) { + x -= ( ( x >> 1 ) & 0x55555555 ); + x = ( ( ( x >> 2 ) & 0x33333333 ) + ( x & 0x33333333 ) ); + x = ( ( ( x >> 4 ) + x ) & 0x0f0f0f0f ); + x += ( x >> 8 ); + return ( ( x + ( x >> 16 ) ) & 0x0000003f ); +} + +ID_INLINE int idMath::BitReverse( int x ) { + x = ( ( ( x >> 1 ) & 0x55555555 ) | ( ( x & 0x55555555 ) << 1 ) ); + x = ( ( ( x >> 2 ) & 0x33333333 ) | ( ( x & 0x33333333 ) << 2 ) ); + x = ( ( ( x >> 4 ) & 0x0f0f0f0f ) | ( ( x & 0x0f0f0f0f ) << 4 ) ); + x = ( ( ( x >> 8 ) & 0x00ff00ff ) | ( ( x & 0x00ff00ff ) << 8 ) ); + return ( ( x >> 16 ) | ( x << 16 ) ); +} + +ID_INLINE int idMath::Abs( int x ) { + int y = x >> 31; + return ( ( x ^ y ) - y ); +} + +ID_INLINE float idMath::Fabs( float f ) { + int tmp = *reinterpret_cast( &f ); + tmp &= 0x7FFFFFFF; + return *reinterpret_cast( &tmp ); +} + +ID_INLINE float idMath::Floor( float f ) { + return floorf( f ); +} + +ID_INLINE float idMath::Ceil( float f ) { + return ceilf( f ); +} + +ID_INLINE float idMath::Rint( float f ) { + return floorf( f + 0.5f ); +} + +ID_INLINE int idMath::Ftoi( float f ) { +#ifdef ID_WIN_X86_SSE + // If a converted result is larger than the maximum signed doubleword integer, + // the floating-point invalid exception is raised, and if this exception is masked, + // the indefinite integer value (80000000H) is returned. + int i; + __asm cvttss2si eax, f + __asm mov i, eax + return i; +#else + // If a converted result is larger than the maximum signed doubleword integer the result is undefined. + return (int) f; +#endif +} + +ID_INLINE int idMath::FtoiFast( float f ) { +#ifdef ID_WIN_X86_ASM + int i; + __asm fld f + __asm fistp i // use default rouding mode (round nearest) + return i; +#elif 0 // round chop (C/C++ standard) + int i, s, e, m, shift; + i = *reinterpret_cast( &f ); + s = i >> IEEE_FLT_SIGN_BIT; + e = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + m = ( i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ) ) | ( 1 << IEEE_FLT_MANTISSA_BITS ); + shift = e - IEEE_FLT_MANTISSA_BITS; + return ( ( ( ( m >> -shift ) | ( m << shift ) ) & ~( e >> 31 ) ) ^ s ) - s; +#elif defined( __linux__ ) + #ifdef __i386__ + int i; + __asm__ __volatile__ ( + "flds %1\n\t" + "fistpl %0\n\t" + : "=m"(i) : "m"(f)); + return i; + #else + // lrintf is equivalent but only inlines at -O3 + // although that should be more portable + return lrintf( f ); + #endif +#elif defined( MACOS_X ) + return lrintf( f ); +#else + return (int) f; +#endif +} + +ID_INLINE byte idMath::Ftob( float f ) { +#ifdef ID_WIN_X86_SSE + // If a converted result is negative the value (0) is returned and if the + // converted result is larger than the maximum byte the value (255) is returned. + byte b; + __asm movss xmm0, f + __asm maxss xmm0, SSE_FLOAT_ZERO + __asm minss xmm0, SSE_FLOAT_255 + __asm cvttss2si eax, xmm0 + __asm mov b, al + return b; +#else + // If a converted result is clamped to the range [0-255]. + int i; + i = (int) f; + if ( i < 0 ) { + return 0; + } else if ( i > 255 ) { + return 255; + } + return i; +#endif +} + +ID_INLINE signed char idMath::ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return i; +} + +// RAVEN BEGIN +ID_INLINE byte idMath::ClampByte( int i ) +{ + if( i < 0 ) + { + return( 0 ); + } + if( i > 255 ) + { + return( 255 ); + } + return( i ); +} +// RAVEN END + +ID_INLINE signed short idMath::ClampShort( int i ) { + if ( i < -32768 ) { + return -32768; + } + if ( i > 32767 ) { + return 32767; + } + return i; +} + +ID_INLINE int idMath::ClampInt( int min, int max, int value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +ID_INLINE float idMath::ClampFloat( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +ID_INLINE float idMath::AngleNormalize360( float angle ) { + if ( ( angle >= 360.0f ) || ( angle < 0.0f ) ) { + angle -= floor( angle / 360.0f ) * 360.0f; + } + return angle; +} + +ID_INLINE float idMath::AngleNormalize180( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0f ) { + angle -= 360.0f; + } + return angle; +} + +ID_INLINE float idMath::AngleDelta( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + +ID_INLINE int idMath::FloatHash( const float *array, const int numFloats ) { + int i, hash = 0; + const int *ptr; + + ptr = reinterpret_cast( array ); + for ( i = 0; i < numFloats; i++ ) { + hash ^= ptr[i]; + } + return hash; +} + +// RAVEN BEGIN +// jscott: fast and reliable random routines + +// This is the VC libc version of rand() without multiple seeds per thread or 12 levels +// of subroutine calls. +// Both calls have been designed to minimise the inherent number of float <--> int +// conversions and the additional math required to get the desired value. +// eg the typical tint = (rand() * 255) / 32768 +// becomes tint = rvRandom::irand( 0, 255 ) + +class rvRandom { +private: + static unsigned long mSeed; +public: + rvRandom( void ) { mSeed = 0x89abcdef; } + + // for a non seed based init + static int Init( void ); + + // Init the seed to a unique number + static void Init( unsigned long seed ) { mSeed = seed; } + + // Returns a float min <= x < max (exclusive; will get max - 0.00001; but never max) + static float flrand( float min, float max ); + + // Returns a float min <= 0 < 1.0 + static float flrand(); + + static float flrand( const idVec2& v ); + + // Returns an integer min <= x <= max (ie inclusive) + static int irand( int min, int max ); +}; + +// RAVEN END + +#endif /* !__MATH_MATH_H__ */ diff --git a/source/idlib/math/Matrix.cpp b/source/idlib/math/Matrix.cpp new file mode 100644 index 0000000..3e6e311 --- /dev/null +++ b/source/idlib/math/Matrix.cpp @@ -0,0 +1,8228 @@ +#include "../precompiled.h" +#pragma hdrstop + + +//=============================================================== +// +// idMat2 +// +//=============================================================== + +idMat2 mat2_zero( idVec2( 0, 0 ), idVec2( 0, 0 ) ); +idMat2 mat2_identity( idVec2( 1, 0 ), idVec2( 0, 1 ) ); + +/* +============ +idMat2::InverseSelf +============ +*/ +bool idMat2::InverseSelf( void ) { + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +} + +/* +============ +idMat2::InverseFastSelf +============ +*/ +bool idMat2::InverseFastSelf( void ) { +#if 1 + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +#else + // 2*4 = 8 multiplications + // 2 division + float *mat = reinterpret_cast(this); + double d, di; + float s; + + di = mat[0]; + s = di; + mat[0*2+0] = d = 1.0f / di; + mat[0*2+1] *= d; + d = -d; + mat[1*2+0] *= d; + d = mat[1*2+0] * di; + mat[1*2+1] += mat[0*2+1] * d; + di = mat[1*2+1]; + s *= di; + mat[1*2+1] = d = 1.0f / di; + mat[1*2+0] *= d; + d = -d; + mat[0*2+1] *= d; + d = mat[0*2+1] * di; + mat[0*2+0] += mat[1*2+0] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#endif +} + +/* +============= +idMat2::ToString +============= +*/ +const char *idMat2::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat3 +// +//=============================================================== + +idMat3 mat3_zero( idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ) ); +idMat3 mat3_identity( idVec3( 1, 0, 0 ), idVec3( 0, 1, 0 ), idVec3( 0, 0, 1 ) ); + +/* +============ +idMat3::ToAngles +============ +*/ +idAngles idMat3::ToAngles( void ) const { + idAngles angles; + double theta; + double cp; + float sp; + + sp = mat[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0f ) { + sp = 1.0f; + } else if ( sp < -1.0f ) { + sp = -1.0f; + } + + theta = -asin( sp ); + cp = idMath::Cos( theta ); + +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( cp > 8192.0f * idMath::FLOAT_EPSILON ) { +// RAVEN END + angles.pitch = RAD2DEG( theta ); + angles.yaw = RAD2DEG( idMath::ATan( mat[ 0 ][ 1 ], mat[ 0 ][ 0 ] ) ); + angles.roll = RAD2DEG( idMath::ATan( mat[ 1 ][ 2 ], mat[ 2 ][ 2 ] ) ); + } else { + angles.pitch = RAD2DEG( theta ); + angles.yaw = RAD2DEG( -idMath::ATan( mat[ 1 ][ 0 ], mat[ 1 ][ 1 ] ) ); + angles.roll = 0; + } + return angles; +} + +// RAVEN BEGIN + +// idMat3::RotateAbsolute -- rotates around world x, y, or z +void idMat3::RotateAbsolute(int whichAxis, float howManyDegrees) +{ + idMat3 rotation; + +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + rotation.Identity(); + switch(whichAxis) + { + case 0: // x-axis + rotation[1][1] = cosVal; + rotation[1][2] = sinVal; + rotation[2][1] = -sinVal; + rotation[2][2] = cosVal; + break; + case 1: // y-axis + rotation[0][0] = cosVal; + rotation[0][2] = -sinVal; + rotation[2][0] = sinVal; + rotation[2][2] = cosVal; + break; + case 2: // z-axis + rotation[0][0] = cosVal; + rotation[0][1] = sinVal; + rotation[1][0] = -sinVal; + rotation[1][1] = cosVal; + break; + default: + assert(!"idMat3::RotateAbsolute -- valid axis numbers are 0, 1, and 2"); + return; + } + *this *= rotation; +} + +// idMat3::RotateRelative -- rotates around the fwd, left, or up vector of the matrix +void idMat3::RotateRelative(int whichAxis, float howManyDegrees) +{ + idMat3 rotation; + +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + rotation.Identity(); + switch(whichAxis) + { + case 0: // x-axis + rotation[1][1] = cosVal; + rotation[1][2] = sinVal; + rotation[2][1] = -sinVal; + rotation[2][2] = cosVal; + break; + case 1: // y-axis + rotation[0][0] = cosVal; + rotation[0][2] = -sinVal; + rotation[2][0] = sinVal; + rotation[2][2] = cosVal; + break; + case 2: // z-axis + rotation[0][0] = cosVal; + rotation[0][1] = sinVal; + rotation[1][0] = -sinVal; + rotation[1][1] = cosVal; + break; + default: + assert(!"idMat3::RotateRelative -- valid axis numbers are 0, 1, and 2"); + return; + } + *this = rotation * (*this); +} + +// idMat3::RotateArbitrary -- rotates around the given unit vector +void idMat3::RotateArbitrary(const idVec3 &rotAxis, float howManyDegrees) +{ +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + float d = idMath::Sqrt(rotAxis[1]*rotAxis[1] + rotAxis[2]*rotAxis[2]); + + // if the rotation axis turns out to be one of the world axes, just do a RotateAbsolute on it + if (rotAxis.Compare(idVec3(1, 0, 0))) + { + RotateAbsolute(0, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(-1, 0, 0))) + { + RotateAbsolute(0, -howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 1, 0))) + { + RotateAbsolute(1, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, -1, 0))) + { + RotateAbsolute(1, -howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 0, 1))) + { + RotateAbsolute(2, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 0, -1))) + { + RotateAbsolute(2, -howManyDegrees); + return; + } + + + idMat3 rotationX(1, 0, 0, 0, rotAxis[2]/d, rotAxis[1]/d, 0, -rotAxis[1]/d, rotAxis[2]/d); + idMat3 rotationY(d, 0, rotAxis[0], 0, 1, 0, -rotAxis[0], 0, d); + idMat3 rotationZ(cosVal, sinVal, 0, -sinVal, cosVal, 0, 0, 0, 1); + idMat3 rotationXinv = rotationX.Inverse(); + idMat3 rotationYinv = rotationY.Inverse(); + idMat3 tempMat; + + tempMat = rotationYinv * rotationXinv; + tempMat = rotationZ * tempMat; + tempMat = rotationY * tempMat; + tempMat = rotationX * tempMat; + *this = *this * tempMat; +} + +// RAVEN END + +/* +============ +idMat3::ToQuat +============ +*/ +idQuat idMat3::ToQuat( void ) const { + idQuat q; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[3] = s * t; + q[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + q[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + q[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[i] = s * t; + q[3] = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + q[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + q[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + return q; +} + +/* +============ +idMat3::ToCQuat +============ +*/ +idCQuat idMat3::ToCQuat( void ) const { + idQuat q = ToQuat(); + if ( q.w < 0.0f ) { + return idCQuat( -q.x, -q.y, -q.z ); + } + return idCQuat( q.x, q.y, q.z ); +} + +/* +============ +idMat3::ToRotation +============ +*/ +idRotation idMat3::ToRotation( void ) const { + idRotation r; + float trace; + float s; + float t; + int i; + int j; + int k; + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.angle = s * t; + r.vec[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + r.vec[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + r.vec[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.vec[i] = s * t; + r.angle = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + r.vec[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + r.vec[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + r.angle = idMath::ACos( r.angle ); + if ( idMath::Fabs( r.angle ) < 1e-10f ) { + r.vec.Set( 0.0f, 0.0f, 1.0f ); + r.angle = 0.0f; + } else { + //vec *= (1.0f / sin( angle )); + r.vec.Normalize(); + r.vec.FixDegenerateNormal(); + r.angle *= 2.0f * idMath::M_RAD2DEG; + } + + r.origin.Zero(); + r.axis = *this; + r.axisValid = true; + return r; +} + +/* +================= +idMat3::ToAngularVelocity +================= +*/ +idVec3 idMat3::ToAngularVelocity( void ) const { + idRotation rotation = ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============ +idMat3::Determinant +============ +*/ +float idMat3::Determinant( void ) const { + + float det2_12_01 = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + float det2_12_02 = mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]; + float det2_12_12 = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + + return mat[0][0] * det2_12_12 - mat[0][1] * det2_12_02 + mat[0][2] * det2_12_01; +} + +/* +============ +idMat3::InverseSelf +============ +*/ +bool idMat3::InverseSelf( void ) { + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +} + +/* +============ +idMat3::InverseFastSelf +============ +*/ +bool idMat3::InverseFastSelf( void ) { +#if 1 + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +#elif 0 + // 3*10 = 30 multiplications + // 3 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + d = -d; + mat[3] *= d; + mat[6] *= d; + d = mat[3] * di; + mat[4] += mat[1] * d; + mat[5] += mat[2] * d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + di = mat[4]; + s *= di; + mat[4] = d = 1.0f / di; + mat[3] *= d; + mat[5] *= d; + d = -d; + mat[1] *= d; + mat[7] *= d; + d = mat[1] * di; + mat[0] += mat[3] * d; + mat[2] += mat[5] * d; + d = mat[7] * di; + mat[6] += mat[3] * d; + mat[8] += mat[5] * d; + di = mat[8]; + s *= di; + mat[8] = d = 1.0f / di; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[2] *= d; + mat[5] *= d; + d = mat[2] * di; + mat[0] += mat[6] * d; + mat[1] += mat[7] * d; + d = mat[5] * di; + mat[3] += mat[6] * d; + mat[4] += mat[7] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 4*2+4*4 = 24 multiplications + // 2*1 = 2 divisions + idMat2 r0; + float r1[2], r2[2], r3; + float det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 2x2 + det = mat[0*3+0] * mat[1*3+1] - mat[0*3+1] * mat[1*3+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*3+1] * invDet; + r0[0][1] = - mat[0*3+1] * invDet; + r0[1][0] = - mat[1*3+0] * invDet; + r0[1][1] = mat[0*3+0] * invDet; + + // r1 = r0 * m1; // 2x1 = 2x2 * 2x1 + r1[0] = r0[0][0] * mat[0*3+2] + r0[0][1] * mat[1*3+2]; + r1[1] = r0[1][0] * mat[0*3+2] + r0[1][1] * mat[1*3+2]; + + // r2 = m2 * r1; // 1x1 = 1x2 * 2x1 + r2[0] = mat[2*3+0] * r1[0] + mat[2*3+1] * r1[1]; + + // r3 = r2 - m3; // 1x1 = 1x1 - 1x1 + r3 = r2[0] - mat[2*3+2]; + + // r3.InverseSelf(); + if ( idMath::Fabs( r3 ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + r3 = 1.0f / r3; + + // r2 = m2 * r0; // 1x2 = 1x2 * 2x2 + r2[0] = mat[2*3+0] * r0[0][0] + mat[2*3+1] * r0[1][0]; + r2[1] = mat[2*3+0] * r0[0][1] + mat[2*3+1] * r0[1][1]; + + // m2 = r3 * r2; // 1x2 = 1x1 * 1x2 + mat[2*3+0] = r3 * r2[0]; + mat[2*3+1] = r3 * r2[1]; + + // m0 = r0 - r1 * m2; // 2x2 - 2x1 * 1x2 + mat[0*3+0] = r0[0][0] - r1[0] * mat[2*3+0]; + mat[0*3+1] = r0[0][1] - r1[0] * mat[2*3+1]; + mat[1*3+0] = r0[1][0] - r1[1] * mat[2*3+0]; + mat[1*3+1] = r0[1][1] - r1[1] * mat[2*3+1]; + + // m1 = r1 * r3; // 2x1 = 2x1 * 1x1 + mat[0*3+2] = r1[0] * r3; + mat[1*3+2] = r1[1] * r3; + + // m3 = -r3; + mat[2*3+2] = -r3; + + return true; +#endif +} + +/* +============ +idMat3::InertiaTranslate +============ +*/ +idMat3 idMat3::InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + return (*this) + m; +} + +/* +============ +idMat3::InertiaTranslateSelf +============ +*/ +idMat3 &idMat3::InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + (*this) += m; + + return (*this); +} + +/* +============ +idMat3::InertiaRotate +============ +*/ +idMat3 idMat3::InertiaRotate( const idMat3 &rotation ) const { + // NOTE: the rotation matrix is stored column-major + return rotation.Transpose() * (*this) * rotation; +} + +/* +============ +idMat3::InertiaRotateSelf +============ +*/ +idMat3 &idMat3::InertiaRotateSelf( const idMat3 &rotation ) { + // NOTE: the rotation matrix is stored column-major + *this = rotation.Transpose() * (*this) * rotation; + return *this; +} + +/* +============= +idMat3::ToString +============= +*/ +const char *idMat3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat4 +// +//=============================================================== + +idMat4 mat4_zero( idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ) ); +idMat4 mat4_identity( idVec4( 1, 0, 0, 0 ), idVec4( 0, 1, 0, 0 ), idVec4( 0, 0, 1, 0 ), idVec4( 0, 0, 0, 1 ) ); + +/* +============ +idMat4::Transpose +============ +*/ +idMat4 idMat4::Transpose( void ) const { + idMat4 transpose; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = 0; j < 4; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat4::TransposeSelf +============ +*/ +idMat4 &idMat4::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = i + 1; j < 4; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat4::Determinant +============ +*/ +float idMat4::Determinant( void ) const { + + // 2x2 sub-determinants + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + return ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); +} + +/* +============ +idMat4::InverseSelf +============ +*/ +bool idMat4::InverseSelf( void ) { + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +} + +/* +============ +idMat4::InverseFastSelf +============ +*/ +bool idMat4::InverseFastSelf( void ) { +#if 0 + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +#elif 0 + // 4*18 = 72 multiplications + // 4 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + d = -d; + mat[4] *= d; + mat[8] *= d; + mat[12] *= d; + d = mat[4] * di; + mat[5] += mat[1] * d; + mat[6] += mat[2] * d; + mat[7] += mat[3] * d; + d = mat[8] * di; + mat[9] += mat[1] * d; + mat[10] += mat[2] * d; + mat[11] += mat[3] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + di = mat[5]; + s *= di; + mat[5] = d = 1.0f / di; + mat[4] *= d; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[1] *= d; + mat[9] *= d; + mat[13] *= d; + d = mat[1] * di; + mat[0] += mat[4] * d; + mat[2] += mat[6] * d; + mat[3] += mat[7] * d; + d = mat[9] * di; + mat[8] += mat[4] * d; + mat[10] += mat[6] * d; + mat[11] += mat[7] * d; + d = mat[13] * di; + mat[12] += mat[4] * d; + mat[14] += mat[6] * d; + mat[15] += mat[7] * d; + di = mat[10]; + s *= di; + mat[10] = d = 1.0f / di; + mat[8] *= d; + mat[9] *= d; + mat[11] *= d; + d = -d; + mat[2] *= d; + mat[6] *= d; + mat[14] *= d; + d = mat[2] * di; + mat[0] += mat[8] * d; + mat[1] += mat[9] * d; + mat[3] += mat[11] * d; + d = mat[6] * di; + mat[4] += mat[8] * d; + mat[5] += mat[9] * d; + mat[7] += mat[11] * d; + d = mat[14] * di; + mat[12] += mat[8] * d; + mat[13] += mat[9] * d; + mat[15] += mat[11] * d; + di = mat[15]; + s *= di; + mat[15] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[3] *= d; + mat[7] *= d; + mat[11] *= d; + d = mat[3] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[2] += mat[14] * d; + d = mat[7] * di; + mat[4] += mat[12] * d; + mat[5] += mat[13] * d; + mat[6] += mat[14] * d; + d = mat[11] * di; + mat[8] += mat[12] * d; + mat[9] += mat[13] * d; + mat[10] += mat[14] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 6*8+2*6 = 60 multiplications + // 2*1 = 2 divisions + idMat2 r0, r1, r2, r3; + float a, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + det = mat[0*4+0] * mat[1*4+1] - mat[0*4+1] * mat[1*4+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*4+1] * invDet; + r0[0][1] = - mat[0*4+1] * invDet; + r0[1][0] = - mat[1*4+0] * invDet; + r0[1][1] = mat[0*4+0] * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*4+2] + r0[0][1] * mat[1*4+2]; + r1[0][1] = r0[0][0] * mat[0*4+3] + r0[0][1] * mat[1*4+3]; + r1[1][0] = r0[1][0] * mat[0*4+2] + r0[1][1] * mat[1*4+2]; + r1[1][1] = r0[1][0] * mat[0*4+3] + r0[1][1] * mat[1*4+3]; + + // r2 = m2 * r1; + r2[0][0] = mat[2*4+0] * r1[0][0] + mat[2*4+1] * r1[1][0]; + r2[0][1] = mat[2*4+0] * r1[0][1] + mat[2*4+1] * r1[1][1]; + r2[1][0] = mat[3*4+0] * r1[0][0] + mat[3*4+1] * r1[1][0]; + r2[1][1] = mat[3*4+0] * r1[0][1] + mat[3*4+1] * r1[1][1]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[2*4+2]; + r3[0][1] = r2[0][1] - mat[2*4+3]; + r3[1][0] = r2[1][0] - mat[3*4+2]; + r3[1][1] = r2[1][1] - mat[3*4+3]; + + // r3.InverseSelf(); + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = a * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[2*4+0] * r0[0][0] + mat[2*4+1] * r0[1][0]; + r2[0][1] = mat[2*4+0] * r0[0][1] + mat[2*4+1] * r0[1][1]; + r2[1][0] = mat[3*4+0] * r0[0][0] + mat[3*4+1] * r0[1][0]; + r2[1][1] = mat[3*4+0] * r0[0][1] + mat[3*4+1] * r0[1][1]; + + // m2 = r3 * r2; + mat[2*4+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[2*4+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*4+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[3*4+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + + // m0 = r0 - r1 * m2; + mat[0*4+0] = r0[0][0] - r1[0][0] * mat[2*4+0] - r1[0][1] * mat[3*4+0]; + mat[0*4+1] = r0[0][1] - r1[0][0] * mat[2*4+1] - r1[0][1] * mat[3*4+1]; + mat[1*4+0] = r0[1][0] - r1[1][0] * mat[2*4+0] - r1[1][1] * mat[3*4+0]; + mat[1*4+1] = r0[1][1] - r1[1][0] * mat[2*4+1] - r1[1][1] * mat[3*4+1]; + + // m1 = r1 * r3; + mat[0*4+2] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*4+3] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*4+2] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*4+3] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + + // m3 = -r3; + mat[2*4+2] = -r3[0][0]; + mat[2*4+3] = -r3[0][1]; + mat[3*4+2] = -r3[1][0]; + mat[3*4+3] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat4::ToString +============= +*/ +const char *idMat4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat5 +// +//=============================================================== + +idMat5 mat5_zero( idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ) ); +idMat5 mat5_identity( idVec5( 1, 0, 0, 0, 0 ), idVec5( 0, 1, 0, 0, 0 ), idVec5( 0, 0, 1, 0, 0 ), idVec5( 0, 0, 0, 1, 0 ), idVec5( 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat5::Transpose +============ +*/ +idMat5 idMat5::Transpose( void ) const { + idMat5 transpose; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = 0; j < 5; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat5::TransposeSelf +============ +*/ +idMat5 &idMat5::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = i + 1; j < 5; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat5::Determinant +============ +*/ +float idMat5::Determinant( void ) const { + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + return mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; +} + +/* +============ +idMat5::InverseSelf +============ +*/ +bool idMat5::InverseSelf( void ) { + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +} + +/* +============ +idMat5::InverseFastSelf +============ +*/ +bool idMat5::InverseFastSelf( void ) { +#if 0 + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +#elif 0 + // 5*28 = 140 multiplications + // 5 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + d = -d; + mat[5] *= d; + mat[10] *= d; + mat[15] *= d; + mat[20] *= d; + d = mat[5] * di; + mat[6] += mat[1] * d; + mat[7] += mat[2] * d; + mat[8] += mat[3] * d; + mat[9] += mat[4] * d; + d = mat[10] * di; + mat[11] += mat[1] * d; + mat[12] += mat[2] * d; + mat[13] += mat[3] * d; + mat[14] += mat[4] * d; + d = mat[15] * di; + mat[16] += mat[1] * d; + mat[17] += mat[2] * d; + mat[18] += mat[3] * d; + mat[19] += mat[4] * d; + d = mat[20] * di; + mat[21] += mat[1] * d; + mat[22] += mat[2] * d; + mat[23] += mat[3] * d; + mat[24] += mat[4] * d; + di = mat[6]; + s *= di; + mat[6] = d = 1.0f / di; + mat[5] *= d; + mat[7] *= d; + mat[8] *= d; + mat[9] *= d; + d = -d; + mat[1] *= d; + mat[11] *= d; + mat[16] *= d; + mat[21] *= d; + d = mat[1] * di; + mat[0] += mat[5] * d; + mat[2] += mat[7] * d; + mat[3] += mat[8] * d; + mat[4] += mat[9] * d; + d = mat[11] * di; + mat[10] += mat[5] * d; + mat[12] += mat[7] * d; + mat[13] += mat[8] * d; + mat[14] += mat[9] * d; + d = mat[16] * di; + mat[15] += mat[5] * d; + mat[17] += mat[7] * d; + mat[18] += mat[8] * d; + mat[19] += mat[9] * d; + d = mat[21] * di; + mat[20] += mat[5] * d; + mat[22] += mat[7] * d; + mat[23] += mat[8] * d; + mat[24] += mat[9] * d; + di = mat[12]; + s *= di; + mat[12] = d = 1.0f / di; + mat[10] *= d; + mat[11] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[2] *= d; + mat[7] *= d; + mat[17] *= d; + mat[22] *= d; + d = mat[2] * di; + mat[0] += mat[10] * d; + mat[1] += mat[11] * d; + mat[3] += mat[13] * d; + mat[4] += mat[14] * d; + d = mat[7] * di; + mat[5] += mat[10] * d; + mat[6] += mat[11] * d; + mat[8] += mat[13] * d; + mat[9] += mat[14] * d; + d = mat[17] * di; + mat[15] += mat[10] * d; + mat[16] += mat[11] * d; + mat[18] += mat[13] * d; + mat[19] += mat[14] * d; + d = mat[22] * di; + mat[20] += mat[10] * d; + mat[21] += mat[11] * d; + mat[23] += mat[13] * d; + mat[24] += mat[14] * d; + di = mat[18]; + s *= di; + mat[18] = d = 1.0f / di; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + mat[19] *= d; + d = -d; + mat[3] *= d; + mat[8] *= d; + mat[13] *= d; + mat[23] *= d; + d = mat[3] * di; + mat[0] += mat[15] * d; + mat[1] += mat[16] * d; + mat[2] += mat[17] * d; + mat[4] += mat[19] * d; + d = mat[8] * di; + mat[5] += mat[15] * d; + mat[6] += mat[16] * d; + mat[7] += mat[17] * d; + mat[9] += mat[19] * d; + d = mat[13] * di; + mat[10] += mat[15] * d; + mat[11] += mat[16] * d; + mat[12] += mat[17] * d; + mat[14] += mat[19] * d; + d = mat[23] * di; + mat[20] += mat[15] * d; + mat[21] += mat[16] * d; + mat[22] += mat[17] * d; + mat[24] += mat[19] * d; + di = mat[24]; + s *= di; + mat[24] = d = 1.0f / di; + mat[20] *= d; + mat[21] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[4] *= d; + mat[9] *= d; + mat[14] *= d; + mat[19] *= d; + d = mat[4] * di; + mat[0] += mat[20] * d; + mat[1] += mat[21] * d; + mat[2] += mat[22] * d; + mat[3] += mat[23] * d; + d = mat[9] * di; + mat[5] += mat[20] * d; + mat[6] += mat[21] * d; + mat[7] += mat[22] * d; + mat[8] += mat[23] * d; + d = mat[14] * di; + mat[10] += mat[20] * d; + mat[11] += mat[21] * d; + mat[12] += mat[22] * d; + mat[13] += mat[23] * d; + d = mat[19] * di; + mat[15] += mat[20] * d; + mat[16] += mat[21] * d; + mat[17] += mat[22] * d; + mat[18] += mat[23] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 86+30+6 = 122 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 3x3 + c0 = mat[1*5+1] * mat[2*5+2] - mat[1*5+2] * mat[2*5+1]; + c1 = mat[1*5+2] * mat[2*5+0] - mat[1*5+0] * mat[2*5+2]; + c2 = mat[1*5+0] * mat[2*5+1] - mat[1*5+1] * mat[2*5+0]; + + det = mat[0*5+0] * c0 + mat[0*5+1] * c1 + mat[0*5+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*5+2] * mat[2*5+1] - mat[0*5+1] * mat[2*5+2] ) * invDet; + r0[0][2] = ( mat[0*5+1] * mat[1*5+2] - mat[0*5+2] * mat[1*5+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*5+0] * mat[2*5+2] - mat[0*5+2] * mat[2*5+0] ) * invDet; + r0[1][2] = ( mat[0*5+2] * mat[1*5+0] - mat[0*5+0] * mat[1*5+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*5+1] * mat[2*5+0] - mat[0*5+0] * mat[2*5+1] ) * invDet; + r0[2][2] = ( mat[0*5+0] * mat[1*5+1] - mat[0*5+1] * mat[1*5+0] ) * invDet; + + // r1 = r0 * m1; // 3x2 = 3x3 * 3x2 + r1[0][0] = r0[0][0] * mat[0*5+3] + r0[0][1] * mat[1*5+3] + r0[0][2] * mat[2*5+3]; + r1[0][1] = r0[0][0] * mat[0*5+4] + r0[0][1] * mat[1*5+4] + r0[0][2] * mat[2*5+4]; + r1[1][0] = r0[1][0] * mat[0*5+3] + r0[1][1] * mat[1*5+3] + r0[1][2] * mat[2*5+3]; + r1[1][1] = r0[1][0] * mat[0*5+4] + r0[1][1] * mat[1*5+4] + r0[1][2] * mat[2*5+4]; + r1[2][0] = r0[2][0] * mat[0*5+3] + r0[2][1] * mat[1*5+3] + r0[2][2] * mat[2*5+3]; + r1[2][1] = r0[2][0] * mat[0*5+4] + r0[2][1] * mat[1*5+4] + r0[2][2] * mat[2*5+4]; + + // r2 = m2 * r1; // 2x2 = 2x3 * 3x2 + r2[0][0] = mat[3*5+0] * r1[0][0] + mat[3*5+1] * r1[1][0] + mat[3*5+2] * r1[2][0]; + r2[0][1] = mat[3*5+0] * r1[0][1] + mat[3*5+1] * r1[1][1] + mat[3*5+2] * r1[2][1]; + r2[1][0] = mat[4*5+0] * r1[0][0] + mat[4*5+1] * r1[1][0] + mat[4*5+2] * r1[2][0]; + r2[1][1] = mat[4*5+0] * r1[0][1] + mat[4*5+1] * r1[1][1] + mat[4*5+2] * r1[2][1]; + + // r3 = r2 - m3; // 2x2 = 2x2 - 2x2 + r3[0][0] = r2[0][0] - mat[3*5+3]; + r3[0][1] = r2[0][1] - mat[3*5+4]; + r3[1][0] = r2[1][0] - mat[4*5+3]; + r3[1][1] = r2[1][1] - mat[4*5+4]; + + // r3.InverseSelf(); // 2x2 + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + c0 = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = c0 * invDet; + + // r2 = m2 * r0; // 2x3 = 2x3 * 3x3 + r2[0][0] = mat[3*5+0] * r0[0][0] + mat[3*5+1] * r0[1][0] + mat[3*5+2] * r0[2][0]; + r2[0][1] = mat[3*5+0] * r0[0][1] + mat[3*5+1] * r0[1][1] + mat[3*5+2] * r0[2][1]; + r2[0][2] = mat[3*5+0] * r0[0][2] + mat[3*5+1] * r0[1][2] + mat[3*5+2] * r0[2][2]; + r2[1][0] = mat[4*5+0] * r0[0][0] + mat[4*5+1] * r0[1][0] + mat[4*5+2] * r0[2][0]; + r2[1][1] = mat[4*5+0] * r0[0][1] + mat[4*5+1] * r0[1][1] + mat[4*5+2] * r0[2][1]; + r2[1][2] = mat[4*5+0] * r0[0][2] + mat[4*5+1] * r0[1][2] + mat[4*5+2] * r0[2][2]; + + // m2 = r3 * r2; // 2x3 = 2x2 * 2x3 + mat[3*5+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[3*5+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*5+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2]; + mat[4*5+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[4*5+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + mat[4*5+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2]; + + // m0 = r0 - r1 * m2; // 3x3 = 3x3 - 3x2 * 2x3 + mat[0*5+0] = r0[0][0] - r1[0][0] * mat[3*5+0] - r1[0][1] * mat[4*5+0]; + mat[0*5+1] = r0[0][1] - r1[0][0] * mat[3*5+1] - r1[0][1] * mat[4*5+1]; + mat[0*5+2] = r0[0][2] - r1[0][0] * mat[3*5+2] - r1[0][1] * mat[4*5+2]; + mat[1*5+0] = r0[1][0] - r1[1][0] * mat[3*5+0] - r1[1][1] * mat[4*5+0]; + mat[1*5+1] = r0[1][1] - r1[1][0] * mat[3*5+1] - r1[1][1] * mat[4*5+1]; + mat[1*5+2] = r0[1][2] - r1[1][0] * mat[3*5+2] - r1[1][1] * mat[4*5+2]; + mat[2*5+0] = r0[2][0] - r1[2][0] * mat[3*5+0] - r1[2][1] * mat[4*5+0]; + mat[2*5+1] = r0[2][1] - r1[2][0] * mat[3*5+1] - r1[2][1] * mat[4*5+1]; + mat[2*5+2] = r0[2][2] - r1[2][0] * mat[3*5+2] - r1[2][1] * mat[4*5+2]; + + // m1 = r1 * r3; // 3x2 = 3x2 * 2x2 + mat[0*5+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*5+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*5+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*5+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + mat[2*5+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0]; + mat[2*5+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1]; + + // m3 = -r3; // 2x2 = - 2x2 + mat[3*5+3] = -r3[0][0]; + mat[3*5+4] = -r3[0][1]; + mat[4*5+3] = -r3[1][0]; + mat[4*5+4] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat5::ToString +============= +*/ +const char *idMat5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat6 +// +//=============================================================== + +idMat6 mat6_zero( idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ) ); +idMat6 mat6_identity( idVec6( 1, 0, 0, 0, 0, 0 ), idVec6( 0, 1, 0, 0, 0, 0 ), idVec6( 0, 0, 1, 0, 0, 0 ), idVec6( 0, 0, 0, 1, 0, 0 ), idVec6( 0, 0, 0, 0, 1, 0 ), idVec6( 0, 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat6::Transpose +============ +*/ +idMat6 idMat6::Transpose( void ) const { + idMat6 transpose; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = 0; j < 6; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat6::TransposeSelf +============ +*/ +idMat6 &idMat6::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = i + 1; j < 6; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat6::Determinant +============ +*/ +float idMat6::Determinant( void ) const { + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + return mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; +} + +/* +============ +idMat6::InverseSelf +============ +*/ +bool idMat6::InverseSelf( void ) { + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +} + +/* +============ +idMat6::InverseFastSelf +============ +*/ +bool idMat6::InverseFastSelf( void ) { +#if 0 + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +#elif 0 + // 6*40 = 240 multiplications + // 6 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + mat[5] *= d; + d = -d; + mat[6] *= d; + mat[12] *= d; + mat[18] *= d; + mat[24] *= d; + mat[30] *= d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + mat[9] += mat[3] * d; + mat[10] += mat[4] * d; + mat[11] += mat[5] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + mat[16] += mat[4] * d; + mat[17] += mat[5] * d; + d = mat[18] * di; + mat[19] += mat[1] * d; + mat[20] += mat[2] * d; + mat[21] += mat[3] * d; + mat[22] += mat[4] * d; + mat[23] += mat[5] * d; + d = mat[24] * di; + mat[25] += mat[1] * d; + mat[26] += mat[2] * d; + mat[27] += mat[3] * d; + mat[28] += mat[4] * d; + mat[29] += mat[5] * d; + d = mat[30] * di; + mat[31] += mat[1] * d; + mat[32] += mat[2] * d; + mat[33] += mat[3] * d; + mat[34] += mat[4] * d; + mat[35] += mat[5] * d; + di = mat[7]; + s *= di; + mat[7] = d = 1.0f / di; + mat[6] *= d; + mat[8] *= d; + mat[9] *= d; + mat[10] *= d; + mat[11] *= d; + d = -d; + mat[1] *= d; + mat[13] *= d; + mat[19] *= d; + mat[25] *= d; + mat[31] *= d; + d = mat[1] * di; + mat[0] += mat[6] * d; + mat[2] += mat[8] * d; + mat[3] += mat[9] * d; + mat[4] += mat[10] * d; + mat[5] += mat[11] * d; + d = mat[13] * di; + mat[12] += mat[6] * d; + mat[14] += mat[8] * d; + mat[15] += mat[9] * d; + mat[16] += mat[10] * d; + mat[17] += mat[11] * d; + d = mat[19] * di; + mat[18] += mat[6] * d; + mat[20] += mat[8] * d; + mat[21] += mat[9] * d; + mat[22] += mat[10] * d; + mat[23] += mat[11] * d; + d = mat[25] * di; + mat[24] += mat[6] * d; + mat[26] += mat[8] * d; + mat[27] += mat[9] * d; + mat[28] += mat[10] * d; + mat[29] += mat[11] * d; + d = mat[31] * di; + mat[30] += mat[6] * d; + mat[32] += mat[8] * d; + mat[33] += mat[9] * d; + mat[34] += mat[10] * d; + mat[35] += mat[11] * d; + di = mat[14]; + s *= di; + mat[14] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + d = -d; + mat[2] *= d; + mat[8] *= d; + mat[20] *= d; + mat[26] *= d; + mat[32] *= d; + d = mat[2] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[3] += mat[15] * d; + mat[4] += mat[16] * d; + mat[5] += mat[17] * d; + d = mat[8] * di; + mat[6] += mat[12] * d; + mat[7] += mat[13] * d; + mat[9] += mat[15] * d; + mat[10] += mat[16] * d; + mat[11] += mat[17] * d; + d = mat[20] * di; + mat[18] += mat[12] * d; + mat[19] += mat[13] * d; + mat[21] += mat[15] * d; + mat[22] += mat[16] * d; + mat[23] += mat[17] * d; + d = mat[26] * di; + mat[24] += mat[12] * d; + mat[25] += mat[13] * d; + mat[27] += mat[15] * d; + mat[28] += mat[16] * d; + mat[29] += mat[17] * d; + d = mat[32] * di; + mat[30] += mat[12] * d; + mat[31] += mat[13] * d; + mat[33] += mat[15] * d; + mat[34] += mat[16] * d; + mat[35] += mat[17] * d; + di = mat[21]; + s *= di; + mat[21] = d = 1.0f / di; + mat[18] *= d; + mat[19] *= d; + mat[20] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[3] *= d; + mat[9] *= d; + mat[15] *= d; + mat[27] *= d; + mat[33] *= d; + d = mat[3] * di; + mat[0] += mat[18] * d; + mat[1] += mat[19] * d; + mat[2] += mat[20] * d; + mat[4] += mat[22] * d; + mat[5] += mat[23] * d; + d = mat[9] * di; + mat[6] += mat[18] * d; + mat[7] += mat[19] * d; + mat[8] += mat[20] * d; + mat[10] += mat[22] * d; + mat[11] += mat[23] * d; + d = mat[15] * di; + mat[12] += mat[18] * d; + mat[13] += mat[19] * d; + mat[14] += mat[20] * d; + mat[16] += mat[22] * d; + mat[17] += mat[23] * d; + d = mat[27] * di; + mat[24] += mat[18] * d; + mat[25] += mat[19] * d; + mat[26] += mat[20] * d; + mat[28] += mat[22] * d; + mat[29] += mat[23] * d; + d = mat[33] * di; + mat[30] += mat[18] * d; + mat[31] += mat[19] * d; + mat[32] += mat[20] * d; + mat[34] += mat[22] * d; + mat[35] += mat[23] * d; + di = mat[28]; + s *= di; + mat[28] = d = 1.0f / di; + mat[24] *= d; + mat[25] *= d; + mat[26] *= d; + mat[27] *= d; + mat[29] *= d; + d = -d; + mat[4] *= d; + mat[10] *= d; + mat[16] *= d; + mat[22] *= d; + mat[34] *= d; + d = mat[4] * di; + mat[0] += mat[24] * d; + mat[1] += mat[25] * d; + mat[2] += mat[26] * d; + mat[3] += mat[27] * d; + mat[5] += mat[29] * d; + d = mat[10] * di; + mat[6] += mat[24] * d; + mat[7] += mat[25] * d; + mat[8] += mat[26] * d; + mat[9] += mat[27] * d; + mat[11] += mat[29] * d; + d = mat[16] * di; + mat[12] += mat[24] * d; + mat[13] += mat[25] * d; + mat[14] += mat[26] * d; + mat[15] += mat[27] * d; + mat[17] += mat[29] * d; + d = mat[22] * di; + mat[18] += mat[24] * d; + mat[19] += mat[25] * d; + mat[20] += mat[26] * d; + mat[21] += mat[27] * d; + mat[23] += mat[29] * d; + d = mat[34] * di; + mat[30] += mat[24] * d; + mat[31] += mat[25] * d; + mat[32] += mat[26] * d; + mat[33] += mat[27] * d; + mat[35] += mat[29] * d; + di = mat[35]; + s *= di; + mat[35] = d = 1.0f / di; + mat[30] *= d; + mat[31] *= d; + mat[32] *= d; + mat[33] *= d; + mat[34] *= d; + d = -d; + mat[5] *= d; + mat[11] *= d; + mat[17] *= d; + mat[23] *= d; + mat[29] *= d; + d = mat[5] * di; + mat[0] += mat[30] * d; + mat[1] += mat[31] * d; + mat[2] += mat[32] * d; + mat[3] += mat[33] * d; + mat[4] += mat[34] * d; + d = mat[11] * di; + mat[6] += mat[30] * d; + mat[7] += mat[31] * d; + mat[8] += mat[32] * d; + mat[9] += mat[33] * d; + mat[10] += mat[34] * d; + d = mat[17] * di; + mat[12] += mat[30] * d; + mat[13] += mat[31] * d; + mat[14] += mat[32] * d; + mat[15] += mat[33] * d; + mat[16] += mat[34] * d; + d = mat[23] * di; + mat[18] += mat[30] * d; + mat[19] += mat[31] * d; + mat[20] += mat[32] * d; + mat[21] += mat[33] * d; + mat[22] += mat[34] * d; + d = mat[29] * di; + mat[24] += mat[30] * d; + mat[25] += mat[31] * d; + mat[26] += mat[32] * d; + mat[27] += mat[33] * d; + mat[28] += mat[34] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 6*27+2*30 = 222 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + c0 = mat[1*6+1] * mat[2*6+2] - mat[1*6+2] * mat[2*6+1]; + c1 = mat[1*6+2] * mat[2*6+0] - mat[1*6+0] * mat[2*6+2]; + c2 = mat[1*6+0] * mat[2*6+1] - mat[1*6+1] * mat[2*6+0]; + + det = mat[0*6+0] * c0 + mat[0*6+1] * c1 + mat[0*6+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*6+2] * mat[2*6+1] - mat[0*6+1] * mat[2*6+2] ) * invDet; + r0[0][2] = ( mat[0*6+1] * mat[1*6+2] - mat[0*6+2] * mat[1*6+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*6+0] * mat[2*6+2] - mat[0*6+2] * mat[2*6+0] ) * invDet; + r0[1][2] = ( mat[0*6+2] * mat[1*6+0] - mat[0*6+0] * mat[1*6+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*6+1] * mat[2*6+0] - mat[0*6+0] * mat[2*6+1] ) * invDet; + r0[2][2] = ( mat[0*6+0] * mat[1*6+1] - mat[0*6+1] * mat[1*6+0] ) * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*6+3] + r0[0][1] * mat[1*6+3] + r0[0][2] * mat[2*6+3]; + r1[0][1] = r0[0][0] * mat[0*6+4] + r0[0][1] * mat[1*6+4] + r0[0][2] * mat[2*6+4]; + r1[0][2] = r0[0][0] * mat[0*6+5] + r0[0][1] * mat[1*6+5] + r0[0][2] * mat[2*6+5]; + r1[1][0] = r0[1][0] * mat[0*6+3] + r0[1][1] * mat[1*6+3] + r0[1][2] * mat[2*6+3]; + r1[1][1] = r0[1][0] * mat[0*6+4] + r0[1][1] * mat[1*6+4] + r0[1][2] * mat[2*6+4]; + r1[1][2] = r0[1][0] * mat[0*6+5] + r0[1][1] * mat[1*6+5] + r0[1][2] * mat[2*6+5]; + r1[2][0] = r0[2][0] * mat[0*6+3] + r0[2][1] * mat[1*6+3] + r0[2][2] * mat[2*6+3]; + r1[2][1] = r0[2][0] * mat[0*6+4] + r0[2][1] * mat[1*6+4] + r0[2][2] * mat[2*6+4]; + r1[2][2] = r0[2][0] * mat[0*6+5] + r0[2][1] * mat[1*6+5] + r0[2][2] * mat[2*6+5]; + + // r2 = m2 * r1; + r2[0][0] = mat[3*6+0] * r1[0][0] + mat[3*6+1] * r1[1][0] + mat[3*6+2] * r1[2][0]; + r2[0][1] = mat[3*6+0] * r1[0][1] + mat[3*6+1] * r1[1][1] + mat[3*6+2] * r1[2][1]; + r2[0][2] = mat[3*6+0] * r1[0][2] + mat[3*6+1] * r1[1][2] + mat[3*6+2] * r1[2][2]; + r2[1][0] = mat[4*6+0] * r1[0][0] + mat[4*6+1] * r1[1][0] + mat[4*6+2] * r1[2][0]; + r2[1][1] = mat[4*6+0] * r1[0][1] + mat[4*6+1] * r1[1][1] + mat[4*6+2] * r1[2][1]; + r2[1][2] = mat[4*6+0] * r1[0][2] + mat[4*6+1] * r1[1][2] + mat[4*6+2] * r1[2][2]; + r2[2][0] = mat[5*6+0] * r1[0][0] + mat[5*6+1] * r1[1][0] + mat[5*6+2] * r1[2][0]; + r2[2][1] = mat[5*6+0] * r1[0][1] + mat[5*6+1] * r1[1][1] + mat[5*6+2] * r1[2][1]; + r2[2][2] = mat[5*6+0] * r1[0][2] + mat[5*6+1] * r1[1][2] + mat[5*6+2] * r1[2][2]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[3*6+3]; + r3[0][1] = r2[0][1] - mat[3*6+4]; + r3[0][2] = r2[0][2] - mat[3*6+5]; + r3[1][0] = r2[1][0] - mat[4*6+3]; + r3[1][1] = r2[1][1] - mat[4*6+4]; + r3[1][2] = r2[1][2] - mat[4*6+5]; + r3[2][0] = r2[2][0] - mat[5*6+3]; + r3[2][1] = r2[2][1] - mat[5*6+4]; + r3[2][2] = r2[2][2] - mat[5*6+5]; + + // r3.InverseSelf(); + r2[0][0] = r3[1][1] * r3[2][2] - r3[1][2] * r3[2][1]; + r2[1][0] = r3[1][2] * r3[2][0] - r3[1][0] * r3[2][2]; + r2[2][0] = r3[1][0] * r3[2][1] - r3[1][1] * r3[2][0]; + + det = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r2[0][1] = r3[0][2] * r3[2][1] - r3[0][1] * r3[2][2]; + r2[0][2] = r3[0][1] * r3[1][2] - r3[0][2] * r3[1][1]; + r2[1][1] = r3[0][0] * r3[2][2] - r3[0][2] * r3[2][0]; + r2[1][2] = r3[0][2] * r3[1][0] - r3[0][0] * r3[1][2]; + r2[2][1] = r3[0][1] * r3[2][0] - r3[0][0] * r3[2][1]; + r2[2][2] = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + r3[0][0] = r2[0][0] * invDet; + r3[0][1] = r2[0][1] * invDet; + r3[0][2] = r2[0][2] * invDet; + r3[1][0] = r2[1][0] * invDet; + r3[1][1] = r2[1][1] * invDet; + r3[1][2] = r2[1][2] * invDet; + r3[2][0] = r2[2][0] * invDet; + r3[2][1] = r2[2][1] * invDet; + r3[2][2] = r2[2][2] * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[3*6+0] * r0[0][0] + mat[3*6+1] * r0[1][0] + mat[3*6+2] * r0[2][0]; + r2[0][1] = mat[3*6+0] * r0[0][1] + mat[3*6+1] * r0[1][1] + mat[3*6+2] * r0[2][1]; + r2[0][2] = mat[3*6+0] * r0[0][2] + mat[3*6+1] * r0[1][2] + mat[3*6+2] * r0[2][2]; + r2[1][0] = mat[4*6+0] * r0[0][0] + mat[4*6+1] * r0[1][0] + mat[4*6+2] * r0[2][0]; + r2[1][1] = mat[4*6+0] * r0[0][1] + mat[4*6+1] * r0[1][1] + mat[4*6+2] * r0[2][1]; + r2[1][2] = mat[4*6+0] * r0[0][2] + mat[4*6+1] * r0[1][2] + mat[4*6+2] * r0[2][2]; + r2[2][0] = mat[5*6+0] * r0[0][0] + mat[5*6+1] * r0[1][0] + mat[5*6+2] * r0[2][0]; + r2[2][1] = mat[5*6+0] * r0[0][1] + mat[5*6+1] * r0[1][1] + mat[5*6+2] * r0[2][1]; + r2[2][2] = mat[5*6+0] * r0[0][2] + mat[5*6+1] * r0[1][2] + mat[5*6+2] * r0[2][2]; + + // m2 = r3 * r2; + mat[3*6+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + mat[3*6+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1] + r3[0][2] * r2[2][1]; + mat[3*6+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2] + r3[0][2] * r2[2][2]; + mat[4*6+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0] + r3[1][2] * r2[2][0]; + mat[4*6+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1] + r3[1][2] * r2[2][1]; + mat[4*6+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2] + r3[1][2] * r2[2][2]; + mat[5*6+0] = r3[2][0] * r2[0][0] + r3[2][1] * r2[1][0] + r3[2][2] * r2[2][0]; + mat[5*6+1] = r3[2][0] * r2[0][1] + r3[2][1] * r2[1][1] + r3[2][2] * r2[2][1]; + mat[5*6+2] = r3[2][0] * r2[0][2] + r3[2][1] * r2[1][2] + r3[2][2] * r2[2][2]; + + // m0 = r0 - r1 * m2; + mat[0*6+0] = r0[0][0] - r1[0][0] * mat[3*6+0] - r1[0][1] * mat[4*6+0] - r1[0][2] * mat[5*6+0]; + mat[0*6+1] = r0[0][1] - r1[0][0] * mat[3*6+1] - r1[0][1] * mat[4*6+1] - r1[0][2] * mat[5*6+1]; + mat[0*6+2] = r0[0][2] - r1[0][0] * mat[3*6+2] - r1[0][1] * mat[4*6+2] - r1[0][2] * mat[5*6+2]; + mat[1*6+0] = r0[1][0] - r1[1][0] * mat[3*6+0] - r1[1][1] * mat[4*6+0] - r1[1][2] * mat[5*6+0]; + mat[1*6+1] = r0[1][1] - r1[1][0] * mat[3*6+1] - r1[1][1] * mat[4*6+1] - r1[1][2] * mat[5*6+1]; + mat[1*6+2] = r0[1][2] - r1[1][0] * mat[3*6+2] - r1[1][1] * mat[4*6+2] - r1[1][2] * mat[5*6+2]; + mat[2*6+0] = r0[2][0] - r1[2][0] * mat[3*6+0] - r1[2][1] * mat[4*6+0] - r1[2][2] * mat[5*6+0]; + mat[2*6+1] = r0[2][1] - r1[2][0] * mat[3*6+1] - r1[2][1] * mat[4*6+1] - r1[2][2] * mat[5*6+1]; + mat[2*6+2] = r0[2][2] - r1[2][0] * mat[3*6+2] - r1[2][1] * mat[4*6+2] - r1[2][2] * mat[5*6+2]; + + // m1 = r1 * r3; + mat[0*6+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0] + r1[0][2] * r3[2][0]; + mat[0*6+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1] + r1[0][2] * r3[2][1]; + mat[0*6+5] = r1[0][0] * r3[0][2] + r1[0][1] * r3[1][2] + r1[0][2] * r3[2][2]; + mat[1*6+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0] + r1[1][2] * r3[2][0]; + mat[1*6+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1] + r1[1][2] * r3[2][1]; + mat[1*6+5] = r1[1][0] * r3[0][2] + r1[1][1] * r3[1][2] + r1[1][2] * r3[2][2]; + mat[2*6+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0] + r1[2][2] * r3[2][0]; + mat[2*6+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1] + r1[2][2] * r3[2][1]; + mat[2*6+5] = r1[2][0] * r3[0][2] + r1[2][1] * r3[1][2] + r1[2][2] * r3[2][2]; + + // m3 = -r3; + mat[3*6+3] = -r3[0][0]; + mat[3*6+4] = -r3[0][1]; + mat[3*6+5] = -r3[0][2]; + mat[4*6+3] = -r3[1][0]; + mat[4*6+4] = -r3[1][1]; + mat[4*6+5] = -r3[1][2]; + mat[5*6+3] = -r3[2][0]; + mat[5*6+4] = -r3[2][1]; + mat[5*6+5] = -r3[2][2]; + + return true; +#endif +} + +/* +============= +idMat6::ToString +============= +*/ +const char *idMat6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMatX +// +//=============================================================== + +// RAVEN BEGIN +// jscott: avoid pointer hackery pokery and use the compiler +float idMatX::tempPtr[MATX_MAX_TEMP]; +//float * idMatX::tempPtr = (float *) ( ( (int) idMatX::temp + 15 ) & ~15 ); +// RAVEN END +int idMatX::tempIndex = 0; + + +/* +============ +idMatX::ChangeSize +============ +*/ +void idMatX::ChangeSize( int rows, int columns, bool makeZero ) { + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldMat = mat; +// RAVEN BEGIN + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +// RAVEN END + if ( makeZero ) { + memset( mat, 0, alloc * sizeof( float ) ); + } + alloced = alloc; + if ( oldMat ) { + int minRow = Min( numRows, rows ); + int minColumn = Min( numColumns, columns ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < minColumn; j++ ) { + mat[ i * columns + j ] = oldMat[ i * numColumns + j ]; + } + } + Mem_Free16( oldMat ); + } + } else { + if ( columns < numColumns ) { + int minRow = Min( numRows, rows ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < columns; j++ ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } else if ( columns > numColumns ) { + for ( int i = Min( numRows, rows ) - 1; i >= 0; i-- ) { + if ( makeZero ) { + for ( int j = columns - 1; j >= numColumns; j-- ) { + mat[ i * columns + j ] = 0.0f; + } + } + for ( int j = numColumns - 1; j >= 0; j-- ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } + if ( makeZero && rows > numRows ) { + memset( mat + numRows * columns, 0, ( rows - numRows ) * columns * sizeof( float ) ); + } + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +/* +============ +idMatX::RemoveRow +============ +*/ +idMatX &idMatX::RemoveRow( int r ) { + int i; + + assert( r < numRows ); + + numRows--; + + for ( i = r; i < numRows; i++ ) { + memcpy( &mat[i * numColumns], &mat[( i + 1 ) * numColumns], numColumns * sizeof( float ) ); + } + + return *this; +} + +/* +============ +idMatX::RemoveColumn +============ +*/ +idMatX &idMatX::RemoveColumn( int r ) { + int i; + + assert( r < numColumns ); + + numColumns--; + + for ( i = 0; i < numRows - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +============ +idMatX::RemoveRowColumn +============ +*/ +idMatX &idMatX::RemoveRowColumn( int r ) { + int i; + + assert( r < numRows && r < numColumns ); + + numRows--; + numColumns--; + + if ( r > 0 ) { + for ( i = 0; i < r - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + } + + memcpy( &mat[r * numColumns], &mat[( r + 1 ) * ( numColumns + 1 )], r * sizeof( float ) ); + + for ( i = r; i < numRows - 1; i++ ) { + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +============ +idMatX::IsOrthogonal + + returns true if (*this) * this->Transpose() == Identity +============ +*/ +bool idMatX::IsOrthogonal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + } + return true; +} + +/* +============ +idMatX::IsOrthonormal + + returns true if (*this) * this->Transpose() == Identity and the length of each column vector is 1 +============ +*/ +bool idMatX::IsOrthonormal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + + ptr2 = mat + i; + sum = ptr2[0] * ptr2[0] - 1.0f; + for ( i = 1; i < numRows; i++ ) { + ptr2 += numColumns; + sum += ptr2[i] * ptr2[i]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::IsPMatrix + + returns true if the matrix is a P-matrix + A square matrix is a P-matrix if all its principal minors are positive. +============ +*/ +bool idMatX::IsPMatrix( const float epsilon ) const { + int i, j; + float d; + idMatX m; + + if ( !IsSquare() ) { + return false; + } + + if ( numRows <= 0 ) { + return true; + } + + if ( (*this)[0][0] <= epsilon ) { + return false; + } + + if ( numRows <= 1 ) { + return true; + } + + m.SetData( numRows - 1, numColumns - 1, MATX_ALLOCA( ( numRows - 1 ) * ( numColumns - 1 ) ) ); + + for ( i = 1; i < numRows; i++ ) { + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + for ( i = 1; i < numRows; i++ ) { + d = (*this)[i][0] / (*this)[0][0]; + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j] - d * (*this)[0][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + return true; +} + +/* +============ +idMatX::IsZMatrix + + returns true if the matrix is a Z-matrix + A square matrix M is a Z-matrix if M[i][j] <= 0 for all i != j. +============ +*/ +bool idMatX::IsZMatrix( const float epsilon ) const { + int i, j; + + if ( !IsSquare() ) { + return false; + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + if ( (*this)[i][j] > epsilon && i != j ) { + return false; + } + } + } + return true; +} + +/* +============ +idMatX::IsPositiveDefinite + + returns true if the matrix is Positive Definite (PD) + A square matrix M of order n is said to be PD if y'My > 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] <= epsilon ) { + return false; + } + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveDefinite + + returns true if the matrix is Symmetric Positive Definite (PD) +============ +*/ +bool idMatX::IsSymmetricPositiveDefinite( const float epsilon ) const { + idMatX m; + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // being able to obtain Cholesky factors is both a necessary and sufficient condition for positive definiteness + return m.Cholesky_Factor(); +} + +/* +============ +idMatX::IsPositiveSemiDefinite + + returns true if the matrix is Positive Semi Definite (PSD) + A square matrix M of order n is said to be PSD if y'My >= 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveSemiDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy original matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Semi Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] < -epsilon ) { + return false; + } + if ( m[j][j] > epsilon ) { + continue; + } + for ( k = 0; k < numRows; k++ ) { + if ( idMath::Fabs( m[k][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( m[j][k] ) > epsilon ) { + return false; + } + } + } + + if ( m[i][i] <= epsilon ) { + continue; + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveSemiDefinite + + returns true if the matrix is Symmetric Positive Semi Definite (PSD) +============ +*/ +bool idMatX::IsSymmetricPositiveSemiDefinite( const float epsilon ) const { + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + return IsPositiveSemiDefinite( epsilon ); +} + +/* +============ +idMatX::LowerTriangularInverse + + in-place inversion of the lower triangular matrix +============ +*/ +bool idMatX::LowerTriangularInverse( void ) { + int i, j, k; + double d, sum; + + for ( i = 0; i < numRows; i++ ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = 0; j < i; j++ ) { + sum = 0.0f; + for ( k = j; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============ +idMatX::UpperTriangularInverse + + in-place inversion of the upper triangular matrix +============ +*/ +bool idMatX::UpperTriangularInverse( void ) { + int i, j, k; + double d, sum; + + for ( i = numRows-1; i >= 0; i-- ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = numRows-1; j > i; j-- ) { + sum = 0.0f; + for ( k = j; k > i; k-- ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============= +idMatX::ToString +============= +*/ +const char *idMatX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============ +idMatX::Update_RankOne + + Updates the matrix to obtain the matrix: A + alpha * v * w' +============ +*/ +void idMatX::Update_RankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float s; + + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * w[j]; + } + } +} + +/* +============ +idMatX::Update_RankOneSymmetric + + Updates the matrix to obtain the matrix: A + alpha * v * v' +============ +*/ +void idMatX::Update_RankOneSymmetric( const idVecX &v, float alpha ) { + int i, j; + float s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * v[j]; + } + } +} + +/* +============ +idMatX::Update_RowColumn + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +void idMatX::Update_RowColumn( const idVecX &v, const idVecX &w, int r ) { + int i; + + assert( w[r] == 0.0f ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[r][i] += w[i]; + } +} + +/* +============ +idMatX::Update_RowColumnSymmetric + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +void idMatX::Update_RowColumnSymmetric( const idVecX &v, int r ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < r; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } + (*this)[r][r] += v[r]; + for ( i = r+1; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } +} + +/* +============ +idMatX::Update_Increment + + Updates the matrix to obtain the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1]], w[numColumns] = 0 +============ +*/ +void idMatX::Update_Increment( const idVecX &v, const idVecX &w ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns-1; i++ ) { + (*this)[numRows-1][i] = w[i]; + } +} + +/* +============ +idMatX::Update_IncrementSymmetric + + Updates the matrix to obtain the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +void idMatX::Update_IncrementSymmetric( const idVecX &v ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows-1; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[numRows-1][i] = v[i]; + } +} + +/* +============ +idMatX::Update_Decrement + + Updates the matrix to obtain a matrix with row r and column r removed. +============ +*/ +void idMatX::Update_Decrement( int r ) { + RemoveRowColumn( r ); +} + +/* +============ +idMatX::Inverse_GaussJordan + + in-place inversion using Gauss-Jordan elimination +============ +*/ +bool idMatX::Inverse_GaussJordan( void ) { + int i, j, k, r, c; + float d, max; + + assert( numRows == numColumns ); + + int *columnIndex = (int *) _alloca16( numRows * sizeof( int ) ); + int *rowIndex = (int *) _alloca16( numRows * sizeof( int ) ); + bool *pivot = (bool *) _alloca16( numRows * sizeof( bool ) ); + + memset( pivot, 0, numRows * sizeof( bool ) ); + + // elimination with full pivoting + for ( i = 0; i < numRows; i++ ) { + + // search the whole matrix except for pivoted rows for the maximum absolute value + max = 0.0f; + r = c = 0; + for ( j = 0; j < numRows; j++ ) { + if ( !pivot[j] ) { + for ( k = 0; k < numRows; k++ ) { + if ( !pivot[k] ) { + d = idMath::Fabs( (*this)[j][k] ); + if ( d > max ) { + max = d; + r = j; + c = k; + } + } + } + } + } + + if ( max == 0.0f ) { + // matrix is not invertible + return false; + } + + pivot[c] = true; + + // swap rows such that entry (c,c) has the pivot entry + if ( r != c ) { + SwapRows( r, c ); + } + + // keep track of the row permutation + rowIndex[i] = r; + columnIndex[i] = c; + + // scale the row to make the pivot entry equal to 1 + d = 1.0f / (*this)[c][c]; + (*this)[c][c] = 1.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[c][k] *= d; + } + + // zero out the pivot column entries in the other rows + for ( j = 0; j < numRows; j++ ) { + if ( j != c ) { + d = (*this)[j][c]; + (*this)[j][c] = 0.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[j][k] -= (*this)[c][k] * d; + } + } + } + } + + // reorder rows to store the inverse of the original matrix + for ( j = numRows - 1; j >= 0; j-- ) { + if ( rowIndex[j] != columnIndex[j] ) { + for ( k = 0; k < numRows; k++ ) { + d = (*this)[k][rowIndex[j]]; + (*this)[k][rowIndex[j]] = (*this)[k][columnIndex[j]]; + (*this)[k][columnIndex[j]] = d; + } + } + } + + return true; +} + +/* +============ +idMatX::Inverse_UpdateRankOne + + Updates the in-place inverse using the Sherman-Morrison formula to obtain the inverse for the matrix: A + alpha * v * w' +============ +*/ +bool idMatX::Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float beta, s; + idVecX y, z; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y.SetData( numRows, VECX_ALLOCA( numRows ) ); + z.SetData( numRows, VECX_ALLOCA( numRows ) ); + + Multiply( y, v ); + TransposeMultiply( z, w ); + beta = 1.0f + ( w * y ); + + if ( beta == 0.0f ) { + return false; + } + + alpha /= beta; + + for ( i = 0; i < numRows; i++ ) { + s = y[i] * alpha; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] -= s * z[j]; + } + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateRowColumn + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !Inverse_UpdateRankOne( v, s, 1.0f ) ) { + return false; + } + if ( !Inverse_UpdateRankOne( s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateIncrement + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return Inverse_UpdateRowColumn( v2, w, numRows-1 ); +} + +/* +============ +idMatX::Inverse_UpdateDecrement + + Updates the in-place inverse to obtain the inverse of the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !Inverse_UpdateRowColumn( v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Inverse_Solve + + Solve Ax = b with A inverted +============ +*/ +void idMatX::Inverse_Solve( idVecX &x, const idVecX &b ) const { + Multiply( x, b ); +} + +/* +============ +idMatX::LU_Factor + + in-place factorization: LU + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + U is a triangular matrix stored in the upper triangle. + If index != NULL partial pivoting is used for numerical stability. + If index != NULL it must point to an array of numRow integers and is used to keep track of the row permutation. + If det != NULL the determinant of the matrix is calculated and stored. +============ +*/ +bool idMatX::LU_Factor( int *index, float *det ) { + int i, j, k, newi, min; + double s, t, d, w; + + // if partial pivoting should be used + if ( index ) { + for ( i = 0; i < numRows; i++ ) { + index[i] = i; + } + } + + w = 1.0f; + min = Min( numRows, numColumns ); + for ( i = 0; i < min; i++ ) { + + newi = i; + s = idMath::Fabs( (*this)[i][i] ); + + if ( index ) { + // find the largest absolute pivot + for ( j = i + 1; j < numRows; j++ ) { + t = idMath::Fabs( (*this)[j][i] ); + if ( t > s ) { + newi = j; + s = t; + } + } + } + + if ( s == 0.0f ) { + return false; + } + + if ( newi != i ) { + + w = -w; + + // swap index elements + k = index[i]; + index[i] = index[newi]; + index[newi] = k; + + // swap rows + for ( j = 0; j < numColumns; j++ ) { + t = (*this)[newi][j]; + (*this)[newi][j] = (*this)[i][j]; + (*this)[i][j] = t; + } + } + + if ( i < numRows ) { + d = 1.0f / (*this)[i][i]; + for ( j = i + 1; j < numRows; j++ ) { + (*this)[j][i] *= d; + } + } + + if ( i < min-1 ) { + for ( j = i + 1; j < numRows; j++ ) { + d = (*this)[j][i]; + for ( k = i + 1; k < numColumns; k++ ) { + (*this)[j][k] -= d * (*this)[i][k]; + } + } + } + } + + if ( det ) { + for ( i = 0; i < numRows; i++ ) { + w *= (*this)[i][i]; + } + *det = w; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateRankOne + + Updates the in-place LU factorization to obtain the factors for the matrix: LU + alpha * v * w' +============ +*/ +bool idMatX::LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ) { + int i, j, max; + float *y, *z; + double diag, beta, p0, p1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[index[i]]; + } + } else { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[i]; + } + } + + memcpy( z, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + max = Min( numRows, numColumns ); + for ( i = 0; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y[i]; + p1 = z[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta = p1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z[j]; + z[j] -= beta * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p0 * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + return true; +} + +/* +============ +idMatX::LU_UpdateRowColumn + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LU + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ) { +#if 0 + + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !LU_UpdateRankOne( v, s, 1.0f, index ) ) { + return false; + } + if ( !LU_UpdateRankOne( s, w, 1.0f, index ) ) { + return false; + } + return true; + +#else + + int i, j, min, max, rp; + float *y0, *y1, *z0, *z1; + double diag, beta0, beta1, p0, p1, q0, q1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numColumns && r < numRows ); + assert( w[r] == 0.0f ); + + y0 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z0 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + y1 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z1 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y0[i] = v[index[i]]; + } + rp = r; + for ( i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + rp = i; + break; + } + } + } else { + memcpy( y0, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + rp = r; + } + + memset( y1, 0, v.GetSize() * sizeof( float ) ); + y1[rp] = 1.0f; + + memset( z0, 0, w.GetSize() * sizeof( float ) ); + z0[r] = 1.0f; + + memcpy( z1, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + // update the beginning of the to be updated row and column + min = Min( r, rp ); + for ( i = 0; i < min; i++ ) { + p0 = y0[i]; + beta1 = z1[i] / (*this)[i][i]; + + (*this)[i][r] += p0; + for ( j = i+1; j < numColumns; j++ ) { + z1[j] -= beta1 * (*this)[i][j]; + } + for ( j = i+1; j < numRows; j++ ) { + y0[j] -= p0 * (*this)[j][i]; + } + (*this)[rp][i] += beta1; + } + + // update the lower right corner starting at r,r + max = Min( numRows, numColumns ); + for ( i = min; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y0[i]; + p1 = z0[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta0 = p1 / diag; + + q0 = y1[i]; + q1 = z1[i]; + diag += q0 * q1; + + if ( diag == 0.0f ) { + return false; + } + + beta1 = q1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + (*this)[j][i] = d; + } + } + return true; + +#endif +} + +/* +============ +idMatX::LU_UpdateIncrement + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ) { + int i, j; + float sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + + // add row to L + for ( i = 0; i < numRows - 1; i++ ) { + sum = w[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[numRows - 1][j] * (*this)[j][i]; + } + (*this)[numRows - 1 ][i] = sum / (*this)[i][i]; + } + + // add row to the permutation index + if ( index != NULL ) { + index[numRows - 1] = numRows - 1; + } + + // add column to U + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = v[index[i]]; + } else { + sum = v[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * (*this)[j][numRows - 1]; + } + (*this)[i][numRows - 1] = sum; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateDecrement + + Updates the in-place LU factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. + If index != NULL then u should store row index[r] of the original matrix. If index == NULL then u = w. +============ +*/ +bool idMatX::LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ) { + int i, p; + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + if ( index != NULL ) { + + // find the pivot row + for ( p = i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + p = i; + break; + } + } + + // update the row and column to identity + v1 = -v; + w1 = -u; + + if ( p != r ) { + idSwap( v1[index[r]], v1[index[p]] ); + idSwap( index[r], index[p] ); + } + + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + + if ( p != r ) { + + if ( idMath::Fabs( u[p] ) < 1e-4f ) { + // NOTE: an additional row interchange is required for numerical stability + } + + // move row index[r] of the original matrix to row index[p] of the original matrix + v1.Zero(); + v1[index[p]] = 1.0f; + w1 = u - w; + + if ( !LU_UpdateRankOne( v1, w1, 1.0f, index ) ) { + return false; + } + } + + // remove the row from the permutation index + for ( i = r; i < numRows - 1; i++ ) { + index[i] = index[i+1]; + } + for ( i = 0; i < numRows - 1; i++ ) { + if ( index[i] > r ) { + index[i]--; + } + } + + } else { + + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LU_Solve + + Solve Ax = b with A factored in-place as: LU +============ +*/ +void idMatX::LU_Solve( idVecX &x, const idVecX &b, const int *index ) const { + int i, j; + double sum; + + assert( x.GetSize() == numColumns && b.GetSize() == numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = b[index[i]]; + } else { + sum = b[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve U + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::LU_Inverse + + Calculates the inverse of the matrix which is factored in-place as LU +============ +*/ +void idMatX::LU_Inverse( idMatX &inv, const int *index ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LU_UnpackFactors + + Unpacks the in-place LU factorization. +============ +*/ +void idMatX::LU_UnpackFactors( idMatX &L, idMatX &U ) const { + int i, j; + + L.Zero( numRows, numColumns ); + U.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + for ( j = i; j < numColumns; j++ ) { + U[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::LU_MultiplyFactors + + Multiplies the factors of the in-place LU factorization to form the original matrix. +============ +*/ +void idMatX::LU_MultiplyFactors( idMatX &m, const int *index ) const { + int r, rp, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + if ( index != NULL ) { + rp = index[r]; + } else { + rp = r; + } + + // calculate row of matrix + for ( i = 0; i < numColumns; i++ ) { + if ( i >= r ) { + sum = (*this)[r][i]; + } else { + sum = 0.0f; + } + for ( j = 0; j <= i && j < r; j++ ) { + sum += (*this)[r][j] * (*this)[j][i]; + } + m[rp][i] = sum; + } + } +} + +/* +============ +idMatX::QR_Factor + + in-place factorization: QR + Q is an orthogonal matrix represented as a product of Householder matrices stored in the lower triangle and c. + R is a triangular matrix stored in the upper triangle except for the diagonal elements which are stored in d. + The initial matrix has to be square. +============ +*/ +bool idMatX::QR_Factor( idVecX &c, idVecX &d ) { + int i, j, k; + double scale, s, t, sum; + bool singular = false; + + assert( numRows == numColumns ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( k = 0; k < numRows-1; k++ ) { + + scale = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = idMath::Fabs( (*this)[i][k] ); + if ( s > scale ) { + scale = s; + } + } + if ( scale == 0.0f ) { + singular = true; + c[k] = d[k] = 0.0f; + } else { + + s = 1.0f / scale; + for ( i = k; i < numRows; i++ ) { + (*this)[i][k] *= s; + } + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = (*this)[i][k]; + sum += s * s; + } + + s = idMath::Sqrt( sum ); + if ( (*this)[k][k] < 0.0f ) { + s = -s; + } + (*this)[k][k] += s; + c[k] = s * (*this)[k][k]; + d[k] = -scale * s; + + for ( j = k+1; j < numRows; j++ ) { + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + sum += (*this)[i][k] * (*this)[i][j]; + } + t = sum / c[k]; + for ( i = k; i < numRows; i++ ) { + (*this)[i][j] -= t * (*this)[i][k]; + } + } + } + } + d[numRows-1] = (*this)[ (numRows-1) ][ (numRows-1) ]; + if ( d[numRows-1] == 0.0f ) { + singular = true; + } + + return !singular; +} + +/* +============ +idMatX::QR_Rotate + + Performs a Jacobi rotation on the rows i and i+1 of the unpacked QR factors. +============ +*/ +void idMatX::QR_Rotate( idMatX &R, int i, float a, float b ) { + int j; + float f, c, s, w, y; + + if ( a == 0.0f ) { + c = 0.0f; + s = ( b >= 0.0f ) ? 1.0f : -1.0f; + } else if ( idMath::Fabs( a ) > idMath::Fabs( b ) ) { + f = b / a; + c = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( a < 0.0f ) { + c = -c; + } + s = f * c; + } else { + f = a / b; + s = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( b < 0.0f ) { + s = -s; + } + c = f * s; + } + for ( j = i; j < numRows; j++ ) { + y = R[i][j]; + w = R[i+1][j]; + R[i][j] = c * y - s * w; + R[i+1][j] = s * y + c * w; + } + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][i]; + w = (*this)[j][i+1]; + (*this)[j][i] = c * y - s * w; + (*this)[j][i+1] = s * y + c * w; + } +} + +/* +============ +idMatX::QR_UpdateRankOne + + Updates the unpacked QR factorization to obtain the factors for the matrix: QR + alpha * v * w' +============ +*/ +bool idMatX::QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ) { + int i, k; + float f; + idVecX u; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + u.SetData( v.GetSize(), VECX_ALLOCA( v.GetSize() ) ); + TransposeMultiply( u, v ); + u *= alpha; + + for ( k = v.GetSize()-1; k > 0; k-- ) { + if ( u[k] != 0.0f ) { + break; + } + } + for ( i = k-1; i >= 0; i-- ) { + QR_Rotate( R, i, u[i], -u[i+1] ); + if ( u[i] == 0.0f ) { + u[i] = idMath::Fabs( u[i+1] ); + } else if ( idMath::Fabs( u[i] ) > idMath::Fabs( u[i+1] ) ) { + f = u[i+1] / u[i]; + u[i] = idMath::Fabs( u[i] ) * idMath::Sqrt( 1.0f + f * f ); + } else { + f = u[i] / u[i+1]; + u[i] = idMath::Fabs( u[i+1] ) * idMath::Sqrt( 1.0f + f * f ); + } + } + for ( i = 0; i < v.GetSize(); i++ ) { + R[0][i] += u[0] * w[i]; + } + for ( i = 0; i < k; i++ ) { + QR_Rotate( R, i, -R[i][i], R[i+1][i] ); + } + return true; +} + +/* +============ +idMatX::QR_UpdateRowColumn + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + QR + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !QR_UpdateRankOne( R, v, s, 1.0f ) ) { + return false; + } + if ( !QR_UpdateRankOne( R, s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::QR_UpdateIncrement + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + R.ChangeSize( R.numRows+1, R.numColumns+1, true ); + R[R.numRows-1][R.numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return QR_UpdateRowColumn( R, v2, w, numRows-1 ); +} + +/* +============ +idMatX::QR_UpdateDecrement + + Updates the unpacked QR factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !QR_UpdateRowColumn( R, v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + R.Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored in-place as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const { + int i, j; + double sum, t; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + x[i] = b[i]; + } + + // multiply b with transpose of Q + for ( i = 0; i < numRows-1; i++ ) { + + sum = 0.0f; + for ( j = i; j < numRows; j++ ) { + sum += (*this)[j][i] * x[j]; + } + t = sum / c[i]; + for ( j = i; j < numRows; j++ ) { + x[j] -= t * (*this)[j][i]; + } + } + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / d[i]; + } +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + + // multiply b with transpose of Q + TransposeMultiply( x, b ); + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= R[i][j] * x[j]; + } + x[i] = sum / R[i][i]; + } +} + +/* +============ +idMatX::QR_Inverse + + Calculates the inverse of the matrix which is factored in-place as: QR +============ +*/ +void idMatX::QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + QR_Solve( x, b, c, d ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::QR_UnpackFactors + + Unpacks the in-place QR factorization. +============ +*/ +void idMatX::QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + R.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + R[i][i] = d[i]; + for ( j = i+1; j < numColumns; j++ ) { + R[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::QR_MultiplyFactors + + Multiplies the factors of the in-place QR factorization to form the original matrix. +============ +*/ +void idMatX::QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + idMatX Q; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = Q[i][j] * d[i]; + for ( k = 0; k < i; k++ ) { + sum += Q[i][k] * (*this)[j][k]; + } + m[i][j] = sum; + } + } +} + +/* +============ +idMatX::Pythag + + Computes (a^2 + b^2)^1/2 without underflow or overflow. +============ +*/ +float idMatX::Pythag( float a, float b ) const { + double at, bt, ct; + + at = idMath::Fabs( a ); + bt = idMath::Fabs( b ); + if ( at > bt ) { + ct = bt / at; + return at * idMath::Sqrt( 1.0f + ct * ct ); + } else { + if ( bt ) { + ct = at / bt; + return bt * idMath::Sqrt( 1.0f + ct * ct ); + } else { + return 0.0f; + } + } +} + +/* +============ +idMatX::SVD_BiDiag +============ +*/ +void idMatX::SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ) { + int i, j, k, l; + double f, h, r, g, s, scale; + + anorm = 0.0f; + g = s = scale = 0.0f; + for ( i = 0; i < numColumns; i++ ) { + l = i + 1; + rv1[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows ) { + for ( k = i; k < numRows; k++ ) { + scale += idMath::Fabs( (*this)[k][i] ); + } + if ( scale ) { + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] /= scale; + s += (*this)[k][i] * (*this)[k][i]; + } + f = (*this)[i][i]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = f * g - s; + (*this)[i][i] = f - g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = i; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = s / h; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] *= scale; + } + } + } + w[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows && i != (numColumns-1) ) { + for ( k = l; k < numColumns; k++ ) { + scale += idMath::Fabs( (*this)[i][k] ); + } + if ( scale ) { + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] /= scale; + s += (*this)[i][k] * (*this)[i][k]; + } + f = (*this)[i][l]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = 1.0f / ( f * g - s ); + (*this)[i][l] = f - g; + for ( k = l; k < numColumns; k++ ) { + rv1[k] = (*this)[i][k] * h; + } + if ( i != (numRows-1) ) { + for ( j = l; j < numRows; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[j][k] * (*this)[i][k]; + } + for ( k = l; k < numColumns; k++ ) { + (*this)[j][k] += s * rv1[k]; + } + } + } + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] *= scale; + } + } + } + r = idMath::Fabs( w[i] ) + idMath::Fabs( rv1[i] ); + if ( r > anorm ) { + anorm = r; + } + } +} + +/* +============ +idMatX::SVD_InitialWV +============ +*/ +void idMatX::SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ) { + int i, j, k, l; + double f, g, s; + + g = 0.0f; + for ( i = (numColumns-1); i >= 0; i-- ) { + l = i + 1; + if ( i < ( numColumns - 1 ) ) { + if ( g ) { + for ( j = l; j < numColumns; j++ ) { + V[j][i] = ((*this)[i][j] / (*this)[i][l]) / g; + } + // double division to reduce underflow + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[i][k] * V[k][j]; + } + for ( k = l; k < numColumns; k++ ) { + V[k][j] += s * V[k][i]; + } + } + } + for ( j = l; j < numColumns; j++ ) { + V[i][j] = V[j][i] = 0.0f; + } + } + V[i][i] = 1.0f; + g = rv1[i]; + } + for ( i = numColumns - 1 ; i >= 0; i-- ) { + l = i + 1; + g = w[i]; + if ( i < (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + } + } + if ( g ) { + g = 1.0f / g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = (s / (*this)[i][i]) * g; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] *= g; + } + } + else { + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] = 0.0f; + } + } + (*this)[i][i] += 1.0f; + } +} + +/* +============ +idMatX::SVD_Factor + + in-place factorization: U * Diag(w) * V.Transpose() + known as the Singular Value Decomposition. + U is a column-orthogonal matrix which overwrites the original matrix. + w is a diagonal matrix with all elements >= 0 which are the singular values. + V is the transpose of an orthogonal matrix. +============ +*/ +bool idMatX::SVD_Factor( idVecX &w, idMatX &V ) { + int flag, i, its, j, jj, k, l, nm; + double c, f, h, s, x, y, z, r, g = 0.0f; + float anorm = 0.0f; + idVecX rv1; + + if ( numRows < numColumns ) { + return false; + } + + rv1.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + rv1.Zero(); + w.Zero( numColumns ); + V.Zero( numColumns, numColumns ); + + SVD_BiDiag( w, rv1, anorm ); + SVD_InitialWV( w, V, rv1 ); + + for ( k = numColumns - 1; k >= 0; k-- ) { + for ( its = 1; its <= 30; its++ ) { + flag = 1; + nm = 0; + for ( l = k; l >= 0; l-- ) { + nm = l - 1; + if ( ( idMath::Fabs( rv1[l] ) + anorm ) == anorm /* idMath::Fabs( rv1[l] ) < idMath::FLT_EPSILON */ ) { + flag = 0; + break; + } + if ( ( idMath::Fabs( w[nm] ) + anorm ) == anorm /* idMath::Fabs( w[nm] ) < idMath::FLT_EPSILON */ ) { + break; + } + } + if ( flag ) { + c = 0.0f; + s = 1.0f; + for ( i = l; i <= k; i++ ) { + f = s * rv1[i]; + + if ( ( idMath::Fabs( f ) + anorm ) != anorm /* idMath::Fabs( f ) > idMath::FLT_EPSILON */ ) { + g = w[i]; + h = Pythag( f, g ); + w[i] = h; + h = 1.0f / h; + c = g * h; + s = -f * h; + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][nm]; + z = (*this)[j][i]; + (*this)[j][nm] = y * c + z * s; + (*this)[j][i] = z * c - y * s; + } + } + } + } + z = w[k]; + if ( l == k ) { + if ( z < 0.0f ) { + w[k] = -z; + for ( j = 0; j < numColumns; j++ ) { + V[j][k] = -V[j][k]; + } + } + break; + } + if ( its == 30 ) { + return false; // no convergence + } + x = w[l]; + nm = k - 1; + y = w[nm]; + g = rv1[nm]; + h = rv1[k]; + f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0f * h * y ); + g = Pythag( f, 1.0f ); + r = ( f >= 0.0f ? g : - g ); + f= ( ( x - z ) * ( x + z ) + h * ( ( y / ( f + r ) ) - h ) ) / x; + c = s = 1.0f; + for ( j = l; j <= nm; j++ ) { + i = j + 1; + g = rv1[i]; + y = w[i]; + h = s * g; + g = c * g; + z = Pythag( f, h ); + rv1[j] = z; + c = f / z; + s = h / z; + f = x * c + g * s; + g = g * c - x * s; + h = y * s; + y = y * c; + for ( jj = 0; jj < numColumns; jj++ ) { + x = V[jj][j]; + z = V[jj][i]; + V[jj][j] = x * c + z * s; + V[jj][i] = z * c - x * s; + } + z = Pythag( f, h ); + w[j] = z; + if ( z ) { + z = 1.0f / z; + c = f * z; + s = h * z; + } + f = ( c * g ) + ( s * y ); + x = ( c * y ) - ( s * g ); + for ( jj = 0; jj < numRows; jj++ ) { + y = (*this)[jj][j]; + z = (*this)[jj][i]; + (*this)[jj][j] = y * c + z * s; + (*this)[jj][i] = z * c - y * s; + } + } + rv1[l] = 0.0f; + rv1[k] = f; + w[k] = x; + } + } + return true; +} + +/* +============ +idMatX::SVD_Solve + + Solve Ax = b with A factored as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const { + int i, j; + double sum; + idVecX tmp; + + assert( x.GetSize() >= numColumns ); + assert( b.GetSize() >= numColumns ); + assert( w.GetSize() == numColumns ); + assert( V.GetNumRows() == numColumns && V.GetNumColumns() == numColumns ); + + tmp.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( w[i] >= idMath::FLOAT_EPSILON ) { +// RAVEN END + for ( j = 0; j < numRows; j++ ) { + sum += (*this)[j][i] * b[j]; + } + sum /= w[i]; + } + tmp[i] = sum; + } + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += V[i][j] * tmp[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::SVD_Inverse + + Calculates the inverse of the matrix which is factored in-place as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const { + int i, j, k; + double wi, sum; + idMatX V2; + + assert( numRows == numColumns ); + + V2 = V; + + // V * [diag(1/w[i])] + for ( i = 0; i < numRows; i++ ) { + wi = w[i]; +// RAVEN BEGIN +// jscott: renamed to prevent name clash + wi = ( wi < idMath::FLOAT_EPSILON ) ? 0.0f : 1.0f / wi; +// RAVEN END + for ( j = 0; j < numColumns; j++ ) { + V2[j][i] *= wi; + } + } + + // V * [diag(1/w[i])] * Ut + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = V2[i][0] * (*this)[j][0]; + for ( k = 1; k < numColumns; k++ ) { + sum += V2[i][k] * (*this)[j][k]; + } + inv[i][j] = sum; + } + } +} + +/* +============ +idMatX::SVD_MultiplyFactors + + Multiplies the factors of the in-place SVD factorization to form the original matrix. +============ +*/ +void idMatX::SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, V.GetNumRows() ); + + for ( r = 0; r < numRows; r++ ) { + // calculate row of matrix +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( w[r] >= idMath::FLOAT_EPSILON ) { +// RAVEN END + for ( i = 0; i < V.GetNumRows(); i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += (*this)[r][j] * V[i][j]; + } + m[r][i] = sum * w[r]; + } + } else { + for ( i = 0; i < V.GetNumRows(); i++ ) { + m[r][i] = 0.0f; + } + } + } +} + +/* +============ +idMatX::Cholesky_Factor + + in-place Cholesky factorization: LL' + L is a triangular matrix stored in the lower triangle. + The upper triangle is not cleared. + The initial matrix has to be symmetric positive definite. +============ +*/ +bool idMatX::Cholesky_Factor( void ) { + int i, j, k; + float *invSqrt; + double sum; + + assert( numRows == numColumns ); + + invSqrt = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + for ( j = 0; j < i; j++ ) { + + sum = (*this)[i][j]; + for ( k = 0; k < j; k++ ) { + sum -= (*this)[i][k] * (*this)[j][k]; + } + (*this)[i][j] = sum * invSqrt[j]; + } + + sum = (*this)[i][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[i][k]; + } + + if ( sum <= 0.0f ) { + return false; + } + + invSqrt[i] = idMath::InvSqrt( sum ); + (*this)[i][i] = invSqrt[i] * sum; + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRankOne + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: LL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, invDiag, diagSqr, newDiag, newDiagSqr, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha * p * p; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha /= newDiagSqr; + beta = p * alpha; + alpha *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d * newDiag; + } + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRowColumn + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::Cholesky_UpdateRowColumn( const idVecX &v, int r ) { + + int i, j; +// RAVEN BEGIN +// jscott: VC7.1 fix + float sum; +// RAVEN END + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + sum = (*this)[0][0]; +// RAVEN BEGIN +// jscott: VC7.1 fix + sum = ( sum * sum ) + v[0]; +// RAVEN END + if ( sum <= 0.0f ) { + return false; + } + (*this)[0][0] = idMath::Sqrt( sum ); + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[r][j] * (*this)[i][j]; + } + (*this)[r][i] = sum / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++) { + sum -= (*this)[r][j] * (*this)[r][j]; + } + if ( sum <= 0.0f ) { + return false; + } + (*this)[r][r] = idMath::Sqrt( sum ); + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !Cholesky_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !Cholesky_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double diag, invDiag, diagSqr, newDiag, newDiagSqr; + double alpha1, alpha2, beta1, beta2, p1, p2, d; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + p1 = v1[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha1 * p1 * p1; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + alpha1 /= newDiagSqr; + beta1 = p1 * alpha1; + alpha1 *= diagSqr; + + p2 = v2[i]; + diagSqr = newDiagSqr; + newDiagSqr = diagSqr + alpha2 * p2 * p2; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha2 /= newDiagSqr; + beta2 = p2 * alpha2; + alpha2 *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d * newDiag; + } + } + +#endif + return true; +} + +/* +============ +idMatX::Cholesky_UpdateIncrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::Cholesky_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // calculate new row of L and calculate the square of the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = x[i]; + sum -= x[i] * x[i]; + } + + if ( sum <= 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = idMath::Sqrt( sum ); + + return true; +} + +/* +============ +idMatX::Cholesky_UpdateDecrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::Cholesky_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + if ( !Cholesky_UpdateRowColumn( v1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Cholesky_Solve + + Solve Ax = b with A factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::Cholesky_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + Cholesky_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::Cholesky_MultiplyFactors + + Multiplies the factors of the in-place Cholesky factorization to form the original matrix. +============ +*/ +void idMatX::Cholesky_MultiplyFactors( idMatX &m ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i && j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::LDLT_Factor + + in-place factorization: LDL' + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + D is a diagonal matrix stored on the diagonal. + The upper triangle is not cleared. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::LDLT_Factor( void ) { + int i, j, k; + float *v; + double d, sum; + + assert( numRows == numColumns ); + + v = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + sum = (*this)[i][i]; + for ( j = 0; j < i; j++ ) { + d = (*this)[i][j]; + v[j] = (*this)[j][j] * d; + sum -= v[j] * d; + } + + if ( sum == 0.0f ) { + return false; + } + + (*this)[i][i] = sum; + d = 1.0f / sum; + + for ( j = i + 1; j < numRows; j++ ) { + sum = (*this)[j][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[j][k] * v[k]; + } + (*this)[j][i] = sum * d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRankOne + + Updates the in-place LDL' factorization to obtain the factors for the matrix: LDL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, newDiag, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + (*this)[i][i] = newDiag = diag + alpha * p * p; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha /= newDiag; + beta = p * alpha; + alpha *= diag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRowColumn + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LDL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::LDLT_UpdateRowColumn( const idVecX &v, int r ) { + int i, j; + double sum; + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + (*this)[0][0] += v[0]; + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * y[j]; + } + y[i] = sum; + } + + // calculate new row of L + for ( i = 0; i < r; i++ ) { + (*this)[r][i] = y[i] / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++ ) { + sum -= (*this)[r][j] * y[j]; + } + if ( sum == 0.0f ) { + return false; + } + (*this)[r][r] = sum; + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = r; i < numColumns; i++ ) { + if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !LDLT_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !LDLT_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double d, diag, newDiag, p1, p2, alpha1, alpha2, beta1, beta2; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + + diag = (*this)[i][i]; + p1 = v1[i]; + newDiag = diag + alpha1 * p1 * p1; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha1 /= newDiag; + beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha2 /= newDiag; + beta2 = p2 * alpha2; + alpha2 *= diag; + + (*this)[i][i] = newDiag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d; + } + } + +#endif + + return true; +} + +/* +============ +idMatX::LDLT_UpdateIncrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::LDLT_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // calculate new row of L and calculate the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = d = x[i] / (*this)[i][i]; + sum -= d * x[i]; + } + + if ( sum == 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = sum; + + return true; +} + +/* +============ +idMatX::LDLT_UpdateDecrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::LDLT_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + // NOTE: msvc compiler bug: the this pointer stored in edi is expected to stay + // untouched when calling LDLT_UpdateRowColumn in the if statement +#if 0 + if ( !LDLT_UpdateRowColumn( v1, r ) ) { +#else + bool ret = LDLT_UpdateRowColumn( v1, r ); + if ( !ret ) { +#endif + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LDLT_Solve + + Solve Ax = b with A factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve D + for ( i = 0; i < numRows; i++ ) { + x[i] /= (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 2; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::LDLT_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LDLT_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LDLT_UnpackFactors + + Unpacks the in-place LDL' factorization. +============ +*/ +void idMatX::LDLT_UnpackFactors( idMatX &L, idMatX &D ) const { + int i, j; + + L.Zero( numRows, numColumns ); + D.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + D[i][i] = (*this)[i][i]; + } +} + +/* +============ +idMatX::LDLT_MultiplyFactors + + Multiplies the factors of the in-place LDL' factorization to form the original matrix. +============ +*/ +void idMatX::LDLT_MultiplyFactors( idMatX &m ) const { + int r, i, j; + float *v; + double sum; + + v = (float *) _alloca16( numRows * sizeof( float ) ); + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < r; i++ ) { + v[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * v[j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::TriDiagonal_ClearTriangles +============ +*/ +void idMatX::TriDiagonal_ClearTriangles( void ) { + int i, j; + + assert( numRows == numColumns ); + for ( i = 0; i < numRows-2; i++ ) { + for ( j = i+2; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + (*this)[j][i] = 0.0f; + } + } +} + +/* +============ +idMatX::TriDiagonal_Solve + + Solve Ax = b with A being tridiagonal. +============ +*/ +bool idMatX::TriDiagonal_Solve( idVecX &x, const idVecX &b ) const { + int i; + float d; + idVecX tmp; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + tmp.SetData( numRows, VECX_ALLOCA( numRows ) ); + + d = (*this)[0][0]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[0] = b[0] * d; + for ( i = 1; i < numRows; i++ ) { + tmp[i] = (*this)[i-1][i] * d; + d = (*this)[i][i] - (*this)[i][i-1] * tmp[i]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[i] = ( b[i] - (*this)[i][i-1] * x[i-1] ) * d; + } + for ( i = numRows - 2; i >= 0; i-- ) { + x[i] -= tmp[i+1] * x[i+1]; + } + return true; +} + +/* +============ +idMatX::TriDiagonal_Inverse + + Calculates the inverse of a tri-diagonal matrix. +============ +*/ +void idMatX::TriDiagonal_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + TriDiagonal_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::HouseholderReduction + + Householder reduction to symmetric tri-diagonal form. + The original matrix is replaced by an orthogonal matrix effecting the accumulated householder transformations. + The diagonal elements of the diagonal matrix are stored in diag. + The off-diagonal elements of the diagonal matrix are stored in subd. + The initial matrix has to be symmetric. +============ +*/ +void idMatX::HouseholderReduction( idVecX &diag, idVecX &subd ) { + int i0, i1, i2, i3; + float h, f, g, invH, halfFdivH, scale, invScale, sum; + + assert( numRows == numColumns ); + + diag.SetSize( numRows ); + subd.SetSize( numRows ); + + for ( i0 = numRows-1, i3 = numRows-2; i0 >= 1; i0--, i3-- ) { + h = 0.0f; + scale = 0.0f; + + if ( i3 > 0 ) { + for ( i2 = 0; i2 <= i3; i2++ ) { + scale += idMath::Fabs( (*this)[i0][i2] ); + } + if ( scale == 0 ) { + subd[i0] = (*this)[i0][i3]; + } else { + invScale = 1.0f / scale; + for (i2 = 0; i2 <= i3; i2++) + { + (*this)[i0][i2] *= invScale; + h += (*this)[i0][i2] * (*this)[i0][i2]; + } + f = (*this)[i0][i3]; + g = idMath::Sqrt( h ); + if ( f > 0.0f ) { + g = -g; + } + subd[i0] = scale * g; + h -= f * g; + (*this)[i0][i3] = f - g; + f = 0.0f; + invH = 1.0f / h; + for (i1 = 0; i1 <= i3; i1++) { + (*this)[i1][i0] = (*this)[i0][i1] * invH; + g = 0.0f; + for (i2 = 0; i2 <= i1; i2++) { + g += (*this)[i1][i2] * (*this)[i0][i2]; + } + for (i2 = i1+1; i2 <= i3; i2++) { + g += (*this)[i2][i1] * (*this)[i0][i2]; + } + subd[i1] = g * invH; + f += subd[i1] * (*this)[i0][i1]; + } + halfFdivH = 0.5f * f * invH; + for ( i1 = 0; i1 <= i3; i1++ ) { + f = (*this)[i0][i1]; + g = subd[i1] - halfFdivH * f; + subd[i1] = g; + for ( i2 = 0; i2 <= i1; i2++ ) { + (*this)[i1][i2] -= f * subd[i2] + g * (*this)[i0][i2]; + } + } + } + } else { + subd[i0] = (*this)[i0][i3]; + } + + diag[i0] = h; + } + + diag[0] = 0.0f; + subd[0] = 0.0f; + for ( i0 = 0, i3 = -1; i0 <= numRows-1; i0++, i3++ ) { + if ( diag[i0] ) { + for ( i1 = 0; i1 <= i3; i1++ ) { + sum = 0.0f; + for (i2 = 0; i2 <= i3; i2++) { + sum += (*this)[i0][i2] * (*this)[i2][i1]; + } + for ( i2 = 0; i2 <= i3; i2++ ) { + (*this)[i2][i1] -= sum * (*this)[i2][i0]; + } + } + } + diag[i0] = (*this)[i0][i0]; + (*this)[i0][i0] = 1.0f; + for ( i1 = 0; i1 <= i3; i1++ ) { + (*this)[i1][i0] = 0.0f; + (*this)[i0][i1] = 0.0f; + } + } + + // re-order + for ( i0 = 1, i3 = 0; i0 < numRows; i0++, i3++ ) { + subd[i3] = subd[i0]; + } + subd[numRows-1] = 0.0f; +} + +/* +============ +idMatX::QL + + QL algorithm with implicit shifts to determine the eigenvalues and eigenvectors of a symmetric tri-diagonal matrix. + diag contains the diagonal elements of the symmetric tri-diagonal matrix on input and is overwritten with the eigenvalues. + subd contains the off-diagonal elements of the symmetric tri-diagonal matrix and is destroyed. + This matrix has to be either the identity matrix to determine the eigenvectors for a symmetric tri-diagonal matrix, + or the matrix returned by the Householder reduction to determine the eigenvalues for the original symmetric matrix. +============ +*/ +bool idMatX::QL( idVecX &diag, idVecX &subd ) { + const int maxIter = 32; + int i0, i1, i2, i3; + float a, b, f, g, r, p, s, c; + + assert( numRows == numColumns ); + + for ( i0 = 0; i0 < numRows; i0++ ) { + for ( i1 = 0; i1 < maxIter; i1++ ) { + for ( i2 = i0; i2 <= numRows - 2; i2++ ) { + a = idMath::Fabs( diag[i2] ) + idMath::Fabs( diag[i2+1] ); + if ( idMath::Fabs( subd[i2] ) + a == a ) { + break; + } + } + if ( i2 == i0 ) { + break; + } + + g = ( diag[i0+1] - diag[i0] ) / ( 2.0f * subd[i0] ); + r = idMath::Sqrt( g * g + 1.0f ); + if ( g < 0.0f ) { + g = diag[i2] - diag[i0] + subd[i0] / ( g - r ); + } else { + g = diag[i2] - diag[i0] + subd[i0] / ( g + r ); + } + s = 1.0f; + c = 1.0f; + p = 0.0f; + for ( i3 = i2 - 1; i3 >= i0; i3-- ) { + f = s * subd[i3]; + b = c * subd[i3]; + if ( idMath::Fabs( f ) >= idMath::Fabs( g ) ) { + c = g / f; + r = idMath::Sqrt( c * c + 1.0f ); + subd[i3+1] = f * r; + s = 1.0f / r; + c *= s; + } else { + s = f / g; + r = idMath::Sqrt( s * s + 1.0f ); + subd[i3+1] = g * r; + c = 1.0f / r; + s *= c; + } + g = diag[i3+1] - p; + r = ( diag[i3] - g ) * s + 2.0f * b * c; + p = s * r; + diag[i3+1] = g + p; + g = c * r - b; + + for ( int i4 = 0; i4 < numRows; i4++ ) { + f = (*this)[i4][i3+1]; + (*this)[i4][i3+1] = s * (*this)[i4][i3] + c * f; + (*this)[i4][i3] = c * (*this)[i4][i3] - s * f; + } + } + diag[i0] -= p; + subd[i0] = g; + subd[i2] = 0.0f; + } + if ( i1 == maxIter ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::Eigen_SolveSymmetricTriDiagonal + + Determine eigen values and eigen vectors for a symmetric tri-diagonal matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric tri-diagonal. +============ +*/ +bool idMatX::Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ) { + int i; + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + for ( i = 0; i < numRows-1; i++ ) { + eigenValues[i] = (*this)[i][i]; + subd[i] = (*this)[i+1][i]; + } + eigenValues[numRows-1] = (*this)[numRows-1][numRows-1]; + + Identity(); + + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::Eigen_SolveSymmetric + + Determine eigen values and eigen vectors for a symmetric matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::Eigen_SolveSymmetric( idVecX &eigenValues ) { + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + HouseholderReduction( eigenValues, subd ); + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::HessenbergReduction + + Reduction to Hessenberg form. +============ +*/ +void idMatX::HessenbergReduction( idMatX &H ) { + int i, j, m; + int low = 0; + int high = numRows - 1; + float scale, f, g, h; + idVecX v; + + v.SetData( numRows, VECX_ALLOCA( numRows ) ); + + for ( m = low + 1; m <= high - 1; m++ ) { + + scale = 0.0f; + for ( i = m; i <= high; i++ ) { + scale = scale + idMath::Fabs( H[i][m-1] ); + } + if ( scale != 0.0f ) { + + // compute Householder transformation. + h = 0.0f; + for ( i = high; i >= m; i-- ) { + v[i] = H[i][m-1] / scale; + h += v[i] * v[i]; + } + g = idMath::Sqrt( h ); + if ( v[m] > 0.0f ) { + g = -g; + } + h = h - v[m] * g; + v[m] = v[m] - g; + + // apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for ( j = m; j < numRows; j++) { + f = 0.0f; + for ( i = high; i >= m; i-- ) { + f += v[i] * H[i][j]; + } + f = f / h; + for ( i = m; i <= high; i++ ) { + H[i][j] -= f * v[i]; + } + } + + for ( i = 0; i <= high; i++ ) { + f = 0.0f; + for ( j = high; j >= m; j-- ) { + f += v[j] * H[i][j]; + } + f = f / h; + for ( j = m; j <= high; j++ ) { + H[i][j] -= f * v[j]; + } + } + v[m] = scale * v[m]; + H[m][m-1] = scale * g; + } + } + + // accumulate transformations + Identity(); + for ( int m = high - 1; m >= low + 1; m-- ) { + if ( H[m][m-1] != 0.0f ) { + for ( i = m + 1; i <= high; i++ ) { + v[i] = H[i][m-1]; + } + for ( j = m; j <= high; j++ ) { + g = 0.0f; + for ( i = m; i <= high; i++ ) { + g += v[i] * (*this)[i][j]; + } + // float division to avoid possible underflow + g = ( g / v[m] ) / H[m][m-1]; + for ( i = m; i <= high; i++ ) { + (*this)[i][j] += g * v[i]; + } + } + } + } +} + +/* +============ +idMatX::ComplexDivision + + Complex scalar division. +============ +*/ +void idMatX::ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ) { + float r, d; + if ( idMath::Fabs( yr ) > idMath::Fabs( yi ) ) { + r = yi / yr; + d = yr + r * yi; + cdivr = ( xr + r * xi ) / d; + cdivi = ( xi - r * xr ) / d; + } else { + r = yr / yi; + d = yi + r * yr; + cdivr = ( r * xr + xi ) / d; + cdivi = ( r * xi - xr ) / d; + } +} + +/* +============ +idMatX::HessenbergToRealSchur + + Reduction from Hessenberg to real Schur form. +============ +*/ +bool idMatX::HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + int i, j, k; + int n = numRows - 1; + int low = 0; + int high = numRows - 1; + float eps = 2e-16f, exshift = 0.0f; + float p = 0.0f, q = 0.0f, r = 0.0f, s = 0.0f, z = 0.0f, t, w, x, y; + + // store roots isolated by balanc and compute matrix norm + float norm = 0.0f; + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + realEigenValues[i] = H[i][i]; + imaginaryEigenValues[i] = 0.0f; + } + for ( j = Max( i - 1, 0 ); j < numRows; j++ ) { + norm = norm + idMath::Fabs( H[i][j] ); + } + } + + int iter = 0; + while( n >= low ) { + + // look for single small sub-diagonal element + int l = n; + while ( l > low ) { + s = idMath::Fabs( H[l-1][l-1] ) + idMath::Fabs( H[l][l] ); + if ( s == 0.0f ) { + s = norm; + } + if ( idMath::Fabs( H[l][l-1] ) < eps * s ) { + break; + } + l--; + } + + // check for convergence + if ( l == n ) { // one root found + H[n][n] = H[n][n] + exshift; + realEigenValues[n] = H[n][n]; + imaginaryEigenValues[n] = 0.0f; + n--; + iter = 0; + } else if ( l == n-1 ) { // two roots found + w = H[n][n-1] * H[n-1][n]; + p = ( H[n-1][n-1] - H[n][n] ) / 2.0f; + q = p * p + w; + z = idMath::Sqrt( idMath::Fabs( q ) ); + H[n][n] = H[n][n] + exshift; + H[n-1][n-1] = H[n-1][n-1] + exshift; + x = H[n][n]; + + if ( q >= 0.0f ) { // real pair + if ( p >= 0.0f ) { + z = p + z; + } else { + z = p - z; + } + realEigenValues[n-1] = x + z; + realEigenValues[n] = realEigenValues[n-1]; + if ( z != 0.0f ) { + realEigenValues[n] = x - w / z; + } + imaginaryEigenValues[n-1] = 0.0f; + imaginaryEigenValues[n] = 0.0f; + x = H[n][n-1]; + s = idMath::Fabs( x ) + idMath::Fabs( z ); + p = x / s; + q = z / s; + r = idMath::Sqrt( p * p + q * q ); + p = p / r; + q = q / r; + + // modify row + for ( j = n-1; j < numRows; j++ ) { + z = H[n-1][j]; + H[n-1][j] = q * z + p * H[n][j]; + H[n][j] = q * H[n][j] - p * z; + } + + // modify column + for ( i = 0; i <= n; i++ ) { + z = H[i][n-1]; + H[i][n-1] = q * z + p * H[i][n]; + H[i][n] = q * H[i][n] - p * z; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + z = (*this)[i][n-1]; + (*this)[i][n-1] = q * z + p * (*this)[i][n]; + (*this)[i][n] = q * (*this)[i][n] - p * z; + } + } else { // complex pair + realEigenValues[n-1] = x + p; + realEigenValues[n] = x + p; + imaginaryEigenValues[n-1] = z; + imaginaryEigenValues[n] = -z; + } + n = n - 2; + iter = 0; + + } else { // no convergence yet + + // form shift + x = H[n][n]; + y = 0.0f; + w = 0.0f; + if ( l < n ) { + y = H[n-1][n-1]; + w = H[n][n-1] * H[n-1][n]; + } + + // Wilkinson's original ad hoc shift + if ( iter == 10 ) { + exshift += x; + for ( i = low; i <= n; i++ ) { + H[i][i] -= x; + } + s = idMath::Fabs( H[n][n-1] ) + idMath::Fabs( H[n-1][n-2] ); + x = y = 0.75f * s; + w = -0.4375f * s * s; + } + + // new ad hoc shift + if ( iter == 30 ) { + s = ( y - x ) / 2.0f; + s = s * s + w; + if ( s > 0 ) { + s = idMath::Sqrt( s ); + if ( y < x ) { + s = -s; + } + s = x - w / ( ( y - x ) / 2.0f + s ); + for ( i = low; i <= n; i++ ) { + H[i][i] -= s; + } + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; + + // look for two consecutive small sub-diagonal elements + int m; + for( m = n-2; m >= l; m-- ) { + z = H[m][m]; + r = x - z; + s = y - z; + p = ( r * s - w ) / H[m+1][m] + H[m][m+1]; + q = H[m+1][m+1] - z - r - s; + r = H[m+2][m+1]; + s = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + p = p / s; + q = q / s; + r = r / s; + if ( m == l ) { + break; + } + if ( idMath::Fabs( H[m][m-1] ) * ( idMath::Fabs( q ) + idMath::Fabs( r ) ) < + eps * ( idMath::Fabs( p ) * ( idMath::Fabs( H[m-1][m-1] ) + idMath::Fabs( z ) + idMath::Fabs( H[m+1][m+1] ) ) ) ) { + break; + } + } + + for ( i = m+2; i <= n; i++ ) { + H[i][i-2] = 0.0f; + if ( i > m+2 ) { + H[i][i-3] = 0.0f; + } + } + + // double QR step involving rows l:n and columns m:n + for ( k = m; k <= n-1; k++ ) { + bool notlast = ( k != n-1 ); + if ( k != m ) { + p = H[k][k-1]; + q = H[k+1][k-1]; + r = ( notlast ? H[k+2][k-1] : 0.0f ); + x = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + if ( x != 0.0f ) { + p = p / x; + q = q / x; + r = r / x; + } + } + if ( x == 0.0f ) { + break; + } + s = idMath::Sqrt( p * p + q * q + r * r ); + if ( p < 0.0f ) { + s = -s; + } + if ( s != 0.0f ) { + if ( k != m ) { + H[k][k-1] = -s * x; + } else if ( l != m ) { + H[k][k-1] = -H[k][k-1]; + } + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // modify row + for ( j = k; j < numRows; j++ ) { + p = H[k][j] + q * H[k+1][j]; + if ( notlast ) { + p = p + r * H[k+2][j]; + H[k+2][j] = H[k+2][j] - p * z; + } + H[k][j] = H[k][j] - p * x; + H[k+1][j] = H[k+1][j] - p * y; + } + + // modify column + for ( i = 0; i <= Min( n, k + 3 ); i++ ) { + p = x * H[i][k] + y * H[i][k+1]; + if ( notlast ) { + p = p + z * H[i][k+2]; + H[i][k+2] = H[i][k+2] - p * r; + } + H[i][k] = H[i][k] - p; + H[i][k+1] = H[i][k+1] - p * q; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + p = x * (*this)[i][k] + y * (*this)[i][k+1]; + if ( notlast ) { + p = p + z * (*this)[i][k+2]; + (*this)[i][k+2] = (*this)[i][k+2] - p * r; + } + (*this)[i][k] = (*this)[i][k] - p; + (*this)[i][k+1] = (*this)[i][k+1] - p * q; + } + } + } + } + } + + // backsubstitute to find vectors of upper triangular form + if ( norm == 0.0f ) { + return false; + } + + for ( n = numRows-1; n >= 0; n-- ) { + p = realEigenValues[n]; + q = imaginaryEigenValues[n]; + + if ( q == 0.0f ) { // real vector + int l = n; + H[n][n] = 1.0f; + for ( i = n-1; i >= 0; i-- ) { + w = H[i][i] - p; + r = 0.0f; + for ( j = l; j <= n; j++ ) { + r = r + H[i][j] * H[j][n]; + } + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + s = r; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + if ( w != 0.0f ) { + H[i][n] = -r / w; + } else { + H[i][n] = -r / ( eps * norm ); + } + } else { // solve real equations + x = H[i][i+1]; + y = H[i+1][i]; + q = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i]; + t = ( x * s - z * r ) / q; + H[i][n] = t; + if ( idMath::Fabs(x) > idMath::Fabs( z ) ) { + H[i+1][n] = ( -r - w * t ) / x; + } else { + H[i+1][n] = ( -s - y * t ) / z; + } + } + + // overflow control + t = idMath::Fabs(H[i][n]); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n] = H[j][n] / t; + } + } + } + } + } else if ( q < 0.0f ) { // complex vector + int l = n-1; + + // last vector component imaginary so matrix is triangular + if ( idMath::Fabs( H[n][n-1] ) > idMath::Fabs( H[n-1][n] ) ) { + H[n-1][n-1] = q / H[n][n-1]; + H[n-1][n] = -( H[n][n] - p ) / H[n][n-1]; + } else { + ComplexDivision( 0.0f, -H[n-1][n], H[n-1][n-1]-p, q, H[n-1][n-1], H[n-1][n] ); + } + H[n][n-1] = 0.0f; + H[n][n] = 1.0f; + for ( i = n-2; i >= 0; i-- ) { + float ra, sa, vr, vi; + ra = 0.0f; + sa = 0.0f; + for ( j = l; j <= n; j++ ) { + ra = ra + H[i][j] * H[j][n-1]; + sa = sa + H[i][j] * H[j][n]; + } + w = H[i][i] - p; + + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + r = ra; + s = sa; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + ComplexDivision( -ra, -sa, w, q, H[i][n-1], H[i][n] ); + } else { + // solve complex equations + x = H[i][i+1]; + y = H[i+1][i]; + vr = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i] - q * q; + vi = ( realEigenValues[i] - p ) * 2.0f * q; + if ( vr == 0.0f && vi == 0.0f ) { + vr = eps * norm * ( idMath::Fabs( w ) + idMath::Fabs( q ) + idMath::Fabs( x ) + idMath::Fabs( y ) + idMath::Fabs( z ) ); + } + ComplexDivision( x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi, H[i][n-1], H[i][n] ); + if ( idMath::Fabs( x ) > ( idMath::Fabs( z ) + idMath::Fabs( q ) ) ) { + H[i+1][n-1] = ( -ra - w * H[i][n-1] + q * H[i][n] ) / x; + H[i+1][n] = ( -sa - w * H[i][n] - q * H[i][n-1] ) / x; + } else { + ComplexDivision( -r - y * H[i][n-1], -s - y * H[i][n], z, q, H[i+1][n-1], H[i+1][n] ); + } + } + + // overflow control + t = Max( idMath::Fabs( H[i][n-1] ), idMath::Fabs( H[i][n] ) ); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n-1] = H[j][n-1] / t; + H[j][n] = H[j][n] / t; + } + } + } + } + } + } + + // vectors of isolated roots + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + for ( j = i; j < numRows; j++ ) { + (*this)[i][j] = H[i][j]; + } + } + } + + // back transformation to get eigenvectors of original matrix + for ( j = numRows - 1; j >= low; j-- ) { + for ( i = low; i <= high; i++ ) { + z = 0.0f; + for ( k = low; k <= Min( j, high ); k++ ) { + z = z + (*this)[i][k] * H[k][j]; + } + (*this)[i][j] = z; + } + } + + return true; +} + +/* +============ +idMatX::Eigen_Solve + + Determine eigen values and eigen vectors for a square matrix. + The eigen values are stored in 'realEigenValues' and 'imaginaryEigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the realEigenValues[i] and imaginaryEigenValues[i]. +============ +*/ +bool idMatX::Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + idMatX H; + + assert( numRows == numColumns ); + + realEigenValues.SetSize( numRows ); + imaginaryEigenValues.SetSize( numRows ); + + H = *this; + + // reduce to Hessenberg form + HessenbergReduction( H ); + + // reduce Hessenberg to real Schur form + return HessenbergToRealSchur( H, realEigenValues, imaginaryEigenValues ); +} + +/* +============ +idMatX::Eigen_SortIncreasing +============ +*/ +void idMatX::Eigen_SortIncreasing( idVecX &eigenValues ) { + int i, j, k; + float min; + + for ( i = 0, j; i <= numRows - 2; i++ ) { + j = i; + min = eigenValues[j]; + for ( k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] < min ) { + j = k; + min = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::Eigen_SortDecreasing +============ +*/ +void idMatX::Eigen_SortDecreasing( idVecX &eigenValues ) { + int i, j, k; + float max; + + for ( i = 0, j; i <= numRows - 2; i++ ) { + j = i; + max = eigenValues[j]; + for ( k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] > max ) { + j = k; + max = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::DeterminantGeneric +============ +*/ +float idMatX::DeterminantGeneric( void ) const { + int *index; + float det; + idMatX tmp; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index, &det ) ) { + return 0.0f; + } + + return det; +} + +/* +============ +idMatX::InverseSelfGeneric +============ +*/ +bool idMatX::InverseSelfGeneric( void ) { + int i, j, *index; + idMatX tmp; + idVecX x, b; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index ) ) { + return false; + } + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + tmp.LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + (*this)[j][i] = x[j]; + } + b[i] = 0.0f; + } + return true; +} + +/* +============ +idMatX::Test +============ +*/ +void idMatX::Test( void ) { + idMatX original, m1, m2, m3, q1, q2, r1, r2; + idVecX v, w, u, c, d; + int offset, size, *index1, *index2; + + size = 6; + original.Random( size, size, 0 ); + original = original * original.Transpose(); + + index1 = (int *) _alloca16( ( size + 1 ) * sizeof( index1[0] ) ); + index2 = (int *) _alloca16( ( size + 1 ) * sizeof( index2[0] ) ); + + /* + idMatX::LowerTriangularInverse + */ + + m1 = original; + m1.ClearUpperTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.LowerTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LowerTriangularInverse failed" ); + } + + /* + idMatX::UpperTriangularInverse + */ + + m1 = original; + m1.ClearLowerTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.UpperTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::UpperTriangularInverse failed" ); + } + + /* + idMatX::Inverse_GaussJordan + */ + + m1 = original; + + m1.Inverse_GaussJordan(); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_GaussJordan failed" ); + } + + /* + idMatX::Inverse_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRankOne( v, w, 1.0f ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRankOne failed" ); + } + + /* + idMatX::Inverse_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRowColumn( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Inverse_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Increment( v, w ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateIncrement( v, w ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateIncrement failed" ); + } + + /* + idMatX::Inverse_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Decrement( offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateDecrement( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Factor + */ + + m1 = original; + + m1.LU_Factor( NULL ); // no pivoting + m1.LU_UnpackFactors( m2, m3 ); + m1 = m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Factor failed" ); + } + + /* + idMatX::LU_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateRankOne( v, w, 1.0f, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRankOne failed" ); + } + + /* + idMatX::LU_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update m1 + m1.LU_UpdateRowColumn( v, w, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LU_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateIncrement( v, w, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateIncrement failed" ); + } + + /* + idMatX::LU_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + u.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + u[i] = original[index1[offset]][i]; + } + + // update factors of m1 + m1.LU_UpdateDecrement( v, w, u, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Inverse + */ + + m2 = original; + + m2.LU_Factor( NULL ); + m2.LU_Inverse( m1, NULL ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Inverse failed" ); + } + + /* + idMatX::QR_Factor + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + m1 = q1 * r1; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Factor failed" ); + } + + /* + idMatX::QR_UpdateRankOne + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + v = w; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateRankOne( r1, v, w, 1.0f ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRankOne failed" ); + } + + /* + idMatX::QR_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update m1 + q1.QR_UpdateRowColumn( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRowColumn failed" ); + } + } + + /* + idMatX::QR_UpdateIncrement + */ + + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateIncrement( r1, v, w ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateIncrement failed" ); + } + + /* + idMatX::QR_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factors of m1 + q1.QR_UpdateDecrement( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateDecrement failed" ); + } + } + + /* + idMatX::QR_Inverse + */ + + m2 = original; + + m2.QR_Factor( c, d ); + m2.QR_Inverse( m1, c, d ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Inverse failed" ); + } + + /* + idMatX::SVD_Factor + */ + + m1 = original; + m3.Zero( size, size ); + w.Zero( size ); + + m1.SVD_Factor( w, m3 ); + m2.Diag( w ); + m3.TransposeSelf(); + m1 = m1 * m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Factor failed" ); + } + + /* + idMatX::SVD_Inverse + */ + + m2 = original; + + m2.SVD_Factor( w, m3 ); + m2.SVD_Inverse( m1, w, m3 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Inverse failed" ); + } + + /* + idMatX::Cholesky_Factor + */ + + m1 = original; + + m1.Cholesky_Factor(); + m1.Cholesky_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Factor failed" ); + } + + /* + idMatX::Cholesky_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.Cholesky_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRankOne failed" ); + } + + /* + idMatX::Cholesky_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + int pdtable[] = { 1, 0, 1, 0, 0, 0 }; + w.Random( size, pdtable[offset] ); + w *= 0.1f; + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.Cholesky_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Cholesky_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.Cholesky_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateIncrement failed" ); + } + + /* + idMatX::Cholesky_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset += size - 1 ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.Cholesky_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateDecrement failed" ); + } + } + + /* + idMatX::Cholesky_Inverse + */ + + m2 = original; + + m2.Cholesky_Factor(); + m2.Cholesky_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Inverse failed" ); + } + + /* + idMatX::LDLT_Factor + */ + + m1 = original; + + m1.LDLT_Factor(); + m1.LDLT_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + m1.LDLT_UnpackFactors( m2, m3 ); + m2 = m2 * m3 * m2.Transpose(); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + /* + idMatX::LDLT_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.LDLT_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRankOne failed" ); + } + + /* + idMatX::LDLT_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.LDLT_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LDLT_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.LDLT_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateIncrement failed" ); + } + + /* + idMatX::LDLT_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.LDLT_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateDecrement failed" ); + } + } + + /* + idMatX::LDLT_Inverse + */ + + m2 = original; + + m2.LDLT_Factor(); + m2.LDLT_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Inverse failed" ); + } + + /* + idMatX::Eigen_SolveSymmetricTriDiagonal + */ + + m3 = original; + m3.TriDiagonal_ClearTriangles(); + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetricTriDiagonal( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetricTriDiagonal failed" ); + } + + /* + idMatX::Eigen_SolveSymmetric + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetric( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetric failed" ); + } + + /* + idMatX::Eigen_Solve + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + w.SetSize( size ); + + m1.Eigen_Solve( v, w ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_Solve failed" ); + } +} diff --git a/source/idlib/math/Matrix.h b/source/idlib/math/Matrix.h new file mode 100644 index 0000000..f7f55ef --- /dev/null +++ b/source/idlib/math/Matrix.h @@ -0,0 +1,3016 @@ + +#ifndef __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +/* +=============================================================================== + + Matrix classes, all matrices are row-major except idMat3 + +=============================================================================== +*/ + +#define MATRIX_INVERSE_EPSILON 1e-14 +#define MATRIX_EPSILON 1e-6 + +class idAngles; +class idQuat; +class idCQuat; +class idRotation; +class idMat4; + +//=============================================================== +// +// idMat2 - 2x2 matrix +// +//=============================================================== + +class idMat2 { +public: + idMat2( void ); + explicit idMat2( const idVec2 &x, const idVec2 &y ); + explicit idMat2( const float xx, const float xy, const float yx, const float yy ); + explicit idMat2( const float src[ 2 ][ 2 ] ); + + const idVec2 & operator[]( int index ) const; + idVec2 & operator[]( int index ); + idMat2 operator-() const; + idMat2 operator*( const float a ) const; + idVec2 operator*( const idVec2 &vec ) const; + idMat2 operator*( const idMat2 &a ) const; + idMat2 operator+( const idMat2 &a ) const; + idMat2 operator-( const idMat2 &a ) const; + idMat2 & operator*=( const float a ); + idMat2 & operator*=( const idMat2 &a ); + idMat2 & operator+=( const idMat2 &a ); + idMat2 & operator-=( const idMat2 &a ); + + friend idMat2 operator*( const float a, const idMat2 &mat ); + friend idVec2 operator*( const idVec2 &vec, const idMat2 &mat ); + friend idVec2 & operator*=( idVec2 &vec, const idMat2 &mat ); + + bool Compare( const idMat2 &a ) const; // exact compare, no epsilon + bool Compare( const idMat2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat2 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat2 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat2 Transpose( void ) const; // returns transpose + idMat2 & TransposeSelf( void ); + idMat2 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat2 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec2 mat[ 2 ]; +}; + +extern idMat2 mat2_zero; +extern idMat2 mat2_identity; +#define mat2_default mat2_identity + +ID_INLINE idMat2::idMat2( void ) { +} + +ID_INLINE idMat2::idMat2( const idVec2 &x, const idVec2 &y ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; +} + +ID_INLINE idMat2::idMat2( const float xx, const float xy, const float yx, const float yy ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; +} + +ID_INLINE idMat2::idMat2( const float src[ 2 ][ 2 ] ) { + memcpy( mat, src, 2 * 2 * sizeof( float ) ); +} + +ID_INLINE const idVec2 &idMat2::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idVec2 &idMat2::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idMat2 idMat2::operator-() const { + return idMat2( -mat[0][0], -mat[0][1], + -mat[1][0], -mat[1][1] ); +} + +ID_INLINE idVec2 idMat2::operator*( const idVec2 &vec ) const { + return idVec2( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y ); +} + +ID_INLINE idMat2 idMat2::operator*( const idMat2 &a ) const { + return idMat2( + mat[0].x * a[0].x + mat[0].y * a[1].x, + mat[0].x * a[0].y + mat[0].y * a[1].y, + mat[1].x * a[0].x + mat[1].y * a[1].x, + mat[1].x * a[0].y + mat[1].y * a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator*( const float a ) const { + return idMat2( + mat[0].x * a, mat[0].y * a, + mat[1].x * a, mat[1].y * a ); +} + +ID_INLINE idMat2 idMat2::operator+( const idMat2 &a ) const { + return idMat2( + mat[0].x + a[0].x, mat[0].y + a[0].y, + mat[1].x + a[1].x, mat[1].y + a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator-( const idMat2 &a ) const { + return idMat2( + mat[0].x - a[0].x, mat[0].y - a[0].y, + mat[1].x - a[1].x, mat[1].y - a[1].y ); +} + +ID_INLINE idMat2 &idMat2::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; + mat[1].x *= a; mat[1].y *= a; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator*=( const idMat2 &a ) { + float x, y; + x = mat[0].x; y = mat[0].y; + mat[0].x = x * a[0].x + y * a[1].x; + mat[0].y = x * a[0].y + y * a[1].y; + x = mat[1].x; y = mat[1].y; + mat[1].x = x * a[0].x + y * a[1].x; + mat[1].y = x * a[0].y + y * a[1].y; + return *this; +} + +ID_INLINE idMat2 &idMat2::operator+=( const idMat2 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; + mat[1].x += a[1].x; mat[1].y += a[1].y; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator-=( const idMat2 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; + + return *this; +} + +ID_INLINE idVec2 operator*( const idVec2 &vec, const idMat2 &mat ) { + return mat * vec; +} + +ID_INLINE idMat2 operator*( const float a, idMat2 const &mat ) { + return mat * a; +} + +ID_INLINE idVec2 &operator*=( idVec2 &vec, const idMat2 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::operator==( const idMat2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat2::operator!=( const idMat2 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat2::Zero( void ) { + mat[0].Zero(); + mat[1].Zero(); +} + +ID_INLINE void idMat2::Identity( void ) { + *this = mat2_identity; +} + +ID_INLINE bool idMat2::IsIdentity( const float epsilon ) const { + return Compare( mat2_identity, epsilon ); +} + +ID_INLINE bool idMat2::IsSymmetric( const float epsilon ) const { + return ( idMath::Fabs( mat[0][1] - mat[1][0] ) < epsilon ); +} + +ID_INLINE bool idMat2::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE float idMat2::Trace( void ) const { + return ( mat[0][0] + mat[1][1] ); +} + +ID_INLINE float idMat2::Determinant( void ) const { + return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; +} + +ID_INLINE idMat2 idMat2::Transpose( void ) const { + return idMat2( mat[0][0], mat[1][0], + mat[0][1], mat[1][1] ); +} + +ID_INLINE idMat2 &idMat2::TransposeSelf( void ) { + float tmp; + + tmp = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp; + + return *this; +} + +ID_INLINE idMat2 idMat2::Inverse( void ) const { + idMat2 invMat; + + invMat = *this; +#ifdef _DEBUG + assert( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat2 idMat2::InverseFast( void ) const { + idMat2 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat2::GetDimension( void ) const { + return 4; +} + +ID_INLINE const float *idMat2::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat2::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat3 - 3x3 matrix +// +// NOTE: matrix is column-major +// +//=============================================================== + +class idMat3 { +public: + idMat3( void ); + explicit idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ); + explicit idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + explicit idMat3( const float src[ 3 ][ 3 ] ); + + const idVec3 & operator[]( int index ) const; + idVec3 & operator[]( int index ); + idMat3 operator-() const; + idMat3 operator*( const float a ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat3 operator*( const idMat3 &a ) const; +// RAVEN BEGIN +// jscott: multiply by the transpose + idVec3 operator/( const idVec3 &vec ) const; + idMat3 operator/( const idMat3 &a ) const; +// RAVEN END + idMat3 operator+( const idMat3 &a ) const; + idMat3 operator-( const idMat3 &a ) const; + idMat3 & operator*=( const float a ); + idMat3 & operator*=( const idMat3 &a ); + idMat3 & operator+=( const idMat3 &a ); + idMat3 & operator-=( const idMat3 &a ); + + friend idMat3 operator*( const float a, const idMat3 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat3 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat3 &mat ); + + bool Compare( const idMat3 &a ) const; // exact compare, no epsilon + bool Compare( const idMat3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat3 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat3 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated( void ) const; + + void ProjectVector( const idVec3 &src, idVec3 &dst ) const; + void UnprojectVector( const idVec3 &src, idVec3 &dst ) const; + + bool FixDegeneracies( void ); // fix degenerate axial cases + bool FixDenormals( void ); // change tiny numbers to zero + + float Trace( void ) const; + float Determinant( void ) const; + idMat3 OrthoNormalize( void ) const; + idMat3 & OrthoNormalizeSelf( void ); + idMat3 Transpose( void ) const; // returns transpose + idMat3 & TransposeSelf( void ); + idMat3 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat3 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + idMat3 TransposeMultiply( const idMat3 &b ) const; + + idMat3 InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const; + idMat3 & InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ); + idMat3 InertiaRotate( const idMat3 &rotation ) const; + idMat3 & InertiaRotateSelf( const idMat3 &rotation ); + + int GetDimension( void ) const; +// RAVEN BEGIN +// abahr: + int GetVec3Dimension( void ) const; +// RAVEN END + + idAngles ToAngles( void ) const; +// RAVEN BEGIN + void RotateAbsolute(int whichAxis, float howManyDegrees); + void RotateRelative(int whichAxis, float howManyDegrees); + void RotateArbitrary(const idVec3 &rotAxis, float howManyDegrees); +// RAVEN END + idQuat ToQuat( void ) const; + idCQuat ToCQuat( void ) const; + idRotation ToRotation( void ) const; + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + friend void TransposeMultiply( const idMat3 &inv, const idMat3 &b, idMat3 &dst ); + friend idMat3 SkewSymmetric( idVec3 const &src ); + +private: + idVec3 mat[ 3 ]; +}; + +extern idMat3 mat3_zero; +extern idMat3 mat3_identity; +#define mat3_default mat3_identity + +ID_INLINE idMat3::idMat3( void ) { +} + +ID_INLINE idMat3::idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE idMat3::idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idMat3::idMat3( const float src[ 3 ][ 3 ] ) { + memcpy( mat, src, 3 * 3 * sizeof( float ) ); +} + +ID_INLINE const idVec3 &idMat3::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3 &idMat3::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idMat3 idMat3::operator-() const { + return idMat3( -mat[0][0], -mat[0][1], -mat[0][2], + -mat[1][0], -mat[1][1], -mat[1][2], + -mat[2][0], -mat[2][1], -mat[2][2] ); +} + +ID_INLINE idVec3 idMat3::operator*( const idVec3 &vec ) const { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE idMat3 idMat3::operator*( const idMat3 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat3 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + dstPtr++; + } + m1Ptr += 3; + } + return dst; +} + +// RAVEN BEGIN +// jscott: divide is overridden to multiply by transpose +ID_INLINE idVec3 idMat3::operator/( const idVec3 &vec ) const +{ + return( idVec3( + mat[0].x * vec.x + mat[0].y * vec.y + mat[0].z * vec.z, + mat[1].x * vec.x + mat[1].y * vec.y + mat[1].z * vec.z, + mat[2].x * vec.x + mat[2].y * vec.y + mat[2].z * vec.z ) ); +} + +ID_INLINE idMat3 idMat3::operator/( const idMat3 &a ) const +{ + idMat3 dst; + + dst[0].x = mat[0].x * a.mat[0].x + mat[0].y * a.mat[0].y + mat[0].z * a.mat[0].z; + dst[0].y = mat[0].x * a.mat[1].x + mat[0].y * a.mat[1].y + mat[0].z * a.mat[1].z; + dst[0].z = mat[0].x * a.mat[2].x + mat[0].y * a.mat[2].y + mat[0].z * a.mat[2].z; + + dst[1].x = mat[1].x * a.mat[0].x + mat[1].y * a.mat[0].y + mat[1].z * a.mat[0].z; + dst[1].y = mat[1].x * a.mat[1].x + mat[1].y * a.mat[1].y + mat[1].z * a.mat[1].z; + dst[1].z = mat[1].x * a.mat[2].x + mat[1].y * a.mat[2].y + mat[1].z * a.mat[2].z; + + dst[2].x = mat[2].x * a.mat[0].x + mat[2].y * a.mat[0].y + mat[2].z * a.mat[0].z; + dst[2].y = mat[2].x * a.mat[1].x + mat[2].y * a.mat[1].y + mat[2].z * a.mat[1].z; + dst[2].z = mat[2].x * a.mat[2].x + mat[2].y * a.mat[2].y + mat[2].z * a.mat[2].z; + + return( dst ); +} +// RAVEN END + +ID_INLINE idMat3 idMat3::operator*( const float a ) const { + return idMat3( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE idMat3 idMat3::operator+( const idMat3 &a ) const { + return idMat3( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE idMat3 idMat3::operator-( const idMat3 &a ) const { + return idMat3( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idMat3 &idMat3::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator*=( const idMat3 &a ) { + int i, j; + const float *m2Ptr; + float *m1Ptr, dst[3]; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[j] = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + } + m1Ptr[0] = dst[0]; m1Ptr[1] = dst[1]; m1Ptr[2] = dst[2]; + m1Ptr += 3; + } + return *this; +} + +ID_INLINE idMat3 &idMat3::operator+=( const idMat3 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator-=( const idMat3 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat3 &mat ) { + return mat * vec; +} + +ID_INLINE idMat3 operator*( const float a, const idMat3 &mat ) { + return mat * a; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat3 &mat ) { + float x = mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z; + float y = mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z; + vec.z = mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z; + vec.x = x; + vec.y = y; + return vec; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) && + mat[2].Compare( a[2] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) && + mat[2].Compare( a[2], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::operator==( const idMat3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat3::operator!=( const idMat3 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat3::Zero( void ) { + memset( mat, 0, sizeof( idMat3 ) ); +} + +ID_INLINE void idMat3::Identity( void ) { + *this = mat3_identity; +} + +ID_INLINE bool idMat3::IsIdentity( const float epsilon ) const { + return Compare( mat3_identity, epsilon ); +} + +ID_INLINE bool idMat3::IsSymmetric( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] - mat[1][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[0][2] - mat[2][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[1][2] - mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[0][2] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon || + idMath::Fabs( mat[1][2] ) > epsilon || + idMath::Fabs( mat[2][0] ) > epsilon || + idMath::Fabs( mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsRotated( void ) const { + // NOTE: assumes the 3x3 matrix is orthonormal + return ( mat[0][0] != 1.0f || mat[1][1] != 1.0f || mat[2][2] != 1.0f ); +} + +ID_INLINE void idMat3::ProjectVector( const idVec3 &src, idVec3 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +ID_INLINE void idMat3::UnprojectVector( const idVec3 &src, idVec3 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +ID_INLINE bool idMat3::FixDegeneracies( void ) { + bool r = mat[0].FixDegenerateNormal(); + r |= mat[1].FixDegenerateNormal(); + r |= mat[2].FixDegenerateNormal(); + return r; +} + +ID_INLINE bool idMat3::FixDenormals( void ) { + bool r = mat[0].FixDenormals(); + r |= mat[1].FixDenormals(); + r |= mat[2].FixDenormals(); + return r; +} + +ID_INLINE float idMat3::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] ); +} + +ID_INLINE idMat3 idMat3::OrthoNormalize( void ) const { + idMat3 ortho; + + ortho = *this; + ortho[ 0 ].Normalize(); + ortho[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + ortho[ 2 ].Normalize(); + ortho[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + ortho[ 1 ].Normalize(); + return ortho; +} + +ID_INLINE idMat3 &idMat3::OrthoNormalizeSelf( void ) { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); + return *this; +} + +ID_INLINE idMat3 idMat3::Transpose( void ) const { + return idMat3( mat[0][0], mat[1][0], mat[2][0], + mat[0][1], mat[1][1], mat[2][1], + mat[0][2], mat[1][2], mat[2][2] ); +} + +ID_INLINE idMat3 &idMat3::TransposeSelf( void ) { + float tmp0, tmp1, tmp2; + + tmp0 = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp0; + tmp1 = mat[0][2]; + mat[0][2] = mat[2][0]; + mat[2][0] = tmp1; + tmp2 = mat[1][2]; + mat[1][2] = mat[2][1]; + mat[2][1] = tmp2; + + return *this; +} + +ID_INLINE idMat3 idMat3::Inverse( void ) const { + idMat3 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat3 idMat3::InverseFast( void ) const { + idMat3 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE idMat3 idMat3::TransposeMultiply( const idMat3 &b ) const { + return idMat3( mat[0].x * b[0].x + mat[1].x * b[1].x + mat[2].x * b[2].x, + mat[0].x * b[0].y + mat[1].x * b[1].y + mat[2].x * b[2].y, + mat[0].x * b[0].z + mat[1].x * b[1].z + mat[2].x * b[2].z, + mat[0].y * b[0].x + mat[1].y * b[1].x + mat[2].y * b[2].x, + mat[0].y * b[0].y + mat[1].y * b[1].y + mat[2].y * b[2].y, + mat[0].y * b[0].z + mat[1].y * b[1].z + mat[2].y * b[2].z, + mat[0].z * b[0].x + mat[1].z * b[1].x + mat[2].z * b[2].x, + mat[0].z * b[0].y + mat[1].z * b[1].y + mat[2].z * b[2].y, + mat[0].z * b[0].z + mat[1].z * b[1].z + mat[2].z * b[2].z ); +} + +ID_INLINE void TransposeMultiply( const idMat3 &transpose, const idMat3 &b, idMat3 &dst ) { + dst[0].x = transpose[0].x * b[0].x + transpose[1].x * b[1].x + transpose[2].x * b[2].x; + dst[0].y = transpose[0].x * b[0].y + transpose[1].x * b[1].y + transpose[2].x * b[2].y; + dst[0].z = transpose[0].x * b[0].z + transpose[1].x * b[1].z + transpose[2].x * b[2].z; + dst[1].x = transpose[0].y * b[0].x + transpose[1].y * b[1].x + transpose[2].y * b[2].x; + dst[1].y = transpose[0].y * b[0].y + transpose[1].y * b[1].y + transpose[2].y * b[2].y; + dst[1].z = transpose[0].y * b[0].z + transpose[1].y * b[1].z + transpose[2].y * b[2].z; + dst[2].x = transpose[0].z * b[0].x + transpose[1].z * b[1].x + transpose[2].z * b[2].x; + dst[2].y = transpose[0].z * b[0].y + transpose[1].z * b[1].y + transpose[2].z * b[2].y; + dst[2].z = transpose[0].z * b[0].z + transpose[1].z * b[1].z + transpose[2].z * b[2].z; +} + +ID_INLINE idMat3 SkewSymmetric( idVec3 const &src ) { + return idMat3( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +ID_INLINE int idMat3::GetDimension( void ) const { + return 9; +} + +// RAVEN BEGIN +// abahr: made version for when getting vectors +ID_INLINE int idMat3::GetVec3Dimension( void ) const { + return 3; +} + +ID_INLINE const float *idMat3::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat3::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat4 - 4x4 matrix +// +//=============================================================== + +class idMat4 { +public: + idMat4( void ); + explicit idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ); + explicit idMat4(const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ); + explicit idMat4( const idMat3 &rotation, const idVec3 &translation ); + explicit idMat4( const float src[ 4 ][ 4 ] ); + + const idVec4 & operator[]( int index ) const; + idVec4 & operator[]( int index ); + idMat4 operator*( const float a ) const; + idVec4 operator*( const idVec4 &vec ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat4 operator*( const idMat4 &a ) const; + idMat4 operator+( const idMat4 &a ) const; + idMat4 operator-( const idMat4 &a ) const; + idMat4 & operator*=( const float a ); + idMat4 & operator*=( const idMat4 &a ); + idMat4 & operator+=( const idMat4 &a ); + idMat4 & operator-=( const idMat4 &a ); + + friend idMat4 operator*( const float a, const idMat4 &mat ); + friend idVec4 operator*( const idVec4 &vec, const idMat4 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat4 &mat ); + friend idVec4 & operator*=( idVec4 &vec, const idMat4 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat4 &mat ); + + bool Compare( const idMat4 &a ) const; // exact compare, no epsilon + bool Compare( const idMat4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat4 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat4 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated( void ) const; + + void ProjectVector( const idVec4 &src, idVec4 &dst ) const; + void UnprojectVector( const idVec4 &src, idVec4 &dst ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat4 Transpose( void ) const; // returns transpose + idMat4 & TransposeSelf( void ); + idMat4 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat4 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + idMat4 TransposeMultiply( const idMat4 &b ) const; + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec4 mat[ 4 ]; +}; + +extern idMat4 mat4_zero; +extern idMat4 mat4_identity; +#define mat4_default mat4_identity + +ID_INLINE idMat4::idMat4( void ) { +} + +ID_INLINE idMat4::idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ) { + mat[ 0 ] = x; + mat[ 1 ] = y; + mat[ 2 ] = z; + mat[ 3 ] = w; +} + +ID_INLINE idMat4::idMat4( const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ) { + mat[0][0] = xx; mat[0][1] = xy; mat[0][2] = xz; mat[0][3] = xw; + mat[1][0] = yx; mat[1][1] = yy; mat[1][2] = yz; mat[1][3] = yw; + mat[2][0] = zx; mat[2][1] = zy; mat[2][2] = zz; mat[2][3] = zw; + mat[3][0] = wx; mat[3][1] = wy; mat[3][2] = wz; mat[3][3] = ww; +} + +ID_INLINE idMat4::idMat4( const idMat3 &rotation, const idVec3 &translation ) { + // NOTE: idMat3 is transposed because it is column-major + mat[ 0 ][ 0 ] = rotation[0][0]; + mat[ 0 ][ 1 ] = rotation[1][0]; + mat[ 0 ][ 2 ] = rotation[2][0]; + mat[ 0 ][ 3 ] = translation[0]; + mat[ 1 ][ 0 ] = rotation[0][1]; + mat[ 1 ][ 1 ] = rotation[1][1]; + mat[ 1 ][ 2 ] = rotation[2][1]; + mat[ 1 ][ 3 ] = translation[1]; + mat[ 2 ][ 0 ] = rotation[0][2]; + mat[ 2 ][ 1 ] = rotation[1][2]; + mat[ 2 ][ 2 ] = rotation[2][2]; + mat[ 2 ][ 3 ] = translation[2]; + mat[ 3 ][ 0 ] = 0.0f; + mat[ 3 ][ 1 ] = 0.0f; + mat[ 3 ][ 2 ] = 0.0f; + mat[ 3 ][ 3 ] = 1.0f; +} + +ID_INLINE idMat4::idMat4( const float src[ 4 ][ 4 ] ) { + memcpy( mat, src, 4 * 4 * sizeof( float ) ); +} + +ID_INLINE const idVec4 &idMat4::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idVec4 &idMat4::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idMat4 idMat4::operator*( const float a ) const { + return idMat4( + mat[0].x * a, mat[0].y * a, mat[0].z * a, mat[0].w * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, mat[1].w * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a, mat[2].w * a, + mat[3].x * a, mat[3].y * a, mat[3].z * a, mat[3].w * a ); +} + +ID_INLINE idVec4 idMat4::operator*( const idVec4 &vec ) const { + return idVec4( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w * vec.w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w * vec.w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w * vec.w, + mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w * vec.w ); +} + +ID_INLINE idVec3 idMat4::operator*( const idVec3 &vec ) const { + float s = mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w; + if ( s == 0.0f ) { + return idVec3( 0.0f, 0.0f, 0.0f ); + } + if ( s == 1.0f ) { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w ); + } + else { + float invS = 1.0f / s; + return idVec3( + (mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w) * invS, + (mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w) * invS, + (mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w) * invS ); + } +} + +ID_INLINE idMat4 idMat4::operator*( const idMat4 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat4 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ]; + dstPtr++; + } + m1Ptr += 4; + } + return dst; +} + +ID_INLINE idMat4 idMat4::operator+( const idMat4 &a ) const { + return idMat4( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, mat[0].w + a[0].w, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, mat[1].w + a[1].w, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z, mat[2].w + a[2].w, + mat[3].x + a[3].x, mat[3].y + a[3].y, mat[3].z + a[3].z, mat[3].w + a[3].w ); +} + +ID_INLINE idMat4 idMat4::operator-( const idMat4 &a ) const { + return idMat4( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, mat[0].w - a[0].w, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, mat[1].w - a[1].w, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z, mat[2].w - a[2].w, + mat[3].x - a[3].x, mat[3].y - a[3].y, mat[3].z - a[3].z, mat[3].w - a[3].w ); +} + +ID_INLINE idMat4 &idMat4::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; mat[0].w *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; mat[1].w *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; mat[2].w *= a; + mat[3].x *= a; mat[3].y *= a; mat[3].z *= a; mat[3].w *= a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator*=( const idMat4 &a ) { + *this = (*this) * a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator+=( const idMat4 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; mat[0].w += a[0].w; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; mat[1].w += a[1].w; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; mat[2].w += a[2].w; + mat[3].x += a[3].x; mat[3].y += a[3].y; mat[3].z += a[3].z; mat[3].w += a[3].w; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator-=( const idMat4 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; mat[0].w -= a[0].w; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; mat[1].w -= a[1].w; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; mat[2].w -= a[2].w; + mat[3].x -= a[3].x; mat[3].y -= a[3].y; mat[3].z -= a[3].z; mat[3].w -= a[3].w; + return *this; +} + +ID_INLINE idMat4 operator*( const float a, const idMat4 &mat ) { + return mat * a; +} + +ID_INLINE idVec4 operator*( const idVec4 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec4 &operator*=( idVec4 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::operator==( const idMat4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat4::operator!=( const idMat4 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat4::Zero( void ) { + memset( mat, 0, sizeof( idMat4 ) ); +} + +ID_INLINE void idMat4::Identity( void ) { + *this = mat4_identity; +} + +ID_INLINE bool idMat4::IsIdentity( const float epsilon ) const { + return Compare( mat4_identity, epsilon ); +} + +ID_INLINE bool idMat4::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 4; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsRotated( void ) const { + if ( !mat[ 0 ][ 1 ] && !mat[ 0 ][ 2 ] && + !mat[ 1 ][ 0 ] && !mat[ 1 ][ 2 ] && + !mat[ 2 ][ 0 ] && !mat[ 2 ][ 1 ] ) { + return false; + } + return true; +} + +ID_INLINE void idMat4::ProjectVector( const idVec4 &src, idVec4 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; + dst.w = src * mat[ 3 ]; +} + +ID_INLINE void idMat4::UnprojectVector( const idVec4 &src, idVec4 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z + mat[ 3 ] * src.w; +} + +ID_INLINE float idMat4::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] ); +} + +ID_INLINE idMat4 idMat4::Inverse( void ) const { + idMat4 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat4 idMat4::InverseFast( void ) const { + idMat4 invMat; + + invMat = *this; +#ifdef _DEBUG + assert( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE idMat4 idMat3::ToMat4( void ) const { + // NOTE: idMat3 is transposed because it is column-major + return idMat4( mat[0][0], mat[1][0], mat[2][0], 0.0f, + mat[0][1], mat[1][1], mat[2][1], 0.0f, + mat[0][2], mat[1][2], mat[2][2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f ); +} + +ID_INLINE int idMat4::GetDimension( void ) const { + return 16; +} + +ID_INLINE const float *idMat4::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat4::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat5 - 5x5 matrix +// +//=============================================================== + +class idMat5 { +public: + idMat5( void ); + explicit idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ); + explicit idMat5( const float src[ 5 ][ 5 ] ); + + const idVec5 & operator[]( int index ) const; + idVec5 & operator[]( int index ); + idMat5 operator*( const float a ) const; + idVec5 operator*( const idVec5 &vec ) const; + idMat5 operator*( const idMat5 &a ) const; + idMat5 operator+( const idMat5 &a ) const; + idMat5 operator-( const idMat5 &a ) const; + idMat5 & operator*=( const float a ); + idMat5 & operator*=( const idMat5 &a ); + idMat5 & operator+=( const idMat5 &a ); + idMat5 & operator-=( const idMat5 &a ); + + friend idMat5 operator*( const float a, const idMat5 &mat ); + friend idVec5 operator*( const idVec5 &vec, const idMat5 &mat ); + friend idVec5 & operator*=( idVec5 &vec, const idMat5 &mat ); + + bool Compare( const idMat5 &a ) const; // exact compare, no epsilon + bool Compare( const idMat5 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat5 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat5 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat5 Transpose( void ) const; // returns transpose + idMat5 & TransposeSelf( void ); + idMat5 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat5 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec5 mat[ 5 ]; +}; + +extern idMat5 mat5_zero; +extern idMat5 mat5_identity; +#define mat5_default mat5_identity + +ID_INLINE idMat5::idMat5( void ) { +} + +ID_INLINE idMat5::idMat5( const float src[ 5 ][ 5 ] ) { + memcpy( mat, src, 5 * 5 * sizeof( float ) ); +} + +ID_INLINE idMat5::idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; +} + +ID_INLINE const idVec5 &idMat5::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idVec5 &idMat5::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idMat5 idMat5::operator*( const idMat5 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat5 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ]; + dstPtr++; + } + m1Ptr += 5; + } + return dst; +} + +ID_INLINE idMat5 idMat5::operator*( const float a ) const { + return idMat5( + idVec5( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a ), + idVec5( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a ), + idVec5( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a ), + idVec5( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a ), + idVec5( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a ) ); +} + +ID_INLINE idVec5 idMat5::operator*( const idVec5 &vec ) const { + return idVec5( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] ); +} + +ID_INLINE idMat5 idMat5::operator+( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4] ), + idVec5( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4] ), + idVec5( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4] ), + idVec5( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4] ), + idVec5( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4] ) ); +} + +ID_INLINE idMat5 idMat5::operator-( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4] ), + idVec5( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4] ), + idVec5( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4] ), + idVec5( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4] ), + idVec5( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4] ) ); +} + +ID_INLINE idMat5 &idMat5::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator*=( const idMat5 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator+=( const idMat5 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator-=( const idMat5 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; + return *this; +} + +ID_INLINE idVec5 operator*( const idVec5 &vec, const idMat5 &mat ) { + return mat * vec; +} + +ID_INLINE idMat5 operator*( const float a, idMat5 const &mat ) { + return mat * a; +} + +ID_INLINE idVec5 &operator*=( idVec5 &vec, const idMat5 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::operator==( const idMat5 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat5::operator!=( const idMat5 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat5::Zero( void ) { + memset( mat, 0, sizeof( idMat5 ) ); +} + +ID_INLINE void idMat5::Identity( void ) { + *this = mat5_identity; +} + +ID_INLINE bool idMat5::IsIdentity( const float epsilon ) const { + return Compare( mat5_identity, epsilon ); +} + +ID_INLINE bool idMat5::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 5; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat5::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 5; i++ ) { + for ( int j = 0; j < 5; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE float idMat5::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] ); +} + +ID_INLINE idMat5 idMat5::Inverse( void ) const { + idMat5 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat5 idMat5::InverseFast( void ) const { + idMat5 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat5::GetDimension( void ) const { + return 25; +} + +ID_INLINE const float *idMat5::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat5::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat6 - 6x6 matrix +// +//=============================================================== + +class idMat6 { +public: + idMat6( void ); + explicit idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ); + explicit idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ); + explicit idMat6( const float src[ 6 ][ 6 ] ); + + const idVec6 & operator[]( int index ) const; + idVec6 & operator[]( int index ); + idMat6 operator*( const float a ) const; + idVec6 operator*( const idVec6 &vec ) const; + idMat6 operator*( const idMat6 &a ) const; + idMat6 operator+( const idMat6 &a ) const; + idMat6 operator-( const idMat6 &a ) const; + idMat6 & operator*=( const float a ); + idMat6 & operator*=( const idMat6 &a ); + idMat6 & operator+=( const idMat6 &a ); + idMat6 & operator-=( const idMat6 &a ); + + friend idMat6 operator*( const float a, const idMat6 &mat ); + friend idVec6 operator*( const idVec6 &vec, const idMat6 &mat ); + friend idVec6 & operator*=( idVec6 &vec, const idMat6 &mat ); + + bool Compare( const idMat6 &a ) const; // exact compare, no epsilon + bool Compare( const idMat6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat6 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat6 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + idMat3 SubMat3( int n ) const; + float Trace( void ) const; + float Determinant( void ) const; + idMat6 Transpose( void ) const; // returns transpose + idMat6 & TransposeSelf( void ); + idMat6 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat6 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec6 mat[ 6 ]; +}; + +extern idMat6 mat6_zero; +extern idMat6 mat6_identity; +#define mat6_default mat6_identity + +ID_INLINE idMat6::idMat6( void ) { +} + +ID_INLINE idMat6::idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ) { + mat[0] = idVec6( m0[0][0], m0[0][1], m0[0][2], m1[0][0], m1[0][1], m1[0][2] ); + mat[1] = idVec6( m0[1][0], m0[1][1], m0[1][2], m1[1][0], m1[1][1], m1[1][2] ); + mat[2] = idVec6( m0[2][0], m0[2][1], m0[2][2], m1[2][0], m1[2][1], m1[2][2] ); + mat[3] = idVec6( m2[0][0], m2[0][1], m2[0][2], m3[0][0], m3[0][1], m3[0][2] ); + mat[4] = idVec6( m2[1][0], m2[1][1], m2[1][2], m3[1][0], m3[1][1], m3[1][2] ); + mat[5] = idVec6( m2[2][0], m2[2][1], m2[2][2], m3[2][0], m3[2][1], m3[2][2] ); +} + +ID_INLINE idMat6::idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; + mat[5] = v5; +} + +ID_INLINE idMat6::idMat6( const float src[ 6 ][ 6 ] ) { + memcpy( mat, src, 6 * 6 * sizeof( float ) ); +} + +ID_INLINE const idVec6 &idMat6::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idVec6 &idMat6::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idMat6 idMat6::operator*( const idMat6 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat6 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 6; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 6 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 6 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 6 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 6 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 6 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 6 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return dst; +} + +ID_INLINE idMat6 idMat6::operator*( const float a ) const { + return idMat6( + idVec6( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a, mat[0][5] * a ), + idVec6( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a, mat[1][5] * a ), + idVec6( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a, mat[2][5] * a ), + idVec6( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a, mat[3][5] * a ), + idVec6( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a, mat[4][5] * a ), + idVec6( mat[5][0] * a, mat[5][1] * a, mat[5][2] * a, mat[5][3] * a, mat[5][4] * a, mat[5][5] * a ) ); +} + +ID_INLINE idVec6 idMat6::operator*( const idVec6 &vec ) const { + return idVec6( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4] + mat[0][5] * vec[5], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4] + mat[1][5] * vec[5], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4] + mat[2][5] * vec[5], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4] + mat[3][5] * vec[5], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] + mat[4][5] * vec[5], + mat[5][0] * vec[0] + mat[5][1] * vec[1] + mat[5][2] * vec[2] + mat[5][3] * vec[3] + mat[5][4] * vec[4] + mat[5][5] * vec[5] ); +} + +ID_INLINE idMat6 idMat6::operator+( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4], mat[0][5] + a[0][5] ), + idVec6( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4], mat[1][5] + a[1][5] ), + idVec6( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4], mat[2][5] + a[2][5] ), + idVec6( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4], mat[3][5] + a[3][5] ), + idVec6( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4], mat[4][5] + a[4][5] ), + idVec6( mat[5][0] + a[5][0], mat[5][1] + a[5][1], mat[5][2] + a[5][2], mat[5][3] + a[5][3], mat[5][4] + a[5][4], mat[5][5] + a[5][5] ) ); +} + +ID_INLINE idMat6 idMat6::operator-( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4], mat[0][5] - a[0][5] ), + idVec6( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4], mat[1][5] - a[1][5] ), + idVec6( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4], mat[2][5] - a[2][5] ), + idVec6( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4], mat[3][5] - a[3][5] ), + idVec6( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4], mat[4][5] - a[4][5] ), + idVec6( mat[5][0] - a[5][0], mat[5][1] - a[5][1], mat[5][2] - a[5][2], mat[5][3] - a[5][3], mat[5][4] - a[5][4], mat[5][5] - a[5][5] ) ); +} + +ID_INLINE idMat6 &idMat6::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; mat[0][5] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; mat[1][5] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; mat[2][5] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; mat[3][5] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; mat[4][5] *= a; + mat[5][0] *= a; mat[5][1] *= a; mat[5][2] *= a; mat[5][3] *= a; mat[5][4] *= a; mat[5][5] *= a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator*=( const idMat6 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator+=( const idMat6 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; mat[0][5] += a[0][5]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; mat[1][5] += a[1][5]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; mat[2][5] += a[2][5]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; mat[3][5] += a[3][5]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; mat[4][5] += a[4][5]; + mat[5][0] += a[5][0]; mat[5][1] += a[5][1]; mat[5][2] += a[5][2]; mat[5][3] += a[5][3]; mat[5][4] += a[5][4]; mat[5][5] += a[5][5]; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator-=( const idMat6 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; mat[0][5] -= a[0][5]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; mat[1][5] -= a[1][5]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; mat[2][5] -= a[2][5]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; mat[3][5] -= a[3][5]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; mat[4][5] -= a[4][5]; + mat[5][0] -= a[5][0]; mat[5][1] -= a[5][1]; mat[5][2] -= a[5][2]; mat[5][3] -= a[5][3]; mat[5][4] -= a[5][4]; mat[5][5] -= a[5][5]; + return *this; +} + +ID_INLINE idVec6 operator*( const idVec6 &vec, const idMat6 &mat ) { + return mat * vec; +} + +ID_INLINE idMat6 operator*( const float a, idMat6 const &mat ) { + return mat * a; +} + +ID_INLINE idVec6 &operator*=( idVec6 &vec, const idMat6 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::operator==( const idMat6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat6::operator!=( const idMat6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat6::Zero( void ) { + memset( mat, 0, sizeof( idMat6 ) ); +} + +ID_INLINE void idMat6::Identity( void ) { + *this = mat6_identity; +} + +ID_INLINE bool idMat6::IsIdentity( const float epsilon ) const { + return Compare( mat6_identity, epsilon ); +} + +ID_INLINE bool idMat6::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 6; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat6::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 6; i++ ) { + for ( int j = 0; j < 6; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE idMat3 idMat6::SubMat3( int n ) const { + assert( n >= 0 && n < 4 ); + int b0 = ((n & 2) >> 1) * 3; + int b1 = (n & 1) * 3; + return idMat3( + mat[b0 + 0][b1 + 0], mat[b0 + 0][b1 + 1], mat[b0 + 0][b1 + 2], + mat[b0 + 1][b1 + 0], mat[b0 + 1][b1 + 1], mat[b0 + 1][b1 + 2], + mat[b0 + 2][b1 + 0], mat[b0 + 2][b1 + 1], mat[b0 + 2][b1 + 2] ); +} + +ID_INLINE float idMat6::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] + mat[5][5] ); +} + +ID_INLINE idMat6 idMat6::Inverse( void ) const { + idMat6 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat6 idMat6::InverseFast( void ) const { + idMat6 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat6::GetDimension( void ) const { + return 36; +} + +ID_INLINE const float *idMat6::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat6::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMatX - arbitrary sized dense real matrix +// +// The matrix lives on 16 byte aligned and 16 byte padded memory. +// +// NOTE: due to the temporary memory pool idMatX cannot be used by multiple threads. +// +//=============================================================== + +#define MATX_MAX_TEMP 1024 +#define MATX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define MATX_CLEAREND() int s = numRows * numColumns; while( s < ( ( s + 3 ) & ~3 ) ) { mat[s++] = 0.0f; } +#define MATX_ALLOCA( n ) ( (float *) _alloca16( MATX_QUAD( n ) ) ) +#define MATX_SIMD + +// RAVEN BEGIN +// jsinger: this is broken at the moment because idSIMDProcessor is no longer virtual +#ifdef _XENON +#undef MATX_SIMD +#endif +// RAVEN END +class idMatX { +public: + idMatX( void ); + explicit idMatX( int rows, int columns ); + explicit idMatX( int rows, int columns, float *src ); + ~idMatX( void ); + + void Set( int rows, int columns, const float *src ); + void Set( const idMat3 &m1, const idMat3 &m2 ); + void Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ); + + const float * operator[]( int index ) const; + float * operator[]( int index ); + idMatX & operator=( const idMatX &a ); + idMatX operator*( const float a ) const; + idVecX operator*( const idVecX &vec ) const; + idMatX operator*( const idMatX &a ) const; + idMatX operator+( const idMatX &a ) const; + idMatX operator-( const idMatX &a ) const; + idMatX & operator*=( const float a ); + idMatX & operator*=( const idMatX &a ); + idMatX & operator+=( const idMatX &a ); + idMatX & operator-=( const idMatX &a ); + + friend idMatX operator*( const float a, const idMatX &m ); + friend idVecX operator*( const idVecX &vec, const idMatX &m ); + friend idVecX & operator*=( idVecX &vec, const idMatX &m ); + + bool Compare( const idMatX &a ) const; // exact compare, no epsilon + bool Compare( const idMatX &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMatX &a ) const; // exact compare, no epsilon + bool operator!=( const idMatX &a ) const; // exact compare, no epsilon + + void SetSize( int rows, int columns ); // set the number of rows/columns + void ChangeSize( int rows, int columns, bool makeZero = false ); // change the size keeping data intact where possible + int GetNumRows( void ) const { return numRows; } // get the number of rows + int GetNumColumns( void ) const { return numColumns; } // get the number of columns + void SetData( int rows, int columns, float *data ); // set float array pointer + void Zero( void ); // clear matrix + void Zero( int rows, int columns ); // set size and clear matrix + void Identity( void ); // clear to identity matrix + void Identity( int rows, int columns ); // set size and clear to identity matrix + void Diag( const idVecX &v ); // create diagonal matrix from vector + void Random( int seed, float l = 0.0f, float u = 1.0f ); // fill matrix with random values + void Random( int rows, int columns, int seed, float l = 0.0f, float u = 1.0f ); + void Negate( void ); // (*this) = - (*this) + void Clamp( float min, float max ); // clamp all values + idMatX & SwapRows( int r1, int r2 ); // swap rows + idMatX & SwapColumns( int r1, int r2 ); // swap columns + idMatX & SwapRowsColumns( int r1, int r2 ); // swap rows and columns + idMatX & RemoveRow( int r ); // remove a row + idMatX & RemoveColumn( int r ); // remove a column + idMatX & RemoveRowColumn( int r ); // remove a row and column + void ClearUpperTriangle( void ); // clear the upper triangle + void ClearLowerTriangle( void ); // clear the lower triangle + void SquareSubMatrix( const idMatX &m, int size ); // get square sub-matrix from 0,0 to size,size + float MaxDifference( const idMatX &m ) const; // return maximum element difference between this and m + + bool IsSquare( void ) const { return ( numRows == numColumns ); } + bool IsZero( const float epsilon = MATRIX_EPSILON ) const; + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsTriDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthogonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthonormal( const float epsilon = MATRIX_EPSILON ) const; + bool IsPMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsZMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; // returns product of diagonal elements + float Determinant( void ) const; // returns determinant of matrix + idMatX Transpose( void ) const; // returns transpose + idMatX & TransposeSelf( void ); // transposes the matrix itself + idMatX Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMatX InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + bool LowerTriangularInverse( void ); // in-place inversion, returns false if determinant is zero + bool UpperTriangularInverse( void ); // in-place inversion, returns false if determinant is zero + + idVecX Multiply( const idVecX &vec ) const; // (*this) * vec + idVecX TransposeMultiply( const idVecX &vec ) const; // this->Transpose() * vec + + idMatX Multiply( const idMatX &a ) const; // (*this) * a + idMatX TransposeMultiply( const idMatX &a ) const; // this->Transpose() * a + + void Multiply( idVecX &dst, const idVecX &vec ) const; // dst = (*this) * vec + void MultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += (*this) * vec + void MultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= (*this) * vec + void TransposeMultiply( idVecX &dst, const idVecX &vec ) const; // dst = this->Transpose() * vec + void TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += this->Transpose() * vec + void TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= this->Transpose() * vec + + void Multiply( idMatX &dst, const idMatX &a ) const; // dst = (*this) * a + void TransposeMultiply( idMatX &dst, const idMatX &a ) const; // dst = this->Transpose() * a + + int GetDimension( void ) const; // returns total number of values in matrix + + const idVec6 & SubVec6( int row ) const; // interpret beginning of row as a const idVec6 + idVec6 & SubVec6( int row ); // interpret beginning of row as an idVec6 + const idVecX SubVecX( int row ) const; // interpret complete row as a const idVecX + idVecX SubVecX( int row ); // interpret complete row as an idVecX + const float * ToFloatPtr( void ) const; // pointer to const matrix float array + float * ToFloatPtr( void ); // pointer to matrix float array + const char * ToString( int precision = 2 ) const; + + void Update_RankOne( const idVecX &v, const idVecX &w, float alpha ); + void Update_RankOneSymmetric( const idVecX &v, float alpha ); + void Update_RowColumn( const idVecX &v, const idVecX &w, int r ); + void Update_RowColumnSymmetric( const idVecX &v, int r ); + void Update_Increment( const idVecX &v, const idVecX &w ); + void Update_IncrementSymmetric( const idVecX &v ); + void Update_Decrement( int r ); + + bool Inverse_GaussJordan( void ); // invert in-place with Gauss-Jordan elimination + bool Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ); + bool Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ); + bool Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ); + bool Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ); + void Inverse_Solve( idVecX &x, const idVecX &b ) const; + + bool LU_Factor( int *index, float *det = NULL ); // factor in-place: L * U + bool LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ); + bool LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ); + bool LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ); + bool LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ); + void LU_Solve( idVecX &x, const idVecX &b, const int *index ) const; + void LU_Inverse( idMatX &inv, const int *index ) const; + void LU_UnpackFactors( idMatX &L, idMatX &U ) const; + void LU_MultiplyFactors( idMatX &m, const int *index ) const; + + bool QR_Factor( idVecX &c, idVecX &d ); // factor in-place: Q * R + bool QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ); + bool QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ); + bool QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ); + bool QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ); + void QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const; + void QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const; + void QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const; + void QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const; + void QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const; + + bool SVD_Factor( idVecX &w, idMatX &V ); // factor in-place: U * Diag(w) * V.Transpose() + void SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const; + void SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const; + void SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const; + + bool Cholesky_Factor( void ); // factor in-place: L * L.Transpose() + bool Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool Cholesky_UpdateRowColumn( const idVecX &v, int r ); + bool Cholesky_UpdateIncrement( const idVecX &v ); + bool Cholesky_UpdateDecrement( const idVecX &v, int r ); + void Cholesky_Solve( idVecX &x, const idVecX &b ) const; + void Cholesky_Inverse( idMatX &inv ) const; + void Cholesky_MultiplyFactors( idMatX &m ) const; + + bool LDLT_Factor( void ); // factor in-place: L * D * L.Transpose() + bool LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool LDLT_UpdateRowColumn( const idVecX &v, int r ); + bool LDLT_UpdateIncrement( const idVecX &v ); + bool LDLT_UpdateDecrement( const idVecX &v, int r ); + void LDLT_Solve( idVecX &x, const idVecX &b ) const; + void LDLT_Inverse( idMatX &inv ) const; + void LDLT_UnpackFactors( idMatX &L, idMatX &D ) const; + void LDLT_MultiplyFactors( idMatX &m ) const; + + void TriDiagonal_ClearTriangles( void ); + bool TriDiagonal_Solve( idVecX &x, const idVecX &b ) const; + void TriDiagonal_Inverse( idMatX &inv ) const; + + bool Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ); + bool Eigen_SolveSymmetric( idVecX &eigenValues ); + bool Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ); + void Eigen_SortIncreasing( idVecX &eigenValues ); + void Eigen_SortDecreasing( idVecX &eigenValues ); + + static void Test( void ); + +private: + int numRows; // number of rows + int numColumns; // number of columns + int alloced; // floats allocated, if -1 then mat points to data set with SetData + float * mat; // memory the matrix is stored + +// RAVEN BEGIN +// jscott: avoid pointer hackery pokery and use the compiler + ALIGN16( static float tempPtr[MATX_MAX_TEMP] ); +// static float * tempPtr; // pointer to 16 byte aligned temporary memory +// RAVEN END + static int tempIndex; // index into memory pool, wraps around + +private: + void SetTempSize( int rows, int columns ); + float DeterminantGeneric( void ) const; + bool InverseSelfGeneric( void ); + void QR_Rotate( idMatX &R, int i, float a, float b ); + float Pythag( float a, float b ) const; + void SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ); + void SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ); + void HouseholderReduction( idVecX &diag, idVecX &subd ); + bool QL( idVecX &diag, idVecX &subd ); + void HessenbergReduction( idMatX &H ); + void ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ); + bool HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ); +}; + +ID_INLINE idMatX::idMatX( void ) { + numRows = numColumns = alloced = 0; + mat = NULL; +} + +ID_INLINE idMatX::~idMatX( void ) { + // if not temp memory + if ( mat != NULL && ( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( mat ); + } +} + +ID_INLINE idMatX::idMatX( int rows, int columns ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetSize( rows, columns ); +} + +ID_INLINE idMatX::idMatX( int rows, int columns, float *src ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetData( rows, columns, src ); +} + +ID_INLINE void idMatX::Set( int rows, int columns, const float *src ) { + SetSize( rows, columns ); + memcpy( this->mat, src, rows * columns * sizeof( float ) ); +} + +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2 ) { + int i, j; + + SetSize( 3, 6 ); + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + } + } +} + +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ) { + int i, j; + + SetSize( 6, 6 ); + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + mat[(i+3) * numColumns + (j+0)] = m3[i][j]; + mat[(i+3) * numColumns + (j+3)] = m4[i][j]; + } + } +} + +ID_INLINE const float *idMatX::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +ID_INLINE float *idMatX::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +ID_INLINE idMatX &idMatX::operator=( const idMatX &a ) { + SetSize( a.numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Copy16( mat, a.mat, a.numRows * a.numColumns ); +#else + memcpy( mat, a.mat, a.numRows * a.numColumns * sizeof( float ) ); +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX idMatX::operator*( const float a ) const { + idMatX m; + + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Mul16( m.mat, mat, a, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] * a; + } +#endif + return m; +} + +ID_INLINE idVecX idMatX::operator*( const idVecX &vec ) const { + idVecX dst; + + assert( numColumns == vec.GetSize() ); + + dst.SetTempSize( numRows ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + Multiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::operator*( const idMatX &a ) const { + idMatX dst; + + assert( numColumns == a.numRows ); + + dst.SetTempSize( numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + Multiply( dst, a ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::operator+( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Add16( m.mat, mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] + a.mat[i]; + } +#endif + return m; +} + +ID_INLINE idMatX idMatX::operator-( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Sub16( m.mat, mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] - a.mat[i]; + } +#endif + return m; +} + +ID_INLINE idMatX &idMatX::operator*=( const float a ) { +#ifdef MATX_SIMD + SIMDProcessor->MulAssign16( mat, a, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] *= a; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator*=( const idMatX &a ) { + *this = *this * a; + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator+=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->AddAssign16( mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] += a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator-=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->SubAssign16( mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] -= a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX operator*( const float a, idMatX const &m ) { + return m * a; +} + +ID_INLINE idVecX operator*( const idVecX &vec, const idMatX &m ) { + return m * vec; +} + +ID_INLINE idVecX &operator*=( idVecX &vec, const idMatX &m ) { + vec = m * vec; + return vec; +} + +ID_INLINE bool idMatX::Compare( const idMatX &a ) const { + int i, s; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( mat[i] != a.mat[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMatX::Compare( const idMatX &a, const float epsilon ) const { + int i, s; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( idMath::Fabs( mat[i] - a.mat[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMatX::operator==( const idMatX &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMatX::operator!=( const idMatX &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMatX::SetSize( int rows, int columns ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( mat != NULL ) { + Mem_Free16( mat ); + } +//RAVEN BEGIN +//amccarthy: Added allocation tag + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::SetTempSize( int rows, int columns ) { + int newSize; + + newSize = ( rows * columns + 3 ) & ~3; + assert( newSize < MATX_MAX_TEMP ); + if ( idMatX::tempIndex + newSize > MATX_MAX_TEMP ) { + idMatX::tempIndex = 0; + } + mat = idMatX::tempPtr + idMatX::tempIndex; + idMatX::tempIndex += newSize; + alloced = newSize; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::SetData( int rows, int columns, float *data ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + if ( mat != NULL && alloced != -1 ) { + Mem_Free16( mat ); + } + assert( ( ( (int) data ) & 15 ) == 0 ); // data must be 16 byte aligned + mat = data; + alloced = -1; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::Zero( void ) { +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, numRows * numColumns * sizeof( float ) ); +#endif +} + +ID_INLINE void idMatX::Zero( int rows, int columns ) { + SetSize( rows, columns ); +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, rows * columns * sizeof( float ) ); +#endif +} + +ID_INLINE void idMatX::Identity( void ) { + assert( numRows == numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, numRows * numColumns * sizeof( float ) ); +#endif + for ( int i = 0; i < numRows; i++ ) { + mat[i * numColumns + i] = 1.0f; + } +} + +ID_INLINE void idMatX::Identity( int rows, int columns ) { + assert( rows == columns ); + SetSize( rows, columns ); + idMatX::Identity(); +} + +ID_INLINE void idMatX::Diag( const idVecX &v ) { + Zero( v.GetSize(), v.GetSize() ); + for ( int i = 0; i < v.GetSize(); i++ ) { + mat[i * numColumns + i] = v[i]; + } +} + +ID_INLINE void idMatX::Random( int seed, float l, float u ) { + int i, s; + float c; + idRandom rnd(seed); + + c = u - l; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idMatX::Random( int rows, int columns, int seed, float l, float u ) { + int i, s; + float c; + idRandom rnd(seed); + + SetSize( rows, columns ); + c = u - l; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idMatX::Negate( void ) { +#ifdef MATX_SIMD + SIMDProcessor->Negate16( mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = -mat[i]; + } +#endif +} + +ID_INLINE void idMatX::Clamp( float min, float max ) { + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( mat[i] < min ) { + mat[i] = min; + } else if ( mat[i] > max ) { + mat[i] = max; + } + } +} + +ID_INLINE idMatX &idMatX::SwapRows( int r1, int r2 ) { + float *ptr; + + ptr = (float *) _alloca16( numColumns * sizeof( float ) ); + memcpy( ptr, mat + r1 * numColumns, numColumns * sizeof( float ) ); + memcpy( mat + r1 * numColumns, mat + r2 * numColumns, numColumns * sizeof( float ) ); + memcpy( mat + r2 * numColumns, ptr, numColumns * sizeof( float ) ); + + return *this; +} + +ID_INLINE idMatX &idMatX::SwapColumns( int r1, int r2 ) { + int i; + float tmp, *ptr; + + for ( i = 0; i < numRows; i++ ) { + ptr = mat + i * numColumns; + tmp = ptr[r1]; + ptr[r1] = ptr[r2]; + ptr[r2] = tmp; + } + + return *this; +} + +ID_INLINE idMatX &idMatX::SwapRowsColumns( int r1, int r2 ) { + + SwapRows( r1, r2 ); + SwapColumns( r1, r2 ); + return *this; +} + +ID_INLINE void idMatX::ClearUpperTriangle( void ) { + assert( numRows == numColumns ); + for ( int i = numRows-2; i >= 0; i-- ) { + memset( mat + i * numColumns + i + 1, 0, (numColumns - 1 - i) * sizeof(float) ); + } +} + +ID_INLINE void idMatX::ClearLowerTriangle( void ) { + assert( numRows == numColumns ); + for ( int i = 1; i < numRows; i++ ) { + memset( mat + i * numColumns, 0, i * sizeof(float) ); + } +} + +ID_INLINE void idMatX::SquareSubMatrix( const idMatX &m, int size ) { + int i; + assert( size <= m.numRows && size <= m.numColumns ); + SetSize( size, size ); + for ( i = 0; i < size; i++ ) { + memcpy( mat + i * numColumns, m.mat + i * m.numColumns, size * sizeof( float ) ); + } +} + +ID_INLINE float idMatX::MaxDifference( const idMatX &m ) const { + int i, j; + float diff, maxDiff; + + assert( numRows == m.numRows && numColumns == m.numColumns ); + + maxDiff = -1.0f; + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + diff = idMath::Fabs( mat[ i * numColumns + j ] - m[i][j] ); + if ( maxDiff < 0.0f || diff > maxDiff ) { + maxDiff = diff; + } + } + } + return maxDiff; +} + +ID_INLINE bool idMatX::IsZero( const float epsilon ) const { + // returns true if (*this) == Zero + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsIdentity( const float epsilon ) const { + // returns true if (*this) == Identity + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] - (float)( i == j ) ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( i != j && idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsTriDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal plus or minus one column + + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows-2; i++ ) { + for ( int j = i+2; j < numColumns; j++ ) { + if ( idMath::Fabs( (*this)[i][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( (*this)[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsSymmetric( const float epsilon ) const { + // (*this)[i][j] == (*this)[j][i] + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[ i * numColumns + j ] - mat[ j * numColumns + i ] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE float idMatX::Trace( void ) const { + float trace = 0.0f; + + assert( numRows == numColumns ); + + // sum of elements on the diagonal + for ( int i = 0; i < numRows; i++ ) { + trace += mat[i * numRows + i]; + } + return trace; +} + +ID_INLINE float idMatX::Determinant( void ) const { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + return mat[0]; + case 2: + return reinterpret_cast(mat)->Determinant(); + case 3: + return reinterpret_cast(mat)->Determinant(); + case 4: + return reinterpret_cast(mat)->Determinant(); + case 5: + return reinterpret_cast(mat)->Determinant(); + case 6: + return reinterpret_cast(mat)->Determinant(); + default: + return DeterminantGeneric(); + } + return 0.0f; +} + +ID_INLINE idMatX idMatX::Transpose( void ) const { + idMatX transpose; + int i, j; + + transpose.SetTempSize( numColumns, numRows ); + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + transpose.mat[j * transpose.numColumns + i] = mat[i * numColumns + j]; + } + } + + return transpose; +} + +ID_INLINE idMatX &idMatX::TransposeSelf( void ) { + *this = Transpose(); + return *this; +} + +ID_INLINE idMatX idMatX::Inverse( void ) const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE bool idMatX::InverseSelf( void ) { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseSelf(); + case 3: + return reinterpret_cast(mat)->InverseSelf(); + case 4: + return reinterpret_cast(mat)->InverseSelf(); + case 5: + return reinterpret_cast(mat)->InverseSelf(); + case 6: + return reinterpret_cast(mat)->InverseSelf(); + default: + return InverseSelfGeneric(); + } +} + +ID_INLINE idMatX idMatX::InverseFast( void ) const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE bool idMatX::InverseFastSelf( void ) { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseFastSelf(); + case 3: + return reinterpret_cast(mat)->InverseFastSelf(); + case 4: + return reinterpret_cast(mat)->InverseFastSelf(); + case 5: + return reinterpret_cast(mat)->InverseFastSelf(); + case 6: + return reinterpret_cast(mat)->InverseFastSelf(); + default: + return InverseSelfGeneric(); + } +//unreachable +//return false; +} + +ID_INLINE idVecX idMatX::Multiply( const idVecX &vec ) const { + idVecX dst; + + assert( numColumns == vec.GetSize() ); + + dst.SetTempSize( numRows ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + Multiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::Multiply( const idMatX &a ) const { + idMatX dst; + + assert( numColumns == a.numRows ); + + dst.SetTempSize( numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + Multiply( dst, a ); +#endif + return dst; +} + +ID_INLINE idVecX idMatX::TransposeMultiply( const idVecX &vec ) const { + idVecX dst; + + assert( numRows == vec.GetSize() ); + + dst.SetTempSize( numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyVecX( dst, *this, vec ); +#else + TransposeMultiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::TransposeMultiply( const idMatX &a ) const { + idMatX dst; + + assert( numRows == a.numRows ); + + dst.SetTempSize( numColumns, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyMatX( dst, *this, a ); +#else + TransposeMultiply( dst, a ); +#endif + return dst; +} + +ID_INLINE void idMatX::Multiply( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] = sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::MultiplyAdd( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyAddVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] += sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::MultiplySub( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplySubVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] -= sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiply( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] = sum; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyAddVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] += sum; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplySubVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] -= sum; + } +#endif +} + +ID_INLINE void idMatX::Multiply( idMatX &dst, const idMatX &a ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( numColumns == a.numRows ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = ToFloatPtr(); + m2Ptr = a.ToFloatPtr(); + k = numRows; + l = a.GetNumColumns(); + + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = a.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < numColumns; n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiply( idMatX &dst, const idMatX &a ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyMatX( dst, *this, a ); +#else + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( numRows == a.numRows ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = ToFloatPtr(); + k = numColumns; + l = a.numColumns; + + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = ToFloatPtr() + i; + m2Ptr = a.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < numRows; n++ ) { + m1Ptr += numColumns; + m2Ptr += a.numColumns; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } +#endif +} + +ID_INLINE int idMatX::GetDimension( void ) const { + return numRows * numColumns; +} + +ID_INLINE const idVec6 &idMatX::SubVec6( int row ) const { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +ID_INLINE idVec6 &idMatX::SubVec6( int row ) { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +ID_INLINE const idVecX idMatX::SubVecX( int row ) const { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +ID_INLINE idVecX idMatX::SubVecX( int row ) { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +ID_INLINE const float *idMatX::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idMatX::ToFloatPtr( void ) { + return mat; +} + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/source/idlib/math/Ode.cpp b/source/idlib/math/Ode.cpp new file mode 100644 index 0000000..796f804 --- /dev/null +++ b/source/idlib/math/Ode.cpp @@ -0,0 +1,328 @@ + +#include "../precompiled.h" +#pragma hdrstop + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +/* +============= +idODE_Euler::idODE_Euler +============= +*/ +idODE_Euler::idODE_Euler( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derivatives = new float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Euler::~idODE_Euler +============= +*/ +idODE_Euler::~idODE_Euler( void ) { + delete[] derivatives; +} + +/* +============= +idODE_Euler::Evaluate +============= +*/ +float idODE_Euler::Evaluate( const float *state, float *newState, float t0, float t1 ) { + float delta; + int i; + + derive( t0, userData, state, derivatives ); + delta = t1 - t0; + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +/* +============= +idODE_Midpoint::idODE_Midpoint +============= +*/ +idODE_Midpoint::idODE_Midpoint( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + tmpState = new float[dim]; + derivatives = new float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Midpoint::~idODE_Midpoint +============= +*/ +idODE_Midpoint::~idODE_Midpoint( void ) { + delete tmpState; + delete derivatives; +} + +/* +============= +idODE_Midpoint::~Evaluate +============= +*/ +float idODE_Midpoint::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, derivatives ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * derivatives[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, derivatives ); + + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +/* +============= +idODE_RK4::idODE_RK4 +============= +*/ +idODE_RK4::idODE_RK4( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + tmpState = new float[dim]; + d1 = new float[dim]; + d2 = new float[dim]; + d3 = new float[dim]; + d4 = new float[dim]; +} + +/* +============= +idODE_RK4::~idODE_RK4 +============= +*/ +idODE_RK4::~idODE_RK4( void ) { + delete tmpState; + delete d1; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4::Evaluate +============= +*/ +float idODE_RK4::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, sixthDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + return delta; +} + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +/* +============= +idODE_RK4Adaptive::idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::idODE_RK4Adaptive( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + maxError = 0.01f; + tmpState = new float[dim]; + d1 = new float[dim]; + d1half = new float [dim]; + d2 = new float[dim]; + d3 = new float[dim]; + d4 = new float[dim]; +} + +/* +============= +idODE_RK4Adaptive::~idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::~idODE_RK4Adaptive( void ) { + delete tmpState; + delete d1; + delete d1half; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4Adaptive::SetMaxError +============= +*/ +void idODE_RK4Adaptive::SetMaxError( const float err ) { + if ( err > 0.0f ) { + maxError = err; + } +} + +/* +============= +idODE_RK4Adaptive::Evaluate +============= +*/ +float idODE_RK4Adaptive::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, fourthDelta, sixthDelta; + double error, max; + int i, n; + + delta = t1 - t0; + + for ( n = 0; n < 4; n++ ) { + + halfDelta = delta * 0.5; + fourthDelta = delta * 0.25; + + // first step of first half delta + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1[i]; + } + // second step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of first half delta + derive( t0 + halfDelta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of second half delta + derive( t0 + halfDelta, userData, tmpState, d1half ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1half[i]; + } + // second step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of second half delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of full delta + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step of full delta + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step of full delta + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step of full delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // get max estimated error + max = 0.0; + for ( i = 0; i < dimension; i++ ) { + error = idMath::Fabs( (newState[i] - tmpState[i]) / (delta * d1[i] + 1e-10) ); + if ( error > max ) { + max = error; + } + } + error = max / maxError; + + if ( error <= 1.0f ) { + return delta * 4.0; + } + if ( delta <= 1e-7 ) { + return delta; + } + delta *= 0.25; + } + return delta; +} + diff --git a/source/idlib/math/Ode.h b/source/idlib/math/Ode.h new file mode 100644 index 0000000..7db84f0 --- /dev/null +++ b/source/idlib/math/Ode.h @@ -0,0 +1,119 @@ + +#ifndef __MATH_ODE_H__ +#define __MATH_ODE_H__ + +/* +=============================================================================== + + Numerical solvers for ordinary differential equations. + +=============================================================================== +*/ + + +//=============================================================== +// +// idODE +// +//=============================================================== + +typedef void (*deriveFunction_t)( const float t, const void *userData, const float *state, float *derivatives ); + +class idODE { + +public: + virtual ~idODE( void ) {} + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ) = 0; + +protected: + int dimension; // dimension in floats allocated for + deriveFunction_t derive; // derive function + const void * userData; // client data +}; + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +class idODE_Euler : public idODE { + +public: + idODE_Euler( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Euler( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +class idODE_Midpoint : public idODE { + +public: + idODE_Midpoint( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Midpoint( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +class idODE_RK4 : public idODE { + +public: + idODE_RK4( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * d1; // derivatives + float * d2; + float * d3; + float * d4; +}; + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +class idODE_RK4Adaptive : public idODE { + +public: + idODE_RK4Adaptive( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4Adaptive( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + void SetMaxError( const float err ); + +protected: + float maxError; // maximum allowed error + float * tmpState; + float * d1; // derivatives + float * d1half; + float * d2; + float * d3; + float * d4; +}; + +#endif /* !__MATH_ODE_H__ */ diff --git a/source/idlib/math/Plane.cpp b/source/idlib/math/Plane.cpp new file mode 100644 index 0000000..f3f5906 --- /dev/null +++ b/source/idlib/math/Plane.cpp @@ -0,0 +1,127 @@ + +#include "../precompiled.h" +#pragma hdrstop + +idPlane plane_origin( 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPlane::Type +================ +*/ +int idPlane::Type( void ) const { + if ( Normal()[0] == 0.0f ) { + if ( Normal()[1] == 0.0f ) { + return Normal()[2] > 0.0f ? PLANETYPE_Z : PLANETYPE_NEGZ; + } + else if ( Normal()[2] == 0.0f ) { + return Normal()[1] > 0.0f ? PLANETYPE_Y : PLANETYPE_NEGY; + } + else { + return PLANETYPE_ZEROX; + } + } + else if ( Normal()[1] == 0.0f ) { + if ( Normal()[2] == 0.0f ) { + return Normal()[0] > 0.0f ? PLANETYPE_X : PLANETYPE_NEGX; + } + else { + return PLANETYPE_ZEROY; + } + } + else if ( Normal()[2] == 0.0f ) { + return PLANETYPE_ZEROZ; + } + else { + return PLANETYPE_NONAXIAL; + } +} + +/* +================ +idPlane::HeightFit +================ +*/ +bool idPlane::HeightFit( const idVec3 *points, const int numPoints ) { + int i; + float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f; + float sumYY = 0.0f, sumYZ = 0.0f; + idVec3 sum, average, dir; + + if ( numPoints == 1 ) { + a = 0.0f; + b = 0.0f; + c = 1.0f; + d = -points[0].z; + return true; + } + if ( numPoints == 2 ) { + dir = points[1] - points[0]; + Normal() = dir.Cross( idVec3( 0, 0, 1 ) ).Cross( dir ); + Normalize(); + d = -( Normal() * points[0] ); + return true; + } + + sum.Zero(); + for ( i = 0; i < numPoints; i++) { + sum += points[i]; + } + average = sum / numPoints; + + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - average; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + } + + idMat2 m( sumXX, sumXY, sumXY, sumYY ); + if ( !m.InverseSelf() ) { + return false; + } + + a = - sumXZ * m[0][0] - sumYZ * m[0][1]; + b = - sumXZ * m[1][0] - sumYZ * m[1][1]; + c = 1.0f; + Normalize(); + d = -( a * average.x + b * average.y + c * average.z ); + return true; +} + +/* +================ +idPlane::PlaneIntersection +================ +*/ +bool idPlane::PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const { + double n00, n01, n11, det, invDet, f0, f1; + + n00 = Normal().LengthSqr(); + n01 = Normal() * plane.Normal(); + n11 = plane.Normal().LengthSqr(); + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane.d - n11 * d ) * invDet; + f1 = ( n01 * d - n00 * plane.d ) * invDet; + + dir = Normal().Cross( plane.Normal() ); + start = f0 * Normal() + f1 * plane.Normal(); + return true; +} + +/* +============= +idPlane::ToString +============= +*/ +const char *idPlane::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Plane.h b/source/idlib/math/Plane.h new file mode 100644 index 0000000..ea43192 --- /dev/null +++ b/source/idlib/math/Plane.h @@ -0,0 +1,364 @@ + +#ifndef __MATH_PLANE_H__ +#define __MATH_PLANE_H__ + +/* +=============================================================================== + + 3D plane with equation: a * x + b * y + c * z + d = 0 + +=============================================================================== +*/ + + +class idVec3; +class idMat3; + +#define ON_EPSILON 0.1f +#define DEGENERATE_DIST_EPSILON 1e-4f + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +// plane sides +#define PLANESIDE_FRONT 0 +#define PLANESIDE_BACK 1 +#define PLANESIDE_ON 2 +#define PLANESIDE_CROSS 3 + +// plane types +#define PLANETYPE_X 0 +#define PLANETYPE_Y 1 +#define PLANETYPE_Z 2 +#define PLANETYPE_NEGX 3 +#define PLANETYPE_NEGY 4 +#define PLANETYPE_NEGZ 5 +#define PLANETYPE_TRUEAXIAL 6 // all types < 6 are true axial planes +#define PLANETYPE_ZEROX 6 +#define PLANETYPE_ZEROY 7 +#define PLANETYPE_ZEROZ 8 +#define PLANETYPE_NONAXIAL 9 + +class idPlane { +public: + idPlane( void ); + idPlane( float a, float b, float c, float d ); + idPlane( const idVec3 &normal, const float dist ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idPlane operator-() const; // flips plane + idPlane & operator=( const idVec3 &v ); // sets normal and sets idPlane::d to zero + idPlane operator+( const idPlane &p ) const; // add plane equations + idPlane operator-( const idPlane &p ) const; // subtract plane equations + idPlane & operator*=( const idMat3 &m ); // Normal() *= m + + bool Compare( const idPlane &p ) const; // exact compare, no epsilon + bool Compare( const idPlane &p, const float epsilon ) const; // compare with epsilon + bool Compare( const idPlane &p, const float normalEps, const float distEps ) const; // compare with epsilon + bool operator==( const idPlane &p ) const; // exact compare, no epsilon + bool operator!=( const idPlane &p ) const; // exact compare, no epsilon + + void Zero( void ); // zero plane + void SetNormal( const idVec3 &normal ); // sets the normal + const idVec3 & Normal( void ) const; // reference to const normal + idVec3 & Normal( void ); // reference to normal + float Normalize( bool fixDegenerate = true ); // only normalizes the plane normal, does not adjust d + bool FixDegenerateNormal( void ); // fix degenerate normal + bool FixDegeneracies( float distEpsilon ); // fix degenerate normal and dist + float Dist( void ) const; // returns: -d + void SetDist( const float dist ); // sets: d = -dist + int Type( void ) const; // returns plane type + + bool FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate = true ); + bool FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate = true ); + void FitThroughPoint( const idVec3 &p ); // assumes normal is valid + bool HeightFit( const idVec3 *points, const int numPoints ); + idPlane Translate( const idVec3 &translation ) const; + idPlane & TranslateSelf( const idVec3 &translation ); + idPlane Rotate( const idVec3 &origin, const idMat3 &axis ) const; + idPlane & RotateSelf( const idVec3 &origin, const idMat3 &axis ); + + float Distance( const idVec3 &v ) const; + int Side( const idVec3 &v, const float epsilon = 0.0f ) const; + + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + bool PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const; + + int GetDimension( void ) const; + + const idVec4 & ToVec4( void ) const; + idVec4 & ToVec4( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float a; + float b; + float c; + float d; +}; + +extern idPlane plane_origin; +#define plane_zero plane_origin + +ID_INLINE idPlane::idPlane( void ) { +} + +ID_INLINE idPlane::idPlane( float a, float b, float c, float d ) { + this->a = a; + this->b = b; + this->c = c; + this->d = d; +} + +ID_INLINE idPlane::idPlane( const idVec3 &normal, const float dist ) { + this->a = normal.x; + this->b = normal.y; + this->c = normal.z; + this->d = -dist; +} + +ID_INLINE float idPlane::operator[]( int index ) const { + return ( &a )[ index ]; +} + +ID_INLINE float& idPlane::operator[]( int index ) { + return ( &a )[ index ]; +} + +ID_INLINE idPlane idPlane::operator-() const { + return idPlane( -a, -b, -c, -d ); +} + +ID_INLINE idPlane &idPlane::operator=( const idVec3 &v ) { + a = v.x; + b = v.y; + c = v.z; + d = 0; + return *this; +} + +ID_INLINE idPlane idPlane::operator+( const idPlane &p ) const { + return idPlane( a + p.a, b + p.b, c + p.c, d + p.d ); +} + +ID_INLINE idPlane idPlane::operator-( const idPlane &p ) const { + return idPlane( a - p.a, b - p.b, c - p.c, d - p.d ); +} + +ID_INLINE idPlane &idPlane::operator*=( const idMat3 &m ) { + Normal() *= m; + return *this; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p ) const { + return ( a == p.a && b == p.b && c == p.c && d == p.d ); +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float epsilon ) const { + if ( idMath::Fabs( a - p.a ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( b - p.b ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( c - p.c ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( d - p.d ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float normalEps, const float distEps ) const { + if ( idMath::Fabs( d - p.d ) > distEps ) { + return false; + } + if ( !Normal().Compare( p.Normal(), normalEps ) ) { + return false; + } + return true; +} + +ID_INLINE bool idPlane::operator==( const idPlane &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPlane::operator!=( const idPlane &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPlane::Zero( void ) { + a = b = c = d = 0.0f; +} + +ID_INLINE void idPlane::SetNormal( const idVec3 &normal ) { + a = normal.x; + b = normal.y; + c = normal.z; +} + +ID_INLINE const idVec3 &idPlane::Normal( void ) const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec3 &idPlane::Normal( void ) { + return *reinterpret_cast(&a); +} + +ID_INLINE float idPlane::Normalize( bool fixDegenerate ) { + float length = reinterpret_cast(&a)->Normalize(); + + if ( fixDegenerate ) { + FixDegenerateNormal(); + } + return length; +} + +ID_INLINE bool idPlane::FixDegenerateNormal( void ) { + return Normal().FixDegenerateNormal(); +} + +ID_INLINE bool idPlane::FixDegeneracies( float distEpsilon ) { + bool fixedNormal = FixDegenerateNormal(); + // only fix dist if the normal was degenerate + if ( fixedNormal ) { + if ( idMath::Fabs( d - idMath::Rint( d ) ) < distEpsilon ) { + d = idMath::Rint( d ); + } + } + return fixedNormal; +} + +ID_INLINE float idPlane::Dist( void ) const { + return -d; +} + +ID_INLINE void idPlane::SetDist( const float dist ) { + d = -dist; +} + +ID_INLINE bool idPlane::FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate ) { + Normal() = (p1 - p2).Cross( p3 - p2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p2 ); + return true; +} + +ID_INLINE bool idPlane::FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate ) { + Normal() = dir1.Cross( dir2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p ); + return true; +} + +ID_INLINE void idPlane::FitThroughPoint( const idVec3 &p ) { + d = -( Normal() * p ); +} + +ID_INLINE idPlane idPlane::Translate( const idVec3 &translation ) const { + return idPlane( a, b, c, d - translation * Normal() ); +} + +ID_INLINE idPlane &idPlane::TranslateSelf( const idVec3 &translation ) { + d -= translation * Normal(); + return *this; +} + +ID_INLINE idPlane idPlane::Rotate( const idVec3 &origin, const idMat3 &axis ) const { + idPlane p; + p.Normal() = Normal() * axis; + p.d = d + origin * Normal() - origin * p.Normal(); + return p; +} + +ID_INLINE idPlane &idPlane::RotateSelf( const idVec3 &origin, const idMat3 &axis ) { + d += origin * Normal(); + Normal() *= axis; + d -= origin * Normal(); + return *this; +} + +ID_INLINE float idPlane::Distance( const idVec3 &v ) const { + return a * v.x + b * v.y + c * v.z + d; +} + +ID_INLINE int idPlane::Side( const idVec3 &v, const float epsilon ) const { + float dist = Distance( v ); + if ( dist > epsilon ) { + return PLANESIDE_FRONT; + } + else if ( dist < -epsilon ) { + return PLANESIDE_BACK; + } + else { + return PLANESIDE_ON; + } +} + +ID_INLINE bool idPlane::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float d1, d2, fraction; + + d1 = Normal() * start + d; + d2 = Normal() * end + d; + if ( d1 == d2 ) { + return false; + } + if ( d1 > 0.0f && d2 > 0.0f ) { + return false; + } + if ( d1 < 0.0f && d2 < 0.0f ) { + return false; + } + fraction = ( d1 / ( d1 - d2 ) ); + return ( fraction >= 0.0f && fraction <= 1.0f ); +} + +ID_INLINE bool idPlane::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + float d1, d2; + + d1 = Normal() * start + d; + d2 = Normal() * dir; + if ( d2 == 0.0f ) { + return false; + } + scale = -( d1 / d2 ); + return true; +} + +ID_INLINE int idPlane::GetDimension( void ) const { + return 4; +} + +ID_INLINE const idVec4 &idPlane::ToVec4( void ) const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec4 &idPlane::ToVec4( void ) { + return *reinterpret_cast(&a); +} + +ID_INLINE const float *idPlane::ToFloatPtr( void ) const { + return reinterpret_cast(&a); +} + +ID_INLINE float *idPlane::ToFloatPtr( void ) { + return reinterpret_cast(&a); +} + +#endif /* !__MATH_PLANE_H__ */ diff --git a/source/idlib/math/Pluecker.cpp b/source/idlib/math/Pluecker.cpp new file mode 100644 index 0000000..d198278 --- /dev/null +++ b/source/idlib/math/Pluecker.cpp @@ -0,0 +1,59 @@ + +#include "../precompiled.h" +#pragma hdrstop + +idPluecker pluecker_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPluecker::FromPlanes + + pluecker coordinate for the intersection of two planes +================ +*/ +bool idPluecker::FromPlanes( const idPlane &p1, const idPlane &p2 ) { + + p[0] = -( p1[2] * -p2[3] - p2[2] * -p1[3] ); + p[1] = -( p2[1] * -p1[3] - p1[1] * -p2[3] ); + p[2] = p1[1] * p2[2] - p2[1] * p1[2]; + + p[3] = -( p1[0] * -p2[3] - p2[0] * -p1[3] ); + p[4] = p1[0] * p2[1] - p2[0] * p1[1]; + p[5] = p1[0] * p2[2] - p2[0] * p1[2]; + + return ( p[2] != 0.0f || p[5] != 0.0f || p[4] != 0.0f ); +} + +/* +================ +idPluecker::Distance3DSqr + + calculates square of shortest distance between the two + 3D lines represented by their pluecker coordinates +================ +*/ +float idPluecker::Distance3DSqr( const idPluecker &a ) const { + float d, s; + idVec3 dir; + + dir[0] = -a.p[5] * p[4] - a.p[4] * -p[5]; + dir[1] = a.p[4] * p[2] - a.p[2] * p[4]; + dir[2] = a.p[2] * -p[5] - -a.p[5] * p[2]; + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + return -1.0f; // FIXME: implement for parallel lines + } + d = a.p[4] * ( p[2] * dir[1] - -p[5] * dir[0] ) + + a.p[5] * ( p[2] * dir[2] - p[4] * dir[0] ) + + a.p[2] * ( -p[5] * dir[2] - p[4] * dir[1] ); + s = PermutedInnerProduct( a ) / d; + return ( dir * dir ) * ( s * s ); +} + +/* +============= +idPluecker::ToString +============= +*/ +const char *idPluecker::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Pluecker.h b/source/idlib/math/Pluecker.h new file mode 100644 index 0000000..07b3a64 --- /dev/null +++ b/source/idlib/math/Pluecker.h @@ -0,0 +1,382 @@ + +#ifndef __MATH_PLUECKER_H__ +#define __MATH_PLUECKER_H__ + +/* +=============================================================================== + + Pluecker coordinate + +=============================================================================== +*/ + +class idPluecker { +public: + idPluecker( void ); + explicit idPluecker( const float *a ); + explicit idPluecker( const idVec3 &start, const idVec3 &end ); + explicit idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPluecker operator-() const; // flips the direction + idPluecker operator*( const float a ) const; + idPluecker operator/( const float a ) const; + float operator*( const idPluecker &a ) const; // permuted inner product + idPluecker operator-( const idPluecker &a ) const; + idPluecker operator+( const idPluecker &a ) const; + idPluecker & operator*=( const float a ); + idPluecker & operator/=( const float a ); + idPluecker & operator+=( const idPluecker &a ); + idPluecker & operator-=( const idPluecker &a ); + + bool Compare( const idPluecker &a ) const; // exact compare, no epsilon + bool Compare( const idPluecker &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idPluecker &a ) const; // exact compare, no epsilon + bool operator!=( const idPluecker &a ) const; // exact compare, no epsilon + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero( void ); + + idPluecker Rotate( const idMat3 &rotation ) const; + idPluecker Translate( const idVec3 &translation ) const; + idPluecker TranslateAndRotate( const idVec3 &translation, const idMat3 &rotation ) const; + + void FromLine( const idVec3 &start, const idVec3 &end ); // pluecker from line + void FromRay( const idVec3 &start, const idVec3 &dir ); // pluecker from ray + bool FromPlanes( const idPlane &p1, const idPlane &p2 ); // pluecker from intersection of planes + bool ToLine( idVec3 &start, idVec3 &end ) const; // pluecker to line + bool ToRay( idVec3 &start, idVec3 &dir ) const; // pluecker to ray + void ToDir( idVec3 &dir ) const; // pluecker to direction + float PermutedInnerProduct( const idPluecker &a ) const; // pluecker permuted inner product + float Distance3DSqr( const idPluecker &a ) const; // pluecker line distance + + float Length( void ) const; // pluecker length + float LengthSqr( void ) const; // pluecker squared length + idPluecker Normalize( void ) const; // pluecker normalize + float NormalizeSelf( void ); // pluecker normalize + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idPluecker pluecker_origin; +#define pluecker_zero pluecker_origin + +ID_INLINE idPluecker::idPluecker( void ) { +} + +ID_INLINE idPluecker::idPluecker( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idPluecker::idPluecker( const idVec3 &start, const idVec3 &end ) { + FromLine( start, end ); +} + +ID_INLINE idPluecker::idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idPluecker idPluecker::operator-() const { + return idPluecker( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idPluecker::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idPluecker::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idPluecker idPluecker::operator*( const float a ) const { + return idPluecker( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idPluecker::operator*( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE idPluecker idPluecker::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idPluecker( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idPluecker idPluecker::operator+( const idPluecker &a ) const { + return idPluecker( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idPluecker idPluecker::operator-( const idPluecker &a ) const { + return idPluecker( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idPluecker &idPluecker::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator+=( const idPluecker &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator-=( const idPluecker &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPluecker::operator==( const idPluecker &a ) const { + return Compare( a ); +} + +ID_INLINE bool idPluecker::operator!=( const idPluecker &a ) const { + return !Compare( a ); +} + +ID_INLINE void idPluecker::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idPluecker::Zero( void ) { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE idPluecker idPluecker::Rotate( const idMat3 &rotation ) const { + idPluecker result; + result[0] = rotation[ 0 ].z * p[3] - rotation[ 1 ].z * p[1] + rotation[ 2 ].z * p[0]; + result[1] = - rotation[ 0 ].y * p[3] + rotation[ 1 ].y * p[1] - rotation[ 2 ].y * p[0]; + result[2] = rotation[ 0 ].x * p[2] - rotation[ 1 ].x * p[5] + rotation[ 2 ].x * p[4]; + result[3] = rotation[ 0 ].x * p[3] - rotation[ 1 ].x * p[1] + rotation[ 2 ].x * p[0]; + result[4] = rotation[ 0 ].z * p[2] - rotation[ 1 ].z * p[5] + rotation[ 2 ].z * p[4]; + result[5] = - rotation[ 0 ].y * p[2] + rotation[ 1 ].y * p[5] - rotation[ 2 ].y * p[4]; + return result; +} + +ID_INLINE idPluecker idPluecker::Translate( const idVec3 &translation ) const { + idPluecker result; + result[0] = p[0] + translation[0] * p[5] + p[2] * translation[1]; + result[1] = p[1] - translation[0] * p[4] + p[2] * translation[2]; + result[2] = p[2]; + result[3] = p[3] - translation[1] * p[4] - p[5] * translation[2]; + result[4] = p[4]; + result[5] = p[5]; + return result; +} + +ID_INLINE idPluecker idPluecker::TranslateAndRotate( const idVec3 &translation, const idMat3 &rotation ) const { + idVec3 t; + idPluecker result; + t[0] = p[0] + translation[0] * p[5] + p[2] * translation[1]; + t[1] = p[1] - translation[0] * p[4] + p[2] * translation[2]; + t[2] = p[3] - translation[1] * p[4] - p[5] * translation[2]; + result[0] = rotation[ 0 ].z * t[2] - rotation[ 1 ].z * t[1] + rotation[ 2 ].z * t[0]; + result[1] = - rotation[ 0 ].y * t[2] + rotation[ 1 ].y * t[1] - rotation[ 2 ].y * t[0]; + result[2] = rotation[ 0 ].x * p[2] - rotation[ 1 ].x * p[5] + rotation[ 2 ].x * p[4]; + result[3] = rotation[ 0 ].x * t[2] - rotation[ 1 ].x * t[1] + rotation[ 2 ].x * t[0]; + result[4] = rotation[ 0 ].z * p[2] - rotation[ 1 ].z * p[5] + rotation[ 2 ].z * p[4]; + result[5] = - rotation[ 0 ].y * p[2] + rotation[ 1 ].y * p[5] - rotation[ 2 ].y * p[4]; + return result; +} + +ID_INLINE void idPluecker::FromLine( const idVec3 &start, const idVec3 &end ) { + p[0] = start[0] * end[1] - end[0] * start[1]; + p[1] = start[0] * end[2] - end[0] * start[2]; + p[2] = start[0] - end[0]; + p[3] = start[1] * end[2] - end[1] * start[2]; + p[4] = start[2] - end[2]; + p[5] = end[1] - start[1]; +} + +ID_INLINE void idPluecker::FromRay( const idVec3 &start, const idVec3 &dir ) { + p[0] = start[0] * dir[1] - dir[0] * start[1]; + p[1] = start[0] * dir[2] - dir[0] * start[2]; + p[2] = -dir[0]; + p[3] = start[1] * dir[2] - dir[1] * start[2]; + p[4] = -dir[2]; + p[5] = dir[1]; +} + +ID_INLINE bool idPluecker::ToLine( idVec3 &start, idVec3 &end ) const { + idVec3 dir1, dir2; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir2[0] = -p[2]; + dir2[1] = p[5]; + dir2[2] = -p[4]; + + d = dir2 * dir2; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir2.Cross(dir1) * (1.0f / d); + end = start + dir2; + return true; +} + +ID_INLINE bool idPluecker::ToRay( idVec3 &start, idVec3 &dir ) const { + idVec3 dir1; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; + + d = dir * dir; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir.Cross(dir1) * (1.0f / d); + return true; +} + +ID_INLINE void idPluecker::ToDir( idVec3 &dir ) const { + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; +} + +ID_INLINE float idPluecker::PermutedInnerProduct( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE float idPluecker::Length( void ) const { + return ( float )idMath::Sqrt( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::LengthSqr( void ) const { + return ( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::NormalizeSelf( void ) { + float l, d; + + l = LengthSqr(); + if ( l == 0.0f ) { + return l; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( l ); + p[0] *= d; + p[1] *= d; + p[2] *= d; + p[3] *= d; + p[4] *= d; + p[5] *= d; + return d * l; +} + +ID_INLINE idPluecker idPluecker::Normalize( void ) const { + float d; + + d = LengthSqr(); + if ( d == 0.0f ) { + return *this; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( d ); + return idPluecker( p[0]*d, p[1]*d, p[2]*d, p[3]*d, p[4]*d, p[5]*d ); +} + +ID_INLINE int idPluecker::GetDimension( void ) const { + return 6; +} + +ID_INLINE const float *idPluecker::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idPluecker::ToFloatPtr( void ) { + return p; +} + +#endif /* !__MATH_PLUECKER_H__ */ diff --git a/source/idlib/math/Polynomial.cpp b/source/idlib/math/Polynomial.cpp new file mode 100644 index 0000000..2c7a7c8 --- /dev/null +++ b/source/idlib/math/Polynomial.cpp @@ -0,0 +1,215 @@ + +#include "../precompiled.h" +#pragma hdrstop + +const float EPSILON = 1e-6f; + +/* +============= +idPolynomial::Laguer +============= +*/ +int idPolynomial::Laguer( const idComplex *coef, const int degree, idComplex &x ) const { + const int MT = 10, MAX_ITERATIONS = MT * 8; + static const float frac[] = { 0.0f, 0.5f, 0.25f, 0.75f, 0.13f, 0.38f, 0.62f, 0.88f, 1.0f }; + int i, j; + float abx, abp, abm, err; + idComplex dx, cx, b, d, f, g, s, gps, gms, g2; + + for ( i = 1; i <= MAX_ITERATIONS; i++ ) { + b = coef[degree]; + err = b.Abs(); + d.Zero(); + f.Zero(); + abx = x.Abs(); + for ( j = degree - 1; j >= 0; j-- ) { + f = x * f + d; + d = x * d + b; + b = x * b + coef[j]; + err = b.Abs() + abx * err; + } + if ( b.Abs() < err * EPSILON ) { + return i; + } + g = d / b; + g2 = g * g; + s = ( ( degree - 1 ) * ( degree * ( g2 - 2.0f * f / b ) - g2 ) ).Sqrt(); + gps = g + s; + gms = g - s; + abp = gps.Abs(); + abm = gms.Abs(); + if ( abp < abm ) { + gps = gms; + } + if ( Max( abp, abm ) > 0.0f ) { + dx = degree / gps; + } else { + dx = idMath::Exp( idMath::Log( 1.0f + abx ) ) * idComplex( idMath::Cos( i ), idMath::Sin( i ) ); + } + cx = x - dx; + if ( x == cx ) { + return i; + } + if ( i % MT == 0 ) { + x = cx; + } else { + x -= frac[i/MT] * dx; + } + } + return i; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( idComplex *roots ) const { + int i, j; + idComplex x, b, c, *coef; + + coef = (idComplex *) _alloca16( ( degree + 1 ) * sizeof( idComplex ) ); + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + + for ( i = degree - 1; i >= 0; i-- ) { + x.Zero(); + Laguer( coef, i + 1, x ); + if ( idMath::Fabs( x.i ) < 2.0f * EPSILON * idMath::Fabs( x.r ) ) { + x.i = 0.0f; + } + roots[i] = x; + b = coef[i+1]; + for ( j = i; j >= 0; j-- ) { + c = coef[j]; + coef[j] = b; + b = x * b + c; + } + } + + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + for ( i = 0; i < degree; i++ ) { + Laguer( coef, degree, roots[i] ); + } + + for ( i = 1; i < degree; i++ ) { + x = roots[i]; + for ( j = i - 1; j >= 0; j-- ) { + if ( roots[j].r <= x.r ) { + break; + } + roots[j+1] = roots[j]; + } + roots[j+1] = x; + } + + return degree; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( float *roots ) const { + int i, num; + idComplex *complexRoots; + + switch( degree ) { + case 0: return 0; + case 1: return GetRoots1( coefficient[1], coefficient[0], roots ); + case 2: return GetRoots2( coefficient[2], coefficient[1], coefficient[0], roots ); + case 3: return GetRoots3( coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + case 4: return GetRoots4( coefficient[4], coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + } + + // The Abel-Ruffini theorem states that there is no general solution + // in radicals to polynomial equations of degree five or higher. + // A polynomial equation can be solved by radicals if and only if + // its Galois group is a solvable group. + + complexRoots = (idComplex *) _alloca16( degree * sizeof( idComplex ) ); + + GetRoots( complexRoots ); + + for ( num = i = 0; i < degree; i++ ) { + if ( complexRoots[i].i == 0.0f ) { + roots[i] = complexRoots[i].r; + num++; + } + } + return num; +} + +/* +============= +idPolynomial::ToString +============= +*/ +const char *idPolynomial::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idPolynomial::Test +============= +*/ +void idPolynomial::Test( void ) { + int i, num; + float roots[4], value; + idComplex complexRoots[4], complexValue; + idPolynomial p; + + p = idPolynomial( -5.0f, 4.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f, 2.0f, 1.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } +} diff --git a/source/idlib/math/Polynomial.h b/source/idlib/math/Polynomial.h new file mode 100644 index 0000000..a017e63 --- /dev/null +++ b/source/idlib/math/Polynomial.h @@ -0,0 +1,602 @@ + +#ifndef __MATH_POLYNOMIAL_H__ +#define __MATH_POLYNOMIAL_H__ + +/* +=============================================================================== + + Polynomial of arbitrary degree with real coefficients. + +=============================================================================== +*/ + + +class idPolynomial { +public: + idPolynomial( void ); + explicit idPolynomial( int d ); + explicit idPolynomial( float a, float b ); + explicit idPolynomial( float a, float b, float c ); + explicit idPolynomial( float a, float b, float c, float d ); + explicit idPolynomial( float a, float b, float c, float d, float e ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idPolynomial operator-() const; + idPolynomial & operator=( const idPolynomial &p ); + + idPolynomial operator+( const idPolynomial &p ) const; + idPolynomial operator-( const idPolynomial &p ) const; + idPolynomial operator*( const float s ) const; + idPolynomial operator/( const float s ) const; + + idPolynomial & operator+=( const idPolynomial &p ); + idPolynomial & operator-=( const idPolynomial &p ); + idPolynomial & operator*=( const float s ); + idPolynomial & operator/=( const float s ); + + bool Compare( const idPolynomial &p ) const; // exact compare, no epsilon + bool Compare( const idPolynomial &p, const float epsilon ) const;// compare with epsilon + bool operator==( const idPolynomial &p ) const; // exact compare, no epsilon + bool operator!=( const idPolynomial &p ) const; // exact compare, no epsilon + + void Zero( void ); + void Zero( int d ); + + int GetDimension( void ) const; // get the degree of the polynomial + int GetDegree( void ) const; // get the degree of the polynomial + float GetValue( const float x ) const; // evaluate the polynomial with the given real value + idComplex GetValue( const idComplex &x ) const; // evaluate the polynomial with the given complex value + idPolynomial GetDerivative( void ) const; // get the first derivative of the polynomial + idPolynomial GetAntiDerivative( void ) const; // get the anti derivative of the polynomial + + int GetRoots( idComplex *roots ) const; // get all roots + int GetRoots( float *roots ) const; // get the real roots + + static int GetRoots1( float a, float b, float *roots ); + static int GetRoots2( float a, float b, float c, float *roots ); + static int GetRoots3( float a, float b, float c, float d, float *roots ); + static int GetRoots4( float a, float b, float c, float d, float e, float *roots ); + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + static void Test( void ); + +private: + int degree; + int allocated; + float * coefficient; + + void Resize( int d, bool keep ); + int Laguer( const idComplex *coef, const int degree, idComplex &r ) const; +}; + +ID_INLINE idPolynomial::idPolynomial( void ) { + degree = -1; + allocated = 0; + coefficient = NULL; +} + +ID_INLINE idPolynomial::idPolynomial( int d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( d, false ); +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 1, false ); + coefficient[0] = b; + coefficient[1] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 2, false ); + coefficient[0] = c; + coefficient[1] = b; + coefficient[2] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 3, false ); + coefficient[0] = d; + coefficient[1] = c; + coefficient[2] = b; + coefficient[3] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d, float e ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 4, false ); + coefficient[0] = e; + coefficient[1] = d; + coefficient[2] = c; + coefficient[3] = b; + coefficient[4] = a; +} + +ID_INLINE float idPolynomial::operator[]( int index ) const { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE float& idPolynomial::operator[]( int index ) { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE idPolynomial idPolynomial::operator-() const { + int i; + idPolynomial n; + + n = *this; + for ( i = 0; i <= degree; i++ ) { + n[i] = -n[i]; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator=( const idPolynomial &p ) { + Resize( p.degree, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + return *this; +} + +ID_INLINE idPolynomial idPolynomial::operator+( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree > degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator-( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree >= degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = - p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator*( const float s ) const { + idPolynomial n; + + if ( s == 0.0f ) { + n.degree = 0; + } else { + n.Resize( degree, false ); + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * s; + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator/( const float s ) const { + float invs; + idPolynomial n; + + assert( s != 0.0f ); + n.Resize( degree, false ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * invs; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator+=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator-=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = - p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator*=( const float s ) { + if ( s == 0.0f ) { + degree = 0; + } else { + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] *= s; + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator/=( const float s ) { + float invs; + + assert( s != 0.0f ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = invs; + } + return *this;; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( coefficient[i] != p.coefficient[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p, const float epsilon ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( idMath::Fabs( coefficient[i] - p.coefficient[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::operator==( const idPolynomial &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPolynomial::operator!=( const idPolynomial &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPolynomial::Zero( void ) { + degree = 0; +} + +ID_INLINE void idPolynomial::Zero( int d ) { + Resize( d, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = 0.0f; + } +} + +ID_INLINE int idPolynomial::GetDimension( void ) const { + return degree; +} + +ID_INLINE int idPolynomial::GetDegree( void ) const { + return degree; +} + +ID_INLINE float idPolynomial::GetValue( const float x ) const { + float y, z; + y = coefficient[0]; + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idComplex idPolynomial::GetValue( const idComplex &x ) const { + idComplex y, z; + y.Set( coefficient[0], 0.0f ); + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idPolynomial idPolynomial::GetDerivative( void ) const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree - 1, false ); + for ( int i = 1; i <= degree; i++ ) { + n.coefficient[i-1] = i * coefficient[i]; + } +} + +ID_INLINE idPolynomial idPolynomial::GetAntiDerivative( void ) const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree + 1, false ); + n.coefficient[0] = 0.0f; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i+1] = coefficient[i] / ( i + 1 ); + } +} + +ID_INLINE int idPolynomial::GetRoots1( float a, float b, float *roots ) { + assert( a != 0.0f ); + roots[0] = - b / a; + return 1; +} + +ID_INLINE int idPolynomial::GetRoots2( float a, float b, float c, float *roots ) { + float inva, ds; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + c *= inva; + b *= inva; + } + ds = b * b - 4.0f * c; + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + roots[0] = 0.5f * ( -b - ds ); + roots[1] = 0.5f * ( -b + ds ); + return 2; + } else { + roots[0] = 0.5f * -b; + return 1; + } +} + +ID_INLINE int idPolynomial::GetRoots3( float a, float b, float c, float d, float *roots ) { + float inva, f, g, halfg, ofs, ds, dist, angle, cs, ss, t; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + d *= inva; + c *= inva; + b *= inva; + } + + f = ( 1.0f / 3.0f ) * ( 3.0f * c - b * b ); + g = ( 1.0f / 27.0f ) * ( 2.0f * b * b * b - 9.0f * c * b + 27.0f * d ); + halfg = 0.5f * g; + ofs = ( 1.0f / 3.0f ) * b; + ds = 0.25f * g * g + ( 1.0f / 27.0f ) * f * f * f; + + if ( ds < 0.0f ) { + dist = idMath::Sqrt( ( -1.0f / 3.0f ) * f ); + angle = ( 1.0f / 3.0f ) * idMath::ATan( idMath::Sqrt( -ds ), -halfg ); + cs = idMath::Cos( angle ); + ss = idMath::Sin( angle ); + roots[0] = 2.0f * dist * cs - ofs; + roots[1] = -dist * ( cs + idMath::SQRT_THREE * ss ) - ofs; + roots[2] = -dist * ( cs - idMath::SQRT_THREE * ss ) - ofs; + return 3; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + t = -halfg + ds; + if ( t >= 0.0f ) { + roots[0] = idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] = -idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + t = -halfg - ds; + if ( t >= 0.0f ) { + roots[0] += idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] -= idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + roots[0] -= ofs; + return 1; + } else { + if ( halfg >= 0.0f ) { + t = -idMath::Pow( halfg, ( 1.0f / 3.0f ) ); + } else { + t = idMath::Pow( -halfg, ( 1.0f / 3.0f ) ); + } + roots[0] = 2.0f * t - ofs; + roots[1] = -t - ofs; + roots[2] = roots[1]; + return 3; + } +} + +ID_INLINE int idPolynomial::GetRoots4( float a, float b, float c, float d, float e, float *roots ) { + int count; + float inva, y, ds, r, s1, s2, t1, t2, tp, tm; + float roots3[3]; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + e *= inva; + d *= inva; + c *= inva; + b *= inva; + } + + count = 0; + + GetRoots3( 1.0f, -c, b * d - 4.0f * e, -b * b * e + 4.0f * c * e - d * d, roots3 ); + y = roots3[0]; + ds = 0.25f * b * b - c + y; + + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + r = idMath::Sqrt( ds ); + t1 = 0.75f * b * b - r * r - 2.0f * c; + t2 = ( 4.0f * b * c - 8.0f * d - b * b * b ) / ( 4.0f * r ); + tp = t1 + t2; + tm = t1 - t2; + + if ( tp >= 0.0f ) { + s1 = idMath::Sqrt( tp ); + roots[count++] = -0.25f * b + 0.5f * ( r + s1 ); + roots[count++] = -0.25f * b + 0.5f * ( r - s1 ); + } + if ( tm >= 0.0f ) { + s2 = idMath::Sqrt( tm ); + roots[count++] = -0.25f * b + 0.5f * ( s2 - r ); + roots[count++] = -0.25f * b - 0.5f * ( s2 + r ); + } + return count; + } else { + t2 = y * y - 4.0f * e; + if ( t2 >= 0.0f ) { + t2 = 2.0f * idMath::Sqrt( t2 ); + t1 = 0.75f * b * b - 2.0f * c; + if ( t1 + t2 >= 0.0f ) { + s1 = idMath::Sqrt( t1 + t2 ); + roots[count++] = -0.25f * b + 0.5f * s1; + roots[count++] = -0.25f * b - 0.5f * s1; + } + if ( t1 - t2 >= 0.0f ) { + s2 = idMath::Sqrt( t1 - t2 ); + roots[count++] = -0.25f * b + 0.5f * s2; + roots[count++] = -0.25f * b - 0.5f * s2; + } + } + return count; + } +} + +ID_INLINE const float *idPolynomial::ToFloatPtr( void ) const { + return coefficient; +} + +ID_INLINE float *idPolynomial::ToFloatPtr( void ) { + return coefficient; +} + +ID_INLINE void idPolynomial::Resize( int d, bool keep ) { + int alloc = ( d + 1 + 3 ) & ~3; + if ( alloc > allocated ) { +// RAVEN BEGIN + float *ptr = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +// RAVEN END + if ( coefficient != NULL ) { + if ( keep ) { + for ( int i = 0; i <= degree; i++ ) { + ptr[i] = coefficient[i]; + } + } + Mem_Free16( coefficient ); + } + allocated = alloc; + coefficient = ptr; + } + degree = d; +} + +#endif /* !__MATH_POLYNOMIAL_H__ */ diff --git a/source/idlib/math/Quat.cpp b/source/idlib/math/Quat.cpp new file mode 100644 index 0000000..3fca03c --- /dev/null +++ b/source/idlib/math/Quat.cpp @@ -0,0 +1,228 @@ + +#include "../precompiled.h" +#pragma hdrstop + +/* +===================== +idQuat::ToAngles +===================== +*/ +idAngles idQuat::ToAngles( void ) const { + return ToMat3().ToAngles(); +} + +/* +===================== +idQuat::ToRotation +===================== +*/ +idRotation idQuat::ToRotation( void ) const { + idVec3 vec; + float angle; + + vec.x = x; + vec.y = y; + vec.z = z; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +===================== +idQuat::ToMat3 +===================== +*/ +idMat3 idQuat::ToMat3( void ) const { + idMat3 mat; + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = w * x2; + wy = w * y2; + wz = w * z2; + + mat[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + mat[ 0 ][ 1 ] = xy - wz; + mat[ 0 ][ 2 ] = xz + wy; + + mat[ 1 ][ 0 ] = xy + wz; + mat[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + mat[ 1 ][ 2 ] = yz - wx; + + mat[ 2 ][ 0 ] = xz - wy; + mat[ 2 ][ 1 ] = yz + wx; + mat[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + return mat; +} + +/* +===================== +idQuat::ToMat4 +===================== +*/ +idMat4 idQuat::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +===================== +idQuat::ToCQuat +===================== +*/ +idCQuat idQuat::ToCQuat( void ) const { + if ( w < 0.0f ) { + return idCQuat( -x, -y, -z ); + } + return idCQuat( x, y, z ); +} + +/* +============ +idQuat::ToAngularVelocity +============ +*/ +idVec3 idQuat::ToAngularVelocity( void ) const { + idVec3 vec; + + vec.x = x; + vec.y = y; + vec.z = z; + vec.Normalize(); + return vec * idMath::ACos( w ); +} + +/* +============= +idQuat::ToString +============= +*/ +const char *idQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +===================== +idQuat::Slerp + +Spherical linear interpolation between two quaternions. +===================== +*/ +idQuat &idQuat::Slerp( const idQuat &from, const idQuat &to, float t ) { +#ifdef _XENON +#else + idQuat temp; + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + *this = from; + return *this; + } + + if ( t >= 1.0f ) { + *this = to; + return *this; + } + + if ( from == to ) { + *this = to; + return *this; + } + + cosom = from.x * to.x + from.y * to.y + from.z * to.z + from.w * to.w; + if ( cosom < 0.0f ) { + temp = -to; + cosom = -cosom; + } else { + temp = to; + } + + if ( ( 1.0f - cosom ) > 1e-6f ) { +#if 0 + omega = acos( cosom ); + sinom = 1.0f / idMath::Sin( omega ); + scale0 = idMath::Sin( ( 1.0f - t ) * omega ) * sinom; + scale1 = idMath::Sin( t * omega ) * sinom; +#else + scale0 = 1.0f - cosom * cosom; + sinom = idMath::InvSqrt( scale0 ); + omega = idMath::ATan16( scale0 * sinom, cosom ); + scale0 = idMath::Sin16( ( 1.0f - t ) * omega ) * sinom; + scale1 = idMath::Sin16( t * omega ) * sinom; +#endif + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + *this = ( scale0 * from ) + ( scale1 * temp ); + return *this; +#endif +} + +/* +============= +idCQuat::ToAngles +============= +*/ +idAngles idCQuat::ToAngles( void ) const { + return ToQuat().ToAngles(); +} + +/* +============= +idCQuat::ToRotation +============= +*/ +idRotation idCQuat::ToRotation( void ) const { + return ToQuat().ToRotation(); +} + +/* +============= +idCQuat::ToMat3 +============= +*/ +idMat3 idCQuat::ToMat3( void ) const { + return ToQuat().ToMat3(); +} + +/* +============= +idCQuat::ToMat4 +============= +*/ +idMat4 idCQuat::ToMat4( void ) const { + return ToQuat().ToMat4(); +} + +/* +============= +idCQuat::ToString +============= +*/ +const char *idCQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Quat.h b/source/idlib/math/Quat.h new file mode 100644 index 0000000..67a1232 --- /dev/null +++ b/source/idlib/math/Quat.h @@ -0,0 +1,378 @@ + +#ifndef __MATH_QUAT_H__ +#define __MATH_QUAT_H__ + +/* +=============================================================================== + + Quaternion + +=============================================================================== +*/ + + +class idVec3; +class idAngles; +class idRotation; +class idMat3; +class idMat4; +class idCQuat; + +class idQuat { +public: + float x; + float y; + float z; + float w; + + idQuat( void ); + idQuat( float x, float y, float z, float w ); + + void Set( float x, float y, float z, float w ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idQuat operator-() const; + idQuat & operator=( const idQuat &a ); + idQuat operator+( const idQuat &a ) const; + idQuat & operator+=( const idQuat &a ); + idQuat operator-( const idQuat &a ) const; + idQuat & operator-=( const idQuat &a ); + idQuat operator*( const idQuat &a ) const; + idVec3 operator*( const idVec3 &a ) const; + idQuat operator*( float a ) const; + idQuat & operator*=( const idQuat &a ); + idQuat & operator*=( float a ); + + friend idQuat operator*( const float a, const idQuat &b ); + friend idVec3 operator*( const idVec3 &a, const idQuat &b ); + + bool Compare( const idQuat &a ) const; // exact compare, no epsilon + bool Compare( const idQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idQuat &a ) const; // exact compare, no epsilon + + idQuat Inverse( void ) const; + float Length( void ) const; + idQuat & Normalize( void ); + + float CalcW( void ) const; + int GetDimension( void ) const; + + idAngles ToAngles( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idCQuat ToCQuat( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + idQuat & Slerp( const idQuat &from, const idQuat &to, float t ); +}; + +ID_INLINE idQuat::idQuat( void ) { +} + +ID_INLINE idQuat::idQuat( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE float idQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE idQuat idQuat::operator-() const { + return idQuat( -x, -y, -z, -w ); +} + +ID_INLINE idQuat &idQuat::operator=( const idQuat &a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator+( const idQuat &a ) const { + return idQuat( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idQuat& idQuat::operator+=( const idQuat &a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator-( const idQuat &a ) const { + return idQuat( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE idQuat& idQuat::operator-=( const idQuat &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator*( const idQuat &a ) const { + return idQuat( w*a.x + x*a.w + y*a.z - z*a.y, + w*a.y + y*a.w + z*a.x - x*a.z, + w*a.z + z*a.w + x*a.y - y*a.x, + w*a.w - x*a.x - y*a.y - z*a.z ); +} + +ID_INLINE idVec3 idQuat::operator*( const idVec3 &a ) const { +#if 0 + // it's faster to do the conversion to a 3x3 matrix and multiply the vector by this 3x3 matrix + return ( ToMat3() * a ); +#else + // result = this->Inverse() * idQuat( a.x, a.y, a.z, 0.0f ) * (*this) + float xx = x * x; + float zz = z * z; + float ww = w * w; + float yy = y * y; + + float xw2 = x*w*2.0f; + float xy2 = x*y*2.0f; + float xz2 = x*z*2.0f; + float yw2 = y*w*2.0f; + float yz2 = y*z*2.0f; + float zw2 = z*w*2.0f; + + return idVec3( + ( xx - yy - zz + ww ) * a.x + ( xy2 + zw2 ) * a.y + ( xz2 - yw2 ) * a.z, + ( xy2 - zw2 ) * a.x + ( -xx + yy - zz + ww )* a.y + ( yz2 + xw2 ) * a.z, + ( xz2 + yw2 ) * a.x + ( yz2 - xw2 ) * a.y + ( -xx - yy + zz + ww )* a.z ); +#endif +} + +ID_INLINE idQuat idQuat::operator*( float a ) const { + return idQuat( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idQuat operator*( const float a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idVec3 operator*( const idVec3 &a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idQuat& idQuat::operator*=( const idQuat &a ) { + *this = *this * a; + + return *this; +} + +ID_INLINE idQuat& idQuat::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +ID_INLINE bool idQuat::Compare( const idQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && ( w == a.w ) ); +} + +ID_INLINE bool idQuat::Compare( const idQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idQuat::operator==( const idQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idQuat::operator!=( const idQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE void idQuat::Set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE idQuat idQuat::Inverse( void ) const { + return idQuat( -x, -y, -z, w ); +} + +ID_INLINE float idQuat::Length( void ) const { + float len; + + len = x * x + y * y + z * z + w * w; + return idMath::Sqrt( len ); +} + +ID_INLINE idQuat& idQuat::Normalize( void ) { + float len; + float ilength; + + len = this->Length(); + if ( len ) { + ilength = 1 / len; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + return *this; +} + +ID_INLINE float idQuat::CalcW( void ) const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return idMath::Sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ); +} + +ID_INLINE int idQuat::GetDimension( void ) const { + return 4; +} + +ID_INLINE const float *idQuat::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idQuat::ToFloatPtr( void ) { + return &x; +} + + +/* +=============================================================================== + + Compressed quaternion + +=============================================================================== +*/ + +class idCQuat { +public: + float x; + float y; + float z; + + idCQuat( void ); + idCQuat( float x, float y, float z ); + + void Set( float x, float y, float z ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + bool Compare( const idCQuat &a ) const; // exact compare, no epsilon + bool Compare( const idCQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idCQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idCQuat &a ) const; // exact compare, no epsilon + + int GetDimension( void ) const; + + idAngles ToAngles( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idQuat ToQuat( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; +}; + +ID_INLINE idCQuat::idCQuat( void ) { +} + +ID_INLINE idCQuat::idCQuat( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idCQuat::Set( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idCQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idCQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idCQuat::operator==( const idCQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idCQuat::operator!=( const idCQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE int idCQuat::GetDimension( void ) const { + return 3; +} + +ID_INLINE idQuat idCQuat::ToQuat( void ) const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return idQuat( x, y, z, idMath::Sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ) ); +} + +ID_INLINE const float *idCQuat::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idCQuat::ToFloatPtr( void ) { + return &x; +} + +#endif /* !__MATH_QUAT_H__ */ diff --git a/source/idlib/math/Radians.cpp b/source/idlib/math/Radians.cpp new file mode 100644 index 0000000..4c51a49 --- /dev/null +++ b/source/idlib/math/Radians.cpp @@ -0,0 +1,135 @@ +#include "../precompiled.h" +#pragma hdrstop + +#include + +/* +================= +rvAngles::NormalizeFull + +returns angles normalized to the range [0 <= angle < TWO_PI] +================= +*/ +rvAngles &rvAngles::NormalizeFull( void ) +{ + // Get to -TWO_PI < a < TWO_PI + pitch = fmodf( pitch, idMath::TWO_PI ); + yaw = fmodf( yaw, idMath::TWO_PI ); + roll = fmodf( roll, idMath::TWO_PI ); + + // Fix any negatives + if( pitch < 0.0f ) + { + pitch += idMath::TWO_PI; + } + + if( yaw < 0.0f ) + { + yaw += idMath::TWO_PI; + } + + if( roll < 0.0f ) + { + roll += idMath::TWO_PI; + } + + return( *this ); +} + +/* +================= +rvAngles::NormalizeHalf + +returns angles normalized to the range [-PI < angle <= PI] +================= +*/ +rvAngles &rvAngles::NormalizeHalf( void ) +{ + int i; + + // Get to -TWO_PI < a < TWO_PI + pitch = fmodf( pitch, idMath::TWO_PI ); + yaw = fmodf( yaw, idMath::TWO_PI ); + roll = fmodf( roll, idMath::TWO_PI ); + + for( i = 0; i < 3; i++ ) + { + if( ( *this )[i] < -idMath::PI ) + { + ( *this )[i] += idMath::TWO_PI; + } + + if( ( *this )[i] > idMath::PI ) + { + ( *this )[i] -= idMath::TWO_PI; + } + } + + return( *this ); +} + +/* +================= +rvAngles::ToVectors +================= +*/ +void rvAngles::ToVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) const +{ + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + idMath::SinCos( roll, sr, cr ); + + if( forward ) + { + forward->Set( cp * cy, cp * sy, -sp ); + } + + if( right ) + { + right->Set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if( up ) + { + up->Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +/* +================= +rvAngles::ToForward +================= +*/ +idVec3 rvAngles::ToForward( void ) const +{ + float sp, sy, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + + return( idVec3( cp * cy, cp * sy, -sp ) ); +} + +/* +================= +rvAngles::ToMat3 +================= +*/ +idMat3 &rvAngles::ToMat3( idMat3 &mat ) const +{ + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + idMath::SinCos( roll, sr, cr ); + + mat[0].Set( cp * cy, cp * sy, -sp ); + mat[1].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[2].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return( mat ); +} + +// end diff --git a/source/idlib/math/Radians.h b/source/idlib/math/Radians.h new file mode 100644 index 0000000..2e26035 --- /dev/null +++ b/source/idlib/math/Radians.h @@ -0,0 +1,252 @@ +#ifndef _MATH_RADIANS_H_INC_ +#define _MATH_RADIANS_H_INC_ + +/* +=============================================================================== + Euler angles + + This is basically a duplicate of idAngles, but used radians rather than + degrees (to avoid the conversion before trig calls) + + All trig calls use float precision + + ToMat3 passes in workspace to avoid a memcpy + +=============================================================================== +*/ + +#ifndef M_PI +# define M_PI (3.1415926536f) +#endif + +class idVec3; +class idMat3; + +class rvAngles +{ +public: + float pitch; + float yaw; + float roll; + + rvAngles( void ) {} + rvAngles( float pitch, float yaw, float roll ); + explicit rvAngles( const idVec3 &v ); + + void Set( float pitch, float yaw, float roll ); + rvAngles & Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + rvAngles operator-() const; + rvAngles & operator=( const rvAngles &a ); + rvAngles & operator=( const idVec3 &a ); + rvAngles operator+( const rvAngles &a ) const; + rvAngles operator+( const idVec3 &a ) const; + rvAngles & operator+=( const rvAngles &a ); + rvAngles & operator+=( const idVec3 &a ); + rvAngles operator-( const rvAngles &a ) const; + rvAngles operator-( const idVec3 &a ) const; + rvAngles & operator-=( const rvAngles &a ); + rvAngles & operator-=( const idVec3 &a ); + rvAngles operator*( const float a ) const; + rvAngles & operator*=( const float a ); + + friend rvAngles operator+( const idVec3 &a, const rvAngles &b ); + friend rvAngles operator-( const idVec3 &a, const rvAngles &b ); + friend rvAngles operator*( const float a, const rvAngles &b ); + + bool Compare( const rvAngles &a ) const; // exact compare, no epsilon + bool Compare( const rvAngles &a, const float epsilon ) const; // compare with epsilon + bool operator==( const rvAngles &a ) const; // exact compare, no epsilon + bool operator!=( const rvAngles &a ) const; // exact compare, no epsilon + + rvAngles & NormalizeFull( void ); // normalizes 'this' + rvAngles & NormalizeHalf( void ); // normalizes 'this' + + void ToVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ) const; + idVec3 ToForward( void ) const; + idMat3 &ToMat3( idMat3 &mat ) const; + + const float * ToFloatPtr( void ) const { return( &pitch ); } + float * ToFloatPtr( void ) { return( &pitch ); } +}; + +ID_INLINE rvAngles::rvAngles( float pitch, float yaw, float roll ) +{ + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE rvAngles::rvAngles( const idVec3 &v ) +{ + this->pitch = v[0]; + this->yaw = v[1]; + this->roll = v[2]; +} + +ID_INLINE void rvAngles::Set( float pitch, float yaw, float roll ) +{ + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE rvAngles &rvAngles::Zero( void ) +{ + pitch = 0.0f; + yaw = 0.0f; + roll = 0.0f; + return( *this ); +} + +ID_INLINE float rvAngles::operator[]( int index ) const +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return( ( &pitch )[ index ] ); +} + +ID_INLINE float &rvAngles::operator[]( int index ) +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return( ( &pitch )[ index ] ); +} + +ID_INLINE rvAngles rvAngles::operator-( void ) const +{ + return( rvAngles( -pitch, -yaw, -roll ) ); +} + +ID_INLINE rvAngles &rvAngles::operator=( const rvAngles &a ) +{ + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; + return( *this ); +} + +ID_INLINE rvAngles &rvAngles::operator=( const idVec3 &a ) +{ + pitch = a.x; + yaw = a.y; + roll = a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator+( const rvAngles &a ) const +{ + return( rvAngles( pitch + a.pitch, yaw + a.yaw, roll + a.roll ) ); +} + +ID_INLINE rvAngles rvAngles::operator+( const idVec3 &a ) const +{ + return( rvAngles( pitch + a.x, yaw + a.y, roll + a.z ) ); +} + +ID_INLINE rvAngles& rvAngles::operator+=( const rvAngles &a ) +{ + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + return( *this ); +} + +ID_INLINE rvAngles& rvAngles::operator+=( const idVec3 &a ) +{ + pitch += a.x; + yaw += a.y; + roll += a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator-( const rvAngles &a ) const +{ + return( rvAngles( pitch - a.pitch, yaw - a.yaw, roll - a.roll ) ); +} + +ID_INLINE rvAngles rvAngles::operator-( const idVec3 &a ) const +{ + return( rvAngles( pitch - a.x, yaw - a.y, roll - a.z ) ); +} + +ID_INLINE rvAngles& rvAngles::operator-=( const rvAngles &a ) +{ + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + return( *this ); +} + +ID_INLINE rvAngles& rvAngles::operator-=( const idVec3 &a ) +{ + pitch -= a.x; + yaw -= a.y; + roll -= a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator*( const float a ) const +{ + return( rvAngles( pitch * a, yaw * a, roll * a ) ); +} + +ID_INLINE rvAngles& rvAngles::operator*=( float a ) +{ + pitch *= a; + yaw *= a; + roll *= a; + return( *this ); +} + +ID_INLINE rvAngles operator+( const idVec3 &a, const rvAngles &b ) +{ + return( rvAngles( a.x + b.pitch, a.y + b.yaw, a.z + b.roll ) ); +} + +ID_INLINE rvAngles operator-( const idVec3 &a, const rvAngles &b ) +{ + return( rvAngles( a.x - b.pitch, a.y - b.yaw, a.z - b.roll ) ); +} + +ID_INLINE rvAngles operator*( const float a, const rvAngles &b ) +{ + return( rvAngles( a * b.pitch, a * b.yaw, a * b.roll ) ); +} + +ID_INLINE bool rvAngles::Compare( const rvAngles &a ) const +{ + return( ( a.pitch == pitch ) && ( a.yaw == yaw ) && ( a.roll == roll ) ); +} + +ID_INLINE bool rvAngles::Compare( const rvAngles &a, const float epsilon ) const +{ + if( idMath::Fabs( pitch - a.pitch ) > epsilon ) + { + return( false ); + } + + if( idMath::Fabs( yaw - a.yaw ) > epsilon ) + { + return( false ); + } + + if( idMath::Fabs( roll - a.roll ) > epsilon ) + { + return( false ); + } + + return( true ); +} + +ID_INLINE bool rvAngles::operator==( const rvAngles &a ) const +{ + return( Compare( a ) ); +} + +ID_INLINE bool rvAngles::operator!=( const rvAngles &a ) const +{ + return( !Compare( a ) ); +} + +#endif // _MATH_RADIANS_H_INC_ diff --git a/source/idlib/math/Random.h b/source/idlib/math/Random.h new file mode 100644 index 0000000..365840b --- /dev/null +++ b/source/idlib/math/Random.h @@ -0,0 +1,131 @@ + +#ifndef __MATH_RANDOM_H__ +#define __MATH_RANDOM_H__ + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom { +public: + idRandom( int seed = 0 ); + + void SetSeed( int seed ); + int GetSeed( void ) const; + + int RandomInt( void ); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max[ + float RandomFloat( void ); // random number in the range [0.0f, 1.0f] + float CRandomFloat( void ); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + int seed; +}; + +ID_INLINE idRandom::idRandom( int seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom::SetSeed( int seed ) { + this->seed = seed; +} + +ID_INLINE int idRandom::GetSeed( void ) const { + return seed; +} + +ID_INLINE int idRandom::RandomInt( void ) { + seed = 69069 * seed + 1; + return ( seed & idRandom::MAX_RAND ); +} + +ID_INLINE int idRandom::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return RandomInt() % max; +} + +ID_INLINE float idRandom::RandomFloat( void ) { + return ( RandomInt() / ( float )( idRandom::MAX_RAND + 1 ) ); +} + +ID_INLINE float idRandom::CRandomFloat( void ) { + return ( 2.0f * ( RandomFloat() - 0.5f ) ); +} + + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom2 { +public: + idRandom2( unsigned long seed = 0 ); + + void SetSeed( unsigned long seed ); + unsigned long GetSeed( void ) const; + + int RandomInt( void ); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max] + float RandomFloat( void ); // random number in the range [0.0f, 1.0f] + float CRandomFloat( void ); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + unsigned long seed; + + static const unsigned long IEEE_ONE = 0x3f800000; + static const unsigned long IEEE_MASK = 0x007fffff; +}; + +ID_INLINE idRandom2::idRandom2( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom2::SetSeed( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE unsigned long idRandom2::GetSeed( void ) const { + return seed; +} + +ID_INLINE int idRandom2::RandomInt( void ) { + seed = 1664525L * seed + 1013904223L; + return ( (int) seed & idRandom2::MAX_RAND ); +} + +ID_INLINE int idRandom2::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return ( RandomInt() >> ( 16 - idMath::BitsForInteger( max ) ) ) % max; +} + +ID_INLINE float idRandom2::RandomFloat( void ) { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( ( *(float *)&i ) - 1.0f ); +} + +ID_INLINE float idRandom2::CRandomFloat( void ) { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( 2.0f * ( *(float *)&i ) - 3.0f ); +} + +#endif /* !__MATH_RANDOM_H__ */ diff --git a/source/idlib/math/Rotation.cpp b/source/idlib/math/Rotation.cpp new file mode 100644 index 0000000..5015128 --- /dev/null +++ b/source/idlib/math/Rotation.cpp @@ -0,0 +1,130 @@ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +============ +idRotation::ToAngles +============ +*/ +idAngles idRotation::ToAngles( void ) const { + return ToMat3().ToAngles(); +} + +/* +============ +idRotation::ToQuat +============ +*/ +idQuat idRotation::ToQuat( void ) const { + float a, s, c; + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + return idQuat( vec.x * s, vec.y * s, vec.z * s, c ); +} + +/* +============ +idRotation::toMat3 +============ +*/ +const idMat3 &idRotation::ToMat3( void ) const { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + + if ( axisValid ) { + return axis; + } + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + + x = vec[0] * s; + y = vec[1] * s; + z = vec[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + axis[ 0 ][ 1 ] = xy - wz; + axis[ 0 ][ 2 ] = xz + wy; + + axis[ 1 ][ 0 ] = xy + wz; + axis[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + axis[ 1 ][ 2 ] = yz - wx; + + axis[ 2 ][ 0 ] = xz - wy; + axis[ 2 ][ 1 ] = yz + wx; + axis[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + axisValid = true; + + return axis; +} + +/* +============ +idRotation::ToMat4 +============ +*/ +idMat4 idRotation::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +============ +idRotation::ToAngularVelocity +============ +*/ +idVec3 idRotation::ToAngularVelocity( void ) const { + return vec * DEG2RAD( angle ); +} + +/* +============ +idRotation::Normalize180 +============ +*/ +void idRotation::Normalize180( void ) { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 180.0f ) { + angle -= 360.0f; + } + else if ( angle < -180.0f ) { + angle += 360.0f; + } +} + +/* +============ +idRotation::Normalize360 +============ +*/ +void idRotation::Normalize360( void ) { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 360.0f ) { + angle -= 360.0f; + } + else if ( angle < 0.0f ) { + angle += 360.0f; + } +} diff --git a/source/idlib/math/Rotation.h b/source/idlib/math/Rotation.h new file mode 100644 index 0000000..0642d1e --- /dev/null +++ b/source/idlib/math/Rotation.h @@ -0,0 +1,184 @@ + +#ifndef __MATH_ROTATION_H__ +#define __MATH_ROTATION_H__ + +/* +=============================================================================== + + Describes a complete rotation in degrees about an abritray axis. + A local rotation matrix is stored for fast rotation of multiple points. + +=============================================================================== +*/ + + +class idAngles; +class idQuat; +class idMat3; + +class idRotation { + + friend class idAngles; + friend class idQuat; + friend class idMat3; + +public: + idRotation( void ); + idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + + void Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + void SetOrigin( const idVec3 &rotationOrigin ); + void SetVec( const idVec3 &rotationVec ); // has to be normalized + void SetVec( const float x, const float y, const float z ); // has to be normalized + void SetAngle( const float rotationAngle ); + void Scale( const float s ); + void ReCalculateMatrix( void ); + const idVec3 & GetOrigin( void ) const; + const idVec3 & GetVec( void ) const; + float GetAngle( void ) const; + + idRotation operator-() const; // flips rotation + idRotation operator*( const float s ) const; // scale rotation + idRotation operator/( const float s ) const; // scale rotation + idRotation & operator*=( const float s ); // scale rotation + idRotation & operator/=( const float s ); // scale rotation + idVec3 operator*( const idVec3 &v ) const; // rotate vector + + friend idRotation operator*( const float s, const idRotation &r ); // scale rotation + friend idVec3 operator*( const idVec3 &v, const idRotation &r ); // rotate vector + friend idVec3 & operator*=( idVec3 &v, const idRotation &r ); // rotate vector + + idAngles ToAngles( void ) const; + idQuat ToQuat( void ) const; + const idMat3 & ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + + void RotatePoint( idVec3 &point ) const; + + void Normalize180( void ); + void Normalize360( void ); + +private: + idVec3 origin; // origin of rotation + idVec3 vec; // normalized vector to rotate around + float angle; // angle of rotation in degrees + mutable idMat3 axis; // rotation axis + mutable bool axisValid; // true if rotation axis is valid +}; + + +ID_INLINE idRotation::idRotation( void ) { +} + +ID_INLINE idRotation::idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::SetOrigin( const idVec3 &rotationOrigin ) { + origin = rotationOrigin; +} + +ID_INLINE void idRotation::SetVec( const idVec3 &rotationVec ) { + vec = rotationVec; + axisValid = false; +} + +ID_INLINE void idRotation::SetVec( float x, float y, float z ) { + vec[0] = x; + vec[1] = y; + vec[2] = z; + axisValid = false; +} + +ID_INLINE void idRotation::SetAngle( const float rotationAngle ) { + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Scale( const float s ) { + angle *= s; + axisValid = false; +} + +ID_INLINE void idRotation::ReCalculateMatrix( void ) { + axisValid = false; + ToMat3(); +} + +ID_INLINE const idVec3 &idRotation::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idVec3 &idRotation::GetVec( void ) const { + return vec; +} + +ID_INLINE float idRotation::GetAngle( void ) const { + return angle; +} + +ID_INLINE idRotation idRotation::operator-() const { + return idRotation( origin, vec, -angle ); +} + +ID_INLINE idRotation idRotation::operator*( const float s ) const { + return idRotation( origin, vec, angle * s ); +} + +ID_INLINE idRotation idRotation::operator/( const float s ) const { + assert( s != 0.0f ); + return idRotation( origin, vec, angle / s ); +} + +ID_INLINE idRotation &idRotation::operator*=( const float s ) { + angle *= s; + axisValid = false; + return *this; +} + +ID_INLINE idRotation &idRotation::operator/=( const float s ) { + assert( s != 0.0f ); + angle /= s; + axisValid = false; + return *this; +} + +ID_INLINE idVec3 idRotation::operator*( const idVec3 &v ) const { + if ( !axisValid ) { + ToMat3(); + } + return ((v - origin) * axis + origin); +} + +ID_INLINE idRotation operator*( const float s, const idRotation &r ) { + return r * s; +} + +ID_INLINE idVec3 operator*( const idVec3 &v, const idRotation &r ) { + return r * v; +} + +ID_INLINE idVec3 &operator*=( idVec3 &v, const idRotation &r ) { + v = r * v; + return v; +} + +ID_INLINE void idRotation::RotatePoint( idVec3 &point ) const { + if ( !axisValid ) { + ToMat3(); + } + point = ((point - origin) * axis + origin); +} + +#endif /* !__MATH_ROTATION_H__ */ diff --git a/source/idlib/math/Simd.cpp b/source/idlib/math/Simd.cpp new file mode 100644 index 0000000..471720a --- /dev/null +++ b/source/idlib/math/Simd.cpp @@ -0,0 +1,4349 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#ifdef _WINDOWS +#include "Simd_MMX.h" +#include "Simd_3DNow.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_SSE3.h" +#endif // _WINDOWS +#ifdef MACOS_X +#include "Simd_AltiVec.h" +#endif +#ifdef __linux__ +#include "Simd_MMX.h" +#endif +#ifdef _XENON +// RAVEN BEGIN +// jsinger: Adding Xenon processor support +#include "Simd_Xenon.h" +// RAVEN END +#endif + + +idSIMDProcessor * processor = NULL; // pointer to SIMD processor +idSIMDProcessor * generic = NULL; // pointer to generic SIMD implementation +idSIMDProcessor * SIMDProcessor = NULL; + + +/* +================ +idSIMD::Init +================ +*/ +void idSIMD::Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// jsinger: xenon uses idSIMD_Xenon always +#ifdef _XENON + generic = new idSIMD_Xenon; +#else + generic = new idSIMD_Generic; + generic->cpuid = CPUID_GENERIC; +#endif +// RAVEN END + processor = NULL; + SIMDProcessor = generic; +} + +/* +============ +idSIMD::InitProcessor +============ +*/ +void idSIMD::InitProcessor( const char *module, bool forceGeneric ) { + cpuid_t cpuid; + idSIMDProcessor *newProcessor; + + cpuid = idLib::sys->GetProcessorId(); + + if ( forceGeneric ) { + + newProcessor = generic; + + } else { + + if ( !processor ) { +// RAVEN BEGIN +// jsinger: on xenon even the generic is idSIMD_Xenon so this is exactly what we want +#ifdef _XENON + processor = generic; // generic will actually be the xenon SIMD processor on xenon +#else + if ( ( cpuid & CPUID_ALTIVEC ) ) { +#ifdef MACOS_X + processor = new idSIMD_AltiVec; +#else + assert( false ); + processor = generic; +#endif +#ifdef _WINDOWS + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) && ( cpuid & CPUID_SSE2 ) && ( cpuid & CPUID_SSE3 ) ) { + processor = new idSIMD_SSE3; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) && ( cpuid & CPUID_SSE2 ) ) { + processor = new idSIMD_SSE2; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) ) { + processor = new idSIMD_SSE; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_3DNOW ) ) { + processor = new idSIMD_3DNow; + } else if ( ( cpuid & CPUID_MMX ) ) { + processor = new idSIMD_MMX; +#endif // _WINDOWS +#if defined( __linux__ ) && defined( ID_GCC_X86_ASM ) + } else if ( ( cpuid & CPUID_MMX ) ) { + processor = new idSIMD_MMX; +#endif + } else { + processor = generic; + } + processor->cpuid = cpuid; +#endif +// RAVEN END + } + + newProcessor = processor; + } + + if ( newProcessor != SIMDProcessor ) { + SIMDProcessor = newProcessor; + idLib::common->Printf( "%s using %s for SIMD processing\n", module, SIMDProcessor->GetName() ); + } + + if ( cpuid & CPUID_FTZ ) { + idLib::sys->FPU_SetFTZ( true ); + idLib::common->Printf( "enabled Flush-To-Zero mode\n" ); + } + + if ( cpuid & CPUID_DAZ ) { + idLib::sys->FPU_SetDAZ( true ); + idLib::common->Printf( "enabled Denormals-Are-Zero mode\n" ); + } +} + +/* +================ +idSIMD::Shutdown +================ +*/ +void idSIMD::Shutdown( void ) { + if ( processor != generic ) { + delete processor; + } + delete generic; + generic = NULL; + processor = NULL; + SIMDProcessor = NULL; +} + + +//=============================================================== +// +// Test code +// +//=============================================================== + +#define COUNT 1024 // data count +#define NUMTESTS 2048 // number of tests + +#define RANDOM_SEED 1013904223L //((int)idLib::sys->GetClockTicks()) + +idSIMDProcessor *p_simd; +idSIMDProcessor *p_generic; +long baseClocks = 0; + +#ifdef _WINDOWS + +#define TIME_TYPE int + +#pragma warning(disable : 4731) // frame pointer register 'ebx' modified by inline assembly code + +long saved_ebx = 0; + +#define StartRecordTime( start ) \ + __asm mov saved_ebx, ebx \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov start, eax \ + __asm xor eax, eax \ + __asm cpuid + +#define StopRecordTime( end ) \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov end, eax \ + __asm mov ebx, saved_ebx \ + __asm xor eax, eax \ + __asm cpuid + +#elif defined(MACOS_X) + +#include +#include // this is for sleep() +#include +#include +#include + +double ticksPerNanosecond; + +#define TIME_TYPE uint64_t + +/* + + .text + .align 2 + .globl _GetTB +_GetTB: + +loop: + mftbu r4 ; load from TBU + mftb r5 ; load from TBL + mftbu r6 ; load from TBU + cmpw r6, r4 ; see if old == new + bne loop ; if not, carry occured, therefore loop + + stw r4, 0(r3) + stw r5, 4(r3) + +done: + blr ; return + +*/ + +/* + +typedef struct { + unsigned int hi; + unsigned int lo; +} U64; + +double TBToDoubleNano( U64 startTime, U64 stopTime, double ticksPerNanosecond ); + +#if __MWERKS__ +asm void GetTB( U64 * ); +#else +void GetTB( U64 * ); +#endif + +double TBToDoubleNano( U64 startTime, U64 stopTime, double ticksPerNanosecond ) { + #define K_2POWER32 4294967296.0 + #define TICKS_PER_NANOSECOND 0.025 + double nanoTime; + U64 diffTime; + + // calc the difference in TB ticks + diffTime.hi = stopTime.hi - startTime.hi; + diffTime.lo = stopTime.lo - startTime.lo; + + // convert TB ticks into time + nanoTime = (double)(diffTime.hi)*((double)K_2POWER32) + (double)(diffTime.lo); + nanoTime = nanoTime/ticksPerNanosecond; + return (nanoTime); +} + +TIME_TYPE time_in_millisec( void ) { + #define K_2POWER32 4294967296.0 + #define TICKS_PER_NANOSECOND 0.025 + + U64 the_time; + double nanoTime, milliTime; + + GetTB( &the_time ); + + // convert TB ticks into time + nanoTime = (double)(the_time.hi)*((double)K_2POWER32) + (double)(the_time.lo); + nanoTime = nanoTime/ticksPerNanosecond; + + // nanoseconds are 1 billionth of a second. I want milliseconds + milliTime = nanoTime * 1000000.0; + + printf( "ticks per nanosec -- %lf\n", ticksPerNanosecond ); + printf( "nanoTime is %lf -- milliTime is %lf -- as int is %i\n", nanoTime, milliTime, (int)milliTime ); + + return (int)milliTime; +} + +#define StartRecordTime( start ) \ + start = time_in_millisec(); + +#define StopRecordTime( end ) \ + end = time_in_millisec(); + +*/ + +#define StartRecordTime( start ) \ + start = mach_absolute_time(); + +#define StopRecordTime( end ) \ + end = mach_absolute_time(); + +#else + +#define TIME_TYPE int + +#define StartRecordTime( start ) \ + start = 0; + +#define StopRecordTime( end ) \ + end = 1; + +#endif // _WINDOWS + +#define GetBest( start, end, best ) \ + if ( !best || end - start < best ) { \ + best = end - start; \ + } + + +/* +============ +PrintClocks +============ +*/ +void PrintClocks( char *string, int dataCount, int clocks, int otherClocks = 0 ) { + int i; + + idLib::common->Printf( string ); +// RAVEN BEGIN +// bdube: escape codes + for ( i = idStr::LengthWithoutEscapes(string); i < 48; i++ ) { +// RAVEN END + idLib::common->Printf(" "); + } + clocks -= baseClocks; + if ( otherClocks ) { + otherClocks -= baseClocks; + int p = (int) ( (float) ( otherClocks - clocks ) * 100.0f / (float) otherClocks ); + idLib::common->Printf( "c = %4d, clcks = %5d, %d%%\n", dataCount, clocks, p ); + } else { + idLib::common->Printf( "c = %4d, clcks = %5d\n", dataCount, clocks ); + } +} + +/* +============ +GetBaseClocks +============ +*/ +void GetBaseClocks( void ) { + int i, start, end, bestClocks; + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + baseClocks = bestClocks; +} + +/* +============ +TestAdd +============ +*/ +void TestAdd( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Add( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Add( float + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Add( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Add( float + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Add( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Add( float[] + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Add( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Add( float[] + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestSub +============ +*/ +void TestSub( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Sub( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Sub( float + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Sub( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Sub( float + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Sub( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Sub( float[] + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Sub( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Sub( float[] + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMul +============ +*/ +void TestMul( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Mul( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Mul( float * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Mul( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Mul( float * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Mul( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Mul( float[] * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Mul( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Mul( float[] * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDiv +============ +*/ +void TestDiv( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + do { + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } while( idMath::Fabs( fsrc1[i] ) < 0.1f ); + } + + idLib::common->Printf("====================================\n" ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Div( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Div( float * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Div( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Div( float * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Div( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Div( float[] * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Div( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-3f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Div( float[] * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMulAdd +============ +*/ +void TestMulAdd( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst0[k] = k; + } + StartRecordTime( start ); + p_generic->MulAdd( fdst0, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->MulAdd( float * float[%2d] )", j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst1[k] = k; + } + StartRecordTime( start ); + p_simd->MulAdd( fdst1, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MulAdd( float * float[%2d] ) %s", j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMulSub +============ +*/ +void TestMulSub( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst0[k] = k; + } + StartRecordTime( start ); + p_generic->MulSub( fdst0, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->MulSub( float * float[%2d] )", j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst1[k] = k; + } + StartRecordTime( start ); + p_simd->MulSub( fdst1, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MulSub( float * float[%2d] ) %s", j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestDot +============ +*/ +void TestDot( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + ALIGN16( idVec3 v3src0[COUNT] ); + ALIGN16( idVec3 v3src1[COUNT] ); + ALIGN16( idVec3 v3constant( 1.0f, 2.0f, 3.0f ) ); + ALIGN16( idPlane v4src0[COUNT] ); + ALIGN16( idPlane v4constant(1.0f, 2.0f, 3.0f, 4.0f) ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + for ( j = 0; j < 3; j++ ) { + v3src0[i][j] = srnd.CRandomFloat() * 10.0f; + v3src1[i][j] = srnd.CRandomFloat() * 10.0f; + } + v4src0[i] = v3src0[i]; + v4src0[i][3] = srnd.CRandomFloat() * 10.0f; + drawVerts[i].xyz = v3src0[i]; + } + + idLib::common->Printf("====================================\n" ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idPlane[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idPlane[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idPlane[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idPlane[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3src0, v3src1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3[] * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3src0, v3src1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-4f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3[] * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + idLib::common->Printf("====================================\n" ); + + float dot1 = 0.0f, dot2 = 0.0f; + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( dot1, fsrc0, fsrc1, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->Dot( float[%2d] * float[%2d] )", j, j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( dot2, fsrc0, fsrc1, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + result = idMath::Fabs( dot1 - dot2 ) < 1e-4f ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( float[%2d] * float[%2d] ) %s", j, j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestCompare +============ +*/ +void TestCompare( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( byte bytedst[COUNT] ); + ALIGN16( byte bytedst2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpGT( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGT( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpGT( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGT( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpGT( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGT( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpGT( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGT( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpGE( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGE( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpGE( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGE( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpGE( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGE( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpGE( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGE( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpLT( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLT( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpLT( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLT( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpLT( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLT( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpLT( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLT( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpLE( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLE( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpLE( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLE( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpLE( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLE( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpLE( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLE( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMinMax +============ +*/ +void TestMinMax( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( idVec2 v2src0[COUNT] ); + ALIGN16( idVec3 v3src0[COUNT] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( int indexes[COUNT] ); + float min = 0.0f, max = 0.0f, min2 = 0.0f, max2 = 0.0f; + idVec2 v2min, v2max, v2min2, v2max2; + idVec3 vmin, vmax, vmin2, vmax2; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + v2src0[i][0] = srnd.CRandomFloat() * 10.0f; + v2src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][0] = srnd.CRandomFloat() * 10.0f; + v3src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][2] = srnd.CRandomFloat() * 10.0f; + drawVerts[i].xyz = v3src0[i]; + indexes[i] = i; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + min = idMath::INFINITY; + max = -idMath::INFINITY; + StartRecordTime( start ); + p_generic->MinMax( min, max, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( min2, max2, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( min == min2 && max == max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( v2min, v2max, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec2[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( v2min2, v2max2, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( v2min == v2min2 && v2max == v2max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec2[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[], indexes[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[], indexes[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestClamp +============ +*/ +void TestClamp( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Clamp( fdst0, fsrc0, -1.0f, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Clamp( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Clamp( fdst1, fsrc0, -1.0f, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Clamp( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ClampMin( fdst0, fsrc0, -1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ClampMin( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ClampMin( fdst1, fsrc0, -1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ClampMin( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ClampMax( fdst0, fsrc0, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ClampMax( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ClampMax( fdst1, fsrc0, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ClampMax( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMemcpy +============ +*/ +void TestMemcpy( void ) { + int i, j; + byte test0[8192]; + byte test1[8192]; + + idRandom random( RANDOM_SEED ); + + idLib::common->Printf("====================================\n" ); + + for ( i = 5; i < 8192; i += 31 ) { + for ( j = 0; j < i; j++ ) { + test0[j] = random.RandomInt( 255 ); + } + p_simd->Memcpy( test1, test0, 8192 ); + for ( j = 0; j < i; j++ ) { + if ( test1[j] != test0[j] ) { + idLib::common->Printf( " simd->Memcpy() "S_COLOR_RED"X\n" ); + return; + } + } + } + idLib::common->Printf( " simd->Memcpy() ok\n" ); +} + +/* +============ +TestMemset +============ +*/ +void TestMemset( void ) { + int i, j, k; + byte test[8192]; + + for ( i = 0; i < 8192; i++ ) { + test[i] = 0; + } + + for ( i = 5; i < 8192; i += 31 ) { + for ( j = -1; j <= 1; j++ ) { + p_simd->Memset( test, j, i ); + for ( k = 0; k < i; k++ ) { + if ( test[k] != (byte)j ) { + idLib::common->Printf( " simd->Memset() "S_COLOR_RED"X\n" ); + return; + } + } + } + } + idLib::common->Printf( " simd->Memset() ok\n" ); +} + +#define MATX_SIMD_EPSILON 1e-5f + +/* +============ +TestMatXMultiplyVecX +============ +*/ +void TestMatXMultiplyVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= NxN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX %dx%d*%dx1", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX %dx%d*%dx1 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX %dx6*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX %dx6*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX 6x%d*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX 6x%d*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXMultiplyAddVecX +============ +*/ +void TestMatXMultiplyAddVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= NxN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX %dx%d*%dx1", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX %dx%d*%dx1 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX %dx6*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX %dx6*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX 6x%d*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX 6x%d*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyVecX +============ +*/ +void TestMatXTransposeMultiplyVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= Nx6 * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulVecX %dx6*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulVecX %dx6*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulVecX 6x%d*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulVecX 6x%d*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyAddVecX +============ +*/ +void TestMatXTransposeMultiplyAddVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= Nx6 * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulAddVecX %dx6*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulAddVecX %dx6*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulAddVecX 6x%d*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulAddVecX 6x%d*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXMultiplyMatX +============ +*/ +#define TEST_VALUE_RANGE 10.0f +#define MATX_MATX_SIMD_EPSILON 1e-4f + +void TestMatXMultiplyMatX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX m1, m2, dst, tst; + + idLib::common->Printf("================= NxN * Nx6 ===================\n" ); + + // NxN * Nx6 + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX %dx%d*%dx6", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX %dx%d*%dx6 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx6 ===================\n" ); + + // 6xN * Nx6 + for ( i = 1; i <= 5; i++ ) { + m1.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX 6x%d*%dx6", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX 6x%d*%dx6 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6xN ===================\n" ); + + // Nx6 * 6xN + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX %dx6*6x%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX %dx6*6x%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6x6 * 6xN ===================\n" ); + + // 6x6 * 6xN + for ( i = 1; i <= 6; i++ ) { + m1.Random( 6, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX 6x6*6x%d", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX 6x6*6x%d %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyMatX +============ +*/ +void TestMatXTransposeMultiplyMatX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX m1, m2, dst, tst; + + idLib::common->Printf("================= Nx6 * NxN ===================\n" ); + + // Nx6 * NxN + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransMultiplyMatX %dx6*%dx%d", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransMultiplyMatX %dx6*%dx%d %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x6 ===================\n" ); + + // 6xN * 6x6 + for ( i = 1; i <= 6; i++ ) { + m1.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransMultiplyMatX 6x%d*6x6", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransMultiplyMatX 6x%d*6x6 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +#define MATX_LTS_SIMD_EPSILON 1.0f +#define MATX_LTS_SOLVE_SIZE 100 + +/* +============ +TestMatXLowerTriangularSolve +============ +*/ +void TestMatXLowerTriangularSolve( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX L; + idVecX x, b, tst; + + idLib::common->Printf("====================================\n" ); + + L.Random( MATX_LTS_SOLVE_SIZE, MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + x.SetSize( MATX_LTS_SOLVE_SIZE ); + b.Random( MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + + for ( i = 1; i < MATX_LTS_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_LowerTriangularSolve( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = x; + x.Zero(); + + PrintClocks( va( "generic->MatX_LowerTriangularSolve %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_LowerTriangularSolve( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = x.Compare( tst, MATX_LTS_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LowerTriangularSolve %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXLowerTriangularSolveTranspose +============ +*/ +void TestMatXLowerTriangularSolveTranspose( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX L; + idVecX x, b, tst; + + idLib::common->Printf("====================================\n" ); + + L.Random( MATX_LTS_SOLVE_SIZE, MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + x.SetSize( MATX_LTS_SOLVE_SIZE ); + b.Random( MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + + for ( i = 1; i < MATX_LTS_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_LowerTriangularSolveTranspose( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = x; + x.Zero(); + + PrintClocks( va( "generic->MatX_LowerTriangularSolveT %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_LowerTriangularSolveTranspose( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = x.Compare( tst, MATX_LTS_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LowerTriangularSolveT %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +#define MATX_LDLT_SIMD_EPSILON 0.1f +#define MATX_LDLT_FACTOR_SOLVE_SIZE 64 + +/* +============ +TestMatXLDLTFactor +============ +*/ +void TestMatXLDLTFactor( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX src, original, mat1, mat2; + idVecX invDiag1, invDiag2; + + idLib::common->Printf("====================================\n" ); + + original.SetSize( MATX_LDLT_FACTOR_SOLVE_SIZE, MATX_LDLT_FACTOR_SOLVE_SIZE ); + src.Random( MATX_LDLT_FACTOR_SOLVE_SIZE, MATX_LDLT_FACTOR_SOLVE_SIZE, 0, -1.0f, 1.0f ); + src.TransposeMultiply( original, src ); + + for ( i = 1; i < MATX_LDLT_FACTOR_SOLVE_SIZE; i++ ) { + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + mat1 = original; + invDiag1.Zero( MATX_LDLT_FACTOR_SOLVE_SIZE ); + StartRecordTime( start ); + p_generic->MatX_LDLTFactor( mat1, invDiag1, i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + + PrintClocks( va( "generic->MatX_LDLTFactor %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + mat2 = original; + invDiag2.Zero( MATX_LDLT_FACTOR_SOLVE_SIZE ); + StartRecordTime( start ); + p_simd->MatX_LDLTFactor( mat2, invDiag2, i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = mat1.Compare( mat2, MATX_LDLT_SIMD_EPSILON ) && invDiag1.Compare( invDiag2, MATX_LDLT_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LDLTFactor %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestBlendJoints +============ +*/ +void TestBlendJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointQuat baseJoints[COUNT] ); + ALIGN16( idJointQuat joints1[COUNT] ); + ALIGN16( idJointQuat joints2[COUNT] ); + ALIGN16( idJointQuat blendJoints[COUNT] ); + ALIGN16( int index[COUNT] ); + float lerp = 0.3f; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].w = 0.0f; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + blendJoints[i].q = angles.ToQuat(); + blendJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].w = 0.0f; + index[i] = i; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints1[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_generic->BlendJoints( joints1, blendJoints, lerp, index, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->BlendJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints2[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_simd->BlendJoints( joints2, blendJoints, lerp, index, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].t.Compare( joints2[i].t, 1e-3f ) ) { + break; + } + if ( !joints1[i].q.Compare( joints2[i].q, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->BlendJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointQuatsToJointMats +============ +*/ +void TestConvertJointQuatsToJointMats( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointQuat baseJoints[COUNT] ); + ALIGN16( idJointMat joints1[COUNT] ); + ALIGN16( idJointMat joints2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointQuatsToJointMats( joints1, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointQuatsToJointMats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointQuatsToJointMats( joints2, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].Compare( joints2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointQuatsToJointMats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointMatsToJointQuats +============ +*/ +void TestConvertJointMatsToJointQuats( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat baseJoints[COUNT] ); + ALIGN16( idJointQuat joints1[COUNT] ); + ALIGN16( idJointQuat joints2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 10.0f; + v[1] = srnd.CRandomFloat() * 10.0f; + v[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].SetTranslation( v ); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointMatsToJointQuats( joints1, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointMatsToJointQuats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointMatsToJointQuats( joints2, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].q.Compare( joints2[i].q, 1e-4f ) ) { + break; + } + if ( !joints1[i].t.Compare( joints2[i].t, 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointMatsToJointQuats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTransformJoints +============ +*/ +void TestTransformJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat joints[COUNT+1] ); + ALIGN16( idJointMat joints1[COUNT+1] ); + ALIGN16( idJointMat joints2[COUNT+1] ); + ALIGN16( int parents[COUNT+1] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->TransformJoints( joints1, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->TransformJoints( joints2, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i+1].Compare( joints2[i+1], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestUntransformJoints +============ +*/ +void TestUntransformJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat joints[COUNT+1] ); + ALIGN16( idJointMat joints1[COUNT+1] ); + ALIGN16( idJointMat joints2[COUNT+1] ); + ALIGN16( int parents[COUNT+1] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->UntransformJoints( joints1, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->UntransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->UntransformJoints( joints2, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i+1].Compare( joints2[i+1], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UntransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTransformVertsNew +============ +*/ +#undef NUMJOINTS +#undef NUMVERTS + +#define NUMJOINTS 64 +#define NUMVERTS COUNT +#define NUMWEIGHTSPERVERT 2 +#define NUMWEIGHTS (NUMVERTS*NUMWEIGHTSPERVERT) + +void TestTransformVertsNew( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[NUMVERTS] ); + ALIGN16( idDrawVert drawVerts2[NUMVERTS] ); + ALIGN16( idJointMat joints[NUMJOINTS] ); + ALIGN16( idVec4 base[NUMWEIGHTS*3] ); + ALIGN16( jointWeight_t weights[NUMWEIGHTS] ); + idBounds bounds1, bounds2; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < NUMJOINTS; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + } + + for ( i = 0; i < NUMWEIGHTS*3; i++ ) { + base[i][0] = srnd.CRandomFloat() * 2.0f; + base[i][1] = srnd.CRandomFloat() * 2.0f; + base[i][2] = srnd.CRandomFloat() * 2.0f; + base[i][3] = srnd.CRandomFloat(); + } + + for ( i = 0; i < NUMWEIGHTS; i++ ) { + weights[i].weight = srnd.CRandomFloat(); + weights[i].jointMatOffset = idMath::ClampInt( 0, NUMJOINTS-1, i * NUMJOINTS / NUMWEIGHTS + 0 ) * JOINTMAT_SIZE; + weights[i].nextVertexOffset = ( NUMWEIGHTSPERVERT - ( i % NUMWEIGHTSPERVERT ) ) * JOINTWEIGHT_SIZE; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsNew( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsNew()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsNew( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsNew() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsAndTangents( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsAndTangents()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsAndTangents( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 0.1f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 0.1f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsAndTangents() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsAndTangentsFast( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsAndTangentsFast()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsAndTangentsFast( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 0.1f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 0.1f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsAndTangentsFast() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTracePointCull +============ +*/ +void TestTracePointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[4] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + byte totalOr1 = 0, totalOr2 = 0; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 1, 0, 0 ) ); + planes[1].SetNormal( idVec3( -1, 0, 0 ) ); + planes[2].SetNormal( idVec3( 0, 1, 0 ) ); + planes[3].SetNormal( idVec3( 0, -1, 0 ) ); + planes[0][3] = -5.3f; + planes[1][3] = 5.3f; + planes[2][3] = -3.4f; + planes[3][3] = 3.4f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TracePointCull( cullBits1, totalOr1, 0.0f, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TracePointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TracePointCull( cullBits2, totalOr2, 0.0f, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + } + result = ( i >= COUNT && totalOr1 == totalOr2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TracePointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDecalPointCull +============ +*/ +void TestDecalPointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[6] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 1, 0, 0 ) ); + planes[1].SetNormal( idVec3( -1, 0, 0 ) ); + planes[2].SetNormal( idVec3( 0, 1, 0 ) ); + planes[3].SetNormal( idVec3( 0, -1, 0 ) ); + planes[4].SetNormal( idVec3( 0, 0, 1 ) ); + planes[5].SetNormal( idVec3( 0, 0, -1 ) ); + planes[0][3] = -5.3f; + planes[1][3] = 5.3f; + planes[2][3] = -4.4f; + planes[3][3] = 4.4f; + planes[4][3] = -3.5f; + planes[5][3] = 3.5f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DecalPointCull( cullBits1, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DecalPointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DecalPointCull( cullBits2, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DecalPointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestOverlayPointCull +============ +*/ +void TestOverlayPointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[2] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + ALIGN16( idVec2 texCoords1[COUNT] ); + ALIGN16( idVec2 texCoords2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 0.3f, 0.2f, 0.9f ) ); + planes[1].SetNormal( idVec3( 0.9f, 0.2f, 0.3f ) ); + planes[0][3] = -5.3f; + planes[1][3] = -4.3f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->OverlayPointCull( cullBits1, texCoords1, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->OverlayPointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->OverlayPointCull( cullBits2, texCoords2, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + if ( !texCoords1[i].Compare( texCoords2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->OverlayPointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveTriPlanes +============ +*/ +void TestDeriveTriPlanes( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( idPlane planes1[COUNT] ); + ALIGN16( idPlane planes2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveTriPlanes( planes1, drawVerts1, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveTriPlanes()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveTriPlanes( planes2, drawVerts2, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !planes1[i].Compare( planes2[i], 1e-1f, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveTriPlanes() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveTangents +============ +*/ +void TestDeriveTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( idPlane planes1[COUNT] ); + ALIGN16( idPlane planes2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveTangents( planes1, drawVerts1, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveTangents( planes2, drawVerts2, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + idVec3 v1, v2; + + v1 = drawVerts1[i].normal; + v1.Normalize(); + v2 = drawVerts2[i].normal; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[0]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[0]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[1]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[1]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + if ( !planes1[i].Compare( planes2[i], 1e-1f, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveUnsmoothedTangents +============ +*/ +void TestDeriveUnsmoothedTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( dominantTri_s dominantTris[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + + dominantTris[i].v2 = ( i + 1 + srnd.RandomInt( 8 ) ) % COUNT; + dominantTris[i].v3 = ( i + 9 + srnd.RandomInt( 8 ) ) % COUNT; + dominantTris[i].normalizationScale[0] = srnd.CRandomFloat(); + dominantTris[i].normalizationScale[1] = srnd.CRandomFloat(); + dominantTris[i].normalizationScale[2] = srnd.CRandomFloat(); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveUnsmoothedTangents( drawVerts1, dominantTris, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveUnsmoothedTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveUnsmoothedTangents( drawVerts2, dominantTris, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + idVec3 v1, v2; + + v1 = drawVerts1[i].normal; + v1.Normalize(); + v2 = drawVerts2[i].normal; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[0]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[0]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[1]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[1]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveUnsmoothedTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestNormalizeTangents +============ +*/ +void TestNormalizeTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].normal[j] = srnd.CRandomFloat() * 10.0f; + drawVerts1[i].tangents[0][j] = srnd.CRandomFloat() * 10.0f; + drawVerts1[i].tangents[1][j] = srnd.CRandomFloat() * 10.0f; + } + drawVerts2[i] = drawVerts1[i]; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->NormalizeTangents( drawVerts1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->NormalizeTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->NormalizeTangents( drawVerts2, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 1e-2f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 1e-2f ) ) { + break; + } + if ( !drawVerts1[i].tangents[1].Compare( drawVerts2[i].tangents[1], 1e-2f ) ) { + break; + } + + // since we're doing a lot of unaligned work, added this check to + // make sure xyz wasn't getting overwritten + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->NormalizeTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestGetTextureSpaceLightVectors +============ +*/ +void TestGetTextureSpaceLightVectors( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 texCoords1[COUNT] ); + ALIGN16( idVec4 texCoords2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + ALIGN16( idVec3 lightVectors1[COUNT] ); + ALIGN16( idVec3 lightVectors2[COUNT] ); + idVec3 lightOrigin; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + drawVerts[i].normal[j] = srnd.CRandomFloat(); + drawVerts[i].tangents[0][j] = srnd.CRandomFloat(); + drawVerts[i].tangents[1][j] = srnd.CRandomFloat(); + } + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateTextureSpaceLightVectors( lightVectors1, lightOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateTextureSpaceLightVectors()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateTextureSpaceLightVectors( lightVectors2, lightOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !lightVectors1[i].Compare( lightVectors2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateTextureSpaceLightVectors() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestGetSpecularTextureCoords +============ +*/ +void TestGetSpecularTextureCoords( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 texCoords1[COUNT] ); + ALIGN16( idVec4 texCoords2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + ALIGN16( idVec3 lightVectors1[COUNT] ); + ALIGN16( idVec3 lightVectors2[COUNT] ); + idVec3 lightOrigin, viewOrigin; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + drawVerts[i].normal[j] = srnd.CRandomFloat(); + drawVerts[i].tangents[0][j] = srnd.CRandomFloat(); + drawVerts[i].tangents[1][j] = srnd.CRandomFloat(); + } + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + viewOrigin[0] = srnd.CRandomFloat() * 100.0f; + viewOrigin[1] = srnd.CRandomFloat() * 100.0f; + viewOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateSpecularTextureCoords( texCoords1, lightOrigin, viewOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateSpecularTextureCoords()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateSpecularTextureCoords( texCoords2, lightOrigin, viewOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !texCoords1[i].Compare( texCoords2[i], 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateSpecularTextureCoords() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestCreateShadowCache +============ +*/ +void TestCreateShadowCache( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 vertexCache1[COUNT*2] ); + ALIGN16( idVec4 vertexCache2[COUNT*2] ); + ALIGN16( int originalVertRemap[COUNT] ); + ALIGN16( int vertRemap1[COUNT] ); + ALIGN16( int vertRemap2[COUNT] ); + ALIGN16( idVec3 lightOrigin ); + int numVerts1 = 0, numVerts2 = 0; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + } + originalVertRemap[i] = ( srnd.CRandomFloat() > 0.0f ) ? -1 : 0; + } + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + vertRemap1[j] = originalVertRemap[j]; + } + StartRecordTime( start ); + numVerts1 = p_generic->CreateShadowCache( vertexCache1, vertRemap1, lightOrigin, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateShadowCache()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + vertRemap2[j] = originalVertRemap[j]; + } + StartRecordTime( start ); + numVerts2 = p_simd->CreateShadowCache( vertexCache2, vertRemap2, lightOrigin, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( i < ( numVerts1 / 2 ) ) { + if ( !vertexCache1[i*2+0].Compare( vertexCache2[i*2+0], 1e-2f ) ) { + break; + } + if ( !vertexCache1[i*2+1].Compare( vertexCache2[i*2+1], 1e-2f ) ) { + break; + } + } + if ( vertRemap1[i] != vertRemap2[i] ) { + break; + } + } + + result = ( i >= COUNT && numVerts1 == numVerts2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateShadowCache() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateVertexProgramShadowCache( vertexCache1, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateVertexProgramShadowCache()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateVertexProgramShadowCache( vertexCache2, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !vertexCache1[i*2+0].Compare( vertexCache2[i*2+0], 1e-2f ) ) { + break; + } + if ( !vertexCache1[i*2+1].Compare( vertexCache2[i*2+1], 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateVertexProgramShadowCache() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +#if 0 +#include "Simd_TestData.h" + +/* +============ +TestShadowVolumes +============ +*/ +void TestShadowVolumes( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( int shadowIndexes1[8129] ); + ALIGN16( int shadowIndexes2[8129] ); + int numFacing1, numFacing2; + const char *result; + + numFacing1 = numFacing2 = 0; + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CountFacing( SIMD_testFacing, SIMD_numTestFacing ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CountFacing()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CountFacing( SIMD_testFacing, SIMD_numTestFacing ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CountFacing() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CountFacingCull( SIMD_testFacing, SIMD_numTestFacing, SIMD_testIndexes, SIMD_testCullBits ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CountFacing()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CountFacingCull( SIMD_testFacing, SIMD_numTestFacing, SIMD_testIndexes, SIMD_testCullBits ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CountFacingCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CreateSilTriangles( shadowIndexes1, SIMD_testFacing, ( const silEdge_s *)SIMD_testSilEdges, SIMD_numTestSilEdges ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CreateSilTriangles()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CreateSilTriangles( shadowIndexes2, SIMD_testFacing, ( const silEdge_s *)SIMD_testSilEdges, SIMD_numTestSilEdges ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < numFacing1; i++ ) { + if ( shadowIndexes1[i] != shadowIndexes2[i] ) { + break; + } + } + + result = ( i >= numFacing1 && numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CreateSilTriangles() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CreateCapTriangles( shadowIndexes1, SIMD_testFacing, SIMD_testIndexes, SIMD_numTestIndexes ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CreateCapTriangles()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CreateCapTriangles( shadowIndexes2, SIMD_testFacing, SIMD_testIndexes, SIMD_numTestIndexes ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < numFacing1; i++ ) { + if ( shadowIndexes1[i] != shadowIndexes2[i] ) { + break; + } + } + + result = ( i >= numFacing1 && numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CreateCapTriangles() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} +#endif + +/* +============ +TestSoundUpSampling +============ +*/ +#define SOUND_UPSAMPLE_EPSILON 1.0f + +void TestSoundUpSampling( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( short pcm[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float ogg0[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float ogg1[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float samples1[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float samples2[MIXBUFFER_SAMPLES*2] ); + float *ogg[2]; + int kHz, numSpeakers; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < MIXBUFFER_SAMPLES*2; i++ ) { + pcm[i] = srnd.RandomInt( (1<<16) ) - (1<<15); + ogg0[i] = srnd.RandomFloat(); + ogg1[i] = srnd.RandomFloat(); + } + + ogg[0] = ogg0; + ogg[1] = ogg1; + + for ( numSpeakers = 1; numSpeakers <= 2; numSpeakers++ ) { + + for ( kHz = 11025; kHz <= 44100; kHz *= 2 ) { + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->UpSamplePCMTo44kHz( samples1, pcm, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->UpSamplePCMTo44kHz( %d, %d )", kHz, numSpeakers ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->UpSamplePCMTo44kHz( samples2, pcm, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*numSpeakers; i++ ) { + if ( idMath::Fabs( samples1[i] - samples2[i] ) > SOUND_UPSAMPLE_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*numSpeakers ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UpSamplePCMTo44kHz( %d, %d ) %s", kHz, numSpeakers, result ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksSIMD, bestClocksGeneric ); + } + } + + for ( numSpeakers = 1; numSpeakers <= 2; numSpeakers++ ) { + + for ( kHz = 11025; kHz <= 44100; kHz *= 2 ) { + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->UpSampleOGGTo44kHz( samples1, ogg, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->UpSampleOGGTo44kHz( %d, %d )", kHz, numSpeakers ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->UpSampleOGGTo44kHz( samples2, ogg, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*numSpeakers; i++ ) { + if ( idMath::Fabs( samples1[i] - samples2[i] ) > SOUND_UPSAMPLE_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UpSampleOGGTo44kHz( %d, %d ) %s", kHz, numSpeakers, result ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksSIMD, bestClocksGeneric ); + } + } +} + +/* +============ +TestSoundMixing +============ +*/ +#define SOUND_MIX_EPSILON 2.0f + +void TestSoundMixing( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float origMixBuffer[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float mixBuffer1[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float mixBuffer2[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float samples[MIXBUFFER_SAMPLES*6] ); + ALIGN16( short outSamples1[MIXBUFFER_SAMPLES*6] ); + ALIGN16( short outSamples2[MIXBUFFER_SAMPLES*6] ); + float lastV[6]; + float currentV[6]; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < 6; i++ ) { + lastV[i] = srnd.CRandomFloat(); + currentV[i] = srnd.CRandomFloat(); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + origMixBuffer[i] = srnd.CRandomFloat(); + samples[i] = srnd.RandomInt( (1<<16) ) - (1<<15); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundTwoSpeakerMono( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundTwoSpeakerMono()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundTwoSpeakerMono( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundTwoSpeakerMono() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundTwoSpeakerStereo( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundTwoSpeakerStereo()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundTwoSpeakerStereo( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundTwoSpeakerStereo() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundSixSpeakerMono( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundSixSpeakerMono()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundSixSpeakerMono( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundSixSpeakerMono() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundSixSpeakerStereo( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundSixSpeakerStereo()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundSixSpeakerStereo( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundSixSpeakerStereo() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + origMixBuffer[i] = srnd.RandomInt( (1<<17) ) - (1<<16); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixedSoundToSamples( outSamples1, mixBuffer1, MIXBUFFER_SAMPLES*6 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixedSoundToSamples()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixedSoundToSamples( outSamples2, mixBuffer2, MIXBUFFER_SAMPLES*6 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( outSamples1[i] != outSamples2[i] ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixedSoundToSamples() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMath +============ +*/ +void TestMath( void ) { + int i; + TIME_TYPE start, end, bestClocks; + + idLib::common->Printf("====================================\n" ); + + float tst = -1.0f; + float tst2 = 1.0f; + float testvar = 1.0f; + idRandom rnd; + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = fabs( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + int tmp = * ( int * ) &tst; + tmp &= 0x7FFFFFFF; + tst = * ( float * ) &tmp; + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.01f; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + } + PrintClocks( " sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt64( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt64( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = tst * idMath::RSqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::RSqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::SinCos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos16( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( "idMath::SinCos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow16( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log16( tst )", 1, bestClocks ); + + idLib::common->Printf( "testvar = %f\n", testvar ); + + idMat3 resultMat3; + idQuat fromQuat, toQuat, resultQuat; + idCQuat cq; + idAngles ang; + + fromQuat = idAngles( 30, 45, 0 ).ToQuat(); + toQuat = idAngles( 45, 0, 0 ).ToQuat(); + cq = idAngles( 30, 45, 0 ).ToQuat().ToCQuat(); + ang = idAngles( 30, 40, 50 ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = fromQuat.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::ToMat3()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat.Slerp( fromQuat, toQuat, 0.3f ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::Slerp()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = cq.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idCQuat::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = ang.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = ang.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToMat3()", 1, bestClocks ); +} + +/* +============ +idSIMD::Test_f +============ +*/ +void idSIMD::Test_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jsinger: at the moment this doesn't compile on xenon, but we also shouldn't need it anyway +#ifndef _XENON +#ifdef _WINDOWS + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); +#endif // _WINDOWS + + p_simd = processor; + p_generic = generic; + + if ( idStr::Length( args.Argv( 1 ) ) != 0 ) { + cpuid_t cpuid = idLib::sys->GetProcessorId(); + idStr argString = args.Args(); + + argString.Replace( " ", "" ); + +#ifdef _WINDOWS + if ( idStr::Icmp( argString, "MMX" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) ) { + common->Printf( "CPU does not support MMX\n" ); + return; + } + p_simd = new idSIMD_MMX; + } else if ( idStr::Icmp( argString, "3DNow" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_3DNOW ) ) { + common->Printf( "CPU does not support MMX & 3DNow\n" ); + return; + } + p_simd = new idSIMD_3DNow; + } else if ( idStr::Icmp( argString, "SSE" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) ) { + common->Printf( "CPU does not support MMX & SSE\n" ); + return; + } + p_simd = new idSIMD_SSE; + } else if ( idStr::Icmp( argString, "SSE2" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) || !( cpuid & CPUID_SSE2 ) ) { + common->Printf( "CPU does not support MMX & SSE & SSE2\n" ); + return; + } + p_simd = new idSIMD_SSE2; + } else if ( idStr::Icmp( argString, "SSE3" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) || !( cpuid & CPUID_SSE2 ) || !( cpuid & CPUID_SSE3 ) ) { + common->Printf( "CPU does not support MMX & SSE & SSE2 & SSE3\n" ); + return; + } + p_simd = new idSIMD_SSE3(); + } else +#endif // _WINDOWS + if ( idStr::Icmp( argString, "AltiVec" ) == 0 ) { + if ( !( cpuid & CPUID_ALTIVEC ) ) { + common->Printf( "CPU does not support AltiVec\n" ); + return; + } +#ifdef MACOS_X + p_simd = new idSIMD_AltiVec(); +#endif + } else { + common->Printf( "invalid argument, use: MMX, 3DNow, SSE, SSE2, SSE3, AltiVec\n" ); + return; + } + } + + idLib::common->SetRefreshOnPrint( true ); + + idLib::common->Printf( "using %s for SIMD processing\n", p_simd->GetName() ); + + GetBaseClocks(); +/* + TestMath(); + TestAdd(); + TestSub(); + TestMul(); + TestDiv(); + TestMulAdd(); + TestMulSub(); + TestDot(); + TestCompare(); + TestMinMax(); + TestClamp(); + TestMemcpy(); + TestMemset(); + + TestMatXMultiplyVecX(); + TestMatXMultiplyAddVecX(); + TestMatXTransposeMultiplyVecX(); + TestMatXTransposeMultiplyAddVecX(); + TestMatXMultiplyMatX(); + TestMatXTransposeMultiplyMatX(); + TestMatXLowerTriangularSolve(); + TestMatXLowerTriangularSolveTranspose(); + TestMatXLDLTFactor(); + + idLib::common->Printf("====================================\n" ); +*/ + TestBlendJoints(); + TestConvertJointQuatsToJointMats(); + TestConvertJointMatsToJointQuats(); + TestTransformJoints(); + TestUntransformJoints(); + TestTransformVertsNew(); +/* TestTracePointCull(); + TestDecalPointCull(); + TestOverlayPointCull(); + TestDeriveTriPlanes(); + TestDeriveTangents(); + TestDeriveUnsmoothedTangents(); + TestNormalizeTangents(); + TestGetTextureSpaceLightVectors(); + TestGetSpecularTextureCoords(); + TestCreateShadowCache(); + + idLib::common->Printf("====================================\n" ); + + TestSoundUpSampling(); + TestSoundMixing(); +*/ + idLib::common->SetRefreshOnPrint( false ); + + if ( p_simd != processor ) { + delete p_simd; + } + p_simd = NULL; + p_generic = NULL; + +#ifdef _WINDOWS + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL ); +#endif // _WINDOWS +#endif +// RAVEN END +} diff --git a/source/idlib/math/Simd.h b/source/idlib/math/Simd.h new file mode 100644 index 0000000..e93a8cf --- /dev/null +++ b/source/idlib/math/Simd.h @@ -0,0 +1,229 @@ +#ifndef __MATH_SIMD_H__ +#define __MATH_SIMD_H__ + +/* +=============================================================================== + + Single Instruction Multiple Data (SIMD) + + For optimal use data should be aligned on a 16 byte boundary. + All idSIMDProcessor routines are thread safe. + +=============================================================================== +*/ + +class idSIMD { +public: + static void Init( void ); + static void InitProcessor( const char *module, bool forceGeneric ); + static void Shutdown( void ); + static void Test_f( const class idCmdArgs &args ); +}; + + +/* +=============================================================================== + + virtual base class for different SIMD processors + +=============================================================================== +*/ + +#ifdef _WIN32 +#define VPCALL __fastcall +#else +#define VPCALL +#endif + +class idVec2; +class idVec3; +#ifdef _XENON +class __declspec(align(16)) idVec4; +#else +class idVec4; +#endif +class idVec5; +class idVec6; +class idVecX; +class idMat2; +class idMat3; +class idMat4; +class idMat5; +class idMat6; +class idMatX; +class idPlane; +class idBounds; +class idDrawVert; +class idJointQuat; +class idJointMat; +struct dominantTri_s; +struct jointWeight_t; +struct silEdge_s; + +// RAVEN BEGIN +// dluetscher: declared new vertex format +#ifdef _MD5R_SUPPORT +class rvSilTraceVertT; +#endif +// RAVEN END + +const int MIXBUFFER_SAMPLES = 4096; + +typedef enum { + SPEAKER_LEFT = 0, + SPEAKER_RIGHT, + SPEAKER_CENTER, + SPEAKER_LFE, + SPEAKER_BACKLEFT, + SPEAKER_BACKRIGHT +} speakerLabel; + + +// RAVEN BEGIN +// jsinger: forward declare and use a typedef so that xenon doesn't have to use inheritence for the SIMD stuff +#ifdef _XENON +class idSIMD_Xenon; +typedef idSIMD_Xenon idSIMDProcessor; +#else +// RAVEN END +class idSIMDProcessor { +public: + idSIMDProcessor( void ) { cpuid = CPUID_NONE; } + virtual ~idSIMDProcessor( void ) { } + + cpuid_t cpuid; + + virtual const char * VPCALL GetName( void ) const = 0; + + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Div( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ) = 0; + + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) = 0; + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ) = 0; + + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) = 0; + + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ) = 0; + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ) = 0; + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ) = 0; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ) = 0; + virtual void VPCALL Memset( void *dst, const int val, const int count ) = 0; + + // these assume 16 byte aligned and 16 byte padded memory + virtual void VPCALL Zero16( float *dst, const int count ) = 0; + virtual void VPCALL Negate16( float *dst, const int count ) = 0; + virtual void VPCALL Copy16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ) = 0; + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ) = 0; + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ) = 0; + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ) = 0; + + // idMatX operations + virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) = 0; + virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) = 0; + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ) = 0; + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) = 0; + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) = 0; + + // rendering + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) = 0; + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) = 0; + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) = 0; + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) = 0; + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) = 0; + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) = 0; + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) = 0; + virtual int VPCALL ShadowVolume_CountFacing( const byte *facing, const int numFaces ) = 0; + virtual int VPCALL ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ) = 0; + virtual int VPCALL ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ) = 0; + virtual int VPCALL ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ) = 0; + + // sound mixing + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ) = 0; + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) = 0; + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) = 0; + virtual void VPCALL MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) = 0; + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) = 0; + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) = 0; + virtual void VPCALL MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) = 0; + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) = 0; + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) = 0; + + // rvSilTraceVertT operations +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts +#ifdef _MD5R_SUPPORT + virtual void VPCALL JointMat_MultiplyMats( float *destMats, const idJointMat *src1Mats, const idJointMat *src2Mats, int *transformPalette, int transformCount ) = 0; + virtual void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ) = 0; // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ) = 0; // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const unsigned short *indexes, const int numIndexes ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int *indexes, const int count ) = 0; +#endif +// RAVEN END +}; +// RAVEN BEGIN +#endif +// RAVEN END + +// pointer to SIMD processor +extern idSIMDProcessor *SIMDProcessor; + +#endif /* !__MATH_SIMD_H__ */ diff --git a/source/idlib/math/Simd_3DNow.cpp b/source/idlib/math/Simd_3DNow.cpp new file mode 100644 index 0000000..d8cff5c --- /dev/null +++ b/source/idlib/math/Simd_3DNow.cpp @@ -0,0 +1,270 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_3DNow.h" + + +//=============================================================== +// +// 3DNow! implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +/* +============ +idSIMD_3DNow::GetName +============ +*/ +const char * idSIMD_3DNow::GetName( void ) const { + return "MMX & 3DNow!"; +} + +// Very optimized memcpy() routine for all AMD Athlon and Duron family. +// This code uses any of FOUR different basic copy methods, depending +// on the transfer size. +// NOTE: Since this code uses MOVNTQ (also known as "Non-Temporal MOV" or +// "Streaming Store"), and also uses the software prefetchnta instructions, +// be sure you're running on Athlon/Duron or other recent CPU before calling! + +#define TINY_BLOCK_COPY 64 // upper limit for movsd type copy +// The smallest copy uses the X86 "movsd" instruction, in an optimized +// form which is an "unrolled loop". + +#define IN_CACHE_COPY 64 * 1024 // upper limit for movq/movq copy w/SW prefetch +// Next is a copy that uses the MMX registers to copy 8 bytes at a time, +// also using the "unrolled loop" optimization. This code uses +// the software prefetch instruction to get the data into the cache. + +#define UNCACHED_COPY 197 * 1024 // upper limit for movq/movntq w/SW prefetch +// For larger blocks, which will spill beyond the cache, it's faster to +// use the Streaming Store instruction MOVNTQ. This write instruction +// bypasses the cache and writes straight to main memory. This code also +// uses the software prefetch instruction to pre-read the data. +// USE 64 * 1024 FOR THIS VALUE IF YOU'RE ALWAYS FILLING A "CLEAN CACHE" + +#define BLOCK_PREFETCH_COPY infinity // no limit for movq/movntq w/block prefetch +#define CACHEBLOCK 80h // number of 64-byte blocks (cache lines) for block prefetch +// For the largest size blocks, a special technique called Block Prefetch +// can be used to accelerate the read operations. Block Prefetch reads +// one address per cache line, for a series of cache lines, in a short loop. +// This is faster than using software prefetch. The technique is great for +// getting maximum read bandwidth, especially in DDR memory systems. + +/* +================ +idSIMD_3DNow::Memcpy + + optimized memory copy routine that handles all alignment cases and block sizes efficiently +================ +*/ +void VPCALL idSIMD_3DNow::Memcpy( void *dest, const void *src, const int n ) { + __asm { + + mov ecx, [n] // number of bytes to copy + mov edi, [dest] // destination + mov esi, [src] // source + mov ebx, ecx // keep a copy of count + + cld + cmp ecx, TINY_BLOCK_COPY + jb $memcpy_ic_3 // tiny? skip mmx copy + + cmp ecx, 32*1024 // don't align between 32k-64k because + jbe $memcpy_do_align // it appears to be slower + cmp ecx, 64*1024 + jbe $memcpy_align_done +$memcpy_do_align: + mov ecx, 8 // a trick that's faster than rep movsb... + sub ecx, edi // align destination to qword + and ecx, 111b // get the low bits + sub ebx, ecx // update copy count + neg ecx // set up to jump into the array + add ecx, offset $memcpy_align_done + jmp ecx // jump to array of movsb's + +align 4 + movsb + movsb + movsb + movsb + movsb + movsb + movsb + movsb + +$memcpy_align_done: // destination is dword aligned + mov ecx, ebx // number of bytes left to copy + shr ecx, 6 // get 64-byte block count + jz $memcpy_ic_2 // finish the last few bytes + + cmp ecx, IN_CACHE_COPY/64 // too big 4 cache? use uncached copy + jae $memcpy_uc_test + +// This is small block copy that uses the MMX registers to copy 8 bytes +// at a time. It uses the "unrolled loop" optimization, and also uses +// the software prefetch instruction to get the data into the cache. +align 16 +$memcpy_ic_1: // 64-byte block copies, in-cache copy + + prefetchnta [esi + (200*64/34+192)] // start reading ahead + + movq mm0, [esi+0] // read 64 bits + movq mm1, [esi+8] + movq [edi+0], mm0 // write 64 bits + movq [edi+8], mm1 // note: the normal movq writes the + movq mm2, [esi+16] // data to cache; a cache line will be + movq mm3, [esi+24] // allocated as needed, to store the data + movq [edi+16], mm2 + movq [edi+24], mm3 + movq mm0, [esi+32] + movq mm1, [esi+40] + movq [edi+32], mm0 + movq [edi+40], mm1 + movq mm2, [esi+48] + movq mm3, [esi+56] + movq [edi+48], mm2 + movq [edi+56], mm3 + + add esi, 64 // update source pointer + add edi, 64 // update destination pointer + dec ecx // count down + jnz $memcpy_ic_1 // last 64-byte block? + +$memcpy_ic_2: + mov ecx, ebx // has valid low 6 bits of the byte count +$memcpy_ic_3: + shr ecx, 2 // dword count + and ecx, 1111b // only look at the "remainder" bits + neg ecx // set up to jump into the array + add ecx, offset $memcpy_last_few + jmp ecx // jump to array of movsd's + +$memcpy_uc_test: + cmp ecx, UNCACHED_COPY/64 // big enough? use block prefetch copy + jae $memcpy_bp_1 + +$memcpy_64_test: + or ecx, ecx // tail end of block prefetch will jump here + jz $memcpy_ic_2 // no more 64-byte blocks left + +// For larger blocks, which will spill beyond the cache, it's faster to +// use the Streaming Store instruction MOVNTQ. This write instruction +// bypasses the cache and writes straight to main memory. This code also +// uses the software prefetch instruction to pre-read the data. +align 16 +$memcpy_uc_1: // 64-byte blocks, uncached copy + + prefetchnta [esi + (200*64/34+192)] // start reading ahead + + movq mm0,[esi+0] // read 64 bits + add edi,64 // update destination pointer + movq mm1,[esi+8] + add esi,64 // update source pointer + movq mm2,[esi-48] + movntq [edi-64], mm0 // write 64 bits, bypassing the cache + movq mm0,[esi-40] // note: movntq also prevents the CPU + movntq [edi-56], mm1 // from READING the destination address + movq mm1,[esi-32] // into the cache, only to be over-written + movntq [edi-48], mm2 // so that also helps performance + movq mm2,[esi-24] + movntq [edi-40], mm0 + movq mm0,[esi-16] + movntq [edi-32], mm1 + movq mm1,[esi-8] + movntq [edi-24], mm2 + movntq [edi-16], mm0 + dec ecx + movntq [edi-8], mm1 + jnz $memcpy_uc_1 // last 64-byte block? + + jmp $memcpy_ic_2 // almost done + +// For the largest size blocks, a special technique called Block Prefetch +// can be used to accelerate the read operations. Block Prefetch reads +// one address per cache line, for a series of cache lines, in a short loop. +// This is faster than using software prefetch, in this case. +// The technique is great for getting maximum read bandwidth, +// especially in DDR memory systems. +$memcpy_bp_1: // large blocks, block prefetch copy + + cmp ecx, CACHEBLOCK // big enough to run another prefetch loop? + jl $memcpy_64_test // no, back to regular uncached copy + + mov eax, CACHEBLOCK / 2 // block prefetch loop, unrolled 2X + add esi, CACHEBLOCK * 64 // move to the top of the block +align 16 +$memcpy_bp_2: + mov edx, [esi-64] // grab one address per cache line + mov edx, [esi-128] // grab one address per cache line + sub esi, 128 // go reverse order + dec eax // count down the cache lines + jnz $memcpy_bp_2 // keep grabbing more lines into cache + + mov eax, CACHEBLOCK // now that it's in cache, do the copy +align 16 +$memcpy_bp_3: + movq mm0, [esi ] // read 64 bits + movq mm1, [esi+ 8] + movq mm2, [esi+16] + movq mm3, [esi+24] + movq mm4, [esi+32] + movq mm5, [esi+40] + movq mm6, [esi+48] + movq mm7, [esi+56] + add esi, 64 // update source pointer + movntq [edi ], mm0 // write 64 bits, bypassing cache + movntq [edi+ 8], mm1 // note: movntq also prevents the CPU + movntq [edi+16], mm2 // from READING the destination address + movntq [edi+24], mm3 // into the cache, only to be over-written, + movntq [edi+32], mm4 // so that also helps performance + movntq [edi+40], mm5 + movntq [edi+48], mm6 + movntq [edi+56], mm7 + add edi, 64 // update dest pointer + + dec eax // count down + + jnz $memcpy_bp_3 // keep copying + sub ecx, CACHEBLOCK // update the 64-byte block count + jmp $memcpy_bp_1 // keep processing chunks + +// The smallest copy uses the X86 "movsd" instruction, in an optimized +// form which is an "unrolled loop". Then it handles the last few bytes. +align 4 + movsd + movsd // perform last 1-15 dword copies + movsd + movsd + movsd + movsd + movsd + movsd + movsd + movsd // perform last 1-7 dword copies + movsd + movsd + movsd + movsd + movsd + movsd + +$memcpy_last_few: // dword aligned from before movsd's + mov ecx, ebx // has valid low 2 bits of the byte count + and ecx, 11b // the last few cows must come home + jz $memcpy_final // no more, let's leave + rep movsb // the last 1, 2, or 3 bytes + +$memcpy_final: + emms // clean up the MMX state + sfence // flush the write buffer + mov eax, [dest] // ret value = destination pointer + + } +} + +#endif // _WINDOWS diff --git a/source/idlib/math/Simd_3DNow.h b/source/idlib/math/Simd_3DNow.h new file mode 100644 index 0000000..deabec7 --- /dev/null +++ b/source/idlib/math/Simd_3DNow.h @@ -0,0 +1,23 @@ + +#ifndef __MATH_SIMD_3DNOW_H__ +#define __MATH_SIMD_3DNOW_H__ + +/* +=============================================================================== + + 3DNow! implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_3DNow : public idSIMD_MMX { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); + +#endif +}; + +#endif /* !__MATH_SIMD_3DNOW_H__ */ diff --git a/source/idlib/math/Simd_AltiVec.cpp b/source/idlib/math/Simd_AltiVec.cpp new file mode 100644 index 0000000..f40bdff --- /dev/null +++ b/source/idlib/math/Simd_AltiVec.cpp @@ -0,0 +1,11159 @@ +// Copyright (C) 2004 Id Software, Inc. +// + + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_AltiVec.h" +#include + +#if defined(MACOS_X) && defined(__ppc__) + +#ifdef PPC_INTRINSICS + #include +#endif + +#if defined(bool) && __GNUC__ < 4 + #undef bool +#endif + +// Doom3 SIMD Library version 0.5 +// Patrick Flanagan (pflanagan@apple.com) +// Sanjay Patel (spatel@apple.com) +// Architecture & Performance Group, Apple Computer + + +//=============================================================== +// +// AltiVec implementation of idSIMDProcessor +// +//=============================================================== + +// Data struct sizes + +// 64 bytes +#define DRAWVERT_OFFSET 16 +// 16 bytes each, 4 floats +#define PLANE_OFFSET 4 +// 16 bytes each, 4 floats +#define IDVEC4_OFFSET 4 + +// Alignment tests +#define IS_16BYTE_ALIGNED( x ) ( ( (unsigned long)&x & 0x0F ) == 0 ) +#define NOT_16BYTE_ALIGNED( x ) ( ( (unsigned long)&x & 0x0F) != 0 ) + +// Aligned storing floats +#define ALIGNED_STORE2( ADDR, V0, V1 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ) + +#define ALIGNED_STORE3( ADDR, V0, V1, V2 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ) + +#define ALIGNED_STORE4( ADDR, V0, V1, V2, V3 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ) + +#define ALIGNED_STORE6( ADDR, V0, V1, V2, V3, V4, V5 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ); \ + vec_st( V4, 64, ADDR ); \ + vec_st( V5, 80, ADDR ) + +#define ALIGNED_STORE8( ADDR, V0, V1, V2, V3, V4, V5, V6, V7 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ); \ + vec_st( V4, 64, ADDR ); \ + vec_st( V5, 80, ADDR ); \ + vec_st( V6, 96, ADDR ); \ + vec_st( V7, 112, ADDR ) + +// Unaligned storing floats. These assume that we can trash the input +#define UNALIGNED_STORE1( ADDR, V0 ) { \ + /* use store element */ \ + vector unsigned char ULStoreMacroPerm = vec_lvsr( 0, ADDR ); \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + vec_ste( V0, 0, ADDR ); \ + vec_ste( V0, 4, ADDR ); \ + vec_ste( V0, 8, ADDR ); \ + vec_ste( V0, 12, ADDR ); \ + } + +#define UNALIGNED_STORE2( ADDR, V0, V1 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 31, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); } + +#define UNALIGNED_STORE3( ADDR, V0, V1, V2 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 47, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); } + +#define UNALIGNED_STORE4( ADDR, V0, V1, V2, V3 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 63, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); } + +#define UNALIGNED_STORE6( ADDR, V0, V1, V2, V3, V4, V5 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 95, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + V4 = vec_perm( V4, V4, ULStoreMacroPerm ); \ + V5 = vec_perm( V5, V5, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5, ULStoreVal6, ULStoreVal7; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, V4, ULStoreMacroMask ); \ + ULStoreVal6 = vec_sel( V4, V5, ULStoreMacroMask ); \ + ULStoreVal7 = vec_sel( V5, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); \ + vec_st( ULStoreVal6, 79, ADDR ); \ + vec_st( ULStoreVal7, 95, ADDR ); } + +#define UNALIGNED_STORE9( ADDR, V0, V1, V2, V3, V4, V5, V6, V7, V8 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 143, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + V4 = vec_perm( V4, V4, ULStoreMacroPerm ); \ + V5 = vec_perm( V5, V5, ULStoreMacroPerm ); \ + V6 = vec_perm( V6, V6, ULStoreMacroPerm ); \ + V7 = vec_perm( V7, V7, ULStoreMacroPerm ); \ + V8 = vec_perm( V8, V8, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5, ULStoreVal6, ULStoreVal7; \ + vector float ULStoreVal8, ULStoreVal9, ULStoreVal10; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, V4, ULStoreMacroMask ); \ + ULStoreVal6 = vec_sel( V4, V5, ULStoreMacroMask ); \ + ULStoreVal7 = vec_sel( V5, V6, ULStoreMacroMask ); \ + ULStoreVal8 = vec_sel( V6, V7, ULStoreMacroMask ); \ + ULStoreVal9 = vec_sel( V7, V8, ULStoreMacroMask ); \ + ULStoreVal10 = vec_sel( V8, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); \ + vec_st( ULStoreVal6, 79, ADDR ); \ + vec_st( ULStoreVal7, 95, ADDR ); \ + vec_st( ULStoreVal8, 111, ADDR ); \ + vec_st( ULStoreVal9, 127, ADDR ); \ + vec_st( ULStoreVal10, 143, ADDR ); } + + +/* +============ +idSIMD_AltiVec::GetName +============ +*/ +const char *idSIMD_AltiVec::GetName( void ) const { + return "AltiVec"; +} + +/* + Helper Functions +*/ +#if 0 +// Prints the values of a vector, useful for debugging but +// should never be called in real code +inline void debugPrintVector( vector float v, char *msg ) { + printf("%s -- %vf\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned int v, char *msg ) { + printf("%s -- %vd\n", msg, v ); +} + +inline void debugPrintVector( vector bool int v, char *msg ) { + printf("%s -- %vi\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned char v, char *msg ) { + printf("%s -- %vuc\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned short v, char *msg ) { + printf("%s -- %vs\n", msg, v ); +} +#endif +/* +=============== + Reciprocal + + For each element in vector: + n = 1 / n +=============== +*/ + +// Use Newton-Raphson to calculate reciprocal of a vector +inline vector float Reciprocal( vector float v ) { + //Get the reciprocal estimate + vector float estimate = vec_re( v ); + //One round of Newton-Raphson refinement + return vec_madd( vec_nmsub( estimate, v, (vector float) (1.0) ), estimate, estimate ); +} + +/* +=============== + ReciprocalSquareRoot + + For each element in vector: + n = 1 / sqrt(n) +=============== +*/ +// Reciprocal square root estimate of a vector +inline vector float ReciprocalSquareRoot( vector float v ) { + //Get the square root reciprocal estimate + vector float zero = (vector float)(0); + vector float oneHalf = (vector float)(0.5); + vector float one = (vector float)(1.0); + vector float estimate = vec_rsqrte( vec_max( v, (vector float)(FLT_MIN) ) ); + + //One round of Newton-Raphson refinement + vector float estimateSquared = vec_madd( estimate, estimate, zero ); + vector float halfEstimate = vec_madd( estimate, oneHalf, zero ); + return vec_madd( vec_nmsub( v, estimateSquared, one ), halfEstimate, estimate ); +} + + +/* +=============== + Divide + + For each element in vectors: + n = a / b +=============== +*/ +// Use reciprocal estimate and multiply to divide a vector +inline vector float Divide( vector float a, vector float b ) { + return vec_madd( a, Reciprocal( b ), (vector float)(0) ); +} + +/* +=============== + loadSplatUnalignedScalar + + For each element in vector: + n = s +=============== +*/ +inline vector float loadSplatUnalignedScalar( const float *s ) { + vector unsigned char splatMap = vec_lvsl( 0, s ); + vector float v = vec_ld( 0, s ); + splatMap = (vector unsigned char) vec_splat( (vector float) splatMap, 0 ); + return vec_perm( v, v, splatMap ); +} + +/* +=============== + VectorATan16 + + For each element in vector: + n = idMath::ATan16( x, y ) +=============== +*/ +// calculates arc tangent of a vector with 16 bits of precision, based on atan16 in idMath +inline vector float VectorATan16( vector float x, vector float y ) { + + vector float xDivY = Divide( x, y ); + vector float yDivX = Divide( y, x ); + vector float zeroVector = (vector float)(0); + + vector bool int vecCmp = vec_cmpgt( vec_abs( y ), vec_abs( x ) ); + vector float vecA = vec_sel( yDivX, xDivY, vecCmp ); + vector bool int vecCmp2 = vec_cmplt( vecA, zeroVector ); + vector float vecS = vec_madd( vecA, vecA, (vector float)(0) ); + + // do calculation for S + vector float vecWork1 = vec_madd( (vector float)(0.0028662257f), vecS, (vector float)(-0.0161657367f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.0429096138f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.0752896400f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.1065626393f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.1420889944f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.1999355085f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.3333314528f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(1) ); + + // get the regular S value + vecS = vec_madd( vecWork1, vecA, (vector float)(0) ); + + // calculate what to return if y > x + vector float negSPlusHalfPI = vec_madd( vecS, (vector float)(-1), (vector float)(0.5f * 3.14159265358979323846f) ); + vector float negSMinusHalfPI = vec_madd( vecS, (vector float)(-1), (vector float)(-0.5f * 3.14159265358979323846f) ); + vector float modRet = vec_sel( negSPlusHalfPI, negSMinusHalfPI, vecCmp2 ); + + return vec_sel( modRet, vecS, vecCmp ); +} + +/* +=============== + VectorSin16 + + For each element in vector: + n = idMath::Sin16( v ) +=============== +*/ +inline vector float VectorSin16( vector float v ) { + vector float zero = (vector float)(0); + +#if 0 + // load up half PI and use it to calculate the rest of the values. This is + // sometimes cheaper than loading them from memory + + vector float halfPI = (vector float) ( 0.5f * 3.14159265358979323846f ); + vector float PI = vec_add( halfPI, halfPI ); + vector float oneandhalfPI = vec_add( PI, halfPI ); + vector float twoPI = vec_add( oneandhalfPI, halfPI ); +#else + vector float halfPI = (vector float) ( 0.5f * 3.14159265358979323846f ); + vector float PI = (vector float)(3.14159265358979323846f); + vector float oneandhalfPI = (vector float)(3.14159265358979323846f + ( 0.5f * 3.14159265358979323846f ) ); + vector float twoPI = (vector float)( 2.0f * 3.14159265358979323846f); +#endif + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4; + + vector float vecMod; + vector float vecResult; + + // fix the range if needbe + vecMod = vec_floor( Divide( v, twoPI ) ); + vecResult = vec_nmsub( vecMod, twoPI, v ); + + vector float vecPIminusA = vec_sub( PI, vecResult ); + vector float vecAminus2PI = vec_sub( vecResult, twoPI ); + + vecCmp1 = vec_cmplt( vecResult, PI ); + vecCmp2 = vec_cmpgt( vecResult, halfPI ); + + // these are the ones where a > PI + HALF_PI so set a = a - TWO_PI + vecCmp3 = vec_cmpgt( vecResult, oneandhalfPI ); + + // we also want to set a = PI - a everywhere that !(a < PI) and !(a > PI + HALF_PI) + vecCmp4 = vec_and( vec_xor( vecCmp3, (vector bool int)(1) ), vec_xor( vecCmp1, (vector bool int)(1) ) ); // everywhere that both of those are false + + // these are ones where a < PI and a > HALF_PI so we set a = PI - a + vecCmp1 = vec_and( vecCmp1, vecCmp2 ); + vecCmp1 = vec_or( vecCmp1, vecCmp4 ); + + // put the correct values into place + vecResult = vec_sel( vecResult, vecPIminusA, vecCmp1 ); + vecResult = vec_sel( vecResult, vecAminus2PI, vecCmp3 ); + + // calculate answer + vector float vecASquared = vec_madd( vecResult, vecResult, zero ); + vector float vecEst = vec_madd( (vector float)(-2.39e-08f), vecASquared, (vector float)(2.7526e-06f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(-1.98409e-04f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(8.3333315e-03f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(-1.666666664e-01f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(1.0f) ); + return vec_madd( vecResult, vecEst, zero ); +} + +/* +=============== + vecSplatWithRunTime + + For each element in vector: + n = v(i) +=============== +*/ +// splats an element across a vector using a runtime variable +inline vector float vecSplatWithRunTime( vector float v, int i ) { + vector unsigned char rotate = vec_lvsl( i * sizeof( float ), (int*) 0L ); + v = vec_perm( v, v, rotate ); + return vec_splat( v, 0 ); +} + + +/* +=============== + FastScalarInvSqrt + + n = 1 / sqrt( f ) +=============== +*/ +inline float FastScalarInvSqrt( float f ) { +#ifdef PPC_INTRINSICS + float estimate; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt + estimate = __frsqrte ( f + kSmallestFloat ); + + //if you require less precision, you may reduce the number of loop iterations. + // This will do 2 rounds of NR + estimate = estimate + 0.5f * estimate * ( 1.0f - f * estimate * estimate ); + estimate = estimate + 0.5f * estimate * ( 1.0f - f * estimate * estimate ); + return estimate; +#else + return idMath::InvSqrt( f ); +#endif +} + +/* +=============== + FastScalarInvSqrt_x3 + + arg1 = 1 / sqrt( arg1 ) + arg2 = 1 / sqrt( arg2 ) + arg3 = 1 / sqrt( arg3 ) +=============== +*/ +inline void FastScalarInvSqrt_x3( float *arg1, float *arg2, float *arg3 ) { +#ifdef PPC_INTRINSICS + register float estimate1, estimate2, estimate3; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt of each + estimate1 = __frsqrte ( *arg1 + kSmallestFloat ); + estimate2 = __frsqrte ( *arg2 + kSmallestFloat ); + estimate3 = __frsqrte ( *arg3 + kSmallestFloat ); + + // two rounds newton-raphson + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + + *arg1 = estimate1; + *arg2 = estimate2; + *arg3 = estimate3; +#else + *arg1 = idMath::InvSqrt( *arg1 ); + *arg2 = idMath::InvSqrt( *arg2 ); + *arg3 = idMath::InvSqrt( *arg3 ); +#endif +} + +/* +=============== + FastScalarInvSqrt_x6 + + arg1 = 1 / sqrt( arg1 ) + arg2 = 1 / sqrt( arg2 ) + arg3 = 1 / sqrt( arg3 ) + arg4 = 1 / sqrt( arg4 ) + arg5 = 1 / sqrt( arg5 ) + arg6 = 1 / sqrt( arg6 ) + + On a G5, you've got 2 pipeline stages to fill. (2 FPU's with 6 stages each) +=============== +*/ +inline void FastScalarInvSqrt_x6( float *arg1, float *arg2, float *arg3, float *arg4, float *arg5, float *arg6 ) { +#ifdef PPC_INTRINSICS + register float estimate1, estimate2, estimate3, estimate4, estimate5, estimate6; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt of each + estimate1 = __frsqrte ( *arg1 + kSmallestFloat ); + estimate2 = __frsqrte ( *arg2 + kSmallestFloat ); + estimate3 = __frsqrte ( *arg3 + kSmallestFloat ); + estimate4 = __frsqrte ( *arg4 + kSmallestFloat ); + estimate5 = __frsqrte ( *arg5 + kSmallestFloat ); + estimate6 = __frsqrte ( *arg6 + kSmallestFloat ); + + // two rounds newton-raphson + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate4 = estimate4 + 0.5f * estimate4 * ( 1.0f - *arg4 * estimate4 * estimate4 ); + estimate5 = estimate5 + 0.5f * estimate5 * ( 1.0f - *arg5 * estimate5 * estimate5 ); + estimate6 = estimate6 + 0.5f * estimate6 * ( 1.0f - *arg6 * estimate6 * estimate6 ); + + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate4 = estimate4 + 0.5f * estimate4 * ( 1.0f - *arg4 * estimate4 * estimate4 ); + estimate5 = estimate5 + 0.5f * estimate5 * ( 1.0f - *arg5 * estimate5 * estimate5 ); + estimate6 = estimate6 + 0.5f * estimate6 * ( 1.0f - *arg6 * estimate6 * estimate6 ); + + *arg1 = estimate1; + *arg2 = estimate2; + *arg3 = estimate3; + *arg4 = estimate4; + *arg5 = estimate5; + *arg6 = estimate6; +#else + *arg1 = idMath::InvSqrt( *arg1 ); + *arg2 = idMath::InvSqrt( *arg2 ); + *arg3 = idMath::InvSqrt( *arg3 ); + *arg4 = idMath::InvSqrt( *arg4 ); + *arg5 = idMath::InvSqrt( *arg5 ); + *arg6 = idMath::InvSqrt( *arg6 ); +#endif +} + + +// End Helper Functions + +#ifdef ENABLE_SIMPLE_MATH + +/* +============ +idSIMD_AltiVec::Add + + dst[i] = constant + src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Add( float *dst, const float constant, const float *src, const int count ) { + vector float v0, v1, v2, v3; + vector float v0_low, v0_hi, v1_hi; + vector unsigned char permVec; + vector float constVec; + int i; + + // handle unaligned cases at beginning + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant + src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), (vector unsigned char)(1) ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v0_hi, v1_hi, permVec ); + + v2 = vec_add( v0, constVec ); + v3 = vec_add( v1, constVec ); + + // store results + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant + src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Add + + dst[i] = src0[i] + src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Add( float *dst, const float *src0, const float *src1, const int count ) { + + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] + src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = vec_add( v0, v1 ); + v5 = vec_add( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Sub + + dst[i] = constant - src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Sub( float *dst, const float constant, const float *src, const int count ) { + + register vector float v0, v1, v2, v3; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector unsigned char permVec; + register vector float constVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant - src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute vector and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_sub( constVec, v0 ); + v3 = vec_sub( constVec, v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant - src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Sub + + dst[i] = src0[i] - src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Sub( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] - src1[i]; + } + + //calculate permute and do first loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = vec_sub( v0, v1 ); + v5 = vec_sub( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] - src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Mul + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Mul( float *dst, const float constant, const float *src, const int count) { + register vector float v0, v0_low, v0_hi, v1_low, v1_hi, v1, v2, v3; + register vector float constVec; + register vector unsigned char permVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + register vector float zeroVector = (vector float)(0.0); + int i; + + // handle unaligned data at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_madd( constVec, v0, zeroVector ); + v3 = vec_madd( constVec, v1, zeroVector ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Mul + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Mul( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + register vector float constVec = (vector float)(0.0); + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //no such thing as regular multiply so we do + //multiply then add zero + v4 = vec_madd( v0, v1, constVec ); + v5 = vec_madd( v2, v3, constVec ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] * src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Div + + dst[i] = constant / divisor[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Div( float *dst, const float constant, const float *divisor, const int count ) { + register vector float v0, v1, v2, v3; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector unsigned char permVec; + register vector float constVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = constant / divisor[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do first loads + permVec = vec_add( vec_lvsl( -1, (int*) &divisor[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &divisor[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &divisor[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &divisor[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = Divide( constVec, v0 ); + v3 = Divide( constVec, v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant / divisor[i]; + } +} + +/* +============ +idSIMD_AltiVec::Div + + dst[i] = src0[i] / src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Div( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] / src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = Divide( v0, v1 ); + v5 = Divide( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] / src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulAdd + + dst[i] += constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulAdd( float *dst, const float constant, const float *src, const int count ) { + + register vector float v0, v1, v2, v3, v4, v5; + register vector float constVec; + //src + register vector float v0_low, v0_hi, v2_low, v2_hi; + //permute vectors + register vector unsigned char permVec1; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] += constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + + // at this point, dst is known to be aligned + v1 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + + v4 = vec_madd( constVec, v0, v1 ); + v5 = vec_madd( constVec, v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] += constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulAdd + + dst[i] += src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulAdd( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] += src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + // load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //we know dst is aligned because we handled unaligned cases + //up front + v4 = vec_ld( 0, &dst[i] ); + v5 = vec_ld( 16, &dst[i] ); + + v6 = vec_madd( v0, v1, v4 ); + v7 = vec_madd( v2, v3, v5 ); + + ALIGNED_STORE2( &dst[i], v6, v7 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] += src0[i] * src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulSub + + dst[i] -= constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulSub( float *dst, const float constant, const float *src, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + register vector float constVec; + //src + register vector float v0_low, v0_hi, v2_low, v2_hi; + //permute vectors + register vector unsigned char permVec1; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] -= constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + + //we know dst will be aligned here because we already handled the preceeding + //unaligned cases + v1 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + + v4 = vec_nmsub( v0, constVec, v1 ); + v5 = vec_nmsub( v2, constVec, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] -= constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulSub + + dst[i] -= src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulSub( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] -= src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + // load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //we know dst is aligned because we handled unaligned cases + //up front + v4 = vec_ld( 0, &dst[i] ); + v5 = vec_ld( 16, &dst[i] ); + + v6 = vec_nmsub( v0, v1, v4 ); + v7 = vec_nmsub( v2, v3, v5 ); + + ALIGNED_STORE2( &dst[i], v6, v7 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] -= src0[i] * src1[i]; + } +} + +#endif /* ENABLE_SIMPLE_MATH */ + +#ifdef ENABLE_DOT +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) { + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + register vector float vecX, vecY, vecZ; + vector float vecX2, vecY2, vecZ2; + const float *addr = src[0].ToFloatPtr(); + float tempVal[4]; + float constVal[4]; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ; + + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + int i; + + // for scalar cleanup, if necessary + constVal[0] = constant[0]; + constVal[1] = constant[1]; + constVal[2] = constant[2]; + constVal[3] = 0; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vecLd1 = vec_ld( 0, constant.ToFloatPtr() ); + vecLd2 = vec_ld( 11, constant.ToFloatPtr() ); + vecLd1 = vec_perm( vecLd1, vecLd2, constPerm ); + + + // populate const vectors + vecConstX = vec_splat( vecLd1, 0 ); + vecConstY = vec_splat( vecLd1, 1 ); + vecConstZ = vec_splat( vecLd1, 2 ); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i]; + } + + for ( ; i + 7 < count; i += 8 ) { + float *vecPtr = (float*)( addr + (i*3) ); + vector float v0, v1, v2, v3, v4, v5; + + v0 = vecOld; //vec_ld( 0, vecPtr ); + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + v4 = vec_ld( 63, vecPtr ); + v5 = vec_ld( 79, vecPtr ); + vecOld = vec_ld( 95, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + + vecLd4 = vec_perm( v3, v4, permVec ); + vecLd5 = vec_perm( v4, v5, permVec ); + vecLd6 = vec_perm( v5, vecOld, permVec ); + + // permute into X Y Z vectors + vecX = vec_perm( vecLd1, vecLd2, permX1 ); + vecY = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX = vec_perm( vecX, vecLd3, permX2 ); + vecY = vec_perm( vecY, vecLd3, permY2 ); + vecZ = vec_perm( vecZ, vecLd3, permZ2 ); + + vecX2 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY2 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ2 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX2 = vec_perm( vecX2, vecLd6, permX2 ); + vecY2 = vec_perm( vecY2, vecLd6, permY2 ); + vecZ2 = vec_perm( vecZ2, vecLd6, permZ2 ); + + // do multiply + vecX = vec_madd( vecX, vecConstX, zeroVector ); + vecY = vec_madd( vecY, vecConstY, vecX ); + vecZ = vec_madd( vecZ, vecConstZ, vecY ); + + vecX2 = vec_madd( vecX2, vecConstX, zeroVector ); + vecY2 = vec_madd( vecY2, vecConstY, vecX2 ); + vecZ2 = vec_madd( vecZ2, vecConstZ, vecY2 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ, vecZ2 ); + } + + //cleanup + for ( ; i < count; i++ ) { + // look up whats at the address we want, cast it as float pointer, then + // dereference that pointer + tempVal[0] = *( addr + (i*3) + 0 ); + tempVal[1] = *( addr + (i*3) + 1 ); + tempVal[2] = *( addr + (i*3) + 2 ); + dst[i] = constVal[0] * tempVal[0] + constVal[1] * tempVal[1] + constVal[2] * tempVal[2]; + } +} + + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].Normal() + src[i][3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].Normal() + src[(X)][3]; + + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + + int i; + float constVal[4]; + float srcVal[3]; + float srcI3; + float tempVal; + + vector float vecPlaneLd1, vecPlaneLd2, vecPlaneLd3, vecPlaneLd4; + vector float vecPlaneLd5, vecPlaneLd6, vecPlaneLd7, vecPlaneLd8; + vector float vecX, vecY, vecZ, vecI3; + vector float vecX2, vecY2, vecZ2, vecI32; + vector float vecConstX, vecConstY, vecConstZ; + + constVal[0] = constant[0]; + constVal[1] = constant[1]; + constVal[2] = constant[2]; + constVal[3] = 1; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vector float v0 = vec_ld( 0, constant.ToFloatPtr() ); + vector float v1 = vec_ld( 11, constant.ToFloatPtr() ); + vector float vecConst = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( vecConst, 0 ); + vecConstY = vec_splat( vecConst, 1 ); + vecConstZ = vec_splat( vecConst, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].Normal() + src[i][3]; + } + + const float *addr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + for ( ; i + 7 < count; i += 8 ) { + float *planePtr = (float*)( addr + (i*PLANE_OFFSET) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + v0 = vecOld; //vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + v2 = vec_ld( 31, planePtr ); + v3 = vec_ld( 47, planePtr ); + v4 = vec_ld( 63, planePtr ); + v5 = vec_ld( 79, planePtr ); + v6 = vec_ld( 95, planePtr ); + v7 = vec_ld( 111, planePtr ); + vecOld = vec_ld( 127, planePtr ); + + vecPlaneLd1 = vec_perm( v0, v1, permVec ); + vecPlaneLd2 = vec_perm( v1, v2, permVec ); + vecPlaneLd3 = vec_perm( v2, v3, permVec ); + vecPlaneLd4 = vec_perm( v3, v4, permVec ); + + vecPlaneLd5 = vec_perm( v4, v5, permVec ); + vecPlaneLd6 = vec_perm( v5, v6, permVec ); + vecPlaneLd7 = vec_perm( v6, v7, permVec ); + vecPlaneLd8 = vec_perm( v7, vecOld, permVec ); + + // permute into X Y Z vectors, since this is square its basically + // a matrix transpose + v0 = vec_mergeh( vecPlaneLd1, vecPlaneLd3 ); + v1 = vec_mergeh( vecPlaneLd2, vecPlaneLd4 ); + v2 = vec_mergel( vecPlaneLd1, vecPlaneLd3 ); + v3 = vec_mergel( vecPlaneLd2, vecPlaneLd4 ); + + vecX = vec_mergeh( v0, v1 ); + vecY = vec_mergel( v0, v1 ); + vecZ = vec_mergeh( v2, v3 ); + vecI3 = vec_mergel( v2, v3 ); + + v4 = vec_mergeh( vecPlaneLd5, vecPlaneLd7 ); + v5 = vec_mergeh( vecPlaneLd6, vecPlaneLd8 ); + v6 = vec_mergel( vecPlaneLd5, vecPlaneLd7 ); + v7 = vec_mergel( vecPlaneLd6, vecPlaneLd8 ); + + vecX2 = vec_mergeh( v4, v5 ); + vecY2 = vec_mergel( v4, v5 ); + vecZ2 = vec_mergeh( v6, v7 ); + vecI32 = vec_mergel( v6, v7 ); + + // do calculation + v6 = vec_madd( vecZ, vecConstZ, vecI3 ); + v5 = vec_madd( vecY, vecConstY, v6 ); + v4 = vec_madd( vecX, vecConstX, v5 ); + + v0 = vec_madd( vecZ2, vecConstZ, vecI32 ); + v1 = vec_madd( vecY2, vecConstY, v0 ); + v2 = vec_madd( vecX2, vecConstX, v1 ); + + // store results + ALIGNED_STORE2( &dst[i], v4, v2 ); + } + + // cleanup + for ( ; i < count; i++ ) { + // populate srcVal with src X Y Z + srcVal[0] = *(addr + (i*PLANE_OFFSET) + 0 ); + srcVal[1] = *(addr + (i*PLANE_OFFSET) + 1 ); + srcVal[2] = *(addr + (i*PLANE_OFFSET) + 2 ); + + // put src[i][3] into srcI3 + srcI3 = *(addr + (i*PLANE_OFFSET) + 3 ); + + tempVal = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] = tempVal + srcI3; + } +} + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].xyz; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + int i; + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 11, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + // permute into constant vectors + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].xyz; + } + + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < count ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + for ( ; i+3 < count; i += 4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + + // store results + vec_st( vecSrcZ1, 0, &dst[i] ); + } + + for ( ; i < count; i++ ) { + dst[i] = constant * src[i].xyz; + } +} +#else +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].xyz; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + int i; + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 11, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + // permute into constant vectors + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].xyz; + } + + for ( ; i+3 < count; i += 4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + + // store results + vec_st( vecSrcZ1, 0, &dst[i] ); + } + + for ( ; i < count; i++ ) { + dst[i] = constant * src[i].xyz; + } +} + +#endif /* DRAWVERT_PADDED */ + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i] + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idVec3 *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)] + constant[3]; + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + register vector float vecX, vecY, vecZ, vecX2, vecY2, vecZ2; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecConst3; + + idVec3 constNormal = constant.Normal(); + float const3 = constant[3]; + + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + int i; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vecLd1 = vec_ld( 0, constant.ToFloatPtr() ); + vecLd2 = vec_ld( 15, constant.ToFloatPtr() ); + vecLd1 = vec_perm( vecLd1, vecLd2, constPerm ); + + // populate const vec + vecConstX = vec_splat( vecLd1, 0 ); + vecConstY = vec_splat( vecLd1, 1 ); + vecConstZ = vec_splat( vecLd1, 2 ); + + // put constant to add in vector + vecConst3 = loadSplatUnalignedScalar( &const3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i] + constant[3]; + } + + const float *addr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + for ( ; i+7 < count; i += 8 ) { + float *vecPtr = (float*)( addr + (i*3) ); + vector float v0, v1, v2, v3, v4, v5; + + v0 = vecOld; //vec_ld( 0, vecPtr ); + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + v4 = vec_ld( 63, vecPtr ); + v5 = vec_ld( 79, vecPtr ); + vecOld = vec_ld( 95, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + + vecLd4 = vec_perm( v3, v4, permVec ); + vecLd5 = vec_perm( v4, v5, permVec ); + vecLd6 = vec_perm( v5, vecOld, permVec ); + + // permute into X Y Z vectors + vecX = vec_perm( vecLd1, vecLd2, permX1 ); + vecY = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX = vec_perm( vecX, vecLd3, permX2 ); + vecY = vec_perm( vecY, vecLd3, permY2 ); + vecZ = vec_perm( vecZ, vecLd3, permZ2 ); + + vecX2 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY2 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ2 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX2 = vec_perm( vecX2, vecLd6, permX2 ); + vecY2 = vec_perm( vecY2, vecLd6, permY2 ); + vecZ2 = vec_perm( vecZ2, vecLd6, permZ2 ); + + // calculate dot product + vecX = vec_madd( vecX, vecConstX, zeroVector ); + vecY = vec_madd( vecY, vecConstY, vecX ); + vecZ = vec_madd( vecZ, vecConstZ, vecY ); + + vecX2 = vec_madd( vecX2, vecConstX, zeroVector ); + vecY2 = vec_madd( vecY2, vecConstY, vecX2 ); + vecZ2 = vec_madd( vecZ2, vecConstZ, vecY2 ); + + // add in constant[3] + vecZ = vec_add( vecZ, vecConst3 ); + vecZ2 = vec_add( vecZ2, vecConst3 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ, vecZ2 ); + } + + //cleanup + for ( ; i < count; i++ ) { + dst[i] = constNormal * src[i] + const3; + } +} + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idPlane *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].Normal() + constant[3] * src[(X)][3]; + + // check plane size + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + + float constVal[4]; + float srcVal[4]; + + int i; + const float *constPtr = constant.ToFloatPtr(); + + register vector float vecX, vecY, vecZ, vecI3; + register vector float vecX2, vecY2, vecZ2, vecI32; + + vector float vecPlaneLd1, vecPlaneLd2, vecPlaneLd3, vecPlaneLd4; + vector float vecPlaneLd5, vecPlaneLd6, vecPlaneLd7, vecPlaneLd8; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + + constVal[0] = *(constPtr); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vector + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vector float v0 = vec_ld( 0, constant.ToFloatPtr() ); + vector float v1 = vec_ld( 15, constant.ToFloatPtr() ); + vector float vecConst = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( vecConst, 0 ); + vecConstY = vec_splat( vecConst, 1 ); + vecConstZ = vec_splat( vecConst, 2 ); + vecConstI3 = vec_splat( vecConst, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; + } + + const float *srcPtr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + for ( ; i+7 < count; i += 8 ) { + float *planePtr = (float*)( srcPtr + (i*PLANE_OFFSET) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + v0 = vecOld; // vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + v2 = vec_ld( 31, planePtr ); + v3 = vec_ld( 47, planePtr ); + v4 = vec_ld( 63, planePtr ); + v5 = vec_ld( 79, planePtr ); + v6 = vec_ld( 95, planePtr ); + v7 = vec_ld( 111, planePtr ); + vecOld = vec_ld( 127, planePtr ); + + vecPlaneLd1 = vec_perm( v0, v1, permVec ); + vecPlaneLd2 = vec_perm( v1, v2, permVec ); + vecPlaneLd3 = vec_perm( v2, v3, permVec ); + vecPlaneLd4 = vec_perm( v3, v4, permVec ); + + vecPlaneLd5 = vec_perm( v4, v5, permVec ); + vecPlaneLd6 = vec_perm( v5, v6, permVec ); + vecPlaneLd7 = vec_perm( v6, v7, permVec ); + vecPlaneLd8 = vec_perm( v7, vecOld, permVec ); + + // permute into X Y Z vectors, since this is square its basically + // a matrix transpose + v0 = vec_mergeh( vecPlaneLd1, vecPlaneLd3 ); + v1 = vec_mergeh( vecPlaneLd2, vecPlaneLd4 ); + v2 = vec_mergel( vecPlaneLd1, vecPlaneLd3 ); + v3 = vec_mergel( vecPlaneLd2, vecPlaneLd4 ); + + vecX = vec_mergeh( v0, v1 ); + vecY = vec_mergel( v0, v1 ); + vecZ = vec_mergeh( v2, v3 ); + vecI3 = vec_mergel( v2, v3 ); + + v4 = vec_mergeh( vecPlaneLd5, vecPlaneLd7 ); + v5 = vec_mergeh( vecPlaneLd6, vecPlaneLd8 ); + v6 = vec_mergel( vecPlaneLd5, vecPlaneLd7 ); + v7 = vec_mergel( vecPlaneLd6, vecPlaneLd8 ); + + vecX2 = vec_mergeh( v4, v5 ); + vecY2 = vec_mergel( v4, v5 ); + vecZ2 = vec_mergeh( v6, v7 ); + vecI32 = vec_mergel( v6, v7 ); + + // do calculation + v4 = vec_madd( vecConstX, vecX, zeroVector ); + v5 = vec_madd( vecConstY, vecY, v4 ); + v6 = vec_madd( vecConstZ, vecZ, v5 ); + v7 = vec_madd( vecConstI3, vecI3, v6 ); + + v0 = vec_madd( vecConstX, vecX2, zeroVector ); + v1 = vec_madd( vecConstY, vecY2, v0 ); + v2 = vec_madd( vecConstZ, vecZ2, v1 ); + v3 = vec_madd( vecConstI3, vecI32, v2 ); + + //store result + ALIGNED_STORE2( &dst[i], v7, v3 ); + } + + // cleanup + for ( ; i < count; i++ ) { + //dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; + srcVal[0] = *(srcPtr + (i*PLANE_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*PLANE_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*PLANE_OFFSET) + 2 ); + srcVal[3] = *(srcPtr + (i*PLANE_OFFSET) + 3 ); + dst[i] = srcVal[0] * constVal[0] + srcVal[1] * constVal[1] + srcVal[2] * constVal[2] + constVal[3] * srcVal[3]; + } +} + + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyz + constant[3]; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + int i; + const float *constPtr = constant.ToFloatPtr(); + const float *srcPtr = src[0].xyz.ToFloatPtr(); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float vecDest1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + float constVal[4]; + float srcVal[3]; + + constVal[0] = *(constPtr+0); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vec + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 15, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + vecConstI3 = vec_splat( v0, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].xyz + constant[3]; + } + + // every fourth one will have the same alignment, so can store these. Make sure we + // have enough so we don't run off the end of the array + if ( i+3 < count ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + for ( ; i+3 < count; i+=4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + vecDest1 = vec_add( vecSrcZ1, vecConstI3 ); + + // store results + vec_st( vecDest1, 0, &dst[i] ); + } + + // cleanup + for ( ; i < count; i++ ) { + srcVal[0] = *(srcPtr + (i*DRAWVERT_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*DRAWVERT_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*DRAWVERT_OFFSET) + 2 ); + // dst[i] = constant.Normal() * src[i].xyz + constant[3]; + + dst[i] = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] += constVal[3]; + } +} +#else +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyz + constant[3]; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + int i; + const float *constPtr = constant.ToFloatPtr(); + const float *srcPtr = src[0].xyz.ToFloatPtr(); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float vecDest1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + float constVal[4]; + float srcVal[3]; + + constVal[0] = *(constPtr+0); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vec + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 15, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + vecConstI3 = vec_splat( v0, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].xyz + constant[3]; + } + + for ( ; i+3 < count; i+=4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + vecDest1 = vec_add( vecSrcZ1, vecConstI3 ); + + // store results + vec_st( vecDest1, 0, &dst[i] ); + } + + // cleanup + for ( ; i < count; i++ ) { + srcVal[0] = *(srcPtr + (i*DRAWVERT_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*DRAWVERT_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*DRAWVERT_OFFSET) + 2 ); + // dst[i] = constant.Normal() * src[i].xyz + constant[3]; + + dst[i] = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] += constVal[3]; + } +} + +#endif /* DRAWVERT_PADDED */ + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] * src1[(X)]; + + int i; + float src0Val[3]; + float src1Val[3]; + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + vector float vecLd7, vecLd8, vecLd9, vecLd10, vecLd11, vecLd12; + register vector float vecX0, vecY0, vecZ0, vecX1, vecY1, vecZ1; + register vector float vecX02, vecY02, vecZ02, vecX12, vecY12, vecZ12; + register vector float zeroVector = (vector float)(0.0); + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] * src1[i]; + } + + const float *src0Ptr = src0[i].ToFloatPtr(); + const float *src1Ptr = src1[i].ToFloatPtr(); + vector unsigned char permVec1 = vec_add( vec_lvsl( -1, src0Ptr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, src1Ptr ), (vector unsigned char)(1) ); + vector float vecOld0 = vec_ld( 0, src0Ptr ); + vector float vecOld1 = vec_ld( 0, src1Ptr ); + + for ( i = 0; i+7 < count; i += 8 ) { + float *s0Ptr = (float*)( src0Ptr + (i*3) ); + float *s1Ptr = (float*)( src1Ptr + (i*3) ); + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11; + v0 = vecOld0; + v1 = vec_ld( 15, s0Ptr ); + v2 = vec_ld( 31, s0Ptr ); + v3 = vec_ld( 47, s0Ptr ); + v4 = vec_ld( 63, s0Ptr ); + v5 = vec_ld( 79, s0Ptr ); + vecOld0 = vec_ld( 95, s0Ptr ); + + v6 = vecOld1; + v7 = vec_ld( 15, s1Ptr ); + v8 = vec_ld( 31, s1Ptr ); + v9 = vec_ld( 47, s1Ptr ); + v10 = vec_ld( 63, s1Ptr ); + v11 = vec_ld( 79, s1Ptr ); + vecOld1 = vec_ld( 95, s1Ptr ); + + vecLd1 = vec_perm( v0, v1, permVec1 ); + vecLd2 = vec_perm( v1, v2, permVec1 ); + vecLd3 = vec_perm( v2, v3, permVec1 ); + vecLd4 = vec_perm( v3, v4, permVec1 ); + vecLd5 = vec_perm( v4, v5, permVec1 ); + vecLd6 = vec_perm( v5, vecOld0, permVec1 ); + + vecLd7 = vec_perm( v6, v7, permVec2 ); + vecLd8 = vec_perm( v7, v8, permVec2 ); + vecLd9 = vec_perm( v8, v9, permVec2 ); + vecLd10 = vec_perm( v9, v10, permVec2 ); + vecLd11 = vec_perm( v10, v11, permVec2 ); + vecLd12 = vec_perm( v11, vecOld1, permVec2 ); + + // permute into X Y Z vectors + vecX0 = vec_perm( vecLd1, vecLd2, permX1 ); + vecY0 = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ0 = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX0 = vec_perm( vecX0, vecLd3, permX2 ); + vecY0 = vec_perm( vecY0, vecLd3, permY2 ); + vecZ0 = vec_perm( vecZ0, vecLd3, permZ2 ); + + vecX02 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY02 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ02 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX02 = vec_perm( vecX02, vecLd6, permX2 ); + vecY02 = vec_perm( vecY02, vecLd6, permY2 ); + vecZ02 = vec_perm( vecZ02, vecLd6, permZ2 ); + + vecX1 = vec_perm( vecLd7, vecLd8, permX1 ); + vecY1 = vec_perm( vecLd7, vecLd8, permY1 ); + vecZ1 = vec_perm( vecLd7, vecLd8, permZ1 ); + vecX1 = vec_perm( vecX1, vecLd9, permX2 ); + vecY1 = vec_perm( vecY1, vecLd9, permY2 ); + vecZ1 = vec_perm( vecZ1, vecLd9, permZ2 ); + + vecX12 = vec_perm( vecLd10, vecLd11, permX1 ); + vecY12 = vec_perm( vecLd10, vecLd11, permY1 ); + vecZ12 = vec_perm( vecLd10, vecLd11, permZ1 ); + vecX12 = vec_perm( vecX12, vecLd12, permX2 ); + vecY12 = vec_perm( vecY12, vecLd12, permY2 ); + vecZ12 = vec_perm( vecZ12, vecLd12, permZ2 ); + + // do multiply + vecX0 = vec_madd( vecX0, vecX1, zeroVector ); + vecY0 = vec_madd( vecY0, vecY1, vecX0 ); + vecZ0 = vec_madd( vecZ0, vecZ1, vecY0 ); + vecX02 = vec_madd( vecX02, vecX12, zeroVector ); + vecY02 = vec_madd( vecY02, vecY12, vecX02 ); + vecZ02 = vec_madd( vecZ02, vecZ12, vecY02 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ0, vecZ02 ); + } + + // cleanup + for ( ; i < count; i++ ) { + // dst[i] = src0[i] * src1[i]; + src0Val[0] = *( src0Ptr + (i*3) + 0 ); + src0Val[1] = *( src0Ptr + (i*3) + 1 ); + src0Val[2] = *( src0Ptr + (i*3) + 2 ); + + src1Val[0] = *( src1Ptr + (i*3) + 0 ); + src1Val[1] = *( src1Ptr + (i*3) + 1 ); + src1Val[2] = *( src1Ptr + (i*3) + 2 ); + + dst[i] = src0Val[0] * src1Val[0] + src0Val[1] * src1Val[1] + src0Val[2] * src1Val[2]; + } +} + +/* +============ +idSIMD_AltiVec::Dot + + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2] + ... +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float &dot, const float *src1, const float *src2, const int count ) { + dot = 0.0f; + + register vector float v0, v1, v2, v3; + register vector float zeroVector; + register vector float runningTotal1, runningTotal2; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i = 0; + + runningTotal1 = (vector float)(0.0); + runningTotal2 = (vector float)(0.0); + zeroVector = (vector float)(0.0); + + if ( count >= 8 ) { + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src2[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src1[i] ); + v3_hi = vec_ld( 0, &src2[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src1[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src1[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src2[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src2[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //multiply together and keep running sum + runningTotal1 = vec_madd( v0, v1, runningTotal1 ); + runningTotal2 = vec_madd( v2, v3, runningTotal2 ); + } + + runningTotal1 = vec_add( runningTotal1, runningTotal2 ); + + // sum accross vector + v0 = vec_add( runningTotal1, vec_sld( runningTotal1, runningTotal1, 8 ) ); + v1 = vec_add( v0, vec_sld( v0, v0, 4 ) ); + runningTotal1 = vec_splat( v1, 0 ); + vec_ste( runningTotal1, 0, &dot ); + } + + //handle cleanup. when profiling the game, we found that most of the counts to this function were small, so it + // spends a lot of time in this scalar code. It's already really really fast (eg 1 TB tick) for scalar code for + // counts less than 50, so not much point in trying to get vector code in on the action + for ( ; i < count ; i++ ) { + dot += src1[i] * src2[i]; + } + +} +#endif /* ENABLE_DOT */ + +#ifdef ENABLE_COMPARES + +/* +============ +idSIMD_AltiVec::CmpGT + + dst[i] = src0[i] > constant; +============ +*/ + +void VPCALL idSIMD_AltiVec::CmpGT( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] > constant; + + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] > constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmpgt( v0, constVec ); + vr2 = vec_cmpgt( v1, constVec ); + vr3 = vec_cmpgt( v2, constVec ); + vr4 = vec_cmpgt( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] > constant; + } +} + + +/* +============ +idSIMD_AltiVec::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] > constant ) << bitNum; + + // Temp vector registers + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] > constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmpgt( vs0, constVec ); + vtbi1 = vec_cmpgt( vs1, constVec ); + vtbi2 = vec_cmpgt( vs2, constVec ); + vtbi3 = vec_cmpgt( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1 to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] > constant ) << bitNum; + } +} + +/* +============ +idSIMD_AltiVec::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGE( byte *dst, const float *src0, const float constant, const int count ) { + + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] >= constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmpge( v0, constVec ); + vr2 = vec_cmpge( v1, constVec ); + vr3 = vec_cmpge( v2, constVec ); + vr4 = vec_cmpge( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] >= constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] >= constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmpge( vs0, constVec ); + vtbi1 = vec_cmpge( vs1, constVec ); + vtbi2 = vec_cmpge( vs2, constVec ); + vtbi3 = vec_cmpge( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] >= constant ) << bitNum; + } +} + + +/* +============ +idSIMD_AltiVec::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLT( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] < constant; + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] < constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmplt( v0, constVec ); + vr2 = vec_cmplt( v1, constVec ); + vr3 = vec_cmplt( v2, constVec ); + vr4 = vec_cmplt( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] < constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] < constant ) << bitNum; + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] < constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmplt( vs0, constVec ); + vtbi1 = vec_cmplt( vs1, constVec ); + vtbi2 = vec_cmplt( vs2, constVec ); + vtbi3 = vec_cmplt( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] < constant ) << bitNum; + } + +} +//#endif + +/* +============ +idSIMD_AltiVec::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLE( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] <= constant; + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] <= constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmple( v0, constVec ); + vr2 = vec_cmple( v1, constVec ); + vr3 = vec_cmple( v2, constVec ); + vr4 = vec_cmple( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] <= constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] <= constant ) << bitNum; + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] <= constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmple( vs0, constVec ); + vtbi1 = vec_cmple( vs1, constVec ); + vtbi2 = vec_cmple( vs2, constVec ); + vtbi3 = vec_cmple( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] <= constant ) << bitNum; + } +} +#endif /* ENABLE_COMPARES */ + +#ifdef ENABLE_MINMAX + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( float &min, float &max, const float *src, const int count ) { + min = idMath::INFINITY; max = -idMath::INFINITY; +//#define OPER(X) if ( src[(X)] < min ) {min = src[(X)];} if ( src[(X)] > max ) {max = src[(X)];} + + register vector float v0, v1, v2, v3; + register vector float maxVec, minVec, tempMin, tempMax; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i = 0; + + if ( count >= 4 ) { + + //calculate permute and do first load to + //get a starting point for min and max + permVec = vec_add( vec_lvsl( -1, (int*) &src[0] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[0] ); + + maxVec = loadSplatUnalignedScalar( &max ); + minVec = loadSplatUnalignedScalar( &min ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load sources + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + // minimum + v2 = vec_min( v0, v1 ); + minVec = vec_min( minVec, v2 ); + // maximum + v3 = vec_max( v0, v1 ); + maxVec = vec_max( maxVec, v3 ); + } + + //minVec and maxVec hold the min/max elements from the array, but now + //we need to figure out which particular element it is + + tempMin = minVec; + tempMax = maxVec; + + // rotate vector around and compare to itself to find the real min/max + tempMin = vec_min( tempMin, vec_sld( tempMin, tempMin, 8 ) ); + tempMax = vec_max( tempMax, vec_sld( tempMax, tempMax, 8 ) ); + tempMin = vec_min( tempMin, vec_sld( tempMin, tempMin, 4 ) ); + tempMax = vec_max( tempMax, vec_sld( tempMax, tempMax, 4 ) ); + minVec = vec_splat( tempMin, 0 ); + maxVec = vec_splat( tempMax, 0 ); + vec_ste( minVec, 0, &min ); + vec_ste( maxVec, 0, &max ); + } + + //cleanup + for ( ; i < count; i++ ) { + if ( src[i] < min ) { + min = src[i]; + } + if ( src[i] > max ) { + max = src[i]; + } + } +} + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) { + min[0] = min[1] = idMath::INFINITY; max[0] = max[1] = -idMath::INFINITY; +//#define OPER(X) const idVec2 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } + + idVec2 v; + int i = 0; + int j; + + const float *srcPtr = src[0].ToFloatPtr(); + register vector float vecLd1, vecLd2, vecLd3, vecLd4; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3; + + if ( count > 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + for ( i = 0, j = 0; i+7 < count; i += 8, j += 4) { + // load data + float *vecPtr = (float*)( srcPtr + (j*4) ); + vector float v0, v1, v2, v3; + + v0 = vecOld; + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + vecOld = vec_ld( 63, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + vecLd4 = vec_perm( v3, vecOld, permVec ); + + // each of these vectors contains 2 elements + // looks like | X Y X Y | X Y X Y + v0 = vec_min( vecLd1, vecLd2 ); + v1 = vec_min( vecLd3, vecLd4 ); + v0 = vec_min( v0, v1 ); + + v2 = vec_max( vecLd1, vecLd2 ); + v3 = vec_max( vecLd3, vecLd4 ); + v2 = vec_max( v2, v3 ); + + // since its always X Y X Y we don't have to re-merge each time. we can wait + // until the end + vecMin = vec_min( v0, vecMin ); + vecMax = vec_max( v2, vecMax ); + } + + vecMin = vec_min( vecMin, vec_sld( vecMin, vecMin, 8 ) ); + vecMax = vec_max( vecMax, vec_sld( vecMax, vecMax, 8 ) ); + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMax, 0 ); + v3 = vec_splat( vecMax, 1 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &max[0] ); + vec_ste( v3, 0, &max[1] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i]; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + } +} + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +//#define OPER(X) const idVec3 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + + int i = 0; + const float *srcPtr = src[0].ToFloatPtr(); + idVec3 v; + + register vector float vecLd1, vecLd2, vecLd3; + register vector float vecMin, vecMax; + register vector float vecSrc1, vecSrc2, vecSrc3, vecSrc4; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + // 4 elements at a time + for ( ; i+3 < count; i += 4 ) { + float *vecPtr = (float*)( srcPtr + (i*3) ); + vector float v0, v1, v2; + + v0 = vecOld; + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + vecOld = vec_ld( 47, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, vecOld, permVec ); + + // put each idVec3 into its own vector as X Y Z (crap) + vecSrc1 = vecLd1; + vecSrc2 = vec_sld( vecLd1, vecLd2, 12 ); + vecSrc3 = vec_sld( vecLd2, vecLd3, 8 ); + vecSrc4 = vec_sld( vecLd3, vecLd3, 4 ); + + // do min and max + vecMin1 = vec_min( vecSrc1, vecSrc2 ); + vecMin2 = vec_min( vecSrc3, vecSrc4 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( vecSrc1, vecSrc2 ); + vecMax2 = vec_max( vecSrc3, vecSrc4 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax1, vecMax ); + } + + // store results + vector float v0, v1, v2, v3, v4, v5; + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i ++ ) { + v = src[i]; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + } +} + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + idVec3 v; + int i = 0; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} +#else +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + idVec3 v; + int i = 0; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} + +#endif /* DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + + idVec3 v; + int i = 0; + + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1; + vector unsigned char vertPerm2; + vector unsigned char vertPerm3; + vector unsigned char vertPerm4; + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[indexes[i]].xyz.ToFloatPtr(); + const float *vertPtr2 = src[indexes[i+1]].xyz.ToFloatPtr(); + const float *vertPtr3 = src[indexes[i+2]].xyz.ToFloatPtr(); + const float *vertPtr4 = src[indexes[i+3]].xyz.ToFloatPtr(); + + vertPerm1 = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, vertPtr2 ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, vertPtr3 ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, vertPtr4 ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[indexes[i]].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} +#else +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + + idVec3 v; + int i = 0; + + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1; + vector unsigned char vertPerm2; + vector unsigned char vertPerm3; + vector unsigned char vertPerm4; + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[indexes[i]].xyz.ToFloatPtr(); + const float *vertPtr2 = src[indexes[i+1]].xyz.ToFloatPtr(); + const float *vertPtr3 = src[indexes[i+2]].xyz.ToFloatPtr(); + const float *vertPtr4 = src[indexes[i+3]].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[indexes[i]].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} + + +#endif /* DRAWVERT_PADDED */ + +#endif /* ENABLE_MINMAX */ + +#ifdef ENABLE_CLAMP + +/* +============ +idSIMD_AltiVec::Clamp +============ +*/ +void VPCALL idSIMD_AltiVec::Clamp( float *dst, const float *src, const float min, const float max, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)] > max ? max : src[(X)]; + register vector float v0, v1, v2, v3, v4, v5; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneVector = (vector unsigned char)(1); + register vector float minVec, maxVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < min ? min : src[i] > max ? max : src[i]; + } + + //splat min/max into a vector + minVec = loadSplatUnalignedScalar( &min ); + maxVec = loadSplatUnalignedScalar( &max ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + //apply minimum + v2 = vec_max( v0, minVec ); + v3 = vec_max( v1, minVec ); + + //apply maximum + v4 = vec_min( v2, maxVec ); + v5 = vec_min( v3, maxVec ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < min ? min : src[i] > max ? max : src[i]; + } +} + +/* +============ +idSIMD_AltiVec::ClampMin +============ +*/ +void VPCALL idSIMD_AltiVec::ClampMin( float *dst, const float *src, const float min, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)]; + register vector float v0, v1, v2, v3; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector float constVec; + vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < min ? min : src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &min ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_max( v0, constVec ); + v3 = vec_max( v1, constVec ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < min ? min : src[i]; + } + } + +/* +============ +idSIMD_AltiVec::ClampMax +============ +*/ +void VPCALL idSIMD_AltiVec::ClampMax( float *dst, const float *src, const float max, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] > max ? max : src[(X)]; + register vector float v0, v1, v2, v3; + register vector unsigned char permVec; + register vector float constVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < max ? max : src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &max ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_min( v0, constVec ); + v3 = vec_min( v1, constVec ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < max ? max : src[i]; + } +} + +#endif /* ENABLE_CLAMP */ + +#ifdef ENABLE_16ROUTINES + +/* +============ +idSIMD_AltiVec::Zero16 +============ +*/ +void VPCALL idSIMD_AltiVec::Zero16( float *dst, const int count ) { + memset( dst, 0, count * sizeof( float ) ); +} + +/* +============ +idSIMD_AltiVec::Negate16 + + Assumptions: + dst is aligned +============ +*/ +void VPCALL idSIMD_AltiVec::Negate16( float *dst, const int count ) { +//#define OPER(X) ptr[(X)] ^= ( 1 << 31 ) // IEEE 32 bits float sign bit + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + int i = 0; + vector float v0, v1, v2, v3; + + //know its 16-byte aligned + for ( ; i + 7 < count2; i += 8 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_ld( 16, &dst[i] ); + + v2 = vec_sub( (vector float)(0), v0 ); + v3 = vec_sub( (vector float)(0), v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_sub( (vector float)(0), v0 ); + vec_st( v1, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Copy16 +============ +*/ +void VPCALL idSIMD_AltiVec::Copy16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] + memcpy( dst, src, sizeof(float) * count ); +} + +/* +============ +idSIMD_AltiVec::Add16 + + Assumptions: + Assumes dst, src1, src2 all start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Add16( float *dst, const float *src1, const float *src2, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] + src2[(X)] + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + // src2 is aligned + assert( IS_16BYTE_ALIGNED( src2[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //know all data is 16-byte aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load sources + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_ld( 0, &src2[i] ); + v3 = vec_ld( 16, &src2[i] ); + v4 = vec_add( v0, v2 ); + v5 = vec_add( v1, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 0, &src2[i] ); + v2 = vec_add( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Sub16 + + Assumptions: + Assumes that dst, src1, and src2 all start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Sub16( float *dst, const float *src1, const float *src2, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] - src2[(X)] + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + // src2 is aligned + assert( IS_16BYTE_ALIGNED( src2[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //know data is aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load sources + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_ld( 0, &src2[i] ); + v3 = vec_ld( 16, &src2[i] ); + v4 = vec_sub( v0, v2 ); + v5 = vec_sub( v1, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 0, &src2[i] ); + v2 = vec_sub( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Mul16 + + Assumptions: + Assumes that dst and src1 start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Mul16( float *dst, const float *src1, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] * constant + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3; + register vector float constVec; + register vector float zeroVector = (vector float)(0.0); + int i = 0; + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //know data is aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load source + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_madd( constVec, v0, zeroVector ); + v3 = vec_madd( constVec, v1, zeroVector ); + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_madd( constVec, v0, zeroVector ); + vec_st( v1, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::AddAssign16 + + Assumptions: + Assumes that dst and src start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::AddAssign16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] += src[(X)] + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src is aligned + assert( IS_16BYTE_ALIGNED( src[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 16, &src[i] ); + v2 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + v4 = vec_add( v0, v2 ); + v5 = vec_add( v1, v3 ); + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 0, &dst[i] ); + v2 = vec_add( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::SubAssign16 + + Assumptions: + Assumes that dst and src start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::SubAssign16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] -= src[(X)] + register vector float v0, v1, v2, v3, v4, v5; + int i=0; + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src is aligned + assert( IS_16BYTE_ALIGNED( src[0] ) ); + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 16, &src[i] ); + v2 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + v4 = vec_sub( v2, v0 ); + v5 = vec_sub( v3, v1 ); + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 0, &dst[i] ); + v2 = vec_sub( v1, v0 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::MulAssign16 + + Assumptions: + Assumes that dst starts at aligned address and count is multiple of 4 +============ +*/ +void VPCALL idSIMD_AltiVec::MulAssign16( float *dst, const float constant, const int count ) { +//#define OPER(X) dst[(X)] *= constant + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3; + register vector float constVec; + int i = 0; + register vector float zeroVector = (vector float)(0.0); + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_ld( 16, &dst[i] ); + v2 = vec_madd( v0, constVec, zeroVector ); + v3 = vec_madd( v1, constVec, zeroVector ); + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_madd( v0, constVec, zeroVector ); + vec_st( v1, 0, &dst[i] ); + } +} + +#endif /* ENABLE_16ROUTINES */ + +#ifdef ENABLE_LOWER_TRIANGULAR + +/* +============ +idSIMD_AltiVec::MatX_LowerTriangularSolve + + solves x in L * x = b for the first n rows of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ + +void VPCALL idSIMD_AltiVec::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + + int i, j; + const float *lptr; + const float *lptr2; + const float *lptr3; + const float *lptr4; + float sum; + float sum2; + float sum3; + float sum4; + float tempSum; + float tempSum2; + float tempSum3; + float tempSum4; + vector float vecSum1 = (vector float)(0.0); + vector float vecSum2 = (vector float)(0.0); + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + vector float zeroVector = (vector float)(0.0); + vector float vecSum3, vecSum4, vecSum5, vecSum6, vecSum7, vecSum8; + + vector unsigned char vecPermX = vec_add( vec_lvsl( -1, &x[0] ), (vector unsigned char)(1) ); + + // unrolled this loop a bit + for ( i = skip; i+3 < n; i+=4 ) { + sum = b[i]; + sum2 = b[i+1]; + sum3 = b[i+2]; + sum4 = b[i+3]; + + vecSum1 = zeroVector; + vecSum2 = zeroVector; + vecSum3 = vecSum4 = vecSum5 = vecSum6 = vecSum7 = vecSum8 = zeroVector; + lptr = L[i]; + lptr2 = L[i+1]; + lptr3 = L[i+2]; + lptr4 = L[i+3]; + + vector unsigned char vecPermLptr1 = vec_add( vec_lvsl( -1, lptr ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr2 = vec_add( vec_lvsl( -1, lptr2 ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr3 = vec_add( vec_lvsl( -1, lptr3 ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr4 = vec_add( vec_lvsl( -1, lptr4 ), (vector unsigned char)(1) ); + + for ( j = 0 ; j+7 < i; j+=8 ) { + + v0 = vec_ld( 0, &x[j] ); + v1 = vec_ld( 15, &x[j] ); + vector float vecExtraX = vec_ld( 31, &x[j] ); + v0 = vec_perm( v0, v1, vecPermX ); + v1 = vec_perm( v1, vecExtraX, vecPermX ); + + v2 = vec_ld( 0, lptr + j ); + v3 = vec_ld( 15, lptr + j ); + vector float vecExtra1 = vec_ld( 31, lptr + j ); + v2 = vec_perm( v2, v3, vecPermLptr1 ); + v3 = vec_perm( v3, vecExtra1, vecPermLptr1 ); + + v4 = vec_ld( 0, lptr2 + j ); + v5 = vec_ld( 15, lptr2 + j ); + vector float vecExtra2 = vec_ld( 31, lptr2 + j ); + v4 = vec_perm( v4, v5, vecPermLptr2 ); + v5 = vec_perm( v5, vecExtra2, vecPermLptr2 ); + + v6 = vec_ld( 0, lptr3 + j ); + v7 = vec_ld( 15, lptr3 + j ); + vector float vecExtra3 = vec_ld( 31, lptr3 + j ); + v6 = vec_perm( v6, v7, vecPermLptr3 ); + v7 = vec_perm( v7, vecExtra3, vecPermLptr3 ); + + v8 = vec_ld( 0, lptr4 + j ); + v9 = vec_ld( 15, lptr4 + j ); + vector float vecExtra4 = vec_ld( 31, lptr4 + j ); + v8 = vec_perm( v8, v9, vecPermLptr4 ); + v9 = vec_perm( v9, vecExtra4, vecPermLptr4 ); + + vecSum1 = vec_madd( v2, v0, vecSum1 ); + vecSum2 = vec_madd( v3, v1, vecSum2 ); + + vecSum3 = vec_madd( v4, v0, vecSum3 ); + vecSum4 = vec_madd( v5, v1, vecSum4 ); + + vecSum5 = vec_madd( v6, v0, vecSum5 ); + vecSum6 = vec_madd( v7, v1, vecSum6 ); + + vecSum7 = vec_madd( v8, v0, vecSum7 ); + vecSum8 = vec_madd( v9, v1, vecSum8 ); + } + + // if we ran the unrolled code, we need to sum accross the vectors + // to find out how much to subtract from sum + if ( j > 0 ) { + vecSum1 = vec_add( vecSum1, vecSum2 ); + vecSum3 = vec_add( vecSum3, vecSum4 ); + vecSum5 = vec_add( vecSum5, vecSum6 ); + vecSum7 = vec_add( vecSum7, vecSum8 ); + //sum accross the vectors + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 8 ) ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 4 ) ); + + vecSum3 = vec_add( vecSum3, vec_sld( vecSum3, vecSum3, 8 ) ); + vecSum3 = vec_add( vecSum3, vec_sld( vecSum3, vecSum3, 4 ) ); + + vecSum5 = vec_add( vecSum5, vec_sld( vecSum5, vecSum5, 8 ) ); + vecSum5 = vec_add( vecSum5, vec_sld( vecSum5, vecSum5, 4 ) ); + + vecSum7 = vec_add( vecSum7, vec_sld( vecSum7, vecSum7, 8 ) ); + vecSum7 = vec_add( vecSum7, vec_sld( vecSum7, vecSum7, 4 ) ); + + //move the result to the FPU + vec_ste( vec_splat( vecSum1, 0 ), 0, &tempSum ); + vec_ste( vec_splat( vecSum3, 0 ), 0, &tempSum2 ); + vec_ste( vec_splat( vecSum5, 0 ), 0, &tempSum3 ); + vec_ste( vec_splat( vecSum7, 0 ), 0, &tempSum4 ); + + sum -= tempSum; + sum2 -= tempSum2; + sum3 -= tempSum3; + sum4 -= tempSum4; + } + + //cleanup + for ( ; j < i; j++ ) { + sum -= lptr[j] * x[j]; + sum2 -= lptr2[j] * x[j]; + sum3 -= lptr3[j] * x[j]; + sum4 -= lptr4[j] * x[j]; + } + + // store the 4 results at a time + sum2 -= ( lptr2[i] * sum ); + sum3 = sum3 - ( lptr3[i+1] * sum2 ) - ( lptr3[i] * sum ); + sum4 = sum4 - ( lptr4[i+2] * sum3 ) - ( lptr4[i+1] * sum2 ) - ( lptr4[i] * sum ); + + x[i] = sum; + x[i+1] = sum2; + x[i+2] = sum3; + x[i+3] = sum4; + } + + // cleanup + for ( ; i < n; i++ ) { + sum = b[i]; + vecSum1 = zeroVector; + vecSum2 = zeroVector; + lptr = L[i]; + vector unsigned char vecPermLptr = vec_add( vec_lvsl( -1, lptr ), (vector unsigned char)(1) ); + + for ( j = 0 ; j+7 < i; j+=8 ) { + + v0 = vec_ld( 0, &x[j] ); + v2 = vec_ld( 15, &x[j] ); + vector float vecExtraX = vec_ld( 31, &x[j] ); + v0 = vec_perm( v0, v2, vecPermX ); + v2 = vec_perm( v2, vecExtraX, vecPermX ); + + v1 = vec_ld( 0, lptr + j ); + v3 = vec_ld( 15, lptr + j ); + vector float vecExtra = vec_ld( 31, lptr + j ); + v1 = vec_perm( v1, v3, vecPermLptr ); + v3 = vec_perm( v3, vecExtra, vecPermLptr ); + + vecSum1 = vec_madd( v1, v0, vecSum1 ); + vecSum2 = vec_madd( v3, v2, vecSum2 ); + } + + // if we ran the unrolled code, we need to sum accross the vectors + // to find out how much to subtract from sum + if ( j > 0 ) { + //sum accross the vectors + vecSum1 = vec_add( vecSum1, vecSum2 ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 8 ) ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 4 ) ); + + //move the result to the FPU + vec_ste( vec_splat( vecSum1, 0 ), 0, &tempSum ); + sum -= tempSum; + } + + //cleanup + for ( ; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + } +} + +/* +============ +idSIMD_AltiVec::MatX_LowerTriangularSolveTranspose + + solves x in L.Transpose() * x = b for the first n rows of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_AltiVec::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + float x0, x1, x2, x3, x4, x5, x6; + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + // using local variables to avoid aliasing issues + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x1 = b[1]; + x0 = b[0] - lptr[1*nc+0] * x1; + + x[1] = x1; + x[0] = x0; + return; + case 3: + x2 = b[2]; + x1 = b[1] - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + case 4: + x3 = b[3]; + x2 = b[2] - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + + return; + case 5: + x4 = b[4]; + x3 = b[3] - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + case 6: + x5 = b[5]; + x4 = b[4] - lptr[5*nc+4] * x5; + x3 = b[3] - lptr[5*nc+3] * x5 - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[5*nc+2] * x5 - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[5*nc+1] * x5 - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[5*nc+0] * x5 - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[5] = x5; + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + + return; + case 7: + x6 = b[6]; + x5 = b[5] - lptr[6*nc+5] * x6; + x4 = b[4] - lptr[6*nc+4] * x6 - lptr[5*nc+4] * x5; + x3 = b[3] - lptr[6*nc+3] * x6 - lptr[5*nc+3] * x5 - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[6*nc+2] * x6 - lptr[5*nc+2] * x5 - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[6*nc+1] * x6 - lptr[5*nc+1] * x5 - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[6*nc+0] * x6 - lptr[5*nc+0] * x5 - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[6] = x6; + x[5] = x5; + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + } + return; + } + + int i, j; + register float s0, s1, s2, s3; + float *xptr; + + lptr = L.ToFloatPtr() + n * nc + n - 4; + xptr = x + n; + + // process 4 rows at a time + for ( i = n; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + for ( j = 0; j < n-i; j += 4 ) { + s0 -= lptr[(j+0)*nc+0] * xptr[j+0]; + s1 -= lptr[(j+0)*nc+1] * xptr[j+0]; + s2 -= lptr[(j+0)*nc+2] * xptr[j+0]; + s3 -= lptr[(j+0)*nc+3] * xptr[j+0]; + s0 -= lptr[(j+1)*nc+0] * xptr[j+1]; + s1 -= lptr[(j+1)*nc+1] * xptr[j+1]; + s2 -= lptr[(j+1)*nc+2] * xptr[j+1]; + s3 -= lptr[(j+1)*nc+3] * xptr[j+1]; + s0 -= lptr[(j+2)*nc+0] * xptr[j+2]; + s1 -= lptr[(j+2)*nc+1] * xptr[j+2]; + s2 -= lptr[(j+2)*nc+2] * xptr[j+2]; + s3 -= lptr[(j+2)*nc+3] * xptr[j+2]; + s0 -= lptr[(j+3)*nc+0] * xptr[j+3]; + s1 -= lptr[(j+3)*nc+1] * xptr[j+3]; + s2 -= lptr[(j+3)*nc+2] * xptr[j+3]; + s3 -= lptr[(j+3)*nc+3] * xptr[j+3]; + } + // process left over of the 4 rows + s0 -= lptr[0-1*nc] * s3; + s1 -= lptr[1-1*nc] * s3; + s2 -= lptr[2-1*nc] * s3; + s0 -= lptr[0-2*nc] * s2; + s1 -= lptr[1-2*nc] * s2; + s0 -= lptr[0-3*nc] * s1; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4 + 4 * nc; + xptr -= 4; + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } +} + +/* +============ +idSIMD_AltiVec::MatX_LDLTFactor +============ +*/ +#if __GNUC__ >= 4 +bool VPCALL idSIMD_AltiVec::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#else +unsigned char VPCALL idSIMD_AltiVec::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#endif + int i, j, k, nc; + float *v, *diag, *mptr; + float s0, s1, s2, s3, sum, d; + float s0_2, s1_2, s2_2, s3_2, sum_2; + float *mptr2; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + // unrolling madness! + mptr = mat[i+1]; + mptr2 = mat[i+1] + nc; + + for ( j = i+1; j+1 < n; j+=2 ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + + s0_2 = mptr2[0] * v[0]; + s1_2 = mptr2[1] * v[1]; + s2_2 = mptr2[2] * v[2]; + s3_2 = mptr2[3] * v[3]; + + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + + s0_2 += mptr2[k+0] * v[k+0]; + s1_2 += mptr2[k+1] * v[k+1]; + s2_2 += mptr2[k+2] * v[k+2]; + s3_2 += mptr2[k+3] * v[k+3]; + s0_2 += mptr2[k+4] * v[k+4]; + s1_2 += mptr2[k+5] * v[k+5]; + s2_2 += mptr2[k+6] * v[k+6]; + s3_2 += mptr2[k+7] * v[k+7]; + } + + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; s0_2 += mptr2[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; s1_2 += mptr2[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; s2_2 += mptr2[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; s3_2 += mptr2[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; s0_2 += mptr2[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; s1_2 += mptr2[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; s2_2 += mptr2[k+0] * v[k+0]; + } + // disassociate these adds + s3 += s2; + s1 += s0; + sum = s1 + s3; + + s3_2 += s2_2; + s1_2 += s0_2; + sum_2 = s1_2 + s3_2; + + mptr[i] = ( mptr[i] - sum ) * d; + mptr2[i] = ( mptr2[i] - sum_2 ) * d; + + mptr += nc*2; + mptr2 += nc*2; + } + + // cleanup + for ( ; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + // disassociate these adds + s3 += s2; + s1 += s0; + sum = s1 + s3; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + return true; +} +#endif /* ENABLE_LOWER_TRIANGULAR */ + + +#ifdef LIVE_VICARIOUSLY +/* +============ +idSIMD_AltiVec::BlendJoints +============ +*/ +void VPCALL idSIMD_AltiVec::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + int i; + + // since lerp is a constant, we can special case the two cases if they're true + if ( lerp <= 0.0f ) { + // this sets joints back to joints. No sense in doing no work, so just return + return; + } + + if ( lerp >= 1.0f ) { + // this copies each q from blendJoints to joints and copies each t from blendJoints to joints + memcpy( joints[0].q.ToFloatPtr(), blendJoints[0].q.ToFloatPtr(), sizeof(idJointQuat) * numJoints ); + return; + } + + vector float vecLerp = loadSplatUnalignedScalar( &lerp ); + vector float zeroVector = (vector float)(0); + + for ( i = 0; i+3 < numJoints; i+=4 ) { + int j = index[i]; + int j2 = index[i+1]; + int j3 = index[i+2]; + int j4 = index[i+3]; + + // slerp + const float *jointPtr = joints[j].q.ToFloatPtr(); + const float *blendPtr = blendJoints[j].q.ToFloatPtr(); + const float *jointPtr2 = joints[j2].q.ToFloatPtr(); + const float *blendPtr2 = blendJoints[j2].q.ToFloatPtr(); + const float *jointPtr3 = joints[j3].q.ToFloatPtr(); + const float *blendPtr3 = blendJoints[j3].q.ToFloatPtr(); + const float *jointPtr4 = joints[j4].q.ToFloatPtr(); + const float *blendPtr4 = blendJoints[j4].q.ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, jointPtr2 ), (vector unsigned char)(1) ); + vector unsigned char permVec3 = vec_add( vec_lvsl( -1, jointPtr3 ), (vector unsigned char)(1) ); + vector unsigned char permVec4 = vec_add( vec_lvsl( -1, jointPtr4 ), (vector unsigned char)(1) ); + + vector unsigned char permVec5 = vec_add( vec_lvsl( -1, blendPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec6 = vec_add( vec_lvsl( -1, blendPtr2 ), (vector unsigned char)(1) ); + vector unsigned char permVec7 = vec_add( vec_lvsl( -1, blendPtr3 ), (vector unsigned char)(1) ); + vector unsigned char permVec8 = vec_add( vec_lvsl( -1, blendPtr4 ), (vector unsigned char)(1) ); + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11; + vector float v12, v13, v14, v15, v16; + vector float vecFromX, vecFromY, vecFromZ, vecFromW; + vector float vecToX, vecToY, vecToZ, vecToW; + + // load up the the idJointQuats from joints + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_perm( v0, v1, permVec ); + + v3 = vec_ld( 0, jointPtr2 ); + v4 = vec_ld( 15, jointPtr2 ); + v5 = vec_perm( v3, v4, permVec2 ); + + v6 = vec_ld( 0, jointPtr3 ); + v7 = vec_ld( 15, jointPtr3 ); + v8 = vec_perm( v6, v7, permVec3 ); + + v9 = vec_ld( 0, jointPtr4 ); + v10 = vec_ld( 15, jointPtr4 ); + v11 = vec_perm( v9, v10, permVec4 ); + + // planarizing, so put each x y z w into its own vector + v0 = vec_mergeh( v2, v8 ); + v1 = vec_mergeh( v5, v11 ); + v3 = vec_mergel( v2, v8 ); + v4 = vec_mergel( v5, v11 ); + + vecFromX = vec_mergeh( v0, v1 ); + vecFromY = vec_mergel( v0, v1 ); + vecFromZ = vec_mergeh( v3, v4 ); + vecFromW = vec_mergel( v3, v4 ); + + // load up idJointQuats from blendJoints + v5 = vec_ld( 0, blendPtr ); + v6 = vec_ld( 15, blendPtr ); + v7 = vec_perm( v5, v6, permVec5 ); + + v8 = vec_ld( 0, blendPtr2 ); + v9 = vec_ld( 15, blendPtr2 ); + v10 = vec_perm( v8, v9, permVec6 ); + + v11 = vec_ld( 0, blendPtr3 ); + v12 = vec_ld( 15, blendPtr3 ); + v13 = vec_perm( v11, v12, permVec7 ); + + v14 = vec_ld( 0, blendPtr4 ); + v15 = vec_ld( 15, blendPtr4 ); + v16 = vec_perm( v14, v15, permVec8 ); + + // put these into their own vectors too + v5 = vec_mergeh( v7, v13 ); + v6 = vec_mergeh( v10, v16 ); + v8 = vec_mergel( v7, v13 ); + v9 = vec_mergel( v10, v16 ); + + vecToX = vec_mergeh( v5, v6 ); + vecToY = vec_mergel( v5, v6 ); + vecToZ = vec_mergeh( v8, v9 ); + vecToW = vec_mergel( v8, v9 ); + + // calculate cosom + vector float vecCosom = vec_madd( vecFromX, vecToX, (vector float)(0) ); + vecCosom = vec_madd( vecFromY, vecToY, vecCosom ); + vecCosom = vec_madd( vecFromZ, vecToZ, vecCosom ); + vecCosom = vec_madd( vecFromW, vecToW, vecCosom ); + + // if cosom is < 0, negate it and set temp to negated elements in to. otherwise, set temp to + // to + vector bool int vecCmp, vecCmp2; + vecCmp = vec_cmplt( vecCosom, zeroVector ); + + // negate if needed + vecToX = vec_sel( vecToX, vec_madd( vecToX, (vector float)(-1), zeroVector ), vecCmp ); + vecToY = vec_sel( vecToY, vec_madd( vecToY, (vector float)(-1), zeroVector ), vecCmp ); + vecToZ = vec_sel( vecToZ, vec_madd( vecToZ, (vector float)(-1), zeroVector ), vecCmp ); + vecToW = vec_sel( vecToW, vec_madd( vecToW, (vector float)(-1), zeroVector ), vecCmp ); + vecCosom = vec_sel( vecCosom, vec_madd( vecCosom, (vector float)(-1), zeroVector ), vecCmp ); + + // check if we need to calculate scale + vecCmp2 = vec_cmpgt( vec_sub( (vector float)(1), vecCosom ), (vector float)(1e-6f) ); + vector float vecScale0 = vec_sub( (vector float)(1), vecLerp ); + vector float vecScale1 = vec_splat( vecLerp, 0 ); + + vector float vecWork1 = vec_sub( (vector float)(1), vec_madd( vecCosom, vecCosom, zeroVector ) ); + vector float vecWork2 = ReciprocalSquareRoot( vecWork1 ); + vector float vecWork3 = VectorATan16( vec_madd( vecWork1, vecWork2, zeroVector ), vecCosom ); + + vecWork1 = vec_madd( VectorSin16( vec_madd( vecScale0, vecWork3, zeroVector ) ), vecWork2, zeroVector ); + vecWork2 = vec_madd( VectorSin16( vec_madd( vecLerp, vecWork3, zeroVector ) ), vecWork2, zeroVector ); + + // see which ones we have to insert into our scale0 and scale1 vectors + vecScale0 = vec_sel( vecScale0, vecWork1, vecCmp2 ); + vecScale1 = vec_sel( vecScale1, vecWork2, vecCmp2 ); + + // multiply each element by the scale + vecFromX = vec_madd( vecFromX, vecScale0, zeroVector ); + vecFromY = vec_madd( vecFromY, vecScale0, zeroVector ); + vecFromZ = vec_madd( vecFromZ, vecScale0, zeroVector ); + vecFromW = vec_madd( vecFromW, vecScale0, zeroVector ); + + // multiply temp by scale and add to result + vecFromX = vec_madd( vecToX, vecScale1, vecFromX ); + vecFromY = vec_madd( vecToY, vecScale1, vecFromY ); + vecFromZ = vec_madd( vecToZ, vecScale1, vecFromZ ); + vecFromW = vec_madd( vecToW, vecScale1, vecFromW ); + + // do a transform again to get the results back to vectors we can store out + v5 = vec_mergeh( vecFromX, vecFromZ ); + v6 = vec_mergeh( vecFromY, vecFromW ); + v8 = vec_mergel( vecFromX, vecFromZ ); + v9 = vec_mergel( vecFromY, vecFromW ); + + vecToX = vec_mergeh( v5, v6 ); + vecToY = vec_mergel( v5, v6 ); + vecToZ = vec_mergeh( v8, v9 ); + vecToW = vec_mergel( v8, v9 ); + + vector unsigned char storePerm1 = vec_lvsr( 0, jointPtr ); + vector unsigned char storePerm2 = vec_lvsr( 0, jointPtr2 ); + vector unsigned char storePerm3 = vec_lvsr( 0, jointPtr3 ); + vector unsigned char storePerm4 = vec_lvsr( 0, jointPtr4 ); + + // right rotate the input data + vecToX = vec_perm( vecToX, vecToX, storePerm1 ); + vecToY = vec_perm( vecToY, vecToY, storePerm2 ); + vecToZ = vec_perm( vecToZ, vecToZ, storePerm3 ); + vecToW = vec_perm( vecToW, vecToW, storePerm4 ); + + vec_ste( vecToX, 0, (float*) jointPtr ); + vec_ste( vecToX, 4, (float*) jointPtr ); + vec_ste( vecToX, 8, (float*) jointPtr ); + vec_ste( vecToX, 12, (float*) jointPtr ); + + vec_ste( vecToY, 0, (float*) jointPtr2 ); + vec_ste( vecToY, 4, (float*) jointPtr2 ); + vec_ste( vecToY, 8, (float*) jointPtr2 ); + vec_ste( vecToY, 12, (float*) jointPtr2 ); + + vec_ste( vecToZ, 0, (float*) jointPtr3 ); + vec_ste( vecToZ, 4, (float*) jointPtr3 ); + vec_ste( vecToZ, 8, (float*) jointPtr3 ); + vec_ste( vecToZ, 12, (float*) jointPtr3 ); + + vec_ste( vecToW, 0, (float*) jointPtr4 ); + vec_ste( vecToW, 4, (float*) jointPtr4 ); + vec_ste( vecToW, 8, (float*) jointPtr4 ); + vec_ste( vecToW, 12, (float*) jointPtr4 ); + + // lerp is v1 + l * ( v2 - v1 ); + // the idVec3 T is going to be 12 bytes after the Q, so we can do this without calling ToFloatPtr() again. since its + float *jointVecPtr = (float*)( jointPtr + 4 ); + float *jointVecPtr2 = (float*)( jointPtr2 + 4 ); + float *jointVecPtr3 = (float*)( jointPtr3 + 4 ); + float *jointVecPtr4 = (float*)( jointPtr4 + 4 ); + + v0 = vec_ld( 0, jointVecPtr ); + v1 = vec_ld( 11, jointVecPtr ); + vector float vecLd1 = vec_perm( v0, v1, vec_add( vec_lvsl( -1, jointVecPtr ), (vector unsigned char)(1) ) ); + + v2 = vec_ld( 0, jointVecPtr2 ); + v3 = vec_ld( 11, jointVecPtr2 ); + vector float vecLd2 = vec_perm( v2, v3, vec_add( vec_lvsl( -1, jointVecPtr2 ), (vector unsigned char)(1) ) ); + + v4 = vec_ld( 0, jointVecPtr3 ); + v5 = vec_ld( 11, jointVecPtr3 ); + vector float vecLd3 = vec_perm( v4, v5, vec_add( vec_lvsl( -1, jointVecPtr3 ), (vector unsigned char)(1) ) ); + + v6 = vec_ld( 0, jointVecPtr4 ); + v7 = vec_ld( 11, jointVecPtr4 ); + vector float vecLd4 = vec_perm( v6, v7, vec_add( vec_lvsl( -1, jointVecPtr4 ), (vector unsigned char)(1) ) ); + + vector float vecVecX, vecVecY, vecVecZ; + vecVecX = vecVecY = vecVecZ = zeroVector; + + // planarize + v0 = vec_mergeh( vecLd1, vecLd3 ); + v1 = vec_mergeh( vecLd2, vecLd4 ); + v3 = vec_mergel( vecLd1, vecLd3 ); + v4 = vec_mergel( vecLd2, vecLd4 ); + + vecVecX = vec_mergeh( v0, v1 ); + vecVecY = vec_mergel( v0, v1 ); + vecVecZ = vec_mergeh( v3, v4 ); + + // load blend joint idvec3's + float *blendVecPtr = (float*)( blendPtr + 4 ); + float *blendVecPtr2 =(float*)( blendPtr2 + 4 ); + float *blendVecPtr3 = (float*)( blendPtr3 + 4 ); + float *blendVecPtr4 = (float*)( blendPtr4 + 4 ); + + v0 = vec_ld( 0, blendVecPtr ); + v1 = vec_ld( 11, blendVecPtr ); + vector float vecLd5 = vec_perm( v0, v1, vec_add( vec_lvsl( -1, blendVecPtr ), (vector unsigned char)(1) ) ); + + v2 = vec_ld( 0, blendVecPtr2 ); + v3 = vec_ld( 11, blendVecPtr2 ); + vector float vecLd6 = vec_perm( v2, v3, vec_add( vec_lvsl( -1, blendVecPtr2 ), (vector unsigned char)(1) ) ); + + v4 = vec_ld( 0, blendVecPtr3 ); + v5 = vec_ld( 11, blendVecPtr3 ); + vector float vecLd7 = vec_perm( v4, v5, vec_add( vec_lvsl( -1, blendVecPtr3 ), (vector unsigned char)(1) ) ); + + v6 = vec_ld( 0, blendVecPtr4 ); + v7 = vec_ld( 11, blendVecPtr4 ); + vector float vecLd8 = vec_perm( v6, v7, vec_add( vec_lvsl( -1, blendVecPtr4 ), (vector unsigned char)(1) ) ); + + vector float vecBlendX, vecBlendY, vecBlendZ; + vecBlendX = vecBlendY = vecBlendZ = zeroVector; + + // planarize + v0 = vec_mergeh( vecLd5, vecLd7 ); + v1 = vec_mergeh( vecLd6, vecLd8 ); + v3 = vec_mergel( vecLd5, vecLd7 ); + v4 = vec_mergel( vecLd6, vecLd8 ); + + vecBlendX = vec_mergeh( v0, v1 ); + vecBlendY = vec_mergel( v0, v1 ); + vecBlendZ = vec_mergeh( v3, v4 ); + + // do subtraction + vecWork1 = vec_sub( vecBlendX, vecVecX ); + vecWork2 = vec_sub( vecBlendY, vecVecY ); + vecWork3 = vec_sub( vecBlendZ, vecVecZ ); + + // multiply by lerp and add to v1 + vecVecX = vec_madd( vecWork1, vecLerp, vecVecX ); + vecVecY = vec_madd( vecWork2, vecLerp, vecVecY ); + vecVecZ = vec_madd( vecWork3, vecLerp, vecVecZ ); + + // put it back in original form + v0 = vec_mergeh( vecVecX, vecVecZ ); + v1 = vec_mergeh( vecVecY, zeroVector ); + v3 = vec_mergel( vecVecX, vecVecZ ); + v4 = vec_mergel( vecVecY, zeroVector ); + + // generate vectors to store + vecWork1 = vec_mergeh( v0, v1 ); + vecWork2 = vec_mergel( v0, v1 ); + vecWork3 = vec_mergeh( v3, v4 ); + vector float vecWork4 = vec_mergel( v3, v4 ); + + // store the T values + storePerm1 = vec_lvsr( 0, jointVecPtr ); + storePerm2 = vec_lvsr( 0, jointVecPtr2 ); + storePerm3 = vec_lvsr( 0, jointVecPtr3 ); + storePerm4 = vec_lvsr( 0, jointVecPtr4 ); + + // right rotate the input data + vecWork1 = vec_perm( vecWork1, vecWork1, storePerm1 ); + vecWork2 = vec_perm( vecWork2, vecWork2, storePerm2 ); + vecWork3 = vec_perm( vecWork3, vecWork3, storePerm3 ); + vecWork4 = vec_perm( vecWork4, vecWork4, storePerm4 ); + + vec_ste( vecWork1, 0, (float*) jointVecPtr ); + vec_ste( vecWork1, 4, (float*) jointVecPtr ); + vec_ste( vecWork1, 8, (float*) jointVecPtr ); + + vec_ste( vecWork2, 0, (float*) jointVecPtr2 ); + vec_ste( vecWork2, 4, (float*) jointVecPtr2 ); + vec_ste( vecWork2, 8, (float*) jointVecPtr2 ); + + vec_ste( vecWork3, 0, (float*) jointVecPtr3 ); + vec_ste( vecWork3, 4, (float*) jointVecPtr3 ); + vec_ste( vecWork3, 8, (float*) jointVecPtr3 ); + + vec_ste( vecWork4, 0, (float*) jointVecPtr4 ); + vec_ste( vecWork4, 4, (float*) jointVecPtr4 ); + vec_ste( vecWork4, 8, (float*) jointVecPtr4 ); + } + + // cleanup + for ( ; i < numJoints; i++ ) { + int j = index[i]; + joints[j].q.Slerp( joints[j].q, blendJoints[j].q, lerp ); + joints[j].t.Lerp( joints[j].t, blendJoints[j].t, lerp ); + } +} + +/* +============ +idSIMD_AltiVec::ConvertJointQuatsToJointMats +============ +*/ + +// SSE doesn't vectorize this, and I don't think we should either. Its mainly just copying data, there's very little math involved and +// it's not easily parallelizable +void VPCALL idSIMD_AltiVec::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { + + for ( int i = 0; i < numJoints; i++ ) { + + const float *q = jointQuats[i].q.ToFloatPtr(); + float *m = jointMats[i].ToFloatPtr(); + + m[0*4+3] = q[4]; + m[1*4+3] = q[5]; + m[2*4+3] = q[6]; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + + { + float xx = q[0] * x2; + float yy = q[1] * y2; + float zz = q[2] * z2; + + m[0*4+0] = 1.0f - yy - zz; + m[1*4+1] = 1.0f - xx - zz; + m[2*4+2] = 1.0f - xx - yy; + } + + { + float yz = q[1] * z2; + float wx = q[3] * x2; + + m[2*4+1] = yz - wx; + m[1*4+2] = yz + wx; + } + + { + float xy = q[0] * y2; + float wz = q[3] * z2; + + m[1*4+0] = xy - wz; + m[0*4+1] = xy + wz; + } + + { + float xz = q[0] * z2; + float wy = q[3] * y2; + + m[0*4+2] = xz - wy; + m[2*4+0] = xz + wy; + } + } +} + +/* +============ +idSIMD_AltiVec::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_AltiVec::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { + + int index; + + // Since we use very little of the data we have to pull in for the altivec version, we end up with + // a lot of wasted math. Rather than try to force it to use altivec, I wrote an optimized version + // of InvSqrt for the G5, and made it use that instead. With only this change, we get a little + // bigger than 50% speedup, which is not too shabby. Should really replace idMath::InvSqrt with + // my function so everyone can benefit on G5. + + for ( index = 0; index < numJoints; index++ ) { + + idJointQuat jq; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + float *mat = (float*)( jointMats[index].ToFloatPtr() ); + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + //s = idMath::InvSqrt( t ) * 0.5f; + s = FastScalarInvSqrt( t ) * 0.5f; + + jq.q[3] = s * t; + jq.q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + jq.q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + jq.q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + //s = idMath::InvSqrt( t ) * 0.5f; + s = FastScalarInvSqrt( t ) * 0.5f; + + jq.q[i] = s * t; + jq.q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + jq.q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + jq.q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + jq.t[0] = mat[0 * 4 + 3]; + jq.t[1] = mat[1 * 4 + 3]; + jq.t[2] = mat[2 * 4 + 3]; + jointQuats[index] = jq; + } +} + +/* +============ +idSIMD_AltiVec::TransformJoints +============ +*/ +void VPCALL idSIMD_AltiVec::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + int i; +#if 0 + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } +#else + + // I don't think you can unroll this since the next iteration of the loop might depending on the previous iteration, depending + // on what the parents array looks like. This is true in the test code. + for ( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + float *jointPtr = jointMats[i].ToFloatPtr(); + float *parentPtr = jointMats[parents[i]].ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, parentPtr ), (vector unsigned char)(1) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + // we need to load up 12 float elements that make up the Mat + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_ld( 31, jointPtr ); + v3 = vec_ld( 47, jointPtr ); + + // load parents + v4 = vec_ld( 0, parentPtr ); + v5 = vec_ld( 15, parentPtr ); + v6 = vec_ld( 31, parentPtr ); + v7 = vec_ld( 47, parentPtr ); + + // permute into vectors + vector float vecJointMat1 = vec_perm( v0, v1, permVec ); + vector float vecJointMat2 = vec_perm( v1, v2, permVec ); + vector float vecJointMat3 = vec_perm( v2, v3, permVec ); + + vector float vecParentMat1 = vec_perm( v4, v5, permVec2 ); + vector float vecParentMat2 = vec_perm( v5, v6, permVec2 ); + vector float vecParentMat3 = vec_perm( v6, v7, permVec2 ); + + vector float zero = (vector float)(0); + vector float C1, C2, C3; + + // matrix multiply + C1 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 0 ), zero ); // m(0 to 3) * a(0) + C2 = vec_madd( vecJointMat1, vec_splat( vecParentMat2, 0 ), zero ); // m(4 to 7) * a(4) + C3 = vec_madd( vecJointMat1, vec_splat( vecParentMat3, 0 ), zero ); // m(8 to 11) * a(8) + + C1 = vec_madd( vecJointMat2, vec_splat( vecParentMat1, 1 ), C1 ); // add in m(4 to 7) * a(1) + C2 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 1 ), C2 ); // add in m(4 to 7) * a(5) + C3 = vec_madd( vecJointMat2, vec_splat( vecParentMat3, 1 ), C3 ); // add in m(4 to 7) * a(9) + + C1 = vec_madd( vecJointMat3, vec_splat( vecParentMat1, 2 ), C1 ); + C2 = vec_madd( vecJointMat3, vec_splat( vecParentMat2, 2 ), C2 ); + C3 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 2 ), C3 ); + + // do the addition at the end + vector unsigned char permZeroAndLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,28,29,30,31); + C1 = vec_add( C1, vec_perm( zero, vecParentMat1, permZeroAndLast ) ); + C2 = vec_add( C2, vec_perm( zero, vecParentMat2, permZeroAndLast ) ); + C3 = vec_add( C3, vec_perm( zero, vecParentMat3, permZeroAndLast ) ); + + // store results + UNALIGNED_STORE3( (float*) jointPtr, C1, C2, C3 ); + } +#endif +} + +/* +============ +idSIMD_AltiVec::UntransformJoints +============ +*/ +void VPCALL idSIMD_AltiVec::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + int i; +#if 0 + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } +#else + // I don't think you can unroll this since the next iteration of the loop might depending on the previous iteration, depending + // on what the parents array looks like. This is true in the test code. + for ( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + float *jointPtr = jointMats[i].ToFloatPtr(); + float *parentPtr = jointMats[parents[i]].ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, parentPtr ), (vector unsigned char)(1) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + // we need to load up 12 float elements that make up the Mat + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_ld( 31, jointPtr ); + v3 = vec_ld( 47, jointPtr ); + + // load parents + v4 = vec_ld( 0, parentPtr ); + v5 = vec_ld( 15, parentPtr ); + v6 = vec_ld( 31, parentPtr ); + v7 = vec_ld( 47, parentPtr ); + + // permute into vectors + vector float vecJointMat1 = vec_perm( v0, v1, permVec ); + vector float vecJointMat2 = vec_perm( v1, v2, permVec ); + vector float vecJointMat3 = vec_perm( v2, v3, permVec ); + + vector float vecParentMat1 = vec_perm( v4, v5, permVec2 ); + vector float vecParentMat2 = vec_perm( v5, v6, permVec2 ); + vector float vecParentMat3 = vec_perm( v6, v7, permVec2 ); + + vector float zero = (vector float)(0); + vector float C1, C2, C3; + + // do subtraction at the beginning + vector unsigned char permZeroAndLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,28,29,30,31); + vecJointMat1 = vec_sub( vecJointMat1, vec_perm( zero, vecParentMat1, permZeroAndLast ) ); + vecJointMat2 = vec_sub( vecJointMat2, vec_perm( zero, vecParentMat2, permZeroAndLast ) ); + vecJointMat3 = vec_sub( vecJointMat3, vec_perm( zero, vecParentMat3, permZeroAndLast ) ); + + // matrix multiply + C1 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 0 ), zero ); + C2 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 1 ), zero ); + C3 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 2 ), zero ); + + C1 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 0 ), C1 ); + C2 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 1 ), C2 ); + C3 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 2 ), C3 ); + + C1 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 0 ), C1 ); + C2 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 1 ), C2 ); + C3 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 2 ), C3 ); + + // store results back + vector unsigned char storePerm = vec_lvsr( 0, jointPtr ); + + // right rotate the input data + C1 = vec_perm( C1, C1, storePerm ); + C2 = vec_perm( C2, C2, storePerm ); + C3 = vec_perm( C3, C3, storePerm ); + + vec_ste( C1, 0, (float*) jointPtr ); + vec_ste( C1, 4, (float*) jointPtr ); + vec_ste( C1, 8, (float*) jointPtr ); + vec_ste( C1, 12, (float*) jointPtr ); + + vec_ste( C2, 16, (float*) jointPtr ); + vec_ste( C2, 20, (float*) jointPtr ); + vec_ste( C2, 24, (float*) jointPtr ); + vec_ste( C2, 28, (float*) jointPtr ); + + vec_ste( C3, 32, (float*) jointPtr ); + vec_ste( C3, 36, (float*) jointPtr ); + vec_ste( C3, 40, (float*) jointPtr ); + vec_ste( C3, 44, (float*) jointPtr ); + } + +#endif +} + +#endif /* LIVE_VICARIOUSLY */ + +#ifdef ENABLE_CULL + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::TracePointCull +============ +*/ +void VPCALL idSIMD_AltiVec::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + byte tOr; + tOr = 0; + + // pointers + const float *planePtr = planes[0].ToFloatPtr(); + + vector unsigned int vecShift1 = (vector unsigned int)(0,1,2,3); + vector unsigned int vecShift2 = (vector unsigned int)(4,5,6,7); + vector unsigned int vecFlipBits = (vector unsigned int)(0x0F); + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector float zeroVector = (vector float)(0); + vector float vecRadius; + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float vec1Sum1, vec1Sum2, vec1Sum3, vec1Sum4; + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector float vecDPlusRadius1, vecDPlusRadius2, vecDPlusRadius3, vecDPlusRadius4; + vector float vecDMinusRadius1, vecDMinusRadius2, vecDMinusRadius3, vecDMinusRadius4; + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4, vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecTotals; + vector unsigned int tempIntSum; + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + // load constants + vecRadius = loadSplatUnalignedScalar( &radius ); + + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + int i = 0; + + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane3 ); + + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec1Sum3 ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec1Sum3 ); + vec1Sum3 = vec_add( vec1Sum3, vecPlane3 ); + + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec1Sum4 ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec1Sum4 ); + vec1Sum4 = vec_add( vec1Sum4, vecPlane3 ); + + // vec1Sum1 now holds d0, d1, d2, d3. calculate the + // difference with +radius and -radius + vecDPlusRadius1 = vec_add( vec1Sum1, vecRadius ); + vecDMinusRadius1 = vec_sub( vec1Sum1, vecRadius ); + vecDPlusRadius2 = vec_add( vec1Sum2, vecRadius ); + vecDMinusRadius2 = vec_sub( vec1Sum2, vecRadius ); + vecDPlusRadius3 = vec_add( vec1Sum3, vecRadius ); + vecDMinusRadius3 = vec_sub( vec1Sum3, vecRadius ); + vecDPlusRadius4 = vec_add( vec1Sum4, vecRadius ); + vecDMinusRadius4 = vec_sub( vec1Sum4, vecRadius ); + + // do compare + vecCmp1 = vec_cmplt( vecDPlusRadius1, zeroVector ); + vecCmp2 = vec_cmplt( vecDMinusRadius1, zeroVector ); + vecCmp3 = vec_cmplt( vecDPlusRadius2, zeroVector ); + vecCmp4 = vec_cmplt( vecDMinusRadius2, zeroVector ); + vecCmp5 = vec_cmplt( vecDPlusRadius3, zeroVector ); + vecCmp6 = vec_cmplt( vecDMinusRadius3, zeroVector ); + vecCmp7 = vec_cmplt( vecDPlusRadius4, zeroVector ); + vecCmp8 = vec_cmplt( vecDMinusRadius4, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + // OR (add) them all together + vecBitShifted1 = vec_add( vecBitShifted1, vecBitShifted2 ); + vecBitShifted3 = vec_add( vecBitShifted3, vecBitShifted4 ); + vecBitShifted5 = vec_add( vecBitShifted5, vecBitShifted6 ); + vecBitShifted7 = vec_add( vecBitShifted7, vecBitShifted8 ); + + vecTotals = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecTotals = vec_add( vecTotals, vec_sld( vecTotals, vecTotals, 4 ) ); + tempIntSum = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_mergeh( vecTotals, tempIntSum ); + tempIntSum = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermHalves ); + tempIntSum = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermLast ); + + // store out results + vector unsigned int tempSt = vec_xor( vecTotals, vecFlipBits ); + tempSt = vec_perm( tempSt, tempSt, cullBitPerm ); + vec_ste( tempSt, 0, &cullBitVal[0] ); + vec_ste( tempSt, 4, &cullBitVal[0] ); + vec_ste( tempSt, 8, &cullBitVal[0] ); + vec_ste( tempSt, 12, &cullBitVal[0] ); + + tOr |= cullBitVal[0]; + tOr |= cullBitVal[1]; + tOr |= cullBitVal[2]; + tOr |= cullBitVal[3]; + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} +#else + +/* +============ +idSIMD_AltiVec::TracePointCull +============ +*/ +void VPCALL idSIMD_AltiVec::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + byte tOr; + tOr = 0; + + // pointers + const float *planePtr = planes[0].ToFloatPtr(); + + vector unsigned int vecShift1 = (vector unsigned int)(0,1,2,3); + vector unsigned int vecShift2 = (vector unsigned int)(4,5,6,7); + vector unsigned int vecFlipBits = (vector unsigned int)(0x0F); + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector float zeroVector = (vector float)(0); + vector float vecRadius; + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float vec1Sum1, vec1Sum2, vec1Sum3, vec1Sum4; + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector float vecDPlusRadius1, vecDPlusRadius2, vecDPlusRadius3, vecDPlusRadius4; + vector float vecDMinusRadius1, vecDMinusRadius2, vecDMinusRadius3, vecDMinusRadius4; + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4, vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecTotals; + vector unsigned int tempIntSum; + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + // load constants + vecRadius = loadSplatUnalignedScalar( &radius ); + + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + int i = 0; + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + vecXYZ1 = vec_ld( 0, vertPtr ); + vecXYZ2 = vec_ld( 0, vertPtr2 ); + vecXYZ3 = vec_ld( 0, vertPtr3 ); + vecXYZ4 = vec_ld( 0, vertPtr4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane3 ); + + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec1Sum3 ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec1Sum3 ); + vec1Sum3 = vec_add( vec1Sum3, vecPlane3 ); + + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec1Sum4 ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec1Sum4 ); + vec1Sum4 = vec_add( vec1Sum4, vecPlane3 ); + + // vec1Sum1 now holds d0, d1, d2, d3. calculate the + // difference with +radius and -radius + vecDPlusRadius1 = vec_add( vec1Sum1, vecRadius ); + vecDMinusRadius1 = vec_sub( vec1Sum1, vecRadius ); + vecDPlusRadius2 = vec_add( vec1Sum2, vecRadius ); + vecDMinusRadius2 = vec_sub( vec1Sum2, vecRadius ); + vecDPlusRadius3 = vec_add( vec1Sum3, vecRadius ); + vecDMinusRadius3 = vec_sub( vec1Sum3, vecRadius ); + vecDPlusRadius4 = vec_add( vec1Sum4, vecRadius ); + vecDMinusRadius4 = vec_sub( vec1Sum4, vecRadius ); + + // do compare + vecCmp1 = vec_cmplt( vecDPlusRadius1, zeroVector ); + vecCmp2 = vec_cmplt( vecDMinusRadius1, zeroVector ); + vecCmp3 = vec_cmplt( vecDPlusRadius2, zeroVector ); + vecCmp4 = vec_cmplt( vecDMinusRadius2, zeroVector ); + vecCmp5 = vec_cmplt( vecDPlusRadius3, zeroVector ); + vecCmp6 = vec_cmplt( vecDMinusRadius3, zeroVector ); + vecCmp7 = vec_cmplt( vecDPlusRadius4, zeroVector ); + vecCmp8 = vec_cmplt( vecDMinusRadius4, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + // OR (add) them all together + vecBitShifted1 = vec_add( vecBitShifted1, vecBitShifted2 ); + vecBitShifted3 = vec_add( vecBitShifted3, vecBitShifted4 ); + vecBitShifted5 = vec_add( vecBitShifted5, vecBitShifted6 ); + vecBitShifted7 = vec_add( vecBitShifted7, vecBitShifted8 ); + + vecTotals = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecTotals = vec_add( vecTotals, vec_sld( vecTotals, vecTotals, 4 ) ); + tempIntSum = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_mergeh( vecTotals, tempIntSum ); + tempIntSum = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermHalves ); + tempIntSum = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermLast ); + + // store out results + vector unsigned int tempSt = vec_xor( vecTotals, vecFlipBits ); + tempSt = vec_perm( tempSt, tempSt, cullBitPerm ); + vec_ste( tempSt, 0, &cullBitVal[0] ); + vec_ste( tempSt, 4, &cullBitVal[0] ); + vec_ste( tempSt, 8, &cullBitVal[0] ); + vec_ste( tempSt, 12, &cullBitVal[0] ); + + tOr |= cullBitVal[0]; + tOr |= cullBitVal[1]; + tOr |= cullBitVal[2]; + tOr |= cullBitVal[3]; + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +#endif /* DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::DecalPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + const float *planePtr = planes[0].ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3, vecPlane4, vecPlane5, vecPlane6, vecPlane7; + vector float zeroVector = (vector float)(0.0); + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 16 ); + v1 = vec_ld( 15, planePtr + 16 ); + vecPlane4 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 20 ); + v3 = vec_ld( 15, planePtr + 20 ); + vecPlane5 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + v0 = vec_mergeh( vecPlane4, zeroVector ); + v1 = vec_mergeh( vecPlane5, zeroVector ); + v2 = vec_mergel( vecPlane4, zeroVector ); + v3 = vec_mergel( vecPlane5, zeroVector ); + + vecPlane4 = vec_mergeh( v0, v1 ); + vecPlane5 = vec_mergel( v0, v1 ); + vecPlane6 = vec_mergeh( v2, v3 ); + vecPlane7 = vec_mergel( v2, v3 ); + + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector bool int oneIntVector = (vector bool int)(1); + vector float vec1Sum1, vec1Sum2, vec2Sum1, vec2Sum2, vec3Sum1, vec3Sum2, vec4Sum1, vec4Sum2; + vector unsigned int vecShift1 = (vector unsigned int)(0, 1, 2, 3 ); + vector unsigned int vecShift2 = (vector unsigned int)(4, 5, 0, 0 ); + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4; + vector unsigned int vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecFlipBits = (vector unsigned int)( 0x3F, 0x3F, 0x3F, 0x3F ); + vector unsigned int vecR1, vecR2, vecR3, vecR4; + vector unsigned char permHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int vBits[4]; + vector unsigned char vBitPerm = vec_lvsr( 0, &vBits[4] ); + + i = 0; + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane4, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane5, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane6, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane7 ); + + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec2Sum1 ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec2Sum1 ); + vec2Sum1 = vec_add( vec2Sum1, vecPlane3 ); + + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane4, zeroVector ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane5, vec2Sum2 ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane6, vec2Sum2 ); + vec2Sum2 = vec_add( vec2Sum2, vecPlane7 ); + + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec3Sum1 ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec3Sum1 ); + vec3Sum1 = vec_add( vec3Sum1, vecPlane3 ); + + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane4, zeroVector ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane5, vec3Sum2 ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane6, vec3Sum2 ); + vec3Sum2 = vec_add( vec3Sum2, vecPlane7 ); + + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec4Sum1 ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec4Sum1 ); + vec4Sum1 = vec_add( vec4Sum1, vecPlane3 ); + + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane4, zeroVector ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane5, vec4Sum2 ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane6, vec4Sum2 ); + vec4Sum2 = vec_add( vec4Sum2, vecPlane7 ); + + vecCmp1 = vec_cmplt( vec1Sum1, zeroVector ); + vecCmp2 = vec_cmplt( vec1Sum2, zeroVector ); + vecCmp3 = vec_cmplt( vec2Sum1, zeroVector ); + vecCmp4 = vec_cmplt( vec2Sum2, zeroVector ); + vecCmp5 = vec_cmplt( vec3Sum1, zeroVector ); + vecCmp6 = vec_cmplt( vec3Sum2, zeroVector ); + vecCmp7 = vec_cmplt( vec4Sum1, zeroVector ); + vecCmp8 = vec_cmplt( vec4Sum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + //OR them all together (this is the same as adding them, since they're all only 1 bit set) + vecR1 = (vector unsigned int)(0); //zeroIntVector; + vecR1 = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecR1 = vec_add( vecR1, vec_sld( vecR1, vecR1, 4 ) ); + vecR1 = vec_add(vecR1, vecBitShifted2 ); + vecR1 = vec_or( vecR1, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + vecR2 = (vector unsigned int)(0); //zeroIntVector; + vecR2 = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + vecR2 = vec_add( vecR2, vec_sld( vecR2, vecR2, 4 ) ); + vecR2 = vec_add(vecR2, vecBitShifted4 ); + vecR2 = vec_or( vecR2, vec_sld( vecBitShifted4, vecBitShifted4, 4 ) ); + + vecR3 = (vector unsigned int)(0); //zeroIntVector; + vecR3 = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + vecR3 = vec_add( vecR3, vec_sld( vecR3, vecR3, 4 ) ); + vecR3 = vec_add(vecR3, vecBitShifted6 ); + vecR3 = vec_or( vecR3, vec_sld( vecBitShifted6, vecBitShifted6, 4 ) ); + + vecR4 = (vector unsigned int)(0); //zeroIntVector; + vecR4 = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + vecR4 = vec_add( vecR4, vec_sld( vecR4, vecR4, 4 ) ); + vecR4 = vec_add(vecR4, vecBitShifted8 ); + vecR4 = vec_or( vecR4, vec_sld( vecBitShifted8, vecBitShifted8, 4 ) ); + + // take the first element from each vector and put them into vecR1 + vecR1 = vec_mergeh( vecR1, vecR2 ); + vecR3 = vec_mergeh( vecR3, vecR4 ); + vecR1 = vec_perm( vecR1, vecR3, permHalves ); + + // XOR with 0x3F to flip lower 6 bits + vecR1 = vec_xor( vecR1, vecFlipBits ); + + // store out results. don't have 16 at a time so let's just + // do this and avoid alignment concerns + vecR1 = vec_perm( vecR1, vecR1, vBitPerm ); + vec_ste( vecR1, 0, &vBits[0] ); + vec_ste( vecR1, 4, &vBits[0] ); + vec_ste( vecR1, 8, &vBits[0] ); + vec_ste( vecR1, 12, &vBits[0] ); + + cullBits[i] = vBits[0]; + cullBits[i+1] = vBits[1]; + cullBits[i+2] = vBits[2]; + cullBits[i+3] = vBits[3]; + } + + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + // they check if the sign bit is set by casting as long and shifting right 31 places. + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +#else + +/* +============ +idSIMD_AltiVec::DecalPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + const float *planePtr = planes[0].ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3, vecPlane4, vecPlane5, vecPlane6, vecPlane7; + vector float zeroVector = (vector float)(0.0); + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 16 ); + v1 = vec_ld( 15, planePtr + 16 ); + vecPlane4 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 20 ); + v3 = vec_ld( 15, planePtr + 20 ); + vecPlane5 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + v0 = vec_mergeh( vecPlane4, zeroVector ); + v1 = vec_mergeh( vecPlane5, zeroVector ); + v2 = vec_mergel( vecPlane4, zeroVector ); + v3 = vec_mergel( vecPlane5, zeroVector ); + + vecPlane4 = vec_mergeh( v0, v1 ); + vecPlane5 = vec_mergel( v0, v1 ); + vecPlane6 = vec_mergeh( v2, v3 ); + vecPlane7 = vec_mergel( v2, v3 ); + + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector bool int oneIntVector = (vector bool int)(1); + vector float vec1Sum1, vec1Sum2, vec2Sum1, vec2Sum2, vec3Sum1, vec3Sum2, vec4Sum1, vec4Sum2; + vector unsigned int vecShift1 = (vector unsigned int)(0, 1, 2, 3 ); + vector unsigned int vecShift2 = (vector unsigned int)(4, 5, 0, 0 ); + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4; + vector unsigned int vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecFlipBits = (vector unsigned int)( 0x3F, 0x3F, 0x3F, 0x3F ); + vector unsigned int vecR1, vecR2, vecR3, vecR4; + vector unsigned char permHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int vBits[4]; + vector unsigned char vBitPerm = vec_lvsr( 0, &vBits[4] ); + + i = 0; + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane4, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane5, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane6, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane7 ); + + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec2Sum1 ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec2Sum1 ); + vec2Sum1 = vec_add( vec2Sum1, vecPlane3 ); + + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane4, zeroVector ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane5, vec2Sum2 ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane6, vec2Sum2 ); + vec2Sum2 = vec_add( vec2Sum2, vecPlane7 ); + + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec3Sum1 ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec3Sum1 ); + vec3Sum1 = vec_add( vec3Sum1, vecPlane3 ); + + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane4, zeroVector ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane5, vec3Sum2 ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane6, vec3Sum2 ); + vec3Sum2 = vec_add( vec3Sum2, vecPlane7 ); + + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec4Sum1 ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec4Sum1 ); + vec4Sum1 = vec_add( vec4Sum1, vecPlane3 ); + + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane4, zeroVector ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane5, vec4Sum2 ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane6, vec4Sum2 ); + vec4Sum2 = vec_add( vec4Sum2, vecPlane7 ); + + vecCmp1 = vec_cmplt( vec1Sum1, zeroVector ); + vecCmp2 = vec_cmplt( vec1Sum2, zeroVector ); + vecCmp3 = vec_cmplt( vec2Sum1, zeroVector ); + vecCmp4 = vec_cmplt( vec2Sum2, zeroVector ); + vecCmp5 = vec_cmplt( vec3Sum1, zeroVector ); + vecCmp6 = vec_cmplt( vec3Sum2, zeroVector ); + vecCmp7 = vec_cmplt( vec4Sum1, zeroVector ); + vecCmp8 = vec_cmplt( vec4Sum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + //OR them all together (this is the same as adding them, since they're all only 1 bit set) + vecR1 = (vector unsigned int)(0); //zeroIntVector; + vecR1 = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecR1 = vec_add( vecR1, vec_sld( vecR1, vecR1, 4 ) ); + vecR1 = vec_add(vecR1, vecBitShifted2 ); + vecR1 = vec_or( vecR1, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + vecR2 = (vector unsigned int)(0); //zeroIntVector; + vecR2 = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + vecR2 = vec_add( vecR2, vec_sld( vecR2, vecR2, 4 ) ); + vecR2 = vec_add(vecR2, vecBitShifted4 ); + vecR2 = vec_or( vecR2, vec_sld( vecBitShifted4, vecBitShifted4, 4 ) ); + + vecR3 = (vector unsigned int)(0); //zeroIntVector; + vecR3 = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + vecR3 = vec_add( vecR3, vec_sld( vecR3, vecR3, 4 ) ); + vecR3 = vec_add(vecR3, vecBitShifted6 ); + vecR3 = vec_or( vecR3, vec_sld( vecBitShifted6, vecBitShifted6, 4 ) ); + + vecR4 = (vector unsigned int)(0); //zeroIntVector; + vecR4 = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + vecR4 = vec_add( vecR4, vec_sld( vecR4, vecR4, 4 ) ); + vecR4 = vec_add(vecR4, vecBitShifted8 ); + vecR4 = vec_or( vecR4, vec_sld( vecBitShifted8, vecBitShifted8, 4 ) ); + + // take the first element from each vector and put them into vecR1 + vecR1 = vec_mergeh( vecR1, vecR2 ); + vecR3 = vec_mergeh( vecR3, vecR4 ); + vecR1 = vec_perm( vecR1, vecR3, permHalves ); + + // XOR with 0x3F to flip lower 6 bits + vecR1 = vec_xor( vecR1, vecFlipBits ); + + // store out results. don't have 16 at a time so let's just + // do this and avoid alignment concerns + vecR1 = vec_perm( vecR1, vecR1, vBitPerm ); + vec_ste( vecR1, 0, &vBits[0] ); + vec_ste( vecR1, 4, &vBits[0] ); + vec_ste( vecR1, 8, &vBits[0] ); + vec_ste( vecR1, 12, &vBits[0] ); + + cullBits[i] = vBits[0]; + cullBits[i+1] = vBits[1]; + cullBits[i+2] = vBits[2]; + cullBits[i+3] = vBits[3]; + } + + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + // they check if the sign bit is set by casting as long and shifting right 31 places. + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + + +#endif /*DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::OverlayPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + + float p0x, p0y, p0z, p0d; + float p1x, p1y, p1z, p1d; + + const float *planePtr = planes[0].ToFloatPtr(); + const float *vertPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector unsigned char vecPerm; + vector float zeroVector = (vector float)(0); + + p0x = *(planePtr + 0); + p0y = *(planePtr + 1); + p0z = *(planePtr + 2); + p0d = *(planePtr + 3); + p1x = *(planePtr + 4); + p1y = *(planePtr + 5); + p1z = *(planePtr + 6); + p1d = *(planePtr + 7); + + // populate the planes + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 31, planePtr ); + vecPlane1 = vec_perm( v1, v2, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane0 ); + v1 = vec_mergeh( vecPlane1, vecPlane1 ); + v2 = vec_mergel( vecPlane0, vecPlane0 ); + v3 = vec_mergel( vecPlane1, vecPlane1); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float oneVector = (vector float)(1); + + vector float vecSum1, vecSum2, vecSum1Inv,vecSum2Inv; + + vector bool int vecCmp1, vecCmp2, vecCmp1Inv, vecCmp2Inv; + vector float negTwoVector = (vector float)(-2); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted1Inv, vecBitShifted2Inv; + vector unsigned int vecShift = (vector unsigned int)( 0, 1, 0, 1 ); + vector unsigned int vecShiftInv = (vector unsigned int)( 2, 3, 2, 3 ); + vector unsigned char vecPermFirstThird = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + + i = 0; + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + // like a splat, but only doing halves + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum1 ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum1 ); + vecSum1 = vec_add( vecSum1, vecPlane3 ); + + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum2 ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum2 ); + vecSum2 = vec_add( vecSum2, vecPlane3 ); + + // store out results + UNALIGNED_STORE2( &texCoords[i][0], vecSum1, vecSum2 ); + + // bit manipulation + vecCmp1 = vec_cmplt( vecSum1, zeroVector ); + vecCmp2 = vec_cmplt( vecSum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + + // store out and write to cullBits + // finally, a use for algebra! 1-x = x + 1 - 2x + vecSum1Inv = vec_madd( vecSum1, negTwoVector, vecSum1 ); + vecSum2Inv = vec_madd( vecSum2, negTwoVector, vecSum2 ); + vecSum1Inv = vec_add( vecSum1Inv, oneVector ); + vecSum2Inv = vec_add( vecSum2Inv, oneVector ); + + // do the same comparisons for the inverted d0/d1 + vecCmp1Inv = vec_cmplt( vecSum1Inv, zeroVector ); + vecCmp2Inv = vec_cmplt( vecSum2Inv, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1Inv = vec_and( vecCmp1Inv, oneIntVector ); + vecCmp2Inv = vec_and( vecCmp2Inv, oneIntVector ); + + // shift them as needed + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift ); + vecBitShifted1Inv = vec_sl( (vector unsigned int)vecCmp1Inv, vecShiftInv ); + vecBitShifted2Inv = vec_sl( (vector unsigned int)vecCmp2Inv, vecShiftInv ); + + // OR them all together. since only 1 bit is set for each value, thats + // the same as adding them. add up d0 + d1 + d0Inv + d1Inv + vector unsigned int vecResult; + vector unsigned int vecResult2; + vector unsigned int vecResult3; + vecResult = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 4 ) ); + + vecResult2 = vec_add( vecBitShifted2, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + // vecResult now holds the values without the inverses yet, so add those + vecResult = vec_perm( vecResult, vecResult2, vecPermFirstThird ); + vecResult2 = vec_add( vecBitShifted1Inv, vec_sld( vecBitShifted1Inv, vecBitShifted1Inv, 4 ) ); + vecResult3 = vec_add( vecBitShifted2Inv, vec_sld( vecBitShifted2Inv, vecBitShifted2Inv, 4 ) ); + vecResult2 = vec_perm( vecResult2, vecResult3, vecPermFirstThird ); + + vecResult = vec_add( vecResult, vecResult2 ); + + //store out results + vecResult = vec_perm( vecResult, vecResult, cullBitPerm ); + vec_ste( vecResult, 0, &cullBitVal[0] ); + vec_ste( vecResult, 4, &cullBitVal[0] ); + vec_ste( vecResult, 8, &cullBitVal[0] ); + vec_ste( vecResult, 12, &cullBitVal[0] ); + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1; + float vx, vy, vz; + + vx = *( vertPtr + (i*DRAWVERT_OFFSET) + 0 ); + vy = *( vertPtr + (i*DRAWVERT_OFFSET) + 1 ); + vz = *( vertPtr + (i*DRAWVERT_OFFSET) + 2 ); + + d0 = p0x * vx + p0y * vy + p0z * vz + p0d; + d1 = p1x * vx + p1y * vy + p1z * vz + p1d; + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = ( d0 >= 0 ) ? 0 : 1; + d0 = 1.0f - d0; + bits |= ( d1 >= 0 ) ? 0 : 1*2; + d1 = 1.0f - d1; + + bits |= ( d0 >= 0 ) ? 0: 1*4; + bits |= ( d1 >= 0 ) ? 0: 1*8; + + cullBits[i] = bits; + } +} +#else + +/* +============ +idSIMD_AltiVec::OverlayPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + + float p0x, p0y, p0z, p0d; + float p1x, p1y, p1z, p1d; + + const float *planePtr = planes[0].ToFloatPtr(); + const float *vertPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector unsigned char vecPerm; + vector float zeroVector = (vector float)(0); + + p0x = *(planePtr + 0); + p0y = *(planePtr + 1); + p0z = *(planePtr + 2); + p0d = *(planePtr + 3); + p1x = *(planePtr + 4); + p1y = *(planePtr + 5); + p1z = *(planePtr + 6); + p1d = *(planePtr + 7); + + // populate the planes + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 31, planePtr ); + vecPlane1 = vec_perm( v1, v2, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane0 ); + v1 = vec_mergeh( vecPlane1, vecPlane1 ); + v2 = vec_mergel( vecPlane0, vecPlane0 ); + v3 = vec_mergel( vecPlane1, vecPlane1); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float oneVector = (vector float)(1); + + vector float vecSum1, vecSum2, vecSum1Inv,vecSum2Inv; + + vector bool int vecCmp1, vecCmp2, vecCmp1Inv, vecCmp2Inv; + vector float negTwoVector = (vector float)(-2); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted1Inv, vecBitShifted2Inv; + vector unsigned int vecShift = (vector unsigned int)( 0, 1, 0, 1 ); + vector unsigned int vecShiftInv = (vector unsigned int)( 2, 3, 2, 3 ); + vector unsigned char vecPermFirstThird = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + + i = 0; + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + vecXYZ1 = vec_ld( 0, vertPtr ); + vecXYZ2 = vec_ld( 0, vertPtr2 ); + vecXYZ3 = vec_ld( 0, vertPtr3 ); + vecXYZ4 = vec_ld( 0, vertPtr4 ); + + // like a splat, but only doing halves + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum1 ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum1 ); + vecSum1 = vec_add( vecSum1, vecPlane3 ); + + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum2 ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum2 ); + vecSum2 = vec_add( vecSum2, vecPlane3 ); + + // store out results + UNALIGNED_STORE2( &texCoords[i][0], vecSum1, vecSum2 ); + + // bit manipulation + vecCmp1 = vec_cmplt( vecSum1, zeroVector ); + vecCmp2 = vec_cmplt( vecSum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + + // store out and write to cullBits + // finally, a use for algebra! 1-x = x + 1 - 2x + vecSum1Inv = vec_madd( vecSum1, negTwoVector, vecSum1 ); + vecSum2Inv = vec_madd( vecSum2, negTwoVector, vecSum2 ); + vecSum1Inv = vec_add( vecSum1Inv, oneVector ); + vecSum2Inv = vec_add( vecSum2Inv, oneVector ); + + // do the same comparisons for the inverted d0/d1 + vecCmp1Inv = vec_cmplt( vecSum1Inv, zeroVector ); + vecCmp2Inv = vec_cmplt( vecSum2Inv, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1Inv = vec_and( vecCmp1Inv, oneIntVector ); + vecCmp2Inv = vec_and( vecCmp2Inv, oneIntVector ); + + // shift them as needed + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift ); + vecBitShifted1Inv = vec_sl( (vector unsigned int)vecCmp1Inv, vecShiftInv ); + vecBitShifted2Inv = vec_sl( (vector unsigned int)vecCmp2Inv, vecShiftInv ); + + // OR them all together. since only 1 bit is set for each value, thats + // the same as adding them. add up d0 + d1 + d0Inv + d1Inv + vector unsigned int vecResult; + vector unsigned int vecResult2; + vector unsigned int vecResult3; + vecResult = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 4 ) ); + + vecResult2 = vec_add( vecBitShifted2, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + // vecResult now holds the values without the inverses yet, so add those + vecResult = vec_perm( vecResult, vecResult2, vecPermFirstThird ); + vecResult2 = vec_add( vecBitShifted1Inv, vec_sld( vecBitShifted1Inv, vecBitShifted1Inv, 4 ) ); + vecResult3 = vec_add( vecBitShifted2Inv, vec_sld( vecBitShifted2Inv, vecBitShifted2Inv, 4 ) ); + vecResult2 = vec_perm( vecResult2, vecResult3, vecPermFirstThird ); + + vecResult = vec_add( vecResult, vecResult2 ); + + //store out results + vecResult = vec_perm( vecResult, vecResult, cullBitPerm ); + vec_ste( vecResult, 0, &cullBitVal[0] ); + vec_ste( vecResult, 4, &cullBitVal[0] ); + vec_ste( vecResult, 8, &cullBitVal[0] ); + vec_ste( vecResult, 12, &cullBitVal[0] ); + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1; + float vx, vy, vz; + + vx = *( vertPtr + (i*DRAWVERT_OFFSET) + 0 ); + vy = *( vertPtr + (i*DRAWVERT_OFFSET) + 1 ); + vz = *( vertPtr + (i*DRAWVERT_OFFSET) + 2 ); + + d0 = p0x * vx + p0y * vy + p0z * vz + p0d; + d1 = p1x * vx + p1y * vy + p1z * vz + p1d; + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = ( d0 >= 0 ) ? 0 : 1; + d0 = 1.0f - d0; + bits |= ( d1 >= 0 ) ? 0 : 1*2; + d1 = 1.0f - d1; + + bits |= ( d0 >= 0 ) ? 0: 1*4; + bits |= ( d1 >= 0 ) ? 0: 1*8; + + cullBits[i] = bits; + } +} + + +#endif /* DRAWVERT_PADDED */ + +#endif /* ENABLE_CULL */ + +#ifdef ENABLE_DERIVE +/* +============ +idSIMD_AltiVec::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_AltiVec::DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idPlane size + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + int i; + + vector float vecD0, vecD1, vecD2, vecD3, vecD4, vecD5, vecD6, vecD7; + vector float vecVertA, vecVertB, vecVertC; + vector float vecVertA2, vecVertB2, vecVertC2; + vector float vecVertA3, vecVertB3, vecVertC3; + vector float vecVertA4, vecVertB4, vecVertC4; + + vector float vecN, vecN2, vecN3, vecN4; + vector float vecWork1, vecWork2, vecWork3, vecWork4, vecWork5, vecWork6, vecWork7, vecWork8; + vector unsigned char vecPerm1 = (vector unsigned char)(4,5,6,7,8,9,10,11,0,1,2,3,12,13,14,15); + vector unsigned char vecPerm2 = (vector unsigned char)(8,9,10,11,0,1,2,3,4,5,6,7,12,13,14,15); + vector float vecF; + vector float vecF1, vecF2, vecF3, vecF4; + vector float zeroVector = (vector float)(0); + vector float vecNegOne = (vector float)(-1); + vector float vecSecondHalf, vecFirstHalf, vecSecondHalf2, vecFirstHalf2, vecSecondHalf3, vecFirstHalf3, vecFirstHalf4, vecSecondHalf4; + + vector unsigned char vecPermA, vecPermA2, vecPermA3, vecPermA4; + vector unsigned char vecPermB, vecPermB2, vecPermB3, vecPermB4; + vector unsigned char vecPermC, vecPermC2, vecPermC3, vecPermC4; + + vector unsigned char oneVector = (vector unsigned char)(1); + vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *xyzPtr = verts[0].xyz.ToFloatPtr(); + float *planePtr = planes[0].ToFloatPtr(); + + int j; + for ( j = 0, i = 0; i+11 < numIndexes; i += 12, j += 4 ) { + +#ifndef DRAWVERT_PADDED + // calculate permute vectors to load as needed. these are all + // triangle indexes and are usaully pretty close together but + // not guaranteed to be in any particular order + vecPermA = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ), oneVector ); +#endif + +#ifndef DRAWVERT_PADDED + // load first A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + + vecVertA = vec_perm( vecLd1, vecLd2, vecPermA ); + vecVertB = vec_perm( vecLd3, vecLd4, vecPermB ); + vecVertC = vec_perm( vecLd5, vecLd6, vecPermC ); + + // set the last element to 0 + vecVertA = vec_perm( vecVertA, zeroVector, vecPermZeroLast ); + vecVertB = vec_perm( vecVertB, zeroVector, vecPermZeroLast ); + vecVertC = vec_perm( vecVertC, zeroVector, vecPermZeroLast ); + + // load second A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + + vecVertA2 = vec_perm( vecLd1, vecLd2, vecPermA2 ); + vecVertB2 = vec_perm( vecLd3, vecLd4, vecPermB2 ); + vecVertC2 = vec_perm( vecLd5, vecLd6, vecPermC2 ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load third A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + + vecVertA3 = vec_perm( vecLd1, vecLd2, vecPermA3 ); + vecVertB3 = vec_perm( vecLd3, vecLd4, vecPermB3 ); + vecVertC3 = vec_perm( vecLd5, vecLd6, vecPermC3 ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load the fourth A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + + vecVertA4 = vec_perm( vecLd1, vecLd2, vecPermA4 ); + vecVertB4 = vec_perm( vecLd3, vecLd4, vecPermB4 ); + vecVertC4 = vec_perm( vecLd5, vecLd6, vecPermC4 ); + + // set the last element to 0 + vecVertA4 = vec_perm( vecVertA4, zeroVector, vecPermZeroLast ); + vecVertB4 = vec_perm( vecVertB4, zeroVector, vecPermZeroLast ); + vecVertC4 = vec_perm( vecVertC4, zeroVector, vecPermZeroLast ); +#else + // load first A B C + vecVertA = vec_ld( 0, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecVertB = vec_ld( 0, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecVertC = vec_ld( 0, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA = vec_perm( vecVertA, zeroVector, vecPermZeroLast ); + vecVertB = vec_perm( vecVertB, zeroVector, vecPermZeroLast ); + vecVertC = vec_perm( vecVertC, zeroVector, vecPermZeroLast ); + + // load second A B C + vecVertA2 = vec_ld( 0, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecVertB2 = vec_ld( 0, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecVertC2 = vec_ld( 0, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load third A B C + vecVertA3 = vec_ld( 0, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecVertB3 = vec_ld( 0, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecVertC3 = vec_ld( 0, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA3 = vec_perm( vecVertA3, zeroVector, vecPermZeroLast ); + vecVertB3 = vec_perm( vecVertB3, zeroVector, vecPermZeroLast ); + vecVertC3 = vec_perm( vecVertC3, zeroVector, vecPermZeroLast ); + + // load the fourth A B C + vecVertA4 = vec_ld( 0, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecVertB4 = vec_ld( 0, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecVertC4 = vec_ld( 0, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA4 = vec_perm( vecVertA4, zeroVector, vecPermZeroLast ); + vecVertB4 = vec_perm( vecVertB4, zeroVector, vecPermZeroLast ); + vecVertC4 = vec_perm( vecVertC4, zeroVector, vecPermZeroLast ); +#endif + // calculate d0 and d1 for each + vecD0 = vec_sub( vecVertB, vecVertA ); + vecD1 = vec_sub( vecVertC, vecVertA ); + + vecD2 = vec_sub( vecVertB2, vecVertA2 ); + vecD3 = vec_sub( vecVertC2, vecVertA2 ); + + vecD4 = vec_sub( vecVertB3, vecVertA3 ); + vecD5 = vec_sub( vecVertC3, vecVertA3 ); + + vecD6 = vec_sub( vecVertB4, vecVertA4 ); + vecD7 = vec_sub( vecVertC4, vecVertA4 ); + + vecWork1 = vec_perm( vecD0, vecD0, vecPerm1 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPerm2 ); + vecWork3 = vec_perm( vecD2, vecD2, vecPerm1 ); + vecWork4 = vec_perm( vecD3, vecD3, vecPerm2 ); + vecWork5 = vec_perm( vecD4, vecD4, vecPerm1 ); + vecWork6 = vec_perm( vecD5, vecD5, vecPerm2 ); + vecWork7 = vec_perm( vecD6, vecD6, vecPerm1 ); + vecWork8 = vec_perm( vecD7, vecD7, vecPerm2 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD1, vecD1, vecPerm1 ); + vecWork2 = vec_perm( vecD0, vecD0, vecPerm2 ); + vecWork3 = vec_perm( vecD3, vecD3, vecPerm1 ); + vecWork4 = vec_perm( vecD2, vecD2, vecPerm2 ); + vecWork5 = vec_perm( vecD5, vecD5, vecPerm1 ); + vecWork6 = vec_perm( vecD4, vecD4, vecPerm2 ); + vecWork7 = vec_perm( vecD7, vecD7, vecPerm1 ); + vecWork8 = vec_perm( vecD6, vecD6, vecPerm2 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecN = vec_madd( vecSecondHalf, vecNegOne, vecFirstHalf ); + vecN2 = vec_madd( vecSecondHalf2, vecNegOne, vecFirstHalf2 ); + vecN3 = vec_madd( vecSecondHalf3, vecNegOne, vecFirstHalf3 ); + vecN4 = vec_madd( vecSecondHalf4, vecNegOne, vecFirstHalf4 ); + + // transpose vecNs + vector float v0, v1, v2, v3; + v0 = vec_mergeh( vecN, vecN3 ); + v1 = vec_mergeh( vecN2, vecN4 ); + v2 = vec_mergel( vecN, vecN3 ); + v3 = vec_mergel( vecN2, vecN4 ); + + vecN = vec_mergeh( v0, v1 ); + vecN2 = vec_mergel( v0, v1 ); + vecN3 = vec_mergeh( v2, v3 ); + vecN4 = vec_mergel( v2, v3 ); + + vecF = vec_madd( vecN, vecN, zeroVector ); + vecF = vec_madd( vecN2, vecN2, vecF ); + vecF = vec_madd( vecN3, vecN3, vecF ); + + vecF = ReciprocalSquareRoot( vecF ); + + vecF1 = vec_madd( vecF, vecN, zeroVector ); + vecF2 = vec_madd( vecF, vecN2, zeroVector ); + vecF3 = vec_madd( vecF, vecN3, zeroVector ); + vecF4 = vec_madd( vecF, vecN4, zeroVector ); + + vector float v8, v9, v10, v11; + v8 = vecF1; + v9 = vecF2; + v10 = vecF3; + v11 = vecF4; + + // transpose vecVerts + v0 = vec_mergeh( vecVertA, vecVertA3 ); + v1 = vec_mergeh( vecVertA2, vecVertA4 ); + v2 = vec_mergel( vecVertA, vecVertA3 ); + v3 = vec_mergel( vecVertA2, vecVertA4 ); + + vecVertA = vec_mergeh( v0, v1 ); + vecVertA2 = vec_mergel( v0, v1 ); + vecVertA3 = vec_mergeh( v2, v3 ); + vecVertA4 = vec_mergel( v2, v3 ); + + vector float vecTotals; + vecTotals = vec_madd( vecVertA, v8, zeroVector ); + vecTotals = vec_madd( vecVertA2, v9, vecTotals ); + vecTotals = vec_madd( vecVertA3, v10, vecTotals ); + vecTotals = vec_madd( vecVertA4, v11, vecTotals ); + vecF = vec_madd( vecTotals, vecNegOne, zeroVector ); + + // transpose vecFs + v0 = vec_mergeh( vecF1, vecF3 ); + v1 = vec_mergeh( vecF2, vecF ); + v2 = vec_mergel( vecF1, vecF3 ); + v3 = vec_mergel( vecF2, vecF ); + + vecF1 = vec_mergeh( v0, v1 ); + vecF2 = vec_mergel( v0, v1 ); + vecF3 = vec_mergeh( v2, v3 ); + vecF4 = vec_mergel( v2, v3 ); + + // store results + UNALIGNED_STORE4( planePtr + ( j * PLANE_OFFSET ), vecF1, vecF2, vecF3, vecF4 ); + } + + // cleanup + for ( ; i < numIndexes; i += 3, j++ ) { + const idDrawVert *a, *b, *c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = FastScalarInvSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + //idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes[j].SetNormal( n ); + planes[j].FitThroughPoint( a->xyz ); + } +} + +/* +============ +idSIMD_AltiVec::DeriveTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from all triangles + using the vertex which results in smooth tangents across the mesh. + In the process the triangle planes are calculated as well. + +============ +*/ +void VPCALL idSIMD_AltiVec::DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + int i; + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + idPlane *planesPtr = planes; + for ( i = 0; i < numIndexes; i += 3 ) { + idDrawVert *a, *b, *c; + // unsigned long signBit; + float d0[5], d1[5], area; + idVec3 n, t0, t1; + float f1, f2, f3; + + int v0 = indexes[i + 0]; + int v1 = indexes[i + 1]; + int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + // normal + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f1 = n.x * n.x + n.y * n.y + n.z * n.z; + + // area sign bit + area = d0[3] * d1[4] - d0[4] * d1[3]; + + // first tangent + t0[0] = d0[0] * d1[4] - d0[4] * d1[0]; + t0[1] = d0[1] * d1[4] - d0[4] * d1[1]; + t0[2] = d0[2] * d1[4] - d0[4] * d1[2]; + + f2 = t0.x * t0.x + t0.y * t0.y + t0.z * t0.z; + + // second tangent + t1[0] = d0[3] * d1[0] - d0[0] * d1[3]; + t1[1] = d0[3] * d1[1] - d0[1] * d1[3]; + t1[2] = d0[3] * d1[2] - d0[2] * d1[3]; + + f3 = t1.x * t1.x + t1.y * t1.y + t1.z * t1.z; + + // Behold! The power of the pipeline + FastScalarInvSqrt_x3( &f1, &f2, &f3 ); +#ifdef PPC_INTRINSICS + f2 = __fsel( area, f2, -f2 ); + f3 = __fsel( area, f3, -f3 ); +#else + f2 = ( area < 0.0f ) ? -f2 : f2; + f3 = ( area < 0.0f ) ? -f3 : f3; +#endif + t0.x *= f2; + t0.y *= f2; + t0.z *= f2; + + n.x *= f1; + n.y *= f1; + n.z *= f1; + + planesPtr->SetNormal( n ); + planesPtr->FitThroughPoint( a->xyz ); + planesPtr++; + + t1.x *= f3; + t1.y *= f3; + t1.z *= f3; + + if ( used[v0] ) { + a->normal += n; + a->tangents[0] += t0; + a->tangents[1] += t1; + } else { + a->normal = n; + a->tangents[0] = t0; + a->tangents[1] = t1; + used[v0] = true; + } + + if ( used[v1] ) { + b->normal += n; + b->tangents[0] += t0; + b->tangents[1] += t1; + } else { + b->normal = n; + b->tangents[0] = t0; + b->tangents[1] = t1; + used[v1] = true; + } + + if ( used[v2] ) { + c->normal += n; + c->tangents[0] += t0; + c->tangents[1] += t1; + } else { + c->normal = n; + c->tangents[0] = t0; + c->tangents[1] = t1; + used[v2] = true; + } + } +} + + +#ifdef DERIVE_UNSMOOTH_DRAWVERT_ALIGNED + +/* +============ +idSIMD_AltiVec::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT +void VPCALL idSIMD_AltiVec::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + + int i; + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // drawverts aligned + assert( IS_16BYTE_ALIGNED( verts[0] ) ); + + vector float vecVertA, vecVertB, vecVertC; + vector float vecVertA2, vecVertB2, vecVertC2; + vector float vecVertA3, vecVertB3, vecVertC3; + vector float vecVertA4, vecVertB4, vecVertC4; + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8; + vector float vecS0, vecS1, vecS2; + vector float vecS0_2, vecS1_2, vecS2_2; + vector float vecS0_3, vecS1_3, vecS2_3; + vector float vecS0_4, vecS1_4, vecS2_4; + + vector float vecD1, vecD2, vecD3, vecD4, vecD5, vecD6; + vector float vecD7, vecD8, vecD9, vecD10, vecD11, vecD12; + vector float vecT1, vecT1_2, vecT1_3, vecT1_4, vecT2, vecT2_2, vecT2_3, vecT2_4; + vector float vecWork1, vecWork2, vecWork3, vecWork4, vecWork5, vecWork6, vecWork7, vecWork8; + vector float vecN, vecN2, vecN3, vecN4; + + vector unsigned char vecPermN0 = (vector unsigned char)(8,9,10,11,0,1,2,3,4,5,6,7,12,13,14,15); + vector unsigned char vecPermN1 = (vector unsigned char)(4,5,6,7,8,9,10,11,0,1,2,3,12,13,14,15); + vector unsigned char vecPermT0 = (vector unsigned char)(0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3); + vector unsigned char vecPermT1 = (vector unsigned char)(8,9,10,11,8,9,10,11,8,9,10,11,8,9,10,11); + vector float zeroVector = (vector float)(0); + + vector float vecNegOne = (vector float)(-1.0); + + vector float vecStore1, vecStore2, vecStore3; + vector unsigned char vecPermFirstThreeLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermStoreSecond = (vector unsigned char)(4,5,6,7,8,9,10,11,16,17,18,19,20,21,22,23); + vector unsigned char vecPermLeadAndThree = (vector unsigned char)(0,1,2,3,16,17,18,19,20,21,22,23,24,25,26,27); + vector unsigned char vecPermStore2 = (vector unsigned char)(4,5,6,7,8,9,10,11,24,25,26,27,28,29,30,31); + vector unsigned char vecPermStore3 = (vector unsigned char)(4,5,6,7,8,9,10,11,16,17,18,19,20,21,22,23); + vector unsigned char vecPermStore4 = (vector unsigned char)(8,9,10,11,16,17,18,19,20,21,22,23,24,25,26,27); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + + vector float vecLd1, vecLd2, vecLd3; + vector unsigned char vecPerm0, vecPerm1, vecPerm2, vecPerm3, vecPerm4; + + float *normalPtr = verts[0].normal.ToFloatPtr(); + float *xyzPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecFirstHalf, vecSecondHalf; + vector float vecFirstHalf2, vecSecondHalf2; + vector float vecFirstHalf3, vecSecondHalf3; + vector float vecFirstHalf4, vecSecondHalf4; + + for ( i = 0; i+3 < numVerts; i+=4 ) { + int bOffset1, bOffset2, bOffset3, bOffset4; + int cOffset1, cOffset2, cOffset3, cOffset4; + + bOffset1 = dominantTris[i].v2; + cOffset1 = dominantTris[i].v3; + bOffset2 = dominantTris[i+1].v2; + cOffset2 = dominantTris[i+1].v3; + bOffset3 = dominantTris[i+2].v2; + cOffset3 = dominantTris[i+2].v3; + bOffset4 = dominantTris[i+3].v2; + cOffset4 = dominantTris[i+3].v3; + + vecPerm0 = vec_lvsl( 0, xyzPtr + ( i * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + (i * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + (i * DRAWVERT_OFFSET ) ); + vecVertA = vec_perm( v0, v1, vecPerm0 ); + + vecPerm1 = vec_lvsl( 0, xyzPtr + (bOffset1 * DRAWVERT_OFFSET ) ); + v2 = vec_ld( 0, xyzPtr + ( bOffset1 * DRAWVERT_OFFSET ) ); + v3 = vec_ld( 16, xyzPtr + ( bOffset1 * DRAWVERT_OFFSET ) ); + vecVertB = vec_perm( v2, v3, vecPerm1 ); + + vecPerm2 = vec_lvsl( 0, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 0, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 16, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + vecVertC = vec_perm( v4, v5, vecPerm2 ); + + // put remainder into v2 + v1 = vec_perm( v1, v1, vecPerm0 ); + v3 = vec_perm( v3, v3, vecPerm1 ); + v5 = vec_perm( v5, v5, vecPerm2 ); + + v1 = vec_mergeh( v1, v5 ); + v2 = vec_mergeh( v3, zeroVector ); + v2 = vec_mergeh( v1, v2 ); + v2 = vec_perm( v2, v2, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load second one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + vecVertA2 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm3 = vec_lvsl( 0, xyzPtr + (bOffset2 * DRAWVERT_OFFSET ) ); + v3 = vec_ld( 0, xyzPtr + ( bOffset2 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 16, xyzPtr + ( bOffset2 * DRAWVERT_OFFSET ) ); + vecVertB2 = vec_perm( v3, v4, vecPerm3 ); + + vecPerm4 = vec_lvsl( 0, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 0, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 16, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + vecVertC2 = vec_perm( v5, v6, vecPerm4 ); + + // put remainder into v3 + v1 = vec_perm( v1, v1, vecPerm0 ); + v4 = vec_perm( v4, v4, vecPerm3 ); + v5 = vec_perm( v6, v6, vecPerm4 ); + + v1 = vec_mergeh( v1, v5 ); + v3 = vec_mergeh( v4, zeroVector ); + v3 = vec_mergeh( v1, v3 ); + v3 = vec_perm( v3, v3, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load third one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + vecVertA3 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm1 = vec_lvsl( 0, xyzPtr + (bOffset3 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 0, xyzPtr + ( bOffset3 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 16, xyzPtr + ( bOffset3 * DRAWVERT_OFFSET ) ); + vecVertB3 = vec_perm( v4, v5, vecPerm1 ); + + vecPerm2 = vec_lvsl( 0, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 0, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + v7 = vec_ld( 16, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + vecVertC3 = vec_perm( v6, v7, vecPerm2 ); + + // put remainder into v4 + v1 = vec_perm( v1, v1, vecPerm0 ); + v5 = vec_perm( v5, v5, vecPerm1 ); + v7 = vec_perm( v7, v7, vecPerm2 ); + + v1 = vec_mergeh( v1, v7 ); + v4 = vec_mergeh( v5, zeroVector ); + v4 = vec_mergeh( v1, v4 ); + v4 = vec_perm( v4, v4, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load fourth one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + vecVertA4 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm3 = vec_lvsl( 0, xyzPtr + (bOffset4 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 0, xyzPtr + ( bOffset4 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 16, xyzPtr + ( bOffset4 * DRAWVERT_OFFSET ) ); + vecVertB4 = vec_perm( v5, v6, vecPerm3 ); + + vecPerm4 = vec_lvsl( 0, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + v7 = vec_ld( 0, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + v8 = vec_ld( 16, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + vecVertC4 = vec_perm( v7, v8, vecPerm4 ); + + // put remainder into v5 + v1 = vec_perm( v1, v1, vecPerm0 ); + v6 = vec_perm( v6, v6, vecPerm3 ); + v8 = vec_perm( v8, v8, vecPerm4 ); + + v1 = vec_mergeh( v1, v8 ); + v5 = vec_mergeh( v6, zeroVector ); + v5 = vec_mergeh( v1, v5 ); + v5 = vec_perm( v5, v5, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // remainder vectors look like b->st[1], a->st[1], c->st[1], a->st[1] + + //vecD1 now holds d0, d1, d2, d3 + vecD1 = vec_sub( vecVertB, vecVertA ); + vecD4 = vec_sub( vecVertB2, vecVertA2 ); + vecD7 = vec_sub( vecVertB3, vecVertA3 ); + vecD10 = vec_sub( vecVertB4, vecVertA4 ); + + // vecD2 how holds d5, d6, d7, d8 + vecD2 = vec_sub( vecVertC, vecVertA ); + vecD5 = vec_sub( vecVertC2, vecVertA2 ); + vecD8 = vec_sub( vecVertC3, vecVertA3 ); + vecD11 = vec_sub( vecVertC4, vecVertA4 ); + + // vecD3 now holds d4, crap, d9, crap + vecD3 = vec_sub( v2, vec_sld( v2, v2, 4 ) ); + vecD6 = vec_sub( v3, vec_sld( v3, v3, 4 ) ); + vecD9 = vec_sub( v4, vec_sld( v4, v4, 4 ) ); + vecD12 = vec_sub( v5, vec_sld( v5, v5, 4 ) ); + + // get permute vectors for loading from dt + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+1].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm3 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+2].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm4 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+3].normalizationScale[0] ), (vector unsigned char)(1) ); + + // load S values from dominantTris + v0 = vec_ld( 0, &dominantTris[i].normalizationScale[0] ); + v1 = vec_ld( 11, &dominantTris[i].normalizationScale[0] ); + v2 = vec_ld( 0, &dominantTris[i+1].normalizationScale[0] ); + v3 = vec_ld( 11, &dominantTris[i+1].normalizationScale[0] ); + v4 = vec_ld( 0, &dominantTris[i+2].normalizationScale[0] ); + v5 = vec_ld( 11, &dominantTris[i+2].normalizationScale[0] ); + v6 = vec_ld( 0, &dominantTris[i+3].normalizationScale[0] ); + v7 = vec_ld( 11, &dominantTris[i+3].normalizationScale[0] ); + + v0 = vec_perm( v0, v1, vecPerm1 ); + v2 = vec_perm( v2, v3, vecPerm2 ); + v4 = vec_perm( v4, v5, vecPerm3 ); + v6 = vec_perm( v6, v7, vecPerm4 ); + + vecS0 = vec_splat( v0, 0 ); + vecS1 = vec_splat( v0, 1 ); + vecS2 = vec_splat( v0, 2 ); + + vecS0_2 = vec_splat( v2, 0); + vecS1_2 = vec_splat( v2, 1 ); + vecS2_2 = vec_splat( v2, 2 ); + + vecS0_3 = vec_splat( v4, 0 ); + vecS1_3 = vec_splat( v4, 1 ); + vecS2_3 = vec_splat( v4, 2 ); + + vecS0_4 = vec_splat( v6, 0 ); + vecS1_4 = vec_splat( v6, 1 ); + vecS2_4 = vec_splat( v6, 2 ); + + // do calculation + vecWork1 = vec_perm( vecD2, vecD2, vecPermN1 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPermN0 ); + vecWork3 = vec_perm( vecD5, vecD5, vecPermN1 ); + vecWork4 = vec_perm( vecD4, vecD4, vecPermN0 ); + vecWork5 = vec_perm( vecD8, vecD8, vecPermN1 ); + vecWork6 = vec_perm( vecD7, vecD7, vecPermN0 ); + vecWork7 = vec_perm( vecD11, vecD11, vecPermN1 ); + vecWork8 = vec_perm( vecD10, vecD10, vecPermN0 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD2, vecD2, vecPermN0 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPermN1 ); + vecWork3 = vec_perm( vecD5, vecD5, vecPermN0 ); + vecWork4 = vec_perm( vecD4, vecD4, vecPermN1 ); + vecWork5 = vec_perm( vecD8, vecD8, vecPermN0 ); + vecWork6 = vec_perm( vecD7, vecD7, vecPermN1 ); + vecWork7 = vec_perm( vecD11, vecD11, vecPermN0 ); + vecWork8 = vec_perm( vecD10, vecD10, vecPermN1 ); + + vecSecondHalf = vec_nmsub( vecWork1, vecWork2, vecFirstHalf ); + vecSecondHalf2 = vec_nmsub( vecWork3, vecWork4, vecFirstHalf2 ); + vecSecondHalf3 = vec_nmsub( vecWork5, vecWork6, vecFirstHalf3 ); + vecSecondHalf4 = vec_nmsub( vecWork7, vecWork8, vecFirstHalf4 ); + + + // calculate N values + vecN = vec_madd( vecS2, vecSecondHalf, zeroVector ); + vecN2 = vec_madd( vecS2_2, vecSecondHalf2, zeroVector ); + vecN3 = vec_madd( vecS2_3, vecSecondHalf3, zeroVector ); + vecN4 = vec_madd( vecS2_4, vecSecondHalf4, zeroVector ); + + // calculate both halves of the calculation for t + vecWork1 = vecD1; + vecWork2 = vec_perm( vecD3, vecD3, vecPermT1 ); + vecWork3 = vecD4; + vecWork4 = vec_perm( vecD6, vecD6, vecPermT1 ); + vecWork5 = vecD7; + vecWork6 = vec_perm( vecD9, vecD9, vecPermT1 ); + vecWork7 = vecD10; + vecWork8 = vec_perm( vecD12, vecD12, vecPermT1 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vecD2; + vecWork2 = vec_perm( vecD3, vecD3, vecPermT0 ); + vecWork3 = vecD5; + vecWork4 = vec_perm( vecD6, vecD6, vecPermT0 ); + vecWork5 = vecD8; + vecWork6 = vec_perm( vecD9, vecD9, vecPermT0 ); + vecWork7 = vecD11; + vecWork8 = vec_perm( vecD12, vecD12, vecPermT0 ); + + vecSecondHalf = vec_nmsub( vecWork1, vecWork2, vecFirstHalf ); + vecSecondHalf2 = vec_nmsub( vecWork3, vecWork4, vecFirstHalf2 ); + vecSecondHalf3 = vec_nmsub( vecWork5, vecWork6, vecFirstHalf3 ); + vecSecondHalf4 = vec_nmsub( vecWork7, vecWork8, vecFirstHalf4 ); + + // calculate T values + vecT1 = vec_madd( vecS0, vecSecondHalf, zeroVector ); + vecT1_2 = vec_madd( vecS0_2, vecSecondHalf2, zeroVector ); + vecT1_3 = vec_madd( vecS0_3, vecSecondHalf3, zeroVector ); + vecT1_4 = vec_madd( vecS0_4, vecSecondHalf4, zeroVector ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + vecWork1 = vecD1; + vecWork2 = vec_perm( vecD2, vecD2, vecPermT2 ); + vecWork3 = vecD4; + vecWork4 = vec_perm( vecD5, vecD5, vecPermT2 ); + vecWork5 = vecD7; + vecWork6 = vec_perm( vecD8, vecD8, vecPermT2 ); + vecWork7 = vecD10; + vecWork8 = vec_perm( vecD11, vecD11, vecPermT2 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD1, vecD1, vecPermT2 ); + vecWork2 = vecD2; + vecWork3 = vec_perm( vecD4, vecD4, vecPermT2 ); + vecWork4 = vecD5; + vecWork5 = vec_perm( vecD7, vecD7, vecPermT2 ); + vecWork6 = vecD8; + vecWork7 = vec_perm( vecD10, vecD10, vecPermT2 ); + vecWork8 = vecD11; + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + +#else + vecWork1 = vec_perm( vecN, vecN, vecPermN1 ); + vecWork2 = vec_perm( vecT1, vecT1, vecPermN0 ); + vecWork3 = vec_perm( vecN2, vecN2, vecPermN1 ); + vecWork4 = vec_perm( vecT1_2, vecT1_2, vecPermN0 ); + vecWork5 = vec_perm( vecN3, vecN3, vecPermN1 ); + vecWork6 = vec_perm( vecT1_3, vecT1_3, vecPermN0 ); + vecWork7 = vec_perm( vecN4, vecN4, vecPermN1 ); + vecWork8 = vec_perm( vecT1_4, vecT1_4, vecPermN0 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecN, vecN, vecPermN0 ); + vecWork2 = vec_perm( vecT1, vecT1, vecPermN1 ); + vecWork3 = vec_perm( vecN2, vecN2, vecPermN0 ); + vecWork4 = vec_perm( vecT1_2, vecT1_2, vecPermN1 ); + vecWork5 = vec_perm( vecN3, vecN3, vecPermN0 ); + vecWork6 = vec_perm( vecT1_3, vecT1_3, vecPermN1 ); + vecWork7 = vec_perm( vecN4, vecN4, vecPermN0 ); + vecWork8 = vec_perm( vecT1_4, vecT1_4, vecPermN1 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); +#endif + // finish the calculation + vecSecondHalf = vec_madd( vecSecondHalf, vecNegOne, vecFirstHalf ); + vecSecondHalf2 = vec_madd( vecSecondHalf2, vecNegOne, vecFirstHalf2 ); + vecSecondHalf3 = vec_madd( vecSecondHalf3, vecNegOne, vecFirstHalf3 ); + vecSecondHalf4 = vec_madd( vecSecondHalf4, vecNegOne, vecFirstHalf4 ); + + vecT2 = vec_madd( vecS1, vecSecondHalf, zeroVector ); + vecT2_2 = vec_madd( vecS1_2, vecSecondHalf2, zeroVector ); + vecT2_3 = vec_madd( vecS1_3, vecSecondHalf3, zeroVector ); + vecT2_4 = vec_madd( vecS1_4, vecSecondHalf4, zeroVector ); + + // Store results + + // read values that we need to preserve + vecLd1 = vec_ld( 0, normalPtr + ( i * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 32, normalPtr + ( i * DRAWVERT_OFFSET ) ); + + //generate vectors to store + vecStore1 = vec_perm( vecLd1, vecN, vecPermLeadAndThree ); + vecStore2 = vec_perm( vecT1, vecT2, vecPermFirstThreeLast ); + vecStore3 = vec_perm( vecT2, vecLd2, vecPermStore2 ); + + // store out results + ALIGNED_STORE3( normalPtr + ( i * DRAWVERT_OFFSET ), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd3 = vec_ld( 32, normalPtr + ( (i+1) * DRAWVERT_OFFSET )); + + // generate vectors to store + vecStore1 = vec_perm( vecN2, vecT1_2, vecPermFirstThreeLast ); + vecStore2 = vec_perm( vecT1_2, vecT2_2, vecPermStoreSecond ); + vecStore3 = vec_perm( vecT2_2, vecLd3, (vector unsigned char)(8,9,10,11,20,21,22,23,24,25,26,27,28,29,30,31) ); + + // instead of doing permute, shift it where it needs to be and use vec_ste + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+1) * DRAWVERT_OFFSET), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd1 = vec_ld( 0, normalPtr + ( (i+2) * DRAWVERT_OFFSET ) ); + + // generate vectors to store + vecStore1 = vec_perm( vecLd1, vecN3, vecPermFirstThreeLast ); + vecStore2 = vec_perm( vecN3, vecT1_3, vecPermStore3 ); + vecStore3 = vec_perm( vecT1_3, vecT2_3, vecPermStore4 ); + + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+2) * DRAWVERT_OFFSET), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd2 = vec_ld( 0, normalPtr + ((i+3) * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 32, normalPtr + ((i+3) * DRAWVERT_OFFSET ) ); + + // generate vectors to store + vecStore1 = vec_perm( vecLd2, vecN4, vecPermHalves ); + vecStore2 = vec_perm( vecN4, vecT1_4, vecPermStore4 ); + vecStore3 = vec_perm( vecT2_4, vecLd3, vecPermFirstThreeLast ); + + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+3) * DRAWVERT_OFFSET ), vecStore1, vecStore2, vecStore3 ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +#else +/* +============ +idSIMD_AltiVec::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_AltiVec::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + int i; + + for ( i = 0; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } + +} +#endif /* DERIVE_UNSMOOTH_DRAWVERT_ALIGNED */ + +/* +============ +idSIMD_AltiVec::NormalizeTangents + + Normalizes each vertex normal and projects and normalizes the + tangent vectors onto the plane orthogonal to the vertex normal. +============ +*/ +void VPCALL idSIMD_AltiVec::NormalizeTangents( idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + float *addr = verts[0].normal.ToFloatPtr(); + float *tAddr = verts[0].tangents[0].ToFloatPtr(); + + // v0 through v3 maintain originally loaded values so we don't take + // as much hit for unaligned stores + vector float v0, v1, v2, v3; + // v5 through v8 are the "working" values of the vectors + vector float v5, v6, v7, v8; + // working values + vector float vec1T0, vec1T1, vec2T0, vec2T1, vec3T0, vec3T1, vec4T0, vec4T1; + vector float vecSum, vecTSum1, vecTSum2, tempSum, tempSum2, tempSum3; + vector float vecF, vecF2; + vector float vecTemp, vecTemp2, vecTemp3, vecTemp4; + + register vector float zeroVector = (vector float)(0.0); + + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermSplatFirstWithZero = (vector unsigned char)(0,1,2,3,0,1,2,3,0,1,2,3,16,17,18,19); + vector unsigned char vecPerm0, vecPerm1, vecPerm2, vecPerm3; + vector unsigned char storePerm0, storePerm1, storePerm2, storePerm3; + + vector float vecTan11, vecTan12, vecTan13, vecTan21, vecTan22, vecTan23; + vector float vecTan31, vecTan32, vecTan33, vecTan41, vecTan42, vecTan43; + + vector unsigned char vec1T0Perm, vec1T1Perm, vec2T0Perm, vec2T1Perm, vec3T0Perm, vec3T1Perm, vec4T0Perm, vec4T1Perm; + vector unsigned char storeT11, storeT12, storeT21, storeT22, storeT31, storeT32; + vector unsigned char storeT41, storeT42; + + int i = 0; + + if ( i+3 < numVerts ) { + // for loading normal from idDrawVert + vecPerm0 = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vecPerm1 = vec_add( vec_lvsl( -1, addr + ( 1 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, addr + ( 2 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vecPerm3 = vec_add( vec_lvsl( -1, addr + ( 3 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + + // for loading tangents from idDrawVert + vec1T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 0 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec1T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 0 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec2T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 1 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec2T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 1 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec3T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 2 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec3T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 2 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec4T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 3 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec4T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 3 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + + // generate permute vectors to store normals + storePerm0 = vec_lvsr( 0, addr ); + storePerm1 = vec_lvsr( 0, addr + ( 1 * DRAWVERT_OFFSET ) ); + storePerm2 = vec_lvsr( 0, addr + ( 2 * DRAWVERT_OFFSET ) ); + storePerm3 = vec_lvsr( 0, addr + ( 3 * DRAWVERT_OFFSET ) ); + + // generate permute vectors to store tangents + storeT11 = vec_lvsr( 0, tAddr + ( 0 * DRAWVERT_OFFSET ) ); + storeT12 = vec_lvsr( 12, tAddr + ( 0 * DRAWVERT_OFFSET ) ); + + storeT21 = vec_lvsr( 0, tAddr + ( 1 * DRAWVERT_OFFSET ) ); + storeT22 = vec_lvsr( 12, tAddr + ( 1 * DRAWVERT_OFFSET ) ); + + storeT31 = vec_lvsr( 0, tAddr + ( 2 * DRAWVERT_OFFSET ) ); + storeT32 = vec_lvsr( 12, tAddr + ( 2 * DRAWVERT_OFFSET ) ); + + storeT41 = vec_lvsr( 0, tAddr + ( 3 * DRAWVERT_OFFSET ) ); + storeT42 = vec_lvsr( 12, tAddr + ( 3 * DRAWVERT_OFFSET ) ); + } + + for ( ; i+3 < numVerts; i+=4 ) { + + // load normals + vector float vecNormal11 = vec_ld( 0, addr + ( i * DRAWVERT_OFFSET ) ); + vector float vecNormal12 = vec_ld( 15, addr + ( i * DRAWVERT_OFFSET ) ); + v0 = vec_perm( vecNormal11, vecNormal12, vecPerm0 ); + + vector float vecNormal21 = vec_ld( 0, addr + ((i+1) * DRAWVERT_OFFSET ) ); + vector float vecNormal22 = vec_ld( 15, addr + ((i+1) * DRAWVERT_OFFSET ) ); + v1 = vec_perm( vecNormal21, vecNormal22, vecPerm1 ); + + vector float vecNormal31 = vec_ld( 0, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vector float vecNormal32 = vec_ld( 15, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + v2 = vec_perm( vecNormal31, vecNormal32, vecPerm2 ); + + vector float vecNormal41 = vec_ld( 0, addr + ((i+3) * DRAWVERT_OFFSET ) ); + vector float vecNormal42 = vec_ld( 15, addr + ((i+3) * DRAWVERT_OFFSET ) ); + v3 = vec_perm( vecNormal41, vecNormal42, vecPerm3 ); + + // zero out the last element of each useless vector + v0 = vec_perm( v0, zeroVector, vecPermLast ); + v1 = vec_perm( v1, zeroVector, vecPermLast ); + v2 = vec_perm( v2, zeroVector, vecPermLast ); + v3 = vec_perm( v3, zeroVector, vecPermLast ); + + // got 4 vectors in v0 through v3, sum them each accross + // and put into one vector + vecTemp = vec_madd( v0, v0, zeroVector ); + + vecSum = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecSum = vec_add( vecSum, vec_sld( vecSum, vecSum, 4 ) ); + // element 0 of vecSum now has sum of v0 + + vecTemp2 = vec_madd( v1, v1, zeroVector ); + tempSum = vec_add( vecTemp2, vec_sld( vecTemp2, vecTemp2, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_mergeh( vecSum, tempSum ); + + vecTemp3 = vec_madd( v2, v2, zeroVector ); + tempSum = vec_add( vecTemp3, vec_sld( vecTemp3, vecTemp3, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_perm( vecSum, tempSum, vecPermHalves ); + + vecTemp4 = vec_madd( v3, v3, zeroVector ); + tempSum = vec_add( vecTemp4, vec_sld( vecTemp4, vecTemp4, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_perm( vecSum, tempSum, vecPermLast ); + + // take reciprocal square roots of these + vecF = ReciprocalSquareRoot( vecSum ); + + // multiply each vector by f + v5 = vec_madd( v0, vec_splat( vecF, 0 ), zeroVector ); + v6 = vec_madd( v1, vec_splat( vecF, 1 ), zeroVector ); + v7 = vec_madd( v2, vec_splat( vecF, 2 ), zeroVector ); + v8 = vec_madd( v3, vec_splat( vecF, 3 ), zeroVector ); + + // load tangents as unaligned + vecTan11 = vec_ld( 0, tAddr + ( i * DRAWVERT_OFFSET ) ); + vecTan12 = vec_ld( 11, tAddr + ( i * DRAWVERT_OFFSET ) ); + vecTan13 = vec_ld( 23, tAddr + ( i * DRAWVERT_OFFSET ) ); + + vecTan21 = vec_ld( 0, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + vecTan22 = vec_ld( 11, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + vecTan23 = vec_ld( 23, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + + vecTan31 = vec_ld( 0, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + vecTan32 = vec_ld( 11, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + vecTan33 = vec_ld( 23, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + + vecTan41 = vec_ld( 0, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + vecTan42 = vec_ld( 11, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + vecTan43 = vec_ld( 23, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + + vec1T0 = vec_perm( vecTan11, vecTan12, vec1T0Perm ); + vec1T1 = vec_perm( vecTan12, vecTan13, vec1T1Perm ); + vec2T0 = vec_perm( vecTan21, vecTan22, vec2T0Perm ); + vec2T1 = vec_perm( vecTan22, vecTan23, vec2T1Perm ); + vec3T0 = vec_perm( vecTan31, vecTan32, vec3T0Perm ); + vec3T1 = vec_perm( vecTan32, vecTan33, vec3T1Perm ); + vec4T0 = vec_perm( vecTan41, vecTan42, vec4T0Perm ); + vec4T1 = vec_perm( vecTan42, vecTan43, vec4T1Perm ); + + //zero out last element of tangents + vec1T0 = vec_perm( vec1T0, zeroVector, vecPermLast ); + vec1T1 = vec_perm( vec1T1, zeroVector, vecPermLast ); + vec2T0 = vec_perm( vec2T0, zeroVector, vecPermLast ); + vec2T1 = vec_perm( vec2T1, zeroVector, vecPermLast ); + vec3T0 = vec_perm( vec3T0, zeroVector, vecPermLast ); + vec3T1 = vec_perm( vec3T1, zeroVector, vecPermLast ); + vec4T0 = vec_perm( vec4T0, zeroVector, vecPermLast ); + vec4T1 = vec_perm( vec4T1, zeroVector, vecPermLast ); + + // all tangents[0] + tempSum = zeroVector; + tempSum = vec_madd( vec1T0, v5, tempSum ); + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + // put tempSum splatted accross vecTSum1 + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v5, zeroVector ); + + //vec1T0 now contains what needs to be rsqrt'd and multiplied by f + vec1T0 = vec_sub( vec1T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec2T0, v6, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v6, zeroVector ); + vec2T0 = vec_sub( vec2T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec3T0, v7, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v7, zeroVector ); + vec3T0 = vec_sub( vec3T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec4T0, v8, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v8, zeroVector ); + vec4T0 = vec_sub( vec4T0, vecTSum1 ); + + // all tangents[1] + tempSum = zeroVector; + tempSum = vec_madd( vec1T1, v5, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v5, zeroVector ); + + //vec1T0 now contains what needs to be rsqrt'd and multiplied by f + vec1T1 = vec_sub( vec1T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec2T1, v6, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v6, zeroVector ); + vec2T1 = vec_sub( vec2T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec3T1, v7, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v7, zeroVector ); + vec3T1 = vec_sub( vec3T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec4T1, v8, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v8, zeroVector ); + vec4T1 = vec_sub( vec4T1, vecTSum1 ); + + + // sum accross vectors and put into one vector + vecTemp = vec_madd( vec1T0, vec1T0, zeroVector ); + vecTSum1 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + + // element 0 of vecSum now has sum of v0 + vecTemp = vec_madd( vec2T0, vec2T0, zeroVector ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTemp = vec_madd( vec3T0, vec3T0, zeroVector ); + vecTSum1 = vec_mergeh( vecTSum1, tempSum2 ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTSum1 = vec_perm( vecTSum1, tempSum2, vecPermHalves ); + vecTemp = vec_madd( vec4T0, vec4T0, zeroVector ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTSum1 = vec_perm( vecTSum1, tempSum2, vecPermLast ); + + vecTemp = vec_madd( vec1T1, vec1T1, zeroVector ); + vecTSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecTSum2 = vec_add( vecTSum2, vec_sld( vecTSum2, vecTSum2, 4 ) ); + // element 0 of vecSum now has sum of v0 + vecTemp = vec_madd( vec2T1, vec2T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_mergeh( vecTSum2, tempSum3 ); + vecTemp = vec_madd( vec3T1, vec3T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_perm( vecTSum2, tempSum3, vecPermHalves ); + vecTemp = vec_madd( vec4T1, vec4T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_perm( vecTSum2, tempSum3, vecPermLast ); + + // tangents[0] + vecF = ReciprocalSquareRoot( vecTSum1 ); + // tangents[1] + vecF2 = ReciprocalSquareRoot( vecTSum2 ); + + // multiply each tangent vector by f + + vec1T0 = vec_madd( vec1T0, vec_splat( vecF, 0 ), zeroVector ); + vec2T0 = vec_madd( vec2T0, vec_splat( vecF, 1 ), zeroVector ); + vec3T0 = vec_madd( vec3T0, vec_splat( vecF, 2 ), zeroVector ); + vec4T0 = vec_madd( vec4T0, vec_splat( vecF, 3 ), zeroVector ); + + vec1T1 = vec_madd( vec1T1, vec_splat( vecF2, 0 ), zeroVector ); + vec2T1 = vec_madd( vec2T1, vec_splat( vecF2, 1 ), zeroVector ); + vec3T1 = vec_madd( vec3T1, vec_splat( vecF2, 2 ), zeroVector ); + vec4T1 = vec_madd( vec4T1, vec_splat( vecF2, 3 ), zeroVector ); + + // rotate input data + v5 = vec_perm( v5, v5, storePerm0 ); + v6 = vec_perm( v6, v6, storePerm1 ); + v7 = vec_perm( v7, v7, storePerm2 ); + v8 = vec_perm( v8, v8, storePerm3 ); + + vec_ste( v5, 0, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + vec_ste( v5, 4, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + vec_ste( v5, 8, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + + vec_ste( v6, 0, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + vec_ste( v6, 4, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + vec_ste( v6, 8, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + + vec_ste( v7, 0, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vec_ste( v7, 4, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vec_ste( v7, 8, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + + vec_ste( v8, 0, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + vec_ste( v8, 4, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + vec_ste( v8, 8, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + + // store tangents[0] and tangents[1] + vec1T0 = vec_perm( vec1T0, vec1T0, storeT11 ); + vec1T1 = vec_perm( vec1T1, vec1T1, storeT12 ); + + vec_ste( vec1T0, 0, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T0, 4, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T0, 8, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 12, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 16, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 20, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + + // store second tangents[0] and tangents[1] + vec2T0 = vec_perm( vec2T0, vec2T0, storeT21 ); + vec2T1 = vec_perm( vec2T1, vec2T1, storeT22 ); + + vec_ste( vec2T0, 0, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T0, 4, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T0, 8, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 12, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 16, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 20, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + + // store third tangents[0] and tangents[1] + vec3T0 = vec_perm( vec3T0, vec3T0, storeT31 ); + vec3T1 = vec_perm( vec3T1, vec3T1, storeT32 ); + + vec_ste( vec3T0, 0, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T0, 4, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T0, 8, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 12, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 16, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 20, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + + // store fourth tangents[0] and tangents[1] + vec4T0 = vec_perm( vec4T0, vec4T0, storeT41 ); + vec4T1 = vec_perm( vec4T1, vec4T1, storeT42 ); + + vec_ste( vec4T0, 0, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T0, 4, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T0, 8, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 12, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 16, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 20, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + idVec3 &v = verts[i].normal; + float f; + + //f = idMath::RSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + f = FastScalarInvSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + v.x *= f; v.y *= f; v.z *= f; + + for ( int j = 0; j < 2; j++ ) { + idVec3 &t = verts[i].tangents[j]; + + t -= ( t * v ) * v; + // f = idMath::RSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + f = FastScalarInvSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + t.x *= f; t.y *= f; t.z *= f; + } + } +} +#endif /* ENABLE_DERIVE */ + +#ifdef ENABLE_CREATE + +/* +============ +idSIMD_AltiVec::CreateTextureSpaceLightVectors + + Calculates light vectors in texture space for the given triangle vertices. + For each vertex the direction towards the light origin is projected onto texture space. + The light vectors are only calculated for the vertices referenced by the indexes. +============ +*/ + +void VPCALL idSIMD_AltiVec::CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + int i; + for ( i = 0; i+7 < numIndexes; i+= 8 ) { + used[indexes[i]] = true; + used[indexes[i+1]] = true; + used[indexes[i+2]] = true; + used[indexes[i+3]] = true; + used[indexes[i+4]] = true; + used[indexes[i+5]] = true; + used[indexes[i+6]] = true; + used[indexes[i+7]] = true; + } + + for ( ; i < numIndexes; i++ ) { + used[indexes[i]] = true; + } + + for ( i = 0; i+1 < numVerts; i+=2 ) { + + const idDrawVert *v = &verts[i]; + const idDrawVert *v2 = &verts[i+1]; + + float x, y, z; + float x2, y2, z2; + idVec3 lightDir, lightDir2; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightDir2[0] = lightOrigin[0] - v2->xyz[0]; + lightDir2[1] = lightOrigin[1] - v2->xyz[1]; + lightDir2[2] = lightOrigin[2] - v2->xyz[2]; + + x = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + y = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + z = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + + x2 = lightDir2[0] * v2->tangents[0][0] + lightDir2[1] * v2->tangents[0][1] + lightDir2[2] * v2->tangents[0][2]; + y2 = lightDir2[0] * v2->tangents[1][0] + lightDir2[1] * v2->tangents[1][1] + lightDir2[2] * v2->tangents[1][2]; + z2 = lightDir2[0] * v2->normal[0] + lightDir2[1] * v2->normal[1] + lightDir2[2] * v2->normal[2]; + + if ( used[i] ) { + lightVectors[i][0] = x; + lightVectors[i][1] = y; + lightVectors[i][2] = z; + } + + if ( used[i+1] ) { + lightVectors[i+1][0] = x2; + lightVectors[i+1][1] = y2; + lightVectors[i+1][2] = z2; + } + } + + // cleanup + for ( ; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + idVec3 lightDir; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightVectors[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + lightVectors[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + lightVectors[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + } +} + +#if 1 +/* +============ +idSIMD_AltiVec::CreateSpecularTextureCoords + + Calculates specular texture coordinates for the given triangle vertices. + For each vertex the normalized direction towards the light origin is added to the + normalized direction towards the view origin and the result is projected onto texture space. + The texture coordinates are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_AltiVec::CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + int i; + for ( i = 0; i+7 < numIndexes; i+= 8 ) { + used[indexes[i]] = true; + used[indexes[i+1]] = true; + used[indexes[i+2]] = true; + used[indexes[i+3]] = true; + used[indexes[i+4]] = true; + used[indexes[i+5]] = true; + used[indexes[i+6]] = true; + used[indexes[i+7]] = true; + } + + for ( ; i < numIndexes; i++ ) { + used[indexes[i]] = true; + } + + // load lightOrigin and viewOrigin into vectors + const float *lightOriginPtr = lightOrigin.ToFloatPtr(); + const float *viewOriginPtr = viewOrigin.ToFloatPtr(); + vector unsigned char permVec = vec_lvsl( 0, lightOriginPtr ); + vector unsigned char permVec2 = vec_lvsl( 0, viewOriginPtr ); + vector float v0 = vec_ld( 0, lightOriginPtr ); + vector float v1 = vec_ld( 15, lightOriginPtr ); + vector float v2 = vec_ld( 0, viewOriginPtr ); + vector float v3 = vec_ld( 15, viewOriginPtr ); + vector float vecLightOrigin = vec_perm( v0, v1, permVec ); + vector float vecViewOrigin = vec_perm( v2, v3, permVec2 ); + const vector float zeroVector = (vector float)(0); + int index; + + for ( index = 0; index+1 < numVerts; index+=2 ) { + const float *vertPtr = verts[index].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[index+1].xyz.ToFloatPtr(); + + permVec = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + permVec2 = vec_add( vec_lvsl( -1, vertPtr2 ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + vector float v2 = vec_ld( 31, vertPtr ); + vector float v3 = vec_ld( 47, vertPtr ); + vector float v4 = vec_ld( 63, vertPtr ); + + vector float v5 = vec_ld( 0, vertPtr2 ); + vector float v6 = vec_ld( 15, vertPtr2 ); + vector float v7 = vec_ld( 31, vertPtr2 ); + vector float v8 = vec_ld( 47, vertPtr2 ); + vector float v9 = vec_ld( 63, vertPtr2 ); + + // figure out what values go where + vector float vecXYZ = vec_perm( v0, v1, permVec ); + vector float vecNormal = vec_perm( v1, v2, permVec ); + vecNormal = vec_sld( vecNormal, vecNormal, 4 ); + const vector float vecTangent0 = vec_perm( v2, v3, permVec ); + permVec = vec_add( permVec, (vector unsigned char)(-4) ); //shift permute right 3 elements + const vector float vecTangent1 = vec_perm( v3, v4, permVec ); + + vector float vecXYZ2 = vec_perm( v5, v6, permVec2 ); + vector float vecNormal2 = vec_perm( v6, v7, permVec2 ); + vecNormal2 = vec_sld( vecNormal2, vecNormal2, 4 ); + const vector float vecTangent02 = vec_perm( v7, v8, permVec2 ); + permVec2 = vec_add( permVec2, (vector unsigned char)(-4) ); + const vector float vecTangent12 = vec_perm( v8, v9, permVec2 ); + + // calculate lightDir + vector float vecLightDir = vec_sub( vecLightOrigin, vecXYZ ); + vector float vecViewDir = vec_sub( vecViewOrigin, vecXYZ ); + + vector float vecLightDir2 = vec_sub( vecLightOrigin, vecXYZ2 ); + vector float vecViewDir2 = vec_sub( vecViewOrigin, vecXYZ2 ); + + // calculate distance + vector float vecTempLight = vec_madd( vecLightDir, vecLightDir, zeroVector ); + vector float vecTempView = vec_madd( vecViewDir, vecViewDir, zeroVector ); + + vector float vecTempLight2 = vec_madd( vecLightDir2, vecLightDir2, zeroVector ); + vector float vecTempView2 = vec_madd( vecViewDir2, vecViewDir2, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum = vec_add( vecTempLight, vec_sld( vecTempLight, vecTempLight, 4 ) ); + vecTempLight = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vector float tempSum2 = vec_add( vecTempView, vec_sld( vecTempView, vecTempView, 4 ) ); + vecTempView = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 8 ) ); + + vector float tempSum4 = vec_add( vecTempLight2, vec_sld( vecTempLight2, vecTempLight2, 4 ) ); + vecTempLight2 = vec_add( tempSum4, vec_sld( tempSum4, tempSum4, 8 ) ); + vector float tempSum5 = vec_add( vecTempView2, vec_sld( vecTempView2, vecTempView2, 4 ) ); + vecTempView2 = vec_add( tempSum5, vec_sld( tempSum5, tempSum5, 8 ) ); + + // splat sum accross the whole vector + vecTempLight = vec_splat( vecTempLight, 0 ); + vecTempView = vec_splat( vecTempView, 0 ); + + vecTempLight2 = vec_splat( vecTempLight2, 0 ); + vecTempView2 = vec_splat( vecTempView2, 0 ); + + vecTempLight = ReciprocalSquareRoot( vecTempLight ); + vecTempView = ReciprocalSquareRoot( vecTempView ); + + vecTempLight2 = ReciprocalSquareRoot( vecTempLight2 ); + vecTempView2 = ReciprocalSquareRoot( vecTempView2 ); + + // modify light and view vectors based on ilength + vecViewDir = vec_madd( vecViewDir, vecTempView, zeroVector ); + vecLightDir = vec_madd( vecLightDir, vecTempLight, vecViewDir ); + + vecViewDir2 = vec_madd( vecViewDir2, vecTempView2, zeroVector ); + vecLightDir2 = vec_madd( vecLightDir2, vecTempLight2, vecViewDir2 ); + + // calculate what to store in each texture coord + vector float vecTC0 = vec_madd( vecLightDir, vecTangent0, zeroVector ); + vector float vecTC1 = vec_madd( vecLightDir, vecTangent1, zeroVector ); + vector float vecTC2 = vec_madd( vecLightDir, vecNormal, zeroVector ); + + vector float vecTC3 = vec_madd( vecLightDir2, vecTangent02, zeroVector ); + vector float vecTC4 = vec_madd( vecLightDir2, vecTangent12, zeroVector ); + vector float vecTC5 = vec_madd( vecLightDir2, vecNormal2, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum3; + tempSum = vec_add( vecTC0, vec_sld( vecTC0, vecTC0, 4 ) ); + vecTC0 = vec_add( tempSum, vec_sld( vecTC0, vecTC0, 8 ) ); + tempSum2 = vec_add( vecTC1, vec_sld( vecTC1, vecTC1, 4 ) ); + vecTC1 = vec_add( tempSum2, vec_sld( vecTC1, vecTC1, 8 ) ); + tempSum3 = vec_add( vecTC2, vec_sld( vecTC2, vecTC2, 4 ) ); + vecTC2 = vec_add( tempSum3, vec_sld( vecTC2, vecTC2, 8 ) ); + + tempSum4 = vec_add( vecTC3, vec_sld( vecTC3, vecTC3, 4 ) ); + vecTC3 = vec_add( tempSum4, vec_sld( vecTC3, vecTC3, 8 ) ); + tempSum5 = vec_add( vecTC4, vec_sld( vecTC4, vecTC4, 4 ) ); + vecTC4 = vec_add( tempSum5, vec_sld( vecTC4, vecTC4, 8 ) ); + vector float tempSum6 = vec_add( vecTC5, vec_sld( vecTC5, vecTC5, 4 ) ); + vecTC5 = vec_add( tempSum6, vec_sld( vecTC5, vecTC5, 8 ) ); + + vecTC0 = vec_splat( vecTC0, 0 ); + vecTC1 = vec_splat( vecTC1, 0 ); + vecTC2 = vec_splat( vecTC2, 0 ); + + vecTC3 = vec_splat( vecTC3, 0 ); + vecTC4 = vec_splat( vecTC4, 0 ); + vecTC5 = vec_splat( vecTC5, 0 ); + + if ( used[index] ) { + // store out results + vec_ste( vecTC0, 0, &texCoords[index][0] ); + vec_ste( vecTC1, 0, &texCoords[index][1] ); + vec_ste( vecTC2, 0, &texCoords[index][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index][3] ); + } + + if ( used[index+1] ) { + vec_ste( vecTC3, 0, &texCoords[index+1][0] ); + vec_ste( vecTC4, 0, &texCoords[index+1][1] ); + vec_ste( vecTC5, 0, &texCoords[index+1][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index+1][3] ); + } + } + + // cleanup + for ( ; index < numVerts; index++ ) { + if ( !used[index] ) { + continue; + } + + const float *vertPtr = verts[index].xyz.ToFloatPtr(); + + permVec = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + vector float v2 = vec_ld( 31, vertPtr ); + vector float v3 = vec_ld( 47, vertPtr ); + vector float v4 = vec_ld( 63, vertPtr ); + + // figure out what values go where + vector float vecXYZ = vec_perm( v0, v1, permVec ); + vector float vecNormal = vec_perm( v1, v2, permVec ); + vecNormal = vec_sld( vecNormal, vecNormal, 4 ); + const vector float vecTangent0 = vec_perm( v2, v3, permVec ); + permVec = vec_add( permVec, (vector unsigned char)(-4) ); //shift permute right 3 elements + const vector float vecTangent1 = vec_perm( v3, v4, permVec ); + + // calculate lightDir + vector float vecLightDir = vec_sub( vecLightOrigin, vecXYZ ); + vector float vecViewDir = vec_sub( vecViewOrigin, vecXYZ ); + + // calculate distance + vector float vecTempLight = vec_madd( vecLightDir, vecLightDir, zeroVector ); + vector float vecTempView = vec_madd( vecViewDir, vecViewDir, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum = vec_add( vecTempLight, vec_sld( vecTempLight, vecTempLight, 4 ) ); + vecTempLight = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vector float tempSum2 = vec_add( vecTempView, vec_sld( vecTempView, vecTempView, 4 ) ); + vecTempView = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 8 ) ); + + // splat sum accross the whole vector + vecTempLight = vec_splat( vecTempLight, 0 ); + vecTempView = vec_splat( vecTempView, 0 ); + + vecTempLight = ReciprocalSquareRoot( vecTempLight ); + vecTempView = ReciprocalSquareRoot( vecTempView ); + + // modify light and view vectors based on ilength + vecViewDir = vec_madd( vecViewDir, vecTempView, zeroVector ); + vecLightDir = vec_madd( vecLightDir, vecTempLight, vecViewDir ); + + // calculate what to store in each texture coord + vector float vecTC0 = vec_madd( vecLightDir, vecTangent0, zeroVector ); + vector float vecTC1 = vec_madd( vecLightDir, vecTangent1, zeroVector ); + vector float vecTC2 = vec_madd( vecLightDir, vecNormal, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum3; + tempSum = vec_add( vecTC0, vec_sld( vecTC0, vecTC0, 4 ) ); + vecTC0 = vec_add( tempSum, vec_sld( vecTC0, vecTC0, 8 ) ); + tempSum2 = vec_add( vecTC1, vec_sld( vecTC1, vecTC1, 4 ) ); + vecTC1 = vec_add( tempSum2, vec_sld( vecTC1, vecTC1, 8 ) ); + tempSum3 = vec_add( vecTC2, vec_sld( vecTC2, vecTC2, 4 ) ); + vecTC2 = vec_add( tempSum3, vec_sld( vecTC2, vecTC2, 8 ) ); + + vecTC0 = vec_splat( vecTC0, 0 ); + vecTC1 = vec_splat( vecTC1, 0 ); + vecTC2 = vec_splat( vecTC2, 0 ); + + // store out results + vec_ste( vecTC0, 0, &texCoords[index][0] ); + vec_ste( vecTC1, 0, &texCoords[index][1] ); + vec_ste( vecTC2, 0, &texCoords[index][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index][3] ); + + } +} +#endif /* 0 for disable spec coord */ + +#if 1 + +#ifdef VERTEXCACHE_ALIGNED +/* +============ +idSIMD_AltiVec::CreateShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { + int outVerts = 0; + int i = 0; + + assert( IS_16BYTE_ALIGNED( vertexCache[0] ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector unsigned char vecPerm, vecPerm2, vecPerm3, vecPerm4, vecPerm5; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *lPtr = lightOrigin.ToFloatPtr(); + const float *vPtr; + const float *vPtr2; + const float *vPtr3; + const float *vPtr4; + + // put values into a vector + vecPerm = vec_add( vec_lvsl( -1, lPtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, lPtr ); + v1 = vec_ld( 15, lPtr ); + v0 = vec_perm( v0, v1, vecPerm ); + v0 = vec_perm( v0, zeroVector, vecPermZeroLast ); + + //v0 now contains lightOrigin[0], lightOrigin[1], lightOrigin[2], 0 + for ( ; i+3 < numVerts; i+= 4 ) { + if ( ! vertRemap[i] ) { + vPtr = verts[i].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm2 = vec_add( vec_lvsl( -1, vPtr ), (vector unsigned char)(1) ); + v2 = vec_ld( 0, vPtr ); + v3 = vec_ld( 15, vPtr ); + v7 = vec_perm( v2, v3, vecPerm2 ); +#else + v7 = vec_ld( 0, vPtr ); +#endif + v2 = vec_perm( v7, zeroVector, vecPermZeroLast ); + v3 = vec_perm( v7, oneVector, vecPermZeroLast ); + v1 = vec_sub( v2, v0 ); + + vec_st( v3, 0, &vertexCache[outVerts][0] ); + vec_st( v1, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+1] ) { + vPtr2 = verts[i+1].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm3 = vec_add( vec_lvsl( -1, vPtr2 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr2 ); + v5 = vec_ld( 15, vPtr2 ); + v6 = vec_perm( v4, v5, vecPerm3 ); +#else + v6 = vec_ld( 0, vPtr2 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + vec_st( v5, 0, &vertexCache[outVerts][0] ); + vec_st( v6, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+1] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+2] ) { + vPtr3 = verts[i+2].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm4 = vec_add( vec_lvsl( -1, vPtr3 ), (vector unsigned char)(1) ); + v1 = vec_ld( 0, vPtr3 ); + v2 = vec_ld( 15, vPtr3 ); + v3 = vec_perm( v1, v2, vecPerm4 ); +#else + v3 = vec_ld( 0, vPtr3 ); +#endif + v1 = vec_perm( v3, zeroVector, vecPermZeroLast ); + v2 = vec_perm( v3, oneVector, vecPermZeroLast ); + v3 = vec_sub( v1, v0 ); + + vec_st( v2, 0, &vertexCache[outVerts][0] ); + vec_st( v3, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+2] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+3] ) { + vPtr4 = verts[i+3].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm5 = vec_add( vec_lvsl( -1, vPtr4 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr4 ); + v5 = vec_ld( 16, vPtr4 ); + v6 = vec_perm( v4, v5, vecPerm5 ); +#else + v6 = vec_ld( 0, vPtr4 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + vec_st( v5, 0, &vertexCache[outVerts][0] ); + vec_st( v6, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+3] = outVerts; + outVerts += 2; + } + } + + // cleanup + for (; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} + +#else + +/* +============ +idSIMD_AltiVec::CreateShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { + int outVerts = 0; + int i = 0; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector unsigned char vecPerm, vecPerm2, vecPerm3, vecPerm4, vecPerm5; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *lPtr = lightOrigin.ToFloatPtr(); + const float *vPtr; + const float *vPtr2; + const float *vPtr3; + const float *vPtr4; + + // put values into a vector + vecPerm = vec_add( vec_lvsl( -1, lPtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, lPtr ); + v1 = vec_ld( 15, lPtr ); + v0 = vec_perm( v0, v1, vecPerm ); + v0 = vec_perm( v0, zeroVector, vecPermZeroLast ); + + //v0 now contains lightOrigin[0], lightOrigin[1], lightOrigin[2], 0 + for ( ; i+3 < numVerts; i+= 4 ) { + if ( ! vertRemap[i] ) { + vPtr = verts[i].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm2 = vec_add( vec_lvsl( -1, vPtr ), (vector unsigned char)(1) ); + v2 = vec_ld( 0, vPtr ); + v3 = vec_ld( 15, vPtr ); + v7 = vec_perm( v2, v3, vecPerm2 ); +#else + v7 = vec_ld( 0, vPtr ); +#endif + v2 = vec_perm( v7, zeroVector, vecPermZeroLast ); + v3 = vec_perm( v7, oneVector, vecPermZeroLast ); + v1 = vec_sub( v2, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v3, v1 ); + + vertRemap[i] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+1] ) { + vPtr2 = verts[i+1].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm3 = vec_add( vec_lvsl( -1, vPtr2 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr2 ); + v5 = vec_ld( 15, vPtr2 ); + v6 = vec_perm( v4, v5, vecPerm3 ); +#else + v6 = vec_ld( 0, vPtr2 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v5, v6 ); + + vertRemap[i+1] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+2] ) { + vPtr3 = verts[i+2].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm4 = vec_add( vec_lvsl( -1, vPtr3 ), (vector unsigned char)(1) ); + v1 = vec_ld( 0, vPtr3 ); + v2 = vec_ld( 15, vPtr3 ); + v3 = vec_perm( v1, v2, vecPerm4 ); +#else + v3 = vec_ld( 0, vPtr3 ); +#endif + v1 = vec_perm( v3, zeroVector, vecPermZeroLast ); + v2 = vec_perm( v3, oneVector, vecPermZeroLast ); + v3 = vec_sub( v1, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v2, v3 ); + + vertRemap[i+2] = outVerts; + outVerts += 2; + } + if ( ! vertRemap[i+3] ) { + vPtr4 = verts[i+3].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm5 = vec_add( vec_lvsl( -1, vPtr4 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr4 ); + v5 = vec_ld( 16, vPtr4 ); + v6 = vec_perm( v4, v5, vecPerm5 ); +#else + v6 = vec_ld( 0, vPtr4 ); +#endif + + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v5, v6 ); + + + vertRemap[i+3] = outVerts; + outVerts += 2; + } + } + + // cleanup + for (; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} +#endif /* VERTEXCACHE_ALIGNED */ + +#endif /* 0 to disable shadow cache */ + +#if 1 + +#ifdef VERTEXCACHE_ALIGNED +/* +============ +idSIMD_AltiVec::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { + + // vertexCache aligned + assert( IS_16BYTE_ALIGNED( vertexCache[0] ) ); + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idVec4 size + assert( sizeof(idVec4) == IDVEC4_OFFSET * sizeof(float) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermThreeOne = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + int i = 0; + +#ifndef DRAWVERT_PADDED + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } +#endif + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v1 = vec_perm( v2, v3, vertPerm2 ); + v2 = vec_perm( v4, v5, vertPerm3 ); + v3 = vec_perm( v6, v7, vertPerm4 ); +#else + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 0, vertPtr2 ); + v2 = vec_ld( 0, vertPtr3 ); + v3 = vec_ld( 0, vertPtr4 ); +#endif + + v0 = vec_perm( v0, oneVector, vecPermThreeOne ); + v4 = vec_perm( v0, zeroVector, vecPermThreeOne ); + + v1 = vec_perm( v1, oneVector, vecPermThreeOne ); + v5 = vec_perm( v1, zeroVector, vecPermThreeOne ); + + v2 = vec_perm( v2, oneVector, vecPermThreeOne ); + v6 = vec_perm( v2, zeroVector, vecPermThreeOne ); + + v3 = vec_perm( v3, oneVector, vecPermThreeOne ); + v7 = vec_perm( v3, zeroVector, vecPermThreeOne ); + + // store results + ALIGNED_STORE4( &vertexCache[i*2][0], v0, v4, v1, v5 ); + ALIGNED_STORE4( &vertexCache[(i+2)*2][0], v2, v6, v3, v7 ); + + } + + // cleanup + for ( ; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +#else +/* +============ +idSIMD_AltiVec::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idVec4 size + assert( sizeof(idVec4) == IDVEC4_OFFSET * sizeof(float) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermThreeOne = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + int i = 0; + +#ifndef DRAWVERT_PADDED + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } +#endif + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v1 = vec_perm( v2, v3, vertPerm2 ); + v2 = vec_perm( v4, v5, vertPerm3 ); + v3 = vec_perm( v6, v7, vertPerm4 ); +#else + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 0, vertPtr2 ); + v2 = vec_ld( 0, vertPtr3 ); + v3 = vec_ld( 0, vertPtr4 ); +#endif + + v0 = vec_perm( v0, oneVector, vecPermThreeOne ); + v4 = vec_perm( v0, zeroVector, vecPermThreeOne ); + + v1 = vec_perm( v1, oneVector, vecPermThreeOne ); + v5 = vec_perm( v1, zeroVector, vecPermThreeOne ); + + v2 = vec_perm( v2, oneVector, vecPermThreeOne ); + v6 = vec_perm( v2, zeroVector, vecPermThreeOne ); + + v3 = vec_perm( v3, oneVector, vecPermThreeOne ); + v7 = vec_perm( v3, zeroVector, vecPermThreeOne ); + + // store results as unaligned + vector unsigned char storePerm = vec_sub( vec_lvsr( 15, &vertexCache[i*2][0] ), (vector unsigned char)(1) ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + vector float vc1 = vec_ld( 0, &vertexCache[i*2][0] ); + vector float vc2 = vec_ld( 127, &vertexCache[i*2][0] ); + + // right rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + + vec_st( vec_sel( vc1, v0, mask ), 0 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v0, v4, mask ), 15 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v4, v1, mask ), 31 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v1, v5, mask ), 47 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v5, v2, mask ), 63 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v2, v6, mask ), 79 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v6, v3, mask ), 95 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v3, v7, mask ), 111 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v7, vc2, mask ), 127 , &vertexCache[i*2][0] ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +#endif /* VERTEXCACHE_ALIGNED */ + +#endif /* 0 to kill VP shader cache */ + +#endif /* ENABLE_CREATE */ + +#ifdef ENABLE_SOUND_ROUTINES + +#ifdef SOUND_DEST_ALIGNED +/* +============ +idSIMD_AltiVec::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + Assumes that dest starts at aligned address +============ +*/ +void idSIMD_AltiVec::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + + // dest is aligned + assert( IS_16BYTE_ALIGNED( dest[0] ) ); + + vector signed short vs0, vs1; + register vector signed int vi0, vi1; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + // permute vectors + register vector unsigned char vecFirstHalf = (vector unsigned char)(0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7); + register vector unsigned char vecSecondHalf = (vector unsigned char)(8,9,10,11,12,13,14,15,8,9,10,11,12,13,14,15); + + register vector unsigned char vecBottom = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char vecTop = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + // If this can be assumed true, we can eliminate another conditional that checks to see if we can + // load up a vector before the loop + assert( numSamples >= 12 ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // 8 at a time + int i = 0; + + vector signed short vsOld = vec_ld( 0, &src[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[i] ), (vector unsigned char)(1) ); + + for ( ; i+7 < numSamples; i+= 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // permute into vectors in the order to store + + v2 = vec_splat( v0, 0 ); + v3 = vec_splat( v0, 1 ); + v4 = vec_splat( v0, 2 ); + v5 = vec_splat( v0, 3 ); + v6 = vec_splat( v1, 0 ); + v7 = vec_splat( v1, 1 ); + v8 = vec_splat( v1, 2 ); + v9 = vec_splat( v1, 3 ); + + // store results + ALIGNED_STORE8( &dest[i*4], v2, v3, v4, v5, v6, v7, v8, v9 ); + } + // cleanup + for (; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + int i = 0; + + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( ; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = v2; + v4 = vec_perm( v0, v0, vecSecondHalf ); + v5 = v4; + v6 = vec_perm( v1, v1, vecFirstHalf ); + v7 = v6; + v8 = vec_perm (v1, v1, vecSecondHalf ); + v9 = v8; + + // store results + ALIGNED_STORE8( &dest[i*4], v2, v3, v4, v5, v6, v7, v8, v9 ); + } + + for ( ; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 0, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecBottom ); + v3 = vec_perm( v0, v0, vecTop ); + v4 = vec_perm( v1, v1, vecBottom ); + v5 = vec_perm (v1, v1, vecTop ); + + // store results + ALIGNED_STORE4( &dest[i*2], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = vec_perm( v0, v0, vecSecondHalf ); + v4 = vec_perm( v1, v1, vecFirstHalf ); + v5 = vec_perm (v1, v1, vecSecondHalf ); + + // store results + ALIGNED_STORE4( &dest[i*2], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + //unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + + //convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + + //store results + ALIGNED_STORE2( &dest[i], v0, v1 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + No assumptions +============ +*/ +void idSIMD_AltiVec::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + + vector signed short vs0, vs1; + register vector signed int vi0, vi1; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + // permute vectors + register vector unsigned char vecFirstHalf = (vector unsigned char)(0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7); + register vector unsigned char vecSecondHalf = (vector unsigned char)(8,9,10,11,12,13,14,15,8,9,10,11,12,13,14,15); + + register vector unsigned char vecBottom = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char vecTop = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + // calculate perm vector and masks for stores + vector unsigned char storePerm = vec_sub( vec_lvsr( 15, &dest[0] ), (vector unsigned char)(1) ); + // original values of dest + vector float vecDest = vec_ld( 0, &dest[0] ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // 8 at a time + int i = 0; + + vector signed short vsOld = vec_ld( 0, &src[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[i] ), (vector unsigned char)(1) ); + + for ( ; i+7 < numSamples; i+= 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // permute into vectors in the order to store + + v2 = vec_splat( v0, 0 ); + v3 = vec_splat( v0, 1 ); + v4 = vec_splat( v0, 2 ); + v5 = vec_splat( v0, 3 ); + v6 = vec_splat( v1, 0 ); + v7 = vec_splat( v1, 1 ); + v8 = vec_splat( v1, 2 ); + v9 = vec_splat( v1, 3 ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + v9 = vec_perm( v9, v9, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v7, v8, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v8, v9, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v9, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + // cleanup + for (; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + int i = 0; + + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( ; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = v2; + v4 = vec_perm( v0, v0, vecSecondHalf ); + v5 = v4; + v6 = vec_perm( v1, v1, vecFirstHalf ); + v7 = v6; + v8 = vec_perm (v1, v1, vecSecondHalf ); + v9 = v8; + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + v9 = vec_perm( v9, v9, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v7, v8, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v8, v9, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v9, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + + for ( ; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 0, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecBottom ); + v3 = vec_perm( v0, v0, vecTop ); + v4 = vec_perm( v1, v1, vecBottom ); + v5 = vec_perm (v1, v1, vecTop ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = vec_perm( v0, v0, vecSecondHalf ); + v4 = vec_perm( v1, v1, vecFirstHalf ); + v5 = vec_perm (v1, v1, vecSecondHalf ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + } + // cleanup + for ( ; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + //vs0 = vec_ld( 0, &src[i] ); + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 31, &dest[i] ); + + //unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + + //convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i] ); + vecDest = vec_sel( v1, vecDestEnd, mask ); + vec_st( vecDest, 31, &dest[i] ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +#endif + +#ifdef SOUND_DEST_ALIGNED +/* +============ +idSIMD_AltiVec::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + Assumes that dest starts at aligned address +============ +*/ +void idSIMD_AltiVec::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + // dest is aligned + assert( IS_16BYTE_ALIGNED( dest[0] ) ); + + register vector float oggVec1, oggVec2, oggVec3, oggVec4, oggVec5, oggVec6, oggVec7, oggVec8; + register vector float constVec, zeroVector; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10; + vector unsigned char vecPerm1; + vector unsigned char vecPerm2; + + vector unsigned char vecOneTwo = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char vecThreeFour = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + vector unsigned char vecFirst = (vector unsigned char)(0,1,2,3,16,17,18,19,0,1,2,3,16,17,18,19); + vector unsigned char vecSecond = (vector unsigned char)(4,5,6,7,20,21,22,23,4,5,6,7,20,21,22,23); + vector unsigned char vecThird = (vector unsigned char)(8,9,10,11,24,25,26,27,8,9,10,11,24,25,26,27); + vector unsigned char vecFourth = (vector unsigned char)(12,13,14,15,28,29,30,31,12,13,14,15,28,29,30,31); + + constVec = (vector float)(32768.0f); + zeroVector = (vector float)(0.0); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+7 < numSamples; i += 8 ) { + // as it happens, ogg[0][i] through ogg[0][i+3] are contiguous in memory + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + v0 = vec_madd( oggVec1, constVec, zeroVector ); + v1 = vec_madd( oggVec2, constVec, zeroVector ); + v2 = vec_madd( oggVec3, constVec, zeroVector ); + v3 = vec_madd( oggVec4, constVec, zeroVector ); + v4 = vec_madd( oggVec5, constVec, zeroVector ); + v5 = vec_madd( oggVec6, constVec, zeroVector ); + v6 = vec_madd( oggVec7, constVec, zeroVector ); + v7 = vec_madd( oggVec8, constVec, zeroVector ); + + //store results + ALIGNED_STORE8( &dest[i*4], v0, v1, v2, v3, v4, v5, v6, v7 ); + + } + + //cleanup + for ( ; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + + } else { + + // calculate perm vec for ogg + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i+=4 ) { // +1 += 2 + // load and splat from the array ( ogg[0][i] to ogg[0][i+3] ) + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + + // load and splat from the array ( ogg[1][i] to ogg[1][i+3] ) + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + oggVec1 = vec_madd( oggVec1, constVec, zeroVector ); // ogg[0][i] * 32768 + oggVec2 = vec_madd( oggVec2, constVec, zeroVector ); // ogg[0][i+1] * 32768 + oggVec3 = vec_madd( oggVec3, constVec, zeroVector ); // ogg[0][i+2] * 32768 + oggVec4 = vec_madd( oggVec4, constVec, zeroVector ); // ogg[0][i+3] * 32768 + oggVec5 = vec_madd( oggVec5, constVec, zeroVector ); // ogg[1][i] * 32768 + oggVec6 = vec_madd( oggVec6, constVec, zeroVector ); // ogg[1][i+1] * 32768 + oggVec7 = vec_madd( oggVec7, constVec, zeroVector ); // ogg[1][i+2] * 32768 + oggVec8 = vec_madd( oggVec8, constVec, zeroVector ); // ogg[1][i+3] * 32768 + + //merge generates the interleaved pattern that we want and it + //doesn't require a permute vector, so use that instead + v0 = vec_mergeh( oggVec1, oggVec5 ); + v1 = vec_mergel( oggVec1, oggVec5 ); + v2 = vec_mergeh( oggVec2, oggVec6 ); + v3 = vec_mergel( oggVec2, oggVec6 ); + + v4 = vec_mergeh( oggVec3, oggVec7 ); + v5 = vec_mergel( oggVec3, oggVec7 ); + v6 = vec_mergeh( oggVec4, oggVec8 ); + v10 = vec_mergel( oggVec4, oggVec8 ); + + //store results + ALIGNED_STORE8( &dest[i*8], v0, v1, v2, v3, v4, v5, v6, v10 ); + } + + //cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // permute into results vectors to store + v5 = vec_perm( v0, v0, vecOneTwo ); + v6 = vec_perm( v0, v0, vecThreeFour); + v7 = vec_perm( v1, v1, vecOneTwo ); + v8 = vec_perm( v1, v1, vecThreeFour ); + + //store results + ALIGNED_STORE4( &dest[i*2], v5, v6, v7, v8 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + // load ogg[0][i] to ogg[0][i+4] + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors to store + v2 = vec_perm( v0, v1, vecFirst ); + v3 = vec_perm( v0, v1, vecSecond ); + v4 = vec_perm( v0, v1, vecThird ); + v5 = vec_perm( v0, v1, vecFourth ); + + // store results + ALIGNED_STORE4( &dest[i*4], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v9; + v7 = vec_ld( 15, &ogg[0][i] ); + v6 = v7; + v9 = vec_ld( 31, &ogg[0][i] ); + + v0 = vec_perm( v8, v7, vecPerm1 ); + v1 = vec_perm( v6, v9, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + ALIGNED_STORE2( &dest[i], v0, v1 ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors + v2 = vec_mergeh( v0, v1 ); + v3 = vec_mergel( v0, v1 ); + + // store results + ALIGNED_STORE2( &dest[i*2], v2, v3 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + No assumptions +============ +*/ +void idSIMD_AltiVec::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + + register vector float oggVec1, oggVec2, oggVec3, oggVec4, oggVec5, oggVec6, oggVec7, oggVec8; + register vector float constVec, zeroVector; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10; + vector unsigned char vecPerm1; + vector unsigned char vecPerm2; + + vector unsigned char vecOneTwo = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char vecThreeFour = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + vector unsigned char vecFirst = (vector unsigned char)(0,1,2,3,16,17,18,19,0,1,2,3,16,17,18,19); + vector unsigned char vecSecond = (vector unsigned char)(4,5,6,7,20,21,22,23,4,5,6,7,20,21,22,23); + vector unsigned char vecThird = (vector unsigned char)(8,9,10,11,24,25,26,27,8,9,10,11,24,25,26,27); + vector unsigned char vecFourth = (vector unsigned char)(12,13,14,15,28,29,30,31,12,13,14,15,28,29,30,31); + + vector unsigned char storePerm; + + constVec = (vector float)(32768.0f); + zeroVector = (vector float)(0.0); + + // calculate perm vector and masks for stores + storePerm = vec_sub( vec_lvsr( 15, &dest[0] ), (vector unsigned char)(1) ); + // original values of dest + vector float vecDest = vec_ld( 0, &dest[0] ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+7 < numSamples; i += 8 ) { + // as it happens, ogg[0][i] through ogg[0][i+3] are contiguous in memory + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + v0 = vec_madd( oggVec1, constVec, zeroVector ); + v1 = vec_madd( oggVec2, constVec, zeroVector ); + v2 = vec_madd( oggVec3, constVec, zeroVector ); + v3 = vec_madd( oggVec4, constVec, zeroVector ); + v4 = vec_madd( oggVec5, constVec, zeroVector ); + v5 = vec_madd( oggVec6, constVec, zeroVector ); + v6 = vec_madd( oggVec7, constVec, zeroVector ); + v7 = vec_madd( oggVec8, constVec, zeroVector ); + + // rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v1, v2, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v7, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + + //cleanup + for ( ; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + + } else { + + // calculate perm vec for ogg + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i+=4 ) { // +1 += 2 + // load and splat from the array ( ogg[0][i] to ogg[0][i+3] ) + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 127, &dest[i*8] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + + // load and splat from the array ( ogg[1][i] to ogg[1][i+3] ) + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + oggVec1 = vec_madd( oggVec1, constVec, zeroVector ); // ogg[0][i] * 32768 + oggVec2 = vec_madd( oggVec2, constVec, zeroVector ); // ogg[0][i+1] * 32768 + oggVec3 = vec_madd( oggVec3, constVec, zeroVector ); // ogg[0][i+2] * 32768 + oggVec4 = vec_madd( oggVec4, constVec, zeroVector ); // ogg[0][i+3] * 32768 + oggVec5 = vec_madd( oggVec5, constVec, zeroVector ); // ogg[1][i] * 32768 + oggVec6 = vec_madd( oggVec6, constVec, zeroVector ); // ogg[1][i+1] * 32768 + oggVec7 = vec_madd( oggVec7, constVec, zeroVector ); // ogg[1][i+2] * 32768 + oggVec8 = vec_madd( oggVec8, constVec, zeroVector ); // ogg[1][i+3] * 32768 + + //merge generates the interleaved pattern that we want and it + //doesn't require a permute vector, so use that instead + v0 = vec_mergeh( oggVec1, oggVec5 ); + v1 = vec_mergel( oggVec1, oggVec5 ); + v2 = vec_mergeh( oggVec2, oggVec6 ); + v3 = vec_mergel( oggVec2, oggVec6 ); + + v4 = vec_mergeh( oggVec3, oggVec7 ); + v5 = vec_mergel( oggVec3, oggVec7 ); + v6 = vec_mergeh( oggVec4, oggVec8 ); + v10 = vec_mergel( oggVec4, oggVec8 ); + + // rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v10 = vec_perm( v10, v10, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i*8] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i*8] ); + vec_st( vec_sel( v1, v2, mask ), 31, &dest[i*8] ); + vec_st( vec_sel( v2, v3, mask ), 47, &dest[i*8] ); + vec_st( vec_sel( v3, v4, mask ), 63, &dest[i*8] ); + vec_st( vec_sel( v4, v5, mask ), 79, &dest[i*8] ); + vec_st( vec_sel( v5, v6, mask ), 95, &dest[i*8] ); + vec_st( vec_sel( v6, v10, mask ), 111, &dest[i*8] ); + vecDest = vec_sel( v10, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*8] ); + } + + //cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + + // load values from ogg + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // permute into results vectors to store + v5 = vec_perm( v0, v0, vecOneTwo ); + v6 = vec_perm( v0, v0, vecThreeFour); + v7 = vec_perm( v1, v1, vecOneTwo ); + v8 = vec_perm( v1, v1, vecThreeFour ); + + // rotate input data + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v5, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v5, v6, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v6, v7, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v7, v8, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v8, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + // load ogg[0][i] to ogg[0][i+4] + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 63, &dest[i*4] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors to store + v2 = vec_perm( v0, v1, vecFirst ); + v3 = vec_perm( v0, v1, vecSecond ); + v4 = vec_perm( v0, v1, vecThird ); + v5 = vec_perm( v0, v1, vecFourth ); + + // rotate input data + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*4] ); + } + + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v9; + v7 = vec_ld( 15, &ogg[0][i] ); + v6 = v7; + v9 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 31, &dest[i] ); + + v0 = vec_perm( v8, v7, vecPerm1 ); + v1 = vec_perm( v6, v9, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // rotate data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i] ); + vecDest = vec_sel( v1, vecDestEnd, mask ); + vec_st( vecDest, 31, &dest[i] ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors + v2 = vec_mergeh( v0, v1 ); + v3 = vec_mergel( v0, v1 ); + + // store results + UNALIGNED_STORE2( &dest[i*2], v2, v3 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} +#endif /* SOUND_DEST_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerMono + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + int i; + float inc[2]; + float spkr[4]; + + register vector float vecInc; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + register vector float vecSamplesLd1, vecSamplesLd2; + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + + register vector unsigned char permVec1 = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); //0,0,1,1 + register vector unsigned char permVec2 = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); //2,2,3,3 + register vector unsigned char permVec3 = (vector unsigned char)(16,17,18,19,16,17,18,19,20,21,22,23,20,21,22,23); //4,4,5,5 + register vector unsigned char permVec4 = (vector unsigned char)(24,25,26,27,24,25,26,27,28,29,30,31,28,29,30,31); //6,6,7,7 + + //constants + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] *= 2; + inc[1] *= 2; + + //load data into registers + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i=0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + + //load samples and mix buffers + vecSamplesLd1 = vecSamplesLast; //vec_ld( 0, &samples[i] ); + vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLast = vec_ld( 31, &samples[i] ); + + vecSamplesLd1 = vec_perm( vecSamplesLd1, vecSamplesLd2, samplesPerm ); + vecSamplesLd2 = vec_perm( vecSamplesLd2, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*2] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[i*2+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[i*2+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[i*2+12] ); + + vecSamples1 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec1 ); + vecSamples2 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec2 ); + vecSamples3 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec3 ); + vecSamples4 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec4 ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + // store results + ALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + + //add for next iteration + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerMono + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + int i; + float inc[2]; + float spkr[4]; + + register vector float vecInc; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + register vector float vecSamplesLd1, vecSamplesLd2; + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + + register vector unsigned char permVec1 = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); //0,0,1,1 + register vector unsigned char permVec2 = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); //2,2,3,3 + register vector unsigned char permVec3 = (vector unsigned char)(16,17,18,19,16,17,18,19,20,21,22,23,20,21,22,23); //4,4,5,5 + register vector unsigned char permVec4 = (vector unsigned char)(24,25,26,27,24,25,26,27,28,29,30,31,28,29,30,31); //6,6,7,7 + + //constants + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] *= 2; + inc[1] *= 2; + + //load data into registers + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0]), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i=0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + + //load samples and mix buffers + vecSamplesLd1 = vecSamplesLast; + vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLast = vec_ld( 31, &samples[i] ); + + vecSamplesLd1 = vec_perm( vecSamplesLd1, vecSamplesLd2, samplesPerm ); + vecSamplesLd2 = vec_perm( vecSamplesLd2, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*2] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*2] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*2] ); + vector float vecDestEnd = vec_ld( 63, &mixBuffer[i*2] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecDestEnd, mixBufferPerm ); + + vecSamples1 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec1 ); + vecSamples2 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec2 ); + vecSamples3 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec3 ); + vecSamples4 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec4 ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + // store results + UNALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + + //add for next iteration + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + } +} + +#endif /* SOUND_MIX_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerStereo + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + int i, k; + float inc[2]; + float spkr[4]; + + // loading buffers + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + // loading buffers + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecInc; + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + for ( k = 0; k < 2; k++ ) { + inc[k] *= 2; + } + + // load data in vectors + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i = 0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + // load mix buffers and samples + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*2] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[i*2+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[i*2+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[i*2+12] ); + + vecSamples1 = vecSamplesLast; + vecSamples2 = vec_ld( 15, &samples[i*2] ); + vecSamples3 = vec_ld( 31, &samples[i*2] ); + vecSamples4 = vec_ld( 47, &samples[i*2] ); + vecSamplesLast = vec_ld( 63, &samples[i*2] ); + + vecSamples1 = vec_perm( vecSamples1, vecSamples2, samplesPerm ); + vecSamples2 = vec_perm( vecSamples2, vecSamples3, samplesPerm ); + vecSamples3 = vec_perm( vecSamples3, vecSamples4, samplesPerm ); + vecSamples4 = vec_perm( vecSamples4, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + + //store results + ALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerStereo + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + int i, k; + float inc[2]; + float spkr[4]; + // loading buffers + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + // loading buffers + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecInc; + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + for ( k = 0; k < 2; k++ ) { + inc[k] *= 2; + } + + // load data in vectors + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i = 0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + // load mix buffers and samples + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*2] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*2] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*2] ); + vector float vecDestEnd = vec_ld( 63, &mixBuffer[i*2] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecDestEnd, mixBufferPerm ); + + vecSamples1 = vecSamplesLast; + vecSamples2 = vec_ld( 15, &samples[i*2] ); + vecSamples3 = vec_ld( 31, &samples[i*2] ); + vecSamples4 = vec_ld( 47, &samples[i*2] ); + vecSamplesLast = vec_ld( 63, &samples[i*2] ); + + vecSamples1 = vec_perm( vecSamples1, vecSamples2, samplesPerm ); + vecSamples2 = vec_perm( vecSamples2, vecSamples3, samplesPerm ); + vecSamples3 = vec_perm( vecSamples3, vecSamples4, samplesPerm ); + vecSamples4 = vec_perm( vecSamples4, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + + // store results + UNALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + } +} + +#endif /* SOUND_DEST_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerMono + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + float incL[24]; + float sL[24]; + int i, k; + + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4, vecIncl5, vecIncl6, vecIncl7; + vector float vecSL1, vecSL2, vecSL3, vecSL4, vecSL5, vecSL6, vecSL7; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4, vecSamples5, vecSamples6; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6; + // permute vectors for sample + vector unsigned char samplePerm2 = (vector unsigned char)( 0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char samplePerm5 = (vector unsigned char)( 8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = incL[12] = incL[18] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = incL[13] = incL[19] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = incL[14] = incL[20] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = incL[15] = incL[21] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = incL[16] = incL[22] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = incL[17] = incL[23] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + for ( k = 0; k < 6; k++ ) { + sL[k] = lastV[k]; + } + for ( k = 6; k < 12; k++ ) { + sL[k] = lastV[k-6] + incL[k]; + } + for ( k = 12; k < 18; k++ ) { + sL[k] = lastV[k-12] + incL[k] + incL[k]; + } + for ( k = 18; k < 24; k++ ) { + sL[k] = lastV[k-18] + incL[k] + incL[k] + incL[k]; + } + + // multiply by 2 since doing 12 at a time + for ( k = 0; k < 24; k++ ) { + incL[k] *= 4; + } + + //load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + vecIncl5 = vec_ld( 63, &incL[0] ); + vecIncl6 = vec_ld( 79, &incL[0] ); + vecIncl7 = vec_ld( 95, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + vecIncl4 = vec_perm( vecIncl4, vecIncl5, incPerm ); + vecIncl5 = vec_perm( vecIncl5, vecIncl6, incPerm ); + vecIncl6 = vec_perm( vecIncl6, vecIncl7, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + vecSL5 = vec_ld( 63, &sL[0] ); + vecSL6 = vec_ld( 79, &sL[0] ); + vecSL7 = vec_ld( 95, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + vecSL4 = vec_perm( vecSL4, vecSL5, slPerm ); + vecSL5 = vec_perm( vecSL5, vecSL6, slPerm ); + vecSL6 = vec_perm( vecSL6, vecSL7, slPerm ); + + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 4, we don't + //need a cleanup loop + for( i = 0; i <= MIXBUFFER_SAMPLES - 4; i += 4 ) { + //load mix buffer into vectors, assume aligned + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*6] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[(i*6)+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[(i*6)+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[(i*6)+12] ); + vecMixBuffer5 = vec_ld( 0, &mixBuffer[(i*6)+16] ); + vecMixBuffer6 = vec_ld( 0, &mixBuffer[(i*6)+20] ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want + vecSamples1 = vec_splat( vecSamplesLd, 0 ); + vecSamples2 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm2 ); + vecSamples3 = vec_splat( vecSamplesLd, 1 ); + vecSamples4 = vec_splat( vecSamplesLd, 2 ); + vecSamples5 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm5 ); + vecSamples6 = vec_splat( vecSamplesLd, 3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSL4, vecMixBuffer4 ); + vecMixBuffer5 = vec_madd( vecSamples5, vecSL5, vecMixBuffer5 ); + vecMixBuffer6 = vec_madd( vecSamples6, vecSL6, vecMixBuffer6 ); + + //store out results + ALIGNED_STORE6( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + vecSL4 = vec_add( vecSL4, vecIncl4 ); + vecSL5 = vec_add( vecSL5, vecIncl5 ); + vecSL6 = vec_add( vecSL6, vecIncl6 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerMono + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + float incL[24]; + float sL[24]; + int i, k; + + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4, vecIncl5, vecIncl6, vecIncl7; + vector float vecSL1, vecSL2, vecSL3, vecSL4, vecSL5, vecSL6, vecSL7; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4, vecSamples5, vecSamples6; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6; + // permute vectors for sample + register vector unsigned char samplePerm2 = (vector unsigned char)( 0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char samplePerm5 = (vector unsigned char)( 8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = incL[12] = incL[18] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = incL[13] = incL[19] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = incL[14] = incL[20] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = incL[15] = incL[21] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = incL[16] = incL[22] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = incL[17] = incL[23] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + for ( k = 0; k < 6; k++ ) { + sL[k] = lastV[k]; + } + for ( k = 6; k < 12; k++ ) { + sL[k] = lastV[k-6] + incL[k]; + } + for ( k = 12; k < 18; k++ ) { + sL[k] = lastV[k-12] + incL[k] + incL[k]; + } + for ( k = 18; k < 24; k++ ) { + sL[k] = lastV[k-18] + incL[k] + incL[k] + incL[k]; + } + + // multiply by 2 since doing 12 at a time + for ( k = 0; k < 24; k++ ) { + incL[k] *= 4; + } + + // load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + vecIncl5 = vec_ld( 63, &incL[0] ); + vecIncl6 = vec_ld( 79, &incL[0] ); + vecIncl7 = vec_ld( 95, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + vecIncl4 = vec_perm( vecIncl4, vecIncl5, incPerm ); + vecIncl5 = vec_perm( vecIncl5, vecIncl6, incPerm ); + vecIncl6 = vec_perm( vecIncl6, vecIncl7, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + vecSL5 = vec_ld( 63, &sL[0] ); + vecSL6 = vec_ld( 79, &sL[0] ); + vecSL7 = vec_ld( 95, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + vecSL4 = vec_perm( vecSL4, vecSL5, slPerm ); + vecSL5 = vec_perm( vecSL5, vecSL6, slPerm ); + vecSL6 = vec_perm( vecSL6, vecSL7, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 4, we don't + //need a cleanup loop + for( i = 0; i <= MIXBUFFER_SAMPLES - 4; i += 4 ) { + //load mix buffer into vectors + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*6] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*6] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*6] ); + vecMixBuffer5 = vec_ld( 63, &mixBuffer[i*6] ); + vecMixBuffer6 = vec_ld( 79, &mixBuffer[i*6] ); + vector float vecDestEnd = vec_ld( 95, &mixBuffer[i*6] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecMixBuffer5, mixBufferPerm ); + vecMixBuffer5 = vec_perm( vecMixBuffer5, vecMixBuffer6, mixBufferPerm ); + vecMixBuffer6 = vec_perm( vecMixBuffer6, vecDestEnd, mixBufferPerm ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want + vecSamples1 = vec_splat( vecSamplesLd, 0 ); + vecSamples2 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm2 ); + vecSamples3 = vec_splat( vecSamplesLd, 1 ); + vecSamples4 = vec_splat( vecSamplesLd, 2 ); + vecSamples5 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm5 ); + vecSamples6 = vec_splat( vecSamplesLd, 3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSL4, vecMixBuffer4 ); + vecMixBuffer5 = vec_madd( vecSamples5, vecSL5, vecMixBuffer5 ); + vecMixBuffer6 = vec_madd( vecSamples6, vecSL6, vecMixBuffer6 ); + + // store results + UNALIGNED_STORE6( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + vecSL4 = vec_add( vecSL4, vecIncl4 ); + vecSL5 = vec_add( vecSL5, vecIncl5 ); + vecSL6 = vec_add( vecSL6, vecIncl6 ); + } +} + +#endif /* SOUND_MIX_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerStereo + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ + +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + float incL[12]; + float sL[12]; + int i; + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4; + vector float vecSL1, vecSL2, vecSL3, vecSL4; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3; + // permute vectors for sample + vector unsigned char samplePerm1 = (vector unsigned char)( 0,1,2,3,4,5,6,7,0,1,2,3,0,1,2,3); + vector unsigned char samplePerm3 = (vector unsigned char)( 8,9,10,11,8,9,10,11,8,9,10,11,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + sL[0] = lastV[0]; + sL[1] = lastV[1]; + sL[2] = lastV[2]; + sL[3] = lastV[3]; + sL[4] = lastV[4]; + sL[5] = lastV[5]; + sL[6] = lastV[0] + incL[0]; + sL[7] = lastV[1] + incL[1]; + sL[8] = lastV[2] + incL[2]; + sL[9] = lastV[3] + incL[3]; + sL[10] = lastV[4] + incL[4]; + sL[11] = lastV[5] + incL[5]; + + // multiply by 2 since doing 12 at a time + incL[0] *= 2; + incL[1] *= 2; + incL[2] *= 2; + incL[3] *= 2; + incL[4] *= 2; + incL[5] *= 2; + incL[6] *= 2; + incL[7] *= 2; + incL[8] *= 2; + incL[9] *= 2; + incL[10] *= 2; + incL[11] *= 2; + + //we aligned this data, so load it up + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + + //load mix buffer into vectors, assume aligned + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*6] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[(i*6)+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[(i*6)+8] ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i*2] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want. For the 2nd vector, + //the order happens to be the same as the order we loaded them + //in, so there's no need to permute that one + vecSamples1 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm1 ); + vecSamples2 = vecSamplesLd; + vecSamples3 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + + //store out results + ALIGNED_STORE3( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerStereo + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + float incL[12]; + float sL[12]; + + int i; + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4; + vector float vecSL1, vecSL2, vecSL3, vecSL4; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3; + // permute vectors for sample + vector unsigned char samplePerm1 = (vector unsigned char)( 0,1,2,3,4,5,6,7,0,1,2,3,0,1,2,3); + vector unsigned char samplePerm3 = (vector unsigned char)( 8,9,10,11,8,9,10,11,8,9,10,11,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + sL[0] = lastV[0]; + sL[1] = lastV[1]; + sL[2] = lastV[2]; + sL[3] = lastV[3]; + sL[4] = lastV[4]; + sL[5] = lastV[5]; + sL[6] = lastV[0] + incL[0]; + sL[7] = lastV[1] + incL[1]; + sL[8] = lastV[2] + incL[2]; + sL[9] = lastV[3] + incL[3]; + sL[10] = lastV[4] + incL[4]; + sL[11] = lastV[5] + incL[5]; + + // multiply by 2 since doing 12 at a time + incL[0] *= 2; + incL[1] *= 2; + incL[2] *= 2; + incL[3] *= 2; + incL[4] *= 2; + incL[5] *= 2; + incL[6] *= 2; + incL[7] *= 2; + incL[8] *= 2; + incL[9] *= 2; + incL[10] *= 2; + incL[11] *= 2; + + // load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + + //load mix buffer into vectors + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*6] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*6] ); + vector float vecDestEnd = vec_ld( 47, &mixBuffer[i*6] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecDestEnd, mixBufferPerm ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i*2] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want. For the 2nd vector, + //the order happens to be the same as the order we loaded them + //in, so there's no need to permute that one + vecSamples1 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm1 ); + vecSamples2 = vecSamplesLd; + vecSamples3 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + + // store results + UNALIGNED_STORE3( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + } +} + +#endif //SOUND_MIX_ALIGNED + +/* +============ +idSIMD_AltiVec::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_AltiVec::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { + //this is basically a clamp for sound mixing + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector signed int vi0, vi1, vi2, vi3; + register vector signed short vs0, vs1; + register vector float minVec, maxVec, constVec; + int i = 0; + + //unaligned at start, since samples is not 16-byte aligned + for ( ; NOT_16BYTE_ALIGNED( samples[i] ) && ( i < numSamples ); i++ ) { + samples[i] = mixBuffer[i] <= -32768.0f ? -32768 : mixBuffer[i] >= 32767.0f ? 32767 : (short) mixBuffer[i]; + } + + constVec = (vector float)(65536.0f); + + //splat min/max into a vector + minVec = (vector float)(-32768.0f); + maxVec = (vector float)(32767.0f); + + vector float vecOld = vec_ld( 0, &mixBuffer[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &mixBuffer[i] ), (vector unsigned char)(1) ); + + //vectorize! + for ( ; i+15 < numSamples; i += 16 ) { + //load source + v0 = vecOld; + v1 = vec_ld( 15, &mixBuffer[i] ); + v2 = vec_ld( 31, &mixBuffer[i] ); + v3 = vec_ld( 31, &mixBuffer[i] ); + vecOld = vec_ld( 47, &mixBuffer[i] ); + + v0 = vec_perm( v0, v1, permVec ); + v1 = vec_perm( v1, v2, permVec ); + v2 = vec_perm( v2, v3, permVec ); + v3 = vec_perm( v3, vecOld, permVec ); + + //apply minimum + v4 = vec_max( v0, minVec ); + v5 = vec_max( v1, minVec ); + v6 = vec_max( v2, minVec ); + v7 = vec_max( v3, minVec ); + + //apply maximum + v4 = vec_min( v4, maxVec ); + v5 = vec_min( v5, maxVec ); + v6 = vec_min( v6, maxVec ); + v7 = vec_min( v7, maxVec ); + + // convert floats to ints + vi0 = vec_cts( v4, 0 ); + vi1 = vec_cts( v5, 0 ); + vi2 = vec_cts( v6, 0 ); + vi3 = vec_cts( v7, 0 ); + + // pack ints into shorts + vs0 = vec_pack( vi0, vi1 ); + vs1 = vec_pack( vi2, vi3 ); + ALIGNED_STORE2( &samples[i], vs0, vs1 ); + } + + //handle cleanup + for ( ; i < numSamples ; i++ ) { + samples[i] = mixBuffer[i] <= -32768.0f ? -32768 : mixBuffer[i] >= 32767.0f ? 32767 : (short) mixBuffer[i]; + } +} +#endif /* ENABLE_SOUND_ROUTINES */ + +#endif /* MACOS_X */ diff --git a/source/idlib/math/Simd_AltiVec.h b/source/idlib/math/Simd_AltiVec.h new file mode 100644 index 0000000..dfe3373 --- /dev/null +++ b/source/idlib/math/Simd_AltiVec.h @@ -0,0 +1,232 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MATH_SIMD_ALTIVEC_H__ +#define __MATH_SIMD_ALTIVEC_H__ + +/* +=============================================================================== + + AltiVec implementation of idSIMDProcessor + +=============================================================================== +*/ + +// Defines for enabling parts of the library + +// Turns on/off the simple math routines (add, sub, div, etc) +#define ENABLE_SIMPLE_MATH + +// Disable dot routines since they introduce artifacts with the shadows. I can't measure +// a performance improvement with sampling tools with DOT enabled, so disable for now +// Turns on/off the dot routines +//#define ENABLE_DOT + +// Turns on/off the compare routines +#define ENABLE_COMPARES + +// The MinMax routines introduce a couple of bugs. In the bathroom of the alphalabs2 map, the +// wrong surface appears in the mirror at times. It also introduces a noticable delay when map +// data is loaded such as going through doors. +// Turns on/off MinMax routines +//#define ENABLE_MINMAX + +// Turns on/off Clamp routines +#define ENABLE_CLAMP + +// Disabling because members of idAFConstraint aren't aligned properly +// Turns on/off XXX16 routines +//#define ENABLE_16ROUTINES + +// Turns on/off LowerTriangularSolve, LowerTriangularSolveTranspose, and MatX_LDLTFactor +#define ENABLE_LOWER_TRIANGULAR + +// Turns on/off TracePointCull, DecalPointCull, and OverlayPoint +// The Enable_Cull routines breaks the g_decals functionality, DecalPointCull is +// the likely suspect. Bullet holes do not appear on the walls when this optimization +// is enabled. +//#define ENABLE_CULL + +// Turns on/off DeriveTriPlanes, DeriveTangents, DeriveUnsmoothedTangents, NormalizeTangents +#define ENABLE_DERIVE + +// Turns on/off CreateTextureSpaceLightVectors, CreateShadowCache, CreateVertexProgramShadowCache +#define ENABLE_CREATE + +// Turns on/off the sound routines +#define ENABLE_SOUND_ROUTINES + +// Turns on/off the stuff that isn't on elsewhere +// Currently: BlendJoints, TransformJoints, UntransformJoints, ConvertJointQuatsToJointMats, and +// ConvertJointMatsToJointQuats +//#define LIVE_VICARIOUSLY + +// This assumes that the dest array to the sound functions is aligned. If this is not true, we take a large +// performance hit from having to do unaligned stores +//#define SOUND_DEST_ALIGNED + +// This assumes that the finalMixBuffer array to the sound functions is aligned. If this is not true, we take a large +// performance hit from having to do unaligned stores +//#define SOUND_MIX_ALIGNED + +// This assumes that the vertexCache array to CreateShadowCache and CreateVertexProgramShadowCache is aligned. If it's not, +// then we take a big performance hit from unaligned stores. +//#define VERTEXCACHE_ALIGNED + +// This turns on support for PPC intrinsics in the SIMD_AltiVec.cpp file. Right now it's only used for frsqrte. GCC +// supports these intrinsics but XLC does not. +#define PPC_INTRINSICS + +// This assumes that the idDrawVert array that is used in DeriveUnsmoothedTangents is aligned. If its not aligned, +// then we don't get any speedup +//#define DERIVE_UNSMOOTH_DRAWVERT_ALIGNED + +// The QuakeIV definition of idDrawVert is 64 bytes, so enable this optimization +#define DRAWVERT_PADDED + +class idSIMD_AltiVec : public idSIMD_Generic { +#if defined(MACOS_X) && defined(__ppc__) +public: + + virtual const char * VPCALL GetName( void ) const; + +#ifdef ENABLE_SIMPLE_MATH + // Basic math, works for both aligned and unaligned data + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count); + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Div( float *dst, const float constant, const float *divisor, const int count ); + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ); +#endif + +#ifdef ENABLE_DOT + // Dot products, expects data structures in contiguous memory + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ); + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ); +#endif + +#ifdef ENABLE_COMPARES + // Comparisons, works for both aligned and unaligned data + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); +#endif + +#ifdef ENABLE_MINMAX + // Min/Max. Expects data structures in contiguous memory + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ); + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ); +#endif + +#ifdef ENABLE_CLAMP + // Clamp operations. Works for both aligned and unaligned data + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ); + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ); + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ); +#endif + + // These are already using memcpy and memset functions. Leaving default implementation +// virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); +// virtual void VPCALL Memset( void *dst, const int val, const int count ); + +#ifdef ENABLE_16ROUTINES + // Operations that expect 16-byte aligned data and 16-byte padded memory (with zeros), generally faster + virtual void VPCALL Zero16( float *dst, const int count ); + virtual void VPCALL Negate16( float *dst, const int count ); + virtual void VPCALL Copy16( float *dst, const float *src, const int count ); + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ); + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ); +#endif + +// Most of these deal with tiny matrices or vectors, generally not worth altivec'ing since +// the scalar code is already really fast + +// virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); +// virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + +#ifdef ENABLE_LOWER_TRIANGULAR + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); +#if __GNUC__ >= 4 + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); +#else + virtual unsigned char VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); +#endif +#endif +#ifdef LIVE_VICARIOUSLY + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); +#endif + +#ifdef LIVE_VICARIOUSLY + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); +#endif + +#ifdef ENABLE_CULL + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_DERIVE + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ); + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_CREATE + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ); + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_SOUND_ROUTINES + // Sound upsampling and mixing routines, works for aligned and unaligned data + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); +#endif +#endif + +}; + +#endif /* !__MATH_SIMD_ALTIVEC_H__ */ diff --git a/source/idlib/math/Simd_InstructionMacros.h b/source/idlib/math/Simd_InstructionMacros.h new file mode 100644 index 0000000..aea6129 --- /dev/null +++ b/source/idlib/math/Simd_InstructionMacros.h @@ -0,0 +1,500 @@ + +#ifndef __SIMD_SSE_MACROS_H__ +#define __SIMD_SSE_MACROS_H__ + +//#include // MSVC intrinsic SSE is very poor + +/* +=============================================================================== + + Instruction Macros. + + The first argument of an instruction macro is the destination and + the second argument is the source operand. For most instructions + the destination operand can be _xmm0 to _xmm7 only. The source + operand can be any one of the registers _xmm0 to _xmm7 or _eax, + _ecx, _edx, _esp, _ebp, _ebx, _esi, or _edi that contains the + effective address. + + For instance: haddps xmm0, xmm1 + becomes: _haddps( _xmm0, _xmm1 ) + and: haddps xmm0, [esi] + becomes: _haddps( _xmm0, _esi ) + + The ADDRESS_ADD_C macro can be used when the effective source address + is formed by adding a constant to a general purpose register. + The constant must be in the range [-128, 127]. Use the ADDRESS_ADD_LC + macro if the constant is not in this range. + + For instance: haddps xmm0, [esi+48] + becomes: _haddps( _xmm0, ADDRESS_ADD_C( _esi, 48 ) ) + + The ADDRESS_ADD_R macro can be used when the effective source address + is formed by adding two general purpose registers. + + For instance: haddps xmm0, [esi+eax] + becomes: _haddps( _xmm0, ADDRESS_ADD_R( _esi, _eax ) ) + + The ADDRESS_ADD_RC macro can be used when the effective source address + is formed by adding two general purpose registers and a constant. + The constant must be in the range [-128, 127]. Use the ADDRESS_ADD_RLC + macro if the constant is not in this range. + + For instance: haddps xmm0, [esi+eax+48] + becomes: _haddps( _xmm0, ADDRESS_ADD_RC( _esi, _eax, 48 ) ) + + The ADDRESS_ADD_RS macro can be used when the effective source address is formed + by adding a scaled general purpose register to another general purpose register. + The scale must be either 1, 2, 4 or 8. + + For instance: haddps xmm0, [esi+eax*4] + becomes: _haddps( _xmm0, ADDRESS_ADD_RS( _esi, _eax, 4 ) ) + + The ADDRESS_ADD_RSC macro can be used when the effective source address is formed + by adding a scaled general purpose register to another general purpose register and + also adding a constant. The scale must be either 1, 2, 4 or 8. The constant must + be in the range [-128, 127]. Use the ADDRESS_ADD_RSLC macro if the constant is not + in this range. + + For instance: haddps xmm0, [esi+eax*4+64] + becomes: _haddps( _xmm0, ADDRESS_ADD_RSC( _esi, _eax, 4, 64 ) ) + +=============================================================================== +*/ + +/* +=============================================================================== + + Register constants. + +=============================================================================== +*/ + +#define _eax 0x00 +#define _ecx 0x01 +#define _edx 0x02 +#define _ebx 0x03 +#define _esp 0x04 +#define _ebp 0x05 +#define _esi 0x06 +#define _edi 0x07 + +#define _mm0 0xC0 +#define _mm1 0xC1 +#define _mm2 0xC2 +#define _mm3 0xC3 +#define _mm4 0xC4 +#define _mm5 0xC5 +#define _mm6 0xC6 +#define _mm7 0xC7 + +#define _xmm0 0xC0 +#define _xmm1 0xC1 +#define _xmm2 0xC2 +#define _xmm3 0xC3 +#define _xmm4 0xC4 +#define _xmm5 0xC5 +#define _xmm6 0xC6 +#define _xmm7 0xC7 + +/* +=============================================================================== + + Addressing modes. + +=============================================================================== +*/ + +#define RSCALE( s ) ( ((s)&2)<<5 ) | ( ((s)&4)<<5 ) | ( ((s)&8)<<3 ) | ( ((s)&8)<<4 ) + +#define ADDRESS_ADD_C( reg0, constant ) 0x40 | ( reg0 & 7 ) \ + _asm _emit constant + +#define ADDRESS_ADD_LC( reg0, constant ) 0x80 | ( reg0 & 7 ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +#define ADDRESS_ADD_R( reg0, reg1 ) 0x04 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) + +#define ADDRESS_ADD_RC( reg0, reg1, constant ) 0x44 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) \ + _asm _emit constant + +#define ADDRESS_ADD_RLC( reg0, reg1, constant ) 0x84 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +#define ADDRESS_ADD_RS( reg0, reg1, scale ) 0x04 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) + +#define ADDRESS_ADD_RSC( reg0, reg1, scale, constant ) 0x44 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) \ + _asm _emit constant + +#define ADDRESS_ADD_RSLC( reg0, reg1, scale, constant ) 0x84 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +/* +=============================================================================== + + Macros for third operand of shuffle/swizzle instructions. + +=============================================================================== +*/ + +#define SHUFFLE_PS( x, y, z, w ) (( (x) & 3 ) << 6 | ( (y) & 3 ) << 4 | ( (z) & 3 ) << 2 | ( (w) & 3 )) +#define R_SHUFFLE_PS( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) + +#define SHUFFLE_PD( x, y ) (( (x) & 1 ) << 1 | ( (y) & 1 )) +#define R_SHUFFLE_PD( x, y ) (( (y) & 1 ) << 1 | ( (x) & 1 )) + +#define SHUFFLE_D( x, y, z, w ) (( (x) & 3 ) << 6 | ( (y) & 3 ) << 4 | ( (z) & 3 ) << 2 | ( (w) & 3 )) +#define R_SHUFFLE_D( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) + +/* +=============================================================================== + + SSE compare instructions. + +=============================================================================== +*/ + +#define _EQ 0 +#define _LT 1 +#define _LE 2 +#define _UNORDERED 3 +#define _NEQ 4 +#define _NLT 5 +#define _NLE 6 +#define _ORDERED 7 + +#define _cmpps( dst, src, cond ) \ + _asm _emit 0x0F \ + _asm _emit 0xC2 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src \ + _asm _emit cond + +// Equal ( dst[0-3] = ( dst[0-3] == src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpeps( dst, src ) _cmpps( dst, src, _EQ ) +#define _cmpeqps( dst, src ) _cmpps( dst, src, _EQ ) + +// Not Equal ( dst[0-3] = ( dst[0-3] != src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpneps( dst, src ) _cmpps( dst, src, _NEQ ) +#define _cmpneqps( dst, src ) _cmpps( dst, src, _NEQ ) + +// Less Than ( dst[0-3] = ( dst[0-3] < src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpltps( dst, src ) _cmpps( dst, src, _LT ) + +// Less Equal ( dst[0-3] = ( dst[0-3] <= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpleps( dst, src ) _cmpps( dst, src, _LE ) + +// Greater Equal ( dst[0-3] = ( dst[0-3] >= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgeps( dst, src ) _cmpps( dst, src, _NLT ) + +// Greater Than ( dst[0-3] = ( dst[0-3] > src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgtps( dst, src ) _cmpps( dst, src, _NLE ) + +// Not Less Than ( dst[0-3] = ( dst[0-3] >= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnltps( dst, src ) _cmpps( dst, src, _NLT ) + +// Not Less Equal ( dst[0-3] = ( dst[0-3] > src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnleps( dst, src ) _cmpps( dst, src, _NLE ) + +// Ordered ( dst[0-3] = ( dst[0-3] != NaN && src[0-3] != NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpordps( dst, src ) _cmpps( dst, src, _ORDERED ) + +// Unordered ( dst[0-3] = ( dst[0-3] == NaN || src[0-3] == NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpunordps( dst, src ) _cmpps( dst, src, _UNORDERED ) + +#define _cmpss( dst, src, cond ) \ + _asm _eint 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0xC2 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src \ + _asm _emit cond + +// Equal ( dst[0] = ( dst[0] == src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpess( dst, src ) _cmpss( dst, src, _EQ ) +#define _cmpeqss( dst, src ) _cmpss( dst, src, _EQ ) + +// Not Equal ( dst[0] = ( dst[0] != src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpness( dst, src ) _cmpss( dst, src, _NEQ ) +#define _cmpneqss( dst, src ) _cmpss( dst, src, _NEQ ) + +// Less Than ( dst[0] = ( dst[0] < src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpltss( dst, src ) _cmpss( dst, src, _LT ) + +// Less Equal ( dst[0] = ( dst[0] <= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpless( dst, src ) _cmpss( dst, src, _LE ) + +// Greater Equal ( dst[0] = ( dst[0] >= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgess( dst, src ) _cmpss( dst, src, _NLT ) + +// Greater Than ( dst[0] = ( dst[0] > src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgtss( dst, src ) _cmpss( dst, src, _NLE ) + +// Not Less Than ( dst[0] = ( dst[0] >= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnltss( dst, src ) _cmpss( dst, src, _NLT ) + +// Not Less Equal ( dst[0] = ( dst[0] > src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnless( dst, src ) _cmpss( dst, src, _NLE ) + +// Ordered ( dst[0] = ( dst[0] != NaN && src[0] != NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpordss( dst, src ) _cmpss( dst, src, _ORDERED ) + +// Unordered ( dst[0] = ( dst[0] == NaN || src[0] == NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpunordss( dst, src ) _cmpss( dst, src, _UNORDERED ) + +/* +=============================================================================== + + SSE3 instructions. + +=============================================================================== +*/ + +// Packed Single-FP Add/Subtract ( dst[0]=dst[0]+src[0], dst[1]=dst[1]-src[1], dst[2]=dst[2]+src[2], dst[3]=dst[3]-src[3] ) +#define _addsubps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0xD0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Add/Subtract ( dst[0]=dst[0]+src[0], dst[1]=dst[1]-src[1] ) +#define _addsubpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0xD0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Single-FP Horizontal Add ( dst[0]=dst[0]+dst[1], dst[1]=dst[2]+dst[3], dst[2]=src[0]+src[1], dst[3]=src[2]+src[3] ) +#define _haddps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x7C \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Horizontal Add ( dst[0]=dst[0]+dst[1], dst[1]=src[0]+src[1] ) +#define _haddpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0x7C \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Single-FP Horizontal Subtract ( dst[0]=dst[0]-dst[1], dst[1]=dst[2]-dst[3], dst[2]=src[0]-src[1], dst[3]=src[2]-src[3] ) +#define _hsubps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x7D \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Horizontal Subtract ( dst[0]=dst[0]-dst[1], dst[1]=src[0]-src[1] ) +#define _hsubpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0x7D \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move Packed Single-FP Low and Duplicate ( dst[0]=src[0], dst[1]=src[0], dst[2]=src[2], dst[3]=src[2] ) +#define _movsldup( dst, src ) \ + _asm _emit 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0x12 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move One Double-FP Low and Duplicate ( dst[0]=src[0], dst[1]=src[0] ) +#define _movdldup( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x12 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move Packed Single-FP High and Duplicate ( dst[0]=src[1], dst[1]=src[1], dst[2]=src[3], dst[3]=src[3] ) +#define _movshdup( dst, src ) \ + _asm _emit 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0x16 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move One Double-FP High and Duplicate ( dst[0]=src[1], dst[1]=src[1] ) +#define _movdhdup( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x16 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Load Unaligned Integer 128 bits +#define _lddqu( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0xF0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + + +/* +=============================================================================== + + SSE transpose macros + +=============================================================================== +*/ + +// transpose a 4x4 matrix loaded into 4 xmm registers (reg4 is temporary) +#define TRANSPOSE_4x4( reg0, reg1, reg2, reg3, reg4 ) \ + __asm movaps reg4, reg2 /* reg4 = 8, 9, 10, 11 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 8, 12, 9, 13 */ \ + __asm unpckhps reg4, reg3 /* reg4 = 10, 14, 11, 15 */ \ + __asm movaps reg3, reg0 /* reg3 = 0, 1, 2, 3 */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 4, 1, 5 */ \ + __asm unpckhps reg3, reg1 /* reg3 = 2, 6, 3, 7 */ \ + __asm movaps reg1, reg0 /* reg1 = 0, 4, 1, 5 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 1, 0, 1 ) /* reg0 = 0, 4, 8, 12 */ \ + __asm shufps reg1, reg2, R_SHUFFLE_PS( 2, 3, 2, 3 ) /* reg1 = 1, 5, 9, 13 */ \ + __asm movaps reg2, reg3 /* reg2 = 2, 6, 3, 7 */ \ + __asm shufps reg2, reg4, R_SHUFFLE_PS( 0, 1, 0, 1 ) /* reg2 = 2, 6, 10, 14 */ \ + __asm shufps reg3, reg4, R_SHUFFLE_PS( 2, 3, 2, 3 ) /* reg3 = 3, 7, 11, 15 */ + +// transpose a 4x4 matrix from memory into 4 xmm registers (reg4 is temporary) +#define TRANPOSE_4x4_FROM_MEMORY( address, reg0, reg1, reg2, reg3, reg4 ) \ + __asm movlps reg1, [address+ 0] /* reg1 = 0, 1, X, X */ \ + __asm movlps reg3, [address+ 8] /* reg3 = 2, 3, X, X */ \ + __asm movhps reg1, [address+16] /* reg1 = 0, 1, 4, 5 */ \ + __asm movhps reg3, [address+24] /* reg3 = 2, 3, 6, 7 */ \ + __asm movlps reg2, [address+32] /* reg2 = 8, 9, X, X */ \ + __asm movlps reg4, [address+40] /* reg4 = 10, 11, X, X */ \ + __asm movhps reg2, [address+48] /* reg2 = 8, 9, 12, 13 */ \ + __asm movhps reg4, [address+56] /* reg4 = 10, 11, 14, 15 */ \ + __asm movaps reg0, reg1 /* reg0 = 0, 1, 4, 5 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* reg0 = 0, 4, 8, 12 */ \ + __asm shufps reg1, reg2, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* reg1 = 1, 5, 9, 13 */ \ + __asm movaps reg2, reg3 /* reg2 = 2, 3, 6, 7 */ \ + __asm shufps reg2, reg4, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* reg2 = 2, 6, 10, 14 */ \ + __asm shufps reg3, reg4, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* reg3 = 3, 7, 11, 15 */ + +// transpose a 4x4 matrix to memory from 4 xmm registers (reg4 is temporary) +#define TRANPOSE_4x4_TO_MEMORY( address, reg0, reg1, reg2, reg3, reg4 ) \ + __asm movaps reg4, reg0 /* reg4 = 0, 4, 8, 12 */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 1, 4, 5 */ \ + __asm unpckhps reg4, reg1 /* reg4 = 8, 9, 12, 13 */ \ + __asm movaps reg1, reg2 /* reg1 = 2, 6, 10, 14 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 2, 3, 6, 7 */ \ + __asm unpckhps reg1, reg3 /* reg1 = 10, 11, 14, 15 */ \ + __asm movlps [address+ 0], reg0 /* mem0 = 0, 1, X, X */ \ + __asm movlps [address+ 8], reg2 /* mem0 = 0, 1, 2, 3 */ \ + __asm movhps [address+16], reg0 /* mem1 = 4, 5, X, X */ \ + __asm movhps [address+24], reg2 /* mem1 = 4, 5, 6, 7 */ \ + __asm movlps [address+32], reg4 /* mem2 = 8, 9, X, X */ \ + __asm movlps [address+40], reg1 /* mem2 = 8, 9, 10, 11 */ \ + __asm movhps [address+48], reg4 /* mem3 = 12, 13, X, X */ \ + __asm movhps [address+56], reg1 /* mem3 = 12, 13, 14, 15 */ + +// transpose a 4x3 matrix loaded into 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3( reg0, reg1, reg2, reg3 ) \ + __asm movaps reg3, reg2 /* reg3 = 8, 9, 10, 11 */ \ + __asm shufps reg3, reg1, R_SHUFFLE_PS( 2, 3, 0, 1 ) /* reg3 = 10, 11, 4, 5 */ \ + __asm shufps reg2, reg0, R_SHUFFLE_PS( 0, 1, 2, 3 ) /* reg2 = 8, 9, 2, 3 */ \ + __asm shufps reg1, reg0, R_SHUFFLE_PS( 2, 3, 0, 1 ) /* reg1 = 6, 7, 0, 1 */ \ + __asm movaps reg0, reg1 /* reg0 = 6, 7, 0, 1 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 2, 0, 3, 1 ) /* reg0 = 0, 6, 3, 9 */ \ + __asm shufps reg1, reg3, R_SHUFFLE_PS( 3, 1, 2, 0 ) /* reg1 = 1, 7, 4, 10 */ \ + __asm shufps reg2, reg3, R_SHUFFLE_PS( 2, 0, 3, 1 ) /* reg2 = 2, 8, 5, 11 */ + +// transpose a 4x3 matrix from memory into 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3_FROM_MEMORY( address, reg0, reg1, reg2, reg3 ) \ + __asm movlps reg1, [address+ 0] /* reg1 = 0, 1, X, X */ \ + __asm movlps reg2, [address+ 8] /* reg2 = 2, 3, X, X */ \ + __asm movlps reg3, [address+16] /* reg3 = 4, 5, X, X */ \ + __asm movhps reg1, [address+24] /* reg1 = 0, 1, 6, 7 */ \ + __asm movhps reg2, [address+32] /* reg2 = 2, 3, 8, 9 */ \ + __asm movhps reg3, [address+40] /* reg3 = 4, 5, 10, 11 */ \ + __asm movaps reg0, reg1 /* reg0 = 0, 1, 6, 7 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 2, 1, 3 ) /* reg0 = 0, 6, 3, 9 */ \ + __asm shufps reg1, reg3, R_SHUFFLE_PS( 1, 3, 0, 2 ) /* reg1 = 1, 7, 4, 10 */ \ + __asm shufps reg2, reg3, R_SHUFFLE_PS( 0, 2, 1, 3 ) /* reg2 = 2, 8, 5, 11 */ + +// transpose a 4x3 matrix to memory from 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3_TO_MEMORY( address, reg0, reg1, reg2, reg3 ) \ + __asm movhlps reg3, reg0 /* reg3 = 3, 9, X, X */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 1, 6, 7 */ \ + __asm unpckhps reg1, reg2 /* reg1 = 4, 5, 10, 11 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 2, 3, 8, 9 */ \ + __asm movlps [address+ 0], reg0 /* mem0 = 0, 1, X, X */ \ + __asm movlps [address+ 8], reg2 /* mem0 = 0, 1, 2, 3 */ \ + __asm movlps [address+16], reg1 /* mem1 = 4, 5, X, X */ \ + __asm movhps [address+24], reg0 /* mem1 = 4, 5, 6, 7 */ \ + __asm movhps [address+32], reg2 /* mem2 = 8, 9, X, X */ \ + __asm movhps [address+40], reg1 /* mem2 = 8, 9, 10, 11 */ + +/* +=============================================================================== + + SSE2 transpose macros + +=============================================================================== +*/ + +// transpose a 4x4 integer matrix loaded into 4 xmm registers (reg4 is temporary) +// this is faster than the SSE float version because punpcklqdq has a lower latency than shufps +#define TRANSPOSE_4x4_INT( reg0, reg1, reg2, reg3, reg4 ) \ + __asm movdqa reg4, reg2 /* reg4 = 8, 9, 10, 11 */ \ + __asm punpckldq reg2, reg3 /* reg2 = 8, 12, 9, 13 */ \ + __asm punpckhdq reg4, reg3 /* reg4 = 10, 14, 11, 15 */ \ + __asm movdqa reg3, reg0 /* reg3 = 0, 1, 2, 3 */ \ + __asm punpckldq reg0, reg1 /* reg0 = 0, 4, 1, 5 */ \ + __asm punpckhdq reg3, reg1 /* reg3 = 2, 6, 3, 7 */ \ + __asm movdqa reg1, reg0 /* reg1 = 0, 4, 1, 5 */ \ + __asm punpcklqdq reg0, reg2 /* reg0 = 0, 4, 8, 12 */ \ + __asm punpckhqdq reg1, reg2 /* reg1 = 1, 5, 9, 13 */ \ + __asm movdqa reg2, reg3 /* reg2 = 2, 6, 3, 7 */ \ + __asm punpcklqdq reg2, reg4 /* reg2 = 2, 6, 10, 14 */ \ + __asm punpckhqdq reg3, reg4 /* reg3 = 3, 7, 11, 15 */ + + +/* +=============================================================================== + + Macros to create SSE constants. + +=============================================================================== +*/ + +#define ALIGN4_INIT1( X, I0 ) ALIGN16( static X[4] ) = { I0, I0, I0, I0 } +#define ALIGN4_INIT4( X, I0, I1, I2, I3 ) ALIGN16( static X[4] ) = { I0, I1, I2, I3 } +#define ALIGN8_INIT1( X, I0 ) ALIGN16( static X[8] ) = { I0, I0, I0, I0, I0, I0, I0, I0 } +#define ALIGN16_INIT1( X, I0 ) ALIGN16( static X[16] ) = { I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0 } + +#define IEEE_SP_ZERO 0 +#define IEEE_SP_ONE 0x3f800000 +#define IEEE_SP_SIGN ((unsigned long) ( 1 << 31 )) +#define IEEE_SP_INF ((unsigned long) ( 1 << 23 )) + +/* +=============================================================================== + + Macro to remove boundary check from switch statements. + +=============================================================================== +*/ + +#ifdef _DEBUG +#define NODEFAULT default: assert( 0 ) +#elif _WIN32 +#define NODEFAULT default: __assume( 0 ) +#else +#define NODEFAULT +#endif + +#endif /* !__SIMD_SSE_MACROS_H__ */ diff --git a/source/idlib/math/Simd_MMX.cpp b/source/idlib/math/Simd_MMX.cpp new file mode 100644 index 0000000..de6cb66 --- /dev/null +++ b/source/idlib/math/Simd_MMX.cpp @@ -0,0 +1,583 @@ +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" + + +//=============================================================== +// +// MMX implementation of idSIMDProcessor +// +//=============================================================== + +/* +gcc inline assembly: +inline assembly for the MMX SIMD processor written there mostly as an experiment +does not increase performance on timedemos ( nor did I expect it to, libc-i686 does the job very well already ) + +although the newer gcc can read inline asm using the intel syntax ( with minor reformatting and escaping of register names ), +it's still a long way from providing an easy compatibility with MSVC inline assembly +mostly because of the input/output registers, the clobber lists +and generally all the things gcc tries to be clever about when you give it a piece of inline assembly +( typically, compiling this at -O1 or better will produce bad code, and some of it won't compile with -fPIC either ) + +at this point, writing everything in nasm from the ground up, or using intel's compiler to produce the Simd_*.o objects is +still the best alternative +*/ + +#if defined( _WINDOWS ) || defined( __linux__ ) + +#ifdef _WINDOWS +#define EMMS_INSTRUCTION __asm emms +#else +#define EMMS_INSTRUCTION __asm__ __volatile__ ( "emms\n\t" ); +#endif + +/* +============ +idSIMD_MMX::GetName +============ +*/ +const char * idSIMD_MMX::GetName( void ) const { + return "MMX"; +} + +/* +================ +MMX_Memcpy8B +================ +*/ +void MMX_Memcpy8B( void *dest, const void *src, const int count ) { +#ifdef _MSC_VER + _asm { + mov esi, src + mov edi, dest + mov ecx, count + shr ecx, 3 // 8 bytes per iteration + +loop1: + movq mm1, 0[ESI] // Read in source data + movntq 0[EDI], mm1 // Non-temporal stores + + add esi, 8 + add edi, 8 + dec ecx + jnz loop1 + + } + EMMS_INSTRUCTION +#elif 0 + /* +not using constraints, so no double escape of registers +not necessary to push edi/esi + */ + __asm__ __volatile__ ( + // "mov %edi, dest\n\t" +"mov %edi, DWORD PTR [%ebp+8]\n\t" + // "mov %esi, src\n\t" +"mov %esi, DWORD PTR [%ebp+12]\n\t" + // "mov %ecx, count\n\t" +"mov %ecx, DWORD PTR [%ebp+16]\n\t" + + "shr %ecx, 3\n\t" // 8 bytes per iteration +"loop1_1:\n\t" + "movq %mm1, 0[%ESI]\n\t" // Read in source data + "movntq 0[%EDI], %mm1\n\t" // Non-temporal stores + "add %esi, 8\n\t" + "add %edi, 8\n\t" + "dec %ecx \n\t" + "jnz loop1_1\n\t" + "emms\n\t" + ); +#elif 1 + __asm__ __volatile__ ( + // "mov %esi, src\n\t" + // "mov %edi, dest\n\t" + // "mov %ecx, count\n\t" + "shr %%ecx, 3\n\t" // 8 bytes per iteration +"0:\n\t" + "movq %%mm1, 0[%%esi]\n\t" // Read in source data + "movntq 0[%%edi], %%mm1\n\t" // Non-temporal stores + "add %%esi, 8\n\t" + "add %%edi, 8\n\t" + "dec %%ecx \n\t" + "jnz 0b\n\t" + "emms\n\t" + : /* no outputs */ + : "S" (src), "D" (dest), "c" (count) + ); +#endif +} + +/* +================ +MMX_Memcpy64B + + 165MB/sec +================ +*/ +void MMX_Memcpy64B( void *dest, const void *src, const int count ) { +#ifdef _MSC_VER + _asm { + mov esi, src + mov edi, dest + mov ecx, count + shr ecx, 6 // 64 bytes per iteration + +loop1: + prefetchnta 64[ESI] // Prefetch next loop, non-temporal + prefetchnta 96[ESI] + + movq mm1, 0[ESI] // Read in source data + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add esi, 64 + add edi, 64 + dec ecx + jnz loop1 + } + EMMS_INSTRUCTION +#else + __asm__ __volatile__ ( + //"mov %%esi, src \n\t" + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 6 \n\t"// 64 bytes per iteration +"\n\t" +"1: \n\t" +"prefetchnta 64[%%ESI] \n\t"// Prefetch next loop, non-temporal +"prefetchnta 96[%%ESI] \n\t" +"\n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz 1b \n\t" +"emms \n\t" +: +: "S" (src), "D" (dest), "c" (count) + ); +#endif +} + +/* +================ +MMX_Memcpy2kB + + 240MB/sec +================ +*/ +void MMX_Memcpy2kB( void *dest, const void *src, const int count ) { + byte *tbuf = (byte *)_alloca16(2048); +#ifdef _MSC_VER + __asm { + push ebx + mov esi, src + mov ebx, count + shr ebx, 11 // 2048 bytes at a time + mov edi, dest + +loop2k: + push edi // copy 2k into temporary buffer + mov edi, tbuf + mov ecx, 32 + +loopMemToL1: + prefetchnta 64[ESI] // Prefetch next loop, non-temporal + prefetchnta 96[ESI] + + movq mm1, 0[ESI] // Read in source data + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movq 0[EDI], mm1 // Store into L1 + movq 8[EDI], mm2 + movq 16[EDI], mm3 + movq 24[EDI], mm4 + movq 32[EDI], mm5 + movq 40[EDI], mm6 + movq 48[EDI], mm7 + movq 56[EDI], mm0 + add esi, 64 + add edi, 64 + dec ecx + jnz loopMemToL1 + + pop edi // Now copy from L1 to system memory + push esi + mov esi, tbuf + mov ecx, 32 + +loopL1ToMem: + movq mm1, 0[ESI] // Read in source data from L1 + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add esi, 64 + add edi, 64 + dec ecx + jnz loopL1ToMem + + pop esi // Do next 2k block + dec ebx + jnz loop2k + pop ebx + } + EMMS_INSTRUCTION +#else + +#ifdef __PIC__ + memcpy( dest, src, count ); +#else + /* +ebx problem: +when not compiling with -fPIC, compiles fine. No need to push/pop ebx, the constraints setup will save and restore ( or so it seems with no optimizations ) + +when compiling with -fPIC: +if not putting ebx in clobber list, "can't find a register in class 'BREG' while reloading 'asm'" +if putting ebx in clobber list, "PIC register 'ebx' clobbered in 'asm'" +but really, you don't want to put it in clobber list, you want to push/pop it + +BREG error due to -masm=intel? ( doesn't sound likely - could test with the cpuid thing? ) + +tbuf constrained in memory since the loop loads it up in edi + */ + __asm__ __volatile__ ( +"push %%ebx \n\t" +//"mov %%esi, src \n\t" +//"mov %%ebx, count \n\t" +"shr %%ebx, 11 \n\t"// 2048 bytes at a time +//"mov %%edi, dest \n\t" +"\n\t" +"loop2k: \n\t" +"push %%edi \n\t"// copy 2k into temporary buffer +//"mov %%edi, tbuf \n\t" +"mov %%edi, %0 \n\t" +"mov %%ecx, 32 \n\t" +"\n\t" +"loopMemToL1: \n\t" +"prefetchnta 64[%%ESI] \n\t"// Prefetch next loop, non-temporal +"prefetchnta 96[%%ESI] \n\t" +"\n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movq 0[%%EDI], %%mm1 \n\t"// Store into L1 +"movq 8[%%EDI], %%mm2 \n\t" +"movq 16[%%EDI], %%mm3 \n\t" +"movq 24[%%EDI], %%mm4 \n\t" +"movq 32[%%EDI], %%mm5 \n\t" +"movq 40[%%EDI], %%mm6 \n\t" +"movq 48[%%EDI], %%mm7 \n\t" +"movq 56[%%EDI], %%mm0 \n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loopMemToL1 \n\t" +"\n\t" +"pop %%edi \n\t"// Now copy from L1 to system memory +"push %%esi \n\t" +//"mov %%esi, tbuf \n\t" +"mov %%esi, %0 \n\t" +"mov %%ecx, 32 \n\t" +"\n\t" +"loopL1ToMem: \n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data from L1 +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loopL1ToMem \n\t" +"\n\t" +"pop %%esi \n\t"// Do next 2k block +"dec %%ebx \n\t" +"jnz loop2k \n\t" +"pop %%ebx \n\t" +"emms \n\t" +: +: "m" (tbuf), "S" (src), "D" (dest), "b" (count) +//: "ebx" + ); + +#endif // !ID_PIC + +#endif +} + + +/* +================ +idSIMD_MMX::Memcpy + + optimized memory copy routine that handles all alignment cases and block sizes efficiently +================ +*/ +void VPCALL idSIMD_MMX::Memcpy( void *dest0, const void *src0, const int count0 ) { +#ifndef _WIN32 + memcpy( dest0, src0, count0 ); +#else + // if copying more than 16 bytes and we can copy 8 byte aligned + if ( count0 > 16 && !( ( (int)dest0 ^ (int)src0 ) & 7 ) ) { + byte *dest = (byte *)dest0; + byte *src = (byte *)src0; + + // copy up to the first 8 byte aligned boundary + int count = ((int)dest) & 7; + memcpy( dest, src, count ); + dest += count; + src += count; + count = count0 - count; + + // if there are multiple blocks of 2kB + if ( count & ~4095 ) { + MMX_Memcpy2kB( dest, src, count ); + src += (count & ~2047); + dest += (count & ~2047); + count &= 2047; + } + + // if there are blocks of 64 bytes + if ( count & ~63 ) { + MMX_Memcpy64B( dest, src, count ); + src += (count & ~63); + dest += (count & ~63); + count &= 63; + } + + // if there are blocks of 8 bytes + if ( count & ~7 ) { + MMX_Memcpy8B( dest, src, count ); + src += (count & ~7); + dest += (count & ~7); + count &= 7; + } + + // copy any remaining bytes + memcpy( dest, src, count ); + } else { + // use the regular one if we cannot copy 8 byte aligned + memcpy( dest0, src0, count0 ); + } +#endif // _WIN32 +} + +/* +================ +idSIMD_MMX::Memset +================ +*/ +void VPCALL idSIMD_MMX::Memset( void* dest0, const int val, const int count0 ) { +#ifndef _WIN32 +#else + union { + byte bytes[8]; + word words[4]; + dword dwords[2]; + } dat; + + byte *dest = (byte *)dest0; + int count = count0; + + while( count > 0 && (((int)dest) & 7) ) { + *dest = val; + dest++; + count--; + } + if ( !count ) { + return; + } + + dat.bytes[0] = val; + dat.bytes[1] = val; + dat.words[1] = dat.words[0]; + dat.dwords[1] = dat.dwords[0]; + + if ( count >= 64 ) { +#ifdef _MSC_VER + __asm { + mov edi, dest + mov ecx, count + shr ecx, 6 // 64 bytes per iteration + movq mm1, dat // Read in source data + movq mm2, mm1 + movq mm3, mm1 + movq mm4, mm1 + movq mm5, mm1 + movq mm6, mm1 + movq mm7, mm1 + movq mm0, mm1 +loop1: + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add edi, 64 + dec ecx + jnz loop1 + } +#else +/* +dat constrained in memory +*/ + __asm__ __volatile__ ( + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 6 \n\t"// 64 bytes per iteration +//"movq %%mm1, dat \n\t"// Read in source data +"movq %%mm1, %0 \n\t" +"movq %%mm2, %%mm1 \n\t" +"movq %%mm3, %%mm1 \n\t" +"movq %%mm4, %%mm1 \n\t" +"movq %%mm5, %%mm1 \n\t" +"movq %%mm6, %%mm1 \n\t" +"movq %%mm7, %%mm1 \n\t" +"movq %%mm0, %%mm1 \n\t" +"loop1_3: \n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loop1_3 \n\t" +: +: "m" (dat), "D" (dest), "c" (count) + ); +#endif + dest += ( count & ~63 ); + count &= 63; + } + + if ( count >= 8 ) { +#ifdef _MSC_VER + __asm { + mov edi, dest + mov ecx, count + shr ecx, 3 // 8 bytes per iteration + movq mm1, dat // Read in source data +loop2: + movntq 0[EDI], mm1 // Non-temporal stores + + add edi, 8 + dec ecx + jnz loop2 + } +#else +/* +dat constrained in memory +*/ + __asm__ __volatile__ ( + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 3 \n\t"// 8 bytes per iteration +//"movq %%mm1, dat \n\t"// Read in source data +"movq %%mm1, %0 \n\t"// Read in source data +"loop2: \n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"\n\t" +"add %%edi, 8 \n\t" +"dec %%ecx \n\t" +"jnz loop2 \n\t" +: +: "m" (dat), "D" (dest), "c" (count) + ); +#endif + dest += (count & ~7); + count &= 7; + } + + while( count > 0 ) { + *dest = val; + dest++; + count--; + } + + EMMS_INSTRUCTION +#endif // _WIN32 +} + +#endif diff --git a/source/idlib/math/Simd_MMX.h b/source/idlib/math/Simd_MMX.h new file mode 100644 index 0000000..a007611 --- /dev/null +++ b/source/idlib/math/Simd_MMX.h @@ -0,0 +1,24 @@ + +#ifndef __MATH_SIMD_MMX_H__ +#define __MATH_SIMD_MMX_H__ + +/* +=============================================================================== + + MMX implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_MMX : public idSIMD_Generic { +#if defined( _WIN32 ) || defined( __linux__ ) +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); + virtual void VPCALL Memset( void *dst, const int val, const int count ); + +#endif +}; + +#endif /* !__MATH_SIMD_MMX_H__ */ diff --git a/source/idlib/math/Simd_SSE.cpp b/source/idlib/math/Simd_SSE.cpp new file mode 100644 index 0000000..8ef274b --- /dev/null +++ b/source/idlib/math/Simd_SSE.cpp @@ -0,0 +1,19801 @@ +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_InstructionMacros.h" + + +//=============================================================== +// M +// SSE implementation of idSIMDProcessor MrE +// E +//=============================================================== + +#if defined( _WINDOWS ) + +// with alignment +#define KFLOATINITS( SRC0, COUNT, PRE, POST ) KFLOATINITDSS( SRC0,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITD( DST, COUNT, PRE, POST ) KFLOATINITDSS( DST,DST,DST,COUNT,PRE,POST ) +#define KFLOATINITDS( DST, SRC0, COUNT, PRE, POST ) KFLOATINITDSS( DST,SRC0,SRC0,COUNT,PRE,POST ) + +#define KFLOATINITDSS( DST, SRC0, SRC1, COUNT, PRE, POST )\ + __asm mov ecx,DST \ + __asm shr ecx,2 \ + __asm mov ebx,COUNT \ + __asm neg ecx \ + __asm mov edx,SRC0 \ + __asm and ecx,3 \ + __asm mov esi,SRC1 \ + __asm sub ebx,ecx \ + __asm jge noUnderFlow \ + __asm xor ebx,ebx \ + __asm mov ecx,COUNT \ + __asm noUnderFlow: \ + __asm mov PRE,ecx \ + __asm mov eax,ebx \ + __asm mov edi,DST \ + __asm and eax,8-1 \ + __asm mov POST,eax \ + __asm and ebx,0xfffffff8 \ + __asm jle done \ + __asm shl ebx,2 \ + __asm lea ecx,[ecx*4+ebx] \ + __asm neg ebx \ + __asm add edx,ecx \ + __asm add esi,ecx \ + __asm add edi,ecx \ + __asm mov eax,edx \ + __asm or eax,esi + +// without alignment (pre==0) +#define KFLOATINITS_NA( SRC0, COUNT, PRE, POST ) KFLOATINITDSS_NA( SRC0,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITD_NA( DST, COUNT, PRE, POST ) KFLOATINITDSS_NA( DST,DST,DST,COUNT,PRE,POST ) +#define KFLOATINITDS_NA( DST, SRC0, COUNT, PRE, POST ) KFLOATINITDSS_NA( DST,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITDSS_NA( DST, SRC0, SRC1, COUNT, PRE, POST )\ + __asm mov eax,COUNT \ + __asm mov PRE,0 \ + __asm and eax,8-1 \ + __asm mov ebx,COUNT \ + __asm mov POST,eax \ + __asm and ebx,0xfffffff8 \ + __asm je done \ + __asm shl ebx,2 \ + __asm mov edx,SRC0 \ + __asm mov esi,SRC1 \ + __asm mov edi,DST \ + __asm add edx,ebx \ + __asm add esi,ebx \ + __asm add edi,ebx \ + __asm mov eax,edx \ + __asm or eax,esi \ + __asm or eax,edi \ + __asm neg ebx \ + +/* + when OPER is called: + edx = s0 + esi = s1 + edi = d + ebx = index*4 + + xmm0 & xmm1 must not be trashed +*/ +#define KMOVDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm movss DST,xmm2 +#define KMOVDS4( DST, SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm movups DST,xmm2 +#define KMINDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm minss DST,xmm2 +#define KMAXDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm maxss DST,xmm2 + +// general ALU operation +#define KALUDSS1( OP, DST, SRC0, SRC1 ) \ + __asm movss xmm2,SRC0 \ + __asm OP##ss xmm2,SRC1 \ + __asm movss DST,xmm2 +#define KALUDSS4( OP, DST, SRC0, SRC1 ) \ + __asm movups xmm2,SRC0 \ + __asm movups xmm3,SRC1 \ + __asm OP##ps xmm2,xmm3 \ + __asm movups DST,xmm2 + +#define KADDDSS1( DST, SRC0, SRC1 ) KALUDSS1( add, DST,SRC0,SRC1 ) +#define KADDDSS4( DST, SRC0, SRC1 ) KALUDSS4( add, DST,SRC0,SRC1 ) +#define KSUBDSS1( DST, SRC0, SRC1 ) KALUDSS1( sub, DST,SRC0,SRC1 ) +#define KSUBDSS4( DST, SRC0, SRC1 ) KALUDSS4( sub, DST,SRC0,SRC1 ) +#define KMULDSS1( DST, SRC0, SRC1 ) KALUDSS1( mul, DST,SRC0,SRC1 ) +#define KMULDSS4( DST, SRC0, SRC1 ) KALUDSS4( mul, DST,SRC0,SRC1 ) + +#define KDIVDSS1( DST, SRC0, SRC1 ) \ + __asm movss xmm2,SRC1 \ + __asm rcpss xmm3,xmm2 \ + __asm mulss xmm2,xmm3 \ + __asm mulss xmm2,xmm3 \ + __asm addss xmm3,xmm3 \ + __asm subss xmm3,xmm2 \ + __asm mulss xmm3,SRC0 \ + __asm movss DST,xmm3 +#define KDIVDSS4( DST, SRC0, SRC1 ) \ + __asm movups xmm2,SRC1 \ + __asm rcpps xmm3,xmm2 \ + __asm mulps xmm2,xmm3 \ + __asm mulps xmm2,xmm3 \ + __asm addps xmm3,xmm3 \ + __asm subps xmm3,xmm2 \ + __asm movups xmm2,SRC0 \ + __asm mulps xmm3,xmm2 \ + __asm movups DST,xmm3 +#define KF2IDS1( SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm cvttps2pi mm2,xmm2 \ + __asm movd [edi+ebx],mm2 +#define KF2IDS4( SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm cvttps2pi mm2,xmm2 \ + __asm movq [edi+ebx+0],mm2 \ + __asm shufps xmm2,xmm2,SHUFFLE_PS(1,0,3,2) \ + __asm cvttps2pi mm2,xmm2 \ + __asm movq [edi+ebx+8],mm2 +#define KISQRTDS1( DST,SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm rsqrtss xmm3,xmm2 \ + __asm mulss xmm2,xmm3 \ + __asm mulss xmm2,xmm3 \ + __asm subss xmm2,xmm1 \ + __asm mulss xmm3,xmm0 \ + __asm mulss xmm3,xmm2 \ + __asm movss DST,xmm3 +#define KISQRTDS4( DST,SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm rsqrtps xmm3,xmm2 \ + __asm mulps xmm2,xmm3 \ + __asm mulps xmm2,xmm3 \ + __asm subps xmm2,xmm1 \ + __asm mulps xmm3,xmm0 \ + __asm mulps xmm3,xmm2 \ + __asm movups DST,xmm3 + +// this is used in vector4 implementation to shift constant V4 +#define KANDREGDSV( DST, SRC0, VALUE ) \ + __asm mov DST,SRC0 \ + __asm and DST,VALUE + +// this is used in vector4 code to operate with float arrays as sources +#define KEXPANDFLOAT( DST, SRC ) \ + __asm movss DST,SRC \ + __asm shufps DST,DST,0 + +#define KADDDS1( DST,SRC ) KADDDSS1( DST,DST,SRC ) +#define KADDDS4( DST,SRC ) KADDDSS4( DST,DST,SRC ) +#define KSUBDS1( DST,SRC ) KSUBDSS1( DST,DST,SRC ) +#define KSUBDS4( DST,SRC ) KSUBDSS4( DST,DST,SRC ) +#define KMULDS1( DST,SRC ) KMULDSS1( DST,DST,SRC ) +#define KMULDS4( DST,SRC ) KMULDSS4( DST,DST,SRC ) +#define KDIVDS1( DST,SRC ) KDIVDSS1( DST,DST,SRC ) +#define KDIVDS4( DST,SRC ) KDIVDSS4( DST,DST,SRC ) + +// handles pre & post leftovers +#define KFLOATOPER( OPER, OPER4, COUNT ) \ + __asm mov ecx,pre \ + __asm mov ebx,COUNT \ + __asm cmp ebx,ecx \ + __asm cmovl ecx,COUNT \ + __asm test ecx,ecx \ + __asm je preDone \ + __asm xor ebx,ebx \ + __asm lpPre: \ + OPER \ + __asm add ebx,4 \ + __asm dec ecx \ + __asm jg lpPre \ + __asm preDone: \ + __asm mov ecx,post \ + __asm mov ebx,COUNT \ + __asm sub ebx,ecx \ + __asm shl ebx,2 \ + __asm cmp ecx,4 \ + __asm jl post4Done \ + OPER4 \ + __asm sub ecx,4 \ + __asm add ebx,4*4 \ + __asm post4Done: \ + __asm test ecx,ecx \ + __asm je postDone \ + __asm lpPost: \ + OPER \ + __asm add ebx,4 \ + __asm dec ecx \ + __asm jg lpPost \ + __asm postDone: + +// operate on a constant and a float array +#define KFLOAT_CA( ALUOP, DST, SRC, CONSTANT, COUNT ) \ + int pre,post; \ + __asm movss xmm0,CONSTANT \ + __asm shufps xmm0,xmm0,0 \ + KFLOATINITDS( DST, SRC, COUNT, pre, post ) \ + __asm and eax,15 \ + __asm jne lpNA \ + __asm jmp lpA \ + __asm align 16 \ + __asm lpA: \ + __asm prefetchnta [edx+ebx+64] \ + __asm movaps xmm1,xmm0 \ + __asm movaps xmm2,xmm0 \ + __asm ALUOP##ps xmm1,[edx+ebx] \ + __asm ALUOP##ps xmm2,[edx+ebx+16] \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpA \ + __asm jmp done \ + __asm align 16 \ + __asm lpNA: \ + __asm prefetchnta [edx+ebx+64] \ + __asm movaps xmm1,xmm0 \ + __asm movaps xmm2,xmm0 \ + __asm movups xmm3,[edx+ebx] \ + __asm movups xmm4,[edx+ebx+16] \ + __asm ALUOP##ps xmm1,xmm3 \ + __asm ALUOP##ps xmm2,xmm4 \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpNA \ + __asm done: \ + __asm mov edx,SRC \ + __asm mov edi,DST \ + __asm KFLOATOPER( KALUDSS1( ALUOP, [edi+ebx],xmm0,[edx+ebx] ), \ + __asm KALUDSS4( ALUOP, [edi+ebx],xmm0,[edx+ebx] ), COUNT ) + +// operate on two float arrays +#define KFLOAT_AA( ALUOP, DST, SRC0, SRC1, COUNT ) \ + int pre,post; \ + KFLOATINITDSS( DST, SRC0, SRC1, COUNT, pre, post ) \ + __asm and eax,15 \ + __asm jne lpNA \ + __asm jmp lpA \ + __asm align 16 \ + __asm lpA: \ + __asm movaps xmm1,[edx+ebx] \ + __asm movaps xmm2,[edx+ebx+16] \ + __asm ALUOP##ps xmm1,[esi+ebx] \ + __asm ALUOP##ps xmm2,[esi+ebx+16] \ + __asm prefetchnta [edx+ebx+64] \ + __asm prefetchnta [esi+ebx+64] \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpA \ + __asm jmp done \ + __asm align 16 \ + __asm lpNA: \ + __asm movups xmm1,[edx+ebx] \ + __asm movups xmm2,[edx+ebx+16] \ + __asm movups xmm3,[esi+ebx] \ + __asm movups xmm4,[esi+ebx+16] \ + __asm prefetchnta [edx+ebx+64] \ + __asm prefetchnta [esi+ebx+64] \ + __asm ALUOP##ps xmm1,xmm3 \ + __asm ALUOP##ps xmm2,xmm4 \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpNA \ + __asm done: \ + __asm mov edx,SRC0 \ + __asm mov esi,SRC1 \ + __asm mov edi,DST \ + KFLOATOPER( KALUDSS1( ALUOP, [edi+ebx],[edx+ebx],[esi+ebx] ), \ + KALUDSS4( ALUOP, [edi+ebx],[edx+ebx],[esi+ebx] ), COUNT ) + + +ALIGN8_INIT1( unsigned short SIMD_W_zero, 0 ); +ALIGN8_INIT1( unsigned short SIMD_W_maxShort, 1<<15 ); + +ALIGN4_INIT4( unsigned long SIMD_SP_firstSignBit, (unsigned long) ( 1 << 31 ), 0, 0, 0 ); +ALIGN4_INIT1( unsigned long SIMD_SP_signBit, (unsigned long) ( 1 << 31 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_absMask, (unsigned long) ~( 1 << 31 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_infinityMask, (unsigned long) ~( 1 << 23 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_not, 0xFFFFFFFF ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearLast, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearFirstThree, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF ); + +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle0, (3<<0)|(2<<8)|(1<<16)|(0<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle1, (0<<0)|(1<<8)|(2<<16)|(3<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle2, (1<<0)|(0<<8)|(3<<16)|(2<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle3, (2<<0)|(3<<8)|(0<<16)|(1<<24) ); + +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x1, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x2, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); + +ALIGN4_INIT1( float SIMD_SP_zero, 0.0f ); +ALIGN4_INIT1( float SIMD_SP_half, 0.5f ); +ALIGN4_INIT1( float SIMD_SP_one, 1.0f ); +ALIGN4_INIT1( float SIMD_SP_two, 2.0f ); +ALIGN4_INIT1( float SIMD_SP_three, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_four, 4.0f ); +ALIGN4_INIT1( float SIMD_SP_maxShort, (1<<15) ); +ALIGN4_INIT1( float SIMD_SP_tiny, 1e-10f ); +ALIGN4_INIT1( float SIMD_SP_PI, idMath::PI ); +ALIGN4_INIT1( float SIMD_SP_halfPI, idMath::HALF_PI ); +ALIGN4_INIT1( float SIMD_SP_twoPI, idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_oneOverTwoPI, 1.0f / idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); +ALIGN4_INIT4( float SIMD_SP_lastOne, 0.0f, 0.0f, 0.0f, 1.0f ); + +ALIGN4_INIT1( float SIMD_SP_rsqrt_c0, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_rsqrt_c1, -0.5f ); +ALIGN4_INIT1( float SIMD_SP_mat2quat_rsqrt_c1, -0.5f*0.5f ); + +ALIGN4_INIT1( float SIMD_SP_sin_c0, -2.39e-08f ); +ALIGN4_INIT1( float SIMD_SP_sin_c1, 2.7526e-06f ); +ALIGN4_INIT1( float SIMD_SP_sin_c2, -1.98409e-04f ); +ALIGN4_INIT1( float SIMD_SP_sin_c3, 8.3333315e-03f ); +ALIGN4_INIT1( float SIMD_SP_sin_c4, -1.666666664e-01f ); + +ALIGN4_INIT1( float SIMD_SP_cos_c0, -2.605e-07f ); +ALIGN4_INIT1( float SIMD_SP_cos_c1, 2.47609e-05f ); +ALIGN4_INIT1( float SIMD_SP_cos_c2, -1.3888397e-03f ); +ALIGN4_INIT1( float SIMD_SP_cos_c3, 4.16666418e-02f ); +ALIGN4_INIT1( float SIMD_SP_cos_c4, -4.999999963e-01f ); + +ALIGN4_INIT1( float SIMD_SP_atan_c0, 0.0028662257f ); +ALIGN4_INIT1( float SIMD_SP_atan_c1, -0.0161657367f ); +ALIGN4_INIT1( float SIMD_SP_atan_c2, 0.0429096138f ); +ALIGN4_INIT1( float SIMD_SP_atan_c3, -0.0752896400f ); +ALIGN4_INIT1( float SIMD_SP_atan_c4, 0.1065626393f ); +ALIGN4_INIT1( float SIMD_SP_atan_c5, -0.1420889944f ); +ALIGN4_INIT1( float SIMD_SP_atan_c6, 0.1999355085f ); +ALIGN4_INIT1( float SIMD_SP_atan_c7, -0.3333314528f ); + +/* +============ +SSE_InvSqrt +============ +*/ +float SSE_InvSqrt( float x ) { + float y; + + __asm { + movss xmm0, x + rsqrtss xmm1, xmm0 + mulss xmm0, xmm1 + mulss xmm0, xmm1 + subss xmm0, SIMD_SP_rsqrt_c0 + mulss xmm1, SIMD_SP_rsqrt_c1 + mulss xmm0, xmm1 + movss y, xmm0 + } + return y; +} + +/* +============ +SSE_InvSqrt4 +============ +*/ +void SSE_InvSqrt4( float x[4] ) { + __asm { + mov edi, x + movaps xmm0, [edi] + rsqrtps xmm1, xmm0 + mulps xmm0, xmm1 + mulps xmm0, xmm1 + subps xmm0, SIMD_SP_rsqrt_c0 + mulps xmm1, SIMD_SP_rsqrt_c1 + mulps xmm0, xmm1 + movaps [edi], xmm0 + } +} + +/* +============ +SSE_SinZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +float SSE_SinZeroHalfPI( float a ) { +#if 1 + + float t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + __asm { + movss xmm0, a + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm2, SIMD_SP_sin_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + movss t, xmm2 + } + + return t; + +#else + + float s, t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + s = a * a; + t = -2.39e-08f; + t *= s; + t += 2.7526e-06f; + t *= s; + t += -1.98409e-04f; + t *= s; + t += 8.3333315e-03f; + t *= s; + t += -1.666666664e-01f; + t *= s; + t += 1.0f; + t *= a; + + return t; + +#endif +} + +/* +============ +SSE_Sin4ZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +void SSE_Sin4ZeroHalfPI( float a[4], float s[4] ) { + __asm { + mov edi, a + mov esi, s + movaps xmm0, [edi] + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm2, SIMD_SP_sin_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + movaps [esi], xmm2 + } +} + +/* +============ +SSE_Sin +============ +*/ +float SSE_Sin( float a ) { +#if 1 + + float t; + + __asm { + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm2, SIMD_SP_sin_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + movss t, xmm2 + } + + return t; + +#else + + float s, t; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } + + a = idMath::PI - a; + if ( fabs( a ) >= idMath::HALF_PI ) { + a = ( ( a < 0.0f ) ? -idMath::PI : idMath::PI ) - a; + } + + s = a * a; + t = -2.39e-08f; + t *= s; + t += 2.7526e-06f; + t *= s; + t += -1.98409e-04f; + t *= s; + t += 8.3333315e-03f; + t *= s; + t += -1.666666664e-01f; + t *= s; + t += 1.0f; + t *= a; + + return t; + +#endif +} + +/* +============ +SSE_Sin4 +============ +*/ +void SSE_Sin4( float a[4], float s[4] ) { + __asm { + mov edi, a + mov esi, s + movaps xmm1, [edi] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm2, SIMD_SP_sin_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + movaps [esi], xmm2 + } +} + +/* +============ +SSE_CosZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +float SSE_CosZeroHalfPI( float a ) { +#if 1 + + float t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + __asm { + movss xmm0, a + mulss xmm0, xmm0 + movss xmm1, SIMD_SP_cos_c0 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c1 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c2 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c3 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c4 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_one + movss t, xmm1 + } + + return t; + +#else + + float s, t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + s = a * a; + t = -2.605e-07f; + t *= s; + t += 2.47609e-05f; + t *= s; + t += -1.3888397e-03f; + t *= s; + t += 4.16666418e-02f; + t *= s; + t += -4.999999963e-01f; + t *= s; + t += 1.0f; + + return t; + +#endif +} + +/* +============ +SSE_Cos4ZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +void SSE_Cos4ZeroHalfPI( float a[4], float c[4] ) { + __asm { + mov edi, a + mov esi, c + movaps xmm0, [edi] + mulps xmm0, xmm0 + movaps xmm1, SIMD_SP_cos_c0 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c1 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c2 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c3 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c4 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_one + movaps [esi], xmm2 + } +} + +/* +============ +SSE_Cos +============ +*/ +float SSE_Cos( float a ) { +#if 1 + + float t; + + __asm { + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + mulss xmm0, xmm0 + movss xmm1, SIMD_SP_cos_c0 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c1 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c2 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c3 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c4 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_one + xorps xmm2, SIMD_SP_signBit + xorps xmm1, xmm2 + movss t, xmm1 + } + + return t; + +#else + + float s, t; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } + + a = idMath::PI - a; + if ( fabs( a ) >= idMath::HALF_PI ) { + a = ( ( a < 0.0f ) ? -idMath::PI : idMath::PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } + + s = a * a; + t = -2.605e-07f; + t *= s; + t += 2.47609e-05f; + t *= s; + t += -1.3888397e-03f; + t *= s; + t += 4.16666418e-02f; + t *= s; + t += -4.999999963e-01f; + t *= s; + t += 1.0f; + t *= d; + + return t; + +#endif +} + +/* +============ +SSE_Cos4 +============ +*/ +void SSE_Cos4( float a[4], float c[4] ) { + __asm { + mov edi, a + mov esi, c + movaps xmm1, [edi] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + mulps xmm0, xmm0 + movaps xmm1, SIMD_SP_cos_c0 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c1 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c2 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c3 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c4 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_one + xorps xmm2, SIMD_SP_signBit + xorps xmm1, xmm2 + movaps [esi], xmm1 + } +} + +/* +============ +SSE_SinCos +============ +*/ +void SSE_SinCos( float a, float &s, float &c ) { + __asm { + mov edi, s + mov esi, c + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm3, SIMD_SP_sin_c0 + movss xmm4, SIMD_SP_cos_c0 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c1 + addss xmm4, SIMD_SP_cos_c1 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c2 + addss xmm4, SIMD_SP_cos_c2 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c3 + addss xmm4, SIMD_SP_cos_c3 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c4 + addss xmm4, SIMD_SP_cos_c4 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_one + addss xmm4, SIMD_SP_one + mulss xmm3, xmm0 + xorps xmm2, SIMD_SP_signBit + xorps xmm4, xmm2 + movss [edi], xmm2 + movss [esi], xmm3 + } +} + +/* +============ +SSE_SinCos4 +============ +*/ +void SSE_SinCos4( float a[4], float s[4], float c[4] ) { + __asm { + mov eax, a + mov edi, s + mov esi, c + movaps xmm1, [eax] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movaps xmm0, [eax] + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm3, SIMD_SP_sin_c0 + movaps xmm4, SIMD_SP_cos_c0 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c1 + addps xmm4, SIMD_SP_cos_c1 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c2 + addps xmm4, SIMD_SP_cos_c2 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c3 + addps xmm4, SIMD_SP_cos_c3 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c4 + addps xmm4, SIMD_SP_cos_c4 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_one + addps xmm4, SIMD_SP_one + mulps xmm3, xmm0 + xorps xmm2, SIMD_SP_signBit + xorps xmm4, xmm2 + movaps [edi], xmm3 + movaps [esi], xmm4 + } +} + +/* +============ +SSE_ATanPositive + + Both 'x' and 'y' must be positive. +============ +*/ +float SSE_ATanPositive( float y, float x ) { +#if 1 + + float t; + + assert( y >= 0.0f && x >= 0.0f ); + + __asm { + movss xmm0, x + movss xmm3, xmm0 + movss xmm1, y + minss xmm0, xmm1 + maxss xmm1, xmm3 + cmpeqss xmm3, xmm0 + rcpss xmm2, xmm1 + mulss xmm1, xmm2 + mulss xmm1, xmm2 + addss xmm2, xmm2 + subss xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulss xmm0, xmm2 // xmm0 = x / y or y / x + movss xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + andps xmm3, SIMD_SP_halfPI // xmm3 = HALF_PI or 0.0f + movss xmm1, xmm0 + mulss xmm1, xmm1 // xmm1 = s + movss xmm2, SIMD_SP_atan_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c5 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c6 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c7 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + addss xmm2, xmm3 + movss t, xmm2 + } + + return t; + +#else + + float a, d, s, t; + + assert( y >= 0.0f && x >= 0.0f ); + + if ( y > x ) { + a = -x / y; + d = idMath::HALF_PI; + } else { + a = y / x; + d = 0.0f; + } + s = a * a; + t = 0.0028662257f; + t *= s; + t += -0.0161657367f; + t *= s; + t += 0.0429096138f; + t *= s; + t += -0.0752896400f; + t *= s; + t += 0.1065626393f; + t *= s; + t += -0.1420889944f; + t *= s; + t += 0.1999355085f; + t *= s; + t += -0.3333314528f; + t *= s; + t += 1.0f; + t *= a; + t += d; + + return t; + +#endif +} + +/* +============ +SSE_ATan4Positive + + Both 'x' and 'y' must be positive. +============ +*/ +void SSE_ATan4Positive( float y[4], float x[4], float at[4] ) { + __asm { + mov esi, x + mov edi, y + mov edx, at + movaps xmm0, [esi] + movaps xmm3, xmm0 + movaps xmm1, [edi] + minps xmm0, xmm1 + maxps xmm1, xmm3 + cmpeqps xmm3, xmm0 + rcpps xmm2, xmm1 + mulps xmm1, xmm2 + mulps xmm1, xmm2 + addps xmm2, xmm2 + subps xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulps xmm0, xmm2 // xmm0 = x / y or y / x + movaps xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + andps xmm3, SIMD_SP_halfPI // xmm3 = HALF_PI or 0.0f + movaps xmm1, xmm0 + mulps xmm1, xmm1 // xmm1 = s + movaps xmm2, SIMD_SP_atan_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c5 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c6 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c7 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + addps xmm2, xmm3 + movaps [edx], xmm2 + } +} + +/* +============ +SSE_ATan +============ +*/ +float SSE_ATan( float y, float x ) { +#if 1 + + float t; + + __asm { + movss xmm0, x + movss xmm3, xmm0 + movss xmm4, xmm0 + andps xmm0, SIMD_SP_absMask + movss xmm1, y + xorps xmm4, xmm1 + andps xmm1, SIMD_SP_absMask + andps xmm4, SIMD_SP_signBit + minss xmm0, xmm1 + maxss xmm1, xmm3 + cmpeqss xmm3, xmm0 + rcpss xmm2, xmm1 + mulss xmm1, xmm2 + mulss xmm1, xmm2 + addss xmm2, xmm2 + subss xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulss xmm0, xmm2 // xmm0 = x / y or y / x + xorps xmm0, xmm4 + movss xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + orps xmm4, SIMD_SP_halfPI // xmm4 = +/- HALF_PI + andps xmm3, xmm4 // xmm3 = +/- HALF_PI or 0.0f + movss xmm1, xmm0 + mulss xmm1, xmm1 // xmm1 = s + movss xmm2, SIMD_SP_atan_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c5 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c6 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c7 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + addss xmm2, xmm3 + movss t, xmm2 + } + + return t; + +#else + + float a, d, s, t; + + if ( fabs( y ) > fabs( x ) ) { + a = -x / y; + d = idMath::HALF_PI; + *((unsigned long *)&d) ^= ( *((unsigned long *)&x) ^ *((unsigned long *)&y) ) & (1<<31); + } else { + a = y / x; + d = 0.0f; + } + + s = a * a; + t = 0.0028662257f; + t *= s; + t += -0.0161657367f; + t *= s; + t += 0.0429096138f; + t *= s; + t += -0.0752896400f; + t *= s; + t += 0.1065626393f; + t *= s; + t += -0.1420889944f; + t *= s; + t += 0.1999355085f; + t *= s; + t += -0.3333314528f; + t *= s; + t += 1.0f; + t *= a; + t += d; + + return t; + +#endif +} + +/* +============ +SSE_ATan4 +============ +*/ +void SSE_ATan4( float y[4], float x[4], float at[4] ) { + __asm { + mov esi, x + mov edi, y + mov edx, at + movaps xmm0, [esi] + movaps xmm3, xmm0 + movaps xmm4, xmm0 + andps xmm0, SIMD_SP_absMask + movaps xmm1, [edi] + xorps xmm4, xmm1 + andps xmm1, SIMD_SP_absMask + andps xmm4, SIMD_SP_signBit + minps xmm0, xmm1 + maxps xmm1, xmm3 + cmpeqps xmm3, xmm0 + rcpps xmm2, xmm1 + mulps xmm1, xmm2 + mulps xmm1, xmm2 + addps xmm2, xmm2 + subps xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulps xmm0, xmm2 // xmm0 = x / y or y / x + xorps xmm0, xmm4 + movaps xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + orps xmm4, SIMD_SP_halfPI // xmm4 = +/- HALF_PI + andps xmm3, xmm4 // xmm3 = +/- HALF_PI or 0.0f + movaps xmm1, xmm0 + mulps xmm1, xmm1 // xmm1 = s + movaps xmm2, SIMD_SP_atan_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c5 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c6 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c7 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + addps xmm2, xmm3 + movaps [edx], xmm2 + } +} + +/* +============ +SSE_TestTrigonometry +============ +*/ +void SSE_TestTrigonometry( void ) { + int i; + float a, s1, s2, c1, c2; + + for ( i = 0; i < 100; i++ ) { + a = i * idMath::HALF_PI / 100.0f; + + s1 = idMath::Sin( a ); + s2 = SSE_SinZeroHalfPI( a ); + + if ( fabs( s1 - s2 ) > 1e-7f ) { + assert( 0 ); + } + + c1 = idMath::Cos( a ); + c2 = SSE_CosZeroHalfPI( a ); + + if ( fabs( c1 - c2 ) > 1e-7f ) { + assert( 0 ); + } + } + + for ( i = -200; i < 200; i++ ) { + a = i * idMath::TWO_PI / 100.0f; + + s1 = idMath::Sin( a ); + s2 = SSE_Sin( a ); + + if ( fabs( s1 - s2 ) > 1e-6f ) { + assert( 0 ); + } + + c1 = idMath::Cos( a ); + c2 = SSE_Cos( a ); + + if ( fabs( c1 - c2 ) > 1e-6f ) { + assert( 0 ); + } + + SSE_SinCos( a, s2, c2 ); + if ( fabs( s1 - s2 ) > 1e-6f || fabs( c1 - c2 ) > 1e-6f ) { + assert( 0 ); + } + } +} + +/* +============ +idSIMD_SSE::GetName +============ +*/ +const char * idSIMD_SSE::GetName( void ) const { + return "MMX & SSE"; +} + +/* +============ +idSIMD_SSE::Add + + dst[i] = constant + src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Add( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( add, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Add + + dst[i] = src0[i] + src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Add( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( add, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Sub + + dst[i] = constant - src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Sub( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( sub, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Sub + + dst[i] = src0[i] - src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Sub( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( sub, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Mul + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Mul( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( mul, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Mul + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Mul( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( mul, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Div + + dst[i] = constant / src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Div( float *dst, const float constant, const float *src, const int count ) { + int pre, post; + + // 1 / x = 2 * rcpps(x) - (x * rcpps(x) * rcpps(x)); + __asm + { + movss xmm1,constant + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [edx+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,xmm1 + mulps xmm5,xmm1 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpA + jmp done + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [edx+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,xmm1 + mulps xmm5,xmm1 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpNA +done: + mov edx,src + mov edi,dst + KFLOATOPER( KDIVDSS1( [edi+ebx],xmm1,[edx+ebx] ), + KDIVDSS4( [edi+ebx],xmm1,[edx+ebx] ), count ) + } +} + +/* +============ +idSIMD_SSE::Div + + dst[i] = src0[i] / src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Div( float *dst, const float *src0, const float *src1, const int count ) { + int pre,post; + + // 1 / x = 2 * rcpps(x) - (x * rcpps(x) * rcpps(x)); + __asm + { + KFLOATINITDSS( dst, src0, src1, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[esi+ebx] + movaps xmm3,[esi+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [esi+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,[edx+ebx] + mulps xmm5,[edx+ebx+16] + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpA + jmp done + align 16 +lpNA: + movups xmm2,[esi+ebx] + movups xmm3,[esi+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [esi+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + mulps xmm4,xmm2 + mulps xmm5,xmm3 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpNA +done: + mov edx,src0 + mov esi,src1 + mov edi,dst + KFLOATOPER( KDIVDSS1( [edi+ebx],[edx+ebx],[esi+ebx] ), + KDIVDSS4( [edi+ebx],[edx+ebx],[esi+ebx] ), count ) + } +} +/* +============ +Simd_MulAdd + + assumes count >= 7 +============ +*/ +static void Simd_MulAdd( float *dst, const float constant, const float *src, const int count ) { + __asm mov esi, dst + __asm mov edi, src + __asm mov eax, count + __asm shl eax, 2 + __asm mov ecx, esi + __asm mov edx, eax + __asm or ecx, edi + __asm fld constant + __asm and ecx, 15 + __asm jz SimdMulAdd16 + __asm and ecx, 3 + __asm jnz SimdMulAdd8 + __asm mov ecx, esi + __asm xor ecx, edi + __asm and ecx, 15 + __asm jnz MulAdd8 + __asm mov ecx, esi + __asm and ecx, 15 + __asm neg ecx + __asm add ecx, 16 + __asm sub eax, ecx + __asm add edi, ecx + __asm add esi, ecx + __asm neg ecx + __asm mov edx, eax + __asm loopPreMulAdd16: + __asm fld st + __asm fmul dword ptr [edi+ecx] + __asm fadd dword ptr [esi+ecx] + __asm fstp dword ptr [esi+ecx] + __asm add ecx, 4 + __asm jl loopPreMulAdd16 + __asm SimdMulAdd16: + __asm and eax, ~15 + __asm movss xmm1, constant + __asm shufps xmm1, xmm1, 0x00 + __asm add esi, eax + __asm add edi, eax + __asm neg eax + __asm align 16 + __asm loopMulAdd16: + __asm movaps xmm0, [edi+eax] + __asm mulps xmm0, xmm1 + __asm addps xmm0, [esi+eax] + __asm movaps [esi+eax], xmm0 + __asm add eax, 16 + __asm jl loopMulAdd16 + __asm jmp postMulAdd + __asm MulAdd8: + __asm mov ecx, esi + __asm and ecx, 7 + __asm jz SimdMulAdd8 + __asm sub eax, ecx + __asm add esi, ecx + __asm add edi, ecx + __asm neg ecx + __asm mov edx, eax + __asm loopPreMulAdd8: + __asm fld st + __asm fmul dword ptr [edi+ecx] + __asm fadd dword ptr [esi+ecx] + __asm fstp dword ptr [esi+ecx] + __asm add ecx, 4 + __asm jl loopPreMulAdd8 + __asm SimdMulAdd8: + __asm and eax, ~15 + __asm movss xmm1, constant + __asm shufps xmm1, xmm1, 0x00 + __asm add esi, eax + __asm add edi, eax + __asm neg eax + __asm align 16 + __asm loopMulAdd8: + __asm movlps xmm0, [edi+eax] + __asm movhps xmm0, [edi+eax+8] + __asm mulps xmm0, xmm1 + __asm movlps xmm2, [esi+eax] + __asm movhps xmm2, [esi+eax+8] + __asm addps xmm0, xmm2 + __asm movlps [esi+eax], xmm0 + __asm movhps [esi+eax+8], xmm0 + __asm add eax, 16 + __asm jl loopMulAdd8 + __asm jmp postMulAdd + __asm postMulAdd: + __asm and edx, 15 + __asm jz MulAddDone + __asm add esi, edx + __asm add edi, edx + __asm neg edx + __asm loopPostMulAdd: + __asm fld st + __asm fmul dword ptr [edi+edx] + __asm fadd dword ptr [esi+edx] + __asm fstp dword ptr [esi+edx] + __asm add edx, 4 + __asm jl loopPostMulAdd + __asm MulAddDone: + __asm fstp st +} + +#define MULADD_FEW( OPER ) \ +switch( count ) { \ + case 0: \ + return; \ + case 1: \ + dst[0] OPER c * src[0]; \ + return; \ + case 2: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; \ + return; \ + case 3: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; \ + return; \ + case 4: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + return; \ + case 5: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; \ + return; \ + case 6: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; \ + return; \ + case 7: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; \ + return; \ + case 8: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + return; \ + case 9: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; \ + return; \ + case 10: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; dst[9] OPER c * src[9]; \ + return; \ + case 11: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; dst[9] OPER c * src[9]; dst[10] OPER c * src[10]; \ + return; \ +} + +/* +============ +idSIMD_SSE::MulAdd + + dst[i] += constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulAdd( float *dst, const float constant, const float *src, const int count ) { + float c = constant; + MULADD_FEW( += ) + Simd_MulAdd( dst, constant, src, count ); +} + +/* +============ +idSIMD_SSE::MulAdd + + dst[i] += src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulAdd( float *dst, const float *src0, const float *src1, const int count ) { + for ( int i = 0; i < count; i++ ) { + dst[i] += src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_SSE::MulSub + + dst[i] -= constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulSub( float *dst, const float constant, const float *src, const int count ) { + float c = constant; + MULADD_FEW( -= ) + Simd_MulAdd( dst, -constant, src, count ); +} + +/* +============ +idSIMD_SSE::MulSub + + dst[i] -= src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulSub( float *dst, const float *src0, const float *src1, const int count ) { + for ( int i = 0; i < count; i++ ) { + dst[i] -= src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) { + __asm + { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: + movlps xmm1, [esi+eax+ 0] + movlps xmm2, [esi+eax+ 8] + movlps xmm3, [esi+eax+16] + movhps xmm1, [esi+eax+24] + movhps xmm2, [esi+eax+32] + movhps xmm3, [esi+eax+40] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 0, 2 ) + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + add ecx, 16 + add eax, 4*12 + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].Normal() + src[i][3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) { + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm5, [edi+0] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, 16 + add esi, eax + neg eax + + loopVert4: + + movlps xmm1, [esi+eax+ 0] + movlps xmm3, [esi+eax+ 8] + movhps xmm1, [esi+eax+16] + movhps xmm3, [esi+eax+24] + movlps xmm2, [esi+eax+32] + movlps xmm4, [esi+eax+40] + movhps xmm2, [esi+eax+48] + movhps xmm4, [esi+eax+56] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm2, xmm3 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm3, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) + + add ecx, 16 + add eax, 4*16 + + mulps xmm0, xmm5 + mulps xmm1, xmm6 + mulps xmm2, xmm7 + addps xmm0, xmm3 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm5 + mulss xmm1, xmm6 + mulss xmm2, xmm7 + addss xmm0, [esi+eax+12] + add ecx, 4 + addss xmm0, xmm1 + add eax, 16 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { + + assert_16_byte_aligned( src ); + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert4: + movlps xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, X, X */ + movhps xmm1, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, 6, 7 */ + movlps xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, X, X */ + movss xmm7, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, 14, 15 */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + shufps xmm2, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + add ecx, 16 + add eax, 4*DRAWVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, DRAWVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i] + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idVec3 *src, const int count ) { + __asm + { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: + movlps xmm1, [esi+eax+ 0] + movlps xmm2, [esi+eax+ 8] + movlps xmm3, [esi+eax+16] + movhps xmm1, [esi+eax+24] + movhps xmm2, [esi+eax+32] + movhps xmm3, [esi+eax+40] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 0, 2 ) + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + add ecx, 16 + add eax, 4*12 + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idPlane *src, const int count ) { + +#define SINGLE_OP(SRC, DEST) \ + __asm movlps xmm0,[SRC] \ + __asm movlps xmm1,[SRC+8] \ + __asm mulps xmm0,xmm4 \ + __asm mulps xmm1,xmm5 \ + __asm addps xmm0,xmm1 \ + __asm movaps xmm1,xmm0 \ + __asm shufps xmm1,xmm1,SHUFFLE_PS(1,1,1,1) \ + __asm addss xmm0,xmm1 \ + __asm movss [DEST],xmm0 \ + __asm add SRC,16 \ + __asm add DEST,4 + +#define DUAL_OP(SRC, DEST) \ + __asm movlps xmm0,[SRC] \ + __asm movlps xmm1,[SRC+8] \ + __asm movhps xmm0,[SRC+16] \ + __asm movhps xmm1,[SRC+24] \ + __asm mulps xmm0,xmm4 \ + __asm mulps xmm1,xmm5 \ + __asm addps xmm0,xmm1 \ + __asm shufps xmm1,xmm0,SHUFFLE_PS(2,0,1,0) \ + __asm shufps xmm0,xmm0,SHUFFLE_PS(3,1,2,0) \ + __asm addps xmm0,xmm1 \ + __asm movhps [DEST],xmm0 \ + __asm add SRC,32 \ + __asm add DEST,8 + + __asm { + mov edx, dst + mov eax, src + mov ebx, constant + mov ecx, count + + movlps xmm4, [ebx] + shufps xmm4, xmm4, SHUFFLE_PS(1,0,1,0) + movlps xmm5, [ebx+8] + shufps xmm5, xmm5, SHUFFLE_PS(1,0,1,0) + + xorps xmm0, xmm0 + xorps xmm1, xmm1 + + _lpAlignDest: + test edx, 0x0f + jz _destAligned + SINGLE_OP(eax,edx) + dec ecx + jnz _lpAlignDest + jmp _vpExit + + _destAligned: + push ecx + + cmp ecx, 4 + jl _post + + and ecx, ~3 + shl ecx, 2 + lea eax, [eax+ecx*4] + add edx, ecx + neg ecx + + movlps xmm0, [eax+ecx*4] + movhps xmm0, [eax+ecx*4+16] + movlps xmm2, [eax+ecx*4+32] + movhps xmm2, [eax+ecx*4+48] + jmp _lpStart + + align 16 + _lp: + prefetchnta [eax+ecx*4+128] + addps xmm1, xmm0 + movlps xmm0, [eax+ecx*4] + movhps xmm0, [eax+ecx*4+16] + movlps xmm2, [eax+ecx*4+32] + movhps xmm2, [eax+ecx*4+48] + movaps [edx+ecx-16],xmm1 + _lpStart: + movlps xmm1, [eax+ecx*4+8] + movhps xmm1, [eax+ecx*4+24] + movlps xmm3, [eax+ecx*4+40] + movhps xmm3, [eax+ecx*4+56] + add ecx, 16 + mulps xmm1, xmm5 + mulps xmm2, xmm4 + mulps xmm3, xmm5 + addps xmm2, xmm3 // y3+w3 x3+z3 y2+w2 x2+z2 + mulps xmm0, xmm4 + addps xmm0, xmm1 // y1+w1 x1+z1 y0+w0 x0+z0 + movaps xmm1, xmm0 + shufps xmm0, xmm2, SHUFFLE_PS(2,0,2,0) // x3+z3 x2+z2 x1+z1 x0+z0 + shufps xmm1, xmm2, SHUFFLE_PS(3,1,3,1) // y3+w3 y2+w2 y1+w1 y0+w0 + js _lp + addps xmm1, xmm0 + movaps [edx+ecx-16], xmm1 + _post: + pop ecx + and ecx, 0x3 + cmp ecx, 2 + jl _post1 + DUAL_OP(eax,edx) + sub ecx, 2 + _post1: + cmp ecx, 1 + jne _vpExit + SINGLE_OP(eax,edx) + _vpExit: + } + +#undef DUAL_OP +#undef SINGLE_OP + +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { + assert_16_byte_aligned( src ); + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert4: + movlps xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, X, X */ + movhps xmm1, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, 6, 7 */ + movlps xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, 12, 13 */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + movss xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, 14, 15 */ + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + add ecx, 16 + add eax, 4*DRAWVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, DRAWVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) { + __asm + { + mov eax, count + mov edi, src0 + mov edx, eax + mov esi, src1 + mov ecx, dst + and eax, ~3 + + jz done4 + imul eax, 12 + add edi, eax + add esi, eax + neg eax + + loop4: + movlps xmm0, [esi+eax] // 0, 1, X, X + movlps xmm3, [edi+eax] // 0, 1, X, X + movlps xmm1, [esi+eax+8] // 2, 3, X, X + movlps xmm4, [edi+eax+8] // 2, 3, X, X + movhps xmm0, [esi+eax+24] // 0, 1, 6, 7 + movhps xmm3, [edi+eax+24] // 0, 1, 6, 7 + movhps xmm1, [esi+eax+32] // 2, 3, 8, 9 + movhps xmm4, [edi+eax+32] // 2, 3, 8, 9 + movlps xmm2, [esi+eax+16] // 4, 5, X, X + movlps xmm5, [edi+eax+16] // 4, 5, X, X + movhps xmm2, [esi+eax+40] // 4, 5, 10, 11 + movhps xmm5, [edi+eax+40] // 4, 5, 10, 11 + + add ecx, 16 + add eax, 48 + + mulps xmm0, xmm3 + mulps xmm1, xmm4 + mulps xmm2, xmm5 + movaps xmm7, xmm0 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) // 0, 6, 3, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 1, 3, 0, 2 ) // 1, 7, 4, 10 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) // 2, 8, 5, 11 + addps xmm7, xmm0 + addps xmm7, xmm1 + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + movlps [ecx-16+0], xmm7 + movhps [ecx-16+8], xmm7 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm3, [edi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm4, [edi+eax+4] + movss xmm2, [esi+eax+8] + movss xmm5, [edi+eax+8] + mulss xmm0, xmm3 + mulss xmm1, xmm4 + mulss xmm2, xmm5 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2] + ... +============ +*/ +void VPCALL idSIMD_SSE::Dot( float &dot, const float *src1, const float *src2, const int count ) { + switch( count ) { + case 0: + dot = 0.0f; + return; + case 1: + dot = src1[0] * src2[0]; + return; + case 2: + dot = src1[0] * src2[0] + src1[1] * src2[1]; + return; + case 3: + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2]; + return; + default: + __asm { + mov ecx, src1 + mov edx, src2 + mov eax, ecx + or eax, edx + and eax, 15 + jz alignedDot + // unaligned + mov eax, count + shr eax, 2 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + movups xmm0, [ecx+eax] + movups xmm1, [edx+eax] + mulps xmm0, xmm1 + add eax, 16 + jz doneDot + loopUnalignedDot: + movups xmm1, [ecx+eax] + movups xmm2, [edx+eax] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add eax, 16 + jl loopUnalignedDot + jmp doneDot + // aligned + alignedDot: + mov eax, count + shr eax, 2 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + movaps xmm0, [ecx+eax] + movaps xmm1, [edx+eax] + mulps xmm0, xmm1 + add eax, 16 + jz doneDot + loopAlignedDot: + movaps xmm1, [ecx+eax] + movaps xmm2, [edx+eax] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add eax, 16 + jl loopAlignedDot + doneDot: + } + switch( count & 3 ) { + case 1: + __asm { + movss xmm1, [ecx] + movss xmm2, [edx] + mulss xmm1, xmm2 + addss xmm0, xmm1 + } + break; + case 2: + __asm { + xorps xmm2, xmm2 + movlps xmm1, [ecx] + movlps xmm2, [edx] + mulps xmm1, xmm2 + addps xmm0, xmm1 + } + break; + case 3: + __asm { + movss xmm1, [ecx] + movhps xmm1, [ecx+4] + movss xmm2, [edx] + movhps xmm2, [edx+4] + mulps xmm1, xmm2 + addps xmm0, xmm1 + } + break; + } + __asm { + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + mov eax, dot + movss [eax], xmm0 + } + return; + } +} + +// +// cmpeqps == Equal +// cmpneqps != Not Equal +// cmpltps < Less Than +// cmpnltps >= Not Less Than +// cmpnleps > Not Less Or Equal +// +#define FLIP not al +#define NOFLIP + +#define COMPARECONSTANT( DST, SRC0, CONSTANT, COUNT, CMP, CMPSIMD, DOFLIP ) \ + int i, cnt, pre, post; \ + float *aligned; \ + \ + /* if the float array is not aligned on a 4 byte boundary */ \ + if ( ((int) SRC0) & 3 ) { \ + /* unaligned memory access */ \ + pre = 0; \ + cnt = COUNT >> 2; \ + post = COUNT - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, SRC0 \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm mov ecx, 0x01010101 \ + __asm loopNA: \ + __asm movups xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, ecx \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopNA \ + __asm pop ebx \ + } \ + else { \ + /* aligned memory access */ \ + aligned = (float *) ((((int) SRC0) + 15) & ~15); \ + if ( (int)aligned > ((int)src0) + COUNT ) { \ + pre = COUNT; \ + post = 0; \ + } \ + else { \ + pre = aligned - SRC0; \ + cnt = (COUNT - pre) >> 2; \ + post = COUNT - pre - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, aligned \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm add edi, pre \ + __asm mov ecx, 0x01010101 \ + __asm loopA: \ + __asm movaps xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, ecx \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopA \ + __asm pop ebx \ + } \ + } \ + doneCmp: \ + double c = constant; \ + for ( i = 0; i < pre; i++ ) { \ + dst[i] = src0[i] CMP c; \ + } \ + for ( i = count - post; i < count; i++ ) { \ + dst[i] = src0[i] CMP c; \ + } + +#define COMPAREBITCONSTANT( DST, BITNUM, SRC0, CONSTANT, COUNT, CMP, CMPSIMD, DOFLIP ) \ + int i, cnt, pre, post; \ + float *aligned; \ + \ + /* if the float array is not aligned on a 4 byte boundary */ \ + if ( ((int) SRC0) & 3 ) { \ + /* unaligned memory access */ \ + pre = 0; \ + cnt = COUNT >> 2; \ + post = COUNT - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, SRC0 \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm mov cl, bitNum \ + __asm loopNA: \ + __asm movups xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, 0x01010101 \ + __asm shl ebx, cl \ + __asm or ebx, dword ptr [edi] \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopNA \ + __asm pop ebx \ + } \ + else { \ + /* aligned memory access */ \ + aligned = (float *) ((((int) SRC0) + 15) & ~15); \ + if ( (int)aligned > ((int)src0) + COUNT ) { \ + pre = COUNT; \ + post = 0; \ + } \ + else { \ + pre = aligned - SRC0; \ + cnt = (COUNT - pre) >> 2; \ + post = COUNT - pre - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, aligned \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm add edi, pre \ + __asm mov cl, bitNum \ + __asm loopA: \ + __asm movaps xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, 0x01010101 \ + __asm shl ebx, cl \ + __asm or ebx, dword ptr [edi] \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopA \ + __asm pop ebx \ + } \ + } \ + doneCmp: \ + float c = constant; \ + for ( i = 0; i < pre; i++ ) { \ + dst[i] |= ( src0[i] CMP c ) << BITNUM; \ + } \ + for ( i = count - post; i < count; i++ ) { \ + dst[i] |= ( src0[i] CMP c ) << BITNUM; \ + } + +/* +============ +idSIMD_SSE::CmpGT + + dst[i] = src0[i] > constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpGT( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, >, cmpnleps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, >, cmpnleps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpGE( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, >=, cmpnltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, >=, cmpnltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpLT( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, <, cmpltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, <, cmpltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpLE( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, <=, cmpnleps, FLIP ) +} + +/* +============ +idSIMD_SSE::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, <=, cmpnleps, FLIP ) +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( float &min, float &max, const float *src, const int count ) { + int i, pre, post; + + min = idMath::INFINITY; max = -idMath::INFINITY; + + __asm + { + push ebx + mov eax, min + mov ebx, max + movss xmm0, [eax] + movss xmm1, [ebx] + shufps xmm0, xmm0, 0 + shufps xmm1, xmm1, 0 + + KFLOATINITS( src, count, pre, post ) + and eax, 15 + jz lpA + jmp lpNA + align 16 +lpNA: + movups xmm2, [edx+ebx] + movups xmm3, [edx+ebx+16] + minps xmm0, xmm2 + maxps xmm1, xmm2 + prefetchnta [edx+ebx+64] + minps xmm0, xmm3 + maxps xmm1, xmm3 + add ebx, 16*2 + jl lpNA + jmp done2 +lpA: + movaps xmm2, [edx+ebx] + movaps xmm3, [edx+ebx+16] + minps xmm0, xmm2 + maxps xmm1, xmm2 + prefetchnta [edx+ebx+64] + minps xmm0, xmm3 + maxps xmm1, xmm3 + add ebx, 16*2 + jl lpA + jmp done2 + align 16 +done2: + movaps xmm2, xmm0 + movaps xmm3, xmm1 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + mov eax, min + mov ebx, max + movss [eax], xmm0 + movss [ebx], xmm1 +done: + pop ebx + } + + for ( i = 0; i < pre; i++ ) { + float tmp = src[i]; + if ( tmp > max ) { + max = tmp; + } + if ( tmp < min ) { + min = tmp; + } + } + for ( i = count - post; i < count; i++ ) { + float tmp = src[i]; + if ( tmp > max ) { + max = tmp; + } + if ( tmp < min ) { + min = tmp; + } + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) { + __asm { + mov eax, count + test eax, eax + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + jz done + mov esi, src + test eax, 1 + jz startLoop + movlps xmm2, [esi] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + add esi, 2*4 + dec eax + minps xmm0, xmm2 + maxps xmm1, xmm2 + jz done + startLoop: + shl eax, 3 + add esi, eax + neg eax + loopVert: + movlps xmm2, [esi+eax] + movhps xmm2, [esi+eax+8] + add eax, 4*4 + minps xmm0, xmm2 + maxps xmm1, xmm2 + jl loopVert + done: + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 3, 0, 1 ) + minps xmm0, xmm2 + mov esi, min + movlps [esi], xmm0 + movaps xmm3, xmm1 + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + maxps xmm1, xmm3 + mov edi, max + movlps [edi], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) { + __asm { + + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: +// prefetchnta [esi+4*12] + + movss xmm4, [esi+eax+0*12+8] + movhps xmm4, [esi+eax+0*12+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + movss xmm5, [esi+eax+1*12+0] + movhps xmm5, [esi+eax+1*12+4] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + movss xmm6, [esi+eax+2*12+8] + movhps xmm6, [esi+eax+2*12+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + movss xmm7, [esi+eax+3*12+0] + movhps xmm7, [esi+eax+3*12+4] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*12 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + imul eax, 12 + add esi, eax + neg eax + + loop1: + movss xmm4, [esi+eax+0*12+8] + movhps xmm4, [esi+eax+0*12+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 12 + jl loop1 + + done1: + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 1, 0, 2 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 1, 0, 2 ) + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + __asm { + + movss xmm0, SIMD_SP_infinity + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loop4: + movss xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + movss xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + movss xmm6, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm6, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + movss xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*DRAWVERT_SIZE + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loop1: + movss xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, DRAWVERT_SIZE + jl loop1 + + done1: + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + + __asm { + + movss xmm0, SIMD_SP_infinity + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov edi, indexes + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + shl eax, 2 + add edi, eax + neg eax + + loop4: + mov edx, [edi+eax+0] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + mov edx, [edi+eax+4] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + mov edx, [edi+eax+8] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + mov edx, [edi+eax+12] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*4 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + shl eax, 2 + add edi, eax + neg eax + + loop1: + mov edx, [edi+eax+0] + imul edx, DRAWVERT_SIZE; + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 4 + jl loop1 + + done1: + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::Clamp +============ +*/ +void VPCALL idSIMD_SSE::Clamp( float *dst, const float *src, const float min, const float max, const int count ) { + int i, pre, post; + + __asm + { + movss xmm0,min + movss xmm1,max + shufps xmm0,xmm0,0 + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + maxps xmm3,xmm0 + prefetchnta [edx+ebx+64] + minps xmm2,xmm1 + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + maxps xmm3,xmm0 + prefetchnta [edx+ebx+64] + minps xmm2,xmm1 + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for ( i = 0; i < pre; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } + + for( i = count - post; i < count; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::ClampMin +============ +*/ +void VPCALL idSIMD_SSE::ClampMin( float *dst, const float *src, const float min, const int count ) { + int i, pre, post; + + __asm + { + movss xmm0,min + shufps xmm0,xmm0,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + prefetchnta [edx+ebx+64] + maxps xmm3,xmm0 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + prefetchnta [edx+ebx+64] + maxps xmm3,xmm0 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for( i = 0; i < pre; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else + dst[i] = src[i]; + } + for( i = count - post; i < count; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::ClampMax +============ +*/ +void VPCALL idSIMD_SSE::ClampMax( float *dst, const float *src, const float max, const int count ) { + int i, pre, post; + + __asm + { + movss xmm1,max + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + minps xmm2,xmm1 + prefetchnta [edx+ebx+64] + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + minps xmm2,xmm1 + prefetchnta [edx+ebx+64] + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for( i = 0; i < pre; i++ ) { + if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } + + for( i = count - post; i < count; i++ ) { + if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::Zero16 +============ +*/ +void VPCALL idSIMD_SSE::Zero16( float *dst, const int count ) { + __asm { + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneZero16 + shl eax, 4 + add edx, eax + neg eax + xorps xmm0, xmm0 + loopZero16: + movaps [edx+eax], xmm0 + add eax, 16 + jl loopZero16 + doneZero16: + } +} + +/* +============ +idSIMD_SSE::Negate16 +============ +*/ +void VPCALL idSIMD_SSE::Negate16( float *dst, const int count ) { + __asm { + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneNegate16 + shl eax, 4 + add edx, eax + neg eax + movss xmm0, SIMD_SP_signBit + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + loopNegate16: + movaps xmm1, [edx+eax] + xorps xmm1, xmm0 + movaps [edx+eax], xmm1 + add eax, 16 + jl loopNegate16 + doneNegate16: + } +} + +/* +============ +idSIMD_SSE::Copy16 +============ +*/ +void VPCALL idSIMD_SSE::Copy16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, src + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneCopy16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopCopy16: + movaps xmm0, [ecx+eax] + movaps [edx+eax], xmm0 + add eax, 16 + jl loopCopy16 + doneCopy16: + } +} + +/* +============ +idSIMD_SSE::Add16 +============ +*/ +void VPCALL idSIMD_SSE::Add16( float *dst, const float *src1, const float *src2, const int count ) { + __asm { + mov ecx, src1 + mov edx, src2 + mov esi, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneAdd16 + shl eax, 4 + add esi, eax + add ecx, eax + add edx, eax + neg eax + loopAdd16: + movaps xmm0, [ecx+eax] + addps xmm0, [edx+eax] + movaps [esi+eax], xmm0 + add eax, 16 + jl loopAdd16 + doneAdd16: + } +} + +/* +============ +idSIMD_SSE::Sub16 +============ +*/ +void VPCALL idSIMD_SSE::Sub16( float *dst, const float *src1, const float *src2, const int count ) { + __asm { + mov ecx, src1 + mov edx, src2 + mov esi, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneSub16 + shl eax, 4 + add esi, eax + add ecx, eax + add edx, eax + neg eax + loopSub16: + movaps xmm0, [ecx+eax] + subps xmm0, [edx+eax] + movaps [esi+eax], xmm0 + add eax, 16 + jl loopSub16 + doneSub16: + } +} + +/* +============ +idSIMD_SSE::Mul16 +============ +*/ +void VPCALL idSIMD_SSE::Mul16( float *dst, const float *src1, const float constant, const int count ) { + __asm { + mov ecx, dst + mov edx, src1 + mov eax, count + add eax, 3 + shr eax, 2 + jz doneMulScalar16 + movss xmm1, constant + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + shufps xmm1, xmm1, 0x00 + loopMulScalar16: + movaps xmm0, [edx+eax] + mulps xmm0, xmm1 + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopMulScalar16 + doneMulScalar16: + } +} + +/* +============ +idSIMD_SSE::AddAssign16 +============ +*/ +void VPCALL idSIMD_SSE::AddAssign16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, dst + mov edx, src + mov eax, count + add eax, 3 + shr eax, 2 + jz doneAddAssign16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopAddAssign16: + movaps xmm0, [ecx+eax] + addps xmm0, [edx+eax] + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopAddAssign16 + doneAddAssign16: + } +} + +/* +============ +idSIMD_SSE::SubAssign16 +============ +*/ +void VPCALL idSIMD_SSE::SubAssign16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, dst + mov edx, src + mov eax, count + add eax, 3 + shr eax, 2 + jz doneSubAssign16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopSubAssign16: + movaps xmm0, [ecx+eax] + subps xmm0, [edx+eax] + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopSubAssign16 + doneSubAssign16: + } +} + +/* +============ +idSIMD_SSE::MulAssign16 +============ +*/ +void VPCALL idSIMD_SSE::MulAssign16( float *dst, const float constant, const int count ) { + __asm { + mov ecx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneMulAssign16 + movss xmm1, constant + shl eax, 4 + add ecx, eax + neg eax + shufps xmm1, xmm1, 0x00 + loopMulAssign16: + movaps xmm0, [ecx+eax] + mulps xmm0, xmm1 + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopMulAssign16 + doneMulAssign16: + } +} + +/* +============ +idSIMD_SSE::MatX_MultiplyVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss [eax+offset], reg1 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps [eax+offset], reg1 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 \ + __asm movhps [eax+offset+8], reg1 +#define STOREC = + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplyAddVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm addss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC += + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplySubVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm subss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC -= + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss [eax+offset], reg1 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps [eax+offset], reg1 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 \ + __asm movhps [eax+offset+8], reg1 +#define STOREC = + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyAddVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm addss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC += + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +void idSIMD_SSE::MatX_TransposeMultiplySubVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm subss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC -= + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplyMatX + + optimizes the following matrix multiplications: + + NxN * Nx6 + 6xN * Nx6 + Nx6 * 6xN + 6x6 * 6xN + + with N in the range [1-6]. + + The hot cache clock cycle counts are generally better for the SIMD version than the + FPU version. At times up to 40% less clock cycles on a P3. In practise however, + the results are poor probably due to memory access. +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( m1.GetNumColumns() == m2.GetNumRows() ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + k = m1.GetNumRows(); + l = m2.GetNumColumns(); + n = m1.GetNumColumns(); + + switch( n ) { + case 1: { + if ( !(l^6) ) { + switch( k ) { + case 1: { // 1x1 * 1x6, no precision loss compared to FPU version + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movss xmm0, [edi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, [esi] + mulps xmm1, xmm0 + movaps [eax], xmm1 + movlps xmm2, [esi+16] + mulps xmm2, xmm0 + movlps [eax+16], xmm2 + } + return; + } + case 6: { // 6x1 * 1x6, no precision loss compared to FPU version + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + xorps xmm1, xmm1 + movaps xmm0, [edi] + movlps xmm1, [edi+16] + movlhps xmm1, xmm0 + movhlps xmm2, xmm0 + movlhps xmm2, xmm1 + // row 0 and 1 + movaps xmm3, [esi] + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, xmm3 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + movaps [eax], xmm4 + movaps [eax+16], xmm5 + movaps [eax+32], xmm6 + // row 2 and 3 + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 3, 3 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm2 + movaps [eax+48], xmm4 + movaps [eax+64], xmm5 + movaps [eax+80], xmm3 + // row 4 and 5 + movlps xmm3, [esi+16] + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm2 + movaps [eax+96], xmm4 + movaps [eax+112], xmm5 + movaps [eax+128], xmm3 + } + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + } + case 2: { + if ( !(l^6) ) { + switch( k ) { + case 2: { // 2x2 * 2x6 + + #define MUL_Nx2_2x6_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movlps xmm1, [esi+16] \ + __asm movhps xmm1, [esi+40] \ + __asm movlps xmm2, [esi+24] \ + __asm movhps xmm2, [esi+32] + + #define MUL_Nx2_2x6_ROW2( row ) \ + __asm movaps xmm3, [edi+row*16] \ + __asm movaps xmm5, xmm0 \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm6, xmm2 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 1, 1 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm5, xmm6 \ + __asm movaps [eax+row*48], xmm5 \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movaps xmm7, xmm1 \ + __asm mulps xmm7, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm5, xmm0 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) \ + __asm mulps xmm5, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm6, xmm2 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 3, 3, 3 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm movaps xmm6, xmm1 \ + __asm mulps xmm6, xmm3 \ + __asm movaps xmm4, xmm7 \ + __asm movlhps xmm7, xmm6 \ + __asm movhlps xmm6, xmm4 \ + __asm addps xmm6, xmm7 \ + __asm movlps [eax+row*48+16], xmm6 \ + __asm movlps [eax+row*48+24], xmm5 \ + __asm movhps [eax+row*48+32], xmm5 \ + __asm movhps [eax+row*48+40], xmm6 + + MUL_Nx2_2x6_INIT + MUL_Nx2_2x6_ROW2( 0 ) + + return; + } + case 6: { // 6x2 * 2x6 + + MUL_Nx2_2x6_INIT + MUL_Nx2_2x6_ROW2( 0 ) + MUL_Nx2_2x6_ROW2( 1 ) + MUL_Nx2_2x6_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr += 2; + } + break; + } + case 3: { + if ( !(l^6) ) { + switch( k ) { + case 3: { // 3x3 * 3x6 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movaps xmm5, xmmword ptr [esi] + movlps xmm6, qword ptr [esi+24] + movhps xmm6, qword ptr [esi+32] + movaps xmm7, xmmword ptr [esi+48] + movss xmm0, dword ptr [edi] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm5 + movss xmm1, dword ptr [edi+4] + shufps xmm1, xmm1, 0 + mulps xmm1, xmm6 + movss xmm2, dword ptr [edi+8] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + movaps xmmword ptr [eax], xmm0 + movss xmm3, dword ptr [edi+12] + shufps xmm3, xmm3, 0 + mulps xmm3, xmm5 + movss xmm4, dword ptr [edi+16] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm6 + movss xmm0, dword ptr [edi+20] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm7 + addps xmm3, xmm4 + addps xmm0, xmm3 + movlps qword ptr [eax+24], xmm0 + movhps qword ptr [eax+32], xmm0 + movss xmm1, dword ptr [edi+24] + shufps xmm1, xmm1, 0 + mulps xmm1, xmm5 + movss xmm2, dword ptr [edi+28] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm6 + movss xmm3, dword ptr [edi+32] + shufps xmm3, xmm3, 0 + mulps xmm3, xmm7 + addps xmm1, xmm2 + addps xmm1, xmm3 + movaps xmmword ptr [eax+48], xmm1 + movlps xmm5, qword ptr [esi+16] + movlps xmm6, qword ptr [esi+40] + movlps xmm7, qword ptr [esi+64] + shufps xmm5, xmm5, 0x44 + shufps xmm6, xmm6, 0x44 + shufps xmm7, xmm7, 0x44 + movaps xmm3, xmmword ptr [edi] + movlps xmm4, qword ptr [edi+16] + movaps xmm0, xmm3 + shufps xmm0, xmm0, 0xF0 + mulps xmm0, xmm5 + movaps xmm1, xmm3 + shufps xmm1, xmm4, 0x05 + mulps xmm1, xmm6 + shufps xmm3, xmm4, 0x5A + mulps xmm3, xmm7 + addps xmm1, xmm0 + addps xmm1, xmm3 + movlps qword ptr [eax+16], xmm1 + movhps qword ptr [eax+40], xmm1 + movss xmm0, dword ptr [edi+24] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm5 + movss xmm2, dword ptr [edi+28] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm6 + movss xmm4, dword ptr [edi+32] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm7 + addps xmm0, xmm2 + addps xmm0, xmm4 + movlps qword ptr [eax+64], xmm0 + } + return; + } + case 6: { // 6x3 * 3x6 + #define MUL_Nx3_3x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] + + #define MUL_Nx3_3x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm3, [edi+(row*3+0)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm3, xmm0 \ + __asm movss xmm4, [edi+(row*3+1)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm4, xmm1 \ + __asm addps xmm3, xmm4 \ + __asm movss xmm5, [edi+(row*3+2)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm5 \ + __asm movlps [eax+(row*6+0)*4], xmm3 \ + __asm movhps [eax+(row*6+2)*4], xmm3 + + #define MUL_Nx3_3x6_LAST2COLUMNS_ROW6 \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm shufps xmm0, xmm0, 0x44 \ + __asm shufps xmm1, xmm1, 0x44 \ + __asm shufps xmm2, xmm2, 0x44 \ + __asm movlps xmm3, [edi+0*4] \ + __asm movhps xmm3, [edi+2*4] \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm5, xmm3 \ + __asm shufps xmm3, xmm3, 0xF0 \ + __asm mulps xmm3, xmm0 \ + __asm movlps xmm6, [edi+4*4] \ + __asm movhps xmm6, [edi+6*4] \ + __asm shufps xmm4, xmm6, 0x05 \ + __asm mulps xmm4, xmm1 \ + __asm addps xmm3, xmm4 \ + __asm shufps xmm5, xmm6, 0x5A \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm5 \ + __asm movlps [eax+4*4], xmm3 \ + __asm movhps [eax+10*4], xmm3 \ + __asm movaps xmm5, xmm6 \ + __asm movlps xmm3, [edi+8*4] \ + __asm movhps xmm3, [edi+10*4] \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm5, xmm3, 0x5A \ + __asm mulps xmm5, xmm0 \ + __asm shufps xmm6, xmm3, 0xAF \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm4, 0xF0 \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm4, xmm5 \ + __asm movlps [eax+16*4], xmm4 \ + __asm movhps [eax+22*4], xmm4 \ + __asm movlps xmm6, [edi+12*4] \ + __asm movhps xmm6, [edi+14*4] \ + __asm movaps xmm5, xmm6 \ + __asm movaps xmm4, xmm6 \ + __asm shufps xmm6, xmm6, 0xF0 \ + __asm mulps xmm6, xmm0 \ + __asm movlps xmm3, [edi+16*4] \ + __asm shufps xmm5, xmm3, 0x05 \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm3, 0x5A \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm4, xmm5 \ + __asm movlps [eax+28*4], xmm4 \ + __asm movhps [eax+34*4], xmm4 + + MUL_Nx3_3x6_FIRST4COLUMNS_INIT + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx3_3x6_LAST2COLUMNS_ROW6 + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr += 3; + } + break; + } + case 4: { + if ( !(l^6) ) { + switch( k ) { + case 4: { // 4x4 * 4x6 + + #define MUL_Nx4_4x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] + + #define MUL_Nx4_4x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm4, [edi+row*16+0*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm4, xmm0 \ + __asm movss xmm5, [edi+row*16+1*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm4, xmm5 \ + __asm movss xmm6, [edi+row*16+2*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm4, xmm6 \ + __asm movss xmm7, [edi+row*16+3*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm3 \ + __asm addps xmm4, xmm7 \ + __asm movlps [eax+row*24+0], xmm4 \ + __asm movhps [eax+row*24+8], xmm4 + + #define MUL_Nx4_4x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_Nx4_4x6_LAST2COLUMNS_ROW2( row ) \ + __asm movlps xmm7, [edi+row*32+ 0*4] \ + __asm movhps xmm7, [edi+row*32+ 4*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 3, 3 ) \ + __asm mulps xmm6, xmm0 \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 1, 2, 2 ) \ + __asm mulps xmm7, xmm1 \ + __asm addps xmm6, xmm7 \ + __asm movlps xmm4, [edi+row*32+ 2*4] \ + __asm movhps xmm4, [edi+row*32+ 6*4] \ + __asm movaps xmm5, xmm4 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 3, 3 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 2, 2 ) \ + __asm mulps xmm4, xmm3 \ + __asm addps xmm6, xmm4 \ + __asm movlps [eax+row*48+ 4*4], xmm6 \ + __asm movhps [eax+row*48+10*4], xmm6 + + MUL_Nx4_4x6_FIRST4COLUMNS_INIT + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx4_4x6_LAST2COLUMNS_INIT + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 1 ) + + return; + } + case 6: { // 6x4 * 4x6 + + MUL_Nx4_4x6_FIRST4COLUMNS_INIT + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx4_4x6_LAST2COLUMNS_INIT + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr += 4; + } + break; + } + case 5: { + if ( !(l^6) ) { + switch( k ) { + case 5: { // 5x5 * 5x6 + + #define MUL_Nx5_5x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] \ + __asm movlps xmm4, [esi+24*4] \ + __asm movhps xmm4, [esi+26*4] + + #define MUL_Nx5_5x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm6, [edi+row*20+0*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm0 \ + __asm movss xmm5, [edi+row*20+1*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+2*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+3*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+4*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movlps [eax+row*24+0], xmm6 \ + __asm movhps [eax+row*24+8], xmm6 + + #define MUL_Nx5_5x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm movlps xmm4, [esi+28*4] \ + __asm shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm4, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_Nx5_5x6_LAST2COLUMNS_ROW2( row ) \ + __asm movlps xmm7, [edi+row*40+ 0*4] \ + __asm movhps xmm7, [edi+row*40+ 6*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 2, 2 ) \ + __asm mulps xmm6, xmm0 \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 3, 3 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm movlps xmm7, [edi+row*40+ 2*4] \ + __asm movhps xmm7, [edi+row*40+ 8*4] \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 2, 2 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 3, 3 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm movlps xmm5, [edi+row*40+ 4*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movlps [eax+row*48+ 4*4], xmm6 \ + __asm movhps [eax+row*48+10*4], xmm6 + + #define MUL_Nx5_5x6_LAST2COLUMNS_ROW( row ) \ + __asm movlps xmm6, [edi+20*4+0*4] \ + __asm unpcklps xmm6, xmm6 \ + __asm mulps xmm6, xmm0 \ + __asm movlps xmm5, [edi+20*4+2*4] \ + __asm unpcklps xmm5, xmm5 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+20*4+4*4] \ + __asm unpcklps xmm5, xmm5 \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movhlps xmm7, xmm6 \ + __asm addps xmm6, xmm7 \ + __asm movlps [eax+row*24+4*4], xmm6 + + MUL_Nx5_5x6_FIRST4COLUMNS_INIT + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx5_5x6_LAST2COLUMNS_INIT + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW( 4 ) + + return; + } + case 6: { // 6x5 * 5x6 + + MUL_Nx5_5x6_FIRST4COLUMNS_INIT + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx5_5x6_LAST2COLUMNS_INIT + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr += 5; + } + break; + } + case 6: { + switch( k ) { + case 1: { + if ( !(l^1) ) { // 1x6 * 6x1 + dstPtr[0] = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[1] + m1Ptr[2] * m2Ptr[2] + + m1Ptr[3] * m2Ptr[3] + m1Ptr[4] * m2Ptr[4] + m1Ptr[5] * m2Ptr[5]; + return; + } + break; + } + case 2: { + if ( !(l^2) ) { // 2x6 * 6x2 + + #define MUL_Nx6_6x2_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] + + #define MUL_Nx6_6x2_ROW2( row ) \ + __asm movaps xmm7, [edi+row*48+0*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm7, xmm0 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movaps xmm6, [edi+row*48+4*4] \ + __asm movaps xmm5, xmm6 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm5, xmm0 \ + __asm movaps xmm6, [edi+row*48+24+2*4] \ + __asm movaps xmm4, xmm6 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm5, xmm4 \ + __asm movaps xmm4, xmm5 \ + __asm movhlps xmm5, xmm7 \ + __asm movlhps xmm7, xmm4 \ + __asm addps xmm7, xmm5 \ + __asm movaps [eax+row*16], xmm7 + + MUL_Nx6_6x2_INIT + MUL_Nx6_6x2_ROW2( 0 ) + + return; + } + break; + } + case 3: { + if ( !(l^3) ) { // 3x6 * 6x3 + + #define MUL_Nx6_6x3_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movss xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 1*4] \ + __asm movss xmm1, [esi+ 3*4] \ + __asm movhps xmm1, [esi+ 4*4] \ + __asm movss xmm2, [esi+ 6*4] \ + __asm movhps xmm2, [esi+ 7*4] \ + __asm movss xmm3, [esi+ 9*4] \ + __asm movhps xmm3, [esi+10*4] \ + __asm movss xmm4, [esi+12*4] \ + __asm movhps xmm4, [esi+13*4] \ + __asm movss xmm5, [esi+15*4] \ + __asm movhps xmm5, [esi+16*4] + + #define MUL_Nx6_6x3_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movss [eax+row*12+0], xmm7 \ + __asm movhps [eax+row*12+4], xmm7 + + MUL_Nx6_6x3_INIT + MUL_Nx6_6x3_ROW( 0 ) + MUL_Nx6_6x3_ROW( 1 ) + MUL_Nx6_6x3_ROW( 2 ) + + return; + } + break; + } + case 4: { + if ( !(l^4) ) { // 4x6 * 6x4 + + #define MUL_Nx6_6x4_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] \ + __asm movaps xmm3, [esi+48] \ + __asm movaps xmm4, [esi+64] \ + __asm movaps xmm5, [esi+80] + + #define MUL_Nx6_6x4_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movaps [eax+row*16], xmm7 + + MUL_Nx6_6x4_INIT + MUL_Nx6_6x4_ROW( 0 ) + MUL_Nx6_6x4_ROW( 1 ) + MUL_Nx6_6x4_ROW( 2 ) + MUL_Nx6_6x4_ROW( 3 ) + + return; + } + break; + } + case 5: { + if ( !(l^5) ) { // 5x6 * 6x5 + + #define MUL_Nx6_6x5_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movlps xmm1, [esi+20] \ + __asm movhps xmm1, [esi+28] \ + __asm movlps xmm2, [esi+40] \ + __asm movhps xmm2, [esi+48] \ + __asm movlps xmm3, [esi+60] \ + __asm movhps xmm3, [esi+68] \ + __asm movaps xmm4, [esi+80] \ + __asm movlps xmm5, [esi+100] \ + __asm movhps xmm5, [esi+108] + + #define MUL_Nx6_6x5_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm fld dword ptr [edi+(row*6+0)*4] \ + __asm fmul dword ptr [esi+(4+0*5)*4] \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+1)*4] \ + __asm fmul dword ptr [esi+(4+1*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+2)*4] \ + __asm fmul dword ptr [esi+(4+2*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+3)*4] \ + __asm fmul dword ptr [esi+(4+3*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+4)*4] \ + __asm fmul dword ptr [esi+(4+4*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+5)*4] \ + __asm fmul dword ptr [esi+(4+5*5)*4] \ + __asm faddp st(1),st \ + __asm fstp dword ptr [eax+(row*5+4)*4] \ + __asm movlps [eax+row*20], xmm7 \ + __asm movhps [eax+row*20+8], xmm7 + + MUL_Nx6_6x5_INIT + MUL_Nx6_6x5_ROW( 0 ) + MUL_Nx6_6x5_ROW( 1 ) + MUL_Nx6_6x5_ROW( 2 ) + MUL_Nx6_6x5_ROW( 3 ) + MUL_Nx6_6x5_ROW( 4 ) + + return; + } + break; + } + case 6: { + switch( l ) { + case 1: { // 6x6 * 6x1 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + movlps [eax], xmm0 + movhps [eax+8], xmm0 + movlps [eax+16], xmm2 + } + return; + } + case 2: { // 6x6 * 6x2 + + MUL_Nx6_6x2_INIT + MUL_Nx6_6x2_ROW2( 0 ) + MUL_Nx6_6x2_ROW2( 1 ) + MUL_Nx6_6x2_ROW2( 2 ) + + return; + } + case 3: { // 6x6 * 6x3 + + MUL_Nx6_6x3_INIT + MUL_Nx6_6x3_ROW( 0 ) + MUL_Nx6_6x3_ROW( 1 ) + MUL_Nx6_6x3_ROW( 2 ) + MUL_Nx6_6x3_ROW( 3 ) + MUL_Nx6_6x3_ROW( 4 ) + MUL_Nx6_6x3_ROW( 5 ) + + return; + } + case 4: { // 6x6 * 6x4 + + MUL_Nx6_6x4_INIT + MUL_Nx6_6x4_ROW( 0 ) + MUL_Nx6_6x4_ROW( 1 ) + MUL_Nx6_6x4_ROW( 2 ) + MUL_Nx6_6x4_ROW( 3 ) + MUL_Nx6_6x4_ROW( 4 ) + MUL_Nx6_6x4_ROW( 5 ) + + return; + } + case 5: { // 6x6 * 6x5 + + MUL_Nx6_6x5_INIT + MUL_Nx6_6x5_ROW( 0 ) + MUL_Nx6_6x5_ROW( 1 ) + MUL_Nx6_6x5_ROW( 2 ) + MUL_Nx6_6x5_ROW( 3 ) + MUL_Nx6_6x5_ROW( 4 ) + MUL_Nx6_6x5_ROW( 5 ) + + return; + } + case 6: { // 6x6 * 6x6 + __asm { + mov ecx, dword ptr m2Ptr + movlps xmm3, qword ptr [ecx+72] + mov edx, dword ptr m1Ptr + // Loading first 4 columns (upper 4 rows) of m2Ptr. + movaps xmm0, xmmword ptr [ecx] + movlps xmm1, qword ptr [ecx+24] + movhps xmm1, qword ptr [ecx+32] + movaps xmm2, xmmword ptr [ecx+48] + movhps xmm3, qword ptr [ecx+80] + // Calculating first 4 elements in the first row of the destination matrix. + movss xmm4, dword ptr [edx] + movss xmm5, dword ptr [edx+4] + mov eax, dword ptr dstPtr + shufps xmm4, xmm4, 0 + movss xmm6, dword ptr [edx+8] + shufps xmm5, xmm5, 0 + movss xmm7, dword ptr [edx+12] + mulps xmm4, xmm0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + addps xmm5, xmm4 + mulps xmm7, xmm3 + addps xmm6, xmm5 + addps xmm7, xmm6 + movaps xmmword ptr [eax], xmm7 + // Calculating first 4 elements in the second row of the destination matrix. + movss xmm4, dword ptr [edx+24] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm0 + movss xmm5, dword ptr [edx+28] + shufps xmm5, xmm5, 0 + mulps xmm5, xmm1 + movss xmm6, dword ptr [edx+32] + shufps xmm6, xmm6, 0 + movss xmm7, dword ptr [edx+36] + shufps xmm7, xmm7, 0 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm7, xmm6 + addps xmm5, xmm4 + addps xmm7, xmm5 + // Calculating first 4 elements in the third row of the destination matrix. + movss xmm4, dword ptr [edx+48] + movss xmm5, dword ptr [edx+52] + movlps qword ptr [eax+24], xmm7 ; save 2nd + movhps qword ptr [eax+32], xmm7 ; row + movss xmm6, dword ptr [edx+56] + movss xmm7, dword ptr [edx+60] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm5, xmm4 + addps xmm7, xmm6 + addps xmm7, xmm5 + movaps xmmword ptr [eax+48], xmm7 + // Calculating first 4 elements in the fourth row of the destination matrix. + movss xmm4, dword ptr [edx+72] + movss xmm5, dword ptr [edx+76] + movss xmm6, dword ptr [edx+80] + movss xmm7, dword ptr [edx+84] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm4, xmm5 + addps xmm6, xmm4 + addps xmm7, xmm6 + movlps qword ptr [eax+72], xmm7 + movhps qword ptr [eax+80], xmm7 + // Calculating first 4 elements in the fifth row of the destination matrix. + movss xmm4, dword ptr [edx+96] + movss xmm5, dword ptr [edx+100] + movss xmm6, dword ptr [edx+104] + movss xmm7, dword ptr [edx+108] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm5, xmm4 + addps xmm7, xmm6 + addps xmm7, xmm5 + movaps xmmword ptr [eax+96], xmm7 + // Calculating first 4 elements in the sixth row of the destination matrix. + movss xmm4, dword ptr [edx+120] + movss xmm5, dword ptr [edx+124] + movss xmm6, dword ptr [edx+128] + movss xmm7, dword ptr [edx+132] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm4, xmm5 + addps xmm6, xmm4 + addps xmm7, xmm6 + movhps qword ptr [eax+128], xmm7 + movlps qword ptr [eax+120], xmm7 + // Loading first 4 columns (lower 2 rows) of m2Ptr. + movlps xmm0, qword ptr [ecx+96] + movhps xmm0, qword ptr [ecx+104] + movlps xmm1, qword ptr [ecx+120] + movhps xmm1, qword ptr [ecx+128] + // Calculating first 4 elements in the first row of the destination matrix. + movss xmm2, dword ptr [edx+16] + shufps xmm2, xmm2, 0 + movss xmm4, dword ptr [edx+40] + movss xmm3, dword ptr [edx+20] + movss xmm5, dword ptr [edx+44] + movaps xmm6, xmmword ptr [eax] + movlps xmm7, qword ptr [eax+24] + shufps xmm3, xmm3, 0 + shufps xmm5, xmm5, 0 + movhps xmm7, qword ptr [eax+32] + shufps xmm4, xmm4, 0 + mulps xmm5, xmm1 + mulps xmm2, xmm0 + mulps xmm3, xmm1 + mulps xmm4, xmm0 + addps xmm6, xmm2 + addps xmm7, xmm4 + addps xmm7, xmm5 + addps xmm6, xmm3 + movlps qword ptr [eax+24], xmm7 + movaps xmmword ptr [eax], xmm6 + movhps qword ptr [eax+32], xmm7 + // Calculating first 4 elements in the third row of the destination matrix. + movss xmm2, dword ptr [edx+64] + movss xmm4, dword ptr [edx+88] + movss xmm5, dword ptr [edx+92] + movss xmm3, dword ptr [edx+68] + movaps xmm6, xmmword ptr [eax+48] + movlps xmm7, qword ptr [eax+72] + movhps xmm7, qword ptr [eax+80] + shufps xmm2, xmm2, 0 + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm3, xmm3, 0 + mulps xmm2, xmm0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm1 + addps xmm6, xmm2 + addps xmm6, xmm3 + addps xmm7, xmm4 + addps xmm7, xmm5 + movlps qword ptr [eax+72], xmm7 + movaps xmmword ptr [eax+48], xmm6 + movhps qword ptr [eax+80], xmm7 + // Calculating first 4 elements in the fifth row of the destination matrix. + movss xmm2, dword ptr [edx+112] + movss xmm3, dword ptr [edx+116] + movaps xmm6, xmmword ptr [eax+96] + shufps xmm2, xmm2, 0 + shufps xmm3, xmm3, 0 + mulps xmm2, xmm0 + mulps xmm3, xmm1 + addps xmm6, xmm2 + addps xmm6, xmm3 + movaps xmmword ptr [eax+96], xmm6 + // Calculating first 4 elements in the sixth row of the destination matrix. + movss xmm4, dword ptr [edx+136] + movss xmm5, dword ptr [edx+140] + movhps xmm7, qword ptr [eax+128] + movlps xmm7, qword ptr [eax+120] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + addps xmm7, xmm4 + addps xmm7, xmm5 + // Calculating last 2 columns of the destination matrix. + movlps xmm0, qword ptr [ecx+16] + movhps xmm0, qword ptr [ecx+40] + movhps qword ptr [eax+128], xmm7 + movlps qword ptr [eax+120], xmm7 + movlps xmm2, qword ptr [ecx+64] + movhps xmm2, qword ptr [ecx+88] + movaps xmm3, xmm2 + shufps xmm3, xmm3, 4Eh + movlps xmm4, qword ptr [ecx+112] + movhps xmm4, qword ptr [ecx+136] + movaps xmm5, xmm4 + shufps xmm5, xmm5, 4Eh + movlps xmm6, qword ptr [edx] + movhps xmm6, qword ptr [edx+24] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movlps xmm6, qword ptr [edx+8] + movhps xmm6, qword ptr [edx+32] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movhps xmm6, qword ptr [edx+40] + movlps xmm6, qword ptr [edx+16] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+16], xmm7 + movhps qword ptr [eax+40], xmm7 + movlps xmm6, qword ptr [edx+48] + movhps xmm6, qword ptr [edx+72] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movhps xmm6, qword ptr [edx+80] + movlps xmm6, qword ptr [edx+56] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps xmm6, qword ptr [edx+64] + movhps xmm6, qword ptr [edx+88] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+64], xmm7 + movhps qword ptr [eax+88], xmm7 + movlps xmm6, qword ptr [edx+96] + movhps xmm6, qword ptr [edx+120] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movlps xmm6, qword ptr [edx+104] + movhps xmm6, qword ptr [edx+128] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps xmm6, qword ptr [edx+112] + movhps xmm6, qword ptr [edx+136] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+112], xmm7 + movhps qword ptr [eax+136], xmm7 + } + return; + } + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l] + m1Ptr[5] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr += 6; + } + break; + } + default: { + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumColumns(); n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += m1.GetNumColumns(); + } + break; + } + } +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyMatX + + optimizes the following transpose matrix multiplications: + + Nx6 * NxN + 6xN * 6x6 + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( m1.GetNumRows() == m2.GetNumRows() ); + + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + k = m1.GetNumColumns(); + l = m2.GetNumColumns(); + + switch( m1.GetNumRows() ) { + case 1: + if ( !((k^6)|(l^1)) ) { // 1x6 * 1x1 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + movaps [eax], xmm0 + movlps [eax+16], xmm1 + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 2: + if ( !((k^6)|(l^2)) ) { // 2x6 * 2x2 + #define MUL_2xN_2x2_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi] \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm1, [esi+8] \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_2xN_2x2_ROW2( N, row ) \ + __asm movlps xmm6, [edi+(row+0*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm7, [edi+(row+1*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm0 \ + __asm mulps xmm7, xmm1 \ + __asm addps xmm6, xmm7 \ + __asm movaps [eax+(row*2)*4], xmm6 + + MUL_2xN_2x2_INIT + MUL_2xN_2x2_ROW2( 6, 0 ) + MUL_2xN_2x2_ROW2( 6, 2 ) + MUL_2xN_2x2_ROW2( 6, 4 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 3: + if ( !((k^6)|(l^3)) ) { // 3x6 * 3x3 + + #define MUL_3xN_3x3_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movss xmm0, [esi+(0*3+0)*4] \ + __asm movhps xmm0, [esi+(0*3+1)*4] \ + __asm movss xmm1, [esi+(1*3+0)*4] \ + __asm movhps xmm1, [esi+(1*3+1)*4] \ + __asm movss xmm2, [esi+(2*3+0)*4] \ + __asm movhps xmm2, [esi+(2*3+1)*4] + + #define MUL_3xN_3x3_INIT_ROW4 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 3, 0 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 3, 0 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 2, 3, 0 ) + + #define MUL_3xN_3x3_ROW4( N, row ) \ + __asm movlps xmm3, [edi+(row+0*N+0)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+0)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+0)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+0)*4], xmm3 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm movlps xmm3, [edi+(row+0*N+1)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+1)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+1)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+4)*4], xmm3 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm movlps xmm3, [edi+(row+0*N+2)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+2)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+2)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+8)*4], xmm3 + + #define MUL_3xN_3x3_INIT_ROW4_ROW4 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + #define MUL_3xN_3x3_INIT_ROW4_ROW \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 1, 2, 3 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 2, 3 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 1, 2, 3 ) + + #define MUL_3xN_3x3_ROW( N, row ) \ + __asm movss xmm3, [edi+(row+0*N)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm movss xmm4, [edi+(row+1*N)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm movss xmm5, [edi+(row+2*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movss [eax+(row*3+0)*4], xmm3 \ + __asm movhps [eax+(row*3+1)*4], xmm3 + + MUL_3xN_3x3_INIT + MUL_3xN_3x3_INIT_ROW4 + MUL_3xN_3x3_ROW4( 6, 0 ) + MUL_3xN_3x3_INIT_ROW4_ROW + MUL_3xN_3x3_ROW( 6, 4 ) + MUL_3xN_3x3_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 4: + if ( !((k^6)|(l^4)) ) { // 4x6 * 4x4 + + #define MUL_4xN_4x4_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] \ + __asm movaps xmm3, [esi+48] + + #define MUL_4xN_4x4_ROW( N, row ) \ + __asm movss xmm7, [edi+(row+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(row+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movaps [eax+row*16], xmm7 + + MUL_4xN_4x4_INIT + MUL_4xN_4x4_ROW( 6, 0 ) + MUL_4xN_4x4_ROW( 6, 1 ) + MUL_4xN_4x4_ROW( 6, 2 ) + MUL_4xN_4x4_ROW( 6, 3 ) + MUL_4xN_4x4_ROW( 6, 4 ) + MUL_4xN_4x4_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 5: + if ( !((k^6)|(l^5)) ) { // 5x6 * 5x5 + + #define MUL_5xN_5x5_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 5*4] \ + __asm movhps xmm1, [esi+ 7*4] \ + __asm movlps xmm2, [esi+10*4] \ + __asm movhps xmm2, [esi+12*4] \ + __asm movlps xmm3, [esi+15*4] \ + __asm movhps xmm3, [esi+17*4] \ + __asm movlps xmm4, [esi+20*4] \ + __asm movhps xmm4, [esi+22*4] + + #define MUL_5xN_5x5_ROW( N, row ) \ + __asm movss xmm6, [edi+(row+0*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm0 \ + __asm fld dword ptr [edi+(row+0*N)*4] \ + __asm fmul dword ptr [esi+ 4*4] \ + __asm movss xmm5, [edi+(row+1*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+1*N)*4] \ + __asm fmul dword ptr [esi+ 9*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+2*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+2*N)*4] \ + __asm fmul dword ptr [esi+14*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+3*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+3*N)*4] \ + __asm fmul dword ptr [esi+19*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+4*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+4*N)*4] \ + __asm fmul dword ptr [esi+24*4] \ + __asm faddp st(1),st \ + __asm fstp dword ptr [eax+(row*5+4)*4] \ + __asm movlps [eax+(row*5+0)*4], xmm6 \ + __asm movhps [eax+(row*5+2)*4], xmm6 + + MUL_5xN_5x5_INIT + MUL_5xN_5x5_ROW( 6, 0 ) + MUL_5xN_5x5_ROW( 6, 1 ) + MUL_5xN_5x5_ROW( 6, 2 ) + MUL_5xN_5x5_ROW( 6, 3 ) + MUL_5xN_5x5_ROW( 6, 4 ) + MUL_5xN_5x5_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 6: + if ( !(l^6) ) { + switch( k ) { + case 1: { // 6x1 * 6x6 + #define MUL_6xN_6x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] \ + __asm movlps xmm4, [esi+24*4] \ + __asm movhps xmm4, [esi+26*4] \ + __asm movlps xmm5, [esi+30*4] \ + __asm movhps xmm5, [esi+32*4] + + #define MUL_6xN_6x6_FIRST4COLUMNS_ROW( N, row ) \ + __asm movss xmm7, [edi+(row+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(row+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+4*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+5*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*6+0)*4], xmm7 \ + __asm movhps [eax+(row*6+2)*4], xmm7 + + #define MUL_6xN_6x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm4, [esi+28*4] \ + __asm movlps xmm5, [esi+34*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_6xN_6x6_LAST2COLUMNS_ROW2( N, row ) \ + __asm movlps xmm7, [edi+(row*2+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm7, xmm0 \ + __asm movlps xmm6, [edi+(row*2+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+4*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+5*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*12+ 4)*4], xmm7 \ + __asm movhps [eax+(row*12+10)*4], xmm7 + + #define MUL_6xN_6x6_LAST2COLUMNS_ROW( N, row ) \ + __asm movss xmm7, [edi+(1*N-1)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(2*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(3*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(4*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(5*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(6*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*6+4)*4], xmm7 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 1, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW( 1, 0 ) + + return; + } + case 2: { // 6x2 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 2, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 2, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 2, 0 ) + + return; + } + case 3: { // 6x3 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 2 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 3, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW( 3, 2 ) + + return; + } + case 4: { // 6x4 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 3 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 4, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 4, 1 ) + + return; + } + case 5: { // 6x5 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 3 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 4 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 5, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 5, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW( 5, 4 ) + + return; + } + case 6: { // 6x6 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 3 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 4 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 5 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l] + m1Ptr[5*k] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + default: + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = m1.ToFloatPtr() + i; + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumRows(); n++ ) { + m1Ptr += k; + m2Ptr += l; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } + break; + } +} + +/* +============ +idSIMD_SSE::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + int nc; + const float *lptr; + + if ( skip >= n ) { + return; + } + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + // this code assumes n > 4 + __asm { + push ebx + mov eax, skip // eax = i + shl eax, 2 // eax = i*4 + mov edx, n // edx = n + shl edx, 2 // edx = n*4 + mov esi, x // esi = x + mov edi, lptr // edi = lptr + add esi, eax + add edi, eax + mov ebx, b // ebx = b + + // check for aligned memory + mov ecx, nc + shl ecx, 2 + or ecx, esi + or ecx, edi + and ecx, 15 + jnz loopurow + + // aligned + looprow: + mov ecx, eax + neg ecx + movaps xmm0, [esi+ecx] + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg donedot8 + dot8: + movaps xmm1, [esi+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm3, [esi+ecx-(4*4)] + mulps xmm3, [edi+ecx-(4*4)] + addps xmm0, xmm3 + add ecx, 8*4 + jle dot8 + donedot8: + sub ecx, 4*4 + jg donedot4 + //dot4: + movaps xmm1, [esi+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + donedot4: + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + sub ecx, 4*4 + jz dot0 + add ecx, 4 + jz dot1 + add ecx, 4 + jz dot2 + //dot3: + movss xmm1, [esi-(3*4)] + mulss xmm1, [edi-(3*4)] + addss xmm0, xmm1 + dot2: + movss xmm3, [esi-(2*4)] + mulss xmm3, [edi-(2*4)] + addss xmm0, xmm3 + dot1: + movss xmm5, [esi-(1*4)] + mulss xmm5, [edi-(1*4)] + addss xmm0, xmm5 + dot0: + movss xmm1, [ebx+eax] + subss xmm1, xmm0 + movss [esi], xmm1 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp looprow + + // unaligned + loopurow: + mov ecx, eax + neg ecx + movups xmm0, [esi+ecx] + movups xmm1, [edi+ecx] + mulps xmm0, xmm1 + add ecx, 12*4 + jg doneudot8 + udot8: + movups xmm1, [esi+ecx-(8*4)] + movups xmm2, [edi+ecx-(8*4)] + mulps xmm1, xmm2 + addps xmm0, xmm1 + movups xmm3, [esi+ecx-(4*4)] + movups xmm4, [edi+ecx-(4*4)] + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 8*4 + jle udot8 + doneudot8: + sub ecx, 4*4 + jg doneudot4 + //udot4: + movups xmm1, [esi+ecx-(4*4)] + movups xmm2, [edi+ecx-(4*4)] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add ecx, 4*4 + doneudot4: + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + sub ecx, 4*4 + jz udot0 + add ecx, 4 + jz udot1 + add ecx, 4 + jz udot2 + //udot3: + movss xmm1, [esi-(3*4)] + movss xmm2, [edi-(3*4)] + mulss xmm1, xmm2 + addss xmm0, xmm1 + udot2: + movss xmm3, [esi-(2*4)] + movss xmm4, [edi-(2*4)] + mulss xmm3, xmm4 + addss xmm0, xmm3 + udot1: + movss xmm5, [esi-(1*4)] + movss xmm6, [edi-(1*4)] + mulss xmm5, xmm6 + addss xmm0, xmm5 + udot0: + movss xmm1, [ebx+eax] + subss xmm1, xmm0 + movss [esi], xmm1 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp loopurow + done: + pop ebx + } +} + +/* +============ +idSIMD_SSE::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + +#if 1 + + int i, j, m; + float *xptr; + double s0; + + // if the number of columns is not a multiple of 2 we're screwed for alignment. + // however, if the number of columns is a multiple of 2 but the number of to be + // processed rows is not a multiple of 2 we can still run 8 byte aligned + m = n; + if ( m & 1 ) { + + m--; + x[m] = b[m]; + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows_1: + movlps xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + movhps xmm0, [ebx+eax*4-8] // load b[i-4], b[i-3] + xor ecx, ecx + sub eax, m + neg eax + jz done4x4_1 + process4x4_1: // process 4x4 blocks + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + add edi, edx + movss xmm1, [esi+4*ecx+0] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm3, [edi+0] + movhps xmm3, [edi+8] + add edi, edx + mulps xmm1, xmm2 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+0] + movhps xmm4, [edi+8] + add edi, edx + mulps xmm1, xmm3 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+8] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm5, [edi+0] + movhps xmm5, [edi+8] + add edi, edx + mulps xmm1, xmm4 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+12] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add ecx, 4 + cmp ecx, eax + mulps xmm1, xmm5 + subps xmm0, xmm1 + jl process4x4_1 + done4x4_1: // process left over of the 4 rows + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + movss xmm1, [esi+4*ecx] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm2 + subps xmm0, xmm1 + imul ecx, edx + sub edi, ecx + neg eax + + add eax, m + sub eax, 4 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + sub edi, edx + movss [esi-4], xmm3 // xptr[-1] = s3 + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [edi+8] // lptr[-1*nc+2] * s3 + mulss xmm4, [edi+4] // lptr[-1*nc+1] * s3 + mulss xmm5, [edi+0] // lptr[-1*nc+0] * s3 + subss xmm2, xmm3 + movss [esi-8], xmm2 // xptr[-2] = s2 + movss xmm6, xmm2 + sub edi, edx + subss xmm0, xmm5 + subss xmm1, xmm4 + mulss xmm2, [edi+4] // lptr[-2*nc+1] * s2 + mulss xmm6, [edi+0] // lptr[-2*nc+0] * s2 + subss xmm1, xmm2 + movss [esi-12], xmm1 // xptr[-3] = s1 + subss xmm0, xmm6 + sub edi, edx + cmp eax, 4 + mulss xmm1, [edi+0] // lptr[-3*nc+0] * s1 + subss xmm0, xmm1 + movss [esi-16], xmm0 // xptr[-4] = s0 + jl done4rows_1 + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows_1 + done4rows_1: + pop ebx + } + + } else { + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows: + movlps xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + movhps xmm0, [ebx+eax*4-8] // load b[i-4], b[i-3] + sub eax, m + jz done4x4 + neg eax + xor ecx, ecx + process4x4: // process 4x4 blocks + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + add edi, edx + movss xmm1, [esi+4*ecx+0] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm3, [edi+0] + movhps xmm3, [edi+8] + add edi, edx + mulps xmm1, xmm2 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+0] + movhps xmm4, [edi+8] + add edi, edx + mulps xmm1, xmm3 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+8] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm5, [edi+0] + movhps xmm5, [edi+8] + add edi, edx + mulps xmm1, xmm4 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+12] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add ecx, 4 + cmp ecx, eax + mulps xmm1, xmm5 + subps xmm0, xmm1 + jl process4x4 + imul ecx, edx + sub edi, ecx + neg eax + done4x4: // process left over of the 4 rows + add eax, m + sub eax, 4 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + sub edi, edx + movss [esi-4], xmm3 // xptr[-1] = s3 + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [edi+8] // lptr[-1*nc+2] * s3 + mulss xmm4, [edi+4] // lptr[-1*nc+1] * s3 + mulss xmm5, [edi+0] // lptr[-1*nc+0] * s3 + subss xmm2, xmm3 + movss [esi-8], xmm2 // xptr[-2] = s2 + movss xmm6, xmm2 + sub edi, edx + subss xmm0, xmm5 + subss xmm1, xmm4 + mulss xmm2, [edi+4] // lptr[-2*nc+1] * s2 + mulss xmm6, [edi+0] // lptr[-2*nc+0] * s2 + subss xmm1, xmm2 + movss [esi-12], xmm1 // xptr[-3] = s1 + subss xmm0, xmm6 + sub edi, edx + cmp eax, 4 + mulss xmm1, [edi+0] // lptr[-3*nc+0] * s1 + subss xmm0, xmm1 + movss [esi-16], xmm0 // xptr[-4] = s0 + jl done4rows + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows + done4rows: + pop ebx + } + } + + // process left over rows + for ( i = (m&3)-1; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#else + + int i, j, m; + double s0, s1, s2, s3, t; + const float *lptr2; + float *xptr, *xptr2; + + m = n; + if ( m & 1 ) { + + m--; + x[m] = b[m]; + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + // process 4 rows at a time + for ( i = m; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + xptr2 = xptr; // x + i; + lptr2 = lptr; // ptr = L[i] + i - 4; + for ( j = 0; j < m-i; j += 4 ) { + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + } + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + // process left over of the 4 rows + lptr -= nc; + s0 -= lptr[0] * s3; + s1 -= lptr[1] * s3; + s2 -= lptr[2] * s3; + lptr -= nc; + s0 -= lptr[0] * s2; + s1 -= lptr[1] * s2; + lptr -= nc; + s0 -= lptr[0] * s1; + lptr -= nc; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + + } else { + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + // process 4 rows at a time + for ( i = m; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + xptr2 = xptr; // x + i; + lptr2 = lptr; // ptr = L[i] + i - 4; + for ( j = 0; j < m-i; j += 4 ) { + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + } + // process left over of the 4 rows + lptr -= nc; + s0 -= lptr[0] * s3; + s1 -= lptr[1] * s3; + s2 -= lptr[2] * s3; + lptr -= nc; + s0 -= lptr[0] * s2; + s1 -= lptr[1] * s2; + lptr -= nc; + s0 -= lptr[0] * s1; + lptr -= nc; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < m; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#endif +} + +/* +============ +idSIMD_SSE::MatX_LDLTFactor + + in-place factorization LDL' of the n * n sub-matrix of mat + the reciprocal of the diagonal elements are stored in invDiag + currently assumes the number of columns of mat is a multiple of 4 +============ +*/ +bool VPCALL idSIMD_SSE::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#if 1 + + int j, nc; + float *v, *diag, *invDiagPtr, *mptr; + double s0, s1, s2, sum, d; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + invDiagPtr = invDiag.ToFloatPtr(); + + nc = mat.GetNumColumns(); + + assert( ( nc & 3 ) == 0 ); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiagPtr[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiagPtr[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiagPtr[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiagPtr[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + int ncf = nc * sizeof( float ); + mptr = mat[0]; + + __asm { + xorps xmm2, xmm2 + xorps xmm3, xmm3 + xorps xmm4, xmm4 + + push ebx + mov ebx, 4 + + loopRow: + cmp ebx, n + jge done + + mov ecx, ebx // esi = i + shl ecx, 2 // esi = i * 4 + mov edx, diag // edx = diag + add edx, ecx // edx = &diag[i] + mov edi, ebx // edi = i + imul edi, ncf // edi = i * nc * sizeof( float ) + add edi, mptr // edi = mat[i] + add edi, ecx // edi = &mat[i][i] + mov esi, v // ecx = v + add esi, ecx // ecx = &v[i] + mov eax, invDiagPtr // eax = invDiagPtr + add eax, ecx // eax = &invDiagPtr[i] + neg ecx + + movaps xmm0, [edx+ecx] + mulps xmm0, [edi+ecx] + movaps [esi+ecx], xmm0 + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg doneDot8 + dot8: + movaps xmm1, [edx+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + movaps [esi+ecx-(8*4)], xmm1 + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm2, [edx+ecx-(4*4)] + mulps xmm2, [edi+ecx-(4*4)] + movaps [esi+ecx-(4*4)], xmm2 + mulps xmm2, [edi+ecx-(4*4)] + addps xmm0, xmm2 + add ecx, 8*4 + jle dot8 + doneDot8: + sub ecx, 4*4 + jg doneDot4 + movaps xmm1, [edx+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + movaps [esi+ecx-(4*4)], xmm1 + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + doneDot4: + sub ecx, 2*4 + jg doneDot2 + movlps xmm3, [edx+ecx-(2*4)] + movlps xmm4, [edi+ecx-(2*4)] + mulps xmm3, xmm4 + movlps [esi+ecx-(2*4)], xmm3 + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 2*4 + doneDot2: + sub ecx, 1*4 + jg doneDot1 + movss xmm3, [edx+ecx-(1*4)] + movss xmm4, [edi+ecx-(1*4)] + mulss xmm3, xmm4 + movss [esi+ecx-(1*4)], xmm3 + mulss xmm3, xmm4 + addss xmm0, xmm3 + doneDot1: + movhlps xmm2, xmm0 + addps xmm0, xmm2 + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm2 + movss xmm1, [edi] + subss xmm1, xmm0 + movss [edi], xmm1 // mptr[i] = sum; + movss [edx], xmm1 // diag[i] = sum; + + // if ( sum == 0.0f ) return false; + movaps xmm2, xmm1 + cmpeqss xmm2, SIMD_SP_zero + andps xmm2, SIMD_SP_tiny + orps xmm1, xmm2 + + rcpss xmm7, xmm1 + mulss xmm1, xmm7 + mulss xmm1, xmm7 + addss xmm7, xmm7 + subss xmm7, xmm1 + movss [eax], xmm7 // invDiagPtr[i] = 1.0f / sum; + + mov edx, n // edx = n + sub edx, ebx // edx = n - i + dec edx // edx = n - i - 1 + jle doneSubRow // if ( i + 1 >= n ) return true; + + mov eax, ebx // eax = i + shl eax, 2 // eax = i * 4 + neg eax + + loopSubRow: + add edi, ncf + mov ecx, eax + movaps xmm0, [esi+ecx] + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg doneSubDot8 + subDot8: + movaps xmm1, [esi+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm2, [esi+ecx-(4*4)] + mulps xmm2, [edi+ecx-(4*4)] + addps xmm0, xmm2 + add ecx, 8*4 + jle subDot8 + doneSubDot8: + sub ecx, 4*4 + jg doneSubDot4 + movaps xmm1, [esi+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + doneSubDot4: + sub ecx, 2*4 + jg doneSubDot2 + movlps xmm3, [esi+ecx-(2*4)] + movlps xmm4, [edi+ecx-(2*4)] + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 2*4 + doneSubDot2: + sub ecx, 1*4 + jg doneSubDot1 + movss xmm3, [esi+ecx-(1*4)] + movss xmm4, [edi+ecx-(1*4)] + mulss xmm3, xmm4 + addss xmm0, xmm3 + doneSubDot1: + movhlps xmm2, xmm0 + addps xmm0, xmm2 + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm2 + movss xmm1, [edi] + subss xmm1, xmm0 + mulss xmm1, xmm7 + movss [edi], xmm1 + dec edx + jg loopSubRow + doneSubRow: + inc ebx + jmp loopRow + done: + pop ebx + } + + return true; + +#else + + int i, j, k, nc; + float *v, *diag, *mptr; + double s0, s1, s2, s3, sum, d; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + mptr = mat[i+1]; + for ( j = i+1; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + + return true; + +#endif +} + +/* +============ +idSIMD_SSE::BlendJoints +============ +*/ +void VPCALL idSIMD_SSE::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( blendJoints ); + assert_16_byte_aligned( JOINTQUAT_Q_OFFSET ); + assert_16_byte_aligned( JOINTQUAT_T_OFFSET ); + + ALIGN16( float jointQuat0[4]; ) + ALIGN16( float jointQuat1[4]; ) + ALIGN16( float jointQuat2[4]; ) + ALIGN16( float jointQuat3[4]; ) + ALIGN16( float blendQuat0[4]; ) + ALIGN16( float blendQuat1[4]; ) + ALIGN16( float blendQuat2[4]; ) + ALIGN16( float blendQuat3[4]; ) + int a0, a1, a2, a3; + + __asm { + movss xmm7, lerp + cmpnless xmm7, SIMD_SP_zero + movmskps ecx, xmm7 + test ecx, 1 + jz done1 + + mov eax, numJoints + shl eax, 2 + mov esi, joints + mov edi, blendJoints + mov edx, index + + add edx, eax + neg eax + jz done1 + + movss xmm7, lerp + cmpnltss xmm7, SIMD_SP_one + movmskps ecx, xmm7 + test ecx, 1 + jz lerpJoints + + loopCopy: + mov ecx, [edx+eax] + shl ecx, JOINTQUAT_SIZE_SHIFT + + add eax, 1*4 + + movaps xmm0, [edi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm1, [edi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_Q_OFFSET], xmm0 + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm1 + + jl loopCopy + + jmp done1 + + lerpJoints: + add eax, 4*4 + jge done4 + + loopJoint4: + movss xmm3, lerp + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov ecx, [edx+eax-4*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a0, ecx + + // lerp first translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load first quaternions + movaps xmm0, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm4, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-3*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a1, ecx + + // lerp second translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load second quaternions + movaps xmm1, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm5, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-2*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a2, ecx + + // lerp third translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load third quaternions + movaps xmm2, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm6, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-1*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a3, ecx + + // lerp fourth translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load fourth quaternions + movaps xmm3, [esi+ecx+JOINTQUAT_Q_OFFSET] + + TRANSPOSE_4x4( xmm0, xmm1, xmm2, xmm3, xmm7 ) + + movaps jointQuat0, xmm0 + movaps jointQuat1, xmm1 + movaps jointQuat2, xmm2 + movaps jointQuat3, xmm3 + + movaps xmm7, [edi+ecx+JOINTQUAT_Q_OFFSET] + + TRANSPOSE_4x4( xmm4, xmm5, xmm6, xmm7, xmm3 ) + + movaps blendQuat0, xmm4 + movaps blendQuat1, xmm5 + movaps blendQuat2, xmm6 + movaps blendQuat3, xmm7 + + // lerp quaternions + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movaps xmm3, jointQuat3 + mulps xmm3, blendQuat3 + addps xmm0, xmm3 // xmm0 = cosom + + movaps xmm1, xmm0 + movaps xmm2, xmm0 + andps xmm1, SIMD_SP_signBit // xmm1 = signBit + xorps xmm0, xmm1 + mulps xmm2, xmm2 + + xorps xmm4, xmm4 + movaps xmm3, SIMD_SP_one + subps xmm3, xmm2 // xmm3 = scale0 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 + + movaps xmm2, xmm3 + rsqrtps xmm4, xmm2 + mulps xmm2, xmm4 + mulps xmm2, xmm4 + subps xmm2, SIMD_SP_rsqrt_c0 + mulps xmm4, SIMD_SP_rsqrt_c1 + mulps xmm2, xmm4 + mulps xmm3, xmm2 // xmm3 = sqrt( scale0 ) + + // omega0 = atan2( xmm3, xmm0 ) + movaps xmm4, xmm0 + minps xmm0, xmm3 + maxps xmm3, xmm4 + cmpeqps xmm4, xmm0 + + rcpps xmm5, xmm3 + mulps xmm3, xmm5 + mulps xmm3, xmm5 + addps xmm5, xmm5 + subps xmm5, xmm3 // xmm5 = 1 / y or 1 / x + mulps xmm0, xmm5 // xmm0 = x / y or y / x + movaps xmm3, xmm4 + andps xmm3, SIMD_SP_signBit + xorps xmm0, xmm3 // xmm0 = -x / y or y / x + andps xmm4, SIMD_SP_halfPI // xmm4 = HALF_PI or 0.0f + movaps xmm3, xmm0 + mulps xmm3, xmm3 // xmm3 = s + movaps xmm5, SIMD_SP_atan_c0 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c1 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c2 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c3 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c4 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c5 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c6 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c7 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_one + mulps xmm5, xmm0 + addps xmm5, xmm4 // xmm5 = omega0 + + movss xmm6, lerp // xmm6 = lerp + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm5 // xmm6 = omega1 + subps xmm5, xmm6 // xmm5 = omega0 + + // scale0 = sin( xmm5 ) * xmm2 + // scale1 = sin( xmm6 ) * xmm2 + movaps xmm3, xmm5 + movaps xmm7, xmm6 + mulps xmm3, xmm3 + mulps xmm7, xmm7 + movaps xmm4, SIMD_SP_sin_c0 + movaps xmm0, SIMD_SP_sin_c0 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c1 + addps xmm0, SIMD_SP_sin_c1 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c2 + addps xmm0, SIMD_SP_sin_c2 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c3 + addps xmm0, SIMD_SP_sin_c3 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c4 + addps xmm0, SIMD_SP_sin_c4 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_one + addps xmm0, SIMD_SP_one + mulps xmm5, xmm4 + mulps xmm6, xmm0 + mulps xmm5, xmm2 // xmm5 = scale0 + mulps xmm6, xmm2 // xmm6 = scale1 + + xorps xmm6, xmm1 + + movaps xmm0, jointQuat0 + mulps xmm0, xmm5 + movaps xmm1, blendQuat0 + mulps xmm1, xmm6 + addps xmm0, xmm1 + + movaps xmm1, jointQuat1 + mulps xmm1, xmm5 + movaps xmm2, blendQuat1 + mulps xmm2, xmm6 + addps xmm1, xmm2 + + movaps xmm2, jointQuat2 + mulps xmm2, xmm5 + movaps xmm3, blendQuat2 + mulps xmm3, xmm6 + addps xmm2, xmm3 + + movaps xmm3, jointQuat3 + mulps xmm3, xmm5 + movaps xmm4, blendQuat3 + mulps xmm4, xmm6 + addps xmm3, xmm4 + + add eax, 4*4 + + // transpose xmm0, xmm1, xmm2, xmm3 to memory + movaps xmm7, xmm0 + movaps xmm6, xmm2 + + unpcklps xmm0, xmm1 + unpcklps xmm2, xmm3 + + mov ecx, a0 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm0 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm2 + + mov ecx, a1 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm0 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm2 + + unpckhps xmm7, xmm1 + unpckhps xmm6, xmm3 + + mov ecx, a2 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm7 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm6 + + mov ecx, a3 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm7 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm6 + + jle loopJoint4 + + done4: + sub eax, 4*4 + jz done1 + + loopJoint1: + movss xmm3, lerp + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov ecx, [edx+eax] + shl ecx, JOINTQUAT_SIZE_SHIFT + + // lerp first translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load first quaternions + movaps xmm0, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm1, [edi+ecx+JOINTQUAT_Q_OFFSET] + + movaps jointQuat0, xmm0 + movaps blendQuat0, xmm1 + + // lerp quaternions + mulps xmm1, xmm0 + movhlps xmm0, xmm1 + addps xmm1, xmm0 + movaps xmm0, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 2, 3 ) + addss xmm0, xmm1 // xmm0 = cosom + + movss xmm1, xmm0 + movss xmm2, xmm0 + andps xmm1, SIMD_SP_signBit // xmm1 = signBit + xorps xmm0, xmm1 + mulss xmm2, xmm2 + + xorps xmm4, xmm4 + movss xmm3, SIMD_SP_one + subss xmm3, xmm2 // xmm3 = scale0 + cmpeqss xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 + + movss xmm2, xmm3 + rsqrtss xmm4, xmm2 + mulss xmm2, xmm4 + mulss xmm2, xmm4 + subss xmm2, SIMD_SP_rsqrt_c0 + mulss xmm4, SIMD_SP_rsqrt_c1 + mulss xmm2, xmm4 + mulss xmm3, xmm2 // xmm3 = sqrt( scale0 ) + + // omega0 = atan2( xmm3, xmm0 ) + movss xmm4, xmm0 + minss xmm0, xmm3 + maxss xmm3, xmm4 + cmpeqss xmm4, xmm0 + + rcpss xmm5, xmm3 + mulss xmm3, xmm5 + mulss xmm3, xmm5 + addss xmm5, xmm5 + subss xmm5, xmm3 // xmm5 = 1 / y or 1 / x + mulss xmm0, xmm5 // xmm0 = x / y or y / x + movss xmm3, xmm4 + andps xmm3, SIMD_SP_signBit + xorps xmm0, xmm3 // xmm0 = -x / y or y / x + andps xmm4, SIMD_SP_halfPI // xmm4 = HALF_PI or 0.0f + movss xmm3, xmm0 + mulss xmm3, xmm3 // xmm3 = s + movss xmm5, SIMD_SP_atan_c0 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c1 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c2 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c3 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c4 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c5 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c6 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c7 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_one + mulss xmm5, xmm0 + addss xmm5, xmm4 // xmm5 = omega0 + + movss xmm6, lerp // xmm6 = lerp + mulss xmm6, xmm5 // xmm6 = omega1 + subss xmm5, xmm6 // xmm5 = omega0 + + // scale0 = sin( xmm5 ) * xmm2 + // scale1 = sin( xmm6 ) * xmm2 + movss xmm3, xmm5 + movss xmm7, xmm6 + mulss xmm3, xmm3 + mulss xmm7, xmm7 + movss xmm4, SIMD_SP_sin_c0 + movss xmm0, SIMD_SP_sin_c0 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c1 + addss xmm0, SIMD_SP_sin_c1 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c2 + addss xmm0, SIMD_SP_sin_c2 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c3 + addss xmm0, SIMD_SP_sin_c3 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c4 + addss xmm0, SIMD_SP_sin_c4 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_one + addss xmm0, SIMD_SP_one + mulss xmm5, xmm4 + mulss xmm6, xmm0 + mulss xmm5, xmm2 // xmm5 = scale0 + mulss xmm6, xmm2 // xmm6 = scale1 + + xorps xmm6, xmm1 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, jointQuat0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, blendQuat0 + addps xmm5, xmm6 + + movaps [esi+ecx+JOINTQUAT_Q_OFFSET], xmm5 + + add eax, 1*4 + jl loopJoint1 + + done1: + } + +#else + + int i; + + if ( lerp <= 0.0f ) { + return; + } else if ( lerp >= 1.0f ) { + for ( i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j] = blendJoints[j]; + } + return; + } + + for ( i = 0; i <= numJoints - 4; i += 4 ) { + ALIGN16( float jointQuat0[4]; ) + ALIGN16( float jointQuat1[4]; ) + ALIGN16( float jointQuat2[4]; ) + ALIGN16( float jointQuat3[4]; ) + ALIGN16( float blendQuat0[4]; ) + ALIGN16( float blendQuat1[4]; ) + ALIGN16( float blendQuat2[4]; ) + ALIGN16( float blendQuat3[4]; ) + + for ( int j = 0; j < 4; j++ ) { + int n = index[i+j]; + + joints[n].t[0] = lerp * ( blendJoints[n].t[0] - joints[n].t[0] ); + joints[n].t[1] = lerp * ( blendJoints[n].t[1] - joints[n].t[1] ); + joints[n].t[2] = lerp * ( blendJoints[n].t[2] - joints[n].t[2] ); + + jointQuat0[j] = joints[n].q[0]; + jointQuat1[j] = joints[n].q[1]; + jointQuat2[j] = joints[n].q[2]; + jointQuat3[j] = joints[n].q[3]; + + blendQuat0[j] = blendJoints[n].q[0]; + blendQuat1[j] = blendJoints[n].q[1]; + blendQuat2[j] = blendJoints[n].q[2]; + blendQuat3[j] = blendJoints[n].q[3]; + } + + jointVert0[0] += lerp * ( blendVert0[0] - jointVert0[0] ); + jointVert0[1] += lerp * ( blendVert0[1] - jointVert0[1] ); + jointVert0[2] += lerp * ( blendVert0[2] - jointVert0[2] ); + jointVert0[3] += lerp * ( blendVert0[3] - jointVert0[3] ); + + jointVert1[0] += lerp * ( blendVert1[0] - jointVert1[0] ); + jointVert1[1] += lerp * ( blendVert1[1] - jointVert1[1] ); + jointVert1[2] += lerp * ( blendVert1[2] - jointVert1[2] ); + jointVert1[3] += lerp * ( blendVert1[3] - jointVert1[3] ); + + jointVert2[0] += lerp * ( blendVert2[0] - jointVert2[0] ); + jointVert2[1] += lerp * ( blendVert2[1] - jointVert2[1] ); + jointVert2[2] += lerp * ( blendVert2[2] - jointVert2[2] ); + jointVert2[3] += lerp * ( blendVert2[3] - jointVert2[3] ); + + ALIGN16( float cosom[4]; ) + ALIGN16( float sinom[4]; ) + ALIGN16( float omega0[4]; ) + ALIGN16( float omega1[4]; ) + ALIGN16( float scale0[4]; ) + ALIGN16( float scale1[4]; ) + ALIGN16( unsigned long signBit[4]; ) + + cosom[0] = jointQuat0[0] * blendQuat0[0]; + cosom[1] = jointQuat0[1] * blendQuat0[1]; + cosom[2] = jointQuat0[2] * blendQuat0[2]; + cosom[3] = jointQuat0[3] * blendQuat0[3]; + + cosom[0] += jointQuat1[0] * blendQuat1[0]; + cosom[1] += jointQuat1[1] * blendQuat1[1]; + cosom[2] += jointQuat1[2] * blendQuat1[2]; + cosom[3] += jointQuat1[3] * blendQuat1[3]; + + cosom[0] += jointQuat2[0] * blendQuat2[0]; + cosom[1] += jointQuat2[1] * blendQuat2[1]; + cosom[2] += jointQuat2[2] * blendQuat2[2]; + cosom[3] += jointQuat2[3] * blendQuat2[3]; + + cosom[0] += jointQuat3[0] * blendQuat3[0]; + cosom[1] += jointQuat3[1] * blendQuat3[1]; + cosom[2] += jointQuat3[2] * blendQuat3[2]; + cosom[3] += jointQuat3[3] * blendQuat3[3]; + + signBit[0] = (*(unsigned long *)&cosom[0]) & ( 1 << 31 ); + signBit[1] = (*(unsigned long *)&cosom[1]) & ( 1 << 31 ); + signBit[2] = (*(unsigned long *)&cosom[2]) & ( 1 << 31 ); + signBit[3] = (*(unsigned long *)&cosom[3]) & ( 1 << 31 ); + + (*(unsigned long *)&cosom[0]) ^= signBit[0]; + (*(unsigned long *)&cosom[1]) ^= signBit[1]; + (*(unsigned long *)&cosom[2]) ^= signBit[2]; + (*(unsigned long *)&cosom[3]) ^= signBit[3]; + + scale0[0] = 1.0f - cosom[0] * cosom[0]; + scale0[1] = 1.0f - cosom[1] * cosom[1]; + scale0[2] = 1.0f - cosom[2] * cosom[2]; + scale0[3] = 1.0f - cosom[3] * cosom[3]; + + scale0[0] = ( scale0[0] <= 0.0f ) ? SIMD_SP_tiny[0] : scale0[0]; + scale0[1] = ( scale0[1] <= 0.0f ) ? SIMD_SP_tiny[1] : scale0[1]; + scale0[2] = ( scale0[2] <= 0.0f ) ? SIMD_SP_tiny[2] : scale0[2]; + scale0[3] = ( scale0[3] <= 0.0f ) ? SIMD_SP_tiny[3] : scale0[3]; + + sinom[0] = SSE_ReciprocalSqrt( scale0[0] ); + sinom[1] = SSE_ReciprocalSqrt( scale0[1] ); + sinom[2] = SSE_ReciprocalSqrt( scale0[2] ); + sinom[3] = SSE_ReciprocalSqrt( scale0[3] ); + + scale0[0] *= sinom[0]; + scale0[1] *= sinom[1]; + scale0[2] *= sinom[2]; + scale0[3] *= sinom[3]; + + // NOTE: scale0 and cosom are always positive + omega0[0] = SSE_ATanPositive( scale0[0], cosom[0] ); + omega0[1] = SSE_ATanPositive( scale0[1], cosom[1] ); + omega0[2] = SSE_ATanPositive( scale0[2], cosom[2] ); + omega0[3] = SSE_ATanPositive( scale0[3], cosom[3] ); + + omega1[0] = lerp * omega0[0]; + omega1[1] = lerp * omega0[1]; + omega1[2] = lerp * omega0[2]; + omega1[3] = lerp * omega0[3]; + + omega0[0] -= omega1[0]; + omega0[1] -= omega1[1]; + omega0[2] -= omega1[2]; + omega0[3] -= omega1[3]; + + // NOTE: omega0 is always in the range [0, PI/2] + scale0[0] = SSE_SinZeroHalfPI( omega0[0] ) * sinom[0]; + scale0[1] = SSE_SinZeroHalfPI( omega0[1] ) * sinom[1]; + scale0[2] = SSE_SinZeroHalfPI( omega0[2] ) * sinom[2]; + scale0[3] = SSE_SinZeroHalfPI( omega0[3] ) * sinom[3]; + + // NOTE: omega1 is always in the range [0, PI/2] + scale1[0] = SSE_SinZeroHalfPI( omega1[0] ) * sinom[0]; + scale1[1] = SSE_SinZeroHalfPI( omega1[1] ) * sinom[1]; + scale1[2] = SSE_SinZeroHalfPI( omega1[2] ) * sinom[2]; + scale1[3] = SSE_SinZeroHalfPI( omega1[3] ) * sinom[3]; + + (*(unsigned long *)&scale1[0]) ^= signBit[0]; + (*(unsigned long *)&scale1[1]) ^= signBit[1]; + (*(unsigned long *)&scale1[2]) ^= signBit[2]; + (*(unsigned long *)&scale1[3]) ^= signBit[3]; + + jointQuat0[0] = scale0[0] * jointQuat0[0] + scale1[0] * blendQuat0[0]; + jointQuat0[1] = scale0[1] * jointQuat0[1] + scale1[1] * blendQuat0[1]; + jointQuat0[2] = scale0[2] * jointQuat0[2] + scale1[2] * blendQuat0[2]; + jointQuat0[3] = scale0[3] * jointQuat0[3] + scale1[3] * blendQuat0[3]; + + jointQuat1[0] = scale0[0] * jointQuat1[0] + scale1[0] * blendQuat1[0]; + jointQuat1[1] = scale0[1] * jointQuat1[1] + scale1[1] * blendQuat1[1]; + jointQuat1[2] = scale0[2] * jointQuat1[2] + scale1[2] * blendQuat1[2]; + jointQuat1[3] = scale0[3] * jointQuat1[3] + scale1[3] * blendQuat1[3]; + + jointQuat2[0] = scale0[0] * jointQuat2[0] + scale1[0] * blendQuat2[0]; + jointQuat2[1] = scale0[1] * jointQuat2[1] + scale1[1] * blendQuat2[1]; + jointQuat2[2] = scale0[2] * jointQuat2[2] + scale1[2] * blendQuat2[2]; + jointQuat2[3] = scale0[3] * jointQuat2[3] + scale1[3] * blendQuat2[3]; + + jointQuat3[0] = scale0[0] * jointQuat3[0] + scale1[0] * blendQuat3[0]; + jointQuat3[1] = scale0[1] * jointQuat3[1] + scale1[1] * blendQuat3[1]; + jointQuat3[2] = scale0[2] * jointQuat3[2] + scale1[2] * blendQuat3[2]; + jointQuat3[3] = scale0[3] * jointQuat3[3] + scale1[3] * blendQuat3[3]; + + for ( int j = 0; j < 4; j++ ) { + int n = index[i+j]; + + joints[n].q[0] = jointQuat0[j]; + joints[n].q[1] = jointQuat1[j]; + joints[n].q[2] = jointQuat2[j]; + joints[n].q[3] = jointQuat3[j]; + } + } + + for ( ; i < numJoints; i++ ) { + int n = index[i]; + + idVec3 &jointVert = joints[n].t; + const idVec3 &blendVert = blendJoints[n].t; + + jointVert[0] += lerp * ( blendVert[0] - jointVert[0] ); + jointVert[1] += lerp * ( blendVert[1] - jointVert[1] ); + jointVert[2] += lerp * ( blendVert[2] - jointVert[2] ); + + idQuat &jointQuat = joints[n].q; + const idQuat &blendQuat = blendJoints[n].q; + + float cosom; + float sinom; + float omega; + float scale0; + float scale1; + unsigned long signBit; + + cosom = jointQuat.x * blendQuat.x + jointQuat.y * blendQuat.y + jointQuat.z * blendQuat.z + jointQuat.w * blendQuat.w; + + signBit = (*(unsigned long *)&cosom) & ( 1 << 31 ); + + (*(unsigned long *)&cosom) ^= signBit; + + scale0 = 1.0f - cosom * cosom; + scale0 = ( scale0 <= 0.0f ) ? SIMD_SP_tiny[0] : scale0; + sinom = SSE_ReciprocalSqrt( scale0 ); + omega = SSE_ATanPositive( scale0 * sinom, cosom ); + scale0 = SSE_SinZeroHalfPI( ( 1.0f - lerp ) * omega ) * sinom; + scale1 = SSE_SinZeroHalfPI( lerp * omega ) * sinom; + + (*(unsigned long *)&scale1) ^= signBit; + + jointQuat.x = scale0 * jointQuat.x + scale1 * blendQuat.x; + jointQuat.y = scale0 * jointQuat.y + scale1 * blendQuat.y; + jointQuat.z = scale0 * jointQuat.z + scale1 * blendQuat.z; + jointQuat.w = scale0 * jointQuat.w + scale1 * blendQuat.w; + } + +#endif +} + +/* +============ +idSIMD_SSE::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + assert_16_byte_aligned( jointQuats ); + + __asm { + mov eax, numJoints + shl eax, JOINTQUAT_SIZE_SHIFT + mov esi, jointQuats + mov edi, jointMats + + add esi, eax + neg eax + jz done + + loopQuat: + movaps xmm0, [esi+eax+JOINTQUAT_Q_OFFSET] // xmm0 = q.x, q.y, q.z, q.w + movaps xmm6, [esi+eax+JOINTQUAT_T_OFFSET] // xmm6 = t.x, t.y, t.z, w + + add edi, JOINTMAT_SIZE + + movaps xmm1, xmm0 // xmm1 = x, y, z, w + addps xmm1, xmm1 // xmm1 = x2, y2, z2, w2 + + add eax, JOINTQUAT_SIZE + + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 1 ) // xmm2 = y, x, x, y + movaps xmm3, xmm1 + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 1, 2, 2 ) // xmm3 = y2, y2, z2, z2 + mulps xmm2, xmm3 // xmm2 = yy2, xy2, xz2, yz2 + + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 3, 3, 3 ) // xmm4 = z, w, w, w + movaps xmm5, xmm1 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 1, 0 ) // xmm5 = z2, z2, y2, x2 + mulps xmm4, xmm5 // xmm4 = zz2, wz2, wy2, wx2 + + mulss xmm0, xmm1 // xmm0 = xx2, y2, z2, w2 + + // calculate the last two elements of the third row + movss xmm7, SIMD_SP_one // xmm7 = 1, 0, 0, 0 + subss xmm7, xmm0 // xmm7 = -xx2+1, 0, 0, 0 + subss xmm7, xmm2 // xmm7 = -xx2-yy2+1, 0, 0, 0 + shufps xmm7, xmm6, R_SHUFFLE_PS( 0, 1, 2, 3 ) // xmm7 = -xx2-yy2+1, 0, t.z, w + + // calcluate first row + xorps xmm2, SIMD_SP_quat2mat_x0 // xmm2 = yy2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x1 // xmm4 = -zz2, wz2, -wy2, -wx2 + addss xmm4, SIMD_SP_one // xmm4 = -zz2+1, wz2, -wy2, -wx2 + movaps xmm3, xmm4 // xmm3 = -zz2+1, wz2, -wy2, -wx2 + subps xmm3, xmm2 // xmm3 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movaps [edi-JOINTMAT_SIZE+0*16+0*4], xmm3 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movss [edi-JOINTMAT_SIZE+0*16+3*4], xmm6 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, t.x + + // calculate second row + movss xmm2, xmm0 // xmm2 = xx2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x2 // xmm4 = -zz2+1, -wz2, wy2, wx2 + subps xmm4, xmm2 // xmm4 = -xx2-zz2+1, xy2-wz2, xz2+wy2, yz2+wx2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) // xmm6 = t.y, t.z, w, t.x + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 0, 3, 2 ) // xmm4 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movaps [edi-JOINTMAT_SIZE+1*16+0*4], xmm4 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movss [edi-JOINTMAT_SIZE+1*16+3*4], xmm6 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, t.y + + // calculate third row + movhlps xmm3, xmm4 // xmm3 = yz2+wx2, xz2+wy2, xz2-wy2, yz2-wx2 + shufps xmm3, xmm7, R_SHUFFLE_PS( 1, 3, 0, 2 ) // xmm3 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + movaps [edi-JOINTMAT_SIZE+2*16+0*4], xmm3 // row2 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + + jl loopQuat + + done: + } + +#else + + for ( int i = 0; i < numJoints; i++ ) { + const float *q = &jointQuats[i].q; + float *m = jointMats[i].mat; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + float w2 = q[3] + q[3]; + + float yy2 = q[1] * y2; + float xy2 = q[0] * y2; + float xz2 = q[0] * z2; + float yz2 = q[1] * z2; + + float zz2 = q[2] * z2; + float wz2 = q[3] * z2; + float wy2 = q[3] * y2; + float wx2 = q[3] * x2; + + float xx2 = q[0] * x2; + + m[0*4+0] = - yy2 - zz2 + 1.0f; + m[0*4+1] = xy2 + wz2; + m[0*4+2] = xz2 - wy2; + m[0*4+3] = q[4]; + + m[1*4+0] = xy2 - wz2; + m[1*4+1] = - xx2 - zz2 + 1.0f; + m[1*4+2] = yz2 + wx2; + m[1*4+3] = q[5]; + + m[2*4+0] = xz2 + wy2; + m[2*4+1] = yz2 - wx2; + m[2*4+2] = - xx2 - yy2 + 1.0f; + m[2*4+3] = q[6]; + } + +#endif +} + +/* +============ +idSIMD_SSE::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { +#if 1 + + ALIGN16( byte shuffle[16]; ) + + __asm { + mov eax, numJoints + mov esi, jointMats + mov edi, jointQuats + and eax, ~3 + jz done4 + imul eax, JOINTMAT_SIZE + add esi, eax + neg eax + + loopMat4: + movss xmm5, [esi+eax+3*JOINTMAT_SIZE+0*16+0*4] + movss xmm6, [esi+eax+3*JOINTMAT_SIZE+1*16+1*4] + movss xmm7, [esi+eax+3*JOINTMAT_SIZE+2*16+2*4] + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+2*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+2*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+2*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+1*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+1*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+1*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+0*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+0*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+0*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + // ------------------- + + movaps xmm0, xmm5 + addps xmm0, xmm6 + addps xmm0, xmm7 + cmpnltps xmm0, SIMD_SP_zero // xmm0 = m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f + + movaps xmm1, xmm5 + movaps xmm2, xmm5 + cmpnltps xmm1, xmm6 + cmpnltps xmm2, xmm7 + andps xmm2, xmm1 // xmm2 = m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] + + movaps xmm4, xmm6 + cmpnltps xmm4, xmm7 // xmm3 = m[1 * 4 + 1] > m[2 * 4 + 2] + + movaps xmm1, xmm0 + andnps xmm1, xmm2 + orps xmm2, xmm0 + movaps xmm3, xmm2 + andnps xmm2, xmm4 + orps xmm3, xmm2 + xorps xmm3, SIMD_SP_not + + andps xmm0, SIMD_DW_mat2quatShuffle0 + movaps xmm4, xmm1 + andps xmm4, SIMD_DW_mat2quatShuffle1 + orps xmm0, xmm4 + movaps xmm4, xmm2 + andps xmm4, SIMD_DW_mat2quatShuffle2 + orps xmm0, xmm4 + movaps xmm4, xmm3 + andps xmm4, SIMD_DW_mat2quatShuffle3 + orps xmm4, xmm0 + + movaps shuffle, xmm4 + + movaps xmm0, xmm2 + orps xmm0, xmm3 // xmm0 = xmm2 | xmm3 = s0 + orps xmm2, xmm1 // xmm2 = xmm1 | xmm2 = s2 + orps xmm1, xmm3 // xmm1 = xmm1 | xmm3 = s1 + + andps xmm0, SIMD_SP_signBit + andps xmm1, SIMD_SP_signBit + andps xmm2, SIMD_SP_signBit + + xorps xmm5, xmm0 + xorps xmm6, xmm1 + xorps xmm7, xmm2 + addps xmm5, xmm6 + addps xmm7, SIMD_SP_one + addps xmm5, xmm7 // xmm5 = t + + movaps xmm7, xmm5 // xmm7 = t + rsqrtps xmm6, xmm5 + mulps xmm5, xmm6 + mulps xmm5, xmm6 + subps xmm5, SIMD_SP_rsqrt_c0 + mulps xmm6, SIMD_SP_mat2quat_rsqrt_c1 + mulps xmm6, xmm5 // xmm5 = s + + mulps xmm7, xmm6 // xmm7 = s * t + xorps xmm6, SIMD_SP_signBit // xmm6 = -s + + // ------------------- + + add edi, 4*JOINTQUAT_SIZE + + movzx ecx, byte ptr shuffle[0*4+0] // ecx = k0 + movss [edi+ecx*4-4*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[0*4+1] // edx = k1 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-4*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[0*4+2] // ecx = k2 + movss xmm3, [esi+eax+0*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+0*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-4*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[0*4+3] // edx = k3 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-4*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+0*JOINTMAT_SIZE+0*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+0*JOINTMAT_SIZE+1*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+0*JOINTMAT_SIZE+2*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-4*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[1*4+0] // ecx = k0 + movss [edi+ecx*4-3*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[1*4+1] // edx = k1 + movss xmm4, [esi+eax+1*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+1*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-3*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[1*4+2] // ecx = k2 + movss xmm3, [esi+eax+1*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+1*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-3*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[1*4+3] // edx = k3 + movss xmm4, [esi+eax+1*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+1*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-3*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+1*JOINTMAT_SIZE+0*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+1*JOINTMAT_SIZE+1*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+1*JOINTMAT_SIZE+2*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-3*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[2*4+0] // ecx = k0 + movss [edi+ecx*4-2*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[2*4+1] // edx = k1 + movss xmm4, [esi+eax+2*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+2*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-2*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[2*4+2] // ecx = k2 + movss xmm3, [esi+eax+2*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+2*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-2*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[2*4+3] // edx = k3 + movss xmm4, [esi+eax+2*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+2*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-2*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+2*JOINTMAT_SIZE+0*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+2*JOINTMAT_SIZE+1*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+2*JOINTMAT_SIZE+2*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-2*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[3*4+0] // ecx = k0 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[3*4+1] // edx = k1 + movss xmm4, [esi+eax+3*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+3*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[3*4+2] // ecx = k2 + movss xmm3, [esi+eax+3*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+3*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[3*4+3] // edx = k3 + movss xmm4, [esi+eax+3*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+3*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+3*JOINTMAT_SIZE+0*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+3*JOINTMAT_SIZE+1*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+3*JOINTMAT_SIZE+2*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-1*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + add eax, 4*JOINTMAT_SIZE + jl loopMat4 + + done4: + mov eax, numJoints + and eax, 3 + jz done1 + imul eax, JOINTMAT_SIZE + add esi, eax + neg eax + + loopMat1: + movss xmm5, [esi+eax+0*JOINTMAT_SIZE+0*16+0*4] + movss xmm6, [esi+eax+0*JOINTMAT_SIZE+1*16+1*4] + movss xmm7, [esi+eax+0*JOINTMAT_SIZE+2*16+2*4] + + // ------------------- + + movaps xmm0, xmm5 + addss xmm0, xmm6 + addss xmm0, xmm7 + cmpnltss xmm0, SIMD_SP_zero // xmm0 = m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f + + movaps xmm1, xmm5 + movaps xmm2, xmm5 + cmpnltss xmm1, xmm6 + cmpnltss xmm2, xmm7 + andps xmm2, xmm1 // xmm2 = m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] + + movaps xmm4, xmm6 + cmpnltss xmm4, xmm7 // xmm3 = m[1 * 4 + 1] > m[2 * 4 + 2] + + movaps xmm1, xmm0 + andnps xmm1, xmm2 + orps xmm2, xmm0 + movaps xmm3, xmm2 + andnps xmm2, xmm4 + orps xmm3, xmm2 + xorps xmm3, SIMD_SP_not + + andps xmm0, SIMD_DW_mat2quatShuffle0 + movaps xmm4, xmm1 + andps xmm4, SIMD_DW_mat2quatShuffle1 + orps xmm0, xmm4 + movaps xmm4, xmm2 + andps xmm4, SIMD_DW_mat2quatShuffle2 + orps xmm0, xmm4 + movaps xmm4, xmm3 + andps xmm4, SIMD_DW_mat2quatShuffle3 + orps xmm4, xmm0 + + movss shuffle, xmm4 + + movaps xmm0, xmm2 + orps xmm0, xmm3 // xmm0 = xmm2 | xmm3 = s0 + orps xmm2, xmm1 // xmm2 = xmm1 | xmm2 = s2 + orps xmm1, xmm3 // xmm1 = xmm1 | xmm3 = s1 + + andps xmm0, SIMD_SP_signBit + andps xmm1, SIMD_SP_signBit + andps xmm2, SIMD_SP_signBit + + xorps xmm5, xmm0 + xorps xmm6, xmm1 + xorps xmm7, xmm2 + addss xmm5, xmm6 + addss xmm7, SIMD_SP_one + addss xmm5, xmm7 // xmm5 = t + + movss xmm7, xmm5 // xmm7 = t + rsqrtss xmm6, xmm5 + mulss xmm5, xmm6 + mulss xmm5, xmm6 + subss xmm5, SIMD_SP_rsqrt_c0 + mulss xmm6, SIMD_SP_mat2quat_rsqrt_c1 + mulss xmm6, xmm5 // xmm5 = s + + mulss xmm7, xmm6 // xmm7 = s * t + xorps xmm6, SIMD_SP_signBit // xmm6 = -s + + // ------------------- + + movzx ecx, byte ptr shuffle[0] // ecx = k0 + add edi, JOINTQUAT_SIZE + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[1] // edx = k1 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[2] // ecx = k2 + movss xmm3, [esi+eax+0*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+0*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[3] // edx = k3 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+0*JOINTMAT_SIZE+0*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+0*JOINTMAT_SIZE+1*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+0*JOINTMAT_SIZE+2*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-1*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + add eax, JOINTMAT_SIZE + jl loopMat1 + + done1: + } + +#else + + compile_time_assert( (UINT_PTR)(&((idJointQuat *)0)->t) == (UINT_PTR)(&((idJointQuat *)0)->q) + (UINT_PTR)sizeof( ((idJointQuat *)0)->q ) ); + + for ( int i = 0; i < numJoints; i++ ) { + float s0, s1, s2; + int k0, k1, k2, k3; + + float *q = jointQuats[i].q.ToFloatPtr(); + const float *m = jointMats[i].ToFloatPtr(); + + if ( m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f ) { + + k0 = 3; + k1 = 2; + k2 = 1; + k3 = 0; + s0 = 1.0f; + s1 = 1.0f; + s2 = 1.0f; + + } else if ( m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] ) { + + k0 = 0; + k1 = 1; + k2 = 2; + k3 = 3; + s0 = 1.0f; + s1 = -1.0f; + s2 = -1.0f; + + } else if ( m[1 * 4 + 1] > m[2 * 4 + 2] ) { + + k0 = 1; + k1 = 0; + k2 = 3; + k3 = 2; + s0 = -1.0f; + s1 = 1.0f; + s2 = -1.0f; + + } else { + + k0 = 2; + k1 = 3; + k2 = 0; + k3 = 1; + s0 = -1.0f; + s1 = -1.0f; + s2 = 1.0f; + + } + + float t = s0 * m[0 * 4 + 0] + s1 * m[1 * 4 + 1] + s2 * m[2 * 4 + 2] + 1.0f; + float s = idMath::InvSqrt( t ) * 0.5f; + + q[k0] = s * t; + q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + q[4] = m[0 * 4 + 3]; + q[5] = m[1 * 4 + 3]; + q[6] = m[2 * 4 + 3]; + q[7] = 0.0f; + } + +#endif +} + +/* +============ +idSIMD_SSE::TransformJoints +============ +*/ +void VPCALL idSIMD_SSE::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov ecx, firstJoint + mov eax, lastJoint + sub eax, ecx + jl done + shl ecx, 2 // ecx = firstJoint * 4 + mov edi, parents + add edi, ecx // edx = &parents[firstJoint] + lea ecx, [ecx+ecx*2] + shl ecx, 2 // ecx = firstJoint * JOINTMAT_SIZE + mov esi, jointMats // esi = jointMats + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + add edi, eax + neg eax + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movaps xmm7, [esi+edx+ 0] + movaps xmm4, xmm7 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + movaps xmm5, xmm7 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + + add ecx, JOINTMAT_SIZE + add eax, 4 + + movaps xmm6, xmm7 + shufps xmm6, xmm6, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm4, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+ 0], xmm4 + + movaps xmm3, [esi+edx+16] + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + movaps xmm6, xmm3 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm4, xmm2 + addps xmm5, xmm4 + andps xmm3, SIMD_SP_clearFirstThree + addps xmm5, xmm3 + + movaps [esi+ecx-JOINTMAT_SIZE+16], xmm5 + + movaps xmm7, [esi+edx+32] + movaps xmm6, xmm7 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm4, xmm7 + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm6, xmm4 + movaps xmm3, xmm7 + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm6, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+32], xmm6 + + jle loopJoint + done: + } + +#else + + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE::UntransformJoints +============ +*/ +void VPCALL idSIMD_SSE::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov edx, firstJoint + mov eax, lastJoint + mov ecx, eax + sub eax, edx + jl done + mov esi, jointMats // esi = jointMats + lea ecx, [ecx+ecx*2] + shl ecx, 4 // ecx = lastJoint * JOINTMAT_SIZE + shl edx, 2 + mov edi, parents + add edi, edx // edi = &parents[firstJoint] + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movss xmm6, [esi+edx+12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm0, xmm6 + movss xmm7, [esi+edx+28] + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm1, xmm7 + movss xmm3, [esi+edx+44] + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm2, xmm3 + + sub ecx, JOINTMAT_SIZE + sub eax, 4 + + movss xmm4, [esi+edx+ 0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + movss xmm5, [esi+edx+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + movss xmm6, [esi+edx+32] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+ 0], xmm4 + + movss xmm5, [esi+edx+ 4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + movss xmm6, [esi+edx+20] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + movss xmm7, [esi+edx+36] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm5, xmm7 + + movaps [esi+ecx+JOINTMAT_SIZE+16], xmm5 + + movss xmm6, [esi+edx+ 8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm7, [esi+edx+24] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm3, [esi+edx+40] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + + movaps [esi+ecx+JOINTMAT_SIZE+32], xmm6 + + jge loopJoint + done: + } + +#else + + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE::MultiplyJoints +============ +*/ +void VPCALL idSIMD_SSE::MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopJoint: + + movaps xmm0, [edx+eax+0] + movaps xmm1, [edx+eax+16] + movaps xmm2, [edx+eax+32] + + movss xmm3, [ecx+eax+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + movss xmm4, [ecx+eax+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + movss xmm5, [ecx+eax+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + movss xmm6, [ecx+eax+12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm3, xmm6 + + movaps [edi+eax+0], xmm3 + + movss xmm7, [ecx+eax+16] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm0 + movss xmm4, [ecx+eax+20] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm7, xmm4 + movss xmm5, [ecx+eax+24] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm7, xmm5 + movss xmm6, [ecx+eax+28] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm7, xmm6 + + movaps [edi+eax+16], xmm7 + + movss xmm3, [ecx+eax+32] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + movss xmm4, [ecx+eax+36] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + movss xmm5, [ecx+eax+40] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + movss xmm6, [ecx+eax+44] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm3, xmm6 + + movaps [edi+eax+32], xmm3 + + add eax, JOINTMAT_SIZE + jl loopJoint + done: + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } + +#endif +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm5, xmm6 // xmm5 = m6+m8, m7+t2 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm5 = m7+t2, m6+m8 + addss xmm5, xmm6 // xmm5 = m6+m8+m7+t2 + movss xmm6, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm5 + + movaps xmm7, xmm6 + minps xmm7, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm7 + movaps tmpMax, xmm6 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + movss xmm2, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm5, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + movaps xmm7, xmm6 + shufps xmm7, xmm7, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + movaps xmm7, xmm6 + shufps xmm7, xmm7, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TracePointCull +============ +*/ +void VPCALL idSIMD_SSE::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + assert( sizeof( idDrawVert ) == DRAWVERT_SIZE ); + assert( (int)&((idDrawVert *)0)->xyz == DRAWVERT_XYZ_OFFSET ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + + mov edi, planes + movlps xmm1, [edi] // xmm1 = 0, 1, X, X + movhps xmm1, [edi+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [edi+8] // xmm3 = 2, 3, X, X + movhps xmm3, [edi+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [edi+32] // xmm4 = 8, 9, X, X + movhps xmm4, [edi+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [edi+40] // xmm5 = 10, 11, X, X + movhps xmm5, [edi+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + movss xmm7, radius + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + xor edx, edx + mov esi, verts + mov edi, cullBits + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert: + movss xmm4, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + mulps xmm4, xmm0 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulps xmm5, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + addps xmm4, xmm5 + mulps xmm6, xmm2 + addps xmm4, xmm3 + addps xmm4, xmm6 + movaps xmm5, xmm4 + xorps xmm5, SIMD_SP_signBit + cmpltps xmm4, xmm7 + movmskps ecx, xmm4 + cmpltps xmm5, xmm7 + movmskps ebx, xmm5 + shl cx, 4 + or cl, bl + inc edi + or dl, cl + add eax, DRAWVERT_SIZE + mov byte ptr [edi-1], cl + jl loopVert + + done: + mov esi, totalOr + mov byte ptr [esi], dl + pop ebx + } + +#else + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; + +#endif +} + +/* +============ +idSIMD_SSE::DecalPointCull +============ +*/ +void VPCALL idSIMD_SSE::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + ALIGN16( float p0[4]; ) + ALIGN16( float p1[4]; ) + ALIGN16( float p2[4]; ) + ALIGN16( float p3[4]; ) + ALIGN16( float p4[4]; ) + ALIGN16( float p5[4]; ) + ALIGN16( float p6[4]; ) + ALIGN16( float p7[4]; ) + + __asm { + mov ecx, planes + movlps xmm1, [ecx] // xmm1 = 0, 1, X, X + movhps xmm1, [ecx+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [ecx+8] // xmm3 = 2, 3, X, X + movhps xmm3, [ecx+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [ecx+32] // xmm4 = 8, 9, X, X + movhps xmm4, [ecx+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [ecx+40] // xmm5 = 10, 11, X, X + movhps xmm5, [ecx+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + + movaps p0, xmm0 + movaps p1, xmm1 + movaps p2, xmm2 + movaps p3, xmm3 + + movlps xmm4, [ecx+64] // xmm4 = p40, p41, X, X + movhps xmm4, [ecx+80] // xmm4 = p40, p41, p50, p51 + movaps xmm5, xmm4 // xmm5 = p40, p41, p50, p51 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm4 = p40, p50, p40, p50 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm5 = p41, p51, p41, p51 + movlps xmm6, [ecx+72] // xmm6 = p42, p43, X, X + movhps xmm6, [ecx+88] // xmm6 = p42, p43, p52, p53 + movaps xmm7, xmm6 // xmm7 = p42, p43, p52, p53 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm6 = p42, p52, p42, p52 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm7 = p43, p53, p43, p53 + + movaps p4, xmm4 + movaps p5, xmm5 + movaps p6, xmm6 + movaps p7, xmm7 + + mov esi, verts + mov edi, cullBits + mov eax, numVerts + and eax, ~1 + jz done2 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert2: + movaps xmm6, p0 + movss xmm0, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + movaps xmm6, p0 + movss xmm3, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm3 + movaps xmm7, p1 + movss xmm4, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps edx, xmm6 + mov ch, dl + + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, p4 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, p5 + addps xmm0, xmm1 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + add edi, 2 + + mov dh, dl + shl dl, 4 + shl dh, 2 + and edx, (3<<4)|(3<<12) + or ecx, edx + + add eax, 2*DRAWVERT_SIZE + mov word ptr [edi-2], cx + jl loopVert2 + + done2: + + mov eax, numVerts + and eax, 1 + jz done + + movaps xmm6, p0 + movss xmm0, [esi+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + mulps xmm0, p4 + mulps xmm1, p5 + addps xmm0, xmm1 + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + and edx, 3 + shl edx, 4 + or ecx, edx + + mov byte ptr [edi], cl + + done: + } + + +#else + + int i; + + for ( i = 0; i < numVerts; i += 2 ) { + unsigned short bits0, bits1; + float d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11; + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = planes[0][0] * v0[0] + planes[0][1] * v0[1] + planes[0][2] * v0[2] + planes[0][3]; + d1 = planes[1][0] * v0[0] + planes[1][1] * v0[1] + planes[1][2] * v0[2] + planes[1][3]; + d2 = planes[2][0] * v0[0] + planes[2][1] * v0[1] + planes[2][2] * v0[2] + planes[2][3]; + d3 = planes[3][0] * v0[0] + planes[3][1] * v0[1] + planes[3][2] * v0[2] + planes[3][3]; + + d4 = planes[4][0] * v0[0] + planes[4][1] * v0[1] + planes[4][2] * v0[2] + planes[4][3]; + d5 = planes[5][0] * v0[0] + planes[5][1] * v0[1] + planes[5][2] * v0[2] + planes[5][3]; + d10 = planes[4][0] * v1[0] + planes[4][1] * v1[1] + planes[4][2] * v1[2] + planes[4][3]; + d11 = planes[5][0] * v1[0] + planes[5][1] * v1[1] + planes[5][2] * v1[2] + planes[5][3]; + + d6 = planes[0][0] * v1[0] + planes[0][1] * v1[1] + planes[0][2] * v1[2] + planes[0][3]; + d7 = planes[1][0] * v1[0] + planes[1][1] * v1[1] + planes[1][2] * v1[2] + planes[1][3]; + d8 = planes[2][0] * v1[0] + planes[2][1] * v1[1] + planes[2][2] * v1[2] + planes[2][3]; + d9 = planes[3][0] * v1[0] + planes[3][1] * v1[1] + planes[3][2] * v1[2] + planes[3][3]; + + bits0 = FLOATSIGNBITSET( d0 ) << (0+0); + bits0 |= FLOATSIGNBITSET( d1 ) << (0+1); + bits0 |= FLOATSIGNBITSET( d2 ) << (0+2); + bits0 |= FLOATSIGNBITSET( d3 ) << (0+3); + bits0 |= FLOATSIGNBITSET( d4 ) << (0+4); + bits0 |= FLOATSIGNBITSET( d5 ) << (0+5); + + bits1 = FLOATSIGNBITSET( d6 ) << (8+0); + bits1 |= FLOATSIGNBITSET( d7 ) << (8+1); + bits1 |= FLOATSIGNBITSET( d8 ) << (8+2); + bits1 |= FLOATSIGNBITSET( d9 ) << (8+3); + bits1 |= FLOATSIGNBITSET( d10 ) << (8+4); + bits1 |= FLOATSIGNBITSET( d11 ) << (8+5); + + *(unsigned short *)(cullBits + i) = ( bits0 | bits1 ) ^ 0x3F3F; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[numVerts - 1].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + d4 = planes[4][0] * v[0] + planes[4][1] * v[1] + planes[4][2] * v[2] + planes[4][3]; + d5 = planes[5][0] * v[0] + planes[5][1] * v[1] + planes[5][2] * v[2] + planes[5][3]; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[numVerts - 1] = bits ^ 0x3F; // flip lower 6 bits + } + +#endif +} + +/* +============ +idSIMD_SSE::OverlayPointCull +============ +*/ +void VPCALL idSIMD_SSE::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + __asm { + mov eax, numVerts + mov edx, verts + mov esi, texCoords + mov edi, cullBits + + mov ecx, planes + movss xmm4, [ecx+ 0] + movss xmm5, [ecx+16] + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm5, [ecx+ 4] + movss xmm6, [ecx+20] + shufps xmm5, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm6, [ecx+ 8] + movss xmm7, [ecx+24] + shufps xmm6, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm7, [ecx+12] + movss xmm0, [ecx+28] + shufps xmm7, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) + + and eax, ~1 + jz done2 + add edi, eax + neg eax + + loopVert2: + movss xmm0, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movss xmm3, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movaps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 3, 2, 3 ) + add edx, 2*DRAWVERT_SIZE + movmskps ecx, xmm0 + mov byte ptr [edi+eax+0], cl + add esi, 4*4 + movmskps ecx, xmm1 + mov byte ptr [edi+eax+1], cl + add eax, 2 + jl loopVert2 + + done2: + mov eax, numVerts + and eax, 1 + jz done + + movss xmm0, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movlps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movmskps ecx, xmm0 + mov byte ptr [edi], cl + + done: + } + +#else + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + + for ( int i = 0; i < numVerts - 1; i += 2 ) { + unsigned short bits; + float d0, d1, d2, d3; + + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + d2 = p0[0] * v1[0] + p0[1] * v1[1] + p0[2] * v1[2] + p0[3]; + d3 = p1[0] * v1[0] + p1[1] * v1[1] + p1[2] * v1[2] + p1[3]; + + texCoords[i+0][0] = d0; + texCoords[i+0][1] = d1; + texCoords[i+1][0] = d2; + texCoords[i+1][1] = d3; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 8; + bits |= FLOATSIGNBITSET( d3 ) << 9; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + d2 = 1.0f - d2; + d3 = 1.0f - d3; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + bits |= FLOATSIGNBITSET( d2 ) << 10; + bits |= FLOATSIGNBITSET( d3 ) << 11; + + *(unsigned short *)(cullBits + i) = bits; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1; + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + const idVec3 &v0 = verts[numVerts - 1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[numVerts - 1] = bits; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTriPlanes +============ +*/ +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +void VPCALL idSIMD_SSE::DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { +#if 1 + + int d, a; + int n = numIndexes / 3; + ALIGN16( float x0[4] ); + ALIGN16( float x1[4] ); + ALIGN16( float x2[4] ); + + __asm { + push ebx + mov eax, n + shl eax, 4 + mov esi, verts + mov edi, indexes + mov edx, planes + + add edx, eax + neg eax + + mov d, edx + + add eax, 4*16 + jge done4 + + loopPlane4: + mov a, eax + + mov ecx, [edi+0*12+0] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+0] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+0] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+0] + shl eax, DRAWVERT_SIZE_SHIFT + + movlps xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, X, X */ + movss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, X, X */ + movhps xmm4, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, 4, 5 */ + movhps xmm5, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm3, xmm4 /* xmm3 = 0, 1, 4, 5 */ + shufps xmm3, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm3 = 0, 4, 8, 12 */ + shufps xmm4, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm4 = 1, 5, 9, 13 */ + shufps xmm5, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm5 = 2, 6, 10, 14 */ + + mov ecx, [edi+0*12+4] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+4] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+4] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+4] + shl eax, DRAWVERT_SIZE_SHIFT + + movaps x0, xmm3 + movaps x1, xmm4 + movaps x2, xmm5 + + movlps xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, X, X, X */ + movhps xmm1, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + shufps xmm2, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + mov ecx, [edi+0*12+8] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+8] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+8] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+8] + shl eax, DRAWVERT_SIZE_SHIFT + + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movlps xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, X, X */ + movss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, X, X */ + movhps xmm4, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, 4, 5 */ + movhps xmm5, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm3, xmm4 /* xmm3 = 0, 1, 4, 5 */ + shufps xmm3, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm3 = 0, 4, 8, 12 */ + shufps xmm4, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm4 = 1, 5, 9, 13 */ + shufps xmm5, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm5 = 2, 6, 10, 14 */ + + mov eax, a + mov edx, d + add edi, 4*12 + + subps xmm3, x0 + subps xmm4, x1 + subps xmm5, x2 + + movaps xmm6, xmm4 + mulps xmm6, xmm2 + movaps xmm7, xmm5 + mulps xmm7, xmm1 + subps xmm6, xmm7 + + mulps xmm5, xmm0 + mulps xmm2, xmm3 + subps xmm5, xmm2 + + mulps xmm3, xmm1 + mulps xmm4, xmm0 + subps xmm3, xmm4 + + add eax, 4*16 + + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm1, xmm5 + mulps xmm5, xmm5 + movaps xmm2, xmm3 + mulps xmm3, xmm3 + + addps xmm3, xmm5 + addps xmm3, xmm6 + rsqrtps xmm3, xmm3 + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm4, x0 + movaps xmm5, x1 + movaps xmm6, x2 + + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + + addps xmm4, xmm5 + addps xmm4, xmm6 + xorps xmm4, SIMD_SP_signBit + + // transpose xmm0, xmm1, xmm2, xmm4 to memory + movaps xmm7, xmm0 + movaps xmm5, xmm2 + + unpcklps xmm0, xmm1 + unpcklps xmm2, xmm4 + + movlps [edx+eax-8*16+0], xmm0 + movlps [edx+eax-8*16+8], xmm2 + + movhps [edx+eax-7*16+0], xmm0 + movhps [edx+eax-7*16+8], xmm2 + + unpckhps xmm7, xmm1 + unpckhps xmm5, xmm4 + + movlps [edx+eax-6*16+0], xmm7 + movlps [edx+eax-6*16+8], xmm5 + + movhps [edx+eax-5*16+0], xmm7 + movhps [edx+eax-5*16+8], xmm5 + + jle loopPlane4 + + done4: + + sub eax, 4*16 + jge done + + loopPlane1: + mov ecx, [edi+0] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+4] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+8] + shl edx, DRAWVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] + subss xmm0, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+ebx+DRAWVERT_XYZ_OFFSET+4] + subss xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] + subss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + movss xmm3, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + subss xmm3, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+4] + subss xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + movss xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + subss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + movss xmm6, xmm4 + mulss xmm6, xmm2 + movss xmm7, xmm5 + mulss xmm7, xmm1 + subss xmm6, xmm7 + + add edi, 1*12 + + mulss xmm5, xmm0 + mulss xmm2, xmm3 + subss xmm5, xmm2 + + mulss xmm3, xmm1 + mulss xmm4, xmm0 + subss xmm3, xmm4 + + mov edx, d + + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm1, xmm5 + mulss xmm5, xmm5 + movss xmm2, xmm3 + mulss xmm3, xmm3 + + add eax, 1*16 + + addss xmm3, xmm5 + addss xmm3, xmm6 + rsqrtss xmm3, xmm3 + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [edx+eax-1*16+0], xmm0 + movss [edx+eax-1*16+4], xmm1 + movss [edx+eax-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + mulss xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + mulss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx+eax-1*16+12], xmm0 + + jl loopPlane1 + + done: + pop ebx + } + +#else + + int i, j; + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a, *b, *c; + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + + d3[j] = c->xyz[0] - a->xyz[0]; + d4[j] = c->xyz[1] - a->xyz[1]; + d5[j] = c->xyz[2] - a->xyz[2]; + } + + ALIGN16( float tmp[4]; ) + + n0[0] = d4[0] * d2[0]; + n0[1] = d4[1] * d2[1]; + n0[2] = d4[2] * d2[2]; + n0[3] = d4[3] * d2[3]; + + n0[0] -= d5[0] * d1[0]; + n0[1] -= d5[1] * d1[1]; + n0[2] -= d5[2] * d1[2]; + n0[3] -= d5[3] * d1[3]; + + n1[0] = d5[0] * d0[0]; + n1[1] = d5[1] * d0[1]; + n1[2] = d5[2] * d0[2]; + n1[3] = d5[3] * d0[3]; + + n1[0] -= d3[0] * d2[0]; + n1[1] -= d3[1] * d2[1]; + n1[2] -= d3[2] * d2[2]; + n1[3] -= d3[3] * d2[3]; + + n2[0] = d3[0] * d1[0]; + n2[1] = d3[1] * d1[1]; + n2[2] = d3[2] * d1[2]; + n2[3] = d3[3] * d1[3]; + + n2[0] -= d4[0] * d0[0]; + n2[1] -= d4[1] * d0[1]; + n2[2] -= d4[2] * d0[2]; + n2[3] -= d4[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a; + + a = verts + indexes[i + j * 3]; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + } + } + + for ( ; i < numIndexes; i += 3 ) { + const idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4, d5; + float n0, n1, n2; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + + d3 = c->xyz[0] - a->xyz[0]; + d4 = c->xyz[1] - a->xyz[1]; + d5 = c->xyz[2] - a->xyz[2]; + + float tmp; + + n0 = d4 * d2 - d5 * d1; + n1 = d5 * d0 - d3 * d2; + n2 = d3 * d1 - d4 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTangents +============ +*/ +//#define REFINE_TANGENT_SQUAREROOT +#define FIX_DEGENERATE_TANGENT + +void VPCALL idSIMD_SSE::DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + int i; + + assert( planes != NULL ); + assert( verts != NULL ); + assert( numVerts >= 0 ); + +#ifdef REFINE_TANGENT_SQUAREROOT + __asm { + movaps xmm6, SIMD_SP_rsqrt_c0 + movaps xmm7, SIMD_SP_rsqrt_c1 + } +#endif + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + idDrawVert *a, *b, *c; + ALIGN16( unsigned long signBit[4]; ) + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float d6[4]; ) + ALIGN16( float d7[4]; ) + ALIGN16( float d8[4]; ) + ALIGN16( float d9[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + ALIGN16( float t0[4]; ) + ALIGN16( float t1[4]; ) + ALIGN16( float t2[4]; ) + ALIGN16( float t3[4]; ) + ALIGN16( float t4[4]; ) + ALIGN16( float t5[4]; ) + + for ( int j = 0; j < 4; j++ ) { + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + d3[j] = b->st[0] - a->st[0]; + d4[j] = b->st[1] - a->st[1]; + + d5[j] = c->xyz[0] - a->xyz[0]; + d6[j] = c->xyz[1] - a->xyz[1]; + d7[j] = c->xyz[2] - a->xyz[2]; + d8[j] = c->st[0] - a->st[0]; + d9[j] = c->st[1] - a->st[1]; + } + +#if 1 + + __asm { + // normal + movaps xmm0, d6 + mulps xmm0, d2 + movaps xmm1, d7 + mulps xmm1, d1 + subps xmm0, xmm1 + + movaps xmm1, d7 + mulps xmm1, d0 + movaps xmm2, d5 + mulps xmm2, d2 + subps xmm1, xmm2 + + movaps xmm2, d5 + mulps xmm2, d1 + movaps xmm3, d6 + mulps xmm3, d0 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + mulps xmm0, xmm3 + movaps n0, xmm0 + mulps xmm1, xmm3 + movaps n1, xmm1 + mulps xmm2, xmm3 + movaps n2, xmm2 + + // area sign bit + movaps xmm0, d3 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d8 + subps xmm0, xmm1 + andps xmm0, SIMD_SP_signBit + movaps signBit, xmm0 + + // first tangent + movaps xmm0, d0 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d5 + subps xmm0, xmm1 + + movaps xmm1, d1 + mulps xmm1, d9 + movaps xmm2, d4 + mulps xmm2, d6 + subps xmm1, xmm2 + + movaps xmm2, d2 + mulps xmm2, d9 + movaps xmm3, d4 + mulps xmm3, d7 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulps xmm0, xmm3 + movaps t0, xmm0 + mulps xmm1, xmm3 + movaps t1, xmm1 + mulps xmm2, xmm3 + movaps t2, xmm2 + + // second tangent + movaps xmm0, d3 + mulps xmm0, d5 + movaps xmm1, d0 + mulps xmm1, d8 + subps xmm0, xmm1 + + movaps xmm1, d3 + mulps xmm1, d6 + movaps xmm2, d1 + mulps xmm2, d8 + subps xmm1, xmm2 + + movaps xmm2, d3 + mulps xmm2, d7 + movaps xmm3, d2 + mulps xmm3, d8 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulps xmm0, xmm3 + movaps t3, xmm0 + mulps xmm1, xmm3 + movaps t4, xmm1 + mulps xmm2, xmm3 + movaps t5, xmm2 + } + +#else + + ALIGN16( float tmp[4]; ) + + // normal + n0[0] = d6[0] * d2[0]; + n0[1] = d6[1] * d2[1]; + n0[2] = d6[2] * d2[2]; + n0[3] = d6[3] * d2[3]; + + n0[0] -= d7[0] * d1[0]; + n0[1] -= d7[1] * d1[1]; + n0[2] -= d7[2] * d1[2]; + n0[3] -= d7[3] * d1[3]; + + n1[0] = d7[0] * d0[0]; + n1[1] = d7[1] * d0[1]; + n1[2] = d7[2] * d0[2]; + n1[3] = d7[3] * d0[3]; + + n1[0] -= d5[0] * d2[0]; + n1[1] -= d5[1] * d2[1]; + n1[2] -= d5[2] * d2[2]; + n1[3] -= d5[3] * d2[3]; + + n2[0] = d5[0] * d1[0]; + n2[1] = d5[1] * d1[1]; + n2[2] = d5[2] * d1[2]; + n2[3] = d5[3] * d1[3]; + + n2[0] -= d6[0] * d0[0]; + n2[1] -= d6[1] * d0[1]; + n2[2] -= d6[2] * d0[2]; + n2[3] -= d6[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + // area sign bit + tmp[0] = d3[0] * d9[0]; + tmp[1] = d3[1] * d9[1]; + tmp[2] = d3[2] * d9[2]; + tmp[3] = d3[3] * d9[3]; + + tmp[0] -= d4[0] * d8[0]; + tmp[1] -= d4[1] * d8[1]; + tmp[2] -= d4[2] * d8[2]; + tmp[3] -= d4[3] * d8[3]; + + signBit[0] = ( *(unsigned long *)&tmp[0] ) & ( 1 << 31 ); + signBit[1] = ( *(unsigned long *)&tmp[1] ) & ( 1 << 31 ); + signBit[2] = ( *(unsigned long *)&tmp[2] ) & ( 1 << 31 ); + signBit[3] = ( *(unsigned long *)&tmp[3] ) & ( 1 << 31 ); + + // first tangent + t0[0] = d0[0] * d9[0]; + t0[1] = d0[1] * d9[1]; + t0[2] = d0[2] * d9[2]; + t0[3] = d0[3] * d9[3]; + + t0[0] -= d4[0] * d5[0]; + t0[1] -= d4[1] * d5[1]; + t0[2] -= d4[2] * d5[2]; + t0[3] -= d4[3] * d5[3]; + + t1[0] = d1[0] * d9[0]; + t1[1] = d1[1] * d9[1]; + t1[2] = d1[2] * d9[2]; + t1[3] = d1[3] * d9[3]; + + t1[0] -= d4[0] * d6[0]; + t1[1] -= d4[1] * d6[1]; + t1[2] -= d4[2] * d6[2]; + t1[3] -= d4[3] * d6[3]; + + t2[0] = d2[0] * d9[0]; + t2[1] = d2[1] * d9[1]; + t2[2] = d2[2] * d9[2]; + t2[3] = d2[3] * d9[3]; + + t2[0] -= d4[0] * d7[0]; + t2[1] -= d4[1] * d7[1]; + t2[2] -= d4[2] * d7[2]; + t2[3] -= d4[3] * d7[3]; + + tmp[0] = t0[0] * t0[0]; + tmp[1] = t0[1] * t0[1]; + tmp[2] = t0[2] * t0[2]; + tmp[3] = t0[3] * t0[3]; + + tmp[0] += t1[0] * t1[0]; + tmp[1] += t1[1] * t1[1]; + tmp[2] += t1[2] * t1[2]; + tmp[3] += t1[3] * t1[3]; + + tmp[0] += t2[0] * t2[0]; + tmp[1] += t2[1] * t2[1]; + tmp[2] += t2[2] * t2[2]; + tmp[3] += t2[3] * t2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + *(unsigned long *)&tmp[0] ^= signBit[0]; + *(unsigned long *)&tmp[1] ^= signBit[1]; + *(unsigned long *)&tmp[2] ^= signBit[2]; + *(unsigned long *)&tmp[3] ^= signBit[3]; + + t0[0] *= tmp[0]; + t0[1] *= tmp[1]; + t0[2] *= tmp[2]; + t0[3] *= tmp[3]; + + t1[0] *= tmp[0]; + t1[1] *= tmp[1]; + t1[2] *= tmp[2]; + t1[3] *= tmp[3]; + + t2[0] *= tmp[0]; + t2[1] *= tmp[1]; + t2[2] *= tmp[2]; + t2[3] *= tmp[3]; + + // second tangent + t3[0] = d3[0] * d5[0]; + t3[1] = d3[1] * d5[1]; + t3[2] = d3[2] * d5[2]; + t3[3] = d3[3] * d5[3]; + + t3[0] -= d0[0] * d8[0]; + t3[1] -= d0[1] * d8[1]; + t3[2] -= d0[2] * d8[2]; + t3[3] -= d0[3] * d8[3]; + + t4[0] = d3[0] * d6[0]; + t4[1] = d3[1] * d6[1]; + t4[2] = d3[2] * d6[2]; + t4[3] = d3[3] * d6[3]; + + t4[0] -= d1[0] * d8[0]; + t4[1] -= d1[1] * d8[1]; + t4[2] -= d1[2] * d8[2]; + t4[3] -= d1[3] * d8[3]; + + t5[0] = d3[0] * d7[0]; + t5[1] = d3[1] * d7[1]; + t5[2] = d3[2] * d7[2]; + t5[3] = d3[3] * d7[3]; + + t5[0] -= d2[0] * d8[0]; + t5[1] -= d2[1] * d8[1]; + t5[2] -= d2[2] * d8[2]; + t5[3] -= d2[3] * d8[3]; + + tmp[0] = t3[0] * t3[0]; + tmp[1] = t3[1] * t3[1]; + tmp[2] = t3[2] * t3[2]; + tmp[3] = t3[3] * t3[3]; + + tmp[0] += t4[0] * t4[0]; + tmp[1] += t4[1] * t4[1]; + tmp[2] += t4[2] * t4[2]; + tmp[3] += t4[3] * t4[3]; + + tmp[0] += t5[0] * t5[0]; + tmp[1] += t5[1] * t5[1]; + tmp[2] += t5[2] * t5[2]; + tmp[3] += t5[3] * t5[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + *(unsigned long *)&tmp[0] ^= signBit[0]; + *(unsigned long *)&tmp[1] ^= signBit[1]; + *(unsigned long *)&tmp[2] ^= signBit[2]; + *(unsigned long *)&tmp[3] ^= signBit[3]; + + t3[0] *= tmp[0]; + t3[1] *= tmp[1]; + t3[2] *= tmp[2]; + t3[3] *= tmp[3]; + + t4[0] *= tmp[0]; + t4[1] *= tmp[1]; + t4[2] *= tmp[2]; + t4[3] *= tmp[3]; + + t5[0] *= tmp[0]; + t5[1] *= tmp[1]; + t5[2] *= tmp[2]; + t5[3] *= tmp[3]; + +#endif + + for ( int j = 0; j < 4; j++ ) { + + const int v0 = indexes[i + j * 3 + 0]; + const int v1 = indexes[i + j * 3 + 1]; + const int v2 = indexes[i + j * 3 + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + + if ( used[v0] ) { + a->normal[0] += n0[j]; + a->normal[1] += n1[j]; + a->normal[2] += n2[j]; + + a->tangents[0][0] += t0[j]; + a->tangents[0][1] += t1[j]; + a->tangents[0][2] += t2[j]; + + a->tangents[1][0] += t3[j]; + a->tangents[1][1] += t4[j]; + a->tangents[1][2] += t5[j]; + } else { + a->normal[0] = n0[j]; + a->normal[1] = n1[j]; + a->normal[2] = n2[j]; + + a->tangents[0][0] = t0[j]; + a->tangents[0][1] = t1[j]; + a->tangents[0][2] = t2[j]; + + a->tangents[1][0] = t3[j]; + a->tangents[1][1] = t4[j]; + a->tangents[1][2] = t5[j]; + + used[v0] = true; + } + + if ( used[v1] ) { + b->normal[0] += n0[j]; + b->normal[1] += n1[j]; + b->normal[2] += n2[j]; + + b->tangents[0][0] += t0[j]; + b->tangents[0][1] += t1[j]; + b->tangents[0][2] += t2[j]; + + b->tangents[1][0] += t3[j]; + b->tangents[1][1] += t4[j]; + b->tangents[1][2] += t5[j]; + } else { + b->normal[0] = n0[j]; + b->normal[1] = n1[j]; + b->normal[2] = n2[j]; + + b->tangents[0][0] = t0[j]; + b->tangents[0][1] = t1[j]; + b->tangents[0][2] = t2[j]; + + b->tangents[1][0] = t3[j]; + b->tangents[1][1] = t4[j]; + b->tangents[1][2] = t5[j]; + + used[v1] = true; + } + + if ( used[v2] ) { + c->normal[0] += n0[j]; + c->normal[1] += n1[j]; + c->normal[2] += n2[j]; + + c->tangents[0][0] += t0[j]; + c->tangents[0][1] += t1[j]; + c->tangents[0][2] += t2[j]; + + c->tangents[1][0] += t3[j]; + c->tangents[1][1] += t4[j]; + c->tangents[1][2] += t5[j]; + } else { + c->normal[0] = n0[j]; + c->normal[1] = n1[j]; + c->normal[2] = n2[j]; + + c->tangents[0][0] = t0[j]; + c->tangents[0][1] = t1[j]; + c->tangents[0][2] = t2[j]; + + c->tangents[1][0] = t3[j]; + c->tangents[1][1] = t4[j]; + c->tangents[1][2] = t5[j]; + + used[v2] = true; + } + } + } + + for ( ; i < numIndexes; i += 3 ) { + idDrawVert *a, *b, *c; + ALIGN16( unsigned long signBit[4]; ) + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const int v0 = indexes[i + 0]; + const int v1 = indexes[i + 1]; + const int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + +#if 1 + + __asm { + // normal + movss xmm0, d6 + mulss xmm0, d2 + movss xmm1, d7 + mulss xmm1, d1 + subss xmm0, xmm1 + + movss xmm1, d7 + mulss xmm1, d0 + movss xmm2, d5 + mulss xmm2, d2 + subss xmm1, xmm2 + + movss xmm2, d5 + mulss xmm2, d1 + movss xmm3, d6 + mulss xmm3, d0 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + mulss xmm0, xmm3 + movss n0, xmm0 + mulss xmm1, xmm3 + movss n1, xmm1 + mulss xmm2, xmm3 + movss n2, xmm2 + + // area sign bit + movss xmm0, d3 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d8 + subss xmm0, xmm1 + andps xmm0, SIMD_SP_signBit + movaps signBit, xmm0 + + // first tangent + movss xmm0, d0 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d5 + subss xmm0, xmm1 + + movss xmm1, d1 + mulss xmm1, d9 + movss xmm2, d4 + mulss xmm2, d6 + subss xmm1, xmm2 + + movss xmm2, d2 + mulss xmm2, d9 + movss xmm3, d4 + mulss xmm3, d7 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulss xmm0, xmm3 + movss t0, xmm0 + mulss xmm1, xmm3 + movss t1, xmm1 + mulss xmm2, xmm3 + movss t2, xmm2 + + // second tangent + movss xmm0, d3 + mulss xmm0, d5 + movss xmm1, d0 + mulss xmm1, d8 + subss xmm0, xmm1 + + movss xmm1, d3 + mulss xmm1, d6 + movss xmm2, d1 + mulss xmm2, d8 + subss xmm1, xmm2 + + movss xmm2, d3 + mulss xmm2, d7 + movss xmm3, d2 + mulss xmm3, d8 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulss xmm0, xmm3 + movss t3, xmm0 + mulss xmm1, xmm3 + movss t4, xmm1 + mulss xmm2, xmm3 + movss t5, xmm2 + } + +#else + + float tmp; + + // normal + n0 = d6 * d2 - d7 * d1; + n1 = d7 * d0 - d5 * d2; + n2 = d5 * d1 - d6 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + // area sign bit + tmp = d3 * d9 - d4 * d8; + signBit[0] = ( *(unsigned long *)&tmp ) & ( 1 << 31 ); + + // first tangent + t0 = d0 * d9 - d4 * d5; + t1 = d1 * d9 - d4 * d6; + t2 = d2 * d9 - d4 * d7; + + tmp = idMath::RSqrt( t0 * t0 + t1 * t1 + t2 * t2 ); + *(unsigned long *)&tmp ^= signBit[0]; + + t0 *= tmp; + t1 *= tmp; + t2 *= tmp; + + // second tangent + t3 = d3 * d5 - d0 * d8; + t4 = d3 * d6 - d1 * d8; + t5 = d3 * d7 - d2 * d8; + + tmp = idMath::RSqrt( t3 * t3 + t4 * t4 + t5 * t5 ); + *(unsigned long *)&tmp ^= signBit[0]; + + t3 *= tmp; + t4 *= tmp; + t5 *= tmp; + +#endif + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + + if ( used[v0] ) { + a->normal[0] += n0; + a->normal[1] += n1; + a->normal[2] += n2; + + a->tangents[0][0] += t0; + a->tangents[0][1] += t1; + a->tangents[0][2] += t2; + + a->tangents[1][0] += t3; + a->tangents[1][1] += t4; + a->tangents[1][2] += t5; + } else { + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + + used[v0] = true; + } + + if ( used[v1] ) { + b->normal[0] += n0; + b->normal[1] += n1; + b->normal[2] += n2; + + b->tangents[0][0] += t0; + b->tangents[0][1] += t1; + b->tangents[0][2] += t2; + + b->tangents[1][0] += t3; + b->tangents[1][1] += t4; + b->tangents[1][2] += t5; + } else { + b->normal[0] = n0; + b->normal[1] = n1; + b->normal[2] = n2; + + b->tangents[0][0] = t0; + b->tangents[0][1] = t1; + b->tangents[0][2] = t2; + + b->tangents[1][0] = t3; + b->tangents[1][1] = t4; + b->tangents[1][2] = t5; + + used[v1] = true; + } + + if ( used[v2] ) { + c->normal[0] += n0; + c->normal[1] += n1; + c->normal[2] += n2; + + c->tangents[0][0] += t0; + c->tangents[0][1] += t1; + c->tangents[0][2] += t2; + + c->tangents[1][0] += t3; + c->tangents[1][1] += t4; + c->tangents[1][2] += t5; + } else { + c->normal[0] = n0; + c->normal[1] = n1; + c->normal[2] = n2; + + c->tangents[0][0] = t0; + c->tangents[0][1] = t1; + c->tangents[0][2] = t2; + + c->tangents[1][0] = t3; + c->tangents[1][1] = t4; + c->tangents[1][2] = t5; + + used[v2] = true; + } + } +} + +/* +============ +idSIMD_SSE::DeriveUnsmoothedTangents +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_SSE::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + int i, j; + + for ( i = 0; i <= numVerts - 4; i += 4 ) { + ALIGN16( float s0[4]; ) + ALIGN16( float s1[4]; ) + ALIGN16( float s2[4]; ) + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float d6[4]; ) + ALIGN16( float d7[4]; ) + ALIGN16( float d8[4]; ) + ALIGN16( float d9[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + ALIGN16( float t0[4]; ) + ALIGN16( float t1[4]; ) + ALIGN16( float t2[4]; ) + ALIGN16( float t3[4]; ) + ALIGN16( float t4[4]; ) + ALIGN16( float t5[4]; ) + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a, *b, *c; + + const dominantTri_s &dt = dominantTris[i+j]; + + s0[j] = dt.normalizationScale[0]; + s1[j] = dt.normalizationScale[1]; + s2[j] = dt.normalizationScale[2]; + + a = verts + i + j; + b = verts + dt.v2; + c = verts + dt.v3; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + d3[j] = b->st[0] - a->st[0]; + d4[j] = b->st[1] - a->st[1]; + + d5[j] = c->xyz[0] - a->xyz[0]; + d6[j] = c->xyz[1] - a->xyz[1]; + d7[j] = c->xyz[2] - a->xyz[2]; + d8[j] = c->st[0] - a->st[0]; + d9[j] = c->st[1] - a->st[1]; + } + +#if 1 + + __asm { + + movaps xmm0, d6 + mulps xmm0, d2 + movaps xmm1, d7 + mulps xmm1, d1 + + movaps xmm2, d7 + mulps xmm2, d0 + movaps xmm3, d5 + mulps xmm3, d2 + + movaps xmm4, d5 + mulps xmm4, d1 + movaps xmm5, d6 + mulps xmm5, d0 + + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s2 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps n0, xmm0 + mulps xmm2, xmm7 + movaps n1, xmm2 + mulps xmm4, xmm7 + movaps n2, xmm4 + + movaps xmm0, d0 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d5 + + movaps xmm2, d1 + mulps xmm2, d9 + movaps xmm3, d4 + mulps xmm3, d6 + + movaps xmm4, d2 + mulps xmm4, d9 + movaps xmm5, d4 + mulps xmm5, d7 + + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s0 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps t0, xmm0 + mulps xmm2, xmm7 + movaps t1, xmm2 + mulps xmm4, xmm7 + movaps t2, xmm4 + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + movaps xmm0, d3 + mulps xmm0, d5 + movaps xmm1, d0 + mulps xmm1, d8 + + movaps xmm2, d3 + mulps xmm2, d6 + movaps xmm3, d1 + mulps xmm3, d8 + + movaps xmm4, d3 + mulps xmm4, d7 + movaps xmm5, d2 + mulps xmm5, d8 +#else + movaps xmm0, n2 + mulps xmm0, t1 + movaps xmm1, n1 + mulps xmm1, t2 + + movaps xmm2, n0 + mulps xmm2, t2 + movaps xmm3, n2 + mulps xmm3, t0 + + movaps xmm4, n1 + mulps xmm4, t0 + movaps xmm5, n0 + mulps xmm5, t1 +#endif + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s1 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps t3, xmm0 + mulps xmm2, xmm7 + movaps t4, xmm2 + mulps xmm4, xmm7 + movaps t5, xmm4 + } + +#else + + n0[0] = d6[0] * d2[0]; + n0[1] = d6[1] * d2[1]; + n0[2] = d6[2] * d2[2]; + n0[3] = d6[3] * d2[3]; + + n1[0] = d7[0] * d0[0]; + n1[1] = d7[1] * d0[1]; + n1[2] = d7[2] * d0[2]; + n1[3] = d7[3] * d0[3]; + + n2[0] = d5[0] * d1[0]; + n2[1] = d5[1] * d1[1]; + n2[2] = d5[2] * d1[2]; + n2[3] = d5[3] * d1[3]; + + n0[0] -= d7[0] * d1[0]; + n0[1] -= d7[1] * d1[1]; + n0[2] -= d7[2] * d1[2]; + n0[3] -= d7[3] * d1[3]; + + n1[0] -= d5[0] * d2[0]; + n1[1] -= d5[1] * d2[1]; + n1[2] -= d5[2] * d2[2]; + n1[3] -= d5[3] * d2[3]; + + n2[0] -= d6[0] * d0[0]; + n2[1] -= d6[1] * d0[1]; + n2[2] -= d6[2] * d0[2]; + n2[3] -= d6[3] * d0[3]; + + n0[0] *= s2[0]; + n0[1] *= s2[1]; + n0[2] *= s2[2]; + n0[3] *= s2[3]; + + n1[0] *= s2[0]; + n1[1] *= s2[1]; + n1[2] *= s2[2]; + n1[3] *= s2[3]; + + n2[0] *= s2[0]; + n2[1] *= s2[1]; + n2[2] *= s2[2]; + n2[3] *= s2[3]; + + t0[0] = d0[0] * d9[0]; + t0[1] = d0[1] * d9[1]; + t0[2] = d0[2] * d9[2]; + t0[3] = d0[3] * d9[3]; + + t1[0] = d1[0] * d9[0]; + t1[1] = d1[1] * d9[1]; + t1[2] = d1[2] * d9[2]; + t1[3] = d1[3] * d9[3]; + + t2[0] = d2[0] * d9[0]; + t2[1] = d2[1] * d9[1]; + t2[2] = d2[2] * d9[2]; + t2[3] = d2[3] * d9[3]; + + t0[0] -= d4[0] * d5[0]; + t0[1] -= d4[1] * d5[1]; + t0[2] -= d4[2] * d5[2]; + t0[3] -= d4[3] * d5[3]; + + t1[0] -= d4[0] * d6[0]; + t1[1] -= d4[1] * d6[1]; + t1[2] -= d4[2] * d6[2]; + t1[3] -= d4[3] * d6[3]; + + t2[0] -= d4[0] * d7[0]; + t2[1] -= d4[1] * d7[1]; + t2[2] -= d4[2] * d7[2]; + t2[3] -= d4[3] * d7[3]; + + t0[0] *= s0[0]; + t0[1] *= s0[1]; + t0[2] *= s0[2]; + t0[3] *= s0[3]; + + t1[0] *= s0[0]; + t1[1] *= s0[1]; + t1[2] *= s0[2]; + t1[3] *= s0[3]; + + t2[0] *= s0[0]; + t2[1] *= s0[1]; + t2[2] *= s0[2]; + t2[3] *= s0[3]; + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3[0] = d3[0] * d5[0]; + t3[1] = d3[1] * d5[1]; + t3[2] = d3[2] * d5[2]; + t3[3] = d3[3] * d5[3]; + + t4[0] = d3[0] * d6[0]; + t4[1] = d3[1] * d6[1]; + t4[2] = d3[2] * d6[2]; + t4[3] = d3[3] * d6[3]; + + t5[0] = d3[0] * d7[0]; + t5[1] = d3[1] * d7[1]; + t5[2] = d3[2] * d7[2]; + t5[3] = d3[3] * d7[3]; + + t3[0] -= d0[0] * d8[0]; + t3[1] -= d0[1] * d8[1]; + t3[2] -= d0[2] * d8[2]; + t3[3] -= d0[3] * d8[3]; + + t4[0] -= d1[0] * d8[0]; + t4[1] -= d1[1] * d8[1]; + t4[2] -= d1[2] * d8[2]; + t4[3] -= d1[3] * d8[3]; + + t5[0] -= d2[0] * d8[0]; + t5[1] -= d2[1] * d8[1]; + t5[2] -= d2[2] * d8[2]; + t5[3] -= d2[3] * d8[3]; +#else + t3[0] = n2[0] * t1[0]; + t3[1] = n2[1] * t1[1]; + t3[2] = n2[2] * t1[2]; + t3[3] = n2[3] * t1[3]; + + t4[0] = n0[0] * t2[0]; + t4[1] = n0[1] * t2[1]; + t4[2] = n0[2] * t2[2]; + t4[3] = n0[3] * t2[3]; + + t5[0] = n1[0] * t0[0]; + t5[1] = n1[1] * t0[1]; + t5[2] = n1[2] * t0[2]; + t5[3] = n1[3] * t0[3]; + + t3[0] -= n1[0] * t2[0]; + t3[1] -= n1[1] * t2[1]; + t3[2] -= n1[2] * t2[2]; + t3[3] -= n1[3] * t2[3]; + + t4[0] -= n2[0] * t0[0]; + t4[1] -= n2[1] * t0[1]; + t4[2] -= n2[2] * t0[2]; + t4[3] -= n2[3] * t0[3]; + + t5[0] -= n0[0] * t1[0]; + t5[1] -= n0[1] * t1[1]; + t5[2] -= n0[2] * t1[2]; + t5[3] -= n0[3] * t1[3]; +#endif + t3[0] *= s1[0]; + t3[1] *= s1[1]; + t3[2] *= s1[2]; + t3[3] *= s1[3]; + + t4[0] *= s1[0]; + t4[1] *= s1[1]; + t4[2] *= s1[2]; + t4[3] *= s1[3]; + + t5[0] *= s1[0]; + t5[1] *= s1[1]; + t5[2] *= s1[2]; + t5[3] *= s1[3]; + +#endif + + for ( j = 0; j < 4; j++ ) { + idDrawVert *a; + + a = verts + i + j; + + a->normal[0] = n0[j]; + a->normal[1] = n1[j]; + a->normal[2] = n2[j]; + + a->tangents[0][0] = t0[j]; + a->tangents[0][1] = t1[j]; + a->tangents[0][2] = t2[j]; + + a->tangents[1][0] = t3[j]; + a->tangents[1][1] = t4[j]; + a->tangents[1][2] = t5[j]; + } + } + + for ( ; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + +#if 1 + + __asm { + + movss xmm0, d6 + mulss xmm0, d2 + movss xmm1, d7 + mulss xmm1, d1 + + movss xmm2, d7 + mulss xmm2, d0 + movss xmm3, d5 + mulss xmm3, d2 + + movss xmm4, d5 + mulss xmm4, d1 + movss xmm5, d6 + mulss xmm5, d0 + + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s2 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss n0, xmm0 + mulss xmm2, xmm7 + movss n1, xmm2 + mulss xmm4, xmm7 + movss n2, xmm4 + + movss xmm0, d0 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d5 + + movss xmm2, d1 + mulss xmm2, d9 + movss xmm3, d4 + mulss xmm3, d6 + + movss xmm4, d2 + mulss xmm4, d9 + movss xmm5, d4 + mulss xmm5, d7 + + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s0 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss t0, xmm0 + mulss xmm2, xmm7 + movss t1, xmm2 + mulss xmm4, xmm7 + movss t2, xmm4 + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + movss xmm0, d3 + mulss xmm0, d5 + movss xmm1, d0 + mulss xmm1, d8 + + movss xmm2, d3 + mulss xmm2, d6 + movss xmm3, d1 + mulss xmm3, d8 + + movss xmm4, d3 + mulss xmm4, d7 + movss xmm5, d2 + mulss xmm5, d8 +#else + movss xmm0, n2 + mulss xmm0, t1 + movss xmm1, n1 + mulss xmm1, t2 + + movss xmm2, n0 + mulss xmm2, t2 + movss xmm3, n2 + mulss xmm3, t0 + + movss xmm4, n1 + mulss xmm4, t0 + movss xmm5, n0 + mulss xmm5, t1 +#endif + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s1 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss t3, xmm0 + mulss xmm2, xmm7 + movss t4, xmm2 + mulss xmm4, xmm7 + movss t5, xmm4 + } + +#else + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +/* +============ +idSIMD_SSE::NormalizeTangents +============ +*/ +void VPCALL idSIMD_SSE::NormalizeTangents( idDrawVert *verts, const int numVerts ) { + ALIGN16( float normal[12] ); + + assert( verts != NULL ); + assert( numVerts >= 0 ); + + __asm { + mov eax, numVerts + test eax, eax + jz done +#ifdef REFINE_TANGENT_SQUAREROOT + movaps xmm6, SIMD_SP_rsqrt_c0 + movaps xmm7, SIMD_SP_rsqrt_c1 +#endif + mov esi, verts + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + add eax, DRAWVERT_SIZE*4 + jle loopVert4 + + sub eax, DRAWVERT_SIZE*4 + jl loopVert1 + + loopVert4: + + sub eax, DRAWVERT_SIZE*4 + + // normalize 4 idDrawVert::normal + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + // save the 4 idDrawVert::normal to project the tangents + + movaps [normal+ 0], xmm0 + movaps [normal+16], xmm1 + movaps [normal+32], xmm2 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + // project and normalize 4 idDrawVert::tangent[0] + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + addps xmm3, xmm4 + addps xmm3, xmm5 + + movaps xmm4, xmm3 + movaps xmm5, xmm3 + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + // project and normalize 4 idDrawVert::tangent[1] + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + addps xmm3, xmm4 + addps xmm3, xmm5 + + movaps xmm4, xmm3 + movaps xmm5, xmm3 + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + add eax, DRAWVERT_SIZE*8 + + jle loopVert4 + + sub eax, DRAWVERT_SIZE*4 + jge done + + loopVert1: + + // normalize one idDrawVert::normal + + movss xmm0, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + // project and normalize one idDrawVert::tangent[0] + + movss xmm0, [esi+eax+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_TANGENT0_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + addss xmm3, xmm4 + addss xmm3, xmm5 + + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + subss xmm0, xmm3 + subss xmm1, xmm4 + subss xmm2, xmm5 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + // project and normalize one idDrawVert::tangent[1] + + movss xmm0, [esi+eax+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_TANGENT1_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + addss xmm3, xmm4 + addss xmm3, xmm5 + + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + subss xmm0, xmm3 + subss xmm1, xmm4 + subss xmm2, xmm5 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + add eax, DRAWVERT_SIZE + + jl loopVert1 + done: + } +} + +/* +============ +idSIMD_SSE::CreateTextureSpaceLightVectors +============ +*/ +void VPCALL idSIMD_SSE::CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + +#if 0 + + __asm { + + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + mov ecx, lightOrigin + movss xmm7, [ecx+0] + movhps xmm7, [ecx+4] + + mov ecx, lightVectors + sub ecx, 3*4 + + loopVert: + inc eax + jge done + + add edi, DRAWVERT_SIZE + add ecx, 3*4 + + cmp byte ptr [esi+eax], 0 + je loopVert + + movaps xmm0, xmm7 + movss xmm1, [edi+DRAWVERT_XYZ_OFFSET+0] + movhps xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subps xmm0, xmm1 + + // 0, X, 1, 2 + // 3, X, 4, 5 + // 6, X, 7, 8 + + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+4] + mulps xmm2, xmm0 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movhps xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+4] + mulps xmm3, xmm0 + + movaps xmm5, xmm2 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm3 // xmm5 = 0, 3, X, X + unpckhps xmm2, xmm3 // xmm2 = 1, 4, 2, 5 + + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+0] + movhps xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + mulps xmm4, xmm0 + + movlhps xmm5, xmm4 // xmm5 = 0, 3, 6, X + movhlps xmm4, xmm2 // xmm4 = 2, 5, 7, 8 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 1, 3, 2 ) // xmm2 = 2, 5, 8, 7 + + addps xmm5, xmm4 + addps xmm5, xmm2 + movlps [ecx+0], xmm5 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movss [ecx+8], xmm5 + + jmp loopVert + + done: + } + +#elif 1 + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + idVec3 lightDir; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightVectors[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + lightVectors[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + lightVectors[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + } + +#elif 1 + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + idVec3 localLightOrigin = lightOrigin; + + __asm { + + xor ecx, ecx + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + loopVert4: + inc eax + jge done4 + + add edi, DRAWVERT_SIZE + + cmp byte ptr [esi+eax], 0 + je loopVert4 + + mov usedVertNums[ecx*4], eax + + inc ecx + cmp ecx, 4 + + movss xmm0, localLightOrigin[0] + movss xmm1, localLightOrigin[4] + movss xmm2, localLightOrigin[8] + + subss xmm0, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm2, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss lightDir0[ecx*4-4], xmm0 + movss lightDir1[ecx*4-4], xmm1 + movss lightDir2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_NORMAL_OFFSET+0] + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + movss xmm5, [edi+DRAWVERT_NORMAL_OFFSET+8] + + movss normal0[ecx*4-4], xmm3 + movss normal1[ecx*4-4], xmm4 + movss normal2[ecx*4-4], xmm5 + + movss xmm0, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [edi+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+8] + + movss tangent0[ecx*4-4], xmm0 + movss tangent1[ecx*4-4], xmm1 + movss tangent2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm4, [edi+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm5, [edi+DRAWVERT_TANGENT1_OFFSET+8] + + movss tangent3[ecx*4-4], xmm3 + movss tangent4[ecx*4-4], xmm4 + movss tangent5[ecx*4-4], xmm5 + + jl loopVert4 + + movaps xmm0, lightDir0 + movaps xmm1, lightDir1 + movaps xmm2, lightDir2 + + movaps xmm3, tangent0 + mulps xmm3, xmm0 + movaps xmm4, tangent1 + mulps xmm4, xmm1 + movaps xmm5, tangent2 + mulps xmm5, xmm2 + + addps xmm3, xmm4 + addps xmm5, xmm3 + + movaps xmm3, tangent3 + mulps xmm3, xmm0 + movaps xmm4, tangent4 + mulps xmm4, xmm1 + movaps xmm6, tangent5 + mulps xmm6, xmm2 + + addps xmm3, xmm4 + addps xmm6, xmm3 + + mulps xmm0, normal0 + mulps xmm1, normal1 + mulps xmm2, normal2 + + addps xmm0, xmm1 + addps xmm0, xmm2 + + mov ecx, numVerts + imul ecx, 12 + mov edx, usedVertNums[0] + add ecx, lightVectors + imul edx, 12 + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + xor ecx, ecx + jmp loopVert4 + + done4: + test ecx, ecx + jz done + xor eax, eax + mov edi, numVerts + imul edi, 12 + add edi, lightVectors + + loopVert1: + movss xmm0, lightDir0[eax*4] + movss xmm1, lightDir1[eax*4] + movss xmm2, lightDir2[eax*4] + + mov edx, usedVertNums[eax*4] + imul edx, 12 + + movss xmm3, tangent0[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent1[eax*4] + mulss xmm4, xmm1 + movss xmm5, tangent2[eax*4] + mulss xmm5, xmm2 + + addss xmm3, xmm4 + addss xmm5, xmm3 + movss [edi+edx+0], xmm5 + + movss xmm3, tangent3[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent4[eax*4] + mulss xmm4, xmm1 + movss xmm6, tangent5[eax*4] + mulss xmm6, xmm2 + + addss xmm3, xmm4 + addss xmm6, xmm3 + movss [edi+edx+4], xmm6 + + mulss xmm0, normal0[eax*4] + mulss xmm1, normal1[eax*4] + mulss xmm2, normal2[eax*4] + + addss xmm0, xmm1 + addss xmm0, xmm2 + movss [edi+edx+8], xmm0 + + inc eax + dec ecx + jg loopVert1 + + done: + } + +#else + + ALIGN16( float lightVectors0[4]; ) + ALIGN16( float lightVectors1[4]; ) + ALIGN16( float lightVectors2[4]; ) + int numUsedVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + lightDir0[numUsedVerts] = lightOrigin[0] - v->xyz[0]; + lightDir1[numUsedVerts] = lightOrigin[1] - v->xyz[1]; + lightDir2[numUsedVerts] = lightOrigin[2] - v->xyz[2]; + + normal0[numUsedVerts] = v->normal[0]; + normal1[numUsedVerts] = v->normal[1]; + normal2[numUsedVerts] = v->normal[2]; + + tangent0[numUsedVerts] = v->tangents[0][0]; + tangent1[numUsedVerts] = v->tangents[0][1]; + tangent2[numUsedVerts] = v->tangents[0][2]; + + tangent3[numUsedVerts] = v->tangents[1][0]; + tangent4[numUsedVerts] = v->tangents[1][1]; + tangent5[numUsedVerts] = v->tangents[1][2]; + + usedVertNums[numUsedVerts++] = i; + if ( numUsedVerts < 4 ) { + continue; + } + + lightVectors0[0] = lightDir0[0] * tangent0[0]; + lightVectors0[1] = lightDir0[1] * tangent0[1]; + lightVectors0[2] = lightDir0[2] * tangent0[2]; + lightVectors0[3] = lightDir0[3] * tangent0[3]; + + lightVectors0[0] += lightDir1[0] * tangent1[0]; + lightVectors0[1] += lightDir1[1] * tangent1[1]; + lightVectors0[2] += lightDir1[2] * tangent1[2]; + lightVectors0[3] += lightDir1[3] * tangent1[3]; + + lightVectors0[0] += lightDir2[0] * tangent2[0]; + lightVectors0[1] += lightDir2[1] * tangent2[1]; + lightVectors0[2] += lightDir2[2] * tangent2[2]; + lightVectors0[3] += lightDir2[3] * tangent2[3]; + + lightVectors1[0] = lightDir0[0] * tangent3[0]; + lightVectors1[1] = lightDir0[1] * tangent3[1]; + lightVectors1[2] = lightDir0[2] * tangent3[2]; + lightVectors1[3] = lightDir0[3] * tangent3[3]; + + lightVectors1[0] += lightDir1[0] * tangent4[0]; + lightVectors1[1] += lightDir1[1] * tangent4[1]; + lightVectors1[2] += lightDir1[2] * tangent4[2]; + lightVectors1[3] += lightDir1[3] * tangent4[3]; + + lightVectors1[0] += lightDir2[0] * tangent5[0]; + lightVectors1[1] += lightDir2[1] * tangent5[1]; + lightVectors1[2] += lightDir2[2] * tangent5[2]; + lightVectors1[3] += lightDir2[3] * tangent5[3]; + + lightVectors2[0] = lightDir0[0] * normal0[0]; + lightVectors2[1] = lightDir0[1] * normal0[1]; + lightVectors2[2] = lightDir0[2] * normal0[2]; + lightVectors2[3] = lightDir0[3] * normal0[3]; + + lightVectors2[0] += lightDir1[0] * normal1[0]; + lightVectors2[1] += lightDir1[1] * normal1[1]; + lightVectors2[2] += lightDir1[2] * normal1[2]; + lightVectors2[3] += lightDir1[3] * normal1[3]; + + lightVectors2[0] += lightDir2[0] * normal2[0]; + lightVectors2[1] += lightDir2[1] * normal2[1]; + lightVectors2[2] += lightDir2[2] * normal2[2]; + lightVectors2[3] += lightDir2[3] * normal2[3]; + + + for ( int j = 0; j < 4; j++ ) { + int n = usedVertNums[j]; + + lightVectors[n][0] = lightVectors0[j]; + lightVectors[n][1] = lightVectors1[j]; + lightVectors[n][2] = lightVectors2[j]; + } + + numUsedVerts = 0; + } + + for ( int i = 0; i < numUsedVerts; i++ ) { + + lightVectors0[i] = lightDir0[i] * tangent0[i] + lightDir1[i] * tangent1[i] + lightDir2[i] * tangent2[i]; + lightVectors1[i] = lightDir0[i] * tangent3[i] + lightDir1[i] * tangent4[i] + lightDir2[i] * tangent5[i]; + lightVectors2[i] = lightDir0[i] * normal0[i] + lightDir1[i] * normal1[i] + lightDir2[i] * normal2[i]; + + int n = usedVertNums[i]; + lightVectors[n][0] = lightVectors0[i]; + lightVectors[n][1] = lightVectors1[i]; + lightVectors[n][2] = lightVectors2[i]; + } + +#endif +} + +/* +============ +idSIMD_SSE::CreateSpecularTextureCoords +============ +*/ +void VPCALL idSIMD_SSE::CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + +#if 0 + + __asm { + + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + mov ecx, viewOrigin + movss xmm6, [ecx+0] + movhps xmm6, [ecx+4] + + mov ecx, lightOrigin + movss xmm7, [ecx+0] + movhps xmm7, [ecx+4] + + mov ecx, texCoords + sub ecx, 4*4 + + loopVert: + inc eax + jge done + + add edi, DRAWVERT_SIZE + add ecx, 4*4 + + cmp byte ptr [esi+eax], 0 + je loopVert + + movaps xmm0, xmm7 + movaps xmm1, xmm6 + movss xmm2, [edi+DRAWVERT_XYZ_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_XYZ_OFFSET+4] + subps xmm0, xmm2 + subps xmm1, xmm2 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + mulps xmm3, xmm3 + mulps xmm4, xmm4 + + // 0, X, 1, 2 + // 3, X, 4, 5 + + movaps xmm5, xmm3 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm4 // xmm5 = 0, 3, X, X + unpckhps xmm3, xmm4 // xmm3 = 1, 4, 2, 5 + movhlps xmm4, xmm3 // xmm4 = 2, 5, 4, 5 + + addps xmm5, xmm3 + addps xmm5, xmm4 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 0, 1 ) + rsqrtps xmm5, xmm5 + + movaps xmm4, xmm5 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 1, 1 ) + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+4] + mulps xmm2, xmm0 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movhps xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+4] + mulps xmm3, xmm0 + + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+0] + movhps xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + mulps xmm4, xmm0 + + movaps xmm5, xmm2 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm3 // xmm5 = 0, 3, X, X + unpckhps xmm2, xmm3 // xmm2 = 1, 4, 2, 5 + + movlhps xmm5, xmm4 // xmm5 = 0, 3, 6, X + movhlps xmm4, xmm2 // xmm4 = 2, 5, 7, 8 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 1, 3, 2 ) // xmm2 = 2, 5, 8, 7 + + movaps xmm3, SIMD_SP_one + + addps xmm5, xmm4 + addps xmm5, xmm2 + movaps [ecx+0], xmm5 + movss [ecx+12], xmm3 + + jmp loopVert + + done: + } + +#elif 0 + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + idVec3 viewDir = viewOrigin - v->xyz; + + float ilength; + + ilength = idMath::RSqrt( lightDir[0] * lightDir[0] + lightDir[1] * lightDir[1] + lightDir[2] * lightDir[2] ); + lightDir[0] *= ilength; + lightDir[1] *= ilength; + lightDir[2] *= ilength; + + ilength = idMath::RSqrt( viewDir[0] * viewDir[0] + viewDir[1] * viewDir[1] + viewDir[2] * viewDir[2] ); + viewDir[0] *= ilength; + viewDir[1] *= ilength; + viewDir[2] *= ilength; + + lightDir += viewDir; + + texCoords[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + texCoords[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + texCoords[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + texCoords[i][3] = 1.0f; + } + + +#elif 1 + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float viewDir0[4]; ) + ALIGN16( float viewDir1[4]; ) + ALIGN16( float viewDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + idVec3 localLightOrigin = lightOrigin; + idVec3 localViewOrigin = viewOrigin; + + __asm { + + xor ecx, ecx + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + loopVert4: + inc eax + jge done4 + + add edi, DRAWVERT_SIZE + + cmp byte ptr [esi+eax], 0 + je loopVert4 + + mov usedVertNums[ecx*4], eax + + inc ecx + cmp ecx, 4 + + movss xmm3, localLightOrigin[0] + movss xmm4, localLightOrigin[4] + movss xmm5, localLightOrigin[8] + + subss xmm3, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm4, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm5, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss lightDir0[ecx*4-4], xmm3 + movss lightDir1[ecx*4-4], xmm4 + movss lightDir2[ecx*4-4], xmm5 + + movss xmm0, localViewOrigin[0] + movss xmm1, localViewOrigin[4] + movss xmm2, localViewOrigin[8] + + subss xmm0, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm2, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss viewDir0[ecx*4-4], xmm0 + movss viewDir1[ecx*4-4], xmm1 + movss viewDir2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_NORMAL_OFFSET+0] + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + movss xmm5, [edi+DRAWVERT_NORMAL_OFFSET+8] + + movss normal0[ecx*4-4], xmm3 + movss normal1[ecx*4-4], xmm4 + movss normal2[ecx*4-4], xmm5 + + movss xmm0, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [edi+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+8] + + movss tangent0[ecx*4-4], xmm0 + movss tangent1[ecx*4-4], xmm1 + movss tangent2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm4, [edi+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm5, [edi+DRAWVERT_TANGENT1_OFFSET+8] + + movss tangent3[ecx*4-4], xmm3 + movss tangent4[ecx*4-4], xmm4 + movss tangent5[ecx*4-4], xmm5 + + jl loopVert4 + + movaps xmm6, lightDir0 + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm7, lightDir1 + movaps xmm1, xmm7 + mulps xmm7, xmm7 + addps xmm6, xmm7 + movaps xmm5, lightDir2 + movaps xmm2, xmm5 + mulps xmm5, xmm5 + addps xmm6, xmm5 + rsqrtps xmm6, xmm6 + + mulps xmm0, xmm6 + mulps xmm1, xmm6 + mulps xmm2, xmm6 + + movaps xmm3, viewDir0 + movaps xmm7, xmm3 + mulps xmm7, xmm7 + movaps xmm4, viewDir1 + movaps xmm6, xmm4 + mulps xmm6, xmm6 + addps xmm7, xmm6 + movaps xmm5, viewDir2 + movaps xmm6, xmm5 + mulps xmm6, xmm6 + addps xmm7, xmm6 + rsqrtps xmm7, xmm7 + + mulps xmm3, xmm7 + addps xmm0, xmm3 + mulps xmm4, xmm7 + addps xmm1, xmm4 + mulps xmm5, xmm7 + addps xmm2, xmm5 + + movaps xmm3, tangent0 + mulps xmm3, xmm0 + movaps xmm4, tangent1 + mulps xmm4, xmm1 + addps xmm3, xmm4 + movaps xmm5, tangent2 + mulps xmm5, xmm2 + addps xmm5, xmm3 + + movaps xmm3, tangent3 + mulps xmm3, xmm0 + movaps xmm4, tangent4 + mulps xmm4, xmm1 + addps xmm3, xmm4 + movaps xmm6, tangent5 + mulps xmm6, xmm2 + addps xmm6, xmm3 + + mulps xmm0, normal0 + mulps xmm1, normal1 + addps xmm0, xmm1 + mulps xmm2, normal2 + addps xmm0, xmm2 + + mov ecx, numVerts + shl ecx, 4 + mov edx, usedVertNums[0] + add ecx, texCoords + shl edx, 4 + movss xmm3, SIMD_SP_one + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + xor ecx, ecx + jmp loopVert4 + + done4: + test ecx, ecx + jz done + xor eax, eax + mov edi, numVerts + shl edi, 4 + add edi, texCoords + + loopVert1: + movss xmm6, lightDir0[eax*4] + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm7, lightDir1[eax*4] + movss xmm1, xmm7 + mulss xmm7, xmm7 + addss xmm6, xmm7 + movss xmm5, lightDir2[eax*4] + movss xmm2, xmm5 + mulss xmm5, xmm5 + addss xmm6, xmm5 + rsqrtss xmm6, xmm6 + + mulss xmm0, xmm6 + mulss xmm1, xmm6 + mulss xmm2, xmm6 + + movss xmm3, viewDir0[eax*4] + movss xmm7, xmm3 + mulss xmm7, xmm7 + movss xmm4, viewDir1[eax*4] + movss xmm6, xmm4 + mulss xmm6, xmm6 + addss xmm7, xmm6 + movss xmm5, viewDir2[eax*4] + movss xmm6, xmm5 + mulss xmm6, xmm6 + addss xmm7, xmm6 + rsqrtss xmm7, xmm7 + + mulss xmm3, xmm7 + addss xmm0, xmm3 + mulss xmm4, xmm7 + addss xmm1, xmm4 + mulss xmm5, xmm7 + addss xmm2, xmm5 + + mov edx, usedVertNums[eax*4] + shl edx, 4 + + movss xmm3, tangent0[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent1[eax*4] + mulss xmm4, xmm1 + addss xmm3, xmm4 + movss xmm5, tangent2[eax*4] + mulss xmm5, xmm2 + addss xmm5, xmm3 + movss [edi+edx+0], xmm5 + + movss xmm3, tangent3[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent4[eax*4] + mulss xmm4, xmm1 + addss xmm3, xmm4 + movss xmm6, tangent5[eax*4] + mulss xmm6, xmm2 + addss xmm6, xmm3 + movss [edi+edx+4], xmm6 + + mulss xmm0, normal0[eax*4] + mulss xmm1, normal1[eax*4] + addss xmm0, xmm1 + mulss xmm2, normal2[eax*4] + addss xmm0, xmm2 + movss [edi+edx+8], xmm0 + + movss xmm3, SIMD_SP_one + movss [edi+edx+12], xmm3 + + inc eax + dec ecx + jg loopVert1 + + done: + } + +#else + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float viewDir0[4]; ) + ALIGN16( float viewDir1[4]; ) + ALIGN16( float viewDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + ALIGN16( float texCoords0[4]; ) + ALIGN16( float texCoords1[4]; ) + ALIGN16( float texCoords2[4]; ) + idVec3 localLightOrigin = lightOrigin; + idVec3 localViewOrigin = viewOrigin; + int numUsedVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + lightDir0[numUsedVerts] = localLightOrigin[0] - v->xyz[0]; + lightDir1[numUsedVerts] = localLightOrigin[1] - v->xyz[1]; + lightDir2[numUsedVerts] = localLightOrigin[2] - v->xyz[2]; + + viewDir0[numUsedVerts] = localViewOrigin[0] - v->xyz[0]; + viewDir1[numUsedVerts] = localViewOrigin[1] - v->xyz[1]; + viewDir2[numUsedVerts] = localViewOrigin[2] - v->xyz[2]; + + normal0[numUsedVerts] = v->normal[0]; + normal1[numUsedVerts] = v->normal[1]; + normal2[numUsedVerts] = v->normal[2]; + + tangent0[numUsedVerts] = v->tangents[0][0]; + tangent1[numUsedVerts] = v->tangents[0][1]; + tangent2[numUsedVerts] = v->tangents[0][2]; + + tangent3[numUsedVerts] = v->tangents[1][0]; + tangent4[numUsedVerts] = v->tangents[1][1]; + tangent5[numUsedVerts] = v->tangents[1][2]; + + usedVertNums[numUsedVerts++] = i; + if ( numUsedVerts < 4 ) { + continue; + } + + ALIGN16( float temp[4]; ) + + temp[0] = lightDir0[0] * lightDir0[0]; + temp[1] = lightDir0[1] * lightDir0[1]; + temp[2] = lightDir0[2] * lightDir0[2]; + temp[3] = lightDir0[3] * lightDir0[3]; + + temp[0] += lightDir1[0] * lightDir1[0]; + temp[1] += lightDir1[1] * lightDir1[1]; + temp[2] += lightDir1[2] * lightDir1[2]; + temp[3] += lightDir1[3] * lightDir1[3]; + + temp[0] += lightDir2[0] * lightDir2[0]; + temp[1] += lightDir2[1] * lightDir2[1]; + temp[2] += lightDir2[2] * lightDir2[2]; + temp[3] += lightDir2[3] * lightDir2[3]; + + temp[0] = idMath::RSqrt( temp[0] ); + temp[1] = idMath::RSqrt( temp[1] ); + temp[2] = idMath::RSqrt( temp[2] ); + temp[3] = idMath::RSqrt( temp[3] ); + + lightDir0[0] *= temp[0]; + lightDir0[1] *= temp[1]; + lightDir0[2] *= temp[2]; + lightDir0[3] *= temp[3]; + + lightDir1[0] *= temp[0]; + lightDir1[1] *= temp[1]; + lightDir1[2] *= temp[2]; + lightDir1[3] *= temp[3]; + + lightDir2[0] *= temp[0]; + lightDir2[1] *= temp[1]; + lightDir2[2] *= temp[2]; + lightDir2[3] *= temp[3]; + + temp[0] = viewDir0[0] * viewDir0[0]; + temp[1] = viewDir0[1] * viewDir0[1]; + temp[2] = viewDir0[2] * viewDir0[2]; + temp[3] = viewDir0[3] * viewDir0[3]; + + temp[0] += viewDir1[0] * viewDir1[0]; + temp[1] += viewDir1[1] * viewDir1[1]; + temp[2] += viewDir1[2] * viewDir1[2]; + temp[3] += viewDir1[3] * viewDir1[3]; + + temp[0] += viewDir2[0] * viewDir2[0]; + temp[1] += viewDir2[1] * viewDir2[1]; + temp[2] += viewDir2[2] * viewDir2[2]; + temp[3] += viewDir2[3] * viewDir2[3]; + + temp[0] = idMath::RSqrt( temp[0] ); + temp[1] = idMath::RSqrt( temp[1] ); + temp[2] = idMath::RSqrt( temp[2] ); + temp[3] = idMath::RSqrt( temp[3] ); + + viewDir0[0] *= temp[0]; + viewDir0[1] *= temp[1]; + viewDir0[2] *= temp[2]; + viewDir0[3] *= temp[3]; + + viewDir1[0] *= temp[0]; + viewDir1[1] *= temp[1]; + viewDir1[2] *= temp[2]; + viewDir1[3] *= temp[3]; + + viewDir2[0] *= temp[0]; + viewDir2[1] *= temp[1]; + viewDir2[2] *= temp[2]; + viewDir2[3] *= temp[3]; + + lightDir0[0] += viewDir0[0]; + lightDir0[1] += viewDir0[1]; + lightDir0[2] += viewDir0[2]; + lightDir0[3] += viewDir0[3]; + + lightDir1[0] += viewDir1[0]; + lightDir1[1] += viewDir1[1]; + lightDir1[2] += viewDir1[2]; + lightDir1[3] += viewDir1[3]; + + lightDir2[0] += viewDir2[0]; + lightDir2[1] += viewDir2[1]; + lightDir2[2] += viewDir2[2]; + lightDir2[3] += viewDir2[3]; + + texCoords0[0] = lightDir0[0] * tangent0[0]; + texCoords0[1] = lightDir0[1] * tangent0[1]; + texCoords0[2] = lightDir0[2] * tangent0[2]; + texCoords0[3] = lightDir0[3] * tangent0[3]; + + texCoords0[0] += lightDir1[0] * tangent1[0]; + texCoords0[1] += lightDir1[1] * tangent1[1]; + texCoords0[2] += lightDir1[2] * tangent1[2]; + texCoords0[3] += lightDir1[3] * tangent1[3]; + + texCoords0[0] += lightDir2[0] * tangent2[0]; + texCoords0[1] += lightDir2[1] * tangent2[1]; + texCoords0[2] += lightDir2[2] * tangent2[2]; + texCoords0[3] += lightDir2[3] * tangent2[3]; + + texCoords1[0] = lightDir0[0] * tangent3[0]; + texCoords1[1] = lightDir0[1] * tangent3[1]; + texCoords1[2] = lightDir0[2] * tangent3[2]; + texCoords1[3] = lightDir0[3] * tangent3[3]; + + texCoords1[0] += lightDir1[0] * tangent4[0]; + texCoords1[1] += lightDir1[1] * tangent4[1]; + texCoords1[2] += lightDir1[2] * tangent4[2]; + texCoords1[3] += lightDir1[3] * tangent4[3]; + + texCoords1[0] += lightDir2[0] * tangent5[0]; + texCoords1[1] += lightDir2[1] * tangent5[1]; + texCoords1[2] += lightDir2[2] * tangent5[2]; + texCoords1[3] += lightDir2[3] * tangent5[3]; + + texCoords2[0] = lightDir0[0] * normal0[0]; + texCoords2[1] = lightDir0[1] * normal0[1]; + texCoords2[2] = lightDir0[2] * normal0[2]; + texCoords2[3] = lightDir0[3] * normal0[3]; + + texCoords2[0] += lightDir1[0] * normal1[0]; + texCoords2[1] += lightDir1[1] * normal1[1]; + texCoords2[2] += lightDir1[2] * normal1[2]; + texCoords2[3] += lightDir1[3] * normal1[3]; + + texCoords2[0] += lightDir2[0] * normal2[0]; + texCoords2[1] += lightDir2[1] * normal2[1]; + texCoords2[2] += lightDir2[2] * normal2[2]; + texCoords2[3] += lightDir2[3] * normal2[3]; + + for ( int j = 0; j < 4; j++ ) { + int n = usedVertNums[j]; + + texCoords[n][0] = texCoords0[j]; + texCoords[n][1] = texCoords1[j]; + texCoords[n][2] = texCoords2[j]; + texCoords[n][3] = 1.0f; + } + + numUsedVerts = 0; + } + + for ( int i = 0; i < numUsedVerts; i++ ) { + float temp; + + temp = lightDir0[i] * lightDir0[i] + lightDir1[i] * lightDir1[i] + lightDir2[i] * lightDir2[i]; + temp = idMath::RSqrt( temp ); + + lightDir0[i] *= temp; + lightDir1[i] *= temp; + lightDir2[i] *= temp; + + temp = viewDir0[i] * viewDir0[i] + viewDir1[i] * viewDir1[i] + viewDir2[i] * viewDir2[i]; + temp = idMath::RSqrt( temp ); + + viewDir0[i] *= temp; + viewDir1[i] *= temp; + viewDir2[i] *= temp; + + lightDir0[i] += viewDir0[i]; + lightDir1[i] += viewDir1[i]; + lightDir2[i] += viewDir2[i]; + + texCoords0[i] = lightDir0[i] * tangent0[i] + lightDir1[i] * tangent1[i] + lightDir2[i] * tangent2[i]; + texCoords1[i] = lightDir0[i] * tangent3[i] + lightDir1[i] * tangent4[i] + lightDir2[i] * tangent5[i]; + texCoords2[i] = lightDir0[i] * normal0[i] + lightDir1[i] * normal1[i] + lightDir2[i] * normal2[i]; + + int n = usedVertNums[i]; + texCoords[n][0] = texCoords0; + texCoords[n][1] = texCoords1; + texCoords[n][2] = texCoords2; + texCoords[n][3] = 1.0f; + } + +#endif +} + +/* +============ +idSIMD_SSE::CreateShadowCache +============ +*/ +int VPCALL idSIMD_SSE::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { +#if 1 + int outVerts; + + __asm { + push ebx + + mov esi, lightOrigin + movaps xmm5, SIMD_SP_lastOne + movss xmm6, [esi+0] + movhps xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm6, SIMD_SP_lastOne + movaps xmm7, xmm6 + + xor ebx, ebx + xor ecx, ecx + + mov edx, vertRemap + mov esi, verts + mov edi, vertexCache + mov eax, numVerts + and eax, ~3 + jz done4 + shl eax, 2 + add edx, eax + neg eax + + loop4: + prefetchnta [edx+128] + prefetchnta [esi+4*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + + cmp dword ptr [edx+eax+0], ebx + jne skip1 + + mov dword ptr [edx+eax+0], ecx + movss xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ); + orps xmm0, xmm5 + movaps [edi+0*16], xmm0 + subps xmm0, xmm6 + movaps [edi+1*16], xmm0 + add edi, 2*16 + + skip1: + cmp dword ptr [edx+eax+4], ebx + jne skip2 + + mov dword ptr [edx+eax+4], ecx + movss xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movhps xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + add ecx, 2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm1, xmm5 + movaps [edi+0*16], xmm1 + subps xmm1, xmm7 + movaps [edi+1*16], xmm1 + add edi, 2*16 + + skip2: + cmp dword ptr [edx+eax+8], ebx + jne skip3 + + mov dword ptr [edx+eax+8], ecx + movss xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 3, 0, 1 ); + orps xmm2, xmm5 + movaps [edi+0*16], xmm2 + subps xmm2, xmm6 + movaps [edi+1*16], xmm2 + add edi, 2*16 + + skip3: + cmp dword ptr [edx+eax+12], ebx + jne skip4 + + mov dword ptr [edx+eax+12], ecx + movss xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movhps xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + add ecx, 2 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm3, xmm5 + movaps [edi+0*16], xmm3 + subps xmm3, xmm7 + movaps [edi+1*16], xmm3 + add edi, 2*16 + + skip4: + add esi, 4*DRAWVERT_SIZE + add eax, 4*4 + jl loop4 + + done4: + mov eax, numVerts + and eax, 3 + jz done1 + shl eax, 2 + add edx, eax + neg eax + + loop1: + cmp dword ptr [edx+eax+0], ebx + jne skip0 + + mov dword ptr [edx+eax+0], ecx + movss xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) + orps xmm0, xmm5 + movaps [edi+0*16], xmm0 + subps xmm0, xmm6 + movaps [edi+1*16], xmm0 + add edi, 2*16 + + skip0: + + add esi, DRAWVERT_SIZE + add eax, 4 + jl loop1 + + done1: + pop ebx + mov outVerts, ecx + } + return outVerts; + +#else + + int outVerts = 0; + for ( int i = 0; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; + +#endif +} + +/* +============ +idSIMD_SSE::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_SSE::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { +#if 1 + + assert_16_byte_aligned( vertexCache ); + assert_16_byte_aligned( verts ); + assert_16_byte_aligned( DRAWVERT_SIZE ); + assert_16_byte_aligned( DRAWVERT_XYZ_OFFSET ); + + __asm { + movaps xmm4, SIMD_SP_clearLast + movaps xmm5, SIMD_SP_lastOne + movaps xmm6, xmm4 + movaps xmm7, xmm5 + + mov esi, verts + mov edi, vertexCache + mov eax, numVerts + and eax, ~3 + jz done4 + shl eax, 5 + add edi, eax + neg eax + + loop4: + prefetchnta [esi+4*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + + movaps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm0, xmm4 + movaps [edi+eax+1*16], xmm0 + orps xmm0, xmm5 + movaps [edi+eax+0*16], xmm0 + + movaps xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm1, xmm6 + movaps [edi+eax+3*16], xmm1 + orps xmm1, xmm7 + movaps [edi+eax+2*16], xmm1 + + movaps xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm2, xmm4 + movaps [edi+eax+5*16], xmm2 + orps xmm2, xmm5 + movaps [edi+eax+4*16], xmm2 + + movaps xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm3, xmm6 + movaps [edi+eax+7*16], xmm3 + orps xmm3, xmm7 + movaps [edi+eax+6*16], xmm3 + + add esi, 4*DRAWVERT_SIZE + add eax, 4*8*4 + jl loop4 + + done4: + mov eax, numVerts + and eax, 3 + jz done1 + shl eax, 5 + add edi, eax + neg eax + + loop1: + movaps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm0, xmm4 + movaps [edi+eax+1*16], xmm0 + orps xmm0, xmm5 + movaps [edi+eax+0*16], xmm0 + + add esi, DRAWVERT_SIZE + add eax, 8*4 + jl loop1 + + done1: + } + return numVerts * 2; + +#else + + for ( int i = 0; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; + +#endif +} + +/* +============ +SSE_UpSample11kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample11kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-2*4*4+0], xmm0 + movhps [edi-2*4*4+8], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-1*4*4+0], xmm1 + movhps [edi-1*4*4+8], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + movhps [edi+8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample11kHzStereoPCMTo44kHz +============ +*/ +static void SSE_UpSample11kHzStereoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + test eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 8*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + + unpcklps xmm0, xmm1 + + movlps [edi-8*4+0], xmm0 + movlps [edi-8*4+8], xmm0 + movlps [edi-4*4+0], xmm0 + movlps [edi-4*4+8], xmm0 + + add eax, 2*2 + jl loop2 + + done2: + } +} + +/* +============ +SSE_UpSample22kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample22kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-4*4+0], xmm0 + movhps [edi-4*4+8], xmm0 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzStereoPCMTo44kHz +============ +*/ +static void SSE_UpSample22kHzStereoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + test eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + movss [edi-4*4], xmm0 + movss [edi-2*4], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + movss [edi-3*4], xmm1 + movss [edi-1*4], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + } +} + +/* +============ +SSE_UpSample44kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample44kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + movss [edi-2*4], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + movss [edi-1*4], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + movss [edi], xmm0 + + done: + } +} + +/* +============ +idSIMD_SSE::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_SSE::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + SSE_UpSample11kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + SSE_UpSample11kHzStereoPCMTo44kHz( dest, src, numSamples ); + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + SSE_UpSample22kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + SSE_UpSample22kHzStereoPCMTo44kHz( dest, src, numSamples ); + } + } else if ( kHz == 44100 ) { + SSE_UpSample44kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + assert( 0 ); + } +} + +/* +============ +SSE_UpSample11kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample11kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 2 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*16 + + movss xmm0, [esi+eax+0] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-32], xmm0 + movlps [edi-24], xmm0 + + movss xmm1, [esi+eax+4] + mulss xmm1, xmm7 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-16], xmm1 + movlps [edi- 8], xmm1 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [esi] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + movlps [edi+8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample11kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample11kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 4*16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-8*8], xmm0 + movlps [edi-7*8], xmm0 + movlps [edi-6*8], xmm0 + movlps [edi-5*8], xmm0 + movhps [edi-4*8], xmm0 + movhps [edi-3*8], xmm0 + movhps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + movlps [edi+1*8], xmm0 + movlps [edi+2*8], xmm0 + movlps [edi+3*8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample22kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 2 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*8 + + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm7 + movlps [edi-16], xmm0 + movhps [edi- 8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [esi] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample22kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 2*16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-4*8], xmm0 + movlps [edi-3*8], xmm0 + movhps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + movlps [edi+1*8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample44kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample44kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + KFLOAT_CA( mul, dest, src, constant, numSamples ) +} + +/* +============ +SSE_UpSample44kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample44kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + + done: + } +} + +/* +============ +idSIMD_SSE::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_SSE::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + SSE_UpSample11kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample11kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + SSE_UpSample22kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample22kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + SSE_UpSample44kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample44kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { +#if 1 + + ALIGN16( float incs[2]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm6, [ecx] + xorps xmm7, xmm7 + movhps xmm7, incs + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + addps xmm6, xmm7 + shufps xmm7, xmm7, R_SHUFFLE_PS( 2, 3, 2, 3 ) + addps xmm7, xmm7 + + loop16: + add edi, 4*4*4 + + movaps xmm0, [esi+eax+0*4*4] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm0, xmm6 + addps xmm0, [edi-4*4*4] + addps xmm6, xmm7 + movaps [edi-4*4*4], xmm0 + + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm1, xmm6 + addps xmm1, [edi-3*4*4] + addps xmm6, xmm7 + movaps [edi-3*4*4], xmm1 + + movaps xmm2, [esi+eax+1*4*4] + movaps xmm3, xmm2 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm2, xmm6 + addps xmm2, [edi-2*4*4] + addps xmm6, xmm7 + movaps [edi-2*4*4], xmm2 + + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm3, xmm6 + addps xmm3, [edi-1*4*4] + addps xmm6, xmm7 + movaps [edi-1*4*4], xmm3 + + add eax, 2*4*4 + + jl loop16 + } + +#else + + int i; + float incL; + float incR; + float sL0, sL1; + float sR0, sR1; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sR0 = lastV[1]; + sL1 = lastV[0] + incL; + sR1 = lastV[1] + incR; + + incL *= 2; + incR *= 2; + + for( i = 0; i < MIXBUFFER_SAMPLES; i += 2 ) { + mixBuffer[i*2+0] += samples[i+0] * sL0; + mixBuffer[i*2+1] += samples[i+0] * sR0; + mixBuffer[i*2+2] += samples[i+1] * sL1; + mixBuffer[i*2+3] += samples[i+1] * sR1; + sL0 += incL; + sR0 += incR; + sL1 += incL; + sR1 += incR; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerMonoSimple( float *mixBuffer, const float *samples, const int numSamples ) { +#if 1 + + assert( numSamples == MIXBUFFER_SAMPLES ); + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + loop16: + add edi, 4*4*4 + + movups xmm0, [esi+eax+0*4*4] + movups xmm1, xmm0 + movups xmm2, [esi+eax+1*4*4] + movups xmm3, xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) + + addps xmm0, [edi-4*4*4] + addps xmm1, [edi-3*4*4] + addps xmm2, [edi-2*4*4] + addps xmm3, [edi-1*4*4] + + movaps [edi-4*4*4], xmm0 + movaps [edi-3*4*4], xmm1 + movaps [edi-2*4*4], xmm2 + movaps [edi-1*4*4], xmm3 + + add eax, 2*4*4 + + jl loop16 + } + +#else + + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMonoSimple"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j += 2 ) { + mixBuffer[j*2+0] += samples[j]; + mixBuffer[j*2+1] += samples[j]; + mixBuffer[j*2+2] += samples[j+1]; + mixBuffer[j*2+3] += samples[j+1]; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerStereo +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { +#if 1 + + ALIGN16( float incs[2]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 3 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm6, [ecx] + xorps xmm7, xmm7 + movhps xmm7, incs + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + addps xmm6, xmm7 + shufps xmm7, xmm7, R_SHUFFLE_PS( 2, 3, 2, 3 ) + addps xmm7, xmm7 + + loop16: + add edi, 4*4*4 + + movaps xmm0, [esi+eax+0*4*4] + mulps xmm0, xmm6 + addps xmm0, [edi-4*4*4] + addps xmm6, xmm7 + movaps [edi-4*4*4], xmm0 + + movaps xmm2, [esi+eax+1*4*4] + mulps xmm2, xmm6 + addps xmm2, [edi-3*4*4] + addps xmm6, xmm7 + movaps [edi-3*4*4], xmm2 + + movaps xmm3, [esi+eax+2*4*4] + mulps xmm3, xmm6 + addps xmm3, [edi-2*4*4] + addps xmm6, xmm7 + movaps [edi-2*4*4], xmm3 + + movaps xmm4, [esi+eax+3*4*4] + mulps xmm4, xmm6 + addps xmm4, [edi-1*4*4] + addps xmm6, xmm7 + movaps [edi-1*4*4], xmm4 + + add eax, 4*4*4 + + jl loop16 + } + +#else + + int i; + float incL; + float incR; + float sL0, sL1; + float sR0, sR1; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sR0 = lastV[1]; + sL1 = lastV[0] + incL; + sR1 = lastV[1] + incR; + + incL *= 2; + incR *= 2; + + for( i = 0; i < MIXBUFFER_SAMPLES; i += 2 ) { + mixBuffer[i*2+0] += samples[i*2+0] * sL0; + mixBuffer[i*2+1] += samples[i*2+1] * sR0; + mixBuffer[i*2+2] += samples[i*2+2] * sL1; + mixBuffer[i*2+3] += samples[i*2+3] * sR1; + sL0 += incL; + sR0 += incR; + sL1 += incL; + sR1 += incR; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundSixSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { +#if 1 + + ALIGN16( float incs[6]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incs[2] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incs[3] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incs[4] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incs[5] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm2, [ecx+ 0] + movhps xmm2, [ecx+ 8] + movlps xmm3, [ecx+16] + movaps xmm4, xmm2 + shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm4, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + + xorps xmm5, xmm5 + movhps xmm5, incs + movlps xmm7, incs+8 + movhps xmm7, incs+16 + addps xmm3, xmm5 + addps xmm4, xmm7 + shufps xmm5, xmm7, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movaps xmm6, xmm7 + shufps xmm6, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm5, xmm5 + addps xmm6, xmm6 + addps xmm7, xmm7 + + loop24: + add edi, 6*16 + + movaps xmm0, [esi+eax] + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm2 + addps xmm1, [edi-6*16] + addps xmm2, xmm5 + movaps [edi-6*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm1, xmm3 + addps xmm1, [edi-5*16] + addps xmm3, xmm6 + movaps [edi-5*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm1, xmm4 + addps xmm1, [edi-4*16] + addps xmm4, xmm7 + movaps [edi-4*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm1, xmm2 + addps xmm1, [edi-3*16] + addps xmm2, xmm5 + movaps [edi-3*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm1, xmm3 + addps xmm1, [edi-2*16] + addps xmm3, xmm6 + movaps [edi-2*16], xmm1 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 3, 3, 3 ) + mulps xmm0, xmm4 + addps xmm0, [edi-1*16] + addps xmm4, xmm7 + movaps [edi-1*16], xmm0 + + add eax, 4*4 + + jl loop24 + } + +#else + + int i; + float sL0, sL1, sL2, sL3, sL4, sL5, sL6, sL7, sL8, sL9, sL10, sL11; + float incL0, incL1, incL2, incL3, incL4, incL5; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sL1 = lastV[1]; + sL2 = lastV[2]; + sL3 = lastV[3]; + sL4 = lastV[4]; + sL5 = lastV[5]; + + sL6 = lastV[0] + incL0; + sL7 = lastV[1] + incL1; + sL8 = lastV[2] + incL2; + sL9 = lastV[3] + incL3; + sL10 = lastV[4] + incL4; + sL11 = lastV[5] + incL5; + + incL0 *= 2; + incL1 *= 2; + incL2 *= 2; + incL3 *= 2; + incL4 *= 2; + incL5 *= 2; + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + mixBuffer[i*6+ 0] += samples[i+0] * sL0; + mixBuffer[i*6+ 1] += samples[i+0] * sL1; + mixBuffer[i*6+ 2] += samples[i+0] * sL2; + mixBuffer[i*6+ 3] += samples[i+0] * sL3; + + mixBuffer[i*6+ 4] += samples[i+0] * sL4; + mixBuffer[i*6+ 5] += samples[i+0] * sL5; + mixBuffer[i*6+ 6] += samples[i+1] * sL6; + mixBuffer[i*6+ 7] += samples[i+1] * sL7; + + mixBuffer[i*6+ 8] += samples[i+1] * sL8; + mixBuffer[i*6+ 9] += samples[i+1] * sL9; + mixBuffer[i*6+10] += samples[i+1] * sL10; + mixBuffer[i*6+11] += samples[i+1] * sL11; + + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + + sL4 += incL4; + sL5 += incL5; + sL6 += incL0; + sL7 += incL1; + + sL8 += incL2; + sL9 += incL3; + sL10 += incL4; + sL11 += incL5; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundSixSpeakerStereo +============ +*/ +void VPCALL idSIMD_SSE::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { +#if 1 + + ALIGN16( float incs[6]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incs[2] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incs[3] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incs[4] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incs[5] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 3 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm2, [ecx+ 0] + movhps xmm2, [ecx+ 8] + movlps xmm3, [ecx+16] + movaps xmm4, xmm2 + shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm4, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + + xorps xmm5, xmm5 + movhps xmm5, incs + movlps xmm7, incs+ 8 + movhps xmm7, incs+16 + addps xmm3, xmm5 + addps xmm4, xmm7 + shufps xmm5, xmm7, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movaps xmm6, xmm7 + shufps xmm6, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm5, xmm5 + addps xmm6, xmm6 + addps xmm7, xmm7 + + loop12: + add edi, 3*16 + + movaps xmm0, [esi+eax+0] + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 0 ) + mulps xmm1, xmm2 + addps xmm1, [edi-3*16] + addps xmm2, xmm5 + movaps [edi-3*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 2, 3 ) + mulps xmm1, xmm3 + addps xmm1, [edi-2*16] + addps xmm3, xmm6 + movaps [edi-2*16], xmm1 + + add eax, 4*4 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 2, 2, 3 ) + mulps xmm0, xmm4 + addps xmm0, [edi-1*16] + addps xmm4, xmm7 + movaps [edi-1*16], xmm0 + + jl loop12 + + emms + } + +#else + + int i; + float sL0, sL1, sL2, sL3, sL4, sL5, sL6, sL7, sL8, sL9, sL10, sL11; + float incL0, incL1, incL2, incL3, incL4, incL5; + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sL1 = lastV[1]; + sL2 = lastV[2]; + sL3 = lastV[3]; + sL4 = lastV[4]; + sL5 = lastV[5]; + + sL6 = lastV[0] + incL0; + sL7 = lastV[1] + incL1; + sL8 = lastV[2] + incL2; + sL9 = lastV[3] + incL3; + sL10 = lastV[4] + incL4; + sL11 = lastV[5] + incL5; + + incL0 *= 2; + incL1 *= 2; + incL2 *= 2; + incL3 *= 2; + incL4 *= 2; + incL5 *= 2; + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + mixBuffer[i*6+ 0] += samples[i*2+0+0] * sL0; + mixBuffer[i*6+ 1] += samples[i*2+0+1] * sL1; + mixBuffer[i*6+ 2] += samples[i*2+0+0] * sL2; + mixBuffer[i*6+ 3] += samples[i*2+0+0] * sL3; + + mixBuffer[i*6+ 4] += samples[i*2+0+0] * sL4; + mixBuffer[i*6+ 5] += samples[i*2+0+1] * sL5; + mixBuffer[i*6+ 6] += samples[i*2+2+0] * sL6; + mixBuffer[i*6+ 7] += samples[i*2+2+1] * sL7; + + mixBuffer[i*6+ 8] += samples[i*2+2+0] * sL8; + mixBuffer[i*6+ 9] += samples[i*2+2+0] * sL9; + mixBuffer[i*6+10] += samples[i*2+2+0] * sL10; + mixBuffer[i*6+11] += samples[i*2+2+1] * sL11; + + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + + sL4 += incL4; + sL5 += incL5; + sL6 += incL0; + sL7 += incL1; + + sL8 += incL2; + sL9 += incL3; + sL10 += incL4; + sL11 += incL5; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_SSE::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { +#if 1 + + assert( ( numSamples % MIXBUFFER_SAMPLES ) == 0 ); + + __asm { + + mov eax, numSamples + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add edi, eax + neg eax + + loop16: + + movaps xmm0, [edi+eax+0*16] + movaps xmm2, [edi+eax+1*16] + movaps xmm4, [edi+eax+2*16] + movaps xmm6, [edi+eax+3*16] + + add esi, 4*4*2 + + movhlps xmm1, xmm0 + movhlps xmm3, xmm2 + movhlps xmm5, xmm4 + movhlps xmm7, xmm6 + + prefetchnta [edi+eax+64] + + cvtps2pi mm0, xmm0 + cvtps2pi mm2, xmm2 + cvtps2pi mm4, xmm4 + cvtps2pi mm6, xmm6 + + prefetchnta [edi+eax+128] + + cvtps2pi mm1, xmm1 + cvtps2pi mm3, xmm3 + cvtps2pi mm5, xmm5 + cvtps2pi mm7, xmm7 + + add eax, 4*16 + + packssdw mm0, mm1 + packssdw mm2, mm3 + packssdw mm4, mm5 + packssdw mm6, mm7 + + movq [esi-4*4*2], mm0 + movq [esi-3*4*2], mm2 + movq [esi-2*4*2], mm4 + movq [esi-1*4*2], mm6 + + jl loop16 + + emms + } + +#else + + for ( int i = 0; i < numSamples; i++ ) { + if ( mixBuffer[i] <= -32768.0f ) { + samples[i] = -32768; + } else if ( mixBuffer[i] >= 32767.0f ) { + samples[i] = 32767; + } else { + samples[i] = (short) mixBuffer[i]; + } + } + +#endif +} + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT +/* +============ +idSIMD_SSE::TransformVertsMinMax4Bone +============ +*/ +// dluetscher: added TransformVertsMinMax to transform an array of index-weighted vertices into +// an array of idSilTraceVerts, while simulatenously calculating the bounds +static ALIGN16( float packedOnes[4] ) = { 1.0f, 1.0f, 1.0f, 1.0f }; + +void VPCALL idSIMD_SSE::TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte *vertexInputData, + int vertStride, int numVerts, + float *skinToModelTransforms ) { + byte *vertexOutputData, *endVertexInputData; + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + vertexOutputData = (byte*) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + __asm + { + mov eax, vertexInputData + mov ecx, skinToModelTransforms + + ; + ; load the indices into ebx, and 4 weights into xmm0 + ; + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + shl edx, 6 ; multiply i0 by 64 + movaps xmm0, [eax+16] ; xmm0 = w0, w1, w2, w3 + and edx, 0x3FC0 + add edx, ecx + movaps xmm7, xmm0 + + ; + ; load the first matrix, scale by first weight + ; + movaps xmm1, [edx] ; load first matrix + shufps xmm7, xmm0, 0x00 ; broadcast w0 to all 4 values + movaps xmm2, [edx+16] + movaps xmm3, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm1, xmm7 ; scale first matrix by first weight + shr edx, 2 ; multiply i1 by 64 (right shift by 8, left shift by 6) + mulps xmm2, xmm7 + and edx, 0x3FC0 + mulps xmm3, xmm7 + add edx, ecx + movaps xmm7, xmm0 + + ; + ; load the second matrix, scale by the second weight, accumulate + ; + movaps xmm4, [edx] ; load second matrix + shufps xmm7, xmm0, 0x55 ; broadcast w1 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm4, xmm7 ; scale second matrix by second weight + shr edx, 10 ; multiply i2 by 64 (right shift by 16, left shift by 6) + mulps xmm5, xmm7 + addps xmm1, xmm4 + and edx, 0x3FC0 + mulps xmm6, xmm7 + addps xmm2, xmm5 + add edx, ecx + movaps xmm7, xmm0 + addps xmm3, xmm6 + + ; + ; load the third matrix, scale by the third weight, accumulate + ; + movaps xmm4, [edx] ; load third matrix + shufps xmm7, xmm0, 0xAA ; broadcast w3 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm4, xmm7 ; scale third matrix by third weight + shr edx, 18 ; multiply i3 by 64 (right shift by 24, left shift by 6) + mulps xmm5, xmm7 + addps xmm1, xmm4 + and edx, 0x3FC0 + mulps xmm6, xmm7 + addps xmm2, xmm5 + add edx, ecx + movaps xmm7, xmm0 + addps xmm3, xmm6 + + ; + ; load the fourth matrix, scale by the fourth weight, accumulate + ; + movaps xmm4, [edx] ; load fourth matrix + shufps xmm7, xmm0, 0xFF ; broadcast w4 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + movaps xmm0, [eax] ; xmm0 = x, y, z + + mulps xmm4, xmm7 ; scale fourth matrix by fourth weight + mulps xmm5, xmm7 + mulps xmm6, xmm7 + movaps xmm7, packedOnes + + addps xmm1, xmm4 + movaps xmm4, xmm0 + addps xmm2, xmm5 + addps xmm3, xmm6 + movss xmm4, xmm7 + + ; + ; transform the position by the combined matrix + ; + mov eax, vertexOutputData + mov edx, curMin + shufps xmm0, xmm4, 0x24 + mov ecx, curMax + + mulps xmm1, xmm0 + mulps xmm2, xmm0 + mulps xmm3, xmm0 + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + shufps xmm4, xmm4, 0x4E + shufps xmm5, xmm5, 0x4E + shufps xmm6, xmm6, 0x4E + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + movaps xmm0, [edx] + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + movaps xmm7, [ecx] + + shufps xmm4, xmm4, 0x11 + shufps xmm5, xmm5, 0x11 + shufps xmm6, xmm6, 0x11 + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + shufps xmm1, xmm2, 0 + shufps xmm1, xmm3, 0x8 + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps [eax], xmm1 + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +void VPCALL idSIMD_SSE::TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte *vertexInputData, + int vertStride, int numVerts, + float *skinToModelTransforms ) { + byte *vertexOutputData, *endVertexInputData; + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + vertexOutputData = (byte*) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + __asm + { + mov eax, vertexInputData + mov ecx, skinToModelTransforms + + ; + ; load the indices into ebx + ; + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + movaps xmm0, [eax] ; xmm0 = x, y, z + shl edx, 6 ; multiply i0 by 64 + movaps xmm7, packedOnes + and edx, 0x3FC0 + movaps xmm4, xmm0 + add edx, ecx + movss xmm4, xmm7 + + ; + ; load the first matrix + ; + movaps xmm1, [edx] ; load first matrix + movaps xmm2, [edx+16] + movaps xmm3, [edx+32] + + ; + ; transform the position by the combined matrix + ; + mov eax, vertexOutputData + mov edx, curMin + shufps xmm0, xmm4, 0x24 + mov ecx, curMax + + mulps xmm1, xmm0 + mulps xmm2, xmm0 + mulps xmm3, xmm0 + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + shufps xmm4, xmm4, 0x4E + shufps xmm5, xmm5, 0x4E + shufps xmm6, xmm6, 0x4E + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + movaps xmm0, [edx] + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + movaps xmm7, [ecx] + + shufps xmm4, xmm4, 0x11 + shufps xmm5, xmm5, 0x11 + shufps xmm6, xmm6, 0x11 + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + shufps xmm1, xmm2, 0 + shufps xmm1, xmm3, 0x8 + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps [eax], xmm1 + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ) { + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + // 0, 1, 2 + // 3, 4, 5 + // 6, 7, 8 + // 9, 10, 11 + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert4: + movss xmm0, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, X, X + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 2, X, X, X + movhps xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, 0, 1 + movaps xmm1, xmm0 // 3, X, 0, 1 + + movlps xmm1, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 4, 5, 0, 1 + shufps xmm2, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) // 2, X, 4, 5 + + movss xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, X, X + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, 6, 7 + shufps xmm0, xmm3, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 0, 3, 6, 9 + + movlps xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 10, 11, 6, 7 + shufps xmm1, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 1, 4, 7, 10 + + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 10, 11, 8, X + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 3, 2, 1 ) // 2, 5, 8, 11 + + add ecx, 16 + add eax, 4*SILTRACEVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, SILTRACEVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ) { + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + // 0, 1, 2 + // 3, 4, 5 + // 6, 7, 8 + // 9, 10, 11 + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert4: + movss xmm0, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, X, X + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 2, X, X, X + movhps xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, 0, 1 + movaps xmm1, xmm0 // 3, X, 0, 1 + + movlps xmm1, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 4, 5, 0, 1 + shufps xmm2, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) // 2, X, 4, 5 + + movss xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, X, X + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, 6, 7 + shufps xmm0, xmm3, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 0, 3, 6, 9 + + movlps xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 10, 11, 6, 7 + shufps xmm1, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 1, 4, 7, 10 + + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 10, 11, 8, X + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 3, 2, 1 ) // 2, 5, 8, 11 + + add ecx, 16 + add eax, 4*SILTRACEVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, SILTRACEVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + + +/* +============ +idSIMD_SSE::TracePointCull +============ +*/ +void VPCALL idSIMD_SSE::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + + mov edi, planes + movlps xmm1, [edi] // xmm1 = 0, 1, X, X + movhps xmm1, [edi+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [edi+8] // xmm3 = 2, 3, X, X + movhps xmm3, [edi+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [edi+32] // xmm4 = 8, 9, X, X + movhps xmm4, [edi+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [edi+40] // xmm5 = 10, 11, X, X + movhps xmm5, [edi+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + movss xmm7, radius + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + xor edx, edx + mov esi, verts + mov edi, cullBits + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert: + movss xmm4, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + mulps xmm4, xmm0 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulps xmm5, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + addps xmm4, xmm5 + mulps xmm6, xmm2 + addps xmm4, xmm3 + addps xmm4, xmm6 + movaps xmm5, xmm4 + xorps xmm5, SIMD_SP_signBit + cmpltps xmm4, xmm7 + movmskps ecx, xmm4 + cmpltps xmm5, xmm7 + movmskps ebx, xmm5 + shl cx, 4 + or cl, bl + inc edi + or dl, cl + add eax, SILTRACEVERT_SIZE + mov byte ptr [edi-1], cl + jl loopVert + + done: + mov esi, totalOr + mov byte ptr [esi], dl + pop ebx + } + +#else + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; + +#endif +} + +/* +============ +idSIMD_SSE::DecalPointCull +============ +*/ +void VPCALL idSIMD_SSE::DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + ALIGN16( float p0[4]; ) + ALIGN16( float p1[4]; ) + ALIGN16( float p2[4]; ) + ALIGN16( float p3[4]; ) + ALIGN16( float p4[4]; ) + ALIGN16( float p5[4]; ) + ALIGN16( float p6[4]; ) + ALIGN16( float p7[4]; ) + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov ecx, planes + movlps xmm1, [ecx] // xmm1 = 0, 1, X, X + movhps xmm1, [ecx+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [ecx+8] // xmm3 = 2, 3, X, X + movhps xmm3, [ecx+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [ecx+32] // xmm4 = 8, 9, X, X + movhps xmm4, [ecx+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [ecx+40] // xmm5 = 10, 11, X, X + movhps xmm5, [ecx+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + + movaps p0, xmm0 + movaps p1, xmm1 + movaps p2, xmm2 + movaps p3, xmm3 + + movlps xmm4, [ecx+64] // xmm4 = p40, p41, X, X + movhps xmm4, [ecx+80] // xmm4 = p40, p41, p50, p51 + movaps xmm5, xmm4 // xmm5 = p40, p41, p50, p51 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm4 = p40, p50, p40, p50 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm5 = p41, p51, p41, p51 + movlps xmm6, [ecx+72] // xmm6 = p42, p43, X, X + movhps xmm6, [ecx+88] // xmm6 = p42, p43, p52, p53 + movaps xmm7, xmm6 // xmm7 = p42, p43, p52, p53 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm6 = p42, p52, p42, p52 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm7 = p43, p53, p43, p53 + + movaps p4, xmm4 + movaps p5, xmm5 + movaps p6, xmm6 + movaps p7, xmm7 + + mov esi, verts + mov edi, cullBits + mov eax, numVerts + and eax, ~1 + jz done2 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert2: + movaps xmm6, p0 + movss xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + movaps xmm6, p0 + movss xmm3, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm3 + movaps xmm7, p1 + movss xmm4, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm5, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps edx, xmm6 + mov ch, dl + + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, p4 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, p5 + addps xmm0, xmm1 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + add edi, 2 + + mov dh, dl + shl dl, 4 + shl dh, 2 + and edx, (3<<4)|(3<<12) + or ecx, edx + + add eax, 2*SILTRACEVERT_SIZE + mov word ptr [edi-2], cx + jl loopVert2 + + done2: + + mov eax, numVerts + and eax, 1 + jz done + + movaps xmm6, p0 + movss xmm0, [esi+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + mulps xmm0, p4 + mulps xmm1, p5 + addps xmm0, xmm1 + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + and edx, 3 + shl edx, 4 + or ecx, edx + + mov byte ptr [edi], cl + + done: + } + + +#else + + int i; + + for ( i = 0; i < numVerts; i += 2 ) { + unsigned short bits0, bits1; + float d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11; + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = planes[0][0] * v0[0] + planes[0][1] * v0[1] + planes[0][2] * v0[2] + planes[0][3]; + d1 = planes[1][0] * v0[0] + planes[1][1] * v0[1] + planes[1][2] * v0[2] + planes[1][3]; + d2 = planes[2][0] * v0[0] + planes[2][1] * v0[1] + planes[2][2] * v0[2] + planes[2][3]; + d3 = planes[3][0] * v0[0] + planes[3][1] * v0[1] + planes[3][2] * v0[2] + planes[3][3]; + + d4 = planes[4][0] * v0[0] + planes[4][1] * v0[1] + planes[4][2] * v0[2] + planes[4][3]; + d5 = planes[5][0] * v0[0] + planes[5][1] * v0[1] + planes[5][2] * v0[2] + planes[5][3]; + d10 = planes[4][0] * v1[0] + planes[4][1] * v1[1] + planes[4][2] * v1[2] + planes[4][3]; + d11 = planes[5][0] * v1[0] + planes[5][1] * v1[1] + planes[5][2] * v1[2] + planes[5][3]; + + d6 = planes[0][0] * v1[0] + planes[0][1] * v1[1] + planes[0][2] * v1[2] + planes[0][3]; + d7 = planes[1][0] * v1[0] + planes[1][1] * v1[1] + planes[1][2] * v1[2] + planes[1][3]; + d8 = planes[2][0] * v1[0] + planes[2][1] * v1[1] + planes[2][2] * v1[2] + planes[2][3]; + d9 = planes[3][0] * v1[0] + planes[3][1] * v1[1] + planes[3][2] * v1[2] + planes[3][3]; + + bits0 = FLOATSIGNBITSET( d0 ) << (0+0); + bits0 |= FLOATSIGNBITSET( d1 ) << (0+1); + bits0 |= FLOATSIGNBITSET( d2 ) << (0+2); + bits0 |= FLOATSIGNBITSET( d3 ) << (0+3); + bits0 |= FLOATSIGNBITSET( d4 ) << (0+4); + bits0 |= FLOATSIGNBITSET( d5 ) << (0+5); + + bits1 = FLOATSIGNBITSET( d6 ) << (8+0); + bits1 |= FLOATSIGNBITSET( d7 ) << (8+1); + bits1 |= FLOATSIGNBITSET( d8 ) << (8+2); + bits1 |= FLOATSIGNBITSET( d9 ) << (8+3); + bits1 |= FLOATSIGNBITSET( d10 ) << (8+4); + bits1 |= FLOATSIGNBITSET( d11 ) << (8+5); + + *(unsigned short *)(cullBits + i) = ( bits0 | bits1 ) ^ 0x3F3F; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[numVerts - 1].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + d4 = planes[4][0] * v[0] + planes[4][1] * v[1] + planes[4][2] * v[2] + planes[4][3]; + d5 = planes[5][0] * v[0] + planes[5][1] * v[1] + planes[5][2] * v[2] + planes[5][3]; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[numVerts - 1] = bits ^ 0x3F; // flip lower 6 bits + } + +#endif +} + +/* +============ +idSIMD_SSE::OverlayPointCull +============ +*/ +void VPCALL idSIMD_SSE::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov eax, numVerts + mov edx, verts + mov esi, texCoords + mov edi, cullBits + + mov ecx, planes + movss xmm4, [ecx+ 0] + movss xmm5, [ecx+16] + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm5, [ecx+ 4] + movss xmm6, [ecx+20] + shufps xmm5, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm6, [ecx+ 8] + movss xmm7, [ecx+24] + shufps xmm6, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm7, [ecx+12] + movss xmm0, [ecx+28] + shufps xmm7, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) + + and eax, ~1 + jz done2 + add edi, eax + neg eax + + loopVert2: + movss xmm0, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm3, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movaps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 3, 2, 3 ) + add edx, 2*SILTRACEVERT_SIZE + movmskps ecx, xmm0 + mov byte ptr [edi+eax+0], cl + add esi, 4*4 + movmskps ecx, xmm1 + mov byte ptr [edi+eax+1], cl + add eax, 2 + jl loopVert2 + + done2: + mov eax, numVerts + and eax, 1 + jz done + + movss xmm0, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movlps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movmskps ecx, xmm0 + mov byte ptr [edi], cl + + done: + } + +#else + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + + for ( int i = 0; i < numVerts - 1; i += 2 ) { + unsigned short bits; + float d0, d1, d2, d3; + + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + d2 = p0[0] * v1[0] + p0[1] * v1[1] + p0[2] * v1[2] + p0[3]; + d3 = p1[0] * v1[0] + p1[1] * v1[1] + p1[2] * v1[2] + p1[3]; + + texCoords[i+0][0] = d0; + texCoords[i+0][1] = d1; + texCoords[i+1][0] = d2; + texCoords[i+1][1] = d3; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 8; + bits |= FLOATSIGNBITSET( d3 ) << 9; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + d2 = 1.0f - d2; + d3 = 1.0f - d3; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + bits |= FLOATSIGNBITSET( d2 ) << 10; + bits |= FLOATSIGNBITSET( d3 ) << 11; + + *(unsigned short *)(cullBits + i) = bits; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1; + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + const idVec3 &v0 = verts[numVerts - 1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[numVerts - 1] = bits; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTriPlanes +============ +*/ +void VPCALL idSIMD_SSE::DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov eax, numIndexes + shl eax, 2 + mov esi, verts + mov edi, indexes + mov edx, planes + + add edi, eax + neg eax + + add eax, 4*12 + jge done4 + + loopPlane4: + mov ebx, [edi+eax-4*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-4*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm1, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm2, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax-4*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm3, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm3, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm4, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm4, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm5, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm5, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax-3*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-3*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-3*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + mov ebx, [edi+eax-2*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-2*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-2*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + mov ebx, [edi+eax-1*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-1*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-1*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + movaps xmm6, xmm4 + mulps xmm6, xmm2 + movaps xmm7, xmm5 + mulps xmm7, xmm1 + subps xmm6, xmm7 + + mulps xmm5, xmm0 + mulps xmm2, xmm3 + subps xmm5, xmm2 + + mulps xmm3, xmm1 + mulps xmm4, xmm0 + subps xmm3, xmm4 + + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm1, xmm5 + mulps xmm5, xmm5 + movaps xmm2, xmm3 + mulps xmm3, xmm3 + + addps xmm3, xmm5 + addps xmm3, xmm6 + rsqrtps xmm3, xmm3 + + add edx, 4*16 + mov ecx, [edi+eax-1*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [edx-1*16+0], xmm0 + movss [edx-1*16+4], xmm1 + movss [edx-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-1*16+12], xmm0 + + mov ecx, [edi+eax-2*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-2*16+0], xmm0 + movss [edx-2*16+4], xmm1 + movss [edx-2*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-2*16+12], xmm0 + + mov ecx, [edi+eax-3*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-3*16+0], xmm0 + movss [edx-3*16+4], xmm1 + movss [edx-3*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-3*16+12], xmm0 + + mov ecx, [edi+eax-4*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-4*16+0], xmm0 + movss [edx-4*16+4], xmm1 + movss [edx-4*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-4*16+12], xmm0 + + add eax, 4*12 + jle loopPlane4 + + done4: + + sub eax, 4*12 + jge done + + loopPlane1: + mov ebx, [edi+eax+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm1, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm2, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + movss xmm3, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm3, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm4, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm4, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm5, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm5, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + movss xmm6, xmm4 + mulss xmm6, xmm2 + movss xmm7, xmm5 + mulss xmm7, xmm1 + subss xmm6, xmm7 + + mulss xmm5, xmm0 + mulss xmm2, xmm3 + subss xmm5, xmm2 + + mulss xmm3, xmm1 + mulss xmm4, xmm0 + subss xmm3, xmm4 + + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm1, xmm5 + mulss xmm5, xmm5 + movss xmm2, xmm3 + mulss xmm3, xmm3 + + addss xmm3, xmm5 + addss xmm3, xmm6 + rsqrtss xmm3, xmm3 + + add edx, 1*16 + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [edx-1*16+0], xmm0 + movss [edx-1*16+4], xmm1 + movss [edx-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-1*16+12], xmm0 + + add eax, 1*12 + jl loopPlane1 + + done: + } + +#else + + int i, j; + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + + for ( j = 0; j < 4; j++ ) { + const rvSilTraceVertT *a, *b, *c; + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + + d3[j] = c->xyz[0] - a->xyz[0]; + d4[j] = c->xyz[1] - a->xyz[1]; + d5[j] = c->xyz[2] - a->xyz[2]; + } + + ALIGN16( float tmp[4]; ) + + n0[0] = d4[0] * d2[0]; + n0[1] = d4[1] * d2[1]; + n0[2] = d4[2] * d2[2]; + n0[3] = d4[3] * d2[3]; + + n0[0] -= d5[0] * d1[0]; + n0[1] -= d5[1] * d1[1]; + n0[2] -= d5[2] * d1[2]; + n0[3] -= d5[3] * d1[3]; + + n1[0] = d5[0] * d0[0]; + n1[1] = d5[1] * d0[1]; + n1[2] = d5[2] * d0[2]; + n1[3] = d5[3] * d0[3]; + + n1[0] -= d3[0] * d2[0]; + n1[1] -= d3[1] * d2[1]; + n1[2] -= d3[2] * d2[2]; + n1[3] -= d3[3] * d2[3]; + + n2[0] = d3[0] * d1[0]; + n2[1] = d3[1] * d1[1]; + n2[2] = d3[2] * d1[2]; + n2[3] = d3[3] * d1[3]; + + n2[0] -= d4[0] * d0[0]; + n2[1] -= d4[1] * d0[1]; + n2[2] -= d4[2] * d0[2]; + n2[3] -= d4[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + + for ( j = 0; j < 4; j++ ) { + const rvSilTraceVertT *a; + + a = verts + indexes[i + j * 3]; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + } + } + + for ( ; i < numIndexes; i += 3 ) { + const rvSilTraceVertT *a, *b, *c; + float d0, d1, d2, d3, d4, d5; + float n0, n1, n2; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + + d3 = c->xyz[0] - a->xyz[0]; + d4 = c->xyz[1] - a->xyz[1]; + d5 = c->xyz[2] - a->xyz[2]; + + float tmp; + + n0 = d4 * d2 - d5 * d1; + n1 = d5 * d0 - d3 * d2; + n2 = d3 * d1 - d4 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + } + +#endif +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *vertexInputData, const int numVerts ) { + + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + const rvSilTraceVertT *endVertexInputData; + + assert( numVerts > 0 ); + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + endVertexInputData = vertexInputData + numVerts; + + __asm + { + mov edx, curMin + mov ecx, curMax + mov eax, vertexInputData + mov esi, endVertexInputData + movaps xmm0, [edx] + movaps xmm7, [ecx] + + loop1: + movaps xmm1, [eax] + add eax, SILTRACEVERT_SIZE + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + cmp eax, esi + jne loop1 + + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *vertexInputData, const int *indices, const int count ) { + +#if 1 + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + const int *endIndices; + + if ( count <= 0 ) + { + return; + } + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( count % 3 == 0 ); + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + endIndices = indices + count; + + __asm + { + mov edx, curMin + mov ecx, curMax + mov esi, vertexInputData + mov edi, indices + movaps xmm0, [edx] + movaps xmm7, [ecx] + + loop1: + mov eax, [edi] + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + movaps xmm1, [eax] + + mov eax, [edi+4] + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps xmm1, [eax] + + mov eax, [edi+8] + add edi, 12 + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps xmm1, [eax] + mov eax, endIndices + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + cmp edi, eax + jl loop1 + + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +#else + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov edi, indexes + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + shl eax, 2 + add edi, eax + neg eax + + loop4: +// prefetchnta [edi+128] +// prefetchnta [esi+4*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET] + + mov edx, [edi+eax+0] + imul edx, SILTRACEVERT_SIZE + movss xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + mov edx, [edi+eax+4] + imul edx, SILTRACEVERT_SIZE + movss xmm5, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + movhps xmm5, [esi+edx+SILTRACEVERT_XYZW_OFFSET+4] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + mov edx, [edi+eax+8] + imul edx, SILTRACEVERT_SIZE + movss xmm6, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm6, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + mov edx, [edi+eax+12] + imul edx, SILTRACEVERT_SIZE + movss xmm7, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + movhps xmm7, [esi+edx+SILTRACEVERT_XYZW_OFFSET+4] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*4 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + shl eax, 2 + add edi, eax + neg eax + + loop1: + mov edx, [edi+eax+0] + imul edx, SILTRACEVERT_SIZE; + movss xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 4 + jl loop1 + + done1: + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 1, 0, 2 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 1, 0, 2 ) + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +#endif +} + +#endif // #ifdef _MD5R_SUPPORT +// RAVEN END + +#endif // _WINDOWS diff --git a/source/idlib/math/Simd_SSE.h b/source/idlib/math/Simd_SSE.h new file mode 100644 index 0000000..0d9c059 --- /dev/null +++ b/source/idlib/math/Simd_SSE.h @@ -0,0 +1,129 @@ + +#ifndef __MATH_SIMD_SSE_H__ +#define __MATH_SIMD_SSE_H__ + +/* +=============================================================================== + + SSE implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE : public idSIMD_MMX { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Div( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ); + + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ); + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ); + + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ); + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ); + + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ); + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ); + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ); + + virtual void VPCALL Zero16( float *dst, const int count ); + virtual void VPCALL Negate16( float *dst, const int count ); + virtual void VPCALL Copy16( float *dst, const float *src, const int count ); + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ); + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ); + + virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); + + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ); + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ); + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ); + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ); + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ); + + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundTwoSpeakerMonoSimple( float *mixBuffer, const float *samples, const int numSamples ); + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT + virtual void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int *indexes, const int count ); +#endif +// RAVEN END +#endif +}; + +#endif /* !__MATH_SIMD_SSE_H__ */ diff --git a/source/idlib/math/Simd_SSE2.cpp b/source/idlib/math/Simd_SSE2.cpp new file mode 100644 index 0000000..353992b --- /dev/null +++ b/source/idlib/math/Simd_SSE2.cpp @@ -0,0 +1,2211 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_InstructionMacros.h" + + +//=============================================================== +// +// SSE2 implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +#include "Simd_InstructionMacros.h" + +ALIGN8_INIT1( unsigned short SIMD_W_zero, 0 ); +ALIGN8_INIT1( unsigned short SIMD_W_maxShort, 1<<15 ); + +ALIGN4_INIT4( unsigned long SIMD_SP_firstSignBit, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_ZERO ); +ALIGN4_INIT1( unsigned long SIMD_SP_signBit, IEEE_SP_SIGN ); +ALIGN4_INIT1( unsigned long SIMD_SP_absMask, ~IEEE_SP_SIGN ); +ALIGN4_INIT1( unsigned long SIMD_SP_infinityMask, ~IEEE_SP_INF ); +ALIGN4_INIT1( unsigned long SIMD_SP_not, 0xFFFFFFFF ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearLast, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearFirstThree, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF ); + +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x1, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x2, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); + +ALIGN4_INIT4( unsigned long SIMD_DW_zero, 0, 0, 0, 0 ); +ALIGN16_INIT1( unsigned char SIMD_B_one, 1 ); + +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c0, 0, 0, 0, 1 ); +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c1, 1, 1, 0, 0 ); +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c2, 0, 1, 0, 0 ); + +ALIGN4_INIT1( float SIMD_SP_zero, 0.0f ); +ALIGN4_INIT1( float SIMD_SP_one, 1.0f ); +ALIGN4_INIT1( float SIMD_SP_two, 2.0f ); +ALIGN4_INIT1( float SIMD_SP_three, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_four, 4.0f ); +ALIGN4_INIT1( float SIMD_SP_maxShort, (1<<15) ); +ALIGN4_INIT1( float SIMD_SP_tiny, 1e-10f ); +ALIGN4_INIT1( float SIMD_SP_PI, idMath::PI ); +ALIGN4_INIT1( float SIMD_SP_halfPI, idMath::HALF_PI ); +ALIGN4_INIT1( float SIMD_SP_twoPI, idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_oneOverTwoPI, 1.0f / idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); + + +/* +============ +idSIMD_SSE2::GetName +============ +*/ +const char * idSIMD_SSE2::GetName( void ) const { + return "MMX & SSE & SSE2"; +} + +#if 0 // the SSE2 code is ungodly slow + +/* +============ +idSIMD_SSE2::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE2::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + int nc; + const float *lptr; + + if ( skip >= n ) { + return; + } + + lptr = L[skip]; + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + __asm { + push ebx + mov eax, skip // eax = i + shl eax, 2 // eax = i*4 + mov edx, n // edx = n + shl edx, 2 // edx = n*4 + mov esi, x // esi = x + mov edi, lptr // edi = lptr + add esi, eax + add edi, eax + mov ebx, b // ebx = b + // aligned + looprow: + mov ecx, eax + neg ecx + cvtps2pd xmm0, [esi+ecx] + cvtps2pd xmm2, [edi+ecx] + mulpd xmm0, xmm2 + cvtps2pd xmm1, [esi+ecx+8] + cvtps2pd xmm3, [edi+ecx+8] + mulpd xmm1, xmm3 + add ecx, 20*4 + jg donedot16 + dot16: + cvtps2pd xmm2, [esi+ecx-(16*4)] + cvtps2pd xmm3, [edi+ecx-(16*4)] + cvtps2pd xmm4, [esi+ecx-(14*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(14*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(12*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(12*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(10*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(10*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(8*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(8*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(6*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(6*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(4*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(4*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(2*4)] + addpd xmm0, xmm2 + add ecx, 16*4 + mulpd xmm4, xmm5 + addpd xmm1, xmm4 + jle dot16 + donedot16: + sub ecx, 8*4 + jg donedot8 + dot8: + cvtps2pd xmm2, [esi+ecx-(8*4)] + cvtps2pd xmm3, [edi+ecx-(8*4)] + cvtps2pd xmm7, [esi+ecx-(6*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(6*4)] + addpd xmm0, xmm2 + cvtps2pd xmm6, [esi+ecx-(4*4)] + mulpd xmm7, xmm5 + cvtps2pd xmm3, [edi+ecx-(4*4)] + addpd xmm1, xmm7 + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm6, xmm3 + cvtps2pd xmm7, [edi+ecx-(2*4)] + addpd xmm0, xmm6 + add ecx, 8*4 + mulpd xmm4, xmm7 + addpd xmm1, xmm4 + donedot8: + sub ecx, 4*4 + jg donedot4 + dot4: + cvtps2pd xmm2, [esi+ecx-(4*4)] + cvtps2pd xmm3, [edi+ecx-(4*4)] + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(2*4)] + addpd xmm0, xmm2 + add ecx, 4*4 + mulpd xmm4, xmm5 + addpd xmm1, xmm4 + donedot4: + addpd xmm0, xmm1 + movaps xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 0 ) + addsd xmm0, xmm1 + sub ecx, 4*4 + jz dot0 + add ecx, 4 + jz dot1 + add ecx, 4 + jz dot2 + //dot3: + cvtss2sd xmm1, [esi-(3*4)] + cvtss2sd xmm2, [edi-(3*4)] + mulsd xmm1, xmm2 + addsd xmm0, xmm1 + dot2: + cvtss2sd xmm3, [esi-(2*4)] + cvtss2sd xmm4, [edi-(2*4)] + mulsd xmm3, xmm4 + addsd xmm0, xmm3 + dot1: + cvtss2sd xmm5, [esi-(1*4)] + cvtss2sd xmm6, [edi-(1*4)] + mulsd xmm5, xmm6 + addsd xmm0, xmm5 + dot0: + cvtss2sd xmm1, [ebx+eax] + subsd xmm1, xmm0 + cvtsd2ss xmm0, xmm1 + movss [esi], xmm0 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp looprow + // done + done: + pop ebx + } +} + +/* +============ +idSIMD_SSE2::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE2::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + + int i, j, m; + float *xptr; + double s0; + + // if the number of columns is not a multiple of 2 we're screwed for alignment. + // however, if the number of columns is a multiple of 2 but the number of to be + // processed rows is not a multiple of 2 we can still run 8 byte aligned + m = n; + if ( m & 1 ) { + m--; + x[m] = b[m]; + + lptr = L[m] + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows_1: + cvtps2pd xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + cvtps2pd xmm2, [ebx+eax*4-8] // load b[i-4], b[i-3] + xor ecx, ecx + sub eax, m + neg eax + jz done4x4_1 + process4x4_1: // process 4x4 blocks + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+0] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+4] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + cvtps2pd xmm3, [edi] + mulpd xmm6, xmm7 + cvtps2pd xmm4, [edi+8] + subpd xmm0, xmm1 + subpd xmm2, xmm6 + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+8] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+12] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + add ecx, 4 + mulpd xmm6, xmm7 + cmp ecx, eax + subpd xmm0, xmm1 + subpd xmm2, xmm6 + jl process4x4_1 + done4x4_1: // process left over of the 4 rows + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + cvtss2sd xmm5, [esi+4*ecx] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + mulpd xmm4, xmm5 + subpd xmm0, xmm3 + subpd xmm2, xmm4 + imul ecx, edx + sub edi, ecx + neg eax + + add eax, m + sub eax, 4 + movapd xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 1 ) + movapd xmm3, xmm2 + shufpd xmm3, xmm3, R_SHUFFLE_PD( 1, 1 ) + sub edi, edx + cvtsd2ss xmm7, xmm3 + movss [esi-4], xmm7 // xptr[-1] = s3 + movsd xmm4, xmm3 + movsd xmm5, xmm3 + cvtss2sd xmm7, [edi+8] + mulsd xmm3, xmm7 // lptr[-1*nc+2] * s3 + cvtss2sd xmm7, [edi+4] + mulsd xmm4, xmm7 // lptr[-1*nc+1] * s3 + cvtss2sd xmm7, [edi] + mulsd xmm5, xmm7 // lptr[-1*nc+0] * s3 + subsd xmm2, xmm3 + cvtsd2ss xmm7, xmm2 + movss [esi-8], xmm7 // xptr[-2] = s2 + movsd xmm6, xmm2 + sub edi, edx + subsd xmm0, xmm5 + subsd xmm1, xmm4 + cvtss2sd xmm7, [edi+4] + mulsd xmm2, xmm7 // lptr[-2*nc+1] * s2 + cvtss2sd xmm7, [edi] + mulsd xmm6, xmm7 // lptr[-2*nc+0] * s2 + subsd xmm1, xmm2 + cvtsd2ss xmm7, xmm1 + movss [esi-12], xmm7 // xptr[-3] = s1 + subsd xmm0, xmm6 + sub edi, edx + cmp eax, 4 + cvtss2sd xmm7, [edi] + mulsd xmm1, xmm7 // lptr[-3*nc+0] * s1 + subsd xmm0, xmm1 + cvtsd2ss xmm7, xmm0 + movss [esi-16], xmm7 // xptr[-4] = s0 + jl done4rows_1 + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows_1 + done4rows_1: + pop ebx + } + } + else { + lptr = L.ToFloatPtr() + m * L.GetNumColumns() + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows: + cvtps2pd xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + cvtps2pd xmm2, [ebx+eax*4-8] // load b[i-4], b[i-3] + sub eax, m + jz done4x4 + neg eax + xor ecx, ecx + process4x4: // process 4x4 blocks + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+0] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+4] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + cvtps2pd xmm3, [edi] + mulpd xmm6, xmm7 + cvtps2pd xmm4, [edi+8] + subpd xmm0, xmm1 + subpd xmm2, xmm6 + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+8] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+12] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + add ecx, 4 + mulpd xmm6, xmm7 + cmp ecx, eax + subpd xmm0, xmm1 + subpd xmm2, xmm6 + jl process4x4 + imul ecx, edx + sub edi, ecx + neg eax + done4x4: // process left over of the 4 rows + add eax, m + sub eax, 4 + movapd xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 1 ) + movapd xmm3, xmm2 + shufpd xmm3, xmm3, R_SHUFFLE_PD( 1, 1 ) + sub edi, edx + cvtsd2ss xmm7, xmm3 + movss [esi-4], xmm7 // xptr[-1] = s3 + movsd xmm4, xmm3 + movsd xmm5, xmm3 + cvtss2sd xmm7, [edi+8] + mulsd xmm3, xmm7 // lptr[-1*nc+2] * s3 + cvtss2sd xmm7, [edi+4] + mulsd xmm4, xmm7 // lptr[-1*nc+1] * s3 + cvtss2sd xmm7, [edi] + mulsd xmm5, xmm7 // lptr[-1*nc+0] * s3 + subsd xmm2, xmm3 + cvtsd2ss xmm7, xmm2 + movss [esi-8], xmm7 // xptr[-2] = s2 + movsd xmm6, xmm2 + sub edi, edx + subsd xmm0, xmm5 + subsd xmm1, xmm4 + cvtss2sd xmm7, [edi+4] + mulsd xmm2, xmm7 // lptr[-2*nc+1] * s2 + cvtss2sd xmm7, [edi] + mulsd xmm6, xmm7 // lptr[-2*nc+0] * s2 + subsd xmm1, xmm2 + cvtsd2ss xmm7, xmm1 + movss [esi-12], xmm7 // xptr[-3] = s1 + subsd xmm0, xmm6 + sub edi, edx + cmp eax, 4 + cvtss2sd xmm7, [edi] + mulsd xmm1, xmm7 // lptr[-3*nc+0] * s1 + subsd xmm0, xmm1 + cvtsd2ss xmm7, xmm0 + movss [esi-16], xmm7 // xptr[-4] = s0 + jl done4rows + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows + done4rows: + pop ebx + } + } + + // process left over rows + for ( i = (m&3)-1; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[i+1] + i; + for ( j = i + 1; j < m; j++ ) { + s0 -= lptr[0] * x[j]; + lptr += nc; + } + x[i] = s0; + } +} + +#endif + +/* +============ +idSIMD_SSE2::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_SSE2::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + assert_16_byte_aligned( jointQuats ); + + __asm { + mov eax, numJoints + shl eax, JOINTQUAT_SIZE_SHIFT + mov esi, jointQuats + mov edi, jointMats + + add esi, eax + neg eax + jz done + + loopQuat: + movaps xmm0, [esi+eax+JOINTQUAT_Q_OFFSET] // xmm0 = q.x, q.y, q.z, q.w + movaps xmm6, [esi+eax+JOINTQUAT_T_OFFSET] // xmm6 = t.x, t.y, t.z, w + + add edi, JOINTMAT_SIZE + + movaps xmm1, xmm0 // xmm1 = x, y, z, w + addps xmm1, xmm1 // xmm1 = x2, y2, z2, w2 + + add eax, JOINTQUAT_SIZE + + // calculate the 9 products + pshufd xmm2, xmm0, R_SHUFFLE_D( 1, 0, 0, 1 ) // xmm2 = y, x, x, y + pshufd xmm3, xmm1, R_SHUFFLE_D( 1, 1, 2, 2 ) // xmm3 = y2, y2, z2, z2 + mulps xmm2, xmm3 // xmm2 = yy2, xy2, xz2, yz2 + + pshufd xmm4, xmm0, R_SHUFFLE_D( 2, 3, 3, 3 ) // xmm4 = z, w, w, w + pshufd xmm5, xmm1, R_SHUFFLE_D( 2, 2, 1, 0 ) // xmm5 = z2, z2, y2, x2 + mulps xmm4, xmm5 // xmm4 = zz2, wz2, wy2, wx2 + + mulss xmm0, xmm1 // xmm0 = xx2, y2, z2, w2 + + // calculate the last two elements of the third row + movss xmm7, SIMD_SP_one // xmm7 = 1, 0, 0, 0 + subss xmm7, xmm0 // xmm7 = -xx2+1, 0, 0, 0 + subss xmm7, xmm2 // xmm7 = -xx2-yy2+1, 0, 0, 0 + shufps xmm7, xmm6, R_SHUFFLE_PS( 0, 1, 2, 3 ) // xmm7 = -xx2-yy2+1, 0, t.z, w + + // calcluate first row + xorps xmm2, SIMD_SP_quat2mat_x0 // xmm2 = yy2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x1 // xmm4 = -zz2, wz2, -wy2, -wx2 + addss xmm4, SIMD_SP_one // xmm4 = -zz2+1, wz2, -wy2, -wx2 + movaps xmm3, xmm4 // xmm3 = -zz2+1, wz2, -wy2, -wx2 + subps xmm3, xmm2 // xmm3 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movaps [edi-JOINTMAT_SIZE+0*16+0*4], xmm3 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movss [edi-JOINTMAT_SIZE+0*16+3*4], xmm6 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, t.x + + // calculate second row + movss xmm2, xmm0 // xmm2 = xx2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x2 // xmm4 = -zz2+1, -wz2, wy2, wx2 + subps xmm4, xmm2 // xmm4 = -xx2-zz2+1, xy2-wz2, xz2+wy2, yz2+wx2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) // xmm6 = t.y, t.z, w, t.x + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 0, 3, 2 ) // xmm4 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movaps [edi-JOINTMAT_SIZE+1*16+0*4], xmm4 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movss [edi-JOINTMAT_SIZE+1*16+3*4], xmm6 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, t.y + + // calculate third row + movhlps xmm3, xmm4 // xmm3 = yz2+wx2, xz2+wy2, xz2-wy2, yz2-wx2 + shufps xmm3, xmm7, R_SHUFFLE_PS( 1, 3, 0, 2 ) // xmm3 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + movaps [edi-JOINTMAT_SIZE+2*16+0*4], xmm3 // row2 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + + jl loopQuat + + done: + } + +#else + + for ( int i = 0; i < numJoints; i++ ) { + const float *q = &jointQuats[i].q; + float *m = jointMats[i].mat; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + float w2 = q[3] + q[3]; + + float yy2 = q[1] * y2; + float xy2 = q[0] * y2; + float xz2 = q[0] * z2; + float yz2 = q[1] * z2; + + float zz2 = q[2] * z2; + float wz2 = q[3] * z2; + float wy2 = q[3] * y2; + float wx2 = q[3] * x2; + + float xx2 = q[0] * x2; + + m[0*4+0] = - yy2 - zz2 + 1.0f; + m[0*4+1] = xy2 + wz2; + m[0*4+2] = xz2 - wy2; + m[0*4+3] = q[4]; + + m[1*4+0] = xy2 - wz2; + m[1*4+1] = - xx2 - zz2 + 1.0f; + m[1*4+2] = yz2 + wx2; + m[1*4+3] = q[5]; + + m[2*4+0] = xz2 + wy2; + m[2*4+1] = yz2 - wx2; + m[2*4+2] = - xx2 - yy2 + 1.0f; + m[2*4+3] = q[6]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::MultiplyJoints +============ +*/ +/* +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor1, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor2, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_SIGN ); + +void VPCALL idSIMD_SSE2::MultiplyJoints( idJointQuat *result, const idJointQuat *joints1, const idJointQuat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopQuat: + movaps xmm0, [ecx+eax] // Ax, Ay, Az, Aw 6/1 + movaps xmm1, [edx+eax] // Bx, By, Bz, Bw 6/1 + pshufd xmm2, xmm0, 0x00 // Ax, Ax, Ax, Ax 4/2 + pshufd xmm3, xmm1, 0x1B // BW, BZ, BY, BX 4/2 + pshufd xmm4, xmm0, 0x55 // Ay, Ay, Ay, Ay 4/2 + pshufd xmm5, xmm1, 0x4E // Bz, Bw, Bx, By 4/2 + mulps xmm2, xmm3 // Ax*Bw, Ax*Bz, Ax*By, Ax*Bx 6/2 + pshufd xmm6, xmm0, 0xAA // Az, Az, Az, Az 4/2 + mulps xmm4, xmm5 // Ay*Bz, Ay*Bw, Ay*Bx, Ay*By 6/2 + pshufd xmm7, xmm1, 0xB1 // By, Bx, Bw, Bz 4/2 + xorps xmm2, SIMD_SP_quatmul_xor0 // Ax*Bw, -Ax*Bz, Ax*By, -Ax*Bx 4/2 + pshufd xmm0, xmm0, 0xFF // Aw, Aw, Aw, Aw 4/2 + mulps xmm6, xmm7 // Az*By, Az*Bx, Az*Bw, Az*Bz 6/2 + xorps xmm4, SIMD_SP_quatmul_xor1 // Ay*Bz, Ay*Bw, -Ay*Bx, -Ay*By 4/2 + mulps xmm0, xmm1 // Aw*Bx, Aw*By, Aw*Bz, Aw*Bw 6/2 + addps xmm2, xmm4 // Ax*Bw+Ay*Bz, -Ax*Bz+Ay*Bw, Ax*By-Ay*Bx, -Ax*Bx-Ay*By 4/2 + xorps xmm6, SIMD_SP_quatmul_xor2 // -Az*By, Az*Bx, Az*Bw, -Az*Bz 4/2 + addps xmm0, xmm2 // add to the result 4/2 + addps xmm0, xmm6 // add to the result 4/2 + movaps [edi+eax], xmm0 // save result 6/1 + add eax, JOINTQUAT_SIZE + jl loopQuat + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + result[i].q = joints1[i].q * joints2[i].q; + } + +#endif +} +*/ + +/* +============ +idSIMD_SSE2::TransformJoints +============ +*/ +void VPCALL idSIMD_SSE2::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov ecx, firstJoint + mov eax, lastJoint + sub eax, ecx + jl done + shl ecx, 2 // ecx = firstJoint * 4 + mov edi, parents + add edi, ecx // edx = &parents[firstJoint] + lea ecx, [ecx+ecx*2] + shl ecx, 2 // ecx = firstJoint * JOINTMAT_SIZE + mov esi, jointMats // esi = jointMats + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + add edi, eax + neg eax + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movaps xmm7, [esi+edx+ 0] + pshufd xmm4, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + pshufd xmm5, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + + add ecx, JOINTMAT_SIZE + add eax, 4 + + pshufd xmm6, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm4, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+ 0], xmm4 + + movaps xmm3, [esi+edx+16] + pshufd xmm5, xmm3, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + pshufd xmm6, xmm3, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + pshufd xmm4, xmm3, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm4, xmm2 + addps xmm5, xmm4 + andps xmm3, SIMD_SP_clearFirstThree + addps xmm5, xmm3 + + movaps [esi+ecx-JOINTMAT_SIZE+16], xmm5 + + movaps xmm7, [esi+edx+32] + pshufd xmm6, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm6, xmm4 + pshufd xmm3, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm6, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+32], xmm6 + + jle loopJoint + done: + } + +#else + + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::UntransformJoints +============ +*/ +void VPCALL idSIMD_SSE2::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov edx, firstJoint + mov eax, lastJoint + mov ecx, eax + sub eax, edx + jl done + mov esi, jointMats // esi = jointMats + lea ecx, [ecx+ecx*2] + shl ecx, 4 // ecx = lastJoint * JOINTMAT_SIZE + shl edx, 2 + mov edi, parents + add edi, edx // edi = &parents[firstJoint] + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movss xmm7, [esi+edx+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm0, xmm7 + movss xmm6, [esi+edx+28] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm1, xmm6 + movss xmm5, [esi+edx+44] + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm2, xmm5 + + sub ecx, JOINTMAT_SIZE + sub eax, 4 + + movaps xmm7, [esi+edx+ 0] + + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm0 + + movaps xmm7, [esi+edx+16] + + pshufd xmm0, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm0, xmm1 + addps xmm3, xmm0 + pshufd xmm6, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm4, xmm6 + pshufd xmm0, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm0, xmm1 + addps xmm5, xmm0 + + movaps xmm7, [esi+edx+32] + + pshufd xmm6, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm6, xmm2 + addps xmm3, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+ 0], xmm3 + + pshufd xmm1, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm1, xmm2 + addps xmm4, xmm1 + + movaps [esi+ecx+JOINTMAT_SIZE+16], xmm4 + + pshufd xmm6, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm5, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+32], xmm5 + + jge loopJoint + done: + } + +#else + + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::MultiplyJoints +============ +*/ +void VPCALL idSIMD_SSE2::MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopJoint: + + movaps xmm0, [edx+eax+ 0] + movaps xmm1, [edx+eax+16] + movaps xmm2, [edx+eax+32] + + movaps xmm7, [ecx+eax+ 0] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + + add eax, JOINTMAT_SIZE + + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+0], xmm3 + + movaps xmm7, [ecx+eax-JOINTMAT_SIZE+16] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+16], xmm3 + + movaps xmm7, [ecx+eax-JOINTMAT_SIZE+32] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+32], xmm3 + + jl loopJoint + done: + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } + +#endif +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE2::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm5, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm5, xmm6 // xmm5 = m6+m8+m7+t2 + movss xmm6, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm5 + + movaps xmm7, xmm6 + minps xmm7, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm7 + movaps tmpMax, xmm6 + + jl loopVert + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + movss xmm0, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm1, xmm0 + movaps xmm2, xmm0 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm3, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CountFacing +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CountFacing( const byte *facing, const int numFaces ) { +#if 1 + ALIGN16( int n[4]; ) + + __asm { + + mov eax, numFaces + mov edi, facing + test eax, eax + jz done + + pxor xmm6, xmm6 + pxor xmm7, xmm7 + + sub eax, 256 + jl run16 + + loop256: + movdqa xmm0, [edi+ 0*16] + movdqa xmm1, [edi+ 1*16] + movdqa xmm2, [edi+ 2*16] + movdqa xmm3, [edi+ 3*16] + paddusb xmm0, [edi+ 4*16] + paddusb xmm1, [edi+ 5*16] + paddusb xmm2, [edi+ 6*16] + paddusb xmm3, [edi+ 7*16] + paddusb xmm0, [edi+ 8*16] + paddusb xmm1, [edi+ 9*16] + paddusb xmm2, [edi+10*16] + paddusb xmm3, [edi+11*16] + paddusb xmm0, [edi+12*16] + paddusb xmm1, [edi+13*16] + paddusb xmm2, [edi+14*16] + paddusb xmm3, [edi+15*16] + paddusb xmm0, xmm1 + paddusb xmm2, xmm3 + paddusb xmm0, xmm2 + + add edi, 256 + sub eax, 256 + + movdqa xmm1, xmm0 + punpcklbw xmm0, xmm7 + punpckhbw xmm1, xmm7 + paddusw xmm0, xmm1 + movdqa xmm1, xmm0 + punpcklwd xmm0, xmm7 + punpckhwd xmm1, xmm7 + paddd xmm0, xmm1 + paddd xmm6, xmm0 + + jge loop256 + + run16: + pxor xmm0, xmm0 + add eax, 256 - 16 + jl run4 + + loop16: + paddusb xmm0, [edi] + add edi, 16 + sub eax, 16 + jge loop16 + + run4: + add eax, 16 - 4 + jl run1 + + pxor xmm1, xmm1 + + loop4: + movd xmm1, [edi] + paddusb xmm0, xmm1 + add edi, 4 + sub eax, 4 + jge loop4 + + run1: + movdqa xmm1, xmm0 + punpcklbw xmm0, xmm7 + punpckhbw xmm1, xmm7 + paddusw xmm0, xmm1 + movdqa xmm1, xmm0 + punpcklwd xmm0, xmm7 + punpckhwd xmm1, xmm7 + paddd xmm0, xmm1 + paddd xmm6, xmm0 + + movdqa n, xmm6 + add eax, 4-1 + jl done + + mov edx, dword ptr n + + loop1: + movzx ecx, [edi] + add edx, ecx + add edi, 1 + sub eax, 1 + jge loop1 + + mov dword ptr n, edx + + done: + } + + return n[0] + n[1] + n[2] + n[3]; + +#else + + int i, n0, n1, n2, n3; + + n0 = n1 = n2 = n3 = 0; + for ( i = 0; i < numFaces - 3; i += 4 ) { + n0 += facing[i+0]; + n1 += facing[i+1]; + n2 += facing[i+2]; + n3 += facing[i+3]; + } + for ( ; i < numFaces; i++ ) { + n0 += facing[i]; + } + return n0 + n1 + n2 + n3; + +#endif +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacingCull +============ +*/ +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +int VPCALL idSIMD_SSE2::ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ) { +#if 1 + + ALIGN16( int n[4]; ) + + __asm { + push ebx + mov eax, numFaces + mov esi, indexes + mov edi, cull + mov ebx, facing + test eax, eax + jz done + add ebx, eax + neg eax + + pxor xmm5, xmm5 + pxor xmm6, xmm6 + movdqa xmm7, SIMD_B_one + + add eax, 4 + jg run1 + + loop4: + + mov ecx, dword ptr [esi+0*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+1*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+2*4] + and dl, byte ptr [edi+ecx] + + mov ecx, dword ptr [esi+3*4] + mov dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+4*4] + and dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+5*4] + and dh, byte ptr [edi+ecx] + movd xmm0, edx + + mov ecx, dword ptr [esi+6*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+7*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+8*4] + and dl, byte ptr [edi+ecx] + + mov ecx, dword ptr [esi+9*4] + mov dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+10*4] + and dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+11*4] + and dh, byte ptr [edi+ecx] + pinsrw xmm0, edx, 1 + + add esi, 12*4 + + movd xmm1, [ebx+eax-4] + pcmpgtb xmm0, xmm6 + pand xmm0, xmm7 + por xmm1, xmm0 + movd [ebx+eax-4], xmm1 + + add eax, 4 + + punpcklbw xmm1, xmm6 + punpcklwd xmm1, xmm6 + paddd xmm5, xmm1 + + jle loop4 + + run1: + sub eax, 4 + jge done + + loop1: + mov ecx, dword ptr [esi+0*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+1*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+2*4] + and dl, byte ptr [edi+ecx] + + neg edx + shr edx, 31 + movzx ecx, byte ptr [ebx+eax] + or ecx, edx + mov byte ptr [ebx+eax], cl + movd xmm0, ecx + paddd xmm5, xmm0 + + add esi, 3*4 + add eax, 1 + jl loop1 + + done: + pop ebx + movdqa dword ptr n, xmm5 + } + + return n[0] + n[1] + n[2] + n[3]; + +#else + + int i, n; + + n = 0; + for ( i = 0; i < numFaces - 3; i += 4 ) { + int c0 = cull[indexes[0]] & cull[indexes[ 1]] & cull[indexes[ 2]]; + int c1 = cull[indexes[3]] & cull[indexes[ 4]] & cull[indexes[ 5]]; + int c2 = cull[indexes[6]] & cull[indexes[ 7]] & cull[indexes[ 8]]; + int c3 = cull[indexes[9]] & cull[indexes[10]] & cull[indexes[11]]; + facing[i+0] |= ( (-c0) >> 31 ) & 1; + facing[i+1] |= ( (-c1) >> 31 ) & 1; + facing[i+2] |= ( (-c2) >> 31 ) & 1; + facing[i+3] |= ( (-c3) >> 31 ) & 1; + n += facing[i+0]; + n += facing[i+1]; + n += facing[i+2]; + n += facing[i+3]; + indexes += 12; + } + for ( ; i < numFaces; i++ ) { + int c = cull[indexes[0]] & cull[indexes[1]] & cull[indexes[2]]; + c = ( (-c) >> 31 ) & 1; + facing[i] |= c; + n += facing[i]; + indexes += 3; + } + return n; + +#endif +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CreateSilTriangles +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ) { +#if 1 + + int num; + + __asm { + push ebx + mov eax, numSilEdges + mov ebx, shadowIndexes + mov esi, facing + mov edi, silEdges + shl eax, 4 + jz done + add edi, eax + neg eax + shr ebx, 3 + + add eax, 4*16 + jg run1 + + loop4: + mov ecx, dword ptr [edi+eax-4*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax-4*16+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax-4*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, edx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea edx, [ecx*2+ecx] + pxor xmm0, xmm1 + add edx, ebx + movlps qword ptr [ebx*8+0*4], xmm0 + pxor xmm2, xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + + mov ecx, dword ptr [edi+eax-3*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm3, ecx + mov ebx, dword ptr [edi+eax-3*16+4] + movzx ebx, byte ptr [esi+ebx] + pinsrw xmm3, ebx, 2 + movq xmm0, qword ptr [edi+eax-3*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, ebx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea ebx, [ecx*2+ecx] + pxor xmm0, xmm1 + add ebx, edx + movlps qword ptr [edx*8+0*4], xmm0 + pxor xmm3, xmm0 + movhps qword ptr [edx*8+2*4], xmm0 + movlps qword ptr [edx*8+4*4], xmm3 + + mov ecx, dword ptr [edi+eax-2*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax-2*16+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax-2*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, edx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea edx, [ecx*2+ecx] + pxor xmm0, xmm1 + add edx, ebx + movlps qword ptr [ebx*8+0*4], xmm0 + pxor xmm2, xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + + mov ecx, dword ptr [edi+eax-1*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm3, ecx + mov ebx, dword ptr [edi+eax-1*16+4] + movzx ebx, byte ptr [esi+ebx] + pinsrw xmm3, ebx, 2 + movq xmm0, qword ptr [edi+eax-1*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, ebx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea ebx, [ecx*2+ecx] + pxor xmm0, xmm1 + add ebx, edx + movlps qword ptr [edx*8+0*4], xmm0 + pxor xmm3, xmm0 + movhps qword ptr [edx*8+2*4], xmm0 + add eax, 4*16 + movlps qword ptr [edx*8+4*4], xmm3 + + jle loop4 + + run1: + sub eax, 4*16 + jge done + + loop1: + mov ecx, dword ptr [edi+eax+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + pxor xmm0, xmm1 + movlps qword ptr [ebx*8+0*4], xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + pxor xmm2, xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + xor ecx, edx + lea edx, [ecx*2+ecx] + add ebx, edx + + add eax, 16 + jl loop1 + + done: + shl ebx, 3 + mov num, ebx + pop ebx + } + + return ( num - (int)shadowIndexes ) >> 2; + +#else + + int i; + const silEdge_t *sil; + int *si; + + si = shadowIndexes; + for ( sil = silEdges, i = numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = sil->v1 << 1; + int v2 = sil->v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + si[0] = v1; + si[1] = v2 ^ f1; + si[2] = v2 ^ f2; + si[3] = v1 ^ f2; + si[4] = v1 ^ f1; + si[5] = v2 ^ 1; + + si += 6; + } + return si - shadowIndexes; + +#endif +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CreateCapTriangles +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ) { +#if 1 + + int num = numIndexes / 3; + + __asm { + push ebx + mov eax, numIndexes + mov ebx, shadowIndexes + mov esi, facing + mov edi, indexes + shl eax, 2 + jz done + add edi, eax + mov eax, num + add esi, eax + neg eax + shr ebx, 3 + + movaps xmm6, SIMD_DW_capTris_c0 + movaps xmm7, SIMD_DW_capTris_c1 + movaps xmm5, SIMD_DW_capTris_c2 + + add eax, 4 + lea edx, [eax*2+eax] + jg run1 + + loop4: + movdqa xmm0, [edi+edx*4-4*3*4+0] // xmm0 = 0, 1, 2, 3 + paddd xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 1, 0, 0 ) // xmm1 = 2, 1, 0, 0 + movzx ecx, byte ptr [esi+eax-4] + pshufd xmm2, xmm0, R_SHUFFLE_PS( 1, 2, 1, 2 ) // xmm2 = 1, 2, 1, 2 + sub ecx, 1 + pxor xmm1, xmm6 + and ecx, 3 + movlps qword ptr [ebx*8+0*4], xmm1 + add ecx, ebx + movhps qword ptr [ebx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ebx*8+4*4], xmm2 + + movdqa xmm3, [edi+edx*4-3*3*4+4] // xmm3 = 4, 5, 6, 7 + paddd xmm3, xmm3 + shufps xmm0, xmm3, R_SHUFFLE_PS( 3, 3, 1, 0 ) // xmm0 = 3 3, 5, 4 + movzx ebx, byte ptr [esi+eax-3] + movdqa xmm2, xmm3 // xmm2 = 4, 5, 6, 7 + sub ebx, 1 + pxor xmm0, xmm5 + and ebx, 3 + movhps qword ptr [ecx*8+0*4], xmm0 + add ebx, ecx + movlps qword ptr [ecx*8+2*4], xmm0 + pxor xmm2, xmm7 + movlps qword ptr [ecx*8+4*4], xmm2 + + movdqa xmm0, [edi+edx*4-1*3*4-4] // xmm0 = 8, 9, 10, 11 + paddd xmm0, xmm0 + shufps xmm3, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) // xmm3 = 6, 7, 8, 9 + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 1, 0, 0 ) // xmm1 = 8, 7, 6, 6 + movzx ecx, byte ptr [esi+eax-2] + pshufd xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 1, 2 ) // xmm2 = 7, 8, 7, 8 + sub ecx, 1 + pxor xmm1, xmm6 + and ecx, 3 + movlps qword ptr [ebx*8+0*4], xmm1 + add ecx, ebx + movhps qword ptr [ebx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ebx*8+4*4], xmm2 + + pshufd xmm1, xmm0, R_SHUFFLE_PS( 3, 2, 1, 1 ) + movzx ebx, byte ptr [esi+eax-1] + pshufd xmm2, xmm0, R_SHUFFLE_PS( 2, 3, 2, 3 ) + sub ebx, 1 + pxor xmm1, xmm6 + and ebx, 3 + movlps qword ptr [ecx*8+0*4], xmm1 + add ebx, ecx + movhps qword ptr [ecx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ecx*8+4*4], xmm2 + + add edx, 3*4 + add eax, 4 + jle loop4 + + run1: + sub eax, 4 + jge done + + loop1: + lea edx, [eax*2+eax] + movdqu xmm0, [edi+edx*4+0] + paddd xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 1, 0, 0 ) + pshufd xmm2, xmm0, R_SHUFFLE_PS( 1, 2, 1, 2 ) + pxor xmm1, xmm6 + movlps qword ptr [ebx*8+0*4], xmm1 + pxor xmm2, xmm7 + movhps qword ptr [ebx*8+2*4], xmm1 + movzx ecx, byte ptr [esi+eax] + movlps qword ptr [ebx*8+4*4], xmm2 + sub ecx, 1 + and ecx, 3 + add ebx, ecx + + add eax, 1 + jl loop1 + + done: + shl ebx, 3 + mov num, ebx + pop ebx + } + + return ( num - (int)shadowIndexes ) >> 2; + +#else + + int i, j; + int *si; + + si = shadowIndexes; + for ( i = 0, j = 0; i < numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = indexes[i+0]; + int i1 = indexes[i+1]; + int i2 = indexes[i+2]; + + i0 += i0; + i1 += i1; + i2 += i2; + + si[0] = i2; + si[1] = i1; + si[2] = i0; + si[3] = i0 ^ 1; + si[4] = i1 ^ 1; + si[5] = i2 ^ 1; + + si += 6; + } + return si - shadowIndexes; + +#endif +} + +/* +============ +idSIMD_SSE2::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_SSE2::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { + + assert( ( numSamples % MIXBUFFER_SAMPLES ) == 0 ); + + __asm { + + mov eax, numSamples + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add edi, eax + neg eax + + loop16: + + movaps xmm0, [edi+eax+0*16] + movaps xmm1, [edi+eax+1*16] + movaps xmm2, [edi+eax+2*16] + movaps xmm3, [edi+eax+3*16] + + add esi, 4*4*2 + + cvtps2dq xmm4, xmm0 + cvtps2dq xmm5, xmm1 + cvtps2dq xmm6, xmm2 + cvtps2dq xmm7, xmm3 + + prefetchnta [edi+eax+128] + + packssdw xmm4, xmm5 + packssdw xmm6, xmm7 + + add eax, 4*16 + + movlps [esi-4*4*2], xmm4 // FIXME: should not use movlps/movhps to move integer data + movhps [esi-3*4*2], xmm4 + movlps [esi-2*4*2], xmm6 + movhps [esi-1*4*2], xmm6 + + jl loop16 + } +} + +#endif /* _WINDOWS */ diff --git a/source/idlib/math/Simd_SSE2.h b/source/idlib/math/Simd_SSE2.h new file mode 100644 index 0000000..60ba37e --- /dev/null +++ b/source/idlib/math/Simd_SSE2.h @@ -0,0 +1,40 @@ + +#ifndef __MATH_SIMD_SSE2_H__ +#define __MATH_SIMD_SSE2_H__ + +/* +=============================================================================== + + SSE2 implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE2 : public idSIMD_SSE { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + //virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + //virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); + + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ); + + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + + virtual int VPCALL ShadowVolume_CountFacing( const byte *facing, const int numFaces ); + virtual int VPCALL ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ); + virtual int VPCALL ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ); + virtual int VPCALL ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ); + + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); + +#endif +}; + +#endif /* !__MATH_SIMD_SSE2_H__ */ diff --git a/source/idlib/math/Simd_SSE3.cpp b/source/idlib/math/Simd_SSE3.cpp new file mode 100644 index 0000000..9599066 --- /dev/null +++ b/source/idlib/math/Simd_SSE3.cpp @@ -0,0 +1,437 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_SSE3.h" +#include "Simd_InstructionMacros.h" +#include "../geometry/JointTransform.h" + +//=============================================================== +// +// SSE3 implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +#include "Simd_InstructionMacros.h" + +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); + +/* +============ +SSE3_Dot +============ +*/ +float SSE3_Dot( const idVec4 &v1, const idVec4 &v2 ) { + float d; + __asm { + mov esi, v1 + mov edi, v2 + movaps xmm0, [esi] + mulps xmm0, [edi] + _haddps(_xmm0, _xmm0 ) + _haddps(_xmm0, _xmm0 ) + movss d, xmm0 + } + return d; +} + +/* +============ +idSIMD_SSE3::GetName +============ +*/ +const char * idSIMD_SSE3::GetName( void ) const { + return "MMX & SSE & SSE2 & SSE3"; +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE3::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm2 + + pshufd xmm3, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm3, xmm2 + movss xmm2, xmm3 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm3 + + minps xmm6, xmm2 + maxps xmm7, xmm2 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE3::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + movss xmm2, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm5, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + movss xmm5, xmm4 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm4 + + minps xmm6, xmm5 + maxps xmm7, xmm5 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm4 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm4 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm2 + + pshufd xmm4, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm4 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE3::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + movss xmm5, xmm4 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm4 + + minps xmm6, xmm5 + maxps xmm7, xmm5 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm4 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm4 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm2 + + pshufd xmm4, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm4 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +#endif /* _WINDOWS */ diff --git a/source/idlib/math/Simd_SSE3.h b/source/idlib/math/Simd_SSE3.h new file mode 100644 index 0000000..0b6ee88 --- /dev/null +++ b/source/idlib/math/Simd_SSE3.h @@ -0,0 +1,25 @@ + +#ifndef __MATH_SIMD_SSE3_H__ +#define __MATH_SIMD_SSE3_H__ + +/* +=============================================================================== + + SSE3 implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE3 : public idSIMD_SSE2 { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + +#endif +}; + +#endif /* !__MATH_SIMD_SSE3_H__ */ diff --git a/source/idlib/math/Simd_generic.cpp b/source/idlib/math/Simd_generic.cpp new file mode 100644 index 0000000..35375b7 --- /dev/null +++ b/source/idlib/math/Simd_generic.cpp @@ -0,0 +1,3775 @@ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" + + +//=============================================================== +// +// Generic implementation of idSIMDProcessor +// +//=============================================================== + +#define UNROLL1(Y) { int _IX; for (_IX=0;_IX constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpGT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGT"); +#define OPER(X) dst[(X)] = src0[(X)] > constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpGT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGT bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] > constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpGE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGE float-float"); +#define OPER(X) dst[(X)] = src0[(X)] >= constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpGE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGE bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] >= constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpLT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLT"); +#define OPER(X) dst[(X)] = src0[(X)] < constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpLT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLT bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] < constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpLE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLE"); +#define OPER(X) dst[(X)] = src0[(X)] <= constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpLE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLE bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] <= constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( float &min, float &max, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax float"); + min = idMath::INFINITY; max = -idMath::INFINITY; +#define OPER(X) if ( src[(X)] < min ) {min = src[(X)];} if ( src[(X)] > max ) {max = src[(X)];} + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec2 &min, idVec2 &max, const idVec2 * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idVec2"); + min[0] = min[1] = idMath::INFINITY; max[0] = max[1] = -idMath::INFINITY; +#define OPER(X) const idVec2 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idVec3 * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idVec3"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idDrawVert"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int *indexes, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idDrawVert indexed"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[indexes[(X)]].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Clamp +============ +*/ +void VPCALL idSIMD_Generic::Clamp( float * RESTRICT dst, const float * RESTRICT src, const float min, const float max, const int count ) { + TIME_THIS_SCOPE("SIMD Clamp"); +#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)] > max ? max : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::ClampMin +============ +*/ +void VPCALL idSIMD_Generic::ClampMin( float * RESTRICT dst, const float * RESTRICT src, const float min, const int count ) { + TIME_THIS_SCOPE("SIMD ClampMin"); +#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::ClampMax +============ +*/ +void VPCALL idSIMD_Generic::ClampMax( float * RESTRICT dst, const float * RESTRICT src, const float max, const int count ) { + TIME_THIS_SCOPE("SIMD ClampMax"); +#define OPER(X) dst[(X)] = src[(X)] > max ? max : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +================ +idSIMD_Generic::Memcpy +================ +*/ +void VPCALL idSIMD_Generic::Memcpy( void * RESTRICT dst, const void * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Memcpy"); + memcpy( dst, src, count ); +} + +/* +================ +idSIMD_Generic::Memset +================ +*/ +void VPCALL idSIMD_Generic::Memset( void * RESTRICT dst, const int val, const int count ) { + TIME_THIS_SCOPE("SIMD Memset"); + memset( dst, val, count ); +} + +/* +============ +idSIMD_Generic::Zero16 +============ +*/ +void VPCALL idSIMD_Generic::Zero16( float * RESTRICT dst, const int count ) { + TIME_THIS_SCOPE("SIMD Zero16"); + memset( dst, 0, count * sizeof( float ) ); +} + +/* +============ +idSIMD_Generic::Negate16 +============ +*/ +void VPCALL idSIMD_Generic::Negate16( float * RESTRICT dst, const int count ) { + TIME_THIS_SCOPE("SIMD Negate16"); + unsigned int * RESTRICT ptr = reinterpret_cast(dst); +#define OPER(X) ptr[(X)] ^= ( 1 << 31 ) // IEEE 32 bits float sign bit + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Copy16 +============ +*/ +void VPCALL idSIMD_Generic::Copy16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Copy16"); +#define OPER(X) dst[(X)] = src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Add16 +============ +*/ +void VPCALL idSIMD_Generic::Add16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ) { + TIME_THIS_SCOPE("SIMD Add16"); +#define OPER(X) dst[(X)] = src1[(X)] + src2[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Sub16 +============ +*/ +void VPCALL idSIMD_Generic::Sub16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ) { + TIME_THIS_SCOPE("SIMD Sub16"); +#define OPER(X) dst[(X)] = src1[(X)] - src2[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Mul16 +============ +*/ +void VPCALL idSIMD_Generic::Mul16( float * RESTRICT dst, const float * RESTRICT src1, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD Mul16"); +#define OPER(X) dst[(X)] = src1[(X)] * constant + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::AddAssign16 +============ +*/ +void VPCALL idSIMD_Generic::AddAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD AddAssign16"); +#define OPER(X) dst[(X)] += src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::SubAssign16 +============ +*/ +void VPCALL idSIMD_Generic::SubAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD SubAssign16"); +#define OPER(X) dst[(X)] -= src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MulAssign16 +============ +*/ +void VPCALL idSIMD_Generic::MulAssign16( float * RESTRICT dst, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD MulAssign16"); +#define OPER(X) dst[(X)] *= constant + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MatX_MultiplyVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] = sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplyAddVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyAddVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] += sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplySubVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplySubVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] -= sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyVecX"); + int i, j, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] = sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyAddVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyAddVecX"); + int i, j, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] += sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplySubVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplySubVecX"); + int i, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] -= sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplyMatX + + optimizes the following matrix multiplications: + + NxN * Nx6 + 6xN * Nx6 + Nx6 * 6xN + 6x6 * 6xN + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyMatX"); + int i, j, k, l, n; + float * RESTRICT dstPtr; + const float * RESTRICT m1Ptr, * RESTRICT m2Ptr; + double sum; + + assert( m1.GetNumColumns() == m2.GetNumRows() ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + k = m1.GetNumRows(); + l = m2.GetNumColumns(); + + switch( m1.GetNumColumns() ) { + case 1: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx1 * 1x6 + *dstPtr++ = m1Ptr[i] * m2Ptr[0]; + *dstPtr++ = m1Ptr[i] * m2Ptr[1]; + *dstPtr++ = m1Ptr[i] * m2Ptr[2]; + *dstPtr++ = m1Ptr[i] * m2Ptr[3]; + *dstPtr++ = m1Ptr[i] * m2Ptr[4]; + *dstPtr++ = m1Ptr[i] * m2Ptr[5]; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + } + case 2: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx2 * 2x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11]; + m1Ptr += 2; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr += 2; + } + break; + } + case 3: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx3 * 3x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17]; + m1Ptr += 3; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr += 3; + } + break; + } + case 4: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx4 * 4x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12] + m1Ptr[3] * m2Ptr[18]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13] + m1Ptr[3] * m2Ptr[19]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14] + m1Ptr[3] * m2Ptr[20]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15] + m1Ptr[3] * m2Ptr[21]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16] + m1Ptr[3] * m2Ptr[22]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17] + m1Ptr[3] * m2Ptr[23]; + m1Ptr += 4; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr += 4; + } + break; + } + case 5: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx5 * 5x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12] + m1Ptr[3] * m2Ptr[18] + m1Ptr[4] * m2Ptr[24]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13] + m1Ptr[3] * m2Ptr[19] + m1Ptr[4] * m2Ptr[25]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14] + m1Ptr[3] * m2Ptr[20] + m1Ptr[4] * m2Ptr[26]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15] + m1Ptr[3] * m2Ptr[21] + m1Ptr[4] * m2Ptr[27]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16] + m1Ptr[3] * m2Ptr[22] + m1Ptr[4] * m2Ptr[28]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17] + m1Ptr[3] * m2Ptr[23] + m1Ptr[4] * m2Ptr[29]; + m1Ptr += 5; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr += 5; + } + break; + } + case 6: { + switch( k ) { + case 1: { + if ( l == 1 ) { // 1x6 * 6x1 + dstPtr[0] = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[1] + m1Ptr[2] * m2Ptr[2] + + m1Ptr[3] * m2Ptr[3] + m1Ptr[4] * m2Ptr[4] + m1Ptr[5] * m2Ptr[5]; + return; + } + break; + } + case 2: { + if ( l == 2 ) { // 2x6 * 6x2 + for ( i = 0; i < 2; i++ ) { + for ( j = 0; j < 2; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 2 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 2 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 2 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 2 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 2 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 2 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + break; + } + case 3: { + if ( l == 3 ) { // 3x6 * 6x3 + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 3 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 3 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 3 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + break; + } + case 4: { + if ( l == 4 ) { // 4x6 * 6x4 + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 4 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 4 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + case 5: { + if ( l == 5 ) { // 5x6 * 6x5 + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 5 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + case 6: { + switch( l ) { + case 1: { // 6x6 * 6x1 + for ( i = 0; i < 6; i++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 1 ] + + m1Ptr[1] * m2Ptr[ 1 * 1 ] + + m1Ptr[2] * m2Ptr[ 2 * 1 ] + + m1Ptr[3] * m2Ptr[ 3 * 1 ] + + m1Ptr[4] * m2Ptr[ 4 * 1 ] + + m1Ptr[5] * m2Ptr[ 5 * 1 ]; + dstPtr++; + m1Ptr += 6; + } + return; + } + case 2: { // 6x6 * 6x2 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 2; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 2 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 2 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 2 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 2 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 2 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 2 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 3: { // 6x6 * 6x3 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 3 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 3 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 3 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 4: { // 6x6 * 6x4 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 4 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 4 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 5: { // 6x6 * 6x5 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 5 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 6: { // 6x6 * 6x6 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 6; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 6 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 6 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 6 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 6 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 6 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 6 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l] + m1Ptr[5] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr += 6; + } + break; + } + default: { + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumColumns(); n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += m1.GetNumColumns(); + } + break; + } + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyMatX + + optimizes the following tranpose matrix multiplications: + + Nx6 * NxN + 6xN * 6x6 + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyMatX"); + int i, j, k, l, n; + float * RESTRICT dstPtr; + const float * RESTRICT m1Ptr, * RESTRICT m2Ptr; + double sum; + + assert( m1.GetNumRows() == m2.GetNumRows() ); + + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + k = m1.GetNumColumns(); + l = m2.GetNumColumns(); + + switch( m1.GetNumRows() ) { + case 1: + if ( k == 6 && l == 1 ) { // 1x6 * 1x1 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 2: + if ( k == 6 && l == 2 ) { // 2x6 * 2x2 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*2+0] + m1Ptr[1*6] * m2Ptr[1*2+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*2+1] + m1Ptr[1*6] * m2Ptr[1*2+1]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 3: + if ( k == 6 && l == 3 ) { // 3x6 * 3x3 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+0] + m1Ptr[1*6] * m2Ptr[1*3+0] + m1Ptr[2*6] * m2Ptr[2*3+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+1] + m1Ptr[1*6] * m2Ptr[1*3+1] + m1Ptr[2*6] * m2Ptr[2*3+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+2] + m1Ptr[1*6] * m2Ptr[1*3+2] + m1Ptr[2*6] * m2Ptr[2*3+2]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 4: + if ( k == 6 && l == 4 ) { // 4x6 * 4x4 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+0] + m1Ptr[1*6] * m2Ptr[1*4+0] + m1Ptr[2*6] * m2Ptr[2*4+0] + m1Ptr[3*6] * m2Ptr[3*4+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+1] + m1Ptr[1*6] * m2Ptr[1*4+1] + m1Ptr[2*6] * m2Ptr[2*4+1] + m1Ptr[3*6] * m2Ptr[3*4+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+2] + m1Ptr[1*6] * m2Ptr[1*4+2] + m1Ptr[2*6] * m2Ptr[2*4+2] + m1Ptr[3*6] * m2Ptr[3*4+2]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+3] + m1Ptr[1*6] * m2Ptr[1*4+3] + m1Ptr[2*6] * m2Ptr[2*4+3] + m1Ptr[3*6] * m2Ptr[3*4+3]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 5: + if ( k == 6 && l == 5 ) { // 5x6 * 5x5 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+0] + m1Ptr[1*6] * m2Ptr[1*5+0] + m1Ptr[2*6] * m2Ptr[2*5+0] + m1Ptr[3*6] * m2Ptr[3*5+0] + m1Ptr[4*6] * m2Ptr[4*5+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+1] + m1Ptr[1*6] * m2Ptr[1*5+1] + m1Ptr[2*6] * m2Ptr[2*5+1] + m1Ptr[3*6] * m2Ptr[3*5+1] + m1Ptr[4*6] * m2Ptr[4*5+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+2] + m1Ptr[1*6] * m2Ptr[1*5+2] + m1Ptr[2*6] * m2Ptr[2*5+2] + m1Ptr[3*6] * m2Ptr[3*5+2] + m1Ptr[4*6] * m2Ptr[4*5+2]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+3] + m1Ptr[1*6] * m2Ptr[1*5+3] + m1Ptr[2*6] * m2Ptr[2*5+3] + m1Ptr[3*6] * m2Ptr[3*5+3] + m1Ptr[4*6] * m2Ptr[4*5+3]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+4] + m1Ptr[1*6] * m2Ptr[1*5+4] + m1Ptr[2*6] * m2Ptr[2*5+4] + m1Ptr[3*6] * m2Ptr[3*5+4] + m1Ptr[4*6] * m2Ptr[4*5+4]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 6: + if ( l == 6 ) { + switch( k ) { + case 1: // 6x1 * 6x6 + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*1] * m2Ptr[0*6] + + m1Ptr[1*1] * m2Ptr[1*6] + + m1Ptr[2*1] * m2Ptr[2*6] + + m1Ptr[3*1] * m2Ptr[3*6] + + m1Ptr[4*1] * m2Ptr[4*6] + + m1Ptr[5*1] * m2Ptr[5*6]; + m2Ptr++; + } + return; + case 2: // 6x2 * 6x6 + for ( i = 0; i < 2; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*2] * m2Ptr[0*6] + + m1Ptr[1*2] * m2Ptr[1*6] + + m1Ptr[2*2] * m2Ptr[2*6] + + m1Ptr[3*2] * m2Ptr[3*6] + + m1Ptr[4*2] * m2Ptr[4*6] + + m1Ptr[5*2] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 3: // 6x3 * 6x6 + for ( i = 0; i < 3; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*3] * m2Ptr[0*6] + + m1Ptr[1*3] * m2Ptr[1*6] + + m1Ptr[2*3] * m2Ptr[2*6] + + m1Ptr[3*3] * m2Ptr[3*6] + + m1Ptr[4*3] * m2Ptr[4*6] + + m1Ptr[5*3] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 4: // 6x4 * 6x6 + for ( i = 0; i < 4; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*4] * m2Ptr[0*6] + + m1Ptr[1*4] * m2Ptr[1*6] + + m1Ptr[2*4] * m2Ptr[2*6] + + m1Ptr[3*4] * m2Ptr[3*6] + + m1Ptr[4*4] * m2Ptr[4*6] + + m1Ptr[5*4] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 5: // 6x5 * 6x6 + for ( i = 0; i < 5; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*5] * m2Ptr[0*6] + + m1Ptr[1*5] * m2Ptr[1*6] + + m1Ptr[2*5] * m2Ptr[2*6] + + m1Ptr[3*5] * m2Ptr[3*6] + + m1Ptr[4*5] * m2Ptr[4*6] + + m1Ptr[5*5] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 6: // 6x6 * 6x6 + for ( i = 0; i < 6; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*6] + + m1Ptr[1*6] * m2Ptr[1*6] + + m1Ptr[2*6] * m2Ptr[2*6] + + m1Ptr[3*6] * m2Ptr[3*6] + + m1Ptr[4*6] * m2Ptr[4*6] + + m1Ptr[5*6] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l] + m1Ptr[5*k] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + default: + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = m1.ToFloatPtr() + i; + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumRows(); n++ ) { + m1Ptr += k; + m2Ptr += l; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_Generic::MatX_LowerTriangularSolve( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n, int skip ) { + TIME_THIS_SCOPE("SIMD MatX_LowerTriangularSolve"); +#if 1 + + int nc; + const float * RESTRICT lptr; + + if ( skip >= n ) { + return; + } + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + int i, j; + register double s0, s1, s2, s3; + + for ( i = skip; i < n; i++ ) { + s0 = lptr[0] * x[0]; + s1 = lptr[1] * x[1]; + s2 = lptr[2] * x[2]; + s3 = lptr[3] * x[3]; + for ( j = 4; j < i-7; j += 8 ) { + s0 += lptr[j+0] * x[j+0]; + s1 += lptr[j+1] * x[j+1]; + s2 += lptr[j+2] * x[j+2]; + s3 += lptr[j+3] * x[j+3]; + s0 += lptr[j+4] * x[j+4]; + s1 += lptr[j+5] * x[j+5]; + s2 += lptr[j+6] * x[j+6]; + s3 += lptr[j+7] * x[j+7]; + } + switch( i - j ) { + case 7: s0 += lptr[j+6] * x[j+6]; + case 6: s1 += lptr[j+5] * x[j+5]; + case 5: s2 += lptr[j+4] * x[j+4]; + case 4: s3 += lptr[j+3] * x[j+3]; + case 3: s0 += lptr[j+2] * x[j+2]; + case 2: s1 += lptr[j+1] * x[j+1]; + case 1: s2 += lptr[j+0] * x[j+0]; + } + double sum; + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum -= b[i]; + x[i] = -sum; + lptr += nc; + } + +#else + + int i, j; + const float * RESTRICT lptr; + double sum; + + for ( i = skip; i < n; i++ ) { + sum = b[i]; + lptr = L[i]; + for ( j = 0; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + } + +#endif +} + +/* +============ +idSIMD_Generic::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_Generic::MatX_LowerTriangularSolveTranspose( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n ) { + TIME_THIS_SCOPE("SIMD MatX_LowerTriangularSolveTranspose"); +#if 1 + + int nc; + const float * RESTRICT lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + + int i, j; + register double s0, s1, s2, s3; + float * RESTRICT xptr; + + lptr = L.ToFloatPtr() + n * nc + n - 4; + xptr = x + n; + + // process 4 rows at a time + for ( i = n; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + for ( j = 0; j < n-i; j += 4 ) { + s0 -= lptr[(j+0)*nc+0] * xptr[j+0]; + s1 -= lptr[(j+0)*nc+1] * xptr[j+0]; + s2 -= lptr[(j+0)*nc+2] * xptr[j+0]; + s3 -= lptr[(j+0)*nc+3] * xptr[j+0]; + s0 -= lptr[(j+1)*nc+0] * xptr[j+1]; + s1 -= lptr[(j+1)*nc+1] * xptr[j+1]; + s2 -= lptr[(j+1)*nc+2] * xptr[j+1]; + s3 -= lptr[(j+1)*nc+3] * xptr[j+1]; + s0 -= lptr[(j+2)*nc+0] * xptr[j+2]; + s1 -= lptr[(j+2)*nc+1] * xptr[j+2]; + s2 -= lptr[(j+2)*nc+2] * xptr[j+2]; + s3 -= lptr[(j+2)*nc+3] * xptr[j+2]; + s0 -= lptr[(j+3)*nc+0] * xptr[j+3]; + s1 -= lptr[(j+3)*nc+1] * xptr[j+3]; + s2 -= lptr[(j+3)*nc+2] * xptr[j+3]; + s3 -= lptr[(j+3)*nc+3] * xptr[j+3]; + } + // process left over of the 4 rows + s0 -= lptr[0-1*nc] * s3; + s1 -= lptr[1-1*nc] * s3; + s2 -= lptr[2-1*nc] * s3; + s0 -= lptr[0-2*nc] * s2; + s1 -= lptr[1-2*nc] * s2; + s0 -= lptr[0-3*nc] * s1; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4 + 4 * nc; + xptr -= 4; + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#else + + int i, j, nc; + const float * RESTRICT ptr; + double sum; + + nc = L.GetNumColumns(); + for ( i = n - 1; i >= 0; i-- ) { + sum = b[i]; + ptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + sum -= ptr[j*nc] * x[j]; + } + x[i] = sum; + } + +#endif +} + +/* +============ +idSIMD_Generic::MatX_LDLTFactor + + in-place factorization LDL' of the n * n sub-matrix of mat + the reciprocal of the diagonal elements are stored in invDiag +============ +*/ +bool VPCALL idSIMD_Generic::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { + TIME_THIS_SCOPE("SIMD MatX_LDLTFactor"); +#if 1 + + int i, j, k, nc; + float * RESTRICT v, * RESTRICT diag, * RESTRICT mptr; + double s0, s1, s2, s3, sum, d; + + v = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + diag = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + mptr = mat[i+1]; + for ( j = i+1; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + + return true; + +#else + + int i, j, k, nc; + float * RESTRICT v, * RESTRICT ptr, * RESTRICT diagPtr; + double d, sum; + + v = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + nc = mat.GetNumColumns(); + + for ( i = 0; i < n; i++ ) { + + ptr = mat[i]; + diagPtr = mat[0]; + sum = ptr[i]; + for ( j = 0; j < i; j++ ) { + d = ptr[j]; + v[j] = diagPtr[0] * d; + sum -= v[j] * d; + diagPtr += nc + 1; + } + + if ( sum == 0.0f ) { + return false; + } + + diagPtr[0] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + continue; + } + + ptr = mat[i+1]; + for ( j = i + 1; j < n; j++ ) { + sum = ptr[i]; + for ( k = 0; k < i; k++ ) { + sum -= ptr[k] * v[k]; + } + ptr[i] = sum * d; + ptr += nc; + } + } + + return true; + +#endif +} + +/* +============ +idSIMD_Generic::BlendJoints +============ +*/ +// RAVEN BEGIN +// jsinger: BlendJoints() moved to be inline for xenon +// RAVEN END + +/* +============ +idSIMD_Generic::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointQuatsToJointMats( idJointMat * RESTRICT jointMats, const idJointQuat * RESTRICT jointQuats, const int numJoints ) { + TIME_THIS_SCOPE("SIMD ConvertJointQuatsToJointMats"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + jointMats[i].SetRotation( jointQuats[i].q.ToMat3() ); + jointMats[i].SetTranslation( jointQuats[i].t ); + } +} + +/* +============ +idSIMD_Generic::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointMatsToJointQuats( idJointQuat * RESTRICT jointQuats, const idJointMat * RESTRICT jointMats, const int numJoints ) { + TIME_THIS_SCOPE("SIMD ConvertJointMatsToJointQuats"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + jointQuats[i] = jointMats[i].ToJointQuat(); + } +} + +/* +============ +idSIMD_Generic::TransformJoints +============ +*/ +void VPCALL idSIMD_Generic::TransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ) { + TIME_THIS_SCOPE("SIMD TransformJoints"); + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } +} + +/* +============ +idSIMD_Generic::UntransformJoints +============ +*/ +void VPCALL idSIMD_Generic::UntransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ) { + TIME_THIS_SCOPE("SIMD UntransformJoints"); + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } +} + +/* +============ +idSIMD_Generic::MultiplyJoints +============ +*/ +void VPCALL idSIMD_Generic::MultiplyJoints( idJointMat * RESTRICT result, const idJointMat * RESTRICT joints1, const idJointMat * RESTRICT joints2, const int numJoints ) { + TIME_THIS_SCOPE("SIMD MultiplyJoints"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } +} + + +/* +============ +idBounds_AddPoint +============ +*/ +ID_INLINE void idBounds_AddPoint( idBounds &b, const idVec3 &v ) { + float p = ( b[0].x + v.x ) * 0.5f; + b[0].x = p - fabs( b[0].x - p ); + float q = ( b[0].y + v.y ) * 0.5f; + b[0].y = q - fabs( b[0].y - q ); + float r = ( b[0].z + v.z ) * 0.5f; + b[0].z = r - fabs( b[0].z - r ); + float s = ( b[1].x + v.x ) * 0.5f; + b[1].x = s + fabs( b[1].x - s ); + float t = ( b[1].y + v.y ) * 0.5f; + b[1].y = t + fabs( b[1].y - t ); + float u = ( b[1].z + v.z ) * 0.5f; + b[1].z = u + fabs( b[1].z - u ); +} + +/* +============ +idSIMD_Generic::TransformVertsNew +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsNew( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsNew"); + int i, j; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + + bounds.Zero(); + for( j = 0, i = 0; i < numVerts; i++, j++ ) { + idVec3 v; + + v = ( *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ) ) * base[j]; + while( weights[j].nextVertexOffset != JOINTWEIGHT_SIZE ) { + j++; + v += ( *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ) ) * base[j]; + } + + verts[i].xyz = v; + + idBounds_AddPoint( bounds, v ); + } +} + +/* +============ +idSIMD_Generic::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsAndTangents( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsAndTangents"); + int i, j; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + + bounds.Zero(); + for( j = i = 0; i < numVerts; i++, j++ ) { + idJointMat mat; + + idJointMat::Mul( mat, *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ), weights[j].weight ); + while( weights[j].nextVertexOffset != JOINTWEIGHT_SIZE ) { + j++; + idJointMat::Mad( mat, *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ), weights[j].weight ); + } + + verts[i].xyz = mat * base[i*4+0]; + verts[i].normal = mat * base[i*4+1]; + verts[i].tangents[0] = mat * base[i*4+2]; + verts[i].tangents[1] = mat * base[i*4+3]; + + idBounds_AddPoint( bounds, verts[i].xyz ); + } +} + +/* +============ +idSIMD_Generic::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsAndTangentsFast( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsAndTangentsFast"); + int i; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + const byte * RESTRICT weightsPtr = (byte * RESTRICT )weights; + + bounds.Zero(); + for( i = 0; i < numVerts; i++ ) { + const idJointMat &mat = *(idJointMat *) ( jointsPtr + ((jointWeight_t *)weightsPtr)->jointMatOffset ); + + weightsPtr += ((jointWeight_t *)weightsPtr)->nextVertexOffset; + + verts[i].xyz = mat * base[i*4+0]; + verts[i].normal = mat * base[i*4+1]; + verts[i].tangents[0] = mat * base[i*4+2]; + verts[i].tangents[1] = mat * base[i*4+3]; + + idBounds_AddPoint( bounds, verts[i].xyz ); + } +} + +/* +============ +idSIMD_Generic::TracePointCull +============ +*/ +void VPCALL idSIMD_Generic::TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("idSIMD_Generic::TracePointCull idDrawVert"); + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +/* +============ +idSIMD_Generic::DecalPointCull +============ +*/ +void VPCALL idSIMD_Generic::DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DecalPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +/* +============ +idSIMD_Generic::OverlayPointCull +============ +*/ +void VPCALL idSIMD_Generic::OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD OverlayPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1; + const idVec3 &v = verts[i].xyz; + + texCoords[i][0] = d0 = planes[0].Distance( v ); + texCoords[i][1] = d1 = planes[1].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[i] = bits; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes idDrawVert"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyz ); + planes++; + } +} + +/* +============ +idSIMD_Generic::DeriveTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from all triangles + using the vertex which results in smooth tangents across the mesh. + In the process the triangle planes are calculated as well. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTangents( idPlane * RESTRICT planes, idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTangents"); + int i; + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + idPlane * RESTRICT planesPtr = planes; + for ( i = 0; i < numIndexes; i += 3 ) { + idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + unsigned long signBit; + float d0[5], d1[5], f, area; + idVec3 n, t0, t1; + + int v0 = indexes[i + 0]; + int v1 = indexes[i + 1]; + int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + // normal + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planesPtr->SetNormal( n ); + planesPtr->FitThroughPoint( a->xyz ); + planesPtr++; + + // area sign bit + area = d0[3] * d1[4] - d0[4] * d1[3]; + signBit = ( *(unsigned long *)&area ) & ( 1 << 31 ); + + // first tangent + t0[0] = d0[0] * d1[4] - d0[4] * d1[0]; + t0[1] = d0[1] * d1[4] - d0[4] * d1[1]; + t0[2] = d0[2] * d1[4] - d0[4] * d1[2]; + + f = idMath::RSqrt( t0.x * t0.x + t0.y * t0.y + t0.z * t0.z ); + *(unsigned long *)&f ^= signBit; + + t0.x *= f; + t0.y *= f; + t0.z *= f; + + // second tangent + t1[0] = d0[3] * d1[0] - d0[0] * d1[3]; + t1[1] = d0[3] * d1[1] - d0[1] * d1[3]; + t1[2] = d0[3] * d1[2] - d0[2] * d1[3]; + + f = idMath::RSqrt( t1.x * t1.x + t1.y * t1.y + t1.z * t1.z ); + *(unsigned long *)&f ^= signBit; + + t1.x *= f; + t1.y *= f; + t1.z *= f; + + if ( used[v0] ) { + a->normal += n; + a->tangents[0] += t0; + a->tangents[1] += t1; + } else { + a->normal = n; + a->tangents[0] = t0; + a->tangents[1] = t1; + used[v0] = true; + } + + if ( used[v1] ) { + b->normal += n; + b->tangents[0] += t0; + b->tangents[1] += t1; + } else { + b->normal = n; + b->tangents[0] = t0; + b->tangents[1] = t1; + used[v1] = true; + } + + if ( used[v2] ) { + c->normal += n; + c->tangents[0] += t0; + c->tangents[1] += t1; + } else { + c->normal = n; + c->tangents[0] = t0; + c->tangents[1] = t1; + used[v2] = true; + } + } +} + +/* +============ +idSIMD_Generic::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_Generic::DeriveUnsmoothedTangents( idDrawVert * RESTRICT verts, const dominantTri_s * RESTRICT dominantTris, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DeriveUnsmoothedTangents"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +/* +============ +idSIMD_Generic::NormalizeTangents + + Normalizes each vertex normal and projects and normalizes the + tangent vectors onto the plane orthogonal to the vertex normal. +============ +*/ +void VPCALL idSIMD_Generic::NormalizeTangents( idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD NormalizeTangents"); + + for ( int i = 0; i < numVerts; i++ ) { + idVec3 &v = verts[i].normal; + float f; + + f = idMath::RSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + v.x *= f; v.y *= f; v.z *= f; + + for ( int j = 0; j < 2; j++ ) { + idVec3 &t = verts[i].tangents[j]; + + t -= ( t * v ) * v; + f = idMath::RSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + t.x *= f; t.y *= f; t.z *= f; + } + } +} + +/* +============ +idSIMD_Generic::CreateTextureSpaceLightVectors + + Calculates light vectors in texture space for the given triangle vertices. + For each vertex the direction towards the light origin is projected onto texture space. + The light vectors are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_Generic::CreateTextureSpaceLightVectors( idVec3 * RESTRICT lightVectors, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD CreateTextureSpaceLightVectors"); + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert * RESTRICT v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + + lightVectors[i][0] = lightDir * v->tangents[0]; + lightVectors[i][1] = lightDir * v->tangents[1]; + lightVectors[i][2] = lightDir * v->normal; + } +} + +/* +============ +idSIMD_Generic::CreateSpecularTextureCoords + + Calculates specular texture coordinates for the given triangle vertices. + For each vertex the normalized direction towards the light origin is added to the + normalized direction towards the view origin and the result is projected onto texture space. + The texture coordinates are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_Generic::CreateSpecularTextureCoords( idVec4 * RESTRICT texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD CreateSpecularTextureCoords"); + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert * RESTRICT v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + idVec3 viewDir = viewOrigin - v->xyz; + + float ilength; + + ilength = idMath::RSqrt( lightDir * lightDir ); + lightDir[0] *= ilength; + lightDir[1] *= ilength; + lightDir[2] *= ilength; + + ilength = idMath::RSqrt( viewDir * viewDir ); + viewDir[0] *= ilength; + viewDir[1] *= ilength; + viewDir[2] *= ilength; + + lightDir += viewDir; + + texCoords[i][0] = lightDir * v->tangents[0]; + texCoords[i][1] = lightDir * v->tangents[1]; + texCoords[i][2] = lightDir * v->normal; + texCoords[i][3] = 1.0f; + } +} + +/* +============ +idSIMD_Generic::CreateShadowCache +============ +*/ +int VPCALL idSIMD_Generic::CreateShadowCache( idVec4 * RESTRICT vertexCache, int * RESTRICT vertRemap, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD CreateShadowCache"); + int outVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float * RESTRICT v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} + +/* +============ +idSIMD_Generic::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_Generic::CreateVertexProgramShadowCache( idVec4 * RESTRICT vertexCache, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD CreateVertexProgramShadowCache"); + for ( int i = 0; i < numVerts; i++ ) { + const float * RESTRICT v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacing +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CountFacing( const byte * RESTRICT facing, const int numFaces ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CountFacing"); + int i, n; + + n = 0; + for ( i = 0; i < numFaces; i++ ) { + n += facing[i]; + } + return n; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacingCull +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CountFacingCull( byte * RESTRICT facing, const int numFaces, const int * RESTRICT indexes, const byte * RESTRICT cull ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CountFacingCull"); + int i, n; + + n = 0; + for ( i = 0; i < numFaces; i++ ) { + if ( !facing[i] ) { + int i1 = indexes[0]; + int i2 = indexes[1]; + int i3 = indexes[2]; + if ( cull[i1] & cull[i2] & cull[i3] ) { + facing[i] = 1; + n++; + } + } else { + n++; + } + indexes += 3; + } + return n; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CreateSilTriangles +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CreateSilTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const silEdge_s * RESTRICT silEdges, const int numSilEdges ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CreateSilTriangles"); + int i; + const silEdge_t * RESTRICT sil; + int * RESTRICT si; + + si = shadowIndexes; + for ( sil = ( silEdge_t * RESTRICT )silEdges, i = numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = sil->v1 << 1; + int v2 = sil->v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + si[0] = v1; + si[1] = v2 ^ f1; + si[2] = v2 ^ f2; + si[3] = v1 ^ f2; + si[4] = v1 ^ f1; + si[5] = v2 ^ 1; + + si += 6; + } + return si - shadowIndexes; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CreateCapTriangles +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CreateCapTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CreateCapTriangles"); + int i, j; + int * RESTRICT si; + + si = shadowIndexes; + for ( i = 0, j = 0; i < numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = indexes[i+0] << 1; + si[2] = i0; + si[3] = i0 ^ 1; + int i1 = indexes[i+1] << 1; + si[1] = i1; + si[4] = i1 ^ 1; + int i2 = indexes[i+2] << 1; + si[0] = i2; + si[5] = i2 ^ 1; + + si += 6; + } + return si - shadowIndexes; +} + +/* +============ +idSIMD_Generic::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_Generic::UpSamplePCMTo44kHz( float * RESTRICT dest, const short * RESTRICT src, const int numSamples, const int kHz, const int numChannels ) { + TIME_THIS_SCOPE("SIMD UpSamplePCMTo44kHz"); + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + for ( int i = 0; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + for ( int i = 0; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_Generic::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_Generic::UpSampleOGGTo44kHz( float * RESTRICT dest, const float * const * RESTRICT ogg, const int numSamples, const int kHz, const int numChannels ) { + TIME_THIS_SCOPE("SIMD UpSampleOGGTo44kHz"); + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMono"); + float sL = lastV[0]; + float sR = lastV[1]; + float incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j] * sL; + mixBuffer[j*2+1] += samples[j] * sR; + sL += incL; + sR += incR; + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerMonoSimple +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMonoSimple"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j]; + mixBuffer[j*2+1] += samples[j]; + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerStereo +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerStereo"); + float sL = lastV[0]; + float sR = lastV[1]; + float incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j*2+0] * sL; + mixBuffer[j*2+1] += samples[j*2+1] * sR; + sL += incL; + sR += incR; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerMono +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerMono"); + float sL0 = lastV[0]; + float sL1 = lastV[1]; + float sL2 = lastV[2]; + float sL3 = lastV[3]; + float sL4 = lastV[4]; + float sL5 = lastV[5]; + + float incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + float incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + float incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + float incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + float incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i] * sL0; + mixBuffer[i*6+1] += samples[i] * sL1; + mixBuffer[i*6+2] += samples[i] * sL2; + mixBuffer[i*6+3] += samples[i] * sL3; + mixBuffer[i*6+4] += samples[i] * sL4; + mixBuffer[i*6+5] += samples[i] * sL5; + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + sL4 += incL4; + sL5 += incL5; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerMonoSimple +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerMono"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + // Just mix the front 2 speakers - the others are unchanged + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i]; + mixBuffer[i*6+1] += samples[i]; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerStereo +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerStereo"); + float sL0 = lastV[0]; + float sL1 = lastV[1]; + float sL2 = lastV[2]; + float sL3 = lastV[3]; + float sL4 = lastV[4]; + float sL5 = lastV[5]; + + float incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + float incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + float incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + float incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + float incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i*2+0] * sL0; + mixBuffer[i*6+1] += samples[i*2+1] * sL1; + mixBuffer[i*6+2] += samples[i*2+0] * sL2; + mixBuffer[i*6+3] += samples[i*2+0] * sL3; + mixBuffer[i*6+4] += samples[i*2+0] * sL4; + mixBuffer[i*6+5] += samples[i*2+1] * sL5; + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + sL4 += incL4; + sL5 += incL5; + } +} + +/* +============ +idSIMD_Generic::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_Generic::MixedSoundToSamples( short * RESTRICT samples, const float * RESTRICT mixBuffer, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixedSoundToSamples"); + + for ( int i = 0; i < numSamples; i++ ) { + if ( mixBuffer[i] <= -32768.0f ) { + samples[i] = -32768; + } else if ( mixBuffer[i] >= 32767.0f ) { + samples[i] = 32767; + } else { + samples[i] = (short) mixBuffer[i]; + } + } +} + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT +/* +============ +idSIMD_Generic::JointMat_MultiplyMats +// dluetscher: added support for concatenating matrices from two idJointMat arrays, based on the given palette mapping, +// storing the resulting transform palette in an array of 4x4 matrices, +// stored in row-major array ordering, with translation in last column (column-major matrix) +// +// For example the following matrix: Xx Yx Zx Tx +// Xy Yy Zx Ty +// Xz Yz Zz Tz +// 0 0 0 1 +// +// is stored in the resulting order: Xx Yx Zx Tx Xy Yy Zx Ty Xz Yz Zz Tz 0 0 0 1 +============ +*/ +void VPCALL idSIMD_Generic::JointMat_MultiplyMats( float * RESTRICT destMats, + const idJointMat * RESTRICT src1Mats, + const idJointMat * RESTRICT src2Mats, + int * RESTRICT transformPalette, + int transformCount ) { + TIME_THIS_SCOPE("SIMD JointMat_MultiplyMats"); + float * RESTRICT destPtr; + const float * RESTRICT src1Ptr, * RESTRICT src2Ptr; + int curTransform, matOffset; + + for ( curTransform = 0; curTransform < transformCount; curTransform++ ) { + + matOffset = transformPalette[ curTransform ]; + + src1Ptr = src1Mats[matOffset].ToFloatPtr(); + src2Ptr = src2Mats[matOffset].ToFloatPtr(); + destPtr = destMats + (curTransform << 4); + + destPtr[0 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 0] = 0.f; + + destPtr[0 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 1] = 0.f; + + destPtr[0 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 2] = 0.f; + + destPtr[0 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 3] = 1.f; + + destPtr[0 * 4 + 3] += src2Ptr[0 * 4 + 3]; + destPtr[1 * 4 + 3] += src2Ptr[1 * 4 + 3]; + destPtr[2 * 4 + 3] += src2Ptr[2 * 4 + 3]; + } +} +#endif +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added TransformVertsMinMax to transform an array of index-weighted vertices into +// an array of idSilTraceVerts, while simulatenously calculating the bounds +#ifdef _MD5R_SUPPORT +void VPCALL idSIMD_Generic::TransformVertsMinMax4Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte * RESTRICT vertexInputData, + int vertStride, int numVerts, + float * RESTRICT skinToModelTransforms ) { + TIME_THIS_SCOPE("SIMD TransformVertsMinMax4Bone"); + float curMin[3], curMax[3]; + float * RESTRICT curTransform, * RESTRICT vertexPos, * RESTRICT blendWeights, * RESTRICT transformedPos; + byte * RESTRICT vertexOutputData, * RESTRICT blendIndices, * RESTRICT endVertexInputData; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + + vertexOutputData = (byte* RESTRICT ) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + vertexPos = (float * RESTRICT ) vertexInputData; + blendIndices = vertexInputData + sizeof(float)*3; + blendWeights = (float * RESTRICT ) (vertexInputData + sizeof(float)*3 + sizeof(byte)*4); + transformedPos = (float * RESTRICT ) vertexOutputData; + + curTransform = skinToModelTransforms + ((dword) blendIndices[0] << 4); + transformedPos[0] = blendWeights[0]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] = blendWeights[0]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] = blendWeights[0]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[1] << 4); + transformedPos[0] += blendWeights[1]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[1]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[1]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[2] << 4); + transformedPos[0] += blendWeights[2]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[2]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[2]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[3] << 4); + transformedPos[0] += blendWeights[3]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[3]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[3]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curMin[0] = transformedPos[0] < curMin[0] ? transformedPos[0] : curMin[0]; + curMin[1] = transformedPos[1] < curMin[1] ? transformedPos[1] : curMin[1]; + curMin[2] = transformedPos[2] < curMin[2] ? transformedPos[2] : curMin[2]; + + curMax[0] = transformedPos[0] > curMax[0] ? transformedPos[0] : curMax[0]; + curMax[1] = transformedPos[1] > curMax[1] ? transformedPos[1] : curMax[1]; + curMax[2] = transformedPos[2] > curMax[2] ? transformedPos[2] : curMax[2]; + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +void VPCALL idSIMD_Generic::TransformVertsMinMax1Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte * RESTRICT vertexInputData, + int vertStride, int numVerts, + float * RESTRICT skinToModelTransforms ) { + TIME_THIS_SCOPE("SIMD TransformVertsMinMax1Bone"); + float curMin[3], curMax[3]; + float * RESTRICT curTransform, * RESTRICT vertexPos, * RESTRICT transformedPos; + byte * RESTRICT vertexOutputData, * RESTRICT blendIndices, * RESTRICT endVertexInputData; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + + vertexOutputData = (byte* RESTRICT ) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + vertexPos = (float * RESTRICT ) vertexInputData; + blendIndices = vertexInputData + sizeof(float)*3; + transformedPos = (float * RESTRICT ) vertexOutputData; + + curTransform = skinToModelTransforms + ((dword) blendIndices[0] << 4); + transformedPos[0] = (vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] = (vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] = (vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curMin[0] = transformedPos[0] < curMin[0] ? transformedPos[0] : curMin[0]; + curMin[1] = transformedPos[1] < curMin[1] ? transformedPos[1] : curMin[1]; + curMin[2] = transformedPos[2] < curMin[2] ? transformedPos[2] : curMin[2]; + + curMax[0] = transformedPos[0] > curMax[0] ? transformedPos[0] : curMax[0]; + curMax[1] = transformedPos[1] > curMax[1] ? transformedPos[1] : curMax[1]; + curMax[2] = transformedPos[2] > curMax[2] ? transformedPos[2] : curMax[2]; + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_Generic::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_Generic::Dot( float * RESTRICT dst, const idVec3 &constant, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Dot idVec3-rvSilTraceVertT"); +#define OPER(X) dst[(X)] = constant * src[(X)].xyzw.ToVec3(); + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_Generic::Dot( float * RESTRICT dst, const idPlane &constant, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Dot idPlane-rvSilTraceVertT"); +#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyzw.ToVec3() + constant[3]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::TracePointCull +============ +*/ +void VPCALL idSIMD_Generic::TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD TracePointCull"); + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +/* +============ +idSIMD_Generic::DecalPointCull +============ +*/ +void VPCALL idSIMD_Generic::DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DecalPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +/* +============ +idSIMD_Generic::OverlayPointCull +============ +*/ +void VPCALL idSIMD_Generic::OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD OverlayPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + texCoords[i][0] = d0 = planes[0].Distance( v ); + texCoords[i][1] = d1 = planes[1].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[i] = bits; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes rvSilTraceVertT-int"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const rvSilTraceVertT * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyzw[0] - a->xyzw[0]; + d0[1] = b->xyzw[1] - a->xyzw[1]; + d0[2] = b->xyzw[2] - a->xyzw[2]; + + d1[0] = c->xyzw[0] - a->xyzw[0]; + d1[1] = c->xyzw[1] - a->xyzw[1]; + d1[2] = c->xyzw[2] - a->xyzw[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyzw.ToVec3() ); + planes++; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const unsigned short * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes rvSilTraceVertT-ushort"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const rvSilTraceVertT * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyzw[0] - a->xyzw[0]; + d0[1] = b->xyzw[1] - a->xyzw[1]; + d0[2] = b->xyzw[2] - a->xyzw[2]; + + d1[0] = c->xyzw[0] - a->xyzw[0]; + d1[1] = c->xyzw[1] - a->xyzw[1]; + d1[2] = c->xyzw[2] - a->xyzw[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyzw.ToVec3() ); + planes++; + } +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax rvSilTraceVertT"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)].xyzw.ToVec3(); if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int * RESTRICT indexes, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax rvSilTraceVertT indexed"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[indexes[(X)]].xyzw.ToVec3(); if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} +#endif // #ifdef _MD5R_SUPPORT diff --git a/source/idlib/math/Simd_generic.h b/source/idlib/math/Simd_generic.h new file mode 100644 index 0000000..660c5c5 --- /dev/null +++ b/source/idlib/math/Simd_generic.h @@ -0,0 +1,158 @@ + +#ifndef __MATH_SIMD_GENERIC_H__ +#define __MATH_SIMD_GENERIC_H__ + +/* +=============================================================================== + + Generic implementation of idSIMDProcessor + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: should not be virtual on xenon otherwise the mispredicted branch far outways the +// savings we get from doing SIMD in the first place. Also inheritence is unnecessary +#ifdef _XENON +#define VIRTUAL +class idSIMD_Generic { +#else +#define VIRTUAL virtual +class idSIMD_Generic : public idSIMDProcessor { +#endif +public: + VIRTUAL const char * VPCALL GetName( void ) const; + + VIRTUAL void VPCALL Add( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Add( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Sub( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Sub( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Mul( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Mul( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Div( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Div( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL MulAdd( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulAdd( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL MulSub( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulSub( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idPlane * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idPlane * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 * RESTRICT src0, const idVec3 * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Dot( float &dot, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + + VIRTUAL void VPCALL CmpGT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + + VIRTUAL void VPCALL MinMax( float &min, float &max, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int * RESTRICT indexes, const int count ); + + VIRTUAL void VPCALL Clamp( float * RESTRICT dst, const float * RESTRICT src, const float min, const float max, const int count ); + VIRTUAL void VPCALL ClampMin( float * RESTRICT dst, const float * RESTRICT src, const float min, const int count ); + VIRTUAL void VPCALL ClampMax( float * RESTRICT dst, const float * RESTRICT src, const float max, const int count ); + + VIRTUAL void VPCALL Memcpy( void * RESTRICT dst, const void * RESTRICT src, const int count ); + VIRTUAL void VPCALL Memset( void * RESTRICT dst, const int val, const int count ); + + VIRTUAL void VPCALL Zero16( float * RESTRICT dst, const int count ); + VIRTUAL void VPCALL Negate16( float * RESTRICT dst, const int count ); + VIRTUAL void VPCALL Copy16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Add16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + VIRTUAL void VPCALL Sub16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + VIRTUAL void VPCALL Mul16( float * RESTRICT dst, const float * RESTRICT src1, const float constant, const int count ); + VIRTUAL void VPCALL AddAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL SubAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulAssign16( float * RESTRICT dst, const float constant, const int count ); + + VIRTUAL void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + VIRTUAL void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + VIRTUAL void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n, int skip = 0 ); + VIRTUAL void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float * RESTRICT x, const float *b, const int n ); + VIRTUAL bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); + + ID_INLINE void VPCALL BlendJoints( idJointQuat * RESTRICT joints, const idJointQuat * RESTRICT blendJoints, const float lerp, const int * RESTRICT index, const int numJoints ); + VIRTUAL void VPCALL ConvertJointQuatsToJointMats( idJointMat * RESTRICT jointMats, const idJointQuat * RESTRICT jointQuats, const int numJoints ); + VIRTUAL void VPCALL ConvertJointMatsToJointQuats( idJointQuat * RESTRICT jointQuats, const idJointMat * RESTRICT jointMats, const int numJoints ); + VIRTUAL void VPCALL TransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ); + VIRTUAL void VPCALL UntransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ); + VIRTUAL void VPCALL MultiplyJoints( idJointMat * RESTRICT result, const idJointMat * RESTRICT joints1, const idJointMat * RESTRICT joints2, const int numJoints ); + VIRTUAL void VPCALL TransformVertsNew( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TransformVertsAndTangents( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TransformVertsAndTangentsFast( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveTangents( idPlane * RESTRICT planes, idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveUnsmoothedTangents( idDrawVert * RESTRICT verts, const dominantTri_s * RESTRICT dominantTris, const int numVerts ); + VIRTUAL void VPCALL NormalizeTangents( idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL CreateTextureSpaceLightVectors( idVec3 * RESTRICT lightVectors, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL CreateSpecularTextureCoords( idVec4 * RESTRICT texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL int VPCALL CreateShadowCache( idVec4 * RESTRICT vertexCache, int * RESTRICT vertRemap, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL int VPCALL CreateVertexProgramShadowCache( idVec4 * RESTRICT vertexCache, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL int VPCALL ShadowVolume_CountFacing( const byte * RESTRICT facing, const int numFaces ); + VIRTUAL int VPCALL ShadowVolume_CountFacingCull( byte * RESTRICT facing, const int numFaces, const int * RESTRICT indexes, const byte * RESTRICT cull ); + VIRTUAL int VPCALL ShadowVolume_CreateSilTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const silEdge_s * RESTRICT silEdges, const int numSilEdges ); + VIRTUAL int VPCALL ShadowVolume_CreateCapTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const int * RESTRICT indexes, const int numIndexes ); + + VIRTUAL void VPCALL UpSamplePCMTo44kHz( float * RESTRICT dest, const short * RESTRICT pcm, const int numSamples, const int kHz, const int numChannels ); + VIRTUAL void VPCALL UpSampleOGGTo44kHz( float * RESTRICT dest, const float * const * RESTRICT ogg, const int numSamples, const int kHz, const int numChannels ); + VIRTUAL void VPCALL MixSoundTwoSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ); + VIRTUAL void VPCALL MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ); + VIRTUAL void VPCALL MixSoundTwoSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ); + VIRTUAL void VPCALL MixSoundSixSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ); + VIRTUAL void VPCALL MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ); + VIRTUAL void VPCALL MixSoundSixSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ); + VIRTUAL void VPCALL MixedSoundToSamples( short * RESTRICT samples, const float * RESTRICT mixBuffer, const int numSamples ); + + // rvSilTraceVertT operations +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT + VIRTUAL void VPCALL JointMat_MultiplyMats( float * RESTRICT destMats, const idJointMat * RESTRICT src1Mats, const idJointMat * RESTRICT src2Mats, int * RESTRICT transformPalette, int transformCount ); + VIRTUAL void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, idVec3 &min, idVec3 &max, byte * RESTRICT vertexInputData, int vertStride, int numVerts, float * RESTRICT skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + VIRTUAL void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, idVec3 &min, idVec3 &max, byte * RESTRICT vertexInputData, int vertStride, int numVerts, float * RESTRICT skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const unsigned short * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int * RESTRICT indexes, const int count ); +#endif +}; + +// jsinger: inlined during profiling with Microsoft. This shows up pretty high on our profiles +// inlining reduced the many call overhead and every little bit helps on xenon +void VPCALL idSIMD_Generic::BlendJoints( idJointQuat * RESTRICT joints, const idJointQuat * RESTRICT blendJoints, const float lerp, const int * RESTRICT index, const int count ) { +#define UNROLL4(Y) { int _IX, _NM = count&0xfffffffc; for (_IX=0;_IX<_NM;_IX+=4){Y(_IX+0);Y(_IX+1);Y(_IX+2);Y(_IX+3);}for(;_IX= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + + +//=============================================================== +// +// idVec3 +// +//=============================================================== + +/* +============= +idVec3::ToYaw +============= +*/ +float idVec3::ToYaw( void ) const { + float yaw; + + if ( ( y == 0.0f ) && ( x == 0.0f ) ) { + yaw = 0.0f; + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + } + + return yaw; +} + +/* +============= +idVec3::ToPitch +============= +*/ +float idVec3::ToPitch( void ) const { + float forward; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return pitch; +} + +/* +============= +idVec3::ToAngles +============= +*/ +idAngles idVec3::ToAngles( void ) const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return idAngles( -pitch, yaw, 0.0f ); +} + +// RAVEN BEGIN +/* +============= +idVec3::ToRadians +============= +*/ +rvAngles idVec3::ToRadians( void ) const +{ + float forward; + float yaw; + float pitch; + + if( !x && !y ) + { + yaw = 0.0f; + if( z > 0.0f ) + { + pitch = idMath::HALF_PI; + } + else + { + pitch = idMath::THREEFOURTHS_PI; + } + } + else + { + yaw = idMath::ATan( y, x ); + if( yaw < 0.0f ) + { + yaw += idMath::TWO_PI; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = idMath::ATan( z, forward ); + if( pitch < 0.0f ) + { + pitch += idMath::TWO_PI; + } + } + + return( rvAngles( -pitch, yaw, 0.0f ) ); +} +// RAVEN END + +/* +============= +idVec3::ToPolar +============= +*/ +idPolar3 idVec3::ToPolar( void ) const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + return idPolar3( idMath::Sqrt( x * x + y * y + z * z ), yaw, -pitch ); +} + +/* +============= +idVec3::ToMat3 +============= +*/ +idMat3 idVec3::ToMat3( void ) const { + idMat3 mat; + float d; + + mat[0] = *this; + d = x * x + y * y; + if ( !d ) { + mat[1][0] = 1.0f; + mat[1][1] = 0.0f; + mat[1][2] = 0.0f; + } else { + d = idMath::InvSqrt( d ); + mat[1][0] = -y * d; + mat[1][1] = x * d; + mat[1][2] = 0.0f; + } + mat[2] = Cross( mat[1] ); + + return mat; +} + +/* +============= +idVec3::ToMat3 +============= +*/ +// RAVEN BEGIN +// abahr: added axis so we can create matrix with non-x vector +idMat3 idVec3::ToMat3( int axis ) const { + idMat3 mat; + float d; + int index_x = axis % GetDimension(); + int index_y = (axis + 1) % GetDimension(); + int index_z = (axis + 2) % GetDimension(); + float local_x = (*this)[index_x]; + float local_y = (*this)[index_y]; + + mat[axis] = *this; + d = local_x * local_x + local_y * local_y; + if ( !d ) { + mat[index_y][index_x] = 1.0f; + mat[index_y][index_y] = 0.0f; + mat[index_y][index_z] = 0.0f; + } else { + d = idMath::InvSqrt( d ); + mat[index_y][index_x] = -local_y * d; + mat[index_y][index_y] = local_x * d; + mat[index_y][index_z] = 0.0f; + } + mat[index_z] = Cross( mat[index_y] ); + + return mat; +} +// RAVEN END + +// RAVEN BEGIN +// jscott: slightly quicker version without the copy +idMat3 &idVec3::ToMat3( idMat3 &mat ) const +{ + float d; + + mat[0] = *this; + d = x * x + y * y; + if ( !d ) + { + mat[1][0] = 1.0f; + mat[1][1] = 0.0f; + mat[1][2] = 0.0f; + } + else + { + d = idMath::InvSqrt( d ); + mat[1][0] = -y * d; + mat[1][1] = x * d; + mat[1][2] = 0.0f; + } + mat[2] = Cross( mat[1] ); + return( mat ); +} +// RAVEN END + +/* +============= +idVec3::ToString +============= +*/ +const char *idVec3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec3::Lerp( const idVec3 &v1, const idVec3 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + +/* +============= +SLerp + +Spherical linear interpolation from v1 to v2. +Vectors are expected to be normalized. +============= +*/ +#define LERP_DELTA 1e-6 + +void idVec3::SLerp( const idVec3 &v1, const idVec3 &v2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + (*this) = v1; + return; + } else if ( t >= 1.0f ) { + (*this) = v2; + return; + } + + cosom = v1 * v2; + if ( ( 1.0f - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = idMath::Sin( omega ); + scale0 = idMath::Sin( ( 1.0f - t ) * omega ) / sinom; + scale1 = idMath::Sin( t * omega ) / sinom; + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + (*this) = ( v1 * scale0 + v2 * scale1 ); +} + +/* +============= +ProjectSelfOntoSphere + +Projects the z component onto a sphere. +============= +*/ +void idVec3::ProjectSelfOntoSphere( const float radius ) { + float rsqr = radius * radius; + float len = Length(); + if ( len < rsqr * 0.5f ) { + z = idMath::Sqrt( rsqr - len ); + } else { + z = rsqr / ( 2.0f * idMath::Sqrt( len ) ); + } +} + +//=============================================================== +// +// idVec4 +// +//=============================================================== + +/* +============= +idVec4::ToString +============= +*/ +const char *idVec4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +// jsinger: moved to inline in the header file for Xenon + +//=============================================================== +// +// idVec5 +// +//=============================================================== + +/* +============= +idVec5::ToString +============= +*/ +const char *idVec5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idVec5::Lerp +============= +*/ +void idVec5::Lerp( const idVec5 &v1, const idVec5 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + x = v1.x + l * ( v2.x - v1.x ); + y = v1.y + l * ( v2.y - v1.y ); + z = v1.z + l * ( v2.z - v1.z ); + s = v1.s + l * ( v2.s - v1.s ); + t = v1.t + l * ( v2.t - v1.t ); + } +} + + +//=============================================================== +// +// idVec6 +// +//=============================================================== + +/* +============= +idVec6::ToString +============= +*/ +const char *idVec6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +//=============================================================== +// +// idVecX +// +//=============================================================== + +// RAVEN BEGIN +float idVecX::tempPtr[VECX_MAX_TEMP]; +//float * idVecX::tempPtr = (float *) ( ( (int) idVecX::temp + 15 ) & ~15 ); +// RAVEN END +int idVecX::tempIndex = 0; + +/* +============= +idVecX::ToString +============= +*/ +const char *idVecX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/source/idlib/math/Vector.h b/source/idlib/math/Vector.h new file mode 100644 index 0000000..421075b --- /dev/null +++ b/source/idlib/math/Vector.h @@ -0,0 +1,2493 @@ + +#ifndef __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +/* +=============================================================================== + + Vector classes + +=============================================================================== +*/ + +#define VECTOR_EPSILON 0.001f + +class idAngles; +class idPolar3; +class idMat3; + +// RAVEN BEGIN +class rvAngles; +// RAVEN END + +//=============================================================== +// +// idVec2 - 2D vector +// +//=============================================================== + +class idVec2 { +public: + float x; + float y; + + idVec2( void ); + explicit idVec2( const float x, const float y ); + + void Set( const float x, const float y ); + void Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec2 operator-() const; + float operator*( const idVec2 &a ) const; + idVec2 operator*( const float a ) const; + idVec2 operator/( const float a ) const; + idVec2 operator+( const idVec2 &a ) const; + idVec2 operator-( const idVec2 &a ) const; + idVec2 & operator+=( const idVec2 &a ); + idVec2 & operator-=( const idVec2 &a ); + idVec2 & operator/=( const idVec2 &a ); + idVec2 & operator/=( const float a ); + idVec2 & operator*=( const float a ); + + friend idVec2 operator*( const float a, const idVec2 b ); + + bool Compare( const idVec2 &a ) const; // exact compare, no epsilon + bool Compare( const idVec2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec2 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec2 &a ) const; // exact compare, no epsilon + +// RAVEN BEGIN +// ddynerman: vector normalization operator + idVec2 & operator~( void ); +// RAVEN END + + float Length( void ) const; + float LengthFast( void ) const; + float LengthSqr( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + idVec2 & Truncate( float length ); // cap length + void Clamp( const idVec2 &min, const idVec2 &max ); + void Snap( void ); // snap to closest integer value + void SnapInt( void ); // snap towards integer (floor) + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec2 &v1, const idVec2 &v2, const float l ); + +// RAVEN BEGIN +// jscott: Ensures second element greater than first + void EnsureIncremental( void ); +// RAVEN END +}; + +extern idVec2 vec2_origin; +#define vec2_zero vec2_origin + +ID_INLINE idVec2::idVec2( void ) { +} + +ID_INLINE idVec2::idVec2( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Set( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Zero( void ) { + x = y = 0.0f; +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) ); +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec2::operator==( const idVec2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec2::operator!=( const idVec2 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec2::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2::operator[]( int index ) { + return ( &x )[ index ]; +} + +// RAVEN BEGIN +// ddynerman: vector normalization +ID_INLINE idVec2& idVec2::operator~( void ) { + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec2::Length( void ) const { + return ( float )idMath::Sqrt( x * x + y * y ); +} + +ID_INLINE float idVec2::LengthFast( void ) const { + float sqrLength; + + sqrLength = x * x + y * y; + return sqrLength * idMath::RSqrt( sqrLength ); +} + +ID_INLINE float idVec2::LengthSqr( void ) const { + return ( x * x + y * y ); +} + +ID_INLINE float idVec2::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec2::NormalizeFast( void ) { + float lengthSqr, invLength; + + lengthSqr = x * x + y * y; + invLength = idMath::RSqrt( lengthSqr ); + x *= invLength; + y *= invLength; + return invLength * lengthSqr; +} + +ID_INLINE idVec2 &idVec2::Truncate( float length ) { + float length2; + float ilength; + + if ( !length ) { + Zero(); + } + else { + length2 = LengthSqr(); + if ( length2 > length * length ) { + ilength = length * idMath::InvSqrt( length2 ); + x *= ilength; + y *= ilength; + } + } + + return *this; +} + +ID_INLINE void idVec2::Clamp( const idVec2 &min, const idVec2 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } +} + +ID_INLINE void idVec2::Snap( void ) { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); +} + +ID_INLINE void idVec2::SnapInt( void ) { + x = float( int( x ) ); + y = float( int( y ) ); +} + +ID_INLINE idVec2 idVec2::operator-() const { + return idVec2( -x, -y ); +} + +ID_INLINE idVec2 idVec2::operator-( const idVec2 &a ) const { + return idVec2( x - a.x, y - a.y ); +} + +ID_INLINE float idVec2::operator*( const idVec2 &a ) const { + return x * a.x + y * a.y; +} + +ID_INLINE idVec2 idVec2::operator*( const float a ) const { + return idVec2( x * a, y * a ); +} + +ID_INLINE idVec2 idVec2::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec2( x * inva, y * inva ); +} + +ID_INLINE idVec2 operator*( const float a, const idVec2 b ) { + return idVec2( b.x * a, b.y * a ); +} + +ID_INLINE idVec2 idVec2::operator+( const idVec2 &a ) const { + return idVec2( x + a.x, y + a.y ); +} + +ID_INLINE idVec2 &idVec2::operator+=( const idVec2 &a ) { + x += a.x; + y += a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const idVec2 &a ) { + x /= a.x; + y /= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator-=( const idVec2 &a ) { + x -= a.x; + y -= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator*=( const float a ) { + x *= a; + y *= a; + + return *this; +} + +ID_INLINE int idVec2::GetDimension( void ) const { + return 2; +} + +ID_INLINE const float *idVec2::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec2::ToFloatPtr( void ) { + return &x; +} + +// RAVEN BEGIN +// jscott: ensures the second element is greater than the first +ID_INLINE void idVec2::EnsureIncremental( void ) +{ + float temp; + + if( x < y ) + { + return; + } + temp = x; + x = y; + y = temp; +} +// RAVEN END + +//=============================================================== +// +// idVec3 - 3D vector +// +//=============================================================== + +class idVec3 { +public: + float x; + float y; + float z; + + idVec3( void ); + explicit idVec3( const float x, const float y, const float z ); + + void Set( const float x, const float y, const float z ); + void Zero( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec3 operator-() const; + idVec3 & operator=( const idVec3 &a ); // required because of a msvc 6 & 7 bug +// RAVEN BEGIN +// bdube: added vec2 equal + idVec3 & operator=( const idVec2 &a ); + idVec3 & operator*=( const idVec3 &a ); +// RAVEN END + float operator*( const idVec3 &a ) const; + idVec3 operator*( const float a ) const; + idVec3 operator/( const float a ) const; + idVec3 operator+( const idVec3 &a ) const; + idVec3 operator-( const idVec3 &a ) const; + idVec3 & operator+=( const idVec3 &a ); + idVec3 & operator-=( const idVec3 &a ); + idVec3 & operator/=( const idVec3 &a ); + idVec3 & operator/=( const float a ); + idVec3 & operator*=( const float a ); + + friend idVec3 operator*( const float a, const idVec3 b ); + + bool Compare( const idVec3 &a ) const; // exact compare, no epsilon + bool Compare( const idVec3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec3 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec3 &a ) const; // exact compare, no epsilon + + bool FixDegenerateNormal( void ); // fix degenerate axial cases + bool FixDenormals( void ); // change tiny numbers to zero + + idVec3 Cross( const idVec3 &a ) const; + idVec3 & Cross( const idVec3 &a, const idVec3 &b ); + +// RAVEN BEGIN +// ddynerman: vector normalization operator + idVec3 & operator~( void ); +// RAVEN END + + float Length( void ) const; + float LengthSqr( void ) const; + float LengthFast( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + idVec3 & Truncate( float length ); // cap length + void Clamp( const idVec3 &min, const idVec3 &max ); + void ClampMin( const float &minx, const float &miny, const float &minz ); + void Snap( void ); // snap to closest integer value + void SnapInt( void ); // snap towards integer (floor) + + int GetDimension( void ) const; + + float ToYaw( void ) const; + float ToPitch( void ) const; + idAngles ToAngles( void ) const; + idPolar3 ToPolar( void ) const; + idMat3 ToMat3( void ) const; // vector should be normalized + const idVec2 & ToVec2( void ) const; + idVec2 & ToVec2( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +// RAVEN BEGIN +// abahr: added axis so we can create matrix with non-x vector + idMat3 ToMat3( int axis ) const; // vector should be normalized +// abahr: + idVec3 ToNormal() const { idVec3 v( *this ); v.Normalize(); return v; } + idVec3 Random( const idVec3& range, idRandom& random ) const; +// RAVEN END + + void NormalVectors( idVec3 &left, idVec3 &down ) const; // vector should be normalized + void OrthogonalBasis( idVec3 &left, idVec3 &up ) const; + + void ProjectOntoPlane( const idVec3 &normal, const float overBounce = 1.0f ); + bool ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce = 1.0f ); + void ProjectSelfOntoSphere( const float radius ); + +// RAVEN BEGIN + float ProjectOntoVector(const idVec3 &U); +// RAVEN END + + +// RAVEN BEGIN +// cdr - Extremely useful Vector opterations for Computational Geometry + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const idVec3 &A, const idVec3 &B) const { + return ((A.x*B.y - A.y*B.x) + (B.x*y - x*B.y) + (x*A.y - A.x*y)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool IsLeftOf(const idVec3 &Start, const idVec3 &End) const { + return (AreaParallelPipid(Start, End)>0.0f); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line Segment + // + // Given a line segment (Start->End) this function will return the distance of the + // given point (this) to the segment. You must also pass in a Scale argument which + // will tell you: + // Scale (-INF, 0.0f) => (this) projected before (Start) + // Scale [0.0f, 1.0f] => (this) projected inside the segment (Start->End) + // Scale (1.0f, +INF) => (this) projected beyond (End) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + float DistToLineSeg(const idVec3 &Start, const idVec3 &End, float& Scale) const { + static idVec3 U; + static idVec3 V; + static float ULen2; + + V = (*this) - Start; // Compute V + U = End - Start; // Compute U + ULen2 = U.LengthSqr(); // Normalize U + Scale = (V*U / ULen2); // Find the scale of this vector on U + if (Scale<0.0f) {return Dist(Start);} // If Negative Scale, Projected In Front Of Start + if (Scale>1.0f) {return Dist(End);} // If Positive Scale, Projected In Beyond End + return Dist(Start + (U*Scale)); // Otherwise, project U to new location and return dist + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project On To Line Segment + // + // Given a line segment (Start->End) this function will project the point onto the + // segment. + // + // (this) ___---(End) + // \ ---/ + // __ (Result) + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool ProjectToLineSeg(const idVec3 &Start, const idVec3 &End) { + static idVec3 U; + static idVec3 V; + static float ULen2; + static float Scale; + + V = (*this) - Start; // Compute V + U = End - Start; // Compute U + ULen2 = U.LengthSqr(); // Normalize U + Scale = (V*U / ULen2); // Find the scale of this vector on U + + if (Scale<0.0f) { + (*this) = Start; // If Negative Scale, Projected In Front Of Start + return false; // Off The End + } else if (Scale>1.0f) { + (*this) = End; // If Positive Scale, Projected In Beyond End + return false; // Off The End + } else { + (*this) = Start + (U*Scale); // Otherwise, project U to new location and return dist + } + return true; // Perpendicular Intersection Is On The Segment + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance Squared (Much Faster than Dist) + //////////////////////////////////////////////////////////////////////////////////// + float Dist2(const idVec3& Pt) const + { + return ((Pt.x-x)*(Pt.x-x) + (Pt.y-y)*(Pt.y-y) + (Pt.z-z)*(Pt.z-z)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance Squared OnlyXY + //////////////////////////////////////////////////////////////////////////////////// + float Dist2XY(const idVec3& Pt) const + { + return ((Pt.x-x)*(Pt.x-x) + (Pt.y-y)*(Pt.y-y)); + } + //////////////////////////////////////////////////////////////////////////////////// + // Distance OnlyXY + //////////////////////////////////////////////////////////////////////////////////// + float DistXY(const idVec3& Pt) const + { + idVec3 delta(x,y,Pt.z); + delta = delta - Pt; + return delta.LengthFast(); + } +// RAVEN END + + void Lerp( const idVec3 &v1, const idVec3 &v2, const float l ); + void SLerp( const idVec3 &v1, const idVec3 &v2, const float l ); + +// RAVEN BEGIN +// jscott: Ensures second element greater than first + void EnsureIncremental( void ); +// jscott: for BSE + int GetLargestAxis( void ) const; +// jscott: for rvAngles + rvAngles ToRadians( void ) const; + idMat3 &ToMat3( idMat3 &mat ) const; // vector should be normalized + float Dist(const idVec3 &Pt) const + { + idVec3 delta(x,y,z); + delta = delta - Pt; + return delta.LengthFast(); + } + +// RAVEN END + + bool IsZero( void ) const; +}; + +extern idVec3 vec3_origin; +#define vec3_zero vec3_origin + +ID_INLINE idVec3::idVec3( void ) { +} + +ID_INLINE idVec3::idVec3( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idVec3::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE void idVec3::Set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idVec3::Zero( void ) { + x = y = z = 0.0f; +} + +ID_INLINE idVec3 idVec3::operator-() const { + return idVec3( -x, -y, -z ); +} + +ID_INLINE idVec3 &idVec3::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + return *this; +} + +// RAVEN BEGIN +// bdube: added vec3 from vec2 assignment +ID_INLINE idVec3 &idVec3::operator=( const idVec2 &a ) { + x = a.x; + y = a.y; + return *this; +} +ID_INLINE idVec3 &idVec3::operator*=( const idVec3 &a ) +{ + x *= a.x; + y *= a.y; + z *= a.z; + return *this; +} +// RAVEN END + +ID_INLINE idVec3 idVec3::operator-( const idVec3 &a ) const { + return idVec3( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3::operator*( const idVec3 &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3 idVec3::operator*( const float a ) const { + return idVec3( x * a, y * a, z * a ); +} + +ID_INLINE idVec3 idVec3::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec3( x * inva, y * inva, z * inva ); +} + +ID_INLINE idVec3 operator*( const float a, const idVec3 b ) { + return idVec3( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3 idVec3::operator+( const idVec3 &a ) const { + return idVec3( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3 &idVec3::operator+=( const idVec3 &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const idVec3 &a ) { + x /= a.x; + y /= a.y; + z /= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator-=( const idVec3 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec3::operator==( const idVec3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec3::operator!=( const idVec3 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec3::NormalizeFast( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = idMath::RSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE bool idVec3::FixDegenerateNormal( void ) { + if ( x == 0.0f ) { + if ( y == 0.0f ) { + if ( z > 0.0f ) { + if ( z != 1.0f ) { + z = 1.0f; + return true; + } + } else { + if ( z != -1.0f ) { + z = -1.0f; + return true; + } + } + return false; + } else if ( z == 0.0f ) { + if ( y > 0.0f ) { + if ( y != 1.0f ) { + y = 1.0f; + return true; + } + } else { + if ( y != -1.0f ) { + y = -1.0f; + return true; + } + } + return false; + } + } else if ( y == 0.0f ) { + if ( z == 0.0f ) { + if ( x > 0.0f ) { + if ( x != 1.0f ) { + x = 1.0f; + return true; + } + } else { + if ( x != -1.0f ) { + x = -1.0f; + return true; + } + } + return false; + } + } + if ( idMath::Fabs( x ) == 1.0f ) { + if ( y != 0.0f || z != 0.0f ) { + y = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( y ) == 1.0f ) { + if ( x != 0.0f || z != 0.0f ) { + x = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( z ) == 1.0f ) { + if ( x != 0.0f || y != 0.0f ) { + x = y = 0.0f; + return true; + } + return false; + } + return false; +} + +ID_INLINE bool idVec3::FixDenormals( void ) { + bool denormal = false; + if ( fabs( x ) < 1e-30f ) { + x = 0.0f; + denormal = true; + } + if ( fabs( y ) < 1e-30f ) { + y = 0.0f; + denormal = true; + } + if ( fabs( z ) < 1e-30f ) { + z = 0.0f; + denormal = true; + } + return denormal; +} + +ID_INLINE idVec3 idVec3::Cross( const idVec3 &a ) const { + return idVec3( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3 &idVec3::Cross( const idVec3 &a, const idVec3 &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +// RAVEN BEGIN +// ddynerman: vector normalization operator +ID_INLINE idVec3& idVec3::operator~( void ) { + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec3::Length( void ) const { + return ( float )idMath::Sqrt( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthSqr( void ) const { + return ( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthFast( void ) const { + float sqrLength; + + sqrLength = x * x + y * y + z * z; + return sqrLength * idMath::RSqrt( sqrLength ); +} + +ID_INLINE float idVec3::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; +// RAVEN BEGIN +// jscott: fixed degenerate case + if ( !sqrLength ) { + return 0.0f; + } +// RAVEN END + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE idVec3 &idVec3::Truncate( float length ) { + float length2; + float ilength; + + if ( !length ) { + Zero(); + } + else { + length2 = LengthSqr(); + if ( length2 > length * length ) { + ilength = length * idMath::InvSqrt( length2 ); + x *= ilength; + y *= ilength; + z *= ilength; + } + } + + return *this; +} + +ID_INLINE void idVec3::Clamp( const idVec3 &min, const idVec3 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } + if ( z < min.z ) { + z = min.z; + } else if ( z > max.z ) { + z = max.z; + } +} + +ID_INLINE void idVec3::ClampMin( const float &minx, const float &miny, const float &minz ) { + if ( x < minx ) { + x = minx; + } + if ( y < miny ) { + y = miny; + } + if ( z < minz ) { + z = minz; + } +} + +ID_INLINE void idVec3::Snap( void ) { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); + z = floor( z + 0.5f ); +} + +ID_INLINE void idVec3::SnapInt( void ) { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +ID_INLINE int idVec3::GetDimension( void ) const { + return 3; +} + +ID_INLINE const idVec2 &idVec3::ToVec2( void ) const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec3::ToVec2( void ) { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec3::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec3::ToFloatPtr( void ) { + return &x; +} + +ID_INLINE void idVec3::NormalVectors( idVec3 &left, idVec3 &down ) const { + float d; + + d = x * x + y * y; + if ( !d ) { + left[0] = 1; + left[1] = 0; + left[2] = 0; + } else { + d = idMath::InvSqrt( d ); + left[0] = -y * d; + left[1] = x * d; + left[2] = 0; + } + down = left.Cross( *this ); +} + +ID_INLINE void idVec3::OrthogonalBasis( idVec3 &left, idVec3 &up ) const { + float l, s; + + if ( idMath::Fabs( z ) > 0.7f ) { + l = y * y + z * z; + s = idMath::InvSqrt( l ); + up[0] = 0; + up[1] = z * s; + up[2] = -y * s; + left[0] = l * s; + left[1] = -x * up[2]; + left[2] = x * up[1]; + } + else { + l = x * x + y * y; + s = idMath::InvSqrt( l ); + left[0] = -y * s; + left[1] = x * s; + left[2] = 0; + up[0] = -z * left[1]; + up[1] = z * left[0]; + up[2] = l * s; + } +} + +ID_INLINE void idVec3::ProjectOntoPlane( const idVec3 &normal, const float overBounce ) { + float backoff; + + backoff = *this * normal; + + if ( overBounce != 1.0 ) { + if ( backoff < 0 ) { + backoff *= overBounce; + } else { + backoff /= overBounce; + } + } + + *this -= backoff * normal; +} + +// RAVEN BEGIN +//////////////////////////////////////////////////////////////////////////////////// +// Project +// +// Standard projection function. Take the (this) and project it onto the vector +// (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) +// Vector. That then becomes the new vector. +// +// The value returned is the scale of the new vector with respect to the one passed +// to the function. If the scale is less than (1.0) then the new vector is shorter +// than (U). If the scale is negative, then the vector is going in the opposite +// direction of (U). +// +// _ (U) +// /| +// / _ (this) +// / RESULTS-> /| +// / / +// / __\ (this) / +// /___--- / / +// +//////////////////////////////////////////////////////////////////////////////////// +ID_INLINE float idVec3::ProjectOntoVector(const idVec3 &U) +{ + float Scale = ((*this)*(U) / U.LengthSqr()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; +} +// RAVEN END + + +ID_INLINE bool idVec3::ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce ) { + idVec3 cross; + float len; + + cross = this->Cross( normal ).Cross( (*this) ); + // normalize so a fixed epsilon can be used + cross.Normalize(); + len = normal * cross; + if ( idMath::Fabs( len ) < epsilon ) { + return false; + } + cross *= overBounce * ( normal * (*this) ) / len; + (*this) -= cross; + return true; +} + +// RAVEN BEGIN +// jscott: ensures the second element is greater than the first and that the third is greater than the 2nd +ID_INLINE void idVec3::EnsureIncremental( void ) +{ + float temp; + + if ( y < x ) + { + temp = x; + x = y; + y = temp; + } + + if ( z < y ) + { + temp = y; + y = z; + z = temp; + } + + if ( y < x ) + { + temp = x; + x = y; + y = temp; + } +} + +ID_INLINE int idVec3::GetLargestAxis( void ) const +{ + float a = fabs( x ); + float b = fabs( y ); + float c = fabs( z ); + + if( a >= b && a >= c ) + { + return( 0 ); + } + if( b >= a && b >= c ) + { + return( 1 ); + } + if( c >= a && c >= b ) + { + return( 2 ); + } + return( 0 ); +} + +// abahr +ID_INLINE idVec3 idVec3::Random( const idVec3& range, idRandom& random ) const { + idVec3 v( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + v[ ix ] += v[ ix ] * range[ix] * random.CRandomFloat(); + } + return v; +} +// RAVEN END + +ID_INLINE bool idVec3::IsZero( void ) const { + return ( ( ( *( const unsigned long * ) &( x ) ) | ( *( const unsigned long * ) &( y ) ) | ( *( const unsigned long * ) &( z ) ) ) & ~( 1<<31 ) ) == 0; +} + +//=============================================================== +// +// idVec4 - 4D vector +// +//=============================================================== +#ifdef _XENON +#else +class idVec4 { +#endif +public: +#ifndef _XENON + float x; + float y; + float z; + float w; +#else +#endif + ID_INLINE idVec4( void ); + ID_INLINE explicit idVec4( const float x, const float y, const float z, const float w ); + + ID_INLINE void Set( const float x, const float y, const float z, const float w ); + ID_INLINE void Zero( void ); + + ID_INLINE float operator[]( const int index ) const; + ID_INLINE float & operator[]( const int index ); + ID_INLINE idVec4 operator-() const; + ID_INLINE float operator*( const idVec4 &a ) const; + ID_INLINE idVec4 operator*( const float a ) const; + ID_INLINE idVec4 operator/( const float a ) const; + ID_INLINE idVec4 operator+( const idVec4 &a ) const; + ID_INLINE idVec4 operator-( const idVec4 &a ) const; + ID_INLINE idVec4 & operator+=( const idVec4 &a ); + ID_INLINE idVec4 & operator-=( const idVec4 &a ); + ID_INLINE idVec4 & operator/=( const idVec4 &a ); + ID_INLINE idVec4 & operator/=( const float a ); + ID_INLINE idVec4 & operator*=( const float a ); + + ID_INLINE friend idVec4 operator*( const float a, const idVec4 b ); + +// RAVEN BEGIN +// jscott: optimised vector to byte array function + ID_INLINE operator int ( void ); +// RAVEN END + + ID_INLINE bool Compare( const idVec4 &a ) const; // exact compare, no epsilon + ID_INLINE bool Compare( const idVec4 &a, const float epsilon ) const; // compare with epsilon + ID_INLINE bool operator==( const idVec4 &a ) const; // exact compare, no epsilon + ID_INLINE bool operator!=( const idVec4 &a ) const; // exact compare, no epsilon + +// RAVEN BEGIN +// ddynerman: unary normalization operator + ID_INLINE idVec4& operator~( void ); +// RAVEN END + + ID_INLINE float Length( void ) const; + ID_INLINE float LengthSqr( void ) const; + ID_INLINE float Normalize( void ); // returns length + ID_INLINE float NormalizeFast( void ); // returns length + + ID_INLINE int GetDimension( void ) const; + + ID_INLINE const idVec2 & ToVec2( void ) const; + ID_INLINE idVec2 & ToVec2( void ); + ID_INLINE const idVec3 & ToVec3( void ) const; + ID_INLINE idVec3 & ToVec3( void ); + ID_INLINE const float * ToFloatPtr( void ) const; + ID_INLINE float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + ID_INLINE void Lerp( const idVec4 &v1, const idVec4 &v2, const float l ); + +}; + +extern idVec4 vec4_origin; +extern idVec4 vec4_one; + +#define vec4_zero vec4_origin + +#ifndef _XENON +ID_INLINE idVec4::idVec4( void ) { + AlignmentChecker::UpdateCount(&x); +} + +ID_INLINE idVec4::idVec4( const float x, const float y, const float z, const float w ) { + AlignmentChecker::UpdateCount(&x); + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4::Set( const float x, const float y, const float z, const float w ) { + AlignmentChecker::UpdateCount(&x); + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4::Zero( void ) { + AlignmentChecker::UpdateCount(&x); + x = y = z = w = 0.0f; +} + +ID_INLINE float idVec4::operator[]( int index ) const { + AlignmentChecker::UpdateCount(&x); + return ( &x )[ index ]; +} + +ID_INLINE float& idVec4::operator[]( int index ) { + AlignmentChecker::UpdateCount(&x); + return ( &x )[ index ]; +} + +ID_INLINE idVec4 idVec4::operator-() const { + AlignmentChecker::UpdateCount(&x); + return idVec4( -x, -y, -z, -w ); +} + +ID_INLINE idVec4 idVec4::operator-( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return idVec4( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE float idVec4::operator*( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return x * a.x + y * a.y + z * a.z + w * a.w; +} + +ID_INLINE idVec4 idVec4::operator*( const float a ) const { + AlignmentChecker::UpdateCount(&x); + return idVec4( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idVec4 idVec4::operator/( const float a ) const { + AlignmentChecker::UpdateCount(&x); + float inva = 1.0f / a; + return idVec4( x * inva, y * inva, z * inva, w * inva ); +} + +ID_INLINE idVec4 operator*( const float a, const idVec4 b ) { + AlignmentChecker::UpdateCount(&b.x); + return idVec4( b.x * a, b.y * a, b.z * a, b.w * a ); +} + + +ID_INLINE idVec4 idVec4::operator+( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return idVec4( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idVec4 &idVec4::operator+=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const float a ) { + AlignmentChecker::UpdateCount(&x); + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + w *= inva; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator-=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator*=( const float a ) { + AlignmentChecker::UpdateCount(&x); + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +// RAVEN BEGIN +// jscott: Opt float array to byte array conversion +#ifdef _WINDOWS +ID_INLINE idVec4::operator int( void ) +{ + int tmp, retval; + float constant = 255.0f; + + _asm + { + push edx + mov edx, this + fld dword ptr[edx + 0] + fld dword ptr[edx + 4] + fld dword ptr[edx + 8] + fld dword ptr[edx + 12] + + fmul [constant] + fistp tmp + mov ah, byte ptr [tmp] + + fmul [constant] + fistp tmp + mov al, byte ptr [tmp] + + shl eax, 16 + + fmul [constant] + fistp tmp + mov ah, byte ptr [tmp] + + fmul [constant] + fistp tmp + mov al, byte ptr [tmp] + + mov [retval], eax + pop edx + } + return( retval ); +} +#else +ID_INLINE idVec4::operator int( void ) +{ + AlignmentChecker::UpdateCount(&x); + int retval; +// RAVEN BEGIN +// jnewquist: byte ordering depends on platform +#ifdef _LITTLE_ENDIAN + retval = ( ( int )( x * 255.0f ) ) << 0; + retval |= ( ( int )( y * 255.0f ) ) << 8; + retval |= ( ( int )( z * 255.0f ) ) << 16; + retval |= ( ( int )( w * 255.0f ) ) << 24; +#else + retval = ( ( int )( x * 255.0f ) ) << 24; + retval |= ( ( int )( y * 255.0f ) ) << 16; + retval |= ( ( int )( z * 255.0f ) ) << 8; + retval |= ( ( int )( w * 255.0f ) ) << 0; +#endif +// RAVEN END + + return( retval ); +} +#endif +// RAVEN END + +ID_INLINE bool idVec4::Compare( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && w == a.w ); +} + +ID_INLINE bool idVec4::Compare( const idVec4 &a, const float epsilon ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec4::operator==( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return Compare( a ); +} + +ID_INLINE bool idVec4::operator!=( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return !Compare( a ); +} + +// RAVEN BEGIN +// ddynerman: unary normalization operator +ID_INLINE idVec4& idVec4::operator~( void ) { + AlignmentChecker::UpdateCount(&x); + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec4::Length( void ) const { + AlignmentChecker::UpdateCount(&x); + return ( float )idMath::Sqrt( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::LengthSqr( void ) const { + AlignmentChecker::UpdateCount(&x); + return ( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::Normalize( void ) { + AlignmentChecker::UpdateCount(&x); + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec4::NormalizeFast( void ) { + AlignmentChecker::UpdateCount(&x); + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::RSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec4::GetDimension( void ) const { + AlignmentChecker::UpdateCount(&x); + return 4; +} + +ID_INLINE const idVec2 &idVec4::ToVec2( void ) const { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec4::ToVec2( void ) { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE const idVec3 &idVec4::ToVec3( void ) const { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec4::ToVec3( void ) { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec4::ToFloatPtr( void ) const { + AlignmentChecker::UpdateCount(&x); + return &x; +} + +ID_INLINE float *idVec4::ToFloatPtr( void ) { + AlignmentChecker::UpdateCount(&x); + return &x; +} + +ID_INLINE void idVec4::Lerp( const idVec4 &v1, const idVec4 &v2, const float l ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&v1.x); + AlignmentChecker::UpdateCount(&v2.x); + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} +#endif + +//=============================================================== +// +// idVec5 - 5D vector +// +//=============================================================== + +class idVec5 { +public: + float x; + float y; + float z; + float s; + float t; + + idVec5( void ); + explicit idVec5( const idVec3 &xyz, const idVec2 &st ); + explicit idVec5( const float x, const float y, const float z, const float s, const float t ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec5 & operator=( const idVec3 &a ); + + int GetDimension( void ) const; + + const idVec3 & ToVec3( void ) const; + idVec3 & ToVec3( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec5 &v1, const idVec5 &v2, const float l ); +}; + +extern idVec5 vec5_origin; +#define vec5_zero vec5_origin + +ID_INLINE idVec5::idVec5( void ) { +} + +ID_INLINE idVec5::idVec5( const idVec3 &xyz, const idVec2 &st ) { + x = xyz.x; + y = xyz.y; + z = xyz.z; + s = st[0]; + t = st[1]; +} + +ID_INLINE idVec5::idVec5( const float x, const float y, const float z, const float s, const float t ) { + this->x = x; + this->y = y; + this->z = z; + this->s = s; + this->t = t; +} + +ID_INLINE float idVec5::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec5 &idVec5::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + s = t = 0; + return *this; +} + +ID_INLINE int idVec5::GetDimension( void ) const { + return 5; +} + +ID_INLINE const idVec3 &idVec5::ToVec3( void ) const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec5::ToVec3( void ) { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec5::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec5::ToFloatPtr( void ) { + return &x; +} + + +//=============================================================== +// +// idVec6 - 6D vector +// +//=============================================================== + +class idVec6 { +public: + idVec6( void ); + explicit idVec6( const float *a ); + explicit idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec6 operator-() const; + idVec6 operator*( const float a ) const; + idVec6 operator/( const float a ) const; + float operator*( const idVec6 &a ) const; + idVec6 operator-( const idVec6 &a ) const; + idVec6 operator+( const idVec6 &a ) const; + idVec6 & operator*=( const float a ); + idVec6 & operator/=( const float a ); + idVec6 & operator+=( const idVec6 &a ); + idVec6 & operator-=( const idVec6 &a ); + + friend idVec6 operator*( const float a, const idVec6 b ); + + bool Compare( const idVec6 &a ) const; // exact compare, no epsilon + bool Compare( const idVec6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec6 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec6 &a ) const; // exact compare, no epsilon + + float Length( void ) const; + float LengthSqr( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + + int GetDimension( void ) const; + + const idVec3 & SubVec3( int index ) const; + idVec3 & SubVec3( int index ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idVec6 vec6_origin; +#define vec6_zero vec6_origin +extern idVec6 vec6_infinity; + +ID_INLINE idVec6::idVec6( void ) { +} + +ID_INLINE idVec6::idVec6( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idVec6::idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idVec6 idVec6::operator-() const { + return idVec6( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idVec6::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idVec6::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idVec6 idVec6::operator*( const float a ) const { + return idVec6( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idVec6::operator*( const idVec6 &a ) const { + return p[0] * a[0] + p[1] * a[1] + p[2] * a[2] + p[3] * a[3] + p[4] * a[4] + p[5] * a[5]; +} + +ID_INLINE idVec6 idVec6::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idVec6( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idVec6 idVec6::operator+( const idVec6 &a ) const { + return idVec6( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idVec6 idVec6::operator-( const idVec6 &a ) const { + return idVec6( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idVec6 &idVec6::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator+=( const idVec6 &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator-=( const idVec6 &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE idVec6 operator*( const float a, const idVec6 b ) { + return b * a; +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec6::operator==( const idVec6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec6::operator!=( const idVec6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idVec6::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idVec6::Zero( void ) { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE float idVec6::Length( void ) const { + return ( float )idMath::Sqrt( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::LengthSqr( void ) const { + return ( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::InvSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec6::NormalizeFast( void ) { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::RSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec6::GetDimension( void ) const { + return 6; +} + +ID_INLINE const idVec3 &idVec6::SubVec3( int index ) const { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE idVec3 &idVec6::SubVec3( int index ) { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE const float *idVec6::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idVec6::ToFloatPtr( void ) { + return p; +} + + +//=============================================================== +// +// idVecX - arbitrary sized vector +// +// The vector lives on 16 byte aligned and 16 byte padded memory. +// +// NOTE: due to the temporary memory pool idVecX cannot be used by multiple threads +// +//=============================================================== + +#define VECX_MAX_TEMP 1024 +#define VECX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define VECX_CLEAREND() int s = size; while( s < ( ( s + 3) & ~3 ) ) { p[s++] = 0.0f; } +#define VECX_ALLOCA( n ) ( (float *) _alloca16( VECX_QUAD( n ) ) ) +#define VECX_SIMD + +// RAVEN BEGIN +// jsinger: this is broken at the moment because idSIMDProcessor is no longer virtual +#ifdef _XENON +#undef VECX_SIMD +#endif +// RAVEN END + +class idVecX { + friend class idMatX; + +public: + idVecX( void ); + explicit idVecX( int length ); + explicit idVecX( int length, float *data ); + ~idVecX( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVecX operator-() const; + idVecX & operator=( const idVecX &a ); + idVecX operator*( const float a ) const; + idVecX operator/( const float a ) const; + float operator*( const idVecX &a ) const; + idVecX operator-( const idVecX &a ) const; + idVecX operator+( const idVecX &a ) const; + idVecX & operator*=( const float a ); + idVecX & operator/=( const float a ); + idVecX & operator+=( const idVecX &a ); + idVecX & operator-=( const idVecX &a ); + + friend idVecX operator*( const float a, const idVecX b ); + + bool Compare( const idVecX &a ) const; // exact compare, no epsilon + bool Compare( const idVecX &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVecX &a ) const; // exact compare, no epsilon + bool operator!=( const idVecX &a ) const; // exact compare, no epsilon + + void SetSize( int size ); + void ChangeSize( int size, bool makeZero = false ); + int GetSize( void ) const { return size; } + void SetData( int length, float *data ); + void Zero( void ); + void Zero( int length ); + void Random( int seed, float l = 0.0f, float u = 1.0f ); + void Random( int length, int seed, float l = 0.0f, float u = 1.0f ); + void Negate( void ); + void Clamp( float min, float max ); + idVecX & SwapElements( int e1, int e2 ); + + float Length( void ) const; + float LengthSqr( void ) const; + idVecX Normalize( void ) const; + float NormalizeSelf( void ); + + int GetDimension( void ) const; + + const idVec3 & SubVec3( int index ) const; + idVec3 & SubVec3( int index ); + const idVec6 & SubVec6( int index ) const; + idVec6 & SubVec6( int index ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + int size; // size of the vector + int alloced; // if -1 p points to data set with SetData + float * p; // memory the vector is stored + +// RAVEN BEGIN + ALIGN16( static float tempPtr[VECX_MAX_TEMP] ); // used to store intermediate results +// static float * tempPtr; // pointer to 16 byte aligned temporary memory +// RAVEN END + static int tempIndex; // index into memory pool, wraps around + +private: + void SetTempSize( int size ); +}; + + +ID_INLINE idVecX::idVecX( void ) { + size = alloced = 0; + p = NULL; +} + +ID_INLINE idVecX::idVecX( int length ) { + size = alloced = 0; + p = NULL; + SetSize( length ); +} + +ID_INLINE idVecX::idVecX( int length, float *data ) { + size = alloced = 0; + p = NULL; + SetData( length, data ); +} + +ID_INLINE idVecX::~idVecX( void ) { + // if not temp memory + if ( p && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } +} + +ID_INLINE float idVecX::operator[]( const int index ) const { + assert( index >= 0 && index < size ); + return p[index]; +} + +ID_INLINE float &idVecX::operator[]( const int index ) { + assert( index >= 0 && index < size ); + return p[index]; +} + +ID_INLINE idVecX idVecX::operator-() const { + int i; + idVecX m; + + m.SetTempSize( size ); + for ( i = 0; i < size; i++ ) { + m.p[i] = -p[i]; + } + return m; +} + +ID_INLINE idVecX &idVecX::operator=( const idVecX &a ) { + SetSize( a.size ); +#ifdef VECX_SIMD + SIMDProcessor->Copy16( p, a.p, a.size ); +#else + memcpy( p, a.p, a.size * sizeof( float ) ); +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX idVecX::operator+( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Add16( m.p, p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] + a.p[i]; + } +#endif + return m; +} + +ID_INLINE idVecX idVecX::operator-( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Sub16( m.p, p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] - a.p[i]; + } +#endif + return m; +} + +ID_INLINE idVecX &idVecX::operator+=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + SIMDProcessor->AddAssign16( p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] += a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX &idVecX::operator-=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + SIMDProcessor->SubAssign16( p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] -= a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX idVecX::operator*( const float a ) const { + idVecX m; + + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Mul16( m.p, p, a, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] * a; + } +#endif + return m; +} + +ID_INLINE idVecX &idVecX::operator*=( const float a ) { +#ifdef VECX_SIMD + SIMDProcessor->MulAssign16( p, a, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] *= a; + } +#endif + return *this; +} + +ID_INLINE idVecX idVecX::operator/( const float a ) const { + assert( a != 0.0f ); + return (*this) * ( 1.0f / a ); +} + +ID_INLINE idVecX &idVecX::operator/=( const float a ) { + assert( a != 0.0f ); + (*this) *= ( 1.0f / a ); + return *this; +} + +ID_INLINE idVecX operator*( const float a, const idVecX b ) { + return b * a; +} + +ID_INLINE float idVecX::operator*( const idVecX &a ) const { + int i; + float sum = 0.0f; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + sum += p[i] * a.p[i]; + } + return sum; +} + +ID_INLINE bool idVecX::Compare( const idVecX &a ) const { + int i; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + if ( p[i] != a.p[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idVecX::Compare( const idVecX &a, const float epsilon ) const { + int i; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + if ( idMath::Fabs( p[i] - a.p[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idVecX::operator==( const idVecX &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVecX::operator!=( const idVecX &a ) const { + return !Compare( a ); +} + +ID_INLINE void idVecX::SetSize( int newSize ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( p ) { + Mem_Free16( p ); + } +//RAVEN BEGIN +//amccarthy: Added allocation tag + p = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + } + size = newSize; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::ChangeSize( int newSize, bool makeZero ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldVec = p; +//RAVEN BEGIN +//amccarthy: Added allocation tag + p = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + if ( oldVec ) { + for ( int i = 0; i < size; i++ ) { + p[i] = oldVec[i]; + } + Mem_Free16( oldVec ); + } + if ( makeZero ) { + // zero any new elements + for ( int i = size; i < newSize; i++ ) { + p[i] = 0.0f; + } + } + } + size = newSize; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::SetTempSize( int newSize ) { + + size = newSize; + alloced = ( newSize + 3 ) & ~3; + assert( alloced < VECX_MAX_TEMP ); + if ( idVecX::tempIndex + alloced > VECX_MAX_TEMP ) { + idVecX::tempIndex = 0; + } + p = idVecX::tempPtr + idVecX::tempIndex; + idVecX::tempIndex += alloced; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::SetData( int length, float *data ) { + if ( p && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } + assert( ( ( (int) data ) & 15 ) == 0 ); // data must be 16 byte aligned + p = data; + size = length; + alloced = -1; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::Zero( void ) { +#ifdef VECX_SIMD + SIMDProcessor->Zero16( p, size ); +#else + memset( p, 0, size * sizeof( float ) ); +#endif +} + +ID_INLINE void idVecX::Zero( int length ) { + SetSize( length ); +#ifdef VECX_SIMD + SIMDProcessor->Zero16( p, length ); +#else + memset( p, 0, size * sizeof( float ) ); +#endif +} + +ID_INLINE void idVecX::Random( int seed, float l, float u ) { + int i; + float c; + idRandom rnd( seed ); + + c = u - l; + for ( i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idVecX::Random( int length, int seed, float l, float u ) { + int i; + float c; + idRandom rnd( seed ); + + SetSize( length ); + c = u - l; + for ( i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idVecX::Negate( void ) { +#ifdef VECX_SIMD + SIMDProcessor->Negate16( p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] = -p[i]; + } +#endif +} + +ID_INLINE void idVecX::Clamp( float min, float max ) { + int i; + for ( i = 0; i < size; i++ ) { + if ( p[i] < min ) { + p[i] = min; + } else if ( p[i] > max ) { + p[i] = max; + } + } +} + +ID_INLINE idVecX &idVecX::SwapElements( int e1, int e2 ) { + float tmp; + tmp = p[e1]; + p[e1] = p[e2]; + p[e2] = tmp; + return *this; +} + +ID_INLINE float idVecX::Length( void ) const { + int i; + float sum = 0.0f; + + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return idMath::Sqrt( sum ); +} + +ID_INLINE float idVecX::LengthSqr( void ) const { + int i; + float sum = 0.0f; + + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return sum; +} + +ID_INLINE idVecX idVecX::Normalize( void ) const { + int i; + idVecX m; + float invSqrt, sum = 0.0f; + + m.SetTempSize( size ); + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + invSqrt = idMath::InvSqrt( sum ); + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] * invSqrt; + } + return m; +} + +ID_INLINE float idVecX::NormalizeSelf( void ) { + float invSqrt, sum = 0.0f; + int i; + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + invSqrt = idMath::InvSqrt( sum ); + for ( i = 0; i < size; i++ ) { + p[i] *= invSqrt; + } + return invSqrt * sum; +} + +ID_INLINE int idVecX::GetDimension( void ) const { + return size; +} + +ID_INLINE idVec3 &idVecX::SubVec3( int index ) { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE const idVec3 &idVecX::SubVec3( int index ) const { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE idVec6 &idVecX::SubVec6( int index ) { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +ID_INLINE const idVec6 &idVecX::SubVec6( int index ) const { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +ID_INLINE const float *idVecX::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idVecX::ToFloatPtr( void ) { + return p; +} + + +//=============================================================== +// +// idPolar3 +// +//=============================================================== + +class idPolar3 { +public: + float radius, theta, phi; + + idPolar3( void ); + explicit idPolar3( const float radius, const float theta, const float phi ); + + void Set( const float radius, const float theta, const float phi ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPolar3 operator-() const; + idPolar3 & operator=( const idPolar3 &a ); + + idVec3 ToVec3( void ) const; +}; + +ID_INLINE idPolar3::idPolar3( void ) { +} + +ID_INLINE idPolar3::idPolar3( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE void idPolar3::Set( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE float idPolar3::operator[]( const int index ) const { + return ( &radius )[ index ]; +} + +ID_INLINE float &idPolar3::operator[]( const int index ) { + return ( &radius )[ index ]; +} + +ID_INLINE idPolar3 idPolar3::operator-() const { + return idPolar3( radius, -theta, -phi ); +} + +ID_INLINE idPolar3 &idPolar3::operator=( const idPolar3 &a ) { + radius = a.radius; + theta = a.theta; + phi = a.phi; + return *this; +} + +ID_INLINE idVec3 idPolar3::ToVec3( void ) const { + float sp, cp, st, ct; + idMath::SinCos( phi, sp, cp ); + idMath::SinCos( theta, st, ct ); + return idVec3( cp * radius * ct, cp * radius * st, radius * sp ); +} + + +/* +=============================================================================== + + Old 3D vector macros, should no longer be used. + +=============================================================================== +*/ + +#define DotProduct( a, b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define VectorSubtract( a, b, c ) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd( a, b, c ) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorScale( v, s, o ) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA( v, s, b, o ) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +#define VectorCopy( a, b ) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) + + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/source/idlib/precompiled.h b/source/idlib/precompiled.h new file mode 100644 index 0000000..ce5e8e5 --- /dev/null +++ b/source/idlib/precompiled.h @@ -0,0 +1,400 @@ + +#ifndef __PRECOMPILED_H__ +#define __PRECOMPILED_H__ + +#ifdef __cplusplus + +// RAVEN BEGIN +// nrausch: conditional cvar archive flag so that the pc build will archive certain cvars +#ifdef _XENON + +#undef _WINDOWS +#define PC_CVAR_ARCHIVE CVAR_NOCHEAT //so it doesn't clobber +#else +#define PC_CVAR_ARCHIVE CVAR_ARCHIVE +#endif +// RAVEN END +class ThreadedAlloc; // class that is only used to expand the AutoCrit template to tag allocs/frees called from inside the R_AddModelSurfaces call graph + + +//----------------------------------------------------- +// RAVEN BEGIN +// jscott: set up conditional compiles +#ifdef _DEBUG_MEMORY +#define ID_REDIRECT_NEWDELETE // Doesn't work with Radiant +#define ID_DEBUG_MEMORY +#endif + +#if defined( _FINAL ) && !defined( _MPBETA ) + #define ID_CONSOLE_LOCK +#endif + +#ifdef _WINDOWS + + // _WIN32 always defined + // _WIN64 also defined for x64 target + #if !defined( _WIN64 ) + #define ID_WIN_X86_ASM + #define ID_WIN_X86_MMX + #define ID_WIN_X86_SSE + //#define ID_WIN_X86_SSE2 + #endif + + // we should never rely on this define in our code. this is here so dodgy external libraries don't get confused + #ifndef WIN32 + #define WIN32 + #endif + + #undef _XBOX + #undef _CONSOLE // Used to comment out code that can't be used on a console + #define _OPENGL + #define _LITTLE_ENDIAN + #undef _CASE_SENSITIVE_FILESYSTEM + #define _USE_OPENAL + #define _USE_VOICECHAT + #define __WITH_PB__ + //#define _RV_MEM_SYS_SUPPORT + // when using the PC to make Xenon builds, enable _MD5R_SUPPORT / _MD5R_WRITE_SUPPORT and run with fs_game q4baseXenon + #ifdef Q4SDK + // the SDK can't be compiled with _MD5R_SUPPORT, but since the PC version is we need to maintain ABI + // to make things worse, only the windows version was compiled with _MD5R enabled, the Linux and Mac builds didn't + #define Q4SDK_MD5R + #else // Q4SDK + #define _MD5R_SUPPORT + #define _MD5R_WRITE_SUPPORT + #endif // !Q4SDK + #define _GLVAS_SUPPPORT + //#define RV_BINARYDECLS + #define RV_SINGLE_DECL_FILE + // this can't be used with _RV_MEM_SYS_SUPPORT and actually shouldn't be used at all on the Xenon at present + #if !defined(_RV_MEM_SYS_SUPPORT) && !defined(ID_REDIRECT_NEWDELETE) + #define RV_UNIFIED_ALLOCATOR + #endif + + // SMP support for running the backend on a 2nd thread + #define ENABLE_INTEL_SMP + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + + // Empty define for Xbox 360 compatibility + #define RESTRICT + #define TIME_THIS_SCOPE(x) + + #define NEWLINE "\r\n" + + #pragma warning( disable : 4100 ) // unreferenced formal parameter + #pragma warning( disable : 4127 ) // conditional expression is constant + #pragma warning( disable : 4201 ) // non standard extension nameless struct or union + #pragma warning( disable : 4244 ) // conversion to smaller type, possible loss of data + #pragma warning( disable : 4245 ) // signed/unsigned mismatch + #pragma warning( disable : 4389 ) // signed/unsigned mismatch + #pragma warning( disable : 4714 ) // function marked as __forceinline not inlined + #pragma warning( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + +#endif // _WINDOWS + +#ifdef __linux__ + +// for offsetof +#include +// FLT_MAX and such +#include +#include + + #define __WITH_PB__ + #undef WIN32 + #undef _XBOX + #undef _CONSOLE + #define _OPENGL + #define _LITTLE_ENDIAN + #define _CASE_SENSITIVE_FILESYSTEM + + #define NEWLINE "\n" + + #define _GLVAS_SUPPPORT + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + + #define RESTRICT + #define TIME_THIS_SCOPE(x) + + // we release both a non-SMP and an SMP binary for Linux + #ifdef ENABLE_INTEL_SMP + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + #endif + +#endif + +#ifdef MACOS_X + +// for offsetof +#include + +#include // for square root estimate instruction +#include +#include // for FLT_MIN + + // SMP support for running the backend on a 2nd thread +#ifndef ENABLE_INTEL_SMP + #define ENABLE_INTEL_SMP +#endif + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + + #define __WITH_PB__ + #undef WIN32 + #undef _XBOX + #undef _CONSOLE + #define _OPENGL +#ifdef __ppc__ + #undef _LITTLE_ENDIAN +#else + #define _LITTLE_ENDIAN +#endif + #define _CASE_SENSITIVE_FILESYSTEM + #define _USE_OPENAL + #define ID_INLINE inline + #define NEWLINE "\n" + + #define _GLVAS_SUPPPORT + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + + #define RESTRICT + #define TIME_THIS_SCOPE(x) +#endif + +#ifdef _WINDOWS + +#ifndef Q4SDK + +#if !defined( GAME_DLL ) && !defined( GAME_MONO ) + +#define _WIN32_WINNT 0x501 +#define WINVER 0x501 + +#ifdef ID_DEDICATED +// dedicated sets windows version here +#define WIN32_LEAN_AND_MEAN +#else +#ifdef TOOL_DLL +// non-dedicated includes MFC and sets windows verion here +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // prevent auto literal to string conversion +#include "../tools/comafx/StdAfx.h" +#endif // TOOL_DLL +#endif // ID_DEDICATED + +#include +#include +#include + +#define DIRECTINPUT_VERSION 0x0700 +#define DIRECTSOUND_VERSION 0x0800 + +#include "../mssdk/include/dsound.h" +#include "../mssdk/include/dinput.h" +#include "../mssdk/include/dxerr8.h" + +#endif // GAME_DLL +#endif // !Q4SDK + +#include // no malloc.h on mac or unix +#include // for qgl.h + +// RAVEN BEGIN +// bdube: for dual monitor support in tools +#ifndef GET_X_LPARAM +#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam)) +#endif +#ifndef GET_Y_LPARAM +#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam)) +#endif +// RAVEN END + +#undef FindText // stupid namespace poluting Microsoft monkeys + +#endif // _WINDOWS + +//----------------------------------------------------- + +#if !defined( _DEBUG ) && !defined( NDEBUG ) + // don't generate asserts + #define NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------- + +// non-portable system services +#include "../sys/sys_public.h" + +// id lib +#include "../idlib/Lib.h" + +#if !defined( Q4SDK ) && defined( __WITH_PB__ ) + #include "../punkbuster/pbcommon.h" +#endif + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +// jsinger: Serializable class support +#include "../serialization/Serializable.h" +#endif +// RAVEN END + +// framework +#include "../framework/BuildVersion.h" +#include "../framework/BuildDefines.h" +#include "../framework/licensee.h" +#include "../framework/CmdSystem.h" +#include "../framework/CVarSystem.h" +#include "../framework/Common.h" +#include "../framework/File.h" +#include "../framework/FileSystem.h" +#include "../framework/UsercmdGen.h" + +// decls +#include "../framework/declManager.h" +#include "../framework/declTable.h" +#include "../framework/declSkin.h" +#include "../framework/declEntityDef.h" +// RAVEN BEGIN +// jscott: not using +//#include "../framework/DeclFX.h" +//#include "../framework/DeclParticle.h" +// RAVEN END +#include "../framework/declAF.h" +#include "../framework/DeclPDA.h" +#include "../framework/DeclPlayerModel.h" +// RAVEN BEGIN +// jscott: new decl types +#include "../framework/declMatType.h" +#include "../framework/declLipSync.h" +#include "../framework/declPlayback.h" +// RAVEN END + +// We have expression parsing and evaluation code in multiple places: +// materials, sound shaders, and guis. We should unify them. +const int MAX_EXPRESSION_OPS = 4096; +const int MAX_EXPRESSION_REGISTERS = 4096; + +// Sanity check for any axis in bounds +const float MAX_BOUND_SIZE = 65536.0f; + +// renderer +#include "../renderer/qgl.h" +#include "../renderer/Cinematic.h" +#include "../renderer/Material.h" +#include "../renderer/Model.h" +#include "../renderer/ModelManager.h" +#include "../renderer/RenderSystem.h" +#include "../renderer/RenderWorld.h" + +// sound engine +#include "../sound/sound.h" + +// RAVEN BEGIN +// jscott: Effects system interface +#include "../bse/BSEInterface.h" +// RAVEN END + +// asynchronous networking +#include "../framework/async/NetworkSystem.h" + +// user interfaces +#include "../ui/ListGUI.h" +#include "../ui/UserInterface.h" + +// collision detection system +#include "../cm/CollisionModel.h" + +// AAS files and manager +#include "../aas/AASFile.h" +#include "../aas/AASFileManager.h" + +// game +#include "../game/Game.h" + +//----------------------------------------------------- + +#if defined( Q4SDK ) || defined( GAME_DLL ) || defined( GAME_MONO ) + +#ifdef GAME_MPAPI +#include "../mpgame/Game_local.h" +#else +#include "../game/Game_local.h" +#endif + +#else + +#include "../framework/DemoChecksum.h" + +// framework +#include "../framework/Compressor.h" +#include "../framework/EventLoop.h" +#include "../framework/KeyInput.h" +#include "../framework/EditField.h" +#include "../framework/Console.h" +#include "../framework/DemoFile.h" +#include "../framework/Session.h" + +// asynchronous networking +#include "../framework/async/AsyncNetwork.h" + +// RAVEN BEGIN +#include "../tools/Tools.h" +// RAVEN END + +#endif /* !GAME_DLL */ + +// RAVEN BEGIN +// jsinger: add AutoPtr and text-to-binary compiler support +#include "AutoPtr.h" +#include "LexerFactory.h" +#include "TextCompiler.h" +// jsinger: AutoCrit.h contains classes which aid in code synchronization +// AutoAcquire.h contains a class that aids in thread acquisition of the direct3D device for xenon +// Both compile out completely if the #define's above are not present +#include "threads/AutoCrit.h" +// RAVEN END + +//----------------------------------------------------- + +#endif /* __cplusplus */ + +#endif /* !__PRECOMPILED_H__ */ diff --git a/source/idlib/rvHeap.cpp b/source/idlib/rvHeap.cpp new file mode 100644 index 0000000..6c1be6a --- /dev/null +++ b/source/idlib/rvHeap.cpp @@ -0,0 +1,1556 @@ +// +// rvHeap.cpp - Heap object +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +//#define DETECT_MEM_OVERWRITES + +// Define some structures used by each heap +struct rvMemoryBlock_s +{ + rvMemoryBlock_s* m_prev; // previous block in memory (next is implied by address of memory block plus distToNextBlock) + dword m_dwordDistToNextBlock : NUM_MEMORY_BLOCK_SIZE_BITS; // distance, in double words (4 bytes), from the first byte of this rvMemoryBlock_s header to the first byte of the next rvMemoryBlock_s header + dword m_tag : NUM_TAG_BITS; // bits used to tag the type of allocation and state of memory block (zero for free) + + // GetDistToNextBlock + // + // returns: the distance to the next block, in bytes + ID_INLINE dword GetDistToNextBlock() const + { + return m_dwordDistToNextBlock << 2; + } + + // SetDistToNextBlock + // + // sets the distance to the next block, in bytes + ID_INLINE void SetDistToNextBlock( dword distBytes ) + { + assert( !(distBytes & 0x03) ); + m_dwordDistToNextBlock = distBytes >> 2; + } +}; + +struct rvFreeMemoryBlock_s : rvMemoryBlock_s +{ + rvFreeMemoryBlock_s* m_prevFree; + rvFreeMemoryBlock_s* m_nextFree; +}; + +static const dword rvHeapAlignmentPadding = 0xFFFFFFFF; // fixed value used for padding an allocation for alignment + +#ifdef DETECT_MEM_OVERWRITES +// a header and a trailer is stored at the front and rear of each allocation for +// the purposes of detecting if an allocation has been written past its bounds +static const uint OVERWRITE_HEADER_SIZE = 4; +static const uint OVERWRITE_TRAILER_SIZE = 4; +static const char rvHeapAllocHeader[OVERWRITE_HEADER_SIZE] = { '{', 'R', 'a', 'V' }; +static const char rvHeapAllocTrailer[OVERWRITE_TRAILER_SIZE] = { 'e', 'N', '1', '}' }; +#else +static const uint OVERWRITE_HEADER_SIZE = 0; +static const uint OVERWRITE_TRAILER_SIZE = 0; +#endif + +#define SIZE_TO_SMALL_TABLE_OFFSET(sizeBytes) ((((sizeBytes) + SMALL_BLOCK_ALIGN_SIZE_MASK) >> SMALL_BLOCK_ALIGN_SIZE_SHIFT) - 1) +#define SMALL_TABLE_OFFSET_TO_SIZE(tableOffset) (((tableOffset) + 1) << SMALL_BLOCK_ALIGN_SIZE_SHIFT) + +// rvHeap +// +// constructor +rvHeap::rvHeap( ) : + mDebugID(-1) +{ + // ResetValues(); do this in the Init() call instead (due to the fact that other constructors could call rvHeap::Init() before this constructor is called) +} + +// ~rvHeap +// +// destructor +rvHeap::~rvHeap( ) +{ + Shutdown(); +} + +// Init +// +// initializes this heap for use within the given arena, and with the given size limit that it can grow to +// +// heapArena heap arena that this heap is associated with +// maxHeapSizeBytes maximum number of bytes this heap can grow to +// flags flags describing the capabilities of this heap +void rvHeap::Init( rvHeapArena &heapArena, uint maxHeapSizeBytes, uint flags ) +{ + DWORD protection = PAGE_READWRITE; + DWORD allocType; + uint numPageTableBytes, numTablePages; + uint largeFreeRangeByteSize; + byte* pageTableData; + + ResetValues(); + + // create the critical section used by this heap + InitializeCriticalSection( &m_criticalSection ); + + // determine how many pages the virtual address range will consume + m_numPages = (maxHeapSizeBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + m_maxHeapSizeBytes = m_numPages << PAGE_SIZE_SHIFT; + + m_heapRangeBytes = m_maxHeapSizeBytes + PAGE_SIZE; // add a page at the beginning for zero-length allocations (not part of page table and is never committed) + + assert(sizeof(dword) == 4); // assumed by following code + m_pageCommitStateArrayByteSize = ((m_numPages + 31) >> 5)*sizeof(dword); + + m_smallFreePageRangeByteSize = m_numPages*sizeof(freePageRange_t); + m_largeFreeRangeStorageSize = m_numPages/MAX_SMALL_PAGE_RANGE; + if ( m_largeFreeRangeStorageSize < 1 ) + { + m_largeFreeRangeStorageSize = 1; + } + largeFreeRangeByteSize = m_largeFreeRangeStorageSize*sizeof(freeLargePageRange_t); + + numPageTableBytes = m_pageCommitStateArrayByteSize; // allocate storage for the m_pageCommitStateTable array + numPageTableBytes += m_smallFreePageRangeByteSize; // allocate storage for the m_smallFreePageRangeTable array + numPageTableBytes += largeFreeRangeByteSize; // allocate storage for the m_largeFreeRangeStorage array + + numTablePages = (numPageTableBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + numPageTableBytes = numTablePages << PAGE_SIZE_SHIFT; + + // reserve the range of virtual memory needed by this heap + if ( (flags & rvHeapFlagWriteCombine) != 0 ) + { + protection |= PAGE_WRITECOMBINE; + } + if ( (flags & rvHeapFlagNoCache) != 0 ) + { + protection |= PAGE_NOCACHE; + } + allocType = MEM_RESERVE; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } + allocType |= MEM_NOZERO; +#endif + m_baseAddress = (byte *) VirtualAlloc( NULL, numPageTableBytes+m_heapRangeBytes, allocType, protection ); + if ( NULL == m_baseAddress ) + { + common->FatalError( "Unable to reserve desired size for heap.\n" ); + return; + } + m_zeroLengthAllocPage = m_baseAddress + numPageTableBytes; + m_heapStorageStart = m_zeroLengthAllocPage + PAGE_SIZE; + + // commit the initial block of that range for use by the page table + // Note: We want this to zero the memory for the page table init! + allocType = MEM_COMMIT; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } +#endif + pageTableData = (byte *) VirtualAlloc( m_baseAddress, numPageTableBytes, allocType, PAGE_READWRITE ); + if ( NULL == pageTableData ) + { + common->FatalError( "Unable to commit pages needed for heap's page table.\n" ); + VirtualFree( m_baseAddress, 0, MEM_RELEASE ); + return; + } + m_committedSizeBytes = numPageTableBytes; + + m_pageCommitStateTable = (dword*) pageTableData; + pageTableData += m_pageCommitStateArrayByteSize; + + m_smallFreePageRangeTable = (freePageRange_t *) pageTableData; + pageTableData += m_smallFreePageRangeByteSize; + + m_largeFreeRangeStorage = (freeLargePageRange_t *) pageTableData; + BuildLargeFreeRangeReuseList(); + + // set up the information members + m_flags = flags; + + heapArena.InitHeap( *this ); +} + +// Shutdown +// +// releases this heap from use +void rvHeap::Shutdown( ) +{ + if ( m_arena != NULL ) + { + VirtualFree( m_baseAddress, 0, MEM_RELEASE ); + m_arena->ShutdownHeap( *this ); + + DeleteCriticalSection( &m_criticalSection ); + } + ResetValues(); +} + +// ResetValues +// +// resets the data members to their pre-initialized state +void rvHeap::ResetValues( ) +{ + m_arena = NULL; + m_next = NULL; + memset( &m_criticalSection, 0, sizeof(m_criticalSection) ); + m_baseAddress = NULL; + m_zeroLengthAllocPage = NULL; + m_heapStorageStart = NULL; + memset( m_smallFreeBlocks, 0, sizeof(m_smallFreeBlocks) ); + m_largeFreeBlocks = NULL; + m_pageCommitStateTable = NULL; + m_smallFreePageRangeTable = NULL; + memset( m_smallFreePageRangeLists, 0, sizeof(m_smallFreePageRangeLists) ); + m_largeFreePageRangeList = NULL; + m_largeFreeRangeStorage = NULL; + m_largeFreeRangeReuseList = NULL; + m_largeFreeRangeStorageSize = 0; + m_numPages = 0; + m_heapRangeBytes = 0; + m_committedSizeBytes = 0; + m_maxHeapSizeBytes = 0; + m_numBytesRequested = 0; + m_numBytesAllocated = 0; + memset(m_allocatedBytesByTag, 0, sizeof(m_allocatedBytesByTag)); + memset(m_peekAllocatedBytesByTag, 0, sizeof(m_peekAllocatedBytesByTag)); + memset(m_numAllocationsByTag, 0, sizeof(m_numAllocationsByTag)); + m_numAllocations = 0; + m_flags = 0; + m_numZeroLengthAllocations = 0; + memset(m_name, 0, sizeof(m_name)); +} + +// BuildLargeFreeRangeReuseList +// +// Builds the m_largeFreeRangeReuseList linked-list from the m_largeFreeRangeStorage[] array. +void rvHeap::BuildLargeFreeRangeReuseList( ) +{ + uint nOffset; + + m_largeFreeRangeReuseList = m_largeFreeRangeStorage + 1; + for ( nOffset = 1; nOffset < m_largeFreeRangeStorageSize-1; nOffset++ ) + { + m_largeFreeRangeStorage[ nOffset ].m_nextFree = &m_largeFreeRangeStorage[ nOffset + 1 ]; + } + m_largeFreeRangeStorage[ m_largeFreeRangeStorageSize-1 ].m_nextFree = NULL; + + m_largeFreePageRangeList = m_largeFreeRangeStorage; + m_largeFreePageRangeList->m_nextFree = NULL; + m_largeFreePageRangeList->m_firstPageOffset = 0; + m_largeFreePageRangeList->m_numContiguousPages = m_numPages; +} + +// AllocateMemory +// +// allocates memory from this heap. +// +// sizeBytes Size, in bytes, of desired allocation. +// allocationTag Tag stored with this allocation (describing type of allocation) +// align16Flag true if allocation must be aligned on a 16-byte boundary, false otherwise. +// +// returns: a pointer to the allocated memory, NULL if enough memory does not exist for allocation +byte *rvHeap::AllocateMemory( uint sizeBytes, int allocationTag, bool align16Flag ) +{ +// align16Flag=true; + byte *allocation; + rvMemoryBlock_s *allocatedBlock; + uint actualSizeBytes, extraBytes, distToNextBlock; + + if ( allocationTag == MA_NONE || allocationTag >= MA_DO_NOT_USE ) + { + allocationTag = MA_DEFAULT; + } + + EnterHeapCriticalSection( ); + + // check for a zero-length allocation - returns a valid virtual address that will page fault should + // it be written to, or read from (uncommitted page) + if ( !sizeBytes ) + { + // NOTE: The following is an attempt to provide an address within the range of this heap + // that takes up no space (with the exception of a single virtual, uncommitted page). + // The uniqueness of the address returned here is not guaranteed and is not aligned. + m_numZeroLengthAllocations++; + m_numAllocations++; + allocation = (byte *) (m_zeroLengthAllocPage + (m_numZeroLengthAllocations & PAGE_SIZE_MASK)); + + ExitHeapCriticalSection( ); + return allocation; + } + + // determine how big the allocation is really going to need to be + actualSizeBytes = sizeof(rvMemoryBlock_s) + sizeBytes + OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + extraBytes = OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + if ( align16Flag ) + { + actualSizeBytes += 3*sizeof(rvHeapAlignmentPadding); // for alignment + extraBytes += 3*sizeof(rvHeapAlignmentPadding); + } + + if ( actualSizeBytes > PAGE_SIZE ) + { + // if greater than a page, and the tail just barely crosses a page boundary, + // ask for a size that is a multiple of free memory block structure (so there is room for such a structure at the end) + if ( (actualSizeBytes & PAGE_SIZE_MASK) < sizeof(rvFreeMemoryBlock_s) ) + { + actualSizeBytes = (actualSizeBytes + (uint) sizeof(rvFreeMemoryBlock_s) - 1) & ~((uint) sizeof(rvFreeMemoryBlock_s) - 1); + } + } + else if ( actualSizeBytes < sizeof(rvFreeMemoryBlock_s) ) + { + actualSizeBytes = sizeof(rvFreeMemoryBlock_s); + + // make sure that the size is 4-byte aligned + actualSizeBytes = ( actualSizeBytes + 3 ) & ~3; + } + + // make sure that the size is 4-byte aligned + actualSizeBytes = ( actualSizeBytes + 3 ) & ~3; + + if ( actualSizeBytes > MAX_SINGLE_ALLOCATION_SIZE ) + { + // beyond what any heap can handle + ExitHeapCriticalSection( ); + return NULL; + } + + if ( actualSizeBytes <= MAX_SMALL_BLOCK_SIZE ) + { + // perform a small block allocation + allocatedBlock = AllocateMemorySmallBlock( actualSizeBytes ); + } + else + { + // perform a large block allocation + allocatedBlock = AllocateMemoryLargeBlock( actualSizeBytes ); + } + + if ( NULL == allocatedBlock ) + { + ExitHeapCriticalSection( ); + return NULL; + } + + distToNextBlock = allocatedBlock->GetDistToNextBlock(); + + assert( allocationTag < MA_MAX ); + allocatedBlock->m_tag = (dword) allocationTag; + m_allocatedBytesByTag[ allocationTag ] += distToNextBlock; + if ( m_allocatedBytesByTag[ allocationTag ] > m_peekAllocatedBytesByTag[ allocationTag ] ) + { + m_peekAllocatedBytesByTag[ allocationTag ] = m_allocatedBytesByTag[ allocationTag ]; + } + m_numAllocationsByTag[ allocationTag ]++; + + m_numBytesAllocated += distToNextBlock; + m_numBytesRequested += (distToNextBlock - sizeof(rvMemoryBlock_s) - extraBytes); + m_numAllocations++; + + allocation = (byte *) allocatedBlock + sizeof(rvMemoryBlock_s); + +#ifdef DETECT_MEM_OVERWRITES + memcpy( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ); + allocation += OVERWRITE_HEADER_SIZE; + byte *nextBlockAddress = (byte*) allocatedBlock + distToNextBlock; + nextBlockAddress -= OVERWRITE_TRAILER_SIZE; + memcpy( nextBlockAddress, rvHeapAllocTrailer, OVERWRITE_TRAILER_SIZE ); +#endif + + if ( align16Flag ) + { + while ( ((ulong) allocation & 0x0F) != 0 ) + { + *(dword*)allocation = rvHeapAlignmentPadding; + allocation += sizeof(rvHeapAlignmentPadding); + } + } + + ExitHeapCriticalSection( ); + return allocation; +} + +// AllocateMemorySmallBlock +// +// perform a small block allocation - try to use an existing free block pointed to +// by the table. +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateMemorySmallBlock( uint sizeBytes ) +{ + uint smallBlockTableOffset, stopOffset; //, largerBlockOffset; + + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(sizeBytes); + assert(smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES); + +#if 1 + stopOffset = smallBlockTableOffset + NUM_SMALL_BLOCK_TABLE_ENTRIES; +#else + stopOffset = smallBlockTableOffset << 1; +#endif + if ( stopOffset > m_largestFreeSmallBlockOffset ) + { + stopOffset = m_largestFreeSmallBlockOffset; + } + + while ( smallBlockTableOffset < stopOffset ) + { + // check to see if there is a block that matches exactly + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + // there is a block that is just the right size, remove from free list and return it. + return AllocateBlockFromTable( smallBlockTableOffset, sizeBytes ); + } + +/* + NOTE: The following code is experimental and seems to result in greater fragmentation. + + // check to see if there is a block that matches at some multiple of the exact size + largerBlockOffset = smallBlockTableOffset << 1; + while ( largerBlockOffset <= m_largestFreeSmallBlockOffset ) + { + if ( m_smallFreeBlocks[largerBlockOffset] != NULL ) + { + // found a larger block to allocate from + return AllocateBlockFromTable( largerBlockOffset, sizeBytes ); + } + largerBlockOffset = largerBlockOffset << 1; + } +*/ + + smallBlockTableOffset++; + } + + // an existing small block could not be found, try a larger block + return AllocateMemoryLargeBlock( sizeBytes ); +} + +// AllocateBlockFromTable +// +// Allocates the a small memory block from the free block table +// and partitions it, if necessary, into an allocated chunk +// and a free chunk (which remains in the m_smallFreeBlocks[] table). +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateBlockFromTable( uint smallBlockTableOffset, uint sizeBytes ) +{ + rvFreeMemoryBlock_s* allocatedBlockHeader, *newFreeBlock; + uint originalBlockSize, newBlockSize, newBlockTableOffset; + + allocatedBlockHeader = m_smallFreeBlocks[smallBlockTableOffset]; + assert( sizeBytes <= allocatedBlockHeader->GetDistToNextBlock() ); + + originalBlockSize = allocatedBlockHeader->GetDistToNextBlock(); + assert( originalBlockSize == SMALL_TABLE_OFFSET_TO_SIZE(smallBlockTableOffset) ); + + // remove the free block from the table + m_smallFreeBlocks[smallBlockTableOffset] = m_smallFreeBlocks[smallBlockTableOffset]->m_nextFree; + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[smallBlockTableOffset]->m_prevFree = NULL; + } + + // see if the requested size essentially covers the entire free block + if ( sizeBytes + sizeof(rvFreeMemoryBlock_s) <= allocatedBlockHeader->GetDistToNextBlock() ) + { + // this block is large enough to be split into two - an allocated chunk and a free chunk. + // re-add the now smaller free chunk back into smallFreeBlocks[] table + newBlockSize = allocatedBlockHeader->GetDistToNextBlock() - sizeBytes; + + newFreeBlock = (rvFreeMemoryBlock_s*) ((byte *) allocatedBlockHeader + sizeBytes); + + newFreeBlock->m_prev = allocatedBlockHeader; + newFreeBlock->SetDistToNextBlock( newBlockSize ); + newFreeBlock->m_tag = MA_NONE; + + newBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(newBlockSize); + if ( m_smallFreeBlocks[newBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[newBlockTableOffset]->m_prevFree = newFreeBlock; + } + + newFreeBlock->m_prevFree = NULL; + newFreeBlock->m_nextFree = m_smallFreeBlocks[newBlockTableOffset]; + m_smallFreeBlocks[newBlockTableOffset] = newFreeBlock; + + FixUpNextBlocksPrev( (byte *) newFreeBlock, newBlockSize ); + + allocatedBlockHeader->SetDistToNextBlock( sizeBytes ); + } + + allocatedBlockHeader->m_tag = MA_DEFAULT; + + // check to see if the largest free small block has moved down + TestLargestFreeSmallBlockOffset( smallBlockTableOffset ); + + return allocatedBlockHeader; +} + +// TestLargestFreeSmallBlockOffset +// +// checks to see if the largest free small block has moved down +void rvHeap::TestLargestFreeSmallBlockOffset( uint smallBlockTableOffset ) +{ + if ( smallBlockTableOffset >= m_largestFreeSmallBlockOffset ) { + + // find the next largest available small block (if table entry is NULL) + uint nextAvailableBlock = smallBlockTableOffset; + while ( nextAvailableBlock > 0 && NULL == m_smallFreeBlocks[nextAvailableBlock] ) + { + nextAvailableBlock--; + } + m_largestFreeSmallBlockOffset = nextAvailableBlock; + } +} + +// AllocateMemoryLargeBlock +// +// perform a large block allocation - try to use an existing free block from within +// the large free block linked-list. +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateMemoryLargeBlock( uint sizeBytes ) +{ + rvFreeMemoryBlock_s **prevNextBlock, *curFreeBlock, *freeBlock; + rvMemoryBlock_s *block = NULL; + byte *newPageStart; + uint numPages, totalAllocated = 0, remainingSize; + + prevNextBlock = &m_largeFreeBlocks; + curFreeBlock = m_largeFreeBlocks; + while ( curFreeBlock != NULL ) + { + if ( sizeBytes <= curFreeBlock->GetDistToNextBlock() ) + { + // we found a block that is big enough, remove it from the large free block list + *prevNextBlock = curFreeBlock->m_nextFree; + if ( curFreeBlock->m_nextFree != NULL ) + { + curFreeBlock->m_nextFree->m_prevFree = curFreeBlock->m_prevFree; + } + totalAllocated = curFreeBlock->GetDistToNextBlock(); + block = (rvMemoryBlock_s *) curFreeBlock; + break; + } + + prevNextBlock = &curFreeBlock->m_nextFree; + curFreeBlock = curFreeBlock->m_nextFree; + } + + if ( NULL == block ) + { + // an existing block could not be found, commit a new page range for use + numPages = (sizeBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + newPageStart = (byte *) PageCommit( numPages ); + if ( NULL == newPageStart ) + { + // out of memory error + return NULL; + } + + // break the new page up into an allocated chunk (returned) and a free chunk (added to m_smallFreeBlocks[] or m_largeFreeBlocks) + block = (rvMemoryBlock_s *) newPageStart; + block->m_prev = NULL; + + totalAllocated = numPages << PAGE_SIZE_SHIFT; + } + + block->m_tag = MA_DEFAULT; + + remainingSize = totalAllocated - sizeBytes; + if ( remainingSize < sizeof(rvFreeMemoryBlock_s) ) + { + // it is not worth creating a free block for what remains of last page + block->SetDistToNextBlock( totalAllocated ); + } + else + { + // there is a enough space left at the end of the last page to generate + // a free block + block->SetDistToNextBlock( sizeBytes ); + freeBlock = (rvFreeMemoryBlock_s *) ((byte *) block + sizeBytes); + assert( !((uint) freeBlock & 0x03) ); + + if ( ((uint) block >> PAGE_SIZE_SHIFT) == (((uint) block + sizeBytes) >> PAGE_SIZE_SHIFT) ) + { + // new free block is on the same page + freeBlock->m_prev = block; + } + else + { + // new free block is on a different page + freeBlock->m_prev = NULL; + } + freeBlock->SetDistToNextBlock( remainingSize ); + freeBlock->m_tag = MA_NONE; + + FixUpNextBlocksPrev( (byte *) freeBlock, remainingSize ); + + AddFreeBlock( freeBlock ); + } + return block; +} + +// FixUpNextBlocksPrev +// +// Fixes up the previous pointer of the memory block that lies immediately +// past the given newBlock, if it remains on the same page. +void rvHeap::FixUpNextBlocksPrev( byte *newBlock, uint blockSize ) +{ + rvMemoryBlock_s* nextBlockPastNew; + + if ( ((uint) newBlock >> PAGE_SIZE_SHIFT) == ((uint) (newBlock + blockSize) >> PAGE_SIZE_SHIFT) ) + { + // the next block remains on the same page, the previous pointer must be fixed up + nextBlockPastNew = (rvMemoryBlock_s*) (newBlock + blockSize); + nextBlockPastNew->m_prev = (rvMemoryBlock_s*) newBlock; + } +} + +// AddFreeBlock +// +// Adds the given free block to the small allocation table or large allocation list. +void rvHeap::AddFreeBlock( rvFreeMemoryBlock_s *freeBlock ) +{ + uint smallBlockTableOffset; + + if ( freeBlock->GetDistToNextBlock() <= MAX_SMALL_BLOCK_SIZE ) + { + // add the block to the small free block table + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(freeBlock->GetDistToNextBlock()); + assert(smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES); + + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[smallBlockTableOffset]->m_prevFree = freeBlock; + } + else if ( smallBlockTableOffset > m_largestFreeSmallBlockOffset ) + { + m_largestFreeSmallBlockOffset = smallBlockTableOffset; + } + + freeBlock->m_prevFree = NULL; + freeBlock->m_nextFree = m_smallFreeBlocks[smallBlockTableOffset]; + m_smallFreeBlocks[smallBlockTableOffset] = freeBlock; + } + else + { + if ( m_largeFreeBlocks != NULL ) + { + m_largeFreeBlocks->m_prevFree = freeBlock; + } + freeBlock->m_prevFree = NULL; + freeBlock->m_nextFree = m_largeFreeBlocks; + m_largeFreeBlocks = freeBlock; + } +} + +// RemoveFreeBlock +// +// Removes the given block from the small allocation table or large allocation list. +void rvHeap::RemoveFreeBlock( rvFreeMemoryBlock_s *freeBlock ) +{ + uint smallBlockTableOffset; + + if ( freeBlock->GetDistToNextBlock() <= MAX_SMALL_BLOCK_SIZE ) + { + if ( NULL == freeBlock->m_prevFree ) + { + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(freeBlock->GetDistToNextBlock()); + + assert( smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES ); + assert( m_smallFreeBlocks[smallBlockTableOffset] == freeBlock ); + + m_smallFreeBlocks[smallBlockTableOffset] = freeBlock->m_nextFree; + + // check to see if the largest free small block has moved down + TestLargestFreeSmallBlockOffset( smallBlockTableOffset ); + } + else + { + freeBlock->m_prevFree->m_nextFree = freeBlock->m_nextFree; + } + } + else + { + if ( NULL == freeBlock->m_prevFree ) + { + assert( m_largeFreeBlocks == freeBlock ); + + m_largeFreeBlocks = freeBlock->m_nextFree; + } + else + { + freeBlock->m_prevFree->m_nextFree = freeBlock->m_nextFree; + } + } + + if ( freeBlock->m_nextFree != NULL ) + { + freeBlock->m_nextFree->m_prevFree = freeBlock->m_prevFree; + } +} + +// Free +// +// free memory +void rvHeap::Free( void *p ) +{ + rvMemoryBlock_s *block, *prevBlock, *nextBlock; + rvFreeMemoryBlock_s *freeBlock; + byte *nextBlockAddress, *allocation; + byte *pageAddress; + uint numPages, remainingSize, extraBytes, allocationTag, distToNextBlock; + + EnterHeapCriticalSection( ); + + assert( m_numAllocations > 0 ); + assert( p != NULL ); + assert( DoesAllocBelong( p ) ); + + m_numAllocations--; + + if ( p < m_zeroLengthAllocPage+PAGE_SIZE ) + { + // this is a zero-length allocation + assert(m_numZeroLengthAllocations>0); + m_numZeroLengthAllocations--; +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + ExitHeapCriticalSection( ); + return; + } + + assert( !((uint) p & 0x03) ); + + // back up over any heap alignment padding + allocation = (byte *) p; + extraBytes = 0; + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + } + } + extraBytes += 3*sizeof(rvHeapAlignmentPadding); + } + + // make sure that the overwrite header and trailer are intact +#ifdef DETECT_MEM_OVERWRITES + allocation -= OVERWRITE_HEADER_SIZE; + extraBytes += OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + if ( memcmp( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header overwrite (0x%x)", (dword) allocation ); + } +#endif + + // restore the memory block pointer + block = (rvMemoryBlock_s *) (allocation - sizeof(rvMemoryBlock_s)); + +#ifdef DETECT_MEM_OVERWRITES + nextBlockAddress = (byte*) block + block->GetDistToNextBlock(); + nextBlockAddress -= OVERWRITE_TRAILER_SIZE; + if ( memcmp( nextBlockAddress, rvHeapAllocTrailer, OVERWRITE_TRAILER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block trailer overwrite (0x%x)", (dword) nextBlockAddress ); + } +#endif + + pageAddress = (byte *) (((dword) allocation) & ~PAGE_SIZE_MASK); + + if ( ( block->m_prev != NULL && (byte *) block->m_prev < pageAddress ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header corrupted (0x%x)", (dword) allocation ); + } + + if ( block->m_tag == MA_NONE ) + { + idLib::common->FatalError( "rvHeap::Free: attempt to free allocation that is already freed (0x%x)", (dword) allocation ); + } + + assert( (byte*) block >= m_heapStorageStart && ((byte*) block + block->GetDistToNextBlock()) <= (m_heapStorageStart + m_maxHeapSizeBytes) ); + + distToNextBlock = block->GetDistToNextBlock(); + assert( distToNextBlock <= m_numBytesAllocated ); + m_numBytesAllocated -= distToNextBlock; + m_numBytesRequested -= (distToNextBlock - sizeof(rvMemoryBlock_s) - extraBytes); + + allocationTag = block->m_tag; + assert( distToNextBlock <= m_allocatedBytesByTag[ allocationTag ] && m_numAllocationsByTag[ allocationTag ] > 0 ); + m_allocatedBytesByTag[ allocationTag ] -= distToNextBlock; + m_numAllocationsByTag[ allocationTag ]--; + + if ( block->GetDistToNextBlock() > PAGE_SIZE ) + { + // this allocation that is being freed spans multiple pages - uncommit the ones + // before the last one + numPages = block->GetDistToNextBlock() >> PAGE_SIZE_SHIFT; + + remainingSize = block->GetDistToNextBlock() - (numPages << PAGE_SIZE_SHIFT); + + PageUncommit( pageAddress, numPages ); + + if ( !remainingSize ) + { + // there are no remaining blocks +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + ExitHeapCriticalSection( ); + return; + } + + pageAddress += (numPages << PAGE_SIZE_SHIFT); + + block = (rvMemoryBlock_s *) pageAddress; + block->m_prev = NULL; + block->SetDistToNextBlock( remainingSize ); + block->m_tag = MA_NONE; + + FixUpNextBlocksPrev( (byte *) block, remainingSize ); + } + + nextBlockAddress = (byte*) block + block->GetDistToNextBlock(); + + // mark the block as free + block->m_tag = MA_NONE; + + // determine if the block we are freeing should be merged with the previous block + prevBlock = block->m_prev; + if ( prevBlock != NULL ) + { + if ( prevBlock->m_tag == MA_NONE ) + { + // the previous block is free, merge the block we are freeing to its previous neighbor + RemoveFreeBlock( (rvFreeMemoryBlock_s *) prevBlock ); + prevBlock->SetDistToNextBlock( prevBlock->GetDistToNextBlock() + block->GetDistToNextBlock() ); + block = prevBlock; + + FixUpNextBlocksPrev( (byte *) prevBlock, prevBlock->GetDistToNextBlock() ); + } + } + + // determine if the block we are freeing should be merged with the next block + if ( nextBlockAddress < pageAddress+PAGE_SIZE ) + { + nextBlock = (rvMemoryBlock_s *) nextBlockAddress; + if ( nextBlock->m_tag == MA_NONE ) + { + // the next block is free, merge the block we are freeing to its next neighbor + RemoveFreeBlock( (rvFreeMemoryBlock_s *) nextBlock ); + + block->SetDistToNextBlock( block->GetDistToNextBlock() + nextBlock->GetDistToNextBlock() ); + + FixUpNextBlocksPrev( (byte *) block, block->GetDistToNextBlock() ); + } + } + + // determine if block being freed should be added to small allocation table, large + // allocation list, or if the page itself should be uncommitted + freeBlock = (rvFreeMemoryBlock_s *) block; + if ( freeBlock->GetDistToNextBlock() < PAGE_SIZE ) + { + AddFreeBlock( freeBlock ); + } + else + { + // this free block has reached the size of an entire page, uncommit it + PageUncommit( pageAddress, 1 ); + } + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + + ExitHeapCriticalSection( ); +} + +// Msize +// +// returns: the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). +int rvHeap::Msize( void *p ) +{ + rvMemoryBlock_s *block; + byte *allocation; + dword size; + + EnterHeapCriticalSection( ); + + assert( m_numAllocations > 0 ); + assert( p != NULL ); + assert( DoesAllocBelong( p ) ); + + if ( p < m_zeroLengthAllocPage+PAGE_SIZE ) + { + // this is a zero-length allocation + ExitHeapCriticalSection( ); + return 0; + } + + assert( !((uint) p & 0x03) ); + + // back up over any heap alignment padding + allocation = (byte *) p; + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + } + } + } + + // make sure that the overwrite header and trailer are intact +#ifdef DETECT_MEM_OVERWRITES + allocation -= OVERWRITE_HEADER_SIZE; + if ( memcmp( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header overwrite (0x%x)", (dword) allocation ); + } +#endif + + // restore the memory block pointer + block = (rvMemoryBlock_s *) (allocation - sizeof(rvMemoryBlock_s)); + + size = block->GetDistToNextBlock(); + size -= ((dword)p-(dword)block) + OVERWRITE_TRAILER_SIZE; + + ExitHeapCriticalSection( ); + + return size; +} + +// FreeAll +// +// frees all the allocations from this heap (and decommits all pages) +void rvHeap::FreeAll( ) +{ + if ( NULL == m_baseAddress ) + { + return; + } + + EnterHeapCriticalSection( ); + + VirtualFree( m_heapStorageStart, m_maxHeapSizeBytes, MEM_DECOMMIT ); // this decommits all of the physical pages (frees physical memory), but leaves the virtual address range reserved to this heap + + memset( m_smallFreeBlocks, 0, sizeof(rvFreeMemoryBlock_s*)*NUM_SMALL_BLOCK_TABLE_ENTRIES ); + m_largeFreeBlocks = NULL; + + m_committedSizeBytes = 0; + m_numBytesAllocated = 0; + m_numBytesRequested = 0; + + m_numAllocations = 0; + m_numZeroLengthAllocations = 0; + + m_largestFreeSmallBlockOffset = 0; + + memset( m_pageCommitStateTable, 0, m_pageCommitStateArrayByteSize ); + memset( m_smallFreePageRangeTable, 0, m_smallFreePageRangeByteSize ); + BuildLargeFreeRangeReuseList( ); + + ExitHeapCriticalSection( ); +} + +// PageCommit +// +// commits the given number of contiguous pages to physical memory +// (actually allocates the physical pages). +// +// returns: pointer to the first virtual address of the first committed page, +// NULL if commit failed. +void *rvHeap::PageCommit( uint numDesiredPages ) +{ + freePageRange_t *freePageRange; + freeLargePageRange_t *freeLargePageRange, **prevNextLargePageRange; + uint listOffset, tableOffset, numRemainingPages, remainingPagesOffset, remainingListOffset; + + if ( numDesiredPages <= MAX_SMALL_PAGE_RANGE ) + { + // the desired range of contiguous pages is considered "small" and the retrieval + // of such a range is accelerated by the m_smallFreePageRangeLists[] table + listOffset = numDesiredPages - 1; + do + { + if ( m_smallFreePageRangeLists[listOffset] != NULL ) + { + // we have found a contiguous range of pages that will hold the desired amount + freePageRange = m_smallFreePageRangeLists[listOffset]; + m_smallFreePageRangeLists[listOffset] = m_smallFreePageRangeLists[listOffset]->m_nextFree; + + // determine the starting table offset of the page range (starting page number) + tableOffset = freePageRange - m_smallFreePageRangeTable; + + // re-add any remainder back into the m_smallFreePageRangeTable[] + numRemainingPages = listOffset + 1 - numDesiredPages; + if ( numRemainingPages > 0 ) + { + remainingListOffset = numRemainingPages - 1; + remainingPagesOffset = tableOffset + numDesiredPages; + m_smallFreePageRangeTable[ remainingPagesOffset ].m_nextFree = m_smallFreePageRangeLists[ remainingListOffset ]; + m_smallFreePageRangeLists[ remainingListOffset ] = &m_smallFreePageRangeTable[ remainingPagesOffset ]; + } + + // actually commit the pages to physical memory and mark the pages as committed within the m_pageCommitStateTable[] + return CommitPageRange( tableOffset, numDesiredPages ); + } + + listOffset++; + } + while ( listOffset < MAX_SMALL_PAGE_RANGE ); + } + + // try find a suitable contiguous range of pages within the m_largeFreePageRangeList linked list + freeLargePageRange = m_largeFreePageRangeList; + prevNextLargePageRange = &m_largeFreePageRangeList; + while ( freeLargePageRange != NULL ) + { + if ( freeLargePageRange->m_numContiguousPages >= numDesiredPages ) + { + // found a block that is big enough - split it if necessary + tableOffset = freeLargePageRange->m_firstPageOffset; + numRemainingPages = freeLargePageRange->m_numContiguousPages - numDesiredPages; + remainingPagesOffset = tableOffset + numDesiredPages; + + if ( numRemainingPages <= MAX_SMALL_PAGE_RANGE ) + { + // the freeLargePageRange_t structure is no longer needed in the m_largeFreePageRangeList list, remove it + *prevNextLargePageRange = freeLargePageRange->m_nextFree; + freeLargePageRange->m_nextFree = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = freeLargePageRange; + + if ( numRemainingPages > 0 ) + { + // add the remainder into the m_smallFreePageRangeTable[] + remainingListOffset = numRemainingPages - 1; + m_smallFreePageRangeTable[ remainingPagesOffset ].m_nextFree = m_smallFreePageRangeLists[ remainingListOffset ]; + m_smallFreePageRangeLists[ remainingListOffset ] = &m_smallFreePageRangeTable[ remainingPagesOffset ]; + } + } + else + { + // add the remainder back into the m_largeFreePageRangeList linked-list (NOTE: structure is still part of list) + freeLargePageRange->m_firstPageOffset = remainingPagesOffset; + freeLargePageRange->m_numContiguousPages = numRemainingPages; + } + + return CommitPageRange( tableOffset, numDesiredPages ); + } + + prevNextLargePageRange = &freeLargePageRange->m_nextFree; + freeLargePageRange = freeLargePageRange->m_nextFree; + } + + return NULL; +} + +// PageUncommit +// +// uncommits the given range of contiguous pages back to physical memory +// (actually frees the physical pages). +void rvHeap::PageUncommit( byte *pageAddress, uint numPages ) +{ + freeLargePageRange_t *freeLargePageRange = NULL; + uint startPageOffset, prevPageOffset, nextPageOffset, listOffset; + uint curDWordOffset, freeBlockPageCount, largeRangeCount, orgStartPageOffset, orgNumPages; + + pageAddress = (byte *) (((dword) pageAddress) & ~PAGE_SIZE_MASK); + + startPageOffset = (uint) (pageAddress - m_heapStorageStart) >> PAGE_SIZE_SHIFT; + + orgStartPageOffset = startPageOffset; + orgNumPages = numPages; + + // determine if there is a free block of pages immediately previous and/or immediately following + // this newly freed block of pages - merge into one big block if we can. + if ( startPageOffset > 0 ) + { + prevPageOffset = startPageOffset - 1; + curDWordOffset = prevPageOffset >> 5; + if ( !(m_pageCommitStateTable[ curDWordOffset ] & (1 << (prevPageOffset & 0x1F))) ) + { + // the previous block is free, so now we need to determine what linked-list it + // is on (how big is the continuous block of pages) + freeBlockPageCount = 1; + while ( prevPageOffset > 0 && freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + curDWordOffset = (prevPageOffset - 1) >> 5; + if ( (m_pageCommitStateTable[ curDWordOffset ] & (1 << ((prevPageOffset - 1) & 0x1F))) != 0 ) + { + break; + } + prevPageOffset--; + freeBlockPageCount++; + } + + if ( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + RemoveFromSmallFreeRangeList( prevPageOffset, freeBlockPageCount ); + startPageOffset = prevPageOffset; + numPages += freeBlockPageCount; + } + else + { + RemoveFromLargeFreeRangeList( prevPageOffset, startPageOffset, largeRangeCount ); + numPages += largeRangeCount; + } + } + } + + if ( startPageOffset + numPages < m_numPages ) + { + nextPageOffset = startPageOffset + numPages; + curDWordOffset = nextPageOffset >> 5; + if ( !(m_pageCommitStateTable[ curDWordOffset ] & (1 << (nextPageOffset & 0x1F))) ) + { + // the next block is free, so now we need to determine what linked-list it + // is on (how big is the continuous block of pages) + freeBlockPageCount = 1; + while ( nextPageOffset < m_numPages && freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + nextPageOffset++; + curDWordOffset = nextPageOffset >> 5; + if ( (m_pageCommitStateTable[ curDWordOffset ] & (1 << (nextPageOffset & 0x1F))) != 0 ) + { + break; + } + freeBlockPageCount++; + } + + if ( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + RemoveFromSmallFreeRangeList( startPageOffset + numPages, freeBlockPageCount ); + numPages += freeBlockPageCount; + } + else + { + RemoveFromLargeFreeRangeList( startPageOffset + numPages, nextPageOffset, largeRangeCount ); + numPages += largeRangeCount; + } + } + } + + if ( numPages <= MAX_SMALL_PAGE_RANGE ) + { + // add the uncommitted page block into the m_smallFreePageRangeTable[] + listOffset = numPages - 1; + m_smallFreePageRangeTable[ startPageOffset ].m_nextFree = m_smallFreePageRangeLists[ listOffset ]; + m_smallFreePageRangeLists[ listOffset ] = &m_smallFreePageRangeTable[ startPageOffset ]; + } + else + { + // add the uncommitted page block onto the m_largeFreePageRangeList linked-list + assert( m_largeFreeRangeReuseList != NULL ); + freeLargePageRange = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = m_largeFreeRangeReuseList->m_nextFree; + + freeLargePageRange->m_nextFree = m_largeFreePageRangeList; + m_largeFreePageRangeList = freeLargePageRange; + + freeLargePageRange->m_firstPageOffset = startPageOffset; + freeLargePageRange->m_numContiguousPages = numPages; + } + + // actually perform the decommit of the pages and clear the bits in the page table + UncommitPageRange( orgStartPageOffset, orgNumPages ); +} + +// RemoveFromSmallFreeRangeList +// +// Removes the given page range from the m_smallFreePageRangeLists[] +void rvHeap::RemoveFromSmallFreeRangeList( uint pageOffset, uint freeBlockPageCount ) +{ + freePageRange_t *freePageRange, **prevNextFreePageRange; + uint curOffset; + + assert( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE && freeBlockPageCount > 0 ); + + prevNextFreePageRange = &m_smallFreePageRangeLists[ freeBlockPageCount - 1 ]; + freePageRange = m_smallFreePageRangeLists[ freeBlockPageCount - 1 ]; + while ( freePageRange != NULL ) + { + curOffset = (uint) (freePageRange - m_smallFreePageRangeTable); + if ( curOffset == pageOffset ) + { + *prevNextFreePageRange = freePageRange->m_nextFree; + return; + } + prevNextFreePageRange = &freePageRange->m_nextFree; + freePageRange = freePageRange->m_nextFree; + } + assert(0); // we should not reach this +} + +// RemoveFromLargeFreeRangeList +// +// Removes the given page range from the m_largeFreePageRangeList +void rvHeap::RemoveFromLargeFreeRangeList( uint pageOffset, uint &startPageOffset, uint &pageCount ) +{ + freeLargePageRange_t *freeLargePageRange, ** prevNextFreeLargePageRange; + + prevNextFreeLargePageRange = &m_largeFreePageRangeList; + freeLargePageRange = m_largeFreePageRangeList; + while ( freeLargePageRange != NULL ) + { + if ( pageOffset >= freeLargePageRange->m_firstPageOffset && + pageOffset < freeLargePageRange->m_firstPageOffset + freeLargePageRange->m_numContiguousPages ) + { + // found it! + *prevNextFreeLargePageRange = freeLargePageRange->m_nextFree; + startPageOffset = freeLargePageRange->m_firstPageOffset; + pageCount = freeLargePageRange->m_numContiguousPages; + + freeLargePageRange->m_nextFree = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = freeLargePageRange; + return; + } + + prevNextFreeLargePageRange = &freeLargePageRange->m_nextFree; + freeLargePageRange = freeLargePageRange->m_nextFree; + } + assert(0); // we should not reach this +} + +// CommitPageRange +// +// Commits the given range of pages and flags them as committed within the m_pageCommitStateTable array +void *rvHeap::CommitPageRange( uint startPageOffset, uint numPages ) +{ + void* committedBaseAddress; + uint curDWordOffset, endDWordOffset, commitSize, tailShift; + dword mask; + DWORD allocType; + + assert( startPageOffset + numPages <= m_numPages ); + + // implement the commit + commitSize = numPages << PAGE_SIZE_SHIFT; + m_committedSizeBytes += commitSize; + + allocType = MEM_COMMIT; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } + allocType |= MEM_NOZERO; +#endif + committedBaseAddress = VirtualAlloc( m_heapStorageStart + (startPageOffset << PAGE_SIZE_SHIFT), commitSize, allocType, PAGE_READWRITE ); + if ( NULL == committedBaseAddress) + { + common->Warning( "Out of physical memory - unable to commit requested page range.\n" ); + return NULL; + } + + assert(sizeof(dword) == 4); // the following code assumes that a dword is 4 bytes + + // enable the bits that correspond to the pages being committed within the m_pageCommitStateTable array + curDWordOffset = startPageOffset >> 5; + endDWordOffset = (startPageOffset + numPages - 1) >> 5; + + if ( curDWordOffset == endDWordOffset ) + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + tailShift = (startPageOffset + numPages) & 0x1F; + if ( tailShift != 0 ) + { + mask ^= (0xFFFFFFFF << tailShift); + } + m_pageCommitStateTable[ curDWordOffset ] |= mask; + } + else + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + m_pageCommitStateTable[ curDWordOffset++ ] |= mask; + + while ( curDWordOffset < endDWordOffset ) + { + m_pageCommitStateTable[ curDWordOffset ] |= 0xFFFFFFFF; + curDWordOffset++; + } + + mask = 0xFFFFFFFF >> (32 - (startPageOffset + numPages - (endDWordOffset << 5))); + m_pageCommitStateTable[ curDWordOffset ] |= mask; + } + return committedBaseAddress; +} + +// UncommitPageRange +// +// Uncommits the given range of pages and clears their flags within the m_pageCommitStateTable array +void rvHeap::UncommitPageRange( uint startPageOffset, uint numPages ) +{ + BOOL rtnValue; + uint curDWordOffset, endDWordOffset, decommitSize, tailShift; + dword mask; + + assert( startPageOffset + numPages <= m_numPages ); + + // implement the free + decommitSize = numPages << PAGE_SIZE_SHIFT; + assert( decommitSize <= m_committedSizeBytes ); + m_committedSizeBytes -= decommitSize; + + rtnValue = VirtualFree( m_heapStorageStart + (startPageOffset << PAGE_SIZE_SHIFT), + decommitSize, + MEM_DECOMMIT ); + assert( rtnValue ); + assert(sizeof(dword) == 4); // the following code assumes that a dword is 4 bytes + + // disable the bits that correspond to the pages being decommitted within the m_pageCommitStateTable array + curDWordOffset = startPageOffset >> 5; + endDWordOffset = (startPageOffset + numPages - 1) >> 5; + + if ( curDWordOffset == endDWordOffset ) + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + tailShift = (startPageOffset + numPages) & 0x1F; + if ( tailShift != 0 ) + { + mask ^= (0xFFFFFFFF << tailShift); + } + m_pageCommitStateTable[ curDWordOffset ] &= ~mask; + } + else + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + m_pageCommitStateTable[ curDWordOffset++ ] &= ~mask; + + while ( curDWordOffset < endDWordOffset ) + { + m_pageCommitStateTable[ curDWordOffset++ ] = 0; + } + + mask = 0xFFFFFFFF >> (32 - (startPageOffset + numPages - (endDWordOffset << 5))); + m_pageCommitStateTable[ curDWordOffset ] &= ~mask; + } +} + +// EnterHeapCriticalSection +// +// enters this heap's critical section +void rvHeap::EnterHeapCriticalSection() +{ + ::EnterCriticalSection( &m_criticalSection ); +} + +// ExitHeapCriticalSection +// +// exits this heap's critical section +void rvHeap::ExitHeapCriticalSection() +{ + ::LeaveCriticalSection( &m_criticalSection ); +} + +// GetSmallBlockFreeCount +// +// Get number of free blocks for this block type +// If there's a lot of free blocks, then it may be bad. +int rvHeap::GetSmallBlockFreeCount( int block ) const +{ + return GetBlockFreeCount( m_smallFreeBlocks[block] ); +} + + +// GetSmallBlockFreeSize +// +// Get the actual physical storage committed for empty space in this block +dword rvHeap::GetSmallBlockFreeSize( int block ) const +{ + return GetBlockFreeSize( m_smallFreeBlocks[block] ); +} + +void rvHeap::SetName(const char* name) +{ + if(name) + { + idStr::Copynz(m_name, name, sizeof(m_name)); + } +} + +const char* rvHeap::GetName(void) const +{ + if(!m_name[0]) + { + return "UN-NAMED"; + } + return m_name; +} + +// GetLargeBlockFreeCount +// +int rvHeap::GetLargeBlockFreeCount() const +{ + return GetBlockFreeCount( m_largeFreeBlocks ); +} + +// GetLargeBlockFreeSize +// +dword rvHeap::GetLargeBlockFreeSize() const +{ + return GetBlockFreeSize( m_largeFreeBlocks ); +} + +// GetBlockFreeCount +// +int rvHeap::GetBlockFreeCount( rvFreeMemoryBlock_s* currentBlock ) const +{ + dword freeCount = 0; + while ( currentBlock ) + { + currentBlock = currentBlock->m_nextFree; + ++freeCount; + } + return freeCount; +} + +// GetBlockFreeSize +// +dword rvHeap::GetBlockFreeSize( rvFreeMemoryBlock_s* currentBlock ) const +{ + dword freeSize = 0; + while (currentBlock) + { + dword blockSize = currentBlock->m_dwordDistToNextBlock << 2; + + if ( blockSize > PAGE_SIZE ) + { +#ifdef _DEBUG + dword initialBlockSize = blockSize; +#endif + // Round up to the next block boundary + dword rem = (dword)currentBlock & ~PAGE_SIZE_MASK; + blockSize-=rem; + + // Subtract off the pages that should be decomitted + while ( blockSize > PAGE_SIZE ) + { + blockSize-=PAGE_SIZE; + } + + // Make sure we didn't wrap +#ifdef _DEBUG + assert( initialBlockSize > blockSize ); +#endif + freeSize+=blockSize; + } + else + { + // Does not span blocks, so just add + freeSize+=blockSize; + } + + currentBlock = currentBlock->m_nextFree; + } + + return freeSize; +} + +// GetSmallBlockFreeSize +// +void Mem_FragmentationStats_f( const idCmdArgs &args ) +{ + rvHeap *heapArray[MAX_SYSTEM_HEAPS]; + rvGetAllSysHeaps( heapArray ); + + dword unusedMem = 0; + dword totalFragments = 0; + + for ( int i = 0; i < MAX_SYSTEM_HEAPS; ++i ) + { + rvHeap *curr = heapArray[i]; + if ( curr ) + { + for ( int j = 0; j < curr->SmallBlockCount(); ++j ) + { + int freeCount = curr->GetSmallBlockFreeCount( j ); + if ( freeCount ) + { + dword unused = curr->GetSmallBlockFreeSize(j); + idLib::common->Printf( "i:%d c:%d t:%d - ", j, freeCount, unused ); + unusedMem+=unused; + totalFragments+=freeCount; + } + } + + dword unused = curr->GetLargeBlockFreeSize(); + dword freeCount = curr->GetLargeBlockFreeCount(); + unusedMem+=unused; + totalFragments+=freeCount; + idLib::common->Printf( "i:large c:%d t:%d\n", freeCount, unused ); + +// dword comSize = curr->GetCommittedSize(); + dword bytesAl = curr->GetBytesAllocated(); + + idLib::common->Printf( "Total fragments: %d Fragment memory: %d\n", totalFragments, unusedMem ); + idLib::common->Printf( "Fragmentation: %f\n", float(unusedMem) / float(bytesAl) ); + + // We only want the first one, since they all appear to be the same heap right now. + break; + } + } +} + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS +// TestFreeBlockValidity +// +// Tests for free memory block merge problems +void rvHeap::TestFreeBlockValidity() +{ + byte *pageAddress; + rvMemoryBlock_s *nextBlock; + rvFreeMemoryBlock_s *freeBlock; + int smallBlockTableOffset; + + for ( smallBlockTableOffset = 0; + smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES; + smallBlockTableOffset++ ) + { + // check to see if there is a block that matches exactly + freeBlock = m_smallFreeBlocks[smallBlockTableOffset]; + while ( freeBlock != NULL ) + { + // check the blocks around this block + if ( freeBlock->m_prev != NULL && freeBlock->m_prev->m_tag == MA_NONE ) + { + common->Warning( "Previous block failed to merge" ); + } + + nextBlock = (rvMemoryBlock_s *) ((byte*) freeBlock + freeBlock->GetDistToNextBlock()); + pageAddress = (byte *) (((dword) freeBlock) & ~PAGE_SIZE_MASK); + + if ( (byte*) nextBlock < pageAddress+PAGE_SIZE && nextBlock->m_tag == MA_NONE ) + { + common->Warning( "Next block failed to merge" ); + } + + freeBlock = freeBlock->m_nextFree; + } + } +} +#endif + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/source/idlib/rvHeap.h b/source/idlib/rvHeap.h new file mode 100644 index 0000000..218b0ee --- /dev/null +++ b/source/idlib/rvHeap.h @@ -0,0 +1,377 @@ +// +// rvHeap.h - Heap object (replacing the idHeap class) +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#ifndef __RV_HEAP_H__ +#define __RV_HEAP_H__ + +class rvHeapArena; +struct rvMemoryBlock_s; +struct rvFreeMemoryBlock_s; + +//#define _DETECT_BLOCK_MERGE_PROBLEMS + +// Define some flags used in describing various properties of allocations +// coming from this heap (these flags are passed into rvHeap::Init()). +static const uint rvHeapFlagDefault = 0; +static const uint rvHeapFlagWriteCombine = 0x0001; // flag specified if the memory allocated from this heap is intended to have a write-combine cache policy +static const uint rvHeapFlagNoCache = 0x0002; // flag specified if the memory allocated from this heap is intended to have a no cache policy + +// Define some limits used by each heap +static const uint NUM_MEMORY_BLOCK_SIZE_BITS = 27; +static const uint NUM_TAG_BITS = 5; + +static const uint MAX_SINGLE_ALLOCATION_SIZE = ((1 << (NUM_MEMORY_BLOCK_SIZE_BITS+2)) - 1); // maximum number of bytes that can be allocated in a single allocation +static const uint NUM_SMALL_BLOCK_TABLE_ENTRIES = 2048; // number of entries within each heap's small free memory block table + +static const uint SMALL_BLOCK_ALIGN_SIZE_SHIFT = 2; // left shift of the size that all allocations are rounded to +static const uint SMALL_BLOCK_ALIGN_SIZE = 1 << SMALL_BLOCK_ALIGN_SIZE_SHIFT; // size that all allocations are rounded to +static const uint SMALL_BLOCK_ALIGN_SIZE_MASK = SMALL_BLOCK_ALIGN_SIZE-1; // mask of the bits used for the size that all allocations are rounded to +static const uint MAX_SMALL_BLOCK_SIZE = (NUM_SMALL_BLOCK_TABLE_ENTRIES << SMALL_BLOCK_ALIGN_SIZE_SHIFT) - 1; // maximum size of an allocation to be considered a "small block" allocation + +static const uint PAGE_SIZE_SHIFT = 16; // left shift of the page size used by this heap object +static const uint PAGE_SIZE = 1 << PAGE_SIZE_SHIFT; // page size used by this heap object +static const uint PAGE_SIZE_MASK = PAGE_SIZE - 1; + +static const uint MAX_SMALL_PAGE_RANGE = 128; // maximum number of contiguous pages for a single range to be considered "small" +static const uint MIN_LARGE_PAGE_RANGE = MAX_SMALL_PAGE_RANGE+1; // minimum number of contiguous pages for a single range to be considered "large" + +// Define the freePageRange_t structure used by this rvHeap used to maintain linked-lists of contiguous page ranges below LARGE_PAGE_RANGE +typedef struct freePageRange_s +{ + freePageRange_s* m_nextFree; // next range of free pages in a linked-list +} +freePageRange_t; + +// Define the freePageRange_t structure used by this rvHeap used to maintain a linked-list of contiguous page ranges above LARGE_PAGE_RANGE +typedef struct freeLargePageRange_s +{ + freeLargePageRange_s* m_nextFree; // next large range of free pages in a linked-list + uint m_firstPageOffset; // offset of the first page in this range + uint m_numContiguousPages; // count of the number of contiguous pages in this range +} +freeLargePageRange_t; + +class rvHeap +{ +public: + rvHeap( ); // constructor + ~rvHeap( ); // destructor + + void Init( rvHeapArena &heapArena, uint maxHeapSizeBytes, uint flags = rvHeapFlagDefault ); // initializes this heap for use within the given arena, and with the given size limit that it can grow to + void Shutdown( ); // releases this heap from use + + ID_INLINE bool IsInitialized( ) const; // returns true if this heap is currently initialized, and not shutdown + + ID_INLINE void *Allocate( uint sizeBytes, int debugTag = 0 ); // allocate memory + ID_INLINE void *Allocate16( uint sizeBytes, int debugTag = 0 ); // allocate memory aligned on a 16-byte boundary + void Free( void *allocation ); // free memory + + int Msize( void *allocation ); // returns the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). + + void FreeAll( ); // frees all the allocations from this heap (and decommits all pages) + + ID_INLINE bool DoesAllocBelong( void *allocBase ); // returns true if the given address was allocated from this heap, false otherwise + + ID_INLINE rvHeapArena *GetArena( ); // returns the arena this heap is associated with + + ID_INLINE void PushCurrent( ); // makes this heap the new top of stack for its associated arena, thus making it current + ID_INLINE void PopCurrent( ); // pops this heap, or any other heap, from the top of stack of this heap's associated arena, thus making its predecessor current + + ID_INLINE uint GetSize( ) const; // returns the current size that this heap has grown to, in bytes + ID_INLINE uint GetMaxSize( ) const; // returns the maximum size that this heap can grow to, in bytes + + ID_INLINE uint GetCommittedSize( ) const; // returns the number of physical bytes this heap is currently using (total bytes of all committed pages) + ID_INLINE uint GetBytesAllocated( ) const; // returns the number of bytes currently allocated from this heap (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE uint GetBytesRequested( ) const; + ID_INLINE int GetNumAllocations( ) const; // returns the number of allocations made from this heap that are still active (have not been freed) + + ID_INLINE uint GetBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const; // returns the number of bytes currently allocated from this heap with the given allocation tag (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE uint GetPeekBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const; // returns the peek number of bytes allocated from this heap with the given allocation tag (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE int GetNumAllocationsByTag( Mem_Alloc_Types_t allocType ) const; // returns the current number of allocation from this heap with the given allocation tag + + ID_INLINE bool IsWriteCombine( ) const; // returns true if memory allocated from this heap has a write-combine cache policy, false otherwise + ID_INLINE bool IsNoCache( ) const; // returns true if memory allocated from this heap has a no-cache policy, false otherwise + + int SmallBlockCount() const { return NUM_SMALL_BLOCK_TABLE_ENTRIES; } + int GetSmallBlockFreeCount( int block ) const; + dword GetSmallBlockFreeSize( int block ) const; + + int GetLargeBlockFreeCount() const; + dword GetLargeBlockFreeSize() const; + + int GetBlockFreeCount( rvFreeMemoryBlock_s* currentBlock ) const; + dword GetBlockFreeSize( rvFreeMemoryBlock_s* currentBlock ) const; + + void SetName(const char* name); + const char* GetName(void) const; + void SetDebugID(byte debugID) {mDebugID=debugID;} + byte DebugID(void) const {return mDebugID;} + +protected: + rvHeapArena *m_arena; // arena associated with this heap + rvHeap *m_next; // next heap belonging to the same arena + CRITICAL_SECTION m_criticalSection; // critical section associated with this heap + + byte *m_baseAddress; // base address of this heap (virtual) + byte *m_zeroLengthAllocPage; // special page for zero-length allocations (remains uncommitted) + byte *m_heapStorageStart; // address of the start of this heap's storage (just past page table) + + rvFreeMemoryBlock_s *m_smallFreeBlocks[NUM_SMALL_BLOCK_TABLE_ENTRIES]; // table used to manage linked-lists of small free memory blocks + rvFreeMemoryBlock_s *m_largeFreeBlocks; // linked-list of all of the large free memory blocks + + dword *m_pageCommitStateTable; // a very long bit mask (1-bit per page) that describes whether the page has been committed or not to physical memory + freePageRange_t *m_smallFreePageRangeTable; // array of structures that each correspond to a particular page - the structures representing the pages at the start of a small range are added to lists within the m_smallFreePageRangeLists[] array below + + freePageRange_t *m_smallFreePageRangeLists[MAX_SMALL_PAGE_RANGE]; // array where each entry is a linked-list of structures that individually describe a contiguous range of pages (where the range count matches the index into this array plus 1) + freeLargePageRange_t *m_largeFreePageRangeList; // linked-list of structures that individually describe a large contiguous range of pages + + // the following two data members are just used to manage the storage of the freeLargePageRange_t structures + freeLargePageRange_t *m_largeFreeRangeReuseList; // linked-list of freeLargePageRange_t that are currently not part of the m_largeFreePageRangeList linked-list and are available for use + freeLargePageRange_t *m_largeFreeRangeStorage; // array that acts as a store of the freeLargePageRange_t structures + uint m_largeFreeRangeStorageSize; // number of entries in the m_largeFreeRangeStorage[] array + uint m_pageCommitStateArrayByteSize; // size of the m_pageCommitStateTable[] array in bytes + uint m_smallFreePageRangeByteSize; // size of the m_smallFreePageRangeTable[] array in bytes + + uint m_numPages; // number of pages that can span this heap entirely + + uint m_heapRangeBytes; // the number of bytes in the virtual address space of this heap (including zero-length allocation page) + + uint m_committedSizeBytes; // the number of physical bytes this heap is currently using (total bytes of all committed pages) + uint m_maxHeapSizeBytes; // the maximum size that this heap can grow to, in bytes (allocatable space) + + uint m_numBytesRequested; // the number of bytes currently requested for allocation from this heap (memory footprint without the overhaed of internal headers and alignment bytes) + uint m_numBytesAllocated; // the number of bytes currently allocated from this heap (actual memory footprint of all active allocations including internal headers and alignment bytes) + + uint m_allocatedBytesByTag[MA_MAX]; // the number of bytes allocated from this heap - organized by usage tag + uint m_peekAllocatedBytesByTag[MA_MAX]; // the peek number of bytes allocated from this heap - organized by usage tag + int m_numAllocationsByTag[MA_MAX]; // the number of allocations - organized by usage tag + + int m_numAllocations; // the number of allocations made from this heap that are still active (have not been freed) + int m_numZeroLengthAllocations; // the number of zero length allocations made from this heap + + uint m_largestFreeSmallBlockOffset; // small block table offset of the largest free small block available (block that is part of m_smallFreeBlocks[] table) + + uint m_flags; // flags that describe various properties of this heap + + byte mDebugID; // ID that identifies heap. Needed to absolutely identify a heap. + char m_name[20]; // for debug display + + void ResetValues( ); // resets the data members to their pre-initialized state + + void BuildLargeFreeRangeReuseList( ); // builds the m_largeFreeRangeReuseList linked-list from the m_largeFreeRangeStorage[] array. + + byte *AllocateMemory( uint sizeBytes, int debugTag, bool align16Flag ); // performs memory allocation for the given number of bytes from this heap + + rvMemoryBlock_s *AllocateMemorySmallBlock( uint sizeBytes ); // perform a small block allocation - try to use an existing free block pointed to by the table. + rvMemoryBlock_s *AllocateBlockFromTable( uint smallBlockTableOffset, uint sizeBytes ); // allocates the a small memory block from the free block table and partitions it, if necessary, into an allocated chunk and a free chunk (which remains in the m_smallFreeBlocks[] table). + rvMemoryBlock_s *AllocateMemoryLargeBlock( uint sizeBytes ); // perform a large block allocation - try to use an existing free block from within the large free block linked-list. + + void TestLargestFreeSmallBlockOffset( uint smallBlockTableOffset ); // checks to see if the largest free small block has moved down + + void FixUpNextBlocksPrev( byte *newBlock, uint blockSize ); // fixes up the previous pointer of the memory block that lies immediately past the given newBlock, if it remains on the same page. + + void AddFreeBlock( rvFreeMemoryBlock_s *freeBlock ); // adds the given free block to the small allocation table or large allocation list. + void RemoveFreeBlock( rvFreeMemoryBlock_s *freeBlock ); // removes the given block from the small allocation table or large allocation list. + + void *PageCommit( uint numDesiredPages ); // commits the given number of contiguous pages to physical memory + void PageUncommit( byte *pageAddress, uint numPages ); // uncommits the given range of contiguous pages back to physical memory + + void RemoveFromSmallFreeRangeList( uint pageOffset, uint freeBlockPageCount ); // removes the given page range from the m_smallFreePageRangeLists[] + void RemoveFromLargeFreeRangeList( uint pageOffset, uint &startPageOffset, uint &pageCount ); // removes the given page range from the m_largeFreePageRangeList + + void *CommitPageRange( uint startPageOffset, uint numPages ); // commits the given range of pages and flags them as committed within the m_pageCommitStateTable array + void UncommitPageRange( uint startPageOffset, uint numPages ); // uncommits the given range of pages and clears their flags within the m_pageCommitStateTable array + + void EnterHeapCriticalSection(); // enters this heap's critical section + void ExitHeapCriticalSection(); // exits this heap's critical section + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + void TestFreeBlockValidity(); // test for free memory block merge problems +#endif + + friend class rvHeapArena; // give the rvHeapArena class access to the following methods + // { + ID_INLINE void SetArena( rvHeapArena *arena ); // sets the arena this heap is associated with + ID_INLINE void SetNext( rvHeap *next ); // sets the next heap associated with the same arena + ID_INLINE rvHeap *GetNext( ); // returns the next heap associated with the same arena + // } +}; + +// IsInitialized +// +// returns: true if this heap is currently initialized, and not shutdown +ID_INLINE bool rvHeap::IsInitialized( ) const +{ + return m_arena != NULL; +} + +// Allocate +// +// allocate memory +void *rvHeap::Allocate( uint sizeBytes, int debugTag ) +{ + return AllocateMemory( sizeBytes, debugTag, false ); +} + +// Allocate16 +// +// allocate memory aligned on a 16-byte boundary +void *rvHeap::Allocate16( uint sizeBytes, int debugTag ) +{ + return AllocateMemory( sizeBytes, debugTag, true ); +} + +// DoesAllocBelong +// +// returns: true if the given address was allocated from this heap, false otherwise +ID_INLINE bool rvHeap::DoesAllocBelong( void *allocBase ) +{ + return allocBase >= m_zeroLengthAllocPage && allocBase < m_heapStorageStart + m_heapRangeBytes; +} + +// GetArena +// +// returns: the arena this heap is associated with +ID_INLINE rvHeapArena *rvHeap::GetArena( ) +{ + return m_arena; +} + +// GetNext +// +// returns: the next heap belonging to the same arena +ID_INLINE rvHeap *rvHeap::GetNext( ) +{ + return m_next; +} + +// PushCurrent +// +// makes this heap the new top of stack for its associated arena, thus making it current. +ID_INLINE void rvHeap::PushCurrent( ) +{ + m_arena->Push( *this ); +} + +// PopCurrent +// +// pops this heap, or any other heap, from the top of stack of this heap's associated arena, +// thus making its predecessor current +ID_INLINE void rvHeap::PopCurrent( ) +{ + m_arena->Pop( ); +} + +// GetCommittedSize +// +// returns: the number of physical bytes this heap is currently using (total bytes of all committed pages) +ID_INLINE uint rvHeap::GetCommittedSize( ) const +{ + return m_committedSizeBytes; +} + +// GetMaxSize +// +// returns the maximum size that this heap can grow to, in bytes +ID_INLINE uint rvHeap::GetMaxSize( ) const +{ + return m_maxHeapSizeBytes; +} + +// GetBytesAllocated +// +// returns: the number of bytes currently allocated from this heap +// (actual memory footprint of all active allocations +// including internal headers and alignment bytes) +// +ID_INLINE uint rvHeap::GetBytesAllocated( ) const +{ + return m_numBytesAllocated; +} + +// GetBytesRequested +// +// returns: the number of bytes currently requested from this heap +// (i.e. without overhead) +// +ID_INLINE uint rvHeap::GetBytesRequested( ) const +{ + return m_numBytesRequested; +} + +// GetNumAllocations +// +// returns: the number of allocations made from this heap that are still active (have not been freed) +ID_INLINE int rvHeap::GetNumAllocations( ) const +{ + return m_numAllocations; +} + +// GetBytesAllocatedByTag +// +// returns: the number of bytes currently allocated from this heap with the given allocation tag +// (actual memory footprint of all active allocations including internal headers and +// alignment bytes). +ID_INLINE uint rvHeap::GetBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_allocatedBytesByTag[(uint) allocType]; +} + +// GetPeekBytesAllocatedByTag +// +// returns: the peek number of bytes allocated from this heap with the given allocation tag (actual +// memory footprint of all active allocations including internal headers and alignment bytes) +ID_INLINE uint rvHeap::GetPeekBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_peekAllocatedBytesByTag[(uint) allocType]; +} + +// GetNumAllocationsByTag +// +// returns: the current number of allocation from this heap with the given allocation tag +ID_INLINE int rvHeap::GetNumAllocationsByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_numAllocationsByTag[(uint) allocType]; +} + +// IsWriteCombine +// +// returns: true if memory allocated from this heap has a write-combine cache policy, false otherwise +ID_INLINE bool rvHeap::IsWriteCombine( ) const +{ + return (m_flags & rvHeapFlagWriteCombine) != 0; +} + +// IsNoCache +// +// returns: true if memory allocated from this heap has a no-cache policy, false otherwise +ID_INLINE bool rvHeap::IsNoCache( ) const +{ + return (m_flags & rvHeapFlagNoCache) != 0; +} + +// SetArena +// +// sets the arena this heap is associated with +ID_INLINE void rvHeap::SetArena( rvHeapArena *arena ) +{ + m_arena = arena; +} + +// SetNext +// +// sets the next heap associated with the same arena +ID_INLINE void rvHeap::SetNext( rvHeap *next ) +{ + m_next = next; +} + +extern void Mem_FragmentationStats_f( const class idCmdArgs &args ); + +#endif // #ifndef __RV_HEAP_H__ diff --git a/source/idlib/rvHeapArena.cpp b/source/idlib/rvHeapArena.cpp new file mode 100644 index 0000000..f6b4646 --- /dev/null +++ b/source/idlib/rvHeapArena.cpp @@ -0,0 +1,303 @@ +// +// rvHeapArena.cpp - Heap arena object that manages a set of heaps +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +// rvHeapArena +// +// constructor +rvHeapArena::rvHeapArena() +{ + // ResetValues(); do this in the Init() call instead (due to the fact that other constructors could call rvHeapArena::Init() before this constructor is called) +} + +// ~rvHeapArena +// +// destructor +rvHeapArena::~rvHeapArena() +{ + Shutdown(); +} + +// Init +// +// initializes this heap arena for use +void rvHeapArena::Init( ) +{ + if ( m_isInitialized ) + { + return; + } + + ResetValues(); + m_isInitialized = true; + + // create the critical section used by this heap arena + InitializeCriticalSection( &m_criticalSection ); +} + +// Shutdown +// +// releases this heap arena from use (shutting down all associated heaps) +void rvHeapArena::Shutdown( ) +{ + // shutdown each heap from this arena's list + rvHeap *curHeap = m_heapList, *nextHeap; + while ( curHeap != NULL ) + { + nextHeap = curHeap->GetNext(); + + curHeap->Shutdown(); + + curHeap = nextHeap; + } + DeleteCriticalSection( &m_criticalSection ); + ResetValues(); +} + +// ResetValues +// +// resets the data members to their pre-initialized state +void rvHeapArena::ResetValues( ) +{ + memset( m_heapStack, 0, sizeof(m_heapStack) ); + memset( &m_criticalSection, 0, sizeof(m_criticalSection) ); + m_tos = -1; + m_heapList = NULL; + m_isInitialized = false; +} + +// Push +// +// pushes the given heap onto the top of the stack making it the active one for this arena +void rvHeapArena::Push( rvHeap &newActiveHeap ) +{ + EnterArenaCriticalSection(); + assert( newActiveHeap.GetArena() == this ); + assert(m_tos+1 < maxHeapStackDepth); // stack overflow? + if (m_tos+1 < maxHeapStackDepth) + { + m_heapStack[++m_tos] = &newActiveHeap; + } + ExitArenaCriticalSection(); +} + +// Pop +// +// pops the top of the stack, restoring the previous heap as the active heap for this arena +void rvHeapArena::Pop( ) +{ + EnterArenaCriticalSection(); + assert(m_tos > -1); // stack underflow? + if (m_tos > -1) + { + m_tos--; + } + ExitArenaCriticalSection(); +} + +// GetHeap +// +// returns: the heap that the given allocation was made from, NULL for none +rvHeap *rvHeapArena::GetHeap( void *p ) +{ + EnterArenaCriticalSection(); + if ( !m_isInitialized ) + { + ExitArenaCriticalSection(); + return NULL; + } + + rvHeap *curHeap = m_heapList; + while ( curHeap != NULL && !curHeap->DoesAllocBelong(p) ) + { + curHeap = curHeap->GetNext(); + } + ExitArenaCriticalSection(); + + return curHeap; +} + + +// Allocate +// +// allocates the given amount of memory from this arena. +void *rvHeapArena::Allocate( unsigned int sizeBytes, int debugTag ) +{ + rvHeap *curHeap; + + EnterArenaCriticalSection(); + assert( m_tos >= 0 && m_tos < maxHeapStackDepth ); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + curHeap = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + + return curHeap->Allocate( sizeBytes, debugTag ); +} + +// Allocate16 +// +// allocates the given amount of memory from this arena, +// aligned on a 16-byte boundary. +void *rvHeapArena::Allocate16( unsigned int sizeBytes, int debugTag ) +{ + rvHeap *curHeap; + + EnterArenaCriticalSection(); + assert( m_tos >= 0 && m_tos < maxHeapStackDepth ); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + curHeap = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + + return curHeap->Allocate16( sizeBytes, debugTag ); +} + +// Free +// +// free memory back to this arena +void rvHeapArena::Free( void *p ) +{ + rvHeap *heap = GetHeap( p ); // arena critical section protection is in GetHeap() + if (heap != NULL) + { + heap->Free( p ); + } +} + +// Msize +// +// returns: the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). +int rvHeapArena::Msize( void *p ) +{ + rvHeap *heap = GetHeap( p ); // arena critical section protection is in GetHeap() + if (heap != NULL) + { + return heap->Msize( p ); + } + return 0; +} + +// InitHeap +// +// initializes the given heap to be under the care of this arena +void rvHeapArena::InitHeap( rvHeap &newActiveHeap ) +{ + assert( newActiveHeap.GetArena() == NULL ); + + newActiveHeap.SetArena( this ); + newActiveHeap.SetNext( m_heapList ); + m_heapList = &newActiveHeap; +} + +// ShutdownHeap +// +// releases the given heap from the care of this arena +void rvHeapArena::ShutdownHeap( rvHeap &activeHeap ) +{ + int stackPos, copyPos; + + assert( activeHeap.GetArena() == this ); + + activeHeap.SetArena( NULL ); + + // make sure that the heap is removed from the stack + for ( stackPos = 0; stackPos <= m_tos; stackPos++ ) + { + if ( m_heapStack[stackPos] == &activeHeap ) + { + for ( copyPos = stackPos; copyPos < m_tos; copyPos++ ) + { + m_heapStack[copyPos] = m_heapStack[copyPos+1]; + } + m_tos--; + } + } + + // remove the heap from this arena's list + rvHeap *curHeap = m_heapList, * prevHeap = NULL; + while ( curHeap != NULL ) + { + if ( curHeap == &activeHeap ) + { + if ( NULL == prevHeap ) + { + m_heapList = m_heapList->GetNext(); + } + else + { + prevHeap->SetNext( curHeap->GetNext() ); + } + break; + } + prevHeap = curHeap; + curHeap = curHeap->GetNext(); + } +} + +// GetNextHeap +// +// returns: that follows the given one (associated with this arena), NULL for none +rvHeap *rvHeapArena::GetNextHeap( rvHeap &rfPrevHeap ) +{ + rvHeap *nextHeap; + + EnterArenaCriticalSection(); + if ( rfPrevHeap.GetArena() != this ) + { + nextHeap = NULL; + } + else + { + nextHeap = rfPrevHeap.GetNext(); + } + ExitArenaCriticalSection(); + + return nextHeap; +} + +// GetTagStats +// +// returns: the total stats for a particular tag type (across all heaps managed by this arena) +void rvHeapArena::GetTagStats(int tag, int &num, int &size, int &peak) +{ + int curPeak; + + assert( tag < MA_MAX ); + + EnterArenaCriticalSection(); + + num = size = peak = 0; + + rvHeap *curHeap = m_heapList; + while ( curHeap != NULL ) + { + num += curHeap->GetNumAllocationsByTag( (Mem_Alloc_Types_t) tag ); + size += curHeap->GetBytesAllocatedByTag( (Mem_Alloc_Types_t) tag ); + + curPeak = curHeap->GetPeekBytesAllocatedByTag( (Mem_Alloc_Types_t) tag ); + if ( curPeak > peak ) + { + peak = curPeak; + } + + curHeap = curHeap->GetNext(); + } + + ExitArenaCriticalSection(); +} + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/source/idlib/rvHeapArena.h b/source/idlib/rvHeapArena.h new file mode 100644 index 0000000..c0dc312 --- /dev/null +++ b/source/idlib/rvHeapArena.h @@ -0,0 +1,139 @@ +// +// rvHeapArena.h - Heap arena object that manages a set of heaps +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#ifndef __RV_HEAP_ARENA_H__ +#define __RV_HEAP_ARENA_H__ + +class rvHeap; + +// Define some limits used by a heap arena +static const int maxNumHeapsPerArena = 16; // maximum number of heaps that can be simultaneously initialized with the same arena +static const int maxHeapStackDepth = maxNumHeapsPerArena*2; // maximum depth of each arena's heap stack + + +class rvHeapArena { +public: + rvHeapArena(); // constructor + ~rvHeapArena(); // destructor + + void Init( ); // initializes this heap arena for use + void Shutdown( ); // releases this heap arena from use (shutting down all associated heaps) + + ID_INLINE bool IsInitialized( ) const; // returns true if this rvHeapArena object is currently initialized and not released, false otherwise + + // Push the current heap through the heap object itself (you can Pop() there as well) + void Pop( ); // pops the top of the stack, restoring the previous heap as the active heap for this arena + ID_INLINE bool IsStackFull( ); // returns true if the heap arena stack is full, false otherwise + + ID_INLINE rvHeap *GetActiveHeap( ); // returns the active heap for this arena (from the top of the stack, NULL if stack is empty) + + ID_INLINE void EnterArenaCriticalSection(); // enters this heap arena's critical section + ID_INLINE void ExitArenaCriticalSection(); // exits this heap arena's critical section + + void *Allocate( unsigned int sizeBytes, int debugTag = 0); // allocates the given amount of memory from this arena + void *Allocate16( unsigned int sizeBytes, int debugTag = 0); // allocates the given amount of memory from this arena, aligned on a 16-byte boundary + void Free( void *p ); // free memory back to this arena + + int Msize( void *p ); // returns the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). + + rvHeap *GetHeap( void *p ); // returns the heap that the given allocation was made from, NULL for none + + ID_INLINE rvHeap *GetFirstHeap( ); // returns the first heap associated with this arena, NULL for none + rvHeap *GetNextHeap( rvHeap &rfPrevHeap ); // returns that follows the given one (associated with this arena), NULL for none + + void GetTagStats(int tag, int &num, int &size, int &peak); // returns the total stats for a particular tag type (across all heaps managed by this arena) + +protected: + // NOTE: we cannot use idList here - dynamic memory is not yet available. + rvHeap *m_heapStack[maxHeapStackDepth]; // stack of heap object pointers + CRITICAL_SECTION m_criticalSection; // critical section associated with this heap + int m_tos; // top of stack + rvHeap *m_heapList; // linked-list of all the heaps that are actively associated with this arena + bool m_isInitialized; // set to true if this rvHeapArena object is currently initialized and not released + + void ResetValues( ); // resets the data members to their pre-initialized state + + friend class rvHeap; // give the rvHeap class access to the following methods + // { + void Push( rvHeap &newActiveHeap ); // pushes the given heap onto the top of the stack making it the active one for this arena + void InitHeap( rvHeap &newActiveHeap ); // initializes the given heap to be under the care of this arena + void ShutdownHeap( rvHeap &newActiveHeap ); // releases the given heap from the care of this arena + // } +}; + +// IsInitialized +// +// returns: true if this rvHeapArena object is currently initialized and not released, false otherwise +ID_INLINE bool rvHeapArena::IsInitialized( ) const +{ + return m_isInitialized; +} + +// EnterArenaCriticalSection +// +// enters this heap arena's critical section +ID_INLINE void rvHeapArena::EnterArenaCriticalSection() +{ + ::EnterCriticalSection( &m_criticalSection ); +} + +// ExitArenaCriticalSection +// +// exits this heap arena's critical section +ID_INLINE void rvHeapArena::ExitArenaCriticalSection() +{ + ::LeaveCriticalSection( &m_criticalSection ); +} + +// IsStackFull +// +// returns: true if the heap arena stack is full, false otherwise +ID_INLINE bool rvHeapArena::IsStackFull( ) +{ + bool full; + + EnterArenaCriticalSection(); + full = m_tos >= (maxHeapStackDepth - 1); + ExitArenaCriticalSection(); + + return full; +} + +// GetActiveHeap +// +// returns: the active heap for this arean (from the top of the stack, NULL if stack is empty) +ID_INLINE rvHeap *rvHeapArena::GetActiveHeap( ) +{ + rvHeap *tos; + EnterArenaCriticalSection(); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + tos = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + return tos; +} + +// GetFirstHeap +// +// returns: the first heap associated with this arena, NULL for none +ID_INLINE rvHeap *rvHeapArena::GetFirstHeap( ) +{ + rvHeap *firstHeap; + + EnterArenaCriticalSection(); + firstHeap = m_heapList; + ExitArenaCriticalSection(); + + return firstHeap; +} + +#endif // #ifndef __RV_HEAP_MANAGER_H__ + + + diff --git a/source/idlib/rvMemSys.cpp b/source/idlib/rvMemSys.cpp new file mode 100644 index 0000000..62bc680 --- /dev/null +++ b/source/idlib/rvMemSys.cpp @@ -0,0 +1,1268 @@ +// +// rvMemSys.cpp - Memory management system +// Date: 12/17/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +static memoryStats_t mem_total_allocs = { 0, 0x0fffffff, -1, 0 }; +static memoryStats_t mem_frame_allocs; +static memoryStats_t mem_frame_frees; + +rvHeapArena mainHeapArena; +rvHeapArena *currentHeapArena; // this is the main heap arena that all other heaps use +rvHeap defaultHeap; // this is the default system heap + +static rvHeap *systemHeapArray[MAX_SYSTEM_HEAPS]; // array of pointers to rvHeaps that are common to idLib, Game, and executable + +#if defined(_WIN32) && !defined(_XENON) +static LPVOID sharedMem = NULL; // pointer to shared memory +static HANDLE hMapObject = NULL; // handle to file mapping +#endif + +// Descriptions that go with each tag. When updating the tag enum in Heap.h please +// update this list as well. +// (also update the list in Heap.cpp) +char *TagNames[] = { + "none", + "New operation", + "default", + "Lexer", + "Parser", + "AAS routing", + "Class", + "Script program", + "Collision Model", + "CVar", + "Decl System", + "File System", + "Images", + "Materials", + "Models", + "Fonts", + "Main renderer", + "Vertex data", + "Sound", + "Window", + "Event loop", + "Math - Matrices and vectors", + "Animation", + "Dynamic Blocks", + "Strings", + "GUI", + "Effects", + "Entities", + "Physics", + "AI", + "Network", + "Not Used" +}; + +template +class TagTableCheck +{ +private: + TagTableCheck(); +}; + +template<> +class TagTableCheck<1> +{ +}; + +// An error here means you need to synchronize TagNames and Mem_Alloc_Types_t +TagTableCheck TagTableCheckedHere; +// An error here means there are too many tags. No more than 32! +TagTableCheck TagMaxCheckedHere; + +#ifndef ENABLE_INTEL_SMP +MemScopedTag* MemScopedTag::mTop = NULL; +#endif + +/* +================== +GetMemAllocStats + +Gets some memory allocation stats based on a particular tag. +================== +*/ +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak) +{ + assert( tag < MA_MAX ); + + currentHeapArena->GetTagStats( tag, num, size, peak ); + return TagNames[tag]; +} + +/* +================== +rvSetSysHeap + +sets the system heap that is associated with the given heap ID value +================== +*/ +void rvSetSysHeap(Rv_Sys_Heap_ID_t sysHeapID, rvHeap *heapPtr) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + if ( NULL == heapPtr ) + { + // set the system heap back to the default heap + systemHeapArray[ (uint) sysHeapID ] = &defaultHeap; + } + else + { + systemHeapArray[ (uint) sysHeapID ] = heapPtr; + } +} + +/* +================== + rvPushSysHeap + +pushes the system heap associated with the given identifier to the top of the arena stack, making it current. +================== +*/ +void rvPushSysHeap(Rv_Sys_Heap_ID_t sysHeapID) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + assert( systemHeapArray[ (uint) sysHeapID ] != NULL ); + systemHeapArray[ (uint) sysHeapID ]->PushCurrent( ); +} + +/* +================== + rvEnterArenaCriticalSection + +enters the heap arena critical section. +================== +*/ +void rvEnterArenaCriticalSection( ) +{ + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + assert( currentHeapArena != NULL ); + currentHeapArena->EnterArenaCriticalSection( ); +} + +/* +================== + rvExitArenaCriticalSection + +exits the heap arena critical section +================== +*/ +void rvExitArenaCriticalSection( ) +{ + assert( currentHeapArena != NULL ); + currentHeapArena->ExitArenaCriticalSection( ); +} + +/* +================== +Mem_Init + +initializes the memory system for use +================== +*/ +void Mem_Init( void ) +{ + if ( NULL == currentHeapArena ) + { + +#if defined(_WIN32) && !defined(_XENON) + hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file + NULL, // default security attributes + PAGE_READWRITE, // read/write access + 0, // size: high 32-bits + sizeof(systemHeapArray)+sizeof(currentHeapArena), // size: low 32-bits + "quake4share"); // name of map object + if ( hMapObject == NULL ) + { + return; + } + + // The first process to attach initializes memory. + bool firstInit = (GetLastError() != ERROR_ALREADY_EXISTS); + + // Get a pointer to the file-mapped shared memory. + sharedMem = MapViewOfFile(hMapObject, // object to map view of + FILE_MAP_WRITE, // read/write access + 0, // high offset: map from + 0, // low offset: beginning + 0); // default: map entire file + if ( sharedMem == NULL ) + { + return; + } + + if ( !firstInit ) + { + memcpy( ¤tHeapArena, sharedMem, sizeof(currentHeapArena) ); + memcpy( &systemHeapArray, (byte*) sharedMem + sizeof(currentHeapArena), sizeof(systemHeapArray) ); + + UnmapViewOfFile( sharedMem ); + CloseHandle( hMapObject ); + + sharedMem = NULL; + hMapObject = NULL; + return; + } +#endif + + currentHeapArena = &mainHeapArena; + currentHeapArena->Init(); + defaultHeap.Init( *currentHeapArena, 256*1024*1024 ); + defaultHeap.SetDebugID(0); + defaultHeap.SetName("DEFAULT"); + rvSetSysHeap( RV_HEAP_ID_DEFAULT, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_PERMANENT, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_LEVEL, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_MULTIPLE_FRAME, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_SINGLE_FRAME, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_TEMPORARY, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_IO_TEMP, &defaultHeap ); + RV_PUSH_SYS_HEAP_ID( RV_HEAP_ID_DEFAULT ); + +#if defined(_WIN32) && !defined(_XENON) + memcpy( sharedMem, ¤tHeapArena, sizeof(currentHeapArena) ); + memcpy( (byte*) sharedMem + sizeof(currentHeapArena), &systemHeapArray, sizeof(systemHeapArray) ); +#endif + } +} + +/* +================== +Mem_Shutdown + +Shuts down the memory system from all further use +================== +*/ +void Mem_Shutdown( void ) +{ +#if defined(_WIN32) && !defined(_XENON) + if ( sharedMem != NULL ) + { + UnmapViewOfFile( sharedMem ); + CloseHandle( hMapObject ); + sharedMem = NULL; + hMapObject = NULL; + } +#endif + + if ( currentHeapArena == &mainHeapArena ) + { + memset(systemHeapArray, 0, sizeof(systemHeapArray)); + defaultHeap.Shutdown(); + currentHeapArena->Shutdown(); + } +} + +void Mem_EnableLeakTest( const char *name ) +{ + +} + +/* +================== +Mem_UpdateStats +================== +*/ +void Mem_UpdateStats( memoryStats_t &stats, int size ) +{ + stats.num++; + if ( size < stats.minSize ) { + stats.minSize = size; + } + if ( size > stats.maxSize ) { + stats.maxSize = size; + } + stats.totalSize += size; +} + +/* +================== +Mem_UpdateAllocStats +================== +*/ +void Mem_UpdateAllocStats( int size ) +{ + Mem_UpdateStats( mem_frame_allocs, size ); + Mem_UpdateStats( mem_total_allocs, size ); +} + +/* +================== +Mem_UpdateFreeStats +================== +*/ +void Mem_UpdateFreeStats( int size ) { + Mem_UpdateStats( mem_frame_frees, size ); + mem_total_allocs.num--; + mem_total_allocs.totalSize -= size; +} + +/* +================== +Mem_ClearFrameStats +================== +*/ +void Mem_ClearFrameStats( void ) +{ + mem_frame_allocs.num = mem_frame_frees.num = 0; + mem_frame_allocs.minSize = mem_frame_frees.minSize = 0x0fffffff; + mem_frame_allocs.maxSize = mem_frame_frees.maxSize = -1; + mem_frame_allocs.totalSize = mem_frame_frees.totalSize = 0; +} + +/* +================== +Mem_GetFrameStats +================== +*/ +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ) +{ + allocs = mem_frame_allocs; + frees = mem_frame_frees; +} + +/* +================== +Mem_GetStats +================== +*/ +void Mem_GetStats( memoryStats_t &stats ) +{ + stats = mem_total_allocs; +} + +void Mem_AllocDefragBlock( void ) +{ + +} + +/* +================== +Mem_ShowMemAlloc_f +================== +*/ +void Mem_ShowMemAlloc_f( const idCmdArgs &args ) +{ + const char *tagName; + int tag, num, size, peak; + DWORD totalOutstanding = 0; + + for ( tag = 1; tag < MA_DO_NOT_USE; tag++ ) + { + tagName = GetMemAllocStats( tag, num, size, peak ); + + if ( size || peak ) + { + idLib::common->Printf("%-25s peak %9d curr %9d count %9d\n", tagName, peak, size, num ); + totalOutstanding += size; + } + } + idLib::common->Printf("Mem_Alloc Outstanding: %d\n", totalOutstanding); +} + +#ifdef ID_DEBUG_MEMORY + +#undef Mem_Alloc +#undef Mem_ClearedAlloc +#undef Com_ClearedReAlloc +#undef Mem_Free +#undef Mem_CopyString +#undef Mem_Alloc16 +#undef Mem_Free16 + +#define MAX_CALLSTACK_DEPTH 10 + +// size of this struct must be a multiple of 16 bytes +typedef struct debugMemory_s { + const char * fileName; + short lineNumber; + byte heapId; + byte memTag; + int frameNumber; + int size; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct debugMemory_s * prev; + struct debugMemory_s * next; +} debugMemory_t; + +static debugMemory_t * mem_debugMemory = NULL; +static char mem_leakName[256] = ""; + +/* +================== +Mem_CleanupFileName +================== +*/ +const char *Mem_CleanupFileName( const char *fileName ) +{ + int i1, i2; + idStr newFileName; + static char newFileNames[4][MAX_STRING_CHARS]; + static int index; + + newFileName = fileName; + newFileName.BackSlashesToSlashes(); + i1 = newFileName.Find( "neo", false ); + if ( i1 >= 0 ) { + i1 = newFileName.Find( "/", false, i1 ); + newFileName = newFileName.Right( newFileName.Length() - ( i1 + 1 ) ); + } + while( 1 ) { + i1 = newFileName.Find( "/../" ); + if ( i1 <= 0 ) { + break; + } + i2 = i1 - 1; + while( i2 > 1 && newFileName[i2-1] != '/' ) { + i2--; + } + newFileName = newFileName.Left( i2 - 1 ) + newFileName.Right( newFileName.Length() - ( i1 + 4 ) ); + } + index = ( index + 1 ) & 3; + strncpy( newFileNames[index], newFileName.c_str(), sizeof( newFileNames[index] ) ); + return newFileNames[index]; +} + +/* +================== +Mem_Dump +================== +*/ +void Mem_Dump( const char *fileName ) +{ + int i, numBlocks, totalSize; + char dump[32], *ptr; + debugMemory_t *b; + idStr module, funcName; + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + totalSize = 0; + for ( numBlocks = 0, b = mem_debugMemory; b; b = b->next, numBlocks++ ) { + ptr = ((char *) b) + sizeof(debugMemory_t); + totalSize += b->size; + for ( i = 0; i < (sizeof(dump)-1) && i < b->size; i++) { + if ( ptr[i] >= 32 && ptr[i] < 127 ) { + dump[i] = ptr[i]; + } else { + dump[i] = '_'; + } + } + dump[i] = '\0'; + if ( ( b->size >> 10 ) != 0 ) { + fprintf( f, "size: %6d KB: %s, line: %d [%s], call stack: %s\r\n", ( b->size >> 10 ), Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else { + fprintf( f, "size: %7d B: %s, line: %d [%s], call stack: %s\r\n", b->size, Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +} + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) +{ + const char *fileName; + + if ( args.Argc() >= 2 ) { + fileName = args.Argv( 1 ); + } + else { + fileName = "memorydump.txt"; + } + Mem_Dump( fileName ); +} + +typedef struct allocInfo_s +{ + const char * fileName; + short lineNumber; + byte heapId; + byte memTag; + int size; + int numAllocs; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct allocInfo_s * next; +} allocInfo_t; + +typedef enum +{ + MEMSORT_SIZE, + MEMSORT_LOCATION, + MEMSORT_NUMALLOCS, + MEMSORT_CALLSTACK, + MEMSORT_HEAPID, + MEMSORT_MEMTAG +} memorySortType_t; + +/* +================== +Mem_DumpCompressed +================== +*/ +void Mem_DumpCompressed( const char *fileName, memorySortType_t memSort, int sortCallStack, int numFrames, bool verbose ) +{ + int numBlocks, totalSize, r, j; + debugMemory_t *b; + allocInfo_t *a, *nexta, *allocInfo = NULL, *sortedAllocInfo = NULL, *prevSorted, *nextSorted; + idStr module, funcName; + + // build list with memory allocations + totalSize = 0; + numBlocks = 0; + nextSorted = NULL; + + for ( b = mem_debugMemory; b; b = b->next ) { + + if ( numFrames && b->frameNumber < idLib::frameNumber - numFrames ) { + continue; + } + + numBlocks++; + totalSize += b->size; + + // search for an allocation from the same source location + for ( a = allocInfo; a; a = a->next ) + { + if ( a->lineNumber != b->lineNumber ) + { + continue; + } +#ifndef _XENON + // removed the call stack info for better consolidation of info and speed of dump + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + if ( a->callStack[j] != b->callStack[j] ) { + break; + } + } + if ( j < MAX_CALLSTACK_DEPTH ) { + continue; + } +#endif + if ( idStr::Cmp( a->fileName, b->fileName ) != 0 ) { + continue; + } + + if ( a->heapId != b->heapId ) + { + continue; + } + + a->numAllocs++; + a->size += b->size; + break; + } + + // if this is an allocation from a new source location + if ( !a ) + { + a = (allocInfo_t *) currentHeapArena->Allocate( sizeof( allocInfo_t ) ); + a->fileName = b->fileName; + a->lineNumber = b->lineNumber; + a->heapId = b->heapId; + a->memTag = b->memTag; + a->size = b->size; + a->numAllocs = 1; + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + a->callStack[j] = b->callStack[j]; + } + a->next = allocInfo; + allocInfo = a; + } + } + + // sort list + for ( a = allocInfo; a; a = nexta ) + { + nexta = a->next; + + prevSorted = NULL; + switch( memSort ) { + // sort on size + case MEMSORT_SIZE: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->size > nextSorted->size ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on file name and line number + case MEMSORT_LOCATION: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + r = idStr::Cmp( Mem_CleanupFileName( a->fileName ), Mem_CleanupFileName( nextSorted->fileName ) ); + if ( r < 0 || ( r == 0 && a->lineNumber < nextSorted->lineNumber ) ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on the number of allocations + case MEMSORT_NUMALLOCS: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->numAllocs > nextSorted->numAllocs ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on call stack + case MEMSORT_CALLSTACK: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->callStack[sortCallStack] < nextSorted->callStack[sortCallStack] ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on heap ID + case MEMSORT_HEAPID: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->heapId > nextSorted->heapId ) { + break; + } + prevSorted = nextSorted; + } + break; + } + } + if ( !prevSorted ) { + a->next = sortedAllocInfo; + sortedAllocInfo = a; + } + else { + prevSorted->next = a; + a->next = nextSorted; + } + } + +// RAVEN BEGIN +// dluetscher: changed xenon version to output anything above 1K to the console +// mwhitlock: added verbose option to show < 1K also since I need to see leaks, +// no matter how small. +#ifdef _XENON + + unsigned int notShownSize = 0; + unsigned int notShownNumAllocs = 0; + + // write list to debug output and console + char buff[256]; + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + + if((a->size >> 10) > 0) + { + idStr::snPrintf(buff, sizeof(buff), "%6d K",( a->size >> 10)); + } + else + { + idStr::snPrintf(buff, sizeof(buff), "%6d B", a->size); + } + + if ( verbose || ((a->size >> 10) > 0 ) ) + { + idLib::common->Printf("size: %s, allocs: %5d: %s, line: %d, heap: %d, call stack: %s\r\n", + buff, a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, a->heapId, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else + { + notShownSize+=(unsigned int)a->size; + notShownNumAllocs+=(unsigned int)a->numAllocs; + } + + currentHeapArena->Free( a ); + } + + idLib::sys->ShutdownSymbols(); + + idLib::common->Printf("%8d bytes in %d allocs not shown\r\n", notShownSize, notShownNumAllocs ); + idLib::common->Printf("%8d total memory blocks allocated\r\n", numBlocks ); + idLib::common->Printf("%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + +#else +// RAVEN END + FILE *f; + +// in case you want to flip the above ifdef and write to disc on the xenon + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + // write list to file + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + fprintf( f, "size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + currentHeapArena->Free( a ); +// RAVEN END + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +#endif +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) +{ + int argNum; + const char *arg, *fileName; + memorySortType_t memSort = MEMSORT_LOCATION; + int sortCallStack = 0, numFrames = 0; + bool verbose = false; + + // get cmd-line options + argNum = 1; + arg = args.Argv( argNum ); + while( arg[0] == '-' ) { + arg = args.Argv( ++argNum ); + if ( idStr::Icmp( arg, "s" ) == 0 ) { + memSort = MEMSORT_SIZE; + } else if ( idStr::Icmp( arg, "l" ) == 0 ) { + memSort = MEMSORT_LOCATION; + } else if ( idStr::Icmp( arg, "a" ) == 0 ) { + memSort = MEMSORT_NUMALLOCS; + } else if ( idStr::Icmp( arg, "cs1" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 2; + } else if ( idStr::Icmp( arg, "cs2" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 1; + } else if ( idStr::Icmp( arg, "cs3" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 0; + } else if ( idStr::Icmp( arg, "h" ) == 0 ) { + memSort = MEMSORT_HEAPID; + } else if ( idStr::Icmp( arg, "v" ) == 0 ) { + verbose = true; + } else if ( arg[0] == 'f' ) { + numFrames = atoi( arg + 1 ); + } else { + idLib::common->Printf( "memoryDumpCompressed [options] [filename]\n" + "options:\n" + " -s sort on size\n" + " -l sort on location\n" + " -a sort on the number of allocations\n" + " -cs1 sort on first function on call stack\n" + " -cs2 sort on second function on call stack\n" + " -cs3 sort on third function on call stack\n" + " -h sort on heap ID\n" + " -f only report allocations the last X frames\n" + " -v verbose (list all, even those totalling less than 1K\n" + "By default the memory allocations are sorted on location.\n" + "By default a 'memorydump.txt' is written if no file name is specified.\n" ); + return; + } + arg = args.Argv( ++argNum ); + } + if ( argNum >= args.Argc() ) { + fileName = "memorydump.txt"; + } else { + fileName = arg; + } + Mem_DumpCompressed( fileName, memSort, sortCallStack, numFrames, verbose ); +} + +/* +================== +Mem_AllocDebugMemory +================== +*/ +void *Mem_AllocDebugMemory( const int size, const char *fileName, const int lineNumber, const bool align16, byte tag ) +{ + void *p; + debugMemory_t *m; + + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + + if ( align16 ) + { + p = currentHeapArena->Allocate16( size + sizeof( debugMemory_t ), tag ); + } + else + { + p = currentHeapArena->Allocate( size + sizeof( debugMemory_t ), tag ); + } + + if ( NULL == p ) + { + return NULL; + } + + Mem_UpdateAllocStats( currentHeapArena->Msize( p ) ); + + m = (debugMemory_t *) p; + m->fileName = fileName; + m->lineNumber = lineNumber; + m->heapId = currentHeapArena->GetHeap(p)->DebugID(); + m->memTag = tag; + m->frameNumber = idLib::frameNumber; + m->size = size; + m->next = mem_debugMemory; + m->prev = NULL; + if ( mem_debugMemory ) { + mem_debugMemory->prev = m; + } + mem_debugMemory = m; + + if ( idLib::sys != NULL ) + { + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + } + else + { + memset( m->callStack, 0, sizeof(m->callStack) ); + } + + return ( ( (byte *) p ) + sizeof( debugMemory_t ) ); +} + +/* +================== +Mem_FreeDebugMemory +================== +*/ +void Mem_FreeDebugMemory( void *p, const char *fileName, const int lineNumber, const bool align16 ) +{ + debugMemory_t *m; + + if ( !p ) + { + return; + } + + m = (debugMemory_t *) ( ( (byte *) p ) - sizeof( debugMemory_t ) ); + + if ( m->size < 0 ) + { + idLib::common->FatalError( "memory freed twice, first from %s, now from %s", idLib::sys->GetCallStackStr( m->callStack, MAX_CALLSTACK_DEPTH ), idLib::sys->GetCallStackCurStr( MAX_CALLSTACK_DEPTH ) ); + } + + Mem_UpdateFreeStats( currentHeapArena->Msize( m ) ); + + if ( m->next ) + { + m->next->prev = m->prev; + } + + if ( m->prev ) + { + m->prev->next = m->next; + } + else + { + mem_debugMemory = m->next; + } + + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = -m->size; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + assert( currentHeapArena != NULL ); + currentHeapArena->Free( m ); +} + +/* +================== +Mem_Alloc +================== +*/ +void *Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + return Mem_AllocDebugMemory( size, fileName, lineNumber, false, tag ); +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ) +{ + if ( !ptr ) + { + return; + } + Mem_FreeDebugMemory( ptr, fileName, lineNumber, false ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +void *Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + void *mem = Mem_AllocDebugMemory( size, fileName, lineNumber, true, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ) +{ + if ( !ptr ) { + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + Mem_FreeDebugMemory( ptr, fileName, lineNumber, true ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void *Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + void *mem = Mem_Alloc( size, fileName, lineNumber, tag ); + + if ( mem != NULL ) + { + SIMDProcessor->Memset( mem, 0, size ); + } + else + { + idLib::common->FatalError( "Ran out of memory during a cleared allocation" ); + } + return mem; +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in, const char *fileName, const int lineNumber ) +{ + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, fileName, lineNumber ); + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) +{ + if ( !ptr ) + { + return 0; + } + assert( currentHeapArena != NULL ); + return currentHeapArena->Msize( (byte*)ptr - sizeof( debugMemory_t ) ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void Mem_Dump_f( const class idCmdArgs &args ) +{ + +} + +void Mem_DumpCompressed_f( const class idCmdArgs &args ) +{ + +} + +/* +================== +Mem_Alloc + +Allocate memory from the heap at the top of the current arena stack +================== +*/ +void *Mem_Alloc( const int size, byte tag ) +{ + void *p; + + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + + p = currentHeapArena->Allocate( size, tag ); + if ( NULL == p ) + { + return NULL; + } + + Mem_UpdateAllocStats( currentHeapArena->Msize( p ) ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(p, size); +#endif + + return p; +} + +// Mem_ClearedAlloc +// +// Allocate memory from the heap at the top of the current arena stack, +// clear that memory to zero before returning it. +void *Mem_ClearedAlloc( const int size, byte tag ) +{ + byte *allocation = (byte *) Mem_Alloc( size, tag ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(allocation, size); +#endif + + if ( allocation != NULL ) + { + SIMDProcessor->Memset( allocation, 0, size ); + } + else + { + idLib::common->FatalError( "Ran out of memory during a cleared allocation" ); + } + return allocation; + +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr ) +{ + if ( !ptr ) + { + return; + } + assert( currentHeapArena != NULL ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnDelete(ptr); +#endif + + Mem_UpdateFreeStats( currentHeapArena->Msize( ptr ) ); + + currentHeapArena->Free( ptr ); +} + +/* +================== +Mem_CopyString + +Allocates memory for a copy of the given string, and copies +that string into the new allocation. +================== +*/ +char *Mem_CopyString( const char *in ) +{ + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, MA_STRING ); + if ( out != NULL ) + { + strcpy( out, in ); + } + else + { + idLib::common->FatalError( "Ran out of memory during string copy allocation" ); + } + return out; +} + +/* +================== +Mem_Alloc16 + +Allocate memory from the heap at the top of the current arena +stack that is aligned on a 16-byte boundary. +================== +*/ +void *Mem_Alloc16( const int size, byte tag ) +{ + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + void *mem = currentHeapArena->Allocate16( size, tag ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(mem, size); +#endif + + return mem; +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free16( void *ptr ) +{ + if ( !ptr ) + { + return; + } + assert( currentHeapArena != NULL ); + currentHeapArena->Free( ptr ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnDelete(ptr); +#endif +} + +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) +{ + if ( !ptr ) + { + return 0; + } + assert( currentHeapArena != NULL ); + return currentHeapArena->Msize( ptr ); +} + +#endif // #else #ifdef ID_DEBUG_MEMORY + +/* +================== +rvGetAllSysHeaps + +retrieves the specified system heap +================== +*/ + +rvHeap* rvGetSysHeap( Rv_Sys_Heap_ID_t sysHeapID ) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + return systemHeapArray[ (uint) sysHeapID ]; +} + +/* +================== +rvGetAllSysHeaps + +retrieves all the MAX_SYSTEM_HEAPS heap pointers into the given array +================== +*/ +void rvGetAllSysHeaps( rvHeap *destSystemHeapArray[MAX_SYSTEM_HEAPS] ) +{ + memcpy(destSystemHeapArray, systemHeapArray, sizeof(systemHeapArray)); +} + +/* +================== +rvSetAllSysHeaps + +associates all the MAX_SYSTEM_HEAPS heap pointers from the given array with their corresponding id value +================== +*/ +void rvSetAllSysHeaps( rvHeap *srcSystemHeapArray[MAX_SYSTEM_HEAPS] ) +{ + memcpy(systemHeapArray, srcSystemHeapArray, sizeof(systemHeapArray)); +} + +#if 0 +/* +================== +rvGetHeapContainingMemory + +Returns pointer to the heap containg the memory specified. If the memory specified +is not in a heap, returns 0. +================== +*/ +rvHeap* rvGetHeapContainingMemory( const void* mem ) +{ + return (currentHeapArena!=0)?currentHeapArena->GetHeap(const_cast(mem)):0; +} +#endif + +/* +================== +rvPushHeapContainingMemory + +Pushes the heap containg the memory specified. If the memory specified +is not in a heap, does nothing and returns false, otherwise returns +true on success. +================== +*/ +bool rvPushHeapContainingMemory( const void* mem ) +{ + if(currentHeapArena) + { + rvHeap* heap=currentHeapArena->GetHeap(const_cast(mem)); + if(heap) + { + heap->PushCurrent(); + return true; + } + } + return false; +} + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/source/idlib/rvMemSys.h b/source/idlib/rvMemSys.h new file mode 100644 index 0000000..10e94ee --- /dev/null +++ b/source/idlib/rvMemSys.h @@ -0,0 +1,144 @@ +// +// rvMemSys.h - Memory management system +// Date: 12/17/04 +// Created by: Dwight Luetscher +// +// Date: 04/01/05 +// Modified by: Marcus Whitlock +// Added permanent heap and new heap push function to push the heap that contains +// a specified piece of memory. +// +// Date: 04/04/05 +// Modified by: Marcus Whitlock +// Added rvAutoHeapCtxt class for push and auto-pop of heap context. +// + +#ifndef __RV_MEM_SYS_H__ +#define __RV_MEM_SYS_H__ + +typedef enum +{ + RV_HEAP_ID_DEFAULT, // heap that exists on application startup + RV_HEAP_ID_PERMANENT, // heap for allocations that have permanent (application scope) lifetime + RV_HEAP_ID_LEVEL, // heap for allocations that have a level lifetime + RV_HEAP_ID_MULTIPLE_FRAME, // heap for run-time allocations that have a lifetime of multiple draw frames + RV_HEAP_ID_SINGLE_FRAME, // heap for run-time allocations that have a lifetime of a single draw frame + RV_HEAP_ID_TEMPORARY, // heap for objects that have a short lifetime (temporaries generally used for level loading) + RV_HEAP_ID_IO_TEMP, // heap for allocations that are temporaries used in I/O operations like level loading or writing out data + rv_heap_ID_max_count // just a count, not a valid type +} +Rv_Sys_Heap_ID_t; + +static const uint MAX_SYSTEM_HEAPS = (uint) rv_heap_ID_max_count; + +#ifdef _RV_MEM_SYS_SUPPORT + +// +// _RV_MEM_SYS_SUPPORT is defined +// + +extern rvHeapArena *currentHeapArena; + +// Functions for getting and setting the system heaps. +void rvSetSysHeap( Rv_Sys_Heap_ID_t sysHeapID, rvHeap *heapPtr ); // associates a heap with the given system heap ID value +rvHeap* rvGetSysHeap( Rv_Sys_Heap_ID_t sysHeapID ); // retrieves the specified system heap +void rvGetAllSysHeaps( rvHeap *destSystemHeapArray[MAX_SYSTEM_HEAPS] ); // retrieves all the MAX_SYSTEM_HEAPS heap pointers into the given array +void rvSetAllSysHeaps( rvHeap *srcSystemHeapArray[MAX_SYSTEM_HEAPS] ); // associates all the MAX_SYSTEM_HEAPS heap pointers from the given array with their corresponding id value +bool rvPushHeapContainingMemory( const void* mem ); // pushes the heap containg the memory specified to the top of the arena stack, making it current - mwhitlock + +void rvEnterArenaCriticalSection( ); // enters the heap arena critical section +void rvExitArenaCriticalSection( ); // exits the heap arena critical section +void rvPushSysHeap(Rv_Sys_Heap_ID_t sysHeapID); // pushes the system heap associated with the given identifier to the top of the arena stack, making it current + +// Useful in situations where a heap is pushed, but a return on error or an +// exception could cause the stack to be unwound, bypassing the heap pop +// operation - mwhitlock. +class rvAutoHeapCtxt +{ + bool mPushed; + +public: + rvAutoHeapCtxt(void) : + mPushed(false) + { + // Should never call this. + assert(0); + } + + rvAutoHeapCtxt(Rv_Sys_Heap_ID_t sysHeapID) : + mPushed(false) + { + rvPushSysHeap(sysHeapID); + mPushed = true; + } + + rvAutoHeapCtxt(const void* mem) : + mPushed(false) + { + mPushed = rvPushHeapContainingMemory( mem ); + } + + ~rvAutoHeapCtxt(void) + { + if(mPushed) + { + currentHeapArena->Pop(); + } + } +}; + +// +// RV_PUSH_SYS_HEAP_ID() +// Push system heaps by their ID (always available to idLib, Game and executable). +// +#define RV_PUSH_SYS_HEAP_ID(sysHeapID) rvPushSysHeap(sysHeapID) +#define RV_PUSH_SYS_HEAP_ID_AUTO(varName,sysHeapID) rvAutoHeapCtxt varName(sysHeapID) + +// +// RV_PUSH_HEAP_MEM() +// Push the heap containing the piece of memory pointed to. Note that if the +// piece of memory is not from a heap, no heap will be pushed. +// +#define RV_PUSH_HEAP_MEM(memPtr) rvPushHeapContainingMemory(memPtr) +#define RV_PUSH_HEAP_MEM_AUTO(varName,memPtr) rvAutoHeapCtxt varName(memPtr) + +// +// RV_PUSH_HEAP_PTR() +// Local heaps used mainly by executable (idLib and Game would use these only +// if heap was passed in) +// +#define RV_PUSH_HEAP_PTR(heapPtr) ( (heapPtr)->PushCurrent() ) + +// +// RV_PUSH_SYS_HEAP() +// Pop top of heap stack, regardless of how it was pushed. +// +#define RV_POP_HEAP() ( currentHeapArena->Pop() ) + +// The following versions enter/exit the heap arena's critical section so that +// critical section protection remains active between a push/pop pair (NOTE that +// the heap and heap arena are always protected by critical sections within a single method call) +#define RV_PUSH_SYS_HEAP_ENTER_CRIT_SECT(sysHeapID) { rvEnterArenaCriticalSection( ); rvPushSysHeap( sysHeapID ); } +#define RV_PUSH_HEAP_ENTER_CRIT_SECT(heapPtr) { rvEnterArenaCriticalSection( ); (heapPtr)->PushCurrent( ); } +#define RV_POP_HEAP_EXIT_CRIT_SECT() { currentHeapArena->Pop( ); rvExitArenaCriticalSection( ); } + +#else // #ifdef _RV_MEM_SYS_SUPPORT + +// +// _RV_MEM_SYS_SUPPORT is not defined +// + +#define RV_PUSH_SYS_HEAP_ID(sysHeapID) +#define RV_PUSH_SYS_HEAP_ID_AUTO(varName,sysHeapID) +#define RV_PUSH_HEAP_MEM(memPtr) +#define RV_PUSH_HEAP_MEM_AUTO(varName,memPtr) +#define RV_PUSH_HEAP_PTR(heapPtr) +#define RV_POP_HEAP() + +#define RV_PUSH_SYS_HEAP_ENTER_CRIT_SECT(sysHeapID) +#define RV_PUSH_HEAP_ENTER_CRIT_SECT(heapPtr) +#define RV_POP_HEAP_EXIT_CRIT_SECT() + +#endif // #else not #ifdef _RV_MEM_SYS_SUPPORT + +#endif // #ifndef __RV_MEM_SYS_H__ diff --git a/source/idlib/threads/AutoCrit.h b/source/idlib/threads/AutoCrit.h new file mode 100644 index 0000000..ef1acae --- /dev/null +++ b/source/idlib/threads/AutoCrit.h @@ -0,0 +1,222 @@ +#ifndef __AUTOCRIT_H__ +#define __AUTOCRIT_H__ + +#ifdef RV_USE_CRITICALSECTION +// This class eliminates the need to figure out where to call InitializeCriticalSection +// and DeleteCriticalSection. It makes critical sections into a meaningful class. More +// importantly, now when they are created as statics, they are already initialized, so +// the AutoCrit doesn't have to worry about checking to see if they've been initialized yet +class CriticalSection +{ +public: + ID_INLINE CriticalSection() + { + InitializeCriticalSection(&mCrit); + } + ID_INLINE ~CriticalSection() + { + DeleteCriticalSection(&mCrit); + } + + ID_INLINE void Enter() + { + while(!Try()) + { + Sleep(0); // give another thread a chance to run to try to eliminate deadlock + } + } + ID_INLINE void Leave() + { + LeaveCriticalSection(&mCrit); + } + + ID_INLINE bool Try() + { + return TryEnterCriticalSection(&mCrit) != 0; + } + +private: + CRITICAL_SECTION mCrit; +}; +#endif + +#ifdef RV_USE_AUTOCRIT +// Enters a critical section unique to the type of object t on construction +// Exits that critical section on destruction. Effectively synchronizes calls +// to ANY instance of type t +template +class AutoCrit +{ +public: + ID_INLINE AutoCrit() + { + sCrit.Enter(); + } + ID_INLINE ~AutoCrit() + { + sCrit.Leave(); + } +private: + static CriticalSection sCrit; +}; + +template +CriticalSection AutoCrit::sCrit; + +template +class ConditionalAutoCrit +{ +public: + ID_INLINE ConditionalAutoCrit() + { + if(sThreading) + { + sCrit.Enter(); + mMustLeave = true; + } + else + { + mMustLeave = false; + } + } + ID_INLINE ~ConditionalAutoCrit() + { + if(mMustLeave) + { + sCrit.Leave(); + } + } + + static ID_INLINE SetThreading(bool threading) + { + // not threadsafe...must be called BEFORE entering and AFTER leaving a threaded section, not during + sThreading = threading; + } + + static ID_INLINE bool IsThreading() + { + return sThreading; + } + +private: + static bool sThreading; + static CriticalSection sCrit; + bool mMustLeave; +}; + +template +CriticalSection ConditionalAutoCrit::sCrit; + +template +bool ConditionalAutoCrit::sThreading=false; + +#else +// these compile out completely in release and removes the need +// for ifdefs all over the code +template +class AutoCrit +{ +public: + AutoCrit() { } + ~AutoCrit() { } +}; + +template +class ConditionalAutoCrit +{ +public: + ConditionalAutoCrit() { } + ~ConditionalAutoCrit() { } +}; +#endif + +#ifdef RV_USE_AUTOINSTANCECRIT + +class CritInfo +{ +public: + ID_INLINE CritInfo() { refCount = 0; } + int refCount; + CriticalSection crit; +}; + + +// Enters a critical section unique to the specific instance of an object of type t on construction +// Exits that critical section on destruction. This is templated instead of using void * in order +// to reduce search time when entering and leaving. Effectively synchronizes on a single instance +// of an object. This would allow containers for example to not be synchronized to other containers +// of the same type. +template +class AutoInstanceCrit +{ +public: + + ID_INLINE AutoInstanceCrit(t *ptr) + { + CritInfo *critPtr = NULL; + //AutoCrit > crit; // protect access to the map + critSect.Enter(); + mPtr = ptr; + int i = sPtrs.FindIndex(mPtr); + + if(i == -1) // not found + { + i = sPtrs.Num(); + sPtrs.Append(mPtr); + sInfos.Append(CritInfo()); + } + critPtr = &(sInfos[i]); + critPtr->refCount++; + critSect.Leave(); + + critPtr->crit.Enter(); + } + + ID_INLINE ~AutoInstanceCrit() + { + CritInfo *critPtr = NULL; + { + //AutoCrit > crit; // protect access to the map + critSect.Enter(); + int i = sPtrs.FindIndex(mPtr); + assert(i != -1); + critPtr = &(sInfos[i]); + critPtr->refCount--; // decrement refcount + critSect.Leave(); + } + critPtr->crit.Leave(); + /* + if(critPtr->refCount == 0) + { + sPtrs.RemoveIndex(i); + sInfos.RemoveIndex(i); + }*/ + } + +private: + t *mPtr; // the pointer we're protecting + static CriticalSection critSect; + static idList sPtrs; + static idList sInfos; +}; +template +idList AutoInstanceCrit::sPtrs; + +template +idList AutoInstanceCrit::sInfos; + +template +CriticalSection AutoInstanceCrit::critSect; + +#else +// this compiles out completely in release and removes the need +// for ifdefs all over the code +template +class AutoInstanceCrit +{ +public: + ID_INLINE AutoInstanceCrit(t *ptr) { } +}; +#endif + +#endif diff --git a/source/mpgame.vcproj b/source/mpgame.vcproj new file mode 100644 index 0000000..42d0beb --- /dev/null +++ b/source/mpgame.vcproj @@ -0,0 +1,1112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/mpgame/AF.cpp b/source/mpgame/AF.cpp new file mode 100644 index 0000000..0619c82 --- /dev/null +++ b/source/mpgame/AF.cpp @@ -0,0 +1,1336 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ +#define ARTICULATED_FIGURE_ANIM "af_pose" +#define POSE_BOUNDS_EXPANSION 5.0f + +/* +================ +idAF::idAF +================ +*/ +idAF::idAF( void ) { + self = NULL; + animator = NULL; + modifiedAnim = 0; + baseOrigin.Zero(); + baseAxis.Identity(); + poseTime = -1; + restStartTime = -1; + isLoaded = false; + isActive = false; + hasBindConstraints = false; +} + +/* +================ +idAF::~idAF +================ +*/ +idAF::~idAF( void ) { +} + +/* +================ +idAF::Save +================ +*/ +void idAF::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + savefile->WriteString( GetName() ); + savefile->WriteBool( hasBindConstraints ); + savefile->WriteVec3( baseOrigin ); + savefile->WriteMat3( baseAxis ); + savefile->WriteInt( poseTime ); + savefile->WriteInt( restStartTime ); + savefile->WriteBool( isLoaded ); + savefile->WriteBool( isActive ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idAF::Restore +================ +*/ +void idAF::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( name ); + savefile->ReadBool( hasBindConstraints ); + savefile->ReadVec3( baseOrigin ); + savefile->ReadMat3( baseAxis ); + savefile->ReadInt( poseTime ); + savefile->ReadInt( restStartTime ); + savefile->ReadBool( isLoaded ); + savefile->ReadBool( isActive ); + + animator = NULL; + modifiedAnim = 0; + + if ( self ) { + SetAnimator( self->GetAnimator() ); + Load( self, name ); + if ( hasBindConstraints ) { + AddBindConstraints(); + } + } + + savefile->ReadStaticObject( physicsObj ); + + if ( self ) { + if ( isActive ) { + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + + // switch to articulated figure physics + self->RestorePhysics( &physicsObj ); + physicsObj.EnableClip(); + } + UpdateAnimation(); + } +} + +/* +================ +idAF::UpdateAnimation +================ +*/ +bool idAF::UpdateAnimation( void ) { + int i; + idVec3 origin, renderOrigin, bodyOrigin; + idMat3 axis, renderAxis, bodyAxis; + renderEntity_t *renderEntity; + + if ( !IsLoaded() ) { + return false; + } + + if ( !IsActive() ) { + return false; + } + + renderEntity = self->GetRenderEntity(); + if ( !renderEntity ) { + return false; + } + + if ( physicsObj.IsAtRest() ) { + if ( restStartTime == physicsObj.GetRestStartTime() ) { + return false; + } + restStartTime = physicsObj.GetRestStartTime(); + } + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // create an animation frame which reflects the current pose of the articulated figure + animator->InitAFPose(); + for ( i = 0; i < jointMods.Num(); i++ ) { + // check for the origin joint + if ( jointMods[i].jointHandle == 0 ) { + continue; + } + bodyOrigin = physicsObj.GetOrigin( jointMods[i].bodyId ); + bodyAxis = physicsObj.GetAxis( jointMods[i].bodyId ); + axis = jointMods[i].jointBodyAxis.Transpose() * ( bodyAxis * renderAxis.Transpose() ); + origin = ( bodyOrigin - jointMods[i].jointBodyOrigin * axis - renderOrigin ) * renderAxis.Transpose(); + animator->SetAFPoseJointMod( jointMods[i].jointHandle, jointMods[i].jointMod, axis, origin ); + } + animator->FinishAFPose( modifiedAnim, GetBounds().Expand( POSE_BOUNDS_EXPANSION ), gameLocal.time ); + animator->SetAFPoseBlendWeight( 1.0f ); + + return true; +} + +/* +================ +idAF::GetBounds + + returns bounds for the current pose +================ +*/ +idBounds idAF::GetBounds( void ) const { + int i; + idAFBody *body; + idVec3 origin, entityOrigin; + idMat3 axis, entityAxis; + idBounds bounds, b; + + bounds.Clear(); + + // get model base transform + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + + entityAxis = baseAxis.Transpose() * axis; + entityOrigin = origin - baseOrigin * entityAxis; + + // get bounds relative to base + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + origin = ( body->GetWorldOrigin() - entityOrigin ) * entityAxis.Transpose(); + axis = body->GetWorldAxis() * entityAxis.Transpose(); + b.FromTransformedBounds( body->GetClipModel()->GetBounds(), origin, axis ); + + bounds += b; + } + + return bounds; +} + +/* +================ +idAF::SetupPose + + Transforms the articulated figure to match the current animation pose of the given entity. +================ +*/ +void idAF::SetupPose( idEntity *ent, int time ) { + int i; + idAFBody *body; + idVec3 origin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + } + + if ( isActive ) { + physicsObj.UpdateClipModels(); + } +} + +/* +================ +idAF::ChangePose + + Change the articulated figure to match the current animation pose of the given entity + and set the velocity relative to the previous pose. +================ +*/ +void idAF::ChangePose( idEntity *ent, int time ) { + int i; + float invDelta; + idAFBody *body; + idVec3 origin, lastOrigin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + invDelta = 1.0f / MS2SEC( time - poseTime ); + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + lastOrigin = body->GetWorldOrigin(); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + body->SetLinearVelocity( ( body->GetWorldOrigin() - lastOrigin ) * invDelta ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::EntitiesTouchingAF +================ +*/ +int idAF::EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const { + int i, j, numClipModels; + idAFBody *body; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + int numTouching; + + if ( !IsLoaded() ) { + return 0; + } + + numTouching = 0; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( self, physicsObj.GetAbsBounds(), -1, clipModels, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + for ( j = 0; j < numClipModels; j++ ) { + cm = clipModels[j]; + + if ( !cm || cm->GetEntity() == self ) { + continue; + } + + if ( !cm->IsTraceModel() ) { + continue; + } + + if ( !body->GetClipModel()->GetAbsBounds().IntersectsBounds( cm->GetAbsBounds() ) ) { + continue; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.ContentsModel( self, body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), -1, cm->GetCollisionModel(), cm->GetOrigin(), cm->GetAxis() ) ) { +// RAVEN END + touchList[ numTouching ].touchedByBody = body; + touchList[ numTouching ].touchedClipModel = cm; + touchList[ numTouching ].touchedEnt = cm->GetEntity(); + numTouching++; + clipModels[j] = NULL; + } + } + } + + return numTouching; +} + +/* +================ +idAF::BodyForClipModelId +================ +*/ +int idAF::BodyForClipModelId( int id ) const { + if ( id >= 0 ) { + return id; + } else { + id = CLIPMODEL_ID_TO_JOINT_HANDLE( id ); + if ( id < jointBody.Num() ) { + return jointBody[id]; + } else { + return 0; + } + } +} + +/* +================ +idAF::GetPhysicsToVisualTransform +================ +*/ +void idAF::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const { + origin = - baseOrigin; + axis = baseAxis.Transpose(); +} + +/* +================ +idAF::GetImpactInfo +================ +*/ +void idAF::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + SetupPose( self, gameLocal.time ); + physicsObj.GetImpactInfo( BodyForClipModelId( id ), point, info ); +} + +/* +================ +idAF::ApplyImpulse +================ +*/ +void idAF::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + SetupPose( self, gameLocal.time ); + physicsObj.ApplyImpulse( BodyForClipModelId( id ), point, impulse ); +} + +/* +================ +idAF::AddForce +================ +*/ +void idAF::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + SetupPose( self, gameLocal.time ); + physicsObj.AddForce( BodyForClipModelId( id ), point, force ); +} + +/* +================ +idAF::AddBody + + Adds a body. +================ +*/ +void idAF::AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ) { + int index; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + handle = animator->GetJointHandle( jointName ); + if ( handle == INVALID_JOINT ) { + gameLocal.Error( "idAF for entity '%s' at (%s) modifies unknown joint '%s'", self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), jointName ); + } + + assert( handle < animator->NumJoints() ); + origin = joints[ handle ].ToVec3(); + axis = joints[ handle ].ToMat3(); + + index = jointMods.Num(); + jointMods.SetNum( index + 1, false ); + jointMods[index].bodyId = physicsObj.GetBodyId( body ); + jointMods[index].jointHandle = handle; + jointMods[index].jointMod = mod; + jointMods[index].jointBodyOrigin = ( body->GetWorldOrigin() - origin ) * axis.Transpose(); + jointMods[index].jointBodyAxis = body->GetWorldAxis() * axis.Transpose(); +} + +/* +================ +idAF::SetBase + + Sets the base body. +================ +*/ +void idAF::SetBase( idAFBody *body, const idJointMat *joints ) { + physicsObj.ForceBodyId( body, 0 ); + baseOrigin = body->GetWorldOrigin(); + baseAxis = body->GetWorldAxis(); + AddBody( body, joints, animator->GetJointName( animator->GetFirstChild( "origin" ) ), AF_JOINTMOD_AXIS ); +} + +/* +================ +idAF::LoadBody +================ +*/ +bool idAF::LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ) { + int id, i; + float length, mass; + idTraceModel trm; + idClipModel *clip; + idAFBody *body; + idMat3 axis, inertiaTensor; + idVec3 centerOfMass, origin; + idBounds bounds; + idList jointList; + + origin = fb->origin.ToVec3(); + axis = fb->angles.ToMat3(); + bounds[0] = fb->v1.ToVec3(); + bounds[1] = fb->v2.ToVec3(); + + switch( fb->modelType ) { + case TRM_BOX: { + trm.SetupBox( bounds ); + break; + } + case TRM_OCTAHEDRON: { + trm.SetupOctahedron( bounds ); + break; + } + case TRM_DODECAHEDRON: { + trm.SetupDodecahedron( bounds ); + break; + } + case TRM_CYLINDER: { + trm.SetupCylinder( bounds, fb->numSides ); + break; + } + case TRM_CONE: { + // place the apex at the origin + bounds[0].z -= bounds[1].z; + bounds[1].z = 0.0f; + trm.SetupCone( bounds, fb->numSides ); + break; + } + case TRM_BONE: { + // direction of bone + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + length = axis[2].Normalize(); + // axis of bone trace model + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + // create bone trace model + trm.SetupBone( length, fb->width ); + break; + } + default: + assert( 0 ); + break; + } + + trm.GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + trm.Translate( -centerOfMass ); + origin += centerOfMass * axis; + + body = physicsObj.GetBody( fb->name ); + if ( body ) { + clip = body->GetClipModel(); + if ( !clip->IsEqual( trm ) ) { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body->SetClipModel( clip ); + } + clip->SetContents( fb->contents ); + body->SetDensity( fb->density, fb->inertiaScale ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( axis ); + id = physicsObj.GetBodyId( body ); + } + else { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body = new idAFBody( fb->name, clip, fb->density ); + if ( fb->inertiaScale != mat3_identity ) { + body->SetDensity( fb->density, fb->inertiaScale ); + } + id = physicsObj.AddBody( body ); + } + if ( fb->linearFriction != -1.0f ) { + body->SetFriction( fb->linearFriction, fb->angularFriction, fb->contactFriction ); + } + body->SetClipMask( fb->clipMask ); + body->SetSelfCollision( fb->selfCollision ); + + if ( fb->jointName == "origin" ) { + SetBase( body, joints ); + } else { + AFJointModType_t mod; + if ( fb->jointMod == DECLAF_JOINTMOD_AXIS ) { + mod = AF_JOINTMOD_AXIS; + } else if ( fb->jointMod == DECLAF_JOINTMOD_ORIGIN ) { + mod = AF_JOINTMOD_ORIGIN; + } else if ( fb->jointMod == DECLAF_JOINTMOD_BOTH ) { + mod = AF_JOINTMOD_BOTH; + } else { + mod = AF_JOINTMOD_AXIS; + } + AddBody( body, joints, fb->jointName, mod ); + } + + if ( fb->frictionDirection.ToVec3() != vec3_origin ) { + body->SetFrictionDirection( fb->frictionDirection.ToVec3() ); + } + if ( fb->contactMotorDirection.ToVec3() != vec3_origin ) { + body->SetContactMotorDirection( fb->contactMotorDirection.ToVec3() ); + } + + // update table to find the nearest articulated figure body for a joint of the skeletal model + animator->GetJointList( fb->containedJoints, jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + if ( jointBody[ jointList[ i ] ] != -1 ) { + +// RAVEN BEGIN +// kfuller: better load time warning for joints contained by multiple bodies + gameLocal.Warning( "%s: body '%s': joint '%s' is already contained by body '%s'", + name.c_str(), fb->name.c_str(), + animator->GetJointName( (jointHandle_t)jointList[i] ), + physicsObj.GetBody( jointBody[ jointList[ i ] ] )->GetName().c_str() ); +// RAVEN END + } + jointBody[ jointList[ i ] ] = id; + } + + return true; +} + +/* +================ +idAF::LoadConstraint +================ +*/ +bool idAF::LoadConstraint( const idDeclAF_Constraint *fc ) { + idAFBody *body1, *body2; + + body1 = physicsObj.GetBody( fc->body1 ); + body2 = physicsObj.GetBody( fc->body2 ); + + switch( fc->type ) { + case DECLAF_CONSTRAINT_FIXED: { + idAFConstraint_Fixed *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Fixed( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + break; + } + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_BallAndSocketJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0], fc->shaft[0].ToVec3() ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1], fc->shaft[0].ToVec3() ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_UniversalJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetShafts( fc->shaft[0].ToVec3(), fc->shaft[1].ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0] ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1] ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_HINGE: { + idAFConstraint_Hinge *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Hinge( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetAxis( fc->axis.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + idVec3 left, up, axis, shaft; + fc->axis.ToVec3().OrthogonalBasis( left, up ); + axis = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[0] ); + shaft = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[2] ); + c->SetLimit( axis, fc->limitAngles[1], shaft ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_SLIDER: { + idAFConstraint_Slider *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Slider( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAxis( fc->axis.ToVec3() ); + break; + } + case DECLAF_CONSTRAINT_SPRING: { + idAFConstraint_Spring *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Spring( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3(), fc->anchor2.ToVec3() ); + c->SetSpring( fc->stretch, fc->compress, fc->damping, fc->restLength ); + c->SetLimit( fc->minLength, fc->maxLength ); + break; + } + } + return true; +} + +/* +================ +GetJointTransform +================ +*/ +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + jointHandle_t joint; + + joint = reinterpret_cast(model)->GetJointHandle( jointName ); + if ( ( joint >= 0 ) && ( joint < reinterpret_cast(model)->NumJoints() ) ) { + origin = frame[ joint ].ToVec3(); + axis = frame[ joint ].ToMat3(); + return true; + } else { + return false; + } +} + +/* +================ +idAF::Load +================ +*/ +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one +bool idAF::Load( idEntity *ent, const char *fileName, bool purgeAF /* = false */ ) { +// RAVEN END + int i, j; + const idDeclAF *file; + const idDeclModelDef *modelDef; + idRenderModel *model; + int numJoints; + idJointMat *joints; + + assert( ent ); + + self = ent; + physicsObj.SetSelf( self ); + + if ( animator == NULL ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s): NULL animator\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + name = fileName; + name.StripFileExtension(); + + file = static_cast( declManager->FindType( DECL_AF, name ) ); + if ( !file ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s)\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + if ( file->bodies.Num() == 0 || file->bodies[0]->jointName != "origin" ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no body which modifies the origin joint.", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + modelDef = animator->ModelDef(); + if ( modelDef == NULL || modelDef->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted modelDef '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), modelDef ? modelDef->GetName() : "" ); + return false; + } + + model = animator->ModelHandle(); + if ( model == NULL || model->IsDefaultModel() ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted model '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), model ? model->Name() : "" ); + return false; + } + + // get the modified animation + modifiedAnim = animator->GetAnim( ARTICULATED_FIGURE_ANIM ); + if ( !modifiedAnim ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no modified animation '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), ARTICULATED_FIGURE_ANIM ); + return false; + } + + // create the animation frame used to setup the articulated figure + numJoints = animator->NumJoints(); + joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + gameEdit->ANIM_CreateAnimFrame( model, animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset(), animator->RemoveOrigin() ); + + // set all vector positions from model joints + file->Finish( GetJointTransform, joints, animator ); + + // initialize articulated figure physics + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( file->clipMask ); + physicsObj.SetDefaultFriction( file->defaultLinearFriction, file->defaultAngularFriction, file->defaultContactFriction ); + physicsObj.SetSuspendSpeed( file->suspendVelocity, file->suspendAcceleration ); + physicsObj.SetSuspendTolerance( file->noMoveTime, file->noMoveTranslation, file->noMoveRotation ); + physicsObj.SetSuspendTime( file->minMoveTime, file->maxMoveTime ); + physicsObj.SetSelfCollision( file->selfCollision ); +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + physicsObj.SetFastEval( file->fastEval ); +// RAVEN END + + // clear the list with transforms from joints to bodies + jointMods.SetNum( 0, false ); + + // clear the joint to body conversion list + jointBody.AssureSize( animator->NumJoints() ); + for ( i = 0; i < jointBody.Num(); i++ ) { + jointBody[i] = -1; + } + +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + // delete any bodies in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + physicsObj.DeleteBody( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + idAFBody *body = physicsObj.GetBody( i ); + for ( j = 0; j < file->bodies.Num(); j++ ) { + if ( file->bodies[j]->name.Icmp( body->GetName() ) == 0 ) { + break; + } + } + if ( j >= file->bodies.Num() ) { + physicsObj.DeleteBody( i ); + i--; + } + } + } + + // delete any constraints in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + idAFConstraint *constraint = physicsObj.GetConstraint( i ); + for ( j = 0; j < file->constraints.Num(); j++ ) { + if ( file->constraints[j]->name.Icmp( constraint->GetName() ) == 0 && + file->constraints[j]->type == constraint->GetType() ) { + break; + } + } + if ( j >= file->constraints.Num() ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } + } +// RAVEN END + // load bodies from the file + for ( i = 0; i < file->bodies.Num(); i++ ) { + LoadBody( file->bodies[i], joints ); + } + + // load constraints from the file + for ( i = 0; i < file->constraints.Num(); i++ ) { + LoadConstraint( file->constraints[i] ); + } + + physicsObj.UpdateClipModels(); + + // check if each joint is contained by a body + for( i = 0; i < animator->NumJoints(); i++ ) { + if ( jointBody[i] == -1 ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) joint '%s' is not contained by a body", + name.c_str(), self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), animator->GetJointName( (jointHandle_t)i ) ); + } + } + + physicsObj.SetMass( file->totalMass ); + physicsObj.SetChanged(); + + // disable the articulated figure for collision detection until activated + physicsObj.DisableClip(); + + isLoaded = true; + + poseTime = -1; + + return true; +} + +/* +================ +idAF::Start +================ +*/ +void idAF::Start( void ) { + if ( !IsLoaded() ) { + return; + } + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + // switch to articulated figure physics + self->SetPhysics( &physicsObj ); + // start the articulated figure physics simulation + physicsObj.EnableClip(); + + physicsObj.Activate(); + isActive = true; +} + +/* +================ +idAF::TestSolid +================ +*/ +bool idAF::TestSolid( void ) const { + int i; + idAFBody *body; + trace_t trace; + idStr str; + bool solid; + + if ( !IsLoaded() ) { + return false; + } + + if ( !af_testSolid.GetBool() ) { + return false; + } + + solid = false; + + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + body = physicsObj.GetBody( i ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Translation( self, trace, body->GetWorldOrigin(), body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), body->GetClipMask(), self ) ) { +// RAVEN END + float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); + + body->SetWorldOrigin( body->GetWorldOrigin() + trace.c.normal * ( depth + 8.0f ) ); + + gameLocal.DWarning( "%s: body '%s' stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", self->name.c_str(), + body->GetName().c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); + solid = true; + + } + } + return solid; +} + +/* +================ +idAF::StartFromCurrentPose +================ +*/ +void idAF::StartFromCurrentPose( int inheritVelocityTime ) { + if ( !IsLoaded() ) { + return; + } + + // if the ragdoll should inherit velocity from the animation + if ( inheritVelocityTime > 0 ) { + + // make sure the ragdoll is at rest + physicsObj.PutToRest(); + + // set the pose for some time back + SetupPose( self, gameLocal.time - inheritVelocityTime ); + + // change the pose for the current time and set velocities + ChangePose( self, gameLocal.time ); + } + else { + // transform the articulated figure to reflect the current animation pose + SetupPose( self, gameLocal.time ); + } + + physicsObj.UpdateClipModels(); + + TestSolid(); + + Start(); + + UpdateAnimation(); + + // update the render entity origin and axis + self->UpdateModel(); + + // make sure the renderer gets the updated origin and axis + self->Present(); +} + +/* +================ +idAF::Stop +================ +*/ +void idAF::Stop( void ) { + // disable the articulated figure for collision detection + physicsObj.UnlinkClip(); + isActive = false; +} + +/* +================ +idAF::Rest +================ +*/ +void idAF::Rest( void ) { + physicsObj.PutToRest(); +} + +/* +================ +idAF::SetConstraintPosition + + Only moves constraints that bind the entity to another entity. +================ +*/ +void idAF::SetConstraintPosition( const char *name, const idVec3 &pos ) { + idAFConstraint *constraint; + + constraint = GetPhysics()->GetConstraint( name ); + + if ( !constraint ) { + gameLocal.Warning( "can't find a constraint with the name '%s'", name ); + return; + } + + if ( constraint->GetBody2() != NULL ) { + gameLocal.Warning( "constraint '%s' does not bind to another entity", name ); + return; + } + + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *bs = static_cast(constraint); + bs->Translate( pos - bs->GetAnchor() ); + break; + } + case CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *uj = static_cast(constraint); + uj->Translate( pos - uj->GetAnchor() ); + break; + } + case CONSTRAINT_HINGE: { + idAFConstraint_Hinge *hinge = static_cast(constraint); + hinge->Translate( pos - hinge->GetAnchor() ); + break; + } + default: { + gameLocal.Warning( "cannot set the constraint position for '%s'", name ); + break; + } + } +} + +/* +================ +idAF::SaveState +================ +*/ +void idAF::SaveState( idDict &args ) const { + int i; + idAFBody *body; + idStr key, value; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + key = "body " + body->GetName(); + value = body->GetWorldOrigin().ToString( 8 ); + value += " "; + value += body->GetWorldAxis().ToAngles().ToString( 8 ); + args.Set( key, value ); + } +} + +/* +================ +idAF::LoadState +================ +*/ +void idAF::LoadState( const idDict &args ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idVec3 origin; + idAngles angles; + + kv = args.MatchPrefix( "body ", NULL ); + while ( kv ) { + + name = kv->GetKey(); + name.Strip( "body " ); + body = physicsObj.GetBody( name ); + if ( body ) { + sscanf( kv->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( angles.ToMat3() ); + } else { + gameLocal.Warning("Unknown body part %s in articulated figure %s", name.c_str(), this->name.c_str()); + } + + kv = args.MatchPrefix( "body ", kv ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::AddBindConstraints +================ +*/ +void idAF::AddBindConstraints( void ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idLexer lexer; + idToken type, bodyName, jointName; + idVec3 origin, renderOrigin; + idMat3 axis, renderAxis; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + idToken jointFriction; +// RAVEN END + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // parse all the bind constraints + for ( kv = args.MatchPrefix( "bindConstraint ", NULL ); kv; kv = args.MatchPrefix( "bindConstraint ", kv ) ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + + lexer.ReadToken( &bodyName ); + body = physicsObj.GetBody( bodyName ); + if ( !body ) { + gameLocal.Warning( "idAF::AddBindConstraints: body '%s' not found on entity '%s'", bodyName.c_str(), self->name.c_str() ); + lexer.FreeSource(); + continue; + } + + if ( type.Icmp( "fixed" ) == 0 ) { + idAFConstraint_Fixed *c; + + c = new idAFConstraint_Fixed( name, body, NULL ); + physicsObj.AddConstraint( c ); + } + else if ( type.Icmp( "ballAndSocket" ) == 0 ) { + idAFConstraint_BallAndSocketJoint *c; + + c = new idAFConstraint_BallAndSocketJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if ( type.Icmp( "universal" ) == 0 ) { + idAFConstraint_UniversalJoint *c; + + c = new idAFConstraint_UniversalJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if (type.Icmp( "hinge" ) == 0 ) + { + idAFConstraint_Hinge *c; + c = new idAFConstraint_Hinge( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) + { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found\n", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetAxis(renderAxis[1]); + c->SetNoLimit(); + if (lexer.ReadToken(&jointFriction)) + { + float frictionValue = 0; + + sscanf(jointFriction.c_str(), "%f", &frictionValue); + c->SetFriction(frictionValue); + } + idToken hingeAxis; + if (lexer.ReadToken(&hingeAxis)) + { + int hingeAxisValue = 1; + + sscanf(hingeAxis.c_str(), "%d", &hingeAxisValue); + if (hingeAxisValue >= 0 && hingeAxisValue <= 2) + { + c->SetAxis(renderAxis[hingeAxisValue]); + } + } + } +// RAVEN END + else { + gameLocal.Warning( "idAF::AddBindConstraints: unknown constraint type '%s' on entity '%s'", type.c_str(), self->name.c_str() ); + } + + lexer.FreeSource(); + } + + hasBindConstraints = true; +} + +/* +================ +idAF::RemoveBindConstraints +================ +*/ +void idAF::RemoveBindConstraints( void ) { + const idKeyValue *kv; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + idStr name; + + kv = args.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + if ( physicsObj.GetConstraint( name ) ) { + physicsObj.DeleteConstraint( name ); + } + + kv = args.MatchPrefix( "bindConstraint ", kv ); + } + + hasBindConstraints = false; +} diff --git a/source/mpgame/AF.h b/source/mpgame/AF.h new file mode 100644 index 0000000..8d6e8b5 --- /dev/null +++ b/source/mpgame/AF.h @@ -0,0 +1,97 @@ + +#ifndef __GAME_AF_H__ +#define __GAME_AF_H__ + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ + +typedef struct jointConversion_s { + int bodyId; // id of the body + jointHandle_t jointHandle; // handle of joint this body modifies + AFJointModType_t jointMod; // modify joint axis, origin or both + idVec3 jointBodyOrigin; // origin of body relative to joint + idMat3 jointBodyAxis; // axis of body relative to joint +} jointConversion_t; + +typedef struct afTouch_s { + idEntity * touchedEnt; + idClipModel * touchedClipModel; + idAFBody * touchedByBody; +} afTouch_t; + +class idAF { +public: + idAF( void ); + ~idAF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetAnimator( idAnimator *a ) { animator = a; } +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + bool Load( idEntity *ent, const char *fileName, bool purgeAF = false ); +// RAVEN END + bool IsLoaded( void ) const { return isLoaded && self != NULL; } + const char * GetName( void ) const { return name.c_str(); } + void SetupPose( idEntity *ent, int time ); + void ChangePose( idEntity *ent, int time ); + int EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const; + void Start( void ); + void StartFromCurrentPose( int inheritVelocityTime ); + void Stop( void ); + void Rest( void ); + bool IsActive( void ) const { return isActive; } + void SetConstraintPosition( const char *name, const idVec3 &pos ); + + idPhysics_AF * GetPhysics( void ) { return &physicsObj; } + const idPhysics_AF * GetPhysics( void ) const { return &physicsObj; } + idBounds GetBounds( void ) const; + bool UpdateAnimation( void ); + + void GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const; + void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + idPhysics_AF physicsObj; // articulated figure physics + bool TestSolid( void ) const; + +protected: + idStr name; // name of the loaded .af file + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // anim to modify + idVec3 baseOrigin; // offset of base body relative to skeletal model origin + idMat3 baseAxis; // axis of base body relative to skeletal model origin + idListjointMods; // list with transforms from skeletal model joints to articulated figure bodies + idList jointBody; // table to find the nearest articulated figure body for a joint of the skeletal model + int poseTime; // last time the articulated figure was transformed to reflect the current animation pose + int restStartTime; // time the articulated figure came to rest + bool isLoaded; // true when the articulated figure is properly loaded + bool isActive; // true if the articulated figure physics is active + bool hasBindConstraints; // true if the bind constraints have been added + +protected: + void SetBase( idAFBody *body, const idJointMat *joints ); + void AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ); + + bool LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ); + bool LoadConstraint( const idDeclAF_Constraint *fc ); + +}; + +#endif /* !__GAME_AF_H__ */ diff --git a/source/mpgame/AFEntity.cpp b/source/mpgame/AFEntity.cpp new file mode 100644 index 0000000..c07739e --- /dev/null +++ b/source/mpgame/AFEntity.cpp @@ -0,0 +1,3203 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + + idMultiModelAF + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMultiModelAF ) +END_CLASS + +/* +================ +idMultiModelAF::Spawn +================ +*/ +void idMultiModelAF::Spawn( void ) { + physicsObj.SetSelf( this ); +} + +/* +================ +idMultiModelAF::~idMultiModelAF +================ +*/ +idMultiModelAF::~idMultiModelAF( void ) { + int i; + + for ( i = 0; i < modelDefHandles.Num(); i++ ) { + if ( modelDefHandles[i] != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandles[i] ); + modelDefHandles[i] = -1; + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMultiModelAF::SetModelForId +================ +*/ +void idMultiModelAF::SetModelForId( int id, const idStr &modelName ) { + modelHandles.AssureSize( id+1, NULL ); + modelDefHandles.AssureSize( id+1, -1 ); + modelHandles[id] = renderModelManager->FindModel( modelName ); +} + +/* +================ +idMultiModelAF::Present +================ +*/ +void idMultiModelAF::Present( void ) { + int i; + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + for ( i = 0; i < modelHandles.Num(); i++ ) { + + if ( !modelHandles[i] ) { + continue; + } + + renderEntity.origin = physicsObj.GetOrigin( i ); + renderEntity.axis = physicsObj.GetAxis( i ); + renderEntity.hModel = modelHandles[i]; + renderEntity.bodyId = i; + + // add to refresh list + if ( modelDefHandles[i] == -1 ) { + modelDefHandles[i] = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandles[i], &renderEntity ); + } + } +} + +/* +================ +idMultiModelAF::Think +================ +*/ +void idMultiModelAF::Think( void ) { + RunPhysics(); + Present(); +} + + +/* +=============================================================================== + + idChain + +=============================================================================== +*/ + +CLASS_DECLARATION( idMultiModelAF, idChain ) +END_CLASS + +/* +================ +idChain::BuildChain + + builds a chain hanging down from the ceiling + the highest link is a child of the link below it etc. + this allows an object to be attached to multiple chains while keeping a single tree structure +================ +*/ +void idChain::BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld ) { + int i; + float halfLinkLength = linkLength * 0.5f; + idTraceModel trm; + idClipModel *clip; + idAFBody *body, *lastBody; + idAFConstraint_BallAndSocketJoint *bsj; + idAFConstraint_UniversalJoint *uj; + idVec3 org; + + // create a trace model + trm = idTraceModel( linkLength, linkWidth ); + trm.Translate( -trm.offset ); + + org = origin - idVec3( 0, 0, halfLinkLength ); + + lastBody = NULL; + for ( i = 0; i < numLinks; i++ ) { + + // add body +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clip = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip->SetContents( CONTENTS_SOLID ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, org, mat3_identity ); +// RAVEN END +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + body = new idAFBody( name + idStr(i), clip, density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.AddBody( body ); + + // visual model for body + SetModelForId( physicsObj.GetBodyId( body ), spawnArgs.GetString( "model" ) ); + + // add constraint + if ( bindToWorld ) { + if ( !lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), body, lastBody ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, -1 ), idVec3( 0, 0, 1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, -1 ), 30.0f ); + //uj->SetPyramidLimit( idVec3( 0, 0, -1 ), idVec3( 1, 0, 0 ), 90.0f, 30.0f ); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, 1 ), 30.0f ); + } + uj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + uj->SetFriction( 0.9f ); + physicsObj.AddConstraint( uj ); + } + else { + if ( lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + bsj = new idAFConstraint_BallAndSocketJoint( "joint" + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + bsj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + bsj->SetConeLimit( idVec3( 0, 0, 1 ), 60.0f, idVec3( 0, 0, 1 ) ); + physicsObj.AddConstraint( bsj ); + } + } + + org[2] -= linkLength; + + lastBody = body; + } +} + +/* +================ +idChain::Spawn +================ +*/ +void idChain::Spawn( void ) { + int numLinks; + float length, linkLength, linkWidth, density; + bool drop; + idVec3 origin; + + spawnArgs.GetBool( "drop", "0", drop ); + spawnArgs.GetInt( "links", "3", numLinks ); + spawnArgs.GetFloat( "length", idStr( numLinks * 32.0f ), length ); + spawnArgs.GetFloat( "width", "8", linkWidth ); + spawnArgs.GetFloat( "density", "0.2", density ); + linkLength = length / numLinks; + origin = GetPhysics()->GetOrigin(); + + // initialize physics + physicsObj.SetSelf( this ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY ); + SetPhysics( &physicsObj ); + + BuildChain( "link", origin, linkLength, linkWidth, density, numLinks, !drop ); +} + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +const idEventDef EV_ClearAnims( "clearAnims" ); + +CLASS_DECLARATION( idAnimatedEntity, idAFAttachment ) +// RAVEN BEGIN +// jshepard: we'd like these to animate. + EVENT( AI_PlayAnim, idAFAttachment::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAFAttachment::Event_PlayCycle ) + EVENT( EV_ClearAnims, idAFAttachment::Event_ClearAnims ) +// RAVEN END +END_CLASS + +/* +===================== +idAFAttachment::idAFAttachment +===================== +*/ +idAFAttachment::idAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; + +// RAVEN BEGIN +// jscott: Lip Syncing variables + lipSyncAnim = 0; + lipSyncData = NULL; + soundJoint = INVALID_JOINT; +// MCG + noPlayerImpactFX = false; +// RAVEN END +} + +/* +===================== +idAFAttachment::~idAFAttachment +===================== +*/ +idAFAttachment::~idAFAttachment( void ) { + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN + EndLipSyncing(); +// RAVEN END +} + +/* +===================== +idAFAttachment::Spawn +===================== +*/ +void idAFAttachment::Spawn( void ) { + idStr jointName; + + idleAnim = animator.GetAnim( "idle" ); + +// RAVEN BEGIN + // Init the lip sync data + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } + + // Grab the joint to play sounds off + soundJoint = INVALID_JOINT; + spawnArgs.GetString( "sound_bone", "", jointName ); + + // Do we want a bone to play sounds on? + if( jointName.Length() ) { + + // Try to find the specified bone + soundJoint = animator.GetJointHandle( jointName ); + + if ( soundJoint == INVALID_JOINT ) { + // The def specified a bone for this and we can't find it - warn them. + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +// RAVEN END +} + +/* +===================== +idAFAttachment::InitCopyJoints +===================== +*/ +void idAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on entity %s", jointName, name.c_str() ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %s", kv->GetValue().c_str(), name.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +idAFAttachment::CopyJointsFromBody +===================== +*/ +void idAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +idAFAttachment::SetBody +===================== +*/ +void idAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + fl.takedamage = true; + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +idAFAttachment::ClearBody +===================== +*/ +void idAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +idAFAttachment::GetBody +===================== +*/ +idEntity *idAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Save + +archive object for savegame file +================ +*/ +void idAFAttachment::Save( idSaveGame *savefile ) const { + int i; + + body.Save ( savefile ); + savefile->WriteInt( idleAnim ); + savefile->WriteJoint( damageJoint ); + savefile->WriteJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->WriteInt( lipSyncAnim ); +// RAVEN END + + savefile->WriteInt( copyJoints.Num() ); + for( i = 0; i < copyJoints.Num(); i++ ) { + savefile->WriteInt( copyJoints[i].mod ); + savefile->WriteJoint( copyJoints[i].from ); + savefile->WriteJoint( copyJoints[i].to ); + } + + bool hadCombatModel = false; + if ( combatModel ) { + hadCombatModel = true; + } + savefile->WriteBool( hadCombatModel ); + + savefile->WriteBool( noPlayerImpactFX ); +} + +/* +================ +idAFAttachment::Restore + +unarchives object from save game file +================ +*/ +void idAFAttachment::Restore( idRestoreGame *savefile ) { + body.Restore ( savefile ); + savefile->ReadInt( idleAnim ); + savefile->ReadJoint( damageJoint ); + savefile->ReadJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->ReadInt( lipSyncAnim ); +// jscott: difficult to start a sound mid sentence and impossible to sync up the timing with the animation + lipSyncData = NULL; +// RAVEN END + + int i; + int num; + savefile->ReadInt( num ); + copyJoints.SetNum( num ); + for( i = 0; i < num; i++ ) { + int val; + savefile->ReadInt( val ); + copyJoints[i].mod = static_cast( val ); + savefile->ReadJoint( copyJoints[i].from ); + savefile->ReadJoint( copyJoints[i].to ); + } + + bool hadCombatModel; + savefile->ReadBool( hadCombatModel ); + if ( hadCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + savefile->ReadBool( noPlayerImpactFX ); + + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void idAFAttachment::Hide( void ) { + idEntity::Hide(); + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void idAFAttachment::Show( void ) { + idEntity::Show(); + LinkCombat(); +} + +/* +============ +idAFAttachment::Damage + +Pass damage to body at the bindjoint +============ +*/ +void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if ( body ) { + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, damageJoint ); + } +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void idAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +idAFAttachment::GetImpactInfo +================ +*/ +void idAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFAttachment::CanPlayImpactEffect +================ +*/ +bool idAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFAttachment::GetNoPlayerImpactFX +================ +*/ +bool idAFAttachment::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFAttachment::ApplyImpulse +================ +*/ +void idAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFAttachment::AddForce +================ +*/ +void idAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFAttachment::PlayIdleAnim +================ +*/ +// RAVEN BEGIN +// bdube: added channel +void idAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +// RAVEN END +} + +/* +================ +idAfAttachment::Think +================ +*/ +void idAFAttachment::Think( void ) { +// RAVEN BEGIN +// jscott: Lip sync main code + HandleLipSync(); +// RAVEN END + + idAnimatedEntity::Think(); +} + +// RAVEN BEGIN +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAFAttachment::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = GetPhysics()->GetAxis(); + } else { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); + } + + return true; +} +// RAVEN END + +/* +================ +idAFAttachment::UpdateAnimationControllers +================ +*/ +bool idAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return idAnimatedEntity::UpdateAnimationControllers( ); +} + +/* +================ +idAFAttachment::SetCombatModel +================ +*/ +void idAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + combatModel->SetOwner( body ); +} + +/* +================ +idAFAttachment::GetCombatModel +================ +*/ +idClipModel *idAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFAttachment::LinkCombat +================ +*/ +void idAFAttachment::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFAttachment::UnlinkCombat +================ +*/ +void idAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +// RAVEN BEGIN +// bdube: return the body entity for damage +/* +================ +idAFAttachment::GetDamageEntity +================ +*/ +idEntity* idAFAttachment::GetDamageEntity ( void ) { + return body ? body : this; +} + +// jshepard: we need to animate these +/* +================ +idAFAttachment::Event_PlayAnim +================ +*/ +void idAFAttachment::Event_PlayAnim ( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animTime - gameLocal.time ) ); + } + blendFrames = 0; +} + +// jdischler: and we want cycling, too. +/* +=============== +idAFAttachment::Event_PlayCycle +=============== +*/ +void idAFAttachment::Event_PlayCycle( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +================ +idAFAttachment::Event_ClearAnims + +Clears any animation running on the idAFAttachment +================ +*/ +void idAFAttachment::Event_ClearAnims( void ) { + //animator.ClearAllAnims( gameLocal.time, 100 ); + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, 0 ); +} + +// RAVEN END + +/* +=============================================================================== + + idAFEntity_Base + +=============================================================================== +*/ + +const idEventDef EV_SetConstraintPosition( "SetConstraintPosition", "sv" ); +// RAVEN BEGIN +// kfuller: added +const idEventDef EV_TPose( "tpose", NULL ); +// RAVEN END + +CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base ) + EVENT( EV_SetConstraintPosition, idAFEntity_Base::Event_SetConstraintPosition ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idAFEntity_Base::idAFEntity_Base +================ +*/ +idAFEntity_Base::idAFEntity_Base( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); + noPlayerImpactFX = false; +} + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +idAFEntity_Base::~idAFEntity_Base( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +idAFEntity_Base::Save +================ +*/ +void idAFEntity_Base::Save( idSaveGame *savefile ) const { + savefile->WriteBool( noPlayerImpactFX ); + savefile->WriteInt( combatModelContents ); + savefile->WriteClipModel( combatModel ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( nextSoundTime ); + af.Save( savefile ); +} + +/* +================ +idAFEntity_Base::Restore +================ +*/ +void idAFEntity_Base::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( noPlayerImpactFX ); + savefile->ReadInt( combatModelContents ); + savefile->ReadClipModel( combatModel ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( nextSoundTime ); + LinkCombat(); + + af.Restore( savefile ); +} + +/* +================ +idAFEntity_Base::Spawn +================ +*/ +void idAFEntity_Base::Spawn( void ) { + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + nextSoundTime = 0; + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +} + +/* +================ +idAFEntity_Base::LoadAF +================ +*/ +bool idAFEntity_Base::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + if ( !af.Load( this, fileName ) ) { + gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() ); + } + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +idAFEntity_Base::Think +================ +*/ +void idAFEntity_Base::Think( void ) { + RunPhysics(); + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + +// RAVEN BEGIN +// kfuller: added + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } + +// rjohnson: added check to see if we no longer need to think! + if (GetPhysics()->IsAtRest() && !animator.IsAnimating( gameLocal.time, true ) ) { + BecomeInactive( TH_ANIMATE ); + } +// RAVEN END +} + +/* +================ +idAFEntity_Base::BodyForClipModelId +================ +*/ +int idAFEntity_Base::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +idAFEntity_Base::SaveState +================ +*/ +void idAFEntity_Base::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +idAFEntity_Base::LoadState +================ +*/ +void idAFEntity_Base::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +idAFEntity_Base::AddBindConstraints +================ +*/ +void idAFEntity_Base::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +idAFEntity_Base::RemoveBindConstraints +================ +*/ +void idAFEntity_Base::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +idAFEntity_Base::GetImpactInfo +================ +*/ +void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFEntity_Base::ApplyImpulse +================ +*/ +void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( !splash && ( noPlayerImpactFX || GetNoPlayerImpactFX( ) ) ) { + if ( ent->IsType( idPlayer::GetClassType() ) ) + {//player + return; + } + if ( ent->IsType( idProjectile::GetClassType() ) ) + {//projectile + if ( ((idProjectile*)ent)->GetOwner() && ((idProjectile*)ent)->GetOwner()->IsType( idPlayer::GetClassType() ) ) + {//owned by player + return; + } + } + } + + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFEntity_Base::AddForce +================ +*/ +void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + idEntity::AddForce( ent, id, point, force ); + } +} + +bool idAFEntity_Base::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFEntity_Base::Collide +================ +*/ +bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +idAFEntity_Base::GetPhysicsToVisualTransform +================ +*/ +bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return idEntity::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +idAFEntity_Base::UpdateAnimationControllers +================ +*/ +bool idAFEntity_Base::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +idAFEntity_Base::SetCombatModel +================ +*/ +void idAFEntity_Base::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::GetCombatModel +================ +*/ +idClipModel *idAFEntity_Base::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFEntity_Base::SetCombatContents +================ +*/ +void idAFEntity_Base::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +idAFEntity_Base::LinkCombat +================ +*/ +void idAFEntity_Base::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::UnlinkCombat +================ +*/ +void idAFEntity_Base::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +idAFEntity_Base::FreeModelDef +================ +*/ +void idAFEntity_Base::FreeModelDef( void ) { + UnlinkCombat(); + idEntity::FreeModelDef(); +} + +/* +=============== +idAFEntity_Base::ShowEditingDialog +=============== +*/ +void idAFEntity_Base::ShowEditingDialog( void ) { + common->InitTool( EDITOR_AF, &spawnArgs ); +} + +/* +================ +idAFEntity_Base::DropAFs + + The entity should have the following key/value pairs set: + "def_dropAF" "af def" + "dropSkin" "skin name" + To drop multiple articulated figures the following key/value pairs can be used: + "def_dropAF*" "af def" + where * is an aribtrary string. +================ +*/ +void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName; + idEntity *newEnt; + idAFEntity_Base *af; + idDict args; + const idDeclSkin *skin; + + // drop the articulated figures + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL ); + while ( kv ) { + + args.Set( "classname", kv->GetValue() ); + gameLocal.SpawnEntityDef( args, &newEnt ); + + if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(newEnt); + af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() ); + af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() ); + af->af.SetupPose( ent, gameLocal.time ); + if ( list ) { + list->Append( af ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv ); + } + + // change the skin to hide all the dropped articulated figures + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +================ +idAFEntity_Base::GetNoPlayerImpactFX +================ +*/ +bool idAFEntity_Base::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFEntity_Base::Event_SetConstraintPosition +================ +*/ +void idAFEntity_Base::Event_SetConstraintPosition( const char *name, const idVec3 &pos ) { + af.SetConstraintPosition( name, pos ); +} + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +const idEventDef EV_Gib( "gib", "s" ); +const idEventDef EV_Gibbed( "" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Gibbable ) + EVENT( EV_Gib, idAFEntity_Gibbable::Event_Gib ) + EVENT( EV_Gibbed, idAFEntity_Base::Event_Remove ) +END_CLASS + + +/* +================ +idAFEntity_Gibbable::idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::idAFEntity_Gibbable( void ) { + skeletonModel = NULL; + skeletonModelDefHandle = -1; + gibbed = false; +} + +/* +================ +idAFEntity_Gibbable::~idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::~idAFEntity_Gibbable() { + if ( skeletonModelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( skeletonModelDefHandle ); + skeletonModelDefHandle = -1; + } +} + +/* +================ +idAFEntity_Gibbable::Save +================ +*/ +void idAFEntity_Gibbable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( gibbed ); + savefile->WriteBool( combatModel != NULL ); +} + +/* +================ +idAFEntity_Gibbable::Restore +================ +*/ +void idAFEntity_Gibbable::Restore( idRestoreGame *savefile ) { + bool hasCombatModel; + + savefile->ReadBool( gibbed ); + savefile->ReadBool( hasCombatModel ); + + InitSkeletonModel(); + + if ( hasCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::Spawn +================ +*/ +void idAFEntity_Gibbable::Spawn( void ) { + InitSkeletonModel(); + + gibbed = false; + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::InitSkeletonModel +================ +*/ +void idAFEntity_Gibbable::InitSkeletonModel( void ) { + const char *modelName; + const idDeclModelDef *modelDef; + + skeletonModel = NULL; + skeletonModelDefHandle = -1; + + modelName = spawnArgs.GetString( "model_gib" ); + + modelDef = NULL; + if ( modelName[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + skeletonModel = modelDef->ModelHandle(); + } else { + skeletonModel = renderModelManager->FindModel( modelName ); + } + if ( skeletonModel != NULL && renderEntity.hModel != NULL ) { + if ( skeletonModel->NumJoints() != renderEntity.hModel->NumJoints() ) { + gameLocal.Error( "gib model '%s' has different number of joints than model '%s'", + skeletonModel->Name(), renderEntity.hModel->Name() ); + } + } + } +} + +/* +================ +idAFEntity_Gibbable::Present +================ +*/ +void idAFEntity_Gibbable::Present( void ) { + renderEntity_t skeleton; + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // update skeleton model + if ( gibbed && !IsHidden() && skeletonModel != NULL ) { + skeleton = renderEntity; + skeleton.hModel = skeletonModel; + // add to refresh list + if ( skeletonModelDefHandle == -1 ) { + skeletonModelDefHandle = gameRenderWorld->AddEntityDef( &skeleton ); + } else { + gameRenderWorld->UpdateEntityDef( skeletonModelDefHandle, &skeleton ); + } + } + + idEntity::Present(); +} + +/* +================ +idAFEntity_Gibbable::Damage +================ +*/ +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } +} + +/* +===================== +idAFEntity_Gibbable::SpawnGibs +===================== +*/ +void idAFEntity_Gibbable::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + int i; + bool gibNonSolid; + idVec3 entityCenter, velocity; + idList list; + + assert( !gameLocal.isClient ); + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + // spawn gib articulated figures + idAFEntity_Base::DropAFs( this, "gib", &list ); + + // spawn gib items + idMoveableItem::DropItems( this, "gib", &list ); + + // blow out the gibs in the given direction away from the center of the entity + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + gibNonSolid = damageDef->GetBool( "gibNonSolid" ); + for ( i = 0; i < list.Num(); i++ ) { + if ( gibNonSolid ) { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( 0 ); + list[i]->GetPhysics()->UnlinkClip(); + list[i]->GetPhysics()->PutToRest(); + } else { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + list[i]->GetPhysics()->SetLinearVelocity( velocity * 225.0f ); + } + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; +// RAVEN BEGIN +// dluetscher: reduced the gib lifetime from 4. to 1.5 + list[i]->PostEventSec( &EV_Remove, 1.5f ); +// RAVEN END + } +} + +/* +============ +idAFEntity_Gibbable::Gib +============ +*/ +void idAFEntity_Gibbable::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + if ( damageDef->GetBool( "gibNonSolid" ) ) { + GetAFPhysics()->SetContents( 0 ); + GetAFPhysics()->SetClipMask( 0 ); + GetAFPhysics()->UnlinkClip(); + GetAFPhysics()->PutToRest(); + } else { + GetAFPhysics()->SetContents( CONTENTS_CORPSE ); + GetAFPhysics()->SetClipMask( CONTENTS_SOLID ); + } + + UnlinkCombat(); +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( g_decals.GetBool() ) { +// RAVEN END + if ( gameLocal.time > gameLocal.GetGibTime() ) { + gameLocal.SetGibTime( gameLocal.time + GIB_DELAY ); + SpawnGibs( dir, damageDefName ); + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + StartSound( "snd_gibbed", SND_CHANNEL_ANY, 0, false, NULL ); + gibbed = true; + } + } else { + gibbed = true; + } + +// RAVEN BEGIN +// bdube: default is to remove the character immediately + // however, this is bad in the case of the player. What was happening is that the + // player was being removed right away, but the respawn logic runs off of + // idPlayer::EvaluateControls, which no longer gets run when the player is gibbed and removed. + // So...the game effectively sits locked on a black screen and you never get a menu.. + if ( !gameLocal.isMultiplayer && this->IsType( idPlayer::GetClassType() )) { + return; + } + PostEventSec( &EV_Gibbed, spawnArgs.GetFloat ( "gibRemoveDelay", "0" ) ); +// RAVEN END +} + +/* +============ +idAFEntity_Gibbable::Event_Gib +============ +*/ +void idAFEntity_Gibbable::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_Generic ) + EVENT( EV_Activate, idAFEntity_Generic::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_Generic::idAFEntity_Generic +================ +*/ +idAFEntity_Generic::idAFEntity_Generic( void ) { + keepRunningPhysics = false; +} + +/* +================ +idAFEntity_Generic::~idAFEntity_Generic +================ +*/ +idAFEntity_Generic::~idAFEntity_Generic( void ) { +} + +/* +================ +idAFEntity_Generic::Save +================ +*/ +void idAFEntity_Generic::Save( idSaveGame *savefile ) const { + savefile->WriteBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Restore +================ +*/ +void idAFEntity_Generic::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Think +================ +*/ +void idAFEntity_Generic::Think( void ) { + idAFEntity_Base::Think(); + + if ( keepRunningPhysics ) { + BecomeActive( TH_PHYSICS ); + } +} + +/* +================ +idAFEntity_Generic::Spawn +================ +*/ +void idAFEntity_Generic::Spawn( void ) { + if ( !LoadAF() ) { + gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() ); + } + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + +// RAVEN BEGIN +// kfuller: we just put the guy to rest so don't be affected by an attractor +/* + const char *attractor = spawnArgs.GetString("attractor"); + if (attractor && attractor[0]) { + rvRagdollAttractor *attractorEnt = dynamic_cast(gameLocal.FindEntity(attractor)); + if (attractorEnt) { + attractorEnt->RemoveAttractee(entityNumber); + } else { + gameLocal.Warning("idAFEntity::LoadAF -- failed to find attractor '%s'", attractor); + } + } +*/ +// RAVEN END + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; +} + +/* +================ +idAFEntity_Generic::Event_Activate +================ +*/ +void idAFEntity_Generic::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_WithAttachedHead ) + EVENT( EV_Gib, idAFEntity_WithAttachedHead::Event_Gib ) + EVENT( EV_Activate, idAFEntity_WithAttachedHead::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead() { + head = NULL; +} + +/* +================ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead() { + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Spawn +================ +*/ +void idAFEntity_WithAttachedHead::Spawn( void ) { + SetupHead(); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; + + if ( head.GetEntity() ) { + int anim = head.GetEntity()->GetAnimator()->GetAnim( "dead" ); + + if ( anim ) { +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + head.GetEntity()->GetAnimator()->SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + } + } + + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Save +================ +*/ +void idAFEntity_WithAttachedHead::Save( idSaveGame *savefile ) const { + head.Save( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::Restore +================ +*/ +void idAFEntity_WithAttachedHead::Restore( idRestoreGame *savefile ) { + head.Restore( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::SetupHead +================ +*/ +void idAFEntity_WithAttachedHead::SetupHead( const char* headDefName ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); + } + + if ( headDefName[ 0 ] ) { + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headDefName, joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + + head->InitCopyJoints ( ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Think +================ +*/ +void idAFEntity_WithAttachedHead::Think( void ) { + idAFEntity_Base::Think(); +} + +/* +================ +idAFEntity_WithAttachedHead::LinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::UnlinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Hide +================ +*/ +void idAFEntity_WithAttachedHead::Hide( void ) { + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + UnlinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::Show +================ +*/ +void idAFEntity_WithAttachedHead::Show( void ) { + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + LinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::ProjectOverlay +================ +*/ +void idAFEntity_WithAttachedHead::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + + idEntity::ProjectOverlay( origin, dir, size, material ); + + if ( head.GetEntity() ) { + head.GetEntity()->ProjectOverlay( origin, dir, size, material ); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Gib +============ +*/ +void idAFEntity_WithAttachedHead::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Event_Gib +============ +*/ +void idAFEntity_WithAttachedHead::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +================ +idAFEntity_WithAttachedHead::Event_Activate +================ +*/ +void idAFEntity_WithAttachedHead::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_Vehicle + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Vehicle ) +END_CLASS + +/* +================ +idAFEntity_Vehicle::idAFEntity_Vehicle +================ +*/ +idAFEntity_Vehicle::idAFEntity_Vehicle( void ) { + player = NULL; + eyesJoint = INVALID_JOINT; + steeringWheelJoint = INVALID_JOINT; + wheelRadius = 0.0f; + steerAngle = 0.0f; + steerSpeed = 0.0f; +// dustSmoke = NULL; +} + +/* +================ +idAFEntity_Vehicle::Spawn +================ +*/ +void idAFEntity_Vehicle::Spawn( void ) { + const char *eyesJointName = spawnArgs.GetString( "eyesJoint", "eyes" ); + const char *steeringWheelJointName = spawnArgs.GetString( "steeringWheelJoint", "steeringWheel" ); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + if ( !eyesJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no eyes joint specified", name.c_str() ); + } + eyesJoint = animator.GetJointHandle( eyesJointName ); + if ( !steeringWheelJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no steering wheel joint specified", name.c_str() ); + } + steeringWheelJoint = animator.GetJointHandle( steeringWheelJointName ); + + spawnArgs.GetFloat( "wheelRadius", "20", wheelRadius ); + spawnArgs.GetFloat( "steerSpeed", "5", steerSpeed ); + + player = NULL; + steerAngle = 0.0f; + +/* + const char *smokeName = spawnArgs.GetString( "smoke_vehicle_dust", "muzzlesmoke" ); + if ( *smokeName != '\0' ) { + dustSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } +*/ +} + +/* +================ +idAFEntity_Vehicle::Use +================ +*/ +void idAFEntity_Vehicle::Use( idPlayer *other ) { + idVec3 origin; + idMat3 axis; + + if ( player ) { + if ( player == other ) { + other->Unbind(); + player = NULL; + + af.GetPhysics()->SetComeToRest( true ); + } + } + else { + player = other; + animator.GetJointTransform( eyesJoint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + player->GetPhysics()->SetOrigin( origin ); + player->BindToBody( this, 0, true ); + + af.GetPhysics()->SetComeToRest( false ); + af.GetPhysics()->Activate(); + } +} + +/* +================ +idAFEntity_Vehicle::GetSteerAngle +================ +*/ +float idAFEntity_Vehicle::GetSteerAngle( void ) { + float idealSteerAngle, angleDelta; + + idealSteerAngle = player->usercmd.rightmove * ( 30.0f / 128.0f ); + angleDelta = idealSteerAngle - steerAngle; + + if ( angleDelta > steerSpeed ) { + steerAngle += steerSpeed; + } else if ( angleDelta < -steerSpeed ) { + steerAngle -= steerSpeed; + } else { + steerAngle = idealSteerAngle; + } + + return steerAngle; +} + + +/* +=============================================================================== + + idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleFourWheels ) +END_CLASS + + +/* +================ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels +================ +*/ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; +} + +/* +================ +idAFEntity_VehicleFourWheels::Spawn +================ +*/ +void idAFEntity_VehicleFourWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 4; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 2; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleFourWheels::Think +================ +*/ +void idAFEntity_VehicleFourWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 2; i++ ) { + wheels[2+i]->SetContactMotorVelocity( velocity ); + wheels[2+i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + wheels[2]->SetContactMotorVelocity( velocity * 0.5f ); + } + else if ( steerAngle > 0.0f ) { + wheels[3]->SetContactMotorVelocity( velocity * 0.5f ); + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + for ( i = 0; i < 2; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSixWheels ) +END_CLASS + + /* +================ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels +================ +*/ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels( void ) { + int i; + + for ( i = 0; i < 6; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; + steering[2] = NULL; + steering[3] = NULL; +} + +/* +================ +idAFEntity_VehicleSixWheels::Spawn +================ +*/ +void idAFEntity_VehicleSixWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyMiddleLeft", + "wheelBodyMiddleRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointMiddleLeft", + "wheelJointMiddleRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + "steeringHingeRearLeft", + "steeringHingeRearRight" + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 6; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 4; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSixWheels::Think +================ +*/ +void idAFEntity_VehicleSixWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 6; i++ ) { + wheels[i]->SetContactMotorVelocity( velocity ); + wheels[i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + else if ( steerAngle > 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[1+(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + steering[2]->SetSteerAngle( -steerAngle ); + steering[3]->SetSteerAngle( -steerAngle ); + for ( i = 0; i < 4; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 6; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_SteamPipe + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_SteamPipe ) +END_CLASS + + +/* +================ +idAFEntity_SteamPipe::idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::idAFEntity_SteamPipe( void ) { + steamBody = 0; + steamForce = 0.0f; + steamUpForce = 0.0f; + steamModelDefHandle = -1; + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); +} + +/* +================ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe( void ) { + if ( steamModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( steamModelDefHandle ); + } +} + +/* +================ +idAFEntity_SteamPipe::Save +================ +*/ +void idAFEntity_SteamPipe::Save( idSaveGame *savefile ) const { +} + +/* +================ +idAFEntity_SteamPipe::Restore +================ +*/ +void idAFEntity_SteamPipe::Restore( idRestoreGame *savefile ) { + Spawn(); +} + +/* +================ +idAFEntity_SteamPipe::Spawn +================ +*/ +void idAFEntity_SteamPipe::Spawn( void ) { + idVec3 steamDir; + const char *steamBodyName; + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + steamBodyName = spawnArgs.GetString( "steamBody", "" ); + steamForce = spawnArgs.GetFloat( "steamForce", "2000" ); + steamUpForce = spawnArgs.GetFloat( "steamUpForce", "10" ); + steamDir = af.GetPhysics()->GetAxis( steamBody )[2]; + steamBody = af.GetPhysics()->GetBodyId( steamBodyName ); + force.SetPosition( af.GetPhysics(), steamBody, af.GetPhysics()->GetOrigin( steamBody ) ); + force.SetForce( steamDir * -steamForce ); + + InitSteamRenderEntity(); + + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_SteamPipe::InitSteamRenderEntity +================ +*/ +void idAFEntity_SteamPipe::InitSteamRenderEntity( void ) { + const char *temp; + const idDeclModelDef *modelDef; + + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); + steamRenderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + modelDef = NULL; + temp = spawnArgs.GetString ( "model_steam" ); + if ( *temp != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + steamRenderEntity.hModel = modelDef->ModelHandle(); + } + } + + if ( !steamRenderEntity.hModel ) { + steamRenderEntity.hModel = renderModelManager->FindModel( temp ); + } + + if ( steamRenderEntity.hModel ) { + steamRenderEntity.bounds = steamRenderEntity.hModel->Bounds( &steamRenderEntity ); + } else { + steamRenderEntity.bounds.Zero(); + } + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + steamModelDefHandle = gameRenderWorld->AddEntityDef( &steamRenderEntity ); + } +} + +/* +================ +idAFEntity_SteamPipe::Think +================ +*/ +void idAFEntity_SteamPipe::Think( void ) { + idVec3 steamDir; + + if ( thinkFlags & TH_THINK ) { + steamDir.x = gameLocal.random.CRandomFloat() * steamForce; + steamDir.y = gameLocal.random.CRandomFloat() * steamForce; + steamDir.z = steamUpForce; + force.SetForce( steamDir ); + force.Evaluate( gameLocal.time ); + //gameRenderWorld->DebugArrow( colorWhite, af.GetPhysics()->GetOrigin( steamBody ), af.GetPhysics()->GetOrigin( steamBody ) - 10.0f * steamDir, 4 ); + } + + if ( steamModelDefHandle >= 0 ){ + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + gameRenderWorld->UpdateEntityDef( steamModelDefHandle, &steamRenderEntity ); + } + + idAFEntity_Base::Think(); +} + + +/* +=============================================================================== + + idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +const idEventDef EV_SetFingerAngle( "setFingerAngle", "f" ); +const idEventDef EV_StopFingers( "stopFingers" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_ClawFourFingers ) + EVENT( EV_SetFingerAngle, idAFEntity_ClawFourFingers::Event_SetFingerAngle ) + EVENT( EV_StopFingers, idAFEntity_ClawFourFingers::Event_StopFingers ) +END_CLASS + +static const char *clawConstraintNames[] = { + "claw1", "claw2", "claw3", "claw4" +}; + +/* +================ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers +================ +*/ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers( void ) { + fingers[0] = NULL; + fingers[1] = NULL; + fingers[2] = NULL; + fingers[3] = NULL; +} + +/* +================ +idAFEntity_ClawFourFingers::Save +================ +*/ +void idAFEntity_ClawFourFingers::Save( idSaveGame *savefile ) const { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->Save( savefile ); + } +} + +/* +================ +idAFEntity_ClawFourFingers::Restore +================ +*/ +void idAFEntity_ClawFourFingers::Restore( idRestoreGame *savefile ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + fingers[i]->Restore( savefile ); + } + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFEntity_ClawFourFingers::Spawn +================ +*/ +void idAFEntity_ClawFourFingers::Spawn( void ) { + int i; + + LoadAF(); + + SetCombatModel(); + + af.GetPhysics()->LockWorldConstraints( true ); + af.GetPhysics()->SetForcePushable( true ); + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + if ( !fingers[i] ) { + gameLocal.Error( "idClaw_FourFingers '%s': can't find claw constraint '%s'", name.c_str(), clawConstraintNames[i] ); + } + } +} + +/* +================ +idAFEntity_ClawFourFingers::Event_SetFingerAngle +================ +*/ +void idAFEntity_ClawFourFingers::Event_SetFingerAngle( float angle ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( angle ); + fingers[i]->SetSteerSpeed( 0.5f ); + } + af.GetPhysics()->Activate(); +} + +/* +================ +idAFEntity_ClawFourFingers::Event_StopFingers +================ +*/ +void idAFEntity_ClawFourFingers::Event_StopFingers( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( fingers[i]->GetAngle() ); + } +} + + +/* +=============================================================================== + + editor support routines + +=============================================================================== +*/ + + +/* +================ +idGameEdit::AF_SpawnEntity +================ +*/ +bool idGameEdit::AF_SpawnEntity( const char *fileName ) { + idDict args; + idPlayer *player; + idAFEntity_Generic *ent; + const idDeclAF *af; + idVec3 org; + float yaw; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return false; + } + + af = static_cast( declManager->FindType( DECL_AF, fileName ) ); + if ( !af ) { + return false; + } + + yaw = player->viewAngles.yaw; + args.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + args.Set( "origin", org.ToString() ); + args.Set( "spawnclass", "idAFEntity_Generic" ); + if ( af->model[0] ) { + args.Set( "model", af->model.c_str() ); + } else { + args.Set( "model", fileName ); + } + if ( af->skin[0] ) { + args.Set( "skin", af->skin.c_str() ); + } + args.Set( "articulatedFigure", fileName ); + args.Set( "nodrop", "1" ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + ent = static_cast(gameLocal.SpawnEntityType( idAFEntity_Generic::GetClassType(), &args)); +// RAVEN END + + // always update this entity + ent->BecomeActive( TH_THINK ); + ent->KeepRunningPhysics(); + ent->fl.forcePhysicsUpdate = true; + + player->dragEntity.SetSelected( ent ); + + return true; +} + +/* +================ +idGameEdit::AF_UpdateEntities +================ +*/ +void idGameEdit::AF_UpdateEntities( const char *fileName ) { + idEntity *ent; + idAFEntity_Base *af; + idStr name; + + name = fileName; + name.StripFileExtension(); + + // reload any idAFEntity_Generic which uses the given articulated figure file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( name.Icmp( af->GetAFName() ) == 0 ) { + af->LoadAF(); + af->GetAFPhysics()->PutToRest(); + } + } + } +} + +/* +================ +idGameEdit::AF_UndoChanges +================ +*/ +void idGameEdit::AF_UndoChanges( void ) { + int i, c; + idEntity *ent; + idAFEntity_Base *af; + idDeclAF *decl; + + c = declManager->GetNumDecls( DECL_AF ); + for ( i = 0; i < c; i++ ) { + decl = static_cast( const_cast( declManager->DeclByIndex( DECL_AF, i, false ) ) ); + if ( !decl->modified ) { + continue; + } + + decl->Invalidate(); + declManager->FindType( DECL_AF, decl->GetName() ); + + // reload all AF entities using the file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) { + af->LoadAF(); + } + } + } + } +} + +/* +================ +GetJointTransform +================ +*/ +typedef struct { + renderEntity_t *ent; + const idMD5Joint *joints; +} jointTransformData_t; + +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + int i; + jointTransformData_t *data = reinterpret_cast(model); + + for ( i = 0; i < data->ent->numJoints; i++ ) { + if ( data->joints[i].name.Icmp( jointName ) == 0 ) { + break; + } + } + if ( i >= data->ent->numJoints ) { + return false; + } + origin = frame[i].ToVec3(); + axis = frame[i].ToMat3(); + return true; +} + +/* +================ +GetArgString +================ +*/ +static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) { + const char *s; + + s = args.GetString( key ); + if ( !s[0] && defArgs ) { + s = defArgs->GetString( key ); + } + return s; +} + +/* +================ +idGameEdit::AF_CreateMesh +================ +*/ +idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) { + int i, jointNum; + const idDeclAF *af; + const idDeclAF_Body *fb = NULL; + renderEntity_t ent; + idVec3 origin, *bodyOrigin, *newBodyOrigin, *modifiedOrigin; + idMat3 axis, *bodyAxis, *newBodyAxis, *modifiedAxis; + declAFJointMod_t *jointMod; + idAngles angles; + const idDict *defArgs; + const idKeyValue *arg; + idStr name; + jointTransformData_t data; + const char *classname, *afName, *modelName; + idRenderModel *md5; + const idDeclModelDef *modelDef; + const idMD5Anim *MD5anim; + const idMD5Joint *MD5joint; + const idMD5Joint *MD5joints; + int numMD5joints; + idJointMat *originalJoints; + int parentNum; + + poseIsSet = false; + meshOrigin.Zero(); + meshAxis.Identity(); + + classname = args.GetString( "classname" ); + defArgs = gameLocal.FindEntityDefDict( classname ); + + // get the articulated figure + afName = GetArgString( args, defArgs, "articulatedFigure" ); + af = static_cast( declManager->FindType( DECL_AF, afName ) ); + if ( !af ) { + return NULL; + } + + // get the md5 model + modelName = GetArgString( args, defArgs, "model" ); + modelDef = static_cast< const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( !modelDef ) { + return NULL; + } + + // make sure model hasn't been purged + if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) { + modelDef->ModelHandle()->LoadModel(); + } + + // get the md5 + md5 = modelDef->ModelHandle(); + if ( !md5 || md5->IsDefaultModel() ) { + return NULL; + } + + // get the articulated figure pose anim + int animNum = modelDef->GetAnim( "af_pose" ); + if ( !animNum ) { + return NULL; + } + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + MD5anim = anim->MD5Anim( 0 ); + MD5joints = md5->GetJoints(); + numMD5joints = md5->NumJoints(); + + // setup a render entity + memset( &ent, 0, sizeof( ent ) ); + ent.customSkin = modelDef->GetSkin(); + ent.bounds.Clear(); + ent.numJoints = numMD5joints; + ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) ); + + // create animation from of the af_pose + ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false ); + + // buffers to store the initial origin and axis for each body + bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + + // finish the AF positions + data.ent = &ent; + data.joints = MD5joints; + af->Finish( GetJointTransform, ent.joints, &data ); + + // get the initial origin and axis for each AF body + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->modelType == TRM_BONE ) { + // axis of bone trace model + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + } else { + axis = fb->angles.ToMat3(); + } + + newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3(); + newBodyAxis[i] = bodyAxis[i] = axis; + } + + // get any new body transforms stored in the key/value pairs + for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) { + name = arg->GetKey(); + name.Strip( "body " ); + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + if ( fb->name.Icmp( name ) == 0 ) { + break; + } + } + if ( i >= af->bodies.Num() ) { + continue; + } + sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + meshAxis = bodyAxis[i].Transpose() * angles.ToMat3(); + meshOrigin = origin - bodyOrigin[i] * meshAxis; + poseIsSet = true; + } else { + newBodyOrigin[i] = origin; + newBodyAxis[i] = angles.ToMat3(); + } + } + + // save the original joints + originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN END + + // buffer to store the joint mods + jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) ); + memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) ); + modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) ); + memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) ); + modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) ); + memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) ); + + // get all the joint modifications + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + continue; + } + + for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) { + if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) { + break; + } + } + + if ( jointNum >= 0 && jointNum < ent.numJoints ) { + jointMod[ jointNum ] = fb->jointMod; + modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() ); + // FIXME: calculate correct modifiedOrigin + modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3(); + } + } + + // apply joint modifications to the skeleton + MD5joint = MD5joints + 1; + for( i = 1; i < numMD5joints; i++, MD5joint++ ) { + + parentNum = MD5joint->parent - MD5joints; + idMat3 parentAxis = originalJoints[ parentNum ].ToMat3(); + idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose(); + idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose(); + + switch( jointMod[i] ) { + case DECLAF_JOINTMOD_ORIGIN: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + case DECLAF_JOINTMOD_AXIS: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + case DECLAF_JOINTMOD_BOTH: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + default: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + } + } + + // instantiate a mesh using the joint information from the render entity + return md5->InstantiateDynamicModel( &ent, NULL, NULL ); +} + +// RAVEN BEGIN +// bdube: af attractors + +/* +=============================================================================== + + rvAFAttractor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAFAttractor ) +END_CLASS + +// RAVEN END diff --git a/source/mpgame/AFEntity.h b/source/mpgame/AFEntity.h new file mode 100644 index 0000000..22eee07 --- /dev/null +++ b/source/mpgame/AFEntity.h @@ -0,0 +1,508 @@ + +#ifndef __GAME_AFENTITY_H__ +#define __GAME_AFENTITY_H__ + + +/* +=============================================================================== + +idMultiModelAF + +Entity using multiple separate visual models animated with a single +articulated figure. Only used for debugging! + +=============================================================================== +*/ +const int GIB_DELAY = 200; // only gib this often to keep performace hits when blowing up several mobs + +class idMultiModelAF : public idEntity { +public: + CLASS_PROTOTYPE( idMultiModelAF ); + + void Spawn( void ); + ~idMultiModelAF( void ); + + virtual void Think( void ); + virtual void Present( void ); + +protected: + idPhysics_AF physicsObj; + + void SetModelForId( int id, const idStr &modelName ); + +private: + idList modelHandles; + idList modelDefHandles; +}; + + +/* +=============================================================================== + +idChain + +Chain hanging down from the ceiling. Only used for debugging! + +=============================================================================== +*/ + +class idChain : public idMultiModelAF { +public: + CLASS_PROTOTYPE( idChain ); + + void Spawn( void ); + +protected: + void BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld = true ); +}; + + +/* +=============================================================================== + +idAFAttachment + +=============================================================================== +*/ + +typedef struct { + jointModTransform_t mod; + jointHandle_t from; + jointHandle_t to; +} copyJoints_t; + +class idAFAttachment : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFAttachment ); + + idAFAttachment( void ); + virtual ~idAFAttachment( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + +// RAVEN BEGIN +// bdube: added channel + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + // for getting th speaker position + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// jshepard: animations for heads + void Event_PlayAnim ( int channel, const char *animname ); +// jdischler: animations for heads + void Event_PlayCycle ( int channel, const char *animname ); + void Event_ClearAnims ( void ); + + +// RAVEN END + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + int StartLipSyncing( const char *speechDecl ); + void HandleLipSync( void ); + void EndLipSyncing( void ); + bool IsLipSyncing( void ) const; + + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + + + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + jointHandle_t soundJoint; + + int lipSyncAnim; // Anim that contains the visemes + class rvLipSyncData* lipSyncData; // The current instance of lip syncing data + + idList copyJoints; // copied from the body animation to the head model + + bool noPlayerImpactFX; +}; + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE bool idAFAttachment::IsLipSyncing( void ) const { + return !!lipSyncData; +} + +ID_INLINE void idAFAttachment::SetDamageJoint ( jointHandle_t _damageJoint ) { + damageJoint = _damageJoint; +} +// RAVEN END + +/* +=============================================================================== + +idAFEntity_Base + +=============================================================================== +*/ + +class idAFEntity_Base : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFEntity_Base ); + + idAFEntity_Base( void ); + virtual ~idAFEntity_Base( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeModelDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + virtual void ShowEditingDialog( void ); + + static void DropAFs( idEntity *ent, const char *type, idList *list ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound + + bool noPlayerImpactFX; + + void Event_SetConstraintPosition( const char *name, const idVec3 &pos ); +}; + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +extern const idEventDef EV_Gib; +extern const idEventDef EV_Gibbed; + +class idAFEntity_Gibbable : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Gibbable ); + + idAFEntity_Gibbable( void ); + ~idAFEntity_Gibbable( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +protected: + idRenderModel * skeletonModel; + int skeletonModelDefHandle; + bool gibbed; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void InitSkeletonModel( void ); + + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +class idAFEntity_Generic : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_Generic ); + + idAFEntity_Generic( void ); + ~idAFEntity_Generic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void KeepRunningPhysics( void ) { keepRunningPhysics = true; } + +private: + void Event_Activate( idEntity *activator ); + + bool keepRunningPhysics; +}; + + +/* +=============================================================================== + +idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +class idAFEntity_WithAttachedHead : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_WithAttachedHead ); + + idAFEntity_WithAttachedHead(); + ~idAFEntity_WithAttachedHead(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SetupHead( const char* headDefName = "" ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + +protected: + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + + idEntityPtr head; // safe pointer to attached head + +private: + + void Event_Gib( const char *damageDefName ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idAFEntity_Vehicle + +=============================================================================== +*/ + +class idAFEntity_Vehicle : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Vehicle ); + + idAFEntity_Vehicle( void ); + + void Spawn( void ); + void Use( idPlayer *player ); + +protected: + idPlayer * player; + jointHandle_t eyesJoint; + jointHandle_t steeringWheelJoint; + float wheelRadius; + float steerAngle; + float steerSpeed; +// const idDeclParticle * dustSmoke; + + float GetSteerAngle( void ); +}; + + +/* +=============================================================================== + +idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleFourWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleFourWheels ); + + idAFEntity_VehicleFourWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +protected: + idAFBody * wheels[4]; + idAFConstraint_Hinge * steering[2]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleSixWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSixWheels ); + + idAFEntity_VehicleSixWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +private: + idAFBody * wheels[6]; + idAFConstraint_Hinge * steering[4]; + jointHandle_t wheelJoints[6]; + float wheelAngles[6]; +}; + + +/* +=============================================================================== + +idAFEntity_SteamPipe + +=============================================================================== +*/ + +class idAFEntity_SteamPipe : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_SteamPipe ); + + idAFEntity_SteamPipe( void ); + ~idAFEntity_SteamPipe( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + int steamBody; + float steamForce; + float steamUpForce; + idForce_Constant force; + renderEntity_t steamRenderEntity; + qhandle_t steamModelDefHandle; + + void InitSteamRenderEntity( void ); +}; + + +/* +=============================================================================== + +idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +class idAFEntity_ClawFourFingers : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_ClawFourFingers ); + + idAFEntity_ClawFourFingers( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idAFConstraint_Hinge * fingers[4]; + + void Event_SetFingerAngle( float angle ); + void Event_StopFingers( void ); +}; + +// RAVEN BEGIN +// bdube: AFAttractor + +/* +=============================================================================== + +idAFAttractor + +=============================================================================== +*/ + +class rvAFAttractor : public idEntity { +public: + CLASS_PROTOTYPE( rvAFAttractor ); + + rvAFAttractor( void ) { } + +private: +}; + +// RAVEN END + +#endif /* !__GAME_AFENTITY_H__ */ diff --git a/source/mpgame/Actor.cpp b/source/mpgame/Actor.cpp new file mode 100644 index 0000000..e4be147 --- /dev/null +++ b/source/mpgame/Actor.cpp @@ -0,0 +1,3853 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "Vehicle/Vehicle.h" +#endif + +#include "ai/AI.h" +#include "ai/AI_Manager.h" + +/*********************************************************************** + + idAnimState + +***********************************************************************/ + +/* +===================== +idAnimState::idAnimState +===================== +*/ +idAnimState::idAnimState() { + self = NULL; + animator = NULL; + idleAnim = true; + disabled = true; + channel = ANIMCHANNEL_ALL; + animBlendFrames = 0; + lastAnimBlendFrames = 0; +} + +/* +===================== +idAnimState::~idAnimState +===================== +*/ +idAnimState::~idAnimState() { +} + +/* +===================== +idAnimState::Save +===================== +*/ +void idAnimState::Save( idSaveGame *savefile ) const { + + savefile->WriteBool( idleAnim ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( lastAnimBlendFrames ); + + savefile->WriteObject( self ); + + // Save the entity owner of the animator + savefile->WriteObject( animator->GetEntity() ); + + savefile->WriteInt( channel ); + savefile->WriteBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Save( savefile ); +// RAVEN END +} + +/* +===================== +idAnimState::Restore +===================== +*/ +void idAnimState::Restore( idRestoreGame *savefile ) { + + savefile->ReadBool( idleAnim ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( lastAnimBlendFrames ); + + savefile->ReadObject( reinterpret_cast( self ) ); + + idEntity *animowner; + savefile->ReadObject( reinterpret_cast( animowner ) ); + if ( animowner ) { + animator = animowner->GetAnimator(); + } + + savefile->ReadInt( channel ); + savefile->ReadBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Restore( savefile, self ); +// RAVEN END +} + +/* +===================== +idAnimState::Init +===================== +*/ +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it +void idAnimState::Init( idEntity *owner, idAnimator *_animator, int animchannel ) { +// RAVEN BEGIN + assert( owner ); + assert( _animator ); + self = owner; + animator = _animator; + channel = animchannel; + + stateThread.SetName ( va("%s_anim_%d", owner->GetName(), animchannel ) ); + stateThread.SetOwner ( owner ); +} + +/* +===================== +idAnimState::Shutdown +===================== +*/ +void idAnimState::Shutdown( void ) { + stateThread.Clear ( true ); +} + +/* +===================== +idAnimState::PostState +===================== +*/ +void idAnimState::PostState ( const char* statename, int blendFrames, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, blendFrames, delay, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + disabled = false; +} + +/* +===================== +idAnimState::SetState +===================== +*/ +void idAnimState::SetState( const char *statename, int blendFrames, int flags ) { + if ( SRESULT_OK != stateThread.SetState ( statename, blendFrames, 0, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + disabled = false; + idleAnim = false; +} + +/* +===================== +idAnimState::StopAnim +===================== +*/ +void idAnimState::StopAnim( int frames ) { + animBlendFrames = 0; + animator->Clear( channel, gameLocal.time, FRAME2MS( frames ) ); +} + +/* +===================== +idAnimState::PlayAnim +===================== +*/ +void idAnimState::PlayAnim( int anim ) { + if ( anim ) { + animator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::CycleAnim +===================== +*/ +void idAnimState::CycleAnim( int anim ) { + if ( anim ) { + animator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::BecomeIdle +===================== +*/ +void idAnimState::BecomeIdle( void ) { + idleAnim = true; +} + +/* +===================== +idAnimState::Disabled +===================== +*/ +bool idAnimState::Disabled( void ) const { + return disabled; +} + +/* +===================== +idAnimState::AnimDone +===================== +*/ +bool idAnimState::AnimDone( int blendFrames ) const { + int animDoneTime; + + animDoneTime = animator->CurrentAnim( channel )->GetEndTime(); + if ( animDoneTime < 0 ) { + // playing a cycle + return false; + } else if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } else { + return false; + } +} + +/* +===================== +idAnimState::IsIdle +===================== +*/ +bool idAnimState::IsIdle( void ) const { + return disabled || idleAnim; +} + +/* +===================== +idAnimState::GetAnimFlags +===================== +*/ +animFlags_t idAnimState::GetAnimFlags( void ) const { + animFlags_t flags; + + memset( &flags, 0, sizeof( flags ) ); + if ( !disabled && !AnimDone( 0 ) ) { + flags = animator->GetAnimFlags( animator->CurrentAnim( channel )->AnimNum() ); + } + + return flags; +} + +/* +===================== +idAnimState::Enable +===================== +*/ +void idAnimState::Enable( int blendFrames ) { + if ( disabled ) { + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + } +} + +/* +===================== +idAnimState::Disable +===================== +*/ +void idAnimState::Disable( void ) { + disabled = true; + idleAnim = false; +} + +/* +===================== +idAnimState::UpdateState +===================== +*/ +bool idAnimState::UpdateState( void ) { + if ( disabled ) { + return false; + } + + stateThread.Execute ( ); + + return true; +} + +/*********************************************************************** + + idActor + +***********************************************************************/ + +const idEventDef AI_EnableEyeFocus( "enableEyeFocus" ); +const idEventDef AI_DisableEyeFocus( "disableEyeFocus" ); +const idEventDef AI_EnableBlink( "enableBlinking" ); +const idEventDef AI_DisableBlink( "disableBlinking" ); +const idEventDef EV_Footstep( "footstep" ); +const idEventDef EV_FootstepLeft( "leftFoot" ); +const idEventDef EV_FootstepRight( "rightFoot" ); +const idEventDef EV_EnableWalkIK( "EnableWalkIK" ); +const idEventDef EV_DisableWalkIK( "DisableWalkIK" ); +const idEventDef EV_EnableLegIK( "EnableLegIK", "d" ); +const idEventDef EV_DisableLegIK( "DisableLegIK", "d" ); +const idEventDef AI_StopAnim( "stopAnim", "dd" ); +const idEventDef AI_PlayAnim( "playAnim", "ds", 'd' ); +const idEventDef AI_PlayCycle( "playCycle", "ds", 'd' ); +const idEventDef AI_IdleAnim( "idleAnim", "ds", 'd' ); +const idEventDef AI_SetSyncedAnimWeight( "setSyncedAnimWeight", "ddf" ); +const idEventDef AI_SetBlendFrames( "setBlendFrames", "dd" ); +const idEventDef AI_GetBlendFrames( "getBlendFrames", "d", 'd' ); +const idEventDef AI_AnimDone( "animDone", "dd", 'd' ); +const idEventDef AI_OverrideAnim( "overrideAnim", "d" ); +const idEventDef AI_EnableAnim( "enableAnim", "dd" ); +const idEventDef AI_PreventPain( "preventPain", "f" ); +const idEventDef AI_DisablePain( "disablePain" ); +const idEventDef AI_EnablePain( "enablePain" ); +const idEventDef AI_SetAnimPrefix( "setAnimPrefix", "s" ); +const idEventDef AI_HasEnemies( "hasEnemies", NULL, 'd' ); +const idEventDef AI_NextEnemy( "nextEnemy", "E", 'e' ); +const idEventDef AI_ClosestEnemyToPoint( "closestEnemyToPoint", "v", 'e' ); +const idEventDef AI_GetHead( "getHead", NULL, 'e' ); + + +// RAVEN BEGIN +// bdube: added +const idEventDef AI_Flashlight("flashlight", "d" ); +const idEventDef AI_Teleport("teleport", "vv"); +const idEventDef AI_EnterVehicle ( "enterVehicle", "e" ); +const idEventDef AI_ExitVehicle ( "exitVehicle", "d" ); +const idEventDef AI_PostExitVehicle ( "", "d" ); + +//jshepard: change animation rate +const idEventDef AI_SetAnimRate ( "setAnimRate","f"); +//MCG: damage over time +const idEventDef EV_DamageOverTime ( "damageOverTime","ddEEvsfd" ); +const idEventDef EV_DamageOverTimeEffect ( "damageOverTimeEffect","dds" ); +// MCG: script-callable joint crawl effect +const idEventDef EV_JointCrawlEffect ( "jointCrawlEffect","sf" ); + +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idActor ) + EVENT( AI_EnableEyeFocus, idActor::Event_EnableEyeFocus ) + EVENT( AI_DisableEyeFocus, idActor::Event_DisableEyeFocus ) + EVENT( AI_EnableBlink, idActor::Event_EnableBlink ) + EVENT( AI_DisableBlink, idActor::Event_DisableBlink ) + EVENT( EV_Footstep, idActor::Event_Footstep ) + EVENT( EV_FootstepLeft, idActor::Event_Footstep ) + EVENT( EV_FootstepRight, idActor::Event_Footstep ) + EVENT( EV_EnableWalkIK, idActor::Event_EnableWalkIK ) + EVENT( EV_DisableWalkIK, idActor::Event_DisableWalkIK ) + EVENT( EV_EnableLegIK, idActor::Event_EnableLegIK ) + EVENT( EV_DisableLegIK, idActor::Event_DisableLegIK ) + EVENT( AI_PreventPain, idActor::Event_PreventPain ) + EVENT( AI_DisablePain, idActor::Event_DisablePain ) + EVENT( AI_EnablePain, idActor::Event_EnablePain ) + EVENT( AI_SetAnimPrefix, idActor::Event_SetAnimPrefix ) + EVENT( AI_SetSyncedAnimWeight, idActor::Event_SetSyncedAnimWeight ) + EVENT( AI_SetBlendFrames, idActor::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idActor::Event_GetBlendFrames ) + EVENT( AI_OverrideAnim, idActor::Event_OverrideAnim ) + EVENT( AI_EnableAnim, idActor::Event_EnableAnim ) + EVENT( AI_HasEnemies, idActor::Event_HasEnemies ) + EVENT( AI_NextEnemy, idActor::Event_NextEnemy ) + EVENT( AI_ClosestEnemyToPoint, idActor::Event_ClosestEnemyToPoint ) + EVENT( EV_StopSound, idActor::Event_StopSound ) + EVENT( AI_GetHead, idActor::Event_GetHead ) + +// RAVEN BEGIN +// bdube: added + EVENT( AI_Flashlight, idActor::Event_Flashlight ) + EVENT( AI_Teleport, idActor::Event_Teleport ) + EVENT( AI_EnterVehicle, idActor::Event_EnterVehicle ) + + // twhitaker: Yeah... this just got confusing. + // basically, I need a delay in between the time the space bar is hit and the time the person actually get's ejected. + // this is mostly for things such as screen fades when exiting a vehicle. This was the least obtrusive way I could think of. + EVENT( AI_ExitVehicle, idActor::Event_PreExitVehicle ) + EVENT( AI_PostExitVehicle, idActor::Event_ExitVehicle ) + +// jshepard: added + EVENT( AI_SetAnimRate, idActor::Event_SetAnimRate ) + +// twhitaker: added animation support (mostly for vehicle purposes) + EVENT( AI_PlayAnim, idActor::Event_PlayAnim ) + +// MCG: added recurring damage + EVENT( EV_DamageOverTime, idActor::Event_DamageOverTime ) + EVENT( EV_DamageOverTimeEffect, idActor::Event_DamageOverTimeEffect ) +// MCG: script-callable joint crawl effect + EVENT( EV_JointCrawlEffect, idActor::Event_JointCrawlEffect ) +// RAVEN END + +END_CLASS + +CLASS_STATES_DECLARATION ( idActor ) + STATE ( "Wait_Frame", idActor::State_Wait_Frame ) + STATE ( "Wait_LegsAnim", idActor::State_Wait_LegsAnim ) + STATE ( "Wait_TorsoAnim", idActor::State_Wait_TorsoAnim ) +END_CLASS_STATES + +// RAVEN END + +/* +===================== +idActor::idActor +===================== +*/ +idActor::idActor( void ) +{ + viewAxis.Identity(); + + use_combat_bbox = false; + head = NULL; + + eyeOffset.Zero(); + chestOffset.Zero(); + modelOffset.Zero(); + + team = 0; + rank = 0; + fovDot = 0.0f; + pain_debounce_time = 0; + pain_delay = 0; + + leftEyeJoint = INVALID_JOINT; + rightEyeJoint = INVALID_JOINT; + soundJoint = INVALID_JOINT; + eyeOffsetJoint = INVALID_JOINT; + chestOffsetJoint = INVALID_JOINT; + neckJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + + deltaViewAngles.Zero(); + + painTime = 0; + inDamageEvent = false; + disablePain = true; + allowEyeFocus = false; + + blink_anim = NULL; + blink_time = 0; + blink_min = 0; + blink_max = 0; + + finalBoss = false; + + attachments.SetGranularity( 1 ); + + enemyNode.SetOwner( this ); + enemyList.SetOwner( this ); + + teamNode.SetOwner ( this ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlightHandle = -1; + + deathPushTime = 0; + + lightningEffects = 0; + lightningNextTime = 0; +} + +/* +===================== +idActor::~idActor +===================== +*/ +idActor::~idActor( void ) { +// RAVEN BEGIN +// bdube: flashlights + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef( flashlightHandle ); + flashlightHandle = -1; + } +// RAVEN END + + int i; + idEntity *ent; + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + ShutdownThreads(); +} + +/* +===================== +idActor::Spawn +===================== +*/ +void idActor::Spawn( void ) { + idEntity *ent; + idStr jointName; + float fovDegrees; + float fovDegreesClose; + + animPrefix = ""; + + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "team", "0", team ); + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + spawnArgs.GetBool( "use_combat_bbox", "0", use_combat_bbox ); + + viewAxis = GetPhysics()->GetAxis(); + + spawnArgs.GetFloat( "fov", "90", fovDegrees ); + spawnArgs.GetFloat( "fovClose", "200", fovDegreesClose ); + spawnArgs.GetFloat( "fovCloseRange", "180", fovCloseRange ); + SetFOV( fovDegrees, fovDegreesClose ); + + pain_debounce_time = 0; + + pain_delay = SEC2MS( spawnArgs.GetFloat( "pain_delay" ) ); + + LoadAF ( ); + + walkIK.Init( this, IK_ANIM, modelOffset ); + + // the animation used to be set to the IK_ANIM at this point, but that was fixed, resulting in + // attachments not binding correctly, so we're stuck setting the IK_ANIM before attaching things. + animator.ClearAllAnims( gameLocal.time, 0 ); +// RAVEN BEGIN +// jscott: new setframe stuff + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, animator.GetAnim( IK_ANIM ), frameBlend ); +// RAVEN END + + // spawn any attachments we might have + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_attach", NULL ); + while ( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue().c_str() ); + + // make items non-touchable so the player can't take them out of the character's hands + args.Set( "no_touch", "1" ); + + // don't let them drop to the floor + args.Set( "dropToFloor", "0" ); + + gameLocal.SpawnEntityDef( args, &ent ); + if ( !ent ) { + gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); + } else { + Attach( ent ); + } + kv = spawnArgs.MatchPrefix( "def_attach", kv ); + } + + SetupDamageGroups(); + + // MP sets up heads on players from UpdateModelSetup() + if( !gameLocal.isMultiplayer || !IsType( idPlayer::GetClassType() ) ) { + SetupHead ( ); + } + + // clear the bind anim + animator.ClearAllAnims( gameLocal.time, 0 ); + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } + + // set up blinking + blink_anim = headAnimator->GetAnim( "blink" ); + blink_time = 0; // it's ok to blink right away + blink_min = SEC2MS( spawnArgs.GetFloat( "blink_min", "0.5" ) ); + blink_max = SEC2MS( spawnArgs.GetFloat( "blink_max", "8" ) ); + fl.allowAutoBlink = spawnArgs.GetBool( "allowAutoBlink", "1" ); + + if ( spawnArgs.GetString( "sound_bone", "", jointName ) ) { + soundJoint = animator.GetJointHandle( jointName ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + finalBoss = spawnArgs.GetBool( "finalBoss" ); + +// RAVEN BEGIN +// bdube: flashlight + flashlightJoint = animator.GetJointHandle( spawnArgs.GetString ( "joint_flashlight", "flashlight" ) ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlight.suppressLightInViewID = entityNumber + 1; + flashlight.allowLightInViewID = 0; + flashlight.lightId = 1 + entityNumber; + + flashlight.allowLightInViewID = 1; + + idVec3 color; + spawnArgs.GetVector ( "flashlightColor", "1 1 1", color ); + + flashlight.pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + flashlight.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_flashlight", "muzzleflash" ), false ); + flashlight.shaderParms[ SHADERPARM_RED ] = color[0]; + flashlight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + flashlight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + flashlight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + flashlight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + flashlight.lightRadius[0] = flashlight.lightRadius[1] = + flashlight.lightRadius[2] = spawnArgs.GetFloat ( "flashlightRadius" ); + + if ( !flashlight.pointLight ) { + flashlight.target = spawnArgs.GetVector( "flashlightTarget" ); + flashlight.up = spawnArgs.GetVector( "flashlightUp" ); + flashlight.right = spawnArgs.GetVector( "flashlightRight" ); + flashlight.end = spawnArgs.GetVector( "flashlightTarget" ); + } + + spawnArgs.GetVector ( "flashlightOffset", "0 0 0", flashlightOffset ); + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + +// RAVEN END + + stateThread.SetName ( GetName() ); + stateThread.SetOwner ( this ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = true; +// RAVEN END + + FinishSetup(); +} + +/* +================ +idActor::FinishSetup +================ +*/ +void idActor::FinishSetup( void ) { + if ( spawnArgs.GetBool ( "flashlight", "0" ) ) { + FlashlightUpdate ( true ); + } + + SetupBody(); +} + +/* +================ +idActor::SetupHead +================ +*/ +void idActor::SetupHead( const char* headDefName, idVec3 headOffset ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); +// jshepard: allow for heads to override persona defs + headDefName = spawnArgs.GetString( "override_head", headDefName ); + } + + if ( headDefName[ 0 ] ) { + // free the old head if we want a new one + if( gameLocal.isServer ) { + if( head && idStr::Icmp( head->spawnArgs.GetString( "classname" ), headDefName ) ) { + head->SetName( va( "%s_oldhead", name.c_str() ) ); + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } else if( head ) { + // the current head is OK + return; + } + } + + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin + headOffset ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } + + if ( head ) { + int i; + // set the damage joint to be part of the head damage group + for( i = 0; i < damageGroups.Num(); i++ ) { + if ( damageGroups[ i ] == "head" ) { + head->SetDamageJoint ( static_cast( i ) ); + break; + } + } + + head->InitCopyJoints ( ); + head->SetInstance( instance ); + } +} + +/* +================ +idActor::Restart +================ +*/ +void idActor::Restart( void ) { + assert( !head.GetEntity() ); + // MP sets up heads from UpdateModelSetup() + if( !gameLocal.isMultiplayer ) { + SetupHead(); + } + FinishSetup(); +} + +/* +================ +idActor::Save + +archive object for savegame file +================ +*/ +void idActor::Save( idSaveGame *savefile ) const { + idActor *ent; + int i; + + savefile->WriteInt( team ); + + // cnicholson: This line was already commented out, so we aint changing it. + // No need to write/read teamNode + // idLinkList teamNode; + + savefile->WriteInt( rank ); + savefile->WriteMat3( viewAxis ); + +// twhitaker: this confuses me... should we be writing out enemyList.Num() or enemyList.Next()->enemyNode->Num(). I'm not sure what these variables represent. +// cnicholson: bdube said to do it how id does it, so here goes: + savefile->WriteInt( enemyList.Num() ); + for ( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + savefile->WriteObject( ent ); + } + + savefile->WriteInt( lightningNextTime );// cnicholson: Added unwritten var + savefile->WriteInt( lightningEffects ); // cnicholson: Added unwritten var + + savefile->WriteFloat( fovDot ); + savefile->WriteFloat( fovCloseDot ); + savefile->WriteFloat( fovCloseRange ); + savefile->WriteVec3( eyeOffset ); + savefile->WriteVec3( chestOffset ); + savefile->WriteVec3( modelOffset ); + savefile->WriteAngles( deltaViewAngles ); + + savefile->WriteInt( pain_debounce_time ); + savefile->WriteInt( pain_delay ); + + savefile->WriteInt( damageGroups.Num() ); + for( i = 0; i < damageGroups.Num(); i++ ) { + savefile->WriteString( damageGroups[ i ] ); + } + + savefile->WriteInt( damageScale.Num() ); + for( i = 0; i < damageScale.Num(); i++ ) { + savefile->WriteFloat( damageScale[ i ] ); + } +//MCG + savefile->WriteBool( inDamageEvent ); + + savefile->WriteBool( use_combat_bbox ); + + savefile->WriteJoint( leftEyeJoint ); + savefile->WriteJoint( rightEyeJoint ); + savefile->WriteJoint( soundJoint ); + savefile->WriteJoint( eyeOffsetJoint ); + savefile->WriteJoint( chestOffsetJoint ); + savefile->WriteJoint( neckJoint ); + savefile->WriteJoint( headJoint ); + + walkIK.Save( savefile ); + + savefile->WriteString( animPrefix ); + savefile->WriteString( painType ); + savefile->WriteString( painAnim ); + + savefile->WriteInt( blink_anim ); + savefile->WriteInt( blink_time ); + savefile->WriteInt( blink_min ); + savefile->WriteInt( blink_max ); + + headAnim.Save( savefile ); + torsoAnim.Save( savefile ); + legsAnim.Save( savefile ); + + stateThread.Save( savefile ); + + // idEntityPtr head; + head.Save( savefile ); // cnicholson: Added unwritten var + + savefile->WriteBool( disablePain ); + savefile->WriteBool( allowEyeFocus ); + savefile->WriteBool( finalBoss ); + + savefile->WriteInt( painTime ); + + savefile->WriteInt( attachments.Num() ); + for ( i = 0; i < attachments.Num(); i++ ) { + attachments[i].ent.Save( savefile ); + savefile->WriteInt( attachments[i].channel ); + } + + vehicleController.Save ( savefile ); + + // These aren't saved in the same order as they're declared, due to the dependency (I didn't see the need to change the order of declaration) + savefile->WriteInt ( flashlightHandle ); + savefile->WriteJoint ( flashlightJoint ); + savefile->WriteVec3 ( flashlightOffset ); + savefile->WriteRenderLight ( flashlight ); + + savefile->WriteInt( deathPushTime ); + savefile->WriteVec3( deathPushForce ); + savefile->WriteJoint( deathPushJoint ); +} + +/* +================ +idActor::Restore + +unarchives object from save game file +================ +*/ +void idActor::Restore( idRestoreGame *savefile ) { + int i, num; + idActor *ent; + + savefile->ReadInt( team ); + +// cnicholson: This line was already commented out, so we aint changing it. +// No need to write/read teamNode + // idLinkList teamNode; + + savefile->ReadInt( rank ); + savefile->ReadMat3( viewAxis ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->enemyNode.AddToEnd( enemyList ); + } + } + + savefile->ReadInt( lightningEffects ); + savefile->ReadInt( lightningNextTime ); + + savefile->ReadFloat( fovDot ); + savefile->ReadFloat( fovCloseDot ); + savefile->ReadFloat( fovCloseRange ); + savefile->ReadVec3( eyeOffset ); + savefile->ReadVec3( chestOffset ); + savefile->ReadVec3( modelOffset ); + savefile->ReadAngles( deltaViewAngles ); + + savefile->ReadInt( pain_debounce_time ); + savefile->ReadInt( pain_delay ); + + savefile->ReadInt( num ); + damageGroups.SetGranularity( 1 ); + damageGroups.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( damageGroups[ i ] ); + } + + savefile->ReadInt( num ); + damageScale.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadFloat( damageScale[ i ] ); + } +//MCG + savefile->ReadBool( inDamageEvent ); + + savefile->ReadBool( use_combat_bbox ); + + savefile->ReadJoint( leftEyeJoint ); + savefile->ReadJoint( rightEyeJoint ); + savefile->ReadJoint( soundJoint ); + savefile->ReadJoint( eyeOffsetJoint ); + savefile->ReadJoint( chestOffsetJoint ); + savefile->ReadJoint( neckJoint ); + savefile->ReadJoint( headJoint ); + + walkIK.Restore( savefile ); + + savefile->ReadString( animPrefix ); + savefile->ReadString( painType ); + savefile->ReadString( painAnim ); + + savefile->ReadInt( blink_anim ); + savefile->ReadInt( blink_time ); + savefile->ReadInt( blink_min ); + savefile->ReadInt( blink_max ); + + headAnim.Restore( savefile ); + torsoAnim.Restore( savefile ); + legsAnim.Restore( savefile ); + + stateThread.Restore( savefile, this ); + +// cnicholson: Restore unread var + // idEntityPtr head; + head.Restore(savefile); + + savefile->ReadBool( disablePain ); + savefile->ReadBool( allowEyeFocus ); + savefile->ReadBool( finalBoss ); + + savefile->ReadInt( painTime ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idAttachInfo &attach = attachments.Alloc(); + attach.ent.Restore( savefile ); + savefile->ReadInt( attach.channel ); + } + +// RAVEN BEGIN +// bdube: added + vehicleController.Restore ( savefile ); + + savefile->ReadInt ( flashlightHandle ); + savefile->ReadJoint ( flashlightJoint ); + savefile->ReadVec3 ( flashlightOffset ); + savefile->ReadRenderLight ( flashlight ); + if ( flashlightHandle != -1 ) { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + + savefile->ReadInt( deathPushTime ); + savefile->ReadVec3( deathPushForce ); + savefile->ReadJoint( deathPushJoint ); + +// mekberg: update this + FlashlightUpdate( ); +// RAVEN END +} + +/* +================ +idActor::Hide +================ +*/ +void idActor::Hide( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Hide(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Off(); + } + } + } + UnlinkCombat(); +} + +/* +================ +idActor::Show +================ +*/ +void idActor::Show( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Show(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->On(); + } + } + } + LinkCombat(); +} + +/* +============== +idActor::GetDefaultSurfaceType +============== +*/ +int idActor::GetDefaultSurfaceType( void ) const { + return SURFTYPE_FLESH; +} + +/* +================ +idActor::ProjectOverlay +================ +*/ +void idActor::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + idEntity *ent; + idEntity *next; + + idEntity::ProjectOverlay( origin, dir, size, material ); + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + if ( ent->fl.takedamage && ent->spawnArgs.GetBool( "bleed" ) ) { + ent->ProjectOverlay( origin, dir, size, material ); + } + } + } +} + +/* +================ +idActor::LoadAF +================ +*/ +bool idActor::LoadAF( const char* keyname, bool purgeAF /* = false */ ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) || !fileName.Length() ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName, purgeAF ); +} + +/* +===================== +idActor::SetupBody +===================== +*/ +void idActor::SetupBody( void ) { + const char* jointname; + idAnimator* headAnimator; + float height; + + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ClearAllJoints(); + + // Cache the head entity pointer and determine which animator to use + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = GetAnimator( ); + } + + // Get left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_leftEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_leftEye" ); + } + leftEyeJoint = headAnimator->GetJointHandle( jointname ); + + // Get right eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_rightEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_rightEye" ); + } + rightEyeJoint = headAnimator->GetJointHandle( jointname ); + + // If head height is specified, just use that + if ( spawnArgs.GetFloat( "eye_height", "0", height ) ) { + SetEyeHeight( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString( "joint_eyeOffset", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_eyeOffset" ); + } + // Get the eye offset joint + eyeOffsetJoint = headAnimator->GetJointHandle( jointname ); + } + + // If eye height is specified, just use that + if ( spawnArgs.GetFloat( "chest_height", "0", height ) ) { + SetChestHeight( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + spawnArgs.GetString( "joint_chestOffset", "", &jointname ); + // Get the chest offset joint + chestOffsetJoint = animator.GetJointHandle( jointname ); + } + + // Get the neck joint + spawnArgs.GetString( "joint_look_neck", "", &jointname ); + neckJoint = animator.GetJointHandle( jointname ); + + // Get the head joint + spawnArgs.GetString( "joint_look_head", "", &jointname ); + headJoint = animator.GetJointHandle( jointname ); + + headAnim.Init( this, &animator, ANIMCHANNEL_HEAD ); + torsoAnim.Init( this, &animator, ANIMCHANNEL_TORSO ); + legsAnim.Init( this, &animator, ANIMCHANNEL_LEGS ); +} + +/* +===================== +idActor::CheckBlink +===================== +*/ +void idActor::CheckBlink( void ) { + // check if it's time to blink + if ( !blink_anim || ( health <= 0 ) || ( blink_time > gameLocal.time ) || !fl.allowAutoBlink ) { + return; + } + + idAnimator *animator = head.GetEntity() ? head->GetAnimator() : &this->animator; + animator->PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + + // set the next blink time + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +================ +idActor::GetPhysicsToVisualTransform +================ +*/ +bool idActor::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } +// RAVEN BEGIN +// bdube: position player in seat (nmckenzie: copy and paste from the player version of this call) + if ( vehicleController.IsDriving ( ) ) { + vehicleController.GetDriverPosition ( origin, axis ); + return true; + } +// RAVEN END + + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +idActor::GetPhysicsToSoundTransform +================ +*/ +bool idActor::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + origin += modelOffset; + axis = viewAxis; + } else { + origin = GetPhysics()->GetGravityNormal() * -eyeOffset.z; + axis.Identity(); + } + return true; +} + +/*********************************************************************** + + script state management + +***********************************************************************/ + +/* +================ +idActor::ShutdownThreads +================ +*/ +void idActor::ShutdownThreads( void ) { + headAnim.Shutdown(); + torsoAnim.Shutdown(); + legsAnim.Shutdown(); +} + +/* +===================== +idActor::OnStateThreadClear +===================== +*/ +void idActor::OnStateThreadClear( const char *statename, int flags ) { +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const char *statename, int flags ) { + OnStateThreadClear( statename, flags ); + stateThread.SetState ( statename, 0, 0, flags ); +} + +/* +===================== +idActor::PostState +===================== +*/ +void idActor::PostState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::InterruptState +===================== +*/ +void idActor::InterruptState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.InterruptState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::UpdateState +===================== +*/ +void idActor::UpdateState ( void ) { + stateThread.Execute ( ); +} + +/*********************************************************************** + + vision + +***********************************************************************/ + +/* +===================== +idActor::setFov +===================== +*/ +void idActor::SetFOV( float fov, float fovClose ) { + fovDot = idMath::Cos( DEG2RAD( fov * 0.5f ) ); + fovCloseDot = idMath::Cos( DEG2RAD( fovClose * 0.5f ) ); +} + +/* +===================== +idActor::SetEyeHeight +===================== +*/ +void idActor::SetEyeHeight( float height ) { + eyeOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::EyeHeight +===================== +*/ +float idActor::EyeHeight( void ) const { + return eyeOffset.z; +} + +/* +===================== +idActor::SetChestHeight +===================== +*/ +void idActor::SetChestHeight( float height ) { + chestOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::GetEyePosition +===================== +*/ +idVec3 idActor::GetEyePosition( void ) const { + return GetPhysics()->GetOrigin() + eyeOffset * viewAxis; +} + +/* +===================== +idActor::GetChestPosition +===================== +*/ +idVec3 idActor::GetChestPosition( void ) const { + return GetPhysics()->GetOrigin() + chestOffset * viewAxis; +} + +// RAVEN BEGIN +// bdube: flashlights + +/* +===================== +idActor::GetGroundEntity +===================== +*/ +idEntity* idActor::GetGroundEntity( void ) const { + return static_cast(GetPhysics())->GetGroundEntity(); +} + +/* +===================== +idActor::Present +===================== +*/ +void idActor::Present( void ) { + idAFEntity_Gibbable::Present(); + + FlashlightUpdate(); +} + +/* +===================== +idActor::Event_Teleport +===================== +*/ +void idActor::Event_Teleport( idVec3 &newPos, idVec3 &newAngles ) { + Teleport ( newPos, newAngles.ToAngles(), NULL ); +} + +/* +===================== +idActor::Event_EnterVehicle +===================== +*/ +void idActor::Event_EnterVehicle ( idEntity* vehicle ) { + if ( IsInVehicle ( ) ) { + return; + } + EnterVehicle ( vehicle ); +} + +/* +===================== +idActor::Event_ExitVehicle +===================== +*/ +void idActor::Event_ExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + ExitVehicle ( force ); +} + +/* +===================== +idActor::Event_PreExitVehicle +===================== +*/ +void idActor::Event_PreExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + + // call the script func regardless of the fact that we may not be getting out just yet. + // this allows things in the script to happen before ejection actually occurs (such as screen fades). + vehicleController.GetVehicle()->OnExit(); + + // this is done because having an exit delay when the player is dead was causing bustedness if you died in the walker + // specifically the restart menu would not appear if you were still in the walker + if ( health > 0 ) { + PostEventMS( &AI_PostExitVehicle, vehicleController.GetVehicle()->spawnArgs.GetInt( "exit_vehicle_delay" ), force ); + } + else { + ExitVehicle(true); + } +} + +/* +===================== +idActor::Event_Flashlight +===================== +*/ +void idActor::Event_Flashlight( bool on ) { + if ( on ) { + FlashlightUpdate(true); + } else { + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( flashlightHandle ); + flashlightHandle = -1; + } + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + StopEffect( "fx_flashlight", true ); + } + } +} + +/* +===================== +idActor::FlashlightUpdate +===================== +*/ +void idActor::FlashlightUpdate ( bool forceOn ) { + + // Dont do anything if flashlight is off and its not being forced on + if ( !forceOn && flashlightHandle == -1 ) { + return; + } + + if ( !flashlight.lightRadius[0] || flashlightJoint == INVALID_JOINT ) { + return; + } + + if ( forceOn && flashlightHandle == -1 ) { + //first time turning it on + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + ShowSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + PlayEffect( "fx_flashlight", flashlightJoint, true ); + } + } + + // the flash has an explicit joint for locating it + GetJointWorldTransform ( flashlightJoint, gameLocal.time, flashlight.origin, flashlight.axis ); + flashlight.origin += flashlightOffset * flashlight.axis; + + if ( flashlightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( flashlightHandle, &flashlight ); + } else { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + +} + +// RAVEN END + +/* +===================== +idActor::GetViewPos +===================== +*/ +void idActor::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + origin = GetEyePosition(); + axis = viewAxis; +} + +/* +===================== +idActor::CheckFOV +===================== +*/ +bool idActor::CheckFOV( const idVec3 &pos, float ang ) const { + float testAng = ( ang != -1.0f ) ? idMath::Cos( DEG2RAD( ang * 0.5f ) ) : fovDot; + if ( testAng == 1.0f ) { + return true; + } + + if(!GetPhysics()) { + return false; + } + + float dot; + float dist; + idVec3 delta; + + delta = pos - GetEyePosition(); + dist = delta.LengthFast(); + + //NOTE!!! + //This logic is BACKWARDS, but it's too late in the project + //for me to feel comfortable fixing this. It SHOULD be: + //if ( ang == -1.0f ) + // - MCG + if ( ang != -1.0f ) + {//not overriding dot test value + if (distGetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + dot = viewAxis[ 0 ] * delta; + + return ( dot >= testAng ); +} + +/* +===================== +idActor::HasFOV +===================== +*/ + +bool idActor::HasFOV( idEntity *ent ) +{ + // Fixme: Make this do something, anything. + return true; +} +// RAVEN END + +/* +===================== +idActor::CanSee +===================== +*/ +bool idActor::CanSee( const idEntity *ent, bool useFov ) const { + return CanSeeFrom ( GetEyePosition ( ), ent, useFov ); +} + +/* +===================== +idActor::CanSeeFrom +===================== +*/ +bool idActor::CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFov ) const { + idVec3 toPos; + + if ( !ent || ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ((idActor*)ent)->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetAbsBounds().GetCenter ( ); + } + + return CanSeeFrom ( from, toPos, useFov ); +} + +bool idActor::CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFov ) const { + trace_t tr; + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + return true; + } + gameLocal.TracePoint( this, tr, from, toPos, MASK_OPAQUE, this ); + if ( tr.fraction >= 1.0f ) { // || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + + return false; +} + +/* +===================== +idActor::GetRenderView +===================== +*/ +renderView_t *idActor::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->viewaxis = viewAxis; + rv->vieworg = GetEyePosition(); + return rv; +} + +/*********************************************************************** + + Model/Ragdoll + +***********************************************************************/ + +/* +================ +idActor::SetCombatModel +================ +*/ +void idActor::SetCombatModel( void ) { + idAFAttachment *headEnt; + +// RAVEN BEGIN +// bdube: set the combat model reguardless + if ( 1 ) { // !use_combat_bbox ) { +// RAVEN END + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->SetCombatModel(); + } + } +} + +/* +================ +idActor::GetCombatModel +================ +*/ +idClipModel *idActor::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idActor::LinkCombat +================ +*/ +void idActor::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden || use_combat_bbox ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idActor::UnlinkCombat +================ +*/ +void idActor::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idActor::StartRagdoll +================ +*/ +bool idActor::StartRagdoll( void ) { + float slomoStart, slomoEnd; + float jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd; + float contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd; + + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // Raise the origin up 5 units to help ensure the ragdoll doesnt start in the ground + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -5.0f ); + UpdateModelTransform(); + + // disable the monster bounding box + GetPhysics()->DisableClip(); + + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" ); + slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" ); + + // do the first part of the death in slow motion + af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd ); + + jointFrictionDent = spawnArgs.GetFloat( "ragdoll_jointFrictionDent", "0.1" ); + jointFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionStart", "0.2" ); + jointFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionEnd", "1.2" ); + + // set joint friction dent + af.GetPhysics()->SetJointFrictionDent( jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd ); + + contactFrictionDent = spawnArgs.GetFloat( "ragdoll_contactFrictionDent", "0.1" ); + contactFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionStart", "1.0" ); + contactFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionEnd", "2.0" ); + + // set contact friction dent + af.GetPhysics()->SetContactFrictionDent( contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd ); + + // drop any items the actor is holding + idList list; + idMoveableItem::DropItems( this, "death", &list ); + for ( int i = 0; i < list.Num(); i++ ) { + if ( list[i] && list[i]->GetPhysics() ) + { + idVec3 velocity; + float pitchDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float yawDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float rollDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + velocity.Set( pitchDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + yawDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + (gameLocal.random.RandomFloat() * 300.0f) + 100.0f ); + list[i]->GetPhysics()->SetAngularVelocity( idVec3( pitchDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + yawDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + rollDir*((gameLocal.random.RandomFloat() * 10.0f) + 3.0f))); + if ( gibbed ) { + //only throw them if we end up gibbed? + list[i]->GetPhysics()->SetLinearVelocity( velocity ); + } + } + } + + // drop any articulated figures the actor is holding + idAFEntity_Base::DropAFs( this, "death", NULL ); + + RemoveAttachments(); + +// RAVEN BEGIN +// bdube: evaluate one ragdoll frame + RunPhysics(); +// RAVEN END + + return true; +} + +/* +================ +idActor::StopRagdoll +================ +*/ +void idActor::StopRagdoll( void ) { + if ( af.IsActive() ) { + af.Stop(); + } +} + +/* +================ +idActor::UpdateAnimationControllers +================ +*/ +bool idActor::UpdateAnimationControllers( void ) { + + if ( af.IsActive() ) { + return idAFEntity_Gibbable::UpdateAnimationControllers(); + } else { + animator.ClearAFPose(); + } + + if ( walkIK.IsInitialized() ) { + walkIK.Evaluate(); + return true; + } + + idMat3 axis; + idAnimatedEntity* headEnt = head.GetEntity( ); + if ( !headEnt ) { + headEnt = this; + } + + // Dynamically update the eye offset if a joint was specified + if ( eyeOffsetJoint != INVALID_JOINT ) { + headEnt->GetJointWorldTransform( eyeOffsetJoint, gameLocal.time, eyeOffset, axis ); + eyeOffset = (eyeOffset - GetPhysics()->GetOrigin()) * viewAxis.Transpose( ); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorRed, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + // Dynamically update the chest offset if a joint was specified + if ( chestOffsetJoint != INVALID_JOINT ) { + GetJointWorldTransform( chestOffsetJoint, gameLocal.time, chestOffset, axis ); + chestOffset = ( chestOffset - GetPhysics()->GetOrigin() ) * viewAxis.Transpose(); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorPink, GetChestPosition(), GetChestPosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + return false; +} + +/* +================ +idActor::RemoveAttachments +================ +*/ +void idActor::RemoveAttachments( void ) { + int i; + idEntity *ent; + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && ent->spawnArgs.GetBool( "remove" ) ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +================ +idActor::Attach +================ +*/ +void idActor::Attach( idEntity *ent ) { + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + idStr jointName; + idAttachInfo &attach = attachments.Alloc(); + idAngles angleOffset; + idVec3 originOffset; + + jointName = ent->spawnArgs.GetString( "joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); + } + + angleOffset = ent->spawnArgs.GetAngles( "angles" ); + originOffset = ent->spawnArgs.GetVector( "origin" ); + + attach.channel = animator.GetChannelForJoint( joint ); + GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + attach.ent = ent; + + ent->SetOrigin( origin + originOffset * renderEntity.axis ); + idMat3 rotate = angleOffset.ToMat3(); + idMat3 newAxis = rotate * axis; + ent->SetAxis( newAxis ); + ent->BindToJoint( this, joint, true ); + ent->cinematic = cinematic; +} + +idEntity* idActor::FindAttachment( const char* attachmentName ) +{ + idEntity *ent = NULL; + const char* fullName = va("idAFAttachment_%s",attachmentName); + // find the specified attachment + for( int i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && !ent->name.CmpPrefix(fullName) ) { + return ent; + } + } + return NULL; +} + +void idActor::HideAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Hide(); + } +} + +void idActor::ShowAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Show(); + } +} + +/* +================ +idActor::Teleport +================ +*/ +void idActor::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + viewAxis = angles.ToMat3(); + + UpdateVisuals(); + + if ( !IsHidden() ) { + // kill anything at the new position + gameLocal.KillBox( this ); + } +} + +/* +================ +idActor::GetDeltaViewAngles +================ +*/ +const idAngles &idActor::GetDeltaViewAngles( void ) const { + return deltaViewAngles; +} + +/* +================ +idActor::SetDeltaViewAngles +================ +*/ +void idActor::SetDeltaViewAngles( const idAngles &delta ) { + deltaViewAngles = delta; +} + +/* +================ +idActor::HasEnemies +================ +*/ +bool idActor::HasEnemies( void ) const { + idActor *ent; + + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden ) { + return true; + } + } + + return false; +} + +/* +================ +idActor::ClosestEnemyToPoint +================ +*/ +idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos, float maxRange, bool returnFirst, bool checkPVS ) { + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + pvsHandle_t pvs; + + //just to supress the compiler warning + pvs.i = 0; + + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + } + + bestDistSquared = maxRange?(maxRange*maxRange):idMath::INFINITY; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - pos; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + if ( checkPVS ) { + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + } + bestEnt = ent; + bestDistSquared = distSquared; + if ( returnFirst ) { + break; + } + } + } + + if ( checkPVS ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + return bestEnt; +} + +/* +================ +idActor::EnemyWithMostHealth +================ +*/ +idActor *idActor::EnemyWithMostHealth() { + idActor *ent; + idActor *bestEnt; + + int most = -9999; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden && ( ent->health > most ) ) { + bestEnt = ent; + most = ent->health; + } + } + return bestEnt; +} + +/* +================ +idActor::OnLadder +================ +*/ +bool idActor::OnLadder( void ) const { + return false; +} + +/* +============== +idActor::GetAASLocation +============== +*/ +void idActor::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + idVec3 size; + idBounds bounds; + + GetFloorPos( 64.0f, pos ); + if ( !aas ) { + areaNum = 0; + return; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aas->PushPointIntoAreaNum( areaNum, pos ); + } +} + +/*********************************************************************** + + animation state + +***********************************************************************/ + +/* +===================== +idActor::StopAnimState +===================== +*/ +void idActor::StopAnimState( int channel ) { + GetAnimState( channel ).Shutdown ( ); +} + +/* +===================== +idActor::PostAnimState +===================== +*/ +void idActor::PostAnimState( int channel, const char* statename, int blendFrames, int delay, int flags ) { + GetAnimState( channel ).PostState( statename, blendFrames, delay, flags ); +} + +/* +===================== +idActor::SetAnimState +===================== +*/ +void idActor::SetAnimState( int channel, const char *statename, int blendFrames, int flags ) { + switch ( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.GetStateThread().Clear(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.GetStateThread().Clear(); + legsAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.GetStateThread().Clear(); + torsoAnim.Enable( blendFrames ); + break; + } + + OnStateChange( channel ); + + PostAnimState( channel, statename, blendFrames, flags ); +} + +/* +===================== +idActor::OnStateChange +===================== +*/ +void idActor::OnStateChange ( int channel ) { + allowEyeFocus = true; + + // Only clear eye focus on head channel change + if ( channel == ANIMCHANNEL_HEAD ) { + return; + } + + disablePain = false; +} + +/* +===================== +idActor::OnFriendlyFire +===================== +*/ +void idActor::OnFriendlyFire ( idActor* attacker ) { +} + +/* +===================== +idActor::UpdateAnimState +===================== +*/ +void idActor::UpdateAnimState( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ANIM); +// RAVEN END + headAnim.UpdateState(); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); +} + +/* +===================== +idActor::GetAnim +===================== +*/ +int idActor::GetAnim( int channel, const char *animname, bool forcePrefix ) { + int anim; + const char *temp; + idAnimator *animatorPtr; + + if ( channel == ANIMCHANNEL_HEAD ) { + if ( !head.GetEntity() ) { + return 0; + } + animatorPtr = head.GetEntity()->GetAnimator(); + } else { + animatorPtr = &animator; + } + + // Allow for anim substitution + animname = spawnArgs.GetString ( va("anim %s", animname ), animname ); + + if ( animPrefix.Length() ) { + temp = va( "%s_%s", animPrefix.c_str(), animname ); + anim = animatorPtr->GetAnim( temp ); + if ( anim ) { + return anim; + } else if ( forcePrefix ) { + return NULL; + } + } + + anim = animatorPtr->GetAnim( animname ); + + return anim; +} + +/* +=============== +idActor::SyncAnimChannels +=============== +*/ +void idActor::SyncAnimChannels( int channel, int syncToChannel, int blendFrames ) { + idAnimator *headAnimator; + idAFAttachment *headEnt; + int anim; + idAnimBlend *syncAnim; + int starttime; + int blendTime; + int cycle; + + blendTime = FRAME2MS( blendFrames ); + if ( channel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = animator.CurrentAnim( syncToChannel ); + if ( syncAnim ) { + anim = headAnimator->GetAnim( syncAnim->AnimFullName() ); + if ( !anim ) { + anim = headAnimator->GetAnim( syncAnim->AnimName() ); + } + if ( anim ) { + cycle = animator.CurrentAnim( syncToChannel )->GetCycleCount(); + starttime = animator.CurrentAnim( syncToChannel )->GetStartTime(); + headAnimator->PlayAnim( ANIMCHANNEL_LEGS, anim, gameLocal.time, blendTime ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetCycleCount( cycle ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetStartTime( starttime ); + } else { + headEnt->PlayIdleAnim( ANIMCHANNEL_LEGS, blendTime ); + } + } + } + } else if ( syncToChannel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS ); + if ( syncAnim ) { + anim = GetAnim( channel, syncAnim->AnimFullName() ); + if ( !anim ) { + anim = GetAnim( channel, syncAnim->AnimName() ); + } + if ( anim ) { + cycle = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetCycleCount(); + starttime = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetStartTime(); + animator.PlayAnim( channel, anim, gameLocal.time, blendTime ); + animator.CurrentAnim( channel )->SetCycleCount( cycle ); + animator.CurrentAnim( channel )->SetStartTime( starttime ); + } + } + } + } else { + animator.SyncAnimChannels( channel, syncToChannel, gameLocal.time, blendTime ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idActor::Gib +============ +*/ +void idActor::Gib( const idVec3 &dir, const char *damageDefName ) { + // for multiplayer we use client-side models + if ( gameLocal.isMultiplayer ) { + return; + } + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + StopSound( SND_CHANNEL_VOICE, false ); + + gameLocal.PlayEffect ( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +} + +void idActor::CheckDeathObjectives( void ) +{ + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player || !player->GetObjectiveHud() ) { + return; + } + + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_failed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_failed" ) ) ); + + player->FailObjective( spawnArgs.GetString( "objectivetitle_failed" ) ); + } + + if ( spawnArgs.GetString( "objectivetitle_completed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_completed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_completed" ) ) ); + + player->CompleteObjective( spawnArgs.GetString( "objectivetitle_completed" ) ); + } +} +/* +============ +idActor::Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +Bleeding wounds and surface overlays are applied in the collision code that +calls Damage() +============ +*/ +void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // friendly fire damage + bool noDmgFeedback = false; + if ( attacker->IsType ( idActor::Type ) && static_cast(attacker)->team == team ) { + + OnFriendlyFire ( static_cast(attacker) ); + + // jshepard: + // if the player deals friendly fire damage it is reduced to 0. If the damage is splash damage, + // then the victim should use a pain anim. + if( static_cast( attacker ) == gameLocal.GetLocalPlayer() ) { + + //play pain (maybe one day a special anim?) for damages that have the cower keyword + if ( damageDef->GetBool( "cower" )) { + Pain( inflictor, attacker, damage, dir, location ); + } + + //reduce the damage + damage = 0; + noDmgFeedback = true; + } + + // reduce friendly fire damage by the teamscale + damage = floor( damage * damageDef->GetFloat ( "teamScale", "0.5" ) ); + + + } + + if ( !IsType( idPlayer::GetClassType() ) && attacker->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)attacker)->aifl.killerGuard ) { + //Hard-coded to do lots of damage + damage = 100; + } + } + + if ( !noDmgFeedback ) { + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + } + +// RAVEN BEGIN +// jnewquist: FIXME - Was this removed from Xenon intentionally? +#ifdef _XENON + // singleplayer stat reporting. + if(!gameLocal.isMultiplayer) { + int methodOfDeath = -1; + // jnewquist: Use accessor for static class type + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + // RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::GetClassType() ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + if( methodOfDeath != -1 && attacker && attacker->IsType( idActor::Type ) ) { +// jnewquist: Fix Xenon compile warning + statManager->WeaponHit( static_cast (attacker) , this, methodOfDeath, !!damage ); + } + } +#endif +// RAVEN END + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + if ( damage > 0 ) { + int oldHealth = health; + AdjustHealthByDamage ( damage ); + if ( health <= 0 ) { + + //allow for quick burning + if (damageDef->GetFloat( "quickburn", "0" ) && !spawnArgs.GetFloat("no_quickburn", "0")) { + fl.quickBurn = true; + } + + if ( health < -999 ) { + health = -999; + } + //annoying hack for StartRagdoll + bool saveGibbed = gibbed; + bool canDMG_Gib = (spawnArgs.GetBool( "gib" ) | spawnArgs.GetBool( "DMG_gib" )); + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + gibbed = true; + } + } + Killed( inflictor, attacker, damage, dir, location ); + gibbed = saveGibbed; + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + Gib( dir, damageDefName ); + } + } + + if ( oldHealth > 0 && !gibbed && !fl.quickBurn) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::GetClassType() ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, damageDef, pushScale ); + } + } else { + painType = damageDef->GetString ( "pain" ); + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + /* + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calli/ng af.Rest() + BecomeActive( TH_PHYSICS ); + } + */ + } +} + +/* +===================== +idActor::InitDeathPush +===================== +*/ +void idActor::InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale ) { + idVec2 forceMin; + idVec2 forceMax; + + if( !af.IsActive() ) { + return; + } + + if ( deathPushTime > gameLocal.time ) { + return; + } + + if ( !damageDict->GetInt ( "deathPush", "0", deathPushTime ) || deathPushTime <= 0 ) { + return; + } + + damageDict->GetVec2( "deathPushMin", "", forceMin ); + damageDict->GetVec2( "deathPushMax", "", forceMax ); + +/* + forceMin *= (pushScale * GetPhysics()->GetMass()); + forceMax *= (pushScale * GetPhysics()->GetMass()); +*/ + forceMin *= (pushScale); + forceMax *= (pushScale); + + deathPushForce = dir; + deathPushForce.Normalize ( ); + deathPushForce = rvRandom::flrand ( forceMin.x, forceMax.x ) * deathPushForce + + -rvRandom::flrand ( forceMin.y, forceMax.y ) * GetPhysics()->GetGravityNormal ( ); + + deathPushTime += gameLocal.time; + deathPushJoint = (jointHandle_t) location; +} + +/* +===================== +idActor::DeathPush +===================== +*/ +void idActor::DeathPush ( void ) { + if ( deathPushTime <= gameLocal.time ) { + return; + } + + idVec3 center; + center = GetPhysics()->GetAbsBounds ( ).GetCenter(); + + GetPhysics()->ApplyImpulse ( 0, center, -0.5f * GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()) * GetPhysics()->GetGravity ( ) ); + + if ( deathPushJoint != INVALID_JOINT ) { + idVec3 origin; + idMat3 axis; + GetJointWorldTransform ( deathPushJoint, gameLocal.time, origin, axis ); + GetPhysics()->ApplyImpulse ( 0, origin, deathPushForce ); + } else { + GetPhysics()->ApplyImpulse ( 0, center, deathPushForce ); + } +} + +/* +===================== +idActor::SkipImpulse +===================== +*/ +bool idActor::SkipImpulse( idEntity* ent, int id ) { + return idAFEntity_Gibbable::SkipImpulse( ent, id ) || health <= 0 || gibbed || ent->IsType( idActor::GetClassType() ) || ent->IsType( idProjectile::GetClassType() ); +} + +/* +===================== +idActor::AddDamageEffect +===================== +*/ +void idActor::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !gameLocal.isMultiplayer && inflictor && inflictor->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast(inflictor)->team == team ) { + return; + } + } + idAFEntity_Gibbable::AddDamageEffect( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +idActor::ClearPain +===================== +*/ +void idActor::ClearPain( void ) { + pain_debounce_time = 0; +} + +/* +===================== +idActor::Pain +===================== +*/ +bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + if ( gameLocal.time < pain_debounce_time ) { + return false; + } + + // No pain if being hit by a friendly target + // jshepard: friendly targets can now cause pain +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return false; + } + } +*/ + + // don't play pain sounds more than necessary + pain_debounce_time = gameLocal.time + pain_delay; + + float f; +// RAVEN BEGIN +// mekberg: fixed divide by zero + float spawnHealth = spawnArgs.GetFloat ( "health", "1" ); + if (spawnHealth<1.0f) { + spawnHealth = 1.0f; // more devide by zero nonsense + } + f = (float)damage / spawnHealth; +// RAVEN END + if( gameLocal.isMultiplayer && IsType( idPlayer::GetClassType() ) && (health < 0.25f * ((idPlayer*)this)->inventory.maxHealth) ) { + StartSound( "snd_pain_low_health", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + if ( f > 0.75f ) { + StartSound( "snd_pain_huge", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.5f ) { + StartSound( "snd_pain_large", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.25f ) { + StartSound( "snd_pain_medium", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + StartSound( "snd_pain_small", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( disablePain || ( gameLocal.time < painTime ) ) { + // don't play a pain anim + return false; + } + + // set the pain anim + idStr damageGroup = GetDamageGroup( location ); + + painAnim.Clear ( ); + + // If we have both a damage group and a pain type then check that combination first + if ( damageGroup.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s_%s", painType.c_str(), damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the pain type? + if ( !painAnim.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s", painType.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the damage group? + if ( !painAnim.Length ( ) && damageGroup.Length ( ) ) { + painAnim = va ( "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + if ( !painAnim.Length() ) { + painAnim = "pain"; + } + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + return true; +} + +/* +===================== +idActor::SpawnGibs +===================== +*/ +void idActor::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + idAFEntity_Gibbable::SpawnGibs( dir, damageDefName ); + RemoveAttachments(); +} + +/* +===================== +idActor::SetupDamageGroups + +FIXME: only store group names once and store an index for each joint +===================== +*/ +void idActor::SetupDamageGroups( void ) { + int i; + const idKeyValue *arg; + idStr groupname; + idList jointList; + int jointnum; + float scale; + + // create damage zones + damageGroups.SetNum( animator.NumJoints() ); + arg = spawnArgs.MatchPrefix( "damage_zone ", NULL ); + while ( arg ) { + groupname = arg->GetKey(); + groupname.Strip( "damage_zone " ); + animator.GetJointList( arg->GetValue(), jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + damageGroups[ jointnum ] = groupname; + } + jointList.Clear(); + arg = spawnArgs.MatchPrefix( "damage_zone ", arg ); + } + + // initilize the damage zones to normal damage + damageScale.SetNum( animator.NumJoints() ); + for( i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = 1.0f; + } + + // set the percentage on damage zones + arg = spawnArgs.MatchPrefix( "damage_scale ", NULL ); + while ( arg ) { + scale = atof( arg->GetValue() ); + groupname = arg->GetKey(); + groupname.Strip( "damage_scale " ); + for( i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupname ) { + damageScale[ i ] = scale; + } + } + arg = spawnArgs.MatchPrefix( "damage_scale ", arg ); + } +} + +/* +===================== +idActor::GetDamageForLocation +===================== +*/ +int idActor::GetDamageForLocation( int damage, int location ) { + if ( ( location < 0 ) || ( location >= damageScale.Num() ) ) { + return damage; + } + + return (int)ceil( damage * damageScale[ location ] ); +} + +/* +===================== +idActor::GetDamageGroup +===================== +*/ +const char *idActor::GetDamageGroup( int location ) { + if ( ( location < 0 ) || ( location >= damageGroups.Num() ) ) { + return ""; + } + + return damageGroups[ location ]; +} + + +// RAVEN BEGIN +// bdube: added for vehicle +/* +============== +idActor::ExitVehicle +============== +*/ +bool idActor::ExitVehicle ( bool force ) { + idMat3 axis; + idVec3 origin; + + if ( !IsInVehicle ( ) ) { + return false; + } + + if ( vehicleController.GetVehicle()->IsLocked() ) { + if ( force ) { + vehicleController.GetVehicle()->Unlock(); + } else { + return false; + } + } + + if( !vehicleController.FindClearExitPoint(origin, axis) ) { + if ( force ) { + origin = GetPhysics()->GetOrigin() + idVec3( spawnArgs.GetVector( "forced_exit_offset", "-100 0 0" ) ); + axis = GetPhysics()->GetAxis(); + } else { + return false; + } + } + + vehicleController.Eject ( force ); + + GetPhysics()->SetOrigin( origin ); + viewAxis = axis[0].ToMat3(); + GetPhysics()->SetAxis( mat3_identity ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + return true; +} + +/* +===================== +idActor::EnterVehicle +===================== +*/ +bool idActor::EnterVehicle ( idEntity* ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsInVehicle ( ) || !ent->IsType ( rvVehicle::GetClassType() ) ) { +// RAVEN END + return false ; + } + + // Get in the vehicle + if ( !vehicleController.Drive ( static_cast(ent), this ) ) { + return false; + } + + return true; +} + +// RAVEN END + + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +===================== +idActor::FootStep +===================== +*/ +void idActor::FootStep( void ) { + const char* sound; + const rvDeclMatType* materialType; + + if ( !GetPhysics()->HasGroundContacts() ) { + return; + } + + // start footstep sound based on material type + materialType = GetPhysics()->GetContact( 0 ).materialType; + sound = NULL; + + // Sound based on material type? + if ( materialType ) { + sound = spawnArgs.GetString( va( "snd_footstep_%s", materialType->GetName() ) ); + } + if ( !sound || !*sound ) { + sound = spawnArgs.GetString( "snd_footstep" ); + } + + // If we have a sound then play it + if ( sound && *sound ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::Event_EnableEyeFocus( void ) { + allowEyeFocus = true; + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableEyeFocus +===================== +*/ +void idActor::Event_DisableEyeFocus( void ) { + allowEyeFocus = false; + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } else { + animator.Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } +} + +/* +===================== +idActor::Event_EnableBlink +===================== +*/ +void idActor::Event_EnableBlink( void ) { + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableBlink +===================== +*/ +void idActor::Event_DisableBlink( void ) { + blink_time = 0x7FFFFFFF; +} + +/* +=============== +idActor::Event_Footstep +=============== +*/ +void idActor::Event_Footstep( void ) { + FootStep ( ); +} + +/* +===================== +idActor::Event_EnableWalkIK +===================== +*/ +void idActor::Event_EnableWalkIK( void ) { + walkIK.EnableAll(); +} + +/* +===================== +idActor::Event_DisableWalkIK +===================== +*/ +void idActor::Event_DisableWalkIK( void ) { + walkIK.DisableAll(); +} + +/* +===================== +idActor::Event_EnableLegIK +===================== +*/ +void idActor::Event_EnableLegIK( int num ) { + walkIK.EnableLeg( num ); +} + +/* +===================== +idActor::Event_DisableLegIK +===================== +*/ +void idActor::Event_DisableLegIK( int num ) { + walkIK.DisableLeg( num ); +} + +/* +===================== +idActor::Event_PreventPain +===================== +*/ +void idActor::Event_PreventPain( float duration ) { + painTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idActor::Event_DisablePain +=============== +*/ +void idActor::Event_DisablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END +} + +/* +=============== +idActor::Event_EnablePain +=============== +*/ +void idActor::Event_EnablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = false; +// RAVEN END +} + +/* +===================== +idActor::Event_SetAnimPrefix +===================== +*/ +void idActor::Event_SetAnimPrefix( const char *prefix ) { + animPrefix = prefix; +} + +/* +=============== +idActor::Event_StopAnim +=============== +*/ +void idActor::Event_StopAnim( int channel, int frames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.StopAnim( frames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_PlayAnim +=============== +*/ +void idActor::Event_PlayAnim( int channel, const char *animname ) { + idThread::ReturnFloat( MS2SEC(PlayAnim(channel, animname, -1)) ); +} + +/* +=============== +idActor::Event_PlayCycle +=============== +*/ +void idActor::Event_PlayCycle( int channel, const char *animname ) { + PlayCycle ( channel, animname, -1 ); + idThread::ReturnInt( true ); +} + +/* +===================== +idAI::DebugFilter +===================== +*/ +bool idActor::DebugFilter ( const idCVar& test ) const { + return ( health>0 && (test.GetBool() || test.GetInteger()>0) && (!ai_debugFilterString.GetString()[0] || !stricmp( name.c_str(), ai_debugFilterString.GetString() ))); +} + +/* +=============== +idActor::Event_IdleAnim +=============== +*/ +void idActor::Event_IdleAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso body if it doesn't override idle anims + headAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + // everything is idle, so play the anim on the head and copy it to the torso and legs + headAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } else if ( torsoAnim.IsIdle() ) { + // sync the head and torso to the legs + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, headAnim.animBlendFrames ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } else { + // sync the head to the torso + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, headAnim.animBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + if ( legsAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to legs if legs anim doesn't override idle anims + torsoAnim.CycleAnim( anim ); + } else if ( legsAnim.IsIdle() ) { + // play the anim in both legs and torso + torsoAnim.CycleAnim( anim ); + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + // sync the anim to the legs + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } + + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso if torso anim doesn't override idle anims + legsAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() ) { + // play the anim in both legs and torso + legsAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } else { + // sync the anim to the torso + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, legsAnim.animBlendFrames ); + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +================ +idActor::Event_SetSyncedAnimWeight +================ +*/ +void idActor::Event_SetSyncedAnimWeight( int channel, int anim, float weight ) { + idEntity *headEnt; + + headEnt = head.GetEntity(); + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headEnt ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } else { + animator.CurrentAnim( ANIMCHANNEL_HEAD )->SetSyncedAnimWeight( anim, weight ); + } + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + case ANIMCHANNEL_TORSO : + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + break; + + case ANIMCHANNEL_LEGS : + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +=============== +idActor::Event_OverrideAnim +=============== +*/ +void idActor::Event_OverrideAnim( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Disable(); + if ( !torsoAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_EnableAnim +=============== +*/ +void idActor::Event_EnableAnim( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Enable( blendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_SetBlendFrames +=============== +*/ +void idActor::Event_SetBlendFrames( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.animBlendFrames = blendFrames; + headAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.animBlendFrames = blendFrames; + torsoAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.animBlendFrames = blendFrames; + legsAnim.lastAnimBlendFrames = blendFrames; + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_GetBlendFrames +=============== +*/ +void idActor::Event_GetBlendFrames( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + idThread::ReturnInt( headAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_TORSO : + idThread::ReturnInt( torsoAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_LEGS : + idThread::ReturnInt( legsAnim.animBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +================ +idActor::Event_HasEnemies +================ +*/ +void idActor::Event_HasEnemies( void ) { + bool hasEnemy; + + hasEnemy = HasEnemies(); + idThread::ReturnInt( hasEnemy ); +} + +/* +================ +idActor::Event_NextEnemy +================ +*/ +void idActor::Event_NextEnemy( idEntity *ent ) { + idActor *actor; + + if ( !ent || ( ent == this ) ) { + actor = enemyList.Next(); + } else { + if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' cannot be an enemy", ent->name.c_str() ); + } + + actor = static_cast( ent ); + if ( actor->enemyNode.ListHead() != &enemyList ) { + gameLocal.Error( "'%s' is not in '%s' enemy list", actor->name.c_str(), name.c_str() ); + } + } + + for( ; actor != NULL; actor = actor->enemyNode.Next() ) { + if ( !actor->fl.hidden ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idActor::Event_ClosestEnemyToPoint +================ +*/ +void idActor::Event_ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *bestEnt = ClosestEnemyToPoint( pos ); + idThread::ReturnEntity( bestEnt ); +} + +/* +================ +idActor::Event_StopSound +================ +*/ +void idActor::Event_StopSound( int channel, int netSync ) { + if ( channel == SND_CHANNEL_VOICE ) { + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->StopSound( channel, ( netSync != 0 ) ); + } + } + StopSound( channel, ( netSync != 0 ) ); +} + +/* +===================== +idActor::Event_GetHead +===================== +*/ +void idActor::Event_GetHead( void ) { + idThread::ReturnEntity( head.GetEntity() ); +} + +// RAVEN BEGIN +// jshepard: added +/* +===================== +idActor::Event_SetAnimRate +===================== +*/ +void idActor::Event_SetAnimRate( float multiplier ) { + animator.SetPlaybackRate(multiplier); +} + + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idActor::State_Wait_Frame + +Stop a state thread for a single frame +================ +*/ +stateResult_t idActor::State_Wait_Frame ( const stateParms_t& parms ) { + return SRESULT_DONE_WAIT; +} + +/* +================ +idActor::State_Wait_LegsAnim + +Stop a state thread until the animation running on the legs channel is finished +================ +*/ +stateResult_t idActor::State_Wait_LegsAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::State_Wait_TorsoAnim + +Stop a state thread until the animation running on the torso channel is finished +================ +*/ +stateResult_t idActor::State_Wait_TorsoAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::PlayAnim +================ +*/ +int idActor::PlayAnim ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + idEntity *headEnt; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + + if( ai_animShow.GetBool() ){ + gameLocal.DPrintf( "Playing animation '%s' on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "head", "" ) ); + } + + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return 0; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnim.idleAnim = false; + headAnim.PlayAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.PlayAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.PlayAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default : + gameLocal.Error( "Unknown anim group" ); + break; + } + + return animator.CurrentAnim( channel )->Length(); +} + +/* +================ +idActor::PlayCycle +================ +*/ +bool idActor::PlayCycle ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return false; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.idleAnim = false; + headAnim.CycleAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.CycleAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.CycleAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + return true; +} + +void idActor::IdleAnim ( int channel, const char *name, int blendFrames ) { + Event_SetBlendFrames ( channel, blendFrames ); + Event_IdleAnim ( channel, name ); +} + +void idActor::OverrideAnim ( int channel ) { + Event_OverrideAnim ( channel ); +} + +idAnimState& idActor::GetAnimState ( int channel ) { + switch ( channel ) { + case ANIMCHANNEL_LEGS: return legsAnim; + case ANIMCHANNEL_TORSO: return torsoAnim; + case ANIMCHANNEL_HEAD: return headAnim; + default: + gameLocal.Error( "idActor::GetAnimState: Unknown anim channel" ); + return torsoAnim; + } +} + +void idActor::DisableAnimState ( int channel ) { + Event_OverrideAnim ( channel ); +// GetAnimState ( channel ).Disable ( ); +} + +void idActor::EnableAnimState ( int channel ) { + GetAnimState ( channel ).Enable ( 4 ); +} + +bool idActor::HasAnim ( int channel, const char* animname, bool forcePrefix ) { + return GetAnim( channel, animname, forcePrefix ) != NULL; +} + +bool idActor::AnimDone ( int channel, int blendFrames ) { + return GetAnimState( channel ).AnimDone ( blendFrames ); +} + +/* +===================== +idActor::GetDebugInfo +===================== +*/ +void idActor::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAFEntity_Gibbable::GetDebugInfo ( proc, userData ); + + proc ( "idActor", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); + + proc ( "idActor", "legs_state", legsAnim.GetStateThread().GetState()?legsAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "legs_disable", legsAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "legs_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) : "", userData ); + + proc ( "idActor", "torso_state", torsoAnim.GetStateThread().GetState()?torsoAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "torso_disabled", torsoAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "torso_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) : "", userData ); + + proc ( "idActor", "head_state", headAnim.GetStateThread().GetState()?headAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "head_disabled", headAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "head_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD )->AnimName ( ) : "", userData ); + + proc ( "idActor", "painAnim", painAnim.c_str(), userData ); + proc ( "idActor", "animPrefix", animPrefix.c_str(), userData ); +} + +//MCG: damage over time +void idActor::Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + inDamageEvent = true; + Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + inDamageEvent = false; + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + } +} + +void idActor::Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), this, interval ); + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +// MCG: script-callable joint crawl effect +void idActor::Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ) { + if ( effectKeyName ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( spawnArgs, effectKeyName ), this, 100 ); + effect->Play ( gameLocal.GetTime(), false ); + crawlSecs -= 0.1f; + if ( crawlSecs >= 0.1f ) { + PostEventMS( &EV_JointCrawlEffect, 100, effectKeyName, crawlSecs ); + } + } +} + +idEntity* idActor::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + + if ( testElevator && groundEnt != testElevator ) { + return groundEnt; + } + + idEntity* traceEnt; + idVec3 testPoint = GetPhysics()->GetOrigin(); + idVec3 testBottom; + testPoint.z += 1.0f; + + for ( int x = 0; x < 2; x++ ) { + testPoint.x = GetPhysics()->GetAbsBounds()[x].x; + for ( int y = 0; y < 2; y++ ) { + testPoint.y = GetPhysics()->GetAbsBounds()[y].y; + testBottom = testPoint; + testBottom.z -= 65.0f; + + trace_t tr; + gameLocal.TracePoint( this, tr, testPoint, testBottom, GetPhysics()->GetClipMask(), this ); + traceEnt = gameLocal.FindEntity( tr.c.entityNum ); + if ( !traceEnt ) { + return NULL; + } + while ( traceEnt->GetBindMaster() ) { + traceEnt = traceEnt->GetBindMaster(); + } + if ( traceEnt != groundEnt ) { + return traceEnt; + } + if ( testElevator && traceEnt != testElevator ) { + return traceEnt; + } + } + } + + return groundEnt; +} + +void idActor::GuidedProjectileIncoming( idGuidedProjectile *projectile ) +{ + if ( IsInVehicle() ) + { + if ( vehicleController.GetVehicle() ) + { + vehicleController.GetVehicle()->GuidedProjectileIncoming( projectile ); + } + } +} +// RAVEN END diff --git a/source/mpgame/Actor.h b/source/mpgame/Actor.h new file mode 100644 index 0000000..3476c35 --- /dev/null +++ b/source/mpgame/Actor.h @@ -0,0 +1,426 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ACTOR_H__ +#define __GAME_ACTOR_H__ + +/* +=============================================================================== + + idActor + +=============================================================================== +*/ + +extern const idEventDef AI_EnableEyeFocus; +extern const idEventDef AI_DisableEyeFocus; +extern const idEventDef EV_Footstep; +extern const idEventDef EV_FootstepLeft; +extern const idEventDef EV_FootstepRight; +extern const idEventDef EV_EnableWalkIK; +extern const idEventDef EV_DisableWalkIK; +extern const idEventDef EV_EnableLegIK; +extern const idEventDef EV_DisableLegIK; +extern const idEventDef AI_SetAnimPrefix; +extern const idEventDef AI_PlayAnim; +extern const idEventDef AI_PlayCycle; +extern const idEventDef AI_AnimDone; +extern const idEventDef AI_SetBlendFrames; +extern const idEventDef AI_GetBlendFrames; +extern const idEventDef AI_ScriptedMove; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; + +// RAVEN BEGIN +// bdube: added flashlight +extern const idEventDef AI_Flashlight; +extern const idEventDef AI_EnterVehicle; +extern const idEventDef AI_ExitVehicle; +// nmckenzie: +extern const idEventDef AI_OverrideAnim; +extern const idEventDef AI_IdleAnim; +extern const idEventDef AI_SetState; +// jshepard: adjust animation speed +extern const idEventDef AI_SetAnimRate; +//MCG: damage over time +extern const idEventDef EV_DamageOverTime; +extern const idEventDef EV_DamageOverTimeEffect; +//MCG: script-callable joint crawl effect +extern const idEventDef EV_JointCrawlEffect; + +// abahr: +extern const idEventDef AI_LookAt; +extern const idEventDef AI_FaceEnemy; +extern const idEventDef AI_FaceEntity; +extern const idEventDef AI_JumpDown; +extern const idEventDef AI_SetLeader; +// RAVEN END + +class idAnimState { +public: + + bool idleAnim; + int animBlendFrames; + int lastAnimBlendFrames; // allows override anims to blend based on the last transition time + +public: + idAnimState(); + ~idAnimState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Init( idEntity *owner, idAnimator *_animator, int animchannel ); + void Shutdown( void ); + void SetState ( const char *name, int blendFrames, int flags = 0 ); + void PostState ( const char* name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnim( int frames ); + void PlayAnim( int anim ); + void CycleAnim( int anim ); + void BecomeIdle( void ); + bool UpdateState( void ); + bool Disabled( void ) const; + void Enable( int blendFrames ); + void Disable( void ); + bool AnimDone( int blendFrames ) const; + bool IsIdle( void ) const; + animFlags_t GetAnimFlags( void ) const; + + rvStateThread& GetStateThread ( void ); + + idAnimator * GetAnimator( void ) const {return animator;}; +private: +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it + idEntity * self; +// RAVEN END + idAnimator * animator; + int channel; + bool disabled; + + rvStateThread stateThread; +}; + +ID_INLINE rvStateThread& idAnimState::GetStateThread ( void ) { + return stateThread; +} + +class idAttachInfo { +public: + idEntityPtr ent; + int channel; +}; + +class idActor : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idActor ); + + int team; + idLinkList teamNode; + int rank; // monsters don't fight back if the attacker's rank is higher + idMat3 viewAxis; // view axis of the actor + + idLinkList enemyNode; // node linked into an entity's enemy list for quick lookups of who is attacking him + idLinkList enemyList; // list of characters that have targeted the player as their enemy + +public: + idActor( void ); + virtual ~idActor( void ); + + void Spawn( void ); + virtual void Restart( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual int GetDefaultSurfaceType( void ) const; + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual bool LoadAF( const char* keyname = NULL, bool purgeAF = false ); + void SetupBody( void ); + + virtual void CheckBlink( void ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + // script state management + void ShutdownThreads ( void ); + void UpdateState ( void ); + + virtual void OnStateThreadClear ( const char *statename, int flags = 0 ); + void SetState ( const char *statename, int flags = 0 ); + void PostState ( const char* statename, int delay = 0, int flags = 0 ); + void InterruptState ( const char* statename, int delay = 0, int flags = 0 ); + + // vision testing + void SetEyeHeight( float height ); + void SetChestHeight ( float height ); + float EyeHeight( void ) const; + + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 GetChestPosition ( void ) const; + idEntity* GetGroundEntity ( void ) const; + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + void Present( void ); + + virtual void GetViewPos ( idVec3 &origin, idMat3 &axis ) const; + void SetFOV ( float fov, float fovClose ); + bool CheckFOV ( const idVec3 &pos, float ang = -1.0f ) const; + virtual bool HasFOV ( idEntity *ent ); + virtual bool CanSee ( const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFOV ) const; + + // damage + void SetupDamageGroups( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +// RAVEN BEGIN +// nmckenzie: a final hook in the middle of the damage function + virtual void AdjustHealthByDamage ( int inDamage ){health -= inDamage;} +// RAVEN END + + virtual int GetDamageForLocation( int damage, int location ); + const char * GetDamageGroup( int location ); + void ClearPain( void ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + // model/combat model/ragdoll + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + bool StartRagdoll( void ); + void StopRagdoll( void ); + virtual bool UpdateAnimationControllers( void ); + + // delta view angles to allow movers to rotate the view of the actor + const idAngles & GetDeltaViewAngles( void ) const; + void SetDeltaViewAngles( const idAngles &delta ); + + bool HasEnemies( void ) const; + idActor * ClosestEnemyToPoint( const idVec3 &pos, float maxRange=0.0f, bool returnFirst=false, bool checkPVS=false ); + idActor * EnemyWithMostHealth(); + + virtual bool OnLadder ( void ) const; + virtual void OnStateChange ( int channel ); + virtual void OnFriendlyFire ( idActor* attacker ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + + void Attach( idEntity *ent ); + idEntity* FindAttachment( const char* attachmentName ); + void HideAttachment( const char* attachmentName ); + void ShowAttachment( const char* attachmentName ); + idEntity* GetHead() { return head; } + + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual renderView_t * GetRenderView(); + + // Animation + int PlayAnim ( int channel, const char *name, int blendFrames ); + bool PlayCycle ( int channel, const char *name, int blendFrames ); + void IdleAnim ( int channel, const char *name, int blendFrames ); + void OverrideAnim ( int channel ); + bool HasAnim ( int channel, const char *name, bool forcePrefix = false ); + int GetAnim ( int channel, const char *name, bool forcePrefix = false ); + bool AnimDone ( int channel, int blendFrames ); + + // animation state control + void UpdateAnimState ( void ); + void SetAnimState ( int channel, const char *name, int blendFrames = 0, int flags = 0 ); + void PostAnimState ( int channel, const char *name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnimState ( int channel ); + bool InAnimState ( int channel, const char *name ); + + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +// RAVEN BEGIN +// bdube: added for vehicle + bool IsInVehicle ( void ) const; + rvVehicleController& GetVehicleController ( void ); + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); + + bool DebugFilter (const idCVar& test) const; +// RAVEN END + virtual bool IsCrouching ( void ) const {return false;}; + + virtual bool SkipImpulse( idEntity* ent, int id ); + + int lightningNextTime; + int lightningEffects; + +protected: + friend class idAnimState; + + float fovDot; // cos( fovDegrees ) + float fovCloseDot; // cos( fovDegreesClose ) + float fovCloseRange; // range within to use fovCloseDot + idVec3 eyeOffset; // offset of eye relative to physics origin + idVec3 chestOffset; // offset of chest relative to physics origin + idVec3 modelOffset; // offset of visual model relative to the physics origin + + idAngles deltaViewAngles; // delta angles relative to view input angles + + int pain_debounce_time; // next time the actor can show pain + int pain_delay; // time between playing pain sound + + idStrList damageGroups; // body damage groups + idList damageScale; // damage scale per damage gruop + bool inDamageEvent; // hacky-ass bool to prevent us from starting a new EV_DamageOverTime in our ::Damage + + bool use_combat_bbox; // whether to use the bounding box for combat collision + + // joint handles + jointHandle_t leftEyeJoint; + jointHandle_t rightEyeJoint; + jointHandle_t soundJoint; + jointHandle_t eyeOffsetJoint; + jointHandle_t chestOffsetJoint; + jointHandle_t neckJoint; + jointHandle_t headJoint; + + idIK_Walk walkIK; + + idStr animPrefix; + idStr painType; + idStr painAnim; + + // blinking + int blink_anim; + int blink_time; + int blink_min; + int blink_max; + + idAnimState headAnim; + idAnimState torsoAnim; + idAnimState legsAnim; + + rvStateThread stateThread; + + idEntityPtr head; // safe pointer to attached head + + bool disablePain; + bool allowEyeFocus; + bool finalBoss; + + int painTime; + + idList attachments; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void CheckDeathObjectives( void ); + +// RAVEN BEGIN +// bdube: vehicles + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); +// RAVEN END + + // removes attachments with "remove" set for when character dies + void RemoveAttachments( void ); + +// RAVEN BEGIN +// bdube: vehicles + rvVehicleController vehicleController; +// bdube: flashlights + renderLight_t flashlight; + int flashlightHandle; + jointHandle_t flashlightJoint; + idVec3 flashlightOffset; + +// bdube: death force + int deathPushTime; + idVec3 deathPushForce; + jointHandle_t deathPushJoint; + + void FlashlightUpdate ( bool forceOn = false ); + void InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale = 1.0f ); + void DeathPush ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +protected: + + virtual void FootStep ( void ); + virtual void SetupHead( const char* headDefName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + void SyncAnimChannels( int channel, int syncToChannel, int blendFrames ); + void FinishSetup( void ); + + void Event_EnableEyeFocus( void ); + void Event_DisableEyeFocus( void ); + void Event_EnableBlink( void ); + void Event_DisableBlink( void ); + void Event_Footstep( void ); + void Event_EnableWalkIK( void ); + void Event_DisableWalkIK( void ); + void Event_EnableLegIK( int num ); + void Event_DisableLegIK( int num ); + void Event_SetAnimPrefix( const char *name ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_PreventPain( float duration ); + void Event_DisablePain( void ); + void Event_EnablePain( void ); + void Event_StopAnim( int channel, int frames ); + void Event_PlayAnim( int channel, const char *name ); + void Event_PlayCycle( int channel, const char *name ); + void Event_IdleAnim( int channel, const char *name ); + void Event_SetSyncedAnimWeight( int channel, int anim, float weight ); + void Event_OverrideAnim( int channel ); + void Event_EnableAnim( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_HasEnemies( void ); + void Event_NextEnemy( idEntity *ent ); + void Event_ClosestEnemyToPoint( const idVec3 &pos ); + void Event_StopSound( int channel, int netsync ); + void Event_GetHead( void ); + + void Event_Teleport ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Flashlight ( bool enable ); + void Event_EnterVehicle ( idEntity* vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_PreExitVehicle( bool force ); + + void Event_SetAnimRate ( float multiplier ); + void Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, const char *damageDefName, const float damageScale, int location ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + void Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ); + + CLASS_STATES_PROTOTYPE ( idActor ); + +protected: + + // Wait states + stateResult_t State_Wait_LegsAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_TorsoAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_Frame ( const stateParms_t& parms ); + + void DisableAnimState ( int channel ); + void EnableAnimState ( int channel ); + idAnimState& GetAnimState ( int channel ); +}; + +ID_INLINE bool idActor::IsInVehicle( void ) const { + return vehicleController.IsDriving(); +} + +ID_INLINE rvVehicleController& idActor::GetVehicleController( void ) { + return vehicleController; +} + +#endif /* !__GAME_ACTOR_H__ */ + +// RAVEN END diff --git a/source/mpgame/BrittleFracture.cpp b/source/mpgame/BrittleFracture.cpp new file mode 100644 index 0000000..d277e8e --- /dev/null +++ b/source/mpgame/BrittleFracture.cpp @@ -0,0 +1,1355 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes +#ifdef _MD5R_SUPPORT +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" +#endif +// RAVEN END + +CLASS_DECLARATION( idEntity, idBrittleFracture ) + EVENT( EV_Activate, idBrittleFracture::Event_Activate ) + EVENT( EV_Touch, idBrittleFracture::Event_Touch ) +END_CLASS + +const int SHARD_ALIVE_TIME = 5000; +const int SHARD_FADE_START = 2000; + +static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; + +/* +================ +idBrittleFracture::idBrittleFracture +================ +*/ +idBrittleFracture::idBrittleFracture( void ) { + material = NULL; + decalMaterial = NULL; + decalSize = 0.0f; + maxShardArea = 0.0f; + maxShatterRadius = 0.0f; + minShatterRadius = 0.0f; + linearVelocityScale = 0.0f; + angularVelocityScale = 0.0f; + shardMass = 0.0f; + density = 0.0f; + friction = 0.0f; + bouncyness = 0.0f; + fxFracture.Clear(); + + bounds.Clear(); + disableFracture = false; + + lastRenderEntityUpdate = -1; + changed = false; + + fl.networkSync = true; +} + +/* +================ +idBrittleFracture::~idBrittleFracture +================ +*/ +idBrittleFracture::~idBrittleFracture( void ) { + int i; + + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->decals.DeleteContents( true ); + delete shards[i]; + } + + // make sure the render entity is freed before the model is freed + FreeModelDef(); + renderModelManager->FreeModel( renderEntity.hModel ); +} + +/* +================ +idBrittleFracture::Save +================ +*/ +void idBrittleFracture::Save( idSaveGame *savefile ) const { + int i, j; + + savefile->WriteInt( health ); + savefile->Write( &fl, sizeof( fl ) ); + + // setttings + savefile->WriteMaterial( material ); + savefile->WriteMaterial( decalMaterial ); + savefile->WriteFloat( decalSize ); + savefile->WriteFloat( maxShardArea ); + savefile->WriteFloat( maxShatterRadius ); + savefile->WriteFloat( minShatterRadius ); + savefile->WriteFloat( linearVelocityScale ); + savefile->WriteFloat( angularVelocityScale ); + savefile->WriteFloat( shardMass ); + savefile->WriteFloat( density ); + savefile->WriteFloat( friction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteString( fxFracture ); + + // state + savefile->WriteBounds( bounds ); + savefile->WriteBool( disableFracture ); + + savefile->WriteInt( lastRenderEntityUpdate ); + savefile->WriteBool( changed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( shards.Num() ); + for ( i = 0; i < shards.Num(); i++ ) { + savefile->WriteWinding( shards[i]->winding ); + + savefile->WriteInt( shards[i]->decals.Num() ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { + savefile->WriteWinding( *shards[i]->decals[j] ); + } + + savefile->WriteInt( shards[i]->neighbours.Num() ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index = shards.FindIndex(shards[i]->neighbours[j]); + assert(index != -1); + savefile->WriteInt( index ); + } + + savefile->WriteInt( shards[i]->edgeHasNeighbour.Num() ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->WriteBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->WriteInt( shards[i]->droppedTime ); + savefile->WriteInt( shards[i]->islandNum ); + savefile->WriteBool( shards[i]->atEdge ); + savefile->WriteStaticObject( shards[i]->physicsObj ); + } +} + +/* +================ +idBrittleFracture::Restore +================ +*/ +void idBrittleFracture::Restore( idRestoreGame *savefile ) { + int i, j , num; + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; + + savefile->ReadInt( health ); + savefile->Read( &fl, sizeof( fl ) ); + + // setttings + savefile->ReadMaterial( material ); + savefile->ReadMaterial( decalMaterial ); + savefile->ReadFloat( decalSize ); + savefile->ReadFloat( maxShardArea ); + savefile->ReadFloat( maxShatterRadius ); + savefile->ReadFloat( minShatterRadius ); + savefile->ReadFloat( linearVelocityScale ); + savefile->ReadFloat( angularVelocityScale ); + savefile->ReadFloat( shardMass ); + savefile->ReadFloat( density ); + savefile->ReadFloat( friction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadString( fxFracture ); + + // state + savefile->ReadBounds(bounds); + savefile->ReadBool( disableFracture ); + + savefile->ReadInt( lastRenderEntityUpdate ); + savefile->ReadBool( changed ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + shards.SetNum( num ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + for ( i = 0; i < num; i++ ) { + shards[i] = new shard_t; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + savefile->ReadWinding( shards[i]->winding ); + + savefile->ReadInt( j ); + shards[i]->decals.SetNum( j ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shards[i]->decals[j] = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + savefile->ReadWinding( *shards[i]->decals[j] ); + } + + savefile->ReadInt( j ); + shards[i]->neighbours.SetNum( j ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index; + savefile->ReadInt( index ); + assert(index != -1); + shards[i]->neighbours[j] = shards[index]; + } + + savefile->ReadInt( j ); + shards[i]->edgeHasNeighbour.SetNum( j ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->ReadBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->ReadInt( shards[i]->droppedTime ); + savefile->ReadInt( shards[i]->islandNum ); + savefile->ReadBool( shards[i]->atEdge ); + savefile->ReadStaticObject( shards[i]->physicsObj ); + if ( shards[i]->droppedTime < 0 ) { + shards[i]->clipModel = physicsObj.GetClipModel( i ); + } else { + shards[i]->clipModel = shards[i]->physicsObj.GetClipModel(); + } + } +} + +/* +================ +idBrittleFracture::Spawn +================ +*/ +void idBrittleFracture::Spawn( void ) { + + // get shard properties + decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) ); + decalSize = spawnArgs.GetFloat( "decalSize", "40" ); + maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ); + maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea ); + maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" ); + minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" ); + linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" ); + angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" ); + fxFracture = spawnArgs.GetString( "fx" ); + + // get rigid body properties + shardMass = spawnArgs.GetFloat( "shardMass", "20" ); + shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass ); + spawnArgs.GetFloat( "density", "0.1", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.4", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + disableFracture = spawnArgs.GetBool( "disableFracture", "0" ); + health = spawnArgs.GetInt( "health", "40" ); + fl.takedamage = true; + + // FIXME: set "bleed" so idProjectile calls AddDamageEffect + spawnArgs.SetBool( "bleed", 1 ); + + CreateFractures( renderEntity.hModel ); + + FindNeighbours(); + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; +} + +/* +================ +idBrittleFracture::AddShard +================ +*/ +void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shard_t *shard = new shard_t; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shard->clipModel = clipModel; + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + shards.Append( shard ); +} + +/* +================ +idBrittleFracture::RemoveShard +================ +*/ +void idBrittleFracture::RemoveShard( int index ) { + int i; + + delete shards[index]; + shards.RemoveIndex( index ); + physicsObj.RemoveIndex( index ); + + for ( i = index; i < shards.Num(); i++ ) { + shards[i]->clipModel->SetId( i ); + } +} + +/* +================ +idBrittleFracture::UpdateRenderEntity +================ +*/ +bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + int i, j, k, n, msec, numTris, numDecalTris; + float fade; + dword packedColor; + srfTriangles_t *tris, *decalTris; + modelSurface_t surface; + idDrawVert *v; + idPlane plane; + idMat3 tangents; + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + return false; + } + + // don't regenerate it if it is current + if ( lastRenderEntityUpdate == gameLocal.time || !changed ) { + return false; + } + + lastRenderEntityUpdate = gameLocal.time; + changed = false; + + numTris = 0; + numDecalTris = 0; + for ( i = 0; i < shards.Num(); i++ ) { + n = shards[i]->winding.GetNumPoints(); + if ( n > 2 ) { + numTris += n - 2; + } + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + n = shards[i]->decals[k]->GetNumPoints(); + if ( n > 2 ) { + numDecalTris += n - 2; + } + } + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName ); + + // allocate triangle surfaces for the fractures and decals + { + ConditionalAutoCrit crit; + + tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 ); + decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 ); + } + + for ( i = 0; i < shards.Num(); i++ ) { + const idVec3 &origin = shards[i]->clipModel->GetOrigin(); + const idMat3 &axis = shards[i]->clipModel->GetAxis(); + + fade = 1.0f; + if ( shards[i]->droppedTime >= 0 ) { + msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START; + if ( msec > 0 ) { + fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START ); + } + } + packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade, + renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade, + renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade, + fade ) ); + + const idWinding &winding = shards[i]->winding; + + winding.GetPlane( plane ); + tangents = ( plane.Normal() * axis ).ToMat3(); + + for ( j = 2; j < winding.GetNumPoints(); j++ ) { + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[0].ToVec3() * axis; + v->st[0] = winding[0].s; + v->st[1] = winding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j-1].ToVec3() * axis; + v->st[0] = winding[j-1].s; + v->st[1] = winding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j].ToVec3() * axis; + v->st[0] = winding[j].s; + v->st[1] = winding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + + if ( material->ShouldCreateBackSides() ) { + + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + } + } + + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + const idWinding &decalWinding = *shards[i]->decals[k]; + + for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) { + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[0].ToVec3() * axis; + v->st[0] = decalWinding[0].s; + v->st[1] = decalWinding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j-1].ToVec3() * axis; + v->st[0] = decalWinding[j-1].s; + v->st[1] = decalWinding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j].ToVec3() * axis; + v->st[0] = decalWinding[j].s; + v->st[1] = decalWinding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + + if ( decalMaterial->ShouldCreateBackSides() ) { + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + } + } + } + } + + tris->tangentsCalculated = true; + decalTris->tangentsCalculated = true; + + SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts ); + SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = material; + surface.id = 0; + surface.geometry = tris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = decalMaterial; + surface.id = 1; + surface.geometry = decalTris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + return true; +} + +/* +================ +idBrittleFracture::ModelCallback +================ +*/ +bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + const idBrittleFracture *ent; + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idBrittleFracture::Present +================ +*/ +void idBrittleFracture::Present() { + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + renderEntity.bounds = bounds; + renderEntity.origin.Zero(); + renderEntity.axis.Identity(); + + // force an update because the bounds/origin/axis may stay the same while the model changes + renderEntity.forceUpdate = true; + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + + changed = true; +} + +/* +================ +idBrittleFracture::Think +================ +*/ +void idBrittleFracture::Think( void ) { + int i, startTime, endTime, droppedTime; + shard_t *shard; + bool atRest = true, fading = false; + + // remove overdue shards + for ( i = 0; i < shards.Num(); i++ ) { + droppedTime = shards[i]->droppedTime; + if ( droppedTime != -1 ) { + if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) { + RemoveShard( i ); + i--; + } + fading = true; + } + } + + // remove the entity when nothing is visible + if ( !shards.Num() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( thinkFlags & TH_PHYSICS ) { + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + // run physics on shards + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime == -1 ) { + continue; + } + + shard->physicsObj.Evaluate( endTime - startTime, endTime ); + + if ( !shard->physicsObj.IsAtRest() ) { + atRest = false; + } + } + + if ( atRest ) { + BecomeInactive( TH_PHYSICS ); + } else { + BecomeActive( TH_PHYSICS ); + } + } + + if ( !atRest || bounds.IsCleared() ) { + bounds.Clear(); + for ( i = 0; i < shards.Num(); i++ ) { + bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() ); + } + } + + if ( fading ) { + BecomeActive( TH_UPDATEVISUALS | TH_THINK ); + } else { + BecomeInactive( TH_THINK ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idBrittleFracture::ApplyImpulse +================ +*/ +void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.ApplyImpulse( 0, point, impulse ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, impulse, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::AddForce +================ +*/ +void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.AddForce( 0, point, force ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, force, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::ProjectDecal +================ +*/ +void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { + int i, j, bits, clipBits; + float a, c, s; + idVec2 st[MAX_POINTS_ON_WINDING]; + idVec3 origin; + idMat3 axis, axistemp; + idPlane textureAxis[2]; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( dir[0] ); + msg.WriteFloat( dir[1] ); + msg.WriteFloat( dir[2] ); + ServerSendInstanceEvent( EVENT_PROJECT_DECAL, &msg, true, -1 ); + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; + } + } + + if ( time >= gameLocal.time ) { + // try to get the sound from the damage def + const idDeclEntityDef *damageDef = NULL; + const idSoundShader *sndShader = NULL; + if ( damageDefName ) { + damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) ); + } + } + + if ( sndShader ) { + StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); + } else { + StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + + axis[2] = -dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + textureAxis[0] = axis[0] * ( 1.0f / decalSize ); + textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; + + textureAxis[1] = axis[1] * ( 1.0f / decalSize ); + textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; + + for ( i = 0; i < shards.Num(); i++ ) { + idFixedWinding &winding = shards[i]->winding; + origin = shards[i]->clipModel->GetOrigin(); + axis = shards[i]->clipModel->GetAxis(); + float d0, d1; + + clipBits = -1; + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + idVec3 p = origin + winding[j].ToVec3() * axis; + + st[j].x = d0 = textureAxis[0].Distance( p ); + st[j].y = d1 = textureAxis[1].Distance( p ); + + bits = FLOATSIGNBITSET( d0 ); + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 2; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 1; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + clipBits &= bits; + } + + if ( clipBits ) { + continue; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idFixedWinding *decal = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shards[i]->decals.Append( decal ); + + decal->SetNumPoints( winding.GetNumPoints() ); + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + (*decal)[j].ToVec3() = winding[j].ToVec3(); + (*decal)[j].s = st[j].x; + (*decal)[j].t = st[j].y; + } + } + + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idBrittleFracture::DropShard +================ +*/ +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { + int i, j, clipModelId; + float dist, f; + idVec3 dir2, origin; + idMat3 axis; + shard_t *neighbour; + + // don't display decals on dropped shards + shard->decals.DeleteContents( true ); + + // remove neighbour pointers of neighbours pointing to this shard + for ( i = 0; i < shard->neighbours.Num(); i++ ) { + neighbour = shard->neighbours[i]; + for ( j = 0; j < neighbour->neighbours.Num(); j++ ) { + if ( neighbour->neighbours[j] == shard ) { + neighbour->neighbours.RemoveIndex( j ); + break; + } + } + } + + // remove neighbour pointers + shard->neighbours.Clear(); + + // remove the clip model from the static physics object + clipModelId = shard->clipModel->GetId(); + physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false ); + + origin = shard->clipModel->GetOrigin(); + axis = shard->clipModel->GetAxis(); + + // set the dropped time for fading + shard->droppedTime = time; + + dir2 = origin - point; + dist = dir2.Normalize(); +// RAVEN BEGIN +// jscott: changed to avoid negative sqrt call which propagated into badness + if( dist > maxShatterRadius ) { + f = 1.0f; + } else if( dist < minShatterRadius ) { + f = 0.0f; + } else { + f = idMath::Sqrt( dist - minShatterRadius ) * idMath::InvSqrt( maxShatterRadius - minShatterRadius ); + } +// RAVEN END + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, density ); + shard->physicsObj.SetMass( shardMass ); + shard->physicsObj.SetOrigin( origin ); + shard->physicsObj.SetAxis( axis ); + shard->physicsObj.SetBouncyness( bouncyness ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); + + shard->clipModel->SetId( clipModelId ); + + BecomeActive( TH_PHYSICS ); +} + +/* +================ +idBrittleFracture::Shatter +================ +*/ +void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i; + idVec3 dir; + shard_t *shard; + float m; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( impulse[0] ); + msg.WriteFloat( impulse[1] ); + msg.WriteFloat( impulse[2] ); + ServerSendInstanceEvent( EVENT_SHATTER, &msg, true, -1 ); + } + + if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) { + StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( !IsBroken() ) { + Break(); + } + +// RAVEN BEGIN +// bdube: raven effect system + PlayEffect ( "fx_shatter", point, GetPhysics()->GetAxis() ); +// RAVEN END + + dir = impulse; + m = dir.Normalize(); + + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime != -1 ) { + continue; + } + + if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) { + continue; + } + + DropShard( shard, point, dir, m, time ); + } + + DropFloatingIslands( point, impulse, time ); +} + +/* +================ +idBrittleFracture::DropFloatingIslands +================ +*/ +void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i, j, numIslands; + int queueStart, queueEnd; + shard_t *curShard, *nextShard, **queue; + bool touchesEdge; + idVec3 dir; + + dir = impulse; + dir.Normalize(); + + numIslands = 0; + queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->islandNum = 0; + } + + for ( i = 0; i < shards.Num(); i++ ) { + + if ( shards[i]->droppedTime != -1 ) { + continue; + } + + if ( shards[i]->islandNum ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = shards[i]; + shards[i]->islandNum = numIslands+1; + touchesEdge = false; + + if ( shards[i]->atEdge ) { + touchesEdge = true; + } + + for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) { + + for ( j = 0; j < curShard->neighbours.Num(); j++ ) { + + nextShard = curShard->neighbours[j]; + + if ( nextShard->droppedTime != -1 ) { + continue; + } + + if ( nextShard->islandNum ) { + continue; + } + + queue[queueEnd++] = nextShard; + nextShard->islandNum = numIslands+1; + + if ( nextShard->atEdge ) { + touchesEdge = true; + } + } + } + numIslands++; + + // if the island is not connected to the world at any edges + if ( !touchesEdge ) { + for ( j = 0; j < queueEnd; j++ ) { + DropShard( queue[j], point, dir, 0.0f, time ); + } + } + } +} + +/* +================ +idBrittleFracture::Break +================ +*/ +void idBrittleFracture::Break( void ) { + fl.takedamage = false; + physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); +} + +/* +================ +idBrittleFracture::IsBroken +================ +*/ +bool idBrittleFracture::IsBroken( void ) const { + return ( fl.takedamage == false ); +} + +/* +================ +idBrittleFracture::Killed +================ +*/ +void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !disableFracture ) { + ActivateTargets( this ); + Break(); + } +} + +/* +================ +idBrittleFracture::AddDamageEffect +================ +*/ +void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !disableFracture ) { + ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName ); + } +} + +/* +================ +idBrittleFracture::Fracture_r +================ +*/ +void idBrittleFracture::Fracture_r( idFixedWinding &w ) { + int i, j, bestPlane; + float a, c, s, dist, bestDist; + idVec3 origin; + idPlane windingPlane, splitPlanes[2]; + idMat3 axis, axistemp; + idFixedWinding back; + idTraceModel trm; + idClipModel *clipModel; + + while( 1 ) { + origin = w.GetCenter(); + w.GetPlane( windingPlane ); + + if ( w.GetArea() < maxShardArea ) { + break; + } + + // randomly create a split plane + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + axis[2] = windingPlane.Normal(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + // get the best split plane + bestDist = 0.0f; + bestPlane = 0; + for ( i = 0; i < 2; i++ ) { + splitPlanes[i].SetNormal( axis[i] ); + splitPlanes[i].FitThroughPoint( origin ); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + dist = splitPlanes[i].Distance( w[j].ToVec3() ); + if ( dist > bestDist ) { + bestDist = dist; + bestPlane = i; + } + } + } + + // split the winding + if ( !w.Split( &back, splitPlanes[bestPlane] ) ) { + break; + } + + // recursively create shards for the back winding + Fracture_r( back ); + } + + // translate the winding to it's center + origin = w.GetCenter(); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + w[j].ToVec3() -= origin; + } + w.RemoveEqualPoints(); + + trm.SetupPolygon( w ); + trm.Shrink( CM_CLIP_EPSILON ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() ); + + AddShard( clipModel, w ); +} + +/* +================ +idBrittleFracture::CreateFractures +================ +*/ +void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) { + int i, j, k; + const modelSurface_t *surf; + const idDrawVert *v; + idFixedWinding w; + + if ( !renderModel ) { + return; + } + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 ); + + for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) { + surf = renderModel->Surface( i ); + material = surf->shader; + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes + srfTriangles_t * tri = surf->geometry; +#ifdef _MD5R_SUPPORT + if ( tri->primBatchMesh != NULL ) { + + srfTriangles_t *tempTri = (srfTriangles_t *) _alloca16( sizeof(srfTriangles_t) ); + memset( tempTri, 0, sizeof( srfTriangles_t ) ); + + assert( tri->silTraceVerts != NULL ); + + tempTri->numVerts = tri->primBatchMesh->GetNumDrawVertices(); + tempTri->numIndexes = tri->primBatchMesh->GetNumDrawIndices(); + + tempTri->indexes = (glIndex_t *) _alloca16( tempTri->numIndexes * sizeof( tempTri->indexes[0] ) ); + tempTri->verts = (idDrawVert *) _alloca16( tempTri->numVerts * sizeof( idDrawVert ) ); + + renderSystem->CopyPrimBatchTriangles(tempTri->verts, tempTri->indexes, tri->primBatchMesh, tri->silTraceVerts ); + + tri = tempTri; + } +#endif +// RAVEN END + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + for ( k = 0; k < 3; k++ ) { +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes (referred to surf->geometry as tri) + v = &tri->verts[ tri->indexes[ j + 2 - k ] ]; +// RAVEN END + w.AddPoint( v->xyz ); + w[k].s = v->st[0]; + w[k].t = v->st[1]; + } + Fracture_r( w ); + } + } + + physicsObj.SetContents( material->GetContentFlags() ); + SetPhysics( &physicsObj ); +} + +/* +================ +idBrittleFracture::FindNeighbours +================ +*/ +void idBrittleFracture::FindNeighbours( void ) { + int i, j, k, l; + idVec3 p1, p2, dir; + idMat3 axis; + idPlane plane[4]; + + for ( i = 0; i < shards.Num(); i++ ) { + + shard_t *shard1 = shards[i]; + const idWinding &w1 = shard1->winding; + const idVec3 &origin1 = shard1->clipModel->GetOrigin(); + const idMat3 &axis1 = shard1->clipModel->GetAxis(); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + + p1 = origin1 + w1[k].ToVec3() * axis1; + p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1; + dir = p2 - p1; + dir.Normalize(); + axis = dir.ToMat3(); + + plane[0].SetNormal( dir ); + plane[0].FitThroughPoint( p1 ); + plane[1].SetNormal( -dir ); + plane[1].FitThroughPoint( p2 ); + plane[2].SetNormal( axis[1] ); + plane[2].FitThroughPoint( p1 ); + plane[3].SetNormal( axis[2] ); + plane[3].FitThroughPoint( p1 ); + + for ( j = 0; j < shards.Num(); j++ ) { + + if ( i == j ) { + continue; + } + + shard_t *shard2 = shards[j]; + + for ( l = 0; l < shard1->neighbours.Num(); l++ ) { + if ( shard1->neighbours[l] == shard2 ) { + break; + } + } + if ( l < shard1->neighbours.Num() ) { + continue; + } + + const idWinding &w2 = shard2->winding; + const idVec3 &origin2 = shard2->clipModel->GetOrigin(); + const idMat3 &axis2 = shard2->clipModel->GetAxis(); + + for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) { + p1 = origin2 + w2[l].ToVec3() * axis2; + p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2; + if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) { + if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) { + if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) { + shard1->neighbours.Append( shard2 ); + shard1->edgeHasNeighbour[k] = true; + shard2->neighbours.Append( shard1 ); + shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true; + break; + } + } + } + } + } + } + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + if ( !shard1->edgeHasNeighbour[k] ) { + break; + } + } + if ( k < w1.GetNumPoints() ) { + shard1->atEdge = true; + } else { + shard1->atEdge = false; + } + } +} + +/* +================ +idBrittleFracture::Event_Activate +================ +*/ +void idBrittleFracture::Event_Activate( idEntity *activator ) { + disableFracture = false; + if ( health <= 0 ) { + Break(); + } +} + +/* +================ +idBrittleFracture::Event_Touch +================ +*/ +void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 point, impulse; + + if ( !IsBroken() ) { + return; + } + + if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) { + return; + } + + point = shards[trace->c.id]->clipModel->GetOrigin(); + impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass(); + + Shatter( point, impulse, gameLocal.time ); +} + +/* +================ +idBrittleFracture::ClientPredictionThink +================ +*/ +void idBrittleFracture::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientReceiveEvent +================ +*/ +bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 point, dir; + + switch( event ) { + case EVENT_PROJECT_DECAL: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + ProjectDecal( point, dir, time, NULL ); + return true; + } + case EVENT_SHATTER: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + Shatter( point, dir, time ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} diff --git a/source/mpgame/BrittleFracture.h b/source/mpgame/BrittleFracture.h new file mode 100644 index 0000000..aa90aa2 --- /dev/null +++ b/source/mpgame/BrittleFracture.h @@ -0,0 +1,103 @@ + +#ifndef __GAME_BRITTLEFRACTURE_H__ +#define __GAME_BRITTLEFRACTURE_H__ + + +/* +=============================================================================== + +B-rep Brittle Fracture - Static entity using the boundary representation +of the render model which can fracture. + +=============================================================================== +*/ + +typedef struct shard_s { + idClipModel * clipModel; + idFixedWinding winding; + idList decals; + idList edgeHasNeighbour; + idList neighbours; + idPhysics_RigidBody physicsObj; + int droppedTime; + bool atEdge; + int islandNum; +} shard_t; + + +class idBrittleFracture : public idEntity { + +public: + CLASS_PROTOTYPE( idBrittleFracture ); + + idBrittleFracture( void ); + virtual ~idBrittleFracture( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Present( void ); + virtual void Think( void ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ); + bool IsBroken( void ) const; + + enum { + EVENT_PROJECT_DECAL = idEntity::EVENT_MAXEVENTS, + EVENT_SHATTER, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + // setttings + const idMaterial * material; + const idMaterial * decalMaterial; + float decalSize; + float maxShardArea; + float maxShatterRadius; + float minShatterRadius; + float linearVelocityScale; + float angularVelocityScale; + float shardMass; + float density; + float friction; + float bouncyness; + idStr fxFracture; + + // state + idPhysics_StaticMulti physicsObj; + idList shards; + idBounds bounds; + bool disableFracture; + + // for rendering + mutable int lastRenderEntityUpdate; + mutable bool changed; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void AddShard( idClipModel *clipModel, idFixedWinding &w ); + void RemoveShard( int index ); + void DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ); + void Shatter( const idVec3 &point, const idVec3 &impulse, const int time ); + void DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ); + void Break( void ); + void Fracture_r( idFixedWinding &w ); + void CreateFractures( const idRenderModel *renderModel ); + void FindNeighbours( void ); + + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_BRITTLEFRACTURE_H__ */ diff --git a/source/mpgame/Camera.cpp b/source/mpgame/Camera.cpp new file mode 100644 index 0000000..eac2a43 --- /dev/null +++ b/source/mpgame/Camera.cpp @@ -0,0 +1,2208 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idCamera + + Base class for cameras + +=============================================================================== +*/ + +ABSTRACT_DECLARATION( idEntity, idCamera ) +END_CLASS + +/* +===================== +idCamera::Spawn +===================== +*/ +void idCamera::Spawn( void ) { +} + +/* +===================== +idCamera::GetRenderView +===================== +*/ +renderView_t *idCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + GetViewParms( rv ); + return rv; +} + +/*********************************************************************** + + idCameraView + +***********************************************************************/ +const idEventDef EV_Camera_SetAttachments( "", NULL ); + +// RAVEN BEGIN +// bdube: added events +const idEventDef EV_SetFOV ( "setFOV", "f" ); +const idEventDef EV_GetFOV ( "getFOV", NULL, 'f' ); +const idEventDef EV_BlendFOV ( "blendFOV", "fff"); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraView ) + EVENT( EV_Activate, idCameraView::Event_Activate ) + EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) + +// RAVEN BEGIN +// bdube: added events + EVENT( EV_SetFOV, idCameraView::Event_SetFOV ) + EVENT( EV_BlendFOV, idCameraView::Event_BlendFOV ) + EVENT( EV_GetFOV, idCameraView::Event_GetFOV ) +// RAVEN END +END_CLASS + +/* +=============== +idCameraView::idCameraView +================ +*/ +idCameraView::idCameraView() { +// RAVEN BEGIN +// bdube: interpolate fov +// scork: get it from the cvar, don't assume 90 + fov.Init ( gameLocal.time, 0, g_fov.GetFloat(), g_fov.GetFloat() ); +// RAVEN END + attachedTo = NULL; + attachedView = NULL; +} + +/* +=============== +idCameraView::Save +================ +*/ +void idCameraView::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// bdube: fov interpolated now + savefile->WriteInt( fov.GetDuration() ); + savefile->WriteInt( fov.GetStartTime() ); + savefile->WriteFloat( fov.GetStartValue() ); + savefile->WriteFloat( fov.GetEndValue() ); +// RAVEN END + savefile->WriteObject( attachedTo ); + savefile->WriteObject( attachedView ); +} + +/* +=============== +idCameraView::Restore +================ +*/ +void idCameraView::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// bdube: fov interpolated now + int set; + float setf; + savefile->ReadInt( set ); + fov.SetDuration( set ); + savefile->ReadInt( set ); + fov.SetStartTime( set ); + savefile->ReadFloat( setf ); + fov.SetStartValue( setf ); + savefile->ReadFloat( setf ); + fov.SetEndValue( setf ); +// RAVEN END + savefile->ReadObject( reinterpret_cast( attachedTo ) ); + savefile->ReadObject( reinterpret_cast( attachedView ) ); +} + +/* +=============== +idCameraView::Event_SetAttachments +================ +*/ +void idCameraView::Event_SetAttachments( ) { + SetAttachment( &attachedTo, "attachedTo" ); + SetAttachment( &attachedView, "attachedView" ); +} + +/* +=============== +idCameraView::Event_Activate +================ +*/ +void idCameraView::Event_Activate( idEntity *activator ) { + if (spawnArgs.GetBool("trigger")) { + if (gameLocal.GetCamera() != this) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + gameLocal.SetCamera(this); + } else { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + } + } +} + +/* +===================== +idCameraView::Stop +===================== +*/ +void idCameraView::Stop( void ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + ActivateTargets( gameLocal.GetLocalPlayer() ); +} + +// RAVEN BEGIN +// bdube: added events +/* +===================== +idCameraView::Event_SetFOV +===================== +*/ +void idCameraView::Event_SetFOV ( float newfov ) +{ + fov.Init ( gameLocal.time, 0, newfov, newfov ); +} + +/* +===================== +idCameraView::Event_BlendFOV +===================== +*/ +void idCameraView::Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ) +{ + fov.Init ( gameLocal.time, SEC2MS(blendTime), beginFOV, endFOV ); +} + +/* +===================== +idCameraView::Event_GetFOV +===================== +*/ +void idCameraView::Event_GetFOV() +{ + idThread::ReturnFloat(fov.GetCurrentValue(gameLocal.time)); +} +// RAVEN END + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::SetAttachment( idEntity **e, const char *p ) { + const char *cam = spawnArgs.GetString( p ); + if ( strlen ( cam ) ) { + *e = gameLocal.FindEntity( cam ); + } +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::Spawn( void ) { + // if no target specified use ourself + const char *cam = spawnArgs.GetString("cameraTarget"); + if ( strlen ( cam ) == 0) { + spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); + } +// RAVEN BEGIN +// bdube: interpolate fov +// scork: ... but default from the cvar, not hardwired 90 + fov.Init ( gameLocal.time, 0, spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())), spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())) ); +// RAVEN END + + PostEventMS( &EV_Camera_SetAttachments, 0 ); + + UpdateChangeableSpawnArgs(NULL); +} + +/* +===================== +idCamera::RenderView +===================== +*/ +void idCameraView::GetViewParms( renderView_t *view ) { + assert( view ); + + if (view == NULL) { + return; + } + + idVec3 dir; + idEntity *ent; + + if ( attachedTo ) { + ent = attachedTo; + } else { + ent = this; + } + + view->vieworg = ent->GetPhysics()->GetOrigin(); + if ( attachedView ) { + dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; + dir.Normalize(); + view->viewaxis = dir.ToMat3(); + } else { + view->viewaxis = ent->GetPhysics()->GetAxis(); + } + +// RAVEN BEGIN +// bdube: interpolate fov + gameLocal.CalcFov( fov.GetCurrentValue( gameLocal.time ), view->fov_x, view->fov_y ); +// RAVEN END +} + + + + + + + + + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/*********************************************************************** + + rvCameraAnimation + +***********************************************************************/ +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( void ) { + frameRate = 0; +} + +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ) { + cameraCuts = anim->cameraCuts; + camera = anim->camera; + frameLookup = anim->frameLookup; + frameCommands = anim->frameCommands; + frameRate = anim->frameRate; + name = anim->name; + realname = anim->realname; +} + +/* +===================== +rvCameraAnimation::~rvCameraAnimation +===================== +*/ +rvCameraAnimation::~rvCameraAnimation( void ) { +} + +/* +===================== +rvCameraAnimation::Name +===================== +*/ +const char *rvCameraAnimation::Name( void ) const { + return name; +} + +/* +===================== +rvCameraAnimation::FullName +===================== +*/ +const char *rvCameraAnimation::FullName( void ) const { + return realname; +} + +/* +===================== +rvCameraAnimation::NumFrames +===================== +*/ +int rvCameraAnimation::NumFrames( void ) const { + return camera.Num(); +} + +/* +===================== +rvCameraAnimation::NumCuts +===================== +*/ +int rvCameraAnimation::NumCuts( void ) const { + return cameraCuts.Num(); +} + +void rvCameraAnimation::SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ) { + int version; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + int numFrames; + int numCuts; + int i; + + filename.SetFileExtension( MD5_CAMERA_EXT ); + if ( !parser.LoadFile( filename ) ) { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate <= 0 ) { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) { + cameraCuts[ i ] = parser.ParseInt(); + if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); + camera[ i ].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); + +#if 0 + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + idDebugGraph gGraph; + idDebugGraph tGraph; + idDebugGraph qGraph; + idDebugGraph dtGraph; + idDebugGraph dqGraph; + gGraph.SetNumSamples( numFrames ); + tGraph.SetNumSamples( numFrames ); + qGraph.SetNumSamples( numFrames ); + dtGraph.SetNumSamples( numFrames ); + dqGraph.SetNumSamples( numFrames ); + + gameLocal.Printf( "\n\ndelta vec:\n" ); + float diff_t, last_t, t; + float diff_q, last_q, q; + diff_t = last_t = 0.0f; + diff_q = last_q = 0.0f; + for( i = 1; i < numFrames; i++ ) { + t = ( camera[ i ].t - camera[ i - 1 ].t ).Length(); + q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length(); + diff_t = t - last_t; + diff_q = q - last_q; + gGraph.AddValue( ( i % 10 ) == 0 ); + tGraph.AddValue( t ); + qGraph.AddValue( q ); + dtGraph.AddValue( diff_t ); + dqGraph.AddValue( diff_q ); + + gameLocal.Printf( "%d: %.8f : %.8f, %.8f : %.8f\n", i, t, diff_t, q, diff_q ); + last_t = t; + last_q = q; + } + + gGraph.Draw( colorBlue, 300.0f ); + tGraph.Draw( colorOrange, 60.0f ); + dtGraph.Draw( colorYellow, 6000.0f ); + qGraph.Draw( colorGreen, 60.0f ); + dqGraph.Draw( colorCyan, 6000.0f ); +#endif +} + +/* +===================== +rvCameraAnimation::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *rvCameraAnimation::AddFrameCommand( const idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { +/* if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token );*/ + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( camera.Num() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > camera.Num() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, name.c_str(), camera.Num() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +/* +===================== +rvCameraAnimation::CallFrameCommandSound +===================== +*/ +void rvCameraAnimation::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} + +/* +===================== +rvCameraAnimation::CallFrameCommands +===================== +*/ +void rvCameraAnimation::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + if ( !frameLookup.Num() ) { + return; + } + + numframes = NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "Cameraframecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect ( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect ( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect ( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_origin, mat3_identity ); + } else { + cent = gameLocal.PlayEffect ( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent && command.joint2 && ent->IsType ( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + cent->SetEndOrigin ( ent->GetAnimator()->GetJointHandle ( *command.joint2 ) ); + } +// RAVEN END + break; + } + case FC_SKIN: +// ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { + break; + } + case FC_FOOTSTEP : { +// ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { +// ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { +// ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { +// ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { +// ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { +// ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { +// ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: +// ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: +// ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: +// ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: +// ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: +// ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: +// ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: +// ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_ACT_ATTACH_SHOW: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_AI_ATTACK_MELEE: +// ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + + + + + + + + + +/*********************************************************************** + + idDeclCameraDef + +***********************************************************************/ + +/* +===================== +idDeclCameraDef::idDeclCameraDef +===================== +*/ +idDeclCameraDef::idDeclCameraDef() { +} + +/* +===================== +idDeclCameraDef::~idDeclCameraDef +===================== +*/ +idDeclCameraDef::~idDeclCameraDef() { + FreeData(); +} + +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +/* +===================== +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +===================== +*/ +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::Write( SerialOutputStream &stream ) const +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::AddReferences() const +{ + // not supported yet + assert(false); +} +#endif +// RAVEN END +/* +================= +idDeclCameraDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclCameraDef::Size( void ) const { + + size_t size; + + size = sizeof( idDeclCameraDef ); + size += anims.Allocated(); + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclCameraDef::CopyDecl +===================== +*/ +void idDeclCameraDef::CopyDecl( const idDeclCameraDef *decl ) { + int i; + + FreeData(); + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new rvCameraAnimation( this, decl->anims[ i ] ); + } +} + +/* +===================== +idDeclCameraDef::FreeData +===================== +*/ +void idDeclCameraDef::FreeData( void ) { + anims.DeleteContents( true ); +} + +/* +================ +idDeclCameraDef::DefaultDefinition +================ +*/ +const char *idDeclCameraDef::DefaultDefinition( void ) const { + return "{ }"; +} + + + +/* +===================== +idDeclCameraDef::Touch +===================== +*/ +void idDeclCameraDef::Touch( void ) const { +} + +/* +===================== +idDeclCameraDef::ParseAnim +===================== +*/ +bool idDeclCameraDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + rvCameraAnimation *anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + + numAnims = 0; + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new rvCameraAnimation(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, token ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + return true; +} + +/* +================ +idDeclCameraDef::Parse +================ +*/ +bool idDeclCameraDef::Parse( const char *text, const int textLength, bool noCaching ) { + idStr filename; + idStr extension; + idLexer src; + idToken token; + idToken token2; + int numDefaultAnims; + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + else if ( token == "anim" ) { + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclCameraDef::Validate +===================== +*/ +bool idDeclCameraDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclCameraDef *pSelf = (idDeclCameraDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclCameraDef::HasAnim +===================== +*/ +bool idDeclCameraDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclCameraDef::NumAnims +===================== +*/ +int idDeclCameraDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclCameraDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclCameraDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclCameraDef::GetAnim +===================== +*/ +int idDeclCameraDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + + + + + + + + + + +/* +=============================================================================== + + idCameraAnim + +=============================================================================== +*/ +const idEventDef EV_Camera_Start( "start", NULL ); +const idEventDef EV_Camera_Stop( "stop", NULL ); + +// RAVEN BEGIN +// mekberg: wait support +const idEventDef EV_Camera_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraAnim ) + EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) + EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) + EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) + EVENT( EV_Activate, idCameraAnim::Event_Activate ) + + // RAVEN BEGIN + // mekberg: wait support + EVENT( EV_IsActive, idCameraAnim::Event_IsActive ) + // RAVEN END +END_CLASS + + +/* +===================== +idCameraAnim::idCameraAnim +===================== +*/ +idCameraAnim::idCameraAnim() { + threadNum = 0; + offset.Zero(); + cycle = 1; + starttime = 0; + activator = NULL; + lastFrame = -1; +} + +/* +===================== +idCameraAnim::~idCameraAnim +===================== +*/ +idCameraAnim::~idCameraAnim() { + if ( gameLocal.GetCamera() == this ) { + gameLocal.SetCamera( NULL ); + } +} + +/* +=============== +idCameraAnim::Save +================ +*/ +void idCameraAnim::Save( idSaveGame *savefile ) const { + savefile->WriteInt( threadNum ); + savefile->WriteVec3( offset ); + savefile->WriteInt( starttime ); + savefile->WriteInt( cycle ); + savefile->WriteInt( lastFrame ); // cnicholson: Added unsaved var + + activator.Save( savefile ); +} + +/* +=============== +idCameraAnim::Restore +================ +*/ +void idCameraAnim::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + savefile->ReadVec3( offset ); + savefile->ReadInt( starttime ); + savefile->ReadInt( cycle ); + savefile->ReadInt( lastFrame ); // cnicholson: Added unread var + + activator.Restore( savefile ); + + LoadAnim(); +} + +/* +===================== +idCameraAnim::Spawn +===================== +*/ +void idCameraAnim::Spawn( void ) { + if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { + offset = GetPhysics()->GetOrigin() - offset; + } else { + offset.Zero(); + } + + // always think during cinematics + cinematic = true; + + LoadAnim(); + +// RAVEN BEGIN +#ifndef _CONSOLE + // touch the cinematic streaming command file during build + if ( cvarSystem->GetCVarBool("com_makingBuild") && cvarSystem->GetCVarBool("com_Bundler") ) + { + idFile *file; + idStr filename = "cinematics/"; + filename += gameLocal.mapFileNameStripped; + filename += "_"; + filename += name; + filename += ".cincmd"; + file = fileSystem->OpenFileRead( filename ); + fileSystem->CloseFile( file ); + } +#endif +// RAVEN END +} + +/* +================ +idCameraAnim::Load +================ +*/ +void idCameraAnim::LoadAnim( void ) { + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + idStr filename; + const char *key; + + key = spawnArgs.GetString( "camera" ); + const idDecl *mapDecl = declManager->FindType( DECL_CAMERADEF, key, false ); + cameraDef = static_cast( mapDecl ); + + key = spawnArgs.GetString( "anim" ); + if ( !key ) { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + +} + +/* +=============== +idCameraAnim::Start +================ +*/ +void idCameraAnim::Start( void ) { + cycle = spawnArgs.GetInt( "cycle" ); + if ( !cycle ) { + cycle = 1; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + lastFrame = -1; + starttime = gameLocal.time; + gameLocal.SetCamera( this ); + BecomeActive( TH_THINK ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_BEGIN, cameraDef->GetAnim(1)->GetFrameRate(), GetName() ); +#endif +// RAVEN END + + // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame +// RAVEN BEGIN +// mekberg: make sure render view is valid. + if ( gameLocal.GetLocalPlayer( )->GetRenderView( ) && gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) { +// RAVEN END + gameLocal.GetLocalPlayer()->CalculateRenderView(); + } +} + +/* +===================== +idCameraAnim::Stop +===================== +*/ +void idCameraAnim::Stop( void ) { + if ( gameLocal.GetCamera() == this ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + + BecomeInactive( TH_THINK ); + gameLocal.SetCamera( NULL ); + if ( threadNum ) { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; + } + ActivateTargets( activator.GetEntity() ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_END, cameraDef->GetAnim(1)->GetFrameRate() ); +#endif +// RAVEN END + } +} + +/* +===================== +idCameraAnim::Think +===================== +*/ +void idCameraAnim::Think( void ) { + int frame; + int frameTime; + + if ( thinkFlags & TH_THINK ) { + // check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics). +// RAVEN BEGIN +// abahr: removed '!' + if ( gameLocal.skipCinematic ) { +// RAVEN END + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() < 2 ) { + // 1 frame anims never end + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + } + + if ( frame > cameraDef->GetAnim(1)->NumFrames() + cameraDef->GetAnim(1)->NumCuts() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + lastFrame = -1; + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + } else { + Stop(); + } + } + } +} + +/* +===================== +idCameraAnim::GetViewParms +===================== +*/ +void idCameraAnim::GetViewParms( renderView_t *view ) { + int realFrame; + int frame; + int frameTime; + float lerp; + float invlerp; + const cameraFrame_t *camFrame; + int i; + int cut; + idQuat q1, q2, q3; + + assert( view ); + if ( !view ) { + return; + } +//RAVEN BEGIN +//jshepard: safety first + if( !cameraDef ) { + gameLocal.Warning("Invalid cameraDef in GetViewParms"); + return; + } +//RAVEN END + if ( !cameraDef->GetAnim(1) ) { + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() == 0 ) { + // we most likely are in the middle of a restore + // FIXME: it would be better to fix it so this doesn't get called during a restore + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + lerp = 0.0f; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + lerp = ( frameTime % 1000 ) * 0.001f; + } + + // skip any frames where camera cuts occur + realFrame = frame; + cut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( frame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + frame++; + cut++; + } + if ( lastFrame != frame ) { + cameraDef->GetAnim(1)->CallFrameCommands( this, lastFrame, frame ); + lastFrame = frame; + } + + if ( g_debugCinematic.GetBool() ) { + int prevFrameTime = ( gameLocal.time - starttime - gameLocal.msec ) * cameraDef->GetAnim(1)->GetFrameRate(); + int prevFrame = prevFrameTime / 1000; + int prevCut; + + prevCut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( prevFrame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + prevFrame++; + prevCut++; + } + + if ( prevCut != cut ) { + gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); + } + } + + // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, + // but since they're mainly used for static cams anyway, just stay on it infinitely. + if ( ( frame < 0 ) || ( cameraDef->GetAnim(1)->NumFrames() < 2 ) ) { + view->viewaxis = cameraDef->GetAnim(1)->GetAnim( 0 )->q.ToQuat().ToMat3(); + view->vieworg = cameraDef->GetAnim(1)->GetAnim( 0 )->t + offset; + view->fov_x = cameraDef->GetAnim(1)->GetAnim( 0 )->fov; + } else if ( frame > cameraDef->GetAnim(1)->NumFrames() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + GetViewParms( view ); + return; + } + + Stop(); + if ( gameLocal.GetCamera() != NULL ) { + // we activated another camera when we stopped, so get it's viewparms instead + gameLocal.GetCamera()->GetViewParms( view ); + return; + } else { + // just use our last frame + camFrame = cameraDef->GetAnim(1)->GetAnim( cameraDef->GetAnim(1)->NumFrames() - 1 ); + view->viewaxis = camFrame->q.ToQuat().ToMat3(); + view->vieworg = camFrame->t + offset; + view->fov_x = camFrame->fov; + } + } else if ( lerp == 0.0f ) { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + view->viewaxis = camFrame[ 0 ].q.ToMat3(); + view->vieworg = camFrame[ 0 ].t + offset; + view->fov_x = camFrame[ 0 ].fov; + } else { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + invlerp = 1.0f - lerp; + q1 = camFrame[ 0 ].q.ToQuat(); + q2 = camFrame[ 1 ].q.ToQuat(); + q3.Slerp( q1, q2, lerp ); + view->viewaxis = q3.ToMat3(); + view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; + view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; + } + + gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); + + // setup the pvs for this frame + UpdatePVSAreas( view->vieworg ); + +#if 0 + static int lastFrame = 0; + static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); + if ( gameLocal.time != lastFrame ) { + gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); + gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); + gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + lastFrameVec = view->vieworg; + lastFrame = gameLocal.time; + } +#endif + + if ( g_showcamerainfo.GetBool() ) { + gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ); + } +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_UPDATE, realFrame ); +#endif +} +// RAVEN END + +/* +=============== +idCameraAnim::Event_Activate +================ +*/ +void idCameraAnim::Event_Activate( idEntity *_activator ) { + activator = _activator; + if ( thinkFlags & TH_THINK ) { + Stop(); + } else { + Start(); + } +} + +/* +=============== +idCameraAnim::Event_Start +================ +*/ +void idCameraAnim::Event_Start( void ) { + Start(); +} + +/* +=============== +idCameraAnim::Event_Stop +================ +*/ +void idCameraAnim::Event_Stop( void ) { + Stop(); +} + +/* +================ +idCameraAnim::Event_SetCallback +================ +*/ +void idCameraAnim::Event_SetCallback( void ) { + if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// mekberg: wait support +/* +================ +idCameraAnim::Event_IsActive +================ +*/ +void idCameraAnim::Event_IsActive( void ) { + idThread::ReturnFloat( gameLocal.GetCamera( ) ? 1.0f : 0.0f ); +} + +// RAVEN END + +// RAVEN BEGIN +// jscott: portal sky support + +/*********************************************************************** + + rvCameraPortalSky + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPortalSky ) +END_CLASS + +/* +=============== +rvCameraPortalSky::Save +================ +*/ +void rvCameraPortalSky::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPortalSky::Restore +================ +*/ +void rvCameraPortalSky::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPortalSky::Spawn +===================== +*/ +void rvCameraPortalSky::Spawn( void ) +{ + if( gameLocal.GetPortalSky() ) + { + gameLocal.Error( "Only one portal sky camera allowed" ); + } + gameLocal.SetPortalSky( this ); +} + +/* +===================== +rvCameraPortalSky::GetViewParms +===================== +*/ +void rvCameraPortalSky::GetViewParms( renderView_t *view ) +{ + assert( view ); + if( view ) + { + view->vieworg = GetPhysics()->GetOrigin(); + view->viewID = -1; + } +} + + +/*********************************************************************** + + rvCameraPlayback + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPlayback ) +END_CLASS + +/* +=============== +rvCameraPlayback::Save +================ +*/ +void rvCameraPlayback::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPlayback::Restore +================ +*/ +void rvCameraPlayback::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPlayback::Spawn +===================== +*/ +void rvCameraPlayback::Spawn( void ) +{ + startTime = gameLocal.time; + playback = declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); +} + +/* +===================== +rvCameraPlayback::GetViewParms +===================== +*/ +void rvCameraPlayback::GetViewParms( renderView_t *view ) +{ + rvDeclPlaybackData pbd; + + assert( view ); + if( view ) + { + pbd.Init(); + if( declManager->GetPlaybackData( playback, PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, gameLocal.time - startTime, gameLocal.time - startTime, &pbd ) ) + { + startTime = gameLocal.time; + } + + view->vieworg = pbd.GetPosition(); + view->viewaxis = pbd.GetAngles().ToMat3(); + + // field of view +// RAVEN BEGIN +// jshepard: fov as a float for smoove transitions + gameLocal.CalcFov ( g_fov.GetFloat(), view->fov_x, view->fov_y ); + +// RAVEN END + } +} + +// RAVEN END diff --git a/source/mpgame/Camera.h b/source/mpgame/Camera.h new file mode 100644 index 0000000..21237e0 --- /dev/null +++ b/source/mpgame/Camera.h @@ -0,0 +1,296 @@ + +#ifndef __GAME_CAMERA_H__ +#define __GAME_CAMERA_H__ + +/* +=============================================================================== + +Camera providing an alternative view of the level. + +=============================================================================== +*/ + +class idCamera : public idEntity { +public: + ABSTRACT_PROTOTYPE( idCamera ); + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ) = 0; + virtual renderView_t * GetRenderView(); + virtual void Stop( void ){} ; +}; + +/* +=============================================================================== + +idCameraView + +=============================================================================== +*/ + +extern const idEventDef EV_SetFOV; +extern const idEventDef EV_Camera_Start; +extern const idEventDef EV_Camera_Stop; + +class idCameraView : public idCamera { +public: + CLASS_PROTOTYPE( idCameraView ); + idCameraView(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( ); + virtual void GetViewParms( renderView_t *view ); + virtual void Stop( void ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_SetAttachments(); + void SetAttachment( idEntity **e, const char *p ); +// RAVEN BEGIN +// bdube: changed fov to interpolated value + idInterpolate fov; +// RAVEN END + idEntity *attachedTo; + idEntity *attachedView; + +// RAVEN BEGIN +// bdube: added setfov event + void Event_SetFOV ( float fov ); + void Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ); + void Event_GetFOV ( void ); +// RAVEN END +}; + + + +/* +=============================================================================== + +A camera which follows a path defined by an animation. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/* +============================================================================================== + + rvCameraAnimation + +============================================================================================== +*/ +class idDeclCameraDef; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +class rvCameraAnimation { +private: + idList cameraCuts; + idList camera; + idList frameLookup; + idList frameCommands; + int frameRate; + idStr name; + idStr realname; + +public: + rvCameraAnimation(); + rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ); + ~rvCameraAnimation(); + + void SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ); + const char *Name( void ) const; + const char *FullName( void ) const; + int NumFrames( void ) const; + const cameraFrame_t * GetAnim( int index ) const; + int NumCuts( void ) const; + const int GetCut( int index ) const; + const int GetFrameRate( void ) const; + + const char *AddFrameCommand( const class idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ); + void CallFrameCommands( idEntity *ent, int from, int to ) const; + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; +}; + +ID_INLINE const cameraFrame_t *rvCameraAnimation::GetAnim( int index ) const { + return &camera[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetCut( int index ) const { + return cameraCuts[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetFrameRate( void ) const { + return frameRate; +} + +/* +============================================================================================== + + idDeclCameraDef + +============================================================================================== +*/ +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclCameraDef : public idDecl, public Serializable<'IDCD'> { +public: + idDeclCameraDef( SerialInputStream &stream ); + + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; +#else +class idDeclCameraDef : public idDecl { +#endif +// RAVEN END +public: + idDeclCameraDef(); + ~idDeclCameraDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + int NumAnims( void ) const; + const rvCameraAnimation * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + +private: + void CopyDecl( const idDeclCameraDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idList anims; +}; + +ID_INLINE const rvCameraAnimation *idDeclCameraDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idCameraAnim + +============================================================================================== +*/ +class idCameraAnim : public idCamera { +public: + CLASS_PROTOTYPE( idCameraAnim ); + + idCameraAnim(); + ~idCameraAnim(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int threadNum; + idVec3 offset; + int starttime; + int cycle; + const idDeclCameraDef *cameraDef; + int lastFrame; + idEntityPtr activator; + + void Start( void ); + void Stop( void ); + void Think( void ); + + void LoadAnim( void ); + void Event_Start( void ); + void Event_Stop( void ); + void Event_SetCallback( void ); + void Event_Activate( idEntity *activator ); + +// RAVEN BEGIN +// mekberg: wait support + void Event_IsActive( ); + + idList imageTable; + idList imageCmds; +// RAVEN END +}; +// RAVEN END + +// RAVEN BEGIN +/* +=============================================================================== + +rvCameraPortalSky + +=============================================================================== +*/ +// jscott: for portal skies +class rvCameraPortalSky : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPortalSky ); + + rvCameraPortalSky( void ) {} + ~rvCameraPortalSky( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); +}; + +/* +=============================================================================== + +rvCameraPlayback + +=============================================================================== +*/ +class rvCameraPlayback : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPlayback ); + + rvCameraPlayback( void ) {} + ~rvCameraPlayback( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int startTime; + const rvDeclPlayback *playback; +}; +// RAVEN END + +#endif /* !__GAME_CAMERA_H__ */ diff --git a/source/mpgame/Effect.cpp b/source/mpgame/Effect.cpp new file mode 100644 index 0000000..c7b3a4f --- /dev/null +++ b/source/mpgame/Effect.cpp @@ -0,0 +1,463 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Effect.h" +#include "client/ClientEffect.h" + +const idEventDef EV_LookAtTarget( "lookAtTarget", NULL ); +const idEventDef EV_Attenuate( "attenuate", "f" ); + +CLASS_DECLARATION( idEntity, rvEffect ) + EVENT( EV_Activate, rvEffect::Event_Activate ) + EVENT( EV_LookAtTarget, rvEffect::Event_LookAtTarget ) + EVENT( EV_Earthquake, rvEffect::Event_EarthQuake ) + EVENT( EV_Camera_Start, rvEffect::Event_Start ) + EVENT( EV_Camera_Stop, rvEffect::Event_Stop ) + EVENT( EV_Attenuate, rvEffect::Event_Attenuate ) + EVENT( EV_IsActive, rvEffect::Event_IsActive ) +END_CLASS + +/* +================ +rvEffect::rvEffect +================ +*/ +rvEffect::rvEffect ( void ) { + fl.networkSync = true; + loop = false; + lookAtTarget = false; + effect = NULL; + endOrigin.Zero(); +} + +/* +================ +rvEffect::Spawn +================ +*/ +void rvEffect::Spawn( void ) { + const char* fx; + if ( !spawnArgs.GetString ( "fx", "", &fx ) || !*fx ) { + if ( !( gameLocal.editors & EDITOR_FX ) ) { + gameLocal.Warning ( "no effect file specified on effect entity '%s'", name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + } else { + effect = ( const idDecl * )declManager->FindEffect( spawnArgs.GetString ( "fx" ) ); + if( effect->IsImplicit() ) { + common->Warning( "Unknown effect \'%s\' on entity \'%s\'", spawnArgs.GetString ( "fx" ), GetName() ); + } + } + + spawnArgs.GetVector ( "endOrigin", "0 0 0", endOrigin ); + + spawnArgs.GetBool ( "loop", "0", loop ); + + // If look at target is set the effect will continually update itself to look at its target + spawnArgs.GetBool( "lookAtTarget", "0", lookAtTarget ); + + renderEntity.shaderParms[SHADERPARM_ALPHA] = spawnArgs.GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[SHADERPARM_BRIGHTNESS] = spawnArgs.GetFloat ( "_brightness", "1" ); + + if( spawnArgs.GetBool( "start_on", loop ? "1" : "0" ) ) { + ProcessEvent( &EV_Activate, this ); + } +#if 0 + // If anyone ever gets around to a flood fill from the origin rather than the over generous PushVolumeIntoTree bounds, + // this warning will become useful. Until then, it's a bogus warning. + if( gameRenderWorld->PointInArea( GetPhysics()->GetOrigin() ) < 0 ) { + common->Warning( "Effect \'%s\' out of world", name.c_str() ); + } +#endif +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Think( void ) { + + if( clientEntities.IsListEmpty ( ) ) { + BecomeInactive( TH_THINK ); + + // Should the func_fx be removed now? + if( !(gameLocal.editors & EDITOR_FX) && spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + return; + } + else if( lookAtTarget ) { + // If activated and looking at its target then update the target information + ProcessEvent( &EV_LookAtTarget ); + } + + UpdateVisuals(); + Present ( ); +} + +/* +================ +rvEffect::Save +================ +*/ +void rvEffect::Save ( idSaveGame *savefile ) const { + savefile->WriteBool ( loop ); + savefile->WriteBool ( lookAtTarget ); + savefile->WriteString ( effect->GetName() ); + savefile->WriteVec3 ( endOrigin ); + clientEffect.Save ( savefile ); +} + +/* +================ +rvEffect::Restore +================ +*/ +void rvEffect::Restore ( idRestoreGame *savefile ) { + idStr name; + + savefile->ReadBool ( loop ); + savefile->ReadBool ( lookAtTarget ); + savefile->ReadString ( name ); + effect = declManager->FindType( DECL_EFFECT, name ); + savefile->ReadVec3 ( endOrigin ); + clientEffect.Restore ( savefile ); +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Stop( bool destroyParticles ) { + StopEffect ( effect, destroyParticles ); +} + +/* +================ +rvEffect::Play +================ +*/ +bool rvEffect::Play( void ) { + clientEffect = PlayEffect ( effect, renderEntity.origin, renderEntity.axis, loop, endOrigin ); + if ( clientEffect ) { + + idVec4 color; + color[0] = renderEntity.shaderParms[SHADERPARM_RED]; + color[1] = renderEntity.shaderParms[SHADERPARM_GREEN]; + color[2] = renderEntity.shaderParms[SHADERPARM_BLUE]; + color[3] = renderEntity.shaderParms[SHADERPARM_ALPHA]; + clientEffect->SetColor ( color ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + clientEffect->SetAmbient( true ); + + BecomeActive ( TH_THINK ); + return true; + } + + return false; +} + +/* +================ +rvEffect::Attenuate +================ +*/ +void rvEffect::Attenuate ( float attenuation ) { + rvClientEntity* cent; + for( cent = clientEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { +// RAVEN END + static_cast(cent)->Attenuate ( attenuation ); + } + } +} + +/* +================ +rvEffect::Restart +================ +*/ +void rvEffect::Restart( void ) { + Stop( false ); + + if( loop ) { + Play(); + } +} + +/* +================ +rvEffect::UpdateChangeableSpawnArgs +================ +*/ +void rvEffect::UpdateChangeableSpawnArgs( const idDict *source ) { + const char* fx; + const idDecl *newEffect; + bool newLoop; + + idEntity::UpdateChangeableSpawnArgs(source); + if ( !source ) { + return; + } + + if ( source->GetString ( "fx", "", &fx ) && *fx ) { + newEffect = ( const idDecl * )declManager->FindEffect( fx ); + } else { + newEffect = NULL; + } + + idVec3 color; + source->GetVector( "_color", "1 1 1", color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = source->GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] = source->GetFloat ( "_brightness", "1" ); + if ( clientEffect ) { + clientEffect->SetColor ( idVec4(color[0],color[1],color[2],renderEntity.shaderParms[ SHADERPARM_ALPHA ]) ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + } + + source->GetBool ( "loop", "0", newLoop ); + + spawnArgs.Copy( *source ); + + // IF the effect handle has changed or the loop status has changed then restart the effect + if ( newEffect != effect || loop != newLoop ) { + Stop ( false ); + + loop = newLoop; + effect = newEffect; + + if ( effect ) { + Play ( ); + BecomeActive( TH_THINK ); + UpdateVisuals(); + } else { + BecomeInactive ( TH_THINK ); + UpdateVisuals(); + } + } +} + +/* +=============== +rvEffect::ShowEditingDialog +=============== +*/ +void rvEffect::ShowEditingDialog( void ) { + common->InitTool( EDITOR_FX, &spawnArgs ); +} + +/* +================= +rvEffect::WriteToSnapshot +================= +*/ +void rvEffect::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteBits( loop, 1 ); +} + +/* +================= +rvEffect::ReadFromSnapshot +================= +*/ +void rvEffect::ReadFromSnapshot( const idBitMsgDelta &msg ) { + const idDecl *old = effect; + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + loop = ( msg.ReadBits( 1 ) != 0 ); + + if ( effect && !old ) { + // TODO: need to account for when the effect really started + Play(); + } +} + +/* +================= +rvEffect::ClientPredictionThink +================= +*/ +void rvEffect::ClientPredictionThink( void ) { + if ( gameLocal.isNewFrame ) { + Think ( ); + } + RunPhysics(); + Present(); +} + +/* +================ +rvEffect::Event_Start +================ +*/ +void rvEffect::Event_Start ( void ) { + if( !effect || !clientEntities.IsListEmpty ( ) ) { + return; + } + + if( !Play() ) { + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + } else { + gameLocal.Warning( "Unable to play effect '%s'", effect->GetName() ); + } + BecomeInactive ( TH_THINK ); + } + + ProcessEvent( &EV_LookAtTarget ); +} + +/* +================ +rvEffect::Event_Stop +================ +*/ +void rvEffect::Event_Stop ( void ) { + if( !effect ) { + return; + } + + Stop( false ); +} + +/* +================= +rvEffect::Event_Activate +================= +*/ +void rvEffect::Event_Activate( idEntity *activator ) { + // Stop the effect if its already playing + if( !clientEntities.IsListEmpty ( ) ) { + Event_Stop ( ); + } else { + Event_Start ( ); + } + + ActivateTargets( activator ); +} + +/* +================ +rvEffect::Event_LookAtTarget + +Reorients the effect entity towards its target and sets the end origin as well +================ +*/ +void rvEffect::Event_LookAtTarget ( void ) { + const idKeyValue *kv; + idVec3 dir; + + if ( !effect || !clientEffect ) { + return; + } + + kv = spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if( ent ) { + if( !idStr::Icmp( ent->GetEntityDefName(), "target_null" ) ) { + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + + clientEffect->SetEndOrigin ( ent->GetPhysics()->GetOrigin() ); + clientEffect->SetAxis ( dir.ToMat3( ) ); + return; + } + } + kv = spawnArgs.MatchPrefix( "target", kv ); + } +} + +/* +================ +rvEffect::Event_EarthQuake +================ +*/ +void rvEffect::Event_EarthQuake ( float requiresLOS ) { + float quakeChance; + + if ( !spawnArgs.GetFloat("quakeChance", "0", quakeChance) ) { + return; + } + + if ( rvRandom::flrand(0, 1.0f) > quakeChance ) { + // failed its activation roll + return; + } + + if ( requiresLOS ) { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + + // activate this effect now + ProcessEvent ( &EV_Activate, gameLocal.entities[ENTITYNUM_WORLD] ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_Attenuate( float attenuation ) { + Attenuate( attenuation ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_IsActive( void ) { + idThread::ReturnFloat( ( !effect || !clientEntities.IsListEmpty() ) ? 0.0f : 1.0f ); +} + +/* +================ +rvEffect::InstanceLeave +================ +*/ +void rvEffect::InstanceLeave( void ) { + idEntity::InstanceLeave(); + Stop( true ); +} + +/* +================ +rvEffect::InstanceJoin +================ +*/ +void rvEffect::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + Restart(); +} diff --git a/source/mpgame/Effect.h b/source/mpgame/Effect.h new file mode 100644 index 0000000..8b93585 --- /dev/null +++ b/source/mpgame/Effect.h @@ -0,0 +1,66 @@ +//---------------------------------------------------------------- +// Effect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_EFFECT_H__ +#define __GAME_EFFECT_H__ + +class rvEffect : public idEntity +{ +public: + + CLASS_PROTOTYPE( rvEffect ); + + rvEffect ( void ); + + const bool GetEndOrigin ( idVec3 &result ) const; + void SetEndOrigin ( const idVec3 &origin ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Play ( void ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + void Attenuate ( float attenuation ); + + float GetBrightness ( void ) const; + + bool IsLooping ( void ) { return( loop ); } + + virtual void UpdateChangeableSpawnArgs ( const idDict *source ); + virtual void ShowEditingDialog ( void ); + + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + void ClientPredictionThink ( void ); + virtual void InstanceLeave ( void ); + virtual void InstanceJoin ( void ); + +protected: + + bool loop; + bool lookAtTarget; + const idDecl *effect; + idVec3 endOrigin; + rvClientEntityPtr clientEffect; + +private: + + void Event_Activate ( idEntity *activator ); + void Event_LookAtTarget ( void ); + void Event_EarthQuake ( float requiresLOS ); + + void Event_Start ( void ); + void Event_Stop ( void ); + + void Event_Attenuate ( float attenuation ); + void Event_IsActive ( void ); +}; + +#endif // __GAME_EFFECT_H__ diff --git a/source/mpgame/Entity.cpp b/source/mpgame/Entity.cpp new file mode 100644 index 0000000..0646ac5 --- /dev/null +++ b/source/mpgame/Entity.cpp @@ -0,0 +1,6791 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// bdube: client effects +#include "client/ClientEffect.h" +//mcg: need to know team for AddDamageEffects +#include "ai/AI_Manager.h" +// RAVEN END + +/* +=============================================================================== + + idEntity + +=============================================================================== +*/ + +// overridable events +const idEventDef EV_PostSpawn( "", NULL ); +const idEventDef EV_FindTargets( "", NULL ); +const idEventDef EV_Touch( "", "et" ); +const idEventDef EV_GetName( "getName", NULL, 's' ); +const idEventDef EV_SetName( "setName", "s" ); +const idEventDef EV_Activate( "activate", "e" ); +const idEventDef EV_ActivateTargets( "activateTargets", "e" ); +const idEventDef EV_NumTargets( "numTargets", NULL, 'f' ); +const idEventDef EV_GetTarget( "getTarget", "f", 'e' ); +const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' ); +const idEventDef EV_Bind( "bind", "e" ); +const idEventDef EV_BindPosition( "bindPosition", "e" ); +const idEventDef EV_BindToJoint( "bindToJoint", "esf" ); +const idEventDef EV_Unbind( "unbind", NULL ); +const idEventDef EV_RemoveBinds( "removeBinds" ); +const idEventDef EV_SpawnBind( "", NULL ); +const idEventDef EV_SetOwner( "setOwner", "e" ); +const idEventDef EV_SetModel( "setModel", "s" ); +const idEventDef EV_SetSkin( "setSkin", "s" ); +const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' ); +const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" ); +const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' ); +const idEventDef EV_SetOrigin( "setOrigin", "v" ); +const idEventDef EV_GetAngles( "getAngles", NULL, 'v' ); +const idEventDef EV_SetAngles( "setAngles", "v" ); +const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' ); +const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" ); +const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' ); +const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" ); +const idEventDef EV_GetSize( "getSize", NULL, 'v' ); +const idEventDef EV_SetSize( "setSize", "vv" ); +const idEventDef EV_GetMins( "getMins", NULL, 'v' ); +const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' ); +const idEventDef EV_IsHidden( "isHidden", NULL, 'd' ); +const idEventDef EV_Hide( "hide", NULL ); +const idEventDef EV_Show( "show", NULL ); +const idEventDef EV_Touches( "touches", "E", 'd' ); +const idEventDef EV_ClearSignal( "clearSignal", "d" ); +const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' ); +const idEventDef EV_SetShaderParm( "setShaderParm", "df" ); +const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" ); +const idEventDef EV_SetColor( "setColor", "fff" ); +const idEventDef EV_GetColor( "getColor", NULL, 'v' ); +const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" ); +const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' ); +const idEventDef EV_StartSound( "startSound", "sdd", 'f' ); +const idEventDef EV_StopSound( "stopSound", "dd" ); +const idEventDef EV_FadeSound( "fadeSound", "dff" ); +const idEventDef EV_SetGuiParm( "setGuiParm", "ss" ); +const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" ); +const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' ); +const idEventDef EV_SetKey( "setKey", "ss" ); +const idEventDef EV_GetKey( "getKey", "s", 's' ); +const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' ); +const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' ); +const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' ); +const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' ); +const idEventDef EV_RestorePosition( "restorePosition" ); +const idEventDef EV_UpdateCameraTarget( "", NULL ); +const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' ); +const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' ); +const idEventDef EV_StartFx( "startFx", "s" ); +const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); +const idEventDef EV_CallFunction( "callFunction", "s" ); +const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); + +// RAVEN BEGIN +// bgeisler: go back to default skin +const idEventDef EV_ClearSkin( "clearSkin"); +// kfuller: added events +const idEventDef EV_SetContents( "setContents", "d" ); +const idEventDef EV_GetLastBlocker( "getLastBlocker", NULL, 'e' ); +const idEventDef EV_Earthquake( "earthquake", "f" ); +// we should probably try to integrate this with AI_PlayAnim +const idEventDef EV_PlayAnim("playAnimNoChannel", "s"); +const idEventDef EV_PlayAnimXTimes("playAnimXTimes", "sf"); +// bdube: effect events +const idEventDef EV_PlayEffect( "playEffect", "ssd" ); +const idEventDef EV_StopEffect( "stopEffect", "s" ); +const idEventDef EV_StopAllEffects( "stopAllEffects" ); +const idEventDef EV_GetHealth ( "getHealth", NULL, 'f' ); +// bdube: surface related events +const idEventDef EV_HideSurface( "hideSurface", "s" ); +const idEventDef EV_ShowSurface( "showSurface", "s" ); +// bdube: added gui events +const idEventDef EV_GuiEvent ( "guiEvent", "s" ); +// jscott: for playback button handling +const idEventDef EV_PlaybackCallback( "playbackCallback", "ddd" ); +// nmckenzie: +const idEventDef EV_GetBindMaster( "getBindMaster", NULL, 'e' ); +const idEventDef EV_ApplyImpulse( "applyImpulse", "evv" ); +// abahr: +const idEventDef EV_RemoveNullTargets( "removeNullTargets" ); +const idEventDef EV_IsA( "isA", "s", 'f' ); +const idEventDef EV_IsSameTypeAs( "isSameTypeAs", "e", 'f' ); +const idEventDef EV_MatchPrefix( "matchPrefix", "ss", 's' ); +const idEventDef EV_ClearTargetList( "clearTargetList", "f" ); +// twhitaker: +const idEventDef EV_AppendTarget( "appendTarget", "E", 'f' ); +const idEventDef EV_RemoveTarget( "removeTarget", "e" ); +// mekberg: +const idEventDef EV_SetHealth( "setHealth", "f" ); +// RAVEN END + +ABSTRACT_DECLARATION( idClass, idEntity ) + EVENT( EV_GetName, idEntity::Event_GetName ) + EVENT( EV_SetName, idEntity::Event_SetName ) + EVENT( EV_FindTargets, idEntity::Event_FindTargets ) + EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets ) + EVENT( EV_NumTargets, idEntity::Event_NumTargets ) + EVENT( EV_GetTarget, idEntity::Event_GetTarget ) + EVENT( EV_RandomTarget, idEntity::Event_RandomTarget ) + EVENT( EV_BindToJoint, idEntity::Event_BindToJoint ) + EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds ) + EVENT( EV_Bind, idEntity::Event_Bind ) + EVENT( EV_BindPosition, idEntity::Event_BindPosition ) + EVENT( EV_Unbind, idEntity::Event_Unbind ) + EVENT( EV_SpawnBind, idEntity::Event_SpawnBind ) + EVENT( EV_SetOwner, idEntity::Event_SetOwner ) + EVENT( EV_SetModel, idEntity::Event_SetModel ) + EVENT( EV_SetSkin, idEntity::Event_SetSkin ) + EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm ) + EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm ) + EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms ) + EVENT( EV_SetColor, idEntity::Event_SetColor ) + EVENT( EV_GetColor, idEntity::Event_GetColor ) + EVENT( EV_IsHidden, idEntity::Event_IsHidden ) + EVENT( EV_Hide, idEntity::Event_Hide ) + EVENT( EV_Show, idEntity::Event_Show ) + EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader ) + EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader ) + EVENT( EV_StartSound, idEntity::Event_StartSound ) + EVENT( EV_StopSound, idEntity::Event_StopSound ) + EVENT( EV_FadeSound, idEntity::Event_FadeSound ) + EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin ) + EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin ) + EVENT( EV_GetOrigin, idEntity::Event_GetOrigin ) + EVENT( EV_SetOrigin, idEntity::Event_SetOrigin ) + EVENT( EV_GetAngles, idEntity::Event_GetAngles ) + EVENT( EV_SetAngles, idEntity::Event_SetAngles ) + EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity ) + EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity ) + EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity ) + EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity ) + EVENT( EV_GetSize, idEntity::Event_GetSize ) + EVENT( EV_SetSize, idEntity::Event_SetSize ) + EVENT( EV_GetMins, idEntity::Event_GetMins) + EVENT( EV_GetMaxs, idEntity::Event_GetMaxs ) + EVENT( EV_Touches, idEntity::Event_Touches ) + EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm ) + EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat ) + EVENT( EV_GetNextKey, idEntity::Event_GetNextKey ) + EVENT( EV_SetKey, idEntity::Event_SetKey ) + EVENT( EV_GetKey, idEntity::Event_GetKey ) + EVENT( EV_GetIntKey, idEntity::Event_GetIntKey ) + EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey ) + EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey ) + EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey ) + EVENT( EV_RestorePosition, idEntity::Event_RestorePosition ) + EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget ) + EVENT( EV_DistanceTo, idEntity::Event_DistanceTo ) + EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint ) + EVENT( EV_StartFx, idEntity::Event_StartFx ) + EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame ) + EVENT( EV_Thread_Wait, idEntity::Event_Wait ) + EVENT( EV_HasFunction, idEntity::Event_HasFunction ) + EVENT( EV_CallFunction, idEntity::Event_CallFunction ) + EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) + +// RAVEN BEGIN +// bgeisler: go back to default skin + EVENT( EV_ClearSkin, idEntity::Event_ClearSkin ) +// kfuller: added events + EVENT( EV_SetContents, idEntity::Event_SetContents ) + EVENT( EV_GetLastBlocker, idEntity::Event_GetLastBlocker) +// bdube: effect events + EVENT( EV_PlayEffect, idEntity::Event_PlayEffect ) + EVENT( EV_StopEffect, idEntity::Event_StopEffect ) + EVENT( EV_StopAllEffects, idEntity::Event_StopAllEffects ) + EVENT( EV_GetHealth, idEntity::Event_GetHealth ) +// bdube: mesh events + EVENT( EV_HideSurface, idEntity::Event_HideSurface ) + EVENT( EV_ShowSurface, idEntity::Event_ShowSurface ) +// bdube: gui events + EVENT( EV_GuiEvent, idEntity::Event_GuiEvent ) +// jscott: playback callback + EVENT( EV_PlaybackCallback, idEntity::Event_PlaybackCallback ) +// nmckenzie: Check who we're bound to. + EVENT( EV_GetBindMaster, idEntity::Event_GetBindMaster ) + EVENT( EV_ApplyImpulse, idEntity::Event_ApplyImpulse ) +// abahr: so we can call this from script + EVENT( EV_RemoveNullTargets, idEntity::Event_RemoveNullTargets ) + EVENT( EV_IsA, idEntity::Event_IsA ) + EVENT( EV_IsSameTypeAs, idEntity::Event_IsSameTypeAs ) + EVENT( EV_MatchPrefix, idEntity::Event_MatchPrefix ) + EVENT( EV_ClearTargetList, idEntity::Event_ClearTargetList ) +// twhitaker: to dynamically add/remove targets in script + EVENT( EV_AppendTarget, idEntity::Event_AppendTarget ) + EVENT( EV_RemoveTarget, idEntity::Event_RemoveTarget ) +// mekberg: added + EVENT( EV_SetHealth, idEntity::Event_SetHealth ) +// RAVEN END +END_CLASS + +/* +================ +UpdateGuiParms +================ +*/ +void UpdateGuiParms( idUserInterface *gui, const idDict *args ) { + if ( gui == NULL || args == NULL ) { + return; + } + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + while( kv ) { + gui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = args->MatchPrefix( "gui_parm", kv ); + } + gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ; + gui->StateChanged( gameLocal.time ); +} + +/* +================ +AddRenderGui +================ +*/ +void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) { + + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + *gui = uiManager->FindGui( name, true, ( kv != NULL ) || args->GetBool( "gui_noninteractive" ) ); + UpdateGuiParms( *gui, args ); +} + +/* +================ +idGameEdit::ParseSpawnArgsToRenderEntity + +parse the static model parameters +this is the canonical renderEntity parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) { + int i; + const char *temp; + idVec3 color; + float angle; + const idDeclModelDef *modelDef; + + memset( renderEntity, 0, sizeof( *renderEntity ) ); + + temp = args->GetString( "model" ); + + modelDef = NULL; + if ( temp[0] != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + renderEntity->hModel = modelDef->ModelHandle(); + if ( renderEntity->hModel && !renderEntity->hModel->IsLoaded() ) { + renderEntity->hModel->LoadModel(); + } + } + } + + if ( !renderEntity->hModel ) { + renderEntity->hModel = renderModelManager->FindModel( temp ); + } + } + if ( renderEntity->hModel ) { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } else { + renderEntity->bounds.Zero(); + } + + temp = args->GetString( "skin" ); + if ( temp[0] != '\0' ) { + renderEntity->customSkin = declManager->FindSkin( temp ); + } else if ( modelDef ) { + renderEntity->customSkin = modelDef->GetDefaultSkin(); + } + + temp = args->GetString( "shader" ); + if ( temp[0] != '\0' ) { + renderEntity->customShader = declManager->FindMaterial( temp ); + } + + args->GetVector( "origin", "0 0 0", renderEntity->origin ); + + // get the rotation matrix in either full form, or single angle form + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) { + angle = args->GetFloat( "angle" ); +// RAVEN BEGIN +// abahr: allowing up and down buttons to affect orientation + if( angle == -1.0f ) { + renderEntity->axis = idAngles( -90.0f, 0.0f, 0.0f ).ToMat3(); + } else if( angle == -2.0f ) { + renderEntity->axis = idAngles( 90.0f, 0.0f, 0.0f ).ToMat3(); + } else +// RAVEN END + if ( angle != 0.0f ) { + renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + renderEntity->axis.Identity(); + } + } + +// RAVEN BEGIN + renderEntity->referenceSoundHandle = -1; +// RAVEN END + + // get shader parms + args->GetVector( "_color", "1 1 1", color ); + renderEntity->shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" ); + renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" ); + renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" ); + renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" ); + renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" ); + renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" ); + renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" ); + renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" ); + renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" ); + + // check noDynamicInteractions flag + renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" ); + + // check noshadows flag + renderEntity->noShadow = args->GetBool( "noshadows" ); + + // check noselfshadows flag + renderEntity->noSelfShadow = args->GetBool( "noselfshadows" ); + + // init any guis, including entity-specific states + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) ); + if ( temp[ 0 ] != '\0' ) { + AddRenderGui( temp, &renderEntity->gui[ i ], args ); + } + } +} + +/* +================ +idGameEdit::ParseSpawnArgsToRefSound + +parse the sound parameters +this is the canonical refSound parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) { + const char *temp; + + memset( refSound, 0, sizeof( *refSound ) ); + refSound->referenceSoundHandle = -1; + +// RAVEN BEGIN + refSound->parms.minDistance = args->GetFloat( "s_mindistance" ); + refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" ); + // WARNING: This overrides the volume; it does not modify it + if( args->GetFloat( "s_volume" ) != 0.0f ) { + refSound->parms.volume = idMath::dBToScale( args->GetFloat( "s_volume" ) ); + } + + if( refSound->parms.volume < 0.0f || refSound->parms.volume > 5.0f ) { + common->Warning( "Unreasonable volume (%g) on entity \'%s\'", refSound->parms.volume, args->GetString( "name" ) ); + refSound->parms.volume = 5.0f; + } +// RAVEN END + refSound->parms.shakes = args->GetFloat( "s_shakes" ); + + args->GetVector( "origin", "0 0 0", refSound->origin ); + + // if a diversity is not specified, every sound start will make + // a random one. Specifying diversity is usefull to make multiple + // lights all share the same buzz sound offset, for instance. + refSound->diversity = args->GetFloat( "s_diversity", "-1" ); + refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" ); + + if ( args->GetBool( "s_omni" ) ) { + refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + if ( args->GetBool( "s_looping" ) ) { + refSound->parms.soundShaderFlags |= SSF_LOOPING; + } + if ( args->GetBool( "s_occlusion" ) ) { + refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + if ( args->GetBool( "s_global" ) ) { + refSound->parms.soundShaderFlags |= SSF_GLOBAL; + } + if ( args->GetBool( "s_unclamped" ) ) { + refSound->parms.soundShaderFlags |= SSF_UNCLAMPED; + } + if ( args->GetBool( "s_center" ) ) { + refSound->parms.soundShaderFlags |= SSF_CENTER; + } + + refSound->parms.soundClass = args->GetInt( "s_soundClass" ); + + temp = args->GetString( "s_shader" ); + if ( temp[0] != '\0' ) { + refSound->shader = declManager->FindSound( temp ); + } + +// RAVEN BEGIN + if( refSound->parms.maxDistance < refSound->parms.minDistance ) { + common->Warning( "ParseSpawnArgsToRefSound: Max distance less than min distance for entity \'%s\'", args->GetString( "name", "*unknown*" ) ); + } +// RAVEN END +} + +/* +=============== +idEntity::UpdateChangeableSpawnArgs + +Any key val pair that might change during the course of the game ( via a gui or whatever ) +should be initialize here so a gui or other trigger can change something and have it updated +properly. An optional source may be provided if the values reside in an outside dictionary and +first need copied over to spawnArgs +=============== +*/ +void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) { + int i; + const char *target; + + if ( !source ) { + source = &spawnArgs; + } + cameraTarget = NULL; + target = source->GetString( "cameraTarget" ); + if ( target && target[0] ) { +// RAVEN BEGIN +// bdube: EV_UpdateCameraTarget pulls from spawnargs so we need to move the target over + spawnArgs.Set ( "cameraTarget", target ); +// RAVEN END + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], source ); + } +} + +/* +================ +idEntity::idEntity +================ +*/ +idEntity::idEntity() { + + entityNumber = ENTITYNUM_NONE; + entityDefNumber = -1; + + spawnNode.SetOwner( this ); + activeNode.SetOwner( this ); + + snapshotNode.SetOwner( this ); + snapshotSequence = -1; + snapshotBits = 0; + + thinkFlags = 0; + dormantStart = 0; + cinematic = false; + renderView = NULL; + cameraTarget = NULL; + health = 0; + + physics = NULL; + bindMaster = NULL; + bindJoint = INVALID_JOINT; + bindBody = -1; + teamMaster = NULL; + teamChain = NULL; + signals = NULL; + + memset( PVSAreas, 0, sizeof( PVSAreas ) ); + numPVSAreas = -1; + + memset( &fl, 0, sizeof( fl ) ); + fl.neverDormant = true; // most entities never go dormant + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + modelDefHandle = -1; + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + + mpGUIState = -1; + +// RAVEN BEGIN +// rjohnson: added this to persist long thinking entities + mLastLongThinkTime = 0; + mLastLongThinkColor.Zero(); +// ddynerman: instance, clipworld + SetInstance( 0 ); + SetClipWorld( 0 ); + fl.persistAcrossInstances = false; +// twhitaker + forwardDamageEnt = NULL; +// ddynerman: optional preprediction + predictTime = 0; +// RAVEN END +} + +/* +================ +idEntity::Spawn +================ +*/ +void idEntity::Spawn( void ) { + int i; + const char *temp; + idVec3 origin; + idMat3 axis; + const idKeyValue *networkSync; + const char *classname; + const char *scriptObjectName; + + gameLocal.RegisterEntity( this ); + +// bdube: make sure there is a classname before trying to use it + if ( spawnArgs.GetString( "classname", NULL, &classname ) ) { + const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false ); + if ( def ) { + entityDefNumber = def->Index(); + } + } + + // Persona is a set of keys that augment an entity giving it its own custom persona + const idDict* dict; + dict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_persona", "" ), false ); + if ( dict ) { + spawnArgs.Copy ( *dict ); + } +// RAVEN END + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + +// RAVEN BEGIN +// bdube: added hidesurface + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } +// RAVEN END + + renderEntity.entityNum = entityNumber; + +// RAVEN BEGIN +// ddynerman: LOD code + renderEntity.shadowLODDistance = spawnArgs.GetFloat( "shadow_lod_distance", "768.0" ); + renderEntity.shadowLODDistance *= renderEntity.shadowLODDistance; +// ddynerman: multiple clip worlds + int spawnInstance = spawnArgs.GetInt( "instance" ); + SetInstance( spawnInstance ); +// RAVEN END + + // go dormant within 5 frames so that when the map starts most monsters are dormant + dormantStart = gameLocal.time - DELAY_DORMANT_TIME + gameLocal.msec * 5; + + origin = renderEntity.origin; + axis = renderEntity.axis; + + // do the audio parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + // only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId + // don't spatialize sounds from the same entity + refSound.listenerId = entityNumber + 1; + + cameraTarget = NULL; + temp = spawnArgs.GetString( "cameraTarget" ); + if ( temp && temp[0] ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs ); + } + + fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" ); +// RAVEN BEGIN +// bdube: usable + fl.usable = spawnArgs.GetBool ( "usable", "0" ); +// RAVEN END + + fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" ); + fl.hidden = spawnArgs.GetBool( "hide", "0" ); + if ( fl.hidden ) { + // make sure we're hidden, since a spawn function might not set it up right + PostEventMS( &EV_Hide, 0 ); + } + cinematic = spawnArgs.GetBool( "cinematic", "0" ); + + networkSync = spawnArgs.FindKey( "networkSync" ); + if ( networkSync ) { + fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 ); + } + + // every object will have a unique name + temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) ); + SetName( temp ); + + // if we have targets, wait until all entities are spawned to get them + if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); + + temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + + if ( spawnArgs.GetString( "bind", "", &temp ) ) { + PostEventMS( &EV_SpawnBind, 0 ); + } + + // auto-start a sound on the entity + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + // setup script object + if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + +// RAVEN BEGIN + fl.persistAcrossInstances = false; +// bgeisler: added + fl.triggerAnim = spawnArgs.GetBool( "trigger_anim" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +// RAVEN END +} + +/* +================ +idEntity::~idEntity +================ +*/ +idEntity::~idEntity( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + if ( thinkFlags ) { + BecomeInactive( thinkFlags ); + } + activeNode.Remove(); + + Signal( SIG_REMOVED ); + + // we have to set back the default physics object before unbinding because the entity + // specific physics object might be an entity variable and as such could already be destroyed. + SetPhysics( NULL ); + + // remove any entities that are bound to me + RemoveBinds(); + + // unbind from master + Unbind(); + QuitTeam(); + + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + + delete renderView; + renderView = NULL; + + delete signals; + signals = NULL; + +// RAVEN BEGIN +// bdube: make sure all sounds and attached effects are stopped + StopSound( SCHANNEL_ANY, false ); + + RemoveClientEntities(); +// RAVEN END + + FreeModelDef(); + FreeSoundEmitter( false ); + + gameLocal.UnregisterEntity( this ); +} + +/* +================ +idEntity::Save +================ +*/ +void idEntity::Save( idSaveGame *savefile ) const { + int i, j; + rvClientEntity* cent; + + savefile->WriteInt( entityNumber ); + savefile->WriteInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->WriteInt( snapshotSequence ); + savefile->WriteInt( snapshotBits ); + + savefile->WriteString( name ); + savefile->WriteDict( &spawnArgs ); + scriptObject.Save( savefile ); + + savefile->WriteInt( thinkFlags ); + savefile->WriteInt( dormantStart ); + savefile->WriteBool( cinematic ); + + // renderView_t * renderView; + + savefile->WriteObject( cameraTarget ); + + savefile->WriteInt( targets.Num() ); + for( i = 0; i < targets.Num(); i++ ) { + targets[ i ].Save( savefile ); + } + + savefile->WriteInt( health ); + + savefile->WriteInt( clientEntities.Num() ); + for( cent = clientEntities.Next(); cent; cent = cent->bindNode.Next() ) { + savefile->WriteObject( cent ); + } + +// savefile->WriteInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->WriteVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( modelDefHandle ); + savefile->WriteRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper save + forwardDamageEnt.Save ( savefile ); +// RAVEN END + + savefile->WriteStaticObject( defaultPhysicsObj ); + + savefile->WriteObject( bindMaster.GetEntity() ); + savefile->WriteJoint( bindJoint ); + savefile->WriteInt( bindBody ); + savefile->WriteObject( teamMaster ); + savefile->WriteObject( teamChain ); + + savefile->WriteInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->WriteInt( PVSAreas[ i ] ); + } + + if ( !signals ) { + savefile->WriteBool( false ); + } else { + savefile->WriteBool( true ); + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->WriteInt( signals->signal[ i ].Num() ); + for( j = 0; j < signals->signal[ i ].Num(); j++ ) { + savefile->WriteInt( signals->signal[ i ][ j ].threadnum ); + savefile->WriteString( signals->signal[ i ][ j ].function->Name() ); + } + } + } + + savefile->WriteInt( mpGUIState ); + + savefile->WriteInt( instance ); + savefile->WriteInt( clipWorld ); +} + +/* +================ +idEntity::Restore +================ +*/ +void idEntity::Restore( idRestoreGame *savefile ) { + int i, j; + int num; + rvClientEntity *temp; + idStr funcname; + + savefile->ReadInt( entityNumber ); + savefile->ReadInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->ReadInt( snapshotSequence ); + savefile->ReadInt( snapshotBits ); + + savefile->ReadString( name ); + SetName( name ); + savefile->ReadDict( &spawnArgs ); + + scriptObject.Restore( savefile ); + + savefile->ReadInt( thinkFlags ); + savefile->ReadInt( dormantStart ); + savefile->ReadBool( cinematic ); + + // renderView_t * renderView; + + savefile->ReadObject( reinterpret_cast( cameraTarget ) ); + + targets.Clear(); + savefile->ReadInt( num ); + targets.SetNum( num ); + for( i = 0; i < num; i++ ) { + targets[ i ].Restore( savefile ); + } + + savefile->ReadInt( health ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( temp ) ); + if( temp ) { + temp->bindNode.AddToEnd( clientEntities ); + } + } + +// savefile->ReadInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->ReadVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Read( &fl, sizeof( fl ) ); + +// RAVEN BEGIN + savefile->ReadRenderEntity( renderEntity, &spawnArgs ); +// RAVEN END + savefile->ReadInt( modelDefHandle ); + savefile->ReadRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper restore + forwardDamageEnt.Restore ( savefile ); +// RAVEN END + + savefile->ReadStaticObject( defaultPhysicsObj ); + RestorePhysics( &defaultPhysicsObj ); + + idEntity *templol = 0; + savefile->ReadObject( reinterpret_cast( templol ) ); + bindMaster = templol; + + savefile->ReadJoint( bindJoint ); + savefile->ReadInt( bindBody ); + savefile->ReadObject( reinterpret_cast( teamMaster ) ); + savefile->ReadObject( reinterpret_cast( teamChain ) ); + + savefile->ReadInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->ReadInt( PVSAreas[ i ] ); + } + + bool readsignals; + savefile->ReadBool( readsignals ); + if ( readsignals ) { + signals = new signalList_t; + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->ReadInt( num ); + signals->signal[ i ].SetNum( num ); + for( j = 0; j < num; j++ ) { + savefile->ReadInt( signals->signal[ i ][ j ].threadnum ); + savefile->ReadString( funcname ); + signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname ); + if ( !signals->signal[ i ][ j ].function ) { + savefile->Error( "Function '%s' not found", funcname.c_str() ); + } + } + } + } + + savefile->ReadInt( mpGUIState ); + + // restore must retrieve modelDefHandle from the renderer + if ( modelDefHandle != -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + savefile->ReadInt( instance ); + savefile->ReadInt( clipWorld ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +} + +/* +================ +idEntity::GetEntityDefName +================ +*/ +const char * idEntity::GetEntityDefName( void ) const { + if ( entityDefNumber < 0 ) { + return "*unknown*"; + } + return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); +} + +/* +================ +idEntity::SetName +================ +*/ +void idEntity::SetName( const char *newname ) { + if ( name.Length() ) { + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, NULL ); + } + + name = newname; + if ( name.Length() ) { + if ( ( name == "NULL" ) || ( name == "null_entity" ) ) { + gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() ); + } + gameLocal.AddEntityToHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, this ); + } +} + +/* +================ +idEntity::GetName +================ +*/ +const char * idEntity::GetName( void ) const { + return name.c_str(); +} + + +/*********************************************************************** + + Thinking + +***********************************************************************/ + +/* +================ +idEntity::Think +================ +*/ +void idEntity::Think( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::DoDormantTests + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::DoDormantTests( void ) { + // Never go dormant? + if ( fl.neverDormant || (gameLocal.inCinematic && cinematic) ) { + return false; + } + + // if the monster area is not topologically connected to a player + if ( !gameLocal.InPlayerConnectedArea( this ) ) { + return true; + } else { + // the monster area is topologically connected to a player, but if + // the monster hasn't been woken up before, do the more precise PVS check + if ( !fl.hasAwakened ) { + if ( !gameLocal.InPlayerPVS( this ) ) { + return true; + } + } + } + + return false; +} + +/* +================ +idEntity::CheckDormant + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::CheckDormant( void ) { + bool dormant; + + dormant = DoDormantTests(); + if ( dormant ) { + if ( dormantStart == 0 ) { + dormantStart = gameLocal.time; + } + if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) { + dormant = false; + } + } else { + dormantStart = 0; + fl.hasAwakened = true; + } + + if ( dormant && !fl.isDormant ) { + fl.isDormant = true; + DormantBegin(); + } else if ( !dormant && fl.isDormant ) { + fl.isDormant = false; + DormantEnd(); + } + + return dormant; +} + +/* +================ +idEntity::DormantBegin + +called when entity becomes dormant +================ +*/ +void idEntity::DormantBegin( void ) { +} + +/* +================ +idEntity::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idEntity::DormantEnd( void ) { +} + +/* +================ +idEntity::IsActive +================ +*/ +bool idEntity::IsActive( void ) const { + return activeNode.InList(); +} + +/* +================ +idEntity::BecomeActive +================ +*/ +void idEntity::BecomeActive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // enable the team master if this entity is part of a physics team + if ( teamMaster && teamMaster != this ) { + teamMaster->BecomeActive( TH_PHYSICS ); + } else if ( !( thinkFlags & TH_PHYSICS ) ) { + // if this is a pusher +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Parametric::GetClassType() ) || physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + gameLocal.sortPushers = true; + } +// RAVEN BEGIN +// abahr: +// jnewquist: Use accessor for static class type + if( physics->IsType( rvPhysics_Spline::GetClassType() ) ) { + gameLocal.sortPushers = true; + } +// RAVEN END + } + } + + int oldFlags = thinkFlags; + thinkFlags |= flags; + if ( thinkFlags ) { + if ( !IsActive() ) { + activeNode.AddToEnd( gameLocal.activeEntities ); + } else if ( !oldFlags ) { + // we became inactive this frame, so we have to decrease the count of entities to deactivate + gameLocal.numEntitiesToDeactivate--; + } + } +} + +/* +================ +idEntity::BecomeInactive +================ +*/ +void idEntity::BecomeInactive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // may only disable physics on a team master if no team members are running physics or bound to a joints + if ( teamMaster == this ) { + for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) { + if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) { + flags &= ~TH_PHYSICS; + break; + } + } + } + } + + if ( thinkFlags ) { + thinkFlags &= ~flags; + if ( !thinkFlags && IsActive() ) { + gameLocal.numEntitiesToDeactivate++; + } + } + + if ( ( flags & TH_PHYSICS ) ) { + // if this entity has a team master + if ( teamMaster && teamMaster != this ) { + // if the team master is at rest + if ( teamMaster->IsAtRest() ) { + teamMaster->BecomeInactive( TH_PHYSICS ); + } + } + } +} + +/*********************************************************************** + + Visuals + +***********************************************************************/ + +/* +================ +idEntity::SetShaderParm +================ +*/ +void idEntity::SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderEntity.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( float red, float green, float blue ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = red; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = green; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec3 &color ) { + SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec3 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec4 &color ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec4 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idEntity::UpdateAnimationControllers +================ +*/ +bool idEntity::UpdateAnimationControllers( void ) { + // any ragdoll and IK animation controllers should be updated here + return false; +} + +/* +================ +idEntity::SetModel +================ +*/ +void idEntity::SetModel( const char *modelname ) { + assert( modelname ); + + FreeModelDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetSkin +================ +*/ +void idEntity::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} +// RAVEN BEGIN +// bgeisler: go back to default skin +/* +================ +idEntity::ClearSkin +================ +*/ +void idEntity::ClearSkin( void ) +{ + if ( GetAnimator() && GetAnimator()->ModelDef() ) { + renderEntity.customSkin = GetAnimator()->ModelDef()->GetDefaultSkin(); + } else { + renderEntity.customSkin = NULL; + } + + UpdateVisuals(); +} +// RAVEN END + +/* +================ +idEntity::GetSkin +================ +*/ +const idDeclSkin *idEntity::GetSkin( void ) const { + return renderEntity.customSkin; +} + +/* +================ +idEntity::FreeModelDef +================ +*/ +void idEntity::FreeModelDef( void ) { + if ( modelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandle ); + modelDefHandle = -1; + + rvClientEntity* cent; + + for( cent = clientEntities.Next(); cent != NULL; cent = cent->bindNode.Next() ) { + cent->FreeEntityDef(); + } + } +} + +/* +================ +idEntity::FreeLightDef +================ +*/ +void idEntity::FreeLightDef( void ) { +} + +/* +================ +idEntity::IsHidden +================ +*/ +bool idEntity::IsHidden( void ) const { + return fl.hidden; +} + +/* +================ +idEntity::Hide +================ +*/ +void idEntity::Hide( void ) { + if ( !IsHidden() ) { + fl.hidden = true; + FreeModelDef(); + UpdateVisuals(); + } +} + +/* +================ +idEntity::Show +================ +*/ +void idEntity::Show( void ) { + if ( IsHidden() ) { + fl.hidden = false; + UpdateVisuals(); + } +} + +/* +================ +idEntity::UpdateModelTransform +================ +*/ +void idEntity::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +idEntity::UpdateModel +================ +*/ +void idEntity::UpdateModel( void ) { + UpdateModelTransform(); + +// RAVEN BEGIN +// abahr: moved GetAnimator call because its invalid when called from a destructor + UpdateRenderEntityCallback(); +// RAVEN END + + // set to invalid number to force an update the next time the PVS areas are retrieved + ClearPVSAreas(); + + // ensure that we call Present this frame + BecomeActive( TH_UPDATEVISUALS ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEntity::UpdateRenderEntityCallback +================ +*/ +void idEntity::UpdateRenderEntityCallback() { +} +// RAVEN END + +/* +================ +idEntity::UpdateVisuals +================ +*/ +void idEntity::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( void ) { + int localNumPVSAreas, localPVSAreas[32]; + idBounds modelAbsBounds; + int i; + + modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis ); + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + + // FIXME: some particle systems may have huge bounds and end up in many PVS areas + // the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should + if ( localNumPVSAreas > MAX_PVS_AREAS ) { + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( renderEntity.origin ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + } + + for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) { + PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas]; + } + + for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) { + PVSAreas[ i ] = 0; + } +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( const idVec3 &pos ) { + int i; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS ); + i = numPVSAreas; + while ( i < MAX_PVS_AREAS ) { + PVSAreas[ i++ ] = 0; + } +} + +/* +================ +idEntity::GetNumPVSAreas +================ +*/ +int idEntity::GetNumPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return numPVSAreas; +} + +/* +================ +idEntity::GetPVSAreas +================ +*/ +const int *idEntity::GetPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return PVSAreas; +} + +/* +================ +idEntity::ClearPVSAreas +================ +*/ +void idEntity::ClearPVSAreas( void ) { + numPVSAreas = -1; +} + +/* +================ +idEntity::PhysicsTeamInPVS + + FIXME: for networking also return true if any of the entity shadows is in the PVS +================ +*/ +bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) { + idEntity *part; + + if ( teamMaster ) { + for ( part = teamMaster; part; part = part->teamChain ) { + if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) { + return true; + } + } + } else { + return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() ); + } + return false; +} + +/* +============== +idEntity::ProjectOverlay +============== +*/ +void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( modelDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +idEntity::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idEntity::Present( void ) { + + if ( !gameLocal.isNewFrame ) { + return; + } + + // if there is no handle yet, go ahead and add it, ignoring the last predict frame early out + // if not, that causes next render frame to have a bunch of spurious primitive draws ( r_showPrimitives ) + // ( we suspect this is because TH_UPDATEVISUALS doesn't get cleared? ) + if ( !gameLocal.isLastPredictFrame && modelDefHandle != -1 ) { + return; + } + +// RAVEN BEGIN +// ddynerman: don't render objects not in our instance (only on server) + if ( gameLocal.isServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != GetInstance() ) { + FreeModelDef(); + return; + } +// RAVEN END + + // don't render server demo stuff that's not in our instance + if ( gameLocal.IsServerDemoPlaying() ) { + if ( instance != 0 ) { + FreeModelDef(); + return; + } + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views +// RAVEN BEGIN +// rjohnson: removed PVS check for when func_static's are not starting in your PVS + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { +// RAVEN END + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idEntity::UpdateRenderEntity +================ +*/ +bool idEntity::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +idEntity::ModelCallback + + NOTE: may not change the game state whatsoever! +================ +*/ +bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + idEntity *ent; + + ent = gameLocal.entities[ renderEntity->entityNum ]; + if ( !ent ) { + gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity '%d'", renderEntity->entityNum ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idEntity::GetAnimator + +Subclasses will be responsible for allocating animator. +================ +*/ +idAnimator *idEntity::GetAnimator( void ) { + return NULL; +} + +/* +============= +idEntity::GetRenderView + +This is used by remote camera views to look from an entity +============= +*/ +renderView_t *idEntity::GetRenderView( void ) { + if ( !renderView ) { + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + renderView->vieworg = GetPhysics()->GetOrigin(); + renderView->fov_x = 120; + renderView->fov_y = 120; + renderView->viewaxis = GetPhysics()->GetAxis(); + + // copy global shader parms + for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time = gameLocal.time; + + return renderView; +} + +// RAVEN BEGIN +// bdube: added convienince functions for effects + +/*********************************************************************** + + effects + +***********************************************************************/ + +/* +================ +idEntity::PlayEffect +================ +*/ +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + if ( joint == INVALID_JOINT ) { + return NULL; + } + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteLong( joint ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + msg.WriteBits( loop, 1 ); + msg.WriteBits( predictBit, 1 ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT_JOINT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if ( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if ( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( originOffset ); + clientEffect->SetAxis( axisOffset ); + clientEffect->Bind( this, joint ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + idVec3 localOrigin; + idMat3 localAxis; + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( entityNumber == ENTITYNUM_WORLD ) { + return gameLocal.PlayEffect( effect, origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); + } + + // Calculate the local origin and axis from the given globals + localOrigin = ( origin - renderEntity.origin ) * renderEntity.axis.Transpose(); + localAxis = axis * renderEntity.axis.Transpose(); + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idCQuat quat; + + quat = localAxis.ToCQuat(); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( localOrigin.x ); + msg.WriteFloat( localOrigin.y ); + msg.WriteFloat( localOrigin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return( NULL ); + } +// ddynerman: a listen server might get this far re: playing effects, don't actually play out of instance effects + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); +// RAVEN END + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( localOrigin ); + clientEffect->SetAxis( localAxis ); + clientEffect->Bind( this ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +/* +================ +idEntity::StopAllEffects +================ +*/ +void idEntity::StopAllEffects( bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + static_cast( cent )->Stop( destroyParticles ); + } + } +} + +/* +================ +idEntity::StopEffect +================ +*/ +void idEntity::StopEffect( const idDecl *effect, bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + if( !effect ) { + return; + } + + // Build a list of all the effects to stop + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + + // Is this client entity an effect? + if ( !cent->IsType( rvClientEffect::GetClassType() ) ) { + continue; + } + + // Now check to make sure its the specific effect we want to stop + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex() == effect->Index() ) { + clientEffect->Stop( destroyParticles ); + } + } +} + +void idEntity::StopEffect( const char* effectName, bool destroyParticles ) { + StopEffect( gameLocal.GetEffect( spawnArgs, effectName ), destroyParticles ); +} + +// RAVEN END + +/*********************************************************************** + + Sound + +***********************************************************************/ + +/* +================ +idEntity::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idEntity::CanPlayChatterSounds( void ) const { + return true; +} + +/* +================ +idEntity::StartSound +================ +*/ +bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + +#ifdef _DEBUG + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + idStr soundNameStr = soundName; + if ( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } +#endif + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); +} + +/* +================ +idEntity::StartSoundShader +================ +*/ +bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + float diversity; + int len; + + if ( length ) { + *length = 0; + } + + if ( !shader ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return true; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, shader ); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STARTSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return false; + } + + // rjohnson: don't play sounds on a dedicated server! + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return false; + } + + // set a random value for diversity unless one was parsed from the entity + if ( refSound.diversity < 0.0f ) { + diversity = gameLocal.random.RandomFloat(); + } else { + diversity = refSound.diversity; + } + +// RAVEN BEGIN + // if we don't have a soundEmitter allocated yet, get one now + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + len = emitter->StartSound( shader, channel, diversity, soundShaderFlags ); + if ( length ) { + *length = len; + } + } +// RAVEN END + + // set reference to the sound for shader synced effects + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + + return true; +} + +/* +================ +idEntity::StopSound +================ +*/ +void idEntity::StopSound( const s_channelType channel, bool broadcast ) { + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STOPSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return; + } + +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->StopSound( channel ); + } +// RAVEN END +} + +/* +================ +idEntity::SetSoundVolume + + Must be called before starting a new sound. +================ +*/ +void idEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + +/* +================ +idEntity::UpdateSound +================ +*/ +void idEntity::UpdateSound( void ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { +// RAVEN END + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + +// RAVEN BEGIN + refSound.velocity = GetPhysics()->GetLinearVelocity(); + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); +// RAVEN END + } +} + +/* +================ +idEntity::GetListenerId +================ +*/ +int idEntity::GetListenerId( void ) const { + return refSound.listenerId; +} + +/* +================ +idEntity::GetSoundEmitter +================ +*/ +// RAVEN BEGIN +int idEntity::GetSoundEmitter( void ) const { + return( refSound.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idEntity::FreeSoundEmitter +================ +*/ +void idEntity::FreeSoundEmitter( bool immediate ) { +// RAVEN BEGIN + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, immediate ); + refSound.referenceSoundHandle = -1; +// RAVEN END +} + +// RAVEN BEGIN +// bdube: client entities + +/*********************************************************************** + + client entities + +***********************************************************************/ + +/* +================ +idEntity::RemoveClientEntities +================ +*/ +void idEntity::RemoveClientEntities( void ) { + rvClientEntity* cent; + // Unbinding should remove the node from the list so keep using the head until + // there are no more entities + for( cent = clientEntities.Next(); cent != NULL; cent = clientEntities.Next() ) { + cent->Unbind( ); + delete cent; + } + clientEntities.Clear( ); +} +// RAVEN END + +/*********************************************************************** + + entity binding + +***********************************************************************/ + +/* +================ +idEntity::PreBind +================ +*/ +void idEntity::PreBind( void ) { +} + +/* +================ +idEntity::PostBind +================ +*/ +void idEntity::PostBind( void ) { +} + +/* +================ +idEntity::PreUnbind +================ +*/ +void idEntity::PreUnbind( void ) { +} + +/* +================ +idEntity::PostUnbind +================ +*/ +void idEntity::PostUnbind( void ) { +} + +/* +================ +idEntity::InitBind +================ +*/ +bool idEntity::InitBind( idEntity *master ) { + + if ( master == this ) { + gameLocal.Error( "Tried to bind an object to itself." ); + return false; + } + + if ( this == gameLocal.world ) { + gameLocal.Error( "Tried to bind world to another entity" ); + return false; + } + + // unbind myself from my master + Unbind(); + + // add any bind constraints to an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master && IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->AddBindConstraints(); + } + + if ( !master || master == gameLocal.world ) { + // this can happen in scripts, so safely exit out. + return false; + } + + return true; +} + +/* +================ +idEntity::FinishBind +================ +*/ +void idEntity::FinishBind( void ) { + + // set the master on the physics object + physics->SetMaster( bindMaster, fl.bindOrientated ); + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindMaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + JoinTeam( bindMaster ); + + // if our bindMaster is enabled during a cinematic, we must be, too +// RAVEN BEGIN +// rjohnson: players should always have cinematic turned on, no matter what + if ( !IsType ( idPlayer::GetClassType() ) ) { + cinematic = bindMaster->cinematic; + } +// RAVEN END + + // make sure the team master is active so that physics get run + teamMaster->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idEntity::Bind + + bind relative to the visual position of the master +================ +*/ +void idEntity::Bind( idEntity *master, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind( ); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) { + jointHandle_t jointnum; + idAnimator *masterAnimator; + + if ( !InitBind( master ) ) { + return; + } + + masterAnimator = master->GetAnimator(); + if ( !masterAnimator ) { + gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() ); + return; + } + + jointnum = masterAnimator->GetJointHandle( jointname ); + if ( jointnum == INVALID_JOINT ) { + gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() ); + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToBody + + bind relative to a collision model used by the physics of the master +================ +*/ +void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + if ( bodyId < 0 ) { + gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId ); + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = bodyId; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::Unbind +================ +*/ +void idEntity::Unbind( void ) { + idEntity * prev; + idEntity * next; + idEntity * last; + idEntity * ent; + + // remove any bind constraints from an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->RemoveBindConstraints(); + } + + if ( !bindMaster ) { + return; + } + + if ( !teamMaster ) { + // Teammaster already has been freed + bindMaster = NULL; + return; + } + + PreUnbind(); + + if ( physics ) { + physics->SetMaster( NULL, fl.bindOrientated ); + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = teamMaster; + for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) { + prev = ent; + } + + assert( ent == this ); // If ent is not pointing to this, then something is very wrong. + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = teamChain; next != NULL; next = next->teamChain ) { + if ( !next->IsBoundTo( this ) ) { + break; + } + + // Tell them I'm now the teamMaster + next->teamMaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->teamChain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( teamMaster != this ) { + prev->teamChain = next; + if ( !next && ( teamMaster == prev ) ) { + prev->teamMaster = NULL; + } + } else if ( next ) { + // If we were the teamMaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamMaster = next; + } + next->teamMaster = next; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( teamChain ) { + // make myself my own team + teamMaster = this; + } else { + // no longer a team + teamMaster = NULL; + } + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = NULL; + + PostUnbind(); +} + +/* +================ +idEntity::RemoveBinds +================ +*/ +void idEntity::RemoveBinds( void ) { + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->teamChain; + if ( ent->bindMaster == this ) { + ent->Unbind(); + ent->PostEventMS( &EV_Remove, 0 ); + next = teamChain; + } + } +} + +/* +================ +idEntity::IsBound +================ +*/ +bool idEntity::IsBound( void ) const { + if ( bindMaster ) { + return true; + } + return false; +} + +/* +================ +idEntity::IsBoundTo +================ +*/ +// RAVEN BEGIN +// abahr: added const so it can be called from const functions +bool idEntity::IsBoundTo( const idEntity *master ) const { +// RAVEN END + idEntity *ent; + + if ( !bindMaster ) { + return false; + } + + for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) { + if ( ent == master ) { + return true; + } + } + + return false; +} + +/* +================ +idEntity::GetBindMaster +================ +*/ +idEntity *idEntity::GetBindMaster( void ) const { + return bindMaster; +} + +/* +================ +idEntity::GetBindJoint +================ +*/ +jointHandle_t idEntity::GetBindJoint( void ) const { + return bindJoint; +} + +/* +================ +idEntity::GetBindBody +================ +*/ +int idEntity::GetBindBody( void ) const { + return bindBody; +} + +/* +================ +idEntity::GetTeamMaster +================ +*/ +idEntity *idEntity::GetTeamMaster( void ) const { + return teamMaster; +} + +/* +================ +idEntity::GetNextTeamEntity +================ +*/ +idEntity *idEntity::GetNextTeamEntity( void ) const { + return teamChain; +} + +/* +===================== +idEntity::ConvertLocalToWorldTransform +===================== +*/ +void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + UpdateModelTransform(); + + offset = renderEntity.origin + offset * renderEntity.axis; + axis *= renderEntity.axis; +} + +/* +================ +idEntity::GetLocalVector + +Takes a vector in worldspace and transforms it into the parent +object's localspace. + +Note: Does not take origin into acount. Use getLocalCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetLocalCoordinates + +Takes a vector in world coordinates and transforms it into the parent +object's local coordinates. +================ +*/ +idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec - masterOrigin, pos ); + + return pos; +} + +// RAVEN BEGIN +// kfuller: added method + +/* +================ +idEntity::DistanceTo2d +================ +*/ +float idEntity::DistanceTo2d ( const idVec3& pos ) const { + idVec3 pos1; + idVec3 pos2; + pos1 = pos - (pos * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + pos2 = GetPhysics()->GetOrigin ( ); + pos2 = pos2 - (pos2 * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + return (pos2 - pos1).LengthFast ( ); +} + +/* +================ +idEntity::GetLocalAngles +================ +*/ +void idEntity::GetLocalAngles(idAngles &localAng) +{ + idVec3 localVec = GetPhysics()->GetAxis()[0]; + + GetLocalVector(localVec); + localAng = localVec.ToAngles(); +} +// RAVEN END + +/* +================ +idEntity::GetWorldVector + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. + +Note: Does not take origin into acount. Use getWorldCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldCoordinates + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. +================ +*/ +idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + pos += masterOrigin; + + return pos; +} + +/* +================ +idEntity::GetMasterPosition +================ +*/ +bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idVec3 localOrigin; + idMat3 localAxis; + idAnimator *masterAnimator; + + if ( bindMaster ) { + // if bound to a joint of an animated model + if ( bindJoint != INVALID_JOINT ) { + masterAnimator = bindMaster->GetAnimator(); + if ( !masterAnimator ) { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } else { + masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis ); + masterAxis *= bindMaster->renderEntity.axis; + masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis; + } + } else if ( bindBody >= 0 && bindMaster->GetPhysics() ) { + masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody ); + masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody ); + } else { + masterOrigin = bindMaster->renderEntity.origin; + masterAxis = bindMaster->renderEntity.axis; + } + return true; + } else { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } +} + +// RAVEN BEGIN +// abahr: needed so client get the correct position +/* +================ +idEntity::GetPosition +================ +*/ +void idEntity::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = renderEntity.origin; + axis = renderEntity.axis; +} +// RAVEN END + +/* +================ +idEntity::GetWorldVelocities +================ +*/ +void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const { + + linearVelocity = physics->GetLinearVelocity(); + angularVelocity = physics->GetAngularVelocity(); + + if ( bindMaster ) { + idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity; + idMat3 masterAxis; + + // get position of master + GetMasterPosition( masterOrigin, masterAxis ); + + // get master velocities + bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity ); + + // linear velocity relative to master plus master linear and angular velocity + linearVelocity = linearVelocity * masterAxis + masterLinearVelocity + + masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin ); + } +} + +/* +================ +idEntity::JoinTeam +================ +*/ +void idEntity::JoinTeam( idEntity *teammember ) { + idEntity *ent; + idEntity *master; + idEntity *prev; + idEntity *next; + + // if we're already on a team, quit it so we can join this one + if ( teamMaster && ( teamMaster != this ) ) { + QuitTeam(); + } + + assert( teammember ); + + if ( teammember == this ) { + teamMaster = this; + return; + } + + // check if our new team mate is already on a team + master = teammember->teamMaster; + if ( !master ) { + // he's not on a team, so he's the new teamMaster + master = teammember; + teammember->teamMaster = teammember; + teammember->teamChain = this; + + // make anyone who's bound to me part of the new team + for( ent = teamChain; ent != NULL; ent = ent->teamChain ) { + ent->teamMaster = master; + } + } else { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->teamChain; + if ( bindMaster ) { + // if we have a bindMaster, join after any entities bound to the entity + // we're joining + while( next && next->IsBoundTo( teammember ) ) { + prev = next; + next = next->teamChain; + } + } else { + // if we're not bound to someone, then put us at the end of the team + while( next ) { + prev = next; + next = next->teamChain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamChain->teamMaster = master; + } + + prev->teamChain = this; + ent->teamChain = next; + } + + teamMaster = master; + + // reorder the active entity list + gameLocal.sortTeamMasters = true; +} + +/* +================ +idEntity::QuitTeam +================ +*/ +void idEntity::QuitTeam( void ) { + idEntity *ent; + + if ( !teamMaster ) { + return; + } + + // check if I'm the teamMaster + if ( teamMaster == this ) { + // do we have more than one teammate? + if ( !teamChain->teamChain ) { + // no, break up the team + teamChain->teamMaster = NULL; + } else { + // yes, so make the first teammate the teamMaster + for( ent = teamChain; ent; ent = ent->teamChain ) { + ent->teamMaster = teamChain; + } + } + } else { + assert( teamMaster ); + assert( teamMaster->teamChain ); + + // find the previous member of the teamChain + ent = teamMaster; + while( ent->teamChain != this ) { + assert( ent->teamChain ); // this should never happen + ent = ent->teamChain; + } + + // remove this from the teamChain + ent->teamChain = teamChain; + + // if no one is left on the team, break it up + if ( !teamMaster->teamChain ) { + teamMaster->teamMaster = NULL; + } + } + + teamMaster = NULL; + teamChain = NULL; +} + +/*********************************************************************** + + Physics. + +***********************************************************************/ + +/* +================ +idEntity::InitDefaultPhysics +================ +*/ +void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( temp ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); +// RAVEN BEGIN +// bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); +// RAVEN END + } else { + trm.SetupBox( bounds ); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { +// RAVEN BEGIN +// jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( this ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; +} + +/* +================ +idEntity::SetPhysics +================ +*/ +void idEntity::SetPhysics( idPhysics *phys ) { + // clear any contacts the current physics object has + if ( physics ) { + physics->ClearContacts(); + } + // set new physics object or set the default physics if NULL + if ( phys != NULL ) { + defaultPhysicsObj.SetClipModel( NULL, 1.0f ); + physics = phys; + physics->Activate(); + } else { + physics = &defaultPhysicsObj; + } + physics->UpdateTime( gameLocal.time ); + physics->SetMaster( bindMaster, fl.bindOrientated ); +} + +/* +================ +idEntity::RestorePhysics +================ +*/ +void idEntity::RestorePhysics( idPhysics *phys ) { + assert( phys != NULL ); + // restore physics pointer + physics = phys; +} + +/* +================ +idEntity::RunPhysics +================ +*/ +bool idEntity::RunPhysics( void ) { + int i, reachedTime, startTime, endTime; + idEntity * part, *blockedPart, *blockingEntity = NULL; + trace_t results; + bool moved; + + moved = false; + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PHYSICS); +// RAVEN END + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } +// RAVEN BEGIN +// kfuller: we want to be able to debug draw the bbox regardless + physics->DebugDraw(); +// RAVEN END + return false; + } + + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics +// RAVEN BEGIN +// ddynerman: optional pre-prediction + moved = part->physics->Evaluate( endTime - startTime + part->predictTime, endTime ); + part->predictTime = 0; +// RAVEN END + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // cdr: Obstacle Avoidance + if (ai_useRVMasterMove.GetBool() && moved && fl.isAIObstacle) { + AI_EntityMoved(this); + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + + if ( gameLocal.isClient ) { + return false; + } + + // if the master pusher has a "blocked" function, call it + Signal( SIG_BLOCKED ); + ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity ); + // call the blocked function on the blocked part + blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity ); + return false; + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->physics->SetPushed( endTime - startTime ); + } + + if ( gameLocal.isClient ) { + return true; + } + + // post reached event if the current time is at or past the end point of the motion + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + reachedTime = part->physics->GetLinearEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedPos ); + } + reachedTime = part->physics->GetAngularEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedAng ); + } + } + } + + return true; +} + +/* +================ +idEntity::UpdateFromPhysics +================ +*/ +void idEntity::UpdateFromPhysics( bool moveBack ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idActor::GetClassType() ) ) { +// RAVEN END + idActor *actor = static_cast( this ); + + // set master delta angles for actors + if ( GetBindMaster() ) { + idAngles delta = actor->GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= static_cast(physics)->GetMasterDeltaYaw(); + } else { + delta.yaw += static_cast(physics)->GetMasterDeltaYaw(); + } + actor->SetDeltaViewAngles( delta ); + } + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetOrigin +================ +*/ +void idEntity::SetOrigin( const idVec3 &org ) { + + GetPhysics()->SetOrigin( org ); + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAxis +================ +*/ +void idEntity::SetAxis( const idMat3 &axis ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + static_cast(this)->viewAxis = axis; + } else { + GetPhysics()->SetAxis( axis ); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAngles +================ +*/ +void idEntity::SetAngles( const idAngles &ang ) { + SetAxis( ang.ToMat3() ); +} + +/* +================ +idEntity::GetFloorPos +================ +*/ +bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const { + trace_t result; + + if ( !GetPhysics()->HasGroundContacts() ) { + GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL ); + if ( result.fraction < 1.0f ) { + floorpos = result.endpos; + return true; + } else { + floorpos = GetPhysics()->GetOrigin(); + return false; + } + } else { + floorpos = GetPhysics()->GetOrigin(); + return true; + } +} + +/* +================ +idEntity::GetPhysicsToVisualTransform +================ +*/ +bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +idEntity::GetPhysicsToSoundTransform +================ +*/ +bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + // by default play the sound at the center of the bounding box of the first clip model + if ( GetPhysics()->GetNumClipModels() > 0 ) { + origin = GetPhysics()->GetBounds().GetCenter(); + axis.Identity(); + return true; + } + return false; +} + +/* +================ +idEntity::Collide +================ +*/ +bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + // this entity collides with collision.c.entityNum + return false; +} + +/* +================ +idEntity::GetImpactInfo +================ +*/ +void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +idEntity::ApplyImpulse +================ +*/ +void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( SkipImpulse(ent, id) ) { + return; + } + + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +idEntity::AddForce +================ +*/ +void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +idEntity::ActivatePhysics +================ +*/ +void idEntity::ActivatePhysics( idEntity *ent ) { + GetPhysics()->Activate(); +} + +/* +================ +idEntity::IsAtRest +================ +*/ +bool idEntity::IsAtRest( void ) const { + return GetPhysics()->IsAtRest(); +} + +/* +================ +idEntity::GetRestStartTime +================ +*/ +int idEntity::GetRestStartTime( void ) const { + return GetPhysics()->GetRestStartTime(); +} + +/* +================ +idEntity::AddContactEntity +================ +*/ +void idEntity::AddContactEntity( idEntity *ent ) { + GetPhysics()->AddContactEntity( ent ); +} + +/* +================ +idEntity::RemoveContactEntity +================ +*/ +void idEntity::RemoveContactEntity( idEntity *ent ) { +// RAVEN BEGIN + if( GetPhysics() ) { + + GetPhysics()->RemoveContactEntity( ent ); + } +// RAVEN END +} + + + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idEntity::CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +// RAVEN BEGIN +// bdube: added ignore entity +bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt ) const { +// RAVEN END + idVec3 dest; + trace_t tr; + idVec3 midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5; + + dest = midpoint; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + // this should probably check in the plane of projection, rather than in world coordinate + dest = midpoint; + dest[0] += 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN ENE + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] += 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN EN + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] += 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] -= 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + return false; +} + +/* +================ +idEntity::DamageFeedback + +callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // implemented in subclasses +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +============ +*/ +void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + return; + } + + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + //jshepard: this is kinda important, no? + health -= damage; + + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + +/* +============ +idEntity::SkipImpulse +============ +*/ +// RAVEN BEGIN +// abahr: push stuff +bool idEntity::SkipImpulse( idEntity *ent, int id ) { + return false;//ent == this; +} + +/* +============ +idEntity::ApplyImpulse +============ +*/ +void idEntity::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ) { + ApplyImpulse( ent, id, point, dir * damageDef->GetFloat("push", "5000") ); +} +// RAVEN END + +/* +================ +idEntity::AddDamageEffect +================ +*/ +void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + const char *sound, *decal, *key; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); +// RAVEN BEGIN +// bdube: impact_blood is now in the damage def + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { +// RAVEN END + return; + } + + const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + if ( g_decals.GetBool() ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + } + } +} + +/* +================ +idEntity::CanPlayImpactEffect +================ +*/ +bool idEntity::CanPlayImpactEffect ( idEntity* owner, idEntity* ent ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsTeamGame() && !cvarSystem->GetCVarBool("si_teamDamage") && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && ((idPlayer*)owner)->team == ((idPlayer*)ent)->team ) { + return false; + } + + // default to blood + return true; + } else { + idActor* actorOwner; + idAI* aiEnt; + actorOwner = dynamic_cast( owner ); + + if ( ent->IsType ( idAFAttachment::GetClassType ( ) ) ) { + aiEnt = dynamic_cast( static_cast( ent )->GetBody ( ) ); + } else { + aiEnt = dynamic_cast( ent ); + } + + if ( !actorOwner || !aiEnt ) { + return true; + } + + return (actorOwner->team != aiEnt->team); + } +} + +/* +============ +idEntity::Pain + +Called whenever an entity recieves damage. Returns whether the entity responds to the pain. +This is a virtual function that subclasses are expected to implement. +============ +*/ +bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return false; +} + +/* +============ +idEntity::Killed + +Called whenever an entity's health is reduced to 0 or less. +This is a virtual function that subclasses are expected to implement. +============ +*/ +void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +} + +/*********************************************************************** + + Script functions + +***********************************************************************/ + +/* +================ +idEntity::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idEntity::ShouldConstructScriptObjectAtSpawn( void ) const { + return true; +} + +/* +================ +idEntity::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idEntity::ConstructScriptObject( void ) { + idThread *thread; + const function_t *constructor; + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, constructor, true ); + thread->DelayedStart( 0 ); + } else { + thread = NULL; + } + + // clear out the object's memory + scriptObject.ClearObject(); + + return thread; +} + +/* +================ +idEntity::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idEntity::DeconstructScriptObject( void ) { + idThread *thread; + const function_t *destructor; + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and be destroyed +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_TEMPORARY); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, destructor, true ); + thread->Execute(); + delete thread; + } +} + +/* +================ +idEntity::HasSignal +================ +*/ +bool idEntity::HasSignal( signalNum_t signalnum ) const { + if ( !signals ) { + return false; + } + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + return ( signals->signal[ signalnum ].Num() > 0 ); +} + +/* +================ +idEntity::SetSignal +================ +*/ +void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) { + int i; + int num; + signal_t sig; + int threadnum; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + signals = new signalList_t; + } + + assert( thread ); + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ][ i ].function = function; + return; + } + } + + if ( num >= MAX_SIGNAL_THREADS ) { + thread->Error( "Exceeded maximum number of signals per object" ); + } + + sig.threadnum = threadnum; + sig.function = function; + signals->signal[ signalnum ].Append( sig ); +} + +/* +================ +idEntity::ClearSignal +================ +*/ +void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) { + assert( thread ); + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + signals->signal[ signalnum ].Clear(); +} + +/* +================ +idEntity::ClearSignalThread +================ +*/ +void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) { + int i; + int num; + int threadnum; + + assert( thread ); + + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ].RemoveIndex( i ); + return; + } + } +} + +/* +================ +idEntity::Signal +================ +*/ +void idEntity::Signal( signalNum_t signalnum ) { + int i; + int num; + signal_t sigs[ MAX_SIGNAL_THREADS ]; + idThread *thread; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + return; + } + + // we copy the signal list since each thread has the potential + // to end any of the threads in the list. By copying the list + // we don't have to worry about the list changing as we're + // processing it. + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + sigs[ i ] = signals->signal[ signalnum ][ i ]; + } + + // clear out the signal list so that we don't get into an infinite loop + signals->signal[ signalnum ].Clear(); + + for( i = 0; i < num; i++ ) { + thread = idThread::GetThread( sigs[ i ].threadnum ); + if ( thread ) { + thread->CallFunction( this, sigs[ i ].function, true ); + thread->Execute(); + } + } +} + +/* +================ +idEntity::SignalEvent +================ +*/ +void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) { + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + Signal( signalnum ); +} + +/*********************************************************************** + + Guis. + +***********************************************************************/ + + +/* +================ +idEntity::TriggerGuis +================ +*/ +void idEntity::TriggerGuis( void ) { + int i; + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->Trigger( gameLocal.time ); + } + } +} + +/* +================ +idEntity::HandleGuiCommands +================ +*/ +bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) { + idEntity *targetEnt; + bool ret = false; + if ( entityGui && cmds && *cmds ) { + idLexer src; + idToken token, token2, token3, token4; + src.LoadMemory( cmds, strlen( cmds ), "guiCommands" ); + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + return ret; + } + + if ( token == ";" ) { + continue; + } + + if ( token.Icmp( "activate" ) == 0 ) { + bool targets = true; + if ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + } else { + targets = false; + } + } + + if ( targets ) { + entityGui->ActivateTargets( this ); + } else { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + } + } + + entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f; + continue; + } + + + if ( token.Icmp( "runScript" ) == 0 ) { + if ( src.ReadToken( &token2 ) ) { + while( src.CheckTokenString( "::" ) ) { + idToken token3; + if ( !src.ReadToken( &token3 ) ) { + gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() ); + } + token2 += "::" + token3; + } + } +// RAVEN BEGIN +// abahr: allow parms to be passed in +// For some reason the semi colon is used as a delimeter so we need the above code + rvScriptFuncUtility utility; + if( utility.Init(token2) > SFU_ERROR ) { + utility.InsertEntity( entityGui, 0 ); + utility.CallFunc( &entityGui->spawnArgs ); + } +// RAVEN END + continue; + } + + if ( token.Icmp("play") == 0 ) { + if ( src.ReadToken( &token2 ) ) { + const idSoundShader *shader = declManager->FindSound(token2); + entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + continue; + } + + if ( token.Icmp( "setkeyval" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->spawnArgs.Set( token3, token4 ); + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + } + } + continue; + } + + if ( token.Icmp( "setshaderparm" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) { + entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) ); + entityGui->UpdateVisuals(); + } + continue; + } + + if ( token.Icmp("close") == 0 ) { + ret = true; + continue; + } + + // handy for debugging GUI stuff + if ( !token.Icmp( "print" ) ) { + idStr msg; + while ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + break; + } + msg += token2.c_str(); + } + common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() ); + continue; + } + + // if we get to this point we don't know how to handle it + src.UnreadToken(&token); + if ( !HandleSingleGuiCommand( entityGui, &src ) ) { + // not handled there see if entity or any of its targets can handle it + // this will only work for one target atm + if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) { + continue; + } + + int c = entityGui->targets.Num(); + int i; + for ( i = 0; i < c; i++) { + targetEnt = entityGui->targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) { + break; + } + } + + if ( i == c ) { + // not handled + common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() ); + src.ReadToken( &token ); + } + } + + } + } + return ret; +} + +/* +================ +idEntity::HandleSingleGuiCommand +================ +*/ +bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + return false; +} + +/*********************************************************************** + + Targets + +***********************************************************************/ + +/* +=============== +idEntity::FindTargets + +We have to wait until all entities are spawned +Used to build lists of targets after the entity is spawned. Since not all entities +have been spawned when the entity is created at map load time, we have to wait +=============== +*/ +void idEntity::FindTargets( void ) { + int i; + + // targets can be a list of multiple names + gameLocal.GetTargets( spawnArgs, targets, "target" ); + + // ensure that we don't target ourselves since that could cause an infinite loop when activating entities + for( i = 0; i < targets.Num(); i++ ) { + if ( targets[ i ].GetEntity() == this ) { + gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() ); + } + } +} + +/* +================ +idEntity::RemoveNullTargets +================ +*/ +void idEntity::RemoveNullTargets( void ) { + int i; + + for( i = targets.Num() - 1; i >= 0; i-- ) { + if ( !targets[ i ].GetEntity() ) { + targets.RemoveIndex( i ); + } + } +} + +/* +============================== +idEntity::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idEntity::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->renderEntity.gui[ j ] ) { + ent->renderEntity.gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +// RAVEN BEGIN +// twhitaker: added (meant to be used from script) +/* +================ +idEntity::AppendTarget +================ +*/ +int idEntity::AppendTarget( idEntity *appendMe ) { + + int index = -1; + // silently fail if they pass in null + if ( appendMe ) { + index = targets.Append( appendMe ); + RemoveNullTargets(); + } + return index; +} +/* +================ +idEntity::RemoveTarget +================ +*/ +void idEntity::RemoveTarget( idEntity *removeMe ) { + + targets.Remove( removeMe ); + RemoveNullTargets(); +} +/* +================ +idEntity::RemoveTargets +================ +*/ +void idEntity::RemoveTargets( bool destroyContents ) { + if( destroyContents ) { + targets.RemoveContents( true ); + } else { + targets.Clear(); + } +} + +// jshepard: added +/* +================ +idEntity::UnbindTargets +================ +*/ +void idEntity::UnbindTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + ent->Unbind(); + + } +} + +// bdube: added +/* +================ +idEntity::Event_SetContents +================ +*/ +void idEntity::Event_SetContents( int contents ) { + GetPhysics()->SetContents( contents ); +} + +/* +================ +idEntity::Event_GetLastBlocker +================ +*/ +void idEntity::Event_GetLastBlocker(idThread *thread) { + int whichEntNum = GetLastBlocker(); + + if (whichEntNum < 0 || whichEntNum == ENTITYNUM_WORLD) { + thread->ReturnEntity(this); + return; + } + thread->ReturnEntity(gameLocal.entities[whichEntNum]); +} + +/* +================ +idEntity::ShowSurface +================ +*/ +void idEntity::ShowSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask &= (~renderEntity.hModel->GetSurfaceMask ( surface )); +} + +/* +================ +idEntity::Event_ShowSurface +================ +*/ +void idEntity::Event_ShowSurface ( const char* surface ) { + ShowSurface ( surface ); +} + +/* +================ +idEntity::HideSurface +================ +*/ +void idEntity::HideSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask |= renderEntity.hModel->GetSurfaceMask ( surface ) ; +} + +/* +================ +idEntity::Event_HideSurface +================ +*/ +void idEntity::Event_HideSurface ( const char* surface ) { + HideSurface ( surface ); +} + +/* +================ +idEntity::Event_GuiEvent +================ +*/ +void idEntity::Event_GuiEvent ( const char* eventName ) { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( eventName ); + } +} + +/* +================ +idEntity::Event_clearSkin +================ +*/ +void idEntity::Event_ClearSkin( void ) { + ClearSkin(); +} + +/* +================ +idEntity::Event_StopAllEffects +================ +*/ +void idEntity::Event_StopAllEffects ( void ) { + StopAllEffects ( ); +} + +/* +================ +idEntity::Event_GetHealth +================ +*/ +void idEntity::Event_GetHealth ( void ) { + idThread::ReturnFloat( health ); +} + +// jscott: +/* +================ +idEntity::Event_PlaybackCallback +================ +*/ +void idEntity::Event_PlaybackCallback ( int type, int changed, int impulse ) { + common->Printf( "Playback callback type %d - %d/%d\n", type, changed, impulse ); +} + +// nmckenzie: Check who we're bound to. +/* +================ +idEntity::Event_GetBindMaster +================ +*/ + +void idEntity::Event_GetBindMaster ( void ) { + idThread::ReturnEntity( GetBindMaster() ); +} + +/* +================ +idEntity::Event_ApplyImpulse +================ +*/ + +void idEntity::Event_ApplyImpulse( idEntity *source, const idVec3 &point, const idVec3 &impulse ){ + ApplyImpulse( source, 0, point, impulse ); +} + +/* +================ +idEntity::Event_PlayEffect +================ +*/ +void idEntity::Event_PlayEffect( const char *effectName, const char* jointName, bool loop ) { + jointHandle_t joint; + joint = GetAnimator ( ) ? GetAnimator()->GetJointHandle ( jointName ) : INVALID_JOINT; + if ( joint != INVALID_JOINT ) { + PlayEffect ( effectName, joint, loop ); + } else { + PlayEffect ( effectName, renderEntity.origin, renderEntity.axis, loop ); + } +} + +/* +================ +idEntity::Event_StopEffect +================ +*/ +void idEntity::Event_StopEffect( const char *effectName ) { + StopEffect ( effectName ); +} + +// END RAVEN + +/*********************************************************************** + + Misc. + +***********************************************************************/ + +/* +================ +idEntity::Teleport +================ +*/ +void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( angles.ToMat3() ); + + UpdateVisuals(); +} + +/* +============ +idEntity::TouchTriggers + + Activate all trigger entities touched at the current position. + + Optionally only activate triggers of ownerType +============ +*/ +bool idEntity::TouchTriggers( const idTypeInfo* ownerType ) const { + int i, numClipModels, numEntities; + idClipModel * cm; + idClipModel * clipModels[ MAX_GENTITIES ]; + idEntity * ent; + trace_t trace; + + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = GetPhysics()->GetOrigin(); + trace.endAxis = GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES ); +// RAVEN END + numEntities = 0; + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + // don't touch it if we're the owner + if ( cm->GetOwner() == this ) { + continue; + } + + ent = cm->GetEntity(); + + if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } + + if( ownerType && !ent->IsType( *ownerType ) ) { + continue; + } + +// RAVEN BEGIN +// abahr: needed so tram car can has collision model and touch triggers + bool useSimpleClip = spawnArgs.GetBool("useSimpleTriggerClip"); + if ( !useSimpleClip && !GetPhysics()->ClipContents( cm ) ) { +// RAVEN END + continue; + } + + numEntities++; + + trace.c.contents = cm->GetContents(); + trace.c.entityNum = cm->GetEntity()->entityNumber; + trace.c.id = cm->GetId(); + + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, this, &trace ); + + if ( !gameLocal.entities[ entityNumber ] ) { + gameLocal.Printf( "entity was removed while touching triggers\n" ); + return true; + } + } + + return ( numEntities != 0 ); +} + +/* +================ +idEntity::GetSpline +================ +*/ +idCurve_Spline *idEntity::GetSpline( void ) const { + int i, numPoints, t; + const idKeyValue *kv; + idLexer lex; + idVec3 v; + idCurve_Spline *spline; + const char *curveTag = "curve_"; + + kv = spawnArgs.MatchPrefix( curveTag ); + if ( !kv ) { + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) ); + if ( str.Icmp( "CatmullRomSpline" ) == 0 ) { + spline = new idCurve_CatmullRomSpline(); + } else if ( str.Icmp( "nubs" ) == 0 ) { + spline = new idCurve_NonUniformBSpline(); + } else if ( str.Icmp( "nurbs" ) == 0 ) { + spline = new idCurve_NURBS(); + } else { + spline = new idCurve_BSpline(); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + spline->SetBoundaryType( idCurve_Spline::BT_CLAMPED ); + + lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag ); + numPoints = lex.ParseInt(); + lex.ExpectTokenString( "(" ); + for ( t = i = 0; i < numPoints; i++, t += 100 ) { + v.x = lex.ParseFloat(); + v.y = lex.ParseFloat(); + v.z = lex.ParseFloat(); + spline->AddValue( t, v ); + } + lex.ExpectTokenString( ")" ); + + return spline; +} + +/* +=============== +idEntity::ShowEditingDialog +=============== +*/ +void idEntity::ShowEditingDialog( void ) { +} + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +================ +idEntity::Event_GetName +================ +*/ +void idEntity::Event_GetName( void ) { + idThread::ReturnString( name.c_str() ); +} + +/* +================ +idEntity::Event_SetName +================ +*/ +void idEntity::Event_SetName( const char *newname ) { + SetName( newname ); +} + +/* +=============== +idEntity::Event_FindTargets +=============== +*/ +void idEntity::Event_FindTargets( void ) { + FindTargets(); +} + +/* +============ +idEntity::Event_ActivateTargets + +Activates any entities targeted by this entity. Mainly used as an +event to delay activating targets. +============ +*/ +void idEntity::Event_ActivateTargets( idEntity *activator ) { + ActivateTargets( activator ); +} + +// RAVEN BEGIN +// jshepard: added +/* +============ +idEntity::Event_UnbindTargets + +Unbinds all targets of this entity. Useful to make held or clamped items +drop when shot, and for breakable walls. +============ +*/ +void idEntity::Event_UnbindTargets( idEntity *activator ) { + UnbindTargets( activator ); +} + +// RAVEN END + + +/* +================ +idEntity::Event_NumTargets +================ +*/ +void idEntity::Event_NumTargets( void ) { + idThread::ReturnFloat( targets.Num() ); +} + +/* +================ +idEntity::Event_GetTarget +================ +*/ +void idEntity::Event_GetTarget( float index ) { + int i; + + i = ( int )index; + if ( ( i < 0 ) || i >= targets.Num() ) { + idThread::ReturnEntity( NULL ); + } else { + idThread::ReturnEntity( targets[ i ].GetEntity() ); + } +} + +/* +================ +idEntity::Event_RandomTarget +================ +*/ +void idEntity::Event_RandomTarget( const char *ignore ) { + int num; + idEntity *ent; + int i; + int ignoreNum; + + RemoveNullTargets(); + if ( !targets.Num() ) { + idThread::ReturnEntity( NULL ); + return; + } + + ignoreNum = -1; + if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ( ent->name == ignore ) ) { + ignoreNum = i; + break; + } + } + } + + if ( ignoreNum >= 0 ) { + num = gameLocal.random.RandomInt( targets.Num() - 1 ); + if ( num >= ignoreNum ) { + num++; + } + } else { + num = gameLocal.random.RandomInt( targets.Num() ); + } + + ent = targets[ num ].GetEntity(); + idThread::ReturnEntity( ent ); +} + +// RAVEN BEGIN +// abahr: so we can call this from script +/* +================ +idEntity::Event_RemoveNullTargets +================ +*/ +void idEntity::Event_RemoveNullTargets() { + RemoveNullTargets(); +} + +// twhitaker: So targets can be added from script +/* +================ +idEntity::Event_AppendTarget +================ +*/ +void idEntity::Event_AppendTarget( idEntity *appendMe ) { + idThread::ReturnFloat( AppendTarget( appendMe ) ); +} + +/* +================ +idEntity::Event_RemoveTarget +================ +*/ +void idEntity::Event_RemoveTarget( idEntity *removeMe ) { + RemoveTarget( removeMe ); +} + +/* +================ +idEntity::Event_ClearTargetList +================ +*/ +void idEntity::Event_ClearTargetList( float destroyContents ) { + RemoveTargets( destroyContents != 0.0f ); +} + +/* +================ +idEntity::Event_MatchPrefix +================ +*/ +void idEntity::Event_MatchPrefix( const char *prefix, const char* previousKey ) { + const idKeyValue* kv = (previousKey[0]) ? spawnArgs.FindKey(previousKey) : NULL; + + kv = spawnArgs.MatchPrefix( prefix, kv ); + if( !kv || !kv->GetValue() ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( kv->GetKey() ); +} + +/* +================ +idEntity::Event_IsA +================ +*/ +void idEntity::Event_IsA( const char* entityDefName ) { + const idDict* dict = gameLocal.FindEntityDefDict( entityDefName ); + if( !dict ) { + idThread::ReturnFloat( false ); + return; + } + + idTypeInfo* info = idClass::GetClass( dict->GetString("spawnclass") ); + if( !info ) { + idThread::ReturnFloat( false ); + return; + } + + idThread::ReturnFloat( IsType(*info) ); +} + +/* +================ +idEntity::Event_IsSameTypeAs +================ +*/ +void idEntity::Event_IsSameTypeAs( const idEntity* ent ) { + assert( ent ); + + idThread::ReturnFloat( IsType(ent->Type) ); +} + +// mekberg: allow sethealth on all entities. +// jshepard: removed clamping +/* +================ +idEntity::Event_SetHealth +================ +*/ +void idEntity::Event_SetHealth( float newHealth ) { + health = newHealth; +} +// RAVEN END + +/* +================ +idEntity::Event_BindToJoint +================ +*/ +void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) { + BindToJoint( master, jointname, ( orientated != 0.0f ) ); +} + +/* +================ +idEntity::Event_RemoveBinds +================ +*/ +void idEntity::Event_RemoveBinds( void ) { + RemoveBinds(); +} + +/* +================ +idEntity::Event_Bind +================ +*/ +void idEntity::Event_Bind( idEntity *master ) { + Bind( master, true ); +} + +/* +================ +idEntity::Event_BindPosition +================ +*/ +void idEntity::Event_BindPosition( idEntity *master ) { + Bind( master, false ); +} + +/* +================ +idEntity::Event_Unbind +================ +*/ +void idEntity::Event_Unbind( void ) { + Unbind(); +} + +/* +================ +idEntity::Event_SpawnBind +================ +*/ +void idEntity::Event_SpawnBind( void ) { + idEntity *parent; + const char *bind, *joint, *bindanim; + jointHandle_t bindJoint; + bool bindOrientated; + int id; + const idAnim *anim; + int animNum; + idAnimator *parentAnimator; + + if ( spawnArgs.GetString( "bind", "", &bind ) ) { + if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) { + //FIXME: Completely unneccessary since the worldspawn is called "world" + parent = gameLocal.world; + } else { + parent = gameLocal.FindEntity( bind ); + } + bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" ); + if ( parent ) { + // bind to a joint of the skeletal model of the parent + if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) { + parentAnimator = parent->GetAnimator(); + if ( !parentAnimator ) { + gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() ); + } + bindJoint = parentAnimator->GetJointHandle( joint ); + if ( bindJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() ); + } + + // bind it relative to a specific anim + if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) { + animNum = parentAnimator->GetAnim( bindanim ); + if ( !animNum ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + anim = parentAnimator->GetAnim( animNum ); + if ( !anim ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + + // make sure parent's render origin has been set + parent->UpdateModelTransform(); + + //FIXME: need a BindToJoint that accepts a joint position + parentAnimator->CreateFrame( gameLocal.time, true ); + idJointMat *frame = parent->renderEntity.joints; + gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() ); + BindToJoint( parent, joint, bindOrientated ); + parentAnimator->ForceUpdate(); + } else { + BindToJoint( parent, joint, bindOrientated ); + } + } + // bind to a body of the physics object of the parent + else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) { + BindToBody( parent, id, bindOrientated ); + } + // bind to the parent + else { + Bind( parent, bindOrientated ); + } + } + } +} + +/* +================ +idEntity::Event_SetOwner +================ +*/ +void idEntity::Event_SetOwner( idEntity *owner ) { + int i; + + for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) { + GetPhysics()->GetClipModel( i )->SetOwner( owner ); + } +} + +/* +================ +idEntity::Event_SetModel +================ +*/ +void idEntity::Event_SetModel( const char *modelname ) { + SetModel( modelname ); +} + +/* +================ +idEntity::Event_SetSkin +================ +*/ +void idEntity::Event_SetSkin( const char *skinname ) { + renderEntity.customSkin = declManager->FindSkin( skinname ); + UpdateVisuals(); +} + +/* +================ +idEntity::Event_GetShaderParm +================ +*/ +void idEntity::Event_GetShaderParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] ); +} + +/* +================ +idEntity::Event_SetShaderParm +================ +*/ +void idEntity::Event_SetShaderParm( int parmnum, float value ) { + SetShaderParm( parmnum, value ); +} + +/* +================ +idEntity::Event_SetShaderParms +================ +*/ +void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + UpdateVisuals(); +} + + +/* +================ +idEntity::Event_SetColor +================ +*/ +void idEntity::Event_SetColor( float red, float green, float blue ) { + SetColor( red, green, blue ); +} + +/* +================ +idEntity::Event_GetColor +================ +*/ +void idEntity::Event_GetColor( void ) { + idVec3 out; + + GetColor( out ); + idThread::ReturnVector( out ); +} + +/* +================ +idEntity::Event_IsHidden +================ +*/ +void idEntity::Event_IsHidden( void ) { + idThread::ReturnInt( fl.hidden ); +} + +/* +================ +idEntity::Event_Hide +================ +*/ +void idEntity::Event_Hide( void ) { + Hide(); +} + +/* +================ +idEntity::Event_Show +================ +*/ +void idEntity::Event_Show( void ) { + Show(); +} + +/* +================ +idEntity::Event_CacheSoundShader +================ +*/ +void idEntity::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idEntity::Event_StartSoundShader +================ +*/ +void idEntity::Event_StartSoundShader( const char *soundName, int channel ) { + int length; + + StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length ); + idThread::ReturnFloat( MS2SEC( length ) ); +} + +/* +================ +idEntity::Event_StopSound +================ +*/ +void idEntity::Event_StopSound( int channel, int netSync ) { + StopSound( channel, ( netSync != 0 ) ); +} + +/* +================ +idEntity::Event_StartSound +================ +*/ +void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) { + int time; + + StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +/* +================ +idEntity::Event_FadeSound +================ +*/ +void idEntity::Event_FadeSound( int channel, float to, float over ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->FadeSound( channel, to, over ); + } +// RAVEN END +} + +/* +================ +idEntity::Event_GetWorldOrigin +================ +*/ +void idEntity::Event_GetWorldOrigin( void ) { + idThread::ReturnVector( GetPhysics()->GetOrigin() ); +} + +/* +================ +idEntity::Event_SetWorldOrigin +================ +*/ +void idEntity::Event_SetWorldOrigin( idVec3 const &org ) { + idVec3 neworg = GetLocalCoordinates( org ); + SetOrigin( neworg ); +} + +/* +================ +idEntity::Event_SetOrigin +================ +*/ +void idEntity::Event_SetOrigin( idVec3 const &org ) { + SetOrigin( org ); +} + +/* +================ +idEntity::Event_GetOrigin +================ +*/ +void idEntity::Event_GetOrigin( void ) { + idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) ); +} + +/* +================ +idEntity::Event_SetAngles +================ +*/ +void idEntity::Event_SetAngles( idAngles const &ang ) { + SetAngles( ang ); +} + +/* +================ +idEntity::Event_GetAngles +================ +*/ +void idEntity::Event_GetAngles( void ) { + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idEntity::Event_SetLinearVelocity +================ +*/ +void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) { + GetPhysics()->SetLinearVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetLinearVelocity +================ +*/ +void idEntity::Event_GetLinearVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetLinearVelocity() ); +} + +/* +================ +idEntity::Event_SetAngularVelocity +================ +*/ +void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) { + GetPhysics()->SetAngularVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetAngularVelocity +================ +*/ +void idEntity::Event_GetAngularVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetAngularVelocity() ); +} + +/* +================ +idEntity::Event_SetSize +================ +*/ +void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) { + GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f ); +} + +/* +================ +idEntity::Event_GetSize +================ +*/ +void idEntity::Event_GetSize( void ) { + idBounds bounds; + + bounds = GetPhysics()->GetBounds(); + idThread::ReturnVector( bounds[1] - bounds[0] ); +} + +/* +================ +idEntity::Event_GetMins +================ +*/ +void idEntity::Event_GetMins( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[0] ); +} + +/* +================ +idEntity::Event_GetMaxs +================ +*/ +void idEntity::Event_GetMaxs( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[1] ); +} + +/* +================ +idEntity::Event_Touches +================ +*/ +void idEntity::Event_Touches( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + const idBounds &myBounds = GetPhysics()->GetAbsBounds(); + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) ); +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiParm( const char *key, const char *val ) { +// RAVEN BEGIN +// mekberg: added + idStr temp = key; + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) ) { + temp.Insert( "gui_", 0 ); + } + spawnArgs.Set( temp.c_str(), val ); +// RAVEN END + + renderEntity.gui[ i ]->SetStateString( key, val ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiFloat( const char *key, float f ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_GetNextKey +================ +*/ +void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) { + const idKeyValue *kv; + const idKeyValue *previous; + + if ( *lastMatch ) { + previous = spawnArgs.FindKey( lastMatch ); + } else { + previous = NULL; + } + + kv = spawnArgs.MatchPrefix( prefix, previous ); + if ( !kv ) { + idThread::ReturnString( "" ); + } else { + idThread::ReturnString( kv->GetKey() ); + } +} + +/* +================ +idEntity::Event_SetKey +================ +*/ +void idEntity::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +/* +================ +idEntity::Event_GetKey +================ +*/ +void idEntity::Event_GetKey( const char *key ) { + const char *value; + + spawnArgs.GetString( key, "", &value ); + idThread::ReturnString( value ); +} + +/* +================ +idEntity::Event_GetIntKey +================ +*/ +void idEntity::Event_GetIntKey( const char *key ) { + int value; + + spawnArgs.GetInt( key, "0", value ); + + // scripts only support floats + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetFloatKey +================ +*/ +void idEntity::Event_GetFloatKey( const char *key ) { + float value; + + spawnArgs.GetFloat( key, "0", value ); + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetVectorKey +================ +*/ +void idEntity::Event_GetVectorKey( const char *key ) { + idVec3 value; + + spawnArgs.GetVector( key, "0 0 0", value ); + idThread::ReturnVector( value ); +} + +/* +================ +idEntity::Event_GetEntityKey +================ +*/ +void idEntity::Event_GetEntityKey( const char *key ) { + idEntity *ent; + const char *entname; + + if ( !spawnArgs.GetString( key, NULL, &entname ) ) { + idThread::ReturnEntity( NULL ); + return; + } + + ent = gameLocal.FindEntity( entname ); + if ( !ent ) { + gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + } + + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_RestorePosition +================ +*/ +void idEntity::Event_RestorePosition( void ) { + idVec3 org; + idAngles angles; + idMat3 axis; + idEntity * part; + + spawnArgs.GetVector( "origin", "0 0 0", org ); + + // get the rotation matrix in either full form, or single angle form + if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { + angles = axis.ToAngles(); + } else { + angles[ 0 ] = 0; + angles[ 1 ] = spawnArgs.GetFloat( "angle" ); + angles[ 2 ] = 0; + } + + Teleport( org, angles, NULL ); + + for ( part = teamChain; part != NULL; part = part->teamChain ) { + if ( part->bindMaster != this ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + if ( static_cast(part->GetPhysics())->IsPusher() ) { + gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() ); + } + } else if ( part->GetPhysics()->IsType( idPhysics_AF::GetClassType() ) ) { +// RAVEN END + gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() ); + } + } +} + +/* +================ +idEntity::Event_UpdateCameraTarget +================ +*/ +void idEntity::Event_UpdateCameraTarget( void ) { + const char *target; + const idKeyValue *kv; + idVec3 dir; + + target = spawnArgs.GetString( "cameraTarget" ); + + cameraTarget = gameLocal.FindEntity( target ); + + if ( cameraTarget ) { + kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) { + dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin(); + dir.Normalize(); + cameraTarget->SetAxis( dir.ToMat3() ); +// RAVEN BEGIN +// rjohnson: if you have a func_cameraview pointing to an info_null via "cameratarget" and +// you have a func_static pointing to the func_cameraview via a "cameratarget" then +// the func_static evaluates 'target' to the func_cameraview and its target it the info null +// the SexAxis() is then applied to the the func_static rather than the func_cameraview +// SetAxis(dir.ToMat3()); +// RAVEN END + break; + } + kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv ); + } + } + UpdateVisuals(); +} + +/* +================ +idEntity::Event_DistanceTo +================ +*/ +void idEntity::Event_DistanceTo( idEntity *ent ) { + if ( !ent ) { + // just say it's really far away + idThread::ReturnFloat( MAX_WORLD_SIZE ); + } else { + float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast(); + idThread::ReturnFloat( dist ); + } +} + +/* +================ +idEntity::Event_DistanceToPoint +================ +*/ +void idEntity::Event_DistanceToPoint( const idVec3 &point ) { + float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast(); + idThread::ReturnFloat( dist ); +} + +/* +================ +idEntity::Event_StartFx +================ +*/ +void idEntity::Event_StartFx( const char *fx ) { +// RAVEN BEGIN +// bdube: not used +// idEntityFx::StartFx( fx, NULL, NULL, this, true ); +// RAVEN END +} + +/* +================ +idEntity::Event_WaitFrame +================ +*/ +void idEntity::Event_WaitFrame( void ) { + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->WaitFrame(); + } +} + +/* +===================== +idEntity::Event_Wait +===================== +*/ +void idEntity::Event_Wait( float time ) { + idThread *thread = idThread::CurrentThread(); + + if ( !thread ) { + gameLocal.Error( "Event 'wait' called from outside thread" ); + } + + thread->WaitSec( time ); +} + +/* +===================== +idEntity::Event_HasFunction +===================== +*/ +void idEntity::Event_HasFunction( const char *name ) { + const function_t *func; + + func = scriptObject.GetFunction( name ); + if ( func ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idEntity::Event_CallFunction +===================== +*/ +void idEntity::Event_CallFunction( const char *funcname ) { +// RAVEN BEGIN +// bdube: states + stateParms_t parms = {0}; + if ( ProcessState ( funcname, parms ) != SRESULT_ERROR ) { + return; + } + gameLocal.CallObjectFrameCommand ( this, funcname ); +// RAVEN END +} + +/* +================ +idEntity::Event_SetNeverDormant +================ +*/ +void idEntity::Event_SetNeverDormant( int enable ) { + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + +/*********************************************************************** + + Network + +***********************************************************************/ + +/* +================ +idEntity::ClientPredictionThink +================ +*/ +void idEntity::ClientPredictionThink( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::WriteBindToSnapshot +================ +*/ +void idEntity::WriteBindToSnapshot( idBitMsgDelta &msg ) const { + int bindInfo; + + if ( bindMaster ) { + bindInfo = bindMaster->entityNumber; + bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS; + if ( bindJoint != INVALID_JOINT ) { + bindInfo |= 1 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS ); + } else if ( bindBody != -1 ) { + bindInfo |= 2 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS ); + } + } else { + bindInfo = ENTITYNUM_NONE; + } + msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 ); +} + +/* +================ +idEntity::ReadBindFromSnapshot +================ +*/ +void idEntity::ReadBindFromSnapshot( const idBitMsgDelta &msg ) { + int bindInfo, bindEntityNum, bindPos; + bool bindOrientated; + idEntity *master; + + bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 ); + bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + + if ( bindEntityNum != ENTITYNUM_NONE ) { + master = gameLocal.entities[ bindEntityNum ]; + + bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1; + bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) ); + switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) { + case 1: { + BindToJoint( master, (jointHandle_t) bindPos, bindOrientated ); + break; + } + case 2: { + BindToBody( master, bindPos, bindOrientated ); + break; + } + default: { + Bind( master, bindOrientated ); + break; + } + } + } else if ( bindMaster ) { + Unbind(); + } +} + +/* +================ +idEntity::WriteColorToSnapshot +================ +*/ +void idEntity::WriteColorToSnapshot( idBitMsgDelta &msg ) const { + idVec4 color; + + color[0] = renderEntity.shaderParms[ SHADERPARM_RED ]; + color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; + msg.WriteLong( PackColor( color ) ); +} + +/* +================ +idEntity::ReadColorFromSnapshot +================ +*/ +void idEntity::ReadColorFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 color; + + UnpackColor( msg.ReadLong(), color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3]; +} + +/* +================ +idEntity::WriteGUIToSnapshot +================ +*/ +void idEntity::WriteGUIToSnapshot( idBitMsgDelta &msg ) const { + // no need to loop over MAX_RENDERENTITY_GUI at this time + if ( renderEntity.gui[ 0 ] ) { + msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) ); + } else { + msg.WriteByte( 0 ); + } +} + +/* +================ +idEntity::ReadGUIFromSnapshot +================ +*/ +void idEntity::ReadGUIFromSnapshot( const idBitMsgDelta &msg ) { + int state; + idUserInterface *gui; + state = msg.ReadByte( ); + gui = renderEntity.gui[ 0 ]; + if ( gui && state != mpGUIState ) { + mpGUIState = state; + gui->SetStateInt( "networkState", state ); + gui->HandleNamedEvent( "networkState" ); + } +} + +/* +================ +idEntity::WriteToSnapshot +================ +*/ +void idEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idEntity::ReadFromSnapshot +================ +*/ +void idEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +idEntity::ServerSendEvent + + Saved events are also sent to any client that connects late so all clients + always receive the events nomatter what time they join the game. + ================ + */ +void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ServerSendReliableMessageExcluding( excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ServerSendInstanceEvent +================ +*/ +void idEntity::ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isNewFrame ); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + gameLocal.ServerSendInstanceReliableMessageExcluding( this, excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ClientSendEvent +================ +*/ +void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isClient ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idEntity::ServerReceiveEvent +================ +*/ +bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case 0: { + } + default: { + return false; + } + } +} + +/* +================ +idEntity::ClientReceiveEvent +================ +*/ +bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + const idSoundShader *shader; + s_channelType channel; + + switch( event ) { + case EVENT_STARTSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + if ( time < gameLocal.realClientTime - 300 ) { + // too old, skip it + common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time ); + return true; + } + shader = static_cast< const idSoundShader* >( idGameLocal::ReadDecl( msg, DECL_SOUND ) ); + channel = (s_channelType)msg.ReadByte(); + StartSoundShader( shader, channel, 0, false, NULL ); + return true; + } + case EVENT_STOPSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + channel = (s_channelType)msg.ReadByte(); + StopSound( channel, false ); + return true; + } +// RAVEN BEGIN +// bdube: new events + case EVENT_PLAYEFFECT_JOINT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin; + rvClientEffect* clientEffect; + effectCategory_t category; + jointHandle_t jointHandle; + bool loop; + bool predictBit; + + // NOTE: doesn't actually happen in multiplayer + // the joint version of PlayEffect is triggered client side for CTF flags, but doesn't have a broadcast flag and is therefore not transmitted + // (and that's pretty much the only instance this is used) + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + jointHandle = ( jointHandle_t )msg.ReadLong(); + origin.x = msg.ReadFloat(); + origin.y = msg.ReadFloat(); + origin.z = msg.ReadFloat(); + category = ( effectCategory_t )msg.ReadByte(); + loop = ( msg.ReadBits ( 1 ) != 0 ); + predictBit = ( msg.ReadBits ( 1 ) != 0 ); + + if ( bse->CanPlayRateLimited( category ) && ( !predictBit || !g_predictedProjectiles.GetBool() ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin( vec3_origin ); + clientEffect->SetAxis( mat3_identity ); + clientEffect->Bind( this, jointHandle ); + + clientEffect->Play( time, loop, origin ); + } + return true; + } + + case EVENT_PLAYEFFECT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* clientEffect; + effectCategory_t category; + bool loop; + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + loop = ( msg.ReadBits( 1 ) != 0 ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( origin ); + clientEffect->SetAxis ( quat.ToMat3() ); + clientEffect->Bind ( this ); + + clientEffect->Play ( time, loop, origin2 ); + } + return true; + } +// RAVEN END + default: { + return false; + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idEntity::ClientStale +================ +*/ +bool idEntity::ClientStale( void ) { + FreeModelDef(); + UpdateVisuals(); + GetPhysics()->UnlinkClip(); + return false; +} + +/* +================ +idEntity::ClientUnstale +================ +*/ +void idEntity::ClientUnstale( void ) { +} + +/* +================ +idEntity::GetDamageEntity + +Returns the entity that should take damage in place of this entity. The default is the +entity itself. +================ +*/ +idEntity* idEntity::GetDamageEntity( void ) { + return forwardDamageEnt.IsValid() ? forwardDamageEnt.GetEntity() : this; +} + +// rjohnson: moved entity info out of idGameLocal into its own function +/* +================ +idEntity::DrawDebugEntityInfo +================ +*/ +void idEntity::DrawDebugEntityInfo( idBounds *viewBounds, idBounds *viewTextBounds, idVec4 *overrideColor ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idMat3 axis = player->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + // skip if the entity is very far away + if ( viewBounds && !viewBounds->IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + return; + } + + const idBounds &entBounds = GetPhysics()->GetAbsBounds(); + + if (overrideColor) { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( *overrideColor, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( *overrideColor, entBounds, vec3_origin ); + } + } else { + int contents = GetPhysics()->GetContents(); + if ( contents & CONTENTS_BODY ) { + gameRenderWorld->DebugBounds ( colorCyan, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_TRIGGER ) { + gameRenderWorld->DebugBounds( colorOrange, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_SOLID ) { + gameRenderWorld->DebugBounds( colorGreen, entBounds, vec3_origin ); + } else { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds, vec3_origin ); + } + } + } + + if ( !viewTextBounds || viewTextBounds->IntersectsBounds( entBounds ) ) { + gameRenderWorld->DrawText( name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + + if ( gameLocal.GetLocalPlayer() && this != gameLocal.GetLocalPlayer() && teamMaster != gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + } +} + +/* +===================== +idEntity::SetInstance +===================== +*/ +void idEntity::SetInstance( int newInstance ) { + instance = newInstance; + + if( gameLocal.isServer ) { + SetClipWorld( newInstance ); + } +} + +/* +===================== +idEntity::InstanceJoin +Gets called when the local player joins the same instance as this entity +===================== +*/ +void idEntity::InstanceJoin( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ); + + BecomeActive( TH_UPDATEVISUALS ); + Present(); +} + +/* +===================== +idEntity::InstanceLeave +Gets called when the local player leaves the same instance as this entity +===================== +*/ +void idEntity::InstanceLeave( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ); + + FreeLightDef(); + FreeModelDef(); + //RemoveClientEntities(); + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +===================== +idEntity::GetDebugInfo +===================== +*/ +void idEntity::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idClass::GetDebugInfo ( proc, userData ); + + proc ( "idEntity", "health", va("%d",health), userData ); + proc ( "idEntity", "name", name, userData ); + proc ( "idEntity", "entityNumber", va("%d",entityNumber), userData ); + proc ( "idEntity", "origin", renderEntity.origin.ToString ( ), userData ); + + proc ( "idEntity", "notarget", fl.notarget?"true":"false", userData ); + proc ( "idEntity", "takedamage", fl.takedamage?"true":"false", userData ); + proc ( "idEntity", "hidden", fl.hidden?"true":"false", userData ); + proc ( "idEntity", "bindOrientated",fl.bindOrientated?"true":"false", userData ); + proc ( "idEntity", "isDormant", fl.isDormant?"true":"false", userData ); + proc ( "idEntity", "neverDormant", fl.neverDormant?"true":"false", userData ); + proc ( "idEntity", "isAIObstacle", fl.isAIObstacle?"true":"false", userData ); + + proc ( "idEntity", "forwardDamageEnt",forwardDamageEnt.GetEntity() ? forwardDamageEnt.GetEntity()->GetName() : "", userData ); + + proc ( "idEntity", "bindMaster", bindMaster ? bindMaster->GetName() : "", userData ); + proc ( "idEntity", "bindJoint", va("%d",((int)bindJoint)), userData ); + proc ( "idEntity", "bindBody", va("%d",bindBody), userData ); + + proc ( "idEntity", "teamMaster", teamMaster ? teamMaster->GetName() : "", userData ); + proc ( "idEntity", "teamChain", teamChain ? teamChain->GetName() : "", userData ); +} + +// mwhitlock: memory profiling +/* +===================== +idEntity::Size() + +Returns memory size of an idEntity instance +===================== +*/ + +size_t idEntity::Size( void ) const +{ + // TODO: more crap needs to go here! + return sizeof (idEntity); +} +// RAVEN END + + + +/* +=============================================================================== + + idAnimatedEntity + +=============================================================================== +*/ + +const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' ); +const idEventDef EV_ClearAllJoints( "clearAllJoints" ); +const idEventDef EV_ClearJoint( "clearJoint", "d" ); +const idEventDef EV_SetJointPos( "setJointPos", "ddv" ); +const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); +const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); +const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); + + +// RAVEN BEGIN +// bdube: programmer controlled joint events +const idEventDef EV_SetJointAngularVelocity ( "setJointAngularVelocity", "sfffd" ); +const idEventDef EV_CollapseJoints ( "collapseJoints", "ss" ); +// jshepard: clear out all animations still running on the model +const idEventDef EV_ClearAnims( "clearAnims" ); + +// RAVEN END + +CLASS_DECLARATION( idEntity, idAnimatedEntity ) + EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) + EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) + EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint ) + EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos ) + EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) + EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) + EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) + +// RAVEEN BEGIN +// bdube: programmer controlled joint events + EVENT( EV_SetJointAngularVelocity, idAnimatedEntity::Event_SetJointAngularVelocity ) + EVENT( EV_CollapseJoints, idAnimatedEntity::Event_CollapseJoints ) +// RAVEN END +END_CLASS + +/* +================ +idAnimatedEntity::idAnimatedEntity +================ +*/ +idAnimatedEntity::idAnimatedEntity() { + animator.SetEntity( this ); + damageEffects = NULL; +} + +/* +================ +idAnimatedEntity::~idAnimatedEntity +================ +*/ +idAnimatedEntity::~idAnimatedEntity() { + damageEffect_t *de; + + for ( de = damageEffects; de; de = damageEffects ) { + damageEffects = de->next; + delete de; + } +} + +/* +================ +idAnimatedEntity::Save + +archives object for save game file +================ +*/ +void idAnimatedEntity::Save( idSaveGame *savefile ) const { + animator.Save( savefile ); + + // Wounds are very temporary, ignored at this time + //damageEffect_t *damageEffects; +} + +/* +================ +idAnimatedEntity::Restore + +unarchives object from save game file +================ +*/ +void idAnimatedEntity::Restore( idRestoreGame *savefile ) { + animator.Restore( savefile ); + + // check if the entity has an MD5 model + if ( animator.ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( modelDefHandle != -1 ) { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + } +} + +/* +================ +idAnimatedEntity::ClientPredictionThink +================ +*/ +void idAnimatedEntity::ClientPredictionThink( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::Think +================ +*/ +void idAnimatedEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::UpdateAnimation +================ +*/ +void idAnimatedEntity::UpdateAnimation( void ) { + // don't do animations if they're not enabled + if ( !( thinkFlags & TH_ANIMATE ) ) { + return; + } + + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + +// RAVEN BEGIN +// bgeisler: for triggered anims + // call any frame commands that have happened in the past frame + if ( !fl.hidden || fl.triggerAnim ) { + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } +// RAVEN END + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() && !fl.hidden ) { + gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +idAnimatedEntity::GetAnimator +================ +*/ +idAnimator *idAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +================ +idAnimatedEntity::SetModel +================ +*/ +void idAnimatedEntity::SetModel( const char *modelname ) { + FreeModelDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + idEntity::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + UpdateVisuals(); +} + +/* +===================== +idAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass(); + axis = renderEntity.axis; + return true; + } + + if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) { + return false; + } + + ConvertLocalToWorldTransform( offset, axis ); + return true; +} + +/* +============== +idAnimatedEntity::GetJointTransformForAnim +============== +*/ +bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const { + const idAnim *anim; + int numJoints; + idJointMat *frame; + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass() - GetPhysics()->GetOrigin(); + axis = renderEntity.axis; + return true; + } + + anim = animator.GetAnim( animNum ); + if ( !anim ) { + assert( 0 ); + return false; + } + + numJoints = animator.NumJoints(); + if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) { + assert( 0 ); + return false; + } + + frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) ); + gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() ); + + offset = frame[ jointHandle ].ToVec3(); + axis = frame[ jointHandle ].ToMat3(); + + return true; +} + +// RAVEN BEGIN +// ddynerman: removed/merged AddLocalDamageEffect() (redundant math) +/* +============== +idAnimatedEntity::AddDamageEffect + + Dammage effects track the animating impact position, spitting out particles. +============== +*/ +void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // ddynerman: note, on client the collision struct is incomplete. Only contains impact point and material + const char *splat, *decal, *key; + idVec3 dir; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { + return; + } + + if ( !spawnArgs.GetBool( "bleed" ) ) { + return; + } + + dir = velocity; + dir.Normalize(); + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( collision.c.point[0] ); + msg.WriteFloat( collision.c.point[1] ); + msg.WriteFloat( collision.c.point[2] ); + msg.WriteDir( dir, 24 ); + idGameLocal::WriteDecl( msg, def ); + idGameLocal::WriteDecl( msg, collision.c.material ); + ServerSendInstanceEvent( EVENT_ADD_DAMAGE_EFFECT, &msg, false, -1 ); + } + + if ( !g_decals.GetBool() ) { + return; + } + + if ( gameLocal.GetLocalPlayer() && GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; // no blood from other instances + } + + // blood splats are thrown onto nearby surfaces + splat = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_splat_%s", collision.c.material->GetMaterialType()->GetName() ); + splat = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !splat || !*splat ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + } + if ( splat && *splat ) { + //jshepard original 64.0f + // dluetscher: changed from 64. to 48. for performance reasons + gameLocal.BloodSplat( this, collision.c.point, dir, 48.0f, splat ); + } + + // can't see wounds on the player model in single player mode + if ( !( IsType( idPlayer::GetClassType() ) && !gameLocal.isMultiplayer ) ) { + //If this is a buddy marine, no wound decals until they're actually dead unless it's mp. + if ( gameLocal.isMultiplayer + || !IsType( idAI::GetClassType() ) + || this->health <= 0 + || ((idAI*)this)->team != AITEAM_MARINE ) { + // place a wound overlay on the model + decal = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_wound_%s", collision.c.material->GetMaterialType()->GetName() ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !decal || !*decal ) { + decal = spawnArgs.RandomPrefix( "mtr_wound", gameLocal.random ); + } + if ( decal && *decal ) { + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + if( IsType( idPlayer::GetClassType() ) ) { + ProjectHeadOverlay( collision.c.point, dir, 20.0f, decal ); + } + } + } + } +} +// RAVEN END + +/* +============== +idAnimatedEntity::GetDefaultSurfaceType +============== +*/ +int idAnimatedEntity::GetDefaultSurfaceType( void ) const { + return SURFTYPE_METAL; +} + +/* +================ +idAnimatedEntity::ClientReceiveEvent +================ +*/ +bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 origin, dir; + + switch( event ) { + case EVENT_ADD_DAMAGE_EFFECT: { + origin[0] = msg.ReadFloat(); + origin[1] = msg.ReadFloat(); + origin[2] = msg.ReadFloat(); + dir = msg.ReadDir( 24 ); + const idDeclEntityDef *damageDef = static_cast< const idDeclEntityDef* >( idGameLocal::ReadDecl( msg, DECL_ENTITYDEF ) ); + const idMaterial *collisionMaterial = static_cast< const idMaterial* >( idGameLocal::ReadDecl( msg, DECL_MATERIAL ) ); +// RAVEN BEGIN +// ddynerman: removed redundant AddLocalDamageEffect() + trace_t collision; + collision.c.point = origin; + collision.c.material = collisionMaterial; + AddDamageEffect( collision, dir, damageDef->GetName(), NULL ); +// RAVEN END + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// abahr: so we don't crash if UpdateModel is called from a destructor +/* +================ +idAnimatedEntity::UpdateRenderEntityCallback +================ +*/ +void idAnimatedEntity::UpdateRenderEntityCallback() { + // check if the entity has an MD5 model + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + } +} +// RAVEN END + +/* +================ +idAnimatedEntity::Event_GetJointHandle + +looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found. +================ +*/ +void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) { + jointHandle_t joint; + + joint = animator.GetJointHandle( jointname ); + idThread::ReturnInt( joint ); +} + +/* +================ +idAnimatedEntity::Event_ClearAllJoints + +removes any custom transforms on all joints +================ +*/ +void idAnimatedEntity::Event_ClearAllJoints( void ) { + animator.ClearAllJoints(); +} + +/* +================ +idAnimatedEntity::Event_ClearJoint + +removes any custom transforms on the specified joint +================ +*/ +void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) { + animator.ClearJoint( jointnum ); +} + +/* +================ +idAnimatedEntity::Event_ClearAnims + +Clears any animation running on the animated entity +================ +*/ +void idAnimatedEntity::Event_ClearAnims( void ) { + animator.Clear( ANIMCHANNEL_ALL, gameLocal.GetTime(), gameLocal.GetTime() ); +} + +/* +================ +idAnimatedEntity::Event_SetJointPos + +modifies the position of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + animator.SetJointPos( jointnum, transform_type, pos ); +} + +/* +================ +idAnimatedEntity::Event_SetJointAngle + +modifies the orientation of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +/* +================ +idAnimatedEntity::Event_GetJointPos + +returns the position of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idThread::ReturnVector( offset ); +} + +/* +================ +idAnimatedEntity::Event_GetJointAngle + +returns the orientation of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idAngles ang = axis.ToAngles(); + idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); + idThread::ReturnVector( vec ); +} + +// RAVEN BEGIN +// bdube: moved to idAnimatedEntity +/* +================ +idAnimatedEntity::Event_SetJointAngularVelocity +================ +*/ +void idAnimatedEntity::Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ) { + jointHandle_t joint = animator.GetJointHandle ( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + animator.SetJointAngularVelocity ( joint, idAngles(pitch,yaw,roll), gameLocal.time, blendTime ); +} + +/* +================ +idAnimatedEntity::Event_CollapseJoints +================ +*/ +void idAnimatedEntity::Event_CollapseJoints ( const char* jointnames, const char* collapseTo ) { + jointHandle_t collapseToJoint = animator.GetJointHandle ( collapseTo ); + if ( collapseToJoint == INVALID_JOINT ) { + return; + } + + animator.CollapseJoints ( jointnames, collapseToJoint ); +} +// RAVEN END + diff --git a/source/mpgame/Entity.h b/source/mpgame/Entity.h new file mode 100644 index 0000000..f46748a --- /dev/null +++ b/source/mpgame/Entity.h @@ -0,0 +1,849 @@ + +#ifndef __GAME_ENTITY_H__ +#define __GAME_ENTITY_H__ + +/* +=============================================================================== + + Game entity base class. + +=============================================================================== +*/ + +static const int DELAY_DORMANT_TIME = 3000; + +extern const idEventDef EV_PostSpawn; +extern const idEventDef EV_FindTargets; +extern const idEventDef EV_Touch; +extern const idEventDef EV_Use; +extern const idEventDef EV_Activate; +extern const idEventDef EV_ActivateTargets; +extern const idEventDef EV_Hide; +extern const idEventDef EV_Show; +extern const idEventDef EV_GetShaderParm; +extern const idEventDef EV_SetShaderParm; +extern const idEventDef EV_SetOwner; +extern const idEventDef EV_GetAngles; +extern const idEventDef EV_SetAngles; +extern const idEventDef EV_SetLinearVelocity; +extern const idEventDef EV_SetAngularVelocity; +extern const idEventDef EV_SetSkin; +extern const idEventDef EV_StartSoundShader; +extern const idEventDef EV_StopSound; +extern const idEventDef EV_CacheSoundShader; + +// RAVEN BEGIN +extern const idEventDef EV_CallFunction; +extern const idEventDef EV_SetGuiParm; +extern const idEventDef EV_SetGuiFloat; +extern const idEventDef EV_ClearSkin; +// bdube: more global events for idEntity +extern const idEventDef EV_GetFloatKey; +extern const idEventDef EV_HideSurface; +extern const idEventDef EV_ShowSurface; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_StopAllEffects; +extern const idEventDef EV_PlayEffect; +extern const idEventDef EV_Earthquake; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_SetKey; +// jscott: +extern const idEventDef EV_PlaybackCallback; +// jshepard: +extern const idEventDef EV_UnbindTargets; +// twhitaker: +extern const idEventDef EV_ApplyImpulse; +// RAVEN END + +class idGuidedProjectile; + +// Think flags +enum { + TH_ALL = -1, + TH_THINK = 1, // run think function each frame + TH_PHYSICS = 2, // run physics each frame + TH_ANIMATE = 4, // update animation each frame + TH_UPDATEVISUALS = 8, // update renderEntity +}; + +// +// Signals +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + SIG_TOUCH, // object was touched + SIG_USE, // object was used + SIG_TRIGGER, // object was activated + SIG_REMOVED, // object was removed from the game + SIG_DAMAGE, // object was damaged + SIG_BLOCKED, // object was blocked + + SIG_MOVER_POS1, // mover at position 1 (door closed) + SIG_MOVER_POS2, // mover at position 2 (door open) + SIG_MOVER_1TO2, // mover changing from position 1 to 2 + SIG_MOVER_2TO1, // mover changing from position 2 to 1 + +// RAVEN BEGIN +// kfuller: added signals + // WARNING: these entries are mirrored in scripts/defs.script so make sure if they move + // here that they move there as well + SIG_REACHED, // object reached it's rotation/motion destination +// RAVEN END + + NUM_SIGNALS +} signalNum_t; + +// FIXME: At some point we may want to just limit it to one thread per signal, but +// for now, I'm allowing multiple threads. We should reevaluate this later in the project +#define MAX_SIGNAL_THREADS 16 // probably overkill, but idList uses a granularity of 16 + +struct signal_t { + int threadnum; + const function_t *function; +}; + +class signalList_t { +public: + idList signal[ NUM_SIGNALS ]; +}; + +class idEntity : public idClass { +public: + static const int MAX_PVS_AREAS = 4; + + int entityNumber; // index into the entity list + int entityDefNumber; // index into the entity def list + + idLinkList spawnNode; // for being linked into spawnedEntities list + idLinkList activeNode; // for being linked into activeEntities list + + idLinkList snapshotNode; // for being linked into snapshotEntities list + int snapshotSequence; // last snapshot this entity was in + int snapshotBits; // number of bits this entity occupied in the last snapshot + + idStr name; // name of entity + idDict spawnArgs; // key/value pairs used to spawn and initialize entity + idScriptObject scriptObject; // contains all script defined data for this entity + + int thinkFlags; // TH_? flags + int dormantStart; // time that the entity was first closed off from player + bool cinematic; // during cinematics, entity will only think if cinematic is set + + renderView_t * renderView; // for camera views from this entity + idEntity * cameraTarget; // any remoteRenderMap shaders will use this + + idList< idEntityPtr > targets; // when this entity is activated these entities entity are activated + + int health; // FIXME: do all objects really need health? + +// RAVEN BEGIN +// ddynerman: optional pre-prediction + int predictTime; +// bdube: client entities + idLinkList clientEntities; + +// rjohnson: will now draw entity info for long thinkers + int mLastLongThinkTime; + idVec4 mLastLongThinkColor; +// RAVEN END + + struct entityFlags_s { + bool notarget :1; // if true never attack or target this entity + bool noknockback :1; // if true no knockback from hits + bool takedamage :1; // if true this entity can be damaged + bool hidden :1; // if true this entity is not visible + bool bindOrientated :1; // if true both the master orientation is used for binding + bool solidForTeam :1; // if true this entity is considered solid when a physics team mate pushes entities + bool forcePhysicsUpdate :1; // if true always update from the physics whether the object moved or not + bool selected :1; // if true the entity is selected for editing + bool neverDormant :1; // if true the entity never goes dormant + bool isDormant :1; // if true the entity is dormant + bool hasAwakened :1; // before a monster has been awakened the first time, use full PVS for dormant instead of area-connected + bool networkSync :1; // if true the entity is synchronized over the network + +// RAVEN BEGIN +// bdube: added + bool networkStale :1; // was in the snapshot but isnt anymore +// bgeisler: added block + bool triggerAnim :1; + bool usable :1; // if true the entity is usable by the player +// cdr: Obstacle Avoidance + bool isAIObstacle :1; // if true, this entity will add itself to the obstacles list in each aas area it touches +// RAVEN END + +// RAVEN BEGIN +// ddynerman: exclude this entity from instance-purging + // twhitaker: moved variable to be within the bit flags + bool persistAcrossInstances :1; +// twhitaker: exited vehicle already? + bool exitedVehicle :1; +// twhitaker: blinking + bool allowAutoBlink :1; +// jshepard: instant burnout when destroyed + bool quickBurn :1; + +// RAVEN END + } fl; + +public: + ABSTRACT_PROTOTYPE( idEntity ); + + idEntity(); + ~idEntity(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + const char * GetEntityDefName( void ) const; + void SetName( const char *name ); + const char * GetName( void ) const; + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + // clients generate views based on all the player specific options, + // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + + // thinking + virtual void Think( void ); + bool CheckDormant( void ); // dormant == on the active list, but out of PVS + virtual void DormantBegin( void ); // called when entity becomes dormant + virtual void DormantEnd( void ); // called when entity wakes from being dormant + bool IsActive( void ) const; + void BecomeActive( int flags ); + void BecomeInactive( int flags ); + void UpdatePVSAreas( const idVec3 &pos ); + +// RAVEN BEGIN +// abahr: + bool IsActive( int flags ) const { return (flags & thinkFlags) > 0; } + const char* GetEntityDefClassName() const { return spawnArgs.GetString("classname"); } + bool IsEntityDefClass( const char* className ) const { return !idStr::Icmp(className, GetEntityDefClassName()); } + virtual void GetPosition( idVec3& origin, idMat3& axis ) const; +// kfuller: added methods + virtual void GetLocalAngles(idAngles &localAng); +// RAVEN END + + // visuals + virtual void Present( void ); + // instance visuals + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); +// RAVEN BEGIN +// bdube: removed virtual so it could be inlined + renderEntity_t * GetRenderEntity( void ); + int GetModelDefHandle( void ); +// RAVEN END + virtual void SetModel( const char *modelname ); + void SetSkin( const idDeclSkin *skin ); + const idDeclSkin * GetSkin( void ) const; + +// RAVEN BEGIN +// bdube: surfaces + void HideSurface ( const char* surface ); + void ShowSurface ( const char* surface ); + void ClearSkin( void ); +// RAVEN END + + void SetShaderParm( int parmnum, float value ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec4 &out ) const; + virtual void FreeModelDef( void ); + virtual void FreeLightDef( void ); + virtual void Hide( void ); + virtual void Show( void ); + bool IsHidden( void ) const; + void UpdateVisuals( void ); + void UpdateModel( void ); +// RAVEN BEGIN +// abahr: added virtual to UpdateModelTransform + virtual + void UpdateModelTransform( void ); + virtual void UpdateRenderEntityCallback(); + virtual const idAnimator * GetAnimator( void ) const { return NULL; } // returns animator object used by this entity +// RAVEN END + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + int GetNumPVSAreas( void ); + const int * GetPVSAreas( void ); + void ClearPVSAreas( void ); + bool PhysicsTeamInPVS( pvsHandle_t pvsHandle ); + + // animation + virtual bool UpdateAnimationControllers( void ); + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + virtual idAnimator * GetAnimator( void ); // returns animator object used by this entity + + // sound + virtual bool CanPlayChatterSounds( void ) const; + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound( const s_channelType channel, bool broadcast ); // pass SND_CHANNEL_ANY to stop all sounds + void SetSoundVolume( float volume ); + void UpdateSound( void ); + int GetListenerId( void ) const; +// RAVEN BEGIN + int GetSoundEmitter( void ) const; +// RAVEN END + void FreeSoundEmitter( bool immediate ); + +// RAVEN BEGIN +// bdube: added effect functions + // effects + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + + rvClientEffect* PlayEffect ( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + void StopEffect ( const char* effectName, bool destroyParticles = false ); + void StopEffect ( const idDecl *effect, bool destroyParticles = false ); + void StopAllEffects ( bool destroyParticles = false ); + void UpdateEffects ( void ); + + float DistanceTo ( idEntity* ent ); + float DistanceTo ( const idVec3& pos ) const; + float DistanceTo2d ( idEntity* ent ); + float DistanceTo2d ( const idVec3& pos ) const; + + virtual bool CanTakeDamage ( void ) const; +// RAVEN END + + // entity binding + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void PreUnbind( void ); + virtual void PostUnbind( void ); + void JoinTeam( idEntity *teammember ); + void Bind( idEntity *master, bool orientated ); + void BindToJoint( idEntity *master, const char *jointname, bool orientated ); + void BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ); + void BindToBody( idEntity *master, int bodyId, bool orientated ); + void Unbind( void ); + bool IsBound( void ) const; +// RAVEN BEGIN +// abahr: added const so it can be called from const functions + bool IsBoundTo( const idEntity *master ) const; +// RAVEN END + idEntity * GetBindMaster( void ) const; + jointHandle_t GetBindJoint( void ) const; + int GetBindBody( void ) const; + idEntity * GetTeamMaster( void ) const; + idEntity * GetNextTeamEntity( void ) const; +// RAVEN BEGIN +// abahr: added virtual + virtual + void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); +// RAVEN END + idVec3 GetLocalVector( const idVec3 &vec ) const; + idVec3 GetLocalCoordinates( const idVec3 &vec ) const; + idVec3 GetWorldVector( const idVec3 &vec ) const; + idVec3 GetWorldCoordinates( const idVec3 &vec ) const; + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + void GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const; + + // physics + // set a new physics object to be used by this entity + void SetPhysics( idPhysics *phys ); + // get the physics object used by this entity + idPhysics * GetPhysics( void ) const; + // restore physics pointer for save games + void RestorePhysics( idPhysics *phys ); + // run the physics for this entity + bool RunPhysics( void ); + // set the origin of the physics object (relative to bindMaster if not NULL) + void SetOrigin( const idVec3 &org ); + // set the axis of the physics object (relative to bindMaster if not NULL) + void SetAxis( const idMat3 &axis ); + // use angles to set the axis of the physics object (relative to bindMaster if not NULL) + void SetAngles( const idAngles &ang ); + // get the floor position underneath the physics object + bool GetFloorPos( float max_dist, idVec3 &floorpos ) const; + // retrieves the transformation going from the physics origin/axis to the visual origin/axis + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + // retrieves the transformation going from the physics origin/axis to the sound origin/axis + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + // called from the physics object when colliding, should return true if the physics simulation should stop + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { hitTeleporter = false; return Collide(collision, velocity); } + // retrieves impact information, 'ent' is the entity retrieving the info + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + // apply an impulse to the physics object, 'ent' is the entity applying the impulse + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + // add a force to the physics object, 'ent' is the entity adding the force + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + // activate the physics object, 'ent' is the entity activating this entity + virtual void ActivatePhysics( idEntity *ent ); + // returns true if the physics object is at rest + virtual bool IsAtRest( void ) const; + // returns the time the physics object came to rest + virtual int GetRestStartTime( void ) const; + // add a contact entity + virtual void AddContactEntity( idEntity *ent ); + // remove a touching entity + virtual void RemoveContactEntity( idEntity *ent ); + +// RAVEN BEGIN +// kfuller: added blocked methods + virtual void LastBlockedBy(int blockedEntNum) {} + virtual int GetLastBlocker(void) { return -1; } +// rjohnson: moved entity info out of idGameLocal into its own function + virtual void DrawDebugEntityInfo( idBounds *viewBounds = 0, idBounds *viewTextBounds = 0, idVec4 *overrideColor = 0 ); +// nmckenzie: Adding ability for non-Actors to be enemies for AI characters. Rename this function at some point, most likely. + virtual idVec3 GetEyePosition( void ) const { return GetPhysics()->GetOrigin(); } +// abahr: + virtual bool SkipImpulse( idEntity *ent, int id ); + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ); +// RAVEN END + + // damage +// RAVEN BEGIN + // twhitaker: // Sets the damage enitty + void SetDamageEntity ( idEntity * forward ) { forwardDamageEnt = forward; } + + // bdube: added ignore entity + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + + // returns true if this entity can be damaged from the given origin + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt = NULL ) const; +// RAVEN END + // applies damage to this entity + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + // adds a damage effect like overlays, blood, sparks, debris etc. + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + // callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + // notifies this entity that it is in pain + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + // notifies this entity that is has been killed + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // scripting + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread * ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ); + void ClearSignal( idThread *thread, signalNum_t signalnum ); + void ClearSignalThread( signalNum_t signalnum, idThread *thread ); + bool HasSignal( signalNum_t signalnum ) const; + void Signal( signalNum_t signalnum ); + void SignalEvent( idThread *thread, signalNum_t signalnum ); + + // gui + void TriggerGuis( void ); + bool HandleGuiCommands( idEntity *entityGui, const char *cmds ); + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + + // targets +// RAVEN BEGIN +// abahr: made virtual + virtual + void FindTargets( void ); + virtual + void RemoveNullTargets( void ); + virtual + void ActivateTargets( idEntity *activator ) const; +// jshepard: unbind targets + void UnbindTargets( idEntity *activator ) const; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: Add to the list of targets (usually from script) + int AppendTarget( idEntity *appendMe ); + void RemoveTarget( idEntity *removeMe ); + void RemoveTargets( bool destroyContents ); +// RAVEN END + + // misc + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + bool TouchTriggers( const idTypeInfo* ownerType = NULL ) const; + idCurve_Spline *GetSpline( void ) const; + virtual void ShowEditingDialog( void ); + + enum { + EVENT_STARTSOUNDSHADER, + EVENT_STOPSOUNDSHADER, +// RAVEN BEGIN +// bdube: new events + EVENT_PLAYEFFECT, + EVENT_PLAYEFFECT_JOINT, + EVENT_STOPEFFECT, +// RAVEN END + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); +// RAVEN BEGIN + // the entity was not in the snapshot sent by server. means it either went out of PVS, or was deleted server side + // return true if the entity wishes to be deleted locally + // depending on the entity, understanding stale as just another state, or a removal is best + // ( by default, idEntity keeps the entity around after a little cleanup ) + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); +// RAVEN END + + void WriteBindToSnapshot( idBitMsgDelta &msg ) const; + void ReadBindFromSnapshot( const idBitMsgDelta &msg ); + void WriteColorToSnapshot( idBitMsgDelta &msg ) const; + void ReadColorFromSnapshot( const idBitMsgDelta &msg ); + void WriteGUIToSnapshot( idBitMsgDelta &msg ) const; + void ReadGUIFromSnapshot( const idBitMsgDelta &msg ); + + void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ClientSendEvent( int eventId, const idBitMsg *msg ) const; + +// RAVEN BEGIN +// bdube: debugging + virtual void GetDebugInfo( debugInfoProc_t proc, void* userData ); +// mwhitlock: memory profiling + virtual size_t Size( void ) const; +// ddynerman: multiple arenas (for MP) + virtual void SetInstance( int newInstance ); + virtual int GetInstance( void ) const; +// ddynerman: multiple clip world support + virtual int GetClipWorld( void ) const; + virtual void SetClipWorld( int newCW ); +// scork: accessors so sound editor can indicate current-highlit ent + virtual int GetRefSoundShaderFlags( void ) const; + virtual void SetRefSoundShaderFlags( int iFlags ); +// twhitaker: guided projectiles + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ) { } +// RAVEN END + +protected: + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + refSound_t refSound; // used to present sound to the audio engine + idEntityPtr< idEntity > forwardDamageEnt; // damage applied to the invoking object will be forwarded to this entity + idEntityPtr< idEntity > bindMaster; // entity bound to if unequal NULL + jointHandle_t bindJoint; // joint bound to if unequal INVALID_JOINT +private: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics * physics; // physics used for this entity + int bindBody; // body bound to if unequal -1 + idEntity * teamMaster; // master of the physics team + idEntity * teamChain; // next entity in physics team + + int numPVSAreas; // number of renderer areas the entity covers + int PVSAreas[MAX_PVS_AREAS]; // numbers of the renderer areas the entity covers + + signalList_t * signals; + + int mpGUIState; // local cache to avoid systematic SetStateInt +// RAVEN BEGIN +// abahr: changed to protected for access in children classes +protected: +// ddynerman: multiple game instances + int instance; +// ddynerman: multiple collision worlds + int clipWorld; +// RAVEN END + +// RAVEN BEGIN +// bdube: made virtual + virtual bool DoDormantTests( void ); // dormant == on the active list, but out of PVS +// RAVEN END + + // physics + // initialize the default physics + void InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ); + // update visual position from the physics + void UpdateFromPhysics( bool moveBack ); + + // entity binding + bool InitBind( idEntity *master ); // initialize an entity binding + void FinishBind( void ); // finish an entity binding + void RemoveBinds( void ); // deletes any entities bound to this object + void QuitTeam( void ); // leave the current team + + void UpdatePVSAreas( void ); + +// RAVEN BEGIN +// bdube: client entities + void RemoveClientEntities ( void ); // deletes any client entities bound to this object +// RAVEN END + + // events + void Event_GetName( void ); + void Event_SetName( const char *name ); + void Event_FindTargets( void ); + void Event_ActivateTargets( idEntity *activator ); + void Event_NumTargets( void ); + void Event_GetTarget( float index ); + void Event_RandomTarget( const char *ignore ); + void Event_Bind( idEntity *master ); + void Event_BindPosition( idEntity *master ); + void Event_BindToJoint( idEntity *master, const char *jointname, float orientated ); + void Event_Unbind( void ); + void Event_RemoveBinds( void ); + void Event_SpawnBind( void ); + void Event_SetOwner( idEntity *owner ); + void Event_SetModel( const char *modelname ); + void Event_SetSkin( const char *skinname ); + void Event_GetShaderParm( int parmnum ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetColor( float red, float green, float blue ); + void Event_GetColor( void ); + void Event_IsHidden( void ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_StartSoundShader( const char *soundName, int channel ); + void Event_StopSound( int channel, int netSync ); + void Event_StartSound( const char *soundName, int channel, int netSync ); + void Event_FadeSound( int channel, float to, float over ); + void Event_GetWorldOrigin( void ); + void Event_SetWorldOrigin( idVec3 const &org ); + void Event_GetOrigin( void ); + void Event_SetOrigin( const idVec3 &org ); + void Event_GetAngles( void ); + void Event_SetAngles( const idAngles &ang ); + void Event_SetLinearVelocity( const idVec3 &velocity ); + void Event_GetLinearVelocity( void ); + void Event_SetAngularVelocity( const idVec3 &velocity ); + void Event_GetAngularVelocity( void ); + void Event_SetSize( const idVec3 &mins, const idVec3 &maxs ); + void Event_GetSize( void ); + void Event_GetMins( void ); + void Event_GetMaxs( void ); + void Event_Touches( idEntity *ent ); + void Event_SetGuiParm( const char *key, const char *val ); + void Event_SetGuiFloat( const char *key, float f ); + void Event_GetNextKey( const char *prefix, const char *lastMatch ); + void Event_SetKey( const char *key, const char *value ); + void Event_GetKey( const char *key ); + void Event_GetIntKey( const char *key ); + void Event_GetFloatKey( const char *key ); + void Event_GetVectorKey( const char *key ); + void Event_GetEntityKey( const char *key ); + void Event_RestorePosition( void ); + void Event_UpdateCameraTarget( void ); + void Event_DistanceTo( idEntity *ent ); + void Event_DistanceToPoint( const idVec3 &point ); + void Event_StartFx( const char *fx ); + void Event_WaitFrame( void ); + void Event_Wait( float time ); + void Event_HasFunction( const char *name ); + void Event_CallFunction( const char *name ); + void Event_SetNeverDormant( int enable ); + +// RAVEN BEGIN +// kfuller: added events + void Event_SetContents ( int contents ); + void Event_GetLastBlocker ( idThread *thread ); +// begisler: added + void Event_ClearSkin ( void ); +// bdube: effect events + void Event_PlayEffect ( const char* effectName, const char* boneName, bool loop ); + void Event_StopEffect ( const char* effectName ); + void Event_StopAllEffects ( void ); + void Event_GetHealth ( void ); +// bdube: mesh events + void Event_ShowSurface ( const char* surface ); + void Event_HideSurface ( const char* surface ); +// bdube: gui events + void Event_GuiEvent ( const char* eventName ); +// jscott: + void Event_PlaybackCallback ( int type, int changed, int impulse ); +// nmckenzie: get bind master + void Event_GetBindMaster ( void ); + void Event_ApplyImpulse ( idEntity *source, const idVec3 &point, const idVec3 &impulse ); +// jshepard: unbind all targets + void Event_UnbindTargets ( idEntity *activator); +// abahr: + void Event_RemoveNullTargets (); + void Event_IsA ( const char* entityDefName ); + void Event_IsSameTypeAs ( const idEntity* ent ); + void Event_MatchPrefix ( const char *prefix, const char* previousKey ); + void Event_ClearTargetList ( float destroyContents ); +// twhitaker: added - to add targets from script + void Event_AppendTarget ( idEntity *appendMe ); + void Event_RemoveTarget ( idEntity *removeMe ); +// mekberg: added + void Event_SetHealth ( float newHealth ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added inlines +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, vec3_origin, mat3_identity, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, originOffset, axisOffset, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + + +ID_INLINE idPhysics *idEntity::GetPhysics( void ) const { + return physics; +} + +ID_INLINE renderEntity_t *idEntity::GetRenderEntity( void ) { + return &renderEntity; +} + +ID_INLINE int idEntity::GetModelDefHandle( void ) { + return modelDefHandle; +} + +// scork: accessors so sound editor can indicate current-highlit ent +ID_INLINE int idEntity::GetRefSoundShaderFlags( void ) const +{ + return refSound.parms.soundShaderFlags; +} + +ID_INLINE void idEntity::SetRefSoundShaderFlags( int iFlags ) +{ + refSound.parms.soundShaderFlags = iFlags; +} + + +// RAVEN END + +/* +=============================================================================== + + Animated entity base class. + +=============================================================================== +*/ + +typedef struct damageEffect_s { + jointHandle_t jointNum; + idVec3 localOrigin; + idVec3 localNormal; + int time; +// RAVEN BEGIN +// jscott: not using +// const idDeclParticle* type; +// RAVEN END + struct damageEffect_s * next; +} damageEffect_t; + +class idAnimatedEntity : public idEntity { +public: + CLASS_PROTOTYPE( idAnimatedEntity ); + + idAnimatedEntity(); + ~idAnimatedEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + virtual void Think( void ); + + void UpdateAnimation( void ); + + virtual idAnimator * GetAnimator( void ); + virtual void SetModel( const char *modelname ); + + bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + bool GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int currentTime, idVec3 &offset, idMat3 &axis ) const; + + virtual int GetDefaultSurfaceType( void ) const; + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) {} + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +// RAVEN BEGIN +// abahr: + virtual const idAnimator * GetAnimator( void ) const { return &animator; } + virtual void UpdateRenderEntityCallback(); +// RAVEN BEGIN + + enum { + EVENT_ADD_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +protected: + idAnimator animator; + damageEffect_t * damageEffects; + +// RAVEN BEGIN +// jshepard: + void Event_ClearAnims ( void ); +// RAVEN END + +private: + void Event_GetJointHandle( const char *jointname ); + void Event_ClearAllJoints( void ); + void Event_ClearJoint( jointHandle_t jointnum ); + void Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + void Event_GetJointPos( jointHandle_t jointnum ); + void Event_GetJointAngle( jointHandle_t jointnum ); + +// RAVEN BEGIN +// bdube: programmer controlled joint events + void Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ); + void Event_CollapseJoints ( const char* jointnames, const char* collapseTo ); + + + +// RAVEN END +}; + +// RAVEN BEGIN +void UpdateGuiParms( idUserInterface *gui, const idDict *args ); +// RAVEN END + +ID_INLINE float idEntity::DistanceTo ( idEntity* ent ) { + return DistanceTo ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE float idEntity::DistanceTo ( const idVec3& pos ) const { + return (pos - GetPhysics()->GetOrigin()).LengthFast ( ); +} + +ID_INLINE float idEntity::DistanceTo2d ( idEntity* ent ) { + return DistanceTo2d ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE bool idEntity::CanTakeDamage ( void ) const { + return fl.takedamage; +} + +// RAVEN BEGIN +// ddynerman: MP arena stuff +ID_INLINE int idEntity::GetInstance( void ) const { + return instance; +} + +// ddynerman: multiple collision worlds +ID_INLINE int idEntity::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idEntity::SetClipWorld( int newCW ) { + clipWorld = newCW; + if( GetPhysics() ) { + GetPhysics()->UnlinkClip(); + GetPhysics()->LinkClip(); + } +} +// RAVEN END +#endif /* !__GAME_ENTITY_H__ */ diff --git a/source/mpgame/GameEdit.cpp b/source/mpgame/GameEdit.cpp new file mode 100644 index 0000000..bc74e35 --- /dev/null +++ b/source/mpgame/GameEdit.cpp @@ -0,0 +1,1960 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: added +#include "Effect.h" +// nmckenzie: +//#include "rvAI/AI.h" +#include "ai/AI.h" +#include "client/ClientEffect.h" +// RAVEN END + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idCursor3D ) +END_CLASS + +/* +=============== +idCursor3D::idCursor3D +=============== +*/ +idCursor3D::idCursor3D( void ) { + draggedPosition.Zero(); +} + +/* +=============== +idCursor3D::~idCursor3D +=============== +*/ +idCursor3D::~idCursor3D( void ) { +} + +/* +=============== +idCursor3D::Spawn +=============== +*/ +void idCursor3D::Spawn( void ) { +} + +/* +=============== +idCursor3D::Present +=============== +*/ +void idCursor3D::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + const idVec3 &origin = GetPhysics()->GetOrigin(); + const idMat3 &axis = GetPhysics()->GetAxis(); + gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 ); + gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 ); +} + +/* +=============== +idCursor3D::Think +=============== +*/ +void idCursor3D::Think( void ) { + if ( thinkFlags & TH_THINK ) { + drag.Evaluate( gameLocal.time ); + } + Present(); +} + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +#define MAX_DRAG_TRACE_DISTANCE 2048.0f + +/* +============== +idDragEntity::idDragEntity +============== +*/ +idDragEntity::idDragEntity( void ) { + cursor = NULL; + Clear(); +} + +/* +============== +idDragEntity::~idDragEntity +============== +*/ +idDragEntity::~idDragEntity( void ) { + StopDrag(); + selected = NULL; + delete cursor; + cursor = NULL; +} + + +/* +============== +idDragEntity::Clear +============== +*/ +void idDragEntity::Clear() { + dragEnt = NULL; + joint = INVALID_JOINT; + id = 0; + localEntityPoint.Zero(); + localPlayerPoint.Zero(); + bodyName.Clear(); + selected = NULL; +} + +/* +============== +idDragEntity::StopDrag +============== +*/ +void idDragEntity::StopDrag( void ) { + dragEnt = NULL; + if ( cursor ) { + cursor->BecomeInactive( TH_THINK ); + } +} + +/* +============== +idDragEntity::Update +============== +*/ +void idDragEntity::Update( idPlayer *player ) { + idVec3 viewPoint, origin; + idMat3 viewAxis, axis; + trace_t trace; + idEntity *newEnt; + idAngles angles; + jointHandle_t newJoint = INVALID_JOINT; + idStr newBodyName; + + player->GetViewPos( viewPoint, viewAxis ); + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + + if ( player->usercmd.buttons & BUTTON_ATTACK ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( player, trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + + newEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( newEnt ) { + + if ( newEnt->GetBindMaster() ) { + if ( newEnt->GetBindJoint() ) { + trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); + } else { + trace.c.id = newEnt->GetBindBody(); + } + newEnt = newEnt->GetBindMaster(); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( newEnt->IsType( idAFEntity_Base::GetClassType() ) && static_cast(newEnt)->IsActiveAF() ) { +// RAVEN END + idAFEntity_Base *af = static_cast(newEnt); + + // joint being dragged + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + // get the body id from the trace model id which might be a joint handle + trace.c.id = af->BodyForClipModelId( trace.c.id ); + // get the name of the body being dragged + newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( !newEnt->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + + if ( trace.c.id < 0 ) { + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + } else { + newJoint = INVALID_JOINT; + } + newBodyName = ""; + + } else { + + newJoint = INVALID_JOINT; + newEnt = NULL; + } + } + if ( newEnt ) { + dragEnt = newEnt; + selected = newEnt; + joint = newJoint; + id = trace.c.id; + bodyName = newBodyName; + + if ( !cursor ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::GetClassType() ); +// RAVEN END + } + + idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); + localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); + origin = phys->GetOrigin( id ); + axis = phys->GetAxis( id ); + localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); + + cursor->drag.Init( g_dragDamping.GetFloat() ); + cursor->drag.SetPhysics( phys, id, localEntityPoint ); + cursor->Show(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( phys->IsType( idPhysics_AF::GetClassType() ) || + phys->IsType( idPhysics_RigidBody::GetClassType() ) || + phys->IsType( idPhysics_Monster::GetClassType() ) ) { +// RAVEN END + cursor->BecomeActive( TH_THINK ); + } + } + } + } + } + + // if there is an entity selected for dragging + idEntity *drag = dragEnt.GetEntity(); + if ( drag ) { + + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag(); + return; + } + + cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); + cursor->SetAxis( viewAxis ); + + cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); + + renderEntity_t *renderEntity = drag->GetRenderEntity(); + idAnimator *dragAnimator = drag->GetAnimator(); + + if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) { + dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); + cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; + gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } else { + cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); + gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } + } + + // if there is a selected entity + if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { + // draw the bbox of the selected entity + renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); + if ( renderEntity ) { + gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); + } + } +} + +/* +============== +idDragEntity::SetSelected +============== +*/ +void idDragEntity::SetSelected( idEntity *ent ) { + selected = ent; + StopDrag(); +} + +/* +============== +idDragEntity::DeleteSelected +============== +*/ +void idDragEntity::DeleteSelected( void ) { + delete selected.GetEntity(); + selected = NULL; + StopDrag(); +} + +/* +============== +idDragEntity::BindSelected +============== +*/ +void idDragEntity::BindSelected( void ) { + int num, largestNum; + idLexer lexer; + idToken type, bodyName; + idStr key, value, bindBodyName; + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(dragEnt.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName(); + largestNum = 1; + + // parse all the bind constraints + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + key = kv->GetKey(); + key.Strip( "bindConstraint " ); + if ( sscanf( key, "bind%d", &num ) ) { + if ( num >= largestNum ) { + largestNum = num + 1; + } + } + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + lexer.ReadToken( &bodyName ); + lexer.FreeSource(); + + // if there already exists a bind constraint for this body + if ( bodyName.Icmp( bindBodyName ) == 0 ) { + // delete the bind constraint + af->spawnArgs.Delete( kv->GetKey() ); + kv = NULL; + } + + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + sprintf( key, "bindConstraint bind%d", largestNum ); + sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) ); + + af->spawnArgs.Set( key, value ); + af->spawnArgs.Set( "bind", "worldspawn" ); + af->Bind( gameLocal.world, true ); +} + +/* +============== +idDragEntity::UnbindSelected +============== +*/ +void idDragEntity::UnbindSelected( void ) { + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(selected.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + // unbind the selected entity + af->Unbind(); + + // delete all the bind constraints + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + selected.GetEntity()->spawnArgs.Delete( kv->GetKey() ); + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + } + + // delete any bind information + af->spawnArgs.Delete( "bind" ); + af->spawnArgs.Delete( "bindToJoint" ); + af->spawnArgs.Delete( "bindToBody" ); +} + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ + +/* +============== +idEditEntities::idEditEntities +============== +*/ +idEditEntities::idEditEntities( void ) { + selectableEntityClasses.Clear(); + nextSelectTime = 0; +} + +// RAVEN BEGIN +// bdube: made this special to edit entities +/* +============= +idEditEntities::FindTraceEntity +============= +*/ +idEntity* idEditEntities::FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ) { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent != skip && EntityIsSelectable ( ent ) ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} +// RAVEN END + +/* +============= +idEditEntities::SelectEntity +============= +*/ +bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) { + idVec3 end; + idEntity *ent; + + if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) { + return false; + } + + if ( gameLocal.time < nextSelectTime ) { + return true; + } + nextSelectTime = gameLocal.time + 300; + + end = origin + dir * 4096.0f; + +// RAVEN BEGIN +// bdube: more generic + ent = NULL; + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + ent = FindTraceEntity( origin, end, skip ); + if ( ent ) { + break; + } + } + if ( !ent ) { + return false; + } + + ClearSelectedEntities(); + + AddSelectedEntity( ent ); + gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() ); + + if ( gameLocal.editors & EDITOR_ENTVIEW ) { + common->InitTool ( EDITOR_ENTVIEW, &ent->spawnArgs ); + } else { + ent->ShowEditingDialog(); + } + + return true; +// RAVEN END +} + +/* +============= +idEditEntities::AddSelectedEntity +============= +*/ +void idEditEntities::AddSelectedEntity(idEntity *ent) { + ent->fl.selected = true; + selectedEntities.AddUnique(ent); +} + +/* +============== +idEditEntities::RemoveSelectedEntity +============== +*/ +void idEditEntities::RemoveSelectedEntity( idEntity *ent ) { + if ( selectedEntities.Find( ent ) ) { + selectedEntities.Remove( ent ); + } +} + +/* +============= +idEditEntities::ClearSelectedEntities +============= +*/ +void idEditEntities::ClearSelectedEntities() { + int i, count; + + count = selectedEntities.Num(); + for ( i = 0; i < count; i++ ) { + selectedEntities[i]->fl.selected = false; + } + selectedEntities.Clear(); +} + + +/* +============= +idEditEntities::EntityIsSelectable +============= +*/ +bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) { +// RAVEN BEGIN +// bdube: no matter what dont let the player or its entities be selectable + idPlayer* player; + if ( 0 != (player = gameLocal.GetLocalPlayer() )) { + if ( ent == player || ent == player->GetWeaponViewModel ( ) || ent == player->GetWeaponWorldModel ( ) ) { + return false; + } + } + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + if ( ent->IsType ( *selectableEntityClasses[i].typeInfo ) ) { +// RAVEN END + if ( text ) { + *text = selectableEntityClasses[i].textKey; + } + if ( color ) { + if ( ent->fl.selected ) { + *color = colorRed; + } else { + switch( i ) { + case 1 : + *color = colorYellow; + break; + case 2 : + *color = colorBlue; + break; + default: + *color = colorGreen; + } + } + } + return true; + } + } + return false; +} + +/* +============= +idEditEntities::DisplayEntities +============= +*/ +void idEditEntities::DisplayEntities( void ) { + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + selectableEntityClasses.Clear(); + selectedTypeInfo_t sit; + + switch( g_editEntityMode.GetInteger() ) { + case 1: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idLight::GetClassType(); +// RAVEN END + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 2: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idSound::GetClassType(); +// scork: added secondary "name" field as well (Zack request) + sit.textKey = "s_shader|name"; + selectableEntityClasses.Append( sit ); +// scork: Zack (reasonably enough) doesn't want the lights displayed when editing sounds +// sit.typeInfo = &idLight::GetClassType(); +// sit.textKey = "texture"; +// selectableEntityClasses.Append( sit ); +// RAVEN END + break; + case 3: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAFEntity_Base::GetClassType(); +// RAVEN END + sit.textKey = "articulatedFigure"; + selectableEntityClasses.Append( sit ); + break; + case 4: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idFuncEmitter::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + case 5: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAI::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 6: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 7: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; +// RAVEN BEGIN +// bdube: added fx entities + case 8: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &rvEffect::GetClassType(); +// RAVEN END + sit.textKey = "fx"; + selectableEntityClasses.Append ( sit ); + break; +// RAVEN END + default: + return; + } + + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + + viewBounds.ExpandSelf( g_editEntityDistance.GetFloat() ); +// RAVEN BEGIN +// scork: changed from 128 to 256 so we can see speaker ent descriptions before getting right up to them +// rhummer: Added cvar to adjust the distance for the text too. + viewTextBounds.ExpandSelf( g_editEntityTextDistance.GetFloat() ); +// RAVEN END + + idStr textKey; +// RAVEN BEGIN +// scork: secondary field + idStr textKey2; + idStr strOutput; +// RAVEN END + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + idVec4 color; + + textKey = ""; + if ( !EntityIsSelectable( ent, &color, &textKey ) ) { + continue; + } + +// RAVEN BEGIN +// scork: handle optional secondary field + textKey2 = ""; + int iIndex = textKey.Find('|'); + if (iIndex >= 0) + { + textKey2 = textKey.Mid ( iIndex+1, textKey.Length()-(iIndex+1) ); // hmmm, they emulate 99% of MS CString but don't have a single-param Mid() func? + textKey = textKey.Left( iIndex ); + } +// RAVEN END + + + bool drawArrows = false; +// RAVEN BEGIN +// bdube: added + bool drawDirection = false; +// RAVEN END +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetType() == &idAFEntity_Base::GetClassType() ) { +// RAVEN END + if ( !static_cast(ent)->IsActiveAF() ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &idSound::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) + { + drawArrows = true; + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag |= SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + else + { + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag &= ~SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + ent->UpdateSound(); + const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) ); + if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) { + color.Set( 1.0f, 0.0f, 1.0f, 1.0f ); + } +// RAVEN BEGIN +// bdube: added +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &rvEffect::GetClassType() ) { + drawDirection = true; + if ( ent->fl.selected ) { + drawArrows = true; + } + } else if ( ent->GetType() == &idFuncEmitter::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) { + drawArrows = true; + } + } + + if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + continue; + } + + gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) ); + if ( drawArrows ) { + idVec3 start = ent->GetPhysics()->GetOrigin(); + idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 1, 0, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * +20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * +20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * -20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis ); + } + +// RAVEN BEGIN +// bdube: added + if ( drawDirection ) { + idVec3 start = ent->GetPhysics()->GetOrigin ( ); + idVec3 end = start + ent->GetPhysics()->GetAxis()[0] * 35.0f; + gameRenderWorld->DebugArrow ( colorYellow, start, end, 6 ); + } +// RAVEN END + + if ( textKey.Length() ) { +// RAVEN BEGIN +// scork: handle optional secondary field, plus only call GetString when bounds are within view + if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + strOutput = ent->spawnArgs.GetString( textKey ); + if (!textKey2.IsEmpty()) + { + strOutput += " ( "; + strOutput += ent->spawnArgs.GetString( textKey2 ); + strOutput += " )"; + } + gameRenderWorld->DrawText( strOutput.c_str(), ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 ); + } +// RAVEN END + } + } +} + + +/* +=============================================================================== + + idGameEdit + +=============================================================================== +*/ + +idGameEdit gameEditLocal; +idGameEdit * gameEdit = &gameEditLocal; + + +/* +============= +idGameEdit::GetSelectedEntities +============= +*/ +int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) { + int num = 0; + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + list[num++] = ent; + if ( num >= max ) { + break; + } + } + } + return num; +} + +/* +============= +idGameEdit::TriggerSelected +============= +*/ +void idGameEdit::TriggerSelected() { + idEntity *ent; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + } + } +} + +/* +================ +idGameEdit::ClearEntitySelection +================ +*/ +void idGameEdit::ClearEntitySelection() { + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + ent->fl.selected = false; + } +// RAVEN BEGIN +// bdube: fixed potential crash + if ( gameLocal.editEntities ) { + gameLocal.editEntities->ClearSelectedEntities(); + } +// RAVEN END +} + +/* +================ +idGameEdit::AddSelectedEntity +================ +*/ +void idGameEdit::AddSelectedEntity( idEntity *ent ) { +// RAVEN BEGIN +// mekberg: fixed crash + if ( ent && gameLocal.editEntities ) { + gameLocal.editEntities->AddSelectedEntity( ent ); + } +// RAVEN END +} + +/* +================ +idGameEdit::FindEntityDefDict +================ +*/ +const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const { + return gameLocal.FindEntityDefDict( name, makeDefault ); +} + +/* +================ +idGameEdit::SpawnEntityDef +================ +*/ +void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) { + gameLocal.SpawnEntityDef( args, ent ); +} + +/* +================ +idGameEdit::FindEntity +================ +*/ +idEntity *idGameEdit::FindEntity( const char *name ) const { + return gameLocal.FindEntity( name ); +} + +/* +============= +idGameEdit::GetUniqueEntityName + +generates a unique name for a given classname +============= +*/ +const char *idGameEdit::GetUniqueEntityName( const char *classname ) const { + int id; + static char name[1024]; + + // can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one + for( id = 0; id < MAX_GENTITIES; id++ ) { + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + if ( !gameLocal.FindEntity( name ) ) { + return name; + } + } + + // id == MAX_GENTITIES + 1, which can't be in use if we get here + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + return name; +} + +/* +================ +idGameEdit::EntityGetOrigin +================ +*/ +void idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const { + if ( ent ) { + org = ent->GetPhysics()->GetOrigin(); + } +} + +/* +================ +idGameEdit::EntityGetAxis +================ +*/ +void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const { + if ( ent ) { + axis = ent->GetPhysics()->GetAxis(); + } +} + +/* +================ +idGameEdit::EntitySetOrigin +================ +*/ +void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->SetOrigin( org ); + } +} + +/* +================ +idGameEdit::EntitySetAxis +================ +*/ +void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) { + if ( ent ) { + ent->SetAxis( axis ); + } +} + +/* +================ +idGameEdit::EntitySetColor +================ +*/ +void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) { + if ( ent ) { + ent->SetColor( color ); + } +} + +/* +================ +idGameEdit::EntityTranslate +================ +*/ +void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->GetPhysics()->Translate( org ); + } +} + +/* +================ +idGameEdit::EntityGetSpawnArgs +================ +*/ +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile +const idDict *idGameEdit::EntityGetSpawnArgs( const idEntity *ent ) const { +// RAVEN END + if ( ent ) { + return &ent->spawnArgs; + } + return NULL; +} + +/* +================ +idGameEdit::EntityUpdateChangeableSpawnArgs +================ +*/ +void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) { + if ( ent ) { + ent->UpdateChangeableSpawnArgs( dict ); + } +} + +/* +================ +idGameEdit::EntityChangeSpawnArgs +================ +*/ +void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) { + if ( ent ) { + for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) { + const idKeyValue *kv = newArgs->GetKeyVal( i ); + + if ( kv->GetValue().Length() > 0 ) { + ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() ); + } else { + ent->spawnArgs.Delete ( kv->GetKey() ); + } + } + } +} + +/* +================ +idGameEdit::EntityUpdateVisuals +================ +*/ +void idGameEdit::EntityUpdateVisuals( idEntity *ent ) { + if ( ent ) { + ent->UpdateVisuals(); + } +} + +/* +================ +idGameEdit::EntitySetModel +================ +*/ +void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) { + if ( ent ) { + ent->spawnArgs.Set( "model", val ); + ent->SetModel( val ); + } +} + +/* +================ +idGameEdit::EntityStopSound +================ +*/ +void idGameEdit::EntityStopSound( idEntity *ent ) { + if ( ent ) { + ent->StopSound( SND_CHANNEL_ANY, false ); + } +} + +/* +================ +idGameEdit::EntityDelete +================ +*/ +void idGameEdit::EntityDelete( idEntity *ent ) { + delete ent; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idGameEdit::EntityGetRenderEntity +================ +*/ +renderEntity_t* idGameEdit::EntityGetRenderEntity ( idEntity* ent ) { + return ent->GetRenderEntity(); +} + +/* +================ +idGameEdit::EntityGetName +================ +*/ +const char* idGameEdit::EntityGetName ( idEntity* ent ) const { + if ( !ent ) { + return ""; + } + return ent->GetName(); +} + +/* +================ +idGameEdit::EntityGetClassname +================ +*/ +const char* idGameEdit::EntityGetClassname ( idEntity* ent ) const { + return ent->GetType()->classname; +} + +/* +================ +idGameEdit::EntityIsDerivedFrom +================ +*/ +bool idGameEdit::EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const { + idTypeInfo* type; + type = idClass::GetClass ( classname ); + if ( !type ) { + return false; + } + + return ent->IsType ( *type ); +} + +/* +================ +idGameEdit::EntityIsValid +================ +*/ +int idGameEdit::EntityToSafeId ( idEntity* ent ) const { + if ( !ent ) { + return 0; + } + return ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameEdit::EntityFromSafeId +================ +*/ +idEntity *idGameEdit::EntityFromSafeId( int safeID ) const { + int entityNum = safeID & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( safeID >> GENTITYNUM_BITS ) ) ) { + return gameLocal.entities[ entityNum ]; + } + return NULL; +} + +/* +================ +idGameEdit::EntitySetSkin +================ +*/ +void idGameEdit::EntitySetSkin ( idEntity* ent, const char* temp ) const { + ent->SetSkin ( declManager->FindSkin ( temp ) ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityClearSkin ( idEntity* ent ) const { + ent->ClearSkin ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityShow ( idEntity* ent ) const { + ent->Show ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityHide ( idEntity* ent ) const { + ent->Hide ( ); +} + +/* +================ +idGameEdit::EntityGetBounds +================ +*/ +void idGameEdit::EntityGetBounds ( idEntity* ent, idBounds &bounds ) const { + bounds = ent->GetRenderEntity()->bounds; +} + +/* +================ +idGameEdit::EntityPlayAnim +================ +*/ +int idGameEdit::EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ) { + if ( !ent->GetAnimator ( ) ) { + return 0; + } + ent->GetAnimator()->PlayAnim ( ANIMCHANNEL_ALL, animNum, time, blendtime ); + ent->GetAnimator()->ServiceAnims ( time, time ); + return ent->GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->GetEndTime ( ); +} + +/* +================ +idGameEdit::EntitySetFrame +================ +*/ +void idGameEdit::EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ) { + idAnimator* animator; + + animator = ent->GetAnimator ( ); + if ( !animator ) { + return; + } + + animator->ClearAllAnims ( time, time ); + + // Move to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, frame, frame, 1.0f, 0 }; + animator->SetFrame ( ANIMCHANNEL_ALL, animNum, frameBlend ); +// RAVEN END + animator->ForceUpdate ( ); +} + +/* +================ +idGameEdit::EntityGetDelta +================ +*/ +void idGameEdit::EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ) { + ent->GetAnimator()->GetDelta ( fromTime, toTime, delta ); +} + +/* +================ +idGameEdit::EntityRemoveOriginOffset +================ +*/ +void idGameEdit::EntityRemoveOriginOffset ( idEntity* ent, bool remove ) { + ent->GetAnimator()->RemoveOriginOffset ( remove ); +} + +/* +================ +idGameEdit::EntityStopAllEffects +================ +*/ +void idGameEdit::EntityStopAllEffects ( idEntity* ent ) { + ent->StopAllEffects ( ); + ent->StopSound ( SND_CHANNEL_ANY, false ); +} + +// RAVEN BEGIN +// scork: some accessor functions for various utils +idEntity *idGameEdit::EntityGetNextTeamEntity( idEntity *pEnt ) const { + return pEnt->GetNextTeamEntity(); +} +void idGameEdit::GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum, idAngles *deltaViewAngles ) const +{ + game->GetPlayerInfo( v3Origin, mat3Axis, PlayerNum, deltaViewAngles ); +} + +void idGameEdit::SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum ) const +{ + game->SetPlayerInfo( v3Origin, mat3Axis, PlayerNum ); +} +void idGameEdit::EntitySetName( idEntity* pEnt, const char *psName ) +{ + pEnt->SetName( psName ); +} +// RAVEN END + +/* +================ +idGameEdit::LightSetParms +================ +*/ +void idGameEdit::LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ) { + int data; + idLight* light; + + // Switch to a light entity + light = dynamic_cast(ent); + if ( !light ) + { + return; + } + + light->ProcessEvent ( &EV_Light_SetMaxLightLevel, maxLevel ); + light->ProcessEvent ( &EV_Light_SetCurrentLightLevel, (int)currentLevel ); + + (*(float*)&data) = radius; + light->ProcessEventArgPtr ( &EV_Light_SetRadius, &data ); + + light->SetLightLevel(); +} +// RAVEN END + +/* +================ +idGameEdit::PlayerIsValid +================ +*/ +bool idGameEdit::PlayerIsValid() const { + return ( gameLocal.GetLocalPlayer() != NULL ); +} + +/* +================ +idGameEdit::PlayerGetOrigin +================ +*/ +void idGameEdit::PlayerGetOrigin( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); +} + +/* +================ +idGameEdit::PlayerGetAxis +================ +*/ +void idGameEdit::PlayerGetAxis( idMat3 &axis ) const { + axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis(); +} + +/* +================ +idGameEdit::PlayerGetViewAngles +================ +*/ +void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const { + angles = gameLocal.GetLocalPlayer()->viewAngles; +} + +/* +================ +idGameEdit::PlayerGetEyePosition +================ +*/ +void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetEyePosition(); +} + + +/* +================ +idGameEdit::MapGetEntityDict +================ +*/ +const idDict *idGameEdit::MapGetEntityDict( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + return &mapent->epairs; + } + } + return NULL; +} + +/* +================ +idGameEdit::MapSave +================ +*/ +void idGameEdit::MapSave( const char *path ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { +// RAVEN BEGIN +// rjohnson: added entity export + if (mapFile->HasExportEntities()) { + mapFile->WriteExport( (path) ? path : mapFile->GetName() ); + } else { + if ( path ) { + mapFile->Write( path, ".map"); + } else { + idStr osPath; + osPath = mapFile->GetName ( ); + osPath.DefaultFileExtension ( ".map" ); + idFile* file = fileSystem->OpenFileRead ( osPath ); + if ( file ) { + osPath = file->GetFullPath ( ); + fileSystem->CloseFile ( file ); + mapFile->Write ( osPath, ".map", false ); + } else { + mapFile->Write ( file->GetName(), ".map" ); + } + } + } +// RAVEN END + } +} + +// RAVEN BEGIN +// rjohnson: added entity export +bool idGameEdit::MapHasExportEntities( void ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { + return mapFile->HasExportEntities(); + } + return false; +} +// scork: simple query function for the sound editor +// cdr: changed to also return the full string name of the map file (still compatable as a bool test) +const char* idGameEdit::MapLoaded( void ) const { + + const char *psMapName = gameLocal.GetMapName(); + if (psMapName && psMapName[0]) { + return psMapName; + } + return 0; +} + +// cdr: AASTactical +idAASFile* idGameEdit::GetAASFile( int i ) { + if (gameLocal.GetAAS( i )) { + return gameLocal.GetAAS( i )->GetFile(); + } + return 0; +} +// RAVEN END + +/* +================ +idGameEdit::MapSetEntityKeyVal +================ +*/ +void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + mapent->epairs.Set( key, val ); + } + } +} + +/* +================ +idGameEdit::MapCopyDictToEntity +================ +*/ +void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + const char *key = kv->GetKey(); + const char *val = kv->GetValue(); + mapent->epairs.Set( key, val ); + } + } + } +} + + + +/* +================ +idGameEdit::MapGetUniqueMatchingKeyVals +================ +*/ +int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *ent = mapFile->GetEntity( i ); + if ( ent ) { + const char *k = ent->epairs.GetString( key ); + if ( k && *k && count < max ) { + list[count++] = k; + } + } + } + } + return count; +} + +/* +================ +idGameEdit::MapAddEntity +================ +*/ +void idGameEdit::MapAddEntity( const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = new idMapEntity(); + ent->epairs = *dict; + mapFile->AddEntity( ent ); + } +} + +/* +================ +idGameEdit::MapRemoveEntity +================ +*/ +void idGameEdit::MapRemoveEntity( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = mapFile->FindEntity( name ); + if ( ent ) { + mapFile->RemoveEntity( ent ); + } + } +} + + +/* +================ +idGameEdit::MapGetEntitiesMatchignClassWithString +================ +*/ +int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + int entCount = mapFile->GetNumEntities(); + for ( int i = 0 ; i < entCount; i++ ) { + idMapEntity *ent = mapFile->GetEntity(i); + if (ent) { + idStr work = ent->epairs.GetString("classname"); + if ( work.Icmp( classname ) == 0 ) { + if ( match && *match ) { + work = ent->epairs.GetString( "soundgroup" ); + if ( count < max && work.Icmp( match ) == 0 ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } else if ( count < max ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } + } + } + } + return count; +} + + +/* +================ +idGameEdit::MapEntityTranslate +================ +*/ +void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + idVec3 origin; + mapent->epairs.GetVector( "origin", "", origin ); + origin += v; + mapent->epairs.SetVector( "origin", origin ); + } + } +} + +// RAVEN BEGIN +// bdube: new game edit stuff +/* +================ +idGameEdit::PlayerTraceFromEye +================ +*/ +bool idGameEdit::PlayerTraceFromEye ( trace_t &results, float length, int contentMask ) { + idVec3 start; + idVec3 end; + idAngles angles; + + PlayerGetEyePosition( start ); + PlayerGetEyePosition( end ); + PlayerGetViewAngles ( angles ); + + end += angles.ToForward() * length; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint ( gameLocal.GetLocalPlayer(), results, start, end, contentMask, gameLocal.GetLocalPlayer() ); +// RAVEN END +} + +/* +================ +idGameEdit::EffectRefreshTemplate +================ +*/ +void idGameEdit::EffectRefreshTemplate ( const idDecl *effect ) const { + rvClientEntity* cent; + + // Restart all effects + for ( cent = gameLocal.clientSpawnedEntities.Next(); cent; cent = cent->spawnNode.Next() ) { + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex ( ) == effect->Index() ) { + clientEffect->Restart ( ); + } + } + } +} + +/* +================ +idGameEdit::GetGameTime +================ +*/ +int idGameEdit::GetGameTime ( int *previous ) const { + if ( previous ) { + *previous = gameLocal.previousTime; + } + + return gameLocal.time; +} + +/* +================ +idGameEdit::SetGameTime +================ +*/ +void idGameEdit::SetGameTime ( int time ) const { + gameLocal.time = time; + gameLocal.previousTime = time; +} + +/* +================ +idGameEdit::TracePoint +================ +*/ +bool idGameEdit::TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint( gameLocal.GetLocalPlayer(), results, start, end, contentMask, NULL ); +// RAVEN END +} + +/* +================ +idGameEdit::CacheDictionaryMedia +================ +*/ +void idGameEdit::CacheDictionaryMedia ( const idDict* dict ) const { + gameLocal.CacheDictionaryMedia ( dict ); +} + +/* +================ +idGameEdit::SetCamera +================ +*/ +void idGameEdit::SetCamera ( idEntity* camera ) const { + gameLocal.SetCamera ( dynamic_cast(camera) ); +} + +/* +================ +idGameEdit::ScriptGetStatementLineNumber +================ +*/ +int idGameEdit::ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).linenumber; +} + +/* +================ +idGameEdit::ScriptGetStatementFileName +================ +*/ +const char* idGameEdit::ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const { + return program->GetFilename ( program->GetStatement ( instructionPointer ).file ); +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).op; +} + +/* +================ +idGameEdit::ScriptGetCurrentFunction +================ +*/ +void* idGameEdit::ScriptGetCurrentFunction ( idInterpreter* interpreter ) const { + return (void*)interpreter->GetCurrentFunction ( ); +} + +/* +================ +idGameEdit::ScriptGetCurrentFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const { + if ( interpreter->GetCurrentFunction ( ) ) { + return interpreter->GetCurrentFunction ( )->Name(); + } + return ""; +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetCallstackDepth ( idInterpreter* interpreter ) const { + return interpreter->GetCallstackDepth ( ); +} + +/* +================ +idGameEdit::ScriptGetCallstackFunction +================ +*/ +void* idGameEdit::ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const { + return (void*)interpreter->GetCallstack ( )[depth].f; +} + +/* +================ +idGameEdit::ScriptGetCallstackFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].f->Name(); +} + +/* +================ +idGameEdit::ScriptGetCallstackStatement +================ +*/ +int idGameEdit::ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].s; +} + +/* +================ +idGameEdit::ScriptIsReturnOperator +================ +*/ +bool idGameEdit::ScriptIsReturnOperator ( int op ) const { + return op == OP_RETURN; +} + +/* +================ +idGameEdit::ScriptGetRegisterValue +================ +*/ +const char* idGameEdit::ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const { + static char value[4096]; + idStr out; + + value[0] = '\0'; + if ( interpreter->GetRegisterValue ( varname, out, callstackDepth ) ) { + idStr::snPrintf ( value, 4095, out.c_str() ); + } + + return value; +} + +/* +================ +idGameEdit::ScriptGetThread +================ +*/ +idThread* idGameEdit::ScriptGetThread ( idInterpreter* interpreter ) const { + return interpreter->GetThread(); +} + +/* +================ +idGameEdit::ThreadGetCount +================ +*/ +int idGameEdit::ThreadGetCount ( void ) { + return idThread::GetThreads().Num(); +} + +/* +================ +idGameEdit::ThreadGetThread +================ +*/ +idThread* idGameEdit::ThreadGetThread ( int index ) { + return idThread::GetThreads()[index]; +} + +/* +================ +idGameEdit::ThreadGetName +================ +*/ +const char* idGameEdit::ThreadGetName ( idThread* thread ) { + return thread->GetThreadName ( ); +} + +/* +================ +idGameEdit::ThreadGetNumber +================ +*/ +int idGameEdit::ThreadGetNumber ( idThread* thread ) { + return thread->GetThreadNum ( ); +} + +/* +================ +idGameEdit::ThreadGetState +================ +*/ +const char* idGameEdit::ThreadGetState ( idThread* thread ) { + if ( thread->IsDying() ) { + return "Dying"; + } else if ( thread->IsWaiting() ) { + return "Waiting"; + } else if ( thread->IsDoneProcessing() ) { + return "Stopped"; + } + + return "Running"; +} + +/* +================ +idGameEdit::GetClassDebugInfo +================ +*/ +void idGameEdit::GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ) { + const_cast( entity )->GetDebugInfo ( proc, userdata ); +} + +/* +================ +idGameEdit::GetGameEntityRegisterTime +================ +*/ +int idGameEdit::GetGameEntityRegisterTime ( void ) const { + return gameLocal.entityRegisterTime; +} + +/* +================ +idGameEdit::GetFirstSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetFirstSpawnedEntity ( void ) const { + return gameLocal.spawnedEntities.Next(); +} + +/* +================ +idGameEdit::GetNextSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetNextSpawnedEntity ( idEntity* from ) const { + if ( !from ) { + return NULL; + } + return from->spawnNode.Next(); +} + + +// RAVEN END + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant +void idGameEdit::FlushUnusedAnims ( void ) { + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END +} + +/* +=============================================================================== + + rvModviewModel + + Actor model for modview + +=============================================================================== +*/ + +class rvModviewModel : public idActor { +public: + CLASS_PROTOTYPE( rvModviewModel ); + + rvModviewModel ( void ); + +private: + + void Event_Speak ( const char* lipsync ); +}; + +CLASS_DECLARATION( idActor, rvModviewModel ) + EVENT( AI_Speak, rvModviewModel::Event_Speak ) +END_CLASS + +/* +===================== +rvModviewModel::rvModviewModel +===================== +*/ +rvModviewModel::rvModviewModel ( void ) { +} + +/* +===================== +rvModviewModel::Event_Speak +===================== +*/ +void rvModviewModel::Event_Speak ( const char* lipsync ) { + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + lipsync = spawnArgs.GetString ( lipsync ); + if ( !lipsync || !*lipsync ) { + return; + } + + if ( head ) { + head->StartLipSyncing( lipsync ); + } else { + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +// RAVEN END + diff --git a/source/mpgame/GameEdit.h b/source/mpgame/GameEdit.h new file mode 100644 index 0000000..e70503a --- /dev/null +++ b/source/mpgame/GameEdit.h @@ -0,0 +1,96 @@ + +#ifndef __GAME_EDIT_H__ +#define __GAME_EDIT_H__ + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +class idCursor3D : public idEntity { +public: + CLASS_PROTOTYPE( idCursor3D ); + + idCursor3D( void ); + ~idCursor3D( void ); + + void Spawn( void ); + void Present( void ); + void Think( void ); + + idForce_Drag drag; + idVec3 draggedPosition; +}; + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +class idDragEntity { +public: + idDragEntity( void ); + ~idDragEntity( void ); + + void Clear(); + void Update( idPlayer *player ); + void SetSelected( idEntity *ent ); + idEntity * GetSelected( void ) const { return selected.GetEntity(); } + void DeleteSelected( void ); + void BindSelected( void ); + void UnbindSelected( void ); + +private: + idEntityPtr dragEnt; // entity being dragged + jointHandle_t joint; // joint being dragged + int id; // id of body being dragged + idVec3 localEntityPoint; // dragged point in entity space + idVec3 localPlayerPoint; // dragged point in player space + idStr bodyName; // name of the body being dragged + idCursor3D * cursor; // cursor entity + idEntityPtr selected; // last dragged entity + + void StopDrag( void ); +}; + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ +typedef struct selectedTypeInfo_s { + idTypeInfo *typeInfo; + idStr textKey; +} selectedTypeInfo_t; + +class idEditEntities { +public: + idEditEntities( void ); + bool SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ); + void AddSelectedEntity( idEntity *ent ); + void RemoveSelectedEntity( idEntity *ent ); + void ClearSelectedEntities( void ); + void DisplayEntities( void ); + bool EntityIsSelectable( idEntity *ent, idVec4 *color = NULL, idStr *text = NULL ); +// RAVEN BEGIN +// bdube: added + idEntity* FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ); +// RAVEN END +private: + int nextSelectTime; + idList selectableEntityClasses; + idList selectedEntities; +}; + +#endif /* !__GAME_EDIT_H__ */ diff --git a/source/mpgame/Game_Debug.cpp b/source/mpgame/Game_Debug.cpp new file mode 100644 index 0000000..59c1143 --- /dev/null +++ b/source/mpgame/Game_Debug.cpp @@ -0,0 +1,372 @@ +//---------------------------------------------------------------- +// Game_Debug.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +rvGameDebug gameDebug; + +/* +=============================================================================== + + rvGameDebug + +=============================================================================== +*/ + +/* +================ +rvGameDebug::rvGameDebug +================ +*/ +rvGameDebug::rvGameDebug ( void ) { +} + +/* +================ +rvGameDebug::Init +================ +*/ +void rvGameDebug::Init ( void ) { + focusEntity = NULL; + overrideEntity = NULL; + currentHud = NULL; + memset ( &hud, 0, sizeof(hud) ); + + jumpIndex = -1; + jumpPoints.Clear ( ); +} + +/* +================ +rvGameDebug::Shutdown +================ +*/ +void rvGameDebug::Shutdown ( void ) { + nonGameState.Clear ( ); + gameStats.Clear ( ); + + currentHud = NULL; + focusEntity = NULL; + overrideEntity = NULL; + + memset ( &hud, 0, sizeof(hud) ); +} + +/* +================ +rvGameDebug::Think +================ +*/ +void rvGameDebug::BeginFrame ( void ) { + int hudIndex; + + inFrame = true; + + hudIndex = g_showDebugHud.GetInteger(); + if ( hudIndex <= 0 ) { + focusEntity = NULL; + currentHud = NULL; + return; + } + + // Update the current debug hud if the cvar has changed + if ( g_showDebugHud.IsModified() || !currentHud ) { + if ( hudIndex > DBGHUD_MAX ) { + g_showDebugHud.SetInteger( 0 ); + focusEntity = NULL; + currentHud = NULL; + return; + } + + g_showDebugHud.ClearModified( ); + + // If the debug hud hasnt been loaded yet then load it now + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->FindGui( va("guis/debug/hud%d.gui",hudIndex), true, true, true ); + + // If the hud wasnt found auto-generate one + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->Alloc(); + } + } + + // Cache the debug hud state. + currentHud = hud[hudIndex]; + } + + currentHud->ClearState ( ); + + // IF there is an override entity just use that, otherwise find one that + // is in front of the players crosshair + if ( overrideEntity ) { + focusEntity = overrideEntity; + overrideEntity = NULL; + } else { + idPlayer* player; + idVec3 start; + idVec3 end; + trace_t tr; + + player = gameLocal.GetLocalPlayer ( ); + start = player->GetEyePosition(); + end = start + player->viewAngles.ToForward() * 4096.0f; + + gameLocal.TracePoint( player, tr, start, end, MASK_SHOT_BOUNDINGBOX, player ); + if ( tr.fraction < 1.0 && tr.c.entityNum != ENTITYNUM_WORLD ) { + focusEntity = static_cast(gameLocal.entities[ tr.c.entityNum ]); + } else { + focusEntity = NULL; + } + } + + // Automatically add some basic entity information + if ( focusEntity ) { + SetInt ( "entityNumber", focusEntity->entityNumber ); + SetInt ( "entityHealth", focusEntity->health ); + SetString ( "entityName", focusEntity->name ); + SetString ( "entityClass", focusEntity->GetClassname ( ) ); + } + + // General map information + SetString ( "mapname", gameLocal.GetMapName ( ) ); + SetString ( "version", cvarSystem->GetCVarString ( "si_version" ) ); + if ( gameLocal.GetLocalPlayer() ) { + SetString ( "viewpos", gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin().ToString() ); + } +} + +/* +================ +rvGameDebug::EndFrame +================ +*/ +void rvGameDebug::EndFrame ( void ) { + inFrame = false; +} + +/* +================ +rvGameDebug::DrawHud +================ +*/ +void rvGameDebug::DrawHud ( void ) { + if ( !currentHud ) { + return; + } + + // The scratch hud displays key value pairs in a list so + // we need to push the keys into the list + if ( IsHudActive ( DBGHUD_SCRATCH ) ) { + int index; + idDict tempState; + + tempState.Copy ( currentHud->State() ); + currentHud->ClearState ( ); + + for ( index = 0; index < tempState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + + kv = tempState.GetKeyVal ( index ); + SetString ( va("scratchKey_item_%d", index ), kv->GetKey() ); + SetString ( va("scratchValue_item_%d", index ), kv->GetValue() ); + } + } + else { + int index; + for ( index = 0; index < nonGameState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + kv = nonGameState.GetKeyVal ( index ); + currentHud->SetStateString ( kv->GetKey(), kv->GetValue() ); + } + } + + // Activate the hud to ensure lists get updated and redraw it + currentHud->StateChanged ( gameLocal.time ); + currentHud->Redraw( gameLocal.time ); +} + +/* +================ +rvGameDebug::AppendList +================ +*/ +void rvGameDebug::AppendList ( const char* listname, const char* value ) { + if ( !currentHud ) { + return; + } + + int count; + char countName[1024]; + char itemName[1024]; + + idStr::snPrintf ( countName, 1023, "%sCount", listname ); + count = GetInt ( countName ); + + idStr::snPrintf ( itemName, 1023, "%s_item_%d", listname, count ); + SetString ( va("%s_item_%d", listname, count ), value ); + + SetInt ( countName, count + 1 ); +} + +/* +================ +rvGameDebug::Set +================ +*/ +void rvGameDebug::SetInt ( const char* key, int value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateInt( key, value ); + } + } else { + nonGameState.SetInt ( key, value ); + } +} + +void rvGameDebug::SetFloat ( const char* key, float value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateFloat( key, value ); + } + } else { + nonGameState.SetFloat ( key, value ); + } +} + +void rvGameDebug::SetString ( const char* key, const char* value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateString( key, value ); + } + } else { + nonGameState.Set ( key, value ); + } +} + +/* +================ +rvGameDebug::Get +================ +*/ +int rvGameDebug::GetInt ( const char* key ) { + return currentHud ? currentHud->State().GetInt ( key ) : 0; +} + +float rvGameDebug::GetFloat ( const char* key ) { + return currentHud ? currentHud->State().GetFloat ( key ) : 0.0f; +} + +const char* rvGameDebug::GetString ( const char* key ) { + return currentHud ? currentHud->State().GetString ( key ) : ""; +} + +/* +================ +rvGameDebug::SetStat +================ +*/ +void rvGameDebug::SetStatInt ( const char* key, int value ) { + gameStats.SetInt ( key, value ); +} + +void rvGameDebug::SetStatFloat ( const char* key, float value ) { + gameStats.SetFloat ( key, value ); +} + +void rvGameDebug::SetStatString ( const char* key, const char* value ) { + gameStats.Set ( key, value ); +} + +/* +================ +rvGameDebug::GetStat +================ +*/ +int rvGameDebug::GetStatInt ( const char* key ) { + return gameStats.GetInt ( key ); +} + +float rvGameDebug::GetStatFloat ( const char* key ) { + return gameStats.GetFloat ( key ); +} + +const char* rvGameDebug::GetStatString ( const char* key ) { + return gameStats.GetString ( key ); +} + + +/* +================ +rvGameDebug::JumpAdd +================ +*/ +void rvGameDebug::JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ) { + debugJumpPoint_t jump; + jump.name = name; + jump.origin = origin; + jump.angles = angles; + jumpPoints.Append ( jump ); +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( const char* name ) { + int index; + for ( index = 0; index < jumpPoints.Num(); index ++ ) { + if ( !jumpPoints[index].name.Icmp ( name ) ) { + JumpTo ( index ); + return; + } + } +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( int jumpIndex ) { + if ( jumpIndex >= jumpPoints.Num() ) { + return; + } + + jumpIndex = jumpIndex; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player ) { + player->Teleport( jumpPoints[jumpIndex].origin, jumpPoints[jumpIndex].angles, NULL ); + } +} + +/* +================ +rvGameDebug::JumpNext +================ +*/ +void rvGameDebug::JumpNext ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + 1 ) % jumpPoints.Num() ); +} + +/* +================ +rvGameDebug::JumpPrev +================ +*/ +void rvGameDebug::JumpPrev ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + jumpPoints.Num() - 1 ) % jumpPoints.Num() ); +} diff --git a/source/mpgame/Game_Debug.h b/source/mpgame/Game_Debug.h new file mode 100644 index 0000000..6a14e56 --- /dev/null +++ b/source/mpgame/Game_Debug.h @@ -0,0 +1,95 @@ +//---------------------------------------------------------------- +// Game_Debug.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_DEBUG_H__ +#define __GAME_DEBUG_H__ + +#define DBGHUD_NONE (0) +#define DBGHUD_PLAYER (1<<0) +#define DBGHUD_PHYSICS (1<<1) +#define DBGHUD_AI (1<<2) +#define DBGHUD_VEHICLE (1<<3) +#define DBGHUD_PERFORMANCE (1<<4) +#define DBGHUD_FX (1<<5) +#define DBGHUD_MAPINFO (1<<6) +#define DBGHUD_AI_PERFORM (1<<7) +#define DBGHUD_SCRATCH (1<<31) +#define DBGHUD_ANY (0xFFFFFFFF) + +#define DBGHUD_MAX 32 + +typedef struct debugJumpPoint_s +{ + idStr name; + idVec3 origin; + idAngles angles; + +} debugJumpPoint_t; + +class rvGameDebug { +public: + + rvGameDebug( ); + + void Init ( void ); + void Shutdown ( void ); + void BeginFrame ( void ); + void EndFrame ( void ); + + void SetFocusEntity ( idEntity* focusEnt ); + + bool IsHudActive ( int hudMask, const idEntity* focusEnt = NULL ); + + void DrawHud ( void ); + + void AppendList ( const char* listname, const char* value ); + + void SetInt ( const char* key, int value ); + void SetFloat ( const char* key, float value ); + void SetString ( const char* key, const char* value ); + + int GetInt ( const char* key ); + float GetFloat ( const char* key ); + const char* GetString ( const char* key ); + + void SetStatInt ( const char* key, int value ); + void SetStatFloat ( const char* key, float value ); + void SetStatString ( const char* key, const char* value ); + + int GetStatInt ( const char* key ); + float GetStatFloat ( const char* key ); + const char* GetStatString ( const char* key ); + + void JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ); + void JumpTo ( const char* name ); + void JumpTo ( int jumpIndex ); + void JumpNext ( void ); + void JumpPrev ( void ); + +private: + + idList jumpPoints; + int jumpIndex; + + idEntityPtr focusEntity; + idEntityPtr overrideEntity; + idUserInterface * hud[DBGHUD_MAX+1]; + idUserInterface * currentHud; + idDict nonGameState, gameStats; + bool inFrame; +}; + +ID_INLINE bool rvGameDebug::IsHudActive ( int hudMask, const idEntity* ent ) { + return (g_showDebugHud.GetInteger() && (hudMask & (1 << (g_showDebugHud.GetInteger()-1))) && (!ent || focusEntity == ent ) ); +} + +ID_INLINE void rvGameDebug::SetFocusEntity ( idEntity* ent ) { + overrideEntity = ent; +} + +extern rvGameDebug gameDebug; + +#endif /* !__GAME_DEBUG_H__ */ diff --git a/source/mpgame/Game_Log.cpp b/source/mpgame/Game_Log.cpp new file mode 100644 index 0000000..e12bec9 --- /dev/null +++ b/source/mpgame/Game_Log.cpp @@ -0,0 +1,228 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Game_Log.h" + +/* +=============================================================================== + + rvGameLog + +=============================================================================== +*/ + +rvGameLogLocal gameLogLocal; +rvGameLog* gameLog = &gameLogLocal; + +/* +================ +rvGameLogLocal::rvGameLogLocal +================ +*/ +rvGameLogLocal::rvGameLogLocal ( ) { + initialized = false; +} + +/* +================ +rvGameLogLocal::Init +================ +*/ +void rvGameLogLocal::Init ( void ) { + file = NULL; + indexCount = 0; + initialized = true; + + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); +} + +/* +================ +rvGameLogLocal::Shutdown +================ +*/ +void rvGameLogLocal::Shutdown ( void ) { + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); + + if ( initialized && file ) { + const char* out; + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + file->Flush ( ); + fileSystem->CloseFile ( file ); + file = NULL; + } + initialized = false; +} + +/* +================ +rvGameLogLocal::BeginFrame +================ +*/ +void rvGameLogLocal::BeginFrame ( int time ) { + // See if logging has been turned on or not + if ( g_gamelog.GetBool ( ) != initialized ) { + if ( initialized ) { + Shutdown ( ); + return; + } else { + Init ( ); + } + } else if ( !g_gamelog.GetBool ( ) ) { + return; + } +} + +/* +================ +rvGameLogLocal::EndFrame +================ +*/ +void rvGameLogLocal::EndFrame ( void ) { + int i; + const char* out; + bool wroteTime; + + // Dont do anything if not logging + if ( !g_gamelog.GetBool ( ) ) { + return; + } + + // When not in multiplayer, log the players approx origin and viewangles + if ( !gameLocal.isMultiplayer ) { + idPlayer* player; + player = gameLocal.GetLocalPlayer ( ); + if ( player ) { + Set ( "player0_origin", va("%d %d %d", (int)player->GetPhysics()->GetOrigin()[0], (int)player->GetPhysics()->GetOrigin()[1], (int)player->GetPhysics()->GetOrigin()[2] ) ); + Set ( "player0_angles_yaw", va("%g", (float)player->viewAngles[YAW] ) ); + Set ( "player0_angles_pitch", va("%g", (float)player->viewAngles[PITCH] ) ); + Set ( "player0_buttons", player->usercmd.buttons ); + Set ( "player0_health", player->health ); + Set ( "player0_armor", player->inventory.armor ); + } + } + + if ( !file ) { + idStr mapName; + idStr filename; + mapName = gameLocal.serverInfo.GetString( "si_map" ); + mapName.StripFileExtension ( ); + filename = "logs/" + mapName + "/" + cvarSystem->GetCVarString("win_username") + "_"; + + // Find a unique filename + for ( i = 0; fileSystem->ReadFile( filename + va("%06d.log", i ), NULL, NULL ) > 0; i ++ ); + + // Actually open the file now + file = fileSystem->OpenFileWrite ( filename + va("%06d.log", i ), "fs_cdpath" ); + if ( !file ) { + return; + } + + timer_fps.Stop( ); + timer_fps.Clear ( ); + timer_fps.Start ( ); + } else { + static int fpsIndex; + static float fpsValue[4]; + + timer_fps.Stop ( ); + fpsValue[(fpsIndex++)%4] = 1000.0f / (timer_fps.Milliseconds ( ) + 1); + if ( fpsIndex >= 4 ) { + GAMELOG_SET ( "fps", Min(60,(int)((int)(fpsValue[0] + fpsValue[1] + fpsValue[2] + fpsValue[3]) / 40.0f) * 10) ); + } + timer_fps.Clear ( ); + timer_fps.Start ( ); + } + + // Write out any new indexes that were added this frame + for ( ; indexCount < index.Num(); indexCount ++ ) { + const char* out; + out = va("#%d ", indexCount ); + file->Write ( out, strlen ( out ) ); + file->Write ( index[indexCount].c_str(), index[indexCount].Length() ); + file->Write ( "\r\n", 2 ); + } + + // Write out any data that was added this frame + wroteTime = false; + for ( i = frame.Num() - 1; i >= 0; i -- ) { + // TODO: filter + if ( oldframe[i] != frame[i] ) { + if ( !wroteTime ) { + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + wroteTime = true; + } + out = va(" %d \"", i ); + file->Write ( out, strlen(out) ); + file->Write ( frame[i].c_str(), frame[i].Length ( ) ); + file->Write ( "\"", 1 ); + oldframe[i] = frame[i]; + } + } + + if ( wroteTime ) { + file->Write ( "\r\n", 2 ); + file->Flush ( ); + } + + // Clear the frame for next time + for ( i = index.Num() - 1; i >= 0; i -- ) { + frame[i] = ""; + } +} + +/* +================ +rvGameLogLocal::Set +================ +*/ +void rvGameLogLocal::Set ( const char* keyword, const char* value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = value; +} + +void rvGameLogLocal::Set ( const char* keyword, int value ) { + Set ( keyword, va("%d", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, float value ) { + Set ( keyword, va("%g", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, bool value ) { + Set ( keyword, va("%d", (int)value ) ); +} + +/* +================ +rvGameLogLocal::Add +================ +*/ +void rvGameLogLocal::Add ( const char* keyword, int value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%d",atoi(frame[i].c_str()) + value ); +} + +void rvGameLogLocal::Add ( const char* keyword, float value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%g",atof(frame[i].c_str()) + value ); +} + + diff --git a/source/mpgame/Game_Log.h b/source/mpgame/Game_Log.h new file mode 100644 index 0000000..a76d15f --- /dev/null +++ b/source/mpgame/Game_Log.h @@ -0,0 +1,40 @@ + +#ifndef __GAME_LOG_H__ +#define __GAME_LOG_H__ + +//============================================================================ + +class rvGameLogLocal : public rvGameLog { +public: + + rvGameLogLocal ( void ); + + virtual void Init ( void ); + virtual void Shutdown ( void ); + + virtual void BeginFrame ( int time ); + virtual void EndFrame ( void ); + + virtual void Set ( const char* keyword, int value ); + virtual void Set ( const char* keyword, float value ); + virtual void Set ( const char* keyword, const char* value ); + virtual void Set ( const char* keyword, bool value ); + + virtual void Add ( const char* keyword, int value ); + virtual void Add ( const char* keyword, float value ); + +protected: + + int lastTime; + int indexCount; + idStrList index; + idStrList frame; + idStrList oldframe; + idFile* file; + bool initialized; + idTimer timer_fps; +}; + +extern rvGameLogLocal gameLogLocal; + +#endif // __GAME_LOG_H__ diff --git a/source/mpgame/Game_local.cpp b/source/mpgame/Game_local.cpp new file mode 100644 index 0000000..ce2a346 --- /dev/null +++ b/source/mpgame/Game_local.cpp @@ -0,0 +1,8641 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "../sys/AutoVersion.h" + +struct game_name_s { + game_name_s( void ) { + sprintf( string, "Q4MP %s", VERSION_STRING_DOTTED ); + } + char string[256]; +} game_name; + +idCVar gamename( "gamename", game_name.string, CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "" ); + +// RAVEN BEGIN +#include "../bse/BSEInterface.h" +#include "Projectile.h" +#include "client/ClientEffect.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AAS_tactical.h" +#include "Game_Log.h" +// RAVEN END + +//#define UI_DEBUG 1 + +#ifdef GAME_DLL + +idSys * sys = NULL; +idCommon * common = NULL; +idCmdSystem * cmdSystem = NULL; +idCVarSystem * cvarSystem = NULL; +idFileSystem * fileSystem = NULL; +idNetworkSystem * networkSystem = NULL; +idRenderSystem * renderSystem = NULL; +idSoundSystem * soundSystem = NULL; +idRenderModelManager * renderModelManager = NULL; +idUserInterfaceManager * uiManager = NULL; +idDeclManager * declManager = NULL; +idAASFileManager * AASFileManager = NULL; +idCollisionModelManager * collisionModelManager = NULL; + +// RAVEN BEGIN +// jscott: game interface to the fx system +rvBSEManager * bse = NULL; +// RAVEN END + +idCVar * idCVar::staticVars = NULL; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +idCVarHelp * idCVarHelp::staticCVarHelps = NULL; +idCVarHelp * idCVarHelp::staticCVarHelpsTail = NULL; +// RAVEN END + +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); + +#endif + +idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world + +static gameExport_t gameExport; + +// global animation lib +// RAVEN BEGIN +// jsinger: changed to a pointer to prevent its constructor from allocating +// memory before the unified allocator could be initialized +idAnimManager *animationLib = NULL; +// RAVEN END + +// the rest of the engine will only reference the "game" variable, while all local aspects stay hidden +idGameLocal gameLocal; +idGame * game = &gameLocal; // statically pointed at an idGameLocal + +const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { + "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", + "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" +}; + +/* +=========== +GetGameAPI +============ +*/ + +#if __GNUC__ >= 4 +#pragma GCC visibility push(default) +#endif +extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { + + if ( import->version == GAME_API_VERSION ) { + + // set interface pointers used by the game + sys = import->sys; + common = import->common; + cmdSystem = import->cmdSystem; + cvarSystem = import->cvarSystem; + fileSystem = import->fileSystem; + networkSystem = import->networkSystem; + renderSystem = import->renderSystem; + soundSystem = import->soundSystem; + renderModelManager = import->renderModelManager; + uiManager = import->uiManager; + declManager = import->declManager; + AASFileManager = import->AASFileManager; + collisionModelManager = import->collisionModelManager; +// RAVEN BEGIN +// jscott: import the fx system + bse = import->bse; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: import the memory system variables +#ifdef _RV_MEM_SYS_SUPPORT + ::currentHeapArena = import->heapArena; + rvSetAllSysHeaps( import->systemHeapArray ); +#endif +// RAVEN END + } + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // setup export interface + gameExport.version = GAME_API_VERSION; + gameExport.game = game; + gameExport.gameEdit = gameEdit; +// RAVEN BEGIN +// bdube: game log + gameExport.gameLog = gameLog; +// RAVEN END + + return &gameExport; +} +#if __GNUC__ >= 4 +#pragma GCC visibility pop +#endif + +/* +=========== +TestGameAPI +============ +*/ +void TestGameAPI( void ) { + gameImport_t testImport; + gameExport_t testExport; + + testImport.sys = ::sys; + testImport.common = ::common; + testImport.cmdSystem = ::cmdSystem; + testImport.cvarSystem = ::cvarSystem; + testImport.fileSystem = ::fileSystem; + testImport.networkSystem = ::networkSystem; + testImport.renderSystem = ::renderSystem; + testImport.soundSystem = ::soundSystem; + testImport.renderModelManager = ::renderModelManager; + testImport.uiManager = ::uiManager; + testImport.declManager = ::declManager; + testImport.AASFileManager = ::AASFileManager; + testImport.collisionModelManager = ::collisionModelManager; + +// RAVEN BEGIN +// jscott: import the fx system + testImport.bse = ::bse; +// RAVEN END + + testExport = *GetGameAPI( &testImport ); +} + +/* +================ +idGameLocal::BuildModList +================ +*/ +void idGameLocal::BuildModList( ) { + int i; + idStr currentMod; + + int numServers = networkSystem->GetNumScannedServers(); + + if ( filterMod >= 0 && filterMod < modList.Num() ) { + currentMod = modList[ filterMod ]; + } else { + currentMod = ""; + } + + modList.Clear(); + for (i = 0; i < numServers; i++) { + const scannedServer_t *server; + idStr modname; + + server = networkSystem->GetScannedServerInfo( i ); + + server->serverInfo.GetString( "fs_game", "", modname ); + modname.ToLower(); + modList.AddUnique( modname ); + } + + modList.Sort(); + + if ( modList.Num() > 0 && (modList[ 0 ].Cmp( "" ) == 0) ) { + modList.RemoveIndex( 0 ); + } + + filterMod = modList.Num(); + for (i = 0; i < modList.Num(); i++) { + if ( modList[ i ].Icmp( currentMod ) == 0 ) { + filterMod = i; + } + } +} + +/* +================ +FilterByMod +================ +*/ +static int FilterByMod( const int* serverIndex ) { + const scannedServer_t *server; + + if ( gameLocal.filterMod < 0 || gameLocal.filterMod >= gameLocal.modList.Num() ) { + return (int)false; + } + + server = networkSystem->GetScannedServerInfo( *serverIndex ); + + return (int)(gameLocal.modList[ gameLocal.filterMod ].Icmp( server->serverInfo.GetString( "fs_game" ) ) != 0); +} + +static sortInfo_t filterByMod = { + SC_ALL, + NULL, + FilterByMod, + "#str_123006" +}; + +/* +=========== +idGameLocal::idGameLocal +============ +*/ +idGameLocal::idGameLocal() { + Clear(); +} + +/* +=========== +idGameLocal::Clear +============ +*/ +void idGameLocal::Clear( void ) { + int i; + + serverInfo.Clear(); + repeaterInfo.Clear(); + numClients = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + usercmds = NULL; + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + firstFreeIndex = 0; + num_entities = 0; + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortPushers = false; + sortTeamMasters = false; + persistentLevelInfo.Clear(); + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + random.SetSeed( 0 ); + world = NULL; + frameCommandThread = NULL; + testmodel = NULL; + ShutdownInstances(); + clip.Clear(); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + clientsPVS[ i ].i = -1; + clientsPVS[ i ].h = -1; + } + freePlayerPVS = false; + pvs.Shutdown(); + sessionCommand.Clear(); + locationEntities = NULL; + editEntities = NULL; + entityHash.Clear( 1024, MAX_GENTITIES ); + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + framenum = 0; + previousTime = 0; + time = 0; + vacuumAreaNum = 0; + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + mapFileName.Clear(); +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped.Clear(); +// RAVEN END + mapFile = NULL; + spawnCount = INITIAL_SPAWN_COUNT; + memset( isMapEntity, 0, sizeof( bool ) * MAX_GENTITIES ); + + camera = NULL; + +// RAVEN BEGIN +// jscott: for portal skies + portalSky = NULL; +// RAVEN END + + aasList.Clear(); + aasNames.Clear(); +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + spawnArgs.Clear(); + gravity.Set( 0, 0, -1 ); + playerPVS.i = -1; + playerPVS.h = -1; + playerConnectedAreas.i = -1; + playerConnectedAreas.h = -1; + gamestate = GAMESTATE_UNINITIALIZED; + skipCinematic = false; + influenceActive = false; + + localClientNum = 0; + isMultiplayer = false; + isServer = false; + isClient = false; + isTVClient = false; + realClientTime = 0; + isNewFrame = true; + entityDefBits = 0; + + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = false; +// RITUAL END + globalMaterial = NULL; + newInfo.Clear(); + lastGUIEnt = NULL; + lastGUI = 0; + + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + ReallocViewers( 0 ); + + maxViewers = 0; + maxViewer = 0; + viewers = NULL; + viewerEntityStates = NULL; + viewerPVS = NULL; + viewerSnapshots = NULL; + viewerUnreliableMessages = NULL; + + eventQueue.Init(); + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset( clientEntities, 0, sizeof( clientEntities ) ); + memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) ); + + gameDebug.Shutdown ( ); + gameLogLocal.Shutdown ( ); + currentThinkingEntity = NULL; +// RAVEN END + + demoState = DEMO_NONE; + serverDemo = false; + timeDemo = false; + + memset( &usercmd, 0, sizeof( usercmd ) ); + memset( &oldUsercmd, 0, sizeof( oldUsercmd ) ); + followPlayer = -1; // start free flying + + demo_protocol = 0; + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + minSpawnIndex = MAX_CLIENTS; + + modList.Clear(); + filterMod = -1; + if ( networkSystem ) { + networkSystem->UseSortFunction( filterByMod, false ); + } + clientAckSequence = -1; +} + +/* +=========== +idGameLocal::Init + + initialize the game object, only happens once at startup, not each level load +============ +*/ +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +extern idHashTable *visemeTable100; +extern idHashTable *visemeTable66; +extern idHashTable *visemeTable33; +#ifdef RV_UNIFIED_ALLOCATOR +void idGameLocal::Init( void *(*allocator)(size_t size), void (*deallocator)( void *ptr ), size_t (*msize)(void *ptr) ) { +#else +void idGameLocal::Init( void ) { +#endif +// RAVEN END + const idDict *dict; + idAAS *aas; + +#ifndef GAME_DLL + + TestGameAPI(); + +#else + + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + Memory::InitAllocator(allocator, deallocator, msize); +#endif +// RAVEN END + // initialize idLib + idLib::Init(); + + // register static cvars declared in the game + idCVar::RegisterStaticVars(); + + // initialize processor specific SIMD + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + +#endif +// RAVEN BEGIN +// jsinger: these need to be initialized after the InitAllocator call above in order +// to avoid crashes when the allocator is used. + animationLib = new idAnimManager(); + visemeTable100 = new idHashTable; + visemeTable66 = new idHashTable; + visemeTable33 = new idHashTable; +// RAVEN END + + Printf( "------------- Initializing Game -------------\n" ); + Printf( "gamename: %s\n", game_name.string ); + Printf( "gamedate: %s\n", __DATE__ ); + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + idCVarHelp::RegisterStatics(); + +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + idStr prefix=""; + if(cvarSystem->GetCVarBool("com_binaryDeclRead")) + { + prefix = "binary/"; + } + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator, idDeclStreamAllocator ); + //declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclFolderWrapper( prefix + "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( prefix + "af", ".af", DECL_AF ); +#else +// RAVEN END + // register game specific decl types + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator ); +// RAVEN END + // register game specific decl folders +// RAVEN BEGIN +#ifndef RV_SINGLE_DECL_FILE + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); +// bdube: not used in quake 4 +// declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); +// declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); +// declManager->RegisterDeclFolderWrapper( "newpdas", ".pda", DECL_PDA ); +#else + if(!cvarSystem->GetCVarBool("com_SingleDeclFile")) + { + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); + } + else + { + // loads the second set of decls from the file which will contain + // modles, cameras + declManager->LoadDeclsFromFile(); + declManager->FinishLoadingDecls(); + } +#endif +#endif // RV_BINARYDECLS +// RAVEN END + + cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + + Clear(); + + idEvent::Init(); +// RAVEN BEGIN +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + idClass::RegisterClasses(); +// RAVEN END + idClass::Init(); + + InitConsoleCommands(); + // load default scripts + program.Startup( SCRIPT_DEFAULT ); + + // set up the aas +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + dict = FindEntityDefDict( "aas_types", false ); +// RAVEN END + if ( !dict ) { + Error( "Unable to find entityDef for 'aas_types'" ); + } + + // allocate space for the aas + const idKeyValue *kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + aas = idAAS::Alloc(); + aasList.Append( aas ); + aasNames.Append( kv->GetValue() ); + kv = dict->MatchPrefix( "type", kv ); + } + + gamestate = GAMESTATE_NOMAP; + + Printf( "...%d aas types\n", aasList.Num() ); + Printf( "game initialized.\n" ); + Printf( "---------------------------------------------\n" ); + +// RAVEN BEGIN +// bdube: debug stuff + gameDebug.Init(); + gameLogLocal.Init(); + +// jscott: facial animation init + if( !FAS_Init( "annosoft" ) ) { + common->Warning( "Failed to load viseme file" ); + } + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_RENDER); +// shouchard: make sure ban list starts out in a known state + banListLoaded = false; + banListChanged = false; + memset( clientGuids, 0, sizeof( clientGuids ) ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + for(int i=0;iAddSortFunction( filterByMod ); + + incompatibleMaps = 0; +} + +/* +=========== +idGameLocal::Shutdown + + shut down the entire game +============ +*/ +void idGameLocal::Shutdown( void ) { + + int i; + + if ( !common ) { + return; + } + +// RAVEN BEGIN +// jscott: FAS + FAS_Shutdown(); +// shouchard: clean up ban list stuff + SaveBanList(); + FlushBanList(); +// RAVEN END + + Printf( "--------------- Game Shutdown ---------------\n" ); + + networkSystem->RemoveSortFunction( filterByMod ); + + mpGame.Shutdown(); + + MapShutdown(); + + aasList.DeleteContents( true ); + aasNames.Clear(); + + idAI::FreeObstacleAvoidanceNodes(); + + // shutdown the model exporter + idModelExport::Shutdown(); + + idEvent::Shutdown(); + + program.Shutdown(); + + idClass::Shutdown(); + + // clear list with forces + idForce::ClearForceList(); + + // free the program data + program.FreeData(); + + // delete the .map file + delete mapFile; + mapFile = NULL; + + // free the collision map + collisionModelManager->FreeMap( GetMapName() ); + +// RAVEN BEGIN +// jscott: free up static objects + for( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[i].Clear(); + } + + serverInfo.Clear(); + repeaterInfo.Clear(); + persistentLevelInfo.Clear(); + sessionCommand.Clear(); + mapFileName.Clear(); + mapFileNameStripped.Clear(); + entityUsageList.Clear(); + + newInfo.Clear(); + spawnArgs.Clear(); + shakeSounds.Clear(); + aiManager.Clear(); +// RAVEN END + + ShutdownConsoleCommands(); + + // free memory allocated by class objects + Clear(); + + // shut down the animation manager +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->Shutdown(); +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + entityUsageList.Clear(); +// RAVEN END + + Printf( "---------------------------------------------\n" ); + + instances.DeleteContents( true ); + +#ifdef GAME_DLL + + // remove auto-completion function pointers pointing into this DLL + cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); + + // enable leak test + Mem_EnableLeakTest( "game" ); + + // shutdown idLib + idLib::ShutDown(); + +#endif +} + +/* +=========== +idGameLocal::SaveGame + +save the current player state, level name, and level state +the session may have written some data to the file already +============ +*/ +// RAVEN BEGIN +// mekberg: added saveTypes +void idGameLocal::SaveGame( idFile *f, saveType_t saveType ) { +// RAVEN END + int i; + idEntity *ent; + idEntity *link; + + //remove weapon effect entites from the world + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + + GetLocalPlayer()->weapon->PreSave(); + } + + idSaveGame savegame( f ); + + if (g_flushSave.GetBool( ) == true ) { + // force flushing with each write... for tracking down + // save game bugs. + f->ForceFlush(); + } + + savegame.WriteBuildNumber( BUILD_NUMBER ); + + // go through all entities and threads and add them to the object list + for( i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if ( ent ) { + if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { + continue; + } + for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { + savegame.AddObject( link ); + } + } + } + + idList threads; + threads = idThread::GetThreads(); + + for( i = 0; i < threads.Num(); i++ ) { + savegame.AddObject( threads[i] ); + } + +// RAVEN BEGIN +// abahr: saving clientEntities + rvClientEntity* clientEnt = NULL; + for( i = 0; i < MAX_CENTITIES; ++i ) { + clientEnt = clientEntities[ i ]; + if( !clientEnt ) { + continue; + } +// if( clientEnt->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.AddObject( clientEnt ); + } +// RAVEN END + + // write out complete object list + savegame.WriteObjectList(); + + program.Save( &savegame ); + + savegame.WriteInt( g_skill.GetInteger() ); + + savegame.WriteDict( &serverInfo ); + + savegame.WriteInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't write out userinfo. Grab from cvars +// savegame.WriteDict( &userInfo[ i ] ); +// RAVEN END +// savegame.WriteUsercmd( usercmds[ i ] ); + savegame.WriteDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.WriteObject( entities[ i ] ); + savegame.WriteInt( spawnIds[ i ] ); + } + +// RAVEN BEGIN +// abahr: more clientEntities saving + for( i = 0; i < MAX_CENTITIES; i++ ) { +// if( clientEntities[ i ] && clientEntities[ i ]->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.WriteObject( clientEntities[ i ] ); + savegame.WriteInt( clientSpawnIds[ i ] ); + } +// RAVEN END + + savegame.WriteInt( firstFreeIndex ); + savegame.WriteInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.WriteObject( world ); + + savegame.WriteInt( spawnedEntities.Num() ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + savegame.WriteObject( ent ); + } + +// RAVEN BEGIN +// abahr: saving scriptObject proxies + savegame.WriteInt( scriptObjectProxies.Num() ); + for( i = 0; i < scriptObjectProxies.Num(); ++i ) { + scriptObjectProxies[i].Save( &savegame ); + } +// abahr: save out client stuff + savegame.WriteInt( clientSpawnedEntities.Num() ); + for( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + savegame.WriteObject( clientEnt ); + } +// RAVEN END + + savegame.WriteInt( activeEntities.Num() ); + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( numEntitiesToDeactivate ); + savegame.WriteBool( sortPushers ); + savegame.WriteBool( sortTeamMasters ); + savegame.WriteDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.WriteFloat( globalShaderParms[ i ] ); + } + + savegame.WriteInt( random.GetSeed() ); + savegame.WriteObject( frameCommandThread ); + + // clip + // push + // pvs + + testmodel = NULL; +// RAVEN BEGIN +// bdube: no test fx +// testFx = NULL; +// RAVEN END + + savegame.WriteString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system save itself too. + aiManager.Save( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.WriteInt( cinematicSkipTime ); + savegame.WriteInt( cinematicStopTime ); + savegame.WriteInt( cinematicMaxSkipTime ); + savegame.WriteBool( inCinematic ); + savegame.WriteBool( skipCinematic ); + + savegame.WriteBool( isMultiplayer ); + savegame.WriteInt( gameType ); + + savegame.WriteInt( framenum ); + savegame.WriteInt( previousTime ); + savegame.WriteInt( time ); + + savegame.WriteInt( vacuumAreaNum ); + + savegame.WriteInt( entityDefBits ); + savegame.WriteBool( isServer ); + savegame.WriteBool( isClient ); +// RAVEN BEGIN + savegame.WriteBool( isListenServer ); +// RAVEN END + + savegame.WriteInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.WriteInt( realClientTime ); + savegame.WriteBool( isNewFrame ); + + savegame.WriteBool( mapCycleLoaded ); + savegame.WriteInt( spawnCount ); + + if ( !locationEntities ) { + savegame.WriteInt( 0 ); + } else { + savegame.WriteInt( gameRenderWorld->NumAreas() ); + for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { + savegame.WriteObject( locationEntities[ i ] ); + } + } + + savegame.WriteObject( camera ); + + savegame.WriteMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertActor.Save( &savegame ); + lastAIAlertEntity.Save( &savegame ); + savegame.WriteInt( lastAIAlertEntityTime ); + savegame.WriteInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.WriteDict( &spawnArgs ); + + savegame.WriteInt( playerPVS.i ); + savegame.WriteInt( playerPVS.h ); + savegame.WriteInt( playerConnectedAreas.i ); + savegame.WriteInt( playerConnectedAreas.h ); + + savegame.WriteVec3( gravity ); + + // gamestate + + savegame.WriteBool( influenceActive ); + savegame.WriteInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // write out pending events + idEvent::Save( &savegame ); + + savegame.Close(); + +// RAVEN BEGIN +// mekberg: added saveTypes and wrapped saveMessage + if ( saveType != ST_AUTO && GetLocalPlayer() && GetLocalPlayer()->GetHud() ) { + GetLocalPlayer()->SaveMessage(); + } + +// jshepard: resume weapon operation + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + GetLocalPlayer()->weapon->PostSave(); + } +// RAVEN END +} + +/* +=========== +idGameLocal::GetPersistentPlayerInfo +============ +*/ +const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { + idEntity *ent; + + persistentPlayerInfo[ clientNum ].Clear(); + ent = entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SavePersistantInfo(); + } + + return persistentPlayerInfo[ clientNum ]; +} + +/* +=========== +idGameLocal::SetPersistentPlayerInfo +============ +*/ +void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + persistentPlayerInfo[ clientNum ] = playerInfo; +} + +/* +============ +idGameLocal::Printf +============ +*/ +void idGameLocal::Printf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::DPrintf +============ +*/ +void idGameLocal::DPrintf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::Warning +============ +*/ +void idGameLocal::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +============ +idGameLocal::DWarning +============ +*/ +void idGameLocal::DWarning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->DWarning( "%s", text ); + } +} + +/* +============ +idGameLocal::Error +============ +*/ +void idGameLocal::Error( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN +// scork: some model errors arrive here during validation which kills the whole process, so let's just warn about them instead... + if ( common->DoingDeclValidation() ) { + this->Warning( "%s", text ); + return; + } +// RAVEN END + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Error( "%s", text ); + } else { + common->Error( "%s", text ); + } +} + +/* +=============== +gameError +=============== +*/ +void gameError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + gameLocal.Error( "%s", text ); +} + +/* +=========== +idGameLocal::SetLocalClient +============ +*/ +void idGameLocal::SetLocalClient( int clientNum ) { + localClientNum = clientNum; +} + +/* +=========== +idGameLocal::SetUserInfo +============ +*/ +const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + bool modifiedInfo = false; + + this->isClient = isClient; + + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + idGameLocal::userInfo[ clientNum ] = userInfo; + + // server sanity + if ( !isClient ) { + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < numClients; i++ ) { + if ( i == clientNum ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_ because of client %d\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), i ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + modifiedInfo |= static_cast( entities[ clientNum ] )->UserInfoChanged(); + } + + if ( !isClient ) { + // now mark this client in game + mpGame.EnterGame( clientNum ); + } + } + + if ( modifiedInfo ) { + newInfo = idGameLocal::userInfo[ clientNum ]; + return &newInfo; + } + return NULL; +} + +/* +=========== +idGameLocal::GetUserInfo +============ +*/ +const idDict* idGameLocal::GetUserInfo( int clientNum ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return &userInfo[ clientNum ]; + } + return NULL; +} + +/* +=========== +idGameLocal::IsClientActive +============ +*/ +bool idGameLocal::IsClientActive( int clientNum ) { + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + return true; + } + + return false; +} + +/* +=========== +idGameLocal::RepeaterSetUserInfo +============ +*/ +const idDict* idGameLocal::RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + assert( cvarSystem->GetCVarInteger( "ri_maxViewers" ) > clientNum ); + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + idDict *info; + + int i; + bool modifiedInfo = false; + + info = &viewers[ clientNum ].info; + *info = userInfo; + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( info->GetString( "ui_name" ) ) ) { + info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < maxViewer; i++ ) { + if ( i == clientNum ) { + continue; + } + + if ( !viewers[ i ].active ) { + continue; + } + + if ( !idStr::Icmp( info->GetString( "ui_name" ), viewers[ i ].info.GetString( "ui_name" ) ) ) { + info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + + if ( modifiedInfo ) { + newInfo = *info; + return &newInfo; + } + + return NULL; +} + +/* +=========== +idGameLocal::UpdateRepeaterInfo +============ +*/ + +void idGameLocal::UpdateRepeaterInfo( bool transmit ) { + if ( !isRepeater ) { + return; + } + + repeaterInfo = serverInfo; + repeaterInfo.Set( "si_tv", "1" ); + + // fudge a few cvars so old browsers display correctly + repeaterInfo.Set( "si_usePass", ri_useViewerPass.GetBool() ? "1" : "0" ); + + const char *ri_name = cvarSystem->GetCVarString( "ri_name" ); + if ( ri_name[0] ) { + if ( !serverInfo.GetString( "ri_origName" ) [0] ) { + repeaterInfo.Set( "ri_origName", serverInfo.GetString( "si_name" ) ); + } + repeaterInfo.Set( "si_name", ri_name ); + } + + const char *si_serverURL = cvarSystem->GetCVarString( "si_serverURL" ); + if ( si_serverURL[0] ) { + if ( !serverInfo.GetString( "ri_origServerURL" ) [0] ) { + repeaterInfo.Set( "ri_origServerURL", serverInfo.GetString( "si_serverURL" ) ); + } + repeaterInfo.Set( "si_serverURL", si_serverURL ); + } + + repeaterInfo.Copy( *cvarSystem->MoveCVarsToDict( CVAR_REPEATERINFO ) ); + + networkSystem->RepeaterSetInfo( repeaterInfo ); + + if ( transmit ) { + // Let our clients know the server info changed + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.repeaterInfo, NULL ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } +} + +/* +=========== +idGameLocal::SetServerInfo +============ +*/ +void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + bool timeLimitChanged = false; + +// RAVEN BEGIN +// mekberg: clear announcer and reschedule time announcements + if ( ( isClient || isListenServer ) && mpGame.GetGameState( ) && mpGame.GetGameState( )->GetMPGameState( ) == GAMEON && + serverInfo.GetInt( "si_timelimit" ) != _serverInfo.GetInt( "si_timelimit" ) ) { + timeLimitChanged = true; + } + + serverInfo = _serverInfo; + + if ( timeLimitChanged ) { + mpGame.ClearAnnouncerSounds( ); + mpGame.ScheduleTimeAnnouncements( ); + } +// RAVEN END + + UpdateRepeaterInfo( true ); + + if ( isServer ) { + // Let our clients know the server info changed + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg, true ); + } else if ( isClient ) { + networkSystem->ClientSetServerInfo( gameLocal.serverInfo ); + isTVClient = ( gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) ? true : false; + } +} + +/* +=================== +idGameLocal::LoadMap + +Initializes all map variables common to both save games and spawned games. +=================== +*/ +void idGameLocal::LoadMap( const char *mapName, int randseed ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PARSER); +// RAVEN END + bool sameMap = (mapFile && idStr::Icmp(mapFile->GetName(), mapName) == 0); + + networkSystem->SetLoadingText( mapName ); + + InitAsyncNetwork(); + + // these can changed based upon sp / mp + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + + if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { + // load the .map file + if ( mapFile ) { + delete mapFile; + } + mapFile = new idMapFile; + if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { + delete mapFile; + mapFile = NULL; + Error( "Couldn't load %s", mapName ); + } +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + mapFile->Resolve(); +// RAVEN END + } + mapFileName = mapFile->GetName(); + + assert(!idStr::Cmp(mapFileName, mapFile->GetName())); + +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped = mapFileName; + mapFileNameStripped.StripFileExtension(); + mapFileNameStripped.StripPath(); + + const char* entityFilter; + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + mapFileNameStripped += " "; + mapFileNameStripped += entityFilter; + } +// RAVEN END + + // load the collision map + networkSystem->SetLoadingText( common->GetLocalizedString( "#str_107668" ) ); + collisionModelManager->LoadMap( mapFile, false ); + + numClients = 0; + + // initialize all entities for this game + memset( entities, 0, sizeof( entities ) ); + usercmds = NULL; + memset( spawnIds, -1, sizeof( spawnIds ) ); + spawnCount = INITIAL_SPAWN_COUNT; + + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortTeamMasters = false; + sortPushers = false; + lastGUIEnt = NULL; + lastGUI = 0; + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear ( ); + memset ( clientSpawnIds, -1, sizeof(clientSpawnIds ) ); + memset ( clientEntities, 0, sizeof(clientEntities) ); + firstFreeClientIndex = 0; +// RAVEN END + + globalMaterial = NULL; + + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + num_entities = MAX_CLIENTS; + firstFreeIndex = MAX_CLIENTS; + + // reset the random number generator. + random.SetSeed( isMultiplayer ? randseed : 0 ); + + camera = NULL; + world = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// RAVEN END + +// RAVEN BEGIN +// bdube: merged + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + + previousTime = 0; + time = 0; + framenum = 0; + sessionCommand = ""; + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = 0; +// RITUAL END + + vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + if ( !editEntities ) { + editEntities = new idEditEntities; + } + + if( gameLocal.isMultiplayer ) { + gravity.Set( 0, 0, -g_mp_gravity.GetFloat() ); + } else { + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + } + + spawnArgs.Clear(); + +// RAVEN BEGIN +// nmckenzie: + //make sure we clear all reachabilities we marked as blocked! + aiManager.UnMarkAllReachBlocked(); + aiManager.Clear(); +// RAVEN END + + skipCinematic = false; + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + +// RAVEN BEGIN +// ddynerman: main world instance + PACIFIER_UPDATE; + AddInstance( 0, true ); + assert( instances.Num() == 1 && instances[ 0 ]->GetInstanceID() == 0 ); +// RAVEN END + pvs.Init(); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // Experimental use of game's PVS for reducing amount of stuff streamed. Will + // do this a bit more cleanly if I decide to keep this. + extern idPVS* pvsForStreaming; + pvsForStreaming=&pvs; +#endif +// RAVEN END + + playerPVS.i = -1; + playerConnectedAreas.i = -1; + + // load navigation system for all the different monster sizes + for( i = 0; i < aasNames.Num(); i++ ) { + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); + } + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + AI_MoveInitialize(); +// RAVEN END + + FindEntityDef( "preCacheExtras", false ); + + if ( !sameMap ) { + mapFile->RemovePrimitiveData(); + } + +// RAVEN BEGIN +// ddynerman: ambient light list + ambientLights.Clear(); +// RAVEN END +} + +/* +=================== +idGameLocal::LocalMapRestart +=================== +*/ +void idGameLocal::LocalMapRestart( int instance ) { + int i, latchSpawnCount; + + Printf( "----------- Game Map Restart (%s) ------------\n", instance == -1 ? "all instances" : va( "instance %d", instance ) ); + + // client always respawns everything, so make sure it picks up the right map entities + if( instance == -1 || isClient ) { + memset( isMapEntity, 0, sizeof(bool) * MAX_GENTITIES ); + } else { + assert( instance >= 0 && instance < instances.Num() ); + + for( int i = 0; i < instances[ instance ]->GetNumMapEntities(); i++ ) { + if ( instances[ instance ]->GetMapEntityNumber( i ) >= 0 && instances[ instance ]->GetMapEntityNumber( i ) < MAX_GENTITIES ) { + isMapEntity[ instances[ instance ]->GetMapEntityNumber( i ) ] = false; + } + } + } + + gamestate = GAMESTATE_SHUTDOWN; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); + } + } + + eventQueue.Shutdown(); + + MapClear( false, instance ); + + // clear the sound system + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + + // clear icons + iconManager->Shutdown(); + + // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId + // if we don't do that, network clients are confused and don't show any map entities + latchSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + + gamestate = GAMESTATE_STARTUP; + + program.Restart(); + + InitScriptForMap(); + + MapPopulate( instance ); + + // once the map is populated, set the spawnCount back to where it was so we don't risk any collision + // (note that if there are no players in the game, we could just leave it at it's current value) + spawnCount = latchSpawnCount; + + // setup the client entities again + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->Restart(); + } + } + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +=================== +idGameLocal::MapRestart +=================== +*/ +void idGameLocal::MapRestart( int instance ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idDict newInfo; + int i; + const idKeyValue *keyval, *keyval2; + + if ( isClient ) { + SetGameType(); + LocalMapRestart( instance ); + } else { + + if ( mpGame.PickMap( "", true ) ) { + common->Warning( "map %s and gametype %s are incompatible, aborting map change", si_map.GetString(), si_gameType.GetString() ); + if ( (++incompatibleMaps) >= 50 ) { + common->Warning( "too many incompatible maps for gametype %s; setting gametype to DM.", si_gameType.GetString() ); + si_gameType.SetString( "DM" ); + } + return; + } + + incompatibleMaps = 0; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + // this has to be after the cvars are moved to the dict + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + break; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + break; + } +//RAVEN BEGIN + //asalmon: need to restart if the game type has changed but the map has not cause someone could be connecting + if( keyval->GetValue().Icmp( keyval2->GetValue() ) && (!keyval->GetKey().Icmp("si_gametype"))) + { + break; + } +//RAVEN END + + // some calculations about time are done from the frame, and + // that changes when si_fps changes. full restart resets to 0. + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + !keyval->GetKey().Icmp( "si_fps" ) ) { + break; + } + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + + SetGameType(); + + mpGame.isBuyingAllowedRightNow = false; + + if ( i != newInfo.GetNumKeyVals() ) { + gameLocal.sessionCommand = "nextMap"; + } else { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg, true ); + + if ( isRepeater ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( repeaterInfo, NULL ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } + + LocalMapRestart( instance ); + mpGame.MapRestart(); + } + } +} + +/* +=================== +idGameLocal::VerifyServerSettings_f +=================== +*/ +void idGameLocal::VerifyServerSettings_f( const idCmdArgs &args ) { + gameLocal.mpGame.PickMap( si_gameType.GetString() ); +} + +/* +=================== +idGameLocal::MapRestart_f +=================== +*/ +void idGameLocal::MapRestart_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running - use spawnServer\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + return; + } + + int instance = -1; + if( args.Argc() > 1 ) { + instance = atoi( args.Args( 1 ) ); + if( instance < 0 || instance >= gameLocal.GetNumInstances() ) { + common->Warning( "idGameLocal::MapRestart_f() - Invalid instance '%d' specified\n", instance ); + return; + } + gameLocal.LocalMapRestart( instance ); + return; + } + + gameLocal.MapRestart( instance ); +} + +/* +=================== +idGameLocal::NextMap +=================== +*/ +bool idGameLocal::NextMap( void ) { + const function_t *func; + idThread *thread; + idDict newInfo; + const idKeyValue *keyval, *keyval2; + int i; + const char *mapCycleList, *currentMap; + +//RAVEN BEGIN +//asalmon: pick another map on Xenon +#ifdef _XENON + //Live()->PickMap(); + return true; +#endif +//RAVEN END + +// RAVEN BEGIN +// rjohnson: traditional map cycle +// si_mapCycle "mp/q4dm4;mp/q4dm5;mp/q4dm6" + mapCycleList = si_mapCycle.GetString(); + if ( mapCycleList && strlen( mapCycleList ) ) { + idLexer src; + idToken token, firstFound; + int numMaps = 0; + bool foundMap = false; + + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( mapCycleList, strlen( mapCycleList ), "idGameLocal::NextMap" ); + if ( src.IsLoaded() ) { + + currentMap = si_map.GetString(); + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + continue; + } + numMaps++; + + if ( numMaps == 1 ) { + // guarantee that we use a map no matter what ( or when we wrap ) + firstFound = token; + } + if ( foundMap ) { + firstFound = token; + break; + } + + if ( idStr::Icmp( token, currentMap ) == 0 ) { + foundMap = true; + } + } + + if ( firstFound != "" ) { + si_map.SetString( firstFound ); + return true; + } + } + + return false; + } +// RAVEN END + + if ( !g_mapCycle.GetString()[0] ) { + Printf( common->GetLocalizedString( "#str_104294" ) ); + return false; + } + if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) { + if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) { + Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() ); + return false; + } else { + g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) ); + } + } + + Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + if ( !func ) { + program.CompileFile( g_mapCycle.GetString(), false ); + func = program.FindFunction( "mapcycle::cycle" ); + } + if ( !func ) { + Printf( "Couldn't find mapcycle::cycle\n" ); + return false; + } + thread = new idThread( func ); + thread->Start(); + delete thread; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 || keyval->GetValue().Icmp( keyval2->GetValue() ) ) { + break; + } + } + return ( i != newInfo.GetNumKeyVals() ); +} + +/* +=================== +idGameLocal::NextMap_f +=================== +*/ +void idGameLocal::NextMap_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running\n" ); + return; + } + + gameLocal.NextMap( ); + // next map was either voted for or triggered by a server command - always restart + gameLocal.MapRestart( ); +} + +/* +=============== +idGameLocal::GetStartingIndexForInstance +=============== +*/ +int idGameLocal::GetStartingIndexForInstance( int instanceID ) { + if ( isServer ) { + assert( instancesEntityIndexWatermarks.Num() >= instanceID ); + if ( instanceID == 0 ) { + return MAX_CLIENTS; + } else { + // the high watermark of the previous instance is the starting index of the next one + return instancesEntityIndexWatermarks[ instanceID - 1 ]; + } + } else { + assert( instanceID == 0 ); + return clientInstanceFirstFreeIndex; + } +} + +/* +=============== +idGameLocal::ServerSetEntityIndexWatermark +keep track of the entity layout at the server - specially when there are multiple instances ( tourney ) +=============== +*/ +void idGameLocal::ServerSetEntityIndexWatermark( int instanceID ) { + if ( isClient ) { + return; + } + instancesEntityIndexWatermarks.AssureSize( instanceID + 1, MAX_CLIENTS ); + // make sure there is no drift. if a value was already set it has to match + // otherwise that means the server is repopulating with different indexes, and that would likely lead to net corruption + assert( instancesEntityIndexWatermarks[ instanceID ] == MAX_CLIENTS || instancesEntityIndexWatermarks[ instanceID ] == firstFreeIndex ); + instancesEntityIndexWatermarks[ instanceID ] = firstFreeIndex; +} + +/* +=============== +idGameLocal::ServerSetMinSpawnIndex +=============== +*/ +void idGameLocal::ServerSetMinSpawnIndex( void ) { + if ( isClient ) { + return; + } + // setup minSpawnIndex with enough headroom so gameplay entities don't cause bad offsets + // only needed on server, clients are completely slaved up to server entity layout + if ( !idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) ) { + minSpawnIndex = MAX_CLIENTS + GetNumMapEntities() * MAX_INSTANCES; + } +} + +/* +=================== +idGameLocal::MapPopulate +=================== +*/ +void idGameLocal::MapPopulate( int instance ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + if ( isMultiplayer ) { + cvarSystem->SetCVarBool( "r_skipSpecular", false ); + } + + minSpawnIndex = MAX_CLIENTS; + + // parse the key/value pairs and spawn entities +// RAVEN BEGIN +// ddynerman: instance code + // reload the instances + if ( instance == -1 ) { + int i; + firstFreeIndex = MAX_CLIENTS; + for ( i = 0; i < instances.Num(); i++ ) { + if ( instances[ i ] ) { + instances[ i ]->Restart(); + + ServerSetEntityIndexWatermark( i ); + } + } + } else { + assert( instance >= 0 && instance < instances.Num() ); + instances[ instance ]->Restart(); + } +// RAVEN END + + ServerSetMinSpawnIndex(); + + // mark location entities in all connected areas + SpreadLocations(); + + // RAVEN BEGIN + // ddynerman: prepare the list of spawn spots + InitializeSpawns(); + // RAVEN END + + // execute pending events before the very first game frame + // this makes sure the map script main() function is called + // before the physics are run so entities can bind correctly + Printf( "------------ Processing events --------------\n" ); + idEvent::ServiceEvents(); +} + +/* +=================== +idGameLocal::InitFromNewMap +=================== +*/ +void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + this->isServer = isServer; + this->isClient = isClient; +// RAVEN BEGIN +// ddynerman: listen server + this->isListenServer = isServer && !cvarSystem->GetCVarBool( "net_serverDedicated" ); +// RAVEN END + this->isMultiplayer = isServer || isClient; + + if ( this->isClient && gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) { + this->isTVClient = true; + } + + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 3 ) { + networkSystem->HTTPEnable( this->isServer || this->isRepeater ); + } + + if ( !this->isMultiplayer ) + gameLocal.Error( "This mod is for multiplayer only" ); + + PACIFIER_UPDATE; + +//RAVEN BEGIN +//asalmon: stats for single player + if (!this->isMultiplayer) { +#ifdef _MPBETA + return; +#else + statManager->EndGame(); +#ifdef _XENON + Live()->DeleteSPSession(true); +#endif + statManager->Shutdown(); + statManager->Init(); + statManager->BeginGame(); + statManager->ClientConnect(0); +#ifdef _XENON + Live()->CreateSPSession(); +#endif +#endif // _MPBETA + } +//RAVEN END + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "-------------- Game Map Init ----------------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->BeginLevelLoad(); +#endif + + SetGameType(); + + LoadMap( mapName, randseed ); + + InitScriptForMap(); + + MapPopulate(); + + mpGame.Reset(); + + mpGame.Precache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->EndLevelLoad(); +#endif +// RAVEN END + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "---------------------------------------------\n" ); +} + +/* +================= +idGameLocal::InitFromSaveGame +================= +*/ +bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + int num; + idEntity *ent; + idDict si; + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "---------- Game Map Init SaveGame -----------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + + idRestoreGame savegame( saveGameFile ); + + savegame.ReadBuildNumber(); + + // Create the list of all objects in the game + savegame.CreateObjects(); + + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame + if ( program.Restore( &savegame ) == false ) { + + // Abort the load process, and let the session know so that it can restart the level + // with the player persistent data. + savegame.DeleteObjects(); + program.Restart(); + + return false; + } + + // load the map needed for this savegame + LoadMap( mapName, 0 ); + + savegame.ReadInt( i ); + g_skill.SetInteger( i ); + + // precache any media specified in the map + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *mapEnt = mapFile->GetEntity( i ); + + if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { + CacheDictionaryMedia( &mapEnt->epairs ); + const char *classname = mapEnt->epairs.GetString( "classname" ); + if ( classname != '\0' ) { + FindEntityDef( classname, false ); + } + } + } + + savegame.ReadDict( &si ); + SetServerInfo( si ); + + savegame.ReadInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't read in userinfo. Grab from cvars +// savegame.ReadDict( &userInfo[ i ] ); +// RAVEN END +// savegame.ReadUsercmd( usercmds[ i ] ); + savegame.ReadDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); + savegame.ReadInt( spawnIds[ i ] ); + + // restore the entityNumber + if ( entities[ i ] != NULL ) { + entities[ i ]->entityNumber = i; + } + } + + // Precache the player +// RAVEN BEGIN +// bdube: changed so we actually cache stuff + FindEntityDef( idPlayer::GetSpawnClassname( ) ); + +// abahr: saving clientEntities + for( i = 0; i < MAX_CENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( clientEntities[ i ] ) ); + savegame.ReadInt( clientSpawnIds[ i ] ); + + // restore the entityNumber + if ( clientEntities[ i ] != NULL ) { + clientEntities[ i ]->entityNumber = i; + } + } +// RAVEN END + + savegame.ReadInt( firstFreeIndex ); + savegame.ReadInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.ReadObject( reinterpret_cast( world ) ); + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->spawnNode.AddToEnd( spawnedEntities ); + } + } + +// RAVEN BEGIN +// abahr: save scriptObject proxies + savegame.ReadInt( num ); + scriptObjectProxies.SetNum( num ); + for( i = 0; i < num; ++i ) { + scriptObjectProxies[i].Restore( &savegame ); + } +// abahr: save client entity stuff + rvClientEntity* clientEnt = NULL; + savegame.ReadInt( num ); + for( i = 0; i < num; ++i ) { + savegame.ReadObject( reinterpret_cast( clientEnt ) ); + assert( clientEnt ); + if ( clientEnt ) { + clientEnt->spawnNode.AddToEnd( clientSpawnedEntities ); + } + } +// RAVEN END + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->activeNode.AddToEnd( activeEntities ); + } + } + + savegame.ReadInt( numEntitiesToDeactivate ); + savegame.ReadBool( sortPushers ); + savegame.ReadBool( sortTeamMasters ); + savegame.ReadDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); + } + + savegame.ReadInt( i ); + random.SetSeed( i ); + + savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); + + // clip + // push + // pvs + + // testmodel = "" + // testFx = "" + + savegame.ReadString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system load itself too. + aiManager.Restore( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.ReadInt( cinematicSkipTime ); + savegame.ReadInt( cinematicStopTime ); + savegame.ReadInt( cinematicMaxSkipTime ); + savegame.ReadBool( inCinematic ); + savegame.ReadBool( skipCinematic ); + + savegame.ReadBool( isMultiplayer ); + savegame.ReadInt( (int &)gameType ); + + savegame.ReadInt( framenum ); + savegame.ReadInt( previousTime ); + savegame.ReadInt( time ); + + savegame.ReadInt( vacuumAreaNum ); + + savegame.ReadInt( entityDefBits ); + savegame.ReadBool( isServer ); + savegame.ReadBool( isClient ); +// RAVEN BEGIN + savegame.ReadBool( isListenServer ); +// RAVEN END + + savegame.ReadInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.ReadInt( realClientTime ); + savegame.ReadBool( isNewFrame ); + + savegame.ReadBool( mapCycleLoaded ); + savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); + if ( num ) { + if ( num != gameRenderWorld->NumAreas() ) { + savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); + } + + locationEntities = new idLocationEntity *[ num ]; + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); + } + } + + savegame.ReadObject( reinterpret_cast( camera ) ); + + savegame.ReadMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity.Restore( &savegame ); + savegame.ReadInt( lastAIAlertEntityTime ); + lastAIAlertActor.Restore( &savegame ); + savegame.ReadInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.ReadDict( &spawnArgs ); + + savegame.ReadInt( playerPVS.i ); + savegame.ReadInt( (int &)playerPVS.h ); + savegame.ReadInt( playerConnectedAreas.i ); + savegame.ReadInt( (int &)playerConnectedAreas.h ); + + savegame.ReadVec3( gravity ); + + // gamestate is restored after restoring everything else + + savegame.ReadBool( influenceActive ); + savegame.ReadInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // Read out pending events + idEvent::Restore( &savegame ); + + savegame.RestoreObjects(); + + mpGame.Reset(); + + mpGame.Precache(); + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); + + return true; +} + +/* +=========== +idGameLocal::MapClear +=========== +*/ +// RAVEN BEGIN +// ddynerman: multiple instances +void idGameLocal::MapClear( bool clearClients, int instance ) { +// RAVEN END + int i; + +// RAVEN BEGIN +// bdube: delete client entities first since they reference real entities + for( i = 0; i < MAX_CENTITIES; i++ ) { + // on the server we need to keep around client entities bound to entities in our instance2 + if( gameLocal.isServer && gameLocal.GetLocalPlayer() && instance != -1 && + instance != gameLocal.GetLocalPlayer()->GetInstance() && + clientEntities[ i ] && clientEntities[ i ]->GetBindMaster() && + clientEntities[ i ]->GetBindMaster()->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + delete clientEntities[ i ]; + clientEntities[ i ] = NULL; + clientSpawnIds[ i ] = -1; + } +// RAVEN END + + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { + if( instance >= 0 && entities[ i ] && entities[ i ]->GetInstance() != instance ) { + continue; + } + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); +// RAVEN BEGIN +// see FIXME in idRestoreGame::Error + entities[ i ] = NULL; +// RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + spawnedEntities.Clear(); + } +// RAVEN END + + if ( !clearClients ) { + // add back the hashes of the clients/stuff in other instances + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + entities[ i ]->spawnNode.AddToEnd( spawnedEntities ); + } +// RAVEN END + } + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; +// abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + +// RAVEN BEGIN +// ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); +// RAVEN END + + // set the free index back at MAX_CLIENTS for registering entities again + // the deletion of map entities causes the index to go down, but it may not have been left exactly at 32 + // under such conditions, the map populate that will follow may be offset + firstFreeIndex = MAX_CLIENTS; +} + +// RAVEN BEGIN +// ddynerman: instance-specific clear +void idGameLocal::InstanceClear( void ) { + // note: clears all ents EXCEPT those in the instance + int i; + + for( i = 0; i < MAX_CENTITIES; i++ ) { + delete clientEntities[ i ]; + assert( !clientEntities[ i ] ); + clientSpawnIds[ i ] = -1; + } + + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + if( i == ENTITYNUM_CLIENT || i == ENTITYNUM_WORLD || i == ENTITYNUM_NONE ) { + continue; + } + + if( entities[ i ] && entities[ i ]->fl.persistAcrossInstances ) { + //common->DPrintf( "Instance %d: persistant: excluding ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + continue; + } + + //if( entities[ i ] ) { + //Printf( "Instance %d: CLEARING ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + //} + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); + // RAVEN BEGIN + // see FIXME in idRestoreGame::Error + entities[ i ] = NULL; + // RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; + // abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + + // RAVEN BEGIN + // ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); + + nextLagoCheck = 0; +} +// RAVEN END + +/* +=========== +idGameLocal::MapShutdown +============ +*/ +void idGameLocal::MapShutdown( void ) { + Printf( "------------ Game Map Shutdown --------------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + if ( soundSystem ) { + soundSystem->ResetListener(); + } + +// RAVEN BEGIN +// rjohnson: new blur special effect + renderSystem->ShutdownSpecialEffects(); +// RAVEN END + + // clear out camera if we're in a cinematic + if ( inCinematic ) { + camera = NULL; + inCinematic = false; + } + +// RAVEN BEGIN +// jscott: cleanup playbacks + gameEdit->ShutdownPlaybacks(); +// RAVEN END + + MapClear( true ); + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + +// RAVEN BEGIN +// jscott: make sure any spurious events are killed + idEvent::ClearEventList(); + + // reset the script to the state it was before the map was started + program.Restart(); + +// bdube: game debug + gameDebug.Shutdown( ); + gameLogLocal.Shutdown( ); +// RAVEN END + + iconManager->Shutdown(); + + pvs.Shutdown(); + +// RAVEN BEGIN +// ddynerman: MP multiple instances + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + idClipModel::ClearTraceModelCache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + idForce::ClearForceList(); +#endif +// RAVEN END + + ShutdownAsyncNetwork(); + + mapFileName.Clear(); + + gameRenderWorld = NULL; + + gamestate = GAMESTATE_NOMAP; + + camera = NULL; + portalSky = NULL; + + Printf( "---------------------------------------------\n" ); +} + +/* +=================== +idGameLocal::DumpOggSounds +=================== +*/ +void idGameLocal::DumpOggSounds( void ) { + int i, j, k, size, totalSize; + idFile *file; + idStrList oggSounds, weaponSounds; + const idSoundShader *soundShader; + const soundShaderParms_t *parms; + idStr soundName; + + for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) { + soundShader = static_cast(declManager->DeclByIndex( DECL_SOUND, i, false )); + parms = soundShader->GetParms(); + + if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) { + + const_cast(soundShader)->EnsureNotPurged(); + + for ( j = 0; j < soundShader->GetNumSounds(); j++ ) { + soundName = soundShader->GetSound( j ); + soundName.BackSlashesToSlashes(); + + // don't OGG sounds that cause a shake because that would + // cause continuous seeking on the OGG file which is expensive + if ( parms->shakes != 0.0f ) { + shakeSounds.AddUnique( soundName ); + continue; + } + + // if not voice over or combat chatter + if ( soundName.Find( "/vo/", false ) == -1 && + soundName.Find( "/combat_chatter/", false ) == -1 && + soundName.Find( "/bfgcarnage/", false ) == -1 && + soundName.Find( "/enpro/", false ) == - 1 && + soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) { + // don't OGG weapon sounds + if ( soundName.Find( "weapon", false ) != -1 || + soundName.Find( "gun", false ) != -1 || + soundName.Find( "bullet", false ) != -1 || + soundName.Find( "bfg", false ) != -1 || + soundName.Find( "plasma", false ) != -1 ) { + weaponSounds.AddUnique( soundName ); + continue; + } + } + + for ( k = 0; k < shakeSounds.Num(); k++ ) { + if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) { + break; + } + } + if ( k < shakeSounds.Num() ) { + continue; + } + + oggSounds.AddUnique( soundName ); + } + } + } + + file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" ); + if ( file == NULL ) { + common->Warning( "Couldn't open makeogg.bat" ); + return; + } + + // list all the shake sounds + totalSize = 0; + for ( i = 0; i < shakeSounds.Num(); i++ ) { + size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL ); + totalSize += size; + shakeSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 ); + + // list all the weapon sounds + totalSize = 0; + for ( i = 0; i < weaponSounds.Num(); i++ ) { + size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL ); + totalSize += size; + weaponSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 ); + + // list commands to convert all other sounds to ogg + totalSize = 0; + for ( i = 0; i < oggSounds.Num(); i++ ) { +// RAVEN BEGIN +// rjohnson: changed path to raven's directories +// jnewquist: Use filesystem search path to get absolute file path + idStr tempFile; + idFile *f = fileSystem->OpenFileRead( oggSounds[i] ); + if ( !f ) { + continue; + } + size = f->Length(); + totalSize += size; + tempFile = f->GetFullPath(); + fileSystem->CloseFile(f); + f = NULL; + tempFile.Replace( "/", "\\" ); + +// jnewquist: prevent alterations to files relative to cdpath + const char *cdPath = cvarSystem->GetCVarString("fs_cdpath"); + const int cdPathLen = idStr::Length(cdPath); + if ( cdPathLen > 0 && idStr::Icmpn(cdPath, tempFile, cdPathLen) == 0 ) { + file->Printf( "rem Ignored file from CD path: %s\n", tempFile.c_str() ); + continue; + } + + file->Printf( "echo %d / %d\n", i, oggSounds.Num() ); + file->Printf( "k:\\utility\\oggenc -q 0 \"%s\"\n", tempFile.c_str() ); + file->Printf( "del \"%s\"\n", tempFile.c_str() ); +// RAVEN END + } + file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 ); + + fileSystem->CloseFile( file ); + + shakeSounds.Clear(); +} + +/* +=================== +idGameLocal::GetShakeSounds +=================== +*/ +void idGameLocal::GetShakeSounds( const idDict *dict ) { + const idSoundShader *soundShader; + const char *soundShaderName; + idStr soundName; + + soundShaderName = dict->GetString( "s_shader" ); + if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) { + soundShader = declManager->FindSound( soundShaderName ); + + for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) { + soundName = soundShader->GetSound( i ); + soundName.BackSlashesToSlashes(); + + shakeSounds.AddUnique( soundName ); + } + } +} + +/* +=================== +idGameLocal::CacheDictionaryMedia + +This is called after parsing an EntityDef and for each entity spawnArgs before +merging the entitydef. It could be done post-merge, but that would +avoid the fast pre-cache check associated with each entityDef +=================== +*/ +void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { + idDict spawnerArgs; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( dict == NULL ) { +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild") ) { + DumpOggSounds(); + } +#endif + return; + } + +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) { + GetShakeSounds( dict ); + } +#endif + + int numVals = dict->GetNumKeyVals(); + + for ( int i = 0; i < numVals; ++i ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + + #define MATCH(s) \ + (!kv->GetKey().Icmpn( s, strlen(s) )) + /**/ + + if ( !kv || !kv->GetValue().Length() ) { + continue; + } + + if ( MATCH( "model" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MODEL); + declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); + // precache model/animations + if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( kv->GetValue() ); + // precache .cm files only + collisionModelManager->PreCacheModel( GetMapName(), kv->GetValue() ); + } + } else if ( MATCH( "s_shader" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "snd_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "gui_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_GUI); + if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) + || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) + || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { + // unfortunate flag names, they aren't actually a gui + } else { + declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); + uiManager->FindGui( kv->GetValue().c_str(), true ); + } + } else if ( MATCH( "texture" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "mtr_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "screenShot" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "inv_icon" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "teleport" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + int teleportType = atoi( kv->GetValue() ); + const char *p = ( teleportType ) ? va( "effects/teleporter%i.fx", teleportType ) : "effects/teleporter.fx"; + declManager->FindType( DECL_EFFECT, p ); + } else if ( MATCH( "fx_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); + declManager->FindEffect( kv->GetValue() ); + } else if ( MATCH( "skin" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_SKIN, kv->GetValue() ); + } else if ( MATCH( "def_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + FindEntityDef( kv->GetValue().c_str(), false ); + } else if ( MATCH( "playback_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching playback %s\n", kv->GetValue().c_str() ); + declManager->FindPlayback( kv->GetValue() ); + } else if ( MATCH( "lipsync_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching lipsync %s\n", kv->GetValue().c_str() ); + declManager->FindLipSync( kv->GetValue() ); + declManager->FindSound ( kv->GetValue() ); + } else if ( MATCH( "icon " ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + idLexer src ( LEXFL_ALLOWPATHNAMES ); + idToken token; + idToken token2; + src.LoadMemory( kv->GetValue(), kv->GetValue().Length(), "icon" ); + + src.ReadToken ( &token ); + if ( src.CheckTokenString ( "," ) ) { + int x, y, w, h; + src.ReadToken ( &token2 ) ; + x = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + y = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + w = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + h = token2.GetIntValue ( ); + + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token, x, y, w, h ); + } else { + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token ); + } + } else if ( MATCH( "spawn_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + spawnerArgs.Set ( kv->GetKey ( ).c_str() + 6, kv->GetValue ( ) ); + } + + #undef MATCH + } + + if ( spawnerArgs.GetNumKeyVals() ) { + CacheDictionaryMedia ( &spawnerArgs ); + } +// RAVEN END +} + +/* +=========== +idGameLocal::InitScriptForMap +============ +*/ +void idGameLocal::InitScriptForMap( void ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + // create a thread to run frame commands on + frameCommandThread = new idThread(); + frameCommandThread->ManualDelete(); + frameCommandThread->SetThreadName( "frameCommands" ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_SCRIPT); + + // run the main game script function (not the level specific main) + const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_DEFAULT); + + if ( func != NULL ) { + idThread *thread = new idThread( func ); + if ( thread->Start() ) { + // thread has finished executing, so delete it + delete thread; + } + } + +// RAVEN END + +} + +/* +=========== +idGameLocal::SpawnPlayer +============ +*/ +void idGameLocal::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idEntity *ent; + idDict args; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + // they can connect + common->DPrintf( "SpawnPlayer: %i\n", clientNum ); + + args.SetInt( "spawn_entnum", clientNum ); + args.Set( "name", va( "player%d", clientNum + 1 ) ); +// RAVEN BEGIN +// bdube: changed marine class + args.Set( "classname", idPlayer::GetSpawnClassname() ); +// RAVEN END + + // This takes a really long time. + PACIFIER_UPDATE; + if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) { + Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); + } + + // make sure it's a compatible class + if ( !ent->IsType( idPlayer::GetClassType() ) ) { + Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); + } + + PACIFIER_UPDATE; + + if ( clientNum != ENTITYNUM_NONE ) { + if ( clientNum >= numClients ) { + numClients = clientNum + 1; + } + } + + mpGame.SpawnPlayer( clientNum ); +} + +/* +================ +idGameLocal::GetClientByNum +================ +*/ +idPlayer *idGameLocal::GetClientByNum( int current ) const { + if ( current == MAX_CLIENTS ) { + return NULL; + } + if ( current == ENTITYNUM_NONE ) { + return static_cast( entities[ current ] ); + } + if ( current < 0 || current >= numClients ) { + // that's a bit nasty but I suppose it has it's use + current = 0; + } + if ( entities[current] ) { + return static_cast( entities[ current ] ); + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByName +================ +*/ +idPlayer *idGameLocal::GetClientByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END +// RAVEN BEGIN +// bdube: escape codes + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { +// RAVEN END + return static_cast( ent ); + } + } + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByCmdArgs +================ +*/ +idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const { + idPlayer *player; + idStr client = args.Argv( 1 ); + if ( !client.Length() ) { + return NULL; + } + // we don't allow numeric ui_name so this can't go wrong + if ( client.IsNumeric() ) { + player = GetClientByNum( atoi( client.c_str() ) ); + } else { + player = GetClientByName( client.c_str() ); + } + if ( !player ) { + common->Printf( "Player '%s' not found\n", client.c_str() ); + } + return player; +} + +/* +================ +idGameLocal::GetClientNumByName +================ +*/ +int idGameLocal::GetClientNumByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { + return i; + } + } + } + return -1; +} + +/* +================ +idGameLocal::GetNextClientNum +================ +*/ +int idGameLocal::GetNextClientNum( int _current ) const { + int i, current; + + current = 0; + for ( i = 0; i < numClients; i++) { + current = ( _current + i + 1 ) % numClients; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ current ] && entities[ current ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return current; + } + } + + return current; +} + +/* +================ +idGameLocal::GetLocalPlayer + +Nothing in the game tic should EVER make a decision based on what the +local client number is, it shouldn't even be aware that there is a +draw phase even happening. + +localClientNum == MAX_CLIENTS when playing server demos +don't send back the wrong entity obviously +================ +*/ +idPlayer *idGameLocal::GetLocalPlayer() const { + if ( localClientNum == MAX_CLIENTS ) { + return static_cast( entities[ ENTITYNUM_NONE ] ); + } + + if ( localClientNum < 0 || localClientNum >= MAX_CLIENTS ) { + return NULL; + } + + if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) { + // not fully in game yet + return NULL; + } + + return static_cast( entities[ localClientNum ] ); +} + +/* +================ +idGameLocal::SetupClientPVS +for client spectating others, get the pvs of spectated +================ +*/ +pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { + if ( player->GetPrivateCameraView() ) { + return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); + } else if ( camera ) { + return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); + } else { + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + player = static_cast( entities[ player->spectator ] ); + } + return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +================ +idGameLocal::GetSpawnCount +================ +*/ +int idGameLocal::GetSpawnCount ( void ) const { + return spawnCount; +} + +/* +================ +idGameLocal::SetupPortalSkyPVS +================ +*/ +bool idGameLocal::SetupPortalSkyPVS( idPlayer *player ) { + + int i, count, numAreas; + const int *areaNums; + bool *visibleAreas; + + if( !portalSky ) { + + return( false ); + } + + // Allocate room for the area flags + numAreas = gameRenderWorld->NumAreas(); + visibleAreas = ( bool * )_alloca( numAreas ); + memset( visibleAreas, 0, numAreas ); + + // Grab the areas the player can see.... + count = player->GetNumPVSAreas(); + areaNums = player->GetPVSAreas(); + for( i = 0; i < count; i++ ) { + + // Work out the referenced areas + gameRenderWorld->FindVisibleAreas( player->GetPhysics()->GetOrigin(), areaNums[i], visibleAreas ); + } + + // Do any of the visible areas have a skybox? + for( i = 0; i < numAreas; i++ ) { + + if( !visibleAreas[i] ) { + + continue; + } + + if( gameRenderWorld->HasSkybox( i ) ) { + + break; + } + } + + // .. if any one has a skybox component, then merge in the portal sky + return ( i != numAreas ); +} +// RAVEN END + +/* +=============== +idGameLocal::UpdateClientsPVS +=============== +*/ +void idGameLocal::UpdateClientsPVS( void ) { + int i; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[ i ] ) { + continue; + } + assert( clientsPVS[ i ].i == -1 ); + clientsPVS[ i ] = GetClientPVS( static_cast< idPlayer * >( entities[ i ] ), PVS_NORMAL ); + } +} + +/* +================ +idGameLocal::SetupPlayerPVS +================ +*/ +void idGameLocal::SetupPlayerPVS( void ) { + int i = 0; + idPlayer * player = NULL; + pvsHandle_t otherPVS, newPVS; + + UpdateClientsPVS( ); + + playerPVS.i = -1; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[i] ) { + return; + } + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + + player = static_cast( entities[ i ] ); + + if ( playerPVS.i == -1 ) { + playerPVS = clientsPVS[ i ]; + freePlayerPVS = false; // don't try to free it as long as we stick to the client PVS + } else { + otherPVS = clientsPVS[ i ]; + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS = newPVS; + freePlayerPVS = true; // that merged one will need to be freed + } + +// RAVEN BEGIN +// jscott: for portal skies + portalSkyVisible = SetupPortalSkyPVS( player ); + if ( portalSkyVisible ) { + + otherPVS = pvs.SetupCurrentPVS( portalSky->GetPVSAreas(), portalSky->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + freePlayerPVS = true; + } +// RAVEN END + + if ( playerConnectedAreas.i == -1 ) { + playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); + } else { + otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } +} + +/* +================ +idGameLocal::FreePlayerPVS +================ +*/ +void idGameLocal::FreePlayerPVS( void ) { + int i; + + // only clear playerPVS if it's a different handle than the one in clientsPVS + if ( freePlayerPVS && playerPVS.i != -1 ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS.i = -1; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( clientsPVS[ i ].i >= 0 ) { + pvs.FreeCurrentPVS( clientsPVS[ i ] ); + clientsPVS[i].i = -1; + } + } + if ( playerConnectedAreas.i != -1 ) { + pvs.FreeCurrentPVS( playerConnectedAreas ); + playerConnectedAreas.i = -1; + } +} + +/* +================ +idGameLocal::InPlayerPVS + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerPVS( idEntity *ent ) const { + if ( playerPVS.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::InPlayerConnectedArea + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { + if ( playerConnectedAreas.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + + +/* +================ +idGameLocal::UpdateGravity +================ +*/ +void idGameLocal::UpdateGravity( void ) { + idEntity *ent; + + idCVar* gravityCVar = NULL; + + if( gameLocal.isMultiplayer ) { + gravityCVar = &g_mp_gravity; + } else { + gravityCVar = &g_gravity; + } + + if ( gravityCVar->IsModified() ) { + if ( gravityCVar->GetFloat() == 0.0f ) { + gravityCVar->SetFloat( 1.0f ); + } + gravity.Set( 0, 0, -gravityCVar->GetFloat() ); + + // update all physics objects + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + idPhysics *phys = ent->GetPhysics(); + if ( phys ) { + phys->SetGravity( gravity ); + } +// RAVEN BEGIN +// ddynerman: jump pads + } else if ( ent->IsType( rvJumpPad::GetClassType() ) ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + } +// RAVEN END + } + gravityCVar->ClearModified(); + } +} + +/* +================ +idGameLocal::GetGravity +================ +*/ +const idVec3 &idGameLocal::GetGravity( void ) const { + return gravity; +} + +/* +================ +idGameLocal::SortActiveEntityList + + Sorts the active entity list such that pushing entities come first, + actors come next and physics team slaves appear after their master. +================ +*/ +void idGameLocal::SortActiveEntityList( void ) { + idEntity *ent, *next_ent, *master, *part; + + // if the active entity list needs to be reordered to place physics team masters at the front + if ( sortTeamMasters ) { + // Sort bind masters first + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + for ( part = ent->GetBindMaster ( ); part; part = part->GetBindMaster ( ) ) { + // Ensure we dont rerun the whole active entity list if our cached next_ent is one + // of the entities we are moving + if ( next_ent == part ) { + next_ent = next_ent->activeNode.Next(); + part = ent->GetBindMaster ( ); + continue; + } + part->activeNode.Remove(); + part->activeNode.AddToFront( activeEntities ); + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( master && master == ent ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + // if the active entity list needs to be reordered to place pushers at the front + if ( sortPushers ) { + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an actor on the team + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + break; + } + } + // if there is an actor on the team + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an entity on the team using parametric physics + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + break; + } + if ( part->GetPhysics()->IsType( rvPhysics_Spline::GetClassType() ) ) { + break; + } + } + // if there is an entity on the team using parametric physics + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + } + + sortTeamMasters = false; + sortPushers = false; +} + +/* +================ +idGameLocal::MenuFrame +Called each session frame when a map is not running (e.g. usually in the main menu) +================ +*/ +void idGameLocal::MenuFrame( void ) { } + +/* +================ +idGameLocal::RunFrame +================ +*/ +// RAVEN BEGIN +gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + idTimer timer_misc, timer_misc2; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + + editors = activeEditors; + isLastPredictFrame = lastCatchupFrame; + + assert( !isClient ); + + player = GetLocalPlayer(); + + do { + + previousTime = time; + + // sanity - game frame is counted on both sides of the DLL fence + // but I've seen cases where it would be offset and we need to make sure that doesn't happen + if ( serverGameFrame != framenum ) { + common->Warning( "core and game frame counters are out of sync: %d/%d", serverGameFrame, framenum ); + if ( serverGameFrame > framenum ) { + int delta = serverGameFrame - framenum; + time += delta * GetMSec(); + framenum = serverGameFrame; + } if ( serverGameFrame < framenum ) { + // don't do anything, let the core catchup to us + memset( &ret, 0, sizeof( ret ) ); + return ret; + } + } + + // update the game time + framenum++; + // bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); + + realClientTime = time; + { +TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()"); + // bdube: added advanced debug support + gameDebug.BeginFrame( ); + gameLogLocal.BeginFrame( time ); + } + +#ifdef GAME_DLL + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + } +#endif + + // make sure the random number counter is used each frame so random events + // are influenced by the player's actions + random.RandomInt(); + + if ( player ) { + // update the renderview so that any gui videos play from the right frame + view = player->GetRenderView(); + if ( view ) { + gameRenderWorld->SetRenderView( view ); + } + } + + // If modview is running then let it think + common->ModViewThink( ); + + // rjohnson: added option for guis to always think + common->RunAlwaysThinkGUIs( time ); + + // nmckenzie: Let AI System stuff update itself. + if ( !isMultiplayer ) { +#ifndef _MPBETA + aiManager.RunFrame(); +#endif // !_MPBETA + } + + timer_misc.Start(); + + // set the user commands for this frame + usercmds = clientCmds; + + // create a merged pvs for all players + // do this before we process events, which may rely on PVS info + SetupPlayerPVS(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->StartFrame(); + } + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + + // jscott: for timing and effect handling + timer_misc2.Start(); + timer_misc.Stop(); + + // let entities think + if ( g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + idPlayer *player; + idVec3 origin; + + player = GetLocalPlayer(); + if ( player ) { + origin = player->GetPhysics()->GetOrigin(); + } + + idBounds viewTextBounds( origin ); + viewTextBounds.ExpandSelf( 128.0f ); + + num = 0; + + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + timer_singlethink.Clear(); + timer_singlethink.Start(); + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + timer_singlethink.Stop(); + ms = timer_singlethink.Milliseconds(); + if ( ms >= g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + Printf( "%d: entity '%s' [%s]: %.1f ms\n", time, ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(), ms ); + + if ( ms >= g_timeentities.GetFloat() * 3.0f ) + { + ent->mLastLongThinkColor = colorRed; + } + else + { + ent->mLastLongThinkColor[0] = 1.0f; + ent->mLastLongThinkColor[1] = 2.0f - (( ms - g_timeentities.GetFloat()) / g_timeentities.GetFloat() ); + ent->mLastLongThinkColor[2] = 0.0f; + ent->mLastLongThinkColor[3] = 1.0f; + } + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + ent->mLastLongThinkTime = time + 2000; + } + else if ( ent->mLastLongThinkTime > time ) + { + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + } + num++; + } + } else { + if ( inCinematic ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } + } + + // remove any entities that have stopped thinking + if ( numEntitiesToDeactivate ) { + idEntity *next_ent; + int c = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + if ( !ent->thinkFlags ) { + ent->activeNode.Remove(); + c++; + } + } + //assert( numEntitiesToDeactivate == c ); + numEntitiesToDeactivate = 0; + } + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + if ( isLastPredictFrame ) { + // bdube: client entities + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // nrausch: player could have been deleted in an event + player = GetLocalPlayer(); + + timer_events.Stop(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->EndFrame(); + } + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // free the player pvs + FreePlayerPVS(); + + // display how long it took to calculate the current game frame + if ( g_frametime.GetBool() ) { + Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", + time, timer_think.Milliseconds() + timer_events.Milliseconds(), + timer_think.Milliseconds(), timer_events.Milliseconds(), num ); + } + + // build the return value + ret.consistencyHash = 0; + ret.sessionCommand[0] = 0; + + // see if a target_sessionCommand has forced a changelevel + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + break; + } + + // make sure we don't loop forever when skipping a cinematic + if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) { + Warning( "Exceeded maximum cinematic skip length. Cinematic may be looping infinitely." ); + skipCinematic = false; + break; + } + + // jscott: additional timings + timer_misc2.Stop(); + + if ( g_frametime.GetInteger() > 1 ) { + gameLocal.Printf( "misc:%.1f misc2:%.1f\n", timer_misc.Milliseconds(), timer_misc2.Milliseconds() ); + } + // bdube: let gameDebug know that its not in a game frame anymore + gameDebug.EndFrame( ); + gameLogLocal.EndFrame( ); + + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->EndCinematic(); + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + g_simpleItems.ClearModified(); + return ret; +} +// RAVEN END + +/* +================ +idGameLocal::RepeaterFrame +================ +*/ +void idGameLocal::RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ) { + int i; + + // passing serverGameFrame is strictly a sanity check + // the adjustment in case of problem in RunFrame should cascade to here and never be a long term problem + static int FrameDelta = 0; + if ( serverGameFrame != framenum ) { + // don't want to spam the warning. don't have a good repro case to fix this but still want to track the error (seen it happen) + int delta = serverGameFrame - framenum; + if ( delta != FrameDelta ) { + if ( framenum != 0 ) { // to skip printing the initial messages + common->Warning( "repeater frame and game frame out of sync: %d/%d", serverGameFrame, framenum ); + } + FrameDelta = delta; + } + } else { + if ( FrameDelta != 0 ) { + common->Warning( "repeater in sync with game frame at %d", serverGameFrame ); + } + FrameDelta = 0; + } + + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + for ( i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].active ) { + continue; + } + + if ( viewers[ i ].nopvs ) { + continue; + } + + viewers[ i ].origin = clientOrigins[ i ]; + + if ( viewers[ i ].origin.followClient >= 0 && entities[ viewers[ i ].origin.followClient ] && entities[ viewers[ i ].origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + // if the viewer is following someone, use their origin + idPlayer *player = static_cast< idPlayer * >( entities[ viewers[ i ].origin.followClient ] ); + viewers[ i ].origin.origin = player->GetEyePosition(); + } + + viewers[ i ].pvsArea = pvs.GetPVSArea( viewers[ i ].origin.origin ); + } +} + +/* +====================================================================== + + Game view drawing + +====================================================================== +*/ + +/* +==================== +idGameLocal::CalcFov + +Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio +==================== +*/ +void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { + float x; + float y; + float ratio_x; + float ratio_y; + + if ( !sys->FPU_StackIsEmpty() ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: FPU stack not empty" ); + } + +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( g_fixedHorizFOV.GetBool() ) { + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + ratio_x = 4.0f; + ratio_y = 3.0f; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + x = ratio_x / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( ratio_y, x ); + fov_y = y * 360.0f / idMath::PI; + fov_x = base_fov; + return; + } +// RAVEN END + + // first, calculate the vertical fov based on a 640x480 view + x = 640.0f / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( 480.0f, x ); + fov_y = y * 360.0f / idMath::PI; + + // FIXME: somehow, this is happening occasionally + assert( fov_y > 0 ); + if ( fov_y <= 0 ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } + + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + fov_x = base_fov; + return; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + + y = ratio_y / idMath::Tan( fov_y / 360.0f * idMath::PI ); + fov_x = idMath::ATan( ratio_x, y ) * 360.0f / idMath::PI; + + if ( fov_x < base_fov ) { + fov_x = base_fov; + x = ratio_x / idMath::Tan( fov_x / 360.0f * idMath::PI ); + fov_y = idMath::ATan( ratio_y, x ) * 360.0f / idMath::PI; + } + + // FIXME: somehow, this is happening occasionally + assert( ( fov_x > 0 ) && ( fov_y > 0 ) ); + if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } +} + +void ClearClipProfile( void ); +void DisplayClipProfile( void ); + +/* +================ +idGameLocal::Draw + +makes rendering and sound system calls +================ +*/ +bool idGameLocal::Draw( int clientNum ) { +// DisplayClipProfile( ); +// ClearClipProfile( ); + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + idPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + renderView_t *view = player->GetRenderView(); + // nrausch: view doesn't necessarily exist yet + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + } +#endif +// RAVEN END + // render the scene + player->playerView.RenderPlayerView( player->hud ); + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud( ); +// RAVEN END + + return true; +} + +/* +================ +idGameLocal::HandleESC +================ +*/ +escReply_t idGameLocal::HandleESC( idUserInterface **gui ) { + +//RAVEN BEGIN +//asalmon: xbox dedicated server needs to bring up a special menu +#ifdef _XBOX + if( cvarSystem->GetCVarBool( "net_serverDedicated" )) + { + return ESC_MAIN; + } +#endif +//RAVEN END + + if ( isMultiplayer ) { + *gui = StartMenu(); + // we may set the gui back to NULL to hide it + return ESC_GUI; + } + idPlayer *player = GetLocalPlayer(); + if ( player ) { + if ( player->HandleESC() ) { + return ESC_IGNORE; + } else { + return ESC_MAIN; + } + } + return ESC_MAIN; +} + +/* +================ +idGameLocal::UpdatePlayerPostMainMenu +================ +*/ +void idGameLocal::UpdatePlayerPostMainMenu() { + idPlayer* player = GetLocalPlayer(); + + //dedicated server? + if( !player) { + return; + } + + //crosshair may have changed + player->UpdateHudWeapon(); +} + +/* +================ +idGameLocal::StartMenu +================ +*/ +idUserInterface* idGameLocal::StartMenu( void ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.StartMenu(); +} + +/* +================ +idGameLocal::GetClientStats +================ +*/ +void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { + mpGame.PlayerStats( clientNum, data, len ); +} + +/* +================ +idGameLocal::SwitchTeam +================ +*/ +void idGameLocal::SwitchTeam( int clientNum, int team ) { + + idPlayer * player; + player = clientNum >= 0 ? static_cast( gameLocal.entities[ clientNum ] ) : NULL; + + if ( !player ) { + return; + } + + int oldTeam = player->team; + + // Put in spectator mode + if ( team == -1 ) { + static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true ); + } + // Switch to a team + else { + mpGame.SwitchToTeam ( clientNum, oldTeam, team ); + } +} + +/* +================ +idGameLocal::HandleGuiCommands +================ +*/ +const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.HandleGuiCommands( menuCommand ); +} + +/* +================ +idGameLocal::HandleMainMenuCommands +================ +*/ +void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { + if ( !idStr::Icmp( menuCommand, "initCreateServerSettings" ) ) { + int guiValue = 0; + + switch ( mpGame.GameTypeToVote( si_gameType.GetString() ) ) { + case idMultiplayerGame::VOTE_GAMETYPE_DM: + guiValue = 0; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TOURNEY: + guiValue = 1; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TDM: + guiValue = 2; + break; + case idMultiplayerGame::VOTE_GAMETYPE_CTF: + guiValue = 3; + break; + case idMultiplayerGame::VOTE_GAMETYPE_ARENA_CTF: + guiValue = 4; + break; + case idMultiplayerGame::VOTE_GAMETYPE_DEADZONE: + guiValue = 5; + break; + } + + gui->SetStateInt( "currentGametype", guiValue ); + } else if ( !idStr::Icmp( menuCommand, "filterByNextMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = 0; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + ++filterMod; + if ( filterMod < modList.Num() ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "filterByPrevMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = modList.Num() - 1; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + --filterMod; + if ( filterMod >= 0 ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "updateFilterByMod" ) ) { + if ( filterMod < 0 || filterMod >= modList.Num() ) { + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } else { + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } + } else if ( !idStr::Icmp( menuCommand, "server_clearSort" ) ) { + filterMod = -1; + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + + return; +} + +/* +================ +idGameLocal::GetLevelMap + + should only be used for in-game level editing +================ +*/ +idMapFile *idGameLocal::GetLevelMap( void ) { +// RAVEN BEGIN +// rhummer: Added the HasBeenResolved check, if resolve has been run then it doesn't have func_groups. +// Which we probably don't want, so force the map to be reloaded. + if ( mapFile && mapFile->HasPrimitiveData() && !mapFile->HasBeenResloved() ) { +// RAVEN END + return mapFile; + } + if ( !mapFileName.Length() ) { + return NULL; + } + + if ( mapFile ) { + delete mapFile; + } + + mapFile = new idMapFile; + if ( !mapFile->Parse( mapFileName ) ) { + delete mapFile; + mapFile = NULL; + } + + return mapFile; +} + +/* +================ +idGameLocal::GetMapName +================ +*/ +const char *idGameLocal::GetMapName( void ) const { + return mapFileName.c_str(); +} + +/* +================ +idGameLocal::CallFrameCommand +================ +*/ +void idGameLocal::CallFrameCommand( idEntity* ent, const char* frameCommand ) { + const function_t *func = program.FindFunction( frameCommand ); + if ( !func ) { + Warning( "Script function '%s' not found.", frameCommand ); + return; + } + CallFrameCommand ( ent, func ); +} + +void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { + frameCommandThread->CallFunction( ent, frameCommand, true ); + frameCommandThread->Execute(); +} + +/* +================ +idGameLocal::CallObjectFrameCommand +================ +*/ +void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { + const function_t *func; + + func = ent->scriptObject.GetFunction( frameCommand ); + if ( !func ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idTestModel::GetClassType() ) ) { +// RAVEN END + Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); + } + } else { + frameCommandThread->CallFunction( ent, func, true ); + frameCommandThread->Execute(); + } +} + +/* +================ +idGameLocal::ShowTargets +================ +*/ +void idGameLocal::ShowTargets( void ) { + idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); + idBounds viewTextBounds( viewPos ); + idBounds viewBounds( viewPos ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + idBounds totalBounds; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + totalBounds = ent->GetPhysics()->GetAbsBounds(); + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); + } + } + + if ( !viewBounds.IntersectsBounds( totalBounds ) ) { + continue; + } + + float dist; + idVec3 dir = totalBounds.GetCenter() - viewPos; + dir.NormalizeFast(); + totalBounds.RayIntersection( viewPos, dir, dist ); + float frac = ( 512.0f - dist ) / 512.0f; + if ( frac < 0.0f ) { + continue; + } + + gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); + gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); + } + } + } +} + +/* +================ +idGameLocal::RunDebugInfo +================ +*/ +void idGameLocal::RunDebugInfo( void ) { + idEntity *ent; + idPlayer *player; + + player = GetLocalPlayer(); + if ( !player ) { + return; + } + + const idVec3 &origin = player->GetPhysics()->GetOrigin(); + + if ( g_showEntityInfo.GetBool() ) { + idBounds viewTextBounds( origin ); + idBounds viewBounds( origin ); + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // don't draw the worldspawn + if ( ent == world ) { + continue; + } + +// RAVEN BEGIN +// rjohnson: moved entity info out of idGameLocal into its own function + ent->DrawDebugEntityInfo(&viewBounds, &viewTextBounds); +// RAVEN END + } + } + + // debug tool to draw bounding boxes around active entities + if ( g_showActiveEntities.GetBool() ) { + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + idBounds b = ent->GetPhysics()->GetBounds(); + if ( b.GetVolume() <= 0 ) { + b[0][0] = b[0][1] = b[0][2] = -8; + b[1][0] = b[1][1] = b[1][2] = 8; + } + if ( ent->fl.isDormant ) { + gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); + } else { + gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); + } + } + } + +// RAVEN BEGIN +// bdube: client entities + if ( cl_showEntityInfo.GetBool ( ) ) { + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->DrawDebugInfo ( ); + } + } +// RAVEN END + + if ( g_showTargets.GetBool() ) { + ShowTargets(); + } + + if ( g_showTriggers.GetBool() ) { + idTrigger::DrawDebugInfo(); + } + + if ( ai_showCombatNodes.GetBool() ) { +// idCombatNode::DrawDebugInfo(); + } + + if ( ai_showPaths.GetBool() ) { + idPathCorner::DrawDebugInfo(); + } + + if ( g_editEntityMode.GetBool() ) { + if ( gameLocal.isMultiplayer ) { + g_editEntityMode.SetInteger(0); + Printf( "Not allowed in multiplayer.\n" ); + } else { + editEntities->DisplayEntities(); + } + } + + if ( g_showCollisionWorld.GetBool() ) { + // use g_maxShowDistance value instead of 128.0f + collisionModelManager->DrawModel( clip[0]->GetWorldCollisionModel(), vec3_origin, mat3_identity, origin, mat3_identity, g_maxShowDistance.GetFloat() ); + } + + if ( g_showCollisionModels.GetBool() ) { + if( g_showCollisionModels.GetInteger() == 2 ) { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player, &idPlayer::GetClassType() ); + } else { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); + } + + } + + if ( g_showCollisionTraces.GetBool() ) { + clip[ 0 ]->PrintStatistics(); + } + + if ( g_showPVS.GetInteger() ) { + pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); + } + +// RAVEN BEGIN +// rjohnson: added debug hud + if ( gameDebug.IsHudActive ( DBGHUD_PHYSICS ) ) { + clip[ 0 ]->DebugHudStatistics(); + } + + clip[ 0 ]->ClearStatistics(); +// RAVEN END + + if ( aas_test.GetInteger() >= 0 ) { + idAAS *aas = GetAAS( aas_test.GetInteger() ); + if ( aas ) { + aas->Test( origin ); + if ( ai_testPredictPath.GetBool() ) { + idVec3 velocity; + predictedPath_t path; + + velocity.x = idMath::Cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.y = idMath::Sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.z = 0.0f; + idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); + } + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin ); + } + } + } + if ( aas_showProblemAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin, true ); + } + } + } +// RAVEN END + } + + if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { + idAAS *aas = GetAAS( 0 ); + if ( aas ) { + idVec3 seekPos; + obstaclePath_t path; + + seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; + idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); + } + } + + // collision map debug output + collisionModelManager->DebugOutput( player->GetEyePosition(), mat3_identity ); + +// RAVEN BEGIN +// jscott: for debugging playbacks + if( g_showPlayback.GetInteger() ) { + gameEdit->DrawPlaybackDebugInfo(); + } +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if ( g_showClipSectors.GetBool() ) { + clip[ 0 ]->DrawClipSectors(); + } + + if ( g_showAreaClipSectors.GetFloat() ) { + clip[ 0 ]->DrawAreaClipSectors( g_showAreaClipSectors.GetFloat() ); + } +// RAVEN END +} + +/* +================== +idGameLocal::NumAAS +================== +*/ +int idGameLocal::NumAAS( void ) const { + return aasList.Num(); +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( int num ) const { + if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { + if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { + return aasList[ num ]; + } + } + return NULL; +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( const char *name ) const { + int i; + + for ( i = 0; i < aasNames.Num(); i++ ) { + if ( aasNames[ i ] == name ) { + if ( !aasList[ i ]->GetSettings() ) { + return NULL; + } else { + return aasList[ i ]; + } + } + } + return NULL; +} + +/* +================== +idGameLocal::SetAASAreaState +================== +*/ +void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->SetAreaState( bounds, areaContents, closed ); + } +} + +/* +================== +idGameLocal::AddAASObstacle +================== +*/ +aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { + int i; + aasHandle_t obstacle; + aasHandle_t check; + + if ( !aasList.Num() ) { + return -1; + } + + obstacle = aasList[ 0 ]->AddObstacle( bounds ); + for( i = 1; i < aasList.Num(); i++ ) { + check = aasList[ i ]->AddObstacle( bounds ); + assert( check == obstacle ); + } + + return obstacle; +} + +/* +================== +idGameLocal::RemoveAASObstacle +================== +*/ +void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveObstacle( handle ); + } +} + +/* +================== +idGameLocal::RemoveAllAASObstacles +================== +*/ +void idGameLocal::RemoveAllAASObstacles( void ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveAllObstacles(); + } +} + +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. +/* +================== +idGameLocal::GetEntityMemoryUsage + +Compute combined total memory footprint of server and client entity storage. +================== +*/ + +size_t idGameLocal::GetEntityMemoryUsage ( void ) const { + + // Server ents. + size_t serverEntitiesSize = 0; + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + serverEntitiesSize += sizeof( idEntity ); + } + + // Client ents. + size_t clientEntitiesSize = 0; + for( rvClientEntity *ent = gameLocal.clientSpawnedEntities.Next() ; ent != NULL; ent = ent->spawnNode.Next() ) { + clientEntitiesSize += ent->Size(); + } + + return serverEntitiesSize + clientEntitiesSize; +} +// RAVEN END + +/* +================== +idGameLocal::CheatsOk +================== +*/ +bool idGameLocal::CheatsOk( bool requirePlayer ) { + idPlayer *player; + + if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + Printf( "Not allowed in multiplayer.\n" ); + return false; + } + + if ( !developer.GetBool() ) { + return false; + } + + player = GetLocalPlayer(); + if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) { + return true; + } + + Printf( "You must be alive to use this command.\n" ); + + return false; +} + +/* +=================== +idGameLocal::RegisterEntity +=================== +*/ +void idGameLocal::RegisterEntity( idEntity *ent ) { + int spawn_entnum; + + if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { + Error( "idGameLocal::RegisterEntity: spawn count overflow" ); + } + + firstFreeIndex = Max( minSpawnIndex, firstFreeIndex ); + + if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { + while ( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) { + firstFreeIndex++; + } + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } + spawn_entnum = firstFreeIndex++; + } else { + assert( spawn_entnum < MAX_CLIENTS || spawn_entnum >= minSpawnIndex ); + } + + entities[ spawn_entnum ] = ent; + spawnIds[ spawn_entnum ] = spawnCount++; + ent->entityNumber = spawn_entnum; + ent->spawnNode.AddToEnd( spawnedEntities ); + ent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( spawn_entnum >= num_entities ) { + num_entities++; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=================== +idGameLocal::UnregisterEntity +=================== +*/ +void idGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( editEntities ) { + editEntities->RemoveSelectedEntity( ent ); + } + + if ( ( ent->entityNumber != ENTITYNUM_NONE || ent == entities[ ENTITYNUM_NONE ] ) && ( entities[ ent->entityNumber ] == ent ) ) { + ent->spawnNode.Remove(); + entities[ ent->entityNumber ] = NULL; + spawnIds[ ent->entityNumber ] = -1; + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) { + firstFreeIndex = ent->entityNumber; + } + ent->entityNumber = ENTITYNUM_NONE; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=============== +idGameLocal::SkipEntityIndex +=============== +*/ +void idGameLocal::SkipEntityIndex( void ) { + assert( entities[ firstFreeIndex ] == NULL ); + firstFreeIndex++; + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } +} + +/* +================ +idGameLocal::SpawnEntityType +================ +*/ +idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { + idClass *obj; + +#ifdef _DEBUG + if ( isClient ) { + assert( bIsClientReadSnapshot ); + } +#endif + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !classdef.IsType( idEntity::GetClassType() ) ) { +// RAVEN END + Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); + } + + try { + if ( args ) { + spawnArgs = *args; + } else { + spawnArgs.Clear(); + } + obj = classdef.CreateInstance(); + obj->CallSpawn(); + } + + catch( idAllocError & ) { + obj = NULL; + } + spawnArgs.Clear(); + + return static_cast(obj); +} + +/* +=================== +idGameLocal::SpawnClientEntityDef + +Finds the spawn function for the client entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnClientEntityDef( const idDict &args, rvClientEntity **cent, bool setDefaults, const char* spawn ) { + const char *classname; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + if ( cent ) { + *cent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ){ + //not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { + error = va( " on '%s'", name ); + } + + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { + // RAVEN BEGIN + // jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } + // RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + + // check if we should spawn a class object + if( spawn == NULL ) { + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + } + + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + + if ( cent && obj->IsType( rvClientEntity::GetClassType() ) ) { + *cent = static_cast(obj); + } + + return true; + } + + Warning( "%s doesn't include a spawnfunc%s.", classname, error.c_str() ); + return false; +} + +/* +=================== +idGameLocal::SpawnEntityDef + +Finds the spawn function for the entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { + const char *classname; + const char *spawn; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( ent ) { + *ent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ) + {//not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + error = va( " on '%s'", name ); +// RAVEN END + } + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { +// RAVEN BEGIN +// jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } +// RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + if ( g_keepEntityStats.GetBool() ) { + if ( idStr::Icmp( classname, "func_spawner" ) == 0 || + idStr::Icmp( classname, "func_spawner_enemy" ) == 0 ) { + // special case for spawners + for( int i = 1; ; i++ ) { + char tempSpawn[128]; + const char *tempClassname; + + sprintf( tempSpawn, "def_spawn_type%d", i ); + tempClassname = spawnArgs.GetString( tempSpawn, NULL ); + if ( tempClassname ) { + const idDeclEntityDef *tempDef = FindEntityDef( tempClassname, false ); + + if ( tempDef ) { + idDict tempArgs = tempDef->dict; + + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } else { + break; + } + } + } + else if ( def->dict.GetBool( "report_stats" ) ) { + idDict tempArgs = spawnArgs; + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } +// RAVEN END + + // check if we should spawn a class object + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && obj->IsType( idEntity::GetClassType() ) ) { +// RAVEN END + *ent = static_cast(obj); + } + + return true; + } + + // check if we should call a script function to spawn + spawnArgs.GetString( "spawnfunc", NULL, &spawn ); + if ( spawn ) { + const function_t *func = program.FindFunction( spawn ); + if ( !func ) { + Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + + return true; + } + + Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); + return false; +} + +// abahr: +idEntity* idGameLocal::SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idDict finalArgs; + const idDict* entityDict = NULL; + idEntity* entity = NULL; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if( !entityDefName ) { + return NULL; + } + + entityDict = FindEntityDefDict( entityDefName, false ); + if( !entityDict ) { + return NULL; + } + + if( !additionalArgs ) { + SpawnEntityDef( *entityDict, &entity ); + } else { + finalArgs.Copy( *entityDict ); + finalArgs.Copy( *additionalArgs ); + SpawnEntityDef( finalArgs, &entity ); + } + + return entity; +} +// RAVEN END + +/* +================ +idGameLocal::FindEntityDef +================ +*/ +const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDecl *decl = NULL; + if ( isMultiplayer ) { + decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); + } + if ( !decl ) { + decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); + } + return static_cast( decl ); +} + +/* +================ +idGameLocal::FindEntityDefDict +================ +*/ +const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); + return decl ? &decl->dict : NULL; +} + +/* +================ +idGameLocal::InhibitEntitySpawn +================ +*/ +bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + + bool result = false; + + if ( isMultiplayer ) { + spawnArgs.GetBool( "not_multiplayer", "0", result ); + } else if ( g_skill.GetInteger() == 0 ) { + spawnArgs.GetBool( "not_easy", "0", result ); + } else if ( g_skill.GetInteger() == 1 ) { + spawnArgs.GetBool( "not_medium", "0", result ); + } else { + spawnArgs.GetBool( "not_hard", "0", result ); + } + + const char *name; +#ifndef ID_DEMO_BUILD + if ( g_skill.GetInteger() == 3 ) { + name = spawnArgs.GetString( "classname" ); + if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ) { + result = true; + } + } +#endif + +// RAVEN BEGIN +// bdube: suppress ents that don't match the entity filter + const char* entityFilter; + if ( serverInfo.GetString( "si_entityFilter", "", &entityFilter ) && *entityFilter ) { + if ( spawnArgs.MatchPrefix ( "filter_" ) && !spawnArgs.GetBool ( va("filter_%s", entityFilter) ) ) { + return true; + } + } +// RAVEN END + +// RITUAL BEGIN +// squirrel: suppress ents that aren't supported in Buying modes (if that's the mode we're in) + if ( mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( spawnArgs.GetBool( "disableSpawnInBuying", "0" ) ) { + return true; + } + + /// Don't spawn weapons or armor vests or ammo in Buying modes + idStr classname = spawnArgs.GetString( "classname" ); + if( idStr::FindText( classname, "weapon_" ) == 0 || + idStr::FindText( classname, "item_armor_small" ) == 0 || + idStr::FindText( classname, "ammo_" ) == 0 || + idStr::FindText( classname, "item_armor_large" ) == 0 ) + { + return true; + } + } +// RITUAL END + + // suppress deadzone triggers if we're not running DZ + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "DeadZone" ) != 0 ) { + if ( idStr::Icmp( spawnArgs.GetString( "classname" ), "trigger_controlzone" ) == 0 ) { + return true; + } + } + + return result; +} + +/* +================ +idGameLocal::SetSkill +================ +*/ +void idGameLocal::SetSkill( int value ) { + int skill_level; + + if ( value < 0 ) { + skill_level = 0; + } else if ( value > 3 ) { + skill_level = 3; + } else { + skill_level = value; + } + + g_skill.SetInteger( skill_level ); +} + +/* +============== +idGameLocal::GameState + +Used to allow entities to know if they're being spawned during the initial spawn. +============== +*/ +gameState_t idGameLocal::GameState( void ) const { + return gamestate; +} + +/* +============== +idGameLocal::SpawnMapEntities + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple game instances +void idGameLocal::SpawnMapEntities( int instance, unsigned short* entityNumIn, unsigned short* entityNumOut, int* startSpawnCount ) { +// RAVEN END + int i; + int num; + int inhibit; + int latchedSpawnCount = -1; + idMapEntity *mapEnt; + int numEntities; + idDict args; + idDict items; + + Printf( "Spawning entities\n" ); + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( mapFile == NULL ) { + Printf("No mapfile present\n"); + return; + } + + SetSkill( g_skill.GetInteger() ); + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + +// RAVEN BEGIN +// ddynerman: on the server, perform world-initialization when spawning instance 0 only and while starting up +// on the client, perform world-init when spawning while starting up, as client main instance may not be ID 0 +// always spawn world-init in single-player + num = inhibit = 0; + + // the worldspawn is a special that performs any global setup + // needed by a level + if( ( gameLocal.isServer && instance == 0 && gamestate == GAMESTATE_STARTUP ) || (gameLocal.isClient && gamestate == GAMESTATE_STARTUP) || !gameLocal.isMultiplayer ) { + mapEnt = mapFile->GetEntity( 0 ); + args = mapEnt->epairs; + args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); + + // on the client, spawnCount won't always start at INITIAL_SPAWN_COUNT (see rvInstance::Populate()) + // make sure the world and physics ent get the right spawn id + if( gameLocal.isClient && spawnCount != INITIAL_SPAWN_COUNT ) { + latchedSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + } + +// abahr: precache stuff on worldSpawn like player def and other misc things + CacheDictionaryMedia( &args ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::GetClassType() ) ) { + Error( "Problem spawning world entity" ); + } + num++; + +// bdube: dummy entity for client entities with physics + args.Clear ( ); + args.SetInt( "spawn_entnum", ENTITYNUM_CLIENT ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityType( rvClientPhysics::GetClassType(), &args, true ) || !entities[ ENTITYNUM_CLIENT ] ) { + Error( "Problem spawning client physics entity" ); + } + + if( gameLocal.isClient && latchedSpawnCount != -1 ) { + spawnCount = latchedSpawnCount; + } + + isMapEntity[ ENTITYNUM_CLIENT ] = true; + isMapEntity[ ENTITYNUM_WORLD ] = true; +// RAVEN END + } + + // capture spawn count of start of map entities (after we've spawned in the physics ents) + if ( startSpawnCount ) { + (*startSpawnCount) = spawnCount; + } + + for ( i = 1 ; i < numEntities ; i++ ) { + + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + +// RAVEN BEGIN +// ddynerman: merge the dicts ahead of SpawnEntityDef() so we can inhibit using merged info + const idDeclEntityDef* entityDef = FindEntityDef( args.GetString( "classname" ), false ); + + if( entityDef == NULL ) { + gameLocal.Error( "idGameLocal::SpawnMapEntities() - Unknown entity classname '%s'\n", args.GetString( "classname" ) ); + return; + } + args.SetDefaults( &(entityDef->dict) ); +// RAVEN END + + if ( !InhibitEntitySpawn( args ) ) { + + if( args.GetBool( "inv_item" ) ) { + if( !items.GetBool( args.GetString( "inv_icon" ) ) ) { + networkSystem->AddLoadingIcon( args.GetString( "inv_icon" ) ); + networkSystem->SetLoadingText( common->GetLocalizedString( args.GetString( "inv_name" ) ) ); + items.SetBool( args.GetString( "inv_icon" ), true ); + } + } + + // precache any media specified in the map entity + CacheDictionaryMedia( &args ); +// RAVEN BEGIN + if ( instance != 0 ) { +// ddynerman: allow this function to be called multiple-times to respawn map entities in other instances + args.SetInt( "instance", instance ); + args.Set( "name", va( "%s_instance%d", args.GetString( "name" ), instance ) ); + //gameLocal.Printf( "Instance %d: Spawning %s (class: %s)\n", instance, args.GetString( "name" ), args.GetString( "classname" ) ); + + // need a better way to do this - map entities that target other map entities need + // to target the ones in the correct instance. + idStr target; + if( args.GetString( "target", "", target ) ) { + args.Set( "target", va( "%s_instance%d", target.c_str(), instance ) ); + } + + // and also udpate binds on a per-instance bases + idStr bind; + if( args.GetString( "bind", "", bind ) ) { + args.Set( "bind", va( "%s_instance%d", bind.c_str(), instance ) ); + } + + // and also door triggers + idStr triggerOnOpen; + if( args.GetString( "triggerOnOpen", "", triggerOnOpen ) ) { + args.Set( "triggerOnOpen", va( "%s_instance%d", triggerOnOpen.c_str(), instance ) ); + } + + idStr triggerOpened; + if( args.GetString( "triggerOpened", "", triggerOpened ) ) { + args.Set( "triggerOpened", va( "%s_instance%d", triggerOpened.c_str(), instance ) ); + } + } + + idEntity* ent = NULL; + SpawnEntityDef( args, &ent ); + //common->Printf( "pop: spawn map ent %d at %d ( %s )\n", i, ent->entityNumber, args.GetString( "name" ) ); + + if ( ent && entityNumOut ) { + entityNumOut[ i ] = ent->entityNumber; + } + + if ( gameLocal.GetLocalPlayer() && ent && gameLocal.isServer && instance != gameLocal.GetLocalPlayer()->GetInstance() ) { + // if we're spawning entities not in our instance, tell them not to draw + ent->BecomeInactive( TH_UPDATEVISUALS ); + } + + if ( ent ) { + isMapEntity[ ent->entityNumber ] = true; + } +// RAVEN END + num++; + } else { + // keep counting and leave an empty slot in the entity list for inhibited entities + // this so we maintain the same layout as the server and don't change it across restarts with different inhibit schemes + //common->Printf( "pop: skip map ent %d at index %d ( %s )\n", i, firstFreeIndex, args.GetString( "name" ) ); + SkipEntityIndex(); + inhibit++; + } + } + + Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); +} + +/* +================ +idGameLocal::AddEntityToHash +================ +*/ +void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { + if ( FindEntity( name ) ) { + Error( "Multiple entities named '%s'", name ); + } + entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); +} + +/* +================ +idGameLocal::RemoveEntityFromHash +================ +*/ +bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { + entityHash.Remove( hash, i ); + return true; + } + } + return false; +} + +/* +================ +idGameLocal::GetTargets +================ +*/ +int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { + int i, num, refLength; + const idKeyValue *arg; + idEntity *ent; + + list.Clear(); + + refLength = strlen( ref ); + num = args.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + + arg = args.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { + + ent = FindEntity( arg->GetValue() ); + if ( ent ) { + idEntityPtr &entityPtr = list.Alloc(); + entityPtr = ent; + } + } + } + + return list.Num(); +} + +/* +============= +idGameLocal::GetTraceEntity + +returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. +============= +*/ +idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { + idEntity *master; + + if ( !entities[ trace.c.entityNum ] ) { + return NULL; + } + master = entities[ trace.c.entityNum ]->GetBindMaster(); + if ( master ) { + return master; + } + return entities[ trace.c.entityNum ]; +} + +/* +============= +idGameLocal::ArgCompletion_EntityName + +Argument completion for entity names +============= +*/ +void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::ArgCompletion_AIName + +Argument completion for idAI entity names +============= +*/ +void idGameLocal::ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idAI::GetClassType() ) ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::FindEntity + +Returns the entity whose name matches the specified string. +============= +*/ +idEntity *idGameLocal::FindEntity( const char *name ) const { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { + return entities[i]; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindEntityUsingDef + +Searches all active entities for the next one using the specified entityDef. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. +============= +*/ +idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { + idEntity *ent; + + if ( !from ) { + ent = spawnedEntities.Next(); + } else { + ent = from->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + assert( ent ); + if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { + return ent; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindTraceEntity + +Searches all active entities for the closest ( to start ) match that intersects +the line start,end +============= +*/ +idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( c ) && ent != skip ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} + +/* +================ +idGameLocal::EntitiesWithinRadius +================ +*/ +int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { + idEntity *ent; + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + entityList[entCount++] = ent; + } + } + + return entCount; +} + +/* +================= +idGameLocal::KillBox + +Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. +Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. +If catch_teleport, this only marks teleport players for death on exit +================= +*/ +void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idPhysics *phys; + + phys = ent->GetPhysics(); + if ( !phys->GetNumClipModels() ) { + return; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = ClipModelsTouchingBounds( ent, phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == ent ) || !hit->fl.takedamage ) { + continue; + } + + if ( !phys->ClipContents( cm ) ) { + continue; + } + + // nail it +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( hit->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { +// RAVEN END + static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + + if ( !gameLocal.isMultiplayer ) { + // let the mapper know about it + Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() ); + } + } +} + +/* +================ +idGameLocal::RequirementMet +================ +*/ +bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { + if ( requires.Length() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + idPlayer *player = static_cast(activator); + idDict *item = player->FindInventoryItem( requires ); + if ( item ) { + if ( removeItem ) { + player->RemoveInventoryItem( item ); + } + return true; + } else { + return false; + } + } + } + + return true; +} + + +/* +================ +idGameLocal::IsWeaponsStayOn +================ +*/ +bool idGameLocal::IsWeaponsStayOn( void ) { + /// Override weapons stay when buying is active + if( isMultiplayer && mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + return false; + } + + return serverInfo.GetBool( "si_weaponStay" ); +} + + +/* +============ +idGameLocal::AlertAI +============ +*/ +void idGameLocal::AlertAI( idEntity *ent ) { +// RAVEN BEGIN +// bdube: merged + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // alert them for the next frame + lastAIAlertActorTime = time + GetMSec(); + lastAIAlertActor = static_cast( ent ); + } else { + lastAIAlertEntityTime = time + GetMSec(); + lastAIAlertEntity = ent; + } + } else { + lastAIAlertEntityTime = 0; + lastAIAlertActorTime = 0; + lastAIAlertEntity = NULL; + lastAIAlertActor = NULL; + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: alert entity returns an entity, alert actor a an actor +/* +============ +idGameLocal::GetAlertEntity +============ +*/ +idEntity *idGameLocal::GetAlertEntity( void ) { + if ( lastAIAlertEntityTime >= time ) { + return lastAIAlertEntity.GetEntity(); + } + + return NULL; +} + +/* +============ +idGameLocal::GetAlertActor +============ +*/ +idActor *idGameLocal::GetAlertActor( void ) { + if ( lastAIAlertActorTime >= time ) { + return lastAIAlertActor.GetEntity(); + } + + return NULL; +} +// RAVEN END + +/* +============ +SortClipModelsByEntity +============ +*/ +static int SortClipModelsByEntity( const void* left, const void* right ) { + idEntity* leftEntity = left ? (*((const idClipModel**)left))->GetEntity() : NULL; + idEntity* rightEntity = right ? (*((const idClipModel**)right))->GetEntity() : NULL; + + int entityNumLeft = (leftEntity) ? leftEntity->entityNumber : 0; + int entityNumRight = (rightEntity) ? rightEntity->entityNumber : 0; + + return entityNumLeft - entityNumRight; +} + +// RAVEN BEGIN +/* +============ +idGameLocal::RadiusDamage +Returns the number of actors damaged +============ +*/ +// abahr: changed to work with deathPush +void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower, int* hitCount ) { + float dist, damageScale, attackerDamageScale, attackerPushScale; + idEntity* ent = NULL; + idEntity* lastEnt = NULL; + idClipModel* clipModel = NULL; + idClipModel* clipModelList[ MAX_GENTITIES ]; + int numListedClipModels; + modelTrace_t result; + idVec3 v, damagePoint, dir; + int i, damage, radius, push; + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + if( gameLocal.isMultiplayer ) { + damageDef->GetFloat( "attackerPushScale", "2", attackerPushScale ); + } else { + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + } + + if ( radius < 1 ) { + radius = 1; + } + + +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, idBounds(origin).Expand(radius), MASK_ALL, clipModelList, MAX_GENTITIES ); + if( numListedClipModels > 0 ) { + //Sort list by unique entities for easier searching + qsort( clipModelList, numListedClipModels, sizeof(clipModelList[0]), SortClipModelsByEntity ); + } + + if ( inflictor ) { + inflictor = inflictor->GetDamageEntity ( ); + } + if ( attacker ) { + attacker = attacker->GetDamageEntity ( ); + } + if ( ignoreDamage ) { + ignoreDamage = ignoreDamage->GetDamageEntity ( ); + } + + for( int c = 0; c < numListedClipModels; ++c ) { + clipModel = clipModelList[ c ]; + assert( clipModel ); + + ent = clipModel->GetEntity(); + + // Skip all entitys that arent primary damage entities + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } + + // Dont damage inflictor or the ignore entity + if( ent == inflictor || ent == ignoreDamage ) { + continue; + } + + idBounds absBounds = clipModel->GetAbsBounds(); + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < absBounds[0][ i ] ) { + v[ i ] = absBounds[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > absBounds[1][ i ] ) { + v[ i ] = origin[ i ] - absBounds[1][ i ]; + } else { + v[ i ] = 0; + } + } + + dist = v.Length(); + if ( dist >= radius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, absBounds.GetCenter()) ) { + continue; + } + + RadiusPushClipModel ( inflictor, origin, push, clipModel ); + + // Only damage unique entities. This works because we have a sorted list + if( lastEnt == ent ) { + continue; + } + + lastEnt = ent; + + if ( ent->CanDamage( origin, damagePoint, ignoreDamage ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + if( gameLocal.isMultiplayer ) { + // fudge the direction in MP to account for player height difference and origin shift + // 31.875 = origin is 23.875 units lower in Q4 than Q3 + player is 8 units taller in Q4 + dir = ( ent->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 31.875f ) ) - origin; + } else { + dir = ent->GetPhysics()->GetOrigin() - origin; + } + + dir[2] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - dist / radius ); + + if ( ent == attacker ) { + damageScale *= attackerDamageScale; + } + + dir.Normalize(); + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(ent->GetPhysics()->GetClipModel()->GetId()) ); + + // for stats, count the first + if( attacker && attacker->IsType( idPlayer::GetClassType() ) && inflictor && inflictor->IsType( idProjectile::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && hitCount ) { + // with splash damage projectiles, one projectile fire can damage multiple people. If anyone is hit, + // the shot counts as a hit but only report accuracy on the first one to avoid accuracies > 100% + statManager->WeaponHit( (const idActor*)attacker, ent, ((idProjectile*)inflictor)->methodOfDeath, (*hitCount) == 0 ); + (*hitCount)++; + } + } + } +} +// RAVEN END + +/* +============== +idGameLocal::RadiusPush +============== +*/ +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { + int i, numListedClipModels; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 dir; + idBounds bounds; + modelTrace_t result; + idEntity *ent; + float scale; + +// RAVEN BEGIN +// abahr: need to use gravity instead of assuming z is up + dir = -GetCurrentGravity( const_cast(inflictor) ).ToNormal(); +// RAVEN END + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, bounds, -1, clipModelList, MAX_GENTITIES ); + +// bdube: getdamageentity + if ( inflictor ) { + inflictor = ((idEntity*)inflictor)->GetDamageEntity ( ); + } + if ( ignore ) { + ignore = ((idEntity*)ignore)->GetDamageEntity ( ); + } +// RAVEN END + + // apply impact to all the clip models through their associated physics objects + for ( i = 0; i < numListedClipModels; i++ ) { + + clipModel = clipModelList[i]; + + // never push render models + if ( clipModel->IsRenderModel() ) { + continue; + } + + ent = clipModel->GetEntity(); +// RAVEN BEGIN +// bdube: damage entity + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } +// RAVEN END + + // never push projectiles +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + continue; + } + + // players use "knockback" in idPlayer::Damage +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idPlayer::GetClassType() ) && !quake ) { +// RAVEN END + continue; + } + + // don't push the ignore entity + if ( ent == ignore ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { + continue; + } + + // scale the push for the inflictor + if ( ent == inflictor ) { + scale = inflictorScale; + } else { + scale = 1.0f; + } + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { +// RAVEN BEGIN +// bdube: inflictor + RadiusPushClipModel( (idEntity*)inflictor, origin, scale * push, clipModel ); +// RAVEN END + } + } +} + +/* +============== +idGameLocal::RadiusPushClipModel +============== +*/ +// RAVEN BEGIN +// bdube: inflictor +void idGameLocal::RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ) { +// RAVEN END + int i, j; + float dot, dist, area; + const idTraceModel *trm; + const traceModelPoly_t *poly; + idFixedWinding w; + idVec3 v, localOrigin, center, impulse; + + trm = clipModel->GetTraceModel(); + if ( !trm || 1 ) { + impulse = clipModel->GetAbsBounds().GetCenter() - origin; + impulse.Normalize(); +// RAVEN BEGIN +// abahr: removed because z isn't always up + //impulse.z += 1.0f; + //impulse = idVec3( 0.0, 0.0, 1.0 ); + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), clipModel->GetOrigin(), push * impulse, true ); +// RAVEN END + return; + } + + localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); + for ( i = 0; i < trm->numPolys; i++ ) { + poly = &trm->polys[i]; + + center.Zero(); + for ( j = 0; j < poly->numEdges; j++ ) { + v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ]; + center += v; + v -= localOrigin; + v.NormalizeFast(); // project point on a unit sphere + w.AddPoint( v ); + } + center /= poly->numEdges; + v = center - localOrigin; + dist = v.NormalizeFast(); + dot = v * poly->normal; + if ( dot > 0.0f ) { + continue; + } + area = w.GetArea(); + // impulse in polygon normal direction + impulse = poly->normal * clipModel->GetAxis(); + // always push up for nicer effect + impulse.z -= 1.0f; + // scale impulse based on visible surface area and polygon angle + impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); + // scale away distance for nicer effect + impulse *= ( dist * 2.0f ); + // impulse is applied to the center of the polygon + center = clipModel->GetOrigin() + center * clipModel->GetAxis(); + +// RAVEN BEGIN +// bdube: inflictor + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), center, impulse ); +// RAVEN END + } +} + +/* +=============== +idGameLocal::ProjectDecal +=============== +*/ +void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { + float s, c; + idMat3 axis, axistemp; + idFixedWinding winding; + idVec3 windingOrigin, projectionOrigin; + + static idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !g_decals.GetBool() ) { + return; + } + +// RAVEN BEGIN +// rjohnson: no decals on dedicated server + if ( isMultiplayer && !isClient && !isListenServer ) { + // no decals on dedicated server + return; + } +// RAVEN END + + // randomly rotate the decal winding + idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); + + // winding orientation + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + windingOrigin = origin + depth * axis[2]; + if ( parallel ) { + projectionOrigin = origin - depth * axis[2]; + } else { + projectionOrigin = origin; + } + + size *= 0.5f; + + winding.Clear(); + winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); + gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), time ); +} + +/* +============== +idGameLocal::BloodSplat +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple collision models +void idGameLocal::BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dirArg, float size, const char *material ) { + + float halfSize = size * 0.5f; + idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), + idVec3( 0.0f, +halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, +halfSize ) }; + idVec3 dir = dirArg; + idTraceModel trm; + idClipModel mdl; + trace_t results; + +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( !g_decals.GetBool() ) { + return; + } +// RAVEN END + + size = halfSize + random.RandomFloat() * halfSize; + trm.SetupPolygon( verts, 4 ); + mdl.LoadModel( trm, NULL ); + + // I don't want dir to be axis aligned, as it is more likely to streak them (because most architecture is axis aligned + dir.Set( dirArg.x*.1f, dirArg.y*.1f, -1 ); + dir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Translation( ent, results, origin, origin + dir * 72.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); +// RAVEN END + ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); +} + +/* +============= +idGameLocal::SetCamera +============= +*/ +void idGameLocal::SetCamera( idCamera *cam ) { + int i; + idEntity *ent; + idAI *ai; + + // this should fix going into a cinematic when dead.. rare but happens + idPlayer *client = GetLocalPlayer(); + if ( client->health <= 0 || client->pfl.dead ) { + return; + } + + camera = cam; + if ( camera ) { +// RAVEN BEGIN +// bdube: tool support + inCinematic = false; + if( !( gameLocal.editors & ( EDITOR_MODVIEW | EDITOR_PLAYBACKS ) ) ) { + inCinematic = true; + } +// RAVEN END + + if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return; + } + + if ( time > cinematicStopTime ) { + cinematicSkipTime = time + CINEMATIC_SKIP_DELAY; + } + + // set r_znear so that transitioning into/out of the player's head doesn't clip through the view + cvarSystem->SetCVarFloat( "r_znear", 1.0f ); + + // hide all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + client = static_cast< idPlayer* >( entities[ i ] ); + client->EnterCinematic(); + } + } + + if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { + // kill any active monsters that are enemies of the player + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->cinematic || ent->fl.isDormant ) { + // only kill entities that aren't needed for cinematics and aren't dormant + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAI::GetClassType() ) ) { +// RAVEN END + ai = static_cast( ent ); + if ( !ai->GetEnemy() || !ai->IsActive() ) { + // no enemy, or inactive, so probably safe to ignore + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { + // remove anything marked to be removed during cinematics + } else { + // ignore everything else + continue; + } + + // remove it + DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + } else { + inCinematic = false; + cinematicStopTime = time + msec; + + // restore r_znear + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // show all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); + client->ExitCinematic(); + } + } + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +============= +idGameLocal::GetPortalSky +============= +*/ +idCamera *idGameLocal::GetPortalSky( void ) const +{ + if( !portalSkyVisible ) { + + return( NULL ); + } + return( portalSky ); +} +/* +============= +idGameLocal::SetPortalSky +============= +*/ +void idGameLocal::SetPortalSky( idCamera *cam ) +{ + portalSky = cam; +} +// RAVEN END + +/* +============= +idGameLocal::GetCamera +============= +*/ +idCamera *idGameLocal::GetCamera( void ) const { + return camera; +} + +/* +============= +idGameLocal::SkipCinematic +============= +*/ +bool idGameLocal::SkipCinematic( void ) { + if ( camera ) { + if ( camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return false; + } + + if ( camera->spawnArgs.GetBool( "instantSkip" ) ) { + camera->Stop(); + return false; + } + } + + soundSystem->SetMute( true ); + if ( !skipCinematic ) { + skipCinematic = true; + cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() ); + } + + return true; +} + +// RAVEN BEGIN +/* +====================== +idGameLocal::StartViewEffect + +For passing in an effect triggered view effect +====================== +*/ +void idGameLocal::StartViewEffect( int type, float time, float scale ) +{ + idPlayer *player; + idPlayerView *view; + + player = GetLocalPlayer(); + if( player ) + { + view = &player->playerView; + + switch( type ) + { + case VIEWEFFECT_DOUBLEVISION: + view->SetDoubleVisionParms( time, scale ); + break; + + case VIEWEFFECT_SHAKE: + if( !gameLocal.isMultiplayer ) { + view->SetShakeParms( time, scale ); + } + break; + + case VIEWEFFECT_TUNNEL: + view->SetTunnelParms( time, scale ); + break; + + default: + gameLocal.Warning( "Invalid view effect" ); + break; + } + } +} + +/* +====================== +idGameLocal::GetPlayerView +====================== +*/ +void idGameLocal::GetPlayerView( idVec3 &origin, idMat3 &axis ) +{ + idPlayer *player; + renderView_t *view; + + player = GetLocalPlayer(); + if( player ) + { + view = player->GetRenderView(); + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + assert( view ); + } + origin = view->vieworg; + axis = view->viewaxis; + } + else + { + origin = vec3_origin; + axis = mat3_identity; + } +} + +/* +====================== +idGameLocal::Translation + +small portion of physics required for the effects system +====================== +*/ +void idGameLocal::Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) { + if( !trm ) { + clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL ); + } else { + idClipModel cm; + + cm.LoadModel( *trm, NULL ); + clip[0]->Translation( trace, source, dest, &cm, mat3_identity, clipMask, NULL, NULL ); + } +} + +/* +====================== +idGameLocal::SpawnClientMoveable +====================== +*/ +void idGameLocal::SpawnClientMoveable( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) { + // find the debris def + const idDict* args = gameLocal.FindEntityDefDict( name, false ); + if ( !args ) { + return; + } + + // Ensure client moveables never last forever + if ( lifetime <= 0 ) { + lifetime = SEC2MS(args->GetFloat( "duration", "5" )); + } + int burn_time = idMath::ClampInt( 0, lifetime, SEC2MS(args->GetFloat( "burn_time", "2.5" )) ); + + // Spawn the debris + + rvClientMoveable* cent = NULL; + // force the args to spawn a rvClientMoveable + SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + return; + } + + cent->SetOrigin( origin ); + cent->SetAxis( axis ); + + cent->GetPhysics()->SetLinearVelocity( velocity ); + cent->GetPhysics()->SetAngularVelocity( angular_velocity ); + + if ( !burn_time ) { + //just disappear + cent->PostEventMS( &EV_Remove, lifetime ); + } else { + cent->PostEventMS( &CL_FadeOut, lifetime-burn_time, burn_time ); + } +} + +/* +====================== +idGameLocal::DebugSet +====================== +*/ +void idGameLocal::DebugSetString( const char* name, const char* value ) { + gameDebug.SetString( name, value ); +} +void idGameLocal::DebugSetFloat( const char* name, float value ) { + gameDebug.SetFloat( name, value ); +} +void idGameLocal::DebugSetInt( const char* name, int value ) { + gameDebug.SetInt( name, value ); +} + +/* +====================== +idGameLocal::DebugGetStat +====================== +*/ +const char* idGameLocal::DebugGetStatString ( const char* name ) { + return gameDebug.GetStatString( name ); +} + +int idGameLocal::DebugGetStatInt ( const char* name ) { + return gameDebug.GetStatInt( name ); +} + +float idGameLocal::DebugGetStatFloat ( const char* name ) { + return gameDebug.GetStatFloat( name ); +} + +/* +====================== +idGameLocal::IsDebugHudActive +====================== +*/ +bool idGameLocal::IsDebugHudActive ( void ) const { + return gameDebug.IsHudActive( DBGHUD_ANY ); +} + + +// rjohnson: added player info support for note taking system +/* +====================== +idGameLocal::GetPlayerInfo +====================== +*/ +bool idGameLocal::GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum, idAngles *deltaViewAngles, int reqClientNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if( reqClientNum != -1 ) { + idPlayer* reqClient = GetClientByNum( reqClientNum ); + if( reqClient && player ) { + if( reqClient->GetInstance() != player->GetInstance() ) { + return false; + } + } + } + + if ( !player ) { + return false; + } + + player->GetViewPos( origin, axis ); + origin = player->GetPhysics()->GetOrigin(); + + if ( deltaViewAngles ) { + *deltaViewAngles = player->GetDeltaViewAngles(); + } + + return true; +}; + +/* +====================== +idGameLocal::SetCurrentPlayerInfo +====================== +*/ +void idGameLocal::SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if ( !player ) { + return; + } + + player->Teleport( origin, axis.ToAngles(), NULL ); +// RAVEN BEGIN +// ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->CalculateFirstPersonView(); + player->CalculateRenderView(); + currentThinkingEntity = NULL; +// RAVEN END + + return; +}; + +bool idGameLocal::PlayerChatDisabled( int clientNum ) { + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !entities[ clientNum ] ) { + return false; + } + + return !( ((idPlayer*)entities[ clientNum ])->isChatting || ((idPlayer*)entities[ clientNum ])->pfl.dead ); +} + +void idGameLocal::SetViewComments( const char *text ) { + idPlayer *player; + + player = GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( text ) { + player->hud->SetStateString( "viewcomments", text ); + player->hud->HandleNamedEvent( "showViewComments" ); + } + else { + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); + } +} + +/* +=================== +idGameLocal::GetNumGravityAreas +=================== +*/ +int idGameLocal::GetNumGravityAreas() const { + return gravityInfo.Num(); +} + +/* +=================== +idGameLocal::GetGravityInfo +=================== +*/ +const rvGravityArea* idGameLocal::GetGravityInfo( int index ) const { + return gravityInfo[ index ]; +} + +/* +=================== +idGameLocal::SetGravityArea +=================== +*/ +void idGameLocal::SetGravityInfo( int index, rvGravityArea* info ) { + gravityInfo[ index ] = info; +} + +/* +=================== +idGameLocal::AddUniqueGravityInfo +=================== +*/ +void idGameLocal::AddUniqueGravityInfo( rvGravityArea* info ) { + gravityInfo.AddUnique( info ); +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( const idVec3& origin ) const { + int numGravityAreas = GetNumGravityAreas(); + if( !numGravityAreas ) { + return -1; + } + + int areaNum = gameRenderWorld->PointInArea( origin ); + + for( int ix = 0; ix < numGravityAreas; ++ix ) { + if( !gameRenderWorld->AreasAreConnected(GetGravityInfo(ix)->GetArea(), areaNum, PS_BLOCK_GRAVITY) ) { + continue; + } + + return ix; + } + + return -1; +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( idEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const { + int index = GetCurrentGravityInfoIndex( origin ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(origin, axis, MASK_SOLID, NULL) : GetGravity(); +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( rvClientEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::ReferenceScriptObjectProxy +=================== +*/ +idEntity* idGameLocal::ReferenceScriptObjectProxy( const char* scriptObjectName ) { + idEntity* proxy = NULL; + idEntityPtr safeProxy; + idDict args; + idScriptObject* object = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + proxy = scriptObjectProxies[ ix ].GetEntity(); + assert( proxy ); + + object = &proxy->scriptObject; + if( !object->data ) { + object->SetType( scriptObjectName ); + proxy->ConstructScriptObject(); + return proxy; + } + } + + args.Set( "classname", "func_static" ); + args.Set( "scriptobject", scriptObjectName ); + args.SetBool( "noclipmodel", true ); + bool spawned = SpawnEntityDef(args, &proxy); + if ( !spawned ) { + assert( 0 ); + } + safeProxy = proxy; + scriptObjectProxies.AddUnique( safeProxy ); + return proxy; +} + +/* +=================== +idGameLocal::ReleaseScriptObjectProxy +=================== +*/ +void idGameLocal::ReleaseScriptObjectProxy( const char* proxyName ) { + idScriptObject* object = NULL; + idEntity* entity = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + entity = scriptObjectProxies[ ix ].GetEntity(); + if( entity && !idStr::Icmp(entity->GetName(), proxyName) ) { + object = &entity->scriptObject; + if( !object ) { + continue; + } + + entity->DeconstructScriptObject(); + object->Free(); + } + } +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +void idGameLocal::ListEntityStats( const idCmdArgs &args ) { + int i, j; + idStr currentMap; + idList uniqueMapNames; + + + for( i = 1; i < args.Argc(); i++ ) { + if ( idStr::Icmp( args.Argv( i ), "clear" ) == 0 ) { + entityUsageList.Clear(); + common->Printf("Entity stats cleared.\n"); + return; + } + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[ i ].SetInt( "reported_stat", false ); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) ) { + continue; + } + + entityUsageList[ i ].GetString( "mapFileName", "none", mapFileName ); + if ( currentMap != mapFileName ) + { + if ( i ) { + common->Printf( "\n" ); + } + common->Printf( "================ %s ================\n", mapFileName.c_str() ); + currentMap = mapFileName; + uniqueMapNames.Insert( mapFileName ); + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + if ( checkMapFileName != mapFileName ) { + break; + } + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 1 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + common->Printf( "\n" ); + common->Printf( "\n" ); + common->Printf( "================ OVERALL ================\n" ); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 2 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 2 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + idFile *FH = fileSystem->OpenFileWrite( "EntityStats.csv" ); + if ( FH ) { + int size = sizeof( int ) * uniqueMapNames.Num(); + int *count = ( int * )_alloca( size ); + + FH->Printf("\"Definition\""); + for( i = 0; i < uniqueMapNames.Num(); i++ ) { + FH->Printf( ",\"%s\"", uniqueMapNames[ i ].c_str() ); + } + FH->Printf(",Total\n"); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr className; + int total; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 3 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + + memset( count, 0, size ); + for( j = i; j < entityUsageList.Num(); j++ ) + { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 3 ); + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + + int loc = uniqueMapNames.FindIndex( checkMapFileName ); + if ( loc >= 0 ) { + count[ loc ]++; + } + } + } + + total = 0; + FH->Printf( "\"%s\"", className.c_str() ); + for( j = 0; j < uniqueMapNames.Num(); j++ ) { + FH->Printf( ",%d", count[ j ] ); + total += count[ j ]; + } + FH->Printf( ",%d\n", total ); + } + + fileSystem->CloseFile( FH ); + } +} +// RAVEN END + +/* +====================== +idGameLocal::SpreadLocations + +Now that everything has been spawned, associate areas with location entities +====================== +*/ +void idGameLocal::SpreadLocations() { + idEntity *ent; + +// RAVEN BEGIN + if( !gameRenderWorld ) { + common->Error( "GameRenderWorld is NULL!" ); + } +// RAVEN END + + // allocate the area table + int numAreas = gameRenderWorld->NumAreas(); + locationEntities = new idLocationEntity *[ numAreas ]; + memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); + + // for each location entity, make pointers from every area it touches + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLocationEntity::GetClassType() ) ) { +// RAVEN END + continue; + } + idVec3 point = ent->spawnArgs.GetVector( "origin" ); + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); + continue; + } + if ( areaNum >= numAreas ) { + Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), + locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); + continue; + } + locationEntities[areaNum] = static_cast(ent); + + // spread to all other connected areas + for ( int i = 0 ; i < numAreas ; i++ ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(ent); + } + } + } +} + +/* +=================== +idGameLocal::AddLocation +=================== +*/ +idLocationEntity* idGameLocal::AddLocation( const idVec3& point, const char* name ) { + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Warning ( "idGameLocal::AddLocation: cannot add location entity '%s' at '%g %g %g'\n", name, point.x, point.y, point.z ); + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::AddLocation: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning ( "idGameLocal::AddLocation: location '%s' already exists at '%g %g %g'\n", locationEntities[areaNum]->GetName(), point.x, point.y, point.z ); + return NULL; + } + + // Spawn the new location entity + idDict args; + args.Set ( "location", name ); + locationEntities[areaNum] = static_cast(SpawnEntityType ( idLocationEntity::GetClassType(), &args )); + + // spread to all other connected areas + for ( int i = gameRenderWorld->NumAreas() - 1 ; i >= 0 ; i-- ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(locationEntities[areaNum]); + } + } + + return locationEntities[areaNum]; +} + +/* +=================== +idGameLocal::LocationForPoint + +The player checks the location each frame to update the HUD text display +May return NULL +=================== +*/ +idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { + if ( !locationEntities ) { + // before SpreadLocations() has been called + return NULL; + } + + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); + } + + return locationEntities[ areaNum ]; +} + +/* +============ +idGameLocal::SetPortalState +============ +*/ +void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL ); + outMsg.WriteLong( portal ); + outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + gameRenderWorld->SetPortalState( portal, blockingBits ); +} + +/* +============ +idGameLocal::sortSpawnPoints +============ +*/ +int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { + const spawnSpot_t *spot1 = static_cast( ptr1 ); + const spawnSpot_t *spot2 = static_cast( ptr2 ); + float diff; + + diff = spot1->dist - spot2->dist; + if ( diff < 0.0f ) { + return 1; + } else if ( diff > 0.0f ) { + return -1; + } else { + return 0; + } +} + +// RAVEN BEGIN +// ddynerman: new gametype specific spawn code +// TODO this should be moved to idMultiplayerGame +/* +=========== +idGameLocal::InitializeSpawns +randomize the order of the initial spawns +prepare for a sequence of initial player spawns +============ +*/ +void idGameLocal::InitializeSpawns( void ) { + idEntity* spot = NULL; + + // initialize the spawns for clients as well, need them for free fly demo replays + if ( !isMultiplayer ) { + return; + } + + spawnSpots.Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[i].Clear(); + } + + spot = FindEntityUsingDef( NULL, "info_player_team" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + spot = FindEntityUsingDef( NULL, "info_player_deathmatch" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + spawnSpots.Append ( static_cast(spot) ); + } + spot = FindEntityUsingDef( spot, "info_player_deathmatch" ); + } + + while( spot ) { + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { + // RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + if( IsFlagGameType() && ( teamSpawnSpots[ TEAM_STROGG ].Num() == 0 || teamSpawnSpots[ TEAM_MARINE ].Num() == 0 ) ) { + Error( "InitializeSpawns() - Map must have at least one Marine and one Strogg spawn for CTF gametype."); + } + + if( spawnSpots.Num() == 0 ) { + Error( "InitializeSpawns() - Map must have a spawn spot." ); + } + + common->Printf( "%d general spawns\n", spawnSpots.Num() ); + common->Printf( "%d team spawns (%d strogg/%d marine)\n", teamSpawnSpots[TEAM_STROGG].Num() + teamSpawnSpots[TEAM_MARINE].Num(), + teamSpawnSpots[TEAM_STROGG].Num(), teamSpawnSpots[TEAM_MARINE].Num()); +} + +/* +=========== +idGameLocal::UpdateForwardSpawn +ddynerman: Updates forward spawn lists +=========== +*/ +void idGameLocal::UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ) { + teamForwardSpawnSpots[ team ].Append( point ); +} + +/* +=========== +idGameLocal::ClearForwardSpawn +ddynerman: Clears forward spawn lists +=========== +*/ +void idGameLocal::ClearForwardSpawns( void ) { + for( int i = 0; i < TEAM_MAX; i++ ) { + teamForwardSpawnSpots[ i ].Clear(); + } +} + +/* +=========== +idGameLocal::SpotWouldTelefrag +=========== +*/ +bool idGameLocal::SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ) { + idPlayer* playerList[ MAX_CLIENTS ]; + idBounds bound = player->GetPhysics()->GetBounds(); + + bound.TranslateSelf( spawn->GetPhysics()->GetOrigin() ); + int numEntities = PlayersTouchingBounds( player, bound, CONTENTS_BODY, playerList, MAX_CLIENTS ); + + return !( numEntities == 0 ); +} + +/* +=========== +idGameLocal::SelectSpawnSpot +ddynerman: Selects a spawn spot randomly from spots furthest from the player + This is taken from q3 +=========== +*/ +idEntity* idGameLocal::SelectSpawnPoint( idPlayer* player ) { + if( !isMultiplayer ) { + idEntity* ent = FindEntityUsingDef( NULL, "info_player_start" ); + if ( !ent ) { + Error( "No info_player_start on map.\n" ); + } + return ent; + } + + if ( player == NULL ) { + return NULL; + } + + // Give spectators any old random spot + if ( player->team < 0 || player->team >= TEAM_MAX || player->spectating ) { + common->DPrintf("Returning a random spot\n"); + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; + } + + idList weightedSpawns; + idList* spawnArray = NULL; + + // Pick which spawns to use based on gametype +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_TOURNEY ) { + spawnArray = &spawnSpots; + } + else if( IsFlagGameType() || gameLocal.gameType == GAME_DEADZONE ) { + if( teamForwardSpawnSpots[ player->team ].Num() ) { + spawnArray = &teamForwardSpawnSpots[ player->team ]; + } else { + spawnArray = &teamSpawnSpots[ player->team ]; + } + } +// RITUAL END + + if ( spawnArray == NULL ) { + Error( "SelectSpawnPoint() - invalid spawn list." ); + return NULL; + } + + idVec3 refPos; + if ( player->lastKiller != NULL && !player->lastKiller->spectating && player->lastKiller->GetInstance() == player->GetInstance() ) { + refPos = player->lastKiller->GetPhysics()->GetOrigin(); + } else { + refPos = player->GetPhysics()->GetOrigin(); + } + + for ( int i = 0; i < spawnArray->Num(); i++ ) { + idPlayerStart* spot = (*spawnArray)[i]; + + if ( spot->GetInstance() != player->GetInstance() || SpotWouldTelefrag( player, spot ) ) { + continue; + } + + idVec3 pos = spot->GetPhysics()->GetOrigin(); + float dist = ( pos - refPos ).LengthSqr(); + + spawnSpot_t newSpot; + + newSpot.dist = dist; + newSpot.ent = (*spawnArray)[ i ]; + weightedSpawns.Append( newSpot ); + } + + if ( weightedSpawns.Num() == 0 ) { + // no spawns avaialable, spawn randomly + common->DPrintf("no spawns avaialable, spawn randomly\n"); + return (*spawnArray)[ random.RandomInt( spawnArray->Num() ) ]; + } + + qsort( ( void * )weightedSpawns.Ptr(), weightedSpawns.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + int rnd = rvRandom::flrand( 0.0, 1.0 ) * (weightedSpawns.Num() / 2); + return weightedSpawns[ rnd ].ent; +} +/* +================ +idGameLocal::UpdateServerInfoFlags +================ +*/ +// RAVEN BEGIN +// ddynerman: new gametype strings +void idGameLocal::SetGameType( void ) { + gameType = GAME_SP; + + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "singleplayer" ) ) { + mpGame.SetGameType(); + } +} +// RAVEN END + +/* +================ +idGameLocal::SetGlobalMaterial +================ +*/ +void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { + globalMaterial = mat; +} + +/* +================ +idGameLocal::GetGlobalMaterial +================ +*/ +const idMaterial *idGameLocal::GetGlobalMaterial() { + return globalMaterial; +} + +/* +================ +idGameLocal::GetSpawnId +================ +*/ +int idGameLocal::GetSpawnId( const idEntity* ent ) const { + return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameLocal::ThrottleUserInfo +================ +*/ +void idGameLocal::ThrottleUserInfo( void ) { + mpGame.ThrottleUserInfo(); +} + +/* +=========== +idGameLocal::ValidateServerSettings +============ +*/ +bool idGameLocal::ValidateServerSettings( const char* map, const char* gametype ) { + // PickMap uses si_map directly + // PickMap returns wether we would have to change the maps, which means settings are invalid + assert( !idStr::Icmp( si_map.GetString(), map ) ); + if ( mpGame.PickMap( gametype, true ) ) { + common->Printf( "map '%s' and gametype '%s' are not compatible\n", map, gametype ); + return false; + } + return true; +} + +/* +=========== +idGameLocal::NeedRestart +============ +*/ +bool idGameLocal::NeedRestart() { + + idDict newInfo; + const idKeyValue *keyval, *keyval2; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + + for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + return true; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) || !keyval->GetKey().Icmp( "si_fps" ) ) ) { + return true; + } + } + return false; +} + +// RAVEN BEGIN +// jshepard: update player hud to alert to end of level +/* +=================== +idGameLocal::UpdateEndLevel +=================== +*/ +void idGameLocal::UpdateEndLevel() { + idPlayer * player = GetLocalPlayer(); + + if( player && player->GetHud() ) { + player->GetHud()->HandleNamedEvent( "showExit" ); + } +} + + +// bdube: added +/* +================ +idGameLocal::GetEffect + +Get the handle of the effect with the given name +================ +*/ +const idDecl *idGameLocal::GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType ) { + const char *effectFile = NULL; + + float chance = args.GetFloat ( idStr("effectchance ") + effectName, "1" ); + if ( random.RandomFloat ( ) > chance ) { + return NULL; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + assert( !idStr::Icmpn( effectName, "fx_", 3 ) ); + + if ( materialType ) { + idStr temp; + const char* result = NULL; + + temp = effectName; + temp += "_"; + + temp += materialType->GetName(); + + // See if the given material effect is specified + idStr testMP = temp; + testMP += "_mp"; + + result = args.GetString( testMP ); + + if ( !result || !*result ) { + result = args.GetString( temp ); + } + if ( result && *result) { + return( ( const idDecl * )declManager->FindEffect( result ) ); + } + } + + // grab the non material effect name + idStr alternateEffect = effectName; + alternateEffect += "_mp"; + + idStr classname = args.GetString( "classname" ); + if ( !alternateEffect.Icmp( "fx_fly_mp" ) ) { + if ( + ( !g_nailTrail.GetBool() && !classname.Icmpn( "projectile_nail", 15 ) ) || + ( !g_rocketTrail.GetBool() && !classname.Icmpn( "projectile_rocket", 17 ) ) || + ( !g_napalmTrail.GetBool() && !classname.Icmpn( "projectile_napalm", 17 ) ) ) + { + alternateEffect += "_low"; + } else if ( !g_grenadeTrail.GetBool() && !classname.Icmpn( "projectile_grenade", 18 ) ) + { + return NULL; + } + } else if ( !alternateEffect.Icmp( "fx_path_mp" ) ) + { + if ( !g_railTrail.GetBool() && !classname.Icmpn( "hitscan_railgun", 15 ) ) + { + alternateEffect += "_low"; + } + } + + effectFile = args.GetString( alternateEffect ); + + if ( !effectFile || !*effectFile ) { + effectFile = args.GetString( effectName ); + } + + if ( !effectFile || !*effectFile ) { + return NULL; + } + + return( ( const idDecl * )declManager->FindEffect( effectFile ) ); +} + +/* +================ +idGameLocal::PlayEffect + +Plays an effect at the given origin using the given direction +================ +*/ +rvClientEffect* idGameLocal::PlayEffect( + const idDecl *effect, + const idVec3& origin, + const idMat3& axis, + bool loop, + const idVec3& endOrigin, + bool broadcast, + bool predictedBit, + effectCategory_t category, + const idVec4& effectTint ) { + + if ( !effect ) { + return NULL; + } + + if ( !gameLocal.isNewFrame ) { + return NULL; + } + + if ( isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idCQuat quat; + + quat = axis.ToCQuat(); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EFFECT ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( origin.x ); + msg.WriteFloat( origin.y ); + msg.WriteFloat( origin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + msg.WriteBits( loop, 1 ); + msg.WriteBits( predictedBit, 1 ); + + // send to everyone who has start or end in it's PVS + SendUnreliableMessagePVS( msg, currentThinkingEntity, pvs.GetPVSArea( origin ), pvs.GetPVSArea( endOrigin ) ); + } + + if ( isServer && localClientNum < 0 ) { + // no effects on dedicated server + return NULL; + } + + if ( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } +// RAVEN END + + if ( gameLocal.isListenServer && currentThinkingEntity && gameLocal.GetLocalPlayer() ) { + if ( currentThinkingEntity->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( origin ); + clientEffect->SetAxis( axis ); + clientEffect->SetGravity( GetCurrentGravity( origin, axis ) ); + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +void idGameLocal::CheckPlayerWhizzBy( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ) +{ + //FIXME: make this client-side? Work in MP? + if ( gameLocal.isMultiplayer ) { + return; + } + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + if ( player->IsHidden() ) { + return; + } + if ( player == attacker ) { + return; + } + if ( player == hitEnt ) { + return; + } + idVec3 center = player->firstPersonViewOrigin; + idVec3 diff = end-center; + if ( diff.Length() < 64.0f ) { + //hit too close - didn't actually pass by, will hear impact sound instead + return; + } + idVec3 closestPoint = player->firstPersonViewOrigin; + if ( closestPoint.ProjectToLineSeg( start, end ) ) { + //on line seg + diff = closestPoint-center; + if ( diff.Length() < 48.0f ) { + //close enough to hear whizz-by + idVec3 dir = end-start; + dir.Normalize(); + idVec3 fxStart = closestPoint+dir*-32.0f; + idVec3 fxEnd = closestPoint+dir*32.0f; + player->PlayEffect( "fx_whizby", fxStart, player->firstPersonViewAxis, false, fxEnd ); + } + } +} + +/* +================ +idGameLocal::HitScan + +Run a hitscan trace from the given origin and direction +================ +*/ +idEntity* idGameLocal::HitScan( + const idDict& hitscanDict, + const idVec3& origOrigin, + const idVec3& origDir, + const idVec3& origFxOrigin, + idEntity* owner, + bool noFX, + float damageScale, +// twhitaker: added additionalIgnore parameter + idEntity* additionalIgnore, + int areas[ 2 ] + ) { + + idVec3 dir; + idVec3 origin; + idVec3 fxOrigin; + idVec3 fxDir; + idVec3 impulse; + idVec4 hitscanTint( 1.0f, 1.0f, 1.0f, 1.0f ); + int reflect; + float tracerChance; + idEntity* ignore; + float penetrate; + + if ( areas ) { + areas[ 0 ] = pvs.GetPVSArea( origFxOrigin ); + areas[ 1 ] = -1; + } + + ignore = owner; + penetrate = hitscanDict.GetFloat( "penetrate" ); + + if( hitscanDict.GetBool( "hitscanTint" ) && owner->IsType( idPlayer::GetClassType() ) ) { + hitscanTint = ((idPlayer*)owner)->GetHitscanTint(); + } + + // twhitaker: additionalIgnore parameter + if ( !additionalIgnore ) { + additionalIgnore = ignore; + } + + origin = origOrigin; + fxOrigin = origFxOrigin; + dir = origDir; + tracerChance = ((g_perfTest_weaponNoFX.GetBool())?0:hitscanDict.GetFloat( "tracerchance", "0" )); + + // Apply player powerups + if ( owner && owner->IsType( idPlayer::GetClassType() ) ) { + damageScale *= static_cast(owner)->PowerUpModifier(PMOD_PROJECTILE_DAMAGE); + } + + // Run reflections + for ( reflect = hitscanDict.GetFloat( "reflect", "0" ); reflect >= 0; reflect-- ) { + idVec3 start; + idVec3 end; + idEntity* ent; + idEntity* actualHitEnt; + trace_t tr; + int contents; + int collisionArea; + idVec3 collisionPoint; + bool tracer; + + // Calculate the end point of the trace + start = origin; + if ( g_perfTest_hitscanShort.GetBool() ) { + end = start + (dir.ToMat3() * idVec3(idMath::ClampFloat(0,2048,hitscanDict.GetFloat ( "range", "2048" )),0,0)); + } else { + end = start + (dir.ToMat3() * idVec3(hitscanDict.GetFloat ( "range", "40000" ),0,0)); + } + if ( g_perfTest_hitscanBBox.GetBool() ) { + contents = MASK_SHOT_BOUNDINGBOX|CONTENTS_PROJECTILE; + } else { + contents = MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE; + } + + // Loop the traces to handle cases where something can be shot through + while ( 1 ) { + // Trace to see if we hit any entities +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( hitscanDict.GetFloat( "trace_size", "0" ) > 0.0f ) + { + float range = hitscanDict.GetFloat ( "range", "1024" ); + if ( range > 4096.0f ) + { + assert( !(range > 4096.0f) ); + Warning( "idGameLocal::HitScan: hitscan def (%s) with trace_size must have max range of 4096!", hitscanDict.GetString( "classname" ) ); + range = idMath::ClampFloat( 0.0f, 4096.0f, range ); + } + end = start + (dir * range); + + idBounds traceBounds; + traceBounds.Zero(); + traceBounds.ExpandSelf( hitscanDict.GetFloat( "trace_size", "0" ) ); + // twhitaker: additionalIgnore parameter + TraceBounds( owner, tr, start, end, traceBounds, contents, additionalIgnore ); + } + else + { + // twhitaker: additionalIgnore parameter + TracePoint( owner, tr, start, end, contents, additionalIgnore ); + } + //gameRenderWorld->DebugArrow( colorRed, start, end, 10, 5000 ); +// RAVEN END + + // If the hitscan hit a no impact surface we can just return out + //assert( tr.c.material ); + if ( tr.fraction >= 1.0f || (tr.c.material && tr.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + PlayEffect( hitscanDict, "fx_path", fxOrigin, dir.ToMat3(), false, tr.endpos, false, false, EC_IGNORE, hitscanTint ); + if ( random.RandomFloat( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, dir.ToMat3(), false, tr.endpos ); + tracer = true; + } else { + tracer = false; + } + + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.endpos ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + + return NULL; + } + + // computing the collisionArea from the collisionPoint fails sometimes + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.c.point ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + collisionPoint = tr.c.point - ( tr.c.normal * tr.c.point - tr.c.dist ) * tr.c.normal; + ent = entities[ tr.c.entityNum ]; + actualHitEnt = NULL; + start = collisionPoint; + + // Keep tracing if we hit water + if ( (ent->GetPhysics()->GetContents() & CONTENTS_WATER) || (tr.c.material && (tr.c.material->GetContentFlags() & CONTENTS_WATER)) ) { + // Apply force to the water entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -(hitscanDict.GetFloat( "push", "5000" )) * tr.c.normal ); + // Continue on excluding water + contents &= (~CONTENTS_WATER); + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + continue; + // Reflect off a bounce target? + } else if ( (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && !hitscanDict.GetBool ( "noBounce" ) ) { + reflect++; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->fl.takedamage && ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + if ( !gameLocal.isClient ) { + + // Apply force to the entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -tr.c.normal, &hitscanDict ); + + // Handle damage to the entity + if ( ent->fl.takedamage && !(( tr.c.material != NULL ) && ( tr.c.material->GetSurfaceFlags() & SURF_NODAMAGE )) ) { + const char* damage; + + damage = NULL; + + // RAVEN BEGIN + // jdischler: code from the other project..to ensure that if an attached head is hit, the body will use the head joint + // otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(tr.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == tr.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } + // RAVEN END + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + + if ( damage && damage[0] ) { + // RAVEN BEGIN + // ddynerman: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, ((idPlayer*)owner)->GetCurrentWeapon() ); + } + // RAVEN END + ent->Damage( owner, owner, dir, damage, damageScale, hitJoint ); + } + + // Let the entity add its own damage effect + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect ( tr, dir, damage, owner ); + } + } else { + if ( actualHitEnt + && actualHitEnt != ent + && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + const char* damage = NULL; + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + if ( damage && damage[0] ) { + actualHitEnt->Damage( owner, owner, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( tr.c.id ) ); + } + } + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect( tr, dir, hitscanDict.GetString ( "def_damage" ), owner ); + } + } + } + + + // Pass through actors + if ( ent->IsType ( idActor::GetClassType() ) && penetrate > 0.0f ) { + start = collisionPoint; + additionalIgnore = ent; + damageScale *= penetrate; + continue; + } + break; + } + + // Path effect + fxDir = collisionPoint - fxOrigin; + fxDir.Normalize( ); + PlayEffect( hitscanDict, "fx_path", fxOrigin, fxDir.ToMat3(), false, collisionPoint, false, false, EC_IGNORE, hitscanTint ); + if ( !ent->fl.takedamage && random.RandomFloat ( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, fxDir.ToMat3(), false, collisionPoint ); + tracer = true; + } else { + tracer = false; + } + + if ( !reflect ) { + //on initial trace only + if ( hitscanDict.GetBool( "doWhizz" ) ) { + //play whizz-by sound if trace is close to player's head + CheckPlayerWhizzBy( origin, collisionPoint, ent, owner ); + } + } + + // Play a different effect when reflecting + if ( !reflect || ent->fl.takedamage ) { + idMat3 axis; + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType ( idActor::GetClassType() ) ) { + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + } else { + axis = tr.c.normal.ToMat3(); + } + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + // End of reflection + return ent; + } else { + PlayEffect( GetEffect( hitscanDict, "fx_reflect", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3() ); + } + + // Calc new diretion based on bounce + origin = start; + fxOrigin = start; + dir = ( dir - ( 2.0f * DotProduct( dir, tr.c.normal ) * tr.c.normal ) ); + dir.Normalize( ); + + // Increase damage scale on reflect + damageScale += hitscanDict.GetFloat( "reflect_powerup", "0" ); + } + + assert( false ); + + return NULL; +} + +/* +=================== +idGameLocal::RegisterClientEntity +=================== +*/ +void idGameLocal::RegisterClientEntity( rvClientEntity *cent ) { + int entityNumber; + + assert ( cent ); + + if ( clientSpawnCount >= ( 1 << ( 32 - CENTITYNUM_BITS ) ) ) { +// Error( "idGameLocal::RegisterClientEntity: spawn count overflow" ); + clientSpawnCount = INITIAL_SPAWN_COUNT; + } + + // Find a free entity index to use + while( clientEntities[firstFreeClientIndex] && firstFreeClientIndex < MAX_CENTITIES ) { + firstFreeClientIndex++; + } + + if ( firstFreeClientIndex >= MAX_CENTITIES ) { + cent->PostEventMS ( &EV_Remove, 0 ); + Warning( "idGameLocal::RegisterClientEntity: no free client entities" ); + return; + } + + entityNumber = firstFreeClientIndex++; + + // Add the client entity to the lists + clientEntities[ entityNumber ] = cent; + clientSpawnIds[ entityNumber ] = clientSpawnCount++; + cent->entityNumber = entityNumber; + cent->spawnNode.AddToEnd( clientSpawnedEntities ); + cent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( entityNumber >= num_clientEntities ) { + num_clientEntities++; + } +} + +/* +=================== +idGameLocal::UnregisterClientEntity +=================== +*/ +void idGameLocal::UnregisterClientEntity( rvClientEntity* cent ) { + assert( cent ); + + // No entity number then it failed to register + if ( cent->entityNumber == -1 ) { + return; + } + + cent->spawnNode.Remove ( ); + cent->bindNode.Remove ( ); + + if ( clientEntities [ cent->entityNumber ] == cent ) { + clientEntities [ cent->entityNumber ] = NULL; + clientSpawnIds[ cent->entityNumber ] = -1; + if ( cent->entityNumber < firstFreeClientIndex ) { + firstFreeClientIndex = cent->entityNumber; + } + cent->entityNumber = -1; + } +} + +/* +=================== +idGameLocal::Translation +=================== +*/ +bool idGameLocal::Translation( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->Translation( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } + + return false; +} + +/* +=================== +idGameLocal::Rotation +=================== +*/ +bool idGameLocal::Rotation( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::Motion +=================== +*/ +bool idGameLocal::Motion( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Motion( results, start, end, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + + +/* +=================== +idGameLocal::Contacts +=================== +*/ +int idGameLocal::Contacts( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contacts( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, passEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::Contents +=================== +*/ +int idGameLocal::Contents( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contents( start, mdl, trmAxis, contentMask, passEntity, touchedEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::TracePoint +=================== +*/ +bool idGameLocal::TracePoint( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TracePoint( results, start, end, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TraceBounds +=================== +*/ +bool idGameLocal::TraceBounds( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TraceBounds( results, start, end, bounds, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TranslationModel +=================== +*/ +void idGameLocal::TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationModel( results, start, end, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::RotationModel +=================== +*/ +void idGameLocal::RotationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->RotationModel( results, start, rotation, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::ContactsModel +=================== +*/ +int idGameLocal::ContactsModel( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContactsModel( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::ContentsModel +=================== +*/ +int idGameLocal::ContentsModel( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContentsModel( start, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::TranslationEntities +=================== +*/ +void idGameLocal::TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationEntities( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } +} + +/* +=================== +idGameLocal::GetModelContactFeature +=================== +*/ +bool idGameLocal::GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->GetModelContactFeature( contact, clipModel, winding ); + } + + return false; +} + +/* +=================== +idGameLocal::EntitiesTouchingBounds +=================== +*/ +int idGameLocal::EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->EntitiesTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::ClipModelsTouchingBounds +=================== +*/ +int idGameLocal::ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ClipModelsTouchingBounds( bounds, contentMask, clipModelList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::PlayersTouchingBounds +=================== +*/ +int idGameLocal::PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->PlayersTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::GetWorldBounds +=================== +*/ +const idBounds& idGameLocal::GetWorldBounds( const idEntity* ent ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->GetWorldBounds(); + } + + return clip[ 0 ]->GetWorldBounds(); +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +const idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) const { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::AddClipWorld +=================== +*/ +int idGameLocal::AddClipWorld( int id ) { + if( id >= clip.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = clip.Num(); i <= id; i++ ) { + clip.Append( NULL ); + } + } + + if( clip[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); +// RAVEN END + clip[ id ] = new idClip(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip[ id ]->Init(); + } + return id; +} + +/* +=================== +idGameLocal::RemoveClipWorld +=================== +*/ +void idGameLocal::RemoveClipWorld( int id ) { + assert( id >= 0 && id < clip.Num() ); + + clip[ id ]->Shutdown(); + delete clip[ id ]; + clip[ id ] = NULL; +} + +/* +=================== +idGameLocal::ShutdownInstances +=================== +*/ +void idGameLocal::ShutdownInstances( void ) { + if( gamestate == GAMESTATE_UNINITIALIZED ) { + return; + } + + instances.DeleteContents( true ); + + // free the trace model used for the defaultClipModel + idClip::FreeDefaultClipModel(); +} + +/* +=================== +idGameLocal::AddInstance +=================== +*/ +int idGameLocal::AddInstance( int id, bool deferPopulate ) { + if ( id == -1 ) { + id = instances.Num(); + } + + if ( id >= instances.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = instances.Num(); i <= id; i++ ) { + instances.Append( NULL ); + } + } + + if ( instances[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + instances[ id ] = new rvInstance( id, deferPopulate ); + RV_POP_HEAP(); +// RAVEN END + + if ( !deferPopulate ) { + // keep track of the high watermark + ServerSetEntityIndexWatermark( id ); + } + + common->DPrintf( "idGameLocal::AddInstance(): Adding instance %d\n", instances[ id ]->GetInstanceID() ); + } else { + common->DPrintf( "idGameLocal::AddInstance(): Instance %d already exists\n", instances[ id ]->GetInstanceID() ); + } + + // keep the min spawn index correctly set + ServerSetMinSpawnIndex(); + + return instances[ id ]->GetInstanceID(); +} + +/* +=================== +idGameLocal::RemoveInstance +=================== +*/ +void idGameLocal::RemoveInstance( int id ) { + delete instances[ id ]; + instances[ id ] = NULL; +} + +/* +=================== +idGameLocal::GetPlayerName +Returns the specified player name, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerName( int clientNum, char* name ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( name, gameLocal.GetUserInfo( clientNum )->GetString( "ui_name" ), 64 ); + name[ 63 ] = 0; +} + +/* +=================== +idGameLocal::GetPlayerClan +Returns the specified player clan, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerClan( int clientNum, char* clan ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( clan, gameLocal.GetUserInfo( clientNum )->GetString( "ui_clan" ), 64 ); + clan[ 63 ] = 0; +} + +/* +=================== +idGameLocal::SetFriend +=================== +*/ +void idGameLocal::SetFriend( int clientNum, bool isFriend ) { + if( !gameLocal.GetLocalPlayer() ) { + Warning( "idGameLocal::SetFriend() - SetFriend() called with NULL local player\n" ); + return; + } + + gameLocal.GetLocalPlayer()->SetFriend( clientNum, isFriend ); +} + +/* +=================== +idGameLocal::GetLongGametypeName +=================== +*/ +const char* idGameLocal::GetLongGametypeName( const char* gametype ) { + return mpGame.GetLongGametypeName( gametype ); +} + +void idGameLocal::Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ) { + int instance = 0; + + if ( args.Argc() > 1 ) { + instance = atoi( args.Argv( 1 ) ); + } + + if( gameLocal.instances[ instance ] ) { + gameLocal.instances[ instance ]->PrintMapNumbers(); + } +} + +void idGameLocal::Cmd_PrintSpawnIds_f( const idCmdArgs& args ) { + for( int i = 0; i < MAX_GENTITIES; i++ ) { + if( gameLocal.entities[ i ] ) { + gameLocal.Printf( "Spawn id %d: %d\n", i, gameLocal.spawnIds[ i ] ); + } + } +} + +/* +=============== +idGameLocal::IsTeamPowerups +=============== +*/ +bool idGameLocal::IsTeamPowerups( void ) { + if ( !serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + return false; + } + if ( !IsTeamGameType() ) { + return false; + } + return ( gameType != GAME_ARENA_CTF ); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +=================== +idGameLocal::FlushBeforelevelLoad +=================== +*/ +void idGameLocal::FlushBeforelevelLoad( void ) +{ + TIME_THIS_SCOPE( __FUNCLINE__); + +#ifndef _XENON + MapShutdown(); +#else + mpGame.Clear(); +#endif + for(int i = 0; i < aasNames.Num(); i++) + { + aasList[i]->Shutdown(); + } +} +#endif +// RAVEN END + +// dluetscher: moved the overloaded new/delete to sys_local.cpp and Game_local.cpp (from Heap.h) +// so that the tools.dll will link. +#if !defined(_XBOX) && (defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT)) + +#undef new +#undef delete +#undef Mem_Alloc +#undef Mem_Free + +#ifdef ID_DEBUG_MEMORY +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p, "", 0 ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p, "", 0 ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void *operator new( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p ); +} +#endif // #else #ifdef ID_DEBUG_MEMORY +#endif // #if defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END diff --git a/source/mpgame/Game_local.h b/source/mpgame/Game_local.h new file mode 100644 index 0000000..ed91d22 --- /dev/null +++ b/source/mpgame/Game_local.h @@ -0,0 +1,1496 @@ +#ifndef __GAME_LOCAL_H__ +#define __GAME_LOCAL_H__ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +inline void *operator new( size_t s ) { return Memory::Allocate(s); } +inline void operator delete( void *p ) { Memory::Free(p); } +inline void *operator new[]( size_t s ) { return Memory::Allocate(s); } +inline void operator delete[]( void *p ) { Memory::Free(p); } +#endif +// RAVEN END + +/* +=============================================================================== + + Local implementation of the public game interface. + +=============================================================================== +*/ + + +// if set to 1 the server sends the client PVS with snapshots and the client compares against what it sees +#ifndef ASYNC_WRITE_PVS + #define ASYNC_WRITE_PVS 0 +#endif + +extern idRenderWorld * gameRenderWorld; + +// classes used by idGameLocal +class idEntity; +class idActor; +class idPlayer; +class idCamera; +class idWorldspawn; +class idTestModel; +class idAAS; +class idAI; +// RAVEN BEGIN +// bdube: not using id effects +//class idSmokeParticles; +//class idEntityFx; +// bdube: client side entities +class rvInstance; +class rvClientEntity; +class rvClientModel; +class rvCTFAssaultPlayerStart; +class idPlayerStart; +// RAVEN END +class idTypeInfo; +class idProgram; +class idThread; +class idEditEntities; +class idLocationEntity; + +// RAVEN BEGIN +// dluetscher: reduced max clients for memory usage +#ifdef _XENON +#define MAX_CLIENTS 16 +#else +// RAVEN END +#define MAX_CLIENTS 32 +#endif + +#define GENTITYNUM_BITS 12 +#define MAX_GENTITIES (1<PostEventMS(&EV_Remove, 0); (p) = NULL; } +// RAVEN END + +//============================================================================ + +void gameError( const char *fmt, ... ); + +//============================================================================ + +typedef enum { + CHANNEL_DEST_NONE, + CHANNEL_DEST_RELIABLE_SERVER, + CHANNEL_DEST_RELIABLE_REPEATER, + + CHANNEL_DEST_COUNT +} channelDestType_t; + +class idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const = 0; + virtual void Send( const idBitMsg &msg ) const = 0; +}; + +class idNullMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_NONE; } + virtual void Send( const idBitMsg &msg ) const { return; }; +}; + +class idServerReliableMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_RELIABLE_SERVER; } + virtual void Send( const idBitMsg &msg ) const { + if ( excludeClient != -1 ) { + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg, inhibitRepeater ); + } else { + networkSystem->ServerSendReliableMessage( clientNum, msg, inhibitRepeater ); + } + }; + const idMessageSender &To( int _clientNum, bool _inhibitRepeater = false ) { + clientNum = _clientNum; + excludeClient = -1; + inhibitRepeater = _inhibitRepeater; + return *this; + } + const idMessageSender &NotTo( int _excludeClient, bool _inhibitRepeater = false ) { + clientNum = -1; + excludeClient = _excludeClient; + inhibitRepeater = _inhibitRepeater; + return *this; + } + +private: + int clientNum; + int excludeClient; + bool inhibitRepeater; +}; + +class idRepeaterReliableMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_RELIABLE_REPEATER; } + virtual void Send( const idBitMsg &msg ) const { + if ( excludeClient != -1 ) { + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg, inhibitHeader, repeaterClient ); + } else { + networkSystem->RepeaterSendReliableMessage( repeaterClient, msg, inhibitHeader, clientNum ); + } + }; + const idMessageSender &To( int _clientNum, int asClient = -1, bool _inhibitHeader = false ) { + repeaterClient = _clientNum; + clientNum = asClient; + excludeClient = -1; + inhibitHeader = _inhibitHeader; + return *this; + } + const idMessageSender &ExcludingTo( int _excludeClient, int _clientNum, bool _inhibitHeader = false ) { + repeaterClient = _clientNum; + clientNum = -1; + excludeClient = _excludeClient; + inhibitHeader = _inhibitHeader; + return *this; + } + +private: + int repeaterClient; // viewer number on repeater + int clientNum; // clientNum of real player to address exclusively to + int excludeClient; // clientNum of real player to exclude + bool inhibitHeader; +}; + +extern idNullMessageSender nullSender; +extern idServerReliableMessageSender serverReliableSender; +extern idRepeaterReliableMessageSender repeaterReliableSender; + +//============================================================================ + +#include "gamesys/Event.h" +// RAVEN BEGIN +// bdube: added +#include "gamesys/State.h" +// RAVEN END +#include "gamesys/Class.h" +#include "gamesys/SysCvar.h" +#include "gamesys/SysCmds.h" +#include "gamesys/SaveGame.h" +#include "gamesys/DebugGraph.h" + +#include "script/Script_Program.h" + +#include "anim/Anim.h" + +#include "ai/AAS.h" + +#include "physics/Clip.h" +#include "physics/Push.h" + +#include "Pvs.h" + +#include "Lagometer.h" + +//============================================================================ + +const int MAX_GAME_MESSAGE_SIZE = 8192; +const int MAX_ENTITY_STATE_SIZE = 512; +const int ENTITY_PVS_SIZE = ((MAX_GENTITIES+31)>>5); +// RAVEN BEGIN +// abahr: changed to NUM_PORTAL_ATTRIBUTES to take into account gravity +const int NUM_RENDER_PORTAL_BITS = NUM_PORTAL_ATTRIBUTES; +// RAVEN END + +typedef struct entityState_s { + int entityNumber; + idBitMsg state; + byte stateBuf[MAX_ENTITY_STATE_SIZE]; + struct entityState_s * next; +} entityState_t; + +typedef struct snapshot_s { + int sequence; + entityState_t * firstEntityState; + int pvs[ENTITY_PVS_SIZE]; + struct snapshot_s * next; +} snapshot_t; + +const int MAX_EVENT_PARAM_SIZE = 128; + +typedef struct entityNetEvent_s { + int spawnId; + int event; + int time; + int paramsSize; + byte paramsBuf[MAX_EVENT_PARAM_SIZE]; + struct entityNetEvent_s *next; + struct entityNetEvent_s *prev; +} entityNetEvent_t; + +enum { + GAME_RELIABLE_MESSAGE_SPAWN_PLAYER, + GAME_RELIABLE_MESSAGE_DELETE_ENT, + GAME_RELIABLE_MESSAGE_CHAT, + GAME_RELIABLE_MESSAGE_TCHAT, + GAME_RELIABLE_MESSAGE_DB, + GAME_RELIABLE_MESSAGE_KILL, + GAME_RELIABLE_MESSAGE_DROPWEAPON, + GAME_RELIABLE_MESSAGE_RESTART, + GAME_RELIABLE_MESSAGE_SERVERINFO, + GAME_RELIABLE_MESSAGE_CALLVOTE, + GAME_RELIABLE_MESSAGE_CASTVOTE, + GAME_RELIABLE_MESSAGE_STARTVOTE, + GAME_RELIABLE_MESSAGE_UPDATEVOTE, + GAME_RELIABLE_MESSAGE_PORTALSTATES, + GAME_RELIABLE_MESSAGE_PORTAL, + GAME_RELIABLE_MESSAGE_VCHAT, + GAME_RELIABLE_MESSAGE_STARTSTATE, + GAME_RELIABLE_MESSAGE_MENU, + GAME_RELIABLE_MESSAGE_EVENT, + GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND, + GAME_RELIABLE_MESSAGE_DEATH, + GAME_RELIABLE_MESSAGE_GAMESTATE, + GAME_RELIABLE_MESSAGE_STAT, + GAME_RELIABLE_MESSAGE_ALL_STATS, + GAME_RELIABLE_MESSAGE_INGAMEAWARD, + GAME_RELIABLE_MESSAGE_SET_INSTANCE, + GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING, + GAME_RELIABLE_MESSAGE_SERVER_ADMIN, + GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE, + GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE, + GAME_RELIABLE_MESSAGE_GETADMINBANLIST, + GAME_RELIABLE_MESSAGE_PRINT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST, + GAME_RELIABLE_MESSAGE_GETVOTEMAPS, + // unreliable message sent over the reliable channel + // used as a debug solution when making sure some missing things are not due to packet drop + GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE, + // server acknowledges events from client, in response to specific GAME_RELIABLE_MESSAGE_EVENT situations + GAME_RELIABLE_MESSAGE_EVENT_ACK, +}; + +enum { + GAME_UNRELIABLE_MESSAGE_EVENT, + GAME_UNRELIABLE_MESSAGE_EFFECT, + GAME_UNRELIABLE_MESSAGE_HITSCAN, + GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER +}; + +enum { + GAME_UNRELIABLE_RECORD_CLIENTNUM, + GAME_UNRELIABLE_RECORD_AREAS, + + GAME_UNRELIABLE_RECORD_COUNT +}; + +typedef enum { + GAMESTATE_UNINITIALIZED, // prior to Init being called + GAMESTATE_NOMAP, // no map loaded + GAMESTATE_STARTUP, // inside InitFromNewMap(). spawning map entities. + GAMESTATE_RESTART, // spawning map entities from an instance restart, but not fully restarting + GAMESTATE_ACTIVE, // normal gameplay + GAMESTATE_SHUTDOWN // inside MapShutdown(). clearing memory. +} gameState_t; + +typedef struct { + idPlayerStart *ent; + int dist; +} spawnSpot_t; + +typedef struct { + bool connected; + bool active; + bool priv; + bool nopvs; + userOrigin_t origin; + int pvsArea; + idDict info; +} viewer_t; + +//============================================================================ +class idEventQueue { +public: + typedef enum { + OUTOFORDER_IGNORE, + OUTOFORDER_DROP, + OUTOFORDER_SORT + } outOfOrderBehaviour_t; + + idEventQueue() : start( NULL ), end( NULL ) {} + + entityNetEvent_t * Alloc(); + void Free( entityNetEvent_t *event ); + void Shutdown(); + + void Init(); + void Enqueue( entityNetEvent_t* event, outOfOrderBehaviour_t oooBehaviour ); + entityNetEvent_t * Dequeue( void ); + entityNetEvent_t * RemoveLast( void ); + + entityNetEvent_t * Start( void ) { return start; } + +private: + entityNetEvent_t * start; + entityNetEvent_t * end; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc eventAllocator; +// RAVEN END +}; + +//============================================================================ + +template< class type > +class idEntityPtr { +public: + idEntityPtr(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + idEntityPtr & operator=( type *ent ); + + // synchronize entity pointers over the network + int GetSpawnId( void ) const { return spawnId; } + bool SetSpawnId( int id ); + bool UpdateSpawnId( void ); + + bool IsValid( void ) const; + type * GetEntity( void ) const; + int GetEntityNum( void ) const; + +// RAVEN BEGIN +// bdube: overloaded operators + idEntityPtr( type* ent ) { *this = ent; } + idEntityPtr& operator=( idEntityPtr& ent ) { *this = ent.GetEntity(); return *this; } + type * operator->( void ) const; + operator type *( void ) const; +// RAVEN END + +private: + int spawnId; +}; + +// RAVEN BEGIN +// abahr: forward declaration +class rvGravityArea; +// RAVEN END + +//============================================================================ +// ddynerman: moved MultiplayerGame.h down here, so it can use more stuff in Game_local (idEntityPtr) +#include "MultiplayerGame.h" + +//============================================================================ + +class idGameLocal : public idGame { +public: + idDict serverInfo; // all the tunable parameters, like numclients, etc + idDict repeaterInfo; // serverInfo + repeater specific stuff + int numClients; // pulled from serverInfo and verified + idDict userInfo[MAX_CLIENTS]; // client specific settings + const usercmd_t *usercmds; // client input commands + idDict persistentPlayerInfo[MAX_CLIENTS]; + idEntity * entities[MAX_GENTITIES];// index to entities + int spawnIds[MAX_GENTITIES];// for use in idEntityPtr + int firstFreeIndex; // first free index in the entities array + int minSpawnIndex; // when spawning multiple instances, so nothing pollutes in between the instances + int num_entities; // current number <= MAX_GENTITIES + idHashIndex entityHash; // hash table to quickly find entities by name + idWorldspawn * world; // world entity + idLinkList spawnedEntities; // all spawned entities + idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) + int numEntitiesToDeactivate;// number of entities that became inactive in current frame + bool sortPushers; // true if active lists needs to be reordered to place pushers at the front + bool sortTeamMasters; // true if active lists needs to be reordered to place physics team masters before their slaves + idDict persistentLevelInfo; // contains args that are kept around between levels + +// RAVEN BEGIN +// bdube: client entities + rvClientEntity * clientEntities[MAX_CENTITIES]; // index to client entities + int clientSpawnIds[MAX_CENTITIES]; // for use in idClientEntityPtr + idLinkList clientSpawnedEntities; // all client side entities + int num_clientEntities; // current number of client entities + int firstFreeClientIndex; // first free index in the client entities array + + int entityRegisterTime; +// RAVEN END + + int maxViewers; // currently allocated + int maxViewer; // highest numbered active viewer + 1 + + viewer_t (*viewers); + + // can be used to automatically effect every material in the world that references globalParms + float globalShaderParms[ MAX_GLOBAL_SHADER_PARMS ]; + + idRandom random; // random number generator used throughout the game + + idProgram program; // currently loaded script and data space + idThread * frameCommandThread; + + idPush push; // geometric pushing + idPVS pvs; // potential visible set + pvsHandle_t clientsPVS[MAX_CLIENTS];// PVS of multiplayer clients updated every frame + + idTestModel * testmodel; // for development testing of models +// RAVEN BEGIN +// bdube: not using id effects +// idEntityFx * testFx; // for development testing of fx +// RAVEN END + + // only set when an end level is activated, which will take over camera positioning + // and draw end-level guis, then + + idStr sessionCommand; // a target_sessionCommand can set this to return something to the session + + idMultiplayerGame mpGame; // handles rules for standard dm + + idEditEntities * editEntities; // in game editing + + int cinematicSkipTime; // don't allow skipping cinemetics until this time has passed so player doesn't skip out accidently from a firefight + int cinematicStopTime; // cinematics have several camera changes, so keep track of when we stop them so that we don't reset cinematicSkipTime unnecessarily + int cinematicMaxSkipTime; // time to end cinematic when skipping. there's a possibility of an infinite loop if the map isn't set up right. + bool inCinematic; // game is playing cinematic (player controls frozen) + bool skipCinematic; + + // are kept up to date with changes to serverInfo + int framenum; + int previousTime; // time in msec of last frame + int time; // in msec + int msec; // time since last update in milliseconds + int mHz; // hertz + + int vacuumAreaNum; // -1 if level doesn't have any outside areas + +// RAVEN BEGIN +// abahr: + idList gravityInfo; // area num for each gravity zone + idList< idEntityPtr > scriptObjectProxies; +// RAVEN END + + gameType_t gameType; + bool isMultiplayer; // set if the game is run in multiplayer mode + bool isServer; // set if the game is run for a dedicated or listen server + bool isClient; // set if the game is run for a client + // discriminates between the RunFrame path and the ClientPrediction path + // NOTE: on a listen server, isClient is false + bool isRepeater; // set if the game is repeating + bool isListenServer; + bool isTVClient; + int localClientNum; // number of the local client. MP: -1 on a dedicated, MAX_CLIENTS when playing a server demo + idLinkList snapshotEntities; // entities from the last snapshot + int realClientTime; // real client time + bool isNewFrame; // true if this is a new game frame, not a rerun due to prediction + int entityDefBits; // bits required to store an entity def number + int clientAckSequence; // holds frame number updated through GAME_RELIABLE_MESSAGE_EVENT_ACK server->client messages + + static const char * sufaceTypeNames[ MAX_SURFACE_TYPES ]; // text names for surface types + + idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f + int lastGUI; // last GUI on the lastGUIEnt + + int editors; // Mirrored editors flags from common determine which editors are running + bool isLastPredictFrame; // on an MP server or in SP game this means 'last catchup frame' rather than predict + +// RAVEN BEGIN +// rjohnson: entity usage stats + idStr mapFileNameStripped; // name of the map, empty string if no map loaded, with path and extension removed. If entity filter, that is appended + idList entityUsageList; +// ddynerman: the entity currently thinking, used to play effects/etc only in the appropriate instance + idEntity* currentThinkingEntity; + + const static int INITIAL_SPAWN_COUNT = 1; + +// RAVEN END + + int filterMod; + idList modList; + + int nextLagoCheck; + + // ---------------------- Public idGame Interface ------------------- + + idGameLocal(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ); +#else + virtual void Init( void ); +#endif +// RAVEN END + virtual void Shutdown( void ); + virtual void SetLocalClient( int clientNum ); + virtual void ThrottleUserInfo( void ); + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ); + virtual const idDict * GetUserInfo( int clientNum ); + virtual bool IsClientActive( int clientNum ); + virtual void SetServerInfo( const idDict &serverInfo ); + + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ); + + virtual const idDict & GetPersistentPlayerInfo( int clientNum ); + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randSeed ); + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ); +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ); +// RAVEN END + virtual void MapShutdown( void ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual void SpawnPlayer( int clientNum ); + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ); + virtual void MenuFrame( void ); + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ); + virtual bool Draw( int clientNum ); + virtual escReply_t HandleESC( idUserInterface **gui ); + virtual idUserInterface *StartMenu( void ); + virtual const char * HandleGuiCommands( const char *menuCommand ); + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ); + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void ServerClientConnect( int clientNum, const char *guid ); + virtual void ServerClientBegin( int clientNum ); + virtual void ServerClientDisconnect( int clientNum ); + virtual void ServerWriteInitialReliableMessages( int clientNum ); + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void RepeaterClientConnect( int clientNum ); + virtual void RepeaterClientBegin( int clientNum ); + virtual void RepeaterClientDisconnect( int clientNum ); + virtual void RepeaterWriteInitialReliableMessages( int clientNum ); + virtual void WriteSnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], idMsgQueue &unreliable, int sequence, idBitMsg &msg, int transmitEntity, int transmitEntity2, int instance, bool doPVS, const idBounds &pvs_bounds, int lastSnapshotFrame ); + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ); + virtual bool ServerApplySnapshot( int clientNum, int sequence ); + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ); + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ); + virtual bool ClientApplySnapshot( int clientNum, int sequence ); + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ); +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ); + virtual void ClientEndFrame( void ); + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ); + virtual void ResetRconGuiStatus( void ); +// RAVEN END + + virtual void GetClientStats( int clientNum, char *data, const int len ); + virtual void SwitchTeam( int clientNum, int team ); + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ); + + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ); + +// RAVEN BEGIN +// bdube: client hitscan + virtual void ClientHitScan( const idBitMsg &msg ); +// jscott: for effects system + virtual void StartViewEffect( int type, float time, float scale ); + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ); + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ); + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ); +// bdube: added debug methods + virtual void DebugSetString ( const char* name, const char* value ); + virtual void DebugSetFloat ( const char* name, float value ); + virtual void DebugSetInt ( const char* name, int value ); + virtual const char* DebugGetStatString ( const char* name ); + virtual int DebugGetStatInt ( const char* name ); + virtual float DebugGetStatFloat ( const char* name ); + virtual bool IsDebugHudActive ( void ) const; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ); + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ); + virtual bool PlayerChatDisabled( int clientNum ); + virtual void SetViewComments( const char *text = 0 ); +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ); + virtual void GetPlayerClan( int clientNum, char* clan ); + virtual void SetFriend( int clientNum, bool isFriend ); + static void Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ); + static void Cmd_PrintSpawnIds_f( const idCmdArgs& args ); +// abahr: + virtual int GetNumGravityAreas() const; + virtual const rvGravityArea* GetGravityInfo( int index ) const; + virtual void SetGravityInfo( int index, rvGravityArea* info ); + virtual void AddUniqueGravityInfo( rvGravityArea* info ); + + virtual int GetCurrentGravityInfoIndex( const idVec3& origin ) const; + virtual bool InGravityArea( idEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( idEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( idEntity* entity ) const; + + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const; + + virtual bool InGravityArea( rvClientEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( rvClientEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( rvClientEntity* entity ) const; + virtual idEntity* ReferenceScriptObjectProxy( const char* scriptObjectName ); + virtual void ReleaseScriptObjectProxy( const char* proxyName ); + +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ); +// RAVEN END + + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ); + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ); + virtual void WriteNetworkInfo( idFile* file, int clientNum ); + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ); + virtual bool ValidateDemoProtocol( int minor_ref, int minor ); + + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ); + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ); + + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ); + virtual void RepeaterEndSnapshots( void ); + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ); + + virtual int GetDemoFollowClient( void ) { return IsServerDemoPlaying() ? followPlayer : -1; } + + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) { Error( "Bot input requested\n" ); }; + + virtual const char * GetLoadingGui( const char *mapDeclName ) { return NULL; } + virtual void SetupLoadingGui( idUserInterface *gui ) {} + + // ---------------------- Public idGameLocal Interface ------------------- + + void Printf( const char *fmt, ... ) const; + void DPrintf( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + void DWarning( const char *fmt, ... ) const; + void Error( const char *fmt, ... ) const; + + // Initializes all map variables common to both save games and spawned games + void LoadMap( const char *mapName, int randseed ); + + void LocalMapRestart( int instance = -1 ); + void MapRestart( int instance = -1 ); + static void VerifyServerSettings_f( const idCmdArgs &args ); + static void MapRestart_f( const idCmdArgs &args ); + bool NextMap( void ); // returns wether serverinfo settings have been modified + static void NextMap_f( const idCmdArgs &args ); + + idMapFile * GetLevelMap( void ); + const char * GetMapName( void ) const; + + int NumAAS( void ) const; + idAAS * GetAAS( int num ) const; + idAAS * GetAAS( const char *name ) const; +// RAVEN BEGIN +// jscott: added accessor for memory tracking + int GetNumAAS( void ) const { return( aasList.Num() ); } +// RAVEN END + void SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ); + aasHandle_t AddAASObstacle( const idBounds &bounds ); + void RemoveAASObstacle( const aasHandle_t handle ); + void RemoveAllAASObstacles( void ); +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. + size_t GetEntityMemoryUsage ( void ) const; +// RAVEN END + bool CheatsOk( bool requirePlayer = true ); + void SetSkill( int value ); + gameState_t GameState( void ) const; + void SetGameState( gameState_t newState ) { gamestate = newState; } + idEntity * SpawnEntityType( const idTypeInfo &classdef, const idDict *args = NULL, bool bIsClientReadSnapshot = false ); + bool SpawnEntityDef( const idDict &args, idEntity **ent = NULL, bool setDefaults = true ); + bool SpawnClientEntityDef( const idDict &args, rvClientEntity **ent = NULL, bool setDefaults = true, const char* spawn = NULL ); +// abahr: + idEntity* SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + template< class type > + type* SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + int GetPreviousTime() const { return previousTime; } +// RAVEN END + int GetSpawnId( const idEntity *ent ) const; + + const idDeclEntityDef * FindEntityDef( const char *name, bool makeDefault = true ) const; + const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + + void RegisterEntity( idEntity *ent ); + void UnregisterEntity( idEntity *ent ); + // used to skip one when registering entities, leaving an empty entity in the array + void SkipEntityIndex( void ); + + bool RequirementMet( idEntity *activator, const idStr &requires, int removeItem ); + +// RITUAL BEGIN +// squirrel: accessor for si_weaponStay checks + bool IsWeaponsStayOn( void ); +// RITUAL END + +// RAVEN BEGIN +// bdube: client entities + void RegisterClientEntity( rvClientEntity *cent ); + void UnregisterClientEntity( rvClientEntity *cent ); +// RAVEN END + + void AlertAI( idEntity *ent ); +// RAVEN BEGIN +// bdube: added get alert actor + idActor * GetAlertActor( void ); + idEntity * GetAlertEntity( void ); +// RAVEN END + + + bool InPlayerPVS( idEntity *ent ) const; + bool InPlayerConnectedArea( idEntity *ent ) const; + + void SetCamera( idCamera *cam ); + idCamera * GetCamera( void ) const; + bool SkipCinematic( void ); + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *GetPortalSky( void ) const; + void SetPortalSky( idCamera *cam ); +// RAVEN END + + void CalcFov( float base_fov, float &fov_x, float &fov_y ) const; + + void AddEntityToHash( const char *name, idEntity *ent ); + bool RemoveEntityFromHash( const char *name, idEntity *ent ); + int GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const; + + // returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. + idEntity * GetTraceEntity( const trace_t &trace ) const; + + static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); + idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; + static void ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +//RAVEN BEGIN +// bgeisler: added, I don't want to have to do this work myself every single time I have an entityNumber + idEntity * FindEntity( int entityNumber ) { return ((entityNumber >= 0 && entityNumber < MAX_GENTITIES) ? entities[entityNumber] : NULL); } +//RAVEN BEGIN + + idEntity * FindEntity( const char *name ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; + int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; + + void KillBox( idEntity *ent, bool catch_teleport = false ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); +// RAVEN BEGIN +// ddynerman: return number of people damaged + void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f, int* hitCount = NULL ); +// bdube: inflictor + void RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ); +// RAVEN END + + void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + void BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dir, float size, const char *material ); +// RAVEN END + + void CallFrameCommand( idEntity *ent, const function_t *frameCommand ); +// RAVEN BEGIN +// bdube: added script object frame commands + void CallFrameCommand( idScriptObject* obj, const function_t* frameCommand ); + void CallFrameCommand( idEntity* ent, const char* frameCommand ); +// RAVEN END + + void CallObjectFrameCommand( idEntity *ent, const char *frameCommand ); + + const idVec3 & GetGravity( void ) const; + + // added the following to assist licensees with merge issues + int GetFrameNum() const { return framenum; } + int GetTime() const { return time; } + int GetMSec() const { return msec; } + int GetMHz() const { return mHz; } + + int GetNextClientNum( int current ) const; + idPlayer * GetClientByNum( int current ) const; + idPlayer * GetClientByName( const char *name ) const; + idPlayer * GetClientByCmdArgs( const idCmdArgs &args ) const; + int GetClientNumByName( const char *name ) const; + + idPlayer * GetLocalPlayer() const; + +// RAVEN BEGIN +// jshepard: update player data after main menu close + void UpdatePlayerPostMainMenu(); + +// bdube: added + int GetSpawnCount ( void ) const; + void SetSpawnCount ( int newSpawnCount ) { spawnCount = newSpawnCount; } +// ddynerman: team type + bool IsTeamGame ( void ) const; +// RAVEN END + + void SpreadLocations(); + idLocationEntity * LocationForPoint( const idVec3 &point ); // May return NULL +// RAVEN BEGIN +// bdube: added + idLocationEntity* AddLocation ( const idVec3& point, const char* name ); +// ddynerman: new gametype specific spawn code + bool SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ); + idEntity* SelectSpawnPoint( idPlayer* player ); + void UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ); + void ClearForwardSpawns( void ); +// RAVEN END + + void SetPortalState( qhandle_t portal, int blockingBits ); + void ServerSendChatMessage( int to, const char *name, const char *text, const char *parm = "" ); + + void SetGlobalMaterial( const idMaterial *mat ); + const idMaterial * GetGlobalMaterial(); + + void SetGibTime( int _time ) { nextGibTime = _time; } + int GetGibTime() { return nextGibTime; } +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + void SetUnFreezeTime( int _time ) { unFreezeTime = _time; }; + int GetUnFreezeTime() { return unFreezeTime; }; + void SetIsFrozen( bool _isFrozen ) { isFrozen = _isFrozen; }; + bool GetIsFrozen() { return isFrozen; }; +// RITUAL END + bool NeedRestart(); + +// RAVEN BEGIN +// jshepard: update end of level on player hud + void UpdateEndLevel(); +// MCG: added whizz-by sound + void CheckPlayerWhizzBy ( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ); +// bdube: added hitscan +// twhitaker: added additionalIgnore parameter + idEntity* HitScan ( const idDict& hitscanDef, const idVec3& origin, const idVec3& dir, const idVec3& fxOrigin, idEntity* owner = NULL, bool noFX = false, float damageScale = 1.0f, idEntity * additionalIgnore = NULL, int *areas = NULL ); +// bdube: added effect calls + virtual rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictedBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictedBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + const idDecl *GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType = NULL ); + + void UpdateRepeaterInfo( bool transmit = false ); + + idList ambientLights; // lights that cast ambient + +// ddynerman: multiple collision world - game collision wrapper functions to +// use the correct idClip +// --------------------------------------------------------------- +// These are wrapper functions around idClip collision detection +// functions. They expose the collision detection engine to the +// game code, but do collision world determination in one spot. +// 'ent' refers to the entity we want collision information about + bool Translation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + bool Rotation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contents ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); + // special case translations versus the rest of the world + bool TracePoint ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ); + bool TraceBounds ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ); + // clip versus a specific model + void TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // clip versus all entities but not the world + void TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + // get a contact feature + bool GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + int PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; + const idBounds & GetWorldBounds( const idEntity* ent ) const; + + void Link( idClipModel* clip, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); + + idClip* GetEntityClipWorld( const idEntity* ent ); + const idClip* GetEntityClipWorld( const idEntity* ent ) const; + int GetNumMapEntities( void ) const; + + int AddClipWorld( int id ); + void RemoveClipWorld( int id ); + int AddInstance( int id = -1, bool deferPopulate = false ); + void RemoveInstance( int id ); + rvInstance* GetInstance( int id ); + int GetNumInstances( void ); +// ddynerman: multiple game instances + void SpawnMapEntities( int instance = 0, unsigned short* entityNumIn = NULL, unsigned short* entityNumOut = NULL, int* startSpawnCount = NULL ); + void InstanceClear( void ); +// ddynerman: utility function + virtual const char* GetLongGametypeName( const char* gametype ); + virtual void ReceiveRemoteConsoleOutput( const char* output ); + + bool IsFlagGameType( void ) { return ( gameType == GAME_CTF || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_ARENA_1F_CTF ); } + bool IsTeamGameType( void ) { return ( gameType == GAME_TDM || gameType == GAME_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ); } + bool IsTeamPowerups( void ); + + // twhitaker: needed this for difficulty settings + float GetDifficultyModifier( void ) { const static float difficulty[] = { -0.3f, 0.0f, 0.4f, 0.8f }; return difficulty[ idMath::ClampInt( 0, 3, g_skill.GetInteger() ) ]; } + + bool IsMultiplayer( void ) { return isMultiplayer; } + +// mekberg: added + bool InCinematic( void ) { return inCinematic; } + + // mekberg: so ban list can be populated outside of multiplayer game + void PopulateBanList( idUserInterface* hud ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ); +#endif +// RAVEN END + + void ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ); + void ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ); + + void SendUnreliableMessage( const idBitMsg &msg, const int clientNum ); + // note: listen server client is always excluded + void SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1 = -1, int area2 = -1 ); + + void RepeaterAppendUnreliableMessage( int icl, const idBitMsg &msg, const idBitMsg *header = NULL ); + void RepeaterUnreliableMessage( const idBitMsg &msg, const int clientNum, const idBitMsg *header = NULL ); + void RepeaterUnreliableMessagePVS( const idBitMsg &msg, const int *areas, int numAreas, const idBitMsg *header = NULL ); + + demoState_t GetDemoState( void ) const { return demoState; } + bool IsServerDemoPlaying( void ) const { return (demoState == DEMO_PLAYING && serverDemo) || serverIsRepeater; } + bool IsServerDemoRecording( void ) const { return demoState == DEMO_RECORDING && serverDemo; } + bool IsRepeaterDemoPlaying( void ) const { return (demoState == DEMO_PLAYING && !serverDemo) && serverIsRepeater; } + bool IsTimeDemo( void ) const { return timeDemo; } + + /* + do not synchronize implicit decls over the network + implicit decls are created when loading sounds and materials that don't have an explicit entry in the def files + clients and server may load those in different orders in a same map, or even join in with different orders because of different map histories before this game + in D3 we maintain remap tables, but it's much better to have tighter multiplayer assets so we have no need at all + still, you want to catch when bad things happen, so indexes should ALL be read and written through these functions + */ + static void WriteDecl( idBitMsg &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsg &msg, declType_t type ); + static void WriteDecl( idBitMsgDelta &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsgDelta &msg, declType_t type ); + + idPlayerStart *RandomSpawn( void ); + + int GetStartingIndexForInstance( int instanceID ); + void ClientSetStartingIndex( int i ) { clientInstanceFirstFreeIndex = i; } + void ServerSetMinSpawnIndex( void ); + void ServerSetEntityIndexWatermark( int instanceID ); + + idLagometer lagometer; +private: +// RAVEN BEGIN +// ddynerman: multiple instance for MP + idList clip; // collision detection + idList instances; +// RAVEN END + + // keep watermarks on the high entity index + // server transmits this to clients so they use the right entity layout + idList instancesEntityIndexWatermarks; + int clientInstanceFirstFreeIndex; + + idStr mapFileName; // name of the map, empty string if no map loaded + idMapFile * mapFile; // will be NULL during the game unless in-game editing is used + bool mapCycleLoaded; + int incompatibleMaps; + + int spawnCount; + bool isMapEntity[ MAX_GENTITIES ]; // it's handy to know which entities are part of the map +// RAVEN BEGIN +// bdube: client entities + int clientSpawnCount; +// RAVEN END + + idLocationEntity ** locationEntities; // for location names, etc + + idCamera * camera; + const idMaterial * globalMaterial; // for overriding everything + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *portalSky; + bool portalSkyVisible; +// RAVEN END + + idList aasList; // area system + idStrList aasNames; + +// RAVEN BEGIN +// bdube: GetAlertActor + idEntityPtr lastAIAlertActor; + int lastAIAlertActorTime; + idEntityPtr lastAIAlertEntity; + int lastAIAlertEntityTime; +// RAVEN END + + idDict spawnArgs; // spawn args used during entity spawning FIXME: shouldn't be necessary anymore +// RAVEN BEGIN +// nmckenzie: + const idDeclEntityDef * spawnOverrides; +// RAVEN END + + pvsHandle_t playerPVS; // merged pvs of all players + bool freePlayerPVS; // tracks if playerPVS needs to be released + pvsHandle_t playerConnectedAreas; // all areas connected to any player area + + idVec3 gravity; // global gravity vector + gameState_t gamestate; // keeps track of whether we're spawning, shutting down, or normal gameplay + bool influenceActive; // true when a phantasm is happening + int nextGibTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int unFreezeTime; // time at which players unfreeze and the match begins + bool isFrozen; // true if the match is frozen (for buying, etc.) +// RITUAL END + + entityState_t * clientEntityStates[MAX_CLIENTS+1][MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int clientPVS[MAX_CLIENTS+1][ENTITY_PVS_SIZE]; + snapshot_t * clientSnapshots[MAX_CLIENTS+1]; + + idList privateViewerIds, nopvsViewerIds; + + entityState_t * (*viewerEntityStates)[MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int (*viewerPVS)[ENTITY_PVS_SIZE]; + snapshot_t * (*viewerSnapshots); + + idMsgQueue (*viewerUnreliableMessages); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc entityStateAllocator; + idBlockAlloc snapshotAllocator; +// RAVEN END + + idEventQueue eventQueue; + + idList spawnSpots; +// RAVEN BEGIN +// ddynerman: two lists to hold team spawn points for team based games + idList teamSpawnSpots[TEAM_MAX]; + idList teamForwardSpawnSpots[TEAM_MAX]; // forward spawn positions, used in CTF +// RAVEN END + + idDict newInfo; + + idStrList shakeSounds; + + idMsgQueue unreliableMessages[ MAX_CLIENTS+1 ]; // MAX_CLIENTS slot for server demo recording + + demoState_t demoState; + bool serverDemo; + bool timeDemo; + bool serverIsRepeater; + + // demo interaction usercmds + usercmd_t usercmd, oldUsercmd; + int followPlayer; // free fly or spectate follow through local interaction during server demo replays + + int demo_protocol; // keep track of the protocol of the demo we're replaying + +private: + + void Clear( void ); + // returns true if the entity shouldn't be spawned at all in this game type or difficulty level + bool InhibitEntitySpawn( idDict &spawnArgs ); + // spawn entities from the map file + // commons used by init, shutdown, and restart + void MapPopulate( int instance = -1 ); + void MapClear( bool clearClients, int instance = -1 ); + + bool SetupPortalSkyPVS( idPlayer *player ); +// RAVEN END + void SetupPlayerPVS( void ); + void FreePlayerPVS( void ); + void UpdateGravity( void ); + void SortActiveEntityList( void ); + void ShowTargets( void ); + void RunDebugInfo( void ); + + void InitScriptForMap( void ); + void InitConsoleCommands( void ); + void ShutdownConsoleCommands( void ); + + void InitAsyncNetwork( void ); + void ShutdownAsyncNetwork( void ); + void InitLocalClient( int clientNum ); + void FreeSnapshotsOlderThanSequence( snapshot_t *&clientSnapshot, int sequence ); + void FreeSnapshotsOlderThanSequence( int clientNum, int sequence ); + bool ApplySnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], int sequence ); + bool ApplySnapshot( int clientNum, int sequence ); + void WriteGameStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadGameStateFromSnapshot( const idBitMsgDelta &msg ); + void NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) id_attribute((format(printf,3,4))); + void ServerProcessEntityNetworkEventQueue( void ); + void ClientProcessEntityNetworkEventQueue( void ); + void ClientShowSnapshot( int clientNum ) const; + void SetGameType( void ); +// RAVEN BEGIN +// ddynerman: gametype specific spawn code + void InitializeSpawns( void ); + idList WeightSpawnSpots( idPlayer* player ); +// RAVEN END + static int sortSpawnPoints( const void *ptr1, const void *ptr2 ); + + void DumpOggSounds( void ); + void GetShakeSounds( const idDict *dict ); + bool ValidateServerSettings( const char *map, const char *gametype ); + + void Tokenize( idStrList &out, const char *in ); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void ShutdownInstances( void ); +// shouchard: ban list support (lives in game_network.cpp) + + void ClientReadUnreliableMessages( const idBitMsg &msg ); + void ProcessUnreliableMessage( const idBitMsg &msg ); + + void UpdateClientsPVS( void ); + + bool IsDemoReplayInAreas( const int areas[2], int numAreas ); + + void ReallocViewers( int newMaxViewers ); + + void BuildModList( void ); + +public: + void LoadBanList(); + void SaveBanList(); + void FlushBanList(); + bool IsPlayerBanned( const char *name ); + bool IsGuidBanned( const char *guid ); + void AddGuidToBanList( const char *guid ); + void RemoveGuidFromBanList( const char *guid ); + virtual void RegisterClientGuid( int clientNum, const char *guid ); + + int GetBanListCount(); + const mpBanInfo_t* GetBanListEntry( int entry ); // returns client name + const char* GetGuidByClientNum( int clientNum ); // returns GUID + int GetClientNumByGuid( const char* ); // returns clientNum + +// mekberg: get and send ban list + void ServerSendBanList( int clientNum ); + +// jscott: made public + pvsHandle_t GetClientPVS( idPlayer *player, pvsType_t type ); + + int GetCurrentDemoProtocol( void ) { return demo_protocol; } + +private: + char clientGuids[ MAX_CLIENTS ][ CLIENT_GUID_LENGTH ]; + idList banList; + bool banListLoaded; + bool banListChanged; +// RAVEN END +}; + +//============================================================================ + +extern idGameLocal gameLocal; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer to prevent it from allocating memory +// before the unified allocator is initialized +extern idAnimManager *animationLib; +// RAVEN END + +//============================================================================ + +ID_INLINE void idGameLocal::WriteDecl( idBitMsg &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsg &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +ID_INLINE void idGameLocal::WriteDecl( idBitMsgDelta &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsgDelta &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +//============================================================================ + +class idGameError : public idException { +public: + idGameError( const char *text ) : idException( text ) {} +}; + +//============================================================================ + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_OPAQUE|CONTENTS_SIGHTCLIP) +#define MASK_SHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL) +#define MASK_SHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY) +#define MASK_LARGESHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_LARGESHOTCLIP) +#define MASK_LARGESHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_LARGESHOTCLIP) +#define MASK_DMGSOLID (CONTENTS_SOLID|CONTENTS_LARGESHOTCLIP) + +// RAVEN BEGIN +// creed: added monster clip +#define MASK_MONSTERCLIP (CONTENTS_SOLID|CONTENTS_MONSTERCLIP) +// RAVEN END + +const float DEFAULT_GRAVITY = 1066.0f; +const float DEFAULT_GRAVITY_MP = 800.0f; + +#define DEFAULT_GRAVITY_STRING "1066" +#define DEFAULT_MP_GRAVITY_STRING "800" +const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); + +const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); + +//============================================================================ + +#include "physics/Force.h" +#include "physics/Force_Constant.h" +#include "physics/Force_Drag.h" +#include "physics/Force_Field.h" +#include "physics/Force_Spring.h" +#include "physics/Physics.h" +#include "physics/Physics_Static.h" +#include "physics/Physics_StaticMulti.h" +#include "physics/Physics_Base.h" +#include "physics/Physics_Actor.h" +#include "physics/Physics_Monster.h" +#include "physics/Physics_Player.h" +#include "physics/Physics_Parametric.h" +#include "physics/Physics_RigidBody.h" +#include "physics/Physics_AF.h" +#include "physics/Physics_Particle.h" +#include "physics/Physics_VehicleMonster.h" + +#include "vehicle/VehicleController.h" + +#include "Entity.h" +#include "Game_Debug.h" +#include "IconManager.h" +#include "GameEdit.h" +#include "AF.h" +#include "IK.h" +#include "AFEntity.h" +#include "Misc.h" +#include "Actor.h" + +// client entities +#include "client/ClientEntity.h" +#include "client/ClientEffect.h" +#include "client/ClientMoveable.h" +#include "client/ClientModel.h" +#include "client/ClientAFEntity.h" + +#include "Weapon.h" + +#include "script/ScriptFuncUtility.h" + +#include "Light.h" +#include "WorldSpawn.h" +#include "Item.h" +#include "PlayerView.h" +// TTimo: moved AI.h up, can't do template instanciation on forward declared-classes +#include "ai/AI.h" +#include "Player.h" +#include "Mover.h" +// RAVEN BEGIN +// abahr: +#include "vehicle/VehicleParts.h" +#include "vehicle/Vehicle.h" +#include "SplineMover.h" +#include "TramGate.h" +#include "vehicle/VehicleDriver.h" +// RAVEN END +#include "Camera.h" +#include "Moveable.h" +#include "Target.h" +#include "Trigger.h" +#include "Sound.h" +#include "SecurityCamera.h" +#include "BrittleFracture.h" + +// RAVEN BEGIN +// nmckenzie: Reduce dependencies. +#include "mp/CTF.h" +#include "mp/stats/StatManager.h" +#include "mp/Tourney.h" +#include "Instance.h" +// RAVEN END +#include "anim/Anim_Testmodel.h" + +// RAVEN BEGIN +// jscott: for lip syncing +#include "LipSync.h" +// RAVEN END + +#include "script/Script_Compiler.h" +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + +ID_INLINE rvClientEffect* idGameLocal::PlayEffect( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( GetEffect( args, effectName ), origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE bool idGameLocal::IsTeamGame( void ) const { + return ( isMultiplayer && ( gameType == GAME_CTF || gameType == GAME_TDM || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ) ); +} + +ID_INLINE int idGameLocal::GetNumMapEntities( void ) const { + if ( mapFile == NULL ) { + return -1; + } else { + return mapFile->GetNumEntities(); + } +} + +ID_INLINE rvInstance* idGameLocal::GetInstance( int id ) { + return instances[ id ]; +} + +ID_INLINE int idGameLocal::GetNumInstances( void ) { + return instances.Num(); +} + +ID_INLINE void idGameLocal::ReceiveRemoteConsoleOutput( const char* output ) { + if( isMultiplayer ) { + mpGame.ReceiveRemoteConsoleOutput( output ); + } +} + +template< class type > +type* idGameLocal::SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idEntity* entity = SpawnEntityDef( entityDefName, additionalArgs ); + if( !entity ) { + return NULL; + } + + if( !entity->IsType(type::GetClassType()) ) { + entity->PostEventMS( &EV_Remove, 0 ); + return NULL; + } + + return static_cast( entity ); +} + +template< class type > +ID_INLINE idEntityPtr::idEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE void idEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void idEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +template< class type > +ID_INLINE idEntityPtr &idEntityPtr::operator=( type *ent ) { + if ( ent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool idEntityPtr::SetSpawnId( int id ) { + if ( ( id >> GENTITYNUM_BITS ) == gameLocal.spawnIds[ id & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool idEntityPtr::IsValid( void ) const { + return ( gameLocal.spawnIds[ spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] == ( spawnId >> GENTITYNUM_BITS ) ); +} + +template< class type > +ID_INLINE type *idEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( spawnId >> GENTITYNUM_BITS ) ) ) { + return static_cast( gameLocal.entities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int idEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ); +} + +// RAVEN BEGIN +// bdube: overloaded operator +template< class type > +ID_INLINE type * idEntityPtr::operator->( void ) const { + return GetEntity ( ); +} + +template< class type > +ID_INLINE idEntityPtr::operator type * ( void ) const { + return GetEntity(); +} +// RAVEN END + +#include "../idlib/containers/ListGame.h" + +#endif /* !__GAME_LOCAL_H__ */ diff --git a/source/mpgame/Game_network.cpp b/source/mpgame/Game_network.cpp new file mode 100644 index 0000000..b016ebd --- /dev/null +++ b/source/mpgame/Game_network.cpp @@ -0,0 +1,3978 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: client entities +#include "client/ClientEffect.h" +// shouchard: ban list support +#define BANLIST_FILENAME "banlist.txt" +// RAVEN END + +/* +=============================================================================== + + Client running game code: + - entity events don't work and should not be issued + - entities should never be spawned outside idGameLocal::ClientReadSnapshot + +=============================================================================== +*/ + +// adds tags to the network protocol to detect when things go bad ( internal consistency ) +// NOTE: this changes the network protocol +#ifndef ASYNC_WRITE_TAGS + #define ASYNC_WRITE_TAGS 1 +#endif + +idCVar net_clientShowSnapshot( "net_clientShowSnapshot", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar net_clientShowSnapshotRadius( "net_clientShowSnapshotRadius", "128", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar net_clientLagOMeter( "net_clientLagOMeter", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "draw prediction graph" ); +idCVar net_clientLagOMeterResolution( "net_clientLagOMeterResolution", "16", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "resolution for predict ahead graph (upper part of lagometer)", 1, 100 ); + +// RAVEN BEGIN +// ddynerman: performance profiling +int net_entsInSnapshot; +int net_snapshotSize; + +extern const int ASYNC_PLAYER_FRAG_BITS; +// RAVEN END + +// message senders +idNullMessageSender nullSender; +idServerReliableMessageSender serverReliableSender; +idRepeaterReliableMessageSender repeaterReliableSender; + +/* +================ +idGameLocal::InitAsyncNetwork +================ +*/ +void idGameLocal::InitAsyncNetwork( void ) { + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + memset( viewerEntityStates, 0, sizeof( *viewerEntityStates ) * maxViewers ); + memset( viewerPVS, 0, sizeof( *viewerPVS ) * maxViewers ); + memset( viewerSnapshots, 0, sizeof( *viewerSnapshots ) * maxViewers ); + memset( viewerUnreliableMessages, 0, sizeof (*viewerUnreliableMessages) * maxViewers ); + + eventQueue.Init(); + + // we used to have some special case -1 value, but we don't anymore so this is always >= 0 now + entityDefBits = idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1; + localClientNum = 0; // on a listen server SetLocalUser will set this right + realClientTime = 0; + isNewFrame = true; + + nextLagoCheck = 0; +} + +/* +================ +idGameLocal::ShutdownAsyncNetwork +================ +*/ +void idGameLocal::ShutdownAsyncNetwork( void ) { + entityStateAllocator.Shutdown(); + snapshotAllocator.Shutdown(); + eventQueue.Shutdown(); + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + memset( viewerEntityStates, 0, sizeof( *viewerEntityStates ) * maxViewers ); + memset( viewerPVS, 0, sizeof( *viewerPVS ) * maxViewers ); + memset( viewerSnapshots, 0, sizeof( *viewerSnapshots ) * maxViewers ); + memset( viewerUnreliableMessages, 0, sizeof (*viewerUnreliableMessages) * maxViewers ); +} + +/* +================ +idGameLocal::InitLocalClient +================ +*/ +void idGameLocal::InitLocalClient( int clientNum ) { + isServer = false; + isClient = true; + localClientNum = clientNum; + + if ( clientNum == MAX_CLIENTS && !entities[ ENTITYNUM_NONE ] ) { + SpawnPlayer( ENTITYNUM_NONE ); + idPlayer *player = gameLocal.GetLocalPlayer(); + assert( player ); + player->SpawnFromSpawnSpot(); + } +} + +/* +================ +idGameLocal::ServerAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + +// RAVEN BEGIN +// shouchard: ban support + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } +// RAVEN END + + if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107140" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + // check private clients + if( serverInfo.GetInt( "si_privatePlayers" ) > 0 ) { + // just in case somehow we have a stale private clientId that matches a new client + mpGame.RemovePrivatePlayer( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privatePassword" ); + if( privatePass[ 0 ] == '\0' ) { + common->Warning( "idGameLocal::ServerAllowClient() - si_privatePlayers > 0 with no g_privatePassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_privatePlayers is set but g_privatePassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + + int numPrivateClients = cvarSystem->GetCVarInteger( "si_numPrivatePlayers" ); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, serverInfo.GetInt( "si_privatePlayers" ), numPrivateClients ); + + if ( !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + mpGame.AddPrivatePlayer( clientId ); + } else if( (numClients - numPrivateClients) >= (serverInfo.GetInt( "si_maxPlayers" ) - serverInfo.GetInt( "si_privatePlayers" )) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + } + + + if ( !cvarSystem->GetCVarBool( "si_usePass" ) ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_password" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "si_usePass is set but g_password is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_usePass is set but g_password is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting client %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ReallocViewers +Allocate/Reallocate space for viewers +================ +*/ +template static ID_INLINE void ReallocateZero( T *&var, int oldSize, int newSize ) { + T *new_var = newSize ? new T[ newSize ] : NULL; + SIMDProcessor->Memcpy( new_var, var, sizeof(*new_var) * Min( newSize, oldSize ) ); + delete[] var; + var = new_var; + if ( newSize > oldSize ) { + SIMDProcessor->Memset( var + oldSize, 0, sizeof(*var) * (newSize - oldSize) ); + } +} + +static ID_INLINE void ReallocateZeroClearInfo( viewer_t *&var, int oldSize, int newSize ) { + int minBound = Min( newSize, oldSize ), i; + viewer_t *new_var = newSize ? new viewer_t[ newSize ] : NULL; + for (i = 0; i < minBound; i++ ) { + new_var[ i ] = var[ i ]; + } + for (i = 0; i < oldSize; i++ ) { + var[ i ].info.Clear(); + } + delete[] var; + var = new_var; + for (i = oldSize; i < newSize; i++) { + var[i].connected = var[i].active = var[i].priv = var[i].nopvs = false; + var[i].pvsArea = -1; + } +} + +void idGameLocal::ReallocViewers( int newMaxViewers ) { + int i; + + if ( newMaxViewers == maxViewers ) { + return; + } + + if ( newMaxViewers > 0 ) { // zinx: during shutdown we clear everything, but there are still connected viewers. + for ( i = newMaxViewers; i < maxViewers; i++ ) { + if ( viewers[ i ].connected ) { + // this should never happen + Error( "Too many active viewers for ri_maxViewers change" ); + } + } + assert( maxViewer <= newMaxViewers ); + } else { + maxViewer = 0; + } + + ReallocateZeroClearInfo( viewers, maxViewers, newMaxViewers ); + ReallocateZero( viewerEntityStates, maxViewers, newMaxViewers ); + ReallocateZero( viewerPVS, maxViewers, newMaxViewers ); + ReallocateZero( viewerSnapshots, maxViewers, newMaxViewers ); + ReallocateZero( viewerUnreliableMessages, maxViewers, newMaxViewers ); + + maxViewers = newMaxViewers; + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::RepeaterAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } + + if ( serverInfo.GetInt( "si_pure" ) != 0 && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !cvarSystem->GetCVarInteger( "ri_maxViewers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_123000" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= cvarSystem->GetCVarInteger( "ri_maxViewers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_123000" ); + return ALLOW_NOTYET; + } + + // check private clients + // just in case somehow we have a stale private clientId that matches a new client + privateViewerIds.Remove( clientId ); + nopvsViewerIds.Remove( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privateViewerPassword" ); + if ( privatePass[ 0 ] == '\0' && ri_privateViewers.GetInteger() > 0 ) { + common->Warning( "idGameLocal::RepeaterAllowClient() - ri_privateViewers > 0 with no g_privateViewerPassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say ri_privateViewers is set but g_privateViewerPassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + int numPrivateClients = privateViewerIds.Num(); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + if ( repeater ) { + const char *nopvsPass = cvarSystem->GetCVarString( "g_repeaterPassword" ); + + if ( nopvsPass[ 0 ] != '\0' ) { + if ( idStr::Cmp( nopvsPass, privatePassword ) ) { + // password is set, and they don't match. + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting viewer %s from IP %s: invalid password for repeater\n", guid, IP ); + return ALLOW_BADPASS; + } + + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } else { + if ( privatePass[ 0 ] != '\0' && !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } + } + + // once this client spawns in, they'll be marked as a broadcaster + nopvsViewerIds.Append( clientId ); + } else if ( privatePass[ 0 ] != '\0' && !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } else if( (numClients - numPrivateClients) >= ( cvarSystem->GetCVarInteger( "ri_maxViewers" ) - ri_privateViewers.GetInteger() ) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + if ( !ri_useViewerPass.GetBool() ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_viewerPassword" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "ri_useViewerPass is set but g_viewerPassword is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say ri_useViewerPass is set but g_viewerPassword is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting viewer %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ServerClientConnect +================ +*/ +void idGameLocal::ServerClientConnect( int clientNum, const char *guid ) { + // make sure no parasite entity is left + if ( entities[ clientNum ] ) { + common->DPrintf( "ServerClientConnect: remove old player entity\n" ); + delete entities[ clientNum ]; + } + unreliableMessages[ clientNum ].Init( 0 ); + userInfo[ clientNum ].Clear(); + mpGame.ServerClientConnect( clientNum ); + Printf( "client %d connected.\n", clientNum ); +} + +/* +================ +idGameLocal::RepeaterClientConnect +================ +*/ +void idGameLocal::RepeaterClientConnect( int clientNum ) { + // ensure that we have enough slots for this client + assert( cvarSystem->GetCVarInteger( "ri_maxViewers" ) > clientNum ); + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + viewers[ clientNum ].connected = true; + viewers[ clientNum ].active = false; + + if ( maxViewer <= clientNum ) { + maxViewer = clientNum + 1; + } + + // mark them as private/nopvs + for ( int i = 0; i < privateViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( privateViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 || (num == clientNum && viewers[ clientNum ].priv) ) { + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + viewers[ clientNum ].priv = true; + continue; + } + } + + for ( int i = 0; i < nopvsViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( nopvsViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 || (num == clientNum && viewers[ clientNum ].nopvs) ) { + nopvsViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + viewers[ clientNum ].nopvs = true; + continue; + } + } + + Printf( "viewer %d connected%s.\n", clientNum, viewers[ clientNum ].nopvs ? " (repeater)" : "" ); + + // count up viewer slots so players know when servers are full + int numClients = 0; + for ( int i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].connected ) { + continue; + } + + ++numClients; + } + + int numPrivateClients = privateViewerIds.Num(); + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + ri_numViewers.SetInteger( numClients ); + ri_numPrivateViewers.SetInteger( numPrivateClients ); + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::ServerClientBegin +================ +*/ +void idGameLocal::ServerClientBegin( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn the player + SpawnPlayer( clientNum ); + if ( clientNum == localClientNum ) { + mpGame.EnterGame( clientNum ); + } + + // ddynerman: connect time + ((idPlayer*)entities[ clientNum ])->SetConnectTime( time ); + + // send message to spawn the player at the clients + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteLong( spawnIds[ clientNum ] ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + if( gameType != GAME_TOURNEY ) { + ((idPlayer*)entities[ clientNum ])->JoinInstance( 0 ); + } else { + // instance 0 might be empty in Tourney + ((idPlayer*)entities[ clientNum ])->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } +//RAVEN BEGIN +//asalmon: This client has finish loading and will be spawned mark them as ready. +#ifdef _XENON + Live()->ClientReady(clientNum); +#endif +//RAVEN END +} + +/* +================ +idGameLocal::RepeaterClientBegin +================ +*/ +void idGameLocal::RepeaterClientBegin( int clientNum ) { + viewers[ clientNum ].active = true; +} + +/* +================ +idGameLocal::ServerClientDisconnect +clientNum == MAX_CLIENTS for cleanup of server demo recording data +================ +*/ +void idGameLocal::ServerClientDisconnect( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( clientNum < MAX_CLIENTS ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT ); + outMsg.WriteBits( ( spawnIds[ clientNum ] << GENTITYNUM_BITS ) | clientNum, 32 ); // see GetSpawnId + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( clientNum, 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( clientEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( clientEntityStates[ clientNum ][ i ] ); + clientEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( clientPVS[ clientNum ], 0, sizeof( clientPVS[ clientNum ] ) ); + + if ( clientNum == MAX_CLIENTS ) { + return; + } + + // only drop MP clients if we're in multiplayer and the server isn't going down + if ( gameLocal.isMultiplayer && !(gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) ) { + // idMultiplayerGame::DisconnectClient will do the delete in MP + mpGame.DisconnectClient( clientNum ); + } else { + // delete the player entity + delete entities[ clientNum ]; + } + + +} + +/* +================ +idGameLocal::RepeaterClientDisconnect +================ +*/ +void idGameLocal::RepeaterClientDisconnect( int clientNum ) { + int i; + + if ( clientNum >= maxViewers ) { + // this can happen during shutdown, because repeater clients are + // discarded when the (shared) entity states/snapshots of the other + // clients are freed + return; + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( viewerSnapshots[ clientNum ], 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( viewerEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( viewerEntityStates[ clientNum ][ i ] ); + viewerEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( viewerPVS[ clientNum ], 0, sizeof( viewerPVS[ clientNum ] ) ); + + viewerUnreliableMessages[ clientNum ].Init( 0 ); + + viewers[ clientNum ].connected = false; + viewers[ clientNum ].active = false; + viewers[ clientNum ].priv = false; + viewers[ clientNum ].nopvs = false; + + if ( maxViewer <= (clientNum+1) ) { + // find the new highest client + for ( maxViewer = clientNum; maxViewer >= 0 && !viewers[ maxViewer ].connected; maxViewer-- ) ; + maxViewer++; + } + + // remove them from private/nopvs lists + for ( int i = 0; i < privateViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( privateViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 ) { + nopvsViewerIds.Remove( privateViewerIds[ i ] ); + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + nopvsViewerIds.Remove( privateViewerIds[ i ] ); + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + } + + // count up viewer slots so players know when servers are full + int numClients = 0; + for ( int i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].connected ) { + continue; + } + + ++numClients; + } + + int numPrivateClients = privateViewerIds.Num(); + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + ri_numViewers.SetInteger( numClients ); + ri_numPrivateViewers.SetInteger( numPrivateClients ); + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::ServerWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::ServerWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL || i == clientNum ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg, true ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->ServerSendReliableMessage( clientNum, outMsg, true ); + + mpGame.ServerWriteInitialReliableMessages( serverReliableSender.To( clientNum, true ), clientNum ); +} + +/* +================ +idGameLocal::RepeaterWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::RepeaterWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->RepeaterSendReliableMessage( clientNum, outMsg ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->RepeaterSendReliableMessage( clientNum, outMsg ); + + mpGame.ServerWriteInitialReliableMessages( repeaterReliableSender.To( clientNum ), ENTITYNUM_NONE ); +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( snapshot_t *&clientSnapshot, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + for ( lastSnapshot = NULL, snapshot = clientSnapshot; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence < sequence ) { + for ( state = snapshot->firstEntityState; state; state = snapshot->firstEntityState ) { + snapshot->firstEntityState = snapshot->firstEntityState->next; + entityStateAllocator.Free( state ); + } + if ( lastSnapshot ) { + lastSnapshot->next = snapshot->next; + } else { + clientSnapshot = snapshot->next; + } + snapshotAllocator.Free( snapshot ); + } else { + lastSnapshot = snapshot; + } + } +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( int clientNum, int sequence ) { + FreeSnapshotsOlderThanSequence( clientSnapshots[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + FreeSnapshotsOlderThanSequence( clientSnapshot, sequence ); + + for ( lastSnapshot = NULL, snapshot = clientSnapshot; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence == sequence ) { + for ( state = snapshot->firstEntityState; state; state = state->next ) { + if ( entityStates[state->entityNumber] ) { + entityStateAllocator.Free( entityStates[state->entityNumber] ); + } + entityStates[state->entityNumber] = state; + } + // ~512 bytes + memcpy( PVS, snapshot->pvs, sizeof( snapshot->pvs ) ); + if ( lastSnapshot ) { + lastSnapshot->next = nextSnapshot; + } else { + clientSnapshot = nextSnapshot; + } + snapshotAllocator.Free( snapshot ); + return true; + } else { + lastSnapshot = snapshot; + } + } + + return false; +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientSnapshots[ clientNum ], clientEntityStates[ clientNum ], clientPVS[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::WriteGameStateToSnapshot +================ +*/ +void idGameLocal::WriteGameStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + msg.WriteFloat( globalShaderParms[i] ); + } + + mpGame.WriteToSnapshot( msg ); +} + +/* +================ +idGameLocal::ReadGameStateFromSnapshot +================ +*/ +void idGameLocal::ReadGameStateFromSnapshot( const idBitMsgDelta &msg ) { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + globalShaderParms[i] = msg.ReadFloat(); + } + + mpGame.ReadFromSnapshot( msg ); +} + +/* +================ +idGameLocal::WriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +void idGameLocal::WriteSnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], idMsgQueue &unreliable, int sequence, idBitMsg &msg, int transmitEntity, int transmitEntity2, int instance, bool doPVS, const idBounds &pvs_bounds, int lastSnapshotFrame ) { + int i, msgSize, msgWriteBit; + idEntity *ent; + pvsHandle_t pvsHandle = { 0, 0 }; // shut warning + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + // free too old snapshots + // ( that's a security, normal acking from server keeps a smaller backlog of snaps ) + FreeSnapshotsOlderThanSequence( clientSnapshot, sequence - 64 ); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshot; + clientSnapshot = snapshot; + memset( snapshot->pvs, 0, sizeof( snapshot->pvs ) ); + + if ( doPVS ) { + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( pvs_bounds, sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( random.RandomInt() ); + msg.WriteLong( tagRandom.GetSeed() ); +#endif + + // write unreliable messages + unreliable.WriteTo( msg ); // NOTE: No flush + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + + // create the snapshot + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // local fake client + if ( ent->entityNumber == ENTITYNUM_NONE ) { + continue; + } + + // if the entity is not in the player PVS + if ( doPVS && !ent->PhysicsTeamInPVS( pvsHandle ) && ent->entityNumber != transmitEntity && ent->entityNumber != transmitEntity2 ) { + continue; + } + + if ( ent->GetInstance() != instance ) { + continue; + } + + // if the entity is a map entity, mark it in PVS + if ( isMapEntity[ ent->entityNumber ] ) { + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + } + + // if that entity is not marked for network synchronization + if ( !ent->fl.networkSync ) { + continue; + } + + // add the entity to the snapshot PVS + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + + // save the write state to which we can revert when the entity didn't change at all + msg.SaveWriteState( msgSize, msgWriteBit ); + + // write the entity to the snapshot + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = entityStates[ent->entityNumber]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + // write the class specific data to the snapshot + ent->WriteToSnapshot( deltaMsg ); + + // if this is a player and a transmit of the player state has been requested, write the player state + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( transmitEntity < 0 || transmitEntity == ent->entityNumber || transmitEntity2 == ent->entityNumber ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + deltaMsg.WriteBits( 1, 1 ); + static_cast< idPlayer * >( ent )->WritePlayerStateToSnapshot( lastSnapshotFrame, deltaMsg ); + } else { + deltaMsg.WriteBits( 0, 1 ); + } + } + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + } + } + + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write the PVS to the snapshot +#if ASYNC_WRITE_PVS + if ( doPVS ) { + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + if ( i < numSourceAreas ) { + msg.WriteLong( sourceAreas[ i ] ); + } else { + msg.WriteLong( 0 ); + } + } + gameLocal.pvs.WritePVS( pvsHandle, msg ); + } +#endif + // always write the entity pvs, even when doPVS isn't set + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + msg.WriteDeltaLong( PVS[i], snapshot->pvs[i] ); + } + + if ( doPVS ) { + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + } + + // write the game state to the snapshot + base = entityStates[ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + WriteGameStateToSnapshot( deltaMsg ); +} + +/* +================ +idGameLocal::ServerWriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) { + + int i; + idPlayer *player, *spectated = NULL; + + player = static_cast( entities[ clientNum ] ); + assert( player ); + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + WriteSnapshot( + clientSnapshots[ clientNum ], + clientEntityStates[ clientNum ], + clientPVS[ clientNum ], + unreliableMessages[ clientNum ], + sequence, + msg, + spectated->entityNumber, + clientNum, + player->GetInstance(), + true, + spectated->GetPlayerPhysics()->GetAbsBounds(), + lastSnapshotFrame + ); + + unreliableMessages[ clientNum ].Init( 0 ); // Flush + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = clientSnapshots[ clientNum ]->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::ServerWriteServerDemoSnapshot +=============== +*/ +void idGameLocal::ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) { + bool ret = ApplySnapshot( clientSnapshots[ MAX_CLIENTS ], clientEntityStates[ MAX_CLIENTS ], clientPVS[ MAX_CLIENTS ], sequence - 1 ); + ret = ret; // shut the warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + idBounds dummy_bounds; + + WriteSnapshot( + clientSnapshots[ MAX_CLIENTS ], + clientEntityStates[ MAX_CLIENTS ], + clientPVS[ MAX_CLIENTS ], + unreliableMessages[ MAX_CLIENTS ], + sequence, + msg, + -1, + -1, + 0, + false, + dummy_bounds, + lastSnapshotFrame + ); + + unreliableMessages[ MAX_CLIENTS ].Init( 0 ); // Flush +} + +/* +=============== +idGameLocal::RepeaterWriteSnapshot +=============== +*/ +void idGameLocal::RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) { + int i; + idBounds bounds; + + // if the client is spectating a valid player + if ( pvs_origin.followClient >= 0 && pvs_origin.followClient < MAX_CLIENTS && entities[ pvs_origin.followClient ] && entities[ pvs_origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer *spectated = static_cast< idPlayer * >( entities[ pvs_origin.followClient ] ); + bounds = spectated->GetPlayerPhysics()->GetAbsBounds(); + } else { + // zinx - FIXME - bounds, not point + bounds = idBounds( pvs_origin.origin ); + } + + WriteSnapshot( + viewerSnapshots[ clientNum ], + viewerEntityStates[ clientNum ], + viewerPVS[ clientNum ], + viewerUnreliableMessages[ clientNum ], + sequence, + msg, + viewers[ clientNum ].nopvs ? -1 : (pvs_origin.followClient >= 0 ? pvs_origin.followClient : ENTITYNUM_NONE ), + ENTITYNUM_NONE, + 0, + viewers[ clientNum ].nopvs ? false : true, + bounds, + lastSnapshotFrame + ); + + viewerUnreliableMessages[ clientNum ].Init( 0 ); // Flush + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = viewerSnapshots[ clientNum ]->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::RepeaterEndSnapshots +=============== +*/ +void idGameLocal::RepeaterEndSnapshots( void ) { +} + +/* +================ +idGameLocal::ServerApplySnapshot +================ +*/ +bool idGameLocal::ServerApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::RepeaterApplySnapshot +================ +*/ +bool idGameLocal::RepeaterApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( viewerSnapshots[ clientNum ], viewerEntityStates[ clientNum ], viewerPVS[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::NetworkEventWarning +================ +*/ +void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { + char buf[1024]; + int length = 0; + va_list argptr; + + int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + int id = event->spawnId >> GENTITYNUM_BITS; + + length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); + va_end( argptr ); + idStr::Append( buf, sizeof(buf), "\n" ); + + common->DWarning( buf ); +} + +/* +================ +idGameLocal::ServerProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ServerProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if ( !entPtr.SetSpawnId( event->spawnId ) ) { + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ServerSendChatMessage +================ +*/ +void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text, const char *parm ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( parm ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + + if ( to == -1 || to == localClientNum ) { + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s", name, temp.c_str() ); + } +} + +/* +================ +idGameLocal::ServerProcessReliableMessage +================ +*/ +void idGameLocal::ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + // This parameter is ignored - it is only used when going to client from server + msg.ReadString( parm, sizeof( parm ) ); + + mpGame.ProcessChatMessage( clientNum, id == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + int index = msg.ReadLong(); + bool team = msg.ReadBits( 1 ) != 0; + mpGame.ProcessVoiceChat( clientNum, team, index ); + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + mpGame.WantKilled( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + mpGame.DropWeapon( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + mpGame.ServerCallVote( clientNum, msg ); + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + bool vote = ( msg.ReadByte() != 0 ); + mpGame.CastVote( clientNum, vote ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + mpGame.ServerCallPackedVote( clientNum, msg ); + break; + } +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + mpGame.ReceiveAndForwardVoiceData( clientNum, msg, id - GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT ); + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + int client = msg.ReadByte(); + statManager->SendStat( clientNum, client ); + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + int clientDest = msg.ReadByte(); + bool mute = ( 0 != msg.ReadByte() ); + mpGame.ServerHandleVoiceMuting( clientNum, clientDest, mute ); + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + int commandType = msg.ReadByte(); + int clientNum = msg.ReadByte(); + if ( SERVER_ADMIN_REMOVE_BAN == commandType ) { + mpGame.HandleServerAdminRemoveBan( "" ); + } else if ( SERVER_ADMIN_KICK == commandType ) { + mpGame.HandleServerAdminKickPlayer( clientNum ); + } else if ( SERVER_ADMIN_FORCE_SWITCH == commandType ) { + mpGame.HandleServerAdminForceTeamSwitch( clientNum ); + } else { + Warning( "Server admin packet with bad type %d", commandType ); + } + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + ServerSendBanList( clientNum ); + break; + } +// RAVEN END + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + mpGame.SendMapList( clientNum ); + break; + } + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::RepeaterProcessReliableMessage +================ +*/ +void idGameLocal::RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + if ( !g_noTVChat.GetBool() ) + { + char name[128], text[128], parm[128]; + idStr suffixed_name, mangled_text; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + + suffixed_name = va( "^0%s^c777 (viewer)", name ); + mangled_text = va( "^c777%s", text ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( suffixed_name.c_str() ); + outMsg.WriteString( mangled_text.c_str() ); + outMsg.WriteString( "" ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + break; + } +// RAVEN BEGIN +// shouchard: multivalue votes + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + break; + } +// RAVEN END +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + break; + } +// RAVEN END + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + break; + } + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::ClientShowSnapshot +================ +*/ +void idGameLocal::ClientShowSnapshot( int clientNum ) const { + int baseBits; + idEntity *ent; + idPlayer *player; + idMat3 viewAxis; + idBounds viewBounds; + entityState_t *base; + + if ( !net_clientShowSnapshot.GetInteger() ) { + return; + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + viewAxis = player->viewAngles.ToMat3(); + viewBounds = player->GetPhysics()->GetAbsBounds().Expand( net_clientShowSnapshotRadius.GetFloat() ); + + for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + + if ( net_clientShowSnapshot.GetInteger() == 1 && ent->snapshotBits == 0 ) { + continue; + } + + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( !entBounds.IntersectsBounds( viewBounds ) ) { + continue; + } + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + baseBits = base->state.GetNumBitsWritten(); + } else { + baseBits = 0; + } + + if ( net_clientShowSnapshot.GetInteger() == 2 && baseBits == 0 ) { + continue; + } + + gameRenderWorld->DebugBounds( colorGreen, entBounds ); + gameRenderWorld->DrawText( va( "%d: %s (%d,%d bytes of %d,%d)\n", ent->entityNumber, + ent->name.c_str(), ent->snapshotBits >> 3, ent->snapshotBits & 7, baseBits >> 3, baseBits & 7 ), + entBounds.GetCenter(), 0.1f, colorWhite, viewAxis, 1 ); + } +} + + +/* +================ +idGameLocal::ReadSnapshot +================ +*/ +void idGameLocal::ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) { + int i, entityDefNumber, numBitsRead; + idEntity *ent; + idPlayer *player, *spectated; + pvsHandle_t pvsHandle; + idDict args; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int spawnId; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + const idDeclEntityDef *decl; + + if ( net_clientLagOMeter.GetBool() && renderSystem && !(GetDemoState() == DEMO_PLAYING && ( IsServerDemoPlaying() || IsTimeDemo() )) ) { + lagometer.Update( aheadOfServer, dupeUsercmds ); + } + + InitLocalClient( clientNum ); + + gameRenderWorld->DebugClear( time ); + + // update the game time + framenum = gameFrame; + time = gameTime; + previousTime = time - GetMSec(); + + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = snapshotSequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( msg.ReadLong() ); +#endif + + ClientReadUnreliableMessages( msg ); + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + Error( "error after read unreliable" ); + } +#endif + + // read all entities from the snapshot + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + base = clientEntityStates[clientNum][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SPAWN_PLAYER should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + assert( entityDefNumber >= 0 ); + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // read the player state from player entities + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( deltaMsg.ReadBits( 1 ) == 1 ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + static_cast< idPlayer * >( ent )->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" ); + assert( entityDefNumber >= 0 ); + assert( entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ); + const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); + Error( "write to and read from snapshot out of sync for classname '%s'\n", classname ); + } +#endif + } + + if ( clientNum < MAX_CLIENTS ) { + player = static_cast( entities[ clientNum ] ); + } else { + player = gameLocal.GetLocalPlayer(); + } + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + if ( spectated ) { + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } else { + gameLocal.Printf( "Client hasn't spawned self yet.\n" ); + numSourceAreas = gameRenderWorld->BoundsInAreas( idBounds( vec3_origin ), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } + + // read the PVS from the snapshot +#if ASYNC_WRITE_PVS +// zinx - FIXME - this isn't written for server demos or for non-PVS'd repeaters, so we shouldn't read it. + int serverPVS[idEntity::MAX_PVS_AREAS]; + i = numSourceAreas; + while ( i < idEntity::MAX_PVS_AREAS ) { + sourceAreas[ i++ ] = 0; + } + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + serverPVS[ i ] = msg.ReadLong(); + } + if ( memcmp( sourceAreas, serverPVS, idEntity::MAX_PVS_AREAS * sizeof( int ) ) ) { + common->Warning( "client PVS areas != server PVS areas, sequence 0x%x", snapshotSequence ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", sourceAreas[ i ] ); + } + common->DPrintf( "\n" ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", serverPVS[ i ] ); + } + common->DPrintf( "\n" ); + } + gameLocal.pvs.ReadPVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + snapshot->pvs[i] = msg.ReadDeltaLong( clientPVS[clientNum][i] ); + } +// RAVEN BEGIN +// ddynerman: performance profiling + net_entsInSnapshot += snapshotEntities.Num(); + net_snapshotSize += msg.GetSize(); +// RAVEN END + // add entities in the PVS that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // local fake client + if ( ent->entityNumber == ENTITYNUM_NONE ) { + continue; + } + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == snapshotSequence ) { + continue; + } + + // if the entity is not in the snapshot PVS + if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) { + + if ( !ent->fl.networkSync ) { + // don't do stale / unstale on entities that are not marked network sync + continue; + } + + if ( ent->PhysicsTeamInPVS( pvsHandle ) ) { + if ( ent->entityNumber >= MAX_CLIENTS && isMapEntity[ ent->entityNumber ] ) { + // server says it's not in PVS, client says it's in PVS + // if that happens on map entities, most likely something is wrong + // I can see that moving pieces along several PVS could be a legit situation though + // this is a band aid, which means something is not done right elsewhere + if ( net_warnStale.GetInteger() > 1 || ( net_warnStale.GetInteger() == 1 && !ent->fl.networkStale ) ) { + common->Warning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), snapshotSequence ); + } + } +// RAVEN BEGIN +// bdube: hide while not in snapshot + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + + } else { + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + } +// RAVEN END + + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + ent->snapshotBits = 0; + +// RAVEN BEGIN +// bdube: hide while not in snapshot + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + if ( !ent->fl.networkSync ) { + // this is not supposed to happen + // it did however, when restarting a map with a different inhibit of entities caused entity numbers to be laid differently + // an idLight would occupy the entity number of an idItem for instance, and although it's not network-synced ( static level light ), + // the presence of a base would cause the system to think that it is and corrupt things + // we changed the map population so the entity numbers are kept the same no matter how things are inhibited + // this code is left as a fall-through fixup / sanity type of thing + // if this still happens, it's likely "client thinks map entity is stale" is happening as well, and we're still at risk of corruption + Warning( "ClientReadSnapshot: entity %d of type %s is not networkSync and has a snapshot base", ent->entityNumber, ent->GetType()->classname ); + entityStateAllocator.Free( clientEntityStates[clientNum][ent->entityNumber] ); + clientEntityStates[clientNum][ent->entityNumber] = NULL; + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( int i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // read the game state from the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + ReadGameStateFromSnapshot( deltaMsg ); + + // visualize the snapshot + if ( clientNum < MAX_CLIENTS ) { + ClientShowSnapshot( clientNum ); + } else { + // FIXME + } + + // process entity events + ClientProcessEntityNetworkEventQueue(); + +} + +/* +=============== +idGameLocal::ClientReadServerDemoSnapshot +=============== +*/ +void idGameLocal::ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) { + bool ret = ClientApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // shut the warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + ClientReadSnapshot( MAX_CLIENTS, sequence, gameFrame, gameTime, 0, 0, msg ); +} + +/* +=============== +idGameLocal::ClientReadRepeaterSnapshot +=============== +*/ +void idGameLocal::ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) { + ClientReadSnapshot( MAX_CLIENTS, sequence, gameFrame, gameTime, 0, aheadOfServer, msg ); +} + +/* +================ +idGameLocal::ClientApplySnapshot +================ +*/ +bool idGameLocal::ClientApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::ClientProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ClientProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + // only process forward, in order + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if ( !entPtr.SetSpawnId( event->spawnId ) ) { + if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + // if new entity exists in this position, silently ignore + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +// RAVEN BEGIN +// bdube: client side hitscan + +/* +================ +idGameLocal::ClientHitScan +================ +*/ +void idGameLocal::ClientHitScan( const idBitMsg &msg ) { + int hitscanDefIndex; + idVec3 muzzleOrigin; + idVec3 dir; + idVec3 fxOrigin; + const idDeclEntityDef *decl; + int num_hitscans; + int i; + idEntity *owner; + + assert( isClient ); + + hitscanDefIndex = msg.ReadLong(); + decl = static_cast< const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, hitscanDefIndex ) ); + if ( !decl ) { + common->Warning( "idGameLocal::ClientHitScan: entity def index %d not found\n", hitscanDefIndex ); + return; + } + num_hitscans = decl->dict.GetInt( "hitscans", "1" ); + + owner = entities[ msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ) ]; + + muzzleOrigin[0] = msg.ReadFloat(); + muzzleOrigin[1] = msg.ReadFloat(); + muzzleOrigin[2] = msg.ReadFloat(); + fxOrigin[0] = msg.ReadFloat(); + fxOrigin[1] = msg.ReadFloat(); + fxOrigin[2] = msg.ReadFloat(); + + // one direction sent per hitscan + for( i = 0; i < num_hitscans; i++ ) { + dir = msg.ReadDir( 24 ); + gameLocal.HitScan( decl->dict, muzzleOrigin, dir, fxOrigin, owner ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientProcessReliableMessage +================ +*/ +void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + idDict backupSI; + int toClient = -1, excludeClient = -1; + bool noEffect = false; + + InitLocalClient( clientNum ); + + if ( IsServerDemoPlaying() ) { + int record_type = msg.ReadByte(); + assert( record_type < DEMO_RECORD_COUNT ); + // if you need to do some special filtering: + switch ( record_type ) { + case DEMO_RECORD_CLIENTNUM: { + toClient = msg.ReadChar(); + if ( toClient >= 0 && toClient < MAX_CLIENTS ) { + // reliable was targetted + if ( toClient != followPlayer ) { + // we're free flying or following someone else + if ( !isRepeater ) { + return; + } else { + noEffect = true; + } + } + } else { + toClient = -1; + } + break; + } + case DEMO_RECORD_EXCLUDE: { + excludeClient = msg.ReadChar(); // may be -1 + if ( excludeClient == followPlayer ) { + if ( !isRepeater ) { + return; + } else { + noEffect = true; + } + } + toClient = -1; + break; + } + } + } + + id = msg.ReadByte(); + + if ( isRepeater ) { + bool inhibitRepeater = false; + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE: { + inhibitRepeater = true; + break; + } + + default: + break; + } + + if ( !inhibitRepeater ) { + // pass it along to the connected viewers + if ( excludeClient == -1 ) { + networkSystem->RepeaterSendReliableMessage( -1, msg, IsServerDemoPlaying(), toClient ); + } else { + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg, IsServerDemoPlaying() ); + } + } + + if ( noEffect ) { + return; + } + } + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + int client = msg.ReadByte(); + int spawnId = msg.ReadLong(); + if ( !entities[ client ] ) { + SpawnPlayer( client ); + entities[ client ]->FreeModelDef(); + } + // fix up the spawnId to match what the server says + // otherwise there is going to be a bogus delete/new of the client entity in the first ClientReadFromSnapshot + spawnIds[ client ] = spawnId; + break; + } + case GAME_RELIABLE_MESSAGE_DELETE_ENT: { + int spawnId = msg.ReadBits( 32 ); + idEntityPtr< idEntity > entPtr; + if( !entPtr.SetSpawnId( spawnId ) ) { + break; + } + if( entPtr.GetEntity() && entPtr.GetEntity()->entityNumber < MAX_CLIENTS ) { + delete entPtr.GetEntity(); + gameLocal.mpGame.UpdatePlayerRanks(); + } else { + delete entPtr.GetEntity(); + } + break; + } + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + idStr temp = va( "%s^0: %s%s\n", name, common->GetLocalizedString( text ), parm ); + mpGame.PrintChatLine( temp.c_str(), ( id == GAME_RELIABLE_MESSAGE_TCHAT && gameLocal.IsTeamGame() ) ); + break; + } + case GAME_RELIABLE_MESSAGE_DB: { + msg_evt_t msg_evt = (msg_evt_t)msg.ReadByte(); + int parm1, parm2; + parm1 = msg.ReadByte( ); + parm2 = msg.ReadByte( ); + mpGame.PrintMessageEvent( -1, msg_evt, parm1, parm2 ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_SERVERINFO: { + idDict info; + msg.ReadDeltaDict( info, NULL ); + SetServerInfo( info ); + break; + } + case GAME_RELIABLE_MESSAGE_RESTART: { + MapRestart(); + break; + } + case GAME_RELIABLE_MESSAGE_STARTVOTE: { + char voteString[ MAX_STRING_CHARS ]; + int clientNum = msg.ReadByte( ); + msg.ReadString( voteString, sizeof( voteString ) ); + mpGame.ClientStartVote( clientNum, voteString ); + break; + } + case GAME_RELIABLE_MESSAGE_PRINT: { + char str[ MAX_PRINT_LEN ] = { '\0' }; + msg.ReadString( str, MAX_PRINT_LEN ); + mpGame.PrintMessage( -1, str ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE: { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + int clientNum = msg.ReadByte(); + voteData.m_fieldFlags = msg.ReadShort(); + char mapName[256]; + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + msg.ReadString( mapName, sizeof( mapName ) ); + voteData.m_map = mapName; + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientStartPackedVote( clientNum, voteData ); + break; + } + case GAME_RELIABLE_MESSAGE_UPDATEVOTE: { + int result = msg.ReadByte( ); + int yesCount = msg.ReadByte( ); + int noCount = msg.ReadByte( ); + int multiVote = msg.ReadByte( ); + voteStruct_t voteData; + char mapNameBuffer[256]; + memset( &voteData, 0, sizeof( voteData ) ); + if ( multiVote ) { + voteData.m_fieldFlags = msg.ReadShort(); + voteData.m_kick = msg.ReadByte(); + msg.ReadString( mapNameBuffer, sizeof( mapNameBuffer ) ); + voteData.m_map = mapNameBuffer; + voteData.m_gameType = msg.ReadByte(); + voteData.m_timeLimit = msg.ReadByte(); + voteData.m_fragLimit = msg.ReadShort(); + voteData.m_tourneyLimit = msg.ReadShort(); + voteData.m_captureLimit = msg.ReadShort(); + voteData.m_buying = msg.ReadByte(); + voteData.m_teamBalance = msg.ReadByte(); + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount, voteData ); + break; + } + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + int numPortals = msg.ReadLong(); + assert( numPortals == gameRenderWorld->NumPortals() ); + for ( int i = 0; i < numPortals; i++ ) { + gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); + } + break; + } + case GAME_RELIABLE_MESSAGE_PORTAL: { + qhandle_t portal = msg.ReadLong(); + int blockingBits = msg.ReadBits( NUM_RENDER_PORTAL_BITS ); + assert( portal > 0 && portal <= gameRenderWorld->NumPortals() ); + gameRenderWorld->SetPortalState( portal, blockingBits ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + mpGame.ClientReadStartState( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND: + mpGame.PlayGlobalItemAcquireSound( msg.ReadBits ( gameLocal.entityDefBits ) ); + break; + case GAME_RELIABLE_MESSAGE_DEATH: { + int attackerEntityNumber = msg.ReadByte( ); + int attackerScore = -1; + if( attackerEntityNumber >= 0 && attackerEntityNumber < MAX_CLIENTS ) { + attackerScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + int victimEntityNumber = msg.ReadByte( ); + int victimScore = -1; + if( victimEntityNumber >= 0 && victimEntityNumber < MAX_CLIENTS ) { + victimScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + + idPlayer* attacker = (attackerEntityNumber != 255 ? static_cast(gameLocal.entities[ attackerEntityNumber ]) : NULL); + idPlayer* victim = (victimEntityNumber != 255 ? static_cast(gameLocal.entities[ victimEntityNumber ]) : NULL); + int methodOfDeath = msg.ReadByte( ); + bool quadKill = msg.ReadBits( 1 ) != 0; + + mpGame.ReceiveDeathMessage( attacker, attackerScore, victim, victimScore, methodOfDeath, quadKill ); + break; + } + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + mpGame.GetGameState()->ReceiveState( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_STAT: { + statManager->ReceiveStat( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_ALL_STATS: { + statManager->ReceiveAllStats( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + if ( !IsServerDemoPlaying() ) { + mpGame.ClientSetInstance( msg ); + } + break; + } + case GAME_RELIABLE_MESSAGE_INGAMEAWARD: { + statManager->ReceiveInGameAward( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + mpBanInfo_t banInfo; + char name[MAX_STRING_CHARS]; + char guid[MAX_STRING_CHARS]; + + FlushBanList( ); + while ( msg.ReadString( name, MAX_STRING_CHARS ) && msg.ReadString( guid, MAX_STRING_CHARS ) ) { + banInfo.name = name; + strncpy( banInfo.guid, guid, CLIENT_GUID_LENGTH ); + banList.Append( banInfo ); + } + + break; + } + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + mpGame.ReadMapList( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE: { + idBitMsg unreliable; + unreliable.Init( msg.GetReadData(), msg.GetRemainingData() ); + unreliable.SetSize( msg.GetRemainingData() ); + unreliable.BeginReading(); + ProcessUnreliableMessage( unreliable ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT_ACK: { + clientAckSequence = msg.ReadLong(); + // make sure the local client is waiting for a ACK + // if he's not, this may indicate a problem + idPlayer *p = GetLocalPlayer(); + if ( net_clientPredictWeaponSwitch.GetBool() && p != NULL && !p->IsWaitingForPredictAck() ) { + common->Warning( "frame %d: got EVENT_ACK seq %d while not expecting one", gameLocal.framenum, clientAckSequence ); + } + break; + } + default: { + Error( "Unknown server->client reliable message: %d", id ); + break; + } + } +} + +// RAVEN BEGIN +/* +================ +idGameLocal::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientRun( void ) { + if( isMultiplayer ) { + mpGame.ClientRun(); + } +} + +/* +================ +idGameLocal::ClientEndFrame +Called once each client render frame (after all ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientEndFrame( void ) { + if( isMultiplayer ) { + mpGame.ClientEndFrame(); + } +} + +/* +================ +idGameLocal::ProcessRconReturn +================ +*/ +void idGameLocal::ProcessRconReturn( bool success ) { + if( isMultiplayer ) { + mpGame.ProcessRconReturn( success ); + } +} + +/* +================ +idGameLocal::ResetGuiRconStatus +================ +*/ +void idGameLocal::ResetRconGuiStatus( void ) { + if( isMultiplayer ) { + mpGame.ResetRconGuiStatus( ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientPrediction +server demos: clientNum == MAX_CLIENTS +================ +*/ +gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame, ClientStats_t *cs ) { + idEntity *ent; + idPlayer *player; // may be NULL when predicting for a server demo + gameReturn_t ret; + + ret.sessionCommand[ 0 ] = '\0'; + + if ( g_showDebugHud.GetInteger() && net_entsInSnapshot && net_snapshotSize) { + gameDebug.SetInt( "snap_ents", net_entsInSnapshot ); + gameDebug.SetInt( "snap_size", net_snapshotSize ); + + net_entsInSnapshot = 0; + net_snapshotSize = 0; + } + + if ( clientNum == localClientNum ) { + gameDebug.BeginFrame(); + gameLog->BeginFrame( time ); + } + + isLastPredictFrame = lastPredictFrame; + + InitLocalClient( clientNum ); + + // update the game time + framenum++; + previousTime = time; + time += GetMSec(); + + // update the real client time and the new frame flag + if ( time > realClientTime ) { + realClientTime = time; + isNewFrame = true; + } else { + isNewFrame = false; + } + + if ( clientNum == MAX_CLIENTS ) { + player = gameLocal.GetLocalPlayer(); + assert( player ); + } else { + player = static_cast( entities[clientNum] ); + } + + // check for local client lag + if ( player ) { + if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) { + player->isLagged = true; + } else { + player->isLagged = false; + } + } + + if ( cs ) { + cs->isLastPredictFrame = isLastPredictFrame; + cs->isLagged = player ? player->isLagged : false; + cs->isNewFrame = isNewFrame; + } + + // set the user commands for this frame + usercmds = clientCmds; + + // run prediction on all entities from the last snapshot + for ( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + // don't force TH_PHYSICS on, only call ClientPredictionThink if thinkFlags != 0 + // it's better to synchronize TH_PHYSICS on specific entities when needed ( movers may be trouble ) + if ( ent->thinkFlags != 0 ) { + ent->ClientPredictionThink(); + } + } + + // run client entities + if ( isNewFrame ) { + // rjohnson: only run the entire logic when it is a new frame + rvClientEntity* cent; + for ( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // freeView + if ( clientNum == MAX_CLIENTS && player && isNewFrame && !isRepeater) { + assert( player->IsFakeClient() ); + + player->ClientPredictionThink(); + player->UpdateVisuals(); + + assert( player->spectating ); + + if ( player->spectator != player->entityNumber ) { + followPlayer = player->spectator; + } else { + followPlayer = -1; + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // show any debug info for this frame + if ( isNewFrame ) { + RunDebugInfo(); + D_DrawDebugLines(); + } + + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + } + +// RAVEN BEGIN +// ddynerman: client logging/debugging + if ( clientNum == localClientNum ) { + gameDebug.EndFrame(); + gameLog->EndFrame(); + } +// RAVEN END + + g_simpleItems.ClearModified(); + return ret; +} + +/* +=============== +idGameLocal::Tokenize +=============== +*/ +void idGameLocal::Tokenize( idStrList &out, const char *in ) { + char buf[ MAX_STRING_CHARS ]; + char *token, *next; + + idStr::Copynz( buf, in, MAX_STRING_CHARS ); + token = buf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + idStr::ToLower( token ); + out.Append( token ); + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } +} + +/* +=============== +idGameLocal::DownloadRequest +=============== +*/ +bool idGameLocal::DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) { + if ( !cvarSystem->GetCVarInteger( "net_serverDownload" ) ) { + return false; + } + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 1 ) { + // 1: single URL redirect + if ( !strlen( cvarSystem->GetCVarString( "si_serverURL" ) ) ) { + common->Warning( "si_serverURL not set" ); + return false; + } + idStr::snPrintf( urls, MAX_STRING_CHARS, "1;%s", cvarSystem->GetCVarString( "si_serverURL" ) ); + return true; + } else { + // 2: table of pak URLs + // 3: table of pak URLs with built-in http server + // first token is the game pak if requested, empty if not requested by the client + // there may be empty tokens for paks the server couldn't pinpoint - the order matters + idStr reply = "2;"; + idStrList dlTable, pakList; + bool matchAll = false; + int i, j; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + matchAll = true; + } else { + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + } + Tokenize( pakList, paks ); + + for ( i = 0; i < pakList.Num(); i++ ) { + if ( i > 0 ) { + reply += ";"; + } + if ( pakList[ i ][ 0 ] == '\0' ) { + if ( i == 0 ) { + // pak 0 will always miss when client doesn't ask for game bin + common->DPrintf( "no game pak request\n" ); + } else { + common->DPrintf( "no pak %d\n", i ); + } + continue; + } + + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + + if ( !url.Length() ) { + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 2 ) { + common->Warning( "net_serverDownload == 2 and net_serverDlBaseURL not set" ); + } else { + url = cvarSystem->GetCVarString( "net_httpServerBaseURL" ); + } + } + + if ( matchAll ) { + url.AppendPath( pakList[i] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } else { + for ( j = 0; j < dlTable.Num(); j++ ) { + if ( !pakList[ i ].Icmp( dlTable[ j ] ) ) { + break; + } + } + if ( j == dlTable.Num() ) { + common->Printf( "download for %s: pak not matched: %s\n", IP, pakList[ i ].c_str() ); + } else { + url.AppendPath( dlTable[ j ] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } + } + } + + idStr::Copynz( urls, reply, MAX_STRING_CHARS ); + return true; + } +} + +/* +=============== +idGameLocal::HTTPRequest +=============== +*/ +bool idGameLocal::HTTPRequest( const char *IP, const char *file, bool isGamePak ) { + idStrList dlTable; + int i; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + return true; + } + + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + while ( *file == '/' ) ++file; // net_serverDlTable doesn't include the initial / + + for ( i = 0; i < dlTable.Num(); i++ ) { + if ( !dlTable[ i ].Icmp( file ) ) { + return true; + } + } + + return false; +} + +/* +=============== +idEventQueue::Alloc +=============== +*/ +entityNetEvent_t* idEventQueue::Alloc() { + entityNetEvent_t* event = eventAllocator.Alloc(); + event->prev = NULL; + event->next = NULL; + return event; +} + +/* +=============== +idEventQueue::Free +=============== +*/ +void idEventQueue::Free( entityNetEvent_t *event ) { + // should only be called on an unlinked event! + assert( !event->next && !event->prev ); + eventAllocator.Free( event ); +} + +/* +=============== +idEventQueue::Shutdown +=============== +*/ +void idEventQueue::Shutdown() { + eventAllocator.Shutdown(); + Init(); +} + +/* +=============== +idEventQueue::Init +=============== +*/ +void idEventQueue::Init( void ) { + start = NULL; + end = NULL; +} + +/* +=============== +idEventQueue::Dequeue +=============== +*/ +entityNetEvent_t* idEventQueue::Dequeue( void ) { + entityNetEvent_t* event = start; + if ( !event ) { + return NULL; + } + + start = start->next; + + if ( !start ) { + end = NULL; + } else { + start->prev = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::RemoveLast +=============== +*/ +entityNetEvent_t* idEventQueue::RemoveLast( void ) { + entityNetEvent_t *event = end; + if ( !event ) { + return NULL; + } + + end = event->prev; + + if ( !end ) { + start = NULL; + } else { + end->next = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::Enqueue +=============== +*/ +void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { + if ( behaviour == OUTOFORDER_IGNORE ) { + // warn if an event comes in with a time before the last one in queue + // as that would cause the one we're adding to get delayed processing + if ( end && end->time > event->time ) { + common->Warning( "new event with id %d (time %d) is earlier than head event with id %d (time %d) - is OUTOFORDER_SORT needed?", event->event, event->time, end->event, end->time ); + } + } + + if ( behaviour == OUTOFORDER_DROP ) { + // go backwards through the queue and determine if there are + // any out-of-order events + while ( end && end->time > event->time ) { + entityNetEvent_t *outOfOrder = RemoveLast(); + common->Warning( "new event with id %d (time %d) caused removal of event with id %d (time %d), game time = %d", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); + Free( outOfOrder ); + } + } else if ( behaviour == OUTOFORDER_SORT && end != NULL ) { + entityNetEvent_t *cur = end; + // iterate until we find a time < the new event's + while ( cur && cur->time > event->time ) { + cur = cur->prev; + } + if ( cur == end ) { + end = event; + } + if ( !cur ) { + // add to start + event->next = start; + event->prev = NULL; + start->prev = event; // start cannot be NULL because end is already != NULL + start = event; + } else { + // insert + event->prev = cur; + event->next = cur->next; + if ( cur->next != NULL ) { // insert possibly at the end so have to check that + cur->next->prev = event; + } + cur->next = event; + } + return; + } + + // add the new event + event->next = NULL; + event->prev = NULL; + + if ( end ) { + end->next = event; + event->prev = end; + } else { + start = event; + } + end = event; +} + +// RAVEN BEGIN +// shouchard: ban list stuff here + +/* +================ +idGameLocal::LoadBanList +================ +*/ +void idGameLocal::LoadBanList() { + + // open file + idStr token; + idFile *banFile = fileSystem->OpenFileRead( BANLIST_FILENAME ); + mpBanInfo_t banInfo; + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::LoadBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + // parse file (read three consecutive strings per banInfo (real complex ;) ) ) + while ( banFile->ReadString( token ) > 0 ) { + // name + banInfo.name = token; + + // guid + if ( banFile->ReadString( token ) > 0 && token.Length() >= 11 ) { + idStr::Copynz( banInfo.guid, token.c_str(), CLIENT_GUID_LENGTH ); + + banList.Append( banInfo ); + continue; + } + + gameLocal.Warning( "idGameLocal::LoadBanList: Potential curruption of banlist file (%s).", BANLIST_FILENAME ); + } + fileSystem->CloseFile( banFile ); + + banListLoaded = true; + banListChanged = false; +} + +/* +================ +idGameLocal::SaveBanList +================ +*/ +void idGameLocal::SaveBanList() { + if ( !banListChanged ) { + return; + } + + // open file + idFile *banFile = fileSystem->OpenFileWrite( BANLIST_FILENAME ); + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::SaveBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + for ( int i = 0; i < banList.Num(); i++ ) { + const mpBanInfo_t& banInfo = banList[ i ]; + char temp[ 16 ] = { 0, }; + banFile->WriteString( va( "%s", banInfo.name.c_str() ) ); + idStr::Copynz( temp, banInfo.guid, CLIENT_GUID_LENGTH ); + banFile->WriteString( temp ); +// idStr::Copynz( temp, (const char*)banInfo.ip, 15 ); +// banFile->WriteString( "255.255.255.255" ); + } + fileSystem->CloseFile( banFile ); + banListChanged = false; +} + +/* +================ +idGameLocal::FlushBanList +================ +*/ +void idGameLocal::FlushBanList() { + banList.Clear(); + banListLoaded = false; + banListChanged = false; +} + +/* +================ +idGameLocal::IsPlayerBanned +================ +*/ +bool idGameLocal::IsPlayerBanned( const char *name ) { + assert( name ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( name, banList[ i ].name ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::IsGuidBanned +================ +*/ +bool idGameLocal::IsGuidBanned( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::AddGuidToBanList +================ +*/ +void idGameLocal::AddGuidToBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + mpBanInfo_t banInfo; + char name[ 512 ]; // TODO: clean this up + gameLocal.GetPlayerName( gameLocal.GetClientNumByGuid( guid ), name ); + banInfo.name = name; + idStr::Copynz( banInfo.guid, guid, CLIENT_GUID_LENGTH ); +// SIMDProcessor->Memset( banInfo.ip, 0xFF, 15 ); + banList.Append( banInfo ); + banListChanged = true; +} + +/* +================ +idGameLocal::RemoveGuidFromBanList +================ +*/ +void idGameLocal::RemoveGuidFromBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we find a match remove it. + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + banList.RemoveIndex( i ); + banListChanged = true; + return; + } + } +} + +/* +================ +idGameLocal::RegisterClientGuid +================ +*/ +void idGameLocal::RegisterClientGuid( int clientNum, const char *guid ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + assert( guid ); + memset( clientGuids[ clientNum ], 0, CLIENT_GUID_LENGTH ); // just in case + idStr::Copynz( clientGuids[ clientNum ], guid, CLIENT_GUID_LENGTH ); +} + +/* +================ +idGameLocal::GetBanListCount +================ +*/ +int idGameLocal::GetBanListCount() { + if ( !banListLoaded ) { + LoadBanList(); + } + + return banList.Num(); +} + +/* +================ +idGameLocal::GetBanListEntry +================ +*/ +const mpBanInfo_t* idGameLocal::GetBanListEntry( int entry ) { + if ( !banListLoaded ) { + LoadBanList(); + } + + if ( entry < 0 || entry >= banList.Num() ) { + return NULL; + } + + return &banList[ entry ]; +} + +/* +================ +idGameLocal::GetGuidByClientNum +================ +*/ +const char *idGameLocal::GetGuidByClientNum( int clientNum ) { + assert( clientNum >= 0 && clientNum < numClients ); + + return clientGuids[ clientNum ]; +} + +/* +================ +idGameLocal::GetClientNumByGuid +================ +*/ +int idGameLocal::GetClientNumByGuid( const char * guid ) { + assert( guid ); + + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( !idStr::Icmp( networkSystem->GetClientGUID( i ), guid ) ) { + return i; + } + } + return -1; +} + +// mekberg: send ban list to client +/* +================ +idGameLocal::ServerSendBanList +================ +*/ +void idGameLocal::ServerSendBanList( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + + if ( !banListLoaded ) { + LoadBanList(); + } + + int i; + int c = banList.Num(); + for ( i = 0; i < c; i++ ) { + outMsg.WriteString( banList[ i ].name.c_str() ); + outMsg.WriteString( banList[ i ].guid, CLIENT_GUID_LENGTH ); + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); +} + +// mekberg: so we can populate ban list outside of multiplayer game +/* +=================== +idGameLocal::PopulateBanList +=================== +*/ +void idGameLocal::PopulateBanList( idUserInterface* hud ) { + if ( !hud ) { + return; + } + + int bans = GetBanListCount(); + for ( int i = 0; i < bans; i++ ) { + const mpBanInfo_t * banInfo = GetBanListEntry( i ); + hud->SetStateString( va( "sa_banList_item_%d", i ), va( "%d: %s\t%s", i+1, banInfo->name.c_str(), banInfo->guid ) ); + } + hud->DeleteStateVar( va( "sa_banList_item_%d", bans ) ); + hud->SetStateString( "sa_banList_sel_0", "-1" ); + // used to trigger a redraw, was slow, and doesn't seem to do anything so took it out. fixes #13675 + hud->StateChanged( gameLocal.time, false ); +} +// RAVEN END + +/* +================ +idGameLocal::ServerSendInstanceReliableMessageExcluding +Works like networkSystem->ServerSendReliableMessageExcluding, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + assert( false ); // does not happen in q4mp, previous revisions of the code would have crashed + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg ); // NOTE: will also transmit through repeater + return; + } + + for ( i = 0; i < numClients; i++ ) { + if ( i == excludeClient ) { + continue; + } + + if ( entities[ i ] == NULL ) { + continue; + } + + if ( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg, true ); + } + + if ( owner->GetInstance() == 0 ) { + // record this message into the demo client if server demo is active + // the message will also go in the server demo data through the above individual client ServerSendReliableMessage, + // but will be ignored on replay unless you are following that particular client + networkSystem->ServerSendReliableMessage( MAX_CLIENTS, msg, true ); + + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg ); + } +} + +/* +================ +idGameLocal::ServerSendInstanceReliableMessage +Works like networkSystem->ServerSendReliableMessage, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); // NOTE: will also transmit through repeater + return; + } + + if ( clientNum == -1 ) { + for ( i = 0; i < numClients; i++ ) { + if ( entities[ i ] == NULL ) { + continue; + } + + if ( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg, true ); + } + + if ( owner->GetInstance() == 0 ) { + // see ServerSendInstanceReliableMessageExcluding + networkSystem->ServerSendReliableMessage( MAX_CLIENTS, msg, true ); + networkSystem->RepeaterSendReliableMessage( -1, msg ); + } + } else { + assert( false ); // not used in q4mp + if ( entities[ clientNum ] && entities[ clientNum ]->GetInstance() == owner->GetInstance() ) { + networkSystem->ServerSendReliableMessage( clientNum, msg, true ); + + if ( owner->GetInstance() == 0 ) { + for ( i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].active ) { + continue; + } + + if ( viewers[ i ].origin.followClient != clientNum && !viewers[ i ].nopvs ) { + continue; + } + + networkSystem->RepeaterSendReliableMessage( clientNum, msg, false, i ); + } + } + } + } +} + +/* +=============== +idGameLocal::RepeaterAppendUnreliableMessage +=============== +*/ +void idGameLocal::RepeaterAppendUnreliableMessage( int icl, const idBitMsg &msg, const idBitMsg *header ) { + assert( isRepeater ); + + // send the normally unreliable message as a reliable message + if ( ( viewers[ icl ].nopvs && ( cvarSystem->GetCVarInteger( "g_repeaterReliableOnly" ) >= 1 ) ) || ( cvarSystem->GetCVarInteger( "g_repeaterReliableOnly" ) >= 2 ) ) { + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + idBitMsg reliable; + + reliable.Init( msgBuf, sizeof( msgBuf ) ); + reliable.WriteByte( GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE ); + if ( header ) { + reliable.WriteData( header->GetData(), header->GetSize() ); + } + reliable.WriteData( msg.GetData(), msg.GetSize() ); + + networkSystem->RepeaterSendReliableMessage( icl, reliable ); + return; + } + + // send the message + if ( header ) { + viewerUnreliableMessages[ icl ].AddConcat( header->GetData(), header->GetSize(), msg.GetData(), msg.GetSize(), false ); + } else { + viewerUnreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } +} + +/* +=============== +idGameLocal::RepeaterUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +header and msg are concatenated in case a header is needed. +=============== +*/ +void idGameLocal::RepeaterUnreliableMessage( const idBitMsg &msg, const int clientNum, const idBitMsg *header ) { + if ( !isRepeater ) { + return; + } + + int icl; + + for ( icl = 0; icl < maxViewer; icl++ ) { + if ( !viewers[ icl ].active ) { + continue; + } + + if ( clientNum != -1 && viewers[ icl ].origin.followClient != clientNum && !viewers[ icl ].nopvs ) { + // not global, not to the followed client + + // not following anyone + if ( viewers[ icl ].origin.followClient == -1 ) { + continue; + } + + // the followed player doesn't exist + if ( !entities[ viewers[ icl ].origin.followClient ] ) { + continue; + } + + // the followed player isn't a player + if ( !entities[ viewers[ icl ].origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + // following a valid player + + idPlayer *player = static_cast< idPlayer * >( entities[ viewers[ icl ].origin.followClient ] ); + if ( !player->spectating || viewers[ icl ].origin.followClient != player->spectator ) { + // .. but not the right player + continue; + } + + // fallthrough + } + + RepeaterAppendUnreliableMessage( icl, msg, header ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +=============== +*/ +void idGameLocal::SendUnreliableMessage( const idBitMsg &msg, const int clientNum ) { + int icl; + idPlayer *player; + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // not to local client + // note that if local is spectated he will still get it + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( icl != clientNum ) { + player = static_cast< idPlayer * >( entities[ icl ] ); + // drop all clients except the ones that follow the client we emit to + if ( !player->spectating || player->spectator != clientNum ) { + continue; + } + } + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING || isRepeater ) { + // record the type and destination for remap on readback + idBitMsg dest; + byte msgBuf[ 16 ]; + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_CLIENTNUM ); + dest.WriteChar( clientNum ); + + if ( demoState == DEMO_RECORDING ) { + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + + RepeaterUnreliableMessage( msg, clientNum, &dest ); + } +} + +/* +=============== +idGameLocal::RepeaterUnreliableMessagePVS +Forwards an unreliable message to viewers based on PVS. +Concats header and msg in case a header needs to be added. +=============== +*/ +void idGameLocal::RepeaterUnreliableMessagePVS( const idBitMsg &msg, const int *areas, int numAreas, const idBitMsg *header ) { + if ( !isRepeater ) { + return; + } + + int i, icl; + pvsHandle_t pvsHandle[ 2 ]; + assert( numAreas <= 2 ); + + for ( i = 0; i < numAreas; i++ ) { + pvsHandle[ i ] = pvs.SetupCurrentPVS( areas[ i ], PVS_NORMAL ); + } + + for ( icl = 0; icl < maxViewer; icl ++ ) { + if ( !viewers[ icl ].active ) { + continue; + } + + // if no areas are given, this is a global emit; also always emit for repeaters + if ( numAreas && !viewers[ icl ].nopvs ) { + // only send if pvs says this client can see it + for ( i = 0; i < numAreas; i++ ) { + if ( pvs.InCurrentPVS( pvsHandle[ i ], viewers[ icl ].pvsArea ) ) { + RepeaterAppendUnreliableMessage( icl, msg, header ); + break; + } + } + } else { + // global, so always send it + RepeaterAppendUnreliableMessage( icl, msg, header ); + } + } + + for ( i = 0; i < numAreas; i++ ) { + pvs.FreeCurrentPVS( pvsHandle[ i ] ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessagePVS +instanceEnt to NULL for no instance checks +excludeClient to -1 for no exclusions +=============== +*/ +void idGameLocal::SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1, int area2 ) { + int icl; + int matchInstance = instanceEnt ? instanceEnt->GetInstance() : -1; + idPlayer *player; + int areas[ 2 ]; + int numEvAreas; + + numEvAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numEvAreas++; + } + if ( area2 != -1 ) { + areas[ numEvAreas ] = area2; + numEvAreas++; + } + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // local client is always excluded + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( matchInstance >= 0 && entities[ icl ]->GetInstance() != matchInstance ) { + continue; + } + if ( clientsPVS[ icl ].i < 0 ) { + // clients for which we don't have PVS info won't get anything + continue; + } + player = static_cast< idPlayer * >( entities[ icl ] ); + + // if no areas are given, this is a global emit + if ( numEvAreas ) { + // ony send if pvs says this client can see it + if ( !pvs.InCurrentPVS( clientsPVS[ icl ], areas, numEvAreas ) ) { + continue; + } + } + + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING || isRepeater ) { + // record the target areas to the message + idBitMsg dest; + byte msgBuf[ 16 ]; + + // Tourney games: only record from instance 0 + if ( !instanceEnt || instanceEnt->GetInstance() == 0 ) { + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_AREAS ); + dest.WriteLong( area1 ); + dest.WriteLong( area2 ); + + if ( demoState == DEMO_RECORDING ) { + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + + RepeaterUnreliableMessagePVS( msg, areas, numEvAreas, &dest ); + } + } +} + +/* +=============== +idGameLocal::ClientReadUnreliableMessages +=============== +*/ +void idGameLocal::ClientReadUnreliableMessages( const idBitMsg &_msg ) { + idMsgQueue localQueue; + int size; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idBitMsg msg; + + localQueue.ReadFrom( _msg ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + while ( localQueue.Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + ProcessUnreliableMessage( msg ); + msg.BeginWriting(); + } +} + +/* +=============== +idGameLocal::DemoReplayInAreas +checks if our current demo replay view ( server demo ) matches the areas given +=============== +*/ +bool idGameLocal::IsDemoReplayInAreas( const int areas[2], int numAreas ) { + idVec3 view; + pvsHandle_t handle; + + bool ret; + + assert( IsServerDemoPlaying() ); + + if ( !numAreas ) { + return true; + } + + if ( followPlayer == -1 ) { + view = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + } else { + view = entities[ followPlayer ]->GetPhysics()->GetOrigin(); + } + + // could probably factorize this, at least for processing all unreliable messages, maybe at a higher level of the loop? + handle = pvs.SetupCurrentPVS( view ); + ret = pvs.InCurrentPVS( handle, areas, numAreas ); + pvs.FreeCurrentPVS( handle ); + return ret; +} + +/* +=============== +idGameLocal::ProcessUnreliableMessage +=============== +*/ +void idGameLocal::ProcessUnreliableMessage( const idBitMsg &msg ) { + if ( IsServerDemoPlaying() ) { + int record_type = msg.ReadByte(); + assert( record_type < GAME_UNRELIABLE_RECORD_COUNT ); + switch ( record_type ) { + case GAME_UNRELIABLE_RECORD_CLIENTNUM: { + int client = msg.ReadChar(); + + RepeaterUnreliableMessage( msg, client ); + + if ( client != -1 ) { + // unreliable was targetted + if ( followPlayer != client ) { + // either free flying, or following someone else + return; + } + } + break; + } + case GAME_UNRELIABLE_RECORD_AREAS: { + int areas[ 2 ]; + int numEvAreas = 0; + + areas[ numEvAreas ] = msg.ReadLong(); + if ( areas[ numEvAreas ] != -1 ) ++numEvAreas; + areas[ numEvAreas ] = msg.ReadLong(); + if ( areas[ numEvAreas ] != -1 ) ++numEvAreas; + + RepeaterUnreliableMessagePVS( msg, areas, numEvAreas ); + + if ( !IsDemoReplayInAreas( areas, numEvAreas ) ) { + return; + } + break; + } + } + } + + int type = msg.ReadByte(); + switch ( type ) { + case GAME_UNRELIABLE_MESSAGE_EVENT: { + idEntityPtr p; + int spawnId = msg.ReadBits( 32 ); + p.SetSpawnId( spawnId ); + + if ( p.GetEntity() ) { + p.GetEntity()->ClientReceiveEvent( msg.ReadByte(), time, msg ); + } else { + Warning( "ProcessUnreliableMessage: no local entity 0x%x for event %d", spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ), msg.ReadByte() ); + } + break; + } + case GAME_UNRELIABLE_MESSAGE_EFFECT: { + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* effect; + effectCategory_t category; + const idDecl *decl; + + decl = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + + category = ( effectCategory_t )msg.ReadByte(); + + bool loop = msg.ReadBits( 1 ) != 0; + bool predictBit = msg.ReadBits( 1 ) != 0; + + if ( bse->CanPlayRateLimited( category ) && ( !predictBit || !g_predictedProjectiles.GetBool() ) ) { + effect = new rvClientEffect( decl ); + effect->SetOrigin( origin ); + effect->SetAxis( quat.ToMat3() ); + effect->Play( time, loop, origin2 ); + } + + break; + } + case GAME_UNRELIABLE_MESSAGE_HITSCAN: { + ClientHitScan( msg ); + break; + } +#ifdef _USE_VOICECHAT + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + mpGame.ReceiveAndPlayVoiceData( msg ); + break; + } +#else + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + break; + } +#endif + default: { + Error( "idGameLocal::ProcessUnreliableMessage() - Unknown unreliable message '%d'\n", type ); + } + } +} + +/* +=============== +idGameLocal::WriteNetworkInfo +=============== +*/ +void idGameLocal::WriteNetworkInfo( idFile* file, int clientNum ) { + int i, j; + snapshot_t *snapshot; + entityState_t *entityState; + + if ( !IsServerDemoRecording() ) { + + // save the current states + for ( i = 0; i < MAX_GENTITIES; i++ ) { + entityState = clientEntityStates[clientNum][i]; + file->WriteBool( !!entityState ); + if ( entityState ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + } + + // save the PVS states + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->WriteInt( clientPVS[i][j] ); + } + } + + } + + // players ( including local client ) + j = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + j++; + } + file->WriteInt( j ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + file->WriteInt( i ); + file->WriteInt( spawnIds[ i ] ); + } + + if ( !IsServerDemoRecording() ) { + + // write number of snapshots so on readback we know how many to allocate + i = 0; + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + i++; + } + file->WriteInt( i ); + + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + file->WriteInt( snapshot->sequence ); + + // write number of entity states in the snapshot + i = 0; + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + i++; + } + file->WriteInt( i ); + + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + + file->Write( snapshot->pvs, sizeof( snapshot->pvs ) ); + } + + } + + // write the 'initial reliables' data + mpGame.WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idGameLocal::ReadNetworkInfo +=============== +*/ +void idGameLocal::ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) { + int i, j, num, numStates, stateSize; + snapshot_t *snapshot, **lastSnap; + entityState_t *entityState, **lastState; + + assert( clientNum == MAX_CLIENTS || !IsServerDemoPlaying() ); + InitLocalClient( clientNum ); + + time = gameTime; + previousTime = gameTime; + + // force new frame + realClientTime = 0; + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + if ( !IsServerDemoPlaying() || IsRepeaterDemoPlaying() ) { + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + bool isValid; + + file->ReadBool( isValid ); + if ( isValid ) { + clientEntityStates[clientNum][i] = entityStateAllocator.Alloc(); + entityState = clientEntityStates[clientNum][i]; + entityState->next = NULL; + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + } else { + clientEntityStates[clientNum][i] = NULL; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->ReadInt( clientPVS[i][j] ); + } + } + + } + + // spawn player entities. ( numClients is not a count but the watermark of client indexes ) + file->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + int icl, spawnId; + file->ReadInt( icl ); + file->ReadInt( spawnId ); + SpawnPlayer( icl ); + spawnIds[ icl ] = spawnId; + numClients = icl + 1; + } + + if ( !IsServerDemoPlaying() || IsRepeaterDemoPlaying() ) { + + file->ReadInt( num ); + lastSnap = &clientSnapshots[ localClientNum ]; + for ( i = 0; i < num; i++ ) { + snapshot = snapshotAllocator.Alloc(); + snapshot->firstEntityState = NULL; + snapshot->next = NULL; + file->ReadInt( snapshot->sequence ); + + file->ReadInt( numStates ); + lastState = &snapshot->firstEntityState; + for ( j = 0; j < numStates; j++ ) { + entityState = entityStateAllocator.Alloc(); + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + entityState->next = NULL; + assert( !(*lastState ) ); + *lastState = entityState; + lastState = &entityState->next; + } + + file->Read( snapshot->pvs, sizeof( snapshot->pvs ) ); + + assert( !(*lastSnap) ); + *lastSnap = snapshot; + lastSnap = &snapshot->next; + } + + // spawn entities + for ( i = 0; i < ENTITYNUM_NONE; i++ ) { + int spawnId, entityDefNumber; + idBitMsgDelta deltaMsg; + idDict args; + entityState_t *base = clientEntityStates[clientNum][i]; + idEntity *ent = entities[i]; + const idDeclEntityDef *decl; + + if ( !base ) { + continue; + } + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + assert( entityDefNumber >= 0 ); + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString("spawnclass") ); + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // read the player state from player entities + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( deltaMsg.ReadBits( 1 ) == 1 ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + static_cast< idPlayer * >( ent )->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + + // this is useful. for instance on idPlayer, resets stuff so powerups actually appear + ent->ClientUnstale(); + } + + { + // specific state read for game and player state + idBitMsgDelta deltaMsg; + entityState_t *base = clientEntityStates[clientNum][ENTITYNUM_NONE]; + + // it's possible to have a recording start right at CS_INGAME and not have a base for reading this yet + if ( base ) { + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + ReadGameStateFromSnapshot( deltaMsg ); + } + } + + // set self spectating state according to userinfo settings + if ( !IsRepeaterDemoPlaying() ) { + GetLocalPlayer()->Spectate( idStr::Icmp( userInfo[ clientNum ].GetString( "ui_spectate" ), "Spectate" ) == 0 ); + } + + } + + // read the 'initial reliables' data + mpGame.ReadNetworkInfo( file, clientNum ); +} + +/* +============ +idGameLocal::SetDemoState +============ +*/ +void idGameLocal::SetDemoState( demoState_t state, bool _serverDemo, bool _timeDemo ) { + if ( demoState == DEMO_RECORDING && state == DEMO_NONE && serverDemo ) { + ServerClientDisconnect( MAX_CLIENTS ); + } + demoState = state; + serverDemo = _serverDemo; + timeDemo = _timeDemo; +} + +/* +============ +idGameLocal::SetRepeaterState +============ +*/ +void idGameLocal::SetRepeaterState( bool isRepeater, bool serverIsRepeater ) { + this->isRepeater = isRepeater; + this->serverIsRepeater = serverIsRepeater; + + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 3 ) { + networkSystem->HTTPEnable( this->isServer || this->isRepeater ); + } + + ReallocViewers( isRepeater ? cvarSystem->GetCVarInteger( "ri_maxViewers" ) : 0 ); +} + +/* +=============== +idGameLocal::ValidateDemoProtocol +=============== +*/ +bool idGameLocal::ValidateDemoProtocol( int minor_ref, int minor ) { +#if 0 + // 1.1 beta : 67 + // 1.1 final: 68 + // 1.2 : 69 + // 1.3 : 71 + + // let 1.3 play 1.2 demos - keep a careful eye on snapshotting changes + demo_protocol = minor; + return ( minor_ref == minor || ( minor_ref == 71 && minor == 69 ) ); +#else + demo_protocol = minor; + return ( minor_ref == minor ); +#endif +} + +/* +=============== +idGameLocal::RandomSpawn +=============== +*/ +idPlayerStart *idGameLocal::RandomSpawn( void ) { + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; +} diff --git a/source/mpgame/Healing_Station.cpp b/source/mpgame/Healing_Station.cpp new file mode 100644 index 0000000..5a6e412 --- /dev/null +++ b/source/mpgame/Healing_Station.cpp @@ -0,0 +1,209 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Healing_Station.h" + +CLASS_DECLARATION( idAnimatedEntity, rvHealingStation ) +END_CLASS + +/* +================ +rvHealingStation::Think +================ +*/ +void rvHealingStation::Think ( void ) { + // TODO: I'm guessing this is bad, but I wanted to get this in so that people could start + // placing it. The entity decided to stop thinking and I didn't have time to debug it. + BecomeActive( TH_ALL ); + + stateThread.Execute(); + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + if ( healthDispensed > 0 ) { + CreateFrame( float( healthDispensed ) / maxHealth ); + } + Present(); + } +} + +/* +================ +rvHealingStation::Spawn +================ +*/ +void rvHealingStation::Spawn ( void ) { + entityToHeal = 0; + nextHealTime = 0; + healFrequency = spawnArgs.GetInt( "heal_frequency", "24" ); + healAmount = spawnArgs.GetInt( "heal_amount", "1" ); + + healthDispensed = 0; + soundStartTime = 0; + soundLength = 0; + maxHealth = spawnArgs.GetInt( "max_health", "100" ); + + dispenseAnim = GetAnimator()->GetAnim( spawnArgs.GetString( "dispense_anim", "dispense" ) ); + + CreateFrame( 0 ); + + stateThread.SetOwner( this ); + stateThread.SetName( GetName() ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 4 ); +} + +/* +================ +rvHealingStation::Save +================ +*/ +void rvHealingStation::Save ( idSaveGame *savefile ) const { + stateThread.Save( savefile ); + entityToHeal.Save ( savefile ); + savefile->WriteInt( nextHealTime ); + savefile->WriteInt( healFrequency ); + savefile->WriteInt( healAmount ); + savefile->WriteInt( healthDispensed ); + savefile->WriteInt( maxHealth ); + savefile->WriteInt( dispenseAnim ); + savefile->WriteInt( soundStartTime ); + savefile->WriteInt( soundLength ); +} + +/* +================ +rvHealingStation::Restore +================ +*/ +void rvHealingStation::Restore ( idRestoreGame *savefile ) { + stateThread.Restore( savefile, this ); + entityToHeal.Restore ( savefile ); + savefile->ReadInt( nextHealTime ); + savefile->ReadInt( healFrequency ); + savefile->ReadInt( healAmount ); + savefile->ReadInt( healthDispensed ); + savefile->ReadInt( maxHealth ); + savefile->ReadInt( dispenseAnim ); + savefile->ReadInt( soundStartTime ); + savefile->ReadInt( soundLength ); +} + +/* +================ +rvHealingStation::BeginHealing +================ +*/ +void rvHealingStation::BeginHealing ( idEntity *toHeal ) { + entityToHeal = toHeal; + stateThread.SetState( "Healing" ); +} + +/* +================ +rvHealingStation::EndHealing +================ +*/ +void rvHealingStation::EndHealing ( void ) { + entityToHeal = NULL; +} + +/* +================ +rvHealingStation::CreateFrame +================ +*/ +void rvHealingStation::CreateFrame ( float station_health ) { + // Update the GUI + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->SetStateFloat( "station_health", 1.0f - station_health ); + renderEntity.gui[ 0 ]->StateChanged( gameLocal.time, true ); + } + + // Update the Animation + int numFrames = GetAnimator()->GetAnim( dispenseAnim )->NumFrames(); + float lerp = numFrames * station_health; + int frame = lerp; + lerp = lerp - frame; + frameBlend_t frameBlend = { 0, frame, frame + 1, 1.0f - lerp, lerp }; + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, dispenseAnim, frameBlend ); +} + +/* +================ +rvHealingStation::IsPlaying +================ +*/ +bool rvHealingStation::IsPlaying ( void ) { + idSoundEmitter* emitter = soundSystem->EmitterForIndex ( SOUNDWORLD_GAME, GetSoundEmitter ( ) ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvHealingStation ) + STATE ( "Healing", rvHealingStation::State_Healing ) +END_CLASS_STATES + +/* +================ +rvHealingStation::State_Healing +================ +*/ +stateResult_t rvHealingStation::State_Healing ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_DISPENSE, + }; + + if ( entityToHeal.IsValid() ) { + idPlayer* player = static_cast( entityToHeal.GetEntity( ) ); + const int entityMaxHealth = player->inventory.maxHealth; + + if ( healthDispensed < maxHealth && // and we have health to dispense... + entityToHeal->health < entityMaxHealth && // and the entity needs health. + entityToHeal->health > 0 ) // and he's still alive. + { + switch ( parms.stage ) { + case STAGE_INIT: + soundStartTime = gameLocal.time; + StartSound( "snd_start", SND_CHANNEL_ANY, 0, false, &soundLength ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time > soundStartTime + soundLength ) { + soundStartTime = 0; + soundLength = 0; + return SRESULT_STAGE ( STAGE_DISPENSE ); + } + return SRESULT_WAIT; + + case STAGE_DISPENSE: + if ( gameLocal.time > nextHealTime ) { // If it's time to heal... + int healthGiven = Min( maxHealth - healthDispensed, Min( healAmount, entityMaxHealth - entityToHeal->health ) ); + entityToHeal->health += healthGiven; + healthDispensed += healthGiven; + nextHealTime = gameLocal.time + healFrequency; + } + if ( !IsPlaying ( ) ) { + StartSound( "snd_loop", SND_CHANNEL_ANY, 0, false, NULL ); + } + return SRESULT_WAIT; + } + } + } + + StopSound ( SND_CHANNEL_ANY, 0 ); + StartSound ( "snd_stop", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_DONE; +} diff --git a/source/mpgame/Healing_Station.h b/source/mpgame/Healing_Station.h new file mode 100644 index 0000000..14b950b --- /dev/null +++ b/source/mpgame/Healing_Station.h @@ -0,0 +1,45 @@ +/* +=============================================================================== + + rvHealingStation + +=============================================================================== +*/ +class rvHealingStation : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvHealingStation ); + + virtual void Think ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BeginHealing ( idEntity *toHeal ); + void EndHealing ( void ); + +protected: + + void CreateFrame ( float station_health ); + + stateResult_t State_Healing ( const stateParms_t& parms ); + + rvStateThread stateThread; + idEntityPtr entityToHeal; + int nextHealTime; + int healFrequency; + int healAmount; + int healthDispensed; + int maxHealth; + int dispenseAnim; + int soundStartTime; + int soundLength; + +private: + + bool IsPlaying ( void ); + + CLASS_STATES_PROTOTYPE ( rvHealingStation ); +}; + diff --git a/source/mpgame/IK.cpp b/source/mpgame/IK.cpp new file mode 100644 index 0000000..5b1d786 --- /dev/null +++ b/source/mpgame/IK.cpp @@ -0,0 +1,1062 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idIK + +=============================================================================== +*/ + +/* +================ +idIK::idIK +================ +*/ +idIK::idIK( void ) { + ik_activate = false; + initialized = false; + self = NULL; + animator = NULL; + modifiedAnim = 0; + modelOffset.Zero(); +} + +/* +================ +idIK::~idIK +================ +*/ +idIK::~idIK( void ) { +} + +/* +================ +idIK::Save +================ +*/ +void idIK::Save( idSaveGame *savefile ) const { + savefile->WriteBool( initialized ); + savefile->WriteBool( ik_activate ); + savefile->WriteObject( self ); + savefile->WriteString( animator != NULL && animator->GetAnim( modifiedAnim ) ? animator->GetAnim( modifiedAnim )->Name() : "" ); + //savefile->WriteInt( modifiedAnim ); // Recomputed during restore, do not save + savefile->WriteVec3( modelOffset ); +} + +/* +================ +idIK::Restore +================ +*/ +void idIK::Restore( idRestoreGame *savefile ) { + idStr anim; + + savefile->ReadBool( initialized ); + savefile->ReadBool( ik_activate ); + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( anim ); + //savefile->ReadInt( modifiedAnim ); // This is defined below + savefile->ReadVec3( modelOffset ); + + if ( self ) { + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + } else { + animator = NULL; + modifiedAnim = 0; + } +} + +/* +================ +idIK::IsInitialized +================ +*/ +bool idIK::IsInitialized( void ) const { + return initialized && ik_enable.GetBool(); +} + +/* +================ +idIK::Init +================ +*/ +bool idIK::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + idRenderModel *model; + + if ( self == NULL ) { + return false; + } + + this->self = self; + + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + if ( animator->ModelDef()->ModelHandle() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) uses default model.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + model = animator->ModelHandle(); + if ( model == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + this->modelOffset = modelOffset; + + return true; +} + +/* +================ +idIK::Evaluate +================ +*/ +void idIK::Evaluate( void ) { +} + +/* +================ +idIK::ClearJointMods +================ +*/ +void idIK::ClearJointMods( void ) { + ik_activate = false; +} + +/* +================ +idIK::SolveTwoBones +================ +*/ +bool idIK::SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ) { + float length, lengthSqr, lengthInv, x, y; + idVec3 vec0, vec1; + + vec0 = endPos - startPos; + lengthSqr = vec0.LengthSqr(); + lengthInv = idMath::InvSqrt( lengthSqr ); + length = lengthInv * lengthSqr; + + // if the start and end position are too far out or too close to each other + if ( length > len0 + len1 || length < idMath::Fabs( len0 - len1 ) ) { + jointPos = startPos + 0.5f * vec0; + return false; + } + + vec0 *= lengthInv; + vec1 = dir - vec0 * dir * vec0; + vec1.Normalize(); + + x = ( length * length + len0 * len0 - len1 * len1 ) * ( 0.5f * lengthInv ); + y = idMath::Sqrt( len0 * len0 - x * x ); + + jointPos = startPos + x * vec0 + y * vec1; + + return true; +} + +/* +================ +idIK::GetBoneAxis +================ +*/ +float idIK::GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ) { + float length; + axis[0] = endPos - startPos; + length = axis[0].Normalize(); + axis[1] = dir - axis[0] * dir * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[1], axis[0] ); + return length; +} + + +/* +=============================================================================== + + idIK_Walk + +=============================================================================== +*/ + +/* +================ +idIK_Walk::idIK_Walk +================ +*/ +idIK_Walk::idIK_Walk() { + int i; + + initialized = false; + footModel = NULL; + numLegs = 0; + enabledLegs = 0; + for ( i = 0; i < MAX_LEGS; i++ ) { + footJoints[i] = INVALID_JOINT; + ankleJoints[i] = INVALID_JOINT; + kneeJoints[i] = INVALID_JOINT; + hipJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + hipForward[i].Zero(); + kneeForward[i].Zero(); + upperLegLength[i] = 0.0f; + lowerLegLength[i] = 0.0f; + upperLegToHipJoint[i].Identity(); + lowerLegToKneeJoint[i].Identity(); + oldAnkleHeights[i] = 0.0f; + } + waistJoint = INVALID_JOINT; + + smoothing = 0.75f; + waistSmoothing = 0.5f; + footShift = 0.0f; + waistShift = 0.0f; + minWaistFloorDist = 0.0f; + minWaistAnkleDist = 0.0f; + footUpTrace = 32.0f; + footDownTrace = 32.0f; + tiltWaist = false; + usePivot = false; + + pivotFoot = -1; + pivotYaw = 0.0f; + pivotPos.Zero(); + + oldHeightsValid = false; + oldWaistHeight = 0.0f; + waistOffset.Zero(); +} + +/* +================ +idIK_Walk::~idIK_Walk +================ +*/ +idIK_Walk::~idIK_Walk() { + if ( footModel ) { + delete footModel; + } +} + +/* +================ +idIK_Walk::Save +================ +*/ +void idIK_Walk::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteClipModel( footModel ); + + savefile->WriteInt( numLegs ); + savefile->WriteInt( enabledLegs ); + savefile->Write( footJoints, sizeof( footJoints ) ); + savefile->Write( ankleJoints, sizeof( ankleJoints ) ); + savefile->Write( kneeJoints, sizeof( kneeJoints ) ); + savefile->Write( hipJoints, sizeof( hipJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + savefile->Write( &waistJoint, sizeof( waistJoint ) ); + + savefile->Write( hipForward, sizeof( hipForward ) ); + savefile->Write( kneeForward, sizeof( kneeForward ) ); + + savefile->Write( upperLegLength, sizeof( upperLegLength ) ); + savefile->Write( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Write( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Write( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->WriteFloat( smoothing ); + savefile->WriteFloat( waistSmoothing ); + savefile->WriteFloat( footShift ); + savefile->WriteFloat( waistShift ); + savefile->WriteFloat( minWaistFloorDist ); + savefile->WriteFloat( minWaistAnkleDist ); + savefile->WriteFloat( footUpTrace ); + savefile->WriteFloat( footDownTrace ); + savefile->WriteBool( tiltWaist ); + savefile->WriteBool( usePivot ); + + savefile->WriteInt( pivotFoot ); + savefile->WriteFloat( pivotYaw ); + savefile->WriteVec3( pivotPos ); + savefile->WriteBool( oldHeightsValid ); + savefile->WriteFloat( oldWaistHeight ); + savefile->Write( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->WriteVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Restore +================ +*/ +void idIK_Walk::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadClipModel( footModel ); + + savefile->ReadInt( numLegs ); + savefile->ReadInt( enabledLegs ); + savefile->Read( footJoints, sizeof( footJoints ) ); + savefile->Read( ankleJoints, sizeof( ankleJoints ) ); + savefile->Read( kneeJoints, sizeof( kneeJoints ) ); + savefile->Read( hipJoints, sizeof( hipJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + savefile->Read( &waistJoint, sizeof( waistJoint ) ); + + savefile->Read( hipForward, sizeof( hipForward ) ); + savefile->Read( kneeForward, sizeof( kneeForward ) ); + + savefile->Read( upperLegLength, sizeof( upperLegLength ) ); + savefile->Read( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Read( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Read( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->ReadFloat( smoothing ); + savefile->ReadFloat( waistSmoothing ); + savefile->ReadFloat( footShift ); + savefile->ReadFloat( waistShift ); + savefile->ReadFloat( minWaistFloorDist ); + savefile->ReadFloat( minWaistAnkleDist ); + savefile->ReadFloat( footUpTrace ); + savefile->ReadFloat( footDownTrace ); + savefile->ReadBool( tiltWaist ); + savefile->ReadBool( usePivot ); + + savefile->ReadInt( pivotFoot ); + savefile->ReadFloat( pivotYaw ); + savefile->ReadVec3( pivotPos ); + savefile->ReadBool( oldHeightsValid ); + savefile->ReadFloat( oldWaistHeight ); + savefile->Read( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->ReadVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Init +================ +*/ +bool idIK_Walk::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + float footSize; + idVec3 verts[4]; + idTraceModel trm; + const char *jointName; + idVec3 dir, ankleOrigin, kneeOrigin, hipOrigin, dirOrigin; + idMat3 axis, ankleAxis, kneeAxis, hipAxis; + + static idVec3 footWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !self ) { + return false; + } + + numLegs = Min( self->spawnArgs.GetInt( "ik_numLegs", "0" ), MAX_LEGS ); + if ( numLegs == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledLegs = 0; + + // get all the joints + for ( i = 0; i < numLegs; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_foot%d", i+1 ) ); + footJoints[i] = animator->GetJointHandle( jointName ); + if ( footJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_ankle%d", i+1 ) ); + ankleJoints[i] = animator->GetJointHandle( jointName ); + if ( ankleJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_knee%d", i+1 ) ); + kneeJoints[i] = animator->GetJointHandle( jointName ); + if ( kneeJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_hip%d", i+1 ) ); + hipJoints[i] = animator->GetJointHandle( jointName ); + if ( hipJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_dir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledLegs |= 1 << i; + } + + jointName = self->spawnArgs.GetString( "ik_waist" ); + waistJoint = animator->GetJointHandle( jointName ); + if ( waistJoint == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'\n", jointName ); + } + + // get the leg bone lengths and rotation matrices + for ( i = 0; i < numLegs; i++ ) { + oldAnkleHeights[i] = 0.0f; + + ankleAxis = joints[ ankleJoints[ i ] ].ToMat3(); + ankleOrigin = joints[ ankleJoints[ i ] ].ToVec3(); + + kneeAxis = joints[ kneeJoints[ i ] ].ToMat3(); + kneeOrigin = joints[ kneeJoints[ i ] ].ToVec3(); + + hipAxis = joints[ hipJoints[ i ] ].ToMat3(); + hipOrigin = joints[ hipJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - kneeOrigin; + } else { + dir.Set( 1.0f, 0.0f, 0.0f ); + } + + hipForward[i] = dir * hipAxis.Transpose(); + kneeForward[i] = dir * kneeAxis.Transpose(); + + // conversion from upper leg bone axis to hip joint axis + upperLegLength[i] = GetBoneAxis( hipOrigin, kneeOrigin, dir, axis ); + upperLegToHipJoint[i] = hipAxis * axis.Transpose(); + + // conversion from lower leg bone axis to knee joint axis + lowerLegLength[i] = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); + lowerLegToKneeJoint[i] = kneeAxis * axis.Transpose(); + } + + smoothing = self->spawnArgs.GetFloat( "ik_smoothing", "0.75" ); + waistSmoothing = self->spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); + footShift = self->spawnArgs.GetFloat( "ik_footShift", "0" ); + waistShift = self->spawnArgs.GetFloat( "ik_waistShift", "0" ); + minWaistFloorDist = self->spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); + minWaistAnkleDist = self->spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); + footUpTrace = self->spawnArgs.GetFloat( "ik_footUpTrace", "32" ); + footDownTrace = self->spawnArgs.GetFloat( "ik_footDownTrace", "32" ); + tiltWaist = self->spawnArgs.GetBool( "ik_tiltWaist", "0" ); + usePivot = self->spawnArgs.GetBool( "ik_usePivot", "0" ); + + // setup a clip model for the feet + footSize = self->spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; + if ( footSize > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + verts[i] = footWinding[i] * footSize; + } + trm.SetupPolygon( verts, 4 ); + footModel = new idClipModel( trm ); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Walk::Evaluate +================ +*/ +void idIK_Walk::Evaluate( void ) { + int i, newPivotFoot = 0; + float modelHeight, jointHeight, lowestHeight, floorHeights[MAX_LEGS]; + float shift, smallestShift, newHeight, step, newPivotYaw, height, largestAnkleHeight; + idVec3 modelOrigin, normal, hipDir, kneeDir, start, end, jointOrigins[MAX_LEGS]; + idVec3 footOrigin, ankleOrigin, kneeOrigin, hipOrigin, waistOrigin; + idMat3 modelAxis, waistAxis, axis; + idMat3 hipAxis[MAX_LEGS], kneeAxis[MAX_LEGS], ankleAxis[MAX_LEGS]; + trace_t results; + + if ( !self || !gameLocal.isNewFrame ) { + return; + } + + // if no IK enabled on any legs + if ( !enabledLegs ) { + return; + } + + normal = - self->GetPhysics()->GetGravityNormal(); + modelOrigin = self->GetPhysics()->GetOrigin(); + modelAxis = self->GetRenderEntity()->axis; + modelHeight = modelOrigin * normal; + + modelOrigin += modelOffset * modelAxis; + + // create frame without joint mods + animator->CreateFrame( gameLocal.time, false ); + + // get the joint positions for the feet + lowestHeight = idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + animator->GetJointTransform( footJoints[i], gameLocal.time, footOrigin, axis ); + jointOrigins[i] = modelOrigin + footOrigin * modelAxis; + jointHeight = jointOrigins[i] * normal; + if ( jointHeight < lowestHeight ) { + lowestHeight = jointHeight; + newPivotFoot = i; + } + } + + if ( usePivot ) { + + newPivotYaw = modelAxis[0].ToYaw(); + + // change pivot foot + if ( newPivotFoot != pivotFoot || idMath::Fabs( idMath::AngleNormalize180( newPivotYaw - pivotYaw ) ) > 30.0f ) { + pivotFoot = newPivotFoot; + pivotYaw = newPivotYaw; + animator->GetJointTransform( footJoints[pivotFoot], gameLocal.time, footOrigin, axis ); + pivotPos = modelOrigin + footOrigin * modelAxis; + } + + // keep pivot foot in place + jointOrigins[pivotFoot] = pivotPos; + } + + // get the floor heights for the feet + for ( i = 0; i < numLegs; i++ ) { + + if ( !( enabledLegs & ( 1 << i ) ) ) { + continue; + } + + start = jointOrigins[i] + normal * footUpTrace; + end = jointOrigins[i] - normal * footDownTrace; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, mat3_identity, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + floorHeights[i] = results.endpos * normal; + + if ( ik_debug.GetBool() && footModel ) { + idFixedWinding w; + for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { + w += footModel->GetTraceModel()->verts[j]; + } + gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); + } + } + + const idPhysics *phys = self->GetPhysics(); + + // test whether or not the character standing on the ground + bool onGround = phys->HasGroundContacts(); + + // test whether or not the character is standing on a plat + bool onPlat = false; + for ( i = 0; i < phys->GetNumContacts(); i++ ) { + idEntity *ent = gameLocal.entities[ phys->GetContact( i ).entityNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != NULL && ent->IsType( idPlat::GetClassType() ) ) { +// RAVEN END + onPlat = true; + break; + } + } + + // adjust heights of the ankles + smallestShift = idMath::INFINITY; + largestAnkleHeight = -idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + + if ( onGround && ( enabledLegs & ( 1 << i ) ) ) { + shift = floorHeights[i] - modelHeight + footShift; + } else { + shift = 0.0f; + } + + if ( shift < smallestShift ) { + smallestShift = shift; + } + + animator->GetJointTransform( ankleJoints[i], gameLocal.time, ankleOrigin, ankleAxis[i] ); + jointOrigins[i] = modelOrigin + ankleOrigin * modelAxis; + + height = jointOrigins[i] * normal; + + if ( oldHeightsValid && !onPlat ) { + step = height + shift - oldAnkleHeights[i]; + shift -= smoothing * step; + } + + newHeight = height + shift; + if ( newHeight > largestAnkleHeight ) { + largestAnkleHeight = newHeight; + } + + oldAnkleHeights[i] = newHeight; + + jointOrigins[i] += shift * normal; + } + + animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin, waistAxis ); + waistOrigin = modelOrigin + waistOrigin * modelAxis; + + // adjust position of the waist + waistOffset = ( smallestShift + waistShift ) * normal; + + // if the waist should be at least a certain distance above the floor + if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { + start = waistOrigin; + end = waistOrigin + waistOffset - normal * minWaistFloorDist; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, modelAxis, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + height = ( waistOrigin + waistOffset - results.endpos ) * normal; + if ( height < minWaistFloorDist ) { + waistOffset += ( minWaistFloorDist - height ) * normal; + } + } + + // if the waist should be at least a certain distance above the ankles + if ( minWaistAnkleDist > 0.0f ) { + height = ( waistOrigin + waistOffset ) * normal; + if ( height - largestAnkleHeight < minWaistAnkleDist ) { + waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; + } + } + + if ( oldHeightsValid ) { + // smoothly adjust height of waist + newHeight = ( waistOrigin + waistOffset ) * normal; + step = newHeight - oldWaistHeight; + waistOffset -= waistSmoothing * step * normal; + } + + // save height of waist for smoothing + oldWaistHeight = ( waistOrigin + waistOffset ) * normal; + + if ( !oldHeightsValid ) { + oldHeightsValid = true; + return; + } + + // solve IK + for ( i = 0; i < numLegs; i++ ) { + + // get the position of the hip in world space + animator->GetJointTransform( hipJoints[i], gameLocal.time, hipOrigin, axis ); + hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; + hipDir = hipForward[i] * axis * modelAxis; + + // get the IK bend direction + animator->GetJointTransform( kneeJoints[i], gameLocal.time, kneeOrigin, axis ); + kneeDir = kneeForward[i] * axis * modelAxis; + + // solve IK and calculate knee position + SolveTwoBones( hipOrigin, jointOrigins[i], kneeDir, upperLegLength[i], lowerLegLength[i], kneeOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, hipOrigin, kneeOrigin ); + gameRenderWorld->DebugLine( colorRed, kneeOrigin, jointOrigins[i] ); + gameRenderWorld->DebugLine( colorYellow, kneeOrigin, kneeOrigin + hipDir ); + gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + kneeDir ); + } + + // get the axis for the hip joint + GetBoneAxis( hipOrigin, kneeOrigin, hipDir, axis ); + hipAxis[i] = upperLegToHipJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the knee joint + GetBoneAxis( kneeOrigin, jointOrigins[i], kneeDir, axis ); + kneeAxis[i] = lowerLegToKneeJoint[i] * ( axis * modelAxis.Transpose() ); + } + + // set the joint mods + animator->SetJointAxis( waistJoint, JOINTMOD_WORLD_OVERRIDE, waistAxis ); + animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_WORLD_OVERRIDE, hipAxis[i] ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_WORLD_OVERRIDE, kneeAxis[i] ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_WORLD_OVERRIDE, ankleAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Walk::ClearJointMods +================ +*/ +void idIK_Walk::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + animator->SetJointAxis( waistJoint, JOINTMOD_NONE, mat3_identity ); + animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} + +/* +================ +idIK_Walk::EnableAll +================ +*/ +void idIK_Walk::EnableAll( void ) { + enabledLegs = ( 1 << numLegs ) - 1; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::DisableAll +================ +*/ +void idIK_Walk::DisableAll( void ) { + enabledLegs = 0; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::EnableLeg +================ +*/ +void idIK_Walk::EnableLeg( int num ) { + enabledLegs |= 1 << num; +} + +/* +================ +idIK_Walk::DisableLeg +================ +*/ +void idIK_Walk::DisableLeg( int num ) { + enabledLegs &= ~( 1 << num ); +} + + +/* +=============================================================================== + + idIK_Reach + +=============================================================================== +*/ + +/* +================ +idIK_Reach::idIK_Reach +================ +*/ +idIK_Reach::idIK_Reach() { + int i; + + initialized = false; + numArms = 0; + enabledArms = 0; + for ( i = 0; i < MAX_ARMS; i++ ) { + handJoints[i] = INVALID_JOINT; + elbowJoints[i] = INVALID_JOINT; + shoulderJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + shoulderForward[i].Zero(); + elbowForward[i].Zero(); + upperArmLength[i] = 0.0f; + lowerArmLength[i] = 0.0f; + upperArmToShoulderJoint[i].Identity(); + lowerArmToElbowJoint[i].Identity(); + } +} + +/* +================ +idIK_Reach::~idIK_Reach +================ +*/ +idIK_Reach::~idIK_Reach() { +} + +/* +================ +idIK_Reach::Save +================ +*/ +void idIK_Reach::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteInt( numArms ); + savefile->WriteInt( enabledArms ); + savefile->Write( handJoints, sizeof( handJoints ) ); + savefile->Write( elbowJoints, sizeof( elbowJoints ) ); + savefile->Write( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + + savefile->Write( shoulderForward, sizeof( shoulderForward ) ); + savefile->Write( elbowForward, sizeof( elbowForward ) ); + + savefile->Write( upperArmLength, sizeof( upperArmLength ) ); + savefile->Write( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Write( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Write( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Restore +================ +*/ +void idIK_Reach::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadInt( numArms ); + savefile->ReadInt( enabledArms ); + savefile->Read( handJoints, sizeof( handJoints ) ); + savefile->Read( elbowJoints, sizeof( elbowJoints ) ); + savefile->Read( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + + savefile->Read( shoulderForward, sizeof( shoulderForward ) ); + savefile->Read( elbowForward, sizeof( elbowForward ) ); + + savefile->Read( upperArmLength, sizeof( upperArmLength ) ); + savefile->Read( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Read( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Read( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Init +================ +*/ +bool idIK_Reach::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + const char *jointName; + idTraceModel trm; + idVec3 dir, handOrigin, elbowOrigin, shoulderOrigin, dirOrigin; + idMat3 axis, handAxis, elbowAxis, shoulderAxis; + + if ( !self ) { + return false; + } + + numArms = Min( self->spawnArgs.GetInt( "ik_numArms", "0" ), MAX_ARMS ); + if ( numArms == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledArms = 0; + + // get all the joints + for ( i = 0; i < numArms; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_hand%d", i+1 ) ); + handJoints[i] = animator->GetJointHandle( jointName ); + if ( handJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid hand joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbow%d", i+1 ) ); + elbowJoints[i] = animator->GetJointHandle( jointName ); + if ( elbowJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid elbow joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_shoulder%d", i+1 ) ); + shoulderJoints[i] = animator->GetJointHandle( jointName ); + if ( shoulderJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid shoulder joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbowDir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledArms |= 1 << i; + } + + // get the arm bone lengths and rotation matrices + for ( i = 0; i < numArms; i++ ) { + + handAxis = joints[ handJoints[ i ] ].ToMat3(); + handOrigin = joints[ handJoints[ i ] ].ToVec3(); + + elbowAxis = joints[ elbowJoints[ i ] ].ToMat3(); + elbowOrigin = joints[ elbowJoints[ i ] ].ToVec3(); + + shoulderAxis = joints[ shoulderJoints[ i ] ].ToMat3(); + shoulderOrigin = joints[ shoulderJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - elbowOrigin; + } else { + dir.Set( -1.0f, 0.0f, 0.0f ); + } + + shoulderForward[i] = dir * shoulderAxis.Transpose(); + elbowForward[i] = dir * elbowAxis.Transpose(); + + // conversion from upper arm bone axis to should joint axis + upperArmLength[i] = GetBoneAxis( shoulderOrigin, elbowOrigin, dir, axis ); + upperArmToShoulderJoint[i] = shoulderAxis * axis.Transpose(); + + // conversion from lower arm bone axis to elbow joint axis + lowerArmLength[i] = GetBoneAxis( elbowOrigin, handOrigin, dir, axis ); + lowerArmToElbowJoint[i] = elbowAxis * axis.Transpose(); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Reach::Evaluate +================ +*/ +void idIK_Reach::Evaluate( void ) { + int i; + idVec3 modelOrigin, shoulderOrigin, elbowOrigin, handOrigin, shoulderDir, elbowDir; + idMat3 modelAxis, axis; + idMat3 shoulderAxis[MAX_ARMS], elbowAxis[MAX_ARMS]; + trace_t trace; + + modelOrigin = self->GetRenderEntity()->origin; + modelAxis = self->GetRenderEntity()->axis; + + // solve IK + for ( i = 0; i < numArms; i++ ) { + + // get the position of the shoulder in world space + animator->GetJointTransform( shoulderJoints[i], gameLocal.time, shoulderOrigin, axis ); + shoulderOrigin = modelOrigin + shoulderOrigin * modelAxis; + shoulderDir = shoulderForward[i] * axis * modelAxis; + + // get the position of the hand in world space + animator->GetJointTransform( handJoints[i], gameLocal.time, handOrigin, axis ); + handOrigin = modelOrigin + handOrigin * modelAxis; + + // get first collision going from shoulder to hand +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( self, trace, shoulderOrigin, handOrigin, CONTENTS_SOLID, self ); +// RAVEN END + handOrigin = trace.endpos; + + // get the IK bend direction + animator->GetJointTransform( elbowJoints[i], gameLocal.time, elbowOrigin, axis ); + elbowDir = elbowForward[i] * axis * modelAxis; + + // solve IK and calculate elbow position + SolveTwoBones( shoulderOrigin, handOrigin, elbowDir, upperArmLength[i], lowerArmLength[i], elbowOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, shoulderOrigin, elbowOrigin ); + gameRenderWorld->DebugLine( colorRed, elbowOrigin, handOrigin ); + gameRenderWorld->DebugLine( colorYellow, elbowOrigin, elbowOrigin + elbowDir ); + gameRenderWorld->DebugLine( colorGreen, elbowOrigin, elbowOrigin + shoulderDir ); + } + + // get the axis for the shoulder joint + GetBoneAxis( shoulderOrigin, elbowOrigin, shoulderDir, axis ); + shoulderAxis[i] = upperArmToShoulderJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the elbow joint + GetBoneAxis( elbowOrigin, handOrigin, elbowDir, axis ); + elbowAxis[i] = lowerArmToElbowJoint[i] * ( axis * modelAxis.Transpose() ); + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_WORLD_OVERRIDE, shoulderAxis[i] ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_WORLD_OVERRIDE, elbowAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Reach::ClearJointMods +================ +*/ +void idIK_Reach::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( handJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} diff --git a/source/mpgame/IK.h b/source/mpgame/IK.h new file mode 100644 index 0000000..f426e72 --- /dev/null +++ b/source/mpgame/IK.h @@ -0,0 +1,155 @@ + +#ifndef __GAME_IK_H__ +#define __GAME_IK_H__ + +/* +=============================================================================== + + IK base class with a simple fast two bone solver. + +=============================================================================== +*/ + +#define IK_ANIM "ik_pose" + +class idIK { +public: + idIK( void ); + virtual ~idIK( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsInitialized( void ) const; + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + bool SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ); + float GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ); + +protected: + bool initialized; + bool ik_activate; + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // animation modified by the IK + idVec3 modelOffset; +}; + + +/* +=============================================================================== + + IK controller for a walking character with an arbitrary number of legs. + +=============================================================================== +*/ + +class idIK_Walk : public idIK { +public: + + idIK_Walk( void ); + virtual ~idIK_Walk( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + void EnableAll( void ); + void DisableAll( void ); + void EnableLeg( int num ); + void DisableLeg( int num ); + +private: + static const int MAX_LEGS = 8; + + idClipModel * footModel; + + int numLegs; + int enabledLegs; + jointHandle_t footJoints[MAX_LEGS]; + jointHandle_t ankleJoints[MAX_LEGS]; + jointHandle_t kneeJoints[MAX_LEGS]; + jointHandle_t hipJoints[MAX_LEGS]; + jointHandle_t dirJoints[MAX_LEGS]; + jointHandle_t waistJoint; + + idVec3 hipForward[MAX_LEGS]; + idVec3 kneeForward[MAX_LEGS]; + + float upperLegLength[MAX_LEGS]; + float lowerLegLength[MAX_LEGS]; + + idMat3 upperLegToHipJoint[MAX_LEGS]; + idMat3 lowerLegToKneeJoint[MAX_LEGS]; + + float smoothing; + float waistSmoothing; + float footShift; + float waistShift; + float minWaistFloorDist; + float minWaistAnkleDist; + float footUpTrace; + float footDownTrace; + bool tiltWaist; + bool usePivot; + + // state + int pivotFoot; + float pivotYaw; + idVec3 pivotPos; + bool oldHeightsValid; + float oldWaistHeight; + float oldAnkleHeights[MAX_LEGS]; + idVec3 waistOffset; +}; + + +/* +=============================================================================== + + IK controller for reaching a position with an arm or leg. + +=============================================================================== +*/ + +class idIK_Reach : public idIK { +public: + + idIK_Reach( void ); + virtual ~idIK_Reach( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + +private: + + static const int MAX_ARMS = 2; + + int numArms; + int enabledArms; + jointHandle_t handJoints[MAX_ARMS]; + jointHandle_t elbowJoints[MAX_ARMS]; + jointHandle_t shoulderJoints[MAX_ARMS]; + jointHandle_t dirJoints[MAX_ARMS]; + + idVec3 shoulderForward[MAX_ARMS]; + idVec3 elbowForward[MAX_ARMS]; + + float upperArmLength[MAX_ARMS]; + float lowerArmLength[MAX_ARMS]; + + idMat3 upperArmToShoulderJoint[MAX_ARMS]; + idMat3 lowerArmToElbowJoint[MAX_ARMS]; +}; + +#endif /* !__GAME_IK_H__ */ diff --git a/source/mpgame/Icon.cpp b/source/mpgame/Icon.cpp new file mode 100644 index 0000000..e89c8f8 --- /dev/null +++ b/source/mpgame/Icon.cpp @@ -0,0 +1,96 @@ +//---------------------------------------------------------------- +// Icon.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Icon.h" + +/* +=============== +rvIcon::rvIcon +=============== +*/ +rvIcon::rvIcon() { + iconHandle = -1; +} + +/* +=============== +rvIcon::~rvIcon +=============== +*/ +rvIcon::~rvIcon() { + FreeIcon(); +} + +/* +=============== +rvIcon::FreeIcon +=============== +*/ +void rvIcon::FreeIcon( void ) { + if ( iconHandle != - 1 ) { + gameRenderWorld->FreeEntityDef( iconHandle ); + iconHandle = -1; + } +} + +/* +=============== +rvIcon::CreateIcon +=============== +*/ +qhandle_t rvIcon::CreateIcon( const char *mtr, int suppressViewID ) { + FreeIcon(); + + memset( &renderEnt, 0, sizeof( renderEnt ) ); + renderEnt.origin = vec3_origin; + renderEnt.axis = mat3_identity; + renderEnt.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 16.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 16.0f; + renderEnt.hModel = renderModelManager->FindModel( "_sprite" ); + renderEnt.callback = NULL; + renderEnt.numJoints = 0; + renderEnt.joints = NULL; + renderEnt.customSkin = 0; + renderEnt.noShadow = true; + renderEnt.noSelfShadow = true; + renderEnt.customShader = declManager->FindMaterial( mtr ); + renderEnt.referenceShader = 0; + renderEnt.bounds = renderEnt.hModel->Bounds( &renderEnt ); + renderEnt.suppressSurfaceInViewID = suppressViewID; + + iconHandle = gameRenderWorld->AddEntityDef( &renderEnt ); + + return iconHandle; +} + +/* +=============== +rvIcon::UpdateIcon +=============== +*/ +void rvIcon::UpdateIcon( const idVec3 &origin, const idMat3 &axis ) { + assert( iconHandle >= 0 ); + + renderEnt.origin = origin; + renderEnt.axis = axis; + gameRenderWorld->UpdateEntityDef( iconHandle, &renderEnt ); +} + +int rvIcon::GetWidth( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ]; +} + +int rvIcon::GetHeight( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ]; +} diff --git a/source/mpgame/Icon.h b/source/mpgame/Icon.h new file mode 100644 index 0000000..7d2a532 --- /dev/null +++ b/source/mpgame/Icon.h @@ -0,0 +1,34 @@ +//---------------------------------------------------------------- +// Icon.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICON_H__ +#define __ICON_H__ + +class rvIcon { +public: + rvIcon(); + ~rvIcon(); + void UpdateIcon( const idVec3 &origin, const idMat3 &axis ); + qhandle_t CreateIcon( const char *mtr, int suppressViewID = 0 ); + void FreeIcon( void ); + qhandle_t GetHandle( void ) const; + + int GetHeight( void ) const; + int GetWidth( void ) const; +private: + void Draw( jointHandle_t joint ); + void Draw( const idVec3 &origin ); + + renderEntity_t renderEnt; + qhandle_t iconHandle; +}; + +ID_INLINE qhandle_t rvIcon::GetHandle( void ) const { + return iconHandle; +} + +#endif /* !_ICON_H_ */ + diff --git a/source/mpgame/IconManager.cpp b/source/mpgame/IconManager.cpp new file mode 100644 index 0000000..66c012f --- /dev/null +++ b/source/mpgame/IconManager.cpp @@ -0,0 +1,205 @@ +//---------------------------------------------------------------- +// IconManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "IconManager.h" + +rvIconManager iconManagerLocal; +rvIconManager* iconManager = &iconManagerLocal; + +/* +=============================================================================== + + rvIconManager + +=============================================================================== +*/ + +void rvIconManager::AddIcon( int clientNum, const char* iconName ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + icons[ clientNum ].Append( rvPair(new rvIcon(), gameLocal.time + ICON_STAY_TIME) ); + icons[ clientNum ][ icons[ clientNum ].Num() - 1 ].First()->CreateIcon( player->spawnArgs.GetString( iconName ), (clientNum == gameLocal.localClientNum ? gameLocal.localClientNum + 1 : 0) ); +} + +void rvIconManager::UpdateIcons( void ) { + if( gameLocal.GetLocalPlayer() == NULL || !gameLocal.GetLocalPlayer()->GetRenderView() ) { + return; + } + + // draw team icons + if( gameLocal.IsTeamGame() ) { + UpdateTeamIcons(); + } + + // draw chat icons + UpdateChatIcons(); + + // remove old icons and icons not in our snapshot + // ** if you want to have permanent icons, you'll have to add support + // ** for the icons to re-appear when the player comes back in your snapshot (like team icons and chat icons) + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( gameLocal.time > icons[ i ][ j ].Second() || (gameLocal.entities[ i ] && gameLocal.entities[ i ]->fl.networkStale) ) { + rvIcon* oldIcon = icons[ i ][ j ].First(); + oldIcon->FreeIcon(); + icons[ i ].RemoveIndex( j-- ); + delete oldIcon; + } + } + } + + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + // draw extra icons + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + if( player->IsHidden() || !icons[ i ].Num() ) { + continue; + } + + // distribute the icons appropriately + int maxHeight = 0; + int totalWidth = 0; + + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + totalWidth += icons[ i ][ j ].First()->GetWidth(); + } + + idVec3 centerIconPosition = player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + + + if( teamIcons[ player->entityNumber ].GetHandle() >= 0 ) { + centerIconPosition[ 2 ] += teamIcons[ player->entityNumber ].GetHeight(); + } + + int incrementalWidth = 0; + for( int j = 0; j < icons[ i ].Num(); j++ ) { + idVec3 iconPosition = centerIconPosition; + iconPosition += ( (-totalWidth / 2) + incrementalWidth + (icons[ i ][ j ].First()->GetWidth() / 2) ) * localPlayer->GetRenderView()->viewaxis[ 1 ]; + incrementalWidth += icons[ i ][ j ].First()->GetWidth(); + icons[ i ][ j ].First()->UpdateIcon( player->GetPhysics()->GetOrigin() + iconPosition, localPlayer->GetRenderView()->viewaxis ); + } + } + } +} + +void rvIconManager::UpdateTeamIcons( void ) { + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return; + } + int localTeam = localPlayer->team; + bool spectating = localPlayer->spectating; + + if( localPlayer->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ localPlayer->spectator ]; + if( spec ) { + localTeam = spec->team; + localPlayer = spec; + } else { + localTeam = -1; + } + } + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + //if entity i is a player, manage his icon. + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale && !spectating ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + //if the player is alive and not hidden, show his icon. + if( player->team == localTeam && !player->IsHidden() && !player->pfl.dead && gameLocal.mpGame.IsInGame( i ) ) { + if( teamIcons[ i ].GetHandle() < 0 ) { + teamIcons[ i ].CreateIcon( player->spawnArgs.GetString( player->team ? "mtr_team_icon_strogg" : "mtr_team_icon_marine" ), (player == localPlayer ? localPlayer->entityNumber + 1 : 0) ); + } + teamIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ), localPlayer->GetRenderView()->viewaxis ); + //else, the player is hidden, dead, or otherwise not needing an icon-- free it. + } else { + if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } + //if entity i is not a player, free icon i from the map. + } else if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } +} + +void rvIconManager::UpdateChatIcons( void ) { + + int localInst = gameLocal.GetLocalPlayer()->GetInstance(); + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( gameLocal.localClientNum == i ) { + continue; + } + + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[ i ] ); + + if ( player->isChatting && + !player->IsHidden() && + !( ( idPhysics_Player* )player->GetPhysics() )->IsDead() && + gameLocal.mpGame.IsInGame( i ) + && (localInst == player->GetInstance())) { + if ( chatIcons[ i ].GetHandle() < 0 ) { + chatIcons[ i ].CreateIcon( player->spawnArgs.GetString( "mtr_icon_chatting" ), ( player == gameLocal.GetLocalPlayer() ? player->entityNumber + 1 : 0) ); + } + int maxHeight = 0; + for ( int j = 0; j < icons[ i ].Num(); j++ ) { + if ( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + } + if ( teamIcons[ i ].GetHandle() >= 0 && teamIcons[ i ].GetHeight() > maxHeight ) { + maxHeight = teamIcons[ i ].GetHeight(); + } + idVec3 centerIconPosition = player->spawnArgs.GetVector( ( player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + centerIconPosition[ 2 ] += maxHeight; + chatIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + centerIconPosition, gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis ); + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } +} + +/* +=============== +rvIconManager::Shutdown +=============== +*/ +void rvIconManager::Shutdown( void ) { + int i, j; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < icons[ i ].Num(); j++ ) { + icons[ i ][ j ].First()->FreeIcon(); + } + icons[ i ].Clear(); + teamIcons[ i ].FreeIcon(); + chatIcons[ i ].FreeIcon(); + } +} diff --git a/source/mpgame/IconManager.h b/source/mpgame/IconManager.h new file mode 100644 index 0000000..b48f9f9 --- /dev/null +++ b/source/mpgame/IconManager.h @@ -0,0 +1,30 @@ +//---------------------------------------------------------------- +// IconManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICONMANAGER_H__ +#define __ICONMANAGER_H__ + +#include "Icon.h" + +const int ICON_STAY_TIME = 2000; + +class rvIconManager { +public: + void AddIcon( int clientNum, const char* iconName ); + void UpdateIcons( void ); + void UpdateTeamIcons( void ); + void UpdateChatIcons( void ); + void Shutdown( void ); + +private: + idList > icons[ MAX_CLIENTS ]; + rvIcon teamIcons[ MAX_CLIENTS ]; + rvIcon chatIcons[ MAX_CLIENTS ]; +}; + +extern rvIconManager* iconManager; + +#endif diff --git a/source/mpgame/Instance.cpp b/source/mpgame/Instance.cpp new file mode 100644 index 0000000..4e4a1ab --- /dev/null +++ b/source/mpgame/Instance.cpp @@ -0,0 +1,201 @@ +//---------------------------------------------------------------- +// Instance.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Instance.h" + +rvInstance::rvInstance( int id, bool deferPopulate ) { + instanceID = id; + spawnInstanceID = id; + + gameLocal.AddClipWorld( id ); + + mapEntityNumbers = NULL; + numMapEntities = 0; + + initialSpawnCount = idGameLocal::INITIAL_SPAWN_COUNT; + + if ( !deferPopulate ) { + Populate(); + } +} + +rvInstance::~rvInstance() { + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + } + gameLocal.RemoveClipWorld( instanceID ); +} + +void rvInstance::Populate( int serverChecksum ) { + gameState_t currentState = gameLocal.GameState(); + + // disable the minSpawnIndex lock out + int latchMinSpawnIndex = gameLocal.minSpawnIndex; + gameLocal.minSpawnIndex = MAX_CLIENTS; + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( GAMESTATE_RESTART ); + } + + if ( gameLocal.isServer ) { + // When populating on a server, record the entity numbers + numMapEntities = gameLocal.GetNumMapEntities(); + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + } + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + // read the index we should start populating at as transmitted by the server + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + //common->Printf( "pos: get starting index for instance %d sets firstFreeIndex to %d\n", instanceID, gameLocal.firstFreeIndex ); + + // remember the spawnCount ahead of time, so that the client can accurately reconstruct its spawnIds + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers, &initialSpawnCount ); + + // only build the message in MP + if ( gameLocal.isMultiplayer ) { + BuildInstanceMessage(); + + // force joins of anyone in our instance so they get potentially new map entitynumbers + for( int i = 0; i < MAX_CLIENTS; i++ ) { + PACIFIER_UPDATE; + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player && player->GetInstance() == instanceID ) { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg, true ); + } + } + } + } else { + // have the client produce a log of the entity layout so we can match it with the server's + // this is also going to be used to issue the EV_FindTargets below + if ( mapEntityNumbers ) { + delete []mapEntityNumbers; + } + numMapEntities = gameLocal.GetNumMapEntities(); + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + + gameLocal.SetSpawnCount( initialSpawnCount ); // that was transmitted through the instance msg + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers ); + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + if ( serverChecksum != 0 && checksum != serverChecksum ) { + common->Error( "client side map populate checksum ( 0x%x ) doesn't match server's ( 0x%x )", checksum, serverChecksum ); + } + } + + + for ( int i = 0; i < numMapEntities; i++ ) { + if ( mapEntityNumbers[ i ] < 0 || mapEntityNumbers[ i ] >= MAX_GENTITIES ) { + continue; + } + + if ( (i % 100) == 0 ) { + PACIFIER_UPDATE; + } + + idEntity* ent = gameLocal.entities[ mapEntityNumbers[ i ] ]; + + if ( ent ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + ent->PostEventMS( &EV_PostSpawn, 0 ); + } + } + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( currentState ); + } + + // re-enable the min spawn index + assert( latchMinSpawnIndex == MAX_CLIENTS || gameLocal.firstFreeIndex <= latchMinSpawnIndex ); + gameLocal.minSpawnIndex = latchMinSpawnIndex; +} + +void rvInstance::PopulateFromMessage( const idBitMsg& msg ) { + initialSpawnCount = msg.ReadShort(); + + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + + int populateIndex = msg.ReadLong(); + gameLocal.ClientSetStartingIndex( populateIndex ); + //common->Printf( "pos: set firstFreeIndex to %d\n", populateIndex ); + int checksum = msg.ReadLong(); + Populate( checksum ); +} + +void rvInstance::Restart( void ) { + if ( gameLocal.isMultiplayer ) { + Populate(); + } else { + gameLocal.SpawnMapEntities(); + } +} + +void rvInstance::BuildInstanceMessage( void ) { + // Build the client join instance msg + mapEntityMsg.BeginWriting(); + mapEntityMsg.Init( mapEntityMsgBuf, sizeof( byte ) * MAX_GAME_MESSAGE_SIZE ); + mapEntityMsg.WriteByte( GAME_RELIABLE_MESSAGE_SET_INSTANCE ); + + mapEntityMsg.WriteByte( instanceID ); + mapEntityMsg.WriteShort( initialSpawnCount ); + // we need to send that down for tourney so the index will match + mapEntityMsg.WriteLong( gameLocal.GetStartingIndexForInstance( instanceID ) ); + + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + //common->Printf( "pop: server checksum: 0x%x\n", checksum ); + mapEntityMsg.WriteLong( checksum ); +} + +void rvInstance::JoinInstance( idPlayer* player ) { + assert( player && gameLocal.isServer ); + + // Transmit the instance information to the new client + if( gameLocal.isListenServer && player == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ServerSetInstance( instanceID ); + } else { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg, true ); + } +} + +void rvInstance::PrintMapNumbers( void ) { + gameLocal.Printf( "Instance: %d\n", instanceID ); + gameLocal.Printf( "Num Map Entities: %d\n", numMapEntities ); + + for( int i = 0; i < numMapEntities; i++ ) { + gameLocal.Printf( "%d\n", mapEntityNumbers[ i ] ); + } +} + +/* +================ +rvInstance::SetSpawnInstanceID +Sets the spawn instance ID for this instance. On the client, only instance 0 is ever +used, but it spawns in map entities for other instances. spawnInstanceID is used to +spawn map entities with the correct instance number on the client. +================ +*/ +void rvInstance::SetSpawnInstanceID( int newInstance ) { + assert( gameLocal.isClient ); + + spawnInstanceID = newInstance; +} diff --git a/source/mpgame/Instance.h b/source/mpgame/Instance.h new file mode 100644 index 0000000..e452679 --- /dev/null +++ b/source/mpgame/Instance.h @@ -0,0 +1,47 @@ +//---------------------------------------------------------------- +// Instance.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include "Game_local.h" + +class rvInstance { +public: + rvInstance( int id, bool deferPopulate = false ); + ~rvInstance(); + + void Populate( int serverChecksum = 0 ); + void PopulateFromMessage( const idBitMsg& msg ); + void Restart( void ); + + void JoinInstance( idPlayer* player ); + int GetInstanceID( void ); + + void SetSpawnInstanceID( int newInstance ); + + void PrintMapNumbers( void ); + int GetNumMapEntities( void ) { return numMapEntities; } + unsigned short GetMapEntityNumber( int i ) { return mapEntityNumbers[ i ]; } + +private: + void BuildInstanceMessage( void ); + + int instanceID; + int spawnInstanceID; + unsigned short* mapEntityNumbers; + int numMapEntities; + int initialSpawnCount; + + idBitMsg mapEntityMsg; + byte mapEntityMsgBuf[ MAX_GAME_MESSAGE_SIZE ]; +}; + +ID_INLINE int rvInstance::GetInstanceID( void ) { + return instanceID; +} + +#endif diff --git a/source/mpgame/Item.cpp b/source/mpgame/Item.cpp new file mode 100644 index 0000000..09d9018 --- /dev/null +++ b/source/mpgame/Item.cpp @@ -0,0 +1,2386 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idItem + +=============================================================================== +*/ + +const idEventDef EV_DropToFloor( "" ); +const idEventDef EV_RespawnItem( "respawn" ); +const idEventDef EV_RespawnFx( "" ); +const idEventDef EV_GetPlayerPos( "" ); +const idEventDef EV_HideObjective( "", "e" ); +const idEventDef EV_CamShot( "" ); + +// RAVEN BEGIN +// abahr: +const idEventDef EV_SetGravity( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idItem ) + EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) + EVENT( EV_Touch, idItem::Event_Touch ) + EVENT( EV_Activate, idItem::Event_Trigger ) + EVENT( EV_RespawnItem, idItem::Event_Respawn ) + EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +// RAVEN BEGIN +// abahr + EVENT( EV_SetGravity, idItem::Event_SetGravity ) +// RAVEN END + +END_CLASS + + +/* +================ +idItem::idItem +================ +*/ +idItem::idItem() { + spin = false; + inView = false; + skin = NULL; + pickupSkin = NULL; + inViewTime = 0; + lastCycle = 0; + lastRenderViewTime = -1; + itemShellHandle = -1; + shellMaterial = NULL; + orgOrigin.Zero(); + canPickUp = true; + fl.networkSync = true; + trigger = NULL; + syncPhysics = false; + srvReady = -1; + clReady = -1; + effectIdle = NULL; + itemPVSArea = 0; + effectIdle = NULL; + simpleItem = false; + pickedUp = false; +} + +/* +================ +idItem::~idItem +================ +*/ +idItem::~idItem() { + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + } + if ( trigger ) { + delete trigger; + } + + SetPhysics( NULL ); +} + +/* +================ +idItem::Save +================ +*/ +void idItem::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteBool( spin ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( pickupSkin ); + + savefile->WriteVec3( orgOrigin ); + + savefile->WriteBool( pulse ); + savefile->WriteBool( canPickUp ); + + savefile->WriteStaticObject( physicsObj ); + +// savefile->WriteInt(itemShellHandle); // cnicholson: Set at end of Restore, do not save + savefile->WriteMaterial( shellMaterial ); + + savefile->WriteBool( inView ); + savefile->WriteInt( inViewTime ); + savefile->WriteInt( lastCycle ); + savefile->WriteInt( lastRenderViewTime ); +} + +/* +================ +idItem::Restore +================ +*/ +void idItem::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadBool( spin ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( pickupSkin ); + + savefile->ReadVec3( orgOrigin ); + + savefile->ReadBool( pulse ); + savefile->ReadBool( canPickUp ); + + savefile->ReadStaticObject ( physicsObj ); + +// savefile->ReadInt(itemShellHandle); // cnicholson: Set at end of function, do not restore + savefile->ReadMaterial( shellMaterial ); + + savefile->ReadBool( inView ); + savefile->ReadInt( inViewTime ); + savefile->ReadInt( lastCycle ); + savefile->ReadInt( lastRenderViewTime ); + + RestorePhysics( &physicsObj ); + + physicsObj.SetSelf( this ); + + itemShellHandle = -1; +} + +/* +================ +idItem::UpdateRenderEntity +================ +*/ +bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + if( simpleItem ) { + return false; + } + + if ( lastRenderViewTime == renderView->time ) { + return false; + } + + lastRenderViewTime = renderView->time; + + // check for glow highlighting if near the center of the view + idVec3 dir = renderEntity->origin - renderView->vieworg; + dir.Normalize(); + float d = dir * renderView->viewaxis[0]; + + // two second pulse cycle + float cycle = ( renderView->time - inViewTime ) / 2000.0f; + + if ( d > 0.94f ) { + if ( !inView ) { + inView = true; + if ( cycle > lastCycle ) { + // restart at the beginning + inViewTime = renderView->time; + cycle = 0.0f; + } + } + } else { + if ( inView ) { + inView = false; + lastCycle = ceil( cycle ); + } + } + + // fade down after the last pulse finishes + if ( !inView && cycle > lastCycle ) { + renderEntity->shaderParms[4] = 0.0f; + } else { + // pulse up in 1/4 second + cycle -= (int)cycle; + if ( cycle < 0.1f ) { + renderEntity->shaderParms[4] = cycle * 10.0f; + } else if ( cycle < 0.2f ) { + renderEntity->shaderParms[4] = 1.0f; + } else if ( cycle < 0.3f ) { + renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f; + } else { + // stay off between pulses + renderEntity->shaderParms[4] = 0.0f; + } + } + + // update every single time this is in view + return true; +} + +/* +================ +idItem::UpdateTrigger +================ +*/ +void idItem::UpdateTrigger ( void ) { + // update trigger position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END +} + +/* +================ +idItem::ModelCallback +================ +*/ +bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) { + const idItem *ent; + + // this may be triggered by a model trace or other non-view related source + if ( !renderView ) { + return false; + } + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + + +/* +================ +idItem::GetPhysicsToVisualTransform +================ +*/ +bool idItem::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if( simpleItem ) { + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetRenderView() ) { + if( gameLocal.GetLocalPlayer()->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ gameLocal.GetLocalPlayer()->spectator ]; + if( spec && spec->GetRenderView() ) { + axis = spec->GetRenderView()->viewaxis; + } + } else { + axis = gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis; + } + } else { + // dedicated server for instance + axis = mat3_identity; + } + origin = idVec3( 0.0f, 0.0f, simpleItemScale / 2.0f ); + return true; + } + + if( !spin || (gameLocal.isServer && !gameLocal.isListenServer) ) { + return false; + } + + idAngles ang; + ang.pitch = ang.roll = 0.0f; + ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + axis = ang.ToMat3() * GetPhysics()->GetAxis(); + + float scale = 0.005f; + float offset = entityNumber * 0.685145f; // rjohnson: just a random number here to shift the cos curve + + origin.Zero(); + + origin += GetPhysics()->GetAxis()[2] * (4.0f + idMath::Cos( ( ( gameLocal.time + 1000 ) * scale ) + offset ) * 4.0f); + + return true; +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idItem::Collide +================ +*/ +bool idItem::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_Remove, 0 ); + } + return false; +} +// RAVEN END + +/* +================ +idItem::Think +================ +*/ +void idItem::Think( void ) { + if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + UpdateTrigger(); + } + + if ( gameLocal.IsMultiplayer() && g_skipItemShadowsMP.GetBool() ) { + renderEntity.suppressShadowInViewID = gameLocal.localClientNum + 1; + } else { + renderEntity.suppressShadowInViewID = 0; + } + + if( !(simpleItem && pickedUp) ) { + UpdateVisuals(); + Present(); + } +} + +/* +================ +idItem::Present +================ +*/ +void idItem::Present( void ) { + idEntity::Present(); + + if ( !fl.hidden && pulse ) { + // also add a highlight shell model + renderEntity_t shell; + + shell = renderEntity; + + // we will mess with shader parms when the item is in view + // to give the "item pulse" effect + shell.callback = idItem::ModelCallback; + shell.entityNum = entityNumber; + shell.customShader = shellMaterial; + if ( itemShellHandle == -1 ) { + itemShellHandle = gameRenderWorld->AddEntityDef( &shell ); + } else { + gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell ); + } + } +} + +/* +================ +idItem::InstanceJoin +================ +*/ +void idItem::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::InstanceLeave +================ +*/ +void idItem::InstanceLeave( void ) { + idEntity::InstanceLeave(); + + StopEffect( "fx_idle", true ); +} + +/* +================ +idItem::Spawn +================ +*/ +void idItem::Spawn( void ) { + idStr giveTo; + idEntity * ent; + idVec3 vSize; + idBounds bounds(vec3_origin); + + // check for triggerbounds, which allows for non-square triggers (useful for, say, a CTF flag) + if ( spawnArgs.GetVector( "triggerbounds", "16 16 16", vSize )) { + bounds.AddPoint(idVec3( vSize.x*0.5f, vSize.y*0.5f, 0.0f)); + bounds.AddPoint(idVec3(-vSize.x*0.5f, -vSize.y*0.5f, vSize.z)); + } + else { + // create a square trigger for item pickup + float tsize; + spawnArgs.GetFloat( "triggersize", "16.0", tsize ); + bounds.ExpandSelf( tsize ); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds )); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + + physicsObj.SetSelf ( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID ); + physicsObj.SetFriction( 0.0f, 0.0f, 6.0f ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_off" ) ) { + trigger->SetContents( 0 ); + Hide(); + } else { + trigger->SetContents( CONTENTS_TRIGGER ); + } + + giveTo = spawnArgs.GetString( "owner" ); + if ( giveTo.Length() ) { + ent = gameLocal.FindEntity( giveTo ); + if ( !ent ) { + gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() ); + } + PostEventMS( &EV_Touch, 0, ent, NULL ); + } + + if ( spawnArgs.GetBool( "spin" ) || gameLocal.isMultiplayer ) { + spin = true; + BecomeActive( TH_THINK ); + } + + // pulse ( and therefore itemShellHandle ) was taken out and shot. do not sync + //pulse = !spawnArgs.GetBool( "nopulse" ); + pulse = false; + orgOrigin = GetPhysics()->GetOrigin(); + + canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) ); + + inViewTime = -1000; + lastCycle = -1; + itemShellHandle = -1; +// RAVEN BEGIN +// abahr: move texture to def file for precaching + shellMaterial = declManager->FindMaterial( spawnArgs.GetString("mtr_highlight", "_default") ); + PostEventMS( &EV_SetGravity, 0 ); +// RAVEN END + if ( spawnArgs.GetString( "skin", NULL ) ) { + skin = declManager->FindSkin( spawnArgs.GetString( "skin" ), false ); + if( skin ) { + SetSkin( skin ); + srvReady = 1; + } + } else { + skin = NULL; + } + + if ( spawnArgs.GetString( "skin_pickup", NULL ) ) { + pickupSkin = declManager->FindSkin( spawnArgs.GetString( "skin_pickup" ), false ); + } else { + pickupSkin = NULL; + } + + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( srvReady == -1 ) { + srvReady = IsHidden() ? 0 : 1; + } + +// RAVEN BEGIN +// mekberg: added for removing pickups in mp in pits + if ( gameLocal.isMultiplayer ) { + trigger->SetContents( trigger->GetContents() | CONTENTS_ITEMCLIP ); + } +// RAVEN END + + if( gameLocal.isMultiplayer ) { + itemPVSArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + } else { + itemPVSArea = 0; + } + + simpleItemScale = spawnArgs.GetFloat( "simple_icon_scale", "20.0" ); + + simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !IsType( rvItemCTFFlag::GetClassType() ); + if( simpleItem ) { + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = simpleItemScale; + renderEntity.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = simpleItemScale; + renderEntity.hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + renderEntity.customSkin = 0; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customShader = declManager->FindMaterial( spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity.referenceShader = 0; + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + SetAxis( mat3_identity ); + } else { + if ( spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + pickedUp = false; +} + +/* +================ +idItem::Event_SetGravity +================ +*/ +void idItem::Event_SetGravity() { + // If the item isnt a dropped item then see if it should settle itself + // to the floor or not + if ( !spawnArgs.GetBool( "dropped" ) ) { + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + PostEventMS( &EV_DropToFloor, 0 ); + } + } +} +// RAVEN END + +/* +================ +idItem::GetAttributes +================ +*/ +void idItem::GetAttributes( idDict &attributes ) { + int i; + const idKeyValue *arg; + + for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Left( 4 ) == "inv_" ) { + attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() ); + } + } +} + +/* +================ +idItem::GiveToPlayer +================ +*/ +bool idItem::GiveToPlayer( idPlayer *player ) { + if ( player == NULL ) { + return false; + } + + if ( spawnArgs.GetBool( "inv_carry" ) ) { + return player->GiveInventoryItem( &spawnArgs ); + } + + // Handle the special ammo pickup that gives ammo for the weapon the player currently has + if ( spawnArgs.GetBool( "item_currentWeaponAmmo" ) ) { + const char *ammoName = player->weapon->GetAmmoNameForIndex(player->weapon->GetAmmoType()); + if ( player->weapon->TotalAmmoCount() != player->weapon->maxAmmo && player->weapon->AmmoRequired() ) { + player->GiveItem(ammoName); + player->GiveInventoryItem( &spawnArgs ); + return true; + } + return false; + } + + return player->GiveItem( this ); +} + +/* +=============== +idItem::SendPickupMsg +=============== +*/ +void idItem::SendPickupMsg( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_PICKUP ); + msg.WriteByte( clientNum ); + + // send as unreliable to client picking up the item, so it can play HUD things + gameLocal.SendUnreliableMessagePVS( msg, this, itemPVSArea ); +} + +/* +================ +idItem::Pickup +================ +*/ +bool idItem::Pickup( idPlayer *player ) { + //dropped weapon? + bool dropped = spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && !dropped && spawnArgs.FindKey( "weaponclass" ) + && gameLocal.IsWeaponsStayOn() && gameLocal.time > player->lastPickupTime + 1000 ) { + + idDict attr; + GetAttributes( attr ); + const idKeyValue* arg = attr.FindKey( "weapon" ); + + if ( arg ) { + if ( !player->inventory.Give( player, player->spawnArgs, arg->GetKey(), arg->GetValue(), NULL, false, dropped, true ) ) { + StartSound( "snd_noacquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + } + + // only predict noacquire on client + if ( gameLocal.isClient ) { + return false; + } + + int givenToPlayer = spawnArgs.GetInt( "givenToPlayer", "-1" ); + if ( player == NULL || ( givenToPlayer != -1 && givenToPlayer != player->entityNumber ) ) { + // idPlayer::GiveItem spawns an idItem for pickup, which appears at the origin before being picked + // and could sometimes be picked by someone else, particularly in buy mode when there is a play spawn sitting on the origin ;-) + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + player->lastPickupTime = gameLocal.time; + + //if a placed item and si_weaponStay is on and we're a weapon, don't remove and respawn + if ( gameLocal.IsMultiplayer() ) { + if ( !dropped ) { + if ( spawnArgs.FindKey( "weaponclass" ) ) { + if ( gameLocal.IsWeaponsStayOn() ) { + return true; + } + } + } + } + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model, or switch to the pickup skin + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + BecomeInactive( TH_THINK ); + } + + pickedUp = true; + + // allow SetSkin or Hide() to get called regardless of simpleitem mode + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + // asalmon: Added option for a differnt respawn rate based on gametype. + float respawn = spawnArgs.GetFloat(va("respawn_%s",gameLocal.serverInfo.GetString( "si_gameType" )), "-1.0"); + if( respawn == -1.0f ) { + respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + } + + bool no_respawn = spawnArgs.GetBool( "no_respawn" ); + + if ( !gameLocal.isMultiplayer ) { + respawn = 0.0f; + } else if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( givenToPlayer != -1 ) { + respawn = 0.0f; + } + } + + if ( respawn && !dropped && !no_respawn ) { + const char *sfx = spawnArgs.GetString( "fx_Respawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, respawn - 0.5f ); + } + PostEventSec( &EV_RespawnItem, respawn ); + } else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) { + // give some time for the pickup sound to play + // FIXME: Play on the owner + if ( !spawnArgs.GetBool( "inv_carry" ) ) { + PostEventMS( &EV_Remove, 5000 ); + } + } + + trigger->SetContents( 0 ); + + StopEffect( "fx_idle" ); + + return true; +} + +/* +================ +idItem::Hide +================ +*/ +void idItem::Hide( void ) { + srvReady = 0; + idEntity::Hide( ); + trigger->SetContents( 0 ); +} + +/* +================ +idItem::Show +================ +*/ +void idItem::Show( void ) { + srvReady = 1; + idEntity::Show( ); + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +idItem::ClientStale +================ +*/ +bool idItem::ClientStale( void ) { + idEntity::ClientStale(); + + StopEffect( "fx_idle" ); + return false; +} + +/* +================ +idItem::ClientUnstale +================ +*/ +void idItem::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::ClientPredictionThink +================ +*/ +void idItem::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + +/* +================ +idItem::WriteFromSnapshot +================ +*/ +void idItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + assert( srvReady != -1 ); + msg.WriteBits( ( srvReady == 1 ), 1 ); +} + +/* +================ +idItem::ReadFromSnapshot +================ +*/ +void idItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + int newReady = ( msg.ReadBits( 1 ) != 0 ); + idVec3 resetOrigin( 0, 0, 0 ); + // client spawns the ent with ready == -1 so the state set happens at least once + if ( newReady != clReady ) { + if ( newReady ) { + // g_simpleItems might force a hide even with a pickup skin + if ( pickupSkin ) { + SetSkin( skin ); + srvReady = 1; + } else { + SetSkin( skin ); + Show(); + } + + if( simpleItem ) { + UpdateVisuals(); + } + pickedUp = false; + + if ( effectIdle.GetEntity( ) ) { + UpdateModelTransform(); + effectIdle->SetOrigin( resetOrigin ); + } else if ( spawnArgs.GetString( "fx_idle" ) && !simpleItem ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } else { + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + } + + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + pickedUp = true; + + StopEffect( "fx_idle" ); + effectIdle = NULL; + } + } + clReady = newReady; +} + +/* +================ +idItem::Event_Pickup +================ +*/ +void idItem::Event_Pickup( int clientNum ) { + idPlayer *player; + + assert( gameLocal.isClient ); + + // play pickup sound + if ( !spawnArgs.GetBool( "globalAcquireSound" ) ) { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + if( clientNum == gameLocal.localClientNum ) { + player = (idPlayer*)gameLocal.entities[ clientNum ]; + player->lastPickupTime = gameLocal.time; + if ( player ) { + player->GiveItem( this ); + } + } +} + +/* +================ +idItem::ClientReceiveEvent +================ +*/ +bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_PICKUP: { + int clientNum = msg.ReadByte(); + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + Event_Pickup( clientNum ); + } + return true; + } + case EVENT_RESPAWNFX: { + Event_RespawnFx(); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idItem::Event_DropToFloor +================ +*/ +void idItem::Event_DropToFloor( void ) { + // don't drop the floor if bound to another entity + if ( GetBindMaster() != NULL && GetBindMaster() != this ) { + return; + } + + physicsObj.DropToFloor( ); +} + +/* +================ +idItem::Event_Touch +================ +*/ +void idItem::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( !canPickUp ) { + return; + } + + Pickup( static_cast(other) ); +} + +/* +================ +idItem::Event_Trigger +================ +*/ +void idItem::Event_Trigger( idEntity *activator ) { + if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) { + canPickUp = true; + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Pickup( static_cast( activator ) ); + } +} + +/* +================ +idItem::Event_Respawn +================ +*/ +void idItem::Event_Respawn( void ) { + + // with simple items, re-show the item, but still set srvReady to true to let clients + // know how to deal + if ( pickupSkin ) { + srvReady = true; + if( !simpleItem ) { + SetSkin( skin ); + } + } + + if( !pickupSkin ) { + BecomeActive( TH_THINK ); + Show(); + } + + if( simpleItem ) { + Show(); + } + + pickedUp = false; + + inViewTime = -1000; + lastCycle = -1; + trigger->SetContents ( CONTENTS_TRIGGER ); + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL ); + PostEventMS( &EV_SetGravity, 0 ); + CancelEvents( &EV_RespawnItem ); // don't double respawn + + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::Event_RespawnFx +================ +*/ +void idItem::Event_RespawnFx( void ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_RESPAWNFX ); + // send unreliable PVS-sensitive + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ) ); + } + + if( !simpleItem ) { + gameLocal.PlayEffect( spawnArgs, "fx_respawn", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + // zinx - HACK - hardcoded sound to avoid new defs for 1.4. Please ensure it remains precached. + // FIXME - put the sound in the defs + if ( !idStr::Icmp( spawnArgs.GetString( "fx_respawn" ), "effects/mp/itemrespawn.fx" ) ) { + const idSoundShader *shader = declManager->FindSound( "mp_item_respawn" ); + StartSoundShader( shader, SND_CHANNEL_ITEM, 0, false, NULL ); + } + } +} + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +/* +=============== +idItemPowerup +=============== +*/ + +CLASS_DECLARATION( idItem, idItemPowerup ) +END_CLASS + +/* +================ +idItemPowerup::idItemPowerup +================ +*/ +idItemPowerup::idItemPowerup() { + time = 0; + type = 0; + droppedTime = 0; + unique = false; +} + +/* +================ +idItemPowerup::Save +================ +*/ +void idItemPowerup::Save( idSaveGame *savefile ) const { + savefile->WriteInt( time ); + savefile->WriteInt( type ); + savefile->WriteInt( droppedTime ); // cnicholson: Added unsaved var +} + +/* +================ +idItemPowerup::Restore +================ +*/ +void idItemPowerup::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( type ); + savefile->ReadInt( droppedTime ); // cnicholson: Added unrestored var +} + +/* +================ +idItemPowerup::Spawn +================ +*/ +void idItemPowerup::Spawn( void ) { + time = SEC2MS( spawnArgs.GetInt( "time", "30" ) ); + // SEC2MS screws up when we want -1 time (no expiration) + if( spawnArgs.GetInt( "time" ) == -1 ) { + time = -1; + } + + type = spawnArgs.GetInt( "type", "0" ); + + // If the powerup was dropped then make it dissapear using its remaining time. + if ( spawnArgs.GetBool( "dropped" ) && time != -1 ) { + droppedTime = gameLocal.time + time; + PostEventMS( &EV_Remove, time ); + } + + // unique powerpus won't respawn while a player has them + unique = spawnArgs.GetBool( "unique", "0" ); + if( unique ) { + spawnArgs.SetBool( "no_respawn", true ); + } + + if ( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else { + team = -1; + } +} + +/* +================ +idItemPowerup::GiveToPlayer +================ +*/ +bool idItemPowerup::GiveToPlayer( idPlayer *player ) { + if ( player == NULL || player->spectating ) { + return false; + } + + // only one arena CTF powerup at a time + if ( type >= POWERUP_AMMOREGEN && type <= POWERUP_SCOUT ) { + if ( ( player->inventory.powerups & ARENA_POWERUP_MASK ) != 0 ) { + return false; + } + } + + // in flavours of arena CTF (or are idItemPowerups only used in Arena? or even, are idItemPowerups MP only?), + // ensure that items with a team can only be picked up by members of that team + if ( gameLocal.IsMultiplayer() && gameLocal.IsTeamGame() && team >= 0 ) { + if( team != player->team ) { + return false; + } + } + + if ( droppedTime > 0 ) { + player->GivePowerUp( type, droppedTime - gameLocal.time ); + } else { + player->GivePowerUp( type, time ); + } + + // also call idItem::GiveToPlayer so any inv_* keywords get applied + idItem::GiveToPlayer( player ); + + return true; +} + +/* +================ +idItemPowerup::Think +================ +*/ +void idItemPowerup::Think( void ) { + int i; + + // idItem::Think() only needs to be called if we're spawned in + if( !IsHidden() || (pickupSkin && GetSkin() != pickupSkin ) ) { + // only get here if spawned in + idItem::Think(); + return; + } + + if( !unique ) { + // non-unique despawned powerups don't need to think + return; + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL ) { + continue; + } + + // only spawn back in if noone on your team has the powerup + if( p->PowerUpActive( type ) && p->team == team ) { + break; + } + } + + if( i >= gameLocal.numClients ) { + PostEventMS( &EV_RespawnItem, 0 ); + } +} + +/* +================ +idItemPowerup::Pickup +================ +*/ +bool idItemPowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + + if( gameLocal.isClient ) { + // no client-side powerup prediction + return false; + } + + pickup = idItem::Pickup( player ); + if( unique ) { + BecomeActive( TH_THINK ); + } + + return pickup; +} + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idObjective ) + EVENT( EV_Activate, idObjective::Event_Trigger ) + EVENT( EV_HideObjective, idObjective::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjective::Event_GetPlayerPos ) + EVENT( EV_CamShot, idObjective::Event_CamShot ) +END_CLASS + +/* +================ +idObjective::idObjective +================ +*/ +idObjective::idObjective() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjective::Save +================ +*/ +void idObjective::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjective::Restore +================ +*/ +void idObjective::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +// RAVEN BEGIN +// jnewquist: Don't do this on Xenon, we want prebuilt textures +#ifndef _XENON + PostEventMS( &EV_CamShot, 250 ); +#endif +// RAVEN END +} + +/* +================ +idObjective::Spawn +================ +*/ +void idObjective::Spawn( void ) { + Hide(); +// RAVEN BEGIN +// jnewquist: Only post a camshot event if the spawn args request it +#ifndef _XENON + const char *camName; + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + common->Warning( "SpawnArg camShot on %s is not recommended.", spawnArgs.GetString( "name" ) ); + PostEventMS( &EV_CamShot, 250 ); + } +#endif +// RAVEN END +} + +/* +================ +idObjective::Event_Screenshot +================ +*/ +void idObjective::Event_CamShot( ) { + const char *camName; +// RAVEN BEGIN +// bdube: changed screenshot location + idStr shotName = "gfx/objectives/"; + shotName += spawnArgs.GetString( "screenshot" ); + shotName.SetFileExtension( ".tga" ); + // RAVEN END + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + idEntity *ent = gameLocal.FindEntity( camName ); + if ( ent && ent->cameraTarget ) { + const renderView_t *view = ent->cameraTarget->GetRenderView(); + renderView_t fullView = *view; + fullView.width = SCREEN_WIDTH; + fullView.height = SCREEN_HEIGHT; + + // draw a view to a texture + renderSystem->CropRenderSize( 256, 256, true ); + gameRenderWorld->RenderScene( &fullView ); + // TTimo: I don't think this jives with SMP code, but I grepped through the final maps and didn't see any using it + renderSystem->CaptureRenderToFile( shotName ); + renderSystem->UnCrop(); + } + } +} + +/* +================ +idObjective::Event_Trigger +================ +*/ +void idObjective::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so when in a vehicle we update the vehicle hud +// twhitaker: changed player->hud to player->GetObjectiveHud(), to resolve all issues + if ( player && player->GetObjectiveHud() ) { +// bdube: changed screenshot location + idStr shotName = spawnArgs.GetString( "screenshot" ); + + player->GetObjectiveHud()->SetStateString( "screenshot", shotName ); + player->GetObjectiveHud()->SetStateString( "objective", "1" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), shotName ); + + // a tad slow but keeps from having to update all objectives in all maps with a name ptr + for( int i = 0; i < gameLocal.num_entities; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::GetClassType() ) ) { +// RAVEN END + if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){ + gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true ); + break; + } + } + } + + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjective::Event_GetPlayerPos +================ +*/ +void idObjective::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjective::Event_HideObjective +================ +*/ +void idObjective::Event_HideObjective(idEntity *e) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idMoveableItem ) + EVENT( EV_Gib, idMoveableItem::Event_Gib ) +END_CLASS + +/* +================ +idMoveableItem::idMoveableItem +================ +*/ +idMoveableItem::idMoveableItem() { +} + +/* +================ +idMoveableItem::~idMoveableItem +================ +*/ +idMoveableItem::~idMoveableItem() { + // If this entity has been allocated, but not spawned, the physics object will + // not have a self pointer, and will not unregister itself from the entity. + SetPhysics( NULL ); +} + +/* +================ +idMoveableItem::Save +================ +*/ +void idMoveableItem::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idMoveableItem::Restore +================ +*/ +void idMoveableItem::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); +} + +/* +================ +idMoveableItem::Spawn +================ +*/ +void idMoveableItem::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + idStr clipModelName; + idBounds bounds; + + // if the model should be shrinked + if ( spawnArgs.GetBool( "clipshrink" ) ) { + trm.Shrink( CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + // setup the physics + physicsObj.SetSelf( this ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // check if a clip model is set + spawnArgs.GetString( "itemclipmodel", "", clipModelName ); + if ( clipModelName[0] ) { + if ( collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + physicsObj.SetClipModel( new idClipModel( trm ), density ); + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + +// RAVEN BEGIN +// mekberg: added + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } +// RAVEN END +} + +/* +================ +idMoveableItem::Think +================ +*/ +void idMoveableItem::Think( void ) { + RunPhysics(); + UpdateTrigger( ); + Present(); +} + +/* +================ +idMoveableItem::DropItem +================ +*/ +idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) { + idDict args; + idEntity *item; + + args.Set( "classname", classname ); + args.Set( "dropped", "1" ); + + // we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor + args.Set( "nodrop", "1" ); + + if ( activateDelay ) { + args.SetBool( "triggerFirst", true ); + } + + gameLocal.SpawnEntityDef( args, &item ); + if ( item ) { + // set item position + item->GetPhysics()->SetOrigin( origin ); + item->GetPhysics()->SetAxis( axis ); + item->GetPhysics()->SetLinearVelocity( velocity ); + item->UpdateVisuals(); + if ( activateDelay ) { + item->PostEventMS( &EV_Activate, activateDelay, item ); + } + if ( !removeDelay ) { + removeDelay = 5 * 60 * 1000; + } + // always remove a dropped item after 5 minutes in case it dropped to an unreachable location + item->PostEventMS( &EV_Remove, removeDelay ); + } + + return item; +} + +/* +================ +idMoveableItem::DropItems + + The entity should have the following key/value pairs set: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + "skin_drop" "skin name" + To drop multiple items the following key/value pairs can be used: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + where is an aribtrary string. +================ +*/ +void idMoveableItem::DropItems( idAnimatedEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName, *c, *jointName; + idStr key, key2; + idVec3 origin; + idMat3 axis; + idAngles angles; + const idDeclSkin *skin; + jointHandle_t joint; + idEntity *item; + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL ); + while ( kv ) { + + c = kv->GetKey().c_str() + kv->GetKey().Length(); + if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) { + + key = kv->GetKey().c_str() + 4; + key2 = key; + key += "Joint"; + key2 += "Offset"; + jointName = ent->spawnArgs.GetString( key ); + joint = ent->GetAnimator()->GetJointHandle( jointName ); + if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + if ( g_dropItemRotation.GetString()[0] ) { + angles.Zero(); + sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll ); + } else { + key = kv->GetKey().c_str() + 4; + key += "Rotation"; + ent->spawnArgs.GetAngles( key, "0 0 0", angles ); + } + axis *= angles.ToMat3() * axis; + + origin += ent->spawnArgs.GetVector( key2, "0 0 0" ); + + item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 ); + if ( list && item ) { + list->Append( item ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv ); + } + + // change the skin to hide all items + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +====================== +idMoveableItem::WriteToSnapshot +====================== +*/ +void idMoveableItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +====================== +idMoveableItem::ReadFromSnapshot +====================== +*/ +void idMoveableItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +============ +idMoveableItem::Gib +============ +*/ +void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) { + // remove the entity + PostEventMS( &EV_Remove, 0 ); +} + +/* +============ +idMoveableItem::Event_Gib +============ +*/ +void idMoveableItem::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idItemRemover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idItemRemover ) + EVENT( EV_Activate, idItemRemover::Event_Trigger ) +END_CLASS + +/* +================ +idItemRemover::Spawn +================ +*/ +void idItemRemover::Spawn( void ) { +} + +/* +================ +idItemRemover::RemoveItem +================ +*/ +void idItemRemover::RemoveItem( idPlayer *player ) { + const char *remove; + + remove = spawnArgs.GetString( "remove" ); + player->RemoveInventoryItem( remove ); +} + +/* +================ +idItemRemover::Event_Trigger +================ +*/ +void idItemRemover::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + RemoveItem( static_cast(activator) ); + } +} + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +CLASS_DECLARATION( idItemRemover, idObjectiveComplete ) + EVENT( EV_Activate, idObjectiveComplete::Event_Trigger ) + EVENT( EV_HideObjective, idObjectiveComplete::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjectiveComplete::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjectiveComplete::idObjectiveComplete +================ +*/ +idObjectiveComplete::idObjectiveComplete() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjectiveComplete::Save +================ +*/ +void idObjectiveComplete::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Restore +================ +*/ +void idObjectiveComplete::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Spawn +================ +*/ +void idObjectiveComplete::Spawn( void ) { + spawnArgs.SetBool( "objEnabled", false ); + Hide(); +} + +/* +================ +idObjectiveComplete::Event_Trigger +================ +*/ +void idObjectiveComplete::Event_Trigger( idEntity *activator ) { + if ( !spawnArgs.GetBool( "objEnabled" ) ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + RemoveItem( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so if in a vehicle we update that hud +// twhitaker: moved objective system to it's own hud, to solve vehicle hud issues. + if ( player->GetObjectiveHud() ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) ); + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjectiveComplete::Event_GetPlayerPos +================ +*/ +void idObjectiveComplete::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjectiveComplete::Event_HideObjective +================ +*/ +void idObjectiveComplete::Event_HideObjective( idEntity *e ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->GetObjectiveHud() ) { + idVec3 v = player->GetPhysics()->GetOrigin(); + v -= playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ +CLASS_DECLARATION( idItemRemover, rvObjectiveFailed ) + EVENT( EV_Activate, rvObjectiveFailed::Event_Trigger ) +END_CLASS + +rvObjectiveFailed::rvObjectiveFailed ( void ) { +} + +/* +================ +rvObjectiveFailed::Event_Trigger +================ +*/ +void rvObjectiveFailed::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->GetObjectiveHud() ) { + return; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + return; + } + + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); + player->FailObjective( spawnArgs.GetString( "objectivetitle" ) ); +} + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +const idEventDef EV_ResetFlag ( "" ); +const idEventDef EV_LinkTrigger( "" ); +CLASS_DECLARATION( idItem, rvItemCTFFlag ) + EVENT( EV_ResetFlag, rvItemCTFFlag::Event_ResetFlag ) + EVENT( EV_LinkTrigger, rvItemCTFFlag::Event_LinkTrigger ) +END_CLASS + +/* +================ +rvItemCTFFlag::rvItemCTFFlag +================ +*/ +rvItemCTFFlag::rvItemCTFFlag() { +} + + +/* +================ +rvItemCTFFlag::Spawn +================ +*/ +void rvItemCTFFlag::Spawn () { +/* rjohnson: I fixed the crash so we don't need to remove them... + //don't spawn outside of CTF games + if( (gameLocal.gameType != GAME_CTF) && (gameLocal.gameType != GAME_1F_CTF) && + (gameLocal.gameType != GAME_ARENA_CTF) && (gameLocal.gameType != GAME_ARENA_1F_CTF) ){ + //don't spawn! + gameLocal.Error("Flag spawn on in non-CTF gametype."); + PostEventMS ( &EV_Remove, 0 ); + return; + } +*/ + spawnArgs.GetBool ( "dropped", "0", dropped ); + spawnArgs.GetInt ( "team", "0", team ); + + bool reset = false; + spawnArgs.GetBool ( "reset", "0", reset ); + + switch ( team ) { + case TEAM_MARINE: { + powerup = POWERUP_CTF_MARINEFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_MARINE ); + break; + } + case TEAM_STROGG: { + powerup = POWERUP_CTF_STROGGFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_STROGG ); + break; + } + case TEAM_MAX: { + powerup = POWERUP_CTF_ONEFLAG; + break; + } + default: + gameLocal.Warning ( "Unknown ctf flag team '%d' on entity '%s'", team, name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + if ( dropped ) { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_DROPPED ); + } + if ( reset ) { + //PostEventSec( &EV_ResetFlag, 1 ); + } else { + PostEventSec( &EV_ResetFlag, 30 ); + } + + // Let powerups settle for a frame before allowing them to be picked up - we need to + // make sure we hit the dropped state for VO, etc to work properly + trigger->SetContents( 0 ); + PostEventSec( &EV_LinkTrigger, 0 ); + } else { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + + spin = false; +} + +/* +================ +rvItemCTFFlag::Event_LinkTrigger +================ +*/ +void rvItemCTFFlag::Event_LinkTrigger( void ) { + trigger->SetContents( CONTENTS_TRIGGER | CONTENTS_ITEMCLIP ); +} + +/* +================ +rvItemCTFFlag::GiveToPlayer +================ +*/ +bool rvItemCTFFlag::GiveToPlayer( idPlayer* player ) { + if ( !gameLocal.IsMultiplayer() ) { + return false; + } + + if ( player->spectating || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && (gameLocal.mpGame.GetGameState())->GetMPGameState() != SUDDENDEATH && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return false; + } + + int teamPowerup; + int enemyPowerup; + + bool canPickup = ( team == player->team ); + + switch ( player->team ) { + default: + case TEAM_MARINE: + teamPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + break; + + case TEAM_STROGG: + teamPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + break; + } + + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + // in one flag CTF, we score touching the enemy's flag + canPickup = ( team == gameLocal.mpGame.OpposingTeam( player->team ) ); + enemyPowerup = POWERUP_CTF_ONEFLAG; + } + + // If the player runs over their own flag the only thing they + // can do is score or return it. + + // when the player touches the one flag, he always gets it + if ( canPickup ) { + // If the flag was dropped, return it + if ( dropped ) { + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + statManager->FlagReturned( player ); + ResetFlag ( teamPowerup ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagReturned", 0 ) ); +// RITUAL END + } else if ( player->PowerUpActive ( enemyPowerup ) ) { + // If they have the enemy flag then they score + if ( !gameLocal.mpGame.CanCapture ( player->team ) ) { + return false; + } + + ResetFlag( enemyPowerup ); + + gameLocal.mpGame.FlagCaptured( player ); + } + return false; + } + + // only pickup one flag in arena CTF + if( ( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) && team != TEAM_MAX ) { + return false; + } + + player->GivePowerUp( enemyPowerup, -1 ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagStolen", 0 ) ); +// RITUAL END + return true; +} + +/* +================ +rvItemCTFFlag::Pickup +================ +*/ +bool rvItemCTFFlag::Pickup( idPlayer *player ) { + if( gameLocal.isClient ) { + // no client-side CTF flag prediction + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + // ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + gameLocal.mpGame.SetFlagEntity( NULL, team ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 1 ); + + if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_ARENA_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_TAKEN ); + } else if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, (player->team == TEAM_MARINE ? FS_TAKEN_MARINE : FS_TAKEN_STROGG) ); + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagCarrier( team, player->entityNumber ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + BecomeInactive( TH_THINK ); + + return true; +} + +/* +================ +rvItemCTFFlag::ResetFlag +================ +*/ +void rvItemCTFFlag::ResetFlag( int powerup ) { + idEntity* ent; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // Make sure no players have the flag anymore +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType ( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->ClearPowerup ( powerup ); + continue; + } + + // If its not a CTF flag item then skip it + if ( !ent->IsType( rvItemCTFFlag::Type ) ) { + continue; + } + + // Make sure its the right type first + rvItemCTFFlag* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->dropped ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + flag->PostEventMS( &EV_RespawnItem, 0 ); + gameLocal.mpGame.SetFlagEntity( flag, flag->team ); + } + } + + if ( !gameLocal.isClient ) { + int team = -1; + if ( powerup == POWERUP_CTF_MARINEFLAG ) { + team = TEAM_MARINE; + } else if ( powerup == POWERUP_CTF_STROGGFLAG ) { + team = TEAM_STROGG; + } else if ( powerup == POWERUP_CTF_ONEFLAG ) { + team = TEAM_MAX; + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + +} + +/* +================ +rvItemCTFFlag::Event_ResetFlag +================ +*/ +void rvItemCTFFlag::Event_ResetFlag( void ) { + ResetFlag( powerup ); +} + +void rvItemCTFFlag::Think( void ) { + idItem::Think(); +} + +/* +================ +rvItemCTFFlag::Collide +================ +*/ +bool rvItemCTFFlag::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + + if ( collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + ResetFlag( powerup ); + } + return false; +} +// RAVEN END + + + + +const idEventDef EV_ResetSpawn ( "" ); +CLASS_DECLARATION( idItemPowerup, riDeadZonePowerup ) + EVENT( EV_ResetSpawn, riDeadZonePowerup::Event_ResetSpawn ) +END_CLASS + +/* +================ +riDeadZonePowerup::idItemPowerup +================ +*/ +riDeadZonePowerup::riDeadZonePowerup() { +} + +/* +================ +riDeadZonePowerup::Save +================ +*/ +void riDeadZonePowerup::Save( idSaveGame *savefile ) const { +} + +/* +================ +riDeadZonePowerup::Restore +================ +*/ +void riDeadZonePowerup::Restore( idRestoreGame *savefile ) { +} + + +/* +================ +riDeadZonePowerup::Show +================ +*/ +void riDeadZonePowerup::Show() +{ + idItem::Show(); +} + + +/* +================ +riDeadZonePowerup::Spawn +================ +*/ +void riDeadZonePowerup::Spawn( void ) { + powerup = POWERUP_DEADZONE; + + time = SEC2MS( gameLocal.serverInfo.GetInt("si_deadZonePowerupTime") ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + time = SEC2MS( spawnArgs.GetInt( "time", "10" ) ); + if ( time > SEC2MS(10) ) + time = SEC2MS(10); + + Show(); + CancelEvents(&EV_Remove); + PostEventSec( &EV_ResetSpawn, MS2SEC(time) ); + } + else + Hide(); +} + + +/* +================ +riDeadZonePowerup::Pickup +================ +*/ +bool riDeadZonePowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + if ( player->PowerUpActive(POWERUP_DEADZONE) ) + return false; + + pickup = idItemPowerup::Pickup( player ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + // Cancel the respawn so the powerup doesn't get removed + // from the player that just picked it up. + PostEventMS( &EV_Remove, 0 ); + CancelEvents( &EV_ResetSpawn ); + } + + return pickup; +} + +/* +================ +riDeadZonePowerup::ResetSpawn +================ +*/ +void riDeadZonePowerup::ResetSpawn( int powerup ) { + int count = 1; + idEntity* ent; + riDeadZonePowerup* spawnSpot = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::Type ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && flag == this ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + if ( !flag->IsVisible() ) { + if ( !(rand()%count) ) { + spawnSpot = flag; + if ( flag->spawnArgs.GetBool("dropped", "0") ) + gameLocal.DPrintf("WARNING: Trying to spawn a powerup at a DROPPED location!"); + } + count++; + } + } + } + + if ( spawnSpot ) { + spawnSpot->Show(); + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + } + else { + gameLocal.DPrintf("WARNING: Failed to find a valid spawn spot!"); + } +} + +/* +================ +riDeadZonePowerup::Collide +================ +*/ +bool riDeadZonePowerup::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_ResetSpawn, 0 ); // Just respawn it. + } + return false; +} + +/* +================ +riDeadZonePowerup::Event_ResetFlag +================ +*/ +void riDeadZonePowerup::Event_ResetSpawn( void ) { + ResetSpawn( POWERUP_DEADZONE ); +} diff --git a/source/mpgame/Item.h b/source/mpgame/Item.h new file mode 100644 index 0000000..fed4afb --- /dev/null +++ b/source/mpgame/Item.h @@ -0,0 +1,363 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ITEM_H__ +#define __GAME_ITEM_H__ + +extern const int ARENA_POWERUP_MASK; +extern const idEventDef EV_ResetFlag; +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_SetGravity; + +/* +=============================================================================== + + Items the player can pick up or use. + +=============================================================================== +*/ + +class idItem : public idEntity { +public: + CLASS_PROTOTYPE( idItem ); + + idItem(); + virtual ~idItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void GetAttributes( idDict &attributes ); + virtual bool GiveToPlayer( idPlayer *player ); + virtual bool Pickup( idPlayer *player ); + virtual void Think( void ); + virtual void Present(); + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// mekberg: added + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); +// RAVEN END + + enum { + EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, + EVENT_RESPAWNFX, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + int IsVisible() { return srvReady; } + + rvClientEntityPtr effectIdle; + bool simpleItem; + float simpleItemScale; + bool pickedUp; + const idDeclSkin* pickupSkin; + void Event_DropToFloor ( void ); +protected: + + void UpdateTrigger( void ); + void SendPickupMsg( int clientNum ); + + idClipModel * trigger; + bool spin; + // only a small subset of idItem need their physics object synced + bool syncPhysics; + + bool pulse; + bool canPickUp; + const idDeclSkin* skin; + +private: + idVec3 orgOrigin; + + rvPhysics_Particle physicsObj; + + // for item pulse effect + int itemShellHandle; + const idMaterial * shellMaterial; + + // used to update the item pulse effect + mutable bool inView; + mutable int inViewTime; + mutable int lastCycle; + mutable int lastRenderViewTime; + + // synced through snapshots to indicate show/hide or pickupSkin state + // -1 on a client means undef, 0 not ready, 1 ready +public: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int srvReady; +private: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int clReady; + + int itemPVSArea; + + bool UpdateRenderEntity ( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback ( renderEntity_s *renderEntity, const renderView_t *renderView ); + + + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_Trigger ( idEntity *activator ); + void Event_Respawn ( void ); + void Event_RespawnFx ( void ); + void Event_Pickup ( int clientNum ); + +// RAVEN BEGIN +// abahr + void Event_SetGravity(); +// RAVEN END +}; + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +class idItemPowerup : public idItem { +public: + CLASS_PROTOTYPE( idItemPowerup ); + + idItemPowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool GiveToPlayer( idPlayer *player ); + virtual void Think( void ); + virtual bool Pickup( idPlayer *player ); + +protected: + + int time; + int type; + int droppedTime; + int team; + bool unique; +}; + +/* +=============================================================================== + + riDeadZonePowerup + +=============================================================================== +*/ + +class riDeadZonePowerup : public idItemPowerup { +public: + CLASS_PROTOTYPE( riDeadZonePowerup ); + + riDeadZonePowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Pickup( idPlayer *player ); + + void Spawn(); + void Show(); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + int powerup; + +protected: + void ResetSpawn( int powerup ); + +private: + void Event_ResetSpawn( void ); +}; + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +class rvItemCTFFlag : public idItem { +public: + CLASS_PROTOTYPE( rvItemCTFFlag ); + + rvItemCTFFlag(); + + void Spawn(); + virtual bool GiveToPlayer ( idPlayer* player ); + virtual bool Pickup( idPlayer *player ); + + static void ResetFlag( int type ); + virtual void Think( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + +private: + int team; + int powerup; + bool dropped; + + void Event_ResetFlag( void ); + void Event_LinkTrigger( void ); +}; + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +class idObjective : public idItem { +public: + CLASS_PROTOTYPE( idObjective ); + + idObjective(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); + void Event_CamShot(); +}; + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +class idMoveableItem : public idItem { +public: + CLASS_PROTOTYPE( idMoveableItem ); + + idMoveableItem(); + virtual ~idMoveableItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual void Think( void ); + + static void DropItems( idAnimatedEntity *ent, const char *type, idList *list ); + static idEntity* DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + idPhysics_RigidBody physicsObj; + + void Gib( const idVec3 &dir, const char *damageDefName ); + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + Item removers. + +=============================================================================== +*/ + +class idItemRemover : public idEntity { +public: + CLASS_PROTOTYPE( idItemRemover ); + + void Spawn(); + void RemoveItem( idPlayer *player ); + +private: + void Event_Trigger( idEntity *activator ); +}; + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +class idObjectiveComplete : public idItemRemover { +public: + CLASS_PROTOTYPE( idObjectiveComplete ); + + idObjectiveComplete(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ + +class rvObjectiveFailed : public idItemRemover { +public: + CLASS_PROTOTYPE( rvObjectiveFailed ); + + rvObjectiveFailed (); +private: + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_ITEM_H__ */ + + +// RAVEN END diff --git a/source/mpgame/Lagometer.cpp b/source/mpgame/Lagometer.cpp new file mode 100644 index 0000000..1808c3e --- /dev/null +++ b/source/mpgame/Lagometer.cpp @@ -0,0 +1,59 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define LAGO_TOP 380 +#define LAGO_LEFT 10 +#define LAGO_WIDTH 64 +#define LAGO_HEIGHT 44 + + +idLagometer::idLagometer() +{ + memset(aheadOfServer, 0, sizeof(int) * LAGOMETER_HISTORY); + memset(dupeUserCmds, 0, sizeof(byte) * LAGOMETER_HISTORY); + + frameIndex = 0; +} + +void idLagometer::Draw() +{ + //renderSystem->DrawStretchPic(x, y, 1, h, 0, 0, 0, 0, lagometerBack); + + const idVec4 colorYellow(1.0f, 1.0f, 0.0f, 1.0f); + const idVec4 colorGreen(0.0f, 1.0f, 0.0f, 1.0f); + const idVec4 colorRed(1.0f, 0.0f, 0.0f, 1.0f); + const idVec4 colorWhite(1.0f, 1.0f, 1.0f, 1.0f); + + const idMaterial *whiteMaterial = declManager->FindMaterial("_white"); + + for (int x=0; x 0) + { + renderSystem->SetColor(colorGreen); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + 10 - aheadOfServer[i], 1, aheadOfServer[i], 0, 0, 0, 0, whiteMaterial); + } + else if (aheadOfServer[i] < 0) + { + renderSystem->SetColor(colorYellow); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + 10, 1, -aheadOfServer[i], 0, 0, 0, 0, whiteMaterial); + } + + renderSystem->SetColor(colorYellow); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + LAGO_HEIGHT - dupeUserCmds[i], 1, dupeUserCmds[i], 0, 0, 0, 0, whiteMaterial); + } +} + +void idLagometer::Update(int ahead, int dupe) +{ + frameIndex = (frameIndex + 1) % LAGOMETER_HISTORY; + + aheadOfServer[frameIndex] = 2 * idMath::ClampInt(-10, 5, + floorf( (float)ahead / net_clientLagOMeterResolution.GetInteger() )); + + dupeUserCmds[frameIndex] = 2 * Min( 6, dupe ); +} \ No newline at end of file diff --git a/source/mpgame/Lagometer.h b/source/mpgame/Lagometer.h new file mode 100644 index 0000000..21613dc --- /dev/null +++ b/source/mpgame/Lagometer.h @@ -0,0 +1,21 @@ +#ifndef __LAGOMETER_H__ +#define __LAGOMETER_H__ + +#define LAGOMETER_HISTORY 64 + +class idLagometer +{ +public: + idLagometer(); + + void Update(int ahead, int dupe); + void Draw(); +protected: + int aheadOfServer[LAGOMETER_HISTORY]; + byte dupeUserCmds[LAGOMETER_HISTORY]; + + int frameIndex; +}; + + +#endif diff --git a/source/mpgame/Light.cpp b/source/mpgame/Light.cpp new file mode 100644 index 0000000..eed389b --- /dev/null +++ b/source/mpgame/Light.cpp @@ -0,0 +1,1486 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idLight + +=============================================================================== +*/ + +const idEventDef EV_Light_SetShader( "setShader", "s" ); +const idEventDef EV_Light_GetLightParm( "getLightParm", "d", 'f' ); +const idEventDef EV_Light_SetLightParm( "setLightParm", "df" ); +const idEventDef EV_Light_SetLightParms( "setLightParms", "ffff" ); +const idEventDef EV_Light_SetRadiusXYZ( "setRadiusXYZ", "fff" ); +const idEventDef EV_Light_SetRadius( "setRadius", "f" ); +const idEventDef EV_Light_On( "On", NULL ); +const idEventDef EV_Light_Off( "Off", NULL ); +const idEventDef EV_Light_FadeOut( "fadeOutLight", "f" ); +const idEventDef EV_Light_FadeIn( "fadeInLight", "f" ); + +// RAVEN BEGIN +// bdube: added +const idEventDef EV_Light_SetLightGUI ( "setLightGUI", "s" ); +// jscott: added for modview +const idEventDef EV_Light_SetCurrentLightLevel( "setCurrentLightLevel", "d" ); +const idEventDef EV_Light_SetMaxLightLevel( "setMaxLightLevel", "d" ); +// kfuller: 8/11/03 +const idEventDef EV_Light_IsOn( "isOn", NULL, 'f' ); +const idEventDef EV_Light_Break( "break", "ef" ); +// kfuller: lights that blink to life +const idEventDef EV_Light_DoneBlinking( "doneBlinking", NULL ); +// kfuller: lights that blink off +const idEventDef EV_Light_DoneBlinkingOff( "doneBlinkingOff", NULL ); +// abahr: +const idEventDef EV_Light_Timer( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idLight ) + EVENT( EV_Light_SetShader, idLight::Event_SetShader ) + EVENT( EV_Light_GetLightParm, idLight::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idLight::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idLight::Event_SetLightParms ) + EVENT( EV_Light_SetRadiusXYZ, idLight::Event_SetRadiusXYZ ) + EVENT( EV_Light_SetRadius, idLight::Event_SetRadius ) + EVENT( EV_Hide, idLight::Event_Hide ) + EVENT( EV_Show, idLight::Event_Show ) + EVENT( EV_Light_On, idLight::Event_On ) + EVENT( EV_Light_Off, idLight::Event_Off ) + EVENT( EV_Activate, idLight::Event_ToggleOnOff ) + EVENT( EV_PostSpawn, idLight::Event_SetSoundHandles ) + EVENT( EV_Light_FadeOut, idLight::Event_FadeOut ) + EVENT( EV_Light_FadeIn, idLight::Event_FadeIn ) + +// RAVEN BEGIN +// bdube: added + EVENT( EV_Light_SetLightGUI, idLight::Event_SetLightGUI ) + EVENT( EV_Light_SetCurrentLightLevel, idLight::Event_SetCurrentLightLevel ) + EVENT( EV_Light_SetMaxLightLevel, idLight::Event_SetMaxLightLevel ) +// kfuller: 8/11/03 + EVENT( EV_Light_IsOn, idLight::Event_IsOn ) + EVENT( EV_Light_Break, idLight::Event_Break ) +// kfuller: lights that blink to life + EVENT( EV_Light_DoneBlinking, idLight::Event_DoneBlinking ) +// kfuller: lights that blink off + EVENT( EV_Light_DoneBlinkingOff, idLight::Event_DoneBlinkingOff ) + EVENT( EV_Earthquake, idLight::Event_EarthQuake ) +// abahr: + EVENT( EV_Light_Timer, idLight::Event_Timer ) +// RAVEN END +END_CLASS + + +/* +================ +idGameEdit::ParseSpawnArgsToRenderLight + +parse the light parameters +this is the canonical renderLight parm parsing, +which should be used by dmap and the editor +================ +*/ +bool idGameEdit::ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ) { + bool gotTarget, gotUp, gotRight; + const char *texture; + idVec3 color; + bool rv = true; + + memset( renderLight, 0, sizeof( *renderLight ) ); + + if (!args->GetVector("light_origin", "", renderLight->origin)) { + args->GetVector( "origin", "", renderLight->origin ); + } + + gotTarget = args->GetVector( "light_target", "", renderLight->target ); + gotUp = args->GetVector( "light_up", "", renderLight->up ); + gotRight = args->GetVector( "light_right", "", renderLight->right ); + args->GetVector( "light_start", "0 0 0", renderLight->start ); + if ( !args->GetVector( "light_end", "", renderLight->end ) ) { + renderLight->end = renderLight->target; + } + + // we should have all of the target/right/up or none of them + if ( ( gotTarget || gotUp || gotRight ) != ( gotTarget && gotUp && gotRight ) ) { + gameLocal.Warning( "Light at (%f,%f,%f) has bad target info\n", + renderLight->origin[0], renderLight->origin[1], renderLight->origin[2] ); + + return false; + } + + if ( !gotTarget ) { + renderLight->pointLight = true; + + // allow an optional relative center of light and shadow offset + args->GetVector( "light_center", "0 0 0", renderLight->lightCenter ); + +// RAVEN BEGIN +// bdube: default light radius changed to 320 + // create a point light + if (!args->GetVector( "light_radius", "320 320 320", renderLight->lightRadius ) ) { + float radius; + + args->GetFloat( "light", "320", radius ); +// RAVEN END + renderLight->lightRadius[0] = renderLight->lightRadius[1] = renderLight->lightRadius[2] = radius; + } + + if ( renderLight->lightRadius[0] == 0 || + renderLight->lightRadius[1] == 0 || + renderLight->lightRadius[2] == 0 ) { + gameLocal.Warning( "PointLight at ( %d, %d, %d ) has at least one radius component of 0!", + ( int )renderLight->origin[0], ( int )renderLight->origin[1], ( int )renderLight->origin[2] ); + rv = false; + } + } + + // get the rotation matrix in either full form, or single angle form + idAngles angles; + idMat3 mat; + if ( !args->GetMatrix( "light_rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + args->GetFloat( "angle", "0", angles[ 1 ] ); + angles[ 0 ] = 0; + angles[ 1 ] = idMath::AngleNormalize360( angles[ 1 ] ); + angles[ 2 ] = 0; + mat = angles.ToMat3(); + } + } + + // fix degenerate identity matrices + mat[0].FixDegenerateNormal(); + mat[1].FixDegenerateNormal(); + mat[2].FixDegenerateNormal(); + + renderLight->axis = mat; + + // check for other attributes + args->GetVector( "_color", "1 1 1", color ); + renderLight->shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight->shaderParms[ SHADERPARM_BLUE ] = color[2]; + args->GetFloat( "shaderParm3", "1", renderLight->shaderParms[ SHADERPARM_TIMESCALE ] ); + if ( !args->GetFloat( "shaderParm4", "0", renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] ) ) { + // offset the start time of the shader to sync it to the game time + renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + args->GetFloat( "shaderParm5", "0", renderLight->shaderParms[5] ); + args->GetFloat( "shaderParm6", "0", renderLight->shaderParms[6] ); + args->GetFloat( "shaderParm7", "0", renderLight->shaderParms[ SHADERPARM_MODE ] ); + args->GetBool( "noshadows", "0", renderLight->noShadows ); + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + args->GetFloat( "detailLevel", "10", renderLight->detailLevel ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: dynamic shadows + args->GetBool( "noDynamicShadows", "0", renderLight->noDynamicShadows ); +// RAVEN END + args->GetBool( "nospecular", "0", renderLight->noSpecular ); + args->GetBool( "parallel", "0", renderLight->parallel ); + + args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL + renderLight->shader = declManager->FindMaterial( texture, false ); + + return rv; +} + +/* +================ +idLight::UpdateChangeableSpawnArgs +================ +*/ +void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + source->Print(); + } + FreeSoundEmitter( true ); + gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); + + UpdateVisuals(); +} + +/* +================ +idLight::idLight +================ +*/ +idLight::idLight() { + memset( &renderLight, 0, sizeof( renderLight ) ); +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + localLightOrigin = vec3_zero; + localLightAxis = mat3_identity; + lightDefHandle = -1; + levels = 0; + currentLevel = 0; + baseColor = vec3_zero; + breakOnTrigger = false; + count = 0; + triggercount = 0; + lightParent = NULL; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + soundWasPlaying = false; + +// RAVEN BEGIN +// bdube: light gui + lightGUI = NULL; + random = 0.0f; + wait = 0.0f; +// RAVEN END +} + +/* +================ +idLight::~idLight +================ +*/ +idLight::~idLight() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idLight::Save + +archives object for save game file +================ +*/ +void idLight::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( renderLight ); + + savefile->WriteBool( renderLight.prelightModel != NULL ); + + savefile->WriteVec3( localLightOrigin ); + savefile->WriteMat3( localLightAxis ); + //qhandle_t lightDefHandle; // cnicholson: This wasn't here from id, so I didnt add it either. + + savefile->WriteString( brokenModel ); + savefile->WriteInt( levels ); + savefile->WriteInt( currentLevel ); + + savefile->WriteVec3( baseColor ); + savefile->WriteBool( breakOnTrigger ); + savefile->WriteInt( count ); + savefile->WriteInt( triggercount ); + savefile->WriteObject( lightParent ); + + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + + lightGUI.Save( savefile ); // cnicholson: added unsaved var + + savefile->WriteBool( soundWasPlaying ); +} + +/* +================ +idLight::Restore + +unarchives object from save game file +================ +*/ +void idLight::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// jscott: constants can be read from the spawnargs + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// RAVEN END + + bool hadPrelightModel; + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadBool( hadPrelightModel ); + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + if ( ( renderLight.prelightModel == NULL ) && hadPrelightModel ) { + assert( 0 ); + if ( developer.GetBool() ) { + // we really want to know if this happens + gameLocal.Error( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } else { + // but let it slide after release + gameLocal.Warning( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } + } + + savefile->ReadVec3( localLightOrigin ); + savefile->ReadMat3( localLightAxis ); + + savefile->ReadString( brokenModel ); + savefile->ReadInt( levels ); + savefile->ReadInt( currentLevel ); + + savefile->ReadVec3( baseColor ); + savefile->ReadBool( breakOnTrigger ); + savefile->ReadInt( count ); + savefile->ReadInt( triggercount ); + savefile->ReadObject( reinterpret_cast( lightParent ) ); + + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +// RAVEN BEGIN +// bdube: light gui + lightGUI.Restore ( savefile ); +// RAVEN END + + savefile->ReadBool( soundWasPlaying ); + + lightDefHandle = -1; + + SetLightLevel(); +} + +/* +================ +idLight::Spawn +================ +*/ +void idLight::Spawn( void ) { + bool start_off; + bool needBroken; + const char *demonic_shader; + + // do the parsing the same way dmap and the editor do + if ( !gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ) ) { + gameLocal.Warning( "Removing invalid light named: %s", GetName() ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + // we need the origin and axis relative to the physics origin/axis + localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); + localLightAxis = renderLight.axis * GetPhysics()->GetAxis().Transpose(); + + // set the base color from the shader parms + baseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + + // set the number of light levels + spawnArgs.GetInt( "levels", "1", levels ); + currentLevel = levels; + if ( levels <= 0 ) { + gameLocal.Error( "Invalid light level set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + // make sure the demonic shader is cached + if ( spawnArgs.GetString( "mat_demonic", NULL, &demonic_shader ) ) { + declManager->FindType( DECL_MATERIAL, demonic_shader ); + } + + // game specific functionality, not mirrored in + // editor or dmap light parsing + + // also put the light texture on the model, so light flares + // can get the current intensity of the light + renderEntity.referenceShader = renderLight.shader; + + lightDefHandle = -1; // no static version yet + + // see if an optimized shadow volume exists + // the renderer will ignore this value after a light has been moved, + // but there may still be a chance to get it wrong if the game moves + // a light before the first present, and doesn't clear the prelight + renderLight.prelightModel = 0; + if ( name[ 0 ] ) { + // this will return 0 if not found + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + } + + spawnArgs.GetBool( "start_off", "0", start_off ); + if ( start_off ) { + Off(); + } + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "break", "0", breakOnTrigger ); + spawnArgs.GetInt( "count", "1", count ); + + triggercount = 0; + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // if we have a health make light breakable + if ( health ) { + idStr model = spawnArgs.GetString( "model" ); // get the visual model + if ( !model.Length() ) { + gameLocal.Error( "Breakable light without a model set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + fl.takedamage = true; + + // see if we need to create a broken model name + needBroken = true; + if ( model.Length() && !brokenModel.Length() ) { + int pos; + + needBroken = false; + + pos = model.Find( "." ); + if ( pos < 0 ) { + pos = model.Length(); + } + if ( pos > 0 ) { + model.Left( pos, brokenModel ); + } + brokenModel += "_broken"; + if ( pos > 0 ) { + brokenModel += &model[ pos ]; + } + } + + // make sure the model gets cached + if ( !renderModelManager->CheckModel( brokenModel ) ) { + if ( needBroken ) { + gameLocal.Error( "Model '%s' not found for entity %d(%s)", brokenModel.c_str(), entityNumber, name.c_str() ); + } else { + brokenModel = ""; + } + } + + GetPhysics()->SetContents( spawnArgs.GetBool( "nonsolid" ) ? 0 : CONTENTS_SOLID ); + } + + PostEventMS( &EV_PostSpawn, 0 ); + +// RAVEN BEGIN +// bdube: light guis + const char* lightGUI; + if ( spawnArgs.GetString ( "light_gui", "", &lightGUI ) ) { + PostEventMS( &EV_Light_SetLightGUI, 0, lightGUI ); + } + +// abahr: + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// AReis: Minor light optimization stuff. + spawnArgs.GetBool( "globalLight", "0", renderLight.globalLight ); +// RAVEN END + + UpdateVisuals(); + +// RAVEN BEGIN +// ddynerman: ambient lights added clientside + if( renderLight.shader && renderLight.shader->IsAmbientLight() ) { + if ( !gameLocal.ambientLights.Find( static_cast(this) ) ) { + gameLocal.ambientLights.Append( static_cast(this) ); + } + fl.networkSync = false; // don't transmit ambient lights + } +// RAVEN END +} + +/* +================ +idLight::SetLightLevel +================ +*/ +void idLight::SetLightLevel( void ) { + idVec3 color; + float intensity; + + intensity = ( float )currentLevel / ( float )levels; + color = baseColor * intensity; + renderLight.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec3 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec4 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderLight.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( float red, float green, float blue ) { + baseColor.Set( red, green, blue ); + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec4 &color ) { + baseColor = color.ToVec3(); + renderLight.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + SetLightLevel(); +} + +/* +================ +idLight::SetShader +================ +*/ +void idLight::SetShader( const char *shadername ) { + // allow this to be NULL + renderLight.shader = declManager->FindMaterial( shadername, false ); + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParm +================ +*/ +void idLight::SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + renderLight.shaderParms[ parmnum ] = value; + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParms +================ +*/ +void idLight::SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + renderLight.shaderParms[ SHADERPARM_RED ] = parm0; + renderLight.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderLight.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderLight.shaderParms[ SHADERPARM_ALPHA ] = parm3; + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + baseColor.Set( parm0, parm1, parm2 ); + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::SetRadiusXYZ +================ +*/ +void idLight::SetRadiusXYZ( float x, float y, float z ) { + renderLight.lightRadius[0] = x; + renderLight.lightRadius[1] = y; + renderLight.lightRadius[2] = z; + PresentLightDefChange(); +} + +/* +================ +idLight::SetRadius +================ +*/ +void idLight::SetRadius( float radius ) { + renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = radius; + PresentLightDefChange(); +} + +/* +================ +idLight::On +================ +*/ +void idLight::On( void ) { + currentLevel = levels; + // offset the start time of the shader to sync it to the game time + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + +// RAVEN BEGIN + idStr blinkOnSound; + if (spawnArgs.GetString("snd_blinkOn", "", blinkOnSound)) + { + refSound.shader = declManager->FindSound(blinkOnSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + PostEventMS(&EV_Light_DoneBlinking, howLongInMS); + soundWasPlaying = false; + idStr blinkOnTexture; + if (spawnArgs.GetString( "mtr_blinkOn", "", blinkOnTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOnTexture, false ); + UpdateVisuals(); + Present(); + } + } + else +// RAVEN END + + if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Off +================ +*/ +void idLight::Off( void ) { +// RAVEN BEGIN +// kfuller: lights can flicker off + idStr blinkOffSound; + if (spawnArgs.GetString("snd_blinkOff", "", blinkOffSound)) + { + refSound.shader = declManager->FindSound(blinkOffSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL );//*1000; + PostEventMS(&EV_Light_DoneBlinkingOff, howLongInMS); + soundWasPlaying = false; + idStr blinkOffTexture; + if (spawnArgs.GetString( "mtr_blinkOff", "", blinkOffTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOffTexture, false ); + UpdateVisuals(); + Present(); + } + } + else + { + currentLevel = 0; + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { + StopSound( SND_CHANNEL_ANY, false ); + soundWasPlaying = true; + } + } +// RAVEN END + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Fade +================ +*/ +void idLight::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idLight::FadeOut +================ +*/ +void idLight::FadeOut( float time ) { + Fade( colorBlack, time ); +} + +/* +================ +idLight::FadeIn +================ +*/ +void idLight::FadeIn( float time ) { + idVec3 color; + idVec4 color4; + + currentLevel = levels; + spawnArgs.GetVector( "_color", "1 1 1", color ); + color4.Set( color.x, color.y, color.z, 1.0f ); + Fade( color4, time ); +} + +/* +================ +idLight::Killed +================ +*/ +void idLight::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + BecomeBroken( attacker ); +} + +/* +================ +idLight::BecomeBroken +================ +*/ +void idLight::BecomeBroken( idEntity *activator ) { + const char *damageDefName; + + fl.takedamage = false; + + if ( brokenModel.Length() ) { + SetModel( brokenModel ); + + if ( !spawnArgs.GetBool( "nonsolid" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel( brokenModel.c_str() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + } else if ( spawnArgs.GetBool( "hideModelOnBreak" ) ) { + SetModel( "" ); + GetPhysics()->SetContents( 0 ); + } + + if ( gameLocal.isServer ) { + + ServerSendInstanceEvent( EVENT_BECOMEBROKEN, NULL, true, -1 ); + + if ( spawnArgs.GetString( "def_damage", "", &damageDefName ) ) { + idVec3 origin = renderEntity.origin + renderEntity.bounds.GetCenter() * renderEntity.axis; + gameLocal.RadiusDamage( origin, activator, activator, this, this, damageDefName ); + } + + } + + ActivateTargets( activator ); + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + renderLight.shaderParms[ SHADERPARM_MODE ] = 1; + + // if the light has a sound, either start the alternate (broken) sound, or stop the sound + const char *parm = spawnArgs.GetString( "snd_broken" ); + if ( refSound.shader || ( parm && *parm ) ) { + StopSound( SND_CHANNEL_ANY, false ); + const idSoundShader *alternate = refSound.shader ? refSound.shader->GetAltSound() : declManager->FindSound( parm ); + if ( alternate ) { + // start it with no diversity, so the leadin break sound plays + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound( alternate, SND_CHANNEL_ANY, 0.0, 0 ); + } + } + } + + parm = spawnArgs.GetString( "mtr_broken" ); + if ( parm && *parm ) { + SetShader( parm ); + } + + UpdateVisuals(); +} + +/* +================ +idLight::PresentLightDefChange +================ +*/ +void idLight::PresentLightDefChange( void ) { + // let the renderer apply it to the world + if ( ( lightDefHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idLight::PresentModelDefChange +================ +*/ +void idLight::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idLight::Present +================ +*/ +void idLight::Present( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_RENDER ); +// RAVEN END + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + idEntity::Present(); + + // current transformation + renderLight.axis = localLightAxis * GetPhysics()->GetAxis(); + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * localLightOrigin; + + // reference the sound for shader synced effects + if ( lightParent ) { +// RAVEN BEGIN + renderLight.referenceSoundHandle = lightParent->GetSoundEmitter(); + renderEntity.referenceSoundHandle = lightParent->GetSoundEmitter(); + } + else { + renderLight.referenceSoundHandle = refSound.referenceSoundHandle; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; +// RAVEN END + } + + // update the renderLight and renderEntity to render the light and flare + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::Think +================ +*/ +void idLight::Think( void ) { + idVec4 color; + + if ( thinkFlags & TH_THINK ) { + if ( fadeEnd > 0 ) { + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + +// RAVEN BEGIN +// bdube: gui controlled lights + if ( lightGUI ) { + SetColor ( lightGUI->GetRenderEntity()->gui[0]->GetLightColor ( ) ); + } +// RAVEN END + } + + RunPhysics(); + Present(); +} + +/* +================ +idLight::GetPhysicsToSoundTransform +================ +*/ +bool idLight::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = localLightOrigin + renderLight.lightCenter; + axis = localLightAxis * GetPhysics()->GetAxis(); + return true; +} + +/* +================ +idLight::FreeLightDef +================ +*/ +void idLight::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================ +idLight::SaveState +================ +*/ +void idLight::SaveState( idDict *args ) { + int i, c = spawnArgs.GetNumKeyVals(); + for ( i = 0; i < c; i++ ) { + const idKeyValue *pv = spawnArgs.GetKeyVal(i); + if ( pv->GetKey().Find( "editor_", false ) >= 0 || pv->GetKey().Find( "parse_", false ) >= 0 ) { + continue; + } + args->Set( pv->GetKey(), pv->GetValue() ); + } +} + +/* +=============== +idLight::ShowEditingDialog +=============== +*/ +void idLight::ShowEditingDialog( void ) { + if ( g_editEntityMode.GetInteger() == 1 ) { + common->InitTool( EDITOR_LIGHT, &spawnArgs ); + } else { + common->InitTool( EDITOR_SOUND, &spawnArgs ); + } +} + +/* +================ +idLight::Event_SetShader +================ +*/ +void idLight::Event_SetShader( const char *shadername ) { + SetShader( shadername ); +} + +/* +================ +idLight::Event_GetLightParm +================ +*/ +void idLight::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderLight.shaderParms[ parmnum ] ); +} + +/* +================ +idLight::Event_SetLightParm +================ +*/ +void idLight::Event_SetLightParm( int parmnum, float value ) { + SetLightParm( parmnum, value ); +} + +/* +================ +idLight::Event_SetLightParms +================ +*/ +void idLight::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + SetLightParms( parm0, parm1, parm2, parm3 ); +} + +/* +================ +idLight::Event_SetRadiusXYZ +================ +*/ +void idLight::Event_SetRadiusXYZ( float x, float y, float z ) { + SetRadiusXYZ( x, y, z ); +} + +/* +================ +idLight::Event_SetRadius +================ +*/ +void idLight::Event_SetRadius( float radius ) { + SetRadius( radius ); +} + +/* +================ +idLight::Event_Hide +================ +*/ +void idLight::Event_Hide( void ) { + Hide(); + PresentModelDefChange(); + Off(); +} + +/* +================ +idLight::Event_Show +================ +*/ +void idLight::Event_Show( void ) { + Show(); + PresentModelDefChange(); + On(); +} + +/* +================ +idLight::Event_On +================ +*/ +void idLight::Event_On( void ) { + On(); +} + +/* +================ +idLight::Event_Off +================ +*/ +void idLight::Event_Off( void ) { + Off(); +} + +/* +================ +idLight::Event_ToggleOnOff +================ +*/ +void idLight::Event_ToggleOnOff( idEntity *activator ) { +// RAVEN BEGIN +// abahr: + if( wait > 0 ) { + if( EventIsPosted(&EV_Light_Timer) ) { + CancelEvents( &EV_Light_Timer ); + } else { + ProcessEvent( &EV_Light_Timer ); + } + } else { +// RAVEN END + triggercount++; + if ( triggercount < count ) { + return; + } + + // reset trigger count + triggercount = 0; + + if ( breakOnTrigger ) { + BecomeBroken( activator ); + breakOnTrigger = false; + return; + } + + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } +// RAVEN BEGIN +// abahr: + } +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSound::Event_Timer +================ +*/ +void idLight::Event_Timer( void ) { +// FIXME: think about putting this logic in helper function so we don't have cut and pasted code + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } + + PostEventSec( &EV_Light_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idLight::Event_SetSoundHandles + + set the same sound def handle on all targeted lights +================ +*/ +void idLight::Event_SetSoundHandles( void ) { + int i; + idEntity *targetEnt; + + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idLight::Type ) ) { + idLight *light = static_cast(targetEnt); + light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->renderEntity.referenceSoundHandle = renderEntity.referenceSoundHandle; + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } +// RAVEN BEGIN +// rjohnson: func_static's can now have their color parms affected by lights + else if ( targetEnt && targetEnt->IsType( idStaticEntity::GetClassType() ) ) { + targetEnt->GetRenderEntity()->referenceShader = renderLight.shader; + targetEnt->GetRenderEntity()->referenceSoundHandle = renderEntity.referenceSoundHandle; + } +// RAVEN END + } +} + +/* +================ +idLight::Event_FadeOut +================ +*/ +void idLight::Event_FadeOut( float time ) { + FadeOut( time ); +} + +/* +================ +idLight::Event_FadeIn +================ +*/ +void idLight::Event_FadeIn( float time ) { + FadeIn( time ); +} + +/* +================ +idLight::ClientPredictionThink +================ +*/ +void idLight::ClientPredictionThink( void ) { + Think(); +} + +/* +================ +idLight::WriteToSnapshot +================ +*/ +void idLight::WriteToSnapshot( idBitMsgDelta &msg ) const { + + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + + msg.WriteByte( currentLevel ); + msg.WriteLong( PackColor( baseColor ) ); + // msg.WriteBits( lightParent.GetEntityNum(), GENTITYNUM_BITS ); + +/* // only helps prediction + msg.WriteLong( PackColor( fadeFrom ) ); + msg.WriteLong( PackColor( fadeTo ) ); + msg.WriteLong( fadeStart ); + msg.WriteLong( fadeEnd ); +*/ + + // FIXME: send renderLight.shader + msg.WriteFloat( renderLight.lightRadius[0], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[1], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[2], 5, 10 ); + + msg.WriteLong( PackColor( idVec4( renderLight.shaderParms[SHADERPARM_RED], + renderLight.shaderParms[SHADERPARM_GREEN], + renderLight.shaderParms[SHADERPARM_BLUE], + renderLight.shaderParms[SHADERPARM_ALPHA] ) ) ); + + msg.WriteFloat( renderLight.shaderParms[SHADERPARM_TIMESCALE], 5, 10 ); + msg.WriteLong( renderLight.shaderParms[SHADERPARM_TIMEOFFSET] ); + //msg.WriteByte( renderLight.shaderParms[SHADERPARM_DIVERSITY] ); + msg.WriteShort( renderLight.shaderParms[SHADERPARM_MODE] ); + + WriteColorToSnapshot( msg ); +} + +/* +================ +idLight::ReadFromSnapshot +================ +*/ +void idLight::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 shaderColor; + int oldCurrentLevel = currentLevel; + idVec3 oldBaseColor = baseColor; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + currentLevel = msg.ReadByte(); + if ( currentLevel != oldCurrentLevel ) { + // need to call On/Off for flickering lights to start/stop the sound + // while doing it this way rather than through events, the flickering is out of sync between clients + // but at least there is no question about saving the event and having them happening globally in the world + if ( currentLevel ) { + On(); + } else { + Off(); + } + } + UnpackColor( msg.ReadLong(), baseColor ); + // lightParentEntityNum = msg.ReadBits( GENTITYNUM_BITS ); + +/* // only helps prediction + UnpackColor( msg.ReadLong(), fadeFrom ); + UnpackColor( msg.ReadLong(), fadeTo ); + fadeStart = msg.ReadLong(); + fadeEnd = msg.ReadLong(); +*/ + + // FIXME: read renderLight.shader + renderLight.lightRadius[0] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[1] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[2] = msg.ReadFloat( 5, 10 ); + + UnpackColor( msg.ReadLong(), shaderColor ); + renderLight.shaderParms[SHADERPARM_RED] = shaderColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = shaderColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = shaderColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = shaderColor[3]; + + renderLight.shaderParms[SHADERPARM_TIMESCALE] = msg.ReadFloat( 5, 10 ); + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadLong(); + //renderLight.shaderParms[SHADERPARM_DIVERSITY] = msg.ReadFloat(); + renderLight.shaderParms[SHADERPARM_MODE] = msg.ReadShort(); + + ReadColorFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( ( currentLevel != oldCurrentLevel ) || ( baseColor != oldBaseColor ) ) { + SetLightLevel(); + } else { + PresentLightDefChange(); + PresentModelDefChange(); + } + } +} + +/* +================ +idLight::ClientReceiveEvent +================ +*/ +bool idLight::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_BECOMEBROKEN: { + BecomeBroken( NULL ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// kfuller: 8/11/03 +void idLight::Event_IsOn() +{ + // not entirely sure this is the best way to check for offness + if (currentLevel == 0) + { + idThread::ReturnFloat( false ); + } + else + { + idThread::ReturnFloat( true ); + } +} + +void idLight::Event_Break(idEntity *activator, float turnOff) +{ + BecomeBroken(activator); + if (turnOff) + { + Off(); + } +} + +void idLight::Event_DoneBlinking() +{ + // switch to a new (possibly non-blinking) shader for the light as well as a new looping sound + idStr blinkedOn; + if (spawnArgs.GetString( "mtr_doneBlinking", "", blinkedOn )) + { + renderLight.shader = declManager->FindMaterial( blinkedOn, false ); + UpdateVisuals(); + Present(); + } + idStr doneBlinkingSound; + if (spawnArgs.GetBool("doneBlinkingNoSound")) + { + StopSound( SCHANNEL_ANY, false ); + } + else if (spawnArgs.GetString("snd_doneBlinking", "", doneBlinkingSound)) + { + StopSound( SCHANNEL_ANY, false ); + if (doneBlinkingSound.Icmp("none")) + { + refSound.shader = declManager->FindSound(doneBlinkingSound); + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + } +} + +void idLight::Event_DoneBlinkingOff() +{ + // switch light and sound off + currentLevel = 0; + SetLightLevel(); +// RAVEN BEGIN + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying ( ) ) { +// RAVEN END + StopSound( SCHANNEL_ANY, false ); + soundWasPlaying = true; + } +} + +// kfuller: want fx entities to be able to respond to earthquakes +void idLight::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + + if (requiresLOS) + { + bool inPVS = gameLocal.InPlayerPVS( this ); + + // for lights, a line-of-sight check doesn't make as much sense, so if the quake requires an LOS check + //we'll actually perform a PVS check + if (!inPVS) + { + return; + } + } + // do something with this light + if (spawnArgs.GetBool("quakeBreak")) + { + spawnArgs.SetBool("quakeBreak", false); + BecomeBroken(gameLocal.entities[ENTITYNUM_WORLD]); + return; + } + + float offTime = spawnArgs.GetFloat("quakeOffTime", "1.0"); + + Off(); + PostEventMS(&EV_Light_On, offTime*1000.0f); +} + +/* +================ +idLight::Event_SetLightGUI +================ +*/ +void idLight::Event_SetLightGUI ( const char* gui ) { + lightGUI = gameLocal.FindEntity( gui ); + if ( lightGUI && lightGUI->GetRenderEntity() && lightGUI->GetRenderEntity()->gui[0] ) { + BecomeActive( TH_THINK ); + } else { + lightGUI = NULL; + } +} + +/* +================ +idLight::Event_SetCurrentLightLevel +================ +*/ +void idLight::Event_SetCurrentLightLevel( int in ) { + currentLevel = in; + SetLightLevel(); +} + +/* +================ +idLight::Event_SetMaxLightLevel +================ +*/ +void idLight::Event_SetMaxLightLevel( int in ) { + levels = in; + SetLightLevel(); +} + +// RAVEN END diff --git a/source/mpgame/Light.h b/source/mpgame/Light.h new file mode 100644 index 0000000..72cfd08 --- /dev/null +++ b/source/mpgame/Light.h @@ -0,0 +1,146 @@ +#ifndef __GAME_LIGHT_H__ +#define __GAME_LIGHT_H__ + +/* +=============================================================================== + + Generic light. + +=============================================================================== +*/ + +extern const idEventDef EV_Light_GetLightParm; +extern const idEventDef EV_Light_SetLightParm; +extern const idEventDef EV_Light_SetLightParms; + +class idLight : public idEntity { +public: + CLASS_PROTOTYPE( idLight ); + + idLight(); + ~idLight(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + virtual void Think( void ); + virtual void FreeLightDef( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + void Present( void ); + + void SaveState( idDict *args ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void GetColor( idVec4 &out ) const; + const idVec3 & GetBaseColor( void ) const { return baseColor; } + void SetShader( const char *shadername ); + void SetLightParm( int parmnum, float value ); + void SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void SetRadiusXYZ( float x, float y, float z ); + void SetRadius( float radius ); + void On( void ); + void Off( void ); + void Fade( const idVec4 &to, float fadeTime ); + void FadeOut( float time ); + void FadeIn( float time ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void BecomeBroken( idEntity *activator ); + qhandle_t GetLightDefHandle( void ) const { return lightDefHandle; } + void SetLightParent( idEntity *lparent ) { lightParent = lparent; } + void SetLightLevel( void ); + +// RAVEN BEGIN +// jshepard: other entities (speakers) need access to the refSound of a light object + void SetRefSound( int rSound ) { refSound.referenceSoundHandle = rSound;} +// ddynerman: sometimes the game needs to know if this light is ambient + bool IsAmbient( void ) { return renderLight.shader->IsAmbientLight(); } +// RAVEN END + virtual void ShowEditingDialog( void ); + + enum { + EVENT_BECOMEBROKEN = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + renderLight_t renderLight; // light presented to the renderer + idVec3 localLightOrigin; // light origin relative to the physics origin + idMat3 localLightAxis; // light axis relative to physics axis + qhandle_t lightDefHandle; // handle to renderer light def + idStr brokenModel; + int levels; + int currentLevel; + idVec3 baseColor; + bool breakOnTrigger; + int count; + int triggercount; + idEntity * lightParent; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; +// RAVEN BEGIN +// bdube: light gui + idEntityPtr lightGUI; +// abahr: + float wait; + float random; +// RAVEN END + +private: + bool soundWasPlaying; + + void PresentLightDefChange( void ); + void PresentModelDefChange( void ); + +// RAVEN BEGIN +// jscott: added events for light level +private: + void Event_SetCurrentLightLevel ( int in ); + void Event_SetMaxLightLevel ( int in ); + void Event_IsOn( void ); + void Event_Break( idEntity *activator, float turnOff ); + void Event_DoneBlinking( void ); + void Event_DoneBlinkingOff( void ); + void Event_EarthQuake( float requiresLOS ); + void Event_Timer( void ); +// RAVEN END + +private: + void Event_SetShader( const char *shadername ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetRadiusXYZ( float x, float y, float z ); + void Event_SetRadius( float radius ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_On( void ); + void Event_Off( void ); + void Event_ToggleOnOff( idEntity *activator ); + void Event_SetSoundHandles( void ); + void Event_FadeOut( float time ); + void Event_FadeIn( float time ); +// RAVEN BEGIN +// bdube: set light gui + void Event_SetLightGUI( const char* gui ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: externed events +extern const idEventDef EV_Light_SetCurrentLightLevel; +extern const idEventDef EV_Light_SetMaxLightLevel; +extern const idEventDef EV_Light_SetRadius; +// RAVEN END + +#endif /* !__GAME_LIGHT_H__ */ diff --git a/source/mpgame/LipSync.cpp b/source/mpgame/LipSync.cpp new file mode 100644 index 0000000..2854fcf --- /dev/null +++ b/source/mpgame/LipSync.cpp @@ -0,0 +1,467 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// 1 normal +// 2 scared +// 3 surprised +// 4 panicked +// 5 angry +// 6 suspicious with rt eyelid raised +// 7 suspicious with lft eyelid raised +// 8 curious +// 9 tired +// 10 happy + +idCVar fas_debug( "fas_debug", "0", CVAR_INTEGER, "debug info for facial animation system" ); +idCVar fas_threshhold0( "fas_threshhold0", "60", CVAR_INTEGER, "intensity required to use frame set 0" ); +idCVar fas_threshhold1( "fas_threshhold1", "30", CVAR_INTEGER, "intensity required to use frame set 1" ); +idCVar fas_blendBias( "fas_blendBias", "1.5", 0, "multiplier to the per phoneme blend time" ); +idCVar fas_intensityBias( "fas_intensityBias", "0", CVAR_INTEGER, "bias applied to the intensity of the phoneme when trying to extract the viseme" ); +idCVar fas_timeOffset( "fas_timeOffset", "50", CVAR_INTEGER, "ms offset to the viseme frame" ); + +idStr phonemeFile; +idHashTable *visemeTable100; +idHashTable *visemeTable66; +idHashTable *visemeTable33; + +/* +================ +rvViseme::Init +================ +*/ +void rvViseme::Init( idStr &phon, int f, int bt ) +{ + phoneme = phon; + frame = f; + blendTime = bt; +} + +/* +================ +FAS_LoadPhonemes + +Load in the the file that cross references phonemes with visemes. +================ +*/ +bool FAS_LoadPhonemes( const char *visemes ) +{ + idStr visemeFile; + rvViseme viseme; + idLexer lexer; + idToken token; + idStr phoneme; + int frame, blendTime, intensity; + + phonemeFile = visemes; + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + + common->Printf( "Loading viseme file: %s\n", visemes ); + + visemeFile = "lipsync/"; + visemeFile += visemes; + visemeFile += ".viseme"; + + lexer.SetFlags( DECL_LEXER_FLAGS ); + lexer.LoadFile( visemeFile ); + + if( !lexer.ExpectTokenString( "visemes" ) ) + { + return( false ); + } + + if( !lexer.ExpectTokenString( "{" ) ) + { + return( false ); + } + + while( true ) + { + if( !lexer.ReadToken( &token ) ) + { + return( false ); + } + + if( token == "}" ) + { + break; + } + + phoneme = token; + lexer.ExpectTokenString( "," ); + frame = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + blendTime = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + intensity = lexer.ParseInt(); + + viseme.Init( phoneme, frame, blendTime ); + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Set( phoneme, viseme ); + } + else if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Set( phoneme, viseme ); + } + else + { + visemeTable33->Set( phoneme, viseme ); + } + } + + return( true ); +} + +/* +================ +rvLipSyncData +================ +*/ +rvLipSyncData::rvLipSyncData( const rvDeclLipSync *ls, int time ) +{ + const char *lsd = ls->GetLipSyncData(); + + mLexer.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWNUMBERNAMES ); + mLexer.LoadMemory( lsd, idStr::Length( lsd ), ls->GetName() ); + mFlags = 0; + mFrame = 0; + mBlendTime = 0; + mEmotion = "idle"; + mNextTokenTime = time; +} + +void rvLipSyncData::SetFrame( int frame ) +{ + mLastFrame = mFrame; + mFrame = frame; + mVisemeStartTime = mNextTokenTime; +} + +float rvLipSyncData::GetFrontLerp( void ) +{ + float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( float )( gameLocal.GetTime() + fas_timeOffset.GetInteger() - mVisemeStartTime ) / ( float )mBlendTime ); + return( lerp ); +} + +/* +================ +FAS_StartVisemeExtraction + +Use a lexer to extract the phoneme data. This is a pretty slow but safe way. +There shouldn't be much in the way of multiple lip syncs going on, and if there are, it should be in a non performance critical cinematic. +================ +*/ +rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ) +{ + rvLipSyncData *lsd; + + lsd = new rvLipSyncData( ls, time ); + + return( lsd ); +} + +/* +================ +FAS_EndVisemeExtraction + +Delete the workspace. FAS could use a void pointer if it wanted +================ +*/ +void FAS_EndVisemeExtraction( rvLipSyncData *lsd ) +{ + delete lsd; +} + +/* +================ +FAS_ExtractViseme + +Extract the correct viseme frame from the lexer +================ +*/ +void FAS_ExtractViseme( rvLipSyncData *lsd, int time ) +{ + idToken token; + rvViseme *viseme; + idStr phoneme, duration; + int index, intensity; + + // Make sure not to return any garbage + lsd->ClearFlags(); + + // Grab all the visemes, phrases and emotions until we are current + while( lsd->Ready( time ) ) + { + if( !lsd->ReadToken( &token ) ) + { + lsd->SetFlags( FAS_ENDED ); + return; + } + + if( token == "<" ) + { + // Extract phrase + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse phrase from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetLastPhrase( token ); + lsd->ExpectTokenString( ">" ); + + lsd->SetFlags( FAS_NEW_PHRASE ); + } + else if( token == "{" ) + { + // Extract emotion + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse emotion from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetEmotion( token ); + lsd->ExpectTokenString( "}" ); + + lsd->SetFlags( FAS_NEW_EMOTION ); + } + else + { + // Extract phoneme data + index = 0; + phoneme = idStr( token[index] ); + if( isupper( phoneme[0] ) ) + { + index++; + phoneme += token[index]; + } + + // Extract duration + index++; + + duration = idStr( token[index] ); + index++; + if( isdigit( token[index] ) ) + { + duration += token[index]; + index++; + } + + // Extract intensity + intensity = ( token[index] - 'a' ) * 4; + intensity += fas_intensityBias.GetInteger(); + + // Extract the viseme data for the selected viseme + viseme = NULL; + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Get( phoneme, &viseme ); + } + if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Get( phoneme, &viseme ); + } + else + { + visemeTable33->Get( phoneme, &viseme ); + } + + if( !viseme ) + { + common->Printf( "FAS: Failed to find phoneme %s intensity %d", phoneme.c_str(), intensity ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetFrame( viseme->GetFrame() ); + lsd->SetNextTokenTime( atol( duration ) * 10 ); + lsd->SetBlendTime( int( viseme->GetBlendTime() * fas_blendBias.GetFloat() ) ); + lsd->SetFlags( FAS_NEW_VISEME ); + } + } +} + +/* +================ +FAS_Reload_f +================ +*/ +void FAS_Reload_f( const idCmdArgs &args ) +{ + // mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + + FAS_LoadPhonemes( phonemeFile.c_str() ); + + fileSystem->SetIsFileLoadingAllowed( false ); +} + +/* +================ +FAS_Init +================ +*/ +bool FAS_Init( const char *visemes ) +{ + // jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_ANIM ); + + cmdSystem->AddCommand( "reloadFAS", FAS_Reload_f, CMD_FL_SYSTEM, "reloads the viseme data" ); + return( FAS_LoadPhonemes( visemes ) ); +} + +/* +================ +FAS_Shutdown +================ +*/ +void FAS_Shutdown( void ) +{ + phonemeFile.Clear(); + if ( visemeTable100 ) { + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + } + + cmdSystem->RemoveCommand( "reloadFAS" ); +} + +/* +================ +idAFAttachment::EndLipSyncing +================ +*/ +void idAFAttachment::EndLipSyncing( void ) +{ + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0.0f }; + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + animator.CycleAnim( ANIMCHANNEL_HEAD, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + + FAS_EndVisemeExtraction( lipSyncData ); + lipSyncData = NULL; +} + +/* +================ +idAFAttachment::StartLipSyncing +================ +*/ +int idAFAttachment::StartLipSyncing( const char *speechDecl ) +{ + int length; + + length = 0; + + // Clean up any spurious data + EndLipSyncing(); + + // Start a new lipsync if there is one + if( speechDecl[0] ) + { + const rvDeclLipSync *lipSync; + int emotion; + idStr anim; + + lipSync = declManager->FindLipSync( speechDecl ); + lipSyncData = FAS_StartVisemeExtraction( lipSync, gameLocal.GetTime() ); + + // Output debug info + if( lipSync->GetDescription() && fas_debug.GetInteger() ) + { + gameLocal.Printf( "Name: %s\n", speechDecl ); + gameLocal.Printf( "Sub: %s\n", lipSync->GetDescription().c_str() ); + gameLocal.Printf( "Lip: %s\n", lipSync->GetLipSyncData() ); + } + + // Start the associated sound + refSound.diversity = 0.0f; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + StartSoundShader( declManager->FindSound( lipSync->GetName() ), SND_CHANNEL_VOICE, refSound.parms.soundShaderFlags | SSF_IS_VO, false, &length ); + + // Start the default emotion + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + } + + return( length ); +} + +/* +================ +idAFAttachment::HandleLipSync +================ +*/ +void idAFAttachment::HandleLipSync( void ) +{ + idStr anim; + int emotion; + + if( !lipSyncData ) + { + return; + } + + FAS_ExtractViseme( lipSyncData, gameLocal.GetTime() + fas_timeOffset.GetInteger() ); + if( lipSyncData->HasEnded() ) + { + EndLipSyncing(); + return; + } + + // If frame non zero - blend to it as a new viseme + if( lipSyncData->GetFrame() || lipSyncData->GetLastFrame() ) + { + frameBlend_t frameBlend; + + frameBlend.cycleCount = 0; + frameBlend.frame1 = idMath::ClampInt( 0, 120, lipSyncData->GetLastFrame() - 1 ); + frameBlend.frame2 = idMath::ClampInt( 0, 120, lipSyncData->GetFrame() - 1 ); + frameBlend.frontlerp = lipSyncData->GetFrontLerp(); + frameBlend.backlerp = 1.0f - frameBlend.frontlerp; + + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + if( fas_debug.GetInteger() > 1 ) + { + common->Printf( "Blending: %d (%2f) -> %d (%2f)\n", frameBlend.frame1, frameBlend.frontlerp, frameBlend.frame2, frameBlend.backlerp ); + } + } + + // If an embedded emotion command, play it. + if( lipSyncData->HasNewEmotion() ) + { + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + + if( fas_debug.GetInteger() ) + { + common->Printf( "Emotion: %s\n", lipSyncData->GetEmotion().c_str() ); + } + } + + // If a new phrase, display in debug mode + if( fas_debug.GetInteger() && lipSyncData->HasNewPhrase() ) + { + common->Printf( "Phrase: %s(%i)\n", lipSyncData->GetLastPhrase().c_str(), lipSyncData->GetFrame() ); + } +} + +// end diff --git a/source/mpgame/LipSync.h b/source/mpgame/LipSync.h new file mode 100644 index 0000000..59dd52e --- /dev/null +++ b/source/mpgame/LipSync.h @@ -0,0 +1,87 @@ +#ifndef _LIPSYNC_H_INC_ +#define _LIPSYNC_H_INC_ + +// Possible TBDs +// Auto head swapping from simple to lip sync head +// Return time of sound from the speak call for the script to wait +// Set emotion directly without encoding it into the string + +class rvViseme +{ +public: + rvViseme( void ) {} + ~rvViseme( void ) { phoneme.Clear(); } + + void Init( idStr &phon, int f, int bt ); + + int GetFrame( void ) const { return( frame ); } + int GetBlendTime( void ) const { return( blendTime ); } + +private: + idStr phoneme; + int frame; + int blendTime; +}; + +#define FAS_NEW_VISEME BIT( 0 ) +#define FAS_NEW_PHRASE BIT( 1 ) +#define FAS_NEW_EMOTION BIT( 2 ) +#define FAS_ENDED BIT( 3 ) + +class rvLipSyncData +{ +public: + rvLipSyncData( const rvDeclLipSync *ls, int time ); + ~rvLipSyncData( void ) {} + + bool Ready( int time ) { return( time >= mNextTokenTime ); } + + int ReadToken( idToken *token ) { return( mLexer.ReadToken( token ) ); } + int ExpectTokenString( const char *str ) { return( mLexer.ExpectTokenString( str ) ); } + void SetNextTokenTime( int time ) { mNextTokenTime += time; } + + void ClearFlags( void ) { mFlags = 0; } + void SetFlags( int flags ) { mFlags |= flags; } + bool HasNewPhoneme( void ) const { return( !!( mFlags & FAS_NEW_VISEME ) ); } + bool HasNewPhrase( void ) const { return( !!( mFlags & FAS_NEW_PHRASE ) ); } + bool HasNewEmotion( void ) const { return( !!( mFlags & FAS_NEW_EMOTION ) ); } + bool HasEnded( void ) const { return( !!( mFlags & FAS_ENDED ) ); } + + void SetFrame( int frame ); + int GetFrame( void ) const { return( mFrame ); } + int GetLastFrame( void ) { return( mLastFrame ); } + + void SetBlendTime( int bt ) { mBlendTime = bt; } + int GetBlendTime( void ) const { return( mBlendTime ); } + + float GetFrontLerp( void ); + + void SetEmotion( idStr &str ) { mEmotion = str; } + const idStr &GetEmotion( void ) const { return( mEmotion ); } + + void SetLastPhrase( idStr &str ) { mLastPhrase = str; } + const idStr &GetLastPhrase( void ) const { return( mLastPhrase ); } + +private: + int mNextTokenTime; + int mFlags; + int mFrame; + int mLastFrame; + int mVisemeStartTime; + int mBlendTime; + idStr mEmotion; + idStr mLastPhrase; + idLexer mLexer; +}; + +bool FAS_Init( const char *visemes ); +void FAS_Shutdown( void ); + +void FAS_ExtractViseme( class rvLipSyncData *lsd, int time ); +class rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ); +void FAS_EndVisemeExtraction( class rvLipSyncData *lsd ); + +extern idCVar fas_debug; +extern idCVar fas_timeOffset; + +#endif // _LIPSYNC_H_INC_ diff --git a/source/mpgame/Misc.cpp b/source/mpgame/Misc.cpp new file mode 100644 index 0000000..6f2d333 --- /dev/null +++ b/source/mpgame/Misc.cpp @@ -0,0 +1,4383 @@ +/* + +Various utility objects and functions. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// nmckenzie: added ai +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#include "ai/AI.h" +// RAVEN END + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpawnableEntity ) +END_CLASS + +/* +====================== +idSpawnableEntity::Spawn +====================== +*/ +void idSpawnableEntity::Spawn() { + // this just holds dict information +} + + +/* +=============================================================================== + + idPlayerStart + +=============================================================================== +*/ + +const idEventDef EV_TeleportStage( "", "e" ); + +CLASS_DECLARATION( idEntity, idPlayerStart ) + EVENT( EV_Activate, idPlayerStart::Event_Teleport ) + EVENT( EV_TeleportStage, idPlayerStart::Event_TeleportStage ) +END_CLASS + +/* +=============== +idPlayerStart::idPlayerStart +================ +*/ +idPlayerStart::idPlayerStart( void ) { + teleportStage = 0; +} + +/* +=============== +idPlayerStart::Spawn +================ +*/ +void idPlayerStart::Spawn( void ) { + teleportStage = 0; +} + +/* +================ +idPlayerStart::Save +================ +*/ +void idPlayerStart::Save( idSaveGame *savefile ) const { + savefile->WriteInt( teleportStage ); +} + +/* +================ +idPlayerStart::Restore +================ +*/ +void idPlayerStart::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( teleportStage ); +} + +/* +================ +idPlayerStart::ClientReceiveEvent +================ +*/ +bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int entityNumber; + + switch( event ) { + case EVENT_TELEPORTPLAYER: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); + float x = msg.ReadFloat(); + float y = msg.ReadFloat(); + float z = msg.ReadFloat(); + + idVec3 prevOrigin( x, y, z ); + if ( player != NULL && player->IsType( idPlayer::GetClassType() ) ) { + Event_TeleportEntity( player, false, prevOrigin ); + } + return true; + } + case EVENT_TELEPORTITEM: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idEntity* activator = gameLocal.entities[ entityNumber ]; + + if ( activator != NULL ) { + Event_TeleportEntity( activator, false ); + } + return true; + } + + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +=============== +idPlayerStart::Event_TeleportStage + +FIXME: add functionality to fx system ( could be done with player scripting too ) +================ +*/ +void idPlayerStart::Event_TeleportStage( idPlayer *player ) { + float teleportDelay = spawnArgs.GetFloat( "teleportDelay" ); + switch ( teleportStage ) { + case 0: + player->playerView.Flash( colorWhite, 125 ); + player->SetInfluenceLevel( INFLUENCE_LEVEL3 ); + player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -20.0f, teleportDelay ); + teleportStage++; + PostEventSec( &EV_TeleportStage, teleportDelay, player ); + break; + case 1: + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0.25f ); + teleportStage++; + PostEventSec( &EV_TeleportStage, 0.25f, player ); + break; + case 2: + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + TeleportPlayer( player ); + player->StopSound( SND_CHANNEL_BODY2, false ); + player->SetInfluenceLevel( INFLUENCE_NONE ); + teleportStage = 0; + break; + default: + break; + } +} + +/* +=============== +idPlayerStart::TeleportPlayer +================ +*/ +void idPlayerStart::TeleportPlayer( idPlayer *player ) { + float pushVel = spawnArgs.GetFloat( "push", "50000" ); + float f = spawnArgs.GetFloat( "visualEffect", "0" ); + const char *viewName = spawnArgs.GetString( "visualView", "" ); + idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL; + + if ( f && ent && !player->spectating ) { + // place in private camera view for some time + // the entity needs to teleport to where the camera view is to have the PVS right + player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this ); + + player->SetPrivateCameraView( static_cast(ent) ); + // the player entity knows where to spawn from the previous Teleport call + if ( !gameLocal.isClient ) { + player->PostEventSec( &EV_Player_ExitTeleporter, f ); + } + } else { + // direct to exit, Teleport will take care of the killbox + player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL ); + + // multiplayer hijacked this entity, so only push the player in multiplayer + if ( gameLocal.isMultiplayer ) { + // push + idVec3 impulse( pushVel, 0, 0 ); + impulse *= GetPhysics( )->GetAxis ( ); + player->ApplyImpulse( gameLocal.world, 0, player->GetPhysics( )->GetOrigin( ), impulse ); + } + } +} + +/* +=============== +idPlayerStart::Teleport +For non-players +================ +*/ +void idPlayerStart::Teleport( idEntity* other ) { + other->SetOrigin( GetPhysics()->GetOrigin() ); + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + vel *= GetPhysics()->GetAxis(); + other->GetPhysics()->SetLinearVelocity( vel ); +} + +void idPlayerStart::Event_TeleportEntity( idEntity* activator, bool predicted, idVec3& prevOrigin ) { + idPlayer *player; + + if ( activator->IsType( idPlayer::GetClassType() ) ) { + player = static_cast( activator ); + } else { + player = NULL; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( activator->entityNumber, GENTITYNUM_BITS ); + ServerSendInstanceEvent( EVENT_TELEPORTITEM, &msg, false, -1 ); + } + + idVec3 oldOrigin = activator->GetPhysics()->GetOrigin(); + Teleport( activator ); + + if( gameLocal.isMultiplayer ) { + if( gameLocal.isServer ) { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", activator->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else if( predicted ) { + // only predict teleport enter + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + + if ( player ) { + if ( spawnArgs.GetBool( "visualFx" ) ) { + + teleportStage = 0; + Event_TeleportStage( player ); + + } else { + idVec3 oldOrigin; + + if ( gameLocal.isServer || prevOrigin == vec3_zero ) { + oldOrigin = activator->GetPhysics()->GetOrigin(); + } else { + oldOrigin = prevOrigin; + } + + if ( gameLocal.isServer && !player->spectating ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + msg.WriteFloat( oldOrigin.x ); + msg.WriteFloat( oldOrigin.y ); + msg.WriteFloat( oldOrigin.z ); + ServerSendInstanceEvent( EVENT_TELEPORTPLAYER, &msg, false, -1 ); + } + + TeleportPlayer( player ); + + if ( gameLocal.isMultiplayer ) { + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport", player->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + } + + +/* +=============== +idPlayerStart::Event_TeleportPlayer +================ +*/ +void idPlayerStart::Event_Teleport( idEntity *activator ) { + Event_TeleportEntity( activator, true ); +} + +/* +=============================================================================== + + idActivator + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idActivator ) + EVENT( EV_Activate, idActivator::Event_Activate ) +END_CLASS + +/* +=============== +idActivator::Save +================ +*/ +void idActivator::Save( idSaveGame *savefile ) const { + savefile->WriteBool( stay_on ); +} + +/* +=============== +idActivator::Restore +================ +*/ +void idActivator::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( stay_on ); + + if ( stay_on ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Spawn +================ +*/ +void idActivator::Spawn( void ) { + bool start_off; + + spawnArgs.GetBool( "stay_on", "0", stay_on ); + spawnArgs.GetBool( "start_off", "0", start_off ); + +// RAVEN BEGIN +// bdube: optional clip model on activators + const char *temp; + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel(temp), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } else { + GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f ); + } + + GetPhysics()->SetContents( 0 ); +// RAVEN END + + if ( !start_off ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Think +================ +*/ +void idActivator::Think( void ) { + RunPhysics(); + if ( thinkFlags & TH_THINK ) { + if ( TouchTriggers() ) { + if ( !stay_on ) { + BecomeInactive( TH_THINK ); + } + } + } + Present(); +} + +/* +=============== +idActivator::Activate +================ +*/ +void idActivator::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + +/* +=============================================================================== + +idPathCorner + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPathCorner ) +// EVENT( AI_RandomPath, idPathCorner::Event_RandomPath ) +END_CLASS + +/* +===================== +idPathCorner::Spawn +===================== +*/ +void idPathCorner::Spawn( void ) { +} + +/* +===================== +idPathCorner::DrawDebugInfo +===================== +*/ +void idPathCorner::DrawDebugInfo( void ) { + idEntity *ent; + idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idPathCorner::Type ) ) { + continue; + } + + idVec3 org = ent->GetPhysics()->GetOrigin(); + gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 ); + } +} + +/* +============ +idPathCorner::RandomPath +============ +*/ +idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) { + int i; + int num; + int which; + idEntity *ent; + idPathCorner *path[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < source->targets.Num(); i++ ) { + ent = source->targets[ i ].GetEntity(); + if ( ent && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) { + path[ num++ ] = static_cast( ent ); + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + return NULL; + } + + which = gameLocal.random.RandomInt( num ); + return path[ which ]; +} + +/* +===================== +idPathCorner::Event_RandomPath +===================== +*/ +void idPathCorner::Event_RandomPath( void ) { + idPathCorner *path; + + path = RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +=============================================================================== + + idDamagable + +=============================================================================== +*/ + +const idEventDef EV_RestoreDamagable( "" ); +// RAVEN BEGIN +// kfuller: spawn other things +const idEventDef EV_SpawnForcefield( "" ); +const idEventDef EV_SpawnTriggerHurt( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idDamagable ) + EVENT( EV_Activate, idDamagable::Event_BecomeBroken ) + EVENT( EV_RestoreDamagable, idDamagable::Event_RestoreDamagable ) +END_CLASS + +/* +================ +idDamagable::idDamagable +================ +*/ +idDamagable::idDamagable( void ) { + count = 0; + nextTriggerTime = 0; + invincibleTime = 0; +} + +/* +================ +idDamagable::Save +================ +*/ +void idDamagable::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( invincibleTime ); + + savefile->WriteInt( stage ); + savefile->WriteInt( stageNext ); + + // cnicholson: Don't save the stageDict, its setup in the restore + + savefile->WriteInt( stageEndTime ); + savefile->WriteInt( stageEndHealth ); + savefile->WriteInt( stageEndSpeed ); + savefile->WriteBool( stageEndOnGround ); + savefile->WriteBool( activateStageOnTrigger ); + + savefile->WriteInt( count ); + savefile->WriteInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Restore +================ +*/ +void idDamagable::Restore( idRestoreGame *savefile ) { + + const char* stageName; + + savefile->ReadInt( invincibleTime ); + + savefile->ReadInt( stage ); + savefile->ReadInt( stageNext ); + savefile->ReadInt( stageEndTime ); + savefile->ReadInt( stageEndHealth ); + savefile->ReadInt( stageEndSpeed ); + savefile->ReadBool( stageEndOnGround ); + savefile->ReadBool( activateStageOnTrigger ); + + savefile->ReadInt( count ); + savefile->ReadInt( nextTriggerTime ); + + + // Get the stage name, if there is none then there are no more stages + stageDict = NULL; + if ( spawnArgs.GetString ( va( "def_stage%d", stage ), "", &stageName ) && stageName ) { + + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + } +} + +/* +================ +idDamagable::Spawn +================ +*/ +void idDamagable::Spawn( void ) { + idStr broken; + bool keepContents; + +// RAVEN BEGIN +// abahr: stage stuff + stage = 0; + stageNext = 1; + stageDict = NULL; + stageEndTime = 0; + stageEndHealth = 9999; + stageEndSpeed = 0; +//jshepard: + stageEndOnGround = false; +// RAVEN END + + health = spawnArgs.GetInt( "health", "5" ); + +// RAVEN BEGIN +// abahr: stage stuff + stageEndHealth = health - 1; + activateStageOnTrigger = spawnArgs.GetBool("activateOnTrigger"); +// RAVEN END + + spawnArgs.GetInt( "count", "1", count ); + invincibleTime = gameLocal.GetTime() + SEC2MS( spawnArgs.GetFloat( "invincibleTime", "0" ) ); + nextTriggerTime = 0; + + // make sure the model gets cached + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) { + gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() ); + } + +// RAVEN BEGIN +// bdube: turn take damage off if it has no health + fl.takedamage = health > 0 ? true : false; +// RAVEN END + + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idDamagable::BecomeBroken +================ +*/ +void idDamagable::BecomeBroken( idEntity *activator ) { + float forceState; + int numStates; + int cycle; + float wait; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + spawnArgs.GetFloat( "wait", "0.1", wait ); + nextTriggerTime = gameLocal.time + SEC2MS( wait ); + if ( count > 0 ) { + count--; + if ( !count ) { + fl.takedamage = false; + } else { + health = spawnArgs.GetInt( "health", "5" ); + } + } + + idStr broken; + + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() ) { + SetModel( broken ); + } + + // offset the start time of the shader to sync it to the gameLocal time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetInt( "numstates", "1", numStates ); + spawnArgs.GetInt( "cycle", "0", cycle ); + spawnArgs.GetFloat( "forcestate", "0", forceState ); + + // set the state parm + if ( cycle ) { + renderEntity.shaderParms[ SHADERPARM_MODE ]++; + if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = 0; + } + } else if ( forceState ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState; + } else { + renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1; + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + ActivateTargets( activator ); + + if ( spawnArgs.GetBool( "hideWhenBroken" ) ) { + Hide(); + PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time ); + BecomeActive( TH_THINK ); + } +} + +/* +============ +idMoveable::Damage +============ +*/ +// RAVEN BEGIN +// abahr +void idDamagable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if ( invincibleTime > gameLocal.GetTime() ) { + return; + } + + // If there is a damage filter we need to check to see if the damage def meets the requirement + const char* damageFilter; + if ( spawnArgs.GetString ( "damage_filter", "", &damageFilter ) && *damageFilter ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + // If the filter isnt matched then ignore it + if ( !damageDef->GetBool ( va("filter_%s", damageFilter ) ) ) { + return; + } + } + if ( spawnArgs.GetBool( "nosplash", "0" ) ) { + //ignore splash damage + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + //if it has radius, then it's splash damage and ignore it + if ( damageDef->GetFloat ( "radius", "0" ) ) { + return; + } + } + + idEntity::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Force a stage update so impact effects will be correct + UpdateStage(); +} + +/* +================ +idDamagable::Killed +================ +*/ +void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + BecomeBroken( attacker ); +} + +/* +================ +idDamagable::UpdateStage +================ +*/ +void idDamagable::UpdateStage ( void ) { + + // If there is a stage to go to then see if its time to do that + if ( stage != stageNext ) { + int oldstage; + + // Check to see if the stage is complete + oldstage = stage; + while ( health < stageEndHealth || (stageEndTime != 0 && gameLocal.time > stageEndTime ) || (stageEndOnGround && GetPhysics()->HasGroundContacts()) || activateStageOnTrigger ) { + stage = stageNext; + + //this only needs to happen once + activateStageOnTrigger = false; + + // Get the stage name, if there is none then there are no more stages + const char* stageName; + if ( !spawnArgs.GetString ( va("def_stage%d", stage ), "", &stageName ) || !stageName ) { + stage = 0; + break; + } + + // Get the stage dictionary + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + if ( !stageDict ) { + gameLocal.Warning ( "could not find stage '%s' for moveable '%s'", stageName, name.c_str() ); + stage = 0; + break; + } + + // Get the end conditions of the stage + stageEndHealth = stageDict->GetInt ( "end_health", "-9999" ); + + stageEndOnGround = stageDict->GetBool( "end_onGround" ); + + // Timed? + stageEndTime = SEC2MS ( GetStageFloat ( "end_time" ) ); + if ( stageEndTime > 0 ) { + stageEndTime += gameLocal.time; + } + + // Set the next stage to move to when end conditions met + stageNext = stage + 1; + + // Execute the new stage + ExecuteStage ( ); + } + } +} + +/* +================ +idDamagable::ExecuteStage +================ +*/ +void idDamagable::ExecuteStage ( void ) { + const idKeyValue* kv; + bool remove; + + // Remove the entity this frame? + remove = stageDict->GetBool ( "remove" ); + + // Targets? + if ( stageDict->GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + // Unbind targets + if ( stageDict->GetBool( "unbindTargets" ) ) { + UnbindTargets( this ); + } + + // Stop current looping effects + StopAllEffects ( ); + + // Play all effects (if they start with "fx_loop" then also loop them + for ( kv = stageDict->MatchPrefix ( "fx_" ); kv; kv = stageDict->MatchPrefix("fx_",kv) ) { + if ( !kv->GetKey().Icmpn ( "fx_loop", 7 ) ) { + if ( !remove ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, kv->GetKey() ), + renderEntity.origin + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() * renderEntity.axis, + true ); + } + } else { + gameLocal.PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_explode" ), + GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ) * GetPhysics()->GetAxis(), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() ); + } + } + + // Kick off debris + for ( kv = stageDict->MatchPrefix ( "def_debris" ); kv; kv = stageDict->MatchPrefix("def_debris",kv) ) { + const idDict* args = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !args ) { + continue; + } + + rvClientMoveable* cent = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + continue; + } + cent->SetOrigin ( GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis() ); + cent->SetAxis ( GetPhysics()->GetAxis ( ) ); + + idVec3 vel; + vel = GetStageVector ( va("vel_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis(); + vel += GetPhysics()->GetLinearVelocity ( ); + cent->GetPhysics()->SetLinearVelocity ( vel ); + cent->PostEventMS ( &CL_FadeOut, 2500, 2500 ); + } + + // Apply force to all objects nearby + float fPush; + stageDict->GetFloat("radiusPush","0",fPush); + if (fPush > 0) { + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 128, fPush, this, this, 1.0f, true ); + } + + // Remove the entity now? + if ( remove ) { + Hide ( ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Switch model? + const char* model; + if ( stageDict->GetString ( "model", "", &model ) && *model ) { + SetModel( model ); + } + + // Skin? + const char* skin; + if ( stageDict->GetString ( "skin", "", &skin ) && *skin ) { + renderEntity.customSkin = declManager->FindSkin ( skin, false ); + } + + // Velocities + idVec3 vel; + vel = GetStageVector ( "vel_world" ); + vel += (GetStageVector ( "vel_local" ) * GetPhysics()->GetAxis() ); + vel += GetPhysics()->GetLinearVelocity ( ); + GetPhysics()->SetLinearVelocity ( vel ); + + // Enable thinking to ensure stages are run + BecomeActive ( TH_THINK ); +} + +/* +================ +idDamagable::GetStageVector +================ +*/ +idVec3 idDamagable::GetStageVector ( const char* key, const char* defaultString ) const { + idVec3 mins; + if ( stageDict->GetVector ( va("%s_min", key ), "", mins ) ) { + idVec3 maxs; + stageDict->GetVector ( va("%s_max", key), mins.ToString(), maxs ); + return mins + (maxs - mins) * gameLocal.random.RandomFloat ( ); + } + + return stageDict->GetVector ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageFloat +================ +*/ +float idDamagable::GetStageFloat ( const char* key, const char* defaultString ) const { + float minValue; + if ( stageDict->GetFloat ( va("%s_min", key ), "", minValue ) ) { + float maxValue; + stageDict->GetFloat ( va("%s_max", key), va("%g",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetFloat ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageInt +================ +*/ +int idDamagable::GetStageInt ( const char* key, const char* defaultString ) const { + int minValue; + if ( stageDict->GetInt ( va("%s_min", key ), "", minValue ) ) { + int maxValue; + stageDict->GetInt ( va("%s_max", key), va("%d",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetInt ( key, defaultString ); +} + +/* +================ +idDamagable::Event_BecomeBroken +================ +*/ +void idDamagable::Event_BecomeBroken( idEntity *activator ) { + BecomeBroken( activator ); + +// RAVEN BEGIN +// bdube: start stages + UpdateStage ( ); +// RAVEN END +} + +/* +================ +idDamagable::Event_RestoreDamagable +================ +*/ +void idDamagable::Event_RestoreDamagable( void ) { + health = spawnArgs.GetInt( "health", "5" ); + Show(); +} + + +/* +=============================================================================== + + idExplodable + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idExplodable ) + EVENT( EV_Activate, idExplodable::Event_Explode ) +END_CLASS + +/* +================ +idExplodable::Spawn +================ +*/ +void idExplodable::Spawn( void ) { + Hide(); +} + +/* +================ +idExplodable::Event_Explode +================ +*/ +void idExplodable::Event_Explode( idEntity *activator ) { + const char *temp; + + if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms + renderEntity.shaderParms[SHADERPARM_RED] = 1.0f; + renderEntity.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEntity.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = 0.0f; + Show(); + + PostEventMS( &EV_Remove, 2000 ); + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpring ) + EVENT( EV_PostSpawn, idSpring::Event_LinkSpring ) +END_CLASS + +/* +================ +idSpring::Think +================ +*/ +void idSpring::Think( void ) { + idVec3 start, end, origin; + idMat3 axis; + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + // evaluate force + spring.Evaluate( gameLocal.time ); + + start = p1; + if ( ent1 && ent1->GetPhysics() ) { + axis = ent1->GetPhysics()->GetAxis(); + origin = ent1->GetPhysics()->GetOrigin(); + start = origin + start * axis; + } + + end = p2; + if ( ent2 && ent2->GetPhysics() ) { + axis = ent2->GetPhysics()->GetAxis(); + origin = ent2->GetPhysics()->GetOrigin(); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true ); + } + + Present(); +} + +/* +================ +idSpring::Event_LinkSpring +================ +*/ +void idSpring::Event_LinkSpring( void ) { + idStr name1, name2; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + + if ( name1.Length() ) { + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() ); + } + } + else { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + if ( name2.Length() ) { + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() ); + } + } + else { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 ); + BecomeActive( TH_THINK ); +} + +/* +================ +idSpring::Spawn +================ +*/ +void idSpring::Spawn( void ) { + float Kstretch, damping, restLength; + + spawnArgs.GetInt( "id1", "0", id1 ); + spawnArgs.GetInt( "id2", "0", id2 ); + spawnArgs.GetVector( "point1", "0 0 0", p1 ); + spawnArgs.GetVector( "point2", "0 0 0", p2 ); + spawnArgs.GetFloat( "constant", "100.0f", Kstretch ); + spawnArgs.GetFloat( "damping", "10.0f", damping ); + spawnArgs.GetFloat( "restlength", "0.0f", restLength ); + + spring.InitSpring( Kstretch, 0.0f, damping, restLength ); + + ent1 = ent2 = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idSpring::Save +================ +*/ +void idSpring::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( id1 ); + savefile->WriteInt ( id2 ); + savefile->WriteVec3 ( p1 ); + savefile->WriteVec3 ( p2 ); + spring.Save ( savefile ); +} + +/* +================ +idSpring::Restore +================ +*/ +void idSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( id1 ); + savefile->ReadInt ( id2 ); + savefile->ReadVec3 ( p1 ); + savefile->ReadVec3 ( p2 ); + spring.Restore ( savefile ); + Event_LinkSpring ( ); +} + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const idEventDef EV_Toggle( "Toggle", NULL ); + +CLASS_DECLARATION( idEntity, idForceField ) + EVENT( EV_Activate, idForceField::Event_Activate ) + EVENT( EV_Toggle, idForceField::Event_Toggle ) + EVENT( EV_FindTargets, idForceField::Event_FindTargets ) +END_CLASS + +/* +=============== +idForceField::Toggle +================ +*/ +void idForceField::Toggle( void ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idForceField::Think +================ +*/ +void idForceField::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + } + Present(); +} + +/* +================ +idForceField::Save +================ +*/ +void idForceField::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( forceField ); +} + +/* +================ +idForceField::Restore +================ +*/ +void idForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( forceField ); +} + +/* +================ +idForceField::Spawn +================ +*/ +void idForceField::Spawn( void ) { + idVec3 uniform; + float explosion, implosion, randomTorque; + + if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) { + forceField.Uniform( uniform ); + } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) { + forceField.Explosion( explosion ); + } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) { + forceField.Implosion( implosion ); + } + + if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) { + forceField.RandomTorque( randomTorque ); + } + + if ( spawnArgs.GetBool( "applyForce", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_FORCE ); + } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE ); + } else { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + } + + forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) ); + forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) ); + + // set the collision model on the force field +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + forceField.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + forceField.SetOwner( this ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idForceField::Event_Toggle +================ +*/ +void idForceField::Event_Toggle( void ) { + Toggle(); +} + +/* +================ +idForceField::Event_Activate +================ +*/ +void idForceField::Event_Activate( idEntity *activator ) { + float wait; + + Toggle(); + if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) { + PostEventSec( &EV_Toggle, wait ); + } +} + +/* +================ +idForceField::Event_FindTargets +================ +*/ +void idForceField::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ); + } +} + +// RAVEN BEGIN +// bdube: jump pads + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const int JUMPPAD_EFFECT_DELAY = 100; + +CLASS_DECLARATION( idForceField, rvJumpPad ) + EVENT( EV_FindTargets, rvJumpPad::Event_FindTargets ) +END_CLASS + +/* +================ +rvJumpPad::rvJumpPad +================ +*/ +rvJumpPad::rvJumpPad ( void ) { + lastEffectTime = -1; +} + +/* +================ +rvJumpPad::Think +================ +*/ +void rvJumpPad::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + + // If force has been applied to an entity and jump pad effect hasnt been played for a bit + // then play it now. + if ( forceField.GetLastApplyTime ( ) - lastEffectTime > JUMPPAD_EFFECT_DELAY ) { + // start locally + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + + // send through unreliable + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMPFX ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( renderEntity.origin ) ); + } + + lastEffectTime = forceField.GetLastApplyTime( ); + } + } + Present(); +} + +/* +================ +rvJumpPad::Spawn +================ +*/ +void rvJumpPad::Spawn( void ) { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + forceField.SetOwner( this ); +} + +/* +================ +rvJumpPad::Event_FindTargets +================ +*/ +void rvJumpPad::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + idEntity* ent; + ent = targets[0].GetEntity(); + assert( ent ); + + idVec3 vert; + idVec3 diff; + idVec3 vel; + idVec3 localGravity( gameLocal.GetCurrentGravity(this) ); + idVec3 localGravityNormal( localGravity.ToNormal() ); + float time; + float dist; + + // Find the distance along the gravity vector between the jump pad and its target + diff = (ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()); + vert = (diff * localGravityNormal) * localGravityNormal; + + // Determine how long it would take to cover the distance along the gravity vector + time = idMath::Sqrt( vert.Length() / (0.5f * localGravity.Length()) ); + if ( !time ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // The final velocity is the direction between the jump pad and its target using + // the travel time to determine the forward vector and adding in an inverse gravity vector. + vel = diff - vert; + dist = vel.Normalize(); + + vel = vel * (dist / time); + vel += (localGravity * -time); + + forceField.Uniform( vel ); + + // calculate a coordinate axis where the vector to the jumppad target is forward + // use this to play the fx_jump fx + diff.Normalize(); + effectAxis = diff.ToMat3(); + } +} + +/* +=============== +rvJumpPad::ClientReceiveEvent +=============== +*/ +bool rvJumpPad::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_JUMPFX: { + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + return true; + } + default: + return idEntity::ClientReceiveEvent( event, time, msg ); + } +} + +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +const idEventDef EV_Animated_Start( "" ); +const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" ); +const idEventDef EV_LaunchMissilesUpdate( "", "dddd" ); +const idEventDef EV_AnimDone( "", "d" ); +const idEventDef EV_StartRagdoll( "startRagdoll" ); + +// RAVEN BEGIN +// bdube: script object +const idEventDef EV_SetAnimState ( "setAnimState", "sd" ); +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated ) + EVENT( EV_Activate, idAnimated::Event_Activate ) + EVENT( EV_Animated_Start, idAnimated::Event_Start ) + EVENT( EV_StartRagdoll, idAnimated::Event_StartRagdoll ) + EVENT( EV_AnimDone, idAnimated::Event_AnimDone ) + EVENT( EV_Footstep, idAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, idAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, idAnimated::Event_Footstep ) + EVENT( EV_LaunchMissiles, idAnimated::Event_LaunchMissiles ) + EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate ) + +// RAVEN BEGIN +// bdube: script object + EVENT( EV_SetAnimState, idAnimated::Event_SetAnimState ) + EVENT( AI_PlayAnim, idAnimated::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAnimated::Event_PlayCycle ) + EVENT( AI_AnimDone, idAnimated::Event_AnimDone2 ) +// RAVEN END + +END_CLASS + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::idAnimated() { + int i; + + anim = 0; + blendFrames = 0; + soundJoint = INVALID_JOINT; + activated = false; + combatModel = NULL; + activator = NULL; + current_anim_index = 0; + num_anims = 0; +// RAVEN BEGIN +// bdube: script control + scriptThread = NULL; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + animDoneTime[i] = 0; + } +// RAVEN END +} + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::~idAnimated() { + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN +// bdube: kill script object + delete scriptThread; +// RAVEN END +} + +/* +=============== +idAnimated::Save +================ +*/ +void idAnimated::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( num_anims ); + savefile->WriteInt( current_anim_index ); + savefile->WriteInt( anim ); + savefile->WriteInt( blendFrames ); + savefile->WriteJoint( soundJoint ); + activator.Save( savefile ); + savefile->WriteBool( activated ); + +// RAVEN BEGIN + savefile->WriteObject( scriptThread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Restore +================ +*/ +void idAnimated::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadInt( num_anims ); + savefile->ReadInt( current_anim_index ); + savefile->ReadInt( anim ); + savefile->ReadInt( blendFrames ); + savefile->ReadJoint( soundJoint ); + activator.Restore( savefile ); + savefile->ReadBool( activated ); + +// RAVEN BEGIN + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Spawn +================ +*/ +void idAnimated::Spawn( void ) { + idStr animname; + int anim2; + float wait; + const char *joint; + + joint = spawnArgs.GetString( "sound_bone", "origin" ); + soundJoint = animator.GetJointHandle( joint ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint ); + } + + LoadAF( NULL ); + + // allow bullets to collide with a combat model + if ( spawnArgs.GetBool( "combatModel", "0" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + // allow the entity to take damage + if ( spawnArgs.GetBool( "takeDamage", "0" ) ) { + fl.takedamage = true; + } + + blendFrames = 0; + +// RAVEN BEGIN +// bdube: script control + // setup script object + const char* scriptObjectName; + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + const function_t *constructor; + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + scriptThread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + scriptThread->ManualDelete(); + scriptThread->ManualControl(); + scriptThread->SetThreadName( name.c_str() ); + scriptThread->CallFunction( this, constructor, true ); + scriptThread->Execute ( ); + } + + fl.takedamage = true; + + return; + } +// RAVEN END + + current_anim_index = 0; + spawnArgs.GetInt( "num_anims", "0", num_anims ); + + blendFrames = spawnArgs.GetInt( "blend_in" ); + + animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" ); + if ( !animname.Length() ) { + anim = 0; + } else { + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + } + + if ( spawnArgs.GetBool( "hide" ) ) { + Hide(); + + if ( !num_anims ) { + blendFrames = 0; + } + } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) { + anim2 = animator.GetAnim( animname ); + if ( !anim2 ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 ); + } else if ( anim ) { + // init joints to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + + if ( !num_anims ) { + blendFrames = 0; + } + } + + spawnArgs.GetFloat( "wait", "-1", wait ); + + if ( wait >= 0 ) { + PostEventSec( &EV_Activate, wait, this ); + } +} + +/* +=============== +idAnimated::LoadAF +=============== +*/ +bool idAnimated::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = renderEntity.axis; + return true; +} + +/* +================ +idAnimated::StartRagdoll +================ +*/ +bool idAnimated::StartRagdoll( void ) { + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable any collision model used + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + return true; +} + +/* +===================== +idAnimated::PlayNextAnim +===================== +*/ +void idAnimated::PlayNextAnim( void ) { + const char *animname; + int len; + int cycle; + + if ( current_anim_index >= num_anims ) { + Hide(); + if ( spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } else { + current_anim_index = 0; + } + return; + } + + Show(); + current_anim_index++; + + spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname ); + if ( !animname ) { + anim = 0; + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) ); + return; + } + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + spawnArgs.GetInt( "cycle", "1", cycle ); + if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) { + cycle = -1; + } + + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, current_anim_index ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_StartRagdoll +================ +*/ +void idAnimated::Event_StartRagdoll( void ) { + StartRagdoll(); +} + +/* +=============== +idAnimated::Event_AnimDone +================ +*/ +void idAnimated::Event_AnimDone( int animindex ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + + if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "auto_advance" ) ) { + PlayNextAnim(); + } else { + activated = false; + } + + ActivateTargets( activator.GetEntity() ); +} + +/* +=============== +idAnimated::Event_Activate +================ +*/ +void idAnimated::Event_Activate( idEntity *_activator ) { +// RAVEN BEGIN +// bdube: script object support + if ( scriptThread ) { + CallHandler ( "onActivate" ); + return; + } +// RAVEN END + + if ( num_anims ) { + PlayNextAnim(); + activator = _activator; + return; + } + + if ( activated ) { + // already activated + return; + } + + activated = true; + activator = _activator; + ProcessEvent( &EV_Animated_Start ); +} + +/* +=============== +idAnimated::Event_Start +================ +*/ +void idAnimated::Event_Start( void ) { + int cycle; + int len; + + Show(); + + if ( num_anims ) { + PlayNextAnim(); + return; + } + + if ( anim ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + spawnArgs.GetInt( "cycle", "1", cycle ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, 1 ); + } + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_Footstep +=============== +*/ +void idAnimated::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +===================== +idAnimated::Event_LaunchMissilesUpdate +===================== +*/ +void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) { + idVec3 launchPos; + idVec3 targetPos; + idMat3 axis; + idVec3 dir; + idEntity * ent; + idProjectile * projectile; + const idDict * projectileDef; + const char * projectilename; + + projectilename = spawnArgs.GetString( "projectilename" ); + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL ); + + animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis ); + launchPos = renderEntity.origin + launchPos * renderEntity.axis; + +// RAVEN BEGIN +// bdube: with no target bone it will just shoot out the direction of the bones orientation + if ( targetjoint != INVALID_JOINT ) { + animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis ); + targetPos = renderEntity.origin + targetPos * renderEntity.axis; + dir = targetPos - launchPos; + } else { + axis *= renderEntity.axis; + dir = axis[0]; + } +// RAVEN END + + dir.Normalize(); + + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + } + projectile = ( idProjectile * )ent; + projectile->Create( this, launchPos, dir ); + projectile->Launch( launchPos, dir, vec3_origin ); + + if ( numshots > 0 ) { + PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay ); + } +} + +/* +===================== +idAnimated::Event_LaunchMissiles +===================== +*/ +void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) { + const idDict * projectileDef; + jointHandle_t launch; + jointHandle_t target; + + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + launch = animator.GetJointHandle( launchjoint ); + if ( launch == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint ); + gameLocal.Error( "Unknown joint '%s'", launchjoint ); + } + + target = animator.GetJointHandle( targetjoint ); +// RAVEN BEGIN +// bdube: invalid is ok now, it means shoot out the direction of the bone +// if ( target == INVALID_JOINT ) { +// gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint ); +// } +// RAVEN END + + spawnArgs.Set( "projectilename", projectilename ); + spawnArgs.Set( "missilesound", sound ); + + CancelEvents( &EV_LaunchMissilesUpdate ); +// RAVEN BEGIN +// nmckenzie: Do this now. No delays. No event loops or updates. Just a straightforward DO THIS. Tacky. + if ( numshots == 1 && framedelay == 0 ){ + Event_LaunchMissilesUpdate ( launch, target, 1, 0 ); + } + else{ + ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay ); + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idAnimated::ShouldConstructScriptObjectAtSpawn +===================== +*/ +bool idAnimated::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Think ( void ) { + UpdateScript ( ); + idAFEntity_Gibbable::Think ( ); +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Damage ( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if ( !scriptThread ) { + return; + } + + CallHandler ( "onDamage" ); +} + +/* +===================== +idAnimated::Event_PlayAnim +===================== +*/ +void idAnimated::Event_PlayAnim( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animDoneTime[channel] - gameLocal.time ) ); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_PlayCycle +=============== +*/ +void idAnimated::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_AnimDone2 +=============== +*/ +void idAnimated::Event_AnimDone2( int channel, int blend ) { + if ( animDoneTime[channel] - FRAME2MS( blend ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idAnimated::Event_SetAnimState +=============== +*/ +void idAnimated::Event_SetAnimState ( const char* statename, int blend ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + idealState = statename; + blendFrames = blend; + scriptThread->DoneProcessing(); +} + +/* +================ +idAnimated::UpdateScript +================ +*/ +void idAnimated::UpdateScript( void ) { + int count; + + if ( !scriptThread || !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + + // If no state has been set then dont execute nothing + if ( !state.Length ( ) || scriptThread->IsWaiting() ) { + return; + } + + // update script state + count = 10; + while( ( scriptThread->Execute() || idealState.Length() ) && count-- ) { + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + } +} + +/* +===================== +idAnimated::CallHandler +===================== +*/ +void idAnimated::CallHandler ( const char* handler ) { + const function_t *func; + func = scriptObject.GetFunction( handler ); + if ( !func ) { + return; + } + + scriptThread->CallFunction( this, func, false ); + scriptThread->Execute ( ); +} + +/* +===================== +idAnimated::SetState +===================== +*/ +void idAnimated::SetState( const char *statename, int frames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + scriptThread->CallFunction( this, func, true ); + state = statename; + blendFrames = frames; + idealState = ""; +} + +// RAVEN END + +/* +=============================================================================== + + idStaticEntity + + Some static entities may be optimized into inline geometry by dmap + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idStaticEntity ) + EVENT( EV_Activate, idStaticEntity::Event_Activate ) +END_CLASS + +/* +=============== +idStaticEntity::idStaticEntity +=============== +*/ +idStaticEntity::idStaticEntity( void ) { + spawnTime = 0; + active = false; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + runGui = false; +} + +/* +=============== +idStaticEntity::Save +=============== +*/ +void idStaticEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnTime ); + savefile->WriteBool( active ); + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( runGui ); +} + +/* +=============== +idStaticEntity::Restore +=============== +*/ +void idStaticEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnTime ); + savefile->ReadBool( active ); + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( runGui ); +} + +/* +=============== +idStaticEntity::Spawn +=============== +*/ +void idStaticEntity::Spawn( void ) { + bool solid; + bool hidden; + bool keepContents; + +// RAVEN BEGIN +// rjohnson: inline models can be solid, since the entities hang around anyway (don't know why it was done that way) + solid = spawnArgs.GetBool( "solid" ); + + // an inline static model will not do anything at all + if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) { + Hide(); + if ( solid ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + return; + } +// RAVEN END + + hidden = spawnArgs.GetBool( "hide" ); + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else if ( solid && !hidden ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + GetPhysics()->SetContents( 0 ); + } + + spawnTime = gameLocal.time; + active = false; + + idStr model = spawnArgs.GetString( "model" ); + // FIXME: temp also catch obsolete .ips extension + if ( model.Find( ".ips" ) >= 0 || model.Find( ".prt" ) >= 0 ) { + // we want the parametric particles out of sync with each other + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 ); + } + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // NOTE: this should be used very rarely because it is expensive + runGui = spawnArgs.GetBool( "runGui" ); + if ( runGui ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idStaticEntity::ShowEditingDialog +================ +*/ +void idStaticEntity::ShowEditingDialog( void ) { +// RAVEN BEGIN +// bdube: not using +// common->InitTool( EDITOR_PARTICLE, &spawnArgs ); +// RAVEN END +} +/* +================ +idStaticEntity::Think +================ +*/ +void idStaticEntity::Think( void ) { + idEntity::Think(); + if ( thinkFlags & TH_THINK ) { + if ( runGui && renderEntity.gui[0] ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->objectiveSystemOpen ) { + for( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } + } + } + } + if ( fadeEnd > 0 ) { + idVec4 color; + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } +} + +/* +================ +idStaticEntity::Fade +================ +*/ +void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idStaticEntity::Hide +================ +*/ +void idStaticEntity::Hide( void ) { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idStaticEntity::Show +================ +*/ +void idStaticEntity::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid" ) ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idStaticEntity::Event_Activate +================ +*/ +void idStaticEntity::Event_Activate( idEntity *activator ) { + idStr activateGui; + + spawnTime = gameLocal.time; + active = !active; + + const idKeyValue *kv = spawnArgs.FindKey( "hide" ); + if ( kv ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime ); + renderEntity.shaderParms[5] = active; + // this change should be a good thing, it will automatically turn on + // lights etc.. when triggered so that does not have to be specifically done + // with trigger parms.. it MIGHT break things so need to keep an eye on it + renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ? 0.0f : 1.0f; + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idStaticEntity::WriteToSnapshot +================ +*/ +void idStaticEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + WriteGUIToSnapshot( msg ); + msg.WriteBits( IsHidden()?1:0, 1 ); +} + +/* +================ +idStaticEntity::ReadFromSnapshot +================ +*/ +void idStaticEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool hidden; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + hidden = msg.ReadBits( 1 ) == 1; + if ( hidden != IsHidden() ) { + if ( hidden ) { + Hide(); + } else { + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncEmitter ) +EVENT( EV_Activate, idFuncEmitter::Event_Activate ) +END_CLASS + +/* +=============== +idFuncEmitter::idFuncEmitter +=============== +*/ +idFuncEmitter::idFuncEmitter( void ) { + hidden = false; +} + +/* +=============== +idFuncEmitter::Spawn +=============== +*/ +void idFuncEmitter::Spawn( void ) { + if ( spawnArgs.GetBool( "start_off" ) ) { + hidden = true; + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 ); + UpdateVisuals(); + } else { + hidden = false; + } +} + +/* +=============== +idFuncEmitter::Save +=============== +*/ +void idFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteBool( hidden ); +} + +/* +=============== +idFuncEmitter::Restore +=============== +*/ +void idFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( hidden ); +} + +/* +================ +idFuncEmitter::Event_Activate +================ +*/ +void idFuncEmitter::Event_Activate( idEntity *activator ) { + if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + hidden = false; + } else { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + hidden = true; + } + UpdateVisuals(); +} + +/* +================ +idFuncEmitter::WriteToSnapshot +================ +*/ +void idFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( hidden ? 1 : 0, 1 ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncEmitter::ReadFromSnapshot +================ +*/ +void idFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hidden = msg.ReadBits( 1 ) != 0; + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + + +const idEventDef EV_Splat( "" ); +CLASS_DECLARATION( idFuncEmitter, idFuncSplat ) +EVENT( EV_Activate, idFuncSplat::Event_Activate ) +EVENT( EV_Splat, idFuncSplat::Event_Splat ) +END_CLASS + +/* +=============== +idFuncSplat::idFuncSplat +=============== +*/ +idFuncSplat::idFuncSplat( void ) { +} + +/* +=============== +idFuncSplat::Spawn +=============== +*/ +void idFuncSplat::Spawn( void ) { +} + +/* +================ +idFuncSplat::Event_Splat +================ +*/ +void idFuncSplat::Event_Splat( void ) { + const char *splat = NULL; + int count = spawnArgs.GetInt( "splatCount", "1" ); + for ( int i = 0; i < count; i++ ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + if ( splat && *splat ) { + float size = spawnArgs.GetFloat( "splatSize", "128" ); + float dist = spawnArgs.GetFloat( "splatDistance", "128" ); + float angle = spawnArgs.GetFloat( "splatAngle", "0" ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle ); + } + } + StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +idFuncSplat::Event_Activate +================ +*/ +void idFuncSplat::Event_Activate( idEntity *activator ) { + idFuncEmitter::Event_Activate( activator ); + PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) ); + StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +=============================================================================== + + idTextEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTextEntity ) +END_CLASS + +/* +================ +idTextEntity::Spawn +================ +*/ +void idTextEntity::Spawn( void ) { + // these are cached as the are used each frame + text = spawnArgs.GetString( "text" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + bool force = spawnArgs.GetBool( "force" ); + if ( developer.GetBool() || force ) { + BecomeActive(TH_THINK); + } +} + +/* +================ +idTextEntity::Save +================ +*/ +void idTextEntity::Save( idSaveGame *savefile ) const { + savefile->WriteString( text ); + savefile->WriteBool( playerOriented ); +} + +/* +================ +idTextEntity::Restore +================ +*/ +void idTextEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadString( text ); + savefile->ReadBool( playerOriented ); +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::Think( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::ClientPredictionThink( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + + + +/* +=============================================================================== + + idVacuumSeperatorEntity + + Can be triggered to let vacuum through a portal (blown out window) + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity ) + EVENT( EV_Activate, idVacuumSeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +idVacuumSeparatorEntity::idVacuumSeparatorEntity +================ +*/ +idVacuumSeparatorEntity::idVacuumSeparatorEntity( void ) { + portal = 0; +} + +/* +================ +idVacuumSeparatorEntity::Save +================ +*/ +void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +idVacuumSeparatorEntity::Restore +================ +*/ +void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +idVacuumSeparatorEntity::Spawn +================ +*/ +void idVacuumSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION ); +} + +/* +================ +idVacuumSeparatorEntity::Event_Activate +================ +*/ +void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_NONE ); +} + + +/* +=============================================================================== + +idLocationSeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationSeparatorEntity ) +END_CLASS + +/* +================ +idLocationSeparatorEntity::Spawn +================ +*/ +void idLocationSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + qhandle_t portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + } + gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION ); +} + + +/* +=============================================================================== + + idVacuumEntity + + Levels should only have a single vacuum entity. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumEntity ) +END_CLASS + +/* +================ +idVacuumEntity::Spawn +================ +*/ +void idVacuumEntity::Spawn() { + if ( gameLocal.vacuumAreaNum != -1 ) { + gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" ); + return; + } + + idVec3 org = spawnArgs.GetVector( "origin" ); + + gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org ); +} + +// RAVEN BEGIN +// abahr: +/* +=============================================================================== + + rvGravitySeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvGravitySeparatorEntity ) + EVENT( EV_Activate, rvGravitySeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +rvGravitySeparatorEntity::idVacuumSeparatorEntity +================ +*/ +rvGravitySeparatorEntity::rvGravitySeparatorEntity( void ) { + portal = 0; +} + +/* +================ +rvGravitySeparatorEntity::Save +================ +*/ +void rvGravitySeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +rvGravitySeparatorEntity::Restore +================ +*/ +void rvGravitySeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +rvGravitySeparatorEntity::Spawn +================ +*/ +void rvGravitySeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "GravitySeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, gameRenderWorld->GetPortalState(portal) | PS_BLOCK_GRAVITY ); +} + +/* +================ +rvGravitySeparatorEntity::Event_Activate +================ +*/ +void rvGravitySeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + + int portalState = gameRenderWorld->GetPortalState( portal ); + gameLocal.SetPortalState( portal, (portalState & PS_BLOCK_GRAVITY) > 0 ? (portalState & ~PS_BLOCK_GRAVITY) : portalState | PS_BLOCK_GRAVITY ); +} + +/* +=============================================================================== + + rvGravityEntity + +=============================================================================== +*/ +ABSTRACT_DECLARATION( idEntity, rvGravityArea ) +END_CLASS + +/* +================ +rvGravityArea::Spawn +================ +*/ +void rvGravityArea::Spawn() { + area = gameRenderWorld->PointInArea( spawnArgs.GetVector("origin") ); + gameLocal.AddUniqueGravityInfo( this ); +} + +/* +================ +rvGravityArea::Save +================ +*/ +void rvGravityArea::Save( idSaveGame *savefile ) const { + savefile->WriteInt( area ); +} + +/* +================ +rvGravityArea::Restore +================ +*/ +void rvGravityArea::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( area ); +} + +/* +================ +rvGravityArea::IsEqualTo +================ +*/ +bool rvGravityArea::IsEqualTo( const rvGravityArea* area ) const { + assert( area ); + return !idStr::Icmp( GetName(), area->GetName() ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea* area ) const { + return IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea& area ) const { + return IsEqualTo( &area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea* area ) const { + return !IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea& area ) const { + return !IsEqualTo( &area ); +} + +/* +=============================================================================== + + rvGravityEntity_Static + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_Static ) +END_CLASS + +/* +================ +rvGravityArea_Static::Spawn +================ +*/ +void rvGravityArea_Static::Spawn() { + gravity = spawnArgs.GetVector( "gravity" );// * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_Static::Save +================ +*/ +void rvGravityArea_Static::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( gravity ); +} + +/* +================ +rvGravityArea_Static::Restore +================ +*/ +void rvGravityArea_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( gravity ); +} + +/* +=============================================================================== + + rvGravityArea_SurfaceNormal + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_SurfaceNormal ) +END_CLASS + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { + trace_t results; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if( !gameLocal.TracePoint( this, results, origin, origin + -axis[2] * 50.0f, clipMask, passEntity ) ) { +// RAVEN END + return gameLocal.GetGravity(); + } + + return -results.c.normal * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const rvClientEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idPhysics* physics ) const { + return GetGravity( physics->GetOrigin(), physics->GetAxis(), physics->GetClipMask(), physics->GetSelf() ); +} +// RAVEN END + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationEntity ) +END_CLASS + +/* +====================== +idLocationEntity::Spawn +====================== +*/ +void idLocationEntity::Spawn() { + idStr realName; + + // this just holds dict information + + // if "location" not already set, use the entity name. + if ( !spawnArgs.GetString( "location", "", realName ) ) { + spawnArgs.Set( "location", name ); + } +} + +/* +====================== +idLocationEntity::GetLocation +====================== +*/ +const char *idLocationEntity::GetLocation( void ) const { + return spawnArgs.GetString( "location" ); +} + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idBeam ) + EVENT( EV_PostSpawn, idBeam::Event_MatchTarget ) + EVENT( EV_Activate, idBeam::Event_Activate ) +END_CLASS + +/* +=============== +idBeam::idBeam +=============== +*/ +idBeam::idBeam() { + target = NULL; + master = NULL; +} + +/* +=============== +idBeam::Save +=============== +*/ +void idBeam::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + master.Save( savefile ); +} + +/* +=============== +idBeam::Restore +=============== +*/ +void idBeam::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + master.Restore( savefile ); +} + +/* +=============== +idBeam::Spawn +=============== +*/ +void idBeam::Spawn( void ) { + float width; + + if ( spawnArgs.GetFloat( "width", "0", width ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width; + } + + SetModel( "_BEAM" ); + Hide(); + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idBeam::Think +================ +*/ +void idBeam::Think( void ) { + idBeam *masterEnt; + + if ( !IsHidden() && !target.GetEntity() ) { + // hide if our target is removed + Hide(); + } + + RunPhysics(); + + masterEnt = master.GetEntity(); + if ( masterEnt ) { + const idVec3 &origin = GetPhysics()->GetOrigin(); + masterEnt->SetBeamTarget( origin ); + } + Present(); +} + +/* +================ +idBeam::SetMaster +================ +*/ +void idBeam::SetMaster( idBeam *masterbeam ) { + master = masterbeam; +} +/* +================ +idBeam::SetBeamTarget +================ +*/ +void idBeam::SetBeamTarget( const idVec3 &origin ) { + if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z; + UpdateVisuals(); + } +} + +/* +================ +idBeam::Show +================ +*/ +void idBeam::Show( void ) { + idBeam *targetEnt; + + idEntity::Show(); + + targetEnt = target.GetEntity(); + if ( targetEnt ) { + const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin(); + SetBeamTarget( origin ); + } +} + +/* +================ +idBeam::Event_MatchTarget +================ +*/ +void idBeam::Event_MatchTarget( void ) { + int i; + idEntity *targetEnt; + idBeam *targetBeam; + + if ( !targets.Num() ) { + return; + } + + targetBeam = NULL; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idBeam::Type ) ) { + targetBeam = static_cast( targetEnt ); + break; + } + } + + if ( !targetBeam ) { + gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() ); + } + + target = targetBeam; + targetBeam->SetMaster( this ); + if ( !spawnArgs.GetBool( "start_off" ) ) { + Show(); + } +} + +/* +================ +idBeam::Event_Activate +================ +*/ +void idBeam::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } +} + +/* +================ +idBeam::WriteToSnapshot +================ +*/ +void idBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] ); +} + +/* +================ +idBeam::ReadFromSnapshot +================ +*/ +void idBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLiquid ) + EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + +/* +================ +idLiquid::Save +================ +*/ +void idLiquid::Save( idSaveGame *savefile ) const { + // Nothing to save + // cnicholson: It says nothing to save here, no mention of *model +} + +/* +================ +idLiquid::Restore +================ +*/ +void idLiquid::Restore( idRestoreGame *savefile ) { + //FIXME: NO! + Spawn(); +} + +/* +================ +idLiquid::Spawn +================ +*/ +void idLiquid::Spawn() { +/* + model = dynamic_cast( renderEntity.hModel ); + if ( !model ) { + gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() ); + } + model->Reset(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +*/ +} + +/* +================ +idLiquid::Event_Touch +================ +*/ +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { + // FIXME: for QuakeCon +/* + idVec3 pos; + + pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); +*/ +} + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idShaking ) + EVENT( EV_Activate, idShaking::Event_Activate ) +END_CLASS + +/* +=============== +idShaking::idShaking +=============== +*/ +idShaking::idShaking() { + active = false; +} + + +idShaking::~idShaking() { + SetPhysics( NULL ); +} + +/* +=============== +idShaking::Save +=============== +*/ +void idShaking::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idShaking::Restore +=============== +*/ +void idShaking::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +=============== +idShaking::Spawn +=============== +*/ +void idShaking::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + SetPhysics( &physicsObj ); + + active = false; + if ( !spawnArgs.GetBool( "start_off" ) ) { + BeginShaking(); + } +} + +/* +================ +idShaking::BeginShaking +================ +*/ +void idShaking::BeginShaking( void ) { + int phase; + idAngles shake; + int period; + + active = true; + phase = gameLocal.random.RandomInt( 1000 ); + shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" ); + period = spawnArgs.GetFloat( "period", "0.05" ) * 1000; + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero ); +} + +/* +================ +idShaking::Event_Activate +================ +*/ +void idShaking::Event_Activate( idEntity *activator ) { + if ( !active ) { + BeginShaking(); + } else { + active = false; + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero ); + } +} + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ +idCVar g_earthquake( "g_earthquake", "1", 0, "controls earthquake effect" ); + +CLASS_DECLARATION( idEntity, idEarthQuake ) + EVENT( EV_Activate, idEarthQuake::Event_Activate ) +END_CLASS + +/* +=============== +idEarthQuake::idEarthQuake +=============== +*/ +idEarthQuake::idEarthQuake() { + wait = 0.0f; + random = 0.0f; + nextTriggerTime = 0; + shakeStopTime = 0; + triggered = false; + playerOriented = false; + disabled = false; + shakeTime = 0.0f; +} + +/* +=============== +idEarthQuake::Save +=============== +*/ +void idEarthQuake::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextTriggerTime ); + savefile->WriteInt( shakeStopTime ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteBool( triggered ); + savefile->WriteBool( playerOriented ); + savefile->WriteBool( disabled ); + savefile->WriteFloat( shakeTime ); +} + +/* +=============== +idEarthQuake::Restore +=============== +*/ +void idEarthQuake::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( nextTriggerTime ); + savefile->ReadInt( shakeStopTime ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadBool( triggered ); + savefile->ReadBool( playerOriented ); + savefile->ReadBool( disabled ); + savefile->ReadFloat( shakeTime ); + + if ( shakeStopTime > gameLocal.time ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idEarthQuake::Spawn +=============== +*/ +void idEarthQuake::Spawn( void ) { + nextTriggerTime = 0; + shakeStopTime = 0; + wait = spawnArgs.GetFloat( "wait", "15" ); + random = spawnArgs.GetFloat( "random", "5" ); + triggered = spawnArgs.GetBool( "triggered" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + disabled = false; + shakeTime = spawnArgs.GetFloat( "shakeTime", "0" ); + + if ( !triggered ){ + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this ); + } + BecomeInactive( TH_THINK ); +} + +/* +================ +idEarthQuake::Event_Activate +================ +*/ +void idEarthQuake::Event_Activate( idEntity *activator ) { + + if ( nextTriggerTime > gameLocal.time ) { + return; + } + + if ( disabled && activator == this ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + nextTriggerTime = 0; + + if ( !triggered && activator != this ){ + // if we are not triggered ( i.e. random ), disable or enable + disabled ^= 1; + if (disabled) { + return; + } else { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } + } + + ActivateTargets( activator ); + + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) ); + if ( playerOriented ) { + player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } else { + StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off + AffectNearbyEntities(spawnArgs.GetFloat("affectEntities", "0")); +// RAVEN END + + if ( shakeTime > 0.0f ) { + shakeStopTime = gameLocal.time + SEC2MS( shakeTime ); + BecomeActive( TH_THINK ); + } + + if ( wait > 0.0f ) { + if ( !triggered ) { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } else { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } + } else if ( shakeTime == 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round, earthquake, etc goes off +void idEarthQuake::AffectNearbyEntities(float affectRadius) +{ + if( !g_earthquake.GetBool() ) + { + return; + } + + if (affectRadius == 0) + { + return; + } + idVec3 affectOrigin(GetPhysics()->GetOrigin()); + + if (playerOriented) + { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) + { + return; + } + affectOrigin = player->GetPhysics()->GetOrigin(); + } + + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities = 0; + idEntity *curEnt = NULL; + bool requiresLOS = spawnArgs.GetBool("requiresLOS", "false"); + + // get all entities touching the bounds + numListedEntities = gameLocal.EntitiesWithinRadius(affectOrigin, affectRadius, entityList, MAX_GENTITIES ); + for (int i = 0; i < numListedEntities; i++) + { + // only affect certain entities. first check if they have the "quakeChance" key/value, then send them an earthquake event + curEnt = entityList[i]; + if (curEnt && curEnt->spawnArgs.GetFloat("quakeChance")) + { + curEnt->PostEventMS(&EV_Earthquake, 0, (float)requiresLOS); + } + } +} +// RAVEN END + +/* +=============== +idEarthQuake::Think +================ +*/ +void idEarthQuake::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( gameLocal.time > shakeStopTime ) { + BecomeInactive( TH_THINK ); + if ( wait <= 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } + return; + } + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, gameLocal.GetLocalPlayer()->firstPersonViewOrigin ); + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true ); + } + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncPortal ) + EVENT( EV_Activate, idFuncPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncPortal::idFuncPortal +=============== +*/ +idFuncPortal::idFuncPortal() { + portal = 0; + state = false; +} + +/* +=============== +idFuncPortal::Save +=============== +*/ +void idFuncPortal::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteBool( state ); +} + +/* +=============== +idFuncPortal::Restore +=============== +*/ +void idFuncPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)portal ); + savefile->ReadBool( state ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); +} + +/* +=============== +idFuncPortal::Spawn +=============== +*/ +void idFuncPortal::Spawn( void ) { + portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) ); + if ( portal > 0 ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +================ +idFuncPortal::Event_Activate +================ +*/ +void idFuncPortal::Event_Activate( idEntity *activator ) { + if ( portal > 0 ) { + state = !state; + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASPortal ) + EVENT( EV_Activate, idFuncAASPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASPortal::idFuncAASPortal +=============== +*/ +idFuncAASPortal::idFuncAASPortal() { + state = false; +} + +/* +=============== +idFuncAASPortal::Save +=============== +*/ +void idFuncAASPortal::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASPortal::Restore +=============== +*/ +void idFuncAASPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============== +idFuncAASPortal::Spawn +=============== +*/ +void idFuncAASPortal::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +================ +idFuncAASPortal::Event_Activate +================ +*/ +void idFuncAASPortal::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASObstacle ) + EVENT( EV_Activate, idFuncAASObstacle::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASObstacle::idFuncAASObstacle +=============== +*/ +idFuncAASObstacle::idFuncAASObstacle() { + state = false; +} + +/* +=============== +idFuncAASObstacle::Save +=============== +*/ +void idFuncAASObstacle::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASObstacle::Restore +=============== +*/ +void idFuncAASObstacle::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============== +idFuncAASObstacle::Spawn +=============== +*/ +void idFuncAASObstacle::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::Event_Activate +================ +*/ +void idFuncAASObstacle::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::SetState +================ +*/ +void idFuncAASObstacle::SetState ( bool _state ) { + state = _state; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +const idEventDef EV_ResetRadioHud( "", "e" ); +idEntityPtr idFuncRadioChatter::lastRadioChatter = 0; + +CLASS_DECLARATION( idEntity, idFuncRadioChatter ) +EVENT( EV_Activate, idFuncRadioChatter::Event_Activate ) +EVENT( EV_ResetRadioHud, idFuncRadioChatter::Event_ResetRadioHud ) +EVENT( EV_IsActive, idFuncRadioChatter::Event_IsActive ) +END_CLASS + +/* +=============== +idFuncRadioChatter::idFuncRadioChatter +=============== +*/ +idFuncRadioChatter::idFuncRadioChatter() { + time = 0.0f; + isActive = false; +} + +/* +=============== +idFuncRadioChatter::Save +=============== +*/ +void idFuncRadioChatter::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( time ); + savefile->WriteBool( isActive ); // cnicholson: Added unsaved var + lastRadioChatter.Save ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Restore +=============== +*/ +void idFuncRadioChatter::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( time ); + savefile->ReadBool( isActive ); // cnicholson: Added unrestored var + lastRadioChatter.Restore ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Spawn +=============== +*/ +void idFuncRadioChatter::Spawn( void ) { + time = spawnArgs.GetFloat( "time", "5.0" ); +} + +/* +================ +idFuncRadioChatter::Event_Activate +================ +*/ +void idFuncRadioChatter::Event_Activate( idEntity *activator ) { + idPlayer *player; + const char *sound; + const idSoundShader *shader; + int length; + bool isDirectedAtPlayer = false; + + lastRadioChatter = this; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + player = static_cast( activator ); + } else { + player = gameLocal.GetLocalPlayer(); + } + +// RAVEN BEGIN +// bdube: replaced named event with method + player->StartRadioChatter(); +// twhitaker: for isActive() script event + isActive = true; +// RAVEN END + + sound = spawnArgs.GetString( "snd_radiochatter", "" ); + if ( sound && *sound ) { + shader = declManager->FindSound( sound ); + if ( shader && shader->IsVO_ForPlayer()){ + isDirectedAtPlayer = true; + } + player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length ); + time = MS2SEC( length + 150 ); + } + + if ( player && player->GetHud() ) { + player->GetHud()->SetStateBool( "rhinochatter", isDirectedAtPlayer ); + } + + // we still put the hud up because this is used with no sound on + // certain frame commands when the chatter is triggered + PostEventSec( &EV_ResetRadioHud, time, player ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + idPlayer *player = ( activator->IsType( idPlayer::GetClassType() ) ) ? static_cast( activator ) : gameLocal.GetLocalPlayer(); +// RAVEN END +// RAVEN BEGIN +// bdube: replaced named event with method + player->StopRadioChatter(); +// twhitaker: for isActive() script event + isActive = false; +// RAVEN END + ActivateTargets( activator ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_IsActive( void ) { + idThread::ReturnFloat( static_cast< float >( isActive ) ); +} + +/* +================ +idFuncRadioChatter::RepeatLast +================ +*/ +void idFuncRadioChatter::RepeatLast( void ) { + if ( lastRadioChatter ) { + lastRadioChatter->Event_Activate( gameLocal.GetLocalPlayer() ); + } +} + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPhantomObjects ) + EVENT( EV_Activate, idPhantomObjects::Event_Activate ) +END_CLASS + +/* +=============== +idPhantomObjects::idPhantomObjects +=============== +*/ +idPhantomObjects::idPhantomObjects() { + target = NULL; + end_time = 0; + throw_time = 0.0f; + shake_time = 0.0f; + shake_ang.Zero(); + speed = 0.0f; + min_wait = 0; + max_wait = 0; + fl.neverDormant = false; +} + +/* +=============== +idPhantomObjects::Save +=============== +*/ +void idPhantomObjects::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( end_time ); + savefile->WriteFloat( throw_time ); + savefile->WriteFloat( shake_time ); + savefile->WriteVec3( shake_ang ); + savefile->WriteFloat( speed ); + savefile->WriteInt( min_wait ); + savefile->WriteInt( max_wait ); + target.Save( savefile ); + savefile->WriteInt( targetTime.Num() ); + for( i = 0; i < targetTime.Num(); i++ ) { + savefile->WriteInt( targetTime[ i ] ); + } + + for( i = 0; i < lastTargetPos.Num(); i++ ) { + savefile->WriteVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Restore +=============== +*/ +void idPhantomObjects::Restore( idRestoreGame *savefile ) { + int num; + int i; + + savefile->ReadInt( end_time ); + savefile->ReadFloat( throw_time ); + savefile->ReadFloat( shake_time ); + savefile->ReadVec3( shake_ang ); + savefile->ReadFloat( speed ); + savefile->ReadInt( min_wait ); + savefile->ReadInt( max_wait ); + target.Restore( savefile ); + + savefile->ReadInt( num ); + targetTime.SetGranularity( 1 ); + targetTime.SetNum( num ); + lastTargetPos.SetGranularity( 1 ); + lastTargetPos.SetNum( num ); + + for( i = 0; i < num; i++ ) { + savefile->ReadInt( targetTime[ i ] ); + } + + if ( savefile->GetBuildNumber() == INITIAL_RELEASE_BUILD_NUMBER ) { + // these weren't saved out in the first release + for( i = 0; i < num; i++ ) { + lastTargetPos[ i ].Zero(); + } + } else { + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( lastTargetPos[ i ] ); + } + } +} + +/* +=============== +idPhantomObjects::Spawn +=============== +*/ +void idPhantomObjects::Spawn( void ) { + throw_time = spawnArgs.GetFloat( "time", "5" ); + speed = spawnArgs.GetFloat( "speed", "1200" ); + shake_time = spawnArgs.GetFloat( "shake_time", "1" ); + throw_time -= shake_time; + if ( throw_time < 0.0f ) { + throw_time = 0.0f; + } + min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) ); + max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) ); + + shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" ); + Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idPhantomObjects::Event_Activate +================ +*/ +void idPhantomObjects::Event_Activate( idEntity *activator ) { + int i; + float time; + float frac; + float scale; + + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + return; + } + + RemoveNullTargets(); + if ( !targets.Num() ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !activator || !activator->IsType( idActor::GetClassType() ) ) { +// RAVEN END + target = gameLocal.GetLocalPlayer(); + } else { + target = static_cast( activator ); + } + + end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) ); + + targetTime.SetNum( targets.Num() ); + lastTargetPos.SetNum( targets.Num() ); + + const idVec3 &toPos = target.GetEntity()->GetEyePosition(); + + // calculate the relative times of all the objects + time = 0.0f; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = SEC2MS( time ); + lastTargetPos[ i ] = toPos; + + frac = 1.0f - ( float )i / ( float )targetTime.Num(); + time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f; + } + + // scale up the times to fit within throw_time + scale = throw_time / time; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale; + } + + BecomeActive( TH_THINK ); +} + +/* +=============== +idPhantomObjects::Think +================ +*/ +void idPhantomObjects::Think( void ) { + int i; + int num; + float time; + idVec3 vel; + idVec3 ang; + idEntity *ent; + idActor *targetEnt; + idPhysics *entPhys; + trace_t tr; + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !( thinkFlags & TH_THINK ) ) { + BecomeInactive( thinkFlags & ~TH_THINK ); + return; + } + + targetEnt = target.GetEntity(); + if ( !targetEnt || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) { + BecomeInactive( TH_THINK ); + } + + const idVec3 &toPos = targetEnt->GetEyePosition(); + + num = 0; + for ( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + if ( !targetTime[ i ] ) { + // already threw this object + continue; + } + + num++; + + time = MS2SEC( targetTime[ i ] - gameLocal.time ); + if ( time > shake_time ) { + continue; + } + + entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, entOrg, toPos, MASK_OPAQUE, ent ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) { + lastTargetPos[ i ] = toPos; + } + + if ( time < 0.0f ) { + idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel ); + vel *= speed; + entPhys->SetLinearVelocity( vel ); + if ( !end_time ) { + targetTime[ i ] = 0; + } else { + targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } + } else { + // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :) + ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z ); + ang *= ( 1.0f - time / shake_time ); + entPhys->SetAngularVelocity( ang ); + } + } + + if ( !num ) { + BecomeInactive( TH_THINK ); + } +} + +// RAVEN BEGIN +// bdube: jump points + +/* +=============================================================================== + +rvDebugJumpPoint + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvDebugJumpPoint ) +END_CLASS + +/* +===================== +rvDebugJumpPoint::Spawn +===================== +*/ +void rvDebugJumpPoint::Spawn() { + // Add the jump point + gameDebug.JumpAdd ( name, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles ( ) ); + + // Dont need the entity any more + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvFuncSaveGame ) + EVENT( EV_Activate, rvFuncSaveGame::Event_Activate ) +END_CLASS + +void rvFuncSaveGame::Spawn( void ) { + +} + +void rvFuncSaveGame::Event_Activate( idEntity *activator ) { + // TODO: Xenon - request the player to save the game. + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "saveGame checkPoint" ); +} diff --git a/source/mpgame/Misc.h b/source/mpgame/Misc.h new file mode 100644 index 0000000..9dd6a74 --- /dev/null +++ b/source/mpgame/Misc.h @@ -0,0 +1,974 @@ + +#ifndef __GAME_MISC_H__ +#define __GAME_MISC_H__ + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +class idSpawnableEntity : public idEntity { +public: + CLASS_PROTOTYPE( idSpawnableEntity ); + + void Spawn( void ); + +private: +}; + +/* +=============================================================================== + + Potential spawning position for players. + The first time a player enters the game, they will be at an 'initial' spot. + Targets will be fired when someone spawns in on them. + + When triggered, will cause player to be teleported to spawn spot. + +=============================================================================== +*/ + +class idPlayerStart : public idEntity { +public: + CLASS_PROTOTYPE( idPlayerStart ); + + enum { + EVENT_TELEPORTPLAYER = idEntity::EVENT_MAXEVENTS, + EVENT_TELEPORTITEM, + EVENT_MAXEVENTS + }; + + idPlayerStart( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + int teleportStage; + + void Event_TeleportEntity( idEntity *activator, bool predicted, idVec3& prevOrigin = vec3_origin ); + void Event_Teleport( idEntity *activator ); + void Teleport( idEntity* other ); + void Event_TeleportStage( idPlayer *player ); + void Event_ResetCamera( idPlayer *player ); + void TeleportPlayer( idPlayer *player ); +}; + + +/* +=============================================================================== + + Non-displayed entity used to activate triggers when it touches them. + Bind to a mover to have the mover activate a trigger as it moves. + When target by triggers, activating the trigger will toggle the + activator on and off. Check "start_off" to have it spawn disabled. + +=============================================================================== +*/ + +class idActivator : public idEntity { +public: + CLASS_PROTOTYPE( idActivator ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + bool stay_on; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + Path entities for monsters to follow. + +=============================================================================== +*/ +class idPathCorner : public idEntity { +public: + CLASS_PROTOTYPE( idPathCorner ); + + void Spawn( void ); + + static void DrawDebugInfo( void ); + + static idPathCorner *RandomPath( const idEntity *source, const idEntity *ignore ); + +private: + void Event_RandomPath( void ); +}; + +// RAVEN BEGIN +// bdube: jump points +/* +=============================================================================== + + Debug Jump Point + +=============================================================================== +*/ + +class rvDebugJumpPoint : public idEntity { +public: + + CLASS_PROTOTYPE( rvDebugJumpPoint ); + + void Spawn(); +}; + +/* +=============================================================================== + + Object that fires targets and changes shader parms when damaged. + +=============================================================================== +*/ + +class idDamagable : public idEntity { +public: + CLASS_PROTOTYPE( idDamagable ); + + idDamagable( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +// RAVEN BEGIN +// abahr: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + int invincibleTime; +// RAVEN BEGIN +// abahr: changed to protected +protected: + int stage; + int stageNext; + const idDict* stageDict; + int stageEndTime; + int stageEndHealth; + int stageEndSpeed; +//jshepard: used to end a stage if a moveable is on the ground (for falling objects) + bool stageEndOnGround; +//jshepard: we want to activate certain objects when triggered-- falling blocks yes, barrels no. + bool activateStageOnTrigger; + + virtual void ExecuteStage ( void ); + void UpdateStage ( void ); + idVec3 GetStageVector ( const char* key, const char* defaultString = "" ) const; + float GetStageFloat ( const char* key, const char* defaultString = "" ) const; + int GetStageInt ( const char* key, const char* defaultString = "" ) const; +// RAVEN END + + int count; + int nextTriggerTime; + + void BecomeBroken( idEntity *activator ); + void Event_BecomeBroken( idEntity *activator ); + void Event_RestoreDamagable( void ); +}; + +/* +=============================================================================== + + Hidden object that explodes when activated + +=============================================================================== +*/ + +class idExplodable : public idEntity { +public: + CLASS_PROTOTYPE( idExplodable ); + + void Spawn( void ); + +private: + void Event_Explode( idEntity *activator ); +}; + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +class idSpring : public idEntity { +public: + CLASS_PROTOTYPE( idSpring ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + idEntityPtr ent1; + idEntityPtr ent2; + int id1; + int id2; + idVec3 p1; + idVec3 p2; + idForce_Spring spring; + + void Event_LinkSpring( void ); +}; + + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +class idForceField : public idEntity { +public: + CLASS_PROTOTYPE( idForceField ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: idDamagable may want to change some things on the fly + void SetExplosion(float force) { forceField.Explosion(force); } +// RAVEN END + + +// RAVEN BEGIN +// bdube: made force field protected +protected: + + idForce_Field forceField; + +private: +// RAVEN END + void Toggle( void ); + + void Event_Activate( idEntity *activator ); + void Event_Toggle( void ); + void Event_FindTargets( void ); +}; + +// RAVEN BEGIN +// bdube: jump pads +/* +=============================================================================== + + rvJumpPad + +=============================================================================== +*/ + +class rvJumpPad : public idForceField { +public: + CLASS_PROTOTYPE( rvJumpPad ); + + rvJumpPad ( void ); + + void Spawn( void ); + void Think( void ); + +private: + + int lastEffectTime; + + void Event_FindTargets( void ); + + enum { + EVENT_JUMPFX = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + idMat3 effectAxis; +}; +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +class idAnimated : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAnimated ); + + idAnimated(); + ~idAnimated(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual bool LoadAF( const char* keyname ); + bool StartRagdoll( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// bdube: script + void Think ( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; +// RAVEN END + +private: + int num_anims; + int current_anim_index; + int anim; + int blendFrames; + jointHandle_t soundJoint; + idEntityPtr activator; + bool activated; + +// RAVEN BEGIN +// bdube: script variables + // script control + idThread * scriptThread; + idStr state; + idStr idealState; + int animDoneTime[ANIM_NumAnimChannels]; + + // Script state management + void UpdateScript( void ); + void SetState( const char *statename, int blend ); + void CallHandler ( const char* handler ); +// RAVEN END + + void PlayNextAnim( void ); + + void Event_Activate( idEntity *activator ); + void Event_Start( void ); + void Event_StartRagdoll( void ); + void Event_AnimDone( int animIndex ); + void Event_Footstep( void ); + void Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ); + void Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetAnimState( const char* state, int blendframes ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone2( int channel, int blendFrames ); +// RAVEN END +}; + +/* +=============================================================================== + + idStaticEntity + +=============================================================================== +*/ + +class idStaticEntity : public idEntity { +public: + CLASS_PROTOTYPE( idStaticEntity ); + + idStaticEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void ShowEditingDialog( void ); + virtual void Hide( void ); + virtual void Show( void ); + void Fade( const idVec4 &to, float fadeTime ); + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_Activate( idEntity *activator ); + + int spawnTime; + bool active; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool runGui; +}; + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + +class idFuncEmitter : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncEmitter ); + + idFuncEmitter( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Event_Activate( idEntity *activator ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + bool hidden; + +}; + + +// RAVEN BEGIN +// bdube: not using +#if 0 +// RAVEN END + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +class idFuncSmoke : public idEntity { +public: + CLASS_PROTOTYPE( idFuncSmoke ); + + idFuncSmoke(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void Event_Activate( idEntity *activator ); + +private: + int smokeTime; + const idDeclParticle * smoke; + bool restart; +}; + +// RAVEN BEGIN +// bdube: not using +#endif +// RAVEN END + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + +class idFuncSplat : public idFuncEmitter { +public: + CLASS_PROTOTYPE( idFuncSplat ); + + idFuncSplat( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Splat(); +}; + + +/* +=============================================================================== + +idTextEntity + +=============================================================================== +*/ + +class idTextEntity : public idEntity { +public: + CLASS_PROTOTYPE( idTextEntity ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +private: + idStr text; + bool playerOriented; +}; + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +class idLocationEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationEntity ); + + void Spawn( void ); + + const char * GetLocation( void ) const; + +private: +}; + +class idLocationSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationSeparatorEntity ); + + void Spawn( void ); + +private: +}; + +class idVacuumSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumSeparatorEntity ); + + idVacuumSeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class idVacuumEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumEntity ); + + void Spawn( void ); + +private: +}; + +// RAVEN BEGIN +// abahr +class rvGravitySeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( rvGravitySeparatorEntity ); + + rvGravitySeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class rvGravityArea : public idEntity { +public: + ABSTRACT_PROTOTYPE( rvGravityArea ); + + void Spawn( void ); + + virtual int GetArea() const { return area; } + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const = 0; + virtual const idVec3 GetGravity( const idEntity* ent ) const = 0; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const = 0; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsEqualTo( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea& area ) const; + bool operator!=( const rvGravityArea* area ) const; + bool operator!=( const rvGravityArea& area ) const; + +protected: + int area; +}; + +class rvGravityArea_Static : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_Static ); + + void Spawn( void ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { return gravity; } + virtual const idVec3 GetGravity( const idEntity* ent ) const { return gravity; } + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const { return gravity; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 gravity; +}; + +class rvGravityArea_SurfaceNormal : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_SurfaceNormal ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const; + virtual const idVec3 GetGravity( const idEntity* ent ) const; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const; + +protected: + virtual const idVec3 GetGravity( const idPhysics* physics ) const; +}; +// RAVEN END + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +class idBeam : public idEntity { +public: + CLASS_PROTOTYPE( idBeam ); + + idBeam(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + void SetMaster( idBeam *masterbeam ); + void SetBeamTarget( const idVec3 &origin ); + + virtual void Show( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_MatchTarget( void ); + void Event_Activate( idEntity *activator ); + + idEntityPtr target; + idEntityPtr master; +}; + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +class idRenderModelLiquid; + +class idLiquid : public idEntity { +public: + CLASS_PROTOTYPE( idLiquid ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Touch( idEntity *other, trace_t *trace ); + + + idRenderModelLiquid *model; +}; + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +class idShaking : public idEntity { +public: + CLASS_PROTOTYPE( idShaking ); + + idShaking(); + ~idShaking(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idPhysics_Parametric physicsObj; + bool active; + + void BeginShaking( void ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +class idEarthQuake : public idEntity { +public: + CLASS_PROTOTYPE( idEarthQuake ); + + idEarthQuake(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off +protected: + void AffectNearbyEntities(float affectRadius); +// RAVEN END + +private: + int nextTriggerTime; + int shakeStopTime; + float wait; + float random; + bool triggered; + bool playerOriented; + bool disabled; + float shakeTime; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +class idFuncPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncPortal ); + + idFuncPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + qhandle_t portal; + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +class idFuncAASPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASPortal ); + + idFuncAASPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +class idFuncAASObstacle : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASObstacle ); + + idFuncAASObstacle(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetState ( bool _state ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +class idFuncRadioChatter : public idEntity { +public: + CLASS_PROTOTYPE( idFuncRadioChatter ); + + idFuncRadioChatter(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RepeatLast ( void ); + +private: + static idEntityPtr lastRadioChatter; + float time; + bool isActive; + void Event_Activate( idEntity *activator ); + void Event_ResetRadioHud( idEntity *activator ); + void Event_IsActive( void ); +}; + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +class idPhantomObjects : public idEntity { +public: + CLASS_PROTOTYPE( idPhantomObjects ); + + idPhantomObjects(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Throw( void ); + void Event_ShakeObject( idEntity *object, int starttime ); + + int end_time; + float throw_time; + float shake_time; + idVec3 shake_ang; + float speed; + int min_wait; + int max_wait; + idEntityPtrtarget; + idList targetTime; + idList lastTargetPos; +}; + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +class rvFuncSaveGame : public idEntity { +public: + CLASS_PROTOTYPE( rvFuncSaveGame ); + + void Spawn( void ); + + void Event_Activate ( idEntity *activator ); + +private: +}; + + +#endif /* !__GAME_MISC_H__ */ diff --git a/source/mpgame/Moveable.cpp b/source/mpgame/Moveable.cpp new file mode 100644 index 0000000..27ab3de --- /dev/null +++ b/source/mpgame/Moveable.cpp @@ -0,0 +1,1347 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif + +/* +=============================================================================== + + idMoveable + +=============================================================================== +*/ + +const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); +const idEventDef EV_SetOwnerFromSpawnArgs( "" ); +const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); +const idEventDef EV_CanDamage( "canDamage", "f" ); +const idEventDef EV_SetHealth( "setHealth", "f" ); +const idEventDef EV_RadiusDamage( "", "es" ); + +CLASS_DECLARATION( idDamagable, idMoveable ) + EVENT( EV_Activate, idMoveable::Event_Activate ) + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) + EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) + EVENT( EV_CanDamage, idMoveable::Event_CanDamage ) + EVENT( EV_SetHealth, idMoveable::Event_SetHealth ) + EVENT( EV_RadiusDamage, idMoveable::Event_RadiusDamage ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; +static const int BOUNCE_SOUND_DELAY_MIN = 500.0f; +static const int BOUNCE_SOUND_DELAY_MAX = 1200.0f; + +/* +================ +idMoveable::idMoveable +================ +*/ +idMoveable::idMoveable( void ) { + minDamageVelocity = 100.0f; + maxDamageVelocity = 200.0f; + nextCollideFxTime = 0; + initialSpline = NULL; + initialSplineDir = vec3_zero; + unbindOnDeath = false; + allowStep = false; + canDamage = false; + + lastAttacker = NULL; +} + +/* +================ +idMoveable::~idMoveable +================ +*/ +idMoveable::~idMoveable( void ) { + delete initialSpline; + initialSpline = NULL; + SetPhysics( NULL ); +} + +/* +================ +idMoveable::Spawn +================ +*/ +void idMoveable::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + int clipShrink; + idStr clipModelName; + bool setClipModel = false; + idBounds bounds; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + idVec3 size; + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on moveable '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on moveable '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + } + + if ( setClipModel ) { + trm.SetupBox( bounds ); + } else { + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString ( "model" ); // use the visual model + } + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + } + + // if the model should be shrinked + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + unbindOnDeath = spawnArgs.GetBool( "unbindondeath" ); + + nextCollideFxTime = 0; + + damage = spawnArgs.GetString( "def_damage", "" ); + canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true; + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + + if ( health ) { + if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { + gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() ); + } + } + + fl.takedamage = (health > 0 ); + + // setup the physics + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM( this ); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( trm, GetRenderModelMaterial() ), density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + physicsObj.DropToFloor(); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + + allowStep = spawnArgs.GetBool( "allowStep", "1" ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = !physicsObj.IsPushable(); +// RAVEN END + + PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 ); +} + +/* +================ +idMoveable::Save +================ +*/ +void idMoveable::Save( idSaveGame *savefile ) const { + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteString( brokenModel ); + savefile->WriteString( damage ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteFloat( minDamageVelocity ); + savefile->WriteFloat( maxDamageVelocity ); + + savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 ); + savefile->WriteVec3( initialSplineDir ); + + savefile->WriteBool( unbindOnDeath ); + savefile->WriteBool( allowStep ); + savefile->WriteBool( canDamage ); + + lastAttacker.Save(savefile); // cnicholson: Added unsaved var +} + +/* +================ +idMoveable::Restore +================ +*/ +void idMoveable::Restore( idRestoreGame *savefile ) { + int initialSplineTime; + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadString( brokenModel ); + savefile->ReadString( damage ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadFloat( minDamageVelocity ); + savefile->ReadFloat( maxDamageVelocity ); + + savefile->ReadInt( initialSplineTime ); + if ( initialSplineTime != -1 ) { + InitInitialSpline( initialSplineTime ); + } else { + initialSpline = NULL; + } + + savefile->ReadVec3( initialSplineDir ); + savefile->ReadBool( unbindOnDeath ); + savefile->ReadBool( allowStep ); + savefile->ReadBool( canDamage ); // cnicholson: Added unrestored var + + lastAttacker.Restore(savefile); // cnicholson: Added unrestored var + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMoveable::Hide +================ +*/ +void idMoveable::Hide( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMoveable::Show +================ +*/ +void idMoveable::Show( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Show(); + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } +} + +/* +================= +idMoveable::Collide +================= +*/ +bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + float len, f; + idVec3 dir; + idEntity *ent; + + dir = velocity; + len = dir.NormalizeFast(); + + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( gameLocal.time > nextCollideFxTime ) { + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_collide",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3() ); +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( len <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + SetSoundVolume( f ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, false, NULL ); + + nextCollideFxTime = gameLocal.time + BOUNCE_SOUND_DELAY_MIN + gameLocal.random.RandomInt(BOUNCE_SOUND_DELAY_MAX - BOUNCE_SOUND_DELAY_MIN); + } + } + + if ( canDamage && damage.Length() ) { + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent && len > minDamageVelocity ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > maxDamageVelocity ) { + f = 1.0f; + } else if( len <= minDamageVelocity ) { + f = 0.0f; + } else { + f = idMath::Sqrt( len - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); + } +// RAVEN END + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + } + } + + return false; +} + +/* +============ +idMoveable::Damage +============ +*/ +void idMoveable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idDamagable::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Cache the attacker to ensure credit for a kill is given to the entity that caused the damage + lastAttacker = inflictor; +// jshepard: +// gameLocal.Warning("idMoveable Damaged! Health is %d", health); + +} + +/* +============ +idMoveable::Killed +============ +*/ +void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + +// jshepard: I am dead! +// gameLocal.Warning("idMoveable Killed! Health is %d", health); + +/* + // No more taking damage + fl.takedamage = false; + + // Should the moveable launch around when killed? + int launchTime; + launchTime = SEC2MS ( spawnArgs.GetFloat ( "launch_time", "0" ); + if ( launchTime > 0 ) { + launchOffset = spawnArgs.GetVector ( "launch_offset" ); + launchDir = spawnArgs.GetVector ( " + } + + spawnArgs.GetFloat ( "explode_lapse", + PostEventSec ( &EV_Remove, explodeLapse ); + + // Two part explosion? + explode_impulse + explode_lapse + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( brokenModel != "" ) { + SetModel( brokenModel ); + } + + if ( explode ) { + if ( brokenModel == "" ) { + Hide(); + physicsObj.PutToRest(); + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 0 ); + } + + const char *splash = spawnArgs.GetString( "def_splash_damage", "" ); + if ( splash && *splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), inflictor, inflictor, this, this, splash ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + StopAllEffects ( ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + + fl.takedamage = false; +*/ +} + + +/* +================ +idMoveable::ExecuteStage +================ +*/ +void idMoveable::ExecuteStage ( void ) { + // Splash damage? + const char *splash; + if ( stageDict->GetString( "def_splash_damage", "", &splash ) && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, splash ); + } + + idDamagable::ExecuteStage(); +} + +/* +================ +idMoveable::AddDamageEffect +================ +*/ +void idMoveable::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + + // Play an impact effect during this stage? + if ( stageDict ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_impact" ), + collision.c.point, collision.c.normal.ToMat3(), + true, vec3_origin, true ); + } +} + +/* +================ +idMoveable::AllowStep +================ +*/ +bool idMoveable::AllowStep( void ) const { + return allowStep; +} + +/* +================ +idMoveable::BecomeNonSolid +================ +*/ +void idMoveable::BecomeNonSolid( void ) { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + +/* +================ +idMoveable::EnableDamage +================ +*/ +void idMoveable::EnableDamage( bool enable, float duration ) { + canDamage = enable; + if ( duration ) { + PostEventSec( &EV_CanDamage, duration, ( !enable ) ? 0.0f : 1.0f ); + } +} + +/* +================ +idMoveable::InitInitialSpline +================ +*/ +void idMoveable::InitInitialSpline( int startTime ) { + int initialSplineTime; + + initialSpline = GetSpline(); + initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" ); + + if ( initialSpline != NULL ) { + initialSpline->MakeUniform( initialSplineTime ); + initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) ); + initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime ); + initialSplineDir *= physicsObj.GetAxis().Transpose(); + initialSplineDir.Normalize(); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idMoveable::FollowInitialSplinePath +================ +*/ +bool idMoveable::FollowInitialSplinePath( void ) { + if ( initialSpline != NULL ) { + if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { + idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.GetMHz(); + physicsObj.SetLinearVelocity( linearVelocity ); + + idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); + idVec3 dir = initialSplineDir * physicsObj.GetAxis(); + idVec3 angularVelocity = dir.Cross( splineDir ); + angularVelocity.Normalize(); + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.GetMHz(); + physicsObj.SetAngularVelocity( angularVelocity ); + return true; + } else { + delete initialSpline; + initialSpline = NULL; + } + } + return false; +} + +/* +================ +idMoveable::Think +================ +*/ +void idMoveable::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // Move to the next stage? + UpdateStage ( ); + + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Think(); +} + +/* +================ +idMoveable::GetRenderModelMaterial +================ +*/ +const idMaterial *idMoveable::GetRenderModelMaterial( void ) const { + if ( renderEntity.customShader ) { + return renderEntity.customShader; + } + if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) { + return renderEntity.hModel->Surface( 0 )->shader; + } + return NULL; +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idMoveable::Event_BecomeNonSolid +================ +*/ +void idMoveable::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idMoveable::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + if ( !spawnArgs.GetInt( "notPushable" ) ) { + physicsObj.EnableImpact(); + } + + physicsObj.Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } + + InitInitialSpline( gameLocal.time ); + +// RAVEN BEGIN +// jshepard: we should update it's stage on activation, specifically for falling blocks. + UpdateStage(); +// RAVEN END + +} + +/* +================ +idMoveable::Event_SetOwnerFromSpawnArgs +================ +*/ +void idMoveable::Event_SetOwnerFromSpawnArgs( void ) { + idStr owner; + + if ( spawnArgs.GetString( "owner", "", owner ) ) { + ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) ); + } +} + +/* +================ +idMoveable::Event_IsAtRest +================ +*/ +void idMoveable::Event_IsAtRest( void ) { + idThread::ReturnInt( physicsObj.IsAtRest() ); +} + +/* +================ +idMoveable::Event_CanDamage +================ +*/ +void idMoveable::Event_CanDamage( float enable ) { + canDamage = ( enable != 0.0f ); +} + +/* +================ +idMoveable::Event_SetHealth +================ +*/ +void idMoveable::Event_SetHealth( float newHealth ) { + health = newHealth; +} + +/* +================ +idMoveable::Event_RadiusDamage +================ +*/ +void idMoveable::Event_RadiusDamage( idEntity *attacker, const char* splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash ); +} + +/* +=============================================================================== + + idBarrel + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveable, idBarrel ) +END_CLASS + +/* +================ +idBarrel::idBarrel +================ +*/ +idBarrel::idBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +idBarrel::Save +================ +*/ +void idBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +idBarrel::Restore +================ +*/ +void idBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +idBarrel::BarrelThink +================ +*/ +void idBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // Progress to the next stage? + UpdateStage ( ); + + // run physics + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + onGround = GetPhysics()->HasGroundContacts(); + curOrigin = GetPhysics()->GetOrigin(); + curAxis = GetPhysics()->GetAxis(); + + // if the barrel is on the ground + if ( onGround ) { + gravityNormal = GetPhysics()->GetGravityNormal(); + + dir = curOrigin - lastOrigin; + dir -= gravityNormal * dir * gravityNormal; + movedDistance = dir.LengthSqr(); + + // if the barrel moved and the barrel is not aligned with the gravity direction + if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) { + + // barrel movement since last think frame orthogonal to the barrel axis + movedDistance = idMath::Sqrt( movedDistance ); + dir *= 1.0f / movedDistance; + movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance; + + // get rotation about barrel axis since last think frame + angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3]; + angle = idMath::ACos( angle ); + // distance along cylinder hull + rotatedDistance = angle * radius; + + // if the barrel moved further than it rotated about it's axis + if ( movedDistance > rotatedDistance ) { + + // additional rotation of the visual model to make it look + // like the barrel rolls instead of slides + angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI); + if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) { + additionalRotation += angle; + } else { + additionalRotation -= angle; + } + dir = vec3_origin; + dir[barrelAxis] = 1.0f; + additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3(); + } + } + } + + // save state for next think + lastOrigin = curOrigin; + lastAxis = curAxis; + } + + Present(); +} + +/* +================ +idBarrel::Think +================ +*/ +void idBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +idBarrel::GetPhysicsToVisualTransform +================ +*/ +bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +idBarrel::Spawn +================ +*/ +void idBarrel::Spawn( void ) { + const idBounds &bounds = GetPhysics()->GetBounds(); + + // radius of the barrel cylinder + radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f; + + // always a vertical barrel with cylinder axis parallel to the z-axis + barrelAxis = 2; + + lastOrigin = GetPhysics()->GetOrigin(); + lastAxis = GetPhysics()->GetAxis(); + + additionalRotation = 0.0f; + additionalAxis.Identity(); +} + +/* +================ +idBarrel::ClientPredictionThink +================ +*/ +void idBarrel::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============================================================================== + +idExplodingBarrel + +=============================================================================== +*/ +const idEventDef EV_Respawn( "" ); +const idEventDef EV_TriggerTargets( "" ); + +CLASS_DECLARATION( idBarrel, idExplodingBarrel ) + EVENT( EV_Activate, idExplodingBarrel::Event_Activate ) + EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn ) + EVENT( EV_Explode, idExplodingBarrel::Event_Explode ) + EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets ) +END_CLASS + +/* +================ +idExplodingBarrel::idExplodingBarrel +================ +*/ +idExplodingBarrel::idExplodingBarrel() { + spawnOrigin.Zero(); + spawnAxis.Zero(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + ipsTime = 0; + lightTime = 0; + time = 0.0f; + explodeFinishTime = 0; +} + +/* +================ +idExplodingBarrel::~idExplodingBarrel +================ +*/ +idExplodingBarrel::~idExplodingBarrel() { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + if ( lightHandle >= 0 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + } +} + +/* +================ +idExplodingBarrel::Save +================ +*/ +void idExplodingBarrel::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( state ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( ipsHandle ); + savefile->WriteInt( lightHandle ); + savefile->WriteRenderEntity( ipsEntity ); + savefile->WriteRenderLight( light ); + savefile->WriteInt( ipsTime ); + savefile->WriteInt( lightTime ); + savefile->WriteFloat( time ); + savefile->WriteInt( explodeFinishTime ); +} + +/* +================ +idExplodingBarrel::Restore +================ +*/ +void idExplodingBarrel::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( (int &)state ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( (int &)ipsHandle ); + savefile->ReadInt( (int &)lightHandle ); +// RAVEN BEGIN + savefile->ReadRenderEntity( ipsEntity, &spawnArgs ); +// RAVEN END + savefile->ReadRenderLight( light ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &light ); + } + savefile->ReadInt( ipsTime ); + savefile->ReadInt( lightTime ); + savefile->ReadFloat( time ); + savefile->ReadInt( explodeFinishTime ); + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } + + if ( ipsHandle != -1 ) { + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::Spawn +================ +*/ +void idExplodingBarrel::Spawn( void ) { + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + lightTime = 0; + ipsTime = 0; + time = spawnArgs.GetFloat( "time" ); + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + explodeFinishTime = 0; + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Think( void ) { + idBarrel::BarrelThink(); + + // MP: EXPLODED means no effect on client when updating the state + if ( !gameLocal.isClient && state == EXPLODING ) { + if ( gameLocal.time > explodeFinishTime ) { + state = EXPLODED; + } + } + + if ( lightHandle >= 0 ){ + if ( state == BURNING ) { + // ramp the color up over 250 ms + float pct = (gameLocal.time - lightTime) / 250.f; + if ( pct > 1.0f ) { + pct = 1.0f; + } + light.origin = physicsObj.GetAbsBounds().GetCenter(); + light.axis = mat3_identity; + light.shaderParms[ SHADERPARM_RED ] = pct; + light.shaderParms[ SHADERPARM_GREEN ] = pct; + light.shaderParms[ SHADERPARM_BLUE ] = pct; + light.shaderParms[ SHADERPARM_ALPHA ] = pct; + gameRenderWorld->UpdateLightDef( lightHandle, &light ); + } else { + if ( gameLocal.time - lightTime > 250 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } + return; + } + } + + if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) { + BecomeInactive( TH_THINK ); + return; + } + + if ( ipsHandle >= 0 ){ + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + gameRenderWorld->UpdateEntityDef( ipsHandle, &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::AddIPS +================ +*/ +void idExplodingBarrel::AddIPS( const char *name, bool burn ) { + if ( name && *name ) { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + memset( &ipsEntity, 0, sizeof ( ipsEntity ) ); + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name ) ); + if ( modelDef ) { + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + ipsEntity.hModel = modelDef->ModelHandle(); + float rgb = ( burn ) ? 0.0f : 1.0f; + ipsEntity.shaderParms[ SHADERPARM_RED ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_GREEN ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_BLUE ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + ipsEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 ); + if ( !ipsEntity.hModel ) { + ipsEntity.hModel = renderModelManager->FindModel( name ); + } + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + if ( burn ) { + BecomeActive( TH_THINK ); + } + ipsTime = gameLocal.time; + } + } +} + +/* +================ +idExplodingBarrel::AddLight +================ +*/ +void idExplodingBarrel::AddLight( const char *name, bool burn ) { + if ( lightHandle >= 0 ){ + gameRenderWorld->FreeLightDef( lightHandle ); + } + memset( &light, 0, sizeof ( light ) ); + light.axis = mat3_identity; + light.lightRadius.x = spawnArgs.GetFloat( "light_radius" ); + light.lightRadius.y = light.lightRadius.z = light.lightRadius.x; + light.origin = physicsObj.GetOrigin(); + light.origin.z += 128; + light.pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: made sure that barrels are set to no shadows + light.noShadows = true; +// RAVEN END + light.shader = declManager->FindMaterial( name ); + light.shaderParms[ SHADERPARM_RED ] = 2.0f; + light.shaderParms[ SHADERPARM_GREEN ] = 2.0f; + light.shaderParms[ SHADERPARM_BLUE ] = 2.0f; + light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f; + lightHandle = gameRenderWorld->AddLightDef( &light ); + lightTime = gameLocal.time; + BecomeActive( TH_THINK ); +} + +/* +================ +idExplodingBarrel::ExplodingEffects +================ +*/ +void idExplodingBarrel::ExplodingEffects( void ) { + const char *temp; + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + temp = spawnArgs.GetString( "model_damage", "" ); + if ( temp && *temp ) { + SetModel( temp ); + Show(); + } + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + temp = spawnArgs.GetString( "mtr_lightexplode", "" ); + if ( temp && *temp ) { + AddLight( temp, false ); + } +*/ + StopEffect ( "fx_burn" ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); +} + +/* +================ +idExplodingBarrel::Killed +================ +*/ +void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if ( IsHidden() || state == EXPLODED || state == EXPLODING || state == BURNING ) { + return; + } + + float f = spawnArgs.GetFloat( "burn" ); + if ( f > 0.0f && state == NORMAL ) { + state = BURNING; + PostEventSec( &EV_Explode, f ); + StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_burn"), + vec3_origin, (-GetPhysics()->GetGravityNormal()).ToMat3(), true, vec3_origin, true ); + return; + } else { + state = EXPLODING; + spawnArgs.GetInt( "explode_lapse", "1000", explodeFinishTime ); + explodeFinishTime += gameLocal.time; + } + + // do this before applying radius damage so the ent can trace to any damagable ents nearby + Hide(); + physicsObj.SetContents( 0 ); + + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, inflictor, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, this, splash); + } + + ExplodingEffects( ); + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + //FIXME: need to precache all the debris stuff here and in the projectiles + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } +*/ +// RAVEN END + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + f = spawnArgs.GetFloat( "respawn" ); + if ( f > 0.0f ) { + PostEventSec( &EV_Respawn, f ); + } else { + PostEventMS( &EV_Remove, 5000 ); + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { + PostEventMS( &EV_Explode, 400 ); + } else { + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + + +/* +================ +idExplodingBarrel::Event_TriggerTargets +================ +*/ +void idExplodingBarrel::Event_TriggerTargets() { + ActivateTargets( this ); +} + +/* +================ +idExplodingBarrel::Event_Explode +================ +*/ +void idExplodingBarrel::Event_Explode() { + if ( state == NORMAL || state == BURNING ) { + state = BURNEXPIRED; + Killed( NULL, NULL, 0, vec3_zero, 0 ); + } +} + +/* +================ +idExplodingBarrel::Event_Respawn +================ +*/ +void idExplodingBarrel::Event_Respawn() { + const char *temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = (health > 0); + physicsObj.SetOrigin( spawnOrigin ); + physicsObj.SetAxis( spawnAxis ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.DropToFloor(); + state = NORMAL; + Show(); + UpdateVisuals(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idExplodingBarrel::Event_Activate( idEntity *activator ) { + Killed( activator, activator, 0, vec3_origin, 0 ); +} + + + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const { + idMoveable::WriteToSnapshot( msg ); + msg.WriteBits( IsHidden(), 1 ); + msg.WriteBits( state, 3 ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) { + explode_state_t newState; + + idMoveable::ReadFromSnapshot( msg ); + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } + newState = (explode_state_t)msg.ReadBits( 3 ); + if ( newState != state ) { + state = newState; + if ( state == EXPLODING ) { + ExplodingEffects( ); + } + } +} + +// RAVEN END diff --git a/source/mpgame/Moveable.h b/source/mpgame/Moveable.h new file mode 100644 index 0000000..39a7903 --- /dev/null +++ b/source/mpgame/Moveable.h @@ -0,0 +1,179 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 06/02/2004 + +#ifndef __GAME_MOVEABLE_H__ +#define __GAME_MOVEABLE_H__ + +/* +=============================================================================== + + Entity using rigid body physics. + +=============================================================================== +*/ + +extern const idEventDef EV_BecomeNonSolid; +extern const idEventDef EV_IsAtRest; + +class idMoveable : public idDamagable { +public: + CLASS_PROTOTYPE( idMoveable ); + + idMoveable( void ); + ~idMoveable( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + bool AllowStep( void ) const; + void EnableDamage( bool enable, float duration ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + idPhysics_RigidBody physicsObj; // physics object + idStr brokenModel; // model set when health drops down to or below zero + idStr damage; // if > 0 apply damage to hit entities + int nextCollideFxTime; // next time it is ok to spawn collision fx + float minDamageVelocity; // minimum velocity before moveable applies damage + float maxDamageVelocity; // velocity at which the maximum damage is applied + idCurve_Spline *initialSpline; // initial spline path the moveable follows + idVec3 initialSplineDir; // initial relative direction along the spline path + bool unbindOnDeath; // unbind from master when health drops down to or below zero + bool allowStep; // allow monsters to step on the object + bool canDamage; // only apply damage when this is set + + idEntityPtr lastAttacker; + + virtual void ExecuteStage ( void ); + + const idMaterial * GetRenderModelMaterial( void ) const; + void BecomeNonSolid( void ); + void InitInitialSpline( int startTime ); + bool FollowInitialSplinePath( void ); + + void Event_Activate( idEntity *activator ); + void Event_BecomeNonSolid( void ); + void Event_SetOwnerFromSpawnArgs( void ); + void Event_IsAtRest( void ); + void Event_CanDamage ( float enable ); + void Event_SetHealth ( float newHealth ); + void Event_RadiusDamage( idEntity *attacker, const char* splash ); +}; + + +/* +=============================================================================== + + A barrel using rigid body physics. The barrel has special handling of + the view model orientation to make it look like it rolls instead of slides. + +=============================================================================== +*/ + +class idBarrel : public idMoveable { + +public: + CLASS_PROTOTYPE( idBarrel ); + idBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void BarrelThink( void ); + virtual void Think( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void ClientPredictionThink( void ); + +private: + float radius; // radius of barrel + int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to + idVec3 lastOrigin; // origin of the barrel the last think frame + idMat3 lastAxis; // axis of the barrel the last think frame + float additionalRotation; // additional rotation of the barrel about it's axis + idMat3 additionalAxis; // additional rotation axis +}; + + +/* +=============================================================================== + + A barrel using rigid body physics and special handling of the view model + orientation to make it look like it rolls instead of slides. The barrel + can burn and explode when damaged. + +=============================================================================== +*/ + +class idExplodingBarrel : public idBarrel { +public: + CLASS_PROTOTYPE( idExplodingBarrel ); + + idExplodingBarrel(); + ~idExplodingBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + typedef enum { + NORMAL = 0, + BURNING, + BURNEXPIRED, + EXPLODING, + EXPLODED + } explode_state_t; + explode_state_t state; + + idVec3 spawnOrigin; + idMat3 spawnAxis; + qhandle_t ipsHandle; + qhandle_t lightHandle; + renderEntity_t ipsEntity; + renderLight_t light; + int ipsTime; + int lightTime; + float time; + + int explodeFinishTime; + + void AddIPS( const char *name, bool burn ); + void AddLight( const char *name , bool burn ); + void ExplodingEffects( void ); + + void Event_Activate( idEntity *activator ); + void Event_Respawn(); + void Event_Explode(); + void Event_TriggerTargets(); +}; + +#endif /* !__GAME_MOVEABLE_H__ */ + +// RAVEN END diff --git a/source/mpgame/Mover.cpp b/source/mpgame/Mover.cpp new file mode 100644 index 0000000..f6590ed --- /dev/null +++ b/source/mpgame/Mover.cpp @@ -0,0 +1,5974 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +// a mover will update any gui entities in it's target list with +// a key/val pair of "mover" "state" from below.. guis can represent +// realtime info like this +// binary only +static const char *guiBinaryMoverStates[] = { + "1", // pos 1 + "2", // pos 2 + "3", // moving 1 to 2 + "4" // moving 2 to 1 +}; + + +/* +=============================================================================== + +idMover + +=============================================================================== +*/ + +const idEventDef EV_FindGuiTargets( "", NULL ); +const idEventDef EV_TeamBlocked( "", "ee" ); +const idEventDef EV_PartBlocked( "", "e" ); +const idEventDef EV_ReachedPos( "", NULL ); +const idEventDef EV_ReachedAng( "", NULL ); +// RAVEN BEGIN +const idEventDef EV_PostRestoreExt( "", "ddddd" ); +// RAVEN END +const idEventDef EV_StopMoving( "stopMoving", NULL ); +const idEventDef EV_StopRotating( "stopRotating", NULL ); +const idEventDef EV_Speed( "speed", "f" ); +const idEventDef EV_Time( "time", "f" ); +const idEventDef EV_AccelTime( "accelTime", "f" ); +const idEventDef EV_DecelTime( "decelTime", "f" ); +const idEventDef EV_MoveTo( "moveTo", "e" ); +const idEventDef EV_MoveToPos( "moveToPos", "v" ); +const idEventDef EV_Move( "move", "ff" ); +const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); +const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); +const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); +const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); +const idEventDef EV_RotateTo( "rotateTo", "v" ); +const idEventDef EV_Rotate( "rotate", "v" ); +const idEventDef EV_RotateOnce( "rotateOnce", "v" ); +const idEventDef EV_Bob( "bob", "ffv" ); +const idEventDef EV_Sway( "sway", "ffv" ); +const idEventDef EV_Mover_OpenPortal( "openPortal" ); +const idEventDef EV_Mover_ClosePortal( "closePortal" ); +const idEventDef EV_AccelSound( "accelSound", "s" ); +const idEventDef EV_DecelSound( "decelSound", "s" ); +// RAVEN BEGIN +// cnicholson: added stop sound support +const idEventDef EV_StoppedSound( "stoppedSound", "s" ); +// RAVEN END +const idEventDef EV_MoveSound( "moveSound", "s" ); +const idEventDef EV_Mover_InitGuiTargets( "", NULL ); +const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); +const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); +const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); +const idEventDef EV_StartSpline( "startSpline", "e" ); +const idEventDef EV_StopSpline( "stopSpline", NULL ); +const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); +const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); +// RAVEN BEGIN +// abahr: +const idEventDef EV_GetSplineEntity( "getSplineEntity", NULL, 'E' ); +const idEventDef EV_MoveAlongVector( "moveAlongVector", "v" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idMover ) + EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) + EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) + EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) + EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) +// RAVEN BEGIN + EVENT( EV_PostRestoreExt, idMover::Event_PostRestoreExt ) +// RAVEN END + EVENT( EV_StopMoving, idMover::Event_StopMoving ) + EVENT( EV_StopRotating, idMover::Event_StopRotating ) + EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) + EVENT( EV_Time, idMover::Event_SetMoveTime ) + EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) + EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) + EVENT( EV_MoveTo, idMover::Event_MoveTo ) + EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) + EVENT( EV_Move, idMover::Event_MoveDir ) + EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) + EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) + EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) + EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) + EVENT( EV_RotateTo, idMover::Event_RotateTo ) + EVENT( EV_Rotate, idMover::Event_Rotate ) + EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) + EVENT( EV_Bob, idMover::Event_Bob ) + EVENT( EV_Sway, idMover::Event_Sway ) + EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) + EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) + EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) +// RAVEN BEGIN +// cnicholson: added stop sound support + EVENT( EV_StoppedSound, idMover::Event_SetStoppedSound ) +// RAVEN END + EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) + EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) + EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) + EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) + EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) + EVENT( EV_StartSpline, idMover::Event_StartSpline ) + EVENT( EV_StopSpline, idMover::Event_StopSpline ) + EVENT( EV_Activate, idMover::Event_Activate ) + EVENT( EV_IsMoving, idMover::Event_IsMoving ) + EVENT( EV_IsRotating, idMover::Event_IsRotating ) +// RAVEN BEGIN +// abahr: + EVENT( EV_GetSplineEntity, idMover::Event_GetSplineEntity ) + EVENT( EV_MoveAlongVector, idMover::Event_MoveAlongVector ) +// RAVEN END +END_CLASS + +/* +================ +idMover::idMover +================ +*/ +idMover::idMover( void ) { + memset( &move, 0, sizeof( move ) ); + memset( &rot, 0, sizeof( rot ) ); + move_thread = 0; + rotate_thread = 0; + dest_angles.Zero(); + angle_delta.Zero(); + dest_position.Zero(); + move_delta.Zero(); + move_speed = 0.0f; + move_time = 0; + deceltime = 0; + acceltime = 0; + stopRotation = false; + useSplineAngles = true; + attenuate = false; + useIdleSound = false; + lastCommand = MOVER_NONE; + damage = 0.0f; + areaPortal = 0; + maxAttenuation = 0.0f; + attenuationScale = 0.0f; + lastOrigin.Zero( ); + lastTime = 0; + splineStartTime = 0; + + fl.networkSync = true; +} + + +idMover::~idMover( void ) { + SetPhysics( NULL ); +} + + + +/* +================ +idMover::Save +================ +*/ +void idMover::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteStaticObject( physicsObj ); + + savefile->Write( &move, sizeof( move ) ); + savefile->Write( &rot, sizeof( rot ) ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( rotate_thread ); + + savefile->WriteAngles( dest_angles ); + savefile->WriteAngles( angle_delta ); + savefile->WriteVec3( dest_position ); + savefile->WriteVec3( move_delta ); + + savefile->WriteFloat( move_speed ); + savefile->WriteInt( move_time ); + savefile->WriteInt( deceltime ); + savefile->WriteInt( acceltime ); + savefile->WriteBool( stopRotation ); + savefile->WriteBool( useSplineAngles ); + savefile->WriteInt( lastCommand ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal > 0 ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } + + if ( splineEnt.GetEntity() ) { + idCurve_Spline *spline = physicsObj.GetSpline(); + if (spline) { + + savefile->WriteBool( true ); + splineEnt.Save( savefile ); + savefile->WriteInt( spline->GetTime( 0 ) ); + savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); + savefile->WriteInt( physicsObj.GetSplineAcceleration() ); + savefile->WriteInt( physicsObj.GetSplineDeceleration() ); + savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); + } else { + savefile->WriteBool( false ); + } + + } else { + savefile->WriteBool( false ); + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->WriteBool( attenuate ); + savefile->WriteFloat( maxAttenuation ); + savefile->WriteFloat( attenuationScale ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteInt( lastTime ); + savefile->WriteBool( useIdleSound ); + splineStateThread.Save( savefile ); + savefile->WriteInt( splineStartTime ); +// RAVEN END +} + +/* +================ +idMover::Restore +================ +*/ +void idMover::Restore( idRestoreGame *savefile ) { + int i, num; + bool hasSpline = false; + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->Read( &move, sizeof( move ) ); + savefile->Read( &rot, sizeof( rot ) ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( rotate_thread ); + + savefile->ReadAngles( dest_angles ); + savefile->ReadAngles( angle_delta ); + savefile->ReadVec3( dest_position ); + savefile->ReadVec3( move_delta ); + + savefile->ReadFloat( move_speed ); + savefile->ReadInt( move_time ); + savefile->ReadInt( deceltime ); + savefile->ReadInt( acceltime ); + savefile->ReadBool( stopRotation ); + savefile->ReadBool( useSplineAngles ); + savefile->ReadInt( (int &)lastCommand ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal > 0 ) { + int portalState = 0; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } + + savefile->ReadBool( hasSpline ); + if ( hasSpline ) { + int starttime; + int totaltime; + int accel; + int decel; + int useAngles; + + splineEnt.Restore( savefile ); + savefile->ReadInt( starttime ); + savefile->ReadInt( totaltime ); + savefile->ReadInt( accel ); + savefile->ReadInt( decel ); + savefile->ReadInt( useAngles ); + +// RAVEN BEGIN + PostEventMS( &EV_PostRestoreExt, 0, starttime, totaltime, accel, decel, useAngles ); +// RAVEN END + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->ReadBool( attenuate ); + savefile->ReadFloat( maxAttenuation ); + savefile->ReadFloat( attenuationScale ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadInt( lastTime ); + savefile->ReadBool( useIdleSound ); + splineStateThread.Restore( savefile, this ); + savefile->ReadInt( splineStartTime ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +/* +================ +idMover::Event_PostRestoreExt +================ +*/ +// RAVEN BEGIN +void idMover::Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ) { +// RAVEN END + idCurve_Spline *spline; + + idEntity *splineEntity = splineEnt.GetEntity(); + if ( !splineEntity ) { + // We should never get this event if splineEnt is invalid + common->Warning( "Invalid spline entity during restore\n" ); + return; + } + + spline = splineEntity->GetSpline(); + + spline->MakeUniform( total ); + spline->ShiftTime( start - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, accel, decel, useSplineAng ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Spawn +================ +*/ +void idMover::Spawn( void ) { + move_thread = 0; + rotate_thread = 0; + stopRotation = false; + lastCommand = MOVER_NONE; + + acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); + deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); + move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value + move_speed = spawnArgs.GetFloat( "move_speed", "0" ); + + spawnArgs.GetFloat( "damage" , "0", damage ); + + dest_position = GetPhysics()->GetOrigin(); + dest_angles = GetPhysics()->GetAxis().ToAngles(); + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + if( spawnArgs.GetBool("removeGimbleLock") ) { + physicsObj.SetAxisOffset( spawnArgs.GetMatrix("rotation", mat3_identity.ToString()) ); + physicsObj.SetAxis( mat3_identity ); + UpdateVisuals(); + } + +// mekberg: attenuation + attenuate = spawnArgs.GetBool( "attenuate" ); + if( attenuate ) { + maxAttenuation = spawnArgs.GetFloat( "maxAttenuation", "3" ); + attenuationScale = spawnArgs.GetFloat( "attenuationScale", "100" ); + + // Check for bad value/prevent divide by zero + if ( attenuationScale == 0.0f || attenuationScale < 0.0f ) { + attenuationScale = 100; + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } else { + maxAttenuation = 0.0f; + attenuationScale = 1.0f; + } + + if ( !idStr::Icmp( spawnArgs.GetString( "snd_idle", "" ), "" ) ) { + useIdleSound = false; + } else { + StartSound( "snd_idle", SND_CHANNEL_BODY, 0, false, NULL ); + useIdleSound = true; + } + + splineStateThread.SetName( "SplineStateThread" ); + splineStateThread.SetOwner( this ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idMover::Think +================ +*/ +void idMover::Think( void ) { + idVec3 deltaPosition; + float deltaTime; + float speed; + float attenuation; + + if ( physicsObj.GetSpline( ) ) { + splineStateThread.Execute( ); + } + + if ( attenuate ) { + deltaPosition = physicsObj.GetOrigin( ) - lastOrigin; + deltaTime = gameLocal.time - lastTime; + + if ( !deltaTime ) { + deltaTime = 1; + } + + speed = deltaPosition.Length( ) * ( 1000.0f / float( deltaTime ) ); + + if ( speed >= VECTOR_EPSILON ) { + soundShaderParms_t parms = refSound.parms; + + attenuation = 0.8f + 0.2f * ( speed / attenuationScale ); + parms.frequencyShift = idMath::ClampFloat( 0.0f, maxAttenuation, attenuation ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( SND_CHANNEL_BODY, &parms ); + } + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } + + idEntity::Think( ); +} + +// abahr +/* +================ +idMover::GetPhysicsToVisualTransform +================ +*/ +bool idMover::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin.Zero(); + axis = physicsObj.GetAxisOffset(); + return physicsObj.UseAxisOffset(); +} + +/* +================ +idMover::MoveAlongVector +================ +*/ +void idMover::MoveAlongVector( const idVec3& vec ) { + idAngles ang; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.GetLocalAngles( ang ); + dest_position = org + vec * ang.ToMat3(); + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAlongVector +================ +*/ +void idMover::Event_MoveAlongVector( const idVec3& vec ) { + MoveAlongVector( vec ); +} +// RAVEN END + +/* +================ +idMover::Hide +================ +*/ +void idMover::Hide( void ) { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMover::Show +================ +*/ +void idMover::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } + SetPhysics( &physicsObj ); +} + +/* +============ +idMover::Killed +============ +*/ +void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + ActivateTargets( this ); +} + + +/* +================ +idMover::Event_SetCallback +================ +*/ +void idMover::Event_SetCallback( void ) { + if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { + lastCommand = MOVER_NONE; + rotate_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { + lastCommand = MOVER_NONE; + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idMover::VectorForDir +================ +*/ +void idMover::VectorForDir( float angle, idVec3 &vec ) { + idAngles ang; + + switch( ( int )angle ) { + case DIR_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 90; + vec = ang.ToForward(); + break; + + case DIR_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw -= 90; + vec = ang.ToForward(); + break; + + case DIR_FORWARD : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + vec = ang.ToForward(); + break; + + case DIR_BACK : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 180; + vec = ang.ToForward(); + break; + + case DIR_REL_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_REL_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_REL_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + vec *= -1; + break; + + case DIR_REL_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + break; + + case DIR_REL_FORWARD : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward(); + break; + + case DIR_REL_BACK : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward() * -1; + break; + + default: + ang.Set( 0, angle, 0 ); + vec = GetWorldVector( ang.ToForward() ); + break; + } +} + +/* +================ +idMover::FindGuiTargets +================ +*/ +void idMover::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover::SetGuiState( const char *key, const char *val ) const { + gameLocal.Printf( "Setting %s to %s\n", key, val ); + for( int i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover::SetGuiStates +================ +*/ +void idMover::SetGuiStates( const char *state ) { + int i; + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( "movestate", state ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_InitGuiTargets( void ) { + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); +} + +/*********************************************************************** + + Translation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopMoving +================ +*/ +void idMover::Event_StopMoving( void ) { + physicsObj.GetLocalOrigin( dest_position ); + DoneMoving(); +} + +/* +================ +idMover::DoneMoving +================ +*/ +void idMover::DoneMoving( void ) { + + if ( lastCommand != MOVER_SPLINE ) { + // set our final position so that we get rid of any numerical inaccuracy + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + } + + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added sig reached + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + +// RAVEN BEGIN +// mekberg: for idle sound + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } +} + +/* +================ +idMover::UpdateMoveSound +================ +*/ +void idMover::UpdateMoveSound( moveStage_t stage ) { +// RAVEN BEGIN +// mekberg: Idle sound plays instead of snd_move. Don't stop idle sounds. + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case LINEAR_STAGE: { + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case DECELERATION_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + // cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + } +// RAVEN END +} + +/* +================ +idMover::Event_UpdateMove +================ +*/ +void idMover::Event_UpdateMove( void ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + + UpdateMoveSound( move.stage ); + + switch( move.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, move.dir, vec3_origin ); + if ( move.movetime > 0 ) { + move.stage = LINEAR_STAGE; + } else if ( move.deceleration > 0 ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, move.movetime, org, move.dir, vec3_origin ); + if ( move.deceleration ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, move.dir, vec3_origin ); + move.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' move done\n", gameLocal.time, name.c_str() ); + } + DoneMoving(); + break; + } + } +} + +/* +================ +idMover::BeginMove +================ +*/ +void idMover::BeginMove( idThread *thread ) { + moveStage_t stage; + idVec3 org; + float dist; + float acceldist; + int totalacceltime; + int at; + int dt; + + lastCommand = MOVER_MOVING; + move_thread = 0; + + physicsObj.GetLocalOrigin( org ); + + move_delta = dest_position - org; + if ( move_delta.Compare( vec3_zero ) ) { + DoneMoving(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + + // if we're moving at a specific speed, we need to calculate the move time + if ( move_speed ) { + dist = move_delta.Length(); + + totalacceltime = acceltime + deceltime; + + // calculate the distance we'll move during acceleration and deceleration + acceldist = totalacceltime * 0.5f * 0.001f * move_speed; + if ( acceldist >= dist ) { + // going too slow for this distance to move at a constant speed + move_time = totalacceltime; + } else { + // calculate move time taking acceleration into account + move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; + } + } + + // scale time up to a whole physics frames + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + move.stage = stage; + move.acceleration = at; + move.movetime = move_time - at - dt; + move.deceleration = dt; + move.dir = move_delta; + + ProcessEvent( &EV_ReachedPos ); +} + +/*********************************************************************** + + Rotation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopRotating +================ +*/ +void idMover::Event_StopRotating( void ) { + physicsObj.GetLocalAngles( dest_angles ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + DoneRotating(); +} + +/* +================ +idMover::DoneRotating +================ +*/ +void idMover::DoneRotating( void ) { + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added reached signal + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( rotate_thread, this ); + rotate_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateRotationSound +================ +*/ +void idMover::UpdateRotationSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); +// RAVEN BEGIN +// cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY, 0, false, NULL ); +// RAVEN END + break; + } + } +} + +/* +================ +idMover::Event_UpdateRotation +================ +*/ +void idMover::Event_UpdateRotation( void ) { + idAngles ang; + + physicsObj.GetLocalAngles( ang ); + + UpdateRotationSound( rot.stage ); + + switch( rot.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, rot.acceleration, ang, rot.rot, ang_zero ); + if ( rot.movetime > 0 ) { + rot.stage = LINEAR_STAGE; + } else if ( rot.deceleration > 0 ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + if ( !stopRotation && !rot.deceleration ) { + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } else { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } + + if ( rot.deceleration ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, rot.deceleration, ang, rot.rot, ang_zero ); + rot.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + lastCommand = MOVER_NONE; + if ( stopRotation ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { + // keep our angular velocity constant + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, rot.rot, ang_zero ); + } + + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.time, name.c_str() ); + } + + DoneRotating(); + break; + } + } +} + +/* +================ +idMover::BeginRotation +================ +*/ +void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { + moveStage_t stage; + idAngles ang; + int at; + int dt; + + lastCommand = MOVER_ROTATING; + rotate_thread = 0; + + // rotation always uses move_time so that if a move was started before the rotation, + // the rotation will take the same amount of time as the move. If no move has been + // started and no time is set, the rotation takes 1 second. + if ( !move_time ) { + move_time = 1; + } + + physicsObj.GetLocalAngles( ang ); + angle_delta = dest_angles - ang; + if ( angle_delta == ang_zero ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + DoneRotating(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + stopRotation = stopwhendone || ( dt != 0 ); + + rot.stage = stage; + rot.acceleration = at; + rot.movetime = move_time - at - dt; + rot.deceleration = dt; + rot.rot = angle_delta; + + ProcessEvent( &EV_ReachedAng ); +} + + +/*********************************************************************** + + Script callable routines + +***********************************************************************/ + +/* +=============== +idMover::Event_TeamBlocked +=============== +*/ +void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +=============== +idMover::Event_PartBlocked +=============== +*/ +void idMover::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +================ +idMover::Event_SetMoveSpeed +================ +*/ +void idMover::Event_SetMoveSpeed( float speed ) { + if ( speed <= 0 ) { + gameLocal.Error( "Cannot set speed less than or equal to 0." ); + } + + move_speed = speed; + move_time = 0; // move_time is calculated for each move when move_speed is non-0 +} + +/* +================ +idMover::Event_SetMoveTime +================ +*/ +void idMover::Event_SetMoveTime( float time ) { + if ( time <= 0 ) { + gameLocal.Error( "Cannot set time less than or equal to 0." ); + } + + move_speed = 0; + move_time = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetAccellerationTime +================ +*/ +void idMover::Event_SetAccellerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set acceleration time less than 0." ); + } + + acceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetDecelerationTime +================ +*/ +void idMover::Event_SetDecelerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set deceleration time less than 0." ); + } + + deceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_MoveTo +================ +*/ +void idMover::Event_MoveTo( idEntity *ent ) { + if ( !ent ) { + gameLocal.Warning( "Entity not found" ); +// RAVEN BEGIN +// abahr: added return so the NULL ptr doesn't get used + return; +// RAVEN END + } + + dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::MoveToPos +================ +*/ +void idMover::MoveToPos( const idVec3 &pos ) { + dest_position = GetLocalCoordinates( pos ); + BeginMove( NULL ); +} + +/* +================ +idMover::Event_MoveToPos +================ +*/ +void idMover::Event_MoveToPos( idVec3 &pos ) { + MoveToPos( pos ); +} + +/* +================ +idMover::Event_MoveDir +================ +*/ +void idMover::Event_MoveDir( float angle, float distance ) { + idVec3 dir; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + VectorForDir( angle, dir ); + dest_position = org + dir * distance; + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAccelerateTo +================ +*/ +void idMover::Event_MoveAccelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int at; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); + } + + // if already moving faster than the desired speed + if ( v >= speed ) { + return; + } + + at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = ACCELERATION_STAGE; + move.acceleration = at; + move.movetime = 0; + move.deceleration = 0; + + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, dir * ( speed - v ), dir * v ); +} + +/* +================ +idMover::Event_MoveDecelerateTo +================ +*/ +void idMover::Event_MoveDecelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int dt; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); + } + + // if already moving slower than the desired speed + if ( v <= speed ) { + return; + } + + dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = DECELERATION_STAGE; + move.acceleration = 0; + move.movetime = 0; + move.deceleration = dt; + + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); +} + +/* +================ +idMover::Event_RotateDownTo +================ +*/ +void idMover::Event_RotateDownTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] > ang[ axis ] ) { + dest_angles[ axis ] -= 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateUpTo +================ +*/ +void idMover::Event_RotateUpTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] < ang[ axis ] ) { + dest_angles[ axis ] += 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateTo +================ +*/ +void idMover::Event_RotateTo( idAngles &angles ) { + dest_angles = angles; + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Rotate +================ +*/ +void idMover::Event_Rotate( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; + + BeginRotation( idThread::CurrentThread(), false ); +} + +/* +================ +idMover::Event_RotateOnce +================ +*/ +void idMover::Event_RotateOnce( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles; + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Bob +================ +*/ +void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); +} + +/* +================ +idMover::Event_Sway +================ +*/ +void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { + idAngles ang, angSpeed; + float duration; + + physicsObj.GetLocalAngles( ang ); + assert ( speed > 0.0f ); + duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; + angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); +} + +/* +================ +idMover::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover::Event_OpenPortal( void ) { + if ( areaPortal ) { + SetPortalState( true ); + } +} + +/* +================ +idMover::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover::Event_ClosePortal( void ) { + if ( areaPortal ) { + SetPortalState( false ); + } +} + +/* +================ +idMover::Event_SetAccelSound +================ +*/ +void idMover::Event_SetAccelSound( const char *sound ) { +// refSound.SetSound( "accel", sound ); +} + +/* +================ +idMover::Event_SetDecelSound +================ +*/ +void idMover::Event_SetDecelSound( const char *sound ) { +// refSound.SetSound( "decel", sound ); +} + +// RAVEN BEGIN +// cnicholson: added stop sound support +/* +================ +idMover::Event_SetStoppedSound +================ +*/ +void idMover::Event_SetStoppedSound( const char *sound ) { +// refSound.SetSound( "stopped", sound ); +} +// RAVEN END + +/* +================ +idMover::Event_SetMoveSound +================ +*/ +void idMover::Event_SetMoveSound( const char *sound ) { +// refSound.SetSound( "move", sound ); +} + +/* +================ +idMover::Event_EnableSplineAngles +================ +*/ +void idMover::Event_EnableSplineAngles( void ) { + useSplineAngles = true; +} + +/* +================ +idMover::Event_DisableSplineAngles +================ +*/ +void idMover::Event_DisableSplineAngles( void ) { + useSplineAngles = false; +} + +/* +================ +idMover::Event_RemoveInitialSplineAngles +================ +*/ +void idMover::Event_RemoveInitialSplineAngles( void ) { + idCurve_Spline *spline; + idAngles ang; + + spline = physicsObj.GetSpline(); + if ( !spline ) { + return; + } + ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); +} + +/* +================ +idMover::Event_StartSpline +================ +*/ +void idMover::Event_StartSpline( idEntity *splineEntity ) { + idCurve_Spline *spline; + + if ( !splineEntity ) { + return; + } + + // Needed for savegames + splineEnt = splineEntity; + + spline = splineEntity->GetSpline(); + if ( !spline ) { + return; + } + + lastCommand = MOVER_SPLINE; + move_thread = 0; + +// RAVEN BEGIN +// bdube: movement speed + // Use movement speed? + if ( idMath::Fabs(move_speed) >= VECTOR_EPSILON ) { + // Set a fixed time to determine the length from + spline->MakeUniform( 1000 ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + + // Calculate the move time from the speed + move.movetime = SEC2MS( spline->GetLengthForTime(spline->GetTime(spline->GetNumValues() - 1)) / move_speed ); + move_time = move.movetime; + + spline->SetConstantSpeed( move.movetime ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } else { + spline->MakeUniform( move_time ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } +// RAVEN END + + if ( acceltime + deceltime > move_time ) { + acceltime = move_time / 2; + deceltime = move_time - acceltime; + } + + move.stage = FINISHED_STAGE; + move.acceleration = acceltime; + move.movetime = move_time; + move.deceleration = deceltime; + + physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + +// RAVEN BEGIN +// mekberg: let the splines use a state thread instead of linear extrapolation + if ( acceltime ) { + splineStateThread.SetState( "Accel" ); + } else { + splineStateThread.SetState( "Linear" ); + } + + splineStartTime = gameLocal.time; +// RAVEN END +} + +/* +================ +idMover::Event_StopSpline +================ +*/ +void idMover::Event_StopSpline( void ) { + physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); + splineEnt = NULL; +} + +/* +================ +idMover::Event_Activate +================ +*/ +void idMover::Event_Activate( idEntity *activator ) { + Show(); + Event_StartSpline( this ); +} + +/* +================ +idMover::Event_IsMoving +================ +*/ +void idMover::Event_IsMoving( void ) { + if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::Event_IsRotating +================ +*/ +void idMover::Event_IsRotating( void ) { + if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::WriteToSnapshot +================ +*/ +void idMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ( ( thinkFlags & TH_PHYSICS ) != 0 ), 1 ); + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( move.stage, 3 ); + msg.WriteBits( rot.stage, 3 ); + WriteBindToSnapshot( msg ); + WriteGUIToSnapshot( msg ); +} + +/* +================ +idMover::ReadFromSnapshot +================ +*/ +void idMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moveStage_t oldMoveStage = move.stage; + moveStage_t oldRotStage = rot.stage; + + // sync down the TH_PHYSICS flag for movers, now that we skip ClientPredictionThink when thinkFlags == 0 and no longer force TH_PHYSICS on + // movers still have prediction issues though, they predict a stop and clear TH_PHYSICS too early + bool physics_on = ( msg.ReadBits( 1 ) != 0 ); + if ( physics_on ) { + thinkFlags |= TH_PHYSICS; + } else { + thinkFlags &= ~TH_PHYSICS; + } + + physicsObj.ReadFromSnapshot( msg ); + move.stage = (moveStage_t) msg.ReadBits( 3 ); + rot.stage = (moveStage_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( move.stage != oldMoveStage ) { + UpdateMoveSound( oldMoveStage ); + } + if ( rot.stage != oldRotStage ) { + UpdateRotationSound( oldRotStage ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover::SetPortalState +================ +*/ +void idMover::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +// RAVEN BEGIN +// abahr: +void idMover::Event_GetSplineEntity() { + idThread::ReturnEntity( splineEnt.GetEntity() ); +} + +CLASS_STATES_DECLARATION( idMover ) + STATE( "Accel", idMover::State_Accel ) + STATE( "Linear", idMover::State_Linear ) + STATE( "Decel", idMover::State_Decel ) +END_CLASS_STATES + +// mekberg: spline states +/* +================ +idMover::State_Accel +================ +*/ +stateResult_t idMover::State_Accel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + acceltime ) { + splineStateThread.SetState( "Linear" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Linear +================ +*/ +stateResult_t idMover::State_Linear( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time - deceltime ) { + if ( deceltime ) { + splineStateThread.SetState( "Decel" ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Decel +================ +*/ +stateResult_t idMover::State_Decel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +// RAVEN END + +/* +=============================================================================== + + idSplinePath, holds a spline path to be used by an idMover + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger +const idEventDef EV_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idSplinePath ) +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger + EVENT( EV_Activate, idSplinePath::Event_Toggle ) + EVENT( EV_IsActive, idSplinePath::Event_IsActive ) +// RAVEN END +END_CLASS + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::idSplinePath() { + sampledTimes = NULL; + numSamples = 0; +} + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::~idSplinePath() { + if ( sampledTimes ) { + delete [] sampledTimes; + sampledTimes = NULL; + } +} + +/* +================ +idSplinePath::Spawn +================ +*/ +void idSplinePath::Spawn( void ) { +// RAVEN BEGIN +// abahr: + SetActive( spawnArgs.GetBool("start_active", "1") ); + SampleSpline ( ); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSplinePath::FindTargets +================ +*/ +void idSplinePath::FindTargets() { + idEntity::FindTargets(); + + gameLocal.GetTargets( spawnArgs, backwardPathTargets, "target_reverse" ); + + // This alows us to seperate forward targets from backward targets + for( int ix = backwardPathTargets.Num() - 1; ix >= 0; --ix ) { + targets.Remove( backwardPathTargets[ix] ); + } +} + +/* +================ +idSplinePath::SortTargets +================ +*/ +int rvSortByActiveState( const void* a, const void* b ) { + idEntityPtr splineA; + idEntityPtr splineB; + + splineA = *(idEntityPtr*)a; + splineB = *(idEntityPtr*)b; + + return splineB->IsActive() - splineA->IsActive(); +} +int idSplinePath::SortTargets( idList< idEntityPtr >& list ) { + int numActive = 0; + idSplinePath* target = NULL; + + RemoveNullTargets(); + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByActiveState ); + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + target = static_cast( list[ix].GetEntity() ); + if( target->IsActive() ) { + numActive++; + } + } + + return numActive; +} + +int idSplinePath::SortTargets() { + return SortTargets( targets ); +} + +int idSplinePath::SortBackwardsTargets() { + return SortTargets( backwardPathTargets ); +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( idList< idEntityPtr >& list ) { + int i; + + for( i = list.Num() - 1; i >= 0; i-- ) { + if ( !list[ i ].GetEntity() ) { + list.RemoveIndex( i ); + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < list.Num(); i++ ) { + ent = list[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( void ) { + RemoveNullTargets( targets ); + RemoveNullTargets( backwardPathTargets ); +} + +/* +============================== +idSplinePath::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idSplinePath::ActivateTargets( idEntity *activator ) const { + ActivateTargets( activator, targets ); + ActivateTargets( activator, backwardPathTargets ); +} + +/* +================ +idSplinePath::Save +================ +*/ +void idSplinePath::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); +} + +/* +================ +idSplinePath::Restore +================ +*/ +void idSplinePath::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); +} + +/* +================ +idSplinePath::Event_IsActive +================ +*/ +void idSplinePath::Event_IsActive() { + idThread::ReturnInt( IsActive() ); +} + +/* +================ +idSplinePath::SampleSpline +================ +*/ +void idSplinePath::SampleSpline ( void ) { + int i; + float splineLength; + idCurve_Spline* tempSpline = GetSpline ( ); + splineLength = tempSpline->GetLengthForTime( tempSpline->GetTime(tempSpline->GetNumValues() - 1) ); + + if ( splineLength > SPLINE_SAMPLE_RATE ) { + numSamples = int( splineLength / SPLINE_SAMPLE_RATE ); + sampledTimes = new float[ numSamples ]; + float stepSize = splineLength / ( numSamples - 1 ); + + for ( i = 0; i < numSamples; i++ ) { + float time = float( i * stepSize ); + if ( time >= splineLength ) { + time = splineLength; + } + sampledTimes[ i ] = tempSpline->GetTimeForLength ( time, 0.01f ); + } + } + SAFE_DELETE_PTR( tempSpline ); +} + +/* +================ +idSplinePath::GetSampledTime +================ +*/ +float idSplinePath::GetSampledTime ( float distance ) const { + if ( sampledTimes && distance >= 0.0f ) { + int lowIndex, highIndex; + float lerp = distance / SPLINE_SAMPLE_RATE; + int actualLowIndex = int( lerp ); + lowIndex = idMath::ClampInt ( 0, numSamples - 2, actualLowIndex ); + highIndex = lowIndex + 1; + lerp = lerp - idMath::Floor ( lerp ); + return ( actualLowIndex != lowIndex ) ? sampledTimes[ highIndex ] : idMath::Lerp( sampledTimes[ lowIndex ], sampledTimes[ highIndex ], lerp ); + } + return -1.0f; +} + +// RAVEN END + + +/* +=============================================================================== + +idElevator + +=============================================================================== +*/ +const idEventDef EV_PostArrival( "postArrival", NULL ); +const idEventDef EV_GotoFloor( "gotoFloor", "d" ); +const idEventDef EV_UpdateFloorInfo ( "updateFloorInfo", NULL ); + +CLASS_DECLARATION( idMover, idElevator ) + EVENT( EV_Activate, idElevator::Event_Activate ) + EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) + EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) + EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) + EVENT( EV_Touch, idElevator::Event_Touch ) + EVENT( EV_UpdateFloorInfo, idElevator::Event_UpdateFloorInfo ) +END_CLASS + +/* +================ +idElevator::idElevator +================ +*/ +idElevator::idElevator( void ) { + state = INIT; + floorInfo.Clear(); + currentFloor = 0; + pendingFloor = 0; + lastFloor = 0; + controlsDisabled = false; + lastTouchTime = 0; + returnFloor = 0; + returnTime = 0; +} + +/* +================ +idElevator::Save +================ +*/ +void idElevator::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( (int)state ); + + savefile->WriteInt( floorInfo.Num() ); + for ( i = 0; i < floorInfo.Num(); i++ ) { + savefile->WriteVec3( floorInfo[ i ].pos ); + savefile->WriteString( floorInfo[ i ].door ); + savefile->WriteInt( floorInfo[ i ].floor ); + } + + savefile->WriteInt( currentFloor ); + savefile->WriteInt( pendingFloor ); + savefile->WriteInt( lastFloor ); + savefile->WriteBool( controlsDisabled ); +// savefile->WriteBool( waitingForPlayerFollowers ); + savefile->WriteFloat( returnTime ); + savefile->WriteInt( returnFloor ); + savefile->WriteInt( lastTouchTime ); +} + +/* +================ +idElevator::Restore +================ +*/ +void idElevator::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)state ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + floorInfo_s floor; + + savefile->ReadVec3( floor.pos ); + savefile->ReadString( floor.door ); + savefile->ReadInt( floor.floor ); + + floorInfo.Append( floor ); + } + + savefile->ReadInt( currentFloor ); + savefile->ReadInt( pendingFloor ); + savefile->ReadInt( lastFloor ); + savefile->ReadBool( controlsDisabled ); +// savefile->ReadBool( waitingForPlayerFollowers ); + savefile->ReadFloat( returnTime ); + savefile->ReadInt( returnFloor ); + savefile->ReadInt( lastTouchTime ); +} + +/* +================ +idElevator::Spawn +================ +*/ +void idElevator::Spawn( void ) { + lastFloor = 0; + currentFloor = 0; + pendingFloor = spawnArgs.GetInt( "floor", "1" ); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); + + returnTime = spawnArgs.GetFloat( "returnTime" ); + returnFloor = spawnArgs.GetInt( "returnFloor" ); + + UpdateFloorInfo ( ); + + lastTouchTime = 0; + state = INIT; + BecomeActive( TH_THINK | TH_PHYSICS ); + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); + controlsDisabled = false; +// waitingForPlayerFollowers = false; +} + +/* +============== +idElevator::UpdateFloorInfo +=============== +*/ +void idElevator::UpdateFloorInfo ( void ) { + int len1; + idStr str; + + floorInfo.Clear ( ); + + len1 = strlen( "floorPos_" ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); + while( kv ) { + str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); + floorInfo_s fi; + fi.floor = atoi( str ); + fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); + fi.pos = spawnArgs.GetVector( kv->GetKey() ); + floorInfo.Append( fi ); + kv = spawnArgs.MatchPrefix( "floorPos_", kv ); + } +} + +/* +============== +idElevator::Event_UpdateFloorInfo +=============== +*/ +void idElevator::Event_UpdateFloorInfo ( void ) { + UpdateFloorInfo ( ); +} + +/* +============== +idElevator::Event_Touch +=============== +*/ +void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { + + if ( gameLocal.time < lastTouchTime + 2000 ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + lastTouchTime = gameLocal.time; + + if ( thinkFlags & TH_PHYSICS ) { + return; + } + + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); + } +} + +/* +================ +idElevator::Think +================ +*/ +void idElevator::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( state == INIT ) { + state = IDLE; + if ( doorent ) { + doorent->BindTeam( this ); + doorent->spawnArgs.Set( "snd_open", "" ); + doorent->spawnArgs.Set( "snd_close", "" ); + doorent->spawnArgs.Set( "snd_opened", "" ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->SetCompanion( doorent ); + } + } + + Event_GotoFloor( pendingFloor ); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + +// RAVEN BEGIN +// bdube: provide floor information to status guis + if ( floorInfo.Num ( ) > 0 ) { + int j; + // Guis on the elevator are considered status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI && renderEntity.gui[j]; j++ ) { + InitStatusGui ( renderEntity.gui[j] ); + } + + // Initialize all the status guis of the elevator + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix( "statusGui" ); kv; kv = spawnArgs.MatchPrefix( "statusGui", kv ) ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( !ent || !ent->GetRenderEntity() ) { + continue; + } + for ( j = 0; j < MAX_RENDERENTITY_GUI && ent->GetRenderEntity()->gui[j]; j++ ) { + InitStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + } +// RAVEN END + + } else if ( state == WAITING_ON_DOORS ) { + if ( doorent ) { + state = doorent->IsOpen() ? WAITING_ON_DOORS : IDLE; + } else { + state = IDLE; + } + if ( state == IDLE ) { + lastFloor = currentFloor; + currentFloor = pendingFloor; + floorInfo_s *fi = GetFloorInfo( currentFloor ); + if ( fi ) { + MoveToPos( fi->pos ); + } + } + } + RunPhysics(); + Present(); +} + +/* +================ +idElevator::Event_Activate +================ +*/ +void idElevator::Event_Activate( idEntity *activator ) { + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + Event_GotoFloor( triggerFloor ); + } +} + +/* +================ +idElevator::Event_TeamBlocked +================ +*/ +void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( blockedEntity == this ) { + Event_GotoFloor( lastFloor ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( blockedEntity && blockedEntity->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + // open the inner doors if one is blocked + idDoor *blocked = static_cast( blockedEntity ); + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && blocked->GetMoveMaster() == door->GetMoveMaster() ) { + door->SetBlocked(true); + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } + } +} + + +/* +=============== +idElevator::HandleSingleGuiCommand +=============== +*/ +bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( controlsDisabled ) { + return false; + } + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "changefloor" ) == 0 ) { + if ( src->ReadToken( &token ) ) { +// RAVEN BEGIN +// bdube: up and down floor commands + int newFloor; + if (!token.Cmp("up")) { + newFloor = currentFloor + 1; + } else if (!token.Cmp("down")) { + newFloor = currentFloor - 1; + } else { + newFloor = atoi( token ); + } +// RAVEN END + + if ( newFloor == currentFloor ) { + // open currentFloor and interior doors + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } else { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, newFloor ); + } else { + ProcessEvent( &EV_GotoFloor, newFloor ); + } + } + return true; + } + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +idElevator::OpenFloorDoor +================ +*/ +void idElevator::OpenFloorDoor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( fi->door ); + if ( door ) { + door->Open(); + } + } +} + +/* +================ +idElevator::OpenInnerDoor +================ +*/ +void idElevator::OpenInnerDoor( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Open(); + } +} + +/* +================ +idElevator::GetFloorInfo +================ +*/ +floorInfo_s *idElevator::GetFloorInfo( int floor ) { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == floor ) { + return &floorInfo[i]; + } + } + return NULL; +} + +/* +================ +idElevator::Event_GotoFloor +================ +*/ +void idElevator::Event_GotoFloor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + if ( door->IsBlocked() || door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + /* + if ( !gameLocal.isMultiplayer ) { + //FIXME: make sure player is my activator? + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->GetGroundEntity() ) { + //player in air + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + if ( player->GetGroundElevator() == this ) { + idActor* actor = NULL; + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + if ( !actor->IsHidden() && actor->health > 0 && actor->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)(actor))->leader == player && !((idAI*)(actor))->move.fl.disabled && !((idAI*)(actor))->aifl.scripted ) { + if ( actor->GetGroundElevator( this ) != this ) { + waitingForPlayerFollowers = true; + //follower of player is not standing on me, don't move! + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + } + } + } else if ( waitingForPlayerFollowers ) { + //player got off, cancel + waitingForPlayerFollowers = false; + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + UpdateStatusGuis ( ); + return; + } + } + } + waitingForPlayerFollowers = false; + */ + DisableAllDoors(); + CloseAllDoors(); + state = WAITING_ON_DOORS; + pendingFloor = floor; + } +} + +/* +================ +idElevator::BeginMove +================ +*/ +void idElevator::BeginMove( idThread *thread ) { + controlsDisabled = true; + CloseAllDoors(); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); + +// RAVEN BEGIN +// bdube: replaced with function + SetAASAreaState ( true ); + + idMover::BeginMove( thread ); + + UpdateStatusGuis ( ); +// RAVEN END +} + +/* +================ +idElevator::GetDoor +================ +*/ +idDoor *idElevator::GetDoor( const char *name ) { + idEntity *ent; + idEntity *master; + idDoor *doorEnt; + + doorEnt = NULL; + if ( name && *name ) { + ent = gameLocal.FindEntity( name ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( ent ); + master = doorEnt->GetMoveMaster(); + if ( master != doorEnt ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( master ); + } else { + doorEnt = NULL; + } + } + } + } + + return doorEnt; +} + +/* +================ +idElevator::Event_PostFloorArrival +================ +*/ +void idElevator::Event_PostFloorArrival() { + OpenFloorDoor( currentFloor ); + OpenInnerDoor(); + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + controlsDisabled = false; + if ( returnTime > 0.0f && returnFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); + } +} + +/* +================ +idElevator::DoneMoving +================ +*/ +void idElevator::DoneMoving( void ) { + idMover::DoneMoving(); + EnableProperDoors(); +// RAVEN BEGIN +// bdube: factored into a function + UpdateStatusGuis ( ); +// RAVEN END + if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { + PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); + } else { + Event_PostFloorArrival(); + } + +// RAVEN BEGIN +// kfuller: we want an elevator to fire its targets when it reaches a floor + SetAASAreaState( false ); + + // Floor targets + if ( lastFloor != 0 ) { + const char* floorTarget = spawnArgs.GetString ( va("floorTarget_%d", currentFloor ) ); + if ( floorTarget && *floorTarget ) { + idEntity* ent = gameLocal.FindEntity ( floorTarget ); + if ( ent ) { + ent->ProcessEvent ( &EV_Activate, this ); + } + } + } + + if (spawnArgs.GetInt("fireTargetsAtFloor") == currentFloor) { + ActivateTargets(gameLocal.entities[ENTITYNUM_WORLD]); + spawnArgs.SetInt("fireTargetsAtFloor", -1); + } +// RAVEN END +} + +/* +================ +idElevator::CloseAllDoors +================ +*/ +void idElevator::CloseAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Close(); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Close(); + } + } +} + +/* +================ +idElevator::DisableAllDoors +================ +*/ +void idElevator::DisableAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( false ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( false ); + } + } +} + +/* +================ +idElevator::EnableProperDoors +================ +*/ +void idElevator::EnableProperDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( true ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == currentFloor ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( true ); + break; + } + } + } +} + +// RAVEN BEGIN +// bdube: more advanced status gui control + +/* +================ +idElevator::SetAASAreaState +================ +*/ +void idElevator::SetAASAreaState ( bool enable ) { + idEntity* ents[16]; + int numEnts; + numEnts = gameLocal.EntitiesTouchingBounds ( this, physicsObj.GetAbsBounds(), CONTENTS_AAS_OBSTACLE, ents, 16 ); + for ( numEnts--; numEnts >= 0; numEnts -- ) { + idFuncAASObstacle* obstacle = dynamic_cast(ents[numEnts]); + if ( obstacle ) { + obstacle->SetState ( enable ); + } + } +} + +/* +================ +idElevator::InitStatusGui +================ +*/ +void idElevator::InitStatusGui ( idUserInterface* gui ) { + int floor; + int topFloor; + int bottomFloor; + + topFloor = -1; + bottomFloor = 9999; + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + topFloor = Max( floorInfo[floor].floor, topFloor ); + bottomFloor = Min( floorInfo[floor].floor, bottomFloor ); + } + + gui->SetStateInt ( "topFloor", topFloor ); + gui->SetStateInt ( "bottomFloor", bottomFloor ); + + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + idStr keySrc; + idStr keyDest; + gui->SetStateInt ( va("floorNumber_%d", floor ), floorInfo[floor].floor ); + keySrc = va("floorName_%d", floorInfo[floor].floor ); + keyDest = va("floorName_%d", floor ); + gui->SetStateString ( keyDest, spawnArgs.GetString ( keySrc, va("%d", floorInfo[floor].floor ) ) ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGui +================ +*/ +void idElevator::UpdateStatusGui ( idUserInterface* gui ) { + idStr floorName; + floorName = va("floorName_%d", currentFloor ); + gui->SetStateInt ( "floor", (physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE) ? currentFloor : lastFloor ); + gui->SetStateInt ( "floorNext", currentFloor ); + gui->SetStateString( "floorName", spawnArgs.GetString ( floorName, va("%d", currentFloor ) ) ); + gui->StateChanged( gameLocal.time, true ); + + // mekberg: trigger all status guis if we moved the elevator from another gui. + if ( lastFloor && !( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) ) { + gui->HandleNamedEvent( "triggerGui" ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGuis +================ +*/ +void idElevator::UpdateStatusGuis ( void ) { + int j; + + // Treat the guis on the elevator as status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( renderEntity.gui[ j ] ) { + UpdateStatusGui ( renderEntity.gui[ j ] ); + } + } + + // All entities linked as status guis should get updated + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + UpdateStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } +} +// RAVEN END + +/* +=============================================================================== + +idMover_Binary + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" + +=============================================================================== +*/ + +const idEventDef EV_Mover_ReturnToPos1( "", NULL ); +const idEventDef EV_Mover_MatchTeam( "", "dd" ); +const idEventDef EV_Mover_Enable( "enable", NULL ); +const idEventDef EV_Mover_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idMover_Binary ) + EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) + EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) + EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) + EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) + EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) + EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) + EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) + EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) + EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) +END_CLASS + +/* +================ +idMover_Binary::idMover_Binary() +================ +*/ +idMover_Binary::idMover_Binary() { + pos1.Zero(); + pos2.Zero(); + moverState = MOVER_POS1; + moveMaster = NULL; + activateChain = NULL; + soundPos1 = 0; + sound1to2 = 0; + sound2to1 = 0; + soundPos2 = 0; + soundLoop = 0; + wait = 0.0f; + damage = 0.0f; + duration = 0; + accelTime = 0; + decelTime = 0; + activatedBy = this; + stateStartTime = 0; + team.Clear(); + enabled = false; + deferedOpen = false; + move_thread = 0; + updateStatus = 0; + areaPortal = 0; + blocked = false; + fl.networkSync = true; +} + +/* +================ +idMover_Binary::~idMover_Binary +================ +*/ +idMover_Binary::~idMover_Binary() { + idMover_Binary *mover; + + // if this is the mover master + if ( this == moveMaster ) { + // make the next mover in the chain the move master + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + mover->moveMaster = this->activateChain; + } + } + else { + // remove mover from the activate chain + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + if ( mover->activateChain == this ) { + mover->activateChain = this->activateChain; + break; + } + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMover_Binary::Save +================ +*/ +void idMover_Binary::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteVec3( pos1 ); + savefile->WriteVec3( pos2 ); + savefile->WriteInt( (moverState_t)moverState ); + + savefile->WriteObject( moveMaster ); + savefile->WriteObject( activateChain ); + + savefile->WriteInt( soundPos1 ); + savefile->WriteInt( sound1to2 ); + savefile->WriteInt( sound2to1 ); + savefile->WriteInt( soundPos2 ); + savefile->WriteInt( soundLoop ); + + savefile->WriteFloat( wait ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( duration ); + savefile->WriteInt( accelTime ); + savefile->WriteInt( decelTime ); + + activatedBy.Save( savefile ); + + savefile->WriteInt( stateStartTime ); + savefile->WriteString( team ); + savefile->WriteBool( enabled ); + savefile->WriteBool( deferedOpen ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( updateStatus ); + + savefile->WriteInt( buddies.Num() ); + for ( i = 0; i < buddies.Num(); i++ ) { + savefile->WriteString( buddies[ i ] ); + } + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( blocked ); + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } +} + +/* +================ +idMover_Binary::Restore +================ +*/ +void idMover_Binary::Restore( idRestoreGame *savefile ) { + int i, num, portalState; + idStr temp; + + savefile->ReadVec3( pos1 ); + savefile->ReadVec3( pos2 ); + savefile->ReadInt( (int &)moverState ); + + savefile->ReadObject( reinterpret_cast( moveMaster ) ); + savefile->ReadObject( reinterpret_cast( activateChain ) ); + + savefile->ReadInt( soundPos1 ); + savefile->ReadInt( sound1to2 ); + savefile->ReadInt( sound2to1 ); + savefile->ReadInt( soundPos2 ); + savefile->ReadInt( soundLoop ); + + savefile->ReadFloat( wait ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( duration ); + savefile->ReadInt( accelTime ); + savefile->ReadInt( decelTime ); + + activatedBy.Restore( savefile ); + + savefile->ReadInt( stateStartTime ); + + savefile->ReadString( team ); + savefile->ReadBool( enabled ); + savefile->ReadBool( deferedOpen ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( updateStatus ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( temp ); + buddies.Append( temp ); + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( blocked ); + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } +} + +/* +================ +idMover_Binary::Spawn + +Base class for all movers. + +"wait" wait before returning (3 default, -1 = never return) +"speed" movement speed +================ +*/ +void idMover_Binary::Spawn( void ) { + idEntity *ent; + const char *temp; + + move_thread = 0; + enabled = true; + areaPortal = 0; + + activateChain = NULL; + + spawnArgs.GetFloat( "wait", "0", wait ); + + spawnArgs.GetInt( "updateStatus", "0", updateStatus ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); + while( kv ) { + buddies.Append( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "buddy", kv ); + } + + spawnArgs.GetString( "team", "", &temp ); + team = temp; + + if ( !team.Length() ) { + ent = this; + } else { + // find the first entity spawned on this team (which could be us) + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { + break; + } + } + if ( !ent ) { + ent = this; + } + } + moveMaster = static_cast(ent); + + // create a physics team for the binary mover parts + if ( ent != this ) { + JoinTeam( ent ); + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( moveMaster != this ) { + JoinActivateTeam( moveMaster ); + } + + idBounds soundOrigin; + idMover_Binary *slave; + + soundOrigin.Clear(); + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + soundOrigin += slave->GetPhysics()->GetAbsBounds(); + } + moveMaster->refSound.origin = soundOrigin.GetCenter(); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } +} + +/* +=============== +idMover_Binary::GetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { + if ( angle == -1 ) { + movedir.Set( 0, 0, 1 ); + } else if ( angle == -2 ) { + movedir.Set( 0, 0, -1 ); + } else { + movedir = idAngles( 0, angle, 0 ).ToForward(); + } +} + +/* +================ +idMover_Binary::Event_SetCallback +================ +*/ +void idMover_Binary::Event_SetCallback( void ) { + if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idMover_Binary::UpdateMoverSound +=============== +*/ +void idMover_Binary::UpdateMoverSound( moverState_t state ) { + if ( moveMaster == this ) { + switch( state ) { + case MOVER_POS1: + break; + case MOVER_POS2: + break; + case MOVER_1TO2: + StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); + break; + case MOVER_2TO1: + StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } +} + +/* +=============== +idMover_Binary::SetMoverState +=============== +*/ +void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { + idVec3 delta; + + moverState = newstate; + move_thread = 0; + + UpdateMoverSound( newstate ); + + stateStartTime = time; + switch( moverState ) { + case MOVER_POS1: { + Signal( SIG_MOVER_POS1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); + break; + } + case MOVER_POS2: { + Signal( SIG_MOVER_POS2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); + break; + } + case MOVER_1TO2: { + Signal( SIG_MOVER_1TO2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + case MOVER_2TO1: { + Signal( SIG_MOVER_2TO1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + } +} + +/* +================ +idMover_Binary::MatchActivateTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { + idMover_Binary *slave; + deferedOpen = false; + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->SetMoverState( newstate, time ); + } +} + +/* +================ +idMover_Binary::Enable +================ +*/ +void idMover_Binary::Enable( bool b ) { + enabled = b; +} + +/* +================ +idMover_Binary::Event_MatchActivateTeam +================ +*/ +void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { + MatchActivateTeam( newstate, time ); +} + +/* +================ +idMover_Binary::BindTeam + +All entities in a mover team will be bound +================ +*/ +void idMover_Binary::BindTeam( idEntity *bindTo ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->Bind( bindTo, true ); + } +} + +/* +================ +idMover_Binary::JoinActivateTeam + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { + this->activateChain = master->activateChain; + master->activateChain = this; +} + +/* +================ +idMover_Binary::Event_Enable + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::Event_Enable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_Disable + +Set all entities in a mover team to be disabled +================ +*/ +void idMover_Binary::Event_Disable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover_Binary::Event_OpenPortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( slave->areaPortal ) { + slave->SetPortalState( true ); + } + } +} + +/* +================ +idMover_Binary::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover_Binary::Event_ClosePortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( !slave->IsHidden() ) { + if ( slave->areaPortal ) { + slave->SetPortalState( false ); + } + } + } +} + +/* +================ +idMover_Binary::Event_ReturnToPos1 +================ +*/ +void idMover_Binary::Event_ReturnToPos1( void ) { + MatchActivateTeam( MOVER_2TO1, gameLocal.time ); +} + +/* +================ +idMover_Binary::Event_Reached_BinaryMover +================ +*/ +void idMover_Binary::Event_Reached_BinaryMover( void ) { + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + if ( moveMaster == this ) { + StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); + } + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 1 ); +// RAVEN END + + if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { + // return to pos1 after a delay + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + + SetBlocked ( false ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 0 ); +// RAVEN END + + // close areaportals + if ( moveMaster == this ) { +// RAVEN BEGIN +// kfuller: added "snd_closed" + StartSound( "snd_closed", SND_CHANNEL_ANY, 0, false, NULL ); +// RAVEN END + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, wait, this ); + } + + SetBlocked ( false ); + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +idMover_Binary::GotoPosition1 +================ +*/ +void idMover_Binary::GotoPosition1( void ) { + idMover_Binary *slave; + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition1(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS2 ) { + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } + if ( !spawnArgs.GetBool( "toggle" ) ) { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + // if already at at position 1 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::GotoPosition2 +================ +*/ +void idMover_Binary::GotoPosition2( void ) { + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition2(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS1 ) { + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + + // only partway up before reversing + if ( moverState == MOVER_2TO1 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + // if already at at position 2 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::UpdateBuddies +================ +*/ +void idMover_Binary::UpdateBuddies( int val ) +{ +// RAVEN BEGIN +// jdischler: was using update status but that was never getting set anyway. +// Additionally, shaderparm_mode was never getting set on the mover itself, which creates +// extra work for the designers. + int c = buddies.Num(); + for ( int i = 0; i < c; i++ ) { + idEntity *buddy = gameLocal.FindEntity( buddies[i] ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, val ); + buddy->UpdateVisuals(); + } + } + // Update the mover itself, too. + SetShaderParm( SHADERPARM_MODE, val ); + UpdateVisuals(); +// RAVEN END +} + +/* +================ +idMover_Binary::SetGuiStates +================ +*/ +void idMover_Binary::SetGuiStates( const char *state ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + + idMover_Binary *mb = activateChain; + while( mb ) { + if ( mb->guiTargets.Num() ) { + mb->SetGuiState( "movestate", state ); + } + mb = mb->activateChain; + } +} + +/* +================ +idMover_Binary::Use_BinaryMover +================ +*/ +void idMover_Binary::Use_BinaryMover( idEntity *activator ) { + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + activatedBy = activator; + + if ( moverState == MOVER_POS1 && !deferedOpen) { + + float openWait = spawnArgs.GetFloat( "openWait", "0" ); + deferedOpen = true; + PostEventMS( &EV_Mover_MatchTeam, SEC2MS(openWait), MOVER_1TO2, SEC2MS(openWait) + gameLocal.time ); + + // TODO: might want to delay these as well if openWait is present? + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + + // any things we should trigger when the door is first asked to open? + // NOTE: with openWait, it's possible (and desirable as per aweldon's request) + // that this will be called before the door actually opens. This is specifically + // used by the rotate/lock doors...the triggering below starts another entity rotating + // and onWait is used to offset the actual open so the rotating piece has time to work... + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOnOpen" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOnOpen", kv ); + } + + return; + } + + // if all the way up, just delay before coming down + if ( moverState == MOVER_POS2 ) { + idMover_Binary *slave; + + if ( wait == -1 ) { + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); + } + return; + } + + // only partway down before reversing + if ( moverState == MOVER_2TO1 ) { + GotoPosition2(); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + GotoPosition1(); + return; + } +} + +/* +================ +idMover_Binary::Event_Use_BinaryMover +================ +*/ +void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { + Use_BinaryMover( activator ); +} + +/* +================ +idMover_Binary::PreBind +================ +*/ +void idMover_Binary::PreBind( void ) { + pos1 = GetWorldCoordinates( pos1 ); + pos2 = GetWorldCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::PostBind +================ +*/ +void idMover_Binary::PostBind( void ) { + pos1 = GetLocalCoordinates( pos1 ); + pos2 = GetLocalCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::FindGuiTargets +================ +*/ +void idMover_Binary::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover_Binary::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover_Binary::SetGuiState( const char *key, const char *val ) const { + int i; + + for( i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_InitGuiTargets( void ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); + } +} + +/* +================ +idMover_Binary::InitSpeed + +pos1, pos2, and speed are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { + idVec3 move; + float distance; + float speed; + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + speed = mspeed ? mspeed : 100; + + // calculate time to reach second position from speed + move = pos2 - pos1; + distance = move.Length(); + duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::InitTime + +pos1, pos2, and time are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::SetBlocked +================ +*/ +void idMover_Binary::SetBlocked( bool b ) { + for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->blocked = b; + if ( b ) { + const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); + } + } + } +} + +/* +================ +idMover_Binary::IsBlocked +================ +*/ +bool idMover_Binary::IsBlocked( void ) { + return blocked; +} + +/* +================ +idMover_Binary::GetActivator +================ +*/ +idEntity *idMover_Binary::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +idMover_Binary::WriteToSnapshot +================ +*/ +void idMover_Binary::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( moverState, 3 ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Binary::ReadFromSnapshot +================ +*/ +void idMover_Binary::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moverState_t oldMoverState = moverState; + + physicsObj.ReadFromSnapshot( msg ); + moverState = (moverState_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( moverState != oldMoverState ) { + UpdateMoverSound( moverState ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover_Binary::SetPortalState +================ +*/ +void idMover_Binary::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + +idDoor + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +const idEventDef EV_Door_StartOpen( "", NULL ); +const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); +const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); +const idEventDef EV_Door_Open( "open", NULL ); +const idEventDef EV_Door_Close( "close", NULL ); +const idEventDef EV_Door_Lock( "lock", "d" ); +const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); +const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); + +CLASS_DECLARATION( idMover_Binary, idDoor ) + EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) + EVENT( EV_Touch, idDoor::Event_Touch ) + EVENT( EV_Activate, idDoor::Event_Activate ) + EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) + EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) + EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) + EVENT( EV_Door_Open, idDoor::Event_Open ) + EVENT( EV_Door_Close, idDoor::Event_Close ) + EVENT( EV_Door_Lock, idDoor::Event_Lock ) + EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) + EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) + EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) + EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) + EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) +// RAVEN BEGIN +// abahr: + EVENT( EV_Mover_ReturnToPos1, idDoor::Event_ReturnToPos1 ) +// RAVEN END +END_CLASS + +/* +================ +idDoor::idDoor +================ +*/ +idDoor::idDoor( void ) { + triggersize = 1.0f; + crusher = false; + noTouch = false; + aas_area_closed = false; + buddyStr.Clear(); + trigger = NULL; + sndTrigger = NULL; + nextSndTriggerTime = 0; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); + requires.Clear(); + removeItem = 0; + syncLock.Clear(); + companionDoor = NULL; + normalAxisIndex = 0; +} + +/* +================ +idDoor::~idDoor +================ +*/ +idDoor::~idDoor( void ) { + if ( trigger ) { + delete trigger; + } + if ( sndTrigger ) { + delete sndTrigger; + } +} + +/* +================ +idDoor::Save +================ +*/ +void idDoor::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( triggersize ); + savefile->WriteBool( crusher ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteString( buddyStr ); + + savefile->WriteClipModel( trigger ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); + + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); + + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteString( syncLock ); + savefile->WriteInt( normalAxisIndex ); + + savefile->WriteObject( companionDoor ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Save( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Restore +================ +*/ +void idDoor::Restore( idRestoreGame *savefile ) { + + savefile->ReadFloat( triggersize ); + savefile->ReadBool( crusher ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( aas_area_closed ); + SetAASAreaState( aas_area_closed ); + savefile->ReadString( buddyStr ); + + savefile->ReadClipModel( trigger ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); + + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); + + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadString( syncLock ); + savefile->ReadInt( normalAxisIndex ); + + savefile->ReadObject( reinterpret_cast( companionDoor ) ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Restore( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Spawn +================ +*/ +void idDoor::Spawn( void ) { + idVec3 abs_movedir; + float distance; + idVec3 size; + idVec3 movedir; + float dir; + float lip; + bool start_open; + float time; + float speed; + + // get the direction to move + if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { + // no movedir, so angle defines movement direction and not orientation, + // a la oldschool Quake + SetAngles( ang_zero ); + spawnArgs.GetFloat( "angle", "0", dir ); + } + GetMovedir( dir, movedir ); + + // default speed of 400 + spawnArgs.GetFloat( "speed", "400", speed ); + + // default wait of 2 seconds + spawnArgs.GetFloat( "wait", "3", wait ); + + // default lip of 8 units + spawnArgs.GetFloat( "lip", "8", lip ); + + // by default no damage + spawnArgs.GetFloat( "damage", "0", damage ); + + // trigger size + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + + spawnArgs.GetBool( "crusher", "0", crusher ); + spawnArgs.GetBool( "start_open", "0", start_open ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // expects syncLock to be a door that must be closed before this door will open + spawnArgs.GetString( "syncLock", "", syncLock ); + + spawnArgs.GetString( "buddy", "", buddyStr ); + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + + // ever separate piece of a door is considered solid when other team mates push entities + fl.solidForTeam = true; + + // first position at start + pos1 = GetPhysics()->GetOrigin(); + + // calculate second position + abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); + abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); + abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); + size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; + distance = ( abs_movedir * size ) - lip; + pos2 = pos1 + distance * movedir; + + // if "start_open", reverse position 1 and 2 + if ( start_open ) { + // post it after EV_SpawnBind + PostEventMS( &EV_Door_StartOpen, 1 ); + } + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } + + if ( moveMaster == this ) { + if ( health ) { + fl.takedamage = true; + } + if ( noTouch || health ) { + // non touch/shoot doors + PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.time ); + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } else { + // spawn trigger + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + if ( !start_open ) { + // start closed + ProcessEvent( &EV_Mover_ClosePortal ); + } + + int locked = spawnArgs.GetInt( "locked" ); + if ( locked ) { + // make sure all members of the team get locked + PostEventMS( &EV_Door_Lock, 0, locked ); + } + + if ( spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); + } + + // sounds have a habit of stuttering when portals close, so make them unoccluded + refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; + + companionDoor = NULL; + + enabled = true; + blocked = false; + +// RAVEN BEGIN +// bdube: added + // Instruct ai to avoid standing in doors + if ( !spawnArgs.GetBool ( "noavoid" ) ) { + aiManager.AddAvoid ( GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetBounds().GetRadius(), -1 ); + } +// RAVEN END +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + if ( sndTrigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idDoor::PreBind +================ +*/ +void idDoor::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idDoor::PostBind +================ +*/ +void idDoor::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); +} + +/* +================ +idDoor::SetAASAreaState +================ +*/ +void idDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); +} + +/* +================ +idDoor::Hide +================ +*/ +void idDoor::Hide( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Hide(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Hide(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Disable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Disable(); + } + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + slave->GetPhysics()->GetClipModel()->Disable(); + slave->idMover_Binary::Hide(); + } + } +} + +/* +================ +idDoor::Show +================ +*/ +void idDoor::Show( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Show(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Show(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Enable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Enable(); + } + if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + slave->GetPhysics()->GetClipModel()->Enable(); + slave->idMover_Binary::Show(); + } + } +} + +/* +================ +idDoor::GetLocalTriggerPosition +================ +*/ +void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +================ +idDoor::Use +================ +*/ +void idDoor::Use( idEntity *other, idEntity *activator ) { + if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + ActivateTargets( activator ); + Use_BinaryMover( activator ); + } +} + +/* +================ +idDoor::Open +================ +*/ +void idDoor::Open( void ) { + GotoPosition2(); +} + +/* +================ +idDoor::Close +================ +*/ +void idDoor::Close( void ) { + GotoPosition1(); +} + +/* +================ +idDoor::Lock +================ +*/ +void idDoor::Lock( int f ) { + idMover_Binary *other; + + // lock all the doors on the team + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + idDoor *door = static_cast( other ); + if ( other == moveMaster ) { + if ( door->sndTrigger == NULL ) { + // in this case the sound trigger never got spawned + const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); + if ( sndtemp && *sndtemp ) { + door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } + if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { + door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + door->spawnArgs.SetInt( "locked", f ); +// RAVEN BEGIN +// jdischler + // locking/unlocking doors should update the shaderparm7 of any buddies + door->UpdateBuddies( !f ); +// RAVEN END + + if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { + door->SetAASAreaState( f != 0 ); + } + } + } + + if ( f ) { + Close(); + } +} + +/* +================ +idDoor::IsLocked +================ +*/ +int idDoor::IsLocked( void ) { + return spawnArgs.GetInt( "locked" ); +} + +/* +================ +idDoor::IsOpen +================ +*/ +bool idDoor::IsOpen( void ) { + return ( moverState != MOVER_POS1 ); +} + +/* +================ +idDoor::IsNoTouch +================ +*/ +bool idDoor::IsNoTouch( void ) { + return noTouch; +} + +/* +====================== +idDoor::CalcTriggerBounds + +Calcs bounds for a trigger. +====================== +*/ +void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { + idMover_Binary *other; + int i; + int best; + + // find the bounds of everything on the team + bounds = GetPhysics()->GetAbsBounds(); + + if ( health > 0 ) { + fl.takedamage = true; + } + for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + // find the bounds of everything on the team + bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); + + // set all of the slaves as shootable + other->fl.takedamage = true; + } + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + normalAxisIndex = best; + bounds[0][ best ] -= size; + bounds[1][ best ] += size; + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); +} + +// RAVEN BEGIN +// abahr: +/* +============================== +idDoor::SetDoorFrameController +============================== +*/ +void idDoor::SetDoorFrameController( idEntity* controller ) { + doorFrameController = controller; +} + +/* +============================== +idDoor::ActivateTargets + +If we have a frame controller we activate its targets +============================== +*/ +void idDoor::ActivateTargets( idEntity *activator ) const { + if ( doorFrameController.IsValid() && static_cast< const idMover_Binary *>( GetMoveMaster() ) == this && moverState == MOVER_POS1 ) { + doorFrameController->ActivateTargets( activator ); + } + + idMover_Binary::ActivateTargets( activator ); +} +// RAVEN END + +/* +====================== +idDoor::Event_StartOpen + +if "start_open", reverse position 1 and 2 +====================== +*/ +void idDoor::Event_StartOpen( void ) { + float time; + float speed; + + // if "start_open", reverse position 1 and 2 + pos1 = pos2; + pos2 = GetPhysics()->GetOrigin(); + + spawnArgs.GetFloat( "speed", "400", speed ); + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } +} + +/* +====================== +idDoor::Event_SpawnDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them. +====================== +*/ +void idDoor::Event_SpawnDoorTrigger( void ) { + idBounds bounds; + idMover_Binary *other; + bool toggle; + + if ( trigger ) { + // already have a trigger, so don't spawn a new one. + return; + } + + // check if any of the doors are marked as toggled + toggle = false; + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { + toggle = true; + break; + } + } + + if ( toggle ) { + // mark them all as toggled + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + other->spawnArgs.Set( "toggle", "1" ); + } + } + // don't spawn trigger + return; + } + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( trigger ); + + MatchActivateTeam( moverState, gameLocal.time ); +} + +/* +====================== +idDoor::Event_SpawnSoundTrigger + +Spawn a sound trigger to activate locked sound if it exists. +====================== +*/ +void idDoor::Event_SpawnSoundTrigger( void ) { + idBounds bounds; + + if ( sndTrigger ) { + return; + } + + CalcTriggerBounds( triggersize * 0.5f, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + sndTrigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 254, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( sndTrigger ); +} + +/* +================ +idDoor::Event_Reached_BinaryMover +================ +*/ +void idDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked( false ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); + } + } else if ( moverState == MOVER_1TO2 ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); + } + } + idMover_Binary::Event_Reached_BinaryMover(); +} + +/* +================ +idDoor::Blocked_Door +================ +*/ +void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + SetBlocked( true ); + + if ( crusher ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( moveMaster->GetActivator() ); + + if ( companionDoor ) { + companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); + } +} + +/* +=============== +idDoor::SetCompanion +=============== +*/ +void idDoor::SetCompanion( idDoor *door ) { + companionDoor = door; +} + +/* +=============== +idDoor::Event_PartBlocked +=============== +*/ +void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idDoor::Event_ReturnToPos1 +================ +*/ +void idDoor::Event_ReturnToPos1( void ) { + idMover_Binary::Event_ReturnToPos1(); + + if( doorFrameController.IsValid() ) { + doorFrameController->ProcessEvent( &EV_CloseGate ); + } +} +// RAVEN END + +/* +================ +idDoor::Event_Touch +================ +*/ +void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate; + idVec3 planeaxis1, planeaxis2, normal; + idBounds bounds; + + if ( !enabled ) { + return; + } + + if ( trigger && trace->c.id == trigger->GetId() ) { + if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { +// RAVEN BEGIN +// abahr: allowing animated door frames + if( doorFrameController.IsValid() && doorFrameController != other ) { + doorFrameController->ProcessEvent( &EV_Touch, other, trace ); + } else { + Use( this, other ); + } +// RAVEN END + } + } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( other && other->IsType( idPlayer::GetClassType() ) && IsLocked() && gameLocal.time > nextSndTriggerTime ) { +// RAVEN END + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + } +} + +/* +================ +idDoor::Event_SpectatorTouch +================ +*/ +void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate, normal; + idBounds bounds; + idPlayer *p; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( other && other->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( other )->spectating ); +// RAVEN END + + p = static_cast< idPlayer * >( other ); + // avoid flicker when stopping right at clip box boundaries + if ( p->lastSpectateTeleport > gameLocal.time - 1000 ) { + return; + } + if ( trigger && !IsOpen() ) { + // teleport to the other side, center to the middle of the trigger brush + bounds = trigger->GetAbsBounds(); + contact = trace->endpos - bounds.GetCenter(); + translate = bounds.GetCenter(); + normal.Zero(); + normal[ normalAxisIndex ] = 1.0f; + if ( normal * contact > 0 ) { + translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } else { + translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } + p->SetOrigin( translate ); + p->lastSpectateTeleport = gameLocal.time; + } +} + +/* +================ +idDoor::Event_Activate +================ +*/ +void idDoor::Event_Activate( idEntity *activator ) { + int old_lock; + + if ( spawnArgs.GetInt( "locked" ) ) { + if ( !trigger ) { + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + UpdateBuddies( 1 ); + + old_lock = spawnArgs.GetInt( "locked" ); + Lock( 0 ); + if ( old_lock == 2 ) { + return; + } + } + + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + +// RAVEN BEGIN +// abahr: + if( doorFrameController.IsValid() && doorFrameController != activator ) { + doorFrameController->ProcessEvent( &EV_Activate, activator ); + } else { + ActivateTargets( activator ); + + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + UpdateVisuals(); + + Use_BinaryMover( activator ); + } +//RAVEN END +} + +/* +================ +idDoor::Event_Open +================ +*/ +void idDoor::Event_Open( void ) { + Open(); +} + +/* +================ +idDoor::Event_Close +================ +*/ +void idDoor::Event_Close( void ) { + Close(); +} + +/* +================ +idDoor::Event_Lock +================ +*/ +void idDoor::Event_Lock( int f ) { + Lock( f ); +} + +/* +================ +idDoor::Event_IsOpen +================ +*/ +void idDoor::Event_IsOpen( void ) { + bool state; + + state = IsOpen(); + idThread::ReturnFloat( state ); +} + +/* +================ +idDoor::Event_Locked +================ +*/ +void idDoor::Event_Locked( void ) { + idThread::ReturnFloat( spawnArgs.GetInt("locked") ); +} + +/* +================ +idDoor::Event_OpenPortal + +Sets the portal associtated with this door to be open +================ +*/ +void idDoor::Event_OpenPortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + } +} + +/* +================ +idDoor::Event_ClosePortal + +Sets the portal associtated with this door to be closed +================ +*/ +void idDoor::Event_ClosePortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( !slave->IsHidden() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + } + } +} + + +/* +=============================================================================== + +idPlat + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Binary, idPlat ) + EVENT( EV_Touch, idPlat::Event_Touch ) + EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) +END_CLASS + +/* +=============== +idPlat::idPlat +=============== +*/ +idPlat::idPlat( void ) { + trigger = NULL; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); +} + +/* +=============== +idPlat::~idPlat +=============== +*/ +idPlat::~idPlat( void ) { + if ( trigger ) { + delete trigger; + } +} + +/* +=============== +idPlat::Save +=============== +*/ +void idPlat::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Restore +=============== +*/ +void idPlat::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Spawn +=============== +*/ +void idPlat::Spawn( void ) { + float lip; + float height; + float time; + float speed; + float accel; + float decel; + bool noTouch; + + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetFloat( "damage", "0", damage ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetFloat( "lip", "8", lip ); + spawnArgs.GetFloat( "accel_time", "0.25", accel ); + spawnArgs.GetFloat( "decel_time", "0.25", decel ); + + // create second position + if ( !spawnArgs.GetFloat( "height", "0", height ) ) { + height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; + } + + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // pos1 is the rest (bottom) position, pos2 is the top + pos2 = GetPhysics()->GetOrigin(); + pos1 = pos2; + pos1[2] -= height; + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, accel, decel ); + } else { + InitSpeed( pos1, pos2, speed, accel, decel ); + } + + SetMoverState( MOVER_POS1, gameLocal.time ); + UpdateVisuals(); + + // spawn the trigger if one hasn't been custom made + if ( !noTouch ) { + // spawn trigger + SpawnPlatTrigger( pos1 ); + } +} + +/* +================ +idPlat::Think +================ +*/ +void idPlat::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idPlat::PreBind +================ +*/ +void idPlat::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idPlat::PostBind +================ +*/ +void idPlat::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ); +} + +/* +================ +idPlat::GetLocalTriggerPosition +================ +*/ +void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +============== +idPlat::SpawnPlatTrigger +=============== +*/ +void idPlat::SpawnPlatTrigger( idVec3 &pos ) { + idBounds bounds; + idVec3 tmin; + idVec3 tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + + bounds = GetPhysics()->GetBounds(); + + tmin[0] = bounds[0][0] + 33; + tmin[1] = bounds[0][1] + 33; + tmin[2] = bounds[0][2]; + + tmax[0] = bounds[1][0] - 33; + tmax[1] = bounds[1][1] - 33; + tmax[2] = bounds[1][2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; + tmax[1] = tmin[1] + 1; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +============== +idPlat::Event_Touch +=============== +*/ +void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { + Use_BinaryMover( other ); + } +} + +/* +================ +idPlat::Event_TeamBlocked +================ +*/ +void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + Use_BinaryMover( activatedBy.GetEntity() ); + + Use_BinaryMover( this ); + +} + +/* +=============== +idPlat::Event_PartBlocked +=============== +*/ +void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + + +/* +=============================================================================== + +idMover_Periodic + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMover_Periodic ) + EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) +END_CLASS + +/* +=============== +idMover_Periodic::idMover_Periodic +=============== +*/ +idMover_Periodic::idMover_Periodic( void ) { + damage = 0.0f; + fl.neverDormant = false; +} + +idMover_Periodic::~idMover_Periodic( void ) { + SetPhysics( NULL ); +} + +/* +=============== +idMover_Periodic::Spawn +=============== +*/ +void idMover_Periodic::Spawn( void ) { + spawnArgs.GetFloat( "damage", "0", damage ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + GetPhysics()->SetContents( 0 ); + } +} + +/* +=============== +idMover_Periodic::Save +=============== +*/ +void idMover_Periodic::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( damage ); +} + +/* +=============== +idMover_Periodic::Restore +=============== +*/ +void idMover_Periodic::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + savefile->ReadFloat( damage ); + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMover_Periodic::Think +================ +*/ +void idMover_Periodic::Think( void ) { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + RunPhysics(); + Present(); +} + +/* +=============== +idMover_Periodic::Event_TeamBlocked +=============== +*/ +void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } +} + +/* +=============== +idMover_Periodic::Event_PartBlocked +=============== +*/ +void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idMover_Periodic::WriteToSnapshot +================ +*/ +void idMover_Periodic::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Periodic::ReadFromSnapshot +================ +*/ +void idMover_Periodic::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idRotater + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRotater ) + EVENT( EV_Activate, idRotater::Event_Activate ) +END_CLASS + +/* +=============== +idRotater::idRotater +=============== +*/ +idRotater::idRotater( void ) { + activatedBy = this; +} + +/* +=============== +idRotater::Spawn +=============== +*/ +void idRotater::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idRotater::Save +=============== +*/ +void idRotater::Save( idSaveGame *savefile ) const { + activatedBy.Save( savefile ); +} + +/* +=============== +idRotater::Restore +=============== +*/ +void idRotater::Restore( idRestoreGame *savefile ) { + activatedBy.Restore( savefile ); +} + +/* +=============== +idRotater::Event_Activate +=============== +*/ +void idRotater::Event_Activate( idEntity *activator ) { + float speed; + bool x_axis; + bool y_axis; + idAngles delta; + + activatedBy = activator; + + delta.Zero(); + + if ( !spawnArgs.GetBool( "rotate" ) ) { + spawnArgs.Set( "rotate", "1" ); + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of rotation + if ( x_axis ) { + delta[2] = speed; + } else if ( y_axis ) { + delta[0] = speed; + } else { + delta[1] = speed; + } + } else { + spawnArgs.Set( "rotate", "0" ); + } + + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idBobber ) +END_CLASS + +/* +=============== +idBobber::idBobber +=============== +*/ +idBobber::idBobber( void ) { +} + +/* +=============== +idBobber::Spawn +=============== +*/ +void idBobber::Spawn( void ) { + float speed; + float height; + float phase; + bool x_axis; + bool y_axis; + idVec3 delta; + + spawnArgs.GetFloat( "speed", "4", speed ); + spawnArgs.GetFloat( "height", "32", height ); + spawnArgs.GetFloat( "phase", "0", phase ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of bobbing + delta = vec3_origin; + if ( x_axis ) { + delta[ 0 ] = height; + } else if ( y_axis ) { + delta[ 1 ] = height; + } else { + delta[ 2 ] = height; + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idPendulum + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idPendulum ) +END_CLASS + +/* +=============== +idPendulum::idPendulum +=============== +*/ +idPendulum::idPendulum( void ) { +} + +/* +=============== +idPendulum::Spawn +=============== +*/ +void idPendulum::Spawn( void ) { + float speed; + float freq; + float length; + float phase; + + spawnArgs.GetFloat( "speed", "30", speed ); + spawnArgs.GetFloat( "phase", "0", phase ); + + if ( spawnArgs.GetFloat( "freq", "", freq ) ) { + if ( freq <= 0.0f ) { + gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); + } + } else { + // find pendulum length + length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); + if ( length < 8 ) { + length = 8; + } + + if( gameLocal.isMultiplayer ) { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_mp_gravity.GetFloat() / ( 3 * length ) ); + } else { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); + } + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRiser ) +EVENT( EV_Activate, idRiser::Event_Activate ) +END_CLASS + +/* +=============== +idRiser::idRiser +=============== +*/ +idRiser::idRiser( void ) { +} + +/* +=============== +idRiser::Spawn +=============== +*/ +void idRiser::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + SetPhysics( &physicsObj ); +} + +/* +================ +idRiser::Event_Activate +================ +*/ +void idRiser::Event_Activate( idEntity *activator ) { + + if ( !IsHidden() && spawnArgs.GetBool("hide") ) { + Hide(); + } else { + Show(); + float time; + float height; + idVec3 delta; + + spawnArgs.GetFloat( "time", "4", time ); + spawnArgs.GetFloat( "height", "32", height ); + + delta = vec3_origin; + delta[ 2 ] = height; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); + } +} + + +/* +=============================================================================== + +rvConveyor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvConveyor ) + EVENT( EV_FindTargets, rvConveyor::Event_FindTargets ) +END_CLASS + +/* +================ +rvConveyor::rvConveyor +================ +*/ +rvConveyor::rvConveyor ( void ) { +} + +/* +================ +rvConveyor::Spawn +================ +*/ +void rvConveyor::Spawn ( void ) { + spawnArgs.GetFloat ( "speed", "100", moveSpeed ); + + float angle; + if ( spawnArgs.GetFloat ( "moveAngle", "0", angle ) ) { + moveDir = idAngles ( 0, angle, 0 ).ToMat3()[0]; + } else { + moveDir = GetPhysics()->GetAxis()[0]; + } + + GetPhysics()->SetContents ( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID ); + GetPhysics()->EnableClip ( ); + + BecomeActive ( TH_THINK|TH_PHYSICS ); +} + +/* +================ +rvConveyor::Think +================ +*/ +void rvConveyor::Think ( void ) { + trace_t pushResults; + idVec3 newOrigin; + idMat3 newAxis; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin ( ); + oldAxis = GetPhysics()->GetAxis ( ); + + newOrigin = GetPhysics()->GetOrigin() + moveDir * moveSpeed * MS2SEC ( gameLocal.GetMSec() ); + newAxis = oldAxis; + gameLocal.push.ClipPush( pushResults, this, 0, + GetPhysics()->GetOrigin(), oldAxis, newOrigin, newAxis ); + + GetPhysics()->SetOrigin ( oldOrigin ); + GetPhysics()->SetAxis ( oldAxis ); + + idEntity::Think(); +} + +/* +================ +rvConveyor::Save +================ +*/ +void rvConveyor::Save( idSaveGame *savefile ) const +{ + savefile->WriteVec3( moveDir ); + savefile->WriteFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Restore +================ +*/ +void rvConveyor::Restore( idRestoreGame *savefile ) +{ + savefile->ReadVec3( moveDir ); + savefile->ReadFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Event_FindTargets +================ +*/ +void rvConveyor::Event_FindTargets ( void ) { + FindTargets ( ); + + moveDir = GetPhysics()->GetAxis ( )[0]; + if ( !targets.Num ( ) || !targets[0] ) { + return; + } + + idEntity* path; + idEntity* next; + path = targets[0]; + path->FindTargets ( ); + next = path->targets.Num() ? path->targets[0].GetEntity() : NULL; + if ( !next ) { + return; + } + + moveDir = next->GetPhysics()->GetOrigin() - path->GetPhysics()->GetOrigin(); + moveDir.Normalize ( ); + + path->PostEventMS ( &EV_Remove, 0 ); +} + + +CLASS_DECLARATION( idMover, rvPusher ) +END_CLASS + +/* +================ +rvPusher::rvPusher +================ +*/ +rvPusher::rvPusher( void ) { + parent = 0; +} + +/* +================ +rvPusher::~rvPusher +================ +*/ +rvPusher::~rvPusher( void ) { +} + +/* +================ +rvPusher::Spawn +================ +*/ +void rvPusher::Spawn( void ) { + parent = 0; +} + +/* +================ +rvPusher::Think +================ +*/ +void rvPusher::Think( void ) { + + // Total hack, but it gets the job done + BecomeActive( TH_ALL ); + + if ( parent ) { + idAnimator *parentAnimator = parent->GetAnimator(); + if ( parentAnimator ) { + + idStr jointName; + if ( spawnArgs.GetString( "attachedBone", "", jointName ) ) { + bindJointHandle = parentAnimator->GetJointHandle( jointName ); + + trace_t pushResults; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin(); + oldAxis = GetPhysics()->GetAxis(); + + parentAnimator->CreateFrame( gameLocal.time, true ); + parentAnimator->ServiceAnims( gameLocal.previousTime, gameLocal.time ); + parentAnimator->GetJointTransform( bindJointHandle, gameLocal.time, pusherOrigin, pusherAxis ); + pusherAxis *= parent->GetRenderEntity()->axis; + pusherOrigin = parent->GetRenderEntity()->origin + pusherOrigin * parent->GetRenderEntity()->axis; + MoveToPos( pusherOrigin ); + gameLocal.push.ClipTranslationalPush(pushResults, this, 0, pusherOrigin, pusherOrigin - oldOrigin ); + GetPhysics()->SetOrigin ( pusherOrigin ); + } + + } + } else { + idStr bindEntName; + parent = 0; + if ( spawnArgs.GetString( "attachedEntity", "", bindEntName ) ) { + parent = gameLocal.FindEntity( bindEntName ); + } + } + + idMover::Think(); +} + + + +// RAVEN END diff --git a/source/mpgame/Mover.h b/source/mpgame/Mover.h new file mode 100644 index 0000000..52f7d19 --- /dev/null +++ b/source/mpgame/Mover.h @@ -0,0 +1,707 @@ + +#ifndef __GAME_MOVER_H__ +#define __GAME_MOVER_H__ + +extern const idEventDef EV_TeamBlocked; +extern const idEventDef EV_PartBlocked; +extern const idEventDef EV_ReachedPos; +extern const idEventDef EV_ReachedAng; + +// RAVEN BEGIN +// bdube: more externs +extern const idEventDef EV_Door_Lock; +extern const idEventDef EV_Door_IsLocked; +// abahr: +extern const idEventDef EV_GetSplineEntity; +extern const idEventDef EV_MoveAlongVector; +extern const idEventDef EV_Door_Open; +extern const idEventDef EV_Door_Close; +extern const idEventDef EV_Door_IsOpen; +extern const idEventDef EV_Move; +extern const idEventDef EV_RotateOnce; +// twhitaker: +extern const idEventDef EV_Speed; +extern const idEventDef EV_IsActive; +extern const idEventDef EV_IsMoving; + +// mekberg: spline sampling +#define SPLINE_SAMPLE_RATE 60.0f +// RAVEN END + +/* +=============================================================================== + + General movers. + +=============================================================================== +*/ + +class idMover : public idEntity { +public: + CLASS_PROTOTYPE( idMover ); + + idMover( void ); + ~idMover( void ); + + void Spawn( void ); + +// RAVEN BEGIN +// mekberg: added + void Think( void ); +// RAVEN END + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + void SetPortalState( bool open ); + +// RAVEN BEGIN +// mekberg: sounds for splines + void UpdateSplineStage ( void ); +// RAVEN END + +protected: + typedef enum { + ACCELERATION_STAGE, + LINEAR_STAGE, + DECELERATION_STAGE, + FINISHED_STAGE + } moveStage_t; + + typedef enum { + MOVER_NONE, + MOVER_ROTATING, + MOVER_MOVING, + MOVER_SPLINE + } moverCommand_t; + + // + // mover directions. make sure to change script/doom_defs.script if you add any, or change their order + // + typedef enum { + DIR_UP = -1, + DIR_DOWN = -2, + DIR_LEFT = -3, + DIR_RIGHT = -4, + DIR_FORWARD = -5, + DIR_BACK = -6, + DIR_REL_UP = -7, + DIR_REL_DOWN = -8, + DIR_REL_LEFT = -9, + DIR_REL_RIGHT = -10, + DIR_REL_FORWARD = -11, + DIR_REL_BACK = -12 + } moverDir_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idVec3 dir; + } moveState_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idAngles rot; + } rotationState_t; + + idPhysics_Parametric physicsObj; + + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_PartBlocked( idEntity *blockingEntity ); + + void MoveToPos( const idVec3 &pos); + void UpdateMoveSound( moveStage_t stage ); + void UpdateRotationSound( moveStage_t stage ); + void SetGuiStates( const char *state ); + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + virtual void DoneMoving( void ); + virtual void DoneRotating( void ); + virtual void BeginMove( idThread *thread = NULL ); + virtual void BeginRotation( idThread *thread, bool stopwhendone ); + moveState_t move; + + rvStateThread splineStateThread; + float damage; + +private: + rotationState_t rot; + + int move_thread; + int rotate_thread; + idAngles dest_angles; + idAngles angle_delta; + idVec3 dest_position; + idVec3 move_delta; + float move_speed; + int move_time; + int deceltime; + int acceltime; + bool stopRotation; + bool useSplineAngles; + bool attenuate; + bool useIdleSound; + idEntityPtr splineEnt; + moverCommand_t lastCommand; + + qhandle_t areaPortal; // 0 = no portal + +// mekberg: attenution and sound additions + float maxAttenuation; + float attenuationScale; + idVec3 lastOrigin; + int lastTime; + int splineStartTime; +// RAVEN END + + idList< idEntityPtr > guiTargets; + +// RAVEN BEGIN +// abahr: + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void MoveAlongVector( const idVec3& vec ); +// RAVEN END + + void VectorForDir( float dir, idVec3 &vec ); + idCurve_Spline *GetSpline( idEntity *splineEntity ) const; + + void Event_SetCallback( void ); + void Event_TeamBlocked( idEntity *blockedPart, idEntity *blockingEntity ); + void Event_StopMoving( void ); + void Event_StopRotating( void ); + void Event_UpdateMove( void ); + void Event_UpdateRotation( void ); + void Event_SetMoveSpeed( float speed ); + void Event_SetMoveTime( float time ); + void Event_SetDecelerationTime( float time ); + void Event_SetAccellerationTime( float time ); + void Event_MoveTo( idEntity *ent ); + void Event_MoveToPos( idVec3 &pos ); + void Event_MoveDir( float angle, float distance ); + void Event_MoveAccelerateTo( float speed, float time ); + void Event_MoveDecelerateTo( float speed, float time ); + void Event_RotateDownTo( int axis, float angle ); + void Event_RotateUpTo( int axis, float angle ); + void Event_RotateTo( idAngles &angles ); + void Event_Rotate( idAngles &angles ); + void Event_RotateOnce( idAngles &angles ); + void Event_Bob( float speed, float phase, idVec3 &depth ); + void Event_Sway( float speed, float phase, idAngles &depth ); + void Event_SetAccelSound( const char *sound ); + void Event_SetDecelSound( const char *sound ); +// RAVEN BEGIN +// cnicholson: added stop sound support + void Event_SetStoppedSound( const char *sound ); +// RAVEN END + void Event_SetMoveSound( const char *sound ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + void Event_EnableSplineAngles( void ); + void Event_DisableSplineAngles( void ); + void Event_RemoveInitialSplineAngles( void ); + void Event_StartSpline( idEntity *splineEntity ); + void Event_StopSpline( void ); + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN + void Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ); +// RAVEN END + void Event_IsMoving( void ); + void Event_IsRotating( void ); +// RAVEN BEGIN +// abahr: + void Event_GetSplineEntity(); + void Event_MoveAlongVector( const idVec3& vec ); + +// mekberg: spline states + CLASS_STATES_PROTOTYPE ( idMover ); + + stateResult_t State_Accel( const stateParms_t& parms ); + stateResult_t State_Linear( const stateParms_t& parms ); + stateResult_t State_Decel( const stateParms_t& parms ); +// RAVEN END +}; + +class idSplinePath : public idEntity { +public: + CLASS_PROTOTYPE( idSplinePath ); + + idSplinePath(); + ~idSplinePath(); + + void Spawn( void ); + +// RAVEN BEGIN +// abahr: so we can ignore these if needed + void Toggle() { SetActive(!active); } + void Activate() { SetActive(true); } + void Deactivate() { SetActive(false); } + + bool IsActive() const { return active; } + + int SortTargets(); + int SortBackwardsTargets(); + int SortTargets( idList< idEntityPtr >& list ); + void RemoveNullTargets( void ); + void ActivateTargets( idEntity *activator ) const; + + void RemoveNullTargets( idList< idEntityPtr >& list ); + void ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idList< idEntityPtr > backwardPathTargets; + +// mekberg: spline sampling + float GetSampledTime ( float distance ) const; + +protected: + void SetActive( bool activate ) { active = activate; } + virtual void FindTargets(); + + void Event_Toggle( idEntity* activator ) { Toggle(); } + void Event_IsActive(); + +// mekberg: spline sampling + void SampleSpline ( void ); + +protected: + bool active; + +// mekberg: sample splines. + float* sampledTimes; + float sampledSpeed; + int numSamples; +// RAVEN END +}; + + +struct floorInfo_s { + idVec3 pos; + idStr door; + int floor; +}; + +class idElevator : public idMover { +public: + CLASS_PROTOTYPE( idElevator ); + + idElevator( void ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand ( idEntity *entityGui, idLexer *src ); + floorInfo_s * GetFloorInfo ( int floor ); + +protected: + virtual void DoneMoving( void ); + virtual void BeginMove( idThread *thread = NULL ); + void SpawnTrigger( const idVec3 &pos ); + void GetLocalTriggerPosition(); + void Event_Touch( idEntity *other, trace_t *trace ); + +// RAVEN BEGIN +// bdube: added + void SetAASAreaState ( bool enable ); + void InitStatusGui ( idUserInterface* gui ); + void UpdateStatusGui ( idUserInterface* gui ); + void UpdateStatusGuis ( void ); + void UpdateFloorInfo ( void ); +// RAVEN END + +private: + typedef enum { + INIT, + IDLE, + WAITING_ON_DOORS + } elevatorState_t; + + elevatorState_t state; + idList floorInfo; + int currentFloor; + int pendingFloor; + int lastFloor; + bool controlsDisabled; +// bool waitingForPlayerFollowers; + float returnTime; + int returnFloor; + int lastTouchTime; + + class idDoor * GetDoor( const char *name ); + void Think( void ); + void OpenInnerDoor( void ); + void OpenFloorDoor( int floor ); + void CloseAllDoors( void ); + void DisableAllDoors( void ); + void EnableProperDoors( void ); + + void Event_TeamBlocked ( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_Activate ( idEntity *activator ); + void Event_PostFloorArrival ( void ); + void Event_GotoFloor ( int floor ); + void Event_UpdateFloorInfo ( void ); +}; + + +/* +=============================================================================== + + Binary movers. + +=============================================================================== +*/ + +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +class idMover_Binary : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Binary ); + + idMover_Binary(); + ~idMover_Binary(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void PreBind( void ); + virtual void PostBind( void ); + + void Enable( bool b ); + void InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ); + void InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ); + void GotoPosition1( void ); + void GotoPosition2( void ); + void Use_BinaryMover( idEntity *activator ); + void SetGuiStates( const char *state ); + void UpdateBuddies( int val ); + idMover_Binary * GetActivateChain( void ) const { return activateChain; } + idMover_Binary * GetMoveMaster( void ) const { return moveMaster; } + void BindTeam( idEntity *bindTo ); + void SetBlocked( bool b ); + bool IsBlocked( void ); + idEntity * GetActivator( void ) const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetPortalState( bool open ); + +protected: + idVec3 pos1; + idVec3 pos2; + moverState_t moverState; + idMover_Binary * moveMaster; + idMover_Binary * activateChain; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + float wait; + float damage; + int duration; + int accelTime; + int decelTime; + idEntityPtr activatedBy; + int stateStartTime; + idStr team; + bool enabled; + bool deferedOpen; + int move_thread; + int updateStatus; // 1 = lock behaviour, 2 = open close status + idStrList buddies; + idPhysics_Parametric physicsObj; + qhandle_t areaPortal; // 0 = no portal + bool blocked; + idList< idEntityPtr > guiTargets; + + void MatchActivateTeam( moverState_t newstate, int time ); + void JoinActivateTeam( idMover_Binary *master ); + + void UpdateMoverSound( moverState_t state ); + void SetMoverState( moverState_t newstate, int time ); + moverState_t GetMoverState( void ) const { return moverState; } + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + void Event_SetCallback( void ); + void Event_ReturnToPos1( void ); + void Event_Use_BinaryMover( idEntity *activator ); + void Event_Reached_BinaryMover( void ); + void Event_MatchActivateTeam( moverState_t newstate, int time ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + + static void GetMovedir( float dir, idVec3 &movedir ); +}; + +class idDoor : public idMover_Binary { +public: + CLASS_PROTOTYPE( idDoor ); + + idDoor( void ); + ~idDoor( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void Hide( void ); + virtual void Show( void ); + + bool IsOpen( void ); + bool IsNoTouch( void ); + int IsLocked( void ); + void Lock( int f ); + void Use( idEntity *other, idEntity *activator ); + void Close( void ); + void Open( void ); + void SetCompanion( idDoor *door ); + +// RAVEN BEGIN +// abahr: + bool IsClosed( void ) const { return moverState == MOVER_POS1; } + void SetDoorFrameController( idEntity* controller ); + virtual void ActivateTargets( idEntity *activator ) const; +// RAVEN END + +private: + float triggersize; + bool crusher; + bool noTouch; + bool aas_area_closed; + idStr buddyStr; + idClipModel * trigger; + idClipModel * sndTrigger; + int nextSndTriggerTime; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + idStr requires; + int removeItem; + idStr syncLock; + int normalAxisIndex; // door faces X or Y for spectator teleports + idDoor * companionDoor; + +// RAVEN BEGIN +// abahr: so we can route calls through a tramGate + idEntityPtr doorFrameController; +// RAVEN END + + void SetAASAreaState( bool closed ); + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void CalcTriggerBounds( float size, idBounds &bounds ); + + void Event_Reached_BinaryMover( void ); + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + void Event_StartOpen( void ); + void Event_SpawnDoorTrigger( void ); + void Event_SpawnSoundTrigger( void ); + void Event_Close( void ); + void Event_Open( void ); + void Event_Lock( int f ); + void Event_IsOpen( void ); + void Event_Locked( void ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + +// RAVEN BEGIN +// abahr: + void Event_ReturnToPos1( void ); +// RAVEN END +}; + +class idPlat : public idMover_Binary { +public: + CLASS_PROTOTYPE( idPlat ); + + idPlat( void ); + ~idPlat( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + +private: + idClipModel * trigger; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void SpawnPlatTrigger( idVec3 &pos ); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Special periodic movers. + +=============================================================================== +*/ + +class idMover_Periodic : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Periodic ); + + idMover_Periodic( void ); + ~idMover_Periodic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idPhysics_Parametric physicsObj; + float damage; + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); +}; + +class idRotater : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRotater ); + + idRotater( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idEntityPtr activatedBy; + + void Event_Activate( idEntity *activator ); +}; + +class idBobber : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idBobber ); + + idBobber( void ); + + void Spawn( void ); + +private: +}; + +class idPendulum : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idPendulum ); + + idPendulum( void ); + + void Spawn( void ); + +private: + +}; + +class idRiser : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRiser ); + + idRiser( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: conveyor belts +class rvConveyor : public idEntity { +public: + CLASS_PROTOTYPE( rvConveyor ); + + rvConveyor ( void ); + + void Spawn( void ); + void Think ( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +private: + + idVec3 moveDir; + float moveSpeed; + + void Event_FindTargets ( void ); +}; + +// nrausch: +class rvPusher : public idMover { +public: + CLASS_PROTOTYPE( rvPusher ); + + rvPusher( void ); + ~rvPusher( void ); + + virtual void Spawn( void ); + virtual void Think ( void ); + +private: + jointHandle_t bindJointHandle; + idEntity *parent; + idVec3 pusherOrigin; + idMat3 pusherAxis; +}; + +// RAVEN END + +#endif /* !__GAME_MOVER_H__ */ diff --git a/source/mpgame/MultiplayerGame.cpp b/source/mpgame/MultiplayerGame.cpp new file mode 100644 index 0000000..59b9ad3 --- /dev/null +++ b/source/mpgame/MultiplayerGame.cpp @@ -0,0 +1,9275 @@ +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" ); + +const char *idMultiplayerGame::MPGuis[] = { +// RAVEN BEGIN +// bdube: use regular hud for now + "guis/hud.gui", +// RAVEN END + "guis/mpmain.gui", + "guis/mpmsgmode.gui", + "guis/netmenu.gui", + "guis/mphud.gui", + NULL +}; + +const char *idMultiplayerGame::ThrottleVars[] = { + "ui_spectate", + "ui_ready", + "ui_team", + NULL +}; + +const char *idMultiplayerGame::ThrottleVarsInEnglish[] = { + "#str_106738", + "#str_106737", + "#str_101991", + NULL +}; + +const int idMultiplayerGame::ThrottleDelay[] = { + 8, + 5, + 5 +}; + +const char* idMultiplayerGame::teamNames[ TEAM_MAX ] = { + "Marine", + "Strogg" +}; + +idCVar gui_ui_name( "gui_ui_name", "", CVAR_GAME | CVAR_NOCHEAT, "copy-over cvar for ui_name" ); + +/* +================ +ComparePlayerByScore +================ +*/ +int ComparePlayersByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +CompareTeamByScore +================ +*/ +int CompareTeamsByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +idMultiplayerGame::idMultiplayerGame +================ +*/ +idMultiplayerGame::idMultiplayerGame() { +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = NULL; +// RITUAL END + scoreBoard = NULL; + statSummary = NULL; + mainGui = NULL; + mapList = NULL; + msgmodeGui = NULL; + defaultWinner = -1; + deadZonePowerupCount = -1; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + + memset( lights, 0, sizeof( lights ) ); + memset( lightHandles, -1, sizeof( lightHandles ) ); + + Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + flagEntities[ i ] = NULL; + teamDeadZoneScore[i] = 0; + } + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + announcerSoundQueue.Clear(); + announcerPlayTime = 0; + + gameState = NULL; + currentSoundOverride = false; + + rankTextPlayer = NULL; + + privatePlayers = 0; + + lastAnnouncerSound = AS_NUM_SOUNDS; +} + +/* +================ +idMultiplayerGame::Shutdown +================ +*/ +void idMultiplayerGame::Shutdown( void ) { + + Clear(); + statManager->Shutdown(); + + if( gameState ) { + delete gameState; + } + gameState = NULL; +} + +/* +================ +idMultiplayerGame::Reset +================ +*/ +void idMultiplayerGame::Reset() { + Clear(); + assert( !scoreBoard && !mainGui && !mapList ); + + mpBuyingManager.Reset(); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = uiManager->FindGui( "guis/buymenu.gui", true, false, true ); + buyMenu->SetStateString( "field_credits", "$0.00"); + buyMenu->SetStateBool( "gameDraw", true ); +// RITUAL END + PACIFIER_UPDATE; + scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true ); + +#ifdef _XENON + statSummary = scoreBoard; +#else + statSummary = uiManager->FindGui( "guis/summary.gui", true, false, true ); + statSummary->SetStateBool( "gameDraw", true ); +#endif + + PACIFIER_UPDATE; + + mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true ); + mapList = uiManager->AllocListGUI( ); + mapList->Config( mainGui, "mapList" ); + + // set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI + mainGui->SetStateBool( "gameDraw", true ); + mainGui->SetKeyBindingNames(); + mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + +// SetMenuSkin(); + + PACIFIER_UPDATE; + msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true ); + msgmodeGui->SetStateBool( "gameDraw", true ); + + memset ( lights, 0, sizeof( lights ) ); + memset ( lightHandles, -1, sizeof( lightHandles ) ); + + renderLight_t *light; + const char *shader; + + light = &lights[ MPLIGHT_CTF_MARINE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 142.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 190.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 84.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_MARINE; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_CTF_STROGG ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 153.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_QUAD ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 0.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 128.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_HASTE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 225.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_REGEN ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 0.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + PACIFIER_UPDATE; + ClearGuis(); + +//asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = 0; + + iconManager->Shutdown(); + + // update serverinfo + UpdatePrivatePlayerCount(); + + lastReadyToggleTime = -1; + + cvarSystem->SetCVarBool( "s_voiceChatTest", false ); +} + +/* +================ +idMultiplayerGame::ServerClientConnect +================ +*/ +void idMultiplayerGame::ServerClientConnect( int clientNum ) { + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + statManager->ClientConnect( clientNum ); +} + +/* +================ +idMultiplayerGame::SpawnPlayer +================ +*/ +void idMultiplayerGame::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + + if ( !p->IsFakeClient() ) { + bool ingame = playerState[ clientNum ].ingame; + // keep ingame to true if needed, that should only happen for local player + + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + if ( !gameLocal.isClient ) { + p->spawnedTime = gameLocal.time; + //if ( gameLocal.IsTeamGame() ) { + // SwitchToTeam( clientNum, -1, p->team ); + //} + playerState[ clientNum ].ingame = ingame; + } + } + + if ( p->IsLocalClient() && gameLocal.GetLocalPlayer() ) { + tourneyGUI.SetupTourneyGUI( gameLocal.GetLocalPlayer()->mphud, scoreBoard ); + } + + lastVOAnnounce = 0; +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::Clear() { + + int i; + + pingUpdateTime = 0; + vote = VOTE_NONE; + voteTimeOut = 0; + voteExecTime = 0; + matchStartedTime = 0; + memset( &playerState, 0 , sizeof( playerState ) ); + currentMenu = 0; + bCurrentMenuMsg = false; + nextMenu = 0; + pureReady = false; + scoreBoard = NULL; + buyMenu = NULL; + isBuyingAllowedRightNow = false; + statSummary = NULL; + mainGui = NULL; + msgmodeGui = NULL; + if ( mapList ) { + uiManager->FreeListGUI( mapList ); + mapList = NULL; + } + memset( &switchThrottle, 0, sizeof( switchThrottle ) ); + voiceChatThrottle = 0; + + voteValue.Clear(); + voteString.Clear(); + + prevAnnouncerSnd = -1; + + localisedGametype.Clear(); + + for( i = 0; i < MAX_CLIENTS; i++ ) { + kickVoteMapNames[ i ].Clear(); + } + + voteMapDecls.Clear(); + voteMapsWaiting = 0; + + for ( i = 0; i < MPLIGHT_MAX; i ++ ) { + FreeLight( i ); + } + + chatHistory.Clear(); + rconHistory.Clear(); + + memset( rankedTeams, 0, sizeof( rvPair ) * TEAM_MAX ); + + if( gameState ) { + gameState->Clear(); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + unrankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + assaultPoints.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); +#endif +// RAVEN END + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + assaultPoints.Clear(); + + ClearAnnouncerSounds(); + + rankTextPlayer = NULL; + + for ( i = 0; i < TEAM_MAX; i++ ) { + flagEntities[ i ] = NULL; + } +} + +/* +================ +idMultiplayerGame::ClearMap +================ +*/ +void idMultiplayerGame::ClearMap( void ) { + assaultPoints.Clear(); + ClearAnnouncerSounds(); + announcerPlayTime = 0; + powerupCount = 0; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + prevAnnouncerSnd = -1; + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + // Dead Zone uses teamFragCount as the "player score" + // so we need to clear it at the beginning of every round. + if ( gameLocal.gameType == GAME_DEADZONE ) { + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + playerState[i].teamFragCount = 0; + playerState[i].deadZoneScore = 0; + } + } +} + +/* +================ +idMultiplayerGame::ClearGuis +================ +*/ +void idMultiplayerGame::ClearGuis() { + int i; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "player%i",i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + scoreBoard->SetStateInt( "rank_self", 0 ); + + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->hud ) { + continue; + } + player->hud->SetStateString( va( "player%i",i+1 ), "" ); + player->hud->SetStateString( va( "player%i_score", i+1 ), "" ); + player->hud->SetStateString( va( "player%i_ready", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + player->hud->SetStateInt( "rank_self", 0 ); + + player->hud->SetStateInt( "team", TEAM_MARINE ); + player->hud->HandleNamedEvent( "flagReturn" ); + player->hud->SetStateInt( "team", TEAM_STROGG ); + player->hud->HandleNamedEvent( "flagReturn" ); + } + + ClearVote(); +} + +/* +================ +idMultiplayerGame::GetPlayerRank +Returns the player rank (0 best), returning the best rank in the case of a tie +================ +*/ +int idMultiplayerGame::GetPlayerRank( idPlayer* player, bool& isTied ) { + int initialRank = -1; + int rank = -1; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rank = i; + initialRank = rank; + } + } + + if( rank == -1 ) { + return rank; + } + + if( rank > 0 ) { + if( rankedPlayers[ rank - 1 ].Second() == rankedPlayers[ rank ].Second() ) { + rank = rankedPlayers[ rank - 1 ].First()->GetRank(); + } else { + rank = rankedPlayers[ rank - 1 ].First()->GetRank() + 1; + } + } + + // check for tie + isTied = false; + + for( int i = rank - 1; i <= rank + 1; i++ ) { + if( i < 0 || i >= rankedPlayers.Num() || rankedPlayers[ i ].First() == player ) { + continue; + } + + if( rankedPlayers[ i ].Second() == rankedPlayers[ initialRank ].Second() ) { + isTied = true; + break; + } + } + + return rank; +} + +/* +================ +idMultiplayerGame::UpdatePlayerRanks +================ +*/ +void idMultiplayerGame::UpdatePlayerRanks( playerRankMode_t rankMode ) { + idEntity* ent = NULL; + + if( rankMode == PRM_AUTO ) { + if( gameLocal.IsTeamGame() ) { + rankMode = PRM_TEAM_SCORE_PLUS_SCORE; + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + rankMode = PRM_WINS; + } else { + rankMode = PRM_SCORE; + } + } + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer* player = (idPlayer*)ent; + + if ( !CanPlay( player ) ) { + unrankedPlayers.Append( player ); + } else { + int rankingValue = 0; + switch( rankMode ) { + case PRM_SCORE: { + rankingValue = GetScore( player ); + break; + } + case PRM_TEAM_SCORE: { + rankingValue = GetTeamScore( player ); + break; + } + case PRM_TEAM_SCORE_PLUS_SCORE: { + rankingValue = GetScore( player ) + GetTeamScore( player ); + break; + } + case PRM_WINS: { + rankingValue = GetWins( player ); + break; + } + default: { + gameLocal.Error( "idMultiplayerGame::UpdatePlayerRanks() - Bad ranking mode '%d'\n", rankMode ); + } + } + rankedPlayers.Append( rvPair(player, rankingValue ) ); + } + } + + qsort( rankedPlayers.Ptr(), rankedPlayers.Num(), rankedPlayers.TypeSize(), ComparePlayersByScore ); + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + bool tied; + rankedPlayers[ i ].First()->SetRank( GetPlayerRank( rankedPlayers[ i ].First(), tied ) ); + } + + for( int i = 0; i < unrankedPlayers.Num(); i++ ) { + unrankedPlayers[ i ]->SetRank( -1 ); + } +} + +/* +================ +idMultiplayerGame::UpdateTeamRanks +================ +*/ +void idMultiplayerGame::UpdateTeamRanks( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + rankedTeams[ i ] = rvPair( i, teamScore[ i ] ); + } + + qsort( rankedTeams, TEAM_MAX, sizeof( rvPair ), CompareTeamsByScore ); +} + +/* +================ +idMultiplayerGame::UpdateRankColor +================ +*/ +void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) { + for ( int j = 1; j < 4; j++ ) { + gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] ); + } +} + +/* +================ +idMultiplayerGame::CanCapture + +Determines if the given flag can be captured in the given gamestate +================ +*/ +bool idMultiplayerGame::CanCapture( int team ) { + // no AP's in one flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + return true; + } else if( gameLocal.gameType != GAME_CTF && gameLocal.gameType != GAME_ARENA_CTF ) { + return false; // no flag caps in none-CTF games + } + + if ( !assaultPoints.Num() ) { + return true; + } + + // since other logic ensures AP's are captured in order, we just need to check the last AP before the enemy flag + if ( team == TEAM_STROGG ) { + // AP 0 is always next to the marine flag + return ((rvCTFGameState*)gameState)->GetAPOwner( 0 ) == TEAM_STROGG; + } + if ( team == TEAM_MARINE ) { + // the last AP is always the one next to the strogg flag + return ((rvCTFGameState*)gameState)->GetAPOwner( assaultPoints.Num() - 1 ) == TEAM_MARINE; + } + + return false; +} + +void idMultiplayerGame::FlagCaptured( idPlayer *player ) { + if( !gameLocal.isClient ) { + AddTeamScore( player->team, 1 ); + AddPlayerTeamScore( player, 5 ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + float teamCashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_flagCapture", 0 ); + GiveCashToTeam( player->team, teamCashAward ); + + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagCapture", 0 ); + player->GiveCash( cashAward ); + } +// RITUAL END + + gameLocal.ClearForwardSpawns(); + + for( int i = 0; i < assaultPoints.Num(); i++ ) { + assaultPoints[ i ]->Reset(); + ((rvCTFGameState*)gameState)->SetAPOwner( i, AS_NEUTRAL ); + } + + statManager->FlagCaptured( player, OpposingTeam( player->team ) ); + player->SetEmote( PE_CHEER ); + } +} + +/* +================ +idMultiplayerGame::SendDeathMessage +================ +*/ +void idMultiplayerGame::SendDeathMessage( idPlayer* attacker, idPlayer* victim, int methodOfDeath, bool quadKill ) { + if( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DEATH ); + if( attacker ) { + outMsg.WriteByte( attacker->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ attacker->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + if( victim ) { + outMsg.WriteByte( victim->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ victim->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + outMsg.WriteByte( methodOfDeath ); + outMsg.WriteBits( quadKill, 1 ); + + gameLocal.ServerSendInstanceReliableMessage( victim, -1, outMsg ); + + if( gameLocal.isListenServer && gameLocal.GetLocalPlayer() && victim && gameLocal.GetLocalPlayer()->GetInstance() == victim->GetInstance() ) + { + // This is for listen servers, which won't get to ClientProcessReliableMessage + ReceiveDeathMessage( attacker, attacker ? playerState[ attacker->entityNumber ].fragCount : -1, victim, victim ? playerState[ victim->entityNumber ].fragCount : -1, methodOfDeath, quadKill ); + } + } +} + +/* +================ +idMultiplayerGame::ReceiveDeathMessage +================ +*/ +void idMultiplayerGame::ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath, bool quadKill ) { + idUserInterface *hud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->hud : NULL; + +// RITUAL BEGIN +// squirrel: force buy menu open when you die + //if( gameLocal.IsMultiplayer() && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && victim == gameLocal.GetLocalPlayer() ) + //{ + // OpenLocalBuyMenu(); + //} +// RITUAL END + + const char* icon = ""; + + // if methodOfDeath is in range [0, MAX_WEAPONS - 1] it refers to a specific weapon. MAX_WEAPONS refers to + // a generic or unknown death (i.e. "Killer killed victim") and values above MAX_WEAPONS + 1 refer + // to other non-weapon deaths (i.e. telefrags) + + // setup to either use weapon icons for a weapon death, or generic death icons + if ( methodOfDeath < MAX_WEAPONS ) { + icon = va( "w%02d", methodOfDeath ); + } else { + icon = va( "dm%d", methodOfDeath - MAX_WEAPONS ); + } + + char* message = NULL; + + if ( gameLocal.IsTeamGame() ) { + idStr attackerStr( ( attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "" ) ); + idStr victimStr( ( victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "" ) ); + + attackerStr.RemoveEscapes(); + victimStr.RemoveEscapes(); + + message = va ( "%s%s ^r%s^i%s %s%s", (attacker ? (attacker->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + attackerStr.c_str(), + quadKill ? "^iqad" : "", + icon, + (victim ? (victim->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + victimStr.c_str() ); + } else { + message = va ( "%s ^r%s^i%s %s", (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : ""), + quadKill ? "^iqad" : "", + icon, + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "") ); + } + + if( hud ) { + hud->SetStateString ( "deathinfo", message ); + hud->HandleNamedEvent ( "addDeathLine" ); + } + + // echo to console + gameLocal.Printf( gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->spawnArgs.GetString( va( "%s_text", icon ), "%s killed %s" ) : "%s killed %s", + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "world"), + (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "world") ); + gameLocal.Printf( "\n" ); + + // display message on hud + if( attacker && victim && (gameLocal.GetLocalPlayer() == attacker || gameLocal.GetLocalPlayer() == victim) && attacker != victim && methodOfDeath < MAX_WEAPONS ) { + if( gameLocal.GetLocalPlayer() == attacker ) { +// RAVEN BEGIN +// rhummer: Added lang entries for "You fragged %s" and "You were fragged by %s" + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107295" ), gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) ) ); + } else { + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107296" ), gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + + if( gameLocal.gameType == GAME_DM ) { + // print rank text next time after we update scores + + // stash the scores on the client so we can print accurate rank info + if( gameLocal.isClient ) { + if( victim ) { + playerState[ victim->entityNumber ].fragCount = victimScore; + } + + if( attacker ) { + playerState[ attacker->entityNumber ].fragCount = attackerScore; + } + } + + if( victim && (gameLocal.GetLocalPlayer() == victim || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == victim->entityNumber)) ) { + rankTextPlayer = victim; + } + + if( attacker && (gameLocal.GetLocalPlayer() == attacker || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == attacker->entityNumber)) ) { + rankTextPlayer = attacker; + } + } + } +} + + +// ddynerman: Gametype specific scoreboard +/* +================ +idMultiplayerGame::UpdateScoreboard +================ +*/ +void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard ) { + scoreBoard->SetStateInt( "gametype", gameLocal.gameType ); + + //statManager->UpdateInGameHud( scoreBoard, true ); + + if( gameLocal.IsTeamGame() ) { + UpdateTeamScoreboard( scoreBoard ); + } else { + UpdateDMScoreboard( scoreBoard ); + } + + return; +} + +/* +================ +idMultiplayerGame::UpdateDMScoreboard +================ +*/ +void idMultiplayerGame::UpdateDMScoreboard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + int i; + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if ( !player ) { + return; + } + + scoreBoard->SetStateString( "scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + if( gameLocal.gameType == GAME_DM ) { + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + } else if( gameLocal.gameType == GAME_TOURNEY ) { + // loop through twice listing players who are playing, then players who have been eliminated + int listIndex = 0; + + + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), false ); + listIndex++; + } + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() != PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), true ); + listIndex++; + } + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%s\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + playerState[ unrankedPlayers[ i ]->entityNumber ].ping, // ping + "" ) ); + } else { + scoreBoard->SetStateString( va( "spectator_scores_item_%i", i ), "" ); + } + } + + for( i = listIndex; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "scores_item_%i", i ), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() + unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); + + scoreBoard->SetStateString( "position_text", GetPlayerRankText( player ) ); + // shouchard: added map name + // mekberg: localized string + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) == 1 ) { + // stupid english plurals + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107712" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } else { + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107715" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "tourney_count", va( common->GetLocalizedString( "#str_107713" ), ((rvTourneyGameState*)gameState)->GetTourneyCount(), gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) ); + if( player ) { + inNonTimedState |= ((rvTourneyGameState*)gameState)->GetArena( player->GetArena() ).GetState() == AS_SUDDEN_DEATH; + } + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || inNonTimedState ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTeamScoreboard +================ +*/ + +// only output 16 clients onto the scoreboard +#define SCOREBOARD_MAX_CLIENTS 16 + +void idMultiplayerGame::UpdateTeamScoreboard( idUserInterface *scoreBoard ) { + idStr gameinfo; + int numTeamEntries[ TEAM_MAX ]; + idPlayer* player = gameLocal.GetLocalPlayer(); + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if ( !player ) { + return; + } + + SIMDProcessor->Memset( numTeamEntries, 0, sizeof( int ) * TEAM_MAX ); + + scoreBoard->SetStateString( "team_0_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "team_1_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + + for ( int i = 0; i < SCOREBOARD_MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( va("team_%i_scores_sel_0", rankedPlayer->team ), numTeamEntries[ rankedPlayer->team ] ); + } + +// RAVEN BEGIN +// mekberg: redid this + if ( gameLocal.gameType == GAME_TDM ) + { + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } + //else if ( gameLocal.gameType == GAME_DEADZONE ) + //{ + // // mekberg: made this check slightly more sane. + // const char* flagString = ""; + // if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + // flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + // } else if ( gameLocal.gameType == GAME_ARENA_CTF && player && rankedPlayer->team == player->team ) { + // flagString = rankedPlayer->GetArenaPowerupString( ); + // } + // scoreBoard->SetStateString ( + // va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + // va("%s\t%s\t%s\t%s\t%s\t%s\t%.01f\t%i\t%i\t%i\t", + // ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + // ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + // ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + // flagString, // shouchard: twhitaker: updated steve's original flag system + // rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + // rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + // rankedScore * 0.1f, // score + // playerState[ rankedPlayer->entityNumber ].fragCount, // kills + // GetPlayerTime( rankedPlayer ), // time + // playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + // numTeamEntries[ rankedPlayer->team ]++; + //} + else + { + // mekberg: made this check slightly more sane. + const char* flagString = ""; + if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + } else if ( gameLocal.gameType == GAME_ARENA_CTF && player && rankedPlayer->team == player->team ) { + flagString = rankedPlayer->GetArenaPowerupString( ); + } + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + flagString, // shouchard: twhitaker: updated steve's original flag system + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].fragCount, // kills + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } +// RAVEN END + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + +// RAVEN BEGIN +// mekberg: redid this + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectating && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping // ping +// RAVEN END + + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + } + } + + // clear unused space + for( int k = 0; k < TEAM_MAX; k++ ) { + for( int i = numTeamEntries[ k ]; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString ( va("team_%i_scores_item_%i", k, i), "" ); + } + } + + scoreBoard->SetStateInt ( "playerteam", player ? player->team : TEAM_NONE ); + + scoreBoard->SetStateInt ( "strogg_score", teamScore[ TEAM_STROGG ] ); + scoreBoard->SetStateInt ( "marine_score", teamScore[ TEAM_MARINE ] ); + scoreBoard->SetStateInt ( "num_strogg_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] ) ); + scoreBoard->SetStateInt ( "num_marine_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] + unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); +// RAVEN BEGIN +// shouchard: added map name +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); +// RAVEN END + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + if ( gameLocal.IsFlagGameType() ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ); + } else if ( gameLocal.gameType == GAME_DEADZONE ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_122008" ), gameLocal.serverInfo.GetInt( "si_controlTime" ) ) ); + } else { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || state == WARMUP || state == GAMEREVIEW || state == SUDDENDEATH ); + + scoreBoard->StateChanged( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::BuildSummaryListString +Returns a summary string for the specified player +================ +*/ +const char* idMultiplayerGame::BuildSummaryListString( idPlayer* player, int rankedScore ) { + // track top 3 accuracies + rvPlayerStat* stat = statManager->GetPlayerStat( player->entityNumber ); + idList > bestAccuracies; + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + // only consider weapons we fired more than a few shots + if( stat->weaponShots[ j ] <= 10 ) { + continue; + } + + float accuracy = (float)stat->weaponHits[ j ] / (float)stat->weaponShots[ j ]; + bestAccuracies.Append( rvPair( j, accuracy ) ); + } + + bestAccuracies.Sort( rvPair::rvPairSecondCompareDirect ); + + // hold upto 3 top weapons at 5 chars each + idStr weaponString; + for( int j = 0; j < 3; j++ ) { + if( j >= bestAccuracies.Num() ) { + continue; + } + + weaponString += va( "^iw%02d", bestAccuracies[ j ].First() ); + } + + return va("%d. %s\t%s\t%d\t%s\t", + player->GetRank() + 1, + player->GetUserInfo()->GetString( "ui_name" ), // name + player->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + weaponString.c_str() ); +} + +/* +================ +idMultiplayerGame::UpdateSummaryBoard +Shows top 10 players if local player is in top 10, otherwise shows top 9 and localplayer +================ +*/ +void idMultiplayerGame::UpdateSummaryBoard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + int playerIndex = -1; + + // update our ranks in case we call this the same frame it happens + UpdatePlayerRanks(); + + // highlight top 3 players + idVec4 blueHighlight = idStr::ColorForIndex( C_COLOR_BLUE ); + idVec4 redHighlight = idStr::ColorForIndex( C_COLOR_RED ); + idVec4 yellowHighlight = idStr::ColorForIndex( C_COLOR_YELLOW ); + blueHighlight[ 3 ] = 0.15f; + redHighlight[ 3 ] = 0.15f; + yellowHighlight[ 3 ] = 0.15f; + + if( gameLocal.IsTeamGame() ) { + scoreBoard->HandleNamedEvent( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? "marine_wins" : "strogg_wins" ); + // summary is top 5 players on each team + int lastHighIndices[ TEAM_MAX ]; + memset( lastHighIndices, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < 5; i++ ) { + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_marine_names", i ), "" ); + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_strogg_names", i ), "" ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + for( int j = 0; j < 5; j++ ) { + idPlayer* rankedPlayer = NULL; + int rankedScore = 0; + int k; + for( k = lastHighIndices[ i ]; k < rankedPlayers.Num(); k++ ) { + if( rankedPlayers[ k ].First()->team == i ) { + rankedPlayer = rankedPlayers[ k ].First(); + rankedScore = rankedPlayers[ k ].Second(); + break; + } + } + + // no more teammates + if( k >= rankedPlayers.Num() ) { + break; + } + + if( j == 4 && playerIndex == -1 && player->team == i ) { + int z; + for( z = 0; z < rankedPlayers.Num(); z++ ) { + if( rankedPlayers[ z ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ z ].Second(); + break; + } + } + } + + if ( rankedPlayer == player ) { + // highlight who we are + playerIndex = j; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", i == TEAM_MARINE ? "summary_marine_names" : "summary_strogg_names", j ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + lastHighIndices[ i ] = k + 1; + } + } + + if( playerIndex > 0 ) { + if( player->team == TEAM_MARINE ) { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + } + } else { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } + } else { + for ( int i = 0; i < 10; i++ ) { + + // mekberg: delete old highlights + scoreBoard->DeleteStateVar( va( "summary_names_item_%d_highlight", i ) ); + + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( i == 9 && playerIndex == -1 ) { + // if the player is ranked, substitute them in + int i; + for( i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ i ].Second(); + break; + } + } + } + + if ( rankedPlayer == player ) { + // highlight who we are + playerIndex = i; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_names", i ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + if( rankedPlayer->GetRank() == 0 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), blueHighlight ); + } else if( rankedPlayer->GetRank() == 1 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), redHighlight ); + } else if( rankedPlayer->GetRank() == 2 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), yellowHighlight ); + } + } else { + scoreBoard->SetStateString ( va("summary_names_item_%i", i), "" ); + } + } + + // highlight who we are (only if not ranked in the top 3) + if( player->GetRank() >= 0 && player->GetRank() < 3 ) { + scoreBoard->SetStateInt( "summary_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_names_sel_0", playerIndex ); + } + } + + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTestScoreboard +================ +*/ +void idMultiplayerGame::UpdateTestScoreboard ( idUserInterface *scoreBoard ) { + int i; + + gameLocal.random.SetSeed ( g_testScoreboard.GetInteger ( ) ); + + if( gameLocal.IsTeamGame() ) { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), "" ); + i++; + } + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), "" ); + i++; + } + + scoreBoard->SetStateInt ( "strogg_score", gameLocal.random.RandomInt ( 10 ) ); + scoreBoard->SetStateInt ( "marine_score", gameLocal.random.RandomInt ( 10 ) ); + } else { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( gameLocal.random.RandomInt() % 2 ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( gameLocal.random.RandomInt() % 2 ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + "", // shouchard: flag + name.c_str(), // name + "Clan", // clan + "", // team score (unused in DM) + gameLocal.random.RandomInt ( 50 ), // score + gameLocal.random.RandomInt ( 10 ), // time + gameLocal.random.RandomInt ( 300 ) + 20 ) ); + + + } + // clear remaining lines (empty slots) + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + i++; + } + } + + scoreBoard->SetStateInt ( "num_marine_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_strogg_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_players", g_testScoreboard.GetInteger() ); + + scoreBoard->SetStateInt( "rank_self", 2 ); + scoreBoard->SetStateInt ( "playercount", g_testScoreboard.GetInteger ( ) ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->SetStateString( "gameinfo", va( "Game Type:%s Frag Limit:%i Time Limit:%i", gameLocal.serverInfo.GetString( "si_gameType" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->Redraw( gameLocal.time ); +} +// RAVEN END + +/* +================ +idMultiplayerGame::GameTime +================ +*/ +const char *idMultiplayerGame::GameTime( void ) { + static char buff[32]; + int m, s, t, ms; + + bool inCountdown = false; + + ms = 0; + if( gameState->GetMPGameState() == COUNTDOWN ) { + inCountdown = true; + ms = gameState->GetNextMPGameStateTime() - gameLocal.realClientTime; + } else if( gameLocal.GetLocalPlayer() && gameLocal.gameType == GAME_TOURNEY && ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_WARMUP ) { + inCountdown = true; + ms = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetNextStateTime() - gameLocal.realClientTime; + } + if ( inCountdown ) { + s = ms / 1000 + 1; + if ( ms <= 0 ) { + // in tourney mode use a different string since warmups happen before each round + // (not really before the overall game) + idStr::snPrintf( buff, sizeof( buff ), "%s --", ( gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY ) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ) ); + } else { + idStr::snPrintf( buff, sizeof( buff ), "%s %i", (gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ), s ); + } + } else { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int startTime = matchStartedTime; + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.GetLocalPlayer() ) { + startTime = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetMatchStartTime(); + } + } + if ( timeLimit ) { + ms = ( timeLimit * 60000 ) - ( gameLocal.time - startTime ); + } else { + ms = gameLocal.time - startTime; + } + if ( ms < 0 ) { + ms = 0; + } + + s = ms / 1000; + m = s / 60; + s -= m * 60; + t = s / 10; + s -= t * 10; + + sprintf( buff, "%i:%i%i", m, t, s ); + } + return &buff[0]; +} + +/* +================ +idMultiplayerGame::NumActualClients +================ +*/ +int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) { + idPlayer *p; + int c = 0; + + if ( teamcounts ) { + teamcounts[ 0 ] = teamcounts[ 1 ] = 0; + } + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + p = static_cast< idPlayer * >( ent ); + if ( countSpectators || CanPlay( p ) ) { + c++; + } + if ( teamcounts && CanPlay( p ) ) { + teamcounts[ p->team ]++; + } + } + return c; +} + +/* +================ +idMultiplayerGame::EnoughClientsToPlay +================ +*/ +bool idMultiplayerGame::EnoughClientsToPlay() { + int team[ 2 ]; + int clients = NumActualClients( false, &team[ 0 ] ); + if ( gameLocal.IsTeamGame() ) { + return clients >= 2 && team[ 0 ] && team[ 1 ]; + } else { + return clients >= 2; + } +} + +/* +================ +idMultiplayerGame::AllPlayersReady +================ +*/ +bool idMultiplayerGame::AllPlayersReady( idStr* reason ) { + int i, minClients, numClients; + idEntity *ent; + idPlayer *p; + int team[ 2 ]; + bool notReady; + + notReady = false; + + minClients = Max( 2, gameLocal.serverInfo.GetInt( "si_minPlayers" ) ); + numClients = NumActualClients( false, &team[ 0 ] ); + if ( numClients < minClients ) { + if( reason ) { + // stupid english plurals + if( minClients == 2 ) { + *reason = common->GetLocalizedString( "#str_107674" ); + } else { + *reason = va( common->GetLocalizedString( "#str_107732" ), minClients - numClients ); + } + + } + + return false; + } + + if ( gameLocal.IsTeamGame() ) { + if ( !team[ 0 ] || !team[ 1 ] ) { + if( reason ) { + *reason = common->GetLocalizedString( "#str_107675" ); + } + + return false; + } + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + p = static_cast< idPlayer * >( ent ); + + if ( CanPlay( p ) && !p->IsReady() ) { + notReady = true; + } + team[ p->team ]++; + } + + if( notReady ) { + if( reason ) { + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() ) { + // Tourney has a different hud layout, so needs a different "you are (not)ready" string + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110018" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107711" ), common->KeysFromBinding( "_impulse17" ) ); + } + } else if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110017" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107710" ), common->KeysFromBinding( "_impulse17" ) ); + } + } + } + return false; + } + + return true; +} + +/* +================ +idMultiplayerGame::FragLimitHit +return the winning player (team player) +if there is no FragLeader(), the game is tied and we return NULL +================ +*/ +idPlayer *idMultiplayerGame::FragLimitHit() { + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + idPlayer *leader = NULL; + + if ( fragLimit <= 0 ) { + return NULL; // fraglimit disabled + } + + leader = FragLeader(); + if ( !leader ) { + return NULL; + } + + if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) { + return leader; + } + + return NULL; +} + +/* +================ +idMultiplayerGame::TimeLimitHit +================ +*/ +bool idMultiplayerGame::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +================ +idMultiplayerGame::FragLeader +return the current winner +NULL if even +relies on UpdatePlayerRanks() being called earlier in frame to sort players +================ +*/ +idPlayer* idMultiplayerGame::FragLeader( void ) { + if( rankedPlayers.Num() < 2 ) { + return NULL; + } + + // mark leaders + int i; + int high = GetScore( rankedPlayers[ 0 ].First() ); + idPlayer* p; + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + p = rankedPlayers[ i ].First(); + if ( !p ) { + continue; + } + p->SetLeader( false ); + + if ( !CanPlay( p ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY ) { + continue; + } + if ( p->spectating ) { + continue; + } + + if ( GetScore( p ) >= high ) { + p->SetLeader( true ); + } + } + + if( gameLocal.IsTeamGame() ) { + // in a team game, find the first player not on the leader's team, and make sure they aren't tied + int i = 0; + while( i < rankedPlayers.Num() && rankedPlayers[ i ].First()->team == rankedPlayers[ 0 ].First()->team ) { + i++; + } + if( i < rankedPlayers.Num() ) { + if( GetScore( rankedPlayers[ i ].First()->entityNumber ) == GetScore( rankedPlayers[ 0 ].First()->entityNumber ) ) { + return NULL; + } + } + } else if( GetScore( rankedPlayers[ 0 ].First()->entityNumber ) == GetScore( rankedPlayers[ 1 ].First()->entityNumber ) ) { + return NULL; + } + + return rankedPlayers[ 0 ].First(); +} + +/* +================ +idMultiplayerGame::PlayerDeath +================ +*/ +void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ) { + // don't do PrintMessageEvent + assert( !gameLocal.isClient ); + + if ( killer ) { + if ( gameLocal.IsTeamGame() ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + + // in flag games, we subtract suicides from team-score rather than player score, which is the true + // kill count + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( killer == dead ? dead : killer, -1 ); + } else { + AddPlayerScore( killer == dead ? dead : killer, -1 ); + } + + } else { + // mark a kill + AddPlayerScore( killer, 1 ); + } + + // additional CTF points + if( gameLocal.IsFlagGameType() ) { + if( dead->PowerUpActive( killer->team ? POWERUP_CTF_STROGGFLAG : POWERUP_CTF_MARINEFLAG ) ) { + AddPlayerTeamScore( killer, 2 ); + } + } + if( gameLocal.gameType == GAME_TDM ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + AddTeamScore( killer->team, -1 ); + } else { + AddTeamScore( killer->team, 1 ); + } + } + } else { + // in tourney mode, we don't award points while in the waiting arena + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameState)->GetArena( killer->GetArena() ).GetState() != AS_WARMUP ) { + AddPlayerScore( killer, ( killer == dead ) ? -1 : 1 ); + } + + // in tourney mode, frags track performance over the entire level load, team score keeps track of + // individual rounds + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( killer, ( killer == dead ) ? -1 : 1 ); + } + } + } else { + // e.g. an environmental death + + // flag gametypes subtract points from teamscore, not playerscore + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( dead, -1 ); + } else { + AddPlayerScore( dead, -1 ); + } + + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( dead, -1 ); + } + if( gameLocal.gameType == GAME_TDM ) { + AddTeamScore( dead->team, -1 ); + } + } + + SendDeathMessage( killer, dead, methodOfDeath, killer ? killer->PowerUpActive( POWERUP_QUADDAMAGE ) : false ); + + statManager->Kill( dead, killer, methodOfDeath ); + +// RAVEN BEGIN +// shouchard: hack for CTF drop messages for listen servers + if ( dead == gameLocal.GetLocalPlayer() && + dead->PowerUpActive( dead->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + if ( dead->mphud ) { + dead->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + dead->mphud->HandleNamedEvent( "main_notice" ); + } + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::PlayerStats +================ +*/ +void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) { + + idEntity *ent; + int team; + + *data = 0; + + // make sure we don't exceed the client list + if ( clientNum < 0 || clientNum > gameLocal.numClients ) { + return; + } + + // find which team this player is on + ent = gameLocal.entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + team = static_cast< idPlayer * >(ent)->team; + } else { + return; + } + + idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount ); +} + +/* +================ +idMultiplayerGame::PlayerVote +================ +*/ +void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) { + playerState[ clientNum ].vote = vote; +} + +/* +================ +idMultiplayerGame::ExecuteVote +the votes are checked for validity/relevance before they are started +we assume that they are still legit when reaching here +================ +*/ +void idMultiplayerGame::ExecuteVote( void ) { + bool needRestart; + ClearVote(); + switch ( vote ) { + case VOTE_RESTART: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_TIMELIMIT: + si_timeLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_FRAGLIMIT: + si_fragLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_GAMETYPE: + cvarSystem->SetCVarString( "si_gametype", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_KICK: + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) ); + break; + case VOTE_MAP: + cvarSystem->SetCVarString( "si_map", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_BUYING: + cvarSystem->SetCVarString( "si_isBuyingEnabled", voteValue ); + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; +// RAVEN BEGIN +// shouchard: added capture limit + case VOTE_CAPTURELIMIT: + si_captureLimit.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + // todo: round limit here (if we add it) + case VOTE_AUTOBALANCE: + si_autobalance.SetInteger( atoi( voteValue ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + break; + case VOTE_MULTIFIELD: + ExecutePackedVote(); + break; +// RAVEN END + case VOTE_CONTROLTIME: + si_controlTime.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + case VOTE_NEXTMAP: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" ); + break; + } +} + +/* +================ +idMultiplayerGame::CheckVote +================ +*/ +void idMultiplayerGame::CheckVote( void ) { + int numVoters, i; + + if ( vote == VOTE_NONE ) { + return; + } + + if ( voteExecTime ) { + if ( gameLocal.time > voteExecTime ) { + voteExecTime = 0; + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + ExecuteVote(); + vote = VOTE_NONE; + } + return; + } + + // count voting players + numVoters = 0; + + for ( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) { + numVoters++; + } + } + if ( !numVoters ) { + // abort + vote = VOTE_NONE; + ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes, currentVoteData ); + return; + } + if ( float(yesVotes) / numVoters > 0.5f ) { + ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes, currentVoteData ); + voteExecTime = gameLocal.time + 2000; + return; + } + if ( gameLocal.time > voteTimeOut || float(noVotes) / numVoters >= 0.5f ) { + ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes, currentVoteData ); + vote = VOTE_NONE; + return; + } +} + +// RAVEN BEGIN +// shouchard: multifield voting here + +/* +================ +idMultiplayerGame::ClientCallPackedVote + +The assumption is that the zero changes case has been handled above. +================ +*/ +void idMultiplayerGame::ClientCallPackedVote( const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( 0 != voteData.m_fieldFlags ); + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ServerCallPackedVote +================ +*/ +void idMultiplayerGame::ServerCallPackedVote( int clientNum, const idBitMsg &msg ) { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + assert( -1 != clientNum ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + // this is set to false if an invalid parameter is asked for-- time limit of -1, or frag limit of "jeff" or whatever. + // if it's a multivote, it may still be valid, but this value is only checked if there are no vote parameters changed. + bool validVote = true; + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + + // flags (short) + voteData.m_fieldFlags = msg.ReadShort(); + + // clear any unallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if ( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // kick + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + if ( voteData.m_kick == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_KICK ); + } + } + + // map (string) + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + char buffer[128]; + msg.ReadString( buffer, sizeof( buffer ) ); + voteData.m_map = buffer; + if ( 0 == idStr::Icmp( buffer, si_map.GetString() ) ) { + //gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map is the same as current map." ); + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + + // because of addon pk4's clients may submit votes for maps the server doesn't have - audit here + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if( !mapDict ) { + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map does not exist on the server" ); + } + } + + // gametype + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + const char *voteString = VoteGameTypeToString( voteData.m_gameType ); + if ( !idStr::Icmp( voteString, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + + if ( voteData.m_fieldFlags & VOTEFLAG_MAP ) { + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if ( !mapDict || !mapDict->GetInt( voteString ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "gametype incompatible with map" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + } + } else { + if ( voteData.m_fieldFlags & VOTEFLAG_MAP ) { + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if ( !mapDict || !mapDict->GetInt( si_gameType.GetString() ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "map incompatible with gametype" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + } + } + + // timelimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + if ( voteData.m_timeLimit < si_timeLimit.GetMinValue() || voteData.m_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %d\n", clientNum, voteData.m_timeLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + if ( voteData.m_timeLimit == si_timeLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + } + + // tourneylimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + if ( voteData.m_tourneyLimit < si_tourneyLimit.GetMinValue() || voteData.m_tourneyLimit > si_tourneyLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104261" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + if ( voteData.m_tourneyLimit == si_tourneyLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104260" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + } + + // capture limit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + if ( voteData.m_captureLimit < si_captureLimit.GetMinValue() || voteData.m_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: caplimit value out of range for vote: %d\n", clientNum, voteData.m_captureLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + if ( voteData.m_captureLimit == si_captureLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + } + + // fraglimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + if ( voteData.m_fragLimit < si_fragLimit.GetMinValue() || voteData.m_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %d\n", clientNum, voteData.m_fragLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + if ( voteData.m_fragLimit == si_fragLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + } + + // spectators +/* if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_SPECTATORS ) ) { + voteData.m_spectators = msg.ReadByte(); + if ( voteData.m_spectators == si_spectators.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104421" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_SPECTATORS ); + } + } */ + + // buying + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadShort(); + if ( voteData.m_buying == si_isBuyingEnabled.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + validVote = false; + voteData.m_buying &= ( ~VOTEFLAG_BUYING ); + } + } + + // autobalance teams + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + if ( voteData.m_teamBalance == si_autobalance.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TEAMBALANCE ); + } + } + + // control time + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + if ( voteData.m_controlTime == si_controlTime.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CONTROLTIME ); + } + } + + // check for no changes at all + if ( 0 == voteData.m_fieldFlags ) { + // If the vote was called empty, announce there were no valid changes. Otherwise, say nothing, there's already been a warning message. + if( validVote ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104400" ); + } + return; + } + + ServerStartPackedVote( clientNum, voteData ); + ClientStartPackedVote( clientNum, voteData ); +} + +/* +================ +idMultiplayerGame::ClientStartPackedVote +================ +*/ +void idMultiplayerGame::ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idUserInterface * mpHud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->mphud : NULL; + + assert( 0 != voteData.m_fieldFlags ); + + if ( !gameLocal.isListenServer && !gameLocal.isClient ) { + return; + } + + // "%s has called a vote!" + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + + // display the vote called text on the hud and play an announcer sound + if ( mpHud ) { + mpHud->SetStateInt( "voteNotice", 1 ); + } + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + currentVoteData = voteData; + + // push data to the interface + if ( mpHud && mainGui ) { + int voteLineCount = 1; + int menuVoteLineCount = 0; + bool kickActive = false; + bool maxWindows = false; + idStr yesKey = common->KeysFromBinding("_impulse28"); + + mainGui->SetStateInt( "vote_going", 1 ); + + //dynamic vote yes/no box + mpHud->SetStateString( "voteNoticeText", va( common->GetLocalizedString( "#str_107242" ), yesKey.c_str(), common->KeysFromBinding("_impulse29") )); + + // kick should always be the highest one + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + // mpGui here, not mpHud + //mpHud->SetStateString( "vote_data0", va( common->GetLocalizedString( "#str_104422" ), player->GetName() ); + kickActive = true; + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + + const char *mapName = currentVoteData.m_map.c_str(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = common->GetLocalizedString( "#str_110011" ); + switch( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = common->GetLocalizedString( "#str_110012" ); + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = common->GetLocalizedString( "#str_110013" ); + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = common->GetLocalizedString( "#str_110014" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = common->GetLocalizedString( "#str_110015" ); + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = common->GetLocalizedString( "#str_110011" ); + break; + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + + //jshep: max of 7 windows and the 7th is always "..." + if( maxWindows ) { + mpHud->SetStateString( "voteInfo_7", "..." ); + } + + mainGui->DeleteStateVar( va( "voteData_item_%d", menuVoteLineCount ) ); + mainGui->SetStateInt( "vote_going", 1 ); + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), yesVotes, noVotes ) ); + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerStartPackedVote +================ +*/ +void idMultiplayerGame::ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( vote == VOTE_NONE ); + + if ( !gameLocal.isServer ) { + return; + } + + // #13705: clients passing a vote during server restart could abuse the voting system into passing the vote right away after the new map loads + if ( !playerState[ clientNum ].ingame ) { + common->Printf( "ignore vote called by client %d: not in game\n", clientNum ); + return; + } + + // setup + yesVotes = 1; + noVotes = 0; + vote = VOTE_MULTIFIELD; + currentVoteData = voteData; + voteTimeOut = gameLocal.time + 30000; // 30 seconds? might need to be longer because it requires fiddling with the GUI + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); +} + +/* +================ +idMultiplayerGame::ExecutePackedVote +================ +*/ +void idMultiplayerGame::ExecutePackedVote( void ) { + assert( VOTE_MULTIFIELD == vote ); + + if ( 0 == currentVoteData.m_fieldFlags ) { + return; + } + + bool needRestart = false; + bool needNextMap = false; + bool needRescanSI = false; + + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + si_isBuyingEnabled.SetInteger( currentVoteData.m_buying ); + needRescanSI = true; + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + si_autobalance.SetInteger( currentVoteData.m_teamBalance ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + si_controlTime.SetInteger( currentVoteData.m_controlTime ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + ShuffleTeams(); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d", currentVoteData.m_kick ) ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + si_map.SetString( currentVoteData.m_map.c_str() ); + needNextMap = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = VoteGameTypeToString( currentVoteData.m_gameType ); + //jshepard: Currently the DM gametypes can be played on any map. The other gametypes require specially configured maps. + //if further gametypes are added that can be played on any map, don't set the "runPickMap" flag. + bool runPickMap = (idStr::Cmp( gameTypeString, "DM" ) != 0) ? true : false; + + si_gameType.SetString( gameTypeString ); + //jshepard: run a pick map here in case the packed vote is trying to pick the wrong map type. + //PickMap returns true if the map has changed (requiring a nextMap call) + if( runPickMap ) { + if( PickMap( gameTypeString ) ) { + needNextMap = true; + } else { + needRestart = true; + } + + const idDict *mapDict = fileSystem->GetMapDecl( si_map.GetString() ); + if ( !mapDict || !mapDict->GetInt( gameTypeString ) ) { + gameLocal.Warning( "server voted to gametype with no maps; resetting gametype to DM." ); + si_gameType.SetString( "DM" ); + needNextMap = false; + needRestart = true; + } + } else { + needRestart = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + si_timeLimit.SetInteger( currentVoteData.m_timeLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + si_tourneyLimit.SetInteger( currentVoteData.m_tourneyLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + si_captureLimit.SetInteger( currentVoteData.m_captureLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + si_fragLimit.SetInteger( currentVoteData.m_fragLimit ); + needRescanSI = true; + } + + if ( needRescanSI ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + if ( needNextMap ) { + gameLocal.sessionCommand = "nextMap"; + } + else if ( needRestart || gameLocal.NeedRestart() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } +} + +// RAVEN END + +/* +================ +idMultiplayerGame::SendMapList +================ +*/ +void idMultiplayerGame::SendMapList( int clientNum ) { + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int i; + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETVOTEMAPS ); + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + + const char *mapName = dict->GetString( "path" ); + assert( mapName[ 0 ] != '\0' ); + outMsg.WriteString( mapName ); + } + outMsg.WriteString( "" ); + + if ( gameLocal.localClientNum == clientNum ) { + outMsg.BeginReading(); + outMsg.ReadByte(); + ReadMapList( outMsg ); + } else { + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + } +} + +/* +================ +idMultiplayerGame::ReadMapList +================ +*/ +void idMultiplayerGame::ReadMapList( const idBitMsg &msg ) { + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + char path[ MAX_STRING_CHARS ]; + int i; + + voteMapDecls.Clear(); + + while ( msg.ReadString( path, MAX_STRING_CHARS ) > 0 ) { + // find the local decl for the path + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + + if ( !idStr::Icmp( path, dict->GetString( "path" ) ) ) { + break; + } + } + if ( i >= numMaps ) { + // ignore maps we don't already have + continue; + } + + voteMapDecls.Append( i ); + } + + // update any map requests that triggered this + int flags = voteMapsWaiting; + + voteMapsWaiting = 0; + + if ( flags & VOTEMAPS_WAITING_MAPLIST ) { + SetVoteMapList(); + } + + if ( flags & VOTEMAPS_WAITING_SAMAPLIST ) { + SetSAMapList(); + } + + if ( flags & VOTEMAPS_WAITING_LISTMAPS ) { + ListMaps(); + } +} + +/* +================ +idMultiplayerGame::RequestVoteMaps +================ +*/ +bool idMultiplayerGame::RequestVoteMaps( int flags ) { + if ( voteMapDecls.Num() > 0 ) { + return true; + } + + if ( gameLocal.isServer || !gameLocal.isClient ) { + int i; + int numMaps = fileSystem->GetNumMaps(); + + voteMapDecls.Clear(); + for (i = 0; i < numMaps; i++) { + voteMapDecls.Append( i ); + } + return true; + } + + if ( voteMapsWaiting ) { + return false; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETVOTEMAPS ); + networkSystem->ClientSendReliableMessage( outMsg ); + + voteMapsWaiting |= flags; + + return false; +} + +/* +================ +idMultiplayerGame::ListMaps +================ +*/ +void idMultiplayerGame::ListMaps( void ) { + if ( !RequestVoteMaps( VOTEMAPS_WAITING_LISTMAPS ) ) { + gameLocal.Printf( "Requesting map list...\n" ); + return; + } + + int i; + int numMaps = voteMapDecls.Num(); + + for (i = 0; i < numMaps; i++) { + const idDict *dict = fileSystem->GetMapDecl( voteMapDecls[ i ] ); + gameLocal.Printf( "%s", dict->GetBool( "DM" ) ? "DM " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Team DM" ) ? "TDM " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "CTF" ) ? "CTF " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Arena CTF" ) ? "ACTF " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Tourney" ) ? "Trn " : " " ); + gameLocal.Printf( "%-20s %s\n", dict->GetString( "path" ), common->GetLocalizedString( dict->GetString( "name" ) ) ); + } +} + +/* +================ +idMultiplayerGame::SetMapList +================ +*/ +void idMultiplayerGame::SetMapList( const char *listName, const char *mapName, int gameTypeInt ) { + int numMaps = voteMapDecls.Num(); + const idDict *dict; + int numMapsAdded = 0; + int i; + + if ( !RequestVoteMaps( !idStr::Cmp( listName, "mapList" ) ? VOTEMAPS_WAITING_MAPLIST : VOTEMAPS_WAITING_SAMAPLIST ) ) { + return; + } + + const char *gameType = VoteGameTypeToString( gameTypeInt ); + + idStr originalMapName = gameLocal.serverInfo.GetString( "si_map" ); + originalMapName.StripFileExtension(); + + bool foundOriginalMap = false; + int originalMapIndex = -1; + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( voteMapDecls[ i ] ); + + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + + if ( idStr::Icmp(dict->GetString( "path" ), originalMapName) == 0 ) { + foundOriginalMap = true; + originalMapIndex = numMapsAdded; + } + + mainGui->SetStateString( va( "%s_item_%d", listName, numMapsAdded), mapName ); + mainGui->SetStateInt( va( "%s_item_%d_id", listName, numMapsAdded), voteMapDecls[ i ] ); + + numMapsAdded++; + } + } + + mainGui->DeleteStateVar( va( "%s_item_%d", listName, numMapsAdded ) ); + + if ( !foundOriginalMap ) { + mainGui->SetStateInt( va( "%s_sel_0", listName ), 0 ); + mainGui->SetStateString( mapName, mainGui->GetStateString( va( "%s_item_0", listName ) ) ); + } else { + mainGui->SetStateInt( va( "%s_sel_0", listName ), originalMapIndex ); + mainGui->SetStateString( mapName, mainGui->GetStateString( va( "%s_item_%d", listName, originalMapIndex ) ) ); + } +} + +/* +================ +idMultiplayerGame::SetVoteMapList +================ +*/ +void idMultiplayerGame::SetVoteMapList( void ) { + SetMapList( "mapList", "mapName", mainGui->GetStateInt( "currentGametype" ) ); +} + +/* +================ +idMultiplayerGame::SetSAMapList +================ +*/ +void idMultiplayerGame::SetSAMapList( void ) { + SetMapList( "sa_mapList", "sa_mapName", mainGui->GetStateInt( "adminCurrentGametype" ) ); +} + +/* +================ +idMultiplayerGame::ClientEndFrame +Called once each render frame (client) after all idGameLocal::ClientPredictionThink() calls +================ +*/ +void idMultiplayerGame::ClientEndFrame( void ) { + iconManager->UpdateIcons(); +} + +/* +================ +idMultiplayerGame::CommonRun +Called once each render frame (client)/once each game frame (server) +================ +*/ +void idMultiplayerGame::CommonRun( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + // twhitaker r282 + // TTimo: sure is a nasty way to do it + if ( gameLocal.isServer && ( gameLocal.serverInfo.GetInt( "net_serverDedicated" ) != cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + } + + if ( player && player->mphud ) { + // update icons + if ( gameLocal.isServer ) { + iconManager->UpdateIcons(); + } + +#ifdef _USE_VOICECHAT + float micLevel; + bool sending, testing; + + // jscott: enable the voice recording + testing = cvarSystem->GetCVarBool( "s_voiceChatTest" ); + sending = soundSystem->EnableRecording( !!( player->usercmd.buttons & BUTTON_VOICECHAT ), testing, micLevel ); + + if( mainGui ) { + mainGui->SetStateFloat( "s_micLevel", micLevel ); + mainGui->SetStateFloat( "s_micInputLevel", cvarSystem->GetCVarFloat( "s_micInputLevel" ) ); + } + +// RAVEN BEGIN +// shouchard: let the UI know about voicechat states + if ( !testing && sending ) { + player->mphud->HandleNamedEvent( "show_transmit_self" ); + } else { + player->mphud->HandleNamedEvent( "hide_transmit_self" ); + } + + if( player->GetUserInfo() && player->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) { + int maxChannels = soundSystem->GetNumVoiceChannels(); + int clientNum = -1; + for (int channels = 0; channels < maxChannels; channels++ ) { + clientNum = soundSystem->GetCommClientNum( channels ); + if ( -1 != clientNum ) { + break; + } + } + + // Sanity check for network errors + assert( clientNum > -2 && clientNum < MAX_CLIENTS ); + + if ( clientNum > -1 && clientNum < MAX_CLIENTS ) { + idPlayer *from = ( idPlayer * )gameLocal.entities[clientNum]; + if( from ) { + player->mphud->SetStateString( "audio_name", from->GetUserInfo()->GetString( "ui_name" ) ); + player->mphud->HandleNamedEvent( "show_transmit" ); + } + } else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } + } + else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } +#endif // _USE_VOICECHAT +// RAVEN END + } +#ifdef _USE_VOICECHAT + // jscott: Send any new voice data + XmitVoiceData(); +#endif + + int oldRank = -1; + int oldLeadingTeam = -1; + bool wasTied = false; + int oldHighScore = idMath::INT_MIN; + + if( player && rankedPlayers.Num() ) { + if( gameLocal.gameType == GAME_DM ) { + oldRank = GetPlayerRank( player, wasTied ); + oldHighScore = rankedPlayers[ 0 ].Second(); + } else if( gameLocal.IsTeamGame() ) { + oldLeadingTeam = rankedTeams[ 0 ].First(); + wasTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + oldHighScore = rankedTeams[ 0 ].Second(); + } + } + + UpdatePlayerRanks(); + if ( gameLocal.IsTeamGame() ) { + UpdateTeamRanks(); + } + + if ( player && rankedPlayers.Num() && gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType == GAME_DM ) { + // leader message + bool isTied = false; + int newRank = GetPlayerRank( player, isTied ); + + if ( newRank == 0 ) { + if( ( oldRank != 0 || wasTied ) && !isTied ) { + // we've gained first place or the person we were tied with dropped out of first place + ScheduleAnnouncerSound( AS_DM_YOU_HAVE_TAKEN_LEAD, gameLocal.time ); + } else if( oldRank != 0 || (!wasTied && isTied) ) { + // we tied first place or we were in first and someone else tied + ScheduleAnnouncerSound( AS_DM_YOU_TIED_LEAD, gameLocal.time ); + } + } else if ( oldRank == 0 ) { + // we lost first place + ScheduleAnnouncerSound( AS_DM_YOU_LOST_LEAD, gameLocal.time ); + } + } else if ( gameLocal.IsTeamGame() ) { + int leadingTeam = rankedTeams[ 0 ].First(); + bool isTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + + if ( !wasTied && isTied ) { + if ( gameLocal.gameType != GAME_DEADZONE ) + ScheduleAnnouncerSound( AS_TEAM_TEAMS_TIED, gameLocal.time ); + } else if ( (leadingTeam != oldLeadingTeam && !isTied) || ( wasTied && !isTied ) ) { + ScheduleAnnouncerSound( leadingTeam ? AS_TEAM_STROGG_LEAD : AS_TEAM_MARINES_LEAD, gameLocal.time ); + } + + if ( gameLocal.gameType == GAME_TDM && oldHighScore != teamScore[ rankedTeams[ 0 ].First() ] && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + } + + if( ( gameLocal.gameType == GAME_DM ) && rankedPlayers[ 0 ].Second() != oldHighScore && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + // fraglimit warning + if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + + } + + if ( rankTextPlayer ) { + bool tied = false; + int rank = GetPlayerRank( rankTextPlayer, tied ); + (gameLocal.GetLocalPlayer())->GUIMainNotice( GetPlayerRankText( rank, tied, playerState[ rankTextPlayer->entityNumber ].fragCount ) ); + rankTextPlayer = NULL; + } + + PlayAnnouncerSounds(); + + + // asalmon: Need to refresh stats periodically if the player is looking at stats + if ( currentStatClient != -1 ) { + rvPlayerStat* clientStat = statManager->GetPlayerStat( currentStatClient ); + if ( ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) { + statManager->SelectStatWindow(currentStatClient, currentStatTeam); + } + } + + bool updateModels = false; + if( g_forceModel.IsModified() && !gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceModel.ClearModified(); + } + + if( g_forceMarineModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceMarineModel.ClearModified(); + } + + if( g_forceStroggModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceStroggModel.ClearModified(); + } + + if( updateModels ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + player->UpdateModelSetup(); + } + } + } + + // do this here rather than in idItem::Think() because clients don't run Think on ents outside their snap + if( g_simpleItems.IsModified() ) { + + for( int i = 0; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( !ent || !ent->IsType( idItem::GetClassType() ) || ent->IsType( rvItemCTFFlag::GetClassType() ) ) { + continue; + } + + idItem* item = (idItem*)ent; + + item->FreeModelDef(); + + renderEntity_t* renderEntity = item->GetRenderEntity(); + memset( renderEntity, 0, sizeof( renderEntity ) ); + + item->simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !item->IsType( rvItemCTFFlag::GetClassType() ); + + if( item->simpleItem ) { + renderEntity->shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] = item->simpleItemScale; + renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = item->simpleItemScale; + renderEntity->hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity->callback = NULL; + renderEntity->numJoints = 0; + renderEntity->joints = NULL; + renderEntity->customSkin = 0; + renderEntity->noShadow = true; + renderEntity->noSelfShadow = true; + renderEntity->customShader = declManager->FindMaterial( item->spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity->referenceShader = 0; + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + renderEntity->axis = mat3_identity; + + item->StopEffect( "fx_idle", true ); + item->effectIdle = NULL; + item->SetAxis( mat3_identity ); + if( item->pickedUp ) { + item->FreeModelDef(); + item->UpdateVisuals(); + } + } else { + gameEdit->ParseSpawnArgsToRenderEntity( &item->spawnArgs, renderEntity ); + item->SetAxis( renderEntity->axis ); + + if ( item->spawnArgs.GetString( "fx_idle" ) ) { + item->UpdateModelTransform(); + item->effectIdle = item->PlayEffect( "fx_idle", renderEntity->origin, renderEntity->axis, true ); + } + + if( item->pickedUp && item->pickupSkin ) { + item->SetSkin( item->pickupSkin ); + } + } + if ( !item->spawnArgs.GetBool( "dropped" ) ) { + if ( item->spawnArgs.GetBool( "nodrop" ) ) { + item->GetPhysics()->PutToRest(); + } else { + item->Event_DropToFloor(); + } + } + } + + g_simpleItems.ClearModified(); + } + + if (hud_showSpeed.IsModified()) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->hud) { + player->hud->HandleNamedEvent( hud_showSpeed.GetBool() ? "showSpeed" : "hideSpeed" ); + } + hud_showSpeed.ClearModified(); + } +} + +/* +================ +idMultiplayerGame::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idMultiplayerGame::ClientRun( void ) { + if ( gameLocal.isRepeater ) { + assert( !gameLocal.isServer ); + pureReady = true; + } + + CommonRun(); +} + + +/* +================ +idMultiplayerGame::ReportZoneControllingPlayer +================ +*/ +void idMultiplayerGame::ReportZoneControllingPlayer( idPlayer* player ) +{ + assert( gameLocal.gameType == GAME_DEADZONE ); + + if ( !player ) + return; + + playerState[player->entityNumber].deadZoneScore += gameLocal.GetMSec(); + playerState[player->entityNumber].teamFragCount = playerState[player->entityNumber].deadZoneScore / 1000; + + float cashPerSecondForDeadZoneControl = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_deadZoneControlPerSecond", 0 ); +// player->GiveCash( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); + player->buyMenuCash += ( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); +} + + +/* +================ +idMultiplayerGame::ReportZoneController +================ +*/ +void idMultiplayerGame::ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger) +{ + assert( gameLocal.gameType == GAME_DEADZONE ); + assert( gameState->IsType( riDZGameState::GetClassType() ) ); + + riDZGameState *dzGameState = static_cast( gameState ); + + powerupCount = pCount; + + idTrigger_Multi* zTrigger = 0; + if ( zoneTrigger && zoneTrigger->IsType( idTrigger_Multi::GetClassType() ) ) { + zTrigger = static_cast( zoneTrigger ); + } + + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != GAMEON && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ) + { + // We're not playing right now. However, make sure all the clients are updated to know + // that the zone is neutral. + dzGameState->SetDZState(TEAM_MARINE, DZ_NONE); + dzGameState->SetDZState(TEAM_STROGG, DZ_NONE); + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + return; + } + + if ( IsValidTeam(team) ) { + const int t = gameLocal.serverInfo.GetInt( "si_controlTime" ); + teamDeadZoneScore[team] += gameLocal.GetMSec() * powerupCount; + teamScore[team] = (int)((float)teamDeadZoneScore[team] / 1000.0f); + + // We have a winner! + if ( teamDeadZoneScore[team] > t*1000 ) { + // Set the shaders and lights back to neutral. + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(idVec3(0.75f, 0.75f, 0.75f)); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + + OnDeadZoneTeamVictory( team ); + + return; + } + } + + // Someone took control of a zone, report this to the + if ( situation == DZ_MARINES_TAKEN || situation == DZ_STROGG_TAKEN || situation == DZ_MARINE_TO_STROGG || + situation == DZ_STROGG_TO_MARINE || situation == DZ_MARINE_REGAIN || situation == DZ_STROGG_REGAIN ) { + dzGameState->SetDZState(TEAM_MARINE, DZ_NONE); // Clear hacked deadlock + dzGameState->SetDZState(team, DZ_TAKEN); + } + + const int NOCHANGE = -2; + const int DEADLOCK = 3; + int controlSit = NOCHANGE; + switch ( situation ) { + case DZ_NONE : + controlSit = NOCHANGE; + break; + case DZ_MARINES_TAKEN : + controlSit = TEAM_MARINE; + break; + case DZ_MARINES_LOST : + controlSit = TEAM_NONE; + dzGameState->SetDZState(TEAM_MARINE, DZ_LOST); + break; + case DZ_STROGG_TAKEN : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_LOST : + controlSit = TEAM_NONE; + dzGameState->SetDZState(TEAM_STROGG, DZ_LOST); + break; + case DZ_MARINE_TO_STROGG : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_TO_MARINE : + controlSit = TEAM_MARINE; + break; + case DZ_MARINE_DEADLOCK : + controlSit = DEADLOCK; + dzGameState->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_STROGG_DEADLOCK : + controlSit = DEADLOCK; + dzGameState->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_MARINE_REGAIN : + controlSit = TEAM_MARINE; + break; + case DZ_STROGG_REGAIN : + controlSit = TEAM_STROGG; + break; + } + + if ( zTrigger && controlSit == NOCHANGE && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + // There's been no change in status, but keep these variables updated on the client + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = (int)targetEnt->GetRenderEntity()->shaderParms[7]; + } + } + + if ( controlSit == NOCHANGE || !zTrigger ) + return; // We're done. + + idVec3 colorVec; + int parmNum = 2; + if ( controlSit == TEAM_NONE ) { + colorVec = idVec3(0.75f, 0.75f, 0.75f); + parmNum = 2; + } + else if ( controlSit == TEAM_MARINE ) { + colorVec = idVec3(0.0f, 1.0f, 0.0f); + parmNum = 0; + } + else if ( controlSit == TEAM_STROGG ) { + colorVec = idVec3(1.0f, 0.5f, 0.0f); + parmNum = 1; + } + else if ( controlSit == DEADLOCK ) { + colorVec = idVec3(1.0f, 0.0f, 0.0f); + parmNum = 3; + } + + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(colorVec); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = parmNum; + targetEnt->SetShaderParm(7, (float)parmNum); + } + } +} + + + +bool idMultiplayerGame::IsValidTeam(int team) +{ + if ( team == TEAM_MARINE || team == TEAM_STROGG ) + return true; + + return false; +} + + +void idMultiplayerGame::OnDeadZoneTeamVictory( int winningTeam ) +{ + OnBuyModeTeamVictory( winningTeam ); + + gameState->NewState( GAMEREVIEW ); +} + +void idMultiplayerGame::OnBuyModeTeamVictory( int winningTeam ) + { + if( !IsBuyingAllowedInTheCurrentGameMode() ) + return; + + float teamCashForWin = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeWin", 0 ); + float teamCashForTie = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeTie", 0 ); + float teamCashForLoss = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeLoss", 0 ); + + if( winningTeam == TEAM_NONE ) + { + GiveCashToTeam( TEAM_MARINE, teamCashForTie ); + GiveCashToTeam( TEAM_STROGG, teamCashForTie ); + } + else + { + int losingTeam = 1 - winningTeam; + GiveCashToTeam( winningTeam, teamCashForWin ); + GiveCashToTeam( losingTeam, teamCashForLoss ); + } + } + +/* +================ +idMultiplayerGame::Run +================ +*/ +void idMultiplayerGame::Run( void ) { + pureReady = true; + + assert( gameLocal.isMultiplayer && gameLocal.isServer && gameState ); + + CommonRun(); + + CheckVote(); + + CheckRespawns(); + + CheckSpecialLights( ); + +//RITUAL BEGIN + UpdateTeamPowerups(); +//RITUAL END + gameState->Run(); + + gameState->SendState( serverReliableSender.To( -1 ) ); + + // don't update the ping every frame to save bandwidth + if ( gameLocal.time > pingUpdateTime ) { + for ( int i = 0; i < gameLocal.numClients; i++ ) { + playerState[i].ping = networkSystem->ServerGetClientPing( i ); + } + pingUpdateTime = gameLocal.time + 1000; + } + + +} + +/* +================ +idMultiplayerGame::UpdateMainGui +================ +*/ +void idMultiplayerGame::UpdateMainGui( void ) { + int i; + mainGui->SetStateInt( "readyon", gameState->GetMPGameState() == WARMUP ? 1 : 0 ); + mainGui->SetStateInt( "readyoff", gameState->GetMPGameState() != WARMUP ? 1 : 0 ); + idStr strReady = cvarSystem->GetCVarString( "ui_ready" ); + if ( strReady.Icmp( "ready") == 0 ){ + strReady = common->GetLocalizedString( "#str_104248" ); + } else { + strReady = common->GetLocalizedString( "#str_104247" ); + } + mainGui->SetStateString( "ui_ready", strReady ); + mainGui->SetStateInt( "num_spec_players", unrankedPlayers.Num() ); + + mainGui->SetStateInt( "gametype", gameLocal.gameType ); + mainGui->SetStateBool( "s_useOpenAL", cvarSystem->GetCVarBool( "s_useOpenAL" ) ); + mainGui->SetStateBool( "s_loadOpenALFailed", cvarSystem->GetCVarBool( "s_loadOpenALFailed" ) ); + + idVec4 hitscanTint; + idStr hitScanValue = cvarSystem->GetCVarString( "ui_hitscanTint" ); + sscanf( hitScanValue.c_str(), "%f %f %f %f", &hitscanTint.x, &hitscanTint.y, &hitscanTint.z, &hitscanTint.w ); + mainGui->SetStateFloat( "ui_hitscanTint", hitscanTint.x ); + + // RAVEN BEGIN +// bdube: capture the flag + if ( gameLocal.IsTeamGame() ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + mainGui->SetStateInt( "teamon", 1 ); + mainGui->SetStateInt( "teamoff", 0 ); + } else { + mainGui->SetStateInt( "teamon", 0 ); + mainGui->SetStateInt( "teamoff", 1 ); + } +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + mainGui->SetStateInt( "teamon", (gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + mainGui->SetStateInt( "teamoff", !(gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { +// RITUAL END + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + } + // setup vote + mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 ); + mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 ); + // send the current serverinfo values + for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) { + const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i ); + mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() ); + } + mainGui->StateChanged( gameLocal.time ); +#if defined( __linux__ ) + // replacing the oh-so-useful s_reverse with sound backend prompt + mainGui->SetStateString( "driver_prompt", "1" ); +#else + mainGui->SetStateString( "driver_prompt", "0" ); +#endif + +//RAVEN BEGIN +// cnicholson: Add Custom Crosshair update + mainGui->SetStateString( "g_crosshairCustom", cvarSystem->GetCVarBool( "g_crosshairCustom" ) ? "1" : "0" ); +//RAVEN END + +// RAVEN BEGIN +// cnicholson: We need to setup the custom crosshair so it shows up the first time the player enters the MP settings menu. +// This block checks the current crosshair, and compares it against the list of crosshairs in player.def (mtr_crosshair*) under the +// player_marine_mp section. If it finds a match, it assigns the crosshair, otherwise, the first found crosshair is used. +#ifndef _XENON + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + +#ifndef _XENON + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { // Loop through all crosshairs listed in the def + if ( kv->GetValue() == currentCrosshair.c_str() ) { // Until a match is found + break; + } + kv = defCH->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL ); // If no natches are found, use the first one. + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + + +//asalmon: Set up a state var for match type of Xbox 360 +#ifdef _XENON + mainGui->SetStateBool("CustomHost", Live()->IsCustomHost()); + mainGui->SetStateInt("MatchType", Live()->GetMatchtype()); + mainGui->SetStateString("si_gametype", gameLocal.serverInfo.GetString("si_gametype")); + const char *damage; + if (gameLocal.serverInfo.GetBool("si_teamdamage")){ + damage = "Yes" ; + } + else { + damage = "No"; + } + mainGui->SetStateString("si_teamdamage", damage); + const char *shuffle; + if (gameLocal.serverInfo.GetBool("si_shuffleMaps")){ + shuffle = "Yes" ; + } + else { + shuffle = "No"; + } + mainGui->SetStateString("si_shuffleMaps", shuffle); + mainGui->SetStateString("si_fraglimit", gameLocal.serverInfo.GetString("si_fraglimit")); + mainGui->SetStateString("si_capturelimit", gameLocal.serverInfo.GetString("si_capturelimit")); + mainGui->SetStateString("si_timelimit", gameLocal.serverInfo.GetString("si_timelimit")); + +// mekberg: send spectating to the mainGui. + if ( gameLocal.GetLocalPlayer( ) ) { + mainGui->SetStateBool( "spectating", gameLocal.GetLocalPlayer( )->spectating ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( gameLocal.GetLocalPlayer()->GetUserInfo() ) { + // additionally in tourney, indicate whether the player is voluntarily spectating + mainGui->SetStateBool( "tourneyspectating", !idStr::Icmp( gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_spectate" ), "Spectate" ) ); + } else { + mainGui->SetStateBool( "tourneyspectating", 1 ); + } + } + } else { + mainGui->SetStateBool( "spectating", false ); + } +#endif +// RAVEN END + +} + +/* +================ +idMultiplayerGame::SetupBuyMenuItems +================ +*/ +void idMultiplayerGame::SetupBuyMenuItems() +{ + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) + return; + + buyMenu->SetStateInt( "buyStatus_shotgun", player->ItemBuyStatus( "weapon_shotgun" ) ); + buyMenu->SetStateInt( "buyStatus_hyperblaster", player->ItemBuyStatus( "weapon_hyperblaster" ) ); + buyMenu->SetStateInt( "buyStatus_grenadelauncher", player->ItemBuyStatus( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "buyStatus_nailgun", player->ItemBuyStatus( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "buyStatus_rocketlauncher", player->ItemBuyStatus( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "buyStatus_railgun", player->ItemBuyStatus( "weapon_railgun" ) ); + buyMenu->SetStateInt( "buyStatus_lightninggun", player->ItemBuyStatus( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "buyStatus_dmg", player->ItemBuyStatus( "weapon_dmg" ) ); + buyMenu->SetStateInt( "buyStatus_napalmgun", player->ItemBuyStatus( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "buyStatus_lightarmor", player->ItemBuyStatus( "item_armor_small" ) ); + buyMenu->SetStateInt( "buyStatus_heavyarmor", player->ItemBuyStatus( "item_armor_large" ) ); + buyMenu->SetStateInt( "buyStatus_ammorefill", player->ItemBuyStatus( "ammorefill" ) ); + + buyMenu->SetStateInt( "buyStatus_special0", player->ItemBuyStatus( "ammo_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special1", player->ItemBuyStatus( "health_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special2", player->ItemBuyStatus( "damage_boost" ) ); + + buyMenu->SetStateInt( "playerTeam", player->team ); + + if ( player->weapon ) + buyMenu->SetStateString( "ammoIcon", player->weapon->spawnArgs.GetString ( "inv_icon" ) ); + + buyMenu->SetStateInt( "player_weapon", player->GetCurrentWeapon() ); +} + +/* +================ +idMultiplayerGame::StartMenu +================ +*/ +idUserInterface* idMultiplayerGame::StartMenu( void ) { + if ( mainGui == NULL ) { + return NULL; + } + //if we're the server, allow access to the admin tab right away. Otherwise, make sure we don't have it. + if( gameLocal.isServer ) { + mainGui->SetStateInt( "password_valid", 1 ); + } else { + mainGui->SetStateInt( "password_valid", 0 ); + } + + int i, j; + + if ( currentMenu ) { + currentMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + } else { + if ( nextMenu >= 2 ) { + currentMenu = nextMenu; + } else { + // for default and explicit + currentMenu = 1; + } + cvarSystem->SetCVarBool( "ui_chat", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = true; + } + + nextMenu = 0; + if ( currentMenu == 1 ) { + UpdateMainGui(); + + // UpdateMainGui sets most things, but it doesn't set these because + // it'd be pointless and/or harmful to set them every frame (for various reasons) + // Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this. + + // player kick data + for ( i = 0; i < 16; i++ ) { + kickVoteMapNames[ i ].Clear(); + kickVoteMap[ i ] = -1; + } + + idStr kickList; + j = 0; + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( kickList.Length() ) { + kickList += ";"; + } + kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) ); + kickVoteMap[ j ] = i; +// RAVEN BEGIN +// shouchard: names for kick vote map + kickVoteMapNames[ j ] = gameLocal.userInfo[ i ].GetString( "ui_name" ); +// RAVEN END + j++; + } + } + mainGui->SetStateString( "kickChoices", kickList ); + + mainGui->SetStateString( "chattext", "" ); + mainGui->Activate( true, gameLocal.time ); + + idPlayer *localP = gameLocal.GetLocalPlayer(); +#ifndef _XENON + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp", false ); +#else + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp_ui", false ); +#endif + idStr buildValues, buildNames; + + int numModels = declManager->GetNumDecls( DECL_PLAYER_MODEL ); + for( int i = 0; i < numModels; i++ ) { + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->DeclByIndex( DECL_PLAYER_MODEL, i, false ); + + if( !playerModel ) { + continue; + } + + const char *resultValue = playerModel->GetName(); + + if ( !resultValue || !resultValue[0] ) { + continue; + } + + const char *team = playerModel->team.c_str(); + + if ( gameLocal.IsTeamGame() ) { + if ( team && localP && localP->team >= 0 && localP->team < TEAM_MAX && idStr::Icmp( teamNames[ localP->team ], team ) == 0 ) { + } else { + // doesn't match, so skip + continue; + } + } + + if ( i ) { + buildValues += ";"; + buildNames += ";"; + } + buildValues += resultValue; + + const char *resultName = common->GetLocalizedString( playerModel->description.c_str() ); + + if ( !resultName || !resultName[0] ) { + buildNames += resultValue; + } else { + buildNames += resultName; + } + } + + mainGui->SetStateString( "model_values", buildValues.c_str() ); + mainGui->SetStateString( "model_names", buildNames.c_str() ); + mainGui->SetStateBool( "player_model_updated", true ); + + const char *model; + if ( localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if( *model == '\0' ) { + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + if( *model == '\0' ) { + model = def->dict.GetString( "def_default_model" ); + } + } + + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + mainGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + mainGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + mainGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + mainGui->SetStateBool( "need_update", true ); + } + + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetUserInfo() ) { + cvarSystem->SetCVarString( "gui_ui_name", gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_name" ) ); + } else { + cvarSystem->SetCVarString( "gui_ui_name", cvarSystem->GetCVarString( "ui_name" ) ); + } + + if ( gameLocal.isTVClient ) { + mainGui->SetStateBool( "is_tv_client", true ); + } else { + mainGui->SetStateBool( "is_tv_client", false ); + } + + return mainGui; + } else if ( currentMenu == 2 ) { + // the setup is done in MessageMode + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; + } + msgmodeGui->Activate( true, gameLocal.time ); + cvarSystem->SetCVarBool( "ui_chat", true ); + return msgmodeGui; + } else if ( currentMenu == 3 ) { + statSummary->Activate( true, gameLocal.time ); + statManager->SetupStatWindow( statSummary ); + UpdateScoreboard( statSummary ); + UpdateSummaryBoard( statSummary ); + statSummary->SetStateFloat( "ready", 0 ); + statSummary->StateChanged( gameLocal.time ); + + // Moved the announcer sound here. This way we can be sure the client has updated team score information by this point. + // #13576 #13544 causing double sounds because it's getting triggered twice at endgame ( from GameStateChanged and from ReceiveAllStats ) + // the move to here was for fixing some problem when running at the previous location ( GameStateChanged ) + // there are too many codepaths leading to various orders of ReceiveAllStats and GameStateChanged + // various attempts to flag the right call that should trigger the sound failed, so just using a timeout now + if ( gameLocal.time - lastVOAnnounce > 1000 ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() ) { + int winningTeam = GetScoreForTeam( TEAM_MARINE ) > GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + if( player->team == winningTeam ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } else if ( gameLocal.gameType != GAME_TOURNEY ) { + if( player->GetRank() == 0 ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } + lastVOAnnounce = gameLocal.time; + } + + return statSummary; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + //if( mpClientGameState.gameState.currentState == COUNTDOWN ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->SetStateInt( "price_shotgun", player->GetItemCost("weapon_shotgun") ); + buyMenu->SetStateInt( "price_hyperblaster", player->GetItemCost("weapon_hyperblaster") ); + buyMenu->SetStateInt( "price_grenadelauncher", player->GetItemCost( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "price_nailgun", player->GetItemCost( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "price_rocketlauncher", player->GetItemCost( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "price_railgun", player->GetItemCost( "weapon_railgun" ) ); + buyMenu->SetStateInt( "price_lightninggun", player->GetItemCost( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "price_dmg", player->GetItemCost( "weapon_dmg" ) ); + buyMenu->SetStateInt( "price_napalmgun", player->GetItemCost( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "price_lightarmor", player->GetItemCost( "item_armor_small" ) ); + buyMenu->SetStateInt( "price_heavyarmor", player->GetItemCost( "item_armor_large" ) ); + buyMenu->SetStateInt( "price_ammorefill", player->GetItemCost( "ammorefill" ) ); + + buyMenu->SetStateInt( "price_special0", player->GetItemCost( "ammo_regen" ) ); + buyMenu->SetStateInt( "price_special1", player->GetItemCost( "health_regen" ) ); + buyMenu->SetStateInt( "price_special2", player->GetItemCost( "damage_boost" ) ); + SetupBuyMenuItems(); + buyMenu->Activate(true, gameLocal.time); + return buyMenu; + //} +// RITUAL END + } + + return NULL; +} + +/* +================ +idMultiplayerGame::DisableMenu +================ +*/ +void idMultiplayerGame::DisableMenu( void ) { + if ( currentMenu == 1 ) { + mainGui->Activate( false, gameLocal.time ); + } else if ( currentMenu == 2 ) { + msgmodeGui->Activate( false, gameLocal.time ); + } else if( currentMenu == 3 ) { + statSummary->Activate( false, gameLocal.time ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + buyMenu->Activate( false, gameLocal.time ); +// RITUAL END + } + + // copy over name from temp cvar + if( currentMenu == 1 && idStr::Cmp( cvarSystem->GetCVarString( "gui_ui_name" ), cvarSystem->GetCVarString( "ui_name" ) ) ) { + cvarSystem->SetCVarString( "ui_name", cvarSystem->GetCVarString( "gui_ui_name" ) ); + } + + currentMenu = 0; + nextMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; +//RAVEN BEGIN +//asalmon: make the scoreboard on Xenon close +#ifdef _XENON + gameLocal.GetLocalPlayer()->scoreBoardOpen = false; +#endif +//RAVEN END + if( gameLocal.GetLocalPlayer()->mphud) { + gameLocal.GetLocalPlayer()->mphud->Activate( true, gameLocal.time ); + } + } + + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = -1; +} + +/* +================ +idMultiplayerGame::SetMapShot +================ +*/ +void idMultiplayerGame::SetMapShot( void ) { +#ifdef _XENON + // Should not be used + assert( 0 ); +#else + char screenshot[ MAX_STRING_CHARS ]; + int mapNum = mapList->GetSelection( NULL, 0 ); + const idDict *dict = NULL; + if ( mapNum >= 0 ) { + dict = fileSystem->GetMapDecl( mapNum ); + } + fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS ); + mainGui->SetStateString( "current_levelshot", screenshot ); +// RAVEN BEGIN +// cnicholson: Need to sort the material screenshot so it doesn't overlap other things + const idMaterial *mat = declManager->FindMaterial( screenshot ); + mat->SetSort( SS_GUI ); +// RAVEN END +#endif +} + +/* +================ +LocalServerRedirect +Dummy local redirect for gui rcon functionality on a local server +================ +*/ +void LocalServerRedirect( const char* string ) { + gameLocal.mpGame.ReceiveRemoteConsoleOutput( string ); +} + +/* +================ +idMultiplayerGame::HandleGuiCommands +================ +*/ +const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) { + idUserInterface *currentGui; +// RAVEN BEGIN +// shouchard: removed the code that deals with these variables + //const char *voteValue; + //int vote_clientNum; +// RAVEN END + int icmd; + idCmdArgs args; + + + + if ( !_menuCommand[ 0 ] ) { + common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" ); + return "continue"; + } + +#ifdef _XENON + if ( currentMenu == 0 && (session->GetActiveGUI() != scoreBoard) ) { +#else + if ( currentMenu == 0 ) { +#endif + return NULL; // this will tell session to not send us events/commands anymore + } + + if ( currentMenu == 1 ) { + currentGui = mainGui; + } +#ifdef _XENON + else if (session->GetActiveGUI() != scoreBoard) { + currentGui = msgmodeGui; + } else { + currentGui = scoreBoard; + } +#else + else if( currentMenu == 2 ) { + currentGui = msgmodeGui; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + currentGui = buyMenu; +// jmartel: make sure var is initialized (compiler complained) + } else if( currentMenu == 3 ) { + currentGui = statSummary; + } else { + gameLocal.Warning( "idMultiplayerGame::HandleGuiCommands() - Unknown current menu '%d'\n", currentMenu ); + currentGui = mainGui; + } +// RITUAL END +#endif + + + args.TokenizeString( _menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( !idStr::Icmp( cmd, ";" ) ) { + continue; + } else if ( !idStr::Icmp( cmd, "inGameMenu" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + currentGui->SetStateString( "chat", chatHistory.c_str() ); + + currentGui->SetStateInt( "player_team", gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->team : TEAM_NONE ); + + // mekberg: added + UpdateMPSettingsModel ( currentGui ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) ) { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107699" ) ); + } else { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107700" ) ); + } + } + + currentGui->SetStateBool( "useReady", gameLocal.serverInfo.GetBool( "si_useReady", "0" ) && gameState->GetMPGameState() == WARMUP ); + if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104247" ) ); + } else if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104248" ) ); + } else { + currentGui->SetStateString( "readyStatus", "" ); + } + + currentGui->SetStateBool( "si_allowVoting", gameLocal.serverInfo.GetBool( "si_allowVoting" ) ); + currentGui->SetStateBool( "si_allowVoice", gameLocal.serverInfo.GetBool( "si_voiceChat" ) ); + + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), false ); + } else { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), true ); + } + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "video" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + if ( idStr::Icmp( vcmd, "low" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 0 ); + } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 1 ); + } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 2 ); + } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 3 ); + } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + } + +// RAVEN BEGIN +// mekberg: set the r_mode. + cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); + currentGui->SetStateInt( "r_aspectRatio", 0 ); + currentGui->HandleNamedEvent( "forceAspect0" ); + currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + currentGui->StateChanged( gameLocal.realClientTime ); + cvarSystem->SetCVarInteger( "r_mode", common->GetRModeForMachineSpec ( cvarSystem->GetCVarInteger( "com_machineSpec" ) ) ); + common->SetDesiredMachineSpec( cvarSystem->GetCVarInteger( "com_machineSpec" ) ); +// RAVEN END + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec" ); + if ( idStr::Icmp( vcmd, "restart" ) == 0) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart\n" ); + } + + continue; + } else if ( !idStr::Icmp( cmd, "join" ) ) { + if ( args.Argc() - icmd >= 1 ) { + JoinTeam( args.Argv( icmd++ ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "quit" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "close" ) ) { + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "spectate" ) ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "admin" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + idStr input( currentGui->State().GetString( "admin_console_input" ) ); + input.StripTrailing( "\n" ); + //jshepard: check to see if this is a server before using rcon! + if( gameLocal.isServer ) { + char redirectBuffer[ RCON_HISTORY_SIZE ]; + common->BeginRedirect( (char *)redirectBuffer, sizeof( redirectBuffer ), LocalServerRedirect ); + + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + ReceiveRemoteConsoleOutput( input.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "%s\n", input.c_str() ) ); + } + + common->EndRedirect(); + } else { + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon \"%s\"\n", input.c_str() ) ); + ReceiveRemoteConsoleOutput( input.c_str() ); + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "chatmessage" ) ) { + int mode = currentGui->State().GetInt( "messagemode" ); + idStr text = currentGui->GetStateString( "chattext" ); +// RAVEN BEGIN +// bdube: dont send chat message if there was no text specified + if ( !text.IsEmpty() ) { + text.Replace( "&", "&" ); + text.Replace( "\\", "&bsl;" ); + if ( mode ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", text.c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text.c_str() ) ); + } + } +// RAVEN BEGIN + currentGui->SetStateString( "chattext", "" ); + if ( currentMenu == 1 || currentMenu == 3 ) { + return "continue"; + } else { + DisableMenu(); + return NULL; + } + } else if ( !idStr::Icmp( cmd, "toggleReady" ) ) { + ToggleReady( ); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + int channel = 1; + if ( snd.Length() == 1 ) { + channel = atoi( snd ); + snd = args.Argv( icmd++ ); + } + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, snd, channel ); + } + continue; + } else if ( !idStr::Icmp( cmd, "callVote" ) ) { +// RAVEN BEGIN +// shouchard: new functionality to match the new interface + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + // kick + int uiKickSelection = mainGui->State().GetInt( "playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + voteData.m_kick = kickVoteMap[ uiKickSelection ]; + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + // restart + if ( 0 != mainGui->State().GetInt( "vote_val2_sel" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } + + if ( 0 != mainGui->State().GetBool( "si_shuffleteams" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_SHUFFLE; + } + // map + int uiMapSelection = mainGui->State().GetInt( "mapList_sel_0" ); + if ( -1 != uiMapSelection ) { +// rjohnson: code commented out below would get the text friendly name of the map and not the file name + int mapNum = mainGui->State().GetInt( va( "mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + voteData.m_map = dict->GetString( "path" ); + voteData.m_fieldFlags |= VOTEFLAG_MAP; + } +// const char *mapName = mainGui->State().GetString( va( "mapList_item_%d", uiMapSelection ) ); +// if ( NULL != mapName && '\0' != mapName[0] ) { +// if ( mapFileName[ 0 ] ) { +// voteData.m_map = va( "mp/%s", mapName ); +// voteData.m_fieldFlags |= VOTEFLAG_MAP; +// } + } + // gametype + // todo: need a function for switching between gametype strings and values + int uiGameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gametype" ); + int serverGameTypeInt = GameTypeToVote( currentGameTypeString ); + + if ( uiGameTypeInt != serverGameTypeInt ) { + voteData.m_gameType = uiGameTypeInt; + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + } + // time limit + int uiTimeLimit = mainGui->GetStateInt( "timeLimit" ); + if ( uiTimeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + voteData.m_timeLimit = uiTimeLimit; + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + } + // autobalance + int uiBalanceTeams = mainGui->GetStateInt( "vote_val6_sel" ); + if ( uiBalanceTeams != gameLocal.serverInfo.GetInt( "si_autobalance" ) ) { + voteData.m_teamBalance = uiBalanceTeams; + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + } + // allow spectators + /* int uiAllowSpectators = mainGui->GetStateInt( "vote_val7_sel" ); + if ( uiAllowSpectators != gameLocal.serverInfo.GetInt( "si_spectators" ) ) { + voteData.m_spectators = uiAllowSpectators; + voteData.m_fieldFlags |= VOTEFLAG_SPECTATORS; + } */ + // minimum players + int uiBuying = mainGui->GetStateInt( "buying" ); + if ( uiBuying != gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + voteData.m_buying = uiBuying; + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + } + // roundlimit (tourney only) + int uiTourneyLimit = mainGui->GetStateInt( "tourneylimit" ); + if ( uiTourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + voteData.m_tourneyLimit = uiTourneyLimit; + voteData.m_fieldFlags |= VOTEFLAG_TOURNEYLIMIT; + } + // capturelimit (ctf only) + int uiCaptureLimit = mainGui->GetStateInt( "capturelimit" ); + if ( uiCaptureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + voteData.m_captureLimit = uiCaptureLimit; + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + } + // controltime (deadzone only) + int uiControlTime = mainGui->GetStateInt( "controlTime" ); + if ( uiControlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + voteData.m_controlTime = uiControlTime; + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + } + // fraglimit (DM & TDM only) + int uiFragLimit = mainGui->GetStateInt( "fraglimit" ); + if ( uiFragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + voteData.m_fragLimit = uiFragLimit; + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + } + DisableMenu(); + + // clear any disallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // this means we haven't changed anything + if ( 0 == voteData.m_fieldFlags ) { + //AddChatLine( common->GetLocalizedString( "#str_104400" ) ); + } else { + ClientCallPackedVote( voteData ); + } + /* + // sjh: original doom code here + vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" ); + if ( voteIndex == VOTE_MAP ) { + int mapNum = mapList->GetSelection( NULL, 0 ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + if ( dict ) { + ClientCallVote( VOTE_MAP, dict->GetString( "path" ) ); + } + } + } else { + voteValue = mainGui->State().GetString( "str_voteValue" ); + if ( voteIndex == VOTE_KICK ) { + vote_clientNum = kickVoteMap[ atoi( voteValue ) ]; + ClientCallVote( voteIndex, va( "%d", vote_clientNum ) ); + } else { + ClientCallVote( voteIndex, voteValue ); + } + } + */ + return NULL; + } else if ( !idStr::Icmp( cmd, "voteYes" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteNo" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "click_playerList" ) ) { + // push data into the name field + int sel = mainGui->GetStateInt( "playerList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "playerKick", "" ); + } else { + mainGui->SetStateString( "playerKick", kickVoteMapNames[ sel ] ); + } + continue; + } else if ( !idStr::Icmp( cmd, "click_voteMapList" ) ) { + int sel = mainGui->GetStateInt( "mapList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "mapName", "" ); + } else { + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", sel ) ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "setVoteMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + SetVoteMapList(); +#endif + continue; + } else if ( !idStr::Icmp( cmd, "setVoteData" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // push data into the vote_ cvars so the UI can start at where we currently are + int players; + for ( players=0; playersSetStateString( va( "playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "playerList_item_%d", players ) ); + } + mainGui->SetStateString( "playerList_sel_0", "-1" ); + mainGui->SetStateString( "playerKick", "" ); + mainGui->SetStateInt( "vote_val2_sel", 0 ); + + +// RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + mainGui->SetStateString( "mapName", mapName ); +// RAVEN END + + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "currentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "vote_val6_sel", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "si_shuffleteams", 0 ); + mainGui->StateChanged( gameLocal.time ); + mainGui->HandleNamedEvent( "gametypeChange" ); +#endif + + SetVoteMapList(); + continue; + } else if ( !idStr::Icmp( cmd, "populateServerInfo" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + mainGui->SetStateString( "serverInfoList_item_0", va( "%s:\t%s", common->GetLocalizedString( "#str_107725" ), gameLocal.serverInfo.GetString( "si_name" ) ) ); + idStr serverAddress = networkSystem->GetServerAddress( ); + mainGui->SetStateString( "serverInfoList_item_1", va( "%s:\t%s", common->GetLocalizedString( "#str_107726" ), serverAddress.c_str() ) ); + mainGui->SetStateString( "serverInfoList_item_2", va( "%s:\t%s", common->GetLocalizedString( "#str_107727" ), LocalizeGametype() ) ); + + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } +// rhummer localized "map name" + mainGui->SetStateString( "serverInfoList_item_3", va( "%s\t%s", common->GetLocalizedString( "#str_107730" ), mapName ) ); + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetString( "si_captureLimit" ) ) ); + } + else if ( 0 == idStr::Icmp( gameType, "DM" ) || 0 == idStr::Icmp( gameType, "Team DM" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetString( "si_fragLimit" ) ) ); + } + mainGui->SetStateString( "serverInfoList_item_5", va( "%s:\t%s", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetString( "si_timeLimit" ) ) ); + mainGui->SetStateString( "serverInfoList_item_6", va( "%s:\t%s", common->GetLocalizedString( "#str_107662" ), gameLocal.serverInfo.GetString( "si_pure" ) ) ); + mainGui->SetStateString( "serverInfoList_item_7", va( "%s:\t%s", common->GetLocalizedString( "#str_107663" ), gameLocal.serverInfo.GetString( "si_maxPlayers" ) ) ); + mainGui->SetStateString( "serverInfoList_item_8", va( "%s:\t%s", common->GetLocalizedString( "#str_107664" ), gameLocal.serverInfo.GetString( "si_teamDamage" ) ) ); + mainGui->SetStateString( "serverInfoList_item_9", va( "%s:\t%s", common->GetLocalizedString( "#str_104254" ), gameLocal.serverInfo.GetString( "si_spectators" ) ) ); +#endif + continue; + // handler for the server admin tab (normal stuff) + } else if ( !idStr::Icmp( cmd, "checkAdminPass" )) { + //password has been added, so call the rcon verifypassword command + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon verifyRconPass" ); + continue; + + } else if ( !idStr::Icmp( cmd, "initServerAdmin" ) ) { + mainGui->SetStateInt( "admin_server_val1_sel", 0 ); // restart defaults to off + // maplist handled in initServerAdminMaplist; this needs to be called first + // to properly set the gametype since we read it back to show an appropriate list + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "admincurrentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "sa_timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "sa_tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "sa_capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "sa_controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "sa_autobalance", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "sa_buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "sa_fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "sa_shuffleteams", 0 ); +// mekberg: get the ban list if not server + if ( !gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + networkSystem->ClientSendReliableMessage( outMsg ); + } + + mainGui->StateChanged( gameLocal.time ); + + continue; + // handler for populating the map list; called both on open and on change gametype so it'll show the right maps + } else if ( !idStr::Icmp( cmd, "initServerAdminMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + SetSAMapList(); +#endif + continue; + // handler for updating the current map in the name + } else if ( !idStr::Icmp( cmd, "serverAdminUpdateMap" ) ) { + int mapSelection = mainGui->GetStateInt( "sa_mapList_sel_0" ); + if ( -1 == mapSelection ) { + + const idDict *mapDict = fileSystem->GetMapDecl( gameLocal.serverInfo.GetString( "si_map" ) ); + if ( mapDict ) { + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( mapDict->GetString( "name" )) ); + } else { + mainGui->SetStateString( "sa_mapName", gameLocal.serverInfo.GetString( "si_map" ) ); + } + + } else { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", mapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( dict->GetString( "name" )) ); + } + } + continue; + // handler for initializing the player list on the admin player tab + } else if ( !idStr::Icmp( cmd, "initServerAdminPlayer" ) ) { + int players; + for ( players=0; playersSetStateString( va( "sa_playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", players ) ); + //common->Printf( "DELETING at slot %d\n", players ); + } + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + continue; + // handler for actually changing something on the server admin tab + } else if ( !idStr::Icmp( cmd, "handleServerAdmin" ) ) { + // read in a bunch of data, pack it into the appropriate structure + serverAdminData_t data; + memset( &data, 0, sizeof( data ) ); + data.restartMap = 0 != mainGui->GetStateInt( "admin_server_val1_sel" ); + // map list here + int uiMapSelection = mainGui->State().GetInt( "sa_mapList_sel_0" ); + if (-1 != uiMapSelection ) { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + data.mapName = common->GetLocalizedString( dict->GetString( "path" )); + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + + switch ( mainGui->GetStateInt( "admincurrentGametype" ) ) { + case VOTE_GAMETYPE_DM: + data.gameType = GAME_DM; + break; + case VOTE_GAMETYPE_TOURNEY: + data.gameType = GAME_TOURNEY; + break; + case VOTE_GAMETYPE_TDM: + data.gameType = GAME_TDM; + break; + case VOTE_GAMETYPE_CTF: + data.gameType = GAME_CTF; + break; + case VOTE_GAMETYPE_ARENA_CTF: + data.gameType = GAME_ARENA_CTF; + break; + case VOTE_GAMETYPE_DEADZONE: + data.gameType = GAME_DEADZONE; + } + data.captureLimit = mainGui->GetStateInt( "sa_captureLimit" ); + data.fragLimit = mainGui->GetStateInt( "sa_fragLimit" ); + data.tourneyLimit = mainGui->GetStateInt( "sa_tourneylimit" ); + data.timeLimit = mainGui->GetStateInt( "sa_timeLimit" ); + data.buying = mainGui->GetStateInt( "sa_buying" ); + data.autoBalance = 0 != mainGui->GetStateInt( "sa_autobalance" ); + data.buying = 0 != mainGui->GetStateInt( "sa_buying" ); + data.controlTime = mainGui->GetStateInt( "sa_controlTime" ); + data.shuffleTeams = 0 != mainGui->GetStateInt( "sa_shuffleteams" ); + + // make the call to change the server data + if ( gameLocal.mpGame.HandleServerAdminCommands( data ) ) { + DisableMenu(); + return NULL; + } + continue; + // handler for the kick button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminKick" ) ) { + int uiKickSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + HandleServerAdminKickPlayer( kickVoteMap[ uiKickSelection ] ); + DisableMenu(); + return NULL; + } + //common->Printf( "HANDLE SERVER ADMIN KICK!\n" ); + continue; + // handler for the ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiBanSelection ) { + HandleServerAdminBanPlayer( kickVoteMap[ uiBanSelection ] ); + DisableMenu(); + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + return NULL; + } + continue; + // handler for the remove ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminRemoveBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN REMOVE BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_banList_sel_0" ); + if ( -1 != uiBanSelection ) { + idStr guid = &mainGui->GetStateString( va( "sa_banList_item_%d", uiBanSelection ) )[ 4 ]; + guid = guid.ReplaceChar( '\t', '\0' ); + guid = &guid.c_str()[ strlen( guid.c_str() ) + 1 ]; + HandleServerAdminRemoveBan( guid.c_str() ); + DisableMenu(); + return NULL; + } + continue; + // handler for the switch teams button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminSwitchTeams" ) ) { + if ( gameLocal.IsTeamGame() ) { + int uiSwitchSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiSwitchSelection ) { + HandleServerAdminForceTeamSwitch( kickVoteMap[ uiSwitchSelection ] ); + DisableMenu(); + return NULL; + } + } + continue; + // handler for the show ban list button of the server admin gui + } else if ( !idStr::Icmp( cmd, "populateBanList" ) ) { + gameLocal.PopulateBanList( mainGui ); + continue; +// RAVEN END + } else if ( !idStr::Icmp( cmd, "voteyes" ) ) { + CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteno" ) ) { + CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "bind" ) ) { + if ( args.Argc() - icmd >= 2 ) { + idStr key = args.Argv( icmd++ ); + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "clearbind" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "MAPScan" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" ); + if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) { + gametype = "DM"; + } + + int i, num; + idStr si_map = gameLocal.serverInfo.GetString("si_map"); + const idDict *dict; + + mapList->Clear(); + mapList->SetSelection( -1 ); + num = fileSystem->GetNumMaps(); + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if ( dict ) { + // any MP gametype supported + bool isMP = false; + int igt = GAME_SP + 1; + while ( si_gameTypeArgs[ igt ] ) { + if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) { + isMP = true; + break; + } + igt++; + } + if ( isMP ) { + const char *mapName = dict->GetString( "name" ); + if ( mapName[0] == '\0' ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mapList->Add( i, mapName ); + if ( !si_map.Icmp( dict->GetString( "path" ) ) ) { + mapList->SetSelection( mapList->Num() - 1 ); + } + } + } + } + // set the current level shot + SetMapShot( ); +#endif + return "continue"; + } else if ( !idStr::Icmp( cmd, "click_maplist" ) ) { + SetMapShot( ); + return "continue"; + } else if ( !idStr::Icmp( cmd, "sm_select_player" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + int index = atoi( vcmd.c_str() ); + if( index > 0 && index < MAX_CLIENTS && statSummary && currentMenu == 3 ) { + statManager->UpdateEndGameHud( statSummary, index - 1 ); + } + return "continue"; + } else if ( !idStr::Icmp( cmd, "update_model" ) ) { + UpdateMPSettingsModel( currentGui ); + continue; + } else if( !idStr::Icmp( cmd, "ingameStats" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + // setup the player list + statManager->SetupStatWindow( currentGui ); + } else if( !igArg.Icmp( "spectator" ) ) { + int currentSel = currentGui->State().GetInt( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MAX ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MAX; + } else if( !igArg.Icmp( "dm" ) ) { + int currentSel = currentGui->State().GetInt( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, 0 ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = 0; + } else if( !igArg.Icmp( "strogg" ) ) { + int currentSel = currentGui->State().GetInt( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_STROGG ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_STROGG; + } else if( !igArg.Icmp( "marine" ) ) { + int currentSel = currentGui->State().GetInt( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MARINE ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MARINE; + } + } + continue; + } else if( !idStr::Icmp( cmd, "mainMenu" ) ) { + DisableMenu(); + static idStr menuCmd; + menuCmd.Clear(); // cnicholson: In order to avoid repeated eventnames from screwing up the menu system, clear it. + menuCmd.Append( "main" ); + const char* eventName = ""; + if( args.Argc() - icmd >= 1 ) { + eventName = args.Argv( icmd++ ); + menuCmd.Append( " " ); + menuCmd.Append( eventName ); + } + return menuCmd.c_str(); + } +// RAVEN BEGIN +// cnicholson: The menu calls this prior to entering multiplayer settings. What it does is to check the current crosshair, and compare it +// agasint the list of crosshairs in player.def under the player_marine_mp section. If it finds a match, it assigns the +// crosshair to the next one in the list. If there isn't one, or if its the end of the list, the first found crosshair is used. + else if ( !idStr::Icmp( cmd, "chooseCrosshair" ) ) { +#ifndef _XENON + +#ifndef _XENON + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = def->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { + if ( kv->GetValue() == currentCrosshair.c_str() ) { + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + break; + } + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = def->dict.MatchPrefix("mtr_crosshair", NULL ); + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + } +// RAVEN END + else if( !idStr::Icmp( cmd, "friend" ) ) { + // we friend/unfriend from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + if( ( client < 0 || client >= MAX_CLIENTS ) || !gameLocal.GetLocalPlayer() ) { + continue; + } + + // un-mark this client as a friend + if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.GetLocalPlayer()->IsFriend( client ) ) { + networkSystem->RemoveFriend( client ); + } else { + networkSystem->AddFriend( client ); + } + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + continue; + } else if( !idStr::Icmp( cmd, "mute" ) ) { + // we mute/unmute from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + + if ( gameLocal.GetLocalPlayer() ) { + ClientVoiceMute( client, !gameLocal.GetLocalPlayer()->IsPlayerMuted( client ) ); + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + + continue; + } +//RAVEN BEGIN +//asalmon: pass through some commands that need to be handled in the main menu handle function + else if(strstr( cmd, "LiveInviteAccept" ) == cmd){ +#ifdef _XENON + Live()->SetInvite(); +#endif + } + else if ((strstr( cmd, "FilterMPMapList" ) == cmd) + || (strstr( cmd, "AddMapLive" ) == cmd) + || (strstr( cmd, "RemoveMapLive" ) == cmd) + ) { + static idStr menuCmd; + menuCmd.Clear(); + menuCmd.Append( cmd ); + return menuCmd.c_str(); + } else if( !idStr::Icmp( cmd, "toggleTourney" ) ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } + continue; + } +//RAVEN END + common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd ); + + } + return "continue"; +} + +/* +=============== +idMultiplayerGame::SetShaderParms +=============== +*/ +void idMultiplayerGame::SetShaderParms( renderView_t *view ) { + if ( gameLocal.IsFlagGameType() ) { + view->shaderParms[ 1 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_MARINE ) != FS_AT_BASE ); + view->shaderParms[ 2 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_STROGG ) != FS_AT_BASE ); + } +} + +/* +================ +idMultiplayerGame::Draw +server demo: clientNum == MAX_CLIENTS +================ +*/ +bool idMultiplayerGame::Draw( int clientNum ) { + idPlayer *player, *viewPlayer; + idUserInterface *hud = NULL; + + if ( clientNum == MAX_CLIENTS ) { +// assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + clientNum = ENTITYNUM_NONE; + } + + player = viewPlayer = static_cast( gameLocal.entities[ clientNum ] ); + + if ( player == NULL ) { + return false; + } + + if ( player->spectating ) { + viewPlayer = static_cast( gameLocal.entities[ player->spectator ] ); + if ( viewPlayer == NULL ) { + return false; + } + } + + if ( !viewPlayer->GetRenderView() ) { + return false; + } + + SetShaderParms( viewPlayer->GetRenderView() ); + + // use the hud of the local player + if ( !hud ) { + hud = player->hud; + } + viewPlayer->playerView.RenderPlayerView( hud ); + + // allow force scoreboard to overwrite a fullscreen menu + if ( currentMenu ) { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( !bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 1, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = true; + } +#endif + if ( player->wantSpectate ) { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104249" ) ); + } else { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104250" ) ); + } + // if we died, isChatting is cleared, so re-set our chatting cvar + if ( gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsFakeClient() && !gameLocal.GetLocalPlayer()->isChatting && !gameLocal.GetLocalPlayer()->pfl.dead ) { + cvarSystem->SetCVarBool( "ui_chat", true ); + cvarSystem->SetModifiedFlags( CVAR_USERINFO ); // force update + } + if ( currentMenu == 1 ) { + UpdateMainGui(); + mainGui->Redraw( gameLocal.time ); + } else if( currentMenu == 2 ) { + msgmodeGui->Redraw( gameLocal.time ); + } else if( currentMenu == 3 ) { + DrawStatSummary(); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + SetupBuyMenuItems(); + player->UpdateHudStats( buyMenu ); + buyMenu->HandleNamedEvent( "update_buymenu" ); + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->Redraw(gameLocal.time); +// RITUAL END + } + } else { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = false; + } +#endif + DrawScoreBoard( player ); + } + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud(); +// RAVEN END + return true; +} + +/* +================ +idMultiplayerGame::UpdateHud +================ +*/ +void idMultiplayerGame::UpdateHud( idUserInterface* _mphud ) { + idPlayer *localPlayer; + + if ( !_mphud ) { + return; + } + + // server demos don't have a true local player, but need one for hud updates + localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.IsServerDemoPlaying() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + +//RAVEN BEGIN +//asalmon: Turn on/off the lag icon so that clients know that they are losing connection + if ( networkSystem->ClientGetTimeSinceLastPacket() > 0 && ( networkSystem->ClientGetTimeSinceLastPacket() > cvarSystem->GetCVarInteger("net_clientServerTimeout")*500 ) ) { + _mphud->SetStateBool("IsLagged", true); + } + else{ + _mphud->SetStateBool("IsLagged", false); + } +//RAVEN END + + _mphud->SetStateInt( "marine_score", teamScore[ TEAM_MARINE ] ); + _mphud->SetStateInt( "strogg_score", teamScore[ TEAM_STROGG ] ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + + // Always show GameTime() for WARMUP and COUNTDOWN. + mpGameState_t state = gameState->GetMPGameState(); + _mphud->SetStateString( "timeleft", GameTime() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + /// Set "credits" gui element + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + int cash = 0; + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.IsServerDemoPlaying() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + + idPlayer* specPlayer = NULL; + if ( localPlayer->spectating ) + specPlayer = gameLocal.GetClientByNum( localPlayer->spectator ); + + if ( specPlayer ) + cash = (int)specPlayer->buyMenuCash; + else + cash = (int)localPlayer->buyMenuCash; + + if( localPlayer->CanBuy() ) { + _mphud->SetStateString("credits", va("%s %d %s", common->GetLocalizedString( "#str_122015" ), + cash, common->GetLocalizedString( "#str_122016" ))); + } + else { + _mphud->SetStateString("credits", va("%s %d", common->GetLocalizedString( "#str_122015" ), cash)); + } + } + else + { + _mphud->SetStateString("credits", ""); + } +// RITUAL END + + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + bool inCountdownState = (state == COUNTDOWN); + if( gameLocal.gameType == GAME_TOURNEY ) { + inNonTimedState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_SUDDEN_DEATH); + inCountdownState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_WARMUP); + } + _mphud->SetStateBool( "infinity", ( !timeLimit && !inCountdownState ) || inNonTimedState ); + + if( gameLocal.gameType == GAME_DM ) { + if( rankedPlayers.Num() ) { + _mphud->SetStateString( "player1_name", rankedPlayers[ 0 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player1_score", va( "%d", GetScore( rankedPlayers[ 0 ].First() ) ) ); + _mphud->SetStateString( "player1_rank", "1." ); + + // if we're in the lead or spectating, show the person in 2nd + if( ( (rankedPlayers[ 0 ].First() == localPlayer) || (localPlayer->spectating) ) && rankedPlayers.Num() > 1 ) { + _mphud->SetStateString( "player2_name", rankedPlayers[ 1 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( rankedPlayers[ 1 ].First() ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", rankedPlayers[ 1 ].First()->GetRank() + 1 ) ); + } else if( rankedPlayers[ 0 ].First() != localPlayer && !localPlayer->spectating ) { + // otherwise, show our score + _mphud->SetStateString( "player2_name", localPlayer->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( localPlayer ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", localPlayer->GetRank() + 1 ) ); + } else { + // no person to place in 2nd + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } else { + _mphud->SetStateString( "player1_name", "" ); + _mphud->SetStateString( "player1_score", "" ); + _mphud->SetStateString( "player1_rank", "" ); + + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } + + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DEADZONE ) { + + static int lastMarineScore = teamScore[ TEAM_MARINE ]; + static int lastStroggScore = teamScore[ TEAM_STROGG ]; + int marineScore = teamScore[ TEAM_MARINE ]; + int stroggScore = teamScore[ TEAM_STROGG ]; + const float asymptoticAverageWeight = 0.95f; + + /// Check if Marines have scored since last frame + if( marineScore != lastMarineScore ) + { + /// Pulse the bar's color + marineScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_MARINE ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + marineScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Check if Strogg have scored since last frame + if( stroggScore != lastStroggScore ) + { + /// Pulse the bar's color + stroggScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_STROGG ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + stroggScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Set "gameStatus" gui element + _mphud->SetStateString("gameStatus", "" ); + + _mphud->SetStateFloat( "marine_pulse_amount", marineScoreBarPulseAmount ); + _mphud->SetStateFloat( "strogg_pulse_amount", stroggScoreBarPulseAmount ); + + lastMarineScore = teamScore[ TEAM_MARINE ]; + lastStroggScore = teamScore[ TEAM_STROGG ]; + } + // RITUAL END + + if( gameLocal.gameType == GAME_TOURNEY && localPlayer->GetArena() == MAX_ARENAS ) { + int numWaitingArenaPlayers = 0; + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() && rankedPlayers[ i ].First()->GetArena() == MAX_ARENAS ) { + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers++ ), rankedPlayers[ i ].First()->GetUserInfo()->GetString( "ui_name" ) ); + } + } + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers ), "" ); + _mphud->SetStateBool( "waitroom", true ); + _mphud->SetStateInt( "num_waitroom_players", numWaitingArenaPlayers ); + } else { + _mphud->SetStateBool( "waitroom", false ); + } + + idStr spectateText0; + idStr spectateText1; + idStr spectateText2; + + if( gameLocal.gameType == GAME_TOURNEY ) { + // line 1 - why we aren't playing + if( localPlayer->wantSpectate ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + } else { + rvTourneyArena& currentArena = ((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ); + if( gameState->GetMPGameState() == WARMUP ) { + // grab the reason we aren't playing yet + AllPlayersReady( &spectateText0 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText0 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + spectateText0 = common->GetLocalizedString( "#str_107687" ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ADVANCED ) { + spectateText0 = common->GetLocalizedString( "#str_107688" ); + } else if( ((rvTourneyGameState*)gameState)->HasBye( localPlayer ) ) { + spectateText0 = common->GetLocalizedString( "#str_107709" ); + } else if( currentArena.IsPlaying( localPlayer ) ) { + spectateText0 = va( "%s %d; %s", common->GetLocalizedString( "#str_107716" ), localPlayer->GetArena() + 1, ((rvTourneyGameState*)gameState)->GetRoundDescription() ); + } else if( localPlayer->spectating ) { + // this should only happen if the player was spectating at start of round, but then decides + // to join the tourney + spectateText0 = common->GetLocalizedString( "#str_107684" ); + } + } + + // line 2 - will or wont be seeded, how to cycle + // line 3 - how to enter waiting room + if( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN ) { + if( localPlayer->wantSpectate ) { + spectateText1 = common->GetLocalizedString( "#str_107685" ); + spectateText2 = common->GetLocalizedString( "#str_107695" ); + } else { + spectateText1 = common->GetLocalizedString( "#str_107684" ); + spectateText2 = common->GetLocalizedString( "#str_107694" ); + } + } else if( localPlayer->spectating ) { + if( localPlayer->GetArena() == MAX_ARENAS ) { + spectateText1 = common->GetLocalizedString( "#str_107686" ); + } else { + spectateText1 = va( common->GetLocalizedString( "#str_107670" ), common->KeysFromBinding( "_impulse14" ), common->KeysFromBinding( "_impulse15" ) ); + } + } + } else { + // non-tourney spectate text + if( localPlayer->spectating ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + + // spectating instructions + if( localPlayer->spectator != localPlayer->entityNumber ) { + //cycle & exit follow + spectateText1 = va( common->GetLocalizedString( "#str_107698" ), common->KeysFromBinding( "_attack" ), common->KeysFromBinding( "_moveup" ) ); + } else { + //start follow + spectateText1 = va( common->GetLocalizedString( "#str_108024" ), common->KeysFromBinding( "_attack" ) ); + } + + } + + if( gameState->GetMPGameState() == WARMUP ) { + AllPlayersReady( &spectateText1 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText1 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } + } + + _mphud->SetStateString( "spectatetext0", spectateText0 ); + _mphud->SetStateString( "spectatetext1", spectateText1 ); + _mphud->SetStateString( "spectatetext2", spectateText2 ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + _mphud->StateChanged( gameLocal.time ); + + statManager->UpdateInGameHud( _mphud, ( localPlayer->usercmd.buttons & BUTTON_INGAMESTATS ) != 0 ); + + //update awards + if ( gameLocal.isClient || gameLocal.isListenServer) { + statManager->CheckAwardQueue(); + } +} + +/* +================ +idMultiplayerGame::DrawScoreBoard +================ +*/ +void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) { + if ( player->scoreBoardOpen ) { + if ( !playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( true, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = true; + player->disableHud = true; + } + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameState)->UpdateTourneyBrackets(); + } + UpdateScoreboard( scoreBoard ); + } else { + if ( playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( false, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = false; + player->disableHud = false; + } + } +} + +/* +=============== +idMultiplayerGame::AddChatLine +=============== +*/ +void idMultiplayerGame::AddChatLine( const char *fmt, ... ) +{ + idStr s; + va_list argptr; + va_start( argptr, fmt ); + vsprintf( s, fmt, argptr ); + va_end( argptr ); + PrintChatLine( s, false ); +} + +void idMultiplayerGame::PrintChatLine( const char *message, const bool teamChat ) { + idStr text = message; + text.StripTrailingOnce("\n"); + gameLocal.Printf( "%s\n", text.c_str() ); + + wrapInfo_t wrapInfo; + idStr wrap1; + idStr wrap2; + + idUserInterface *mpHud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->mphud : NULL; + if ( mpHud ) { + wrap1 = text; + wrap2 = text; + do { + memset( &wrapInfo, -1, sizeof ( wrapInfo_t ) ); + mpHud->GetMaxTextIndex( "history1", wrap1.c_str( ), wrapInfo ); + + // If we have a whitespace near the end. Otherwise the user could enter a giant word. + if ( wrapInfo.lastWhitespace != -1 && float( wrapInfo.lastWhitespace ) / float( wrapInfo.maxIndex ) > .75 ) { + wrap2 = wrap1.Left( wrapInfo.lastWhitespace++ ); + + // Just text wrap, no word wrap. + } else if ( wrapInfo.maxIndex != -1 ) { + wrap2 = wrap1.Left( wrapInfo.maxIndex ); + + // We fit in less than a line. + } else { + wrap2 = wrap1; + } + + // Recalc the base string. + wrap1 = wrap2.GetLastColorCode() + wrap1.Right( wrap1.Length( ) - wrap2.Length( ) ); + + // Push to gui. + mpHud->SetStateString( "chattext", wrap2.c_str( ) ); + mpHud->HandleNamedEvent( "addchatline" ); + } while ( wrapInfo.maxIndex != -1 ); + } + + if( chatHistory.Length() + text.Length() > CHAT_HISTORY_SIZE ) { + int removeLength = chatHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + chatHistory.Empty(); + } else { + while( (chatHistory.Length() - removeLength) + text.Length() > CHAT_HISTORY_SIZE ) { + removeLength = chatHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + chatHistory.Empty(); + break; + } + } + } + chatHistory = chatHistory.Right( chatHistory.Length() - removeLength ); + } + + chatHistory.Append( text ); + chatHistory.Append( '\n' ); + + if( mainGui ) { + mainGui->SetStateString( "chat", chatHistory.c_str() ); + } + if( statSummary ) { + statSummary->SetStateString( "chat", chatHistory.c_str() ); + } + + // play chat sound + if( gameLocal.GetLocalPlayer() ) { + if ( teamChat ) { + gameLocal.GetLocalPlayer()->StartSound( "snd_teamchat", SND_CHANNEL_ANY, 0, false, NULL ); + } else { + gameLocal.GetLocalPlayer()->StartSound( "snd_chat", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +void idMultiplayerGame::DrawStatSummary( void ) { + if ( !statSummary->GetStateFloat( "ready" ) ) { + statSummary->SetStateFloat( "ready", 1 ); + statSummary->HandleNamedEvent( "chatFocus" ); + statSummary->StateChanged( gameLocal.time ); + } + statSummary->Redraw( gameLocal.time ); +} + +void idMultiplayerGame::ShowStatSummary( void ) { + if ( !gameLocal.GetLocalPlayer() ) { + assert( false ); + return; + } + DisableMenu( ); + nextMenu = 3; + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.GetLocalPlayer()->GUIMainNotice( "" ); + gameLocal.GetLocalPlayer()->GUIFragNotice( "" ); +} + +/* +================ +idMultiplayerGame::WriteToSnapshot +================ +*/ +void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + int value; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + assert( MAX_CLIENTS % 8 == 0 ); +// RITUAL BEGIN - DeadZone Messages + msg.WriteBits(isBuyingAllowedRightNow, 1); + msg.WriteShort(powerupCount); + msg.WriteFloat( marineScoreBarPulseAmount ); + msg.WriteFloat( stroggScoreBarPulseAmount ); +// RITUAL END + + +// RAVEN BEGIN +// ddynerman: CTF scoring +// FIXME - not in the snapshot + for ( i = 0; i < TEAM_MAX; i++ ) { + msg.WriteShort( teamScore[i] ); + msg.WriteLong( teamDeadZoneScore[i] ); + } +// RAVEN END + + // write ingame bits first, then we only sync down for ingame clients + // do a single write, this doesn't change often it's best to deltify in a single shot + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ingame[ i / 8 ] |= 1 << ( i % 8 ); + } else { + ingame[ i / 8 ] &= ~( 1 << ( i % 8 ) ); + } + } + msg.WriteData( ingame, MAX_CLIENTS / 8 ); + + // those rarely change as well and will deltify away nicely + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + // clamp all values to min/max possible value that we can send over + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + msg.WriteLong( playerState[i].deadZoneScore ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins ); + msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS ); + // only transmit instance info in tourney + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !ent ) { + msg.WriteBits( 0, 1 ); + } else { + msg.WriteBits( 1, 1 ); + value = idMath::ClampInt( 0, MAX_INSTANCES, ent->GetInstance() ); + msg.WriteBits( value, ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( ((idPlayer*)ent)->GetTourneyStatus(), ASYNC_PLAYER_TOURNEY_STATUS_BITS ); + } + } + } + } + + // those change all the time, keep them in a single pack + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping ); + msg.WriteBits( value, ASYNC_PLAYER_PING_BITS ); + } + } +} + +/* +================ +idMultiplayerGame::ReadFromSnapshot +================ +*/ +void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, newInstance; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + isBuyingAllowedRightNow = msg.ReadBits(1); + powerupCount = msg.ReadShort(); + // TTimo: NOTE: sounds excessive to be transmitting floats for that + marineScoreBarPulseAmount = msg.ReadFloat(); + stroggScoreBarPulseAmount = msg.ReadFloat(); + + // CTF/TDM scoring + for( i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = msg.ReadShort( ); + teamDeadZoneScore[ i ] = msg.ReadLong( ); + } + + msg.ReadData( ingame, MAX_CLIENTS / 8 ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( ingame[ i / 8 ] & ( 1 << ( i % 8 ) ) ) { + playerState[i].ingame = true; + } else { + playerState[i].ingame = false; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + playerState[ i ].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].deadZoneScore = msg.ReadLong(); + playerState[ i ].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if( msg.ReadBits( 1 ) ) { + newInstance = msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ); + if( newInstance != ent->GetInstance() ) { + ent->SetInstance( newInstance ); + if( gameLocal.GetLocalPlayer() && i != gameLocal.localClientNum ) { + if( ent->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + ((idPlayer*)ent)->ClientInstanceJoin(); + } else { + ((idPlayer*)ent)->ClientInstanceLeave(); + } + } + } + ((idPlayer*)ent)->SetTourneyStatus( (playerTourneyStatus_t)msg.ReadBits( ASYNC_PLAYER_TOURNEY_STATUS_BITS ) ); + } + } + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + playerState[ i ].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS ); + } + } +} + +// RAVEN BEGIN +// bdube: global item sounds +/* +================ +idMultiplayerGame::PlayGlobalItemAcquireSound +================ +*/ +void idMultiplayerGame::PlayGlobalItemAcquireSound( int defIndex ) { + const idDeclEntityDef* def; + def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, defIndex, false ) ); + if ( !def ) { + gameLocal.Warning ( "NET: invalid entity def index (%d) for global item acquire sound", defIndex ); + return; + } + + if( !gameLocal.GetLocalPlayer() || !gameLocal.currentThinkingEntity || gameLocal.GetLocalPlayer()->GetInstance() == gameLocal.currentThinkingEntity->GetInstance() ) { + soundSystem->PlayShaderDirectly ( SOUNDWORLD_GAME, def->dict.GetString ( "snd_acquire" ) ); + } + + if ( gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND ); + outMsg.WriteBits( defIndex, gameLocal.entityDefBits ); + gameLocal.ServerSendInstanceReliableMessage( gameLocal.currentThinkingEntity, -1, outMsg ); + } +} +// RAVEN END + +/* +================ +idMultiplayerGame::PrintMessageEvent +================ +*/ +void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( to == -1 || ( p && to == p->entityNumber ) ) { + switch ( evt ) { + case MSG_SUICIDE: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_KILLED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_KILLEDTEAM: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_TELEFRAGGED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_DIED: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_VOTE: + AddChatLine( common->GetLocalizedString( "#str_104288" ) ); + break; + case MSG_SUDDENDEATH: + AddChatLine( common->GetLocalizedString( "#str_104287" ) ); + break; + case MSG_FORCEREADY: + AddChatLine( common->GetLocalizedString( "#str_104286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true; + } + break; + case MSG_JOINEDSPEC: + AddChatLine( common->GetLocalizedString( "#str_104285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_TIMELIMIT: + AddChatLine( common->GetLocalizedString( "#str_104284" ) ); + break; + case MSG_FRAGLIMIT: + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { + // RITUAL END + // RAVEN BEGIN + // rhummer: localized "Strogg" and "Marine" + AddChatLine( common->GetLocalizedString( "#str_107665" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + } else { + AddChatLine( common->GetLocalizedString( "#str_104281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + } + break; + case MSG_CAPTURELIMIT: + // RAVEN BEGIN + // rhummer: localized "%s team hit the capture limit." and "Strogg and "Marine" + AddChatLine( common->GetLocalizedString( "#str_108027" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + break; + case MSG_HOLYSHIT: + AddChatLine( common->GetLocalizedString( "#str_106732" ) ); + break; + default: + gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt ); + return; + } + } + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB ); + outMsg.WriteByte( evt ); + outMsg.WriteByte( parm1 ); + outMsg.WriteByte( parm2 ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::PrintMessage +================ +*/ +void idMultiplayerGame::PrintMessage( int to, const char* msg ) { + if( idStr::Length( msg ) >= MAX_PRINT_LEN ) { + common->Warning( "idMultiplayerGame::PrintMessage() - Not transmitting message of length %d", idStr::Length( msg ) ); + return; + } + + AddChatLine( msg ); + + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PRINT ); + outMsg.WriteString( msg ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::CheckSpawns +================ +*/ +void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) { + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast(ent); + + // once we hit sudden death, nobody respawns till game has ended + // no respawns in tourney mode, the tourney manager manually handles spawns + if ( (WantRespawn( p ) || p == spectator) ) { + if ( gameState->GetMPGameState() == SUDDENDEATH && gameLocal.gameType != GAME_TOURNEY ) { + // respawn rules while sudden death are different + // sudden death may trigger while a player is dead, so there are still cases where we need to respawn + // don't do any respawns while we are in end game delay though + if ( gameLocal.IsTeamGame() || p->IsLeader() ) { + //everyone respawns in team games, only fragleaders respawn in DM + p->ServerSpectate( false ); + } else {//if ( !p->IsLeader() ) { + // sudden death is rolling, this player is not a leader, have him spectate + p->ServerSpectate( true ); + CheckAbortGame(); + } + } else { + if ( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType != GAME_TOURNEY ) { + // wait for team to be set before spawning in + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( false ); + } + + } else { + if( p->GetArena() >= 0 && p->GetArena() < MAX_ARENAS ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameState)->GetArena( p->GetArena() ); + if( ( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) && ( p == arena.GetPlayers()[ 0 ] || p == arena.GetPlayers()[ 1 ] ) ) { + // only allow respawn if the arena we're in is active + // and we're one of the assigned players (we're not just spectating it) + p->ServerSpectate( false ); + } + } else { + // always allow respawn in the waiting room + assert( p->GetArena() == MAX_ARENAS ); + p->ServerSpectate( false ); + } + } + } + } + } else if ( p->wantSpectate && !p->spectating ) { + playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets + p->ServerSpectate( true ); + CheckAbortGame(); + } + } +} + +void idMultiplayerGame::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 && gameRenderWorld ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + +void idMultiplayerGame::UpdateLight ( int lightID, idPlayer *player ) { + lights[ lightID ].origin = player->GetPhysics()->GetOrigin() + idVec3( 0, 0, 20 ); + + if ( lightHandles[ lightID ] == -1 ) { + lightHandles[ lightID ] = gameRenderWorld->AddLightDef ( &lights[ lightID ] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[ lightID ], &lights[ lightID ] ); + } +} + +void idMultiplayerGame::CheckSpecialLights( void ) { + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idPlayer *marineFlagCarrier = NULL; + idPlayer *stroggFlagCarrier = NULL; + idPlayer *quadDamageCarrier = NULL; + idPlayer *regenerationCarrier = NULL; + idPlayer *hasteCarrier = NULL; + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast( ent ); + + if( gameLocal.GetLocalPlayer() && p->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + + if ( p->PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + marineFlagCarrier = p; + } + else if ( p->PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + stroggFlagCarrier = p; + } + else if( p->PowerUpActive( POWERUP_QUADDAMAGE ) || p->PowerUpActive( POWERUP_TEAM_DAMAGE_MOD )) { + quadDamageCarrier = p; + } + else if( p->PowerUpActive( POWERUP_REGENERATION ) ) { + regenerationCarrier = p; + } + else if( p->PowerUpActive( POWERUP_HASTE ) ) { + hasteCarrier = p; + } + } + + if ( marineFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_MARINE, marineFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_MARINE ); + } + + if ( stroggFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_STROGG, stroggFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_STROGG ); + } + + if ( quadDamageCarrier ) { + UpdateLight( MPLIGHT_QUAD, quadDamageCarrier ); + } else { + FreeLight( MPLIGHT_QUAD ); + } + + if ( regenerationCarrier ) { + UpdateLight( MPLIGHT_REGEN, regenerationCarrier ); + } else { + FreeLight( MPLIGHT_REGEN ); + } + + if ( hasteCarrier ) { + UpdateLight( MPLIGHT_HASTE, hasteCarrier ); + } else { + FreeLight( MPLIGHT_HASTE ); + } +} + +/* +================ +idMultiplayerGame::ForceReady +================ +*/ +void idMultiplayerGame::ForceReady( ) { + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + idPlayer *p = static_cast( ent ); + if ( !p->IsReady() ) { + PrintMessageEvent( -1, MSG_FORCEREADY, i ); + p->forcedReady = true; + } + } +} + +/* +================ +idMultiplayerGame::ForceReady_f +================ +*/ +void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + gameLocal.Printf( "forceReady: multiplayer server only\n" ); + return; + } + gameLocal.mpGame.ForceReady(); +} + +/* +================ +idMultiplayerGame::DropWeapon +================ +*/ +void idMultiplayerGame::DropWeapon( int clientNum ) { + assert( !gameLocal.isClient ); + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } +// RAVEN BEGIN +// bdube: removed parameter + static_cast< idPlayer* >( ent )->DropWeapon( ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::DropWeapon_f +================ +*/ +void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "clientDropWeapon: only valid in multiplayer\n" ); + return; + } + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::MessageMode_f +================ +*/ +void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) { + gameLocal.mpGame.MessageMode( args ); +} + +/* +================ +idMultiplayerGame::MessageMode +================ +*/ +void idMultiplayerGame::MessageMode( const idCmdArgs &args ) { + const char *mode; + int imode; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientMessageMode: only valid in multiplayer\n" ); + return; + } + if ( !mainGui ) { + common->Printf( "no local client\n" ); + return; + } + mode = args.Argv( 1 ); + if ( !mode[ 0 ] || !gameLocal.IsTeamGame() ) { + imode = 0; + } else { + imode = atoi( mode ); + } + msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" ); + msgmodeGui->SetStateString( "chattext", "" ); + nextMenu = 2; + // let the session know that we want our ingame main menu opened + gameLocal.sessionCommand = "game_startmenu"; +} + +/* +================ +idMultiplayerGame::Vote_f +================ +*/ +void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// shouchard: implemented for testing + if ( args.Argc() < 2 ) { + common->Printf( common->GetLocalizedString( "#str_104418" ) ); + return; + } + + const char *szArg1 = args.Argv(1); + bool voteValue = false; + if ( 0 == idStr::Icmp( szArg1, "yes" ) ) { + voteValue = true; + } + + gameLocal.mpGame.CastVote( gameLocal.localClientNum, voteValue ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::CallVote_f +moved this over the use the packed voting +still only does one vote though, can easily be extended to do more +================ +*/ +void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { + const char *szArg1 = args.Argv(1); + const char *szArg2 = args.Argv(2); + if ( '\0' == *szArg1 ) { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + if ( 0 == idStr::Icmp( szArg1, "restart" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } else if ( 0 == idStr::Icmp( szArg1, "timelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104406" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + voteData.m_timeLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "fraglimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104407" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + voteData.m_fragLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "gametype" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104408" ) ); + common->Printf( common->GetLocalizedString( "#str_104409" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + voteData.m_gameType = gameLocal.mpGame.GameTypeToVote( szArg2 ); + } + else if ( 0 == idStr::Icmp( szArg1, "kick" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104412" ) ); + return; + } + voteData.m_kick = gameLocal.mpGame.GetClientNumFromPlayerName( szArg2 ); + if ( voteData.m_kick >= 0 ) { + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + } else if ( 0 == idStr::Icmp( szArg1, "map" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104413" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_MAP; + voteData.m_map = szArg2; + } else if ( 0 == idStr::Icmp( szArg1, "buying" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122012" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + voteData.m_buying = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "capturelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104415" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + voteData.m_captureLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "autobalance" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104416" ) ); + } + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + voteData.m_teamBalance = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "controlTime" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122002" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + } + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + voteData.m_controlTime = atoi(szArg2 ); + } else { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + if ( voteData.m_fieldFlags != 0 ) { + gameLocal.mpGame.ClientCallPackedVote( voteData ); + } +} + +// RAVEN BEGIN +// shouchard: added voice mute and unmute console commands; sans XBOX to not step on their live voice stuff +#ifndef _XBOX +/* +================ +idMultiplayerGame::VoiceMute_f +================ +*/ +void idMultiplayerGame::VoiceMute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoicemute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), true ); +} + +/* +================ +idMultiplayerGame::VoiceUnmute_f +================ +*/ +void idMultiplayerGame::VoiceUnmute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoiceunmute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), false ); +} + +// RAVEN END +#endif // _XBOX + +// RAVEN BEGIN +/* +================ +idMultiplayerGame::ForceTeamChange_f +================ +*/ +void idMultiplayerGame::ForceTeamChange_f( const idCmdArgs &args) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Forces player to change teams. Usage: ForceTeamChange \n" ); + return; + } + + idStr clientId; + int clientNum; + + clientId = args.Argv( 1 ); + if ( !clientId.IsNumeric() ) { + common->Printf( "usage: ForceTeamChange \n" ); + return; + } + + clientNum = atoi( clientId ); + + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + } + +} + + +/* +================ +idMultiplayerGame::RemoveClientFromBanList_f +================ +*/ +void idMultiplayerGame::RemoveClientFromBanList_f( const idCmdArgs& args ) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Remove player from banlist. Usage: RemoveClientFromBanList \n" ); + return; + } + + idStr clientId; + clientId = args.Argv( 1 ); + int clientNum; + + if ( !clientId.IsNumeric() ) { + common->Printf( "Usage: RemoveClientFromBanList \n" ); + return; + } + + clientNum = atoi( clientId ); + + const char *clientGuid = networkSystem->GetClientGUID( clientNum ); // gameLocal.GetGuidByClientNum( clientNum ); + + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + // remove from the ban list + gameLocal.RemoveGuidFromBanList( clientGuid ); + } + +} + +/* +================ +idMultiplayerGame::ProcessRconReturn +================ +*/ +void idMultiplayerGame::ProcessRconReturn( bool success ) { + + if( success ) { + mainGui->HandleNamedEvent("adminPasswordSuccess"); + } else { + mainGui->HandleNamedEvent("adminPasswordFail"); + } + + +} + + +// RAVEN END + +/* +================ +idMultiplayerGame::ServerStartVote +================ +*/ +void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) { + int i; + + assert( vote == VOTE_NONE ); + + // setup + yesVotes = 1; + noVotes = 0; + vote = voteIndex; + voteValue = value; + voteTimeOut = gameLocal.time + 20000; + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } +} + +/* +================ +idMultiplayerGame::ClientStartVote +================ +*/ +void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteString( _voteString ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + voteString = _voteString; + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); +// RAVEN BEGIN +// shouchard: better info when a vote called in the chat buffer + AddChatLine( voteString ); // TODO: will push this into a UI field later +// shouchard: display the vote called text on the hud + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } +// RAVEN END + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ClientUpdateVote +================ +*/ +void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + const char * localizedString = 0; + idPlayer* player = gameLocal.GetLocalPlayer( ); + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE ); + outMsg.WriteByte( status ); + outMsg.WriteByte( yesCount ); + outMsg.WriteByte( noCount ); +// RAVEN BEGIN +// shouchard: multifield vote support + if ( VOTE_MULTIFIELD != vote ) { + outMsg.WriteByte( 0 ); + } else { + outMsg.WriteByte( 1 ); + outMsg.WriteShort( voteData.m_fieldFlags ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + outMsg.WriteString( voteData.m_map.c_str() ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } else { + currentVoteData = voteData; + } +// RAVEN END + + if ( vote == VOTE_NONE ) { + // clients coming in late don't get the vote start and are not allowed to vote + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + } + return; + } + + switch ( status ) { + case VOTE_FAILED: + localizedString = common->GetLocalizedString( "#str_104278" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_FAILED, gameLocal.time ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_PASSED: + localizedString = common->GetLocalizedString( "#str_104277" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_PASSED, gameLocal.time ); + break; + case VOTE_RESET: + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_ABORTED: + localizedString = common->GetLocalizedString( "#str_104276" ); + AddChatLine( localizedString ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_UPDATE: + if ( player && player->mphud && voted ) { + player->mphud->SetStateString( "voteNoticeText", va("^:%s\n%s: %d %s: %d", + common->GetLocalizedString( "#str_107724" ), + common->GetLocalizedString( "#str_107703" ), + yesCount, + common->GetLocalizedString( "#str_107704" ), + noCount ) ); + } + + if ( mainGui ) { + mainGui->SetStateInt( "playerVoted", voted ); + } + break; + default: + break; + } + + if ( gameLocal.isClient ) { + yesVotes = yesCount; + noVotes = noCount; + } + +// RAVEN BEGIN +// shouchard: remove vote notification + if ( VOTE_FAILED == status || VOTE_PASSED == status || VOTE_RESET == status ) { + ClearVote(); + } + + if ( mainGui ) { + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), (int)yesVotes, (int)noVotes ) ); + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::ClientCallVote +================ +*/ +void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE ); + outMsg.WriteByte( voteIndex ); + outMsg.WriteString( voteValue ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::CastVote +================ +*/ +void idMultiplayerGame::CastVote( int clientNum, bool castVote ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } + + if ( gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE ); + outMsg.WriteByte( castVote ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // sanity + if ( vote == VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104275" ); + common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum ); + return; + } + if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104274" ); + common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote ); + return; + } + + if ( castVote ) { + playerState[ clientNum ].vote = PLAYER_VOTE_YES; + yesVotes++; + } else { + playerState[ clientNum ].vote = PLAYER_VOTE_NO; + noVotes++; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerCallVote +================ +*/ +void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) { + vote_flags_t voteIndex; + int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex, vote_buying; //, vote_kickIndex; +// RAVEN BEGIN +// shouchard: added capture limit and autobalance + int vote_captureLimit; + bool vote_autobalance; +// RAVEN END + int vote_controlTime; + char value[ MAX_STRING_CHARS ]; + + assert( clientNum != -1 ); + assert( !gameLocal.isClient ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + voteIndex = (vote_flags_t)msg.ReadByte( ); + msg.ReadString( value, sizeof( value ) ); + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + switch ( voteIndex ) { + case VOTE_RESTART: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104271" ) ); + break; + } + case VOTE_NEXTMAP: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104272" ) ); + break; + } + case VOTE_TIMELIMIT: { + vote_timeLimit = strtol( value, NULL, 10 ); + if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum ); + return; + } + if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104268" ), vote_timeLimit ) ); + break; + } + case VOTE_FRAGLIMIT: { + vote_fragLimit = strtol( value, NULL, 10 ); + if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum ); + return; + } + if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104303" ), common->GetLocalizedString( "#str_104265" ), vote_fragLimit ) ); + break; + } + case VOTE_GAMETYPE: { +// RAVEN BEGIN +// shouchard: removed magic numbers & added CTF type + vote_gameTypeIndex = strtol( value, NULL, 10 ); + assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex < VOTE_GAMETYPE_COUNT ); + idStr::Copynz( value, VoteGameTypeToString( vote_gameTypeIndex ), sizeof( value ) ); + if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104258" ), value ) ); + break; + } + case VOTE_KICK: { + vote_clientNum = strtol( value, NULL, 10 ); + if ( vote_clientNum == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) ); + break; + } + case VOTE_MAP: { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) { + + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value ); + return; + } + int num = fileSystem->GetNumMaps(); + int i; + const idDict *dict = NULL; + bool haveMap = false; + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if( !dict ) { + gameLocal.Warning( "idMultiplayerGame::ServerCallVote() - bad map decl index on vote\n" ); + break; + } + if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) { + haveMap = true; + break; + } + } + if ( !haveMap ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104296", value ); + common->Printf( "client %d: map not found: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104256" ), dict ? dict->GetString( "name" ) : value ) ); +#endif + break; + } + case VOTE_BUYING: { + vote_buying = strtol( value, NULL, 10 ); + if ( vote_buying == gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + common->DPrintf( "client %d: already at the voted buying mode\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_122014" ), vote_buying ) ); + break; + } +// RAVEN BEGIN +// shouchard: added capture limit, round limit, and autobalance + case VOTE_CAPTURELIMIT: { + vote_captureLimit = strtol( value, NULL, 10 ); + if ( vote_captureLimit == gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + common->DPrintf( "client %d: already at the voted Capture Limit\n", clientNum ); + return; + } + if ( vote_captureLimit < si_captureLimit.GetMinValue() || vote_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_captureLimit" ); + break; + } + // round limit is for tourneys + case VOTE_ROUNDLIMIT: { + // need a CVar or something to change here + break; + } + case VOTE_AUTOBALANCE: { + vote_autobalance = (0 != strtol( value, NULL, 10 ) ); + if ( vote_autobalance == gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + common->DPrintf( "client %d: already at the voted balance teams\n", clientNum ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_autobalance" ); + break; + } +// RAVEN END + case VOTE_CONTROLTIME: { + vote_controlTime = strtol( value, NULL, 10 ); + if ( vote_controlTime == gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + common->DPrintf( "client %d: already at the voted Control Time\n", clientNum ); + return; + } + if ( vote_controlTime < si_controlTime.GetMinValue() || vote_controlTime > si_controlTime.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122018" ); + common->DPrintf( "client %d: controlTime value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_controlTime" ); + break; + } + default: { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104297", va( "%d", ( int )voteIndex ) ); + common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex ); + } + } +} + + +/* +================ +idMultiplayerGame::DisconnectClient +================ +*/ +void idMultiplayerGame::DisconnectClient( int clientNum ) { + // gameLocal.entities[ clientNum ] could be null if server is shutting down + if( gameLocal.entities[ clientNum ] ) { + // only kill non-spectators + if( !((idPlayer*)gameLocal.entities[ clientNum ])->spectating ) { + static_cast( gameLocal.entities[ clientNum ] )->Kill( true, true ); + } + statManager->ClientDisconnect( clientNum ); + } + + delete gameLocal.entities[ clientNum ]; + + UpdatePlayerRanks(); + CheckAbortGame(); + + privatePlayers &= ~( 1 << clientNum ); + + // update serverinfo + UpdatePrivatePlayerCount(); +} + +/* +================ +idMultiplayerGame::CheckAbortGame +================ +*/ +void idMultiplayerGame::CheckAbortGame( void ) { + // only checks for aborts -> game review below + if ( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != GAMEON && gameState->GetMPGameState() != SUDDENDEATH ) { + return; + } + + // in tourney, if we don't have enough clients to play we need to cycle back to + // warmup to re-seed + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( !EnoughClientsToPlay() ) { + gameState->NewState( WARMUP ); + } + } else { + if ( !EnoughClientsToPlay() && TimeLimitHit() ) { + gameState->NewState( GAMEREVIEW ); + } + } +} + +/* +================ +idMultiplayerGame::WantKilled +================ +*/ +void idMultiplayerGame::WantKilled( int clientNum ) { + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Kill( false, false ); + } +} + +/* +================ +idMultiplayerGame::ClearVote +================ +*/ +void idMultiplayerGame::ClearVote( int clientNum ) { + int start = 0; + int end = MAX_CLIENTS; + + if( clientNum != -1 ) { + start = clientNum; + end = clientNum + 1; + } + + for ( int i = start; i < end; i++ ) { + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } + // clear the local demo player's vote too + if ( clientNum == -1 && gameLocal.IsServerDemoPlaying() ) do { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } while(0); + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + mainGui->StateChanged( gameLocal.time ); + } +} +/* +================ +idMultiplayerGame::MapRestart +================ +*/ +void idMultiplayerGame::MapRestart( void ) { + int clientNum; + // jshepard: clean up votes + ClearVote(); + + ClearAnnouncerSounds(); + + assert( !gameLocal.isClient ); + if ( gameLocal.GameState() != GAMESTATE_SHUTDOWN && gameState->GetMPGameState() != WARMUP ) { + gameState->NewState( WARMUP ); + // force an immediate state detection/update, otherwise if we update our state this + // same frame we'll miss transitions + gameState->SendState( serverReliableSender.To( -1 ) ); + + gameState->SetNextMPGameState( INACTIVE ); + gameState->SetNextMPGameStateTime( 0 ); + + } + + // mekberg: moved this before the updateUI just in case these values weren't reset. + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } + + // mekberg: Re-wrote this loop to always updateUI. Previously the player would be + // on a team but the UI wouldn't know about it + // shouchard: balance teams extended to CTF + for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) { + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + // mekberg: clear wins only on map restart + idPlayer *player = static_cast( gameLocal.entities[ clientNum ] ); + SetPlayerWin( player, 0 ); + + /*if( clientNum == gameLocal.localClientNum ) { + if ( player->alreadyDidTeamAnnouncerSound ) { + player->alreadyDidTeamAnnouncerSound = false; + } else { + if ( gameLocal.IsTeamGame() ) { + player->alreadyDidTeamAnnouncerSound = true; + if( player->team == TEAM_STROGG ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( player->team == TEAM_MARINE ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + }*/ + // let the player rejoin the team through normal channels + //player->ServerSpectate( true ); + //player->team = -1; + //player->latchedTeam = -1; + + // shouchard: BalanceTDM->BalanceTeam + //if ( gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // player->BalanceTeam(); + //} + + // core is in charge of syncing down userinfo changes + // it will also call back game through SetUserInfo with the current info for update + /*cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );*/ + } + } +} + +/* +================ +idMultiplayerGame::SwitchToTeam +================ +*/ +void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) { + assert( gameLocal.IsTeamGame() ); + + assert( oldteam != newteam ); + assert( !gameLocal.isClient ); + + if ( !gameLocal.isClient && newteam >= 0 ) { + // clients might not have userinfo of joining client at this point, so + // send down the player's name + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p->wantSpectate ) { + PrintMessage( -1, va( common->GetLocalizedString( "#str_104280" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ), newteam ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ) ); + } + } + + if ( oldteam != -1 ) { + // kill and respawn + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( p->IsInTeleport() ) { + p->ServerSendInstanceEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 ); + p->SetPrivateCameraView( NULL ); + } +//RITUAL BEGIN + p->inventory.carryOverWeapons = 0; + p->ResetCash(); +//RITUAL END + p->Kill( true, true ); + CheckAbortGame(); + } +} + +/* +================ +idMultiplayerGame::JoinTeam +================ +*/ +void idMultiplayerGame::JoinTeam( const char* team ) { + if( !idStr::Icmp( team, "auto" ) ) { + int teamCount[ TEAM_MAX ]; + idEntity *ent; + + memset( teamCount, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ ((idPlayer*)ent)->team ]++; + } + } + } + + int minCount = idMath::INT_MAX; + int minCountTeam = -1; + for( int i = 0; i < TEAM_MAX; i++ ) { + if( teamCount[ i ] < minCount ) { + minCount = teamCount[ i ]; + minCountTeam = i; + } + } + + if( minCountTeam >= 0 && minCountTeam < TEAM_MAX ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ minCountTeam ] ); + } else { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ gameLocal.random.RandomInt( TEAM_MAX - 1 ) ] ); + } + } else if( !idStr::Icmp( team, "spectator" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + if( !idStr::Icmp( team, teamNames[ i ] ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ i ] ); + break; + } + } + if( i >= TEAM_MAX ) { + gameLocal.Warning( "idMultiplayerGame::JoinTeam() - unknown team '%s'\n", team ); + } + } +} + +/* +================ +idMultiplayerGame::ProcessChatMessage +================ +*/ +void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + const char *suffix = NULL; + int send_to; // 0 - all, 1 - specs, 2 - team + int i; + idEntity *ent; + idPlayer *p; + idStr suffixed_name; + idStr prefixed_text; + + assert( !gameLocal.isClient ); + + if ( clientNum >= 0 ) { + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating && ( p->wantSpectate || gameLocal.gameType == GAME_TOURNEY ) ) { + suffix = "spectating"; + if ( team || ( !g_spectatorChat.GetBool() && ( gameState->GetMPGameState() == GAMEON || gameState->GetMPGameState() == SUDDENDEATH ) ) ) { + // to specs + send_to = 1; + } else { + // to all + send_to = 0; + } + } else if ( team ) { + suffix = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, p->team ? "Strogg^0" : "Marine^0" ); + // to team + send_to = 2; + } else { + if( gameLocal.gameType == GAME_TOURNEY ) { + suffix = va( "Arena %d", (p->GetArena() + 1) ); + } + // to all + send_to = 0; + } + } else { + p = NULL; + send_to = 0; + } + // put the message together + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( ( send_to == 2 ) ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + + if ( suffix ) { + suffixed_name = va( "^0%s^0 (%s)", name, suffix ); + } else { + suffixed_name = va( "^0%s^0", name ); + } + if( p && send_to == 2 ) { + prefixed_text = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, common->GetLocalizedString( text ) ); + } else { + prefixed_text = common->GetLocalizedString( text ); + } + + if( suffixed_name.Length() + prefixed_text.Length() >= 240 ) { + gameLocal.Warning( "idMultiplayerGame::ProcessChatMessage() - Chat line too long\n" ); + return; + } + + outMsg.WriteString( suffixed_name ); + outMsg.WriteString( prefixed_text ); + outMsg.WriteString( "" ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *to = static_cast< idPlayer * >( ent ); + switch( send_to ) { + case 0: + if ( !p || !to->IsPlayerMuted( p ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 1: + if ( !p || ( to->spectating && !to->IsPlayerMuted( p ) ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 2: + if ( !p || ( to->team == p->team && !to->IsPlayerMuted( p ) ) ) { + if ( !to->spectating ) { + if ( i == gameLocal.localClientNum ) { + PrintChatLine( va( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ), true ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + } + break; + } + } +} + +/* +================ +idMultiplayerGame::Precache +================ +*/ +void idMultiplayerGame::Precache( void ) { + int i; + + if ( !gameLocal.isMultiplayer ) { + return; + } + gameLocal.FindEntityDef( "player_marine", false ); + + // MP game sounds + for ( i = 0; i < AS_NUM_SOUNDS; i++ ) { + declManager->FindSound( announcerSoundDefs[ i ], false ); + } + + // MP guis. just make sure we hit all of them + i = 0; + while ( MPGuis[ i ] ) { + uiManager->FindGui( MPGuis[ i ], true ); + i++; + } +} + +/* +================ +idMultiplayerGame::ToggleSpectate +================ +*/ +void idMultiplayerGame::ToggleSpectate( void ) { + bool spectating; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( spectating ) { + // always allow toggling to play + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + } else { + // only allow toggling to spectate if spectators are enabled. + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_106747" ) ); + } + } +} + +/* +================ +idMultiplayerGame::ToggleReady +================ +*/ +void idMultiplayerGame::ToggleReady( void ) { + bool ready; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + if ( lastReadyToggleTime == -1 ) { + lastReadyToggleTime = gameLocal.time; + } else { + int currentTime = gameLocal.time; + if ( currentTime - lastReadyToggleTime < 500 ) { + return; + } else { + lastReadyToggleTime = currentTime; + } + } + + ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready ) { + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + } else { + cvarSystem->SetCVarString( "ui_ready", "Ready" ); + } +} + +/* +================ +idMultiplayerGame::ToggleTeam +================ +*/ +void idMultiplayerGame::ToggleTeam( void ) { + bool team; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + // RAVEN BEGIN + // ddynerman: new multiplayer teams + team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Marine" ) == 0 ); + if ( team ) { + cvarSystem->SetCVarString( "ui_team", "Strogg" ); + } else { + cvarSystem->SetCVarString( "ui_team", "Marine" ); + } + // RAVEN END +} + +/* +================ +idMultiplayerGame::ToggleUserInfo +================ +*/ +void idMultiplayerGame::ThrottleUserInfo( void ) { + int i; + + if ( gameLocal.localClientNum == MAX_CLIENTS ) { + // repeater; UserInfo doesn't get changed in-game anyway. + return; + } + + assert( gameLocal.localClientNum >= 0 ); + + i = 0; + while ( ThrottleVars[ i ] ) { + if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ), + cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) { + if ( gameLocal.realClientTime < switchThrottle[ i ] ) { + AddChatLine( common->GetLocalizedString( "#str_104299" ), common->GetLocalizedString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 ); + cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) ); + } else { + switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000; + } + } + i++; + } +} + +/* +================ +idMultiplayerGame::CanPlay +================ +*/ +bool idMultiplayerGame::CanPlay( idPlayer *p ) { + return !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::EnterGame +================ +*/ +void idMultiplayerGame::EnterGame( int clientNum ) { + assert( !gameLocal.isClient ); + + if ( !playerState[ clientNum ].ingame ) { + playerState[ clientNum ].ingame = true; + if ( gameLocal.isMultiplayer ) { + // can't use PrintMessageEvent as clients don't know the nickname yet + //gameLocal.ServerSendChatMessage( -1, common->GetLocalizedString( "#str_102047" ), va( common->GetLocalizedString( "#str_107177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + } + + // mark them as private and update si_numPrivatePlayers + for( int i = 0; i < privateClientIds.Num(); i++ ) { + int num = networkSystem->ServerGetClientNum( privateClientIds[ i ] ); + + // check for timed out clientids + if( num < 0 ) { + privateClientIds.RemoveIndex( i ); + i--; + continue; + } + + if( num == clientNum ) { + privatePlayers |= (1 << clientNum); + } + } + + // update serverinfo + UpdatePrivatePlayerCount(); + } +} + +/* +================ +idMultiplayerGame::WantRespawn +================ +*/ +bool idMultiplayerGame::WantRespawn( idPlayer *p ) { + return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, false ); +} + +/* +================ +idMultiplayerGame::UpdateMPSettingsModel +================ +*/ +void idMultiplayerGame::UpdateMPSettingsModel( idUserInterface* currentGui ) { + if ( !currentGui ) { + return; + } + + const char *model; + idPlayer *localP = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() && localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + cvarSystem->SetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ), model ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( "def_default_model" ); + cvarSystem->SetCVarString( "ui_model", model ); + } + } + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + currentGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + currentGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + currentGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + currentGui->SetStateBool( "need_update", true ); + } +} + +/* +================ +idMultiplayerGame::VoiceChatTeam +================ +*/ +void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, true ); +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) { + idBitMsg outMsg; + byte msgBuf[128]; + const char *voc; + const idDict *spawnArgs; + const idKeyValue *keyval; + int index; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientVoiceChat: only valid in multiplayer\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "clientVoiceChat: bad args\n" ); + return; + } + // throttle + if ( gameLocal.realClientTime < voiceChatThrottle ) { + return; + } + + voc = args.Argv( 1 ); + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + index = 0; + while ( keyval ) { + if ( !keyval->GetValue().Icmp( voc ) ) { + break; + } + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index++; + } + if ( !keyval ) { + common->Printf( "Voice command not found: %s\n", voc ); + return; + } + voiceChatThrottle = gameLocal.realClientTime + 1000; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT ); + outMsg.WriteLong( index ); + outMsg.WriteBits( team ? 1 : 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ProcessVoiceChat +================ +*/ +void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) { + const idDict *spawnArgs; + const idKeyValue *keyval; + idStr name; + idStr snd_key; + idStr text_key; + idPlayer *p; + + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating ) { + return; + } + + // lookup the sound def + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + while ( index > 0 && keyval ) { + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index--; + } + if ( !keyval ) { + common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index ); + return; + } + snd_key = keyval->GetKey(); + name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" ); + sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() ); + if ( team || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEREVIEW ) { + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) ); + } else { + p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL ); + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL ); + } +} + +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat +/* +================ +idMultiplayerGame::ClientVoiceMute +================ +*/ +void idMultiplayerGame::ClientVoiceMute( int muteClient, bool mute ) { + // clients/listen server only + assert( gameLocal.isListenServer || gameLocal.isClient ); + + if ( NULL == gameLocal.GetLocalPlayer() ) { + return; + } + + if ( muteClient == -1 || !gameLocal.mpGame.IsInGame( muteClient ) ) { + gameLocal.Warning( "idMultiplayerGame::ClientVoiceMute() - Invalid client '%d' specified", muteClient ); + return; + } + + // do the mute/unmute + gameLocal.GetLocalPlayer()->MutePlayer( muteClient, mute ); + + // tell the server + if( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING ); + outMsg.WriteByte( muteClient ); + outMsg.WriteByte( mute ? 1 : 0 ); // 1 for mute, 0 for unmute + networkSystem->ClientSendReliableMessage( outMsg ); + } + + // display some niceties + common->Printf( "Player %s's has been %s.\n", gameLocal.GetUserInfo( muteClient )->GetString( "ui_name" ), mute ? "muted" : "unmuted" ); +} + +/* +================ +idMultiplayerGame::GetClientNumFromPlayerName +================ +*/ +int idMultiplayerGame::GetClientNumFromPlayerName( const char *playerName ) { + if ( NULL == playerName || '\0' == *playerName ) { + return -1; + } + + int clientNum = -1; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + if ( 0 == idStr::Icmp( gameLocal.userInfo[ i ].GetString( "ui_name" ), playerName ) ) { + clientNum = i; + break; + } + } + } + + if ( -1 == clientNum ) { + common->Warning( "idMultiplayerGame::GetClientNumFromPlayerName(): unknown player '%s'", playerName ); + } + + return clientNum; +} + +/* +================ +idMultiplayerGame::ServerHandleVoiceMuting +================ +*/ +void idMultiplayerGame::ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ) { + assert( !gameLocal.isClient ); + + idPlayer *playerSrc = gameLocal.GetClientByNum( clientSrc ); + idPlayer *playerDest = gameLocal.GetClientByNum( clientDest ); + + if ( NULL == playerSrc ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientSrc ); + return; + } + + if ( NULL == playerDest ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientDest ); + return; + } + + if ( mute ) { + playerSrc->MutePlayer( playerDest, true ); + common->DPrintf( "DEBUG: client %s muted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } else { + playerSrc->MutePlayer( playerDest, false ); + common->DPrintf( "DEBUG: client %s unmuted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } +} + + +/* +================ +idMultiplayerGame::ClearAnnouncerSounds + +This method deletes unplayed announcer sounds at the end of a game round. +This fixes a bug where the round time warnings were being played from +previous rounds. +================ +*/ +void idMultiplayerGame::ClearAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + snd->announcerSoundNode.Remove ( ); + delete snd; + } + + announcerPlayTime = 0; +} + +/* +================ +idMultiplayerGame::HandleServerAdminBanPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminBanPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + if ( gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban the host!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i ban", clientNum ) ); + } else { + if ( clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban yourserlf!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i ban", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminRemoveBan +================ +*/ +void idMultiplayerGame::HandleServerAdminRemoveBan( const char * clientGuid ) { + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + gameLocal.RemoveGuidFromBanList( clientGuid ); + } else { + int clientNum = gameLocal.GetClientNumByGuid( clientGuid ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon removeClientFromBanList %d", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminKickPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminKickPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminKickPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i", clientNum ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminForceTeamSwitch +================ +*/ +void idMultiplayerGame::HandleServerAdminForceTeamSwitch( int clientNum ) { + if ( !gameLocal.IsTeamGame() ) { + return; + } + + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminForceTeamSwitch: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "forceTeamChange %d\n", clientNum)); + +/* if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + }*/ + } else { +/* idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVER_ADMIN ); + outMsg.WriteByte( SERVER_ADMIN_FORCE_SWITCH ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); */ + + //jshepard: need to be able to do this via rcon + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon forceTeamChange %d\n", clientNum)); + + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminCommands +================ +*/ +bool idMultiplayerGame::HandleServerAdminCommands( serverAdminData_t &data ) { + bool restartNeeded = false; + bool nextMapNeeded = false; + bool anyChanges = false; + bool runPickMap = false; + int nGameType = 0; + idStr currentMap = si_map.GetString( ); + + const char *szGameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( szGameType, "DM" ) ) { + nGameType = GAME_DM; + } else if ( 0 == idStr::Icmp( szGameType, "Team DM" ) ) { + nGameType = GAME_TDM; + } else if ( 0 == idStr::Icmp( szGameType, "CTF" ) ) { + nGameType = GAME_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "Tourney" ) ) { + nGameType = GAME_TOURNEY; + } else if ( 0 == idStr::Icmp( szGameType, "Arena CTF" ) ) { + nGameType = GAME_ARENA_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "DeadZone" ) ) { + nGameType = GAME_DEADZONE; + } else { + nGameType = GAME_SP; + } + if ( nGameType != data.gameType ) { + + switch ( data.gameType ) { + case GAME_TDM: szGameType = "Team DM"; runPickMap = true; break; + case GAME_TOURNEY: szGameType = "Tourney"; runPickMap = true; break; + case GAME_CTF: szGameType = "CTF"; runPickMap = true; break; + case GAME_ARENA_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + + // mekberg: hack, if we had 1f ctf the gui index wouldn't be off =( + case GAME_1F_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + case GAME_DEADZONE: szGameType = "DeadZone"; runPickMap = true; break; + default: + case GAME_DM: szGameType = "DM"; break; + } + + //we're going to reset the map here, so make sure to kill the active vote. + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + + si_gameType.SetString( szGameType ); + if( runPickMap && gameLocal.isServer ) { + //set the selected map to the admin data value, then make sure it can run the selected gametype. + si_map.SetString( data.mapName.c_str() ); + if( PickMap( szGameType ) || idStr::Icmp( si_map.GetString( ), currentMap.c_str( ) ) ) { + nextMapNeeded = true; + restartNeeded = false; + data.mapName = idStr( si_map.GetString() ); + data.restartMap = true; + } + } + } + + if ( gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) != data.buying ) + restartNeeded = true; + + // Rcon these cvars if this isn't the server. We can trust the input from the gui that the + // gametype and map always match. + if ( !gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_autoBalance %d", data.autoBalance ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_isBuyingEnabled %d", data.buying ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_captureLimit %d", data.captureLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_controlTime %d", data.controlTime ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_fragLimit %d", data.fragLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_gameType %s", szGameType ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_map %s", data.mapName.c_str() ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_tourneyLimit %d", data.tourneyLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_minPlayers %d", data.minPlayers ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_timeLimit %d", data.timeLimit ) ); + if( runPickMap ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon verifyServerSettings" ) ); + } + + if( data.shuffleTeams ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon shuffleTeams" ); + } + + if( restartNeeded || data.restartMap || nextMapNeeded || idStr::Icmp( gameLocal.serverInfo.GetString( "si_map" ), data.mapName.c_str() ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverMapRestart" ); + } + else + { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon rescanSI" ); + } + + return true; + } + + if ( data.restartMap ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + } + + if ( data.shuffleTeams ) { + ShuffleTeams(); + anyChanges = true; + } + + //this section won't be encountered if the gametype was changed. But that's ok. + if ( data.mapName.c_str() && idStr::Icmp( data.mapName.c_str(), si_map.GetString() ) ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + si_map.SetString(data.mapName.c_str()); + cvarSystem->SetCVarString( "si_map", data.mapName.c_str() ); + nextMapNeeded = true; + anyChanges = true; + } + + if ( data.captureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + si_captureLimit.SetInteger( data.captureLimit ); + anyChanges = true; + } + if ( data.fragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + si_fragLimit.SetInteger( data.fragLimit ); + anyChanges = true; + } + if ( data.tourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + si_tourneyLimit.SetInteger( data.tourneyLimit ); + anyChanges = true; + } + if ( data.timeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + si_timeLimit.SetInteger( data.timeLimit ); + anyChanges = true; + } + if ( data.buying != gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + si_isBuyingEnabled.SetInteger( data.buying ); + anyChanges = true; + restartNeeded = true; + } + if ( data.autoBalance != gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + si_autobalance.SetBool( data.autoBalance ); + anyChanges = true; + } + if ( data.controlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + si_controlTime.SetInteger( data.controlTime ); + anyChanges = true; + } + + if ( gameLocal.NeedRestart() || restartNeeded || nextMapNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + return anyChanges; +} + + +// RAVEN END + +/* +=============== +idMultiplayerGame::WriteStartState +=============== +*/ + void idMultiplayerGame::WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ) { + int i; + idEntity *ent; + + // send the start time + msg.WriteLong( matchStartedTime ); + // send the powerup states and the spectate states + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( withLocalClient || i != clientNum ) && ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + msg.WriteShort( i ); + msg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups ); + msg.WriteBits( ent->GetInstance(), ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 ); + } + } + msg.WriteShort( MAX_CLIENTS ); +} + +/* +================ +idMultiplayerGame::ServerWriteInitialReliableMessages +================ +*/ +void idMultiplayerGame::ServerWriteInitialReliableMessages( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE ); + WriteStartState( clientNum, outMsg, false ); + sender.Send( outMsg ); + + // we send SI in connectResponse messages, but it may have been modified already + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + if ( sender.GetChannelType() == CHANNEL_DEST_RELIABLE_REPEATER ) { + assert( gameLocal.isRepeater ); + outMsg.WriteDeltaDict( gameLocal.repeaterInfo, NULL ); + } else { + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + } + sender.Send( outMsg ); + + gameState->SendInitialState( sender, clientNum ); +} + +/* +================ +idMultiplayerGame::ClientReadStartState +================ +*/ +void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) { + int i, client, powerup; + + assert( gameLocal.isClient ); + + // read the state in preparation for reading snapshot updates + matchStartedTime = msg.ReadLong( ); + while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::GetClassType() ) ); +// RAVEN END + powerup = msg.ReadShort(); + + int instance = ( msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ) ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->SetInstance( instance ); + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate ); + + // set powerups after we get instance information for this client + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( powerup & ( 1 << i ) ) { + static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 ); + } + } + } +} + +const char* idMultiplayerGame::announcerSoundDefs[ AS_NUM_SOUNDS ] = { + // General announcements + "announce_general_one", // AS_GENERAL_ONE + "announce_general_two", // AS_GENERAL_TWO + "announce_general_three", // AS_GENERAL_THREE + "announce_general_you_win", // AS_GENERAL_YOU_WIN + "announce_general_you_lose", // AS_GENERAL_YOU_LOSE + "announce_general_fight", // AS_GENERAL_FIGHT + "announce_general_sudden_death", // AS_GENERAL_SUDDEN_DEATH + "announce_general_vote_failed", // AS_GENERAL_VOTE_FAILED + "announce_general_vote_passed", // AS_GENERAL_VOTE_PASSED + "announce_general_vote_now", // AS_GENERAL_VOTE_NOW + "announce_general_one_frag", // AS_GENERAL_ONE_FRAG + "announce_general_two_frags", // AS_GENERAL_TWO_FRAGS + "announce_general_three_frags", // AS_GENERAL_THREE_FRAGS + "announce_general_one_minute", // AS_GENERAL_ONE_MINUTE + "announce_general_five_minute", // AS_GENERAL_FIVE_MINUTE + "announce_general_prepare_to_fight", // AS_GENERAL_PREPARE_TO_FIGHT + "announce_general_quad_damage", // AS_GENERAL_QUAD_DAMAGE + "announce_general_regeneration", // AS_GENERAL_REGENERATION + "announce_general_haste", // AS_GENERAL_HASTE + "announce_general_invisibility", // AS_GENERAL_INVISIBILITY + // DM announcements + "announce_dm_you_tied_lead", // AS_DM_YOU_TIED_LEAD + "announce_dm_you_have_taken_lead", // AS_DM_YOU_HAVE_TAKEN_LEAD + "announce_dm_you_lost_lead", // AS_DM_YOU_LOST_LEAD + // Team announcements + "announce_team_enemy_score", // AS_TEAM_ENEMY_SCORES + "announce_team_you_score", // AS_TEAM_YOU_SCORE + "announce_team_teams_tied", // AS_TEAM_TEAMS_TIED + "announce_team_strogg_lead", // AS_TEAM_STROGG_LEAD + "announce_team_marines_lead", // AS_TEAM_MARINES_LEAD + "announce_team_join_marine", // AS_TEAM_JOIN_MARINE + "announce_team_join_strogg", // AS_TEAM_JOIN_STROGG + // CTF announcements + "announce_ctf_you_have_flag", // AS_CTF_YOU_HAVE_FLAG + "announce_ctf_your_team_has_flag", // AS_CTF_YOUR_TEAM_HAS_FLAG + "announce_ctf_enemy_has_flag", // AS_CTF_ENEMY_HAS_FLAG + "announce_ctf_your_team_drops_flag", // AS_CTF_YOUR_TEAM_DROPS_FLAG + "announce_ctf_enemy_drops_flag", // AS_CTF_ENEMY_DROPS_FLAG + "announce_ctf_your_flag_returned", // AS_CTF_YOUR_FLAG_RETURNED + "announce_ctf_enemy_returns_flag", // AS_CTF_ENEMY_RETURNS_FLAG + // Tourney announcements + "announce_tourney_advance", // AS_TOURNEY_ADVANCE + "announce_tourney_join_arena_one", // AS_TOURNEY_JOIN_ARENA_ONE + "announce_tourney_join_arena_two", // AS_TOURNEY_JOIN_ARENA_TWO + "announce_tourney_join_arena_three", // AS_TOURNEY_JOIN_ARENA_THREE + "announce_tourney_join_arena_four", // AS_TOURNEY_JOIN_ARENA_FOUR + "announce_tourney_join_arena_five", // AS_TOURNEY_JOIN_ARENA_FIVE + "announce_tourney_join_arena_six", // AS_TOURNEY_JOIN_ARENA_SIX + "announce_tourney_join_arena_seven", // AS_TOURNEY_JOIN_ARENA_SEVEN + "announce_tourney_join_arena_eight", // AS_TOURNEY_JOIN_ARENA_EIGHT + "announce_tourney_join_arena_waiting", // AS_TOURNEY_JOIN_ARENA_WAITING + "announce_tourney_done", // AS_TOURNEY_DONE + "announce_tourney_start", // AS_TOURNEY_START + "announce_tourney_eliminated", // AS_TOURNEY_ELIMINATED + "announce_tourney_won", // AS_TOURNEY_WON + "announce_tourney_prelims", // AS_TOURNEY_PRELIMS + "announce_tourney_quarter_finals", // AS_TOURNEY_QUARTER_FINALS + "announce_tourney_semi_finals", // AS_TOURNEY_SEMI_FINALS + "announce_tourney_final_match", // AS_TOURNEY_FINAL_MATCH + "sound/vo/mp/9_99_320_10", // AS_GENERAL_TEAM_AMMOREGEN + "sound/vo/mp/9_99_360_6" // AS_GENERAL_TEAM_DOUBLER +}; + +void idMultiplayerGame::ScheduleAnnouncerSound( announcerSound_t sound, float time, int instance, bool allowOverride ) { + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + if ( time < gameLocal.time ) { + return; + } + + if ( sound >= AS_NUM_SOUNDS ) { + return; + } + + announcerSoundNode_t* newSound = new announcerSoundNode_t; + newSound->soundShader = sound; + newSound->time = time; + newSound->announcerSoundNode.SetOwner( newSound ); + newSound->instance = instance; + newSound->allowOverride = allowOverride; + + announcerSoundNode_t* snd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = snd->announcerSoundNode.Next() ) { + if ( snd->time > newSound->time ) { + newSound->announcerSoundNode.InsertBefore( snd->announcerSoundNode ); + break; + } + } + if ( snd == NULL ) { + newSound->announcerSoundNode.AddToEnd( announcerSoundQueue ); + } +} + +void idMultiplayerGame::RemoveAnnouncerSound( int type ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + if ( snd->soundShader == type ) { + snd->announcerSoundNode.Remove( ); + delete snd; + break; + } + } + + // if a sound is currently playing, stop it + if( gameLocal.GetLocalPlayer() && lastAnnouncerSound == type ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + } +} + +void idMultiplayerGame::RemoveAnnouncerSoundRange( int startType, int endType ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + for( int i = startType; i <= endType; i++ ) { + if ( snd->soundShader == i ) { + snd->announcerSoundNode.Remove( ); + delete snd; + } + } + } + + // if a sound is currently playing, stop it + if ( gameLocal.GetLocalPlayer() ) { + for( int i = startType; i <= endType; i++ ) { + if( lastAnnouncerSound == i ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + break; + } + } + } +} + + +void idMultiplayerGame::ScheduleTimeAnnouncements( void ) { + if( !gameLocal.GetLocalPlayer() || !gameState ) { + // too early + return; + } + + // clean out any preexisting announcer sounds + RemoveAnnouncerSound( AS_GENERAL_ONE_MINUTE ); + RemoveAnnouncerSound( AS_GENERAL_FIVE_MINUTE ); + + if( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != WARMUP ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int endGameTime = 0; + + if( gameLocal.gameType == GAME_TOURNEY ) { + int arena = gameLocal.GetLocalPlayer()->GetArena(); + if( !((rvTourneyGameState*)gameState)->GetArena( arena ).IsPlaying() ) { + return; // arena is not active + } + // per-arena timelimits + endGameTime = ((rvTourneyGameState*)gameState)->GetArena( arena ).GetMatchStartTime() + ( timeLimit * 60000 ); + } else { + endGameTime = matchStartedTime + ( timeLimit * 60000 ); + } + + if( timeLimit > 5 ) { + ScheduleAnnouncerSound( AS_GENERAL_FIVE_MINUTE, endGameTime - (5 * 60000) ); + } + if( timeLimit > 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_MINUTE, endGameTime - (60000) ); + } + } +} + +void idMultiplayerGame::PlayAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + // if we're done playing the last sound reset override status + if( announcerPlayTime <= gameLocal.time ) { + currentSoundOverride = false; + } + + if ( announcerPlayTime > gameLocal.time && !currentSoundOverride ) { + return; + } + + // in tourney only play sounds scheduled for your current arena + if ( gameLocal.gameType == GAME_TOURNEY ) { + // go through and find the first sound to play in our arena, delete any sounds + // for other arenas we see along the way. + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + + if( snd->time > gameLocal.time ) { + return; + } + + if( snd->instance == -1 || snd->soundShader == AS_GENERAL_VOTE_NOW || snd->soundShader == AS_GENERAL_VOTE_PASSED || snd->soundShader == AS_GENERAL_VOTE_FAILED ) { + // all-instance sound + break; + } + + if( snd->instance == gameLocal.GetLocalPlayer()->GetInstance() ) { + if( snd->allowOverride && nextSnd && nextSnd->time <= gameLocal.time ) { + // this sound is OK with being over-ridden, + // and the next sound is ready to play, so go ahead and look at the next sound + snd->announcerSoundNode.Remove ( ); + delete snd; + + continue; + } else { + break; + } + } + + snd->announcerSoundNode.Remove ( ); + delete snd; + } + } else { + snd = announcerSoundQueue.Next(); + if( snd && snd->time > gameLocal.time ) { + return; + } + } + + // play the sound locally + if ( snd && snd->soundShader < AS_NUM_SOUNDS ) { + int length = 0; + + //don't play timelimit countdown announcements if game is already over + mpGameState_t state = gameState->GetMPGameState(); + if ( state == GAMEREVIEW //game is over, in scoreboard + && ( snd->soundShader == AS_GENERAL_ONE_MINUTE + || snd->soundShader == AS_GENERAL_FIVE_MINUTE ) ) { + //ignore scheduled time limit warnings that haven't executed yet + snd->announcerSoundNode.Remove(); + delete snd; + } else { + snd->announcerSoundNode.Remove(); + + gameLocal.GetLocalPlayer()->StartSoundShader( declManager->FindSound( announcerSoundDefs[ snd->soundShader ], false ), SND_CHANNEL_MP_ANNOUNCER, 0, false, &length ); + currentSoundOverride = snd->allowOverride; + lastAnnouncerSound = snd->soundShader; + + delete snd; + } + + // if sounds remain to be played, check again + announcerPlayTime = gameLocal.time + length; + } +} + +void idMultiplayerGame::ClearTeamScores ( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } +} + +void idMultiplayerGame::AddTeamScore ( int team, int amount ) { + if ( team < 0 || team >= TEAM_MAX ) { + return; + } + + teamScore[ team ] += amount; +} + +void idMultiplayerGame::AddPlayerScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount += amount; + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].fragCount ); +} + +void idMultiplayerGame::AddPlayerTeamScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount += amount; + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].teamFragCount ); +} + +void idMultiplayerGame::AddPlayerWin( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins += amount; + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ player->entityNumber ].wins ); +} + +void idMultiplayerGame::SetPlayerScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); + +} + +void idMultiplayerGame::SetPlayerTeamScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); +} + +void idMultiplayerGame::SetPlayerDeadZoneScore( idPlayer* player, float value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].deadZoneScore = value; +} + +void idMultiplayerGame::SetPlayerWin( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, value ); +} + +rvCTF_AssaultPoint* idMultiplayerGame::NextAP( int team ) { + for( int i = 0; i < assaultPoints.Num(); i++ ) { + if( assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]->GetOwner() == team ) { + continue; + } + return assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]; + } + return NULL; +} + +void idMultiplayerGame::ClientSetInstance( const idBitMsg& msg ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + int instance = msg.ReadByte(); + + if ( !player ) { + gameLocal.Warning( "idMultiplayerGame::ClientSetInstance - NULL local player" ); + return; + } + + gameLocal.GetInstance( 0 )->SetSpawnInstanceID( instance ); + // on the client, we delete all entities, + // the server will send over new ones + gameLocal.InstanceClear(); + // set the starting offset for repopulation back to matching what the server will have + // this should be covered by setting indexes when populating the instances as well, but it doesn't hurt + gameLocal.firstFreeIndex = MAX_CLIENTS; + + player->SetArena( instance ); + player->SetInstance( instance ); + + // spawn the instance entities + gameLocal.GetInstance( 0 )->PopulateFromMessage( msg ); + + // players in other instances might have been hidden, update them + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p ) { + if( p->GetInstance() == instance ) { + p->ClientInstanceJoin(); + } else { + p->ClientInstanceLeave(); + } + } + } +} + +void idMultiplayerGame::ServerSetInstance( int instance ) { + for( int i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( ent ) { + if( ent->GetInstance() != instance ) { + ent->InstanceLeave(); + } else { + ent->InstanceJoin(); + } + } + } +} + +const char* idMultiplayerGame::GetLongGametypeName( const char* gametype ) { + if( !idStr::Icmp( gametype, "Tourney" ) ) { + return common->GetLocalizedString( "#str_107676" ); + } else if( !idStr::Icmp( gametype, "Team DM" ) ) { + return common->GetLocalizedString( "#str_107677" ); + } else if( !idStr::Icmp( gametype, "CTF" ) ) { + return common->GetLocalizedString( "#str_107678" ); + } else if( !idStr::Icmp( gametype, "DM" ) ) { + return common->GetLocalizedString( "#str_107679" ); + } else if( !idStr::Icmp( gametype, "One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107680" ); + } else if( !idStr::Icmp( gametype, "Arena CTF" ) ) { + return common->GetLocalizedString( "#str_107681" ); + } else if( !idStr::Icmp( gametype, "Arena One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107682" ); + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + } else if( !idStr::Icmp( gametype, "DeadZone" ) ) { + return common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + // RITUAL END + } + + return ""; +} + +/* +================ +idMultiplayerGame::VoteGameTypeToString +================ +*/ +const char *idMultiplayerGame::VoteGameTypeToString( int gameTypeInt ) { + const char *gameType = NULL; + switch ( gameTypeInt ) { + default: + case VOTE_GAMETYPE_DM: + gameType = "DM"; + break; + case VOTE_GAMETYPE_TOURNEY: + gameType = "Tourney"; + break; + case VOTE_GAMETYPE_TDM: + gameType = "Team DM"; + break; + case VOTE_GAMETYPE_CTF: + gameType = "CTF"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameType = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + gameType = "DeadZone"; + break; + } + return gameType; +} + +int idMultiplayerGame::GameTypeToVote( const char *gameType ) { + if ( 0 == idStr::Icmp( gameType, "DM" ) ) { + return VOTE_GAMETYPE_DM; + } else if ( 0 == idStr::Icmp( gameType, "Tourney" ) ) { + return VOTE_GAMETYPE_TOURNEY; + } else if ( 0 == idStr::Icmp( gameType, "Team DM" ) ) { + return VOTE_GAMETYPE_TDM; + } else if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + return VOTE_GAMETYPE_CTF; + } else if ( 0 == idStr::Icmp( gameType, "Arena CTF" ) ) { + return VOTE_GAMETYPE_ARENA_CTF; + } else if ( 0 == idStr::Icmp( gameType, "DeadZone" ) ) { + return VOTE_GAMETYPE_DEADZONE; + } + + return VOTE_GAMETYPE_DM; +} + +float idMultiplayerGame::GetPlayerDeadZoneScore( idPlayer* player ) { + return playerState[ player->entityNumber ].deadZoneScore; +} + +int idMultiplayerGame::GetPlayerTime( idPlayer* player ) { + return ( gameLocal.time - player->GetConnectTime() ) / 60000; +} + +int idMultiplayerGame::GetTeamScore( idPlayer* player ) { + return GetTeamScore( player->entityNumber ); +} + +int idMultiplayerGame::GetScore( idPlayer* player ) { + return GetScore( player->entityNumber ); +} + +int idMultiplayerGame::GetWins( idPlayer* player ) { + return GetWins( player->entityNumber ); +} + +void idMultiplayerGame::EnableDamage( bool enable ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + player->fl.takedamage = enable; + } +} + +void idMultiplayerGame::ReceiveRemoteConsoleOutput( const char* output ) { + if( mainGui ) { + idStr newOutput( output ); + + if( rconHistory.Length() + newOutput.Length() > RCON_HISTORY_SIZE ) { + int removeLength = rconHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + rconHistory.Empty(); + } else { + while( (rconHistory.Length() - removeLength) + newOutput.Length() > RCON_HISTORY_SIZE ) { + removeLength = rconHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + rconHistory.Empty(); + break; + } + } + } + rconHistory = rconHistory.Right( rconHistory.Length() - removeLength ); + } + + + int consoleInputStart = newOutput.Find( "Console Input: " ); + if( consoleInputStart != -1 ) { + idStr consoleInput = newOutput.Right( newOutput.Length() - consoleInputStart - 15 ); + newOutput = newOutput.Left( consoleInputStart ); + newOutput.StripTrailing( "\n" ); + consoleInput.StripTrailing( "\n" ); + mainGui->SetStateString( "admin_console_input", consoleInput.c_str() ); + } + + if( newOutput.Length() ) { + rconHistory.Append( newOutput ); + rconHistory.Append( '\n' ); + } + + mainGui->SetStateString( "admin_console_history", rconHistory.c_str() ); + } +} + +/* +=============== +idMultiplayerGame::ShuffleTeams +=============== +*/ +void idMultiplayerGame::ShuffleTeams( void ) { + // turn off autobalance if its on + bool autoBalance = gameLocal.serverInfo.GetBool( "si_autoBalance" ); + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", false ); + } + + int loosingTeam = teamScore[ TEAM_MARINE ] < teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG; + int winningTeam = loosingTeam == TEAM_MARINE ? TEAM_STROGG : TEAM_MARINE; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( !(i % 2) ) { + // switch even players to losing team + if( rankedPlayers[ i ].First()->team != loosingTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ loosingTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } else { + if( rankedPlayers[ i ].First()->team != winningTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ winningTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } + } + + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", true ); + } +} + + +rvGameState* idMultiplayerGame::GetGameState( void ) { + return gameState; +} + +void idMultiplayerGame::SetGameType( void ) { + if ( gameState != NULL ) { + delete gameState; + gameState = NULL; + } + + if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) == 0 ) ) { + gameLocal.gameType = GAME_DM; + gameState = new rvDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Tourney" ) == 0 ) ) { + gameLocal.gameType = GAME_TOURNEY; + gameState = new rvTourneyGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) == 0 ) ) { + gameLocal.gameType = GAME_TDM; + gameState = new rvTeamDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DeadZone" ) == 0 ) ) { + gameLocal.gameType = GAME_DEADZONE; + gameState = new riDZGameState; + } else { + gameLocal.Error( "idMultiplayerGame::SetGameType() - Unknown gametype '%s'\n", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } + + // force entity filter to gametype name in multiplayer + if ( gameLocal.gameType != GAME_SP ) { + gameLocal.serverInfo.Set( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + // also set as a CVar for when serverinfo is rescanned + cvarSystem->SetCVarString( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } +} + +//asalmon: need to access total frags for a team and total score for a team +int idMultiplayerGame::GetTeamsTotalFrags( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + if(rankedPlayers[ j ].First()->team == i) + { + total += GetScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +int idMultiplayerGame::GetTeamsTotalScore( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + idPlayer foo; + + if(rankedPlayers[ j ].First()->team == i) + { + total += GetTeamScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +/* +=============== +idMultiplayerGame::PickMap +=============== +*/ +bool idMultiplayerGame::PickMap( idStr gameType, bool checkOnly ) { + + idStrList maps; + int miss = 0; + const idDict *mapDict; + int index = 0; + int btype; + const char* mapName; + + mapName = si_map.GetString(); + + // if we didn't set up a gametype, grab the current game type. + if ( gameType.IsEmpty() ) { + gameType = si_gameType.GetString(); + } + + // if we're playing a map of this gametype, don't change. + mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + btype = mapDict->GetInt( gameType ); + if ( btype ) { + // ( not sure what the gloubi boulga is about re-setting si_map two ways after reading it at the start of the function already ) + cvarSystem->SetCVarString( "si_map", mapName ); + si_map.SetString( mapName ); + return false; + } + } + + if ( checkOnly ) { + // always allow switching to DM mode, whatever the settings on the map ( DM should always be possible ) + if ( !idStr::Icmp( si_gameType.GetString(), "DM" ) ) { + return false; + } + // don't actually change anything, indicate we would + return true; + } + + int i; + idFileList *files; + idStrList fileList; + + int count = 0; + + files = fileSystem->ListFiles( "maps/mp", ".map" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + fileList.AddUnique( va( "mp/%s", files->GetList()[i].c_str() ) ); + } + fileSystem->FreeFileList( files ); + + files = fileSystem->ListFiles( "maps/mp", ".mapc" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + idStr fixedExtension(files->GetList()[i]); + fixedExtension.SetFileExtension("map"); + fileList.AddUnique( va( "mp/%s", fixedExtension.c_str() ) ); + } + + fileList.Sort(); + + idStr name; + idStr cycle; + + //Populate the map list + for ( i = 0; i < fileList.Num(); i++) { + //Add only MP maps. + if(!idStr::FindText(fileList[i].c_str(), "mp/")) + { + maps.AddUnique(fileList[i].c_str()); + } + } + maps.Sort(); + + if(maps.Num() > 0) + { + while(miss < 100) + { + index = gameLocal.random.RandomInt( maps.Num() ); + mapName = maps[index].c_str(); + + mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + btype = mapDict->GetInt( gameType ); + if(btype) + { + cvarSystem->SetCVarString("si_map",mapName); + si_map.SetString( mapName ); + return true; + + } + } + miss++; + + } + + } + + //something is wrong and there are no maps for this game type. This should never happen. + gameLocal.Error( "No maps found for game type: %s.\n", gameType.c_str() ); + return false; +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( int rank, bool tied, int score ) { + char* placeString; + + if( rank == 0 ) { + //"1st^0 place with" + placeString = va( "%s%s %d", S_COLOR_BLUE, common->GetLocalizedString( "#str_107689" ), score ); + } else if( rank == 1 ) { + //"2nd^0 place with" + placeString = va( "%s%s %d", S_COLOR_RED, common->GetLocalizedString( "#str_107690" ), score ); + } else if( rank == 2 ) { + //"3rd^0 place with" + placeString = va( "%s%s %d", S_COLOR_YELLOW, common->GetLocalizedString( "#str_107691" ), score ); + } else { + //"th^0 place with" + placeString = va( "%d%s %d", rank + 1, common->GetLocalizedString( "#str_107692" ), score ); + } + + if( tied ) { + //Tied for + return va( "%s %s", common->GetLocalizedString( "#str_107693" ), placeString ); + } else { + return placeString; + } +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( idPlayer* player ) { + if( player == NULL ) { + return ""; + } + + bool tied = false; + int rank = GetPlayerRank( player, tied ); + return GetPlayerRankText( rank, tied, GetScore( player ) ); +} + +/* +=============== +idMultiplayerGame::WriteNetworkInfo +=============== +*/ +void idMultiplayerGame::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteStartState( clientNum, msg, true ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); + + gameState->WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idMultiplayerGame::ReadNetworkInfo +=============== +*/ +void idMultiplayerGame::ReadNetworkInfo( idFile* file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ClientReadStartState( msg ); + + gameState->ReadNetworkInfo( file, clientNum ); +} + +void idMultiplayerGame::AddPrivatePlayer( int clientId ) { + privateClientIds.Append( clientId ); +} + +void idMultiplayerGame::RemovePrivatePlayer( int clientId ) { + for( int i = 0; i < privateClientIds.Num(); i++ ) { + if( clientId == privateClientIds[ i ] ) { + privateClientIds.RemoveIndex( i ); + i--; + } + } +} + +void idMultiplayerGame::UpdatePrivatePlayerCount( void ) { + if ( !gameLocal.isServer ) { + return; + } + + int numPrivatePlayers = 0; + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( privatePlayers & (1 << i) ) { + if( gameLocal.entities[ i ] ) { + numPrivatePlayers++; + } else { + privatePlayers &= ~( 1 << i ); + } + } + } + + cvarSystem->SetCVarInteger( "si_numPrivatePlayers", numPrivatePlayers ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); +} + +void idMultiplayerGame::SetFlagEntity( idEntity* ent, int team ) { + assert( ( team == TEAM_STROGG || team == TEAM_MARINE ) ); + + flagEntities[ team ] = ent; +} + +idEntity* idMultiplayerGame::GetFlagEntity( int team ) { + assert( team >= 0 && team < TEAM_MAX ); + + return flagEntities[ team ]; +} + + +// +void idMultiplayerGame::CheckTeamBalance_f( const idCmdArgs &args ) { + + if ( args.Argc() < 5 ) { + return; + } + + idPlayer *localPlayer = gameLocal.GetLocalPlayer(); + + const char *team = args.Argv(1); + const char *yesEvent = args.Argv(2); + const char *noEvent = args.Argv(3); + const char *sameTeamEvent = args.Argv(4); + + if ( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) || !gameLocal.IsTeamGame() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + return; + } + + int teamCount[2]; + teamCount[0] = teamCount[1] = 0; + + for ( int i = 0; i < gameLocal.numClients; ++i ) { + idEntity *ent = gameLocal.entities[i]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != localPlayer ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + if ( idStr::Icmp( team, "marine" ) == 0 ) { + if ( localPlayer->team == TEAM_MARINE && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_MARINE] > teamCount[TEAM_STROGG] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } else { + + if ( localPlayer->team == TEAM_STROGG && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_STROGG] > teamCount[TEAM_MARINE] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } +} + +/* +================ +idMultiplayerGame::LocalizeGametype + +dupe of rvServerScanGUI::LocalizeGametype +================ +*/ +const char *idMultiplayerGame::LocalizeGametype( void ) { + + const char *gameType; + + gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + localisedGametype = gameType; + + if( !idStr::Icmp( gameType, "DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110011" ); + } + if( !idStr::Icmp( gameType, "Tourney" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110012" ); + } + if( !idStr::Icmp( gameType, "Team DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110013" ); + } + if( !idStr::Icmp( gameType, "CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110014" ); + } + if( !idStr::Icmp( gameType, "Arena CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110015" ); + } + if( !idStr::Icmp( gameType, "DeadZone" ) ) { + localisedGametype = common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + } + + return( localisedGametype.c_str() ); +} + +int idMultiplayerGame::VerifyTeamSwitch( int wantTeam, idPlayer *player ) { + idEntity* ent; + int teamCount[ TEAM_MAX ]; + int balanceTeam = -1; + + if( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) ) { + return wantTeam; + } + + teamCount[ TEAM_MARINE ] = teamCount[ TEAM_STROGG ] = 0; + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != player ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ TEAM_MARINE ] > teamCount[ TEAM_STROGG ] ) { + balanceTeam = TEAM_STROGG; + } else if ( teamCount[ TEAM_STROGG ] > teamCount[ TEAM_MARINE ] ) { + balanceTeam = TEAM_MARINE; + } + + return (balanceTeam == -1) ? wantTeam : balanceTeam; +} + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================ +idMultiplayerGame::NumberOfPlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfPlayersOnTeam( int team ) +{ + int teamPlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + teamPlayerCount ++; + } + } + } + + return teamPlayerCount; +} + + +/* +================ +idMultiplayerGame::NumberOfAlivePlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfAlivePlayersOnTeam( int team ) +{ + int teamAlivePlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team && entPlayer->allowedToRespawn ) + { + teamAlivePlayerCount ++; + } + } + } + + return teamAlivePlayerCount; +} + + +// RITUAL END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +/* +================ +idMultiplayerGame::OpenLocalBuyMenu +================ +*/ +void idMultiplayerGame::OpenLocalBuyMenu( void ) +{ + // Buy menu work in progress + //if ( gameLocal.mpGame.GetCurrentMenu() == 4 ) + //{ + // return; + //} + + if ( currentMenu == 4 ) + return; // Already open + + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.mpGame.nextMenu = 4; +} + +/* +================ +idMultiplayerGame::RedrawLocalBuyMenu +================ +*/ +void idMultiplayerGame::RedrawLocalBuyMenu( void ) +{ + if ( !buyMenu ) + return; + + SetupBuyMenuItems(); + buyMenu->HandleNamedEvent( "update_buymenu" ); +} + + +/* +================ +idMultiplayerGame::GiveCashToTeam +================ +*/ +void idMultiplayerGame::GiveCashToTeam( int team, float cashAmount ) +{ + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + entPlayer->GiveCash( cashAmount ); + } + } + } + +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode( void ) { + if ( !gameLocal.isMultiplayer ) { + return false; + } + + if ( gameLocal.gameType != GAME_TOURNEY ) { + return gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ); + } + + return false; +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedRightNow +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedRightNow( void ) +{ + return ( IsBuyingAllowedInTheCurrentGameMode() && isBuyingAllowedRightNow ); +} + + +void idMultiplayerGame::AddTeamPowerup(int powerup, int time, int team) +{ + int i; + for ( i=0; i > assaultPoints; + + // Buying Manager - authority for buying system game balance constants (awards, + // costs, etc.) + riBuyingManager mpBuyingManager; + + idUserInterface* statSummary; // stat summary + rvTourneyGUI tourneyGUI; + + void ShowStatSummary( void ); + bool CanCapture( int team ); + void FlagCaptured( idPlayer *player ); + + void UpdatePlayerRanks( playerRankMode_t rankMode = PRM_AUTO ); + void UpdateTeamRanks( void ); + void UpdateHud( idUserInterface* _mphud ); + idPlayer * FragLimitHit( void ); + idPlayer * FragLeader( void ); + bool TimeLimitHit( void ); + int GetCurrentMenu( void ) { return currentMenu; } + + void SetFlagEntity( idEntity* ent, int team ); + idEntity* GetFlagEntity( int team ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile* file, int clientNum ); + + void SetShaderParms( renderView_t *view ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int NumberOfPlayersOnTeam( int team ); + int NumberOfAlivePlayersOnTeam( int team ); + void ReportZoneControllingPlayer( idPlayer* player ); + void ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger = 0); + bool IsValidTeam(int team); + void ControlZoneStateChanged( int team ); + + void ListMaps( void ); + + int powerupCount; + int prevAnnouncerSnd; + int defaultWinner; + int deadZonePowerupCount; + dzState_t dzState[ TEAM_MAX ]; + float marineScoreBarPulseAmount; + float stroggScoreBarPulseAmount; +// RITUAL END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + bool isBuyingAllowedRightNow; + + void OpenLocalBuyMenu( void ); + void RedrawLocalBuyMenu( void ); + void GiveCashToTeam( int team, float cashAmount ); + bool IsBuyingAllowedInTheCurrentGameMode( void ); + bool IsBuyingAllowedRightNow( void ); +// RITUAL END + static const char* teamNames[ TEAM_MAX ]; + +private: + static const char *MPGuis[]; + static const char *ThrottleVars[]; + static const char *ThrottleVarsInEnglish[]; + static const int ThrottleDelay[]; + + char killNotificationMsg[ KILL_NOTIFICATION_LEN ]; + + int pingUpdateTime; // time to update ping + + mpPlayerState_t playerState[ MAX_CLIENTS ]; + + // game state + rvGameState* gameState; + + // vote vars + vote_flags_t vote; // active vote or VOTE_NONE + int voteTimeOut; // when the current vote expires + int voteExecTime; // delay between vote passed msg and execute + int yesVotes; // counter for yes votes + int noVotes; // and for no votes + idStr voteValue; // the data voted upon ( server ) + idStr voteString; // the vote string ( client ) + bool voted; // hide vote box ( client ) + int kickVoteMap[ MAX_CLIENTS ]; +// RAVEN BEGIN +// shouchard: names for kickVoteMap + idStr kickVoteMapNames[ MAX_CLIENTS ]; + voteStruct_t currentVoteData; // used for multi-field votes +// RAVEN END + + idStr localisedGametype; + + idList voteMapDecls; + int voteMapsWaiting; + + // time related + int matchStartedTime; // time current match started + + // guis +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + //int sqRoundNumber; // round number in DeadZone; match expires when this equals "sq_numRoundsPerMatch" (cvar) +// squirrel: Mode-agnostic buymenus + idUserInterface *buyMenu; // buy menu +// RITUAL END + idUserInterface *scoreBoard; // scoreboard + idUserInterface *mainGui; // ready / nick / votes etc. + idListGUI *mapList; + idUserInterface *msgmodeGui; // message mode + int currentMenu; // 0 - none, 1 - mainGui, 2 - msgmodeGui + int nextMenu; // if 0, will do mainGui + bool bCurrentMenuMsg; // send menu state updates to server + + + enum { + MPLIGHT_CTF_MARINE, + MPLIGHT_CTF_STROGG, + MPLIGHT_QUAD, + MPLIGHT_HASTE, + MPLIGHT_REGEN, + MPLIGHT_MAX + }; + + int lightHandles[ MPLIGHT_MAX ]; + renderLight_t lights[ MPLIGHT_MAX ]; + + // chat buffer + idStr chatHistory; + + // rcon buffer + idStr rconHistory; + +//RAVEN BEGIN +//asalmon: Need to refresh stats periodically if the player is looking at stats + int currentStatClient; + int currentStatTeam; +//RAVEN END + +public: + // current player rankings + idList > rankedPlayers; + idList unrankedPlayers; + + rvPair rankedTeams[ TEAM_MAX ]; + +private: + + int lastVOAnnounce; + + int lastReadyToggleTime; + bool pureReady; // defaults to false, set to true once server game is running with pure checksums + bool currentSoundOverride; + int switchThrottle[ 3 ]; + int voiceChatThrottle; + + void SetupBuyMenuItems(); + + idList privateClientIds; + int privatePlayers; + + // player who's rank info we're displaying + idEntityPtr rankTextPlayer; + + idEntityPtr flagEntities[ TEAM_MAX ]; + idEntityPtr flagCarriers[ TEAM_MAX ]; + + // updates the passed gui with current score information + void UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ); + + // bdube: test scoreboard + void UpdateTestScoreboard( idUserInterface *scoreBoard ); + + // ddynerman: gametype specific scoreboard + void UpdateScoreboard( idUserInterface *scoreBoard ); + + void UpdateDMScoreboard( idUserInterface *scoreBoard ); + void UpdateTeamScoreboard( idUserInterface *scoreBoard ); + void UpdateSummaryBoard( idUserInterface *scoreBoard ); + + int GetPlayerRank( idPlayer* player, bool& isTied ); + char* GetPlayerRankText( idPlayer* player ); + char* GetPlayerRankText( int rank, bool tied, int score ); + + const char* BuildSummaryListString( idPlayer* player, int rankedScore ); + + void UpdatePrivatePlayerCount( void ); + + typedef struct announcerSoundNode_s { + announcerSound_t soundShader; + float time; + idLinkList announcerSoundNode; + int instance; + bool allowOverride; + } announcerSoundNode_t; + + idLinkList announcerSoundQueue; + announcerSound_t lastAnnouncerSound; + + static const char* announcerSoundDefs[ AS_NUM_SOUNDS ]; + + float announcerPlayTime; + + void PlayAnnouncerSounds ( void ); + + int teamScore[ TEAM_MAX ]; + int teamDeadZoneScore[ TEAM_MAX]; + void ClearTeamScores ( void ); + + void UpdateLeader( idPlayer* oldLeader ); + + void ClearGuis( void ); + void DrawScoreBoard( idPlayer *player ); + void CheckVote( void ); + bool AllPlayersReady( idStr* reason = NULL ); + + const char * GameTime( void ); + + bool EnoughClientsToPlay( void ); + void DrawStatSummary( void ); + // go through the clients, and see if they want to be respawned, and if the game allows it + // called during normal gameplay for death -> respawn cycles + // and for a spectator who want back in the game (see param) + void CheckRespawns( idPlayer *spectator = NULL ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID, idPlayer *player ); + void CheckSpecialLights( void ); + void ForceReady(); + // when clients disconnect or join spectate during game, check if we need to end the game + void CheckAbortGame( void ); + void MessageMode( const idCmdArgs &args ); + void DisableMenu( void ); + void SetMapShot( void ); + // scores in TDM + void VoiceChat( const idCmdArgs &args, bool team ); + +// RAVEN BEGIN +// mekberg: added + void UpdateMPSettingsModel( idUserInterface* currentGui ); +// RAVEN END + + void WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ); + + bool RequestVoteMaps( int flags ); + + void SetMapList( const char *listName, const char *mapName, int gameTypeInt ); + void SetVoteMapList( void ); + void SetSAMapList( void ); +}; + +ID_INLINE bool idMultiplayerGame::IsPureReady( void ) const { + return pureReady; +} + +ID_INLINE void idMultiplayerGame::ClearFrags( int clientNum ) { + playerState[ clientNum ].fragCount = 0; +} + +ID_INLINE bool idMultiplayerGame::IsInGame( int clientNum ) { + return playerState[ clientNum ].ingame; +} + +ID_INLINE int idMultiplayerGame::OpposingTeam( int team ) { + return (team == TEAM_STROGG ? TEAM_MARINE : TEAM_STROGG); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetRankedPlayer( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].First(); + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetRankedPlayerScore( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].Second(); + } else { + return 0; + } +} + +ID_INLINE int idMultiplayerGame::GetNumUnrankedPlayers( void ) { + return unrankedPlayers.Num(); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetUnrankedPlayer( int i ) { + if( i >= 0 && i < unrankedPlayers.Num() ) { + return unrankedPlayers[ i ]; + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetNumRankedPlayers( void ) { + return rankedPlayers.Num(); +} + +ID_INLINE int idMultiplayerGame::GetTeamScore( int i ) { + return playerState[ i ].teamFragCount; +} + +ID_INLINE int idMultiplayerGame::GetScore( int i ) { + return playerState[ i ].fragCount; +} + +ID_INLINE int idMultiplayerGame::GetWins( int i ) { + return playerState[ i ].wins; +} + +ID_INLINE void idMultiplayerGame::ResetRconGuiStatus( void ) { + if( mainGui) { + mainGui->SetStateInt( "password_valid", 0 ); + } +} + +// asalmon: needed access team scores for rich presence +ID_INLINE int idMultiplayerGame::GetScoreForTeam( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + return teamScore[ i ]; +} + +ID_INLINE int idMultiplayerGame::TeamLeader( void ) { + if( teamScore[ TEAM_MARINE ] == teamScore[ TEAM_STROGG ] ) { + return -1; + } else { + return ( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG ); + } +} + +int ComparePlayersByScore( const void* left, const void* right ); +int CompareTeamsByScore( const void* left, const void* right ); + +#endif /* !__MULTIPLAYERGAME_H__ */ + +// RAVEN END diff --git a/source/mpgame/Playback.cpp b/source/mpgame/Playback.cpp new file mode 100644 index 0000000..3f874e5 --- /dev/null +++ b/source/mpgame/Playback.cpp @@ -0,0 +1,417 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI.h" + +#define RECORD_STATE_TRACE_LEN 2048.0f + +class rvGamePlayback +{ +public: + rvGamePlayback( void ); + ~rvGamePlayback( void ); + + void RecordData( const usercmd_t &cmd, idEntity *source ); +private: + int mStartTime; + byte mOldFlags; + idStr mName; + idClipModel *mClipModel; + rvDeclPlayback *mPlayback; +}; + +idCVar g_recordPlayback( "g_recordPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "record the player movement in a playback" ); +idCVar g_playPlayback( "g_playPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "plays the current playback in a camera path" ); + +rvGamePlayback *gamePlayback = NULL; +rvCameraPlayback *playbackCamera = NULL; + +rvGamePlayback::rvGamePlayback( void ) +{ + idStr newName; + const idVec3 trace_mins( -1.0f, -1.0f, -1.0f ); + const idVec3 trace_maxs( 1.0f, 1.0f, 1.0f ); + const idBounds trace_bounds( trace_mins, trace_maxs ); + idTraceModel traceModel( trace_bounds ); + + mStartTime = gameLocal.time; + mOldFlags = 0; + mClipModel = new idClipModel( traceModel ); + + if( !g_currentPlayback.GetInteger() ) + { + newName = declManager->GetNewName( DECL_PLAYBACK, "playbacks/untitled" ); + mPlayback = ( rvDeclPlayback * )declManager->CreateNewDecl( DECL_PLAYBACK, newName, newName + ".playback" ); + mPlayback->ReplaceSourceFileText(); + mPlayback->Invalidate(); + + g_currentPlayback.SetInteger( mPlayback->Index() ); + } + else + { + mPlayback = ( rvDeclPlayback * )declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); + } + + declManager->StartPlaybackRecord( mPlayback ); + common->Printf( "Starting playback record to %s type %d\n", mPlayback->GetName(), g_recordPlayback.GetInteger() ); +} + +rvGamePlayback::~rvGamePlayback( void ) +{ + declManager->FinishPlayback( mPlayback ); + delete mClipModel; + + common->Printf( "Stopping playback play/record\n" ); +} + +void rvGamePlayback::RecordData( const usercmd_t &cmd, idEntity *source ) +{ + idPlayer *player; + trace_t trace; + idMat3 axis; + idVec3 start, end; + rvDeclPlaybackData info; + + info.Init(); + + switch( g_recordPlayback.GetInteger() ) + { + case 1: + info.SetPosition( source->GetPhysics()->GetOrigin() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + break; + + case 2: + gameLocal.GetPlayerView( start, axis ); + + end = start + axis[0] * RECORD_STATE_TRACE_LEN; + + gameLocal.Translation( gameLocal.GetLocalPlayer(), trace, start, end, mClipModel, mat3_identity, CONTENTS_SOLID | CONTENTS_RENDERMODEL, source ); + + if( trace.fraction != 1.0f ) + { + info.SetPosition( trace.endpos ); + info.SetAngles( trace.c.normal.ToAngles() ); + } + break; + + case 3: + assert( source->IsType( idPlayer::GetClassType() ) ); + if( source->IsType( idPlayer::GetClassType() ) ) + { + player = static_cast( source ); + info.SetPosition( player->GetEyePosition() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + } + break; + } + + // Record buttons + info.SetButtons( cmd.buttons ); + + // Record impulses + if( ( cmd.flags & UCF_IMPULSE_SEQUENCE ) != ( mOldFlags & UCF_IMPULSE_SEQUENCE ) ) + { + info.SetImpulse( cmd.impulse ); + } + else + { + info.SetImpulse( 0 ); + } + mOldFlags = cmd.flags; + + declManager->SetPlaybackData( mPlayback, gameLocal.time - mStartTime, -1, &info ); +} + +// ================================================================================================ + +void idGameEdit::DrawPlaybackDebugInfo( void ) +{ + int duration, time; + rvDeclPlaybackData pbd, pbdOld; + const rvDeclPlayback *pb; + + pb = declManager->PlaybackByIndex( g_currentPlayback.GetInteger(), true ); + if( pb ) + { + duration = SEC2MS( pb->GetDuration() ); + pbd.Init(); + pbdOld.Init(); + + declManager->GetPlaybackData( pb, -1, 0, 0, &pbdOld ); + for( time = gameLocal.GetMSec(); time < duration; time += gameLocal.GetMSec() * g_showPlayback.GetInteger() ) + { + declManager->GetPlaybackData( pb, -1, time, time, &pbd ); + gameRenderWorld->DebugArrow( colorGreen, pbdOld.GetPosition(), pbd.GetPosition(), 2 ); + pbdOld = pbd; + } + + gameRenderWorld->DebugBounds( colorRed, pb->GetBounds(), pb->GetOrigin() ); + } +} + +void idGameEdit::RecordPlayback( const usercmd_t &cmd, idEntity *source ) +{ + // Not recording - so instantly exit + if( !g_recordPlayback.GetInteger() && !gamePlayback ) + { + return; + } + + if( !gamePlayback ) + { + gamePlayback = new rvGamePlayback(); + } + + if( g_recordPlayback.GetInteger() ) + { + gamePlayback->RecordData( cmd, source ); + } + else + { + delete gamePlayback; + gamePlayback = NULL; + } +} + +// ================================================================================================ + +bool idGameEdit::PlayPlayback( void ) +{ + // Not playing - so instantly exit + if( !g_playPlayback.GetInteger() && !playbackCamera ) + { + return( false ); + } + + if( !playbackCamera ) + { + playbackCamera = static_cast( gameLocal.SpawnEntityType( rvCameraPlayback::GetClassType() ) ); + SetCamera( playbackCamera ); + + common->Printf( "Starting playback play\n" ); + } + + if( g_currentPlayback.IsModified() ) + { + // Spawn is a misnomer - it should be init with new data + playbackCamera->Spawn(); + g_currentPlayback.ClearModified(); + } + + if( !g_playPlayback.GetInteger() ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } + + return( true ); +} + +// ================================================================================================ + +void idGameEdit::ShutdownPlaybacks( void ) +{ + g_recordPlayback.SetInteger( 0 ); + g_playPlayback.SetInteger( 0 ); + + if( gamePlayback ) + { + delete gamePlayback; + gamePlayback = NULL; + } + + if( playbackCamera ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } +} + +// ================================================================================================ + +/* +============ +rvPlaybackDriver::Start + +Start a new playback automatically blending with the old playback (if any) over numFrames +============ +*/ +bool rvPlaybackDriver::Start( const char *playback, idEntity *owner, int flags, int numFrames ) +{ + idVec3 startPos; + + if( !idStr::Length( playback ) ) + { + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + return( true ); + } + + const rvDeclPlayback *pb = declManager->FindPlayback( playback ); + + if( g_showPlayback.GetInteger() ) + { + common->Printf( "Starting playback: %s\n", pb->GetName() ); + } + + mOldPlaybackDecl = mPlaybackDecl; + mOldFlags = mFlags; + mOldStartTime = mStartTime; + mOldOffset = mOffset; + + mPlaybackDecl = pb; + mFlags = flags; + mStartTime = gameLocal.time; + mOffset.Zero(); + + if( flags & PBFL_RELATIVE_POSITION ) + { + mOffset = owner->GetPhysics()->GetOrigin() - pb->GetOrigin(); + } + + mTransitionTime = numFrames * gameLocal.GetMSec(); + return( true ); +} + +/* +============ +PlaybackCallback + +Called whenever a button up or down, or an impulse event is found while getting the playback data +============ +*/ +void PlaybackCallback( int type, float time, const void *data ) +{ + const rvDeclPlaybackData *pbd = ( const rvDeclPlaybackData * )data; + idEntity *ent = pbd->GetEntity(); + + ent->PostEventSec( &EV_PlaybackCallback, time, type, pbd->GetChanged(), pbd->GetImpulse() ); +} + +/* +============ +rvPlaybackDriver::UpdateFrame + +Blend two playbacks together +============ +*/ +bool rvPlaybackDriver::UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ) +{ + rvDeclPlaybackData pbd, oldPbd; + float blend, invBlend; + idStr ret; + bool expired, oldExpired; + + // Get the current playback position + pbd.Init(); + pbd.SetCallback( ent, PlaybackCallback ); + expired = declManager->GetPlaybackData( mPlaybackDecl, mFlags, gameLocal.time - mStartTime, mLastTime - mStartTime, &pbd ); + pbd.SetPosition( pbd.GetPosition() + mOffset ); + + // Get the playback data we are merging from + oldPbd.Init(); + oldExpired = declManager->GetPlaybackData( mOldPlaybackDecl, mOldFlags, gameLocal.time - mOldStartTime, gameLocal.time - mOldStartTime, &oldPbd ); + oldPbd.SetPosition( oldPbd.GetPosition() + mOldOffset ); + + mLastTime = gameLocal.time; + + if( g_showPlayback.GetInteger() && mPlaybackDecl ) + { + common->Printf( "Running playback: %s at %.1f\n", mPlaybackDecl->GetName(), MS2SEC( gameLocal.time - mStartTime ) ); + } + + // Fully merged - so delete the old one + if( gameLocal.time > mStartTime + mTransitionTime ) + { + oldExpired = true; + } + + // Interpolate the result + if( expired && oldExpired ) + { + out.Init(); + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + } + else if( !expired && oldExpired ) + { + out = pbd; + mOldPlaybackDecl = NULL; + } + else if( expired && !oldExpired ) + { + out = oldPbd; + mPlaybackDecl = NULL; + } + else + { + // Linear zero to one + blend = idMath::ClampFloat( 0.0f, 1.0f, ( gameLocal.time - mStartTime ) / ( float )mTransitionTime ); + + // Sinusoidal + blend = idMath::Sin( blend * idMath::HALF_PI ); + invBlend = 1.0f - blend; + + out.SetPosition( blend * pbd.GetPosition() + invBlend * oldPbd.GetPosition() ); + out.SetAngles( blend * pbd.GetAngles() + invBlend * oldPbd.GetAngles() ); + out.SetButtons( pbd.GetButtons() ); + out.SetImpulse( pbd.GetImpulse() ); + } + + return( expired ); +} + +// cnicholson: Begin Added save/restore functionality +/* +============ +rvPlaybackDriver::Save + +Save all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( mLastTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mTransitionTime ); // cnicholson: Added unsaved var + + savefile->WriteInt( mStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->WriteVec3( mOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt( mOldStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mOldFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->WriteVec3( mOldOffset ); // cnicholson: Added unsaved var +} + +/* +============ +rvPlaybackDriver::Restore + +Restore all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( mLastTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mTransitionTime ); // cnicholson: Added unrestored var + + savefile->ReadInt( mStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->ReadVec3( mOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt( mOldStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mOldFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->ReadVec3( mOldOffset ); // cnicholson: Added unrestored var +} +// cnicholson: End Added save/restore functionality + +// end + diff --git a/source/mpgame/Player.cpp b/source/mpgame/Player.cpp new file mode 100644 index 0000000..d48ec86 --- /dev/null +++ b/source/mpgame/Player.cpp @@ -0,0 +1,14156 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "Weapon.h" +#include "Projectile.h" +#include "vehicle/Vehicle.h" +#include "client/ClientModel.h" +#include "ai/AAS_tactical.h" +#include "Healing_Station.h" +#include "ai/AI_Medic.h" + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off +#ifdef _XENON +#include "../ui/Window.h" + +// nrausch: support for direct button input +#include "../sys/xenon/xen_input.h" +#endif +// RAVEN END + +idCVar net_predictionErrorDecay( "net_predictionErrorDecay", "112", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "time in milliseconds it takes to fade away prediction errors", 0.0f, 200.0f ); +idCVar net_showPredictionError( "net_showPredictionError", "-1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "show prediction errors for the given client", -1, MAX_CLIENTS ); + + +/* +=============================================================================== + + Player control. + This object handles all player movement and world interaction. + +=============================================================================== +*/ + +#ifdef _XENON +bool g_ObjectiveSystemOpen = false; +#endif + +// distance between ladder rungs (actually is half that distance, but this sounds better) +const int LADDER_RUNG_DISTANCE = 32; + +// amount of health per dose from the health station +const int HEALTH_PER_DOSE = 10; + +// time before a weapon dropped to the floor disappears +const int WEAPON_DROP_TIME = 20 * 1000; + +// time before a next or prev weapon switch happens +const int WEAPON_SWITCH_DELAY = 150; + +const float PLAYER_ITEM_DROP_SPEED = 100.0f; + +// how many units to raise spectator above default view height so it's in the head of someone +const int SPECTATE_RAISE = 25; + +const int HEALTH_PULSE = 1000; // Regen rate and heal leak rate (for health > 100) +const int ARMOR_PULSE = 1000; // armor ticking down due to being higher than maxarmor +const int AMMO_REGEN_PULSE = 1000; // ammo regen in Arena CTF +const int POWERUP_BLINKS = 5; // Number of times the powerup wear off sound plays +const int POWERUP_BLINK_TIME = 1000; // Time between powerup wear off sounds +const float MIN_BOB_SPEED = 5.0f; // minimum speed to bob and play run/walk animations at +const int MAX_RESPAWN_TIME = 10000; +const int RAGDOLL_DEATH_TIME = 3000; +#ifdef _XENON + const int RAGDOLL_DEATH_TIME_XEN_SP = 1000; + const int MAX_RESPAWN_TIME_XEN_SP = 3000; +#endif +const int STEPUP_TIME = 200; +const int MAX_INVENTORY_ITEMS = 20; + +const int ARENA_POWERUP_MASK = ( 1 << POWERUP_AMMOREGEN ) | ( 1 << POWERUP_GUARD ) | ( 1 << POWERUP_DOUBLER ) | ( 1 << POWERUP_SCOUT ); + +//const idEventDef EV_Player_HideDatabaseEntry ( "", NULL ); +const idEventDef EV_Player_ZoomIn ( "" ); +const idEventDef EV_Player_ZoomOut ( "" ); + +const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' ); +const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' ); +const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' ); +const idEventDef EV_Player_SetViewAngles( "setViewAngles", "v" ); +const idEventDef EV_Player_StopFxFov( "stopFxFov" ); +const idEventDef EV_Player_EnableWeapon( "enableWeapon" ); +const idEventDef EV_Player_DisableWeapon( "disableWeapon" ); +const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' ); +const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' ); +const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" ); +const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' ); +const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" ); +const idEventDef EV_Player_HideTip( "hideTip" ); +const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); +const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); +const idEventDef EV_Player_GetViewPos("getViewPos", NULL, 'v'); +const idEventDef EV_Player_FinishHearingLoss ( "", "f" ); +const idEventDef EV_Player_GetAmmoData( "getAmmoData", "s", 'v'); +const idEventDef EV_Player_RefillAmmo( "refillAmmo" ); +const idEventDef EV_Player_SetExtraProjPassEntity( "setExtraProjPassEntity", "E" ); +const idEventDef EV_Player_SetArmor( "setArmor", "f" ); +const idEventDef EV_Player_DamageEffect( "damageEffect", "sE" ); +const idEventDef EV_Player_AllowFallDamage( "allowFallDamage", "d" ); + +// mekberg: allow enabling/disabling of objectives +const idEventDef EV_Player_EnableObjectives( "enableObjectives" ); +const idEventDef EV_Player_DisableObjectives( "disableObjectives" ); + +// mekberg: don't suppress showing of new objectives anymore +const idEventDef EV_Player_AllowNewObjectives( "" ); + +// RAVEN END + +CLASS_DECLARATION( idActor, idPlayer ) +// EVENT( EV_Player_HideDatabaseEntry, idPlayer::Event_HideDatabaseEntry ) + EVENT( EV_Player_ZoomIn, idPlayer::Event_ZoomIn ) + EVENT( EV_Player_ZoomOut, idPlayer::Event_ZoomOut ) + EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) + EVENT( EV_Player_GetMove, idPlayer::Event_GetMove ) + EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles ) + EVENT( EV_Player_SetViewAngles, idPlayer::Event_SetViewAngles ) + EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov ) + EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon ) + EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon ) + EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon ) + EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon ) + EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity ) + EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter ) + EVENT( EV_Player_HideTip, idPlayer::Event_HideTip ) + EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) + EVENT( EV_Player_GetViewPos, idPlayer::Event_GetViewPos ) + EVENT( EV_Player_FinishHearingLoss, idPlayer::Event_FinishHearingLoss ) + EVENT( EV_Player_GetAmmoData, idPlayer::Event_GetAmmoData ) + EVENT( EV_Player_RefillAmmo, idPlayer::Event_RefillAmmo ) + EVENT( EV_Player_AllowFallDamage, idPlayer::Event_AllowFallDamage ) + + +// mekberg: allow enabling/disabling of objectives + EVENT ( EV_Player_EnableObjectives, idPlayer::Event_EnableObjectives ) + EVENT ( EV_Player_DisableObjectives, idPlayer::Event_DisableObjectives ) + +// mekberg: don't suppress showing of new objectives anymore + EVENT ( EV_Player_AllowNewObjectives, idPlayer::Event_AllowNewObjectives ) +// RAVEN END + + EVENT( AI_EnableTarget, idPlayer::Event_EnableTarget ) + EVENT( AI_DisableTarget, idPlayer::Event_DisableTarget ) + + EVENT( EV_ApplyImpulse, idPlayer::Event_ApplyImpulse ) + +// RAVEN BEGIN +// mekberg: sethealth on player. + EVENT( AI_SetHealth, idPlayer::Event_SetHealth ) +//MCG: setArmor + EVENT( EV_Player_SetArmor, idPlayer::Event_SetArmor ) +// RAVEN END; + EVENT( EV_Player_SetExtraProjPassEntity,idPlayer::Event_SetExtraProjPassEntity ) +//MCG: direct damage + EVENT( EV_Player_DamageEffect, idPlayer::Event_DamageEffect ) +END_CLASS + +// RAVEN BEGIN +// asalmon: Xenon weapon combo system +#ifdef _XENON +nextWeaponCombo_t weaponComboChart[12] = { + // up, down, left, right + {1,3,2,4}, // 0: empty slot (select none) + {10,0,1,1}, + {2,2,5,0}, + {0,7,3,3}, + {4,4,0,9}, + {5,5,6,2}, + {6,6,6,5}, + {3,7,7,7}, + {8,8,9,8}, + {9,9,4,8}, + {10,1,10,10}, + {0,0,0,0} +}; +#endif +// RAVEN END + +const idVec4 marineHitscanTint( 0.69f, 1.0f, 0.4f, 1.0f ); +const idVec4 stroggHitscanTint( 1.0f, 0.5f, 0.0f, 1.0f ); +const idVec4 defaultHitscanTint( 0.4f, 1.0f, 0.4f, 1.0f ); + +/* +============== +idInventory::Clear +============== +*/ +void idInventory::Clear( void ) { + maxHealth = 0; + weapons = 0; + carryOverWeapons = 0; + powerups = 0; + armor = 0; + maxarmor = 0; + secretAreasDiscovered = 0; + + memset( ammo, 0, sizeof( ammo ) ); + + ClearPowerUps(); + + memset( weaponMods, 0, sizeof(weaponMods) ); + + // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level + memset( clip, -1, sizeof( clip ) ); + + items.DeleteContents( true ); + pdas.Clear(); + videos.Clear(); + + levelTriggers.Clear(); + + nextItemPickup = 0; + nextItemNum = 1; + onePickupTime = 0; + objectiveNames.Clear(); + + ammoPredictTime = 0; + lastGiveTime = 0; + + memset( ammoRegenStep, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoIndices, -1, sizeof( int ) * MAX_WEAPONS ); + memset( startingAmmo, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoRegenTime, -1, sizeof( int ) * MAX_WEAPONS ); +} + +/* +============== +idInventory::GivePowerUp +============== +*/ +void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) { + powerups |= 1 << powerup; + powerupEndTime[ powerup ] = msec == -1 ? -1 : (gameLocal.time + msec); +} + +/* +============== +idInventory::ClearPowerUps +============== +*/ +void idInventory::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + powerupEndTime[ i ] = 0; + } + powerups = 0; +} + +/* +============== +idInventory::GetPersistantData +============== +*/ +void idInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // armor + dict.SetInt( "armor", armor ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex( i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + // items + num = 0; + for( i = 0; i < items.Num(); i++ ) { + item = items[ i ]; + + // copy all keys with "inv_" + kv = item->MatchPrefix( "inv_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "inv_", kv ); + } + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + dict.SetInt( va( "weapon_mods_%i", i ), weaponMods[ i ] ); + } + + dict.SetInt( "levelTriggers", levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + sprintf( key, "levelTrigger_Level_%i", i ); + dict.Set( key, levelTriggers[i].levelName ); + sprintf( key, "levelTrigger_Trigger_%i", i ); + dict.Set( key, levelTriggers[i].triggerName ); + } +} + +/* +============== +idInventory::RestoreInventory +============== +*/ +void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + //We might not need to clear it out. + //Clear(); + + // health/armor + maxHealth = dict.GetInt( "maxhealth", "100" ); + armor = dict.GetInt( "armor", "50" ); + maxarmor = dict.GetInt( "maxarmor", "100" ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex ( i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + // items + num = dict.GetInt( "items" ); + items.SetNum( num ); + for( i = 0; i < num; i++ ) { + item = new idDict(); + items[ i ] = item; + sprintf( itemname, "item_%i ", i ); + kv = dict.MatchPrefix( itemname ); + while( kv ) { + key = kv->GetKey(); + key.Strip( itemname ); + item->Set( key, kv->GetValue() ); + kv = dict.MatchPrefix( itemname, kv ); + } + } + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + +// RAVEN BEGIN +// mekberg: removed nightmare weapon check. + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); +// RAVEN END + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + weaponMods[ i ] = dict.GetInt( va( "weapon_mods_%i", i ) ); + } + // forcefully invalidate the weapon + owner->GiveWeaponMods( 0 ); + + num = dict.GetInt( "levelTriggers" ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "levelTrigger_Level_%i", i ); + idLevelTriggerInfo lti; + lti.levelName = dict.GetString( itemname ); + sprintf( itemname, "levelTrigger_Trigger_%i", i ); + lti.triggerName = dict.GetString( itemname ); + levelTriggers.Append( lti ); + } + +} + +/* +============== +idInventory::Save +============== +*/ +void idInventory::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( maxHealth ); + savefile->WriteInt( weapons ); + savefile->WriteInt( powerups ); + savefile->WriteInt( armor ); + savefile->WriteInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->WriteInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->WriteInt( clip[ i ] ); + savefile->WriteInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->WriteInt( powerupEndTime[ i ] ); + } + + savefile->WriteInt( ammoPredictTime ); + savefile->WriteInt( lastGiveTime ); + + // Save Items + savefile->WriteInt( items.Num() ); + for( i = 0; i < items.Num(); i++ ) { + savefile->WriteDict( items[ i ] ); + } + + // TOSAVE: idStrList pdas; + // TOSAVE: idStrList pdaSecurity; + // TOSAVE: idStrList videos; + + // Save level triggers + savefile->WriteInt( levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + savefile->WriteString( levelTriggers[i].levelName ); + savefile->WriteString( levelTriggers[i].triggerName ); + } + + savefile->WriteInt( nextItemPickup ); + savefile->WriteInt( nextItemNum ); + savefile->WriteInt( onePickupTime ); + + // Save pick up item names + savefile->WriteInt( pickupItemNames.Num() ); + for( i = 0; i < pickupItemNames.Num(); i++ ) { + savefile->WriteString( pickupItemNames[ i ].name ); + savefile->WriteString( pickupItemNames[ i ].icon ); + } + + // Save objectives + savefile->WriteInt( objectiveNames.Num() ); + for( i = 0; i < objectiveNames.Num(); i++ ) { + savefile->WriteString( objectiveNames[i].screenshot ); + savefile->WriteString( objectiveNames[i].text ); + savefile->WriteString( objectiveNames[i].title ); + } +/* + // Save database + savefile->WriteInt ( database.Num() ); + for ( i = 0; i < database.Num(); i ++ ) { + savefile->WriteString ( database[i].title ); + savefile->WriteString ( database[i].text ); + savefile->WriteString ( database[i].image ); + savefile->WriteString ( database[i].filter ); + } +*/ + savefile->WriteInt( secretAreasDiscovered ); + + savefile->WriteSyncId(); +} + +/* +============== +idInventory::Restore +============== +*/ +void idInventory::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( maxHealth ); + savefile->ReadInt( weapons ); + savefile->ReadInt( powerups ); + savefile->ReadInt( armor ); + savefile->ReadInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->ReadInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->ReadInt( clip[ i ] ); + savefile->ReadInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->ReadInt( powerupEndTime[ i ] ); + } + + savefile->ReadInt( ammoPredictTime ); + savefile->ReadInt( lastGiveTime ); + + // Load Items + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idDict *itemdict = new idDict; + + savefile->ReadDict( itemdict ); + items.Append( itemdict ); + } + + // TORESTORE: idStrList pdas; + // TORESTORE: idStrList pdaSecurity; + // TORESTORE: idStrList videos; + + // Load level triggers + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idLevelTriggerInfo lti; + savefile->ReadString( lti.levelName ); + savefile->ReadString( lti.triggerName ); + levelTriggers.Append( lti ); + } + + savefile->ReadInt( nextItemPickup ); + savefile->ReadInt( nextItemNum ); + savefile->ReadInt( onePickupTime ); + + // Load pickup items + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idItemInfo itemInfo; + savefile->ReadString( itemInfo.name ); + savefile->ReadString( itemInfo.icon ); + pickupItemNames.Append( itemInfo ); + } + + // Load objectives + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idObjectiveInfo obj; + savefile->ReadString( obj.screenshot ); + savefile->ReadString( obj.text ); + savefile->ReadString( obj.title ); + objectiveNames.Append( obj ); + } +/* + // Load database + savefile->ReadInt ( num ); + for ( i = 0; i < num; i++ ) { + rvDatabaseEntry entry; + savefile->ReadString ( entry.title ); + savefile->ReadString ( entry.text ); + savefile->ReadString ( entry.image ); + savefile->ReadString ( entry.filter ); + database.Append ( entry ); + } +*/ + savefile->ReadInt( secretAreasDiscovered ); + + savefile->ReadSyncId( "idInventory::Restore" ); +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +int idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const { + return rvWeapon::GetAmmoIndexForName( ammo_classname ); +} + +/* +============== +idInventory::MaxAmmoForAmmoClass +============== +*/ +int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); +} + +/* +============== +idInventory::AmmoIndexForWeaponClass +============== +*/ +int idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + if ( ammoRequired ) { + *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + } + return AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); +} + +/* +============== +idInventory::AmmoClassForWeaponClass +============== +*/ +const char * idInventory::AmmoClassForWeaponClass( const char *weapon_classname ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + + return decl->dict.GetString( "ammoType" ); +} + +// RAVEN BEGIN +// mekberg: if the player can pick up ammo at this time +/* +============== +idInventory::DetermineAmmoAvailability +============== +*/ +bool idInventory::DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ) { + const idDeclEntityDef *weaponDecl = NULL; + const idDict* weaponDict = NULL; + const char* mod = NULL; + const idDict* modDict = NULL; + int weaponIndex = -1; + int clipSize = 0; + int modClipSize = 0; + int difference = 0; + idStr realAmmoName( ammoName ); + + // Early out + if ( ammo[ ammoIndex ] == ammoMax ) { + return false; + } + + // Make sure the clip info is updated. + if ( owner->weapon ) { + clip[ owner->GetCurrentWeapon( ) ] = owner->weapon->AmmoInClip( ); + } + + if ( !idStr::Icmpn( ammoName, "start_ammo_", 11 ) ) { + realAmmoName.StripLeading( "start_" ); + } + + // Find the entityDef for the weapon that uses this ammo. + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + + weaponDecl = owner->GetWeaponDef( i ); + + if ( !weaponDecl ) { + continue; + } + + if ( !idStr::Icmp ( weaponDecl->dict.GetString( "ammoType" ), realAmmoName.c_str() ) ) { + weaponDict = &( weaponDecl->dict ); + weaponIndex = i; + break; + } + } + + // If we didn't find one. + if ( weaponIndex == -1 ) { + // If we are picking up ammo and we aren't currently full. + if ( ammoAmount && ammo[ ammoIndex ] != ammoMax ) { + ammo[ ammoIndex ] += ammoAmount; + if ( ammo[ ammoIndex ] > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } + return true; + } + return false; + } + + clipSize = weaponDict->GetInt( "clipSize", "0" ); + + // Find the weaponmods for this weapon and see if we have any clipsize mods. + for ( int m = 0; m < MAX_WEAPONMODS; m ++ ) { + if ( ! ( weaponMods[ weaponIndex ] & ( 1 << m ) ) ) { + continue; + } + + mod = weaponDict->GetString ( va ( "def_mod%d" , m + 1 ) ); + if ( !mod || !*mod ) { + break; + } + + modDict = gameLocal.FindEntityDefDict ( mod, false ); + modClipSize = modDict->GetInt( "clipSize", "0" ); + + if ( modClipSize > clipSize ) { + clipSize = modClipSize; + } + } + + // Don't bother with these checks if we don't have a clipsize + if ( clipSize ) { + difference = ( ammoMax - clipSize ) - ( ammo[ ammoIndex ] - clip[ weaponIndex ] ); + + if ( difference ) { + if ( ammoAmount > difference ) { + ammo[ ammoIndex ] += difference; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; + } else { + return false; + } + } else if ( ( ammo[ ammoIndex ] + ammoAmount ) > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; +} +// RAVEN END + + +/* +============== +idInventory::AmmoIndexForWeaponIndex +============== +*/ +int idInventory::AmmoIndexForWeaponIndex( int weaponIndex ) { + if( ammoIndices[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoIndexForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + ammoIndices[ weaponIndex ] = AmmoIndexForWeaponClass( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ) ); + } + + return ammoIndices[ weaponIndex ]; +} + +/* +============== +idInventory::StartingAmmoForWeaponIndex +============== +*/ +int idInventory::StartingAmmoForWeaponIndex( int weaponIndex ) { + if( startingAmmo[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::StartingAmmoForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + const idKeyValue* kv = weaponDict->MatchPrefix( "inv_start_ammo" ); + if( kv == NULL ) { + startingAmmo[ weaponIndex ] = 1; + } else { + startingAmmo[ weaponIndex ] = atoi( kv->GetValue() ); + kv = weaponDict->MatchPrefix( "inv_start_ammo", kv ); + if( kv != NULL ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Weapon dict for player's def_weapon%d has multiple inv_start_ammo entries\n", weaponIndex ); + return -1; + } + } + } + + return startingAmmo[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenStepForWeaponIndex +============== +*/ +int idInventory::AmmoRegenStepForWeaponIndex( int weaponIndex ) { + if( ammoRegenStep[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenStepForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenStepForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenStep[ weaponIndex ] = weaponDict->GetInt( "ammoRegenStep", "1" ); + } + + return ammoRegenStep[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenTimeForAmmoIndex +============== +*/ +int idInventory::AmmoRegenTimeForWeaponIndex( int weaponIndex ) { + if ( ammoRegenTime[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenTimeForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenTimeForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenTime[ weaponIndex ] = weaponDict->GetInt( "ammoRegenTime", "1" ); + } + + return ammoRegenTime[ weaponIndex ]; +} + + +/* +============== +idInventory::Give +If checkOnly is true, check only for possibility of adding to inventory, don't actually add +============== +*/ +bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped, bool checkOnly ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + int amount; + + if ( owner->IsFakeClient() ) { + return false; + } + + if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( ammo[ i ] >= max ) { + return false; + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmpn( statname, "start_ammo_", 11 ) ) { + // starting ammo gives only if current ammo is below it + idStr ammoname( statname ); + ammoname.StripLeading( "start_" ); + i = AmmoIndexForAmmoClass( ammoname.c_str() ); + max = MaxAmmoForAmmoClass( owner, ammoname.c_str() ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( amount ) { + if ( ammo[ i ] >= amount ) { + amount = 1; + } else { + amount = amount - ammo[ i ]; + } + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor * 2 ) { + return false; + } + } else if ( !idStr::Icmp( statname, "health" ) ) { + if ( owner->health >= maxHealth ) { + return false; + } + } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { + i = owner->SlotForWeapon ( statname + 7 ); + if ( i != -1 && !checkOnly ) { + // set, don't add. not going over the clip size limit. + clip[ i ] = atoi( value ); + } + } else if ( !idStr::Icmp( statname, "quad" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_QUADDAMAGE, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "regen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_REGENERATION, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "haste" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_HASTE, SEC2MS( atof( value ) ) ); + } else if( !idStr::Icmp( statname, "ammoregen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_AMMOREGEN, -1 ); + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + bool tookWeapon = false; + for( pos = value; pos != NULL; pos = end ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponName( pos, 0, len ); + + // find the number of the matching weapon names + i = owner->SlotForWeapon ( weaponName ); + if ( i == -1 ) { + gameLocal.Warning( "Unknown weapon '%s'", weaponName.c_str() ); + return false; + } + + if ( gameLocal.isMultiplayer + && ( weapons & ( 1 << i ) ) ) { + //already have this weapon + if ( !dropped ) { + //a placed weapon item + if ( gameLocal.IsWeaponsStayOn() ) { + //don't pick up weapons at all if you already have them... + continue; + } + } + // don't pickup "no ammo" weapon types twice + // not for singleplayer.. there is only one case in the game where you can get a no ammo + // weapon when you might already have it, in that case it is more consistent to pick it up + // cache the media for this weapon + const idDict* dict; + dict = &owner->GetWeaponDef ( i )->dict; + if ( dict && !dict->GetInt( "ammoRequired" ) ) { + continue; + } + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) ) { + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) + || ( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) ) + && idealWeapon && !checkOnly ) + { + // client prediction should not get here + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time && !checkOnly ) { + owner->hud->SetStateInt( "newWeapon", i ); + owner->hud->HandleNamedEvent( "newWeapon" ); + lastGiveTime = gameLocal.time; + } + if ( !checkOnly ) { + weapons |= ( 1 << i ); + } + tookWeapon = true; + } + } + } + return tookWeapon; + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + return false; + } else { + // unknown item + gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname ); + return false; + } + + return true; +} + +/* +=============== +idInventoy::Drop +=============== +*/ +void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) { + // remove the weapon bit + // also remove the ammo associated with the weapon as we pushed it in the item + assert( weapon_index != -1 || weapon_classname ); + if ( weapon_index == -1 ) { + for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) { + if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) { + break; + } + } + + if ( weapon_index >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weapon_classname ); + } + } else if ( !weapon_classname ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ); + } + weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) ); + int ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL ); + if ( ammo_i ) { + clip[ weapon_index ] = -1; + ammo[ ammo_i ] = 0; + } + + weaponMods[weapon_index] = 0; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( int index, int amount ) { + if ( ( index == 0 ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ index ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ index ] / amount; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + int index; + index = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( index, ammoRequired ); +} + +/* +=============== +idInventory::UseAmmo +=============== +*/ +bool idInventory::UseAmmo( int index, int amount ) { + if ( !HasAmmo( index, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ index ] >= 0 ) { + ammo[ index ] -= amount; + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +/* +============== +idPlayer::idPlayer +============== +*/ +idPlayer::idPlayer() { + memset( &usercmd, 0, sizeof( usercmd ) ); + + alreadyDidTeamAnnouncerSound = false; + + noclip = false; + godmode = false; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + spawnAnglesSet = false; + spawnAngles = ang_zero; + viewAngles = ang_zero; + deltaViewAngles = ang_zero; + cmdAngles = ang_zero; + + demoViewAngleTime = 0; + demoViewAngles = ang_zero; + + oldButtons = 0; + buttonMask = 0; + oldFlags = 0; + + lastHitFrame = 0; + lastHitArmor = false; + lastSavingThrowTime = 0; + + weapon = NULL; + + hud = NULL; + mphud = NULL; + objectiveSystem = NULL; + objectiveSystemOpen = false; + showNewObjectives = false; +#ifdef _XENON + g_ObjectiveSystemOpen = false; +#endif + objectiveButtonReleased = false; + cinematicHud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + lastDmgTime = 0; + deathClearContentsTime = 0; + nextHealthPulse = 0; + + scoreBoardOpen = false; + forceScoreBoard = false; + forceScoreBoardTime = 0; + forceRespawn = false; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// squirrel: Mode-agnostic buymenus + inBuyZone = false; + inBuyZonePrev = false; +// RITUAL END + spectating = false; + spectator = 0; + forcedReady = false; + wantSpectate = false; + + minRespawnTime = 0; + maxRespawnTime = 0; + + firstPersonViewOrigin = vec3_zero; + firstPersonViewAxis = mat3_identity; + + hipJoint = INVALID_JOINT; + chestJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + neckLeanJoint = INVALID_JOINT; + + bobFoot = 0; + bobFrac = 0.0f; + bobfracsin = 0.0f; + bobCycle = 0; + xyspeed = 0.0f; + stepUpTime = 0; + stepUpDelta = 0.0f; + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + viewBobAngles = ang_zero; + viewBob = vec3_zero; + landChange = 0; + landTime = 0; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + carryOverCurrentWeapon = -1; +// RITUAL END + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = true; + oldInventoryWeapons = 0; + +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; +// RAVEN END + + skin = NULL; + weaponViewSkin = NULL; + headSkin = NULL; + powerUpSkin = NULL; + + numProjectilesFired = 0; + numProjectileHits = 0; + + airless = false; + airTics = 0; + lastAirDamage = 0; + + gibDeath = false; + gibsLaunched = false; + gibDir = vec3_zero; + + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + privateCameraView = NULL; + + memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) ); + memset( loggedAccel, 0, sizeof( loggedAccel ) ); + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + + cursor = NULL; + talkCursor = 0; + + oldMouseX = 0; + oldMouseY = 0; + + lastDamageDef = 0; + lastDamageDir = vec3_zero; + lastDamageLocation = 0; + + predictedFrame = 0; + predictedOrigin = vec3_zero; + predictedAngles = ang_zero; + predictedUpdated = false; + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + predictionErrorTime = 0; + + fl.networkSync = true; + + latchedTeam = -1; + hudTeam = -1; + doingDeathSkin = false; + weaponGone = false; + useInitialSpawns = false; + lastSpectateTeleport = 0; + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + lastKiller = NULL; + + respawning = false; + ready = false; + leader = false; + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + weaponCatchup = false; + lastSnapshotSequence = 0; + + aimClientNum = -1; + + spawnedTime = 0; + + isTelefragged = false; + isLagged = false; + isChatting = false; + + intentDir.Zero(); + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + ResetCash(); +// RITUAL END + + zoomFov.Init ( 0, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + + memset ( cachedWeaponDefs, 0, sizeof(cachedWeaponDefs) ); + memset ( cachedPowerupDefs, 0, sizeof(cachedPowerupDefs) ); + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time; + + weaponChangeIconsUp = false; + + reloadModel = false; + modelDecl = NULL; + + disableHud = false; + + mutedPlayers = 0; + friendPlayers = 0; + connectTime = 0; + rank = -1; + arena = 0; + + memset( nextAmmoRegenPulse, 0, sizeof( int ) * MAX_AMMO ); + + spectator = 0; + + quadOverlay = NULL; + hasteOverlay = NULL; + regenerationOverlay = NULL; + invisibilityOverlay = NULL; + powerUpOverlay = NULL; + + tourneyStatus = PTS_UNKNOWN; + + vsMsgState = false; + + deathSkinTime = 0; + + lastPickupTime = 0; + + int i; + for ( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + voiceDest[i] = -1; + voiceDestTimes[i] = 0; + } + + itemCosts = NULL; + + teamHealthRegen = NULL; + teamHealthRegenPending = false; + teamAmmoRegen = NULL; + teamAmmoRegenPending = false; + teamDoubler = NULL; + teamDoublerPending = false; + + leanVelocity = vec3_zero; + leanMaxSpeed = 0.0f; + leanBlendRatio = 0.0f; + leanMaxLateralAngle = 0.0f; + leanMaxForwardAngle = 0.0f; + leanHeadRatio = 0.0f; + leanHipRatio = 0.0f; + + ignoreOnGroundUntilFrame = 0; + prevOnGround = true; + clientIdealWeaponPredictFrame = -1; + serverReceiveEvent = false; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +void idPlayer::SetShowHud( bool showHud ) { + disableHud = !showHud; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +bool idPlayer::GetShowHud( void ) { + return !disableHud; +} + +/* +============== +idPlayer::SetWeapon +============== +*/ +void idPlayer::SetWeapon( int weaponIndex ) { + if ( weapon && weaponIndex == currentWeapon ) { + return; + } + + // Clear the weapon entity + delete weapon; + weapon = NULL; + + previousWeapon = currentWeapon; + currentWeapon = weaponIndex; + weaponGone = false; + + if ( weaponIndex < 0 ) { + weaponGone = true; + return; + } + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + + idTypeInfo* typeInfo; + weaponDef = GetWeaponDef( currentWeapon ); + if ( !weaponDef ) { + gameLocal.Error( "Weapon definition not found for weapon %d", currentWeapon ) ; + } + typeInfo = idClass::GetClass( weaponDef->dict.GetString( "weaponclass", "rvWeapon" ) ); + if ( !typeInfo || !typeInfo->IsType( rvWeapon::GetClassType() ) ) { + gameLocal.Error( "Invalid weapon class '%s' specified for weapon '%s'", animPrefix.c_str(), weaponDef->dict.GetString ( "weaponclass", "rvWeapon" ) ); + } + weapon = static_cast( typeInfo->CreateInstance() ); + weapon->Init( this, weaponDef, currentWeapon, isStrogg ); + weapon->CallSpawn( ); + + // Reset the zoom fov on weapon change + if ( zoomed ) { + zoomFov.Init ( gameLocal.time, 100, CalcFov(true), DefaultFov() ); + zoomed = false; + } + + UpdateHudWeapon(); + + // Remove the "weapon_" from the anim prefect for the player world anims + animPrefix.Strip( "weapon_" ); + + // Make sure weapon is hidden + if ( !weaponEnabled ) { + Event_DisableWeapon(); + } +} + +/* +============== +idPlayer::SetupWeaponEntity +============== +*/ +void idPlayer::SetupWeaponEntity( void ) { + int w; + const char *weap; + const idDeclEntityDef *decl; + idEntity *spawn; + + // don't setup weapons for spectators + if ( gameLocal.isClient || (weaponViewModel && weaponWorldModel) || spectating ) { + return; + } + + idDict args; + + if ( !weaponViewModel ) { + // setup the view model +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_viewweapon", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_viewweapon" ); + } + args.Set( "name", va( "%s_weapon", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponViewModel" ); + } + weaponViewModel = static_cast(spawn); + weaponViewModel->SetName( va("%s_weapon", name.c_str() ) ); + weaponViewModel->SetInstance( instance ); + } + + + if ( !weaponWorldModel ) { + // setup the world model + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_animatedentity", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_animatedentity" ); + } + args.Set( "name", va( "%s_weapon_world", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponWorldModel" ); + } + weaponWorldModel = static_cast(spawn); + weaponWorldModel->fl.networkSync = true; + weaponWorldModel->SetName ( va("%s_weapon_world", name.c_str() ) ); + weaponWorldModel->SetInstance( instance ); + } + + currentWeapon = -1; + + weaponWorldModel->fl.persistAcrossInstances = true; + weaponViewModel->fl.persistAcrossInstances = true; + + for( w = 0; w < MAX_WEAPONS; w++ ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap && *weap ) { + rvWeapon::CacheWeapon( weap ); + } + } +} + +/* +============== +idPlayer::Init +============== +*/ +void idPlayer::Init( void ) { + const char *value; + const idDict *userInfo = GetUserInfo(); + + Hide(); + + noclip = false; + godmode = false; + godmodeDamage = 0; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + oldButtons = 0; + oldFlags = 0; + + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = userInfo ? userInfo->GetBool( "ui_showGun" ) : cvarSystem->GetCVarBool( "ui_showGun" ); + oldInventoryWeapons = 0; + + lastHitFrame = 0; + lastHitArmor = false; + + lastDmgTime = 0; + + bobCycle = 0; + bobFrac = 0.0f; + landChange = 0; + landTime = 0; + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + talkCursor = 0; + + lightningEffects = 0; + lightningNextTime = 0; + + modelName = idStr(); + + // Remove any hearing loss that may be set up from the last map + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0 ); + + // remove any damage effects + playerView.ClearEffects(); + + // damage values + fl.takedamage = true; + ClearPain(); + + // restore persistent data + RestorePersistantInfo(); + + bobCycle = 0; + + SetupWeaponEntity( ); + currentWeapon = -1; + previousWeapon = -1; + + flashlightOn = false; + + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + + leanVelocity = vec3_zero; + + ignoreOnGroundUntilFrame = 0; + prevOnGround = true; + + vehicleCameraDist = 0.0f; + + SetPMCVars(); + + // air always initialized to maximum too + airTics = pm_airTics.GetFloat(); + airless = false; + + gibDeath = false; + gibsLaunched = false; + gibDir.Zero(); + + // set the gravity + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + + // start out standing + SetEyeHeight( pm_normalviewheight.GetFloat() ); + + stepUpTime = 0; + stepUpDelta = 0.0f; + viewBobAngles.Zero(); + viewBob.Zero(); + + if ( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + value = spawnArgs.GetString( va( "model_%s", team ? "strogg" : "marine" ), NULL ); + } else { + value = spawnArgs.GetString( "model" ); + } + + if ( gameLocal.isMultiplayer ) { + UpdateModelSetup( true ); + } else { + if ( value && ( *value != 0 ) ) { + SetModel( value ); + } + // check head + if( idStr::Icmp( head ? head->spawnArgs.GetString( "classname", "" ) : "", spawnArgs.GetString( "def_head", "" ) ) ) { + SetupHead(); + } + } + + if ( cursor ) { + cursor->SetStateInt( "talkcursor", 0 ); + cursor->HandleNamedEvent( "showCrossCombat" ); + } + + if ( !gameLocal.isMultiplayer ) { + if ( g_testDeath.GetBool() && skin ) { + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) { + skin = declManager->FindSkin( value ); + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + if( clientHead->GetModelDefHandle() > 0) { + gameRenderWorld->RemoveDecals( clientHead->GetModelDefHandle() ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + value = spawnArgs.GetString( "joint_hips", "" ); + hipJoint = animator.GetJointHandle( value ); + if ( hipJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_hips' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_chest", "" ); + chestJoint = animator.GetJointHandle( value ); + if ( chestJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_chest' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_head", "" ); + headJoint = animator.GetJointHandle( value ); + if ( headJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_neckLean", "neckcontrol" ); + neckLeanJoint = animator.GetJointHandle( value ); + if ( neckLeanJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_neck' on '%s'", value, name.c_str() ); + } + + // read player lean properties + spawnArgs.GetFloat( "lean_maxSpeed", "500", leanMaxSpeed ); + spawnArgs.GetFloat( "lean_blendRatio", "0.9", leanBlendRatio ); + spawnArgs.GetFloat( "lean_maxLateralAngle", "30", leanMaxLateralAngle ); + spawnArgs.GetFloat( "lean_maxForwardAngle", "50", leanMaxForwardAngle ); + spawnArgs.GetFloat( "lean_headRatio", "-0.5", leanHeadRatio ); + spawnArgs.GetFloat( "lean_hipRatio", "0.5", leanHipRatio ); + + // initialize the script variables + memset( &pfl, 0, sizeof( pfl ) ); + pfl.onGround = true; + pfl.noFallingDamage = false; + + // Start in idle + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + forceScoreBoard = false; + forceScoreBoardTime = 0; + forcedReady = false; + + privateCameraView = NULL; + + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + lastKiller = NULL; + teleportKiller = -1; + leader = false; + + SetPrivateCameraView( NULL ); + + lastSnapshotSequence = 0; + + if ( !gameLocal.isMultiplayer ) { + // in MP we set isStrogg in UpdateModelSetup() + isStrogg = spawnArgs.GetBool ( "strogg", "0" ); + } + + + aimClientNum = -1; + if ( mphud ) { + mphud->HandleNamedEvent( "aim_fade" ); + } + + isChatting = false; + + SetInitialHud(); + + emote = PE_NONE; + + powerupEffectTime = 0; + powerupEffect = NULL; + powerupEffectType = 0; + hasteEffect = NULL; + flagEffect = NULL; + arenaEffect = NULL; + + quadOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_quaddamage_overlay" ), false ); + hasteOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_haste_overlay" ), false ); + regenerationOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_regeneration_overlay" ), false ); + invisibilityOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_invisibility_overlay" ), false ); + powerUpOverlay = NULL; + + if ( gameLocal.isMultiplayer && IsLocalClient() ) { + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP) && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ){ + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_WARMUP && ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_SUDDEN_DEATH ) { + // don't clear notices while in warmup modes or sudden death + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP) && vsMsgState ) { + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + deathSkinTime = 0; + deathStateHitch = false; + + lastPickupTime = 0; + + if ( teamHealthRegenPending ) { + assert( teamHealthRegen == NULL ); + teamHealthRegenPending = false; + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamAmmoRegenPending ) { + assert( teamAmmoRegen == NULL ); + teamAmmoRegenPending = false; + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamDoublerPending ) { + assert( teamDoubler == NULL ); + teamDoublerPending = false; + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + + clientIdealWeaponPredictFrame = -1; + serverReceiveEvent = false; +} + +/* +=============== +idPlayer::ProjectHeadOverlay +=============== +*/ +void idPlayer::ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) { + + if( clientHead ) { + clientHead.GetEntity()->ProjectOverlay( point, dir, size, decal ); + } +} + +/* +=============== +idPlayer::GetCursorGUI +=============== +*/ +idUserInterface* idPlayer::GetCursorGUI( void ) { + idStr temp; + + assert( !gameLocal.isMultiplayer || IsLocalClient() ); + if ( cursor ) { + return cursor; + } + if ( spawnArgs.GetString( "cursor", "", temp ) ) { + cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer ); + } + return cursor; +} + +/* +============== +idPlayer::Spawn + +Prepare any resources used by the player. +============== +*/ +void idPlayer::Spawn( void ) { + idStr temp; + idBounds bounds; + + if ( entityNumber >= MAX_CLIENTS && !IsFakeClient() ) { + gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); + } + + // allow thinking during cinematics + cinematic = true; + + if ( gameLocal.isMultiplayer ) { + // always start in spectating state waiting to be spawned in + // do this before SetClipModel to get the right bounding box + spectating = true; + } + + // set our collision model + physicsObj.SetSelf( this ); + SetClipModel( ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + SetPhysics( &physicsObj ); + InitAASLocation(); + + skin = renderEntity.customSkin; + + // only the local player needs guis + // for server netdemos that have no local player, we use demo_* guis in idGameLocal + if ( !gameLocal.isMultiplayer || IsLocalClient() ) { + + // load HUD + hud = NULL; + mphud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + objectiveSystem = NULL; + + if ( spawnArgs.GetString( "hud", "", temp ) ) { + hud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No hud for player." ); + } + + if ( gameLocal.isMultiplayer ) { + if ( spawnArgs.GetString( "mphud", "", temp ) ) { + mphud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No MP hud overlay while in MP."); + } + } + + if ( hud ) { + hud->Activate( true, gameLocal.time ); + } + + if ( mphud ) { + mphud->Activate( true, gameLocal.time ); + } + + // load cursor + GetCursorGUI(); + if ( cursor ) { + cursor->Activate( true, gameLocal.time ); + } + + // Load + + if ( spawnArgs.GetString ( "cinematicHud", "", temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + + if ( !gameLocal.isMultiplayer ) { + objectiveSystem = uiManager->FindGui( spawnArgs.GetString( "wristcomm", "guis/wristcomm.gui" ), true, false, true ); + objectiveSystemOpen = false; +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + + // clear votes + // if we want to display current votes that were started before a player was connected + // but are still being voted on, this should check the current vote and update the gui appropriately + gameLocal.mpGame.ClearVote( entityNumber ); + } + + // load the armor sound feedback + declManager->FindSound( "player_sounds_hitArmor" ); + + animator.RemoveOriginOffset( true ); + + // initialize user info related settings + // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game + // ocassionally, a race condition may mark a client in-game before he is spawned, if this is the case, parse the userinfo here + if ( (gameLocal.isClient || IsLocalClient() ) || (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber ) ) ) { + UserInfoChanged(); + } + + // create combat collision hull for exact collision detection + SetCombatModel(); + + // init the damage effects + playerView.SetPlayerEntity( this ); + + // supress model in non-player views, but allow it in mirrors and remote views + renderEntity.suppressSurfaceInViewID = entityNumber+1; + + // don't project shadow on self or weapon + renderEntity.noSelfShadow = true; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + clientHead->GetRenderEntity()->noSelfShadow = true; + } + } else { + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1; + headEnt->GetRenderEntity()->noSelfShadow = true; + } + } + + if ( gameLocal.isMultiplayer ) { + Init(); + Hide(); // properly hidden if starting as a spectator + + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + if( inOtherInstance ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } + + if ( !gameLocal.isClient ) { + // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + spectator = entityNumber; + forceRespawn = true; + assert( spectating ); + } + } else { + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + } + + // trigger playtesting item gives, if we didn't get here from a previous level + // the devmap key will be set on the first devmap, but cleared on any level + // transitions + if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) { + // fire a trigger with the name "devmap" + idEntity *ent = gameLocal.FindEntity( "devmap" ); + if ( ent ) { + ent->ActivateTargets( this ); + } + } + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + hiddenWeapon = true; + if ( weapon ) { + weapon->LowerWeapon(); + } + idealWeapon = SlotForWeapon( "weapon_blaster" ); + Event_DisableWeapon(); + } else { + hiddenWeapon = false; + } + + if ( hud ) { + UpdateHudWeapon( ); + hud->StateChanged( gameLocal.time ); + } + + tipUp = false; + objectiveUp = false; + + aiManager.AddTeammate( this ); + + if ( inventory.levelTriggers.Num() ) { + PostEventMS( &EV_Player_LevelTrigger, 0 ); + } + + // ddynerman: defaults for these values are the single player fall deltas + fatalFallDelta = spawnArgs.GetFloat("fatal_fall_delta", "65"); + hardFallDelta = spawnArgs.GetFloat("hard_fall_delta", "45"); + softFallDelta = spawnArgs.GetFloat("soft_fall_delta", "30"); + noFallDelta = spawnArgs.GetFloat("no_fall_delta", "7"); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); + + gibSkin = declManager->FindSkin( spawnArgs.GetString( "skin_gibskin" ) ); + + // Skil levels + dynamicProtectionScale = 1.0f; + if ( !gameLocal.isMultiplayer ) { + if ( g_skill.GetInteger() < 2 ) { + if ( health < 25 ) { + health = 25; + } + } else { + //g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + } + } + + // Powerup joints? + if ( spawnArgs.GetString ( "powerup_effect_joints", "", temp ) ) { + animator.GetJointList ( temp, powerupEffectJoints ); + } + + // RAVEN BEGIN + // mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; + + // mekberg: new objectives are suppressed until this event is processed + PostEventMS( &EV_Player_AllowNewObjectives, 5000 ); + tourneyStatus = PTS_UNKNOWN; + + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + + // zero out view angles when we spawn ourselves in MP - the server will send down + // the correct ones (only zero if our input is still zero'd) + if( gameLocal.isClient && IsLocalClient() && usercmd.angles[ 0 ] == 0 && usercmd.angles[ 1 ] == 0 && usercmd.angles[ 2 ] == 0 ) { + deltaViewAngles = ang_zero; + } +//RITUAL BEGIN + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = 0; +//RITUAL END + + itemCosts = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "ItemCostConstants", false ) ); +} + +/* +============== +idPlayer::~idPlayer() + +Release any resources used by the player. +============== +*/ +idPlayer::~idPlayer() { + if( gameLocal.mpGame.GetGameState() ) { + gameLocal.mpGame.GetGameState()->ClientDisconnect( this ); + } + + delete weaponViewModel; + delete weaponWorldModel; + delete weapon; + delete aasSensor; + + SetPhysics( NULL ); +} + +/* +=========== +idPlayer::Save +=========== +*/ +void idPlayer::Save( idSaveGame *savefile ) const { + assert( !IsFakeClient() ); + + int i; + + savefile->WriteUsercmd( usercmd ); + + playerView.Save( savefile ); + + savefile->WriteBool( noclip ); + savefile->WriteBool( godmode ); + savefile->WriteInt ( godmodeDamage ); + savefile->WriteBool( undying ); + + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame + savefile->WriteAngles( spawnAngles ); + savefile->WriteAngles( viewAngles ); + savefile->WriteAngles( cmdAngles ); + + savefile->WriteInt( buttonMask ); + savefile->WriteInt( oldButtons ); + savefile->WriteInt( oldFlags ); + + savefile->WriteInt( 0 ); + savefile->WriteInt( 0 ); + savefile->WriteInt( lastSavingThrowTime ); + + // idBoolFields don't need to be saved, just re-linked in Restore + savefile->Write( &pfl, sizeof( pfl ) ); + + inventory.Save( savefile ); + + //weapon->Save( savefile ); // Don't save this + + weaponViewModel.Save( savefile ); + weaponWorldModel.Save ( savefile ); + // weaponDef restore = weaponDef = GetWeaponDef ( currentWeapon ); + + savefile->WriteUserInterface( hud, false ); +// savefile->WriteUserInterface( mphud, false ); // Don't save MP stuff + savefile->WriteUserInterface( objectiveSystem, false ); + savefile->WriteUserInterface( cinematicHud, false ); + savefile->WriteBool( objectiveSystemOpen ); + savefile->WriteBool( disableHud ); + + savefile->WriteInt( lastDmgTime ); + savefile->WriteInt( deathClearContentsTime ); + savefile->WriteBool( doingDeathSkin ); + savefile->WriteInt( nextHealthPulse ); + savefile->WriteInt( nextArmorPulse ); + savefile->WriteBool( hiddenWeapon ); + +// savefile->WriteInt( spectator ); // Don't save MP stuff + +// savefile->WriteBool( scoreBoardOpen ); // Don't save MP stuff +// savefile->WriteBool( tourneyBracketsOpen ); // Don't save MP stuff +// savefile->WriteBool( forceScoreBoard ); // Don't save MP stuff +// savefile->WriteBool( forceRespawn ); // Don't save MP stuff + +// savefile->WriteBool( spectating ); // Don't save MP stuff +// savefile->WriteBool( lastHitToggle ); // Don't save MP stuff +// savefile->WriteBool( forcedReady ); // Don't save MP stuff +// savefile->WriteBool( wantSpectate ); // Don't save MP stuff + +// savefile->WriteBool( weaponGone ); // Don't save MP stuff +// savefile->WriteBool( useInitialSpawns ); // Don't save MP stuff +// savefile->WriteBool( isLagged ); // Don't save MP stuff +// savefile->WriteBool( isChatting ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateTeleport ); // Don't save MP stuff +// savefile->WriteInt( latchedTeam ); // Don't save MP stuff +// savefile->WriteInt( spawnedTime ); // Don't save MP stuff + +// teleportEntity.Save( savefile ); // Don't save MP stuff +// savefile->WriteInt( teleportKiller ); // Don't save MP stuff + + savefile->WriteInt( minRespawnTime ); + savefile->WriteInt( maxRespawnTime ); + + savefile->WriteVec3( firstPersonViewOrigin ); + savefile->WriteMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity or aasSensor since it's a dev tool + savefile->WriteVec3( intentDir ); + + savefile->WriteFloat ( vehicleCameraDist ); + + savefile->WriteJoint( hipJoint ); + savefile->WriteJoint( chestJoint ); + savefile->WriteJoint( headJoint ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( aasLocation.Num() ); + for( i = 0; i < aasLocation.Num(); i++ ) { + savefile->WriteInt( aasLocation[ i ].areaNum ); + savefile->WriteVec3( aasLocation[ i ].pos ); + } + + savefile->WriteString( modelName ); // cnicholson: Added unsaved var + // TOSAVE: const idDict* modelDict + + savefile->WriteInt( bobFoot ); + savefile->WriteFloat( bobFrac ); + savefile->WriteFloat( bobfracsin ); + savefile->WriteInt( bobCycle ); + savefile->WriteFloat( xyspeed ); + savefile->WriteInt( stepUpTime ); + savefile->WriteFloat( stepUpDelta ); + savefile->WriteFloat( idealLegsYaw ); + savefile->WriteFloat( legsYaw ); + savefile->WriteBool( legsForward ); + savefile->WriteFloat( oldViewYaw ); + savefile->WriteAngles( viewBobAngles ); + savefile->WriteVec3( viewBob ); + savefile->WriteInt( landChange ); + savefile->WriteInt( landTime ); + + savefile->WriteFloat( fatalFallDelta ); + savefile->WriteFloat( hardFallDelta ); + savefile->WriteFloat( softFallDelta ); + savefile->WriteFloat( noFallDelta ); + + savefile->WriteInt( currentWeapon ); + savefile->WriteInt( idealWeapon ); + savefile->WriteInt( previousWeapon ); + savefile->WriteInt( weaponSwitchTime ); + savefile->WriteBool( weaponEnabled ); + + savefile->WriteBool ( flashlightOn); + savefile->WriteBool ( zoomed ); + + savefile->WriteBool( reloadModel ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( powerUpSkin ); + savefile->WriteSkin( gibSkin ); + + savefile->WriteInt( numProjectilesFired ); + savefile->WriteInt( numProjectileHits ); + + savefile->WriteBool( airless ); + savefile->WriteInt( airTics ); + savefile->WriteInt( lastAirDamage ); + + savefile->WriteBool( gibDeath ); + savefile->WriteBool( gibsLaunched ); + savefile->WriteVec3( gibDir ); + + savefile->WriteBool( isStrogg ); + + savefile->WriteInterpolate( zoomFov ); + savefile->WriteInterpolate( centerView ); + savefile->WriteBool( fxFov ); + + savefile->WriteFloat( influenceFov ); + savefile->WriteInt( influenceActive ); + savefile->WriteObject( influenceEntity ); + savefile->WriteMaterial( influenceMaterial ); + savefile->WriteFloat( influenceRadius ); + savefile->WriteSkin( influenceSkin ); + + savefile->WriteObject( privateCameraView ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->WriteAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->WriteInt( loggedAccel[ i ].time ); + savefile->WriteVec3( loggedAccel[ i ].dir ); + } + savefile->WriteInt( currentLoggedAccel ); + + savefile->WriteUserInterface( focusUI, false ); + savefile->WriteInt( focusTime ); + savefile->WriteInt ( focusType ); + focusEnt.Save ( savefile ); + savefile->WriteUserInterface( focusBrackets, false ); + savefile->WriteInt( focusBracketsTime ); + + talkingNPC.Save( savefile ); + + extraProjPassEntity.Save( savefile ); + + savefile->WriteInt( talkCursor ); + savefile->WriteUserInterface( cursor, false ); + + savefile->WriteUserInterface( overlayHud, false ); + savefile->WriteInt ( overlayHudTime ); + + savefile->WriteBool( targetFriendly ); + + savefile->WriteInt( oldMouseX ); + savefile->WriteInt( oldMouseY ); + + savefile->WriteBool( tipUp ); + savefile->WriteBool( objectiveUp ); + + savefile->WriteFloat( dynamicProtectionScale ); + savefile->WriteInt( lastDamageDef ); + savefile->WriteVec3( lastDamageDir ); + savefile->WriteInt( lastDamageLocation ); + + savefile->WriteInt( predictedFrame ); + savefile->WriteVec3( predictedOrigin ); + savefile->WriteAngles( predictedAngles ); + savefile->WriteBool( predictedUpdated ); + savefile->WriteVec3( predictionOriginError ); + savefile->WriteAngles( predictionAnglesError ); + savefile->WriteInt( predictionErrorTime ); + +// savefile->WriteBool( ready ); // Don't save MP stuff +// savefile->WriteBool( respawning ); // Don't save MP stuff +// savefile->WriteBool( leader ); // Don't save MP stuff +// savefile->WriteBool( weaponCatchup ); // Don't save MP stuff +// savefile->WriteBool( isTelefragged ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateChange ); // Don't save MP stuff +// savefile->WriteInt( lastTeleFX ); // Don't save MP stuff +// savefile->WriteInt( lastSnapshotSequence ); // Don't save MP stuff + +// savefile->WriteInt( aimClientNum ); // Don't save MP stuff + +// lastImpulsePlayer->Save( savefile ); // Don't save MP stuff + +// savefile->WriteInt( arena ); // Don't save MP stuff + +// savefile->WriteInt( connectTime ); // Don't save MP stuff +// savefile->WriteInt( mutedPlayers ); // Don't save MP stuff +// savefile->WriteInt( friendPlayers ); // Don't save MP stuff + +// savefile->WriteInt( rank ); // Don't save MP stuff + + savefile->WriteInt( lastImpulseTime ); + bossEnemy.Save( savefile ); // cnicholson: Added unsaved var + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->WriteBool( weaponChangeIconsUp ); // cnicholson: Added unsaved var + + // mekberg: added + savefile->WriteBool( showNewObjectives ); + savefile->WriteBool( objectivesEnabled ); + + savefile->WriteBool( flagCanFire ); + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + +#ifndef _XENON + if ( hud ) { + hud->SetStateString( "message", common->GetLocalizedString( "#str_102916" ) ); + hud->HandleNamedEvent( "Message" ); + } +#endif +} + +/* +=========== +idPlayer::Restore +=========== +*/ +void idPlayer::Restore( idRestoreGame *savefile ) { + assert( !IsFakeClient() ); + + int i; + int num; + + savefile->ReadUsercmd( usercmd ); + + playerView.Restore( savefile ); + + savefile->ReadBool( noclip ); + savefile->ReadBool( godmode ); + savefile->ReadInt ( godmodeDamage ); + savefile->ReadBool( undying ); + + savefile->ReadAngles( spawnAngles ); + savefile->ReadAngles( viewAngles ); + savefile->ReadAngles( cmdAngles ); + + memset( usercmd.angles, 0, sizeof( usercmd.angles ) ); + SetViewAngles( viewAngles ); + spawnAnglesSet = true; + + savefile->ReadInt( buttonMask ); + savefile->ReadInt( oldButtons ); + savefile->ReadInt( oldFlags ); + + usercmd.flags = 0; + oldFlags = 0; + + int foo; + savefile->ReadInt( foo ); + savefile->ReadInt( foo ); + savefile->ReadInt( lastSavingThrowTime ); + + savefile->Read( &pfl, sizeof( pfl ) ); + + inventory.Restore( savefile ); + + assert( !weapon ); + + weaponViewModel.Restore( savefile ); + weaponWorldModel.Restore( savefile ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + assert( !mphud ); // Don't save MP stuff + savefile->ReadUserInterface( objectiveSystem, &spawnArgs ); + savefile->ReadUserInterface( cinematicHud, &spawnArgs ); + savefile->ReadBool( objectiveSystemOpen ); + +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + + objectiveButtonReleased = false; + savefile->ReadBool( disableHud ); // cnicholson: Added unrestored var + + savefile->ReadInt( lastDmgTime ); + savefile->ReadInt( deathClearContentsTime ); + savefile->ReadBool( doingDeathSkin ); + savefile->ReadInt( nextHealthPulse ); + savefile->ReadInt( nextArmorPulse ); + savefile->ReadBool( hiddenWeapon ); + + assert( !spectator ); // Don't save MP stuff + + assert( !scoreBoardOpen ); // Don't save MP stuff + assert( !forceScoreBoard ); // Don't save MP stuff + assert( !forceRespawn ); // Don't save MP stuff + + assert( !spectating ); // Don't save MP stuff + assert( !forcedReady ); // Don't save MP stuff + assert( !wantSpectate ); // Don't save MP stuff + + assert( !weaponGone ); // Don't save MP stuff + assert( !useInitialSpawns ); // Don't save MP stuff + assert( !isLagged ); // Don't save MP stuff + assert( !isChatting ); // Don't save MP stuff + + assert( !lastSpectateTeleport ); // Don't save MP stuff + assert( latchedTeam == -1 ); // Don't save MP stuff + assert( !spawnedTime ); // Don't save MP stuff + + assert( !teleportEntity ); // Don't save MP stuff + assert( teleportKiller == -1 ); // Don't save MP stuff + + savefile->ReadInt( minRespawnTime ); + savefile->ReadInt( maxRespawnTime ); + + savefile->ReadVec3( firstPersonViewOrigin ); + savefile->ReadMat3( firstPersonViewAxis ); + + // don't bother restoring dragEntity since it's a dev tool + dragEntity.Clear(); + savefile->ReadVec3( intentDir ); + + savefile->ReadFloat ( vehicleCameraDist ); + + savefile->ReadJoint( hipJoint ); + savefile->ReadJoint( chestJoint ); + savefile->ReadJoint( headJoint ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( aasLocation[ i ].areaNum ); + savefile->ReadVec3( aasLocation[ i ].pos ); + } + + savefile->ReadString( modelName ); // cnicholson: Added unrestored var + // TORESTORE: const idDict* modelDict + + savefile->ReadInt( bobFoot ); + savefile->ReadFloat( bobFrac ); + savefile->ReadFloat( bobfracsin ); + savefile->ReadInt( bobCycle ); + savefile->ReadFloat( xyspeed ); + savefile->ReadInt( stepUpTime ); + savefile->ReadFloat( stepUpDelta ); + savefile->ReadFloat( idealLegsYaw ); + savefile->ReadFloat( legsYaw ); + savefile->ReadBool( legsForward ); + savefile->ReadFloat( oldViewYaw ); + savefile->ReadAngles( viewBobAngles ); + savefile->ReadVec3( viewBob ); + savefile->ReadInt( landChange ); + savefile->ReadInt( landTime ); + + savefile->ReadFloat( fatalFallDelta ); + savefile->ReadFloat( hardFallDelta ); + savefile->ReadFloat( softFallDelta ); + savefile->ReadFloat( noFallDelta ); + + savefile->ReadInt( currentWeapon ); + savefile->ReadInt( idealWeapon ); + savefile->ReadInt( previousWeapon ); + savefile->ReadInt( weaponSwitchTime ); + savefile->ReadBool( weaponEnabled ); + + savefile->ReadBool ( flashlightOn ); + savefile->ReadBool ( zoomed ); + + savefile->ReadBool ( reloadModel ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( powerUpSkin ); + savefile->ReadSkin( gibSkin ); + + savefile->ReadInt( numProjectilesFired ); + savefile->ReadInt( numProjectileHits ); + + savefile->ReadBool( airless ); + savefile->ReadInt( airTics ); + savefile->ReadInt( lastAirDamage ); + + savefile->ReadBool( gibDeath ); + savefile->ReadBool( gibsLaunched ); + savefile->ReadVec3( gibDir ); + + savefile->ReadBool( isStrogg ); + + savefile->ReadInterpolate( zoomFov ); + savefile->ReadInterpolate( centerView ); + savefile->ReadBool( fxFov ); + + savefile->ReadFloat( influenceFov ); + savefile->ReadInt( influenceActive ); + savefile->ReadObject( reinterpret_cast( influenceEntity ) ); + savefile->ReadMaterial( influenceMaterial ); + savefile->ReadFloat( influenceRadius ); + savefile->ReadSkin( influenceSkin ); + + savefile->ReadObject( reinterpret_cast( privateCameraView ) ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->ReadAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->ReadInt( loggedAccel[ i ].time ); + savefile->ReadVec3( loggedAccel[ i ].dir ); + } + savefile->ReadInt( currentLoggedAccel ); + + savefile->ReadUserInterface( focusUI, &spawnArgs ), + savefile->ReadInt( focusTime ); + savefile->ReadInt( (int&)focusType ); + focusEnt.Restore ( savefile ); + savefile->ReadUserInterface( focusBrackets, &spawnArgs ); + savefile->ReadInt( focusBracketsTime ); + + talkingNPC.Restore( savefile ); + + extraProjPassEntity.Restore( savefile ); + + savefile->ReadInt( talkCursor ); + savefile->ReadUserInterface( cursor, &spawnArgs ); + + savefile->ReadUserInterface( overlayHud, &spawnArgs ); // cnicholson: Added unrestored var + savefile->ReadInt ( overlayHudTime ); // cnicholson: Added unrestored var + + savefile->ReadBool( targetFriendly ); + + savefile->ReadInt( oldMouseX ); + savefile->ReadInt( oldMouseY ); + + savefile->ReadBool( tipUp ); + savefile->ReadBool( objectiveUp ); + + savefile->ReadFloat( dynamicProtectionScale ); // cnicholson: Added unrestored var + savefile->ReadInt( lastDamageDef ); + savefile->ReadVec3( lastDamageDir ); + savefile->ReadInt( lastDamageLocation ); + + savefile->ReadInt( predictedFrame ); + savefile->ReadVec3( predictedOrigin ); + savefile->ReadAngles( predictedAngles ); + savefile->ReadBool( predictedUpdated ); + savefile->ReadVec3( predictionOriginError ); + savefile->ReadAngles( predictionAnglesError ); + savefile->ReadInt( predictionErrorTime ); + + assert( !ready ); // Don't save MP stuff + assert( !respawning ); // Don't save MP stuff + assert( !leader ); // Don't save MP stuff + assert( !weaponCatchup ); // Don't save MP stuff + assert( !isTelefragged ); // Don't save MP stuff + + assert( !lastSpectateChange ); // Don't save MP stuff + assert( lastTeleFX == -9999 ); // Don't save MP stuff + assert( !lastSnapshotSequence ); // Don't save MP stuff + + assert( aimClientNum == -1 ); // Don't save MP stuff + + assert( !lastImpulsePlayer ); // Don't save MP stuff + + assert( !arena ); // Don't save MP stuff + + assert( !connectTime ); // Don't save MP stuff + assert( !mutedPlayers ); // Don't save MP stuff + assert( !friendPlayers ); // Don't save MP stuff + + assert( rank == -1 ); // Don't save MP stuff + + savefile->ReadInt( lastImpulseTime ); + bossEnemy.Restore( savefile ); // cnicholson: Added unrestored var + + // TORESTORE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TORESTORE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->ReadBool( weaponChangeIconsUp ); // cnicholson: Added unrestored var + +// mekberg: added + savefile->ReadBool( showNewObjectives ); + savefile->ReadBool( objectivesEnabled ); + + savefile->ReadBool( flagCanFire ); + + // set the pm_ cvars + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + + // Loading a game on easy mode ensures you alwasy have 20% health when you load + i = spawnArgs.GetInt ( va("minRestoreHealth%d", g_skill.GetInteger ( ) ) ); + if ( health < i ) { + health = i; + } + + //if there's hearing loss, make sure we post a finishing event + if( pfl.hearingLoss ) { + Event_FinishHearingLoss( 3.0f ); + } else { + Event_FinishHearingLoss( 0.05f ); + } + // create combat collision hull for exact collision detection + SetCombatModel(); + +// RAVEN BEGIN +// mekberg: Grab from user info. + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); +// RAVEN END +} + +/* +=============== +idPlayer::PrepareForRestart +================ +*/ +void idPlayer::PrepareForRestart( void ) { + ClearPowerUps(); + Spectate( true ); + + forceRespawn = true; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// RITUAL END + + // we will be restarting program, clear the client entities from program-related things first + ShutdownThreads(); + + // the sound world is going to be cleared, don't keep references to emitters + FreeSoundEmitter( false ); +} + +/* +=============== +idPlayer::Restart +================ +*/ +void idPlayer::Restart( void ) { + idActor::Restart(); + + // client needs to setup the animation script object again + if ( gameLocal.isClient ) { + // clear the existing model to force a reload + Init(); + if ( !spectating ) { + Show(); + } + } else { + // choose a random spot and prepare the point of view in case player is left spectating + assert( spectating ); + SpawnFromSpawnSpot(); + } + + lastKiller = NULL; + useInitialSpawns = true; +} + +/* +=============== +idPlayer::ServerSpectate +================ +*/ +void idPlayer::ServerSpectate( bool spectate ) { + assert( !gameLocal.isClient ); + + if ( spectating != spectate ) { + // if we select spectating on the client + // mekberg: drop and clear powerups from the player. + DropPowerups(); + ClearPowerUps(); + Spectate( spectate ); + gameLocal.mpGame.GetGameState()->Spectate( this ); + if ( spectate ) { + SetSpectateOrigin( ); + } + } + if ( !spectate ) { + SpawnFromSpawnSpot( ); + } +} + +/* +=========== +idPlayer::SelectSpawnPoint + +Find a spawn point marked, otherwise use normal spawn selection. +============ +*/ +bool idPlayer::SelectSpawnPoint( idVec3 &origin, idAngles &angles ) { + idEntity *spot; + idStr skin; + + spot = gameLocal.SelectSpawnPoint( this ); + + // no spot, try again next frame + if( !spot ) { + forceRespawn = true; + return false; + } + + // set the player skin from the spawn location + if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) { + spawnArgs.Set( "spawn_skin", skin ); + } + + if ( spot->spawnArgs.GetString( "spawn_model", NULL ) ) { + spawnArgs.Set( "model", spot->spawnArgs.GetString( "spawn_model", NULL ) ); + } + + if ( spot->spawnArgs.GetString( "def_head", NULL ) ) { + spawnArgs.Set( "def_head", spot->spawnArgs.GetString( "def_head", NULL ) ); + } + + // activate the spawn locations targets + spot->PostEventMS( &EV_ActivateTargets, 0, this ); + + origin = spot->GetPhysics()->GetOrigin(); + origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor + angles = spot->GetPhysics()->GetAxis().ToAngles(); + + return true; +} + +/* +=========== +idPlayer::SpawnFromSpawnSpot + +Chooses a spawn location and spawns the player +============ +*/ +void idPlayer::SpawnFromSpawnSpot( void ) { + idVec3 spawn_origin; + idAngles spawn_angles; + + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + forceRespawn = true; + return; + } + SpawnToPoint( spawn_origin, spawn_angles ); +} + +/* +=========== +idPlayer::SpawnToPoint + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState + +when called here with spectating set to true, just place yourself and init +============ +*/ +void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) { + idVec3 spec_origin; + + assert( !gameLocal.isClient || IsFakeClient() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // Record previous weapons for later restoration + inventory.carryOverWeapons &= ~CARRYOVER_WEAPONS_MASK; + inventory.carryOverWeapons |= inventory.weapons; + } +// RITUAL END + + respawning = true; + + Init(); + + // Force players to use bounding boxes when in multiplayer + if ( gameLocal.isMultiplayer ) { + use_combat_bbox = true; + + // Make sure the combat model is unlinked + if ( combatModel ) { + combatModel->Unlink( ); + } + } + + // Any health over max health will tick down + if ( health > inventory.maxHealth ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } + + fl.noknockback = false; + // stop any ragdolls being used + StopRagdoll(); + // set back the player physics + SetPhysics( &physicsObj ); + physicsObj.SetClipModelAxis(); + physicsObj.EnableClip(); + if ( !spectating ) { + SetCombatContents( true ); + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + // setup our initial view + if ( !spectating ) { + SetOrigin( spawn_origin ); +// RAVEN BEGIN +// abahr: taking into account gravity + SetAxis( spawn_angles.ToMat3() ); +// RAVEN END + } else { + spec_origin = spawn_origin; + spec_origin[ 2 ] += pm_normalheight.GetFloat(); + spec_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spec_origin ); + } + + // if this is the first spawn of the map, we don't have a usercmd yet, + // so the delta angles won't be correct. This will be fixed on the first think. + viewAngles = ang_zero; + SetDeltaViewAngles( ang_zero ); + SetViewAngles( spawn_angles ); + spawnAngles = spawn_angles; + spawnAnglesSet = false; + + legsForward = true; + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + leanVelocity = vec3_zero; + + if ( spectating ) { + Hide(); + } else { + Show(); + } + + if ( gameLocal.isMultiplayer ) { + if ( !spectating ) { + // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof' + if ( lastTeleFX < gameLocal.time - 1000 ) { + // currentThinkingEntity not set here (called out of Run()) + idEntity* thinker = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + gameLocal.PlayEffect( spawnArgs, "fx_spawn", renderEntity.origin, idVec3(0,0,1).ToMat3(), false, vec3_origin, true ); + lastTeleFX = gameLocal.time; + gameLocal.currentThinkingEntity = thinker; + } + } +//RAVEN BEGIN +//asalmon: Clear the respwan message + if ( mphud ) { + mphud->SetStateInt( "respawnNotice", 0 ); + } +//RAVEN END + pfl.teleport = true; + } else { + pfl.teleport = false; + } + + // kill anything at the new position + if ( !spectating ) { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it +// RAVEN BEGIN +// abahr: this is killing the tram car when spawning in. Ooooops! + if( gameLocal.isMultiplayer ) { + gameLocal.KillBox( this ); + } +// RAVEN END + } + + // don't allow full run speed for a bit + physicsObj.SetKnockBack( 100 ); + + // set our respawn time and buttons so that if we're killed we don't respawn immediately + minRespawnTime = gameLocal.time; + maxRespawnTime = gameLocal.time; + if ( !spectating ) { + forceRespawn = false; + } + + privateCameraView = NULL; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + // restore previous weapons + inventory.weapons |= inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + for( int weaponIndex = 0; weaponIndex < MAX_WEAPONS; weaponIndex++ ) + { + if( inventory.weapons & ( 1 << weaponIndex ) ) + { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( weaponIndex ); + inventory.ammo[ ammoIndex ] = inventory.StartingAmmoForWeaponIndex( weaponIndex ); + } + } + + /// Restore armor purchased while dead + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_LIGHT; + GiveItem( "item_armor_small" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_HEAVY; + GiveItem( "item_armor_large" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_AMMO; + GiveItem( "ammorefill" ); + } + + // Reactivate team powerups + gameLocal.mpGame.SetUpdateForTeamPowerups(team); + UpdateTeamPowerups(); + } +// RITUAL END + + BecomeActive( TH_THINK ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + Think(); + + respawning = false; + isTelefragged = false; + isLagged = false; + isChatting = false; + + lastImpulsePlayer = NULL; + lastImpulseTime = 0; +} + +/* +=============== +idPlayer::SavePersistantInfo + +Saves any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::SavePersistantInfo( void ) { + if ( IsFakeClient() ) { + return; + } + + idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; + + playerInfo.Clear(); + inventory.GetPersistantData( playerInfo ); + playerInfo.SetInt( "health", health ); + playerInfo.SetInt( "current_weapon", currentWeapon ); +} + +/* +=============== +idPlayer::RestorePersistantInfo + +Restores any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::RestorePersistantInfo( void ) { + if ( IsFakeClient() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + gameLocal.persistentPlayerInfo[entityNumber].Clear(); + } + + spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); + + inventory.RestoreInventory( this, spawnArgs ); + health = spawnArgs.GetInt( "health", "100" ); + if ( !gameLocal.isClient ) { + idealWeapon = spawnArgs.GetInt( "current_weapon", "0" ); + } +} + +/* +================ +idPlayer::GetUserInfo +================ +*/ +idDict *idPlayer::GetUserInfo( void ) { + if ( IsFakeClient() ) { + return NULL; + } + return &gameLocal.userInfo[ entityNumber ]; +} + +/* +============== +idPlayer::UpdateModelSetup +Updates the player's model setup. Model setups are read from the player def, and contain +information about the player's model, the player's head model, a skin, and a team for the +composite model. +============== +*/ +void idPlayer::UpdateModelSetup( bool forceReload ) { + const idDict* dict; + const char* uiKeyName = NULL; + const char* defaultModel = NULL; + const char* newModelName = NULL; + + if( !gameLocal.isMultiplayer || spectating ) { + return; + } + + if( gameLocal.IsTeamGame() ) { + defaultModel = spawnArgs.GetString( va( "def_default_model_%s", idMultiplayerGame::teamNames[ team ] ), NULL ); + + if( g_forceMarineModel.GetString()[ 0 ] && team == TEAM_MARINE ) { + newModelName = g_forceMarineModel.GetString(); + } else if( g_forceStroggModel.GetString()[ 0 ] && team == TEAM_STROGG ) { + newModelName = g_forceStroggModel.GetString(); + } else { + uiKeyName = va( "ui_model_%s", idMultiplayerGame::teamNames[ team ] ); + newModelName = GetUserInfo() ? GetUserInfo()->GetString( uiKeyName ) : ""; + } + } else { + defaultModel = spawnArgs.GetString( "def_default_model" ); + + if( g_forceModel.GetString()[ 0 ] ) { + newModelName = g_forceModel.GetString(); + } else { + uiKeyName = "ui_model"; + newModelName = GetUserInfo() ? GetUserInfo()->GetString( uiKeyName ) : ""; + } + } + + if( !idStr::Icmp( newModelName, "" ) ) { + newModelName = defaultModel; + } + + // model hasn't changed + if( !modelName.Icmp( newModelName ) && !forceReload ) { + return; + } + + rvDeclPlayerModel* model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + + // validate that the model they've selected is OK for this team game + if( gameLocal.IsTeamGame() && model ) { + if( idStr::Icmp( model->team, idMultiplayerGame::teamNames[ team ] ) ) { + gameLocal.Warning( "idPlayer::UpdateModelSetup() - Player %d (%s) set to model %s which is restricted to team %s (Player team: %s)\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?", newModelName, model->team.c_str(), idMultiplayerGame::teamNames[ team ] ); + if( uiKeyName ) { + cvarSystem->SetCVarBool( uiKeyName, "" ); + } + + dict = NULL; + } + } + + // check to see if the user-specified ui_model/ui_model_strogg/ui_model_marine is valid + if( !model ) { + newModelName = defaultModel; + + model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + if( !model ) { + gameLocal.Error( "idPlayer::UpdateModelSetup() - Can't find default model (%s)\n", defaultModel ); + } else { + // If it's not valid, set the cvar to the default model + if( uiKeyName && GetUserInfo() ) { + GetUserInfo()->Set( uiKeyName, defaultModel ); + + if( IsLocalClient() ) { + cvarSystem->SetCVarString( uiKeyName, defaultModel ); + } + + if( gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "updateUI %d\n", entityNumber ) ); + return; + } + } + + } + } + + modelName = newModelName; + modelDecl = model; + + reloadModel = true; + + // if we have a strogg model, set the strogg flag here + isStrogg = false; + if( modelDecl && !modelDecl->team.Icmp( "strogg" ) ) { + isStrogg = true; + } + + if (gameLocal.isClient || gameLocal.isListenServer) + UpdateSounds(modelDecl); +} + +void idPlayer::UpdateSounds(const rvDeclPlayerModel *modelDecl) +{ + // reset to the player.def sounds just in case we're switching from a model + // which defines sounds back to one that doesnt + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + const idKeyValue *kv = playerDict->MatchPrefix( "snd_", NULL ); + while (kv) + { + spawnArgs.Set(kv->GetKey().c_str(), kv->GetValue().c_str()); + kv = playerDict->MatchPrefix( "snd_", kv ); + } + + spawnArgs.Copy(modelDecl->sounds); +} + +/* +============== +idPlayer::BalanceTeam +============== +*/ +bool idPlayer::BalanceTeam( void ) { + assert( !IsFakeClient() ); + + int i, balanceTeam, teamCount[2]; + idEntity *ent; + + teamCount[ 0 ] = teamCount[ 1 ] = 0; + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ 0 ] < teamCount[ 1 ] ) { + balanceTeam = 0; + } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) { + balanceTeam = 1; + } + + if ( balanceTeam != -1 && team != balanceTeam && teamCount[ balanceTeam ]+1 != teamCount[ !balanceTeam ] ) { + common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "strogg" : "marine" ); + team = balanceTeam; + GetUserInfo()->Set( "ui_team", team ? "Strogg" : "Marine" ); + return true; + } + return false; +} + +void HSVtoRGB( float &r, float &g, float &b, float h, float s, float v ) { + int i; + float f, p, q, t; + + h = idMath::ClampFloat( 0.0f, 360.0f, h ); + s = idMath::ClampFloat( 0.0f, 1.0f, s ); + v = idMath::ClampFloat( 0.75f, 1.0f, v ); + + if( s == 0 ) { + // achromatic (grey) + r = g = b = v; + return; + } + + h /= 60; // sector 0 to 5 + i = floor( h ); + f = h - i; // factorial part of h + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch( i ) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: // case 5: + r = v; + g = p; + b = q; + break; + } +} + +/* +============== +idPlayer::UserInfoChanged +============== +*/ +bool idPlayer::UserInfoChanged( void ) { + idDict *userInfo; + bool modifiedInfo; + bool spec; + bool newready; + + if ( IsFakeClient() ) { + showWeaponViewModel = cvarSystem->GetCVarBool( "ui_showGun" ); + wantSpectate = true; + spectator = this->entityNumber; + return false; + } + + userInfo = GetUserInfo(); + showWeaponViewModel = userInfo->GetBool( "ui_showGun" ); + + if ( !gameLocal.isMultiplayer ) { + handicap = 1.0f; + return false; + } + + modifiedInfo = false; + + // update/apply handicap + handicap = userInfo->GetInt( "ui_handicap", "100" ) / 100.0f; + if ( handicap <= 0.0f || handicap >= 1.0f ) { + handicap = 1.0f; + } + + if( PowerUpActive( POWERUP_GUARD ) ) { + inventory.maxHealth = 200; + inventory.maxarmor = 200; + } else { + inventory.maxHealth = spawnArgs.GetInt( "maxhealth", "100" ); + inventory.maxarmor = spawnArgs.GetInt( "maxarmor", "100" ); + } + + spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + // never let spectators go back to game while sudden death is on + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == SUDDENDEATH && !spec && wantSpectate == true ) { + userInfo->Set( "ui_spectate", "Spectate" ); + modifiedInfo |= true; + } else { + if ( spec != wantSpectate && !spec ) { + // returning from spectate, set forceRespawn so we don't get stuck in spectate forever + forceRespawn = true; + } + wantSpectate = spec; + } + } else { + if ( spec ) { + userInfo->Set( "ui_spectate", "Play" ); + modifiedInfo |= true; + } else if ( spectating ) { + // allow player to leaving spectator mode if they were in it when it was disallowed + forceRespawn = true; + } + wantSpectate = false; + } + + if ( gameLocal.serverInfo.GetBool( "si_useReady" ) ) { + newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready != newready && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP && !wantSpectate ) { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_107180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLocalizedString( "#str_104300" ) : common->GetLocalizedString( "#str_104301" ) ); + } + ready = newready; + } + + int newTeam = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Strogg" ) == 0 ); + + if( hud && gameLocal.IsTeamGame() ) { + hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + } else if( hud ) { + hud->HandleNamedEvent( "setTeam_marine" ); + } + + if ( gameLocal.IsTeamGame() && newTeam != latchedTeam ) { + team = newTeam; + + if ( gameLocal.isServer ) { + int verifyTeam = gameLocal.mpGame.VerifyTeamSwitch( newTeam, this ); + if( verifyTeam != newTeam ) { + if( verifyTeam == TEAM_MARINE || verifyTeam == TEAM_STROGG ) { + userInfo->Set( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + if( IsLocalClient() ) { + cvarSystem->SetCVarString( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + } + modifiedInfo = true; + team = verifyTeam; + } + } + } + + // if still OK to change + if( team != latchedTeam ) { + if( gameLocal.isServer ) { + gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team ); + } + + SetInitialHud(); + if( mphud ) { + mphud->SetStateInt( "playerteam", team ); + mphud->HandleNamedEvent( "TeamChange" ); + } + + if( IsLocalClient() ) { + alreadyDidTeamAnnouncerSound = true; + // the client might set its team to a value before the server corrects for team balance + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_MARINE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_STROGG ); + + if ( !wantSpectate ) { + if( team == TEAM_STROGG ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( team == TEAM_MARINE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + + // ATVI DevTrack #13224 - update on each team change + iconManager->UpdateTeamIcons(); + + latchedTeam = team; + } + } + + //if ( !gameLocal.isClient && gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // bool teamsBalanced = BalanceTeam(); + // modifiedInfo |= teamsBalanced; + //} + + UpdateModelSetup(); + + if( (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber )) || (gameLocal.isClient && IsLocalClient() ) ) { + isChatting = userInfo->GetBool( "ui_chat", "0" ); + if ( isChatting && pfl.dead ) { + // if dead, always force chat icon off. + isChatting = false; + userInfo->SetBool( "ui_chat", false ); + modifiedInfo |= true; + } + } + + // grab hitscan tint + hitscanTint = userInfo->GetVec4( "ui_hitscanTint", "81 1 1 1" ); + HSVtoRGB( hitscanTint.x, hitscanTint.y, hitscanTint.z, hitscanTint.x, hitscanTint.y, hitscanTint.z ); + // force alpha to 1 + hitscanTint.w = 1.0f; + + return modifiedInfo; +} + +/* +=============== +idPlayer::UpdateHudAmmo +=============== +*/ +void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) { + int inclip; + int ammoamount; + + assert( weapon ); + assert( _hud ); + + inclip = weapon->AmmoInClip(); + ammoamount = weapon->AmmoAvailable(); + + if ( ammoamount < 0 ) { + // show infinite ammo + _hud->SetStateString( "player_ammo", "-1" ); + _hud->SetStateString( "player_totalammo", "-1" ); + _hud->SetStateFloat ( "player_ammopct", 1.0f ); + } else if ( weapon->ClipSize ( ) && !gameLocal.isMultiplayer ) { + _hud->SetStateInt ( "player_clip_size", weapon->ClipSize() ); + _hud->SetStateFloat ( "player_ammopct", (float)inclip / (float)weapon->ClipSize ( ) ); + if ( weapon->ClipSize ( )==1) { + _hud->SetStateInt ( "player_totalammo", ammoamount ); + } + else { + _hud->SetStateInt ( "player_totalammo", ammoamount - inclip ); + } + _hud->SetStateInt ( "player_ammo", inclip ); + } else { + _hud->SetStateFloat ( "player_ammopct", (float)ammoamount / (float)weapon->maxAmmo ); + _hud->SetStateInt ( "player_totalammo", ammoamount ); + _hud->SetStateInt ( "player_ammo", -1 ); + } + + _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); +} + +/* +=============== +idPlayer::UpdateHudStats +=============== +*/ +void idPlayer::UpdateHudStats( idUserInterface *_hud ) { + int temp; + + assert ( _hud ); + + temp = _hud->State().GetInt ( "player_health", "-1" ); + if ( temp != health ) { + _hud->SetStateInt ( "player_healthDelta", temp == -1 ? 0 : (temp - health) ); + _hud->SetStateInt ( "player_health", health < -100 ? -100 : health ); + _hud->SetStateFloat ( "player_healthpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)health / (float)inventory.maxHealth ) ); + _hud->HandleNamedEvent ( "updateHealth" ); + } + + temp = _hud->State().GetInt ( "player_armor", "-1" ); + if ( temp != inventory.armor ) { + _hud->SetStateInt ( "player_armorDelta", temp == -1 ? 0 : (temp - inventory.armor) ); + _hud->SetStateInt ( "player_armor", inventory.armor ); + _hud->SetStateFloat ( "player_armorpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)inventory.armor / (float)inventory.maxarmor ) ); + _hud->HandleNamedEvent ( "updateArmor" ); + } + + // Boss bar + if ( _hud->State().GetInt ( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt ( "boss_health", -1 ); + _hud->HandleNamedEvent ( "hideBossBar" ); + _hud->HandleNamedEvent ( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt ( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent ( "updateBossBar" ); + } + } + + // god mode information + _hud->SetStateString( "player_god", va( "%i", (godmode && g_showGodDamage.GetBool()) ) ); + _hud->SetStateString( "player_god_damage", va( "%i", godmodeDamage ) ); + + // Update the hit direction + idVec3 localDir; + viewAxis.ProjectVector( lastDamageDir, localDir ); + _hud->SetStateFloat( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + + //_hud->HandleNamedEvent( "updateArmorHealthAir" ); + + if ( weapon ) { + UpdateHudAmmo( _hud ); + } + + UpdateHudPowerUps( _hud ); + + if ( hud_showSpeed.GetBool() ) { + idVec3 velocity = physicsObj.GetLinearVelocity(); + velocity[2] = 0; + _hud->SetStateString("player_speed", va("%d ups", (int)velocity.Length())); + } + + if (hud_showInput.GetBool()) + DrawInput(); + + _hud->StateChanged( gameLocal.time ); +} + +void idPlayer::DrawInput() +{ + const idVec4 highlightColor(1.0f, 0.0f, 0.0f, 0.8f); + sscanf( hud_inputColor.GetString(), "%f %f %f", &highlightColor.x, &highlightColor.y, &highlightColor.z ); + + const idVec4 normalColor(1.0f, 1.0f, 1.0f, 0.8f); + + idVec2 inputPos(500,90); + sscanf( hud_inputPosition.GetString(), "%f %f", &inputPos.x, &inputPos.y ); + + const idMaterial *centerMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_center"); + renderSystem->SetColor( normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, centerMaterial ); + + const idMaterial *forwardMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_forward"); + renderSystem->SetColor( (usercmd.forwardmove == 127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, forwardMaterial ); + + const idMaterial *backMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_back"); + renderSystem->SetColor( (usercmd.forwardmove == -127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y + 20, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, backMaterial ); + + const idMaterial *leftMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveleft"); + renderSystem->SetColor( (usercmd.rightmove == -127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, leftMaterial ); + + const idMaterial *rightMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveright"); + renderSystem->SetColor( (usercmd.rightmove == 127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 20, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, rightMaterial ); + + const idMaterial *upMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveup"); + renderSystem->SetColor( physicsObj.IsJumping() ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 36, inputPos.y, 14.0f, 14.0f, 0.0, 0.0, 1.0f, 1.0f, upMaterial ); + + const idMaterial *downMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_movedown"); + renderSystem->SetColor( physicsObj.IsCrouching() ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 36, inputPos.y + 17, 14.0f, 14.0f, 0.0, 0.0, 1.0f, 1.0f, downMaterial ); +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateHudWeapon( int displayWeapon ) { + + if ( ( displayWeapon < -1 ) || ( displayWeapon >= MAX_WEAPONS ) ) { + common->DPrintf( "displayweapon was out of range" ); + return; + } + + int index = 0; + int idealIndex = 0; + idUserInterface * hud = idPlayer::hud; + idUserInterface * mphud = idPlayer::mphud; + idUserInterface * cursor = idPlayer::cursor; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p->spectating && p->spectator == entityNumber ) { + hud = p->hud; + mphud = p->mphud; + cursor = p->cursor; + } + + if ( !hud || !weapon ) { + return; + } + + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + const char *weapnum = va( "weapon%d", i ); + int weapstate = 0; + if ( ( inventory.weapons & ( 1 << i ) ) && spawnArgs.GetBool( va( "weapon%d_cycle", i ) ) ) { + hud->SetStateBool( weapnum, true ); + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + weapstate++; + + if ( idealWeapon == i ) { + idealIndex = index; + } + + const char *weaponIcon = GetWeaponDef ( i )->dict.GetString ( "inv_icon" ); + + hud->SetStateInt ( va( "weapon%d_index", i ), index++ ); + hud->SetStateString ( va( "weapon%d_icon", i ), weaponIcon ); + hud->SetStateInt ( va( "weapon%d_ammo", i ), inventory.ammo[ inventory.AmmoIndexForWeaponClass( weap ) ] ); + } + } else { + hud->SetStateBool( weapnum, false ); + } + } + + hud->SetStateInt( "weaponcount", index ); + + const idMaterial *material = declManager->FindMaterial( weapon->GetIcon() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + material = declManager->FindMaterial( weapon->spawnArgs.GetString( "inv_icon" ) ); + + hud->SetStateString( "weapicon", weapon->GetIcon() ); + hud->SetStateString( "ammoIcon", weapon->spawnArgs.GetString( "inv_icon" ) ); + hud->SetStateInt( "player_weapon", currentWeapon ); + hud->SetStateInt( "player_lastweapon", previousWeapon ); + int hud_idealWeapon = ( displayWeapon != -1 ) ? displayWeapon : idealWeapon; + hud->SetStateInt( "player_idealWeapon", hud_idealWeapon ); + // unused in q4mp hud.gui + //hud->SetStateInt( "player_idealIndex", idealIndex ); + + // Weapon name for weapon selection + const idDeclEntityDef* w = GetWeaponDef( ( displayWeapon != -1 ) ? displayWeapon : idealWeapon ); + if ( w ) { + idStr langToken = w->dict.GetString( "inv_name" ); + hud->SetStateString( "weaponname", common->GetLocalizedString( langToken ) ); + } + + UpdateHudAmmo( hud ); + + if ( cursor ) { + weapon->UpdateCrosshairGUI( cursor ); + + // mekberg: force a redraw so ON_INIT gets called and doesn't stomp all over + // the color values we set in weaponChange. + cursor->Redraw( gameLocal.time ); + cursor->HandleNamedEvent( "weaponChange" ); + } + + hud->HandleNamedEvent( "weaponChange" ); + hud->StateChanged( gameLocal.time ); + weaponChangeIconsUp = true; +} + +void idPlayer::UpdateHudPowerUps( idUserInterface *_hud ) { + assert( _hud ); + + int i, index; + + _hud->HandleNamedEvent( "clearPowerups" ); + + for ( i = 0, index = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + // for flags, set the powerup_flag_* variables, which give us a special pulsing flag display + if( i == POWERUP_CTF_MARINEFLAG || i == POWERUP_CTF_STROGGFLAG || i == POWERUP_CTF_ONEFLAG ) { + _hud->SetStateInt( "powerup_flag_visible", 1 ); + } else { + _hud->SetStateString ( va("powerup%d_icon", index ), GetPowerupDef(i)->dict.GetString ( "inv_icon" ) ); + _hud->SetStateString ( va("powerup%d_time", index ), inventory.powerupEndTime[i] == -1 ? "" : va( "%d" , (int)MS2SEC(inventory.powerupEndTime[i] - gameLocal.time) + 1 ) ); + _hud->SetStateInt ( va( "powerup%d_visible", index ), 1 ); + index++; + } + + continue; + } + } + +} + +/* +=============== +idPlayer::StartRadioChatter +=============== +*/ +void idPlayer::StartRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterUp" ); + } + if ( vehicleController.IsDriving ( ) ) { + vehicleController.StartRadioChatter ( ); + } +} + +/* +=============== +idPlayer::StopRadioChatter +=============== +*/ +void idPlayer::StopRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + if ( vehicleController.IsDriving( ) ) { + vehicleController.StopRadioChatter( ); + } +} + +/* +=============== +idPlayer::DrawShadow +=============== +*/ +void idPlayer::DrawShadow( renderEntity_t *headRenderEnt ) { + if ( gameLocal.isMultiplayer && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all player shadows for the local client + renderEntity.suppressShadowInViewID = gameLocal.localClientNum+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = gameLocal.localClientNum+1; + } + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all player shadows + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + // Only show player shadows for other clients + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } +} + +/* +=============== +idPlayer::DrawHUD +=============== +*/ +void idPlayer::DrawHUD( idUserInterface *_hud ) { + idUserInterface * cursor = idPlayer::cursor; + + if ( gameLocal.GetLocalPlayer() ) { + if ( team != gameLocal.GetLocalPlayer()->hudTeam && _hud ) { + _hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + gameLocal.GetLocalPlayer()->hudTeam = team; + } + } + + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p != this && p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + + if ( disableHud || influenceActive != INFLUENCE_NONE || privateCameraView || !_hud || !g_showHud.GetBool() ) { + return; + } + + if ( objectiveSystemOpen ) { + if ( !GuiActive() ) { + // showing weapon zoom gui when objectives are open because that's the way I'z told to make it werkz + if ( weapon && weapon->GetZoomGui() && zoomed ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } + } + return; + } + + _hud->SetStateBool( "mp", true ); + + // Draw the cinematic hud when in a cinematic + if ( gameLocal.GetCamera() ) { + if ( cinematicHud && !(gameLocal.editors & EDITOR_MODVIEW) ) { + cinematicHud->Redraw( gameLocal.time ); + } + return; + } + + // Let the vehicle draw the hud instead + if ( vehicleController.IsDriving( ) ) { + if ( !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + vehicleController.DrawHUD( ); + if ( cursor && health > 0 ) { + // mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->SetStateBool( "vehiclecursor", true ); + + vehicleController.UpdateCursorGUI( cursor ); + cursor->Redraw( gameLocal.time ); + } + } + _hud = GetHud(); + // Boss bar + if ( _hud && _hud->State().GetInt( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt( "boss_health", -1 ); + _hud->HandleNamedEvent( "hideBossBar" ); + _hud->HandleNamedEvent( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent( "updateBossBar" ); + } + } + return; + } + + if ( cursor ) { + cursor->SetStateBool( "vehiclecursor", false ); + } + + // FIXME: this is temp to allow the sound meter to show up in the hud + // it should be commented out before shipping but the code can remain + // for mod developers to enable for the same functionality + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + // don't draw main hud in spectator (only mphud) + if ( !spectating && !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( weapon && weapon->GetZoomGui() && zoomed ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } + if ( cursor && health > 0 ) { + // Pass the current weapon to the cursor gui for custom crosshairs + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->Redraw( gameLocal.time ); + } + } + + UpdateHudStats( _hud ); + + if ( focusBrackets ) { + // If 2d_calc is still true then the gui didnt render so we can abandon it + if ( focusBrackets->State().GetBool( "2d_calc" ) ) { + focusBrackets->SetStateBool( "2d_calc", false ); + focusBrackets = NULL; + focusBracketsTime = 0; + _hud->HandleNamedEvent( "hideBrackets" ); + } else { + _hud->SetStateString( "bracket_left", focusBrackets->State().GetString( "2d_min_x" ) ); + _hud->SetStateString( "bracket_top", focusBrackets->State().GetString( "2d_min_y" ) ); + _hud->SetStateFloat( "bracket_width", focusBrackets->State().GetFloat( "2d_max_x" ) - focusBrackets->State().GetFloat( "2d_min_x" ) ); + _hud->SetStateFloat( "bracket_height", focusBrackets->State().GetFloat( "2d_max_y" ) - focusBrackets->State().GetFloat( "2d_min_y" ) ); + // TODO: Find a way to get bracket text from gui to hud + } + } + _hud->Redraw( gameLocal.realClientTime ); + } + + if ( gameLocal.isMultiplayer ) { + idUserInterface* _mphud = mphud; + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer() != this ) { + // if we're spectating someone else, use our local hud + _mphud = gameLocal.GetLocalPlayer()->mphud; + } + if ( _mphud ) { + gameLocal.mpGame.UpdateHud( _mphud ); + _mphud->Redraw( gameLocal.time ); + } + + if ( overlayHud && overlayHudTime > gameLocal.time && overlayHudTime != 0 ) { + overlayHud->Redraw( gameLocal.time ); + } else { + overlayHud = NULL; + overlayHudTime = 0; + } + } +} + +/* +=============== +idPlayer::EnterCinematic +=============== +*/ +void idPlayer::EnterCinematic( void ) { + Hide(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(true); +// RAVEN END + + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + + cinematicHud = NULL; + + // See if camera has custom cinematic gui + if ( gameLocal.GetCamera ( ) ) { + const char* guiCinematic; + guiCinematic = gameLocal.GetCamera()->spawnArgs.GetString ( "guiCinematic", "" ); + if ( *guiCinematic ) { + cinematicHud = uiManager->FindGui( guiCinematic, true, false, true ); + } + } + + // Load default cinematic gui? + if ( !cinematicHud ) { + const char* temp; + if ( spawnArgs.GetString ( "cinematicHud", "", &temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + } + + // Have the cinematic hud start + if ( cinematicHud ) { + cinematicHud->Activate ( true, gameLocal.time ); + cinematicHud->HandleNamedEvent ( "cinematicStart" ); +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( cvarSystem->GetCVarInteger( "r_aspectRatio" ) != 0 ) { + cinematicHud->HandleNamedEvent ( "hideLetterbox" ); + } +// RAVEN END + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + if ( weaponEnabled && weapon ) { + //this preSave kills all effects and sounds that we don't need lingering around. + weapon->PreSave(); + weapon->EnterCinematic(); + } + + // Reset state flags + memset( &pfl, 0, sizeof(pfl) ); + pfl.onGround = true; + pfl.dead = (health <= 0); +} + +/* +=============== +idPlayer::ExitCinematic +=============== +*/ +void idPlayer::ExitCinematic( void ) { + Show(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(false); +// RAVEN END + + if ( weaponEnabled && weapon ) { + //and this will restore them! + weapon->PostSave(); + weapon->ExitCinematic(); + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + UpdateState(); +} + +/* +=============== +idPlayer::SkipCinematic +=============== +*/ +bool idPlayer::SkipCinematic( void ) { + StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL ); + return gameLocal.SkipCinematic(); +} + +/* +===================== +idPlayer::UpdateConditions +===================== +*/ +void idPlayer::UpdateConditions( void ) { + idVec3 velocity; + float fallspeed; + float forwardspeed; + float sidespeed; + + // minus the push velocity to avoid playing the walking animation and sounds when riding a mover + velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity(); + fallspeed = velocity * physicsObj.GetGravityNormal(); + + if ( influenceActive ) { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } else if ( gameLocal.time - lastDmgTime < 500 ) { + forwardspeed = velocity * viewAxis[ 0 ]; + sidespeed = velocity * viewAxis[ 1 ]; + pfl.forward = pfl.onGround && ( forwardspeed > 20.01f ); + pfl.backward = pfl.onGround && ( forwardspeed < -20.01f ); + pfl.strafeLeft = pfl.onGround && ( sidespeed > 20.01f ); + pfl.strafeRight = pfl.onGround && ( sidespeed < -20.01f ); + } else if ( xyspeed > MIN_BOB_SPEED ) { + pfl.forward = pfl.onGround && ( usercmd.forwardmove > 0 ); + pfl.backward = pfl.onGround && ( usercmd.forwardmove < 0 ); + pfl.strafeLeft = pfl.onGround && ( usercmd.rightmove < 0 ); + pfl.strafeRight = pfl.onGround && ( usercmd.rightmove > 0 ); + } else { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } + + pfl.run = 1; + pfl.dead = ( health <= 0 ); +} + +/* +================== +idPlayer::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) { + // force a blink + blink_time = 0; + + // play the fire animation + pfl.weaponFired = true; + + // Bias the intent direction more heavily due to firing + BiasIntentDir( viewAxis[0]*100.0f, 1.0f ); + + // update view feedback + playerView.WeaponFireFeedback( weaponDef ); +} + +/* +=============== +idPlayer::StopFiring +=============== +*/ +void idPlayer::StopFiring( void ) { + pfl.attackHeld = false; + pfl.weaponFired = false; + pfl.reload = false; + if ( weapon ) { + weapon->EndAttack(); + } +} + +/* +=============== +idPlayer::FireWeapon +=============== +*/ +void idPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) { + return; + } + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ); + return; + } + + if ( !hiddenWeapon && weapon->IsReady() ) { + // cheap hack so in MP the LG isn't allowed to fire in the short lapse while it goes from Fire -> Idle before changing to another weapon + // this gimps the weapon a lil bit but is consistent with the visual feedback clients are getting since 1.0 + bool noFireWhileSwitching = false; + noFireWhileSwitching = ( gameLocal.isMultiplayer && idealWeapon != currentWeapon && weapon->NoFireWhileSwitching() ); + if ( !noFireWhileSwitching ) { + if ( weapon->AmmoInClip() || weapon->AmmoAvailable() ) { + pfl.attackHeld = true; + weapon->BeginAttack(); + } else { + pfl.attackHeld = false; + pfl.weaponFired = false; + StopFiring(); + NextBestWeapon(); + } + } else { + StopFiring(); + } + } + // If reloading when fire is hit cancel the reload + else if ( weapon->IsReloading() ) { + weapon->CancelReload(); + } + + if ( hud && weaponChangeIconsUp ) { + hud->HandleNamedEvent( "weaponFire" ); + // nrausch: objectiveSystem does not necessarily exist (in mp it doesn't) + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "weaponFire" ); + } + weaponChangeIconsUp = false; + } +} + +/* +=============== +idPlayer::CacheWeapons +=============== +*/ +void idPlayer::CacheWeapons( void ) { + idStr weap; + int w; + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + if ( inventory.weapons & ( 1 << w ) ) { + if ( !GetWeaponDef ( w ) ) { + inventory.weapons &= ~( 1 << w ); + } else { + rvWeapon::CacheWeapon( spawnArgs.GetString( va( "def_weapon%d", w ) ) ); + } + } + } +} + +/* +=============== +idPlayer::Give +=============== +*/ +bool idPlayer::Give( const char *statname, const char *value, bool dropped ) { + int amount; + + if ( pfl.dead ) { + return false; + } + + if ( IsInVehicle ( ) ) { + vehicleController.Give ( statname, value ); + } + + int boundaryHealth = inventory.maxHealth; + int boundaryArmor = inventory.maxarmor; + if( PowerUpActive( POWERUP_GUARD ) ) { + boundaryHealth = inventory.maxHealth / 2; + boundaryArmor = inventory.maxarmor / 2; + } + if( PowerUpActive( POWERUP_SCOUT ) ) { + boundaryArmor = 0; + } + if ( gameLocal.isMultiplayer ) { + //In MP, you can get twice your max from pickups + boundaryArmor *= 2; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= boundaryHealth ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth ) { + health = boundaryHealth; + } + } + } else if ( !idStr::Icmp( statname, "bonushealth" ) ) { + // allow health over max health + if ( health >= boundaryHealth * 2 ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth * 2 ) { + health = boundaryHealth * 2; + } + } + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( inventory.armor >= boundaryArmor ) { + return false; + } + amount = atoi( value ); + + inventory.armor += amount; + if ( inventory.armor > boundaryArmor ) { + inventory.armor = boundaryArmor; + } + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airTics >= pm_airTics.GetInteger() ) { + return false; + } + airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger(); + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } else if ( !idStr::Icmp ( statname, "weaponmod" ) ) { + if( !idStr::Icmp( value, "all" ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + GiveWeaponMods( i, 0xFFFFFFFF ); + } + } + } else { + const char* pos = value; + + while( pos != NULL ) { + const char* end = strchr( pos, ',' ); + int len; + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponMod ( pos, 0, len ); + GiveWeaponMod ( weaponMod ); + + pos = end; + } + } + } else { + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true, dropped ); + } + return true; +} + +/* +=============== +idPlayer::GiveItem + +Returns false if the item shouldn't be picked up +=============== +*/ +bool idPlayer::GiveItem( idItem *item ) { + int i; + const idKeyValue *arg; + idDict attr; + bool gave; + + bool dropped = item->spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && spectating ) { + return false; + } + + item->GetAttributes( attr ); + + if( gameLocal.isServer || !gameLocal.isMultiplayer ) { + gave = false; + bool skipWeaponKey = false; + bool skipRestOfKeys = false; + if ( gameLocal.IsMultiplayer() ) { + dropped = item->spawnArgs.GetBool( "dropped" ); + if ( item->spawnArgs.FindKey( "weaponclass" ) ) { + //this is really fucking lame, but + //this is the only way we know we're trying + //to pick up a weapon before we blindly start + //processesing the attribute arguments in + //whatever order they're in below. We need + //to not process any at all if we're not allowed + //to pick up the weapon in the first place! + arg = attr.FindKey( "weapon" ); + if ( arg ) { + skipWeaponKey = true; + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } else if ( !dropped//not a dropped weapon + && gameLocal.IsWeaponsStayOn() ) { + //if failed to give weapon, don't give anything else with the weapon + skipRestOfKeys = true; + } + } + } + } + if ( !skipRestOfKeys ) { + for( i = 0; i < attr.GetNumKeyVals(); i++ ) { + arg = attr.GetKeyVal( i ); + if ( skipWeaponKey && arg->GetKey() == "weapon" ) { + //already processed this above + continue; + } + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } + } + } + + // hack - powerups call into this code to let them give stuff based on inv_ keywords + // for powerups that don't have any ammo/etc to give to the player, we still want to + // display the inv_name on the hud + // since idItemPowerup::GiveToPlayer() handles whether or not a player gets a powerup, + // we can override gave here for powerups + if ( !gave && !item->IsType( idItemPowerup::GetClassType() ) ) { + return false; + } + } else { + gave = true; + } + + arg = item->spawnArgs.MatchPrefix( "inv_ammo_", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "ammoPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_health", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL ); + if ( arg && hud ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + if ( gameLocal.isMultiplayer ) { + UpdateHudWeapon(); + } else { + //so weapon mods highlight the correct weapon when received + int weapon = SlotForWeapon( arg->GetValue() ); + UpdateHudWeapon( weapon ); + } + hud->HandleNamedEvent( "weaponPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_armor", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "armorPulse" ); + } + +// GiveDatabaseEntry ( &item->spawnArgs ); + + // Show the item pickup on the hud + if ( hud ) { + idStr langToken = item->spawnArgs.GetString( "inv_name" ); + hud->SetStateString ( "itemtext", common->GetLocalizedString( langToken ) ); + hud->SetStateString ( "itemicon", item->spawnArgs.GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } +//RITUAL BEGIN + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + gameLocal.mpGame.RedrawLocalBuyMenu(); +//RITUAL END + + return gave; +} + +/* +=============== +idPlayer::PowerUpModifier +=============== +*/ +float idPlayer::PowerUpModifier( int type ) { + float mod = 1.0f; + + if ( PowerUpActive( POWERUP_QUADDAMAGE ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_PROJECTILE_DEATHPUSH: { + mod *= 2.0f; + break; + } + } + } + + if ( PowerUpActive( POWERUP_HASTE ) ) { + switch ( type ) { + case PMOD_SPEED: + mod *= 1.3f; + break; + + case PMOD_FIRERATE: + mod *= 0.7f; + break; + } + } + + // Arena CTF powerups + if( PowerUpActive( POWERUP_AMMOREGEN ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= 0.7f; + break; + } + } + } + + if( PowerUpActive( POWERUP_DOUBLER ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 2.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 2.0f; + break; + } + } + } + +//RITUAL BEGIN + if( PowerUpActive( POWERUP_TEAM_DAMAGE_MOD ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_FIRERATE: { + mod *= 0.80f; + break; + } + } + } +//RITUAL END + if( PowerUpActive( POWERUP_SCOUT ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= (2.0f / 3.0f); + break; + } + case PMOD_SPEED: { + mod *= 1.5f; + break; + } + } + } + + return mod; +} + +/* +=============== +idPlayer::PowerUpActive +=============== +*/ +bool idPlayer::PowerUpActive( int powerup ) const { + return ( inventory.powerups & ( 1 << powerup ) ) != 0; +} + +/* +=============== +idPlayer::StartPowerUpEffect +=============== +*/ +void idPlayer::StartPowerUpEffect( int powerup ) { + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_marine_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_marine_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_strogg_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_strogg_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_ONEFLAG: { + AddClientModel( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + PlayEffect( "fx_deadzone", animator.GetJointHandle( "origin" ), true ); + break; + } + case POWERUP_QUADDAMAGE: { + powerUpOverlay = quadOverlay; + + StopEffect( "fx_regeneration" ); + PlayEffect( "fx_quaddamage", animator.GetJointHandle( "chest" ), true ); + StartSound( "snd_quaddamage_idle", SND_CHANNEL_POWERUP_IDLE, 0, false, NULL ); + + // Spawn quad effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_quaddamage_crawl" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_QUADDAMAGE; + + break; + } + + case POWERUP_REGENERATION: { + + // when buy mode is enabled, we use the guard effect for team powerup regen ( more readable than everyone going red ) + if ( gameLocal.IsTeamPowerups() ) { + // don't setup the powerup on dead bodies, it will float up where the body is invisible and the orientation will be messed up + if ( teamHealthRegen == NULL ) { + if ( health <= 0 ) { + // we can't start it now, it will be floating where the hidden dead body is + teamHealthRegenPending = true; + } else { + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + powerUpOverlay = regenerationOverlay; + + StopEffect( "fx_quaddamage" ); + PlayEffect( "fx_regeneration", animator.GetJointHandle( "chest" ), true ); + + // Spawn regen effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_regeneration" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_REGENERATION; + } + + break; + } + + case POWERUP_HASTE: { + powerUpOverlay = hasteOverlay; + + hasteEffect = PlayEffect( "fx_haste", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), true ); + break; + } + + case POWERUP_INVISIBILITY: { + powerUpOverlay = invisibilityOverlay; + + powerUpSkin = declManager->FindSkin( spawnArgs.GetString( "skin_invisibility" ), false ); + break; + } + + case POWERUP_GUARD: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_guard", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_SCOUT: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_scout", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_AMMOREGEN: { + if ( gameLocal.IsTeamPowerups() ) { + if ( teamAmmoRegen == NULL ) { + if ( health <= 0 ) { + teamAmmoRegenPending = true; + } else { + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + break; + } + + case POWERUP_TEAM_DAMAGE_MOD: { + assert( gameLocal.IsTeamPowerups() ); + if ( teamDoubler == NULL ) { + if ( health <= 0 ) { + teamDoublerPending = true; + } else { + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + } + break; + } + + case POWERUP_DOUBLER: { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + break; + } + } +} + +/* +=============== +idPlayer::StopPowerUpEffect +=============== +*/ +void idPlayer::StopPowerUpEffect( int powerup ) { + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if ( !( + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) + ) ) { + powerUpOverlay = NULL; + } + + // only quad has a hum, clear it even when other powerups might mean leaving the overlay on + if ( ( inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) == 0 ) { + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + switch( powerup ) { + case POWERUP_QUADDAMAGE: { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_quaddamage" ); + break; + } + case POWERUP_REGENERATION: { + if ( gameLocal.IsTeamPowerups() ) { + teamHealthRegenPending = false; + StopEffect( "fx_guard" ); + } else { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_regeneration" ); + } + break; + } + case POWERUP_HASTE: { + StopEffect( "fx_haste" ); + break; + } + case POWERUP_INVISIBILITY: { + powerUpSkin = NULL; + break; + } + case POWERUP_CTF_STROGGFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + break; + } + case POWERUP_CTF_MARINEFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + break; + } + case POWERUP_CTF_ONEFLAG: { + RemoveClientModel ( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + StopEffect( "fx_deadzone" ); + break; + } + case POWERUP_SCOUT: { + StopEffect( "fx_scout" ); + break; + } + case POWERUP_GUARD: { + StopEffect( "fx_guard" ); + break; + } + case POWERUP_TEAM_DAMAGE_MOD: + teamDoublerPending = false; + // fallthrough + case POWERUP_DOUBLER: { + StopEffect( "fx_doubler" ); + break; + } + case POWERUP_AMMOREGEN: { + teamAmmoRegenPending = false; + StopEffect( "fx_ammoregen" ); + break; + } + } +} + +/* +=============== +idPlayer::GivePowerUp +passiveEffectsOnly - GivePowerup() is used to restore effects on stale players coming +back into snapshot. We don't want to announce powerups in this case +(just re-start effects) +=============== +*/ +bool idPlayer::GivePowerUp( int powerup, int time, bool team ) { + if ( powerup < 0 || powerup >= POWERUP_MAX ) { + gameLocal.Warning( "Player given power up %i\n which is out of range", powerup ); + return false; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( powerup ); + msg.WriteBits( 1, 1 ); + // team flag only needed for POWERUP_AMMOREGEN + msg.WriteBits( team, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.GivePowerUp( this, powerup, time ); + + // only start client effects in the same instance + // play all stuff in instance 0 for server netdemo - atm other instances are not recorded + bool playClientEffects = ( ( gameLocal.IsServerDemoPlaying() && instance == 0 ) || + ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) ); + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_ONEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_QUADDAMAGE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_QUAD_DAMAGE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + + case POWERUP_REGENERATION: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + + // Have to test for this because buying the team regeneration powerup will cause + // this to get hit multiple times as the server distributes the powerups to the clients. + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_REGENERATION, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_HASTE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_HASTE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_INVISIBILITY: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_INVISIBILITY, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_GUARD: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + inventory.maxHealth = 200; + inventory.maxarmor = 200; + + break; + } + case POWERUP_SCOUT: { + inventory.armor = 0; + + break; + } + case POWERUP_AMMOREGEN: { + if ( team && gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_AMMOREGEN, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_TEAM_DAMAGE_MOD: { + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_DOUBLER, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } +//RITUAL BEGIN + case POWERUP_DEADZONE: { + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_122000" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + mphud->HandleNamedEvent( "main_notice" ); + } + } + break; + } +//RITUAL END + } + + // only start effects if in our instances and snapshot + if ( playClientEffects && !fl.networkStale ) { + StartPowerUpEffect( powerup ); + } + + return true; +} + +/* +============== +idPlayer::ClearPowerup +============== +*/ +void idPlayer::ClearPowerup( int i ) { + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( i ); + msg.WriteBits( 0, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.powerups &= ~( 1 << i ); + inventory.powerupEndTime[ i ] = 0; + + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if ( !( + (inventory.powerups & ( 1 << POWERUP_TEAM_DAMAGE_MOD ) ) || + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) || + (inventory.powerups & ( 1 << POWERUP_DEADZONE ) ) + ) ) { + powerUpOverlay = NULL; + } + + // only quad has a hum, clear it even when other powerups might mean leaving the overlay on + if ( ( inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) == 0 ) { + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + StopPowerUpEffect( i ); +} + +/* +============== +idPlayer::GetArenaPowerupString +============== +*/ +const char* idPlayer::GetArenaPowerupString ( void ) { + if ( PowerUpActive( POWERUP_SCOUT ) ) { + return "^isct"; + } else if ( PowerUpActive( POWERUP_GUARD ) ) { + return "^igrd"; + } else if ( PowerUpActive( POWERUP_DOUBLER ) ) { + return "^idbl"; + } else if ( PowerUpActive( POWERUP_AMMOREGEN ) ) { + return "^irgn"; + } else { + return "^ixxx"; + } +} + +/* +============== +idPlayer::UpdatePowerUps +============== +*/ +void idPlayer::UpdatePowerUps( void ) { + int i; + int wearoff = -1; + bool playWearoffSound = false; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && ( p->spectating && p->spectator == entityNumber || !p->spectating && p->entityNumber == entityNumber ) ) { + playWearoffSound = true; + } + + for ( i = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + if ( playWearoffSound ) { + // Play the wearoff sound for the powerup that is closest to wearing off + if ( ( wearoff == -1 || inventory.powerupEndTime[i] < inventory.powerupEndTime[wearoff] ) && inventory.powerupEndTime[i] != -1 ) { + wearoff = i; + } + } + continue; + } else if ( inventory.powerupEndTime[ i ] != -1 && gameLocal.isServer ) { + // This particular powerup needs to respawn in a special way. + if ( i == POWERUP_DEADZONE ) { + gameLocal.mpGame.GetGameState()->SpawnDeadZonePowerup(); + } + // Powerup time has run out so take it away from the player + ClearPowerup( i ); + } + } + + // PLay wear off sound? + if ( gameLocal.isNewFrame && wearoff != -1 ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) < POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) / POWERUP_BLINK_TIME != ( inventory.powerupEndTime[wearoff] - gameLocal.previousTime ) / POWERUP_BLINK_TIME ) { + StartSound ( "snd_powerup_wearoff", SND_CHANNEL_POWERUP, 0, false, NULL ); + } + } + } + + // Reneration regnerates faster when less than maxHealth and can regenerate up to maxHealth * 2 + if ( gameLocal.time > nextHealthPulse ) { +// RITUAL BEGIN +// squirrel: health regen only applies if you have positive health + if( health > 0 ) { + if ( PowerUpActive ( POWERUP_REGENERATION ) || PowerUpActive ( POWERUP_GUARD ) ) { + int healthBoundary = inventory.maxHealth; // health will regen faster under this value, slower above + int healthTic = 15; + + if( PowerUpActive ( POWERUP_GUARD ) ) { + // guard max health == 200, so set the boundary back to 100 + healthBoundary = inventory.maxHealth / 2; + if( PowerUpActive (POWERUP_REGENERATION) ) { + healthTic = 30; + } + } + + if ( health < healthBoundary ) { + // only actually give health on the server + if( gameLocal.isServer ) { + health += healthTic; + if ( health > (healthBoundary * 1.1f) ) { + health = healthBoundary * 1.1f; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( health < (healthBoundary * 2) ) { + if( gameLocal.isServer ) { + health += healthTic / 3; + if ( health > (healthBoundary * 2) ) { + health = healthBoundary * 2; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + // Health above max technically isnt a powerup but functions as one so handle it here + } else if ( health > inventory.maxHealth && gameLocal.isServer ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + health--; + } + } +// RITUAL END + } + + // Regenerate ammo + if( gameLocal.isServer && PowerUpActive( POWERUP_AMMOREGEN ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( inventory.weapons & ( 1 << i ) ) { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( i ); + int max = inventory.StartingAmmoForWeaponIndex( i ); + + // only regen ammo if lower than starting + if( gameLocal.time > nextAmmoRegenPulse[ ammoIndex ] && inventory.ammo[ ammoIndex ] < max ) { + int step = inventory.AmmoRegenStepForWeaponIndex( i ); + int time = inventory.AmmoRegenTimeForWeaponIndex( i ); + + if( inventory.ammo[ ammoIndex ] < max ) { + inventory.ammo[ ammoIndex ] += step; + } + if( inventory.ammo[ ammoIndex ] >= max ) { + inventory.ammo[ ammoIndex ] = max; + } + + nextAmmoRegenPulse[ ammoIndex ] = gameLocal.time + time; + } + } + } + } + + // Tick armor down if greater than max armor + if ( !gameLocal.isClient && gameLocal.time > nextArmorPulse ) { + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse += ARMOR_PULSE; + inventory.armor--; + } + } + + // Assign the powerup skin as long as we are alive + if ( health > 0 ) { + if ( powerUpSkin ) { + renderEntity.customSkin = powerUpSkin; + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + + if( weaponWorldModel ) { + weaponWorldModel->SetSkin( powerUpSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( powerUpSkin ); + } + } else { + renderEntity.customSkin = skin; + + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetOverlayShader( powerUpOverlay ); + } + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + if( weaponWorldModel ) { + weaponWorldModel->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + renderEntity.overlayShader = powerUpOverlay; + } else { + renderEntity.overlayShader = NULL; + powerUpOverlay = NULL; + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = NULL; + } + + if ( renderEntity.customSkin != gibSkin ) { + if ( influenceSkin ) { + renderEntity.customSkin = influenceSkin; + } else { + renderEntity.customSkin = skin; + } + } + } + + // Spawn quad effect + if( PowerUpActive( powerupEffectType ) && powerupEffect && gameLocal.time >= powerupEffectTime ) { + rvClientCrawlEffect* effect = new rvClientCrawlEffect( powerupEffect, this, 100, &powerupEffectJoints ); + effect->Play ( gameLocal.time, false ); + effect->GetRenderEffect()->suppressSurfaceInViewID = entityNumber+1; + powerupEffectTime = gameLocal.time + 400; + } + + // Attenuate haste effect + if ( hasteEffect ) { + hasteEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if ( flagEffect ) { + flagEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if( arenaEffect ) { + arenaEffect->SetOrigin( vec3_zero ); + } +} + +/* +=============== +idPlayer::ClearPowerUps +=============== +*/ +void idPlayer::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( PowerUpActive( i ) ) { + ClearPowerup( i ); + } + } + + inventory.ClearPowerUps(); +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int mods ) { + inventory.weaponMods[currentWeapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int weapon, int mods ) { + inventory.weaponMods[weapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +============== +idPlayer::GiveWeaponMod +============== +*/ +void idPlayer::GiveWeaponMod ( const char* weaponmod ) { + const idDict* modDict; + const idDict* weaponDict; + const char* weaponClass; + int m; + int weaponIndex; + + // Grab the weapon mod dictionary + modDict = gameLocal.FindEntityDefDict ( weaponmod, false ); + if ( !modDict ) { + gameLocal.Warning ( "Invalid weapon modification def specified '%s'", weaponmod ); + return; + } + + // Get the weapon it modifies + weaponClass = modDict->GetString ( "weapon" ); + weaponDict = gameLocal.FindEntityDefDict ( weaponClass, false ); + if ( !weaponDict ) { + gameLocal.Warning ( "Invalid weapon classname '%s' specified on weapon modification '%s'", weaponClass, weaponmod ); + return; + } + + weaponIndex = SlotForWeapon ( weaponClass ); + + // Find the index of the weapon mod + for ( m = 0; m < MAX_WEAPONMODS; m ++ ) { + const char* mod; + mod = weaponDict->GetString ( va("def_mod%d",m+1) ); + if ( !mod || !*mod ) { + break; + } + + if ( !idStr::Icmp ( weaponmod, mod ) ) { + if ( !(inventory.weaponMods[weaponIndex] & (1<GetLocalizedString( item->GetString( "inv_name" ) ); + hud->SetStateString ( "itemtext", itemName ); + hud->SetStateString ( "itemicon", item->GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } + + return true; +} + +/* +============== +idPlayer::UpdateObjectiveInfo +============== + */ +void idPlayer::UpdateObjectiveInfo( void ) { + if ( objectiveSystem == NULL ) { + return; + } + objectiveSystem->SetStateString( "objective1", "" ); + objectiveSystem->SetStateString( "objective2", "" ); + objectiveSystem->SetStateString( "objective3", "" ); + +// RAVEN BEGIN +// mekberg: swap objective positions to allow for stack-like appearance. + int objectiveCount = inventory.objectiveNames.Num(); + for ( int i = 0; i < inventory.objectiveNames.Num(); i++, objectiveCount-- ) { + objectiveSystem->SetStateString( va( "objective%i", objectiveCount ), "1" ); + objectiveSystem->SetStateString( va( "objectivetitle%i", objectiveCount ), inventory.objectiveNames[i].title.c_str() ); + objectiveSystem->SetStateString( va( "objectivetext%i", objectiveCount), inventory.objectiveNames[i].text.c_str() ); + objectiveSystem->SetStateInt( va( "objectiveLength%i", objectiveCount), inventory.objectiveNames[i].text.Length() ); + objectiveSystem->SetStateString( va( "objectiveshot%i", objectiveCount), inventory.objectiveNames[i].screenshot.c_str() ); + } + objectiveSystem->SetStateBool( "noObjective", !objectiveCount ); +// RAVEN END + + objectiveSystem->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::GiveObjective +=============== +*/ +void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) { + idObjectiveInfo info; +// RAVEN BEGIN + info.title = common->GetLocalizedString( title ); + info.text = common->GetLocalizedString( text ); +// RAVEN END + info.screenshot = screenshot; + inventory.objectiveNames.Append( info ); + if ( showNewObjectives ) { + ShowObjective( "newObjective" ); + } + if ( objectiveSystem ) { + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + } +} + +/* +=============== +idPlayer::CompleteObjective +=============== +*/ +void idPlayer::CompleteObjective( const char *title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); +// RAVEN END + int c = inventory.objectiveNames.Num(); + for ( int i = 0; i < c; i++ ) { + if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) { + inventory.objectiveNames.RemoveIndex( i ); + break; + } + } + ShowObjective( "newObjectiveComplete" ); + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "newObjectiveComplete" ); + } + + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } +} + +/* +=============== +idPlayer::FailObjective +=============== +*/ +void idPlayer::FailObjective ( const char* title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); + +// mekberg: prevent save games if objective failed. + gameLocal.sessionCommand = "objectiveFailed "; + gameLocal.sessionCommand += title; +// RAVEN END + HideObjective ( ); + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "objectiveFailed" ); + } + if( IsInVehicle() ) { + vehicleController.GetVehicle()->EjectAllDrivers(); + } + fl.takedamage = true; + pfl.objectiveFailed = true; +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; +#else + playerView.Fade( colorBlack, 12000 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict *idPlayer::FindInventoryItem( const char *name ) { + for ( int i = 0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname && *iname ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + return inventory.items[i]; + } + } + } + return NULL; +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( const char *name ) { + idDict *item = FindInventoryItem(name); + if ( item ) { + RemoveInventoryItem( item ); + } +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( idDict *item ) { + inventory.items.Remove( item ); + delete item; +} + +/* +=============== +idPlayer::GiveItem +=============== +*/ +void idPlayer::GiveItem( const char *itemname ) { + idDict args; + + args.Set( "classname", itemname ); + args.Set( "owner", name.c_str() ); + args.Set( "givenToPlayer", va( "%d", entityNumber ) ); + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // check if this is a weapon + if( !idStr::Icmpn( itemname, "weapon_", 7 ) ) { + int weaponIndex = SlotForWeapon( itemname ); + if( weaponIndex >= 0 && weaponIndex < MAX_WEAPONS ) + { + int weaponIndexBit = ( 1 << weaponIndex ); + inventory.weapons |= weaponIndexBit; + inventory.carryOverWeapons |= weaponIndexBit; + carryOverCurrentWeapon = weaponIndex; + } + } + + // if the player is dead, credit him with this armor or ammo purchase + if ( health <= 0 ) { + if( !idStr::Icmp( itemname, "item_armor_small" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_LIGHT; + } else if( !idStr::Icmp( itemname, "item_armor_large" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_HEAVY; + } else if( !idStr::Icmp( itemname, "ammorefill" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_AMMO; + } + } else { + if ( !idStr::Icmp( itemname, "ammorefill" ) ) { + int i; + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + int a = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( rvWeapon::GetAmmoNameForIndex( i ), 0 ); + inventory.ammo[i] += a; + if ( inventory.ammo[i] > inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) { + inventory.ammo[i] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ); + } + } + } + } + } + + // spawn the item if the player is alive + if ( health > 0 && idStr::Icmp( itemname, "ammorefill" ) ) { + gameLocal.SpawnEntityDef( args ); + } + +} + +/* +================== +idPlayer::SlotForWeapon +================== +*/ +int idPlayer::SlotForWeapon( const char *weaponName ) { + int i; + + for( i = 0; i < MAX_WEAPONS; i++ ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + return i; + } + } + + // not found + return -1; +} + +/* +=============== +idPlayer::Reload +=============== +*/ +void idPlayer::Reload( void ) { + if ( gameLocal.isClient || spectating || gameLocal.inCinematic || influenceActive || !weapon ) { + return; + } + + weapon->Reload(); +} + +#ifdef _XENON +/* +=============== +idPlayer::ScheduleWeaponSwitch +=============== +*/ +void idPlayer::ScheduleWeaponSwitch(int weapon) +{ + CancelEvents(&EV_Player_SelectWeapon); + hud->SetStateInt("player_selectedWeapon", weapon-1); + hud->HandleNamedEvent( "weaponSelect" ); + + // nrausch: support for turning the weapon change ui on and off + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( false ); + } + + if ( weapon > 0 ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", weapon-1 ) ); + PostEventSec(&EV_Player_SelectWeapon, 0.25f, weap); + } +} +#endif + +/* +=============== +idPlayer::ShowCrosshair +=============== +*/ +void idPlayer::ShowCrosshair( void ) { + if ( !weaponEnabled ) { + return; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::HideCrosshair +=============== +*/ +void idPlayer::HideCrosshair( void ) { + if ( cursor ) { + cursor->HandleNamedEvent( "crossHide" ); + } +} + +/* +=============== +idPlayer::LastWeapon +=============== +*/ +void idPlayer::LastWeapon( void ) { + // Dont bother if previousWeapon is invalid or the player is spectating + if ( spectating || previousWeapon < 0 ) { + return; + } + + // Do we have the weapon still? + if ( !(inventory.weapons & ( 1 << previousWeapon ) ) ) { + return; + } + + idealWeapon = previousWeapon; +} + +/* +=============== +idPlayer::NextBestWeapon +=============== +*/ +void idPlayer::NextBestWeapon( void ) { + const char *weap; + int w = MAX_WEAPONS; + + if ( gameLocal.isClient || !weaponEnabled ) { + return; + } + + while ( w > 0 ) { + w--; + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) { + continue; + } + if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) { + continue; + } + break; + } + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::NextWeapon +=============== +*/ +void idPlayer::NextWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + // if the weapon change below fails (weapon not present), we still send back a ACK just in case the client decided it had a weapon + CheckAckReply(); + } + + w = idealWeapon; + while ( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( w != idealWeapon ) { + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::PrevWeapon +=============== +*/ +void idPlayer::PrevWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + CheckAckReply(); + } + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( w != idealWeapon ) { + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( const char *weapon_name ) { + Event_SelectWeapon( weapon_name ); +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( int num, bool force ) { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + // if the weapon change below fails (weapon not present), we still send back a ACK just in case the client decided it had a weapon + CheckAckReply(); + } + + weap = spawnArgs.GetString( va( "def_weapon%d", num ) ); + if ( !weap[ 0 ] ) { + gameLocal.Warning( "Invalid weapon def_weapon%d\n", num ); + return; + } + + // cycle in-between weapons + // if a weapon_def has a "def_weapon_swap" keyvalue pointing to another + // weapon, hitting that impulse twice will cycle to the target swap. + if ( num == currentWeapon ) { + const idDict* weapDict = gameLocal.FindEntityDefDict( weap, false ); + + if ( weapDict == NULL ) { + gameLocal.Warning( "Invalid weapon entity %s\n", weap ); + return; + } + + const char* destWeapon = weapDict->GetString( "def_weapon_swap", NULL ); + + if ( destWeapon != NULL ) { + int swapNum = SlotForWeapon( destWeapon ); + if( swapNum == -1 ) { + gameLocal.Warning( "Swap weapon for %s (%s) is invalid", weap, destWeapon ); + } else { + num = swapNum; + } + } + } + + if ( force || ( inventory.weapons & ( 1 << num ) ) ) { + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) { + return; + } + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) { + assert( false ); // I'm assuming this does not happen in MP + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) { + return; + } + idealWeapon = previousWeapon; + } else { + idealWeapon = num; + } + UpdateHudWeapon(); + } +} + +/* +================= +idPlayer::DropItem +================= +*/ +idEntity* idPlayer::DropItem( const char* itemClass, const idDict& customArgs, const idVec3& velocity ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.SetFloat ( "angle", 360.0f * gameLocal.random.RandomFloat ( ) ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + // If a velocity was given then just use that, otherwise randomly throw it around + if ( velocity != vec3_origin ) { + ent->GetPhysics()->SetLinearVelocity ( velocity ); + } else { + idVec3 vel; + float ang; + ang = idMath::TWO_PI * gameLocal.random.RandomFloat(); + vel[0] = PLAYER_ITEM_DROP_SPEED * idMath::Cos ( ang ); + vel[1] = PLAYER_ITEM_DROP_SPEED * idMath::Sin ( ang ); + vel[2] = PLAYER_ITEM_DROP_SPEED * 2; + ent->GetPhysics()->SetLinearVelocity ( vel ); + } + return ent; +} + +/* +================= +idPlayer::DropPowerups +================= +*/ +void idPlayer::DropPowerups( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + // These powerups aren't dropped + if ( i >= POWERUP_TEAM_AMMO_REGEN && i <= POWERUP_TEAM_DAMAGE_MOD ) + continue; + + // Don't drop this either with buying enabled. + if ( i == POWERUP_REGENERATION && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + continue; + + /// Don't drop arena rune powerups in non-Arena modes + if( gameLocal.gameType != GAME_ARENA_CTF ) + { + if( i == POWERUP_AMMOREGEN || + i == POWERUP_GUARD || + i == POWERUP_DOUBLER || + i == POWERUP_SCOUT ) + { + continue; + } + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + if( def->dict.GetBool( "nodrop" ) ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + args.SetInt( "instance", GetInstance() ); + item = DropItem ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +idPlayer::ResetFlag +================= +*/ +idEntity* idPlayer::ResetFlag ( const char* itemClass, const idDict& customArgs ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.Set( "reset", "1" ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + return ent; +} + +/* +================= +idPlayer::RespawnFlags +================= +*/ +void idPlayer::RespawnFlags ( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = POWERUP_CTF_MARINEFLAG; i < POWERUP_CTF_ONEFLAG; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + item = ResetFlag ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +DropWeapon +================= +*/ +void idPlayer::DropWeapon( void ) { + idEntity* item; + idDict args; + const char* itemClass; + + assert( !gameLocal.isClient ); + + if( !gameLocal.isMultiplayer ) { + return; + } + +// RITUAL BEGIN +// squirrel: don't drop weapons in Buying modes unless "always drop" is on + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && !gameLocal.serverInfo.GetBool( "si_dropWeaponsInBuyingModes" ) ) { + return; + } +// RITUAL END + + if ( spectating || weaponGone || !weapon ) { + return; + } + + // Make sure the weapon is droppable + itemClass = weapon->spawnArgs.GetString ( "def_dropItem" ); + if ( !itemClass || !*itemClass ) { + return; + } + + // If still alive then the weapon is being thrown so start it a bit in front of the player + + // copy over the instance + args.SetInt( "instance", GetInstance() ); + + if ( health > 0 ) { + idVec3 forward; + idVec3 up; + viewAngles.ToVectors( &forward, NULL, &up ); + args.SetBool( "triggerFirst", true ); + item = DropItem ( itemClass, args, 250.0f*forward + 150.0f*up ); + } else { + item = DropItem ( itemClass, args ); + } + + // Drop the weapon + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop weapon '%s'", entityNumber, weapon->spawnArgs.GetString ( "def_dropItem" ) ); + return; + } + + // Since this weapon was dropped, replace any starting ammo values with real ammo values + const idKeyValue* keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_" ); + idDict newArgs; + while( keyval ) { + newArgs.Set( va( "inv_ammo_%s", keyval->GetKey().Right( keyval->GetKey().Length() - 15 ).c_str() ), keyval->GetValue().c_str() ); + item->spawnArgs.Set( keyval->GetKey(), "" ); + keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_", keyval ); + } + + item->spawnArgs.SetDefaults( &newArgs ); + + // Set the appropriate mods on the dropped item + int i; + int mods; + idStr out; + mods = weapon->GetMods ( ); + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + if ( mods & (1<spawnArgs.GetString ( va("def_mod%d", i+1) ); + } + } + if ( out.Length() ) { + item->spawnArgs.Set ( "inv_weaponmod", out ); + } + + // Make sure the weapon removes itself over time. + item->PostEventMS ( &EV_Remove, WEAPON_DROP_TIME ); + + // Delay aquire since the weapon is being thrown + if ( health > 0 ) { + item->PostEventMS ( &EV_Activate, 500, item ); + inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 ); + NextWeapon(); + } +} + +/* +=============== +idPlayer::ActiveGui +=============== +*/ +idUserInterface *idPlayer::ActiveGui( void ) { +#ifdef _XENON + if ( objectiveSystemOpen ) { + return 0; + } +#endif + return focusUI; +} + +/* +=============== +idPlayer::Weapon_Combat +=============== +*/ +void idPlayer::Weapon_Combat( void ) { + + if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + if ( weapon ) { + weapon->RaiseWeapon(); + + if ( weapon->IsReloading() ) { + if ( !pfl.reload ) { + pfl.reload = true; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Reload", 4 ); + UpdateState(); + } + } else { + pfl.reload = false; + } + } + + if ( idealWeapon != currentWeapon ) { + if ( !weapon || weaponCatchup ) { + assert( gameLocal.isClient ); + weaponGone = false; + SetWeapon( idealWeapon ); + + weapon->NetCatchup(); + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + } else { + if ( weapon->IsReady() || weapon->IsReloading() ) { + weapon->PutAway(); + } + + if ( weapon->IsHolstered() && weaponViewModel ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + SetWeapon( idealWeapon ); + + weapon->Raise(); + } + } + } else { + weaponGone = false; + if ( weapon->IsHolstered() ) { + if ( !weapon->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else { + weapon->Raise(); + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 3 ); + } + } + } + + weaponCatchup = false; + + // check for attack + pfl.weaponFired = false; + if ( !influenceActive ) { + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + pfl.attackHeld = false; + weapon->EndAttack(); + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + UpdateHudWeapon(); + } + + // update our ammo clip in our inventory + if ( gameLocal.GetLocalPlayer() == this && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + if ( hud && ( currentWeapon == idealWeapon ) ) { + UpdateHudAmmo( hud ); + } + } +} + +/* +=============== +idPlayer::Weapon_Vehicle +=============== +*/ +void idPlayer::Weapon_Vehicle( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + ProcessEvent ( &AI_EnterVehicle, focusEnt.GetEntity() ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_Usable +=============== +*/ +void idPlayer::Weapon_Usable( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + focusEnt->ProcessEvent ( &EV_Activate, this ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_NPC +=============== +*/ +void idPlayer::Weapon_NPC( void ) { + + flagCanFire = false; + + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + + if ( currentWeapon ) { + StopFiring(); + } + + if ( !focusEnt || focusEnt->health <= 0 ) { + ClearFocus ( ); + return; + } + + if ( talkCursor && ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + buttonMask |= BUTTON_ATTACK; + if ( !talkingNPC ) { + idAI *focusAI = static_cast(focusEnt.GetEntity()); + if ( focusAI ) { + focusAI->TalkTo( this ); + talkingNPC = focusAI; + } + } + } else if ( currentWeapon == SlotForWeapon ( "weapon_blaster" ) ) { + Weapon_Combat(); + } +} + + +/* +=============== +idPlayer::LowerWeapon +=============== +*/ +void idPlayer::LowerWeapon( void ) { + if ( weapon && !weapon->IsHidden() ) { + weapon->LowerWeapon(); + } +} + +/* +=============== +idPlayer::RaiseWeapon +=============== +*/ +void idPlayer::RaiseWeapon( void ) { + if ( weapon && weapon->IsHidden() ) { + weapon->RaiseWeapon(); + } +} + +/* +=============== +idPlayer::WeaponLoweringCallback +=============== +*/ +void idPlayer::WeaponLoweringCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LowerWeapon", 3 ); + UpdateState(); +} + +/* +=============== +idPlayer::WeaponRisingCallback +=============== +*/ +void idPlayer::WeaponRisingCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 2 ); + UpdateState(); +} + +/* +=============== +idPlayer::Weapon_GUI +=============== +*/ +void idPlayer::Weapon_GUI( void ) { + + flagCanFire = false; + + if ( !objectiveSystemOpen ) { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon->LowerWeapon(); + } + + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { + sysEvent_t ev; + const char *command = NULL; + bool updateVisuals = false; + + idUserInterface *ui = ActiveGui(); + if ( ui ) { + ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +=============== +idPlayer::UpdateWeapon +=============== +*/ +void idPlayer::UpdateWeapon( void ) { + if ( health <= 0 ) { + return; + } + + assert( !spectating ); + + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( gameLocal.isClient && (!weaponViewModel || !weaponWorldModel) ) { + return; + } + + // always make sure the weapon is correctly setup before accessing it + if ( !weapon ) { + if ( idealWeapon != -1 ) { + SetWeapon( idealWeapon ); + weaponCatchup = false; + assert( weapon ); + } else { + return; + } + } + + + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { + HideTip(); + } + + // Make sure the weapon is in a settled state before preventing thinking due + // to drag entity. This way things like hitting reload, zoom, etc, wont crash + if ( g_dragEntity.GetInteger() ) { + StopFiring(); + flagCanFire = false; + if ( weapon ) { + weapon->LowerWeapon(); + } + dragEntity.Update( this ); + return; + } else if ( focusType == FOCUS_CHARACTER) { + flagCanFire = false; + Weapon_NPC(); + } else if ( focusType == FOCUS_VEHICLE ) { + flagCanFire = false; + Weapon_Vehicle(); + } else if ( focusType == FOCUS_USABLE || focusType == FOCUS_USABLE_VEHICLE ) { + flagCanFire = false; + Weapon_Usable(); + } else if ( ActiveGui() ) { + flagCanFire = false; + Weapon_GUI(); + } else if ( !hiddenWeapon ) { /* no pda yet || ( ( weapon_pda >= 0 ) && ( idealWeapon == weapon_pda ) ) ) { */ + flagCanFire = true; + Weapon_Combat(); + } + + // Range finder for debugging + if ( g_showRange.GetBool ( ) ) { + idVec3 start; + idVec3 end; + trace_t tr; + + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 50000.0f; + gameLocal.TracePoint( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); + + idVec3 forward; + idVec3 right; + idVec3 up; + viewAngles.ToVectors ( &forward, &right, &up ); + gameRenderWorld->DrawText( va( "%d qu", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d m", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f - up * 6.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d 2d", ( int )DistanceTo2d( tr.endpos ) ), start + forward * 100.0f + right * 25.0f - up * 12.0f, .2f, colorCyan, viewAxis ); + } + + if ( weapon ) { + if ( hiddenWeapon ) { + weapon->LowerWeapon(); + } + + // update weapon state, particles, dlights, etc + weaponViewModel->PresentWeapon( showWeaponViewModel ); + } +} + +/* +=============== +idPlayer::SpectateFreeFly +=============== +*/ +void idPlayer::SpectateFreeFly( bool force ) { + idPlayer *player; + idVec3 newOrig; + idVec3 spawn_origin; + idAngles spawn_angles; + + player = gameLocal.GetClientByNum( spectator ); + if ( force || gameLocal.time > lastSpectateChange ) { + spectator = entityNumber; + if ( player && player != this && !player->spectating && !player->IsInTeleport() ) { + newOrig = player->GetPhysics()->GetOrigin(); + if ( player->physicsObj.IsCrouching() ) { + newOrig[ 2 ] += pm_crouchviewheight.GetFloat(); + } else { + newOrig[ 2 ] += pm_normalviewheight.GetFloat(); + } + newOrig[ 2 ] += SPECTATE_RAISE; + idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + idVec3 start = player->GetPhysics()->GetOrigin(); + start[2] += pm_spectatebbox.GetFloat() * 0.5f; + trace_t t; + // assuming spectate bbox is inside stand or crouch box +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( player, t, start, newOrig, b, MASK_PLAYERSOLID, player ); +// RAVEN END + newOrig.Lerp( start, newOrig, t.fraction ); + SetOrigin( newOrig ); + idAngles angle = player->viewAngles; + angle[ 2 ] = 0; + SetViewAngles( angle ); + } else { + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + return; + } + spawn_origin[ 2 ] += pm_normalviewheight.GetFloat(); + spawn_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spawn_origin ); + SetViewAngles( spawn_angles ); + } + lastSpectateChange = gameLocal.time + 500; + } +} + +/* +=============== +idPlayer::SpectateCycle +=============== +*/ +void idPlayer::SpectateCycle( void ) { + idPlayer *player; + + if ( gameLocal.time > lastSpectateChange ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + if ( !player ) { + SpectateFreeFly( true ); + return; + } + + // ignore other spectators + int latchedSpectator = spectator; + while ( player->spectating ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + if ( spectator == latchedSpectator ) { + break; + } + } + lastSpectateChange = gameLocal.time + 500; + + if ( !player || player->spectating ) { + SpectateFreeFly( true ); + return; + } + + if ( player ) { + player->UpdateHudWeapon( player->currentWeapon ); + } + } +} + +/* +=============== +idPlayer::UpdateSpectating +=============== +*/ +void idPlayer::UpdateSpectating( void ) { + assert( spectating ); + assert( !gameLocal.isClient || IsFakeClient() ); + assert( IsHidden() ); + idPlayer *player; + if ( !gameLocal.isMultiplayer ) { + return; + } + player = gameLocal.GetClientByNum( spectator ); + if ( !player || ( player->spectating && player != this ) ) { + SpectateFreeFly( true ); + } else if ( usercmd.upmove > 0 && player && player != this ) { + // following someone and hit jump? release. + SpectateFreeFly( false ); + } else if ( usercmd.buttons & BUTTON_ATTACK && gameLocal.gameType != GAME_TOURNEY ) { + // tourney mode uses seperate cycling + SpectateCycle(); + } +} + +/* +=============== +idPlayer::HandleSingleGuiCommand +=============== +*/ +bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "addhealth" ) == 0 ) { + if ( entityGui && health < 100 ) { + int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); + int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health; + _health -= amt; + entityGui->spawnArgs.SetInt( "gui_parm1", _health ); + if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) { + entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health ); + } + health += amt; + if ( health > 100 ) { + health = 100; + } + } + return true; + } + + if ( token.Icmp( "ready" ) == 0 ) { + PerformImpulse( IMPULSE_17 ); + return true; + } + + if ( token.Icmp( "heal" ) == 0 && + entityGui->IsType( rvHealingStation::GetClassType() ) && + src->ReadToken( &token ) ) + { + rvHealingStation * station = static_cast< rvHealingStation * >( entityGui ); + + if ( token.Icmp( "begin" ) == 0 ) { + station->BeginHealing( this ); + } else if ( token.Icmp( "end" ) == 0 ) { + station->EndHealing( ); + } else { + return false; + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +============== +idPlayer::Collide +============== +*/ +bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *other; + other = gameLocal.entities[ collision.c.entityNum ]; + + // allow client-side prediction of item collisions for simple client effects + if ( gameLocal.isClient && !other->IsType( idItem::GetClassType() ) ) { + return false; + } + + + if ( other ) { + other->Signal( SIG_TOUCH ); + if ( !spectating ) { + if ( other->RespondsTo( EV_Touch ) ) { + other->ProcessEvent( &EV_Touch, this, &collision ); + } + } else { + if ( other->RespondsTo( EV_SpectatorTouch ) ) { + other->ProcessEvent( &EV_SpectatorTouch, this, &collision ); + } + } + } + return false; +} + +/* +================ +idPlayer::UpdateLocation + +Searches nearby locations +================ +*/ + void idPlayer::UpdateLocation( void ) { + if ( hud ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if ( locationEntity ) { + hud->SetStateString( "location", locationEntity->GetLocation() ); + } else { +// RAVEN BEGIN +// rjohnson: temp fix until id corrects slow downs created from constant string lookup +// hud->SetStateString( "location", common->GetLocalizedString( "#str_102911" ) ); + hud->SetStateString( "location", "Unidentified" ); +// RAVEN END + } + } +} + +/* +================ +idPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event +================ +*/ +void idPlayer::UpdateFocus( void ) { + + // These only need to be updated at the last tic + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idClipModel* clipModelList[ MAX_GENTITIES ]; + idClipModel* clip; + int listedClipModels; + idEntity* ent; + idUserInterface* oldBrackets; + int oldTalkCursor; + int i; + int j; + idVec3 start; + idVec3 end; + trace_t renderTrace, bboxTrace, allTrace; + guiPoint_t pt; + +// RAVEN BEGIN + // mekberg: removed check to see if attack was held. +// RAVEN END + + // No focus during cinimatics + if ( gameLocal.inCinematic ) { + return; + } + + // Focus has a limited time, make sure it hasnt expired + if ( focusTime && gameLocal.time > focusTime ) { + ClearFocus ( ); + } + + if ( spectating ) { + return; + } + + if ( g_perfTest_noPlayerFocus.GetBool() ) { + return; + } + +#ifndef _XENON + cvarSystem->SetCVarInteger( "pm_isZoomed", zoomed ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + +#ifdef _XENON + if ( cursor ) { + if ( weapon ) { + cursor->SetStateInt( "autoaim", weapon->AllowAutoAim() ? 1 : 0 ); + } else { + cursor->SetStateInt( "autoaim", 0 ); + } + } +#endif + + // Kill the focus brackets when their time has elapsed + oldBrackets = focusBrackets; + if ( focusBracketsTime && gameLocal.time > focusBracketsTime ) { + focusBrackets = NULL; + } + + oldTalkCursor = talkCursor; + talkCursor = 0; + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 768.0f; + + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && IsLocalClient() ) { + trace_t trace; + idVec3 end = start + viewAngles.ToForward() * 768.0f; + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + // no aim text if player is invisible + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) && ( !((idPlayer*)gameLocal.entities[ trace.c.entityNum ])->PowerUpActive( POWERUP_INVISIBILITY ) ) ) { + char* teammateHealth = ""; + idPlayer* p = static_cast(gameLocal.entities[ trace.c.entityNum ]); + + if( trace.c.entityNum != aimClientNum ) { + if( mphud ) { + mphud->SetStateString( "aim_text", va( "%s\n%s", gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_name" ), gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_clan" ) ) ); + if( gameLocal.IsTeamGame() ) { + mphud->SetStateInt( "aim_player_team", p->team ); + + if( p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + // when looking at a friendly, color the crosshair + if( cursor ) { + if( p->team == team ) { + cursor->HandleNamedEvent( "targetFriendly" ); + } else { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + + + mphud->HandleNamedEvent( "aim_text" ); + aimClientNum = trace.c.entityNum; + } + } else { + // update health + if( gameLocal.IsTeamGame() && p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + } + } else { + if( mphud && aimClientNum != -1 ) { + mphud->HandleNamedEvent( "aim_fade" ); + aimClientNum = -1; + } + if( cursor ) { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + +#ifdef _XENON + + bboxTrace.fraction = -1; + + bestEnemy = NULL; + if ( gameLocal.isMultiplayer ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + return; + } + + idEntity *ent = NULL; + idActor *actor = NULL; + bool inside; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + + if ( showDebug ) { + gameRenderWorld->DebugFrustum(colorRed, aimArea, false, 20); + } + + idLinkList *entities = &gameLocal.snapshotEntities; + if ( gameLocal.isServer ) { + entities = &gameLocal.spawnedEntities; + } + ent = entities->Next(); + while ( ent != NULL ) { + + bool isOk = true; + + if ( !ent->IsType(idPlayer::GetClassType()) ) { + isOk = false; + } else if ( gameLocal.IsTeamGame() && ((idPlayer*)ent)->team == team ) { + isOk = false; + } else if ( ((idPlayer*)ent)->instance != instance ) { + isOk = false; + } else if ( ((idPlayer*)ent)->spectating ) { + isOk = false; + } else if ( !idStr::Icmp(name.c_str(),ent->name.c_str()) ) { + isOk = false; + } else if ( ent->health <= 0 ) { + isOk = false; + } + + if ( !isOk ) { + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + continue; + } + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + if ( showDebug ) { + gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + } + inside = aimArea.IntersectsBounds(bounds); + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + } + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + if ( bestEnemy ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + trace_t trace; + + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + return; + } + +#endif + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); + + // dluetscher: added optimization to eliminate redundant traces + renderTrace.fraction = -1; + bboxTrace.fraction = -1; + allTrace.fraction = -1; + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + bool wasTargetFriendly = targetFriendly; + targetFriendly = false; + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); +//RITUAL BEGIN +//singlis: if ent is null, continue; + if(ent == NULL || ent->IsHidden()) { + continue; + } +//RITUAL END + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + // SP only + // basically what was happening was that heads, which are an idAFAttachment, were being used for the focusLength + // calculations, but that generates a different focus length than the body would (when you scan the crosshair back + // and forth between the head and body). This ends up looking like a bug when you are right at the threshold where + // the body will display the name and rank, but doesn't when you pitch up to aim at the head. Hence, using the body + // for this special case. + if ( !gameLocal.isMultiplayer && ent->IsType( idAFAttachment::GetClassType() )) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType()) ) { + focusLength = (body->GetPhysics()->GetOrigin() - start).LengthFast() - body->GetPhysics()->GetBounds().GetRadius(); + } + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + isFriendly = (static_cast( ent )->team == team); + } + + //change crosshair color if over a friendly + if ( !gameLocal.isMultiplayer + && focusType == FOCUS_NONE + && !g_crosshairCharInfoFar.GetBool() ) { + if ( focusLength < 512 ) { + bool newTargetFriendly = false; + if ( isAI && isFriendly ) { + newTargetFriendly = true; + } else if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->team == team ) ) { + newTargetFriendly = true; + } + } + if ( newTargetFriendly ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + targetFriendly = true; + if( cursor && !wasTargetFriendly ) { + cursor->HandleNamedEvent( "showCrossBuddy" ); + } + } + } + } + } + +// RAVEN BEGIN + +#ifdef _XENON + if ( doAimAssist ) { + if ( isAI && !isFriendly && (ent->health > 0) ) { + + if ( idStr::Icmp( name.c_str(),ent->name.c_str() ) != 0 ) { + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + //if ( showDebug ) { + //gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + //} + bool inside = aimArea.IntersectsBounds(bounds); + + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } + } +#endif + +// mekberg: allowFocus removed + if ( focusLength < (g_crosshairCharInfoFar.GetBool()?256.0f:80.0f) ) { +// RAVEN END + if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->GetTalkState() >= TALK_OK ) ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, body, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + + if ( isAI ) { + if ( static_cast( ent )->GetTalkState() >= TALK_OK ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, ent, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + } + + if ( focusLength < 80.0f ) { + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + rvVehicle* vehicle = static_cast(ent); + // dluetscher: added optimization to eliminate redundant traces + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum == ent->entityNumber ) && ((end - start).Length() * bboxTrace.fraction < vehicle->FocusLength()) ) { + //jshepard: locked or unusable vehicles + if ( !vehicle->IsLocked() && vehicle->HasOpenPositions() ) { + SetFocus ( FOCUS_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + + //jshepard: unusable vehicle + if( ent->spawnArgs.GetBool( "unusableVehicle", "0" ) ) { + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + break; + } + } + // Usable entities are last + if ( ent->fl.usable ) { + //jshepard: fake vehicles + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + if( ent->spawnArgs.GetBool("crosshair_vehicle")) { + SetFocus ( FOCUS_USABLE_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_USABLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + } + + if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) { + continue; + } + + if ( ent->spawnArgs.GetBool( "inv_item" ) ) { + // don't allow guis on pickup items focus + continue; + } + + pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end ); + if ( pt.x != -1 ) { + idUserInterface* ui = NULL; + renderEntity_t* focusGUIrenderEntity; + + focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId >= 1 && pt.guiId <= MAX_RENDERENTITY_GUI ) { + ui = focusGUIrenderEntity->gui[ pt.guiId-1 ]; + } + + if ( ui == NULL || !ui->IsInteractive ( ) ) { + continue; + } + + // All focused guis get brackets + focusBrackets = ui; + + // Any GUI that is too far away will just get bracket focus so the player can still shoot + // but still see which guis are interractive + if ( focusLength > 300.0f ) { + ClearFocus ( ); + break; + } else if ( focusLength > 80.0f ) { + ClearFocus ( ); + focusType = FOCUS_BRACKETS; +#ifdef _XENON + // Hide the "press whatever" text + hud->SetStateInt( "GUIIsNotUsingDPad", 3 ); + hud->SetStateInt( "hideInteractive", 1 ); +#endif + break; + } + + // If this is the first time this gui was activated then set up some things + if ( focusEnt != ent ) { + const idKeyValue* kv; + + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + ui->SetStateInt( "inv_count", inventory.items.Num() ); + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + ui->SetStateString( va( "inv_name_%i", j), iname ); + ui->SetStateString( va( "inv_icon_%i", j), iicon ); + ui->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + ui->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + ui->SetStateInt( iname, 1 ); + } + + for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) { + const char *p = inventory.pdaSecurity[ j ]; + + if ( p && *p ) { + ui->SetStateInt( p, 1 ); + } + } + + ui->SetStateString( "player_health", va("%i", health ) ); + ui->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); + + kv = ent->spawnArgs.MatchPrefix( "gui_", NULL ); + while ( kv ) { + ui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = ent->spawnArgs.MatchPrefix( "gui_", kv ); + } + } + + // clamp the mouse to the corner + const char* command; + sysEvent_t ev; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + +#ifdef _XENON + int usepad = 0; + if ( focusUI ) { + idWindow *dwin = ui->GetDesktop(); + if ( dwin ) { + idWinVar *wv = dwin->GetWinVarByName("dpadGUI"); + if ( wv ) { + usepad = atoi(wv->c_str()); + } + } + } + hud->SetStateInt( "GUIIsNotUsingDPad", (usepad) ? 0 : 1 ); + hud->SetStateInt( "hideInteractive", 0 ); +#endif + + SetFocus ( FOCUS_GUI, FOCUS_GUI_TIME, ent, ui ); + break; + } + } + +#ifdef _XENON + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } else { + + if ( bestEnemy && doAimAssist ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + } + trace_t trace; + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + } +#endif + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + if ( wasTargetFriendly && !targetFriendly && focusType == FOCUS_NONE ) { + if ( cursor ) { + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); + } + } + } + + // Update the focus brackets within the hud + if ( focusBrackets && hud ) { + if (focusType == FOCUS_BRACKETS || focusType == FOCUS_GUI ) { + if ( !oldBrackets || focusBracketsTime ) { + hud->HandleNamedEvent ( "showBrackets" ); + } + focusBracketsTime = 0; + } else { + if ( focusBracketsTime == 0 ) { + hud->HandleNamedEvent ( "fadeBrackets" ); + focusBracketsTime = gameLocal.time + 2000; + } + } + focusBrackets->SetStateBool ( "2d_calc", true ); + } + + if ( cursor && ( oldTalkCursor != talkCursor ) ) { + cursor->SetStateInt( "talkcursor", talkCursor ); + } + +} + +/* +================ +idPlayer::ClearFocus +================ +*/ +void idPlayer::ClearFocus ( void ) { + SetFocus ( FOCUS_NONE, 0, NULL, NULL ); +} + +void idPlayer::UpdateFocusCharacter( idEntity* newEnt ) { + if ( !cursor ) { + return; + } + // Handle character interaction + cursor->SetStateString( "npc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_name", "Joe" )) ); + cursor->SetStateString( "npcdesc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_description", "" )) ); + if ( newEnt->IsType( rvAIMedic::GetClassType() ) ) { + if ( ((rvAIMedic*)newEnt)->isTech ) { + cursor->SetStateInt( "npc_medictech", 2 ); + } else { + cursor->SetStateInt( "npc_medictech", 1 ); + } + } else { + cursor->SetStateInt( "npc_medictech", 0 ); + } +} +/* +================ +idPlayer::SetFocus +================ +*/ +void idPlayer::SetFocus ( playerFocus_t newType, int _focusTime, idEntity* newEnt, idUserInterface* newUI ) { + const char* command; + + // Handle transitions from one user interface to another or to none + if ( newUI != focusUI ) { + if ( focusUI ) { + command = focusUI->Activate( false, gameLocal.time ); + HandleGuiCommands( focusEnt, command ); + StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } + if ( newUI ) { + command = newUI->Activate( true, gameLocal.time ); + HandleGuiCommands( newEnt, command ); + StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + + // Hide the weapon when a gui is being used + /* + if ( weapon ) { + weapon->Hide(); + } + */ + }/* + else if ( weapon ) { + // Show the weapon since it was hidden when a gui first got focus + weapon->Show(); + } + */ + } + + //jshepard: the medic/tech crosshair won't update unless handleNamedEvent is called. I moved this outside + //of the switch below because the focus type is the same when moving directly from marine to marine, but the + //medic/tech status may change. + if ( newType == FOCUS_CHARACTER ) { + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + cursor->HandleNamedEvent ( "showCrossTalk" ); + } + } + // Show the appropriate cursor for the current focus type + if ( cursor && ( focusType != newType ) ) { + switch ( newType ) { + case FOCUS_VEHICLE: + case FOCUS_USABLE_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicle" ); + break; + case FOCUS_LOCKED_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicleLocked" ); + break; + case FOCUS_USABLE: + cursor->HandleNamedEvent ( "showCrossUsable" ); + break; + case FOCUS_GUI: + cursor->HandleNamedEvent ( "showCrossGui" ); + break; + case FOCUS_CHARACTER: + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + } + cursor->HandleNamedEvent ( "showCrossTalk" ); + break; + default: + // Make sure the weapon is shown in the default state +// RAVEN BEGIN +// abahr: don't do this if weapons are disabled + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); +// RAVEN END + break; + } + } + + + focusType = newType; + focusEnt = newEnt; + focusUI = newUI; + + if ( focusType == FOCUS_NONE ) { + focusTime = 0; + } else { + focusTime = gameLocal.time + _focusTime; + } +} + +/* +================= +idPlayer::CrashLand + +Check for hard landings that generate sound events +================= +*/ +void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + idVec3 origin, velocity; + idVec3 gravityVector, gravityNormal; + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + waterLevel_t waterLevel; + bool noDamage; + + pfl.softLanding = false; + pfl.hardLanding = false; + + // if the player is not on the ground + if ( !physicsObj.HasGroundContacts() ) { + return; + } + + gravityNormal = physicsObj.GetGravityNormal(); + + // if the player wasn't going down + if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { + return; + } + + waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + noDamage = false; + for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { + const contactInfo_t &contact = physicsObj.GetContact( i ); + if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { + noDamage = true; + break; + } + } + + //jshepard: no falling damage if falling damage is disabled + if( pfl.noFallingDamage ) { + return; + } + + origin = GetPhysics()->GetOrigin(); + gravityVector = physicsObj.GetGravity(); + + // calculate the exact velocity on landing + dist = ( origin - oldOrigin ) * -gravityNormal; + vel = oldVelocity * -gravityNormal; + acc = -gravityVector.Length(); + + a = acc / 2.0f; + b = vel; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < 1.0f ) { + return; + } + + // Some bug in the player movement code causes double fall damage on the 2nd frame in certain situations, + // so hack around this. Basically, if the next frame's velocity is going to be great enough to cause damage, + // don't do the damage this frame. This feels a lot safer than messing with things that could break player + // movement in worse ways. + if ( gameLocal.isMultiplayer ) { + + float vel2 = GetPhysics()->GetLinearVelocity() * -gravityNormal; + + a = acc / 2.0f; + b = vel2; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + float delta2 = vel2 + t * acc; + delta2 = delta2 * delta2 * 0.0001; + + if ( delta2 > softFallDelta ) { + return; + } + } + + + // ddynerman: moved height delta selection to player def + if ( delta > fatalFallDelta && fatalFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -32; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + } + } else if ( delta > hardFallDelta && hardFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -24; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + } + } else if ( delta > softFallDelta && softFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -16; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + } + } else if ( delta > noFallDelta && noFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -8; + landTime = gameLocal.time; + } + + // ddynerman: sometimes the actual landing animation is pre-empted by another animation (i.e. sliding, moving forward) + // so we play the landing sound here instead of relying on the anim + if( pfl.hardLanding ) { + StartSound ( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL ); + StartSound ( "snd_land_hard_pain", SND_CHANNEL_ANY, 0, false, NULL ); + } else if ( pfl.softLanding ) { + // todo - 2 different landing sounds for variety? + StartSound ( "snd_land_soft", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +/* +=============== +idPlayer::BobCycle +=============== +*/ +void idPlayer::BobCycle( const idVec3 &pushVelocity ) { + float bobmove; + int old, deltaTime; + idVec3 vel, gravityDir, velocity; + idMat3 viewaxis; + float bob; + float delta; + float speed; + float f; + + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + velocity = physicsObj.GetLinearVelocity() - pushVelocity; + + if ( noclip ) { + velocity.Zero ( ); + } + + gravityDir = physicsObj.GetGravityNormal(); + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) { + // airborne + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { + // start at beginning of cycle again + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + if ( physicsObj.IsCrouching() ) { + bobmove = pm_crouchbob.GetFloat(); + // ducked characters never play footsteps + } else { + // vary the bobbing based on the speed of the player + bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; + } + + // check for footstep / splash sounds + old = bobCycle; + bobCycle = (int)( old + bobmove * gameLocal.GetMSec() ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( idMath::Sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + // no view bob at all in MP while zoomed in + if ( IsZoomed() ) { + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + + // add angles based on velocity + delta = velocity * viewaxis[0]; + viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); + + delta = velocity * viewaxis[1]; + viewBobAngles.roll -= delta * pm_runroll.GetFloat(); + + // add angles based on bob + // make sure the bob is visible even at low speeds + speed = xyspeed > 200 ? xyspeed : 200; + + delta = bobfracsin * pm_bobpitch.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching + } + viewBobAngles.pitch += delta; + delta = bobfracsin * pm_bobroll.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching accentuates roll + } + if ( bobFoot & 1 ) { + delta = -delta; + } + viewBobAngles.roll += delta; + } + // calculate position for view bobbing + viewBob.Zero(); + + if ( physicsObj.HasSteppedUp() ) { + + // check for stepping up before a previous step is completed + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); + } else { + stepUpDelta = physicsObj.GetStepUp(); + } + if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { + stepUpDelta = 2.0f * pm_stepsize.GetFloat(); + } + stepUpTime = gameLocal.time; + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // if the player stepped up recently + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); + } + + // add bob height after any movement smoothing + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if ( bob > 6 ) { + bob = 6; + } +// RAVEN BEGIN +// abahr: added gravity + viewBob += bob * -gravityDir; +// RAVEN END + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + viewBob -= gravity * ( landChange * f ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + viewBob -= gravity * ( landChange * f ); + } +} + +/* +================ +idPlayer::UpdateDeltaViewAngles +================ +*/ +void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); + } + SetDeltaViewAngles( delta ); +} + +/* +================ +idPlayer::SetViewAngles +================ +*/ +void idPlayer::SetViewAngles( const idAngles &angles ) { + UpdateDeltaViewAngles(angles); + viewAngles = angles; +} + +/* +================ +idPlayer::UpdateViewAngles +================ +*/ +void idPlayer::UpdateViewAngles( void ) { + int i; + idAngles delta; + + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) { + // no view changes at all, but we still want to update the deltas or else when + // we get out of this mode, our view will snap to a kind of random angle + UpdateDeltaViewAngles( viewAngles ); + return; + } + + // if dead + if ( health <= 0 ) { + if ( pm_thirdPersonDeath.GetBool() ) { + viewAngles.roll = 0.0f; + viewAngles.pitch = 30.0f; + } else { + viewAngles.roll = 40.0f; + viewAngles.pitch = -15.0f; + } + return; + } + + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); + if ( influenceActive == INFLUENCE_LEVEL3 ) { + viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) ); + } else { + viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i] ) + deltaViewAngles[i] ); + } + } + if ( !centerView.IsDone( gameLocal.time ) ) { + viewAngles.pitch = centerView.GetCurrentValue( gameLocal.time ); + } + + // clamp the pitch + if ( noclip ) { + if ( viewAngles.pitch > 89.0f ) { + // don't let the player look down more than 89 degrees while noclipping + viewAngles.pitch = 89.0f; + } else if ( viewAngles.pitch < -89.0f ) { + // don't let the player look up more than 89 degrees while noclipping + viewAngles.pitch = -89.0f; + } + } else { + if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) { + // don't let the player look down enough to see the shadow of his (non-existant) feet + viewAngles.pitch = pm_maxviewpitch.GetFloat(); + } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) { + // don't let the player look up more than 89 degrees + viewAngles.pitch = pm_minviewpitch.GetFloat(); + } + } + + UpdateDeltaViewAngles( viewAngles ); + + // orient the model towards the direction we're looking + SetAngles( idAngles( 0, viewAngles.yaw, 0 ) ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; +} + +/* +============== +idPlayer::UpdateAir +============== +*/ +void idPlayer::UpdateAir( void ) { + + if ( health <= 0 ) { + return; + } + + // see if the player is connected to the info_vacuum + bool newAirless = false; + + if ( gameLocal.vacuumAreaNum != -1 ) { + int num = GetNumPVSAreas(); + if ( num > 0 ) { + int areaNum; + + // if the player box spans multiple areas, get the area from the origin point instead, + // otherwise a rotating player box may poke into an outside area + if ( num == 1 ) { + const int *pvsAreas = GetPVSAreas(); + areaNum = pvsAreas[0]; + } else { + areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() ); + } + newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR ); + } + } + + if ( newAirless ) { + if ( !airless ) { + StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( hud ) { + hud->HandleNamedEvent( "noAir" ); + } + } + airTics--; + if ( airTics < 0 ) { + airTics = 0; + // check for damage + const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); + int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); + if ( gameLocal.time > lastAirDamage + dmgTiming ) { + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + lastAirDamage = gameLocal.time; + } + } + + } else { + if ( airless ) { + StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + if ( hud ) { + hud->HandleNamedEvent( "Air" ); + } + } + airTics+=2; // regain twice as fast as lose + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } + + airless = newAirless; + + if ( hud ) { + hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() ); + } +} + +// RAVEN BEGIN +// abahr +/* +============== +idPlayer::UpdateGravity +============== +*/ +void idPlayer::UpdateGravity( void ) { + GetPhysics()->SetGravity( gameLocal.GetCurrentGravity(this) ); +} +// RAVEN END + +/* +============== +idPlayer::ToggleObjectives +============== +*/ +void idPlayer::ToggleObjectives ( void ) { +// RAVEN BEGIN +// mekberg: allow disabling of objectives. + if ( objectiveSystem == NULL || !objectivesEnabled ) { + return; + } +// RAVEN END + + if ( !objectiveSystemOpen ) { + int j, c = inventory.items.Num(); + objectiveSystem->SetStateInt( "inv_count", c ); + for ( j = 0; j < c; j++ ) { + idDict *item = inventory.items[j]; + if ( !item->GetBool( "inv_pda" ) ) { + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname ); + objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon ); + objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext ); + const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL ); + if ( kv ) { + objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + } + } + + for ( j = 0; j < MAX_WEAPONS; j++ ) { + const char *weapnum = va( "def_weapon%d", j ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << j ) ) { + const char *weap = spawnArgs.GetString( weapnum ); + if ( weap && *weap ) { + weapstate++; + } + } + objectiveSystem->SetStateInt( weapnum, weapstate ); + } + + UpdateObjectiveInfo(); + objectiveSystem->Activate( true, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommShow" ); + } else { + objectiveSystem->Activate( false, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommHide" ); + } + objectiveSystemOpen ^= 1; +} + +/* +============== +idPlayer::ToggleScoreboard +============== +*/ +void idPlayer::ToggleScoreboard( void ) { + scoreBoardOpen ^= 1; +} + +/* +============== +idPlayer::Spectate +============== +*/ +void idPlayer::Spectate( bool spectate, bool force ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + // track invisible player bug + // all hiding and showing should be performed through Spectate calls + // except for the private camera view, which is used for teleports + // ok for players in other instances to be hidden + assert( force || ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() || !spectating ) || ( IsHidden() && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ); + + if ( spectating == spectate && !force ) { + return; + } + + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + + spectating = spectate; + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteBits( spectating, 1 ); + ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 ); + } + + // on the client, we'll get spectate messages about clients in other instances - always assume they are spectating + if ( spectating || inOtherInstance ) { + // join the spectators + delete clientHead; + clientHead = NULL; + + ClearPowerUps(); + spectator = this->entityNumber; + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + common->DPrintf( "idPlayer::Spectate() - Disabling clip for %d '%s' - spectate: %d force: %d\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?", spectate, force ); + physicsObj.DisableClip(); + Hide(); + Event_DisableWeapon(); + + // remove the weapon + delete weapon; + weapon = NULL; + if ( !gameLocal.isClient ) { + delete weaponViewModel; + weaponViewModel = NULL; + delete weaponWorldModel; + weaponWorldModel = NULL; + } + } else { + // put everything back together again + UpdateModelSetup( true ); + currentWeapon = -1; // to make sure the def will be loaded if necessary + Show(); + Event_EnableWeapon(); + } + + SetClipModel( inOtherInstance ); + + if ( inOtherInstance ) { + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } +} + +/* +============== +idPlayer::SetClipModel +============== +*/ +void idPlayer::SetClipModel( bool forceSpectatorBBox ) { + idBounds bounds; + + common->DPrintf( "idPlayer::SetClipModel() - Called on %d '%s' forceSpectatorBBox = %d spectate = %d instance = %d local instance = %d\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "", forceSpectatorBBox, spectating, instance, gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->GetInstance() : -1 ); + if ( spectating || forceSpectatorBBox ) { + bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + } else { + bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 ); + bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() ); + } + // the origin of the clip model needs to be set before calling SetClipModel + // otherwise our physics object's current origin value gets reset to 0 + idClipModel *newClip; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + // < 0: don't use alternate alignment, > 0: use alternate alignment (faces aligned with axes for even-sided cylinders) + // 0: use AABB; 1: use 8-sided cylinder; 3+: use custom number of sides for cylinder + int sides = pm_usecylinder.GetInteger(); + bool alt_align = (sides > 0); + sides = idMath::Abs( sides ); + + if ( sides >= 1 ) { + if ( sides < 3 ) { + sides = 8; + } + idTraceModel trm( bounds, sides, alt_align ); +// trm.findWalkSurfaces = true; + newClip = new idClipModel( trm, NULL ); + } else { + idTraceModel trm( bounds ); +// trm.findWalkSurfaces = true; + newClip = new idClipModel( trm, NULL ); + } + + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); +} + +/* +============== +idPlayer::EnterVehicle +============== +*/ +bool idPlayer::EnterVehicle( idEntity* vehicle ) { + if ( !idActor::EnterVehicle ( vehicle ) ) { + return false; + } + +// RAVEN BEGIN +// jshepard: safety first + if( weapon) { + weapon->Hide(); + } + +// abahr: + //HideCrosshair(); +// RAVEN END + + return true; +} + +/* +============== +idPlayer::ExitVehicle +============== +*/ +bool idPlayer::ExitVehicle ( bool force ) { + if ( !idActor::ExitVehicle ( force ) ) { + return false; + } + + SetViewAngles( viewAxis[0].ToAngles() ); + +// RAVEN BEGIN +// jshepard: had this crash on me more than once :( + if(weapon) { + weapon->Show(); + } +// abahr: Would like to call something more specific + ShowCrosshair(); +// RAVEN END + + return true; +} + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + +/* +============== +GetItemCost +============== +*/ +int idPlayer::GetItemCost( const char* itemName ) { + if ( !itemCosts ) { + assert( false ); + return 99999; + } + return itemCosts->dict.GetInt( itemName, "99999" ); +} + +/* +============== +GetItemBuyImpulse +============== +*/ +int GetItemBuyImpulse( const char* itemName ) +{ + struct ItemBuyImpulse + { + const char* itemName; + int itemBuyImpulse; + }; + + ItemBuyImpulse itemBuyImpulseTable[] = + { + { "weapon_shotgun", IMPULSE_100, }, + { "weapon_machinegun", IMPULSE_101, }, + { "weapon_hyperblaster", IMPULSE_102, }, + { "weapon_grenadelauncher", IMPULSE_103, }, + { "weapon_nailgun", IMPULSE_104, }, + { "weapon_rocketlauncher", IMPULSE_105, }, + { "weapon_railgun", IMPULSE_106, }, + { "weapon_lightninggun", IMPULSE_107, }, + // IMPULSE_108 - Unused + { "weapon_napalmgun", IMPULSE_109, }, + // { "weapon_dmg", IMPULSE_110, }, + // IMPULSE_111 - Unused + // IMPULSE_112 - Unused + // IMPULSE_113 - Unused + // IMPULSE_114 - Unused + // IMPULSE_115 - Unused + // IMPULSE_116 - Unused + // IMPULSE_117 - Unused + { "item_armor_small", IMPULSE_118, }, + { "item_armor_large", IMPULSE_119, }, + { "ammorefill", IMPULSE_120, }, + // IMPULSE_121 - Unused + // IMPULSE_122 - Unused + { "ammo_regen", IMPULSE_123, }, + { "health_regen", IMPULSE_124, }, + { "damage_boost", IMPULSE_125, }, + // IMPULSE_126 - Unused + // IMPULSE_127 - Unused + }; + const int itemBuyImpulseTableSize = sizeof(itemBuyImpulseTable) / sizeof(itemBuyImpulseTable[0]); + + for( int i = 0; i < itemBuyImpulseTableSize; ++ i ) + { + if( !stricmp( itemBuyImpulseTable[ i ].itemName, itemName ) ) + { + return itemBuyImpulseTable[ i ].itemBuyImpulse; + } + } + + return 0; +} + + +bool idPlayer::CanBuyItem( const char* itemName ) +{ + itemBuyStatus_t buyStatus = ItemBuyStatus( itemName ); + return( buyStatus == IBS_CAN_BUY ); +} + + +itemBuyStatus_t idPlayer::ItemBuyStatus( const char* itemName ) +{ + idStr itemNameStr = itemName; + if ( itemNameStr == "notimplemented" ) + { + return IBS_NOT_ALLOWED; + } + else if( !idStr::Cmpn( itemName, "wpmod_", 6 ) ) + { + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_small" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_large" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "ammorefill" ) + { + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + return IBS_ALREADY_HAVE; + + // If we are full of ammo for all weapons, you can't buy the ammo refill anymore. + bool fullAmmo = true; + for ( int i = 0 ; i < MAX_AMMOTYPES; i++ ) + { + if ( inventory.ammo[i] != inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) + fullAmmo = false; + } + if ( fullAmmo ) + return IBS_NOT_ALLOWED; + } + else if ( itemNameStr == "fc_armor_regen" ) + { + return IBS_NOT_ALLOWED; + } + + if ( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TOURNEY || gameLocal.gameType == GAME_ARENA_CTF || gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if ( itemNameStr == "ammo_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "health_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "damage_boost" ) + return IBS_NOT_ALLOWED; + } + + if ( CanSelectWeapon(itemName) != -1 ) + return IBS_ALREADY_HAVE; + + int cost = GetItemCost(itemName); + if ( cost > (int)buyMenuCash ) + { + return IBS_CANNOT_AFFORD; + } + + return IBS_CAN_BUY; +} + +/* +============== +idPlayer::UpdateTeamPowerups +============== +*/ +void idPlayer::UpdateTeamPowerups( bool isBuying ) { + for ( int i=0; iIsType( idPlayer::Type ) ) + continue; + + idPlayer* player = static_cast< idPlayer * >( ent ); + + // Not my teammate + if ( player->team != team ) + continue; + + // when a teammate respawns while a powerup is active, we don't want to go through other players which already have the powerup + // otherwise they'll get multiple VO announces + // ignore this when setting up powerups after a buying impulse, it only means someone is buying before expiration + if ( !isBuying && ( ( player->inventory.powerups & ( 1 << powerup ) ) != 0 ) ) { + continue; + } + + player->GivePowerUp( powerup, time, true ); + } + + gameLocal.mpGame.teamPowerups[team][i].update = false; + } + } +} + + +/* +============== +idPlayer::AttemptToBuyTeamPowerup +============== +*/ +bool idPlayer::AttemptToBuyTeamPowerup( const char* itemName ) +{ + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_AMMOREGEN, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "health_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_REGENERATION, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "damage_boost" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_TEAM_DAMAGE_MOD, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + + return false; +} + +/* +============== +idPlayer::AttemptToBuyItem +============== +*/ +bool idPlayer::AttemptToBuyItem( const char* itemName ) +{ + assert( !IsFakeClient() ); + + if ( gameLocal.isClient ) { + return false; + } + + if( !itemName ) { + return false; + } + + int itemCost = GetItemCost( itemName ); + + /// Check if the player is allowed to buy this item + if( !CanBuyItem( itemName ) ) + { + return false; + } + + const char* playerName = GetUserInfo()->GetString( "ui_name" ); + common->DPrintf( "Player %s about to buy item %s; player has %d (%g) credits, cost is %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + buyMenuCash -= (float)itemCost; + + common->DPrintf( "Player %s just bought item %s; player now has %d (%g) credits, cost was %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + + // Team-based effects + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" || itemNameStr == "health_regen" || itemNameStr == "damage_boost" ) { + return AttemptToBuyTeamPowerup(itemName); + } + + GiveStuffToPlayer( this, itemName, NULL ); + gameLocal.mpGame.RedrawLocalBuyMenu(); + return true; +} + +bool idPlayer::CanBuy( void ) { + bool ret = gameLocal.mpGame.IsBuyingAllowedRightNow(); + if ( !ret ) { + return false; + } + return !spectating; +} + + +void idPlayer::GenerateImpulseForBuyAttempt( const char* itemName ) { + if ( !CanBuy() ) + return; + + int itemBuyImpulse = GetItemBuyImpulse( itemName ); + PerformImpulse( itemBuyImpulse ); +} +// RITUAL END + + +/* +============== +idPlayer::PerformImpulse +============== +*/ +void idPlayer::PerformImpulse( int impulse ) { + + if ( gameLocal.isClient ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( IsLocalClient() ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, IMPULSE_NUMBER_OF_BITS ); + ClientSendEvent( EVENT_IMPULSE, &msg ); + } + + if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) { + SelectWeapon( impulse, false ); + return; + } + + switch( impulse ) { + case IMPULSE_13: { + Reload(); + break; + } + case IMPULSE_14: { + NextWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCycleNext( this ); + } + break; + } + case IMPULSE_15: { + PrevWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCyclePrev( this ); + } + break; + } + case IMPULSE_17: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleReady( ); + } + break; + } + case IMPULSE_18: { + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); + break; + } + case IMPULSE_19: { +/* + // when we're not in single player, IMPULSE_19 is used for showScores + // otherwise it does IMPULSE_12 (PDA) + if ( !gameLocal.isMultiplayer ) { + if ( !objectiveSystemOpen ) { + if ( weapon ) { + weapon->Hide (); + } + } + ToggleMap(); + } +*/ + break; + } + case IMPULSE_20: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleTeam( ); + } + break; + } + case IMPULSE_21: { + if( gameLocal.isServer && gameLocal.gameType == GAME_TOURNEY ) { + // only allow a client to join the waiting arena if they are not currently assigned to an arena + + // removed waiting arena functionality for now + /*rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( this != arena.GetPlayers()[ 0 ] && this != arena.GetPlayers()[ 1 ] ) { + if( instance == MAX_ARENAS && !spectating ) { + ServerSpectate( true ); + JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } else if( spectating ) { + JoinInstance( MAX_ARENAS ); + ServerSpectate( false ); + } + }*/ + } + break; + } + case IMPULSE_22: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleSpectate( ); + } + break; + } + + case IMPULSE_28: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + } + break; + } + case IMPULSE_29: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + } + break; + } + case IMPULSE_40: { + idFuncRadioChatter::RepeatLast(); + break; + } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + case IMPULSE_100: AttemptToBuyItem( "weapon_shotgun" ); break; + case IMPULSE_101: AttemptToBuyItem( "weapon_machinegun" ); break; + case IMPULSE_102: AttemptToBuyItem( "weapon_hyperblaster" ); break; + case IMPULSE_103: AttemptToBuyItem( "weapon_grenadelauncher" ); break; + case IMPULSE_104: AttemptToBuyItem( "weapon_nailgun" ); break; + case IMPULSE_105: AttemptToBuyItem( "weapon_rocketlauncher" ); break; + case IMPULSE_106: AttemptToBuyItem( "weapon_railgun" ); break; + case IMPULSE_107: AttemptToBuyItem( "weapon_lightninggun" ); break; + case IMPULSE_108: break; // Unused + case IMPULSE_109: AttemptToBuyItem( "weapon_napalmgun" ); break; + case IMPULSE_110: /* AttemptToBuyItem( "weapon_dmg" );*/ break; + case IMPULSE_111: break; // Unused + case IMPULSE_112: break; // Unused + case IMPULSE_113: break; // Unused + case IMPULSE_114: break; // Unused + case IMPULSE_115: break; // Unused + case IMPULSE_116: break; // Unused + case IMPULSE_117: break; // Unused + case IMPULSE_118: AttemptToBuyItem( "item_armor_small" ); break; + case IMPULSE_119: AttemptToBuyItem( "item_armor_large" ); break; + case IMPULSE_120: AttemptToBuyItem( "ammorefill" ); break; + case IMPULSE_121: break; // Unused + case IMPULSE_122: break; // Unused + case IMPULSE_123: AttemptToBuyItem( "ammo_regen" ); break; + case IMPULSE_124: AttemptToBuyItem( "health_regen" ); break; + case IMPULSE_125: AttemptToBuyItem( "damage_boost" ); break; + case IMPULSE_126: break; // Unused + case IMPULSE_127: break; // Unused +// RITUAL END + + case IMPULSE_50: { + ToggleFlashlight ( ); + break; + } + + case IMPULSE_51: { + LastWeapon(); + break; + } + } + +//RAVEN BEGIN +//asalmon: route d-pad input to the active gui. +#ifdef _XBOX + if (ui && ev.evValue != 0 && !objectiveSystemOpen ) { + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +#endif +//RAVEN END +} + +/* +============== +idPlayer::HandleESC +============== +*/ +bool idPlayer::HandleESC( void ) { + +// jdischler: Straight from the top, cinematic skipping on xenon is OFFICIALLY OUT. Too many problems with it and not enough time to properly address them. +#ifndef _XENON + if ( gameLocal.inCinematic ) { + return SkipCinematic(); + } +#endif + return false; +} + +/* +============== +idPlayer::HandleObjectiveInput +============== +*/ +void idPlayer::HandleObjectiveInput() { +#ifdef _XENON + if ( gameLocal.inCinematic ) { + return; + } + if ( !objectiveButtonReleased ) { + if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 ) { + objectiveButtonReleased = true; + } + } else { + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 ) { + ToggleObjectives ( ); + g_ObjectiveSystemOpen = objectiveSystemOpen; + objectiveButtonReleased = false; + } + } +#else + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 && !objectiveSystemOpen && !gameLocal.inCinematic ) { + ToggleObjectives ( ); + } else if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 && objectiveSystemOpen ) { + ToggleObjectives ( ); + } else if ( objectiveSystemOpen && gameLocal.inCinematic ) { + ToggleObjectives ( ); + } +#endif +} + +/* +============== +idPlayer::EvaluateControls +============== +*/ +void idPlayer::EvaluateControls( void ) { + // check for respawning + if ( pfl.dead || pfl.objectiveFailed ) { +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( allowedToRespawn ) { + if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) { + forceRespawn = true; + } else if ( gameLocal.time > maxRespawnTime ) { + forceRespawn = true; + } + } + else + { + Spectate(true); + } +// RITUAL END + } + + // in MP, idMultiplayerGame decides spawns + if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) { + // in single player, we let the session handle restarting the level or loading a game + gameLocal.sessionCommand = "died"; + } + + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + assert( false ); // unused in multiplayer? + PerformImpulse( usercmd.impulse ); + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + oldFlags = usercmd.flags; + + AdjustSpeed(); + + // update the viewangles + UpdateViewAngles(); +} + +/* +============== +idPlayer::AdjustSpeed +============== +*/ +void idPlayer::AdjustSpeed( void ) { + float speed; + + if ( spectating ) { + speed = pm_spectatespeed.GetFloat(); + bobFrac = 0.0f; + } else if ( noclip ) { + speed = pm_noclipspeed.GetFloat(); + bobFrac = 0.0f; + } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { + bobFrac = 1.0f; + speed = pm_speed.GetFloat(); + } else { + speed = pm_walkspeed.GetFloat(); + bobFrac = 0.0f; + } + + speed *= PowerUpModifier(PMOD_SPEED); + + if ( influenceActive == INFLUENCE_LEVEL3 ) { + speed *= 0.33f; + } + + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); +} + +/* +============== +idPlayer::AdjustBodyAngles +============== +*/ +void idPlayer::AdjustBodyAngles( void ) { + idMat3 lookAxis; + idMat3 legsAxis; + bool blend; + float diff; + float upBlend; + float forwardBlend; + float downBlend; + + if ( health < 0 ) { + return; + } + + blend = true; + + if ( !physicsObj.HasGroundContacts() ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else if ( usercmd.forwardmove < 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = false; + } else if ( usercmd.forwardmove > 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = true; + } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) { + if ( !legsForward ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() ); + } else { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() ); + } + } else if ( usercmd.rightmove != 0 ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else { + legsForward = true; + diff = idMath::Fabs( idealLegsYaw - legsYaw ); + idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw ); + if ( diff < 0.1f ) { + legsYaw = idealLegsYaw; + blend = false; + } + } + + if ( !physicsObj.IsCrouching() ) { + legsForward = true; + } + + oldViewYaw = viewAngles.yaw; + + pfl.turnLeft = false; + pfl.turnRight = false; + if ( idealLegsYaw < -45.0f ) { + idealLegsYaw = 0; + pfl.turnRight = true; + blend = true; + } else if ( idealLegsYaw > 45.0f ) { + idealLegsYaw = 0; + pfl.turnLeft = true; + blend = true; + } + + if ( blend ) { + legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f; + } + legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3(); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis ); + + // ============================================================================================== + // leaning - orient parts of the body towards the movement direction + // we control the chest, neck and hip orientations + + float leanGunCorrection = 0.0f; + if ( g_playerLean.GetFloat() != 0.0f ) { + + idVec3 velocity = physicsObj.GetLinearVelocity(); + velocity.z = 0.0f; + + // clamp and scale max speed to unit velocity + float horizSpeed = velocity.Normalize(); + if ( horizSpeed > leanMaxSpeed ) { + horizSpeed = leanMaxSpeed; + } + velocity *= horizSpeed / leanMaxSpeed; + // now express this velocity in the view's axis base + velocity *= viewAxis.Transpose(); + + // blend + leanVelocity = velocity * leanBlendRatio + leanVelocity * ( 1.0f - leanBlendRatio ); + + // build the rotation + float t = fabs( leanVelocity.x ) + fabs( leanVelocity.y ); + if ( t > 1e-3f ) { + float maxAngle = ( leanMaxForwardAngle * fabs( leanVelocity.x ) + leanMaxLateralAngle * fabs( leanVelocity.y ) ) / t; + maxAngle *= g_playerLean.GetFloat(); + + idVec3 rotate; + rotate.Cross( leanVelocity, idVec3( 0.0f, 0.0f, 1.0f ) ); + float chestAngle = maxAngle * rotate.Normalize(); // doing the blend on the vectors, but idRotation wants a normalized rotation + idRotation chestRotate( vec3_origin, rotate, chestAngle ); + animator.SetJointAxis( chestJoint, JOINTMOD_WORLD, chestRotate.ToMat3() ); + + // extract a rotation angle in the forward plane, this is used to compensate the gun later + idVec3 p( 1.0f, 0.0f, 0.0f ); + chestRotate.RotatePoint( p ); + leanGunCorrection = RAD2DEG( idMath::ATan( p.z, p.x ) ); + + float headAngle = chestAngle * leanHeadRatio; + idRotation neckRotate( vec3_origin, rotate, headAngle ); + animator.SetJointAxis( neckLeanJoint, JOINTMOD_WORLD, neckRotate.ToMat3() ); + + float hipAngle = chestAngle * leanHipRatio; + idRotation hipRotate( vec3_origin, rotate, hipAngle ); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, hipRotate.ToMat3() ); + } else { + leanVelocity = vec3_origin; + leanGunCorrection = 0.0f; + } + } + + // ============================================================================================== + // calculate the blending between down, straight, and up. account for the chest lean + + float leanFrac = idMath::ClampFloat( -1.0f, 1.0f, ( viewAngles.pitch + leanGunCorrection ) / 90.0f ); + if ( leanFrac > 0.0f ) { + downBlend = leanFrac; + forwardBlend = 1.0f - leanFrac; + upBlend = 0.0f; + } else { + downBlend = 0.0f; + forwardBlend = 1.0f + leanFrac; + upBlend = -leanFrac; + } + + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend ); + + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend ); +} + +/* +============== +idPlayer::InitAASLocation +============== +*/ +void idPlayer::InitAASLocation( void ) { + int i; + int num; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + GetFloorPos( 64.0f, origin ); + + num = gameLocal.NumAAS(); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < aasLocation.Num(); i++ ) { + aasLocation[ i ].areaNum = 0; + aasLocation[ i ].pos = origin; + aas = gameLocal.GetAAS( i ); + if ( aas && aas->GetSettings() ) { + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + } + } +} + +/* +============== +idPlayer::SetAASLocation +============== +*/ +void idPlayer::SetAASLocation( void ) { + int i; + int areaNum; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + if ( !GetFloorPos( 64.0f, origin ) ) { + return; + } + + for( i = 0; i < aasLocation.Num(); i++ ) { + aas = gameLocal.GetAAS( i ); + if ( !aas ) { + continue; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aasLocation[ i ].pos = origin; + aasLocation[ i ].areaNum = areaNum; + } + } +} + +/* +============== +idPlayer::GetAASLocation +============== +*/ +void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + int i; + + if ( aas != NULL ) { + for( i = 0; i < aasLocation.Num(); i++ ) { + if ( aas == gameLocal.GetAAS( i ) ) { + areaNum = aasLocation[ i ].areaNum; + pos = aasLocation[ i ].pos; + return; + } + } + } + + areaNum = 0; + pos = physicsObj.GetOrigin(); +} + +/* +============== +idPlayer::Move +============== +*/ +void idPlayer::Move( void ) { + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || pfl.objectiveFailed || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + + // If the player is dead then only run physics on new + // frames since articulated figures are not synchronized over the network + if ( health <= 0 ) { + if ( gameLocal.isNewFrame ) { + DeathPush(); + RunPhysics(); + } + } else { + RunPhysics(); + } + + // update our last valid AAS location for the AI + if ( !gameLocal.isMultiplayer ) { + SetAASLocation(); + } + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( IsInVehicle ( ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + pfl.crouch = false; + pfl.onGround = ( influenceActive == INFLUENCE_LEVEL2 ); + pfl.onLadder = false; + pfl.jump = false; + } else { + pfl.crouch = physicsObj.IsCrouching(); + pfl.onGround = physicsObj.OnGround(); + pfl.onLadder = physicsObj.OnLadder(); + pfl.jump = physicsObj.HasJumped(); + + // check if we're standing on top of a monster and give a push if we are + idEntity *groundEnt = physicsObj.GetGroundEntity(); + if ( groundEnt && groundEnt->IsType( idAI::GetClassType() ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() < 0.1f ) { + vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); + vel.ToVec2().NormalizeFast(); + vel.ToVec2() *= pm_speed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( pfl.jump ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + + // PMF_JUMP can get stuck when dead, which causes some bad spamming + // a cleaner fix would be to make sure PMF_JUMP gets cleared, but this close to release, that will do + // (the AF becomes the active physics object, so physics don't run on idPlayer_Physics and that flag never gets cleared) + if ( health > 0 ) { + // don't use the sound broadcasting facility in StartSound, we only need unreliable, and we need to filter for local client prediction + if ( gameLocal.isServer || IsLocalClient() ) { + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMP ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameRenderWorld->PointInArea( physicsObj.GetOrigin() ) ); + } + } + } + + if ( pfl.onLadder ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + UpdateIntentDir( ); + + BobCycle( pushVelocity ); + + if( !noclip ) { + CrashLand( oldOrigin, oldVelocity ); + } +} + +/* +================== +idPlayer::BiasIntentDir + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::BiasIntentDir( idVec3 newIntentDir, float prevBias ) { + if ( !newIntentDir.Compare( vec3_origin ) ) { + if ( intentDir.Compare( vec3_origin ) ) { + //initialize it + intentDir = newIntentDir; + } else { + intentDir = ((intentDir*prevBias)+newIntentDir)/(prevBias+1.0f); + float iDirLen = idMath::ClampFloat(0.0f,1024.0f,intentDir.Normalize()); + intentDir *= iDirLen; + } + } +} + +/* +============== +idPlayer::UpdateIntentDir +============== +*/ +void idPlayer::UpdateIntentDir( void ) { + idVec3 newIntentDir; + idVec3 viewDir = viewAxis[0]; + viewDir.z = 0; + viewDir.Normalize(); + float prevBias = 199.0f; + if ( intentDir.Compare( vec3_origin ) ) { + newIntentDir = viewDir*50.0f; + } else { + newIntentDir = viewDir*intentDir.Length(); + if ( flashlightOn ) { + // bias them more heavily to looking where I'm looking + prevBias = 19.0f; + } + } + if ( pfl.onGround ) { + idVec3 moveDir = physicsObj.GetLinearVelocity(); + if ( moveDir.x || moveDir.y ) { + // moving, too + moveDir.z = 0; + newIntentDir = moveDir; + prevBias = 39.0f; + } + } + BiasIntentDir( newIntentDir, prevBias ); + if ( ai_debugSquad.GetBool() ) { + idVec4 color = colorCyan; + if ( pfl.weaponFired ) { + color = colorRed; + } + gameRenderWorld->DebugArrow( color, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + intentDir*4.0f, 8, 0 ); + } +} + +/* +============== +idPlayer::UpdateHud +============== +*/ +void idPlayer::UpdateHud( void ) { + if ( !hud ) { + return; + } + + if ( !IsLocalClient() ) { + return; + } + + // FIXME: this is here for level ammo balancing to see pct of hits + hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); + if ( numProjectilesFired ) { + hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); + } else { + hud->SetStateString( "projectilepct", "Hit % 0.0" ); + } + + if ( isLagged && gameLocal.isMultiplayer && IsLocalClient() ) { + hud->SetStateString( "hudLag", "1" ); + } else { + hud->SetStateString( "hudLag", "0" ); + } +} + +/* +============== +idPlayer::UpdateDeathSkin +============== +*/ +void idPlayer::UpdateDeathSkin( bool state_hitch ) { + if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) { + return; + } + if ( health <= 0 ) { + if ( !doingDeathSkin && !deathSkinTime ) { + deathSkinTime = gameLocal.time + 1000; + deathStateHitch = state_hitch; + } + + // wait a bit before switching off the content + if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) { + SetCombatContents( false ); + deathClearContentsTime = 0; + } + } else { + renderEntity.noShadow = false; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } + UpdateVisuals(); + doingDeathSkin = false; + deathClearContentsTime = 0; + } +} + +/* +=============== +idPlayer::LoadDeferredModel +=============== +*/ +void idPlayer::LoadDeferredModel( void ) { + if( !modelDecl ) { + gameLocal.Warning( "idPlayer::LoadDeferredModel() - reloadModel without vaid modelDict\n" ); + return; + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + + if( weapon ) { + weapon->NetCatchup(); + } + + if( !modelDecl->skin.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( modelDecl->skin.c_str(), false ); + } + + SetModel( modelDecl->model ); + + if( !modelDecl->head.Length() ) { + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + } else { + SetupHead( modelDecl->head.c_str(), modelDecl->headOffset ); + } + + if( clientHead && health <= 0 ) { + // update death shader for new head + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ]; + clientHead.GetEntity()->GetRenderEntity()->noShadow = renderEntity.noShadow; + } + + if( powerUpSkin != NULL ) { + SetSkin( powerUpSkin ); + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + } +} + +/* +============== +idPlayer::Think + +Called every tic for each player +============== +*/ +void idPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + if ( talkingNPC ) { + if ( !talkingNPC.IsValid() ) { + talkingNPC = NULL; + } else { + idAI *talkingNPCAI = (idAI*)(talkingNPC.GetEntity()); + if ( !talkingNPCAI ) { + //wtf? + talkingNPC = NULL; + } else if ( talkingNPCAI->talkTarget != this || !talkingNPCAI->IsSpeaking() || DistanceTo( talkingNPCAI ) > 256.0f ) { + //forget about them, okay to talk to someone else now + talkingNPC = NULL; + } + } + } + + if ( !gameLocal.usercmds ) { + return; + } + +#ifdef _XENON + // change the crosshair if it's modified + if ( cursor && weapon && g_crosshairColor.IsModified() ) { + weapon->UpdateCrosshairGUI( cursor ); + cursor->HandleNamedEvent( "weaponChange" ); + g_crosshairColor.ClearModified(); + } +#endif + + // Dont do any thinking if we are in modview + if ( gameLocal.editors & EDITOR_MODVIEW || gameEdit->PlayPlayback() ) { + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + FreeModelDef(); + + if ( weapon ) { + weapon->GetWorldModel()->FreeModelDef(); + } + + if ( head.GetEntity() ) { + head->FreeModelDef(); + } + + if ( clientHead ) { + clientHead->FreeEntityDef(); + } + + return; + } + + if( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + gameEdit->RecordPlayback( usercmd, this ); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ IsFakeClient() ? MAX_CLIENTS : entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + HandleObjectiveInput(); + if ( objectiveSystemOpen ) { + HandleCheats(); + } else { + ClearCheatState(); + } + + aasSensor->Update(); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + // we need to let the camera think inside of this routine + CalculateRenderView(); + return; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // if this is the very first frame of the map, set the delta view angles + // based on the usercmd angles + if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { + spawnAnglesSet = true; + SetViewAngles( spawnAngles ); + oldFlags = usercmd.flags; + } + + if ( gameLocal.inCinematic || influenceActive +#ifdef _XENON + || objectiveSystemOpen +#endif + ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent ( &EV_Player_ZoomIn ); + } else { + ProcessEvent ( &EV_Player_ZoomOut ); + } + + if ( vehicleController.IsDriving( ) ) { +#ifdef _XENON + usercmdGen->SetSlowJoystick( zoom ? pm_zoomedSlow.GetInteger() : 100 ); +#else + cvarSystem->SetCVarInteger( "pm_isZoomed", zoom ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + } + + } + + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + thinkFlags |= TH_PHYSICS; + RunPhysics(); + + if ( health > 0 ) { + TouchTriggers(); + } + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + LinkCombat(); + } else { + UpdateModel(); + } + + // I don't want to have to add this but if you are in a locked vehicle and you fail your objective, you won't be + // ejected from the vehicle (and I don't think we'd want that even though we do have the option of forcing it..) + // and since you are still in the vehicle, EvaluateControls (which covers the logic below for player usercmds) + // will never get called. + if ( pfl.objectiveFailed ) + { + if ( ( gameLocal.time > minRespawnTime && (usercmd.buttons & BUTTON_ATTACK)) || + gameLocal.time > maxRespawnTime ) + { + gameLocal.sessionCommand = "died"; + } + } + + return; + } + + // log movement changes for weapon bobbing effects + if ( usercmd.forwardmove != oldCmd.forwardmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; + acc->dir[1] = acc->dir[2] = 0; + } + + if ( usercmd.rightmove != oldCmd.rightmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; + acc->dir[0] = acc->dir[2] = 0; + } + + // freelook centering + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) { + centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 ); + } + + // if we have an active gui, we will unrotate the view angles as + // we turn the mouse movements into gui events + idUserInterface *gui = ActiveGui(); + if ( gui && gui != focusUI ) { + RouteGuiMouse( gui ); + } + + // set the push velocity on the weapon before running the physics + if ( weapon ) { + weapon->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END + + Move(); + + if ( !g_stopTime.GetBool() ) { + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } else if ( spectating && !noclip ) { + TouchTriggers( &idTrigger_Multi::GetClassType() ); + } + + // not done on clients for various reasons. don't do it on server and save the sound channel for other things + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && dynamicProtectionScale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { + if ( dynamicProtectionScale < 1.0f ) { + dynamicProtectionScale += 0.05f; + } + if ( dynamicProtectionScale > 1.0f ) { + dynamicProtectionScale = 1.0f; + } + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateState(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + } + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeroson / camera view + CalculateRenderView(); + + if ( spectating ) { + UpdateSpectating(); + } else if ( health > 0 && !gameLocal.inCinematic ) { + UpdateWeapon(); + } + + UpdateAir(); + + UpdateHud(); + + UpdatePowerUps(); + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + if( entityNumber == gameLocal.localClientNum ) { + renderEntity.suppressLOD = 1; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + } else { + renderEntity.suppressLOD = 0; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 0; + } + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: get first person flashlight into this + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + + Present(); + + LinkCombat(); + } + + if ( !( thinkFlags & TH_THINK ) ) { + common->DPrintf( "player %d not thinking?\n", entityNumber ); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + common->DPrintf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + common->DPrintf( "%d: enemies\n", num ); + } + + if ( !inBuyZonePrev ) + inBuyZone = false; + + inBuyZonePrev = false; +} + +/* +================= +idPlayer::RouteGuiMouse +================= +*/ +void idPlayer::RouteGuiMouse( idUserInterface *gui ) { + sysEvent_t ev; + const char *command; + + if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) { + ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY ); + command = gui->HandleEvent( &ev, gameLocal.time ); + oldMouseX = usercmd.mx; + oldMouseY = usercmd.my; + } +} + +bool idPlayer::CanZoom( void ) +{ + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + return weapon && weapon->CanZoom(); + } + + return weapon && weapon->CanZoom() && !weapon->IsReloading ( ); +} + +/* +================== +idPlayer::LookAtKiller +================== +*/ +void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) { + idVec3 dir; + + if ( attacker && attacker != this ) { + dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else if ( inflictor && inflictor != this ) { + dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else { + dir = viewAxis[ 0 ]; + } + + idAngles ang( 0, dir.ToYaw(), 0 ); + SetViewAngles( ang ); +} + +/* +============== +idPlayer::Kill +============== +*/ +void idPlayer::Kill( bool delayRespawn, bool nodamage ) { + if ( spectating ) { + SpectateFreeFly( false ); + } else if ( health > 0 ) { + godmode = false; + if ( !g_forceUndying.GetBool() ) { + undying = false; + } + if ( nodamage ) { + ServerSpectate( true ); + forceRespawn = true; + } else { + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + if ( delayRespawn ) { + forceRespawn = false; + int delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + } + } +} + +/* +================== +idPlayer::Killed +================== +*/ +void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + float delay; + + assert( !gameLocal.isClient ); + + // stop taking knockback once dead + fl.noknockback = true; + if ( health < -999 ) { + health = -999; + } + + if ( pfl.dead ) { + pfl.pain = true; + return; + } + +// squirrel: Mode-agnostic buymenus + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP ) { + /// Remove the player's armor + inventory.armor = 0; + + /// Preserve this player's weapons at the state of his death, to be restored on respawn + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = inventory.weapons; + + if ( attacker ) { + idPlayer* killer = NULL; + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + if ( killer == this ) { + // Killed by self + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingSelf", 0 ); + killer->GiveCash( cashAward ); + } + else if ( gameLocal.IsTeamGame() && killer->team == team ) { + // Killed by teammate + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingTeammate", 0 ); + killer->GiveCash( cashAward ); + } else { + // Killed by enemy + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetOpponentKillCashAward(); + killer->GiveCash( cashAward ); + } + } + } + } + } + } +// RITUAL END + + bool noDrop = false; + if ( inflictor + && inflictor->IsType( idTrigger_Hurt::GetClassType() ) + && inflictor->spawnArgs.GetBool( "nodrop" ) ) { + //don't drop weapon or items here, flag auto-returns. + noDrop = true; + } + + if ( !g_testDeath.GetBool() && !gameLocal.isMultiplayer ) { +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); +#else + playerView.Fade( colorBlack, 12000 ); +#endif + } + + + pfl.dead = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + + animator.ClearAllJoints(); + + if ( StartRagdoll() ) { + pm_modelView.SetInteger( 0 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } else { + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + + physicsObj.SetMovementType( PM_DEAD ); + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + + fl.takedamage = true; // can still be gibbed + + if ( weapon ) { // cnicholson: Fix for crash if player dies while in vehicle + weapon->OwnerDied(); // get rid of weapon + if ( !noDrop ) { + DropWeapon( ); // drop the weapon as an item + } + delete weapon; + } + + weapon = NULL; + currentWeapon = -1; + + if ( !g_testDeath.GetBool() ) { + LookAtKiller( inflictor, attacker ); + } + + if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) { + idPlayer *killer = NULL; + int methodOfDeath = MAX_WEAPONS + isTelefragged; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + + lastKiller = killer; + if ( gameLocal.IsTeamGame() && killer->team == team ) { + // don't worry about team killers + lastKiller = NULL; + } + if ( killer == this ) { + // don't worry about yourself + lastKiller = NULL; + } + + if ( health < -20 || killer->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + gibDeath = true; + gibDir = dir; + gibsLaunched = false; + if( gameLocal.isMultiplayer && gameLocal.isListenServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) { + ClientGib( dir ); + } + } + + if ( !isTelefragged ) { + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + } + + if ( methodOfDeath == -1 ) { + methodOfDeath = MAX_WEAPONS + isTelefragged; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( attacker->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + if ( lastImpulseTime > gameLocal.time && lastImpulsePlayer ) { + killer = lastImpulsePlayer; + } + } + + gameLocal.mpGame.PlayerDeath( this, killer, methodOfDeath ); + } else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } + + if ( gameLocal.isMultiplayer && gameLocal.IsFlagGameType() ) { + if ( PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + RemoveClientModel( "mp_ctf_one_flag" ); + } + + if( PowerUpActive( POWERUP_CTF_STROGGFLAG ) || PowerUpActive( POWERUP_CTF_MARINEFLAG ) || PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + idPlayer* killer = (idPlayer*)attacker; + if ( killer != NULL && killer->IsType( idPlayer::GetClassType() ) && killer != this ) { + if ( killer->team != team ) { + // killing the flag carrier gives killer double cash + killer->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingOpponentFlagCarrier", 0 ) ); + } + } + statManager->FlagDropped( this, attacker ); + } + } + + DropPowerups(); + + ClearPowerUps(); + + UpdateVisuals(); + + // AI sometimes needs to respond to having killed someone. + // Note: Would it be better to make this a virtual funciton of... something? + aiManager.RemoveTeammate( this ); + + isChatting = false; +} + +/* +================= +CalcDamagePoints + +Calculates how many health and armor points will be inflicted, but +doesn't actually do anything with them. This is used to tell when an attack +would have killed the player, possibly allowing a "saving throw" +================= +*/ +void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ) { + int damage; + int armorSave; + float pDmgScale; + + damageDef->GetInt( "damage", "20", damage ); + damage = GetDamageForLocation( damage, location ); + + // optional different damage in team games + if ( gameLocal.isMultiplayer && gameLocal.IsTeamGame() && damageDef->GetInt( "damage_team" ) ) { + damage = damageDef->GetInt( "damage_team" ); + } + + idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + switch ( g_skill.GetInteger() ) { + case 0: + damage = ceil(0.80f*(float)damage); + break; + case 2: + damage *= 1.7f; + break; + case 3: + damage *= 3.5f; + break; + default: + //damage *= 1.1f; reverted to 1.0 for default damage... as per Biessman's request. + break; + } + } + } + + damage = ceil(damageScale*(float)damage); + + pDmgScale = damageDef->GetFloat( "playerScale", "1" ); + damage = ceil(pDmgScale*(float)damage); + + // check for completely getting out of the damage + if ( !damageDef->GetBool( "noGod" ) ) { + // check for godmode + if ( godmode ) { + godmodeDamage += damage; + damage = 0; + } + } + + // reduce damage by handicap, except to self + if( player && player != this ) { + damage = ceil(player->handicap*(float)damage); + } + + // save some from armor + if ( !damageDef->GetBool( "noArmor" ) ) { + float armor_protection; + + armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat(); + armorSave = ceil( damage * armor_protection ); + if ( armorSave >= inventory.armor ) { + armorSave = inventory.armor; + } + + if ( !damage ) { + armorSave = 0; + } else if ( armorSave >= damage ) { + armorSave = damage - 1; + damage = 1; + } else { + damage -= armorSave; + } + } else { + armorSave = 0; + } + + // check for team damage + if ( gameLocal.IsTeamGame() + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team ) { + damage = 0; + } + + *health = damage; + *armor = armorSave; +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space + +damageDef an idDict with all the options for damage effects + +inflictor, attacker, dir, and point can be NULL for environmental effects +============ +*/ +void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + idVec3 kick; + int damage; + int armorSave; + int knockback; + idVec3 damage_from; + float attackerPushScale; + + float modifiedDamageScale = damageScale; + + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + modifiedDamageScale *= ( 1.0f + gameLocal.GetDifficultyModifier() ); + } + } + + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, modifiedDamageScale, location ); + return; + } + + // damage is only processed on server + if ( gameLocal.isClient ) { + return; + } + + if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) { + // If in vehicle let it know that something is trying to hurt the invisible player + if ( IsInVehicle ( ) ) { + const idDict *damageDict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDict ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // If the damage def is marked as a hazard then issue a warning to the vehicle + if ( damageDict->GetBool ( "hazard", "0" ) ) { + vehicleController.GetVehicle()->IssueHazardWarning ( ); + } + } + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + // MCG: player doesn't take friendly fire damage, except from self! + if ( !gameLocal.isMultiplayer && attacker != this ) { + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == team ) { + return; + } + } + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->dict.GetBool( "ignore_player" ) ) { + return; + } + + if ( damageDef->dict.GetBool( "lightning_damage_effect" ) ) { + lightningEffects = 0; + lightningNextTime = gameLocal.GetTime(); + } + + // We pass in damageScale, because this function calculates a modified damageScale + // based on g_skill, and we don't want to compensate for skill level twice. + CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave ); + + // + // determine knockback + // + damageDef->dict.GetInt( "knockback", "0", knockback ); + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + damageDef->dict.GetInt( "knockback_team", va( "%d", knockback ), knockback ); + } + + knockback *= damageScale; + + if ( knockback != 0 && !fl.noknockback ) { + if ( !gameLocal.isMultiplayer && attacker == this ) { + //In SP, no knockback from your own stuff + knockback = 0; + } else { + if ( attacker != this ) { + attackerPushScale = 1.0f; + } else { + // since default attackerDamageScale is 0.5, default attackerPushScale should be 2 + damageDef->dict.GetFloat( "attackerPushScale", "2", attackerPushScale ); + } + + kick = dir; + + kick.Normalize(); + kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick ); + + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) ); + } + } + + if ( damageDef->dict.GetBool( "burn" ) ) { + StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL ); + } else if ( damageDef->dict.GetBool( "no_air" ) ) { + if ( !armorSave && health > 0 ) { + StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + + // give feedback on the player view and audibly when armor is helping + inventory.armor -= armorSave; + + if ( g_debugDamage.GetInteger() ) { + gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n", + entityNumber, health, damage, armorSave ); + } + + // move the world direction vector to local coordinates + ClientDamageEffects( damageDef->dict, dir, damage ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + + if( gameLocal.isMultiplayer ) { + idEntity* attacker = NULL; + + int methodOfDeath = -1; + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + attacker = static_cast(inflictor)->GetOwner(); + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + attacker = inflictor; + } + + statManager->Damage( attacker, this, methodOfDeath, damage ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->dict.GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->dict.GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->dict.GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->dict.GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->dict.GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->dict.GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + // do the damage + if ( damage > 0 ) { + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) { + if ( gameLocal.time > lastDmgTime + 500 && dynamicProtectionScale > 0.25f ) { + dynamicProtectionScale -= 0.05f; + } + } + + if ( dynamicProtectionScale > 0.0f ) { + damage *= dynamicProtectionScale; + } + } + + if ( damage < 1 ) { + damage = 1; + } + + int oldHealth = health; + health -= damage; + + GAMELOG_ADD ( va("player%d_damage_taken", entityNumber ), damage ); + GAMELOG_ADD ( va("player%d_damage_%s", entityNumber, damageDefName), damage ); + + // Check undying mode + if ( !damageDef->dict.GetBool( "noGod" ) ) { + if ( undying ) { + if ( health < 1 ) { + health = 1; + } + } + } + + if ( health <= 0 ) { + + if ( health < -999 ) { + health = -999; + } + + isTelefragged = damageDef->dict.GetBool( "telefrag" ); + + lastDmgTime = gameLocal.time; + + Killed( inflictor, attacker, damage, dir, location ); + + if ( oldHealth > 0 ) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::Type ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, &damageDef->dict, pushScale ); + } + } else { + // force a blink + blink_time = 0; + + // let the anim script know we took damage + pfl.pain = Pain( inflictor, attacker, damage, dir, location ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + } + } else { + // don't accumulate impulses + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } + + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = location; +} + +/* +===================== +idPlayer::CanPlayImpactEffect +===================== +*/ +bool idPlayer::CanPlayImpactEffect( idEntity* attacker, idEntity* target ) +{ + if ( !gameLocal.isMultiplayer && attacker->IsType( idAI::GetClassType()) && target->IsType( idPlayer::GetClassType())) + { + // don't display impact effects when marines on our team shoot us... + idPlayer *player = static_cast( target ); + idAI *ai = static_cast( attacker ); + if ( player->team == ai->team ) + { + return false; + } + } + + return idAFEntity_Base::CanPlayImpactEffect( attacker, target ); +} + +/* +===================== +idPlayer::AddDamageEffect +===================== +*/ +void idPlayer::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if( gameLocal.isMultiplayer ) { + if( ! cvarSystem->GetCVarBool("si_teamDamage") && inflictor && inflictor->IsType( idPlayer::GetClassType() ) && gameLocal.IsTeamGame() && ((idPlayer*)inflictor)->team == team ) { + return; + } + } + + idActor::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +=========== +idPlayer::Teleport +============ +*/ +void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + idVec3 org; + + if ( weapon ) { + weapon->LowerWeapon(); + } + + if ( !spectating ) { + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + } else { + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) + idVec3( 0, 0, SPECTATE_RAISE ) ); + } + if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) { + SetOrigin( org ); + } + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + GetPhysics()->SetLinearVelocity( vec3_origin ); + SetViewAngles( angles ); + + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + leanVelocity = vec3_zero; + + if ( gameLocal.isMultiplayer ) { + playerView.Flash( colorWhite, 140 ); + } + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + UpdateVisuals(); + + teleportEntity = destination; + + if ( !gameLocal.isClient && !noclip && !spectating ) { + if ( gameLocal.isMultiplayer ) { + // kill anything at the new position or mark for kill depending on immediate or delayed teleport + gameLocal.KillBox( this, destination != NULL ); + } else { + // kill anything at the new position + gameLocal.KillBox( this, true ); + } + } +} + +/* +==================== +idPlayer::SetPrivateCameraView +==================== +*/ +void idPlayer::SetPrivateCameraView( idCamera *camView ) { + privateCameraView = camView; + if ( camView ) { + StopFiring(); + Hide(); + } else { + if ( !spectating ) { + Show(); + } + } +} + +/* +==================== +idPlayer::DefaultFov + +Returns the base FOV +==================== +*/ +float idPlayer::DefaultFov( void ) const { + float fov; + + fov = g_fov.GetFloat(); + if ( gameLocal.isMultiplayer ) { + if ( fov < 90.0f ) { + return 90.0f; + } else if ( fov > 175.0f ) { + return 175.0f; + } + } + + return fov; +} + +/* +==================== +idPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +float idPlayer::CalcFov( bool honorZoom ) { + float fov; + + if ( fxFov ) { + return DefaultFov() + 10.0f + idMath::Cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f; + } + + if ( influenceFov ) { + return influenceFov; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } else { + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } + + // bound normal viewsize + if ( fov < 1 ) { + fov = 1; + } else if ( fov > 179 ) { + fov = 179; + } + + return fov; +} + +/* +============== +idPlayer::GunTurningOffset + +generate a rotational offset for the gun based on the view angle +history in loggedViewAngles +============== +*/ +idAngles idPlayer::GunTurningOffset( void ) { + idAngles a; + + a.Zero(); + + if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) { + return a; + } + + idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles av, base; + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale, weaponAngleOffsetMax; + + weapon->GetAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax ); + + av = current; + + // calcualte this so the wrap arounds work properly + for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) { + idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles delta = a2 - current; + + if ( delta[1] > 180 ) { + delta[1] -= 360; + } else if ( delta[1] < -180 ) { + delta[1] += 360; + } + + av += delta * ( 1.0f / weaponAngleOffsetAverages ); + } + + a = ( av - current ) * weaponAngleOffsetScale; + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( a[i] < -weaponAngleOffsetMax ) { + a[i] = -weaponAngleOffsetMax; + } else if ( a[i] > weaponAngleOffsetMax ) { + a[i] = weaponAngleOffsetMax; + } + } + + return a; +} + +/* +============== +idPlayer::GunAcceleratingOffset + +generate a positional offset for the gun based on the movement +history in loggedAccelerations +============== +*/ +idVec3 idPlayer::GunAcceleratingOffset( void ) { + idVec3 ofs; + float weaponOffsetTime; + float weaponOffsetScale; + + ofs.Zero(); + + weapon->GetTimeOffsets( &weaponOffsetTime, &weaponOffsetScale ); + + int stop = currentLoggedAccel - NUM_LOGGED_ACCELS; + if ( stop < 0 ) { + stop = 0; + } + for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) { + loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)]; + + float f; + float t = gameLocal.time - acc->time; + if ( t >= weaponOffsetTime ) { + break; // remainder are too old to care about + } + + f = t / weaponOffsetTime; + f = ( idMath::Cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f; + ofs += f * weaponOffsetScale * acc->dir; + } + + return ofs; +} + +/* +============== +idPlayer::CalculateViewWeaponPos + +Calculate the bobbing position of the view weapon +============== +*/ +void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) { + float scale; + float fracsin; + idAngles angles; + int delta; + + // CalculateRenderView must have been called first + const idVec3 &viewOrigin = firstPersonViewOrigin; + const idMat3 &viewAxis = firstPersonViewAxis; + + // the constant was -0.2 in quake3, but -.1 seems closest to the same visual effect in quake4 + float fovOffset = 0; + float curfov = g_fov.GetFloat(); + if ( g_weaponFovEffect.GetBool() && curfov > 90.0f ) + fovOffset = -0.1f * ( curfov - 90.0f ); + + // these cvars are just for hand tweaking before moving a value to the weapon def + idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() + fovOffset ); + + const idPlayer* player = gameLocal.GetLocalPlayer(); + if (player && (this == player || player->spectating && player->spectator == this->entityNumber) ) { + gunpos += weapon->GetViewModelOffset(); + } else { + gunpos = weapon->GetViewModelOffset(); + } + + + // as the player changes direction, the gun will take a small lag + idVec3 gunOfs = GunAcceleratingOffset(); + origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis; + + // on odd legs, invert some angles + if ( noclip || 1 ) { + scale = 0; + } else if ( bobCycle & 128 ) { + scale = -xyspeed; + } else { + scale = xyspeed; + } + + // gun angles from bobbing + angles.roll = scale * bobfracsin * 0.005f + g_gun_roll.GetFloat(); + angles.yaw = scale * bobfracsin * 0.01f + g_gun_yaw.GetFloat(); + angles.pitch = xyspeed * bobfracsin * 0.005f + g_gun_pitch.GetFloat(); + + angles += weapon->GetViewModelAngles(); + + // gun angles from turning + if ( gameLocal.isMultiplayer ) { + idAngles offset = GunTurningOffset(); + offset *= g_mpWeaponAngleScale.GetFloat(); + angles += offset; + } else { + angles += GunTurningOffset(); + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + +// RAVEN BEGIN +// abahr: when looking down, really large deflections cause back of weapons to show + float landChangeFrac = idMath::Lerp( 0.25f, 0.05f, viewAngles.ToForward() * gravity); +// RAVEN ABAHR + + // drop the weapon when landing after a jump / fall + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { +// RAVEN BEGIN +// abahr: changed to use landChangeFrac + origin -= gravity * ( landChange*landChangeFrac * delta / LAND_DEFLECT_TIME ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin -= gravity * ( landChange*landChangeFrac * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME ); +// RAVEN END + } + + // speed sensitive idle drift + if ( !noclip ) { + scale = xyspeed * 0.5f + 40.0f; + fracsin = scale * idMath::Sin( MS2SEC( gameLocal.time ) ) * 0.01f; + angles.roll += fracsin; + angles.yaw += fracsin; + angles.pitch += fracsin; + } + + axis = angles.ToMat3() * viewAxis; +} + +/* +=============== +idPlayer::OffsetThirdPersonVehicleView +=============== +*/ +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world +void idPlayer::OffsetThirdPersonVehicleView( bool clip ) { +// RAVEN END + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + idVec3 origin; + idAngles angles, angles2; + idEntity* vehicle; + + assert ( IsInVehicle ( ) ); + + vehicle = vehicleController.GetVehicle(); + + origin = vehicle->GetRenderEntity()->origin; + angles = vehicle->GetRenderEntity()->axis.ToAngles(); + + angles.yaw += pm_thirdPersonAngle.GetFloat(); + angles.pitch += 25.0f; + +// angles.pitch += viewAngles.pitch; +// angles.yaw += viewAngles.yaw; + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * 8.0f; +// RAVEN END + + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + float speed = vehicle->GetPhysics()->GetLinearVelocity() * + vehicle->GetPhysics()->GetAxis()[0]; + + speed = idMath::Fabs( speed ); + speed *= pm_vehicleCameraSpeedScale.GetFloat(); + if( speed > pm_vehicleCameraScaleMax.GetFloat() ) + { + speed = pm_vehicleCameraScaleMax.GetFloat(); + } + + vehicleCameraDist += ( MS2SEC( gameLocal.GetMSec() ) * ( ( pm_vehicleCameraMinDist.GetFloat() + speed ) - vehicleCameraDist ) ); + + view -= vehicleCameraDist * renderView->viewaxis[ 0 ]; + +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + if ( clip ) { + // 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 + const idVec3 clip_mins( -4.0f, -4.0f, -4.0f ); + const idVec3 clip_maxs( 4.0f, 4.0f, 4.0f ); + const idBounds clip_bounds( clip_mins, clip_maxs ); + idClipModel clipBounds( clip_bounds );// We clip when using a tram gun in the tram car +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + if ( trace.fraction != 1.0 ) + { + view = trace.endpos; +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32; + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + view = trace.endpos; + } + } +// RAVEN END + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) + { + focusDist = 1; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::OffsetThirdPersonView +=============== +*/ +void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + float forwardScale, sideScale; + idVec3 origin; + idAngles angles; + idMat3 axis; + idBounds bounds; + + angles = viewAngles; + GetViewPos( origin, axis ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + focusPoint.z += height; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * (8.0f + height); +// RAVEN END + + angles.pitch *= 0.5f; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale ); + view -= range * forwardScale * renderView->viewaxis[ 0 ]; + view += range * sideScale * renderView->viewaxis[ 1 ]; + + if ( clip ) { + // 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 + bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + if ( trace.fraction != 1.0f ) { + view = trace.endpos; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32.0f; +// RAVEN END + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + view = trace.endpos; + } + } + + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1.0f ) { + focusDist = 1.0f; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + angles.yaw -= angle; + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::GetEyePosition +=============== +*/ +idVec3 idPlayer::GetEyePosition( void ) const { + idVec3 org; + + if ( WantSmoothing() ) { + org = predictedOrigin; + } else { + org = GetPhysics()->GetOrigin(); + } + + return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +=============== +idPlayer::GetViewPos +=============== +*/ +void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + idAngles angles; + + // if dead, fix the angle and don't add any kick + if ( health <= 0 ) { + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else if ( IsInVehicle ( ) ) { + vehicleController.GetEyePosition ( origin, axis ); + + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + + origin += shakeOffset; + axis = (shakeAngleOffset + playerView.AngleOffset()).ToMat3() * axis; + } else { + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + origin = GetEyePosition() + viewBob + shakeOffset; + angles = viewAngles + viewBobAngles + shakeAngleOffset + playerView.AngleOffset(); + + axis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + // adjust the origin based on the camera nodal distance (eye distance from neck) + origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat(); + origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat(); + } +} + +/* +=============== +idPlayer::CalculateFirstPersonView +=============== +*/ +void idPlayer::CalculateFirstPersonView( void ) { + if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) { + // Displays the view from the point of view of the "camera" joint in the player model + + idMat3 axis; + idVec3 origin; + idAngles ang; + + ang = viewBobAngles + playerView.AngleOffset(); + ang.yaw += viewAxis[ 0 ].ToYaw(); + + jointHandle_t joint = animator.GetJointHandle( "camera" ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob; + firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis(); + } else { + // offset for local bobbing and kicks + GetViewPos( firstPersonViewOrigin, firstPersonViewAxis ); + } +} + +/* +================== +idPlayer::GetRenderView + +Returns the renderView that was calculated for this tic +================== +*/ +renderView_t *idPlayer::GetRenderView( void ) { + return renderView; +} + +/* +================== +idPlayer::SmoothenRenderView + +various situations where the view angles need smoothing: + +demo replay: + On a slow client with low fps multiple game frames are run in quick succession + inbetween rendered frames. As a result the usercmds are not recorded at fixed + time intervals but in small bursts. This routine interpolates the view angles + based on the real time at which the usercmds were recorded to make a demo + recorded on a slow client play back smoothly on a fast client. + +spectate follow? +================== +*/ +void idPlayer::SmoothenRenderView( bool firstPerson ) { + int d1, d2; + idAngles angles, anglesDelta, newAngles; + + if ( gameLocal.IsServerDemoPlaying() ) { + + d1 = usercmd.gameTime - demoViewAngleTime; + if ( d1 < 0 ) { + return; + } + d2 = usercmd.realTime - demoViewAngleTime; + if ( d2 <= 0 ) { + return; + } + if ( d1 >= d2 ) { + return; + } + + angles = renderView->viewaxis.ToAngles(); + + anglesDelta = angles - demoViewAngles; + anglesDelta.Normalize180(); + newAngles = demoViewAngles + ( (float) d1 / d2 ) * anglesDelta; + renderView->viewaxis = newAngles.ToMat3(); + + if ( usercmd.gameTime + gameLocal.msec > usercmd.realTime ) { + demoViewAngleTime = usercmd.realTime; + demoViewAngles = angles; + } + + if ( firstPerson ) { + // make sure the view weapon moves smoothly + firstPersonViewAxis = renderView->viewaxis; + } + } +} + +/* +================== +idPlayer::CalculateRenderView + +create the renderView for the current tic +================== +*/ +void idPlayer::CalculateRenderView( void ) { + int i; + float range; + + if ( !renderView ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + // copy global shader parms + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + renderView->time = gameLocal.time; + + // calculate size of 3D view + renderView->x = 0; + renderView->y = 0; + renderView->width = SCREEN_WIDTH; + renderView->height = SCREEN_HEIGHT; + renderView->viewID = 0; + + // check if we should be drawing from a camera's POV + if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) { + // get origin, axis, and fov + if ( privateCameraView ) { + privateCameraView->GetViewParms( renderView ); + } else { + gameLocal.GetCamera()->GetViewParms( renderView ); + } + } else { + bool cameraIsSet = false; + + // First try out any camera views that can possibly fail. + if( !cameraIsSet ){ + if ( g_stopTime.GetBool() ) { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + + if ( !pm_thirdPerson.GetBool() ) { + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } else if ( pm_thirdPerson.GetBool() && IsInVehicle ( ) ) { +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + OffsetThirdPersonVehicleView( pm_thirdPersonClip.GetBool() ); +// RAVEN END + SmoothenRenderView( false ); + } else if ( pm_thirdPerson.GetBool() ) { + OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() ); + SmoothenRenderView( false ); + } else if ( pm_thirdPersonDeath.GetBool() ) { + range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f; + OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false ); + SmoothenRenderView( false ); + } else { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } + + // field of view + gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y ); + + } + + if ( renderView->fov_y == 0 ) { + common->Error( "renderView->fov_y == 0" ); + } + + if ( g_showviewpos.GetBool() ) { + gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() ); + } +} + +/* +================== +idPlayer::Event_EnableTarget +================== +*/ +void idPlayer::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +================== +idPlayer::Event_DisableTarget +================== +*/ +void idPlayer::Event_DisableTarget ( void ) { + fl.notarget = true; +} + +/* +================== +idPlayer::Event_GetViewPos +================== +*/ +void idPlayer::Event_GetViewPos( void ) { + idVec3 viewOrigin; + idMat3 viewAxis; + + GetViewPos(viewOrigin, viewAxis); + idThread::ReturnVector( viewOrigin ); +} + +/* +================== +idPlayer::Event_FinishHearingLoss +================== +*/ +void idPlayer::Event_FinishHearingLoss ( float fadeTime ) { + if ( fadeTime <= 0.0f ) { + StopSound ( SND_CHANNEL_DEMONIC, false ); + pfl.hearingLoss = false; + } else { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, 0.0f ); + } +} + +// RAVEN BEGIN +// twhitaker: added the event +/* +============= +idPlayer::Event_ApplyImpulse +============= +*/ +void idPlayer::Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ) { + GetPhysics()->ApplyImpulse( 0, point, impulse ); +} + +// mekberg: added Event_EnableObjectives +/* +============= +idPlayer::Event_EnableObjectives +============= +*/ +void idPlayer::Event_EnableObjectives ( void ) { + objectivesEnabled = true; +} + +// mekberg: added Event_DisableObjectives +/* +============= +idPlayer::Event_DisableObjectives +============= +*/ +void idPlayer::Event_DisableObjectives ( void ) { + // if it's open, it should be closed + if (objectiveSystemOpen ) { + ToggleObjectives(); + } + objectivesEnabled = false; +} + +// mekberg: added Event_AllowNewObjectives +void idPlayer::Event_AllowNewObjectives ( void ) { + showNewObjectives = true; +} + +// mekberg: added sethealth +/* +============= +idPlayer::Event_SetHealth +============= +*/ +void idPlayer::Event_SetHealth( float newHealth ) { + health = idMath::ClampInt( 1 , inventory.maxHealth, newHealth ); +} +/* +============= +idPlayer::Event_SetArmor +============= +*/ +void idPlayer::Event_SetArmor( float newArmor ) { + inventory.armor = idMath::ClampInt( 0 , inventory.maxarmor, newArmor ); +} + +/* +============= +idPlayer::Event_SetExtraProjPassEntity +============= +*/ +void idPlayer::Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ) { + extraProjPassEntity = _extraProjPassEntity; +} +// RAVEN END + +/* +============= +idPlayer::AddProjectilesFired +============= +*/ +void idPlayer::AddProjectilesFired( int count ) { + numProjectilesFired += count; +} + +/* +============= +idPlayer::AddProjectileHites +============= +*/ +void idPlayer::AddProjectileHits( int count ) { + numProjectileHits += count; +} + +/* +============= +idPlayer::TriggerHitSound +============= +*/ +void idPlayer::TriggerHitSound( bool armor ) { + if ( gameLocal.framenum <= lastHitFrame ) { + return; + } + + lastHitFrame = gameLocal.framenum; + lastHitArmor = armor; + + idUserInterface *cursor = idPlayer::cursor; + bool spectated = false; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + spectated = true; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "weaponHit" ); + } + + // spectated so we get blips for a client we're following + // localClientNum check so listen server plays only for local player + if ( spectated || IsLocalClient() ) { + const char* sound = NULL; + + if ( armor ) { + spawnArgs.GetString( "snd_armorHit", "", &sound ); + } + if ( sound == NULL ) { + spawnArgs.GetString( "snd_weaponHit", "", &sound ); + } + if ( sound != NULL ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + + if ( aimClientNum != -1 ) { + if ( mphud ) { + mphud->HandleNamedEvent( "aim_hit" ); + } + } + + } +} + +/* +============= +idPlayer::SetInfluenceLevel +============= +*/ +void idPlayer::SetInfluenceLevel( int level ) { + if ( level != influenceActive ) { + if ( level ) { + for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + ent->PostEventMS( &EV_Remove, 0 ); + } + } + if ( weaponEnabled && weapon ) { + weapon->EnterCinematic(); + } + } else { + physicsObj.SetLinearVelocity( vec3_origin ); + if ( weaponEnabled && weapon ) { + weapon->ExitCinematic(); + } + } + influenceActive = level; + } +} + +/* +============= +idPlayer::SetInfluenceView +============= +*/ +void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) { + influenceMaterial = NULL; + influenceEntity = NULL; + influenceSkin = NULL; + if ( mtr && *mtr ) { + influenceMaterial = declManager->FindMaterial( mtr ); + } + if ( skinname && *skinname ) { + influenceSkin = declManager->FindSkin( skinname ); + if ( head.GetEntity() ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + UpdateVisuals(); + } + influenceRadius = radius; + if ( radius > 0.0f ) { + influenceEntity = ent; + } +} + +/* +============= +idPlayer::SetInfluenceFov +============= +*/ +void idPlayer::SetInfluenceFov( float fov ) { + influenceFov = fov; +} + +/* +================ +idPlayer::OnLadder +================ +*/ +bool idPlayer::OnLadder( void ) const { + return physicsObj.OnLadder(); +} + +/* +================== +idPlayer::Event_GetButtons +================== +*/ +void idPlayer::Event_GetButtons( void ) { + idThread::ReturnInt( usercmd.buttons ); +} + +/* +================== +idPlayer::Event_GetMove +================== +*/ +void idPlayer::Event_GetMove( void ) { + idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove ); + idThread::ReturnVector( move ); +} + +/* +================ +idPlayer::Event_GetViewAngles +================ +*/ +void idPlayer::Event_GetViewAngles( void ) { + idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) ); +} + +/* +================ +idPlayer::Event_SetViewAngles +================ +*/ +void idPlayer::Event_SetViewAngles( const idVec3 & vec ) { + idAngles ang; + ang.Set( vec.z, vec.y, vec.x ); + SetViewAngles( ang ); +} + +/* +================== +idPlayer::Event_StopFxFov +================== +*/ +void idPlayer::Event_StopFxFov( void ) { + fxFov = false; +} + +/* +================== +idPlayer::StartFxFov +================== +*/ +void idPlayer::StartFxFov( float duration ) { + fxFov = true; + PostEventSec( &EV_Player_StopFxFov, duration ); +} + +/* +================== +idPlayer::Event_EnableWeapon +================== +*/ +void idPlayer::Event_EnableWeapon( void ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", 0 ); + Give( "weapon", spawnArgs.GetString( va( "def_weapon%d", 0 ) ) ); + hiddenWeapon = false; + weaponEnabled = true; + if ( weapon ) { + weapon->ExitCinematic(); + } + ShowCrosshair(); +} + +/* +================== +idPlayer::Event_DisableWeapon +================== +*/ +void idPlayer::Event_DisableWeapon( void ) { + hiddenWeapon = true; + weaponEnabled = false; + if ( weapon ) { + weapon->EnterCinematic(); + } + HideCrosshair(); +} + +/* +================== +idPlayer::Event_GetCurrentWeapon +================== +*/ +void idPlayer::Event_GetCurrentWeapon( void ) { + if ( currentWeapon >= 0 ) { + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ) ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_GetPreviousWeapon +================== +*/ +void idPlayer::Event_GetPreviousWeapon( void ) { + if ( previousWeapon >= 0 ) { + int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon; + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", pw) ) ); + } else { + idThread::ReturnString( "def_weapon0" ); + } +} + +/* +================== +idPlayer::Event_SelectWeapon +================== +*/ +void idPlayer::Event_SelectWeapon( const char *weaponName ) { + int i; + int weaponNum; + + if ( gameLocal.isClient ) { + gameLocal.Warning( "Cannot switch weapons from script in multiplayer" ); + return; + } + + weaponNum = -1; + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + + if ( !inventory.HasAmmo( weap ) ) { + return; + } + weaponNum = i; + break; + } + } + } + + if ( weaponNum < 0 ) { + gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName ); + return; + } + + hiddenWeapon = false; + idealWeapon = weaponNum; + + UpdateHudWeapon(); +} + +/* +================== +idPlayer::Event_GetAmmoData +================== +*/ +void idPlayer::Event_GetAmmoData( const char *ammoClass ) { + + idVec3 weaponAmmo; + + //ammo vector is this: current ammo count, max ammo count, and % + weaponAmmo.x = inventory.ammo[ inventory.AmmoIndexForAmmoClass( ammoClass) ]; + weaponAmmo.y = inventory.MaxAmmoForAmmoClass( this, ammoClass ); + + if( weaponAmmo.y == 0) + weaponAmmo.z = 0; + else + weaponAmmo.z = (float)(weaponAmmo.x / weaponAmmo.y); + + idThread::ReturnVector( weaponAmmo); +} + +/* +================== +idPlayer::Event_RefillAmmo +================== +*/ +void idPlayer::Event_RefillAmmo( void ) { + int a; + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + a = inventory.AmmoIndexForWeaponIndex( i ); + inventory.ammo[ a ] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex( a ) ); + } + } + } +} + +/* +================== +idPlayer::Event_AllowFallDamage +================== +*/ +void idPlayer::Event_AllowFallDamage( int toggle ) { + if( toggle ) { + pfl.noFallingDamage = false; + } else { + pfl.noFallingDamage = true; + } + +} +/* +================== +idPlayer::Event_GetWeaponEntity +================== +*/ +void idPlayer::Event_GetWeaponEntity( void ) { + idThread::ReturnEntity( weaponViewModel ); +} + +/* +================== +idPlayer::Event_HideDatabaseEntry +================== +*/ +void idPlayer::Event_HideDatabaseEntry ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "closeDatabaseEntry" ); + } +} + +/* +================== +idPlayer::Event_ZoomIn +================== +*/ +void idPlayer::Event_ZoomIn ( void ) { + float currentFov; + float t; + + if ( zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming in while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( weapon && this->weaponEnabled ) { + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::Event_ZoomOut +================== +*/ +void idPlayer::Event_ZoomOut ( void ) { + float t; + float currentFov; + + if ( !zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else { + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::TeleportDeath +================== +*/ +void idPlayer::TeleportDeath( int killer ) { + teleportKiller = killer; +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ExitTeleporter( void ) { + idEntity *exitEnt; + float pushVel; + + // verify and setup + exitEnt = teleportEntity; + if ( !exitEnt ) { + common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber ); + return; + } + + pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" ); + + if ( gameLocal.isServer && !spectating ) { + ServerSendInstanceEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 ); + } + + SetPrivateCameraView( NULL ); + // setup origin and push according to the exit target + SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() ); + physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel ); + physicsObj.ClearPushedVelocity( ); + // teleport fx + playerView.Flash( colorWhite, 120 ); + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + UpdateVisuals(); + + gameLocal.PlayEffect( spawnArgs, "fx_teleport", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + + StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( teleportKiller != -1 ) { + // we got killed while being teleported + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + teleportKiller = -1; + } else { + // kill anything that would have waited at teleport exit + gameLocal.KillBox( this ); + } + teleportEntity = NULL; +} + +/* +=============== +idPlayer::Event_DamageOverTimeEffect +=============== +*/ +void idPlayer::Event_DamageOverTimeEffect( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), GetWeaponViewModel(), interval ); + RV_POP_HEAP(); + + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +/* +=============== +idPlayer::LocalClientPredictionThink +=============== +*/ +void idPlayer::LocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ IsFakeClient() ? MAX_CLIENTS : entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + + if ( IsInVehicle( ) ) { + vehicleController.SetInput( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } + + AdjustSpeed(); + + UpdateViewAngles(); + +/* +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END +*/ + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + Move(); + + // predict collisions with items + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers( &idItem::GetClassType() ); + } else if ( spectating && !noclip ) { + // predict teleports for specs + TouchTriggers( &idTrigger_Multi::GetClassType() ); + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + if ( IsFakeClient() && spectating ) { + UpdateSpectating(); + } + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + UpdateHud(); + + if ( gameLocal.isNewFrame ) { + UpdatePowerUps(); + } + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +=============== +idPlayer::NonLocalClientPredictionThink +=============== +*/ +#define LIMITED_PREDICTION 1 + +void idPlayer::NonLocalClientPredictionThink( void ) { + assert( !IsFakeClient() ); + + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + //jshepard: added this to make sure clients can see other clients and the host switching weapons + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + +#if !LIMITED_PREDICTION + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } +#endif + + AdjustSpeed(); + + UpdateViewAngles(); + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + // NOTE: only running on new frames causes prediction errors even when the input does not change! + if ( gameLocal.isNewFrame ) { + Move(); + } else { + PredictionErrorDecay(); + } + } + +#if defined( _XENON ) || !LIMITED_PREDICTION + // update GUIs, Items, and character interactions + UpdateFocus(); +#endif + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + if ( gameLocal.isLastPredictFrame ) { + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + UpdateHud(); + UpdatePowerUps(); + } + +//#if !LIMITED_PREDICTION + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); +//#endif + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +================ +idPlayer::ClientPredictionThink +================ +*/ +void idPlayer::ClientPredictionThink( void ) { + // common code for both the local & non local clients + if ( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + if ( entityNumber == gameLocal.GetDemoFollowClient() ) { + LocalClientPredictionThink(); + return; + } + + if ( IsLocalClient() ) { + LocalClientPredictionThink(); + return; + } + + assert( gameLocal.localClientNum >= 0 ); + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p && p->spectating && p->spectator == entityNumber ) { + LocalClientPredictionThink(); + return; + } + + NonLocalClientPredictionThink(); +} + +/* +================ +idPlayer::GetMasterPosition +================ +*/ +bool idPlayer::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + if( !IsInVehicle() ) { + return idActor::GetMasterPosition( masterOrigin, masterAxis ); + } + + vehicleController.GetDriverPosition( masterOrigin, masterAxis ); + return true; +} + +/* +=============== +idPlayer::PredictionErrorDecay +=============== +*/ +void idPlayer::PredictionErrorDecay( void ) { + if ( predictedUpdated ) { + return; + } + + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + return; + } + + if ( gameLocal.framenum >= predictedFrame ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + + if ( gameLocal.framenum == predictedFrame ) { + + predictionOriginError = predictedOrigin - renderOrigin; + predictionAnglesError = predictedAngles - viewAngles; + predictionAnglesError.Normalize180(); + predictionErrorTime = gameLocal.time; + + // skip doing error decay if the error is too high to be due to misprediction + // this would take care of stray cases where we fail to mark that no decaying should happen + if ( predictionOriginError.Length() > 64.0f ) { + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( 1.0f, 0.0f, 1.0f, colorRed ); + } + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + predictedFrame = gameLocal.framenum; + return; + } + + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( predictionOriginError.Length(), 0.0f, 100.0f, colorGreen ); + renderSystem->DebugGraph( predictionAnglesError.Length(), 0.0f, 180.0f, colorBlue ); + } + } + + int t = gameLocal.time - predictionErrorTime; + float f = ( net_predictionErrorDecay.GetFloat() - t ) / net_predictionErrorDecay.GetFloat(); + if ( f > 0.0f && f < 1.0f ) { + predictedOrigin = renderOrigin + f * predictionOriginError; + predictedAngles = viewAngles + f * predictionAnglesError; + predictedAngles.Normalize180(); + } else { + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + } + + predictedFrame = gameLocal.framenum; + + } + + viewAngles = predictedAngles; + // adjust them now so they are right for the bound objects ( head and weapon ) + AdjustBodyAngles(); + + predictedUpdated = true; +} + +/* +=============== +idPlayer::WantSmoothing +=============== +*/ +bool idPlayer::WantSmoothing( void ) const { + if ( !gameLocal.isClient ) { + return false; + } + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + return false; + } + return true; +} + +/* +================ +idPlayer::GetPhysicsToVisualTransform +================ +*/ +bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + if ( vehicleController.IsDriving() ) { + vehicleController.GetDriverPosition( origin, axis ); + origin.Zero(); + return true; + } + + PredictionErrorDecay(); + + // smoothen the rendered origin and angles of other clients + if ( gameLocal.framenum >= predictedFrame && WantSmoothing() ) { + + axis = idAngles( 0.0f, predictedAngles.yaw, 0.0f ).ToMat3(); + origin = ( predictedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose(); + + } else { + + axis = viewAxis; + origin = modelOffset; + + } + + return true; +} + +/* +================ +idPlayer::GetPhysicsToSoundTransform +================ +*/ +bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idCamera *camera; + + if ( privateCameraView ) { + camera = privateCameraView; + } else { + camera = gameLocal.GetCamera(); + } + + if ( camera ) { + renderView_t view; + + memset( &view, 0, sizeof( view ) ); + camera->GetViewParms( &view ); + origin = view.vieworg; + axis = view.viewaxis; + return true; + } else { + return idActor::GetPhysicsToSoundTransform( origin, axis ); + } +} + +/* +================ +idPlayer::WriteToSnapshot +================ +*/ +void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + assert( !IsFakeClient() ); + + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + msg.WriteShort( health ); + msg.WriteByte( inventory.armor ); + msg.WriteBits( lastDamageDef, gameLocal.entityDefBits ); + msg.WriteDir( lastDamageDir, 9 ); + msg.WriteShort( lastDamageLocation ); + msg.WriteBits( idealWeapon, -idMath::BitsForInteger( MAX_WEAPONS ) ); + msg.WriteBits( inventory.weapons, MAX_WEAPONS ); + msg.WriteBits( weaponViewModel.GetSpawnId(), 32 ); + msg.WriteBits( weaponWorldModel.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( weaponGone, 1 ); + msg.WriteBits( isLagged, 1 ); + msg.WriteBits( isChatting, 1 ); + msg.WriteLong( connectTime ); + msg.WriteByte( lastKiller ? lastKiller->entityNumber : 255 ); + + if ( weapon ) { + msg.WriteBits( 1, 1 ); + weapon->WriteToSnapshot( msg ); + } else { + msg.WriteBits( 0, 1 ); + } + msg.WriteBits( inBuyZone, 1 ); + msg.WriteLong( (int)buyMenuCash ); +} + +/* +================ +idPlayer::ReadFromSnapshot +================ +*/ +void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + assert( !IsFakeClient() ); + + int i, oldHealth, newIdealWeapon, weaponSpawnId, weaponWorldSpawnId; + bool stateHitch; + int lastKillerEntity; + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + health = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + lastDamageDef = msg.ReadBits( gameLocal.entityDefBits ); + lastDamageDir = msg.ReadDir( 9 ); + lastDamageLocation = msg.ReadShort(); + newIdealWeapon = msg.ReadBits( -idMath::BitsForInteger( MAX_WEAPONS ) ); + inventory.weapons = msg.ReadBits( MAX_WEAPONS ); + weaponSpawnId = msg.ReadBits( 32 ); + weaponWorldSpawnId = msg.ReadBits( 32 ); + int latchedSpectator = spectator; + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( spectating && latchedSpectator != spectator ) { + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( this == gameLocal.GetLocalPlayer() ) { + // this is where the client updates their spectated player + if ( gameLocal.gameType == GAME_TOURNEY ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (spectator != arena.GetPlayers()[ 0 ]->entityNumber && spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_BRACKET ); + } else if( spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_ONE ); + } else if( spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + if ( gameLocal.entities[ spectator ] ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ spectator ] ); + p->UpdateHudWeapon( p->currentWeapon ); + if ( p->weapon ) { + p->weapon->SpectatorCycle(); + } + } + } + } + weaponGone = msg.ReadBits( 1 ) != 0; + isLagged = msg.ReadBits( 1 ) != 0; + isChatting = msg.ReadBits( 1 ) != 0; + connectTime = msg.ReadLong(); + lastKillerEntity = msg.ReadByte(); + if( lastKillerEntity >= 0 && lastKillerEntity < MAX_CLIENTS) { + lastKiller = static_cast(gameLocal.entities[ lastKillerEntity ]); + } else { + lastKiller = NULL; + } + + bool weaponChange = ( idealWeapon != newIdealWeapon ); + if ( gameLocal.clientAckSequence < clientIdealWeaponPredictFrame ) { + // we have predicted a weapon change, and sent a reliable message to the server about it + // but it has not been processed yet, so ignore what may be contradictory information here + weaponChange = false; + } else { + clientIdealWeaponPredictFrame = -1; + } + + if ( weaponChange ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + StopFiring(); + UpdateHudWeapon(); + usercmd.buttons &= (~BUTTON_ATTACK); + clientIdealWeaponPredictFrame = -1; + } + + // Attach the world and view entities + if ( weaponWorldModel.GetSpawnId() != weaponWorldSpawnId || weaponViewModel.GetSpawnId() != weaponSpawnId ) { + SetWeapon( -1 ); + + if ( weaponWorldModel.SetSpawnId( weaponWorldSpawnId ) && weaponViewModel.SetSpawnId( weaponSpawnId ) ) { + weaponCatchup = true; + } + } + + // rjohnson: instance persistance information + if ( weaponWorldModel.IsValid() ) { + weaponWorldModel->fl.persistAcrossInstances = true; + weaponWorldModel->SetInstance( GetInstance() ); + } + if ( weaponViewModel.IsValid() ) { + weaponViewModel->fl.persistAcrossInstances = true; + weaponViewModel->SetInstance( GetInstance() ); + } + + // Clear the weapon for clients entering PVS + if ( fl.networkStale && currentWeapon != idealWeapon && entityNumber != gameLocal.localClientNum && weaponWorldModel.IsValid() && weaponViewModel.IsValid() ) { + // We've probably already mispredicted a few frames without the client. + // Clearing the weapon here (during snapshot reading) prevents + // the wrong weapon from getting ClientUnstale() + // Weapon_Combat will set the proper weapon. + SetWeapon( -1 ); + weaponCatchup = true; + } + + // If we have a weapon then update it from the snapshot, otherwise + // we just skip whatever it would have read if it were there + if ( msg.ReadBits( 1 ) ) { + if ( weapon ) { + weapon->ReadFromSnapshot( msg ); + } else { + rvWeapon::SkipFromSnapshot( msg ); + } + } + inBuyZone = msg.ReadBits( 1 ) != 0; + int cash = msg.ReadLong(); + if ( cash != (int)buyMenuCash ) { + buyMenuCash = (float)cash; + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + // no msg reading below this + + // if not a local client assume the client has all ammo types + // don't do this for server demos because they have the player states included + if ( !gameLocal.isRepeater && (entityNumber != gameLocal.GetDemoFollowClient()) && !IsLocalClient() && !IsSpectatedClient() ) { + for( i = 0; i < MAX_AMMO; i++ ) { + inventory.ammo[ i ] = -1; + } + } + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + pfl.dead = true; + ClearPowerUps(); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + animator.ClearAllJoints(); + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + + if ( !stateHitch ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + const idDeclEntityDef* def = static_cast(declManager->DeclByIndex ( DECL_ENTITYDEF, lastDamageDef )); + if ( def ) { + // TODO: get attackers push scale? + InitDeathPush ( lastDamageDir, lastDamageLocation, &def->dict, 1.0f ); + ClientDamageEffects ( def->dict, lastDamageDir, ( oldHealth - health ) * 4 ); + } + + // gib them here + if ( health < -20 || ( lastKiller && lastKiller->PowerUpActive( POWERUP_QUADDAMAGE )) ) { + ClientGib( lastDamageDir ); + } + + if ( weapon ) { + weapon->OwnerDied(); + + // Get rid of the weapon now + delete weapon; + weapon = NULL; + currentWeapon = -1; + } + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + //common->DPrintf( "idPlayer::ReadFromSnapshot() - Player respawn detected for %d '%s' - re-enabling clip\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "" ); + + // this is the first time we've seen the player since we heard he died - he may have picked up + // some powerups since he actually spawned in, so restore those + int latchPowerup = inventory.powerups; + Init(); + inventory.powerups = latchPowerup; + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } else if ( oldHealth - health > 2 && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + ClientDamageEffects ( def->dict, lastDamageDir, oldHealth - health ); + pfl.pain = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef ); + } + } + } + + if ( oldHealth > 0 && health > 0 && IsHidden() && !spectating ) { + // ensure the client is shown after the initial spawn frame + Show(); + } + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idPlayer::WritePlayerStateToSnapshot +================ +*/ +void idPlayer::WritePlayerStateToSnapshot( int lastSnapshotFrame, idBitMsgDelta &msg ) const { + int i; + + // write out a flag if a hit sound was triggered since last snap + msg.WriteBits( lastHitFrame > lastSnapshotFrame ? 1 : 0, 1 ); + msg.WriteBits( lastHitArmor, 1 ); + + msg.WriteDeltaByte( 0, bobCycle ); + msg.WriteDeltaLong( 0, stepUpTime ); + msg.WriteDeltaFloat( 0.0f, stepUpDelta ); + + msg.WriteShort( inventory.weapons ); + msg.WriteByte( inventory.armor ); + msg.WriteShort( inventory.powerups ); + + for( i = 0; i < MAX_AMMO; i++ ) { + // send a value of -1 as the max positive value as we have ASYNC_PLAYER_INV_AMMO_BITS>0 + if ( inventory.ammo[i] == -1 ) { + msg.WriteBits( ( 1 << ASYNC_PLAYER_INV_AMMO_BITS ) - 1, ASYNC_PLAYER_INV_AMMO_BITS ); + } else { + msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS ); + } + } + + for ( i = 0; i < POWERUP_MAX; i ++ ) { + msg.WriteLong( inventory.powerupEndTime[ i ] ); + } +} + +/* +================ +idPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + int i, ammo; + + bool trigger = ( msg.ReadBits( 1 ) != 0 ); + bool armor = ( msg.ReadBits( 1 ) != 0 ); + if ( trigger ) { + TriggerHitSound( armor ); + } + + bobCycle = msg.ReadDeltaByte( 0 ); + stepUpTime = msg.ReadDeltaLong( 0 ); + stepUpDelta = msg.ReadDeltaFloat( 0.0f ); + + inventory.weapons = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + inventory.powerups = msg.ReadShort(); + + for( i = 0; i < MAX_AMMO; i++ ) { + ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS ); + if ( gameLocal.time >= inventory.ammoPredictTime ) { + if ( ammo == ( 1 << ASYNC_PLAYER_INV_AMMO_BITS ) - 1 ) { + inventory.ammo[ i ] = -1; + } else { + inventory.ammo[ i ] = ammo; + } + } + } + + int powerup_max = POWERUP_MAX; + for ( i = 0; i < powerup_max; i ++ ) { + inventory.powerupEndTime[ i ] = msg.ReadLong(); + } + while ( i < POWERUP_MAX ) { + inventory.powerupEndTime[ i ] = 0; + i++; + } + + if ( gameLocal.IsMultiplayer() ) { + if ( (inventory.weapons&~oldInventoryWeapons) ) { + //added a weapon from inventory, bring up bar + UpdateHudWeapon(); + } + oldInventoryWeapons = inventory.weapons; + } +} + +/* +================ +idPlayer::ServerReceiveEvent +================ +*/ +bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + + if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { + return true; + } + + // client->server events + switch ( event ) { + case EVENT_IMPULSE: { + int impulse = msg.ReadBits( IMPULSE_NUMBER_OF_BITS ); + serverReceiveEvent = true; // marking so we know if ACKs are needed + PerformImpulse( impulse ); + serverReceiveEvent = false; + return true; + } + case EVENT_EMOTE: { + // forward the emote on to all clients except the one that sent it to us + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, entityNumber ); + + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return false; + } + } +} + +/* +=============== +idPlayer::CheckAckReply +=============== +*/ +void idPlayer::CheckAckReply( void ) const { + assert( !gameLocal.isClient ); + if ( serverReceiveEvent ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT_ACK ); + outMsg.WriteLong( gameLocal.framenum ); + networkSystem->ServerSendReliableMessage( entityNumber, outMsg ); + } +} + +/* +================ +idPlayer::ClientReceiveEvent +================ +*/ +bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int powerup; + bool start; + + switch ( event ) { + case EVENT_EXIT_TELEPORTER: + Event_ExitTeleporter(); + return true; + case EVENT_ABORT_TELEPORTER: + SetPrivateCameraView( NULL ); + return true; + case EVENT_POWERUP: { + powerup = msg.ReadShort(); + start = ( msg.ReadBits( 1 ) != 0 ); + if ( start ) { + bool team = ( msg.ReadBits( 1 ) != 0 ); + GivePowerUp( powerup, 0, team ); + } else { + ClearPowerup( powerup ); + } + + return true; + } + case EVENT_SPECTATE: { + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + // force to spectator if we got this event about a client in a different + // instance + Spectate( spectate ); + + // spectate might re-link clip for stale players, so re-call ClientStale if we're stale + if ( fl.networkStale ) { + ClientStale(); + } + return true; + } + case EVENT_ADD_DAMAGE_EFFECT: { + if ( spectating ) { + // if we're spectating, ignore + // happens if the event and the spectate change are written on the server during the same frame (fraglimit) + return true; + } + return idActor::ClientReceiveEvent( event, time, msg ); + } + case EVENT_EMOTE: { + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + case EVENT_JUMP: { + // jumps for the local client are predicted + if ( IsLocalClient() ) { + return true; + } + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + return true; + } + default: { + return idActor::ClientReceiveEvent( event, time, msg ); + } + } +} + +/* +================ +idPlayer::Hide +================ +*/ +void idPlayer::Hide( void ) { + idActor::Hide(); + + if ( weapon ) { + weapon->HideWorldModel( ); + } +} + +/* +================ +idPlayer::Show +================ +*/ +void idPlayer::Show( void ) { + idActor::Show(); + + if ( weapon ) { + weapon->ShowWorldModel( ); + } +} + +/* +=============== +idPlayer::ShowTip +=============== +*/ +void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) { + if ( tipUp ) { + return; + } + hud->SetStateString( "tip", tip ); + hud->SetStateString( "tiptitle", title ); + hud->HandleNamedEvent( "tipWindowUp" ); + if ( autoHide ) { + PostEventSec( &EV_Player_HideTip, 5.0f ); + } + tipUp = true; +} + +/* +=============== +idPlayer::HideTip +=============== +*/ +void idPlayer::HideTip( void ) { + hud->HandleNamedEvent( "tipWindowDown" ); + tipUp = false; +} + +/* +=============== +idPlayer::Event_HideTip +=============== +*/ +void idPlayer::Event_HideTip( void ) { + HideTip(); +} + +/* +=============== +idPlayer::ShowObjective +=============== +*/ +void idPlayer::ShowObjective( const char *obj ) { + objectiveSystem->HandleNamedEvent( obj ); + objectiveUp = true; +} + + +/* +=============== +idPlayer::HideObjective +=============== +*/ +void idPlayer::HideObjective( void ) { + objectiveSystem->HandleNamedEvent( "closeObjective" ); + objectiveUp = false; +} + +/* +=============== +idPlayer::SetSpectateOrigin +=============== +*/ +void idPlayer::SetSpectateOrigin( void ) { + idVec3 neworig; + + neworig = GetPhysics()->GetOrigin(); + neworig[ 2 ] += EyeHeight(); + neworig[ 2 ] += 25; + SetOrigin( neworig ); +} + +/* +=============== +idPlayer::RemoveWeapon +=============== +*/ +void idPlayer::RemoveWeapon( const char *weap ) { + if ( weap && *weap ) { + inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 ); + } +} + +/* +=============== +idPlayer::CanShowWeaponViewmodel +=============== +*/ +bool idPlayer::CanShowWeaponViewmodel( void ) const { + return showWeaponViewModel; +} + +/* +=============== +idPlayer::SetLevelTrigger +=============== +*/ +void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) { + if ( levelName && *levelName && triggerName && *triggerName ) { + idLevelTriggerInfo lti; + lti.levelName = levelName; + lti.triggerName = triggerName; + inventory.levelTriggers.Append( lti ); + } +} + + +/* +=============== +idPlayer::Event_LevelTrigger +=============== +*/ +void idPlayer::Event_LevelTrigger( void ) { + idStr mapName = gameLocal.GetMapName(); + mapName.StripPath(); + mapName.StripFileExtension(); + for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){ + idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 1, this ); + } + } + } +} + +/* +================ +idPlayer::ToggleFlashlight +================ +*/ +void idPlayer::ToggleFlashlight ( void ) { + // Dead people can use flashlights +// RAVEN BEGIN +// mekberg: check to see if the weapon is enabled. + if ( health <= 0 || !weaponEnabled ) { + return; + } +// RAVEN END + + int flashlightWeapon = currentWeapon; + if ( !spawnArgs.GetBool( va( "weapon%d_flashlight", flashlightWeapon ) ) ) { + // TODO: find the first flashlight weapon that has ammo starting at the bottom + for( flashlightWeapon = MAX_WEAPONS - 1; flashlightWeapon >= 0; flashlightWeapon-- ) { + if ( inventory.weapons & ( 1 << flashlightWeapon ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", flashlightWeapon ) ); + int ammo = inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ]; + + if ( !ammo ) { + continue; + } + + if ( spawnArgs.GetBool ( va ( "weapon%d_flashlight", flashlightWeapon ) ) ) { + break; + } + } + } + + // Couldnt find flashlight + if ( flashlightWeapon < 0 ) { + return; + } + } + + // If the current weapon isnt the flashlight then always force the flashlight on + if ( flashlightWeapon != idealWeapon ) { + flashlightOn = true; + idealWeapon = flashlightWeapon; + // Inform the weapon to toggle the flashlight, this will eventually cause the players + // Flashlight method to be called + } else if ( weapon ) { + weapon->Flashlight ( ); + } +} + +/* +================ +idPlayer::Flashlight +================ +*/ +void idPlayer::Flashlight ( bool on ) { + flashlightOn = on; +} + +/* +================ +idPlayer::DamageFeedback +================ +*/ +void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + + assert( !gameLocal.isClient ); + + //rvTramCars weren't built on the idActor inheritance hierarchy but need to be treated like one when shot. + //TODO: Maybe add a key to entity flags that will allow them to be shot as actors even if they aren't actors? + if ( !victim || ( !victim->IsType( idActor::GetClassType() ) && !victim->IsType( rvTramCar::GetClassType() ) ) || victim->health <= 0 ) { + return; + } + + bool armorHit = false; + + if ( gameLocal.isMultiplayer && victim->IsType( idPlayer::GetClassType() ) ) { + if ( this == victim ) { + // no feedback for self hits + return; + } + idPlayer *p = static_cast< idPlayer * >( victim ); + if ( gameLocal.IsTeamGame() && p->team == team ) { + // no feedback for team hits + return; + } + if ( p->inventory.armor > 0 ) { + armorHit = true; + } + } + + TriggerHitSound( armorHit ); +} + +/* +============== +idPlayer::GetWeaponDef +============== +*/ +const idDeclEntityDef* idPlayer::GetWeaponDef ( int weaponIndex ) { + if ( cachedWeaponDefs[weaponIndex] ) { + return cachedWeaponDefs[weaponIndex]; + } + + idStr weapon; + weapon = spawnArgs.GetString ( va("def_weapon%d", weaponIndex ) ); + if ( !weapon.Length() ) { + return NULL; + } + + cachedWeaponDefs[weaponIndex] = gameLocal.FindEntityDef ( weapon, false ); + if ( !cachedWeaponDefs[weaponIndex] ) { + gameLocal.Error( "Could not find weapon definition '%s'", weapon.c_str() ); + } + + return cachedWeaponDefs[weaponIndex]; +} + +/* +============== +idPlayer::GetPowerupDef + +Returns the powerup dictionary for the given powerup index. The dictionary is cached to ensure a +speedy retrieval after the first call. +============== +*/ +const idDeclEntityDef* idPlayer::GetPowerupDef ( int powerupIndex ) { + const idDict* types; + int i; + int num; + + if ( cachedPowerupDefs[powerupIndex] ) { + return cachedPowerupDefs[powerupIndex]; + } + + types = gameLocal.FindEntityDefDict( "powerup_types", false ); + if ( !types ) { + gameLocal.Error( "Could not find entity definition for 'powerup_types'" ); + } + + num = types->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + const idKeyValue* kv; + kv = types->GetKeyVal( i ); + if ( atoi(kv->GetValue()) == powerupIndex ) { + cachedPowerupDefs[powerupIndex] = gameLocal.FindEntityDef ( kv->GetKey(), false ); + if ( !cachedPowerupDefs[powerupIndex] ) { + gameLocal.Error( "Could not find powerup definition '%s'", kv->GetKey().c_str() ); + } + return cachedPowerupDefs[powerupIndex]; + } + } + + gameLocal.Error( "Could not find powerup definition '%d'", powerupIndex ); + + return NULL; +} + +/* +============== +idPlayer::discoverSecretArea + +Announces a secret area and increases the secret area tally +============== +*/ +void idPlayer::DiscoverSecretArea(const char* _description) { + + //increment the secret area tally + inventory.secretAreasDiscovered++; +} + +/* +============== +idPlayer::StartBossBattle + +Starts a boss battle with the given entity. During a boss battle the health of the boss +will be displayed on the HUD +============== +*/ +void idPlayer::StartBossBattle ( idEntity* enemy ) { + bossEnemy = enemy; + idUserInterface *hud_ = GetHud(); + if ( hud_ ) { + hud_->SetStateInt ( "boss_maxhealth", enemy->health ); + hud_->HandleNamedEvent ( "showBossBar" ); + } +} + +/* +===================== +idPlayer::SetInitialHud +===================== +*/ +void idPlayer::SetInitialHud ( void ) { + if ( !mphud || !gameLocal.isMultiplayer || gameLocal.GetLocalPlayer() != this ) { + return; + } + + mphud->SetStateInt( "gametype", gameLocal.gameType ); + + if( hud ) { + hud->SetStateInt( "gametype", gameLocal.gameType ); + } + + mphud->HandleNamedEvent( "InitHud" ); + mphud->HandleNamedEvent( "TeamChange" ); + + if( gameLocal.IsFlagGameType() ) { + mphud->SetStateFloat( "ap", gameLocal.mpGame.assaultPoints.Num() ); + + for( int i = 0; i < TEAM_MAX; i++ ) { + mphud->SetStateInt( "team", i ); + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_DROPPED ) { + mphud->HandleNamedEvent( "flagDrop" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN ) { + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_MARINE ) { + mphud->SetStateInt( "team", TEAM_MARINE ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_STROGG ) { + mphud->SetStateInt( "team", TEAM_STROGG ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_AT_BASE ) { + mphud->SetStateInt( "team", i ); + mphud->HandleNamedEvent( "flagReturn" ); + } + } + + for( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + mphud->SetStateFloat( "apindex", i ); + //mphud->SetStateInt( "apteam", ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetAPOwner( i ) ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "APCaptured" ); + } + } + + mphud->StateChanged ( gameLocal.time ); +} + +void idPlayer::RemoveClientModel ( const char *entityDefName ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp ( ( static_cast ( cent ) )->GetClassname(), entityDefName ) ) { + cent->Unbind ( ); + delete cent; + } + } + } +} + +rvClientEntityPtr idPlayer::AddClientModel ( const char* entityDefName, const char* shaderName ) { + rvClientEntityPtr ptr; + ptr = NULL; + + if ( entityDefName == NULL ) { + return ptr; + } + + const idDict* entityDef = gameLocal.FindEntityDefDict ( entityDefName, false ); + + if ( entityDef == NULL ) { + return ptr; + } + + rvClientModel *newModel = NULL; + + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientModel" ); + + if( newModel == NULL ) { + return ptr; + } + idMat3 rotation; + rotation = entityDef->GetAngles( "angles" ).ToMat3(); + newModel->SetAxis( rotation ); + + newModel->SetOrigin( entityDef->GetVector( "origin" ) * rotation ); + + newModel->Bind ( this, animator.GetJointHandle( entityDef->GetString ( "joint" ) ) ); + + newModel->SetCustomShader ( shaderName ); + newModel->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + newModel->GetRenderEntity()->noSelfShadow = true; + newModel->GetRenderEntity()->noShadow = true; + + ptr = newModel; + + return ptr; +} + +void idPlayer::RemoveClientModels ( void ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + cent->Unbind ( ); + delete cent; + } + } +} + +/* +===================== +idPlayer::ClientGib() +ddynerman: Spawns client side gibs around this player +===================== +*/ +void idPlayer::ClientGib( const idVec3& dir ) { + + if( !spawnArgs.GetBool( "gib" ) ) { + return; + } + + int i; + idVec3 entityCenter, velocity; + idList list; + + + // hide the player + SetSkin( gibSkin ); + + //and the head + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->UnlinkCombat(); + delete clientHead; + } + } else { + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + } + + // blow out the gibs in the given direction away from the center of the entity + + + // spawn gib client models + rvClientMoveable::SpawnClientMoveables( this, "clientgib", &list ); + + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + for ( i = 0; i < list.Num(); i++ ) { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + // we don't want collision on gibs + //list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + + list[i]->GetPhysics()->ApplyImpulse( 0, list[i]->GetPhysics()->GetOrigin(), velocity * ( 25000.0f + ( gameLocal.random.RandomFloat() * 50000.0f)) ); +// list[i]->GetPhysics()->SetLinearVelocity( velocity * ( 25.0f + ( gameLocal.random.RandomFloat() * 300.0f))); + list[i]->GetPhysics()->SetAngularVelocity( velocity * ( -250.0f + ( gameLocal.random.RandomFloat() * 500.0f))); + + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + + list[i]->PostEventMS( &CL_FadeOut, SEC2MS( 4.0f ), SEC2MS( 2.0f ) ); + } + + + //play gib fx + gameLocal.PlayEffect( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + // gibs are PVS agnostic. If we gib a player outside of our PVS, set the oldHealth + // to below 0 so when this player re-appears in our snap we respawn him + if( gameLocal.isClient && gameLocal.GetLocalPlayer() && health > 0 ) { + health = -100; + } +} + +/* +===================== +idPlayer::CanDamage +===================== +*/ +bool idPlayer::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ) { + if( gameLocal.isMultiplayer && health <= 0 ) { + return false; + } + + return idActor::CanDamage( origin, damagePoint, ignoreEnt ); +} + +/* +===================== +idPlayer::ClientDamageEffects + +===================== +*/ +void idPlayer::ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ) { + idVec3 from; + idVec3 localDir; + float fadeDB; + + // Only necessary on clients + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + from = dir; + from.Normalize(); + viewAxis.ProjectVector( from, localDir ); + + if ( damage ) { +// RAVEN BEGIN +// jnewquist: Controller rumble + idPlayer *p = gameLocal.GetLocalPlayer(); + + if ( p && ( p == this || ( p->spectating && p->spectator == entityNumber ) ) ) { + playerView.DamageImpulse( localDir, &damageDef, damage ); + } +// RAVEN END + } + + // Visual effects + if ( health > 0 && damage ) { + // Let the hud know about the hit + if ( hud ) { + hud->SetStateFloat ( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + hud->HandleNamedEvent ( "playerHit" ); + } + } + + // Sound effects + if ( damageDef.GetFloat ( "hl_volumeDB", "-40", fadeDB ) ) { + float fadeTime; + + fadeTime = 0.0f; + if ( !pfl.hearingLoss ) { + const char* fade; + + fadeTime = damageDef.GetFloat ( "hl_fadeOutTime", ".25" ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); + + pfl.hearingLoss = true; + + // sound overlayed? + if ( damageDef.GetString ( "snd_hl", "", &fade ) && *fade ) { + StartSoundShader ( declManager->FindSound ( fade ), SND_CHANNEL_DEMONIC, 0, false, NULL ); + } + } + + fadeTime += damageDef.GetFloat ( "hl_time", "1" ); + + CancelEvents ( &EV_Player_FinishHearingLoss ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, damageDef.GetFloat ( "hl_fadeInTime", ".25" ) ); + } +} + +/* +===================== +idPlayer::GetDebugInfo +===================== +*/ +void idPlayer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idActor::GetDebugInfo ( proc, userData ); + proc ( "idPlayer", "inventory.armor", va("%d", inventory.armor ), userData ); + proc ( "idPlayer", "inventory.weapons", va("%d", inventory.weapons ), userData ); + proc ( "idPlayer", "inventory.powerups", va("%d", inventory.powerups ), userData ); +} + + + +// RAVEN END + +/* +===================== +idPlayer::ApplyImpulse +===================== +*/ +void idPlayer::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( !ent) { + gameLocal.Warning( "idPlayer::ApplyImpulse called with null entity as instigator."); + return; + } + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time + 1000; + + if( ent->IsType( idPlayer::Type ) && ent != this ) { + lastImpulsePlayer = static_cast(ent); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + idEntity* owner = static_cast(ent)->GetOwner(); + if( owner && owner->IsType( idPlayer::Type ) && owner != this ) { + lastImpulsePlayer = static_cast(owner); + } + } + + idAFEntity_Base::ApplyImpulse( ent, id, point, impulse, splash ); +} + +/* +===================== +idPlayer::SetupHead +===================== +*/ +void idPlayer::SetupHead( const char* headModel, idVec3 headOffset ) { + if( gameLocal.isMultiplayer ) { + // player's don't use idActor's real head entities - uses clientEntities instead + if( clientHead.GetEntity() ) { + delete clientHead.GetEntity(); + clientHead = NULL; + } + + + if( spectating || (gameLocal.GetLocalPlayer() && instance != gameLocal.GetLocalPlayer()->GetInstance()) ) { + return; + } + + const idDict* headDict = gameLocal.FindEntityDefDict( headModel, false ); + if ( !headDict ) { + return; + } + + rvClientAFAttachment* headEnt = clientHead.GetEntity(); + gameLocal.SpawnClientEntityDef( *headDict, (rvClientEntity**)&headEnt, false ); + if( headEnt ) { + idStr jointName = spawnArgs.GetString( "joint_head" ); + jointHandle_t joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + headEnt->SetBody ( this, headDict->GetString ( "model" ), joint ); + + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->Bind( this, joint, true ); + headEnt->InitCopyJoints(); + + // Spawn might have parsed a skin from the spawnargs, save it for future use here + headSkin = headEnt->GetRenderEntity()->customSkin; + clientHead = headEnt; + } + } else { + idActor::SetupHead( headModel, headOffset ); + + if ( head ) { + head->fl.persistAcrossInstances = true; + } + } +} + +/* +===================== +idPlayer::GUIMainNotice +===================== +*/ +void idPlayer::GUIMainNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "main_notice_text", message ); + mphud->SetStateBool( "main_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "main_notice" ); +} + +/* +===================== +idPlayer::GUIFragNotice +===================== +*/ +void idPlayer::GUIFragNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "frag_notice_text", message ); + mphud->SetStateBool( "frag_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "frag_notice" ); +} + +/* +===================== +idPlayer::SetHudOverlay +===================== +*/ +void idPlayer::SetHudOverlay( idUserInterface* overlay, int duration ) { + overlayHud = overlay; + overlayHudTime = gameLocal.time + duration; +} + +// RAVEN BEGIN +// mekberg: wrap saveMessages +/* +===================== +idPlayer::SaveMessage +===================== +*/ +void idPlayer::SaveMessage( void ) { +#ifndef _XENON + if ( GetHud( ) ) { + GetHud()->HandleNamedEvent( "saveMessage" ); + } + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "saveMessage" ); + } +#endif +} + +// mekberg: set pm_ cvars +/* +===================== +idPlayer::SetPMCVars +===================== +*/ +void idPlayer::SetPMCVars( void ) { + const idKeyValue *kv; + + if ( !gameLocal.isMultiplayer || gameLocal.isServer ) { + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + } +} +// RAVEN END + +/* +===================== +idPlayer::GetSpawnClassname +===================== +*/ +const char* idPlayer::GetSpawnClassname ( void ) { + idEntity* world; + const char* entityFilter; + + // Test player def + if ( *g_testPlayer.GetString() ) { + return g_testPlayer.GetString ( ); + } + + // Multiplayer + if ( gameLocal.isMultiplayer ) { + return "player_marine_mp"; + } + + // See if the world spawn specifies a player + world = gameLocal.entities[ENTITYNUM_WORLD]; + assert( world ); + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + return world->spawnArgs.GetString( va("player_%s", entityFilter ), world->spawnArgs.GetString( "player", "player_marine" ) ); + } + + return world->spawnArgs.GetString( "player", "player_marine" ); +} + +/* +=============== +idPlayer::SetInstance +=============== +*/ +void idPlayer::SetInstance( int newInstance ) { + common->DPrintf( "idPlayer::SetInstance() - Setting instance for '%s' to %d\n", name.c_str(), newInstance ); + idEntity::SetInstance( newInstance ); + + if( head.GetEntity() ) { + head.GetEntity()->SetInstance( newInstance ); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetInstance( newInstance ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetInstance( newInstance ); + } + } + + if( weaponWorldModel ) { + weaponWorldModel->SetInstance( newInstance ); + } + + if( weaponViewModel ) { + weaponViewModel->SetInstance( newInstance ); + } + + // reschedule time announcements if needed + if( this == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleTimeAnnouncements(); + + if( gameLocal.isServer ) { + // remove/add heads on server + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + if( player->instance != newInstance ) { + if( player->clientHead.GetEntity() ) { + delete player->clientHead; + player->clientHead = NULL; + } + } else { + player->UpdateModelSetup( true ); + } + } + } + } + } +} + +/* +=============== +idPlayer::JoinInstance +=============== +*/ +void idPlayer::JoinInstance( int newInstance ) { + assert( gameLocal.isServer ); + if( instance == newInstance ) { + return; + } + + if( newInstance < 0 || newInstance >= MAX_ARENAS ) { + gameLocal.Warning( "idPlayer::JoinInstance() - Invalid instance %d specified\n", newInstance ); + } + + if( gameLocal.GetNumInstances() <= newInstance || gameLocal.GetInstance( newInstance ) == NULL ) { + // don't populate instance until player gets linked into the right one + gameLocal.AddInstance( newInstance ); + + if( this == gameLocal.GetLocalPlayer() ) { + // ensure InstanceLeave() gets called on newly spawned entities before + // InstanceJoin() gets called. + gameLocal.mpGame.ServerSetInstance( instance ); + } + } + + SetArena( newInstance ); + SetInstance( newInstance ); + + gameLocal.GetInstance( newInstance )->JoinInstance( this ); +} + +/* +=============== +idPlayer::SetEmote +=============== +*/ +void idPlayer::SetEmote( playerEmote_t newEmote ) { + emote = newEmote; + + // if we're the ones generating the emote, pass it along + if( IsLocalClient() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( emote ); + if( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, -1 ); + } else { + ClientSendEvent( EVENT_EMOTE, &msg ); + } + } +} + +/* +=============== +idPlayer::GetGroundElevator +=============== +*/ +idEntity* idPlayer::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + //NOTE: for player, don't care if all the way on, or not + return groundEnt; +} + +/* +=================== +idPlayer::IsCrouching +=================== +*/ +bool idPlayer::IsCrouching( void ) const { + return physicsObj.IsCrouching(); +} + +/* +=============== +idPlayer::SetArena +=============== +*/ +void idPlayer::SetArena( int newArena ) { + if( arena == newArena ) { + return; + } + + arena = newArena; + if( gameLocal.GetLocalPlayer() == this && gameLocal.gameType == GAME_TOURNEY ) { + if( arena >= 0 && arena <= MAX_ARENAS ) { + if( arena < MAX_ARENAS ) { +// RAVEN BEGIN +// rhummer: localized these strings. + GUIMainNotice( va( common->GetLocalizedString( "#str_107270" ), newArena + 1 ) ); + } else { + GUIMainNotice( common->GetLocalizedString( "#str_107271" ) ); + } +// RAVEN END + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( newArena, TGH_BRACKET ); + } + gameLocal.mpGame.RemoveAnnouncerSoundRange( AS_TOURNEY_JOIN_ARENA_ONE, AS_TOURNEY_JOIN_ARENA_EIGHT ); + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)(AS_TOURNEY_JOIN_ARENA_ONE + arena), gameLocal.time ); + } + } +} + +/* +=============== +idPlayer::Event_DamageEffect +=============== +*/ +void idPlayer::Event_DamageEffect( const char *damageDefName, idEntity* _damageFromEnt ) +{ + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) + { + idVec3 dir = (_damageFromEnt!=NULL)?(GetEyePosition()-_damageFromEnt->GetEyePosition()):viewAxis[2]; + dir.Normalize(); + int damage = 1; + ClientDamageEffects( damageDef->dict, dir, damage ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = 0; + } +} + +/* +=============== +idPlayer::UpdateDeathShader +=============== +*/ +void idPlayer::UpdateDeathShader ( bool state_hitch ) { + if ( !doingDeathSkin && gameLocal.time > deathSkinTime && deathSkinTime ) { + deathSkinTime = 0; + + deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" ); + doingDeathSkin = true; + if ( state_hitch ) { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } else { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } + renderEntity.noShadow = true; + UpdateVisuals(); + } +} + +#if 0 +/* +=============== +idPlayer::Event_InitWeapon +=============== +*/ +void idPlayer::InitWeapon( void ) { + currentWeapon = -1; + SetWeapon( idealWeapon ); +} +#endif + +/* +=============== +idPlayer::GetHitscanTint +=============== +*/ +const idVec4& idPlayer::GetHitscanTint( void ) { + assert( !IsFakeClient() ); + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 2 ) { + if( team == TEAM_MARINE ) { + return marineHitscanTint; + } else if( team == TEAM_STROGG ) { + return stroggHitscanTint; + } else { + gameLocal.Error( "idPlayer::GetHitscanTint() - Unknown team '%d' on player %d '%s'\n", team, entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + } + } else { + return defaultHitscanTint; + } + } + + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 1 ) { + return hitscanTint; + } + + return defaultHitscanTint; +} + +/* +=============== +idPlayer::IsReady +=============== +*/ +bool idPlayer::IsReady( void ) { + return !gameLocal.serverInfo.GetBool( "si_useReady" ) || ready || forcedReady; +} + +/* +=============== +idPlayer::ForceScoreboard +=============== +*/ +void idPlayer::ForceScoreboard( bool force, int time ) { + forceScoreBoard = force; + forceScoreBoardTime = time; +} + +/* +=============== +idPlayer::GetTextTourneyStatus +=============== +*/ +const char* idPlayer::GetTextTourneyStatus( void ) { + if( tourneyStatus == PTS_ADVANCED ) { + return common->GetLocalizedString( "#str_107740" ); + } else if( tourneyStatus == PTS_ELIMINATED ) { + return common->GetLocalizedString( "#str_107729" ); + } else if( tourneyStatus == PTS_PLAYING ) { + return common->GetLocalizedString( "#str_107728" ); + } else if( tourneyStatus == PTS_UNKNOWN ) { + return common->GetLocalizedString( "#str_107739" ); + } + return "UNKNOWN TOURNEY STATUS"; +} + +/* +=============== +idPlayer::ClientInstanceJoin +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceJoin( void ) { + assert( gameLocal.isClient ); + + common->DPrintf( "idPlayer::ClientInstanceJoin() - client %d ('%s') is being shown\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?" ); + + // restore client + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientInstanceLeave +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceLeave( void ) { + assert( gameLocal.isClient ); + common->DPrintf( "idPlayer::ClientInstanceLeave() - client %d ('%s') is being hidden\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?" ); + + // force client to spectate + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientStale +=============== +*/ +bool idPlayer::ClientStale( void ) { + idEntity::ClientStale(); + + // remove all powerup effects + for( int i = 0; i < POWERUP_MAX; i++ ) { + if( inventory.powerups & ( 1 << i ) ) { + StopPowerUpEffect( i ); + } + } + + + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + + Hide(); + + // never delete client + return false; +} + +/* +=============== +idPlayer::ClientUnstale +=============== +*/ +void idPlayer::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + // force render ent to position + renderEntity.axis = physicsObj.GetAxis(); + renderEntity.origin = physicsObj.GetOrigin(); + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // the powerup effects ( rvClientEntity ) will do some bindings, which in turn will call GetPosition + // which uses the predictedOrigin .. which won't be updated till we Think() so just don't leave the predictedOrigin to the old position + predictedOrigin = renderEntity.origin; + + // restart powerup effects on clients that are coming back into our snapshot + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( inventory.powerups & (1 << i) ) { + StartPowerUpEffect( i ); + } + } + + UpdateModelSetup( true ); + + if ( weapon ) { + weapon->ClientUnstale(); + } +} + +/* +=============== +idPlayer::AllowedVoiceDest +=============== +*/ +bool idPlayer::AllowedVoiceDest( int from ) { + + int i, free; + + free = -1; + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + if( voiceDest[i] == from ) { + voiceDestTimes[i] = gameLocal.time; + return true; + } + + if( voiceDestTimes[i] + 200 < gameLocal.time ) { + free = i; + } + } + + if( free > -1 ) { + voiceDest[free] = from; + voiceDestTimes[i] = gameLocal.time; + return true; + } + + return false; +} + +// RITUAL BEGIN +void idPlayer::ClampCash( float minCash, float maxCash ) +{ + if( buyMenuCash < minCash ) + buyMenuCash = minCash; + + if( buyMenuCash > maxCash ) + buyMenuCash = maxCash; +} + +void idPlayer::GiveCash( float cashDeltaAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + float oldCash = buyMenuCash; + buyMenuCash += cashDeltaAmount; + ClampCash( minCash, maxCash ); + + if( (int)buyMenuCash != (int)oldCash ) + { + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if( (int)buyMenuCash > (int)oldCash ) + { + // Play the "get cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } + else if( (int)buyMenuCash < (int)oldCash ) + { + // Play the "lose cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +void idPlayer::SetCash( float newCashAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + buyMenuCash = newCashAmount; + ClampCash( minCash, maxCash ); +} + +void idPlayer::ResetCash() +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + //buyMenuCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerStartingCash", 0 ); + + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + buyMenuCash = (float) gameLocal.serverInfo.GetInt("si_buyModeStartingCredits"); + ClampCash( minCash, maxCash ); +} + +/** + * Checks to see if the player can accept this item in their inventory + * + * weaponName Name of the weapon. + */ +int idPlayer::CanSelectWeapon(const char* weaponName) +{ + int weaponNum = -1; + if(weaponName == NULL) + return weaponNum; + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + weaponNum = i; + break; + } + } + } + + return weaponNum; +} + +// RITUAL END + +bool idPlayer::IsSpectatedClient( void ) const { + idPlayer *localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return false; + } + if ( localPlayer->spectating && localPlayer->spectator == entityNumber ) { + return true; + } + return false; +} diff --git a/source/mpgame/Player.h b/source/mpgame/Player.h new file mode 100644 index 0000000..fc85bcd --- /dev/null +++ b/source/mpgame/Player.h @@ -0,0 +1,1321 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYER_H__ +#define __GAME_PLAYER_H__ + +/* +=============================================================================== + + Player entity. + +=============================================================================== +*/ + +extern const idEventDef EV_Player_GetButtons; +extern const idEventDef EV_Player_GetMove; +extern const idEventDef EV_Player_GetViewAngles; +extern const idEventDef EV_Player_SetViewAngles; +extern const idEventDef EV_Player_EnableWeapon; +extern const idEventDef EV_Player_DisableWeapon; +extern const idEventDef EV_Player_ExitTeleporter; +extern const idEventDef EV_Player_SelectWeapon; +extern const idEventDef EV_Player_Freeze; +extern const idEventDef EV_SpectatorTouch; +extern const idEventDef EV_Player_SetArmor; +extern const idEventDef EV_Player_SetExtraProjPassEntity; +extern const idEventDef EV_Player_DamageEffect; + +const float THIRD_PERSON_FOCUS_DISTANCE = 512.0f; +const int LAND_DEFLECT_TIME = 150; +const int LAND_RETURN_TIME = 300; +const int FOCUS_TIME = 200; +const int FOCUS_GUI_TIME = 300; +const int FOCUS_USABLE_TIME = 100; + +const int MAX_WEAPONS = 16; +const int MAX_AMMO = 16; +const int CARRYOVER_FLAG_AMMO = 0x40000000; +const int CARRYOVER_FLAG_ARMOR_LIGHT = 0x20000000; +const int CARRYOVER_FLAG_ARMOR_HEAVY = 0x10000000; +const int CARRYOVER_WEAPONS_MASK = 0x0FFFFFFF; +const int CARRYOVER_FLAGS_MASK = 0xF0000000; + +const int MAX_SKILL_LEVELS = 4; + +const int ZERO_VOLUME = -40; // volume at zero +const int DMG_VOLUME = 5; // volume when taking damage +const int DEATH_VOLUME = 15; // volume at death + +const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five seconds + +// grep max_ammo_* in the multiplayer settings, all less than 512 (2^9) +const int ASYNC_PLAYER_INV_AMMO_BITS = 9; +const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60] +// NOTE: protocol 69 used 6 bits, but that's only used for client -> server traffic, so doesn't affect backwards protocol replay compat +const int IMPULSE_NUMBER_OF_BITS = 8; // allows for 2< items; + idStrList pdas; + idStrList pdaSecurity; + idStrList videos; + + idList levelTriggers; + + idInventory() { Clear(); } + ~idInventory() { Clear(); } + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Clear( void ); + void GivePowerUp( idPlayer* player, int powerup, int msec ); + void ClearPowerUps( void ); + void GetPersistantData( idDict &dict ); + void RestoreInventory( idPlayer *owner, const idDict &dict ); + bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped = false, bool checkOnly = false ); + void Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ); + int AmmoIndexForAmmoClass( const char *ammo_classname ) const; + int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + int AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired = NULL ); + const char * AmmoClassForWeaponClass( const char *weapon_classname); + +// RAVEN BEGIN +// mekberg: if the player can pick up the ammo at this time + bool DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ); +// RAVEN END + + int AmmoIndexForWeaponIndex( int weaponIndex ); + int StartingAmmoForWeaponIndex( int weaponIndex ); + int AmmoRegenStepForWeaponIndex( int weaponIndex ); + int AmmoRegenTimeForWeaponIndex( int weaponIndex ); + + int HasAmmo( int index, int amount ); + bool UseAmmo( int index, int amount ); + int HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first + + int nextItemPickup; + int nextItemNum; + int onePickupTime; + idList pickupItemNames; + idList objectiveNames; +// idList database; + + int secretAreasDiscovered; +}; + +class idPlayer : public idActor { +public: + + enum { + EVENT_IMPULSE = idEntity::EVENT_MAXEVENTS, + EVENT_EXIT_TELEPORTER, + EVENT_ABORT_TELEPORTER, + EVENT_POWERUP, + EVENT_SPECTATE, + EVENT_EMOTE, + EVENT_JUMP, + EVENT_MAXEVENTS + }; + + friend class idThread; + + usercmd_t usercmd; + + class idPlayerView playerView; // handles damage kicks and effects + + bool alreadyDidTeamAnnouncerSound; + bool noclip; + bool godmode; + int godmodeDamage; + bool undying; + + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles + idAngles spawnAngles; + idAngles viewAngles; // player view angles + idAngles cmdAngles; // player cmd angles + + int buttonMask; + int oldButtons; + int oldFlags; + + int lastSavingThrowTime; // for the "free miss" effect + + struct playerFlags_s { + bool forward :1; + bool backward :1; + bool strafeLeft :1; + bool strafeRight :1; + bool attackHeld :1; + bool weaponFired :1; + bool jump :1; + bool crouch :1; + bool onGround :1; + bool onLadder :1; + bool dead :1; + bool run :1; + bool pain :1; + bool hardLanding :1; + bool softLanding :1; + bool reload :1; + bool teleport :1; + bool turnLeft :1; + bool turnRight :1; + bool hearingLoss :1; + bool objectiveFailed :1; + bool noFallingDamage :1; + } pfl; + + // inventory + idInventory inventory; + + rvWeapon* weapon; + idEntityPtr weaponViewModel; + idEntityPtr weaponWorldModel; + const idDeclEntityDef* weaponDef; + + + idUserInterface * hud; // Common hud + idUserInterface * mphud; // hud overlay containing MP elements + + idUserInterface * objectiveSystem; + idUserInterface * cinematicHud; + bool objectiveSystemOpen; + bool objectiveButtonReleased; + bool disableHud; + bool showNewObjectives; + + int lastDmgTime; + int deathClearContentsTime; + bool doingDeathSkin; + int nextHealthPulse; // time when health will tick down + int nextAmmoRegenPulse[ MAX_AMMO ]; // time when ammo will regenerate + int nextArmorPulse; // time when armor will tick down + bool hiddenWeapon; // if the weapon is hidden ( in noWeapons maps ) + + // mp stuff + int spectator; + + bool scoreBoardOpen; + bool forceScoreBoard; + bool forceRespawn; + int forceScoreBoardTime; + bool allowedToRespawn; + bool inBuyZone; + bool inBuyZonePrev; + bool spectating; + int lastHitFrame; // game frame of the last hit sound feedback + bool lastHitArmor; + bool forcedReady; + int lastArenaChange; + + bool wantSpectate; // from userInfo + + bool weaponGone; // force stop firing + bool useInitialSpawns; // toggled by a map restart to be active for the first game spawn + bool isLagged; // replicated from server, true if packets haven't been received from client. + bool isChatting; // replicated from server, true if the player is chatting. + + int lastSpectateTeleport; + int latchedTeam; // need to track when team gets changed + int spawnedTime; // when client first enters the game + int hudTeam; + + idEntityPtr teleportEntity; // while being teleported, this is set to the entity we'll use for exit + int teleportKiller; // entity number of an entity killing us at teleporter exit + + idEntityPtr lastKiller; + + // timers + int minRespawnTime; // can respawn when time > this, force after g_forcerespawn + int maxRespawnTime; // force respawn after this time + + // the first person view values are always calculated, even + // if a third person view is used + idVec3 firstPersonViewOrigin; + idMat3 firstPersonViewAxis; + + idDragEntity dragEntity; + idVec3 intentDir; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr extraProjPassEntity; + + bool vsMsgState; + + int lastPickupTime; + + float buyMenuCash; + + float handicap; // multiplier for damage/health + +public: + CLASS_PROTOTYPE( idPlayer ); + + idPlayer(); + virtual ~idPlayer(); + + void Spawn( void ); + void Think( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + static const char* GetSpawnClassname ( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + void Init( void ); + void PrepareForRestart( void ); + virtual void Restart( void ); + void SetWeapon ( int weapon ); + void SetupWeaponEntity( void ); + bool SelectSpawnPoint( idVec3 &origin, idAngles &angles ); + void SpawnFromSpawnSpot( void ); + void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + void SetClipModel( bool forceSpectatorBBox = false ); // spectator mode uses a different bbox size + + void SavePersistantInfo( void ); + void RestorePersistantInfo( void ); + void SetLevelTrigger( const char *levelName, const char *triggerName ); + + bool UserInfoChanged( void ); + idDict * GetUserInfo( void ); + bool BalanceTeam( void ); + void CacheWeapons( void ); + + bool HandleESC( void ); + void EnterCinematic( void ); + void ExitCinematic( void ); + bool SkipCinematic( void ); + + void UpdateConditions( void ); + void SetViewAngles( const idAngles &angles ); + + void BiasIntentDir ( idVec3 newIntentDir, float prevBias = 199.0f ); + + // delta view angles to allow movers to rotate the view of the player + void UpdateDeltaViewAngles( const idAngles &angles ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ); + // use exitEntityNum to specify a teleport with private camera view and delayed exit + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ); + + void Kill( bool delayRespawn, bool nodamage ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + renderView_t * GetRenderView( void ); + void SmoothenRenderView( bool firstPerson ); + void CalculateRenderView( void ); // called every tic by player code + void CalculateFirstPersonView( void ); + + void DrawShadow( renderEntity_t *headRenderEnt ); + void DrawHUD( idUserInterface *hud ); + void DrawInput(); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + float DefaultFov( void ) const; + float CalcFov( bool honorZoom ); + void CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ); + void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void OffsetThirdPersonView( float angle, float range, float height, bool clip ); + void OffsetThirdPersonVehicleView( bool clip ); + bool OffsetThirdPersonTargetView( void ); + + bool Give( const char *statname, const char *value, bool dropped = false ); + bool GiveItem( idItem *item ); + void GiveItem( const char *name ); + + // Inventory + bool GiveInventoryItem( idDict *item ); + void RemoveInventoryItem( idDict *item ); + bool GiveInventoryItem( const char *name ); + void RemoveInventoryItem( const char *name ); + idDict * FindInventoryItem( const char *name ); + + // Wrist computer + void GiveObjective ( const char *title, const char *text, const char *screenshot ); + void CompleteObjective ( const char *title ); + void FailObjective ( const char *title ); + void GiveDatabaseEntry ( const idDict* dbEntry, bool hudPopup = true ); + bool IsObjectiveUp ( void ) const { return objectiveUp; } + idUserInterface * GetObjectiveHud ( void ) { return objectiveSystem; } + + // Secret Areas + void DiscoverSecretArea ( const char *description); + + void StartBossBattle ( idEntity* ent ); + + // Powerups + bool GivePowerUp ( int powerup, int time, bool team = false ); + void ClearPowerUps ( void ); + + void StartPowerUpEffect ( int powerup ); + void StopPowerUpEffect ( int powerup ); + + bool PowerUpActive ( int powerup ) const; + float PowerUpModifier ( int type ); + void ClearPowerup ( int i ); + const char* GetArenaPowerupString ( void ); + + // Helper methods to retrieving dictionaries + const idDeclEntityDef* GetWeaponDef ( int weaponIndex ); + const idDeclEntityDef* GetPowerupDef ( int powerupIndex ); + + // Weapons + bool GiveWeaponMods ( int mods ); + bool GiveWeaponMods ( int weapon, int mods ); + void GiveWeaponMod ( const char* weaponmod ); + + int SlotForWeapon ( const char *weaponName ); + + idEntity* DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity = vec3_origin ) const; + void DropPowerups ( void ); + idEntity* ResetFlag ( const char* itemClass, const idDict& customArgs ) const; + void RespawnFlags ( void ); + void DropWeapon ( void ); + + bool WeaponIsEnabled ( void ) const { return weaponEnabled; } + void ShowCrosshair ( void ); + void HideCrosshair ( void ); + + void Reload ( void ); + void NextWeapon ( void ); + void NextBestWeapon ( void ); + void PrevWeapon ( void ); + void LastWeapon ( void ); + void SelectWeapon ( int num, bool force ); + void SelectWeapon ( const char * ); + void AddProjectilesFired ( int count ); + void AddProjectileHits ( int count ); + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void WeaponLoweringCallback ( void ); + void WeaponRisingCallback ( void ); + void RemoveWeapon ( const char *weap ); + void Flashlight ( bool on ); + void ToggleFlashlight ( void ); + bool CanShowWeaponViewmodel ( void ) const; + + void TriggerHitSound ( bool armor ); + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + bool GuiActive( void ) { return focusType == FOCUS_GUI; } + + void GenerateImpulseForBuyAttempt( const char* itemName ); + bool AttemptToBuyItem( const char* itemName ); + bool AttemptToBuyTeamPowerup( const char* itemName ); + void UpdateTeamPowerups( bool isBuying = false ); + bool CanBuy( void ); + int CanSelectWeapon ( const char* weaponName ); + int GetItemCost(const char* itemName); + void PerformImpulse( int impulse ); + void Spectate( bool spectate, bool force = false ); + void ToggleObjectives ( void ); + void ToggleScoreboard( void ); + void RouteGuiMouse( idUserInterface *gui ); + void UpdateHud( void ); + idUserInterface* GetHud(); + const idUserInterface* GetHud() const; + void SetInfluenceFov( float fov ); + void SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ); + void SetInfluenceLevel( int level ); + int GetInfluenceLevel( void ) { return influenceActive; }; + void SetPrivateCameraView( idCamera *camView ); + idCamera * GetPrivateCameraView( void ) const { return privateCameraView; } + void StartFxFov( float duration ); + void UpdateHudWeapon( int displayWeapon = -1 ); +#ifdef _XENON + void ResetHUDWeaponSwitch( void ); +#endif + void UpdateHudStats( idUserInterface *hud ); + void UpdateHudAmmo( idUserInterface *hud ); + void UpdateHudPowerUps( idUserInterface *hud ); + void ShowTip( const char *title, const char *tip, bool autoHide ); + void HideTip( void ); + bool IsTipVisible( void ) { return tipUp; }; + void ShowObjective( const char *obj ); + void HideObjective( void ); + idVec3 GetEyePosition( void ) const; + + void LocalClientPredictionThink( void ); + void NonLocalClientPredictionThink( void ); + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + void WritePlayerStateToSnapshot( int lastSnapshotFrame, idBitMsgDelta &msg ) const; + void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + bool IsBeingTalkedTo ( void ); + bool IsReady ( void ); + bool IsRespawning ( void ); + bool IsInTeleport ( void ); + bool IsZoomed ( void ); + bool IsFlashlightOn ( void ); + virtual bool IsCrouching ( void ) const; + + // voice com muting + bool IsPlayerMuted ( idPlayer* player ) const; + bool IsPlayerMuted ( int clientNum ) const; + void MutePlayer ( idPlayer* player, bool mute ); + void MutePlayer ( int clientNum, bool mute ); + + + // buddy list + void SetFriend ( idPlayer* player, bool isFriend ); + void SetFriend ( int clientNum, bool isFriend ); + bool IsFriend ( idPlayer* player ) const; + bool IsFriend ( int clientNum ) const; + + // time joined server + int GetConnectTime ( void ) const; + void SetConnectTime ( int time ); + + // emotes + void SetEmote ( playerEmote_t emote ); + + // rankings + int GetRank ( void ) const; + void SetRank ( int newRank ); + + // arenas for tourney mode + int GetArena ( void ) const; + void SetArena ( int newArena ); + + idEntity *GetInfluenceEntity( void ) { return influenceEntity; }; + const idMaterial *GetInfluenceMaterial( void ) { return influenceMaterial; }; + float GetInfluenceRadius( void ) { return influenceRadius; }; + + // server side work for in/out of spectate. takes care of spawning it into the world as well + void ServerSpectate( bool spectate ); + + // for very specific usage. != GetPhysics() + idPhysics *GetPlayerPhysics( void ); + void TeleportDeath( int killer ); + void SetLeader( bool lead ); + bool IsLeader( void ); + + void UpdateModelSetup( bool forceReload = false ); + void UpdateSounds(const rvDeclPlayerModel *modelDecl); + + bool OnLadder( void ) const; + + rvViewWeapon* GetWeaponViewModel ( void ) const; + idAnimatedEntity* GetWeaponWorldModel ( void ) const; + int GetCurrentWeapon ( void ) const; + + bool IsGibbed ( void ) const; + const idVec3& GetGibDir ( void ) const; + + void SetInitialHud ( void ); + + void RemoveClientModel ( const char* entityDefName ); + void RemoveClientModels ( void ); + + rvClientEntityPtr AddClientModel ( const char* entityDefName, const char* shaderName = NULL ); + + void ClientGib ( const idVec3& dir ); + void ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ); + + + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + + void GUIMainNotice( const char* message, bool persist = false ); + void GUIFragNotice( const char* message, bool persist = false ); + + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); + + virtual void SetClipWorld( int newCW ); + virtual int GetClipWorld( void ) const; + + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + int GetWeaponIndex( const char* weaponName ) const; + + virtual void SetInstance( int newInstance ); + void JoinInstance( int newInstance ); + + void ClientInstanceJoin( void ); + void ClientInstanceLeave( void ); + + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void SetShowHud( bool showHud ); + bool GetShowHud( void ); + + + // mekberg: wrap saveMessages + void SaveMessage( void ); + + // mekberg: set pm_ cvars + void SetPMCVars( void ); + + void SetTourneyStatus( playerTourneyStatus_t newStatus ) { tourneyStatus = newStatus; } + playerTourneyStatus_t GetTourneyStatus( void ) { return tourneyStatus; } + const char* GetTextTourneyStatus( void ); + + const idVec4& GetHitscanTint( void ); + + void ForceScoreboard( bool force, int time ); + + // call only on the local player + idUserInterface* GetCursorGUI( void ); + + bool CanFire( void ) const; + + bool AllowedVoiceDest( int from ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer + itemBuyStatus_t ItemBuyStatus( const char* itemName ); + bool CanBuyItem( const char* itemName ); + void GiveCash( float cashDeltaAmount ); + void ClampCash( float minCash, float maxCash ); + void SetCash( float newCashAmount ); + void ResetCash(); +// RITUAL END + + bool IsFakeClient( void ) const { return entityNumber == ENTITYNUM_NONE; } + bool IsLocalClient( void ) const { return entityNumber == gameLocal.localClientNum || IsFakeClient(); } + bool IsSpectatedClient( void ) const; + bool IsWaitingForPredictAck( void ) const; + +protected: + void SetupHead( const char* modelKeyName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + float vehicleCameraDist; + + jointHandle_t hipJoint; + jointHandle_t chestJoint; + jointHandle_t neckLeanJoint; + + idPhysics_Player physicsObj; // player physics + + idList aasLocation; // for AI tracking the player + + idStr modelName; // current model name + const rvDeclPlayerModel* modelDecl; + + int bobFoot; + float bobFrac; + float bobfracsin; + int bobCycle; // for view bobbing and footstep generation + float xyspeed; + int stepUpTime; + float stepUpDelta; + float idealLegsYaw; + float legsYaw; + bool legsForward; + float oldViewYaw; + idAngles viewBobAngles; + idVec3 viewBob; + int landChange; + int landTime; + + // ddynerman: we read fall deltas from spawnargs, cache them to save some lookups + float fatalFallDelta; + float hardFallDelta; + float softFallDelta; + float noFallDelta; + + int carryOverCurrentWeapon; + int currentWeapon; + int idealWeapon; + int clientIdealWeaponPredictFrame; // for weapon switch prediction + int previousWeapon; + int weaponSwitchTime; + bool weaponEnabled; + bool showWeaponViewModel; + + rvClientEntityPtr clientHead; + bool objectivesEnabled; + bool flagCanFire; + + bool flashlightOn; + bool zoomed; + + bool reloadModel; + + const idDeclSkin * skin; + const idDeclSkin * weaponViewSkin; + const idDeclSkin * headSkin; + + const idDeclSkin * powerUpSkin; // active powerup skin + const idDeclSkin * gibSkin; + + const idMaterial* quadOverlay; + const idMaterial* hasteOverlay; + const idMaterial* regenerationOverlay; + const idMaterial* invisibilityOverlay; + const idMaterial* powerUpOverlay; + + int numProjectilesFired; // number of projectiles fired + int numProjectileHits; // number of hits on mobs + + bool airless; + int airTics; // set to pm_airTics at start, drops in vacuum + int lastAirDamage; + + bool gibDeath; + bool gibsLaunched; + idVec3 gibDir; + + playerTourneyStatus_t tourneyStatus; + bool isStrogg; + + idInterpolate zoomFov; + idInterpolate centerView; + bool fxFov; + + float influenceFov; + int influenceActive; // level of influence.. 1 == no gun or hud .. 2 == 1 + no movement + idEntity * influenceEntity; + const idMaterial * influenceMaterial; + float influenceRadius; + const idDeclSkin * influenceSkin; + + idCamera * privateCameraView; + + static const int NUM_LOGGED_VIEW_ANGLES = 64; // for weapon turning angle offsets + idAngles loggedViewAngles[NUM_LOGGED_VIEW_ANGLES]; // [gameLocal.framenum&(LOGGED_VIEW_ANGLES-1)] + static const int NUM_LOGGED_ACCELS = 16; // for weapon turning angle offsets + loggedAccel_t loggedAccel[NUM_LOGGED_ACCELS]; // [currentLoggedAccel & (NUM_LOGGED_ACCELS-1)] + int currentLoggedAccel; + + int demoViewAngleTime; + idAngles demoViewAngles; + + // if there is a focusGUIent, the attack button will be changed into mouse clicks + idUserInterface * focusUI; + int focusTime; + playerFocus_t focusType; + idEntityPtr focusEnt; + idUserInterface * focusBrackets; + int focusBracketsTime; + + bool targetFriendly; + + idEntityPtr talkingNPC; // NPC who's currently talking to us + int talkCursor; // show the state of the focusCharacter (0 == can't talk/dead, 1 == ready to talk, 2 == busy talking) + idUserInterface * cursor; + + idUserInterface * overlayHud; // a temporary hud overlay + int overlayHudTime; + + // full screen guis track mouse movements directly + int oldMouseX; + int oldMouseY; + + bool tipUp; + bool objectiveUp; + + float dynamicProtectionScale; // value to scale damage by due to dynamic protection + int lastDamageDef; + idVec3 lastDamageDir; + int lastDamageLocation; + + int predictedFrame; + idVec3 predictedOrigin; + idAngles predictedAngles; + bool predictedUpdated; + idVec3 predictionOriginError; + idAngles predictionAnglesError; + int predictionErrorTime; + + // mp + bool ready; // from userInfo + bool respawning; // set to true while in SpawnToPoint for telefrag checks + bool leader; // for sudden death situations + bool weaponCatchup; // raise up the weapon silently ( state catchups ) + bool isTelefragged; // proper obituaries + + int lastSpectateChange; + int lastTeleFX; + unsigned int lastSnapshotSequence; // track state hitches on clients + + int aimClientNum; // player num in aim + + idPlayer* lastImpulsePlayer; // the last player who gave me an impulse, may be null + + int arena; // current arena for tourney gameplay + + int connectTime; + int mutedPlayers; // bitfield set to which clients this player wants muted + int friendPlayers; // bitfield set to which clients this player has marked as friends + + int voiceDest[MAX_CONCURRENT_VOICES]; + int voiceDestTimes[MAX_CONCURRENT_VOICES]; + + int rank; + + int deathSkinTime; + bool deathStateHitch; + + playerEmote_t emote; + + int powerupEffectTime; + const idDecl *powerupEffect; + int powerupEffectType; + idList powerupEffectJoints; + rvClientEffectPtr hasteEffect; + rvClientEffectPtr flagEffect; + rvClientEffectPtr arenaEffect; + + rvClientEffectPtr teamHealthRegen; + bool teamHealthRegenPending; + rvClientEffectPtr teamAmmoRegen; + bool teamAmmoRegenPending; + rvClientEffectPtr teamDoubler; + bool teamDoublerPending; + + idVec4 hitscanTint; + // end mp + + int lastImpulseTime; // time of last impulse + idEntityPtr bossEnemy; + + const idDeclEntityDef* cachedWeaponDefs[ MAX_WEAPONS ]; + const idDeclEntityDef* cachedPowerupDefs[ POWERUP_MAX ]; + + bool weaponChangeIconsUp; + + int oldInventoryWeapons; + + const idDeclEntityDef* itemCosts; + + idVec3 leanVelocity; + + // hack to avoid jittery legs + int ignoreOnGroundUntilFrame; + bool prevOnGround; + + // lean props + float leanMaxSpeed; + float leanBlendRatio; + float leanMaxLateralAngle; + float leanMaxForwardAngle; + float leanHeadRatio; + float leanHipRatio; + + // flags when server is processing entity events, for acknowledge replies + bool serverReceiveEvent; + + bool WantSmoothing( void ) const; + void PredictionErrorDecay( void ); + + bool CanZoom(void); + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); + + void StopFiring( void ); + void FireWeapon( void ); + void Weapon_Combat( void ); + void Weapon_NPC( void ); + void Weapon_GUI( void ); + void Weapon_Vehicle ( void ); + void Weapon_Usable ( void ); + void SpectateFreeFly( bool force ); // ignore the timeout to force when followed spec is no longer valid + void SpectateCycle( void ); + idAngles GunTurningOffset( void ); + idVec3 GunAcceleratingOffset( void ); + + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + void BobCycle( const idVec3 &pushVelocity ); + void EvaluateControls( void ); + void AdjustSpeed( void ); + void AdjustBodyAngles( void ); + void Move( void ); + void SetSpectateOrigin( void ); + + void InitAASLocation( void ); + void SetAASLocation( void ); + + idUserInterface * ActiveGui( void ); + + void UpdateWeapon ( void ); + void UpdateSpectating ( void ); + void UpdateAir ( void ); + void UpdateGravity ( void ); + void HandleObjectiveInput ( void ); + void HandleCheats ( void ); + void ClearCheatState ( void ); + void UpdateViewAngles ( void ); + void UpdatePowerUps ( void ); + void UpdateDeathSkin ( bool state_hitch ); + void UpdateFocus ( void ); + void UpdateLocation ( void ); + void UpdateObjectiveInfo ( void ); + void UpdateIntentDir ( void ); + + void LoadDeferredModel ( void ); + + void ClearFocus ( void ); + void UpdateFocusCharacter ( idEntity* newEnt ); + void SetFocus ( playerFocus_t type, int focusTime, idEntity* ent, idUserInterface* ui ); + + void UpdateDeathShader ( bool state_hitch ); + +// void InitWeapon ( void ); + + bool IsLegsIdle ( bool crouching ) const; + + void CheckAckReply ( void ) const; + + void Event_GetButtons ( void ); + void Event_GetMove ( void ); + void Event_GetViewAngles ( void ); + void Event_SetViewAngles ( const idVec3 &vec ); + void Event_StopFxFov ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_GetCurrentWeapon ( void ); + void Event_GetPreviousWeapon ( void ); + void Event_SelectWeapon ( const char *weaponName ); + void Event_GetWeaponEntity ( void ); + void Event_ExitTeleporter ( void ); + void Event_HideTip ( void ); + void Event_LevelTrigger ( void ); + void Event_GetViewPos ( void ); + void Event_TeleportPlayer ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Freeze ( float f ); + void Event_HideDatabaseEntry ( void ); + void Event_ZoomIn ( void ); + void Event_ZoomOut ( void ); + void Event_FinishHearingLoss ( float fadeTime ); + void Event_GetAmmoData ( const char *ammoClass ); + void Event_RefillAmmo ( void ); + void Event_AllowFallDamage ( int toggle ); + + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + + // RAVEN BEGIN + // twhitaker: added Event_ApplyImpulse + void Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ); + + // mekberg: added sethealth + void Event_SetHealth ( float newHealth ); + void Event_SetArmor ( float newArmor ); + + void Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ); + void Event_DamageEffect ( const char *damageDefName, idEntity* _damageFromEnt ); + + // mekberg: allow enabling/disabling of objectives + void Event_EnableObjectives ( void ); + void Event_DisableObjectives ( void ); + + // mekberg: don't supress showing new objectives anymore + void Event_AllowNewObjectives ( void ); + + stateResult_t State_Wait_Alive ( const stateParms_t& parms ); + stateResult_t State_Wait_ReloadAnim ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_IdleThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + stateResult_t State_Torso_RaiseWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_LowerWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire_Windup ( const stateParms_t& parms ); + + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_Dead ( const stateParms_t& parms ); + stateResult_t State_Torso_Emote ( const stateParms_t& parms ); + + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Uncrouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Jump ( const stateParms_t& parms ); + stateResult_t State_Legs_Fall ( const stateParms_t& parms ); + stateResult_t State_Legs_Land ( const stateParms_t& parms ); + stateResult_t State_Legs_Dead ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( idPlayer ); +}; + +ID_INLINE bool idPlayer::IsBeingTalkedTo( void ) { + return talkingNPC!=NULL; +} + +ID_INLINE bool idPlayer::IsRespawning( void ) { + return respawning; +} + +ID_INLINE idPhysics* idPlayer::GetPlayerPhysics( void ) { + return &physicsObj; +} + +ID_INLINE bool idPlayer::IsInTeleport( void ) { + return ( teleportEntity.GetEntity() != NULL ); +} + +ID_INLINE void idPlayer::SetLeader( bool lead ) { + leader = lead; +} + +ID_INLINE bool idPlayer::IsLeader( void ) { + return leader; +} + +ID_INLINE bool idPlayer::IsZoomed( void ) { + return zoomed; +} + +ID_INLINE bool idPlayer::IsFlashlightOn( void ) { + return flashlightOn; +} + +ID_INLINE rvViewWeapon* idPlayer::GetWeaponViewModel( void ) const { + return weaponViewModel; +} + +ID_INLINE idAnimatedEntity* idPlayer::GetWeaponWorldModel( void ) const { + return weaponWorldModel; +} + +ID_INLINE int idPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +ID_INLINE bool idPlayer::IsGibbed( void ) const { + return gibDeath; +} + +ID_INLINE const idVec3& idPlayer::GetGibDir( void ) const { + return gibDir; +} + +ID_INLINE int idPlayer::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idPlayer::SetClipWorld( int newCW ) { + idEntity::SetClipWorld( newCW ); + + if( head.GetEntity() ) { + head.GetEntity()->SetClipWorld( newCW ); + + head.GetEntity()->UnlinkCombat(); + head.GetEntity()->LinkCombat(); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetClipWorld( newCW ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetClipWorld( newCW ); + } + } + + UnlinkCombat(); + LinkCombat(); +} + +ID_INLINE int idPlayer::GetWeaponIndex( const char* weaponName ) const { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( !idStr::Icmp( spawnArgs.GetString( va( "def_weapon%d", i ), "" ), weaponName ) ) { + return i; + } + } + + return 0; +} + +ID_INLINE idUserInterface* idPlayer::GetHud() { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE const idUserInterface* idPlayer::GetHud() const { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE bool idPlayer::IsPlayerMuted( idPlayer* player ) const { + return !!( mutedPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsPlayerMuted( int clientNum ) const { + return !!( mutedPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::MutePlayer( idPlayer* player, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << player->entityNumber ); + } else { + mutedPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::MutePlayer( int clientNum, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << clientNum ); + } else { + mutedPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE bool idPlayer::IsFriend( idPlayer* player ) const { + return !!( friendPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsFriend( int clientNum ) const { + return !!( friendPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::SetFriend( idPlayer* player, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << player->entityNumber ); + } else { + friendPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::SetFriend( int clientNum, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << clientNum ); + } else { + friendPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE int idPlayer::GetConnectTime( void ) const { + return connectTime; +} + +ID_INLINE void idPlayer::SetConnectTime( int time ) { + connectTime = time; +} + +ID_INLINE int idPlayer::GetRank( void ) const { + return rank; +} + +ID_INLINE void idPlayer::SetRank( int newRank ) { + rank = newRank; +} + +ID_INLINE int idPlayer::GetArena( void ) const { + return arena; +} + +ID_INLINE bool idPlayer::CanFire( void ) const { + return flagCanFire; +} + +ID_INLINE bool idPlayer::IsWaitingForPredictAck( void ) const { + return ( clientIdealWeaponPredictFrame != -1 ); +} + +#endif /* !__GAME_PLAYER_H__ */ + +// RAVEN END diff --git a/source/mpgame/PlayerView.cpp b/source/mpgame/PlayerView.cpp new file mode 100644 index 0000000..9044b79 --- /dev/null +++ b/source/mpgame/PlayerView.cpp @@ -0,0 +1,781 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if defined(_XENON) && (defined(_PROFILE) || defined(_DEBUG)) +#include "../sys/xenon/ProfilingSupport.h" +#endif + +const int IMPULSE_DELAY = 150; +/* +============== +idPlayerView::idPlayerView +============== +*/ +idPlayerView::idPlayerView() { + memset( screenBlobs, 0, sizeof( screenBlobs ) ); + memset( &view, 0, sizeof( view ) ); + player = NULL; + dvMaterial = NULL; + dvMaterialBlend = NULL; + tunnelMaterial = NULL; + armorMaterial = NULL; + bloodSprayMaterial = NULL; + bfgVision = false; + dvFinishTime = 0; + tvFinishTime = 0; + tvStartTime = 0; + kickFinishTime = 0; + kickAngles.Zero(); + lastDamageTime = 0.0f; + fadeTime = 0; + fadeRate = 0.0; + fadeFromColor.Zero(); + fadeToColor.Zero(); + fadeColor.Zero(); + + ClearEffects(); + + dvScale = 1.0f; + shakeScale = 1.0f; + tvScale = 1.0f; +} + +/* +============== +idPlayerView::Save +============== +*/ +void idPlayerView::Save( idSaveGame *savefile ) const { + int i; + const screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->WriteMaterial( blob->material ); + savefile->WriteFloat( blob->x ); + savefile->WriteFloat( blob->y ); + savefile->WriteFloat( blob->w ); + savefile->WriteFloat( blob->h ); + savefile->WriteFloat( blob->s1 ); + savefile->WriteFloat( blob->t1 ); + savefile->WriteFloat( blob->s2 ); + savefile->WriteFloat( blob->t2 ); + savefile->WriteInt( blob->finishTime ); + savefile->WriteInt( blob->startFadeTime ); + savefile->WriteFloat( blob->driftAmount ); + } + + savefile->WriteInt( dvFinishTime ); + savefile->WriteMaterial( dvMaterial ); + + savefile->WriteMaterial( dvMaterialBlend ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( dvScale ); + + savefile->WriteInt( kickFinishTime ); + savefile->WriteAngles( kickAngles ); + + savefile->WriteBool( bfgVision ); + + savefile->WriteMaterial( tunnelMaterial ); + savefile->WriteMaterial( armorMaterial ); + + savefile->WriteMaterial( bloodSprayMaterial ); + + savefile->WriteFloat( lastDamageTime ); + + savefile->WriteFloat( shakeFinishTime ); + savefile->WriteFloat( shakeScale ); + savefile->WriteFloat( tvScale ); + savefile->WriteInt( tvFinishTime ); + savefile->WriteInt( tvStartTime ); + + savefile->WriteVec4( fadeColor ); + savefile->WriteVec4( fadeToColor ); + savefile->WriteVec4( fadeFromColor ); + savefile->WriteFloat( fadeRate ); + savefile->WriteInt( fadeTime ); + + savefile->WriteObject( player ); + savefile->WriteRenderView( view ); +} + +/* +============== +idPlayerView::Restore +============== +*/ +void idPlayerView::Restore( idRestoreGame *savefile ) { + int i; + screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->ReadMaterial( blob->material ); + savefile->ReadFloat( blob->x ); + savefile->ReadFloat( blob->y ); + savefile->ReadFloat( blob->w ); + savefile->ReadFloat( blob->h ); + savefile->ReadFloat( blob->s1 ); + savefile->ReadFloat( blob->t1 ); + savefile->ReadFloat( blob->s2 ); + savefile->ReadFloat( blob->t2 ); + savefile->ReadInt( blob->finishTime ); + savefile->ReadInt( blob->startFadeTime ); + savefile->ReadFloat( blob->driftAmount ); + } + + savefile->ReadInt( dvFinishTime ); + savefile->ReadMaterial( dvMaterial ); + + savefile->ReadMaterial( dvMaterialBlend ); // cnicholson: Added unrestored var + + savefile->ReadFloat( dvScale ); + + savefile->ReadInt( kickFinishTime ); + savefile->ReadAngles( kickAngles ); + + savefile->ReadBool( bfgVision ); + + savefile->ReadMaterial( tunnelMaterial ); + savefile->ReadMaterial( armorMaterial ); + + savefile->ReadMaterial( bloodSprayMaterial ); + + savefile->ReadFloat( lastDamageTime ); + + savefile->ReadFloat( shakeFinishTime ); + savefile->ReadFloat( shakeScale ); + savefile->ReadFloat( tvScale ); + savefile->ReadInt( tvFinishTime ); + savefile->ReadInt ( tvStartTime ); + + savefile->ReadVec4( fadeColor ); + savefile->ReadVec4( fadeToColor ); + savefile->ReadVec4( fadeFromColor ); + savefile->ReadFloat( fadeRate ); + savefile->ReadInt( fadeTime ); + + savefile->ReadObject( reinterpret_cast( player ) ); + savefile->ReadRenderView( view ); +} + +/* +============== +idPlayerView::SetPlayerEntity +============== +*/ +void idPlayerView::SetPlayerEntity( idPlayer *playerEnt ) { + player = playerEnt; + + const idDict* dict = NULL; + if( !playerEnt ) { + return; + } + + dict = gameLocal.FindEntityDefDict( playerEnt->spawnArgs.GetString("def_playerView"), false ); + + if( dict ) { + dvMaterial = declManager->FindMaterial( dict->GetString("mtr_doubleVision") ); + dvMaterialBlend = declManager->FindMaterial( dict->GetString("mtr_doubleVisionBlend") ); + tunnelMaterial = declManager->FindMaterial( dict->GetString("mtr_tunnel") ); + armorMaterial = declManager->FindMaterial( dict->GetString("mtr_armourEffect") ); + bloodSprayMaterial = declManager->FindMaterial( dict->GetString("mtr_bloodspray") ); + } +} + +/* +============== +idPlayerView::ClearEffects +============== +*/ +void idPlayerView::ClearEffects() { + lastDamageTime = MS2SEC( gameLocal.time - 99999 ); + + dvFinishTime = ( gameLocal.time - 99999 ); + tvFinishTime = ( gameLocal.time - 99999 ); + tvStartTime = ( gameLocal.time - 99999 ); + kickFinishTime = ( gameLocal.time - 99999 ); + shakeFinishTime = gameLocal.time; + + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlobs[i].finishTime = gameLocal.time; + } + + fadeTime = 0; + bfgVision = false; +} + +/* +============== +idPlayerView::GetScreenBlob +============== +*/ +screenBlob_t *idPlayerView::GetScreenBlob() { + screenBlob_t *oldest = &screenBlobs[0]; + + for ( int i = 1 ; i < MAX_SCREEN_BLOBS ; i++ ) { + if ( screenBlobs[i].finishTime < oldest->finishTime ) { + oldest = &screenBlobs[i]; + } + } + return oldest; +} + +/* +============== +idPlayerView::DamageImpulse + +LocalKickDir is the direction of force in the player's coordinate system, +which will determine the head kick direction +============== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ) { + // + // double vision effect + // + float tvTime = damageDef->GetFloat( "tv_time" ); + if ( tvTime ) { + tvStartTime = gameLocal.time; + tvFinishTime = gameLocal.time + tvTime; + damageDef->GetFloat ( "tv_scale", "1", tvScale ); + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + // + // double vision effect + // + float dvTime = damageDef->GetFloat( "dv_time" ); + if ( dvTime ) { + dvFinishTime = gameLocal.time + (g_dvTime.GetFloat() * dvTime); + damageDef->GetFloat ( "dv_scale", "1", dvScale ); + } + + // + // head angle kick + // + const float modifierScale = 0.25f; + const float inverseModifier = ( 1.0f - modifierScale ); + + float modifier = idMath::ClampFloat( 0.0f, inverseModifier, damage / 100.0f * inverseModifier ) + modifierScale; + float kickTime = damageDef->GetFloat( "kick_time" ); + + if ( kickTime ) { + kickFinishTime = gameLocal.time + g_kickTime.GetFloat() * kickTime; + + // forward / back kick will pitch view + kickAngles[0] = localKickDir[0]; + + // side kick will yaw view + kickAngles[1] = localKickDir[1]*0.5f; + + // up / down kick will pitch view + kickAngles[0] += localKickDir[2]; + + // roll will come from side + kickAngles[2] = localKickDir[1]; + + float kickAmplitude = damageDef->GetFloat( "kick_amplitude" ); + if ( kickAmplitude ) { + kickAngles *= kickAmplitude; + } + + if ( modifier < kickAmplitude ) { + modifier = kickAmplitude; + } + } + else { + kickTime = 500; + } + + // + // screen blob + // + float blobTime = damageDef->GetFloat( "blob_time" ); + if ( blobTime ) { + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + blobTime * g_blobTime.GetFloat(); + + const char *materialName = damageDef->GetString( "mtr_blob" ); + blob->material = declManager->FindMaterial( materialName ); + blob->x = damageDef->GetFloat( "blob_x" ); + blob->x += ( gameLocal.random.RandomInt()&63 ) - 32; + blob->y = damageDef->GetFloat( "blob_y" ); + blob->y += ( gameLocal.random.RandomInt()&63 ) - 32; + + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = damageDef->GetFloat( "blob_width" ) * g_blobSize.GetFloat() * scale; + blob->h = damageDef->GetFloat( "blob_height" ) * g_blobSize.GetFloat() * scale; + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + } + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); +} +// RAVEN END + +/* +================== +idPlayerView::AddBloodSpray + +If we need a more generic way to add blobs then we can do that +but having it localized here lets the material be pre-looked up etc. +================== +*/ +void idPlayerView::AddBloodSpray( float duration ) { +/* + if ( duration <= 0 || bloodSprayMaterial == NULL || g_skipViewEffects.GetBool() ) { + return; + } + // visit this for chainsaw + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + ( duration * 1000 ); + blob->material = bloodSprayMaterial; + blob->x = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->y = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->driftAmount = 0.5f + gameLocal.random.CRandomFloat() * 0.5; + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = 600 * g_blobSize.GetFloat() * scale; + blob->h = 480 * g_blobSize.GetFloat() * scale; + float s1 = 0.0f; + float t1 = 0.0f; + float s2 = 1.0f; + float t2 = 1.0f; + if ( blob->driftAmount < 0.6 ) { + s1 = 1.0f; + s2 = 0.0f; + } else if ( blob->driftAmount < 0.75 ) { + t1 = 1.0f; + t2 = 0.0f; + } else if ( blob->driftAmount < 0.85 ) { + s1 = 1.0f; + s2 = 0.0f; + t1 = 1.0f; + t2 = 0.0f; + } + blob->s1 = s1; + blob->t1 = t1; + blob->s2 = s2; + blob->t2 = t2; +*/ +} + +/* +================== +idPlayerView::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + int recoilTime; + + recoilTime = weaponDef->GetInt( "recoilTime" ); + // don't shorten a damage kick in progress + if ( recoilTime && recoilTime > (kickFinishTime - gameLocal.time) ) { + idAngles angles; + weaponDef->GetAngles( "recoilAngles", "5 0 0", angles ); + kickAngles = angles; + int finish = gameLocal.time + g_kickTime.GetFloat() * recoilTime; + kickFinishTime = finish; + } + +} + +/* +=================== +idPlayerView::CalculateShake +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +float idPlayerView::CalculateShake( idAngles &shakeAngleOffset ) const { + idVec3 origin, matrix; + + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, player->firstPersonViewOrigin ); + // + // shakeVolume should somehow be molded into an angle here + // it should be thought of as being in the range 0.0 -> 1.0, although + // since CurrentShakeAmplitudeForPosition() returns all the shake sounds + // the player can hear, it can go over 1.0 too. + // + shakeAngleOffset[0] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[1] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[2] = gameLocal.random.CRandomFloat() * shakeVolume; + + return shakeVolume; +} +// RAVEN END + +/* +=================== +idPlayerView::AngleOffset + + kickVector, a world space direction that the attack should +=================== +*/ +idAngles idPlayerView::AngleOffset() const { + idAngles ang; + + ang.Zero(); + + if ( gameLocal.time < kickFinishTime ) { + float offset = kickFinishTime - gameLocal.time; + + ang = kickAngles * offset * offset * g_kickAmplitude.GetFloat(); + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( ang[i] > 70.0f ) { + ang[i] = 70.0f; + } else if ( ang[i] < -70.0f ) { + ang[i] = -70.0f; + } + } + } + return ang; +} + +/* +=================== +idPlayerView::ShakeOffsets +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const { + float shakeVolume = 0.0f; + shakeOffset.Zero(); + shakeAngleOffset.Zero(); + + if( gameLocal.isMultiplayer ) { + return; + } + + shakeVolume = CalculateShake( shakeAngleOffset ); + + if( gameLocal.time < shakeFinishTime ) { + float offset = ( shakeFinishTime - gameLocal.time ) * shakeScale * 0.001f; + + shakeOffset[0] = idMath::ClampFloat( bounds[0][0] - 1.0f, bounds[1][0] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[1] = idMath::ClampFloat( bounds[0][1] - 1.0f, bounds[1][1] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[2] = idMath::ClampFloat( bounds[0][2] - 1.0f, bounds[1][2] + 1.0f, rvRandom::flrand( -offset, offset ) ); + + shakeAngleOffset[0] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[1] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[2] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + } +} +// RAVEN END + +/* +================== +idPlayerView::SingleView +================== +*/ +void idPlayerView::SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags ) { + // normal rendering + if ( !view ) { + return; + } + + if ( !( RF_GUI_ONLY & renderFlags ) ) { + // jscott: portal sky rendering with KRABS + idCamera *portalSky = gameLocal.GetPortalSky(); + if ( portalSky ) { + renderView_t portalSkyView = *view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, ( renderFlags & ( ~RF_PRIMARY_VIEW ) ) | RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( view, renderFlags | RF_PENUMBRA_MAP ); + } + + if ( RF_NO_GUI & renderFlags ) { + return; + } + + // draw screen blobs + if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlob_t *blob = &screenBlobs[i]; + if ( blob->finishTime <= gameLocal.time ) { + continue; + } + + blob->y += blob->driftAmount; + + float fade = (float)( blob->finishTime - gameLocal.time ) / ( blob->finishTime - blob->startFadeTime ); + if ( fade > 1.0f ) { + fade = 1.0f; + } + if ( fade ) { + renderSystem->SetColor4( 1,1,1,fade ); + renderSystem->DrawStretchPic( blob->x, blob->y, blob->w, blob->h,blob->s1, blob->t1, blob->s2, blob->t2, blob->material ); + } + } + + // Render tunnel vision + if ( gameLocal.time < tvFinishTime ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, tvScale * ((float)(tvFinishTime - gameLocal.time) / (float)(tvFinishTime - tvStartTime)) ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } + + player->DrawHUD( hud ); + + +/* + // tunnel vision + float health = 0.0f; + if ( g_testHealthVision.GetFloat() != 0.0f ) { + health = g_testHealthVision.GetFloat(); + } else { + health = player->health; + } + float alpha = health / 100.0f; + if ( alpha < 0.0f ) { + alpha = 0.0f; + } + if ( alpha > 1.0f ) { + alpha = 1.0f; + } + + if ( alpha < 1.0f ) { + renderSystem->SetColor4( ( player->health <= 0.0f ) ? MS2SEC( gameLocal.time ) : lastDamageTime, 1.0f, 1.0f, ( player->health <= 0.0f ) ? 0.0f : alpha ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } +*/ + + + + // Render the object system + // RAVEN BEGIN + // twhitaker: always draw objective system + if ( player->objectiveSystem ) { + player->objectiveSystem->Redraw( gameLocal.time ); + } + // RAVEN END + } + + // test a single material drawn over everything + if ( g_testPostProcess.GetString()[0] ) { + const idMaterial *mtr = declManager->FindMaterial( g_testPostProcess.GetString(), false ); + if ( !mtr ) { + common->Printf( "Material not found.\n" ); + g_testPostProcess.SetString( "" ); + } else { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, mtr ); + } + } +} + +/* +=================== +idPlayerView::DoubleVision +=================== +*/ +void idPlayerView::DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ) { + + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } + + float scale = offset * g_dvAmplitude.GetFloat() * dvScale; + if( scale < 0.0f ) { + return; + } + + if ( scale > 0.5f ) { + scale = 0.5f; + } + float shift = scale * idMath::Sin( idMath::Sqrt ( offset ) * g_dvFrequency.GetFloat() ); + shift = fabs( shift ); + + // if double vision, render to a texture + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view, RF_NO_GUI ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + + // carry red tint if in berserk mode + idVec4 color(1, 1, 1, 1); + + renderSystem->SetColor4( color.x, color.y, color.z, 1.0f ); +// RAVEN BEGIN +// jnewquist: Call DrawStretchCopy, which will flip the texcoords for D3D + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, shift, 1, 1, 0, dvMaterial ); + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, 1-shift, 0, dvMaterialBlend ); +// RAVEN END +} + +/* +================= +idPlayerView::Flash + +flashes the player view with the given color +================= +*/ +void idPlayerView::Flash(idVec4 color, int time ) { + Fade(idVec4(0, 0, 0, 0), time); + fadeFromColor = colorWhite; +} + +/* +================= +idPlayerView::Fade + +used for level transition fades +assumes: color.w is 0 or 1 +================= +*/ +void idPlayerView::Fade( idVec4 color, int time ) { + + if ( !fadeTime ) { + fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] ); + } else { + fadeFromColor = fadeColor; + } + fadeToColor = color; + + if ( time <= 0 ) { + fadeRate = 0; + time = 0; + fadeColor = fadeToColor; + } else { + fadeRate = 1.0f / ( float )time; + } + + if ( gameLocal.realClientTime == 0 && time == 0 ) { + fadeTime = 1; + } else { + fadeTime = gameLocal.realClientTime + time; + } +} + +/* +================= +idPlayerView::ScreenFade +================= +*/ +void idPlayerView::ScreenFade() { + int msec; + float t; + + if ( !fadeTime ) { + return; + } + + msec = fadeTime - gameLocal.realClientTime; + + if ( msec <= 0 ) { + fadeColor = fadeToColor; + if ( fadeColor[ 3 ] == 0.0f ) { + fadeTime = 0; + } + } else { + t = ( float )msec * fadeRate; + fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t ); + } + + if ( fadeColor[ 3 ] != 0.0f ) { + renderSystem->SetColor4( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } +} + +/* +=================== +idPlayerView::InfluenceVision +=================== +*/ +void idPlayerView::InfluenceVision( idUserInterface *hud, const renderView_t *view ) { + + float distance = 0.0f; + float pct = 1.0f; + if ( player->GetInfluenceEntity() ) { + distance = ( player->GetInfluenceEntity()->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).Length(); + if ( player->GetInfluenceRadius() != 0.0f && distance < player->GetInfluenceRadius() ) { + pct = distance / player->GetInfluenceRadius(); + pct = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, pct ); + } + } + if ( player->GetInfluenceMaterial() ) { + SingleView( hud, view ); + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, pct ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, player->GetInfluenceMaterial() ); + } else if ( player->GetInfluenceEntity() == NULL ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } else { + int offset = 25 + idMath::Sin ( gameLocal.time ); + DoubleVision( hud, view, pct * offset ); + } +} + +/* +=================== +idPlayerView::RenderPlayerView +=================== +*/ +void idPlayerView::RenderPlayerView( idUserInterface *hud ) { + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( !view ) { + return; + } + + bool guiRendered = false; + + // place the sound origin for the player + soundSystem->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, "Undefined" ); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if ( player->GetInfluenceMaterial() || player->GetInfluenceEntity() ) { + InfluenceVision( hud, view ); + guiRendered = true; + } else if ( g_doubleVision.GetBool() && gameLocal.time < dvFinishTime ) { + DoubleVision( hud, view, dvFinishTime - gameLocal.time ); + guiRendered = false; + } else { + SingleView( hud, view, RF_NO_GUI | RF_PRIMARY_VIEW ); + } + + // Now draw GUI's. + if ( !guiRendered ) { + SingleView( hud, view, RF_GUI_ONLY ); + } + + ScreenFade(); + } + + if ( net_clientLagOMeter.GetBool() && gameLocal.isClient && !( gameLocal.GetDemoState() == DEMO_PLAYING && ( gameLocal.IsServerDemoPlaying() || gameLocal.IsTimeDemo() ) ) ) { + gameLocal.lagometer.Draw(); + } +} + +// RAVEN END diff --git a/source/mpgame/PlayerView.h b/source/mpgame/PlayerView.h new file mode 100644 index 0000000..17ddd98 --- /dev/null +++ b/source/mpgame/PlayerView.h @@ -0,0 +1,142 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYERVIEW_H__ +#define __GAME_PLAYERVIEW_H__ + +/* +=============================================================================== + + Player view. + +=============================================================================== +*/ + +// screenBlob_t are for the on-screen damage claw marks, etc +typedef struct { + const idMaterial * material; + float x, y, w, h; + float s1, t1, s2, t2; + int finishTime; + int startFadeTime; + float driftAmount; +} screenBlob_t; + +#define MAX_SCREEN_BLOBS 8 + +class idPlayerView { +public: + idPlayerView(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayerEntity( class idPlayer *playerEnt ); + + void ClearEffects( void ); + +// RAVEN BEGIN +// jnewquist: Controller rumble + void DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ); +// RAVEN END + + void WeaponFireFeedback( const idDict *weaponDef ); + + idAngles AngleOffset( void ) const; // returns the current kick angle + +// RAVEN BEGIN +// jnewquist: Controller rumble + float CalculateShake( idAngles &shakeAngleOffset ) const; +// RAVEN END + +// RAVEN BEGIN +// jscott: for screen shake + void ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const; +// RAVEN END + + // adds little entities to the renderer for local blood blobs, etc + + // this may involve rendering to a texture and displaying + // that with a warp model or in double vision mode + void RenderPlayerView( idUserInterface *hud ); + + void Fade( idVec4 color, int time ); + + void Flash( idVec4 color, int time ); + + void AddBloodSpray( float duration ); + + // temp for view testing + void EnableBFGVision( bool b ) { bfgVision = b; }; + +// RAVEN BEGIN +// jscott: accessors required for the fx system + void SetDoubleVisionParms( float time, float scale ) { dvFinishTime = SEC2MS( time ); dvScale = scale; } + void SetShakeParms( float time, float scale ) { shakeFinishTime = SEC2MS( time ); shakeScale = scale; } + void SetTunnelParms( float time, float scale ) { tvStartTime = gameLocal.time; tvFinishTime = tvStartTime + time; tvScale = 1.0f / scale; } +// RAVEN END + +private: +// RAVEN BEGIN +// AReis: Modified SingleView() signature to include renderFlags variable. + void SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags = RF_NORMAL ); +// RAVEN END + void DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ); + void BerserkVision( idUserInterface *hud, const renderView_t *view ); + void InfluenceVision( idUserInterface *hud, const renderView_t *view ); + void ScreenFade(); + + screenBlob_t * GetScreenBlob(); + + screenBlob_t screenBlobs[MAX_SCREEN_BLOBS]; + + int dvFinishTime; // double vision will be stopped at this time + const idMaterial * dvMaterial; // material to take the double vision screen shot +// RAVEN BEGIN +// jscott: to make double vision work with alpha components + const idMaterial * dvMaterialBlend; +// jscott: for effects + float dvScale; +// RAVEN END + + int kickFinishTime; // view kick will be stopped at this time + idAngles kickAngles; + + bool bfgVision; // + + const idMaterial * tunnelMaterial; // health tunnel vision + const idMaterial * armorMaterial; // armor damage view effect +// RAVEN BEGIN +// bdube: not using these +// const idMaterial * berserkMaterial; // berserk effect +// const idMaterial * irGogglesMaterial; // ir effect + const idMaterial * bloodSprayMaterial; // blood spray +// const idMaterial * bfgMaterial; // when targeted with BFG +// RAVEN END + + float lastDamageTime; // accentuate the tunnel effect for a while + +// RAVEN BEGIN +// jscott: for effects + float shakeFinishTime; + float shakeScale; + float tvScale; + int tvFinishTime; + int tvStartTime; +// RAVEN END + + idVec4 fadeColor; // fade color + idVec4 fadeToColor; // color to fade to + idVec4 fadeFromColor; // color to fade from + float fadeRate; // fade rate + int fadeTime; // fade time + + idPlayer * player; + renderView_t view; +}; + +#endif /* !__GAME_PLAYERVIEW_H__ */ + +// RAVEN END diff --git a/source/mpgame/Player_Cheats.cpp b/source/mpgame/Player_Cheats.cpp new file mode 100644 index 0000000..a4e3ccc --- /dev/null +++ b/source/mpgame/Player_Cheats.cpp @@ -0,0 +1,16 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +============== +idPlayer::HandleCheats +============== +*/ +void idPlayer::HandleCheats() { +} + + +void idPlayer::ClearCheatState() { +} diff --git a/source/mpgame/Player_States.cpp b/source/mpgame/Player_States.cpp new file mode 100644 index 0000000..15d20a5 --- /dev/null +++ b/source/mpgame/Player_States.cpp @@ -0,0 +1,941 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +CLASS_STATES_DECLARATION ( idPlayer ) + + // Wait States + STATE ( "Wait_Alive", idPlayer::State_Wait_Alive ) + STATE ( "Wait_ReloadAnim", idPlayer::State_Wait_ReloadAnim ) + + // Torso States + STATE ( "Torso_Idle", idPlayer::State_Torso_Idle ) + STATE ( "Torso_IdleThink", idPlayer::State_Torso_IdleThink ) + STATE ( "Torso_Teleport", idPlayer::State_Torso_Teleport ) + STATE ( "Torso_RaiseWeapon", idPlayer::State_Torso_RaiseWeapon ) + STATE ( "Torso_LowerWeapon", idPlayer::State_Torso_LowerWeapon ) + STATE ( "Torso_Fire", idPlayer::State_Torso_Fire ) + STATE ( "Torso_Fire_Windup", idPlayer::State_Torso_Fire_Windup ) + STATE ( "Torso_Reload", idPlayer::State_Torso_Reload ) + STATE ( "Torso_Pain", idPlayer::State_Torso_Pain ) + STATE ( "Torso_Dead", idPlayer::State_Torso_Dead ) + STATE ( "Torso_Emote", idPlayer::State_Torso_Emote ) + + // Leg States + STATE ( "Legs_Idle", idPlayer::State_Legs_Idle ) + STATE ( "Legs_Crouch", idPlayer::State_Legs_Crouch ) + STATE ( "Legs_Uncrouch", idPlayer::State_Legs_Uncrouch ) + STATE ( "Legs_Run_Forward", idPlayer::State_Legs_Run_Forward ) + STATE ( "Legs_Run_Backward", idPlayer::State_Legs_Run_Backward ) + STATE ( "Legs_Run_Left", idPlayer::State_Legs_Run_Left ) + STATE ( "Legs_Run_Right", idPlayer::State_Legs_Run_Right ) + STATE ( "Legs_Walk_Forward", idPlayer::State_Legs_Walk_Forward ) + STATE ( "Legs_Walk_Backward", idPlayer::State_Legs_Walk_Backward ) + STATE ( "Legs_Walk_Left", idPlayer::State_Legs_Walk_Left ) + STATE ( "Legs_Walk_Right", idPlayer::State_Legs_Walk_Right ) + STATE ( "Legs_Crouch_Idle", idPlayer::State_Legs_Crouch_Idle ) + STATE ( "Legs_Crouch_Forward", idPlayer::State_Legs_Crouch_Forward ) + STATE ( "Legs_Crouch_Backward", idPlayer::State_Legs_Crouch_Backward ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Jump", idPlayer::State_Legs_Jump ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Land", idPlayer::State_Legs_Land ) + STATE ( "Legs_Dead", idPlayer::State_Legs_Dead ) + +END_CLASS_STATES + +/* +================ +idPlayer::State_Torso_Idle +================ +*/ +stateResult_t idPlayer::State_Torso_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT == State_Torso_IdleThink ( parms ) ) { + PlayCycle ( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_IdleThink", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_IdleThink +================ +*/ +stateResult_t idPlayer::State_Torso_IdleThink ( const stateParms_t& parms ) { + if ( pfl.teleport ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Teleport", 0 ); + return SRESULT_DONE; + } + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 0 ); + return SRESULT_DONE; + } + + if( pfl.attackHeld && weapon && weapon->wfl.hasWindupAnim) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_Windup", 0 ); + return SRESULT_DONE; + } + + if ( pfl.attackHeld && HasAnim ( ANIMCHANNEL_TORSO, "startfire" ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_StartFire", 2 ); + return SRESULT_DONE; + } + if ( pfl.pain ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); + return SRESULT_DONE; + } + if ( emote != PE_NONE ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Emote", 5 ); + return SRESULT_DONE; + } + + + return SRESULT_WAIT; +} + +/* +================ +idPlayer::State_Torso_Teleport +================ +*/ +stateResult_t idPlayer::State_Torso_Teleport ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + pfl.teleport = false; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Torso_RaiseWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_RaiseWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "raise", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_LowerWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_LowerWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "lower", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire +================ +*/ +stateResult_t idPlayer::State_Torso_Fire ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_FIRE_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_FIRE_WAIT); + + // Wait for the firing animation to be finished + case TORSO_FIRE_WAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", parms.blendFrames ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Keep the gun aimed but dont shoot + case TORSO_FIRE_AIM: + PlayAnim ( ANIMCHANNEL_TORSO, "aim", 3 ); + return SRESULT_STAGE(TORSO_FIRE_AIMWAIT); + + // Keep the gun aimed as long as the attack button is held and nothing is firing + case TORSO_FIRE_AIMWAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 3 ); + return SRESULT_DONE; + } else if ( !pfl.attackHeld ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire_Windup +================ +*/ +stateResult_t idPlayer::State_Torso_Fire_Windup ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_WINDUP_WAIT, + TORSO_FIRE_LOOP, + TORSO_FIRE_WAIT, + TORSO_WINDDOWN_START, + TORSO_WINDDOWN_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + //jshepard: HACK for now we're blending here, but we need to support charge up anims here + PlayAnim ( ANIMCHANNEL_TORSO, "fire", 4 ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_WINDUP_WAIT); + + // wait for the windup anim to end, or the attackHeld to be false. + case TORSO_WINDUP_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + if( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames )) { + return SRESULT_STAGE( TORSO_FIRE_LOOP ); + } + return SRESULT_WAIT; + + // play the firing loop + case TORSO_FIRE_LOOP: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + + // loop the fire anim + case TORSO_FIRE_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + //loop the attack anim + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + } + return SRESULT_WAIT; + + //wind down + case TORSO_WINDDOWN_START: + //jshepard: HACK just blend back into idle for now, we could support winddown anims here. + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + pfl.weaponFired = false; + return SRESULT_DONE; + + } + return SRESULT_DONE; +} +/* +================ +idPlayer::State_Torso_Reload +================ +*/ +stateResult_t idPlayer::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + TORSO_RELOAD_START, + TORSO_RELOAD_STARTWAIT, + TORSO_RELOAD_LOOP, + TORSO_RELOAD_LOOPWAIT, + TORSO_RELOAD_WAIT, + TORSO_RELOAD_END + }; + switch ( parms.stage ) { + // Start the reload by either playing the reload animation or the reload_start animation + case TORSO_RELOAD_START: + if ( HasAnim ( ANIMCHANNEL_TORSO, "reload_start" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "reload_start", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_STARTWAIT); + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for the reload_start animation to finish and transition to reload_loop + case TORSO_RELOAD_STARTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Play a single reload from the reload loop + case TORSO_RELOAD_LOOP: + if ( !pfl.reload ) { + return SRESULT_STAGE(TORSO_RELOAD_END); + } + PlayAnim ( ANIMCHANNEL_TORSO, "reload_loop", 0 ); + return SRESULT_STAGE(TORSO_RELOAD_LOOPWAIT); + + // Wait for the looping reload to finish and either start a new one or end the reload + case TORSO_RELOAD_LOOPWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // End the reload + case TORSO_RELOAD_END: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload_end", 3 ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for reload to finish (called by both reload_end and reload) + case TORSO_RELOAD_WAIT: + if ( pfl.weaponFired || AnimDone ( ANIMCHANNEL_TORSO, 3 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Pain +================ +*/ +stateResult_t idPlayer::State_Torso_Pain ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, painAnim.Length()?painAnim:"pain", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Dead +================ +*/ +stateResult_t idPlayer::State_Torso_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Emote +================ +*/ +stateResult_t idPlayer::State_Torso_Emote ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if( emote == PE_GRAB_A ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_a", parms.blendFrames ); + } else if( emote == PE_GRAB_B ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_b", parms.blendFrames ); + } else if( emote == PE_SALUTE ) { + PlayAnim ( ANIMCHANNEL_TORSO, "salute", parms.blendFrames ); + } else if( emote == PE_CHEER ) { + PlayAnim ( ANIMCHANNEL_TORSO, "cheer", parms.blendFrames ); + } else if( emote == PE_TAUNT ) { + PlayAnim ( ANIMCHANNEL_TORSO, "taunt", parms.blendFrames ); + } + + emote = PE_NONE; + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idPlayer::IsLegsIdle +================ +*/ +bool idPlayer::IsLegsIdle( bool crouching ) const { + return ( ( pfl.crouch == crouching ) && pfl.onGround && ( pfl.forward == pfl.backward ) && ( pfl.strafeLeft == pfl.strafeRight ) ); +} + +/* +================ +idPlayer::State_Legs_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.crouch ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Crouch", parms.blendFrames ); + return SRESULT_DONE; + } + IdleAnim( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + // #34 - have a delay while idle before going to something else if a ground flag change happens + // this makes the legs animation stay in idle state when the ground flag value flip-flops fast + if ( prevOnGround != pfl.onGround ) { + // when explicitely jumping, don't impose any delay + bool explicitJump = ( !pfl.onGround && usercmd.upmove > 0 ); + if ( !explicitJump ) { + ignoreOnGroundUntilFrame = gameLocal.framenum + 5; + } + prevOnGround = pfl.onGround; + } + if ( gameLocal.framenum <= ignoreOnGroundUntilFrame ) { + return SRESULT_WAIT; + } + + // If now crouching go back to idle so we can transition to crouch + if ( pfl.crouch ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + return SRESULT_DONE; + } else if ( pfl.jump ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Jump", 4 ); + return SRESULT_DONE; + } else if ( !pfl.onGround ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + } else if ( pfl.forward && !pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeLeft && !pfl.strafeRight ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeRight && !pfl.strafeLeft ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", parms.blendFrames ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "crouch", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.crouch || pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", 4 ); + return SRESULT_DONE; + } else if ( (pfl.forward && !pfl.backward) || (pfl.strafeLeft != pfl.strafeRight) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Forward", parms.blendFrames ); + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_down", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( true ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Uncrouch +================ +*/ +stateResult_t idPlayer::State_Legs_Uncrouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_up", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Run_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Forward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && ((!pfl.backward && pfl.forward) || (pfl.strafeLeft != pfl.strafeRight)) ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Backward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk_backward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && !pfl.forward && pfl.backward ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Jump +================ +*/ +stateResult_t idPlayer::State_Legs_Jump ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // prevent infinite recursion + pfl.jump = false; + if ( pfl.run ) { + PlayAnim ( ANIMCHANNEL_LEGS, "run_jump", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "jump", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Fall +================ +*/ +stateResult_t idPlayer::State_Legs_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "fall", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Land +================ +*/ +stateResult_t idPlayer::State_Legs_Land ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( IsLegsIdle ( false ) && ( pfl.hardLanding || pfl.softLanding ) ) { + if ( pfl.hardLanding ) { + PlayAnim ( ANIMCHANNEL_LEGS, "hard_land", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "soft_land", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Dead +================ +*/ +stateResult_t idPlayer::State_Legs_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_Alive + +Waits until the player is alive again. +================ +*/ +stateResult_t idPlayer::State_Wait_Alive ( const stateParms_t& parms ) { + if ( pfl.dead ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_ReloadAnim +================ +*/ +stateResult_t idPlayer::State_Wait_ReloadAnim ( const stateParms_t& parms ) { + // The gun firing can cancel any of the relod animations + if ( pfl.weaponFired ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // wait for the animation to finish + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} diff --git a/source/mpgame/Projectile.cpp b/source/mpgame/Projectile.cpp new file mode 100644 index 0000000..6e2decd --- /dev/null +++ b/source/mpgame/Projectile.cpp @@ -0,0 +1,2284 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" +#include "Projectile.h" +#include "spawner.h" + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_Explode( "", NULL ); +const idEventDef EV_Fizzle( "", NULL ); +const idEventDef EV_RadiusDamage( "", "E" ); +const idEventDef EV_ResidualDamage ( "", "E" ); +const idEventDef EV_EmitDamage ( "", "E" ); + +CLASS_DECLARATION( idEntity, idProjectile ) + EVENT( EV_Explode, idProjectile::Event_Explode ) + EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) + EVENT( EV_Touch, idProjectile::Event_Touch ) + EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) + EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage ) + EVENT( EV_EmitDamage, idProjectile::Event_EmitDamage ) +END_CLASS + +/* +================ +idProjectile::idProjectile +================ +*/ +idProjectile::idProjectile( void ) { + methodOfDeath = -1; + owner = NULL; + memset( &projectileFlags, 0, sizeof( projectileFlags ) ); + damagePower = 1.0f; + + memset( &renderLight, 0, sizeof( renderLight ) ); + + lightDefHandle = -1; + lightOffset.Zero(); + lightStartTime = 0; + lightEndTime = 0; + lightColor.Zero(); + + visualAngles.Zero(); + angularVelocity.Zero(); + speed.Init( 0.0f, 0.0f, 0.0f, 0.0f ); + updateVelocity = false; + + rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() ); + + flyEffect = NULL; + flyEffectAttenuateSpeed = 0.0f; + bounceCount = 0; + hitCount = 0; + state = SPAWNED; + + fl.networkSync = true; + + prePredictTime = 0; + + syncPhysics = false; + + launchTime = 0; + launchOrig = vec3_origin; + launchDir = vec3_origin; + launchSpeed = 0.0f; + + playedDamageEffect = false; + + predictedProjectiles = false; +} + +/* +================ +idProjectile::Spawn +================ +*/ +void idProjectile::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( 0 ); + physicsObj.PutToRest(); + SetPhysics( &physicsObj ); + prePredictTime = spawnArgs.GetInt( "predictTime", "0" ); + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( gameLocal.isClient ) { + Hide(); + } + + predictedProjectiles = g_predictedProjectiles.GetBool(); +} + +/* +================ +idProjectile::Save +================ +*/ +void idProjectile::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var + owner.Save( savefile ); + savefile->Write( &projectileFlags, sizeof( projectileFlags ) ); + savefile->WriteFloat( damagePower ); + + savefile->WriteRenderLight( renderLight ); + + savefile->WriteInt( ( int )lightDefHandle ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var + savefile->WriteAngles( angularVelocity ); // cnicholson: moved var + + // Save speed + savefile->WriteBool ( updateVelocity ); + savefile->WriteFloat( speed.GetStartTime() ); + savefile->WriteFloat( speed.GetDuration() ); + savefile->WriteFloat( speed.GetStartValue() ); + savefile->WriteFloat( speed.GetEndValue() ); + + // rotation; this is a class, so it doesnt get saved here + + flyEffect.Save( savefile ); // cnicholson: added unsaved var + savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->WriteInt ( bounceCount ); + savefile->WriteInt ( hitCount ); + + savefile->WriteInt( (int)state ); +} + +/* +================ +idProjectile::Restore +================ +*/ +void idProjectile::Restore( idRestoreGame *savefile ) { + float fset; + idVec3 temp; + + savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var + owner.Restore( savefile ); + savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); + savefile->ReadFloat( damagePower ); + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadInt( (int &)lightDefHandle ); + if ( lightDefHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + + // Restore the physics + savefile->ReadStaticObject( physicsObj ); + RestorePhysics ( &physicsObj ); + + savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var + savefile->ReadAngles( angularVelocity ); // cnicholson: moved var + + // Restore speed + savefile->ReadBool ( updateVelocity ); + savefile->ReadFloat( fset ); + speed.SetStartTime( fset ); + savefile->ReadFloat( fset ); + speed.SetDuration( fset ); + savefile->ReadFloat( fset ); + speed.SetStartValue( fset ); + savefile->ReadFloat( fset ); + speed.SetEndValue( fset ); + + // rotation? + + flyEffect.Restore( savefile ); // cnicholson: added unrestored var + savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->ReadInt ( bounceCount ); + savefile->ReadInt ( hitCount ); + + savefile->ReadInt( (int &)state ); +} + +/* +================ +idProjectile::GetOwner +================ +*/ +idEntity *idProjectile::GetOwner( void ) const { + return owner.GetEntity(); +} + +/* +================ +idProjectile::SetSpeed +================ +*/ +void idProjectile::SetSpeed( float s, int accelTime ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s ); + + if ( accelTime > 0 ) { + updateVelocity = true; + } else { + updateVelocity = false; + } + + // Update the velocity to match the direction we are facing and include any accelerations + physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel ); +} + +/* +================ +idProjectile::Create +================ +*/ +void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) { + idDict args; + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + idVec3 tmp; + idMat3 axis; + + Unbind(); + + axis = dir.ToMat3(); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner ); + physicsObj.extraPassEntity = extraPassEntity; + + owner = _owner; + + memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); + spawnArgs.GetVector( "light_color", "1 1 1", light_color ); + renderLight.shaderParms[0] = light_color[0]; + renderLight.shaderParms[1] = light_color[1]; + renderLight.shaderParms[2] = light_color[2]; + renderLight.shaderParms[3] = 1.0f; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: set the projectile lights to be no shadows + renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + lightStartTime = 0; + lightEndTime = 0; + + damagePower = 1.0f; + + UpdateVisuals(); + + state = CREATED; +} + +/* +================= +idProjectile::~idProjectile +================= +*/ +idProjectile::~idProjectile() { + StopSound( SND_CHANNEL_ANY, false ); + FreeLightDef(); + SetPhysics( NULL ); +} + +/* +================= +idProjectile::FreeLightDef +================= +*/ +void idProjectile::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================= +idProjectile::Launch +================= +*/ +void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) { + float fuse; + idVec3 velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + float temp, temp2; + idVec3 gravVec; + idVec3 tmp; + int contents; + int clipMask; + + // allow characters to throw projectiles during cinematics, but not the player + if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + cinematic = owner.GetEntity()->cinematic; + } else { + cinematic = false; + } + + // Set the damage + damagePower = dmgPower; + + if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) { + spawnArgs.GetVector( "velocity", "0 0 0", tmp ); + temp = tmp[0]; + } else { + float speedRandom; + if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) { + temp += gameLocal.random.CRandomFloat()*speedRandom; + } + } + if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) { + temp2 = temp; + } + float speedDuration; + speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) ); + speed.Init( gameLocal.time, speedDuration, temp, temp2 ); + if ( speedDuration > 0 && temp != temp2 ) { + // only support constant velocity projectiles in MP + // ( we also assume that no MP projectiles use speedRandom ) + assert( !gameLocal.isServer ); + updateVelocity = true; + } + launchSpeed = temp; + + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() ); + bounceCount = spawnArgs.GetInt( "bounce_count", "-1" ); + + //spawn impact entity information + impactEntity = spawnArgs.GetString("def_impactEntity",""); + numImpactEntities = spawnArgs.GetInt("numImpactEntities","0"); + ieMinPitch = spawnArgs.GetInt("ieMinPitch","0"); + ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0"); + ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0"); + + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); + projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); + projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); + projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" ); + + lightStartTime = 0; + lightEndTime = 0; + + impactedEntity = 0; + + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero; +// RAVEN END + + Unbind(); + + contents = 0; + clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX; + + // all projectiles are projectileclip + clipMask |= CONTENTS_PROJECTILECLIP; + + if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) { + clipMask |= CONTENTS_LARGESHOTCLIP; + } + + if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) { + clipMask |= CONTENTS_MOVEABLECLIP; + } + if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) { + clipMask |= CONTENTS_MONSTERCLIP; + } + + if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { + contents |= CONTENTS_TRIGGER; + } + + if ( !spawnArgs.GetBool( "no_contents", "1" ) ) { + contents |= CONTENTS_PROJECTILE; + } + + clipMask |= CONTENTS_PROJECTILE; + + // don't do tracers on client, we don't know origin and direction + if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { + SetModel( spawnArgs.GetString( "model_tracer" ) ); + projectileFlags.isTracer = true; + } + + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce ); + physicsObj.SetGravity( gravVec ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask | CONTENTS_WATER ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( dir.ToMat3() ); + + if ( !gameLocal.isClient ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Explode, fuse ); + } else { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + idQuat q( dir.ToMat3().ToQuat() ); + rotation.Init( gameLocal.GetTime(), 0.0f, q, q ); + + if ( projectileFlags.isTracer ) { + StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); + } else { + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + } + + // used for the plasma bolts but may have other uses as well + if ( projectileFlags.randomShaderSpin ) { + float f = gameLocal.random.RandomFloat(); + f *= 0.5f; + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; + } + + UpdateVisuals(); + + // Make sure these come after update visuals so the origin and axis are correct + PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis ); + + flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true ); + flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" ); + + state = LAUNCHED; + + hitCount = 0; + + predictTime = prePredictTime; + + if ( spawnArgs.GetFloat( "delay_emit_damage" ) > 0.0f ) { + PostEventSec( &EV_EmitDamage, spawnArgs.GetFloat( "wait_emit_damage", "0" ), this ); + } + + if ( gameLocal.isServer ) { + // store launch information for networking + launchTime = gameLocal.time; + launchOrig = physicsObj.GetOrigin(); + launchDir = dir; + } else { + if ( predictedProjectiles ) { + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + } + + if ( g_perfTest_noProjectiles.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idProjectile::Think +================ +*/ +void idProjectile::Think( void ) { + // run physics + if ( thinkFlags & TH_PHYSICS ) { + + // Update the velocity to match the changing speed + if ( updateVelocity ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize ( ); + physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel ); + if ( speed.IsDone ( gameLocal.time ) ) { + updateVelocity = false; + } + } + + RunPhysics(); + + // If we werent at rest and are now then start the atrest fuse + if ( physicsObj.IsAtRest( ) ) { + float fuse = spawnArgs.GetFloat( "fuse_atrest" ); + if ( fuse > 0.0f ) { + if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + CancelEvents( &EV_Explode ); + PostEventSec( &EV_Explode, fuse ); + } else { + CancelEvents( &EV_Fizzle ); + PostEventSec( &EV_Fizzle, fuse ); + } + } + } + + // Stop the trail effect if the physics flag was removed + if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) { + if ( physicsObj.IsAtRest( ) ) { + flyEffect->Stop( ); + flyEffect = NULL; + } else { + float speed; + speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) ); + flyEffect->Attenuate( speed / flyEffectAttenuateSpeed ); + } + } + + UpdateVisualAngles(); + } + + Present(); + + // add the light + if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; + renderLight.axis = GetPhysics()->GetAxis(); + if ( ( lightDefHandle != -1 ) ) { + if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) { + idVec3 color( 0, 0, 0 ); + if ( gameLocal.time < lightEndTime ) { + float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); + color.Lerp( lightColor, color, frac ); + } + renderLight.shaderParms[SHADERPARM_RED] = color.x; + renderLight.shaderParms[SHADERPARM_GREEN] = color.y; + renderLight.shaderParms[SHADERPARM_BLUE] = color.z; + } + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } +} + +/* +================= +idProjectile::UpdateVisualAngles +================= +*/ +void idProjectile::UpdateVisualAngles() { + idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() ); + + if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() ); + return; + } + + if( physicsObj.GetNumContacts() ) { + return; + } + + if( !rotation.IsDone(gameLocal.GetTime()) ) { + return; + } + + if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) { + return; + } + + visualAngles += angularVelocity; + idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat(); + rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q ); +} + +/* +================= +idProjectile::Collide +================= +*/ +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { + bool dummy = false; + return Collide( collision, velocity, dummy ); +} + +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { + idEntity* ent; + idEntity* actualHitEnt = NULL; + idEntity* ignore; + const char* damageDefName; + idVec3 dir; + bool canDamage; + + hitTeleporter = false; + + if ( state == EXPLODED || state == FIZZLED || ( state == IMPACTED && predictedProjectiles ) ) { + return true; + } + + if ( IsHidden() && predictedProjectiles ) { + // teleported, ignore impacts + return false; + } + + // allow projectiles to hit triggers (teleports) + // predict this on a client + if ( collision.c.contents & CONTENTS_TRIGGER ) { + idEntity* trigger = gameLocal.entities[ collision.c.entityNum ]; + + if ( trigger ) { + if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) { + + hitTeleporter = true; + + trace_t trace; + + trace.endpos = physicsObj.GetOrigin(); + trace.endAxis = physicsObj.GetAxis(); + + trace.c.contents = collision.c.contents; + trace.c.entityNum = collision.c.entityNum; + if( trigger->GetPhysics()->GetClipModel() ) { + trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId(); + } else { + trace.c.id = 0; + } + + // hack to play the effect on clients when mispredicted (Hide() ensures it can't play twice for the same teleport) + int wasNewFrame = gameLocal.isNewFrame; + if ( gameLocal.isClient && predictedProjectiles ) { + gameLocal.isNewFrame = true; + } + + trigger->Signal( SIG_TOUCH ); + trigger->ProcessEvent( &EV_Touch, this, &trace ); + + if ( gameLocal.isClient && predictedProjectiles ) { + Hide(); + gameLocal.isNewFrame = wasNewFrame; + } + } + } + + // when we hit a trigger, align our velocity to the trigger's coordinate plane + if( gameLocal.isServer ) { + idVec3 up( 0.0f, 0.0f, 1.0f ); + idVec3 right = collision.c.normal.Cross( up ); + idMat3 mat( collision.c.normal, right, up ); + + physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) ); + physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) ); + + // update the projectile's launchdir and launch origin + // this will propagate the change to the clients for prediction + // re-launch the projectile + + idVec3 newDir = physicsObj.GetLinearVelocity(); + newDir.Normalize(); + launchTime = gameLocal.time; + launchDir = newDir; + physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) ); + physicsObj.SetAxis( newDir.ToMat3() ); + launchOrig = physicsObj.GetOrigin(); + } + + if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) { + return false; + } + } + + // network clients: heuristic to skip predicting collisions + if ( gameLocal.isClient ) { + if ( predictedProjectiles ) { + if ( syncPhysics ) { + return false; + } + } else { + if ( spawnArgs.GetBool( "no_impact_prediction" ) || syncPhysics ) { + return false; + } + } + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::GetClassType() ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->GetTeamMaster() && ent->GetTeamMaster()->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster(); + } + + // Can the projectile damage? + canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE )); + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse +// RAVEN BEGIN +// abahr: added call to SkipDamageImpulse changed where push comes from + damageDefName = NULL; + if ( collision.c.materialType ) { + damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) ); + } + if ( !damageDefName || !*damageDefName ) { + damageDefName = spawnArgs.GetString ( "def_damage" ); + } + + if( damageDefName && damageDefName[0] ) { + const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( dict ) { + ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict ); + } + } +// RAVEN END + + //Spawn any impact entities if necessary. + SpawnImpactEntities(collision, velocity); + + //Apply any impact force if the necessary + //ApplyImpactForce(ent, collision, dir); + + // MP: projectiles open doors + if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + ent->ProcessEvent( &EV_Activate , this ); + } + + // If the projectile hits water then we need to let the projectile keep going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + if ( !physicsObj.IsInWater( ) ) { + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + } + // Pass through water + return false; + } else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + bool bounce = false; + + // Determine if the projectile should bounce + bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage; + bounce = bounce && (bounceCount == -1 || bounceCount > 0); + //assert(collision.c.material); + if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) { + bounce = !projectileFlags.detonate_on_bounce; + } + + if ( bounce ) { + if ( bounceCount != -1 ) { + bounceCount--; + } + + StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ); + + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( ent->IsType ( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } + } else { + // FIXME: clean up + idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() ); + axis[0].ProjectOntoPlane( collision.c.normal ); + axis[0].Normalize(); + axis[2] = collision.c.normal; + axis[1] = axis[2].Cross( axis[0] ).ToNormal(); + + rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() ); + } + if ( actualHitEnt + && actualHitEnt != ent + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); +// SetAxis( collision.endAxis ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + ignore = NULL; + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + bool willPlayDamageEffect = true; + + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + // if the projectile hit an ai + if ( ent->IsType( idAI::GetClassType() ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + idAI * ai_ent = static_cast(ent); + if( ai_ent->team == player->team) { + willPlayDamageEffect = false; + } + } + } + +// RAVEN END + + // if the hit entity takes damage + if ( canDamage ) { + + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); +// RAVEN BEGIN +// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint +// otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == collision.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } +// RAVEN END + ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint ); + + if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) { + statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 ); + hitCount++; + } + } + } + + ignore = ent; + + if ( predictedProjectiles ) { + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { + ent->AddDamageEffect( collision, velocity, damageDefName, owner ); + } + + // hack to play the effect on clients when mispredicted (state/playedDamageEffect ensure it can't happen twice) + int wasNewFrame = gameLocal.isNewFrame; + if ( gameLocal.isClient ) { + gameLocal.isNewFrame = true; + } + + // if the the player is the attacker and the victim is teammate, don't play any effects. + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect && ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + playedDamageEffect = true; + } + + Explode( &collision, false, ignore ); + + gameLocal.isNewFrame = wasNewFrame; + } else { + ent->AddDamageEffect ( collision, velocity, damageDefName, owner ); + + // if the the player is the attacker and the victim is teammate, don't play any effects. + if ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + } + + // don't predict explosions on clients + if ( gameLocal.isClient ) { + return true; + } + + Explode( &collision, false, ignore ); + } + + return true; +} + +void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity) +{ + if( impactEntity.Length() == 0 || numImpactEntities == 0 ) + return; + + const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity); + if(impactEntityDict == NULL) + return; + + idVec3 tempDirection; + idVec3 direction; + direction.Zero(); + + idVec3 up = collision.c.normal; + + //Calculate the axes for that are oriented to the impact point. + idMat3 impactAxes; + + idVec3 right = velocity.Cross(up); + idVec3 forward = up.Cross(right); + + right.Normalize(); + forward.Normalize(); + impactAxes[0] = forward; + impactAxes[1] = right; + impactAxes[2] = up; + + //Calculate the reflection vector by calculating the forward component and up component of the projectile direction + //idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up)); + idVec3 reflectionVelocity = up * 0.01f; + + //The algorithm below will launch entities at a random pitch and somewhat random yaw. + //The yaw is calculated by dividing 360 by the number of entities to spawn. This creates + //a distribution slice. Then using the slice percentage, this will determine how much of the + //slice to use. + //This creates a random,but somewhat even coverage of the circle. + + //Calculate the slice size and pick a random start position. + int sliceSize = 360 / numImpactEntities; + int startPosition = rvRandom::irand(0, 360); + + //Move the origin away from the collision point. This prevents the projectiles + //from colliding with the surface. + idVec3 origin = collision.endpos; + origin += 10.0f * collision.c.normal; + for(int i = 0;i < numImpactEntities; i++) + { + idProjectile* spawnProjectile = NULL; + gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile); + if(spawnProjectile != NULL) + { + int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch); + int sliceMiddle = (i * sliceSize) + startPosition; + int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2; + + int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness); + yaw = yaw % 360; + + float cosPitch = idMath::Cos(DEG2RAD(pitch)); + tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw)); + tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw)); + tempDirection.z = idMath::Sin(DEG2RAD(pitch)); + + spawnProjectile->SetOwner(owner); + + //Now orient the direction to the surface world orientation. + direction = impactAxes * tempDirection; + spawnProjectile->Launch(origin, direction, reflectionVelocity); + } + } +} + +/* +================= +idProjectile::DefaultDamageEffect +================= +*/ +void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) { + idEntity* ent; + idMat3 axis; + ent = gameLocal.entities[ tr.c.entityNum ]; + + // Make sure we want to play effects + if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" )) + && owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) { + return; + } + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) { + idVec3 dir; + dir = velocity; + dir.Normalize ( ); + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + + // Play an actor specific impact effect? + const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType ); + if ( actorImpactEffect ) { + gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true, true ); + return; + } + } else { + axis = tr.c.normal.ToMat3(); + } + + // Play an impact effect on the entity that got hit + if ( ent->IsType( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); + } else { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); + } +} + +/* +================ +idProjectile::Killed +================ +*/ +void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + Explode( NULL, true ); + physicsObj.ClearContacts(); + physicsObj.PutToRest(); + } else { + Fizzle(); + } +} + +/* +================ +idProjectile::Fizzle +================ +*/ +void idProjectile::Fizzle( void ) { + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( predictedProjectiles ) { + if ( state == IMPACTED ) { + return; + } + } else { + if ( gameLocal.isClient ) { + return; + } + } + + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + // No more fly effects + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + Hide(); + FreeLightDef(); + + state = FIZZLED; + + int removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + if ( predictedProjectiles ) { + // ensure the projectile makes it in to at least one snapshot + int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); if ( removeTime < snapshotTime ) + if ( removeTime < snapshotTime ) { + removeTime = snapshotTime; + } + } + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_RadiusDamage( idEntity *ignore ) { + const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount ); + } +} + +/* +================ +idProjectile::Event_ResidualDamage +================ +*/ +void idProjectile::Event_ResidualDamage ( idEntity* ignore ) { + const char *residual_damage = spawnArgs.GetString( "def_residual_damage" ); + if ( residual_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore ); +} + +void idProjectile::Event_EmitDamage ( idEntity* ignore ) { + const char *emit_damage = spawnArgs.GetString( "def_emit_damage" ); + if ( emit_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, emit_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_EmitDamage, spawnArgs.GetFloat ( "delay_emit_damage" ), ignore ); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) { + idVec3 normal, endpos; + int removeTime; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( predictedProjectiles && state == IMPACTED ) { + return; + } + + CancelEvents( &EV_EmitDamage ); + + if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { + GetPhysics()->SetAxis( normal.ToMat3() ); + } else { + normal = collision ? collision->c.normal : idVec3( 0, 0, 1 ); + } + endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin(); + + removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + // play sound + StopSound( SND_CHANNEL_BODY, false ); + StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL ); + + idVec3 fxDir; + if ( physicsObj.GetGravityNormal( ) != vec3_zero ) { + fxDir = -physicsObj.GetGravityNormal( ); + } else { + fxDir = -physicsObj.GetLinearVelocity( ); + fxDir.Normalize( ); + } + + if ( predictedProjectiles ) { + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + } else if ( showExplodeFX ) { + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + + // Stop the fly effect without destroying particles to ensure the trail within can persist. + StopEffect( "fx_fly" ); + + // Stop the remaining particles + StopAllEffects( ); + + Hide(); + FreeLightDef(); + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + if ( predictedProjectiles ) { + state = showExplodeFX ? EXPLODED : IMPACTED; + } else { + state = EXPLODED; + } + + if ( gameLocal.isClient ) { + return; + } + + // alert the ai + gameLocal.AlertAI( owner.GetEntity() ); + + // bind the projectile to the impact entity if necesary + if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { + Bind( gameLocal.entities[collision->c.entityNum], true ); + } + + if ( predictedProjectiles ) { + removeTime = spawnArgs.GetInt( "detonate_remove_time", "0" ); + + // ensure the projectile makes it in to at least one snapshot + int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); + if ( removeTime < snapshotTime ) { + removeTime = snapshotTime; + } + } else { + removeTime = 0; + } + + // splash damage + float delay = spawnArgs.GetFloat( "delay_splash" ); + if ( delay ) { + if ( removeTime < delay * 1000 ) { + removeTime = ( delay + 0.10 ) * 1000; + } + PostEventSec( &EV_RadiusDamage, delay, ignore ); + } else { + Event_RadiusDamage( ignore ); + } + + // Residual damage (damage over time) + delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) ); + if ( delay > 0.0f ) { + PostEventMS ( &EV_ResidualDamage, delay, ignore ); + + // Keep the projectile around until the residual damage is done + delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) ); + if ( removeTime < delay ) { + removeTime = delay; + } + } + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::GetVelocity +================ +*/ +idVec3 idProjectile::GetVelocity( const idDict *projectile ) { + idVec3 velocity; + + velocity.Zero ( ); + if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) { + projectile->GetVector( "velocity", "0 0 0", velocity ); + } + return velocity; +} + +/* +================ +idProjectile::GetGravity +================ +*/ +idVec3 idProjectile::GetGravity( const idDict *projectile ) { + if ( projectile ) { + return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" ); + } + return vec3_origin; +} + +/* +================ +idProjectile::PlayPainEffect +================ +*/ +void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) { + static int damageTable[] = { 100, 50, 25, 10, 0 }; + int index; + + // Normalize the damage value to the damage table + for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ ); + + // loop until we find a pain effect, trying lower damage numbers if needed + for ( ; damageTable[index]; index ++ ) { + // Try the pain effect for the current damage value and if it plays then + // we are done + if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) { + return; + } + } + + // Play the default pain effect + ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() ); +} + +/* +================ +idProjectile::PlayDetonateEffect +================ +*/ +void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis, bool forceImpact ) { + if( physicsObj.HasGroundContacts() || ( forceImpact && predictedProjectiles ) ) { + if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) { + trace_t tr; + idVec3 down; + down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f; + gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this ); + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true, true ); + } else { + gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true, true ); + } + return; + } + + gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true, true ); +} + +/* +================ +idProjectile::Event_Explode +================ +*/ +void idProjectile::Event_Explode( void ) { + // events are processed outside of the think loop, so set the current thinking ent appropriately + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Explode( NULL, true ); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Fizzle +================ +*/ +void idProjectile::Event_Fizzle( void ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Fizzle(); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Touch +================ +*/ +void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { + if ( IsHidden() ) { + return; + } + + if ( other != owner.GetEntity() ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + + bool playDefaultDamageEffect = predictedProjectiles ? !playedDamageEffect : true; + if ( playDefaultDamageEffect ) { + DefaultDamageEffect( collision, collision.c.normal, NULL ); + playedDamageEffect = true; + } + Explode( NULL, !predictedProjectiles ); + + gameLocal.currentThinkingEntity = think; + } +} + +/* +================ +idProjectile::ClientPredictionThink +================ +*/ +void idProjectile::ClientPredictionThink( void ) { + if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) { + return; + } + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + Think(); +} + +/* +================ +idProjectile::WriteToSnapshot +================ +*/ +void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + + msg.WriteBits( state, 3 ); + if ( state >= LAUNCHED ) { + // feed the client with start position, direction and time. let the client do everything else + // this won't change during projectile life and be completely deltified away + msg.WriteLong( launchTime ); + msg.WriteFloat( launchOrig[ 0 ] ); + msg.WriteFloat( launchOrig[ 1 ] ); + msg.WriteFloat( launchOrig[ 2 ] ); + msg.WriteDir( launchDir, 24 ); + + int ownerNum = (GetOwner() && GetOwner()->entityNumber < MAX_CLIENTS) ? GetOwner()->entityNumber : MAX_CLIENTS; + msg.WriteBits( ownerNum, idMath::BitsForInteger(MAX_CLIENTS) ); + } +} + +/* +================ +idProjectile::ReadFromSnapshot +================ +*/ +void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + projectileState_t newState; + idEntity *ownerEnt = NULL; + int newLaunchTime; + + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + + newState = (projectileState_t) msg.ReadBits( 3 ); + if ( newState >= LAUNCHED ) { + newLaunchTime = msg.ReadLong(); + launchOrig[ 0 ] = msg.ReadFloat(); + launchOrig[ 1 ] = msg.ReadFloat(); + launchOrig[ 2 ] = msg.ReadFloat(); + launchDir = msg.ReadDir( 24 ); + + int ownerNum = msg.ReadBits( idMath::BitsForInteger(MAX_CLIENTS) ); + if ( ownerNum < MAX_CLIENTS && gameLocal.entities[ ownerNum ] && gameLocal.entities[ ownerNum ]->IsType( idPlayer::GetClassType() ) ) { + ownerEnt = gameLocal.entities[ ownerNum ]; + } + + if ( predictedProjectiles && launchTime != newLaunchTime ) { + launchTime = newLaunchTime; + + if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + + if ( state == LAUNCHED && newState == LAUNCHED ) { + Show(); + } + + UpdateVisuals(); + } + + if ( !predictedProjectiles ) { + launchTime = newLaunchTime; + } + } + + // we always create and launch at the same time + // state on a client can be SPAWNED, LAUNCHED, EXPLODED + // expect never to get a CREATED projectile, they should launch right away + if ( predictedProjectiles ) { + if ( msg.HasChanged() ) { + if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + } + UpdateVisuals(); + + assert( state != CREATED && newState != CREATED ); + + if ( newState != state ) { + if ( state < LAUNCHED && newState >= LAUNCHED ) { + Create( ownerEnt, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + } + switch ( newState ) { + case FIZZLED: + Fizzle(); + break; + case EXPLODED: + case IMPACTED: + Explode( NULL, state == EXPLODED ); + break; + } + } + } else { + assert( state != CREATED && state != FIZZLED && newState != CREATED ); + + if ( newState != state ) { + switch ( newState ) { + case LAUNCHED: + Create( NULL, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + break; + case FIZZLED: + case EXPLODED: + if ( state != EXPLODED ) { + StopSound( SND_CHANNEL_BODY, false ); + StopAllEffects(); + Hide(); + FreeLightDef(); + state = EXPLODED; + } + break; + } + } + + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + UpdateVisuals(); + } +} + +/* +=============== +idProjectile::ClientStale +=============== +*/ +bool idProjectile::ClientStale( void ) { + // delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway ) + StopAllEffects(); + return true; +} + +/* +================ +idProjectile::GetPhysicsToVisualTransform +================ +*/ +bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose(); + + origin.Zero(); + return true; +} + +/* +=============================================================================== + + idGuidedProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, idGuidedProjectile ) +END_CLASS + +/* +================ +idGuidedProjectile::idGuidedProjectile( void ) +================ +*/ +idGuidedProjectile::idGuidedProjectile( void ) { + guideType = GUIDE_NONE; + turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f ); +} + +/* +================= +idGuidedProjectile::~idGuidedProjectile +================= +*/ +idGuidedProjectile::~idGuidedProjectile() { +} + +/* +================ +idGuidedProjectile::Save +================ +*/ +void idGuidedProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( guideType ); + guideEnt.Save( savefile ); + savefile->WriteVec3 ( guideDir ); + savefile->WriteVec3 ( guidePos ); + savefile->WriteJoint ( guideJoint ); + savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var + + savefile->WriteInt ( driftTime ); + savefile->WriteInt ( driftRate ); + savefile->WriteFloat ( driftRange ); + savefile->WriteFloat ( driftRadius ); + savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var + savefile->WriteFloat ( driftAngle ); + savefile->WriteFloat ( driftAngleStep ); + savefile->WriteFloat ( driftProjectRange ); + + savefile->WriteFloat( turn_max.GetStartTime() ); + savefile->WriteFloat( turn_max.GetDuration() ); + savefile->WriteFloat( turn_max.GetStartValue() ); + savefile->WriteFloat( turn_max.GetEndValue() ); + + savefile->WriteInt ( launchTime ); + savefile->WriteInt ( guideDelay ); + savefile->WriteInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::Restore +================ +*/ +void idGuidedProjectile::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadInt ( guideType ); + guideEnt.Restore( savefile ); + savefile->ReadVec3 ( guideDir ); + savefile->ReadVec3 ( guidePos ); + savefile->ReadJoint ( guideJoint ); + savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var + + savefile->ReadInt ( driftTime ); + savefile->ReadInt ( driftRate ); + savefile->ReadFloat ( driftRange ); + savefile->ReadFloat ( driftRadius ); + savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var + savefile->ReadFloat ( driftAngle ); + savefile->ReadFloat ( driftAngleStep ); + savefile->ReadFloat ( driftProjectRange ); + + savefile->ReadFloat( set ); + turn_max.SetStartTime( set ); + savefile->ReadFloat( set ); + turn_max.SetDuration( set ); + savefile->ReadFloat( set ); + turn_max.SetStartValue( set ); + savefile->ReadFloat( set ); + turn_max.SetEndValue( set ); + + savefile->ReadInt ( launchTime ); + savefile->ReadInt ( guideDelay ); + savefile->ReadInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::GetGuideDir +================ +*/ +bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) { + // Dont start guiding immeidately? + if ( gameLocal.GetTime() - launchTime < guideDelay ) { + return false; + } + + switch ( guideType ) { + case GUIDE_ENTITY: + // If the guide entity is gone or dead then cancel the guide + if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) { + CancelGuide ( ); + return false; + } + // Use eye position for actors and center of bounds for everything else + if ( guideJoint != INVALID_JOINT ) { + idMat3 jointAxis; + guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis ); + outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis); + if ( !guidePos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis; + outDir += jointAxis[0]*guidePos[0]; + outDir += jointAxis[1]*guidePos[1]; + outDir += jointAxis[2]*guidePos[2]; + } + } else { + outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter(); + if ( guideEnt->IsType( idActor::GetClassType() ) ) { + outDir += static_cast(guideEnt.GetEntity())->GetEyePosition(); + outDir *= 0.5f; + } + } + outDir -= physicsObj.GetOrigin(); + break; + + case GUIDE_POS: + outDir = guidePos - physicsObj.GetOrigin(); + break; + + case GUIDE_DIR: + // Project our current position on to the desired direction + outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir); + + // Seek towards a point forward along our desired direction + outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin(); + break; + + default: + return false; + } + + // Add drifting + if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) { + idMat3 axis; + + outDist = outDir.NormalizeFast(); + axis = outDir.ToMat3(); + + if ( gameLocal.time > driftTime ) { + driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity; + driftTime = gameLocal.time + driftRate; + } else { + driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec ); + idMath::AngleNormalize360 ( driftAngle ); + } + + float angle; + angle = DEG2RAD ( driftAngle ); + outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange ); + outDir += axis[2] * (driftRadius * idMath::Sin ( angle )); + outDir += axis[1] * (driftRadius * idMath::Cos ( angle )); + + outDir -= physicsObj.GetOrigin(); + } + + outDist = outDir.Normalize ( ); + + return true; +} + +/* +================ +idGuidedProjectile::Think +================ +*/ +void idGuidedProjectile::Think( void ) { + + if ( state == LAUNCHED ) { + idVec3 dir; + idVec3 vel; + float angle; + float maxangle; + idMat3 axis; + float dist; + + // crank up to normal speed ? + if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) { + float newSpeed = spawnArgs.GetFloat( "speed" ); + float newSpeed2; + if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) { + newSpeed2 = newSpeed; + } + float newSpeedDuration; + newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) ); + speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 ); + guideDelay = 0; + } + + if ( !GetGuideDir( dir, dist ) ) { + idProjectile::Think(); + return; + } + + // Direction of travel + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + + // Calculate the angle between the current projectile direction and where we want to go + angle = RAD2DEG( idMath::ACos( dir * vel ) ); + + // Make sure the angle doesnt cross our max turn radius + maxangle = turn_max.GetCurrentValue( gameLocal.time ); + if ( angle < -maxangle ) { + angle = -maxangle; + } else if ( angle > maxangle ) { + angle = maxangle; + } + + // Debug information + if ( g_debugWeapon.GetBool ( ) ) { + gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f ); + gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f ); + } + + // Calculate the new axis by rotating the current forward vector around the cross of the forward + // vector and the direction vector. + vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle ); + physicsObj.SetLinearVelocity( vel * GetSpeed ( ) ); + + // If within the minium distance to the target anything over a 45 degree change will cancel the guide + if ( guideMinDist != 0.0f && dist < guideMinDist ) { + // Stop guiding if we have passed our target + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize( ); + if ( vel * dir < 0.7f ) { + guideType = GUIDE_NONE; + } + } + + idProjectile::Think(); + } else { + idProjectile::Think(); + } +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + launchTime = gameLocal.GetTime(); + + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) { + GuideTo ( static_cast( owner.GetEntity() )->GetEnemy() ); + } + } + + guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" ); + guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) ); + + if ( guideDelay ) { + float delaySpeed; + if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) { + float delaySpeed2; + if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) { + delaySpeed2 = delaySpeed; + } + float delaySpeedDuration; + delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) ); + speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + } + } + + driftTime = 0; + driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) ); + driftRange = spawnArgs.GetFloat ( "driftRange" ); + driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" ); + driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f; + driftAngleStep = spawnArgs.GetFloat ( "driftRotate" ); + driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f); + driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" )); + + driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" ); + + // Turn rate can be ramped up over time + turn_max.Init ( gameLocal.time, + SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), + 0, + spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() ); + + UpdateVisuals(); +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + idProjectile::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + rvDriftingProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvDriftingProjectile ) +END_CLASS + +/* +================ +rvDriftingProjectile::rvDriftingProjectile( void ) +================ +*/ +rvDriftingProjectile::rvDriftingProjectile( void ) { +} + +/* +================= +rvDriftingProjectile::~rvDriftingProjectile +================= +*/ +rvDriftingProjectile::~rvDriftingProjectile ( void ) { +} + +/* +================ +rvDriftingProjectile::Save +================ +*/ +void rvDriftingProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteVec3 ( startDir ); + savefile->WriteVec3 ( startOrigin ); + savefile->WriteMat3 ( startAxis ); + savefile->WriteFloat ( startSpeed ); + savefile->WriteFloat ( driftOffsetMax ); + savefile->WriteFloat ( driftSpeedMax ); + savefile->WriteFloat ( driftTime ); + + savefile->WriteInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->WriteInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Restore +================ +*/ +void rvDriftingProjectile::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3 ( startDir ); + savefile->ReadVec3 ( startOrigin ); + savefile->ReadMat3 ( startAxis ); + savefile->ReadFloat ( startSpeed ); + savefile->ReadFloat ( driftOffsetMax ); + savefile->ReadFloat ( driftSpeedMax ); + savefile->ReadFloat ( driftTime ); + + savefile->ReadInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->ReadInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Think +================ +*/ +void rvDriftingProjectile::Think( void ) { + idVec3 diff; + idVec3 origin; + idVec3 oldOrigin; + idVec3 dir; + float dist; + + oldOrigin = GetPhysics()->GetOrigin ( ); + + diff = oldOrigin - startOrigin; + dist = diff.Length ( ); + origin = startOrigin + startDir * dist; + + if ( driftSpeed.IsDone ( gameLocal.time ) ) { + driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftSpeed.GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[0].IsDone ( gameLocal.time ) ) { + driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftOffset[0].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[1].IsDone ( gameLocal.time ) ) { + driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, + driftOffset[1].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time ); + origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time ); + + GetPhysics ( )->SetOrigin ( origin ); + GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) ); + + idProjectile::Think(); + + // Now orient the projectile using the old origin + dir = GetPhysics()->GetOrigin() - oldOrigin; + dir.Normalize ( ); + GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) { + startDir = dir; + startOrigin = start; + startAxis = dir.ToMat3(); + + driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" ); + driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" ); + driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) ); + + idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + startSpeed = GetPhysics()->GetLinearVelocity().Length ( ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::UpdateVisualAngles ( void ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() ); +} + +/* +=============================================================================== + + rvSpawnerProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvSpawnerProjectile ) + EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn ) +END_CLASS + +/* +================ +rvSpawnerProjectile::rvSpawnerProjectile( void ) +================ +*/ +rvSpawnerProjectile::rvSpawnerProjectile( void ) { + spawnState = STATE_NONE; +} + +/* +================= +rvSpawnerProjectile::~rvSpawnerProjectile +================= +*/ +rvSpawnerProjectile::~rvSpawnerProjectile ( void ) { + if ( spawnState == STATE_ADDED && spawner ) { + spawner->RemoveSpawnPoint ( this ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::Spawn ( void ) { + if ( *spawnArgs.GetString ( "spawner" ) ) { + PostEventMS ( &EV_PostSpawn, 0 ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +================= +rvSpawnerProjectile::Think +================= +*/ +void rvSpawnerProjectile::Think ( void ) { + idProjectile::Think ( ); + + if ( physicsObj.IsAtRest ( ) ) { + if ( spawnState == STATE_NONE && spawner ) { + spawner->AddSpawnPoint ( this ); + spawnState = STATE_ADDED; + } + } +} + +/* +================= +rvSpawnerProjectile::Event_PostSpawn +================= +*/ +void rvSpawnerProjectile::Event_PostSpawn ( void ) { + const char* temp; + temp = spawnArgs.GetString ( "spawner" ); + if ( temp && *temp ) { + idEntity* ent; + ent = gameLocal.FindEntity ( temp ); + if ( !ent ) { + gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) { + gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else { + SetSpawner ( static_cast(ent) ); + } + } +} + + +/* +=============================================================================== + + rvMIRVProjectile + +=============================================================================== +*/ +idEventDef EV_LaunchWarheads( "launchWarheads" ); + +CLASS_DECLARATION( idProjectile, rvMIRVProjectile ) + EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads ) +END_CLASS + +/* +================ +rvMIRVProjectile::rvMIRVProjectile( void ) +================ +*/ +rvMIRVProjectile::rvMIRVProjectile( void ) { + +} + +/* +================= +rvMIRVProjectile::~rvMIRVProjectile +================= +*/ +rvMIRVProjectile::~rvMIRVProjectile ( void ) { + +} + +/* +================ +void rvMIRVProjectile::Spawn( void ) +================ +*/ +void rvMIRVProjectile::Spawn( void ) { + + float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0"); + //post event for warhead launch + PostEventSec( &EV_LaunchWarheads, launchDelay ); +} + +/* +================ +void rvMIRVProjectile::Event_LaunchWarheads( void ) +================ +*/ +void rvMIRVProjectile::Event_LaunchWarheads( void ) { + + const char* warhead; + int count; + + warhead = spawnArgs.GetString("def_warhead",""); + count = spawnArgs.GetFloat("warhead_count","0"); + + if( warhead && warhead[0] ) { + + //start launching! + float angle = (360.0f / count); + idMat3 normalMat = this->GetPhysics()->GetAxis( ); + idVec3 normal = normalMat[0]; + idVec3 axis = normalMat[1]; + + float t; + idMat3 axisMat; + idProjectile * warheadEntity; + + //hey how 'bout that. + normal.Normalize(); + + for( t = 0; t< count; t++) { + + //rotate axis around normal by angle degrees. + axisMat = axis.ToMat3(); + axisMat.RotateArbitrary( normal, angle * t); + warheadEntity = static_cast(gameLocal.SpawnEntityDef( warhead)); + warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this ); + warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] ); + + } + //foom! + gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + PostEventSec( &EV_Explode, 0 ); + } + + + +} +// RAVEN END diff --git a/source/mpgame/Projectile.h b/source/mpgame/Projectile.h new file mode 100644 index 0000000..ad5d609 --- /dev/null +++ b/source/mpgame/Projectile.h @@ -0,0 +1,363 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_PROJECTILE_H__ +#define __GAME_PROJECTILE_H__ + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_Explode; + +class idProjectile : public idEntity { +public : + CLASS_PROTOTYPE( idProjectile ); + + idProjectile(); + virtual ~idProjectile(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore = NULL, idEntity* extraPassEntity = NULL ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + virtual void FreeLightDef( void ); + +//RITUAL BEGIN + void SetOwner(idEntity* ent) { owner = ent; } +// RITUAL END + + idEntity * GetOwner( void ) const; + + virtual void Think( void ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ); + virtual void Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore = NULL, const char *sndExplode = "snd_explode" ); + void Fizzle( void ); + + static idVec3 GetVelocity( const idDict *projectile ); + static idVec3 GetGravity( const idDict *projectile ); + + void SetSpeed ( float s, int accelTime = 0 ); + float GetSpeed ( void ) const; + + virtual void UpdateVisualAngles(); + + // information about what kind of projectile we are, used for death messages + int methodOfDeath; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + +protected: + void SpawnImpactEntities(const trace_t& collision, const idVec3 projectileDirection); + + + idEntityPtr owner; + + struct projectileFlags_s { + bool detonate_on_world : 1; + bool detonate_on_actor : 1; + bool detonate_on_bounce : 1; // Detonate if hit a bounce surface + bool randomShaderSpin : 1; + bool isTracer : 1; + } projectileFlags; + + float damagePower; + + renderLight_t renderLight; + qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + idEntity* impactedEntity; + + rvPhysics_Particle physicsObj; + idAngles visualAngles; + idAngles angularVelocity; + idInterpolate speed; + bool updateVelocity; + + rvSphericalInterpolate rotation; + + rvClientEffectPtr flyEffect; + float flyEffectAttenuateSpeed; + + int bounceCount; + bool sticky; + + idStr impactEntity; + int numImpactEntities; + int ieMinPitch; + int ieMaxPitch; + float ieSlicePercentage; + + bool predictedProjectiles; + +// RAVEN BEGIN +// ddynerman: hit count for stats + int hitCount; +// ddynerman: pre-prediction ( rocket jumping ) + int prePredictTime; +// RAVEN END + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4, + IMPACTED = 5, + } projectileState_t; + + projectileState_t state; + + void PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& direction ); + virtual void PlayDetonateEffect ( const idVec3& origin, const idMat3& axis, bool forceImpact = false ); + +private: + void DefaultDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + + void Event_Explode ( void ); + void Event_Fizzle ( void ); + void Event_RadiusDamage ( idEntity *ignore ); + void Event_ResidualDamage ( idEntity *ignore ); + void Event_EmitDamage ( idEntity *ignore ); + void Event_Touch ( idEntity *other, trace_t *trace ); + + bool syncPhysics; + + // cheap linear client side projectiles + // transmitted in snapshot + int launchTime; + idVec3 launchOrig; + idVec3 launchDir; + // set from def file in :Launch on both client and server + float launchSpeed; + bool playedDamageEffect; +}; + +ID_INLINE float idProjectile::GetSpeed ( void ) const { + return speed.GetCurrentValue( gameLocal.time ); +} + +/* +=============================================================================== + +idGuidedProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_UpdateGuideTarget; +extern const idEventDef EV_GuideToEntity; +extern const idEventDef EV_GuideToPos; + +class idGuidedProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idGuidedProjectile ); + + idGuidedProjectile( void ); + ~idGuidedProjectile( void ); + + enum { + GUIDE_NONE, + GUIDE_ENTITY, + GUIDE_POS, + GUIDE_DIR, + GUIDE_MAX + }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + void GuideTo ( const idVec3& post, const idVec3& dir ); + void GuideTo ( const idVec3& pos ); + void GuideTo ( idEntity* ent, jointHandle_t guideJoint=INVALID_JOINT, const idVec3 &offset=vec3_origin ); + void CancelGuide ( void ); + + int GetGuideType ( void ) const; + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int guideType; + idEntityPtr guideEnt; + idVec3 guideDir; + idVec3 guidePos; + jointHandle_t guideJoint; + float guideMinDist; + + int driftTime; + int driftRate; + float driftRange; + float driftRadius; + float driftDiversity; + float driftAngle; + float driftAngleStep; + float driftProjectRange; + + virtual bool GetGuideDir ( idVec3 &outDir, float& outDist ); + +private: + + idInterpolate turn_max; + int launchTime; + int guideDelay; + int driftDelay; +}; + +ID_INLINE int idGuidedProjectile::GetGuideType ( void ) const { + return guideType; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos, const idVec3& dir ) { + guideType = GUIDE_DIR; + guidePos = pos; + guideDir = dir; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos ) { + guideType = GUIDE_POS; + guidePos = pos; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( idEntity* ent, jointHandle_t joint, const idVec3 &offset ) { + guideType = GUIDE_ENTITY; + guideEnt = ent; + guideJoint = joint; + guidePos = offset; + + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( this ); + } +} + +ID_INLINE void idGuidedProjectile::CancelGuide ( void ) { + guideType = GUIDE_NONE; + + // twhitaker: TEMP + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + // +} + +/* +=============================================================================== + +rvDriftingProjectile + +=============================================================================== +*/ + +class rvDriftingProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDriftingProjectile ); + + rvDriftingProjectile ( void ); + ~rvDriftingProjectile ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + virtual void Launch ( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + +protected: + + virtual void UpdateVisualAngles ( void ); + + idVec3 startDir; + idVec3 startOrigin; + idMat3 startAxis; + float startSpeed; + + idInterpolateAccelDecelLinear driftOffset[2]; + idInterpolateAccelDecelLinear driftSpeed; + float driftOffsetMax; + float driftSpeedMax; + float driftTime; +}; + +/* +=============================================================================== + +rvSpawnerProjectile + +=============================================================================== +*/ + +class rvSpawner; +class rvSpawnerProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvSpawnerProjectile ); + + rvSpawnerProjectile ( void ); + ~rvSpawnerProjectile ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + void SetSpawner ( rvSpawner* spawner ); + +protected: + + idEntityPtr spawner; + + enum { + STATE_NONE, + STATE_ADDED, + } spawnState; + +private: + + void Event_PostSpawn ( void ); +}; + +/* +=============================================================================== + +rvMIRVProjectile + +=============================================================================== +*/ + +class rvMIRVProjectile : public idProjectile { + CLASS_PROTOTYPE( rvMIRVProjectile ); + + rvMIRVProjectile ( void ); + ~rvMIRVProjectile ( void ); + + + void Spawn ( void ); + +private: + + void Event_LaunchWarheads ( void ); +}; + +#endif /* !__GAME_PROJECTILE_H__ */ + +// RAVEN END diff --git a/source/mpgame/Pvs.cpp b/source/mpgame/Pvs.cpp new file mode 100644 index 0000000..c43353c --- /dev/null +++ b/source/mpgame/Pvs.cpp @@ -0,0 +1,1439 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define MAX_BOUNDS_AREAS 16 + + +typedef struct pvsPassage_s { + byte * canSee; // bit set for all portals that can be seen through this passage +} pvsPassage_t; + + +typedef struct pvsPortal_s { + int areaNum; // area this portal leads to + idWinding * w; // winding goes counter clockwise seen from the area this portal is part of + idBounds bounds; // winding bounds + idPlane plane; // winding plane, normal points towards the area this portal leads to + pvsPassage_t * passages; // passages to portals in the area this portal leads to + bool done; // true if pvs is calculated for this portal + byte * vis; // PVS for this portal + byte * mightSee; // used during construction +} pvsPortal_t; + + +typedef struct pvsArea_s { + int numPortals; // number of portals in this area + idBounds bounds; // bounds of the whole area + pvsPortal_t ** portals; // array with pointers to the portals of this area +} pvsArea_t; + + +typedef struct pvsStack_s { + struct pvsStack_s * next; // next stack entry + byte * mightSee; // bit set for all portals that might be visible through this passage/portal stack +} pvsStack_t; + + +/* +================ +idPVS::idPVS +================ +*/ +idPVS::idPVS( void ) { + int i; + + numAreas = 0; + numPortals = 0; + + connectedAreas = NULL; + areaQueue = NULL; + areaPVS = NULL; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = NULL; + } + + pvsAreas = NULL; + pvsPortals = NULL; +} + +/* +================ +idPVS::~idPVS +================ +*/ +idPVS::~idPVS( void ) { + Shutdown(); +} + +/* +================ +idPVS::GetPortalCount +================ +*/ +int idPVS::GetPortalCount( void ) const { + int i, na, np; + + na = gameRenderWorld->NumAreas(); + np = 0; + for ( i = 0; i < na; i++ ) { + np += gameRenderWorld->NumPortalsInArea( i ); + } + return np; +} + +/* +================ +idPVS::CreatePVSData +================ +*/ +void idPVS::CreatePVSData( void ) { + int i, j, n, cp; + exitPortal_t portal; + pvsArea_t *area; + pvsPortal_t *p, **portalPtrs; + + if ( !numPortals ) { + return; + } + + pvsPortals = new pvsPortal_t[numPortals]; + pvsAreas = new pvsArea_t[numAreas]; + memset( pvsAreas, 0, numAreas * sizeof( *pvsAreas ) ); + + cp = 0; + portalPtrs = new pvsPortal_t*[numPortals]; + + for ( i = 0; i < numAreas; i++ ) { + + area = &pvsAreas[i]; + area->bounds.Clear(); + area->portals = portalPtrs + cp; + + n = gameRenderWorld->NumPortalsInArea( i ); + + for ( j = 0; j < n; j++ ) { + + portal = gameRenderWorld->GetPortal( i, j ); + + p = &pvsPortals[cp++]; + // the winding goes counter clockwise seen from this area + p->w = portal.w->Copy(); + p->areaNum = portal.areas[1]; // area[1] is always the area the portal leads to + + p->vis = new byte[portalVisBytes]; + memset( p->vis, 0, portalVisBytes ); + p->mightSee = new byte[portalVisBytes]; + memset( p->mightSee, 0, portalVisBytes ); + p->w->GetBounds( p->bounds ); + p->w->GetPlane( p->plane ); + // plane normal points to outside the area + p->plane = -p->plane; + // no PVS calculated for this portal yet + p->done = false; + + area->portals[area->numPortals] = p; + area->numPortals++; + + area->bounds += p->bounds; + } + } +} + +/* +================ +idPVS::DestroyPVSData +================ +*/ +void idPVS::DestroyPVSData( void ) { + int i; + + if ( !pvsAreas ) { + return; + } + + // delete portal pointer array + delete[] pvsAreas[0].portals; + + // delete all areas + delete[] pvsAreas; + pvsAreas = NULL; + + // delete portal data + for ( i = 0; i < numPortals; i++ ) { + delete[] pvsPortals[i].vis; + delete[] pvsPortals[i].mightSee; + delete pvsPortals[i].w; + } + + // delete portals + delete[] pvsPortals; + pvsPortals = NULL; +} + +/* +================ +idPVS::FloodFrontPortalPVS_r +================ +*/ +void idPVS::FloodFrontPortalPVS_r( pvsPortal_t *portal, int areaNum ) const { + int i, n; + pvsArea_t *area; + pvsPortal_t *p; + + area = &pvsAreas[ areaNum ]; + + for ( i = 0; i < area->numPortals; i++ ) { + p = area->portals[i]; + n = p - pvsPortals; + // don't flood through if this portal is not at the front + if ( !( portal->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + continue; + } + // don't flood through if already visited this portal + if ( portal->vis[ n>>3 ] & (1 << (n&7)) ) { + continue; + } + // this portal might be visible + portal->vis[ n>>3 ] |= (1 << (n&7)); + // flood through the portal + FloodFrontPortalPVS_r( portal, p->areaNum ); + } +} + +/* +================ +idPVS::FrontPortalPVS +================ +*/ +void idPVS::FrontPortalPVS( void ) const { + int i, j, k, n, p, side1, side2, areaSide; + pvsPortal_t *p1, *p2; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + + for ( j = 0; j < numAreas; j++ ) { + + area = &pvsAreas[j]; + + areaSide = side1 = area->bounds.PlaneSide( p1->plane ); + + // if the whole area is at the back side of the portal + if ( areaSide == PLANESIDE_BACK ) { + continue; + } + + for ( p = 0; p < area->numPortals; p++ ) { + + p2 = area->portals[p]; + + // if we the whole area is not at the front we need to check + if ( areaSide != PLANESIDE_FRONT ) { + // if the second portal is completely at the back side of the first portal + side1 = p2->bounds.PlaneSide( p1->plane ); + if ( side1 == PLANESIDE_BACK ) { + continue; + } + } + + // if the first portal is completely at the front of the second portal + side2 = p1->bounds.PlaneSide( p2->plane ); + if ( side2 == PLANESIDE_FRONT ) { + continue; + } + + // if the second portal is not completely at the front of the first portal + if ( side1 != PLANESIDE_FRONT ) { + // more accurate check + for ( k = 0; k < p2->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the front side + if ( p1->plane.Side( (*p2->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_FRONT ) { + break; + } + } + if ( k >= p2->w->GetNumPoints() ) { + continue; // second portal is at the back of the first portal + } + } + + // if the first portal is not completely at the back side of the second portal + if ( side2 != PLANESIDE_BACK ) { + // more accurate check + for ( k = 0; k < p1->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the back side + if ( p2->plane.Side( (*p1->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_BACK ) { + break; + } + } + if ( k >= p1->w->GetNumPoints() ) { + continue; // first portal is at the front of the second portal + } + } + + // the portal might be visible at the front + n = p2 - pvsPortals; + p1->mightSee[ n >> 3 ] |= 1 << (n&7); + } + } + } + + // flood the front portal pvs for all portals + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + FloodFrontPortalPVS_r( p1, p1->areaNum ); + } +} + +/* +=============== +idPVS::FloodPassagePVS_r +=============== +*/ +pvsStack_t *idPVS::FloodPassagePVS_r( pvsPortal_t *source, const pvsPortal_t *portal, pvsStack_t *prevStack ) const { + int i, j, n, m; + pvsPortal_t *p; + pvsArea_t *area; + pvsStack_t *stack; + pvsPassage_t *passage; + long *sourceVis, *passageVis, *portalVis, *mightSee, *prevMightSee, more; + + area = &pvsAreas[portal->areaNum]; + + stack = prevStack->next; + // if no next stack entry allocated + if ( !stack ) { + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + prevStack->next = stack; + } + + // check all portals for flooding into other areas + for ( i = 0; i < area->numPortals; i++ ) { + + passage = &portal->passages[i]; + + // if this passage is completely empty + if ( !passage->canSee ) { + continue; + } + + p = area->portals[i]; + n = p - pvsPortals; + + // if this portal cannot be seen through our current portal/passage stack + if ( !( prevStack->mightSee[n >> 3] & (1 << (n & 7)) ) ) { + continue; + } + + // mark the portal as visible + source->vis[n >> 3] |= (1 << (n & 7)); + + // get pointers to vis data + prevMightSee = reinterpret_cast(prevStack->mightSee); + passageVis = reinterpret_cast(passage->canSee); + sourceVis = reinterpret_cast(source->vis); + mightSee = reinterpret_cast(stack->mightSee); + + more = 0; + // use the portal PVS if it has been calculated + if ( p->done ) { + portalVis = reinterpret_cast(p->vis); + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++ & *portalVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + else { + // the p->mightSee is implicitely stored in the passageVis + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + + // if nothing more can be seen + if ( !more ) { + continue; + } + + // go through the portal + stack->next = FloodPassagePVS_r( source, p, stack ); + } + + return stack; +} + +/* +=============== +idPVS::PassagePVS +=============== +*/ +void idPVS::PassagePVS( void ) const { + int i; + pvsPortal_t *source; + pvsStack_t *stack, *s; + + // create the passages + CreatePassages(); + + // allocate first stack entry + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + + // calculate portal PVS by flooding through the passages + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + memset( source->vis, 0, portalVisBytes ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( stack->mightSee, source->mightSee, portalVisBytes ); +// RAVEN END + FloodPassagePVS_r( source, source, stack ); + source->done = true; + } + + // free the allocated stack + for ( s = stack; s; s = stack ) { + stack = stack->next; + delete[] s; + } + + // destroy the passages + DestroyPassages(); +} + +/* +=============== +idPVS::AddPassageBoundaries +=============== +*/ +void idPVS::AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const { + int i, j, k, l; + idVec3 v1, v2, normal; + float d, dist; + bool flipTest, front; + idPlane plane; + + + // check all combinations + for ( i = 0; i < source.GetNumPoints(); i++ ) { + + l = (i + 1) % source.GetNumPoints(); + v1 = source[l].ToVec3() - source[i].ToVec3(); + + // find a vertex of pass that makes a plane that puts all of the + // vertices of pass on the front side and all of the vertices of + // source on the back side + for ( j = 0; j < pass.GetNumPoints(); j++ ) { + + v2 = pass[j].ToVec3() - source[i].ToVec3(); + + normal = v1.Cross( v2 ); + if ( normal.Normalize() < 0.01f ) { + continue; + } + dist = normal * pass[j].ToVec3(); + + // + // find out which side of the generated seperating plane has the + // source portal + // + flipTest = false; + for ( k = 0; k < source.GetNumPoints(); k++ ) { + if ( k == i || k == l ) { + continue; + } + d = source[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + // source is on the negative side, so we want all + // pass and target on the positive side + flipTest = false; + break; + } + else if ( d > ON_EPSILON ) { + // source is on the positive side, so we want all + // pass and target on the negative side + flipTest = true; + break; + } + } + if ( k == source.GetNumPoints() ) { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (flipTest) { + normal = -normal; + dist = -dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + front = false; + for ( k = 0; k < pass.GetNumPoints(); k++ ) { + if ( k == j ) { + continue; + } + d = pass[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + break; + } + else if ( d > ON_EPSILON ) { + front = true; + } + } + if ( k < pass.GetNumPoints() ) { + continue; // points on negative side, not a seperating plane + } + if ( !front ) { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if ( flipClip ) { + plane.SetNormal( -normal ); + plane.SetDist( -dist ); + } + else { + plane.SetNormal( normal ); + plane.SetDist( dist ); + } + + // check if the plane is already a passage boundary + for ( k = 0; k < numBounds; k++ ) { + if ( plane.Compare( bounds[k], 0.001f, 0.01f ) ) { + break; + } + } + if ( k < numBounds ) { + break; + } + + if ( numBounds >= maxBounds ) { + gameLocal.Warning( "max passage boundaries." ); + break; + } + bounds[numBounds] = plane; + numBounds++; + break; + } + } +} + +/* +================ +idPVS::CreatePassages +================ +*/ +#define MAX_PASSAGE_BOUNDS 128 + +void idPVS::CreatePassages( void ) const { + int i, j, l, n, numBounds, front, passageMemory, byteNum, bitNum; + int sides[MAX_PASSAGE_BOUNDS]; + idPlane passageBounds[MAX_PASSAGE_BOUNDS]; + pvsPortal_t *source, *target, *p; + pvsArea_t *area; + pvsPassage_t *passage; + idFixedWinding winding; + byte canSee, mightSee, bit; + + passageMemory = 0; + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + area = &pvsAreas[source->areaNum]; + + source->passages = new pvsPassage_t[area->numPortals]; + + for ( j = 0; j < area->numPortals; j++ ) { + target = area->portals[j]; + n = target - pvsPortals; + + passage = &source->passages[j]; + + // if the source portal cannot see this portal + if ( !( source->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + // not all portals in the area have to be visible because areas are not necesarily convex + // also no passage has to be created for the portal which is the opposite of the source + passage->canSee = NULL; + continue; + } + + passage->canSee = new byte[portalVisBytes]; + passageMemory += portalVisBytes; + + // boundary plane normals point inwards + numBounds = 0; + AddPassageBoundaries( *(source->w), *(target->w), false, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + AddPassageBoundaries( *(target->w), *(source->w), true, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + + // get all portals visible through this passage + for ( byteNum = 0; byteNum < portalVisBytes; byteNum++) { + + canSee = 0; + mightSee = source->mightSee[byteNum] & target->mightSee[byteNum]; + + // go through eight portals at a time to speed things up + for ( bitNum = 0; bitNum < 8; bitNum++ ) { + + bit = 1 << bitNum; + + if ( !( mightSee & bit ) ) { + continue; + } + + p = &pvsPortals[(byteNum << 3) + bitNum]; + + if ( p->areaNum == source->areaNum ) { + continue; + } + + for ( front = 0, l = 0; l < numBounds; l++ ) { + sides[l] = p->bounds.PlaneSide( passageBounds[l] ); + // if completely at the back of the passage bounding plane + if ( sides[l] == PLANESIDE_BACK ) { + break; + } + // if completely at the front + if ( sides[l] == PLANESIDE_FRONT ) { + front++; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + + // if not at the front of all bounding planes and thus not completely inside the passage + if ( front != numBounds ) { + + winding = *p->w; + + for ( l = 0; l < numBounds; l++ ) { + // only clip if the winding possibly crosses this plane + if ( sides[l] != PLANESIDE_CROSS ) { + continue; + } + // clip away the part at the back of the bounding plane + winding.ClipInPlace( passageBounds[l] ); + // if completely clipped away + if ( !winding.GetNumPoints() ) { + break; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + } + + canSee |= bit; + } + + // store results of all eight portals + passage->canSee[byteNum] = canSee; + } + + // can always see the target portal + passage->canSee[n >> 3] |= (1 << (n&7)); + } + } + if ( passageMemory < 1024 ) { + gameLocal.Printf( "%5d bytes passage memory used to build PVS\n", passageMemory ); + } + else { + gameLocal.Printf( "%5d KB passage memory used to build PVS\n", passageMemory>>10 ); + } +} + +/* +================ +idPVS::DestroyPassages +================ +*/ +void idPVS::DestroyPassages( void ) const { + int i, j; + pvsPortal_t *p; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + area = &pvsAreas[p->areaNum]; + for ( j = 0; j < area->numPortals; j++ ) { + if ( p->passages[j].canSee ) { + delete[] p->passages[j].canSee; + } + } + delete[] p->passages; + } +} + +/* +================ +idPVS::CopyPortalPVSToMightSee +================ +*/ +void idPVS::CopyPortalPVSToMightSee( void ) const { + int i; + pvsPortal_t *p; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( p->mightSee, p->vis, portalVisBytes ); +// RAVEN BEGIN + } +} + +/* +================ +idPVS::AreaPVSFromPortalPVS +================ +*/ +int idPVS::AreaPVSFromPortalPVS( void ) const { + int i, j, k, areaNum, totalVisibleAreas; + long *p1, *p2; + byte *pvs, *portalPVS; + pvsArea_t *area; + + totalVisibleAreas = 0; + + if ( !numPortals ) { + return totalVisibleAreas; + } + + memset( areaPVS, 0, numAreas * areaVisBytes ); + + for ( i = 0; i < numAreas; i++ ) { + area = &pvsAreas[i]; + pvs = areaPVS + i * areaVisBytes; + + // the area is visible to itself + pvs[ i >> 3 ] |= 1 << (i & 7); + + if ( !area->numPortals ) { + continue; + } + + // store the PVS of all portals in this area at the first portal + for ( j = 1; j < area->numPortals; j++ ) { + p1 = reinterpret_cast(area->portals[0]->vis); + p2 = reinterpret_cast(area->portals[j]->vis); + for ( k = 0; k < portalVisLongs; k++ ) { + *p1++ |= *p2++; + } + } + + // the portals of this area are always visible + for ( j = 0; j < area->numPortals; j++ ) { + k = area->portals[j] - pvsPortals; + area->portals[0]->vis[ k >> 3 ] |= 1 << (k & 7); + } + + // set all areas to visible that can be seen from the portals of this area + portalPVS = area->portals[0]->vis; + for ( j = 0; j < numPortals; j++ ) { + // if this portal is visible + if ( portalPVS[j>>3] & (1 << (j&7)) ) { + areaNum = pvsPortals[j].areaNum; + pvs[ areaNum >> 3 ] |= 1 << (areaNum & 7); + } + } + + // count the number of visible areas + for ( j = 0; j < numAreas; j++ ) { + if ( pvs[j>>3] & (1 << (j&7)) ) { + totalVisibleAreas++; + } + } + } + return totalVisibleAreas; +} + +/* +================ +idPVS::Init +================ +*/ +void idPVS::Init( void ) { + int totalVisibleAreas; + + Shutdown(); + + numAreas = gameRenderWorld->NumAreas(); + if ( numAreas <= 0 ) { + return; + } + + connectedAreas = new bool[numAreas]; + areaQueue = new int[numAreas]; + + areaVisBytes = ( ((numAreas+31)&~31) >> 3); + areaVisLongs = areaVisBytes/sizeof(long); + areaPVS = new byte[numAreas * areaVisBytes]; + memset( areaPVS, 0xFF, numAreas * areaVisBytes ); + + numPortals = GetPortalCount(); + + portalVisBytes = ( ((numPortals+31)&~31) >> 3); + portalVisLongs = portalVisBytes/sizeof(long); + + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = new byte[areaVisBytes]; + memset( currentPVS[i].pvs, 0, areaVisBytes ); + } + + idTimer timer; + timer.Start(); + + CreatePVSData(); + + FrontPortalPVS(); + + CopyPortalPVSToMightSee(); + + PassagePVS(); + + totalVisibleAreas = AreaPVSFromPortalPVS(); + + DestroyPVSData(); + + timer.Stop(); + +#if 0 + // bkreimeier20060123 - per MrE's recommendation, used this in Wolf + // if the PVS is not relexive for (i,j), we assume floating point errors + // and assume a false positive - remove either from either + for ( int i=0; i>3] & (1<<(j&7))); + const bool i_in_j = (pvs_j[i>>3] & (1<<(i&7))); + if ( i_in_j!=j_in_i ) { + pvs_i[j>>3] &= ~(1 << (j&7)); + pvs_j[i>>3] &= ~(1 << (i&7)); + } + } + } +#endif + + gameLocal.Printf( "%5.0f msec to calculate PVS\n", timer.Milliseconds() ); + gameLocal.Printf( "%5d areas\n", numAreas ); + gameLocal.Printf( "%5d portals\n", numPortals ); + +// RAVEN BEGIN +// rjohnson: fix for div by 0 + if( numAreas ) { + gameLocal.Printf( "%5d areas visible on average\n", totalVisibleAreas / numAreas ); + } +// RAVEN END + + if ( numAreas * areaVisBytes < 1024 ) { + gameLocal.Printf( "%5d bytes PVS data\n", numAreas * areaVisBytes ); + } + else { + gameLocal.Printf( "%5d KB PVS data\n", (numAreas * areaVisBytes) >> 10 ); + } +} + +/* +================ +idPVS::Shutdown +================ +*/ +void idPVS::Shutdown( void ) { + if ( connectedAreas ) { + delete[] connectedAreas; + connectedAreas = NULL; + } + if ( areaQueue ) { + delete[] areaQueue; + areaQueue = NULL; + } + if ( areaPVS ) { + delete[] areaPVS; + areaPVS = NULL; + } + if ( currentPVS ) { + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { +// RAVEN BEGIN +// jsinger: modified to check to make sure the pointers have a value before attempting to delete +// them. This prevents a call through the unified allocator before it has been initialized + if(currentPVS[i].pvs) + { + delete[] currentPVS[i].pvs; + } +// RAVEN END + currentPVS[i].pvs = NULL; + } + } +} + +/* +================ +idPVS::GetConnectedAreas + + assumes the 'areas' array is initialized to false +================ +*/ +void idPVS::GetConnectedAreas( int srcArea, bool *areas ) const { + int curArea, nextArea; + int queueStart, queueEnd; + int i, n; + exitPortal_t portal; + + queueStart = -1; + queueEnd = 0; + areas[srcArea] = true; + + for ( curArea = srcArea; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + n = gameRenderWorld->NumPortalsInArea( curArea ); + + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( curArea, i ); + + if ( portal.blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // area[1] is always the area the portal leads to + nextArea = portal.areas[1]; + + // if already visited this area + if ( areas[nextArea] ) { + continue; + } + + // add area to queue + areaQueue[queueEnd++] = nextArea; + areas[nextArea] = true; + } + } +} + +/* +================ +idPVS::GetPVSArea +================ +*/ +int idPVS::GetPVSArea( const idVec3 &point ) const { + return gameRenderWorld->PointInArea( point ); +} + +/* +================ +idPVS::GetPVSAreas +================ +*/ +int idPVS::GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + return gameRenderWorld->BoundsInAreas( bounds, areas, maxAreas ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idVec3 &source, const pvsType_t type ) const { + int sourceArea; + + sourceArea = gameRenderWorld->PointInArea( source ); + + return SetupCurrentPVS( sourceArea, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idBounds &source, const pvsType_t type ) const { + int numSourceAreas, sourceAreas[MAX_BOUNDS_AREAS]; + + numSourceAreas = gameRenderWorld->BoundsInAreas( source, sourceAreas, MAX_BOUNDS_AREAS ); + + return SetupCurrentPVS( sourceAreas, numSourceAreas, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int sourceArea, const pvsType_t type ) const { + int i; + pvsHandle_t handle; + + handle = AllocCurrentPVS( *reinterpret_cast(&sourceArea) ); + + if ( sourceArea < 0 || sourceArea >= numAreas ) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceArea * areaVisBytes, areaVisBytes ); +// RAVEN END + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + GetConnectedAreas( sourceArea, connectedAreas ); + + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type ) const { + int i, j; + unsigned int h; + long *vis, *pvs; + pvsHandle_t handle; + + h = 0; + for ( i = 0; i < numSourceAreas; i++ ) { + h ^= *reinterpret_cast(&sourceAreas[i]); + } + handle = AllocCurrentPVS( h ); + + if ( !numSourceAreas || sourceAreas[0] < 0 || sourceAreas[0] >= numAreas) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + // merge PVS of all areas the source is in +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceAreas[0] * areaVisBytes, areaVisBytes ); +// RAVEN END + for ( i = 1; i < numSourceAreas; i++ ) { + + assert( sourceAreas[i] >= 0 && sourceAreas[i] < numAreas ); + + vis = reinterpret_cast(areaPVS + sourceAreas[i] * areaVisBytes); + pvs = reinterpret_cast(currentPVS[handle.i].pvs); + for ( j = 0; j < areaVisLongs; j++ ) { + *pvs++ |= *vis++; + } + } + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + // get all areas connected to any of the source areas + for ( i = 0; i < numSourceAreas; i++ ) { + if ( !connectedAreas[sourceAreas[i]] ) { + GetConnectedAreas( sourceAreas[i], connectedAreas ); + } + } + + // remove unconnected areas from the PVS + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::MergeCurrentPVS +================ +*/ +pvsHandle_t idPVS::MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const { + int i; + long *pvs1Ptr, *pvs2Ptr, *ptr; + pvsHandle_t handle; + + if ( pvs1.i < 0 || pvs1.i >= MAX_CURRENT_PVS || pvs1.h != currentPVS[pvs1.i].handle.h || + pvs2.i < 0 || pvs2.i >= MAX_CURRENT_PVS || pvs2.h != currentPVS[pvs2.i].handle.h ) { + gameLocal.Error( "idPVS::MergeCurrentPVS: invalid handle" ); + } + + handle = AllocCurrentPVS( pvs1.h ^ pvs2.h ); + + ptr = reinterpret_cast(currentPVS[handle.i].pvs); + pvs1Ptr = reinterpret_cast(currentPVS[pvs1.i].pvs); + pvs2Ptr = reinterpret_cast(currentPVS[pvs2.i].pvs); + + for ( i = 0; i < areaVisLongs; i++ ) { + *ptr++ = *pvs1Ptr++ | *pvs2Ptr++; + } + + return handle; +} + +/* +================ +idPVS::AllocCurrentPVS +================ +*/ +pvsHandle_t idPVS::AllocCurrentPVS( unsigned int h ) const { + int i; + pvsHandle_t handle; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + if ( currentPVS[i].handle.i == -1 ) { + currentPVS[i].handle.i = i; + currentPVS[i].handle.h = h; + return currentPVS[i].handle; + } + } + + gameLocal.Error( "idPVS::AllocCurrentPVS: no free PVS left" ); + + handle.i = -1; + handle.h = 0; + return handle; +} + +/* +================ +idPVS::FreeCurrentPVS +================ +*/ +void idPVS::FreeCurrentPVS( pvsHandle_t handle ) const { + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::FreeCurrentPVS: invalid handle" ); + } + currentPVS[handle.i].handle.i = -1; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const { + int targetArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + targetArea = gameRenderWorld->PointInArea( target ); + + if ( targetArea == -1 ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const { + int i, numTargetAreas, targetAreas[MAX_BOUNDS_AREAS]; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + numTargetAreas = gameRenderWorld->BoundsInAreas( target, targetAreas, MAX_BOUNDS_AREAS ); + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const { + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + if ( targetArea < 0 || targetArea >= numAreas ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const { + int i; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( targetAreas[i] < 0 || targetAreas[i] >= numAreas ) { + continue; + } + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idVec3 &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idBounds &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, num, areas[MAX_BOUNDS_AREAS]; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + num = gameRenderWorld->BoundsInAreas( source, areas, MAX_BOUNDS_AREAS ); + + if ( !num ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + for ( i = 0; i < num; i++ ) { + if ( j == areas[i] ) { + break; + } + } + if ( i < num ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::DrawCurrentPVS: invalid handle" ); + } + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } +} + +#if ASYNC_WRITE_PVS + +/* +=================== +idPVS::WritePVS +=================== +*/ +void idPVS::WritePVS( const pvsHandle_t handle, idBitMsg &msg ) { + msg.WriteData( currentPVS[ handle.i ].pvs, areaVisBytes ); +} + +/* +=================== +idPVS::ReadPVS +=================== +*/ +void idPVS::ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ) { + byte l_pvs[ 256 ]; + int i; + + assert( areaVisBytes <= 256 ); + msg.ReadData( l_pvs, areaVisBytes ); + if ( memcmp( l_pvs, currentPVS[ handle.i ].pvs, areaVisBytes ) ) { + common->Printf( "PVS not matching ( %d areaVisBytes ) - server then client:\n", areaVisBytes ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", l_pvs[ i ] ); + } + common->Printf( "\n" ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", currentPVS[ handle.i ].pvs[ i ] ); + } + common->Printf( "\n" ); + } +} + +#endif + diff --git a/source/mpgame/Pvs.h b/source/mpgame/Pvs.h new file mode 100644 index 0000000..3f84689 --- /dev/null +++ b/source/mpgame/Pvs.h @@ -0,0 +1,104 @@ + +#ifndef __GAME_PVS_H__ +#define __GAME_PVS_H__ + +/* +=================================================================================== + + PVS + + Note: mirrors and other special view portals are not taken into account + +=================================================================================== +*/ + + +typedef struct pvsHandle_s { + int i; // index to current pvs + unsigned int h; // handle for current pvs +} pvsHandle_t; + +typedef struct pvsCurrent_s { + pvsHandle_t handle; // current pvs handle + byte * pvs; // current pvs bit string +} pvsCurrent_t; + +// must be a power of 2 +// base was 8, MP now stores client PVS from frame to frame ( MAX_CLIENTS ) +#define MAX_CURRENT_PVS 64 + +typedef enum { + PVS_NORMAL = 0, // PVS through portals taking portal states into account + PVS_ALL_PORTALS_OPEN = 1, // PVS through portals assuming all portals are open + PVS_CONNECTED_AREAS = 2 // PVS considering all topologically connected areas visible +} pvsType_t; + + +class idPVS { +public: + idPVS( void ); + ~idPVS( void ); + // setup for the current map + void Init( void ); + void Shutdown( void ); + // get the area(s) the source is in + int GetPVSArea( const idVec3 &point ) const; // returns the area number + int GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const; // returns number of areas + // setup current PVS for the source + pvsHandle_t SetupCurrentPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int sourceArea, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const; + void FreeCurrentPVS( pvsHandle_t handle ) const; + // returns true if the target is within the current PVS + bool InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const; + // draw all portals that are within the PVS of the source + void DrawPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + void DrawPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + // visualize the PVS the handle points to + void DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const; + +#if ASYNC_WRITE_PVS + void WritePVS( const pvsHandle_t handle, idBitMsg &msg ); + void ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ); +#endif + +private: + int numAreas; + int numPortals; + bool * connectedAreas; + int * areaQueue; + byte * areaPVS; + + // current PVS for a specific source possibly taking portal states (open/closed) into account + mutable pvsCurrent_t currentPVS[MAX_CURRENT_PVS]; + // used to create PVS + int portalVisBytes; + int portalVisLongs; + int areaVisBytes; + int areaVisLongs; + struct pvsPortal_s *pvsPortals; + struct pvsArea_s * pvsAreas; + +private: + int GetPortalCount( void ) const; + void CreatePVSData( void ); + void DestroyPVSData( void ); + void CopyPortalPVSToMightSee( void ) const; + void FloodFrontPortalPVS_r( struct pvsPortal_s *portal, int areaNum ) const; + void FrontPortalPVS( void ) const; + struct pvsStack_s * FloodPassagePVS_r( struct pvsPortal_s *source, const struct pvsPortal_s *portal, struct pvsStack_s *prevStack ) const; + void PassagePVS( void ) const; + void AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const; + void CreatePassages( void ) const; + void DestroyPassages( void ) const; + int AreaPVSFromPortalPVS( void ) const; + void GetConnectedAreas( int srcArea, bool *connectedAreas ) const; + pvsHandle_t AllocCurrentPVS( unsigned int h ) const; +}; + +#endif /* !__GAME_PVS_H__ */ diff --git a/source/mpgame/SecurityCamera.cpp b/source/mpgame/SecurityCamera.cpp new file mode 100644 index 0000000..ad9c047 --- /dev/null +++ b/source/mpgame/SecurityCamera.cpp @@ -0,0 +1,578 @@ +/* + + SecurityCamera.cpp + + Security camera that triggers targets when player is in view + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idSecurityCamera + +***********************************************************************/ + +const idEventDef EV_SecurityCam_ReverseSweep( "" ); +const idEventDef EV_SecurityCam_ContinueSweep( "" ); +const idEventDef EV_SecurityCam_Pause( "" ); +const idEventDef EV_SecurityCam_Alert( "" ); +const idEventDef EV_SecurityCam_AddLight( "" ); + +CLASS_DECLARATION( idEntity, idSecurityCamera ) + EVENT( EV_SecurityCam_ReverseSweep, idSecurityCamera::Event_ReverseSweep ) + EVENT( EV_SecurityCam_ContinueSweep, idSecurityCamera::Event_ContinueSweep ) + EVENT( EV_SecurityCam_Pause, idSecurityCamera::Event_Pause ) + EVENT( EV_SecurityCam_Alert, idSecurityCamera::Event_Alert ) + EVENT( EV_SecurityCam_AddLight, idSecurityCamera::Event_AddLight ) +END_CLASS + + +idSecurityCamera::~idSecurityCamera() { + SetPhysics( NULL ); +} + +/* +================ +idSecurityCamera::Save +================ +*/ +void idSecurityCamera::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( angle ); + savefile->WriteFloat( sweepAngle ); + savefile->WriteInt( modelAxis ); + savefile->WriteBool( flipAxis ); + savefile->WriteFloat( scanDist ); + savefile->WriteFloat( scanFov ); + + savefile->WriteFloat( sweepStart ); + savefile->WriteFloat( sweepEnd ); + savefile->WriteBool( negativeSweep ); + savefile->WriteBool( sweeping ); + savefile->WriteInt( alertMode ); + savefile->WriteFloat( stopSweeping ); + savefile->WriteFloat( scanFovCos ); + + savefile->WriteVec3( viewOffset ); + + savefile->WriteInt( pvsArea ); + savefile->WriteStaticObject( physicsObj ); + savefile->Write( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Restore +================ +*/ +void idSecurityCamera::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( angle ); + savefile->ReadFloat( sweepAngle ); + savefile->ReadInt( modelAxis ); + savefile->ReadBool( flipAxis ); + savefile->ReadFloat( scanDist ); + savefile->ReadFloat( scanFov ); + + savefile->ReadFloat( sweepStart ); + savefile->ReadFloat( sweepEnd ); + savefile->ReadBool( negativeSweep ); + savefile->ReadBool( sweeping ); + savefile->ReadInt( alertMode ); + savefile->ReadFloat( stopSweeping ); + savefile->ReadFloat( scanFovCos ); + + savefile->ReadVec3( viewOffset ); + + savefile->ReadInt( pvsArea ); + savefile->ReadStaticObject( physicsObj ); + savefile->Read( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Spawn +================ +*/ +void idSecurityCamera::Spawn( void ) { + idStr str; + + sweepAngle = spawnArgs.GetFloat( "sweepAngle", "90" ); + health = spawnArgs.GetInt( "health", "100" ); + scanFov = spawnArgs.GetFloat( "scanFov", "90" ); + scanDist = spawnArgs.GetFloat( "scanDist", "200" ); + flipAxis = spawnArgs.GetBool( "flipAxis" ); + + modelAxis = spawnArgs.GetInt( "modelAxis" ); + if ( modelAxis < 0 || modelAxis > 2 ) { + modelAxis = 0; + } + + spawnArgs.GetVector( "viewOffset", "0 0 0", viewOffset ); + + if ( spawnArgs.GetBool( "spotLight" ) ) { + PostEventMS( &EV_SecurityCam_AddLight, 0 ); + } + + negativeSweep = ( sweepAngle < 0 ) ? true : false; + sweepAngle = abs( sweepAngle ); + + scanFovCos = idMath::Cos( scanFov * idMath::PI / 360.0f ); + + angle = GetPhysics()->GetAxis().ToAngles().yaw; + StartSweep(); + SetAlertMode( SCANNING ); + BecomeActive( TH_THINK ); + + if ( health ) { + fl.takedamage = true; + } + + pvsArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + // if no target specified use ourself + str = spawnArgs.GetString( "cameraTarget" ); + if ( str.Length() == 0 ) { + spawnArgs.Set( "cameraTarget", spawnArgs.GetString( "name" ) ); + } + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", str ); + if ( !str[0] ) { + str = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), str, trm ) ) { + gameLocal.Error( "idSecurityCamera '%s': cannot load collision model %s", name.c_str(), str.c_str() ); + return; + } + + GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + // setup the physics + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idSecurityCamera::Event_AddLight +================ +*/ +void idSecurityCamera::Event_AddLight( void ) { + idDict args; + idVec3 right, up, target, temp; + idVec3 dir; + float radius; + idVec3 lightOffset; + idLight *spotLight; + + dir = GetAxis(); + dir.NormalVectors( right, up ); + target = GetPhysics()->GetOrigin() + dir * scanDist; + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + up = dir + up * radius; + up.Normalize(); + up = GetPhysics()->GetOrigin() + up * scanDist; + up -= target; + + right = dir + right * radius; + right.Normalize(); + right = GetPhysics()->GetOrigin() + right * scanDist; + right -= target; + + spawnArgs.GetVector( "lightOffset", "0 0 0", lightOffset ); + + args.Set( "origin", ( GetPhysics()->GetOrigin() + lightOffset ).ToString() ); + args.Set( "light_target", target.ToString() ); + args.Set( "light_right", right.ToString() ); + args.Set( "light_up", up.ToString() ); + args.SetFloat( "angle", GetPhysics()->GetAxis()[0].ToYaw() ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + spotLight = static_cast( gameLocal.SpawnEntityType( idLight::GetClassType(), &args ) ); +// RAVEN END + spotLight->Bind( this, true ); + spotLight->UpdateVisuals(); +} + +/* +================ +idSecurityCamera::DrawFov +================ +*/ +void idSecurityCamera::DrawFov( void ) { + int i; + float radius, a, s, c, halfRadius; + idVec3 right, up; + idVec4 color(1, 0, 0, 1), color2(0, 0, 1, 1); + idVec3 lastPoint, point, lastHalfPoint, halfPoint, center; + + idVec3 dir = GetAxis(); + dir.NormalVectors( right, up ); + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + halfRadius = radius * 0.5f; + lastPoint = dir + up * radius; + lastPoint.Normalize(); + lastPoint = GetPhysics()->GetOrigin() + lastPoint * scanDist; + lastHalfPoint = dir + up * halfRadius; + lastHalfPoint.Normalize(); + lastHalfPoint = GetPhysics()->GetOrigin() + lastHalfPoint * scanDist; + center = GetPhysics()->GetOrigin() + dir * scanDist; + for ( i = 1; i < 12; i++ ) { + a = idMath::TWO_PI * i / 12.0f; + idMath::SinCos( a, s, c ); + point = dir + right * s * radius + up * c * radius; + point.Normalize(); + point = GetPhysics()->GetOrigin() + point * scanDist; + gameRenderWorld->DebugLine( color, lastPoint, point ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), point ); + lastPoint = point; + + halfPoint = dir + right * s * halfRadius + up * c * halfRadius; + halfPoint.Normalize(); + halfPoint = GetPhysics()->GetOrigin() + halfPoint * scanDist; + gameRenderWorld->DebugLine( color2, point, halfPoint ); + gameRenderWorld->DebugLine( color2, lastHalfPoint, halfPoint ); + lastHalfPoint = halfPoint; + + gameRenderWorld->DebugLine( color2, halfPoint, center ); + } +} + +/* +================ +idSecurityCamera::GetRenderView +================ +*/ +renderView_t *idSecurityCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->fov_x = scanFov; + rv->fov_y = scanFov; + rv->viewaxis = GetAxis().ToAngles().ToMat3(); + rv->vieworg = GetPhysics()->GetOrigin() + viewOffset; + return rv; +} + +/* +================ +idSecurityCamera::CanSeePlayer +================ +*/ +bool idSecurityCamera::CanSeePlayer( void ) { + int i; + float dist; + idPlayer *ent; + trace_t tr; + idVec3 dir; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( pvsArea ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = static_cast(gameLocal.entities[ i ]); + + if ( !ent || ( ent->fl.notarget ) ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.pvs.InCurrentPVS( handle, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dist = dir.Normalize(); + + if ( dist > scanDist ) { + continue; + } + + if ( dir * GetAxis() < scanFovCos ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, GetPhysics()->GetOrigin(), ent->GetEyePosition(), MASK_OPAQUE, this ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return true; + } + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + return false; +} + +/* +================ +idSecurityCamera::SetAlertMode +================ +*/ +void idSecurityCamera::SetAlertMode( int alert ) { + if (alert >= SCANNING && alert <= ACTIVATED) { + alertMode = alert; + } + renderEntity.shaderParms[ SHADERPARM_MODE ] = alertMode; + UpdateVisuals(); +} + +/* +================ +idSecurityCamera::Think +================ +*/ +void idSecurityCamera::Think( void ) { + float pct; + float travel; + + if ( thinkFlags & TH_THINK ) { + if ( g_showEntityInfo.GetBool() ) { + DrawFov(); + } + + if (health <= 0) { + BecomeInactive( TH_THINK ); + return; + } + } + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + if (CanSeePlayer()) { + if (alertMode == SCANNING) { + float sightTime; + + SetAlertMode(ALERT); + stopSweeping = gameLocal.time; + if (sweeping) { + CancelEvents( &EV_SecurityCam_Pause ); + } else { + CancelEvents( &EV_SecurityCam_ReverseSweep ); + } + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_sight", SND_CHANNEL_BODY, 0, false, NULL ); + + sightTime = spawnArgs.GetFloat( "sightTime", "5" ); + PostEventSec(&EV_SecurityCam_Alert, sightTime); + } + } else { + if (alertMode == ALERT) { + float sightResume; + + SetAlertMode(LOSINGINTEREST); + CancelEvents( &EV_SecurityCam_Alert ); + + sightResume = spawnArgs.GetFloat( "sightResume", "1.5" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, sightResume ); + } + + if ( sweeping ) { + idAngles a = GetPhysics()->GetAxis().ToAngles(); + + pct = ( gameLocal.time - sweepStart ) / ( sweepEnd - sweepStart ); + travel = pct * sweepAngle; + if ( negativeSweep ) { + a.yaw = angle + travel; + } else { + a.yaw = angle - travel; + } + + SetAngles( a ); + } + } + } + Present(); +} + +/* +================ +idSecurityCamera::GetAxis +================ +*/ +const idVec3 idSecurityCamera::GetAxis( void ) const { + return (flipAxis) ? -GetPhysics()->GetAxis()[modelAxis] : GetPhysics()->GetAxis()[modelAxis]; +}; + +/* +================ +idSecurityCamera::SweepSpeed +================ +*/ +float idSecurityCamera::SweepSpeed( void ) const { + return spawnArgs.GetFloat( "sweepSpeed", "5" ); +} + +/* +================ +idSecurityCamera::StartSweep +================ +*/ +void idSecurityCamera::StartSweep( void ) { + int speed; + + sweeping = true; + sweepStart = gameLocal.time; + speed = SEC2MS( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed ); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idSecurityCamera::Event_ContinueSweep +================ +*/ +void idSecurityCamera::Event_ContinueSweep( void ) { + float pct = (stopSweeping - sweepStart) / (sweepEnd - sweepStart); + float f = gameLocal.time - (sweepEnd - sweepStart) * pct; + int speed; + + sweepStart = f; + speed = MS2SEC( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed * (1.0 - pct)); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); + SetAlertMode(SCANNING); + sweeping = true; +} + +/* +================ +idSecurityCamera::Event_Alert +================ +*/ +void idSecurityCamera::Event_Alert( void ) { + float wait; + + SetAlertMode(ACTIVATED); + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_activate", SND_CHANNEL_BODY, 0, false, NULL ); + ActivateTargets(this); + CancelEvents( &EV_SecurityCam_ContinueSweep ); + + wait = spawnArgs.GetFloat( "wait", "20" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, wait ); +} + +/* +================ +idSecurityCamera::Event_ReverseSweep +================ +*/ +void idSecurityCamera::Event_ReverseSweep( void ) { + angle = GetPhysics()->GetAxis().ToAngles().yaw; + negativeSweep = !negativeSweep; + StartSweep(); +} + +/* +================ +idSecurityCamera::Event_Pause +================ +*/ +void idSecurityCamera::Event_Pause( void ) { + float sweepWait; + + sweepWait = spawnArgs.GetFloat( "sweepWait", "0.5" ); + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_stop", SND_CHANNEL_BODY, 0, false, NULL ); + PostEventSec( &EV_SecurityCam_ReverseSweep, sweepWait ); +} + +/* +============ +idSecurityCamera::Killed +============ +*/ +void idSecurityCamera::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); +// RAVEN BEGIN +// bdube: replaced fx call with raven call + gameLocal.PlayEffect ( spawnArgs, "fx_destroyed", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +/* + const char *fx = spawnArgs.GetString( "fx_destroyed" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( trm ), 0.02f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( 0.2f ); + physicsObj.SetFriction( 0.6f, 0.6f, 0.2f ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + physicsObj.DropToFloor(); +} + + +/* +============ +idSecurityCamera::Pain +============ +*/ +bool idSecurityCamera::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +// RAVEN BEGIN +// bdube: replaced fx call with raven call + PlayEffect ( "fx_damage", renderEntity.origin, renderEntity.axis ); +/* + const char *fx = spawnArgs.GetString( "fx_damage" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + return true; +} + + +/* +================ +idSecurityCamera::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idSecurityCamera::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} diff --git a/source/mpgame/SecurityCamera.h b/source/mpgame/SecurityCamera.h new file mode 100644 index 0000000..ed8b19c --- /dev/null +++ b/source/mpgame/SecurityCamera.h @@ -0,0 +1,71 @@ + +#ifndef __GAME_SECURITYCAMERA_H__ +#define __GAME_SECURITYCAMERA_H__ + +/* +=================================================================================== + + Security camera + +=================================================================================== +*/ + + +class idSecurityCamera : public idEntity { +public: + CLASS_PROTOTYPE( idSecurityCamera ); + + ~idSecurityCamera( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual renderView_t * GetRenderView(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Present( void ); + +private: + + enum { SCANNING, LOSINGINTEREST, ALERT, ACTIVATED }; + + float angle; + float sweepAngle; + int modelAxis; + bool flipAxis; + float scanDist; + float scanFov; + + float sweepStart; + float sweepEnd; + bool negativeSweep; + bool sweeping; + int alertMode; + float stopSweeping; + float scanFovCos; + + idVec3 viewOffset; + + int pvsArea; + idPhysics_RigidBody physicsObj; + idTraceModel trm; + + void StartSweep( void ); + bool CanSeePlayer( void ); + void SetAlertMode( int status ); + void DrawFov( void ); + const idVec3 GetAxis( void ) const; + float SweepSpeed( void ) const; + + void Event_ReverseSweep( void ); + void Event_ContinueSweep( void ); + void Event_Pause( void ); + void Event_Alert( void ); + void Event_AddLight( void ); +}; + +#endif /* !__GAME_SECURITYCAMERA_H__ */ diff --git a/source/mpgame/Sound.cpp b/source/mpgame/Sound.cpp new file mode 100644 index 0000000..5c9d226 --- /dev/null +++ b/source/mpgame/Sound.cpp @@ -0,0 +1,371 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + SOUND + +=============================================================================== +*/ + +const idEventDef EV_Speaker_On( "On", NULL ); +const idEventDef EV_Speaker_Off( "Off", NULL ); +const idEventDef EV_Speaker_Timer( "", NULL ); + +CLASS_DECLARATION( idEntity, idSound ) + EVENT( EV_Activate, idSound::Event_Trigger ) + EVENT( EV_Speaker_On, idSound::Event_On ) + EVENT( EV_Speaker_Off, idSound::Event_Off ) + EVENT( EV_Speaker_Timer, idSound::Event_Timer ) +END_CLASS + + +/* +================ +idSound::idSound +================ +*/ +idSound::idSound( void ) { + lastSoundVol = 0.0f; + soundVol = 0.0f; + shakeTranslate.Zero(); + shakeRotate.Zero(); + random = 0.0f; + wait = 0.0f; + timerOn = false; + playingUntilTime = 0; +} + +/* +================ +idSound::Save +================ +*/ +void idSound::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( lastSoundVol ); + savefile->WriteFloat( soundVol ); + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( timerOn ); + savefile->WriteVec3( shakeTranslate ); + savefile->WriteAngles( shakeRotate ); + savefile->WriteInt( playingUntilTime ); +} + +/* +================ +idSound::Restore +================ +*/ +void idSound::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( lastSoundVol ); + savefile->ReadFloat( soundVol ); + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( timerOn ); + savefile->ReadVec3( shakeTranslate ); + savefile->ReadAngles( shakeRotate ); + savefile->ReadInt( playingUntilTime ); +} + +/* +================ +idSound::Spawn +================ +*/ +void idSound::Spawn( void ) { + spawnArgs.GetVector( "move", "0 0 0", shakeTranslate ); + spawnArgs.GetAngles( "rotate", "0 0 0", shakeRotate ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + soundVol = 0.0f; + lastSoundVol = 0.0f; + + if ( ( shakeRotate != ang_zero ) || ( shakeTranslate != vec3_zero ) ) { + BecomeActive( TH_THINK ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else { + timerOn = false; + } + +} + +/* +================ +idSound::Event_Trigger + +this will toggle the idle idSound on and off +================ +*/ +void idSound::Event_Trigger( idEntity *activator ) { + if ( wait > 0.0f ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } else { + timerOn = true; + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + } else { + if ( gameLocal.isMultiplayer ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && ( gameLocal.time < playingUntilTime ) ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } else { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } + } +} + +/* +================ +idSound::Event_Timer +================ +*/ +void idSound::Event_Timer( void ) { + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idSound::Think +================ +*/ +void idSound::Think( void ) { + idAngles ang; + + // run physics + RunPhysics(); + + // clear out our update visuals think flag since we never call Present + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============== +idSound::UpdateChangableSpawnArgs +=============== +*/ +void idSound::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + FreeSoundEmitter( true ); + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + + spawnArgs.Copy( *source ); + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + DoSound( false ); + CancelEvents( &EV_Speaker_Timer ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +// RAVEN BEGIN + } else if ( !refSound.waitfortrigger ) { + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + // start it if it isn't already playing, and we aren't waitForTrigger + DoSound( true ); + timerOn = false; + } + } + } +} + +/* +=============== +idSound::SetSound +=============== +*/ +void idSound::SetSound( const char *sound, int channel ) { + const idSoundShader *shader = declManager->FindSound( sound ); + if ( shader != refSound.shader ) { + FreeSoundEmitter( true ); + } + gameEdit->ParseSpawnArgsToRefSound(&spawnArgs, &refSound); + refSound.shader = shader; +// RAVEN BEGIN + // start it if it isn't already playing, and we aren't waitForTrigger + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !refSound.waitfortrigger && !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + DoSound( true ); + } + +} + +/* +================ +idSound::DoSound +================ +*/ +void idSound::DoSound( bool play ) { + if ( play ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); + playingUntilTime += gameLocal.time; + } else { + StopSound( SND_CHANNEL_ANY, true ); + playingUntilTime = 0; + } +} + +/* +================ +idSound::Event_On +================ +*/ +void idSound::Event_On( void ) { + if ( wait > 0.0f ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + DoSound( true ); +} + +/* +================ +idSound::Event_Off +================ +*/ +void idSound::Event_Off( void ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } + DoSound( false ); +} + +// RAVEN BEGIN +// abahr: so we only set the referenceSounds on our targets once +/* +================ +idSound::FindTargets +================ +*/ +void idSound::FindTargets() { + idEntity::FindTargets(); + + if( !targets.Num() ) {// I don't think we ever get in here unless we have targets + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + // if we have targets lets get an emitter + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + SetTargetSoundHandles(); +} +// RAVEN END + +/* +=============== +idSound::ShowEditingDialog +=============== +*/ +void idSound::ShowEditingDialog( void ) { + common->InitTool( EDITOR_SOUND, &spawnArgs ); +} + +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and have those lights use this speaker's refSound +/* +=============== +idSound::SetSoundHandles +=============== +*/ +void idSound::SetTargetSoundHandles( void ) { +//this code is mostly boosted from a similar function in light.cpp + int i; + idEntity *targetEnt; + + //is this check really necessary? We are a speaker after all... + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( targetEnt && targetEnt->IsType( idLight::GetClassType() ) ) { +// RAVEN END + idLight *light = static_cast(targetEnt); + + //no need to make this speaker a lightparent.... + //light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->SetRefSound(refSound.referenceSoundHandle); + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } + } +} + +// abahr: +/* +================ +idSound::GetPhysicsToSoundTransform +================ +*/ +bool idSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = shakeTranslate.Random( spawnArgs.GetVector("move_random_delta"), gameLocal.random ); + axis = shakeRotate.Random( spawnArgs.GetVector("shake_random_delta"), gameLocal.random ).ToMat3(); + return true; +} +// RAVEN END diff --git a/source/mpgame/Sound.h b/source/mpgame/Sound.h new file mode 100644 index 0000000..20ee484 --- /dev/null +++ b/source/mpgame/Sound.h @@ -0,0 +1,58 @@ + +#ifndef __GAME_SOUND_H__ +#define __GAME_SOUND_H__ + +/* +=============================================================================== + + Generic sound emitter. + +=============================================================================== +*/ + +class idSound : public idEntity { +public: + CLASS_PROTOTYPE( idSound ); + + idSound( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + void Spawn( void ); + + void ToggleOnOff( idEntity *other, idEntity *activator ); + void Think( void ); + void SetSound( const char *sound, int channel = SND_CHANNEL_ANY ); + + virtual void ShowEditingDialog( void ); + +private: + float lastSoundVol; + float soundVol; + float random; + float wait; + bool timerOn; + idVec3 shakeTranslate; + idAngles shakeRotate; + int playingUntilTime; + + void Event_Trigger( idEntity *activator ); + void Event_Timer( void ); + void Event_On( void ); + void Event_Off( void ); + + void DoSound( bool play ); +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and tie them to the speaker's ref sound + void SetTargetSoundHandles( void ); +// abahr + virtual void FindTargets(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +// RAVEN END + +}; + +#endif /* !__GAME_SOUND_H__ */ diff --git a/source/mpgame/SplineMover.cpp b/source/mpgame/SplineMover.cpp new file mode 100644 index 0000000..ca9ca50 --- /dev/null +++ b/source/mpgame/SplineMover.cpp @@ -0,0 +1,3289 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" + +const idEventDef EV_OnAcceleration( "" ); +const idEventDef EV_OnDeceleration( "" ); +const idEventDef EV_OnCruising( "" ); + +const idEventDef EV_OnStartMoving( "" ); +const idEventDef EV_OnStopMoving( "" ); + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Spline ) + EVENT( EV_PostRestore, rvPhysics_Spline::Event_PostRestore ) +END_CLASS + +void splinePState_t::ApplyAccelerationDelta( float timeStepSec ) { + speed = SignZero(idealSpeed) * Min( idMath::Fabs(idealSpeed), idMath::Fabs(speed) + acceleration * timeStepSec ); +} + +void splinePState_t::ApplyDecelerationDelta( float timeStepSec ) { + speed = SignZero(speed) * Max( idMath::Fabs(idealSpeed), idMath::Fabs(speed) - deceleration * timeStepSec ); +} + +void splinePState_t::UpdateDist( float timeStepSec ) { + dist += speed * timeStepSec; +} + +bool splinePState_t::ShouldAccelerate() const { + if( Sign(idealSpeed) == Sign(speed) ) { + return idMath::Fabs(speed) < idMath::Fabs(idealSpeed); + } else if( !Sign(speed) ) { + return true; + } + + return false; +} + +bool splinePState_t::ShouldDecelerate() const { + if( Sign(speed) == Sign(idealSpeed) ) { + return idMath::Fabs(speed) > idMath::Fabs(idealSpeed); + } else if( !Sign(idealSpeed) ) { + return true; + } + + return false; +} + +void splinePState_t::Clear() { + origin.Zero(); + localOrigin.Zero(); + axis.Identity(); + localAxis.Identity(); + speed = 0.0f; + idealSpeed = 0.0f; + dist = 0.0f; + acceleration = 0.0f; + deceleration = 0.0f; +} + +void splinePState_t::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteDeltaVec3( vec3_zero, origin ); + msg.WriteDeltaVec3( vec3_zero, localOrigin ); + msg.WriteDeltaMat3( mat3_identity, axis ); + msg.WriteDeltaMat3( mat3_identity, localAxis ); + msg.WriteDeltaFloat( 0.0f, speed ); + msg.WriteDeltaFloat( 0.0f, idealSpeed ); + msg.WriteDeltaFloat( 0.0f, dist ); + msg.WriteDeltaFloat( 0.0f, acceleration ); + msg.WriteDeltaFloat( 0.0f, deceleration ); +} + +void splinePState_t::ReadFromSnapshot( const idBitMsgDelta &msg ) { + origin = msg.ReadDeltaVec3( vec3_zero ); + localOrigin = msg.ReadDeltaVec3( vec3_zero ); + axis = msg.ReadDeltaMat3( mat3_identity ); + localAxis = msg.ReadDeltaMat3( mat3_identity ); + speed = msg.ReadDeltaFloat( 0.0f ); + idealSpeed = msg.ReadDeltaFloat( 0.0f ); + dist = msg.ReadDeltaFloat( 0.0f ); + acceleration = msg.ReadDeltaFloat( 0.0f ); + deceleration = msg.ReadDeltaFloat( 0.0f ); +} + +void splinePState_t::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( origin ); + savefile->WriteVec3( localOrigin ); + savefile->WriteMat3( axis ); + savefile->WriteMat3( localAxis ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( dist ); + savefile->WriteFloat( acceleration ); + savefile->WriteFloat( deceleration ); +} + +void splinePState_t::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( origin ); + savefile->ReadVec3( localOrigin ); + savefile->ReadMat3( axis ); + savefile->ReadMat3( localAxis ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( dist ); + savefile->ReadFloat( acceleration ); + savefile->ReadFloat( deceleration ); +} + +splinePState_t& splinePState_t::Assign( const splinePState_t* state ) { + SIMDProcessor->Memcpy( this, state, sizeof(splinePState_t) ); + return *this; +} + +splinePState_t& splinePState_t::operator=( const splinePState_t& state ) { + return Assign( &state ); +} + +splinePState_t& splinePState_t::operator=( const splinePState_t* state ) { + return Assign( state ); +} + +/* +================ +rvPhysics_Spline::rvPhysics_Spline +================ +*/ +rvPhysics_Spline::rvPhysics_Spline( void ) { + accelDecelStateThread.SetName( "AccelDecel" ); + accelDecelStateThread.SetOwner( this ); + accelDecelStateThread.SetState( "Cruising" ); + + clipModel = NULL; + + spline = NULL; + SetSplineEntity( NULL ); + + memset( &pushResults, 0, sizeof(trace_t) ); + pushResults.fraction = 1.0f; + + current.Clear(); + SaveState(); +} + +/* +================ +rvPhysics_Spline::~rvPhysics_Spline +================ +*/ +rvPhysics_Spline::~rvPhysics_Spline( void ) { + SAFE_DELETE_PTR( clipModel ); + + SAFE_DELETE_PTR( spline ); +} + +/* +================ +rvPhysics_Spline::Save +================ +*/ +void rvPhysics_Spline::Save( idSaveGame *savefile ) const { + + current.Save( savefile ); + saved.Save( savefile ); + + savefile->WriteFloat( splineLength ); + // This spline was retored as NULL, so there's no reason to save it. + //savefile->WriteInt(spline != NULL ? spline->GetTime( 0 ) : -1 ); // cnicholson: Added unsaved var + splineEntity.Save( savefile ); + + savefile->WriteTrace( pushResults ); + + savefile->WriteClipModel( clipModel ); + + accelDecelStateThread.Save( savefile ); +} + +/* +================ +rvPhysics_Spline::Restore +================ +*/ +void rvPhysics_Spline::Event_PostRestore( void ) { + + if( splineEntity.IsValid() ) { + spline = splineEntity->GetSpline(); + } +} + +void rvPhysics_Spline::Restore( idRestoreGame *savefile ) { + + current.Restore( savefile ); + saved.Restore( savefile ); + + savefile->ReadFloat( splineLength ); + SAFE_DELETE_PTR( spline ); + splineEntity.Restore( savefile ); + + savefile->ReadTrace( pushResults ); + + savefile->ReadClipModel( clipModel ); + + accelDecelStateThread.Restore( savefile, this ); +} + +/* +================ +rvPhysics_Spline::SetClipModel +================ +*/ +void rvPhysics_Spline::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; + + LinkClip(); +} + +/* +================ +rvPhysics_Spline::GetClipModel +================ +*/ +idClipModel *rvPhysics_Spline::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Spline::SetContents +================ +*/ +void rvPhysics_Spline::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Spline::GetContents +================ +*/ +int rvPhysics_Spline::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Spline::GetBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Spline::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Spline::SetSpline +================ +*/ +void rvPhysics_Spline::SetSpline( idCurve_Spline* spline ) { + SAFE_DELETE_PTR( this->spline ); + + //Keep any left over dist from last spline to minimize hitches + if( GetSpeed() >= 0.0f ) { + current.dist = Max( 0.0f, current.dist - splineLength ); + } + + if( !spline ) { + splineLength = 0.0f; + return; + } + + this->spline = spline; + + splineLength = spline->GetLengthForTime( spline->GetTime(spline->GetNumValues() - 1) ); + if( GetSpeed() < 0.0f ) { + current.dist = splineLength - current.dist; + } + + Activate(); +} + +/* +================ +rvPhysics_Spline::SetSplineEntity +================ +*/ +void rvPhysics_Spline::SetSplineEntity( idSplinePath* spline ) { + splineEntity = spline; + SetSpline( (spline) ? spline->GetSpline() : NULL ); +} + +/* +================ +rvPhysics_Spline::ComputeDecelFromSpline +================ +*/ +float rvPhysics_Spline::ComputeDecelFromSpline() const { + // FIXME: merge this in better. It seems very special case + float numerator = GetSpeed() * GetSpeed(); + float denomonator = 2.0f * ((GetSpeed() >= 0.0f) ? (splineLength - current.dist) : current.dist); + + assert( denomonator > VECTOR_EPSILON ); + + return numerator / denomonator; +} + +/* +================ +rvPhysics_Spline::SetLinearAcceleration +================ +*/ +void rvPhysics_Spline::SetLinearAcceleration( const float accel ) { + current.acceleration = accel; +} + +/* +================ +rvPhysics_Spline::SetLinearDeceleration +================ +*/ +void rvPhysics_Spline::SetLinearDeceleration( const float decel ) { + current.deceleration = decel; +} + +/* +================ +rvPhysics_Spline::SetSpeed +================ +*/ +void rvPhysics_Spline::SetSpeed( float speed ) { + if( IsAtRest() || StoppedMoving() ) { + current.dist = (speed < 0.0f) ? splineLength - current.dist : current.dist; + } + + current.idealSpeed = speed; + Activate(); +} + +/* +================ +rvPhysics_Spline::GetSpeed +================ +*/ +float rvPhysics_Spline::GetSpeed() const { + return current.speed; +} + +/* +================ +rvPhysics_Spline::Evaluate +================ +*/ +bool rvPhysics_Spline::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + splinePState_t previous = current; + + if( HasValidSpline() ) { + if( StoppedMoving() ) { + Rest(); + return false; + } + + accelDecelStateThread.Execute(); + + // FIXME: clean this up + if( IsAtBeginningOfSpline() || IsAtEndOfSpline() ) { + current = previous; + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + + if( gameLocal.program.GetReturnedBool() ) { + current.speed = 0.0f; + return false; + } else { + return true; + } + } + + float currentTime = splineEntity->GetSampledTime ( current.dist ); + if ( currentTime == -1.0f ) { + currentTime = spline->GetTimeForLength( Min(current.dist, splineLength), 0.01f ); + } + + current.axis = spline->GetCurrentFirstDerivative(currentTime).ToAngles().Normalize360().ToMat3(); + current.origin = spline->GetCurrentValue( currentTime ); + current.localOrigin = current.origin; + current.localAxis = current.axis; + } else if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAxis * masterAxis; + current.origin = masterOrigin + current.localOrigin * masterAxis; + } else { + Rest(); + return false; + } + + gameLocal.push.ClipPush( pushResults, self, 0, previous.origin, previous.axis, current.origin, current.axis ); + if( pushResults.fraction < 1.0f ) { + current = previous; + LinkClip(); + current.speed = 0.0f; + return false; + } + + LinkClip(); + + if( StoppedMoving() && !self->IsBound() ) { + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + return !gameLocal.program.GetReturnedBool(); + } + + return true; +} + +/* +================ +rvPhysics_Spline::Activate +================ +*/ +void rvPhysics_Spline::Activate( void ) { + assert( self ); + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::Rest +================ +*/ +void rvPhysics_Spline::Rest( void ) { + assert( self ); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtRest +================ +*/ +bool rvPhysics_Spline::IsAtRest( void ) const { + assert( self ); + return !self->IsActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtEndOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtEndOfSpline( void ) const { + return current.dist >= splineLength; +} + +/* +================ +rvPhysics_Spline::IsAtBeginningOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtBeginningOfSpline( void ) const { + return current.dist <= 0.0f; +} + +/* +================ +rvPhysics_Spline::IsPushable +================ +*/ +bool rvPhysics_Spline::IsPushable( void ) const { + return !HasValidSpline() && idPhysics_Base::IsPushable(); +} + +/* +================ +rvPhysics_Spline::StartingToMove +================ +*/ +bool rvPhysics_Spline::StartingToMove( void ) const { + float firstDeltaSpeed = current.acceleration * MS2SEC(gameLocal.GetMSec()); + return idMath::Fabs(current.idealSpeed) > VECTOR_EPSILON && idMath::Fabs(current.speed) <= firstDeltaSpeed; +} + +/* +================ +rvPhysics_Spline::StoppedMoving +================ +*/ +bool rvPhysics_Spline::StoppedMoving( void ) const { + return idMath::Fabs(current.idealSpeed) < VECTOR_EPSILON && idMath::Fabs(current.speed) < VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::HasValidSpline +================ +*/ +bool rvPhysics_Spline::HasValidSpline() const { + return spline && splineLength > VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::SaveState +================ +*/ +void rvPhysics_Spline::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Spline::RestoreState +================ +*/ +void rvPhysics_Spline::RestoreState( void ) { + current = saved; + + LinkClip(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Spline::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + + LinkClip(); + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Spline::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } + else { + current.axis = newAxis; + } + + LinkClip(); + Activate(); +} + +/* +================ +rvPhysics_Spline::Translate +================ +*/ +void rvPhysics_Spline::Translate( const idVec3 &translation, int id ) { + SetOrigin( GetLocalOrigin() + translation ); +} + +/* +================ +rvPhysics_Spline::Rotate +================ +*/ +void rvPhysics_Spline::Rotate( const idRotation &rotation, int id ) { + SetAxis( GetLocalAxis() * rotation.ToMat3() ); + SetOrigin( GetLocalOrigin() * rotation ); +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetOrigin( int id ) { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetAxis( int id ) { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) const { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) const { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::SetMaster +================ +*/ +void rvPhysics_Spline::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if( master ) { + if( self->IsBound() ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( GetOrigin() - masterOrigin ) * masterAxis.Transpose(); + current.localAxis = GetAxis() * masterAxis.Transpose(); + } + } +} + +/* +================ +rvPhysics_Spline::ClipTranslation +================ +*/ +void rvPhysics_Spline::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipRotation +================ +*/ +void rvPhysics_Spline::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipContents +================ +*/ +int rvPhysics_Spline::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.ContentsModel( self, GetOrigin(), clipModel, GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, GetOrigin(), clipModel, GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::DisableClip +================ +*/ +void rvPhysics_Spline::DisableClip( void ) { + if( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +rvPhysics_Spline::EnableClip +================ +*/ +void rvPhysics_Spline::EnableClip( void ) { + if( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +rvPhysics_Spline::UnlinkClip +================ +*/ +void rvPhysics_Spline::UnlinkClip( void ) { + if( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +rvPhysics_Spline::LinkClip +================ +*/ +void rvPhysics_Spline::LinkClip( void ) { + if( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), GetOrigin(), GetAxis() ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::GetBlockingInfo +================ +*/ +const trace_t* rvPhysics_Spline::GetBlockingInfo( void ) const { + return (pushResults.fraction < 1.0f) ? &pushResults : NULL; +} + +/* +================ +rvPhysics_Spline::GetBlockingEntity +================ +*/ +idEntity* rvPhysics_Spline::GetBlockingEntity( void ) const { + return (pushResults.fraction < 1.0f) ? gameLocal.entities[ pushResults.c.entityNum ] : NULL; +} + +/* +================ +rvPhysics_Spline::WriteToSnapshot +================ +*/ +void rvPhysics_Spline::WriteToSnapshot( idBitMsgDelta &msg ) const { + current.WriteToSnapshot( msg ); +} + +/* +================ +rvPhysics_Spline::ReadFromSnapshot +================ +*/ +void rvPhysics_Spline::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.ReadFromSnapshot( msg ); + + LinkClip(); +} + +CLASS_STATES_DECLARATION( rvPhysics_Spline ) + STATE( "Accelerating", rvPhysics_Spline::State_Accelerating ) + STATE( "Decelerating", rvPhysics_Spline::State_Decelerating ) + STATE( "Cruising", rvPhysics_Spline::State_Cruising ) +END_CLASS_STATES + +/* +================ +rvPhysics_Spline::State_Accelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Accelerating( const stateParms_t& parms ) { + stateResult_t returnResult = SRESULT_WAIT; + + if( !current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( current.ShouldDecelerate() ? "Decelerating" : "Cruising" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + if( StartingToMove() ) { + self->ProcessEvent( &EV_OnStartMoving ); + } + self->ProcessEvent( &EV_OnAcceleration ); + + returnResult = SRESULT_STAGE( parms.stage + 1 ); + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyAccelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + return returnResult; +} + +/* +================ +rvPhysics_Spline::State_Decelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Decelerating( const stateParms_t& parms ) { + if( !current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( current.ShouldAccelerate() ? "Accelerating" : "Cruising" ); + return SRESULT_DONE; + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyDecelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnDeceleration ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( StoppedMoving() ) { + self->ProcessEvent( &EV_OnStopMoving ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvPhysics_Spline::State_Cruising +================ +*/ +stateResult_t rvPhysics_Spline::State_Cruising( const stateParms_t& parms ) { + if( current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( "Accelerating" ); + return SRESULT_DONE; + } else if( current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( "Decelerating" ); + return SRESULT_DONE; + } + + current.UpdateDist( MS2SEC(gameLocal.GetMSec()) ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnCruising ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + return SRESULT_WAIT; +} + + + +const idEventDef EV_SetSpline( "setSpline", "E" ); + +const idEventDef EV_SetAccel( "setAccel", "f" ); +const idEventDef EV_SetDecel( "setDecel", "f" ); + +const idEventDef EV_SetSpeed( "setSpeed", "f" ); +const idEventDef EV_GetSpeed( "getSpeed", "", 'f' ); + +const idEventDef EV_TramCar_SetIdealSpeed( "setIdealSpeed", "f" ); +const idEventDef EV_TramCar_GetIdealSpeed( "getIdealSpeed", "", 'f' ); + +const idEventDef EV_TramCar_ApplySpeedScale( "applySpeedScale", "f" ); + +const idEventDef EV_GetCurrentTrackInfo( "getCurrentTrackInfo", "", 's' ); +const idEventDef EV_GetTrackInfo( "getTrackInfo", "e", 's' ); + +const idEventDef EV_DoneMoving( "", "", 'd' ); +const idEventDef EV_StartSoundPeriodic( "", "sddd" ); + +idLinkList rvSplineMover::splineMovers; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvSplineMover ) + EVENT( EV_PostSpawn, rvSplineMover::Event_PostSpawn ) + EVENT( EV_Activate, rvSplineMover::Event_Activate ) + EVENT( EV_SetSpline, rvSplineMover::Event_SetSpline ) + EVENT( EV_SetAccel, rvSplineMover::Event_SetAcceleration ) + EVENT( EV_SetDecel, rvSplineMover::Event_SetDeceleration ) + EVENT( EV_SetSpeed, rvSplineMover::Event_SetSpeed ) + EVENT( EV_GetSpeed, rvSplineMover::Event_GetSpeed ) + EVENT( EV_Thread_SetCallback, rvSplineMover::Event_SetCallBack ) + EVENT( EV_DoneMoving, rvSplineMover::Event_DoneMoving ) + EVENT( EV_GetSplineEntity, rvSplineMover::Event_GetSpline ) + EVENT( EV_GetCurrentTrackInfo, rvSplineMover::Event_GetCurrentTrackInfo ) + EVENT( EV_GetTrackInfo, rvSplineMover::Event_GetTrackInfo ) + EVENT( EV_TramCar_SetIdealSpeed, rvSplineMover::Event_SetIdealSpeed ) + EVENT( EV_TramCar_GetIdealSpeed, rvSplineMover::Event_GetIdealSpeed ) + EVENT( EV_TramCar_ApplySpeedScale, rvSplineMover::Event_ApplySpeedScale ) + EVENT( EV_OnAcceleration, rvSplineMover::Event_OnAcceleration ) + EVENT( EV_OnDeceleration, rvSplineMover::Event_OnDeceleration ) + EVENT( EV_OnCruising, rvSplineMover::Event_OnCruising ) + EVENT( EV_OnStartMoving, rvSplineMover::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvSplineMover::Event_OnStopMoving ) + EVENT( EV_StartSoundPeriodic, rvSplineMover::Event_StartSoundPeriodic ) + EVENT( EV_PartBlocked, rvSplineMover::Event_PartBlocked ) +END_CLASS + +/* +================ +rvSplineMover::Spawn +================ +*/ +void rvSplineMover::Spawn() { + waitThreadId = -1; + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( spawnArgs.GetBool("solid", "1") ? CONTENTS_SOLID : 0 ); + physicsObj.SetClipMask( spawnArgs.GetBool("solidClip") ? CONTENTS_SOLID : 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat("accel", "50") ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat("decel", "50") ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + AddSelfToGlobalList(); + + // This is needed so we get sorted correctly + BecomeInactive( TH_PHYSICS ); + BecomeActive( TH_PHYSICS ); + + PlayAnim( ANIMCHANNEL_ALL, "idle", 0 ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvSplineMover::~rvSplineMover +================ +*/ +rvSplineMover::~rvSplineMover() { + RemoveSelfFromGlobalList(); + SetPhysics( NULL ); +} + +/* +================ +rvSplineMover::SetSpeed +================ +*/ +void rvSplineMover::SetSpeed( float newSpeed ) { + physicsObj.SetSpeed( newSpeed ); +} + +/* +================ +rvSplineMover::GetSpeed +================ +*/ +float rvSplineMover::GetSpeed() const { + return physicsObj.GetSpeed(); +} + +/* +================ +rvSplineMover::SetIdealSpeed +================ +*/ +void rvSplineMover::SetIdealSpeed( float newIdealSpeed ) { + idealSpeed = newIdealSpeed; + SetSpeed( newIdealSpeed ); +} + +/* +================ +rvSplineMover::GetIdealSpeed +================ +*/ +float rvSplineMover::GetIdealSpeed() const { + return idealSpeed; +} + +/* +================ +rvSplineMover::SetSpline +================ +*/ +void rvSplineMover::SetSpline( idSplinePath* spline ) { + physicsObj.SetSplineEntity( spline ); + CheckSplineForOverrides( physicsObj.GetSpline(), &spline->spawnArgs ); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +const idSplinePath* rvSplineMover::GetSpline() const { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +idSplinePath* rvSplineMover::GetSpline() { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::SetAcceleration +================ +*/ +void rvSplineMover::SetAcceleration( float accel ) { + physicsObj.SetLinearAcceleration( accel ); +} + +/* +================ +rvSplineMover::SetDeceleration +================ +*/ +void rvSplineMover::SetDeceleration( float decel ) { + physicsObj.SetLinearDeceleration( decel ); +} + +/* +================ +rvSplineMover::CheckSplineForOverrides +================ +*/ +void rvSplineMover::CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ) { + if( !spline || !args ) { + return; + } + + int endSpline = args->GetInt( "end_spline" ); + if( endSpline && Sign(endSpline) == Sign(GetSpeed()) ) { + physicsObj.SetLinearDeceleration( physicsObj.ComputeDecelFromSpline() ); + SetIdealSpeed( 0.0f ); + } +} + +/* +================ +rvSplineMover::RestoreFromOverrides +================ +*/ +void rvSplineMover::RestoreFromOverrides( const idDict* args ) { + if( !args ) { + return; + } + + physicsObj.SetLinearDeceleration( args->GetFloat("decel") ); +} + +/* +================ +rvSplineMover::PlayAnim +================ +*/ +int rvSplineMover::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim( channel )->Length(); +} + +/* +================ +rvSplineMover::CycleAnim +================ +*/ +void rvSplineMover::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return; + } + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvSplineMover::ClearChannel +================ +*/ +void rvSplineMover::ClearChannel( int channel, int clearFrame ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(clearFrame) ); +} + +/* +================ +rvSplineMover::PreBind +================ +*/ +void rvSplineMover::PreBind() { + idAnimatedEntity::PreBind(); + + SetSpline( NULL ); +} + +/* +================ +rvSplineMover::Save +================ +*/ +void rvSplineMover::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteInt( waitThreadId ); +} + +/* +================ +rvSplineMover::Restore +================ +*/ +void rvSplineMover::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadFloat( idealSpeed ); + savefile->ReadInt( waitThreadId ); + + AddSelfToGlobalList(); +} + +/* +================ +rvSplineMover::WriteToSnapshot +================ +*/ +void rvSplineMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvSplineMover::ReadFromSnapshot +================ +*/ +void rvSplineMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvSplineMover::AddSelfToGlobalList +================ +*/ +void rvSplineMover::AddSelfToGlobalList() { + splineMoverNode.SetOwner( this ); + + if( !InGlobalList() ) { + splineMoverNode.AddToEnd( splineMovers ); + } +} + +/* +================ +rvSplineMover::RemoveSelfFromGlobalList +================ +*/ +void rvSplineMover::RemoveSelfFromGlobalList() { + splineMoverNode.Remove(); +} + +/* +================ +rvSplineMover::InGlobalList +================ +*/ +bool rvSplineMover::InGlobalList() const { + return splineMoverNode.InList(); +} + +/* +================ +rvSplineMover::WhosVisible +================ +*/ +bool rvSplineMover::WhosVisible( const idFrustum& frustum, idList& list ) const { + list.Clear(); + + if( !frustum.IsValid() ) { + return false; + } + + for( rvSplineMover* node = splineMovers.Next(); node; node = node->splineMoverNode.Next() ) { + if( node == this ) { + continue; + } + + if( frustum.IntersectsBounds(node->GetPhysics()->GetAbsBounds()) ) { + list.AddUnique( node ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvSplineMover::GetTrackInfo +================ +*/ +idStr rvSplineMover::GetTrackInfo( const idSplinePath* track ) const { + if( !track ) { + return idStr( "" ); + } + + idStr info( track->GetName() ); + return info.Mid( info.Last('_') - 1, 1 ); +} + +/* +================ +rvSplineMover::ConvertToMover +================ +*/ +rvSplineMover* rvSplineMover::ConvertToMover( idEntity* mover ) const { + return mover && mover->IsType(rvSplineMover::Type) ? static_cast(mover) : NULL; +} + +/* +================ +rvSplineMover::ConvertToSplinePath +================ +*/ +idSplinePath* rvSplineMover::ConvertToSplinePath( idEntity* spline ) const { + return (spline && spline->IsType(idSplinePath::GetClassType())) ? static_cast(spline) : NULL; +} + +/* +================ +rvSplineMover::PreDoneMoving +================ +*/ +void rvSplineMover::PreDoneMoving() { + if( waitThreadId >= 0 ) { + idThread::ObjectMoveDone( waitThreadId, this ); + waitThreadId = -1; + } + + RestoreFromOverrides( &spawnArgs ); +} + +/* +================ +rvSplineMover::PostDoneMoving +================ +*/ +void rvSplineMover::PostDoneMoving() { + CallScriptEvents( physicsObj.GetSplineEntity(), "call_doneMoving", this ); +} + +/* +============== +rvSplineMover::CallScriptEvents +============== +*/ +// FIXME: very similier code is in the spawner...if possible try and make one function for both to call +void rvSplineMover::CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ) { + if( !spline || !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spline->spawnArgs.MatchPrefix(prefixKey); kv; kv = spline->spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( spline, 0 ); + func.InsertEntity( parm, 1 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +================ +rvSplineMover::Event_PostSpawn +================ +*/ +void rvSplineMover::Event_PostSpawn() { + idEntityPtr target; + for( int ix = targets.Num() - 1; ix >= 0; --ix ) { + target = targets[ix]; + + if( target.IsValid() && target->IsType(idSplinePath::GetClassType()) ) { + SetSpline( static_cast(target.GetEntity()) ); + break; + } + } + + SetIdealSpeed( spawnArgs.GetBool("waitForTrigger") ? 0.0f : spawnArgs.GetFloat("speed", "50") ); +} + +/* +=============== +rvSplineMover::Event_PartBlocked +=============== +*/ +void rvSplineMover::Event_PartBlocked( idEntity *blockingEntity ) { + assert( blockingEntity ); + + float damage = spawnArgs.GetFloat( "damage" ); + if( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.GetTime(), GetName(), blockingEntity->GetName() ); + } +} + +/* +================ +rvSplineMover::Event_SetSpline +================ +*/ +void rvSplineMover::Event_SetSpline( idEntity* spline ) { + SetSpline( ConvertToSplinePath(spline) ); +} + +/* +================ +rvSplineMover::Event_GetSpline +================ +*/ +void rvSplineMover::Event_GetSpline() { + idThread::ReturnEntity( GetSpline() ); +} + +/* +================ +rvSplineMover::Event_SetAcceleration +================ +*/ +void rvSplineMover::Event_SetAcceleration( float accel ) { + SetAcceleration( accel ); +} + +/* +================ +rvSplineMover::Event_SetDeceleration +================ +*/ +void rvSplineMover::Event_SetDeceleration( float decel ) { + SetDeceleration( decel ); +} + +/* +================ +rvSplineMover::Event_SetSpeed +================ +*/ +void rvSplineMover::Event_SetSpeed( float speed ) { + SetIdealSpeed( speed ); + SetSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetSpeed +================ +*/ +void rvSplineMover::Event_GetSpeed() { + idThread::ReturnFloat( GetSpeed() ); +} + +/* +================ +rvSplineMover::Event_SetIdealSpeed +================ +*/ +void rvSplineMover::Event_SetIdealSpeed( float speed ) { + SetIdealSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetIdealSpeed +================ +*/ +void rvSplineMover::Event_GetIdealSpeed() { + idThread::ReturnFloat( GetIdealSpeed() ); +} + +/* +================ +rvSplineMover::Event_ApplySpeedScale +================ +*/ +void rvSplineMover::Event_ApplySpeedScale( float scale ) { + SetIdealSpeed( spawnArgs.GetFloat("speed", "50") * scale ); +} + +/* +================ +rvSplineMover::Event_SetCallBack +================ +*/ +void rvSplineMover::Event_SetCallBack() { + if( waitThreadId >= 0 ) { + idThread::ReturnInt( false ); + } + + waitThreadId = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); +} + +/* +================ +rvSplineMover::Event_DoneMoving +================ +*/ +void rvSplineMover::Event_DoneMoving() { + PreDoneMoving(); + PostDoneMoving(); + + idThread::ReturnInt( !physicsObj.HasValidSpline() ); +} + +/* +================ +rvSplineMover::Event_GetCurrentTrackInfo +================ +*/ +void rvSplineMover::Event_GetCurrentTrackInfo() { + Event_GetTrackInfo( physicsObj.GetSplineEntity() ); +} + +/* +================ +rvSplineMover::Event_GetTrackInfo +================ +*/ +void rvSplineMover::Event_GetTrackInfo( idEntity* track ) { + idThread::ReturnString( GetTrackInfo(ConvertToSplinePath(track)) ); +} + +/* +================ +rvSplineMover::Event_Activate +================ +*/ +void rvSplineMover::Event_Activate( idEntity* activator ) { + // This is for my special case in tram1b + + //if( physicsObj.StoppedMoving() ) { + // SetIdealSpeed( spawnArgs.GetFloat("speed", "50") ); + //} +} + +/* +================ +rvSplineMover::Event_OnAcceleration +================ +*/ +void rvSplineMover::Event_OnAcceleration() { + StartSound( "snd_accel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnDeceleration +================ +*/ +void rvSplineMover::Event_OnDeceleration() { + StartSound( "snd_decel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnCruising +================ +*/ +void rvSplineMover::Event_OnCruising() { + idVec2 range( spawnArgs.GetVec2("noisePeriodRange") * idMath::M_SEC2MS ); + if( !EventIsPosted(&EV_StartSoundPeriodic) && range.Length() > VECTOR_EPSILON ) { + ProcessEvent( &EV_StartSoundPeriodic, "snd_noise", (int)SND_CHANNEL_ANY, (int)range[0], (int)range[1] ); + } +} + +/* +================ +rvSplineMover::Event_OnStopMoving +================ +*/ +void rvSplineMover::Event_OnStopMoving() { + StopSound( SND_CHANNEL_ANY, false ); + CancelEvents( &EV_StartSoundPeriodic ); +} + +/* +================ +rvSplineMover::Event_OnStartMoving +================ +*/ +void rvSplineMover::Event_OnStartMoving() { +} + +/* +================ +rvSplineMover::Event_StartSoundPeriodic +================ +*/ +void rvSplineMover::Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ) { + CancelEvents( &EV_StartSoundPeriodic ); + + if( physicsObj.StoppedMoving() ) { + return; + } + + int length; + StartSound( sndKey, channel, 0, false, &length ); + + PostEventMS( &EV_StartSoundPeriodic, Max(rvRandom::irand(minDelay, maxDelay), length), sndKey, (int)channel, minDelay, maxDelay ); +} + + +const idEventDef EV_TramCar_RadiusDamage( "", "vs" ); +const idEventDef EV_TramCar_SetIdealTrack( "setIdealTrack", "s" ); +const idEventDef EV_TramCar_DriverSpeak( "driverSpeak", "s", 'e' ); +const idEventDef EV_TramCar_GetDriver( "getDriver", "", 'E' ); + +const idEventDef EV_TramCar_OpenDoors( "openDoors" ); +const idEventDef EV_TramCar_CloseDoors( "closeDoors" ); + +//======================================================= +// +// rvTramCar +// +//======================================================= +CLASS_DECLARATION( rvSplineMover, rvTramCar ) + EVENT( EV_TramCar_DriverSpeak, rvTramCar::Event_DriverSpeak ) + EVENT( EV_TramCar_GetDriver, rvTramCar::Event_GetDriver ) + EVENT( EV_Activate, rvTramCar::Event_Activate ) + EVENT( EV_TramCar_RadiusDamage, rvTramCar::Event_RadiusDamage ) + EVENT( EV_TramCar_SetIdealTrack, rvTramCar::Event_SetIdealTrack ) + EVENT( EV_OnStartMoving, rvTramCar::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvTramCar::Event_OnStopMoving ) + EVENT( EV_TramCar_OpenDoors, rvTramCar::Event_OpenDoors ) + EVENT( EV_TramCar_CloseDoors, rvTramCar::Event_CloseDoors ) + EVENT( AI_SetHealth, rvTramCar::Event_SetHealth ) +END_CLASS + +/* +================ +rvTramCar::Spawn +================ +*/ +void rvTramCar::Spawn() { + RegisterStateThread( idealTrackStateThread, "IdealTrack" ); + RegisterStateThread( speedSoundEffectsStateThread, "SpeedSoundEffects" ); + + numTracksOnMap = gameLocal.world->spawnArgs.GetInt( "numTramCarTracks", "1" ); + + idStr track; + if ( numTracksOnMap == 1 ) { + track += ConvertToTrackLetter( numTracksOnMap - 1 ); + } else { + track = spawnArgs.RandomPrefix( "idealTrack", gameLocal.random, "0" ); + } + Event_SetIdealTrack( track.c_str() ); + idealTrackStateThread.SetState( GetIdealTrack() < 0 ? "RandomTrack" : "AssignedTrack" ); + + SpawnDriver( "def_driver" ); + SpawnWeapons( "def_weapon" ); + SpawnOccupants( "def_occupant" ); + SpawnDoors(); + + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; + const char* frustumKey = spawnArgs.GetString( "collisionFov" ); + if( frustumKey[0] ) { + sscanf( frustumKey, "%f %f %f %f", &dNear, &dFar, &dLeft, &dUp ); + collisionFov.SetSize( dNear, dFar, dLeft, dUp ); + } + + fl.takedamage = health > 0; + + BecomeActive( TH_THINK ); + + SetSpline( NULL ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +rvTramCar::~rvTramCar() { + SAFE_REMOVE( driver ); + occupants.RemoveContents( true ); + weapons.RemoveContents( true ); + + SAFE_REMOVE( leftDoor ); + SAFE_REMOVE( rightDoor ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +void rvTramCar::SpawnDriver( const char* driverKey ) { + driver = (idAI*)SpawnPart( spawnArgs.GetString(driverKey), "def_occupant" ); +} + +/* +================ +rvTramCar::SpawnWeapons +================ +*/ +void rvTramCar::SpawnWeapons( const char* partKey ) { + idEntityPtr weapon; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + weapon = (rvVehicle*)SpawnPart( kv->GetValue().c_str(), partKey ); + weapon->IdleAnim( ANIMCHANNEL_LEGS, "idle", 0 ); + //if( weapon->GetAnimator()->GetAnim("toFire") && weapon->GetAnimator()->GetAnim("toIdle") ) { + weapons.AddUnique( weapon ); + //} + } +} + +/* +================ +rvTramCar::SpawnOccupants +================ +*/ +void rvTramCar::SpawnOccupants( const char* partKey ) { + idEntityPtr occupant; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + occupant = (idAI*)SpawnPart( kv->GetValue().c_str(), partKey ); + occupants.AddUnique( occupant ); + } +} + +/* +================ +rvTramCar::SpawnPart +================ +*/ +idEntity* rvTramCar::SpawnPart( const char* partDefName, const char* subPartDefName ) { + idEntity* part = NULL; + idDict entityDef; + const idDict* info = gameLocal.FindEntityDefDict( partDefName, false ); + if( !info ) { + return NULL; + } + + const idDict* def = gameLocal.FindEntityDefDict( info->GetString(subPartDefName), false ); + if( !def ) { + return NULL; + } + + entityDef.Copy( *def ); + + entityDef.SetBool( "trigger", spawnArgs.GetBool("waitForTrigger") ); + entityDef.SetVector( "origin", GetPhysics()->GetOrigin() + info->GetVector("spawn_offset") * GetPhysics()->GetAxis() ); + entityDef.SetFloat( "angle", info->GetInt("spawn_facing_offset") + spawnArgs.GetFloat("angle") ); + entityDef.Set( "bind", GetName() ); + gameLocal.SpawnEntityDef( entityDef, &part ); + + return part; +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +void rvTramCar::SpawnDoors() { + leftDoor = SpawnDoor( "clipModel_doorLeft" ); + rightDoor = SpawnDoor( "clipModel_doorRight" ); +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +idMover* rvTramCar::SpawnDoor( const char* key ) { + idDict args; + const idDict* dict = NULL; + idMover* outer = NULL; + idMover* inner = NULL; + + dict = gameLocal.FindEntityDefDict( spawnArgs.GetString(key), false ); + if( !dict ) { + return NULL; + } + + args.Set( "open_rotation", dict->GetString("open_rotation") ); + args.Set( "close_rotation", dict->GetString("close_rotation") ); + + args.Set( "open_dir", dict->GetString("open_dir") ); + args.Set( "close_dir", dict->GetString("close_dir") ); + + args.Set( "distExt", dict->GetString("distExt") ); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + dict->GetVector("offset") * GetPhysics()->GetAxis() ); + args.SetMatrix( "rotation", GetPhysics()->GetAxis() ); + args.Set( "clipModel", dict->GetString("clipModel") ); + args.Set( "bind", GetName() ); + outer = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + assert( outer ); + + args.Set( "clipModel", dict->GetString("clipModelExt") ); + args.Set( "bind", outer->GetName() ); + inner = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + + return inner; +} + +/* +================ +rvTramCar::Think +================ +*/ +void rvTramCar::Think() { + RunPhysics(); + + MidThink(); + + Present(); +} + +/* +================ +rvTramCar::MidThink +================ +*/ +void rvTramCar::MidThink() { + if( !physicsObj.StoppedMoving() ) { + LookAround(); + TouchTriggers(); + speedSoundEffectsStateThread.Execute(); + } +} + +/* +================ +rvTramCar::RegisterStateThread +================ +*/ +void rvTramCar::RegisterStateThread( rvStateThread& stateThread, const char* name ) { + stateThread.SetName( name ); + stateThread.SetOwner( this ); +} + +/* +================ +rvTramCar::SetIdealTrack +================ +*/ +void rvTramCar::SetIdealTrack( int track ) { + idealTrack = track; +} + +/* +================ +rvTramCar::GetIdealTrack +================ +*/ +int rvTramCar::GetIdealTrack() const { + return idealTrack; +} + +/* +================ +rvTramCar::AddDamageEffect +================ +*/ +void rvTramCar::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + rvSplineMover::AddDamageEffect( collision, velocity, damageDefName, inflictor ); + + if( GetDamageScale() >= 0.5f && rvRandom::flrand() <= spawnArgs.GetFloat("damageEffectFreq", "0.25") ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 v = (center - collision.endpos).ToNormal(); + float dot = v * collision.c.normal; + PlayEffect( (dot > 0.0f) ? "fx_damage_internal" : "fx_damage_external", collision.endpos, collision.c.normal.ToMat3(2) ); + } +} + +/* +================ +rvTramCar::Damage +================ +*/ +void rvTramCar::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( attacker && GetPhysics()->GetAbsBounds().Contains(attacker->GetPhysics()->GetAbsBounds()) ) { + return; + } + + if( attacker && g_debugVehicle.GetInteger() == 3 ) { + gameLocal.Printf( "Was damaged by %s. Current damage scale: %f\n", attacker->GetName(), GetDamageScale() ); + } + + rvSplineMover::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvTramCar::Killed +================ +*/ +void rvTramCar::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + //Calling gameLocal's version because we don't want to get bound and hidden + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_explode" ), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + PostEventMS( &EV_TramCar_RadiusDamage, 0, center, spawnArgs.GetString("def_damage_explode") ); + + //TODO: think about giving rigid body physics and have it fall from track + Hide(); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetDamageScale() const { + return 1.0f - GetHealthScale(); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetHealthScale() const { + float spawnHealth = spawnArgs.GetFloat( "health" ); + return ( idMath::Fabs(spawnHealth) <= VECTOR_EPSILON ) ? 1.0f : (health / spawnHealth); +} + +/* +================ +rvTramCar::Save +================ +*/ +void rvTramCar::Save( idSaveGame *savefile ) const { + savefile->WriteInt( idealTrack ); +// savefile->WriteString( idealTrackTag.c_str() ); // cnicholson: FIXME: This has a comment of HACK in the .h file... Not sure how to write a idStr yet + + savefile->WriteFrustum( collisionFov ); + + idealTrackStateThread.Save( savefile ); + speedSoundEffectsStateThread.Save( savefile ); + + driver.Save( savefile ); + + savefile->WriteInt( occupants.Num() ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Save( savefile ); + } + + savefile->WriteInt( weapons.Num() ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Save( savefile ); + } + + savefile->WriteInt( numTracksOnMap ); + + leftDoor.Save ( savefile ); + rightDoor.Save ( savefile ); +} + +/* +================ +rvTramCar::Restore +================ +*/ +void rvTramCar::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idealTrack ); + + savefile->ReadFrustum( collisionFov ); + + idealTrackStateThread.Restore( savefile, this ); + speedSoundEffectsStateThread.Restore( savefile, this ); + + driver.Restore( savefile ); + + int num = 0; + savefile->ReadInt( num ); + occupants.SetNum( num ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Restore( savefile ); + } + + savefile->ReadInt( num ); + weapons.SetNum( num ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Restore( savefile ); + } + + savefile->ReadInt( numTracksOnMap ); + + leftDoor.Restore ( savefile ); + rightDoor.Restore ( savefile ); +} + +/* +================ +rvTramCar::WriteToSnapshot +================ +*/ +void rvTramCar::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar::ReadFromSnapshot +================ +*/ +void rvTramCar::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar::HeadTowardsIdealTrack +================ +*/ +void rvTramCar::HeadTowardsIdealTrack() { + idSplinePath* ideal = FindSplineToIdealTrack( GetSpline() ); + if( !ideal ) { + ideal = GetRandomSpline(GetSpline()); + } + + SetSpline( ideal ); +} + +/* +================ +rvTramCar::FindSplineToTrack +================ +*/ +enum { + LOOK_LEFT = -1, + LOOK_RIGHT = 1 +}; +idSplinePath* rvTramCar::FindSplineToTrack( idSplinePath* spline, const idStr& track ) const { + idSplinePath* target = NULL; + idEntity* ent = NULL; + idStr trackInfo; + idList list; + + if( !spline ) { + return NULL; + } + + for( int ix = SortSplineTargets(spline) - 1; ix >= 0; --ix ) { + ent = GetSplineTarget( spline, ix ); + target = static_cast( ent ); + assert( target->IsActive() ); + + trackInfo = GetTrackInfo( target ); + if( -1 >= trackInfo.Find(track) ) { + continue; + } + + // HACK: I hate switch statements + switch( ConvertToTrackNumber(trackInfo) - GetCurrentTrack() ) { + case LOOK_LEFT: { + if( !LookLeft(list) ) { + return target; + } + break; + } + + case LOOK_RIGHT: { + if( !LookRight(list) ) { + return target; + } + break; + } + + default: { + return target; + } + } + // HACK + } + + return NULL; +} + +/* +================ +rvTramCar::SortSplineTargets +================ +*/ +int rvTramCar::SortSplineTargets( idSplinePath* spline ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->SortTargets() : spline->SortBackwardsTargets(); +} + +/* +================ +rvTramCar::GetSplineTarget +================ +*/ +idEntity* rvTramCar::GetSplineTarget( idSplinePath* spline, int index ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->targets[index].GetEntity() : spline->backwardPathTargets[index].GetEntity(); +} + +/* +================ +rvTramCar::FindSplineToIdealTrack +================ +*/ +idSplinePath* rvTramCar::FindSplineToIdealTrack( idSplinePath* spline ) const { + // HACK + int trackDelta = idMath::ClampInt( -1, 1, GetIdealTrack() - GetCurrentTrack() ); + idStr trackLetter( ConvertToTrackLetter(GetCurrentTrack() + trackDelta) ); + idSplinePath* s = NULL; + + if( idealTrackTag.Length() && !trackDelta ) {// On ideal track + s = FindSplineToTrack( spline, idealTrackTag + trackLetter ); + } + if( !s ) { + s = FindSplineToTrack( spline, trackLetter ); + } + return s; +} + +/* +================ +rvTramCar::GetRandomSpline +================ +*/ +idSplinePath* rvTramCar::GetRandomSpline( idSplinePath* spline ) const { + if( !spline ) { + return NULL; + } + + int numActiveTargets = SortSplineTargets( spline ); + if( !numActiveTargets ) { + return NULL; + } + + idEntity* target = GetSplineTarget( spline, rvRandom::irand(0, numActiveTargets - 1) ); + return (target && target->IsType(idSplinePath::GetClassType())) ? static_cast(target) : NULL; +} + +/* +================ +rvTramCar::GetCurrentTrack +================ +*/ +int rvTramCar::GetCurrentTrack() const { + return ConvertToTrackNumber( GetTrackInfo(GetSpline()) ); +} + +/* +================ +rvTramCar::UpdateChannel +================ +*/ +void rvTramCar::UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( channel, &parms ); + } +} + +/* +================ +rvTramCar::AttenuateTrackChannel +================ +*/ +void rvTramCar::AttenuateTrackChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.frequencyShift = attenuation;//idMath::MidPointLerp( 0.0f, 1.0f, 1.1f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY, parms ); +} + +/* +================ +rvTramCar::AttenuateTramCarChannel +================ +*/ +void rvTramCar::AttenuateTramCarChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.volume = (attenuation + 1.0f) * 0.5f; + parms.frequencyShift = Max( 0.4f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY2, parms ); +} + +/* +================ +rvTramCar::LookForward +================ +*/ +bool rvTramCar::LookForward( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( GetPhysics()->GetAxis() ); + + Look( collisionFov, list ); + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( !OnSameTrackAs(list[ix]) ) { + list.Remove( list[ix] ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvTramCar::LookLeft +================ +*/ +bool rvTramCar::LookLeft( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRight +================ +*/ +bool rvTramCar::LookRight( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookLeftForTrackChange +================ +*/ +bool rvTramCar::LookLeftForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRightForTrackChange +================ +*/ +bool rvTramCar::LookRightForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::Look +================ +*/ +int rvSortByDist( const void* left, const void* right ) { + rvSplineMover* leftMover = *(rvSplineMover**)left; + rvSplineMover* rightMover = *(rvSplineMover**)right; + + return rightMover->spawnArgs.GetFloat("distAway") - leftMover->spawnArgs.GetFloat("distAway"); +} +bool rvTramCar::Look( const idFrustum& fov, idList& list ) const { + bool result = WhosVisible( fov, list ); + + if( g_debugVehicle.GetInteger() == 3 ) { + gameRenderWorld->DebugFrustum( colorRed, fov ); + } + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + list[ix]->spawnArgs.SetFloat( "distAway", (list[ix]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).Length() ); + } + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByDist ); + + // Do we need to get rid of these keyvalues? + + return result; +} + +/* +================ +rvTramCar::OnSameTrackAs +================ +*/ +bool rvTramCar::OnSameTrackAs( const rvSplineMover* tram ) const { + return tram && !GetTrackInfo(GetSpline()).Icmp( GetTrackInfo(tram->GetSpline()) ); +} + +/* +================ +rvTramCar::SameIdealTrackAs +================ +*/ +bool rvTramCar::SameIdealTrackAs( const rvSplineMover* tram ) const { + if( !tram ) { + return false; + } + return GetIdealTrack() == static_cast(tram)->GetIdealTrack(); +} + +/* +================ +rvTramCar::LookAround +================ +*/ +void rvTramCar::LookAround() { + idList moverList; + + if( !AdjustSpeed(moverList) ) { + SetSpeed( GetIdealSpeed() ); + } +} + +/* +================ +rvTramCar::AdjustSpeed +================ +*/ +bool rvTramCar::AdjustSpeed( idList& moverList ) { + if( LookForward(moverList) ) { + if( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugLine( colorRed, GetPhysics()->GetOrigin(), moverList[0]->GetPhysics()->GetOrigin() ); + } + + //Slow down and try to get onto other track if allowed + SetSpeed( moverList[0]->GetSpeed() * rvRandom::flrand(0.5f, 0.75f) ); + + // Is this safe if we are on a specified track + SetIdealTrack( (GetIdealTrack() + rvRandom::irand(1, 2)) % numTracksOnMap ); + return true; + } + + return false; +} + +/* +================ +rvTramCar::DriverSpeak +================ +*/ +idEntity* rvTramCar::DriverSpeak( const char* speechDecl, bool random ) { + if( !driver.IsValid() || driver->IsSpeaking() ) { + return NULL; + } + + driver->Speak( speechDecl, random ); + return driver; +} + +/* +================ +rvTramCar::OccupantSpeak +================ +*/ +idEntity* rvTramCar::OccupantSpeak( const char *speechDecl, bool random ) { + idEntityPtr occupant; + + if( !occupants.Num() ) { + return NULL; + } + + occupant = occupants[ rvRandom::irand(0, occupants.Num() - 1) ]; + if( !occupant.IsValid() || occupant->IsSpeaking() ) { + return NULL; + } + + occupant->Speak( speechDecl, random ); + return occupant; +} + +/* +================ +rvTramCar::PostDoneMoving +================ +*/ +void rvTramCar::PostDoneMoving() { + rvSplineMover::PostDoneMoving(); + + HeadTowardsIdealTrack(); +} + +/* +================ +rvTramCar::DeployRamp +================ +*/ +void rvTramCar::DeployRamp() { + OperateRamp( "open" ); +} + +/* +================ +rvTramCar::RetractRamp +================ +*/ +void rvTramCar::RetractRamp() { + OperateRamp( "close" ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation ) { + if( !operation || !operation[0] ) { + return; + } + + PlayAnim( ANIMCHANNEL_ALL, operation, 0 ); + + OperateRamp( operation, leftDoor ); + OperateRamp( operation, rightDoor ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation, idMover* door ) { + if( !operation || !operation[0] ) { + return; + } + + idAngles ang; + idVec3 vec; + if( !door ) { + return; + } + + if( !door->IsBound() ) { + return; + } + + ang = door->spawnArgs.GetAngles( va("%s%s", operation, "_rotation") ); + vec.Set( ang[0], ang[1], ang[2] ); + door->GetBindMaster()->ProcessEvent( &EV_RotateOnce, vec ); + + vec = door->spawnArgs.GetVector(va("%s%s", operation, "_dir")).ToNormal() * door->spawnArgs.GetFloat("distExt"); + door->PostEventSec( &EV_MoveAlongVector, 0.5f, vec ); +} + +/* +================ +rvTramCar::Event_OnStopMoving +================ +*/ +void rvTramCar::Event_OnStopMoving() { + rvSplineMover::Event_OnStopMoving(); + + speedSoundEffectsStateThread.SetState( "IdleSpeed" ); +} + +/* +================ +rvTramCar::Event_OnStartMoving +================ +*/ +void rvTramCar::Event_OnStartMoving() { + rvSplineMover::Event_OnStartMoving(); + + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); +} + +/* +================ +rvTramCar::Event_DriverSpeak +================ +*/ +void rvTramCar::Event_DriverSpeak( const char* voKey ) { + idThread::ReturnEntity( DriverSpeak(voKey) ); +} + +/* +================ +rvTramCar::Event_GetDriver +================ +*/ +void rvTramCar::Event_GetDriver() { + idThread::ReturnEntity( driver ); +} + +/* +================ +rvTramCar::Event_Activate +================ +*/ +void rvTramCar::Event_Activate( idEntity* activator ) { + rvSplineMover::Event_Activate( activator ); + + // If being activated by a spawner we need to attach to it + if( activator->IsType(rvSpawner::GetClassType()) ) { + static_cast(activator)->Attach( this ); + } else { + if( driver.IsValid() ) { + driver->ProcessEvent( &EV_Activate, activator ); + } + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->ProcessEvent( &EV_Activate, activator ); + } + } +} + +/* +================ +rvTramCar::Event_RadiusDamage +================ +*/ +void rvTramCar::Event_RadiusDamage( const idVec3& origin, const char* damageDefName ) { + gameLocal.RadiusDamage( origin, this, this, this, this, damageDefName ); +} + +/* +================ +rvTramCar::Event_SetIdealTrack +================ +*/ +void rvTramCar::Event_SetIdealTrack( const char* track ) { + idStr ideal = track; + + if( ideal.Length() > 1 ) { + idealTrackTag = ideal.Left( ideal.Length() - 1 ); + } + + idealTrackStateThread.SetState( "AssignedTrack" ); + SetIdealTrack( ConvertToTrackNumber(ideal.Right(1)) ); +} + +/* +================ +rvTramCar::Event_OpenDoors +================ +*/ +void rvTramCar::Event_OpenDoors() { + DeployRamp(); +} + +/* +================ +rvTramCar::Event_CloseDoors +================ +*/ +void rvTramCar::Event_CloseDoors() { + RetractRamp(); +} + +/* +================ +rvTramCar::Event_SetHealth +================ +*/ +void rvTramCar::Event_SetHealth ( float health ) { + this->health = health; +} + +CLASS_STATES_DECLARATION( rvTramCar ) + STATE( "IdleSpeed", rvTramCar::State_Idle ) + STATE( "NormalSpeed", rvTramCar::State_NormalSpeed ) + STATE( "ExcessiveSpeed", rvTramCar::State_ExcessiveSpeed ) + STATE( "RandomTrack", rvTramCar::State_RandomTrack ) + STATE( "AssignedTrack", rvTramCar::State_AssignedTrack ) +END_CLASS_STATES + +/* +================ +rvTramCar::State_Idle +================ +*/ +stateResult_t rvTramCar::State_Idle( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_idle_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_idle_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_NormalSpeed +================ +*/ +stateResult_t rvTramCar::State_NormalSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_normal_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_normal_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = 0.8f + 0.2f * ( GetSpeed() / GetNormalSpeed() ); + + if( speedScale >= 1.0f ) { + speedSoundEffectsStateThread.SetState( "ExcessiveSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_ExcessiveSpeed +================ +*/ +stateResult_t rvTramCar::State_ExcessiveSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_excessive_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_excessive_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = GetSpeed() / GetNormalSpeed(); + + if( speedScale < 1.0f ) { + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_RandomTrack +================ +*/ +stateResult_t rvTramCar::State_RandomTrack( const stateParms_t& parms ) { + SetIdealTrack( rvRandom::irand(0, numTracksOnMap - 1) ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_AssignedTrack +================ +*/ +stateResult_t rvTramCar::State_AssignedTrack( const stateParms_t& parms ) { + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +const idEventDef EV_TramCar_UseMountedGun( "useMountedGun", "e" ); +const idEventDef EV_TramCar_SetPlayerDamageEnt( "setPlayerDamageEnt", "f" ); + +CLASS_DECLARATION( rvTramCar, rvTramCar_Marine ) + EVENT( EV_TramCar_UseMountedGun, rvTramCar_Marine::Event_UseMountedGun ) + EVENT( EV_TramCar_SetPlayerDamageEnt, rvTramCar_Marine::Event_SetPlayerDamageEntity ) +END_CLASS + +/* +================ +rvTramCar_Marine::Spawn +================ +*/ +void rvTramCar_Marine::Spawn() { + RegisterStateThread( playerOccupationStateThread, "PlayerOccupation" ); + playerOccupationStateThread.SetState( "NotOccupied" ); + + RegisterStateThread( playerUsingMountedGunStateThread, "MountedGunInUse" ); + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); + lastHeal = 0; + healDelay = spawnArgs.GetInt( "heal_delay", "15" ); + healAmount = spawnArgs.GetInt( "heal_amount", "2" ); +} + +/* +================ +rvTramCar_Marine::MidThink +================ +*/ +void rvTramCar_Marine::MidThink() { + rvTramCar::MidThink(); + + if ( healDelay > 0 && lastHeal + healDelay < gameLocal.time && health < maxHealth ) { + health += healAmount; + + if ( health > maxHealth ) { + health = maxHealth; + } + } + + playerOccupationStateThread.Execute(); +} + +/* +================ +rvTramCar_Marine::ActivateTramHud +================ +*/ +void rvTramCar_Marine::ActivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "enterTram" ); +} + +/* +================ +rvTramCar_Marine::DeactivateTramHud +================ +*/ +void rvTramCar_Marine::DeactivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "leaveTram" ); +} + +/* +================ +rvTramCar_Marine::UpdateTramHud +================ +*/ +void rvTramCar_Marine::UpdateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->SetStateFloat( "tram_healthpct", GetHealthScale() ); +} + +/* +============ +rvTramCar_Marine::Damage +============ +*/ +void rvTramCar_Marine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + rvTramCar::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if( attacker == gameLocal.GetLocalPlayer() ) { + return; + } + + if( fl.takedamage && GetDamageScale() < 0.35f ) { + return; + } + + if( rvRandom::flrand() > spawnArgs.GetFloat("damageWarningFreq", "0.2") ) { + return; + } + + DriverSpeak( "lipsync_damageWarning", true ); +} + +/* +================ +rvTramCar_Marine::Killed +================ +*/ +void rvTramCar_Marine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + playerOccupationStateThread.Execute(); + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && EntityIsInside(player) ) { + player->SetDamageEntity( NULL );// This should fix the damage from tram car propogation issue + player->ExitVehicle( true );// This should fix the issue that was causing the player to get removed + } + + rvTramCar::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvTramCar_Marine::Save +================ +*/ +void rvTramCar_Marine::Save( idSaveGame *savefile ) const { + savefile->WriteInt( visibleEnemies.Num() ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Save( savefile ); + } + + playerOccupationStateThread.Save( savefile ); + playerUsingMountedGunStateThread.Save( savefile ); + + // no need to save this since it's set in the restore + //savefile->WriteInt( maxHealth ); // cnicholson: added unsaved var + savefile->WriteInt( lastHeal ); + savefile->WriteInt( healDelay ); + savefile->WriteInt( healAmount ); +} + +/* +================ +rvTramCar_Marine::Restore +================ +*/ +void rvTramCar_Marine::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + visibleEnemies.SetNum( num ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Restore( savefile ); + } + + playerOccupationStateThread.Restore( savefile, this ); + playerUsingMountedGunStateThread.Restore( savefile, this ); + + savefile->ReadInt( lastHeal ); + savefile->ReadInt( healDelay ); + savefile->ReadInt( healAmount ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); +} + +/* +================ +rvTramCar_Marine::LookAround +================ +*/ +void rvTramCar_Marine::LookAround() { + idList moverList; + + rvTramCar::LookAround(); + + if( !driver.IsValid() || driver->IsSpeaking() ) { + return; + } + + if( LookOverLeftShoulder(moverList)) { + DriverSpeak( "lipsync_warningLeft", true ); + driver->ScriptedAnim( "point_left", 0, false, true ); + } else if( LookOverRightShoulder(moverList)) { + DriverSpeak( "lipsync_warningRight", true ); + driver->ScriptedAnim( "point_right", 0, false, true ); + } +} + + +/* +================ +rvTramCar_Marine::LookOverLeftShoulder +================ +*/ +bool rvTramCar_Marine::LookOverLeftShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, -120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::LookOverRightShoulder +================ +*/ +bool rvTramCar_Marine::LookOverRightShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, 120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::EntityIsInside +================ +*/ +bool rvTramCar_Marine::EntityIsInside( const idEntity* entity ) const { + assert( entity ); + return GetPhysics()->GetAbsBounds().ContainsPoint( entity->GetPhysics()->GetAbsBounds().GetCenter() ); +} + +/* +================ +rvTramCar_Marine::DeployRamp +================ +*/ +void rvTramCar_Marine::DeployRamp() { + rvTramCar::DeployRamp(); + + if( weapons.Num() ) { + weapons[0]->Unlock(); + weapons[0]->EjectAllDrivers( true ); + } +} + +/* +================ +rvTramCar_Marine::RetractRamp +================ +*/ +void rvTramCar_Marine::RetractRamp() { + rvTramCar::RetractRamp(); + + UseMountedGun( gameLocal.GetLocalPlayer() ); +} + +/* +================ +rvTramCar_Marine::UseMountedGun +================ +*/ +void rvTramCar_Marine::UseMountedGun( idPlayer* player ) { + if( playerOccupationStateThread.CurrentStateIs("Occupied") && EntityIsInside(player) && weapons.Num() ) { + player->EnterVehicle( weapons[0] ); + weapons[0]->Lock(); + } +} + +/* +================ +rvTramCar_Marine::Event_UseMountedGun +================ +*/ +void rvTramCar_Marine::Event_UseMountedGun( idEntity* ent ) { + if( !ent || !ent->IsType(idPlayer::GetClassType()) ) { + return; + } + + UseMountedGun( static_cast(ent) ); +} + +/* +================ +rvTramCar_Marine::Event_SetPlayerDamageEntity +================ +*/ +void rvTramCar_Marine::Event_SetPlayerDamageEntity(float f) { + + gameLocal.GetLocalPlayer()->SetDamageEntity( (f? this : NULL) ); +} + +CLASS_STATES_DECLARATION( rvTramCar_Marine ) + STATE( "Occupied", rvTramCar_Marine::State_Occupied ) + STATE( "NotOccupied", rvTramCar_Marine::State_NotOccupied ) + STATE( "UsingMountedGun", rvTramCar_Marine::State_UsingMountedGun ) + STATE( "NotUsingMountedGun",rvTramCar_Marine::State_NotUsingMountedGun ) +END_CLASS_STATES + + +/* +================ +rvTramCar_Marine::State_Occupied +================ +*/ +stateResult_t rvTramCar_Marine::State_Occupied( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_DisableWeapon ); + ActivateTramHud( player ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( !PlayerIsInside() ) { + playerOccupationStateThread.SetState( "NotOccupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( NULL ); + fl.takedamage = false; + return SRESULT_DONE_WAIT; + } + + playerUsingMountedGunStateThread.Execute(); + + UpdateTramHud( player ); + + return SRESULT_WAIT; +} + + + +/* +================ +rvTramCar_Marine::State_NotOccupied +================ +*/ +stateResult_t rvTramCar_Marine::State_NotOccupied( const stateParms_t& parms ) { + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_EnableWeapon ); + DeactivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( PlayerIsInside() ) { + playerOccupationStateThread.SetState( "Occupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( this ); + fl.takedamage = true; + return SRESULT_DONE_WAIT; + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_UsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_UsingMountedGun( const stateParms_t& parms ) { + if( !parms.stage ) { + ActivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && !player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_NotUsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_NotUsingMountedGun( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "UsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +CLASS_DECLARATION( rvTramCar, rvTramCar_Strogg ) + EVENT( EV_PostSpawn, rvTramCar_Strogg::Event_PostSpawn ) +END_CLASS + +/* +================ +rvTramCar_Strogg::Spawn +================ +*/ +void rvTramCar_Strogg::Spawn() { + SetTarget( NULL ); + + RegisterStateThread( targetSearchStateThread, "targetSearch" ); + targetSearchStateThread.SetState( "LookingForTarget" ); +} + +/* +================ +rvTramCar_Strogg::SetTarget +================ +*/ +void rvTramCar_Strogg::SetTarget( idEntity* newTarget ) { + target = ConvertToMover( newTarget ); +} + +/* +================ +rvTramCar_Strogg::GetTarget +================ +*/ +const rvSplineMover* rvTramCar_Strogg::GetTarget() const { + return target.GetEntity(); +} + +/* +================ +rvTramCar_Strogg::Save +================ +*/ +void rvTramCar_Strogg::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + targetSearchStateThread.Save( savefile ); +} + +/* +================ +rvTramCar_Strogg::Restore +================ +*/ +void rvTramCar_Strogg::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + targetSearchStateThread.Restore( savefile, this ); +} + +/* +================ +rvTramCar_Strogg::WriteToSnapshot +================ +*/ +void rvTramCar_Strogg::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar_Strogg::ReadFromSnapshot +================ +*/ +void rvTramCar_Strogg::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar_Strogg::Event_PostSpawn +================ +*/ +void rvTramCar_Strogg::Event_PostSpawn() { + idEntity* enemy = gameLocal.FindEntity( spawnArgs.GetString("enemy") ); + + SetTarget( enemy ); + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->SetEnemy( enemy ); + } + + rvTramCar::Event_PostSpawn(); +} + +/* +================ +rvTramCar_Strogg::TargetIsToLeft +================ +*/ +bool rvTramCar_Strogg::TargetIsToLeft() { + idList list; + + if( LookLeft(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::TargetIsToRight +================ +*/ +bool rvTramCar_Strogg::TargetIsToRight() { + idList list; + + if( LookRight(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::LookAround +================ +*/ +void rvTramCar_Strogg::LookAround() { + targetSearchStateThread.Execute(); +} + +CLASS_STATES_DECLARATION( rvTramCar_Strogg ) + STATE( "LookingForTarget", rvTramCar_Strogg::State_LookingForTarget ) + STATE( "TargetInSight", rvTramCar_Strogg::State_TargetInSight ) +END_CLASS_STATES + +/* +================ +rvTramCar_Strogg::State_LookingForTarget +================ +*/ +stateResult_t rvTramCar_Strogg::State_LookingForTarget( const stateParms_t& parms ) { + static const int MSEC_DELAY = 100; + idList list; + + if( !parms.stage ) {// Go back to random if we are supposed to be random + //idealTrackStateThread.SetState( "RandomTrack" ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + // Could optimise based on what track + if( TargetIsToLeft() || TargetIsToRight() ) { + targetSearchStateThread.SetState( "TargetInSight" ); + return SRESULT_DONE; + } + + SetSpeed( GetIdealSpeed() ); + return SRESULT_DELAY( MSEC_DELAY ); +} + +/* +================ +rvTramCar_Strogg::State_TargetInSight +================ +*/ +stateResult_t rvTramCar_Strogg::State_TargetInSight( const stateParms_t& parms ) { + static const int MSEC_DELAY = 3000; + + if( !target.IsValid() || (!TargetIsToLeft() && !TargetIsToRight()) ) { + targetSearchStateThread.SetState( "LookingForTarget" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + SetIdealTrack( GetCurrentTrack() ); + SetSpeed( target->GetSpeed() * 0.5f ); + idealTrackStateThread.SetState( "AssignedTrack" ); + StartSound( "snd_horn", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idList list; + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + float dot = GetPhysics()->GetAxis()[0] * (target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToNormal(); + float deltaSpeed = target->GetIdealSpeed() * rvRandom::flrand(0.0f, 0.1f) * SignZero(dot); + SetSpeed( target->GetIdealSpeed() + deltaSpeed ); + + return SRESULT_DELAY( MSEC_DELAY ); +} diff --git a/source/mpgame/SplineMover.h b/source/mpgame/SplineMover.h new file mode 100644 index 0000000..5633c96 --- /dev/null +++ b/source/mpgame/SplineMover.h @@ -0,0 +1,494 @@ +#ifndef __RV_SPLINE_MOVER_H +#define __RV_SPLINE_MOVER_H + +extern const idEventDef EV_SetSpline; + +struct splinePState_t { + idVec3 origin; + idVec3 localOrigin; + idMat3 axis; + idMat3 localAxis; + + float speed; + float idealSpeed; + float dist; + + float acceleration; + float deceleration; + + bool ShouldAccelerate() const; + bool ShouldDecelerate() const; + + void ApplyAccelerationDelta( float timeStepSec ); + + void ApplyDecelerationDelta( float timeStepSec ); + + void UpdateDist( float timeStepSec ); + void Clear(); + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + splinePState_t& Assign( const splinePState_t* state ); + splinePState_t& operator=( const splinePState_t& state ); + splinePState_t& operator=( const splinePState_t* state ); +}; + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +class rvPhysics_Spline : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Spline ); + + rvPhysics_Spline( void ); + virtual ~rvPhysics_Spline( void ); + + void Save( idSaveGame *savefile ) const; + void Event_PostRestore( void ); + void Restore( idRestoreGame *savefile ); + + void SetSpline( idCurve_Spline* spline ); + const idCurve_Spline* GetSpline() const { return spline; } + idCurve_Spline* GetSpline() { return spline; } + + void SetSplineEntity( idSplinePath* spline ); + const idSplinePath* GetSplineEntity() const { return splineEntity; } + idSplinePath* GetSplineEntity() { return splineEntity; } + + void SetLinearAcceleration( const float accel ); + void SetLinearDeceleration( const float decel ); + + void SetSpeed( float speed ); + float GetSpeed( void ) const; + + virtual bool StartingToMove( void ) const; + virtual bool StoppedMoving( void ) const; + + float ComputeDecelFromSpline( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool EvaluateSpline( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + bool EvaluateMaster( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + + void Activate( void ); + void Rest( void ); + + bool IsAtRest( void ) const; + bool IsAtEndOfSpline( void ) const; + bool IsAtBeginningOfSpline( void ) const; + + virtual bool IsPushable( void ) const; + + bool HasValidSpline( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + idVec3 & GetOrigin( int id = 0 ); + idMat3 & GetAxis( int id = 0 ); + + void SetMaster( idEntity *master, const bool orientated ); + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual const trace_t* GetBlockingInfo( void ) const; + virtual idEntity* GetBlockingEntity( void ) const; + +public: + stateResult_t State_Accelerating( const stateParms_t& parms ); + stateResult_t State_Decelerating( const stateParms_t& parms ); + stateResult_t State_Cruising( const stateParms_t& parms ); + +protected: + const idVec3 & GetLocalOrigin( int id = 0 ) const; + const idMat3 & GetLocalAxis( int id = 0 ) const; + + idVec3 & GetLocalOrigin( int id = 0 ); + idMat3 & GetLocalAxis( int id = 0 ); + +protected: + splinePState_t current; + splinePState_t saved; + + float splineLength; + idCurve_Spline* spline; + idEntityPtr splineEntity; + + trace_t pushResults; + + idClipModel* clipModel; + + rvStateThread accelDecelStateThread; + + CLASS_STATES_PROTOTYPE( rvPhysics_Spline ); +}; + +extern const idEventDef EV_DoneMoving; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +class rvSplineMover : public idAnimatedEntity { + CLASS_PROTOTYPE( rvSplineMover ); + +public: + void Spawn(); + virtual ~rvSplineMover(); + + virtual void SetSpeed( float newSpeed ); + virtual float GetSpeed() const; + virtual void SetIdealSpeed( float newIdealSpeed ); + virtual float GetIdealSpeed() const; + + virtual void SetSpline( idSplinePath* spline ); + virtual const idSplinePath* GetSpline() const; + virtual idSplinePath* GetSpline(); + + virtual void SetAcceleration( float accel ); + virtual void SetDeceleration( float decel ); + + virtual void CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ); + virtual void RestoreFromOverrides( const idDict* args ); + + int PlayAnim( int channel, const char* animName, int blendFrames ); + void CycleAnim( int channel, const char* animName, int blendFrames ); + void ClearChannel( int channel, int clearFrame ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected:// TramCar utility functions + void AddSelfToGlobalList(); + void RemoveSelfFromGlobalList(); + bool InGlobalList() const; + bool WhosVisible( const idFrustum& frustum, idList& list ) const; + +protected: + virtual idStr GetTrackInfo( const idSplinePath* track ) const; + rvSplineMover* ConvertToMover( idEntity* mover ) const; + idSplinePath* ConvertToSplinePath( idEntity* spline ) const; + + void CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ); + virtual void PreBind(); + + virtual void PreDoneMoving(); + virtual void PostDoneMoving(); + +protected: + void Event_PostSpawn(); + + void Event_SetSpline( idEntity* spline ); + void Event_GetSpline(); + + void Event_SetAcceleration( float accel ); + void Event_SetDeceleration( float decel ); + + void Event_OnAcceleration(); + void Event_OnDeceleration(); + void Event_OnCruising(); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_SetSpeed( float speed ); + void Event_GetSpeed(); + void Event_SetIdealSpeed( float speed ); + void Event_GetIdealSpeed(); + void Event_ApplySpeedScale( float scale ); + + void Event_SetCallBack(); + void Event_DoneMoving(); + + void Event_GetCurrentTrackInfo(); + void Event_GetTrackInfo( idEntity* track ); + + void Event_Activate( idEntity* activator ); + + void Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ); + void Event_PartBlocked( idEntity *blockingEntity ); + +protected: + rvPhysics_Spline physicsObj; + + float idealSpeed; + + int waitThreadId; + +private: + idLinkList splineMoverNode; + static idLinkList splineMovers; +}; + +//======================================================= +// +// rvTramCar +// +//======================================================= +class rvTramCar : public rvSplineMover { + CLASS_PROTOTYPE( rvTramCar ); + +public: + void Spawn(); + virtual ~rvTramCar(); + + virtual void Think(); + virtual void MidThink(); + + virtual float GetNormalSpeed() const { return spawnArgs.GetFloat("normalSpeed"); } + + virtual void SetIdealTrack( int track ); + virtual int GetIdealTrack() const; + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual float GetDamageScale() const; + virtual float GetHealthScale() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_Idle( const stateParms_t& parms ); + stateResult_t State_NormalSpeed( const stateParms_t& parms ); + stateResult_t State_ExcessiveSpeed( const stateParms_t& parms ); + + stateResult_t State_RandomTrack( const stateParms_t& parms ); + stateResult_t State_AssignedTrack( const stateParms_t& parms ); + +protected: + void SpawnDriver( const char* driverKey ); + void SpawnWeapons( const char* partKey ); + void SpawnOccupants( const char* partKey ); + idEntity* SpawnPart( const char* partDefName, const char* subPartDefName ); + void SpawnDoors(); + idMover* SpawnDoor( const char* key ); + + int SortSplineTargets( idSplinePath* spline ) const; + idEntity* GetSplineTarget( idSplinePath* spline, int index ) const; + + void HeadTowardsIdealTrack(); + idSplinePath* FindSplineToTrack( idSplinePath* spline, const idStr& track ) const; + idSplinePath* FindSplineToIdealTrack( idSplinePath* spline ) const; + idSplinePath* GetRandomSpline( idSplinePath* spline ) const; + + char ConvertToTrackLetter( int trackNum ) const { return trackNum + 'A'; } + int ConvertToTrackNumber( char trackLetter ) const { return trackLetter - 'A'; } + int ConvertToTrackNumber( const idStr& trackInfo ) const { return ConvertToTrackNumber(idStr::ToUpper(trackInfo.Right(1)[0])); } + int GetCurrentTrack() const; + + virtual void UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ); + virtual void AttenuateTrackChannel( float attenuation ); + virtual void AttenuateTramCarChannel( float attenuation ); + + virtual void RegisterStateThread( rvStateThread& stateThread, const char* name ); + + bool LookForward( idList& list ) const; + bool LookLeft( idList& list ) const; + bool LookRight( idList& list ) const; + + bool LookLeftForTrackChange( idList& list ) const; + bool LookRightForTrackChange( idList& list ) const; + + bool Look( const idFrustum& fov, idList& list ) const; + + virtual void LookAround(); + virtual bool AdjustSpeed( idList& moverList ); + + virtual bool OnSameTrackAs( const rvSplineMover* tram ) const; + virtual bool SameIdealTrackAs( const rvSplineMover* tram ) const; + + virtual idEntity* DriverSpeak( const char *speechDecl, bool random = false ); + virtual idEntity* OccupantSpeak( const char *speechDecl, bool random = false ); + + virtual void PostDoneMoving(); + + virtual void DeployRamp(); + virtual void RetractRamp(); + void OperateRamp( const char* operation ); + void OperateRamp( const char* operation, idMover* door ); + +protected: + void Event_DriverSpeak( const char* voKey ); + void Event_GetDriver(); + void Event_Activate( idEntity* activator ); + void Event_RadiusDamage( const idVec3& origin, const char* damageDefName ); + void Event_SetIdealTrack( const char* track ); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_OpenDoors(); + void Event_CloseDoors(); + + void Event_SetHealth( float health ); + +protected: + int idealTrack; + idStr idealTrackTag;// HACK + + mutable idFrustum collisionFov; + + rvStateThread idealTrackStateThread; + rvStateThread speedSoundEffectsStateThread; + + idEntityPtr driver; + idList< idEntityPtr > occupants; + idList< idEntityPtr > weapons; + + int numTracksOnMap; + + idEntityPtr leftDoor; + idEntityPtr rightDoor; + + CLASS_STATES_PROTOTYPE( rvTramCar ); +}; + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +class rvTramCar_Marine : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Marine ); + +public: + void Spawn(); + + virtual void MidThink(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: + stateResult_t State_Occupied( const stateParms_t& parms ); + stateResult_t State_NotOccupied( const stateParms_t& parms ); + + stateResult_t State_UsingMountedGun( const stateParms_t& parms ); + stateResult_t State_NotUsingMountedGun( const stateParms_t& parms ); + +protected: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void LookAround(); + + bool LookOverLeftShoulder( idList& list ); + bool LookOverRightShoulder( idList& list ); + + bool EntityIsInside( const idEntity* entity ) const; + bool PlayerIsInside() const { return EntityIsInside(gameLocal.GetLocalPlayer()); } + + void ActivateTramHud( idPlayer* player ); + void DeactivateTramHud( idPlayer* player ); + void UpdateTramHud( idPlayer* player ); + + virtual void DeployRamp(); + virtual void RetractRamp(); + + void UseMountedGun( idPlayer* player ); + + void Event_UseMountedGun( idEntity* ent ); + void Event_SetPlayerDamageEntity(float f); + +protected: + idList< idEntityPtr > visibleEnemies; + + rvStateThread playerOccupationStateThread; + rvStateThread playerUsingMountedGunStateThread; + + int maxHealth; + int lastHeal; + int healDelay; + int healAmount; + + CLASS_STATES_PROTOTYPE( rvTramCar_Marine ); +}; + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +class rvTramCar_Strogg : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Strogg ); + +public: + void Spawn(); + + virtual void SetTarget( idEntity* newTarget ); + virtual const rvSplineMover* GetTarget() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_LookingForTarget( const stateParms_t& parms ); + stateResult_t State_TargetInSight( const stateParms_t& parms ); + +protected: + bool TargetIsToLeft(); + bool TargetIsToRight(); + + virtual void LookAround(); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr target; + + rvStateThread targetSearchStateThread; + + CLASS_STATES_PROTOTYPE( rvTramCar_Strogg ); +}; + +#endif diff --git a/source/mpgame/Target.cpp b/source/mpgame/Target.cpp new file mode 100644 index 0000000..78abf12 --- /dev/null +++ b/source/mpgame/Target.cpp @@ -0,0 +1,2321 @@ +/* + +Invisible entities that affect other entities or the world when activated. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTarget ) +END_CLASS + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Remove ) + EVENT( EV_Activate, idTarget_Remove::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Remove::Event_Activate +================ +*/ +void idTarget_Remove::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Show ) + EVENT( EV_Activate, idTarget_Show::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Show::Event_Activate +================ +*/ +void idTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Damage ) + EVENT( EV_Activate, idTarget_Damage::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Damage::Event_Activate +================ +*/ +void idTarget_Damage::Event_Activate( idEntity *activator ) { + int i; + const char *damage; + idEntity * ent; + + damage = spawnArgs.GetString( "def_damage", "damage_generic" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + } + } +} + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SessionCommand ) + EVENT( EV_Activate, idTarget_SessionCommand::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SessionCommand::Event_Activate +================ +*/ +void idTarget_SessionCommand::Event_Activate( idEntity *activator ) { + gameLocal.sessionCommand = spawnArgs.GetString( "command" ); +} + + +/* +=============================================================================== + +idTarget_EndLevel + +Just a modified form of idTarget_SessionCommand +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EndLevel::Event_Activate +================ +*/ +void idTarget_EndLevel::Event_Activate( idEntity *activator ) { + idStr nextMap; + +#ifdef ID_DEMO_BUILD + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endofDemo"; + return; + } +#else + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endOfGame"; + return; + } +#endif + if ( !spawnArgs.GetString( "nextMap", "", nextMap ) ) { + gameLocal.Printf( "idTarget_SessionCommand::Event_Activate: no nextMap key\n" ); + return; + } + + if ( spawnArgs.GetInt( "devmap", "0" ) ) { + gameLocal.sessionCommand = "devmap "; // only for special demos + } else { + gameLocal.sessionCommand = "map "; + } + + gameLocal.sessionCommand += nextMap; + +// RAVEN BEGIN +// jscott: additional info for multiple maps + const char* entityFilter; + if( spawnArgs.GetString( "entityFilter", "", &entityFilter ) && *entityFilter ) { + gameLocal.sessionCommand += " "; + gameLocal.sessionCommand += entityFilter; + } +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_WaitForButton ) + EVENT( EV_Activate, idTarget_WaitForButton::Event_Activate ) +END_CLASS + +/* +================ +idTarget_WaitForButton::Event_Activate +================ +*/ +void idTarget_WaitForButton::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTarget_WaitForButton::Think +================ +*/ +void idTarget_WaitForButton::Think( void ) { + idPlayer *player; + + if ( thinkFlags & TH_THINK ) { + player = gameLocal.GetLocalPlayer(); + if ( player && ( !player->oldButtons & BUTTON_ATTACK ) && ( player->usercmd.buttons & BUTTON_ATTACK ) ) { + player->usercmd.buttons &= ~BUTTON_ATTACK; + BecomeInactive( TH_THINK ); + ActivateTargets( player ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetGlobalShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetGlobalShaderTime ) +EVENT( EV_Activate, idTarget_SetGlobalShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetGlobalShaderTime::Event_Activate +================ +*/ +void idTarget_SetGlobalShaderTime::Event_Activate( idEntity *activator ) { + int parm = spawnArgs.GetInt( "globalParm" ); + float time = -MS2SEC( gameLocal.time ); + if ( parm >= 0 && parm < MAX_GLOBAL_SHADER_PARMS ) { + gameLocal.globalShaderParms[parm] = time; + } +} + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderParm ) + EVENT( EV_Activate, idTarget_SetShaderParm::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderParm::Event_Activate +================ +*/ +void idTarget_SetShaderParm::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float value; + idVec3 color; + int parmnum; + + // set the color on the targets + if ( spawnArgs.GetVector( "_color", "1 1 1", color ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + } + } + } + + // set any shader parms on the targets + for( parmnum = 0; parmnum < MAX_ENTITY_SHADER_PARMS; parmnum++ ) { + if ( spawnArgs.GetFloat( va( "shaderParm%d", parmnum ), "0", value ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderTime ) + EVENT( EV_Activate, idTarget_SetShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderTime::Event_Activate +================ +*/ +void idTarget_SetShaderTime::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float time; + + time = -MS2SEC( gameLocal.time ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( SHADERPARM_TIMEOFFSET, time ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SetLightParm( SHADERPARM_TIMEOFFSET, time ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_FadeEntity ) + EVENT( EV_Activate, idTarget_FadeEntity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_FadeEntity::idTarget_FadeEntity +================ +*/ +idTarget_FadeEntity::idTarget_FadeEntity( void ) { + fadeFrom.Zero(); + fadeStart = 0; + fadeEnd = 0; +} + +/* +================ +idTarget_FadeEntity::Save +================ +*/ +void idTarget_FadeEntity::Save( idSaveGame *savefile ) const { + savefile->WriteVec4( fadeFrom ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Restore +================ +*/ +void idTarget_FadeEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadVec4( fadeFrom ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Event_Activate +================ +*/ +void idTarget_FadeEntity::Event_Activate( idEntity *activator ) { + idEntity *ent; + int i; + + if ( !targets.Num() ) { + return; + } + + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->GetColor( fadeFrom ); + break; + } + } + + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "fadetime" ) ); +} + +/* +================ +idTarget_FadeEntity::Think +================ +*/ +void idTarget_FadeEntity::Think( void ) { + int i; + idEntity *ent; + idVec4 color; + idVec4 fadeTo; + float frac; + + if ( thinkFlags & TH_THINK ) { + GetColor( fadeTo ); + if ( gameLocal.time >= fadeEnd ) { + color = fadeTo; + BecomeInactive( TH_THINK ); + } else { + frac = ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ); + color.Lerp( fadeFrom, fadeTo, frac ); + } + + // set the color on the targets + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeIn ) + EVENT( EV_Activate, idTarget_LightFadeIn::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeIn::Event_Activate +================ +*/ +void idTarget_LightFadeIn::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeIn( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeOut ) + EVENT( EV_Activate, idTarget_LightFadeOut::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeOut::Event_Activate +================ +*/ +void idTarget_LightFadeOut::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeOut( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Give ) + EVENT( EV_Activate, idTarget_Give::Event_Activate ) +// RAVEN BEGIN +// abahr: + EVENT( EV_PostSpawn, idTarget_Give::Event_PostSpawn ) +// RAVEN END +END_CLASS + +/* +================ +idTarget_Give::Spawn +================ +*/ +void idTarget_Give::Spawn( void ) { +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + if ( spawnArgs.GetBool( "onSpawn" ) ) { + PostEventMS( &EV_PostSpawn, 50 ); + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +#endif + } +// RAVEN END + + // precache decls + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), false, false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +} + +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs +/* +================ +idTarget_Give::Event_PostSpawn +================ +*/ +void idTarget_Give::Event_PostSpawn() { + ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); +} +// RAVEN END + +/* +================ +idTarget_Give::Event_Activate +================ +*/ +void idTarget_Give::Event_Activate( idEntity *activator ) { + + if ( spawnArgs.GetBool( "development" ) && developer.GetInteger() == 0 ) { + return; + } + + static int giveNum = 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( dict ) { + idDict d2; + d2.Copy( *dict ); + d2.Set( "name", va( "givenitem_%i", giveNum++ ) ); + idEntity *ent = NULL; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.SpawnEntityDef( d2, &ent ) && ent && ent->IsType( idItem::GetClassType() ) ) { +// RAVEN END + idItem *item = static_cast(ent); + item->GiveToPlayer( gameLocal.GetLocalPlayer() ); + item->PostEventMS ( &EV_Remove, 0 ); + + // rules are that if we are given a weapon by a character, we are supposed to switch to it regardless of + // whether auto-switch is on or not. + if ( !gameLocal.isMultiplayer && !player->GetUserInfo()->GetBool( "ui_autoSwitch" ) && !spawnArgs.GetBool( "onSpawn" )) { + const idKeyValue *kv = ent->spawnArgs.FindKey( "weaponclass" ); + if ( kv ) { + // does player already have this weapon selected? + if ( player->weapon && idStr::Icmp(player->weapon->GetClassname(), kv->GetValue())) { + kv = ent->spawnArgs.FindKey( "inv_weapon" ); + if ( kv ) { + // nope, so attempt to switch to this weapon + player->SelectWeapon(kv->GetValue()); + } + } + } + } + } + } + kv = spawnArgs.MatchPrefix( "item", kv ); + } + } +} + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveEmail ) +EVENT( EV_Activate, idTarget_GiveEmail::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Spawn +================ +*/ +void idTarget_GiveEmail::Spawn( void ) { +} + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveEmail::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using email +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + const idDeclPDA *pda = player->GetPDA(); + if ( pda ) { + player->GiveEmail( spawnArgs.GetString( "email" ) ); + } else { + player->ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_PDANeeded" ), true ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetModel ) + EVENT( EV_Activate, idTarget_SetModel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetModel::Spawn +================ +*/ +void idTarget_SetModel::Spawn( void ) { + const char *model; + + model = spawnArgs.GetString( "newmodel" ); + if ( declManager->FindType( DECL_MODELDEF, model, false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( model ); + // precache .cm files only + collisionModelManager->PreCacheModel( gameLocal.GetMapName(), model ); + } +} + +/* +================ +idTarget_SetModel::Event_Activate +================ +*/ +void idTarget_SetModel::Event_Activate( idEntity *activator ) { + for( int i = 0; i < targets.Num(); i++ ) { + idEntity *ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetModel( spawnArgs.GetString( "newmodel" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +const idEventDef EV_RestoreInfluence( "" ); +const idEventDef EV_GatherEntities( "" ); +const idEventDef EV_Flash( "", "fd" ); +const idEventDef EV_ClearFlash( "", "f" ); + +CLASS_DECLARATION( idTarget, idTarget_SetInfluence ) + EVENT( EV_Activate, idTarget_SetInfluence::Event_Activate ) + EVENT( EV_RestoreInfluence, idTarget_SetInfluence::Event_RestoreInfluence ) + EVENT( EV_GatherEntities, idTarget_SetInfluence::Event_GatherEntities ) + EVENT( EV_Flash, idTarget_SetInfluence::Event_Flash ) + EVENT( EV_ClearFlash, idTarget_SetInfluence::Event_ClearFlash ) +END_CLASS + +/* +================ +idTarget_SetInfluence::idTarget_SetInfluence +================ +*/ +idTarget_SetInfluence::idTarget_SetInfluence( void ) { + flashIn = 0.0f; + flashOut = 0.0f; + delay = 0.0f; + switchToCamera = NULL; + soundFaded = false; + restoreOnTrigger = false; +} + +/* +================ +idTarget_SetInfluence::Save +================ +*/ +void idTarget_SetInfluence::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( lightList.Num() ); + for( i = 0; i < lightList.Num(); i++ ) { + savefile->WriteInt( lightList[ i ] ); + } + + savefile->WriteInt( guiList.Num() ); + for( i = 0; i < guiList.Num(); i++ ) { + savefile->WriteInt( guiList[ i ] ); + } + + savefile->WriteInt( soundList.Num() ); + for( i = 0; i < soundList.Num(); i++ ) { + savefile->WriteInt( soundList[ i ] ); + } + + savefile->WriteInt( genericList.Num() ); + for( i = 0; i < genericList.Num(); i++ ) { + savefile->WriteInt( genericList[ i ] ); + } + + savefile->WriteFloat( flashIn ); + savefile->WriteFloat( flashOut ); + + savefile->WriteFloat( delay ); + + savefile->WriteString( flashInSound ); + savefile->WriteString( flashOutSound ); + + savefile->WriteObject( switchToCamera ); + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); + + savefile->WriteBool( soundFaded ); + savefile->WriteBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Restore +================ +*/ +void idTarget_SetInfluence::Restore( idRestoreGame *savefile ) { + int i, num; + int itemNum; + float set; + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + lightList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + guiList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + soundList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + genericList.Append( itemNum ); + } + + savefile->ReadFloat( flashIn ); + savefile->ReadFloat( flashOut ); + + savefile->ReadFloat( delay ); + + savefile->ReadString( flashInSound ); + savefile->ReadString( flashOutSound ); + + savefile->ReadObject( reinterpret_cast( switchToCamera ) ); + + savefile->ReadFloat( set ); + fovSetting.SetStartTime( set ); + savefile->ReadFloat( set ); + fovSetting.SetDuration( set ); + savefile->ReadFloat( set ); + fovSetting.SetStartValue( set ); + savefile->ReadFloat( set ); + fovSetting.SetEndValue( set ); + + savefile->ReadBool( soundFaded ); + savefile->ReadBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Spawn +================ +*/ +void idTarget_SetInfluence::Spawn() { + PostEventMS( &EV_GatherEntities, 0 ); + flashIn = spawnArgs.GetFloat( "flashIn", "0" ); + flashOut = spawnArgs.GetFloat( "flashOut", "0" ); + flashInSound = spawnArgs.GetString( "snd_flashin" ); + flashOutSound = spawnArgs.GetString( "snd_flashout" ); + delay = spawnArgs.GetFloat( "delay" ); + soundFaded = false; + restoreOnTrigger = false; + + // always allow during cinematics + cinematic = true; +} + +/* +================ +idTarget_SetInfluence::Event_Flash +================ +*/ +void idTarget_SetInfluence::Event_Flash( float flash, int out ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( idVec4( 1, 1, 1, 1 ), flash ); + const idSoundShader *shader = NULL; + if ( !out && flashInSound.Length() ){ + shader = declManager->FindSound( flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( out && ( flashOutSound.Length() || flashInSound.Length() ) ) { + shader = declManager->FindSound( flashOutSound.Length() ? flashOutSound : flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + PostEventSec( &EV_ClearFlash, flash, flash ); +} + + +/* +================ +idTarget_SetInfluence::Event_ClearFlash +================ +*/ +void idTarget_SetInfluence::Event_ClearFlash( float flash ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( vec4_zero , flash ); +} +/* +================ +idTarget_SetInfluence::Event_GatherEntities +================ +*/ +void idTarget_SetInfluence::Event_GatherEntities() { + int i, listedEntities; + idEntity *entityList[ MAX_GENTITIES ]; + + bool lights = spawnArgs.GetBool( "effect_lights" ); + bool sounds = spawnArgs.GetBool( "effect_sounds" ); + bool guis = spawnArgs.GetBool( "effect_guis" ); + bool models = spawnArgs.GetBool( "effect_models" ); + bool vision = spawnArgs.GetBool( "effect_vision" ); + bool targetsOnly = spawnArgs.GetBool( "targetsOnly" ); + + lightList.Clear(); + guiList.Clear(); + soundList.Clear(); + + if ( spawnArgs.GetBool( "effect_all" ) ) { + lights = sounds = guis = models = vision = true; + } + + if ( targetsOnly ) { + listedEntities = targets.Num(); + for ( i = 0; i < listedEntities; i++ ) { + entityList[i] = targets[i].GetEntity(); + } + } else { + float radius = spawnArgs.GetFloat( "radius" ); + listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + } + + for( i = 0; i < listedEntities; i++ ) { + idEntity *ent = entityList[ i ]; + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( lights && ent->IsType( idLight::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + lightList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( sounds && ent->IsType( idSound::GetClassType() ) && ent->spawnArgs.FindKey( "snd_demonic" ) ) { +// RAVEN END + soundList.Append( ent->entityNumber ); + continue; + } + if ( guis && ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ 0 ] && ent->spawnArgs.FindKey( "gui_demonic" ) ) { + guiList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idStaticEntity::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + genericList.Append( ent->entityNumber ); + continue; + } + } + } + idStr temp; + temp = spawnArgs.GetString( "switchToView" ); + switchToCamera = ( temp.Length() ) ? gameLocal.FindEntity( temp ) : NULL; + +} + +/* +================ +idTarget_SetInfluence::Event_Activate +================ +*/ +void idTarget_SetInfluence::Event_Activate( idEntity *activator ) { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + const char *parm; + const char *skin; + bool update; + idVec3 color; + idVec4 colorTo; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + + if ( spawnArgs.GetBool( "triggerActivate" ) ) { + if ( restoreOnTrigger ) { + ProcessEvent( &EV_RestoreInfluence ); + restoreOnTrigger = false; + return; + } + restoreOnTrigger = true; + } + + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + + if ( delay > 0.0f ) { + PostEventSec( &EV_Activate, delay, activator ); + delay = 0.0f; + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + return; + } else if ( fadeTime && !soundFaded ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( activator ); + } + + if ( flashIn ) { + PostEventSec( &EV_Flash, 0.0f, flashIn, 0 ); + } + + parm = spawnArgs.GetString( "snd_influence" ); + if ( parm && *parm ) { + PostEventSec( &EV_StartSoundShader, flashIn, parm, SND_CHANNEL_ANY ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventSec( &EV_Activate, flashIn + 0.05f, this ); + } + +// RAVEN BEGIN +// mekberg: allow for initial fov and both fovs. + int fov = spawnArgs.GetInt( "fov" ); + int fovInitial = spawnArgs.GetInt( "fov_initial" ); + if ( fov && fovInitial) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, fov ); + BecomeActive( TH_THINK ); + } else if ( fov ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), player->DefaultFov(), fov ); + BecomeActive( TH_THINK ); + } else if ( fovInitial ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, player->DefaultFov() ); + BecomeActive( TH_THINK ); + } +// RAVEN END + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "color_demonic" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + parm = light->spawnArgs.GetString( "mat_demonic" ); + if ( parm && *parm ) { + light->SetShader( parm ); + } + + color = light->spawnArgs.GetVector( "_color" ); + color = light->spawnArgs.GetVector( "color_demonic", color.ToString() ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + parm = sound->spawnArgs.GetString( "snd_demonic" ); + if ( parm && *parm ) { + if ( sound->spawnArgs.GetBool( "overlayDemonic" ) ) { + sound->StartSound( "snd_demonic", SND_CHANNEL_DEMONIC, 0, false, NULL ); + } else { + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( parm ); + } + } + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || ent->GetRenderEntity() == NULL ) { + continue; + } + update = false; + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] && ent->spawnArgs.FindKey( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ) ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ), true ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + + } + + player->SetInfluenceLevel( spawnArgs.GetInt( "influenceLevel" ) ); + + int snapAngle = spawnArgs.GetInt( "snapAngle" ); + if ( snapAngle ) { + idAngles ang( 0, snapAngle, 0 ); + player->SetViewAngles( ang ); + player->SetAngles( ang ); + } + + if ( spawnArgs.GetBool( "effect_vision" ) ) { + parm = spawnArgs.GetString( "mtrVision" ); + skin = spawnArgs.GetString( "skinVision" ); + player->SetInfluenceView( parm, skin, spawnArgs.GetInt( "visionRadius" ), this ); + } + + parm = spawnArgs.GetString( "mtrWorld" ); + if ( parm && *parm ) { + gameLocal.SetGlobalMaterial( declManager->FindMaterial( parm ) ); + } + + if ( !restoreOnTrigger ) { + PostEventMS( &EV_RestoreInfluence, SEC2MS( spawnArgs.GetFloat( "time" ) ) ); + } +} + +/* +================ +idTarget_SetInfluence::Think +================ +*/ +void idTarget_SetInfluence::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + if ( !spawnArgs.GetBool( "leaveFOV" ) ) { + player->SetInfluenceFov( 0 ); + } + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +================ +idTarget_SetInfluence::Event_RestoreInfluence +================ +*/ +void idTarget_SetInfluence::Event_RestoreInfluence() { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + bool update; + idVec3 color; + idVec4 colorTo; + + if ( flashOut ) { + PostEventSec( &EV_Flash, 0.0f, flashOut, 1 ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventMS( &EV_Activate, 0.0f, this ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + colorTo.Set( 1.0f, 1.0f, 1.0f, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + if ( !light->spawnArgs.GetBool( "leave_demonic_mat" ) ) { + const char *texture = light->spawnArgs.GetString( "texture", "lights/squarelight1" ); + light->SetShader( texture ); + } + color = light->spawnArgs.GetVector( "_color" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( sound->spawnArgs.GetString( "s_shader" ) ); + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || GetRenderEntity() == NULL ) { + continue; + } + update = false; + for( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui" : va( "gui%d", j+1 ) ) ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceLevel( 0 ); + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + player->SetInfluenceFov( 0 ); + gameLocal.SetGlobalMaterial( NULL ); + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime / 2.0f ); + } + +} + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetKeyVal ) + EVENT( EV_Activate, idTarget_SetKeyVal::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetKeyVal::Event_Activate +================ +*/ +void idTarget_SetKeyVal::Event_Activate( idEntity *activator ) { + int i; + idStr key, val; + idEntity *ent; + const idKeyValue *kv; + int n; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + kv = spawnArgs.MatchPrefix("keyval"); + while ( kv ) { + n = kv->GetValue().Find( ";" ); + if ( n > 0 ) { + key = kv->GetValue().Left( n ); + val = kv->GetValue().Right( kv->GetValue().Length() - n - 1 ); + ent->spawnArgs.Set( key, val ); + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time ); + } + } + } + } + kv = spawnArgs.MatchPrefix( "keyval", kv ); + } + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetFov ) + EVENT( EV_Activate, idTarget_SetFov::Event_Activate ) +END_CLASS + + +/* +================ +idTarget_SetFov::Save +================ +*/ +void idTarget_SetFov::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); +} + +/* +================ +idTarget_SetFov::Restore +================ +*/ +void idTarget_SetFov::Restore( idRestoreGame *savefile ) { + float setting; + + savefile->ReadFloat( setting ); + fovSetting.SetStartTime( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetDuration( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetStartValue( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetEndValue( setting ); + + fovSetting.GetCurrentValue( gameLocal.time ); +} + +/* +================ +idTarget_SetFov::Event_Activate +================ +*/ +void idTarget_SetFov::Event_Activate( idEntity *activator ) { + // always allow during cinematics + cinematic = true; + + idPlayer *player = gameLocal.GetLocalPlayer(); + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "time" ) ), player ? player->DefaultFov() : g_fov.GetFloat(), spawnArgs.GetFloat( "fov" ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTarget_SetFov::Think +================ +*/ +void idTarget_SetFov::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + player->SetInfluenceFov( 0.0f ); + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetPrimaryObjective ) + EVENT( EV_Activate, idTarget_SetPrimaryObjective::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetPrimaryObjective::Event_Activate +================ +*/ +void idTarget_SetPrimaryObjective::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->objectiveSystem ) { + player->objectiveSystem->SetStateString( "missionobjective", spawnArgs.GetString( "text", common->GetLocalizedString( "#str_104253" ) ) ); + } +} + +// RAVEN BEGIN +// bdube: added database entry trigger +// twhitaker: removed database entry trigger +/* +=============================================================================== + +rvTarget_AddDatabaseEntry + +=============================================================================== +*/ +/* +CLASS_DECLARATION( idTarget, rvTarget_AddDatabaseEntry ) + EVENT( EV_Activate, rvTarget_AddDatabaseEntry::Event_Activate ) +END_CLASS +*/ +/* +================ +rvTarget_AddDatabaseEntry::Event_Activate +================ +*/ +/*void rvTarget_AddDatabaseEntry::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveDatabaseEntry ( gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_db", "" ) ) ); + } +} +*/ +// jshepard: secret area trigger +/* +=============================================================================== + +rvTarget_SecretArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_SecretArea ) + EVENT( EV_Activate, rvTarget_SecretArea::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_SecretArea::Event_Activate +================ +*/ +void rvTarget_SecretArea::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->DiscoverSecretArea( spawnArgs.GetString ( "description" )); + } +} + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +const idEventDef EV_SetShieldPercent( "setShieldPercent", "f" ); +const idEventDef EV_SetBossMaxHealth( "setMaxBossHealth", "f" ); +const idEventDef EV_AllowShieldBar( "allowShieldBar", "f" ); +const idEventDef EV_AllowShieldWarningBar( "allowShieldWarnBar", "f" ); + + +CLASS_DECLARATION( idTarget, rvTarget_BossBattle ) + EVENT( EV_Activate, rvTarget_BossBattle::Event_Activate ) + EVENT( EV_SetShieldPercent, rvTarget_BossBattle::Event_SetShieldPercent ) + EVENT( EV_SetBossMaxHealth, rvTarget_BossBattle::Event_SetBossMaxHealth ) + EVENT( EV_AllowShieldBar, rvTarget_BossBattle::Event_AllowShieldBar ) + EVENT( EV_AllowShieldWarningBar, rvTarget_BossBattle::Event_AllowShieldWarningBar ) +END_CLASS + +/* +================ +rvTarget_BossBattle::Event_Activate +================ +*/ +void rvTarget_BossBattle::Event_Activate( idEntity *activator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + idEntity* enemy = gameLocal.FindEntity ( spawnArgs.GetString ( "target" ) ); + if ( player && enemy ) { + player->StartBossBattle ( enemy ); + } + + StartSound ( "snd_activate", SND_CHANNEL_ANY, 0, false, NULL ); +} + + +/* +================ +rvTarget_BossBattle::Event_AllowShieldBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldBar" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldBar" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_AllowShieldWarningBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldWarningBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldWarn" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldWarn" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_SetShieldPercent +================ +*/ +void rvTarget_BossBattle::Event_SetShieldPercent( float percent ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } +} + +/* +================ +rvTarget_BossBattle::Event_SetMaxBossHealth +================ +*/ +void rvTarget_BossBattle::Event_SetBossMaxHealth( float f ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + + hud->SetStateInt ( "boss_maxhealth",f ); + } +} + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +const idEventDef EV_LaunchProjectile( "launchProjectile", "e" ); +CLASS_DECLARATION( idTarget, rvTarget_LaunchProjectile ) + EVENT( EV_Activate, rvTarget_LaunchProjectile::Event_Activate ) + EVENT( EV_LaunchProjectile, rvTarget_LaunchProjectile::Event_LaunchProjectile ) +END_CLASS + +/* +================ +rvTarget_LaunchProjectile::Spawn +================ +*/ +void rvTarget_LaunchProjectile::Spawn( void ) { + if ( spawnArgs.GetBool( "start_on" ) ) { + PostEventMS( &EV_Activate, 0, this ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_LaunchProjectile +================ +*/ +void rvTarget_LaunchProjectile::Event_LaunchProjectile( idEntity *activator ) { + const idDict* projectileDict; + idEntity* ent; + + projectileDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ), false ); + if ( !projectileDict ) { + gameLocal.Error ( "Could not find entityDef '%s' for launch projectile target '%s'", spawnArgs.GetString ( "def_projectile" ), GetName() ); + } + + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + gameLocal.Error( "Could not spawn entityDef '%s'", projectileDict->GetString( "classname" ) ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idProjectile", ent->GetClassname() ); + } + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + idProjectile* proj = static_cast(ent); + + proj->Create( this, GetPhysics()->GetOrigin(), dir, this ); + proj->Launch( GetPhysics()->GetOrigin(), dir, GetPhysics()->GetLinearVelocity(), 0.0f, 1.0f ); + + + if ( targets.Num() && proj->IsType ( idGuidedProjectile::GetClassType ( ) ) ) { + static_cast(proj)->GuideTo ( targets[0] ); + } + + if ( spawnArgs.GetFloat( "loop_interval" ) ) + { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "loop_interval" ), activator ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_Activate +================ +*/ +void rvTarget_LaunchProjectile::Event_Activate( idEntity *activator ) { + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + if ( spawnArgs.GetString( "fx_launch", NULL ) ) + { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_launch" ), GetPhysics()->GetOrigin(), dir.ToMat3() ); + } + + if ( spawnArgs.GetFloat( "delay" ) ) + { + PostEventSec( &EV_LaunchProjectile, spawnArgs.GetFloat( "delay" ), activator ); + return; + } + ProcessEvent( &EV_LaunchProjectile, activator ); +} + +/* +=============================================================================== + +rvTarget_ExitArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_ExitAreaAlert ) + EVENT( EV_Activate, rvTarget_ExitAreaAlert::Event_Activate ) +END_CLASS + +void rvTarget_ExitAreaAlert::Event_Activate( idEntity *activator ) { + gameLocal.UpdateEndLevel(); +} + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ + +int CompareAmmoData( const void* ammo1, const void* ammo2) { + if ((( ammodata_t *)ammo2)->percentFull > (( ammodata_t *)ammo1)->percentFull ) { + return -1; + } else { + return 1; + } +} + +CLASS_DECLARATION( idTarget, rvTarget_AmmoStash ) + EVENT( EV_Activate, rvTarget_AmmoStash::Event_Activate ) +END_CLASS + +void rvTarget_AmmoStash::Event_Activate( idEntity *activator ) { + + const idKeyValue* kv; + idPlayer* player; + int typeCount; + idEntity* entAmmo; + idItem* item; + idDict args; + + + player = gameLocal.GetLocalPlayer(); + + //set up the array + memset( AmmoArray, 0, sizeof( ammodata_t) * AMMO_ARRAY_SIZE); + for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + AmmoArray[ t ].percentFull = 2.0f; + } + + // we only check for certain types of ammo. + kv = spawnArgs.MatchPrefix ( "def_ammo", NULL ); + if ( kv ) { + kv->GetValue(); + for ( typeCount = 0; typeCount < AMMO_ARRAY_SIZE && kv; kv = spawnArgs.MatchPrefix ( "def_ammo", kv ) ) { + + AmmoArray[ typeCount ].ammoName = kv->GetValue(); + AmmoArray[ typeCount ].ammoIndex = player->inventory.AmmoIndexForAmmoClass( AmmoArray[ typeCount ].ammoName.c_str() ); + + //check and see how much ammo this weapon can hold... + AmmoArray[ typeCount ].ammoMax = player->inventory.MaxAmmoForAmmoClass( player, AmmoArray[ typeCount ].ammoName.c_str() ); + + //and the current amount + AmmoArray[ typeCount ].ammoCount = player->inventory.HasAmmo( AmmoArray[ typeCount ].ammoIndex, 1); + + if( AmmoArray[ typeCount ].ammoMax > 0) { + AmmoArray[ typeCount ].percentFull = float( AmmoArray[ typeCount ].ammoCount / AmmoArray[ typeCount ].ammoMax ); + } else { + AmmoArray[ typeCount ].percentFull = 2.0f; + } + + //increment + typeCount++; + } + + } else { + gameLocal.Warning("Bad ammo data on rvTarget_AmmoStash '%s'", this->GetName()); + return; + } + + //TEMP: print out the ammo counts + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + //sort the types of ammo by need. The most-needed ammo will be placed. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + int i; + //run through our targets until we have no more targets. + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + //drop the most needed ammo at ent's location. + args.Set ( "origin", ent->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", ent->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", AmmoArray[ 0 ].ammoName ); + + gameLocal.SpawnEntityDef ( args, &entAmmo ); + + //now assume that the item was picked up. + item = static_cast< idItem* >(entAmmo); + kv = entAmmo->spawnArgs.MatchPrefix ( "inv_ammo", NULL ); + + if( kv ) { + + //add the ammo as if the player picked it up + AmmoArray[ 0 ].ammoCount += atoi(kv->GetValue().c_str()); + AmmoArray[ 0 ].percentFull = (float)AmmoArray[ 0 ].ammoCount / (float)AmmoArray[ 0 ].ammoMax; + + //resort the ammo. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + } else { + gameLocal.Warning("Bad ammo data on target_ammostash '%s'", this->GetName()); + return; + } + } + +} + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_TetherAI ) + EVENT( EV_Activate, rvTarget_TetherAI::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_TetherAI::Event_Activate +================ +*/ +void rvTarget_TetherAI::Event_Activate( idEntity *activator ) { + int i; + if ( activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + } + } +} +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_Nailable ) +END_CLASS +void rvTarget_Nailable::Spawn() { + + //InitDefaultPhysics( origin, axis ); + +// int contents = GetPhysics()->GetContents(); +// contents &= CONTENTS_BODY; +// contents &= CONTENTS_SOLID; + GetPhysics()->SetContents( MASK_SHOT_BOUNDINGBOX ); + +} + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LockDoor ) + EVENT( EV_Activate, idTarget_LockDoor::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LockDoor::Event_Activate +================ +*/ +void idTarget_LockDoor::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + int lock; + + lock = spawnArgs.GetInt( "locked", "1" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_CallObjectFunction ) + EVENT( EV_Activate, idTarget_CallObjectFunction::Event_Activate ) +END_CLASS + +/* +================ +idTarget_CallObjectFunction::Event_Activate +================ +*/ +void idTarget_CallObjectFunction::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + + funcName = spawnArgs.GetString( "call" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ent->scriptObject.HasObject() ) { + func = ent->scriptObject.GetFunction( funcName ); + if ( !func ) { + gameLocal.Error( "Function '%s' not found on entity '%s' for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' on entity '%s' has the wrong number of parameters for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( !ent->scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' on entity '%s' is the wrong type for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + // create a thread and call the function +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableLevelWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableLevelWeapons ) + EVENT( EV_Activate, idTarget_EnableLevelWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableLevelWeapons::Event_Activate +================ +*/ +void idTarget_EnableLevelWeapons::Event_Activate( idEntity *activator ) { + int i; + const char *weap; + + gameLocal.world->spawnArgs.SetBool( "no_Weapons", spawnArgs.GetBool( "disable" ) ); + + if ( spawnArgs.GetBool( "disable" ) ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_DisableWeapon ); + } + } + } else { + weap = spawnArgs.GetString( "weapon" ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_EnableWeapon ); + if ( weap && weap[ 0 ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, weap ); + } + } + } + } +} + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +const idEventDef EV_TipOff( "" ); +extern const idEventDef EV_GetPlayerPos( "" ); + +CLASS_DECLARATION( idTarget, idTarget_Tip ) + EVENT( EV_Activate, idTarget_Tip::Event_Activate ) + EVENT( EV_TipOff, idTarget_Tip::Event_TipOff ) + EVENT( EV_GetPlayerPos, idTarget_Tip::Event_GetPlayerPos ) +END_CLASS + + +/* +================ +idTarget_Tip::idTarget_Tip +================ +*/ +idTarget_Tip::idTarget_Tip( void ) { + playerPos.Zero(); +} + +/* +================ +idTarget_Tip::Spawn +================ +*/ +void idTarget_Tip::Spawn( void ) { +} + +/* +================ +idTarget_Tip::Save +================ +*/ +void idTarget_Tip::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Restore +================ +*/ +void idTarget_Tip::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_GetPlayerPos( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_TipOff, 100 ); + } +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->IsTipVisible() ) { + PostEventSec( &EV_Activate, 5.1f, activator ); + return; + } + player->ShowTip( spawnArgs.GetString( "text_title" ), spawnArgs.GetString( "text_tip" ), false ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } +} + +/* +================ +idTarget_Tip::Event_TipOff +================ +*/ +void idTarget_Tip::Event_TipOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 96.0f ) { + player->HideTip(); + } else { + PostEventMS( &EV_TipOff, 100 ); + } + } +} + + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveSecurity ) +EVENT( EV_Activate, idTarget_GiveSecurity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveSecurity::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using security +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveSecurity( spawnArgs.GetString( "text_security" ) ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RemoveWeapons ) +EVENT( EV_Activate, idTarget_RemoveWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RemoveWeapons::Event_Activate +================ +*/ +void idTarget_RemoveWeapons::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "weapon", NULL ); + while ( kv ) { + player->RemoveWeapon( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "weapon", kv ); + } +// RAVEN BEGIN +// bdube: default to initial weapon + player->SelectWeapon( 0, true ); +// RAVEN END + } + } +} + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LevelTrigger ) +EVENT( EV_Activate, idTarget_LevelTrigger::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LevelTrigger::Event_Activate +================ +*/ +void idTarget_LevelTrigger::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + player->SetLevelTrigger( spawnArgs.GetString( "levelName" ), spawnArgs.GetString( "triggerName" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableStamina ) +EVENT( EV_Activate, idTarget_EnableStamina::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableStamina::Event_Activate +================ +*/ +void idTarget_EnableStamina::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + if ( spawnArgs.GetBool( "enable" ) ) { + pm_stamina.SetFloat( player->spawnArgs.GetFloat( "pm_stamina" ) ); + } else { + pm_stamina.SetFloat( 0.0f ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ + +const idEventDef EV_RestoreVolume( "" ); +CLASS_DECLARATION( idTarget, idTarget_FadeSoundClass ) +EVENT( EV_Activate, idTarget_FadeSoundClass::Event_Activate ) +EVENT( EV_RestoreVolume, idTarget_FadeSoundClass::Event_RestoreVolume ) +END_CLASS + +/* +================ +idTarget_FadeSoundClass::Event_Activate +================ +*/ +void idTarget_FadeSoundClass::Event_Activate( idEntity *activator ) { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + float fadeDuration = spawnArgs.GetFloat( "fadeDuration" ); + int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, fadeClass, spawnArgs.GetBool( "fadeIn" ) ? fadeDB : 0.0f - fadeDB, fadeTime ); + if ( fadeDuration ) { + PostEventSec( &EV_RestoreVolume, fadeDuration ); + } + } +} + +/* +================ +idTarget_FadeSoundClass::Event_RestoreVolume +================ +*/ +void idTarget_FadeSoundClass::Event_RestoreVolume() { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); +// int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // restore volume + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); +} + diff --git a/source/mpgame/Target.h b/source/mpgame/Target.h new file mode 100644 index 0000000..ac0398a --- /dev/null +++ b/source/mpgame/Target.h @@ -0,0 +1,704 @@ + +#ifndef __GAME_TARGET_H__ +#define __GAME_TARGET_H__ + +//Used to compare two ammoData structs and see who has more. +int CompareAmmoData( const void* ammo1, const void* ammo2); + + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +class idTarget : public idEntity { +public: + CLASS_PROTOTYPE( idTarget ); +}; + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +class idTarget_Remove : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Remove ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +class idTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Show ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +class idTarget_Damage : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Damage ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +class idTarget_SessionCommand : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SessionCommand ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_EndLevel + +=============================================================================== +*/ + +class idTarget_EndLevel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + +private: + void Event_Activate( idEntity *activator ); + +}; + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +class idTarget_WaitForButton : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_WaitForButton ); + + void Think( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetGlobalShaderTime + +=============================================================================== +*/ + +class idTarget_SetGlobalShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetGlobalShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +class idTarget_SetShaderParm : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderParm ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +class idTarget_SetShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +class idTarget_FadeEntity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeEntity ); + + idTarget_FadeEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idVec4 fadeFrom; + int fadeStart; + int fadeEnd; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +class idTarget_LightFadeIn : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeIn ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +class idTarget_LightFadeOut : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeOut ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +class idTarget_Give : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Give ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + void Event_PostSpawn(); +// RAVEN END +}; + + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +class idTarget_GiveEmail : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveEmail ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +class idTarget_SetModel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetModel ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +class idTarget_SetInfluence : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetInfluence ); + + idTarget_SetInfluence( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreInfluence(); + void Event_GatherEntities(); + void Event_Flash( float flash, int out ); + void Event_ClearFlash( float flash ); + void Think( void ); + + idList lightList; + idList guiList; + idList soundList; + idList genericList; + float flashIn; + float flashOut; + float delay; + idStr flashInSound; + idStr flashOutSound; + idEntity * switchToCamera; + idInterpolatefovSetting; + bool soundFaded; + bool restoreOnTrigger; +}; + + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +class idTarget_SetKeyVal : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetKeyVal ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +class idTarget_SetFov : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetFov ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idInterpolate fovSetting; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +class idTarget_SetPrimaryObjective : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetPrimaryObjective ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: added player database +/* +=============================================================================== + +idTarget_AddDatabaseEntry + +=============================================================================== +*/ + +class rvTarget_AddDatabaseEntry : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AddDatabaseEntry ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// jshepard: secret area discovery + +/* +=============================================================================== + +idTarget_SecretArea + +=============================================================================== +*/ + +class rvTarget_SecretArea : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_SecretArea ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +class rvTarget_BossBattle : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_BossBattle ); + +private: + void Event_Activate( idEntity *activator ); + void Event_SetShieldPercent( float percent ); + void Event_SetBossMaxHealth( float f ); + void Event_AllowShieldBar( float activate ); + void Event_AllowShieldWarningBar( float activate ); + +}; + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +class rvTarget_TetherAI : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_TetherAI ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +class rvTarget_Nailable : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_Nailable ); + +private: + void Spawn( void ); +}; + + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +class idTarget_CallObjectFunction : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_CallObjectFunction ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_EnableLevelWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableLevelWeapons ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +class idTarget_Tip : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Tip ); + + idTarget_Tip( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idVec3 playerPos; + + void Event_Activate( idEntity *activator ); + void Event_TipOff( void ); + void Event_GetPlayerPos( void ); +}; + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ +class idTarget_GiveSecurity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveSecurity ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ +class idTarget_RemoveWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RemoveWeapons ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ +class idTarget_LevelTrigger : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LevelTrigger ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ +class idTarget_EnableStamina : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableStamina ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ +class idTarget_FadeSoundClass : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeSoundClass ); +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreVolume(); +}; + + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +class rvTarget_LaunchProjectile : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_LaunchProjectile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_LaunchProjectile( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_ExitAreaAlert + +=============================================================================== +*/ + +class rvTarget_ExitAreaAlert : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_ExitAreaAlert ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ +typedef struct ammodata_s { + + int ammoIndex; + idStr ammoName; + int ammoCount; + int ammoMax; + float percentFull; + +} ammodata_t; + +#define AMMO_ARRAY_SIZE 10 + +class rvTarget_AmmoStash : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AmmoStash ); + + //used to detect which weapons need ammo. The values stored are 0 to 1, with -1 meaning don't check this out. + ammodata_t AmmoArray[ AMMO_ARRAY_SIZE]; + +private: + void Event_Activate( idEntity *activator ); +}; +#endif /* !__GAME_TARGET_H__ */ diff --git a/source/mpgame/TramGate.cpp b/source/mpgame/TramGate.cpp new file mode 100644 index 0000000..9a149fc --- /dev/null +++ b/source/mpgame/TramGate.cpp @@ -0,0 +1,311 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +const idEventDef EV_OpenGate( "" ); +const idEventDef EV_CloseGate( "" ); + +//======================================================= +// +// rvTramGate +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvTramGate ) + EVENT( EV_Touch, rvTramGate::Event_Touch ) + EVENT( EV_Activate, rvTramGate::Event_Activate ) + EVENT( EV_OpenGate, rvTramGate::Event_OpenGate ) + EVENT( EV_CloseGate, rvTramGate::Event_CloseGate ) + EVENT( EV_Door_Lock, rvTramGate::Event_Lock ) + EVENT( EV_Door_IsOpen, rvTramGate::Event_IsOpen ) + EVENT( EV_Door_IsLocked, rvTramGate::Event_IsLocked ) +END_CLASS + +/* +================ +rvTramGate::Spawn +================ +*/ +void rvTramGate::Spawn() { + SpawnDoors(); + + AdjustFrameRate(); +} + +/* +================ +rvTramGate::~rvTramGate +================ +*/ +rvTramGate::~rvTramGate() { + doorList.RemoveContents( true ); +} + +/* +================ +rvTramGate::SpawnDoors +================ +*/ +void rvTramGate::SpawnDoors() { + idDict args; + idVec3 dir = spawnArgs.GetAngles("doorsAxisOffset").ToMat3() * GetPhysics()->GetAxis()[1]; + + args.Set( "team", GetName() ); + args.SetMatrix( "rotation", dir.ToMat3() ); + args.SetVector( "origin", GetPhysics()->GetOrigin() ); + + int len = strlen("door_"); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("door"); kv; kv = spawnArgs.MatchPrefix("door", kv) ) { + args.Set( kv->GetKey().Right(kv->GetKey().Length() - len), kv->GetValue() ); + } + + args.SetFloat( "movedir", (-dir).ToYaw() ); + idDoor* door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door1"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + args.SetFloat( "movedir", dir.ToYaw() ); + door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door2"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + //assert( GetDoorMaster() == door->GetMoveMaster() ); +} + +/* +================ +rvTramGate::AdjustFrameRate +================ +*/ +void rvTramGate::AdjustFrameRate() { + GetAnimator()->SetPlaybackRate( "open", spawnArgs.GetFloat("openFrameRateScale") ); + GetAnimator()->SetPlaybackRate( "close", spawnArgs.GetFloat("closeFrameRateScale") ); +} + +/* +================ +rvTramGate::OpenGate +================ +*/ +void rvTramGate::OpenGate() { + PlayAnim( ANIMCHANNEL_ALL, "open" ); +} + +/* +================ +rvTramGate::CloseGate +================ +*/ +void rvTramGate::CloseGate() { + PlayAnim( ANIMCHANNEL_ALL, "close" ); +} + +/* +================ +rvTramGate::Save +================ +*/ +void rvTramGate::Save( idSaveGame *savefile ) const { + savefile->WriteInt( doorList.Num() ); + for( int i = 0; i < doorList.Num(); i++ ) { + doorList[i].Save( savefile ); + } +} + +/* +================ +rvTramGate::Restore +================ +*/ +void rvTramGate::Restore( idRestoreGame *savefile ) { + int num = 0; + idEntityPtr temp; + savefile->ReadInt( num ); + for( int i = 0; i < num; i++ ) { + temp.Restore( savefile ); + doorList.Append( temp ); + } +} + +/* +================ +rvTramGate::PlayAnim +================ +*/ +int rvTramGate::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return 0; + } + + GetAnimator()->PlayAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim(channel)->PlayLength(); +} + +/* +================ +rvTramGate::CycleAnim +================ +*/ +void rvTramGate::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return; + } + + GetAnimator()->CycleAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAllAnims( int blendFrames ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAnim( int channel, int blendFrames ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +bool rvTramGate::AnimIsPlaying( int channel, int blendFrames ) { + return GetAnimator()->CurrentAnim(channel)->GetEndTime() - FRAME2MS(blendFrames) >= gameLocal.GetTime(); +} + +/* +================ +rvTramGate::IsOpen +================ +*/ +bool rvTramGate::IsOpen() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsOpen() : false; +} + +/* +================ +rvTramGate::IsClosed +================ +*/ +bool rvTramGate::IsClosed() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsClosed() : false; +} + +/* +================ +rvTramGate::GetDoorMaster +================ +*/ +idDoor* rvTramGate::GetDoorMaster() const { + return doorList.Num() ? doorList[0] : NULL; +} + +/* +================ +rvTramGate::IsDoorMasterValid +================ +*/ +bool rvTramGate::IsDoorMasterValid() const { + return GetDoorMaster() != NULL; +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::Event_Touch( idEntity* other, trace_t* trace ) { + if( !IsDoorMasterValid() ) { + return; + } + + if( IsClosed() ) { + OpenGate(); + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } else if( IsOpen() ) { + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } +} + +/* +================ +rvTramGate::Event_Activate +================ +*/ +void rvTramGate::Event_Activate( idEntity* activator ) { + if( !IsDoorMasterValid() ) { + return; + } + + // FIXME: may need some better logic than this. + const char* animName = (IsClosed()) ? "open" : (IsOpen()) ? "close" : NULL; + if( animName ) { + PlayAnim( ANIMCHANNEL_ALL, animName ); + GetDoorMaster()->ProcessEvent( &EV_Activate, this ); + } +} + +/* +================ +rvTramGate::Event_OpenGate +================ +*/ +void rvTramGate::Event_OpenGate() { + OpenGate(); +} + +/* +================ +rvTramGate::Event_CloseGate +================ +*/ +void rvTramGate::Event_CloseGate() { + CloseGate(); +} + +/* +================ +rvTramGate::Event_IsOpen +================ +*/ +void rvTramGate::Event_IsOpen( void ) { + idThread::ReturnInt( IsOpen() ); +} + +/* +================ +rvTramGate::Event_IsLocked +================ +*/ +void rvTramGate::Event_IsLocked( void ) { + idThread::ReturnInt( (IsDoorMasterValid()) ? GetDoorMaster()->IsLocked() : 0 ); +} + +/* +================ +rvTramGate::Event_Lock +================ +*/ +void rvTramGate::Event_Lock( int f ) { + if( IsDoorMasterValid() ) { + GetDoorMaster()->Lock( f ); + } +} diff --git a/source/mpgame/TramGate.h b/source/mpgame/TramGate.h new file mode 100644 index 0000000..272aaec --- /dev/null +++ b/source/mpgame/TramGate.h @@ -0,0 +1,51 @@ +#ifndef __RV_TRAM_GATE_H +#define __RV_TRAM_GATE_H + +extern const idEventDef EV_OpenGate; +extern const idEventDef EV_CloseGate; + +class rvTramGate : public idAnimatedEntity { + CLASS_PROTOTYPE( rvTramGate ); + +public: + void Spawn(); + virtual ~rvTramGate(); + + void OpenGate(); + void CloseGate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnDoors(); + void AdjustFrameRate(); + + int PlayAnim( int channel, const char* animName, int blendFrames = 0 ); + void CycleAnim( int channel, const char* animName, int blendFrames = 0 ); + void ClearAllAnims( int blendFrames = 0 ); + void ClearAnim( int channel, int blendFrames = 0 ); + bool AnimIsPlaying( int channel, int blendFrames = 0 ); + + bool IsOpen() const; + bool IsClosed() const; + + idDoor* GetDoorMaster() const; + bool IsDoorMasterValid() const; + +protected: + void Event_Touch( idEntity* other, trace_t* trace ); + void Event_Activate( idEntity* activator ); + + void Event_OpenGate(); + void Event_CloseGate(); + + void Event_IsOpen( void ); + void Event_IsLocked( void ); + void Event_Lock( int f ); + +protected: + idList< idEntityPtr > doorList; +}; + +#endif diff --git a/source/mpgame/Trigger.cpp b/source/mpgame/Trigger.cpp new file mode 100644 index 0000000..c59d002 --- /dev/null +++ b/source/mpgame/Trigger.cpp @@ -0,0 +1,1616 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +/* +=============================================================================== + + idTrigger + +=============================================================================== +*/ + +const idEventDef EV_Enable( "enable", NULL ); +const idEventDef EV_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idTrigger ) + EVENT( EV_Enable, idTrigger::Event_Enable ) + EVENT( EV_Disable, idTrigger::Event_Disable ) +END_CLASS + +/* +================ +idTrigger::DrawDebugInfo +================ +*/ +void idTrigger::DrawDebugInfo( void ) { + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + bool show; + const function_t *func; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { + show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); + if ( !show ) { + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + show = true; + break; + } + } + } + + if ( !show ) { + continue; + } + + gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + if ( ent->IsType( idTrigger::Type ) ) { + func = static_cast( ent )->GetScriptFunction(); + } else { + func = NULL; + } + + if ( func ) { + gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); + } + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); + if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + } + } + } + } + } +} + +/* +================ +idTrigger::Enable +================ +*/ +void idTrigger::Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); +} + +/* +================ +idTrigger::Disable +================ +*/ +void idTrigger::Disable( void ) { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); +} + +/* +================ +idTrigger::CallScript +================ +*/ +void idTrigger::CallScript( idEntity* scriptEntity ) { +// RAVEN BEGIN +// abahr + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].InsertEntity( scriptEntity, 0 );//We could pass both the activator and self if wanted + scriptFunctions[ix].CallFunc( &spawnArgs ); + scriptFunctions[ix].RemoveIndex( 0 ); + } +// RAVEN END + +} + +/* +================ +idTrigger::GetScriptFunction +================ +*/ +const function_t *idTrigger::GetScriptFunction( void ) const { +// RAVEN BEGIN +// abahr: + return (scriptFunctions.Num()) ? scriptFunctions[0].GetFunc() : NULL; +// RAVEN END +} + +/* +================ +idTrigger::Save +================ +*/ +void idTrigger::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// abahr + savefile->WriteInt( scriptFunctions.Num() ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Save( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Restore +================ +*/ +void idTrigger::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// abahr + int numScripts = 0; + savefile->ReadInt( numScripts ); + scriptFunctions.SetNum( numScripts ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Restore( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Event_Enable +================ +*/ +void idTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +idTrigger::Event_Disable +================ +*/ +void idTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +idTrigger::idTrigger +================ +*/ +idTrigger::idTrigger() { +// RAVEN BEGIN +// abahr: scriptFunction init's itself + //scriptFunction = NULL; +// RAVEN END +} + +/* +================ +idTrigger::Spawn +================ +*/ +void idTrigger::Spawn( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + +// RAVEN BEGIN +// abahr: + scriptFunctions.SetGranularity( 1 ); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("call"); kv; kv = spawnArgs.MatchPrefix("call", kv) ) { + if( !kv->GetValue() ) { + continue; + } + + rvScriptFuncUtility& utility = scriptFunctions.Alloc(); + if( !utility.Init(kv->GetValue()) ) { + gameLocal.Warning( "Trigger '%s' at (%s) trying to call an unknown function.", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + } +// RAVEN END +} + + +/* +=============================================================================== + + idTrigger_Multi + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: changed to 'E' to allow NULL entities +const idEventDef EV_TriggerAction( "", "E" ); +// RAVEN END + +CLASS_DECLARATION( idTrigger, idTrigger_Multi ) + EVENT( EV_FindTargets, idTrigger_Multi::Event_FindTargets ) + + EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) + EVENT( EV_SpectatorTouch, idTrigger_Multi::Event_SpectatorTouch ) + EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) + +// RAVEN BEGIN +// kfuller: respond to earthquakes + EVENT( EV_Earthquake, idTrigger_Multi::Event_EarthQuake ) +// RAVEN END +END_CLASS + + +/* +================ +idTrigger_Multi::idTrigger_Multi +================ +*/ +idTrigger_Multi::idTrigger_Multi( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + removeItem = 0; + touchClient = false; + touchOther = false; + touchVehicle = false; + touchSpec = false; + triggerFirst = false; + triggerWithSelf = false; + buyZoneTrigger = 0; + controlZoneTrigger = 0; + prevZoneController = TEAM_NONE; +} + +/* +================ +idTrigger_Multi::Save +================ +*/ +void idTrigger_Multi::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( touchClient ); + savefile->WriteBool( touchOther ); + savefile->WriteBool( touchVehicle ); + savefile->WriteBool( triggerFirst ); + savefile->WriteBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Restore +================ +*/ +void idTrigger_Multi::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( touchClient ); + savefile->ReadBool( touchOther ); + savefile->ReadBool( touchVehicle ); + savefile->ReadBool( triggerFirst ); + savefile->ReadBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::IsTeleporter + +Returns true if there is a single target, which is a teleporter +================ +*/ +bool idTrigger_Multi::IsTeleporter( void ) const { + if ( targets.Num() != 1 ) { + return false; + } + + return targets[ 0 ].GetEntity()->IsType( idPlayerStart::GetClassType() ); +} + +/* +================ +idTrigger_Multi::Spawn + +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"call" : Script function to call when triggered +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +================ +*/ +void idTrigger_Multi::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); + spawnArgs.GetInt( "buyZone", "0", buyZoneTrigger); + spawnArgs.GetInt( "controlZone", "0", controlZoneTrigger); + + if ( buyZoneTrigger == -1 ) + gameLocal.Warning( "trigger_buyzone '%s' at (%s) has no buyZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + if ( controlZoneTrigger == -1 ) + gameLocal.Warning( "trigger_controlzone '%s' at (%s) has no controlZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + + if ( spawnArgs.GetBool( "onlyVehicle" ) ) { + touchVehicle = true; + } else if ( spawnArgs.GetBool( "anyTouch" ) ) { + touchClient = true; + touchOther = true; + } else if ( spawnArgs.GetBool( "noTouch" ) ) { + touchClient = false; + touchOther = false; + } else if ( spawnArgs.GetBool( "noClient" ) ) { + touchClient = false; + touchOther = true; + } else { + touchClient = true; + touchOther = false; + } + + nextTriggerTime = 0; + + if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); + } else if ( spawnArgs.GetBool( "projectile_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_PROJECTILE ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Multi::FindTargets +================ +*/ +void idTrigger_Multi::Event_FindTargets( void ) { + FindTargets(); + touchSpec = !spawnArgs.GetBool( "noSpec", IsTeleporter() ? "0" : "1" ); +} + +/* +================ +idTrigger_Multi::CheckFacing +================ +*/ +bool idTrigger_Multi::CheckFacing( idEntity *activator ) { + if ( spawnArgs.GetBool( "facing" ) ) { + if ( !activator->IsType( idPlayer::GetClassType() ) ) { + return true; + } + idPlayer *player = static_cast< idPlayer* >( activator ); + + // Unfortunately, the angle key rotates the trigger entity also. So I've added + // an angleFacing key which is used instead when present, otherwise the code defaults + // to the behaviour present prior to this change + idVec3 tFacing = GetPhysics()->GetAxis()[0]; + if ( spawnArgs.FindKey( "angleFacing" )) { + idAngles angs(0,spawnArgs.GetFloat( "angleFacing", "0" ),0); + tFacing = angs.ToForward(); + } + float dot = player->viewAngles.ToForward() * tFacing; + + float angle = RAD2DEG( idMath::ACos( dot ) ); + if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { + return false; + } + } + return true; +} + + +/* +================ +idTrigger_Multi::TriggerAction +================ +*/ +void idTrigger_Multi::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// jdischler: added for Aweldon. The trigger, when activated, will call the listed func with all attached targets, then return. + if ( spawnArgs.GetBool( "_callWithTargets", "0" )) + { + idEntity *ent; + for( int i = 0; i < targets.Num(); i++ ) + { + ent = targets[ i ].GetEntity(); + if ( !ent ) + { + continue; + } + CallScript( ent ); + } + return; + } +// RAVEN END + ActivateTargets( triggerWithSelf ? this : activator ); + CallScript( triggerWithSelf ? this : activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_Multi::Event_TriggerAction +================ +*/ +void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_Multi::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_Multi::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// bdube: moved trigger first + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( activator ) ) { + return; + } +// RAVEN END + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + + +void idTrigger_Multi::HandleControlZoneTrigger() +{ + // This only does something in multiplayer with gameType == DeadZone + if ( !gameLocal.isMultiplayer || gameLocal.gameType != GAME_DEADZONE ) + return; + + const int TEAM_DEADLOCK = 2; + + int pCount = 0; + int count = 0, controllingTeam = TEAM_NONE; + count = playersInTrigger.Num(); + + for ( int i = 0; iPowerUpActive( POWERUP_DEADZONE ) ) + continue; + + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + { + pCount++; + } + + int team = playersInTrigger[i]->team; + + if ( i == 0 ) + controllingTeam = playersInTrigger[i]->team; + + // Assign the controlling team based on the first player + // for zones that accept both. + if ( team != controllingTeam ) + { + controllingTeam = TEAM_DEADLOCK; + pCount = 0; + } + } + + if ( controllingTeam != controlZoneTrigger-1 && controlZoneTrigger != 3 ) + { + controllingTeam = TEAM_NONE; + pCount = 0; + } + + int situation = DZ_NONE; + if ( controllingTeam != prevZoneController ) + { + if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_NONE ) + situation = DZ_MARINES_TAKEN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_NONE ) + situation = DZ_STROGG_TAKEN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINES_LOST; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_LOST; + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_TO_MARINE; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_TO_STROGG; + + // DEADLOCK + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_NONE ) + situation = DZ_MARINE_DEADLOCK; // Unlikely case, just use this. + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINE_REGAIN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_STROGG_REGAIN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINES_LOST; // Unlikely case, just use this. + } + + /// Report individual credits + for( int i = 0; i < count; i++ ) + { + idPlayer* player = playersInTrigger[i]; + + // No token? Ignore em! + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") && !player->PowerUpActive( POWERUP_DEADZONE ) ) + continue; + + int team = player->team; + if( team == controllingTeam ) + { + gameLocal.mpGame.ReportZoneControllingPlayer( player ); + } + } + + /// Report zone control to multiplayer game manager + gameLocal.mpGame.ReportZoneController(controllingTeam, pCount, situation, this); + + playersInTrigger.Clear(); + prevZoneController = controllingTeam; +} + + +/* +================ +idTrigger_Multi::Think +================ +*/ +void idTrigger_Multi::Think() +{ + // Control zone handling + if ( controlZoneTrigger > 0 ) + HandleControlZoneTrigger(); +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + +// RAVEN BEGIN +// jdischler: vehicle only trigger + if ( touchVehicle ) { + if ( !other->IsType(rvVehicle::GetClassType()) ) { + return; + } + } else { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool player = other->IsType( idPlayer::GetClassType() ); +// RAVEN END + if ( player ) { + if ( !touchClient ) { + return; + } + if ( static_cast< idPlayer * >( other )->spectating ) { + Event_SpectatorTouch( other, trace ); + return; + } + + // Buy zone handling + if ( buyZoneTrigger /*&& gameLocal.mpGame.mpGameState.gameState.currentState != 1*/ ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( buyZoneTrigger-1 == p->team || buyZoneTrigger == 3) + { + p->inBuyZone = true; + p->inBuyZonePrev = true; + } + } + + // Control zone handling + if ( controlZoneTrigger > 0 ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( p->PowerUpActive(POWERUP_DEADZONE) || !spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + playersInTrigger.Append(p); + } + + } else if ( !touchOther ) { + return; + } + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { + triggerFirst = true; + } + +// RAVEN BEGIN +// rjohnson: added block + if ( developer.GetBool() && *spawnArgs.GetString ( "message" ) ) { + gameLocal.DPrintf ( "Trigger: %s\n", spawnArgs.GetString ( "message" ) ); + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idPlayer *player; + + if ( !touchSpec ) { + return; + } + + if ( !other->IsType( idPlayer::GetClassType() ) ) { + return; + } + + player = static_cast< idPlayer* >( other ); + if ( !player->spectating ) { + return; + } + + if ( player->lastSpectateTeleport > (gameLocal.time - 1000) ) { + // can't retrigger until the wait is over + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + player->lastSpectateTeleport = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + player->lastSpectateTeleport += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +// RAVEN BEGIN +// kfuller: +void idTrigger_Multi::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + if (requiresLOS) + { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + // activate this effect now + TriggerAction(gameLocal.entities[ENTITYNUM_WORLD]); +} + +// RAVEN END + +/* +=============================================================================== + + idTrigger_EntityName + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) + EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) + EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_EntityName::idTrigger_EntityName +================ +*/ +idTrigger_EntityName::idTrigger_EntityName( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + triggerFirst = false; +} + +/* +================ +idTrigger_EntityName::Save +================ +*/ +void idTrigger_EntityName::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( triggerFirst ); + savefile->WriteString( entityName ); +} + +/* +================ +idTrigger_EntityName::Restore +================ +*/ +void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( triggerFirst ); + savefile->ReadString( entityName ); +} + +/* +================ +idTrigger_EntityName::Spawn +================ +*/ +void idTrigger_EntityName::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + + entityName = spawnArgs.GetString( "entityname" ); + if ( !entityName.Length() ) { + gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + nextTriggerTime = 0; + + if ( !spawnArgs.GetBool( "noTouch" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } +} + +/* +================ +idTrigger_EntityName::TriggerAction +================ +*/ +void idTrigger_EntityName::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// abahr: want same functionality as trigger_multi. Need to move this code into these two function calls + idEntity* scriptEntity = spawnArgs.GetBool("triggerWithSelf") ? this : activator; + ActivateTargets( scriptEntity ); + CallScript( scriptEntity ); +// RAVEN END + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_EntityName::Event_TriggerAction +================ +*/ +void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_EntityName::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !activator ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && activator->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && activator->name != entityName ) { + return; + } +// RAVEN END + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_EntityName::Event_Touch +================ +*/ +void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !other ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && other->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && other->name != entityName ) { + return; + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_Timer + +=============================================================================== +*/ + +const idEventDef EV_Timer( "", NULL ); + +CLASS_DECLARATION( idTrigger, idTrigger_Timer ) + EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) + EVENT( EV_Activate, idTrigger_Timer::Event_Use ) +END_CLASS + +/* +================ +idTrigger_Timer::idTrigger_Timer +================ +*/ +idTrigger_Timer::idTrigger_Timer( void ) { + random = 0.0f; + wait = 0.0f; + on = false; + delay = 0.0f; +} + +/* +================ +idTrigger_Timer::Save +================ +*/ +void idTrigger_Timer::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteString( onName ); + savefile->WriteString( offName ); +} + +/* +================ +idTrigger_Timer::Restore +================ +*/ +void idTrigger_Timer::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadString( onName ); + savefile->ReadString( offName ); +} + +/* +================ +idTrigger_Timer::Spawn + +Repeatedly fires its targets. +Can be turned on or off by using. +================ +*/ +void idTrigger_Timer::Spawn( void ) { + spawnArgs.GetFloat( "random", "1", random ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetBool( "start_on", "0", on ); + spawnArgs.GetFloat( "delay", "0", delay ); + onName = spawnArgs.GetString( "onName" ); + offName = spawnArgs.GetString( "offName" ); + + if ( random >= wait && wait >= 0 ) { + random = wait - 0.001; + gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( on ) { + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Enable +================ +*/ +void idTrigger_Timer::Enable( void ) { + // if off, turn it on + if ( !on ) { + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Disable +================ +*/ +void idTrigger_Timer::Disable( void ) { + // if on, turn it off + if ( on ) { + on = false; + CancelEvents( &EV_Timer ); + } +} + +/* +================ +idTrigger_Timer::Event_Timer +================ +*/ +void idTrigger_Timer::Event_Timer( void ) { + ActivateTargets( this ); + + // set time before next firing + if ( wait >= 0.0f ) { + PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } +} + +/* +================ +idTrigger_Timer::Event_Use +================ +*/ +void idTrigger_Timer::Event_Use( idEntity *activator ) { + // if on, turn it off + if ( on ) { + if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { + return; + } + on = false; + CancelEvents( &EV_Timer ); + } else { + // turn it on + if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { + return; + } + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +=============================================================================== + + idTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Count ) + EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_Count::idTrigger_Count +================ +*/ +idTrigger_Count::idTrigger_Count( void ) { + goal = 0; + count = 0; + delay = 0.0f; +} + +/* +================ +idTrigger_Count::Save +================ +*/ +void idTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +idTrigger_Count::Restore +================ +*/ +void idTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +idTrigger_Count::Spawn +================ +*/ +void idTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +idTrigger_Count::Event_Trigger +================ +*/ +void idTrigger_Count::Event_Trigger( idEntity *activator ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + PostEventSec( &EV_TriggerAction, delay, activator ); + } + } +} + +/* +================ +idTrigger_Count::Event_TriggerAction +================ +*/ +void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript( activator ); + if ( goal == -1 ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============================================================================== + + idTrigger_Hurt + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) + EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) + EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) +END_CLASS + +/* +================ +idTrigger_Hurt::idTrigger_Hurt +================ +*/ +idTrigger_Hurt::idTrigger_Hurt( void ) { + on = false; + delay = 0.0f; + nextTime = 0; +} + +/* +================ +idTrigger_Hurt::Save +================ +*/ +void idTrigger_Hurt::Save( idSaveGame *savefile ) const { + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->WriteBool ( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Restore +================ +*/ +void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->ReadBool( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Spawn + + Damages activator + Can be turned on or off by using. +================ +*/ +void idTrigger_Hurt::Spawn( void ) { + spawnArgs.GetBool( "on", "1", on ); + spawnArgs.GetFloat( "delay", "1.0", delay ); + +// RAVEN BEGIN +// kfuller: playeronly flag + spawnArgs.GetBool( "playerOnly", "0", playerOnly ); +// RAVEN END + + nextTime = gameLocal.time; + Enable(); +} + +/* +================ +idTrigger_Hurt::Event_Touch +================ +*/ +void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { + const char *damage; + +// RAVEN BEGIN +// kfuller: playeronly flag +// jnewquist: Use accessor for static class type + if ( playerOnly && !other->IsType( idPlayer::GetClassType() ) ) { + return; + } +// RAVEN END + + if ( on && other && gameLocal.time >= nextTime ) { + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + other->Damage( this, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); + + ActivateTargets( other ); + CallScript( other ); + + nextTime = gameLocal.time + SEC2MS( delay ); + } +} + +/* +================ +idTrigger_Hurt::Event_Toggle +================ +*/ +void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { + on = !on; +} + + +/* +=============================================================================== + + idTrigger_Fade + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Fade ) + EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) +END_CLASS + +/* +================ +idTrigger_Fade::Event_Trigger +================ +*/ +void idTrigger_Fade::Event_Trigger( idEntity *activator ) { + idVec4 fadeColor; + int fadeTime; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); + fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); + player->playerView.Fade( fadeColor, fadeTime ); + PostEventMS( &EV_ActivateTargets, fadeTime, activator ); + } +} + +/* +=============================================================================== + + idTrigger_Touch + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Touch ) + EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) +END_CLASS + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::idTrigger_Touch( void ) { + clipModel = NULL; +} + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::~idTrigger_Touch( ) { + if ( clipModel ) { + delete clipModel; + clipModel = 0; + } +} + +/* +================ +idTrigger_Touch::Spawn +================ +*/ +void idTrigger_Touch::Spawn( void ) { + // get the clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( GetPhysics()->GetClipModel() ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } + filterTeam = -1; + idStr filterTeamStr = spawnArgs.GetString( "filterTeam" ); + if ( filterTeamStr.Size() ) + { + if ( !idStr::Icmp( "marine", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_MARINE; + } + else if ( !idStr::Icmp( "strogg", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_STROGG; + } + } +} + +/* +================ +idTrigger_Touch::Save +================ +*/ +void idTrigger_Touch::Save( idSaveGame *savefile ) { + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::Restore +================ +*/ +void idTrigger_Touch::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::TouchEntities +================ +*/ +void idTrigger_Touch::TouchEntities( void ) { + int numClipModels, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + +// RAVEN BEGIN +// abahr: now scriptFunction list + if ( clipModel == NULL || !scriptFunctions.Num() ) { +// RAVEN END + return; + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ); +// RAVEN BEGIN +// MCG: filterTeam + if ( filterTeam != -1 ) + { + idActor* actor; + // Iterate through the filter team + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)filterTeam ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden actors and actors that can't be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + if ( !bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + continue; + } + cm = actor->GetPhysics()->GetClipModel(); + if ( !cm || !cm->IsTraceModel() ) { + continue; + } + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ) ) { + continue; + } + ActivateTargets( (idEntity*)actor ); + + CallScript( (idEntity*)actor ); + } + return; + } +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + ActivateTargets( entity ); + +// RAVEN BEGIN +// abahr: changed to be compatible with new script function utility + CallScript( entity ); +// RAVEN END + } +} + +/* +================ +idTrigger_Touch::Think +================ +*/ +void idTrigger_Touch::Think( void ) { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} + +/* +================ +idTrigger_Touch::Event_Trigger +================ +*/ +void idTrigger_Touch::Event_Trigger( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Enable +================ +*/ +void idTrigger_Touch::Enable( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Touch::Disable +================ +*/ +void idTrigger_Touch::Disable( void ) { + BecomeInactive( TH_THINK ); +} diff --git a/source/mpgame/Trigger.h b/source/mpgame/Trigger.h new file mode 100644 index 0000000..96deb36 --- /dev/null +++ b/source/mpgame/Trigger.h @@ -0,0 +1,293 @@ + +#ifndef __GAME_TRIGGER_H__ +#define __GAME_TRIGGER_H__ + +extern const idEventDef EV_Enable; +extern const idEventDef EV_Disable; + +/* +=============================================================================== + + Trigger base. + +=============================================================================== +*/ + +class idTrigger : public idEntity { +public: + CLASS_PROTOTYPE( idTrigger ); + + static void DrawDebugInfo( void ); + + idTrigger(); + void Spawn( void ); + + const function_t * GetScriptFunction( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + +protected: +// RAVEN BEGIN +// abahr: removed const from function + void CallScript( idEntity* scriptEntity ); +// RAVEN END + + void Event_Enable( void ); + void Event_Disable( void ); + +// RAVEN BEGIN +// abahr: changed to allow parms to be passed + idList scriptFunctions; + //const function_t * scriptFunction; +// RAVEN END +}; + + +/* +=============================================================================== + + Trigger which can be activated multiple times. + +=============================================================================== +*/ + +class idTrigger_Multi : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Multi ); + + idTrigger_Multi( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + idStr requires; + int removeItem; + bool touchClient; + bool touchOther; + bool touchVehicle; + bool touchSpec; + bool triggerFirst; + bool triggerWithSelf; + int buyZoneTrigger; + int controlZoneTrigger; + int prevZoneController; + + idList playersInTrigger; + + bool IsTeleporter( void ) const; + bool CheckFacing( idEntity *activator ); + void HandleControlZoneTrigger(); + + void Event_FindTargets( void ); + +// RAVEN BEGIN +// kfuller: want trigger_relays entities to be able to respond to earthquakes + void Event_EarthQuake (float requiresLOS); +// RAVEN END + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Trigger which can only be activated by an entity with a specific name. + +=============================================================================== +*/ + +class idTrigger_EntityName : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_EntityName ); + + idTrigger_EntityName( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + bool triggerFirst; + idStr entityName; + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +/* +=============================================================================== + + Trigger which repeatedly fires targets. + +=============================================================================== +*/ + +class idTrigger_Timer : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Timer ); + + idTrigger_Timer( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Enable( void ); + virtual void Disable( void ); + +private: + float random; + float wait; + bool on; + float delay; + idStr onName; + idStr offName; + + void Event_Timer( void ); + void Event_Use( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ + +class idTrigger_Count : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Count ); + + idTrigger_Count( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + int goal; + int count; + float delay; + + void Event_Trigger( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which hurts touching entities. + +=============================================================================== +*/ + +class idTrigger_Hurt : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Hurt ); + + idTrigger_Hurt( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + bool on; + float delay; + int nextTime; + +// RAVEN BEGIN +// kfuller: added playeronly + bool playerOnly; +// RAVEN END + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Toggle( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fades the player view. + +=============================================================================== +*/ + +class idTrigger_Fade : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Fade ); + +private: + void Event_Trigger( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which continuously tests whether other entities are touching it. + +=============================================================================== +*/ + +class idTrigger_Touch : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Touch ); + + idTrigger_Touch( void ); + ~idTrigger_Touch( ); + + void Spawn( void ); + virtual void Think( void ); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + + void TouchEntities( void ); + +private: + idClipModel * clipModel; + int filterTeam; + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_TRIGGER_H__ */ diff --git a/source/mpgame/Weapon.cpp b/source/mpgame/Weapon.cpp new file mode 100644 index 0000000..44775d1 --- /dev/null +++ b/source/mpgame/Weapon.cpp @@ -0,0 +1,3346 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "Weapon.h" +#include "Projectile.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "client/ClientEffect.h" +//#include "../renderer/tr_local.h" + +/*********************************************************************** + + rvViewWeapon + +***********************************************************************/ + +// class def +CLASS_DECLARATION( idAnimatedEntity, rvViewWeapon ) + EVENT( EV_CallFunction, rvViewWeapon::Event_CallFunction ) +END_CLASS + +/*********************************************************************** + + init + +***********************************************************************/ + +/* +================ +rvViewWeapon::rvViewWeapon() +================ +*/ +rvViewWeapon::rvViewWeapon() { + modelDefHandle = -1; + weapon = NULL; + + Clear(); + + fl.networkSync = true; +} + +/* +================ +rvViewWeapon::~rvViewWeapon() +================ +*/ +rvViewWeapon::~rvViewWeapon() { + Clear(); +} + +/* +================ +rvViewWeapon::Spawn +================ +*/ +void rvViewWeapon::Spawn( void ) { + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +/* +================ +rvViewWeapon::Save +================ +*/ +void rvViewWeapon::Save( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( pendingGUIEvents.Num() ); + for ( i = 0; i < pendingGUIEvents.Num(); i ++ ) { + savefile->WriteString ( pendingGUIEvents[i] ); + } + + // TOSAVE: const idDeclSkin * saveSkin; + // TOSAVE: const idDeclSkin * invisSkin; + // TOSAVE: const idDeclSkin * saveWorldSkin; + // TOSAVE: const idDeclSkin * worldInvisSkin; + // TOSAVE: const idDeclSkin * saveHandsSkin; + // TOSAVE: const idDeclSkin * handsSkin; + + // TOSAVE: friend rvWeapon; + // TOSAVE: rvWeapon* weapon; +} + +/* +================ +rvViewWeapon::Restore +================ +*/ +void rvViewWeapon::Restore( idRestoreGame *savefile ) { + int i; + int num; + savefile->ReadInt ( num ); + pendingGUIEvents.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadString ( pendingGUIEvents[i] ); + } +} + +/* +=============== +rvViewWeapon::ClientPredictionThink +=============== +*/ +void rvViewWeapon::ClientPredictionThink( void ) { + UpdateAnimation(); +} + +/*********************************************************************** + + Weapon definition management + +***********************************************************************/ + +/* +================ +rvViewWeapon::Clear +================ +*/ +void rvViewWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + StopAllEffects( ); + + // TTimo - the weapon doesn't get a proper Event_DisableWeapon sometimes, so the sound sticks + // typically, client side instance join in tourney mode just wipes all ents + StopSound( SND_CHANNEL_ANY, false ); + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.entityNum = entityNumber; + + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customSkin = NULL; + + // set default shader parms + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[3] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f; + renderEntity.shaderParms[5] = 0.0f; + renderEntity.shaderParms[6] = 0.0f; + renderEntity.shaderParms[7] = 0.0f; + + memset( &refSound, 0, sizeof( refSound_t ) ); + refSound.referenceSoundHandle = -1; + + // setting diversity to 0 results in no random sound. -1 indicates random. + refSound.diversity = -1.0f; + + if ( weapon && weapon->GetOwner ( ) ) { + // don't spatialize the weapon sounds + refSound.listenerId = weapon->GetOwner( )->GetListenerId(); + } + + animator.ClearAllAnims( gameLocal.time, 0 ); + + FreeModelDef(); +} + +/* +===================== +rvViewWeapon::GetDebugInfo +===================== +*/ +void rvViewWeapon::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAnimatedEntity::GetDebugInfo( proc, userData ); + weapon->GetDebugInfo( proc, userData ); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +rvViewWeapon::PostGUIEvent +================ +*/ +void rvViewWeapon::PostGUIEvent( const char* event ) { + pendingGUIEvents.Append ( event ); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ + +/* +================ +rvViewWeapon::SetPowerUpSkin +================ +*/ +void rvViewWeapon::SetPowerUpSkin( const char *name ) { +/* FIXME + saveSkin = renderEntity.customSkin; + renderEntity.customSkin = invisSkin; + if ( worldModel.GetEntity() ) { + saveWorldSkin = worldModel.GetEntity()->GetSkin(); + worldModel.GetEntity()->SetSkin( worldInvisSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::UpdateSkin +================ +*/ +void rvViewWeapon::UpdateSkin( void ) { +/* FIXME + renderEntity.customSkin = saveSkin; + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( saveWorldSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::SetModel +================ +*/ +void rvViewWeapon::SetModel( const char *modelname, int mods ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + renderEntity.hModel = animator.SetModel( modelname ); + if ( renderEntity.hModel ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + } else { + renderEntity.customSkin = NULL; + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + } + + // hide the model until an animation is played + Hide(); +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvViewWeapon::Think +================ +*/ +void rvViewWeapon::Think( void ) { + // do nothing because the present is called from the player through PresentWeapon +} + +/* +===================== +rvViewWeapon::ConvertLocalToWorldTransform +===================== +*/ +void rvViewWeapon::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + if( !weapon ) { + idAnimatedEntity::ConvertLocalToWorldTransform( offset, axis ); + return; + } + + offset = GetPhysics()->GetOrigin() + offset * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + axis *= GetPhysics()->GetAxis(); +} + +/* +================ +rvViewWeapon::UpdateModelTransform +================ +*/ +void rvViewWeapon::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( !weapon ) { + idAnimatedEntity::UpdateModelTransform(); + return; + } + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +rvViewWeapon::PresentWeapon +================ +*/ +void rvViewWeapon::PresentWeapon( bool showViewModel ) { + // Dont do anything with the weapon while its stale + if ( fl.networkStale ) { + return; + } + +// RAVEN BEGIN +// rjohnson: cinematics should never be done from the player's perspective, so don't think the weapon ( and their sounds! ) + if ( gameLocal.inCinematic ) { + return; + } +// RAVEN END + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = weapon->GetOwner()->entityNumber + 1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHackInViewID = weapon->GetOwner()->entityNumber + 1; + + weapon->Think(); + + // present the model + if ( showViewModel && !(weapon->wsfl.zoom && weapon->GetZoomGui() ) ) { + Present(); + } else { + FreeModelDef(); + } + + UpdateSound(); +} + +/* +================ +rvViewWeapon::WriteToSnapshot +================ +*/ +void rvViewWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvViewWeapon::ReadFromSnapshot +================ +*/ +void rvViewWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvViewWeapon::ClientStale +================ +*/ +bool rvViewWeapon::ClientStale ( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( weapon ) { + weapon->ClientStale( ); + } + + idEntity::ClientStale( ); + + return false; +} + +/* +================ +rvViewWeapon::ClientReceiveEvent +================ +*/ +bool rvViewWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + if ( idEntity::ClientReceiveEvent( event, time, msg ) ) { + return true; + } + if ( weapon ) { + return weapon->ClientReceiveEvent ( event, time, msg ); + } + return false; +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +===================== +rvViewWeapon::Event_CallFunction +===================== +*/ +void rvViewWeapon::Event_CallFunction( const char *funcname ) { + if ( weapon ) { + stateParms_t parms = {0}; + if ( weapon->ProcessState ( funcname, parms ) == SRESULT_ERROR ) { + gameLocal.Error ( "Unknown function '%s' on entity '%s'", funcname, GetName() ); + } + } +} + +/* +================ +rvViewWeapon::SetSkin +================ +*/ +void rvViewWeapon::SetSkin( const char *skinname ) { + const idDeclSkin *skinDecl; + + if ( !skinname || !skinname[ 0 ] ) { + skinDecl = NULL; + } else { + skinDecl = declManager->FindSkin( skinname ); + } + + renderEntity.customSkin = skinDecl; + UpdateVisuals(); + + // Set the skin on the world model as well + if ( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skinDecl ); + } +} + +void rvViewWeapon::SetSkin( const idDeclSkin* skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); + + if( weapon && weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skin ); + } +} + +/* +================ +rvViewWeapon::GetPosition +================ +*/ +void rvViewWeapon::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); +} + +void rvViewWeapon::SetOverlayShader( const idMaterial* material ) { + renderEntity.overlayShader = material; +} + +/*********************************************************************** + + rvWeapon + +***********************************************************************/ + +CLASS_DECLARATION( idClass, rvWeapon ) +END_CLASS + +/* +================ +rvWeapon::rvWeapon +================ +*/ +rvWeapon::rvWeapon ( void ) { + viewModel = NULL; + worldModel = NULL; + weaponDef = NULL; + +#ifdef _XENON + aimAssistFOV = 10.0f; +#endif + + memset ( &animDoneTime, 0, sizeof(animDoneTime) ); + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + + hitscanAttackDef = -1; + + forceGUIReload = false; +} + +/* +================ +rvWeapon::~rvWeapon +================ +*/ +rvWeapon::~rvWeapon( void ) { + int i; + + // Free all current light defs + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + FreeLight ( i ); + } + + // Disassociate with the view model + if ( viewModel ) { + StopSound( SND_CHANNEL_ANY, false ); + viewModel->weapon = NULL; + } +} + +/* +================ +rvWeapon::Init +================ +*/ +void rvWeapon::Init( idPlayer* _owner, const idDeclEntityDef* def, int _weaponIndex, bool _isStrogg ) { + int i; + + viewModel = _owner->GetWeaponViewModel( ); + worldModel = _owner->GetWeaponWorldModel( ); + weaponDef = def; + owner = _owner; + scriptObject = &viewModel->scriptObject; + weaponIndex = _weaponIndex; + mods = owner->inventory.weaponMods[ weaponIndex ]; + isStrogg = _isStrogg; + + spawnArgs = weaponDef->dict; + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif + + // Apply the mod dictionaries + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict; + if ( !(mods & (1<weapon = this; +} + +/* +================ +rvWeapon::FindViewModelPositionStyle +================ +*/ +void rvWeapon::FindViewModelPositionStyle( idVec3& viewOffset, idAngles& viewAngles ) const { + int viewStyle = g_gunViewStyle.GetInteger(); + const char* styleDefName = spawnArgs.GetString( va("def_viewStyle%d", viewStyle) ); + const idDict* styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + if( !styleDef ) { + styleDefName = spawnArgs.GetString( "def_viewStyle" ); + styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + } + assert( styleDef ); + + viewAngles = styleDef->GetAngles( "viewangles" ); + viewOffset = styleDef->GetVector( "viewoffset" ); +} + +/* +================ +rvWeapon::Spawn +================ +*/ +void rvWeapon::Spawn ( void ) { + + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + +// RAVEN BEGIN +// nrausch: +#if defined(_XENON) + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +// RAVEN END + + // Initialize variables + projectileEnt = NULL; + kick_endtime = 0; + hideStart = 0.0f; + hideEnd = 0.0f; + hideOffset = 0.0f; + status = WP_HOLSTERED; + lastAttack = 0; + clipPredictTime = 0; + + muzzleAxis.Identity(); + muzzleOrigin.Zero(); + pushVelocity.Zero(); + playerViewAxis.Identity(); + playerViewOrigin.Zero(); + viewModelAxis.Identity(); + viewModelOrigin.Zero(); + + // View + viewModelForeshorten = spawnArgs.GetFloat ( "foreshorten", "1" ); + + FindViewModelPositionStyle( viewModelOffset, viewModelAngles ); + + // Offsets + weaponAngleOffsetAverages = spawnArgs.GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = spawnArgs.GetFloat( "weaponAngleOffsetScale", "0.25" ); + weaponAngleOffsetMax = spawnArgs.GetFloat( "weaponAngleOffsetMax", "10" ); + weaponOffsetTime = spawnArgs.GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = spawnArgs.GetFloat( "weaponOffsetScale", "0.005" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate" ) ); + altFireRate = SEC2MS ( spawnArgs.GetFloat ( "altFireRate" ) ); + if( altFireRate == 0 ) { + altFireRate = fireRate; + } + spread = (gameLocal.IsMultiplayer()&&spawnArgs.FindKey("spread_mp"))?spawnArgs.GetFloat ( "spread_mp" ):spawnArgs.GetFloat ( "spread" ); + nextAttackTime = 0; + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); + wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); + + // Attack related values + muzzle_kick_time = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = spawnArgs.GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = spawnArgs.GetVector( "muzzle_kick_offset" ); + + // General weapon properties + wfl.silent_fire = spawnArgs.GetBool( "silent_fire" ); + wfl.hasWindupAnim = spawnArgs.GetBool( "has_windup", "0" ); + icon = spawnArgs.GetString( "mtr_icon" ); + hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) ); + hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" ); + hideStartTime = gameLocal.time - hideTime; + muzzleOffset = weaponDef->dict.GetFloat ( "muzzleOffset", "14" ); + + // Ammo + clipSize = spawnArgs.GetInt( "clipSize" ); + ammoRequired = spawnArgs.GetInt( "ammoRequired" ); + lowAmmo = spawnArgs.GetInt( "lowAmmo" ); + ammoType = GetAmmoIndexForName( spawnArgs.GetString( "ammoType" ) ); + maxAmmo = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ); + + if ( ( ammoType < 0 ) || ( ammoType >= MAX_AMMO ) ) { + gameLocal.Warning( "Unknown ammotype for class '%s'", this->GetClassname ( ) ); + } + + // If the weapon has a clip, then fill it up + ammoClip = owner->inventory.clip[weaponIndex]; + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + } + + // Complex initializations Initialize + InitDefs( ); + InitWorldModel( ); + InitViewModel( ); + + // Requires the view model so must be done after it + InitLights( ); + + viewModel->PostGUIEvent( "weapon_init" ); + viewModel->PostGUIEvent( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent( "weapon_noammo" ); + } + + stateThread.SetName( va("%s_%s_%s", owner->GetName(), viewModel->GetName ( ), spawnArgs.GetString("classname") ) ); + stateThread.SetOwner( this ); + + forceGUIReload = true; +} + +/* +================ +rvWeapon::InitViewModel +================ +*/ +void rvWeapon::InitViewModel( void ) { + const char* guiName; + const char* temp; + int i; + const idKeyValue* kv; + + // Reset view model to clean state + viewModel->Clear ( ); + // Make sure the sound handle is initted + viewModel->refSound.referenceSoundHandle = -1; + + // Intialize the weapon guis + if ( spawnArgs.GetString ( "gui", "", &guiName ) ) { + int g = 0; + do { + viewModel->GetRenderEntity()->gui[g++] = uiManager->FindGui ( guiName, true, false, true ); + guiName = spawnArgs.GetString ( va("gui%d", g + 1 ) ); + } while ( *guiName && viewModel->GetRenderEntity()->gui[g-1] ); + } + + // Set the view models spawn args + viewModel->spawnArgs = weaponDef->dict; + + // Set the model for the view model + if ( isStrogg ) { + temp = spawnArgs.GetString ( "model_view_strogg", spawnArgs.GetString ( "model_view" ) ); + } else { + temp = spawnArgs.GetString ( "model_view" ); + } + viewModel->SetModel( temp, mods ); + + // Hide surfaces + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + + // Show and Hide the mods + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_mod%d", i+1) ), false ); + if ( !modDict ) { + continue; + } + + // Hide any show surfaces for mods that arent on + if ( !(mods & (1<MatchPrefix ( "mod_showsurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_showsurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); // NOTE: HIDING them because we don't have this mod yet + } + } else { + for ( kv = modDict->MatchPrefix ( "mod_hidesurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + } + } + + // find some joints in the model for locating effects + viewAnimator = viewModel->GetAnimator ( ); + barrelJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_barrel", "barrel" ) ); + flashJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flash", "flash" ) ); + ejectJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + guiLightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_guiLight", "guiLight" ) ); + flashlightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flashlight", "flashlight" ) ); + + // Eject offset + spawnArgs.GetVector ( "ejectOffset", "0 0 0", ejectOffset ); + + // Setup a skin for the view model + if ( spawnArgs.GetString ( "skin", "", &temp ) ) { + viewModel->GetRenderEntity()->customSkin = declManager->FindSkin ( temp ); + } + + // make sure we have the correct skin + viewModel->UpdateSkin(); +} + +/* +================ +rvWeapon::InitLights +================ +*/ +void rvWeapon::InitLights ( void ) { + const char* shader; + idVec4 color; + renderLight_t* light; + + memset ( lights, 0, sizeof(lights) ); + memset ( lightHandles, -1, sizeof(lightHandles) ); + + // setup gui light + light = &lights[WPLIGHT_GUI]; + shader = spawnArgs.GetString( "mtr_guiLightShader", "" ); + if ( shader && *shader && viewModel->GetRenderEntity()->gui[0] ) { + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = spawnArgs.GetFloat("glightRadius", "3" ); + color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light->shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->lightId = WPLIGHT_GUI * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + spawnArgs.GetVector ( "glightOffset", "0 0 0", guiLightOffset ); + } + + // Muzzle flash + light = &lights[WPLIGHT_MUZZLEFLASH]; + shader = spawnArgs.GetString( "mtr_flashShader", "muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector ( "flashTarget" ); + light->up = spawnArgs.GetVector ( "flashUp" ); + light->right = spawnArgs.GetVector ( "flashRight" ); + light->end = light->target; + } + light->lightId = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + muzzleFlashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) ); + muzzleFlashEnd = 0; + spawnArgs.GetVector ( "flashViewOffset", "0 0 0", muzzleFlashViewOffset ); + } + + // the world muzzle flash is the same, just positioned differently + lights[WPLIGHT_MUZZLEFLASH_WORLD] = lights[WPLIGHT_MUZZLEFLASH]; + light = &lights[WPLIGHT_MUZZLEFLASH_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_MUZZLEFLASH_WORLD * 100 + owner->entityNumber; + + // flashlight + light = &lights[WPLIGHT_FLASHLIGHT]; + shader = spawnArgs.GetString( "mtr_flashlightShader", "lights/muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashlightColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = + (float)spawnArgs.GetInt( "flashlightRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector( "flashlightTarget" ); + light->up = spawnArgs.GetVector( "flashlightUp" ); + light->right = spawnArgs.GetVector( "flashlightRight" ); + light->end = light->target; + } + + light->allowLightInViewID = owner->entityNumber+1; + light->lightId = WPLIGHT_FLASHLIGHT * 100 + owner->entityNumber; + spawnArgs.GetVector ( "flashlightViewOffset", "0 0 0", flashlightViewOffset ); + } + + // the world muzzle flashlight is the same, just positioned differently + lights[WPLIGHT_FLASHLIGHT_WORLD] = lights[WPLIGHT_FLASHLIGHT]; + light = &lights[WPLIGHT_FLASHLIGHT_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_FLASHLIGHT_WORLD * 100 + owner->entityNumber; +} + +/* +================ +rvWeapon::InitDefs +================ +*/ +void rvWeapon::InitDefs( void ) { + const char* name; + const idDeclEntityDef* def; + const char* spawnclass; + idTypeInfo* cls; + + // get the projectile + attackDict.Clear(); + + // Projectile + if ( spawnArgs.GetString( "def_projectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_hitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + hitscanAttackDef = def->Index(); + } + wfl.attackHitscan = true; + } + + // Alternate projectile + attackAltDict.Clear (); + if ( spawnArgs.GetString( "def_altprojectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown alt projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for alt projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_althitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + wfl.attackAltHitscan = true; + } + + // get the melee damage def + meleeDistance = spawnArgs.GetFloat( "melee_distance" ); + if ( spawnArgs.GetString( "def_melee", "", &name ) && *name ) { + meleeDef = gameLocal.FindEntityDef( name, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s' for weapon '%s'", name, weaponDef->GetName() ); + } + } else { + meleeDef = NULL; + } + + // get the brass def + brassDict.Clear(); + if ( spawnArgs.GetString( "def_ejectBrass", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + brassDict = def->dict; + // force any brass to spawn as client moveable + brassDict.Set( "spawnclass", "rvClientMoveable" ); + } + } +} + +/* +================ +rvWeapon::Think +================ +*/ +void rvWeapon::Think ( void ) { + + // Cache the player origin and axis + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + // calculate weapon position based on player movement bobbing + owner->CalculateViewWeaponPos( viewModelOrigin, viewModelAxis ); + + // hide offset is for dropping the gun when approaching a GUI or NPC + // This is simpler to manage than doing the weapon put-away animation + if ( gameLocal.time - hideStartTime < hideTime ) { + float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime; + if ( hideStart < hideEnd ) { + frac = 1.0f - frac; + frac = 1.0f - frac * frac; + } else { + frac = frac * frac; + } + hideOffset = hideStart + ( hideEnd - hideStart ) * frac; + } else { + hideOffset = hideEnd; + } + viewModelOrigin += hideOffset * viewModelAxis[ 2 ]; + + // kick up based on repeat firing + MuzzleRise( viewModelOrigin, viewModelAxis ); + + if ( viewModel ) { + // set the physics position and orientation + viewModel->GetPhysics()->SetOrigin( viewModelOrigin ); + viewModel->GetPhysics()->SetAxis( viewModelAxis ); + viewModel->UpdateVisuals(); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + + // Update the zoom variable before updating the script + wsfl.zoom = owner->IsZoomed( ); + + // Only update the state loop on new frames + if ( gameLocal.isNewFrame ) { + stateThread.Execute( ); + } + + if ( viewModel ) { + viewModel->UpdateAnimation( ); + } + + // Clear reload and flashlight flags + wsfl.reload = false; + wsfl.flashlight = false; + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( worldModel && worldModel->GetRenderEntity() ) { + // always show your own weapon + if( owner->entityNumber == gameLocal.localClientNum ) { + worldModel->GetRenderEntity()->suppressLOD = 1; + } else { + worldModel->GetRenderEntity()->suppressLOD = 0; + } + + if ( gameLocal.IsMultiplayer() && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all weapon shadows for the local client + worldModel->GetRenderEntity()->suppressShadowInViewID = gameLocal.localClientNum+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all weapon shadows + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + // Only show weapon shadows for other clients + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } + + UpdateGUI(); + + // Update lights + UpdateFlashlight ( ); + UpdateMuzzleFlash ( ); + + // update the gui light + renderLight_t& light = lights[WPLIGHT_GUI]; + if ( light.lightRadius[0] && guiLightJointView != INVALID_JOINT ) { + if ( viewModel ) { + idVec4 color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light.shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light.shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light.shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + GetGlobalJointTransform( true, guiLightJointView, light.origin, light.axis, guiLightOffset ); + UpdateLight ( WPLIGHT_GUI ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + } + + // Alert Monsters if the flashlight is one or a muzzle flash is active? + if ( !gameLocal.isMultiplayer ) { + if ( !owner->fl.notarget && (lightHandles[WPLIGHT_MUZZLEFLASH] != -1 || lightHandles[WPLIGHT_FLASHLIGHT] != -1 ) ) { + AlertMonsters ( ); + } + } +} + +/* +================ +rvWeapon::InitWorldModel +================ +*/ +void rvWeapon::InitWorldModel( void ) { + idEntity *ent; + + ent = worldModel; + if ( !ent ) { + gameLocal.Warning ( "InitWorldModel failed due to missing entity" ); + return; + } + + const char *model = spawnArgs.GetString( "model_world" ); + const char *attach = spawnArgs.GetString( "joint_attach" ); + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner, attach, true ); + ent->GetPhysics()->SetOrigin( vec3_origin ); + ent->GetPhysics()->SetAxis( mat3_identity ); + + // supress model in player views, but allow it in mirrors and remote views + renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity(); + if ( worldModelRenderEntity ) { + worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } + + // the renderEntity is reused, so the relevant fields (except this one) appear to be correctly reinitialized + worldModel->GetRenderEntity()->suppressSurfaceMask = 0; + + // Cache the world joints + worldAnimator = ent->GetAnimator ( ); + flashJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flash", "flash" ) ); + flashlightJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flashlight", "flashlight" ) ); + ejectJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_eject", "eject" ) ); +} + +/* +================ +rvWeapon::SetState +================ +*/ +void rvWeapon::SetState( const char *statename, int blendFrames ) { + stateThread.SetState( statename, blendFrames ); +} + +/* +================ +rvWeapon::PostState +================ +*/ +void rvWeapon::PostState( const char* statename, int blendFrames ) { + stateThread.PostState( statename, blendFrames ); +} + +/* +===================== +rvWeapon::ExecuteState +===================== +*/ +void rvWeapon::ExecuteState ( const char* statename ) { + SetState ( statename, 0 ); + stateThread.Execute ( ); +} + +/* +================ +rvWeapon::UpdateLight +================ +*/ +void rvWeapon::UpdateLight ( int lightID ) { + if ( lightHandles[lightID] == -1 ) { + lightHandles[lightID] = gameRenderWorld->AddLightDef ( &lights[lightID] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[lightID], &lights[lightID] ); + } +} + +/* +================ +rvWeapon::FreeLight +================ +*/ +void rvWeapon::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + + +/*********************************************************************** + + Networking + +***********************************************************************/ + +/* +================ +rvWeapon::WriteToSnapshot +================ +*/ +void rvWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { + // this can probably be reduced a bit, there's no clip/reload in MP + // it seems that's used to drive some of the weapon model firing animations though (such as RL) + msg.WriteBits( ammoClip, ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ReadFromSnapshot +================ +*/ +void rvWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ammoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::SkipFromSnapshot +================ +*/ +void rvWeapon::SkipFromSnapshot ( const idBitMsgDelta &msg ) { + msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ClientStale +================ +*/ +void rvWeapon::ClientStale( void ) { +} + +/* +================ +rvWeapon::ClientReceiveEvent +================ +*/ +bool rvWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_RELOAD: { + if ( gameLocal.time - time < 1000 ) { + wsfl.netReload = true; + wsfl.netEndReload = false; + } + return true; + } + case EVENT_ENDRELOAD: { + wsfl.netEndReload = true; + return true; + } + case EVENT_CHANGESKIN: { +/* + // FIXME: use idGameLocal::ReadDecl + int index = msg.ReadLong(); + renderEntity.customSkin = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL; + UpdateVisuals(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( renderEntity.customSkin ); + } + */ + return true; + } + } + return false; +} + +/*********************************************************************** + + Save / Load + +***********************************************************************/ + +/* +================ +rvWeapon::Save +================ +*/ +void rvWeapon::Save ( idSaveGame *savefile ) const { + int i; + + // Flags + savefile->Write ( &wsfl, sizeof( wsfl ) ); + savefile->Write ( &wfl, sizeof( wfl ) ); + + // Write all cached joints + savefile->WriteJoint ( barrelJointView ); + savefile->WriteJoint ( flashJointView ); + savefile->WriteJoint ( ejectJointView ); + savefile->WriteJoint ( guiLightJointView ); + savefile->WriteJoint ( flashlightJointView ); + + savefile->WriteJoint ( flashJointWorld ); + savefile->WriteJoint ( ejectJointWorld ); + savefile->WriteJoint ( flashlightJointWorld ); + + savefile->WriteInt ( status ); + savefile->WriteInt ( lastAttack ); + + // Hide / Show + savefile->WriteInt ( hideTime ); + savefile->WriteFloat ( hideDistance ); + savefile->WriteInt ( hideStartTime ); + savefile->WriteFloat ( hideStart ); + savefile->WriteFloat ( hideEnd ); + savefile->WriteFloat ( hideOffset ); + + // Write attack related values + savefile->WriteVec3 ( pushVelocity ); + savefile->WriteInt ( kick_endtime ); + savefile->WriteInt ( muzzle_kick_time ); + savefile->WriteInt ( muzzle_kick_maxtime ); + savefile->WriteAngles ( muzzle_kick_angles ); + savefile->WriteVec3 ( muzzle_kick_offset ); + savefile->WriteVec3 ( muzzleOrigin ); + savefile->WriteMat3 ( muzzleAxis ); + savefile->WriteFloat ( muzzleOffset ); + projectileEnt.Save ( savefile ); + savefile->WriteVec3 ( ejectOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt ( fireRate ); + savefile->WriteFloat ( spread ); + // savefile->WriteInt ( nextAttackTime ); // cnicholson: This is set to 0 in restore, so don't save it + + // cnicholson: These 3 idDicts are setup during restore, no need to save them. + // TOSAVE: idDict attackAltDict; + // TOSAVE: idDict attackDict; + // TOSAVE: idDict brassDict; + + // Defs + // TOSAVE: const idDeclEntityDef * meleeDef; // cnicholson: This is setup in restore, so don't save it + savefile->WriteFloat ( meleeDistance ); + + // Zoom + savefile->WriteInt ( zoomFov ); + savefile->WriteUserInterface ( zoomGui, true ); + savefile->WriteFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->WriteInt( lightHandles[i] ); + savefile->WriteRenderLight( lights[i] ); + } + savefile->WriteVec3 ( guiLightOffset ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashViewOffset ); + savefile->WriteVec3 ( flashlightViewOffset ); + savefile->WriteBool ( flashlightOn ); // cnicholson: Added unsaved var + savefile->WriteVec3 ( flashlightViewOffset ); // cnicholson: Added unsaved var + + // Write ammo values + savefile->WriteInt ( ammoType ); + savefile->WriteInt ( ammoRequired ); + savefile->WriteInt ( clipSize ); + savefile->WriteInt ( ammoClip ); + savefile->WriteInt ( lowAmmo ); + savefile->WriteInt ( maxAmmo ); + + // multiplayer + savefile->WriteInt ( clipPredictTime ); // TOSAVE: Save MP value? + + // View + savefile->WriteVec3 ( playerViewOrigin ); + savefile->WriteMat3 ( playerViewAxis ); + + savefile->WriteVec3 ( viewModelOrigin ); + savefile->WriteMat3 ( viewModelAxis ); + savefile->WriteAngles ( viewModelAngles ); + savefile->WriteVec3 ( viewModelOffset ); // cnicholson: Added unsaved var + + // Offsets + savefile->WriteInt ( weaponAngleOffsetAverages ); + savefile->WriteFloat ( weaponAngleOffsetScale ); + savefile->WriteFloat ( weaponAngleOffsetMax ); + savefile->WriteFloat ( weaponOffsetTime ); + savefile->WriteFloat ( weaponOffsetScale ); + + savefile->WriteString ( icon ); + savefile->WriteBool ( isStrogg ); + + // TOSAVE: idDict spawnArgs; + + // TOSAVE: idEntityPtr viewModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* viewAnimator; + // TOSAVE: idEntityPtr worldModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* worldAnimator; + // TOSAVE: const idDeclEntityDef* weaponDef; + // TOSAVE: idScriptObject* scriptObject; + savefile->WriteObject ( owner ); + savefile->WriteInt ( weaponIndex ); // cnicholson: Added unsaved var + savefile->WriteInt ( mods ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( viewModelForeshorten ); + + stateThread.Save( savefile ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } + + savefile->WriteInt ( methodOfDeath ); // cnicholson: Added unsaved var +} + +/* +================ +rvWeapon::Restore +================ +*/ +void rvWeapon::Restore ( idRestoreGame *savefile ) { + int i; + const idDeclEntityDef* def; + + // General + savefile->Read ( &wsfl, sizeof( wsfl ) ); + savefile->Read ( &wfl, sizeof( wfl ) ); + + // Read cached joints + savefile->ReadJoint ( barrelJointView ); + savefile->ReadJoint ( flashJointView ); + savefile->ReadJoint ( ejectJointView ); + savefile->ReadJoint ( guiLightJointView ); + savefile->ReadJoint ( flashlightJointView ); + + savefile->ReadJoint ( flashJointWorld ); + savefile->ReadJoint ( ejectJointWorld ); + savefile->ReadJoint ( flashlightJointWorld ); + + savefile->ReadInt ( (int&)status ); + savefile->ReadInt ( lastAttack ); + + // Hide / Show + savefile->ReadInt ( hideTime ); + savefile->ReadFloat ( hideDistance ); + savefile->ReadInt ( hideStartTime ); + savefile->ReadFloat ( hideStart ); + savefile->ReadFloat ( hideEnd ); + savefile->ReadFloat ( hideOffset ); + + // Read attack related values + savefile->ReadVec3 ( pushVelocity ); + savefile->ReadInt ( kick_endtime ); + savefile->ReadInt ( muzzle_kick_time ); + savefile->ReadInt ( muzzle_kick_maxtime ); + savefile->ReadAngles ( muzzle_kick_angles ); + savefile->ReadVec3 ( muzzle_kick_offset ); + savefile->ReadVec3 ( muzzleOrigin ); + savefile->ReadMat3 ( muzzleAxis ); + savefile->ReadFloat ( muzzleOffset ); + projectileEnt.Restore ( savefile ); + savefile->ReadVec3 ( ejectOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt ( fireRate ); + savefile->ReadFloat ( spread ); + nextAttackTime = 0; + + // Attack Alt Def + attackAltDict.Clear( ); + wfl.attackAltHitscan = false; + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_altprojectile" ), false ); + if ( def ) { + attackAltDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_althitscan" ), false ); + if ( def ) { + attackAltDict = def->dict; + wfl.attackAltHitscan = true; + } + } + + // Attack def + attackDict.Clear( ); + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + wfl.attackHitscan = false; + if ( def ) { + attackDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_hitscan" ), false ); + if ( def ) { + attackDict = def->dict; + wfl.attackHitscan = true; + } + } + + // Brass Def + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_ejectBrass" ), false ); + if ( def ) { + brassDict = def->dict; + } else { + brassDict.Clear(); + } + + // Melee Def + meleeDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_melee" ), false ); + savefile->ReadFloat( meleeDistance ); + + // Zoom + savefile->ReadInt ( zoomFov ); + savefile->ReadUserInterface ( zoomGui, &spawnArgs ); + savefile->ReadFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->ReadInt ( lightHandles[i] ); + savefile->ReadRenderLight( lights[i] ); + if ( lightHandles[i] != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandles[i] = gameRenderWorld->AddLightDef ( &lights[i] ); + } + } + savefile->ReadVec3 ( guiLightOffset ); + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashViewOffset ); + savefile->ReadVec3 ( flashlightViewOffset ); + savefile->ReadBool ( flashlightOn ); // cnicholson: Added unrestored var + savefile->ReadVec3 ( flashlightViewOffset ); // cnicholson: Added unrestored var + + // Read the ammo values + savefile->ReadInt ( (int&)ammoType ); + savefile->ReadInt ( ammoRequired ); + savefile->ReadInt ( clipSize ); + savefile->ReadInt ( ammoClip ); + savefile->ReadInt ( lowAmmo ); + savefile->ReadInt ( maxAmmo ); + + // multiplayer + savefile->ReadInt ( clipPredictTime ); // TORESTORE: Restore MP value? + + // View + savefile->ReadVec3 ( playerViewOrigin ); + savefile->ReadMat3 ( playerViewAxis ); + + savefile->ReadVec3 ( viewModelOrigin ); + savefile->ReadMat3 ( viewModelAxis ); + savefile->ReadAngles ( viewModelAngles ); + savefile->ReadVec3 ( viewModelOffset ); // cnicholson: Added unrestored var + + // Offsets + savefile->ReadInt ( weaponAngleOffsetAverages ); + savefile->ReadFloat ( weaponAngleOffsetScale ); + savefile->ReadFloat ( weaponAngleOffsetMax ); + savefile->ReadFloat ( weaponOffsetTime ); + savefile->ReadFloat ( weaponOffsetScale ); + + savefile->ReadString ( icon ); + savefile->ReadBool ( isStrogg ); + + // TORESTORE: idDict spawnArgs; + + // TORESTORE: idAnimator* viewAnimator; + // TORESTORE: idAnimator* worldAnimator; + // TORESTORE: const idDeclEntityDef* weaponDef; + // TORESTORE: idScriptObject* scriptObject; + + // Entities + savefile->ReadObject( reinterpret_cast( owner ) ); + viewModel = owner->GetWeaponViewModel ( ); + worldModel = owner->GetWeaponWorldModel ( ); + + savefile->ReadInt ( weaponIndex ); // cnicholson: Added unrestored var + savefile->ReadInt ( mods ); // cnicholson: Added unrestored var + + savefile->ReadFloat ( viewModelForeshorten ); + + stateThread.Restore( savefile, this ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } + + savefile->ReadInt ( methodOfDeath ); // cnicholson: Added unrestored var + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvWeapon::Hide +================ +*/ +void rvWeapon::Hide( void ) { + muzzleFlashEnd = 0; + + if ( viewModel ) { + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide ( ); + } + + // Stop flashlight and gui lights + FreeLight ( WPLIGHT_GUI ); + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::Show +================ +*/ +void rvWeapon::Show ( void ) { + if ( viewModel ) { + viewModel->Show(); + } + if ( worldModel ) { + worldModel->Show(); + } +} + +/* +================ +rvWeapon::IsHidden +================ +*/ +bool rvWeapon::IsHidden( void ) const { + return !viewModel || viewModel->IsHidden(); +} + +/* +================ +rvWeapon::HideWorldModel +================ +*/ +void rvWeapon::HideWorldModel ( void ) { + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::ShowWorldModel +================ +*/ +void rvWeapon::ShowWorldModel ( void ) { + if ( worldModel ) { + worldModel->Show(); + } +} + + +/* +================ +rvWeapon::LowerWeapon +================ +*/ +void rvWeapon::LowerWeapon( void ) { + if ( !wfl.hide ) { + hideStart = 0.0f; + hideEnd = hideDistance; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = true; + } +} + +/* +================ +rvWeapon::RaiseWeapon +================ +*/ +void rvWeapon::RaiseWeapon( void ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->Show(); + + if ( forceGUIReload ) { + forceGUIReload = false; + int ammo = AmmoInClip(); + for ( int g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + idUserInterface* gui = viewModel->GetRenderEntity()->gui[g]; + if ( gui ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + } + + if ( wfl.hide ) { + hideStart = hideDistance; + hideEnd = 0.0f; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = false; + } +} + +/* +================ +rvWeapon::PutAway +================ +*/ +void rvWeapon::PutAway( void ) { + wfl.hasBloodSplat = false; + wsfl.lowerWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_lower" ); +} + +/* +================ +rvWeapon::Raise +================ +*/ +void rvWeapon::Raise( void ) { + wsfl.raiseWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_raise" ); +} + +/* +================ +rvWeapon::Flashlight +================ +*/ +void rvWeapon::Flashlight ( void ) { + wsfl.flashlight = true; +} + +/* +================ +rvWeapon::SetPushVelocity +================ +*/ +void rvWeapon::SetPushVelocity( const idVec3& _pushVelocity ) { + pushVelocity = _pushVelocity; +} + +/* +================ +rvWeapon::Reload +NOTE: this is only for impulse-triggered reload, auto reload is scripted +================ +*/ +void rvWeapon::Reload( void ) { + if ( clipSize ) { + wsfl.reload = true; + } +} + +/* +================ +rvWeapon::CancelReload +================ +*/ +void rvWeapon::CancelReload( void ) { + wsfl.attack = true; +} + +/* +================ +rvWeapon::AutoReload +================ +*/ +bool rvWeapon::AutoReload ( void ) { + assert( owner ); + + // on a network client, never predict reloads of other clients. wait for the server + if ( gameLocal.isClient ) { + return false; + } + return gameLocal.userInfo[ owner->entityNumber ].GetBool( "ui_autoReload" ); +} + +/* +================ +rvWeapon::NetReload +================ +*/ +void rvWeapon::NetReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_RELOAD, NULL, false, -1 ); + } +} + +/* +=============== +rvWeapon::NetEndReload +=============== +*/ +void rvWeapon::NetEndReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_ENDRELOAD, NULL, false, -1 ); + } +} + +/* +================ +rvWeapon::SetStatus +================ +*/ +void rvWeapon::SetStatus ( weaponStatus_t _status ) { + status = _status; + switch ( status ) { + case WP_READY: + wsfl.raiseWeapon = false; + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_ready" ); + break; + case WP_OUTOFAMMO: + wsfl.raiseWeapon = false; + break; + case WP_RELOAD: + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_reload" ); + break; + case WP_HOLSTERED: + case WP_RISING: + wsfl.lowerWeapon = false; + owner->WeaponRisingCallback(); + break; + case WP_LOWERING: + wsfl.raiseWeapon = false; + owner->WeaponLoweringCallback(); + break; + } +} + +/* +================ +rvWeapon::OwnerDied +================ +*/ +void rvWeapon::OwnerDied( void ) { + + CleanupWeapon(); + + ExecuteState( "OwnerDied" ); + + if ( viewModel ) { + viewModel->StopSound( SCHANNEL_ANY, false ); + viewModel->StopAllEffects( ); + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::BeginAttack +================ +*/ +void rvWeapon::BeginAttack( void ) { + wsfl.attack = true; + + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } +} + +/* +================ +rvWeapon::EndAttack +================ +*/ +void rvWeapon::EndAttack( void ) { + wsfl.attack = false; +} + +/* +================ +rvWeapon::isReady +================ +*/ +bool rvWeapon::IsReady( void ) const { + return !wfl.hide && ! ( gameLocal.time - hideStartTime < hideTime ) && ( viewModel && !viewModel->IsHidden()) && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +rvWeapon::IsReloading +================ +*/ +bool rvWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +rvWeapon::IsHolstered +================ +*/ +bool rvWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + +/* +================ +rvWeapon::ShowCrosshair +================ +*/ +bool rvWeapon::ShowCrosshair( void ) const { + if ( owner->IsZoomed ( ) && zoomGui && wfl.zoomHideCrosshair ) { + return false; + } + return !( status == WP_HOLSTERED ); +} + +/* +===================== +rvWeapon::CanDrop +===================== +*/ +bool rvWeapon::CanDrop( void ) const { + const char *classname = spawnArgs.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/* +===================== +rvViewWeapon::CanZoom +===================== +*/ +bool rvWeapon::CanZoom( void ) const { +#ifdef _XENON + // apparently a xenon specific bug in medlabs. + return zoomFov != -1 && !IsHidden(); +#else + return zoomFov != -1; +#endif +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +rvWeapon::MuzzleRise +================ +*/ +void rvWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = kick_endtime - gameLocal.time; + if ( time <= 0 ) { + return; + } + + if ( muzzle_kick_maxtime <= 0 ) { + return; + } + + if ( time > muzzle_kick_maxtime ) { + time = muzzle_kick_maxtime; + } + + amount = ( float )time / ( float )muzzle_kick_maxtime; + ang = muzzle_kick_angles * amount; + offset = muzzle_kick_offset * amount; + + origin = origin - axis * offset; + axis = ang.ToMat3() * axis; +} + +/* +================ +rvWeapon::UpdateFlashPosition +================ +*/ +void rvWeapon::UpdateMuzzleFlash ( void ) { + // remove the muzzle flash light when it's done + if ( gameLocal.time >= muzzleFlashEnd || !gameLocal.GetLocalPlayer() || !owner || gameLocal.GetLocalPlayer()->GetInstance() != owner->GetInstance() ) { + FreeLight ( WPLIGHT_MUZZLEFLASH ); + FreeLight ( WPLIGHT_MUZZLEFLASH_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + light.origin = playerViewOrigin + (playerViewAxis * muzzleFlashViewOffset); + light.axis = playerViewAxis; + + // put the world muzzle flash on the end of the joint, no matter what + GetGlobalJointTransform( false, flashJointWorld, lightWorld.origin, lightWorld.axis ); + + UpdateLight ( WPLIGHT_MUZZLEFLASH ); + UpdateLight ( WPLIGHT_MUZZLEFLASH_WORLD ); +} + +/* +================ +rvWeapon::UpdateFlashlight +================ +*/ +void rvWeapon::UpdateFlashlight ( void ) { + // Turn flashlight off? + if (! owner->IsFlashlightOn ( ) ) { + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_FLASHLIGHT]; + renderLight_t& lightWorld = lights[WPLIGHT_FLASHLIGHT_WORLD]; + trace_t tr; + + // the flash has an explicit joint for locating it + GetGlobalJointTransform( true, flashlightJointView, light.origin, light.axis, flashlightViewOffset ); + + // if the desired point is inside or very close to a wall, back it up until it is clear + gameLocal.TracePoint( owner, tr, light.origin - playerViewAxis[0] * 8.0f, light.origin, MASK_SHOT_BOUNDINGBOX, owner ); + + // be at least 8 units away from a solid + light.origin = tr.endpos - (tr.fraction < 1.0f ? (playerViewAxis[0] * 8) : vec3_origin); + + // put the world muzzle flash on the end of the joint, no matter what + if ( flashlightJointWorld != INVALID_JOINT ) { + GetGlobalJointTransform( false, flashlightJointWorld, lightWorld.origin, lightWorld.axis ); + } else { + lightWorld.origin = playerViewOrigin + playerViewAxis[0] * 20.0f; + lightWorld.axis = playerViewAxis; + } + + UpdateLight ( WPLIGHT_FLASHLIGHT ); + UpdateLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::MuzzleFlash +================ +*/ +void rvWeapon::MuzzleFlash ( void ) { + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + if ( !g_muzzleFlash.GetBool() || flashJointView == INVALID_JOINT || !light.lightRadius[0] ) { + return; + } + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( viewModel ) { + // these will be different each fire + light.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + light.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + light.noShadows = true; + + lightWorld.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lightWorld.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + lightWorld.noShadows = true; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + UpdateMuzzleFlash ( ); +} + + +/* +================ +rvWeapon::UpdateGUI +================ +*/ +void rvWeapon::UpdateGUI( void ) { + idUserInterface* gui; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + gui = viewModel->GetRenderEntity()->gui[0]; + if ( !gui || status == WP_HOLSTERED ) { + return; + } + int g; +for ( g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + gui = viewModel->GetRenderEntity()->gui[g]; + + if ( gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( !p ) { + return; + } + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } + + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + // show remaining ammo + if ( gui->State().GetInt ( "player_cachedammo", "-1") != ammo ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + +// viewModel->GetRenderEntity()->gui[g]->SetStateInt ( "player_clip_size", ClipSize() ); +} + for ( int i = 0; i < viewModel->pendingGUIEvents.Num(); i ++ ) { + gui->HandleNamedEvent( viewModel->pendingGUIEvents[i] ); + } + viewModel->pendingGUIEvents.Clear(); +} + +/* +================ +rvWeapon::UpdateCrosshairGUI +================ +*/ +void rvWeapon::UpdateCrosshairGUI( idUserInterface* gui ) const { +// RAVEN BEGIN +// cnicholson: Added support for universal crosshair + + // COMMENTED OUT until Custom crosshair GUI is implemented. + if ( g_crosshairCustom.GetBool() ) { // If there's a custom crosshair, use it. + gui->SetStateString( "crossImage", g_crosshairCustomFile.GetString()); + + const idMaterial *material = declManager->FindMaterial( g_crosshairCustomFile.GetString() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } else { + gui->SetStateString( "crossImage", spawnArgs.GetString( "mtr_crosshair" ) ); + + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } + +// Original Block + //gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); +// RAVEN END + gui->SetStateString( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt( "crossOffsetX", spawnArgs.GetInt( "crosshairOffsetX", "0" ) ); + gui->SetStateInt( "crossOffsetY", spawnArgs.GetInt( "crosshairOffsetY", "0" ) ); + gui->StateChanged( gameLocal.time ); +} + +/* +================ +rvWeapon::ForeshortenAxis +================ +*/ +idMat3 rvWeapon::ForeshortenAxis( const idMat3& axis ) const { + return idMat3( axis[0] * viewModelForeshorten, axis[1], axis[2] ); +} + +/* +================ +rvWeapon::GetAngleOffsets +================ +*/ +void rvWeapon::GetAngleOffsets ( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +rvWeapon::GetTimeOffsets +================ +*/ +void rvWeapon::GetTimeOffsets ( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + +/* +================ +rvWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool rvWeapon::GetGlobalJointTransform ( bool view, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset ) { + if ( view) { + // view model + if ( viewModel && viewAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = origin * ForeshortenAxis(viewModelAxis) + viewModelOrigin; + axis = axis * viewModelAxis; + return true; + } + } else { + // world model + if ( worldModel && worldAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = worldModel->GetPhysics()->GetOrigin() + origin * worldModel->GetPhysics()->GetAxis(); + axis = axis * worldModel->GetPhysics()->GetAxis(); + return true; + } + } + origin = viewModelOrigin + offset * viewModelAxis; + axis = viewModelAxis; + return false; +} + +/*********************************************************************** + + Ammo + +***********************************************************************/ + +/* +================ +rvWeapon::GetAmmoIndexForName +================ +*/ +int rvWeapon::GetAmmoIndexForName( const char *ammoname ) { + int num; + const idDict *ammoDict; + + assert( ammoname ); + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + if ( !ammoname[ 0 ] ) { + return 0; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= MAX_AMMOTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, MAX_AMMOTYPES ); + } + + return num; +} + +/* +================ +rvWeapon::GetAmmoNameForNum +================ +*/ +const char* rvWeapon::GetAmmoNameForIndex( int index ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + char text[ 32 ]; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + sprintf( text, "%d", index ); + + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( kv->GetValue() == text ) { + return kv->GetKey(); + } + } + + return NULL; +} + +/* +================ +rvWeapon::TotalAmmoCount +================ +*/ +int rvWeapon::TotalAmmoCount ( void ) const { + return owner->inventory.HasAmmo( ammoType, 1 ); +} + +/* +================ +rvWeapon::AmmoAvailable +================ +*/ +int rvWeapon::AmmoAvailable( void ) const { + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, ammoRequired ); + } else { + return 0; + } +} + +/* +================ +rvWeapon::AmmoInClip +================ +*/ +int rvWeapon::AmmoInClip( void ) const { + if ( !clipSize ) { + return AmmoAvailable(); + } + return ammoClip; +} + +/* +================ +rvWeapon::ResetAmmoClip +================ +*/ +void rvWeapon::ResetAmmoClip( void ) { + ammoClip = -1; +} + +/* +================ +rvWeapon::GetAmmoType +================ +*/ +int rvWeapon::GetAmmoType( void ) const { + return ammoType; +} + +/* +================ +rvWeapon::ClipSize +================ +*/ +int rvWeapon::ClipSize( void ) const { + return clipSize; +} + +/* +================ +rvWeapon::LowAmmo +================ +*/ +int rvWeapon::LowAmmo() const { + return lowAmmo; +} + +/* +================ +rvWeapon::AmmoRequired +================ +*/ +int rvWeapon::AmmoRequired( void ) const { + return ammoRequired; +} + +/* +================ +rvWeapon::SetClip +================ +*/ +void rvWeapon::SetClip ( int amount ) { + ammoClip = amount; + if ( amount < 0 ) { + ammoClip = 0; + } else if ( amount > clipSize ) { + ammoClip = clipSize; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/* +================ +rvWeapon::UseAmmo +================ +*/ +void rvWeapon::UseAmmo( int amount ) { + owner->inventory.UseAmmo( ammoType, amount * ammoRequired ); + if ( clipSize && ammoRequired ) { + ammoClip -= ( amount * ammoRequired ); + if ( ammoClip < 0 ) { + ammoClip = 0; + } + } +} + +/* +================ +rvWeapon::AddToClip +================ +*/ +void rvWeapon::AddToClip ( int amount ) { + int ammoAvail; + + if ( gameLocal.isClient ) { + return; + } + + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoAvail > 0 && ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/*********************************************************************** + + Attack + +***********************************************************************/ + + +/* +================ +rvWeapon::Attack +================ +*/ +void rvWeapon::Attack( bool altAttack, int num_attacks, float spread, float fuseOffset, float power ) { + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + if ( viewModel->IsHidden() ) { + return; + } + + // avoid all ammo considerations on an MP client + if ( !gameLocal.isClient ) { + // check if we're out of ammo or the clip is empty + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) { + return; + } + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + if ( clipSize && ammoRequired ) { + clipPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + ammoClip -= 1; + } + + // wake up nearby monsters + if ( !wfl.silent_fire ) { + gameLocal.AlertAI( owner ); + } + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + viewModel->SetShaderParm ( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + viewModel->SetShaderParm ( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + if ( worldModel.GetEntity() ) { + worldModel->SetShaderParm( SHADERPARM_DIVERSITY, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel->SetShaderParm( SHADERPARM_TIMEOFFSET, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && spawnArgs.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // go straight out of the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + muzzleOrigin += playerViewAxis[0] * muzzleOffset; + } + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.realClientTime ) { + kick_endtime = gameLocal.realClientTime; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime; + } + + // add the muzzleflash + MuzzleFlash(); + + // quad damage overlays a sound + if ( owner->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + viewModel->StartSound( "snd_quaddamage", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + // Muzzle flash effect + bool muzzleTint = spawnArgs.GetBool( "muzzleTint" ); + viewModel->PlayEffect( "fx_muzzleflash", flashJointView, false, vec3_origin, false, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict, "fx_muzzleflash_world" ), flashJointWorld, vec3_origin, mat3_identity, false, vec3_origin, false, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // Inform the gui of the ammo change + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } + + // The attack is either a hitscan or a launched projectile, do that now. + if ( !gameLocal.isClient ) { + idDict& dict = altAttack ? attackAltDict : attackDict; + power *= owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ); + if ( altAttack ? wfl.attackAltHitscan : wfl.attackHitscan ) { + Hitscan( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, power ); + } else { + LaunchProjectiles( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, fuseOffset, power ); + } + //asalmon: changed to keep stats even in single player + statManager->WeaponFired( owner, weaponIndex, num_attacks ); + + } +} + +/* +================ +rvWeapon::LaunchProjectiles +================ +*/ +void rvWeapon::LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ) { + idProjectile* proj; + idEntity* ent; + int i; + float spreadRad; + idVec3 dir; + idBounds ownerBounds; + + if ( gameLocal.isClient ) { + return; + } + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack ( owner, muzzleOrigin, muzzleAxis[0] ); + } + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + spreadRad = DEG2RAD( spread ); + + idVec3 dirOffset; + idVec3 startOffset; + + spawnArgs.GetVector( "dirOffset", "0 0 0", dirOffset ); + spawnArgs.GetVector( "startOffset", "0 0 0", startOffset ); + + for( i = 0; i < num_projectiles; i++ ) { + float ang; + float spin; + idVec3 dir; + idBounds projBounds; + idVec3 muzzle_pos; + + // Calculate a random launch direction based on the spread + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; + dir.Normalize(); + + // If a projectile entity has already been created then use that one, otherwise + // spawn a new one based on the given dictionary + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + dict.SetInt( "instance", owner->GetInstance() ); + gameLocal.SpawnEntityDef( dict, &ent, false ); + } + + // Make sure it spawned + if ( !ent ) { + gameLocal.Error( "failed to spawn projectile for weapon '%s'", weaponDef->GetName ( ) ); + } + + assert ( ent->IsType( idProjectile::GetClassType() ) ); + + // Create the projectile + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin + startOffset, dir, NULL, owner->extraProjPassEntity ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + idVec3 start; + float distance; + trace_t tr; +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) { + start = muzzle_pos + distance * muzzleAxis[0]; + } +#else + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } +#endif +//RAVEN END + else { + start = ownerBounds.GetCenter(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( owner, tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + muzzle_pos = tr.endpos; + } + + // Launch the actual projectile + proj->Launch( muzzle_pos + startOffset, dir, pushVelocity, fuseOffset, power ); + + // Increment the projectile launch count and let the derived classes + // mess with it if they want. + OnLaunchProjectile ( proj ); + } +} + +/* +================ +rvWeapon::OnLaunchProjectile +================ +*/ +void rvWeapon::OnLaunchProjectile ( idProjectile* proj ) { + owner->AddProjectilesFired( 1 ); + if ( proj ) { + proj->methodOfDeath = owner->GetCurrentWeapon(); + } +} + +/* +================ +rvWeapon::Hitscan +================ +*/ +void rvWeapon::Hitscan( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ) { + idVec3 fxOrigin; + idMat3 fxAxis; + int i; + float ang; + float spin; + idVec3 dir; + int areas[ 2 ]; + + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack( owner, muzzleOrigin, muzzleAxis[0] ); + } + + GetGlobalJointTransform( true, flashJointView, fxOrigin, fxAxis, dict.GetVector( "fxOriginOffset" ) ); + + if ( gameLocal.isServer ) { + + assert( hitscanAttackDef >= 0 ); + assert( owner && owner->entityNumber < MAX_CLIENTS ); + int ownerId = owner ? owner->entityNumber : 0; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_HITSCAN ); + msg.WriteLong( hitscanAttackDef ); + msg.WriteBits( ownerId, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteFloat( muzzleOrigin[0] ); + msg.WriteFloat( muzzleOrigin[1] ); + msg.WriteFloat( muzzleOrigin[2] ); + msg.WriteFloat( fxOrigin[0] ); + msg.WriteFloat( fxOrigin[1] ); + msg.WriteFloat( fxOrigin[2] ); + } + + float spreadRad = DEG2RAD( spread ); + idVec3 end; + for( i = 0; i < num_hitscans; i++ ) { + if( weaponDef->dict.GetBool( "machinegunSpreadStyle" ) ) { + float r = gameLocal.random.RandomFloat() * idMath::PI * 2.0f; + float u = idMath::Sin( r ) * gameLocal.random.CRandomFloat() * spread * 16; + r = idMath::Cos( r ) * gameLocal.random.CRandomFloat() * spread * 16; +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else if( weaponDef->dict.GetBool( "shotgunSpreadStyle" ) ) { + int radius; + float angle; + + // this may look slightly odd, but ensures with an odd number of pellets we get + // two complete circles, and the outer one gets the extra hit + int circleHitscans = num_hitscans - (num_hitscans / 2); + if (i < circleHitscans) + { + radius = spread * 14; + angle = i * (idMath::TWO_PI / circleHitscans); + } + else + { + radius = spread * 6; + angle = (i - circleHitscans) * (idMath::TWO_PI / num_hitscans) * 2; + } + + float r = radius * (idMath::Cos(angle) + (gameLocal.random.CRandomFloat() * 0.2f)); + float u = radius * (idMath::Sin(angle) + (gameLocal.random.CRandomFloat() * 0.2f)); + +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //RAVEN BEGIN + //asalmon: xbox must use the muzzleAxis so the aim can be adjusted for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#endif + //RAVEN END + } + dir.Normalize(); + + gameLocal.HitScan( dict, muzzleOrigin, dir, fxOrigin, owner, false, 1.0f, NULL, areas ); + + if ( gameLocal.isServer ) { + msg.WriteDir( dir, 24 ); + if ( i == num_hitscans - 1 ) { + // NOTE: we emit to the areas of the last hitscan + // there is a remote possibility that multiple hitscans for shotgun would cover more than 2 areas, + // so in some rare case a client might miss it + gameLocal.SendUnreliableMessagePVS( msg, owner, areas[0], areas[1] ); + } + } + } +} + +/* +================ +rvWeapon::AlertMonsters +================ +*/ +void rvWeapon::AlertMonsters( void ) { + trace_t tr; + idEntity *ent; + idVec3 end; + renderLight_t& muzzleFlash = lights[WPLIGHT_MUZZLEFLASH]; + + end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target; + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } + + // jitter the trace to try to catch cases where a trace down the center doesn't hit the monster + end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f ); + end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f ); + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } +} + +/* +================ +rvWeapon::EjectBrass +================ +*/ +void rvWeapon::EjectBrass ( void ) { + if ( g_brassTime.GetFloat() <= 0.0f || !owner->CanShowWeaponViewmodel() ) { + return; + } + + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) { + return; + } + + // Spawn the client side moveable for the brass + rvClientMoveable* cent = NULL; + + gameLocal.SpawnClientEntityDef( brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOwner( GetOwner() ); + cent->SetOrigin ( origin + playerViewAxis * ejectOffset ); + cent->SetAxis ( playerViewAxis ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = GetOwner()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 2.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict.GetVector("linear_velocity").Random( brassDict.GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( GetOwner()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict.GetAngles("angular_velocity").Random( brassDict.GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/* +================ +rvWeapon::BloodSplat +================ +*/ +bool rvWeapon::BloodSplat( float size ) { + float s, c; + idMat3 localAxis, axistemp; + idVec3 localOrigin, normal; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + + if ( wfl.hasBloodSplat ) { + return true; + } + + wfl.hasBloodSplat = true; + + if ( viewModel->modelDefHandle < 0 ) { + return false; + } + + if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) { + return false; + } + + localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f; + localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f; + localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f; + + normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 ); + normal.Normalize(); + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + localAxis[2] = -normal; + localAxis[2].NormalVectors( axistemp[0], axistemp[1] ); + localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + localAxis[0] *= 1.0f / size; + localAxis[1] *= 1.0f / size; + + idPlane localPlane[2]; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" ); + + gameRenderWorld->ProjectOverlay( viewModel->modelDefHandle, localPlane, mtr ); + + return true; +} + +/* +================ +rvWeapon::EnterCinematic +================ +*/ +void rvWeapon::EnterCinematic( void ) { + if( viewModel ) { + viewModel->StopSound( SND_CHANNEL_ANY, false ); + } + ExecuteState( "EnterCinematic" ); + + memset( &wsfl, 0, sizeof(wsfl) ); + + wfl.disabled = true; + + LowerWeapon(); +} + +/* +================ +rvWeapon::ExitCinematic +================ +*/ +void rvWeapon::ExitCinematic( void ) { + wfl.disabled = false; + ExecuteState ( "ExitCinematic" ); + RaiseWeapon(); +} + +/* +================ +rvWeapon::NetCatchup +================ +*/ +void rvWeapon::NetCatchup( void ) { + ExecuteState ( "NetCatchup" ); +} + +/* +=============== +rvWeapon::PlayAnim +=============== +*/ +void rvWeapon::PlayAnim( int channel, const char *animname, int blendFrames ) { + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + int anim; + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::PlayCycle +=============== +*/ +void rvWeapon::PlayCycle( int channel, const char *animname, int blendFrames ) { + int anim; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + anim = worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::AnimDone +=============== +*/ +bool rvWeapon::AnimDone( int channel, int blendFrames ) { + if ( animDoneTime[channel] - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } + return false; +} + +/* +=============== +rvWeapon::StartSound +=============== +*/ +bool rvWeapon::StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + return viewModel->StartSound( soundName, channel, soundShaderFlags, broadcast, length ); +} + +/* +=============== +rvWeapon::StopSound +=============== +*/ +void rvWeapon::StopSound( const s_channelType channel, bool broadcast ) { + if ( viewModel ) { + viewModel->StopSound( channel, broadcast ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } +} + +/* +=============== +rvWeapon::PlayEffect +=============== +*/ +rvClientEffect* rvWeapon::PlayEffect( const char* effectName, jointHandle_t joint, bool loop, const idVec3& endOrigin, bool broadcast ) { + if ( viewModel ) { + return viewModel->PlayEffect( effectName, joint, loop, endOrigin, broadcast ); + } + + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return 0; +} + +/* +================ +rvWeapon::CacheWeapon +================ +*/ +void rvWeapon::CacheWeapon( const char *weaponName ) { + const idDeclEntityDef *weaponDef; + const char *brassDefName; + const char *clipModelName; + idTraceModel trm; + + weaponDef = gameLocal.FindEntityDef( weaponName, false ); + if ( !weaponDef ) { + return; + } + + // precache the brass collision model + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( brassDef ) { + brassDef->dict.GetString( "clipmodel", "", &clipModelName ); + if ( idStr::Icmp( clipModelName, SIMPLE_TRI_NAME ) == 0 ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + if ( !clipModelName[0] ) { + clipModelName = brassDef->dict.GetString( "model" ); // use the visual model + } + // load the trace model + collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ); + } + } + } + + const idKeyValue* kv; + + kv = weaponDef->dict.MatchPrefix( "gui", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + uiManager->FindGui( kv->GetValue().c_str(), true, false, true ); + } + kv = weaponDef->dict.MatchPrefix( "gui", kv ); + } +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeapon ) + STATE ( "Raise", rvWeapon::State_Raise ) + STATE ( "Lower", rvWeapon::State_Lower ) + STATE ( "ExitCinematic", rvWeapon::State_ExitCinematic ) + STATE ( "NetCatchup", rvWeapon::State_NetCatchup ) + STATE ( "EjectBrass", rvWeapon::Frame_EjectBrass ) +END_CLASS_STATES + +/* +================ +rvWeapon::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeapon::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeapon::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeapon::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_NetCatchup ( const stateParms_t& parms ) { + SetState ( "idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_ExitCinematic ( const stateParms_t& parms ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::Frame_EjectBrass +===================== +*/ +stateResult_t rvWeapon::Frame_EjectBrass( const stateParms_t& parms ) { + EjectBrass(); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::GetDebugInfo +===================== +*/ +void rvWeapon::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idClass::GetDebugInfo ( proc, userData ); + proc ( "rvWeapon", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); +} diff --git a/source/mpgame/Weapon.h b/source/mpgame/Weapon.h new file mode 100644 index 0000000..c508b48 --- /dev/null +++ b/source/mpgame/Weapon.h @@ -0,0 +1,492 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 9/30/2004 + +#ifndef __GAME_WEAPON_H__ +#define __GAME_WEAPON_H__ + +/* +=============================================================================== + + Player Weapon + +=============================================================================== +*/ + +typedef enum { + WP_READY, + WP_OUTOFAMMO, + WP_RELOAD, + WP_HOLSTERED, + WP_RISING, + WP_LOWERING, + WP_FLASHLIGHT, +} weaponStatus_t; + +static const int MAX_WEAPONMODS = 4; +static const int MAX_AMMOTYPES = 16; + +class idPlayer; +class idItem; +class idAnimatedEntity; +class idProjectile; +class rvWeapon; + +class rvViewWeapon : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvViewWeapon ); + + rvViewWeapon( void ); + virtual ~rvViewWeapon( void ); + + // Init + void Spawn ( void ); + + // save games + void Save ( idSaveGame *savefile ) const; // archives object for save game file + void Restore ( idRestoreGame *savefile ); // unarchives object from save game file + + + // Weapon definition management + void Clear ( void ); + + // GUIs + void PostGUIEvent ( const char* event ); + + virtual void SetModel ( const char *modelname, int mods = 0 ); + void SetPowerUpSkin ( const char *name ); + void UpdateSkin ( void ); + + // State control/player interface + void Think ( void ); + + // Visual presentation + void PresentWeapon ( bool showViewModel ); + + // Networking + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientPredictionThink ( void ); + virtual bool ClientStale ( void ); + + virtual void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); + virtual void UpdateModelTransform ( void ); + + // Debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + void SetSkin ( const char *skinname ); + void SetSkin ( const idDeclSkin* skin ); + + void SetOverlayShader ( const idMaterial* material ); + + virtual void GetPosition ( idVec3& origin, idMat3& axis ) const; + +private: + + idStrList pendingGUIEvents; + + // effects + const idDeclSkin * saveSkin; + const idDeclSkin * invisSkin; + const idDeclSkin * saveWorldSkin; + const idDeclSkin * worldInvisSkin; + const idDeclSkin * saveHandsSkin; + const idDeclSkin * handsSkin; + + void Event_CallFunction ( const char* function ); + + friend class rvWeapon; + rvWeapon* weapon; +}; + +class rvWeapon : public idClass { +public: + + CLASS_PROTOTYPE( rvWeapon ); + + rvWeapon( void ); + virtual ~rvWeapon( void ); + + enum { + WPLIGHT_MUZZLEFLASH, + WPLIGHT_MUZZLEFLASH_WORLD, + WPLIGHT_FLASHLIGHT, + WPLIGHT_FLASHLIGHT_WORLD, + WPLIGHT_GUI, + WPLIGHT_MAX + }; + + enum { + EVENT_RELOAD = idEntity::EVENT_MAXEVENTS, + EVENT_ENDRELOAD, + EVENT_CHANGESKIN, + EVENT_MAXEVENTS + }; + + void Init ( idPlayer* _owner, const idDeclEntityDef* def, int weaponIndex, bool isStrogg = false ); + + // Virtual overrides + void Spawn ( void ); + virtual void Think ( void ); + virtual void CleanupWeapon ( void ) {} + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientStale ( void ); + virtual void ClientUnstale ( void ) { } + virtual void Attack ( bool altFire, int num_attacks, float spread, float fuseOffset, float power ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + virtual void SpectatorCycle ( void ) { } + virtual bool NoFireWhileSwitching ( void ) const { return false; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void PreSave ( void ); + virtual void PostSave ( void ); + + + // Visual presentation + bool BloodSplat ( float size ); + void MuzzleFlash ( void ); + void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + float GetMuzzleFlashLightParm ( int parm ); + void SetMuzzleFlashLightParm ( int parm, float value ); + void GetAngleOffsets ( int *average, float *scale, float *max ); + void GetTimeOffsets ( float *time, float *scale ); + bool GetGlobalJointTransform ( bool viewModel, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset = vec3_origin ); + + // State control/player interface + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void Raise ( void ); + void PutAway ( void ); + void Hide ( void ); + void Show ( void ); + void HideWorldModel ( void ); + void ShowWorldModel ( void ); + void SetFlashlight ( bool on = true ); + void Flashlight ( void ); + void SetPushVelocity ( const idVec3 &pushVelocity ); + void Reload ( void ); + void OwnerDied ( void ); + void BeginAttack ( void ); + void EndAttack ( void ); + bool IsReady ( void ) const; + bool IsReloading ( void ) const; + bool IsHolstered ( void ) const; + bool ShowCrosshair ( void ) const; + bool CanDrop ( void ) const; + bool CanZoom ( void ) const; + void CancelReload ( void ); + void SetStatus ( weaponStatus_t status ); + bool AutoReload ( void ); + bool IsHidden ( void ) const; + void EjectBrass ( void ); + + // Network helpers + void NetReload ( void ); + void NetEndReload ( void ); + void NetCatchup ( void ); + + // Ammo + static int GetAmmoIndexForName ( const char *ammoname ); + static const char* GetAmmoNameForIndex ( int index ); + int GetAmmoType ( void ) const; + int AmmoAvailable ( void ) const; + int AmmoInClip ( void ) const; + void ResetAmmoClip ( void ); + int ClipSize ( void ) const; + int LowAmmo ( void ) const; + int AmmoRequired ( void ) const; + void AddToClip ( int amount ); + void UseAmmo ( int amount ); + void SetClip ( int amount ); + int TotalAmmoCount ( void ) const; + + // Attack + bool PerformAttack ( idVec3& muzzleOrigin, idMat3& muzzleAxis, float dmgPower ); + void LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ); + void Hitscan ( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ); + void AlertMonsters ( void ); + + // Mods + int GetMods ( void ) const; + + // Zoom + idUserInterface* GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + int GetZoomFov ( void ) const; + + rvViewWeapon* GetViewModel ( void ) const; + idAnimatedEntity* GetWorldModel ( void ) const; + idPlayer* GetOwner ( void ) const; + const char * GetIcon ( void ) const; + renderLight_t& GetLight ( int light ); + const idAngles& GetViewModelAngles ( void ) const; + const idVec3& GetViewModelOffset ( void ) const; + + static void CacheWeapon ( const char *weaponName ); + static void SkipFromSnapshot ( const idBitMsgDelta &msg ); + + void EnterCinematic ( void ); + void ExitCinematic ( void ); + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetState ( const char *statename, int blendFrames ); + void PostState ( const char *statename, int blendFrames ); + void ExecuteState ( const char *statename ); + + void PlayAnim ( int channel, const char *animname, int blendFrames ); + void PlayCycle ( int channel, const char *animname, int blendFrames ); + bool AnimDone ( int channel, int blendFrames ); + bool StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound ( const s_channelType channel, bool broadcast ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false ); + + void FindViewModelPositionStyle ( idVec3& viewOffset, idAngles& viewAngles ) const; + +public: + + void InitLights ( void ); + void InitWorldModel ( void ); + void InitViewModel ( void ); + void InitDefs ( void ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID ); + + void UpdateMuzzleFlash ( void ); + void UpdateFlashlight ( void ); + + void UpdateGUI ( void ); + void UpdateCrosshairGUI ( idUserInterface* gui ) const; + + idMat3 ForeshortenAxis ( const idMat3& axis ) const; + + // Script state management + struct weaponStateFlags_s { + bool attack :1; + bool reload :1; + bool netReload :1; + bool netEndReload :1; + bool raiseWeapon :1; + bool lowerWeapon :1; + bool flashlight :1; + bool zoom :1; + } wsfl; + + // Generic flags + struct weaponFlags_s { + bool attackAltHitscan :1; + bool attackHitscan :1; + bool hide :1; + bool disabled :1; + bool hasBloodSplat :1; + bool silent_fire :1; + bool zoomHideCrosshair :1; + bool flashlightOn :1; + bool hasWindupAnim :1; + } wfl; + + // joints from models + jointHandle_t barrelJointView; + jointHandle_t flashJointView; + jointHandle_t ejectJointView; + jointHandle_t guiLightJointView; + jointHandle_t flashlightJointView; + + jointHandle_t flashJointWorld; + jointHandle_t ejectJointWorld; + jointHandle_t flashlightJointWorld; + + weaponStatus_t status; + int lastAttack; + + // hiding weapon + int hideTime; + float hideDistance; + int hideStartTime; + float hideStart; + float hideEnd; + float hideOffset; + + // Attack + idVec3 pushVelocity; + int kick_endtime; + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + float muzzleOffset; + idEntityPtr projectileEnt; + idVec3 ejectOffset; + + int fireRate; + int altFireRate; + float spread; + int nextAttackTime; + + // we maintain local copies of the projectile and brass dictionaries so they + // do not have to be copied across the DLL boundary when entities are spawned + idDict attackAltDict; + idDict attackDict; + idDict brassDict; + + // Melee + const idDeclEntityDef * meleeDef; + float meleeDistance; + + // zoom + int zoomFov; // variable zoom fov per weapon (-1 is no zoom) + idUserInterface* zoomGui; // whether or not to overlay a zoom scope + float zoomTime; // time it takes to zoom in + + // lights + renderLight_t lights[WPLIGHT_MAX]; + int lightHandles[WPLIGHT_MAX]; + idVec3 guiLightOffset; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashViewOffset; + bool flashlightOn; + idVec3 flashlightViewOffset; + + // ammo management + int ammoType; + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; // if ammo in clip hits this threshold, snd_ + int maxAmmo; + + // multiplayer + int clipPredictTime; + + // these are the player render view parms, which include bobbing + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + + + // View Model + idVec3 viewModelOrigin; + idMat3 viewModelAxis; + idAngles viewModelAngles; + idVec3 viewModelOffset; + + // weighting for viewmodel offsets + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // General + idStr icon; + bool isStrogg; + + bool forceGUIReload; + +public: + + idDict spawnArgs; + +protected: + + idEntityPtr viewModel; + idAnimator* viewAnimator; + idEntityPtr worldModel; + idAnimator* worldAnimator; + const idDeclEntityDef* weaponDef; + idScriptObject* scriptObject; + idPlayer * owner; + int weaponIndex; + int mods; + + float viewModelForeshorten; + + rvStateThread stateThread; + int animDoneTime[ANIM_NumAnimChannels]; + +private: + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_ExitCinematic ( const stateParms_t& parms ); + stateResult_t State_NetCatchup ( const stateParms_t& parms ); + + stateResult_t Frame_EjectBrass ( const stateParms_t& parms ); + + // store weapon index information for death messages + int methodOfDeath; + + // multiplayer hitscans + int hitscanAttackDef; + + CLASS_STATES_PROTOTYPE ( rvWeapon ); +}; + +ID_INLINE rvViewWeapon* rvWeapon::GetViewModel ( void ) const { + return viewModel.GetEntity(); +} + +ID_INLINE idAnimatedEntity* rvWeapon::GetWorldModel ( void ) const { + return worldModel; +} + +ID_INLINE idPlayer* rvWeapon::GetOwner ( void ) const { + return owner; +} + +ID_INLINE const char* rvWeapon::GetIcon ( void ) const { + return icon; +} + +ID_INLINE renderLight_t& rvWeapon::GetLight ( int light ) { + assert ( light < WPLIGHT_MAX ); + return lights[light]; +} + +ID_INLINE const idAngles& rvWeapon::GetViewModelAngles( void ) const { + return viewModelAngles; +} + +ID_INLINE const idVec3& rvWeapon::GetViewModelOffset ( void ) const { + return viewModelOffset; +} + +ID_INLINE int rvWeapon::GetZoomFov ( void ) const { + return zoomFov; +} + +ID_INLINE idUserInterface* rvWeapon::GetZoomGui ( void ) const { + return zoomGui; +} + +ID_INLINE float rvWeapon::GetZoomTime ( void ) const { + return zoomTime; +} + +ID_INLINE int rvWeapon::GetMods ( void ) const { + return mods; +} + +ID_INLINE void rvWeapon::PreSave ( void ) { +} + +ID_INLINE void rvWeapon::PostSave ( void ) { +} + + +#endif /* !__GAME_WEAPON_H__ */ + +// RAVEN END diff --git a/source/mpgame/WorldSpawn.cpp b/source/mpgame/WorldSpawn.cpp new file mode 100644 index 0000000..b50e7dc --- /dev/null +++ b/source/mpgame/WorldSpawn.cpp @@ -0,0 +1,107 @@ +/* +game_worldspawn.cpp + +Worldspawn class. Each map has one worldspawn which handles global spawnargs. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================ +idWorldspawn + +Every map should have exactly one worldspawn. +================ +*/ +CLASS_DECLARATION( idEntity, idWorldspawn ) + EVENT( EV_Remove, idWorldspawn::Event_Remove ) + EVENT( EV_SafeRemove, idWorldspawn::Event_Remove ) +END_CLASS + +/* +================ +idWorldspawn::Spawn +================ +*/ +void idWorldspawn::Spawn( void ) { + idStr scriptname; + idThread *thread; + const function_t *func; + const idKeyValue *kv; + + assert( gameLocal.world == NULL ); + gameLocal.world = this; + + // load script + scriptname = gameLocal.GetMapName(); + scriptname.SetFileExtension( ".script" ); + if ( fileSystem->ReadFile( scriptname, NULL, NULL ) > 0 ) { + gameLocal.program.CompileFile( scriptname ); + + // call the main function by default + func = gameLocal.program.FindFunction( "main" ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // call any functions specified in worldspawn + kv = spawnArgs.MatchPrefix( "call" ); + while( kv != NULL ) { + func = gameLocal.program.FindFunction( kv->GetValue() ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + + thread = new idThread( func ); + thread->DelayedStart( 0 ); + kv = spawnArgs.MatchPrefix( "call", kv ); + } +} + +/* +================= +idWorldspawn::Save +================= +*/ +void idWorldspawn::Save( idRestoreGame *savefile ) { +} + +/* +================= +idWorldspawn::Restore +================= +*/ +void idWorldspawn::Restore( idRestoreGame *savefile ) { + assert( gameLocal.world == this ); + +// RAVEN BEGIN +// bdube: gravity change + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY) ) ); +// RAVEN END +} + +/* +================ +idWorldspawn::~idWorldspawn +================ +*/ +idWorldspawn::~idWorldspawn() { + if ( gameLocal.world == this ) { + gameLocal.world = NULL; + } +} + +/* +================ +idWorldspawn::Event_Remove +================ +*/ +void idWorldspawn::Event_Remove( void ) { + gameLocal.Error( "Tried to remove world" ); +} diff --git a/source/mpgame/WorldSpawn.h b/source/mpgame/WorldSpawn.h new file mode 100644 index 0000000..1415336 --- /dev/null +++ b/source/mpgame/WorldSpawn.h @@ -0,0 +1,28 @@ + +#ifndef __GAME_WORLDSPAWN_H__ +#define __GAME_WORLDSPAWN_H__ + +/* +=============================================================================== + + World entity. + +=============================================================================== +*/ + +class idWorldspawn : public idEntity { +public: + CLASS_PROTOTYPE( idWorldspawn ); + + ~idWorldspawn(); + + void Spawn( void ); + + void Save( idRestoreGame *savefile ); + void Restore( idRestoreGame *savefile ); + +private: + void Event_Remove( void ); +}; + +#endif /* !__GAME_WORLDSPAWN_H__ */ diff --git a/source/mpgame/ai/AAS.cpp b/source/mpgame/ai/AAS.cpp new file mode 100644 index 0000000..f5c08a4 --- /dev/null +++ b/source/mpgame/ai/AAS.cpp @@ -0,0 +1,462 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +/* +============ +idAAS::Alloc +============ +*/ +idAAS *idAAS::Alloc( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + return new idAASLocal; +} + +/* +============ +idAAS::idAAS +============ +*/ +idAAS::~idAAS( void ) { +} + +/* +============ +idAASLocal::idAASLocal +============ +*/ +idAASLocal::idAASLocal( void ) { + file = NULL; +} + +/* +============ +idAASLocal::~idAASLocal +============ +*/ +idAASLocal::~idAASLocal( void ) { + Shutdown(); +} + +/* +============ +idAASLocal::Init +============ +*/ +bool idAASLocal::Init( const idStr &mapName, unsigned int mapFileCRC ) { + if ( file && mapName.Icmp( file->GetName() ) == 0 && mapFileCRC == file->GetCRC() ) { + gameLocal.Printf( "Keeping %s\n", file->GetName() ); + RemoveAllObstacles(); + } + else { + Shutdown(); + + file = AASFileManager->LoadAAS( mapName, mapFileCRC ); + if ( !file ) { + common->DWarning( "Couldn't load AAS file: '%s'", mapName.c_str() ); + return false; + } +// RAVEN BEGIN +// rhummer: Check if this is a dummy file, since it really has no valid data dump it. + else if ( file->IsDummyFile( mapFileCRC ) ) { + AASFileManager->FreeAAS( file ); + file = NULL; + return false; + } +// RAVEN END + SetupRouting(); + } + return true; +} + +/* +============ +idAASLocal::Shutdown +============ +*/ +void idAASLocal::Shutdown( void ) { + if ( file ) { + ShutdownRouting(); + RemoveAllObstacles(); + AASFileManager->FreeAAS( file ); + file = NULL; + } +} + +/* +============ +idAASLocal::Stats +============ +*/ +void idAASLocal::Stats( void ) const { + if ( !file ) { + return; + } + common->Printf( "[%s]\n", file->GetName() ); + file->PrintInfo(); + RoutingStats(); +} + +// RAVEN BEGIN +// jscott: added +/* +============ +idAASLocal::StatsSummary +============ +*/ +size_t idAASLocal::StatsSummary( void ) const { + + int size; + + if( !file ) { + + return( 0 ); + } + + size = ( numAreaTravelTimes * sizeof( unsigned short ) ) + + ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) + + ( portalCacheIndexSize * sizeof( idRoutingCache * ) ); + + return( file->GetMemorySize() + size ); +} +// RAVEN END + +/* +============ +idAASLocal::GetSettings +============ +*/ +const idAASSettings *idAASLocal::GetSettings( void ) const { + if ( !file ) { + return NULL; + } + return &file->GetSettings(); +} + +/* +============ +idAASLocal::PointAreaNum +============ +*/ +int idAASLocal::PointAreaNum( const idVec3 &origin ) const { + if ( !file ) { + return 0; + } + return file->PointAreaNum( origin ); +} + +/* +============ +idAASLocal::PointReachableAreaNum +============ +*/ +int idAASLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->PointReachableAreaNum( origin, searchBounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::BoundsReachableAreaNum +============ +*/ +int idAASLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->BoundsReachableAreaNum( bounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::PushPointIntoAreaNum +============ +*/ +void idAASLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { + if ( !file ) { + return; + } + file->PushPointIntoAreaNum( areaNum, origin ); +} + +/* +============ +idAASLocal::AreaCenter +============ +*/ +idVec3 idAASLocal::AreaCenter( int areaNum ) const { + if ( !file ) { + return vec3_origin; + } + return file->GetArea( areaNum ).center; +} + +// RAVEN BEGIN +// bdube: added +/* +============ +idAASLocal::AreaRadius +============ +*/ +float idAASLocal::AreaRadius( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).bounds.GetRadius(); +} +// mcg: added +/* +============ +idAASLocal::AreaBounds +============ +*/ +idBounds & idAASLocal::AreaBounds( int areaNum ) const { + return file->GetArea( areaNum ).bounds; +} +/* +============ +idAASLocal::AreaCeiling +============ +*/ +float idAASLocal::AreaCeiling( int areaNum ) const { + return file->GetArea( areaNum ).ceiling; +} +// RAVEN END + +/* +============ +idAASLocal::AreaFlags +============ +*/ +int idAASLocal::AreaFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).flags; +} + +/* +============ +idAASLocal::AreaTravelFlags +============ +*/ +int idAASLocal::AreaTravelFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).travelFlags; +} + +/* +============ +idAASLocal::Trace +============ +*/ +bool idAASLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + if ( !file ) { + trace.fraction = 0.0f; + trace.lastAreaNum = 0; + trace.numAreas = 0; + return true; + } + return file->Trace( trace, start, end ); +} + +/* +============ +idAASLocal::GetPlane +============ +*/ +const idPlane &idAASLocal::GetPlane( int planeNum ) const { + if ( !file ) { + static idPlane dummy; + return dummy; + } + return file->GetPlane( planeNum ); +} + +/* +============ +idAASLocal::GetEdgeVertexNumbers +============ +*/ +void idAASLocal::GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const { + if ( !file ) { + verts[0] = verts[1] = 0; + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + verts[0] = v[INTSIGNBITSET(edgeNum)]; + verts[1] = v[INTSIGNBITNOTSET(edgeNum)]; +} + +/* +============ +idAASLocal::GetEdge +============ +*/ +void idAASLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( !file ) { + start.Zero(); + end.Zero(); + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + start = file->GetVertex( v[INTSIGNBITSET(edgeNum)] ); + end = file->GetVertex( v[INTSIGNBITNOTSET(edgeNum)] ); +} + + +/* +=============================================================================== + + idAASCallback + +=============================================================================== +*/ + +/* +============ +idAASCallback::~idAASCallback +============ +*/ +idAASCallback::~idAASCallback ( void ) { +} + +/* +============ +idAASCallback::Test +============ +*/ +idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ) { + // Get AAS file + idAASFile* file = ((idAAS&)*aas).GetFile ( ); + if ( !file ) { + return TEST_BADAREA; + } + + // Get area for edges + aasArea_t& area = file->GetArea ( areaNum ); + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorYellow, area.center, area.center + idVec3(0,0,80.0f), 10000 ); + } + + // Make sure the area itself is valid + if ( !TestArea ( aas, areaNum, area ) ) { + return TEST_BADAREA; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 && point ) { + gameRenderWorld->DebugLine ( colorMagenta, *point, *point + idVec3(0,0,64.0f), 10000 ); + } + + // Test the original origin first + if ( point && TestPointDistance ( origin, *point, minDistance, maxDistance) && TestPoint ( aas, *point ) ) { + goal.areaNum = areaNum; + goal.origin = *point; + return TEST_OK; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorCyan, area.center, area.center + idVec3(0,0,64.0f), 10000 ); + } + + // Test the center of the area + if ( TestPointDistance ( origin, area.center, minDistance, maxDistance) && TestPoint ( aas, area.center, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = area.center; + return TEST_OK; + } + + // For each face test all available edges + int f; + int e; + for ( f = 0; f < area.numFaces; f ++ ) { + aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) ); + + // for each edge test a point between the center of the edge and the center + for ( e = 0; e < face.numEdges; e ++ ) { + idVec3 edgeCenter = file->EdgeCenter ( abs( file->GetEdgeIndex( face.firstEdge + e ) ) ); + idVec3 dir = area.center - edgeCenter; + float dist; + for ( dist = dir.Normalize() - 64.0f; dist > 0.0f; dist -= 64.0f ) { + idVec3 testPoint = edgeCenter + dir * dist; + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorPurple, testPoint, testPoint + idVec3(0,0,64.0f), 10000 ); + } + + if ( TestPointDistance ( origin, testPoint, minDistance, maxDistance) && TestPoint ( aas, testPoint, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = testPoint; + return TEST_OK; + } + } + } + } + + return TEST_BADPOINT; +} + +/* +============ +idAASCallback::Init +============ +*/ +bool idAASCallback::TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ) { + float dist = (origin - point).LengthFast ( ); + if ( minDistance > 0.0f && dist < minDistance ) { + return false; + } + if ( maxDistance > 0.0f && dist > maxDistance ) { + return false; + } + return true; +} + +/* +============ +idAASCallback::Init +============ +*/ +void idAASCallback::Init ( void ) { +} + +/* +============ +idAASCallback::Finish +============ +*/ +void idAASCallback::Finish ( void ) { +} + +/* +============ +idAASCallback::TestArea +============ +*/ +bool idAASCallback::TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ) { + return true; +} + +/* +============ +idAASCallback::TestPoint +============ +*/ +bool idAASCallback::TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow ) { + return true; +} + +// RAVEN END diff --git a/source/mpgame/ai/AAS.h b/source/mpgame/ai/AAS.h new file mode 100644 index 0000000..cc8dbe6 --- /dev/null +++ b/source/mpgame/ai/AAS.h @@ -0,0 +1,163 @@ + +#ifndef __AAS_H__ +#define __AAS_H__ + +/* +=============================================================================== + + Area Awareness System + +=============================================================================== +*/ + +enum { + PATHTYPE_WALK, + PATHTYPE_WALKOFFLEDGE, + PATHTYPE_BARRIERJUMP, + PATHTYPE_JUMP +}; + +typedef struct aasPath_s { + int type; // path type + idVec3 moveGoal; // point the AI should move towards + int moveAreaNum; // number of the area the AI should move towards + idVec3 secondaryGoal; // secondary move goal for complex navigation + const idReachability * reachability; // reachability used for navigation +} aasPath_t; + + +typedef struct aasGoal_s { + int areaNum; // area the goal is in + idVec3 origin; // position of goal +} aasGoal_t; + + +typedef struct aasObstacle_s { + idBounds absBounds; // absolute bounds of obstacle + idBounds expAbsBounds; // expanded absolute bounds of obstacle +} aasObstacle_t; + +class idAASCallback { +public: + virtual ~idAASCallback ( void ); + + enum testResult_t { + TEST_OK, + TEST_BADAREA, + TEST_BADPOINT + }; + + virtual void Init ( void ); + virtual void Finish ( void ); + + testResult_t Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + bool TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ); +}; + +typedef int aasHandle_t; + +class idAAS { +public: + static idAAS * Alloc( void ); + virtual ~idAAS( void ) = 0; + // Initialize for the given map. + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ) = 0; +// RAVEN BEGIN +// jscott: added + // Prints out the memory used by this AAS + virtual size_t StatsSummary( void ) const = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Shutdown( void ) = 0; +#endif +// RAVEN END + // Print AAS stats. + virtual void Stats( void ) const = 0; + // Test from the given origin. + virtual void Test( const idVec3 &origin ) = 0; + // Get the AAS settings. + virtual const idAASSettings *GetSettings( void ) const = 0; + // Returns the number of the area the origin is in. + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + // Returns the number of the nearest reachable area for the given point. + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &bounds, const int areaFlags ) const = 0; + // Returns the number of the first reachable area in or touching the bounds. + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const = 0; + // Push the point into the area. + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const = 0; + // Returns a reachable point inside the given area. + virtual idVec3 AreaCenter( int areaNum ) const = 0; +// RAVEN BEGIN +// bdube: added + // Returns a reachable point inside the given area. + virtual float AreaRadius( int areaNum ) const = 0; + virtual idBounds & AreaBounds( int areaNum ) const = 0; + virtual float AreaCeiling( int areaNum ) const = 0; +// RAVEN END + // Returns the area flags. + virtual int AreaFlags( int areaNum ) const = 0; + // Returns the travel flags for traveling through the area. + virtual int AreaTravelFlags( int areaNum ) const = 0; + // Trace through the areas and report the first collision. + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + // Get a plane for a trace. + virtual const idPlane & GetPlane( int planeNum ) const = 0; + // Get wall edges. + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const = 0; + // Sort the wall edges to create continuous sequences of walls. + virtual void SortWallEdges( int *edges, int numEdges ) const = 0; + // Get the vertex numbers for an edge. + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const = 0; + // Get an edge. + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Find all areas within or touching the bounds with the given contents and disable/enable them for routing. + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) = 0; + // Add an obstacle to the routing system. + virtual aasHandle_t AddObstacle( const idBounds &bounds ) = 0; + // Remove an obstacle from the routing system. + virtual void RemoveObstacle( const aasHandle_t handle ) = 0; + // Remove all obstacles from the routing system. + virtual void RemoveAllObstacles( void ) = 0; + // Returns the travel time towards the goal area in 100th of a second. + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const = 0; + // Get the travel time and first reachability to be used towards the goal, returns true if there is a path. + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const = 0; + // Creates a walk path towards the goal. + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can walk along a straight line from the origin to the goal origin. + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Creates a fly path towards the goal. + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can fly along a straight line from the origin to the goal origin. + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Show the walk path from the origin towards the area. + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Show the fly path from the origin towards the area. + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Find the nearest goal which satisfies the callback. + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const = 0; + +// RAVEN BEGIN +// CDR : Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) = 0; +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ) = 0; + +// rjohnson: added more debug drawing + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const = 0; + virtual bool IsValid( void ) const = 0; +// RAVEN END +}; + +#endif /* !__AAS_H__ */ diff --git a/source/mpgame/ai/AAS_Find.cpp b/source/mpgame/ai/AAS_Find.cpp new file mode 100644 index 0000000..f2de154 --- /dev/null +++ b/source/mpgame/ai/AAS_Find.cpp @@ -0,0 +1,340 @@ +/* +=============================================================================== + +AAS_Find.cpp + +This file has all aas search classes. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + rvAASFindGoalForHide + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForHide::rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::rvAASFindGoalForHide( const idVec3 &hideFromPos ) { + int numPVSAreas; + idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) ); + + // setup PVS + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForHide::~rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::~rvAASFindGoalForHide() { + gameLocal.pvs.FreeCurrentPVS( hidePVS ); +} + +/* +rvAASFindGoalForHide +rvAASFindHide::TestArea +============ +*/ +bool rvAASFindGoalForHide::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ).TranslateSelf(idVec3(0,0,1)), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) { + return true; + } + + return false; +} + +/* +=============================================================================== + + rvAASFindGoalOutOfRange + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange +============ +*/ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange( idAI* _owner ) { + owner = _owner; +} + +/* +============ +rvAASFindAreaOutOfRange::TestArea +============ +*/ +bool rvAASFindGoalOutOfRange::TestPoint ( idAAS* aas, const idVec3& pos, const float zAllow ) { + return aiManager.ValidateDestination ( owner, pos ); +} + +/* +=============================================================================== + +rvAASFindGoalForAttack + +Find a position to move to that allows the ai to at their target + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForAttack::rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::rvAASFindGoalForAttack( idAI* _owner ) { + owner = _owner; + cachedIndex = 0; +} + +/* +============ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack ( void ) { +} + +/* +============ +rvAASFindGoalForAttack::Init +============ +*/ +void rvAASFindGoalForAttack::Init ( void ) { + // setup PVS + int numPVSAreas; + numPVSAreas = gameLocal.pvs.GetPVSAreas( owner->enemy.ent->GetPhysics()->GetAbsBounds(), PVSAreas, idEntity::MAX_PVS_AREAS ); + targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); + + cachedGoals.SetGranularity ( 1024 ); +} + +/* +============ +rvAASFindGoalForAttack::Finish +============ +*/ +void rvAASFindGoalForAttack::Finish ( void ) { + gameLocal.pvs.FreeCurrentPVS( targetPVS ); +} + +/* +============ +rvAASFindGoalForAttack::TestArea +============ +*/ +bool rvAASFindGoalForAttack::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + cachedAreaNum = areaNum; + + // If the whole area is out of range then skip it + float range; + range = area.bounds.ShortestDistance ( owner->enemy.lastKnownPosition ); + if ( range > owner->combat.attackRange[1] ) { + return false; + } + + // Out of pvs? + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + return gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForAttack::TestPoint +============ +*/ +bool rvAASFindGoalForAttack::TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow ) { + float dist; + + idVec3 localPoint = point; + float bestZ = owner->enemy.ent->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + + // Out of attack range? + dist = (localPoint - owner->enemy.ent->GetPhysics()->GetOrigin ( )).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // If tethered make sure the point is within the tether range + if ( owner->tether ) { + idVec3 localPoint = point; + float bestZ = owner->tether.GetEntity()->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !owner->tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + } + + aasGoal_t& goal = cachedGoals.Alloc ( ); + goal.areaNum = cachedAreaNum; + goal.origin = localPoint; + + return false; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedGoal +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoal ( int index ) { + const aasGoal_t& goal = cachedGoals[index]; + + // Out of attack range? + float dist = (goal.origin - owner->enemy.ent->GetPhysics()->GetOrigin ( ) ).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // Someone already there? + if ( !aiManager.ValidateDestination ( owner, goal.origin, true ) ) { + return false; + } + + // Can we see the enemy from this position? + if ( !owner->CanSeeFrom ( goal.origin - owner->GetPhysics()->GetGravityNormal ( ) * owner->combat.visStandHeight, owner->GetEnemy(), false ) ) { + return false; + } + + return true; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedPoints +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoals ( int count, aasGoal_t& goal ) { + int i; + + goal.areaNum = 0; + + // Test as many points as we are allowed to test + for ( i = 0; i < count && cachedIndex < cachedGoals.Num(); cachedIndex ++, i ++ ) { + // Retest simple checks + if ( TestCachedGoal( cachedIndex ) ) { + goal = cachedGoals[cachedIndex]; + return true; + } + } + + return !(cachedIndex >= cachedGoals.Num()); +} + +/* +=============================================================================== + +rvAASFindGoalForTether + +Find a goal to move to that is within the given tether. + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::rvAASFindGoalForTether( idAI* _owner, rvAITether* _tether ) { + owner = _owner; + tether = _tether; +} + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::~rvAASFindGoalForTether( void ) { +} + +/* +============ +rvAASFindGoalForTether::TestArea +============ +*/ +bool rvAASFindGoalForTether::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + // Test super class first + if ( !idAASCallback::TestArea ( aas, areaNum, area ) ) { + return false; + } + + // Make sure the area bounds is remotely valid for the tether + idBounds tempBounds = area.bounds; + tempBounds[1].z = area.ceiling; + if ( !tether->ValidateBounds ( tempBounds ) ) { + return false; + } + return true; +} + +/* +============ +rvAASFindGoalForTether::TestPoint +============ +*/ +bool rvAASFindGoalForTether::TestPoint ( class idAAS* aas, const idVec3& point, const float zAllow ) { + if ( !tether ) { + return false; + } + + idVec3 localPoint = point; + float bestZ = tether->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + + if ( !aiManager.ValidateDestination ( owner, localPoint ) ) { + return false; + } + + return true; +} diff --git a/source/mpgame/ai/AAS_Find.h b/source/mpgame/ai/AAS_Find.h new file mode 100644 index 0000000..20e6b4f --- /dev/null +++ b/source/mpgame/ai/AAS_Find.h @@ -0,0 +1,112 @@ +/* +================ + +AAS_Find.h + +================ +*/ + +#ifndef __AAS_FIND__ +#define __AAS_FIND__ + +class idAI; +class rvAIHelper; + +/* +=============================================================================== + rvAASFindHide +=============================================================================== +*/ + +class rvAASFindGoalForHide : public idAASCallback { +public: + rvAASFindGoalForHide ( const idVec3 &hideFromPos ); + ~rvAASFindGoalForHide ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + +private: + + pvsHandle_t hidePVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +/* +=============================================================================== + rvAASFindAreaOutOfRange +=============================================================================== +*/ + +class rvAASFindGoalOutOfRange : public idAASCallback { +public: + + rvAASFindGoalOutOfRange ( idAI* _owner ); + +protected: + + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + +private: + + idAI* owner; +}; + +/* +=============================================================================== + rvAASFindAttackPosition +=============================================================================== +*/ + +class rvAASFindGoalForAttack : public idAASCallback { +public: + rvAASFindGoalForAttack ( idAI *self ); + ~rvAASFindGoalForAttack ( void ); + + + bool TestCachedGoals ( int count, aasGoal_t& goal ); + + virtual void Init ( void ); + virtual void Finish ( void ); + +private: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + + bool TestCachedGoal ( int index ); + + idAI* owner; + + pvsHandle_t targetPVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + idList cachedGoals; + int cachedIndex; + int cachedAreaNum; +}; + +/* +=============================================================================== + rvAASFindGoalForTether +=============================================================================== +*/ + +class rvAASFindGoalForTether : public idAASCallback { +public: + rvAASFindGoalForTether ( idAI* owner, rvAITether* helper ); + ~rvAASFindGoalForTether ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS* aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + idAI* owner; + rvAITether* tether; +}; + +#endif // __AAS_FIND__ diff --git a/source/mpgame/ai/AAS_debug.cpp b/source/mpgame/ai/AAS_debug.cpp new file mode 100644 index 0000000..f35a5d0 --- /dev/null +++ b/source/mpgame/ai/AAS_debug.cpp @@ -0,0 +1,909 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for cvars and debug drawing +#include "AI.h" +#include "AAS_Find.h" + + +/* +============ +idAASLocal::DrawCone +============ +*/ +void idAASLocal::DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const { + int i; + idMat3 axis; + idVec3 center, top, p, lastp; + + axis[2] = dir; + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + center = origin + dir; + top = center + dir * (3.0f * radius); + lastp = center + radius * axis[1]; + + for ( i = 20; i <= 360; i += 20 ) { + p = center + idMath::Sin( DEG2RAD(i) ) * radius * axis[0] + idMath::Cos( DEG2RAD(i) ) * radius * axis[1]; + gameRenderWorld->DebugLine( color, lastp, p, 0 ); + gameRenderWorld->DebugLine( color, p, top, 0 ); + lastp = p; + } +} + +/* +============ +idAASLocal::DrawReachability +============ +*/ +void idAASLocal::DrawReachability( const idReachability *reach ) const { + gameRenderWorld->DebugArrow( colorCyan, reach->start, reach->end, 2 ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", reach->edgeNum ), ( reach->start + reach->end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis ); + } + + switch( reach->travelType ) { + case TFL_WALK: { +// const idReachability_Walk *walk = static_cast(reach); + break; + } + default: { + break; + } + } +} + +/* +============ +idAASLocal::DrawEdge +============ +*/ +void idAASLocal::DrawEdge( int edgeNum, bool arrow ) const { + const aasEdge_t *edge; + idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &colorRed; + if ( arrow ) { + gameRenderWorld->DebugArrow( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ), 1 ); + } else { + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); + } + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", edgeNum ), ( file->GetVertex( edge->vertexNum[0] ) + file->GetVertex( edge->vertexNum[1] ) ) * 0.5f + idVec3(0,0,4), 0.1f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } +} + +/* +============ +idAASLocal::DrawFace +============ +*/ +void idAASLocal::DrawFace( int faceNum, bool side ) const { + int i, j, numEdges, firstEdge; + const aasFace_t *face; + idVec3 mid, end; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + if ( !numEdges ) + {//wtf? A face with no edges?! + return; + } + + mid = vec3_origin; + for ( i = 0; i < numEdges; i++ ) { + DrawEdge( abs( file->GetEdgeIndex( firstEdge + i ) ), ( face->flags & FACE_FLOOR ) != 0 ); + j = file->GetEdgeIndex( firstEdge + i ); + mid += file->GetVertex( file->GetEdge( abs( j ) ).vertexNum[ j < 0 ] ); + } + + mid /= numEdges; + if ( side ) { + end = mid - 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } else { + end = mid + 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } + gameRenderWorld->DebugArrow( colorGreen, mid, end, 1 ); +} + +/* +============ +idAASLocal::DrawAreaBounds +============ +*/ +void idAASLocal::DrawAreaBounds( int areaNum ) const { + const aasArea_t *area; + if ( !file ) { + return; + } + area = &file->GetArea( areaNum ); + + idVec3 points[8]; + bool drawn[8][8]; + memset( drawn, false, sizeof( drawn ) ); + + area->bounds.ToPoints( points ); + for ( int p1 = 0; p1 < 8; p1++ ) { + for ( int p2 = 0; p2 < 8; p2++ ) { + if ( !drawn[p2][p1] ) { + if ( (points[p1].x == points[p2].x && (points[p1].y == points[p2].y||points[p1].z == points[p2].z)) + || (points[p1].y == points[p2].y && (points[p1].x == points[p2].x||points[p1].z == points[p2].z)) + || (points[p1].z == points[p2].z && (points[p1].x == points[p2].x||points[p1].y == points[p2].y)) ) { + //an edge + gameRenderWorld->DebugLine( colorRed, points[p1], points[p2] ); + drawn[p1][p2] = true; + } + } + } + } +} + +/* +============ +idAASLocal::DrawArea +============ +*/ +void idAASLocal::DrawArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idReachability *reach; + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawFace( abs( file->GetFaceIndex( firstFace + i ) ), file->GetFaceIndex( firstFace + i ) < 0 ); + } + + if (aas_showRevReach.GetInteger()) + { + for ( reach = area->rev_reach; reach; reach = reach->rev_next ) { + DrawReachability( reach ); + } + } + else + { + for ( reach = area->reach; reach; reach = reach->next ) { + DrawReachability( reach ); + } + } +} + +/* +============ +idAASLocal::DefaultSearchBounds +============ +*/ +const idBounds &idAASLocal::DefaultSearchBounds( void ) const { + return file->GetSettings().boundingBoxes[0]; +} + +/* +============ +idAASLocal::ShowArea +============ +*/ +void idAASLocal::ShowArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + const aasArea_t *area; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + org = origin; + PushPointIntoAreaNum( areaNum, org ); + + if ( aas_goalArea.GetInteger() ) { + int travelTime; + idReachability *reach; + + RouteToGoalArea( areaNum, org, aas_goalArea.GetInteger(), TFL_WALK|TFL_AIR, travelTime, &reach ); + gameLocal.Printf( "\rtt = %4d", travelTime ); + if ( reach ) { + gameLocal.Printf( " to area %4d", reach->toAreaNum ); + DrawArea( reach->toAreaNum ); + } + } + + if ( areaNum != lastAreaNum ) { + area = &file->GetArea( areaNum ); + gameLocal.Printf( "area %d: ", areaNum ); + if ( area->flags & AREA_LEDGE ) { + gameLocal.Printf( "AREA_LEDGE " ); + } + if ( area->flags & AREA_REACHABLE_WALK ) { + gameLocal.Printf( "AREA_REACHABLE_WALK " ); + } + if ( area->flags & AREA_REACHABLE_FLY ) { + gameLocal.Printf( "AREA_REACHABLE_FLY " ); + } + if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) { + gameLocal.Printf( "AREACONTENTS_CLUSTERPORTAL " ); + } + if ( area->contents & AREACONTENTS_OBSTACLE ) { + gameLocal.Printf( "AREACONTENTS_OBSTACLE " ); + } + gameLocal.Printf( "\n" ); + lastAreaNum = areaNum; + } + + if ( org != origin ) { + idBounds bnds = file->GetSettings().boundingBoxes[ 0 ]; + bnds[ 1 ].z = bnds[ 0 ].z; + gameRenderWorld->DebugBounds( colorYellow, bnds, org ); + } + + DrawArea( areaNum ); +} + +// RAVEN BEGIN +// rjohnson: added more debug drawing +/* +============ +idAASLocal::DrawSimpleEdge +============ +*/ +void idAASLocal::DrawSimpleEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &file->GetSettings().debugColor; + + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); +} + +/* +============ +idAASLocal::DrawSimpleFace +============ +*/ +const int MAX_AAS_WALL_EDGES = 256; + +// RAVEN BEGIN +// cdr: added visited check +void idAASLocal::DrawSimpleFace( int faceNum, bool visited ) const { +// RAVEN END + int i, numEdges, firstEdge, edgeNum; + const aasFace_t *face; + idVec3 sides[MAX_AAS_WALL_EDGES]; + const aasEdge_t *edge, *nextEdge; + idVec4 color2; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + DrawSimpleEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } + + if ( numEdges >= 2 && numEdges <= MAX_AAS_WALL_EDGES ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + edgeNum = abs( file->GetEdgeIndex( firstEdge + 1 ) ); + nextEdge = &file->GetEdge( abs( edgeNum ) ); + + // need to find the first common edge so that we go form the polygon in the right direction + if ( file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[0] ) || + file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[1] ) ) { + sides[ 0 ] = file->GetVertex( edge->vertexNum[0] ); + } else { + sides[ 0 ] = file->GetVertex( edge->vertexNum[1] ); + } + + for ( i = 1; i < numEdges; i++ ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge + i ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + + if ( sides[ i-1 ] == file->GetVertex( edge->vertexNum[0] ) ) { + sides[ i ] = file->GetVertex( edge->vertexNum[1] ); + } else { + sides[ i ] = file->GetVertex( edge->vertexNum[0] ); + } + } + + color2 = file->GetSettings().debugColor; + color2[3] = 0.20f; + + // RAVEN BEGIN + // cdr: added visited check + if (!visited) + { + color2[3] = 0.05f; + } + // RAVEN END + idWinding winding( sides, numEdges ); + gameRenderWorld->DebugPolygon( color2, winding, 0, true ); + } +} + + +/* +============ +idAASLocal::DrawSimpleArea +============ +*/ +void idAASLocal::DrawSimpleArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawSimpleFace( abs( file->GetFaceIndex( firstFace + i )), true ); + } +} + +/* +============ +idAASLocal::IsValid +============ +*/ +bool idAASLocal::IsValid( void ) const { + if ( !file ) { + return false; + } + + return true; +} + +/* +============ +idAASLocal::ShowAreas +============ +*/ +void idAASLocal::ShowAreas( const idVec3 &origin, bool ShowProblemAreas ) const { + int i, areaNum; + idPlayer *player; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + idReachability *reach; + int travelFlags = (TFL_WALK); + const idBounds bounds = idBounds( origin ).Expand( 256.0f ); + + if ( !file ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = 0; + queueEnd = 1; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + area = &file->GetArea( curArea ); + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + + if ( ShowProblemAreas ) { + for ( i = 0; i < queueEnd; i++ ) { + ShowProblemArea( areaQueue[ i ] ); + } + } else { + for ( i = 0; i < queueEnd; i++ ) { + DrawSimpleArea( areaQueue[ i ] ); + } + } +} + +/* +============ +idAASLocal::ShowProblemEdge +============ +*/ +void idAASLocal::ShowProblemEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + idVec4 color2; + idTraceModel trm; + idClipModel mdl; + idVec3 sides[4]; + trace_t results; + idVec3 forward, forwardNormal, left, down, start, end; + float hullSize, hullHeight; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + + start = (idVec3)file->GetVertex( edge->vertexNum[0] ); + end = (idVec3)file->GetVertex( edge->vertexNum[1] ); + forward = end - start ; + forward.Normalize(); + forwardNormal = forward; + forward.NormalVectors( left, down ); + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ) - 1.0f; // assumption that x and y are the same size + hullHeight = ( file->GetSettings().boundingBoxes[0][1][2] - file->GetSettings().boundingBoxes[0][0][2] ); + + left *= hullSize; + forward *= hullSize; + + sides[0] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[1] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[2] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + sides[3] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + trm.SetupPolygon( sides, 4 ); + mdl.LoadModel( trm, NULL ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( gameLocal.GetLocalPlayer(), results, start, end, &mdl, mat3_identity, MASK_MONSTERSOLID, gameLocal.GetLocalPlayer() ); +// RAVEN END + + if ( results.fraction != 1.0f ) { + color = &file->GetSettings().debugColor; + gameRenderWorld->DebugLine( *color, start - left, end - left ); + gameRenderWorld->DebugLine( *color, start + left, end + left ); + + color = &colorYellow; + gameRenderWorld->DebugLine( *color, start, end ); + + color2 = colorOrange; + color2[3] = 0.25f; + sides[0] += results.endpos; + sides[1] += results.endpos; + sides[2] += results.endpos; + sides[3] += results.endpos; + idWinding winding( sides, 4 ); + gameRenderWorld->DebugPolygon( color2, winding ); + } +} + +/* +============ +idAASLocal::ShowProblemFace +============ +*/ +void idAASLocal::ShowProblemFace( int faceNum ) const { + int i, numEdges, firstEdge; + const aasFace_t *face; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + ShowProblemEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + float hullSize; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + numFaces = area->numFaces; + firstFace = area->firstFace; + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ); // assumption that x and y are the same size + + bounds = area->bounds; + bounds.Expand( hullSize ); +// RAVEN BEGIN +// ddynerman: multiple clip world + numListedEntities = gameLocal.EntitiesTouchingBounds( gameLocal.GetLocalPlayer(), bounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entityList[ i ]->IsType( idMoveable::GetClassType() ) || + entityList[ i ]->IsType( idMover::GetClassType() ) || + entityList[ i ]->IsType( idAI::GetClassType() ) ) { +// RAVEN END + entityList[ i ]->GetPhysics()->DisableClip(); + continue; + } + + entityList[ i ] = NULL; + } + + for ( i = 0; i < numFaces; i++ ) { + ShowProblemFace( abs( file->GetFaceIndex( firstFace + i ) ) ); + } + + for( i = 0; i < numListedEntities; i++) { + if ( entityList[ i ] ) { + entityList[ i ]->GetPhysics()->EnableClip(); + } + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + ShowProblemArea( areaNum ); +} + +// RAVEN END + +/* +============ +idAASLocal::ShowWalkPath +============ +*/ +void idAASLocal::ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_WALK ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorGreen, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( WalkPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowFlyPath +============ +*/ +void idAASLocal::ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_FLY ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_FLY|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorPurple, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( FlyPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_FLY|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowWallEdges +============ +*/ +void idAASLocal::ShowWallEdges( const idVec3 &origin ) const { + int i, areaNum, numEdges, edges[1024]; + idVec3 start, end; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + numEdges = GetWallEdges( areaNum, idBounds( origin ).Expand( 256.0f ), TFL_WALK, edges, 1024 ); + for ( i = 0; i < numEdges; i++ ) { + GetEdge( edges[i], start, end ); + gameRenderWorld->DebugLine( colorRed, start, end ); + gameRenderWorld->DrawText( va( "%d", edges[i] ), ( start + end ) * 0.5f, 0.1f, colorWhite, player->viewAxis ); + } +} + +/* +============ +idAASLocal::ShowHideArea +============ +*/ +void idAASLocal::ShowHideArea( const idVec3 &origin, int targetAreaNum ) const { + int areaNum, numObstacles; + idVec3 target; + aasGoal_t goal; + aasObstacle_t obstacles[10]; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + target = AreaCenter( targetAreaNum ); + + // consider the target an obstacle + obstacles[0].absBounds = idBounds( target ).Expand( 16 ); + numObstacles = 1; + + DrawCone( target, idVec3(0,0,1), 16.0f, colorYellow ); + + rvAASFindGoalForHide findHide ( target ); + if ( FindNearestGoal( goal, areaNum, origin, target, TFL_WALK|TFL_AIR, 0.0f, 0.0f, obstacles, numObstacles, findHide ) ) { + DrawArea( goal.areaNum ); + ShowWalkPath( origin, goal.areaNum, goal.origin ); + DrawCone( goal.origin, idVec3(0,0,1), 16.0f, colorWhite ); + } +} + +/* +============ +idAASLocal::PullPlayer +============ +*/ +bool idAASLocal::PullPlayer( const idVec3 &origin, int toAreaNum ) const { + int areaNum; + idVec3 areaCenter, dir, vel; + idAngles delta; + aasPath_t path; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return true; + } + + idPhysics *physics = player->GetPhysics(); + if ( !physics ) { + return true; + } + + if ( !toAreaNum ) { + return false; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + areaCenter = AreaCenter( toAreaNum ); + if ( player->GetPhysics()->GetAbsBounds().Expand( 8 ).ContainsPoint( areaCenter ) ) { + return false; + } + if ( WalkPathToGoal( path, areaNum, origin, toAreaNum, areaCenter, TFL_WALK|TFL_AIR ) ) { + dir = path.moveGoal - origin; + dir[2] *= 0.5f; + dir.Normalize(); + delta = dir.ToAngles() - player->cmdAngles - player->GetDeltaViewAngles(); + delta.Normalize180(); + player->SetDeltaViewAngles( player->GetDeltaViewAngles() + delta * 0.1f ); + dir[2] = 0.0f; + dir.Normalize(); + dir *= 100.0f; + vel = physics->GetLinearVelocity(); + dir[2] = vel[2]; + physics->SetLinearVelocity( dir ); + return true; + } + else { + return false; + } +} + +/* +============ +idAASLocal::RandomPullPlayer +============ +*/ +void idAASLocal::RandomPullPlayer( const idVec3 &origin ) const { + int rnd, i, n; + + if ( !PullPlayer( origin, aas_pullPlayer.GetInteger() ) ) { + + rnd = gameLocal.random.RandomFloat() * file->GetNumAreas(); + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + n = (rnd + i) % file->GetNumAreas(); + if ( file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) { + aas_pullPlayer.SetInteger( n ); + } + } + } else { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + } +} + +/* +============ +idAASLocal::ShowPushIntoArea +============ +*/ +void idAASLocal::ShowPushIntoArea( const idVec3 &origin ) const { + int areaNum; + idVec3 target; + + target = origin; + areaNum = PointReachableAreaNum( target, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + PushPointIntoAreaNum( areaNum, target ); + gameRenderWorld->DebugArrow( colorGreen, origin, target, 1 ); +} + +/* +============ +idAASLocal::Test +============ +*/ +void idAASLocal::Test( const idVec3 &origin ) { + + if ( !file ) { + return; + } + + if ( aas_randomPullPlayer.GetBool() ) { + RandomPullPlayer( origin ); + } + if ( ( aas_pullPlayer.GetInteger() > 0 ) && ( aas_pullPlayer.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + PullPlayer( origin, aas_pullPlayer.GetInteger() ); + } + if ( ( aas_showPath.GetInteger() > 0 ) && ( aas_showPath.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_showPath.GetInteger(), AreaCenter( aas_showPath.GetInteger() ) ); + } + if ( ( aas_showFlyPath.GetInteger() > 0 ) && ( aas_showFlyPath.GetInteger() < file->GetNumAreas() ) ) { + ShowFlyPath( origin, aas_showFlyPath.GetInteger(), AreaCenter( aas_showFlyPath.GetInteger() ) ); + } + if ( ( aas_showHideArea.GetInteger() > 0 ) && ( aas_showHideArea.GetInteger() < file->GetNumAreas() ) ) { + ShowHideArea( origin, aas_showHideArea.GetInteger() ); + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 1 ) { + ShowArea( origin ); + } + else if ( aas_showAreas.GetInteger() == 2 ) { + ShowAreas( origin ); + } + if ( aas_showProblemAreas.GetInteger() == 1 ) { + ShowProblemArea( origin ); + } + else if ( aas_showProblemAreas.GetInteger() == 2 ) { + ShowAreas( origin, true ); + } +// RAVEN END + if ( aas_showWallEdges.GetBool() ) { + ShowWallEdges( origin ); + } + if ( aas_showPushIntoArea.GetBool() ) { + ShowPushIntoArea( origin ); + } +} diff --git a/source/mpgame/ai/AAS_local.h b/source/mpgame/ai/AAS_local.h new file mode 100644 index 0000000..35a7f73 --- /dev/null +++ b/source/mpgame/ai/AAS_local.h @@ -0,0 +1,193 @@ + +#ifndef __AAS_LOCAL_H__ +#define __AAS_LOCAL_H__ + +#include "AAS.h" +#include "../Pvs.h" + + +class idRoutingCache { + friend class idAASLocal; + +public: + idRoutingCache( int size ); + ~idRoutingCache( void ); + + int Size( void ) const; + +private: + int type; // portal or area cache + int size; // size of cache + int cluster; // cluster of the cache + int areaNum; // area of the cache + int travelFlags; // combinations of the travel flags + idRoutingCache * next; // next in list + idRoutingCache * prev; // previous in list + idRoutingCache * time_next; // next in time based list + idRoutingCache * time_prev; // previous in time based list + unsigned short startTravelTime; // travel time to start with + unsigned char * reachabilities; // reachabilities used for routing + unsigned short * travelTimes; // travel time for every area +}; + + +class idRoutingUpdate { + friend class idAASLocal; + +private: + int cluster; // cluster number of this update + int areaNum; // area number of this update + unsigned short tmpTravelTime; // temporary travel time + unsigned short * areaTravelTimes; // travel times within the area + idVec3 start; // start point into area + idRoutingUpdate * next; // next in list + idRoutingUpdate * prev; // prev in list + bool isInList; // true if the update is in the list +}; + + +class idRoutingObstacle { + friend class idAASLocal; + idRoutingObstacle( void ) { } + +private: + idBounds bounds; // obstacle bounds + idList areas; // areas the bounds are in +}; + +class idAASLocal : public idAAS { +public: + idAASLocal( void ); + virtual ~idAASLocal( void ); + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ); + virtual void Shutdown( void ); +// RAVEN BEGIN +// jscott: added summary flag + virtual size_t StatsSummary( void ) const; +// RAVEN END + virtual void Stats( void ) const; + virtual void Test( const idVec3 &origin ); + virtual const idAASSettings *GetSettings( void ) const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; + virtual idVec3 AreaCenter( int areaNum ) const; +// RAVEN BEGIN +// bdube: added + virtual float AreaRadius( int areaNum ) const; + virtual idBounds & AreaBounds( int areaNum ) const; + virtual float AreaCeiling( int areaNum ) const; +// RAVEN END + virtual int AreaFlags( int areaNum ) const; + virtual int AreaTravelFlags( int areaNum ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual const idPlane & GetPlane( int planeNum ) const; + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const; + virtual void SortWallEdges( int *edges, int numEdges ) const; + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const; + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); + virtual aasHandle_t AddObstacle( const idBounds &bounds ); + virtual void RemoveObstacle( const aasHandle_t handle ); + virtual void RemoveAllObstacles( void ); + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const; + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const; +// RAVEN BEGIN +// creed: Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) {return file;} +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ); +// rjohnson: added more debug drawing + virtual bool IsValid( void ) const; + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const; +// RAVEN END + + +private: + idAASFile * file; + idStr name; + +private: // routing data + idRoutingCache *** areaCacheIndex; // for each area in each cluster the travel times to all other areas in the cluster + int areaCacheIndexSize; // number of area cache entries + idRoutingCache ** portalCacheIndex; // for each area in the world the travel times from each portal + int portalCacheIndexSize; // number of portal cache entries + idRoutingUpdate * areaUpdate; // memory used to update the area routing cache + idRoutingUpdate * portalUpdate; // memory used to update the portal routing cache + unsigned short * goalAreaTravelTimes; // travel times to goal areas + unsigned short * areaTravelTimes; // travel times through the areas + int numAreaTravelTimes; // number of area travel times + mutable idRoutingCache * cacheListStart; // start of list with cache sorted from oldest to newest + mutable idRoutingCache * cacheListEnd; // end of list with cache sorted from oldest to newest + mutable int totalCacheMemory; // total cache memory used + idList obstacleList; // list with obstacles + +private: // routing + bool SetupRouting( void ); + void ShutdownRouting( void ); + unsigned short AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const; + void CalculateAreaTravelTimes( void ); + void DeleteAreaTravelTimes( void ); + void SetupRoutingCache( void ); + void DeleteClusterCache( int clusterNum ); + void DeletePortalCache( void ); + void ShutdownRoutingCache( void ); + void RoutingStats( void ) const; + void LinkCache( idRoutingCache *cache ) const; + void UnlinkCache( idRoutingCache *cache ) const; + void DeleteOldestCache( void ) const; + idReachability * GetAreaReachability( int areaNum, int reachabilityNum ) const; + int ClusterAreaNum( int clusterNum, int areaNum ) const; + void UpdateAreaRoutingCache( idRoutingCache *areaCache ) const; + idRoutingCache * GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void UpdatePortalRoutingCache( idRoutingCache *portalCache ) const; + idRoutingCache * GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void RemoveRoutingCacheUsingArea( int areaNum ); + void DisableArea( int areaNum ); + void EnableArea( int areaNum ); + bool SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ); + void GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const; + void SetObstacleState( const idRoutingObstacle *obstacle, bool enable ); + +private: // pathing + bool EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const; + bool FloorEdgeSplitPoint( idVec3 &split, int areaNum, const idPlane &splitPlane, const idPlane &frontPlane, bool closest ) const; + idVec3 SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + idVec3 SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + +private: // debug + const idBounds & DefaultSearchBounds( void ) const; + void DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const; + void DrawAreaBounds( int areaNum ) const; + void DrawArea( int areaNum ) const; + void DrawFace( int faceNum, bool side ) const; + void DrawEdge( int edgeNum, bool arrow ) const; + void DrawReachability( const idReachability *reach ) const; + void ShowArea( const idVec3 &origin ) const; + void ShowWallEdges( const idVec3 &origin ) const; + void ShowHideArea( const idVec3 &origin, int targerAreaNum ) const; + bool PullPlayer( const idVec3 &origin, int toAreaNum ) const; + void RandomPullPlayer( const idVec3 &origin ) const; + void ShowPushIntoArea( const idVec3 &origin ) const; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + void DrawSimpleEdge( int edgeNum ) const; + void DrawSimpleFace( int faceNum, bool visited ) const; + void DrawSimpleArea( int areaNum ) const; + void ShowProblemEdge( int edgeNum ) const; + void ShowProblemFace( int faceNum ) const; + void ShowProblemArea( int areaNum ) const; + void ShowProblemArea( const idVec3 &origin ) const; +// RAVEN END +}; + +#endif /* !__AAS_LOCAL_H__ */ diff --git a/source/mpgame/ai/AAS_pathing.cpp b/source/mpgame/ai/AAS_pathing.cpp new file mode 100644 index 0000000..fc61ada --- /dev/null +++ b/source/mpgame/ai/AAS_pathing.cpp @@ -0,0 +1,734 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +#define SUBSAMPLE_WALK_PATH 1 +#define SUBSAMPLE_FLY_PATH 0 + +const int maxWalkPathIterations = 10; +const float maxWalkPathDistance = 500.0f; +const float walkPathSampleDistance = 8.0f; + +const int maxFlyPathIterations = 10; +const float maxFlyPathDistance = 500.0f; +const float flyPathSampleDistance = 8.0f; + + +/* +============ +idAASLocal::EdgeSplitPoint + + calculates split point of the edge with the plane + returns true if the split point is between the edge vertices +============ +*/ +bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const { + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + edge = &file->GetEdge( edgeNum ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * plane.Normal() - plane.Dist(); + d2 = v2 * plane.Normal() - plane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + return false; + } + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + return true; +} + +// RAVEN BEGIN +// rjohnson: optimized function +/* +============ +idAASLocal::FloorEdgeSplitPoint + + calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane + the point has to be on the front side of the frontPlane to be valid +============ +*/ +bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const { + int i, j, faceNum, edgeNum; + const aasArea_t *area; + const aasFace_t *face; + idVec3 split; + float dist, bestDist; + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + area = &file->GetArea( areaNum ); + if ( closest ) { + bestDist = maxWalkPathDistance; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist >= -0.1f && dist < bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist < maxWalkPathDistance ); + + } else { + bestDist = -0.1f; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist > bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist > -0.1f ); + } +} +// RAVEN END + +/* +============ +idAASLocal::WalkPathValid + + returns true if one can walk in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex; + idPlane pathPlane, frontPlane, farPlane; + idReachability *reach; + const aasArea_t *area; + idVec3 p, dir; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) ); + pathPlane.Normalize(); + pathPlane.FitThroughPoint( origin ); + + frontPlane.SetNormal( goalOrigin - origin ); + frontPlane.Normalize(); + frontPlane.FitThroughPoint( origin ); + + farPlane.SetNormal( frontPlane.Normal() ); + farPlane.FitThroughPoint( goalOrigin ); + + curAreaNum = areaNum; + lastAreaNum = curAreaNum; + + while ( 1 ) { + + // find the furthest floor face split point on the path + if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) { + endPos = origin; + } + + // if we found a point near or further than the goal we're done + if ( farPlane.Distance( endPos ) > -0.5f ) { + break; + } + + // if we reached the goal area we're done + if ( curAreaNum == goalAreaNum ) { + break; + } + + frontPlane.SetDist( frontPlane.Normal() * endPos ); + + area = &file->GetArea( curAreaNum ); + + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType != TFL_WALK ) { + continue; + } + + // if the reachability goes back to a previous area + if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] || + reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) { + continue; + } + + // if undesired travel flags are required to travel through the area + if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) { + continue; + } + + // don't optimize through an area near a ledge + if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) { + continue; + } + + // find the closest floor face split point on the path + if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) { + continue; + } + + // direction parallel to gravity + dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) - + ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir ); + if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) { + continue; + } + + // direction orthogonal to gravity + dir = endPos - p - dir; + if ( dir.LengthSqr() > Square( 0.2f ) ) { + continue; + } + + break; + } + + if ( !reach ) { + return false; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + } + + endAreaNum = curAreaNum; + + return true; +} + +/* +============ +idAASLocal::SubSampleWalkPath +============ +*/ +idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) { + return point; + } + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::WalkPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxWalkPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.reachability = reach; + // RAVEN END + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + // only optimize a limited distance ahead + if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( reach->travelType != TFL_WALK ) { + break; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + switch( reach->travelType ) { + case TFL_WALKOFFLEDGE: + path.type = PATHTYPE_WALKOFFLEDGE; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_BARRIERJUMP: + path.type |= PATHTYPE_BARRIERJUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_JUMP: + path.type |= PATHTYPE_JUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + default: + break; + } + + return true; +} + +/* +============ +idAASLocal::FlyPathValid + + returns true if one can fly in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + aasTrace_t trace; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + file->Trace( trace, origin, goalOrigin ); + + endPos = trace.endpos; + endAreaNum = trace.lastAreaNum; + + if ( trace.fraction >= 1.0f ) { + return true; + } + + return false; +} + +/* +============ +idAASLocal::SubSampleFlyPath +============ +*/ +idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) { + return point; + } + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::FlyPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxFlyPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + return true; +} + +typedef struct wallEdge_s { + int edgeNum; + int verts[2]; + struct wallEdge_s * next; +} wallEdge_t; + +/* +============ +idAASLocal::SortWallEdges +============ +*/ +void idAASLocal::SortWallEdges( int *edges, int numEdges ) const { + int i, j, k, numSequences; + wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge; + + wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) ); + sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + + for ( i = 0; i < numEdges; i++ ) { + wallEdges[i].edgeNum = edges[i]; + GetEdgeVertexNumbers( edges[i], wallEdges[i].verts ); + wallEdges[i].next = NULL; + sequenceFirst[i] = &wallEdges[i]; + sequenceLast[i] = &wallEdges[i]; + } + numSequences = numEdges; + + for ( i = 0; i < numSequences; i++ ) { + for ( j = i+1; j < numSequences; j++ ) { + if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) { + sequenceLast[j]->next = sequenceFirst[i]; + sequenceFirst[i] = sequenceFirst[j]; + break; + } + if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) { + sequenceLast[i]->next = sequenceFirst[j]; + break; + } + } + if ( j < numSequences ) { + numSequences--; + for ( k = j; k < numSequences; k++ ) { + sequenceFirst[k] = sequenceFirst[k+1]; + sequenceLast[k] = sequenceLast[k+1]; + } + i = -1; + } + } + + k = 0; + for ( i = 0; i < numSequences; i++ ) { + for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) { + edges[k++] = wallEdge->edgeNum; + } + } +} + +/* +============ +idAASLocal::GetWallEdges +============ +*/ +int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const { + int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + const aasFace_t *face1, *face2; + idReachability *reach; + + if ( !file ) { + return 0; + } + + numEdges = 0; + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = -1; + queueEnd = 0; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + area = &file->GetArea( curArea ); + + for ( i = 0; i < area->numFaces; i++ ) { + face1Num = file->GetFaceIndex( area->firstFace + i ); + face1 = &file->GetFace( abs(face1Num) ); + + if ( !(face1->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face1->numEdges; j++ ) { + edge1Num = file->GetEdgeIndex( face1->firstEdge + j ); + absEdge1Num = abs( edge1Num ); + + // test if the edge is shared by another floor face of this area + for ( k = 0; k < area->numFaces; k++ ) { + if ( k == i ) { + continue; + } + face2Num = file->GetFaceIndex( area->firstFace + k ); + face2 = &file->GetFace( abs(face2Num) ); + + if ( !(face2->flags & FACE_FLOOR ) ) { + continue; + } + + for ( l = 0; l < face2->numEdges; l++ ) { + edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) ); + if ( edge2Num == absEdge1Num ) { + break; + } + } + if ( l < face2->numEdges ) { + break; + } + } + if ( k < area->numFaces ) { + continue; + } + + // test if the edge is used by a reachability + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + if ( reach->edgeNum == absEdge1Num ) { + break; + } + } + } + if ( reach ) { + continue; + } + + // test if the edge is already in the list + for ( k = 0; k < numEdges; k++ ) { + if ( edge1Num == edges[k] ) { + break; + } + } + if ( k < numEdges ) { + continue; + } + + // add the edge to the list + edges[numEdges++] = edge1Num; + if ( numEdges >= maxEdges ) { + return numEdges; + } + } + } + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + return numEdges; +} diff --git a/source/mpgame/ai/AAS_routing.cpp b/source/mpgame/ai/AAS_routing.cpp new file mode 100644 index 0000000..5b1bd4b --- /dev/null +++ b/source/mpgame/ai/AAS_routing.cpp @@ -0,0 +1,1424 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for print and error + +#define CACHETYPE_AREA 1 +#define CACHETYPE_PORTAL 2 + +#define MAX_ROUTING_CACHE_MEMORY (2*1024*1024) + +#define LEDGE_TRAVELTIME_PANALTY 250 + +/* +============ +idRoutingCache::idRoutingCache +============ +*/ +idRoutingCache::idRoutingCache( int size ) { + areaNum = 0; + cluster = 0; + next = prev = NULL; + time_next = time_prev = NULL; + travelFlags = 0; + startTravelTime = 0; + type = 0; + this->size = size; + reachabilities = new byte[size]; + memset( reachabilities, 0, size * sizeof( reachabilities[0] ) ); + travelTimes = new unsigned short[size]; + memset( travelTimes, 0, size * sizeof( travelTimes[0] ) ); +} + +/* +============ +idRoutingCache::~idRoutingCache +============ +*/ +idRoutingCache::~idRoutingCache( void ) { + delete [] reachabilities; + delete [] travelTimes; +} + +/* +============ +idRoutingCache::Size +============ +*/ +int idRoutingCache::Size( void ) const { + return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] ); +} + +/* +============ +idAASLocal::AreaTravelTime +============ +*/ +unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const { + float dist; + + dist = ( end - start ).Length(); + + if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) { + dist *= 100.0f / 100.0f; + } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) { + dist *= 100.0f / 150.0f; + } else { + dist *= 100.0f / 300.0f; + } + if ( dist < 1.0f ) { + return 1; + } + return (unsigned short) idMath::FtoiFast( dist ); +} + +/* +============ +idAASLocal::CalculateAreaTravelTimes +============ +*/ +void idAASLocal::CalculateAreaTravelTimes(void) { + int n, i, j, numReach, numRevReach, t, maxt; + byte *bytePtr; + idReachability *reach, *rev_reach; + + // get total memory for all area travel times + numAreaTravelTimes = 0; + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + numReach = 0; + for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) { + numReach++; + } + + numRevReach = 0; + for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + numRevReach++; + } + numAreaTravelTimes += numReach * numRevReach; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + bytePtr = (byte *) areaTravelTimes; + + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + // for each reachability that starts in this area calculate the travel time + // towards all the reachabilities that lead towards this area + for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) { + assert( i < MAX_REACH_PER_AREA ); + if ( i >= MAX_REACH_PER_AREA ) { +// RAVEN BEGIN +// bdube: better error message + gameLocal.Error( "i >= MAX_REACH_PER_AREA on area %d at %g,%g,%g", n, file->GetArea( n ).center.x, file->GetArea( n ).center.y, file->GetArea( n ).center.z ); +// RAVEN END + } + reach->number = i; + reach->disableCount = 0; + reach->areaTravelTimes = (unsigned short *) bytePtr; + for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) { + t = AreaTravelTime( n, reach->start, rev_reach->end ); + reach->areaTravelTimes[j] = t; + if ( t > maxt ) { + maxt = t; + } + } + bytePtr += j * sizeof( unsigned short ); + } + + // if this area is a portal + if ( file->GetArea( n ).cluster < 0 ) { + // set the maximum travel time through this portal + file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt ); + } + } + + assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) ); +} + +/* +============ +idAASLocal::DeleteAreaTravelTimes +============ +*/ +void idAASLocal::DeleteAreaTravelTimes( void ) { + Mem_Free( areaTravelTimes ); + areaTravelTimes = NULL; + numAreaTravelTimes = 0; +} + +/* +============ +idAASLocal::SetupRoutingCache +============ +*/ +void idAASLocal::SetupRoutingCache( void ) { + int i; + byte *bytePtr; + + areaCacheIndexSize = 0; + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndexSize += file->GetCluster( i ).numReachableAreas; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) + + areaCacheIndexSize * sizeof( idRoutingCache *), MA_AAS ); +//RAVEN END + bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** ); + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr; + bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * ); + } + + portalCacheIndexSize = file->GetNumAreas(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), MA_AAS ); + + areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ),MA_AAS ); + portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ),MA_AAS ); + + goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::DeleteClusterCache +============ +*/ +void idAASLocal::DeleteClusterCache( int clusterNum ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !areaCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) { + for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) { + areaCacheIndex[clusterNum][i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::DeletePortalCache +============ +*/ +void idAASLocal::DeletePortalCache( void ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !portalCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) { + portalCacheIndex[i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::ShutdownRoutingCache +============ +*/ +void idAASLocal::ShutdownRoutingCache( void ) { + int i; + + for ( i = 0; i < file->GetNumClusters(); i++ ) { + DeleteClusterCache( i ); + } + + DeletePortalCache(); + + Mem_Free( areaCacheIndex ); + areaCacheIndex = NULL; + areaCacheIndexSize = 0; + Mem_Free( portalCacheIndex ); + portalCacheIndex = NULL; + portalCacheIndexSize = 0; +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( areaUpdate ) { + Mem_Free( areaUpdate ); + areaUpdate = NULL; + } + if ( portalUpdate ) { + Mem_Free( portalUpdate ); + portalUpdate = NULL; + } + if ( goalAreaTravelTimes ) { + Mem_Free( goalAreaTravelTimes ); + goalAreaTravelTimes = NULL; + } +// RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::SetupRouting +============ +*/ +bool idAASLocal::SetupRouting( void ) { +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + areaCacheIndex = NULL; + portalCacheIndex = NULL; + areaUpdate = NULL; + portalUpdate = NULL; + goalAreaTravelTimes = NULL; +// RAVEN END + + CalculateAreaTravelTimes(); + SetupRoutingCache(); + return true; +} + +/* +============ +idAASLocal::ShutdownRouting +============ +*/ +void idAASLocal::ShutdownRouting( void ) { + DeleteAreaTravelTimes(); + ShutdownRoutingCache(); +} + +/* +============ +idAASLocal::RoutingStats +============ +*/ +void idAASLocal::RoutingStats( void ) const { + idRoutingCache *cache; + int numAreaCache, numPortalCache; + int totalAreaCacheMemory, totalPortalCacheMemory; + + numAreaCache = numPortalCache = 0; + totalAreaCacheMemory = totalPortalCacheMemory = 0; + for ( cache = cacheListStart; cache; cache = cache->time_next ) { + if ( cache->type == CACHETYPE_AREA ) { + numAreaCache++; + totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } else { + numPortalCache++; + totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } + } + + gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 ); + gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 ); + gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 ); + gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 ); + gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); + gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); +} + +/* +============ +idAASLocal::RemoveRoutingCacheUsingArea +============ +*/ +void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) { + int clusterNum; + + clusterNum = file->GetArea( areaNum ).cluster; + if ( clusterNum > 0 ) { + // remove all the cache in the cluster the area is in + DeleteClusterCache( clusterNum ); + } + else { + // if this is a portal remove all cache in both the front and back cluster + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] ); + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] ); + } + DeletePortalCache(); +} + +/* +============ +idAASLocal::DisableArea +============ +*/ +void idAASLocal::DisableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) { + return; + } + + file->SetAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::EnableArea +============ +*/ +void idAASLocal::EnableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) { + return; + } + + file->RemoveAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::SetAreaState_r +============ +*/ +bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) { + int res; + const aasNode_t *node; + bool foundClusterPortal = false; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + // if this area is a cluster portal + if ( file->GetArea( -nodeNum ).contents & areaContents ) { + if ( disabled ) { + DisableArea( -nodeNum ); + } else { + EnableArea( -nodeNum ); + } + foundClusterPortal |= true; + } + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled ); + nodeNum = node->children[0]; + } + } + + return foundClusterPortal; +} + +/* +============ +idAASLocal::SetAreaState +============ +*/ +bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { + idBounds expBounds; + + if ( !file ) { + return false; + } + + expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + + // find all areas within or touching the bounds with the given contents and disable/enable them for routing + return SetAreaState_r( 1, expBounds, areaContents, disabled ); +} + +/* +============ +idAASLocal::GetBoundsAreas_r +============ +*/ +void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const { + int res; + const aasNode_t *node; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + areas.Append( -nodeNum ); + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + GetBoundsAreas_r( node->children[1], bounds, areas ); + nodeNum = node->children[0]; + } + } +} + +/* +============ +idAASLocal::SetObstacleState +============ +*/ +void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) { + int i; + const aasArea_t *area; + idReachability *reach, *rev_reach; + bool inside; + + for ( i = 0; i < obstacle->areas.Num(); i++ ) { + + RemoveRoutingCacheUsingArea( obstacle->areas[i] ); + + area = &file->GetArea( obstacle->areas[i] ); + + for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + + if ( rev_reach->travelType & TFL_INVALID ) { + continue; + } + + inside = false; + + if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) { + inside = true; + } + else { + for ( reach = area->reach; reach; reach = reach->next ) { + if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) { + inside = true; + break; + } + } + } + + if ( inside ) { + if ( enable ) { + rev_reach->disableCount--; + if ( rev_reach->disableCount <= 0 ) { + rev_reach->travelType &= ~TFL_INVALID; + rev_reach->disableCount = 0; + } + } + else { + rev_reach->travelType |= TFL_INVALID; + rev_reach->disableCount++; + } + } + } + } +} + +// RAVEN BEGIN +// cdr: Alternate Routes Bug + +/* +============ +idAASLocal::SetReachabilityState +============ +*/ +void idAASLocal::SetReachabilityState( idReachability* reach, bool enable ) { + if ( enable && reach->travelType&TFL_INVALID) { + reach->disableCount--; + if ( reach->disableCount <= 0 ) { + reach->travelType &= ~TFL_INVALID; + reach->disableCount = 0; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } + } else if (!enable && !(reach->travelType&TFL_INVALID)) { + reach->travelType |= TFL_INVALID; + reach->disableCount++; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } +} +// RAVEN END + + +/* +============ +idAASLocal::AddObstacle +============ +*/ +aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) { + idRoutingObstacle *obstacle; + + if ( !file ) { + return -1; + } + + obstacle = new idRoutingObstacle; + obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas ); + SetObstacleState( obstacle, true ); + + obstacleList.Append( obstacle ); + return obstacleList.Num() - 1; +} + +/* +============ +idAASLocal::RemoveObstacle +============ +*/ +void idAASLocal::RemoveObstacle( const aasHandle_t handle ) { + if ( !file ) { + return; + } + if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) { + SetObstacleState( obstacleList[handle], false ); + + delete obstacleList[handle]; + obstacleList.RemoveIndex( handle ); + } +} + +/* +============ +idAASLocal::RemoveAllObstacles +============ +*/ +void idAASLocal::RemoveAllObstacles( void ) { + int i; + + if ( !file ) { + return; + } + + for ( i = 0; i < obstacleList.Num(); i++ ) { + SetObstacleState( obstacleList[i], false ); + delete obstacleList[i]; + } + obstacleList.Clear(); +} + +/* +============ +idAASLocal::LinkCache + + link the cache in the cache list sorted from oldest to newest cache +============ +*/ +void idAASLocal::LinkCache( idRoutingCache *cache ) const { + + // if the cache is already linked + if ( cache->time_next || cache->time_prev || cacheListStart == cache ) { + UnlinkCache( cache ); + } + + totalCacheMemory += cache->Size(); + + // add cache to the end of the list + cache->time_next = NULL; + cache->time_prev = cacheListEnd; + if ( cacheListEnd ) { + cacheListEnd->time_next = cache; + } + cacheListEnd = cache; + if ( !cacheListStart ) { + cacheListStart = cache; + } +} + +/* +============ +idAASLocal::UnlinkCache +============ +*/ +void idAASLocal::UnlinkCache( idRoutingCache *cache ) const { + + totalCacheMemory -= cache->Size(); + + // unlink the cache + if ( cache->time_next ) { + cache->time_next->time_prev = cache->time_prev; + } else { + cacheListEnd = cache->time_prev; + } + if ( cache->time_prev ) { + cache->time_prev->time_next = cache->time_next; + } else { + cacheListStart = cache->time_next; + } + cache->time_next = cache->time_prev = NULL; +} + +/* +============ +idAASLocal::DeleteOldestCache +============ +*/ +void idAASLocal::DeleteOldestCache( void ) const { + idRoutingCache *cache; + + assert( cacheListStart ); + + // unlink the oldest cache + cache = cacheListStart; + UnlinkCache( cache ); + + // unlink the oldest cache from the area or portal cache index + if ( cache->next ) { + cache->next->prev = cache->prev; + } + if ( cache->prev ) { + cache->prev->next = cache->next; + } + else if ( cache->type == CACHETYPE_AREA ) { + areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next; + } + else if ( cache->type == CACHETYPE_PORTAL ) { + portalCacheIndex[cache->areaNum] = cache->next; + } + + delete cache; +} + +/* +============ +idAASLocal::GetAreaReachability +============ +*/ +idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const { + idReachability *reach; + + for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) { + if ( --reachabilityNum < 0 ) { + return reach; + } + } + return NULL; +} + +/* +============ +idAASLocal::ClusterAreaNum +============ +*/ +ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const { + int side, areaCluster; + + areaCluster = file->GetArea( areaNum ).cluster; + if ( areaCluster > 0 ) { + return file->GetArea( areaNum ).clusterAreaNum; + } + else { + side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum; + return file->GetPortal( -areaCluster ).clusterAreaNum[side]; + } +} + +/* +============ +idAASLocal::UpdateAreaRoutingCache +============ +*/ +void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const { + int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas; + unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA]; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + + // number of reachability areas within this cluster + numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas; + + // number of the start area within the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + return; + } + + areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime; + badTravelFlags = ~areaCache->travelFlags; + memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) ); + + // initialize first update + curUpdate = &areaUpdate[clusterAreaNum]; + curUpdate->areaNum = areaCache->areaNum; + curUpdate->areaTravelTimes = startAreaTravelTimes; + curUpdate->tmpTravelTime = areaCache->startTravelTime; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the list + while( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->fromAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + // get the cluster number of the area + cluster = nextArea->cluster; + // don't leave the cluster, however do flood into cluster portals + if ( cluster > 0 && cluster != areaCache->cluster ) { + continue; + } + + // get the number of the area in the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + continue; // should never happen + } + + assert( clusterAreaNum < areaCache->size ); + + // time already travelled plus the traveltime through the current area + // plus the travel time of the reachability towards the next area + t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime; + + if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) { + + areaCache->travelTimes[clusterAreaNum] = t; + areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area + nextUpdate = &areaUpdate[clusterAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->areaTravelTimes = reach->areaTravelTimes; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetAreaRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + int clusterAreaNum; + idRoutingCache *cache, *clusterCache; + + // number of the area in the cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // pointer to the cache for the area in the cluster + clusterCache = areaCacheIndex[clusterNum][clusterAreaNum]; + // check if cache without undesired travel flags already exists + for ( cache = clusterCache; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas ); + cache->type = CACHETYPE_AREA; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = clusterCache; + if ( clusterCache ) { + clusterCache->prev = cache; + } + areaCacheIndex[clusterNum][clusterAreaNum] = cache; + UpdateAreaRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::UpdatePortalRoutingCache +============ +*/ +void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const { + int i, portalNum, clusterAreaNum; + unsigned short t; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *cache; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + + curUpdate = &portalUpdate[ file->GetNumPortals() ]; + curUpdate->cluster = portalCache->cluster; + curUpdate->areaNum = portalCache->areaNum; + curUpdate->tmpTravelTime = portalCache->startTravelTime; + + //put the area to start with in the current read list + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the current list + while( updateListStart ) { + + curUpdate = updateListStart; + // remove the current update from the list + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + // current update is removed from the list + curUpdate->isInList = false; + + cluster = &file->GetCluster( curUpdate->cluster ); + cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags ); + + // take all portals of the cluster + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + assert( portalNum < portalCache->size ); + portal = &file->GetPortal( portalNum ); + + clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum ); + if ( clusterAreaNum >= cluster->numReachableAreas ) { + continue; + } + + t = cache->travelTimes[clusterAreaNum]; + if ( t == 0 ) { + continue; + } + t += curUpdate->tmpTravelTime; + + if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) { + + portalCache->travelTimes[portalNum] = t; + portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum]; + nextUpdate = &portalUpdate[portalNum]; + if ( portal->clusters[0] == curUpdate->cluster ) { + nextUpdate->cluster = portal->clusters[1]; + } + else { + nextUpdate->cluster = portal->clusters[0]; + } + nextUpdate->areaNum = portal->areaNum; + // add travel time through the actual portal area for the next update + nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime; + + if ( !nextUpdate->isInList ) { + + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetPortalRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + idRoutingCache *cache; + + // check if cache without undesired travel flags already exists + for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetNumPortals() ); + cache->type = CACHETYPE_PORTAL; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = portalCacheIndex[areaNum]; + if ( portalCacheIndex[areaNum] ) { + portalCacheIndex[areaNum]->prev = cache; + } + portalCacheIndex[areaNum] = cache; + UpdatePortalRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::RouteToGoalArea +============ +*/ +bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const { + int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum; + unsigned short int t, bestTime; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *areaCache, *portalCache, *clusterCache; + idReachability *bestReach, *r, *nextr; + + travelTime = 0; + *reach = NULL; + + if ( !file ) { + return false; + } + + if ( areaNum == goalAreaNum ) { + return true; + } + + if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum ); +// RAVEN END + return false; + } + if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum ); +// RAVEN END + return false; + } + + while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) { + DeleteOldestCache(); + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the source area is a cluster portal, read directly from the portal cache + if ( clusterNum < 0 ) { + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] ); + travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start ); + return true; + } + + bestTime = 0; + bestReach = NULL; + + // check if the goal area is a portal of the source area cluster + if ( goalClusterNum < 0 ) { + portal = &file->GetPortal( -goalClusterNum ); + if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) { + goalClusterNum = clusterNum; + } + } + + // if both areas are in the same cluster + if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) { + clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags ); + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + if ( clusterCache->travelTimes[clusterAreaNum] ) { + bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] ); + bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start ); + } + else { + clusterCache = NULL; + } + } + else { + clusterCache = NULL; + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + + // the cluster the area is in + cluster = &file->GetCluster( clusterNum ); + // current area inside the current cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // if the area is not a reachable area + if ( clusterAreaNum >= cluster->numReachableAreas) { + return false; + } + + // find the portal of the source area cluster leading towards the goal area + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + + // if the goal area isn't reachable from the portal + if ( !portalCache->travelTimes[portalNum] ) { + continue; + } + + portal = &file->GetPortal( portalNum ); + // get the cache of the portal area + areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags ); + // if the portal is not reachable from this area + if ( !areaCache->travelTimes[clusterAreaNum] ) { + continue; + } + + r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] ); + + if ( clusterCache ) { + // if the next reachability from the portal leads back into the cluster + nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] ); + if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) { + continue; + } + } + + // the total travel time is the travel time from the portal area to the goal area + // plus the travel time from the source area towards the portal area + t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum]; + // NOTE: Should add the exact travel time through the portal area. + // However we add the largest travel time through the portal area. + // We cannot directly calculate the exact travel time through the portal area + // because the reachability used to travel into the portal area is not known. + t += portal->maxAreaTravelTime; + + // if the time is better than the one already found + if ( !bestTime || t < bestTime ) { + bestReach = r; + bestTime = t; + } + } + + if ( !bestReach ) { + return false; + } + + *reach = bestReach; + travelTime = bestTime; + + return true; +} + +/* +============ +idAASLocal::TravelTimeToGoalArea +============ +*/ +int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { + int travelTime; + idReachability *reach; + + if ( !file ) { + return 0; + } + + if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) { + return 0; + } + return travelTime; +} + +/* +============ +idAASLocal::FindNearestGoal +============ +*/ +bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const { + int i, j, k, badTravelFlags, nextAreaNum; + aasGoal_t bestGoal; + unsigned short t, bestTravelTime; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + idVec3 v1, v2, p; + float targetDist, dist; + + if ( file == NULL || areaNum <= 0 ) { + goal.areaNum = areaNum; + goal.origin = origin; + return false; + } + + // setup obstacles + for ( k = 0; k < numObstacles; k++ ) { + obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0]; + } + + badTravelFlags = ~travelFlags; + SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) ); + + targetDist = (target - origin).Length(); + + // initialize first update + curUpdate = &areaUpdate[areaNum]; + curUpdate->areaNum = areaNum; + curUpdate->tmpTravelTime = 0; + curUpdate->start = origin; + curUpdate->next = NULL; + curUpdate->prev = NULL; + + callback.Init ( ); + + // if the first area is valid goal, just return the origin + curUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, areaNum, origin, minDistance, maxDistance, &origin, goal ); + if ( curUpdate->cluster == idAASCallback::TEST_OK ) { + callback.Finish ( ); + return true; + } + + updateListStart = curUpdate; + updateListEnd = curUpdate; + + bestTravelTime = 0; + bestGoal.areaNum = 0; + + // while there are updates in the list + while ( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + // if we already found a closer location + if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) { + continue; + } + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->toAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + t = curUpdate->tmpTravelTime + + AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) + + reach->travelTime; + + // project target origin onto movement vector through the area + v1 = reach->end - curUpdate->start; + v1.Normalize(); + v2 = target - curUpdate->start; + p = curUpdate->start + (v2 * v1) * v1; + + // get the point on the path closest to the target + for ( j = 0; j < 3; j++ ) { + if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) || + (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) { + break; + } + } + if ( j >= 3 ) { + dist = (target - p).Length(); + } else { + dist = (target - reach->end).Length(); + } + + // avoid moving closer to the target + if ( dist < targetDist ) { + t += ( targetDist - dist ) * 10; + } + + // if we already found a closer location + if ( bestTravelTime && t >= bestTravelTime ) { + continue; + } + + // if this is not the best path towards the next area + if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) { + continue; + } + + // path may not go through any obstacles + for ( k = 0; k < numObstacles; k++ ) { + // If the start of the movement vector is inside the expanded bounds then we are already too + // close to the obstacle, so use its unexpanded bounds instead. + if ( obstacles[k].expAbsBounds.ContainsPoint ( curUpdate->start ) ) { + if ( obstacles[k].absBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + // if the movement vector intersects the expanded obstacle bounds + } else if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + } + if ( k < numObstacles ) { + continue; + } + + goalAreaTravelTimes[nextAreaNum] = t; + nextUpdate = &areaUpdate[nextAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->start = reach->end; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + // If outside of max distance skip this area + idVec3 point = origin; + float areaDist; + file->PushPointIntoAreaNum ( nextAreaNum, point ); + areaDist = (origin-point).LengthFast(); + if ( maxDistance > 0.0f && areaDist > maxDistance ) { + curUpdate->cluster = idAASCallback::TEST_BADAREA; + continue; + } + + // don't put goal near a ledge + if ( !( nextArea->flags & AREA_LEDGE ) ) { + + // add travel time through the area + t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center ); + + if ( !bestTravelTime || t < bestTravelTime ) { + // if the area is not visible to the target + nextUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, reach->toAreaNum, origin, minDistance, maxDistance, NULL, bestGoal ); + switch ( nextUpdate->cluster ) { + case idAASCallback::TEST_OK: + bestTravelTime = t; + break; + case idAASCallback::TEST_BADAREA: + if ( curUpdate->cluster != idAASCallback::TEST_BADAREA ) { + continue; + } + break; + } + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + + callback.Finish ( ); + + if ( bestGoal.areaNum ) { + goal = bestGoal; + return true; + } + + return false; +} diff --git a/source/mpgame/ai/AAS_tactical.cpp b/source/mpgame/ai/AAS_tactical.cpp new file mode 100644 index 0000000..7229aa9 --- /dev/null +++ b/source/mpgame/ai/AAS_tactical.cpp @@ -0,0 +1,1765 @@ +/////////////////////////////////////////////////////////////////////////////////// +// rvAAS_tactical.cpp +// +// AAST Tactical Search Parameters Documentation +// --------------------------------------------- +// There are 10 tests at your disposal when creating search functions, 3 +// sets of 3 and one special case test. Each of these 10 tests can have +// hard min and max limits, as well as a "soft" weight which will determine how +// highly the feature is ranked against other features. Let's examine a +// diagram of what these 10 tests are: +// +// [F] ######### Key: +// | ######### [F] = Focus Test Set (enemy, or forward projection) +// / ######### [O] = Owner Test Set (actor who is doing the search) +// [P] ######### [P] = Path Test Set (line between owner and focus) +// | ######### <-> = Advance Test (In front / behind owner) +// / x ##### x = Feature (feature being tested) +// <---[O]---> ##### ### = Walls +// #################### +// #################### +// #################### +// +// Each Test Set Has The Following: +// - Distance RANGE=[ 0, 1] WEIGHT=(-1=Close, 1=Far) +// Distance is computed from the origin of the subject to the origin of the +// feature being tested (x in the above diagram). Distance will be a common +// test to use on all three sets, with all mannor of clamped ranges and +// sort values. Most common though will be to sort close to the owner so +// as to minimize how long it will take for the AI to get to the spot. +// +// - FacingDot RANGE=[-1, 1] WEIGHT=(-1=Behind, 1=In Front) +// FacingDot is computed with the dotproduct of the subject's facing and +// the feature's normal. This too will be a common test to compare with +// all three subjects. With it you can prefer points in front or behind +// things. +// +// - DirectionDot RANGE=[-1, 1] WEIGHT=(-1=Toward, 1=Away) +// DirectionDot is computed by subtracing the origins of the feature and +// the subject, normalizing and taking the dotproduct of the result with +// the feature's normal. Usually, you will want a negative weight on this +// test to comare how close the feature is pointing at the subject (usually +// tested against Focus) +// +// Lastly, there is a special "additional" test for "advance": +// - Advance RANGE=[-1, 1] WEIGHT=(-1=Backward, 1=Forward) +// Advance is computed using the owner direction dot with the path facing. +// This test will tell you how much the feature is between the owner and +// the focus, reguardless of what direction either is facing at the time. +// As a result, it approximates "advancing". +// +/////////////////////////////////////////////////////////////////////////////////// +#include "../../idlib/precompiled.h" +#pragma hdrstop + + +/////////////////////////////////////////////////////////////////////////////////// +// Includes +/////////////////////////////////////////////////////////////////////////////////// +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_local.h" +#include "AAS_tactical.h" + +/////////////////////////////////////////////////////////////////////////////////// +// Global::CONTANER SIZES AND OTHER LIMITS +/////////////////////////////////////////////////////////////////////////////////// +const int MAX_FEATURE_LIST = 20; +const int MAX_AREAS_TOUCHED = 450; + +/////////////////////////////////////////////////////////////////////////////////// +// Global::FEATURE TESTING DISTANCES +/////////////////////////////////////////////////////////////////////////////////// +const float MAX_DISTANCE = 650.0f; +const float MIN_DISTANCE = 72.0f; +const float MIN_NEAR_DISTANCE = 112.0f; +const float FROM_ENEMY_PROJECT = 350.0f; +const float TEST_TEAMMATE_DIST = 150.0f; + + + + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Normal +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Normal() +{ + static idVec3 n(0,0,0); + n[0] = ((float)(normalx) / 127.0f) - 1.0f; + n[1] = ((float)(normaly) / 127.0f) - 1.0f; + return n; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Origin() +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Origin() +{ + static idVec3 o; + o.Set((float)x, (float)y, (float)z); + return o; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::GetLookPos() +/////////////////////////////////////////////////////////////////////////////// +int aasFeature_s::GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance ) +{ + static idVec3 up(0.0f,0.0f,1.0f); + static idVec3 direction; + static idVec3 right; + static float rightDot; + static float distance; + + lookPos = Origin(); + lookPos[2] += height - leanDistance; + direction = aimAtOrigin - lookPos; + distance = direction.NormalizeFast(); + right = Normal().Cross(up); + rightDot = right * direction; + + + // Check For Optimal Conditions + //------------------------------ + if (flags&FEATURE_LOOK_OVER && fabsf(rightDot)<0.2f) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT && rightDot>0.0f) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT && rightDot<0.0f) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + + // So Nothing Matches Perfectly, Let's Try Fallback Cases In This Order + //---------------------------------------------------------------------- + if (flags&FEATURE_LOOK_OVER) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + // This Is Odd, There Must Be No Look Flags On This Feature At All + //----------------------------------------------------------------- + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvSortReach +// +// This structure is used by the search heap below to sort areas by actual +// distance from the start point to do a distance based BFS instead of a +// least links based BFS. +/////////////////////////////////////////////////////////////////////////////// +struct rvSortReach +{ + int mAreaNum; + idReachability* mReach; + float mDistance; + + bool operator<(const rvSortReach& r) const + { + return (mDistance>r.mDistance); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTest +// +// A test is a mechanism to weight values and to provide min and max bounds. +/////////////////////////////////////////////////////////////////////////////// +struct rvTest +{ + float mMin; + float mMax; + float mWeight; + float mValue; + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteFloat(mMin); + savefile->WriteFloat(mMax); + savefile->WriteFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadFloat(mMin); + savefile->ReadFloat(mMax); + savefile->ReadFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Reset - Sets the values to defaults + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mMax = 1.0f; + mMin = -1.0f; + mWeight = 0.0f; + mValue = 0.0f; + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Simple compute weight + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return mValue * mWeight; + } + + /////////////////////////////////////////////////////////////////////////// + // Test - Records the value out of the range and returns true if succeeded + /////////////////////////////////////////////////////////////////////////// + bool Test(float Value, float MaxScale=1.0f) + { + if (mMin>-1.0f || mMax<1.0f || mWeight!=0.0f) + { + mValue = Value; + if (MaxScale!=1.0f) + { + mValue /= MaxScale; + } + mValue = (mValue-mMin) / (mMax-mMin); // Now Scale It [0.0, 1.0] Of Min And Max + return (mValue>=0.0f && mValue<=1.0f); // If Not In [0.0, 1.0], Test Fails + } + return true; // Test Is Not Active + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Returns the abs of the weight, and adds to negatives + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + if (mWeight<0.0f) + { + negatives += mWeight; + } + return fabsf(mWeight); + } + + void DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction); + void DrawDebugInfo(const idVec4 color, const idVec3& origin); +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTestSet +// +// Test sets are designed to be used by the rvAASTacticalSensorLocal class to compute +// various vectors against a given feature. +/////////////////////////////////////////////////////////////////////////////// +struct rvTestSet +{ + // Parameters + //------------ + bool mProjectOrigin; // If True, Origin Is Cast Out Along mFacing Vector + idVec3 mOrigin; // Position Of The Test Subject + idVec3 mFacing; // Orientation Of The Test Subject + + // Computed During Test() + //------------------------ + rvTest mDistance; // Resulting Distance To Feature + rvTest mFacingDot; // Resulting Facing Dot Product To Feature + rvTest mDirectionDot; // Resulting Direction Dot Product To Feature + idVec3 mDirection; // Resulting Direction To Feature + + /////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////// + rvTestSet( void ) + : mOrigin(0,0,0), mFacing(0,0,0), mProjectOrigin(false) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteBool(mProjectOrigin); + savefile->WriteVec3(mOrigin); + savefile->WriteVec3(mFacing); + mDistance.Save(savefile); + mFacingDot.Save(savefile); + mDirectionDot.Save(savefile); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadBool(mProjectOrigin); + savefile->ReadVec3(mOrigin); + savefile->ReadVec3(mFacing); + mDistance.Restore(savefile); + mFacingDot.Restore(savefile); + mDirectionDot.Restore(savefile); + } + + + /////////////////////////////////////////////////////////////////////////// + // Reset - Restets all sub tests + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mProjectOrigin = false; + mDirectionDot.Reset(); + mFacingDot.Reset(); + mDistance.Reset(); +// bdube: Had to comment this out becaue it would break the test function which checks for non defaults +// mDistance.mMin = 0.0f; // special case (default would be -1.0f because all others are dot products) + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Adds up the weights of the sub tests + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return (mDistance.Weight() + mFacingDot.Weight() + mDirectionDot.Weight()); + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Computes weight range of all sub tests + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + return (mDistance.WeightRange(negatives) + mFacingDot.WeightRange(negatives) + mDirectionDot.WeightRange(negatives)); + } + + /////////////////////////////////////////////////////////////////////////// + // Test - This is the actuall feature test + /////////////////////////////////////////////////////////////////////////// + bool Test(aasFeature_t* f, float distance=0.0f) + { + // First Compute The Direction + //----------------------------- + mDirection = f->Origin() - mOrigin; + + // If Project Origin Is True, Then Move Origin Along Facing Vector + //----------------------------------------------------------------- + if (mProjectOrigin) + { + mDirection.ProjectOntoVector(mFacing); + mOrigin += mDirection; + mDirection = f->Origin() - mOrigin; + } + + // If No Override On Distance, Compute It By Normalizing The Direction + //--------------------------------------------------------------------- + if (!distance) + { + distance = mDirection.Normalize(); + } + else + { + mDirection.Normalize(); + } + + + // THE DISTANCE TEST + //------------------- + if (!mDistance.Test(distance, MAX_DISTANCE)) + { + return false; + } + + // THE FACING TEST + //----------------- + if (!mFacingDot.Test(f->Normal()*mFacing)) + { + return false; + } + + // THE DIRECTION TEST + //-------------------- + if (!mDirectionDot.Test(f->Normal()*mDirection)) + { + return false; + } + return true; + } + + /////////////////////////////////////////////////////////////////////////// + // SetupOriginAndFacing - Called For Each Test Set + /////////////////////////////////////////////////////////////////////////// + void SetupOriginAndFacing(const idEntity* ent, const idVec3* originOverride=0, const idVec3* facingOverride=0) + { + if (!ent) + { + mOrigin = (originOverride)?(*originOverride):(vec3_origin); + mFacing = (facingOverride)?(*facingOverride):(vec3_origin); + } + else + { + const idActor* entActor = dynamic_cast(ent); // bleh. Base entity class should properly return origin, angles, and forward vector + + mOrigin = (originOverride)?(*originOverride):(ent->GetPhysics()->GetOrigin()); + mFacing = (facingOverride)?(*facingOverride):(entActor?entActor->viewAxis[0]:ent->GetPhysics()->GetAxis(0)[0]); + } + + mFacing[2] = 0; + mFacing.Normalize(); + } + + /////////////////////////////////////////////////////////////////////////// + // ProjectOriginForward - Used By Several Setup Options To Project Origin + // Along Facing Direction Some + /////////////////////////////////////////////////////////////////////////// + void ProjectOriginForward(float distance, float xyRange=0.0f, bool randomFacing=0.0f) + { + mOrigin += (mFacing * distance); + if (xyRange) + { + mOrigin[0] += rvRandom::flrand(-xyRange, xyRange); + mOrigin[1] += rvRandom::flrand(-xyRange, xyRange); + } + if (randomFacing) + { + mFacing[0] = rvRandom::flrand(-1.0f, 1.0f); + mFacing[1] = rvRandom::flrand(-1.0f, 1.0f); + } + mFacing[2] = 0.0f; + mFacing.Normalize(); + } + + void DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin); +}; + + + + + +/////////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +// +// This is the local implimentation of the rvAASTacticalSensor interface. It +// contains all the various local data and functions needed to execute a +// search of the tactical data in AAS. +/////////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensorLocal : rvAASTacticalSensor +{ + // Owner + /////////////////////////////////////////////////////////////////////////////// + idActor* mOwner; // Owner Of The Sensor + idAI* mOwnerAI; // Owner As Already Cast To AI Type + + // Search Parameters + /////////////////////////////////////////////////////////////////////////////// + idStr mSearchName; // Current Search Name + int mFlagsMatchAny; // Features must match AT LEAST ONE of these flags + int mFlagsMatchAll; // Features must match ALL of these flags + int mFlagsMatchNone; // Features must match NONE of these flags + int mFeaturesSearchMax; // Maximum number of features to extract from the grid + int mFeaturesFinalMax; // After sorting, prune list down to this size + rvTestSet mFromOwner; // Test Set For Owner Relation + rvTestSet mFromEnemy; // Test Set For Focus Relation + rvTestSet mFromTether; // Test Set for Tether Relation + rvTestSet mFromPath; // Test Set For Path Relation + rvTest mAdvance; // Single Test For Advance / Retreat + rvTest mAssignment; // Single Test For Assignment Direction Dot Product + rvTest mLeanNormal; // Single Test For Lean Normal Biasing + idVec3 mAssignmentDirection; // Used By Assignment Test + bool mAssignmentValid; // Turns On And Off The Assignment Test + idEntityPtr mEnemyOverride; // Overrides Enemy Pointer To Any Entity + + // Search & Update Results + /////////////////////////////////////////////////////////////////////////////// + idList mFeatures; // The list of all features found in the most recent search + idVec3 mReservedOrigin; // Origin of feature that is currently reserved + aasFeature_t* mReserved; // Which feature is currently reserved + aasFeature_t* mNear; // Which feature is closest + aasFeature_t* mLook; // Which feature to look down + int mLookStartTime; + float mLookStopDist; + + + + + + // Local API + /////////////////////////////////////////////////////////////////////////////// + rvAASTacticalSensorLocal(); + ~rvAASTacticalSensorLocal(); + void Update(); + void Save(idSaveGame *savefile); + void Restore(idRestoreGame *savefile); + void Clear(); + void DrawDebugInfo(); + + // Search + /////////////////////////////////////////////////////////////////////////////// + void Search(); + void SearchReset(idEntity* enemyOverride=0, float ownerRangeMin=0.0f, float ownerRangeMax=1.0f); + void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f); + void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f); + void SearchHide(idEntity* from=0); + void SearchFlank(); + void SearchAdvance(); + void SearchRetreat(); + void SearchAmbush(); + void SearchDebug(); + float SearchComputeWeightRange(float& rangeNegative); + float SearchComputeWeight(); + + // Feature Testing + /////////////////////////////////////////////////////////////////////////////// + void TestSetupCurrentValues(); + bool TestValid(aasFeature_t* f, float walkDistanceToFeature); + bool TestValidWithCurrentState(aasFeature_t* f=0); + + // Feature Reservation + /////////////////////////////////////////////////////////////////////////////// + void Reserve(aasFeature_t* f); + + // Access To Results + /////////////////////////////////////////////////////////////////////////////// + int FeatureCount() {return mFeatures.Num();} + aasFeature_t* Feature(int i) {return mFeatures[i];} + aasFeature_t* Near() const {return mNear;} + aasFeature_t* Look() const {return mLook;} + aasFeature_t* Reserved() const {return mReserved;} + const idVec3& ReservedOrigin() const {return mReservedOrigin;} +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Objects +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal* mSensor; +float mDebugRadius; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Typedefines +/////////////////////////////////////////////////////////////////////////////// +typedef idEntityPtr TEntPtr; +typedef aasFeature_t* TFeaturePtr; + + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor::CREATE_SENSOR +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensor* rvAASTacticalSensor::CREATE_SENSOR(idActor* owner) +{ + rvAASTacticalSensorLocal* nSensor = new rvAASTacticalSensorLocal(); + nSensor->mOwner = owner; + nSensor->mOwnerAI = dynamic_cast(owner); + return nSensor; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::rvAASTacticalSensorLocal() +{ + mOwner = 0; + mOwnerAI = 0; + Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Destructor +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::~rvAASTacticalSensorLocal() +{ + Reserve(0); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Clear +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Clear() +{ + mReserved = 0; + mNear = 0; + mLook = 0; + mSearchName = ""; + mFeatures.Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Save +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Save(idSaveGame *savefile) +{ + savefile->WriteString(mSearchName); + savefile->WriteInt(mFlagsMatchAny); + savefile->WriteInt(mFlagsMatchAll); + savefile->WriteInt(mFlagsMatchNone); + savefile->WriteInt(mFeaturesSearchMax); + savefile->WriteInt(mFeaturesFinalMax); + mFromOwner.Save(savefile); + mFromEnemy.Save(savefile); + mFromTether.Save(savefile); + mFromPath.Save(savefile); + mAdvance.Save(savefile); + mAssignment.Save(savefile); + mLeanNormal.Save(savefile); + savefile->WriteVec3(mAssignmentDirection); + savefile->WriteBool(mAssignmentValid); + mEnemyOverride.Save(savefile); + +// cnicholson: NOTE: The following 4 vars are set to 0 / cleared in the restore, so don't save them. + // NOSAVE: idList mFeatures; + // NOSAVE: savefile->WriteVec3(mFeatures); + // NOSAVE: aasFeature_t* mReserved; + // NOSAVE: aasFeature_t* mNear; + // NOSAVE: aasFeature_t* mLook; + savefile->WriteInt(mLookStartTime); // cnicholson: Added unsaved var + savefile->WriteFloat(mLookStopDist);// cnicholson: Added unsaved var +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Restore +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Restore(idRestoreGame *savefile) +{ + // Clear Old Data + //---------------- + mFeatures.Clear(); + mNear = 0; + mLook = 0; + mReserved = 0; + + // Read The Save File Search Parameters + //-------------------------------------- + savefile->ReadString(mSearchName); + savefile->ReadInt(mFlagsMatchAny); + savefile->ReadInt(mFlagsMatchAll); + savefile->ReadInt(mFlagsMatchNone); + savefile->ReadInt(mFeaturesSearchMax); + savefile->ReadInt(mFeaturesFinalMax); + mFromOwner.Restore(savefile); + mFromEnemy.Restore(savefile); + mFromTether.Restore(savefile); + mFromPath.Restore(savefile); + mAdvance.Restore(savefile); + mAssignment.Restore(savefile); + mLeanNormal.Restore(savefile); + savefile->ReadVec3(mAssignmentDirection); + savefile->ReadBool(mAssignmentValid); + mEnemyOverride.Restore(savefile); +// Search(); + + savefile->ReadInt(mLookStartTime); // cnicholson: Added unrestored var + savefile->ReadFloat(mLookStopDist);// cnicholson: Added unrestored var +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Update +// +// If called regularly, this function will handle drawing debug information +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Update() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden()) + { + return; + } + + idAASFile* file = aas->GetFile(); + + idVec3 velocityFwd = mOwner->GetPhysics()->GetLinearVelocity(); + const idVec3& ownerOrigin = mOwner->GetPhysics()->GetOrigin(); + int ownerAreaNum = mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aasFeature_t* feature = 0; + aasArea_t& area = file->GetArea(ownerAreaNum); + idActor* teammate = NULL; + float featureDistance = 0.0f; + float featureDotLeft = 0.0f; + float featureDotDirection = 0.0f; + float teammateDistance = 0.0f; + float closestDistance = 0.0f; + idVec3 velocityLeft; + idVec3 velocityDown; + idVec3 featureDirection; + idVec3 teammateDirection; + + + + // Update And Possibly Clear The Near Feature + //-------------------------------------------- + if (mNear) + { + closestDistance = mNear->Origin().Dist(ownerOrigin); + if (closestDistance>MIN_NEAR_DISTANCE) + { + mNear = 0; + } + } + + + // Search For Features In This Area That Are Close To Owner Origin + //----------------------------------------------------------------- + if (area.numFeatures) + { + for (int areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + if (feature!=mNear) + { + featureDirection = feature->Origin(); + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + if (featureDistance1.0f) + { + // Compute Velocity Vectors + //-------------------------- + velocityFwd.NormalizeFast(); + velocityFwd.NormalVectors(velocityLeft, velocityDown); + + + // Check If We Should Clear The Look Feature + //------------------------------------------- + if (mLook) + { + const idVec3& featureOrigin = mLook->Origin(); + const idVec3& featureNormal = mLook->Normal(); + + // Too Far? + //---------- + if (featureOrigin.Dist(ownerOrigin)>mLookStopDist) + { + mLook = 0; + } + + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + mLook = 0; + break; + } + } + teammate = NULL; + } + + // And, If We Are Moving, Check The Near Feature To See If It Qualifies As A Look Feature + //---------------------------------------------------------------------------------------- + if (mNear && mNear!=mLook && (gameLocal.GetTime() - mLookStartTime)>3000) + { + const idVec3& featureOrigin = mNear->Origin(); + const idVec3& featureNormal = mNear->Normal(); + + // Compute Feature Direction + //--------------------------- + featureDirection = featureOrigin; + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + // Must Be Behind Me (I've Alreay Walked Past It) + //------------------------------------------------ + if (featureDistance>16.0f && featureDirection*velocityFwd<0.0f) + { + // Must Be Facing Away From Me + //----------------------------- + featureDotDirection = featureNormal*featureDirection; + if (featureDotDirection>-0.8f) + { + // Must Be Roughly Perpendicular To My Velocity (Within 45 Degrees) + //------------------------------------------------------------------ + featureDotLeft = featureNormal*velocityLeft; + if (fabsf(featureDotLeft)>0.5f) + { + // If On Left Of Me, Must Have A Right Lean, And Converse + //-------------------------------------------------------- + if ((featureDotLeft>0.0f && mNear->flags&FEATURE_LOOK_RIGHT) || + (featureDotLeft<0.0f && mNear->flags&FEATURE_LOOK_LEFT)) + { + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + break; + } + } + + if (!teammate) + { + mLook = mNear; + mLookStartTime = gameLocal.GetTime(); + mLookStopDist = rvRandom::flrand(48.0f, 80.0f); + } + } + } + } + } + } + } + else if (!mOwnerAI || !mOwnerAI->move.fl.moving) + { + mLook = 0; + } + + // Draw Any Debug Info + //--------------------- + DrawDebugInfo(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Reserve +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Reserve(aasFeature_t* f) +{ + if (f!=mReserved && mOwner) + { + mReserved = f; + + if ( f ) + { + mReservedOrigin = f->Origin ( ); + } + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// FEATURE TESTING +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// TestSetupCurrentValuesFor +// +// This function sets up the test sets to have new +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::TestSetupCurrentValues() +{ + // Owner Test Set + //---------------- + mFromOwner.SetupOriginAndFacing(mOwner); + + + // Enemy Test Set + //---------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + if (mOwnerAI && mEnemyOverride) + { + mFromEnemy.SetupOriginAndFacing(mEnemyOverride, &mOwnerAI->LastKnownPosition(mEnemyOverride), 0); + } + else if (mOwnerAI && mOwnerAI->GetEnemy()) + { + mFromEnemy.SetupOriginAndFacing(mOwnerAI->GetEnemy(), &mOwnerAI->LastKnownPosition(mOwnerAI->GetEnemy()), 0); + } + + // Tether Test Set + //---------------- + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.SetupOriginAndFacing(mOwnerAI->GetTether ( )); + } + + // Path Test Set + //---------------- + mFromPath.mProjectOrigin = true; + mFromPath.mOrigin = mFromOwner.mOrigin; + mFromPath.mFacing = mFromEnemy.mOrigin - mFromOwner.mOrigin; + mFromPath.mFacing[2] = 0; + mFromPath.mFacing.Normalize(); + + // Advance Test Set + //------------------ + // NOTHING TO DO HERE FOR NOW... +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValidReserved +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValidWithCurrentState(aasFeature_t* f) +{ + // If Trying To Hide From An Enemy That No Longer Exists, Any Feature is Invalid + //------------------------------------------------------------------------------- + if (mEnemyOverride.GetSpawnId() && !mEnemyOverride.IsValid()) + { + return false; + } + + // Reset The Test Parameters With Current Origins (Cuz Things May Have Moved) + //---------------------------------------------------------------------------- + TestSetupCurrentValues(); + + // And Run The Test That The Original Search Ran + //----------------------------------------------- + if (!f) + { + return TestValid(mReserved, 0.0f); + } + return TestValid(f, 0.0f); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValid +// +// This is THE function that tests if a given feature matches the current +// search parameters. An optional walk distance may be passed into the function +// to replace the from owner straight line distance. +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValid(aasFeature_t* f, float walkDistanceToFeature) +{ + static idVec3 LeanNormal; + static idVec3 Up(0.0f,0.0f,1.0f); + static float LeanNormalDot; + + + // Is There A Feature At All + //--------------------------- + if (!f) + { + return false; + } + + // Does It Match The Flags? + //-------------------------- + if (!(f->flags&mFlagsMatchAny) || + ((f->flags&mFlagsMatchAll)!=mFlagsMatchAll) || + (f->flags&mFlagsMatchNone)) + { + return false; + } + + // Does It Pass The Tests? + //------------------------- + if (!mFromOwner.Test(f, walkDistanceToFeature) || + !mFromEnemy.Test(f) || + !mFromTether.Test(f) || + !mFromPath.Test(f) || + !mAdvance.Test(mFromOwner.mDirection*mFromPath.mFacing) || + !mAssignment.Test(mFromOwner.mDirection*mAssignmentDirection)) + { + return false; + } + + // Make sure this cover point is vaild for the current tether + //------------------------------------------------------------ + if ( mOwnerAI && mOwnerAI->IsTethered() && !mOwnerAI->GetTether()->ValidateDestination ( mOwnerAI, f->Origin() ) ) + { + return false; + } + + // Does It Pass The Lean Normal Test? + //------------------------------------ + if (mOwnerAI && (mEnemyOverride||mOwnerAI->GetEnemy())) + {//we have an enemy position to test against + mLeanNormal.mValue = 0.0f; + if (!(f->flags&FEATURE_LOOK_OVER) && ((f->flags&FEATURE_LOOK_RIGHT) || (f->flags&FEATURE_LOOK_LEFT))) + { + LeanNormal = f->Normal().Cross(Up); // Start With Left + LeanNormalDot = LeanNormal * mFromEnemy.mDirection; + + if (!(f->flags&FEATURE_LOOK_LEFT) || ((f->flags&FEATURE_LOOK_RIGHT) && LeanNormalDot<0.0f)) + { + LeanNormalDot *= -1.0f; // Use The Right Normal + } + + if (!mLeanNormal.Test(LeanNormalDot)) + { + return false; + } + } + } + + + // Is Anyone Else Going There? + //----------------------------- + if (mOwnerAI && !aiManager.ValidateDestination(mOwnerAI, f->Origin())) + { + return false; + } + + // Everything Passed, This Feature Is Good To Go + //----------------------------------------------- + return true; +} + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SEARCH PARAMETERS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchReset +// +// Initialize Default Flags, Feature Counts, And Population Points. This +// function must be called before first in any search function, because +// the search functions rely on this standard set of parameters and then +// build upon them. +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchReset(idEntity* enemyOverride, float ownerRangeMin, float ownerRangeMax) +{ + if (!mOwner) + { + return; + } + + // Setup Base Search Parameters + //------------------------------ + mFlagsMatchAll = 0; // Must Have All Of These + mFlagsMatchAny = (FEATURE_LOOK_LEFT|FEATURE_LOOK_RIGHT|FEATURE_LOOK_OVER); // Must Have At Least One Of These + mFlagsMatchNone = (FEATURE_PINCH|FEATURE_VANTAGE); // Don't Want Any Of These + mFeaturesSearchMax = 100; + mFeaturesFinalMax = 20; + mAssignmentValid = false; // TODO: Turn This Back On + mAssignmentDirection = vec3_zero; + mEnemyOverride = enemyOverride; + + + // Lean Normal Test + //------------------ + mLeanNormal.Reset(); + mLeanNormal.mMin =-0.2f; // Must Lean Toward Enemy + + + // Owner Test Set Default Values + //------------------------------- + mFromOwner.Reset(); + mFromOwner.mDistance.mMin = ownerRangeMin; + mFromOwner.mDistance.mMax = ownerRangeMax; + mFromOwner.mDistance.mWeight =-1.0f; // Prefer Close To Owner + + // Enemy Test Set Default Values + //------------------------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + mFromEnemy.Reset(); + if ( mOwnerAI && mOwnerAI->enemy.ent ) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; // must be at least 100 units from enemy + mFromEnemy.mDistance.mMax = 2.0f; // don't care how far the distance is to the enemy, let it go over max (up to 1600) + mFromEnemy.mDirectionDot.mMax =-0.7f; // Must Face Within 45 Degrees Of enemy + mFromEnemy.mDirectionDot.mWeight =-0.3f; // Prefer To Face Toward Enemy + if (mOwnerAI && mOwnerAI->enemy.ent ) + { + // Cap Min And Max Distances To Attack Range, and Aware Range + //------------------------------------------------------------ + mFromEnemy.mDistance.mMax = mOwnerAI->combat.attackRange[1] / MAX_DISTANCE; + mFromEnemy.mDistance.mMin = mOwnerAI->combat.attackRange[0] / MAX_DISTANCE; + + if (mFromEnemy.mDistance.mMin < (mOwnerAI->combat.awareRange / MAX_DISTANCE)) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; + } + + // If haven't seen enemy in a while, allow you to go right to his last known spot + //-------------------------------------------------------------------------------- + if ( mOwnerAI->enemy.lastVisibleTime && (gameLocal.GetTime() - mOwnerAI->enemy.lastVisibleTime)>mOwnerAI->combat.maxLostVisTime/2.0f) + { + mFromEnemy.mDistance.mMin = 0.0f; + } + } + } + + + // Tether test set + //------------------------------- + mFromTether.Reset(); + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.mFacingDot.mMin = 0.5f; + mFromTether.mFacingDot.mWeight = 0.3f; + + // Disable the owner distance test + mFromOwner.Reset(); + } + + // Other Test Sets + //----------------- + mFromPath.Reset(); + mAdvance.Reset(); + mAssignment.Reset(); + + + // Default Test Values + //--------------------- + TestSetupCurrentValues(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchDebug +// +// TO RUN THIS FUNCTION, TYPE "extract_tactical" ON THE CONSOLE. +// +// Feel free to modify this function to test whatever search or other +// operation you need. The owner will be the player. +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchDebug() +{ + SearchCover(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRadius +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRadius(const idVec3& origin, float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Radius"; + mFromEnemy.Reset(); // Don't Care About Enemy At All + if ( origin != vec3_origin ) + { + mFromOwner.mOrigin = origin; // Override the owner origin + } + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchCover +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchCover(float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Cover"; + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchHide +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchHide(idEntity* from) +{ + SearchReset(from); + mSearchName = "Hide"; + mFlagsMatchNone |= FEATURE_LOOK_OVER; // Want Full Height Walls Here + mFromEnemy.mDirectionDot.mMax = -0.8f; // Must Almost Exactly At The Enemy + mFromOwner.mDistance.mMax = 2.0f; // Go as far as you need to - ignore tethering for hide + mFromOwner.mDistance.mMin = 0.4f; // Get A Good Distance Away + mFromOwner.mDirectionDot.Reset(); // Ignore any direction dot with the leader + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchFlank +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchFlank() +{ + SearchReset(); + mSearchName = "Flank"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMin = -0.2f; // Must Be Behind Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAdvance +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAdvance() +{ + SearchReset(); + mSearchName = "Advance"; + mFromOwner.mDistance.mMin = 0.15f; // Make Sure To Move Some + mAdvance.mMin = 0.3f; // Forward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRetreat +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRetreat() +{ + SearchReset(); + mSearchName = "Retreat"; + mFromOwner.mDistance.mMin = 0.1f; // Make Sure To Move Some + mAdvance.mMax = -0.3f; // Backward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAmbush +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAmbush() +{ + SearchReset(); + mSearchName = "Ambush"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMax = 0.2f; // Must Be In Front Of Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// THE SEARCH +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchComputeWeightRange +// +// We compute the weight range so that we can make a better scale factor +// between 0.0 and 1.0 later on. It's not critical that all the weights +// factor exactly 0.0 to 1.0, but it is nice to see how "good" a feature +// matches the given search parameters +/////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeightRange(float& rangeNegative) +{ + return (mFromOwner.WeightRange(rangeNegative) + + mFromEnemy.WeightRange(rangeNegative) + + mFromTether.WeightRange(rangeNegative) + + mFromPath.WeightRange(rangeNegative) + + mAdvance.WeightRange(rangeNegative) + + mLeanNormal.WeightRange(rangeNegative) + + mAssignment.WeightRange(rangeNegative)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Weight +// +// Add up the computed weight of all tests. +/////////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeight() +{ + return (mFromOwner.Weight() + + mFromEnemy.Weight() + + mFromTether.Weight() + + mFromPath.Weight() + + mAdvance.Weight() + + mLeanNormal.Weight() + + mAssignment.Weight()); +} + +/////////////////////////////////////////////////////////////////////////////// +// SortFeature function (used by Search() below) +/////////////////////////////////////////////////////////////////////////////// +ID_INLINE int rvSortFeature( const TFeaturePtr *a, const TFeaturePtr *b ) +{ + if ((*a)->weight > (*b)->weight) + { + return -1; + } + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Search +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Search() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!mOwner || !aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures()) + { + return; + } + + + static idAASFile* file; + static int areaNum; + static int areaNumOwner; + static rvBits<32000> areaVisit; + static int areaVisitCount; + static int travelTime; + static int areaFeatureNum; + static int featureNum; + static aasFeature_t* featurePtr; + static idVec3 featureOrigin; + static idReachability* reach; + static idList searchHeap; + static rvSortReach sortReach; + static float walkDistanceToFeature; + static float weight; + static float weightRangeNegative; + static float weightRangeTotal; + static idVec3 from; + static idVec3 endPos; + static int endAreaNum; + + + + +//----------------------------------------------------------------------------- +// SETUP +//----------------------------------------------------------------------------- + file = aas->GetFile(); + weightRangeNegative = 0.0f; + weightRangeTotal = SearchComputeWeightRange(weightRangeNegative); + + mFeatures.Clear(); + searchHeap.Clear(); + areaVisit.clear(); + if (!mAssignmentValid) + { + mAssignment.Reset(); // Never Worry About Squad Assignments If No Leader Is Active + } + + + + +//----------------------------------------------------------------------------- +// PHASE I - POPULATE AREA QUEUE +//----------------------------------------------------------------------------- + if ( mOwnerAI ) + { + areaNumOwner = mOwnerAI->PointReachableAreaNum ( mFromOwner.mOrigin ); + } + else + { + areaNumOwner = aas->PointReachableAreaNum(mFromOwner.mOrigin, mOwner->GetPhysics()->GetBounds(), AREA_REACHABLE_WALK); + } + + from = mFromOwner.mOrigin; + areaNum = areaNumOwner; + sortReach.mAreaNum = areaNumOwner; + sortReach.mDistance = 0.0f; + sortReach.mReach = 0; + searchHeap.Append(sortReach); + + + + +//----------------------------------------------------------------------------- +// PHASE II - BREADTH FIRST SEARCH NEIGHBORING AREAS FOR FEATURES THAT TEST OK +//----------------------------------------------------------------------------- + areaVisitCount = 0; + while (searchHeap.Num() && areaVisitCountGetArea(sr.mAreaNum); + if (area.numFeatures) + { + for (areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + featureOrigin = featurePtr->Origin(); + + // If Walk Path Is Valid, Allow From Owner Test To Use Computed Straight Line Distance + //------------------------------------------------------------------------------------- + if (aas->WalkPathValid(areaNumOwner, mFromOwner.mOrigin, sr.mAreaNum, featureOrigin, TFL_WALK, endPos, endAreaNum)) + { + walkDistanceToFeature = 0.0f; // Allows Test to use straight line distance computed + } + + // If It Is Not Possible To Straight Line Walk To A Feature, Use The Enter Point And Distance Of The Area (Which Is A Rough Appx) + //-------------------------------------------------------------------------------------------------------------------------------- + else + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(featureOrigin)):(mFromOwner.mOrigin.Dist(featureOrigin))); + } + + // Test The Feature To See If It's Valid + //--------------------------------------- + if (!TestValid(featurePtr, walkDistanceToFeature)) + { + continue; + } + + // Compute The Weight + //-------------------- + if (weightRangeTotal>0.0f) + { + weight = SearchComputeWeight(); // Compute Weight Sum + weight -= weightRangeNegative; // Bring it into a positive range + weight /= weightRangeTotal; // Scale down to 0.0 - 1.0 + + assert(weight>0.0f && weight<255.0f); + featurePtr->weight = (char)(weight*255); + } + else + { + featurePtr->weight = (unsigned char)(128); // No Sorting + } + + + // Append The Feature To The List + //-------------------------------- + mFeatures.Append(featurePtr); + } + } + + + // Add Neighboring Areas To Search + //--------------------------------- + for (reach=area.reach; reach; reach=reach->next) + { + if ((reach->travelType&TFL_WALK)) + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(reach->end)):(mFromOwner.mOrigin.Dist(reach->end))); + + if (walkDistanceToFeaturetoAreaNum; + sortReach.mReach = reach; + searchHeap.HeapAdd(sortReach); + } + } + } + } + + + + +//----------------------------------------------------------------------------- +// PHASE III - SORT AND CLIP THE FEATURE LIST +//----------------------------------------------------------------------------- + mFeatures.Sort(rvSortFeature); + + // Now, Clip The Sorted List To The Max Size + //------------------------------------------- + if (mFeatures.Num()>MAX_FEATURE_LIST) + { + mFeatures.Resize(MAX_FEATURE_LIST); + } + + // Now Reset Any Parameters Which Were Only "Temporary" During The Search, and Do Not Invalidate The Point Later + //---------------------------------------------------------------------------------------------------------------- + mFromOwner.mDistance.mMin = 0.0f; // Allow Getting Close Again + + + // Print Search Results + //---------------------- + if (ai_showTacticalFeatures.GetInteger()==3) + { + common->Printf( "[%10d] Search%s Found %d Features For %s\n", gameLocal.GetTime(), mSearchName.c_str(), mFeatures.Num(), mOwner->GetName() ); + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DEBUG GRAPHICS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_t::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void aasFeature_t::DrawDebugInfo( int index ) +{ + static idVec3 Height; + static idVec3 Orig; + static idVec3 Norm; + static idVec3 Text; + static idVec4 color; + static idVec3 Left; + + int lifetime = 0; + + color = colorWhite; + + if (flags & FEATURE_COVER) + { + color = colorGreen; + } + + Orig = Origin(); + Orig[2] += 1.0f; + + Height = Orig; + Height[2] += height; + + Norm = Orig; + Norm += Normal() * mDebugRadius; + Left = Normal().Cross(idVec3(0,0,-1)) * mDebugRadius; + + gameRenderWorld->DebugLine( color, Orig, Height, lifetime ); + gameRenderWorld->DebugLine( color, Orig, Norm, lifetime ); + + if (index>=0) + { + Text = (Origin() + Height) * 0.5f; + gameRenderWorld->DrawText( va( "%d", index ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + + Text[2] += 15.0f; + gameRenderWorld->DrawText( va( "%d", (int)weight ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + } + + // Left Corner Is On The Ground + //------------------------------ + if (flags & FEATURE_CORNER_LEFT) + { + gameRenderWorld->DebugLine( color, Orig, Orig + Left, lifetime ); + } + + // Otherwise Windows Are At The Given Height + //------------------------------------------- + else if (flags & FEATURE_LOOK_LEFT) + { + gameRenderWorld->DebugLine( color, Height, Height + Left, lifetime ); + } + + if (flags & FEATURE_CORNER_RIGHT) + { + gameRenderWorld->DebugLine( color, Orig, Orig - Left, lifetime ); + } + else if (flags & FEATURE_LOOK_RIGHT) + { + gameRenderWorld->DebugLine( color, Height, Height - Left, lifetime ); + } + + if (flags & FEATURE_LOOK_OVER) + { + gameRenderWorld->DebugLine( color, Height, Height + (Normal()*mDebugRadius), lifetime ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction) +{ + gameRenderWorld->DebugFOV(color, origin, direction, mMax, 20.0f, mMin, 10.0f, 20.0f); +} + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin) +{ + static idVec3 up(0.0f,0.0f,-1.0f); + if (mMax<1.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMax * MAX_DISTANCE), 25); + } + if (mMin>0.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMin * MAX_DISTANCE), 25); + } +} +/////////////////////////////////////////////////////////////////////////// +// rvTestSet::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTestSet::DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin) +{ + static int lifetime = 0; + static idVec3 origin; + + origin = mOrigin; + if (mProjectOrigin) + { + origin = nonProjectedOrigin; + } + + gameRenderWorld->DebugArrow( color, origin, origin+mFacing * 25.0f, 8, lifetime ); + mFacingDot.DrawDebugInfo(color, origin, mFacing); + mDistance.DrawDebugInfo(color, origin); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::DrawDebugInfo() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden() || (ai_showTacticalFeatures.GetInteger()<2 && !mOwner->DebugFilter(ai_showTacticalFeatures) && !mOwner->DebugFilter(ai_debugTactical))) + { + return; + } + + + static idVec3 pos; + bool reservedDrawn = false; + bool nearDrawn = false; + bool lookDrawn = false; + + mDebugRadius = aas->GetSettings()->boundingBoxes[0][1][0]; + + + // Draw Parameters + //----------------- + if (ai_showTacticalFeatures.GetInteger()==1) + { + if (!mSearchName.IsEmpty()) + { + pos = mFromOwner.mOrigin + mFromEnemy.mOrigin; + pos *= 0.5f; + pos[2] += 25.0f; + gameRenderWorld->DrawText(mSearchName.c_str(), pos, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, 0 ); + pos[2] -= 25.0f; + + // Draw Tests + //------------ + mFromEnemy.DrawDebugInfo(colorYellow, pos); + mFromTether.DrawDebugInfo(colorOrange, pos); + mFromOwner.DrawDebugInfo(colorMagenta, pos); + mFromPath.DrawDebugInfo(colorCyan, pos); + mAssignment.DrawDebugInfo(colorPink, pos); + mAdvance.DrawDebugInfo(colorPurple, mFromOwner.mOrigin, mFromPath.mFacing); + mLeanNormal.DrawDebugInfo(colorPurple, pos); + } + + + // Draw Features + //--------------- + for (int i=0; iDrawDebugInfo(i); + if (mFeatures[i]==mReserved) + { + reservedDrawn = true; + } + if (mFeatures[i]==mNear) + { + nearDrawn = true; + } + if (mFeatures[i]==mLook) + { + lookDrawn = true; + } + } + } + + // Draw All Neighboring Features If Player & CVar==2 + //--------------------------------------------------- + if (mOwner==gameLocal.GetLocalPlayer() && ai_showTacticalFeatures.GetInteger()>=2) + { + idAASFile* file = aas->GetFile(); + const idVec3& playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + for (int i=0; iGetNumFeatures(); i++) + { + if (file->GetFeature(i).Origin().Dist(playerOrigin)<600.0f) + { + file->GetFeature(i).DrawDebugInfo(); + } + } + } + + + // Always Draw Reserved + //---------------------- + if (mReserved) + { + if (!reservedDrawn) + { + mReserved->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorBlue, mOwner->GetPhysics()->GetOrigin(), mReserved->Origin(), 8); + } + + // If Near Is Valid, Draw It + //--------------------------- + if (mNear && (!mReserved || mNear!=mReserved)) + { + if (!nearDrawn) + { + mNear->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorOrange, mOwner->GetPhysics()->GetOrigin(), mNear->Origin(), 8); + } + + // If Look Is True, Then Draw That + //--------------------------------- + if (mLook && (!mOwnerAI || mOwnerAI->InLookAtCoverMode())) + { + idVec3 n = mLook->Normal(); + n *= 64.0f; + gameRenderWorld->DebugArrow(colorYellow, mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32), mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32) + n, 8); + } +} + + + + +// RAVENEND - CDR + + + + + diff --git a/source/mpgame/ai/AAS_tactical.h b/source/mpgame/ai/AAS_tactical.h new file mode 100644 index 0000000..fc1385d --- /dev/null +++ b/source/mpgame/ai/AAS_tactical.h @@ -0,0 +1,69 @@ +/////////////////////////////////////////////////////////////////////////////// +// AAS_tactical +// +// This file is the interface to an AAS Tactical Extractor, which can search +// out from a given start point and report a variety of tactically important +// objectives, including corners, walls, and pinch points. +// +// By seeing the AAS graph as a qualitative and simplified spatial +// representation of the game world. This representation is amply capapble of +// rendering higher level tactical data efficiently in real time. +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AAS_TACTICAL_H__ +#define __AAS_TACTICAL_H__ + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor +// +// The sensor structure is the public interface to the internals of AAS +// tactical features. +/////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensor +{ + // Regular Update Function + /////////////////////////////////////////////////////////////////////// + virtual void Update() = 0; + virtual void Save(idSaveGame *savefile) = 0; + virtual void Restore(idRestoreGame *savefile) = 0; + virtual void Clear() = 0; + + // Search + /////////////////////////////////////////////////////////////////////// + virtual void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchHide(idEntity* from=0) = 0; + virtual void SearchFlank() = 0; + virtual void SearchAdvance() = 0; + virtual void SearchRetreat() = 0; + virtual void SearchAmbush() = 0; + virtual void SearchDebug() = 0; + + // Feature Testing + /////////////////////////////////////////////////////////////////////// + virtual bool TestValid(aasFeature_t* f, float walkDistanceToFeature) = 0; + virtual bool TestValidWithCurrentState(aasFeature_t* f=0) = 0; + + // Feature Reservation + /////////////////////////////////////////////////////////////////////// + virtual void Reserve(aasFeature_t* f) = 0; + + // Access To Results + /////////////////////////////////////////////////////////////////////// + virtual int FeatureCount() = 0; + virtual aasFeature_t* Feature(int i) = 0; + virtual aasFeature_t* Near() const = 0; + virtual aasFeature_t* Look() const = 0; + virtual aasFeature_t* Reserved() const = 0; + virtual const idVec3& ReservedOrigin() const = 0; + + + + // STATIC SYSTEM FUNCTIONS + /////////////////////////////////////////////////////////////////////// + static rvAASTacticalSensor* CREATE_SENSOR(idActor* owner); +}; + + +#endif /* !__AAS_TACTICAL_H__ */ diff --git a/source/mpgame/ai/AI.cpp b/source/mpgame/ai/AI.cpp new file mode 100644 index 0000000..8fce0d7 --- /dev/null +++ b/source/mpgame/ai/AI.cpp @@ -0,0 +1,5151 @@ +/* +================ + +AI.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "../Projectile.h" +#include "../spawner.h" +#include "AI_Tactical.h" + +const char* aiTalkMessageString [ ] = { + "None", + "primary", + "secondary", + "loop" +}; + +static const float AI_SIGHTDELAYSCALE = 5000.0f; // Full sight delay at 5 seconds or more of not seeing enemy + + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +/* +===================== +idAI::idAI +===================== +*/ +idAI::idAI ( void ) { + projectile_height_to_distance_ratio = 1.0f; + + aas = NULL; + aasSensor = NULL; + aasFind = NULL; + + lastHitCheckResult = false; + lastHitCheckTime = 0; + lastAttackTime = 0; + projectile = NULL; + projectileClipModel = NULL; + chatterTime = 0; + talkState = TALK_NEVER; + talkTarget = NULL; + talkMessage = TALKMSG_NONE; + talkBusyCount = 0; + + enemy.ent = NULL; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + + fl.neverDormant = false; // AI's can go dormant + + allowEyeFocus = true; + disablePain = false; + allowJointMod = true; + focusEntity = NULL; + focusTime = 0; + alignHeadTime = 0; + forceAlignHeadTime = 0; + + orientationJoint = INVALID_JOINT; + + eyeVerticalOffset = 0.0f; + eyeHorizontalOffset = 0.0f; + headFocusRate = 0.0f; + eyeFocusRate = 0.0f; + focusAlignTime = 0; + focusRange = 0.0f; + focusType = AIFOCUS_NONE; + + memset ( &combat.fl, 0, sizeof(combat.fl) ); + combat.max_chasing_turn = 0; + combat.shotAtTime = 0; + combat.shotAtAngle = 0.0f; + combat.meleeRange = 0.0f; + combat.tacticalPainTaken = 0; + combat.tacticalFlinches = 0; + combat.investigateTime = 0; + combat.aggressiveScale = 1.0f; + + passive.animFidgetPrefix.Clear ( ); + passive.animIdlePrefix.Clear ( ); + passive.animTalkPrefix.Clear ( ); + passive.idleAnim.Clear(); + passive.prefix.Clear(); + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + memset ( &passive.fl, 0, sizeof(passive.fl) ); + + pain.lastTakenTime = 0; + pain.takenThisFrame = 0; + pain.loopEndTime = 0; + + enemy.range = 0; + enemy.range2d = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + enemy.lastKnownPosition.Zero ( ); + enemy.lastVisibleEyePosition.Zero ( ); + enemy.lastVisibleChestPosition.Zero ( ); + enemy.lastVisibleFromEyePosition.Zero ( ); + enemy.checkTime = 0; + enemy.changeTime = 0; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + memset ( &enemy.fl, 0, sizeof(enemy.fl) ); + + currentFocusPos.Zero(); + eyeAng.Zero(); + lookAng.Zero(); + destLookAng.Zero(); + lookMin.Zero(); + lookMax.Zero(); + + eyeMin.Zero(); + eyeMax.Zero(); + + helperCurrent = NULL; + helperIdeal = NULL; + + speakTime = 0; + + actionAnimNum = 0; + actionSkipTime = 0; + actionTime = 0; +} + +/* +===================== +idAI::~idAI +===================== +*/ +idAI::~idAI() { + // Make sure we arent stuck in the simple think list + simpleThinkNode.Remove ( ); + + delete aasFind; + delete aasSensor; + delete projectileClipModel; + DeconstructScriptObject(); + scriptObject.Free(); + aiManager.RemoveTeammate ( this ); + SetPhysics( NULL ); +} + +/* +===================== +idAI::Save +===================== +*/ +void idAI::Save( idSaveGame *savefile ) const { + int i; + +// cnicholson: These 3 vars are intentionally not saved, as noted in the restore + // NOSAVE: idLinkList simpleThinkNode; + // NOSAVE: idAAS* aas; + // NOSAVE: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + // Movement + move.Save( savefile ); + savedMove.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + savefile->WriteBool( lastHitCheckResult ); + savefile->WriteInt( lastHitCheckTime ); + savefile->WriteInt( lastAttackTime ); + savefile->WriteFloat( projectile_height_to_distance_ratio ); + + savefile->WriteInt( attackAnimInfo.Num() ); + for( i = 0; i < attackAnimInfo.Num(); i++ ) { + savefile->WriteVec3( attackAnimInfo[ i ].attackOffset ); + savefile->WriteVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TOSAVE: mutable idClipModel* projectileClipModel; + projectile.Save ( savefile ); + + // Talking + savefile->WriteInt( chatterTime ); +// savefile->WriteInt( chatterRateCombat ); // NOSAVE: +// savefile->WriteInt( chatterRateIdle ); // NOSAVE: + savefile->WriteInt( talkState ); + talkTarget.Save( savefile ); + savefile->WriteInt( talkMessage ); + savefile->WriteInt( talkBusyCount ); + savefile->WriteInt ( speakTime ); + + // Focus + lookTarget.Save ( savefile ); + savefile->WriteInt( focusType ); + focusEntity.Save ( savefile ); + savefile->WriteFloat ( focusRange ); + savefile->WriteInt ( focusAlignTime ); + savefile->WriteInt ( focusTime ); + savefile->WriteVec3( currentFocusPos ); + + // Looking + savefile->WriteBool( allowJointMod ); + savefile->WriteInt( alignHeadTime ); + savefile->WriteInt( forceAlignHeadTime ); + savefile->WriteAngles( eyeAng ); + savefile->WriteAngles( lookAng ); + savefile->WriteAngles( destLookAng ); + savefile->WriteAngles( lookMin ); + savefile->WriteAngles( lookMax ); + + savefile->WriteInt( lookJoints.Num() ); + for( i = 0; i < lookJoints.Num(); i++ ) { + savefile->WriteJoint( lookJoints[ i ] ); + savefile->WriteAngles( lookJointAngles[ i ] ); + } + + savefile->WriteFloat( eyeVerticalOffset ); + savefile->WriteFloat( eyeHorizontalOffset ); + savefile->WriteFloat( headFocusRate ); + savefile->WriteFloat( eyeFocusRate ); + + // Joint Controllers + savefile->WriteAngles( eyeMin ); + savefile->WriteAngles( eyeMax ); + savefile->WriteJoint( orientationJoint ); + + pusher.Save( savefile ); // cnicholson: Added unsaved var + scriptedActionEnt.Save( savefile ); // cnicholson: Added unsaved var + + savefile->Write( &aifl, sizeof( aifl ) ); + + // Misc + savefile->WriteInt ( actionAnimNum ); + savefile->WriteInt ( actionTime ); + savefile->WriteInt ( actionSkipTime ); + savefile->WriteInt ( flagOverrides ); + + // Combat variables + savefile->Write( &combat.fl, sizeof( combat.fl ) ); + savefile->WriteFloat ( combat.max_chasing_turn ); + savefile->WriteFloat ( combat.shotAtTime ); + savefile->WriteFloat ( combat.shotAtAngle ); + savefile->WriteVec2 ( combat.hideRange ); + savefile->WriteVec2 ( combat.attackRange ); + savefile->WriteInt ( combat.attackSightDelay ); + savefile->WriteFloat ( combat.meleeRange ); + savefile->WriteFloat ( combat.aggressiveRange ); + savefile->WriteFloat ( combat.aggressiveScale ); + savefile->WriteInt ( combat.investigateTime ); + savefile->WriteFloat ( combat.visStandHeight ); + savefile->WriteFloat ( combat.visCrouchHeight ); + savefile->WriteFloat ( combat.visRange ); + savefile->WriteFloat ( combat.earRange ); + savefile->WriteFloat ( combat.awareRange ); + savefile->WriteInt ( combat.tacticalMaskAvailable ); + savefile->WriteInt ( (int&)combat.tacticalCurrent ); + savefile->WriteInt ( combat.tacticalUpdateTime ); + savefile->WriteInt ( combat.tacticalPainTaken ); + savefile->WriteInt ( combat.tacticalPainThreshold ); + savefile->WriteInt ( combat.tacticalFlinches ); + savefile->WriteInt ( combat.maxLostVisTime ); + savefile->WriteFloat ( combat.threatBase ); + savefile->WriteFloat ( combat.threatCurrent ); + savefile->WriteInt ( combat.coverValidTime ); + savefile->WriteInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->WriteString ( passive.prefix ); + savefile->WriteString ( passive.animFidgetPrefix ); + savefile->WriteString ( passive.animIdlePrefix); + savefile->WriteString ( passive.animTalkPrefix ); + savefile->WriteString ( passive.idleAnim ); + savefile->WriteInt ( passive.idleAnimChangeTime ); + savefile->WriteInt ( passive.fidgetTime ); + savefile->WriteInt ( passive.talkTime ); + savefile->Write ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Save ( savefile ); + savefile->Write ( &enemy.fl, sizeof(enemy.fl) ); + savefile->WriteInt ( enemy.lastVisibleChangeTime ); + savefile->WriteVec3 ( enemy.lastKnownPosition ); + savefile->WriteVec3 ( enemy.smoothedLinearVelocity ); + savefile->WriteVec3 ( enemy.smoothedPushedVelocity ); + savefile->WriteVec3 ( enemy.lastVisibleEyePosition ); + savefile->WriteVec3 ( enemy.lastVisibleChestPosition ); + savefile->WriteVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->WriteInt ( enemy.lastVisibleTime ); + savefile->WriteFloat ( enemy.range ); + savefile->WriteFloat ( enemy.range2d ); + savefile->WriteInt ( enemy.changeTime ); + savefile->WriteInt ( enemy.checkTime ); + + // Pain variables + savefile->WriteFloat ( pain.threshold ); + savefile->WriteFloat ( pain.takenThisFrame ); + savefile->WriteInt ( pain.lastTakenTime ); + savefile->WriteInt ( pain.loopEndTime ); + savefile->WriteString ( pain.loopType ); + + // Functions + funcs.first_sight.Save ( savefile ); + funcs.sight.Save ( savefile ); + funcs.pain.Save ( savefile ); + funcs.damage.Save ( savefile ); + funcs.death.Save ( savefile ); + funcs.attack.Save ( savefile ); + funcs.init.Save ( savefile ); + funcs.onclick.Save ( savefile ); + funcs.launch_projectile.Save ( savefile ); + funcs.footstep.Save ( savefile ); + + mPlayback.Save( savefile ); // cnicholson: Added save functionality + mLookPlayback.Save( savefile ); // cnicholson: Added save functionality + + // Tactical Sensor + aasSensor->Save( savefile ); + + // Helpers + tether.Save ( savefile ); + helperCurrent.Save ( savefile ); + helperIdeal.Save ( savefile ); + leader.Save ( savefile ); + spawner.Save ( savefile ); + + // Action timers + actionTimerRangedAttack.Save ( savefile ); + actionTimerEvade.Save ( savefile ); + actionTimerSpecialAttack.Save ( savefile ); + actionTimerPain.Save ( savefile ); + + // Actions + actionEvadeLeft.Save ( savefile ); + actionEvadeRight.Save ( savefile ); + actionRangedAttack.Save ( savefile ); + actionMeleeAttack.Save ( savefile ); + actionLeapAttack.Save ( savefile ); + actionJumpBack.Save ( savefile ); +} + +/* +===================== +idAI::Restore +===================== +*/ +void idAI::Restore( idRestoreGame *savefile ) { + bool restorePhysics; + int i; + int num; + idBounds bounds; + + InitNonPersistentSpawnArgs ( ); + + // INTENTIONALLY NOT SAVED: idLinkList simpleThinkNode; + // INTENTIONALLY NOT SAVED: idAAS* aas; + // INTENTIONALLY NOT SAVED: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + move.Restore( savefile ); + savedMove.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + savefile->ReadBool( restorePhysics ); + if ( restorePhysics ) { + RestorePhysics( &physicsObj ); + } + + savefile->ReadBool( lastHitCheckResult ); + savefile->ReadInt( lastHitCheckTime ); + savefile->ReadInt( lastAttackTime ); + savefile->ReadFloat( projectile_height_to_distance_ratio ); + + savefile->ReadInt( num ); + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( attackAnimInfo[ i ].attackOffset ); + savefile->ReadVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TORESTORE: mutable idClipModel* projectileClipModel; + projectile.Restore( savefile ); + + // Talking + savefile->ReadInt( chatterTime ); +// savefile->ReadInt( chatterRateCombat ); // Don't save +// savefile->ReadInt( chatterRateIdle ); // Don't save + savefile->ReadInt( i ); + talkState = static_cast( i ); + talkTarget.Restore( savefile ); + savefile->ReadInt( i ); + talkMessage = static_cast( i ); + savefile->ReadInt( talkBusyCount ); + savefile->ReadInt ( speakTime ); + + // Focus + lookTarget.Restore ( savefile ); + savefile->ReadInt( i ); + focusType = static_cast( i ); + focusEntity.Restore ( savefile ); + savefile->ReadFloat ( focusRange ); + savefile->ReadInt ( focusAlignTime ); + savefile->ReadInt ( focusTime ); + savefile->ReadVec3( currentFocusPos ); + + // Looking + savefile->ReadBool( allowJointMod ); + savefile->ReadInt( alignHeadTime ); + savefile->ReadInt( forceAlignHeadTime ); + savefile->ReadAngles( eyeAng ); + savefile->ReadAngles( lookAng ); + savefile->ReadAngles( destLookAng ); + savefile->ReadAngles( lookMin ); + savefile->ReadAngles( lookMax ); + + savefile->ReadInt( num ); + lookJoints.SetGranularity( 1 ); + lookJoints.SetNum( num ); + lookJointAngles.SetGranularity( 1 ); + lookJointAngles.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadJoint( lookJoints[ i ] ); + savefile->ReadAngles( lookJointAngles[ i ] ); + } + + savefile->ReadFloat( eyeVerticalOffset ); + savefile->ReadFloat( eyeHorizontalOffset ); + savefile->ReadFloat( headFocusRate ); + savefile->ReadFloat( eyeFocusRate ); + + // Joint Controllers + savefile->ReadAngles( eyeMin ); + savefile->ReadAngles( eyeMax ); + savefile->ReadJoint( orientationJoint ); + + pusher.Restore( savefile ); // cnicholson: Added unrestored var + scriptedActionEnt.Restore ( savefile ); // cnicholson: Added unrestored var + + savefile->Read( &aifl, sizeof( aifl ) ); + + // Misc + savefile->ReadInt ( actionAnimNum ); + savefile->ReadInt ( actionTime ); + savefile->ReadInt ( actionSkipTime ); + savefile->ReadInt ( flagOverrides ); + + // Combat variables + savefile->Read ( &combat.fl, sizeof( combat.fl ) ); + savefile->ReadFloat ( combat.max_chasing_turn ); + savefile->ReadFloat ( combat.shotAtTime ); + savefile->ReadFloat ( combat.shotAtAngle ); + savefile->ReadVec2 ( combat.hideRange ); + savefile->ReadVec2 ( combat.attackRange ); + savefile->ReadInt ( combat.attackSightDelay ); + savefile->ReadFloat ( combat.meleeRange ); + savefile->ReadFloat ( combat.aggressiveRange ); + savefile->ReadFloat ( combat.aggressiveScale ); + savefile->ReadInt ( combat.investigateTime ); + savefile->ReadFloat ( combat.visStandHeight ); + savefile->ReadFloat ( combat.visCrouchHeight ); + savefile->ReadFloat ( combat.visRange ); + savefile->ReadFloat ( combat.earRange ); + savefile->ReadFloat ( combat.awareRange ); + savefile->ReadInt ( combat.tacticalMaskAvailable ); + savefile->ReadInt ( (int&)combat.tacticalCurrent ); + savefile->ReadInt ( combat.tacticalUpdateTime ); + savefile->ReadInt ( combat.tacticalPainTaken ); + savefile->ReadInt ( combat.tacticalPainThreshold ); + savefile->ReadInt ( combat.tacticalFlinches ); + savefile->ReadInt ( combat.maxLostVisTime ); + savefile->ReadFloat ( combat.threatBase ); + savefile->ReadFloat ( combat.threatCurrent ); + savefile->ReadInt ( combat.coverValidTime ); + savefile->ReadInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->ReadString ( passive.prefix ); + savefile->ReadString ( passive.animFidgetPrefix ); + savefile->ReadString ( passive.animIdlePrefix); + savefile->ReadString ( passive.animTalkPrefix ); + savefile->ReadString ( passive.idleAnim ); + savefile->ReadInt ( passive.idleAnimChangeTime ); + savefile->ReadInt ( passive.fidgetTime ); + savefile->ReadInt ( passive.talkTime ); + savefile->Read ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Restore ( savefile ); + savefile->Read ( &enemy.fl, sizeof(enemy.fl) ); + savefile->ReadInt ( enemy.lastVisibleChangeTime ); + savefile->ReadVec3 ( enemy.lastKnownPosition ); + savefile->ReadVec3 ( enemy.smoothedLinearVelocity ); + savefile->ReadVec3 ( enemy.smoothedPushedVelocity ); + savefile->ReadVec3 ( enemy.lastVisibleEyePosition ); + savefile->ReadVec3 ( enemy.lastVisibleChestPosition ); + savefile->ReadVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->ReadInt ( enemy.lastVisibleTime ); + savefile->ReadFloat ( enemy.range ); + savefile->ReadFloat ( enemy.range2d ); + savefile->ReadInt ( enemy.changeTime ); + savefile->ReadInt ( enemy.checkTime ); + + // Pain variables + savefile->ReadFloat ( pain.threshold ); + savefile->ReadFloat ( pain.takenThisFrame ); + savefile->ReadInt ( pain.lastTakenTime ); + savefile->ReadInt ( pain.loopEndTime ); + savefile->ReadString ( pain.loopType ); + + // Functions + funcs.first_sight.Restore ( savefile ); + funcs.sight.Restore ( savefile ); + funcs.pain.Restore ( savefile ); + funcs.damage.Restore ( savefile ); + funcs.death.Restore ( savefile ); + funcs.attack.Restore ( savefile ); + funcs.init.Restore ( savefile ); + funcs.onclick.Restore ( savefile ); + funcs.launch_projectile.Restore ( savefile ); + funcs.footstep.Restore ( savefile ); + + mPlayback.Restore( savefile ); // cnicholson: Added restore functionality + mLookPlayback.Restore( savefile ); // cnicholson: Added restore functionality + + // Tactical Sensor + aasSensor->Restore( savefile ); + + // Helpers + tether.Restore ( savefile ); + helperCurrent.Restore( savefile ); + helperIdeal.Restore ( savefile ); + leader.Restore ( savefile ); + spawner.Restore ( savefile ); + + // Action timers + actionTimerRangedAttack.Restore ( savefile ); + actionTimerEvade.Restore ( savefile ); + actionTimerSpecialAttack.Restore ( savefile ); + actionTimerPain.Restore ( savefile ); + + // Actions + actionEvadeLeft.Restore ( savefile ); + actionEvadeRight.Restore ( savefile ); + actionRangedAttack.Restore ( savefile ); + actionMeleeAttack.Restore ( savefile ); + actionLeapAttack.Restore ( savefile ); + actionJumpBack.Restore ( savefile ); + + // Set the AAS if the character has the correct gravity vector + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + if ( gravity == gameLocal.GetGravity() ) { + SetAAS(); + } + + // create combat collision hull for exact collision detection, do initial set un-hidden + SetCombatModel(); + LinkCombat(); +} + +/* +===================== +idAI::InitNonPersistentSpawnArgs +===================== +*/ +void idAI::InitNonPersistentSpawnArgs ( void ) { + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + aasFind = NULL; + + simpleThinkNode.Remove ( ); + simpleThinkNode.SetOwner ( this ); + + chatterRateIdle = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_idle", "0" ) ); + chatterRateCombat = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_combat", "0" ) ); + chatterTime = 0; + + combat.tacticalMaskUpdate = 0; + + enemy.smoothVelocityRate = spawnArgs.GetFloat ( "smoothVelocityRate", "0.1" ); +} + +/* +===================== +idAI::Spawn +===================== +*/ +void idAI::Spawn( void ) { + const char* jointname; + const idKeyValue* kv; + idStr jointName; + idAngles jointScale; + jointHandle_t joint; + idVec3 local_dir; + + // Are all monsters disabled? + if ( !g_monsters.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Initialize the non saved spawn args + InitNonPersistentSpawnArgs ( ); + + spawnArgs.GetInt( "team", "1", team ); + spawnArgs.GetInt( "rank", "0", rank ); + + animPrefix = spawnArgs.GetString ( "animPrefix", "" ); + + // Standard flags + fl.notarget = spawnArgs.GetBool ( "notarget", "0" ); + fl.quickBurn = false; + + // AI flags + flagOverrides = 0; + memset ( &aifl, 0, sizeof(aifl) ); + aifl.ignoreFlashlight = spawnArgs.GetBool ( "ignore_flashlight", "1" ); + aifl.lookAtPlayer = spawnArgs.GetBool ( "lookAtPlayer", "0" ); + aifl.disableLook = spawnArgs.GetBool ( "noLook", "0" ); + aifl.undying = spawnArgs.GetBool ( "undying", "0" ); + aifl.killerGuard = spawnArgs.GetBool ( "killer_guard", "0" ); + aifl.scriptedEndWithIdle = true; + + // Setup Move Data + move.Spawn( spawnArgs ); + + pain.threshold = spawnArgs.GetInt ( "painThreshold", "0" ); + pain.takenThisFrame = 0; + + // Initialize combat variables + combat.fl.aware = spawnArgs.GetBool ( "ambush", "0" ); + combat.fl.tetherNoBreak = spawnArgs.GetBool ( "tetherNoBreak", "0" ); + combat.fl.noChatter = spawnArgs.GetBool ( "noCombatChatter" ); + combat.hideRange = spawnArgs.GetVec2 ( "hideRange", "150 750" ); + combat.attackRange = spawnArgs.GetVec2 ( "attackRange", "0 1000" ); + combat.attackSightDelay = SEC2MS ( spawnArgs.GetFloat ( "attackSightDelay", "1" ) ); + combat.visRange = spawnArgs.GetFloat( "visRange", "2048" ); + combat.visStandHeight = spawnArgs.GetFloat( "visStandHeight", "68" ); + combat.visCrouchHeight = spawnArgs.GetFloat( "visCrouchHeight", "48" ); + combat.earRange = spawnArgs.GetFloat( "earRange", "2048" ); + combat.awareRange = spawnArgs.GetFloat( "awareRange", "150" ); + combat.aggressiveRange = spawnArgs.GetFloat( "aggressiveRange", "0" ); + combat.maxLostVisTime = SEC2MS ( spawnArgs.GetFloat ( "maxLostVisTime", "10" ) ); + combat.tacticalPainThreshold = spawnArgs.GetInt ( "tactical_painThreshold", va("%d", health / 4) ); + combat.coverValidTime = 0; + combat.maxInvalidCoverTime = SEC2MS ( spawnArgs.GetFloat ( "maxInvalidCoverTime", "1" ) ); + combat.threatBase = spawnArgs.GetFloat ( "threatBase", "1" ); + combat.threatCurrent = combat.threatBase; + + SetPassivePrefix ( spawnArgs.GetString ( "passivePrefix" ) ); + + disablePain = spawnArgs.GetBool ( "nopain", "0" ); + + spawnArgs.GetFloat( "melee_range", "64", combat.meleeRange ); + spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio ); + + //melee superhero -- take far reduced damage from melee. + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) && spawnArgs.GetBool( "meleeSuperhero", "1") ) { + aifl.meleeSuperhero = true; + } else { + aifl.meleeSuperhero = false; + } + + //announce rate + announceRate = spawnArgs.GetFloat( "announceRate" ); + if( 0.0f == announceRate ) { + announceRate = AISPEAK_CHANCE; + } + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" ); + lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" ); + + lookJoints.SetGranularity( 1 ); + lookJointAngles.SetGranularity( 1 ); + kv = spawnArgs.MatchPrefix( "look_joint", NULL ); + while( kv ) { + jointName = kv->GetKey(); + jointName.StripLeadingOnce( "look_joint " ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + } else { + jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" ); + jointScale.roll = 0.0f; + + // if no scale on any component, then don't bother adding it. this may be done to + // zero out rotation from an inherited entitydef. + if ( jointScale != ang_zero ) { + lookJoints.Append( joint ); + lookJointAngles.Append( jointScale ); + } + } + kv = spawnArgs.MatchPrefix( "look_joint", kv ); + } + + // calculate joint positions on attack frames so we can do proper "can hit" tests + CalculateAttackOffsets(); + + eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" ); + eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" ); + eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" ); + eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" ); + headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.06" ); + eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" ); + focusRange = spawnArgs.GetFloat( "focus_range", "0" ); + focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) ); + + jointname = spawnArgs.GetString( "joint_orientation" ); + if ( *jointname ) { + orientationJoint = animator.GetJointHandle( jointname ); + if ( orientationJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + jointname = spawnArgs.GetString( "joint_flytilt" ); + if ( *jointname ) { + move.flyTiltJoint = animator.GetJointHandle( jointname ); + if ( move.flyTiltJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY ); + } else { + if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + } + + // move up to make sure the monster is at least an epsilon above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + physicsObj.SetGravity( gravity ); + + SetPhysics( &physicsObj ); + + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + lookAng.Zero ( ); + lookAng.yaw = move.current_yaw; + + + SetAAS(); + + projectile = NULL; + projectileClipModel = NULL; + + if ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "trigger_anim" ) ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + Hide(); + } else { + // play a looping ambient sound if we have one + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); + } + + if ( health <= 0 ) { + gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() ); + health = 1; + } + + BecomeActive( TH_THINK ); + + if ( move.fl.allowPushMovables ) { + af.SetupPose( this, gameLocal.time ); + af.GetPhysics()->EnableClip(); + } + + // init the move variables + StopMove( MOVE_STATUS_DONE ); + + // Initialize any scripts + idStr prefix = "script_"; + for ( kv = spawnArgs.MatchPrefix ( prefix.c_str(), NULL ); + kv; + kv = spawnArgs.MatchPrefix ( prefix.c_str(), kv ) ) { + SetScript ( kv->GetKey().c_str() + prefix.Length(), kv->GetValue() ); + } + + // Initialize actions + actionEvadeLeft.Init ( spawnArgs, "action_evadeLeft", NULL, 0 ); + actionEvadeRight.Init ( spawnArgs, "action_evadeRight", NULL, 0 ); + actionRangedAttack.Init ( spawnArgs, "action_rangedAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK|AIACTIONF_MELEE ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", NULL, AIACTIONF_ATTACK ); + actionJumpBack.Init ( spawnArgs, "action_jumpBack", NULL, 0 ); + + // Global action timers + actionTimerRangedAttack.Init ( spawnArgs, "actionTimer_RangedAttack" ); + actionTimerEvade.Init ( spawnArgs, "actionTimer_Evade" ); + actionTimerSpecialAttack.Init ( spawnArgs, "actionTimer_SpecialAttack" ); + actionTimerPain.Init ( spawnArgs, "actionTimer_pain" ); + + // Available tactical options + combat.tacticalUpdateTime = 0; + combat.tacticalCurrent = AITACTICAL_NONE; + combat.tacticalMaskUpdate = 0; + combat.tacticalMaskAvailable = AITACTICAL_TURRET_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_MOVE_TETHER_BIT; + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_cover", "0" ) ? AITACTICAL_COVER_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_ranged", "0" ) ? AITACTICAL_RANGED_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_hide", "0" ) ? AITACTICAL_HIDE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_rush", "0" ) ? AITACTICAL_MELEE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_passive", "0" ) ? AITACTICAL_PASSIVE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "allowPlayerPush", "0" ) ? AITACTICAL_MOVE_PLAYERPUSH_BIT : 0); + + // Talking? + const char* npc; + if ( spawnArgs.GetString( "npc_name", NULL, &npc ) != NULL && *npc ) { + if ( spawnArgs.GetBool ( "follows", "0" ) ) { + SetTalkState ( TALK_FOLLOW ); + } else { + switch ( spawnArgs.GetInt( "talks" ) ) + { + case 2: + SetTalkState ( TALK_WAIT ); + break; + case 1: + SetTalkState ( TALK_OK ); + break; + case 0: + default: + SetTalkState ( TALK_BUSY ); + break; + } + } + } else { + SetTalkState ( TALK_NEVER ); + } + + // Passive or aggressive ai? + if ( spawnArgs.GetBool ( "passive" ) ) { + Event_BecomePassive ( true ); + + if ( spawnArgs.GetInt ( "passive" ) > 1 ) { + aifl.disableLook = true; + } + } + + // Print out a warning about any AI that is spawned unhidden since they will be all thinking + if( gameLocal.GameState ( ) == GAMESTATE_STARTUP && !spawnArgs.GetInt( "hide" ) && !spawnArgs.GetInt( "trigger_anim" ) && !spawnArgs.GetInt( "trigger_cover" ) && !spawnArgs.GetInt( "trigger_move" ) ){ + gameLocal.Warning( "Unhidden AI placed in map (will be constantly active): %s (%s)", name.c_str(), GetPhysics()->GetOrigin().ToString() ); + } + + Begin ( ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + PostEventMS( &EV_PostSpawn, 0 ); + // RAVEN END +} + +/* +=================== +idAI::Begin +=================== +*/ +void idAI::Begin ( void ) { + const char* temp; + bool animWalk; + bool animRun; + + // Look for the lack of a run or walk animation and force the opposite + animWalk = HasAnim ( ANIMCHANNEL_LEGS, "walk" ); + animRun = HasAnim ( ANIMCHANNEL_LEGS, "run" ); + if ( !animWalk && animRun ) { + move.fl.noWalk = true; + } else if ( !animRun && animWalk ) { + move.fl.noRun = true; + } + + // If a trigger anim or hide is specified then wait until activated before waking up + if ( spawnArgs.GetString ( "trigger_anim", "", &temp ) || spawnArgs.GetBool ( "hide" ) ) { + Hide(); + PostState ( "Wait_Activated" ); + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + // Wake up + } else { + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + } +} + +/* +=================== +idAI::WakeUp +=================== +*/ +void idAI::WakeUp ( void ) { + const char* temp; + + // Already awake? + if ( aifl.awake ) { + return; + } + + // Find the closest helper + UpdateHelper ( ); + + aifl.awake = true; + + // If the monster is flying then start them with the flying movement type + if ( spawnArgs.GetBool ( "static" ) ) { + SetMoveType ( MOVETYPE_STATIC ); + } else if ( spawnArgs.GetBool ( "flying" ) ) { + SetMoveType ( MOVETYPE_FLY ); + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += move.fly_offset; + } else { + SetMoveType ( MOVETYPE_ANIM ); + } + + // Wake up any linked entities + WakeUpTargets ( ); + + // Default enemy? + if ( !combat.fl.ignoreEnemies ) { + if ( spawnArgs.GetString ( "enemy", "", &temp ) && *temp ) { + SetEnemy ( gameLocal.FindEntity ( temp ) ); + } else if ( spawnArgs.GetBool ( "forceEnemy", "0" ) ) { + SetEnemy ( FindEnemy ( false, true ) ); + } + } + + // Default leader? + if ( spawnArgs.GetString ( "leader", "", &temp ) && *temp ) { + SetLeader ( gameLocal.FindEntity ( temp ) ); + } + + // If hidden and face enemy is specified we should orient ourselves toward our enemy immediately + if ( enemy.ent && IsHidden ( ) && spawnArgs.GetBool ( "faceEnemy" ) ) { + TurnToward ( enemy.ent->GetPhysics()->GetOrigin() ); + move.current_yaw = move.ideal_yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + Show ( ); + + aiManager.AddTeammate ( this ); + + // External script functions + ExecScriptFunction( funcs.init, this ); + + OnWakeUp ( ); +} + +/* +=================== +idAI::List_f +=================== +*/ +void idAI::List_f( const idCmdArgs &args ) { + int e; + int i; + idEntity* check; + int count; + idDict countsFixed; + idDict countsSpawned; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" ); + gameLocal.Printf( "------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + if ( !check ) { + continue; + } + + if ( check->IsType ( idAI::Type ) ) { + idAI* checkAI = static_cast(check); + + // Skip spawned AI + if ( checkAI->spawner ) { + continue; + } + countsFixed.SetInt ( check->GetEntityDefName(), countsFixed.GetInt ( check->GetEntityDefName(), "0" ) + 1 ); + + gameLocal.Printf( "%4i: %-20s %-20s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), checkAI->move.fl.allowAnimMove ); + count++; + } else if ( check->IsType ( rvSpawner::GetClassType() ) ) { + const idKeyValue* kv; + rvSpawner* checkSpawner = static_cast(check); + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + for ( i = 0; i < checkSpawner->GetNumSpawnPoints(); i ++ ) { + check = checkSpawner->GetSpawnPoint ( i ); + if ( !check ) { + continue; + } + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + } + } + } + + // Combine the two lists + for ( e = 0; e < countsSpawned.GetNumKeyVals(); e ++ ) { + const char* keyName = countsSpawned.GetKeyVal ( e )->GetKey(); + countsFixed.Set ( keyName, va("(%s) %3d", + countsSpawned.GetKeyVal ( e )->GetValue().c_str(), + countsFixed.GetInt ( keyName, "0" ) + atoi(countsSpawned.GetKeyVal ( e )->GetValue()) ) ); + } + + // Print out total counts + if ( countsFixed.GetNumKeyVals() ) { + gameLocal.Printf( "------------------------------------------------\n" ); + for ( e = 0; e < countsFixed.GetNumKeyVals(); e ++ ) { + const idKeyValue* kv = countsFixed.GetKeyVal ( e ); + gameLocal.Printf( "%10s: %-20s\n", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + } + + gameLocal.Printf( "------------------------------------------------\n" ); + gameLocal.Printf( "...%d monsters (%d unique types)\n", count, countsFixed.GetNumKeyVals() ); +} + +/* +================ +idAI::DormantBegin + +called when entity becomes dormant +================ +*/ +void idAI::DormantBegin( void ) { + // since dormant happens on a timer, we wont get to update particles to + // hidden through the think loop, but we need to hide them though. + if ( enemyNode.InList() ) { + // remove ourselves from the enemy's enemylist + enemyNode.Remove(); + } + idActor::DormantBegin(); +} + +/* +================ +idAI::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idAI::DormantEnd( void ) { + if ( enemy.ent && !enemyNode.InList() ) { + // let our enemy know we're back on the trail + idActor *enemyEnt = dynamic_cast< idActor *>( enemy.ent.GetEntity() ); + if( enemyEnt ){ + enemyNode.AddToEnd( enemyEnt->enemyList ); + } + } + idActor::DormantEnd(); +} + +/* +===================== +idAI::ValidateCover + +Validate the reserved cover feature with current conditions. Also quickly tests if +cover does not face enemy at all, which triggers an instant update (ignoring maxCoverInvalidTime) +===================== +*/ +bool idAI::ValidateCover( ) { + if ( !InCoverMode( ) ) { + return false; + } + + if ( aasSensor->TestValidWithCurrentState() && (!enemy.ent || enemy.range > combat.awareRange ) ) { + combat.coverValidTime = gameLocal.time; + } else if ( combat.coverValidTime && !IsCoverValid ( ) ) { + combat.coverValidTime = 0; + OnCoverInvalidated(); + } + + return IsCoverValid(); +} + +/* +===================== +idAI::DoDormantTests +===================== +*/ +bool idAI::DoDormantTests ( void ) { + // If in a scripted move that we should never go dormant in + if ( aifl.scripted && aifl.scriptedNeverDormant ) { + return false; + } + // If following a player never go dormant + if ( leader && leader->IsType ( idPlayer::GetClassType ( ) ) ) { + return false; + } + // AI should no longer go dormant when outside of tether + if ( IsTethered () && !IsWithinTether ( ) ) { + return false; + } + return idActor::DoDormantTests ( ); +} + +/* +===================== +idAI::Think +===================== +*/ +void idAI::Think( void ) { + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + // Simple think this frame? + aifl.simpleThink = aiManager.IsSimpleThink ( this ); + + aiManager.thinkCount++; + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Start ( ); + } + + if ( thinkFlags & TH_THINK ) { + // clear out the enemy when he dies or is hidden + idEntity* enemyEnt = enemy.ent; + idActor* enemyAct = dynamic_cast( enemyEnt ); + + // Clear our enemy if necessary + if ( enemyEnt ) { + if (enemyAct && enemyAct->IsInVehicle()) { + SetEnemy(enemyAct->GetVehicleController().GetVehicle()); // always get angry at the enemy's vehicle first, not the enemy himself + } else { + bool enemyDead = (enemyEnt->fl.takedamage && enemyEnt->health <= 0); + if ( enemyDead || enemyEnt->fl.notarget || enemyEnt->IsHidden() || (enemyAct && enemyAct->team == team)) { + ClearEnemy ( enemyDead ); + } + } + } + + // Action time is stopped when the torso is not idle + if ( !aifl.action ) { + actionTime += gameLocal.msec; + } + + ValidateCover(); + + move.current_yaw += deltaViewAngles.yaw; + move.ideal_yaw = idMath::AngleNormalize180( move.ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + + if( move.moveType != MOVETYPE_PLAYBACK ){ + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + if ( !move.fl.allowHiddenMove && IsHidden() ) { + // hidden monsters + UpdateStates (); + } else if( !ai_freeze.GetBool() ) { + Prethink(); + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // update enemy position if not dead + if ( !aifl.dead ) { + UpdateEnemy ( ); + } + + // update state machine + UpdateStates(); + + // run all movement commands + Move(); + + // if not dead, chatter and blink + if( move.moveType != MOVETYPE_DEAD ){ + UpdateChatter(); + CheckBlink(); + } + + Postthink(); + } else { + DrawTactical ( ); + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + aifl.pain = false; + aifl.damage = false; + aifl.pushed = false; + pusher = NULL; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + if ( move.fl.allowPushMovables ) { + PushWithAF(); + } + + if ( fl.hidden && move.fl.allowHiddenMove ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + aasSensor->Update(); + + UpdateAnimation(); + Present(); + LinkCombat(); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Stop ( ); + } +} + +/* +============ +idAI::UpdateFocus +============ +*/ +void idAI::UpdateFocus ( const idMat3& orientationAxis ) { + // Alwasy look at enemy + if ( !allowJointMod || !allowEyeFocus ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( IsBehindCover() && !aifl.action && !IsSpeaking() ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( !aifl.scripted && (IsEnemyRecentlyVisible(2.0f) || gameLocal.GetTime()-lastAttackTime<1000) ) { + //not scripted and: have an enemy OR we shot at an enemy recently (for when we kill an enemy in the middle of a volley) + SetFocus ( AIFOCUS_ENEMY, 1000 ); + } else if ( move.fl.moving && InCoverMode ( ) && DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_COVER, 1000 ); + } else if ( InLookAtCoverMode() ) { + SetFocus ( AIFOCUS_COVERLOOK, 1000 ); + } else if ( talkTarget && gameLocal.time < passive.talkTime ) { + SetFocus ( AIFOCUS_TALK, 100 ); + } else if ( lookTarget ) { + SetFocus ( AIFOCUS_TARGET, 100 ); + } else if ( !IsBehindCover() && tether && DistanceTo ( move.moveDest ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_TETHER, 100 ); + } else if ( IsTethered() && (move.fl.moving || IsBehindCover()) ) { + //tethered and at cover or moving - don't look at leader or player + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( helperCurrent && helperCurrent->IsCombat ( ) ) { + SetFocus ( AIFOCUS_HELPER, 100 ); + } else if ( !aifl.scripted && !move.fl.moving ) { + idEntity* lookat = NULL; + aiFocus_t newfocus = AIFOCUS_NONE; + + if ( leader ) { + idVec3 dir2Leader = leader->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin(); + if ( (viewAxis[0] * dir2Leader) > 0.0f ) { + //leader is in front of me + newfocus = AIFOCUS_LEADER; + lookat = leader; + } else if ( focusType == AIFOCUS_LEADER ) { + //stop looking at him! + SetFocus ( AIFOCUS_NONE, 0 ); + } + } else if ( aifl.lookAtPlayer && !move.fl.moving ) { + newfocus = AIFOCUS_PLAYER; + lookat = gameLocal.GetLocalPlayer(); + } + + if ( newfocus != AIFOCUS_NONE && lookat ) { + if ( DistanceTo ( lookat ) < focusRange * (move.fl.moving?2.0f:1.0f) && CheckFOV ( lookat->GetPhysics()->GetOrigin() ) ) { + SetFocus ( newfocus, 1000 ); + } + } + } + + // Make sure cover look wasnt invalidated + if ( focusType == AIFOCUS_COVERLOOK && !aasSensor->Look() ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_COVER && !aasSensor->Reserved ( ) ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_HELPER && !helperCurrent ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_TETHER && !tether ) { + focusType = AIFOCUS_NONE; + } + + // Update focus type to none when the focus time runs out + if ( focusType != AIFOCUS_NONE ) { + if ( gameLocal.time > focusTime ) { + focusType = AIFOCUS_NONE; + } + } + + // Calculate the focus position + if ( focusType == AIFOCUS_NONE ) { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } else if ( focusType == AIFOCUS_COVER ) { + currentFocusPos = GetEyePosition() + aasSensor->Reserved()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_COVERLOOK ) { + currentFocusPos = GetEyePosition() + aasSensor->Look()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_ENEMY ) { + currentFocusPos = enemy.lastVisibleEyePosition; + } else if ( focusType == AIFOCUS_HELPER ) { + currentFocusPos = helperCurrent->GetPhysics()->GetOrigin() + helperCurrent->GetDirection ( this ); + } else if ( focusType == AIFOCUS_TETHER ) { + currentFocusPos = GetEyePosition() + tether->GetPhysics()->GetAxis()[0] * 64.0f; + } else { + idEntity* focusEnt = NULL; + switch ( focusType ) { + case AIFOCUS_LEADER: focusEnt = leader; break; + case AIFOCUS_PLAYER: focusEnt = gameLocal.GetLocalPlayer(); break; + case AIFOCUS_TALK: focusEnt = talkTarget; break; + case AIFOCUS_TARGET: focusEnt = lookTarget; break; + } + if ( focusEnt ) { + if ( focusEnt->IsType ( idActor::GetClassType ( ) ) ) { + currentFocusPos = static_cast(focusEnt)->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + currentFocusPos = focusEnt->GetPhysics()->GetOrigin(); + } + } else { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } + } + + //draw it so we can see what they think they're looking at! + if ( DebugFilter(ai_debugMove) ) { // Cyan & Blue = currentFocusPos + if ( focusType != AIFOCUS_NONE ) { + gameRenderWorld->DebugArrow( colorCyan, GetEyePosition(), currentFocusPos, 0 ); + } else { + gameRenderWorld->DebugArrow( colorBlue, GetEyePosition(), currentFocusPos, 0 ); + } + } +} + +/* +===================== +idAI::UpdateStates +===================== +*/ +void idAI::UpdateStates ( void ) { + MEM_SCOPED_TAG(tag,MA_DEFAULT); + + // Continue updating tactical state if for some reason we dont have one + if ( !aifl.dead && !aifl.scripted && !aifl.action && stateThread.IsIdle ( ) && aifl.scriptedEndWithIdle ) { + UpdateTactical ( 0 ); + } else { + UpdateState(); + } + + // clear the hit enemy flag so we catch the next time we hit someone + aifl.hitEnemy = false; + + if ( move.fl.allowHiddenMove || !IsHidden() ) { + // update the animstate if we're not hidden + UpdateAnimState(); + } +} + +/* +===================== +idAI::OnStateChange +===================== +*/ +void idAI::OnStateChange ( int channel ) { +} + +/* +===================== +idAI::OverrideFlag +===================== +*/ +void idAI::OverrideFlag ( aiFlagOverride_t flag, bool value ) { + bool oldValue; + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: oldValue = disablePain; break; + case AIFLAGOVERRIDE_DAMAGE: oldValue = fl.takedamage; break; + case AIFLAGOVERRIDE_NOTURN: oldValue = move.fl.noTurn; break; + case AIFLAGOVERRIDE_NOGRAVITY: oldValue = move.fl.noGravity; break; + default: + return; + } + + if ( oldValue == value ) { + return; + } + + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + + if ( (flagOverrides & flagOverride) && !!(flagOverrides & flagOverrideValue) == value ) { + flagOverrides &= ~flagOverride; + } else { + if ( oldValue ) { + flagOverrides |= flagOverrideValue; + } else { + flagOverrides &= ~flagOverrideValue; + } + flagOverrides |= flagOverride; + } + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/* +===================== +idAI::RestoreFlag +===================== +*/ +void idAI::RestoreFlag ( aiFlagOverride_t flag ) { + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + bool value; + + if ( !(flagOverrides&flagOverride) ) { + return; + } + + value = !!(flagOverrides & flagOverrideValue); + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +===================== +idAI::ReactionTo +===================== +*/ +int idAI::ReactionTo( const idEntity *ent ) { + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::GetClassType() ) ) { + return ATTACK_IGNORE; + } + + if( combat.fl.ignoreEnemies ){ + return ATTACK_IGNORE; + } + + const idActor *actor = static_cast( ent ); + if ( actor->IsType( idPlayer::GetClassType() ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + // actors on different teams will always fight each other + if ( actor->team != team ) { + if ( actor->fl.notarget ) { + // don't attack on sight when attacker is notargeted + return ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE ; */ + } + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE; */ + } + + //FIXME: temporarily disabled monsters of same rank fighting because it's happening too much. + // monsters will fight when attacked by lower or equal ranked monsters. rank 0 never fights back. + //if ( rank && ( actor->rank <= rank ) ) { + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +idAI::AdjustHealthByDamage +===================== +*/ +void idAI::AdjustHealthByDamage ( int damage ) { + if ( aifl.undying ) { + return; + } + idActor::AdjustHealthByDamage ( damage ); + + if ( g_perfTest_aiUndying.GetBool() && health <= 0 ) { + //so we still take pain! + health = 1; + } +} + +/* +===================== +idAI::Pain +===================== +*/ +bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + aifl.pain = idActor::Pain( inflictor, attacker, damage, dir, location ); + aifl.damage = true; + + // force a blink + blink_time = 0; + + // No special handling if friendly fire + // jshepard: friendly fire will cause pain. Players will only be able to pain buddy marines + // via splash damage. + +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return aifl.pain; + } + } +*/ + + // ignore damage from self + if ( attacker != this ) { + // React to taking pain + ReactToPain ( attacker, damage ); + + pain.takenThisFrame += damage; + pain.lastTakenTime = gameLocal.time; + combat.tacticalPainTaken += damage; + + // If taken too much pain where we then skip the current destination + if ( combat.tacticalPainThreshold && combat.tacticalPainTaken > combat.tacticalPainThreshold ) { + if (team==AITEAM_STROGG && + (combat.tacticalMaskAvailable&AITACTICAL_COVER_BITS) && + IsType(rvAITactical::GetClassType()) && + ai_allowTacticalRush.GetBool() && + spawnArgs.GetBool("rushOnPain", "1")) { + + // clear any tether + tether = NULL; + + // change ranged distances + combat.attackRange[0] = 0.0f; + combat.attackRange[1] = 100.0f; // make them get really close + + // remove cover behavior + combat.tacticalMaskAvailable &= ~AITACTICAL_COVER_BITS; + + // add ranged behavior + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BITS; + } + ForceTacticalUpdate ( ); + return true; + } + + // If looping pain and a new paintype comes in we can stop the loop + if ( pain.loopEndTime && pain.loopType.Icmp ( painType ) ) { + pain.loopEndTime = 0; + pain.loopType = painType; + } + + ExecScriptFunction( funcs.damage ); + } + + // If we were hit by our enemy and our enemy isnt visible then + // force the enemy visiblity to be updated as if we saw him momentarily + if ( enemy.ent == attacker && !enemy.fl.visible ) { + UpdateEnemyPosition ( true ); + } + + return aifl.pain; +} + +/* +===================== +idAI::Killed +===================== +*/ +void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idAngles ang; + const char* modelDeath; + const idKeyValue* kv; + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( aifl.dead ) { + aifl.pain = true; + aifl.damage = true; + return; + } + + aifl.dead = true; + + // turn off my flashlight, if I had one + ProcessEvent( &AI_Flashlight, false ); + + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + // Hide surfaces on death + for ( kv = spawnArgs.MatchPrefix ( "deathhidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "deathhidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } + + // stop all voice sounds + StopSpeaking( true ); + + SetMoveType ( MOVETYPE_DEAD ); + + move.fl.noGravity = false; + move.fl.allowPushMovables = false; + aifl.scripted = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + + aiManager.AnnounceKill ( this, attacker, inflictor ); + aiManager.AnnounceDeath ( this, attacker ); + } + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( this ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + OnDeath(); + CheckDeathObjectives(); + + ClearEnemy(); + + // make monster nonsolid + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + + Unbind(); + if ( g_perfTest_aiNoRagdoll.GetBool() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else if( fl.quickBurn ){ + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else { + if ( StartRagdoll() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) { + // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + SetModel( modelDeath ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + } + } + + SetState ( "State_Killed" ); + + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while( kv ) { + idDict args; + idEntity *tEnt; + if( kv->GetValue() != "" ){ + args.Set( "classname", kv->GetValue() ); + args.Set( "origin", physicsObj.GetAbsBounds().GetCenter().ToString() ); + // Let items know that they are of the dropped variety + args.Set( "dropped", "1" ); + if (gameLocal.SpawnEntityDef( args, &tEnt )) { + if ( tEnt && tEnt->GetPhysics()) { //tEnt *should* be valid, but hey... + // magic/arbitrary number to give it some spin. Some constants used to ensure guns rarely fall standing up + tEnt->GetPhysics()->SetAngularVelocity( idVec3( (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f)); + } + } + } + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/*********************************************************************** + + Targeting/Combat + +***********************************************************************/ + +/* +===================== +idAI::Activate + +Notifies the script that a monster has been activated by a trigger or flashlight +===================== +*/ +void idAI::Activate( idEntity *activator ) { + idPlayer *player; + + // Set our tether? + if ( activator && activator->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(activator) ); + } + + if ( aifl.dead ) { + // ignore it when they're dead + return; + } + + // make sure he's not dormant + dormantStart = 0; + + aifl.activated = true; + if ( !activator || !activator->IsType( idPlayer::GetClassType() ) ) { + player = gameLocal.GetLocalPlayer(); + } else { + player = static_cast( activator ); + } + + if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) { + SetEnemy( player ); + } + + // If being activated by a spawner we need to attach to it + if ( activator && activator->IsType ( rvSpawner::GetClassType() ) ) { + rvSpawner* spawn = static_cast( activator ); + spawn->Attach ( this ); + SetSpawner ( spawn ); + } +} + +/* +===================== +idAI::TalkTo +===================== +*/ +void idAI::TalkTo( idActor *actor ) { + + // jshepard: the dead do not speak. + if ( aifl.dead ) + return; + + ExecScriptFunction( funcs.onclick ); + + // Cant talk when already talking + if ( aifl.action || IsSpeaking ( ) || !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_TALK ) ) { + return; + } + + + switch ( GetTalkState ( ) ) { + case TALK_OK: + if ( talkMessage >= TALKMSG_LOOP ) { + //MCG: requested change: stop responding after one loop + return; + } + // at least one second between talking to people + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_TALK, 2000 ); + + talkTarget = actor; + + // Move to the next talk message + talkMessage = (talkMessage_t)((int)talkMessage + 1); + + // Loop until we find a valid talk message + while ( 1 ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Try the lipsync for the specific passive prefix first + if ( Speak ( va("lipsync_%stalk_%s", passive.prefix.c_str(), postfix.c_str() ) ) ) { + break; + } + // Try the generic lipsync for the current talk message + if ( Speak ( va("lipsync_talk_%s", postfix.c_str() ) ) ) { + break; + } + // If the first loop failed then there is no other options + if ( talkMessage == TALKMSG_LOOP ) { + return; + } else if ( talkMessage >= TALKMSG_LOOP ) { + talkMessage = TALKMSG_LOOP; + } else { + talkMessage = (talkMessage_t)((int)talkMessage + 1); + } + } + + // Start talking anim if we have one + if ( IsSpeaking ( ) ) { + passive.talkTime = speakTime; + } + + break; + + case TALK_FOLLOW: + if ( actor == leader ) { + leader = NULL; + Speak ( "lipsync_stopfollow", true ); + } else { + leader = actor; + Speak ( "lipsync_follow", true ); + } + break; + + case TALK_BUSY: + if ( talkBusyCount > 3 ) + {//only say a max of 4 lines + return; + } + talkBusyCount++; + //try to say this variant - if we can't, stop forever + if ( !Speak ( va("lipsync_busy_%d",talkBusyCount) ) ) + {//nevermore + talkBusyCount = 999; + } + break; + + case TALK_WAIT: + //do nothing + break; + } +} + +/* +===================== +idAI::GetTalkState +===================== +*/ +talkState_t idAI::GetTalkState( void ) const { + if ( ( talkState != TALK_NEVER ) && aifl.dead ) { + return TALK_DEAD; + } + if ( IsHidden() ) { + return TALK_NEVER; + } + if ( (talkState == TALK_OK || talkState == TALK_FOLLOW) && aifl.scripted ) { + return TALK_BUSY; + } + return talkState; +} + +/* +===================== +idAI::TouchedByFlashlight +===================== +*/ +void idAI::TouchedByFlashlight( idActor *flashlight_owner ) { + if ( RespondToFlashlight() ) { + Activate( flashlight_owner ); + } +} + +/* +===================== +idAI::ClearEnemy +===================== +*/ +void idAI::ClearEnemy( bool dead ) { + // Dont bother if we dont have an enemy + if ( !enemy.ent ) { + return; + } + + if ( move.moveCommand == MOVE_TO_ENEMY ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + } else if ( !aifl.scripted + && move.moveCommand == MOVE_TO_COVER + && !move.fl.done + && aasSensor->Reserved() + && !IsBehindCover() ) { + //clear cover and stop move + aasSensor->Reserve( NULL ); + combat.coverValidTime = 0; + OnCoverInvalidated(); + ForceTacticalUpdate(); + StopMove( MOVE_STATUS_DONE ); + } + + enemyNode.Remove(); + enemy.ent = NULL; + enemy.fl.dead = dead; + combat.fl.seenEnemyDirectly = false; + + UpdateHelper ( ); +} + +/* +===================== +idAI::UpdateEnemyPosition +===================== +*/ +void idAI::UpdateEnemyPosition ( bool forceUpdate ) { + if( !enemy.ent || (!enemy.fl.visible && !forceUpdate) ) { + return; + } + + idActor* enemyActor = dynamic_cast(enemy.ent.GetEntity()); + idEntity* enemyEnt = static_cast(enemy.ent.GetEntity()); + + enemy.lastVisibleFromEyePosition = GetEyePosition ( ); + + // Update the enemy origin if it isnt locked + if ( !enemy.fl.lockOrigin ) { + enemy.lastKnownPosition = enemyEnt->GetPhysics()->GetOrigin(); + + if ( enemyActor ) { + enemy.lastVisibleEyePosition = enemyActor->GetEyePosition(); + enemy.lastVisibleChestPosition = enemyActor->GetChestPosition(); + } else { + enemy.lastVisibleEyePosition = enemy.ent->GetPhysics()->GetOrigin ( ); + enemy.lastVisibleChestPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + } + } +} + +/* +===================== +idAI::UpdateEnemy +===================== +*/ +void idAI::UpdateEnemy ( void ) { + predictedPath_t predictedPath; + + // If we lost our enemy then clear it out to be sure + if( !enemy.ent ) { + return; + } + + // Rest to not being in fov + enemy.fl.inFov = false; + + // Bail out if we arent queued to do a complex think + if ( aifl.simpleThink ) { + // Keep the fov flag up to date + if ( IsEnemyVisible ( ) ) { + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + } + } else { + bool oldVisible; + + // Cache current enemy visiblity so we can see if it changes + oldVisible = IsEnemyVisible ( ); + + // See if enemy still visible + UpdateEnemyVisibility ( ); + + // If our enemy isnt visible but is within aware range then we know where he is + if ( !IsEnemyVisible ( ) && DistanceTo ( enemy.ent ) < combat.awareRange ) { + UpdateEnemyPosition( true ); + } else { +/* + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertActor() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin() ).LengthSqr(); + if ( dist < Square( combat.earRange ) ) { + SetEnemyPosition(); + } + } +*/ + } + + // Update fov + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + + // Set enemy.visibleTime to the time when the visibility flag changed + if ( oldVisible != IsEnemyVisible ( ) ) { + // Just caught sight of the enemy after loosing him, delay the attack actions a bit + if ( !oldVisible && combat.attackSightDelay ) { + float delay; + if ( enemy.lastVisibleChangeTime ) { + // The longer the enemy has not been visible, the more we should delay + delay = idMath::ClampFloat ( 0.0f, 1.0f, (gameLocal.time - enemy.lastVisibleChangeTime) / AI_SIGHTDELAYSCALE ); + } else { + // The enemy has never been visible so delay the full amount + delay = 1.0f; + } + // Add to the ranged attack timer providing its not already running a timer + if ( actionTimerRangedAttack.IsDone ( actionTime) ) { + actionTimerRangedAttack.Clear ( actionTime ); + actionTimerRangedAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + // Add to the special attack timer providing its not already running a timer + if ( actionTimerSpecialAttack.IsDone ( actionTime ) ) { + actionTimerSpecialAttack.Clear ( actionTime ); + actionTimerSpecialAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + } + + enemy.lastVisibleChangeTime = gameLocal.time; + } + + // Handler for visibility change + if ( oldVisible != IsEnemyVisible ( ) ) { + OnEnemyVisiblityChange ( oldVisible ); + } + } + + // Adjust smoothed linear and pushed velocities for enemies + if ( enemy.fl.visible ) { + enemy.smoothedLinearVelocity += ((enemy.ent->GetPhysics()->GetLinearVelocity ( ) - enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedLinearVelocity) * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity += ((enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedPushedVelocity) * enemy.smoothVelocityRate ); + } else { + enemy.smoothedLinearVelocity -= (enemy.smoothedLinearVelocity * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity -= (enemy.smoothedPushedVelocity * enemy.smoothVelocityRate ); + } + + // Update enemy range + enemy.range = DistanceTo ( enemy.lastKnownPosition ); + enemy.range2d = DistanceTo2d ( enemy.lastKnownPosition ); + + // Calulcate the aggression scale + // Skill level 0: (1.0) + // Skill level 1: (1.0 - 1.25) + // Skill level 2: (1.0 - 1.5) + // Skill level 3: (1.0 - 1.75) + if ( combat.aggressiveRange > 0.0f ) { + combat.aggressiveScale = (g_skill.GetFloat() / MAX_SKILL_LEVELS); + combat.aggressiveScale *= 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, enemy.range / combat.aggressiveRange ); + combat.aggressiveScale += 1.0f; + } +} + +/* +===================== +idAI::LastKnownPosition +===================== +*/ +const idVec3& idAI::LastKnownPosition ( const idEntity *ent ) { + return (ent==enemy.ent)?(enemy.lastKnownPosition):(ent->GetPhysics()->GetOrigin()); +} + +/* +===================== +idAI::UpdateEnemyVisibility + +Update whether or not the AI can see its enemy or not and determine how. +===================== +*/ +void idAI::UpdateEnemyVisibility ( void ) { + enemy.fl.visible = false; + + // No enemy so nothing to see + if ( !enemy.ent ) { + return; + } + + // Update enemy visibility flag + enemy.fl.visible = CanSeeFrom ( GetEyePosition ( ), enemy.ent, false ); + + // IF the enemy isnt visible and not forcing an update we can just early out + if ( !enemy.fl.visible ) { + return; + } + + // If we are seeing our enemy for the first time after changing enemies, call him out + if ( enemy.lastVisibleTime < enemy.changeTime ) { + AnnounceNewEnemy( ); + } + + combat.fl.seenEnemyDirectly = true; + enemy.lastVisibleTime = gameLocal.time; + + // Update our known locations of the enemy since we just saw him + UpdateEnemyPosition ( ); +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +void idAI::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +rvSpawner* idAI::GetSpawner ( void ) { + return spawner; +} + +/* +===================== +idAI::SetEnemy +===================== +*/ +bool idAI::SetEnemy( idEntity *newEnemy ) { + idEntity* oldEnemy; + + // Look for obvious early out + if ( enemy.ent == newEnemy ) { + return true; + } + + // Cant set enemy if dead + if ( aifl.dead ) { + ClearEnemy ( false ); + return false; + } + + // Cant set enemy if the enemy is dead or has no target set + if ( newEnemy ) { + if ( newEnemy->health <= 0 || newEnemy->fl.notarget ) { + return false; + } + } + + oldEnemy = enemy.ent; + enemy.fl.dead = false; + enemy.fl.lockOrigin = false; + + if ( !newEnemy ) { + ClearEnemy ( false ); + } else { + // Set our current enemy + enemy.ent = newEnemy; + + // If the enemy is an actor then add to the actors enemy list + if( newEnemy->IsType( idActor::GetClassType() ) ){ + idActor* enemyActor = static_cast( newEnemy ); + enemyNode.AddToEnd( enemyActor->enemyList ); + } + } + + // Allow custom handling of enemy change + if ( enemy.ent != oldEnemy ) { + OnEnemyChange ( oldEnemy ); + } + + return true; +} + + +/* +=================== +idAI::CalculateAttackOffsets + +calculate joint positions on attack frames so we can do proper "can hit" tests +=================== +*/ +void idAI::CalculateAttackOffsets ( void ) { + const idDeclModelDef* modelDef; + int num; + int i; + int frame; + const frameCommand_t* command; + idMat3 axis; + const idAnim* anim; + jointHandle_t joint; + + modelDef = animator.ModelDef(); + if ( !modelDef ) { + return; + } + num = modelDef->NumAnims(); + + // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim + animator.RemoveOriginOffset( false ); + + // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for + // launch offsets so that anim number can be used without subtracting 1. + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num + 1 ); + attackAnimInfo[ 0 ].attackOffset.Zero(); + attackAnimInfo[ 0 ].eyeOffset.Zero(); + + for( i = 1; i <= num; i++ ) { + attackAnimInfo[ i ].attackOffset.Zero(); + attackAnimInfo[ i ].eyeOffset.Zero(); + anim = modelDef->GetAnim( i ); + if ( !anim ) { + continue; + } + + frame = anim->FindFrameForFrameCommand( FC_AI_ATTACK, &command ); + if ( frame >= 0 ) { + joint = animator.GetJointHandle( command->joint->c_str() ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Invalid joint '%s' on 'ai_attack' frame command on frame %d of model '%s'", command->joint->c_str(), frame, modelDef->GetName() ); + } + GetJointTransformForAnim( joint, i, FRAME2MS( frame ), attackAnimInfo[ i ].attackOffset, axis ); + + if ( chestOffsetJoint!= INVALID_JOINT ) { + GetJointTransformForAnim( chestOffsetJoint, i, FRAME2MS( frame ), attackAnimInfo[ i ].eyeOffset, axis ); + } + } + } + + animator.RemoveOriginOffset( true ); +} + +/* +===================== +idAI::CreateProjectileClipModel +===================== +*/ +void idAI::CreateProjectileClipModel( void ) const { + if ( projectileClipModel == NULL ) { + idBounds projectileBounds( vec3_origin ); + projectileBounds.ExpandSelf( 2.0f ); // projectileRadius ); + projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); + } +} + +/* +===================== +idAI::GetPredictedAimDirOffset +===================== +*/ +void idAI::GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const { + float a; + float b; + float c; + float d; + float t; + idVec3 r; + + // Make sure there is something to predict + if ( targetVelocity.LengthSqr ( ) == 0.0f ) { + offset.Zero ( ); + return; + } + + // Solve for (t): magnitude(targetVelocity * t + target - source) = projectileSpeed * t + + r = (target-source); + a = (targetVelocity * targetVelocity) - Square(projectileSpeed); + b = (targetVelocity * 2.0f) * r; + c = r*r; + d = b*b - 4*a*c; + t = 0.0f; + + if ( d >= 0.0f ) { + float t1; + float t2; + float denom; + d = idMath::Sqrt(d); + denom = 1.0f / (2.0f * a); + t1 = (-b + d) * denom; + t2 = (-b - d) * denom; + if ( t1 < 0.0f && t2 < 0.0f ) { + t = 0.0f; + } else if ( t1 < 0.0f ) { + t = t2; + } else if ( t2 < 0.0f ) { + t = t1; + } else { + t = Min(t1,t2); + } + } + + offset = targetVelocity * t; +} + +/* +===================== +idAI::GetAimDir +===================== +*/ +bool idAI::GetAimDir( + const idVec3& firePos, + const idEntity* target, + const idDict* projectileDef, + idEntity* ignore, + idVec3& aimDir, + float aimOffset, + float predict + ) const +{ + idVec3 targetPos1; + idVec3 targetPos2; + idVec3 targetLinearVel; + idVec3 targetPushedVel; + idVec3 delta; + float max_height; + bool result; + idEntity* targetEnt = const_cast(target); + + // if no aimAtEnt or projectile set + if ( !targetEnt ) { + aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + return false; + } + + float projectileSpeed = idProjectile::GetVelocity ( projectileDef ).LengthFast ( ); + idVec3 projectileGravity = idProjectile::GetGravity ( projectileDef ); + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + if ( targetEnt == enemy.ent ) { + targetPos2 = enemy.lastVisibleEyePosition; + targetPos1 = enemy.lastVisibleChestPosition; + targetPushedVel = enemy.smoothedPushedVelocity; + targetLinearVel = enemy.smoothedLinearVelocity; + } else if ( targetEnt->IsType( idActor::GetClassType() ) ) { + targetPos2 = static_cast(targetEnt)->GetEyePosition ( ); + targetPos1 = static_cast(targetEnt)->GetChestPosition ( ); + targetPushedVel = target->GetPhysics()->GetPushedLinearVelocity ( ); + targetLinearVel = (target->GetPhysics()->GetLinearVelocity ( ) - targetPushedVel); + } else { + targetPos1 = targetEnt->GetPhysics()->GetAbsBounds().GetCenter(); + targetPos2 = targetPos1; + targetPushedVel.Zero ( ); + targetLinearVel.Zero ( ); + } + + // Target prediction + if ( projectileSpeed == 0.0f ) { + // Hitscan prediction actually causes the hitscan to miss unless it is predicted. + delta = (targetLinearVel * (-1.0f + predict)); + } else { + // Projectile prediction must figure out how far to lead the shot to hit the target + GetPredictedAimDirOffset ( firePos, targetPos1, projectileSpeed, (targetLinearVel*predict)+targetPushedVel, delta ); + } + targetPos1 += delta; + targetPos2 += delta; + + result = false; + + // try aiming for chest + delta = targetPos1 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos1 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + if ( result || !targetEnt->IsType( idActor::GetClassType() ) ) { + return result; + } + } + + // try aiming for head + delta = targetPos2 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos2 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + } + + return result; +} + +/* +===================== +idAI::CreateProjectile +===================== +*/ +idProjectile *idAI::CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ) { + idEntity* ent; + const char* clsname; + + if ( !projectile.GetEntity() ) { + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + clsname = projectileDict->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + projectile = (idProjectile*)ent; + } + + projectile.GetEntity()->Create( this, pos, dir ); + + return projectile.GetEntity(); +} + +/* +===================== +idAI::RemoveProjectile +===================== +*/ +void idAI::RemoveProjectile( void ) { + if ( projectile.GetEntity() ) { + projectile.GetEntity()->PostEventMS( &EV_Remove, 0 ); + projectile = NULL; + } +} + +/* +===================== +idAI::Attack +===================== +*/ +bool idAI::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + // Get the attack dictionary + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", attackName ) ), false ); + if ( !attackDict ) { + gameLocal.Error ( "could not find attack entityDef 'def_attack_%s (%s)' on AI entity %s", attackName, spawnArgs.GetString ( va("def_attack_%s", attackName ) ), GetName ( ) ); + } + + // Melee Attack? + if ( spawnArgs.GetBool ( va("attack_%s_melee", attackName ), "0" ) ) { + return AttackMelee ( attackName, attackDict ); + } + + // Ranged attack (hitscan or projectile)? + return ( AttackRanged ( attackName, attackDict, joint, target, pushVelocity ) != NULL ); +} + +/* +===================== +idAI::AttackRanged +===================== +*/ +idProjectile* idAI::AttackRanged ( + const char* attackName, + const idDict* attackDict, + jointHandle_t joint, + idEntity* target, + const idVec3& pushVelocity + ) +{ + float attack_accuracy; + float attack_cone; + float attack_spread; + int attack_count; + bool attack_hitscan; + float attack_predict; + float attack_pullback; + int i; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + idVec3 dir; + idAngles ang; + idMat3 axis; + idProjectile* lastProjectile; + + lastProjectile = NULL; + + // Generic attack properties + attack_accuracy = spawnArgs.GetFloat ( va("attack_%s_accuracy", attackName ), "7" ); + attack_cone = spawnArgs.GetFloat ( va("attack_%s_cone", attackName ), "75" ); + attack_spread = DEG2RAD ( spawnArgs.GetFloat ( va("attack_%s_spread", attackName ), "0" ) ); + attack_count = spawnArgs.GetInt ( va("attack_%s_count", attackName ), "1" ); + attack_hitscan = spawnArgs.GetBool ( va("attack_%s_hitscan", attackName ), "0" ); + attack_predict = spawnArgs.GetFloat ( va("attack_%s_predict", attackName ), "0" ); + attack_pullback = spawnArgs.GetFloat ( va("attack_%s_pullback", attackName ), "0" ); + + // Get the muzzle origin and axis from the given launch joint + GetMuzzle( joint, muzzleOrigin, muzzleAxis ); + if ( attack_pullback ) + { + muzzleOrigin -= muzzleAxis[0]*attack_pullback; + } + + // set aiming direction + bool calcAim = true; + if ( GetEnemy() && GetEnemy() == target && GetEnemy() == gameLocal.GetLocalPlayer() && spawnArgs.GetBool( va("attack_%s_missFirstShot",attackName) ) ) { + //purposely miss + if ( gameLocal.random.RandomFloat() < 0.5f ) { + //actually, hit anyway... + } else { + idVec3 targetPos = enemy.lastVisibleEyePosition; + idVec3 eFacing; + idVec3 left, up; + + up.Set( 0, 0, 1 ); + left = viewAxis[0].Cross(up); + + if ( GetEnemy()->IsType( idActor::GetClassType() ) ) + { + eFacing = ((idActor*)GetEnemy())->viewAxis[0]; + } + else + { + eFacing = GetEnemy()->GetPhysics()->GetAxis()[0]; + } + if ( left*eFacing > 0 ) + { + targetPos += left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + else + { + targetPos -= left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + dir = targetPos-muzzleOrigin; + dir.Normalize(); + attack_accuracy = 0; + + calcAim = false; + //don't miss next time + spawnArgs.SetBool( va("attack_%s_missFirstShot",attackName), false ); + } + } + if ( calcAim ) { + if ( target && !spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) { + GetAimDir( muzzleOrigin, target, attackDict, this, dir, + spawnArgs.GetFloat ( va("attack_%s_aimoffset", attackName )), + attack_predict ); + } else { + dir = muzzleAxis[0]; + if ( spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) + { + if ( spawnArgs.GetBool( va("attack_%s_traceToNextJoint", attackName ), "0" ) ) + { + jointHandle_t endJoint = animator.GetFirstChild( joint ); + if ( endJoint != INVALID_JOINT && endJoint != joint ) + { + idVec3 endJointPos; + idMat3 blah; + GetJointWorldTransform( endJoint, gameLocal.GetTime(), endJointPos, blah ); + dir = endJointPos-muzzleOrigin; + //ARGHH: need to be able to set range!!! + //const_cast(attackDict)->SetFloat( "range", dir.Normalize() );//NOTE: yes, I intentionally normalize here as well as use the result for length... + dir.Normalize(); + } + } + } + } + } + + // random accuracy + ang = dir.ToAngles(); + ang.pitch += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + ang.yaw += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + + // Lock attacks to a cone? + if ( attack_cone ) { + float diff; + // clamp the attack direction to be within monster's attack cone so he doesn't do + // things like throw the missile backwards if you're behind him + diff = idMath::AngleDelta( ang.yaw, move.current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = move.current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = move.current_yaw - attack_cone; + } + } + + ang.Normalize360 ( ); + axis = ang.ToMat3(); + + for( i = 0; i < attack_count; i++ ) { + float angle; + float spin; + + // spread the projectiles out + angle = idMath::Sin( attack_spread * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( attack_hitscan ) { + gameLocal.HitScan( *attackDict, muzzleOrigin, dir, muzzleOrigin, this, false, combat.aggressiveScale ); + } else { + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( attackDict, muzzleOrigin, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzleOrigin, dir, pushVelocity, 0.0f, combat.aggressiveScale ); + + // Let the script manage projectiles if need be + ExecScriptFunction ( funcs.launch_projectile, lastProjectile ); + + projectile = NULL; + } + } + + lastAttackTime = gameLocal.time; + + // If shooting at another ai entity then kick off an attack reaction + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) ) { + static_cast(enemy.ent.GetEntity())->ReactToShotAt ( this, muzzleOrigin, axis[0] ); + } + + return lastProjectile; +} + +/*s +================ +idAI::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( ( victim == this ) && inflictor->IsType( idProjectile::GetClassType() ) ) { + // monsters only get half damage from their own projectiles + damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage + } else if ( victim == enemy.ent ) { + aifl.hitEnemy = true; + } +} + +/* +===================== +idAI::DirectDamage + +Causes direct damage to an entity + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + +// ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ); + ent->Damage( this, this, globalKickDir, meleeDefName, damageScale, NULL ); +} + +/* +===================== +idAI::TestMelee +===================== +*/ +bool idAI::TestMelee( void ) const { + trace_t trace; + idEntity* enemyEnt = enemy.ent; + + if ( !enemyEnt || !combat.meleeRange ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( DebugFilter(ai_debugMove) ) { //YELLOW = Test Melee Bounds + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +/* +===================== +idAI::AttackMelee + +jointname allows the endpoint to be exactly specified in the model, +as for the commando tentacle. If not specified, it will be set to +the facing direction + combat.meleeRange. + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +bool idAI::AttackMelee ( const char *attackName, const idDict* meleeDict ) { + idEntity* enemyEnt = enemy.ent; + const char* p; + const idSoundShader* shader; + + if ( !enemyEnt ) { + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // check for the "saving throw" automatic melee miss on lethal blow + // stupid place for this. + bool forceMiss = false; + if ( enemyEnt->IsType( idPlayer::GetClassType() ) && g_skill.GetInteger() < 2 ) { + int damage, armor; + idPlayer *player = static_cast( enemyEnt ); + player->CalcDamagePoints( this, this, meleeDict, 1.0f, INVALID_JOINT, &damage, &armor ); + + if ( enemyEnt->health <= damage ) { + int t = gameLocal.time - player->lastSavingThrowTime; + if ( t > SAVING_THROW_TIME ) { + player->lastSavingThrowTime = gameLocal.time; + t = 0; + } + if ( t < 1000 ) { + forceMiss = true; + } + } + } + + // make sure the trace can actually hit the enemy + if ( forceMiss || !TestMelee( ) ) { + // missed + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // do the damage + // + p = meleeDict->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDict->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + // This allows some AI to be soft against melee-- most marines are selfMeleeDamageScale of 3, which means they take 3X from melee attacks! + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ) * enemyEnt->spawnArgs.GetFloat ( "selfMeleeDamageScale", "1" ); + + //if attacker is a melee superhero, damageScale is way increased + idAI* enemyAI = static_cast(enemyEnt); + if( aifl.meleeSuperhero) { + if( damageScale >=1 ) { + damageScale *= 6; + } else { + damageScale = 6; + } + } + + //if the defender is a melee superhero, damageScale is way decreased + if( enemyAI->aifl.meleeSuperhero) { + damageScale = 0.5f; + } + + int location = INVALID_JOINT; + if ( enemyEnt->IsType ( idAI::Type ) ) { + location = static_cast(enemyEnt)->chestOffsetJoint; + } + enemyEnt->Damage( this, this, globalKickDir, meleeDict->GetString ( "classname" ), damageScale, location ); + + if ( meleeDict->GetString( "fx_impact", NULL ) ) { + if ( enemyEnt == gameLocal.GetLocalPlayer() ) { + idPlayer *ePlayer = static_cast(enemyEnt); + if ( ePlayer ) { + idVec3 dir = ePlayer->firstPersonViewOrigin-GetEyePosition(); + dir.Normalize(); + idVec3 org = ePlayer->firstPersonViewOrigin + (dir * -((gameLocal.random.RandomFloat()*8.0f)+8.0f) ); + idAngles ang = ePlayer->firstPersonViewAxis.ToAngles() * -1; + idMat3 axis = ang.ToMat3(); + gameLocal.PlayEffect( *meleeDict, "fx_impact", org, viewAxis ); + } + } + } + + lastAttackTime = gameLocal.time; + + return true; +} + +/* +================ +idAI::PushWithAF +================ +*/ +void idAI::PushWithAF( void ) { + int i, j; + afTouch_t touchList[ MAX_GENTITIES ]; + idEntity *pushed_ents[ MAX_GENTITIES ]; + idEntity *ent; + idVec3 vel; + int num_pushed; + + num_pushed = 0; + af.ChangePose( this, gameLocal.time ); + int num = af.EntitiesTouchingAF( touchList ); + for( i = 0; i < num; i++ ) { + if ( touchList[ i ].touchedEnt->IsType( idProjectile::GetClassType() ) ) { + // skip projectiles + continue; + } + + // make sure we havent pushed this entity already. this avoids causing double damage + for( j = 0; j < num_pushed; j++ ) { + if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) { + break; + } + } + if ( j >= num_pushed ) { + ent = touchList[ i ].touchedEnt; + pushed_ents[num_pushed++] = ent; + vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); + vel.Normalize(); + ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); + } + } +} + +/*********************************************************************** + + Misc + +***********************************************************************/ + +/* +================ +idAI::GetMuzzle +================ +*/ +void idAI::GetMuzzle( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ) { + if ( joint == INVALID_JOINT ) { + muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; + muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; + } else { + GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); + //MCG + //pull the muzzle back inside the bounds if possible + //FIXME: this is nasty, we should just be able to check for starting in solid and register that as a hit! But... + /* + float scale = 0.0f; + if ( physicsObj.GetBounds().RayIntersection( muzzle, -axis[0], scale ) ) + { + if ( scale != 0.0f ) + {//not already inside + muzzle += scale * -axis[0]; + } + } + else + {//just pull it back a little anyway? + idVec3 xyOfs = muzzle-physicsObj.GetOrigin(); + xyOfs.z = 0; + muzzle += xyOfs.Length() * -axis[0]; + } + */ + } +} + +/* +================ +idAI::Hide +================ +*/ +void idAI::Hide( void ) { + idActor::Hide(); + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + StopSound( SND_CHANNEL_AMBIENT, false ); + + enemy.fl.inFov = false; + enemy.fl.visible = false; + + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::Show +================ +*/ +void idAI::Show( void ) { + idActor::Show(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + + physicsObj.GetClipModel()->Link(); + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); +} + +/* +================ +idAI::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idAI::CanPlayChatterSounds( void ) const { + if ( aifl.dead ) { + return false; + } + + if ( IsHidden() ) { + return false; + } + + if ( enemy.ent ) { + return true; + } + + if ( spawnArgs.GetBool( "no_idle_chatter" ) ) { + return false; + } + + return true; +} + +/* +===================== +idAI::UpdateChatter +===================== +*/ +void idAI::UpdateChatter ( void ) { + int chatterRate; + const char* chatter; + + // No chatter? + if ( !chatterRateIdle && !chatterRateCombat ) { + return; + } + + // check if it's time to play a chat sound + if ( IsHidden() || !aifl.awake || aifl.dead || ( chatterTime > gameLocal.time ) ) { + return; + } + + // Skip first chatter + if ( enemy.ent ) { + chatter = "lipsync_chatter_combat"; + chatterRate = chatterRateCombat; + } else { + chatter = "lipsync_chatter_idle"; + chatterRate = chatterRateIdle; + } + + // Can chatter ? + if ( !chatterRate ) { + return; + } + + // Start chattering, but not if he's already speaking. And he might already be speaking because he was scripted to do so. + if ( chatterTime > 0 && !IsSpeaking() ) { + Speak ( chatter, true ); + } + + // set the next chat time + chatterTime = gameLocal.time + chatterRate + (gameLocal.random.RandomFloat() * chatterRate * 0.5f) - (chatterRate * 0.25f); +} + +/* +===================== +idAI::HeardSound +===================== +*/ +idEntity *idAI::HeardSound( int ignore_team ){ + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertActor(); + if ( actor && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { + idVec3 pos = actor->GetPhysics()->GetOrigin(); + idVec3 org = physicsObj.GetOrigin(); + float dist = ( pos - org ).LengthSqr(); + + if ( dist < Square( combat.earRange ) ) { + //really close? + if ( dist < Square( combat.earRange/4.0f ) ) { + return actor; + //possible LOS + } else if ( dist < Square( combat.visRange * 2.0f ) && CanSee( actor, false ) ) { + return actor; + } else if ( combat.fl.aware ) { + //FIXME: or, maybe find cover/hide/ambush spot and wait to ambush them? + //don't have an enemy and not tethered to a position + } else if ( !GetEnemy() && !tether ) { + //go into search mode + WanderAround(); + move.fl.noRun = false; + move.fl.idealRunning = true; + //undid this: was causing them to not be able to do fine nav. + //move.fl.noWalk = true; + } + } + } + + return NULL; +} + +/* +============ +idAI::SetLeader +============ +*/ +void idAI::SetLeader ( idEntity *newLeader ) { + idEntity* oldLeader = leader; + + if( !newLeader ){ + leader = NULL; + } else if ( !newLeader->IsType( idActor::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", newLeader->name.c_str() ); + } else { + leader = static_cast( newLeader ); + } + + if ( oldLeader != leader ) { + OnLeaderChange ( oldLeader ); + } +} + + + +/*********************************************************************** + + Head & torso aiming + +***********************************************************************/ + +/* +================ +idAI::UpdateAnimationControllers +================ +*/ +bool idAI::UpdateAnimationControllers( void ) { + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + float currentHeadFocusRate = ( combat.fl.aware ? headFocusRate : headFocusRate * 0.5f ); + + MEM_SCOPED_TAG(tag,MA_ANIM); + + if ( aifl.dead ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = move.current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. + // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which + // are already up to date because of getting the joints in this function) and then sets their positions, which + // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, + // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no + // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number + // in order to see how many times an entity transforms the joints per frame. + idActor::UpdateAnimationControllers(); + + // Update the focus position + UpdateFocus( orientationJointAxis ); + + //MCG NOTE: don't know why Dube added this extra check for the torsoAnim (5/09/05), but it was causing popping, so I took it out... :/ + //bool canLook = ( !torsoAnim.AnimDone(0) || !torsoAnim.GetAnimator()->CurrentAnim(ANIMCHANNEL_TORSO)->IsDone(gameLocal.GetTime()) || torsoAnim.Disabled() ) && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook; + //bool canLook = (!torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook); + + bool canLook = (!animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look && !aifl.disableLook); + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + currentFocusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 64.0f; + } + + // Determine the new look yaw + dir = currentFocusPos - orientationJointPos; + newLookAng.yaw = dir.ToYaw( ); + + // Determine the new look pitch + dir = currentFocusPos - GetEyePosition(); + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + newLookAng.roll = 0.0f; + + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + newLookAng.yaw = orientationJointAxis[0].ToYaw(); + newLookAng.pitch = 0; + } + // Add the angle change using the head focus rate and convert the look angles to + // local angles so they can be properly clamped. + float f = lookAng.yaw; + if ( lookMin.yaw <= -360.0f && lookMax.yaw >= 360.0f ) + { + lookAng.yaw = f - orientationJointYaw; + float diff; + diff = ( idMath::AngleNormalize360(newLookAng.yaw) - idMath::AngleNormalize360(f) ); + diff = idMath::AngleNormalize180( diff ); + lookAng.yaw += diff * currentHeadFocusRate; + //lookAng.yaw += ( newLookAng.yaw - f ) * currentHeadFocusRate; + } + else + { + lookAng.yaw = idMath::AngleNormalize180 ( f - orientationJointYaw ); + lookAng.yaw += (idMath::AngleNormalize180 ( newLookAng.yaw - f ) * currentHeadFocusRate); + } + lookAng.pitch += ( idMath::AngleNormalize180( newLookAng.pitch - lookAng.pitch ) * currentHeadFocusRate ); + + // Clamp the look angles + lookAng.Clamp( lookMin, lookMax ); + + // Calcuate the eye angles + f = eyeAng.yaw; + eyeAng.yaw = idMath::AngleNormalize180( f - orientationJointYaw ); + eyeAng.yaw += (idMath::AngleNormalize180( newLookAng.yaw - f ) * eyeFocusRate); + eyeAng.pitch += (idMath::AngleNormalize180( newLookAng.pitch - eyeAng.pitch ) * eyeFocusRate); + + // Clamp eye angles relative to the look angles + jointAng = eyeAng - lookAng; + jointAng.Normalize180( ); + jointAng.Clamp( eyeMin, eyeMax ); + eyeAng = lookAng + jointAng; + eyeAng.Normalize180( ); + + if ( canLook ) { + // Apply the look angles to the look joints + if ( animator.GetAnimFlags( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimNum()).ai_look_head_only ) { + if ( neckJoint != INVALID_JOINT || headJoint != INVALID_JOINT ) { + float jScale = 1.0f; + if ( neckJoint != INVALID_JOINT && headJoint != INVALID_JOINT ) { + jScale = 0.5f; + } + jointAng.pitch = lookAng.pitch * jScale; + jointAng.yaw = lookAng.yaw * jScale; + if ( neckJoint != INVALID_JOINT ) { + animator.SetJointAxis( neckJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + if ( headJoint != INVALID_JOINT ) { + animator.SetJointAxis( headJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + //what if we have a previous joint mod on the rest of the lookJoints + //from an anim that *wasn't* ai_look_head_only...? + //just clear them? Or move them towards 0? + //FIXME: move them towards zero... + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + } + } else { + //blend back + //yes, this is framerate dependant and inefficient and wrong, but... eliminates pops + jointAng.roll = 0.0f; + jointMod_t *curJointMod; + idAngles curJointAngleMod; + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //blend back + curJointMod = animator.FindExistingJointMod( lookJoints[ i ], NULL ); + if ( curJointMod ) + { + curJointAngleMod = curJointMod->mat.ToAngles(); + curJointAngleMod *= 0.75f; + if ( fabs(curJointAngleMod.pitch) < 1.0f + && fabs(curJointAngleMod.yaw) < 1.0f + && fabs(curJointAngleMod.roll) < 1.0f ) + { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + else + { + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, curJointAngleMod.ToMat3() ); + } + } + } + } + } + } else { + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } else { + //if ( animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look || aifl.disableLook ) { + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + animator.ClearJoint ( lookJoints[i] ); + } + } else { + //PCJ back to neutral + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } + + // Convert the look angles back to world angles. This is done to prevent dramatic orietation changes + // from changing the look direction abruptly (unless of course the minimums are not met) + lookAng.yaw = idMath::AngleNormalize180( lookAng.yaw + orientationJointYaw ); + eyeAng.yaw = idMath::AngleNormalize180( eyeAng.yaw + orientationJointYaw ); + + if ( move.moveType == MOVETYPE_FLY || move.fl.flyTurning ) { + // lean into turns + AdjustFlyingAngles(); + } + + // Orient the eyes towards their target + if ( leftEyeJoint != INVALID_JOINT && rightEyeJoint != INVALID_JOINT ) { + if ( head ) { + idAnimator *headAnimator = head->GetAnimator(); + + if ( focusType != AIFOCUS_NONE && allowEyeFocus && canLook ) { + //tweak these since it's looking at the wrong spot and not calculating from each eye and I don't have time to bother rewriting all of this properly + eyeAng.yaw -= 0.5f; + eyeAng.pitch += 3.25f; + idMat3 eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + eyeAng.yaw += 3.0f; + eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + if ( ai_debugEyeFocus.GetBool() ) { + idVec3 eyeOrigin; + idMat3 axis; + + head->GetJointWorldTransform ( rightEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + + head->GetJointWorldTransform ( leftEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + } + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus && focusType != AIFOCUS_NONE && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook ) { + idMat3 eyeAxis = ( eyeAng ).ToMat3() * GetPhysics()->GetAxis().Transpose ( ); + animator.SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + animator.SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + } + + return true; +} + +void idAI::OnTouch( idEntity *other, trace_t *trace ) { + // if we dont have an enemy or had one for at least a second that is a potential enemy the set the enemy + if ( other->IsType( idActor::GetClassType() ) + && !other->fl.notarget + && ( ReactionTo( other )&ATTACK_ON_SIGHT) + && (!enemy.ent || gameLocal.time - enemy.changeTime > 1000 ) ) { + SetEnemy( other ); + } + + if ( !enemy.ent && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + } + pusher = other; + aifl.pushed = true; + + // If pushed by the player update tactical + if ( pusher && pusher->IsType ( idPlayer::GetClassType() ) && (combat.tacticalMaskAvailable & AITACTICAL_MOVE_PLAYERPUSH_BIT) ) { + ForceTacticalUpdate ( ); + } +} + +idProjectile* idAI::AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ) { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel* projClip; + idMat3 axis; + float distance; + idProjectile* result; + + if ( !projectileDict ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + axis = ang.ToMat3(); + if ( !projectile.GetEntity() ) { + CreateProjectile( projectileDict, org, axis[ 0 ] ); + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); +// RAVEN END + + // launch the projectile + projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); + result = projectile; + projectile = NULL; + + lastAttackTime = gameLocal.time; + + return result; +} + +void idAI::RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { + jointHandle_t joint; + idVec3 org; + idMat3 axis; + + if ( !jointname || !jointname[ 0 ] ) { + org = physicsObj.GetOrigin(); + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, org, axis ); + } + + gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); +} + +/* +===================== +idAI::CanBecomeSolid + +returns true if the AI entity could become solid at its current position +===================== +*/ +bool idAI::CanBecomeSolid ( void ) { + int i; + int num; + idEntity * hit; + idClipModel* cm; + idClipModel* clipModels[ MAX_GENTITIES ]; + + // Determine what we are currently touching +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.ClipModelsTouchingBounds( this, physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + return false; + } + } + + return true; +} + +/* +===================== +idAI::BecomeSolid +===================== +*/ +void idAI::BecomeSolid( void ) { + physicsObj.EnableClip(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + physicsObj.GetClipModel()->Link(); +// RAVEN END + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); +} + +/* +===================== +idAI::BecomeNonSolid +===================== +*/ +void idAI::BecomeNonSolid( void ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); +} + +const char *idAI::ChooseAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + return head.GetEntity()->GetAnimator()->AnimFullName( anim ); + } + } else { + return animator.AnimFullName( anim ); + } + } + + return ""; +} + +/* +============ +idAI::ExecScriptFunction +============ +*/ +void idAI::ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm ) { + if( parm ) { + func.InsertEntity( parm, 0 ); + } else { + func.InsertEntity( this, 0 ); + } + + func.CallFunc( &spawnArgs ); + + if( parm ) { + func.RemoveIndex( 0 ); + } +} + +/* +============ +idAI::Prethink +============ +*/ +void idAI::Prethink ( void ) { + // Update our helper if we are moving + if ( move.fl.moving ) { + UpdateHelper ( ); + } + + if ( leader ) { + idEntity* groundEnt = leader->GetGroundEntity ( ); + if ( !(tether && !aifl.tetherMover) && groundEnt ) { + if ( groundEnt->IsType ( idMover::GetClassType ( ) ) ) { + idEntity* ent; + idEntity* next; + for( ent = groundEnt->GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == groundEnt && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(ent) ); + aifl.tetherMover = true; + break; + } + } + } else { + SetTether ( NULL ); + } + } + } else if ( tether && aifl.tetherMover ) { + SetTether ( NULL ); + } +} + +/* +============ +idAI::Postthink +============ +*/ +void idAI::Postthink( void ){ + if ( !aifl.simpleThink ) { + pain.takenThisFrame = 0; + } + + // Draw debug tactical information + DrawTactical ( ); + + if( vehicleController.IsDriving() ){ // Generate some sort of command? + usercmd_t usercmd; + + // Note! usercmd angles stuff is in deltas, not in absolute values. + + memset( &usercmd, 0, sizeof( usercmd ) ); + + idVec3 toEnemy; + + if( enemy.ent ){ + toEnemy = enemy.ent->GetPhysics()->GetOrigin(); + toEnemy -= GetPhysics()->GetOrigin(); + toEnemy.Normalize(); + + idAngles enemyAng; + + enemyAng = toEnemy.ToAngles(); + + usercmd.angles[PITCH] = ANGLE2SHORT( enemyAng.pitch ); + usercmd.angles[YAW] = ANGLE2SHORT( enemyAng.yaw ); + + usercmd.buttons = BUTTON_ATTACK; + + vehicleController.SetInput ( usercmd, enemyAng ); + } + } + + // Keep our threat value up to date + UpdateThreat ( ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::OnDeath( void ){ + if( vehicleController.IsDriving() ){ + usercmd_t usercmd; + + memset( &usercmd, 0, sizeof( usercmd ) ); + usercmd.buttons = BUTTON_ATTACK; + usercmd.upmove = 300.0f; // This will cause the character to eject. + + vehicleController.SetInput( usercmd, idAngles( 0, 0, 0 ) ); + + // Fixme! Is this safe to do immediately? + vehicleController.Eject(); + } + + aiManager.RemoveTeammate ( this ); + + ExecScriptFunction( funcs.death ); + +/* DONT DROP ANYTHING FOR NOW + float rVal = gameLocal.random.RandomInt( 100 ); + + if( spawnArgs.GetFloat( "no_drops" ) >= 1.0 ){ + spawnArgs.Set( "def_dropsItem1", "" ); + }else{ + // Fixme! Better guys should drop better stuffs! Make drops related to guy type? Do something cooler here? + if( rVal < 25 ){ // Half of guys drop nothing? + spawnArgs.Set( "def_dropsItem1", "" ); + }else if( rVal < 50 ){ + spawnArgs.Set( "def_dropsItem1", "item_health_small" ); + } + } +*/ +} + +/* +============ +idAI::OnWakeUp +============ +*/ +void idAI::OnWakeUp ( void ) { +} + +/* +============ +idAI::OnUpdatePlayback +============ +*/ +void idAI::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + return; +} + +/* +============ +idAI::OnLeaderChange +============ +*/ +void idAI::OnLeaderChange ( idEntity* oldLeader ) { + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnEnemyChange +============ +*/ +void idAI::OnEnemyChange ( idEntity* oldEnemy ) { + // Make sure we update our tactical state immediately + ForceTacticalUpdate ( ); + + // see if we should announce the enemy + if ( enemy.ent ) { + combat.fl.aware = true; + combat.investigateTime = 0; + + enemy.changeTime = gameLocal.time; + + enemy.lastKnownPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + + UpdateEnemyVisibility ( ); + UpdateEnemyPosition ( true ); + UpdateEnemy ( ); + } else { + enemy.range = 0; + enemy.range2d = 0; + enemy.changeTime = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + } + + enemy.fl.visible = false; +} + +/* +============ +idAI::OnTacticalChange +============ +*/ +void idAI::OnTacticalChange ( aiTactical_t oldTactical ) { + // if acutally moving to a new tactial location announce it + if ( move.fl.moving ) { + AnnounceTactical( combat.tacticalCurrent ); + } +} + +/* +============ +idAI::OnFriendlyFire +============ +*/ +void idAI::OnFriendlyFire ( idActor* attacker ) { + AnnounceFriendlyFire( static_cast(attacker) ); +} + +/* +============ +idAI::OnStartMoving +============ +*/ +void idAI::OnStartMoving ( void ) { + aifl.simpleThink = false; + combat.fl.crouchViewClear = false; +} + +/* +============ +idAI::OnStopMoving +============ +*/ +void idAI::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { +} + +/* +============ +idAI::OnStartAction +============ +*/ +void idAI::OnStartAction ( void ) { +} + +/* +============ +idAI::OnStopAction +============ +*/ +void idAI::OnStopAction ( void ) { +} + +/* +============ +idAI::OnEnemyVisiblityChange +============ +*/ +void idAI::OnEnemyVisiblityChange ( bool oldVisible ) { +} + +/* +============ +idAI::OnSetKey +============ +*/ +void idAI::OnSetKey ( const char* key, const char* value ) { + if ( !idStr::Icmp ( key, "noCombatChatter" ) ) { + combat.fl.noChatter = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "allowPlayerPush" ) ) { + combat.tacticalMaskAvailable &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + if ( spawnArgs.GetBool ( key ) ) { + combat.tacticalMaskAvailable |= AITACTICAL_MOVE_PLAYERPUSH_BIT; + } + } else if ( !idStr::Icmp ( key, "noLook" ) ) { + aifl.disableLook = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "killer_guard" ) ) { + aifl.killerGuard = spawnArgs.GetBool ( key ); + } +} + +/* +============ +idAI::OnCoverInvalidated +============ +*/ +void idAI::OnCoverInvalidated ( void ) { + // Force a tactical update now + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnCoverNotFacingEnemy +============ +*/ +void idAI::OnCoverNotFacingEnemy ( void ) { + // Clear attack timers so we can shoot right now + actionTimerRangedAttack.Clear ( actionTime ); + actionRangedAttack.timer.Clear( actionTime ); +} + +/* +============ +idAI::SkipCurrentDestination + +Is the AI's current destination ok enough to stay at? +============ +*/ +bool idAI::SkipCurrentDestination ( void ) const { + // can only skip current destination when we are stopped + if ( move.fl.moving ) { + return false; + } +/* + // If we are currently behind cover and that cover is no longer valid we should skip it + if ( IsBehindCover ( ) && !IsCoverValid ( ) ) { + return true; + } +*/ + return false; +} + +/* +============ +idAI::SkipImpulse +============ +*/ +bool idAI::SkipImpulse( idEntity *ent, int id ){ + bool skip = idActor::SkipImpulse( ent, id ); + + if( af.IsActive ( ) ) { + return false; + } + if( !fl.takedamage ){ + return true; + } + if( move.moveCommand == MOVE_RV_PLAYBACK ){ + return true; + } + + return skip; +} + +/* +============ +idAI::CanHitEnemy +============ +*/ +bool idAI::CanHitEnemy ( void ) { + trace_t tr; + idEntity *hit; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + idVec3 eye = GetEyePosition(); + idVec3 dir; + + // expand the ray out as far as possible so we can detect anything behind the enemy + dir = toPos - eye; + dir.Normalize(); + toPos = eye + dir * MAX_WORLD_SIZE; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return lastHitCheckResult; + } + + gameLocal.TracePoint( this, tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + hit = gameLocal.GetTraceEntity( tr ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { + lastHitCheckResult = true; + } else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && + ( static_cast( hit )->team != team ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI::CanHitEnemyFromJoint +============ +*/ + +bool idAI::CanHitEnemyFromJoint( const char *jointname ){ + trace_t tr; + idVec3 muzzle; + idMat3 axis; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return true; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + jointHandle_t joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); + muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, muzzle, toPos, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI:: +============ + +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ); + +int idAI::TestTrajectory( const idVec3 &firePos, const idVec3 &target, const char *projectileName ){ + idVec3 projVelocity; + idVec3 projGravity; + float testTime; + float zVel, height, pitch, s, c; + idVec3 dir; + float newVel; + float delta_x; + float delta_z; + + projectileDef = gameLocal.FindEntityDefDict ( projectileName ); + projVelocity = idProjectile::GetVelocity( projectileDef ); + projGravity = idProjectile::GetGravity( projectileDef ).z * GetPhysics()->GetGravity(); + pitch = DEG2RAD( gameLocal.random.RandomFloat() * 50 + 20 ); // Random pitch range between 20 and 70. Should this be customizeable? + + idMath::SinCos( pitch, s, c ); + + delta_x = idMath::Sqrt( ( target.x - firePos.x ) * ( target.x - firePos.x ) + ( target.y - firePos.y ) * ( target.y - firePos.y ) ); + delta_z = target.z - firePos.z; + newVel = ( delta_x / idMath::Cos( pitch ) ) * idMath::Sqrt( projGravity.z / ( 2.0f * ( delta_x * idMath::Tan( pitch ) - delta_z ) ) ); + testTime = delta_x / ( newVel * c ); + zVel = newVel * s; + + float a = idMath::ASin ( delta_x * GetPhysics()->GetGravity().Length() / (projVelocity.x * projVelocity.x) ); + a = a / 2; + + float r = (projVelocity.x * projVelocity.x) * idMath::Sin ( 2 * a ) / GetPhysics()->GetGravity().Length(); + if ( r < delta_x - (delta_x * 0.1) ) { + mVar.valid_lobbed_shot = 0; + return 0; + } else { + mVar.lobDir = target - firePos; + mVar.lobDir.z = 0; + mVar.lobDir.z = idMath::Tan ( a ) * mVar.lobDir.LengthFast(); + mVar.lobDir.Normalize ( ); + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = 1.0f; // newVel / projVelocity.Length(); + return 1; + } + + projGravity[2] *= -1.0f; + + dir = target - firePos; + dir.z = 0; + dir.Normalize(); + delta_x = idMath::Sqrt( 1 - ( c * c ) ); + dir.x *= delta_x; + dir.y *= delta_x; + dir.z = c; + + height = HeightForTrajectory( firePos, zVel, projGravity[2] ) - firePos.z; + if ( height > MAX_WORLD_SIZE ) { + // goes higher than we want to allow + mVar.valid_lobbed_shot = 0; + return 0; + } + + if ( idAI::TestTrajectory ( firePos, target, zVel, projGravity[2], testTime, height * 2, NULL, + MASK_SHOT_RENDERMODEL, this, enemy.GetEntity(), ai_debugTrajectory.GetBool() ? 4000 : 0 ) ) { + + if ( ai_debugTrajectory.GetBool() ) { + float t = testTime / 100.0f; + idVec3 velocity = dir * newVel; + idVec3 lastPos, pos; + lastPos = firePos; + pos = firePos; + for ( int j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, ai_debugTrajectory.GetBool() ? 4000 : 0 ); + lastPos = pos; + } + } + + mVar.lobDir = dir; + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = newVel / projVelocity.Length(); + }else{ + mVar.valid_lobbed_shot = 0; + } + + return ( (int)mVar.valid_lobbed_shot != 0 ); + +} +*/ + +/* +============ +idAI:: +============ +*/ + +float idAI::GetTurnDelta( void ){ + float amount; + + if ( move.turnRate ) { + amount = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + return amount; + } else { + return 0.0f; + } +} + +/* +============ +idAI::GetIdleAnimName +============ +*/ +const char* idAI::GetIdleAnimName ( void ) { + const char* animName = NULL; + + // Start idle animation + if ( enemy.ent ) { + animName = "idle_alert"; + } + + if ( animName && HasAnim ( ANIMCHANNEL_ALL, animName ) ) { + return animName; + } + + return "idle"; +} + +/* +=============================================================================== + + idAI - Enemy Finding + +=============================================================================== +*/ + +/* +============ +idAI::FindEnemy +============ +*/ +idEntity *idAI::FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr ){ + idActor* actor; + idActor* bestEnemy; + idActor* bestEnemyBackup; + float bestThreat; + float bestThreatBackup; + float distSqr; + float enemyRangeSqr; + float awareRangeSqr; + idVec3 origin; + idVec3 delta; + pvsHandle_t pvs; + + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + bestThreat = 0.0f; + bestThreatBackup = 0.0f; + bestEnemy = NULL; + bestEnemyBackup = NULL; + awareRangeSqr = Square ( combat.awareRange ); + enemyRangeSqr = enemy.ent ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + origin = GetEyePosition ( ); + + // Iterate through the enemy team + for( actor = aiManager.GetEnemyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden enemies and enemies that cant be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + + // Calculate the distance between ourselves and our potential enemy + delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); + distSqr = delta.LengthSqr(); + + // Calculate the adjusted threat for this actor + float threat = CalculateEnemyThreat ( actor ); + + // Save the highest threat enemy as a backup in case we cannot find one we can see + if ( threat > bestThreatBackup ) { + bestThreatBackup = threat; + bestEnemyBackup = actor; + } + + // If we have already found a more threatening enemy then attack that + if ( threat < bestThreat ) { + continue; + } + + // If this enemy isnt in the same pvps then use them as a backup + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // this is pretty specific to Quake 4, but we don't want the player to be around an enemy too long who can't see him. We need to randomly spike the + // awareRange on creatures to simulate them looking behind them, or noticing someone standing around for too long. + // Modders take note, this will prevent most "sneaking up on bad guys" action because they will likely spike their aware ranges out + // during the sneaking. + if( gameLocal.random.RandomFloat() < 0.005f ) { + awareRangeSqr *= 15; + } + + // fov doesn't matter if they're within awareRange, we "sense" them if we're alert... (or should LOS not even matter at this point?) + if ( distSqr < awareRangeSqr || CanSeeFrom ( origin, actor, (inFov && !(combat.fl.aware&&distSqrIsType ( idAI::Type ) ) { + idAI* teammateAI = static_cast(teammate); + enemy.smoothedLinearVelocity = teammateAI->enemy.smoothedLinearVelocity; + enemy.smoothedPushedVelocity = teammateAI->enemy.smoothedPushedVelocity; + enemy.lastKnownPosition = teammateAI->enemy.lastKnownPosition; + enemy.lastVisibleEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleFromEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleChestPosition = teammateAI->enemy.lastVisibleChestPosition; + enemy.lastVisibleTime = 0; + } + + return true; +} + +/* +============ +idAI::CheckForCloserEnemy +============ +*/ +bool idAI::CheckForCloserEnemy ( void ) { + idEntity* newEnemy = NULL; + float maxDistSqr; + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + // See if we happen to have heard someone this frame that we can use + newEnemy = HeardSound( true ); + if ( newEnemy && newEnemy != enemy.ent && newEnemy->IsType( idActor::GetClassType() ) ) { + //heard someone else! + float newDist = DistanceTo ( newEnemy->GetPhysics()->GetOrigin() ); + + // Are they closer than the enemy we are fighting? + if ( newDist < enemy.range ) { + //new enemy is closer than current one, take them! + SetEnemy( newEnemy ); + return true; + } + } + + if ( GetEnemy() && enemy.range ) { + maxDistSqr = Min( Square ( enemy.range ), Square ( combat.awareRange ) ); + } else { + maxDistSqr = Square ( combat.awareRange ); + } + + newEnemy = FindEnemy( false, 0, maxDistSqr ); + + if ( !newEnemy ) { + return false; + } + + SetEnemy( newEnemy ); + return true; +} + +/* +============ +idAI::CheckForReplaceEnemy + +TODO: Call CalculateThreat ( ent ) and compare to current entity +============ +*/ +bool idAI::CheckForReplaceEnemy ( idEntity* replacement ) { + bool replace; + + // If our replacement is a driver a vehicle and they are hidden we will + // want to shoot back at their vehicle not them. + idActor* actor; + actor = dynamic_cast(replacement); + if ( actor && actor->IsInVehicle ( ) && actor->IsHidden ( ) ) { + replacement = actor->GetVehicleController ( ).GetVehicle ( ); + } + + // Invalid replacement? + if ( !replacement) { + return false; + } + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + if ( replacement == enemy.ent ) { + return false; + } + + // Dont want to set our enemy to a friendly target + if ( replacement->IsType( idActor::GetClassType() ) && (static_cast(replacement))->team == team ) { + return false; + } + + // Not having an enemy will set it immediately + if ( !enemy.ent ) { + SetEnemy ( replacement ); + return true; + } + + // Dont change enemies too often when being hit + if ( gameLocal.time - enemy.changeTime < 1000 ) { + return false; + } + + replace = false; + + // If new enemy is more threatening then replace it reguardless if we can see it + if ( CalculateEnemyThreat ( replacement ) > CalculateEnemyThreat ( enemy.ent ) ) { + replace = true; + // Replace our enemy if we havent seen ours in a bit + } else if ( !IsEnemyRecentlyVisible ( 0.25f ) ) { + replace = true; + } + + // Replace enemy? + if ( replace ) { + SetEnemy ( replacement ); + } + + return replace; +} + +/* +============ +idAI::UpdateThreat +============ +*/ +void idAI::UpdateThreat ( void ) { + // Start threat at base threat level + combat.threatCurrent = combat.threatBase; + + // Adjust threat using current tactical state + switch ( combat.tacticalCurrent ) { + case AITACTICAL_HIDE: combat.threatCurrent *= 0.5f; break; + case AITACTICAL_MELEE: combat.threatCurrent *= 2.0f; break; + } + + // Signifigantly reduced threat when in undying mode + if ( aifl.undying ) { + combat.threatCurrent *= 0.25f; + } +} + +/* +============ +idAI::CalculateEnemyThreat +============ +*/ +float idAI::CalculateEnemyThreat ( idEntity* enemyEnt ) { + // Calculate the adjusted threat for this actor + float threat = 1.0f; + if ( enemyEnt->IsType ( idAI::GetClassType ( ) ) ) { + idAI* enemyAI = static_cast(enemyEnt); + threat = enemyAI->combat.threatCurrent; + + // Increase threat for enemies that are targetting us + if ( enemyAI->enemy.ent == this && enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE ) { + threat *= 2.0f; + } + } else if ( enemyEnt->IsType ( idPlayer::GetClassType ( ) ) ) { + threat = 2.0f; + } else { + threat = 1.0f; + } + + float enemyRangeSqr; + float distSqr; + + enemyRangeSqr = (enemy.ent) ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + distSqr = (physicsObj.GetOrigin ( ) - enemyEnt->GetPhysics()->GetOrigin ( )).LengthSqr ( ); + + if ( distSqr > 0 ) { + return threat * (enemyRangeSqr / distSqr); + } + + return threat; +} + +/* +============ +idAI::CheckBlink +============ +*/ +void idAI::CheckBlink ( void ) { +// if ( IsSpeaking ( ) ) { +// return; +// } + idActor::CheckBlink ( ); +} + +/* +============ +idAI::Speak +============ +*/ +bool idAI::Speak( const char *lipsync, bool random ){ + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + if ( random ) { + // If there is no lipsync then skip it + if ( spawnArgs.MatchPrefix ( lipsync ) ) { + lipsync = spawnArgs.RandomPrefix ( lipsync, gameLocal.random ); + } else { + lipsync = NULL; + } + } else { + lipsync = spawnArgs.GetString ( lipsync ); + } + + if ( !lipsync || !*lipsync ) { + return false; + } + + if ( head ) { + speakTime = head->StartLipSyncing( lipsync ); + } else { + speakTime = 0; + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, SSF_IS_VO, false, &speakTime ); + } + + speakTime += gameLocal.time; + return true; +} + +/* +============ +idAI::StopSpeaking +============ +*/ +void idAI::StopSpeaking( bool stopAnims ){ + speakTime = 0; + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + if ( stopAnims ) { + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + } +} + +/* +============ +idAI::CanHitEnemyFromAnim +============ +*/ +bool idAI::CanHitEnemyFromAnim( int animNum, idVec3 offset ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin()+offset; + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + +/* + if( DebugFilter(ai_debugTactical) ) { + gameRenderWorld->DebugLine ( colorYellow, org + attackAnimInfo[ animNum ].eyeOffset * viewAxis, from, 5000 ); + gameRenderWorld->DebugLine ( colorOrange, from, enemy.lastVisibleEyePosition, 5000 ); + } +*/ + + // If the point we are shooting from is within our bounds then we are good to go, otherwise make sure its not in a wall + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + if ( !ownerBounds.ContainsPoint ( from ) ) { + trace_t tr; + if ( !g_perfTest_aiNoVisTrace.GetBool() ) { + gameLocal.TracePoint( this, tr, org + attackAnimInfo[ animNum ].eyeOffset * axis, from, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction < 1.0f ) { + return false; + } + } + } + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +idAI::ScriptedBegin +================ +*/ +bool idAI::ScriptedBegin ( bool endWithIdle, bool allowDormant ) { + if ( aifl.dead ) { + return false; + } + + // Wakeup if not awake already + WakeUp ( ); + + aifl.scriptedEndWithIdle = endWithIdle; + aifl.scripted = true; +// combat.fl.aware = false; + combat.tacticalCurrent = AITACTICAL_NONE; + + // Make sure the entity never goes dormant during a scripted event or + // the event may never end. + aifl.scriptedNeverDormant = !allowDormant; + dormantStart = 0; + +/* + // actors will ignore enemies during scripted events + ClearEnemy ( ); +*/ + + // Cancel any current movement + StopMove ( MOVE_STATUS_DONE ); + + move.fl.allowAnimMove = true; + + return true; +} + +/* +================ +idAI::ScriptedEnd +================ +*/ +void idAI::ScriptedEnd ( void ) { + dormantStart = 0; + aifl.scripted = false; +} + +/* +================ +idAI::ScriptedStop +================ +*/ +void idAI::ScriptedStop ( void ) { + if ( !aifl.scripted ) { + return; + } + aifl.scriptedEndWithIdle = true; + aifl.scripted = false; + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::ScriptedMove +================ +*/ +void idAI::ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + //disable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.UnMarkAllReachBlocked(); + //attempt the move - NOTE: this *can* fail if there's no route or AAS obstacles are in the way! + MoveToEntity ( destEnt, minDist ); + //re-enable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.ReMarkAllReachBlocked(); + + // Move the torso to the idle state if its not already there + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Move the legs into the idle state so he will start moving + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + + // Set up state loop for moving + SetState ( "State_ScriptedMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedFace +================ +*/ +void idAI::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + // Force idle while facing + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Start facing the entity + FaceEntity ( faceEnt ); + + SetState ( "State_ScriptedFace" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedAnim + +Plays an the given animation in an un-interruptable state. If looping will continue indefinately until +another operation which will stop a scripted sequence is called. When done can optionally return the character +back to their normal processing if endWithIdle is set to true. +================ +*/ +void idAI::ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle, true ) ) { + return; + } + + TurnToward ( move.current_yaw ); + + if ( loop ) { + // Loop the given animation + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + // Play the given animation + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + + SetAnimState ( ANIMCHANNEL_LEGS, "Wait_Frame" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_ScriptedAnim" ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + + SetState ( "Wait_ScriptedDone" ); + PostState ( "State_ScriptedStop" ); +} + + +/* +============ +idAI::ScriptedPlaybackAim +============ +*/ +void idAI::ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + mLookPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ); + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackAim" ); + PostState ( "State_ScriptedStop" ); +} + +/* +============ +idAI::ScriptedAction +============ +*/ +void idAI::ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + const char* actionName; + + if ( !actionEnt ) { + return; + } + + // Get the action name + actionName = actionEnt->spawnArgs.GetString ( "action" ); + if ( !*actionName ) { + gameLocal.Error ( "missing action keyword on scripted action entity '%s' for ai '%s'", + actionEnt->GetName(), + GetName() ); + return; + } + + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + scriptedActionEnt = actionEnt; + + SetState ( "State_ScriptedStop" ); + PerformAction ( va("TorsoAction_%s", actionName ), 4, true ); +} + +/* +============ +idAI::FootStep +============ +*/ +void idAI::FootStep ( void ) { + idActor::FootStep ( ); + + ExecScriptFunction( funcs.footstep ); +} + +/* +============ +idAI::SetScript +============ +*/ +void idAI::SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "first_sight" ) ) { + funcs.first_sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "sight" ) ) { + funcs.sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "pain" ) ) { + funcs.pain.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "damage" ) ) { + funcs.damage.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "death" ) ) { + funcs.death.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "attack" ) ) { + funcs.attack.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "init" ) ) { + funcs.init.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "onclick" ) ) { + funcs.onclick.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "launch_projectile" ) ) { + funcs.launch_projectile.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "footstep" ) ) { + funcs.footstep.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "postHeal" ) ) { + // hax: this is a medic only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else if ( !idStr::Icmp ( scriptName, "postWeaponDestroyed" ) ) { + // hax: this is a Gladiator/Light Tank only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else { + gameLocal.Warning ( "unknown script '%s' specified on entity '%s'", scriptName, name.c_str() ); + } +} + +/* +=============================================================================== + + idAI - Reactions + +=============================================================================== +*/ + +/* +============ +idAI::ReactToShotAt +============ +*/ +void idAI::ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ) { + if ( g_perfTest_aiNoDodge.GetBool() ) { + return; + } + + idVec3 foo; + idVec3 diff; + diff = GetPhysics()->GetOrigin() - origOrigin; + diff = origOrigin + diff.ProjectOntoVector ( origDir ) * origDir; + diff = diff - GetPhysics()->GetOrigin(); + diff.NormalizeFast ( ); + diff.z = 0; + + idAngles angles = diff.ToAngles ( ); + float angleDelta = idMath::AngleDelta ( angles[YAW], move.current_yaw ); + + combat.shotAtTime = gameLocal.time; + combat.shotAtAngle = angleDelta; + + // Someone is attacking us so give them a chance to be our new enemy + CheckForReplaceEnemy ( attacker ); +} + +/* +============ +idAI::ReactToPain +============ +*/ +void idAI::ReactToPain ( idEntity* attacker, int damage ) { + CheckForReplaceEnemy ( attacker ); +} + +/* +=============================================================================== + + idAI - Helpers + +=============================================================================== +*/ + +/* +============ +idAI::UpdateHelper +============ +*/ +void idAI::UpdateHelper ( void ) { + rvAIHelper* oldhelper; + + // Link ourselves to the closest helper + oldhelper = helperCurrent; + helperCurrent = aiManager.FindClosestHelper ( physicsObj.GetOrigin() ); + + // Ideal stays the same as current as long as it was the same when we started + if ( oldhelper == helperIdeal ) { + helperIdeal = helperCurrent; + } +} + +/* +============ +idAI::GetActiveHelper + +When we have an enemy our current helper becomes the active helper, when we dont have an +enemy we instead use our ideal. +============ +*/ +rvAIHelper* idAI::GetActiveHelper ( void ) { + return GetEnemy ( ) ? helperCurrent : helperIdeal; +} + +/* +=============================================================================== + + idAI - Tethers + +=============================================================================== +*/ + +/* +============ +idAI::SetTether +============ +*/ +void idAI::SetTether ( rvAITether* newTether ) { + aifl.tetherMover = false; + + // Clear our current tether? + if ( !newTether ) { + if ( tether ) { + tether = NULL; + ForceTacticalUpdate ( ); + } + } else if ( newTether->IsType ( rvAITetherClear::GetClassType ( ) ) ) { + SetTether ( NULL ); + } else { + if ( newTether && !newTether->ValidateAAS ( this ) ) { + // If you have aas error out to make them fix it + if ( aas ) { + gameLocal.Error ( "tether entity '%s' does no link into the aas for ai '%s'. (try moving it closer to the floor where the aas is)", + newTether->GetName(), GetName () ); + // If we dont have aas, just warn + } else { + gameLocal.Warning ( "tether entity '%s' does no link into the aas for ai '%s'. (there is no aas available)", + newTether->GetName(), GetName () ); + } + SetTether ( NULL ); + } else if ( newTether != tether ) { + tether = newTether; + ForceTacticalUpdate ( ); + } + } +} + +/* +============ +idAI::GetTether +============ +*/ +rvAITether* idAI::GetTether ( void ) { + return tether; +} + +/* +============ +idAI::IsTethered +============ +*/ +bool idAI::IsTethered ( void ) const { + // Need a tether entity to be tethered + if ( !tether ) { + return false; + } + // If we have an enemy and that enemy is within our tether then break it if we can + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) && tether->CanBreak ( ) ) { + if ( tether->ValidateDestination ( static_cast(enemy.ent.GetEntity()), enemy.lastKnownPosition ) ) { + return false; + } + } + return true; +} + +/* +============ +idAI::IsWithinTether +============ +*/ +bool idAI::IsWithinTether ( void ) const { + if ( !IsTethered ( ) ) { + return false; + } + if ( !tether->ValidateDestination ( (idAI*)this, physicsObj.GetOrigin ( ) ) ) { + return false; + } + return true; +} + +/* +=============================================================================== + + idAI - NonCombat + +=============================================================================== +*/ + +/* +===================== +idAI::SetTalkState +===================== +*/ +void idAI::SetTalkState ( talkState_t state ) { + // Make sure state is valid + if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { + gameLocal.Error( "Invalid talk state (%d)", (int)state ); + } + + // Same state we are already in? + if ( talkState == state ) { + return; + } + + // Set new talk state + talkState = state; + +} + +/* +============ +idAI::SetPassivePrefix +============ +*/ +void idAI::SetPassivePrefix ( const char* prefix ) { + passive.prefix = prefix; + if ( passive.prefix.Length() ) { + passive.prefix += "_"; + } + + // Force an idle change + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + + // Get animation prefixs + passive.fl.multipleIdles = GetPassiveAnimPrefix ( "idle", passive.animIdlePrefix ); + GetPassiveAnimPrefix ( "fidget", passive.animFidgetPrefix ); + GetPassiveAnimPrefix ( "talk", passive.animTalkPrefix ); +} + +/* +============ +idAI::GetPassiveAnimPrefix +============ +*/ +bool idAI::GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ) { + const idKeyValue* key; + + // First see if we have custom idle animations for the passive prefix + key = NULL; + if ( passive.prefix.Length ( ) ) { + animPrefix = va("anim_%s%s", passive.prefix.c_str(), animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + // If there are no custom idle animations for the prefix then see if there are any custom anims at all + if ( !key ) { + animPrefix = va("anim_%s", animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + if ( !key ) { + animPrefix = ""; + return false; + } + + return spawnArgs.MatchPrefix ( animPrefix, key ) ? true : false; +} + +/* +=================== +idAI::IsMeleeNeeded +=================== +*/ +bool idAI::IsMeleeNeeded( void ) { + + if( enemy.ent && enemy.ent->IsType ( idAI::Type )) { + + idAI* enemyAI = static_cast(enemy.ent.GetEntity()); + + //if our enemy is closing in on us and demands melee, we'll meet him. + if ( enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE && enemy.range < combat.meleeRange ) { + return true; + } + + //other checks... + } + + return false; +} + +/* +=================== +idAI::IsCrouching +=================== +*/ +bool idAI::IsCrouching( void ) const { + return move.fl.crouching; +} + +bool idAI::CheckDeathCausesMissionFailure( void ) +{ + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) + { + return true; + } + if ( targets.Num() ) + { + //go through my targets and see if any are of class rvObjectiveFailed + idEntity* targEnt; + for( int i = 0; i < targets.Num(); i++ ) { + targEnt = targets[ i ].GetEntity(); + if ( !targEnt ) + { + continue; + } + if ( !targEnt->IsType( rvObjectiveFailed::GetClassType() ) ) { + continue; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + continue; + } + //yep! + return true; + } + } + return false; +} diff --git a/source/mpgame/ai/AI.h b/source/mpgame/ai/AI.h new file mode 100644 index 0000000..4607fd7 --- /dev/null +++ b/source/mpgame/ai/AI.h @@ -0,0 +1,1373 @@ +/* +================ + +AI.h + +================ +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +// moved all motion related code to AI_Move.h +#ifndef __AI_MOVE_H__ + #include "AI_Move.h" +#endif +#ifndef __AAS_TACTICAL_H__ + #include "AAS_tactical.h" +#endif + +typedef enum { + AITACTICAL_NONE, + AITACTICAL_MELEE, // Rush towards the enemy and perform melee attacks when within range + AITACTICAL_MOVE_FOLLOW, // Move towards leader, stop when within range + AITACTICAL_MOVE_TETHER, // Move within tether, stop when within tether + AITACTICAL_MOVE_PLAYERPUSH, // Move away when the player pushes us (or another ai entity pushes us that was pushe by player) + AITACTICAL_COVER, // Move to cover and perform attacks from that cover position + AITACTICAL_COVER_FLANK, + AITACTICAL_COVER_ADVANCE, + AITACTICAL_COVER_RETREAT, + AITACTICAL_COVER_AMBUSH, + AITACTICAL_RANGED, // Move to position in which the enemy can be attacked from range, stop when there and attack enemy + AITACTICAL_TURRET, // Stay in current position and attack enemy + AITACTICAL_HIDE, // Move to a position where we cannot be seen by our enemy + AITACTICAL_PASSIVE, // Stay in current position with multiple idles, twitch animations, and conversations + + AITACTICAL_MAX +} aiTactical_t; + +typedef enum { + AICTRESULT_OK, + AICTRESULT_SKIP, + AICTRESULT_NOMOVE, +} aiCTResult_t; + +const int AITACTICAL_NONE_BIT = BIT(AITACTICAL_NONE); +const int AITACTICAL_MELEE_BIT = BIT(AITACTICAL_MELEE); +const int AITACTICAL_MOVE_FOLLOW_BIT = BIT(AITACTICAL_MOVE_FOLLOW); +const int AITACTICAL_MOVE_TETHER_BIT = BIT(AITACTICAL_MOVE_TETHER); +const int AITACTICAL_MOVE_PLAYERPUSH_BIT = BIT(AITACTICAL_MOVE_PLAYERPUSH); +const int AITACTICAL_COVER_BIT = BIT(AITACTICAL_COVER); +const int AITACTICAL_COVER_FLANK_BIT = BIT(AITACTICAL_COVER_FLANK); +const int AITACTICAL_COVER_ADVANCE_BIT = BIT(AITACTICAL_COVER_ADVANCE); +const int AITACTICAL_COVER_RETREAT_BIT = BIT(AITACTICAL_COVER_RETREAT); +const int AITACTICAL_COVER_AMBUSH_BIT = BIT(AITACTICAL_COVER_AMBUSH); +const int AITACTICAL_RANGED_BIT = BIT(AITACTICAL_RANGED); +const int AITACTICAL_TURRET_BIT = BIT(AITACTICAL_TURRET); +const int AITACTICAL_HIDE_BIT = BIT(AITACTICAL_HIDE); +const int AITACTICAL_PASSIVE_BIT = BIT(AITACTICAL_PASSIVE); + +const int AITACTICAL_COVER_BITS = (AITACTICAL_COVER_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); +const int AITACTICAL_RANGED_BITS = (AITACTICAL_RANGED_BIT); +const int AITACTICAL_NONMOVING_BITS = (AITACTICAL_NONE_BIT|AITACTICAL_TURRET_BIT|AITACTICAL_PASSIVE_BIT); + +typedef enum { + TALKMSG_NONE, + TALKMSG_PRIMARY, + TALKMSG_SECONDARY, + TALKMSG_LOOP, +} talkMessage_t; + +typedef enum { + AIFLAGOVERRIDE_DAMAGE, + AIFLAGOVERRIDE_DISABLEPAIN, + AIFLAGOVERRIDE_NOTURN, + AIFLAGOVERRIDE_NOGRAVITY +} aiFlagOverride_t; + +typedef enum { + TALK_NEVER, // Never talk to the player + TALK_DEAD, // Cant talk due to being dead + TALK_OK, // Can talk + TALK_FOLLOW, // Talking to will cause a follow + TALK_BUSY, // Cant talk right now, is busy + TALK_WAIT, // Wait a bit - he's probably in the middle of a conversation (this is so you can still see their names but they won't talk to you when clicked on) + NUM_TALK_STATES +} talkState_t; + +//chance that AI will make an announcement when they have the option (0 - 1.0f) +#define AISPEAK_CHANCE 0.2f + +typedef enum { + AIFOCUS_NONE, + AIFOCUS_LEADER, + AIFOCUS_TARGET, + AIFOCUS_TALK, + AIFOCUS_PLAYER, + AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_ENEMY = AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_COVER, + AIFOCUS_COVERLOOK, + AIFOCUS_HELPER, + AIFOCUS_TETHER, + AIFOCUS_MAX +} aiFocus_t; + +typedef enum { + AIMOVESPEED_DEFAULT, // Choose run/walk depending on situation + AIMOVESPEED_RUN, // Always run + AIMOVESPEED_WALK, // alwasy walk +} aiMoveSpeed_t; + +typedef struct rvAIFuncs_s { + rvScriptFuncUtility first_sight; // script to run when an enemy is first sighted + rvScriptFuncUtility sight; // script to run every time an enemy is sighted + rvScriptFuncUtility pain; // script to run when the AI takes pain + rvScriptFuncUtility damage; // script to run when the AI takes damage + rvScriptFuncUtility death; // script to run when the AI dies + rvScriptFuncUtility attack; // script to run when attacking an enemy + rvScriptFuncUtility init; // script to run on initialization + rvScriptFuncUtility onclick; // script to run when a friendly AI is clicked on + rvScriptFuncUtility launch_projectile; // script to run when a projectile is launched + rvScriptFuncUtility footstep; // script to run on a footstep +} rvAIFuncs_t; + +typedef struct rvAICombat_s{ + struct combatFlags_s { + bool ignoreEnemies :1; + bool alert :1; + bool aware :1; + bool tetherNoBreak :1; // Set to true to prevent enemies from breaking tethers + bool tetherAutoBreak :1; // Set to true to automatically break tethers when within range + bool tetherOutOfRange :1; // Set to true when we are out of range of our curren tether + bool seenEnemyDirectly :1; // Has directly seen an enemy (as opposed to having heard or been told about him) + bool noChatter :1; // No combat announcements + bool crouchViewClear :1; // Can I crouch at the position that I stopped at? + } fl; + + float max_chasing_turn; + float shotAtTime; + float shotAtAngle; + idVec2 hideRange; + idVec2 attackRange; + int attackSightDelay; + float meleeRange; + float aggressiveRange; // Range to become more aggressive + float aggressiveScale; // Scale to use when altering numbers due to aggression + int investigateTime; + + float visStandHeight; // Height to check enemy visibiliy while standing + float visCrouchHeight; // Height to check enemy visiblity while crouching + float visRange; // Maximum distance to check enemy visibility + float earRange; // Maximum distance to check hearing an enemy from + float awareRange; // Distance to become automatically aware of an enemy + + int tacticalMaskAvailable; // Currently available tactical states + int tacticalMaskUpdate; // states currently being evaluated + aiTactical_t tacticalCurrent; // Current tactical state + int tacticalUpdateTime; // Last time the tacitcal state was updated (for delaying updating) + int tacticalPainTaken; // Amount of damage taken in the current tactical state + int tacticalPainThreshold; // Threshold of pain before invalidating the current tactical state + int tacticalFlinches; // Number of flinches that have occured at the current tactical state + + float threatBase; // Base amount of threat generated by AI + float threatCurrent; // Current amount of threat generatd by AI based on current state + + int maxLostVisTime; + + int coverValidTime; + int maxInvalidCoverTime; +} rvAICombat_t; + +typedef struct rvAIPain_s { + float threshold; + float takenThisFrame; + int lastTakenTime; + int loopEndTime; + idStr loopType; +} rvAIPain_t; + +typedef struct rvAIEnemy_s { + struct flags_s { + bool lockOrigin :1; // Stop tracking enemy origin until state changes + bool dead :1; // Enemy is dead + bool inFov :1; // Enemy is currently in fov + bool sighted :1; // Enemy was sighted at least once + bool visible :1; // Enemy is visible? + } fl; + + idEntityPtr ent; + int lastVisibleChangeTime; // last time the visible state of the enemy changed + idVec3 lastVisibleFromEyePosition; // Origin used in last successfull visibility check + idVec3 lastVisibleEyePosition; // Origin of last known visible eye position + idVec3 lastVisibleChestPosition; // Origin of last known visible chest position + int lastVisibleTime; // Time we last saw and enemy + + idVec3 smoothedLinearVelocity; + idVec3 smoothedPushedVelocity; + float smoothVelocityRate; + + idVec3 lastKnownPosition; // last place the enemy was known to be (does not mean visiblity) + + float range; // range to enemy + float range2d; // 2d range to enemy + int changeTime; // Time enemy was last changed + int checkTime; // Time we last checked for a new enemy +} rvAIEnemy_t; + +typedef struct rvAIPassive_s { + struct flags_s { + bool disabled :1; // No advanced passive state + bool multipleIdles :1; // Has multiple idle animations to play + bool fidget :1; // Has fidget animations + } fl; + + idStr animIdlePrefix; + idStr animFidgetPrefix; + idStr animTalkPrefix; + //idStr talkPrefix; + + idStr prefix; + idStr idleAnim; + int idleAnimChangeTime; + int fidgetTime; + int talkTime; +} rvAIPassive_t; + +typedef struct rvAIAttackAnimInfo_s { + idVec3 attackOffset; + idVec3 eyeOffset; +} rvAIAttackAnimInfo_t; + +#define AIACTIONF_ATTACK BIT(0) +#define AIACTIONF_MELEE BIT(1) + +class rvAIActionTimer { +public: + + rvAIActionTimer ( void ); + + bool Init ( const idDict& args, const char* name ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( int currentTime ); + void Reset ( int currentTime, float diversity = 0.0f, float scale = 1.0f ); + void Add ( int _time, float diversity = 0.0f ); + + bool IsDone ( int currentTime ) const; + + int GetTime ( void ) const; + int GetRate ( void ) const; + +protected: + + int time; + int rate; +}; + +ID_INLINE bool rvAIActionTimer::IsDone ( int currentTime ) const { + return currentTime >= time; +} + +ID_INLINE int rvAIActionTimer::GetTime ( void ) const { + return time; +} + +ID_INLINE int rvAIActionTimer::GetRate ( void ) const { + return rate; +} +class rvAIAction { +public: + + rvAIAction ( void ); + + bool Init ( const idDict& args, const char* name, const char* defaultState, int flags ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum EStatus { + STATUS_UNUSED, + STATUS_OK, // action was performed + STATUS_FAIL_DISABLED, // action is current disabled + STATUS_FAIL_TIMER, // actions timer has not finished + STATUS_FAIL_EXTERNALTIMER, // external timer passed in to PerformAction has not finished + STATUS_FAIL_MINRANGE, // enemy is not within minimum range + STATUS_FAIL_MAXRANGE, // enemy is out of maximum range + STATUS_FAIL_CHANCE, // action chance check failed + STATUS_FAIL_ANIM, // bad animation + STATUS_FAIL_CONDITION, // condition given to PerformAction failed + STATUS_FAIL_NOENEMY, // enemy cant be attacked + STATUS_MAX + }; + + struct flags_s { + bool disabled :1; // action disabled? + bool noPain :1; // no pain during action + bool noTurn :1; // no turning during action + bool isAttack :1; // attack? + bool isMelee :1; // melee? + bool overrideLegs :1; // override legs on this action? + bool noSimpleThink :1; // dont use simple think logic for this action + } fl; + + + idStrList anims; + idStr state; + + rvAIActionTimer timer; + + int blendFrames; + int failRate; + + float minRange; + float maxRange; + float minRange2d; + float maxRange2d; + + float chance; + float diversity; + + EStatus status; +}; + +typedef bool (idAI::*checkAction_t)(rvAIAction*,int); + +class rvPlaybackDriver +{ +public: + rvPlaybackDriver( void ) { mPlaybackDecl = NULL; mOldPlaybackDecl = NULL; } + + bool Start( const char *playback, idEntity *owner, int flags, int numFrames ); + bool UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ); + void EndFrame( void ); + bool IsActive( void ) { return( !!mPlaybackDecl || !!mOldPlaybackDecl ); } + + const char *GetDestination( void ); + +// cnicholson: Begin Added save/restore functionality + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +// cnicholson: End Added save/restore functionality + +private: + int mLastTime; + int mTransitionTime; + + int mStartTime; + int mFlags; + const rvDeclPlayback *mPlaybackDecl; + idVec3 mOffset; + + int mOldStartTime; + int mOldFlags; + const rvDeclPlayback *mOldPlaybackDecl; + idVec3 mOldOffset; +}; + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +const float AI_TURN_SCALE = 60.0f; +const float AI_SEEK_PREDICTION = 0.3f; +const float AI_FLY_DAMPENING = 0.15f; +const float AI_HEARING_RANGE = 2048.0f; +const float AI_COVER_MINRANGE = 4.0f; +const float AI_PAIN_LOOP_DELAY = 200; +const int DEFAULT_FLY_OFFSET = 68.0f; + +#define ATTACK_IGNORE 0 +#define ATTACK_ON_DAMAGE BIT(0) +#define ATTACK_ON_ACTIVATE BIT(1) +#define ATTACK_ON_SIGHT BIT(2) + +// defined in script/ai_base.script. please keep them up to date. + +#define DI_NODIR -1 + + +// +// events +// +extern const idEventDef AI_DirectDamage; +extern const idEventDef AI_JumpFrame; +extern const idEventDef AI_EnableClip; +extern const idEventDef AI_DisableClip; +extern const idEventDef AI_EnableGravity; +extern const idEventDef AI_DisableGravity; +extern const idEventDef AI_EnablePain; +extern const idEventDef AI_DisablePain; +extern const idEventDef AI_EnableTarget; +extern const idEventDef AI_DisableTarget; +extern const idEventDef AI_EnableMovement; +extern const idEventDef AI_DisableMovement; +extern const idEventDef AI_Vagary_ChooseObjectToThrow; +extern const idEventDef AI_Speak; +extern const idEventDef AI_SpeakRandom; +extern const idEventDef AI_Attack; +extern const idEventDef AI_AttackMelee; +extern const idEventDef AI_WaitMove; +extern const idEventDef AI_EnableDamage; +extern const idEventDef AI_DisableDamage; +extern const idEventDef AI_LockEnemyOrigin; +extern const idEventDef AI_SetEnemy; +extern const idEventDef AI_ScriptedAnim; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; +extern const idEventDef AI_SetScript; +extern const idEventDef AI_BecomeSolid; +extern const idEventDef AI_BecomePassive; +extern const idEventDef AI_BecomeAggressive; +extern const idEventDef AI_SetHealth; +extern const idEventDef AI_TakeDamage; +extern const idEventDef AI_EnableBlink; +extern const idEventDef AI_DisableBlink; +extern const idEventDef AI_EnableAutoBlink; +extern const idEventDef AI_DisableAutoBlink; + +class idPathCorner; +class idProjectile; +class rvSpawner; +class rvAIHelper; +class rvAITether; + +class idAI : public idActor { +friend class rvAIManager; +friend class idAASFindAttackPosition; +public: + CLASS_PROTOTYPE( idAI ); + + idAI(); + ~idAI(); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Spawn ( void ); + virtual void TalkTo ( idActor *actor ); + + idEntity* GetEnemy ( void ) const; + idEntity* GetGoalEntity ( void ) const; + talkState_t GetTalkState ( void ) const; + const idVec2& GetAttackRange ( void ) const; + const idVec2& GetFollowRange ( void ) const; + int GetTravelFlags ( void ) const; + + void TouchedByFlashlight ( idActor *flashlight_owner ); + + idEntity * FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr = 0.0f ); + void SetSpawner ( rvSpawner* _spawner ); + rvSpawner* GetSpawner ( void ); + + idActor* GetLeader ( void ) const; + + // Outputs a list of all monsters to the console. + static void List_f( const idCmdArgs &args ); + + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + bool IsEnemyVisible ( void ) const; + bool InCoverMode ( void ) const; + bool InCrouchCoverMode ( void ) const; + bool LookAtCoverTall ( void ) const; + bool InLookAtCoverMode ( void ) const; + bool IsBehindCover ( void ) const; + bool IsLipSyncing ( void ) const; + bool IsSpeaking ( void ) const; + bool IsFacingEnt ( idEntity* targetEnt ); + bool IsCoverValid ( void ) const; + virtual bool IsCrouching ( void ) const; + + +public: + + idLinkList simpleThinkNode; + + // navigation + idAAS* aas; + idAASCallback* aasFind; + + // movement + idMoveState move; + idMoveState savedMove; + + // physics + idPhysics_Monster physicsObj; + + // weapon/attack vars + bool lastHitCheckResult; + int lastHitCheckTime; + int lastAttackTime; + float projectile_height_to_distance_ratio; // calculates the maximum height a projectile can be thrown + idList attackAnimInfo; + + mutable idClipModel* projectileClipModel; + idEntityPtr projectile; + + // chatter/talking + int chatterTime; + int chatterRateCombat; + int chatterRateIdle; + talkState_t talkState; + idEntityPtr talkTarget; + talkMessage_t talkMessage; + int talkBusyCount; + int speakTime; + + // Focus + idEntityPtr lookTarget; + aiFocus_t focusType; + idEntityPtr focusEntity; + float focusRange; + int focusAlignTime; + int focusTime; + idVec3 currentFocusPos; + + // Looking + bool allowJointMod; + int alignHeadTime; + int forceAlignHeadTime; + idAngles eyeAng; + idAngles lookAng; + idAngles destLookAng; + idAngles lookMin; + idAngles lookMax; + idList lookJoints; + idList lookJointAngles; + float eyeVerticalOffset; + float eyeHorizontalOffset; + float headFocusRate; + float eyeFocusRate; + + // joint controllers + idAngles eyeMin; + idAngles eyeMax; + jointHandle_t orientationJoint; + + idEntityPtr pusher; + idEntityPtr scriptedActionEnt; + + // script variables + struct aiFlags_s { + bool awake :1; // set to false until state_wakeup is called. + bool damage :1; + bool pain :1; + bool dead :1; + bool activated :1; + bool jump :1; + bool hitEnemy :1; + bool pushed :1; + bool disableAttacks :1; + bool scriptedEndWithIdle :1; + bool scriptedNeverDormant :1; // Prevent going dormant while in scripted sequence + bool scripted :1; + bool simpleThink :1; + bool ignoreFlashlight :1; + bool action :1; + bool lookAtPlayer :1; + bool disableLook :1; + bool undying :1; + bool tetherMover :1; // Currently using a dynamic tether to a mover + bool meleeSuperhero :1; + bool killerGuard :1; // Do 100 points of damage with each hit + } aifl; + + // + // ai/ai.cpp + // + void SetAAS ( void ); + virtual void DormantBegin ( void ); // called when entity becomes dormant + virtual void DormantEnd ( void ); // called when entity wakes from being dormant + virtual void Think ( void ); + void Activate ( idEntity *activator ); + virtual void Hide ( void ); + virtual void Show ( void ); + virtual void AdjustHealthByDamage ( int inDamage ); + void CalculateAttackOffsets ( void ); + + void InitNonPersistentSpawnArgs ( void ); + + /* + =============================================================================== + Speaking & Chatter + =============================================================================== + */ +public: + + bool Speak ( const char *speechDecl, bool random = false ); + void StopSpeaking ( bool stopAnims ); + virtual void CheckBlink ( void ); + +protected: + + virtual bool CanPlayChatterSounds ( void ) const; + void UpdateChatter ( void ); + + + /* + =============================================================================== + Movement + =============================================================================== + */ + + // static helper functions +public: + // Finds a path around dynamic obstacles. + static bool FindPathAroundObstacles ( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ); + // Frees any nodes used for the dynamic obstacle avoidance. + static void FreeObstacleAvoidanceNodes ( void ); + // Predicts movement, returns true if a stop event was triggered. + static bool PredictPath ( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore = NULL ); + // Return true if the trajectory of the clip model is collision free. + static bool TestTrajectory ( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ); + // Finds the best collision free trajectory for a clip model. + static bool PredictTrajectory ( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ); + + + // special flying code + void AdjustFlyingAngles ( void ); + void AddFlyBob ( idVec3 &vel ); + void AdjustFlyHeight ( idVec3 &vel, const idVec3 &goalPos ); + void FlySeekGoal ( idVec3 &vel, idVec3 &goalPos ); + void AdjustFlySpeed ( idVec3 &vel ); + void FlyTurn ( void ); + + // movement types + void Move ( void ); + virtual void DeadMove ( void ); + void AnimMove ( void ); + void SlideMove ( void ); + void PlaybackMove ( void ); + void FlyMove ( void ); + void StaticMove ( void ); + void RVMasterMove ( void ); + void SetMoveType ( moveType_t moveType ); + //twhitaker: added custom move type + virtual void CustomMove ( void ); + + // movement actions + void KickObstacles ( const idVec3 &dir, float force, idEntity *alwaysKick ); + + // steering + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + void GetAnimMoveDelta ( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ); + void CheckObstacleAvoidance ( const idVec3 &goalPos, idVec3 &newPos, idReachability* goalReach=0 ); + bool GetMovePos ( idVec3 &seekPos, idReachability** seekReach=0 ); + + + // navigation + float TravelDistance ( const idVec3 &end ) const; + float TravelDistance ( const idVec3 &start, const idVec3 &end ) const; + float TravelDistance ( idEntity* ent ) const; + float TravelDistance ( idEntity* start, idEntity* end ) const; + int PointReachableAreaNum ( const idVec3 &pos, const float boundsScale = 2.0f ) const; + bool PathToGoal ( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + void BlockedFailSafe ( void ); + + // turning + void Turn ( void ); + bool TurnToward ( float yaw ); + bool TurnToward ( const idVec3 &pos ); + bool TurnTowardLeader ( bool faceLeaderByDefault=false ); + bool FacingIdeal ( void ); + bool DirectionalTurnToward ( const idVec3 &pos ); + + // movement control + bool FaceEnemy ( void ); + bool FaceEntity ( idEntity *ent ); + bool SlideToPosition ( const idVec3 &pos, float time ); + bool WanderAround ( void ); + bool StepDirection ( float dir ); + bool NewWanderDir ( const idVec3 &dest ); + + + /* + =============================================================================== + Reactions + =============================================================================== + */ + void ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ); + void ReactToPain ( idEntity* attacker, int damage ); + + /* + =============================================================================== + AI helpers + =============================================================================== + */ + +public: + + void UpdateHelper ( void ); + rvAIHelper* GetActiveHelper ( void ); + + /* + =============================================================================== + Sensory Perception + =============================================================================== + */ + +public: + + const idVec3& LastKnownPosition ( const idEntity *ent ); + const idVec3& LastKnownFacing ( const idEntity *ent ); + idEntity * HeardSound ( int ignore_team ); + int ReactionTo ( const idEntity *ent ); + void SetLastVisibleEnemyTime ( int time=-1/* DEFAULT IS CURRENT TIME*/ ); + bool IsEnemyRecentlyVisible ( float maxLostVisTimeScale = 1.0f ) const; + + /* + =============================================================================== + Passive + =============================================================================== + */ + +public: + + void SetTalkState ( talkState_t state ); + void SetPassivePrefix ( const char* prefix ); + +protected: + + bool GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ); + + /* + =============================================================================== + Combat + =============================================================================== + */ + +public: + + // enemy managment + bool SetEnemy ( idEntity *newEnemy ); + void ClearEnemy ( bool dead = false ); + + void UpdateEnemy ( void ); + void UpdateEnemyPosition ( bool force = true ); + void UpdateEnemyVisibility ( void ); + + // Attack direction + bool GetAimDir ( const idVec3& source, const idEntity* aimAtEnt, const idDict* projectileDict, idEntity *ignore, idVec3 &aimDir, float aimOffset, float predict ) const; + void GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const; + + // damage + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool CheckDeathCausesMissionFailure ( void ); + + // attacks + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ); + virtual bool AttackMelee ( const char* attackName, const idDict* meleeDict ); + + void CreateProjectileClipModel ( void ) const; + idProjectile* CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ); + void RemoveProjectile ( void ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + void DirectDamage ( const char *meleeDefName, idEntity *ent ); + bool TestMelee ( void ) const; + void PushWithAF ( void ); + bool IsMeleeNeeded ( void ); + + // special effects + void GetMuzzle ( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ); + void LerpLookAngles ( idAngles &curAngles, idAngles newAngles, float orientationJointYaw, float focusRate ); + virtual bool UpdateAnimationControllers ( void ); + + // AI script state management + void UpdateStates ( void ); + void UpdateFocus ( const idMat3& orientationAxis ); + void SetFocus ( aiFocus_t focus, int time ); + + // event? + virtual void FootStep ( void ); + + void CreateMissile ( jointHandle_t joint ); + void RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void BecomeSolid ( void ); + void BecomeNonSolid ( void ); + const char * ChooseAnim ( int channel, const char *animname ); + + // + // ai/ai_events.cpp + // +public: + + + virtual bool CanTakeDamage ( void ) const; + virtual bool CanTakePain ( void ) const; + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual bool CanAnnounce ( float chance ) const; + + virtual bool SkipCurrentDestination ( void ) const; + +// ----------------------------- Functions ------------------------------------ + + virtual bool DoDormantTests ( void ); + + void OverrideFlag ( aiFlagOverride_t flag, bool value ); + void RestoreFlag ( aiFlagOverride_t flag ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual const char* GetIdleAnimName ( void ); + + bool RespondToFlashlight ( void ) { return !aifl.ignoreFlashlight;} + bool ForceFaceEnemy ( void ) { return ( move.moveCommand == MOVE_TO_ENEMY ); } + + bool CanBecomeSolid ( void ); + bool CanHitEnemyFromAnim ( int animNum, idVec3 offset = vec3_origin ); + bool CanHitEnemy ( void ); + bool CanHitEnemyFromJoint ( const char *jointname ); + + float GetTurnDelta ( void ); + + int TestTrajectory ( const idVec3 &firePos, const idVec3 &target, const char *projectileName ); + bool TestAnimMove ( int animNum, idEntity *ignore = NULL, idVec3 *pMoveVec = NULL ); + void ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm = NULL ); + void SetLeader ( idEntity *newLeader ); + + int CheckMelee ( bool disableAttack ); + bool CheckForEnemy ( bool useFov, bool force = false ); + bool CheckForCloserEnemy ( void ); + bool CheckForReplaceEnemy ( idEntity* replacement ); + bool CheckForTeammateEnemy ( void ); + + void DrawSuspicion ( void ); + float RateSuspiciousness ( idActor* shady, bool rateSound = false ); + void RateSuspicionLevel ( void ); + + void UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ); + + void LookAtEntity ( idEntity *ent, float duration ); + +// ----------------------------- Variables ------------------------------------ + + int actionAnimNum; // Index of animation to use for the upcoming action + int actionTime; // Time line for actions (time is stopped when an action is running) + int actionSkipTime; // Time to use if an action is skipped by another + + int flagOverrides; + + float announceRate; // How often (0 - 1.0f) the AI will make certain announcements. + + rvAICombat_t combat; // Members related to combat state + rvAIPassive_t passive; // Members related to passive state + rvAIEnemy_t enemy; // Members related to tracking enemies + rvAIPain_t pain; + rvAIFuncs_t funcs; + + rvPlaybackDriver mPlayback; + rvPlaybackDriver mLookPlayback; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr tether; + idEntityPtr helperCurrent; + idEntityPtr helperIdeal; + idEntityPtr leader; + idEntityPtr spawner; + + bool ValidateCover ( void ); + + virtual bool UpdateRunStatus ( void ); + bool UpdateTactical ( int delay = 0 ); + void ForceTacticalUpdate ( void ); + bool UpdateTactical_r ( void ); + virtual int FilterTactical ( int availableTactical ); + void WakeUpTargets ( void ); + + virtual aiCTResult_t CheckTactical ( aiTactical_t tactical ); + + void Begin ( void ); + void WakeUp ( void ); + + virtual void Prethink ( void ); + virtual void Postthink ( void ); + + /* + =============================================================================== + Threat Management + =============================================================================== + */ + +protected: + + virtual void UpdateThreat ( void ); + virtual float CalculateEnemyThreat ( idEntity* enemy ); + + /* + =============================================================================== + Tethers + =============================================================================== + */ + +public: + + virtual bool IsTethered ( void ) const; + bool IsWithinTether ( void ) const; + rvAITether* GetTether ( void ); + virtual void SetTether ( rvAITether* newTether ); + + /* + =============================================================================== + Scripting + =============================================================================== + */ + +public: + + void ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void ScriptedStop ( void ); + + void SetScript ( const char* scriptName, const char* funcName ); + +private: + + bool ScriptedBegin ( bool endWithIdle, bool allowDormant = false ); + void ScriptedEnd ( void ); + + /* + =============================================================================== + Handlers + =============================================================================== + */ + +protected: + + virtual void OnDeath ( void ); + virtual void OnStateChange ( int channel ); + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnLeaderChange ( idEntity* oldLeader ); + virtual void OnStartMoving ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnFriendlyFire ( idActor* attacker ); + virtual void OnWakeUp ( void ); + virtual void OnTouch ( idEntity *other, trace_t *trace ); + virtual void OnCoverInvalidated ( void ); + virtual void OnCoverNotFacingEnemy ( void ); + virtual void OnEnemyVisiblityChange ( bool oldVisible ); + virtual void OnStartAction ( void ); + virtual void OnStopAction ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + /* + =============================================================================== + Movement / Turning + =============================================================================== + */ + +protected: + + bool StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ); + void StopMove ( moveStatus_t status ); + + bool MoveTo ( const idVec3 &pos, float range = 0.0f ); + bool MoveToAttack ( idEntity *ent, int attack_anim ); + bool MoveToTether ( rvAITether* tether ); + virtual bool MoveToEnemy ( void ); + bool MoveToEntity ( idEntity *ent, float range = 0.0f ); + bool MoveToCover ( float minRange, float maxRange, aiTactical_t coverType ); + bool MoveToHide ( void ); + + bool MoveOutOfRange ( idEntity *entity, float range, float minRange=0.0f ); + + void AnimTurn ( float angles, bool force ); + + bool ReachedPos ( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range = 0.0f ) const; + + /* + =============================================================================== + Debug + =============================================================================== + */ + +public: + + void DrawRoute ( void ) const; + void DrawTactical ( void ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + +public: + + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + void AnnounceNewEnemy ( void ); + void AnnounceKill ( idActor *victim ); + void AnnounceTactical ( aiTactical_t newTactical ); + void AnnounceSuppressed ( idActor *suppressor ); + void AnnounceSuppressing ( void ); + void AnnounceFlinch ( idEntity *attacker ); + void AnnounceInjured ( void ); + void AnnounceFriendlyFire ( idActor* attacker ); + void AnnounceGrenade ( void ); + void AnnounceGrenadeThrow ( void ); + + /* + =============================================================================== + Actions + =============================================================================== + */ + +protected: + + rvAIActionTimer actionTimerRangedAttack; + rvAIActionTimer actionTimerEvade; + rvAIActionTimer actionTimerSpecialAttack; + rvAIActionTimer actionTimerPain; + + rvAIAction actionEvadeLeft; + rvAIAction actionEvadeRight; + rvAIAction actionRangedAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionLeapAttack; + rvAIAction actionJumpBack; + + bool UpdateAction ( void ); + virtual bool CheckActions ( void ); + virtual bool CheckPainActions ( void ); + + bool PerformAction ( rvAIAction* action, bool (idAI::*)(rvAIAction*,int), rvAIActionTimer* timer = NULL ); + void PerformAction ( const char* stateName, int blendFrames = 0, bool noPain = false ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + virtual void Event_PostSpawn ( void ); + // RAVEN END + +public: + + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + + /* + =============================================================================== + Events + =============================================================================== + */ + +private: + + // Orphaned events + void Event_ClosestReachableEnemyOfEntity ( idEntity *team_mate ); + void Event_GetReachableEntityPosition ( idEntity *ent ); + void Event_EntityInAttackCone ( idEntity *ent ); + void Event_TestAnimMoveTowardEnemy ( const char *animname ); + void Event_TestAnimMove ( const char *animname ); + void Event_TestMoveToPosition ( const idVec3 &position ); + void Event_TestMeleeAttack ( void ); + void Event_TestAnimAttack ( const char *animname ); + void Event_SaveMove ( void ); + void Event_RestoreMove ( void ); + void Event_ThrowMoveable ( void ); + void Event_ThrowAF ( void ); + void Event_PredictEnemyPos ( float time ); + void Event_FindActorsInBounds ( const idVec3 &mins, const idVec3 &maxs ); + + void Event_Activate ( idEntity *activator ); + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_LookAt ( idEntity* lookAt ); + + void Event_SetAngles ( idAngles const &ang ); + void Event_SetEnemy ( idEntity *ent ); + void Event_SetHealth ( float newHealth ); + void Event_SetTalkTarget ( idEntity *target ); + void Event_SetTalkState ( int state ); + void Event_SetLeader ( idEntity *newLeader ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetMoveSpeed ( int speed ); + void Event_SetPassivePrefix ( const char* prefix ); + + void Event_GetAngles ( void ); + void Event_GetEnemy ( void ); + void Event_GetLeader ( void ); + + void Event_Attack ( const char* attackName, const char* jointName ); + void Event_AttackMelee ( const char *meleeDefName ); + + void Event_DirectDamage ( idEntity *damageTarget, const char *damageDefName ); + void Event_RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void Event_CanBecomeSolid ( void ); + void Event_BecomeSolid ( void ); + void Event_BecomeNonSolid ( void ); + void Event_BecomeRagdoll ( void ); + void Event_StopRagdoll ( void ); + void Event_FaceEnemy ( void ); + void Event_FaceEntity ( idEntity *ent ); + void Event_WaitMove ( void ); + + void Event_BecomePassive ( int ignoreEnemies ); + void Event_BecomeAggressive ( void ); + + void Event_EnableDamage ( void ); + void Event_EnableClip ( void ); + void Event_EnableGravity ( void ); + void Event_EnableAFPush ( void ); + void Event_EnablePain ( void ); + void Event_DisableDamage ( void ); + void Event_DisableClip ( void ); + void Event_DisableGravity ( void ); + void Event_DisableAFPush ( void ); + void Event_DisablePain ( void ); + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + void Event_TakeDamage ( float takeDamage ); + void Event_SetUndying ( float setUndying ); + void Event_EnableAutoBlink ( void ); + void Event_DisableAutoBlink ( void ); + + void Event_LockEnemyOrigin ( void ); + + void Event_StopThinking ( void ); + void Event_JumpFrame ( void ); + void Event_RealKill ( void ); + void Event_Kill ( void ); + void Event_RemoveUpdateSpawner ( void ); + void Event_AllowHiddenMovement ( int enable ); + void Event_CanReachPosition ( const idVec3 &pos ); + void Event_CanReachEntity ( idEntity *ent ); + void Event_CanReachEnemy ( void ); + + void Event_IsSpeaking ( void ); + void Event_IsTethered ( void ); + void Event_IsWithinTether ( void ); + void Event_IsMoving ( void ); + + void Event_Speak ( const char *speechDecl ); + void Event_SpeakRandom ( const char *speechDecl ); + + void Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void Event_ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_ScriptedJumpDown ( float yaw ); + + void Event_FindEnemy ( float distSquare ); + void Event_SetKey ( const char *key, const char *value ); + + + /* + =============================================================================== + States + =============================================================================== + */ + +protected: + + // Wait states + stateResult_t State_Wait_Activated ( const stateParms_t& parms ); + stateResult_t State_Wait_ScriptedDone ( const stateParms_t& parms ); + stateResult_t State_Wait_Action ( const stateParms_t& parms ); + stateResult_t State_Wait_ActionNoPain ( const stateParms_t& parms ); + + // Global states + stateResult_t State_WakeUp ( const stateParms_t& parms ); + stateResult_t State_TriggerAnim ( const stateParms_t& parms ); + stateResult_t State_Wander ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + stateResult_t State_LightningDeath ( const stateParms_t& parms ); + stateResult_t State_Burn ( const stateParms_t& parms ); + stateResult_t State_Remove ( const stateParms_t& parms ); + + stateResult_t State_Passive ( const stateParms_t& parms ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_CombatCover ( const stateParms_t& parms ); + stateResult_t State_CombatMelee ( const stateParms_t& parms ); + stateResult_t State_CombatRanged ( const stateParms_t& parms ); + stateResult_t State_CombatTurret ( const stateParms_t& parms ); + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + + stateResult_t State_MovePlayerPush ( const stateParms_t& parms ); + stateResult_t State_MoveTether ( const stateParms_t& parms ); + stateResult_t State_MoveFollow ( const stateParms_t& parms ); + + stateResult_t State_ScriptedMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + stateResult_t State_ScriptedStop ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackAim ( const stateParms_t& parms ); + stateResult_t State_ScriptedJumpDown ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_Sight ( const stateParms_t& parms ); + stateResult_t State_Torso_CustomCycle ( const stateParms_t& parms ); + stateResult_t State_Torso_Action ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishAction ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ScriptedAnim ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveIdle ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveFidget ( const stateParms_t& parms ); + + // Leg States + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnLeft ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnRight ( const stateParms_t& parms ); + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_Legs_MoveThink ( const stateParms_t& parms ); + stateResult_t State_Legs_ChangeDirection ( const stateParms_t& parms ); + + // Head states + stateResult_t State_Head_Idle ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( idAI ); +}; + +/* +=============================================================================== + + idAI Inlines + +=============================================================================== +*/ + +ID_INLINE int DelayTime( int min, int range ) { + return min + gameLocal.random.RandomInt ( range + 1 ); +} + +ID_INLINE const idVec2& idAI::GetAttackRange ( void ) const { + return combat.attackRange; +} + +ID_INLINE const idVec2& idAI::GetFollowRange ( void ) const { + return move.followRange; +} + +ID_INLINE int idAI::GetTravelFlags ( void ) const { + return move.travelFlags; +} + +ID_INLINE bool idAI::IsEnemyVisible ( void ) const { + return enemy.ent && enemy.fl.visible; +} + +ID_INLINE bool idAI::IsEnemyRecentlyVisible( float maxLostVisTimeScale ) const { + return (enemy.ent + && combat.fl.seenEnemyDirectly + && (enemy.lastVisibleTime && gameLocal.time-enemy.lastVisibleTime < (combat.maxLostVisTime * maxLostVisTimeScale))); +} + +ID_INLINE bool idAI::LookAtCoverTall( void ) const { + return ( aasSensor->Look() + && (aasSensor->Look()->flags&FEATURE_LOOK_OVER) + && aasSensor->Look()->height > 40.0f ); +} + +ID_INLINE bool idAI::InLookAtCoverMode ( void ) const { + return (!IsBehindCover() + && !aifl.action + && move.fl.moving + && (combat.fl.alert || combat.fl.aware) + && aasSensor->Look() + && combat.tacticalCurrent != AITACTICAL_MELEE + && combat.tacticalCurrent != AITACTICAL_HIDE + && !IsEnemyRecentlyVisible(0.2f)); +} + +ID_INLINE bool idAI::InCoverMode ( void ) const { + return ( (1<Reserved ( ); +} + +ID_INLINE bool idAI::InCrouchCoverMode ( void ) const { + return ( InCoverMode() && (aasSensor->Reserved()->flags&FEATURE_LOOK_OVER) ); +} + +ID_INLINE bool idAI::IsBehindCover ( void ) const { + return ( InCoverMode() && move.fl.done && (aifl.action || DistanceTo2d ( aasSensor->ReservedOrigin() ) < AI_COVER_MINRANGE) ); +} + +ID_INLINE bool idAI::IsSpeaking ( void ) const { + return speakTime && gameLocal.time < speakTime; +} + +ID_INLINE bool idAI::IsFacingEnt ( idEntity* targetEnt ) { + return( move.moveCommand == MOVE_FACE_ENTITY && move.goalEntity == targetEnt && FacingIdeal() ); +} + +ID_INLINE bool idAI::IsCoverValid ( ) const { + return combat.coverValidTime && (gameLocal.time - combat.coverValidTime < combat.maxInvalidCoverTime); +} + +ID_INLINE idActor* idAI::GetLeader ( void ) const { + return leader; +} + +ID_INLINE idEntity* idAI::GetGoalEntity ( void ) const { + return move.goalEntity; +} + +ID_INLINE bool idAI::CanTakeDamage( void ) const { + return idActor::CanTakeDamage( ); +} + +ID_INLINE bool idAI::CanTakePain ( void ) const { + return !disablePain; +} + +ID_INLINE bool idAI::CanTurn ( void ) const { + return move.turnRate && !move.fl.noTurn && !move.fl.disabled; +} + +ID_INLINE bool idAI::CanMove ( void ) const { + return !move.fl.disabled && !move.fl.blocked && gameLocal.GetTime()>move.blockTime; +} + +ID_INLINE bool idAI::CanAnnounce ( float chance ) const { + return !aifl.dead && !IsSpeaking ( ) && !af.IsActive ( ) && !combat.fl.noChatter && ( gameLocal.random.RandomFloat() < chance ); +} + +ID_INLINE void idAI::SetFocus ( aiFocus_t focus, int time ) { + focusType = focus; + focusTime = gameLocal.time + time; +} + +ID_INLINE idEntity *idAI::GetEnemy( void ) const { + return enemy.ent; +} + +ID_INLINE void idAI::ForceTacticalUpdate ( void ) { + combat.tacticalUpdateTime = 0; + combat.tacticalMaskUpdate = 0; + delete aasFind; + aasFind = NULL; +} + +/* +=============================================================================== + + externs + +=============================================================================== +*/ + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; +extern const char* aiTalkMessageString [ ]; +extern const char* aiTacticalString [ AITACTICAL_MAX ]; +extern const char* aiMoveCommandString [ NUM_MOVE_COMMANDS ]; +extern const char* aiFocusString [ AIFOCUS_MAX ]; + +#endif /* !__AI_H__ */ + diff --git a/source/mpgame/ai/AI_Actions.cpp b/source/mpgame/ai/AI_Actions.cpp new file mode 100644 index 0000000..a1f6883 --- /dev/null +++ b/source/mpgame/ai/AI_Actions.cpp @@ -0,0 +1,592 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + + rvAIActionTimer + +=============================================================================== +*/ + +/* +================ +rvAIActionTimer::rvAIActionTimer +================ +*/ +rvAIActionTimer::rvAIActionTimer ( void ) { + time = 0; +} + +/* +================ +rvAIActionTimer::Init +================ +*/ +bool rvAIActionTimer::Init ( const idDict& args, const char* name ) { + rate = SEC2MS ( args.GetFloat ( va("%s_rate",name), "0" ) ); + return true; +} + +/* +================ +rvAIActionTimer::Save +================ +*/ +void rvAIActionTimer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( rate ); + savefile->WriteInt ( time ); +} + +/* +================ +rvAIActionTimer::Restore +================ +*/ +void rvAIActionTimer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( rate ); + savefile->ReadInt ( time ); +} + +/* +================ +rvAIActionTimer::Reset +================ +*/ +void rvAIActionTimer::Reset ( int currentTime, float diversity, float scale ) { + float _rate = rate * scale; + time = currentTime + (-gameLocal.random.RandomInt( 2.0f * _rate * diversity ) + _rate * diversity + _rate); +} + +/* +================ +rvAIActionTimer::Clear +================ +*/ +void rvAIActionTimer::Clear ( int currentTime ) { + time = currentTime; +} + +/* +================ +rvAIActionTimer::Add +================ +*/ +void rvAIActionTimer::Add ( int _time, float diversity ) { + time += (-gameLocal.random.RandomInt( 2.0f * _time * diversity ) + _time * diversity + _time); +} + +/* +=============================================================================== + + rvAIAction + +=============================================================================== +*/ + +/* +================ +rvAIAction::rvAIAction +================ +*/ +rvAIAction::rvAIAction ( void ) { + memset ( &fl, 0, sizeof(fl) ); + status = STATUS_UNUSED; +} + +/* +================ +rvAIAction::Init +================ +*/ +bool rvAIAction::Init ( const idDict& args, const char* name, const char* defaultState, int _flags ) { + const idKeyValue* kv; + + if ( _flags & AIACTIONF_ATTACK ) { + fl.isAttack = true; + } + if ( _flags & AIACTIONF_MELEE ) { + fl.isMelee = true; + } + + // Initialize timer + timer.Init ( args, name ); + + // Is this action enabled? + fl.disabled = !args.GetBool ( va("%s",name), "0" ); + fl.noPain = args.GetBool ( va("%s_nopain",name), "0" ); + fl.noTurn = args.GetBool ( va("%s_noturn",name), "1" ); + fl.overrideLegs = args.GetBool ( va("%s_overrideLegs",name), "1" ); + fl.noSimpleThink = args.GetBool ( va("%s_nosimplethink",name), "0" ); + + blendFrames = args.GetInt ( va("%s_blendFrames",name), "4" ); + failRate = SEC2MS ( args.GetInt ( va("%s_failRate",name), ".1" ) ); + + minRange = args.GetInt ( va("%s_minRange",name), "0" ); + maxRange = args.GetInt ( va("%s_maxRange",name), (_flags & AIACTIONF_ATTACK ) ? "-1" : "0" ); + minRange2d = args.GetInt ( va("%s_minRange2d",name), "0" ); + maxRange2d = args.GetInt ( va("%s_maxRange2d",name), "0" ); + + chance = args.GetFloat ( va("%s_chance",name), "1" ); + diversity = args.GetFloat ( va("%s_diversity", name ), ".5" ); + + // action state + state = args.GetString ( va("%s_state",name), (!defaultState||!*defaultState) ? "Torso_Action" : defaultState ); + + // allow for multiple animations + const char* prefix = va("%s_anim",name); + for ( kv = args.MatchPrefix ( prefix, NULL ); kv; kv = args.MatchPrefix ( prefix, kv ) ) { + if ( kv->GetValue ( ).Length ( ) ) { + anims.Append ( kv->GetValue ( ) ); + } + } + + return true; +} + +/* +================ +rvAIAction::Save +================ +*/ +void rvAIAction::Save ( idSaveGame *savefile ) const { + int i; + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteInt ( anims.Num ( ) ); + for ( i = 0; i < anims.Num(); i ++ ) { + savefile->WriteString ( anims[i] ); + } + savefile->WriteString ( state ); + + timer.Save ( savefile ); + + savefile->WriteInt ( blendFrames ); + savefile->WriteInt ( failRate ); + + savefile->WriteFloat ( minRange ); + savefile->WriteFloat ( maxRange ); + savefile->WriteFloat ( minRange2d ); + savefile->WriteFloat ( maxRange2d ); + + savefile->WriteFloat ( chance ); + savefile->WriteFloat ( diversity ); + + savefile->WriteInt ( (int)status ); +} + +/* +================ +rvAIAction::Restore +================ +*/ +void rvAIAction::Restore ( idRestoreGame *savefile ) { + int num; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadInt ( num ); + anims.Clear ( ); + anims.SetNum ( num ); + for ( num--; num >= 0; num -- ) { + savefile->ReadString ( anims[num] ); + } + savefile->ReadString ( state ); + + timer.Restore ( savefile ); + + savefile->ReadInt ( blendFrames ); + savefile->ReadInt ( failRate ); + + savefile->ReadFloat ( minRange ); + savefile->ReadFloat ( maxRange ); + savefile->ReadFloat ( minRange2d ); + savefile->ReadFloat ( maxRange2d ); + + savefile->ReadFloat ( chance ); + savefile->ReadFloat ( diversity ); + + savefile->ReadInt ( (int&)status ); +} + +/* +=============================================================================== + + Actions + +=============================================================================== +*/ + +/* +================ +idAI::CheckAction_EvadeLeft +================ +*/ +bool idAI::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + // TODO: dont evade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_EvadeRight +================ +*/ +bool idAI::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_JumpBack +================ +*/ +bool idAI::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage ) { + return false; + } + // TODO: enemy must be in front to jump backwards + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_RangedAttack +================ +*/ +bool idAI::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_MeleeAttack +================ +*/ +bool idAI::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_LeapAttack +================ +*/ +bool idAI::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( enemy.range > 64.0f && !TestAnimMove ( animNum, enemy.ent ) ) { + return false; + } + // Must be looking right at the enemy to leap + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 4 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::UpdateAction +================ +*/ +bool idAI::UpdateAction ( void ) { + // Update action MUST be called from the main state loop + assert ( stateThread.IsExecuting ( ) ); + + // If an action is already running then dont let another start + if ( aifl.action ) { + return false; + } + + return CheckActions ( ); +} + +/* +================ +idAI::CheckPainActions +================ +*/ +bool idAI::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + return false; + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +idAI::CheckActions +================ +*/ +bool idAI::CheckActions ( void ) { + + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // Actions are limited at a cover position to shooting and leaning + if ( IsBehindCover ( ) ) { + // Test ranged attack first + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +idAI::PerformAction +================ +*/ +void idAI::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + if ( legsAnim.Disabled() ) { + //MCG: Hmmm... I hope this doesn't break anything, but if an action happens *right* + // at the end of a trigger_anim, then the legs will be enabled (by the SetAnimState + // on the torso) with no state! The actor will then be stuck in place until + // something actually sets the legsAnim state... so let's check for disabled and + // set a default state right here...? + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + } + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + + // Main state will wait until action is finished before continuing + if ( noPain ) { + InterruptState ( "Wait_ActionNoPain" ); + } else { + InterruptState ( "Wait_Action" ); + } + + OnStartAction ( ); +} + +bool idAI::PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + // If we arent ignoring simple think then dont perform this action on a simple think frame + if ( !action->fl.noSimpleThink && aifl.simpleThink ) { + return false; + } + + // Is the action disabled? + if ( action->fl.disabled ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + + // Action timers still running? + if ( !action->timer.IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_TIMER; + return false; + } + + if ( timer && !timer->IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_EXTERNALTIMER; + return false; + } + + // Special code for attacks + if ( action->fl.isAttack ) { + // Attacks disabled? + if ( ai_disableAttacks.GetBool() ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + // No attack actions if we have no enemy or our enemy cant be hurt + if ( !enemy.ent || enemy.ent->health <= 0 ) { + action->status = rvAIAction::STATUS_FAIL_NOENEMY; + return false; + } + } + + // Min Range check + if ( action->minRange ) { + if ( !enemy.ent || !enemy.range || enemy.range < action->minRange ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + if ( action->minRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d < action->minRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + + // Max Range check + if ( action->maxRange != 0 ) { + float maxrange = action->maxRange == -1 ? combat.attackRange[1] : action->maxRange; + if ( !enemy.ent || !enemy.range || enemy.range > maxrange ) { + if ( action->fl.isMelee && GetEnemy() ) { + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = GetEnemy()->GetPhysics()->GetOrigin(); + idBounds enemyBounds = GetEnemy()->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } else { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + } + if ( action->maxRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d > action->maxRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + + int animNum; + if ( action->anims.Num ( ) ) { + // Pick a random animation from the list + animNum = GetAnim ( ANIMCHANNEL_TORSO, action->anims[gameLocal.random.RandomInt(action->anims.Num())] ); + if ( !animNum ) { + action->status = rvAIAction::STATUS_FAIL_ANIM; + return false; + } + } else { + animNum = -1; + } + + // Random chance? + if ( action->chance < 1.0f && gameLocal.random.RandomFloat ( ) > action->chance ) { + action->status = rvAIAction::STATUS_FAIL_CHANCE; + action->timer.Clear ( actionTime ); + action->timer.Add ( 100 ); + return false; + } + + // Check the condition + if ( condition && !(this->*(condition)) ( action, animNum ) ) { + action->status = rvAIAction::STATUS_FAIL_CONDITION; + action->timer.Clear ( actionTime ); + action->timer.Add ( action->failRate ); + return false; + } + + // Disallow turning during action? + if ( action->fl.noTurn ) { + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + } + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + // Override legs for this state? + if ( action->fl.overrideLegs ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + + // When attacking scale the time by the aggression scale + float scale; + if ( action->fl.isAttack ) { + scale = 2.0f - combat.aggressiveScale; + } else { + scale = 1.0f; + } + + // Restart the action timer using the length of the animation being played + action->timer.Reset ( actionTime, action->diversity, scale ); + + // If the action gets interrupted it will be skipped, if it is then move the action timers forward + // by half of its normal delay to allow it to be performed again quicker than usual. This also allows + // other actions that may be still pending to be performed and thus cause less of a pause after taking pain. + actionSkipTime = (action->timer.GetTime ( ) + actionTime) / 2; + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + + action->status = rvAIAction::STATUS_OK; + + actionAnimNum = animNum; + + return true; +} diff --git a/source/mpgame/ai/AI_Announcements.cpp b/source/mpgame/ai/AI_Announcements.cpp new file mode 100644 index 0000000..e68f943 --- /dev/null +++ b/source/mpgame/ai/AI_Announcements.cpp @@ -0,0 +1,554 @@ +/* +================ + +AI_Announcements.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +/* +===================== +idAI::AnnounceNewEnemy + +Announce that we are going to shooting at a different enemy +===================== +*/ +void idAI::AnnounceNewEnemy( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + if ( !enemy.ent || !enemy.ent->IsType( idActor::GetClassType() ) ) { + return; + } + + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY ) ) { + return; + } + + // Check to see if we should announce the new enemy as a sniper + // jshepard: Disabled by request + /* + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER ) && enemy.ent->spawnArgs.GetBool ( "sniper" ) ) { + // Should we announce the sniper as a high up sniper? + if ( (enemy.lastKnownPosition - physicsObj.GetOrigin()) * physicsObj.GetGravityNormal() >= 250.0f ) { + if ( Speak ( "lipsync_high_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + return; + } + + // Just announce the sniper + if ( Speak ( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + */ + + idActor* newEnemyAct = static_cast(enemy.ent.GetEntity()); + bool result = false; + + //first see if the new enemy is behind our buddy closest to the enemy + idActor* teammate = aiManager.NearestTeammateToPoint( this, newEnemyAct->GetPhysics()->GetOrigin(), false, 200.0f ); + if ( teammate ) { + if ( aiManager.ActorIsBehindActor( newEnemyAct, teammate ) ) { + result = Speak ( "lipsync_enemy_back", true ); + } + } + + // If we havent spoken yet then find a teammat near us that we can tell about our enemy and + // determine the best announcement to make based on their location + if ( !result ) { + teammate = aiManager.NearestTeammateToPoint( this, GetPhysics()->GetOrigin(), false, 300 ); + if ( teammate ) { + if ( newEnemyAct->GetPhysics()->GetOrigin().z-GetPhysics()->GetOrigin().z >= 250.0f ) { + result = Speak ( "lipsync_enemy_high", true ); + } + + // IF we still havent spoken and we're talking to the player, give him directional info + if ( !result && teammate->IsType( idPlayer::GetClassType() ) ) { + idPlayer* teamPlayer = static_cast(teammate); + idVec3 dir = newEnemyAct->GetPhysics()->GetOrigin()-teamPlayer->GetPhysics()->GetOrigin(); + dir.z = 0; + dir.NormalizeFast(); + idVec3 fwd = teamPlayer->viewAxis[0]; + fwd.z = 0.0f; + fwd.NormalizeFast(); + idVec3 lt = teamPlayer->viewAxis[1]; + lt.z = 0.0f; + lt.NormalizeFast(); + if ( fabs( dir * fwd ) < 0.4f ) { + // more to the side than the front + if ( dir * lt > 0 ) { + result = Speak ( "lipsync_enemy_left", true ); + } else { + result = Speak ( "lipsync_enemy_right", true ); + } + } + } + + // If we still havent spoken, just announce the enemy + if ( !result ) { + //FIXME: check right & left? + //FIXME: check for surrounded? + result = Speak ( "lipsync_enemy_default", true ); + } + } + } + + // If we spoke then set the timer + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY, 2000 ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announce that we have killed our enemy +===================== +*/ +void idAI::AnnounceKill( idActor* victim ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // If already speaking or one the same team, dont announce the kill + if ( victim->team == team ) { + return; + } + //jshepard: "Watch It" sounds have been cut. Replaced with Victory. + + // Generic annoucement of enemy's death + Speak( "lipsync_victory", true ); + + + return; + +/* + // If the victim is targetting the player and was close to the player when he died + // then announce to the player to be careful + if ( victim->IsType( idAI::Type ) ) { + idAI* vicAI = static_cast(victim); + if ( vicAI && vicAI->GetEnemy() && vicAI->GetEnemy()->IsType ( idPlayer::GetClassType() ) ) { + idPlayer* vicEnemyPlayer = static_cast(vicAI->GetEnemy()); + if ( vicEnemyPlayer->team == team ) { + idVec3 diff = vicAI->GetPhysics()->GetOrigin() - vicEnemyPlayer->GetPhysics()->GetOrigin(); + if ( !vicEnemyPlayer->CheckFOV ( vicAI->GetPhysics()->GetOrigin() ) || diff.LengthSqr() < 300.0f * 300.0f ) { + Speak( "lipsync_watchit", true ); + return; + } + } + } + } + + // Chance that we say "watch it" to the closest teammate to him if the guy that died was facing the teammate + // when he died (ie, was a possible threat) + idActor* teammate = aiManager.NearestTeammateToPoint( this, victim->GetPhysics()->GetOrigin(), false, 300.0f ); + if ( teammate && victim->CheckFOV(teammate->GetPhysics()->GetOrigin()) ) { + if ( gameLocal.random.RandomInt(2) < 1 ) { + Speak( "lipsync_watchit", true ); + return; + } + } +*/ + +} + +/* +===================== +idAI::AnnounceTactical + +Announce the changing of tactical status +===================== +*/ +void idAI::AnnounceTactical( aiTactical_t newTactical ) { + bool result = false; + + // If already speaking dont bother + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL ) ) { + return; + } + + switch ( newTactical ) { + case AITACTICAL_MELEE: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_rush", true ); + } + break; +/* + case AITACTICAL_COVER: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_cover", true ); + } + break; + case AITACTICAL_COVER_FLANK: + result = Speak( "lipsync_flank", true ); + break; + case AITACTICAL_COVER_ADVANCE: + result = Speak( "lipsync_moveup", true ); + break; + case AITACTICAL_COVER_RETREAT: + result = Speak( "lipsync_fallback", true ); + break; + case AITACTICAL_COVER_AMBUSH: + break; + case AITACTICAL_RANGED: + break; + case AITACTICAL_TURRET: + break; + case AITACTICAL_HIDE: + result = Speak( "lipsync_cover", true ); + break; +*/ + } + + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL, 2000 ); + } +} + +/* +===================== +idAI::AnnounceSuppressed + +Announce that someone is using supressing fire on us +===================== +*/ +void idAI::AnnounceSuppressed( idActor *suppressor ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if we are already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED ) ) { + return; + } + + //FIXME: check crossfire? + if ( Speak( "lipsync_supressed", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED, 3000 ); + } +*/ + +} + +/* +===================== +idAI::AnnounceSuppressing + +Announce that we are about to use supressing fire +===================== +*/ +void idAI::AnnounceSuppressing( void ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING ) ) { + return; + } + + //Make the guy being shot at know this + if ( enemy.ent->IsType( idAI::Type ) ) { + idAI* enemyAI = dynamic_cast(enemy.ent.GetEntity()); + if ( enemyAI ) { + enemyAI->AnnounceSuppressed( this ); + } + } + + if ( Speak( "lipsync_supressing", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING, 5000 ); + } +*/ +} + +/* +===================== +idAI::AnnounceFlinch + +Announce that an attack just missed us +===================== +*/ +void idAI::AnnounceFlinch( idEntity *attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + idActor* attackActor = dynamic_cast(attacker); + + // Friendly fire? + if ( attackActor && attackActor->team == team ) { + if ( gameLocal.random.RandomFloat() < 0.2f ) { + AnnounceFriendlyFire( attackActor ); + } + } +//jshepard: sniper announcement removed by request +/* + else if ( attackActor->spawnArgs.GetBool ( "sniper" ) ) { + //TEMP: static debounce timer + static int lastPlayed2 = 0; + if ( gameLocal.time - lastPlayed2 < 10000 ) { + return; + } + lastPlayed2 = gameLocal.time; + Speak( "lipsync_sniper", true ); + } else { +*/ + else { + if ( gameLocal.random.RandomFloat() < 0.4f ) { + return; + } + //TEMP: static debounce timer + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + Speak( "lipsync_closeone", true ); + } +} + +/* +===================== +idAI::AnnounceInjured + +Announce that we have been injured +===================== +*/ +void idAI::AnnounceInjured( void ) { + if ( !CanAnnounce ( 1.0f ) ) { + return; + } + + Speak( "lipsync_needhelp", true ); +} + +/* +===================== +idAI::AnnounceFriendlyFire + +Announce that someone on our own team is shooting us +===================== +*/ +void idAI::AnnounceFriendlyFire( idActor* attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Early outs + if ( health <= 0 || attacker == this ) { + return; + } + + // Don't react to ff from other buddy AI + if ( !attacker->IsType( idPlayer::GetClassType() ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE ) ) { + return; + } + + // Must be on same team for friendly fire + if ( team != attacker->team ) { + return; + } + + // Must be close enough to hear it + if ( DistanceTo ( attacker ) > 300.0f ) { + return; + } + + //FIXME: escalate? + if ( Speak( "lipsync_checkfire", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, 1000 ); + } +} + +/* +===================== +idAI::AnnounceGrenade +===================== +*/ +void idAI::AnnounceGrenade( void ) { + if ( !CanAnnounce ( 1 ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_grenade", true ); +} + +/* +===================== +idAI::AnnounceGrenadeThrow + +Announce that we are throwing a grenade +===================== +*/ +void idAI::AnnounceGrenadeThrow( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 1000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_throw_grenade", true ); +} + + +/* +===================== +rvAIManager::AnnounceDeath + +Announce through an ally of the victem that they have died +===================== +*/ +void rvAIManager::AnnounceDeath( idAI* victim, idEntity* attacker ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire kill? + //MCG NOTE: This isn't even possible anymore... + if ( attacker->IsType ( idPlayer::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + teammate = NearestTeammateToPoint( static_cast(attacker), attacker->GetPhysics()->GetOrigin(), true, 500.0f ); + teammateAI = dynamic_cast(teammate); + + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + teammateAI->Speak( "lipsync_traitor", true ); + return; + } + } + + teammate = NearestTeammateToPoint( victim, victim->GetPhysics()->GetOrigin(), true, 1000.0f ); + teammateAI = dynamic_cast(teammate); + + //jshepard: double check to make sure we don't call out our own death! + if( teammateAI == victim ) { + //MCG: note - NearestTeammateToPoint should *never* allow this, should never happen + assert(0); + return; + } + + // Early out if we dont have a teammate or our teammate cant talk + if ( !attacker || !teammateAI || !teammateAI->CanAnnounce ( teammateAI->announceRate ) ) { + return; + } + + // Announce sniper? + // jshepard: sniper announcement removed by request +/* + if ( attacker->spawnArgs.GetBool ( "sniper" ) ) { + if ( !aiManager.CheckTeamTimer( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER ) ) { + if ( teammateAI->Speak( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + } +*/ + // Annoucne specific death or just a generic death + const char* shortName; + if ( !victim->spawnArgs.GetString ( "npc_shortname", "", &shortName ) || !*shortName || + !teammateAI->Speak ( va("lipsync_%s_killed", shortName ), true ) ) { + teammateAI->Speak( "lipsync_mandown", true ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announces an ai being killed +===================== +*/ +void rvAIManager::AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire deaths are handled elsewhere + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + return; + } + + // If it was an AI guy that did the killing then just let him announce it + if ( attacker->IsType( idAI::Type ) ) { + //announce the kill + static_cast(attacker)->AnnounceKill( victim ); + } else if ( attacker->IsType( idPlayer::GetClassType() ) ) { + idPlayer* attackerPlayer = static_cast(attacker); + + // If the guy who died is an AI guy who was targetting a buddy nearby, have him say "thanks!" + // jshepard: these are cut unless we can get some tighter "thanks" quotes +/* if ( victim->IsType( idAI::Type ) ) { + idAI* victimAI = static_cast(victim); + //if the victim's enemy is a teammate of mine, make the teammate say "thanks!" + if ( victimAI && victimAI->GetEnemy() ) { + idAI* victimEnemyAI = dynamic_cast(victimAI->GetEnemy()); + + // See if the enemy of the guy who died is a teammate and wants to say thanks + if ( victimEnemyAI && victimEnemyAI->CanAnnounce ( ) && victimEnemyAI->team == attackerPlayer->team ) { + float distSqr = (victimAI->GetPhysics()->GetOrigin() - victimEnemyAI->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr < Square ( 300.0f ) ) { + //teammate was fighting him or close to him + victimEnemyAI->Speak( "lipsync_thanks", true ); + return; + } + } + } + } */ + + // Grab a nearby teammate of the player and say "nice shot!" + teammate = NearestTeammateToPoint( attackerPlayer, attacker->GetPhysics()->GetOrigin(), true, 500.0f, true ); + teammateAI = dynamic_cast(teammate); + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + idProjectile* proj = dynamic_cast(inflictor); + //killed them with a grenade? + if ( proj && proj->spawnArgs.GetBool( "thrown" ) ) { + teammateAI->Speak( "lipsync_nicetoss", true ); + // Or just shot them? + } else { + teammateAI->Speak( "lipsync_niceshot", true ); + } + return; + } + } +} + diff --git a/source/mpgame/ai/AI_Debug.cpp b/source/mpgame/ai/AI_Debug.cpp new file mode 100644 index 0000000..21bd80f --- /dev/null +++ b/source/mpgame/ai/AI_Debug.cpp @@ -0,0 +1,330 @@ +/* +================ + +AI_Debug.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" + +const char *aiMoveCommandString[ NUM_MOVE_COMMANDS ] = { + "MOVE_NONE", + "MOVE_FACE_ENEMY", + "MOVE_FACE_ENTITY", + "MOVE_TO_ENEMY", + "MOVE_TO_ENTITY", + "MOVE_TO_ATTACK", + "MOVE_TO_HELPER", + "MOVE_TO_TETHER", + "MOVE_TO_COVER", + "MOVE_TO_HIDE", + "MOVE_TO_POSITION", + "MOVE_OUT_OF_RANGE", + "MOVE_SLIDE_TO_POSITION", + "MOVE_WANDER" +}; + +const char* aiMoveStatusString[ NUM_MOVE_STATUS ] = { + "MOVE_STATUS_DONE", + "MOVE_STATUS_MOVING", + "MOVE_STATUS_WAITING", + "MOVE_STATUS_DEST_NOT_FOUND", + "MOVE_STATUS_DEST_UNREACHABLE", + "MOVE_STATUS_BLOCKED_BY_WALL", + "MOVE_STATUS_BLOCKED_BY_OBJECT", + "MOVE_STATUS_BLOCKED_BY_ENEMY", + "MOVE_STATUS_BLOCKED_BY_MONSTER", + "MOVE_STATUS_BLOCKED_BY_PLAYER", + "MOVE_STATUS_DISABLED" +}; + +const char* aiMoveDirectionString [ MOVEDIR_MAX ] = { + "MOVEDIR_FORWARD", + "MOVEDIR_BACKWARD", + "MOVEDIR_LEFT", + "MOVEDIR_RIGHT" +}; + +const char* aiTacticalString [ AITACTICAL_MAX ] = { + "AITACTICAL_NONE", + "AITACTICAL_MELEE", + "AITACTICAL_MOVE_FOLLOW", + "AITACTICAL_MOVE_TETHER", + "AITACTICAL_MOVE_PLAYERPUSH", + "AITACTICAL_COVER", + "AITACTICAL_COVER_FLANK", + "AITACTICAL_COVER_ADVANCE", + "AITACTICAL_COVER_RETREAT", + "AITACTICAL_COVER_AMBUSH", + "AITACTICAL_RANGED", + "AITACTICAL_TURRET", + "AITACTICAL_HIDE", + "AITACTICAL_PASSIVE", +}; + +const char* aiFocusString [ AIFOCUS_MAX ] = { + "AIFOCUS_NONE", + "AIFOCUS_LEADER", + "AIFOCUS_TARGET", + "AIFOCUS_TALK", + "AIFOCUS_PLAYER", + "AIFOCUS_ENEMY", + "AIFOCUS_COVER", + "AIFOCUS_COVERLOOK", + "AIFOCUS_HELPER", + "AIFOCUS_TETHER", +}; + +const char* aiActionStatusString [ rvAIAction::STATUS_MAX ] = { + "Unused", + "OK", + "Disabled", + "Failed: timer", + "Failed: external timer", + "Failed: within min range", + "Failed: out of max range", + "Failed: random chance", + "Failed: bad animation", + "Failed: condition", + "Failed: no enemy", +}; + +/* +===================== +idAI::GetDebugInfo +===================== +*/ +void idAI::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idActor::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "aifl.damage", aifl.damage ? "true" : "false", userData ); + proc ( "idAI", "aifl.undying", aifl.undying ? "true" : "false", userData ); + proc ( "idAI", "aifl.pain", aifl.pain ? "true" : "false", userData ); + proc ( "idAI", "aifl.dead", aifl.dead ? "true" : "false", userData ); + proc ( "idAI", "aifl.activated", aifl.activated ? "true" : "false", userData ); + proc ( "idAI", "aifl.jump", aifl.jump ? "true" : "false", userData ); + proc ( "idAI", "aifl.hitEnemy", aifl.hitEnemy ? "true" : "false", userData ); + proc ( "idAI", "aifl.pushed", aifl.pushed ? "true" : "false", userData ); + proc ( "idAI", "aifl.lookAtPlayer", aifl.lookAtPlayer ? "true" : "false", userData ); + proc ( "idAI", "aifl.disableAttacks", aifl.disableAttacks ? "true" : "false", userData ); + proc ( "idAI", "aifl.simpleThink", aifl.simpleThink ? "true" : "false", userData ); + proc ( "idAI", "aifl.action", aifl.action ? "true" : "false", userData ); + proc ( "idAI", "aifl.scripted", aifl.scripted? "true" : "false", userData ); + + proc ( "idAI", "leader", leader.GetEntity() ? leader.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "move.followRangeMin", va("%g",move.followRange[0]), userData ); + proc ( "idAI", "move.followRangeMax", va("%g",move.followRange[1]), userData ); + proc ( "idAI", "combat.fl.aware", combat.fl.aware ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.ignoreEnemies", combat.fl.ignoreEnemies ? "true" : "false", userData ); + + proc ( "idAI", "enemy", enemy.ent ? enemy.ent->GetName() : "", userData ); + proc ( "idAI", "enemy.fl.inFov", enemy.fl.inFov ? "true" : "false", userData ); + proc ( "idAI", "enemy.fl.visible", enemy.fl.visible ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.seenEnemyDirectly",combat.fl.seenEnemyDirectly ? "true" : "false", userData ); + proc ( "idAI", "enemy.lastVisibleTime", va("%d",enemy.lastVisibleTime), userData ); + proc ( "idAI", "combat.maxLostVisTime", va("%d",combat.maxLostVisTime), userData ); + proc ( "idAI", "enemy.range", va("%g",enemy.range), userData ); + proc ( "idAI", "enemy.ranged2d", va("%g",enemy.range2d), userData ); + proc ( "idAI", "lastAttackTime", va("%d",lastAttackTime), userData ); + + proc ( "idAI", "move.fl.done", move.fl.done ? "true" : "false", userData ); + proc ( "idAI", "move.fl.disabled", move.fl.disabled ? "true" : "false", userData ); + proc ( "idAI", "move.fl.onGround", move.fl.onGround ? "true" : "false", userData ); + proc ( "idAI", "move.fl.blocked", move.fl.blocked ? "true" : "false", userData ); + proc ( "idAI", "move.fl.obstacleInPath", move.fl.obstacleInPath ? "true" : "false", userData ); + proc ( "idAI", "move.fl.goalUnreachable", move.fl.goalUnreachable ? "true" : "false", userData ); + proc ( "idAI", "move.fl.moving", move.fl.moving ? "true" : "false", userData ); + proc ( "idAI", "move.command", aiMoveCommandString[move.moveCommand], userData ); + proc ( "idAI", "move.status", aiMoveStatusString[move.moveStatus], userData ); + proc ( "idAI", "move.fl.allowDirectional", move.fl.allowDirectional ? "true" : "false", userData ); + proc ( "idAI", "move.direction_ideal", aiMoveDirectionString[move.idealDirection ], userData ); + proc ( "idAI", "move.direction_current", aiMoveDirectionString[move.currentDirection ], userData ); + proc ( "idAI", "move.yaw_ideal", va("%d",(int)move.ideal_yaw), userData ); + proc ( "idAI", "move.yaw_current", va("%d",(int)move.current_yaw), userData ); + proc ( "idAI", "move.fly_roll", va("%g", move.fly_roll ), userData ); + proc ( "idAI", "move.fly_pitch", va("%g", move.fly_pitch ), userData ); + + proc ( "idAI", "tether", tether!=NULL?tether->GetName():"", userData ); + proc ( "idAI", "IsTethered()", IsTethered()?"true":"false", userData ); + + proc ( "idAI", "lookTarget", lookTarget.GetEntity() ? lookTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "talkTarget", talkTarget.GetEntity() ? talkTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "focusType", aiFocusString[focusType], userData ); + proc ( "idAI", "look.yaw", va("%g", lookAng.yaw ), userData ); + proc ( "idAI", "look.pitch", va("%g", lookAng.pitch ), userData ); + + proc ( "idAI", "combat.attackRangeMin", va("%g",combat.attackRange[0]), userData ); + proc ( "idAI", "combat.attackRangeMax", va("%g",combat.attackRange[1]), userData ); + proc ( "idAI", "combat.tacticalCurrent", aiTacticalString[combat.tacticalCurrent], userData ); + + proc ( "idAI", "action_rangedAttack", aiActionStatusString[actionRangedAttack.status], userData ); + proc ( "idAI", "action_meleeAttack", aiActionStatusString[actionMeleeAttack.status], userData ); + proc ( "idAI", "action_leapAttack", aiActionStatusString[actionLeapAttack.status], userData ); + proc ( "idAI", "action_jumpBack", aiActionStatusString[actionJumpBack.status], userData ); + proc ( "idAI", "action_evadeLeft", aiActionStatusString[actionEvadeLeft.status], userData ); + proc ( "idAI", "action_evadeRight", aiActionStatusString[actionEvadeRight.status], userData ); + + proc ( "idAI", "npc_name", spawnArgs.FindKey("npc_name")?common->GetLocalizedString(spawnArgs.GetString( "npc_name", "")):"", userData ); +} + +/* +===================== +idAI::DrawRoute +===================== +*/ +void idAI::DrawRoute( void ) const { + if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } else { + aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } + } +} + +/* +===================== +idAI::DrawTactical + +Draw the debug tactical information +===================== +*/ +void idAI::DrawTactical ( void ) { + if ( !DebugFilter(ai_debugTactical) ) { + return; + } + + // Colors For Lines In This File + //------------------------------- + // Majenta = Move Dest + // Green / Red = Enemy (On Player Side, On Enemy Side) + // Pink = Tether Radius + // Grey = FOV + + // Colors From AAST Draw Debug Info + //---------------------------------- + // Blue = Reserved Feature + // Orange = Near Feature + // Yellow = Look Feature + + + // Get Origin + //------------ + idVec3 origin = GetPhysics()->GetOrigin(); + origin += (GetPhysics()->GetGravityNormal() * 5.0f); + + // Draw FOV (Must be close to player and on enemy team) + //------------------------------------------------------ + if (team && DistanceTo(gameLocal.GetLocalPlayer())<500.0f) { + if (!combat.fl.aware) { + gameRenderWorld->DebugFOV(colorLtGrey, origin, viewAxis[0], fovDot, 300.0f, fovCloseDot, fovCloseRange, 0.3f, 10); + } else if (!combat.fl.seenEnemyDirectly) { + gameRenderWorld->DebugFOV(colorMdGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, 0.3f, 10); + } else { + float alpha = (1.0f - ((float)(gameLocal.GetTime() - enemy.lastVisibleTime) / (float)combat.maxLostVisTime)) * 0.35f; + gameRenderWorld->DebugFOV(colorDkGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, Max(alpha, 0.1f), 10); + } + } + + // Move Over Head + //---------------- + origin -= GetPhysics()->GetGravityNormal() * (GetPhysics()->GetBounds()[ 1 ].z - GetPhysics()->GetBounds()[ 0 ].z); + + // Draw Enemy Related Info + //------------------------- + if ( enemy.ent ) { + // Draw Lost Time + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > combat.maxLostVisTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > ( combat.maxLostVisTime/2 ) ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorGreen, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Enemy Chest position for enemy lines + idVec3 enemyEyePosition; + idVec3 offset; + if ( enemy.ent->IsType ( idActor::GetClassType() ) ){ + enemyEyePosition = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + enemyEyePosition = enemy.ent->GetPhysics()->GetOrigin(); + } + + offset = idVec3(0,0,team*2.0f); + + gameRenderWorld->DebugLine ( aiTeamColor[team], GetEyePosition() + offset, enemy.lastVisibleFromEyePosition + offset, 4.0f ); + if ( enemyEyePosition != enemy.lastVisibleEyePosition ) { + gameRenderWorld->DebugLine ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemy.lastVisibleEyePosition + offset, 4.0f ); + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } else { + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } + } + + // Ivalid Cover Timer + //-------------------- + if ( IsBehindCover() && combat.coverValidTime && combat.coverValidTime < gameLocal.time) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("invalid cover time: %d", (gameLocal.GetTime() - combat.coverValidTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } + + + // Vis Crouch + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Vis Stand + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Aggression + if ( combat.aggressiveScale != 1.0f ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("aggressive: %g", combat.aggressiveScale), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw anim prefix + //----------------------- + if ( animPrefix.Length ( ) ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("anim: %s", animPrefix.c_str()), origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw focus type + //----------------------- + if ( focusType != AIFOCUS_NONE ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiFocusString[focusType], origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw Tactical Current + //----------------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiTacticalString[combat.tacticalCurrent], origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + + // Draw My Name + //-------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText(name, origin, 0.12f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis); + + // Draw the tethered radius + if ( IsTethered ( ) ) { + tether->DebugDraw ( ); + } + + // Draw Move Destination + if ( !move.fl.done ) { + gameRenderWorld->DebugArrow ( colorMagenta, GetPhysics()->GetOrigin(), move.moveDest, 5 ); + } +} diff --git a/source/mpgame/ai/AI_Manager.cpp b/source/mpgame/ai/AI_Manager.cpp new file mode 100644 index 0000000..dbe71d3 --- /dev/null +++ b/source/mpgame/ai/AI_Manager.cpp @@ -0,0 +1,767 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" + +idVec4 aiTeamColor[AITEAM_NUM] = { idVec4 ( 0.0f, 1.0f, 0.0f, 1.0f ), + idVec4 ( 1.0f, 0.0f, 0.0f, 1.0f ) }; + +rvAIManager aiManager; + +/* +================ +rvAIManager::rvAIManager +================ +*/ +rvAIManager::rvAIManager ( void ) { + Clear(); +} + +/* +================ +rvAIManager::IsActive +================ +*/ +bool rvAIManager::IsActive( void ){ + if( gameLocal.isMultiplayer ){ + return false; + } + + return true; +} + +/* +================ +rvAIManager::RunFrame +================ +*/ +void rvAIManager::RunFrame ( void ){ + if ( !IsActive() ) { + return; + } + + // Pop the top simple think off the list + if ( !simpleThink.IsListEmpty() ) { + simpleThink.Next()->simpleThinkNode.Remove ( ); + } + + // Display current ai speeds + if ( ai_speeds.GetBool ( ) && thinkCount > 0 ) { + gameLocal.Printf ( "ai:%6i n:%2i s:%2i all:%5.2f t:%5.2f e:%5.2f m:%5.f\n", + gameLocal.framenum, thinkCount, simpleThinkCount, + timerThink.Milliseconds(), + timerTactical.Milliseconds(), + timerFindEnemy.Milliseconds(), + timerMove.Milliseconds() ); + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + int i; + int j; + // look for reaches that are no longer blocked + for ( i=0; ib.time) { + break; + } + + idEntityPtr blockent = b.blockers[j]; + if (!blockent.IsValid() || blockent->DistanceTo(b.positions[j])>10.0f) { + break; + } + if (blockent->IsType(idAI::GetClassType())) { + idAI* blockentAI = static_cast(blockent.GetEntity()); + if (blockentAI->move.moveDest.Dist2XY(b.positions[j])>100.0f) { + break; + } + } + } + + // if any one of the blockers moved or no longer exists, re-enable this reach + if ( !b.blockers.Num() || jSetReachabilityState(b.reach, true); + blockedReaches.RemoveIndex(i); + break; + } + + // DEBUG GRAPHICS + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, b.reach->start, b.reach->start + idVec3(0,0,40.0f), gameLocal.msec ); + for ( j=0; jDebugArrow( colorRed, b.blockers[j]->GetPhysics()->GetOrigin(), b.reach->start, 8, gameLocal.msec ); + } + } + } + } + // RAVEN END + + timerThink.Clear ( ); + timerTactical.Clear ( ); + timerFindEnemy.Clear ( ); + timerMove.Clear ( ); + + gameDebug.SetStatInt( "ai_thinkCount", thinkCount ); + + thinkCount = 0; + simpleThinkCount = 0; + + // Draw any debugging information + DebugDraw ( ); +} + +/* +================ +rvAIManager::Clear +================ +*/ +void rvAIManager::Clear( void ) { + thinkCount = 0; + simpleThinkCount = 0; + timerThink.Clear ( ); + timerFindEnemy.Clear ( ); + timerTactical.Clear ( ); + timerMove.Clear ( ); + + blockedReaches.Clear ( ); + helpers.Clear ( ); + simpleThink.Clear ( ); + avoids.Clear ( ); + + memset ( &teamTimers, 0, sizeof(teamTimers) ); +} + +/* +================ +rvAIManager::UnMarkAllReachBlocked +================ +*/ +void rvAIManager::UnMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, true); + } +} + +/* +================ +rvAIManager::ReMarkAllReachBlocked +================ +*/ +void rvAIManager::ReMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, false); + } +} + +/* +================ +rvAIManager::MarkReachBlocked +================ +*/ +void rvAIManager::MarkReachBlocked(idAAS* aas, idReachability* reach, const idList& blockers) { + + // only if not already blocked + if (!(reach->travelType&TFL_INVALID)) { + aiBlocked_t blocked; + blocked.aas = aas; + blocked.reach = reach; + blocked.time = gameLocal.GetTime() + 5000.0f; + + for (int i=0; iGetPhysics() && (blockers[i]->GetPhysics()->IsAtRest() || blockers[i]->GetPhysics()->GetLinearVelocity().LengthSqr()<2500.0f)) { + blocked.blockers.Append(blockers[i]); + blocked.positions.Append(blockers[i]->GetPhysics()->GetOrigin()); + } + } + if (blocked.blockers.Num()) { + aas->SetReachabilityState(reach, false); + blockedReaches.Append(blocked); + } + } + +} + +/* +================ +rvAIManager::ReactToPlayerAttack +================ +*/ +void rvAIManager::ReactToPlayerAttack ( idPlayer* player, const idVec3 &origin, const idVec3 &dir ){ + idActor* actor; + float expandSize; + + // Check all enemies and see if they need to react + for ( actor = GetEnemyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + // Skip non ai entities + if ( !actor->IsType ( idAI::Type ) ) { + continue; + } + + idAI *curAI = static_cast(actor); + + // See if it will pass through an expanded bounding box + expandSize = curAI->spawnArgs.GetFloat( "shotAtReactionRange", "16" ); + if ( !curAI->GetPhysics()->GetAbsBounds ( ).Expand(expandSize).LineIntersection ( origin, origin + dir * curAI->combat.visRange ) ) { + continue; + } + + curAI->ReactToShotAt ( player, origin, dir ); + } +} + +/* +================ +rvAIManager::Save +================ +*/ +void rvAIManager::Save( idSaveGame *savefile ) const { + int i; + int j; + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->WriteInt( teams[i].Num() ); + for( actor = teams[i].Next(); actor != NULL; actor = actor->teamNode.Next() ) { + savefile->WriteObject( actor ); + } + } + + // Write out team timers + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->WriteInt ( teamTimers[i][j] ); + } + } + + // Write out team timers + savefile->WriteInt ( avoids.Num ( ) ); + for ( i = 0; i < avoids.Num ( ); i ++ ) { + savefile->WriteVec3 ( avoids[i].origin ); + savefile->WriteFloat ( avoids[i].radius ); + savefile->WriteInt ( avoids[i].team ); + } +} + +/* +================ +rvAIManager::Restore +================ +*/ +void rvAIManager::Restore( idRestoreGame *savefile ){ + int i; + int j; + + Clear ( ); + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->ReadInt( j ); + for ( ; j > 0; j -- ) { + savefile->ReadObject ( reinterpret_cast( actor ) ); + if ( actor ) { + actor->teamNode.AddToEnd ( teams[i] ); + } + } + } + + // Read team timers + memset ( teamTimers, 0, sizeof(teamTimers) ); + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->ReadInt ( teamTimers[i][j] ); + } + } + + // Read in team timers + savefile->ReadInt ( j ); + avoids.SetNum ( j ); + for ( i = 0; i < j; i ++ ) { + savefile->ReadVec3 ( avoids[i].origin ); + savefile->ReadFloat ( avoids[i].radius ); + savefile->ReadInt ( avoids[i].team ); + } +} + +/* +===================== +rvAIManager::AddTeammate +===================== +*/ +void rvAIManager::AddTeammate ( idActor* actor ) { + // If its already in a team least then ignore the call. + // NOTE: You have to call removeteammate before addteammate to switch the + // actor from one team to another + if ( actor->teamNode.InList ( ) ) { + return; + } + actor->teamNode.AddToEnd ( teams[actor->team] ); +} + +/* +===================== +rvAIManager::RemoveTeammate +===================== +*/ +void rvAIManager::RemoveTeammate ( idActor* actor ) { + actor->teamNode.Remove ( ); +} + +/* +===================== +rvAIManager::IsSimpleThink + +Determines whether or not the given AI entity should be simple thinking this frame +===================== +*/ +bool rvAIManager::IsSimpleThink ( idAI* ai ) { + + // no simple think if simple thinking is disabled or we are the head node + if ( ai_disableSimpleThink.GetBool() ) { + return false; + } + + // Add to simple think list + if ( !ai->simpleThinkNode.InList ( ) ) { + ai->simpleThinkNode.AddToEnd ( simpleThink ); + } + + // If head of list then its a complex think + if ( ai->simpleThinkNode.Prev() == NULL ) { + return false; + } + + return true; +} + +/* +================ +rvAIManager::GetEnemyTeam +================ +*/ +idActor* rvAIManager::GetEnemyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_STROGG].Next(); + case AITEAM_STROGG: + return teams[AITEAM_MARINE].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::GetAllyTeam +================ +*/ +idActor* rvAIManager::GetAllyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_MARINE].Next(); + case AITEAM_STROGG: + return teams[AITEAM_STROGG].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::ValidateDestination + +Validate whether or not the destinations is a destination +================ +*/ +bool rvAIManager::ValidateDestination ( idAI* ai, const idVec3& dest, bool skipCurrent, idActor* skipActor ) const { + int i; + idBounds bounds; + idAI* ignore; + + ignore = (!skipCurrent && ai && !ai->SkipCurrentDestination ( )) ? ai : NULL; + + bounds = ai->GetPhysics()->GetBounds ( ); + bounds.TranslateSelf ( dest ); + bounds.ExpandSelf ( 16.0f ); + + // All teams and all actors on those teams + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + // Ignored? + if ( actor == ignore || actor == skipActor || actor->IsHidden ( ) ) { + continue; + } + + // If the actor is AI that is moving we should check their destination rather than where they are now + if ( actor->IsType ( idAI::Type ) ) { + idAI* aiactor = static_cast(actor); + if ( aiactor->move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( bounds.IntersectsBounds ( aiactor->GetPhysics()->GetBounds().Translate ( aiactor->move.moveDest ) ) ) { + return false; + } + continue; + } + } + + // Does the destination overlap this actor? + if ( bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + return false; + } + } + } + + // Destinations inside an avoid area are invalid + for ( i = avoids.Num() - 1; i >= 0; i -- ) { + const aiAvoid_t& avoid = avoids[i]; + // Skip all avoids that arent meant for this team + if ( avoid.team != -1 && ai->team != avoid.team ) { + continue; + } + // Skip if within range of the avoid + if ( (avoid.origin - dest).LengthSqr ( ) < Square ( avoid.radius ) ) { + if ( ai_debugMove.GetBool() || ai_debugTactical.GetBool() ) { + gameRenderWorld->DebugCircle( colorRed, avoid.origin, idVec3(0,0,1), avoid.radius, 25 ); + } + return false; + } + } + + return true; +} + +/* +================ +rvAIManager::NearestTeammateToPoint + +Returns the teammate closest to the given point with the given parameters +================ +*/ +idActor* rvAIManager::NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer, float maxRange, bool checkFOV, bool checkLOS ) { + idActor* actor = NULL; + idActor* closestActor = NULL; + float distSqr; + float bestDistSqr; + + closestActor = 0x0; + bestDistSqr = Square ( maxRange ); + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Player? + if ( nonPlayer && actor->IsType( idPlayer::GetClassType() ) ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (point - actor->GetPhysics()->GetOrigin()).LengthSqr(); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV && !actor->CheckFOV( point ) ) { + continue; + } + //actor has clear LOS to point? + if ( checkLOS && !actor->CanSeeFrom ( from->GetEyePosition ( ), point, false ) ) { + continue; + } + // New best actor + bestDistSqr = distSqr; + closestActor = actor; + } + return closestActor; +} + +/* +================ +rvAIManager::NearestTeammateEnemy + +Returns the closest enemy of an ally. +================ +*/ +idEntity* rvAIManager::NearestTeammateEnemy( idActor* from, float maxRange, bool checkFOV, bool checkLOS, idActor** closestAllyWithEnemy ) { + idActor* actor; + idAI* allyAI; + idEntity* allyEnemy; + idEntity* closestAllyEnemy; + float distSqr; + float bestDistSqr; + + bestDistSqr = Square ( maxRange ); + closestAllyEnemy = NULL; + + // Optionally return the ally whos enemy it is + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = NULL; + } + + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + allyAI = dynamic_cast(actor); + if ( !allyAI ) { + //player? + allyEnemy = actor->EnemyWithMostHealth(); + if ( !allyEnemy ) { + continue; + } + if ( allyEnemy->health <= 0 ) { + continue; + } + } else { + allyEnemy = allyAI->GetEnemy(); + //has enemy? + if ( !allyEnemy ) { + continue; + } + //still alive? + if ( allyAI->enemy.fl.dead ) { + continue; + } + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (actor->GetPhysics()->GetOrigin() - from->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV ) { + if ( !from->CheckFOV( actor->GetPhysics()->GetOrigin() ) ) { + continue; + } + } + //actor has clear LOS to point? + if ( checkLOS ) { + if ( !from->CanSee( actor, false ) ) { + continue; + } + } + + bestDistSqr = distSqr; + closestAllyEnemy = allyEnemy; + + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = actor; + } + } + return closestAllyEnemy; +} + +/* +================ +rvAIManager::LocalTeamHasEnemies + +Returns true if nearby members of my team have any enemies or if any nearby enemies have enemies that are nearby members of my team +================ +*/ +bool rvAIManager::LocalTeamHasEnemies ( idAI* self, float maxBuddyRange, float maxEnemyRange, bool checkPVS ) { + idActor* actor = NULL; + pvsHandle_t pvs; + + if ( !self ) { + return false; + } + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)self->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + if ( actor->IsType( idAI::GetClassType() ) ) { + //Has an enemy? + if ( !((idAI*)actor)->GetEnemy() ) { + continue; + } + //Has an enemy + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ((idAI*)actor)->GetEnemy()->GetPVSAreas(), ((idAI*)actor)->GetEnemy()->GetNumPVSAreas() ) ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + continue; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + + } + //close enough? + if ( actor != self && self->DistanceTo( actor->GetPhysics()->GetOrigin() ) > maxBuddyRange ) { + continue; + } + //Anyone mad at him? + if ( !actor->ClosestEnemyToPoint( actor->GetPhysics()->GetOrigin(), maxEnemyRange, true, checkPVS ) ) { + continue; + } + return true; + } + return false; +} + +/* +================ +rvAIManager::ActorIsBehindActor + +Returns true if the given 'ambuser' is behind the given 'victim' +================ +*/ +bool rvAIManager::ActorIsBehindActor( idActor* ambusher, idActor* victim ) { + idVec3 dir2Ambusher = ambusher->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin(); + float dist = dir2Ambusher.Normalize(); + if ( DotProduct(dir2Ambusher, victim->viewAxis[0] ) < 0 && dist < 200.0f ) { + return true; + } + return false; +} + +/* +=============================================================================== + + rvAIManager - Helpers + +=============================================================================== +*/ + +/* +===================== +rvAIManager::RegisterHelper +===================== +*/ +void rvAIManager::RegisterHelper ( rvAIHelper* helper ) { + helper->helperNode.AddToEnd ( helpers ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::UnregisterHelper +===================== +*/ +void rvAIManager::UnregisterHelper ( rvAIHelper* helper ) { + helper->helperNode.Remove ( ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::FindClosestHelper +===================== +*/ +rvAIHelper* rvAIManager::FindClosestHelper ( const idVec3& origin ) { + rvAIHelper* helper; + rvAIHelper* bestHelper; + float bestDist; + float dist; + + bestDist = idMath::INFINITY; + bestHelper = NULL; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + dist = (origin - helper->GetPhysics()->GetOrigin()).LengthFast ( ); + if ( dist < bestDist ) { + bestDist = dist; + bestHelper = helper; + } + } + return bestHelper; +} + +/* +================ +rvAIManager::UpdateHelpers +================ +*/ +void rvAIManager::UpdateHelpers ( void ) { + int i; + + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + if ( actor->IsHidden ( ) || !actor->IsType ( idAI::Type ) ) { + continue; + } + static_cast(actor)->UpdateHelper ( ); + } + } +} + +/* +=============================================================================== + + rvAIManager - Debugging + +=============================================================================== +*/ + +/* +================ +rvAIManager::DebugDraw +================ +*/ +void rvAIManager::DebugDraw ( void ) { + // Draw helpers? + if ( ai_debugHelpers.GetBool ( ) ) { + DebugDrawHelpers ( ); + + int i; + for ( i = 0; i < avoids.Num(); i ++ ) { + const aiAvoid_t& avoid = avoids[i]; + gameRenderWorld->DebugCircle ( colorOrange, avoid.origin, idVec3(0,0,1), avoid.radius, 10, 0 ); + } + } + + +} + +/* +================ +rvAIManager::DebugDrawHelpers +================ +*/ +void rvAIManager::DebugDrawHelpers ( void ) { + rvAIHelper* helper; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + helper->DrawDebugEntityInfo ( ); + } +} diff --git a/source/mpgame/ai/AI_Manager.h b/source/mpgame/ai/AI_Manager.h new file mode 100644 index 0000000..9116837 --- /dev/null +++ b/source/mpgame/ai/AI_Manager.h @@ -0,0 +1,236 @@ +#ifndef __AI_MANAGER_H__ +#define __AI_MANAGER_H__ + +typedef enum { + AITEAM_MARINE, + AITEAM_STROGG, + AITEAM_NUM +} aiTeam_t; + +typedef enum { + AITEAMTIMER_ANNOUNCE_TACTICAL, // Tactical change + AITEAMTIMER_ANNOUNCE_SUPPRESSING, + AITEAMTIMER_ANNOUNCE_SUPPRESSED, + AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, // Shot by a teammate + AITEAMTIMER_ANNOUNCE_ENEMYSTEATH, + AITEAMTIMER_ANNOUNCE_NEWENEMY, // New enemy was aquired + AITEAMTIMER_ANNOUNCE_SNIPER, // Sniper sighted + AITEAMTIMER_ANNOUNCE_CANIHELPYOU, // Player standing in front of a friendly too long + AITEAMTIMER_ANNOUNCE_SIGHT, // First time seeing an enemy + AITEAMTIMER_ACTION_RELAX, // Play relax animation + AITEAMTIMER_ACTION_PEEK, // Play peek animation + AITEAMTIMER_ACTION_TALK, // Able to talk to another person yet? + AITEAMTIMER_MAX +} aiTeamTimer_t; + +extern idVec4 aiTeamColor[AITEAM_NUM]; + +/* +===================== +blockedReach_t +===================== +*/ +// cdr: Alternate Routes Bug +typedef struct aiBlocked_s { + idAAS* aas; + idReachability* reach; + int time; + idList< idEntityPtr > blockers; + idList< idVec3 > positions; +} aiBlocked_t; + +/* +===================== +aiAvoid_t +===================== +*/ +typedef struct aiAvoid_s { + idVec3 origin; + float radius; + int team; +} aiAvoid_t; + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +class rvAIHelper : public idEntity { +public: + CLASS_PROTOTYPE( rvAIHelper ); + + rvAIHelper ( void ); + + idLinkList helperNode; + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + + idVec3 GetDirection ( const idAI* ent ) const; + +protected: + + virtual void OnActivate ( bool active ); + +private: + + void Event_Activate ( idEntity *activator ); +}; + +/* +=============================================================================== + +rvAIManager + +=============================================================================== +*/ + +class rvAIManager { +public: + + rvAIManager ( void ); + ~rvAIManager ( void ) {} + + /* + =============================================================================== + General + =============================================================================== + */ + + void RunFrame ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( void ); + + bool IsActive ( void ); + bool IsSimpleThink ( idAI* ai ); + + /* + =============================================================================== + Navigation + =============================================================================== + */ + + void UnMarkAllReachBlocked ( void ); + void ReMarkAllReachBlocked ( void ); + void MarkReachBlocked ( idAAS* aas, idReachability* reach, const idList& blockers); + bool ValidateDestination ( idAI* ignore, const idVec3& dest, bool skipCurrent = false, idActor* skipActor = NULL ) const; + void AddAvoid ( const idVec3& origin, float range, int team ); + + /* + =============================================================================== + Helpers + =============================================================================== + */ + + void RegisterHelper ( rvAIHelper* helper ); + void UnregisterHelper ( rvAIHelper* helper ); + rvAIHelper* FindClosestHelper ( const idVec3& origin ); + + /* + =============================================================================== + Team Management + =============================================================================== + */ + + void AddTeammate ( idActor* ent ); + void RemoveTeammate ( idActor* ent ); + + idActor* GetAllyTeam ( aiTeam_t team ); + idActor* GetEnemyTeam ( aiTeam_t team ); + + idActor* NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer = false, float maxRange = 1000.0f, bool checkFOV = false, bool checkLOS = false ); + idEntity* NearestTeammateEnemy ( idActor* from, float maxRange=1000.0f, bool checkFOV = false, bool checkLOS = false, idActor** ally = NULL ); + bool LocalTeamHasEnemies ( idAI* self, float maxBuddyRange=640.0f, float maxEnemyRange=1024.0f, bool checkPVS=false ); + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + + /* + =============================================================================== + Team Timers + =============================================================================== + */ + + bool CheckTeamTimer ( int team, aiTeamTimer_t timer ); + void ClearTeamTimer ( int team, aiTeamTimer_t timer ); + void SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + + void AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ); + void AnnounceDeath ( idAI* victim, idEntity* attacker ); + + /* + =============================================================================== + Reactions + =============================================================================== + */ + + void ReactToPlayerAttack ( idPlayer* player, const idVec3 &origOrigin, const idVec3 &origDir ); + + /* + =============================================================================== + Debugging + =============================================================================== + */ + + idTimer timerFindEnemy; + idTimer timerTactical; + idTimer timerMove; + idTimer timerThink; + + int thinkCount; + int simpleThinkCount; + +protected: + + void UpdateHelpers ( void ); + + void DebugDraw ( void ); + void DebugDrawHelpers ( void ); + + idList blockedReaches; + idLinkList simpleThink; + idLinkList helpers; + + idLinkList teams[AITEAM_NUM]; + int teamTimers[AITEAM_NUM][AITEAMTIMER_MAX]; + + idList avoids; +}; + +ID_INLINE bool rvAIManager::CheckTeamTimer ( int team, aiTeamTimer_t timer ) { + return gameLocal.time >= teamTimers[team][timer]; +} + +ID_INLINE void rvAIManager::ClearTeamTimer ( int team, aiTeamTimer_t timer ) { + teamTimers[team][timer] = 0; +} + +ID_INLINE void rvAIManager::SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ) { + teamTimers[team][timer] = gameLocal.time + delay; +} + +ID_INLINE void rvAIManager::AddAvoid ( const idVec3& origin, float radius, int team ) { + if ( !IsActive ( ) ) { + return; + } + aiAvoid_t& a = avoids.Alloc ( ); + a.origin = origin; + a.radius = radius; + a.team = team; +} + +extern rvAIManager aiManager; + +#endif // __AI_MANAGER_H__ diff --git a/source/mpgame/ai/AI_Medic.cpp b/source/mpgame/ai/AI_Medic.cpp new file mode 100644 index 0000000..b6cea16 --- /dev/null +++ b/source/mpgame/ai/AI_Medic.cpp @@ -0,0 +1,836 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Medic.h" + + +const idEventDef AI_DisableHeal ( "disableHeal" ); +const idEventDef AI_EnableHeal ( "enableHeal" ); +const idEventDef AI_TakePatient ( "takePatient", "e" ); + +CLASS_DECLARATION( rvAITactical, rvAIMedic ) + EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal ) + EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal ) + EVENT( AI_TakePatient, rvAIMedic::TakePatient ) + EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement ) +END_CLASS + +/* +================ +rvAIMedic::rvAIMedic +================ +*/ +rvAIMedic::rvAIMedic ( void ) { + patient = NULL; + isTech = false; + noAutoHeal = false; + stationary = false; + silent = false; + healObeyTether = false; + healing = false; + lastPatientCheckTime = 0; + emergencyOverride = false; + //healedAmount = 0; + healDisabled = false; + wasAware = false; + wasIgnoreEnemies = false; + healDebounceTime = 0; +} + +void rvAIMedic::InitSpawnArgsVariables( void ) +{ + //NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded? + isTech = spawnArgs.GetBool( "tech" ); + noAutoHeal = spawnArgs.GetBool( "noAutoHeal" ); + healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" ); + healObeyTether = spawnArgs.GetBool( "healObeyTether" ); + patientRange = spawnArgs.GetFloat( "patientRange", "640" ); + buddyRange = spawnArgs.GetFloat( "buddyRange", "640" ); + enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" ); + healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) ); + + /* + // JTD: check for player being strogg, since the limits will be different in that case.. + idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" ); + str.Strip( "player_" ); + // ...we'll end up with something like minMarineHeal...or minStroggHeal + */ + minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" ); + maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" ); +} +/* +================ +rvAIMedic::Spawn +================ +*/ +void rvAIMedic::Spawn ( void ) { + InitSpawnArgsVariables(); + + stationary = spawnArgs.GetBool( "stationary" ); + silent = spawnArgs.GetBool( "silent", "0"); + if ( spawnArgs.GetBool( "disableHeal" ) ) { + Event_DisableHeal(); + } + if ( spawnArgs.GetBool( "enableHeal" ) ) { + Event_EnableHeal(); + } + const char *func; + if ( spawnArgs.GetString( "script_postHeal", "", &func ) ) + { + mPostHealScript.Init( func ); + } +} + +/* +================ +rvAIMedic::Show +================ +*/ +void rvAIMedic::Show( void ) { + rvAITactical::Show(); + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +/* +================ +rvAIMedic::Save +================ +*/ +void rvAIMedic::Save( idSaveGame *savefile ) const { + patient.Save( savefile ); + savefile->WriteBool( healing ); + savefile->WriteInt( lastPatientCheckTime ); + savefile->WriteBool( emergencyOverride ); + savefile->WriteBool( healDisabled ); + savefile->WriteBool( wasAware ); + savefile->WriteBool( wasIgnoreEnemies ); + //savefile->WriteInt( healedAmount ); + savefile->WriteInt( healDebounceTime ); + savefile->WriteBool( stationary ); + savefile->WriteBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->WriteInt( curHealValue ); + savefile->WriteInt( maxPatientValue ); + mPostHealScript.Save( savefile ); +} + +/* +================ +rvAIMedic::Restore +================ +*/ +void rvAIMedic::Restore( idRestoreGame *savefile ) { + patient.Restore( savefile ); + savefile->ReadBool( healing ); + savefile->ReadInt( lastPatientCheckTime ); + savefile->ReadBool( emergencyOverride ); + savefile->ReadBool( healDisabled ); + savefile->ReadBool( wasAware ); + savefile->ReadBool( wasIgnoreEnemies ); + //savefile->ReadInt( healedAmount ); + savefile->ReadInt( healDebounceTime ); + savefile->ReadBool( stationary ); + savefile->ReadBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->ReadInt( curHealValue ); + savefile->ReadInt( maxPatientValue ); + mPostHealScript.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvAIMedic::Event_EnableHeal +===================== +*/ +void rvAIMedic::Event_EnableHeal( void ) { + healDisabled = false; +} + +/* +===================== +rvAIMedic::Event_DisableHeal +===================== +*/ +void rvAIMedic::Event_DisableHeal( void ) { + healDisabled = true; + /* + if ( patient ) { + //drop them right now - NOTE: should be done before any scripting? + DropPatient(); + } + */ +} + +/* +================== +rvAIMedic::Event_EnableMovement +================== +*/ +void rvAIMedic::Event_EnableMovement( void ) { + stationary = false; +} + +/* +================== +rvAIMedic::Event_DisableMovement +================== +*/ +void rvAIMedic::Event_DisableMovement ( void ) { + stationary = true; +} + +/* +===================== +rvAIMedic::TalkTo +===================== +*/ +void rvAIMedic::TalkTo( idActor *actor ) { + if ( actor->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* player = dynamic_cast(actor); + if ( player ) + { + emergencyOverride = true; + if ( AvailableToTakePatient() && CheckTakePatient( player ) ) + { + return; + } + emergencyOverride = false; + } + } + rvAITactical::TalkTo( actor ); + + if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) { + if ( GetEnemy() || patient ) { + Speak( "lipsync_heal_busy_", true ); + } else { + //Never got proper VO for this, disabling it... :/ + //Speak( "lipsync_heal_noheal_", true ); + } + } +} + +/* +================ +rvAIMedic::GetDebugInfo +================ +*/ +void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + rvAITactical::GetDebugInfo ( proc, userData ); + + proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData ); + proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData ); + proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData ); + proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData ); + proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData ); + proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData ); + proc ( "rvAIMedic", "silent", silent?"true":"false", userData ); + proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData ); + proc ( "rvAIMedic", "patient", patient==NULL?"":patient->GetName(), userData ); + proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData ); + proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData ); + proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData ); + proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData ); + proc ( "rvAIMedic", "healing", healing?"true":"false", userData ); + proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData ); + //proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData ); + proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData ); + proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData ); + proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData ); + proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData ); + proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData ); + proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData ); + proc ( "rvAIMedic", "tech", isTech?"true":"false", userData ); +} + +/* +============ +rvAIMedic::IsTethered +============ +*/ +bool rvAIMedic::IsTethered ( void ) const { + if ( rvAITactical::IsTethered() ) { + if ( !healObeyTether && patient ) { + return false; + } + return true; + } + return false; +} + +void rvAIMedic::TakePatient( idPlayer* pPatient ) +{ + patient = pPatient; + if ( emergencyOverride ) + { + ClearEnemy(); + } + + wasAware = combat.fl.aware; + wasIgnoreEnemies = combat.fl.ignoreEnemies; + ProcessEvent( &AI_BecomePassive, true ); + combat.fl.ignoreEnemies = true; + ClearEnemy(); + lookTarget = patient; + healing = false; + //healedAmount = 0; + + if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent ) + { + //have enough time to say this before we get there...? + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_call_player_tech_", true ); + } else { + Speak( "lipsync_call_player_", true ); + } + } + + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + SetState( "State_Medic" ); + PostState( "State_Combat" ); + + //just in case + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } +} + +void rvAIMedic::DropPatient( void ) +{ + /* + if ( !spawnArgs.GetBool( "ignoreEnemies" ) ) + { + combat.fl.ignoreEnemies = false; + } + */ + /* + if ( !aifl.scripted ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT ); + } + */ + healing = false; + if ( !aifl.scripted ) { + ProcessEvent( &AI_BecomeAggressive ); + combat.fl.aware = wasAware; + combat.fl.ignoreEnemies = wasIgnoreEnemies; + lookTarget = NULL; + } else if ( lookTarget == patient ) { + //if scripted, clear the looktarget only if it's the patient? This could be wrong, though.... + lookTarget = NULL; + } + + patient = NULL; + + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } + + ForceTacticalUpdate(); + if ( !aifl.scripted ) { + UpdateTactical ( 0 ); + } + //FIXME: what if they stay in this state? + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +void rvAIMedic::SetHealValues( idPlayer* player ) +{ + if ( !player ) + { + return; + } + if ( isTech ) + { + curHealValue = player->inventory.armor; + maxPatientValue = player->inventory.maxarmor; + } + else + { + curHealValue = player->health; + maxPatientValue = player->inventory.maxHealth; + } +} + +bool rvAIMedic::CheckTakePatient( idPlayer* player ) +{ + if ( !player ) + { + return false; + } + if ( player->IsHidden() ) + { + return false; + } + if ( player->health <= 0 ) + { + return false; + } + SetHealValues( player ); + + if ( curHealValue < maxPatientValue ) + {//they are hurt + if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) ) + {//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one) + if ( DistanceTo( player ) < patientRange ) + {//close enough + if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange ) + {//either I am allowed to move or player is close enough that I don't have to + //if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) ) + {//not tethered or patient is in our tether + if ( emergencyOverride || CanSee( player, false ) ) + {//can see the patient - OR: just check PVS? + TakePatient( player ); + return true; + } + } + } + } + } + } + + return false; +} + +bool rvAIMedic::SituationAllowsPatient( void ) +{ + if ( !aifl.scripted && !IsHidden() && !aifl.dead ) + {//not scripted right now + if ( combat.fl.ignoreEnemies ) { + return true; + } + bool enemyInPVS = false; + bool enemyInRange = false; + if ( GetEnemy() ) + {//sorry, we have to bail on you! + enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange); + if ( enemyInRange ) { + pvsHandle_t pvs; + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) { + enemyInPVS = true; + emergencyOverride = false; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + } + if ( emergencyOverride ) + {//don't care how crazy it is, go for it! + return true; + } + if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 ) + {//haven't had an enemy for 5 seconds + if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) ) + {//local buddies don't have enemies + //NOTE: which buddies and enemies are local can change as we head to our patient! + return true; + } + } + } + return false; +} + +bool rvAIMedic::AvailableToTakePatient( void ) +{ + if ( !healDisabled ) + {//not forced to disabled + if ( !aifl.action ) + {//not in the middle of an action + if ( !patient ) + {//don't already have a patient + if ( !IsSpeaking() ) + {//not talking + return SituationAllowsPatient(); + } + } + } + } + return false; +} + +/* +================ +rvAIMedic::Think +================ +*/ +void rvAIMedic::Think ( void ) { + rvAITactical::Think ( ); + +// while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1") +//??? + if ( !noAutoHeal ) + { + if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 ) + { + lastPatientCheckTime = gameLocal.GetTime(); + if ( !patient ) + { + emergencyOverride = false; + } + if ( AvailableToTakePatient() ) + { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( CheckTakePatient( player ) ) + { + return; + } + //otherwise, check team? + /* + idActor* actor; + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) + { + if ( CheckTakePatient( actor ) ) + { + return; + } + } + */ + } + } + } +} + +/* +===================== +rvAIMedic::OnStateThreadClear +===================== +*/ +void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) { + if ( idStr::Icmp( statename, "State_Medic" ) ) { + if ( patient ) { + //BAH! Someone changed our state on us! + if ( lookTarget == patient ) { + lookTarget = NULL; + } + patient = NULL; + healing = false; + } + } +} + +/* +============ +rvAIMedic::OnStartMoving +============ +*/ +void rvAIMedic::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( patient ) + {//we were trying to heal! + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//being told to leave the patient + //abort the heal, for now + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + } +} + +/* +===================== +rvAIMedic::Pain +===================== +*/ +bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( retVal && patient ) { + //if get hit while trying to heal, protect ourselves + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + StopMove ( MOVE_STATUS_DONE ); + } + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + return retVal; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAIMedic ) + STATE ( "State_Medic", rvAIMedic::State_Medic ) +END_CLASS_STATES + +/* +================ +idAI::State_Medic + +Rush towards the patient to melee range and heal until they're okay +================ +*/ +stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the patient, speak & start anim + STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim + STAGE_HEAL_START_WAIT,// Wait for start anim to finish + STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed + STAGE_WAIT_FINISH // Finish anim + }; + if ( !patient || patient->health <= 0 || !SituationAllowsPatient() ) + {//patient dead or situation is bad + //NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume? + StopMove ( MOVE_STATUS_DONE ); + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + + if ( !move.fl.done ) + { + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//something stomped our move, give it up, for now... :/ + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + } + + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) + { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) ) + {//wtf, we're not there yet, try again! + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + } + else + {//we're there! + if ( !healing ) + { + SetHealValues( patient ); + if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent ) + {//didn't speak in last couple seconds + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_heal_start_tech_", true ); + } else { + Speak( "lipsync_heal_start_", true ); + } + } + } + StopMove ( MOVE_STATUS_DONE ); + healing = true; + // check for preHeal anim key and if it's present, play it first. + const char *preHealAnim; + if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim )) + { + PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 ); + return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT ); + } + else // otherwise just use the regular anim to start healing + { + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + } + } + if ( !stationary && move.range != 42.0f ) + { + //MCG: if you ever get this assert, please call me over so I can debug it! + assert(move.range==42.0f); + move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_( + } + /* + else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) ) + {//we're here, just stop + Speak( "lipsync_medic_arrive", true ); + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + */ + + // Perform actions on the way to the patient + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + } + + return SRESULT_WAIT; + + // intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity + case STAGE_PRE_HEAL_ANIM_WAIT: + const char *preHealAnim; + spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim )) + {//finished or interrupted + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + return SRESULT_WAIT; + + + case STAGE_HEAL_START_WAIT: + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) ) + {//finished or interrupted +// PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + //show the tool, just in case the anim was interrupted + ShowAttachment( spawnArgs.GetString("def_attach") ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + return SRESULT_WAIT; + + case STAGE_HEAL: + { + SetHealValues( patient ); + /* + if ( curHealValue >= maxPatientValue + || (curHealValue > minHealValue && healedAmount >= healAmt) ) + {//patient fully healed (or we've used up our allotment), we're done here + Speak( "lipsync_heal_end_", true ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + */ + + // If we are out of melee range or lost sight of our patient then start moving again + /* + if ( !stationary && !move.fl.disabled ) { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) { + MoveToEntity( patient, 42 ); + Speak( "lipsync_heal_move_", true ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_STAGE ( STAGE_MOVE ); + } + } + */ + + /* + combat.fl.ignoreEnemies = true; + */ + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + + // Always face patient when in melee range + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + // Perform actions while standing still + /* + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + combat.tacticalUpdateTime = gameLocal.GetTime(); + if ( UpdateTactical( 100000 ) ) { + DropPatient(); + return SRESULT_DONE_WAIT; + } + } + */ + + //jshepard: tech/medic dependent speech + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) { + if ( !isTech ) { + patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt; + if( !silent) { + Speak( "lipsync_heal_end_", true ); + } + } else { + patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;; + if( !silent) { + Speak( "lipsync_heal_end_tech_", true ); + } + } + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + if ( gameLocal.random.RandomFloat() > 0.5f ) + { + if ( !isTech ) + { + if ( patient->health < maxPatientValue ) { + patient->health++; + } + } + else + { + if ( patient->inventory.armor < maxPatientValue ) { + patient->inventory.armor++; + } + } + } + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) ) + {//finished or interrupted + //turn off the tool, just in case we were interrupted + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/AI_Medic.h b/source/mpgame/ai/AI_Medic.h new file mode 100644 index 0000000..b5c46d4 --- /dev/null +++ b/source/mpgame/ai/AI_Medic.h @@ -0,0 +1,92 @@ +/* +================ + +AI_Medic.h + +================ +*/ +#include "AI_Tactical.h" + +#ifndef __AI_MEDIC__ +#define __AI_MEDIC__ + +class rvAIMedic : public rvAITactical { +public: + + CLASS_PROTOTYPE( rvAIMedic ); + + rvAIMedic ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Show ( void ); + + virtual void TalkTo ( idActor *actor ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool IsTethered ( void ) const; + + virtual void OnStateThreadClear ( const char *statename, int flags ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + bool isTech; + +protected: + virtual void OnStartMoving ( void ); + +private: + + idEntityPtr patient; + bool healing; + int lastPatientCheckTime; + bool emergencyOverride; + + bool noAutoHeal; + bool stationary; + bool silent; + bool healObeyTether; + int healAmt; + float patientRange; + float buddyRange; + float enemyRange; + + int curHealValue; + int maxHealValue; + int minHealValue; + int healedAmount; + int maxPatientValue; + + int healDebounceInterval; + int healDebounceTime; + + bool healDisabled; + bool wasAware; + bool wasIgnoreEnemies; + + void SetHealValues ( idPlayer* player ); + + void TakePatient ( idPlayer* pPatient ); + void DropPatient ( void ); + bool CheckTakePatient ( idPlayer* actor ); + bool SituationAllowsPatient ( void ); + bool AvailableToTakePatient ( void ); + + stateResult_t State_Medic ( const stateParms_t& parms ); + + void Event_EnableHeal ( void ); + void Event_DisableHeal ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + + rvScriptFuncUtility mPostHealScript; // script to run after completing a heal + + CLASS_STATES_PROTOTYPE ( rvAIMedic ); +}; + +#endif /* !__AI_MEDIC__ */ diff --git a/source/mpgame/ai/AI_Move.cpp b/source/mpgame/ai/AI_Move.cpp new file mode 100644 index 0000000..2a689b3 --- /dev/null +++ b/source/mpgame/ai/AI_Move.cpp @@ -0,0 +1,4832 @@ +/* +=============================================================================== + +AI_Move.cpp + +This file has all movement related functions. It and its sister H file were +split from AI.h and AI.cpp in order to prevent merge conflicts and to make +further changes to the system possible. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + idMoveState + +=============================================================================== +*/ + +/* +===================== +idMoveState::idMoveState +===================== +*/ +idMoveState::idMoveState() { + moveType = MOVETYPE_ANIM; + moveCommand = MOVE_NONE; + moveStatus = MOVE_STATUS_DONE; + moveDest.Zero(); + moveDir.Set( 1.0f, 0.0f, 0.0f ); + goalEntity = NULL; + goalEntityOrigin.Zero(); + toAreaNum = 0; + startTime = 0; + duration = 0; + speed = 0.0f; + range = 0.0f; + wanderYaw = 0; + nextWanderTime = 0; + blockTime = 0; + obstacle = NULL; + lastMoveOrigin = vec3_origin; + lastMoveTime = 0; + anim = 0; + travelFlags = TFL_WALK|TFL_AIR; + kickForce = 2048.0f; + fl.ignoreObstacles = false; + fl.allowAnimMove = false; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + blockedRadius = 0.0f; + blockedMoveTime = 750; + blockedAttackTime = 750; + turnRate = 360.0f; + turnVel = 0.0f; + anim_turn_yaw = 0.0f; + anim_turn_amount = 0.0f; + anim_turn_angles = 0.0f; + fly_offset = 0; + fly_seek_scale = 1.0f; + fly_roll_scale = 0.0f; + fly_roll_max = 0.0f; + fly_roll = 0.0f; + fly_pitch_scale = 0.0f; + fly_pitch_max = 0.0f; + fly_pitch = 0.0f; + fly_speed = 0.0f; + fly_bob_strength = 0.0f; + fly_bob_vert = 0.0f; + fly_bob_horz = 0.0f; + currentDirection = MOVEDIR_FORWARD; + idealDirection = MOVEDIR_FORWARD; + current_yaw = 0.0f; + ideal_yaw = 0.0f; + flyTiltJoint = INVALID_JOINT; + addVelocity.Zero(); +} + + + +/* +===================== +idMoveState::Spawn +===================== +*/ +void idMoveState::Spawn( idDict &spawnArgs ) { + memset ( &fl, 0, sizeof(fl) ); + fl.allowAnimMove = true; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + fl.noRun = spawnArgs.GetBool ( "forceWalk", "0" ); + fl.noWalk = spawnArgs.GetBool ( "forceRun", "0" ); + fl.noTurn = spawnArgs.GetBool ( "noTurn", "0" ); + fl.noGravity = spawnArgs.GetBool ( "animate_z", "0" ); + fl.noRangedInterrupt = spawnArgs.GetBool ( "noRangedInterrupt", "0" ); + + fl.flyTurning = spawnArgs.GetBool ( "flyTurning", "0" ); + fl.allowDirectional = spawnArgs.GetBool ( "directionalMovement", "0" ); + fl.allowPushMovables = spawnArgs.GetBool( "af_push_moveables", "0" ); + fl.ignoreObstacles = spawnArgs.GetBool( "ignore_obstacles", "0" ); + fl.disabled = spawnArgs.GetBool ( "noMove", "0" ); + walkRange = spawnArgs.GetFloat ( "walkRange", "100" ); + walkTurn = spawnArgs.GetFloat ( "walkTurn", "45" ); + followRange = spawnArgs.GetVec2 ( "followRange", "70 250" ); + searchRange = spawnArgs.GetVec2 ( "searchRange", "0 1024" ); + attackPositionRange = spawnArgs.GetFloat ( "attackPositionRange", "0" ); + turnDelta = spawnArgs.GetFloat ( "turnDelta", "10" ); + blockTime = 0; + + spawnArgs.GetInt( "fly_offset", "100", fly_offset ); + spawnArgs.GetFloat( "fly_speed", "100", fly_speed ); + spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength ); + spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz ); + spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert ); + spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale ); + spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale ); + spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max ); + spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale ); + spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max ); + + spawnArgs.GetFloat( "turn_rate", "360", turnRate ); + spawnArgs.GetFloat( "kick_force", "4096", kickForce ); + spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius ); + spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime ); + spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime ); +} + + +/* +===================== +idMoveState::Save +===================== +*/ +void idMoveState::Save( idSaveGame *savefile ) const { + int i; + + savefile->Write ( &fl, sizeof(fl) ); + + savefile->WriteInt( (int)moveType ); + savefile->WriteInt( (int)moveCommand ); + savefile->WriteInt( (int)moveStatus ); + savefile->WriteVec3( moveDest ); + savefile->WriteVec3( moveDir ); + + savefile->WriteInt( toAreaNum ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( range ); + savefile->WriteFloat( wanderYaw ); + savefile->WriteInt( nextWanderTime ); + savefile->WriteInt( blockTime ); + obstacle.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( lastMoveTime ); + savefile->WriteInt( anim ); + + savefile->WriteInt( travelFlags ); + + savefile->WriteFloat ( kickForce ); + savefile->WriteFloat ( blockedRadius ); + savefile->WriteInt ( blockedMoveTime ); + savefile->WriteInt ( blockedAttackTime ); + + savefile->WriteFloat( ideal_yaw ); + savefile->WriteFloat( current_yaw ); + savefile->WriteFloat( turnRate ); + savefile->WriteFloat( turnVel ); + savefile->WriteFloat( anim_turn_yaw ); + savefile->WriteFloat( anim_turn_amount ); + savefile->WriteFloat( anim_turn_angles ); + + savefile->WriteJoint( flyTiltJoint ); + savefile->WriteFloat( fly_speed ); + savefile->WriteFloat( fly_bob_strength ); + savefile->WriteFloat( fly_bob_vert ); + savefile->WriteFloat( fly_bob_horz ); + savefile->WriteInt( fly_offset ); + savefile->WriteFloat( fly_seek_scale ); + savefile->WriteFloat( fly_roll_scale ); + savefile->WriteFloat( fly_roll_max ); + savefile->WriteFloat( fly_roll ); + savefile->WriteFloat( fly_pitch_scale ); + savefile->WriteFloat( fly_pitch_max ); + savefile->WriteFloat( fly_pitch ); + + savefile->WriteInt ( (int) currentDirection ); + savefile->WriteInt ( (int) idealDirection ); + savefile->WriteFloat( walkRange ); + savefile->WriteFloat( walkTurn ); + savefile->WriteVec2 ( followRange ); + savefile->WriteVec2 ( searchRange ); + savefile->WriteFloat( attackPositionRange ); + savefile->WriteFloat( turnDelta ); + + savefile->WriteVec3 ( goalPos ); + savefile->WriteInt ( goalArea ); + goalEntity.Save( savefile ); + savefile->WriteVec3( goalEntityOrigin ); + + savefile->WriteVec3( myPos ); // cnicholson: Added unsaved var + savefile->WriteInt( myArea ); // cnicholson: Added unsaved var + + savefile->WriteVec3 ( seekPos ); + + savefile->WriteInt( (int) MAX_PATH_LEN ); // cnicholson: Added unsaved vars + for (i=0; i< MAX_PATH_LEN; ++i) { + // TOSAVE: idReachability* reach; + savefile->WriteVec3( path[i].seekPos ); + } + + savefile->WriteInt( pathLen ); // cnicholson: Added unsaved var + savefile->WriteInt( pathArea ); // cnicholson: Added unsaved var + savefile->WriteInt( pathTime ); // cnicholson: Added unsaved var + + savefile->WriteVec3( addVelocity ); +} + +/* +===================== +idMoveState::Restore +===================== +*/ +void idMoveState::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->Read ( &fl, sizeof(fl) ); + + savefile->ReadInt( (int&)moveType ); + savefile->ReadInt( (int&)moveCommand ); + savefile->ReadInt( (int&)moveStatus ); + savefile->ReadVec3( moveDest ); + savefile->ReadVec3( moveDir ); + + savefile->ReadInt( toAreaNum ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( range ); + savefile->ReadFloat( wanderYaw ); + savefile->ReadInt( nextWanderTime ); + savefile->ReadInt( blockTime ); + obstacle.Restore ( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( lastMoveTime ); + savefile->ReadInt( anim ); + + savefile->ReadInt( travelFlags ); + + savefile->ReadFloat ( kickForce ); + savefile->ReadFloat ( blockedRadius ); + savefile->ReadInt ( blockedMoveTime ); + savefile->ReadInt ( blockedAttackTime ); + + savefile->ReadFloat( ideal_yaw ); + savefile->ReadFloat( current_yaw ); + savefile->ReadFloat( turnRate ); + savefile->ReadFloat( turnVel ); + savefile->ReadFloat( anim_turn_yaw ); + savefile->ReadFloat( anim_turn_amount ); + savefile->ReadFloat( anim_turn_angles ); + + savefile->ReadJoint( flyTiltJoint ); + savefile->ReadFloat( fly_speed ); + savefile->ReadFloat( fly_bob_strength ); + savefile->ReadFloat( fly_bob_vert ); + savefile->ReadFloat( fly_bob_horz ); + savefile->ReadInt( fly_offset ); + savefile->ReadFloat( fly_seek_scale ); + savefile->ReadFloat( fly_roll_scale ); + savefile->ReadFloat( fly_roll_max ); + savefile->ReadFloat( fly_roll ); + savefile->ReadFloat( fly_pitch_scale ); + savefile->ReadFloat( fly_pitch_max ); + savefile->ReadFloat( fly_pitch ); + + savefile->ReadInt ( (int&) currentDirection ); + savefile->ReadInt ( (int&) idealDirection ); + savefile->ReadFloat( walkRange ); + savefile->ReadFloat( walkTurn ); + savefile->ReadVec2 ( followRange ); + savefile->ReadVec2 ( searchRange ); + savefile->ReadFloat( attackPositionRange ); + savefile->ReadFloat( turnDelta ); + + savefile->ReadVec3 ( goalPos ); + savefile->ReadInt ( goalArea ); + goalEntity.Restore ( savefile ); + savefile->ReadVec3( goalEntityOrigin ); + + savefile->ReadVec3( myPos ); // cnicholson: Added unrestored var + savefile->ReadInt( myArea ); // cnicholson: Added unrestored var + + savefile->ReadVec3 ( seekPos ); + + savefile->ReadInt( num ); + for (i=0; i< num; ++i) { + // TOSAVE: idReachability* reach; + savefile->ReadVec3( path[i].seekPos ); + } + + savefile->ReadInt( pathLen ); // cnicholson: Added unrestored var + savefile->ReadInt( pathArea ); // cnicholson: Added unrestored var + savefile->ReadInt( pathTime ); // cnicholson: Added unrestored var + + savefile->ReadVec3( addVelocity ); +} + + +/* +============ +idAI::SetMoveType +============ +*/ +void idAI::SetMoveType ( moveType_t moveType ) { + if ( move.moveType == moveType ) { + return; + } + + StopSound ( SND_CHANNEL_HEART, false ); + + switch ( moveType ) { + case MOVETYPE_STATIC: + move.travelFlags = 0; + break; + + case MOVETYPE_FLY: + move.travelFlags = TFL_FLY|TFL_WALK|TFL_AIR; + StartSound ( "snd_fly", SND_CHANNEL_HEART, 0, false, NULL ); + break; + + case MOVETYPE_ANIM: + move.travelFlags = TFL_WALK|TFL_AIR; + break; + + case MOVETYPE_CUSTOM: + move.travelFlags = TFL_WALK|TFL_AIR|TFL_WALKOFFLEDGE; + break; + } + + move.moveType = moveType; +} + +/* +===================== +idAI::Event_SaveMove +===================== +*/ +void idAI::Event_SaveMove( void ) { + savedMove = move; +} + +/* +===================== +idAI::Event_RestoreMove +===================== +*/ +void idAI::Event_RestoreMove( void ) { + idVec3 dest; + + switch( savedMove.moveCommand ) { + case MOVE_NONE : + StopMove( savedMove.moveStatus ); + break; + + case MOVE_FACE_ENEMY : + FaceEnemy(); + break; + + case MOVE_FACE_ENTITY : + FaceEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_TO_ENEMY : + MoveToEnemy(); + break; + + case MOVE_TO_ENTITY : + MoveToEntity( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_OUT_OF_RANGE : + MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_TO_ATTACK: + MoveToAttack ( savedMove.goalEntity.GetEntity(), savedMove.anim ); + break; + + case MOVE_TO_COVER : + MoveToCover ( combat.attackRange[0], combat.attackRange[1], AITACTICAL_COVER ); + break; + + case MOVE_TO_POSITION : + MoveTo ( savedMove.moveDest, savedMove.range ); + break; + + case MOVE_SLIDE_TO_POSITION : + SlideToPosition( savedMove.moveDest, savedMove.duration ); + break; + + case MOVE_WANDER : + WanderAround(); + break; + } + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, dest ); + } +} + +/* +============ +idAI::KickObstacles +============ +*/ +void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) { + int i, numListedClipModels; + idBounds clipBounds; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + int clipmask; + idVec3 org; + idVec3 forceVec; + idVec3 delta; + idVec2 perpendicular; + + org = physicsObj.GetOrigin(); + + // find all possible obstacles + clipBounds = physicsObj.GetAbsBounds(); + clipBounds.TranslateSelf( dir * 32.0f ); + clipBounds.ExpandSelf( 8.0f ); + clipBounds.AddPoint( org ); + clipmask = physicsObj.GetClipMask(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( this, clipBounds, clipmask, clipModelList, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < numListedClipModels; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + if ( obEnt == alwaysKick ) { + // we'll kick this one outside the loop + continue; + } + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if (( obEnt->IsType( idMoveable::GetClassType() ) || obEnt->IsType( idAFAttachment::GetClassType() )) && obEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + delta = obEnt->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * obEnt->GetPhysics()->GetMass(); + obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec ); + } + } + + if ( alwaysKick ) { + delta = alwaysKick->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * alwaysKick->GetPhysics()->GetMass(); + alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec ); + } + +} + +/* +============ +ValidForBounds +============ +*/ +bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +===================== +idAI::SetAAS +===================== +*/ +void idAI::SetAAS( void ) { + idStr use_aas; + + spawnArgs.GetString( "use_aas", NULL, use_aas ); + if ( !use_aas || !use_aas[0] ) { + //don't intend to use AAS at all? + //no warning - lack of AAS intentional + aas = NULL; + return; + } + aas = gameLocal.GetAAS( use_aas ); + if ( aas ) { + const idAASSettings *settings = aas->GetSettings(); + if ( settings ) { + if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) { + gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() ); + } + float height = settings->maxStepHeight; + physicsObj.SetMaxStepHeight( height ); + return; + } else { + aas = NULL; + } + } + gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() ); +} + +/* +===================== +idAI::ReachedPos +===================== +*/ +bool idAI::ReachedPos( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range ) const { + // When moving towards the enemy just see if our bounding box touches the desination + if ( moveCommand == MOVE_TO_ENEMY ) { + if ( !enemy.ent || physicsObj.GetAbsBounds().IntersectsBounds( enemy.ent->GetPhysics()->GetAbsBounds().Expand( range ) ) ) { + return true; + } + return false; + } + + // Dont add vertical bias when using fly move + if ( move.moveType == MOVETYPE_FLY ) { + float offset; + if ( moveCommand == MOVE_TO_ENTITY ) { + offset = 0.0f; + } else { + offset = move.fly_offset; + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-range), idVec3(range,range,range+offset) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + + if ( moveCommand == MOVE_TO_TETHER ) + {//if you're not actually in the tether, we don't want to stop early (for compliance with ai_trigger condition_tether) + if ( !IsWithinTether() ) + { + return false; + } + } + + // Excluded z height when determining reached + if ( move.toAreaNum > 0 ) { + if ( PointReachableAreaNum( physicsObj.GetOrigin() ) == move.toAreaNum ) { + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-4096.0f), idVec3(range,range,4096.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-16.0f), idVec3(range,range,64.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); +} + +/* +===================== +idAI::PointReachableAreaNum +===================== +*/ +int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const { + int areaNum; + idVec3 size; + idBounds bounds; + + if ( !aas ) { + return 0; + } + + size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + if ( move.moveType == MOVETYPE_FLY ) { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ); + } else { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + } + + return areaNum; +} + +/* +===================== +idAI::PathToGoal +===================== +*/ +bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + idVec3 org; + idVec3 goal; + + if ( !aas ) { + return false; + } + + org = origin; + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !areaNum ) { + return false; + } + + goal = goalOrigin; + aas->PushPointIntoAreaNum( goalAreaNum, goal ); + if ( !goalAreaNum ) { + return false; + } + //push goal back up if flying to a point way above an area + if ( move.moveType == MOVETYPE_FLY ) { + if ( areaNum == goalAreaNum ) { + if ( goalOrigin.z > goal.z ) { + float areaTop = aas->AreaCeiling( goalAreaNum ) - GetPhysics()->GetBounds()[1].z; + if ( goalOrigin.z > areaTop ) { + //crap, cap it... + goal.z = areaTop; + } else { + goal.z = goalOrigin.z; + } + } + } + } + + + if ( move.moveType == MOVETYPE_FLY ) { + return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } else { + return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } +} + +/* +===================== +idAI::TravelDistance + +Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance. + +This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you +are from the goal, so try to break the goals up into shorter distances. +===================== +*/ +float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const { + int fromArea; + int toArea; + float dist; + idVec2 delta; + aasPath_t path; + + if ( !aas ) { + // no aas, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + fromArea = PointReachableAreaNum( start ); + toArea = PointReachableAreaNum( end ); + + if ( !fromArea || !toArea ) { + // can't seem to get there + return -1; + } + + if ( fromArea == toArea ) { + // same area, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + idReachability *reach; + int travelTime; + if ( !aas->RouteToGoalArea( fromArea, start, toArea, move.travelFlags, travelTime, &reach ) ) { + return -1; + } + + if ( DebugFilter(ai_debugMove) ) { // Travel Distance, Fly Path & Walk Path + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( start, toArea, end ); + } else { + aas->ShowWalkPath( start, toArea, end ); + } + } + + return travelTime; +} + +float idAI::TravelDistance ( idEntity *ent ) const { + return TravelDistance ( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( idEntity* start, idEntity* end ) const { + assert( start ); + assert( end ); + return TravelDistance( start->GetPhysics()->GetOrigin(), end->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( const idVec3 &pos ) const { + return TravelDistance( physicsObj.GetOrigin(), pos ); +} + +/* +============ +idAI::ScriptedPlaybackMove +============ +*/ +void idAI::ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + // Start the playback + if ( !mPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ) ) { + ScriptedEnd ( ); + return; + } + + move.goalEntity = NULL; + move.moveCommand = MOVE_RV_PLAYBACK; + move.moveType = MOVETYPE_PLAYBACK; + move.fl.done = false; + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +===================== +idAI::FaceEnemy + +Continually face the enemy's last known position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEnemy( void ) { + idEntity *enemyEnt = enemy.ent; + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + TurnToward( enemy.lastKnownPosition ); + move.goalEntity = enemyEnt; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENEMY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::FaceEntity + +Continually face the entity position. MoveDone will never be true in this case. +===================== +*/ +bool idAI::FaceEntity( idEntity *ent ) { + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + idVec3 entityOrg = ent->GetPhysics()->GetOrigin(); + TurnToward( entityOrg ); + move.goalEntity = ent; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENTITY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = false; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::StartMove + +Initialize a new movement by setting up the movement structure +===================== +*/ +bool idAI::StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ) { + // If we are already there then we are done + if ( ReachedPos( goalOrigin, command ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.lastMoveOrigin = physicsObj.GetOrigin ( ); + + move.seekPos = move.goalPos = move.moveDest = goalOrigin; + move.toAreaNum = goalArea; + move.goalEntity = goalEntity; + move.moveCommand = command; + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = move.fly_speed; + move.startTime = gameLocal.time; + move.range = range; + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = true; + + aasSensor->Reserve ( feature ); + + OnStartMoving ( ); + + return true; +} + +/* +===================== +idAI::StopMove +===================== +*/ +void idAI::StopMove( moveStatus_t status ) { + aiMoveCommand_t oldCommand = move.moveCommand; + float saveZ = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + move.fl.obstacleInPath = false; + move.fl.blocked = false; + + if ( move.moveType == MOVETYPE_FLY ) { + if ( move.moveCommand == MOVE_TO_ENEMY + || move.moveCommand == MOVE_TO_ATTACK ) { + saveZ = (move.moveDest.z-physicsObj.GetOrigin().z); + } + } + move.moveCommand = MOVE_NONE; + move.moveStatus = status; + move.toAreaNum = 0; + move.goalEntity = NULL; + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += saveZ; + move.startTime = gameLocal.time; + move.duration = 0; + move.range = 0.0f; + move.speed = 0.0f; + move.anim = 0; + + move.moveDir.Zero(); + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + + // Callback for handling stopping + if ( oldCommand != MOVE_NONE ) { + OnStopMoving ( oldCommand ); + } +} + +/* +===================== +idAI::MoveToTether +===================== +*/ +bool idAI::MoveToTether ( rvAITether* tether ) { + aasGoal_t goal; + + // find a goal using the currently active tether + if ( !aas || !tether ) { + return false; + } + + if ( !tether->FindGoal ( this, goal ) ) { + //This is extremely bad - if 2 guys both try to get to the center of a tether, they get hosed. + return MoveTo ( tether->GetPhysics()->GetOrigin ( ), tether->GetOriginReachedRange() ); + } + + if ( move.moveType == MOVETYPE_FLY ) { + //Hmm... we shouldn't have to clamp as the area wouldn't have been valid unless the tether was in its bounds and height, but...? + //float areaTop = aas->AreaCeiling( goal.areaNum ); + goal.origin.z = tether->GetPhysics()->GetOrigin().z; + } + + return StartMove ( MOVE_TO_TETHER, goal.origin, goal.areaNum, NULL, NULL, AI_TETHER_MINRANGE ); +} + +/* +===================== +idAI::MoveToAttack +===================== +*/ +bool idAI::MoveToAttack ( idEntity *ent, int attack_anim ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( ent ); + + // if we havent started a find yet or the current find is something other than a attack find then start a new one + if ( !aasFind || !dynamic_cast(aasFind) ) { + // Allocate the new aas find + delete aasFind; + aasFind = new rvAASFindGoalForAttack ( this ); + + // Find a goal that gives us a viable attack position on our enemy + aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, IsTethered()?0:move.searchRange[0], move.searchRange[1], &obstacle, 1, *aasFind ); + } + + assert ( aasFind ); + + // Test some more points with the existing find + rvAASFindGoalForAttack* aasFindAttack = static_cast(aasFind); + if ( !aasFindAttack->TestCachedGoals ( aifl.simpleThink ? 1 : 4, goal ) ) { + delete aasFind; + aasFind = NULL; + return false; + } + + // Havent found a goal yet but exhaused our trace count + if ( !goal.areaNum ) { + return false; + } + + // Dont need the find anymore + delete aasFind; + aasFind = NULL; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(goal.areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( pos.z > areaTop ) { + goal.origin.z = areaTop; + } else if ( pos.z > goal.origin.z+1.0f ) { + goal.origin.z = pos.z; + } else if ( move.fly_offset > 0 ) { + if ( (goal.origin.z+move.fly_offset) > areaTop ) { + goal.origin.z = areaTop; + } else { + goal.origin.z += move.fly_offset; + } + } + } + } + + return StartMove ( MOVE_TO_ATTACK, goal.origin, goal.areaNum, ent, NULL, move.attackPositionRange?move.attackPositionRange:8.0f ); +} + +/* +===================== +idAI::MoveToEnemy +===================== +*/ +bool idAI::MoveToEnemy( void ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !enemy.ent ) { + return false; + } + +// pos = LastKnownPosition ( enemy.ent ); + pos = enemy.ent->GetPhysics()->GetOrigin ( ); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent && move.goalEntityOrigin == pos ) { + return true; + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENEMY, 8.0f ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = pos; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( move.fly_offset > 0 ) { + if ( (pos.z+move.fly_offset) > areaTop ) { + pos.z = areaTop; + move.goalEntityOrigin.z = areaTop; + } else { + pos.z += move.fly_offset; + move.goalEntityOrigin.z += move.fly_offset; + } + } + } + } + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + move.moveDest = pos; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENEMY, pos, areaNum, enemy.ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveToEntity +===================== +*/ +bool idAI::MoveToEntity( idEntity *ent, float range ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !ent ) { + return false; + } + + // Where do we want to go? + pos = ent->GetPhysics()->GetOrigin(); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent && move.goalEntityOrigin == pos ) { + return true; + } + + // If we arent flying we should move to a position on the floor + if ( move.moveType != MOVETYPE_FLY ) { + ent->GetFloorPos( 64.0f, pos ); + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENTITY, range ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = ent->GetPhysics()->GetOrigin(); + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent ) { + move.moveDest = pos; + move.range = range <= 0.0f ? 8.0f : range; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENTITY, pos, areaNum, ent, NULL, range <= 0.0f ? 8.0f : range ); +} + +/* +===================== +idAI::MoveOutOfRange +===================== +*/ +bool idAI::MoveOutOfRange( idEntity *ent, float range, float minRange ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + int obstacles; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + // consider the entity the monster is getting close to as an obstacle + if ( ent != this ) { + obstacles = 1; + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + } else { + obstacles = 0; + } + + pos = LastKnownPosition ( ent ); + + // Find a goal out of range of where we are + rvAASFindGoalOutOfRange findGoal( this ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, minRange, range, &obstacle, obstacles, findGoal ) ) { + return false; + } + + return StartMove ( MOVE_OUT_OF_RANGE, goal.origin, goal.areaNum, ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveTo +===================== +*/ +bool idAI::MoveTo ( const idVec3 &pos, float range ) { + idVec3 org; + int areaNum; + aasPath_t path; + + if ( !aas ) { + return false; + } + + org = pos; + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // Can we get to where we want to go? + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), PointReachableAreaNum( physicsObj.GetOrigin() ), org ) ) { + return false; + } + + // Start moving + return StartMove ( MOVE_TO_POSITION, org, areaNum, NULL, NULL, range ); +} + +/* +===================== +idAI::MoveToCover +===================== +*/ +bool idAI::MoveToCover( float minRange, float maxRange, aiTactical_t coverType ) { + idVec3 org; + int areaNum; + aasPath_t path; + aasFeature_t* feature = 0; + idVec3 featureOrigin; + + if ( !aas ) { + return false; + } + + // Look for nearby cover + switch ( coverType ) { + case AITACTICAL_HIDE: aasSensor->SearchHide(); break; + case AITACTICAL_COVER_FLANK: aasSensor->SearchFlank(); break; + case AITACTICAL_COVER_ADVANCE: aasSensor->SearchAdvance(); break; + case AITACTICAL_COVER_RETREAT: aasSensor->SearchRetreat(); break; + case AITACTICAL_COVER_AMBUSH: aasSensor->SearchAmbush(); break; + case AITACTICAL_COVER: + default: + aasSensor->SearchCover(); + break; + } + + if ( !aasSensor->FeatureCount ( ) ) { + return false; + } + + feature = aasSensor->Feature ( 0 ); + featureOrigin = feature->Origin ( ); + org = featureOrigin; + areaNum = 0; + + // Find the aas area our cover point is in + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // See if there is a path to our goal or not + aas->PushPointIntoAreaNum( areaNum, org ); +/* + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, org ) ) { + return false; + } +*/ + + combat.coverValidTime = gameLocal.time; + + // Start the move + return StartMove ( MOVE_TO_COVER, org, areaNum, enemy.ent, feature, AI_COVER_MINRANGE / 2.0f ); +} + +/* +===================== +idAI::MoveToHide +===================== +*/ +bool idAI::MoveToHide ( void ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + // Need an enemy to hide from + if ( !aas || !enemy.ent ) { + return false; + } + + const idVec3& org = physicsObj.GetOrigin(); + obstacle.absBounds = enemy.ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( enemy.ent ); + + // Search aas for a suitable goal + rvAASFindGoalForHide findGoal( pos ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, 0.0f, 0.0f, &obstacle, 1, findGoal ) ) { + return false; + } + + // Start the movement + return StartMove ( MOVE_TO_HIDE, goal.origin, goal.areaNum, enemy.ent, NULL, 0.0f ); +} + +/* +===================== +idAI::SlideToPosition +===================== +*/ +bool idAI::SlideToPosition( const idVec3 &pos, float time ) { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_SLIDE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = false; + aifl.simpleThink = false; + + move.fl.allowAnimMove = false; + + if ( move.duration > 0 ) { + move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration ); + if ( move.moveType != MOVETYPE_FLY ) { + move.moveDir.z = 0.0f; + } + move.speed = move.moveDir.LengthFast(); + } + + return true; +} + +/* +===================== +idAI::WanderAround +===================== +*/ +bool idAI::WanderAround( void ) { + idVec3 dest; + + StopMove( MOVE_STATUS_DONE ); + + dest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + if ( !NewWanderDir( dest ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + return false; + } + + return StartMove ( MOVE_WANDER, dest, 0, NULL, NULL, 0.0f ); +} + +/* +================ +idAI::StepDirection +================ +*/ +bool idAI::StepDirection( float dir ) { + predictedPath_t path; + idVec3 org; + + move.wanderYaw = dir; + move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward(); + + org = physicsObj.GetOrigin(); + + idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) { + // don't report being blocked if we ran into our goal entity + return true; + } + + if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) { + float z; + + move.moveDir = path.endVelocity * 1.0f / 48.0f; + + // trace down to the floor and see if we can go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 floorPos = path.endPos; + idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = -1.0f; + return true; + } + + // trace up to see if we can go over something and go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 ceilingPos = path.endPos; + + for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) { + idVec3 start; + if ( z <= ceilingPos.z ) { + start.x = org.x; + start.y = org.y; + start.z = z; + } else { + start = ceilingPos; + } + idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = 1.0f; + return true; + } + } + return false; + } + + return ( path.endEvent == 0 ); +} + +/* +================ +idAI::NewWanderDir +================ +*/ +bool idAI::NewWanderDir( const idVec3 &dest ) { + float deltax, deltay; + float d[ 3 ]; + float tdir, olddir, turnaround; + + move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 ); + + olddir = idMath::AngleNormalize360( ( int )( move.current_yaw / 45 ) * 45 ); + turnaround = idMath::AngleNormalize360( olddir - 180 ); + + idVec3 org = physicsObj.GetOrigin(); + deltax = dest.x - org.x; + deltay = dest.y - org.y; + if ( deltax > 10 ) { + d[ 1 ]= 0; + } else if ( deltax < -10 ) { + d[ 1 ] = 180; + } else { + d[ 1 ] = DI_NODIR; + } + + if ( deltay < -10 ) { + d[ 2 ] = 270; + } else if ( deltay > 10 ) { + d[ 2 ] = 90; + } else { + d[ 2 ] = DI_NODIR; + } + + // try direct route + if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) { + if ( d[ 1 ] == 0 ) { + tdir = d[ 2 ] == 90 ? 45 : 315; + } else { + tdir = d[ 2 ] == 90 ? 135 : 215; + } + + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + + // try other directions + if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) { + tdir = d[ 1 ]; + d[ 1 ] = d[ 2 ]; + d[ 2 ] = tdir; + } + + if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) { + return true; + } + + if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) { + return true; + } + + // there is no direct path to the player, so pick another direction + if ( olddir != DI_NODIR && StepDirection( olddir ) ) { + return true; + } + + // randomly determine direction of search + if ( gameLocal.random.RandomInt() & 1 ) { + for( tdir = 0; tdir <= 315; tdir += 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } else { + for ( tdir = 315; tdir >= 0; tdir -= 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } + + if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { + return true; + } + + // can't move + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; +} + +/* +===================== +idAI::GetMovePos +===================== +*/ +bool idAI::GetMovePos( idVec3 &seekPos, idReachability** seekReach ) { + int areaNum; + aasPath_t path; + bool result; + idVec3 org; + + org = physicsObj.GetOrigin(); + seekPos = org; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = 0; + } + // RAVEN END + + switch( move.moveCommand ) { + case MOVE_NONE : + seekPos = move.moveDest; + return false; + + case MOVE_FACE_ENEMY : + case MOVE_FACE_ENTITY : + seekPos = move.moveDest; + return false; + + case MOVE_TO_POSITION_DIRECT : + seekPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + return false; + + case MOVE_SLIDE_TO_POSITION : + seekPos = org; + return false; + + case MOVE_TO_ENTITY: + MoveToEntity( move.goalEntity.GetEntity(), move.range ); + break; + + case MOVE_TO_ENEMY: + if ( !MoveToEnemy() && combat.tacticalCurrent == AITACTICAL_MELEE ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; + } + break; + } + + if ( move.moveType == MOVETYPE_FLY && move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + //flying + if ( DistanceTo( move.moveDest ) < 1024.0f ) { + //less than huge translation dist, which is actually 4096, but, just to be safe... + trace_t moveTrace; + gameLocal.Translation( this, moveTrace, org, move.moveDest, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this, move.goalEntity.GetEntity() ); + if ( moveTrace.fraction >= 1.0f ) { + //can head straight for it, so do it. + seekPos = move.moveDest; + return false; + } + } + } + + move.moveStatus = MOVE_STATUS_MOVING; + result = false; + + if ( move.moveCommand == MOVE_WANDER ) { + move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + } else { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + seekPos = org; + return false; + } + } + + if ( aas && move.toAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) { + seekPos = path.moveGoal; + if ( aas->GetFile() ) { + //we have AAS + if ( move.fly_offset > 0 ) { + if ( (seekPos-move.moveDest).LengthSqr() > 10.0f ) { + //not heading to final dest, that already has offset in it + float areaTop = aas->AreaCeiling(path.moveAreaNum)-GetPhysics()->GetBounds()[1][2]; + if ( (seekPos.z+move.fly_offset) > areaTop ) { + seekPos.z = areaTop; + } else { + seekPos.z += move.fly_offset; + } + } + } + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = (idReachability*)(path.reachability); + } + // RAVEN END + + result = true; + move.nextWanderTime = 0; + } else { + move.fl.goalUnreachable = true; + } + } + + + if ( !result ) { + // wander around + if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) { + result = NewWanderDir( move.moveDest ); + if ( !result ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + seekPos = org; + + return false; + } + } else { + result = true; + } + + seekPos = org + move.moveDir * 2048.0f; + + } else { + move.fl.goalUnreachable = false; + } + + if ( DebugFilter(ai_debugMove) ) { // YELLOW = seekPos + gameRenderWorld->DebugLine( colorYellow, physicsObj.GetOrigin(), seekPos ); + } + + return result; +} + + + +/* +===================== +idAI::BlockedFailSafe +===================== +*/ +void idAI::BlockedFailSafe( void ) { +/* move.fl.blocked = false; + + if ( !ai_blockedFailSafe.GetBool() || move.blockedRadius < 0.0f ) { + return; + } + if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL || + ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( move.blockedRadius ) ) { + move.lastMoveOrigin = physicsObj.GetOrigin(); + move.lastMoveTime = gameLocal.time; + } + if ( move.lastMoveTime < gameLocal.time - move.blockedMoveTime ) { + if ( lastAttackTime < gameLocal.time - move.blockedAttackTime ) { + move.fl.blocked = true; + move.lastMoveTime = gameLocal.time; + } + }*/ +} + +/*********************************************************************** + + turning + +***********************************************************************/ + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( float yaw ) { + move.ideal_yaw = idMath::AngleNormalize180( yaw ); + bool result = FacingIdeal(); + return result; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + dir = pos - physicsObj.GetOrigin(); + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.ent == NULL ) ) { + move.ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + + return FacingIdeal(); +} + +/* +===================== +idAI::TurnTowardLeader +===================== +*/ +bool idAI::TurnTowardLeader( bool faceLeaderByDefault ) { + if ( !leader.GetEntity() ) { + return false; + } + //see if there's a wall in the direction the player's looking + trace_t tr; + + idVec3 leaderLookDest = leader->GetPhysics()->GetOrigin() + (( (leader.GetEntity() && leader.GetEntity()->IsType( idPlayer::GetClassType() )) ? ((idPlayer*)leader.GetEntity())->intentDir:leader->viewAxis[0])*300.0f); + idVec3 myLookDir = leaderLookDest-GetPhysics()->GetOrigin(); + myLookDir.Normalize(); + idVec3 start = GetPhysics()->GetOrigin(); + start.z += EyeHeight(); + idVec3 end = start + (myLookDir*128.0f); + end.z = start.z; + idVec3 currentLookDir = viewAxis[0]; + currentLookDir.Normalize(); + + if ( !GetEnemy() && (!leader->IsType(idPlayer::GetClassType()) || !((idPlayer*)leader.GetEntity())->IsFlashlightOn()) ) { + //Not in combat and leader isn't looking around with flashlight + if ( myLookDir*currentLookDir > 0.666f ) { + //new dir isn't different enough from current dir for me to care + return true; + } + } + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + idEntity* traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //wall there - NOTE: okay to look at doors + if ( faceLeaderByDefault//want to face leader by default + || leader->viewAxis[0].ToYaw() == move.ideal_yaw ) {//a wall must have moved in front of us? + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + //just keep looking in the last valid dir + if ( FacingIdeal() ) { + //make sure it's still valid + idVec3 end = start + (viewAxis[0]*128.0f); + end.z = start.z; + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //a wall right in front of us - NOTE: okay to look at doors + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + } + return true; + } + //opening, face leader's dir + return TurnToward( myLookDir.ToYaw() ); +} + +/* +============ +idAI::DirectionalTurnToward + +Turn toward the given point using directional movement +============ +*/ +bool idAI::DirectionalTurnToward ( const idVec3 &pos ) { + static float moveDirOffset [ MOVEDIR_MAX ] = { + 0.0f, 180.0f, -90.0f, 90.0f + }; + + // Issue standard TurnToward if we are not currently eligible for directional movement + if ( combat.tacticalCurrent != AITACTICAL_MOVE_PLAYERPUSH ) { + //always move directionally when getting out of the player's way + if( !combat.fl.aware || focusType < AIFOCUS_USE_DIRECTIONAL_MOVE || !move.fl.moving || move.moveCommand == MOVE_TO_ENEMY || !move.fl.allowDirectional ) { + move.idealDirection = MOVEDIR_FORWARD; + return TurnToward ( pos ); + } + } + + // Turn towards our desination + float moveYaw; + TurnToward ( pos ); + moveYaw = move.ideal_yaw; + + // Turn towards the goal entity and determine the angle difference + TurnToward ( currentFocusPos ); + + // Check for a direction change only when we can no longer see + // where we need to look and where we are looking is greater than 3/4ths the maximum look + if ( (pos - GetPhysics()->GetOrigin()).LengthFast ( ) > 8.0f ) + if ( !FacingIdeal ( ) ) + if ( fabs ( idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ) >= fabs(lookMax[YAW]) || + fabs ( idMath::AngleNormalize180 ( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) - move.current_yaw ) ) >= 80.0f ) { + + float diffYaw; + diffYaw = idMath::AngleNormalize180 ( move.ideal_yaw - moveYaw ); + + if ( diffYaw > -45.0f && diffYaw < 45.0f ) { + move.idealDirection = MOVEDIR_FORWARD; + } else if ( diffYaw < -135.0f || diffYaw > 135.0f ) { + move.idealDirection = MOVEDIR_BACKWARD; + } else if ( diffYaw < 0.0f ) { + move.idealDirection = MOVEDIR_LEFT; + } else { + move.idealDirection = MOVEDIR_RIGHT; + } + } + + return TurnToward( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) ); +} + +/* +===================== +idAI::Turn +===================== +*/ +void idAI::Turn( void ) { + float diff; + float diff2; + float turnAmount; + animFlags_t animflags; + + // If cant turn or turning is disabled just bail + if ( !CanTurn ( ) ) { + return; + } + + // check if the animator has marked this anim as non-turning + if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) { + animflags = legsAnim.GetAnimFlags(); + } else { + animflags = torsoAnim.GetAnimFlags(); + } + if ( animflags.ai_no_turn ) { + return; + } + + if ( move.anim_turn_angles && animflags.anim_turn ) { + idMat3 rotateAxis; + + // set the blend between no turn and full turn + float frac = move.anim_turn_amount / move.anim_turn_angles; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac ); + + // get the total rotation from the start of the anim + animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis ); + move.current_yaw = idMath::AngleNormalize180( move.anim_turn_yaw + rotateAxis[ 0 ].ToYaw() ); + } else { + diff = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( move.currentDirection != move.idealDirection ) { + move.turnVel += (AI_TURN_SCALE * 2.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.running ) { + move.turnVel += (AI_TURN_SCALE * 3.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.moving ) { + move.turnVel += (AI_TURN_SCALE * 1.5f)* diff * MS2SEC( gameLocal.msec ); + } else { + move.turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec ); + } + + if ( move.turnVel > move.turnRate ) { + move.turnVel = move.turnRate; + } else if ( move.turnVel < -move.turnRate ) { + move.turnVel = -move.turnRate; + } + turnAmount = move.turnVel * MS2SEC( gameLocal.msec ); + if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } + move.current_yaw = idMath::AngleNormalize180( move.current_yaw + turnAmount ); + diff2 = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( idMath::Fabs( diff2 ) < 0.1f ) { + move.current_yaw = move.ideal_yaw; + } + } + + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + +// if ( DebugFilter(ai_debugMove) ) { // RED = ideal_yaw, GREEN = current_yaw, YELLOW = current+velocity +// const idVec3 &org = physicsObj.GetOrigin(); +// gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, move.ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, move.current_yaw, 0 ).ToForward() * 48, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, move.current_yaw + move.turnVel, 0 ).ToForward() * 32, gameLocal.msec ); +// if ( move.anim_turn_angles && animflags.anim_turn ) { +// gameRenderWorld->DebugLine( colorOrange, org, org + idAngles( 0, move.anim_turn_yaw, 0 ).ToForward() * 32, gameLocal.msec ); +// } +// } +} + +/* +===================== +idAI::FacingIdeal +===================== +*/ +bool idAI::FacingIdeal( void ) { + float diff; + + if ( !move.turnRate ) { + return true; + } + + diff = idMath::AngleDelta ( move.current_yaw, move.ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + move.current_yaw = move.ideal_yaw; + return true; + } + + return false; +} + +/* +================ +idAI::AnimTurn +================ +*/ +void idAI::AnimTurn ( float angles, bool force ) { + move.turnVel = 0.0f; + move.anim_turn_angles = angles; + if ( angles ) { + move.anim_turn_yaw = move.current_yaw; + + if ( force ) { + move.anim_turn_amount = angles; + } else { + move.anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( move.current_yaw - move.ideal_yaw ) ); + if ( move.anim_turn_amount > move.anim_turn_angles ) { + move.anim_turn_amount = move.anim_turn_angles; + } + } + } else { + move.anim_turn_amount = 0.0f; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); + } +} + +/*********************************************************************** + + Movement Helper Functions + +***********************************************************************/ + +/* +================ +idAI::ApplyImpulse +================ +*/ +void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + // FIXME: Jim take a look at this and see if this is a reasonable thing to do + // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 ) + // and we don't want him taking physics impulses as it can knock him off the path + if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) { + idActor::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +===================== +idAI::GetAnimMoveDelta +===================== +*/ +void idAI::GetAnimMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) { + idVec3 oldModelOrigin; + idVec3 modelOrigin; + + animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta = axis * delta; + + if ( modelOffset != vec3_zero ) { + // the pivot of the monster's model is around its origin, and not around the bounding + // box's origin, so we have to compensate for this when the model is offset so that + // the monster still appears to rotate around it's origin. + oldModelOrigin = modelOffset * oldaxis; + modelOrigin = modelOffset * axis; + delta += oldModelOrigin - modelOrigin; + } + + delta *= physicsObj.GetGravityAxis(); +} + + + +/* +===================== +TestTeammateCollisions +===================== +*/ + +void TestTeammateCollisions(idAI* owner) { + idActor* teammate; + idVec3 teammateDirection; + idVec3 teammateVelocity; + float teammateDirectionDot; + float teammateDistance; + float teammateSpeed; + const idVec3& myOrigin = owner->GetPhysics()->GetOrigin(); + const idBounds& myBounds = owner->GetPhysics()->GetBounds(); + idVec3 myVelocity = owner->GetPhysics()->GetLinearVelocity(); + float mySpeed = myVelocity.NormalizeFast(); + + // Don't Bother, We're Blocked Or Not Moving + //--------------------------------------------- + if (owner->move.blockTime>gameLocal.GetTime() || !owner->move.fl.moving || mySpeed<1.0f) { + return; + } + + + for (teammate = aiManager.GetAllyTeam((aiTeam_t)owner->team); teammate; teammate = teammate->teamNode.Next()) { + if (teammate->fl.hidden || teammate == owner || teammate->health <= 0) { + continue; + } + + // If On Same Floor + //------------------ + teammateDirection = teammate->GetPhysics()->GetOrigin() - myOrigin; + if (fabsf(teammateDirection[2])>myBounds.Size().z) { + continue; + } + + // If Close Enough + //----------------- + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance>128.0f) { + continue; + } + + // Completely Ignore Dudes Directly Behind Me + //-------------------------------------------- + teammateDirectionDot = teammateDirection*myVelocity; + if (teammateDirectionDot<-0.5f) { + continue; + } + + // Switch To Walk If I'm Heading For A Teammate + //---------------------------------------------- + if (teammateDirectionDot>0.85f) { + owner->move.fl.obstacleInPath = true; // make him slow to a walk + + if ( owner->DebugFilter(ai_debugMove) ) { // WHITE = Walk Teammate Near + gameRenderWorld->DebugArrow( colorWhite, myOrigin, teammate->GetPhysics()->GetOrigin(), 4, 250 ); + } + } + + if (teammateDistance<48.0f) { + + teammateVelocity = teammate->GetPhysics()->GetLinearVelocity(); + teammateSpeed = teammateVelocity.NormalizeFast(); + + if (teammateSpeed>50.0f) { + + // If I'm Following Him, And I'm RIGHT Behind Him, Stop And Let Him Go A Bit Farther Ahead + //------------------------------------------------------------------------------------------ + if (teammateDirectionDot>0.85f && teammateVelocity*myVelocity>0.4f) { + owner->move.blockTime = gameLocal.time + 1000; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1000); + } + + } + + // Stop moving if my leader or a guy with a higher entity number is headed straight for me + //----------------------------------------------------------------------------------------- + else if (teammate->entityNumberentityNumber && teammateDirection*teammateVelocity<0.5f) { + owner->move.blockTime = gameLocal.time + 1500; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1500); + } + } + } + } + } +} + + + + + + +/* +===================== +idAI::CheckObstacleAvoidance +===================== +*/ +void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &seekPos, idReachability* goalReach ) { + + move.fl.blocked = false; // Makes Character Stop Moving + move.fl.obstacleInPath = false; // Makes Character Walk + move.obstacle = NULL; + seekPos = goalPos; + + if (move.fl.ignoreObstacles) { + return; + } + if ( g_perfTest_aiNoObstacleAvoid.GetBool() ) { + return; + } + + // Test For Path Around Obstacles + //-------------------------------- + obstaclePath_t path; + move.fl.blocked = !FindPathAroundObstacles( &physicsObj, aas, move.moveCommand == MOVE_TO_ENEMY ? enemy.ent : NULL, physicsObj.GetOrigin(), goalPos, path ); + move.fl.obstacleInPath = (path.firstObstacle || path.seekPosObstacle || path.startPosObstacle); + move.obstacle = (path.firstObstacle)?(path.firstObstacle):(path.seekPosObstacle); + seekPos = path.seekPos; + + // Don't Worry About Obstacles Out Of Walk Range + //----------------------------------------------- + if (move.obstacle && DistanceTo(move.obstacle)>155.0f) { + move.fl.blocked = false; + move.fl.obstacleInPath = false; + move.obstacle = 0; + seekPos = goalPos; + } + + // cdr: Alternate Routes Bug + // If An Obstacle Remains, And The Seek Pos Is Fairly Farr Off Of The Straight Line Path, Then Mark The Reach As Blocked + //----------------------------------------------------------------------------------------------------------------------- + if (move.fl.obstacleInPath && goalReach && !(goalReach->travelType&TFL_INVALID) && seekPos.Dist2XY(goalPos)>100.0f) { + float scale; + float dist; + + dist = seekPos.DistToLineSeg(physicsObj.GetOrigin(), goalPos, scale); + if (scale<0.95f && dist>50.0f) { + aiManager.MarkReachBlocked(aas, goalReach, path.allObstacles); + } + } + + TestTeammateCollisions(this); + + + + if ( DebugFilter(ai_showObstacleAvoidance) ) { + gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec ); + gameRenderWorld->DebugLine( !move.fl.blocked ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec ); + } +} + +/* +============ +idAI::TestAnimMove +============ +*/ +bool idAI::TestAnimMove ( int animNum, idEntity *ignore, idVec3 *pMoveVec ) { + const idAnim* anim; + predictedPath_t path; + idVec3 moveVec; + + anim = GetAnimator()->GetAnim ( animNum ); + assert ( anim ); + +// moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.current_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec / MS2SEC( anim->Length() ), anim->Length(), 200, SE_BLOCKED | SE_ENTER_LEDGE_AREA, path, ignore ); + + if ( DebugFilter(ai_debugMove) ) { // TestAnimMove + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + } + if ( pMoveVec ) { + *pMoveVec = moveVec; + } + + return ( path.endEvent == 0 ); +} + +/* +===================== +Seek +===================== +*/ +idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) { + idVec3 predictedPos; + idVec3 goalDelta; + idVec3 seekVel; + + // predict our position + predictedPos = org + vel * prediction; + goalDelta = goal - predictedPos; +// goalDelta = goal - org; + seekVel = goalDelta * MS2SEC( gameLocal.msec ); + + return seekVel; +} + +/*********************************************************************** + + Movement + +***********************************************************************/ + +/* +===================== +idAI::DeadMove +===================== +*/ +void idAI::DeadMove( void ) { + idVec3 delta; + monsterMoveResult_t moveResult; + + DeathPush ( ); + + idVec3 org = physicsObj.GetOrigin(); + + GetAnimMoveDelta( viewAxis, viewAxis, delta ); + physicsObj.SetDelta( delta ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + move.fl.onGround = physicsObj.OnGround(); +} + +/* +===================== +idAI::AdjustFlyingAngles +===================== +*/ +void idAI::AdjustFlyingAngles( void ) { + idVec3 vel; + float speed; + float roll; + float pitch; + + vel = physicsObj.GetLinearVelocity(); + + speed = vel.Length(); + if ( speed < 5.0f ) { + roll = 0.0f; + pitch = 0.0f; + } else { + roll = vel * viewAxis[ 1 ] * -move.fly_roll_scale / move.fly_speed; + if ( roll > move.fly_roll_max ) { + roll = move.fly_roll_max; + } else if ( roll < -move.fly_roll_max ) { + roll = -move.fly_roll_max; + } + + pitch = vel * viewAxis[ 0 ] * -move.fly_pitch_scale / move.fly_speed; + if ( pitch > move.fly_pitch_max ) { + pitch = move.fly_pitch_max; + } else if ( pitch < -move.fly_pitch_max ) { + pitch = -move.fly_pitch_max; + } + } + + move.fly_roll = move.fly_roll * 0.95f + roll * 0.05f; + move.fly_pitch = move.fly_pitch * 0.95f + pitch * 0.05f; + + if ( move.flyTiltJoint != INVALID_JOINT ) { + animator.SetJointAxis( move.flyTiltJoint, JOINTMOD_WORLD, idAngles( move.fly_pitch, 0.0f, move.fly_roll ).ToMat3() ); + } else { + viewAxis = idAngles( move.fly_pitch, move.current_yaw, move.fly_roll ).ToMat3(); + } +} + +/* +===================== +idAI::AddFlyBob +===================== +*/ +void idAI::AddFlyBob( idVec3 &vel ) { + idVec3 fly_bob_add; + float t; + + if ( move.fly_bob_strength ) { + t = MS2SEC( gameLocal.time + entityNumber * 497 ); + fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * move.fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * move.fly_bob_vert ) ) * move.fly_bob_strength; + vel += fly_bob_add * MS2SEC( gameLocal.msec ); + if ( DebugFilter(ai_debugMove) ) { // FlyBob + const idVec3 &origin = physicsObj.GetOrigin(); + gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 ); + } + } +} + +/* +===================== +idAI::AdjustFlyHeight +===================== +*/ +void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) { + const idVec3 &origin = physicsObj.GetOrigin(); + predictedPath_t path; + idVec3 end; + idVec3 dest; + trace_t trace; + bool goLower; + + // make sure we're not flying too high to get through doors + // FIXME: with move to enemy this thinks we're going to hit + // the enemy and then says we need to go lower... but that's not quite right... + // if we're about to hit our enemy, then maintaining our height should be desirable..? + goLower = false; + if ( origin.z > goalPos.z ) { + dest = goalPos; + dest.z = origin.z + 128.0f; + idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path, move.goalEntity.GetEntity() ); + if ( path.endPos.z < origin.z ) { + + //Hmm, should we make sure the path.endPos is high enough off the ground? + if ( move.fly_offset && (move.moveCommand == MOVE_TO_ENEMY || move.moveCommand == MOVE_TO_ATTACK) ) { + int pathArea = PointReachableAreaNum( path.endPos ); + if ( aas && (aas->AreaFlags( pathArea )&AREA_FLOOR) ) { + path.endPos.z = aas->AreaBounds( pathArea )[0][2]; + float areaTop = aas->AreaCeiling( pathArea ) - GetPhysics()->GetBounds()[1].z; + if ( path.endPos.z + move.fly_offset > areaTop ) { + path.endPos.z = areaTop; + } else { + path.endPos.z += move.fly_offset; + } + } + } + + idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION ); + vel.z += addVel.z; + goLower = true; + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Height + gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + if ( !goLower ) { + // make sure we don't fly too low + end = origin; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + end.z = enemy.lastKnownPosition.z + move.fly_offset; + } else if ( move.moveCommand == MOVE_TO_ENTITY ) { + end.z = goalPos.z; + } else { + end.z = goalPos.z;// + move.fly_offset; + } + +// RAVEN BEGIN +// ddynerman: multiple collision world + idVec3 cappedEnd = (end-origin); + if ( cappedEnd.LengthFast() > 1024.0f ) { + //don't do translation predictions over 1024 + cappedEnd.Normalize(); + cappedEnd *= 1024.0f; + } + cappedEnd += origin; + gameLocal.Translation( this, trace, origin, cappedEnd, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this ); +// RAVEN END + vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION ); + } +} + +/* +===================== +idAI::FlySeekGoal +===================== +*/ +void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) { + idVec3 seekVel; + + // seek the goal position + seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION ); + seekVel *= move.fly_seek_scale; + //seekVel.Normalize(); + //vel = seekVel*move.speed; + vel += seekVel; +} + +/* +===================== +idAI::AdjustFlySpeed +===================== +*/ +void idAI::AdjustFlySpeed( idVec3 &vel ) { + float goalSpeed; + + // Slow down movespeed when we close to goal (this is similar to how AnimMove ai will + // switch to walking when they are close, it allows for more fine control of movement) + if ( move.walkRange > 0.0f ) { + float distSqr; + distSqr = (physicsObj.GetOrigin ( ) - move.moveDest).LengthSqr ( ); + goalSpeed = move.speed * idMath::ClampFloat ( 0.1f, 1.0f, distSqr / Square ( move.walkRange ) ); + } else { + goalSpeed = move.speed; + } + + // apply dampening as long as we arent within the walk range +// if ( goalSpeed != move.speed) { + float speed; + + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( goalSpeed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if (goalSpeed && ( speed > goalSpeed ) ) { + speed = goalSpeed; + } + + vel *= speed; +// } else { +// vel.Normalize ( ); +// vel *= goalSpeed; +// } +} + +/* +===================== +idAI::FlyTurn +===================== +*/ +void idAI::FlyTurn( void ) { + if ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( focusType != AIFOCUS_NONE && move.fl.allowDirectional ) { + DirectionalTurnToward ( currentFocusPos ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + + Turn(); +} + +/* +===================== +idAI::FlyMove +===================== +*/ +void idAI::FlyMove( void ) { + idVec3 oldorigin; + idVec3 newDest; + + move.fl.blocked = false; + if ( ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) && ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), aiMoveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, move.fly_speed ); + } + + // Dont move when movement is disabled + if ( !CanMove() || legsAnim.Disabled ( ) ) { + // Still allow turning though + FlyTurn ( ); + + if ( (aifl.action || aifl.scripted ) && legsAnim.Disabled () && move.fl.allowAnimMove ) { + idMat3 oldaxis = viewAxis; + idVec3 delta; + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + physicsObj.UseFlyMove( false ); + if ( spawnArgs.GetBool( "alwaysBob" ) ) { + AddFlyBob( delta ); + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + + RunPhysics(); + } else if ( spawnArgs.GetBool( "alwaysBob" ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + AddFlyBob( vel ); + physicsObj.SetLinearVelocity( vel ); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + } + + UpdateAnimationControllers ( ); + return; + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + move.seekPos.x = newDest.x; + move.seekPos.y = newDest.y; + } + + if ( move.speed ) { + FlySeekGoal( vel, move.seekPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, move.seekPos ); + } + + AdjustFlySpeed( vel ); + + vel += move.addVelocity; + move.addVelocity.Zero(); + + physicsObj.SetLinearVelocity( vel ); + } + + // turn + FlyTurn(); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + move.fl.blocked = true; + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); + gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorBlue, org, move.seekPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition ( ), GetEyePosition ( ) + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +============ +idAI::UpdatePlayback +============ +*/ +void idAI::UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ) { + rvDeclPlaybackData pbd; + bool atDest; + + // New playback stuff + if( !mPlayback.IsActive() ) { + return; + } + + atDest = mPlayback.UpdateFrame( this, pbd ); + + goalPos = pbd.GetPosition(); + SetOrigin( goalPos ); + viewAxis = pbd.GetAngles().ToMat3(); + + // Keep the yaw updated + idVec3 local_dir; + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + OnUpdatePlayback ( pbd ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::PlaybackMove( void ){ + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + move.obstacle = NULL; + + goalPos = oldorigin; + + UpdatePlayback( goalPos, delta, oldorigin, oldaxis ); + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else { + move.fl.blocked = true; + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::StaticMove +===================== +*/ +void idAI::StaticMove( void ) { + idEntity* enemyEnt = enemy.ent; + + if ( aifl.dead ) { + return; + } + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemyEnt ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + TurnToward( move.moveDest ); + } + Turn(); + + physicsObj.ForceDeltaMove( true ); // disable gravity + RunPhysics(); + + move.fl.onGround = false; + + if ( DebugFilter(ai_debugMove) ) { // Static Move + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + } +} + +/* +===================== +idAI::SlideMove +===================== +*/ +void idAI::SlideMove( void ) { + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.seekPos = move.moveDest; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.seekPos = move.moveDest; + } else if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + TurnToward( newDest ); + move.seekPos = newDest; + } + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.seekPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + } else { + move.seekPos = move.moveDest; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + float z = vel.z; + idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION; + + // seek the goal position + goalDelta = move.seekPos - predictedPos; + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + vel += goalDelta * MS2SEC( gameLocal.msec ); + + // cap our speed + vel.Truncate( move.fly_speed ); + vel.z = z; + physicsObj.SetLinearVelocity( vel ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); + + if ( DebugFilter(ai_debugMove) ) { // Slide Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // SlideMove + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::AnimMove +===================== +*/ +void idAI::AnimMove( void ) { + + if ( ai_useRVMasterMove.GetBool ( ) ) { + RVMasterMove(); + return; + } + + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idReachability* goalReach; + // RAVEN END + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + //move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( move.moveCommand == MOVE_FACE_ENEMY && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } else { + move.moveStatus = MOVE_STATUS_MOVING; + + // Otherwise, Update The Seek Pos + if ( !aifl.simpleThink && GetMovePos( move.goalPos, &goalReach ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( move.goalPos, move.seekPos, goalReach ); + } else { + move.seekPos = move.goalPos; + } + DirectionalTurnToward ( move.seekPos ); + } + } + } + + Turn(); + + goalDelta = move.seekPos - oldorigin; + goalDist = goalDelta.LengthFast(); + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = move.goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( move.fl.allowAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + } else if ( move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + float speed = delta.LengthFast(); + delta = goalDelta; + delta.Normalize(); + delta *= speed; + } else { + delta.Zero(); + } + + if ( move.moveCommand > NUM_NONMOVING_COMMANDS ) { + //actually *trying* to move to a goal + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + + RunPhysics(); + + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + const idVec3& org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // AnimMove : GREEN / RED Bounds & Move Dest + gameRenderWorld->DebugLine( colorCyan, oldorigin, org, 5000 ); + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), org, gameLocal.msec ); + if (!ReachedPos( move.moveDest, move.moveCommand, move.range )) { + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugArrow( (team==0)?(colorGreen):(colorRed), org, move.moveDest, 4, gameLocal.msec ); + } + DrawRoute(); + } +} + +/* +===================== +idAI::CustomMove +===================== +*/ +void idAI::CustomMove( void ) { + // derived class must implement this +} + + + +/* +====================================================================================== + NEW MOVE CODE BEGIN +====================================================================================== +*/ + +struct rvObstacle; + +const float REACHED_RADIUS = 4.0f; +const float REACHED_RADIUS_SQUARE = REACHED_RADIUS*REACHED_RADIUS; + + + + + + +/* +===================== +LineIntersection2D +===================== +*/ +bool LineIntersection2D(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& contactPoint) { + + // Test If Parallel + //------------------ + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)<0.01f) { + return false; + } + + // Test CD Edge + //-------------- + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f) { + return false; + } + if (s>1.0f) { + return false; + } + + // Test AB Edge + //-------------- + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r<0.0f) { + return false; + } + if (r>1.0f) { + return false; + } + + contactPoint = A + r*(B-A); + return true; +} + + +/* +===================== +rvWindingBox + +Clockwise winding with a simple line intersection test + +[1]---->[2] + ^ | + | verts | + | V +[0]<----[3] + +===================== +*/ +struct rvWindingBox { + idVec3 verts[4]; + aasArea_t* areas[4]; + rvObstacle* obstacles[4]; // CDR_TODO: Should be a list of obstacles for each vertex + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + for (int i=0; i<4; i++) { + verts[i] = vec3_zero; + areas[i] = NULL; + obstacles[i] = NULL; + } + } + + /* + ===================== + FromBounds + ===================== + */ + void FromBounds(const idBounds& b) { + verts[0].x = b[0].x; + verts[0].y = b[0].y; + verts[0].z = b[0].z; + + verts[1].x = b[0].x; + verts[1].y = b[1].y; + verts[1].z = b[0].z; + + verts[2].x = b[1].x; + verts[2].y = b[1].y; + verts[2].z = b[0].z; + + verts[3].x = b[1].x; + verts[3].y = b[0].y; + verts[3].z = b[0].z; + } + + /* + ===================== + LineIntersection + ===================== + */ + bool LineIntersection(const idVec3& start, const idVec3& end, idVec3& contactPoint, int& v1, int& v2) const { + for (int i=0; i<4; i++) { + v1 = i; + v2 = (i<3)?(i+1):(0); + + if (start.IsLeftOf(verts[v1], verts[v2]) && LineIntersection2D(start, end, verts[v1], verts[v2], contactPoint)) { + return true; + } + } + return false; + } + + /* + ===================== + PointInside + ===================== + */ + bool PointInside(const idVec3& point) const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + if (point.IsLeftOf(vert1, vert2)) { + return false; + } + } + return true; + } + + /* + ===================== + DrawDebugGraphics + ===================== + */ + bool DrawDebugGraphics() const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + gameRenderWorld->DebugLine(colorYellow, vert1, vert2, gameLocal.msec); + if (!areas[i] || obstacles[i]) { + gameRenderWorld->DebugLine(colorRed, vert1, vert1+idVec3(0.0f,0.0f,16.0f), gameLocal.msec); + } else if (areas[i]) { + // gameRenderWorld->DebugLine(colorYellow, vert1, areas[i]->center, gameLocal.msec); + } + } + return true; + } +}; + + + + +/* +===================== +rvMarker + +A marker represents an obstacle within +an area. Any single obstacle can have +any number of markers in any number of +areas that it touches +===================== +*/ +struct rvMarker { + rvObstacle* obstacle; + aasArea_t* area; + rvMarker* prev; + rvMarker* next; +}; +rvPool markerPool; + +/* +===================== +rvObstacle +===================== +*/ +struct rvObstacle { + static idAASFile* searchFile; + static idBounds searchBounds; + + entityPointer_t entity; + idVec3 origin; + idVec3 originFuture; + int lastTimeMoved; + bool pendingUpdate; + + rvWindingBox windings[3]; + idList markers; + idList areas; + + /* + ============ + Initialize + ============ + */ + void Initialize(idEntity* ent) { + entity = ent; + origin = vec3_zero; + originFuture = vec3_zero; + lastTimeMoved = 0; + pendingUpdate = true; + + for (int i=0; i<3; i++) { + windings[i].Initialize(); + } + markers.Clear(); + areas.Clear(); + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + if ((gameLocal.time - lastTimeMoved) < 1000) { + gameRenderWorld->DebugArrow(colorBrown, origin, originFuture, 3, gameLocal.msec); + } + + windings[0].DrawDebugGraphics(); + } + + /* + ============ + RemoveMarkers + ============ + */ + void RemoveMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==this); + + if (marker->area->firstMarker==marker) { + marker->area->firstMarker = marker->next; + } + if (marker->prev) { + marker->prev->next = marker->next; + } + if (marker->next) { + marker->next->prev = marker->prev; + } + + marker->obstacle = NULL; + + markerPool.free(marker); + } + markers.Clear(); + } + /* + ============ + SearchAreas_r + ============ + */ + void SearchAreas_r( int nodeNum ) { + int side; + const aasNode_t *node; + + while( nodeNum != 0 ) { + + // Negative nodeNum signifies a Leaf Area + if ( nodeNum < 0 ) { + if ( searchFile->GetArea(-nodeNum).flags&AREA_REACHABLE_WALK ) { + areas.AddUnique( &searchFile->GetArea(-nodeNum) ); + } + break; + } + node = &searchFile->GetNode( nodeNum ); + side = searchBounds.PlaneSide( searchFile->GetPlane( node->planeNum ) ); + if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + SearchAreas_r( node->children[1] ); + nodeNum = node->children[0]; + } + } + } + + /* + ============ + PointInsideArea + ============ + */ + bool PointInsideArea( const idVec3& point, aasArea_t* area ) { + int i, faceNum; + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = searchFile->GetFaceIndex(area->firstFace + i); + + const aasFace_t& face = searchFile->GetFace(abs( faceNum )); + if (!(face.flags & FACE_FLOOR)) { + const idPlane& plane = searchFile->GetPlane(face.planeNum ^ INTSIGNBITSET( faceNum )); + if (plane.Side(point) == PLANESIDE_BACK) { + return false; + } + } + } + return true; + } + + /* + ============ + AddMarkers + ============ + */ + void AddMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==NULL); + + // Setup The New Marker + //---------------------- + marker->obstacle = this; + marker->area = areas[i]; + marker->next = NULL; + marker->prev = NULL; + + + // Fixup Any Existing Linked List (First Marker) + //----------------------------------------------- + if (marker->area->firstMarker) { + marker->area->firstMarker->prev = marker; + marker->next = marker->area->firstMarker; + } + + // Add This Marker At THe Area's Head + //------------------------------------ + marker->area->firstMarker = marker; + + markers.Append(marker); + } + } + + + + /* + ============ + Update + ============ + */ + bool Update() { + static rvMarker* marker; + static rvObstacle* obstacle; + static aasArea_t* area; + static idVec3 expand; + static float speed; + static float distance; + static idVec3 direction; + static int aasFileNum, t, v, a; + static idList touched; + static rvWindingBox* myWinding; + + + pendingUpdate = false; + idEntity* ent = entity.GetEntity(); + if (!ent || ent->health<=0 || ent->fl.hidden || !ent->GetPhysics()) { + RemoveMarkers(); + return false; // Means This Obstacle Structure Should Be Retired + } + + + // Only Update If We've Moved Far Enough + //--------------------------------------- + idPhysics* physics = ent->GetPhysics(); + if (origin.Dist2XY(physics->GetOrigin())<100.0f) { // CDR_TODO: Scale This By Distance To Player, Other Obstacles Near... + return true; + } + + // Get The Obstacle's Seek Direction & Speed + //------------------------------------------- + if (ent->IsType(idAI::GetClassType())) { + speed = (((idAI*)ent)->move.fl.done)?(0.0f):(physics->GetLinearVelocity().LengthFast()) * 2.5f; + direction = (((idAI*)ent)->move.seekPos - physics->GetOrigin()); + distance = direction.NormalizeFast(); + + // Cap The Projection To The Seek Position + if (speed > distance) { + speed = distance; + } + } else { + direction = physics->GetLinearVelocity(); + speed = direction.NormalizeFast() * 2.5f; + } + + origin = physics->GetOrigin(); + originFuture = origin + (direction*speed); // 2.5 seconds into the future predicion + lastTimeMoved = gameLocal.time; + + + // Remove Old Markers + //-------------------- + RemoveMarkers(); + + + // Get The Entity's Bounds And Areas That These Bounds Cover + //----------------------------------------------------------- + for (aasFileNum=0; aasFileNumGetFile(); + searchBounds = physics->GetAbsBounds(); + expand = searchFile->GetSettings().boundingBoxes[0].Size(); + expand[0] *= 0.5f; + expand[1] *= 0.5f; + expand[2] = 0; + expand[0] += REACHED_RADIUS; + expand[1] += REACHED_RADIUS; + + searchBounds.ExpandSelf(expand); + SearchAreas_r(1); + + // Setup The Winding From The Bounds + //----------------------------------- + myWinding->FromBounds(searchBounds); + + // Setup Each Vertex On The Winding + //---------------------------------- + for (v=0; v<4; v++) { + const idVec3& myVertex = myWinding->verts[v]; + + myWinding->areas[v] = NULL; + for (a=0; aareas[v] = area; + + + // Search This Area For All Obstacles That May Contain This Vertex + //----------------------------------------------------------------- + obstacle = NULL; + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle->windings[aasFileNum].PointInside(myVertex)) { + obstacle = marker->obstacle; + break; + } + } + + // Update The Obstacle On The Winding + //------------------------------------ + if (myWinding->obstacles[v] && myWinding->obstacles[v]!=obstacle) { + touched.AddUnique(myWinding->obstacles[v]); + } + if (obstacle) { + touched.AddUnique(obstacle); + } + + myWinding->obstacles[v] = obstacle; + break; + } + } + } + + // Touched Obstacles Need To Test Their Verts Against This Winding + //----------------------------------------------------------------- + for (t=0; tPointInside(obstacle->windings[aasFileNum].verts[v])) { + obstacle->windings[aasFileNum].obstacles[v] = this; + } else if (obstacle->windings[aasFileNum].obstacles[v]==this) { + obstacle->windings[aasFileNum].obstacles[v] = NULL; + } + } + } + + // AddMarkers In Each Found Area + //------------------------------- + AddMarkers(); + } + + return true; + } + + /* + ===================== + VertexValid + ===================== + */ + static bool VertexValid(const rvWindingBox& bounds, int v, const aasArea_t* inArea, const idEntity* ignore) { + return (bounds.areas[v] && (bounds.obstacles[v]==NULL || bounds.obstacles[v]->entity.GetEntity()==ignore)); + } + + /* + ============ + MovedRecently + ============ + */ + bool MovedRecently(int time=800) { + if ((gameLocal.time - lastTimeMoved) < time) { + return true; + } + // idEntity* ent = entity.GetEntity(); + // if (ent && ent->IsType(idAI::GetClassType())) { + // return !((idAI*)ent)->move.fl.done; + // } + return false; + } +}; +rvIndexPool obstaclePool; + +idAASFile* rvObstacle::searchFile; +idBounds rvObstacle::searchBounds; + + + + + + +/* +===================== +rvObstacleFinder +===================== +*/ +struct rvObstacleFinder { + idList obstaclesPendingUpdate; + int obstaclesUpdateTime; + + + struct traceResult_t { + rvObstacle* obstacle; + idVec3 endPoint; + float distance; + int v1; + int v2; + bool v1Valid; + bool v2Valid; + }; + traceResult_t contact; + + + + + /* + ============ + Initialize + ============ + */ + void Initialize() { + markerPool.clear(); + obstaclePool.clear(); + + obstaclesPendingUpdate.Clear(); + obstaclesUpdateTime = 0; + memset(&contact, 0, sizeof(contact)); + + // Clear All The Marker Pointers In Any Areas + //-------------------------------------------- + for (int i=0; iGetFile(); + for (int a=0; aGetNumAreas(); a++) { + file->GetArea(a).firstMarker = NULL; + } + } + } + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + static int nextDrawTime=0; + static rvObstacle* obstacle; + if (nextDrawTime>=gameLocal.time) { + return; + } + nextDrawTime = gameLocal.time; + + for (int i=0; iDrawDebugGraphics(); + } + } + } + + /* + ============ + UpdateObstacles + ============ + */ + void UpdateObstacles() { + static rvObstacle* obstacle; + static float entityNum; + + if (obstaclesUpdateTime < gameLocal.time && obstaclesPendingUpdate.Num()) { + obstaclesUpdateTime = gameLocal.time + 50; + + // CDR_TODO: Priority Queue Of Updates? Track Last Update Time Perhaps? + while (obstaclesPendingUpdate.Num()) { + obstacle = obstaclesPendingUpdate.StackTop(); + obstaclesPendingUpdate.StackPop(); + + if (!obstacle->Update()) { + obstaclePool.free(obstacle->entity.GetEntityNum()); + } + } + } + } + + /* + ============ + MarkEntityForUpdate + ============ + */ + void MarkEntityForUpdate(idEntity* ent) { + + // Ignore Non Physics Entities + //----------------------------- + if (!ent || !ent->GetPhysics()) { + return; + } + + // Ignore Obstacles That Are Already Pending + //-------------------------------------------- + if (obstaclePool.valid(ent->entityNumber) && obstaclePool[ent->entityNumber]->pendingUpdate) { + return; + } + + if (!obstaclePool.valid(ent->entityNumber)) { + + // If No More Obstacles Are Available, Ignore This One + //----------------------------------------------------- + if (obstaclePool.full()) { + return; + } + + obstaclePool.alloc(ent->entityNumber)->Initialize(ent); + } + + + obstaclePool[ent->entityNumber]->pendingUpdate = true; + obstaclesPendingUpdate.Append(obstaclePool[ent->entityNumber]); + } + + + /* + ============ + RecordContact + ============ + */ + void RecordContact(float maxDistance, const idVec3& start, rvObstacle* obstacle, const idVec3& point, int vert1=-1, int vert2=-1) { + static float distance; + distance = point.DistXY(start); + if ((maxDistance==0.0f || distance distance || !contact.obstacle)) { + contact.obstacle = obstacle; + contact.endPoint = point; + contact.distance = distance; + contact.v1 = vert1; + contact.v2 = vert2; + } + } + + /* + ============ + RayTrace + ============ + */ + bool RayTrace(float maxDistance, const aasArea_t* area, const idVec3& start, const idVec3& stop, int aasNum, const idEntity* ignore1, const idEntity* ignore2=NULL, const idEntity* ignore3=NULL) { + static rvMarker* marker; + static idVec3 p; + static int v1; + static int v2; + static rvObstacle* ignoreA; + static rvObstacle* ignoreB; + static rvObstacle* ignoreC; + static idEntity* ent; + static idVec3 startBack; + static idVec3 direction; + static bool pulledBack; + + ignoreA = (ignore1 && obstaclePool.valid(ignore1->entityNumber))?(obstaclePool[ignore1->entityNumber]):(NULL); + ignoreB = (ignore2 && obstaclePool.valid(ignore2->entityNumber))?(obstaclePool[ignore2->entityNumber]):(NULL); + ignoreC = (ignore3 && obstaclePool.valid(ignore3->entityNumber))?(obstaclePool[ignore3->entityNumber]):(NULL); + + contact.obstacle = NULL; + pulledBack = false; + + + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle==NULL || marker->obstacle==ignoreA || marker->obstacle==ignoreB || marker->obstacle==ignoreC) { + continue; + } + + // Handle Moving Obstacles Differently + //------------------------------------- + if (marker->obstacle->MovedRecently()) { + + // Ignore Moving Actors With Higher Entity Numbers + //------------------------------------------------- + ent = marker->obstacle->entity.GetEntity(); + if (ent && ignore1 && ent->entityNumber>ignore1->entityNumber && ent->IsType(idActor::GetClassType())) { + continue; + } + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Moving Obstacle's Predicted Path + //---------------------------------------------------------- + if (LineIntersection2D(startBack, stop, marker->obstacle->origin, marker->obstacle->originFuture, p)) { + RecordContact(maxDistance, startBack, marker->obstacle, p); + } + + + // Stationary Obstacles + //---------------------- + } else { + + // CDR_TODO: Special Line Intersection Test For Player Forward Aim And On Same Team + //---------------------------------------------------------------------------------- + + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Stationary Obstacle's Position + //-------------------------------------------------------- + if (marker->obstacle->windings[aasNum].LineIntersection(startBack, stop, p, v1, v2)) { + RecordContact(maxDistance, startBack, marker->obstacle, p, v1, v2); + } + } + } + + if (contact.obstacle) { + if (contact.obstacle->MovedRecently()) { + contact.v1Valid = true; + contact.v2Valid = true; + } else { + contact.v1Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v1, area, ignore1); + contact.v2Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v2, area, ignore1); + } + + return true; + } + return false; + } +}; +rvObstacleFinder obstacleFinder; + + + + + +/* +===================== +rvPathFinder +===================== +*/ +class rvPathFinder { +public: + /* + ===================== + visitNode + ===================== + */ + struct visitNode { + float costToGoal; + float travelCost; + bool closed; + int vertexNum; + idReachability* reach; + visitNode* from; + + float cost() { + return travelCost + costToGoal; + } + }; + + static int visitSort( const void *a, const void *b ) { + return (int)( (*((visitNode**)b))->cost() - (*((visitNode**)a))->cost() ); + } + + + + enum { + MAX_VISITED = 512, + MAX_OPEN = 255, + MAX_PENDING = 60, + }; + + idAAS* myAAS; + float myRadius; + idMoveState* myMove; + int myTeam; + const idEntity* myIgnoreEntity; + const idEntity* myIgnoreEntity2; + bool drawVisitTree; + + visitNode* next; + + visitNode* open[MAX_OPEN]; + int openCount; + bool openListUpdate; + + visitNode* pending[MAX_PENDING]; + visitNode* pendingBest; + int pendingCount; + + visitNode visited[MAX_VISITED]; + int visitedCount; + idHashIndex visitedIndexReach; + idHashIndex visitedIndexVert; + + + + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + next = NULL; + } + + + /* + ===================== + Close + ===================== + */ + void Close(visitNode* node) { + node->closed = true; + } + + /* + ===================== + DrawVisitTree + ===================== + */ + void DrawVisitTree() { + for (int i=0; imyPos); + const idVec3& stop = (GetSeekPosition(&visited[i])); + const idVec4& color = (visited[i].closed)?(colorOrange):(colorYellow); + const int duration = (visited[i].closed)?(3000):(500); + + gameRenderWorld->DebugArrow(color, start, stop, 3, duration); + } + } + + /* + ===================== + XYLineIntersection + ===================== + */ + bool XYLineIntersection(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& P) { + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)>0.01f) { + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f || s>1.0f) { + return false; + } + + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r>1.0f) { + P = B; + return false; + } + if (r<0.0f) { + P = A; + return false; + } + + P = A + r*(B-A); + return true; + } + + // Lines Are Parallel, No Intersection + return false; + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(idReachability* reach, int vertexNum) { + return ((vertexNum)?(myAAS->GetFile()->GetVertex(vertexNum)):(reach->start)); + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(visitNode* node) { + return GetSeekPosition(node->reach, node->vertexNum); + } + + /* + ===================== + Success + ===================== + */ + bool Success(visitNode* node) { + static idVec3 edgeA; + static idVec3 edgeB; + static idVec3 intersect; + static idVec3 direction; + static idVec3 smoothedPos; + static int at; + static int count; + static rvMarker* marker; + static bool isCorner; + + // Always Add The Goal Pos At The End Of The Path + //------------------------------------------------ + myMove->path[myMove->pathLen].reach = NULL; + myMove->path[myMove->pathLen].seekPos = myMove->goalPos; + myMove->pathLen ++; + + // Build The Path + //---------------- + while (node && myMove->pathLenpath[myMove->pathLen].reach = node->reach; + myMove->path[myMove->pathLen].seekPos = GetSeekPosition(node); + + myMove->pathLen ++; + node = node->from; + } + + + // Additional Path Point Modifications + //------------------------------------- + count = myMove->pathLen-1; + for (at=count; at>0; at--) { + pathSeek_t& pathPrev = myMove->path[at+1]; + pathSeek_t& pathAt = myMove->path[at]; + pathSeek_t& pathNext = myMove->path[at-1]; + + myAAS->GetEdge(pathAt.reach->edgeNum, edgeA, edgeB); + + // Smooth The Path One Pass + //-------------------------- + if (pathAt.reach->travelType==TFL_WALK) { + const idVec3& walkA = (at==count) ? (myMove->myPos) :(pathPrev.seekPos); + const idVec3& walkB = (at==0) ? (myMove->goalPos) :(pathNext.seekPos); + + isCorner = !XYLineIntersection(edgeA, edgeB, walkA, walkB, smoothedPos); + + + // If The Smoothed Position Is Not Blocked By An Obstacle + //-------------------------------------------------------- + aasArea_t* area = &myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum); + for (marker=area->firstMarker; marker; marker=marker->next) { + if (!marker->obstacle || marker->obstacle->entity.GetEntity()==myIgnoreEntity || marker->obstacle->entity.GetEntity()==myIgnoreEntity2) { + continue; + } + if (marker->obstacle->windings[0/*CDR_TODO: use aasNum*/].PointInside(smoothedPos)) { + break; + } + } + if (!marker) { + pathAt.seekPos = smoothedPos; + + // Push Away From The Corner A Bit + //--------------------------------- + if (isCorner) { + smoothedPos.ProjectToLineSeg(walkA, walkB); + direction = pathAt.seekPos - smoothedPos; + direction.NormalizeFast(); + pathAt.seekPos += direction * (REACHED_RADIUS+1.0f); + } + } + } + } + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return true; + } + + /* + ===================== + Failure + ===================== + */ + bool Failure() { + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return false; + } + + + /* + ===================== + ErrorCondition + ===================== + */ + bool ErrorCondition() { + assert(0); + // Stats? + return false; + } + + /* + ===================== + SelectNextVisitedNode + ===================== + */ + visitNode* SelectNextVisitedNode() { + if (pendingBest && (!openCount || open[openCount-1]->cost() > pendingBest->cost())) { + next = pendingBest; + pendingBest = NULL; + openListUpdate = true; + return next; + } + + if (openCount) { + openCount--; + return open[openCount]; + } + return NULL; + } + + /* + ===================== + Open + ===================== + */ + void Open(visitNode* node) { + node->closed = false; + + // If This Node Is Cheaper Than The Existing Best Open Node, Add It To The End Of The Open List + //---------------------------------------------------------------------------------------------- + if (openCountcost() < open[openCount-1]->cost())) { + open[openCount] = node; + openCount++; + return; + } + } + + // Otherwise, Add It To The Pending List + //--------------------------------------- + if (pendingCountcost() < pendingBest->cost())) { + pendingBest = node; + } + } + } + + /* + ===================== + UpdateOpenList + ===================== + */ + void UpdateOpenList() { + + // List Is Updated Now, So Remove The Flag + //----------------------------------------- + openListUpdate = false; + + // Add All Pending Nodes To The Open List + //---------------------------------------- + for (int i=0; iclosed) { + open[openCount] = pending[i]; + openCount ++; + } + } + pendingCount = 0; + pendingBest = NULL; + + + // Sort The Open List + //-------------------- + qsort( (void*)open, (size_t)openCount, (size_t)sizeof(visitNode*), visitSort ); + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(idReachability* reach) { + for (int i=visitedIndexReach.First(abs(reach->edgeNum)); i>=0; i=visitedIndexReach.Next(i)) { + if (visited[i].reach==reach) { + return &visited[i]; + } + } + return NULL; + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(int vertexNum) { + for (int i=visitedIndexVert.First(vertexNum); i>=0; i=visitedIndexVert.Next(i)) { + if (visited[i].vertexNum==vertexNum) { + return &visited[i]; + } + } + return NULL; + } + + + /* + ===================== + TravelCost + ===================== + */ + float TravelCost(idReachability* reach, int vertexNum, visitNode* from) { + static float distance; + + const idVec3& start = (from)?(GetSeekPosition(from)):(myMove->myPos); + const idVec3& stop = GetSeekPosition(reach, vertexNum); + + + // Test For Any Obstacles In The Way + //----------------------------------- + const aasArea_t* area = &myAAS->GetFile()->GetArea((from)?(from->reach->toAreaNum):(myMove->myArea)); + if (obstacleFinder.RayTrace(0.0f, area, start, stop, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + + // If It Is Not Possible To Steer Around, Then This Edge Is Completely Invalid + //----------------------------------------------------------------------------- + if (!obstacleFinder.contact.v1Valid && !obstacleFinder.contact.v2Valid) { + return 0.0f; + } + + // Completely Disable Any Points Completely Covered By An Obstacle + //----------------------------------------------------------------- + if (obstacleFinder.contact.obstacle->windings[0/*CDR_TODO: Get myAASNum*/].PointInside(stop)) { + return 0.0f; + } + distance += 128.0f; + } + + + // Compute Standard Distance + //--------------------------- + distance = start.Dist(stop); + if (from) { + distance += from->travelCost + from->reach->travelTime; + } + + return distance; + } + + + /* + ===================== + Visit + ===================== + */ + void Visit(idReachability* reach, int vertexNum, visitNode* from) { + static visitNode* visit; + static float travelCost; + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Compute Travel Cost, And Test To See If This Edge Is Blocked + //-------------------------------------------------------------- + travelCost = TravelCost(reach, vertexNum, from); + if (travelCost==0.0f) { + return; + } + + // If The Visited Version Is Already Less Costly, Then Ignore This Reach + //----------------------------------------------------------------------- + visit = (vertexNum)?(WasVisited(vertexNum)):(WasVisited(reach)); + if (visit && visit->travelCost<=travelCost) { + return; + } + + + + // Reopen Any Nodes That Were Closed + //----------------------------------- + if (visit && visit->closed) { + Open(visit); + + // Otherwise, If The Node Is Already In The Open List, Just Change The Cost And Mark The List For Resorting + //---------------------------------------------------------------------------------------------------------- + } else if (visit) { + visit->from = from; + visit->travelCost = travelCost; + openListUpdate = true; + + // Must Never Have Visited This Node Before, So Make A Whole New One + //------------------------------------------------------------------- + } else { + + const idVec3& pos = GetSeekPosition(reach, vertexNum); + + // Constant Data (Will Never Change) + //----------------------------------- + visit = &visited[visitedCount]; + visit->reach = reach; + visit->vertexNum = vertexNum; + visit->costToGoal = pos.Dist(myMove->goalPos); + + // Temporary Data + //---------------- + visit->from = from; + visit->travelCost = travelCost; + visit->closed = false; + + // Add It To The Hash Table To Be Found Later + //-------------------------------------------- + if (!vertexNum) { + visitedIndexReach.Add(abs(reach->edgeNum), visitedCount); + } else { + visitedIndexVert.Add(vertexNum, visitedCount); + } + visitedCount++; + + // Mark It As Open + //----------------- + Open(visit); + } + } + + /* + ===================== + VisitReach + + This function first visits the center of the reachability, and then if + the edge is long enough and close enough to the start or end of the + path, it visits the verts as well + ===================== + */ + void VisitReach(idReachability *reach, visitNode* from) { + static int verts[2]; + static int vertexNum; + static idVec3 start; + static idVec3 stop; + + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Ignore Any Reach To The Area We Came From + //------------------------------------------- + if (from && from->reach->fromAreaNum==reach->toAreaNum) { + return; + } + + // Ignore Any Reach That Does Not Match Our Travel Flags + //------------------------------------------------------- + if (reach->travelType&TFL_INVALID || !(reach->travelType&myMove->travelFlags)) { + return; + } + + // Visit The Center Of The Reach + //------------------------------- + Visit(reach, 0, from); + + + // If Running Low On Visit Space, Stop Adding Verts + //-------------------------------------------------- + if (visitedCount>=(int)((float)MAX_VISITED * 0.85f)) { + return; + } + + // If Edge Is Far From Start and Goal, Don't Add Verts + //----------------------------------------------------- + if (!reach->fromAreaNum!=myMove->myArea && + !reach->toAreaNum!=myMove->myArea && + !reach->fromAreaNum!=myMove->goalArea && + !reach->toAreaNum!=myMove->goalArea && + reach->start.Dist2XY(myMove->myPos)>22500.0f/*(250*250)*/ && reach->start.Dist2XY(myMove->goalPos)>22500.0f/*(250*250)*/) { + return; + } + + // If This Edge Is Small Enough, Just Skip The Verts + //--------------------------------------------------- + myAAS->GetEdge(reach->edgeNum, start, stop); + if (start.Dist2XY(stop)<6400.0f/*(80*80)*/) { + return; + } + + // Ok, So Visit The Verts Too + //---------------------------- + myAAS->GetEdgeVertexNumbers(reach->edgeNum, verts); + for (int i=0; i<2 && visitedCountGetFile()->GetArea(areaNum); + + // If Visiting From Another Node, Close That Node Now + //---------------------------------------------------- + if (from) { + + // If Already Closed + //------------------- + if (from->closed) { + if (openListUpdate) { + UpdateOpenList(); // this is kind of hacky... + } + + // Don't Bother To Look At These Reaches + //--------------------------------------- + return; + + // Otherwise, Close It + //--------------------- + } else { + Close(from); + } + } + + // Ok, Iterate All Reachabilities Within This Area + //------------------------------------------------- + for (reach=area.reach; reach && visitedCountnext) { + VisitReach(reach, from); + } + + // Finally, If Necessary, Update The Open List Now + //------------------------------------------------- + if (openListUpdate) { + UpdateOpenList(); + } + } + + + /* + ===================== + FindPath + ===================== + */ + bool FindPath(idAAS* aas, idMoveState& move, float radius, bool inDebugMode, idEntity* ignoreEntity, idEntity* ignoreEntity2) { + myAAS = aas; + myMove = &move; + myRadius = radius; + drawVisitTree = inDebugMode; + myIgnoreEntity = ignoreEntity; + myIgnoreEntity2 = ignoreEntity2; + + myMove->pathArea = myMove->goalArea; + myMove->pathTime = gameLocal.GetTime(); + myMove->pathLen = 0; + + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + + + + if (!myMove->myArea) { + return ErrorCondition(); + } + const aasArea_t* myArea = &myAAS->GetFile()->GetArea(myMove->myArea); + const aasArea_t* goalArea = &myAAS->GetFile()->GetArea(myMove->goalArea); + + + // Special Case For Starting In The Goal Area + //-------------------------------------------- + if (myMove->myArea==myMove->goalArea) { + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, myArea, myMove->myPos, myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(NULL); + } + + // If There Is An Obstacle But We Think We Can Steer Around It, Then We've Still Succeeded + //----------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(NULL); + } + } + + + + // Start With My Area + //-------------------- + VisitArea(myMove->myArea); + + + // While Reachabilities Are Still Pending + //---------------------------------------- + while (openCount || pendingCount) { + + // Select Next Visited Node + //-------------------------- + next = SelectNextVisitedNode(); + if (!next) { + return ErrorCondition(); + } + + // If This Node Reaches Our Target Destination, We've Succeeded + //-------------------------------------------------------------- + if (next->reach->toAreaNum==myMove->goalArea) { + next->closed = true; + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, goalArea, GetSeekPosition(next), myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(next); + } + + // Or If There Is An Obstacle, But One Of The Verts Is Valid, Then This Is Still A Safe Course + //--------------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(next); + } + + // Visit All Reaches In The Next Area + //------------------------------------- + } else { + VisitArea(next->reach->toAreaNum, next); + } + } + + return Failure(); + } +}; + +rvPathFinder pathFinder; + + + +void AI_EntityMoved(idEntity* ent) { + obstacleFinder.MarkEntityForUpdate(ent); +} +void AI_MoveInitialize() { + pathFinder.Initialize(); + obstacleFinder.Initialize(); +} + + + + + + + +/* +===================== +idAI::NewCombinedMove +===================== +*/ +void idAI::RVMasterMove( void ) { + static idVec3 mySeekDelta; + static idVec3 mySeekDirection; + static float mySeekDistance; + + static float moveDistance; + static idVec3 moveDelta; + static idEntity* moveBlockEnt; + static monsterMoveResult_t moveResult; + + + //==================================================================== + // UPDATE DATA + // A simple first step for movement is to get data from the command + // and goal arguments and compute changes if necessary. + //==================================================================== + bool seekMove = CanMove() && move.moveCommand>=NUM_NONMOVING_COMMANDS; + bool seekTurn = CanTurn() && move.moveCommand> MOVE_NONE; + + idMat3 myAxis = viewAxis; + const idVec3& myPos = physicsObj.GetOrigin(); + const idBounds& myBounds = physicsObj.GetBounds(); + float myRadius = myBounds.Size().x / 2.0f; + idVec3 myPosOld = move.myPos; // only used for debug graphics + bool myPosMoved = false; // only used for debug graphics + + const idEntity* goalEntity = move.goalEntity.GetEntity(); + const idVec3& goalPos = (goalEntity)?(LastKnownPosition(goalEntity)):(move.moveDest); + + + + + // Update My Position And Area + //----------------------------- + if (move.myArea==0 || move.myPos.Dist2XY(myPos)>20.0f) { + move.myPos = myPos; + move.myArea = PointReachableAreaNum(move.myPos); + myPosMoved = true; + } + aasArea_t* myArea = &aas->GetFile()->GetArea(move.myArea); + + // Update Goal Position And Area + //------------------------------- + if ((seekMove || seekTurn) && move.goalPos.Dist2XY(goalPos)>20.0f) { + move.goalPos = goalPos; + move.goalArea = PointReachableAreaNum(move.goalPos); + } + + // If Reached The Goal Position, Then Stop Moving + //------------------------------------------------ + if (seekMove && ReachedPos( move.goalPos, move.moveCommand, move.range )) { + StopMove( MOVE_STATUS_DONE ); + seekMove = false; + move.pathLen = 0; + } + + // Update The Obstacle Markers + //----------------------------- + obstacleFinder.UpdateObstacles(); + + + + //==================================================================== + // PATH FINDING + // If the goal area is not the same as myArea, then we need to run + // a pathfinding search to get to the goal area. This operation + // will alter the seek position + //==================================================================== + if (seekMove) { + + // If No Path Exists, Find One + //----------------------------- + if (move.pathArea!=move.goalArea || (!move.pathLen && move.pathTime < (gameLocal.time-10000))) { + pathFinder.FindPath(aas, move, myRadius, DebugFilter(ai_debugMove), this, move.goalEntity.GetEntity()); + } + + + // Set The SeekPos + //----------------- + if (move.pathLen) { + move.seekPos = move.path[move.pathLen-1].seekPos; + + // If We've Reached The Next Path Pos, Pop It Off The List And Continue + //---------------------------------------------------------------------- + if (move.seekPos.Dist2XY(move.myPos)entity; + const rvWindingBox& bounds = tr.obstacle->windings[0/*CDR_TODO: Get actual aasNumber*/]; + + // Is The Obstacle Standing On Seek Position + //------------------------------------------- + if (bounds.PointInside(move.seekPos)) { + if (move.myArea!=move.goalArea) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1150; + move.pathArea = 0; // force a refind path next update + } else { + + // Otherwise, Stop And Wait For It To Move + //----------------------------------------- + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1050; + // CDR_TODO: Issue MoveDestInvalid() Callback Here + } + } + + // Is The Obstacle About To Cross My Path? + //----------------------------------------- + else if (tr.v1==-1) { // CDR_TODO: -1 is an obtouse way to detect this + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1500; + } + + // Ok, So Let's Try To Steer Around The Obstacle + //----------------------------------------------- + else { + + // If Neither Vertex Is Valid, Need To Refind Path + //------------------------------------------------- + if (!tr.v1Valid && !tr.v2Valid) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 3250; + move.pathArea = 0; // force a refind path next update + + } else { + + int vertex; + + // Otherwise, Choose The Best Valid Vertex + //----------------------------------------- + if (!tr.v2Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v1; + } else if (!tr.v1Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v2; + } else { + // CDRTODO: Record clockwise / counter clockwise and only test this once. + vertex = (tr.obstacle->origin.IsLeftOf(move.myPos, move.seekPos))?(tr.v2):(tr.v1); + } + + + // Get Close The Contact Point if The Choosen Vertex Is Not In This Area, Before Going Toward The Vertex + //------------------------------------------------------------------------------------------------------- + if (bounds.areas[vertex]!=myArea && move.myPos.Dist2XY(tr.endPoint)>400.0f /*20*20*/) { + move.seekPos = tr.endPoint; + } else { + move.seekPos = bounds.verts[vertex]; + } + + + // And Recompute The Seek Vectors + //-------------------------------- + mySeekDelta = move.seekPos - move.myPos; + mySeekDirection = mySeekDelta; + mySeekDistance = mySeekDirection.NormalizeFast(); + } + } + } + } + + + + + + //==================================================================== + // TURNING + // Having finialized our seek position, it is now time to turn + // toward it. + //==================================================================== + if (seekTurn) { + DirectionalTurnToward(move.seekPos); + } + Turn(); + + + + //==================================================================== + // VELOCITY + // Now we need to get the instantanious velocity vector, which will + // usually come right from the animation + //==================================================================== + if (move.moveType == MOVETYPE_ANIM) { + if ( move.fl.allowAnimMove || move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( myAxis, viewAxis, moveDelta ); + } else { + moveDelta.Zero(); + } + } else { + // CDR_TODO: Other movetypes here + } + + // If Doing Seek Move, Cap The Delta To Avoid Overshooting The Seek Position + //--------------------------------------------------------------------------- + if (seekMove && moveDelta!=vec3_zero) { + moveDistance = moveDelta.LengthFast(); + + if (mySeekDistance<0.5f) { + moveDelta = vec3_zero; + } else if (mySeekDistanceIsType( idMoveable::GetClassType() ) && moveBlockEnt->GetPhysics()->IsPushable()) { + KickObstacles( viewAxis[ 0 ], move.kickForce, moveBlockEnt ); + } + + // Touch Triggers + //---------------- + if (moveDelta!=vec3_zero ) { + if (moveResult!=MM_BLOCKED) { + TouchTriggers(); + } + } + + + + //==================================================================== + // DEBUG GRAPHICS + //==================================================================== + idVec3 origin = physicsObj.GetOrigin(); + if (DebugFilter(ai_debugMove)) { + static const idVec3 upPole(0.0f, 0.0f, 60.0f); + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + + gameRenderWorld->DebugBounds(colorMagenta, physicsObj.GetBounds(), origin, gameLocal.msec); // Bounds: MAGENTA + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + gameRenderWorld->DebugLine(colorPurple, move.goalPos, move.goalPos + upPole, gameLocal.msec); // Goal: PURPLE + + if (myPosMoved) { + gameRenderWorld->DebugLine(colorCyan, myPosOld, move.myPos, 800); // Trail: CYAN + } + + if (move.pathLen) { + for (int i=move.pathLen-1; i>=0; i--) { + gameRenderWorld->DebugLine(colorBlue, origin, move.path[i].seekPos, gameLocal.msec); // FoundPath: BLUE + origin = move.path[i].seekPos; + } + } + // PathVisited: ORANGE + // PathOpened: YELLOW + } + + if (DebugFilter(ai_showObstacleAvoidance)) { + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + const idVec3& obstaclePos = (move.obstacle.GetEntity())?(move.obstacle->GetPhysics()->GetOrigin()):(move.seekPos); + + if (!DebugFilter(ai_debugMove)) { + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + } + + if (move.blockTime>gameLocal.time) { + gameRenderWorld->DebugArrow(colorRed, move.myPos, obstaclePos, 3, gameLocal.msec); // Blocked Obstacle: RED + } else if (move.fl.obstacleInPath) { + gameRenderWorld->DebugArrow(colorWhite, move.myPos, obstaclePos, 3, gameLocal.msec); // Walk Obstacle: WHITE + } + // ObstacleBox: YELLOW + // VertexInvalid: RED + // FuturePosition: BROWN + obstacleFinder.DrawDebugGraphics(); + } +} + +/* +===================== +idAI::Move +===================== +*/ +void idAI::Move ( void ) { + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Start ( ); + } + + switch( move.moveType ) { + case MOVETYPE_DEAD: + DeadMove(); + break; + case MOVETYPE_FLY : + FlyMove(); + break; + case MOVETYPE_STATIC : + StaticMove(); + break; + case MOVETYPE_ANIM : + AnimMove(); + break; + case MOVETYPE_SLIDE : + SlideMove(); + break; + case MOVETYPE_PLAYBACK: + PlaybackMove(); + break; + case MOVETYPE_CUSTOM: + CustomMove(); + break; + } + + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Stop ( ); + } +} + diff --git a/source/mpgame/ai/AI_Move.h b/source/mpgame/ai/AI_Move.h new file mode 100644 index 0000000..6cec8a1 --- /dev/null +++ b/source/mpgame/ai/AI_Move.h @@ -0,0 +1,295 @@ +/* +=============================================================================== + +AI_Move.h + +This file has all movement related typedefs, enums, flags, and structures. It +and its sister CPP file were split from AI.h and AI.cpp in order to prevent +merge conflicts and to make further changes to the system possible. + +=============================================================================== +*/ + +#ifndef __AI_MOVE_H__ +#define __AI_MOVE_H__ + + +typedef idEntityPtr entityPointer_t; + +#define MAX_PATH_LEN 48 + +/* +===================== +aiMoveDir_t - used by directional movement code +===================== +*/ +typedef enum { + MOVEDIR_FORWARD, + MOVEDIR_BACKWARD, + MOVEDIR_LEFT, + MOVEDIR_RIGHT, + MOVEDIR_MAX +} aiMoveDir_t; +extern const char* aiMoveDirectionString [ MOVEDIR_MAX ]; + +/* +===================== +moveType_t - determines which movement function to call +===================== +*/ +typedef enum { + MOVETYPE_DEAD, + MOVETYPE_ANIM, + MOVETYPE_SLIDE, + MOVETYPE_FLY, + MOVETYPE_STATIC, + MOVETYPE_PLAYBACK, + //twhitaker: added custom move type + MOVETYPE_CUSTOM, + NUM_MOVETYPES +} moveType_t; + +/* +===================== +aiMoveCommand_t - tells the AI how to get there +===================== +*/ +typedef enum { + MOVE_NONE, + MOVE_FACE_ENEMY, + MOVE_FACE_ENTITY, + + // commands < NUM_NONMOVING_COMMANDS don't cause a change in position + NUM_NONMOVING_COMMANDS, + + MOVE_TO_ENEMY = NUM_NONMOVING_COMMANDS, + MOVE_TO_ENTITY, + MOVE_TO_ATTACK, + MOVE_TO_HELPER, + MOVE_TO_TETHER, + MOVE_TO_COVER, + MOVE_TO_HIDE, + MOVE_TO_POSITION, + MOVE_TO_POSITION_DIRECT, + MOVE_OUT_OF_RANGE, + MOVE_SLIDE_TO_POSITION, + MOVE_WANDER, + MOVE_RV_PLAYBACK, + NUM_MOVE_COMMANDS + +} aiMoveCommand_t; + +/* +===================== +moveStatus_t - status results from move commands + +make sure to change script/doom_defs.script if you add any, or change their order +===================== +*/ +typedef enum { + MOVE_STATUS_DONE, + MOVE_STATUS_MOVING, + MOVE_STATUS_WAITING, + MOVE_STATUS_DEST_NOT_FOUND, + MOVE_STATUS_DEST_UNREACHABLE, + MOVE_STATUS_BLOCKED_BY_WALL, + MOVE_STATUS_BLOCKED_BY_OBJECT, + MOVE_STATUS_BLOCKED_BY_ENEMY, + MOVE_STATUS_BLOCKED_BY_MONSTER, + MOVE_STATUS_BLOCKED_BY_PLAYER, + MOVE_STATUS_DISABLED, + NUM_MOVE_STATUS, +} moveStatus_t; +extern const char* aiMoveStatusString[ NUM_MOVE_STATUS ]; + +/* +===================== +stopEvent_t - used by path prediction +===================== +*/ +typedef enum { + SE_BLOCKED = BIT(0), + SE_ENTER_LEDGE_AREA = BIT(1), + SE_ENTER_OBSTACLE = BIT(2), + SE_FALL = BIT(3), + SE_LAND = BIT(4) +} stopEvent_t; + +/* +===================== +obstaclePath_s +===================== +*/ +typedef struct obstaclePath_s { + idVec3 seekPos; // seek position avoiding obstacles + idEntity * firstObstacle; // if != NULL the first obstacle along the path + idVec3 startPosOutsideObstacles; // start position outside obstacles + idEntity * startPosObstacle; // if != NULL the obstacle containing the start position + idVec3 seekPosOutsideObstacles; // seek position outside obstacles + idEntity * seekPosObstacle; // if != NULL the obstacle containing the seek position + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idList allObstacles; + // RAVEN END +} obstaclePath_t; + +/* +===================== +predictedPath_s +===================== +*/ +typedef struct predictedPath_s { + idVec3 endPos; // final position + idVec3 endVelocity; // velocity at end position + idVec3 endNormal; // normal of blocking surface + int endTime; // time predicted + int endEvent; // event that stopped the prediction + const idEntity * blockingEntity; // entity that blocks the movement +} predictedPath_t; + + +/* +===================== +pathSeek_s +===================== +*/ +typedef struct pathSeek_s { + idReachability* reach; + idVec3 seekPos; +} pathSeek_t; + + + +/* +===================== +idMoveState +===================== +*/ +class idMoveState { +public: + idMoveState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Spawn( idDict &spawnArgs ); + + + struct movementFlags_s { + + // current state + bool done :1; + bool moving :1; + bool crouching :1; // is currently crouching? + bool running :1; // is currently running? + bool blocked :1; + bool obstacleInPath :1; + bool goalUnreachable :1; + bool onGround :1; + bool flyTurning :1; // lean into turns when flying + + // ideal state + bool idealRunning :1; // what we want running to be + + + // various enable / disable flags + bool disabled :1; // is movement disabled? (this includes turning) + bool ignoreObstacles :1; // don't check for obsticles in path + + bool allowDirectional :1; // allows directional movement (ie. running sideways, backwards) + bool allowAnimMove :1; // allows any animation movement + bool allowPrevAnimMove :1; // allows slide move if current animmove has no motion extraction on it (smooth transitions) + bool allowHiddenMove :1; // allows character to still move around while hidden + bool allowPushMovables :1; // allows the articulated figure to push moveable objects + bool allowSlideToGoal :1; // allows the AI to excactly slide to the goal position if close enough + + bool noRun :1; // force the actor to walk + bool noWalk :1; // force the actor to run + bool noTurn :1; // can the ai turn? + bool noGravity :1; // dont use gravity + bool noRangedInterrupt :1; // dont stop halfway to attack position just because you have a clear shot + } fl; + + + moveType_t moveType; + aiMoveCommand_t moveCommand; + moveStatus_t moveStatus; + idVec3 moveDest; + idVec3 moveDir; // used for wandering and slide moves + + int toAreaNum; + int startTime; + int duration; + float speed; // only used by flying creatures + float range; + float wanderYaw; + int nextWanderTime; + int blockTime; + idEntityPtr obstacle; + idVec3 lastMoveOrigin; + int lastMoveTime; + int anim; + + int travelFlags; + + float kickForce; + float blockedRadius; + int blockedMoveTime; + int blockedAttackTime; + + // turning + float ideal_yaw; + float current_yaw; + float turnRate; + float turnVel; + float anim_turn_yaw; + float anim_turn_amount; + float anim_turn_angles; + + // flying + jointHandle_t flyTiltJoint; + float fly_speed; + float fly_bob_strength; + float fly_bob_vert; + float fly_bob_horz; + int fly_offset; // prefered offset from player's view + float fly_seek_scale; + float fly_roll_scale; + float fly_roll_max; + float fly_roll; + float fly_pitch_scale; + float fly_pitch_max; + float fly_pitch; + + aiMoveDir_t currentDirection; // Direction currently moving in + aiMoveDir_t idealDirection; // Direction we want to be moving in + float walkRange; // Distance to target before starting to walk + float walkTurn; // Turn delta threshold for walking when turning + idVec2 followRange; // Min and max range for AI following their leader + idVec2 searchRange; // Min and max range to use when searching for a new place to move to + float attackPositionRange; // Override for how close you have to get to your attackPosition before stopping + float turnDelta; // Amount to turn when turning + + idVec3 goalPos; + int goalArea; + entityPointer_t goalEntity; + idVec3 goalEntityOrigin; // move to entity uses this to avoid checking the floor position every frame + + idVec3 myPos; + int myArea; + + idVec3 seekPos; + + pathSeek_t path[MAX_PATH_LEN]; + int pathLen; + int pathArea; + int pathTime; + + idVec3 addVelocity; +}; + +// cdr: Obstacle Avoidance +void AI_EntityMoved(idEntity* ent); +void AI_MoveInitialize(); + + +#endif /* !__AI_MOVE_H__ */ diff --git a/source/mpgame/ai/AI_States.cpp b/source/mpgame/ai/AI_States.cpp new file mode 100644 index 0000000..7bb6a20 --- /dev/null +++ b/source/mpgame/ai/AI_States.cpp @@ -0,0 +1,2213 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +static const int TACTICALUPDATE_MELEEDELAY = 500; // Rate to update tactical state when rushing +static const int TACTICALUPDATE_RANGEDDELAY = 2000; // Rate to update tactical state when in ranged combat +static const int TACTICALUPDATE_COVERDELAY = 5000; // Rate to update tactical state when in cover +static const int TACTICALUPDATE_HIDEDELAY = 5000; // Rate to update tactical state when hiding +static const int TACTICALUPDATE_FOLLOWDELAY = 500; // Rate to update tactical state when following +static const int TACTICALUPDATE_MOVETETHERDELAY = 1000; // Rate to update tactical state when moving into tether range +static const int TACTICALUPDATE_PASSIVEDELAY = 500; // Rate to update tactical state when in passive mode +static const int TACTICALUPDATE_TURRETDELAY = 250; // Rate to update tactical state when in turret mode + +static const int RANGED_ENEMYDELAY = 2000; // Time to wait to move after losing sight of an enemy +static const int COVER_ENEMYDELAY = 5000; // Stay behind cover for 5 seconds after loosing sight of an enemy + +static const float COVER_TRIGGERRADIUS = 64.0f; + +CLASS_STATES_DECLARATION ( idAI ) + // Wait States + STATE ( "Wait_Activated", idAI::State_Wait_Activated ) + STATE ( "Wait_ScriptedDone", idAI::State_Wait_ScriptedDone ) + STATE ( "Wait_Action", idAI::State_Wait_Action ) + STATE ( "Wait_ActionNoPain", idAI::State_Wait_ActionNoPain ) + + // Global States + STATE ( "State_WakeUp", idAI::State_WakeUp ) + STATE ( "State_TriggerAnim", idAI::State_TriggerAnim ) + + // Passive states + STATE ( "State_Passive", idAI::State_Passive ) + + // Combat states + STATE ( "State_Combat", idAI::State_Combat ) + STATE ( "State_CombatCover", idAI::State_CombatCover ) + STATE ( "State_CombatMelee", idAI::State_CombatMelee ) + STATE ( "State_CombatRanged", idAI::State_CombatRanged ) + STATE ( "State_CombatTurret", idAI::State_CombatTurret ) + STATE ( "State_CombatHide", idAI::State_CombatHide ) + + STATE ( "State_Wander", idAI::State_Wander ) + STATE ( "State_MoveTether", idAI::State_MoveTether ) + STATE ( "State_MoveFollow", idAI::State_MoveFollow ) + STATE ( "State_MovePlayerPush", idAI::State_MovePlayerPush ) + STATE ( "State_Killed", idAI::State_Killed ) + STATE ( "State_Dead", idAI::State_Dead ) + STATE ( "State_LightningDeath", idAI::State_LightningDeath ) + STATE ( "State_Burn", idAI::State_Burn ) + STATE ( "State_Remove", idAI::State_Remove ) + STATE ( "State_ScriptedMove", idAI::State_ScriptedMove ) + STATE ( "State_ScriptedFace", idAI::State_ScriptedFace ) + STATE ( "State_ScriptedStop", idAI::State_ScriptedStop ) + STATE ( "State_ScriptedPlaybackMove", idAI::State_ScriptedPlaybackMove ) + STATE ( "State_ScriptedPlaybackAim", idAI::State_ScriptedPlaybackAim ) + STATE ( "State_ScriptedJumpDown", idAI::State_ScriptedJumpDown ) + + // Torso States + STATE ( "Torso_Idle", idAI::State_Torso_Idle ) + STATE ( "Torso_Sight", idAI::State_Torso_Sight ) + STATE ( "Torso_CustomCycle", idAI::State_Torso_CustomCycle ) + STATE ( "Torso_Action", idAI::State_Torso_Action ) + STATE ( "Torso_FinishAction", idAI::State_Torso_FinishAction ) + STATE ( "Torso_Pain", idAI::State_Torso_Pain ) + STATE ( "Torso_ScriptedAnim", idAI::State_Torso_ScriptedAnim ) + STATE ( "Torso_PassiveIdle", idAI::State_Torso_PassiveIdle ) + STATE ( "Torso_PassiveFidget", idAI::State_Torso_PassiveFidget ) + + // Leg States + STATE ( "Legs_Idle", idAI::State_Legs_Idle ) + STATE ( "Legs_Move", idAI::State_Legs_Move ) + STATE ( "Legs_MoveThink", idAI::State_Legs_MoveThink ) + STATE ( "Legs_TurnLeft", idAI::State_Legs_TurnLeft ) + STATE ( "Legs_TurnRight", idAI::State_Legs_TurnRight ) + STATE ( "Legs_ChangeDirection", idAI::State_Legs_ChangeDirection ) + + // Head States + STATE ( "Head_Idle", idAI::State_Head_Idle ) + +END_CLASS_STATES + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +/* +================ +idAI::State_TriggerAnim +================ +*/ +stateResult_t idAI::State_TriggerAnim ( const stateParms_t& parms ) { + const char* triggerAnim; + + // If we dont have the trigger anim, just skip it + triggerAnim = spawnArgs.GetString ( "trigger_anim" ); + if ( !*triggerAnim || !HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + gameLocal.Warning ( "Missing or invalid 'trigger_anim' ('%s') specified for entity '%s'", + triggerAnim, GetName() ); + return SRESULT_DONE; + } + + Show(); + ScriptedAnim ( triggerAnim, 4, false, true ); + + // FIXME: should do this a better way + // Alwasy let trigger anims play out + fl.neverDormant = true; + + return SRESULT_DONE; +} + +/* +================ +idAI::State_WakeUp +================ +*/ +stateResult_t idAI::State_WakeUp ( const stateParms_t& parms ) { + const char* triggerAnim; + + WakeUp ( ); + + // Start immeidately into a playback? + if( spawnArgs.FindKey( "playback_intro" ) ){ + ScriptedPlaybackMove ( "playback_intro", PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, 0 ); + // Start immeidately into a scripted anim? + } else if ( spawnArgs.GetString ( "trigger_anim", "", &triggerAnim) && *triggerAnim && HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + PostState ( "State_TriggerAnim" ); + return SRESULT_DONE; + } else { + // Either wait to be triggered or wait until they have an enemy + if ( spawnArgs.GetBool ( "trigger" ) ) { + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + + PostState ( "Wait_Activated" ); + } + } + + PostState ( "State_Combat" ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Passive +================ +*/ +stateResult_t idAI::State_Passive ( const stateParms_t& parms ) { + if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + if ( UpdateTactical ( TACTICALUPDATE_PASSIVEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Combat + +The base combat state is basically the clearing house for other combat states. By calling +UpdateTactical with a timer of zero it ensures a better tactical move can be found. +================ +*/ +stateResult_t idAI::State_Combat ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + combat.tacticalCurrent = AITACTICAL_NONE; + + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Update the tactical state using all available tactical abilities and reset + // the current tactical state since this is the generic state. + combat.tacticalCurrent = AITACTICAL_NONE; + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If we are here then there isnt a single combat state available, thats not good + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatCover + +Seek out a cover point that has a vantage point on the enemy. Stay there firing at the +enemy until the cover point is invalidated. +================ +*/ +stateResult_t idAI::State_CombatCover ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_ATTACK, + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done && aasSensor->Reserved ( ) ) { + StopMove ( MOVE_STATUS_DONE ); + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we dont have a cover point anymore then just bail out + if ( !aasSensor->Reserved ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // If we have moved off our cover point, move back + if ( DistanceTo ( aasSensor->ReservedOrigin ( ) ) > 8.0f ) { + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + } + // Dont do any cover checks until they are facing towards the wall + if ( !FacingIdeal ( ) ) { + return SRESULT_WAIT; + } + // Perform cover point actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +idAI::State_CombatMelee + +Head directly towards our enemy but allow ranged actions along the way +================ +*/ +stateResult_t idAI::State_CombatMelee ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the enemy + STAGE_ATTACK, // Keep attacking until no longer in melee range + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // If we can no longer get to our enemy, give up on this and do something else! + if ( move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we are out of melee range or lost sight of our enemy then start moving again + if ( !IsEnemyVisible ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Always face enemy when in melee range + TurnToward ( enemy.lastKnownPosition ); + + // Perform actions while standing still + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatRanged + +Move towards enemy until within firing range then continue to shoot at the enemy +================ +*/ +stateResult_t idAI::State_CombatRanged ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move to an attack position + STAGE_ATTACK, // Perform actions until the enemy is out of range or not visible + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // if we lost our enemy we are done here + if ( !enemy.ent ) { + ForceTacticalUpdate ( ); + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + // If done moving or within range of a visible enemy we can stop + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Stop early because we have a shot on our enemy? + if ( !move.fl.noRangedInterrupt && + !aifl.simpleThink && enemy.fl.visible + && gameLocal.time - enemy.lastVisibleChangeTime > 500 + && enemy.range >= combat.attackRange[0] + && enemy.range <= combat.attackRange[1] + && ( DistanceTo ( move.moveDest ) < DistanceTo ( move.lastMoveOrigin ) ) + && (!tether || tether->ValidateDestination ( this, physicsObj.GetOrigin( ) ) ) + && aiManager.ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If the enemy is visible but not in fov then rotate + if ( !enemy.fl.inFov && enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatTurret + +Stay put and shoot at the enemy when nearby +================ +*/ +stateResult_t idAI::State_CombatTurret ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) && !enemy.fl.inFov ) { + TurnToward ( enemy.lastKnownPosition ); + // Turn towards tether direction if we have no enemy + } else if ( !enemy.ent && tether ) { + TurnToward ( physicsObj.GetOrigin() + tether->GetPhysics()->GetAxis()[0] * 64.0f ); + } else if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If there are other available tactical states besides turret then try to + // go to one of them + if ( combat.tacticalMaskAvailable & ~(1<GetPhysics()->GetAxis()[0]; + TurnToward ( toPos ); + if ( !IsBehindCover() ) { + //Do a trace *once* to see if I can crouch-look in the direction of the tether at this point + trace_t tr; + idVec3 crouchEye = physicsObj.GetOrigin( ); + crouchEye.z += 32.0f; + gameLocal.TracePoint( this, tr, crouchEye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + combat.fl.crouchViewClear = true; + } + } + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } + // Update tactical state occasionally to see if there is something better to do + if ( UpdateTactical ( TACTICALUPDATE_MOVETETHERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MoveFollow +================ +*/ +stateResult_t idAI::State_MoveFollow ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + // If we lost our leader we are done + if ( !leader ) { + move.fl.done = true; + // Can we just stop here? + } else if ( DistanceTo ( leader ) < (move.followRange[0]+move.followRange[1])*0.5f && aiManager.ValidateDestination ( this, physicsObj.GetOrigin( ), false, leader ) ) { + move.fl.done = true; + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + move.fl.done = false; + } + */ + } + // Are we done moving? + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + if ( leader ) { + TurnToward ( leader->GetPhysics()->GetOrigin() ); + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else { + if ( UpdateTactical ( TACTICALUPDATE_FOLLOWDELAY ) ) { + return SRESULT_DONE_WAIT; + } + } + + // Perform actions on the way to the enemy + UpdateAction ( ); + + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MovePlayerPush + +Move out of the way when the player is pushing us +================ +*/ +stateResult_t idAI::State_MovePlayerPush ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + if ( move.fl.done ) { + StopMove(MOVE_STATUS_DONE); + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else if ( gameLocal.GetTime() - move.startTime > 2000 ) { + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Killed +================ +*/ +stateResult_t idAI::State_Killed ( const stateParms_t& parms ) { + disablePain = true; + + //quickburning subjects skip all this jazz + if( fl.quickBurn ) { + PostState ( "State_Dead" ); + return SRESULT_DONE; + } + + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + // Make sure all animations stop + animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; +} + + +/* +================ +idAI::State_Dead +================ +*/ +stateResult_t idAI::State_Dead ( const stateParms_t& parms ) { + if ( !fl.hidden ) { + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + if( fl.quickBurn ) { + StopRagdoll(); + PostState ( "State_Burn", SEC2MS(0.05f) ); + } else if ( spawnArgs.GetString( "fx_burn_lightning", NULL ) ) { + lightningNextTime = 0; + lightningEffects = 0; + PostState ( "State_LightningDeath", SEC2MS(burnDelay) ); + } else { + PostState ( "State_Burn", SEC2MS(burnDelay) ); + } + } + float removeDelay = SEC2MS ( spawnArgs.GetFloat ( "removeDelay" ) ); + if ( removeDelay >= 0.0f ) { + PostState ( "State_Remove", removeDelay ); + } + } else { + PostState ( "State_Remove" ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_LightningDeath +================ +*/ +stateResult_t idAI::State_LightningDeath ( const stateParms_t& parms ) { + if ( gameLocal.time > lightningNextTime ) { + if ( !lightningEffects ) + { + StartSound ( "snd_burn_lightning", SND_CHANNEL_BODY, 0, false, NULL ); + } + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( spawnArgs, "fx_burn_lightning" ), this, 100 ); + effect->Play ( gameLocal.time, false ); + lightningNextTime = gameLocal.time + 100; + lightningEffects++; + } + if ( lightningEffects < 10 ) + { + return SRESULT_WAIT; + } + + /* + if ( spawnArgs.GetString( "fx_burn_lightning" ) ) + { + for ( int i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + if ( !gameLocal.random.RandomInt(1) ) { + PlayEffect( "fx_burn_lightning", (jointHandle_t)i ); + } + } + } + } + */ + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + PostState ( "State_Burn", 0 ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Burn +================ +*/ +stateResult_t idAI::State_Burn ( const stateParms_t& parms ) { + if( fl.hidden ){ + return SRESULT_DONE; + } + + + renderEntity.noShadow = true; + + // Dont let the articulated figure be shot once they start burning away + SetCombatContents ( false ); + fl.takedamage = false; + + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + idEntity *head = GetHead(); + if ( head ) { + head->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + } + + UpdateVisuals(); + + if ( spawnArgs.GetString( "fx_burn_particles", NULL ) ) + { + int i = GetAnimator()->GetJointHandle( spawnArgs.GetString("joint_chestOffset","chest") ); + if ( i != INVALID_JOINT ) + { + PlayEffect( "fx_burn_particles_chest", (jointHandle_t)i ); + } + + for ( i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + PlayEffect( "fx_burn_particles", (jointHandle_t)i ); + } + } + //FIXME: head, too? + //FIXME: from joint to parent joint? + //FIXME: not small joints... + } + + StartSound ( "snd_burn", SND_CHANNEL_BODY, 0, false, NULL ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Remove +================ +*/ +stateResult_t idAI::State_Remove ( const stateParms_t& parms ) { + PostEventMS( &EV_Remove, 0 ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_ScriptedAnim +================ +*/ +stateResult_t idAI::State_Torso_ScriptedAnim ( const stateParms_t& parms ) { + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + aifl.scripted = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedMove +================ +*/ +stateResult_t idAI::State_ScriptedMove ( const stateParms_t& parms ) { + if ( !aifl.scripted || move.fl.done ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedFace +================ +*/ +stateResult_t idAI::State_ScriptedFace ( const stateParms_t& parms ) { + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedPlaybackMove +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + if ( mPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedPlaybackAim +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackAim ( const stateParms_t& parms ) { + if ( mLookPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedStop +================ +*/ +stateResult_t idAI::State_ScriptedStop ( const stateParms_t& parms ) { + ScriptedEnd ( ); + + // If ending in an idle animation move the legs back to idle and + // revert back to normal combat + if ( aifl.scriptedEndWithIdle ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + } + + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Torso States + +=============================================================================== +*/ + +/* +================ +idAI::State_Torso_Idle +================ +*/ +stateResult_t idAI::State_Torso_Idle ( const stateParms_t& parms ) { + // Custom passive idle? + if ( combat.tacticalCurrent == AITACTICAL_PASSIVE && passive.animIdlePrefix.Length() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the legs were disabled then get them back into idle again + if ( legsAnim.Disabled ( ) ) { + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + } + + // Start idle animation + const char* idleAnim = GetIdleAnimName (); + if ( !torsoAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_TORSO, idleAnim, parms.blendFrames ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_PassiveIdle +================ +*/ +stateResult_t idAI::State_Torso_PassiveIdle ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT, + STAGE_TALK_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + // Talk animation? + if ( passive.talkTime > gameLocal.time ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Find their talk animation + animName = spawnArgs.GetString ( va("%s_%s", passive.animTalkPrefix.c_str(), postfix.c_str() ) ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.GetString ( passive.animTalkPrefix ); + } + + if ( animName.Length ( ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_TALK_WAIT ); + } + } + + // If we have a fidget animation then see if its time to play it + if ( passive.animFidgetPrefix.Length() ) { + if ( passive.fidgetTime == 0 ) { + passive.fidgetTime = gameLocal.time + SEC2MS ( spawnArgs.GetInt ( "fidget_rate", "20" ) ); + } else if ( gameLocal.time > passive.fidgetTime ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveFidget", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", 4 ); + return SRESULT_DONE; + } + } + + // Do we have a custom idle animation? + passive.idleAnim = spawnArgs.RandomPrefix ( passive.animIdlePrefix, gameLocal.random ); + passive.idleAnimChangeTime = passive.fl.multipleIdles ? SEC2MS ( spawnArgs.GetFloat ( "idle_change_rate", "10" ) ) : 0; + + // Is there a start animation for the idle? + animName = va("%s_start", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, 0 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + // Talk animation interrupting? + if ( passive.animTalkPrefix.Length() && passive.talkTime > gameLocal.time ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If its time to play a new idle animation then do so + if ( passive.idleAnimChangeTime && gameLocal.time > passive.idleAnimChangeTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If its time to fidget then do so + if ( passive.fidgetTime && gameLocal.time > passive.fidgetTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // Loop the idle again + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va("%s_end", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_STAGE ( STAGE_START ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + + case STAGE_TALK_WAIT: + if ( gameLocal.time > passive.talkTime ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_PassiveFidget +================ +*/ +stateResult_t idAI::State_Torso_PassiveFidget ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_INIT: + passive.fidgetTime = 0; + animName = spawnArgs.RandomPrefix ( va("anim_%sfidget", passive.prefix.c_str() ), gameLocal.random ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.RandomPrefix ( "anim_fidget", gameLocal.random ); + } + if ( !animName.Length ( ) ) { + return SRESULT_DONE; + } + if ( !PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_ScriptedJumpDown + +Face edge, walk off +================ +*/ +stateResult_t idAI::State_ScriptedJumpDown( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FACE_WAIT, + STAGE_ANIM, + STAGE_JUMP_WAIT, + STAGE_INAIR, + STAGE_WAIT_LAND, + STAGE_ANIM_WAIT, + STAGE_FINISH + }; + + switch ( parms.stage ) { + case STAGE_INIT: + { + aifl.scripted = true; + Event_SaveMove(); + StopMove(MOVE_STATUS_DONE); + //move.current_yaw = move.ideal_yaw; + move.moveCommand = MOVE_NONE; + move.fl.allowDirectional = false; + } + if ( !move.fl.onGround ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_STAGE ( STAGE_FACE_WAIT ); + break; + case STAGE_FACE_WAIT: + if ( FacingIdeal() ) + { + return SRESULT_STAGE ( STAGE_ANIM ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM: + { + DisableAnimState ( ANIMCHANNEL_LEGS ); + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + if ( HasAnim( ANIMCHANNEL_TORSO, "jumpdown_start" ) ) { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_start", 4 ); + move.fl.allowAnimMove = true; + move.fl.noGravity = true; + return SRESULT_STAGE ( STAGE_JUMP_WAIT ); + } + return SRESULT_STAGE ( STAGE_INAIR ); + } + break; + case STAGE_JUMP_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_WAIT; + break; + case STAGE_INAIR: + { + idVec3 vel = viewAxis[0] * 200.0f; + vel.z = -50.0f; + physicsObj.SetLinearVelocity( vel ); + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_loop", 4 ); + move.fl.allowAnimMove = false; + move.fl.noGravity = false; + return SRESULT_STAGE ( STAGE_WAIT_LAND ); + } + break; + case STAGE_WAIT_LAND: + if ( physicsObj.OnGround() )//GetPhysics()->HasGroundContacts() ) + { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_end", 0 ); + move.fl.allowAnimMove = true; + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 ) ) + { + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + break; + case STAGE_FINISH: + Event_RestoreMove(); + SetState( "State_Combat" ); + aifl.scripted = false; + return SRESULT_DONE; + break; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_CustomCycle +================ +*/ +stateResult_t idAI::State_Torso_CustomCycle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Sight +================ +*/ +stateResult_t idAI::State_Torso_Sight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + // Dont let everyone on the team play their sight sounds at the same time + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT ) ) { + Speak ( "lipsync_sight", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT, 1000 ); + } + + // Execute sighted scripts + if( !enemy.fl.sighted ) { + enemy.fl.sighted = true; + ExecScriptFunction ( funcs.first_sight, this ); + } + ExecScriptFunction ( funcs.sight ); + + idStr animName = spawnArgs.GetString ( "anim_sight", spawnArgs.GetString ( "sight_anim" ) ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_DONE; + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_Action +================ +*/ +stateResult_t idAI::State_Torso_Action ( const stateParms_t& parms ) { + // Invalid animation so dont bother with running the action + if ( actionAnimNum == -1 ) { + return SRESULT_DONE_WAIT; + } + + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + + // Wait till animation is finished + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", parms.blendFrames ); + + return SRESULT_DONE_WAIT; +} + +/* +================ +idAI::State_Torso_FinishAction +================ +*/ +stateResult_t idAI::State_Torso_FinishAction ( const stateParms_t& parms ) { + + RestoreFlag ( AIFLAGOVERRIDE_DISABLEPAIN ); + RestoreFlag ( AIFLAGOVERRIDE_DAMAGE ); + RestoreFlag ( AIFLAGOVERRIDE_NOTURN ); + RestoreFlag ( AIFLAGOVERRIDE_NOGRAVITY ); + + enemy.fl.lockOrigin = false; + aifl.action = false; + + OnStopAction ( ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Pain +================ +*/ +stateResult_t idAI::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + pain.loopEndTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "pain_maxLoopTime", "1" ) ); + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + animName = va( "%s_start", painAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 1 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_TORSO, painAnim, 1 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + if ( gameLocal.time - pain.lastTakenTime > AI_PAIN_LOOP_DELAY ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( !pain.loopEndTime || gameLocal.time > pain.loopEndTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va( "%s_end", painAnim.c_str() ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, 1 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Head States + +=============================================================================== +*/ + +/* +================ +idAI::State_Head_Idle +================ +*/ +stateResult_t idAI::State_Head_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idAI::State_Legs_Idle +================ +*/ +stateResult_t idAI::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + const char* idleAnim; + move.fl.allowAnimMove = false; + idleAnim = GetIdleAnimName ( ); + if ( !legsAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_LEGS, idleAnim, parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + // Dont let the legs do anything from idle until they are done blending + // the animations they have (this is to prevent pops when an action finishes + // and blends to idle then immediately the legs move to walk) + if ( animator.IsBlending ( ANIMCHANNEL_LEGS, gameLocal.time ) ) { + return SRESULT_WAIT; + } + + if ( move.fl.moving && CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Legs_TurnLeft +================ +*/ +stateResult_t idAI::State_Legs_TurnLeft ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_TurnRight +================ +*/ +stateResult_t idAI::State_Legs_TurnRight ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_Move +================ +*/ +stateResult_t idAI::State_Legs_Move ( const stateParms_t& parms ) { + idStr animName; + + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // Movement direction changed? + if ( move.idealDirection != move.currentDirection ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_ChangeDirection", parms.blendFrames ); + } + + // Make sure run status is up to date when legs start moving + UpdateRunStatus ( ); + + // Run or walk? + animName = "run"; + if ( !move.fl.idealRunning && HasAnim ( ANIMCHANNEL_TORSO, "walk" ) ) { + animName = "walk"; + } + + // Append the run direction to the animation name + if ( move.idealDirection == MOVEDIR_LEFT ) { + animName = animName + "_left"; + } else if ( move.idealDirection == MOVEDIR_RIGHT ) { + animName = animName + "_right"; + } else if ( move.idealDirection == MOVEDIR_BACKWARD ) { + animName = animName + "_backwards"; + } else { +/* + // When moving to cover the walk animation is special + if ( move.moveCommand == MOVE_TO_COVER && !move.fl.idealRunning && move.fl.running ) { + animName = "run_slowdown"; + } +*/ + } + + // Update running flag with ideal state + move.fl.running = move.fl.idealRunning; + move.currentDirection = move.idealDirection; + + PlayCycle ( ANIMCHANNEL_LEGS, animName, parms.blendFrames ); + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_MoveThink", parms.blendFrames ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_MoveThink +================ +*/ +stateResult_t idAI::State_Legs_MoveThink ( const stateParms_t& parms ) { + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving anymore then go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the run state has changed restart the leg movement + if ( UpdateRunStatus ( ) || (move.idealDirection != move.currentDirection) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 4 ); + return SRESULT_DONE; + } + + // Just wait a frame before doing anything else + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Legs_ChangeDirection +================ +*/ +stateResult_t idAI::State_Legs_ChangeDirection ( const stateParms_t& parms ) { + if ( FacingIdeal ( ) || (move.idealDirection != move.currentDirection) ) { + return SRESULT_DONE; + } + + move.fl.allowAnimMove = false; + move.fl.allowPrevAnimMove = true; + return SRESULT_WAIT; +} + +/* +=============================================================================== + + State helpers + +=============================================================================== +*/ + +/* +================ +idAI::UpdateRunStatus +================ +*/ +bool idAI::UpdateRunStatus ( void ) { + // Get new run status + if ( !move.fl.moving || !CanMove() ) { + move.fl.idealRunning = false; + } else if ( move.walkRange && !ai_useRVMasterMove.GetBool() && move.moveCommand != MOVE_TO_ENEMY && (DistanceTo ( move.moveDest ) - move.range) < move.walkRange ) { + move.fl.idealRunning = false; + } else if ( IsTethered() && tether->IsRunForced ( ) ) { + move.fl.idealRunning = true; + } else if ( IsTethered() && tether->IsWalkForced ( ) ) { + move.fl.idealRunning = false; + } else if ( move.fl.noWalk ) { + move.fl.idealRunning = true; + } else if ( move.fl.noRun ) { + move.fl.idealRunning = false; + } else if ( move.walkRange ) { + if ( move.fl.obstacleInPath ) { + move.fl.idealRunning = false; + } else if ( InLookAtCoverMode() ) { + move.fl.idealRunning = false; + } else if ( !ai_useRVMasterMove.GetBool() && move.walkTurn && !move.fl.allowDirectional && fabs(GetTurnDelta ( )) > move.walkTurn ) { + move.fl.idealRunning = false; + } else { + move.fl.idealRunning = true; + } + } else { + move.fl.idealRunning = true; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +idAI::UpdateTactical + +This method is a recursive method that will determine the best tactical state to be in +from the given available states. +================ +*/ +bool idAI::UpdateTactical ( int delay ) { + // Update tactical cannot be called while performing an action and it must be called from the main state loop + assert ( !aifl.action ); + + // No movement updating on simple think (if the update is forced do it anyway) + if ( combat.tacticalUpdateTime && aifl.simpleThink && !combat.tacticalMaskUpdate ) { + return false; + } + // Don't let tactical updates pre-empt pain actions + if ( pain.takenThisFrame ) { + return false; + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Start ( ); + } + + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Stop ( ); + } + + // We have sighted an enemy so execute the sight state + if ( IsEnemyVisible() && !enemy.fl.sighted ) { + PerformAction ( "Torso_Sight", 4, true ); + return true; + } + + // continue with the last updatetactical if we still have bits left to check in our update mask + if ( !combat.tacticalMaskUpdate ) { + // Ignore the tactical delay if we are taking too much damage here. + // TODO: use skipcurrentdestination instead + if ( !combat.tacticalPainThreshold || combat.tacticalPainTaken < combat.tacticalPainThreshold ) { + // Delay tactical updating? + if ( combat.tacticalUpdateTime && move.moveStatus != MOVE_STATUS_BLOCKED_BY_ENEMY && delay && (gameLocal.time - combat.tacticalUpdateTime) < delay ) { + return false; + } + } + + // handle Auto break tethers + if ( IsTethered ( ) && tether->IsAutoBreak ( ) && IsWithinTether ( ) ) { + tether = NULL; + } + + // Filter the tactical + combat.tacticalMaskUpdate = FilterTactical ( combat.tacticalMaskAvailable ); + } else { + // Make sure all cached tactical updates are still valid + combat.tacticalMaskUpdate &= FilterTactical ( combat.tacticalMaskAvailable ); + if ( !combat.tacticalMaskUpdate ) { + return false; + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Start ( ); + } + + // Recursively look for a better tactical state + bool result = UpdateTactical_r ( ); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Stop ( ); + } + + return result; +} + +bool idAI::UpdateTactical_r ( void ) { + // Mapping of tactical types to combat states + static const char* tacticalState [ ] = { + "State_Combat", // AITACTICAL_NONE + "State_CombatMelee", // AITACTICAL_MELEE + "State_MoveFollow", // AITACTICAL_MOVE_FOLLOW + "State_MoveTether", // AITACTICAL_MOVE_TETHER + "State_MovePlayerPush", // AITACTICAL_MOVE_PLAYERPUSH + "State_CombatCover", // AITACTICAL_COVER + "State_CombatCover", // AITACTICAL_COVER_FLANK + "State_CombatCover", // AITACTICAL_COVER_ADVANCE + "State_CombatCover", // AITACTICAL_COVER_RETREAT + "State_CombatCover", // AITACTICAL_COVER_AMBUSH + "State_CombatRanged", // AITACTICAL_RANGED + "State_CombatTurret", // AITACTICAL_TURRET + "State_CombatHide", // AITACTICAL_HIDE + "State_Passive", // AITACTICAL_PASSIVE + }; + + if ( g_perfTest_aiStationary.GetBool() ) { + return false; + } + // Determine the new tactical state + aiTactical_t newTactical = AITACTICAL_TURRET; + + if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_PLAYERPUSH_BIT ) { + newTactical = AITACTICAL_MOVE_PLAYERPUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_FOLLOW_BIT ) { + newTactical = AITACTICAL_MOVE_FOLLOW; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_ADVANCE_BIT ) { + newTactical = AITACTICAL_COVER_ADVANCE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_FLANK_BIT ) { + newTactical = AITACTICAL_COVER_FLANK; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_RETREAT_BIT ) { + newTactical = AITACTICAL_COVER_RETREAT; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_AMBUSH_BIT ) { + newTactical = AITACTICAL_COVER_AMBUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_BIT ) { + newTactical = AITACTICAL_COVER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_RANGED_BIT ) { + newTactical = AITACTICAL_RANGED; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MELEE_BIT ) { + newTactical = AITACTICAL_MELEE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_TETHER_BIT ) { + newTactical = AITACTICAL_MOVE_TETHER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_HIDE_BIT ) { + newTactical = AITACTICAL_HIDE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_PASSIVE_BIT ) { + newTactical = AITACTICAL_PASSIVE; + } + + // Remove the new tactical from the available in case we have to recursively call ourself + combat.tacticalMaskUpdate &= ~(1<IsType ( idPlayer::GetClassType ( ) ) ) { + //no current pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, just continue it + availableTactical = AITACTICAL_NONE_BIT; + return availableTactical; + } + //stop pushing away + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } else { + //have a pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, only allow it to continue playerpush, but can re-calc, if needbe + availableTactical = AITACTICAL_MOVE_PLAYERPUSH_BIT; + return availableTactical; + } + if ( ai_playerPushAlways.GetBool() ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + case AITACTICAL_MOVE_FOLLOW: + case AITACTICAL_MOVE_TETHER: + case AITACTICAL_MOVE_PLAYERPUSH: + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + case AITACTICAL_RANGED: + case AITACTICAL_HIDE: + //okay to be pushed by players + break; + default: + if ( !leader ) { + //no leader and not in a moving tactical state, don't be pushed by players + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + break; + } + } else if ( !leader || (move.fl.moving&&move.goalEntity!=leader) ) { + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + } + } + + // No tether move if not tethered + if ( !IsTethered ( ) ) { + availableTactical &= ~(AITACTICAL_MOVE_TETHER_BIT); + + // Filter out any tactical states that require a leader + if ( !leader || (enemy.ent && IsEnemyVisible() && enemy.range < move.followRange[1] * 2.0f ) ) { + availableTactical &= ~(AITACTICAL_MOVE_FOLLOW_BIT); + } else if ( leader && !enemy.ent ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + // When tethered disable actions that dont adhear to the tether. + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_HIDE_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); + } + + // If we dont have an enemy we can filter out pure combat states + if ( !enemy.ent ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_MELEE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + + // If we arent aware either then we cant take cover + if ( !combat.fl.aware ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + } else { + // Dont advance or flank our enemy if we have seen them recently + if ( IsEnemyRecentlyVisible ( 0.5f ) ) { + availableTactical &= ~(AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT); + } + + // FIXME: need conditions for these cover states + availableTactical &= ~(AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_COVER_RETREAT_BIT); + } + + // Filter out all cover states if cover is disabled + if ( ai_disableCover.GetBool ( ) ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + + //if we need to fight in melee then fight in melee! + if( IsMeleeNeeded( ) ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + } + + return availableTactical; +} + +/* +================ +idAI::CheckTactical + +Returns 'AICHECKTACTICAL_MOVE' if we should use the given tactical state and execute a movement to do so +Returns 'AICHECKTACTICAL_SKIP' if the given tactical state should be skipped +Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but no movement is required +================ +*/ +aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) { + // Handle non movement tactical states first + if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) { + // If we are moving + if ( move.fl.moving ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + // If we are tethered always check that first + if ( IsTethered ( ) ) { + if ( !move.fl.moving || tactical != combat.tacticalCurrent ) { + // We stopped out of tether range so try a new move to get back in + if ( !tether->ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + return AICTRESULT_OK; + } + } else { + // Move is out of tether range, so look for another one + if ( !tether->ValidateDestination ( this, move.moveDest ) ) { + return AICTRESULT_OK; + } + } + } + + // Anything wrong with our current destination, if so pick a new move + if ( SkipCurrentDestination ( ) ) { + return AICTRESULT_OK; + } + + switch ( tactical ) { + case AITACTICAL_MOVE_FOLLOW: + // If already moving to our leader dont move again + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == leader ) { + return AICTRESULT_NOMOVE; + } + // If not moving and within follow range we can skip the follow state + if ( !move.fl.moving ) { + if ( DistanceTo ( leader ) < move.followRange[1] ) { + //unless the leader is on an elevator we should be standing on... + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + return AICTRESULT_OK; + } + */ + return AICTRESULT_SKIP; + } + } + return AICTRESULT_OK; + + case AITACTICAL_MOVE_TETHER: + // If not moving we dont want to idle in the move state so skip it + if ( !move.fl.moving ) { + return AICTRESULT_SKIP; + } + // We are still heading towards our tether so dont reissue a move + return AICTRESULT_NOMOVE; + + case AITACTICAL_RANGED: + // Currently issuing a find + if ( dynamic_cast(aasFind) ) { + return AICTRESULT_OK; + } + + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + // If the enemy is visible then no movement is required for ranged combat + if ( !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + if ( !move.fl.moving ) { + // If not within the attack range we need to move + if ( enemy.range > combat.attackRange[1] || enemy.range < combat.attackRange[0] ) { + return AICTRESULT_OK; + } + + // If we havent seen our enemy for a while and arent moving, then get moving! + if ( enemy.lastVisibleTime && gameLocal.time - enemy.lastVisibleTime > RANGED_ENEMYDELAY ) { + return AICTRESULT_OK; + } + } else { + // Is our destination out of range? + if ( (move.moveDest - enemy.lastKnownPosition).LengthSqr ( ) > Square ( combat.attackRange[1] ) ) { + return AICTRESULT_OK; + } + } + // Our position is fine so just stay in ranged combat + return AICTRESULT_NOMOVE; + + case AITACTICAL_MELEE: + // IF we are already moving towards our enemy then we dont need a state change + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + return AICTRESULT_NOMOVE; + } + return AICTRESULT_OK; + + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + if ( enemy.ent && !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + // No longer behind cover? + if ( !IsBehindCover ( ) ) { + return AICTRESULT_OK; + } + // Move if there have been too many close calls + if ( combat.tacticalFlinches && combat.tacticalFlinches > gameLocal.random.RandomInt(12) + 3 ) { + return AICTRESULT_OK; + } + // Move if cover destination isnt valid anymore + if ( !IsCoverValid() ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + return AICTRESULT_OK; +} + +/* +================ +idAI::WakeUpTargets +================ +*/ +void idAI::WakeUpTargets ( void ) { + const idKeyValue* kv; + + for ( kv = spawnArgs.MatchPrefix ( "wakeup_target" ); kv; kv = spawnArgs.MatchPrefix ( "wakeup_target", kv ) ) { + idEntity* ent; + ent = gameLocal.FindEntity ( kv->GetValue ( ) ); + if ( !ent ) { + gameLocal.Warning ( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), GetName() ); + } else { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + ent->TriggerGuis ( ); + } + } + + // Find all the tether entities we target + const char* target; + if ( spawnArgs.GetString ( "tether_target", "", &target ) && *target ) { + idEntity* ent; + ent = gameLocal.FindEntity ( target ); + if ( ent && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + ProcessEvent ( &EV_Activate, ent ); + } + } +} + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idAI::State_Wait_Activated + +Stop the state thread until the ai is activated +================ +*/ +stateResult_t idAI::State_Wait_Activated ( const stateParms_t& parms ) { + if ( (aifl.activated || aifl.pain ) && CanBecomeSolid ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_Action + +Stop the state thread as long as a torso action is running and allow a pain action to interrupt +================ +*/ +stateResult_t idAI::State_Wait_Action ( const stateParms_t& parms ) { + if ( CheckPainActions ( ) ) { + // Our current action is being interrupted by pain so make sure the action can be performed + // immediately after coming out of the pain. + actionTime = actionSkipTime; + return SRESULT_DONE; + } + + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ActionNoPain + +Stop the state thread as long as a torso action is running +================ +*/ +stateResult_t idAI::State_Wait_ActionNoPain ( const stateParms_t& parms ) { + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ScriptedDone + +Stop the state thread as a scripted sequence is active +================ +*/ +stateResult_t idAI::State_Wait_ScriptedDone ( const stateParms_t& parms ) { + if ( aifl.scripted ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} diff --git a/source/mpgame/ai/AI_Tactical.cpp b/source/mpgame/ai/AI_Tactical.cpp new file mode 100644 index 0000000..480cf65 --- /dev/null +++ b/source/mpgame/ai/AI_Tactical.cpp @@ -0,0 +1,1054 @@ +// +// TODO: +// - unarmed posture +// - relaxed posture +// - turning in place too much, rely more on look angles? + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Tactical.h" + +const idEventDef AI_ForcePosture ( "forcePosture", "d" ); + +CLASS_DECLARATION( idAI, rvAITactical ) + EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture ) + EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn ) +END_CLASS + +static const char* aiPostureString[AIPOSTURE_MAX] = { + "stand", // AIPOSTURE_STAND, + "crouch", // AIPOSTURE_CROUCH, + "cover_left", // AIPOSTURE_STAND_COVER_LEFT, + "cover_right", // AIPOSTURE_STAND_COVER_RIGHT, + "crouch_cover", // AIPOSTURE_CROUCH_COVER + "crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT, + "crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT, + "relaxed", // AIPOSTURE_RELAXED + "unarmed", // AIPOSTURE_UNARMED + "at_attention", // AIPOSTURE_AT_ATTENTION +}; + +/* +================ +rvAITactical::rvAITactical +================ +*/ +rvAITactical::rvAITactical ( void ) { + shots = 0; + nextWallTraceTime = 0; +} + +void rvAITactical::InitSpawnArgsVariables ( void ) +{ + // Initialize the posture info + InitPostureInfo ( ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) ); + + healthRegen = spawnArgs.GetInt( "healthRegen", "2" ); + healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" ); +} + +/* +================ +rvAITactical::Spawn +================ +*/ +void rvAITactical::Spawn ( void ) { + InitSpawnArgsVariables(); + + // Force a posture? + const char* temp; + postureForce = AIPOSTURE_DEFAULT; + if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) { + for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) { + if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) { + break; + } + } + if ( postureForce >= AIPOSTURE_MAX ) { + postureForce = AIPOSTURE_DEFAULT; + } + } + + UpdatePosture ( ); + postureCurrent = postureIdeal; + + OnPostureChange ( ); + + ammo = spawnArgs.GetInt ( "ammo", "-1" ); + + // Initialize custom actions + actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK ); + actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK ); + + actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" ); + +// playerFocusTime = 0; +// playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" )); + + healthRegenNextTime = 0; + maxHealth = health; +} + +/* +================ +rvAITactical::Think +================ +*/ +void rvAITactical::Think ( void ) { + idAI::Think ( ); + + // If not simple thinking and not in an action, update the posture + if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) { + if ( UpdatePosture ( ) ) { + PerformAction ( "Torso_SetPosture", 4, true ); + } + } + +// FIXME: disabled for now, its annoying people + /* + idPlayer* localPlayer; + localPlayer = gameLocal.GetLocalPlayer(); + + // If the player has been standing in front of the marine and looking at him for too long he should say something + if ( !aifl.dead && playerFocusTime && playerAnnoyTime + && !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking() + && !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now + && DistanceTo( localPlayer ) < 64.0f ) { + idVec3 diff; + + + diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin(); + diff.NormalizeFast(); + + // Is the player looking at the marine? + if ( diff * localPlayer->viewAxis[0] > 0.7f ) { + // Say something every 5 seconds + if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) { + // Debounce it against other marines + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) { + Speak ( "lipsync_canihelpyou", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 ); + } + } + } else { + playerFocusTime = gameLocal.time; + } + } else { + playerFocusTime = gameLocal.time; + } + */ + + if ( health > 0 ) { + //alive + if ( healthRegenEnabled && healthRegen ) { + if ( gameLocal.GetTime() >= healthRegenNextTime ) { + health = idMath::ClampInt( 0, maxHealth, health+healthRegen ); + healthRegenNextTime = gameLocal.GetTime() + 1000; + } + } + } + + //crappy place to do this, just testing + bool clearPrefix = true; + bool facingWall = false; + if ( move.fl.moving && InCoverMode() && combat.fl.aware ) { + clearPrefix = false; + if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + facingWall = true; + } else if ( nextWallTraceTime < gameLocal.GetTime() ) { + //do an occasional check for solid architecture directly in front of us + nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750; + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + if ( neckJoint != INVALID_JOINT ) { + GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis ); + end = start + axis[0] * 32.0f; + } else { + start = GetEyePosition(); + start += viewAxis[0] * 8.0f;//still inside bbox + end = start + viewAxis[0] * 32.0f; + } + //trace against solid arcitecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this ); + if ( wallTrace.fraction < 1.0f ) { + facingWall = true; + } else { + clearPrefix = true; + } + } + } + + if ( facingWall ) { + if ( !animPrefix.Length() ) { + animPrefix = "nearcover"; + } + } else if ( clearPrefix && animPrefix == "nearcover" ) { + animPrefix = ""; + } +} + +/* +================ +rvAITactical::Save +================ +*/ +void rvAITactical::Save( idSaveGame *savefile ) const { + savefile->WriteSyncId(); + + savefile->WriteInt ( ammo ); + savefile->WriteInt ( shots ); + +// savefile->WriteInt ( playerFocusTime ); +// savefile->WriteInt ( playerAnnoyTime ); + + savefile->WriteInt ( (int&)postureIdeal ); + savefile->WriteInt ( (int&)postureCurrent ); + savefile->WriteInt ( (int&)postureForce ); + // TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson: + savefile->WriteInt ( healthRegenNextTime ); + savefile->WriteInt ( maxHealth ); + savefile->WriteInt ( nextWallTraceTime ); + + + actionElbowAttack.Save ( savefile ); + actionKillswitchAttack.Save ( savefile ); + + actionTimerPeek.Save ( savefile ); +} + +/* +================ +rvAITactical::Restore +================ +*/ +void rvAITactical::Restore( idRestoreGame *savefile ) { + InitSpawnArgsVariables ( ); + savefile->ReadSyncId( "rvAITactical" ); + + savefile->ReadInt ( ammo ); + savefile->ReadInt ( shots ); + +// savefile->ReadInt ( playerFocusTime ); +// savefile->ReadInt ( playerAnnoyTime ); + + savefile->ReadInt ( (int&)postureIdeal ); + savefile->ReadInt ( (int&)postureCurrent ); + savefile->ReadInt ( (int&)postureForce ); + // TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + savefile->ReadInt ( healthRegenNextTime ); + savefile->ReadInt ( maxHealth ); + savefile->ReadInt ( nextWallTraceTime ); + + actionElbowAttack.Restore ( savefile ); + actionKillswitchAttack.Restore ( savefile ); + + actionTimerPeek.Restore ( savefile ); + + UpdateAnimPrefix ( ); +} + +/* +================ +rvAITactical::CanTurn +================ +*/ +bool rvAITactical::CanTurn ( void ) const { + if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvAITactical::CanMove +================ +*/ +bool rvAITactical::CanMove ( void ) const { + if ( !postureInfo[postureCurrent].fl.canMove ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvAITactical::CheckAction_Reload +================ +*/ +bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( ammo == 0 ) { + return true; + } + return false; +} + +/* +================ +rvAITactical::CheckActions +================ +*/ +bool rvAITactical::CheckActions ( void ) { + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // If we are pressed, fight-- do not break melee combat until you or the enemy is dead. + if ( IsMeleeNeeded ( )) { + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + //take no actions other than fighting + return false; + } + + // Handle any posture changes + if ( postureIdeal != postureCurrent ) { + PerformAction ( "Torso_SetPosture", 4, true ); + return true; + } + + // Reload takes precedence + if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) { + PerformAction ( "Torso_Reload", 4, false ); + return true; + } + + if ( IsBehindCover ( ) ) { + // If we have no enemy try peeking + if ( !IsEnemyRecentlyVisible ( ) ) { + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) { + if ( actionTimerPeek.IsDone ( actionTime ) ) { + actionTimerPeek.Reset ( actionTime, 0.5f ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 ); + PerformAction ( "Torso_Cover_Peek", 4, false ); + return true; + } + } + } + + // Attacks from cover + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + // Kill switch attack from cover? + if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) { + if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) { + return true; + } + } + + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; + } + + // Standard attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + + // Ranged attack only if there is ammo + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +rvAITactical::CheckRelaxed + +Returns true if the marine should currently be in a relaxed state +================ +*/ +bool rvAITactical::CheckRelaxed ( void ) const { +// if ( forceRelaxed ) { +// return true; +// } + +/* + // If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed + if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) { + return true; + } +*/ + + // Alwasy relaxed when ignoring enemies + if ( !combat.fl.aware ) { + return true; + } + + if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) { + return false; + } + + if ( gameLocal.time >= focusTime ) { + return false; + } + + return true; +} + +/* +================ +rvAITactical::GetIdleAnimName +================ +*/ +const char* rvAITactical::GetIdleAnimName ( void ) { + return "idle"; +} + +/* +================ +rvAITactical::UpdateAnimPrefix +================ +*/ +void rvAITactical::UpdateAnimPrefix ( void ) { + if ( postureCurrent == AIPOSTURE_STAND ) { + animPrefix = ""; + } else { + animPrefix = aiPostureString[postureCurrent]; + } +} + +/* +================ +rvAITactical::InitPostureInfo +================ +*/ +void rvAITactical::InitPostureInfo ( void ) { + int posture; + for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) { + aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture]; + + postureCurrent = (aiPosture_t)posture; + UpdateAnimPrefix ( ); + + info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true ); + info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true ); + info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true ); + info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true ); + info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true ); + info.fl.canTurn = false; + } + + // FIXME: this should be based on the availablity of turn anims + postureInfo[AIPOSTURE_STAND].fl.canTurn = true; + postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true; + postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true; +} + +/* +================ +rvAITactical::UpdatePosture +================ +*/ +bool rvAITactical::UpdatePosture ( void ) { + // If the posture is being forced then use that until its no longer forced + if ( postureForce != AIPOSTURE_DEFAULT ) { + postureIdeal = postureForce; + // Not forcing posture, determine it from our current state + } else { + postureIdeal = AIPOSTURE_STAND; + + // Behind cover? + if ( IsBehindCover ( ) ) { + bool left; + if ( enemy.ent ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f; + } else if ( tether ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f; + } else { + left = false; + } + // Should be crouching behind cover? + if ( InCrouchCoverMode ( ) ) { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT; + } else { + postureIdeal = AIPOSTURE_CROUCH_COVER; + } + } else { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } + } + } else if ( combat.fl.aware //aggressive + && (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction + && ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here + //we crouch only if leader is + postureIdeal = AIPOSTURE_CROUCH; + } else if ( CheckRelaxed ( ) ) { + postureIdeal = AIPOSTURE_RELAXED; + } + + //never crouch in melee! + if( IsMeleeNeeded() ) { + postureIdeal = AIPOSTURE_STAND; + } + } + + // Default the posture if trying to move with one that doesnt support it + if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) { + postureIdeal = AIPOSTURE_STAND; + // Default the posture if trying to turn and we cant in the posture we chose + } else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) { + postureIdeal = AIPOSTURE_STAND; + } + + return (postureIdeal != postureCurrent); +} + +/* +================ +rvAITactical::OnPostureChange +================ +*/ +void rvAITactical::OnPostureChange ( void ) { + UpdateAnimPrefix ( ); +} + +/* +============ +rvAITactical::OnSetKey +============ +*/ +void rvAITactical::OnSetKey ( const char* key, const char* value ) { + idAI::OnSetKey ( key, value ); + +/* + if ( !idStr::Icmp ( key, "annoyed" ) ) { + playerAnnoyTime = SEC2MS( atof ( value ) ); + } +*/ +} + +/* +================ +rvAITactical::OnStopMoving +================ +*/ +void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + // Ensure the peek doesnt happen immedately every time we stop at a cover + if ( IsBehindCover ( ) ){ + actionTimerPeek.Clear ( actionTime ); + actionTimerPeek.Add ( 2000, 0.5f ); + + actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity ); + + // We should be looking fairly close to the right direction, so just snap it + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + move.current_yaw = move.ideal_yaw; + } + + idAI::OnStopMoving ( oldMoveCommand ); +} + +/* +================ +rvAITactical::CalculateShots +================ +*/ +void rvAITactical::CalculateShots ( const char* fireAnim ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + if ( shots > ammo ) { + shots = ammo; + } + + // Update the firing animation playback rate + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } +} + +/* +================ +rvAITactical::UseAmmo +================ +*/ +void rvAITactical::UseAmmo ( int amount ) { + if ( ammo <= 0 ) { + return; + } + + shots--; + ammo-=amount; + if ( ammo < 0 ) { + ammo = 0; + shots = 0; + } +} + +/* +================ +rvAITactical::GetDebugInfo +================ +*/ +void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData ); + proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData ); + proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData ); + proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData ); + proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData ); + proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData ); + + proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData ); + + + proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAITactical ) + STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack ) + + STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack ) + STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack ) + STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack ) + STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek ) + + STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload ) + + STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture ) + + STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek ) +END_CLASS_STATES + +/* +================ +rvAITactical::State_Torso_SetPosture +================ +*/ +stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_RELAXED, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: { + idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] ); + if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + + if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT + || postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT + || (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) ) + { + // FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION + move.fl.allowAnimMove = false; + } else { + //no need to play cover-to-stand/relaxed transition if: + //scripted... + //or we're moving already... + //or turning away from our old cover direction... + if ( aifl.scripted + /*|| (move.fl.moving&&!move.fl.blocked) + || (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames ); + if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT + && postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT + && postureIdeal == AIPOSTURE_RELAXED ) { + //we need to also play stand_to_relaxed at the end... + return SRESULT_STAGE ( STAGE_WAIT_RELAXED ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT_RELAXED: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_RangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + CalculateShots ( "range_attack" ); + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 ); + UseAmmo ( 1 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov ) { + return SRESULT_STAGE ( STAGE_END ); + } else if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots ( "range_attack_torso" ); + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 || !enemy.fl.inFov ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Reload +================ +*/ +stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + ammo = spawnArgs.GetInt ( "ammo" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanLeftAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanRightAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) { + enum { + STAGE_OUT, + STAGE_OUTWAIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_IN, + STAGE_INWAIT, + }; + switch ( parms.stage ) { + case STAGE_OUT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // The lean out animation cannot blend with any other animations since + // it is essential that the movement delta out match the one back in. Therefore + // we force the legs and torso to be stoped before playing any animations + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 ); + return SRESULT_STAGE ( STAGE_OUTWAIT ); + + case STAGE_OUTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Random number of shots + CalculateShots ( "lean_attack" ); + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 ); + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots > 0 ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_STAGE ( STAGE_IN ); + } + return SRESULT_WAIT; + + case STAGE_IN: + PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 ); + return SRESULT_STAGE ( STAGE_INWAIT ); + + case STAGE_INWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_Peek +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Frame_Peek +================ +*/ +stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) { + CheckForEnemy ( true, true ); + return SRESULT_OK; +} + +/* +================ +rvAITactical::Event_ForcePosture +================ +*/ +void rvAITactical::Event_ForcePosture ( int posture ) { + postureForce = (aiPosture_t)posture; +} + +/* +=================== +rvAITactical::IsCrouching +=================== +*/ +bool rvAITactical::IsCrouching( void ) const { + if ( postureCurrent == AIPOSTURE_CROUCH + || postureCurrent == AIPOSTURE_CROUCH_COVER + || postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT + || postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) { + return true; + } + return idAI::IsCrouching(); +} + +/* +================ +rvAITactical::Event_PostSpawn +================ +*/ +void rvAITactical::Event_PostSpawn( void ) { + idAI::Event_PostSpawn(); + if ( team == AITEAM_MARINE && healthRegenEnabled ) + {//regen-enabled buddy marine + if ( CheckDeathCausesMissionFailure() ) + {//who is important to a mission + if ( g_skill.GetInteger() > 2 ) + {//on impossible + health *= 1.5f; + healthRegen *= 1.5f; + } + else if ( g_skill.GetInteger() > 1 ) + {//on hard + health *= 1.2f; + healthRegen *= 1.25f; + } + } + } +} \ No newline at end of file diff --git a/source/mpgame/ai/AI_Tactical.h b/source/mpgame/ai/AI_Tactical.h new file mode 100644 index 0000000..e9faf9d --- /dev/null +++ b/source/mpgame/ai/AI_Tactical.h @@ -0,0 +1,134 @@ +/* +================ + +AI_Tactical.h + +================ +*/ + +#ifndef __AI_TACTICAL__ +#define __AI_TACTICAL__ + +typedef enum { + AIPOSTURE_DEFAULT = -1, + AIPOSTURE_STAND, + AIPOSTURE_CROUCH, + AIPOSTURE_STAND_COVER_LEFT, + AIPOSTURE_STAND_COVER_RIGHT, + AIPOSTURE_CROUCH_COVER, + AIPOSTURE_CROUCH_COVER_LEFT, + AIPOSTURE_CROUCH_COVER_RIGHT, + AIPOSTURE_RELAXED, + AIPOSTURE_UNARMED, + AIPOSTURE_AT_ATTENTION, + AIPOSTURE_MAX +} aiPosture_t; + +typedef struct { + struct { + bool canMove; + bool canShoot; + bool canPeek; + bool canReload; + bool canTurn; + bool canKillswitch; + } fl; + +} aiPostureInfo_t; + +class rvAITactical : public idAI { +public: + + CLASS_PROTOTYPE( rvAITactical ); + + rvAITactical ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool CheckActions ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + + virtual bool IsCrouching ( void ) const; + +protected: + + virtual const char* GetIdleAnimName ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnPostureChange ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + bool CheckRelaxed ( void ) const; + + void InitPostureInfo ( void ); + bool UpdatePosture ( void ); + void CalculateShots ( const char* fireAnim ); + void UseAmmo ( int amount ); + void SetPosture ( aiPosture_t newPosture ); + void UpdateAnimPrefix ( void ); + + int ammo; + int maxShots; + int minShots; + int shots; + float fireRate; + + int playerFocusTime; + int playerAnnoyTime; + + aiPosture_t postureIdeal; + aiPosture_t postureCurrent; + aiPosture_t postureForce; + aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + + rvAIAction actionElbowAttack; + rvAIAction actionKillswitchAttack; + + rvAIActionTimer actionTimerPeek; + +private: + + int healthRegen; + bool healthRegenEnabled; + int healthRegenNextTime; + int maxHealth; + + int nextWallTraceTime; + + // Custom actions + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + bool CheckAction_Relax ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_SetPosture ( const stateParms_t& parms ); + + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + + stateResult_t State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_Peek ( const stateParms_t& parms ); + + // Frame Commands + stateResult_t State_Frame_Peek ( const stateParms_t& parms ); + + // Events + void Event_ForcePosture ( int posture ); + + virtual void Event_PostSpawn ( void ); + + CLASS_STATES_PROTOTYPE ( rvAITactical ); +}; + +extern const idEventDef AI_ForcePosture; + +#endif /* !__AI_TACTICAL__ */ diff --git a/source/mpgame/ai/AI_Util.cpp b/source/mpgame/ai/AI_Util.cpp new file mode 100644 index 0000000..fd31b1f --- /dev/null +++ b/source/mpgame/ai/AI_Util.cpp @@ -0,0 +1,977 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIHelper ) + EVENT( EV_Activate, rvAIHelper::Event_Activate ) +END_CLASS + +/* +================ +rvAIHelper::rvAIHelper +================ +*/ +rvAIHelper::rvAIHelper ( void ) { + helperNode.SetOwner( this ); +} + +/* +================ +rvAIHelper::Spawn +================ +*/ +void rvAIHelper::Spawn ( void ) { + // Auto activate? + if ( spawnArgs.GetBool ( "start_on" ) ) { + PostEventMS ( &EV_Activate, 0, this ); + } +} + +/* +================ +rvAIHelper::IsCombat +================ +*/ +bool rvAIHelper::IsCombat ( void ) const { + return false; +} + + +/* +================ +rvAIHelper::OnActivate +================ +*/ +void rvAIHelper::OnActivate ( bool active ) { + if ( active ) { + ActivateTargets ( this ); + } +} + +/* +================ +rvAIHelper::Event_Activate +================ +*/ +void rvAIHelper::Event_Activate( idEntity *activator ) { + if ( !helperNode.InList ( ) ) { + aiManager.RegisterHelper ( this ); + OnActivate ( true ); + } else { + aiManager.UnregisterHelper ( this ); + OnActivate ( false ); + } +} + +/* +================ +rvAIHelper::GetDirection +================ +*/ +idVec3 rvAIHelper::GetDirection ( const idAI* ai ) const { + if ( ai->team == 0 ) { + return GetPhysics()->GetAxis()[0]; + } + return -GetPhysics()->GetAxis()[0]; +} + +/* +================ +rvAIHelper::ValidateDestination +================ +*/ +bool rvAIHelper::ValidateDestination ( const idAI* ent, const idVec3& dest ) const { + return true; +} + +/* +=============================================================================== + +rvAICombatHelper + +=============================================================================== +*/ + +class rvAICombatHelper : public rvAIHelper { +public: + CLASS_PROTOTYPE( rvAICombatHelper ); + + rvAICombatHelper ( void ); + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + +protected: + + virtual void OnActivate ( bool active ); + + idEntityPtr location; +}; + +CLASS_DECLARATION( rvAIHelper, rvAICombatHelper ) +END_CLASS + +/* +================ +rvAICombatHelper::rvAICombatHelper +================ +*/ +rvAICombatHelper::rvAICombatHelper ( void ) { +} + +/* +================ +rvAICombatHelper::Spawn +================ +*/ +void rvAICombatHelper::Spawn ( void ) { +} + +/* +================ +rvAICombatHelper::OnActivate +================ +*/ +void rvAICombatHelper::OnActivate ( bool active ) { + rvAIHelper::OnActivate ( active ); + + if ( active ) { + if ( spawnArgs.GetBool ( "tetherLocation", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() ); + } else { + location = NULL; + } + } +} + +/* +================ +rvAICombatHelper::IsCombat +================ +*/ +bool rvAICombatHelper::IsCombat ( void ) const { + return true; +} + +/* +================ +rvAICombatHelper::ValidateDestination +================ +*/ +bool rvAICombatHelper::ValidateDestination ( const idAI* ai, const idVec3& dest ) const { + // If tethering to a location then see if the location of the given points matches our tethered location + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + + // Is the destination on the wrong side of the helper? + idVec3 origin; + idVec3 dir; + + dir = GetDirection(ai); + if ( ai->enemy.ent ) { + origin = ai->enemy.lastKnownPosition; + origin -= (dir * ai->combat.attackRange[0]); + } else { + origin = GetPhysics()->GetOrigin(); + origin -= (dir * 32.0f); + } + + if ( dir * (origin-dest) < 0.0f ) { + return false; + } + + // Would this destination link us to the wrong helper? + if ( static_cast< const rvAIHelper * >( aiManager.FindClosestHelper( dest ) ) != this ) { + return false; + } + + return true; +} + +/* +=============================================================================== + + rvAIAvoid + +=============================================================================== +*/ + +class rvAIAvoid : public idEntity { +public: + CLASS_PROTOTYPE( rvAIAvoid ); + + rvAIAvoid ( void ); + + void Spawn ( void ); +}; + +CLASS_DECLARATION( idEntity, rvAIAvoid ) +END_CLASS + +/* +================ +rvAIAvoid::rvAIAvoid +================ +*/ +rvAIAvoid::rvAIAvoid ( void ) { +} + +/* +================ +rvAIAvoid::Spawn +================ +*/ +void rvAIAvoid::Spawn ( void ) { + int team = -1; + if ( !spawnArgs.GetInt ( "teamFilter", "-1", team ) ) { + //hmm, no "teamFilter" set, check "team" since many were set up like this + team = spawnArgs.GetInt ( "team", "-1" ); + } + aiManager.AddAvoid ( GetPhysics()->GetOrigin(), spawnArgs.GetFloat ( "radius", "64" ), team ); + PostEventMS ( &EV_Remove, 0 ); +} + +/* +=============================================================================== + + rvAITrigger + +=============================================================================== +*/ + +const idEventDef AI_AppendFromSpawner ( "", "ee" ); + +CLASS_DECLARATION( idEntity, rvAITrigger ) + EVENT( EV_Activate, rvAITrigger::Event_Activate ) + EVENT( AI_AppendFromSpawner, rvAITrigger::Event_AppendFromSpawner ) +END_CLASS + +/* +================ +rvAITrigger::rvAITrigger +================ +*/ +rvAITrigger::rvAITrigger( void ) { +} + +/* +================ +rvAITrigger::Spawn +================ +*/ +void rvAITrigger::Spawn ( void ) { + nextTriggerTime = 0; + wait = SEC2MS ( spawnArgs.GetFloat ( "wait", "-1" ) ); + + conditionDead = spawnArgs.GetBool ( "condition_dead", "0" ); + conditionTether = spawnArgs.GetBool ( "condition_tether", "0" ); + conditionStop = spawnArgs.GetBool ( "condition_stop", "0" ); + + percent = spawnArgs.GetFloat ( "percent", "1" ); + + // Start on by default? + nextTriggerTime = spawnArgs.GetBool ( "start_on", "0" ) ? 0 : -1; + if ( nextTriggerTime == 0 ) { + BecomeActive ( TH_THINK ); + } else { + BecomeInactive ( TH_THINK ); + } + + // If there are no conditions we are done + if ( !conditionDead && !conditionTether && !conditionStop ) { + gameLocal.Warning ( "No conditions specified on ai trigger entity '%s'", GetName ( ) ); + PostEventMS ( &EV_Remove, 0 ); + } +} + +/* +================ +rvAITrigger::Save +================ +*/ +void rvAITrigger::Save ( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( testAI.Num ( ) ); + for ( i = 0; i < testAI.Num(); i ++ ) { + testAI[i].Save ( savefile ); + } + + savefile->WriteInt ( testSpawner.Num ( ) ); + for ( i = 0; i < testSpawner.Num(); i ++ ) { + testSpawner[i].Save ( savefile ); + } + + savefile->WriteBool ( conditionDead ); + savefile->WriteBool ( conditionTether ); + savefile->WriteBool ( conditionStop ); + + savefile->WriteInt ( wait ); + savefile->WriteInt ( nextTriggerTime ); + + savefile->WriteFloat ( percent ); +} + +/* +================ +rvAITrigger::Restore +================ +*/ +void rvAITrigger::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + testAI.Clear ( ); + testAI.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testAI[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + testSpawner.Clear ( ); + testSpawner.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testSpawner[i].Restore ( savefile ); + } + + savefile->ReadBool ( conditionDead ); + savefile->ReadBool ( conditionTether ); + savefile->ReadBool ( conditionStop ); + + savefile->ReadInt ( wait ); + savefile->ReadInt ( nextTriggerTime ); + + savefile->ReadFloat ( percent ); +} + +/* +================ +rvAITrigger::Think +================ +*/ +void rvAITrigger::Think ( void ) { + int v; + + // Only trigger so often + if ( nextTriggerTime == -1 || gameLocal.time < nextTriggerTime ) { + return; + } + + // If we have any attached spawners then our condition cannot be met + if ( testSpawner.Num() ) { + for ( v = 0; v < testSpawner.Num(); v ++ ) { + rvSpawner* spawner = testSpawner[v]; + if ( !spawner ) { + testSpawner.RemoveIndex ( v ); + v--; + continue; + } + return; + } + } + + // If we have no AI we are tracking then wait until we do + if ( !testAI.Num ( ) ) { + return; + } + + int count = 0; + for ( v = 0; v < testAI.Num(); v ++ ) { + idAI* ai = testAI[v]; + if ( !ai ) { + testAI.RemoveIndex ( v ); + v--; + continue; + } + if ( !ai->aifl.dead ) { + if ( conditionDead ) { + continue; + } + if ( conditionTether && !ai->IsWithinTether ( ) ) { + continue; + } + if ( conditionStop && ai->move.fl.moving ) { + continue; + } + } + count++; + } + + // If result is true then we should fire our trigger now + if ( count >= (int) ((float)testAI.Num()*percent) ) { + ActivateTargets ( this ); + + // If only triggering once just remove ourselves now + if ( wait < 0 ) { + nextTriggerTime = -1; + } else { + nextTriggerTime = gameLocal.time + wait; + } + } +} + +/* +================ +rvAITrigger::FindTargets +================ +*/ +void rvAITrigger::FindTargets ( void ) { + int t; + + idEntity::FindTargets ( ); + + for ( t = 0; t < targets.Num(); t ++ ) { + idEntity* ent = targets[t]; + if ( !ent ) { + continue; + } + if ( ent->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + if ( ent->IsType ( rvSpawner::GetClassType ( ) ) ) { + static_cast(ent)->AddCallback ( this, &AI_AppendFromSpawner ); + testSpawner.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + } +} + +/* +================ +rvAITrigger::Event_Activate +================ +*/ +void rvAITrigger::Event_Activate( idEntity *activator ) { + + // Add spawners and ai to the list when they come in + if ( activator && activator->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(activator)) ); + return; + } + + if ( nextTriggerTime == -1 ) { + nextTriggerTime = 0; + BecomeActive ( TH_THINK ); + } else { + nextTriggerTime = -1; + BecomeInactive ( TH_THINK ); + } +} + +/* +================ +rvAITrigger::Event_AppendFromSpawner +================ +*/ +void rvAITrigger::Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ) { + // If its an ai entity being spawned then add it to our test list + if ( spawned && spawned->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(spawned)) ); + } +} + +/* +=============================================================================== + + rvAITether + +=============================================================================== +*/ +const idEventDef EV_TetherSetupLocation ( "tetherSetupLocation" ); +const idEventDef EV_TetherGetLocation ( "tetherGetLocation" ); +CLASS_DECLARATION( idEntity, rvAITether ) + EVENT( EV_Activate, rvAITether::Event_Activate ) + EVENT( EV_TetherGetLocation, rvAITether::Event_TetherGetLocation ) + EVENT( EV_TetherSetupLocation, rvAITether::Event_TetherSetupLocation ) +END_CLASS + +/* +================ +rvAITether::rvAITether +================ +*/ +rvAITether::rvAITether ( void ) { +} + +/* +================ +rvAITether::InitNonPersistentSpawnArgs +================ +*/ +void rvAITether::InitNonPersistentSpawnArgs ( void ) { + tfl.canBreak = spawnArgs.GetBool ( "allowBreak", "1" ); + tfl.autoBreak = spawnArgs.GetBool ( "autoBreak", "0" ); + tfl.forceRun = spawnArgs.GetBool ( "forceRun", "0" ); + tfl.forceWalk = spawnArgs.GetBool ( "forceWalk", "0" ); + tfl.becomeAggressive = spawnArgs.GetBool ( "becomeAggressive", "0" ); + tfl.becomePassive = spawnArgs.GetBool ( "becomePassive", "0" ); + + // Check for both being set + if ( tfl.forceRun && tfl.forceWalk ) { + gameLocal.Warning ( "both forceRun and forceWalk were specified for tether '%s', forceRun will take precedence" ); + tfl.forceWalk = false; + } + if ( tfl.becomeAggressive && tfl.becomePassive ) { + gameLocal.Warning ( "both becomePassive and becomeAggressive were specified for tether '%s', becomeAggressive will take precedence" ); + tfl.becomePassive = false; + } +} + +/* +================ +rvAITether::Spawn +================ +*/ +void rvAITether::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); + + PostEventMS( &EV_TetherSetupLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherSetupLocation +================ +*/ +void rvAITether::Event_TetherSetupLocation( void ) { + //NOTE: we now do this right after spawn so we don't stomp other tether's locations + // if we activate after them and are in the same room as them. + // Dynamically-spawned locations are very, very bad! + + // All pre-existing locations should be placed and spread by now + // Get the location entity we are attached to + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + if ( !location ) { + location = gameLocal.AddLocation ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f, "tether_location" ); + } + } else { + location = NULL; + } + PostEventMS( &EV_TetherGetLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherGetLocation +================ +*/ +void rvAITether::Event_TetherGetLocation( void ) { + //NOW: all locations should be made & spread, get our location (may not be the one + // we added, it could be be the same as another tether if it's in the same room as us) + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } else { + location = NULL; + } +} + +/* +================ +rvAITether::Save +================ +*/ +void rvAITether::Save ( idSaveGame *savefile ) const { + location.Save( savefile ); +} + +/* +================ +rvAITether::Restore +================ +*/ +void rvAITether::Restore ( idRestoreGame *savefile ) { + location.Restore( savefile ); + + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITether::ValidateAAS +================ +*/ +bool rvAITether::ValidateAAS ( idAI* ai ) { + if ( !ai->aas ) { + return false; + } + return true; +} + +/* +================ +rvAITether::ValidateDestination +================ +*/ +bool rvAITether::ValidateDestination ( idAI* ai, const idVec3& dest ) { + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + return true; +} + +/* +================ +rvAITether::ValidateBounds +================ +*/ +bool rvAITether::ValidateBounds ( const idBounds& bounds ) { + return true; +} + +/* +================ +rvAITether::FindGoal +================ +*/ +bool rvAITether::FindGoal ( idAI* ai, aasGoal_t& goal ) { + rvAASFindGoalForTether findGoal ( ai, this ); + if ( !ai->aas->FindNearestGoal( goal, + ai->PointReachableAreaNum( ai->GetPhysics()->GetOrigin() ), + ai->GetPhysics()->GetOrigin(), + GetPhysics()->GetOrigin(), + ai->move.travelFlags, + 0.0f, 0.0f, + NULL, 0, findGoal ) ) { + return false; + } + return true; +} + + +/* +================ +rvAITether::Event_Activate +================ +*/ +void rvAITether::Event_Activate( idEntity *activator ) { + int i; + + //WELL! Turns out designers are binding tethers to movers, so we have to get our location *again* on activation... + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } + + if ( activator && activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + + // Aggressive/Passive stance change? + if ( tfl.becomeAggressive ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } else if ( tfl.becomePassive ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, false ); + } + } + } +} + +/* +================ +rvAITether::DebugDraw +================ +*/ +void rvAITether::DebugDraw ( void ) { + const idBounds& bounds = GetPhysics()->GetAbsBounds(); + gameRenderWorld->DebugBounds ( colorYellow, bounds.Expand ( 8.0f ) ); + gameRenderWorld->DebugArrow ( colorWhite, bounds.GetCenter(), bounds.GetCenter() + GetPhysics()->GetAxis()[0] * 16.0f, 8.0f ); + gameRenderWorld->DrawText( name.c_str(), bounds.GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), bounds.GetCenter() - GetPhysics()->GetGravityNormal() * 5.0f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +} + +/* +=============================================================================== + + rvAITetherBehind + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherBehind ) +END_CLASS + +/* +================ +rvAITetherBehind::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherBehind::InitNonPersistentSpawnArgs ( void ) { + range = spawnArgs.GetFloat ( "range" ); +} + +/* +================ +rvAITetherBehind::Spawn +================ +*/ +void rvAITetherBehind::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::Restore +================ +*/ +void rvAITetherBehind::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::ValidateDestination +================ +*/ +bool rvAITetherBehind::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + + // Make sure we include the move range in the tether + idVec3 origin; + if ( range ) { + origin = GetPhysics()->GetOrigin ( ) + GetPhysics()->GetAxis()[0] * GetOriginReachedRange() - GetPhysics()->GetAxis()[0] * range; + if ( GetPhysics()->GetAxis()[0] * (origin-dest) > 0.0f ) { + return false; + } + } + + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis()[0] * GetOriginReachedRange(); + + // Are we on wrong side of tether? + return ( GetPhysics()->GetAxis()[0] * (origin-dest) ) >= 0.0f; +} + +/* +================ +rvAITetherBehind::ValidateBounds +================ +*/ +bool rvAITetherBehind::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + + idPlane plane; + int side; + plane.SetNormal ( GetPhysics()->GetAxis ( )[0] ); + plane.FitThroughPoint ( GetPhysics()->GetOrigin ( ) ); + side = bounds.PlaneSide ( plane ); + return ( side == PLANESIDE_CROSS || side == PLANESIDE_BACK ); +} + +/* +================ +rvAITetherBehind::DebugDraw +================ +*/ +void rvAITetherBehind::DebugDraw ( void ) { + idVec3 dir; + + rvAITether::DebugDraw ( ); + + dir = GetPhysics()->GetGravityNormal ( ).Cross ( GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorPink, + GetPhysics()->GetOrigin() - dir * 1024.0f, + GetPhysics()->GetOrigin() + dir * 1024.0f ); + + if ( range ) { + idVec3 origin; + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis ( )[0] * range; + gameRenderWorld->DebugArrow ( colorPink, GetPhysics()->GetOrigin(), origin, 5.0f ); + gameRenderWorld->DebugLine ( colorPink, + origin - dir * 1024.0f, + origin + dir * 1024.0f ); + } +} + +/* +=============================================================================== + + rvAITetherRadius + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherRadius ) +END_CLASS + +/* +================ +rvAITetherRadius::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherRadius::InitNonPersistentSpawnArgs ( void ) { + float radius; + if ( !spawnArgs.GetFloat ( "tetherRadius", "0", radius ) ) { + radius = spawnArgs.GetFloat ( "radius", "128" ); + } + radiusSqr = Square ( radius ); +} + +/* +================ +rvAITetherRadius::Spawn +================ +*/ +void rvAITetherRadius::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::Restore +================ +*/ +void rvAITetherRadius::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::ValidateDestination +================ +*/ +bool rvAITetherRadius::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + // Are we within tether radius? + return ((dest - GetPhysics()->GetOrigin()).LengthSqr ( ) < radiusSqr - Square ( ai->move.range ) ); +} + + +/* +================ +rvAITetherRadius::ValidateBounds +================ +*/ +bool rvAITetherRadius::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + return ( Square ( bounds.ShortestDistance ( GetPhysics()->GetOrigin ( ) ) ) < radiusSqr ); +} + +/* +================ +rvAITetherRadius::DebugDraw +================ +*/ +void rvAITetherRadius::DebugDraw ( void ) { + rvAITether::DebugDraw ( ); + gameRenderWorld->DebugCircle( colorPink, GetPhysics()->GetOrigin(), GetPhysics()->GetGravityNormal(), idMath::Sqrt(radiusSqr), 25 ); +} + + +/* +=============================================================================== + + rvAITetherClear + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherClear ) +END_CLASS + + +/* +=============================================================================== + + rvAIBecomePassive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomePassive ) + EVENT( EV_Activate, rvAIBecomePassive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomePassive::Event_Activate +================ +*/ +void rvAIBecomePassive::Event_Activate( idEntity *activator ) { + int i; + bool ignoreEnemies; + + ignoreEnemies = spawnArgs.GetBool ( "ignoreEnemies", "1" ); + + // All targeted AI will become passive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, ignoreEnemies ); + } + } +} + +/* +=============================================================================== + + rvAIBecomeAggressive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomeAggressive ) + EVENT( EV_Activate, rvAIBecomeAggressive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomeAggressive::Event_Activate +================ +*/ +void rvAIBecomeAggressive::Event_Activate( idEntity *activator ) { + int i; + + // All targetted AI will become aggressive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } + } +} diff --git a/source/mpgame/ai/AI_Util.h b/source/mpgame/ai/AI_Util.h new file mode 100644 index 0000000..0a7627b --- /dev/null +++ b/source/mpgame/ai/AI_Util.h @@ -0,0 +1,232 @@ +/* +================ + +AI_Util.h + +================ +*/ + +#ifndef __AI_UTIL__ +#define __AI_UTIL__ + +const float AI_TETHER_MINRANGE = 8.0f; + +/* +=============================================================================== + rvAITrigger +=============================================================================== +*/ + +class rvAITrigger : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITrigger ); + + rvAITrigger ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void Think ( void ); + + virtual void FindTargets ( void ); + +protected: + + idList< idEntityPtr > testAI; + idList< idEntityPtr > testSpawner; + + bool conditionDead; + bool conditionTether; + bool conditionStop; + + int wait; + int nextTriggerTime; + + float percent; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_PostRestore ( void ); + + void Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ); +}; + +/* +=============================================================================== + rvAITether +=============================================================================== +*/ + +class rvAITether : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITether ); + + rvAITether ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateAAS ( idAI* ai ); + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + + virtual bool FindGoal ( idAI* ai, aasGoal_t& goal ); + virtual float GetOriginReachedRange ( void ) {return AI_TETHER_MINRANGE;} + + virtual void DebugDraw ( void ); + + bool CanBreak ( void ) const; + bool IsAutoBreak ( void ) const; + + idList areaNum; + + bool IsWalkForced ( void ) const; + bool IsRunForced ( void ) const; + +protected: + + idEntityPtr location; + + struct tetherFlags_s { + bool canBreak :1; // Temporarily break when enemy is within tether + bool autoBreak :1; // Break when the ai gets within the tether + bool forceRun :1; // Alwasy run when heading towards tether + bool forceWalk :1; // Alwasy walk when heading towards tether + bool becomeAggressive :1; // + bool becomePassive :1; + } tfl; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_TetherSetupLocation ( void ); + void Event_TetherGetLocation ( void ); +}; + +ID_INLINE bool rvAITether::CanBreak ( void ) const { + return tfl.canBreak; +} + +ID_INLINE bool rvAITether::IsWalkForced ( void ) const { + return tfl.forceWalk; +} + +ID_INLINE bool rvAITether::IsRunForced ( void ) const { + return tfl.forceRun; +} + +ID_INLINE bool rvAITether::IsAutoBreak ( void ) const { + return tfl.autoBreak; +} + +/* +=============================================================================== + rvAITetherBehind +=============================================================================== +*/ + +class rvAITetherBehind : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherBehind ); + + rvAITetherBehind( void ) { range = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + +protected: + + float range; +}; + +/* +=============================================================================== + rvAITetherRadius +=============================================================================== +*/ + +class rvAITetherRadius : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherRadius ); + + rvAITetherRadius( void ) { radiusSqr = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + + /* + virtual float GetOriginReachedRange ( void ) + { + float rad = sqrt(radiusSqr); + float halfRad = rad/2.0f; + if ( rad < AI_TETHER_MINRANGE ) + { + return rad; + } + return (halfRad" ); +const idEventDef AI_RealKill ( "" ); +const idEventDef AI_Kill ( "kill" ); +const idEventDef AI_RemoveUpdateSpawner ( "removeUpdateSpawner" ); +const idEventDef AI_AllowHiddenMovement ( "allowHiddenMovement", "d" ); +const idEventDef AI_Speak ( "speak", "s" ); +const idEventDef AI_SpeakRandom ( "speakRandom", "s" ); +const idEventDef AI_IsSpeaking ( "isSpeaking", NULL, 'f' ); +const idEventDef AI_IsTethered ( "isTethered", NULL, 'f' ); +const idEventDef AI_IsWithinTether ( "isWithinTether", NULL, 'f' ); +const idEventDef AI_LaunchMissile ( "launchMissile", "vv", 'e' ); +const idEventDef AI_AttackMelee ( "attackMelee", "s", 'd' ); +const idEventDef AI_DirectDamage ( "directDamage", "es" ); +const idEventDef AI_RadiusDamageFromJoint ( "radiusDamageFromJoint", "ss" ); +const idEventDef AI_MeleeAttackToJoint ( "meleeAttackToJoint", "ss", 'd' ); +const idEventDef AI_CanBecomeSolid ( "canBecomeSolid", NULL, 'f' ); +const idEventDef AI_BecomeSolid ( "becomeSolid" ); +const idEventDef AI_BecomeRagdoll ( "becomeRagdoll", NULL, 'd' ); +const idEventDef AI_BecomePassive ( "becomePassive", "d" ); +const idEventDef AI_BecomeAggressive ( "becomeAggressive" ); +const idEventDef AI_StopRagdoll ( "stopRagdoll" ); +const idEventDef AI_FaceEnemy ( "faceEnemy" ); +const idEventDef AI_FaceEntity ( "faceEntity", "E" ); + +//jshepard +const idEventDef AI_FindEnemy ( "findEnemy", "f", 'e'); + +void idAI::Event_Activate( idEntity *activator ) { Activate( activator );} +void idAI::Event_Touch( idEntity *other, trace_t *trace ) { OnTouch( other, trace ); } +void idAI::Event_SetEnemy( idEntity *ent ) { if ( !ent ) ClearEnemy(); else SetEnemy( ent );} +void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { DirectDamage( damageDefName, damageTarget ); } +void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { RadiusDamageFromJoint( jointname, damageDefName ); } +void idAI::Event_CanBecomeSolid( void ) { idThread::ReturnFloat( CanBecomeSolid() ); } +void idAI::Event_BecomeSolid( void ) { BecomeSolid(); } +void idAI::Event_BecomeNonSolid( void ) { BecomeNonSolid(); } +void idAI::Event_BecomeRagdoll( void ) { idThread::ReturnInt( StartRagdoll() ); } +void idAI::Event_StopRagdoll( void ) { StopRagdoll(); SetPhysics( &physicsObj ); } +void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) aifl.dead = false; else aifl.dead = true; } +void idAI::Event_FaceEnemy( void ) { FaceEnemy(); } +void idAI::Event_FaceEntity( idEntity *ent ) { FaceEntity( ent ); } +void idAI::Event_SetTalkState( int state ) { SetTalkState ( (talkState_t)state ); } +void idAI::Event_Speak( const char *speechDecl ) { Speak( speechDecl ); } +void idAI::Event_SpeakRandom( const char *speechDecl ) { Speak( speechDecl, true ); } +void idAI::Event_GetLeader( void ) { idThread::ReturnEntity( leader ); } +void idAI::Event_SetLeader( idEntity* ent ) { SetLeader ( ent ); } +void idAI::Event_GetEnemy( void ) { idThread::ReturnEntity( enemy.ent ); } +void idAI::Event_TakeDamage( float takeDamage ) { fl.takedamage = ( takeDamage ) ? true : false; } +void idAI::Event_SetUndying( float setUndying ) { aifl.undying = ( setUndying ) ? true : false; } + + +void idAI::Event_IsSpeaking ( void ) { idThread::ReturnFloat ( IsSpeaking ( ) ); } +void idAI::Event_IsTethered ( void ) { idThread::ReturnFloat ( IsTethered ( ) ); } +void idAI::Event_IsWithinTether ( void ) { idThread::ReturnFloat ( IsWithinTether ( ) ); } +void idAI::Event_IsMoving ( void ) { idThread::ReturnFloat ( move.fl.moving ); } + +CLASS_DECLARATION( idActor, idAI ) + EVENT( EV_Activate, idAI::Event_Activate ) + EVENT( EV_Touch, idAI::Event_Touch ) + + // Enable / Disable + EVENT( AI_EnableClip, idAI::Event_EnableClip ) + EVENT( AI_DisableClip, idAI::Event_DisableClip ) + EVENT( AI_EnableGravity, idAI::Event_EnableGravity ) + EVENT( AI_DisableGravity, idAI::Event_DisableGravity ) + EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush ) + EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush ) + EVENT( AI_EnableDamage, idAI::Event_EnableDamage ) + EVENT( AI_DisableDamage, idAI::Event_DisableDamage ) + EVENT( AI_EnablePain, idAI::Event_EnablePain ) + EVENT( AI_DisablePain, idAI::Event_DisablePain ) + EVENT( AI_EnableTarget, idAI::Event_EnableTarget ) + EVENT( AI_DisableTarget, idAI::Event_DisableTarget ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_SetUndying, idAI::Event_SetUndying ) + EVENT( AI_EnableAutoBlink, idAI::Event_EnableAutoBlink ) + EVENT( AI_DisableAutoBlink, idAI::Event_DisableAutoBlink ) + + // Scripted sequences + EVENT( AI_ScriptedMove, idAI::Event_ScriptedMove ) + EVENT( AI_ScriptedFace, idAI::Event_ScriptedFace ) + EVENT( AI_ScriptedAnim, idAI::Event_ScriptedAnim ) + EVENT( AI_ScriptedAction, idAI::Event_ScriptedAction ) + EVENT( AI_ScriptedPlaybackMove, idAI::Event_ScriptedPlaybackMove ) + EVENT( AI_ScriptedPlaybackAim, idAI::Event_ScriptedPlaybackAim ) + EVENT( AI_ScriptedDone, idAI::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, idAI::Event_ScriptedStop ) + EVENT( AI_ScriptedJumpDown, idAI::Event_ScriptedJumpDown ) + + // Get / Set + EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) + EVENT( AI_SetLeader, idAI::Event_SetLeader ) + EVENT( AI_GetLeader, idAI::Event_GetLeader ) + EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) + EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) + EVENT( EV_GetAngles, idAI::Event_GetAngles ) + EVENT( EV_SetAngles, idAI::Event_SetAngles ) + EVENT( AI_SetScript, idAI::Event_SetScript ) + EVENT( AI_SetMoveSpeed, idAI::Event_SetMoveSpeed ) + EVENT( AI_SetPassivePrefix, idAI::Event_SetPassivePrefix ) + + // Misc + EVENT( AI_Attack, idAI::Event_Attack ) + EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) + + EVENT( AI_LookAt, idAI::Event_LookAt ) + EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) + EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) + EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid ) + EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid ) + EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid ) + EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll ) + EVENT( AI_BecomePassive, idAI::Event_BecomePassive ) + EVENT( AI_BecomeAggressive, idAI::Event_BecomeAggressive ) + EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll ) + EVENT( AI_SetHealth, idAI::Event_SetHealth ) + EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) + EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) + EVENT( AI_StopThinking, idAI::Event_StopThinking ) + EVENT( AI_LockEnemyOrigin, idAI::Event_LockEnemyOrigin ) + EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) + EVENT( AI_RealKill, idAI::Event_RealKill ) + EVENT( AI_Kill, idAI::Event_Kill ) + EVENT( AI_RemoveUpdateSpawner, idAI::Event_RemoveUpdateSpawner ) + EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) + EVENT( AI_Speak, idAI::Event_Speak ) + EVENT( AI_SpeakRandom, idAI::Event_SpeakRandom ) + EVENT( AI_IsSpeaking, idAI::Event_IsSpeaking ) + EVENT( AI_IsTethered, idAI::Event_IsTethered ) + EVENT( AI_IsWithinTether, idAI::Event_IsWithinTether ) + EVENT( EV_IsMoving, idAI::Event_IsMoving ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) + EVENT( EV_SetKey, idAI::Event_SetKey ) + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + EVENT( EV_PostSpawn, idAI::Event_PostSpawn ) + // RAVEN END +END_CLASS + +/* +===================== +idAI::Event_PredictEnemyPos +===================== +*/ +void idAI::Event_PredictEnemyPos( float time ) { + predictedPath_t path; + idEntity* enemyEnt = enemy.ent; + + // if no enemy set + if ( !enemyEnt ) { + idThread::ReturnVector( physicsObj.GetOrigin() ); + return; + } + + // predict the enemy movement + idAI::PredictPath( enemyEnt, aas, enemy.lastKnownPosition, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnVector( path.endPos ); +} + +/* +===================== +idAI::Event_TestAnimMoveTowardEnemy +===================== +*/ +void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + float yaw; + idVec3 delta; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + yaw = delta.ToYaw(); + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestAnimMove +===================== +*/ +void idAI::Event_TestAnimMove( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + int animLen; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + animLen = animator.AnimLength( anim ); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMoveToPosition +===================== +*/ +void idAI::Event_TestMoveToPosition( const idVec3 &position ) { + predictedPath_t path; + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, gameLocal.msec ); + if ( path.endEvent ) { + gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMeleeAttack +===================== +*/ +void idAI::Event_TestMeleeAttack( void ) { + bool result = TestMelee(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_TestAnimAttack +===================== +*/ +void idAI::Event_TestAnimAttack( const char *animname ) { + int anim; + predictedPath_t path; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.ent ) ); +} + +/* +===================== +idAI::Event_LockEnemyOrigin +===================== +*/ +void idAI::Event_LockEnemyOrigin ( void ) { + enemy.fl.lockOrigin = true; +} + +/* +===================== +idAI::Event_StopThinking +===================== +*/ +void idAI::Event_StopThinking( void ) { + BecomeInactive( TH_THINK ); + idThread *thread = idThread::CurrentThread(); + if ( thread ) { + thread->DoneProcessing(); + } +} + +/* +===================== +idAI::Event_JumpFrame +===================== +*/ +void idAI::Event_JumpFrame( void ) { + aifl.jump = true; +} + +/* +===================== +idAI::Event_EnableClip +===================== +*/ +void idAI::Event_EnableClip( void ) { + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + Event_EnableGravity ( ); +} + +/* +===================== +idAI::Event_DisableClip +===================== +*/ +void idAI::Event_DisableClip( void ) { + physicsObj.SetClipMask( 0 ); + Event_DisableGravity ( ); +} + +/* +===================== +idAI::Event_EnableGravity +===================== +*/ +void idAI::Event_EnableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, false ); +} + +/* +===================== +idAI::Event_DisableGravity +===================== +*/ +void idAI::Event_DisableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, true ); +} + +/* +===================== +idAI::Event_EnableAFPush +===================== +*/ +void idAI::Event_EnableAFPush( void ) { + move.fl.allowPushMovables = true; +} + +/* +===================== +idAI::Event_DisableAFPush +===================== +*/ +void idAI::Event_DisableAFPush( void ) { + move.fl.allowPushMovables = false; +} + +/* +===================== +idAI::Event_EnableDamage +===================== +*/ +void idAI::Event_EnableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, true ); +} + +/* +===================== +idAI::Event_DisableDamage +===================== +*/ +void idAI::Event_DisableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, false ); +} + +/* +=============== +idAI::Event_DisablePain +=============== +*/ +void idAI::Event_DisablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, true ); +} + +/* +=============== +idAI::Event_EnablePain +=============== +*/ +void idAI::Event_EnablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, false ); +} + +/* +=============== +idAI::Event_EnableTarget +=============== +*/ +void idAI::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +=============== +idAI::Event_DisableTarget +=============== +*/ +void idAI::Event_DisableTarget ( void ) { + fl.notarget = true; +} + + +/* +===================== +idAI::Event_EnableAutoBlink +===================== +*/ +void idAI::Event_EnableAutoBlink( void ) { + fl.allowAutoBlink = true; +} + +/* +===================== +idAI::Event_DisableAutoBlink +===================== +*/ +void idAI::Event_DisableAutoBlink( void ) { + fl.allowAutoBlink = false; +} + +/* +===================== +idAI::Event_BecomeAggressive +===================== +*/ +void idAI::Event_BecomeAggressive ( void ) { + combat.fl.ignoreEnemies = false; + combat.fl.aware = true; + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_BecomePassive +===================== +*/ +void idAI::Event_BecomePassive ( int ignoreEnemies ) { + combat.fl.ignoreEnemies = (ignoreEnemies != 0); + combat.fl.aware = false; + SetEnemy ( NULL ); + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_LookAt +===================== +*/ +void idAI::Event_LookAt ( idEntity* lookAt ) { + lookTarget = lookAt; +} + +/* +===================== +idAI::LookAtEntity +===================== +*/ +void idAI::LookAtEntity( idEntity *ent, float duration ) { + if ( ent == this ) { + ent = NULL; + } + + if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = ent; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +================ +idAI::Event_ThrowMoveable +================ +*/ +void idAI::Event_ThrowMoveable( void ) { + idEntity *ent; + idEntity *moveable = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + moveable = ent; + break; + } + } + if ( moveable ) { + moveable->Unbind(); + moveable->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_ThrowAF +================ +*/ +void idAI::Event_ThrowAF( void ) { + idEntity *ent; + idEntity *af = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = ent; + break; + } + } + if ( af ) { + af->Unbind(); + af->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_SetAngles +================ +*/ +void idAI::Event_SetAngles( idAngles const &ang ) { + move.current_yaw = ang.yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); +} + +/* +================ +idAI::Event_GetAngles +================ +*/ +void idAI::Event_GetAngles( void ) { + idThread::ReturnVector( idVec3( 0.0f, move.current_yaw, 0.0f ) ); +} + +/* +================ +idAI::Event_RealKill +================ +*/ +void idAI::Event_RealKill( void ) { + health = 0; + + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + Killed( this, this, 0, vec3_zero, INVALID_JOINT ); +} + +/* +================ +idAI::Event_Kill +================ +*/ +void idAI::Event_Kill( void ) { + PostEventMS( &AI_RealKill, 0 ); +} + +/* +================ +idAI::Event_RemoveUpdateSpawner +================ +*/ +void idAI::Event_RemoveUpdateSpawner( void ) { + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + PostEventMS( &EV_Remove, 0 ); +} +/* +===================== +idAI::Event_FindActorsInBounds +===================== +*/ +void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedEntities = gameLocal.EntitiesTouchingBounds( this, idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++ ) { + ent = entityList[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_ClosestReachableEnemyOfEntity +===================== +*/ +void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) { + idActor *actor; + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + int areaNum; + int enemyAreaNum; + aasPath_t path; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !team_mate->IsType( idActor::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); + } + + actor = static_cast( team_mate ); + + const idVec3 &origin = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( origin ); + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - origin; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin(); + enemyAreaNum = PointReachableAreaNum( enemyPos ); + if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + } + + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_EntityInAttackCone +===================== +*/ +void idAI::Event_EntityInAttackCone( idEntity *ent ) { + float attack_cone; + idVec3 delta; + float yaw; + float relYaw; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + delta = ent->GetPhysics()->GetOrigin() - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + yaw = delta.ToYaw(); + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + relYaw = idMath::AngleNormalize180( move.ideal_yaw - yaw ); + if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idAI::Event_CanReachPosition +================ +*/ +void idAI::Event_CanReachPosition( const idVec3 &pos ) { + aasPath_t path; + int toAreaNum; + int areaNum; + + toAreaNum = PointReachableAreaNum( pos ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEntity +================ +*/ +void idAI::Event_CanReachEntity( idEntity *ent ) { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + toAreaNum = PointReachableAreaNum( pos ); + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEnemy +================ +*/ +void idAI::Event_CanReachEnemy( void ) { + aasPath_t path; + int toAreaNum = 0; + int areaNum; + idVec3 pos; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if( enemyEnt->IsType( idActor::GetClassType() ) ){ + idActor *enemyAct = static_cast( enemyEnt ); + if ( enemyAct->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + enemyAct->GetAASLocation( aas, pos, toAreaNum ); + } + } else { + pos = enemyEnt->GetPhysics()->GetOrigin(); + toAreaNum = PointReachableAreaNum( pos ); + } + + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_GetReachableEntityPosition +================ +*/ +void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { + int toAreaNum; + idVec3 pos; + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + if ( aas ) { + toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( toAreaNum, pos ); + } + + idThread::ReturnVector( pos ); +} + +/* +================ +idAI::Event_ScriptedMove +================ +*/ +void idAI::Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + ScriptedMove ( destEnt, minDist, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedFace +================ +*/ +void idAI::Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + ScriptedFace ( faceEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAnim +================ +*/ +void idAI::Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + ScriptedAnim ( animname, blendFrames, loop, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAction +================ +*/ +void idAI::Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + ScriptedAction ( actionEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedPlaybackMove +================ +*/ +void idAI::Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackMove ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedPlaybackAim +================ +*/ +void idAI::Event_ScriptedPlaybackAim( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackAim ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedDone +================ +*/ +void idAI::Event_ScriptedDone ( void ) { + idThread::ReturnFloat ( !aifl.scripted ); +} + +/* +================ +idAI::Event_ScriptedStop +================ +*/ +void idAI::Event_ScriptedStop ( void ) { + ScriptedStop ( ); +} + +/* +================ +idAI::Event_AllowHiddenMovement +================ +*/ +void idAI::Event_AllowHiddenMovement( int enable ) { + move.fl.allowHiddenMove = ( enable != 0 ); +} + +/* +================ +idAI::Event_SetScript +================ +*/ +void idAI::Event_SetScript ( const char* scriptName, const char* funcName ) { + SetScript ( scriptName, funcName ); +} + +/* +================ +idAI::Event_SetMoveSpeed +================ +*/ +void idAI::Event_SetMoveSpeed ( int speed ) { + switch ( speed ) { + case AIMOVESPEED_DEFAULT: + move.fl.noRun = false; + move.fl.noWalk = false; + break; + + case AIMOVESPEED_RUN: + move.fl.noRun = false; + move.fl.noWalk = true; + break; + + case AIMOVESPEED_WALK: + move.fl.noRun = true; + move.fl.noWalk = false; + break; + } +} + +/* +================ +idAI::Event_SetPassivePrefix +================ +*/ +void idAI::Event_SetPassivePrefix ( const char* prefix ) { + SetPassivePrefix ( prefix ); +} + +/* +================ +idAI::Event_Attack +================ +*/ +void idAI::Event_Attack ( const char* attackName, const char* jointName ) { + Attack ( attackName, animator.GetJointHandle ( jointName ), enemy.ent ); // , physicsObj.GetPushedLinearVelocity ( ) ); +} + +/* +================ +idAI::Event_AttackMelee +================ +*/ +void idAI::Event_AttackMelee( const char* meleeName ) { + const idDict* meleeDict; + meleeDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", meleeName ) ), false ); + if ( !meleeDict ) { + gameLocal.Error ( "missing meleeDef '%s' for ai entity '%s'", meleeName, GetName() ); + } + AttackMelee ( meleeName, meleeDict ); +} + +/* +================ +idAI::Event_ScriptedJumpDown +================ +*/ +void idAI::Event_ScriptedJumpDown( float yaw ) { + if ( animator.HasAnim( "jumpdown_start" ) ) + { + aifl.scripted = true; + move.ideal_yaw = yaw; + SetState( "State_ScriptedJumpDown" ); + } +} + +/* +================ +idAI::Event_FindEnemy +================ +*/ +void idAI::Event_FindEnemy( float distSqr ) { + idThread::ReturnEntity ( FindEnemy( false, 1, distSqr )); +} + +/* +================ +idAI::Event_SetKey +================ +*/ +void idAI::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); + + OnSetKey ( key, value ); +} + +/* +================ +idAI::Event_PostSpawn +================ +*/ +void idAI::Event_PostSpawn( void ) { + // RAVEN BEGIN + // twhitaker: difficulty levels + if ( team == TEAM_MARINE ) { + //health /= 1.0f + gameLocal.GetDifficultyModifier( ); + + //buddies are a little more healthy on hard & nightmare since the baddies deal so much more damage + switch ( g_skill.GetInteger() ) { + case 3: + health *= 1.4f; + break; + case 2: + health *= 1.2f; + break; + case 0: + health *= 1.2f; + break; + case 1: + default: + break; + } + } else { + health *= 1.0f + gameLocal.GetDifficultyModifier( ); + } + // RAVEN END +} diff --git a/source/mpgame/ai/AI_pathing.cpp b/source/mpgame/ai/AI_pathing.cpp new file mode 100644 index 0000000..2413a69 --- /dev/null +++ b/source/mpgame/ai/AI_pathing.cpp @@ -0,0 +1,1626 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +// nmckenzie: +#include "AI.h" +// RAVEN END + +/* +=============================================================================== + + Dynamic Obstacle Avoidance + + - assumes the AI lives inside a bounding box aligned with the gravity direction + - obstacles in proximity of the AI are gathered + - if obstacles are found the AAS walls are also considered as obstacles + - every obstacle is represented by an oriented bounding box (OBB) + - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction + - the 2D windings of the projections are expanded for the AI bbox + - a path tree is build using clockwise and counter clockwise edge walks along the winding edges + - the path tree is pruned and optimized + - the shortest path is chosen for navigation + +=============================================================================== +*/ + +const float MAX_OBSTACLE_RADIUS = 256.0f; +const float PUSH_OUTSIDE_OBSTACLES = 0.5f; +const float CLIP_BOUNDS_EPSILON = 10.0f; +const int MAX_AAS_WALL_EDGES = 256; +const int MAX_OBSTACLES = 256; +const int MAX_PATH_NODES = 256; +const int MAX_OBSTACLE_PATH = 64; + +typedef struct obstacle_s { + idVec2 bounds[2]; + idWinding2D winding; + idEntity * entity; + bool fakePlayerForwardObstacle; +} obstacle_t; + +typedef struct pathNode_s { + int dir; + idVec2 pos; + idVec2 delta; + float dist; + int obstacle; + int edgeNum; + int numNodes; + struct pathNode_s * parent; + struct pathNode_s * children[2]; + struct pathNode_s * next; + void Init(); +} pathNode_t; + +void pathNode_s::Init() { + dir = 0; + pos.Zero(); + delta.Zero(); + obstacle = -1; + edgeNum = -1; + numNodes = 0; + parent = children[0] = children[1] = next = NULL; +} + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc pathNodeAllocator; +// RAVEN END + +/* +============ +LineIntersectsPath +============ +*/ +bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) { + float d0, d1, d2, d3; + idVec3 plane1, plane2; + + plane1 = idWinding2D::Plane2DFromPoints( start, end ); + d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z; + while( node->parent ) { + d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z; + if ( FLOATSIGNBITSET( d0 ) ^ FLOATSIGNBITSET( d1 ) ) { + plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos ); + d2 = plane2.x * start.x + plane2.y * start.y + plane2.z; + d3 = plane2.x * end.x + plane2.y * end.y + plane2.z; + if ( FLOATSIGNBITSET( d2 ) ^ FLOATSIGNBITSET( d3 ) ) { + return true; + } + } + d0 = d1; + node = node->parent; + } + return false; +} + +/* +============ +PointInsideObstacle +============ +*/ +int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point, bool skipFakePlayerForwardObstacles = false ) { + int i; + + for ( i = 0; i < numObstacles; i++ ) { + if ( skipFakePlayerForwardObstacles && obstacles[i].fakePlayerForwardObstacle ) { + //don't care if we're inside of these + continue; + } + + const idVec2 *bounds = obstacles[i].bounds; + if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) { + continue; + } + + if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) { + continue; + } + + return i; + } + + return -1; +} + +/* +============ +GetPointOutsideObstacles +============ +*/ +void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) { + int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2]; + float d, bestd, scale[2]; + idVec3 plane, bestPlane; + idVec2 newPoint, dir, bestPoint; + int *queue; + bool *obstacleVisited; + idWinding2D w1, w2; + + if ( obstacle ) { + *obstacle = -1; + } + if ( edgeNum ) { + *edgeNum = -1; + } + + bestObstacle = PointInsideObstacle( obstacles, numObstacles, point ); + if ( bestObstacle == -1 ) { + return; + } + + const idWinding2D &w = obstacles[bestObstacle].winding; + bestd = idMath::INFINITY; + bestEdgeNum = 0; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d < bestd ) { + bestd = d; + bestPlane = plane; + bestEdgeNum = i; + } + // if this is a wall always try to pop out at the first edge + if ( obstacles[bestObstacle].entity == NULL ) { + break; + } + } + + newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2(); + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + point = newPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + + queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) ); + obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) ); + + queueStart = 0; + queueEnd = 1; + queue[0] = bestObstacle; + + memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) ); + obstacleVisited[bestObstacle] = true; + + bestd = idMath::INFINITY; + for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) { + w1 = obstacles[i].winding; + w1.Expand( PUSH_OUTSIDE_OBSTACLES ); + + for ( j = 0; j < numObstacles; j++ ) { + // if the obstacle has been visited already + if ( obstacleVisited[j] ) { + continue; + } + // if the bounds do not intersect + if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y || + obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + + queue[queueEnd++] = j; + obstacleVisited[j] = true; + + w2 = obstacles[j].winding; + w2.Expand( 0.2f ); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + dir = w1[(k+1)%w1.GetNumPoints()] - w1[k]; + if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) { + continue; + } + for ( n = 0; n < 2; n++ ) { + newPoint = w1[k] + scale[n] * dir; + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + d = ( newPoint - point ).LengthSqr(); + if ( d < bestd ) { + bestd = d; + bestPoint = newPoint; + bestEdgeNum = edgeNums[n]; + bestObstacle = j; + } + } + } + } + } + + if ( bestd < idMath::INFINITY ) { + point = bestPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + } + gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); +} + +/* +============ +GetFirstBlockingObstacle +============ +*/ +bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) { + int i, edgeNums[2]; + float dist, scale1, scale2; + idVec2 bounds[2]; + + // get bounds for the current movement delta + bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(delta.x)].x += delta.x; + bounds[FLOATSIGNBITNOTSET(delta.y)].y += delta.y; + + // test for obstacles blocking the path + blockingScale = idMath::INFINITY; + dist = delta.Length(); + for ( i = 0; i < numObstacles; i++ ) { + if ( i == skipObstacle ) { + continue; + } + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) { + if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) { + blockingScale = scale1; + blockingObstacle = i; + blockingEdgeNum = edgeNums[0]; + } + } + } + return ( blockingScale < 1.0f ); +} + +/* +============ +GetObstacles +============ +*/ +int GetObstacles( const idActor* owner, const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) { + int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum, numIterations, extrude; + int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2]; + float stepHeight, headHeight, blockingScale, min, max; + idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd; + idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal; + idVec2 obDelta; + idPhysics *obPhys; + idBox box; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 extrudeVec; + idPlayer* player = gameLocal.GetLocalPlayer(); + bool extrudePlayer = false; + + if ( player && owner->team == player->team ) { + if ( player->HasEnemies() + || (player->weapon && player->weapon->lastAttack > gameLocal.GetTime() - 2000) + || (owner->IsType( idAI::GetClassType() ) && ((idAI*)owner)->GetEnemy()) ) { + + extrudeVec = player->firstPersonViewAxis[0]; + extrudeVec[2] = 0; + extrudeVec.Normalize(); + idVec3 myMoveDir = (seekPos-startPos); + /* + myMoveDir.z = 0; + if ( myMoveDir * extrudeVec > 200.0f ) + {//I'm already moving out away from him in the direction he's moving... (he's following me?) + } + else + {//we are heading into his line of sight, stop + */ + extrudePlayer = true; + extrudeVec *= (player->GetPhysics()->GetBounds()[1][0]-player->GetPhysics()->GetBounds()[0][0]); + //} + //FIXME: keep moving if already inside this cone? Otherwise, we'll stop in our tracks? + } + } + + numObstacles = 0; + + seekDelta = seekPos - startPos; + expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + + physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight ); + stepHeight += aas->GetSettings()->maxStepHeight; + + // clip bounds for the obstacle search space + clipBounds[0] = clipBounds[1] = startPos; + clipBounds.AddPoint( seekPos ); + clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS ); + clipMask = physics->GetClipMask(); + + // find all obstacles touching the clip bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( owner, clipBounds, clipMask, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( obEnt->IsType( idActor::GetClassType() ) ) { +// RAVEN END + obPhys = obEnt->GetPhysics(); + // ignore myself, my enemy, and dead bodies + if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) { + continue; + } + // if the actor is moving + // cdr: Alternate Routes Bug + // idVec3 v1 = obPhys->GetLinearVelocity(); + // if ( v1.LengthSqr() > Square( 10.0f ) ) { + // idVec3 v2 = physics->GetLinearVelocity(); + // if ( v2.LengthSqr() > Square( 10.0f ) ) { + // // if moving in about the same direction + // if ( v1 * v2 > 0.0f ) { + // continue; + // } + // } + // } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +// cdr: Ignore Pushable objects + } else if (obEnt->GetPhysics()->IsPushable()) { + continue; + } else if ( obEnt->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // moveables are considered obstacles + } else { + // ignore everything else + continue; + } + + // check if we can step over the object + clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max ); + if ( max < stepHeight || min > headHeight ) { + // can step over this one + continue; + } + + numIterations = 1; + if ( extrudePlayer && obEnt == player ) + {//do magic + numIterations = 3; + } + for ( extrude = 0; extrude < numIterations; extrude++ ) { + // project a box containing the obstacle onto the floor plane + box = idBox( clipModel->GetBounds(), extrude?clipModel->GetOrigin()+(extrude*extrudeVec):clipModel->GetOrigin(), clipModel->GetAxis() ); + numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts ); + + // create a 2D winding for the obstacle; + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + for ( j = 0; j < numVerts; j++ ) { + obstacle.winding.AddPoint( silVerts[j].ToVec2() ); + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( j = 0; j < numVerts; j++ ) { + silVerts[j].z = startPos.z; + } + for ( j = 0; j < numVerts; j++ ) { + gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 ); + } + } + + // expand the 2D winding for collision with a 2D box + obstacle.winding.ExpandForAxialBox( expBounds ); + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = obEnt; + if ( extrudePlayer && obEnt == player && extrude > 0 ) { + //don't care if we're inside one of these, because that would just make us stop where we are, anyway! + obstacle.fakePlayerForwardObstacle = true; + } else { + obstacle.fakePlayerForwardObstacle = false; + } + } + } + + // if there are no dynamic obstacles the path should be through valid AAS space + if ( numObstacles == 0 ) { + return 0; + } + + // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space + if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2(), true ) == -1 ) { + if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) { + return 0; + } + } + + // create obstacles for AAS walls + if ( aas ) { + float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f; + + numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES ); + aas->SortWallEdges( wallEdges, numWallEdges ); + + lastVerts[0] = lastVerts[1] = 0; + lastEdgeNormal.Zero(); + nextVerts[0] = nextVerts[1] = 0; + for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) { + aas->GetEdge( wallEdges[i], start, end ); + aas->GetEdgeVertexNumbers( wallEdges[i], verts ); + edgeDir = end.ToVec2() - start.ToVec2(); + edgeDir.Normalize(); + edgeNormal.x = edgeDir.y; + edgeNormal.y = -edgeDir.x; + if ( i < numWallEdges-1 ) { + aas->GetEdge( wallEdges[i+1], nextStart, nextEnd ); + aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts ); + nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2(); + nextEdgeDir.Normalize(); + nextEdgeNormal.x = nextEdgeDir.y; + nextEdgeNormal.y = -nextEdgeDir.x; + } + + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + obstacle.winding.AddPoint( end.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize ); + obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize ); + if ( lastVerts[1] == verts[0] ) { + obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[1] -= edgeDir; + } + if ( verts[1] == nextVerts[0] ) { + obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[0] += edgeDir; + } + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = NULL; + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( lastVerts, verts, sizeof( lastVerts ) ); +// RAVEN END + lastEdgeNormal = edgeNormal; + } + } + + // show obstacles + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( i = 0; i < numObstacles; i++ ) { + obstacle_t &obstacle = obstacles[i]; + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + silVerts[j].ToVec2() = obstacle.winding[j]; + silVerts[j].z = startPos.z; + } + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 ); + } + } + } + + return numObstacles; +} + +/* +============ +FreePathTree_r +============ +*/ +void FreePathTree_r( pathNode_t *node ) { + if ( node->children[0] ) { + FreePathTree_r( node->children[0] ); + } + if ( node->children[1] ) { + FreePathTree_r( node->children[1] ); + } + pathNodeAllocator.Free( node ); +} + +/* +============ +DrawPathTree +============ +*/ +void DrawPathTree( const pathNode_t *root, const float height ) { + int i; + idVec3 start, end; + const pathNode_t *node; + + for ( node = root; node; node = node->next ) { + for ( i = 0; i < 2; i++ ) { + if ( node->children[i] ) { + start.ToVec2() = node->pos; + start.z = height; + end.ToVec2() = node->children[i]->pos; + end.z = height; + gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 ); + break; + } + } + } +} + +/* +============ +GetPathNodeDelta +============ +*/ +bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) { + int numPoints, edgeNum; + bool facing; + idVec2 seekDelta, dir; + pathNode_t *n; + + numPoints = obstacles[node->obstacle].winding.GetNumPoints(); + + // get delta along the current edge + while( 1 ) { + edgeNum = ( node->edgeNum + node->dir ) % numPoints; + node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos; + if ( node->delta.LengthSqr() > 0.01f ) { + break; + } + node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints; + } + + // if not blocked + if ( !blocked ) { + + // test if the current edge faces the goal + seekDelta = seekPos - node->pos; + facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f; + + // if the current edge faces goal and the line from the current + // position to the goal does not intersect the current path + if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) { + node->delta = seekPos - node->pos; + node->edgeNum = -1; + } + } + + // if the delta is along the obstacle edge + if ( node->edgeNum != -1 ) { + // if the edge is found going from this node to the root node + for ( n = node->parent; n; n = n->parent ) { + + if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) { + continue; + } + + // test whether or not the edge segments actually overlap + if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) { + continue; + } + if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) { + continue; + } + + break; + } + if ( n ) { + return false; + } + } + return true; +} + +/* +============ +BuildPathTree +============ +*/ +pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) { + int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH; + float blockingScale; + pathNode_t *root, *node, *child; + + // gcc 4.0 + idQueueTemplate pathNodeQueue, treeQueue; + + root = pathNodeAllocator.Alloc(); + root->Init(); + root->pos = startPos; + + root->delta = seekPos - root->pos; + root->numNodes = 0; + pathNodeQueue.Add( root ); + + for ( node = pathNodeQueue.Get(); node && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) { + + treeQueue.Add( node ); + + // if this path has more than twice the number of nodes than the best path so far + if ( node->numNodes > bestNumNodes * 2 ) { + continue; + } + + // don't move outside of the clip bounds + idVec2 endPos = node->pos + node->delta; + if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x || + endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) { + continue; + } + + // if an obstacle is blocking the path + if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) { + + if ( path.firstObstacle == NULL ) { + path.firstObstacle = obstacles[blockingObstacle].entity; + } + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(obstacles[blockingObstacle].entity); + // RAVEN END + + + node->delta *= blockingScale; + + if ( node->edgeNum == -1 ) { + node->children[0] = pathNodeAllocator.Alloc(); + node->children[0]->Init(); + node->children[1] = pathNodeAllocator.Alloc(); + node->children[1]->Init(); + node->children[0]->dir = 0; + node->children[1]->dir = 1; + node->children[0]->parent = node->children[1]->parent = node; + node->children[0]->pos = node->children[1]->pos = node->pos + node->delta; + node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle; + node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum; + node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[0] ); + } + if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[1] ); + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->obstacle = blockingObstacle; + child->edgeNum = blockingEdgeNum; + child->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) { + pathNodeQueue.Add( child ); + } + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->numNodes = node->numNodes + 1; + + // there is a free path towards goal + if ( node->edgeNum == -1 ) { + if ( node->numNodes < bestNumNodes ) { + bestNumNodes = node->numNodes; + } + continue; + } + + child->obstacle = node->obstacle; + obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints(); + child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints; + + if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) { + pathNodeQueue.Add( child ); + } + } + } + + return root; +} + +/* +============ +PrunePathTree +============ +*/ +void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) { + int i; + float bestDist; + pathNode_t *node, *lastNode, *n, *bestNode; + + node = root; + while( node ) { + + node->dist = ( seekPos - node->pos ).LengthSqr(); + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + + // find the node closest to the goal along this path + bestDist = idMath::INFINITY; + bestNode = node; + for ( n = node; n; n = n->parent ) { + if ( n->children[0] && n->children[1] ) { + break; + } + if ( n->dist < bestDist ) { + bestDist = n->dist; + bestNode = n; + } + } + + // free tree down from the best node + for ( i = 0; i < 2; i++ ) { + if ( bestNode->children[i] ) { + FreePathTree_r( bestNode->children[i] ); + bestNode->children[i] = NULL; + } + } + + for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && ( node->children[1] != lastNode ) ) { + node = node->children[1]; + break; + } + } + } + } +} + +/* +============ +OptimizePath +============ +*/ +int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) { + int i, numPathPoints, edgeNums[2]; + const pathNode_t *curNode, *nextNode; + idVec2 curPos, curDelta, bounds[2]; + float scale1, scale2, curLength; + + optimizedPath[0] = root->pos; +// BDUBE: FIXME - Added this line because quite a few places in the code count on there being at least 2 items in that array + optimizedPath[1] = root->pos; + + numPathPoints = 1; + + for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) { + + for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) { + + // can only take shortcuts when going from one object to another + if ( nextNode->obstacle == curNode->obstacle ) { + continue; + } + + curPos = curNode->pos; + curDelta = nextNode->pos - curPos; + curLength = curDelta.Length(); + + // get bounds for the current movement delta + bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(curDelta.x)].x += curDelta.x; + bounds[FLOATSIGNBITNOTSET(curDelta.y)].y += curDelta.y; + + // test if the shortcut intersects with any obstacles + for ( i = 0; i < numObstacles; i++ ) { + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) { + if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) { + break; + } + if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) { + break; + } + } + } + if ( i >= numObstacles ) { + break; + } + } + + // store the next position along the optimized path + optimizedPath[numPathPoints++] = nextNode->pos; + } + + return numPathPoints; +} + +/* +============ +PathLength +============ +*/ +float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) { + int i; + float pathLength; + + // calculate the path length + pathLength = 0.0f; + for ( i = 0; i < numPathPoints-1; i++ ) { + pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast(); + } + + // add penalty if this path does not go in the current direction + if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) { + pathLength += 100.0f; + } + return pathLength; +} + +/* +============ +FindOptimalPath + + Returns true if there is a path all the way to the goal. +============ +*/ +bool FindOptimalPath( const idActor* owner, const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) { + int i, numPathPoints, bestNumPathPoints; + const pathNode_t *node, *lastNode, *bestNode; + idVec2 optimizedPath[MAX_OBSTACLE_PATH]; + float pathLength, bestPathLength; + bool pathToGoalExists, optimizedPathCalculated; + + seekPos.Zero(); + seekPos.z = height; + + pathToGoalExists = false; + optimizedPathCalculated = false; + + bestNode = root; + bestNumPathPoints = 0; + bestPathLength = idMath::INFINITY; + + node = root; + while( node ) { + + pathToGoalExists |= ( node->dist < 0.1f ); + + if ( node->dist <= bestNode->dist ) { + + if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) { + + if ( !optimizedPathCalculated ) { + bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() ); + seekPos.ToVec2() = optimizedPath[1]; + } + + numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath ); + pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() ); + + if ( pathLength < bestPathLength ) { + bestNode = node; + bestNumPathPoints = numPathPoints; + bestPathLength = pathLength; + seekPos.ToVec2() = optimizedPath[1]; + } + optimizedPathCalculated = true; + + } else { + + bestNode = node; + optimizedPathCalculated = false; + } + } + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && node->children[1] != lastNode ) { + node = node->children[1]; + break; + } + } + } + } + + if ( !pathToGoalExists ) { + if ( root->children[0] ) { + seekPos.ToVec2() = root->children[0]->pos; + } else if ( root->children[1] ) { + seekPos.ToVec2() = root->children[1]->pos; + } + } else if ( !optimizedPathCalculated ) { + OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + seekPos.ToVec2() = optimizedPath[1]; + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + idVec3 start, end; + start.z = end.z = height + 4.0f; + numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + for ( i = 0; i < numPathPoints-1; i++ ) { + start.ToVec2() = optimizedPath[i]; + end.ToVec2() = optimizedPath[i+1]; + gameRenderWorld->DebugArrow( colorCyan, start, end, 1 ); + } + } + + return pathToGoalExists; +} + +/* +============ +idAI::FindPathAroundObstacles + + Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks. +============ +*/ +bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) { + int numObstacles, areaNum, insideObstacle; + static obstacle_t obstacles[MAX_OBSTACLES]; + idBounds clipBounds; + idBounds bounds; + pathNode_t *root; + bool pathToGoalExists; + + path.seekPos = seekPos; + path.firstObstacle = NULL; + path.startPosOutsideObstacles = startPos; + path.startPosObstacle = NULL; + path.seekPosOutsideObstacles = seekPos; + path.seekPosObstacle = NULL; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.Clear(); + // RAVEN END + + if ( !aas ) { + return true; + } + idActor* owner = dynamic_cast(physics->GetSelf()); + + + bounds[1] = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -bounds[1]; + bounds[1].z = 32.0f; + + // get the AAS area number and a valid point inside that area + areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles ); + + // get all the nearby obstacles + numObstacles = GetObstacles( owner, physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds ); + + // get a source position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.startPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.startPosObstacle); + // RAVEN END + } + + // get a goal position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.seekPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.seekPosObstacle); + // RAVEN END + } + + // if start and destination are pushed to the same point, we don't have a path around the obstacle + if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) { + if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) { + return false; + } + } + + // build a path tree + root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path ); + + // draw the path tree + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + DrawPathTree( root, physics->GetOrigin().z ); + } + + // prune the tree + PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() ); + + // find the optimal path + pathToGoalExists = FindOptimalPath( owner, root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos ); + + // free the tree + FreePathTree_r( root ); + + return pathToGoalExists; +} + +/* +============ +idAI::FreeObstacleAvoidanceNodes +============ +*/ +void idAI::FreeObstacleAvoidanceNodes( void ) { + pathNodeAllocator.Shutdown(); +} + + +/* +=============================================================================== + + Path Prediction + + Uses the AAS to quickly and accurately predict a path for a certain + period of time based on an initial position and velocity. + +=============================================================================== +*/ + +const float OVERCLIP = 1.001f; +const int MAX_FRAME_SLIDE = 5; + +typedef struct pathTrace_s { + float fraction; + idVec3 endPos; + idVec3 normal; + const idEntity * blockingEntity; +} pathTrace_t; + +/* +============ +PathTrace + + Returns true if a stop event was triggered. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Adding ignoreent parm +bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path, const idEntity *ignore = NULL ) { +// RAVEN BEGIN + trace_t clipTrace; + aasTrace_t aasTrace; + + memset( &trace, 0, sizeof( trace ) ); + + if ( !aas || !aas->GetSettings() ) { +// RAVEN BEGIN +// nmckenzie: Added ignore ent +// ddynerman: multiple clip worlds + gameLocal.Translation( ent, clipTrace, start, end, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + // NOTE: could do (expensive) ledge detection here for when there is no AAS file + + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } else { + aasTrace.getOutOfSolid = true; + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + aasTrace.flags |= AREA_LEDGE; + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + aasTrace.travelFlags |= TFL_INVALID; + } + + aas->Trace( aasTrace, start, end ); + +// RAVEN BEGIN +// nmckenzie: Added ignore ent. + gameLocal.TranslationEntities( ent, clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + if ( clipTrace.fraction >= 1.0f ) { + + trace.fraction = aasTrace.fraction; + trace.endPos = aasTrace.endpos; + trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal(); + trace.blockingEntity = gameLocal.world; + + if ( aasTrace.fraction < 1.0f ) { + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_LEDGE_AREA; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { // PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_OBSTACLE; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) {// PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + } + } else { + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } + } + + if ( trace.fraction >= 1.0f ) { + trace.blockingEntity = NULL; + } + + return false; +} + +/* +============ +idAI::PredictPath + + Can also be used when there is no AAS file available however ledges are not detected. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Added ignore ent parm. +bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore ) { +// RAVEN END + int i, j, step, numFrames, curFrameTime; + idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart; + idVec3 gravity, gravityDir, invGravityDir; + float maxStepHeight, minFloorCos; + pathTrace_t trace; + + if ( aas && aas->GetSettings() ) { + gravity = aas->GetSettings()->gravity; + gravityDir = aas->GetSettings()->gravityDir; + invGravityDir = aas->GetSettings()->invGravityDir; + maxStepHeight = aas->GetSettings()->maxStepHeight; + minFloorCos = aas->GetSettings()->minFloorCos; + } else { + gravity = DEFAULT_GRAVITY_VEC3; + gravityDir = idVec3( 0, 0, -1 ); + invGravityDir = idVec3( 0, 0, 1 ); + maxStepHeight = 14.0f; + minFloorCos = 0.7f; + } + + path.endPos = start; + path.endVelocity = velocity; + path.endNormal.Zero(); + path.endEvent = 0; + path.endTime = 0; + path.blockingEntity = NULL; + + curStart = start; + curVelocity = velocity; + + numFrames = ( totalTime + frameTime - 1 ) / frameTime; + curFrameTime = frameTime; + for ( i = 0; i < numFrames; i++ ) { + + if ( i == numFrames-1 ) { + curFrameTime = totalTime - i * curFrameTime; + } + + delta = curVelocity * curFrameTime * 0.001f; + + path.endVelocity = curVelocity; + path.endTime = i * frameTime; + + // allow sliding along a few surfaces per frame + for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) { + + idVec3 lineStart = curStart; + + // allow stepping up three times per frame + for ( step = 0; step < 3; step++ ) { + + curEnd = curStart + delta; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path, ignore ) ) { +// RAVEN END + return true; + } + + if ( step ) { + + // step down at end point + tmpStart = trace.endPos; + curEnd = tmpStart - stepUp; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + + // if not moved any further than without stepping up, or if not on a floor surface + if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f || + ( trace.normal * invGravityDir ) < minFloorCos ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = lastEnd; + path.endEvent = SE_BLOCKED; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd ); + } + + return true; + } + + curStart = lastEnd; + break; + } + } + + path.endNormal = trace.normal; + path.blockingEntity = trace.blockingEntity; + + // if the trace is not blocked or blocked by a floor surface + if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) { + curStart = trace.endPos; + break; + } + + // save last result + lastEnd = trace.endPos; + + // step up + stepUp = invGravityDir * maxStepHeight; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + stepUp *= trace.fraction; + curStart = trace.endPos; + } + + if ( ai_debugMove.GetBool() ) {//PredictPath + gameRenderWorld->DebugLine( colorRed, lineStart, curStart ); + } + + if ( trace.fraction >= 1.0f ) { + break; + } + + delta.ProjectOntoPlane( trace.normal, OVERCLIP ); + curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP ); + + if ( stopEvent & SE_BLOCKED ) { + // if going backwards + if ( (curVelocity - gravityDir * curVelocity * gravityDir ) * + (velocity - gravityDir * velocity * gravityDir) < 0.0f ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + + return true; + } + } + } + + if ( j >= MAX_FRAME_SLIDE ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + return true; + } + } + + // add gravity + curVelocity += gravity * frameTime * 0.001f; + } + + path.endTime = totalTime; + path.endVelocity = curVelocity; + path.endPos = curStart; + path.endEvent = 0; + + return false; +} + + +/* +=============================================================================== + + Trajectory Prediction + + Finds the best collision free trajectory for a clip model based on an + initial position, target position and speed. + +=============================================================================== +*/ + +/* +===================== +Ballistics + + get the ideal aim pitch angle in order to hit the target + also get the time it takes for the projectile to arrive at the target +===================== +*/ +typedef struct ballistics_s { + float angle; // angle in degrees in the range [-180, 180] + float time; // time it takes before the projectile arrives +} ballistics_t; + +static int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) { + int n, i; + float x, y, a, b, c, d, sqrtd, inva, p[2]; + + x = ( end.ToVec2() - start.ToVec2() ).Length(); + y = end[2] - start[2]; + + a = 4.0f * y * y + 4.0f * x * x; + b = -4.0f * speed * speed - 4.0f * y * gravity; + c = gravity * gravity; + + d = b * b - 4.0f * a * c; + if ( d <= 0.0f || a == 0.0f ) { + return 0; + } + sqrtd = idMath::Sqrt( d ); + inva = 0.5f / a; + p[0] = ( - b + sqrtd ) * inva; + p[1] = ( - b - sqrtd ) * inva; + n = 0; + for ( i = 0; i < 2; i++ ) { + if ( p[i] <= 0.0f ) { + continue; + } + d = idMath::Sqrt( p[i] ); + bal[n].angle = idMath::ATan( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x ); + bal[n].time = x / ( idMath::Cos( bal[n].angle ) * speed ); + bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) ); + n++; + } + + return n; +} + +/* +===================== +HeightForTrajectory + +Returns the maximum hieght of a given trajectory +===================== +*/ +// RAVEN BEGIN +// nmckenzie: Removing static for this one. +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) { +// RAVEN END + float maxHeight, t; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + + return maxHeight; +} + +/* +===================== +idAI::TestTrajectory +===================== +*/ +bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) { + int i, numSegments; + float maxHeight, t, t2; + idVec3 points[5]; + trace_t trace; + bool result; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + // time it takes to fall from the top to the end height + t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) ); + + // start of parabolic + points[0] = start; + + if ( t < time ) { + numSegments = 4; + // point in the middle between top and start + t2 = ( time - t ) * 0.5f; + points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // top of parabolic + t2 = time - t; + points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // point in the middel between top and end + t2 = time - t * 0.5f; + points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } else { + numSegments = 2; + // point halfway through + t2 = time * 0.5f; + points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f; + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } + + // end of parabolic + points[numSegments] = end; + + if ( drawtime ) { + for ( i = 0; i < numSegments; i++ ) { + gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime ); + } + } + + // make sure projectile doesn't go higher than we want it to go + for ( i = 0; i < numSegments; i++ ) { + if ( points[i].z > max_height ) { + // goes higher than we want to allow + return false; + } + } + + result = true; + for ( i = 0; i < numSegments; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) { + result = true; + } else { + result = false; + } + break; + } + } + + if ( drawtime ) { + if ( clip ) { + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime ); + } else { + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + } + + return result; +} + +/* +===================== +idAI::PredictTrajectory + + returns true if there is a collision free trajectory for the clip model + aimDir is set to the ideal aim direction in order to hit the target +===================== +*/ +bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) { + int n, i, j; + float zVel, a, t, pitch, s, c; + trace_t trace; + ballistics_t ballistics[2]; + idVec3 dir[2]; + idVec3 velocity; + idVec3 lastPos, pos; + + assert( targetEntity ); + + // check if the projectile starts inside the target + if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) { + aimDir = target - firePos; + aimDir.Normalize(); + return true; + } + + // if no velocity or the projectile is not affected by gravity + if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) { + + aimDir = target - firePos; + aimDir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, firePos, target, clip, mat3_identity, clipmask, ignore ); +// RAVEN END + + if ( drawtime ) { + gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime ); + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + + return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ); + } + + n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics ); + if ( n == 0 ) { + // there is no valid trajectory + aimDir = target - firePos; + aimDir.Normalize(); + return false; + } + + // make sure the first angle is the smallest + if ( n == 2 ) { + if ( ballistics[1].angle < ballistics[0].angle ) { + a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a; + t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t; + } + } + + // test if there is a collision free trajectory + for ( i = 0; i < n; i++ ) { + pitch = DEG2RAD( ballistics[i].angle ); + idMath::SinCos( pitch, s, c ); + dir[i] = target - firePos; + dir[i].z = 0.0f; + dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() ); + dir[i].z = s; + + zVel = projectileSpeed * dir[i].z; + + if ( ai_debugTrajectory.GetBool() ) { + t = ballistics[i].time / 100.0f; + velocity = dir[i] * projectileSpeed; + lastPos = firePos; + pos = firePos; + for ( j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; +// RAVEN BEGIN +// nmckenzie: Added time parm to debugline so I can actually see it. + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, drawtime ); +// RAVEN END + lastPos = pos; + } + } + + if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) { + aimDir = dir[i]; + return true; + } + } + + aimDir = dir[0]; + + // there is no collision free trajectory + return false; +} diff --git a/source/mpgame/ai/Monster_Berserker.cpp b/source/mpgame/ai/Monster_Berserker.cpp new file mode 100644 index 0000000..b9e6007 --- /dev/null +++ b/source/mpgame/ai/Monster_Berserker.cpp @@ -0,0 +1,465 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterBerserker : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBerserker ); + + rvMonsterBerserker ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + int FilterTactical ( int availableTactical ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + int standingMeleeNoAttackTime; + int painConsecutive; + + // Actions + rvAIAction actionPopupAttack; + rvAIAction actionChargeAttack; + + bool Berz_CanHitEnemyFromAnim ( int animNum ); + bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ChargeAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_ChargeGroundImpact ( const stateParms_t& parms ); + stateResult_t Frame_DoBlastAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterBerserker ); +}; + +CLASS_DECLARATION( idAI, rvMonsterBerserker ) +END_CLASS + +/* +================ +rvMonsterBerserker::rvMonsterBerserker +================ +*/ +rvMonsterBerserker::rvMonsterBerserker ( ) { + painConsecutive = 0; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterBerserker::Spawn +================ +*/ +void rvMonsterBerserker::Spawn ( void ) { + actionPopupAttack.Init ( spawnArgs, "action_popupAttack", NULL, AIACTIONF_ATTACK ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", "Torso_ChargeAttack", AIACTIONF_ATTACK ); + PlayEffect( "fx_ambient_electricity", animator.GetJointHandle( "r_Lowerarm_Real" ), true ); + PlayEffect( "fx_ambient_electricity_mace", animator.GetJointHandle( "chain9" ), true ); +} + +/* +================ +rvMonsterBerserker::Save +================ +*/ +void rvMonsterBerserker::Save ( idSaveGame *savefile ) const { + actionPopupAttack.Save ( savefile ); + actionChargeAttack.Save ( savefile ); + savefile->WriteInt( painConsecutive ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::Restore +================ +*/ +void rvMonsterBerserker::Restore ( idRestoreGame *savefile ) { + actionPopupAttack.Restore ( savefile ); + actionChargeAttack.Restore ( savefile ); + savefile->ReadInt( painConsecutive ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( GetEnemy() && GetEnemy()->GetPhysics()->GetOrigin().z > GetPhysics()->GetOrigin().z + 24.0f ) + {//this is a ground attack and enemy is above me, so don't even try it, stupid! + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +============ +rvMonsterBerserker::CanHitEnemyFromAnim +============ +*/ +bool rvMonsterBerserker::Berz_CanHitEnemyFromAnim( int animNum ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin(); + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +rvMonsterBerserker::CheckAction_RangedAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !Berz_CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterBerserker::CheckActions +================ +*/ +bool rvMonsterBerserker::CheckActions ( void ) { + // Pop-up attack is a forward moving melee attack that throws the enemy up in the air + if ( PerformAction ( &actionPopupAttack, (checkAction_t)&idAI::CheckAction_LeapAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + // Charge attack is where the berserker will charge up his spike and slam it in to the ground + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterBerserker::CheckAction_ChargeAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&rvMonsterBerserker::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} +/* +================ +rvMonsterBerserker::FilterTactical +================ +*/ +int rvMonsterBerserker::FilterTactical ( int availableTactical ) { + if ( move.moveCommand == MOVE_TO_ENEMY && move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else if ( combat.tacticalCurrent != AITACTICAL_RANGED + && combat.tacticalCurrent != AITACTICAL_MELEE + && (combat.tacticalMaskAvailable&AITACTICAL_RANGED_BIT) ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else { + availableTactical &= ~AITACTICAL_RANGED_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterBerserker::OnTacticalChange + +Enable/Disable the ranged attack based on whether the berzerker needs it +================ +*/ +void rvMonsterBerserker::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + //once you've gone into melee once, it's okay to try ranged attacks later + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BIT; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterBerserker::GetDebugInfo +===================== +*/ +void rvMonsterBerserker::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_popupAttack", aiActionStatusString[actionPopupAttack.status], userData ); + proc ( "idAI", "action_chargeAttack", aiActionStatusString[actionChargeAttack.status], userData ); +} + +/* +================ +rvMonsterBerserker::Pain +================ +*/ +bool rvMonsterBerserker::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterBerserker::CheckPainActions +================ +*/ +bool rvMonsterBerserker::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBerserker ) + STATE ( "State_Killed", rvMonsterBerserker::State_Killed ) + + STATE ( "Torso_ChargeAttack", rvMonsterBerserker::State_Torso_ChargeAttack ) + STATE ( "Torso_Pain", rvMonsterBerserker::State_Torso_Pain ) + + STATE ( "Frame_ChargeGroundImpact", rvMonsterBerserker::Frame_ChargeGroundImpact ) + STATE ( "Frame_DoBlastAttack", rvMonsterBerserker::Frame_DoBlastAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterBerserker::State_Torso_ChargeAttack +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_ChargeAttack ( const stateParms_t& parms ) { + enum { + TORSO_CHARGEATTACK_INIT, + TORSO_CHARGEATTACK_WAIT, + TORSO_CHARGEATTACK_RECOVER, + TORSO_CHARGEATTACK_RECOVERWAIT + }; + + switch ( parms.stage ) { + // Start the charge attack animation + case TORSO_CHARGEATTACK_INIT: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the ground strike + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_WAIT ); + + // Wait for charge attack animation to finish + case TORSO_CHARGEATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVER ); + } + return SRESULT_WAIT; + + // Play recover animation + case TORSO_CHARGEATTACK_RECOVER: + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike_recover", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVERWAIT ); + + // Wait for recover animation to finish + case TORSO_CHARGEATTACK_RECOVERWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBerserker::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_Pain ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterBerserker::State_Killed +================ +*/ +stateResult_t rvMonsterBerserker::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + StopEffect ( "fx_ambient_electricity" ); + StopEffect ( "fx_ambient_electricity_mace" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterBerserker::Frame_ChargeGroundImpact +================ +*/ +stateResult_t rvMonsterBerserker::Frame_ChargeGroundImpact ( const stateParms_t& parms ) { + idVec3 start; + idVec3 end; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform ( animator.GetJointHandle ( "R_lowerArm_Real" ), gameLocal.time, start, axis ); + + end = start; + end.z -= 128; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + + gameLocal.PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_ground_impact" ), tr.endpos, idVec3(0,0,1).ToMat3() ); + + return SRESULT_OK; +} + +/* +================ +rvMonsterBerserker::Frame_DoBlastAttack +================ +*/ +stateResult_t rvMonsterBerserker::Frame_DoBlastAttack ( const stateParms_t& parms ) { + float i; + idVec3 start; + idMat3 axis; + idAngles angles ( 0.0f, move.current_yaw, 0.0f ); + const idDict* blastDict; + + blastDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_spike" ), false ); + if ( !blastDict ) { + gameLocal.Error ( "missing projectile on spike attack for AI entity '%s'", GetName ( ) ) ; + return SRESULT_OK; + } + + GetJointWorldTransform ( animator.GetJointHandle ( "end_spike" ), gameLocal.time, start, axis ); + + for( i = 0; i < 32; i++ ) { + angles.yaw += (360.0f / 32.0f); + AttackProjectile ( blastDict, start, angles ); + } + + return SRESULT_OK; +} diff --git a/source/mpgame/ai/Monster_BossBuddy.cpp b/source/mpgame/ai/Monster_BossBuddy.cpp new file mode 100644 index 0000000..1f8e26c --- /dev/null +++ b/source/mpgame/ai/Monster_BossBuddy.cpp @@ -0,0 +1,662 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +//------------------------------------------------------------ +class rvMonsterBossBuddy : public idAI +//------------------------------------------------------------ +{ +public: + + CLASS_PROTOTYPE( rvMonsterBossBuddy ); + + rvMonsterBossBuddy( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + void Think ( void ); + bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void OnWakeUp ( void ); + + // Add some dynamic externals for debugging + void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + bool CheckActions ( void ); +// void PerformAction ( const char* stateName, int blendFrames, bool noPain ); +// bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + void AdjustShieldState ( bool becomeShielded ); + void ReduceShields ( int amount ); + + int mShots; + int mShields; + int mMaxShields; + int mLastDamageTime; + int mShieldsLastFor; // read from def file, shouldn't need to be saved. + + bool mIsShielded; + bool mRequestedZoneMove; + bool mRequestedRecharge; + + //bool mCanIdle; + //bool mChaseMode; + +private: + + rvAIAction mActionRocketAttack; + rvAIAction mActionSlashMoveAttack; + rvAIAction mActionLightningAttack; + rvAIAction mActionDarkMatterAttack; + rvAIAction mActionMeleeMoveAttack; + rvAIAction mActionMeleeAttack; + + rvScriptFuncUtility mRequestRecharge; // script to run when a projectile is launched + rvScriptFuncUtility mRequestZoneMove; // script to run when he should move to the next zone + + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SlashAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + void Event_RechargeShields( float amount ); + + CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy ); +}; + +#define BOSS_BUDDY_MAX_SHIELDS 8500 +//------------------------------------------------------------ +// rvMonsterBossBuddy::rvMonsterBossBuddy +//------------------------------------------------------------ +rvMonsterBossBuddy::rvMonsterBossBuddy( void ) +{ + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; + mShields = mMaxShields; + mLastDamageTime = 0; + mIsShielded = false; + mRequestedZoneMove = false; + mRequestedRecharge = false; +// mCanIdle = false; +// mChaseMode = true; +} + +void rvMonsterBossBuddy::InitSpawnArgsVariables ( void ) +{ + mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f); + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Spawn +//------------------------------------------------------------ +void rvMonsterBossBuddy::Spawn( void ) +{ + mActionRocketAttack.Init( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + mActionLightningAttack.Init( spawnArgs,"action_lightningAttack", NULL, AIACTIONF_ATTACK ); + mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeMoveAttack.Init( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionSlashMoveAttack.Init( spawnArgs, "action_slashMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + mShields = mMaxShields; + + const char *func; + if ( spawnArgs.GetString( "requestRecharge", "", &func ) ) + { + mRequestRecharge.Init( func ); + } + if ( spawnArgs.GetString( "requestZoneMove", "", &func ) ) + { + mRequestZoneMove.Init( func ); + } + + HideSurface( "models/monsters/bossbuddy/forcefield" ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Save +//------------------------------------------------------------ +void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const +{ + savefile->WriteInt( mShots ); + savefile->WriteInt( mShields ); + savefile->WriteInt( mMaxShields ); + savefile->WriteInt( mLastDamageTime ); + savefile->WriteBool( mIsShielded ); + savefile->WriteBool( mRequestedZoneMove ); + savefile->WriteBool( mRequestedRecharge ); + + mActionRocketAttack.Save( savefile ); + mActionLightningAttack.Save( savefile ); + mActionDarkMatterAttack.Save( savefile ); + mActionMeleeMoveAttack.Save( savefile ); + mActionMeleeAttack.Save( savefile ); + mActionSlashMoveAttack.Save( savefile ); + + mRequestRecharge.Save( savefile ); + mRequestZoneMove.Save( savefile ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Restore +//------------------------------------------------------------ +void rvMonsterBossBuddy::Restore( idRestoreGame *savefile ) +{ + savefile->ReadInt( mShots ); + savefile->ReadInt( mShields ); + savefile->ReadInt( mMaxShields ); + savefile->ReadInt( mLastDamageTime ); + savefile->ReadBool( mIsShielded ); + savefile->ReadBool( mRequestedZoneMove ); + savefile->ReadBool( mRequestedRecharge ); + + mActionRocketAttack.Restore( savefile ); + mActionLightningAttack.Restore( savefile ); + mActionDarkMatterAttack.Restore( savefile ); + mActionMeleeMoveAttack.Restore( savefile ); + mActionMeleeAttack.Restore( savefile ); + mActionSlashMoveAttack.Restore( savefile ); + + mRequestRecharge.Restore( savefile ); + mRequestZoneMove.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +//------------------------------------------------------------ +// rvMonsterBerserker::GetDebugInfo +//------------------------------------------------------------ +void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData ) +{ + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_darkMatterAttack", aiActionStatusString[mActionDarkMatterAttack.status], userData ); + proc ( "idAI", "action_rocketAttack", aiActionStatusString[mActionRocketAttack.status], userData ); + proc ( "idAI", "action_meleeMoveAttack", aiActionStatusString[mActionMeleeMoveAttack.status], userData ); + proc ( "idAI", "action_lightningAttack", aiActionStatusString[mActionLightningAttack.status], userData ); +} + +//-------------------------------------------------------------- +// Custom Script Events +//-------------------------------------------------------------- +const idEventDef EV_RechargeShields( "rechargeShields", "f", 'f' ); + +CLASS_DECLARATION( idAI, rvMonsterBossBuddy ) + EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields ) +END_CLASS + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Event_RechargeShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::Event_RechargeShields( float amount ) +{ + mShields += (int)amount; + + if ( mShields >= mMaxShields ) + { + // charge is done + mShields = mMaxShields; + idThread::ReturnInt(0); + + // reset request states + mRequestedRecharge = false; + mRequestedZoneMove = false; + + // shield warning no longer neede for now + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn"); + } + else + { + // still charging + idThread::ReturnInt(1); + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::ReduceShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::ReduceShields( int amount ) +{ + mShields -= amount; + + // if no mShields left... or the last time we took damage was more than 8 seconds ago + if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time ) + { + //....remove the shielding + AdjustShieldState( false ); + } + + if (mShields < 1000) + { + if (!mRequestedRecharge ) + { + // entering a dangerous state! Get to the recharge station, fast! + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn"); + ExecScriptFunction( mRequestRecharge ); + mRequestedRecharge = true; + } + } + else if (mShields < 4000) + { + if ( !mRequestedZoneMove ) + { + // Getting low, so move him close to the next zone so he can be ready to recharge + ExecScriptFunction( mRequestZoneMove ); + mRequestedZoneMove = true; + } + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::AdjustShieldState +//------------------------------------------------------------ +void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded ) +{ + // only do the work for adjusting the state when it doesn't match our current state + if ( !mIsShielded && becomeShielded ) + { + // Activate Shields! + ShowSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" ); + } + else if ( mIsShielded && !becomeShielded ) + { + // Deactivate Shields! + HideSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL ); +// gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" ); + } + mIsShielded = becomeShielded; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Think() +{ + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) + { + // run simple shielding logic when we have them active + if ( mIsShielded ) + { + ReduceShields( 1 ); + + // if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields + if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time ) + { + AdjustShieldState( false ); + } + } + + // update shield bar + idUserInterface *hud = gameLocal.GetLocalPlayer()->hud; + if ( hud ) + { + float percent = ((float)mShields/mMaxShields); + + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } + } + + if ( move.obstacle.GetEntity() ) + { + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack ); + } + + idAI::Think(); +} +/* +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain ) +{ + // Allow movement in actions + move.fl.allowAnimMove = true; + + if ( mChaseMode ) + { + return; + } + + // Start the action + SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( mCanIdle ) + { + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState( "Wait_ActionNoPain" ); + OnStartAction( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) +{ + if ( mChaseMode ) + { + return false; + } + + return idAI::PerformAction( action, condition ,timer ); +} +*/ +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + // get damage amount so we can decay the shields and check for ignoreShields + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) + { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + // NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def, + // we don't run shielding logic + bool directDamage = damageDef->GetBool( "ignoreShields" ); + + int loc = location; + if ( directDamage ) + { + // Lame, I know, but hack the location + loc = INVALID_JOINT; + } + else if ( attacker == this ) + { + // can't damage self + return; + } + + float scale = 1; + + // Shields will activate for a set amount of time when damage is being taken + mLastDamageTime = gameLocal.time; + + // if shields are active, we should try to 'eat' them before directing damage to the BB + if ( mIsShielded && !directDamage ) + { + // BB is resistant to any kind of splash damage when the shields are up + if ( loc <= INVALID_JOINT ) + { + // damage must have been done by splash damage + return; + } + + int damage = damageDef->GetInt( "damage" ); + ReduceShields( damage * 8 ); + + // Shielding dramatically reduces actual damage done to BB + scale = 0.1f; + } + else if ( mShields > 0 ) // not currently shielded...does he have shields to use? + { + // Yep, so turn them on + AdjustShieldState( true ); + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Pain +//------------------------------------------------------------ +bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) +{ + // immune to small damage. Is this safe to do? + if ( damage > 5 ) + { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) ); + effect->Play( gameLocal.time, false ); + + return idAI::Pain( inflictor, attacker, damage, dir, location ); + } + return false; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CheckActions +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CheckActions( void ) +{ + // If not moving, try turning in place +/* if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnLeft90", 4, true ); + return true; + } + } +*/ + if ( PerformAction( &mActionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack )) + { + return true; + } + + if ( PerformAction( &mActionDarkMatterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionLightningAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack )) + { + return true; + } + + return idAI::CheckActions( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CanTurn +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CanTurn( void ) const +{ +/* if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +*/ + return idAI::CanTurn ( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::OnWakeUp +//------------------------------------------------------------ +void rvMonsterBossBuddy::OnWakeUp( void ) +{ + mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + idAI::OnWakeUp( ); +} + +//------------------------------------------------------------ +// States +//------------------------------------------------------------ + +CLASS_STATES_DECLARATION( rvMonsterBossBuddy ) + STATE( "Torso_RocketAttack", rvMonsterBossBuddy::State_Torso_RocketAttack ) + STATE( "Torso_SlashAttack", rvMonsterBossBuddy::State_Torso_SlashAttack ) + STATE( "Torso_TurnRight90", rvMonsterBossBuddy::State_Torso_TurnRight90 ) + STATE( "Torso_TurnLeft90", rvMonsterBossBuddy::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_RocketAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2start", parms.blendFrames ); + mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2loop2", 0 ); + return SRESULT_STAGE( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + if ( --mShots <= 0 || // exhausted mShots? .. or + (!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) || // ... player is no longer visible .. or + ( enemy.ent && DistanceTo(enemy.ent)<256 ) ) // ... player is so close, we prolly want to do a melee attack + { + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 0 ); + return SRESULT_STAGE( STAGE_WAITEND ); + } + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_SlashAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT_FIRST_SWIPE, + STAGE_WAIT_FINISH + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE ); + + case STAGE_WAIT_FIRST_SWIPE: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnRight90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnRight90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnLeft90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnLeft90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_BossMakron.cpp b/source/mpgame/ai/Monster_BossMakron.cpp new file mode 100644 index 0000000..8af9b3f --- /dev/null +++ b/source/mpgame/ai/Monster_BossMakron.cpp @@ -0,0 +1,2347 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterBossMakron : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBossMakron ); + + rvMonsterBossMakron ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + + bool CanTurn ( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BuildActionArray ( void ); + + //void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + +protected: + + bool CheckActions ( void ); + bool CheckTurnActions ( void ); + + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + //The Makron behaves differently, and we don't always need him sinking back into an idle after every attack + //This version is actually overridden + void PerformAction ( const char* stateName, int blendFrames, bool noPain ); + + //This just calls the parent version + bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + //This performs a patterned action + void PerformPatternedAction ( ); + + //For debug, may not need this. + void OnStopAction ( void ); + + //stops all effects + void StopAllEffects ( void ); + + //for the cannon fire + int shots; + + //determines if the Makron will enter idles between attacks + bool noIdle; + + //changes Makron from patterned, script controlled to normal AI + bool patternedMode; + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + float facingIdealYaw; + float facingTime; + float turnRate; + + //flag to indicate whether or not the Makron is in the corner of the map when he takes an action. + bool flagCornerState; + + //tallies up the number of consecutive actions the Makron has taken in the corner of the map. + //If the tally gets too high, teleport him to the map center. + int cornerCount; + + //in the state of teleporting + bool flagTeleporting; + + //for flying mode ---------------------------------------------------------------------------------------- + void BeginSeparation ( void ); + void CompleteSeparation ( void ); + + bool flagFlyingMode; + bool flagFakeDeath; + int flagUndying; + + rvClientEffectPtr effectHover; + jointHandle_t jointHoverEffect; + + //for the stomp attack ---------------------------------------------------------------------------------------- + float stompMaxRadius; + float stompWidth; + float stompSpeed; + float stompRadius; + + //for the lightning bolt sweep attacks ------------------------------------------------------------------- + void MaintainBoltSweep ( void ); + void InitBoltSweep ( idVec3 idealTarget ); + void LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ); + void StopAllBoltEffects ( void ); + + rvClientEffectPtr leftBoltEffect; + rvClientEffectPtr rightBoltEffect; + rvClientEffectPtr leftBoltImpact; + rvClientEffectPtr rightBoltImpact; + rvClientEffectPtr boltMuzzleFlash; + + idStr boltEffectName; + + idVec3 leftBoltVector; + idVec3 rightBoltVector; + idVec3 boltVectorMin; + idVec3 boltVectorMax; + idMat3 boltAimMatrix; + + bool flagSweepDone; + + //these are grabbed from the def file + float boltSweepTime; + float boltWaitTime; + + //used internally + float boltSweepStartTime; + float boltTime; + float boltNextStateTime; + + int stateBoltSweep; + + jointHandle_t jointLightningBolt; + + //end lightning bolt sweep attacks ------------------------------------------------------------------- + //changed by script event, allows for grenades to spawn baddies. + bool flagAllowSpawns; + + enum { MAKRON_ACTION_DMG, + MAKRON_ACTION_MELEE, + MAKRON_ACTION_CANNON, + MAKRON_ACTION_CANNON_SWEEP, + MAKRON_ACTION_GRENADE, + MAKRON_ACTION_LIGHTNING_1, + MAKRON_ACTION_LIGHTNING_2, + MAKRON_ACTION_STOMP, + MAKRON_ACTION_HEAL, + MAKRON_ACTION_CHARGE, + MAKRON_ACTION_KILLPLAYER, + MAKRON_ACTION_COUNT, }; + + rvAIAction actionDMGAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionCannonAttack; + rvAIAction actionCannonSweepAttack; + rvAIAction actionGrenadeAttack; + rvAIAction actionLightningPattern1Attack; + rvAIAction actionLightningPattern2Attack; + rvAIAction actionStompAttack; + rvAIAction actionHeal; + rvAIAction actionCharge; + rvAIAction actionKillPlayer; + + rvAIAction * actionArray[ MAKRON_ACTION_COUNT ]; + + int actionPatterned; + + //when the Makron is low on health, he'll use this. + rvScriptFuncUtility scriptRecharge; + rvScriptFuncUtility scriptTeleport; + + stateResult_t State_Torso_DMGAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_GrenadeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MeleeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonSweepAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning1Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning2Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_StompAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Recharge ( const stateParms_t& parms ); + stateResult_t State_Torso_Charge ( const stateParms_t& parms ); + stateResult_t State_Torso_KillPlayer ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + + //used when killed + stateResult_t State_Killed ( const stateParms_t& parms ); + + //walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + //flying turn anims + stateResult_t State_Torso_RotateToAngle ( const stateParms_t& parms ); + + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + + //like jesus, except with robot parts + stateResult_t State_Torso_FirstDeath ( const stateParms_t& parms ); + stateResult_t State_Torso_Resurrection ( const stateParms_t& parms ); + + //These two frames activate and deactivate the second lightning sweep + stateResult_t Frame_BeginLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_EndLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_StompAttack ( const stateParms_t& parms ); + stateResult_t Frame_Teleport ( const stateParms_t& parms ); + + + void Event_AllowMoreSpawns ( void ); + void Event_SetNextAction ( const char* actionString ); + void Event_EnablePatternMode ( void ); + void Event_DisablePatternMode ( void ); + void Event_StompAttack ( idVec3 &origin ); + void Event_Separate ( void ); + void Event_FlyingRotate ( idVec3 &vecOrg ); + void Event_ToggleCornerState ( float f ); + + + CLASS_STATES_PROTOTYPE ( rvMonsterBossMakron ); +}; + +const idEventDef EV_AllowMoreSpawns( "allowMoreSpawns" ); +const idEventDef EV_SetNextAction( "setNextAction", "s", 'f' ); +const idEventDef EV_EnablePatternMode( "enablePatternMode" ); +const idEventDef EV_DisablePatternMode( "disablePatternMode" ); +const idEventDef EV_StompAttack( "stompAttack", "v" ); +const idEventDef EV_Separate( "separate" ); +const idEventDef EV_FlyingRotate( "flyingRotate", "v"); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef EV_ToggleCornerState( "toggleCornerState", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterBossMakron ) + EVENT( EV_AllowMoreSpawns, rvMonsterBossMakron::Event_AllowMoreSpawns ) + EVENT( EV_SetNextAction, rvMonsterBossMakron::Event_SetNextAction ) + EVENT( EV_EnablePatternMode, rvMonsterBossMakron::Event_EnablePatternMode ) + EVENT( EV_DisablePatternMode, rvMonsterBossMakron::Event_DisablePatternMode ) + EVENT( EV_StompAttack, rvMonsterBossMakron::Event_StompAttack ) + EVENT( EV_Separate, rvMonsterBossMakron::Event_Separate ) + EVENT( EV_FlyingRotate, rvMonsterBossMakron::Event_FlyingRotate ) + EVENT( AI_ScriptedFace, rvMonsterBossMakron::ScriptedFace ) + EVENT( EV_ToggleCornerState, rvMonsterBossMakron::Event_ToggleCornerState ) +END_CLASS + +/* +================ +rvMonsterBossMakron::rvMonsterBossMakron +================ +*/ +rvMonsterBossMakron::rvMonsterBossMakron ( void ) { + + //set up this action array + + + +} + +/* +================ +rvMonsterBossMakron::BuildActionArray ( void ) +================ +*/ +void rvMonsterBossMakron::BuildActionArray ( void ) { + + actionArray[ MAKRON_ACTION_DMG ] = &actionDMGAttack; + actionArray[ MAKRON_ACTION_MELEE ] = &actionMeleeAttack; + actionArray[ MAKRON_ACTION_CANNON ] = &actionCannonAttack; + actionArray[ MAKRON_ACTION_CANNON_SWEEP ] = &actionCannonSweepAttack; + actionArray[ MAKRON_ACTION_GRENADE ] = &actionGrenadeAttack; + actionArray[ MAKRON_ACTION_LIGHTNING_1 ] = &actionLightningPattern1Attack; + actionArray[ MAKRON_ACTION_LIGHTNING_2 ] = &actionLightningPattern2Attack; + actionArray[ MAKRON_ACTION_STOMP ] = &actionStompAttack; + actionArray[ MAKRON_ACTION_HEAL ] = &actionHeal; + actionArray[ MAKRON_ACTION_CHARGE ] = &actionCharge; + actionArray[ MAKRON_ACTION_KILLPLAYER ] = &actionKillPlayer; + +} +/* +================ +rvMonsterBossMakron::Save +================ +*/ +void rvMonsterBossMakron::Save ( idSaveGame *savefile ) const { + + savefile->WriteInt( shots); + + savefile->WriteFloat( facingIdealYaw); + savefile->WriteFloat( facingTime); + + savefile->WriteBool( noIdle); + + savefile->WriteBool( patternedMode); + + savefile->WriteBool( flagFlyingMode); + savefile->WriteBool( flagFakeDeath); + savefile->WriteInt( flagUndying ); + + effectHover.Save( savefile ); + savefile->WriteJoint( jointHoverEffect ); // cnicholson: added unsaved var + + savefile->WriteFloat( stompRadius); + + leftBoltEffect.Save( savefile ); + rightBoltEffect.Save( savefile ); + leftBoltImpact.Save( savefile ); + rightBoltImpact.Save( savefile ); + boltMuzzleFlash.Save( savefile ); + + savefile->WriteString( boltEffectName); + + savefile->WriteVec3( leftBoltVector); + savefile->WriteVec3( rightBoltVector); + savefile->WriteVec3( boltVectorMin); + savefile->WriteVec3( boltVectorMax); + savefile->WriteMat3( boltAimMatrix); + + savefile->WriteBool( flagSweepDone); + + savefile->WriteFloat( boltSweepTime); + + savefile->WriteFloat( boltSweepStartTime); + savefile->WriteFloat( boltTime); + savefile->WriteFloat( boltNextStateTime); + + savefile->WriteInt( stateBoltSweep); + + savefile->WriteBool( flagAllowSpawns); + + savefile->WriteBool( flagCornerState ); + savefile->WriteInt( cornerCount ); + savefile->WriteBool( flagTeleporting ); + + + actionDMGAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionCannonAttack.Save( savefile ); + actionCannonSweepAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + actionLightningPattern1Attack.Save( savefile ); + actionLightningPattern2Attack.Save( savefile ); + actionStompAttack.Save( savefile ); + actionHeal.Save( savefile ); + actionCharge.Save( savefile ); + actionKillPlayer.Save( savefile ); + + savefile->WriteInt( actionPatterned); + + scriptRecharge.Save( savefile ); + scriptTeleport.Save( savefile ); + // cnicholson: No need to save actionArry, its rebuilt during restore +} + +/* +================ +rvMonsterBossMakron::Restore +================ +*/ +void rvMonsterBossMakron::Restore ( idRestoreGame *savefile ) { + + savefile->ReadInt( shots); + + savefile->ReadFloat( facingIdealYaw); + savefile->ReadFloat( facingTime); + + savefile->ReadBool( noIdle); + + savefile->ReadBool( patternedMode); + + savefile->ReadBool( flagFlyingMode); + savefile->ReadBool( flagFakeDeath); + savefile->ReadInt( flagUndying ); + + effectHover.Restore( savefile ); + savefile->ReadJoint( jointHoverEffect ); // cnicholson: added unrestoed var + + savefile->ReadFloat( stompRadius); + + leftBoltEffect.Restore( savefile ); + rightBoltEffect.Restore( savefile ); + leftBoltImpact.Restore( savefile ); + rightBoltImpact.Restore( savefile ); + boltMuzzleFlash.Restore( savefile ); + + savefile->ReadString( boltEffectName); + + savefile->ReadVec3( leftBoltVector); + savefile->ReadVec3( rightBoltVector); + savefile->ReadVec3( boltVectorMin); + savefile->ReadVec3( boltVectorMax); + savefile->ReadMat3( boltAimMatrix); + + savefile->ReadBool( flagSweepDone); + + savefile->ReadFloat( boltSweepTime); + + savefile->ReadFloat( boltSweepStartTime); + savefile->ReadFloat( boltTime); + savefile->ReadFloat( boltNextStateTime); + + savefile->ReadInt( stateBoltSweep); + + savefile->ReadBool( flagAllowSpawns); + + savefile->ReadBool( flagCornerState ); + savefile->ReadInt( cornerCount ); + savefile->ReadBool( flagTeleporting ); + + + actionDMGAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionCannonAttack.Restore( savefile ); + actionCannonSweepAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + actionLightningPattern1Attack.Restore( savefile ); + actionLightningPattern2Attack.Restore( savefile ); + actionStompAttack.Restore( savefile ); + actionHeal.Restore( savefile ); + actionCharge.Restore( savefile ); + actionKillPlayer.Restore( savefile ); + + + savefile->ReadInt( actionPatterned); + + scriptRecharge.Restore( savefile ); + scriptTeleport.Restore( savefile ); + //reload the action array, this is done in the init section but if we don't do it here our array is bunk. + BuildActionArray(); + InitSpawnArgsVariables(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + + +/* +================ +rvMonsterBossMakron::StopAllEffects +================ +*/ +void rvMonsterBossMakron::StopAllEffects ( void ) { + + StopAllBoltEffects(); + + //stop the over effect + if( effectHover ) { + effectHover.GetEntity()->Stop(); + effectHover = 0; + } + +} + +/* +================ +rvMonsterBossMakron::StopAllBoltEffects +================ +*/ +void rvMonsterBossMakron::StopAllBoltEffects ( void ) { + + if( leftBoltEffect ) { + leftBoltEffect.GetEntity()->Stop(); + leftBoltEffect = 0; + } + + if( rightBoltEffect ) { + rightBoltEffect.GetEntity()->Stop(); + rightBoltEffect = 0; + } + + if( leftBoltImpact ) { + leftBoltImpact.GetEntity()->Stop(); + leftBoltImpact = 0; + } + + if( rightBoltImpact ) { + rightBoltImpact.GetEntity()->Stop(); + rightBoltImpact = 0; + } + + if( boltMuzzleFlash ) { + boltMuzzleFlash .GetEntity()->Stop(); + boltMuzzleFlash = 0; + } + + +} + +void rvMonsterBossMakron::InitSpawnArgsVariables ( void ) { + //slick! + jointLightningBolt = animator.GetJointHandle ( spawnArgs.GetString ( "joint_lightningBolt", "claw_muzzle" ) ); + + stompMaxRadius = spawnArgs.GetFloat( "stomp_max_range", "1600"); + stompWidth = spawnArgs.GetFloat( "stomp_width", "32"); + stompSpeed = spawnArgs.GetFloat( "stomp_speed", "32"); + + boltWaitTime = spawnArgs.GetFloat( "lightingsweep_wait_time", "1"); + + turnRate = spawnArgs.GetFloat( "fly_turnRate", "180"); +} + +/* +================ +rvMonsterBossMakron::Spawn +================ +*/ +void rvMonsterBossMakron::Spawn ( void ) { + + if( spawnArgs.GetBool("passive")) { + flagFakeDeath = true; + } else { + flagFakeDeath = false; + } + + if( spawnArgs.GetBool("furniture") ) { + fl.takedamage = false; + } + + if( spawnArgs.GetBool("junior") ) { + flagUndying = 1; + } else { + flagUndying = 0; + } + + flagAllowSpawns = false; + flagFlyingMode = false; + patternedMode = false; + noIdle = false; + + flagCornerState = false; + cornerCount = 0; + flagTeleporting = false; + + //start clean + actionPatterned = -1; + + boltTime = 0; + boltSweepTime = spawnArgs.GetFloat( "lightningsweep_sweep_time", "3"); + + InitSpawnArgsVariables(); + + actionDMGAttack.Init ( spawnArgs, "action_DMGAttack", "Torso_DMGAttack", AIACTIONF_ATTACK ); + actionCannonAttack.Init ( spawnArgs, "action_cannonAttack", "Torso_CannonAttack", AIACTIONF_ATTACK ); + actionCannonSweepAttack.Init ( spawnArgs, "action_cannonsweepAttack", "Torso_CannonSweepAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", "Torso_GrenadeAttack", AIACTIONF_ATTACK ); + actionLightningPattern1Attack.Init( spawnArgs, "action_lightningPattern1Attack", "Torso_Lightning1Attack", AIACTIONF_ATTACK ); + actionLightningPattern2Attack.Init( spawnArgs, "action_lightningPattern2Attack", "Torso_Lightning2Attack", AIACTIONF_ATTACK ); + actionStompAttack.Init ( spawnArgs, "action_stompAttack", "Torso_StompAttack", AIACTIONF_ATTACK ); + actionHeal.Init( spawnArgs, "action_heal", "Torso_Recharge", AIACTIONF_ATTACK ); + actionCharge.Init( spawnArgs, "action_charge", "Torso_Charge", AIACTIONF_ATTACK ); + actionKillPlayer.Init( spawnArgs, "action_lightningPattern3Attack", "Torso_KillPlayer", AIACTIONF_ATTACK ); + + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", "Torso_MeleeAttack", AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_recharge", "", &func ) ) + { + scriptRecharge.Init( func ); + } + if ( spawnArgs.GetString( "script_teleport", "", &func ) ) + { + scriptTeleport.Init( func ); + } + + //build the action array + BuildActionArray(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + +/* +================ +rvMonsterBossMakron::CheckTurnActions +================ +*/ +bool rvMonsterBossMakron::CheckTurnActions ( void ) { + + if ( !flagFakeDeath && !move.fl.moving && !move.anim_turn_angles ) { //&& gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + ////gameLocal.Printf(" turn yaw == %f \n", turnYaw); + } + return false; +} +/* +================ +rvMonsterBossMakron::CheckActions +================ +*/ +bool rvMonsterBossMakron::CheckActions ( void ) { + + if( spawnArgs.GetFloat("furniture", "0")) { + return true; + } + + //if in the middle of teleporting, do nothing + if( flagTeleporting ) { + return true; + } + + //gameLocal.Printf("Begin CheckActions \n"); + + if( CheckTurnActions() ) { + //gameLocal.Printf("---CheckActions: TurnActions was true \n"); + return true; + } + + + //If we need more baddies, do it right now regardless of range or player FOV + if ( flagAllowSpawns ) { + PerformAction ( actionGrenadeAttack.state,actionGrenadeAttack.blendFrames, actionGrenadeAttack.fl.noPain ); + //gameLocal.Printf("---CheckActions: Spawned in more fellas \n"); + return true; + } + + //melee attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction ( &actionStompAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + //gameLocal.Printf("---CheckActions: Melee attacks \n"); + return true; + } + + //cannon sweep is good to check if we're up close. + //if ( PerformAction ( &actionCannonSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + // return true; + //} + + //If we're in total patterned mode, then don't relay on timers or ranges to perform actions. + if ( patternedMode && !flagFakeDeath ) { + //gameLocal.Printf("**CheckActions: PerformPatternedAction called \n"); + PerformPatternedAction ( ); + return true; + } + //gameLocal.Printf("---CheckActions: FlagFakeDeath: %d \n", flagFakeDeath ); + //gameLocal.Printf("---CheckActions: PatternedMode: %d \n", patternedMode ); + + //ranged attacks + if ( PerformAction ( &actionDMGAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningPattern1Attack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionCannonAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + //gameLocal.Printf("---CheckActions: Ranged attack called \n"); + return true; + } + //gameLocal.Printf("---CheckActions: Defaulting to AI CheckActions \n"); + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterMakron::PerformPatternedAction +================ +*/ +void rvMonsterBossMakron::PerformPatternedAction( ) { + + //gameLocal.Printf("PerformPatternedAction::Begin--\n"); + + //if we don't have an action to perform, don't. + if( actionPatterned == -1) { + //gameLocal.Printf("actionPatterned = -1\n"); + //gameLocal.Printf("PerformPatternedAction::End--\n"); + return; + } + + //if we're in a corner, right now, then add to the corner tally. + if( flagCornerState ) { + cornerCount++; + } else { + cornerCount = 0; + } + + //if this is our third consecutive action in the corner, maybe we should teleport? + if( cornerCount == 3 ) { + gameLocal.Warning("Makron is stuck in a corner!"); + cornerCount = 0; + SetState( "Torso_Teleport" ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + return; + + } + + rvAIAction* action; + rvAIActionTimer* timer; + + action = actionArray[ actionPatterned]; + timer = &actionTimerRangedAttack; + + //if the attack is variable length, then do all this jive... + float scale; + scale = 1.0f; + + //gameLocal.Printf("performing the action\n"); + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + //Clear this out so the next attack can be queued up. + actionPatterned = -1; + + // Restart the action timer using the length of the animation being played + // Even though we don't use the action timer for this instance, we need to keep it updated and fresh. + action->timer.Reset ( actionTime, action->diversity, scale ); + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + +} + + +/* +================ +rvMonsterMakron::CanTurn +================ +*/ +bool rvMonsterBossMakron::CanTurn ( void ) const { + if( !flagFlyingMode ) { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; + } else { + return idAI::CanTurn ( ); + } +} + + +/* +================ +rvMonsterMakron::InitBoltSweep +================ +*/ +void rvMonsterBossMakron::InitBoltSweep ( idVec3 idealTarget ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + idVec3 fwd; + idVec3 right; + idVec3 worldup(0,0,1); + + idVec3 targetPoint; + + //init all the lightning bolt stuff + boltTime = 0; + flagSweepDone = false; + stateBoltSweep = 0; + + //get the vector from makron's gun to the target point. + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + targetPoint = idealTarget; //enemy.lastVisibleChestPosition; + + fwd = targetPoint - origin; + + fwd.Normalize(); + right = fwd.Cross( worldup); + + boltAimMatrix[0] = fwd; + boltAimMatrix[1] = right; + boltAimMatrix[2] = worldup; + + targetPoint = origin + (fwd * 2400); + + //left + targetPoint = targetPoint - (right * 1800); + boltVectorMin = targetPoint - origin; + boltVectorMin.Normalize(); + leftBoltVector = boltVectorMin; + + //right + targetPoint = targetPoint + (right * 1800 * 2); + boltVectorMax = targetPoint - origin; + boltVectorMax.Normalize(); + rightBoltVector = boltVectorMax; + +} + +/* +================ +rvMonsterMakron::MaintainBoltSweep +================ +*/ +void rvMonsterBossMakron::MaintainBoltSweep ( void ) { + enum { + STATE_START, + STATE_WAIT1, + STATE_SWEEP1, + STATE_WAIT2, + STATE_SWEEP2, + STATE_END, + }; + + //advance the state when time is up. + + switch( stateBoltSweep ) { + + //init vars + case STATE_START: + flagSweepDone = false; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + StopAllBoltEffects(); + stateBoltSweep = STATE_WAIT1; + return; + + //blast at the waiting points, vectors do not move + case STATE_WAIT1: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + + case STATE_SWEEP1: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + } + + return; + case STATE_WAIT2: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + case STATE_SWEEP2: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + //min and max are reversed here. + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = 0; + StopAllBoltEffects(); + } + return; + case STATE_END: + flagSweepDone = 1; + return; + } + +} +/* +================ +rvMonsterBossMakron::LightningSweep +================ +*/ +void rvMonsterBossMakron::LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + + //trace out from origin along attackVector + attackVector.Normalize(); + gameLocal.TracePoint( this, tr, origin, origin + (attackVector * 25600), MASK_SHOT_RENDERMODEL, this); + //gameRenderWorld->DebugLine( colorRed, origin, tr.c.point, 100, true); + + if ( !boltEffect ) { + boltEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_fly" ), origin, attackVector.ToMat3(), true, tr.c.point); + } else { + boltEffect->SetOrigin ( origin ); + boltEffect->SetAxis ( attackVector.ToMat3() ); + boltEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_impact" ), tr.c.point, tr.c.normal.ToMat3(), true, tr.c.point); + } else { + impactEffect->SetOrigin ( tr.c.point ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + impactEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !boltMuzzleFlash ) { + boltMuzzleFlash = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_muzzle" ), origin, attackVector.ToMat3(), true, origin); + } else { + boltMuzzleFlash->SetOrigin ( origin ); + boltMuzzleFlash->SetAxis ( attackVector.ToMat3() ); + boltMuzzleFlash->SetEndOrigin ( origin ); + } + + //hurt anything in the way + idEntity* ent = gameLocal.entities[tr.c.entityNum]; + + if( ent) { + ent->Damage ( this, this, attackVector, spawnArgs.GetString ( "def_makron_sweep_damage" ), 1.0f, 0 ); + } + +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( !noIdle ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState ( "Wait_ActionNoPain" ); + + OnStartAction ( ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +bool rvMonsterBossMakron::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + return idAI::PerformAction( action, condition ,timer ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::OnStopAction( void ) { + ////gameLocal.Printf("\n\nAction stopped ----------- \n"); +} + +/* +================ +rvMonsterBossMakron::Damage +================ +*/ +void rvMonsterBossMakron::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + //Deal damage here, + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + //if the Makron has 0 or less health, make sure he's killed + ///if ( health <= 0 && !flagFlyingMode && !flagFakeDeath ) { + // Killed( this, this, 1, dir, location); + //} +} +/* +================ +rvMonsterBossMakron::Killed +================ +*/ +void rvMonsterBossMakron::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + //if this is the undying Makron Jr, don't worry about death. Stop what we're doing, + //Call the script function and let it ride. + if( flagUndying == 1) { + flagUndying = 2; + ExecScriptFunction( funcs.death ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + PerformAction ( actionKillPlayer.state, actionKillPlayer.blendFrames, actionKillPlayer.fl.noPain ); + return; + } + + if( flagUndying == 2) { + return; + } + + //stop all effects. + StopAllEffects(); + + //if the makron isn't in flying mode, it is now. + if( flagFlyingMode ) { + //play the falling animation, then die. + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + //SetState( "Torso_Death_Fall" ); + idAI::Killed( inflictor, attacker, damage, dir, location); + return; + } + + //the Makron is in undying mode until he finishes getting up. + //gameLocal.Warning("First form defeated!"); + + health = 1; + aifl.undying = true; + fl.takedamage = false; + SetMoveType ( MOVETYPE_DEAD ); + StopMove( MOVE_STATUS_DONE ); + //gameLocal.Printf("************\n************\n************\n************\n---flagFakeDeath set to TRUE! ************\n************\n************\n************\n" ); + flagFakeDeath = true; + + + + //play the death anim + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + //Call this anyway-- hopefully the script is prepared to handle multiple Makron deaths... + ExecScriptFunction( funcs.death ); + + //make sure he goes through deadness, but we need to post FinishAction afterwards +// SetState( "Torso_FirstDeath" ); +// aifl.action = true; + SetState( "Torso_FirstDeath" ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FirstDeath", 30, 0, SFLAG_ONCLEAR ); + +} + + +/* +================ +rvMonsterBossMakron::BeginSeparation( void ) +================ +*/ +void rvMonsterBossMakron::BeginSeparation( void ) { + + //spawn in a new Makron leg, + idDict args; + idEntity* newLegs; + + //We may need to do this at a later point + //SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_makron_legs" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &newLegs ); + + //store the name of the entity in the Makron's keys so we can burn it out as well. + spawnArgs.Set( "legs_name", newLegs->GetName() ); + + +} + +/* +================ +rvMonsterBossMakron::CompleteSeparation( void ) +================ +*/ +void rvMonsterBossMakron::CompleteSeparation( void ) { + + //flying mode now + flagFlyingMode = true; + SetMoveType ( MOVETYPE_FLY ); + move.fl.noGravity = true; + animPrefix = "fly"; + + //is there a cooler way to do this? Heal over time? + //health = spawnArgs.GetFloat("health_flying", "5000" ); + + ExecScriptFunction( funcs.init); + + //makron is once again pwnable. + aifl.undying = false; + fl.takedamage = true; + flagFakeDeath = false; + patternedMode = true; + + //make sure he cleans up. +// PostAnimState ( ANIMCHANNEL_ALL, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_LEGS, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + //gameLocal.Printf("*****\n*****\n*****\nPast 'PostAnimState' flagFakeDeath is: %d \n*****\n*****\n*****\n", flagFakeDeath); + + +// These four lines work! + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + + +} + +/* +================ +rvMonsterBossMakron::ScriptedFace( idEntity* faceEnt, bool endWithIdle ) +================ +*/ +/* +void rvMonsterBossMakron::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + + //set the ideal yaw, the change to the facing state. + FaceEntity( faceEnt ); + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + facingIdealYaw = move.ideal_yaw; + + //become scripted + aifl.scripted = true; + + //This will get us close to facing the entity correctly. + SetState( "State_ScriptedFace", SFLAG_ONCLEAR); + +} +*/ + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterBossMakron::Event_AllowMoreSpawns +================ +*/ +// this will allow Makron to spawn more baddies. +void rvMonsterBossMakron::Event_AllowMoreSpawns( void ) { + flagAllowSpawns = true; +} + +/* +================ +rvMonsterBossMakron::Event_EnablePatternMode +================ +*/ +// When set, the Makron will now only fight via scripted patterns +void rvMonsterBossMakron::Event_EnablePatternMode( void ) { + patternedMode = true; + noIdle = true; + flagTeleporting = false; +} + +/* +================ +rvMonsterBossMakron::Event_DisablePatternMode +================ +*/ +void rvMonsterBossMakron::Event_DisablePatternMode( void ) { + patternedMode = false; + noIdle = false; +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_Separate( void ) { + + //all we need to do here is post the separation state, right? + BeginSeparation(); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Resurrection", 0, 5000, SFLAG_ONCLEAR ); + +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_FlyingRotate( idVec3& vecOrg ) { + + //set move.ideal_yaw + TurnToward( vecOrg ); + + //copy it over + facingIdealYaw = move.ideal_yaw; + aifl.scripted = true; + + //set the state + SetState( "Torso_RotateToAngle"); + +} + +/* +================ +rvMonsterBossMakron::Event_SetNextAction +================ +*/ +void rvMonsterBossMakron::Event_SetNextAction( const char * actionString) { + + //if the next action is occupied, return false + if( actionPatterned != -1) { + idThread::ReturnFloat(0); + return; + } + + //otherwise, select the action from a list + if( !idStr::Cmp( actionString, "actionCannon")) { + actionPatterned = MAKRON_ACTION_CANNON; + } + else if( !idStr::Cmp( actionString, "actionCannonSweep")) { + actionPatterned = MAKRON_ACTION_CANNON_SWEEP; + } + else if( !idStr::Cmp( actionString, "actionDMG")) { + actionPatterned = MAKRON_ACTION_DMG; + } + else if( !idStr::Cmp( actionString, "actionDMGrenades")) { + actionPatterned = MAKRON_ACTION_GRENADE; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep1")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_1; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep2")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_2; + } + else if( !idStr::Cmp( actionString, "actionStomp")) { + actionPatterned = MAKRON_ACTION_STOMP; + } + else if( !idStr::Cmp( actionString, "actionHeal")) { + actionPatterned = MAKRON_ACTION_HEAL; + } + else if( !idStr::Cmp( actionString, "actionCharge")) { + actionPatterned = MAKRON_ACTION_CHARGE; + } + else if( !idStr::Cmp( actionString, "actionKillPlayer")) { + actionPatterned = MAKRON_ACTION_KILLPLAYER; + } + else if( !idStr::Cmp( actionString, "actionEndPattern")) { + actionPatterned = -1; + } + else { + gameLocal.Error(" Bad action %s passed into MonsterMakron::SetNextAction", actionString); + idThread::ReturnFloat(0); + return; + } + + idThread::ReturnFloat(1); + return; + + + +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBossMakron ) + STATE ( "Torso_DMGAttack", rvMonsterBossMakron::State_Torso_DMGAttack ) + STATE ( "Torso_MeleeAttack", rvMonsterBossMakron::State_Torso_MeleeAttack ) + STATE ( "Torso_CannonAttack", rvMonsterBossMakron::State_Torso_CannonAttack ) + STATE ( "Torso_GrenadeAttack", rvMonsterBossMakron::State_Torso_GrenadeAttack ) + STATE ( "Torso_CannonSweepAttack", rvMonsterBossMakron::State_Torso_CannonSweepAttack ) + STATE ( "Torso_Lightning1Attack", rvMonsterBossMakron::State_Torso_Lightning1Attack ) + STATE ( "Torso_Lightning2Attack", rvMonsterBossMakron::State_Torso_Lightning2Attack ) + STATE ( "Torso_StompAttack", rvMonsterBossMakron::State_Torso_StompAttack ) + STATE ( "Torso_Recharge", rvMonsterBossMakron::State_Torso_Recharge ) + STATE ( "Torso_Charge", rvMonsterBossMakron::State_Torso_Charge ) + STATE ( "Torso_KillPlayer", rvMonsterBossMakron::State_Torso_KillPlayer ) + STATE ( "Torso_FirstDeath", rvMonsterBossMakron::State_Torso_FirstDeath ) + STATE ( "Torso_Resurrection", rvMonsterBossMakron::State_Torso_Resurrection ) + STATE ( "State_Killed", rvMonsterBossMakron::State_Killed ) + STATE ( "Torso_Teleport", rvMonsterBossMakron::State_Torso_Teleport ) + + STATE ( "Torso_TurnRight90", rvMonsterBossMakron::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterBossMakron::State_Torso_TurnLeft90 ) + + STATE ( "Torso_RotateToAngle", rvMonsterBossMakron::State_Torso_RotateToAngle ) + + STATE ( "Frame_BeginLightningSweep2", rvMonsterBossMakron::Frame_BeginLightningSweep2 ) + STATE ( "Frame_EndLightningSweep2", rvMonsterBossMakron::Frame_EndLightningSweep2 ) + STATE ( "Frame_StompAttack", rvMonsterBossMakron::Frame_StompAttack ) + STATE ( "Frame_Teleport", rvMonsterBossMakron::Frame_Teleport ) + + STATE ( "State_ScriptedFace", rvMonsterBossMakron::State_ScriptedFace ) + + +END_CLASS_STATES + + +/* +================ +rvBossMakron::State_Torso_DMGAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_DMGAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron DMG Go!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_dmg", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_Torso_Charge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Charge ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "run", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_ScriptedFace +================ +*/ + +stateResult_t rvMonsterBossMakron::State_ScriptedFace ( const stateParms_t& parms ) { + + //note this uses the Makron's version of FacingIdeal, + if( !flagFlyingMode) { + if ( !aifl.scripted || (!CheckTurnActions( ) && (!move.anim_turn_angles))) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; + + } else { + + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +} + +/* + enum { + STAGE_INIT, + STAGE_WAIT + }; + + idStr turnAnim; + float turnYaw; + + switch( parms.stage ) { + case STAGE_INIT: + + DisableAnimState ( ANIMCHANNEL_LEGS ); + //which way do we need to face? + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + turnAnim = "turn_right_90"; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + turnAnim = "turn_left_90"; + } else { + //guess we don't need to turn? We're done. + aifl.scripted = false; + return SRESULT_DONE; + } + PlayAnim ( ANIMCHANNEL_TORSO, turnAnim, 4 ); + AnimTurn ( 90.0f, true ); + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + //back to the start to make sure we're facing the right way. + return SRESULT_STAGE ( STAGE_INIT ); + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; + +} +*/ + + +/* +================ +rvBossMakron::State_Torso_FirstDeath +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_FirstDeath ( const stateParms_t& parms ) { + + + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; +} + +/* +================ +rvBossMakron::State_Torso_Resurrection +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Resurrection ( const stateParms_t& parms ) { +/* enum { + STAGE_INIT, + STAGE_WAIT_FIRST, + STAGE_RISE, + STAGE_WAIT_SECOND + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT_FIRST); + + case STAGE_WAIT_FIRST: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_RISE); + } + return SRESULT_WAIT; + + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT_SECOND); + + case STAGE_WAIT_SECOND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +*/ + enum { + STAGE_RISE, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvBossMakron::State_Lightning2Attack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Lightning2Attack ( const stateParms_t& parms ) { + + idVec3 boltVector; + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + + //prep up for the bolt + //gameLocal.Warning("Prepping sweep 2"); + //init these values + StopAllBoltEffects(); + stateBoltSweep = 0; + flagSweepDone = false; + //set up bolt targeting at a little above the players chest-- make him duck this one. + boltVector = enemy.lastVisibleEyePosition; + boltVector.z += 8; + InitBoltSweep( boltVector ); + + //play the anim + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "claw_sweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + if( stateBoltSweep == 1) { + //fire the bolt out from the claw based on where the claw is pointing, sort of. + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact ); + } + else if( stateBoltSweep == 2) { + //gameLocal.Warning("Sweep 2 done."); + StopAllBoltEffects(); + stateBoltSweep = 0; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterBossMakron::State_Torso_StompAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_StompAttack( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //can't do the stomp attack while flying-- ain't got no legs!! + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shockwave_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvMonsterBossMakron::State_Torso_Teleport +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Teleport( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //No teleporting in flying mode. + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + //can't take damage in teleport anim, bad things happen! + aifl.undying = true; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + + //restore former state, which is killable unless some other effect renders Makron unkillable. + if( !flagUndying ) { + aifl.undying = false; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvBossMakron::State_Torso_GrenadeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_GrenadeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Grenades!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + //only allow spawns if the script tells us we can + if( !flagAllowSpawns) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_dm", parms.blendFrames ); + } + else { + flagAllowSpawns = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_spawn", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvBossMakron::State_Torso_Recharge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Recharge ( const stateParms_t& parms ) { + + ExecScriptFunction( scriptRecharge ); + return SRESULT_DONE; +} + +/* +================ +rvBossMakron::State_Torso_MeleeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_MeleeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //swing! + //gameLocal.Warning("Makron Melee Attack"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "melee_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + + +/* +================ +rvMonsterBossMakron::State_Torso_RotateToAngle +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_RotateToAngle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_LOOP, + }; + + float facingTimeDelta; + float turnYaw; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTime = MS2SEC( gameLocal.time); + aifl.scripted = true; + return SRESULT_STAGE ( STAGE_WAIT_LOOP ); + } + aifl.scripted = false; + return SRESULT_DONE; + case STAGE_WAIT_LOOP: + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTimeDelta = MS2SEC( gameLocal.time) - facingTime; + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + ang.yaw += ( turnRate * facingTimeDelta ); + SetAngles( ang); + move.current_yaw = ang.yaw; + return SRESULT_STAGE( STAGE_WAIT_LOOP); + } + aifl.scripted = false; + return SRESULT_DONE; + } + + return SRESULT_ERROR; +} +/* +================ +rvMonsterBossMakron::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_CannonAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + //once an anim starts with the legs disabled, the rest of the anims should match that. + static bool noLegs; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( !flagFlyingMode || ( flagFlyingMode && !move.fl.moving )) { + noLegs = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + } else { + noLegs = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_start", parms.blendFrames ); + + } + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + //if we're flying, and moving, fire fast! + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire_fast", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_fire_fast", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_end", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_Lightning1Attack +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_Lightning1Attack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_INIT_BOLT, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + idVec3 origin; + idVec3 targetPoint; + idMat3 axis; + trace_t tr; + + switch ( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning( "Lightningbolt!"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_INIT_BOLT ); + } + return SRESULT_WAIT; + + case STAGE_INIT_BOLT: + //aim a little below the player's chest + targetPoint = enemy.lastVisibleChestPosition; + targetPoint.z -= 24; + InitBoltSweep( targetPoint ); + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //sweep the blasts back and forth + MaintainBoltSweep(); + + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + if ( flagSweepDone ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_KillPlayer +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_KillPlayer ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = 8; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + shots--; + if ( shots < 1) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + + +/* +================ +rvBossMakron::State_Torso_CannonSweepAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonSweepAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron CannonSweep Go!"); + //sweep across with the cannon + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_cannonsweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + //if the flag is up, fire. + + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterBossMakron::State_Killed +================ +*/ +stateResult_t rvMonsterBossMakron::State_Killed ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + disablePain = true; + // At this point the Makron is killed! + // Make sure all animation stops + //StopAnimState ( ANIMCHANNEL_TORSO ); + //StopAnimState ( ANIMCHANNEL_LEGS ); + //if ( head ) { + // StopAnimState ( ANIMCHANNEL_HEAD ); + //} + + // Make sure all animations stop + //animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_BeginLightningSweep2 ( const stateParms_t& parms ) { + + //begin the sweep with this flag + stateBoltSweep = 1; + boltSweepStartTime = gameLocal.time; + boltSweepTime = 1; + return SRESULT_OK; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_EndLightningSweep2 ( const stateParms_t& parms ) { + + //end the sweep with this flag + stateBoltSweep = 2; + return SRESULT_OK; + +} + +/* +================ +rvMonsterBossMakron::Frame_Teleport +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_Teleport ( const stateParms_t& parms ) { + + //hide + Hide(); + + //turn on the do-nothing teleport flag + flagTeleporting = true; + + //call some script. + ExecScriptFunction( scriptTeleport ); + return SRESULT_DONE; + +} + + +/* +================ +rvMonsterBossMakron::Frame_StompAttack +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_StompAttack ( const stateParms_t& parms ) { + + idVec3 origin; + idVec3 worldUp(0, 0, 1); + + // Eminate from Makron origin + origin = this->GetPhysics()->GetOrigin(); + + //start radius at 256; + stompRadius = spawnArgs.GetFloat("stomp_start_size", "64"); + + //stomp + gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_stomp_wave" ), origin, worldUp.ToMat3(), false, origin); + + //bamf! + PostEventMS( &EV_StompAttack, 0, origin); + + return SRESULT_OK; + +} +/* +================ +rvMonsterBossMakron::Event_ToggleCornerState +================ +*/ +void rvMonsterBossMakron::Event_ToggleCornerState ( float f ) { + if( f == 1.0f) { + flagCornerState = true; + } else { + flagCornerState = false; + } +} + +/* +================ +rvMonsterBossMakron::Event_StompAttack +================ +*/ + +void rvMonsterBossMakron::Event_StompAttack (idVec3& origin) { + + idVec3 targetOrigin; + idVec3 worldUp; + idEntity* entities[ 1024 ]; + int count; + int i; + float stompZ; + idVec3 dir; + modelTrace_t result; + + + stompZ = origin.z; + + worldUp.x = 0; + worldUp.y = 0; + worldUp.z = 1; + + //if the radius is too big, stop. + if ( stompRadius > stompMaxRadius ) { + return; + } + + //get all enemies within radius. If they are: + // within radius, + // more than (radius - stompWidth) units away, + // Z valued within 16 of the the stomp Z + //they take stomp damage. + count = gameLocal.EntitiesWithinRadius ( origin, stompRadius, entities, 1024 ); + + //gameRenderWorld->DebugCircle( colorRed,origin,worldUp,stompRadius,24,20,false); + //gameRenderWorld->DebugCircle( colorBlue,origin,worldUp,stompRadius - stompWidth,24,20,false); + + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + + //don't stomp ourself, derp... + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Are they Z equal (about?) + targetOrigin = ent->GetPhysics()->GetOrigin(); + if( idMath::Abs( targetOrigin.z - origin.z) > 16) { + continue; + } + + // are they within the stomp width? + if( targetOrigin.Dist( origin) < ( stompRadius - stompWidth) || + targetOrigin.Dist( origin) > stompRadius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, ent->GetPhysics()->GetCenterMass()) ) { + continue; + } + + //ok, damage them + dir = targetOrigin - origin; + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_makron_stomp_damage" ), 1.0f, 0 ); + //gameRenderWorld->DebugArrow( colorYellow, origin, targetOrigin, 5, 1000); + + } + + //move the radius along + stompRadius += stompSpeed; + + //run it back + PostEventSec( &EV_StompAttack, stompSpeed / stompMaxRadius , origin ); + +} + + diff --git a/source/mpgame/ai/Monster_ConvoyGround.cpp b/source/mpgame/ai/Monster_ConvoyGround.cpp new file mode 100644 index 0000000..4f55c00 --- /dev/null +++ b/source/mpgame/ai/Monster_ConvoyGround.cpp @@ -0,0 +1,603 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +// +class rvMonsterConvoyGround : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterConvoyGround ); + + rvMonsterConvoyGround ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Postthink ( void ); + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool CheckPainActions ( void ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AdjustHealthByDamage ( int inDamage ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + int shots; + int minShots; + int maxShots; + bool isOpen; + bool vehicleCollision; + float moveCurrentAnimRate; + float moveAnimRateMin; + float moveAnimRateRange; + float moveAccelRate; + bool onGround; + idVec3 oldOrigin; + idVec3 lastPainDir; + + rvAIAction actionBlasterAttack; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual const char* GetIdleAnimName ( void ); + + virtual void OnDeath ( void ); + +private: + + // General states + stateResult_t State_Fall ( const stateParms_t& parms ); + + // Legs States + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Open ( const stateParms_t& parms ); + stateResult_t State_Torso_Close ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyGround ); +}; + +CLASS_DECLARATION( idAI, rvMonsterConvoyGround ) +END_CLASS + +/* +================ +rvMonsterConvoyGround::rvMonsterConvoyGround +================ +*/ +rvMonsterConvoyGround::rvMonsterConvoyGround ( ) { + shots = 0; + isOpen = false; + vehicleCollision = false; + moveCurrentAnimRate = 1.0f; +} + +void rvMonsterConvoyGround::InitSpawnArgsVariables ( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); + moveAccelRate = spawnArgs.GetFloat ( "moveAccelRate", ".1" ); + moveAnimRateMin = spawnArgs.GetFloat ( "moveMinAnimRate", "1" ); + moveAnimRateRange = spawnArgs.GetFloat ( "moveMaxAnimRate", "10" ) - moveAnimRateMin; +} + +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +void rvMonsterConvoyGround::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + aifl.disableLook = true; + + onGround = true; + +} + +/* +================ +rvMonsterConvoyGround::Prethink +================ +*/ +void rvMonsterConvoyGround::Postthink ( void ) { +/* FIXME + if ( onGround && !physicsObj.HasGroundContacts ( ) ) { + onGround = false; + InterruptState ( "State_Fall" ); + } +*/ + + idAI::Postthink ( ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +bool rvMonsterConvoyGround::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + lastPainDir = dir; + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +void rvMonsterConvoyGround::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + //minShots and maxShots are set in the Restore + savefile->WriteBool ( isOpen ); + savefile->WriteBool ( vehicleCollision ); + savefile->WriteBool ( onGround ); + + savefile->WriteFloat ( moveCurrentAnimRate ); + savefile->WriteVec3 ( oldOrigin ); + savefile->WriteVec3 ( lastPainDir ); + + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterConvoyGround::Restore +================ +*/ +void rvMonsterConvoyGround::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadBool ( isOpen ); + savefile->ReadBool ( vehicleCollision ); + savefile->ReadBool ( onGround ); + + savefile->ReadFloat ( moveCurrentAnimRate ); + savefile->ReadVec3 ( oldOrigin ); + savefile->ReadVec3 ( lastPainDir ); + + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterConvoyGround::CanMove +================ +*/ +bool rvMonsterConvoyGround::CanMove ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvMonsterConvoyGround::CanTurn +================ +*/ +bool rvMonsterConvoyGround::CanTurn ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvMonsterConvoyGround::OnDeath +================ +*/ +void rvMonsterConvoyGround::OnDeath ( void ) { + idVec3 fxOrg; + idVec3 up; + idMat3 fxAxis; + + //center it + fxOrg = GetPhysics()->GetCenterMass(); + + //point it up + up.Set( 0, 0, 1 ); + fxAxis = up.ToMat3(); + + //if we can play it at the joint, do that + jointHandle_t axisJoint = animator.GetJointHandle ( "axis" ); + if ( axisJoint != INVALID_JOINT ) { + idMat3 junk; + animator.GetJointLocalTransform( axisJoint, gameLocal.GetTime(), fxOrg, junk ); + fxOrg = renderEntity.origin + (fxOrg*renderEntity.axis); + } + + gameLocal.PlayEffect ( spawnArgs, "fx_death", fxOrg, fxAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterConvoyGround::AdjustHealthByDamage +================ +*/ +void rvMonsterConvoyGround::AdjustHealthByDamage ( int damage ) { + if ( isOpen || vehicleCollision ) { + idAI::AdjustHealthByDamage ( damage ); + } else { + PlayEffect ( "fx_shieldHit", animator.GetJointHandle ( "axis" ) ); + } +} + +void rvMonsterConvoyGround::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + vehicleCollision = false; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + vehicleCollision = true; + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +bool rvMonsterConvoyGround::CheckActions ( void ) { + if ( isOpen ) { + if ( move.fl.moving ) { +/* + || !CheckAction_RangedAttack( &actionBlasterAttack, -1 ) + || enemy.range > actionBlasterAttack.maxRange + || enemy.range < actionBlasterAttack.minRange + || (!move.fl.moving && (gameLocal.GetTime()-move.startTime) > 3000 ) ) { +*/ + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Close", 4, true ); + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + // Open up if we have stopped and have an enemy + if ( !move.fl.moving && physicsObj.HasGroundContacts ( ) && enemy.ent && legsAnim.IsIdle ( ) && CheckTactical ( AITACTICAL_RANGED ) ) { + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Open", 4, true ); + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterConvoyGround::CheckPainActions +================ +*/ +bool rvMonsterConvoyGround::CheckPainActions ( void ) { + if ( isOpen ) { + return false; + } + + return idAI::CheckPainActions ( ); +} + +/* +================ +rvMonsterConvoyGround::GetIdleAnimName +================ +*/ +const char* rvMonsterConvoyGround::GetIdleAnimName ( void ) { + // Start idle animation + if ( isOpen ) { + return "idle_open"; + } + return "idle"; +} + +/* +================ +rvMonsterConvoyGround::FilterTactical +================ +*/ +int rvMonsterConvoyGround::FilterTactical ( int availableTactical ) { + return idAI::FilterTactical ( availableTactical ); +} + +/* +===================== +rvMonsterConvoyGround::GetDebugInfo +===================== +*/ +void rvMonsterConvoyGround::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + + proc ( "rvMonsterConvoyGround", "moveAnimRate", va("%g", moveCurrentAnimRate ), userData ); + proc ( "rvMonsterConvoyGround", "isOpen", isOpen ? "true" : "false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyGround ) + STATE ( "State_Fall", rvMonsterConvoyGround::State_Fall ) + STATE ( "Torso_Open", rvMonsterConvoyGround::State_Torso_Open ) + STATE ( "Torso_Close", rvMonsterConvoyGround::State_Torso_Close ) + STATE ( "Torso_BlasterAttack", rvMonsterConvoyGround::State_Torso_BlasterAttack ) + STATE ( "Torso_Pain", rvMonsterConvoyGround::State_Torso_Pain ) + + STATE ( "Legs_Move", rvMonsterConvoyGround::State_Legs_Move ) +END_CLASS_STATES + +/* +================ +rvMonsterConvoyGround::State_Fall +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact, switch to combat state + STAGE_WAITDONE, + STAGE_DONE + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StopAnimState ( ANIMCHANNEL_LEGS ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle", 0 ); + oldOrigin = physicsObj.GetOrigin ( ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( physicsObj.HasGroundContacts ( ) ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + + if ( (physicsObj.GetOrigin ( ) - oldOrigin).LengthSqr() > Square(128.0f) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "land", 0 ); + return SRESULT_STAGE ( STAGE_WAITDONE ); + } + return SRESULT_STAGE ( STAGE_DONE ); + + case STAGE_WAITDONE: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + onGround = true; + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Open +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Open ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "extend_legs", parms.blendFrames ); + isOpen = true; + aifl.disableLook = false; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Close +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Close ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "retract_legs", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + isOpen = false; + aifl.disableLook = true; + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_END + }; + + switch ( parms.stage ) { + case STAGE_START: + // Force the orientation to the direction we got hit from so the animation looks correct + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + TurnToward ( physicsObj.GetOrigin() - lastPainDir * 128.0f ); + move.current_yaw = move.ideal_yaw; + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END ); + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Legs_Move +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_MOVE + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + move.fl.running = true; + move.currentDirection = MOVEDIR_FORWARD; + // TODO: Looks like current anim rate never gets reset, so they do not correctly accelerate from a stop + // unfortunately, adding this change (with a decent acceleration factor) caused them to do lots of + // not-so-good looking short moves. +// moveCurrentAnimRate = 0; + + oldOrigin = physicsObj.GetOrigin ( ); + PlayCycle ( ANIMCHANNEL_LEGS, "run", 0 ); + StartSound( "snd_move", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_MOVE ); + + case STAGE_MOVE: + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + StopSound( SND_CHANNEL_BODY3, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + + // If on the ground update the animation rate based on the normal of the ground plane + if ( !ai_debugHelpers.GetBool ( ) && physicsObj.HasGroundContacts ( ) ) { + float rate; + idVec3 dir; + + dir = (physicsObj.GetOrigin ( ) - oldOrigin); + + if ( DistanceTo ( move.moveDest ) < move.walkRange ) { + rate = moveAnimRateMin; + } else if ( dir.Normalize ( ) > 0.0f ) { + rate = idMath::ClampFloat ( -0.7f, 0.7f, physicsObj.GetGravityNormal ( ) * dir ) / 0.7f; + rate = moveAnimRateMin + moveAnimRateRange * (1.0f + rate) / 2.0f; + } else { + rate = moveAnimRateMin + moveAnimRateRange * 0.5f; + } + moveCurrentAnimRate += ((rate - moveCurrentAnimRate) * moveAccelRate); + + animator.CurrentAnim ( ANIMCHANNEL_LEGS )->SetPlaybackRate ( gameLocal.time, moveCurrentAnimRate ); + } + + oldOrigin = physicsObj.GetOrigin ( ); + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + diff --git a/source/mpgame/ai/Monster_ConvoyHover.cpp b/source/mpgame/ai/Monster_ConvoyHover.cpp new file mode 100644 index 0000000..7186040 --- /dev/null +++ b/source/mpgame/ai/Monster_ConvoyHover.cpp @@ -0,0 +1,630 @@ +//---------------------------------------------------------------- +// rvMonsterConvoyHover.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +class rvMonsterConvoyHover : public rvVehicleMonster { +public: + CLASS_PROTOTYPE ( rvMonsterConvoyHover ); + + rvMonsterConvoyHover ( void ); + ~rvMonsterConvoyHover ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + +private: + + void Think_Random ( void ); + void Think_Pathing ( void ); + + void AttackBlaster ( void ); + void AttackBeam ( void ); + //void AttackBomb ( void ); + + float angleYaw; // actually acos( angleYaw ) from player + float minYaw; + float maxYaw; + float desiredHeight; // actually acos( desiredHeight ) from player + float minHeight; + float maxHeight; + float distance; // distance to enemy + float minDistance; + float maxDistance; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + + int shotCount; + + idPhysics_RigidBody physicsObj; + + static const int DAMPEN_ANGLE_SAMPLES = 8; + idAngles hoverDampening[ DAMPEN_ANGLE_SAMPLES ]; + + void CalcDampening ( const idAngles & cur, idAngles & out ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_BeamAttack ( const stateParms_t& parms ); + //stateResult_t State_BombAttack ( const stateParms_t& parms ); + + struct { + bool pathing:1; + bool faceEnemy:1; + bool dead:1; + } myfl; + + virtual void OnDeath ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover ); +}; + +CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover ) +END_CLASS + +/* +================ +rvMonsterConvoyHover::rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) { +} + +/* +================ +rvMonsterConvoyHover::~rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvMonsterConvoyHover::Save +================ +*/ +void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( angleYaw ); + savefile->WriteFloat ( desiredHeight ); + savefile->WriteFloat ( distance ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Write( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var +} + +/* +================ +rvMonsterConvoyHover::Restore +================ +*/ +void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( angleYaw ); + savefile->ReadFloat ( desiredHeight ); + savefile->ReadFloat ( distance ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + savefile->ReadInt ( shotCount ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Read( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var + + InitSpawnArgsVariables(); +} + +void rvMonsterConvoyHover::InitSpawnArgsVariables ( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + minYaw = spawnArgs.GetFloat( "minYaw", "0" ); + maxYaw = spawnArgs.GetFloat( "maxYaw", "360" ); + minHeight = spawnArgs.GetFloat( "minHeight", "10" ); + maxHeight = spawnArgs.GetFloat( "maxHeight", "70" ); + minDistance = spawnArgs.GetFloat( "minDistance", "100" ); + maxDistance = spawnArgs.GetFloat( "maxDistance", "500" ); + + minDistance *= minDistance; + maxDistance *= maxDistance; + + if ( minYaw == 0.0f && maxYaw == 0.0f ) { + maxYaw = 360.0f; + } + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} +/* +================ +rvMonsterConvoyHover::Spawn +================ +*/ +void rvMonsterConvoyHover::Spawn ( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( physicsObj ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( vec3_origin ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + + InitSpawnArgsVariables(); + + shotCount = 0; + + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = 0.5f * ( maxHeight - minHeight ) + minHeight; + distance = 0.5f * ( maxDistance - minDistance ) + minDistance; + + lastAttackTime = 0; + + SetState( "Idle" ); + + myfl.pathing = false; + myfl.faceEnemy = true; + myfl.dead = false; + + for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) { + hoverDampening[ i ].Zero(); + } + + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvMonsterConvoyHover::Think +================ +*/ +void rvMonsterConvoyHover::Think ( void ) { + if ( !driver ) { + rvVehicleMonster::Think(); + return; + } + + if ( !driver->IsDriving() ) { + GetPhysics()->SetGravity( gameLocal.GetGravity() ); + } + + trace_t trace; + idVec3 dir = GetPhysics()->GetLinearVelocity(); + dir.Normalize(); + if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) { + if ( trace.c.contents == CONTENTS_FLYCLIP ) { + float maxHeight = this->maxHeight * 2.0f; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f ); + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f ); + } + } + + if ( !driver->CanSee( driver->enemy.ent, false ) ) { + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f ); + } + + if ( myfl.pathing ) { + Think_Pathing( ); + } else { + Think_Random( ); + } + + rvVehicleMonster::Think(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); +// vel += idVec3( idMath::Sin( gameLocal.time + vel.x ), +// idMath::Sin( gameLocal.time + vel.y ), +// idMath::Sin( gameLocal.time + vel.z ) ); + + + idAngles hover = idAngles( idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), + 0.0f, + idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) ); + + CalcDampening( hover, hover ); + + if ( myfl.faceEnemy ) { + LookAtEntity( gameLocal.GetLocalPlayer(), 0 ); + idAngles angles = GetPhysics()->GetAxis( ).ToAngles( ); + angles.pitch *= 0.35f; + GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() ); + } else { + GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Random +================ +*/ +void rvMonsterConvoyHover::Think_Random ( void ) { + const idVec3 xAxis( 1.0f, 0.0f, 0.0f ); + const idVec3 yAxis( 0.0f, 1.0f, 0.0f ); + const idVec3 zAxis( 0.0f, 0.0f, 1.0f ); + const float xSpeed = 16000.0f; + const float ySpeed = 9000.0f; + const float zSpeed = 9000.0f; + + idEntity * ent = driver->enemy.ent; + if ( !ent ) { + ent = gameLocal.GetLocalPlayer(); + } + + idVec3 toEnemyYaw = ent->GetPhysics()->GetOrigin() - GetOrigin(); + toEnemyYaw.z = 0.0f; + toEnemyYaw.Normalize(); + float yaw = toEnemyYaw.ToAngles().yaw; + float height = GetOrigin().z - ent->GetPhysics()->GetOrigin().z; + float dist = GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() ); + + // yaw + if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } else { + idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed ); + impulse *= GetPhysics()->GetAxis(); + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + + // 5% chance of choosing a new angle + if ( rvRandom::flrand() < 0.05f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } + } + + // pitch (changed to height for convenience) + if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) { + desiredHeight = rvRandom::flrand( minHeight, maxHeight ); + } else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed ); + } else { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed ); + } + + // distance + if ( idMath::Fabs( dist - distance ) > 20.0f ) { + idVec3 impulse = xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed ); + idMat3 axis = GetPhysics()->GetAxis(); + axis[ 1 ] = idVec3( 0.0f, 0.0f, 1.0f ); + axis[ 2 ] = axis[ 1 ].Cross( axis[ 0 ] ); + impulse *= axis; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + } else { + // just choose a random distance for now + distance = rvRandom::flrand( minDistance, maxDistance ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Pathing +================ +*/ +void rvMonsterConvoyHover::Think_Pathing ( void ) { +} + +/* +================ +rvMonsterConvoyHover::AttackBlaster +================ +*/ +void rvMonsterConvoyHover::AttackBlaster ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBeam +================ +*/ +void rvMonsterConvoyHover::AttackBeam ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBomb +================ +* +void rvMonsterConvoyHover::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + + PlayEffect ( "fx_bombflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_bomb") ); + if( proj ) { + proj->Create( this, offset, axis[0], NULL ); + proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() ); + } +} +*/ + + + +/* +================ +rvMonsterConvoyHover::OnDeath +================ +*/ +void rvMonsterConvoyHover::OnDeath ( void ) { + myfl.dead = true; + idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) ); + + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetFriction( 0.0f, 0.0f, 0.0f ); + GetPhysics()->SetAngularVelocity ( angular ); + + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); +} + +/* +================ +rvMonsterConvoyHover::CalcDampening +================ +*/ +void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) { + idAngles current = cur; // just incase cur == out + out = cur; + + for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) { + hoverDampening[ i - 1 ] = hoverDampening[ i ]; + out += hoverDampening[ i ]; + } + hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current; + + out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyHover ) + STATE( "Idle", rvMonsterConvoyHover::State_Idle ) + STATE( "BlasterAttack", rvMonsterConvoyHover::State_BlasterAttack ) + STATE( "BeamAttack", rvMonsterConvoyHover::State_BeamAttack ) +// STATE( "BombAttack", rvMonsterConvoyHover::State_BombAttack ) +END_CLASS_STATES + + +/* +================ +rvMonsterConvoyHover::State_Idle +================ +*/ +stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) { + if ( driver ) { + if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) { +// if ( rvRandom::irand( 0, 100 ) < 10 ) { +// distance = 0; +// PostState( "BeamAttack" ); +// } else { + PostState( "BlasterAttack" ); +// } + PostState( "Idle" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterConvoyHover::State_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BeamAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BEAM, + STAGE_BEAMWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BEAM ); + + case STAGE_BEAM: + lastAttackTime = gameLocal.time; + AttackBeam( ); + return SRESULT_STAGE ( STAGE_BEAMWAIT ); + + case STAGE_BEAMWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BEAM ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BombAttack +================ +* +stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ diff --git a/source/mpgame/ai/Monster_FailedTransfer.cpp b/source/mpgame/ai/Monster_FailedTransfer.cpp new file mode 100644 index 0000000..4685fe0 --- /dev/null +++ b/source/mpgame/ai/Monster_FailedTransfer.cpp @@ -0,0 +1,122 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" + +class rvMonsterFailedTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFailedTransfer ); + + rvMonsterFailedTransfer ( void ); + + void Spawn ( void ); + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + bool allowSplit; + + virtual void OnDeath ( void ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterFailedTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFailedTransfer ) +END_CLASS + +/* +================ +rvMonsterFailedTransfer::rvMonsterFailedTransfer +================ +*/ +rvMonsterFailedTransfer::rvMonsterFailedTransfer ( ) { + allowSplit = false; +} + +/* +================ +rvMonsterFailedTransfer::Spawn +================ +*/ +void rvMonsterFailedTransfer::Spawn ( void ) { + LoadAF ( "ragdoll_legs", true ); + LoadAF ( NULL, true ); +} + +/* +================ +rvMonsterFailedTransfer::Save +================ +*/ +void rvMonsterFailedTransfer::Save( idSaveGame *savefile ) const { + savefile->WriteBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::Restore +================ +*/ +void rvMonsterFailedTransfer::Restore( idRestoreGame *savefile ) { + savefile->ReadBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::OnDeath +================ +*/ +void rvMonsterFailedTransfer::OnDeath ( void ) { + idAI::OnDeath ( ); + + if ( allowSplit ) { + idEntity* torso; + idDict args; + + LoadAF ( "ragdoll_legs", true ); + + PlayEffect ( "fx_bloodyburst", animator.GetJointHandle ( "chest" ) ); + SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_failed_transfer_torso" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -50.0f ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &torso ); + torso->fl.takedamage = false; + PostEventMS( &AI_TakeDamage, 100, 1.0f ); + } +} + +/* +================ +rvMonsterFailedTransfer::Killed +================ +*/ +void rvMonsterFailedTransfer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !idStr::Icmp ( GetDamageGroup( location ), "legs" ) && damage < 999 ) { + allowSplit = true; + } else { + allowSplit = false; + } + + idAI::Killed ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFailedTransfer ) +END_CLASS_STATES + diff --git a/source/mpgame/ai/Monster_Fatty.cpp b/source/mpgame/ai/Monster_Fatty.cpp new file mode 100644 index 0000000..8ddb329 --- /dev/null +++ b/source/mpgame/ai/Monster_Fatty.cpp @@ -0,0 +1,419 @@ +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +typedef struct monsterFattyChain_s { + jointHandle_t orientationJoint; + jointHandle_t attackJoint; + bool out; + idEntityPtr projectile; +} monsterFattyChain_t; + +class rvMonsterFatty : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFatty ); + + rvMonsterFatty ( void ) {} + ~rvMonsterFatty ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool UpdateAnimationControllers ( void ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { return false; }; + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + rvAIAction actionWhipAttack; + + enum { + CHAIN_LEFT, + CHAIN_RIGHT, + CHAIN_MAX + }; + + monsterFattyChain_t chains[CHAIN_MAX]; + + float missHeight; + + virtual bool CheckActions ( void ); + + void PlayAttackAnim ( const idVec3& target, int blendFrames ); + + void ResetAllChains ( void ); + void ChainIn ( int chain ); + void ChainOut ( int chain ); + +private: + + // Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + + // Frame Commands + stateResult_t Frame_LeftChainOut ( const stateParms_t& parms ); + stateResult_t Frame_LeftChainIn ( const stateParms_t& parms ); + + stateResult_t Frame_RightChainOut ( const stateParms_t& parms ); + stateResult_t Frame_RightChainIn ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterFatty ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFatty ) +END_CLASS + +/* +================ +rvMonsterFatty::~rvMonsterFatty +================ +*/ +rvMonsterFatty::~rvMonsterFatty ( void ) { + ResetAllChains ( ); +} + +void rvMonsterFatty::InitSpawnArgsVariables ( void ) +{ + chains[CHAIN_LEFT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChain", "chainb1" ) ); + chains[CHAIN_LEFT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChainAttack", "hookb" ) ); + + chains[CHAIN_RIGHT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChain", "chaina1" ) ); + chains[CHAIN_RIGHT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChainAttack", "hooka" ) ); + + missHeight = spawnArgs.GetFloat ( "missHeight", "72" ); +} +/* +================ +rvMonsterFatty::Spawn +================ +*/ +void rvMonsterFatty::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + + // Cache joints + chains[CHAIN_LEFT].out = false; + chains[CHAIN_LEFT].projectile = NULL; + + chains[CHAIN_RIGHT].out = false; + chains[CHAIN_RIGHT].projectile = NULL; + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::Save +================ +*/ +void rvMonsterFatty::Save ( idSaveGame *savefile ) const { + int i; + + actionWhipAttack.Save( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->WriteBool( chains[i].out ); + chains[i].projectile.Save ( savefile ); + } +} + +/* +================ +rvMonsterFatty::Restore +================ +*/ +void rvMonsterFatty::Restore ( idRestoreGame *savefile ) { + int i; + + actionWhipAttack.Restore( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->ReadBool( chains[i].out ); + chains[i].projectile.Restore ( savefile ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::UpdateAnimationControllers +================ +*/ +bool rvMonsterFatty::UpdateAnimationControllers ( void ) { + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + if ( enemy.ent && CheckFOV ( enemy.lastKnownPosition ) ) { + int i; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( enemy.ent->IsType ( idActor::GetClassType ( ) ) ) { + target = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + target = enemy.ent->GetPhysics()->GetOrigin ( ); + } + + if ( !IsEnemyVisible ( ) ) { + target -= enemy.ent->GetPhysics()->GetGravityNormal() * missHeight; + } + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + if ( !chains[i].out ) { + continue; + } + + animator.ClearJoint ( chains[i].orientationJoint ); + GetJointWorldTransform ( chains[i].orientationJoint, gameLocal.time, origin, axis ); + dir = target - origin; + dir.Normalize ( ); + axis.ProjectVector ( dir, localDir ); + animator.SetJointAxis ( chains[i].orientationJoint, JOINTMOD_LOCAL, localDir.ToMat3() ); + } + } + + return true; +} + +/* +================ +rvMonsterFatty::AddDamageEffect +================ +*/ +void rvMonsterFatty::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + /* + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + */ +} + +/* +================ +rvMonsterFatty::CheckAction_WhipAttack +================ +*/ +bool rvMonsterFatty::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterFatty::CheckActions +================ +*/ +bool rvMonsterFatty::CheckActions ( void ) { + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterFatty::CheckAction_WhipAttack, NULL ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterFatty::PlayAttackAnim +================ +*/ +void rvMonsterFatty::PlayAttackAnim ( const idVec3& target, int blendFrames ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -45.0f ) { + animName = "attack4b"; + } else if ( yaw < -20.0f ) { + animName = "attack3b"; + } else if ( yaw < -5.0f ) { + animName = "attack5r"; + } else if ( yaw < 5.0f ) { + animName = "attack5"; + } else if ( yaw < 20.0f ) { + animName = "attack5l"; + } else if ( yaw < 45.0f ) { + animName = "attack2b"; + } else{ + animName = "attack1b"; + } + + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); +} + +/* +================ +rvMonsterFatty::ChainOut +================ +*/ +void rvMonsterFatty::ChainOut ( int chain ) { + idEntity* ent; + idProjectile* proj; + + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_hook" ) ), &ent, false ); + proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + return; + } + + chains[chain].out = true; + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + chains[chain].projectile = proj; + ent->BindToJoint ( this, chains[chain].attackJoint, false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); +} + +/* +================ +rvMonsterFatty::ChainIn +================ +*/ +void rvMonsterFatty::ChainIn ( int chain ) { + chains[chain].out = false; + if ( chains[chain].projectile ) { + delete chains[chain].projectile; + chains[chain].projectile = NULL; + } +} + +/* +================ +rvMonsterFatty::ResetChains +================ +*/ +void rvMonsterFatty::ResetAllChains ( void ) { + int i; + + animator.ClearAllJoints ( ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + ChainIn ( i ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFatty ) + STATE ( "Torso_WhipAttack", rvMonsterFatty::State_Torso_WhipAttack ) + + STATE ( "Frame_LeftChainOut", rvMonsterFatty::Frame_LeftChainOut ) + STATE ( "Frame_LeftChainIn", rvMonsterFatty::Frame_LeftChainIn ) + + STATE ( "Frame_RightChainOut", rvMonsterFatty::Frame_RightChainOut ) + STATE ( "Frame_RightChainIn", rvMonsterFatty::Frame_RightChainIn ) +END_CLASS_STATES + + +/* +================ +rvMonsterFatty::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterFatty::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + PlayAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ResetAllChains ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_RIGHT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_RIGHT ); + return SRESULT_OK; +} diff --git a/source/mpgame/ai/Monster_Gladiator.cpp b/source/mpgame/ai/Monster_Gladiator.cpp new file mode 100644 index 0000000..02f4806 --- /dev/null +++ b/source/mpgame/ai/Monster_Gladiator.cpp @@ -0,0 +1,902 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class rvMonsterGladiator : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGladiator ); + + rvMonsterGladiator ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual int GetDamageForLocation( int damage, int location ); + +// virtual void SetTether ( rvAITether* newTether ); + +protected: + + // Actions + rvAIAction actionRailgunAttack; + + // Blaster attack + int maxShots; + int minShots; + int shots; + int lastShotTime; + + // Shield + bool usingShield; + idEntityPtr shield; + int shieldStartTime; + int shieldWaitTime; + int shieldHitDelay; + //int shieldInDelay; + //int shieldFov; + int shieldHealth; + int shieldConsecutiveHits; + int shieldLastHitTime; + + int railgunHealth; + int railgunDestroyedTime; + int nextTurnTime; + + virtual bool CheckActions ( void ); + void ShowShield ( void ); + void HideShield ( int hideTime=0 ); + void DestroyRailgun ( void ); + +private: + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso states + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldStart ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldEnd ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldFire ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after railgun is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterGladiator ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGladiator ) +END_CLASS + +/* +================ +rvMonsterGladiator::rvMonsterGladiator +================ +*/ +rvMonsterGladiator::rvMonsterGladiator ( ) { + usingShield = false; +} + +void rvMonsterGladiator::InitSpawnArgsVariables ( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + shieldHitDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldHitDelay", "1" ) ); +// shieldInDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldInDelay", "3" ) ); +// shieldFov = spawnArgs.GetInt ( "shieldfov", "90" ); +} +/* +================ +rvMonsterGladiator::Spawn +================ +*/ +void rvMonsterGladiator::Spawn ( void ) { + shieldWaitTime = 0; + shieldStartTime = 0; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + + InitSpawnArgsVariables(); + + shots = 0; + lastShotTime = 0; + + railgunHealth = spawnArgs.GetInt ( "railgunHealth", "100" ); + railgunDestroyedTime = 0; + + actionRailgunAttack.Init ( spawnArgs, "action_railgunAttack", "Torso_RailgunAttack", AIACTIONF_ATTACK ); + + // Disable range attack until using shield + //actionRangedAttack.fl.disabled = true; + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterGladiator::CheckActions + +Overriden to handle taking the shield out and putting it away. Will also ensure the gladiator +stays hidden behind his shield if getting shot at. +================ +*/ +bool rvMonsterGladiator::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f || (turnYaw > 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f || (turnYaw < 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + // Limited actions with shield out + if ( usingShield ) { + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + if ( move.moveCommand == MOVE_TO_ENEMY + && move.fl.moving ) + {//advancing on enemy with shield up + if ( gameLocal.GetTime() - lastShotTime > 1500 ) + {//been at least a second since the last time we fired while moving + if ( !gameLocal.random.RandomInt(2) ) + {//fire! + PerformAction ( "Torso_ShieldFire", 0, true ); + return true; + } + } + } + // Only ranged attack and melee attack are available when using shield + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + ( railgunHealth > 0 + && gameLocal.GetTime() - shieldStartTime > 2000 + && gameLocal.time - pain.lastTakenTime > 500 + && gameLocal.time - combat.shotAtTime > 300 + && gameLocal.GetTime() - shieldLastHitTime > 500 + && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) ) { + shieldWaitTime = 0; + return true; + } + + // see if it's safe to lower it? + if ( gameLocal.GetTime() - shieldStartTime > 2000 ) + {//shield's been up for at least 2 seconds + if ( !enemy.fl.visible || (gameLocal.time - combat.shotAtTime > 1000 && gameLocal.GetTime() - shieldLastHitTime > 1500) ) + { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) + { + PerformAction ( "Torso_ShieldEnd", 4, true ); + return true; + } + } + } + + return false; + } + else + {// Bring the shield out? + if ( combat.tacticalCurrent != AITACTICAL_MELEE || move.fl.done ) + {//not while rushing (NOTE: unless railgun was just destroyed?) + if ( enemy.fl.visible && enemy.fl.inFov ) + { + if ( combat.fl.aware && shieldWaitTime < gameLocal.GetTime() ) + { + if ( gameLocal.time - pain.lastTakenTime <= 1500 + || ( combat.shotAtAngle < 0 && gameLocal.time - combat.shotAtTime < 100 ) + || !gameLocal.random.RandomInt( 20 ) ) + { + if ( !gameLocal.random.RandomInt( 5 ) ) + { + PerformAction ( "Torso_ShieldStart", 4, true ); + return true; + } + } + } + } + } + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterGladiator::ShowShield +================ +*/ +void rvMonsterGladiator::ShowShield ( void ) { + // First time? + if ( !shield ) { + idEntity* ent; + idDict args; + const idDict *shieldDef = gameLocal.FindEntityDefDict( spawnArgs.GetString ( "def_shield" ), false ); + args.Set ( "classname", spawnArgs.GetString ( "def_shield" ) ); + if ( gameLocal.SpawnEntityDef( args, &ent ) ) { + shield = ent; + ent->GetPhysics()->SetClipMask ( 0 ); + ent->GetPhysics()->SetContents ( CONTENTS_RENDERMODEL ); + ent->GetPhysics()->GetClipModel ( )->SetOwner ( this ); + Attach ( ent ); + } + if ( !shield ) { + return; + } + if ( shieldDef && shield->IsType( idAFAttachment::GetClassType() ) ) + { + idAFAttachment* afShield = static_cast(shield.GetEntity()); + if ( afShield ) + { + jointHandle_t joint = animator.GetJointHandle( shieldDef->GetString( "joint" ) ); + afShield->SetBody ( this, shieldDef->GetString( "model" ), joint ); + } + } + } else if ( !shield || !shield->IsHidden() ) { + return; + } + + usingShield = true; + shieldWaitTime = 0; + animPrefix = "shield"; + shieldStartTime = gameLocal.time; +// actionRangedAttack.fl.disabled = false; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StartSound ( "snd_shield_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + + shield->Show ( ); + + SetShaderParm ( 6, gameLocal.time + 2000 ); +} + +/* +================ +rvMonsterGladiator::HideShield +================ +*/ +void rvMonsterGladiator::HideShield ( int hideTime ) { + if ( !shield || shield->IsHidden() ) { + return; + } + + usingShield = false; + animPrefix = ""; + shieldWaitTime = gameLocal.GetTime()+hideTime; +// actionRangedAttack.fl.disabled = true; + shieldHealth = 0; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StopSound ( SND_CHANNEL_ITEM, false ); + + shield->Hide ( ); +} + +/* +================ +rvMonsterGladiator::DestroyRailgun +================ +*/ +void rvMonsterGladiator::DestroyRailgun ( void ) { + HideSurface ( "models/monsters/gladiator/glad_railgun" ); + railgunHealth = -1; + + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_railgun_explode", "gun_main_jt" ) ); + GetJointWorldTransform ( joint, gameLocal.time, origin, axis ); + gameLocal.PlayEffect ( spawnArgs, "fx_railgun_explode", origin, axis ); + PlayEffect ( "fx_railgun_burn", joint, true ); + + GetAFPhysics()->GetBody ( "b_railgun" )->SetClipMask ( 0 ); + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "pain_big"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + railgunDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + actionRailgunAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + actionRangedAttack.chance = 0.25f; + actionRangedAttack.maxRange = 400; + minShots = 5; + maxShots = 15; + + combat.tacticalMaskAvailable &= ~AITACTICAL_HIDE_BIT; + + //temporarily disable this so we can charge and get mad + //FIXME: force MELEE + actionRangedAttack.timer.Add( 6000 ); + actionTimerRangedAttack.Add( 6000 ); + actionMeleeAttack.timer.Reset( actionTime ); + + //drop any tether since we need to advance + //SetTether(NULL); + //nevermind: let scripters handle it + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +================ +rvMonsterGladiator::UpdateRunStatus +================ +*/ +bool rvMonsterGladiator::UpdateRunStatus ( void ) { + // If rushing and moving forward, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.currentDirection == MOVEDIR_FORWARD ) { + move.fl.idealRunning = true; + return move.fl.running != move.fl.idealRunning; + } + + // Alwasy walk with shield out + if ( usingShield ) { + move.fl.idealRunning = false; + return move.fl.running != move.fl.idealRunning; + } + + return idAI::UpdateRunStatus ( ); +} + +/* +============ +rvMonsterGladiator::SetTether +============ +*/ +/* +void rvMonsterGladiator::SetTether ( rvAITether* newTether ) { + if ( railgunHealth <= 0 ) { + //don't allow any tethers! + idAI::SetTether(NULL); + } else { + idAI::SetTether(newTether); + } +} +*/ + +/* +================ +rvMonsterGladiator::FilterTactical +================ +*/ +int rvMonsterGladiator::FilterTactical ( int availableTactical ) { + if ( railgunHealth > 0 ) { // Only let the gladiator rush when he is really close to his enemy + if ( !enemy.range || enemy.range > combat.awareRange ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } else { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } else if ( gameLocal.GetTime() - railgunDestroyedTime < 6000 ) { + availableTactical = AITACTICAL_MELEE_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGladiator::AddDamageEffect +================ +*/ +void rvMonsterGladiator::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( collision.c.material != NULL && (collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ) ) { + // Delay putting shield away and shooting until the shield hasnt been hit for a while + actionRangedAttack.timer.Reset ( actionTime ); + actionRangedAttack.timer.Add ( shieldHitDelay ); + shieldStartTime = gameLocal.time; + return; + } + + return idAI::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +rvMonsterGladiator::GetDamageForLocation +===================== +*/ +int rvMonsterGladiator::GetDamageForLocation( int damage, int location ) { + // If the gun was hit only do damage to it + if ( idStr::Icmp ( GetDamageGroup ( location ), "gun" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( railgunHealth > 0 ){ + railgunHealth -= damage; + if ( railgunHealth <= 0 ) { + DestroyRailgun ( ); + } + } + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterGladiator::CanTurn +================ +*/ +bool rvMonsterGladiator::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterGladiator::Damage +================ +*/ +void rvMonsterGladiator::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef ) + { + if ( usingShield ) + {//shield up + if ( !damageDef->GetString( "filter_electricity", NULL ) ) + {//not by electricity + //If we get hit enough times with shield up, charge forward + if ( gameLocal.GetTime() - shieldLastHitTime > 1500 ) + { + shieldConsecutiveHits = 0; + } + shieldConsecutiveHits++; + shieldLastHitTime = gameLocal.GetTime(); + if ( shieldConsecutiveHits > 20 && combat.tacticalCurrent != AITACTICAL_MELEE && move.fl.done ) + {//really laying into us, move up + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } + + if ( idStr::Icmp ( GetDamageGroup ( location ), "shield" ) == 0 ) + {//Hit in shield + if ( damageDef->GetString( "filter_electricity", NULL ) ) + {//by electricity + shieldHealth -= damageDef->GetInt( "damage" ) * damageScale; + if ( shield ) + { + shield->SetShaderParm( SHADERPARM_MODE, gameLocal.GetTime() + gameLocal.random.RandomInt(1000) + 1000 ); + } + StartSound( "snd_shield_flicker", SND_CHANNEL_ANY, 0, false, NULL ); + if ( shieldHealth <= 0 ) + {//drop it + HideShield( gameLocal.random.RandomInt(3000)+2000 );//FIXME: when it does come back on, flicker back on? + painAnim = "pain_con"; + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + combat.shotAtTime = gameLocal.GetTime(); + } + return; + } + } + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( aifl.pain ) + {//hurt + if ( usingShield ) + {//shield up + //move in! + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } +} + +/* +================ +rvMonsterGladiator::Save +================ +*/ +void rvMonsterGladiator::Save( idSaveGame *savefile ) const { + actionRailgunAttack.Save ( savefile ) ; + + savefile->WriteInt ( shots ); + savefile->WriteInt ( lastShotTime ); + + savefile->WriteBool ( usingShield ); + shield.Save( savefile ); + savefile->WriteInt ( shieldStartTime ); + savefile->WriteInt ( shieldWaitTime ); + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shieldConsecutiveHits ); + savefile->WriteInt ( shieldLastHitTime ); + + savefile->WriteInt ( railgunHealth ); + savefile->WriteInt ( railgunDestroyedTime ); + savefile->WriteInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Save( savefile ); +} + +/* +================ +rvMonsterGladiator::Restore +================ +*/ +void rvMonsterGladiator::Restore( idRestoreGame *savefile ) { + actionRailgunAttack.Restore ( savefile ) ; + + savefile->ReadInt ( shots ); + savefile->ReadInt ( lastShotTime ); + savefile->ReadBool ( usingShield ); + shield.Restore( savefile ); + savefile->ReadInt ( shieldStartTime ); + savefile->ReadInt ( shieldWaitTime ); + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shieldConsecutiveHits ); + savefile->ReadInt ( shieldLastHitTime ); + + savefile->ReadInt ( railgunHealth ); + savefile->ReadInt ( railgunDestroyedTime ); + savefile->ReadInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGladiator::GetDebugInfo +================ +*/ +void rvMonsterGladiator::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_RailgunAttack", aiActionStatusString[actionRailgunAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGladiator ) + STATE ( "State_Killed", rvMonsterGladiator::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterGladiator::State_Torso_BlasterAttack ) + STATE ( "Torso_RailgunAttack", rvMonsterGladiator::State_Torso_RailgunAttack ) + STATE ( "Torso_ShieldStart", rvMonsterGladiator::State_Torso_ShieldStart ) + STATE ( "Torso_ShieldEnd", rvMonsterGladiator::State_Torso_ShieldEnd ) + + STATE ( "Torso_TurnRight90", rvMonsterGladiator::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterGladiator::State_Torso_TurnLeft90 ) + STATE ( "Torso_ShieldFire", rvMonsterGladiator::State_Torso_ShieldFire ) + +END_CLASS_STATES + +/* +================ +rvMonsterGladiator::State_Killed +================ +*/ +stateResult_t rvMonsterGladiator::State_Killed ( const stateParms_t& parms ) { + HideShield ( ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldStart +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldStart ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + ShowShield ( ); + PlayAnim ( ANIMCHANNEL_TORSO, "start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + SetShaderParm ( 6, 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldEnd +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldEnd ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) //anim done + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + HideShield ( 2000 ); + actionRailgunAttack.timer.Reset( actionTime ); + actionMeleeAttack.timer.Reset( actionTime ); + actionRangedAttack.timer.Reset( actionTime ); + actionTimerRangedAttack.Reset( actionTime ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_start", parms.blendFrames ); + //shots = 4; + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_start" ) ) {//anim changed + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + if ( --shots <= 0 || !enemy.fl.inFov || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGladiator::State_Torso_RailgunAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_RailgunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( usingShield ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ShieldEnd", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RailgunAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "railgun_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "railgun_attack" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_right" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_left" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldFire +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldFire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + PlayCycle( ANIMCHANNEL_TORSO, "walk_aim", 1 ); + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + Attack( "blaster", animator.GetJointHandle( "lft_wrist_jt"), GetEnemy() ); + PlayEffect( "fx_blaster_flash", animator.GetJointHandle("lft_wrist_jt") ); + lastShotTime = gameLocal.GetTime(); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( move.fl.done ) + { + return SRESULT_DONE; + } + if ( (gameLocal.GetTime()-lastShotTime) >= 250 ) { + shots--; + if ( GetEnemy() && shots > 0 ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } + PlayCycle( ANIMCHANNEL_TORSO, "walk", 1 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_Grunt.cpp b/source/mpgame/ai/Monster_Grunt.cpp new file mode 100644 index 0000000..e72980f --- /dev/null +++ b/source/mpgame/ai/Monster_Grunt.cpp @@ -0,0 +1,318 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGrunt : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGrunt ); + + rvMonsterGrunt ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void AdjustHealthByDamage ( int damage ); + +protected: + + rvAIAction actionMeleeMoveAttack; + rvAIAction actionChaingunAttack; + + virtual bool CheckActions ( void ); + + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnDeath ( void ); + +private: + + int standingMeleeNoAttackTime; + int rageThreshold; + + void RageStart ( void ); + void RageStop ( void ); + + // Torso States + stateResult_t State_Torso_Enrage ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_LeapAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGrunt ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGrunt ) +END_CLASS + +/* +================ +rvMonsterGrunt::rvMonsterGrunt +================ +*/ +rvMonsterGrunt::rvMonsterGrunt ( void ) { + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterGrunt::Spawn +================ +*/ +void rvMonsterGrunt::Spawn ( void ) { + rageThreshold = spawnArgs.GetInt ( "health_rageThreshold" ); + + // Custom actions + actionMeleeMoveAttack.Init ( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", NULL, AIACTIONF_ATTACK ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", "Torso_LeapAttack", AIACTIONF_ATTACK ); + + // Enraged to start? + if ( spawnArgs.GetBool ( "preinject" ) ) { + RageStart ( ); + } +} + +/* +================ +rvMonsterGrunt::Save +================ +*/ +void rvMonsterGrunt::Save ( idSaveGame *savefile ) const { + actionMeleeMoveAttack.Save( savefile ); + actionChaingunAttack.Save( savefile ); + + savefile->WriteInt( rageThreshold ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::Restore +================ +*/ +void rvMonsterGrunt::Restore ( idRestoreGame *savefile ) { + actionMeleeMoveAttack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + savefile->ReadInt( rageThreshold ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::RageStart +================ +*/ +void rvMonsterGrunt::RageStart ( void ) { + SetShaderParm ( 6, 1 ); + + // Disable non-rage actions + actionEvadeLeft.fl.disabled = true; + actionEvadeRight.fl.disabled = true; + + // Speed up animations + animator.SetPlaybackRate ( 1.25f ); + + // Disable pain + pain.threshold = 0; + + // Start over with health when enraged + health = spawnArgs.GetInt ( "health" ); + + // No more going to rage + rageThreshold = 0; +} + +/* +================ +rvMonsterGrunt::RageStop +================ +*/ +void rvMonsterGrunt::RageStop ( void ) { + SetShaderParm ( 6, 0 ); +} + +/* +================ +rvMonsterGrunt::CheckActions +================ +*/ +bool rvMonsterGrunt::CheckActions ( void ) { + // If our health is below the rage threshold then enrage + if ( health < rageThreshold ) { + PerformAction ( "Torso_Enrage", 4, true ); + return true; + } + + // Moving melee attack? + if ( PerformAction ( &actionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + + // Default actions + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + //allow ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} + +/* +================ +rvMonsterGrunt::OnDeath +================ +*/ +void rvMonsterGrunt::OnDeath ( void ) { + RageStop ( ); + return idAI::OnDeath ( ); +} + +/* +================ +rvMonsterGrunt::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGrunt::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterGrunt::AdjustHealthByDamage +===================== +*/ +void rvMonsterGrunt::AdjustHealthByDamage ( int damage ) { + // Take less damage during enrage process + if ( rageThreshold && health < rageThreshold ) { + health -= (damage * 0.25f); + return; + } + return idAI::AdjustHealthByDamage ( damage ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGrunt ) + STATE ( "Torso_Enrage", rvMonsterGrunt::State_Torso_Enrage ) + STATE ( "Torso_Pain", rvMonsterGrunt::State_Torso_Pain ) + STATE ( "Torso_LeapAttack", rvMonsterGrunt::State_Torso_LeapAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGrunt::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Pain ( const stateParms_t& parms ) { + // Stop streaming pain if its time to get angry + if ( pain.loopEndTime && health < rageThreshold ) { + pain.loopEndTime = 0; + } + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterGrunt::State_Torso_Enrage +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Enrage ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "anger", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + RageStart ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGrunt::State_Torso_LeapAttack +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_LeapAttack ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + lastAttackTime = 0; + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + // If we missed our leap attack get angry + if ( !lastAttackTime && rageThreshold ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Enrage", parms.blendFrames ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_Gunner.cpp b/source/mpgame/ai/Monster_Gunner.cpp new file mode 100644 index 0000000..155e8a5 --- /dev/null +++ b/source/mpgame/ai/Monster_Gunner.cpp @@ -0,0 +1,448 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGunner : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGunner ); + + rvMonsterGunner ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int shots; + int shotsFired; + idStr nailgunPrefix; + int nailgunMinShots; + int nailgunMaxShots; + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + // Actions + rvAIAction actionGrenadeAttack; + rvAIAction actionNailgunAttack; + + rvAIAction actionSideStepLeft; + rvAIAction actionSideStepRight; + rvAIActionTimer actionTimerSideStep; + + bool CheckAction_SideStepLeft ( rvAIAction* action, int animNum ); + bool CheckAction_SideStepRight ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_NailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGunner ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGunner ) +END_CLASS + +/* +================ +rvMonsterGunner::rvMonsterGunner +================ +*/ +rvMonsterGunner::rvMonsterGunner ( ) { + nextShootTime = 0; +} + + +void rvMonsterGunner::InitSpawnArgsVariables( void ) +{ + nailgunMinShots = spawnArgs.GetInt ( "action_nailgunAttack_minshots", "5" ); + nailgunMaxShots = spawnArgs.GetInt ( "action_nailgunAttack_maxshots", "20" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.3" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterGunner::Spawn +================ +*/ +void rvMonsterGunner::Spawn ( void ) { + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + actionNailgunAttack.Init ( spawnArgs, "action_nailgunAttack", "Torso_NailgunAttack", AIACTIONF_ATTACK ); + actionSideStepLeft.Init ( spawnArgs, "action_sideStepLeft", NULL, 0 ); + actionSideStepRight.Init ( spawnArgs, "action_sideStepRight", NULL, 0 ); + actionTimerSideStep.Init ( spawnArgs, "actionTimer_sideStep" ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGunner::Save +================ +*/ +void rvMonsterGunner::Save ( idSaveGame *savefile ) const { + actionGrenadeAttack.Save ( savefile ); + actionNailgunAttack.Save ( savefile ); + actionSideStepLeft.Save ( savefile ); + actionSideStepRight.Save ( savefile ); + actionTimerSideStep.Save ( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + savefile->WriteString ( nailgunPrefix ); + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterGunner::Restore +================ +*/ +void rvMonsterGunner::Restore ( idRestoreGame *savefile ) { + actionGrenadeAttack.Restore ( savefile ); + actionNailgunAttack.Restore ( savefile ); + actionSideStepLeft.Restore ( savefile ); + actionSideStepRight.Restore ( savefile ); + actionTimerSideStep.Restore ( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + savefile->ReadString ( nailgunPrefix ); + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterGunner::OnStopMoving +============ +*/ +void rvMonsterGunner::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + {//hiding + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE || enemy.range <= combat.meleeRange ) + {//in melee state or in melee range + actionMeleeAttack.timer.Clear( actionTime ); + } + else if ( (!actionNailgunAttack.timer.IsDone(actionTime) || !actionTimerRangedAttack.IsDone(actionTime)) + && (!actionGrenadeAttack.timer.IsDone(actionTime) || !actionTimerSpecialAttack.IsDone(actionTime)) ) + {//no attack is ready + //Ready at least one of them + if ( gameLocal.random.RandomInt(3) ) + { + actionNailgunAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + } + else + { + actionGrenadeAttack.timer.Clear( actionTime ); + actionTimerSpecialAttack.Clear( actionTime ); + } + } + } +} + +/* +================ +rvMonsterGunner::UpdateRunStatus +================ +*/ +bool rvMonsterGunner::UpdateRunStatus ( void ) { + move.fl.idealRunning = false; + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterGunner::FilterTactical +================ +*/ +int rvMonsterGunner::FilterTactical ( int availableTactical ) { + if ( !move.fl.moving && enemy.range > combat.meleeRange ) + {//keep moving! + if ( (!actionNailgunAttack.timer.IsDone(actionTime+500) || !actionTimerRangedAttack.IsDone(actionTime+500)) + && (!actionGrenadeAttack.timer.IsDone(actionTime+500) || !actionTimerSpecialAttack.IsDone(actionTime+500)) ) + {//won't be attacking in the next 1 second + combat.tacticalUpdateTime = 0; + availableTactical |= (AITACTICAL_MELEE_BIT); + if ( !gameLocal.random.RandomInt(2) ) + { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGunner::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGunner::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + //walk for at least 2 seconds (default update time of 500 is too short) + combat.tacticalUpdateTime = gameLocal.GetTime() + 2000 + gameLocal.random.RandomInt(1000); + break; + } +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepLeft +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepLeft ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the left? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepRight +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepRight ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove ( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the right? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckActions +================ +*/ +bool rvMonsterGunner::CheckActions ( void ) { + // Fire a grenade? + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) || + PerformAction ( &actionNailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + bool action = idAI::CheckActions( ); + if ( !action ) { + //try a strafe + if ( GetEnemy() && enemy.fl.visible && gameLocal.GetTime()-lastAttackTime > actionTimerRangedAttack.GetRate()+1000 ) { + //we can see our enemy but haven't been able to shoot him in a while... + if ( PerformAction ( &actionSideStepLeft, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepLeft, &actionTimerSideStep ) + || PerformAction ( &actionSideStepRight, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepRight, &actionTimerSideStep ) ) { + return true; + } + } + } + return action; +} + +/* +===================== +rvMonsterGunner::GetDebugInfo +===================== +*/ +void rvMonsterGunner::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_grenadeAttack", aiActionStatusString[actionGrenadeAttack.status], userData ); + proc ( "idAI", "action_nailgunAttack", aiActionStatusString[actionNailgunAttack.status], userData ); + proc ( "idAI", "actionSideStepLeft", aiActionStatusString[actionSideStepLeft.status], userData ); + proc ( "idAI", "actionSideStepRight", aiActionStatusString[actionSideStepRight.status], userData ); +} + +bool rvMonsterGunner::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + actionTimerRangedAttack.Clear( actionTime ); + actionNailgunAttack.timer.Clear( actionTime ); + return (idAI::Pain( inflictor, attacker, damage, dir, location )); +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGunner ) + STATE ( "Torso_NailgunAttack", rvMonsterGunner::State_Torso_NailgunAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterGunner::State_Torso_MovingRangedAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGunner::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "nail", attackJoint, GetEnemy() ); + PlayEffect( "fx_nail_flash", attackJoint ); + } + StartSound( "snd_nailgun_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= nailgunMinShots) || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGunner::State_Torso_NailgunAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_NailgunAttack ( const stateParms_t& parms ) { + static const char* nailgunAnims [ ] = { "nailgun_short", "nailgun_long" }; + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && !actionNailgunAttack.fl.overrideLegs && FacingIdeal() && !gameLocal.random.RandomInt(1) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + DisableAnimState ( ANIMCHANNEL_LEGS ); + shotsFired = 0; + nailgunPrefix = nailgunAnims[shots%2]; + if ( !CanHitEnemyFromAnim( GetAnim( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ) ) ) ) + {//this is hacky, but we really need to test the attack anim first since they're so different + //can't hit with this one, just use the other one... + nailgunPrefix = nailgunAnims[(shots+1)%2]; + } + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_start", nailgunPrefix.c_str() ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ), 0 ); + shotsFired++; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (shotsFired >= nailgunMinShots && !enemy.fl.inFov) || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_end", nailgunPrefix.c_str() ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_Harvester.cpp b/source/mpgame/ai/Monster_Harvester.cpp new file mode 100644 index 0000000..dd6ccb7 --- /dev/null +++ b/source/mpgame/ai/Monster_Harvester.cpp @@ -0,0 +1,1190 @@ +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +class rvMonsterHarvester : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvester ); + + rvMonsterHarvester ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + + virtual bool UpdateAnimationControllers ( void ); + bool CanTurn ( void ) const; + virtual int GetDamageForLocation ( int damage, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + enum { + WHIP_LEFT, + WHIP_CENTER, + WHIP_RIGHT, + WHIP_MAX + }; + + enum { + PART_ARM_R, + PART_ARM_L, + PART_LEG_FR, + PART_LEG_FL, + PART_LEG_BR, + PART_LEG_BL, + PART_TANK_R, + PART_TANK_L, + PARTS_MAX + }; + idStr partLocation[PARTS_MAX]; + idStr partSurf[PARTS_MAX]; + idStr partJoint[PARTS_MAX]; + int partHealth[PARTS_MAX]; + void DestroyPart ( int part ); + + rvAIAction actionWhipAttack; + rvAIAction actionSprayAttack; + rvAIAction actionRocketAttack; + rvAIAction actionGrenadeAttack; + + jointHandle_t whipJoints[WHIP_MAX]; + idEntityPtr whipProjectiles[WHIP_MAX]; + + jointHandle_t jointLeftMuzzle; + jointHandle_t jointRightMuzzle; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + const char* GetMeleeAttackAnim ( const idVec3& target ); + bool PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ); + const char* GetRangedAttackAnim ( const idVec3& target ); + bool PlayRangedAttackAnim ( const idVec3& target, int blendFrames ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + + void DropLeg ( int part ); +// Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RocketAttack( rvAIAction* action, int animNum ); + bool CheckAction_GrenadeAttack( rvAIAction* action, int animNum ); + + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ClawAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvester ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvester ) +END_CLASS + +/* +================ +rvMonsterHarvester::rvMonsterHarvester +================ +*/ +rvMonsterHarvester::rvMonsterHarvester ( void ) { +} + +void rvMonsterHarvester::InitSpawnArgsVariables( void ) +{ + whipJoints[WHIP_LEFT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_left" ) ); + whipJoints[WHIP_RIGHT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_right" ) ); + whipJoints[WHIP_CENTER] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_center" ) ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partLocation[part] = spawnArgs.GetString( va("part_%d_location",part), "" ); + partSurf[part] = spawnArgs.GetString( va("part_%d_surf",part), "" ); + partJoint[part] = spawnArgs.GetString( va("part_%d_joint",part), "" ); + } + + jointLeftMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_left_arm" ) ); + jointRightMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_right_arm" ) ); +} +/* +================ +rvMonsterHarvester::Spawn +================ +*/ +void rvMonsterHarvester::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + whipProjectiles[i] = NULL; + } + + InitSpawnArgsVariables(); + shots = 0; + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partHealth[part] = spawnArgs.GetInt( va("part_%d_health",part), "500" ); + } +} + +/* +================ +rvMonsterHarvester::Save +================ +*/ +void rvMonsterHarvester::Save ( idSaveGame *savefile ) const { + actionWhipAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->WriteObject( whipProjectiles[i] ); + } + + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->WriteInt( partHealth[part] ); + } +} + +/* +================ +rvMonsterHarvester::Restore +================ +*/ +void rvMonsterHarvester::Restore ( idRestoreGame *savefile ) { + actionWhipAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->ReadObject( reinterpret_cast( whipProjectiles[i] ) ); + } + + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->ReadInt( partHealth[part] ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterHarvester::UpdateAnimationControllers +================ +*/ +bool rvMonsterHarvester::UpdateAnimationControllers ( void ) { + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + return true; +} + +/* +===================== +rvMonsterHarvester::SkipImpulse +===================== +*/ +bool rvMonsterHarvester::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +void rvMonsterHarvester::DropLeg( int part ) +{ + jointHandle_t joint = INVALID_JOINT; + const char* legDef = NULL; + switch ( part ) + { + case PART_LEG_FR: + joint = animator.GetJointHandle( "r_toe_front" ); + legDef = "def_leg_part1"; + break; + case PART_LEG_FL: + joint = animator.GetJointHandle( "l_toe_front" ); + legDef = "def_leg_part2"; + break; + case PART_LEG_BR: + joint = animator.GetJointHandle( "r_toe_back" ); + legDef = "def_leg_part4"; + break; + case PART_LEG_BL: + joint = animator.GetJointHandle( "l_toe_back" ); + legDef = "def_leg_part3"; + break; + } + if ( joint != INVALID_JOINT ) + { + idEntity* leg = gameLocal.SpawnEntityDef( spawnArgs.GetString( legDef ) ); + if ( leg ) + { + idVec3 jointOrg; + idMat3 jointAxis; + animator.GetJointTransform( joint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + leg->GetPhysics()->SetOrigin( jointOrg ); + leg->GetPhysics()->SetAxis( jointAxis*renderEntity.axis ); + leg->PlayEffect( "fx_trail", vec3_origin, jointAxis, true, vec3_origin, true ); + if ( leg->IsType( idDamagable::GetClassType() ) ) + {//don't be destroyed for at least 5 seconds + ((idDamagable*)leg)->invincibleTime = gameLocal.GetTime() + 5000; + } + // push it + if ( jointOrg.z > GetPhysics()->GetOrigin().z+20.0f ) + {//leg was blown off while in the air... + jointHandle_t attachJoint = animator.GetJointHandle(partJoint[part].c_str()); + if ( attachJoint != INVALID_JOINT ) + { + animator.GetJointTransform( attachJoint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + + idVec3 impulse = leg->GetPhysics()->GetCenterMass() - jointOrg; + impulse.z = 0; + impulse.Normalize(); + impulse *= ((gameLocal.random.RandomFloat()*3.0f)+2.0f) * 1000;//away + impulse.z = (gameLocal.random.CRandomFloat()*500.0f)+1000.0f;//up! + leg->ApplyImpulse( this, 0, jointOrg, impulse ); + } + } + } + } +} + +void rvMonsterHarvester::DestroyPart( int part ) +{ + idStr explodeFX; + idStr trailFX; + switch ( part ) + { + case PART_ARM_R: + if ( partHealth[PART_ARM_L] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + case PART_ARM_L: + if ( partHealth[PART_ARM_R] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + //FIXME: spawn leg func_movables + case PART_LEG_FR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_frt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_FL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_flt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_brt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_blt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_TANK_R: + if ( partHealth[PART_TANK_L] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + case PART_TANK_L: + if ( partHealth[PART_TANK_R] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + } + HideSurface( partSurf[part].c_str() ); + PlayEffect( explodeFX, animator.GetJointHandle(partJoint[part].c_str()) ); + PlayEffect( trailFX, animator.GetJointHandle(partJoint[part].c_str()), true ); + //make sure it plays this pain + actionTimerPain.Reset ( actionTime ); +} + +/* +================ +rvMonsterHarvester::CanTurn +================ +*/ +bool rvMonsterHarvester::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); +} + +/* +===================== +rvMonsterHarvester::GetDamageForLocation +===================== +*/ +int rvMonsterHarvester::GetDamageForLocation( int damage, int location ) { + // If the part was hit only do damage to it + const char* dmgGroup = GetDamageGroup ( location ); + if ( dmgGroup ) + { + for ( int part = 0; part < PARTS_MAX; part++ ) + { + if ( idStr::Icmp ( dmgGroup, partLocation[part].c_str() ) == 0 ) + { + if ( partHealth[part] > 0 ) + { + partHealth[part] -= damage; + painAnim = "pain"; + if ( partHealth[part] <= 0 ) + { + if ( animPrefix.Length() + && (part == PART_LEG_FR + || part == PART_LEG_FL + || part == PART_LEG_BR + || part == PART_LEG_BL ) ) + {//just blew off a leg and already had one blown off... + DestroyPart( part ); + //we dead + health = 0; + return damage; + } + else + { + //FIXME: big pain? + DestroyPart( part ); + } + } + else + { + if ( !animPrefix.Length() ) + { + switch ( part ) + { + case PART_LEG_FR: + painAnim = "leg_pain_fr"; + break; + case PART_LEG_FL: + painAnim = "leg_pain_fl"; + break; + case PART_LEG_BR: + painAnim = "leg_pain_br"; + break; + case PART_LEG_BL: + painAnim = "leg_pain_bl"; + break; + } + } + } + + if ( pain.threshold < damage && health > 0 ) + //if ( move.anim_turn_angles == 0.0f ) + {//not in the middle of a turn + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + } + //pain.takenThisFrame = damage; + return 0; + } + } + } + + if ( health <= spawnArgs.GetInt( "death_damage_threshold" ) + && spawnArgs.GetInt( "death_damage_threshold" ) > damage ) + {//doesn't meet the minimum damage requirements to kill us + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterHarvester::Damage +================ +*/ +void rvMonsterHarvester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + //don't take damage from ourselves + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHarvester::CheckAction_SprayAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !CheckFOV( GetEnemy()->GetEyePosition(), 20.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RocketAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RocketAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_GrenadeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_GrenadeAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_WhipAttack +================:: +*/ +bool rvMonsterHarvester::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_MeleeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 90 ) ) { + return false; + } + if ( !GetMeleeAttackAnim( enemy.ent->GetEyePosition() ) ) + { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( idAI::CheckAction_RangedAttack(action,animNum) && enemy.ent && GetRangedAttackAnim( enemy.ent->GetEyePosition() ) ); +} + +/* +================ +rvMonsterHarvester::CheckActions +================ +*/ +bool rvMonsterHarvester::CheckActions ( void ) { + + // such a dirty hack... I'm not sure what is actually wrong, but somehow nextTurnTime is getting to be a rediculously high number. + // I have some more significant bugs that really need to be solved, so for now, this will have to do. + if ( nextTurnTime > gameLocal.time + 500 ) { + nextTurnTime = gameLocal.time-1; + } + + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.6f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.6f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_WhipAttack, NULL ) ) { + return true; + } + if ( PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_SprayAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_RocketAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_GrenadeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterHarvester::FilterTactical +================ +*/ +int rvMonsterHarvester::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT|AITACTICAL_RANGED_BITS|AITACTICAL_MELEE_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvester ) + STATE ( "State_Killed", rvMonsterHarvester::State_Killed ) + STATE ( "State_Dead", rvMonsterHarvester::State_Dead ) + + STATE ( "Torso_WhipAttack", rvMonsterHarvester::State_Torso_WhipAttack ) + STATE ( "Torso_ClawAttack", rvMonsterHarvester::State_Torso_ClawAttack ) + STATE ( "Torso_RangedAttack", rvMonsterHarvester::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvester::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvester::State_Torso_TurnLeft90 ) + STATE ( "Torso_SprayAttack", rvMonsterHarvester::State_Torso_SprayAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHarvester::State_Torso_RocketAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvester::State_Killed +================ +*/ +stateResult_t rvMonsterHarvester::State_Killed ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + int numLegsLost = 0; + for ( int i = PART_LEG_FR; i <= PART_LEG_BL; i++ ) { + if ( partHealth[i] <= 0 ) { + numLegsLost++; + } + } + if ( numLegsLost > 1 ) { + //dmg_death when 2 legs are blown off + PlayAnim( ANIMCHANNEL_TORSO, "dmg_death", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "death", 0 ); + } + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterHarvester::State_Dead +================ +*/ +stateResult_t rvMonsterHarvester::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterHarvester::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + idEntity* ent; + int i; + + for ( i = 0; i < WHIP_MAX; i ++ ) { + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_whip" ) ), &ent, false ); + idProjectile* proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + continue; + } + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + whipProjectiles[i] = proj; + ent->BindToJoint ( this, whipJoints[i], false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + delete whipProjectiles[i]; + whipProjectiles[i] = NULL; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvMonsterHarvester::Attack +===================== +*/ +bool rvMonsterHarvester::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + //NOTE: this stops spawning of projectile, but not muzzle flash... oh well... + if ( joint == jointLeftMuzzle && partHealth[PART_ARM_L] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + if ( joint == jointRightMuzzle && partHealth[PART_ARM_R] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + return idAI::Attack( attackName, joint, target, pushVelocity ); +} + +/* +================ +rvMonsterHarvester::GetMeleeAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -10.0f ) { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw_rt"; + } else if ( yaw > 10.0f ) { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw_lt"; + } else{ + if ( gameLocal.random.RandomFloat() < 0.5f || partHealth[PART_LEG_FR] <= 0 ) + { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw"; + } + else + { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw"; + } + } + return animName; +} +/* +================ +rvMonsterHarvester::PlayMeleeAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetMeleeAttackAnim( target ); + if ( animName ) + { + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::GetRangedAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetRangedAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName = NULL; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -20.0f ) { + if ( partHealth[PART_ARM_R] <= 0 ) + { + return NULL; + } + animName = "fire_right"; + } else if ( yaw > 20.0f ) { + if ( partHealth[PART_ARM_L] <= 0 ) + { + return NULL; + } + animName = "fire_left"; + } else{ + animName = "fire_forward"; + } + + return animName; +} + +/* +================ +rvMonsterHarvester::PlayRangedAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayRangedAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetRangedAttackAnim( target ); + if ( animName ) + { + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::State_Torso_ClawAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_ClawAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + if ( !PlayMeleeAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ) ) + { + return SRESULT_DONE; + } + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent || !PlayRangedAttackAnim ( enemy.ent->GetEyePosition(), 0 ) ) + { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) + { + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_STAGE ( STAGE_FIRE ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "missile_fire_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "missile_fire_start", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + if ( partHealth[PART_TANK_L] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("l_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("l_hopper_muzzle_flash"), enemy.ent ); + } + if ( partHealth[PART_TANK_R] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("r_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("r_hopper_muzzle_flash"), enemy.ent ); + } + + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_DONE; + } + + PlayAnim ( ANIMCHANNEL_TORSO, "attack_rocket", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "attack_rocket", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_HarvesterDispersal.cpp b/source/mpgame/ai/Monster_HarvesterDispersal.cpp new file mode 100644 index 0000000..132ad41 --- /dev/null +++ b/source/mpgame/ai/Monster_HarvesterDispersal.cpp @@ -0,0 +1,394 @@ +/* +================ +rvMonsterHarvesterDispersal.cpp + +AI for the Harvester on dispersal +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterHarvesterDispersal : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvesterDispersal ); + + rvMonsterHarvesterDispersal ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + rvAIAction actionSprayScream; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + +// Custom actions + virtual bool CheckAction_SprayScream( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_SprayScream ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvesterDispersal ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvesterDispersal ) +END_CLASS + +/* +================ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal +================ +*/ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal ( void ) { +} + +void rvMonsterHarvesterDispersal::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterHarvesterDispersal::Spawn +================ +*/ +void rvMonsterHarvesterDispersal::Spawn ( void ) { + actionSprayScream.Init ( spawnArgs, "action_sprayScream", "State_SprayScream", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterHarvesterDispersal::Save +================ +*/ +void rvMonsterHarvesterDispersal::Save ( idSaveGame *savefile ) const { + actionSprayScream.Save( savefile ); + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); +} + +/* +================ +rvMonsterHarvesterDispersal::Restore +================ +*/ +void rvMonsterHarvesterDispersal::Restore ( idRestoreGame *savefile ) { + actionSprayScream.Restore( savefile ); + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvMonsterHarvesterDispersal::SkipImpulse +===================== +*/ +bool rvMonsterHarvesterDispersal::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +/* +================ +rvMonsterHarvesterDispersal::CanTurn +================ +*/ +bool rvMonsterHarvesterDispersal::CanTurn ( void ) const { + return false; + /* + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); + */ +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( enemy.ent && idAI::CheckAction_RangedAttack( action, animNum ) && IsEnemyRecentlyVisible( ) ); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_SprayScream +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_SprayScream ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + return (!IsEnemyRecentlyVisible()); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckActions +================ +*/ +bool rvMonsterHarvesterDispersal::CheckActions ( void ) { + + // If not moving, try turning in place + /* + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.8f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.8f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + */ + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( idAI::CheckActions() ) { + return true; + } + + if ( PerformAction ( &actionSprayScream, (checkAction_t)&rvMonsterHarvesterDispersal::CheckAction_SprayScream ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterHarvesterDispersal::FilterTactical +================ +*/ +int rvMonsterHarvesterDispersal::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvesterDispersal ) + STATE ( "Torso_RangedAttack", rvMonsterHarvesterDispersal::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ) + STATE ( "State_SprayScream", rvMonsterHarvesterDispersal::State_SprayScream ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent ) + { + return SRESULT_DONE; + } + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_SprayScream ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH, + STAGE_SCREAM, + STAGE_FINISH_SCREAM + }; + if ( parms.stage < STAGE_SCREAM && IsEnemyRecentlyVisible() ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + lookTarget = GetEnemy(); + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) { + return SRESULT_STAGE ( STAGE_SWEEP ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam on + PlayAnim ( ANIMCHANNEL_TORSO, "dispersal_vent", 0 ); + return SRESULT_STAGE ( STAGE_FINISH_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_FINISH_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam off + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/mpgame/ai/Monster_HeavyHoverTank.cpp b/source/mpgame/ai/Monster_HeavyHoverTank.cpp new file mode 100644 index 0000000..6f2c6cb --- /dev/null +++ b/source/mpgame/ai/Monster_HeavyHoverTank.cpp @@ -0,0 +1,534 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +class rvMonsterHeavyHoverTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHeavyHoverTank ); + + rvMonsterHeavyHoverTank ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + +protected: + + enum weaponState_t { + WEAPONSTATE_BLASTER, + WEAPONSTATE_ROCKET, + WEAPONSTATE_MAX + }; + + int blasterAnimIndex; + int shots; + weaponState_t weaponStateCurrent; + weaponState_t weaponStateIdeal; + + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionStrafe; + + jointHandle_t jointHoverEffect; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover; + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + +// virtual const char* GetIdleAnimName ( void ); + +private: + + float strafeSpeed; + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ChangeWeaponState ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHeavyHoverTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHeavyHoverTank ) +END_CLASS + +/* +================ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank +================ +*/ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank ( ) { + weaponStateIdeal = WEAPONSTATE_BLASTER; + weaponStateCurrent = weaponStateIdeal; + + effectDust = NULL; + effectHover = NULL; + + blasterAnimIndex = 0; + shots = 0; + + strafeSpeed = 0; +} + +void rvMonsterHeavyHoverTank::InitSpawnArgsVariables( void ) +{ + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover") ); + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "300" ); +} +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +void rvMonsterHeavyHoverTank::Spawn ( void ) { + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + + InitSpawnArgsVariables(); + + if ( jointHoverEffect != INVALID_JOINT ) { + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + } +} + +/* +================ +rvMonsterHeavyHoverTank::Think +================ +*/ +void rvMonsterHeavyHoverTank::Think ( void ) { + idAI::Think ( ); + + // If thinking we should play an effect on the ground under us + if ( jointHoverEffect != INVALID_JOINT ) { + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 128 units down from the hover effect joint + GetJointWorldTransform ( jointHoverEffect, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * 128.0f, CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + } +} + +/* +================ +rvMonsterHeavyHoverTank::Save +================ +*/ +void rvMonsterHeavyHoverTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( blasterAnimIndex ); + savefile->WriteInt ( shots ); + savefile->WriteInt ( (int)weaponStateCurrent ); + savefile->WriteInt ( (int)weaponStateIdeal ); + + actionRocketAttack.Save ( savefile ); + actionBlasterAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + + effectDust.Save ( savefile ); + effectHover.Save ( savefile ); +} + +/* +================ +rvMonsterHeavyHoverTank::Restore +================ +*/ +void rvMonsterHeavyHoverTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( blasterAnimIndex ); + savefile->ReadInt ( shots ); + savefile->ReadInt ( (int&)weaponStateCurrent ); + savefile->ReadInt ( (int&)weaponStateIdeal ); + + actionRocketAttack.Restore ( savefile ); + actionBlasterAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + + effectDust.Restore ( savefile ); + effectHover.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +void rvMonsterHeavyHoverTank::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + if ( damageScale > 0.0f ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + //push me hard! + float push = idMath::ClampFloat( 250.0f, 500.0f, damageScale*1000.0f ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += dir * push; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHeavyHoverTank::OnDeath +================ +*/ +void rvMonsterHeavyHoverTank::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + if ( effectHover ) { + effectHover->Stop ( ); + effectHover = NULL; + } + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterHeavyHoverTank::CheckAction_Strafe +================ +*/ +bool rvMonsterHeavyHoverTank::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +bool rvMonsterHeavyHoverTank::CheckActions ( void ) { + if ( weaponStateIdeal != weaponStateCurrent ) { + PerformAction ( "Torso_ChangeWeaponState", 4 ); + return true; + } + + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterHeavyHoverTank::CheckAction_Strafe ) ) + { + return true; + } + return false; +} + +/* +================ +rvMonsterHeavyHoverTank::OnEnemyChange +================ +*/ +void rvMonsterHeavyHoverTank::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } + + if ( enemy.ent->IsType ( rvVehicle::GetClassType() ) ) { + weaponStateIdeal = WEAPONSTATE_ROCKET; + } +} + +/* +================ +rvMonsterHeavyHoverTank::GetIdleAnimName +================ +*/ +/* +const char* rvMonsterHeavyHoverTank::GetIdleAnimName ( void ) { + if ( weaponStateCurrent == WEAPONSTATE_ROCKET ) { + return "rocket_idle"; + } + + return idAI::GetIdleAnimName ( ); +} +*/ + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHeavyHoverTank ) + STATE ( "Torso_BlasterAttack", rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHeavyHoverTank::State_Torso_RocketAttack ) + STATE ( "Torso_ChangeWeaponState", rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ) + STATE ( "Torso_EvadeLeft", rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterHeavyHoverTank::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterHeavyHoverTank::State_Torso_Strafe ) +END_CLASS_STATES + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_BLASTER; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BlasterAttack", parms.blendFrames ); + return SRESULT_DONE; + } + shots = gameLocal.random.RandomInt ( 8 ) + 4; + blasterAnimIndex = (blasterAnimIndex + 1) % 2; + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_preshoot", blasterAnimIndex + 1 ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_fire", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_postshoot", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_ROCKET; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RocketAttack", parms.blendFrames ); + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ( const stateParms_t& parms ) { + static const char* stateAnims [ WEAPONSTATE_MAX ] [ WEAPONSTATE_MAX ] = { + { NULL, "blaster_to_rocket" }, // WEAPONSTATE_BLASTER + { "rocket_to_blaster", NULL }, // WEAPONSTATE_ROCKET + }; + + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // No anim for that transition? + if ( stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ] == NULL ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ], parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + weaponStateCurrent = weaponStateIdeal; + switch ( weaponStateCurrent ) { + case WEAPONSTATE_ROCKET: + animPrefix = "rocket"; + break; + + default: + animPrefix = ""; + break; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeRight", parms.blendFrames ); + } else { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeLeft", parms.blendFrames ); + } + return SRESULT_DONE; +} diff --git a/source/mpgame/ai/Monster_IronMaiden.cpp b/source/mpgame/ai/Monster_IronMaiden.cpp new file mode 100644 index 0000000..9bdd613 --- /dev/null +++ b/source/mpgame/ai/Monster_IronMaiden.cpp @@ -0,0 +1,531 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterIronMaiden : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterIronMaiden ); + + rvMonsterIronMaiden ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual int FilterTactical ( int availableTactical ); + +protected: + + int phaseTime; + jointHandle_t jointBansheeAttack; + int enemyStunTime; + + rvAIAction actionBansheeAttack; + + virtual bool CheckActions ( void ); + + void PhaseOut ( void ); + void PhaseIn ( void ); + + virtual void OnDeath ( void ); + +private: + + // Custom actions + bool CheckAction_BansheeAttack ( rvAIAction* action, int animNum ); + bool PerformAction_PhaseOut ( void ); + bool PerformAction_PhaseIn ( void ); + + // Global States + stateResult_t State_Phased ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_PhaseIn ( const stateParms_t& parms ); + stateResult_t State_Torso_PhaseOut ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttackDone ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_PhaseIn ( const stateParms_t& parms ); + stateResult_t Frame_PhaseOut ( const stateParms_t& parms ); + stateResult_t Frame_BansheeAttack ( const stateParms_t& parms ); + stateResult_t Frame_EndBansheeAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden ); +}; + +CLASS_DECLARATION( idAI, rvMonsterIronMaiden ) +END_CLASS + +/* +================ +rvMonsterIronMaiden::rvMonsterIronMaiden +================ +*/ +rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) { + enemyStunTime = 0; + phaseTime = 0; +} + +void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) { + // Cache the mouth joint + jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) ); +} +/* +================ +rvMonsterIronMaiden::Spawn +================ +*/ +void rvMonsterIronMaiden::Spawn ( void ) { + // Custom actions + actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true ); +} + +/* +================ +rvMonsterIronMaiden::Save +================ +*/ +void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( phaseTime ); + savefile->WriteInt ( enemyStunTime ); + + actionBansheeAttack.Save ( savefile ); +} + +/* +================ +rvMonsterIronMaiden::Restore +================ +*/ +void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( phaseTime ); + savefile->ReadInt ( enemyStunTime ); + + actionBansheeAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterIronMaiden::FilterTactical +================ +*/ +int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) { + // When hidden the iron maiden only uses ranged tactical + if ( fl.hidden ) { + availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT); + } + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterIronMaiden::CheckAction_BansheeAttack +================ +*/ +bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) { + return CheckAction_RangedAttack ( action, animNum ); +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseIn +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) { + if ( !phaseTime ) { + return false; + } + + // Must be out for at least 3 seconds + if ( gameLocal.time - phaseTime < 3000 ) { + return false; + } + + // Phase in after 10 seconds or our movement is done + if ( gameLocal.time - phaseTime > 10000 || move.fl.done ) { + // Make sure we arent in something + if ( CanBecomeSolid ( ) ) { + PerformAction ( "Torso_PhaseIn", 4, true ); + return true; + } + } + + return false; +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseOut +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) { + // Little randomization + if ( gameLocal.random.RandomFloat ( ) > 0.5f ) { + return false; + } + if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) { + return false; + } + + PerformAction ( "Torso_PhaseOut", 4, true ); + return true; +} + +/* +================ +rvMonsterIronMaiden::CheckActions +================ +*/ +bool rvMonsterIronMaiden::CheckActions ( void ) { + // When phased the only available action is phase in + if ( phaseTime ) { + if ( PerformAction_PhaseIn ( ) ) { + return true; + } + return false; + } + + if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction_PhaseOut ( ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterIronMaiden::PhaseOut +================ +*/ +void rvMonsterIronMaiden::PhaseOut ( void ) { + if ( phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + Hide ( ); + + move.fl.allowHiddenMove = true; + + ProcessEvent ( &EV_BecomeNonSolid ); + + StopMove ( MOVE_STATUS_DONE ); + SetState ( "State_Phased" ); + + // Move away from here, to anywhere + MoveOutOfRange ( this, 500.0f, 150.0f ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); + + phaseTime = gameLocal.time; +} + +/* +================ +rvMonsterIronMaiden::PhaseIn +================ +*/ +void rvMonsterIronMaiden::PhaseIn ( void ) { + if ( !phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + ProcessEvent ( &AI_BecomeSolid ); + + Show ( ); + +// PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true ); + + phaseTime = 0; + // Wait for the action that started the phase in to finish, then go back to combat + SetState ( "Wait_Action" ); + PostState ( "State_Combat" ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); +} + +/* +================ +rvMonsterIronMaiden::OnDeath +================ +*/ +void rvMonsterIronMaiden::OnDeath ( void ) { + StopSound ( SND_CHANNEL_ITEM, false ); + + // Stop looping effects + StopEffect ( "fx_banshee" ); + StopEffect ( "fx_dress" ); + + idAI::OnDeath( ); +} + +/* +================ +rvMonsterIronMaiden::GetDebugInfo +================ +*/ +void rvMonsterIronMaiden::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_BansheeAttack", aiActionStatusString[actionBansheeAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterIronMaiden ) + STATE ( "State_Phased", rvMonsterIronMaiden::State_Phased ) + + STATE ( "Torso_PhaseOut", rvMonsterIronMaiden::State_Torso_PhaseOut ) + STATE ( "Torso_PhaseIn", rvMonsterIronMaiden::State_Torso_PhaseIn ) + STATE ( "Torso_BansheeAttack", rvMonsterIronMaiden::State_Torso_BansheeAttack ) + STATE ( "Torso_BansheeAttackDone", rvMonsterIronMaiden::State_Torso_BansheeAttackDone ) + + STATE ( "Frame_PhaseIn", rvMonsterIronMaiden::Frame_PhaseIn ) + STATE ( "Frame_PhaseOut", rvMonsterIronMaiden::Frame_PhaseOut ) + STATE ( "Frame_BansheeAttack", rvMonsterIronMaiden::Frame_BansheeAttack ) + STATE ( "Frame_EndBansheeAttack", rvMonsterIronMaiden::Frame_EndBansheeAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterIronMaiden::State_Phased +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) { + // If done moving and cant become solid here, move again + if ( move.fl.done ) { + if ( !CanBecomeSolid ( ) ) { + MoveOutOfRange ( this, 300.0f, 150.0f ); + } + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Make sure we are checking actions if we have no tactical move + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + STAGE_PHASE, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttackDone + +To ensure the movement is enabled after the banshee attack and that the effect is stopped this state +will be posted to be run after the banshe attack finishes. +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) { + Frame_EndBansheeAttack ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterIronMaiden::Frame_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) { + PhaseIn ( ); + return SRESULT_OK; +} + + +/* +================ +rvMonsterIronMaiden::Frame_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) { + PhaseOut ( ); + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) { + idVec3 origin; + idMat3 axis; + idEntity* entities[ 1024 ]; + int count; + int i; + + // Get mouth origin + GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis ); + + // Find all entities within the banshee attacks radius + count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 ); + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Has to be within fov + if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) { + continue; + } + + // Do some damage + idVec3 dir; + dir = origin = ent->GetEyePosition ( ); + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 ); + + // Cache the last time we stunned our own enemy for the phase out + if ( ent == enemy.ent ) { + enemyStunTime = gameLocal.time; + } + } + + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_EndBansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_banshee" ); + return SRESULT_OK; +} diff --git a/source/mpgame/ai/Monster_LightTank.cpp b/source/mpgame/ai/Monster_LightTank.cpp new file mode 100644 index 0000000..d146d8a --- /dev/null +++ b/source/mpgame/ai/Monster_LightTank.cpp @@ -0,0 +1,610 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterLightTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterLightTank ); + + rvMonsterLightTank ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual int GetDamageForLocation ( int damage, int location ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + int flamethrowerHealth; + int chargeDebounce; + void DestroyFlamethrower ( void ); + +private: + + int standingMeleeNoAttackTime; + bool damaged; +// bool damagedMove; + int powerUpStartTime; + + rvAIAction actionFlameThrower; + rvAIAction actionPowerUp; + rvAIAction actionChargeAttack; + + bool CheckAction_PowerUp ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms ); + stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_PowerUp ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterLightTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterLightTank ) +END_CLASS + +/* +================ +rvMonsterLightTank::rvMonsterLightTank +================ +*/ +rvMonsterLightTank::rvMonsterLightTank ( void ) { + damaged = false; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterLightTank::Spawn +================ +*/ +void rvMonsterLightTank::Spawn ( void ) { +// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" ); + flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" ); + chargeDebounce = 0; + + actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK ); + actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterLightTank::Save +================ +*/ +void rvMonsterLightTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( flamethrowerHealth ); + savefile->WriteInt( chargeDebounce ); + + savefile->WriteBool( damaged ); +// savefile->WriteBool( damagedMove ); + savefile->WriteInt( powerUpStartTime ); + + actionFlameThrower.Save( savefile ); + actionPowerUp.Save( savefile ); + actionChargeAttack.Save( savefile ); + mPostWeaponDestroyed.Save( savefile ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::Restore +================ +*/ +void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( flamethrowerHealth ); + savefile->ReadInt( chargeDebounce ); + + savefile->ReadBool( damaged ); +// savefile->ReadBool( damagedMove ); + savefile->ReadInt( powerUpStartTime ); + + actionFlameThrower.Restore( savefile ); + actionPowerUp.Restore( savefile ); + actionChargeAttack.Restore( savefile ); + mPostWeaponDestroyed.Restore( savefile ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::FilterTactical +================ +*/ +int rvMonsterLightTank::FilterTactical ( int availableTactical ) { + if ( flamethrowerHealth > 0 ) { + // Only let the light tank use ranged tactical when he is really far from his enemy + if ( !enemy.range || enemy.range < combat.attackRange[1] ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + if ( chargeDebounce > gameLocal.GetTime() ) + {//don't charge again any time soon + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterLightTank::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + break; + + default: + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +================ +rvMonsterLightTank::UpdateRunStatus +================ +*/ +bool rvMonsterLightTank::UpdateRunStatus ( void ) { + // If rushing, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + move.fl.idealRunning = true; + } + else + { + move.fl.idealRunning = false; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterLightTank::DestroyFlamethrower +================ +*/ +void rvMonsterLightTank::DestroyFlamethrower ( void ) { + + StopEffect ( "fx_flame_muzzle" ); + //HideSurface ( "models/monsters/light_tank/flamethrower" ); + //GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 ); + animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) ); + flamethrowerHealth = -1; + + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; +// flamethrowerDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + + PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") ); + PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true ); + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "damaged"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + chargeDebounce = 0; + damaged = true; + animPrefix = "damage"; + + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS); + + actionMeleeAttack.timer.Reset( actionTime ); + actionChargeAttack.timer.Reset( actionTime ); + + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +===================== +rvMonsterLightTank::Pain +===================== +*/ +bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( move.fl.moving && move.fl.running ) + { + painAnim = "pain_charge"; + } + return didPain; +} + /* +===================== +rvMonsterLightTank::GetDamageForLocation +===================== +*/ +int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) { + // If the flamethrower was hit only do damage to it + if( !damaged && !aifl.dead ) + { + if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( flamethrowerHealth > 0 ){ + flamethrowerHealth -= damage; + if ( flamethrowerHealth <= 0 ) { + DestroyFlamethrower(); + } + } + return 0; + } + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterLightTank::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( !damaged ) + { + if ( victim == GetEnemy() && inflictor == this ) + { + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//okay, get out of melee state for now + chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000; + } + } + } + + idAI::DamageFeedback( victim, inflictor, damage ); +} + +/* +============ +rvMonsterLightTank::OnStopMoving +============ +*/ +void rvMonsterLightTank::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionFlameThrower.timer.Clear( actionTime ); + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//so we don't stand there and look stupid + actionMeleeAttack.timer.Clear( actionTime ); + actionChargeAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterLightTank::CheckAction_PowerUp +================ +*/ +bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum ) +{ + if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeLeft( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeRight +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeRight( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckActions +================ +*/ +bool rvMonsterLightTank::CheckActions ( void ) { + if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterLightTank ) + STATE ( "State_Killed", rvMonsterLightTank::State_Killed ) + + STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower ) + STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink ) + STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain ) + STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack ) + STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp ) + +END_CLASS_STATES + +/* +================ +rvMonsterLightTank::State_Killed +================ +*/ +stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_destroy_arm_trail" ); + StopEffect ( "fx_flame_muzzle" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrower +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Flame effect + PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames ); + + // Delay start the flame thrower think to ensure he flames for a minimum time + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 ); + + return SRESULT_DONE; +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrowerThink +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) { + if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + StopEffect ( "fx_flame_muzzle" ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterLightTank::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) { + + StopEffect ( "fx_flame_muzzle" ); + + // Default pain animation + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( !move.fl.moving || !FacingIdeal() ) { + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames ); + } + + return SRESULT_STAGE ( STAGE_FINISH ); + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterLightTank::State_Torso_PowerUp +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + //fl.takedamage = false; + powerUpStartTime = gameLocal.GetTime(); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + health++; + if ( health >= spawnArgs.GetInt( "health" ) + || gameLocal.GetTime() - powerUpStartTime > 3875 ) + {//full health or been charging up for 3 full anim loops + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_NetworkGuardian.cpp b/source/mpgame/ai/Monster_NetworkGuardian.cpp new file mode 100644 index 0000000..2e8a8c5 --- /dev/null +++ b/source/mpgame/ai/Monster_NetworkGuardian.cpp @@ -0,0 +1,822 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterNetworkGuardian : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterNetworkGuardian ); + + rvMonsterNetworkGuardian ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + + int shots; + int landTime; + + int flagFlying; + + float strafeSpeed; + + //this flag allows the AI to control when it takes off and lands. + bool flagAutopilot; + + int battleStage; + + enum { + FLY_NONE = 0, + FLY_TRANSITION, + FLY_FLYING, + }; + +private: + + rvAIAction actionShotgunRocketAttack; + rvAIAction actionFlyingRangedAttack; + rvAIAction actionFlyingSweepAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionBlasterSweepGround; + rvAIAction actionBlasterAttack; + rvAIAction actionMIRVAttack; + + + + stateResult_t State_Wait_Flying ( const stateParms_t& parms ); + +// stateResult_t State_Dead ( const stateParms_t& parms ); + + // walking melee attacks + + // walking ranged attacks + stateResult_t State_Torso_ShotgunRocket ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterSweepGround ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MIRVAttack ( const stateParms_t& parms ); + + //flying evades + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + //flying ranged attacks + stateResult_t State_Torso_FlyingRanged ( const stateParms_t& parms ); + stateResult_t State_Torso_FlyingSweep ( const stateParms_t& parms ); + + // flying anims + stateResult_t State_Torso_LiftOff ( const stateParms_t& parms ); + stateResult_t State_Torso_Fall ( const stateParms_t& parms ); + // walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + + //force the NG into walking mode. This does not play the landing anim, and will make him fall from the sky. + void Event_ForceWalkMode( void ); + + //These commands change the NG's state, but do so through states and animations. + void Event_ForceLanding( void ); + void Event_ForceTakeoff( void ); + + //toggles the NG between AI controlled flight and script controlled flight + void Event_AllowAutopilot( float f); + + //for staged combat, sets the int value of the battle stage + void Event_SetBattleStage( float f); + + CLASS_STATES_PROTOTYPE ( rvMonsterNetworkGuardian ); +}; + +const idEventDef EV_ForceWalkMode( "forceWalkMode" ); +const idEventDef EV_ForceLanding( "forceLanding" ); +const idEventDef EV_ForceTakeoff( "forceTakeoff" ); +const idEventDef EV_AllowAutopilot( "allowAutopilot", "f" ); +const idEventDef EV_SetBattleStage( "setBattleStage", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterNetworkGuardian ) + EVENT( EV_ForceWalkMode, rvMonsterNetworkGuardian::Event_ForceWalkMode ) + EVENT( EV_ForceLanding, rvMonsterNetworkGuardian::Event_ForceLanding ) + EVENT( EV_ForceTakeoff, rvMonsterNetworkGuardian::Event_ForceTakeoff ) + EVENT( EV_AllowAutopilot, rvMonsterNetworkGuardian::Event_AllowAutopilot ) + EVENT( EV_SetBattleStage, rvMonsterNetworkGuardian::Event_SetBattleStage ) + +END_CLASS + +/* +================ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian +================ +*/ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian ( ) { + shots = 0; + landTime = 0; + flagFlying = FLY_NONE; + battleStage = 1; + strafeSpeed = 0; + +} + +/* +================ +rvMonsterNetworkGuardian::Spawn +================ +*/ +void rvMonsterNetworkGuardian::Spawn ( void ) { + + disablePain = true; + flagAutopilot = false; + + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "500" ); + + actionShotgunRocketAttack.Init ( spawnArgs, "action_ShotgunRocket", "Torso_ShotgunRocket_Attack", AIACTIONF_ATTACK ); + actionFlyingRangedAttack.Init( spawnArgs, "action_FlyingRangedAttack", "Torso_FlyingRanged_Attack", AIACTIONF_ATTACK ); + actionFlyingSweepAttack.Init( spawnArgs, "action_blasterSweepAirAttack", "Torso_FlyingSweep_Attack", AIACTIONF_ATTACK ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterSweepGround.Init( spawnArgs, "action_blasterSweepGroundAttack", "Torso_BlasterSweepGround_Attack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init( spawnArgs, "action_blasterAttack", "Torso_Blaster_Attack", AIACTIONF_ATTACK ); + actionMIRVAttack.Init( spawnArgs, "action_MIRVAttack", "Torso_MIRV_Attack", AIACTIONF_ATTACK ); + +} + + +/* +================ +rvMonsterNetworkGuardian::Save +================ +*/ +void rvMonsterNetworkGuardian::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( shots ); + savefile->WriteInt( landTime ); + savefile->WriteInt( flagFlying ); + savefile->WriteInt( battleStage ); + savefile->WriteBool( flagAutopilot ); + savefile->WriteFloat( strafeSpeed ); + + actionShotgunRocketAttack.Save( savefile ); + actionFlyingRangedAttack.Save( savefile ); + actionFlyingSweepAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionBlasterSweepGround.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionMIRVAttack.Save( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::Restore +================ +*/ +void rvMonsterNetworkGuardian::Restore ( idRestoreGame *savefile ) +{ + savefile->ReadInt( shots ); + savefile->ReadInt( landTime ); + savefile->ReadInt( flagFlying ); + savefile->ReadInt( battleStage ); + savefile->ReadBool( flagAutopilot ); + savefile->ReadFloat( strafeSpeed ); + + actionShotgunRocketAttack.Restore( savefile ); + actionFlyingRangedAttack.Restore( savefile ); + actionFlyingSweepAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionBlasterSweepGround.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionMIRVAttack.Restore( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::CheckActions +================ +*/ +bool rvMonsterNetworkGuardian::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + //if the flight is transitioning, do nothing + if( flagFlying == FLY_TRANSITION ) { + return false; + } + + //this is the autopilot section. + if( flagAutopilot ) { + // If he's been on the ground long enough, fly... + if ( move.moveType == MOVETYPE_ANIM && move.fl.onGround && !flagFlying && gameLocal.time > landTime ) + { + PostState ( "Wait_Flying" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + return true; + } + else if ( move.moveType == MOVETYPE_FLY && gameLocal.time > landTime ) + { + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + return true; + } + } + + //Normal actions here ----------- + + //if he's close enough for melee, use melee + if( flagFlying == FLY_NONE) { + if( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + //check for ranged attacks on the ground + if( flagFlying == FLY_NONE ) { + + switch (battleStage) { + + case 1: + if( PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + case 2: + if( PerformAction ( &actionMIRVAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + default: + gameLocal.Error("Bad battleStage '%d' set for Network Guardian.", battleStage); + break; + } + + } + //airborne attack actions + if( (flagFlying == FLY_FLYING) ) { + + if( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionFlyingRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionFlyingSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ){ + return true; + } + } + + //No. + //return idAI::CheckActions ( ); + return false; +} + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterNetworkGuardian::Event_ForceWalkMode +================ +*/ +// forces NG to obey gravity and immediately switch to walking mode. +void rvMonsterNetworkGuardian::Event_ForceWalkMode( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_NONE; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceLanding +================ +*/ +// forces NG play his landing animation. He will not just fall from the sky. +void rvMonsterNetworkGuardian::Event_ForceLanding( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + //PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceTakeoff +================ +*/ +// forces NG to take off and fly. +void rvMonsterNetworkGuardian::Event_ForceTakeoff( void ) { + + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + move.fl.allowDirectional = true; + SetState ( "Wait_Flying" ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_AllowAutoPilot +================ +*/ +// toggles the AI autoPilot for deciding when to fly and land. +void rvMonsterNetworkGuardian::Event_AllowAutopilot( float f ) { + + flagAutopilot = f ? true : false; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_SetBattleStage +================ +*/ +// sets the current battle stage. Each stage has different behaviors. +void rvMonsterNetworkGuardian::Event_SetBattleStage( float f ) { + + battleStage = f; + +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterNetworkGuardian ) + STATE ( "Wait_Flying", rvMonsterNetworkGuardian::State_Wait_Flying ) + STATE ( "State_Dead", rvMonsterNetworkGuardian::State_Dead ) + + STATE ( "Torso_ShotgunRocket_Attack", rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ) + STATE ( "Torso_BlasterSweepGround_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ) + STATE ( "Torso_Blaster_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterAttack ) + STATE ( "Torso_MIRV_Attack", rvMonsterNetworkGuardian::State_Torso_MIRVAttack) + + STATE ( "Torso_EvadeLeft", rvMonsterNetworkGuardian::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterNetworkGuardian::State_Torso_EvadeRight ) + + STATE ( "Torso_FlyingRanged_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingRanged ) + STATE ( "Torso_FlyingSweep_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingSweep ) + + STATE ( "Torso_LiftOff", rvMonsterNetworkGuardian::State_Torso_LiftOff ) + STATE ( "Torso_Fall", rvMonsterNetworkGuardian::State_Torso_Fall ) + + STATE ( "Torso_TurnRight90", rvMonsterNetworkGuardian::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ) + +END_CLASS_STATES + + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterNetworkGuardian::State_Torso_ShotgunRocket +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shotgunRocket_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_grd", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingSweep +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingSweep ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_air", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingRanged +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingRanged ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "flyingRanged_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_MIRVAttack +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_MIRVAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_vert", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterNetworkGuardian::State_Wait_Flying +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Wait_Flying ( const stateParms_t& parms ) { + if ( move.moveType == MOVETYPE_ANIM ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterNetworkGuardian::State_Dead +================ +*/ +/* +stateResult_t rvMonsterNetworkGuardian::State_Dead ( const stateParms_t& parms ) { + return SRESULT_DONE; +}*/ + +/* +================ +rvMonsterNetworkGuardian::State_Torso_LiftOff +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_LiftOff ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.noGravity = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "liftoff", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + SetMoveType ( MOVETYPE_FLY ); + landTime = gameLocal.time + DelayTime ( 5000, 10000 ); + animPrefix = "fly"; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + flagFlying = FLY_FLYING; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_Fall +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_Fall ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + // we've landed! determine the next fly time + landTime = gameLocal.time + DelayTime ( 10000, 15000 ); + flagFlying = FLY_NONE; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnRight90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnLeft90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_BlasterAttack +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( (flagFlying != FLY_NONE ) && move.fl.moving ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 12 ) + 8) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; + +} diff --git a/source/mpgame/ai/Monster_RepairBot.cpp b/source/mpgame/ai/Monster_RepairBot.cpp new file mode 100644 index 0000000..2f5f067 --- /dev/null +++ b/source/mpgame/ai/Monster_RepairBot.cpp @@ -0,0 +1,376 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class repairBotArm_t { +public: + jointHandle_t joint; + int repairTime; + bool repairing; + rvClientEffectPtr effectRepair; + rvClientEffectPtr effectImpact; + + int periodicEndTime; + + repairBotArm_t () { + periodicEndTime = -1; + } + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvMonsterRepairBot : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterRepairBot ); + + rvMonsterRepairBot ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + int repairEndTime; + float repairEffectDist; + + repairBotArm_t armLeft; + repairBotArm_t armRight; + +private: + + void UpdateRepairs ( repairBotArm_t& arm ); + void StopRepairs ( repairBotArm_t& arm ); + + // Leg states + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_Repair ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_RepairDone ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterRepairBot ); +}; + +CLASS_DECLARATION( idAI, rvMonsterRepairBot ) +END_CLASS + +/* +================ +rvMonsterRepairBot::rvMonsterRepairBot +================ +*/ +rvMonsterRepairBot::rvMonsterRepairBot ( ) { +} + +void rvMonsterRepairBot::InitSpawnArgsVariables( void ) +{ + armLeft.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_left", "l_fx" ) ); + armRight.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_right", "r_fx" ) ); + repairEffectDist = spawnArgs.GetFloat( "repairEffectDist", "64" ); +} +/* +================ +rvMonsterRepairBot::Spawn +================ +*/ +void rvMonsterRepairBot::Spawn ( void ) { + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::CheckActions +================ +*/ +bool rvMonsterRepairBot::CheckActions ( void ) { + return false; +} + +/* +================ +rvMonsterRepairBot::OnDeath +================ +*/ +void rvMonsterRepairBot::OnDeath ( void ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), viewAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterRepairBot::Save +================ +*/ +void rvMonsterRepairBot::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( repairEndTime ); + + armLeft.Save ( savefile ); + armRight.Save ( savefile ); +} + +/* +================ +rvMonsterRepairBot::Restore +================ +*/ +void rvMonsterRepairBot::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( repairEndTime ); + + armLeft.Restore ( savefile ); + armRight.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::Think +================ +*/ +void rvMonsterRepairBot::Think ( void ) { + idAI::Think ( ); + + // Update repair effects? (dont worry about stopping them, the state ending will do so) + UpdateRepairs ( armLeft ); + UpdateRepairs ( armRight ); +} + +/* +================ +rvMonsterRepairBot::UpdateRepairs +================ +*/ +void rvMonsterRepairBot::UpdateRepairs ( repairBotArm_t& arm ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + if ( arm.joint == INVALID_JOINT ) { + return; + } + + if ( gameLocal.time > repairEndTime ) { + StopRepairs ( arm ) ; + return; + } + + // If the repair time has been crossed we need to start/stop the repairs + if ( gameLocal.time > arm.repairTime ) { + if ( arm.repairing ) { + StopRepairs ( arm ); + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 500 ); + arm.repairing = false; + } else { + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 2500 ); + arm.repairing = true; + } + } + + if ( !arm.repairing ) { + return; + } + + // Left repair effect + GetJointWorldTransform ( arm.joint, gameLocal.time, origin, axis ); + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * repairEffectDist, MASK_SHOT_RENDERMODEL, this ); + + if ( tr.fraction >= 1.0f ) { + StopRepairs ( arm ); + } else { + // Start the repair effect if not already started + if ( !arm.effectRepair ) { + arm.effectRepair = PlayEffect ( "fx_repair", arm.joint, true ); + } + // If the repair effect is running then set its end origin + if ( arm.effectRepair ) { + arm.effectRepair->SetEndOrigin ( tr.endpos ); + } + // Start the impact effect + if ( !arm.effectImpact ) { + arm.effectImpact = PlayEffect ( "fx_repair_impact", tr.endpos, tr.c.normal.ToMat3(), true ); + + } else { + // Calculate the local origin and axis from the given globals + idVec3 localOrigin = (tr.endpos - renderEntity.origin) * renderEntity.axis.Transpose ( ); + idMat3 localAxis = tr.c.normal.ToMat3 ();// * renderEntity.axis.Transpose(); + arm.effectImpact->SetOrigin ( localOrigin ); + arm.effectImpact->SetAxis ( localAxis ); + } + + if( gameLocal.GetTime() > arm.periodicEndTime ) {// FIXME: Seems dumb to keep banging on this if the fx isn't defined. + gameLocal.PlayEffect( spawnArgs, "fx_repair_impact_periodic", tr.endpos, tr.c.normal.ToMat3() ); + arm.periodicEndTime = gameLocal.GetTime() + SEC2MS( rvRandom::flrand(spawnArgs.GetVec2("impact_fx_delay_range", "1 1")) ); + } + } +} + +/* +================ +rvMonsterRepairBot::StopRepairs +================ +*/ +void rvMonsterRepairBot::StopRepairs ( repairBotArm_t& arm ) { + if ( arm.effectImpact ) { + arm.effectImpact->Stop ( ); + arm.effectImpact = NULL; + } + if ( arm.effectRepair ) { + arm.effectRepair->Stop ( ); + arm.effectRepair = NULL; + } + + arm.periodicEndTime = -1; +} + +/* +================ +rvMonsterRepairBot::GetDebugInfo +================ +*/ +void rvMonsterRepairBot::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterRepairBot ) + STATE ( "Legs_Move", rvMonsterRepairBot::State_Legs_Move ) + STATE ( "TorsoAction_Repair", rvMonsterRepairBot::State_TorsoAction_Repair ) + STATE ( "TorsoAction_RepairDone", rvMonsterRepairBot::State_TorsoAction_RepairDone ) +END_CLASS_STATES + +/* +================ +rvMonsterRepairBot::State_Legs_Move +================ +*/ +stateResult_t rvMonsterRepairBot::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_MOVE, + STAGE_MOVE_WAIT, + STAGE_STOP, + STAGE_STOP_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_LEGS, "idle_to_run", 4 ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + return SRESULT_STAGE ( STAGE_MOVE ); + } + return SRESULT_WAIT; + + case STAGE_MOVE: + PlayCycle ( ANIMCHANNEL_LEGS, "run", 4 ); + return SRESULT_STAGE ( STAGE_MOVE_WAIT ); + + case STAGE_MOVE_WAIT: + if ( !move.fl.moving || !CanMove() ) { + return SRESULT_STAGE ( STAGE_STOP ); + } + return SRESULT_WAIT; + + case STAGE_STOP: + PlayAnim ( ANIMCHANNEL_LEGS, "run_to_idle", 4 ); + return SRESULT_STAGE ( STAGE_STOP_WAIT ); + + case STAGE_STOP_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_Repair +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_Repair ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_REPAIR, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "repair", 4 ); + repairEndTime = gameLocal.time + SEC2MS(scriptedActionEnt->spawnArgs.GetInt ( "duration", "5" ) ); + PostAnimState ( ANIMCHANNEL_TORSO, "TorsoAction_RepairDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE(STAGE_REPAIR); + + case STAGE_REPAIR: + if ( gameLocal.time > repairEndTime ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_RepairDone +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_RepairDone ( const stateParms_t& parms ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + + return SRESULT_DONE; +} + +/* +================ +repairBotArm_t::Save +================ +*/ +void repairBotArm_t::Save ( idSaveGame* savefile ) const { + savefile->WriteInt ( repairTime ); + savefile->WriteBool ( repairing ); + effectRepair.Save ( savefile ); + effectImpact.Save ( savefile ); + + savefile->WriteInt ( periodicEndTime ); +} + +/* +================ +repairBotArm_t::Restore +================ +*/ +void repairBotArm_t::Restore ( idRestoreGame* savefile ) { + savefile->ReadInt ( repairTime ); + savefile->ReadBool ( repairing ); + effectRepair.Restore ( savefile ); + effectImpact.Restore ( savefile ); + + savefile->ReadInt ( periodicEndTime ); +} diff --git a/source/mpgame/ai/Monster_Scientist.cpp b/source/mpgame/ai/Monster_Scientist.cpp new file mode 100644 index 0000000..c403b00 --- /dev/null +++ b/source/mpgame/ai/Monster_Scientist.cpp @@ -0,0 +1,76 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterScientist : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterScientist ); + + rvMonsterScientist ( void ); + + void Spawn ( void ); + + virtual void OnDeath ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterScientist ); +}; + +CLASS_DECLARATION( idAI, rvMonsterScientist ) +END_CLASS + +/* +================ +rvMonsterScientist::rvMonsterScientist +================ +*/ +rvMonsterScientist::rvMonsterScientist ( void ) { +} + +/* +================ +rvMonsterScientist::Spawn +================ +*/ +void rvMonsterScientist::Spawn ( void ) { + PlayEffect ( "fx_fly", animator.GetJointHandle ( "effects_bone" ), true ); +} + +/* +================ +rvMonsterScientist::OnDeath +================ +*/ +void rvMonsterScientist::OnDeath ( void ) { + StopEffect ( "fx_fly" ); + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterScientist::GetDebugInfo +================ +*/ +void rvMonsterScientist::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterScientist ) +END_CLASS_STATES diff --git a/source/mpgame/ai/Monster_Sentry.cpp b/source/mpgame/ai/Monster_Sentry.cpp new file mode 100644 index 0000000..fedb7a4 --- /dev/null +++ b/source/mpgame/ai/Monster_Sentry.cpp @@ -0,0 +1,412 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSentry : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSentry ); + + rvMonsterSentry ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + rvAIAction actionBlasterAttack; + rvAIAction actionKamakaziAttack; + rvAIAction actionCircleStrafe; + + int shots; + int minShots; + int maxShots; + int kamakaziHealth; + int strafeTime; + bool strafeRight; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual void OnDeath ( void ); + + void Explode ( bool force = false ); + +private: + + int nextChatterTime; + + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_KamakaziAttack ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Action_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSentry ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSentry ) +END_CLASS + +/* +================ +rvMonsterSentry::rvMonsterSentry +================ +*/ +rvMonsterSentry::rvMonsterSentry ( void ) { + strafeTime = 0; + strafeRight = false; +} + +void rvMonsterSentry::InitSpawnArgsVariables( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); +} +/* +================ +rvMonsterSentry::Spawn +================ +*/ +void rvMonsterSentry::Spawn ( void ) { + // Custom actions + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionKamakaziAttack.Init ( spawnArgs, "action_kamakaziAttack", "Torso_KamakaziAttack", AIACTIONF_ATTACK ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "State_Action_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + kamakaziHealth = spawnArgs.GetInt ( "kamakazi_Health", va("%d", health / 2) ); + nextChatterTime = 0; +} + +/* +================ +rvMonsterSentry::Save +================ +*/ +void rvMonsterSentry::Save ( idSaveGame *savefile ) const { + actionBlasterAttack.Save( savefile ); + actionKamakaziAttack.Save( savefile ); + actionCircleStrafe.Save( savefile ); + + savefile->WriteInt( shots ); + savefile->WriteInt( kamakaziHealth ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteInt ( nextChatterTime ); +} + +/* +================ +rvMonsterSentry::Restore +================ +*/ +void rvMonsterSentry::Restore ( idRestoreGame *savefile ) { + actionBlasterAttack.Restore( savefile ); + actionKamakaziAttack.Restore( savefile ); + actionCircleStrafe.Restore( savefile ); + + savefile->ReadInt( shots ); + savefile->ReadInt( kamakaziHealth ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadInt ( nextChatterTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSentry::Pain +================ +*/ +bool rvMonsterSentry::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( kamakaziHealth > 0 && health < kamakaziHealth ) { + kamakaziHealth = 0; + move.speed = move.fly_speed = spawnArgs.GetFloat ( "kamakazi_fly_speed", va("%g", move.fly_speed ) ); + move.turnRate = spawnArgs.GetFloat ( "kamakazi_turn_rate", va("%g", move.turnRate ) ); + move.fly_pitch_max = spawnArgs.GetFloat ( "kamakazi_fly_pitch_max" ); + move.fly_pitch_scale = spawnArgs.GetFloat ( "kamakazi_fly_pitch_scale" ); + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterSentry::Think +================ +*/ +void rvMonsterSentry::Think ( void ) { + // Explode whenever we hit something + if ( kamakaziHealth == 0 && physicsObj.GetSlideMoveEntity () ) { + Explode ( ); + } else { + /* + if ( nextChatterTime < gameLocal.GetTime() ) { + if ( GetEnemy() ) { + Speak( "lipsync_chatter_combat" ); + nextChatterTime = speakTime + 2000 + gameLocal.random.RandomInt( 2000 ); + } else { + Speak( "lipsync_chatter_idle" ); + nextChatterTime = speakTime + 3000 + gameLocal.random.RandomInt( 3000 ); + } + } + */ + + idAI::Think ( ); + } +} + +/* +================ +rvMonsterSentry::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterSentry::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterSentry::CheckActions +================ +*/ +bool rvMonsterSentry::CheckActions ( void ) { + if ( kamakaziHealth != 0 ) { + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterSentry::CheckAction_CircleStrafe ) ) { + return true; + } + } else { + if ( PerformAction ( &actionKamakaziAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterSentry::FilterTactical +================ +*/ +int rvMonsterSentry::FilterTactical ( int availableTactical ) { + // Kamakazi Health will be set to 0 when its time to kamakazi + if ( !kamakaziHealth ) { + return availableTactical & (AITACTICAL_MELEE_BIT|AITACTICAL_TURRET_BIT); + // No rush when not in kamakazi mode + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterSentry::OnDeath +================ +*/ +void rvMonsterSentry::OnDeath ( void ) { + idAI::OnDeath ( ); + + Explode ( true ); +} + +/* +================ +rvMonsterSentry::Explode +================ +*/ +void rvMonsterSentry::Explode ( bool force ) { + if ( health > 0 || force ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString ( "def_kamakazi_damage" ), 1.0f ); + + // Kill ourselves + Damage ( this, this, viewAxis[0], "damage_gib", 1.0f, 0 ); + } +} + +/* +================ +rvMonsterSentry::GetDebugInfo +================ +*/ +void rvMonsterSentry::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_kamakaziAttack", aiActionStatusString[actionKamakaziAttack.status], userData ); + proc ( "idAI", "action_circleStrafe", aiActionStatusString[actionCircleStrafe.status], userData ); + proc ( "rvMonsterSentry", "strafeRight",strafeRight?"true":"false", userData ); + proc ( "rvMonsterSentry", "strafeTime", va("%d",strafeTime), userData ); + proc ( "rvMonsterSentry", "nextChatterTime", va("%d",nextChatterTime), userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSentry ) + STATE ( "Torso_BlasterAttack", rvMonsterSentry::State_Torso_BlasterAttack ) + STATE ( "Torso_KamakaziAttack", rvMonsterSentry::State_Torso_KamakaziAttack ) + STATE ( "State_CircleStrafe", rvMonsterSentry::State_CircleStrafe ) + STATE ( "State_Action_CircleStrafe",rvMonsterSentry::State_Action_CircleStrafe ) + STATE ( "Torso_EvadeLeft", rvMonsterSentry::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterSentry::State_Torso_EvadeRight ) +END_CLASS_STATES + +/* +================ +rvMonsterSentry::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSentry::State_Torso_KamakaziAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_KamakaziAttack ( const stateParms_t& parms ) { + Explode ( ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_CircleStrafe ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_CIRCLE + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( strafeTime ) { + //already strafing, just change dir? + strafeTime = (strafeTime0.5f); + } + return SRESULT_STAGE ( STAGE_CIRCLE ); + case STAGE_CIRCLE: + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) { + //FIXME: also stop if I bump into something + strafeTime = 0; + SetState( "State_Combat" ); + return SRESULT_DONE; + } + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (strafeRight?-200:200); + vel.Normalize(); + vel *= 200.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( enemy.lastKnownPosition ); + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterSentry::State_Action_CircleStrafe ( const stateParms_t& parms ) { + SetState( "State_CircleStrafe" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = false; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * 400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeRight ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = true; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} diff --git a/source/mpgame/ai/Monster_SlimyTransfer.cpp b/source/mpgame/ai/Monster_SlimyTransfer.cpp new file mode 100644 index 0000000..8e456b6 --- /dev/null +++ b/source/mpgame/ai/Monster_SlimyTransfer.cpp @@ -0,0 +1,205 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSlimyTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSlimyTransfer ); + + rvMonsterSlimyTransfer ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + jointHandle_t jointVomitMuzzle; + + int vomitNextAttackTime; + int vomitAttackRate; + +private: + + rvAIAction actionVomitAttack; + + // Torso States + stateResult_t State_Torso_VomitAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishVomitAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t State_Frame_StartVomit ( const stateParms_t& parms ); + stateResult_t State_Frame_StopVomit ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSlimyTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSlimyTransfer ) +END_CLASS + +/* +================ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer +================ +*/ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer ( void ) { +} + +void rvMonsterSlimyTransfer::InitSpawnArgsVariables ( void ) { + jointVomitMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_vomitMuzzle", "puke_bone" ) ); + + vomitAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_vomit_rate", ".15" ) ); +} + +/* +================ +rvMonsterSlimyTransfer::Spawn +================ +*/ +void rvMonsterSlimyTransfer::Spawn ( void ) { + actionVomitAttack.Init ( spawnArgs, "action_vomitAttack", "Torso_VomitAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::Save +================ +*/ +void rvMonsterSlimyTransfer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( vomitNextAttackTime ); + + actionVomitAttack.Save( savefile ); +} + +/* +================ +rvMonsterSlimyTransfer::Restore +================ +*/ +void rvMonsterSlimyTransfer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( vomitNextAttackTime ); + + actionVomitAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::CheckActions +================ +*/ +bool rvMonsterSlimyTransfer::CheckActions ( void ) { + if ( PerformAction ( &actionVomitAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterSlimyTransfer::OnDeath +================ +*/ +void rvMonsterSlimyTransfer::OnDeath ( void ) { + StopEffect ( "fx_vomit_muzzle" ); + idAI::OnDeath ( ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSlimyTransfer ) + STATE ( "Torso_VomitAttack", rvMonsterSlimyTransfer::State_Torso_VomitAttack ) + STATE ( "Torso_FinishVomitAttack", rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ) + + STATE ( "Frame_StartVomit", rvMonsterSlimyTransfer::State_Frame_StartVomit ) + STATE ( "Frame_StopVomit", rvMonsterSlimyTransfer::State_Frame_StopVomit ) +END_CLASS_STATES + +/* +================ +rvMonsterSlimyTransfer::State_Torso_VomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_VomitAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + vomitNextAttackTime = 0; + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "vomit_attack", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishVomitAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + if ( vomitNextAttackTime && gameLocal.time >= vomitNextAttackTime ) { + Attack ( "vomit", jointVomitMuzzle, enemy.ent ); + vomitNextAttackTime = gameLocal.time + vomitAttackRate; + } + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ( const stateParms_t& parms ) { + State_Frame_StopVomit ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StartVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StartVomit ( const stateParms_t& parms ) { + PlayEffect ( "fx_vomit_muzzle", jointVomitMuzzle, true ); + vomitNextAttackTime = gameLocal.time; + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StopVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StopVomit ( const stateParms_t& parms ) { + StopEffect ( "fx_vomit_muzzle" ); + vomitNextAttackTime = 0; + return SRESULT_DONE; +} diff --git a/source/mpgame/ai/Monster_StreamProtector.cpp b/source/mpgame/ai/Monster_StreamProtector.cpp new file mode 100644 index 0000000..2b0fcad --- /dev/null +++ b/source/mpgame/ai/Monster_StreamProtector.cpp @@ -0,0 +1,538 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterStreamProtector : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStreamProtector ); + + rvMonsterStreamProtector ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + virtual bool UpdateAnimationControllers ( void ); + + jointHandle_t jointPlasmaMuzzle; + + int attackEndTime; + int attackNextTime; + int plasmaAttackRate; + int shots; + +private: + + int painConsecutive; + + rvAIAction actionPlasmaAttack; + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionHeavyBlasterAttack; + rvAIAction actionLightningActtack; + rvAIAction actionChaingunAttack; + + // Torso States + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + stateResult_t State_Torso_PlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_LightningAttack ( const stateParms_t& parms ); + + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStreamProtector ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStreamProtector ) +END_CLASS + +/* +================ +rvMonsterStreamProtector::rvMonsterStreamProtector +================ +*/ +rvMonsterStreamProtector::rvMonsterStreamProtector ( void ) { + painConsecutive = 0; +} + +void rvMonsterStreamProtector::InitSpawnArgsVariables ( void ) { + jointPlasmaMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_plasmaMuzzle", "NM_muzzle" ) ); + + plasmaAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_plasma_rate", ".15" ) ); +} +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +void rvMonsterStreamProtector::Spawn ( void ) { + actionPlasmaAttack.Init ( spawnArgs, "action_plasmaAttack", "Torso_PlasmaAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", NULL, AIACTIONF_ATTACK ); + actionHeavyBlasterAttack.Init ( spawnArgs, "action_heavyBlasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionLightningActtack.Init ( spawnArgs, "action_lightningAttack", "Torso_LightningAttack", AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", "Torso_ChaingunAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Save +================ +*/ +void rvMonsterStreamProtector::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( attackEndTime ); + savefile->WriteInt( attackNextTime ); + savefile->WriteInt( shots ); + savefile->WriteInt( painConsecutive ); + + actionPlasmaAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionHeavyBlasterAttack.Save ( savefile ); + actionLightningActtack.Save( savefile ); + actionChaingunAttack.Save( savefile ); +} + +/* +================ +rvMonsterStreamProtector::Restore +================ +*/ +void rvMonsterStreamProtector::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( attackEndTime ); + savefile->ReadInt( attackNextTime ); + savefile->ReadInt( shots ); + savefile->ReadInt( painConsecutive ); + + actionPlasmaAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionHeavyBlasterAttack.Restore ( savefile ); + actionLightningActtack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +bool rvMonsterStreamProtector::UpdateAnimationControllers ( void ) { + // TODO: Target enemies behind us? (doesnt need to be the same enemy that we are targetting) + + return idAI::UpdateAnimationControllers ( ); +} + +/* +================ +rvMonsterStreamProtector::CheckPainActions +================ +*/ +bool rvMonsterStreamProtector::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +rvMonsterStreamProtector::CheckActions +================ +*/ +bool rvMonsterStreamProtector::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( PerformAction ( &actionPlasmaAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningActtack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionHeavyBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStreamProtector::CanTurn +================ +*/ +bool rvMonsterStreamProtector::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterStreamProtector::Pain +================ +*/ +bool rvMonsterStreamProtector::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterStreamProtector::Damage +================ +*/ +void rvMonsterStreamProtector::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker && attacker->IsType( rvMonsterStreamProtector::GetClassType() ) ) { + //don't take damage from ourselves or other stream protectors + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStreamProtector ) + STATE ( "State_Killed", rvMonsterStreamProtector::State_Killed ) + STATE ( "State_Dead", rvMonsterStreamProtector::State_Dead ) + + STATE ( "Torso_PlasmaAttack", rvMonsterStreamProtector::State_Torso_PlasmaAttack ) + STATE ( "Torso_FinishPlasmaAttack", rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ) + STATE ( "Torso_BlasterAttack", rvMonsterStreamProtector::State_Torso_BlasterAttack ) + STATE ( "Torso_LightningAttack", rvMonsterStreamProtector::State_Torso_LightningAttack ) + + STATE ( "Torso_TurnRight90", rvMonsterStreamProtector::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterStreamProtector::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +/* +================ +rvMonsterStreamProtector::State_Killed +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Killed ( const stateParms_t& parms ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death", parms.blendFrames ); + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Dead +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterStreamProtector::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_PlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_INITIALFIRE_WAIT, + STAGE_FIRE_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + attackEndTime = gameLocal.time + 500; + attackNextTime = gameLocal.time; + + // Flame effect + PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true ); + + PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 ); + + return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT ); + + case STAGE_INITIALFIRE_WAIT: + if ( gameLocal.time > attackEndTime ) { + attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f ); + return SRESULT_STAGE ( STAGE_FIRE_WAIT ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_FIRE_WAIT: + // If we have been using plasma too long or havent seen our enemy for at least half a second then + // stop now. + if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_STAGE ( STAGE_END ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_END: + // End animations + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_LightningAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + attackEndTime = gameLocal.time + 5000; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_StroggFlyer.cpp b/source/mpgame/ai/Monster_StroggFlyer.cpp new file mode 100644 index 0000000..e16abf3 --- /dev/null +++ b/source/mpgame/ai/Monster_StroggFlyer.cpp @@ -0,0 +1,363 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterStroggFlyer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggFlyer ); + + rvMonsterStroggFlyer ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnWakeUp ( void ); + + rvAIAction actionBombAttack; + rvAIAction actionBlasterAttack; + + idVec3 velocity; + + int shotCount; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + +private: + + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); + + void AttackBlaster ( void ); + void AttackBomb ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggFlyer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggFlyer ) +END_CLASS + +/* +================ +rvMonsterStroggFlyer::rvMonsterStroggFlyer +================ +*/ +rvMonsterStroggFlyer::rvMonsterStroggFlyer ( ) { + shotCount = 0; + lastAttackTime = 0; + attackStartTime = 0; +} + +void rvMonsterStroggFlyer::InitSpawnArgsVariables( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} + +/* +================ +rvMonsterStroggFlyer::Spawn +================ +*/ +void rvMonsterStroggFlyer::Spawn ( void ) { + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::OnUpdatePlayback +================ +*/ +void rvMonsterStroggFlyer::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + byte buttons; + byte changed; + byte impulse; + + velocity = pbd.GetVelocity ( ); + + buttons = pbd.GetButtons(); + changed = pbd.GetChanged(); + impulse = pbd.GetImpulse(); + + // Shoot the blaster if the attack button was pressed + if ( (changed & buttons) & BUTTON_ATTACK ){ + AttackBlaster ( ); + } + + if ( (changed & buttons) & BUTTON_ZOOM ){ + AttackBomb ( ); + } + + switch ( impulse ) { + case 40: + aifl.disableAttacks = true; + break; + + case 41: + aifl.disableAttacks = false; + break; + + case 42: + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } +} + +/* +================ +rvMonsterStroggFlyer::OnWakeUp +================ +*/ +void rvMonsterStroggFlyer::OnWakeUp ( void ) { + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); + + return idAI::OnWakeUp ( ); +} + + +/* +================ +rvMonsterStroggFlyer::AttackBlaster +================ +*/ +void rvMonsterStroggFlyer::AttackBlaster ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_muzzleflash", joint ); + Attack ( "blaster", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::AttackBomb +================ +*/ +void rvMonsterStroggFlyer::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( "fx_bombflash", joint ); + Attack ( "bomb", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::CheckActions +================ +*/ +bool rvMonsterStroggFlyer::CheckActions ( void ) { + if ( PerformAction ( &actionBombAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStroggFlyer::Save +================ +*/ +void rvMonsterStroggFlyer::Save( idSaveGame *savefile ) const { + actionBombAttack.Save ( savefile ) ; + actionBlasterAttack.Save ( savefile ); + + savefile->WriteVec3 ( velocity ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); +} + +/* +================ +rvMonsterStroggFlyer::Restore +================ +*/ +void rvMonsterStroggFlyer::Restore( idRestoreGame *savefile ) { + actionBombAttack.Restore ( savefile ) ; + actionBlasterAttack.Restore ( savefile ); + + savefile->ReadVec3 ( velocity ); + + savefile->ReadInt ( shotCount ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::GetDebugInfo +================ +*/ +void rvMonsterStroggFlyer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggFlyer ) + STATE ( "State_WakeUp", rvMonsterStroggFlyer::State_WakeUp ) + STATE ( "State_ScriptedPlaybackMove", rvMonsterStroggFlyer::State_ScriptedPlaybackMove ) + STATE ( "State_Killed", rvMonsterStroggFlyer::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterStroggFlyer::State_Torso_BlasterAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggFlyer::State_Torso_BombAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggFlyer::State_ScriptedPlaybackMove +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + // When the playback finishes cancel any running states + if ( !mPlayback.IsActive() ) { + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Perform actions + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterStroggFlyer::State_Killed +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Killed ( const stateParms_t& parms ) { + PlayEffect ( "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_StroggHover.cpp b/source/mpgame/ai/Monster_StroggHover.cpp new file mode 100644 index 0000000..390ca58 --- /dev/null +++ b/source/mpgame/ai/Monster_StroggHover.cpp @@ -0,0 +1,1448 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +#define MAX_MISSILE_JOINTS 4 +#define MAX_HOVER_JOINTS 4 +class rvMonsterStroggHover : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggHover ); + + rvMonsterStroggHover ( void ); + ~rvMonsterStroggHover ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + virtual void DeadMove ( void ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual int FilterTactical ( int availableTactical ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Hide( void ); + virtual void Show( void ); + + void StartHeadlight ( void ); + void StopHeadlight ( void ); + +protected: + +// rvAIAction actionRocketAttack; +// rvAIAction actionBlasterAttack; + rvAIAction actionMGunAttack; + rvAIAction actionMissileAttack; + rvAIAction actionBombAttack; + rvAIAction actionStrafe; + rvAIAction actionCircleStrafe; + + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnStartMoving ( void ); + + virtual const char* GetIdleAnimName ( void ); + +private: + + idEntityPtr marker; + idVec3 attackPosOffset; + bool inPursuit; + int holdPosTime; + + int strafeTime; + bool strafeRight; + bool circleStrafing; + float deathPitch; + float deathRoll; + float deathPitchRate; + float deathYawRate; + float deathRollRate; + float deathSpeed; + float deathGrav; + + int markerCheckTime; + + bool MarkerPosValid ( void ); + void TryStartPursuit ( void ); + void Pursue ( void ); + void CircleStrafe ( void ); + void Evade ( bool left ); + + int mGunFireRate; + int missileFireRate; + int bombFireRate; + int nextMGunFireTime; + int nextMissileFireTime; + int nextBombFireTime; + + int mGunMinShots; + int mGunMaxShots; + int missileMinShots; + int missileMaxShots; + int bombMinShots; + int bombMaxShots; + + int shots; + + int evadeDebounce; + int evadeDebounceRate; + float evadeChance; + float evadeSpeed; + float strafeSpeed; + float circleStrafeSpeed; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover[MAX_HOVER_JOINTS]; + rvClientEffectPtr effectHeadlight; + + jointHandle_t jointDust; + int numHoverJoints; + jointHandle_t jointHover[MAX_HOVER_JOINTS]; + jointHandle_t jointBomb; + jointHandle_t jointMGun; + int numMissileJoints; + jointHandle_t jointMissile[MAX_MISSILE_JOINTS]; + jointHandle_t jointHeadlight; + jointHandle_t jointHeadlightControl; + + renderLight_t renderLight; + int lightHandle; +// bool lightOn; + + void DoNamedAttack ( const char* attackName, jointHandle_t joint ); + + void UpdateLightDef ( void ); + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + bool CheckAction_BombAttack ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + +// stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_DeathSpiral ( const stateParms_t& parms ); + stateResult_t State_Pursue ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggHover ) +END_CLASS + +/* +================ +rvMonsterStroggHover::rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::rvMonsterStroggHover ( ) { + effectDust = NULL; + for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) { + effectHover[i] = NULL; + } + effectHeadlight = NULL; + + shots = 0; + strafeTime = 0; + strafeRight = false; + circleStrafing = false; + evadeDebounce = 0; + deathPitch = 0; + deathRoll = 0; + deathPitchRate = 0; + deathYawRate = 0; + deathRollRate = 0; + deathSpeed = 0; + deathGrav = 0; + + markerCheckTime = 0; + + marker = NULL; + attackPosOffset.Zero(); + inPursuit = false; + holdPosTime = 0; + + nextMGunFireTime = 0; + nextMissileFireTime = 0; + nextBombFireTime = 0; + + lightHandle = -1; +} + +/* +================ +rvMonsterStroggHover::~rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::~rvMonsterStroggHover ( ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +void rvMonsterStroggHover::InitSpawnArgsVariables( void ) +{ + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) ); + } + + jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) ); + + jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) ); + numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" )); + for ( int i = 0; i < numMissileJoints; i++ ) { + jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) ); + } + jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) ); + + mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" )); + missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" )); + bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" )); + + mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" ); + mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" ); + missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" ); + missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" ); + bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" ); + bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" ); + + evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" )); + evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" ); + evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" ); + strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" ); + circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" ); + + //LIGHT + jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) ); + jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) ); +} + +void rvMonsterStroggHover::Hide( void ) +{ + StopHeadlight(); + idAI::Hide(); +} + +void rvMonsterStroggHover::Show( void ) +{ + idAI::Show(); + StartHeadlight(); +} + +void rvMonsterStroggHover::StartHeadlight( void ) +{ + if ( jointHeadlight != INVALID_JOINT ) + { + lightHandle = -1; + if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) { + idVec3 color; + //const char* temp; + + const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + if ( headLightMaterial ) + { + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + spawnArgs.GetVector( "light_color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "light_target" ); + renderLight.up = spawnArgs.GetVector( "light_up" ); + renderLight.right = spawnArgs.GetVector( "light_right" ); + renderLight.end = spawnArgs.GetVector( "light_target" );; + } + + //lightOn = spawnArgs.GetBool( "start_on", "1" ); + + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } + // Hide flare surface if there is one + /* + temp = spawnArgs.GetString ( "light_flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + */ + + // Sounds shader when turning light + //spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + //spawnArgs.GetString ( "snd_off", "", soundOff); + + UpdateLightDef ( ); + } +} + +void rvMonsterStroggHover::StopHeadlight( void ) +{ + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( lightHandle ); + lightHandle = -1; + } + memset ( &renderLight, 0, sizeof(renderLight) ); +} +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +void rvMonsterStroggHover::Spawn ( void ) { +// actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); +// actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK ); + actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK ); + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + evadeDebounce = 0; + + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true ); + } + } + + if ( !marker ) { + marker = gameLocal.SpawnEntityDef( "target_null" ); + } + + //LIGHT + StopHeadlight(); + StartHeadlight(); + + if ( jointHeadlight != INVALID_JOINT ) + { + effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true ); + } +} + +/* +================ +rvMonsterStroggHover::GetDebugInfo +================ +*/ +void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData ); + proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData ); + + proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData ); + proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData ); + + proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData ); + + proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData ); + proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData ); + proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData ); + + proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData ); + proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData ); + proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData ); + proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData ); + proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData ); + proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData ); + proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData ); + proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData ); + + proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData ); + proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData ); + proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData ); + proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData ); + proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData ); + proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData ); +} + +/* +===================== +rvMonsterStroggHover::UpdateLightDef +===================== +*/ +void rvMonsterStroggHover::UpdateLightDef ( void ) { + if ( jointHeadlight != INVALID_JOINT ) + { + idVec3 origin; + idMat3 axis; + + if ( jointHeadlightControl != INVALID_JOINT ) { + idAngles jointAng; + jointAng.Zero(); + jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI ); + jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI ); + + animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + + GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis ); + + //origin += (localOffset * axis); + + // Include this part in the total bounds + // FIXME: bounds are local + //parent->AddToBounds ( worldOrigin ); + //UpdateOrigin ( ); + + if ( lightHandle != -1 ) { + renderLight.origin = origin; + renderLight.axis = axis; + + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); + } + } +} + +/* +================ +rvMonsterStroggHover::Think +================ +*/ +void rvMonsterStroggHover::Think ( void ) { + idAI::Think ( ); + + if ( !aifl.dead ) + { + // If thinking we should play an effect on the ground under us + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 80 units down from the bottom of our bbox + GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + /* + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + */ + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + //Try to circle strafe or pursue + if ( circleStrafing ) + { + CircleStrafe(); + } + else if ( !inPursuit ) + { + if ( !aifl.action && move.fl.done && !aifl.scripted ) + { + if ( GetEnemy() ) + { + if ( DistanceTo( GetEnemy() ) > 2000.0f + || (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f ) + {//enemy is far away or moving away from us at a pretty decent speed + TryStartPursuit(); + } + } + } + } + else + { + Pursue(); + } + + //Dodge + if ( !circleStrafing ) { + if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) { + if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) { + if ( gameLocal.random.RandomFloat() > evadeChance ) { + //40% chance of ignoring it - makes them dodge rockets less often but bullets more often? + combat.shotAtTime = 0; + } else if ( evadeDebounce < gameLocal.GetTime() ) { + //ramps down from 400 to 100 over 1 second + float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f))); + idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed; + evadeVel.z *= 0.5f; + move.addVelocity += evadeVel; + move.addVelocity.Normalize(); + move.addVelocity *= speed; + /* + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) { + //just need to do it once? + combat.shotAtTime = 0; + } + */ + if ( evadeDebounceRate > 1 ) + { + evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f)); + } + } + } + } + } + + //If using melee rush to nav to him, stop when we're close enough to attack + if ( combat.tacticalCurrent == AITACTICAL_MELEE + && move.moveCommand == MOVE_TO_ENEMY + && !move.fl.done + && nextBombFireTime < gameLocal.GetTime() - 3000 + && enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) { + StopMove( MOVE_STATUS_DONE ); + ForceTacticalUpdate(); + } else { + //whenever we're not in the middle of something, force an update of our tactical + if ( !aifl.action ) { + if ( !aasFind ) { + if ( move.fl.done ) { + if ( !inPursuit && !circleStrafing ) { + ForceTacticalUpdate(); + } + } + } + } + } + } + + //update light +// if ( lightOn ) { + UpdateLightDef ( ); +// } +} + +/* +============ +rvMonsterStroggHover::OnStartMoving +============ +*/ +void rvMonsterStroggHover::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( move.moveCommand == MOVE_TO_ENEMY ) { + move.range = combat.meleeRange; + } +} + +/* +================ +rvMonsterStroggHover::Save +================ +*/ +void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteBool ( circleStrafing ); + savefile->WriteFloat ( deathPitch ); + savefile->WriteFloat ( deathRoll ); + savefile->WriteFloat ( deathPitchRate ); + savefile->WriteFloat ( deathYawRate ); + savefile->WriteFloat ( deathRollRate ); + savefile->WriteFloat ( deathSpeed ); + savefile->WriteFloat ( deathGrav ); + savefile->WriteInt ( markerCheckTime ); + + savefile->WriteVec3( attackPosOffset ); + +// actionRocketAttack.Save ( savefile ); +// actionBlasterAttack.Save ( savefile ); + actionMGunAttack.Save ( savefile ); + actionMissileAttack.Save ( savefile ); + actionBombAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + actionCircleStrafe.Save ( savefile ); + + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Save ( savefile ); + } + + effectDust.Save ( savefile ); + effectHeadlight.Save ( savefile ); + + marker.Save( savefile ); + savefile->WriteBool ( inPursuit ); + savefile->WriteInt ( holdPosTime ); + savefile->WriteInt ( nextMGunFireTime ); + savefile->WriteInt ( nextMissileFireTime ); + savefile->WriteInt ( nextBombFireTime ); + + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + + savefile->WriteInt( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Restore +================ +*/ +void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadBool ( circleStrafing ); + savefile->ReadFloat ( deathPitch ); + savefile->ReadFloat ( deathRoll ); + savefile->ReadFloat ( deathPitchRate ); + savefile->ReadFloat ( deathYawRate ); + savefile->ReadFloat ( deathRollRate ); + savefile->ReadFloat ( deathSpeed ); + savefile->ReadFloat ( deathGrav ); + savefile->ReadInt ( markerCheckTime ); + + savefile->ReadVec3( attackPosOffset ); + +// actionRocketAttack.Restore ( savefile ); +// actionBlasterAttack.Restore ( savefile ); + actionMGunAttack.Restore ( savefile ); + actionMissileAttack.Restore ( savefile ); + actionBombAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + actionCircleStrafe.Restore ( savefile ); + + InitSpawnArgsVariables(); + //NOTE: if the def file changes the the number of numHoverJoints, this will be BAD... + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Restore ( savefile ); + } + + effectDust.Restore ( savefile ); + effectHeadlight.Restore ( savefile ); + + marker.Restore( savefile ); + savefile->ReadBool ( inPursuit ); + savefile->ReadInt ( holdPosTime ); + savefile->ReadInt ( nextMGunFireTime ); + savefile->ReadInt ( nextMissileFireTime ); + savefile->ReadInt ( nextBombFireTime ); + + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadInt ( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Collide +================ +*/ +bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( aifl.dead ) { + StopHeadlight(); + //stop headlight + if ( effectHeadlight ) { + effectHeadlight->Stop ( ); + effectHeadlight = NULL; + } + // Stop the crash & burn effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + SetState ( "State_Remove" ); + return false; + } + return idAI::Collide( collision, velocity ); +} + +/* +================ +rvMonsterStroggHover::Damage +================ +*/ +void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + bool wasDead = aifl.dead; + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( !wasDead && aifl.dead ) { + SetState( "State_DeathSpiral" ); + } +} +/* +================ +rvMonsterStroggHover::OnDeath +================ +*/ +void rvMonsterStroggHover::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + + idAI::OnDeath ( ); +} + +/* +===================== +rvMonsterStroggHover::DeadMove +===================== +*/ +void rvMonsterStroggHover::DeadMove( void ) { + DeathPush ( ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); +} + +/* +===================== +rvMonsterStroggHover::SkipImpulse +===================== +*/ +bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) { + return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK)); +} + +/* +================ +rvMonsterStroggHover::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( inPursuit && !holdPosTime ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + if ( evadeDebounce >= gameLocal.GetTime() ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( inPursuit ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) { + if ( !GetEnemy() || !enemy.fl.visible ) { + return false; + } + /* + if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) { + //not moving enough + return false; + } + */ + if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) { + //enemy is moving toward me, drop 'em! + return true; + } + return false; +} + +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +bool rvMonsterStroggHover::CheckActions ( void ) { + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) { + return true; + } + +/* + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } +*/ + if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterStroggHover::OnEnemyChange +================ +*/ +void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } +} + +/* +================ +rvMonsterStroggHover::GetIdleAnimName +================ +*/ +const char* rvMonsterStroggHover::GetIdleAnimName ( void ) { + /* + if ( move.moveType == MOVETYPE_FLY ) { + return "flying_idle"; + } + */ + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterStroggHover::DoNamedAttack +================ +*/ +void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) { + if ( joint != INVALID_JOINT ) { + StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( va("fx_%s_flash",attackName), joint ); + Attack ( attackName, joint, GetEnemy() ); + } +} + +/* +================ +rvMonsterStroggHover::FilterTactical +================ +*/ +int rvMonsterStroggHover::FilterTactical ( int availableTactical ) { + availableTactical = idAI::FilterTactical( availableTactical ); + if ( circleStrafing || inPursuit ) { + return 0; + } + if ( nextBombFireTime >= gameLocal.GetTime() ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } else if ( enemy.fl.visible ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } + return availableTactical; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggHover ) +// STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack ) +// STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack ) + STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack ) + STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack ) +// STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft ) +// STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe ) + STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe ) + STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe ) + STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral ) + STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggHover::State_Torso_MGunAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "mgun", jointMGun ); + nextMGunFireTime = gameLocal.GetTime() + mGunFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMGunFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_MissileAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] ); + nextMissileFireTime = gameLocal.GetTime() + missileFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMissileFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) { + //out of shots or enemy too close to safely keep launching rockets at + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( vel.z < 150.0f ) { + vel.z += 20.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + switch ( parms.stage ) { + case STAGE_INIT: + move.fly_offset = 800;//go up! + shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots; + if ( GetEnemy() ) { + //if I'm not above him, give me a quick boost first + float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z; + if ( zDiff < 150.0f ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel.z += 200.0f; + if ( zDiff < 0.0f ) { + //even more if I'm below him! + vel.z -= zDiff*2.0f; + } + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + if ( move.moveCommand == MOVE_TO_ATTACK ) { + StopMove( MOVE_STATUS_DONE ); + } + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + ForceTacticalUpdate(); + } + if ( move.moveCommand == MOVE_NONE + && !inPursuit && !circleStrafing ) { + MoveToEnemy(); + } + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "bomb", jointBomb ); + nextBombFireTime = gameLocal.GetTime() + bombFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextBombFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + move.fly_offset = spawnArgs.GetFloat("fly_offset","250"); + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BlasterAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( 8 ) + 4; + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ +/* +================ +rvMonsterStroggHover::State_Torso_RocketAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} +*/ + +void rvMonsterStroggHover::Evade ( bool left ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed); + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); +} + +stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + Evade( false ); + } else { + Evade( true ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) { + circleStrafing = true; + return SRESULT_DONE; +} + +bool rvMonsterStroggHover::MarkerPosValid ( void ) +{ + //debouncer ftw + if( markerCheckTime > gameLocal.GetTime() ) { + return true; + } + + markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500); + + trace_t trace; + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL ); + if ( !(trace.c.contents&GetPhysics()->GetClipMask()) ) + {//not in solid + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() ); + idActor* enemyAct = NULL; + rvVehicle* enemyVeh = NULL; + if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) { + enemyVeh = static_cast(GetEnemy()); + } else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) { + enemyAct = static_cast(GetEnemy()); + } + idEntity* hitEnt = gameLocal.entities[trace.c.entityNum]; + idActor* hitAct = NULL; + if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) { + hitAct = static_cast(hitEnt); + } + if ( trace.fraction >= 1.0f + || (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum]) + || (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) ) + {//have a clear LOS to enemy + if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) ) + {//valid AAS there... + return true; + } + } + } + return false; +} + +void rvMonsterStroggHover::TryStartPursuit ( void ) +{ + if ( GetEnemy() ) + { + inPursuit = false; + if ( !marker.GetEntity() ) { + //wtf?! + assert(0); + return; + } + attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f ); + if ( attackPosOffset.Length() < 150.0f ) + { + attackPosOffset.Normalize(); + attackPosOffset *= 150.0f; + } + attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset; + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( MarkerPosValid() ) + { + if ( MoveToEntity( marker ) ) + { + inPursuit = true; + holdPosTime = 0; + SetState( "State_Pursue" ); + } + } + } +} + +void rvMonsterStroggHover::Pursue ( void ) +{ + if ( marker.GetEntity() && GetEnemy() ) + { + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() ); + } + if ( MarkerPosValid() ) + { + bool breakOff = false; + if ( move.fl.done ) + {//even once get there, hold that position for a while... + if ( holdPosTime && holdPosTime > gameLocal.GetTime() ) + {//held this position long enough + breakOff = true; + } + else + { + if ( !holdPosTime ) + {//just got there, hold position for a bit + holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime(); + } + if ( !MoveToEntity( marker ) ) + { + breakOff = true; + } + } + } + if ( !breakOff ) + { + return; + } + } + } + if ( !move.fl.done ) + { + StopMove( MOVE_STATUS_DONE ); + } + inPursuit = false; +} + +stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) { + if ( inPursuit ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +void rvMonsterStroggHover::CircleStrafe ( void ) +{ + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) + { + //FIXME: also stop if I bump into something + circleStrafing = false; + strafeTime = 0; + SetState( "State_Combat" ); + return; + } + if ( !strafeTime ) + { + strafeTime = gameLocal.GetTime() + 8000; + //FIXME: try to see which side it clear? + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + } + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed); + strafeVel.z = 0.0f; + vel += strafeVel; + vel.Normalize(); + vel *= circleStrafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( GetEnemy()->GetPhysics()->GetOrigin() ); +} + +stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) { + if ( circleStrafing ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SPIRAL + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + disablePain = true; + + // Make sure all animation and attack states stop + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + + // Start the crash & burn effects + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + PlayEffect ( "fx_hurt", jointHover[i], false ); + effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true ); + } + } + deathPitch = viewAxis.ToAngles()[0]; + deathRoll = viewAxis.ToAngles()[2]; + + deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f; + deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f; + deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f; + deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f; + deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f; + + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + StopSound( SND_CHANNEL_HEART, false ); + StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL ); + } + return SRESULT_STAGE ( STAGE_SPIRAL ); + case STAGE_SPIRAL: + { + move.current_yaw += (strafeRight?-deathYawRate:deathYawRate); + deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate ); + deathRoll += (strafeRight?deathRollRate:-deathRollRate); + viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[0] * deathSpeed; + strafeVel.z = 0; + vel += strafeVel; + vel.Normalize(); + vel *= deathSpeed; + vel += GetPhysics()->GetGravity()/deathGrav; + + physicsObj.UseVelocityMove( true ); + + physicsObj.SetLinearVelocity( vel ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + refSound.parms.frequencyShift += 0.025f; + emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms ); + } + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_StroggMarine.cpp b/source/mpgame/ai/Monster_StroggMarine.cpp new file mode 100644 index 0000000..f4effa8 --- /dev/null +++ b/source/mpgame/ai/Monster_StroggMarine.cpp @@ -0,0 +1,753 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +//NOTE: actually a bit of a misnomer, as all Strogg Marine types use this class now... +class rvMonsterStroggMarine : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggMarine ); + + rvMonsterStroggMarine ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + + int maxShots; + int minShots; + int shots; + int shotsFired; + + int fireAnimNum; + bool spraySideRight; + int sweepCount; + + bool EnemyMovingToRight ( void ); + +private: + + void CalculateShots ( void ); + + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + // Actions + rvAIAction actionStrafe; + rvAIAction actionCrouchRangedAttack; + rvAIAction actionRollAttack; + rvAIAction actionSprayAttack; + rvAIAction actionAngry; + rvAIAction actionReload; + + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RollAttack ( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_Angry ( rvAIAction* action, int animNum ); + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_RollAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggMarine ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggMarine ) +END_CLASS + +/* +================ +rvMonsterStroggMarine::rvMonsterStroggMarine +================ +*/ +rvMonsterStroggMarine::rvMonsterStroggMarine ( ) { + nextShootTime = 0; +} + +void rvMonsterStroggMarine::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.2" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterStroggMarine::Spawn +================ +*/ +void rvMonsterStroggMarine::Spawn ( void ) { + actionStrafe.Init ( spawnArgs, "action_strafe", NULL, 0 ); + actionCrouchRangedAttack.Init ( spawnArgs, "action_crouchRangedAttack", NULL, AIACTIONF_ATTACK ); + actionRollAttack.Init ( spawnArgs, "action_rollAttack", NULL, AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionAngry.Init ( spawnArgs, "action_angry", NULL, 0 ); + actionReload.Init ( spawnArgs, "action_reload", NULL, 0 ); + + InitSpawnArgsVariables(); + + shots = 0; + shotsFired = 0; +} + +/* +================ +rvMonsterStroggMarine::Save +================ +*/ +void rvMonsterStroggMarine::Save ( idSaveGame *savefile ) const { + actionStrafe.Save ( savefile ); + actionCrouchRangedAttack.Save( savefile ); + actionRollAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionAngry.Save( savefile ); + actionReload.Save( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + + savefile->WriteInt ( fireAnimNum ); + savefile->WriteBool ( spraySideRight ); + savefile->WriteInt ( sweepCount ); + + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterStroggMarine::Restore +================ +*/ +void rvMonsterStroggMarine::Restore ( idRestoreGame *savefile ) { + actionStrafe.Restore ( savefile ); + actionCrouchRangedAttack.Restore( savefile ); + actionRollAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionAngry.Restore( savefile ); + actionReload.Restore( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + + savefile->ReadInt ( fireAnimNum ); + savefile->ReadBool ( spraySideRight ); + savefile->ReadInt ( sweepCount ); + + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterStroggMarine::OnStopMoving +============ +*/ +void rvMonsterStroggMarine::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + actionMeleeAttack.timer.Clear( actionTime ); + } + else + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionCrouchRangedAttack.timer.Clear( actionTime ); + actionRollAttack.timer.Clear( actionTime ); + actionSprayAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterStroggMarine::CheckAction_JumpBack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage && gameLocal.time - pain.lastTakenTime > 1500 ) { + return false; + } + + // enemy must be in front to jump backwards + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + } + // TODO: dont evade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeRight +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + return idAI::CheckAction_RangedAttack( action, animNum ); +} + +/* +================ +rvMonsterStroggMarine::CheckAction_CrouchRangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RollAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RollAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_SprayAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Angry +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Angry ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Reload +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckActions +================ +*/ +bool rvMonsterStroggMarine::CheckActions ( void ) { + + if ( idAI::CheckActions ( ) ) + { + return true; + } + if ( PerformAction ( &actionCrouchRangedAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_CrouchRangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRollAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_RollAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_SprayAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Strafe ) || + PerformAction ( &actionAngry, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Angry ) || + PerformAction ( &actionReload, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Reload ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggMarine ) + STATE ( "Torso_RollAttack", rvMonsterStroggMarine::State_Torso_RollAttack ) + STATE ( "Torso_RangedAttack", rvMonsterStroggMarine::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterStroggMarine::State_Torso_MovingRangedAttack ) + STATE ( "Torso_SprayAttack", rvMonsterStroggMarine::State_Torso_SprayAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggMarine::State_Torso_RollAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RollAttack ( const stateParms_t& parms ) { + enum { + TORSO_ROLLATTACK_ROLL, + TORSO_ROLLATTACK_FACE, + TORSO_ROLLATTACK_FIRE, + TORSO_ROLLATTACK_FINISH + }; + + TurnToward(enemy.lastKnownPosition); + + switch ( parms.stage ) { + // Start the roll attack animation + case TORSO_ROLLATTACK_ROLL: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the roll + PlayAnim ( ANIMCHANNEL_TORSO, "dive_turn", parms.blendFrames ); + move.fl.noTurn = false; + //FaceEnemy(); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FACE ); + + // Wait for roll animation to finish + case TORSO_ROLLATTACK_FACE: + if ( AnimDone ( ANIMCHANNEL_LEGS, 6 ) ) { + return SRESULT_STAGE ( TORSO_ROLLATTACK_FIRE ); + } + return SRESULT_WAIT; + + // Play fire animation + case TORSO_ROLLATTACK_FIRE: + if ( !enemy.ent || !enemy.fl.visible ) + {//whoops! rolled out of LOS + return SRESULT_DONE; + } + if ( enemy.fl.inFov ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "shotgun_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FINISH ); + } + return SRESULT_WAIT; + + // Wait for fire animation to finish + case TORSO_ROLLATTACK_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterStroggMarine::CalculateShots +================ +*/ +void rvMonsterStroggMarine::CalculateShots ( void ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + + // Update the firing animation playback rate + /* + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } + */ +} + +/* +================ +rvMonsterStroggMarine::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + //TurnToward(enemy.lastKnownPosition); + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && move.fl.running && !actionRangedAttack.fl.overrideLegs && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + fireAnimNum = gameLocal.random.RandomInt(2)+1; + CalculateShots(); + shotsFired = 0; + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_loop", fireAnimNum), 0 ); + shots--; + shotsFired++; + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov && shotsFired >= minShots ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots(); + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "base", attackJoint, GetEnemy() ); + PlayEffect( "fx_blaster_muzzleflash", attackJoint ); + } + StartSound( "snd_weapon_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= minShots) || !move.fl.running || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::EnemyMovingToRight +================ +*/ +bool rvMonsterStroggMarine::EnemyMovingToRight( void ) +{ + if ( !GetEnemy() ) + { + return false; + } + //use their movement direction + idVec3 dir = GetEnemy()->GetPhysics()->GetLinearVelocity(); + //flatten + dir.z = 0; + dir.Normalize(); + + idVec3 fwd = viewAxis[0]; + idVec3 lt = viewAxis[1]; + + float dot = 0.0f; + dot = DotProduct(dir, lt); + if ( dot > 0 ) + { + return false; + } + else + { + return true; + } +} + +/* +================ +rvMonsterStroggMarine::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + spraySideRight = EnemyMovingToRight(); + sweepCount = 0; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_start", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_start", 0 ); + } + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_sweep", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_sweep", 0 ); + } + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + bool curEnemyMovingRight = EnemyMovingToRight(); + if ( sweepCount < 3 + && (!gameLocal.random.RandomInt(2) + || (spraySideRight && !curEnemyMovingRight) + || (!spraySideRight && curEnemyMovingRight)) ) + { + spraySideRight = !spraySideRight; + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_end", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_end", 0 ); + } + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_TeleportDropper.cpp b/source/mpgame/ai/Monster_TeleportDropper.cpp new file mode 100644 index 0000000..69b7bf9 --- /dev/null +++ b/source/mpgame/ai/Monster_TeleportDropper.cpp @@ -0,0 +1,536 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +class rvMonsterTeleportDropper : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTeleportDropper ); + + rvMonsterTeleportDropper ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* projectileDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual const char* GetIdleAnimName ( void ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + +protected: + + idEntityPtr spawner; + + // Actions + rvAIAction actionDropSpawners; + + //bool dropAtGoalOnly; + +// virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); +// virtual bool MoveToEnemy ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + +private: + + bool leftSideBlocked; + bool rightSideBlocked; + jointHandle_t jointLeftFrontCannon; + jointHandle_t jointLeftRearCannon; + jointHandle_t jointRightFrontCannon; + jointHandle_t jointRightRearCannon; + + bool leapDidAttack; + + bool CheckAction_DropSpawners ( rvAIAction* action, int animNum ); + // Torso states + stateResult_t State_Torso_DropSpawners ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTeleportDropper ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTeleportDropper ) +END_CLASS + +/* +================ +rvMonsterTeleportDropper::rvMonsterTeleportDropper +================ +*/ +rvMonsterTeleportDropper::rvMonsterTeleportDropper ( void ) { + spawner = NULL; + leftSideBlocked = rightSideBlocked = false; + leapDidAttack = false; +// dropAtGoalOnly = false; +} + +void rvMonsterTeleportDropper::InitSpawnArgsVariables ( void ) +{ + jointLeftFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_front_cannon" ) ); + jointLeftRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_rear_cannon" ) ); + jointRightFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_front_cannon" ) ); + jointRightRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_rear_cannon" ) ); +} + +/* +================ +rvMonsterTeleportDropper::Spawn +================ +*/ +void rvMonsterTeleportDropper::Spawn ( void ) { + idEntity* ent; + idDict args; + + // Create the spawner entity + args.Clear ( ); + args.Set ( "classname", spawnArgs.GetString ( "def_spawner" ) ); + gameLocal.SpawnEntityDef ( args, &ent ); + + if ( ent ) { + spawner = static_cast(ent); + spawner->ProcessEvent ( &EV_Activate, this ); + } + + // Define actions + actionDropSpawners.Init ( spawnArgs, "action_dropSpawners", "Torso_DropSpawners", 0 ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Save +================ +*/ +void rvMonsterTeleportDropper::Save ( idSaveGame *savefile ) const { + spawner.Save( savefile ); + + actionDropSpawners.Save( savefile ); + savefile->WriteBool(leftSideBlocked); + savefile->WriteBool(rightSideBlocked); + savefile->WriteBool(leapDidAttack); +} + +/* +================ +rvMonsterTeleportDropper::Restore +================ +*/ +void rvMonsterTeleportDropper::Restore ( idRestoreGame *savefile ) { + spawner.Restore( savefile ); + + actionDropSpawners.Restore( savefile ); + savefile->ReadBool(leftSideBlocked); + savefile->ReadBool(rightSideBlocked); + savefile->ReadBool(leapDidAttack); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Think +================ +*/ +void rvMonsterTeleportDropper::Think ( void ) { + idAI::Think ( ); + if ( aifl.action && actionLeapAttack.status == rvAIAction::STATUS_OK && !leapDidAttack ) { + //in leap attack action + if ( GetEnemy() && enemy.fl.inFov && enemy.range < 64.0f ) + { + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_leap" ), false ); + AttackMelee( "leap", attackDict ); + StartSound( "snd_leap_hit", SND_CHANNEL_BODY, 0, 0, 0 ); + leapDidAttack = true; + } + } +} + +/* +================ +rvMonsterTeleportDropper::State_CombatHide +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_CombatHide ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) ) { + if ( !move.fl.moving ) { + TurnToward ( enemy.lastKnownPosition ); + } + } + + if ( !(FilterTactical( combat.tacticalMaskAvailable )&AITACTICAL_HIDE_BIT) ) + {//shouldn't hide anymore + ForceTacticalUpdate(); + } + + if ( UpdateTactical ( 5000 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; +} + +/* +void rvMonsterTeleportDropper::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + idAI::OnStopMoving(oldMoveCommand); + dropAtGoalOnly = false; +} + +bool rvMonsterTeleportDropper::MoveToEnemy ( void ) { + dropAtGoalOnly = false; + if ( !targets.Num() ) + { + return (idAI::MoveToEnemy()); + } + if ( !GetEnemy() ) + { + return false; + } + idEntity* goalEnt; + idEntity* bestGoal = NULL; + int areaNum = 0; + aasPath_t path; + idVec3 pos; + float dist; + float bestDist = actionDropSpawners.maxRange; + + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + if ( !aiManager.ValidateDestination(this,goalEnt->GetPhysics()->GetOrigin()) ) + { + continue; + } + dist = GetEnemy()->DistanceTo(pos); + if ( dist >= bestDist ) + { + continue; + } + // See if it's possible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + continue; + } + } + dist = bestDist; + bestGoal = goalEnt; + } + if ( bestGoal ) + { + if ( MoveToEntity( bestGoal, bestGoal->spawnArgs.GetFloat("range","64") ) ) + { + dropAtGoalOnly = true; + return true; + } + } + return (idAI::MoveToEnemy()); +} +*/ + +/* +================ +rvMonsterTeleportDropper::FilterTactical +================ +*/ +int rvMonsterTeleportDropper::FilterTactical ( int availableTactical ) { + //do normal filter + availableTactical = idAI::FilterTactical ( availableTactical ); + + //being tethered removes these - add them back in, we don't obey tethers! tethers are for chumps! + availableTactical |= (AITACTICAL_MELEE_BIT|AITACTICAL_HIDE_BIT); + + // Only allow hiding while the drop spawner action isnt ready + if ( !actionDropSpawners.timer.IsDone ( actionTime ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + // Hide while the spawner is still active + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + if ( leftSideBlocked && rightSideBlocked ) + {//both sides are blocked + if ( move.fl.done ) + {//not trying to move + //get out of here! + ForceTacticalUpdate(); + //try finding somewhere else to stand! + availableTactical |= AITACTICAL_RANGED_BIT; + } + } + return availableTactical; +} + +/* +================ +rvMonsterTeleportDropper::CheckAction_DropSpawners +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_DropSpawners ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + //Check to see if at least one side is open + leftSideBlocked = rightSideBlocked = false; + + //trace against solid architecture only, don't care about other entities + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + start = GetPhysics()->GetCenterMass(); + //NOTE: ASSUMPTION + int mask = (MASK_SOLID|CONTENTS_LARGESHOTCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_MONSTERCLIP); + //check left + end = start + (viewAxis[1] * 64.0f); + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //left side is blocked + leftSideBlocked = true; + } + //check right + end = start - (viewAxis[1] * 64.0f); + //trace against solid architecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //right side blocked + rightSideBlocked = true; + } + if ( leftSideBlocked && rightSideBlocked ) + { + return false; + } + + /* + if ( dropAtGoalOnly && !move.fl.done && move.moveCommand == MOVE_TO_ENTITY ) + {//FIXME: check ReachedPos? + return false; + } + //fuck it, just check them all + if ( targets.Num() ) + { + idEntity* goalEnt; + idVec3 pos; + float dist; + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + dist = DistanceTo(pos); + if ( dist <= goalEnt->spawnArgs.GetFloat("range","64") ) + { + return true; + } + } + return false; + } + */ + + return true; +} + +/* +================ +rvMonsterTeleportDropper::Collide +================ +*/ + +/* +================ +rvMonsterTeleportDropper::CheckAction_LeapAttack +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + if ( !move.fl.done + && !move.fl.blocked ) + {//still running away + return false; + } + //done running, okay to attack if in range + } + else + { + if ( !leftSideBlocked || !rightSideBlocked ) + {//clear to shoot on sides + return false; + } + } + + if ( idAI::CheckAction_LeapAttack( action, animNum ) ) + { + leapDidAttack = false; + return true; + } + return false; +} + +/* +================ +rvMonsterTeleportDropper::CheckActions +================ +*/ +bool rvMonsterTeleportDropper::CheckActions ( void ) { + if ( PerformAction ( &actionDropSpawners, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_DropSpawners ) ) { + return true; + } + if ( CheckPainActions ( ) ) { + return true; + } + if ( PerformAction ( &actionLeapAttack, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_LeapAttack ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterTeleportDropper::AttackMissileExt +================ +*/ +idProjectile* rvMonsterTeleportDropper::AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + idProjectile* proj; + + // Launch the projectile + if ( leftSideBlocked || rightSideBlocked ) + { + if ( idStr::Icmp( attackName, "dropSpawner" ) == 0 ) + { + if ( leftSideBlocked ) + { + if ( joint == jointLeftFrontCannon + || joint == jointLeftRearCannon ) + { + return NULL; + } + } + else if ( rightSideBlocked ) + { + if ( joint == jointRightFrontCannon + || joint == jointRightRearCannon ) + { + return NULL; + } + } + } + } + proj = idAI::AttackRanged ( attackName, attackDict, joint, target, pushVelocity ); + + if ( !proj ) { + return NULL; + } + + // If it was a spawner projectile set the spawer + if ( proj->IsType ( rvSpawnerProjectile::GetClassType() ) ) { + static_cast(proj)->SetSpawner ( spawner ); + } + + return proj; +} + +/* +================ +rvMonsterTeleportDropper::GetIdleAnimName +================ +*/ +const char* rvMonsterTeleportDropper::GetIdleAnimName ( void ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) { + return "idle_hide"; + } + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterTeleportDropper::GetDebugInfo +================ +*/ +void rvMonsterTeleportDropper::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterTeleportDropper", "action_dropSpawners", aiActionStatusString[actionDropSpawners.status], userData ); + //proc ( "rvMonsterTeleportDropper", "dropAtGoalOnly", dropAtGoalOnly?"true":"false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTeleportDropper ) + STATE ( "State_CombatHide", rvMonsterTeleportDropper::State_CombatHide ) + STATE ( "Torso_DropSpawners", rvMonsterTeleportDropper::State_Torso_DropSpawners ) +END_CLASS_STATES + +/* +================ +rvMonsterTeleportDropper::State_Torso_DropSpawners +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_Torso_DropSpawners ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "drop_spawners", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ForceTacticalUpdate ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/source/mpgame/ai/Monster_Turret.cpp b/source/mpgame/ai/Monster_Turret.cpp new file mode 100644 index 0000000..3074d9f --- /dev/null +++ b/source/mpgame/ai/Monster_Turret.cpp @@ -0,0 +1,204 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurret : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurret ); + + rvMonsterTurret ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + virtual bool CheckActions ( void ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTurret ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurret ) +END_CLASS + +/* +================ +rvMonsterTurret::rvMonsterTurret +================ +*/ +rvMonsterTurret::rvMonsterTurret ( ) { + shieldHealth = 0; +} + +void rvMonsterTurret::InitSpawnArgsVariables ( void ) { + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurret::Spawn +================ +*/ +void rvMonsterTurret::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + health += shieldHealth; + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterTurret::Save +================ +*/ +void rvMonsterTurret::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterTurret::Restore +================ +*/ +void rvMonsterTurret::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTurret::CheckActions +================ +*/ +bool rvMonsterTurret::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterTurret::Pain +================ +*/ +bool rvMonsterTurret::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurret ) + STATE ( "State_Combat", rvMonsterTurret::State_Combat ) + STATE ( "State_Killed", rvMonsterTurret::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurret::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurret::State_Combat +================ +*/ +stateResult_t rvMonsterTurret::State_Combat ( const stateParms_t& parms ) { + // Aquire a new enemy if we dont have one + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + FaceEnemy ( ); + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurret::State_Killed +================ +*/ +stateResult_t rvMonsterTurret::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_death" ), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurret::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurret::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/Monster_TurretFlying.cpp b/source/mpgame/ai/Monster_TurretFlying.cpp new file mode 100644 index 0000000..c46ad85 --- /dev/null +++ b/source/mpgame/ai/Monster_TurretFlying.cpp @@ -0,0 +1,297 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurretFlying : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurretFlying ); + + rvMonsterTurretFlying ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual bool CheckActions ( void ); + +protected: + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Fall ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + CLASS_STATES_PROTOTYPE ( rvMonsterTurretFlying ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurretFlying ) +END_CLASS + +/* +================ +rvMonsterTurretFlying::rvMonsterTurretFlying +================ +*/ +rvMonsterTurretFlying::rvMonsterTurretFlying ( ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); +} + +void rvMonsterTurretFlying::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurretFlying::Spawn +================ +*/ +void rvMonsterTurretFlying::Spawn ( void ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + + InitSpawnArgsVariables(); + shots = 0; + + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + //don't take damage until we open up + fl.takedamage = false; +} + +/* +================ +rvMonsterTurretFlying::CheckActions +================ +*/ +bool rvMonsterTurretFlying::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + + +/* +================ +rvMonsterTurretFlying::Pain +================ +*/ +bool rvMonsterTurretFlying::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterTurretFlying::Save +================ +*/ +void rvMonsterTurretFlying::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ) ; +} + +/* +================ +rvMonsterTurretFlying::Restore +================ +*/ +void rvMonsterTurretFlying::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ) ; + + InitSpawnArgsVariables(); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurretFlying ) + STATE ( "State_Combat", rvMonsterTurretFlying::State_Combat ) + STATE ( "State_Fall", rvMonsterTurretFlying::State_Fall ) + STATE ( "State_Killed", rvMonsterTurretFlying::State_Killed ) + + STATE ( "Torso_Idle", rvMonsterTurretFlying::State_Torso_Idle ) + STATE ( "Legs_Idle", rvMonsterTurretFlying::State_Legs_Idle ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurretFlying::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurretFlying::State_Combat +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Combat ( const stateParms_t& parms ) { + // Special handling for not being on the ground + if ( !move.fl.onGround ) { + PostState ( "State_Fall", 0 ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurretFlying::State_Fall +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact + STAGE_WAITOPEN, // Wait for drop turret to open up + STAGE_OPENED, // Drop turret opened and ready for combat + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle_closed", 0 ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( move.fl.onGround ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + PlayAnim ( ANIMCHANNEL_TORSO, "open", 2 ); + return SRESULT_STAGE ( STAGE_WAITOPEN ); + + case STAGE_WAITOPEN: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_OPENED ); + } + return SRESULT_WAIT; + + case STAGE_OPENED: + // Activate shield + fl.takedamage = true; + health += shieldHealth; + PlayEffect ( "fx_shieldOpen", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + PostState ( "State_Combat" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterTurretFlying::State_Killed +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurretFlying::State_Torso_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_Idle ( const stateParms_t& parms ) { + if ( move.fl.onGround ) { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_open", parms.blendFrames ); + } else { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_closed", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Legs_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Legs_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, (shots&1)?"range_attack_top":"range_attack_bottom", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/ai/VehicleAI.cpp b/source/mpgame/ai/VehicleAI.cpp new file mode 100644 index 0000000..734b898 --- /dev/null +++ b/source/mpgame/ai/VehicleAI.cpp @@ -0,0 +1,242 @@ +//---------------------------------------------------------------- +// rvVehicleAI.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +CLASS_DECLARATION( idAI, rvVehicleAI ) + EVENT( VD_ChoosePathTarget, rvVehicleAI::Event_ChoosePathTarget ) +END_CLASS + +/* +================ +rvVehicleAI::rvVehicleAI +================ +*/ +rvVehicleAI::rvVehicleAI ( void ) { + flags = 0; +} + +/* +================ +rvVehicleAI::~rvVehicleAI +================ +*/ +rvVehicleAI::~rvVehicleAI ( void ) { +} + +/* +================ +rvVehicleAI::Spawn +================ +*/ +void rvVehicleAI::Spawn ( void ) { + driver = static_cast( gameLocal.SpawnEntityType( rvVehicleDriver::GetClassType() ) ); +} + +/* +================ +rvVehicleAI::Save +================ +*/ +void rvVehicleAI::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); + savefile->WriteInt ( flags ); +} + +/* +================ +rvVehicleAI::Restore +================ +*/ +void rvVehicleAI::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); + savefile->ReadInt ( flags ); +} + +/* +================ +rvVehicleAI::SetVehicle +================ +*/ +void rvVehicleAI::SetVehicle ( rvVehicleMonster * vehicle ) { + const idKeyValue * val = spawnArgs.MatchPrefix( "target", 0 ); + + if ( !driver ) { + return; + } + + driver->ProcessEvent ( &AI_EnterVehicle, vehicle ); + driver->SetPathingMode( rvVehicleDriver::VDPM_Custom, this ); + vehicle->ProcessEvent ( &EV_Door_Lock, true ); + vehicle->team = team; + driver->team = team; + + if ( val ) { + driver->ProcessEvent ( &AI_ScriptedMove, gameLocal.FindEntity( val->GetValue() ), 0.0f, 0 ); + } else { + driver->ProcessEvent ( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::Event_ChoosePathTarget +================ +*/ +void rvVehicleAI::Event_ChoosePathTarget ( idEntity * current ) { + if ( !current ) { + current = const_cast( FindClosestNode() ); + } + + if ( ( flags & VAIF_Freeze ) == 0 ) { + const idVec3 & playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + switch ( flags & 0x03 ) { + case 1 : + idThread::ReturnEntity( MoveCloserTo( playerOrigin, current ) ); + break; + + case 2 : + idThread::ReturnEntity( MoveAwayFrom( playerOrigin, current ) ); + break; + + default: + idThread::ReturnEntity( NULL ); + } + } + + //PostEventMS( &VD_ChoosePathTarget, gameLocal.random.RandomInt( 100 ) + 100, driver->lastPathTargetInfo.node.GetEntity() ); +} + +/* +================ +rvVehicleAI::OnWakeUp +================ +*/ +void rvVehicleAI::OnWakeUp ( void ) { + SetMoveType ( MOVETYPE_CUSTOM ); +} + +/* +================ +rvVehicleAI::CustomMove +================ +*/ +void rvVehicleAI::CustomMove ( void ) { + if ( !driver->pathTargetInfo.node && !(flags & VAIF_Freeze) ) { + driver->ProcessEvent( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::MoveCloserTo +================ +*/ +const idEntity * rvVehicleAI::MoveCloserTo ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float shortestDistance = FLT_MAX; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( shortestDistance > distance ) { + shortestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::MoveAwayFrom +================ +*/ +const idEntity * rvVehicleAI::MoveAwayFrom ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float longestDistance = FLT_MIN; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( longestDistance < distance ) { + longestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::FindClosestNode +================ +*/ +const idEntity * rvVehicleAI::FindClosestNode ( void ) const { + idEntity * current = 0; + const idEntity * best = 0; + float bestDistance = FLT_MAX; + + for (;;) { + current = gameLocal.FindEntityUsingDef( current, "target_vehicle_path" ); + + if ( !current ) { + break; + } + + float dist = ( current->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist < bestDistance ) { + bestDistance = dist; + best = current; + } + } + + return best; +} + +/* +================ +rvVehicleAI::Think +================ +*/ +void rvVehicleAI::Think ( void ) { + idAI::Think(); + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + //HACK: always choose player as the enemy... something is broken somewhere, + // and this is a quick way to get some gameplay out of this for now. Deadlines pwn. + if ( driver && driver->IsDriving() ) { + enemy.ent = gameLocal.GetLocalPlayer(); + enemy.range = ( enemy.ent->GetPhysics()->GetOrigin() - driver->vehicleController.GetVehicle()->GetPhysics()->GetOrigin() ).Length(); + } +} + + diff --git a/source/mpgame/ai/VehicleAI.h b/source/mpgame/ai/VehicleAI.h new file mode 100644 index 0000000..9d4a60e --- /dev/null +++ b/source/mpgame/ai/VehicleAI.h @@ -0,0 +1,96 @@ +//---------------------------------------------------------------- +// rvVehicleAI.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEAI_H__ +#define __GAME_VEHICLEAI_H__ + +#ifndef __AI_H__ +#include "AI.h" +#endif +#ifndef __GAME_VEHICLEMONSTER_H__ +#include "../vehicle/VehicleMonster.h" +#endif + +enum VehicleAI_Flags { + VAIF_Chase = 1, + VAIF_Avoid = 2, + VAIF_Freeze = 4, +}; + +class rvVehicleAI : public idAI { + friend class rvVehicleMonster; + +public: + CLASS_PROTOTYPE( rvVehicleAI ); + + rvVehicleAI ( void ); + ~rvVehicleAI ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void SetVehicle ( rvVehicleMonster * vehicle ); + + void Random ( void ); + void StraightToEnemy ( void ); + void ChaseEnemy ( void ); + void AvoidEnemy ( void ); + void Stop ( void ); + void Start ( void ); + + int & GetFlags ( void ) { return flags; } + + bool IsDriving ( void ) { return driver && driver->IsDriving(); } + rvVehicleDriver* GetDriver ( void ) { return driver.GetEntity(); } + const rvVehicleDriver* GetDriver ( void ) const { return driver.GetEntity(); } + +private: + virtual void OnWakeUp ( void ); + virtual void CustomMove ( void ); + + const idEntity * MoveCloserTo ( const idVec3 & point, idEntity * current ); + const idEntity * MoveAwayFrom ( const idVec3 & point, idEntity * current ); + const idEntity * FindClosestNode ( void ) const; + + void Event_ChoosePathTarget ( idEntity * current ); + + idEntityPtr driver; + int flags; +}; + +ID_INLINE void rvVehicleAI::Random ( void ) { + flags = 0; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::StraightToEnemy ( void ) { + driver->ProcessEvent( &AI_ScriptedMove, enemy.ent.GetEntity(), 0.0f, 0 ); +} + +ID_INLINE void rvVehicleAI::ChaseEnemy ( void ) { + flags = VAIF_Chase; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::AvoidEnemy ( void ) { + flags = VAIF_Avoid; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::Stop ( void ) { + flags = ( flags & 0x03 ) | VAIF_Freeze; + driver->ProcessEvent( &AI_ScriptedStop ); +} + +ID_INLINE void rvVehicleAI::Start ( void ) { + flags = ( flags & 0x03 ) & ~VAIF_Freeze; + //driver->ProcessEvent( &EV_Activate, this ); + CustomMove(); +} + +#endif // __GAME_VEHICLEAI_H__ diff --git a/source/mpgame/anim/Anim.cpp b/source/mpgame/anim/Anim.cpp new file mode 100644 index 0000000..77430f7 --- /dev/null +++ b/source/mpgame/anim/Anim.cpp @@ -0,0 +1,1182 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +bool idAnimManager::forceExport = false; + +/*********************************************************************** + + idMD5Anim + +***********************************************************************/ + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::idMD5Anim() { + ref_count = 0; + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + totaldelta.Zero(); +} + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::~idMD5Anim() { + Free(); +} + +/* +==================== +idMD5Anim::Free +==================== +*/ +void idMD5Anim::Free( void ) { + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + name = ""; + + totaldelta.Zero(); + + jointInfo.Clear(); + bounds.Clear(); + componentFrames.Clear(); +} + +/* +==================== +idMD5Anim::NumFrames +==================== +*/ +int idMD5Anim::NumFrames( void ) const { + return numFrames; +} + +/* +==================== +idMD5Anim::NumJoints +==================== +*/ +int idMD5Anim::NumJoints( void ) const { + return numJoints; +} + +/* +==================== +idMD5Anim::Length +==================== +*/ +int idMD5Anim::Length( void ) const { + return animLength; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const idVec3 &idMD5Anim::TotalMovementDelta( void ) const { + return totaldelta; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const char *idMD5Anim::Name( void ) const { + return name; +} + +/* +==================== +idMD5Anim::Reload +==================== +*/ +bool idMD5Anim::Reload( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idStr filename; + + filename = name; + Free(); + + return LoadAnim( filename ); +} + +/* +==================== +idMD5Anim::Allocated +==================== +*/ +size_t idMD5Anim::Allocated( void ) const { + size_t size = bounds.Allocated() + jointInfo.Allocated() + componentFrames.Allocated() + name.Allocated(); + return size; +} + +/* +==================== +idMD5Anim::LoadAnim +==================== +*/ +bool idMD5Anim::LoadAnim( const char *filename ) { + int version; +// RAVEN BEGIN +// jsinger: done this way to minimize amount of code change + idAutoPtr lexer( LexerFactory::MakeLexer(LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT) ); + Lexer &parser(*lexer); +// RAVEN END + idToken token; + int i, j; + int num; + + if ( !parser.LoadFile( filename ) ) { + return false; + } + + Free(); + + name = filename; + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + numJoints = parser.ParseInt(); + if ( numJoints <= 0 ) { + parser.Error( "Invalid number of joints: %d", numJoints ); + } + + // parse frame rate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate < 0 ) { + parser.Error( "Invalid frame rate: %d", frameRate ); + } + + // parse number of animated components + parser.ExpectTokenString( "numAnimatedComponents" ); + numAnimatedComponents = parser.ParseInt(); + if ( ( numAnimatedComponents < 0 ) || ( numAnimatedComponents > numJoints * 6 ) ) { + parser.Error( "Invalid number of animated components: %d", numAnimatedComponents ); + } + + // parse the hierarchy + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( numJoints ); + parser.ExpectTokenString( "hierarchy" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + parser.ReadToken( &token ); +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + jointInfo[ i ].nameIndex = animationLib->JointIndex( token ); +// RAVEN END + + // parse parent num + jointInfo[ i ].parentNum = parser.ParseInt(); + if ( jointInfo[ i ].parentNum >= i ) { + parser.Error( "Invalid parent num: %d", jointInfo[ i ].parentNum ); + } + + if ( ( i != 0 ) && ( jointInfo[ i ].parentNum < 0 ) ) { + parser.Error( "Animations may have only one root joint" ); + } + + // parse anim bits + jointInfo[ i ].animBits = parser.ParseInt(); + if ( jointInfo[ i ].animBits & ~63 ) { + parser.Error( "Invalid anim bits: %d", jointInfo[ i ].animBits ); + } + + // parse first component + jointInfo[ i ].firstComponent = parser.ParseInt(); + if ( ( numAnimatedComponents > 0 ) && ( ( jointInfo[ i ].firstComponent < 0 ) || ( jointInfo[ i ].firstComponent >= numAnimatedComponents ) ) ) { + parser.Error( "Invalid first component: %d", jointInfo[ i ].firstComponent ); + } + } + + parser.ExpectTokenString( "}" ); + + // parse bounds + parser.ExpectTokenString( "bounds" ); + parser.ExpectTokenString( "{" ); + bounds.SetGranularity( 1 ); + bounds.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, bounds[ i ][ 0 ].ToFloatPtr() ); + parser.Parse1DMatrix( 3, bounds[ i ][ 1 ].ToFloatPtr() ); + } + parser.ExpectTokenString( "}" ); + + // parse base frame + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( numJoints ); + parser.ExpectTokenString( "baseframe" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + idCQuat q; + parser.Parse1DMatrix( 3, baseFrame[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, q.ToFloatPtr() );//baseFrame[ i ].q.ToFloatPtr() ); + baseFrame[ i ].q = q.ToQuat();//.w = baseFrame[ i ].q.CalcW(); + } + parser.ExpectTokenString( "}" ); + + // parse frames + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( numAnimatedComponents * numFrames ); + + float *componentPtr = componentFrames.Ptr(); + for( i = 0; i < numFrames; i++ ) { + parser.ExpectTokenString( "frame" ); + num = parser.ParseInt(); + if ( num != i ) { + parser.Error( "Expected frame number %d", i ); + } + parser.ExpectTokenString( "{" ); + + for( j = 0; j < numAnimatedComponents; j++, componentPtr++ ) { + *componentPtr = parser.ParseFloat(); + } + + parser.ExpectTokenString( "}" ); + } + + // get total move delta + if ( !numAnimatedComponents ) { + totaldelta.Zero(); + } else { + componentPtr = &componentFrames[ jointInfo[ 0 ].firstComponent ]; + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.x; + } + totaldelta.x = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.x = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.y; + } + totaldelta.y = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.y = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.z; + } + totaldelta.z = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + } else { + totaldelta.z = 0.0f; + } + } + baseFrame[ 0 ].t.Zero(); + + // we don't count last frame because it would cause a 1 frame pause at the end + animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + // done + return true; +} + +/* +==================== +idMD5Anim::IncreaseRefs +==================== +*/ +void idMD5Anim::IncreaseRefs( void ) const { + ref_count++; +} + +/* +==================== +idMD5Anim::DecreaseRefs +==================== +*/ +void idMD5Anim::DecreaseRefs( void ) const { + ref_count--; +} + +/* +==================== +idMD5Anim::NumRefs +==================== +*/ +int idMD5Anim::NumRefs( void ) const { + return ref_count; +} + +/* +==================== +idMD5Anim::ConvertTimeToFrame +==================== +*/ +void idMD5Anim::ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const { + int frameTime; + int frameNum; + + if ( numFrames <= 1 ) { + frame.frame1 = 0; + frame.frame2 = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + if ( time <= 0 ) { + frame.frame1 = 0; + frame.frame2 = 1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + frameTime = time * frameRate; + frameNum = frameTime / 1000; + frame.cycleCount = frameNum / ( numFrames - 1 ); + + if ( ( cyclecount > 0 ) && ( frame.cycleCount >= cyclecount ) ) { + frame.cycleCount = cyclecount - 1; + frame.frame1 = numFrames - 1; + frame.frame2 = frame.frame1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + return; + } + + frame.frame1 = frameNum % ( numFrames - 1 ); + frame.frame2 = frame.frame1 + 1; + if ( frame.frame2 >= numFrames ) { + frame.frame2 = 0; + } + + frame.backlerp = ( frameTime % 1000 ) * 0.001f; + frame.frontlerp = 1.0f - frame.backlerp; +} + +// RAVEN BEGIN +// jscott: added block +/* +==================== +idMD5Anim::ConvertFrameToTime + +MD5_FRAMERATE is always 24 +==================== +*/ +int idMD5Anim::ConvertFrameToTime( frameBlend_t &frame ) const { + int time; + + // Adjust the time so the lerping doesn't break the reverse calc + time = ( ( frame.frame1 % (numFrames - 1)) * 1000 ) / frameRate; + time += 500 / frameRate; + + return( time ); +} +// RAVEN END + +/* +==================== +idMD5Anim::GetOrigin +==================== +*/ +void idMD5Anim::GetOrigin( idVec3 &offset, int time, int cyclecount ) const { + frameBlend_t frame; + + offset = baseFrame[ 0 ].t; + if ( !( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) ) { + // just use the baseframe + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + + if ( frame.cycleCount ) { + offset += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetOriginRotation +==================== +*/ +void idMD5Anim::GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const { + frameBlend_t frame; + int animBits; + + animBits = jointInfo[ 0 ].animBits; + if ( !( animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) ) { + // just use the baseframe + rotation = baseFrame[ 0 ].q; + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *jointframe1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *jointframe2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( animBits & ANIM_TX ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TY ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TZ ) { + jointframe1++; + jointframe2++; + } + + idQuat q1; + idQuat q2; + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case ANIM_QX: + q1.x = jointframe1[0]; + q2.x = jointframe2[0]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY: + q1.y = jointframe1[0]; + q2.y = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QZ: + q1.z = jointframe1[0]; + q2.z = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + q1.x = jointframe1[0]; + q1.z = jointframe1[1]; + q2.x = jointframe2[0]; + q2.z = jointframe2[1]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + q1.y = jointframe1[0]; + q1.z = jointframe1[1]; + q2.y = jointframe2[0]; + q2.z = jointframe2[1]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q1.z = jointframe1[2]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q2.z = jointframe2[2]; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + } + + rotation.Slerp( q1, q2, frame.backlerp ); +} + +/* +==================== +idMD5Anim::GetBounds +==================== +*/ +void idMD5Anim::GetBounds( idBounds &bnds, int time, int cyclecount ) const { + frameBlend_t frame; + idVec3 offset; + + ConvertTimeToFrame( time, cyclecount, frame ); + + bnds = bounds[ frame.frame1 ]; + bnds.AddBounds( bounds[ frame.frame2 ] ); + + // origin position + offset = baseFrame[ 0 ].t; + if ( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) { + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + } + + bnds[ 0 ] -= offset; + bnds[ 1 ] -= offset; +} + +/* +==================== +idMD5Anim::GetInterpolatedFrame +==================== +*/ +void idMD5Anim::GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const { + int i, numLerpJoints; + const float *frame1; + const float *frame2; + const float *jointframe1; + const float *jointframe2; + const jointAnimInfo_t *infoPtr; + int animBits; + idJointQuat *blendJoints; + idJointQuat *jointPtr; + idJointQuat *blendPtr; + int *lerpIndex; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return; + } +#endif + + if ( !numAnimatedComponents ) { + // just use the base frame + return; + } + + blendJoints = (idJointQuat *)_alloca16( baseFrame.Num() * sizeof( blendPtr[ 0 ] ) ); + lerpIndex = (int *)_alloca16( baseFrame.Num() * sizeof( lerpIndex[ 0 ] ) ); + numLerpJoints = 0; + + frame1 = &componentFrames[ frame.frame1 * numAnimatedComponents ]; + frame2 = &componentFrames[ frame.frame2 * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + blendPtr = &blendJoints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + lerpIndex[numLerpJoints++] = j; + + jointframe1 = frame1 + infoPtr->firstComponent; + jointframe2 = frame2 + infoPtr->firstComponent; + + switch( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + case 0: + blendPtr->t = jointPtr->t; + break; + case ANIM_TX: + jointPtr->t.x = jointframe1[0]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointPtr->t.y; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TY: + jointPtr->t.y = jointframe1[0]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TZ: + jointPtr->t.z = jointframe1[0]; + blendPtr->t.z = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.y = jointPtr->t.y; + jointframe1++; + jointframe2++; + break; + case ANIM_TX|ANIM_TY: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointPtr->t.z; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.y = jointPtr->t.y; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TY|ANIM_TZ: + jointPtr->t.y = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.x = jointPtr->t.x; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TY|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + jointPtr->t.z = jointframe1[2]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointframe2[2]; + jointframe1 += 3; + jointframe2 += 3; + break; + } + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case 0: + blendPtr->q = jointPtr->q; + break; + case ANIM_QX: + jointPtr->q.x = jointframe1[0]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointPtr->q.y; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY: + jointPtr->q.y = jointframe1[0]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QZ: + jointPtr->q.z = jointframe1[0]; + blendPtr->q.z = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + jointPtr->q.y = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.x = jointPtr->q.x; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + jointPtr->q.z = jointframe1[2]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointframe2[2]; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + } + } + } + + SIMDProcessor->BlendJoints( joints, blendJoints, frame.backlerp, lerpIndex, numLerpJoints ); + + if ( frame.cycleCount ) { + joints[ 0 ].t += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetSingleFrame +==================== +*/ +void idMD5Anim::GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const { + int i; + const float *frame; + const float *jointframe; + int animBits; + idJointQuat *jointPtr; + const jointAnimInfo_t *infoPtr; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( ( framenum == 0 ) || !numAnimatedComponents ) { + // just use the base frame + return; + } + + frame = &componentFrames[ framenum * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + jointframe = frame + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe++; + } + + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe++; + } + + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe++; + } + + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe++; + } + + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe; + } + + jointPtr->q.w = jointPtr->q.CalcW(); + } + } + } +} + +/* +==================== +idMD5Anim::CheckModelHierarchy +==================== +*/ +void idMD5Anim::CheckModelHierarchy( const idRenderModel *model ) const { + int i; + int jointNum; + int parent; + + if ( jointInfo.Num() != model->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Error( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), model->NumJoints(), name.c_str(), jointInfo.Num() ); +// scork: if we don't return here, we get dozens of other warnings generated by mismatching models below, one warning is sufficient... + if (common->DoingDeclValidation()) { + return; + } +// RAVEN END + } + + const idMD5Joint *modelJoints = model->GetJoints(); + for( i = 0; i < jointInfo.Num(); i++ ) { + jointNum = jointInfo[ i ].nameIndex; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + if ( modelJoints[ i ].name != animationLib->JointName( jointNum ) ) { +// RAVEN END + gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() ); + } + if ( modelJoints[ i ].parent ) { + parent = modelJoints[ i ].parent - modelJoints; + } else { + parent = -1; + } + if ( parent != jointInfo[ i ].parentNum ) { + gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() ); + } + } +} + +/*********************************************************************** + + idAnimManager + +***********************************************************************/ + +/* +==================== +idAnimManager::idAnimManager +==================== +*/ +idAnimManager::idAnimManager() { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + insideLevelLoad=false; +#endif +// RAVEN END +} + +/* +==================== +idAnimManager::~idAnimManager +==================== +*/ +idAnimManager::~idAnimManager() { + Shutdown(); +} + +/* +==================== +idAnimManager::Shutdown +==================== +*/ +void idAnimManager::Shutdown( void ) { + animations.DeleteContents(); + jointnames.Clear(); + jointnamesHash.Free(); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +==================== +idAnimManager::BeginLevelLoad +==================== +*/ +void idAnimManager::BeginLevelLoad( void ) +{ + insideLevelLoad = true; +} + +/* +==================== +idAnimManager::EndLevelLoad +==================== +*/ +void idAnimManager::EndLevelLoad( void ) +{ + insideLevelLoad = false; +} +#endif +// RAVEN END + +/* +==================== +idAnimManager::GetAnim +==================== +*/ +idMD5Anim *idAnimManager::GetAnim( const char *name ) { + idMD5Anim **animptrptr; + idMD5Anim *anim; + + // see if it has been asked for before + animptrptr = NULL; + if ( animations.Get( name, &animptrptr ) ) { + anim = *animptrptr; + } else { + idStr extension; + idStr filename = name; + + filename.ExtractFileExtension( extension ); + if ( extension != MD5_ANIM_EXT ) { +// RAVEN BEGIN +// nmckenzie: I'm not interested in debugging this blindly again. + gameLocal.Warning( "Animation '%s' doesn't have the correct extension for an animation file.", filename.c_str() ); +// RAVEN END + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(insideLevelLoad?RV_HEAP_ID_LEVEL:RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + anim = new idMD5Anim(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + if ( !anim->LoadAnim( filename ) ) { + gameLocal.Warning( "Couldn't load anim: '%s'", filename.c_str() ); + delete anim; + anim = NULL; + } + animations.Set( filename, anim ); + } + + return anim; +} + +/* +================ +idAnimManager::ReloadAnims +================ +*/ +void idAnimManager::ReloadAnims( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + ( *animptr )->Reload(); + } + } +} + +/* +================ +idAnimManager::JointIndex +================ +*/ +int idAnimManager::JointIndex( const char *name ) { + int i, hash; + + hash = jointnamesHash.GenerateKey( name ); + for ( i = jointnamesHash.First( hash ); i != -1; i = jointnamesHash.Next( i ) ) { + if ( jointnames[i].Cmp( name ) == 0 ) { + return i; + } + } + + i = jointnames.Append( name ); + jointnamesHash.Add( hash, i ); + return i; +} + +/* +================ +idAnimManager::JointName +================ +*/ +const char *idAnimManager::JointName( int index ) const { + return jointnames[ index ]; +} + +/* +================ +idAnimManager::ListAnims +================ +*/ +void idAnimManager::ListAnims( void ) const { + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t s; + size_t namesize; + int num; + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + s = anim->Size(); + gameLocal.Printf( "%8d bytes : %2d refs : %s\n", s, anim->NumRefs(), anim->Name() ); + size += s; + num++; + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + gameLocal.Printf( "\n%d memory used in %d anims\n", size, num ); + gameLocal.Printf( "%d memory used in %d joint names\n", namesize, jointnames.Num() ); +} + +// RAVEN BEGIN +/* +================ +idAnimManager::PrintMemInfo +================ +*/ +void idAnimManager::PrintMemInfo( MemInfo *mi ) { + + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t namesize; + int num; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_anim.txt" ); + if( !f ) { + return; + } + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + size += anim->Size(); + num++; + + f->Printf( "%8d: %s\n", anim->Size(), anim->Name() ); + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + mi->animsAssetsCount = num; + mi->animsAssetsTotal = namesize + size; + + f->Printf( "\nTotal anim bytes allocated: %s (%s items)\n", idStr::FormatNumber( mi->animsAssetsTotal ).c_str(), idStr::FormatNumber( mi->animsAssetsCount ).c_str() ); + fileSystem->CloseFile( f ); +} +// RAVEN END + +/* +================ +idAnimManager::FlushUnusedAnims +================ +*/ +void idAnimManager::FlushUnusedAnims( void ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + idList removeAnims; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + if ( ( *animptr )->NumRefs() <= 0 ) { + removeAnims.Append( *animptr ); + } + } + } + + for( i = 0; i < removeAnims.Num(); i++ ) { + animations.Remove( removeAnims[ i ]->Name() ); + delete removeAnims[ i ]; + } +} diff --git a/source/mpgame/anim/Anim.h b/source/mpgame/anim/Anim.h new file mode 100644 index 0000000..c72b45c --- /dev/null +++ b/source/mpgame/anim/Anim.h @@ -0,0 +1,801 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __ANIM_H__ +#define __ANIM_H__ + +// +// animation channels +// these can be changed by modmakers and licensees to be whatever they need. +const int ANIM_NumAnimChannels = 5; +const int ANIM_MaxAnimsPerChannel = 3; +const int ANIM_MaxSyncedAnims = 3; + +// +// animation channels. make sure to change script/doom_defs.script if you add any channels, or change their order +// +const int ANIMCHANNEL_ALL = 0; +const int ANIMCHANNEL_TORSO = 1; +const int ANIMCHANNEL_LEGS = 2; +const int ANIMCHANNEL_HEAD = 3; +const int ANIMCHANNEL_EYELIDS = 4; + +// for converting from 24 frames per second to milliseconds +ID_INLINE int FRAME2MS( int framenum ) { + return ( framenum * 1000 ) / 24; +} + +class idRenderModel; +class idAnimator; +class idAnimBlend; +class function_t; +class idEntity; +class idSaveGame; +class idRestoreGame; + +typedef struct { + int cycleCount; // how many times the anim has wrapped to the begining (0 for clamped anims) + int frame1; + int frame2; + float frontlerp; + float backlerp; +} frameBlend_t; + +typedef struct { + int nameIndex; + int parentNum; + int animBits; + int firstComponent; +} jointAnimInfo_t; + +typedef struct { + jointHandle_t num; + jointHandle_t parentNum; + int channel; +} jointInfo_t; + +// +// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order. +// +typedef enum { + JOINTMOD_NONE, // no modification + JOINTMOD_LOCAL, // modifies the joint's position or orientation in joint local space + JOINTMOD_LOCAL_OVERRIDE, // sets the joint's position or orientation in joint local space + JOINTMOD_WORLD, // modifies joint's position or orientation in model space + JOINTMOD_WORLD_OVERRIDE, // sets the joint's position or orientation in model space + JOINTMOD_COLLAPSE +} jointModTransform_t; + +typedef struct { + jointHandle_t jointnum; + idMat3 mat; + idVec3 pos; + jointModTransform_t transform_pos; + jointModTransform_t transform_axis; + +// RAVEN BEGIN +// bdube: added more features to programmer controlled joints + idInterpolateAccelDecelLinear angularVelocity; + int lastTime; + + jointHandle_t collapseJoint; +// RAVEN END + +} jointMod_t; + +#define ANIM_TX BIT( 0 ) +#define ANIM_TY BIT( 1 ) +#define ANIM_TZ BIT( 2 ) +#define ANIM_QX BIT( 3 ) +#define ANIM_QY BIT( 4 ) +#define ANIM_QZ BIT( 5 ) + +typedef enum { + FC_SCRIPTFUNCTION, + FC_SCRIPTFUNCTIONOBJECT, + FC_EVENTFUNCTION, +// RAVEN BEGIN +// abahr: event call with parms + FC_EVENTFUNCTION_ARGS, +// RAVEN END + FC_SOUND, + FC_SOUND_VOICE, + FC_SOUND_VOICE2, + FC_SOUND_BODY, + FC_SOUND_BODY2, + FC_SOUND_BODY3, + FC_SOUND_WEAPON, + FC_SOUND_ITEM, + FC_SOUND_GLOBAL, + FC_SOUND_CHATTER, + FC_SKIN, + FC_TRIGGER, + FC_DIRECTDAMAGE, + FC_MUZZLEFLASH, + FC_FOOTSTEP, + FC_LEFTFOOT, + FC_RIGHTFOOT, + FC_ENABLE_EYE_FOCUS, + FC_DISABLE_EYE_FOCUS, + FC_FX, + FC_DISABLE_GRAVITY, + FC_ENABLE_GRAVITY, + FC_JUMP, + FC_ENABLE_CLIP, + FC_DISABLE_CLIP, + FC_ENABLE_WALK_IK, + FC_DISABLE_WALK_IK, + FC_ENABLE_LEG_IK, + FC_DISABLE_LEG_IK, + FC_RECORDDEMO, + FC_AVIGAME, + FC_GUIEVENT, + + FC_AI_ENABLE_PAIN, + FC_AI_DISABLE_PAIN, + FC_AI_ENABLE_DAMAGE, + FC_AI_DISABLE_DAMAGE, + FC_AI_LOCKENEMYORIGIN, + FC_AI_ATTACK, + FC_AI_ATTACK_MELEE, + + FC_AI_SPEAK, + FC_AI_SPEAK_RANDOM, +// MCG: for attachment managing + FC_ACT_ATTACH_HIDE, + FC_ACT_ATTACH_SHOW, + + FC_ENABLE_BLINKING, + FC_DISABLE_BLINKING, + FC_ENABLE_AUTOBLINK, + FC_DISABLE_AUTOBLINK, + + FC_COUNT + +} frameCommandType_t; + +// RAVEN BEGIN +// rjohnson: new camera frame commands +extern struct frameCommandInfo_t +{ + const char* name; + bool modview; + +} frameCommandInfo[FC_COUNT]; +// RAVEN END + +typedef struct { + int num; + int firstCommand; +} frameLookup_t; + +typedef struct { + frameCommandType_t type; + + idStr* string; + +// RAVEN BEGIN +// bdube: added joint + idStr* joint; + idStr* joint2; +// abahr: + idList* parmList; +// RAVEN END + + union { + const idSoundShader *soundShader; + const function_t *function; + const idDeclSkin *skin; + int index; +// RAVEN BEGIN +// bdube: effects + const idDecl *effect; + idStr* projectile; + idStr* melee; +// abahr: + const class idEventDef* event; +// RAVEN END + }; +} frameCommand_t; + +typedef struct { + bool prevent_idle_override : 1; + bool random_cycle_start : 1; + bool ai_no_turn : 1; + bool ai_no_look : 1; + bool ai_look_head_only : 1; + bool anim_turn : 1; + bool sync_cycle : 1; +} animFlags_t; + + +/* +============================================================================================== + + idModelExport + +============================================================================================== +*/ + +class idModelExport { +private: + void Reset( void ); + bool ParseOptions( idLexer &lex ); + int ParseExportSection( idParser &parser ); + + static bool CheckMayaInstall( void ); + static void LoadMayaDll( void ); + + bool ConvertMayaToMD5( void ); + static bool initialized; + +public: + idStr commandLine; + idStr src; + idStr dest; + bool force; + + idModelExport(); + + static void Shutdown( void ); + + int ExportDefFile( const char *filename ); + bool ExportModel( const char *model ); + bool ExportAnim( const char *anim ); + int ExportModels( const char *pathname, const char *extension ); +}; + +/* +============================================================================================== + + idMD5Anim + +============================================================================================== +*/ + +class idMD5Anim { +private: + int numFrames; + int frameRate; + int animLength; + int numJoints; + int numAnimatedComponents; + idList bounds; + idList jointInfo; + idList baseFrame; + idList componentFrames; + idStr name; + idVec3 totaldelta; + mutable int ref_count; + +public: + idMD5Anim(); + ~idMD5Anim(); + + void Free( void ); + bool Reload( void ); + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); }; + bool LoadAnim( const char *filename ); + + void IncreaseRefs( void ) const; + void DecreaseRefs( void ) const; + int NumRefs( void ) const; + + void CheckModelHierarchy( const idRenderModel *model ) const; + void GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const; + void GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumJoints( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + const char *Name( void ) const; + + void ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const; +// RAVEN BEGIN +// jscott: for modview + int ConvertFrameToTime( frameBlend_t &frame ) const; +// RAVEN END + + void GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const; + void GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const; + void GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const; +}; + +/* +============================================================================================== + + idAnim + +============================================================================================== +*/ + +class idAnim { +private: + const class idDeclModelDef *modelDef; + const idMD5Anim *anims[ ANIM_MaxSyncedAnims ]; + int numAnims; + idStr name; + idStr realname; + idList frameLookup; + idList frameCommands; + animFlags_t flags; + +// RAVEN BEGIN +// bdube: added anim speed + float rate; +// RAVEN END + +public: + idAnim(); + idAnim( const idDeclModelDef *modelDef, const idAnim *anim ); + ~idAnim(); + + void SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ); + const char *Name( void ) const; + const char *FullName( void ) const; + const idMD5Anim *MD5Anim( int num ) const; + const idDeclModelDef *ModelDef( void ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumAnims( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + bool GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const; + bool GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const; + bool GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const; +// RAVEN BEGIN +// bdube: frame command function that takes a list of frames + const char *AddFrameCommand( const class idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ); +// RAVEN END + void CallFrameCommands( idEntity *ent, int from, int to ) const; + bool HasFrameCommands( void ) const; + + // returns first frame (zero based) that command occurs. returns -1 if not found. + int FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const; + void SetAnimFlags( const animFlags_t &animflags ); + const animFlags_t &GetAnimFlags( void ) const; + +// RAVEN BEGIN +// bdube: added + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; + float GetPlaybackRate ( void ) const; + void SetPlaybackRate ( float rate ); +// jsinger: to support binary serialization/deserialization of idAnims +#ifdef RV_BINARYDECLS + idAnim( idDeclModelDef const *def, SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; +#endif +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added configurable playback rate +ID_INLINE float idAnim::GetPlaybackRate ( void ) const { + return rate; +} + +ID_INLINE void idAnim::SetPlaybackRate ( float _rate ) { + rate = _rate; +} + +// RAVEN END + +/* +============================================================================================== + + idDeclModelDef + +============================================================================================== +*/ + +// RAVEN BEGIN +// jsinger; allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclModelDef : public idDecl, public Serializable<'DMD '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idDeclModelDef( SerialInputStream &stream ); +private: + int mNumChannels; +#else +class idDeclModelDef : public idDecl { +#endif +// RAVEN END +public: + idDeclModelDef(); + ~idDeclModelDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + const idDeclSkin * GetDefaultSkin( void ) const; + const idJointQuat * GetDefaultPose( void ) const; + void SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const; + idRenderModel * ModelHandle( void ) const; + void GetJointList( const char *jointnames, idList &jointList ) const; + const jointInfo_t * FindJoint( const char *name ) const; + + int NumAnims( void ) const; + const idAnim * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + const idDeclSkin * GetSkin( void ) const; + const char * GetModelName( void ) const; + const idList & Joints( void ) const; + const int * JointParents( void ) const; + int NumJoints( void ) const; + const jointInfo_t * GetJoint( int jointHandle ) const; + const char * GetJointName( int jointHandle ) const; + int NumJointsOnChannel( int channel ) const; + const int * GetChannelJoints( int channel ) const; + + const idVec3 & GetVisualOffset( void ) const; + +private: + void CopyDecl( const idDeclModelDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idVec3 offset; + idList joints; + idList jointParents; + idList channelJoints[ ANIM_NumAnimChannels ]; + idRenderModel * modelHandle; + idList anims; + const idDeclSkin * skin; +}; + +ID_INLINE const idAnim *idDeclModelDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idAnimBlend + +============================================================================================== +*/ + +class idAnimBlend { +private: + const class idDeclModelDef *modelDef; + int starttime; + int endtime; + int timeOffset; + float rate; + + int blendStartTime; + int blendDuration; + float blendStartValue; + float blendEndValue; + + float animWeights[ ANIM_MaxSyncedAnims ]; + short cycle; + short animNum; + bool allowMove; + bool allowFrameCommands; + bool useFrameBlend; + + frameBlend_t frameBlend; + + friend class idAnimator; + + void Reset( const idDeclModelDef *_modelDef ); + void CallFrameCommands( idEntity *ent, int fromtime, int totime ) const; +// RAVEN BEGIN +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( const idDeclModelDef *modelDef, int animnum, const frameBlend_t & frameBlend ); +// jshepard: added rate parameter so we can speed up/slow down animations. + void CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); + void PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); +// RAVEN END + bool BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const; + void BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const; + void BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const; + void BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const; + bool AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const; + +public: + idAnimBlend(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ); + const char *AnimName( void ) const; + const char *AnimFullName( void ) const; + float GetWeight( int currenttime ) const; + float GetFinalWeight( void ) const; + void SetWeight( float newweight, int currenttime, int blendtime ); + int NumSyncedAnims( void ) const; + bool SetSyncedAnimWeight( int num, float weight ); + void Clear( int currentTime, int clearTime ); + bool IsDone( int currentTime ) const; + bool FrameHasChanged( int currentTime ) const; + int GetCycleCount( void ) const; + void SetCycleCount( int count ); + void SetPlaybackRate( int currentTime, float newRate ); + float GetPlaybackRate( void ) const; + void SetStartTime( int startTime ); + int GetStartTime( void ) const; + int GetEndTime( void ) const; + int GetFrameNumber( int currenttime ) const; + int AnimTime( int currenttime ) const; + int NumFrames( void ) const; + int Length( void ) const; + int PlayLength( void ) const; + void AllowMovement( bool allow ); + void AllowFrameCommands( bool allow ); + const idAnim *Anim( void ) const; + int AnimNum( void ) const; +}; + +ID_INLINE const idAnim *idAnimBlend::Anim( void ) const { + if ( !modelDef ) { + return NULL; + } + return modelDef->GetAnim( animNum ); +} + +/* +============================================================================================== + + idAFPoseJointMod + +============================================================================================== +*/ + +typedef enum { + AF_JOINTMOD_AXIS, + AF_JOINTMOD_ORIGIN, + AF_JOINTMOD_BOTH +} AFJointModType_t; + +class idAFPoseJointMod { +public: + idAFPoseJointMod( void ); + + AFJointModType_t mod; + idMat3 axis; + idVec3 origin; +}; + +ID_INLINE idAFPoseJointMod::idAFPoseJointMod( void ) { + mod = AF_JOINTMOD_AXIS; + axis.Identity(); + origin.Zero(); +} + +/* +============================================================================================== + + idAnimator + +============================================================================================== +*/ + +class idAnimator{ + public: + idAnimator(); + ~idAnimator(); + + size_t Allocated( void ) const; + size_t Size( void ) const; + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetEntity( idEntity *ent ); + idEntity *GetEntity( void ) const ; + void RemoveOriginOffset( bool remove ); + bool RemoveOrigin( void ) const; + + void GetJointList( const char *jointnames, idList &jointList ) const; + + int NumAnims( void ) const; + const idAnim *GetAnim( int index ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + + void ServiceAnims( int fromtime, int totime ); + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation + bool IsAnimating ( int currentTime, bool IgnoreAF = false ) const; + bool IsBlending ( int channelNum, int currentTime ) const; +// RAVEN END + + void GetJoints( int *numJoints, idJointMat **jointsPtr ); + int NumJoints( void ) const; + jointHandle_t GetFirstChild( jointHandle_t jointnum ) const; + jointHandle_t GetFirstChild( const char *name ) const; + + idRenderModel *SetModel( const char *modelname ); + idRenderModel *ModelHandle( void ) const; + const idDeclModelDef *ModelDef( void ) const; + + void ForceUpdate( void ); + void ClearForceUpdate( void ); + bool CreateFrame( int animtime, bool force ); + bool FrameHasChanged( int animtime ) const; + void GetDelta( int fromtime, int totime, idVec3 &delta ) const; + bool GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const; + void GetOrigin( int currentTime, idVec3 &pos ) const; + bool GetBounds( int currentTime, idBounds &bounds ); + + idAnimBlend *CurrentAnim( int channelNum ); + void Clear( int channelNum, int currentTime, int cleartime ); + +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( int channelNum, int animnum, const frameBlend_t & frameBlend ); + void CycleAnim( int channelNum, int animnum, int currenttime, int blendtime ); + void PlayAnim( int channelNum, int animnum, int currenttime, int blendTime ); + + // copies the current anim from fromChannelNum to channelNum. + // the copied anim will have frame commands disabled to avoid executing them twice. + void SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime ); + + void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void GetJointAxis( jointHandle_t jointnum, idMat3 &mat ); + void CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ); + void CollapseJoints ( const char* jointnames, jointHandle_t collapseJoint ); + void ClearJoint( jointHandle_t jointnum ); + void ClearAllJoints( void ); + +// RAVEN BEGIN +// bdube: more joint control functions + void AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int blendtime ); + void SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ); + idAngles GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ); + void ClearJointAngularVelocity ( jointHandle_t jointnum ); + +// jshepard: rate of playback change + void SetPlaybackRate(float multiplier); +// abahr: + void SetPlaybackRate( const char* animName, float rate ); + void SetPlaybackRate( int animHandle, float rate ); +// RAVEN END + + void InitAFPose( void ); + void SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ); + void FinishAFPose( int animnum, const idBounds &bounds, const int time ); + void SetAFPoseBlendWeight( float blendWeight ); + bool BlendAFPose( idJointQuat *blendFrame ) const; + void ClearAFPose( void ); + + void ClearAllAnims( int currentTime, int cleartime ); + + jointHandle_t GetJointHandle( const char *name ) const; + const char * GetJointName( jointHandle_t handle ) const; + int GetChannelForJoint( jointHandle_t joint ) const; + bool GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis ); + bool GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + + const animFlags_t GetAnimFlags( int animnum ) const; + int NumFrames( int animnum ) const; + int NumSyncedAnims( int animnum ) const; + const char *AnimName( int animnum ) const; + const char *AnimFullName( int animnum ) const; +// RAVEN BEGIN +// rjohnson: more output for animators + const char *AnimMD5Name( int animnum, int index ) const; +// RAVEN END + int AnimLength( int animnum ) const; + const idVec3 &TotalMovementDelta( int animnum ) const; + +// RAVEN BEGIN +// nrausch: get the nearest joint to a segment - ignores joints behind the origin +// you can pass it a null jointList in order to test against all joints ( use NumJoints() for the count ) + jointHandle_t GetNearestJoint( const idVec3 &start, const idVec3 &end, int time, jointHandle_t *jointList, int cnt ); +//MCG + jointMod_t * FindExistingJointMod( jointHandle_t jointnum, int *index ); +// RAVEN END + +private: +// RAVEN BEGIN +// bdube: added methods + jointMod_t * FindJointMod ( jointHandle_t jointnum ); +// RAVEN END + + void FreeData( void ); + void PushAnims( int channel, int currentTime, int blendTime ); + +private: + const idDeclModelDef * modelDef; + idEntity * entity; + + idAnimBlend channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ]; + idList jointMods; + int numJoints; + idJointMat * joints; + + mutable int lastTransformTime; // mutable because the value is updated in CreateFrame + mutable bool stoppedAnimatingUpdate; + bool removeOriginOffset; + bool forceUpdate; + + idBounds frameBounds; + + float AFPoseBlendWeight; + idList AFPoseJoints; + idList AFPoseJointMods; + idJointQuat * AFPoseJointFrame; + int AFPoseJointFrameSize; + idBounds AFPoseBounds; + int AFPoseTime; + +// RAVEN BEGIN +// jshepard: multiplier for the animation rate for all anims under this animator + float rateMultiplier; +// RAVEN END +}; + +ID_INLINE void idAnimator::SetPlaybackRate ( float _rate ) { + rateMultiplier = _rate; +} + +/* +============================================================================================== + + idAnimManager + +============================================================================================== +*/ + +class idAnimManager { +public: + idAnimManager(); + ~idAnimManager(); + + static bool forceExport; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void BeginLevelLoad( void ); + void EndLevelLoad( void ); +#endif +// RAVEN END + void Shutdown( void ); + idMD5Anim * GetAnim( const char *name ); + void ReloadAnims( void ); + void ListAnims( void ) const; + void PrintMemInfo( MemInfo *mi ); + int JointIndex( const char *name ); + const char * JointName( int index ) const; + + void ClearAnimsInUse( void ); + void FlushUnusedAnims( void ); + +private: + idHashTable animations; + idStrList jointnames; + idHashIndex jointnamesHash; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool insideLevelLoad; +#endif +// RAVEN END +}; + +#endif /* !__ANIM_H__ */ + +// RAVEN END diff --git a/source/mpgame/anim/Anim_Blend.cpp b/source/mpgame/anim/Anim_Blend.cpp new file mode 100644 index 0000000..4e5b612 --- /dev/null +++ b/source/mpgame/anim/Anim_Blend.cpp @@ -0,0 +1,6015 @@ +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../mpgame/Projectile.h" +#include "../ai/AI.h" + +static const char *channelNames[ ANIM_NumAnimChannels ] = { + "all", "torso", "legs", "head", "eyelids" +}; + +/*********************************************************************** + + idAnim + +***********************************************************************/ + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim() { + modelDef = NULL; +// RAVEN BEGIN +// bdube: added speed + rate = 1.0f; +// RAVEN END + numAnims = 0; + memset( anims, 0, sizeof( anims ) ); + memset( &flags, 0, sizeof( flags ) ); +} + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim( const idDeclModelDef *modelDef, const idAnim *anim ) { + int i; + + this->modelDef = modelDef; + numAnims = anim->numAnims; + name = anim->name; + realname = anim->realname; + flags = anim->flags; +// RAVEN BEGIN +// bdube: copy speed info + rate = anim->rate; +// RAVEN END + + memset( anims, 0, sizeof( anims ) ); + for( i = 0; i < numAnims; i++ ) { + anims[ i ] = anim->anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + frameLookup.SetNum( anim->frameLookup.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( frameLookup.Ptr(), anim->frameLookup.Ptr(), frameLookup.MemoryUsed() ); +// RAVEN END + + frameCommands.SetNum( anim->frameCommands.Num() ); + for( i = 0; i < frameCommands.Num(); i++ ) { + frameCommands[ i ] = anim->frameCommands[ i ]; + if ( anim->frameCommands[ i ].string ) { + frameCommands[ i ].string = new idStr( *anim->frameCommands[ i ].string ); + } +// RAVEN BEGIN +// bdube: copy joint information + if ( anim->frameCommands[ i ].joint ) { + frameCommands[ i ].joint = new idStr ( *anim->frameCommands[i].joint ); + } + if ( anim->frameCommands[ i ].joint2 ) { + frameCommands[ i ].joint2 = new idStr ( *anim->frameCommands[i].joint2 ); + } +// abahr: + if ( anim->frameCommands[ i ].parmList ) { + frameCommands[ i ].parmList = new idList( *anim->frameCommands[i].parmList ); + } +// RAVEN END + } +} + +/* +===================== +idAnim::~idAnim +===================== +*/ +idAnim::~idAnim() { + int i; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + } + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joint support + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } +} + +/* +===================== +idAnim::SetAnim +===================== +*/ +void idAnim::SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ) { + int i; + + this->modelDef = modelDef; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + anims[ i ] = NULL; + } + + assert( ( num > 0 ) && ( num <= ANIM_MaxSyncedAnims ) ); + numAnims = num; + realname = sourcename; + name = animname; + + for( i = 0; i < num; i++ ) { + anims[ i ] = md5anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + memset( &flags, 0, sizeof( flags ) ); + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joints as their own string + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } + + frameLookup.Clear(); + frameCommands.Clear(); +} + +/* +===================== +idAnim::Name +===================== +*/ +const char *idAnim::Name( void ) const { + return name; +} + +/* +===================== +idAnim::FullName +===================== +*/ +const char *idAnim::FullName( void ) const { + return realname; +} + +/* +===================== +idAnim::MD5Anim + +index 0 will never be NULL. Any anim >= NumAnims will return NULL. +===================== +*/ +const idMD5Anim *idAnim::MD5Anim( int num ) const { + if ( anims == NULL || anims[0] == NULL ) { + return NULL; + } + return anims[ num ]; +} + +/* +===================== +idAnim::ModelDef +===================== +*/ +const idDeclModelDef *idAnim::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnim::Length +===================== +*/ +int idAnim::Length( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->Length(); +} + +/* +===================== +idAnim::NumFrames +===================== +*/ +int idAnim::NumFrames( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->NumFrames(); +} + +/* +===================== +idAnim::NumAnims +===================== +*/ +int idAnim::NumAnims( void ) const { + return numAnims; +} + +/* +===================== +idAnim::TotalMovementDelta +===================== +*/ +const idVec3 &idAnim::TotalMovementDelta( void ) const { + if ( !anims[ 0 ] ) { + return vec3_zero; + } + + return anims[ 0 ]->TotalMovementDelta(); +} + +/* +===================== +idAnim::GetOrigin +===================== +*/ +bool idAnim::GetOrigin( idVec3 &offset, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + offset.Zero(); + return false; + } + + anims[ animNum ]->GetOrigin( offset, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetOriginRotation +===================== +*/ +bool idAnim::GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + return false; + } + + anims[ animNum ]->GetOriginRotation( rotation, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetBounds +===================== +*/ +ID_INLINE bool idAnim::GetBounds( idBounds &bounds, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + return false; + } + + anims[ animNum ]->GetBounds( bounds, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *idAnim::AddFrameCommand( const idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0] ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "enableBlinking" ) { + fc.type = FC_ENABLE_BLINKING; + } else if ( token == "disableBlinking" ) { + fc.type = FC_DISABLE_BLINKING; + } else if ( token == "enableAutoBlink" ) { + fc.type = FC_ENABLE_AUTOBLINK; + } else if ( token == "disableAutoBlink" ) { + fc.type = FC_DISABLE_AUTOBLINK; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "speak_random" ) { + fc.type = FC_AI_SPEAK_RANDOM; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +//MCG - added attachment frame commands + } else if ( token == "attachment_hide" ) { + fc.type = FC_ACT_ATTACH_HIDE; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "attachment_show" ) { + fc.type = FC_ACT_ATTACH_SHOW; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( anims[ 0 ]->NumFrames() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > anims[ 0 ]->NumFrames() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, anims[ 0 ]->Name(), anims[ 0 ]->NumFrames() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +// RAVEN BEGIN +// bdube: added for debugging +struct frameCommandInfo_t frameCommandInfo[FC_COUNT] = { + { "call", false }, + { "object_call", false }, + { "event", false }, + { "eventArgs", false }, + + { "sound", true }, + { "sound_voice", true }, + { "sound_voice2", true }, + { "sound_body", true }, + { "sound_body2", true }, + { "sound_body3", true }, + { "sound_weapon", true }, + { "sound_item", true }, + { "sound_global", true }, + { "sound_chatter", true }, + + { "skin", true }, + { "trigger", false }, + { "direct_damage", false }, + { "muzzle_flash", false }, + { "footstep", false }, + { "leftfoot", false }, + { "rightfoot", false }, + { "enableEyeFocus", false }, + { "disableEyeFocus", false }, + { "effect", true }, + { "disable_gravity", false }, + { "enable_gravity", false }, + { "jump", false }, + { "enableClip", false }, + { "disableClip", false }, + { "enableWalkIK", false }, + { "disableWalkIK", false }, + { "enableLegIK", false }, + { "disableLegID", false }, + { "recordDemo", false }, + { "aviGame", false }, + { "guievent", false }, + + { "ai_enablePain", false }, + { "ai_disablePain", false }, + { "ai_enableDamage", false }, + { "ai_disableDamage", false }, + { "ai_lockEnemyOrigin", false }, + { "ai_attack", false }, + { "ai_attack_melee", false }, + { "speak", true }, +}; +// RAVEN END + +// RAVEN BEGIN +// bdube: added frame command methods +/* +===================== +idAnim::CallFrameCommandSound +===================== +*/ +void idAnim::CallFrameCommandSound( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if ( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + + // ignore frame command jump sounds + // we trigger jump sounds from the player physics and explicitely send them over the network + if ( command.string->Icmp( "snd_jump" ) == 0 ) { + return; + } + + ent->StartSound( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} +// RAVEN END + +/* +===================== +idAnim::CallFrameCommands +===================== +*/ +void idAnim::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + numframes = anims[ 0 ]->NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "framecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } + + if ( ( gameLocal.editors & EDITOR_MODVIEW ) && !frameCommandInfo[command.type].modview ) { + continue; + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { + + if ( gameLocal.localClientNum == -1 ) { + // early ret on dedicated server + break; + } +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_zero, mat3_identity ); + } else { + cent = gameLocal.PlayEffect( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? + if ( cent && command.joint2 && ent->IsType( idAnimatedEntity::GetClassType() ) ) { + cent->SetEndOrigin( ent->GetAnimator()->GetJointHandle( *command.joint2 ) ); + } + + // Error print should the effect fail to play + if ( !cent ) { + idStr error = "Failed to play effect"; + + if( command.string ) { + error += va( " \'%s\'", command.string->c_str() ); + } + if ( command.effect ) { + error += va( " \'%s\'", command.effect->GetName() ); + } + if( command.joint ) { + error += va( " on bone \'%s\'", command.joint->c_str() ); + } + common->Warning( error.c_str() ); + } +// RAVEN END + break; + } + case FC_SKIN: + ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { +// RAVEN BEGIN +// nmckenzie: We're not using this. +// ent->ProcessEvent( &AI_MuzzleFlash, command.string->c_str() ); +// RAVEN END + break; + } + case FC_FOOTSTEP : { + ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { + ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { + ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_ENABLE_BLINKING: { + ent->ProcessEvent( &AI_EnableBlink ); + break; + } + case FC_DISABLE_BLINKING: { + ent->ProcessEvent( &AI_DisableBlink ); + break; + } + case FC_ENABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_EnableAutoBlink ); + break; + } + case FC_DISABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_DisableAutoBlink ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { + ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { + ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { + ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { + ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: + ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: + ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: + ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: + ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: + ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: + ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: + ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_AI_SPEAK_RANDOM: + ent->ProcessEvent( &AI_SpeakRandom, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } + break; + + case FC_ACT_ATTACH_SHOW: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } + break; + + case FC_AI_ATTACK_MELEE: + ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + +/* +===================== +idAnim::FindFrameForFrameCommand +===================== +*/ +int idAnim::FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const { + int frame; + int index; + int numframes; + int end; + + if ( !frameCommands.Num() ) { + return -1; + } + + numframes = anims[ 0 ]->NumFrames(); + for( frame = 0; frame < numframes; frame++ ) { + end = frameLookup[ frame ].firstCommand + frameLookup[ frame ].num; + for( index = frameLookup[ frame ].firstCommand; index < end; index++ ) { + if ( frameCommands[ index ].type == framecommand ) { + if ( command ) { + *command = &frameCommands[ index ]; + } + return frame; + } + } + } + + if ( command ) { + *command = NULL; + } + + return -1; +} + +/* +===================== +idAnim::HasFrameCommands +===================== +*/ +bool idAnim::HasFrameCommands( void ) const { + if ( !frameCommands.Num() ) { + return false; + } + return true; +} + +/* +===================== +idAnim::SetAnimFlags +===================== +*/ +void idAnim::SetAnimFlags( const animFlags_t &animflags ) { + flags = animflags; +} + +/* +===================== +idAnim::GetAnimFlags +===================== +*/ +const animFlags_t &idAnim::GetAnimFlags( void ) const { + return flags; +} + +/*********************************************************************** + + idAnimBlend + +***********************************************************************/ + +/* +===================== +idAnimBlend::idAnimBlend +===================== +*/ +idAnimBlend::idAnimBlend( void ) { + Reset( NULL ); +} + +/* +===================== +idAnimBlend::Save + +archives object for save game file +===================== +*/ +void idAnimBlend::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( starttime ); + savefile->WriteInt( endtime ); + savefile->WriteInt( timeOffset ); + savefile->WriteFloat( rate ); + + savefile->WriteInt( blendStartTime ); + savefile->WriteInt( blendDuration ); + savefile->WriteFloat( blendStartValue ); + savefile->WriteFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->WriteFloat( animWeights[ i ] ); + } + savefile->WriteShort( cycle ); + savefile->WriteShort( animNum ); + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowFrameCommands ); + savefile->WriteBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Restore + +unarchives object from save game file +===================== +*/ +void idAnimBlend::Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ) { + int i; + + this->modelDef = modelDef; + + savefile->ReadInt( starttime ); + savefile->ReadInt( endtime ); + savefile->ReadInt( timeOffset ); + savefile->ReadFloat( rate ); + + savefile->ReadInt( blendStartTime ); + savefile->ReadInt( blendDuration ); + savefile->ReadFloat( blendStartValue ); + savefile->ReadFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->ReadFloat( animWeights[ i ] ); + } + savefile->ReadShort( cycle ); + savefile->ReadShort( animNum ); + if ( !modelDef ) { + animNum = 0; + } else if ( ( animNum < 0 ) || ( animNum > modelDef->NumAnims() ) ) { + gameLocal.Warning( "Anim number %d out of range for model '%s' during save game", animNum, modelDef->GetModelName() ); + animNum = 0; + } + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowFrameCommands ); + savefile->ReadBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Reset +===================== +*/ +void idAnimBlend::Reset( const idDeclModelDef *_modelDef ) { + modelDef = _modelDef; + cycle = 1; + starttime = 0; + endtime = 0; + timeOffset = 0; + rate = 1.0f; + allowMove = true; + allowFrameCommands = true; + animNum = 0; + + memset( animWeights, 0, sizeof( animWeights ) ); + + blendStartValue = 0.0f; + blendEndValue = 0.0f; + blendStartTime = 0; + blendDuration = 0; + useFrameBlend = false; + + memset( &frameBlend, 0, sizeof( frameBlend ) ); +} + +/* +===================== +idAnimBlend::FullName +===================== +*/ +const char *idAnimBlend::AnimFullName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} + +/* +===================== +idAnimBlend::AnimName +===================== +*/ +const char *idAnimBlend::AnimName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->Name(); +} + +/* +===================== +idAnimBlend::NumFrames +===================== +*/ +int idAnimBlend::NumFrames( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumFrames(); +} + +/* +===================== +idAnimBlend::Length +===================== +*/ +int idAnimBlend::Length( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->Length(); +} + +/* +===================== +idAnimBlend::GetWeight +===================== +*/ +float idAnimBlend::GetWeight( int currentTime ) const { + int timeDelta; + float frac; + float w; + + timeDelta = currentTime - blendStartTime; + if ( timeDelta <= 0 ) { + w = blendStartValue; + } else if ( timeDelta >= blendDuration ) { + w = blendEndValue; + } else { + frac = ( float )timeDelta / ( float )blendDuration; + w = blendStartValue + ( blendEndValue - blendStartValue ) * frac; + } + + return w; +} + +/* +===================== +idAnimBlend::GetFinalWeight +===================== +*/ +float idAnimBlend::GetFinalWeight( void ) const { + return blendEndValue; +} + +/* +===================== +idAnimBlend::SetWeight +===================== +*/ +void idAnimBlend::SetWeight( float newweight, int currentTime, int blendTime ) { + blendStartValue = GetWeight( currentTime ); + blendEndValue = newweight; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + + if ( !newweight ) { + endtime = currentTime + blendTime; + } +} + +/* +===================== +idAnimBlend::NumSyncedAnims +===================== +*/ +int idAnimBlend::NumSyncedAnims( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumAnims(); +} + +/* +===================== +idAnimBlend::SetSyncedAnimWeight +===================== +*/ +bool idAnimBlend::SetSyncedAnimWeight( int num, float weight ) { + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + if ( ( num < 0 ) || ( num > anim->NumAnims() ) ) { + return false; + } + + animWeights[ num ] = weight; + return true; +} + +/* +===================== +idAnimBlend::SetFrame +===================== +*/ +void idAnimBlend::SetFrame( const idDeclModelDef *modelDef, int _animNum, const frameBlend_t & _frameBlend ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + frameBlend = _frameBlend; + animNum = _animNum; + useFrameBlend = true; + + // a frame of 0 means it's not a single frame blend, so we set it to frame + 1 + if ( frameBlend.frame1 < 0 ) { + frameBlend.frame1 = 0; + } else if ( frameBlend.frame1 >= _anim->NumFrames() ) { + frameBlend.frame1 = _anim->NumFrames() - 1; + } + + if ( frameBlend.frame2 < 0 ) { + frameBlend.frame2 = 0; + } else if ( frameBlend.frame2 >= _anim->NumFrames() ) { + frameBlend.frame2 = _anim->NumFrames() - 1; + } +} + +/* +===================== +idAnimBlend::CycleAnim +===================== +*/ +void idAnimBlend::CycleAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + animWeights[ 0 ] = 1.0f; + endtime = -1; + cycle = -1; + if ( _anim->GetAnimFlags().random_cycle_start ) { + // start the animation at a random time so that characters don't walk in sync + starttime = currentTime - gameLocal.random.RandomFloat() * _anim->Length(); + } else { + starttime = currentTime; + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } +// RAVEN END +} + +/* +===================== +idAnimBlend::PlayAnim +===================== +*/ +void idAnimBlend::PlayAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = starttime + _anim->Length(); + cycle = 1; + animWeights[ 0 ] = 1.0f; + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } + +// RAVEN END +} + +/* +===================== +idAnimBlend::Clear +===================== +*/ +void idAnimBlend::Clear( int currentTime, int clearTime ) { + if ( !clearTime ) { + Reset( modelDef ); + } else { + SetWeight( 0.0f, currentTime, clearTime ); + } +} + +/* +===================== +idAnimBlend::IsDone +===================== +*/ +bool idAnimBlend::IsDone( int currentTime ) const { + if ( !useFrameBlend && ( endtime > 0 ) && ( currentTime >= endtime ) ) { + return true; + } + + if ( ( blendEndValue <= 0.0f ) && ( currentTime >= ( blendStartTime + blendDuration ) ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimBlend::FrameHasChanged +===================== +*/ +bool idAnimBlend::FrameHasChanged( int currentTime ) const { + // if we don't have an anim, no change + if ( !animNum ) { + return false; + } + + // if anim is done playing, no change + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + // if our blend weight changes, we need to update + if ( ( currentTime < ( blendStartTime + blendDuration ) && ( blendStartValue != blendEndValue ) ) ) { + return true; + } + + // if we're a single frame anim and this isn't the frame we started on, we don't need to update + if ( ( useFrameBlend || ( NumFrames() == 1 ) ) && ( currentTime != starttime ) ) { + return false; + } + + return true; +} + +/* +===================== +idAnimBlend::GetCycleCount +===================== +*/ +int idAnimBlend::GetCycleCount( void ) const { + return cycle; +} + +/* +===================== +idAnimBlend::SetCycleCount +===================== +*/ +void idAnimBlend::SetCycleCount( int count ) { + const idAnim *anim = Anim(); + + if ( !anim ) { + cycle = -1; + endtime = 0; + } else { + cycle = count; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + if ( count < 0 ) { +// RAVEN END + cycle = -1; + endtime = -1; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + } else if ( count == 0 ) { +// RAVEN END + cycle = 1; + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length(); + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + anim->Length() / rate; + } else { + endtime = -1; + } + } else { + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length() * cycle; + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + ( anim->Length() * cycle ) / rate; + } else { + endtime = -1; + } + } + } +} + +/* +===================== +idAnimBlend::SetPlaybackRate +===================== +*/ +void idAnimBlend::SetPlaybackRate( int currentTime, float newRate ) { + int animTime; + + if ( rate == newRate ) { + return; + } + + animTime = AnimTime( currentTime ); + if ( newRate == 1.0f ) { + timeOffset = animTime - ( currentTime - starttime ); + } else { + timeOffset = animTime - ( currentTime - starttime ) * newRate; + } + + rate = newRate; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetPlaybackRate +===================== +*/ +float idAnimBlend::GetPlaybackRate( void ) const { + return rate; +} + +/* +===================== +idAnimBlend::SetStartTime +===================== +*/ +void idAnimBlend::SetStartTime( int _startTime ) { + starttime = _startTime; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetStartTime +===================== +*/ +int idAnimBlend::GetStartTime( void ) const { + if ( !animNum ) { + return 0; + } + + return starttime; +} + +/* +===================== +idAnimBlend::GetEndTime +===================== +*/ +int idAnimBlend::GetEndTime( void ) const { + if ( !animNum ) { + return 0; + } + + return endtime; +} + +/* +===================== +idAnimBlend::PlayLength +===================== +*/ +int idAnimBlend::PlayLength( void ) const { + if ( !animNum ) { + return 0; + } + + if ( endtime < 0 ) { + return -1; + } + + return endtime - starttime + timeOffset; +} + +/* +===================== +idAnimBlend::AllowMovement +===================== +*/ +void idAnimBlend::AllowMovement( bool allow ) { + allowMove = allow; +} + +/* +===================== +idAnimBlend::AllowFrameCommands +===================== +*/ +void idAnimBlend::AllowFrameCommands( bool allow ) { + allowFrameCommands = allow; +} + +/* +===================== +idAnimBlend::AnimNum +===================== +*/ +int idAnimBlend::AnimNum( void ) const { + return animNum; +} + +/* +===================== +idAnimBlend::AnimTime +===================== +*/ +int idAnimBlend::AnimTime( int currentTime ) const { + int time; + int length; + const idAnim *anim = Anim(); + + if ( anim ) { + if ( useFrameBlend ) { + return FRAME2MS( frameBlend.frame1 ); + } + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + time = currentTime - starttime + timeOffset; + } else { + time = static_cast( ( currentTime - starttime ) * rate ) + timeOffset; + } + + // given enough time, we can easily wrap time around in our frame calculations, so + // keep cycling animations' time within the length of the anim. + length = anim->Length(); + if ( ( cycle < 0 ) && ( length > 0 ) ) { + time %= length; + + // time will wrap after 24 days (oh no!), resulting in negative results for the %. + // adding the length gives us the proper result. + if ( time < 0 ) { + time += length; + } + } + return time; + } else { + return 0; + } +} + +/* +===================== +idAnimBlend::GetFrameNumber +===================== +*/ +int idAnimBlend::GetFrameNumber( int currentTime ) const { + const idMD5Anim *md5anim; + frameBlend_t frameinfo; + int animTime; + + const idAnim *anim = Anim(); + if ( !anim ) { + return 1; + } + + if ( useFrameBlend ) { + return frameBlend.frame1; + } + + md5anim = anim->MD5Anim( 0 ); + animTime = AnimTime( currentTime ); + md5anim->ConvertTimeToFrame( animTime, cycle, frameinfo ); + + return frameinfo.frame1 + 1; +} + +/* +===================== +idAnimBlend::CallFrameCommands +===================== +*/ +void idAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + const idMD5Anim *md5anim; + frameBlend_t frame1; + frameBlend_t frame2; + int fromFrameTime; + int toFrameTime; + + if ( !allowFrameCommands || !ent || useFrameBlend || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->HasFrameCommands() ) { + return; + } + + if ( totime <= starttime ) { + // don't play until next frame or we'll play commands twice. + // this happens on the player sometimes. + return; + } + + fromFrameTime = AnimTime( fromtime ); + toFrameTime = AnimTime( totime ); + if ( toFrameTime < fromFrameTime ) { + toFrameTime += anim->Length(); + } + + md5anim = anim->MD5Anim( 0 ); + md5anim->ConvertTimeToFrame( fromFrameTime, cycle, frame1 ); + md5anim->ConvertTimeToFrame( toFrameTime, cycle, frame2 ); + + if ( fromFrameTime <= 0 ) { + // make sure first frame is called + anim->CallFrameCommands( ent, -1, frame2.frame1 ); + } else { + anim->CallFrameCommands( ent, frame1.frame1, frame2.frame1 ); + } +} + +/* +===================== +idAnimBlend::BlendAnim +===================== +*/ +bool idAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + int i; + float lerp; + float mixWeight; + const idMD5Anim *md5anim; + idJointQuat *ptr; + frameBlend_t frametime = {0}; + idJointQuat *jointFrame; + idJointQuat *mixFrame; + int numAnims; + int time; + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( blendWeight > 0.0f ) { + if ( ( endtime >= 0 ) && ( currentTime >= endtime ) ) { + return false; + } + if ( !weight ) { + return false; + } + if ( overrideBlend ) { + blendWeight = 1.0f - weight; + } + } + + if ( ( channel == ANIMCHANNEL_ALL ) && !blendWeight ) { + // we don't need a temporary buffer, so just store it directly in the blend frame + jointFrame = blendFrame; + } else { + // allocate a temporary buffer to copy the joints from + jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + } + + time = AnimTime( currentTime ); + + numAnims = anim->NumAnims(); + if ( numAnims == 1 ) { + md5anim = anim->MD5Anim( 0 ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->ConvertTimeToFrame( time, cycle, frametime ); + md5anim->GetInterpolatedFrame( frametime, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + } else { + // + // need to mix the multipoint anim together first + // + // allocate a temporary buffer to copy the joints to + mixFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + + if ( !useFrameBlend ) { + anim->MD5Anim( 0 )->ConvertTimeToFrame( time, cycle, frametime ); + } + + ptr = jointFrame; + mixWeight = 0.0f; + for( i = 0; i < numAnims; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + lerp = animWeights[ i ] / mixWeight; + md5anim = anim->MD5Anim( i ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->GetInterpolatedFrame( frametime, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + // only blend after the first anim is mixed in + if ( ptr != jointFrame ) { + SIMDProcessor->BlendJoints( jointFrame, ptr, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + ptr = mixFrame; + } + } + + if ( !mixWeight ) { + return false; + } + } + + if ( removeOriginOffset ) { + if ( allowMove ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + if ( anim->GetAnimFlags().anim_turn ) { + jointFrame[ 0 ].q.Set( -0.70710677f, 0.0f, 0.0f, 0.70710677f ); + } + } + + if ( !blendWeight ) { + blendWeight = weight; + if ( channel != ANIMCHANNEL_ALL ) { + const int *index = modelDef->GetChannelJoints( channel ); + const int num = modelDef->NumJointsOnChannel( channel ); + for( i = 0; i < num; i++ ) { + int j = index[i]; + blendFrame[j].t = jointFrame[j].t; + blendFrame[j].q = jointFrame[j].q; + } + } + } else { + blendWeight += weight; + lerp = weight / blendWeight; + SIMDProcessor->BlendJoints( blendFrame, jointFrame, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + if ( printInfo ) { + if ( useFrameBlend ) { + gameLocal.Printf( " %s: '%s', %d, %.2f%%\n", channelNames[ channel ], anim->FullName(), frameBlend.frame1, weight * 100.0f ); + } else { + gameLocal.Printf( " %s: '%s', %.3f, %.2f%%\n", channelNames[ channel ], anim->FullName(), ( float )frametime.frame1 + frametime.backlerp, weight * 100.0f ); + } + } + + return true; +} + +/* +===================== +idAnimBlend::BlendOrigin +===================== +*/ +void idAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + float lerp; + idVec3 animpos; + idVec3 pos; + int time; + int num; + int i; + + if ( useFrameBlend || ( ( endtime > 0 ) && ( currentTime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + if ( allowMove && removeOriginOffset ) { + return; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return; + } + + time = AnimTime( currentTime ); + + pos.Zero(); + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time, cycle ); + pos += animpos * animWeights[ i ]; + } + + if ( !blendWeight ) { + blendPos = pos; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendPos += lerp * ( pos - blendPos ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDelta +===================== +*/ +void idAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + idVec3 pos1; + idVec3 pos2; + idVec3 animpos; + idVec3 delta; + int time1; + int time2; + float lerp; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + num = anim->NumAnims(); + + pos1.Zero(); + pos2.Zero(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time1, cycle ); + pos1 += animpos * animWeights[ i ]; + + anim->GetOrigin( animpos, i, time2, cycle ); + pos2 += animpos * animWeights[ i ]; + } + + delta = pos2 - pos1; + if ( !blendWeight ) { + blendDelta = delta; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta += lerp * ( delta - blendDelta ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDeltaRotation +===================== +*/ +void idAnimBlend::BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const { + idQuat q1; + idQuat q2; + idQuat q3; + int time1; + int time2; + float lerp; + float mixWeight; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->GetAnimFlags().anim_turn ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + q1.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + q2.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + + mixWeight = 0.0f; + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + if ( animWeights[ i ] == mixWeight ) { + anim->GetOriginRotation( q1, i, time1, cycle ); + anim->GetOriginRotation( q2, i, time2, cycle ); + } else { + lerp = animWeights[ i ] / mixWeight; + anim->GetOriginRotation( q3, i, time1, cycle ); + q1.Slerp( q1, q3, lerp ); + + anim->GetOriginRotation( q3, i, time2, cycle ); + q2.Slerp( q1, q3, lerp ); + } + } + } + + q3 = q1.Inverse() * q2; + if ( !blendWeight ) { + blendDelta = q3; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta.Slerp( blendDelta, q3, lerp ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::AddBounds +===================== +*/ +bool idAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + int i; + int num; + idBounds b; + int time; + idVec3 pos; + bool addorigin; + + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return false; + } + + time = AnimTime( currentTime ); + num = anim->NumAnims(); + + addorigin = !allowMove || !removeOriginOffset; + for( i = 0; i < num; i++ ) { + if ( anim->GetBounds( b, i, time, cycle ) ) { + if ( addorigin ) { + anim->GetOrigin( pos, i, time, cycle ); + b.TranslateSelf( pos ); + } + bounds.AddBounds( b ); + } + } + + return true; +} + +/*********************************************************************** + + idDeclModelDef + +***********************************************************************/ + +/* +===================== +idDeclModelDef::idDeclModelDef +===================== +*/ +idDeclModelDef::idDeclModelDef() { + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels=0; +#endif +// RAVEN END +} + +/* +===================== +idDeclModelDef::~idDeclModelDef +===================== +*/ +idDeclModelDef::~idDeclModelDef() { + FreeData(); +} + +/* +================= +idDeclModelDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclModelDef::Size( void ) const { + + int i; + size_t size; + + size = sizeof( idDeclModelDef ); + size += joints.Allocated(); + size += jointParents.Allocated(); + size += anims.Allocated(); + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + + size += channelJoints[i].Allocated(); + } + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclModelDef::CopyDecl +===================== +*/ +void idDeclModelDef::CopyDecl( const idDeclModelDef *decl ) { + int i; + + FreeData(); + + offset = decl->offset; + modelHandle = decl->modelHandle; + skin = decl->skin; + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new idAnim( this, decl->anims[ i ] ); + } + + joints.SetNum( decl->joints.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( joints.Ptr(), decl->joints.Ptr(), decl->joints.Num() * sizeof( joints[0] ) ); +// RAVEN END + jointParents.SetNum( decl->jointParents.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( jointParents.Ptr(), decl->jointParents.Ptr(), decl->jointParents.Num() * sizeof( jointParents[0] ) ); +// RAVEN END + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i] = decl->channelJoints[i]; + } +} + +/* +===================== +idDeclModelDef::FreeData +===================== +*/ +void idDeclModelDef::FreeData( void ) { + anims.DeleteContents( true ); + joints.Clear(); + jointParents.Clear(); + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +================ +idDeclModelDef::DefaultDefinition +================ +*/ +const char *idDeclModelDef::DefaultDefinition( void ) const { + return "{ }"; +} + +/* +==================== +idDeclModelDef::FindJoint +==================== +*/ +const jointInfo_t *idDeclModelDef::FindJoint( const char *name ) const { + int i; + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + joint = modelHandle->GetJoints(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( !joint->name.Icmp( name ) ) { + return &joints[ i ]; + } + } + + return NULL; +} + +/* +===================== +idDeclModelDef::ModelHandle +===================== +*/ +idRenderModel *idDeclModelDef::ModelHandle( void ) const { + return ( idRenderModel * )modelHandle; +} + +/* +===================== +idDeclModelDef::GetJointList +===================== +*/ +void idDeclModelDef::GetJointList( const char *jointnames, idList &jointList ) const { + const char *pos; + idStr jointname; + const jointInfo_t *joint; + const jointInfo_t *child; + int i; + int num; + bool getChildren; + bool subtract; + + if ( !modelHandle ) { + return; + } + + jointList.Clear(); + + num = modelHandle->NumJoints(); + + // scan through list of joints and add each to the joint list + pos = jointnames; + while( *pos ) { + // skip over whitespace + while( ( *pos != 0 ) && isspace( *pos ) ) { + pos++; + } + + if ( !*pos ) { + // no more names + break; + } + + // copy joint name + jointname = ""; + + if ( *pos == '-' ) { + subtract = true; + pos++; + } else { + subtract = false; + } + + if ( *pos == '*' ) { + getChildren = true; + pos++; + } else { + getChildren = false; + } + + while( ( *pos != 0 ) && !isspace( *pos ) ) { + jointname += *pos; + pos++; + } + + joint = FindJoint( jointname ); + if ( !joint ) { + gameLocal.Warning( "Unknown joint '%s' in '%s' for model '%s'", jointname.c_str(), jointnames, GetName() ); + continue; + } + + if ( !subtract ) { + jointList.AddUnique( joint->num ); + } else { + jointList.Remove( joint->num ); + } + + if ( getChildren ) { + // include all joint's children + child = joint + 1; + for( i = joint->num + 1; i < num; i++, child++ ) { + // all children of the joint should follow it in the list. + // once we reach a joint without a parent or with a parent + // who is earlier in the list than the specified joint, then + // we've gone through all it's children. + if ( child->parentNum < joint->num ) { + break; + } + + if ( !subtract ) { + jointList.AddUnique( child->num ); + } else { + jointList.Remove( child->num ); + } + } + } + } +} + +/* +===================== +idDeclModelDef::Touch +===================== +*/ +void idDeclModelDef::Touch( void ) const { + if ( modelHandle ) { + renderModelManager->FindModel( modelHandle->Name() ); + } +} + +/* +===================== +idDeclModelDef::GetDefaultSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetDefaultSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetDefaultPose +===================== +*/ +const idJointQuat *idDeclModelDef::GetDefaultPose( void ) const { + return modelHandle->GetDefaultPose(); +} + +/* +===================== +idDeclModelDef::SetupJoints +===================== +*/ +void idDeclModelDef::SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const { + int num; + const idJointQuat *pose; + idJointMat *list; + + if ( !modelHandle || modelHandle->IsDefaultModel() ) { + Mem_Free16( (*jointList) ); + (*jointList) = NULL; + frameBounds.Clear(); + return; + } + + // get the number of joints + num = modelHandle->NumJoints(); + + if ( !num ) { + gameLocal.Error( "model '%s' has no joints", modelHandle->Name() ); + } + + // set up initial pose for model (with no pose, model is just a jumbled mess) +//RAVEN BEGIN +//amccarthy: Added allocation tag + list = (idJointMat *) Mem_Alloc16( num * sizeof( list[0] ), MA_ANIM ); +//RAVEN END + pose = GetDefaultPose(); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( list, pose, joints.Num() ); + + // check if we offset the model by the origin joint + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + list[ 0 ].SetTranslation( idVec3( offset.x, offset.y + pose[0].t.y, offset.z + pose[0].t.z ) ); +#else + list[ 0 ].SetTranslation( offset ); +#endif + } else { + list[ 0 ].SetTranslation( pose[0].t + offset ); + } + + // transform the joint hierarchy + SIMDProcessor->TransformJoints( list, jointParents.Ptr(), 1, joints.Num() - 1 ); + + *numJoints = num; + *jointList = list; + + // get the bounds of the default pose + frameBounds = modelHandle->Bounds( NULL ); +} + +/* +===================== +idDeclModelDef::ParseAnim +===================== +*/ +bool idDeclModelDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + idAnim *anim; + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + const idMD5Anim *md5anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + animFlags_t flags; + + numAnims = 0; + memset( md5anims, 0, sizeof( md5anims ) ); + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new idAnim(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + do { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + // lookup the animation +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( token ); +// RAVEN END + if ( !md5anim ) { + src.Warning( "Couldn't load anim '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + md5anim->CheckModelHierarchy( modelHandle ); + + if ( numAnims > 0 ) { + // make sure it's the same length as the other anims + if ( md5anim->Length() != md5anims[ 0 ]->Length() ) { + src.Warning( "Anim '%s' does not match length of anim '%s'", md5anim->Name(), md5anims[ 0 ]->Name() ); + MakeDefault(); + return false; + } + } + + if ( numAnims >= ANIM_MaxSyncedAnims ) { + src.Warning( "Exceeded max synced anims (%d)", ANIM_MaxSyncedAnims ); + MakeDefault(); + return false; + } + + // add it to our list + md5anims[ numAnims ] = md5anim; + numAnims++; + } while ( src.CheckTokenString( "," ) ); + + if ( !numAnims ) { + src.Warning( "No animation specified" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, numAnims, md5anims ); + memset( &flags, 0, sizeof( flags ) ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + }else if ( token == "prevent_idle_override" ) { + flags.prevent_idle_override = true; + } else if ( token == "random_cycle_start" ) { + flags.random_cycle_start = true; +// RAVEN BEGIN +// bdube: added speed + } else if ( token == "sync_cycle" ) { + flags.sync_cycle = true; + } else if ( token == "rate" ) { + anim->SetPlaybackRate ( src.ParseFloat ( ) ); + } else if ( token == "ai_no_look" ) { + flags.ai_no_look = true; + } else if ( token == "ai_look_head_only" ) { + flags.ai_look_head_only = true; +// RAVEN END + } else if ( token == "ai_no_turn" ) { + flags.ai_no_turn = true; + } else if ( token == "anim_turn" ) { + flags.anim_turn = true; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } + +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + // set the flags + anim->SetAnimFlags( flags ); + return true; +} + +/* +================ +idDeclModelDef::Parse +================ +*/ +bool idDeclModelDef::Parse( const char *text, const int textLength, bool noCaching ) { + int i; + int num; + idStr filename; + idStr extension; + const idMD5Joint *md5joint; + const idMD5Joint *md5joints; + idLexer src; + idToken token; + idToken token2; + idStr jointnames; + int channel; + jointHandle_t jointnum; + idList jointList; + int numDefaultAnims; +// RAVEN BEGIN +// bdube: attachments + idList attachJoints; +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( token == "inherit" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + const idDeclModelDef *copy = static_cast( declManager->FindType( DECL_MODELDEF, token2, false ) ); + if ( !copy ) { + gameLocal.Warning( "Unknown model definition '%s'", token2.c_str() ); + } else if ( copy->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "inherited model definition '%s' defaulted", token2.c_str() ); + MakeDefault(); + return false; + } else { + CopyDecl( copy ); + numDefaultAnims = anims.Num(); + } + } else if ( token == "skin" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + skin = declManager->FindSkin( token2 ); + if ( !skin ) { + src.Warning( "Skin '%s' not found", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "mesh" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + filename = token2; + filename.ExtractFileExtension( extension ); + if ( extension != MD5_MESH_EXT ) { + src.Warning( "Invalid model for MD5 mesh" ); + MakeDefault(); + return false; + } + modelHandle = renderModelManager->FindModel( filename ); + if ( !modelHandle ) { + src.Warning( "Model '%s' not found", filename.c_str() ); + MakeDefault(); + return false; + } + + if ( modelHandle->IsDefaultModel() ) { + src.Warning( "Model '%s' defaulted", filename.c_str() ); + MakeDefault(); + return false; + } + + // get the number of joints + num = modelHandle->NumJoints(); + if ( !num ) { + src.Warning( "Model '%s' has no joints", filename.c_str() ); + } + + // set up the joint hierarchy + joints.SetGranularity( 1 ); + joints.SetNum( num ); + jointParents.SetNum( num ); + channelJoints[0].SetNum( num ); + md5joints = modelHandle->GetJoints(); + md5joint = md5joints; + for( i = 0; i < num; i++, md5joint++ ) { + joints[i].channel = ANIMCHANNEL_ALL; + joints[i].num = static_cast( i ); + if ( md5joint->parent ) { + joints[i].parentNum = static_cast( md5joint->parent - md5joints ); + } else { + joints[i].parentNum = INVALID_JOINT; + } + jointParents[i] = joints[i].parentNum; + channelJoints[0][i] = i; + } + } else if ( token == "remove" ) { + // removes any anims whos name matches + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + num = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( ( token2 == anims[ i ]->Name() ) || ( token2 == anims[ i ]->FullName() ) ) { + delete anims[ i ]; + anims.RemoveIndex( i ); + if ( i >= numDefaultAnims ) { + src.Warning( "Anim '%s' was not inherited. Anim should be removed from the model def.", token2.c_str() ); + MakeDefault(); + return false; + } + i--; + numDefaultAnims--; + num++; + continue; + } + } + if ( !num ) { + src.Warning( "Couldn't find anim '%s' to remove", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "anim" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining anims" ); + MakeDefault(); + return false; + } + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else if ( token == "offset" ) { + if ( !src.Parse1DMatrix( 3, offset.ToFloatPtr() ) ) { + src.Warning( "Expected vector following 'offset'" ); + MakeDefault(); + return false; + } + } else if ( token == "channel" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining channels" ); + MakeDefault(); + return false; + } + + // set the channel for a group of joints + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( !src.CheckTokenString( "(" ) ) { + src.Warning( "Expected { after '%s'\n", token2.c_str() ); + MakeDefault(); + return false; + } + + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !stricmp( channelNames[ i ], token2 ) ) { + break; + } + } + + if ( i >= ANIM_NumAnimChannels ) { + src.Warning( "Unknown channel '%s'", token2.c_str() ); + MakeDefault(); + return false; + } + + channel = i; + jointnames = ""; + + while( !src.CheckTokenString( ")" ) ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + jointnames += token2; + if ( ( token2 != "*" ) && ( token2 != "-" ) ) { + jointnames += " "; + } + } + + GetJointList( jointnames, jointList ); + + channelJoints[ channel ].SetNum( jointList.Num() ); + for( num = i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + if ( joints[ jointnum ].channel != ANIMCHANNEL_ALL ) { + src.Warning( "Joint '%s' assigned to multiple channels", modelHandle->GetJointName( jointnum ) ); + continue; + } + joints[ jointnum ].channel = channel; + channelJoints[ channel ][ num++ ] = jointnum; + } + channelJoints[ channel ].SetNum( num ); +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels++; +#endif +// RAVEN END + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclModelDef::Validate +===================== +*/ +bool idDeclModelDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclModelDef *pSelf = (idDeclModelDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclModelDef::HasAnim +===================== +*/ +bool idDeclModelDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclModelDef::NumAnims +===================== +*/ +int idDeclModelDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclModelDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclModelDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +int idDeclModelDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + +/* +===================== +idDeclModelDef::GetSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetModelName +===================== +*/ +const char *idDeclModelDef::GetModelName( void ) const { + if ( modelHandle ) { + return modelHandle->Name(); + } else { + return ""; + } +} + +/* +===================== +idDeclModelDef::Joints +===================== +*/ +const idList &idDeclModelDef::Joints( void ) const { + return joints; +} + +/* +===================== +idDeclModelDef::JointParents +===================== +*/ +const int * idDeclModelDef::JointParents( void ) const { + return jointParents.Ptr(); +} + +/* +===================== +idDeclModelDef::NumJoints +===================== +*/ +int idDeclModelDef::NumJoints( void ) const { + return joints.Num(); +} + +/* +===================== +idDeclModelDef::GetJoint +===================== +*/ +const jointInfo_t *idDeclModelDef::GetJoint( int jointHandle ) const { + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJoint : joint handle out of range" ); + } + return &joints[ jointHandle ]; +} + +/* +==================== +idDeclModelDef::GetJointName +==================== +*/ +const char *idDeclModelDef::GetJointName( int jointHandle ) const { + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJointName : joint handle out of range" ); + } + + joint = modelHandle->GetJoints(); + return joint[ jointHandle ].name.c_str(); +} + +/* +===================== +idDeclModelDef::NumJointsOnChannel +===================== +*/ +int idDeclModelDef::NumJointsOnChannel( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::NumJointsOnChannel : channel out of range" ); + } + return channelJoints[ channel ].Num(); +} + +/* +===================== +idDeclModelDef::GetChannelJoints +===================== +*/ +const int * idDeclModelDef::GetChannelJoints( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::GetChannelJoints : channel out of range" ); + } + return channelJoints[ channel ].Ptr(); +} + +/* +===================== +idDeclModelDef::GetVisualOffset +===================== +*/ +const idVec3 &idDeclModelDef::GetVisualOffset( void ) const { + return offset; +} + +// RAVEN BEGIN +// jsinger: allow exporting of this decl type in a preparsed form +#ifdef RV_BINARYDECLS +void idDeclModelDef::Write( SerialOutputStream &stream ) const +{ + WriteValue(offset[0], stream); + WriteValue(offset[1], stream); + WriteValue(offset[2], stream); + WriteValue(joints.Num(), stream); + for(int i=0; iName()), stream); + WriteValue(anims.Num(), stream); + for(int i=0; iWrite(stream); + } + + if(skin) + { + WriteValue(idStr(skin->GetName()), stream); + } + else + { + WriteValue(idStr(""), stream); + } +} + +void idDeclModelDef::AddReferences() const +{ +} + +idDeclModelDef::idDeclModelDef( SerialInputStream &stream ) +{ + offset[0] = stream.ReadFloatValue(); + offset[1] = stream.ReadFloatValue(); + offset[2] = stream.ReadFloatValue(); + int numJoints = stream.ReadIntValue(); + joints.AssureSize(numJoints); + for(int i=0; iFindModel( modelName.c_str() ); + } + int numAnims = stream.ReadIntValue(); + anims.AssureSize(numAnims); + for(int i=0; iFindSkin(skinName.c_str()); + } + else + { + skin = NULL; + } +} +#endif +// RAVEN END +/*********************************************************************** + + idAnimator + +***********************************************************************/ + +/* +===================== +idAnimator::idAnimator +===================== +*/ +idAnimator::idAnimator() { + int i, j; + + modelDef = NULL; + entity = NULL; + numJoints = 0; + joints = NULL; + lastTransformTime = -1; + stoppedAnimatingUpdate = false; + removeOriginOffset = false; + forceUpdate = false; + +// RAVEN BEGIN +// jshepard: + rateMultiplier = 1; +// RAVEN END + + frameBounds.Clear(); + + AFPoseJoints.SetGranularity( 1 ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ClearAFPose(); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } +} + +/* +===================== +idAnimator::~idAnimator +===================== +*/ +idAnimator::~idAnimator() { + FreeData(); +} + +/* +===================== +idAnimator::Allocated +===================== +*/ +size_t idAnimator::Allocated( void ) const { + size_t size; + + size = jointMods.Allocated() + numJoints * sizeof( joints[0] ) + jointMods.Num() * sizeof( jointMods[ 0 ] ) + AFPoseJointMods.Allocated() + AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) + AFPoseJoints.Allocated(); + + return size; +} + +/* +===================== +idAnimator::Save + +archives object for save game file +===================== +*/ +void idAnimator::Save( idSaveGame *savefile ) const { + int i; + int j; + + savefile->WriteModelDef( modelDef ); + savefile->WriteObject( entity ); + + savefile->WriteInt( jointMods.Num() ); + for( i = 0; i < jointMods.Num(); i++ ) { + savefile->Write( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->WriteInt( numJoints ); + savefile->Write( joints, numJoints * sizeof( joints[0] ) ); + + savefile->WriteInt( lastTransformTime ); + savefile->WriteBool( stoppedAnimatingUpdate ); + savefile->WriteBool( forceUpdate ); + savefile->WriteBounds( frameBounds ); + + savefile->WriteFloat( AFPoseBlendWeight ); + + savefile->WriteInt( AFPoseJoints.Num() ); + savefile->Write( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointMods.Num() ); + savefile->Write( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointFrameSize ); + savefile->Write( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->WriteBounds( AFPoseBounds ); + savefile->WriteInt( AFPoseTime ); + + savefile->WriteBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Save( savefile ); + } + } +} + +/* +===================== +idAnimator::Restore + +unarchives object from save game file +===================== +*/ +void idAnimator::Restore( idRestoreGame *savefile ) { + int i; + int j; + int num; + + savefile->ReadModelDef( modelDef ); + savefile->ReadObject( reinterpret_cast( entity ) ); + + savefile->ReadInt( num ); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + savefile->Read( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->ReadInt( numJoints ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + joints = (idJointMat *) Mem_Alloc16( numJoints * sizeof( joints[0] ), MA_ANIM ); +//RAVEN END + savefile->Read( joints, numJoints * sizeof( joints[0] ) ); + + savefile->ReadInt( lastTransformTime ); + savefile->ReadBool( stoppedAnimatingUpdate ); + savefile->ReadBool( forceUpdate ); + savefile->ReadBounds( frameBounds ); + + savefile->ReadFloat( AFPoseBlendWeight ); + + savefile->ReadInt( num ); + AFPoseJoints.SetGranularity( 1 ); + AFPoseJoints.SetNum( num ); + savefile->Read( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->ReadInt( num ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointMods.SetNum( num ); + savefile->Read( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->ReadInt( AFPoseJointFrameSize ); + AFPoseJointFrame = (idJointQuat *)Mem_Alloc16( AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + savefile->Read( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->ReadBounds( AFPoseBounds ); + savefile->ReadInt( AFPoseTime ); + + savefile->ReadBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Restore( savefile, modelDef ); + } + } +} + +/* +===================== +idAnimator::FreeData +===================== +*/ +void idAnimator::FreeData( void ) { + int i, j; + + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + } + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } + + jointMods.DeleteContents( true ); + + Mem_Free16( joints ); + joints = NULL; + numJoints = 0; + + modelDef = NULL; + + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ForceUpdate(); +} + +/* +===================== +idAnimator::PushAnims +===================== +*/ +void idAnimator::PushAnims( int channelNum, int currentTime, int blendTime ) { + int i; + idAnimBlend *channel; + + channel = channels[ channelNum ]; + if ( !channel[ 0 ].GetWeight( currentTime ) || ( channel[ 0 ].starttime == currentTime ) ) { + return; + } + + for( i = ANIM_MaxAnimsPerChannel - 1; i > 0; i-- ) { + channel[ i ] = channel[ i - 1 ]; + } + + channel[ 0 ].Reset( modelDef ); + channel[ 1 ].Clear( currentTime, blendTime ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetModel +===================== +*/ +idRenderModel *idAnimator::SetModel( const char *modelname ) { + int i, j; + + FreeData(); + + // check if we're just clearing the model + if ( !modelname || !*modelname ) { + return NULL; + } + + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( !modelDef ) { + return NULL; + } + + idRenderModel *renderModel = modelDef->ModelHandle(); + if ( !renderModel ) { + modelDef = NULL; + return NULL; + } + + // make sure model hasn't been purged + modelDef->Touch(); + +// RAVEN BEGIN +// bdube: make sure models dont get purged + if ( modelDef->ModelHandle() ) { + modelDef->ModelHandle()->SetLevelLoadReferenced ( true ); + } +// RAVEN END + + modelDef->SetupJoints( &numJoints, &joints, frameBounds, removeOriginOffset ); + modelDef->ModelHandle()->Reset(); + + // set the modelDef on all channels + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( modelDef ); + } + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::Size +===================== +*/ +size_t idAnimator::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +===================== +idAnimator::SetEntity +===================== +*/ +void idAnimator::SetEntity( idEntity *ent ) { + entity = ent; +} + +/* +===================== +idAnimator::GetEntity +===================== +*/ +idEntity *idAnimator::GetEntity( void ) const { + return entity; +} + +/* +===================== +idAnimator::RemoveOriginOffset +===================== +*/ +void idAnimator::RemoveOriginOffset( bool remove ) { + removeOriginOffset = remove; +} + +/* +===================== +idAnimator::RemoveOrigin +===================== +*/ +bool idAnimator::RemoveOrigin( void ) const { + return removeOriginOffset; +} + +/* +===================== +idAnimator::GetJointList +===================== +*/ +void idAnimator::GetJointList( const char *jointnames, idList &jointList ) const { + if ( modelDef ) { + modelDef->GetJointList( jointnames, jointList ); + } +} + +/* +===================== +idAnimator::NumAnims +===================== +*/ +int idAnimator::NumAnims( void ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->NumAnims(); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +const idAnim *idAnimator::GetAnim( int index ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->GetAnim( index ); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +int idAnimator::GetAnim( const char *name ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->GetAnim( name ); +} + +/* +===================== +idAnimator::HasAnim +===================== +*/ +bool idAnimator::HasAnim( const char *name ) const { + if ( !modelDef ) { + return false; + } + + return modelDef->HasAnim( name ); +} + +/* +===================== +idAnimator::NumJoints +===================== +*/ +int idAnimator::NumJoints( void ) const { + return numJoints; +} + +/* +===================== +idAnimator::ModelHandle +===================== +*/ +idRenderModel *idAnimator::ModelHandle( void ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::ModelDef +===================== +*/ +const idDeclModelDef *idAnimator::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnimator::CurrentAnim +===================== +*/ +idAnimBlend *idAnimator::CurrentAnim( int channelNum ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CurrentAnim : channel out of range" ); + } + + return &channels[ channelNum ][ 0 ]; +} + +/* +===================== +idAnimator::Clear +===================== +*/ +void idAnimator::Clear( int channelNum, int currentTime, int cleartime ) { + int i; + idAnimBlend *blend; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::Clear : channel out of range" ); + } + + blend = channels[ channelNum ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->Clear( currentTime, cleartime ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetFrame +===================== +*/ +void idAnimator::SetFrame( int channelNum, int animNum, const frameBlend_t & frameBlend ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SetFrame : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, gameLocal.time, 0 ); + channels[ channelNum ][ 0 ].SetFrame( modelDef, animNum, frameBlend ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::CycleAnim +===================== +*/ +void idAnimator::CycleAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CycleAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].CycleAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + + // If the old animation and new animation both sync cycles then do so now + idAnimBlend* oldChannel = &channels[channelNum][1]; + idAnimBlend* newChannel = &channels[channelNum][0]; + if ( oldChannel->AnimNum ( ) && newChannel->AnimNum ( ) ) { + if ( oldChannel->Anim ( )->GetAnimFlags ( ).sync_cycle && newChannel->Anim ( )->GetAnimFlags ( ).sync_cycle ) { + // Calculate the percentage through the animation the old channel was + float f = (float)oldChannel->GetFrameNumber ( currentTime ) / (float)oldChannel->NumFrames ( ); + // Move the new channel back in time to start it mid cycle + newChannel->SetStartTime ( gameLocal.time - f * (newChannel->Length ( ) / newChannel->GetPlaybackRate()) ); + } + } +} + +/* +===================== +idAnimator::PlayAnim +===================== +*/ +void idAnimator::PlayAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::PlayAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].PlayAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SyncAnimChannels +===================== +*/ +void idAnimator::SyncAnimChannels( int channelNum, int fromChannelNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) || ( fromChannelNum < 0 ) || ( fromChannelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SyncToChannel : channel out of range" ); + } + + idAnimBlend &fromBlend = channels[ fromChannelNum ][ 0 ]; + idAnimBlend &toBlend = channels[ channelNum ][ 0 ]; + + float weight = fromBlend.blendEndValue; + if ( ( fromBlend.Anim() != toBlend.Anim() ) || ( fromBlend.GetStartTime() != toBlend.GetStartTime() ) || ( fromBlend.GetEndTime() != toBlend.GetEndTime() ) ) { + PushAnims( channelNum, currentTime, blendTime ); + toBlend = fromBlend; + toBlend.blendStartValue = 0.0f; + toBlend.blendEndValue = 0.0f; + } + toBlend.SetWeight( weight, currentTime - 1, blendTime ); + + // disable framecommands on the current channel so that commands aren't called twice + toBlend.AllowFrameCommands( false ); + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +// RAVEN BEGIN +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindExistingJointMod( jointHandle_t jointnum, int *index ) { + jointMod_t* jointMod; + int i; + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( index ) + { + *index = i; + } + return jointMod; +} + +// bdube: added methods +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindJointMod ( jointHandle_t jointnum ) { + jointMod_t* jointMod; + int i; + + jointMod = FindExistingJointMod( jointnum, &i ); + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMod->transform_pos = JOINTMOD_NONE; + jointMod->angularVelocity.Init ( 0, 0, 0, 0, idAngles(0,0,0), idAngles(0,0,0) ); + jointMod->lastTime = 0; + jointMods.Insert( jointMod, i ); + } + + return jointMod; +} + +// abahr: +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( const char* animName, float rate ) { + SetPlaybackRate( GetAnim(animName), rate ); +} + +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( int animHandle, float rate ) { + idAnim* anim = const_cast( GetAnim(animHandle) ); + if( anim ) { + anim->SetPlaybackRate( rate ); + } +} +// RAVEN END + +/* +===================== +idAnimator::SetJointPos +===================== +*/ +void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); + +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->pos = pos; + jointMod->transform_pos = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAxis +===================== +*/ +void idAnimator::SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ) { + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod( jointnum ); + jointMod->mat = mat; + jointMod->transform_axis = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +// RAVEN BEGIN +// twhitaker: just added this for uniformity +/* +===================== +idAnimator::GetJointAxis +===================== +*/ +void idAnimator::GetJointAxis( jointHandle_t jointnum, idMat3 &mat ) { + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + mat = FindJointMod ( jointnum )->mat; +} +// RAVEN END + +/* +===================== +idAnimator::CollapseJoint + +Add a joint modification for the given joint to collapse it to another joint +===================== +*/ +void idAnimator::CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ) { + jointMod_t *jointMod; + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod ( jointnum ); + + jointMod->transform_pos = JOINTMOD_COLLAPSE; + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat.Zero ( ); + jointMod->collapseJoint = collapseTo; +} + +/* +===================== +idAnimator::CollapseJoints + +Add a collapse joint modification for each joint listed in "jointnames" to the given joint "coppaseJoint" +===================== +*/ +void idAnimator::CollapseJoints ( const char *jointnames, jointHandle_t collpaseJoint ) { + idList jointList; + int i; + + GetJointList( jointnames, jointList ); + + for ( i = 0; i < jointList.Num(); i ++ ) { + CollapseJoint ( jointList[i], collpaseJoint ); + } +} + +// RAVEN BEGIN +// bdube: added more programmer controlled joint methods +/* +===================== +idAnimator::AimJointAt +===================== +*/ +void idAnimator::AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int currentTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + idVec3 offset; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + + GetJointTransform ( jointnum, currentTime, offset, axis ); + + origin = entity->GetPhysics()->GetOrigin() + offset * entity->GetPhysics()->GetAxis(); + + dir = pos - origin; + dir.NormalizeFast ( ); + entity->GetPhysics()->GetAxis().ProjectVector ( dir, localDir ); + + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat = localDir.ToMat3(); + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAngularVelocity +===================== +*/ +void idAnimator::SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + if ( !jointMod->lastTime ) { + jointMod->lastTime = currentTime; + jointMod->mat = idAngles(0,0,0).ToMat3(); + } + + jointMod->transform_axis = JOINTMOD_LOCAL; + + if ( blendTime <= 0 ) { + jointMod->angularVelocity.Init ( currentTime, 0, 0, 1, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } else { + jointMod->angularVelocity.Init ( currentTime, blendTime/2, blendTime/2, blendTime, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +idAngles idAnimator::GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + return jointMod->angularVelocity.GetCurrentValue( currentTime ); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +void idAnimator::ClearJointAngularVelocity ( jointHandle_t jointnum ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + jointMod->angularVelocity.Init( 0, 0, 0, 0, jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue(), jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue() ); +} +// RAVEN END + +/* +===================== +idAnimator::ClearJoint +===================== +*/ +void idAnimator::ClearJoint( jointHandle_t jointnum ) { + int i; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + delete jointMods[ i ]; + jointMods.RemoveIndex( i ); + ForceUpdate(); + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } +} + +/* +===================== +idAnimator::ClearAllJoints +===================== +*/ +void idAnimator::ClearAllJoints( void ) { + if ( jointMods.Num() ) { + ForceUpdate(); + } + jointMods.DeleteContents( true ); +} + +/* +===================== +idAnimator::ClearAllAnims +===================== +*/ +void idAnimator::ClearAllAnims( int currentTime, int cleartime ) { + int i; + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + Clear( i, currentTime, cleartime ); + } + + ClearAFPose(); + ForceUpdate(); +} + +/* +==================== +idAnimator::GetDelta +==================== +*/ +void idAnimator::GetDelta( int fromtime, int totime, idVec3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Zero(); + return; + } + + delta.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + } +} + +/* +==================== +idAnimator::GetDeltaRotation +==================== +*/ +bool idAnimator::GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + idQuat q; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Identity(); + return false; + } + + q.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + } + + if ( blendWeight > 0.0f ) { + delta = q.ToMat3(); + return true; + } else { + delta.Identity(); + return false; + } +} + +/* +==================== +idAnimator::GetOrigin +==================== +*/ +void idAnimator::GetOrigin( int currentTime, idVec3 &pos ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() ) { + pos.Zero(); + return; + } + + pos.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + } + + pos += modelDef->GetVisualOffset(); +} + +/* +==================== +idAnimator::GetBounds +==================== +*/ +bool idAnimator::GetBounds( int currentTime, idBounds &bounds ) { + int i, j; + const idAnimBlend *blend; + int count; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( AFPoseJoints.Num() ) { + bounds = AFPoseBounds; + count = 1; + } else { + bounds.Clear(); + count = 0; + } + + blend = channels[ 0 ]; + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->AddBounds( currentTime, bounds, removeOriginOffset ) ) { + count++; + } + } + } + + if ( !count ) { + if ( !frameBounds.IsCleared() ) { + bounds = frameBounds; + return true; + } else { + bounds.Zero(); + return false; + } + } + + bounds.TranslateSelf( modelDef->GetVisualOffset() ); +// RAVEN BEGIN +// jscott: HACK! The bounds only get the range of the skeleton, so add in the distance between the highest bone and the top of the head + bounds[1][2] += 4.0f; +// RAVEN END + + if ( g_debugBounds.GetBool() ) { + if ( bounds[1][0] - bounds[0][0] > 2048 || bounds[1][1] - bounds[0][1] > 2048 ) { + if ( entity ) { + gameLocal.Warning( "big frameBounds on entity '%s' with model '%s': %f,%f", entity->name.c_str(), modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } else { + gameLocal.Warning( "big frameBounds on model '%s': %f,%f", modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } + } + } + + frameBounds = bounds; + + return true; +} + +/* +===================== +idAnimator::InitAFPose +===================== +*/ +void idAnimator::InitAFPose( void ) { + + if ( !modelDef ) { + return; + } + + AFPoseJoints.SetNum( modelDef->Joints().Num(), false ); + AFPoseJoints.SetNum( 0, false ); + AFPoseJointMods.SetNum( modelDef->Joints().Num(), false ); + + if ( AFPoseJointFrame == NULL || AFPoseJointFrameSize != modelDef->Joints().Num() ) { + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = (idJointQuat *) Mem_Alloc16( modelDef->Joints().Num() * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + AFPoseJointFrameSize = modelDef->Joints().Num(); + } +} + +/* +===================== +idAnimator::SetAFPoseJointMod +===================== +*/ +void idAnimator::SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ) { + AFPoseJointMods[jointNum].mod = mod; + AFPoseJointMods[jointNum].axis = axis; + AFPoseJointMods[jointNum].origin = origin; + + int index = idBinSearch_GreaterEqual( AFPoseJoints.Ptr(), AFPoseJoints.Num(), jointNum ); + if ( index >= AFPoseJoints.Num() || jointNum != AFPoseJoints[index] ) { + AFPoseJoints.Insert( jointNum, index ); + } +} + +/* +===================== +idAnimator::FinishAFPose +===================== +*/ +void idAnimator::FinishAFPose( int animNum, const idBounds &bounds, const int time ) { + int i, j; + int numJoints; + int parentNum; + int jointMod; + int jointNum; + const int * jointParent; + + if ( !modelDef ) { + return; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return; + } + + numJoints = modelDef->Joints().Num(); + if ( !numJoints ) { + return; + } + + idRenderModel *md5 = modelDef->ModelHandle(); + const idMD5Anim *md5anim = anim->MD5Anim( 0 ); + + if ( numJoints != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", md5->Name(), numJoints, md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + md5anim->GetSingleFrame( 0, jointFrame, modelDef->GetChannelJoints( ANIMCHANNEL_ALL ), modelDef->NumJointsOnChannel( ANIMCHANNEL_ALL ) ); + + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( *joints ) ); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( AFPoseJoints.Num() && AFPoseJoints[0] == 0 ) { + switch( AFPoseJointMods[0].mod ) { + case AF_JOINTMOD_AXIS: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + } + j = 1; + } else { + j = 0; + } + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // transform the child joints + for( i = 1; j < AFPoseJoints.Num(); j++, i++ ) { + jointMod = AFPoseJoints[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod - 1 ); + i = jointMod; + + parentNum = jointParent[i]; + + switch( AFPoseJointMods[jointMod].mod ) { + case AF_JOINTMOD_AXIS: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[i].SetRotation( joints[i].ToMat3() * joints[parentNum].ToMat3() ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + // untransform hierarchy + SIMDProcessor->UntransformJoints( joints, jointParent, 1, numJoints - 1 ); + + // convert joint matrices back to joint quaternions + SIMDProcessor->ConvertJointMatsToJointQuats( AFPoseJointFrame, joints, numJoints ); + + // find all modified joints and their parents + bool *blendJoints = (bool *) _alloca16( numJoints * sizeof( bool ) ); + memset( blendJoints, 0, numJoints * sizeof( bool ) ); + + // mark all modified joints and their parents + for( i = 0; i < AFPoseJoints.Num(); i++ ) { + for( jointNum = AFPoseJoints[i]; jointNum != INVALID_JOINT; jointNum = jointParent[jointNum] ) { + blendJoints[jointNum] = true; + } + } + + // lock all parents of modified joints + AFPoseJoints.SetNum( 0, false ); + for ( i = 0; i < numJoints; i++ ) { + if ( blendJoints[i] ) { + AFPoseJoints.Append( i ); + } + } + + AFPoseBounds = bounds; + AFPoseTime = time; + + ForceUpdate(); +} + +/* +===================== +idAnimator::SetAFPoseBlendWeight +===================== +*/ +void idAnimator::SetAFPoseBlendWeight( float blendWeight ) { + AFPoseBlendWeight = blendWeight; +} + +/* +===================== +idAnimator::BlendAFPose +===================== +*/ +bool idAnimator::BlendAFPose( idJointQuat *blendFrame ) const { + + if ( !AFPoseJoints.Num() ) { + return false; + } + + SIMDProcessor->BlendJoints( blendFrame, AFPoseJointFrame, AFPoseBlendWeight, AFPoseJoints.Ptr(), AFPoseJoints.Num() ); + + return true; +} + +/* +===================== +idAnimator::ClearAFPose +===================== +*/ +void idAnimator::ClearAFPose( void ) { + if ( AFPoseJoints.Num() ) { + ForceUpdate(); + } + AFPoseBlendWeight = 1.0f; + AFPoseJoints.SetNum( 0, false ); + AFPoseBounds.Clear(); + AFPoseTime = 0; +} + +/* +===================== +idAnimator::ServiceAnims +===================== +*/ +void idAnimator::ServiceAnims( int fromtime, int totime ) { + int i, j; + idAnimBlend *blend; + + if ( !modelDef ) { + return; + } + + if ( modelDef->ModelHandle() ) { + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + blend->CallFrameCommands( entity, fromtime, totime ); + } + } + } + + if ( !IsAnimating( totime ) ) { + stoppedAnimatingUpdate = true; + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + + // present one more time with stopped animations so the renderer can properly recreate interactions + entity->BecomeActive( TH_UPDATEVISUALS ); + } + } +} + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation +/* +===================== +idAnimator::IsAnimating +===================== +*/ +bool idAnimator::IsAnimating( int currentTime, bool IgnoreAF ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( !IgnoreAF && AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( !blend->IsDone( currentTime ) ) { + return true; + } + } + } + + return false; +} +// RAVEN END + +/* +===================== +idAnimator::FrameHasChanged +===================== +*/ +bool idAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->FrameHasChanged( currentTime ) ) { + return true; + } + } + } + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimator::CreateFrame +===================== +*/ +bool idAnimator::CreateFrame( int currentTime, bool force ) { + int i, j; + int numJoints; + int parentNum; + bool hasAnim; + bool debugInfo; + float baseBlend; + float blendWeight; + const idAnimBlend * blend; + const int * jointParent; +// RAVEN BEGIN +// bdube: removed const + jointMod_t * jointMod; +// RAVEN END + const idJointQuat * defaultPose; + + static idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only, 3 = draw joints only", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + +// RAVEN BEGIN +// rjohnson: always check the time transform except force. Even when skeleton is on + if ( !force ) { + if ( lastTransformTime == currentTime ) { + return false; + } + } + + if ( !force && !r_showSkel.GetInteger() ) { + if ( lastTransformTime != -1 && !stoppedAnimatingUpdate && !IsAnimating( currentTime ) ) { + return false; + } + } + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return false; + } +#endif +// RAVEN END + + lastTransformTime = currentTime; + stoppedAnimatingUpdate = false; + + if ( entity && ( ( g_debugAnim.GetInteger() == entity->entityNumber ) || ( g_debugAnim.GetInteger() == -2 ) ) ) { + debugInfo = true; + gameLocal.Printf( "---------------\n%d: entity '%s':\n", gameLocal.time, entity->GetName() ); + gameLocal.Printf( "model '%s':\n", modelDef->GetModelName() ); + } else { + debugInfo = false; + } + + // init the joint buffer + if ( AFPoseJoints.Num() ) { + // initialize with AF pose anim for the case where there are no other animations and no AF pose joint modifications + defaultPose = AFPoseJointFrame; + } else { + defaultPose = modelDef->GetDefaultPose(); + } + + if ( !defaultPose ) { + //gameLocal.Warning( "idAnimator::CreateFrame: no defaultPose on '%s'", modelDef->Name() ); + return false; + } + + numJoints = modelDef->Joints().Num(); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( jointFrame[0] ) ); + SIMDProcessor->Memcpy( jointFrame, defaultPose, numJoints * sizeof( jointFrame[0] ) ); + + hasAnim = false; + + // blend the all channel + baseBlend = 0.0f; + blend = channels[ ANIMCHANNEL_ALL ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_ALL, numJoints, jointFrame, baseBlend, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( baseBlend >= 1.0f ) { + break; + } + } + } + + // only blend other channels if there's enough space to blend into + if ( baseBlend < 1.0f ) { + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !modelDef->NumJointsOnChannel( i ) ) { + continue; + } + if ( i == ANIMCHANNEL_EYELIDS ) { + // eyelids blend over any previous anims, so skip it and blend it later + continue; + } + blendWeight = baseBlend; + blend = channels[ i ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, i, numJoints, jointFrame, blendWeight, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + + if ( debugInfo && !AFPoseJoints.Num() && !blendWeight ) { + gameLocal.Printf( "%d: %s using default pose in model '%s'\n", gameLocal.time, channelNames[ i ], modelDef->GetModelName() ); + } + } + } + + // blend in the eyelids + if ( modelDef->NumJointsOnChannel( ANIMCHANNEL_EYELIDS ) ) { + blend = channels[ ANIMCHANNEL_EYELIDS ]; + blendWeight = baseBlend; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_EYELIDS, numJoints, jointFrame, blendWeight, removeOriginOffset, true, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + } + + // blend the articulated figure pose + if ( BlendAFPose( jointFrame ) ) { + hasAnim = true; + } + + if ( !hasAnim && !jointMods.Num() ) { + // no animations were updated + return false; + } + + // convert the joint quaternions to rotation matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // check if we need to modify the origin + if ( jointMods.Num() && ( jointMods[0]->jointnum == 0 ) ) { + jointMod = jointMods[0]; + + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetRotation( jointMod->mat * joints[0].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[0].SetRotation( joints[0].ToMat3() * jointMod->mat ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetRotation( jointMod->mat ); + break; + } + + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetTranslation( joints[0].ToVec3() + jointMod->pos ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetTranslation( jointMod->pos ); + break; + } + j = 1; + } else { + j = 0; + } + + // add in the model offset + joints[0].SetTranslation( joints[0].ToVec3() + modelDef->GetVisualOffset() ); + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // add in any joint modifications + for( i = 1; j < jointMods.Num(); j++, i++ ) { + jointMod = jointMods[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod->jointnum - 1 ); + i = jointMod->jointnum; + + parentNum = jointParent[i]; + +// RAVEN BEGIN +// bdube: angular velocity on joints + if ( jointMod->angularVelocity.GetStartTime ( ) ) { + idAngles angles; + angles = jointMod->angularVelocity.GetCurrentValue ( currentTime ) * MS2SEC(currentTime-jointMod->lastTime); + angles.Normalize360 ( ); + jointMod->mat *= angles.ToMat3 ( ); + } +// RAVEN END + + // modify the axis + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + joints[i].SetRotation( joints[i].ToMat3() * joints[ parentNum ].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetRotation( jointMod->mat * ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetRotation( jointMod->mat * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetRotation( ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) * jointMod->mat ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetRotation( jointMod->mat ); + break; + } + + // modify the position + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetTranslation( joints[parentNum].ToVec3() + ( joints[i].ToVec3() + jointMod->pos ) * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + jointMod->pos * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() + jointMod->pos ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetTranslation( jointMod->pos ); + break; + + case JOINTMOD_COLLAPSE: + joints[i].SetTranslation( joints[jointMod->collapseJoint].ToVec3() ); + break; + } + +// RAVEN BEGIN +// bdube: more joint control + jointMod->lastTime = currentTime; +// RAVEN END + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + return true; +} + +/* +===================== +idAnimator::ForceUpdate +===================== +*/ +void idAnimator::ForceUpdate( void ) { + lastTransformTime = -1; + forceUpdate = true; +} + +/* +===================== +idAnimator::ClearForceUpdate +===================== +*/ +void idAnimator::ClearForceUpdate( void ) { + forceUpdate = false; +} + +/* +===================== +idAnimator::GetJointTransform + +===================== +*/ +bool idAnimator::GetJointTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef || ( jointHandle < 0 ) || ( jointHandle >= modelDef->NumJoints() ) ) { + return false; + } + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + CreateFrame( currentTime, false ); + + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + + return true; +} + +/* +===================== +idAnimator::GetJointLocalTransform +===================== +*/ +bool idAnimator::GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef ) { + return false; + } + + const idList &modelJoints = modelDef->Joints(); + + if ( ( jointHandle < 0 ) || ( jointHandle >= modelJoints.Num() ) ) { + return false; + } + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + // FIXME: overkill + CreateFrame( currentTime, false ); + + if ( jointHandle > 0 ) { + idJointMat m = joints[ jointHandle ]; + m /= joints[ modelJoints[ jointHandle ].parentNum ]; + offset = m.ToVec3(); + axis = m.ToMat3(); + } else { + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + } + + return true; +} + +/* +===================== +idAnimator::GetJointHandle +===================== +*/ +jointHandle_t idAnimator::GetJointHandle( const char *name ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return INVALID_JOINT; + } + + return modelDef->ModelHandle()->GetJointHandle( name ); +} + +/* +===================== +idAnimator::GetJointName +===================== +*/ +const char *idAnimator::GetJointName( jointHandle_t handle ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return ""; + } + + return modelDef->ModelHandle()->GetJointName( handle ); +} + +/* +===================== +idAnimator::GetChannelForJoint +===================== +*/ +int idAnimator::GetChannelForJoint( jointHandle_t joint ) const { + if ( !modelDef ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: NULL model" ); + } + + if ( ( joint < 0 ) || ( joint >= numJoints ) ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: invalid joint num (%d)", joint ); + } + + return modelDef->GetJoint( joint )->channel; +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( const char *name ) const { + return GetFirstChild( GetJointHandle( name ) ); +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( jointHandle_t jointnum ) const { + int i; + int num; + const jointInfo_t *joint; + + if ( !modelDef ) { + return INVALID_JOINT; + } + + num = modelDef->NumJoints(); + if ( !num ) { + return jointnum; + } + joint = modelDef->GetJoint( 0 ); + for( i = 0; i < num; i++, joint++ ) { + if ( joint->parentNum == jointnum ) { + return ( jointHandle_t )joint->num; + } + } + return jointnum; +} + +/* +===================== +idAnimator::GetJoints +===================== +*/ +void idAnimator::GetJoints( int *numJoints, idJointMat **jointsPtr ) { + *numJoints = this->numJoints; + *jointsPtr = this->joints; +} + +/* +===================== +idAnimator::GetAnimFlags +===================== +*/ +const animFlags_t idAnimator::GetAnimFlags( int animNum ) const { + animFlags_t result; + + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->GetAnimFlags(); + } + + memset( &result, 0, sizeof( result ) ); + return result; +} + +/* +===================== +idAnimator::IsBlending + +Returns true if the given channel number is currently blending more than one animation +===================== +*/ +bool idAnimator::IsBlending ( int channel, int currentTime ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::GetAnimCount : channel (%d) out of range", channel ); + } + + int i; + for( i = 1; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( !channels[ channel ][ i ].IsDone ( currentTime ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idAnimator::NumFrames +===================== +*/ +int idAnimator::NumFrames( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumFrames(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::NumSyncedAnims +===================== +*/ +int idAnimator::NumSyncedAnims( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumAnims(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::AnimName +===================== +*/ +const char *idAnimator::AnimName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Name(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimFullName +===================== +*/ +const char *idAnimator::AnimFullName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } else { + return ""; + } +} + +// RAVEN BEGIN +// rjohnson: more output for animators +/* +===================== +idAnimator::AnimMD5Name +===================== +*/ +const char *idAnimator::AnimMD5Name( int animNum, int index ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + const idMD5Anim *md5Anim = anim->MD5Anim( index ); + + if ( md5Anim ) { + return md5Anim->Name(); + } else { + return ""; + } + } else { + return ""; + } +} +// RAVEN END + +/* +===================== +idAnimator::AnimLength +===================== +*/ +int idAnimator::AnimLength( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Length(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::TotalMovementDelta +===================== +*/ +const idVec3 &idAnimator::TotalMovementDelta( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->TotalMovementDelta(); + } else { + return vec3_origin; + } +} + +// RAVEN BEGIN +// nrausch: added func +/* +===================== +idAnimator::GetNearestJoint +===================== +*/ +jointHandle_t idAnimator::GetNearestJoint( const idVec3 &start, const idVec3 &end, int currentTime, jointHandle_t *jointList, int cnt ) { + + int numJoints = 0; + idJointMat *joints = 0; + GetJoints( &numJoints, &joints ); + + jointHandle_t closestJoint = INVALID_JOINT; + float closest = 0.0f; + + idVec3 a = end - start; + a.Normalize(); + + for ( int i = 0; i < cnt; ++i ) { + + // If we specified a null jointlist, just run through each joint + jointHandle_t jointNum = (jointHandle_t)i; + if ( jointList ) { + jointNum = jointList[i]; + } + + if ( jointNum != INVALID_JOINT ) { + idVec3 jointPos; + idMat3 jointMat; + + GetJointTransform( jointNum, currentTime, jointPos, jointMat ); + + idVec3 b = jointPos - start; + b.Normalize(); + + float newDot = DotProduct(a, b); + + if ( newDot > closest ) { + closest = newDot; + closestJoint = jointNum; + } + } + } + + return closestJoint; +} +// RAVEN END + +/*********************************************************************** + + Util functions + +***********************************************************************/ + +/* +===================== +ANIM_GetModelDefFromEntityDef +===================== +*/ +const idDeclModelDef *ANIM_GetModelDefFromEntityDef( const idDict *args ) { + const idDeclModelDef *modelDef; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef && modelDef->ModelHandle() ) { + return modelDef; + } + + return NULL; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const idDict *args ) { + idRenderModel *model; + const idDeclModelDef *modelDef; + + model = NULL; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + + if ( !model ) { + model = renderModelManager->FindModel( name ); + } + + if ( model && model->IsDefaultModel() ) { + return NULL; + } + + return model; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const char *classname ) { + const idDict *args; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + return ANIM_GetModelFromEntityDef( args ); +} + +/* +===================== +idGameEdit::ANIM_GetModelOffsetFromEntityDef +===================== +*/ +const idVec3 &idGameEdit::ANIM_GetModelOffsetFromEntityDef( const char *classname ) { + const idDict *args; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return vec3_origin; + } + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( !modelDef ) { + return vec3_origin; + } + + return modelDef->GetVisualOffset(); +} + +/* +===================== +idGameEdit::ANIM_GetModelFromName +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromName( const char *modelName ) { + const idDeclModelDef *modelDef; + idRenderModel *model; + + model = NULL; + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + if ( !model ) { + model = renderModelManager->FindModel( modelName ); + } + return model; +} + +/* +===================== +idGameEdit::ANIM_GetAnimFromEntityDef +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ) { + const idDict *args; + const idMD5Anim *md5anim; + const idAnim *anim; + int animNum; + const char *modelname; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + md5anim = NULL; + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( animNum ) { + anim = modelDef->GetAnim( animNum ); + if ( anim ) { + md5anim = anim->MD5Anim( 0 ); + } + } + } + return md5anim; +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetAnimFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return NULL; + } + + return anim->MD5Anim(0); +} + +/* +===================== +idGameEdit::ANIM_GetAnimPlaybackRateFromEntity +===================== +*/ +float idGameEdit::ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return 1.0f; + } + + return anim->GetPlaybackRate(); +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const char* idGameEdit::ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} +// RAVEN END + +/* +===================== +idGameEdit::ANIM_GetNumAnimsFromEntityDef +===================== +*/ +int idGameEdit::ANIM_GetNumAnimsFromEntityDef( const idDict *args ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + return modelDef->NumAnims(); + } + return 0; +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntityDef +===================== +*/ +const char *idGameEdit::ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + const idAnim* anim = modelDef->GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } + } + return ""; +} + +/* +===================== +idGameEdit::ANIM_GetAnim +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnim( const char *fileName ) { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + return animationLib->GetAnim( fileName ); +// RAVEN END +} + +/* +===================== +idGameEdit::ANIM_GetLength +===================== +*/ +int idGameEdit::ANIM_GetLength( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->Length(); +} + +/* +===================== +idGameEdit::ANIM_GetNumFrames +===================== +*/ +int idGameEdit::ANIM_GetNumFrames( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->NumFrames(); +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetFilename +===================== +*/ +const char * idGameEdit::ANIM_GetFilename( const idMD5Anim* anim ) { + return anim->Name(); +} + +/* +===================== +idGameEdit::ANIM_ConvertFrameToTime +===================== +*/ +int idGameEdit::ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ) { + frameBlend_t fb; + fb.frame1 = frame; + fb.frame2 = frame; + return anim->ConvertFrameToTime ( fb ); +} + +/* +===================== +idGameEdit::ANIM_ConvertTimeToFrame +===================== +*/ +int idGameEdit::ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ) { + frameBlend_t fb; + anim->ConvertTimeToFrame ( time, 0, fb ); + return fb.frame1; +} + +// RAVEN END + +/* +===================== +idGameEdit::ANIM_CreateAnimFrame +===================== +*/ +void idGameEdit::ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *joints, int time, const idVec3 &offset, bool remove_origin_offset ) { + int i; + frameBlend_t frame; + const idMD5Joint *md5joints; + int *index; + + if ( !model || model->IsDefaultModel() || !anim ) { + return; + } + + if ( numJoints != model->NumJoints() ) { + gameLocal.Error( "ANIM_CreateAnimFrame: different # of joints in renderEntity_t (%d) than in model (%s)(%d)", model->NumJoints(), model->Name(), numJoints ); + } + + if ( !model->NumJoints() ) { + // FIXME: Print out a warning? + return; + } + + if ( !joints ) { + gameLocal.Error( "ANIM_CreateAnimFrame: NULL joint frame pointer on model (%s)", model->Name() ); + } + + if ( numJoints != anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), numJoints, anim->Name(), anim->NumJoints() ); +// RAVEN END + for( i = 0; i < numJoints; i++ ) { + joints[i].SetRotation( mat3_identity ); + joints[i].SetTranslation( offset ); + } + return; + } + + // create index for all joints + index = ( int * )_alloca16( numJoints * sizeof( int ) ); + for ( i = 0; i < numJoints; i++ ) { + index[i] = i; + } + + // create the frame + anim->ConvertTimeToFrame( time, 1, frame ); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + anim->GetInterpolatedFrame( frame, jointFrame, index, numJoints ); + + // convert joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( remove_origin_offset ) { + joints[0].SetTranslation( offset ); + } else { + joints[0].SetTranslation( joints[0].ToVec3() + offset ); + } + + // transform the children + md5joints = model->GetJoints(); + for( i = 1; i < numJoints; i++ ) { + joints[i] *= joints[ md5joints[i].parent - md5joints ]; + } +} + +/* +===================== +idGameEdit::ANIM_CreateMeshForAnim +===================== +*/ +idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ) { + renderEntity_t ent; + const idDict *args; + const char *temp; + idRenderModel *newmodel; + const idMD5Anim *md5anim; + idStr filename; + idStr extension; + const idAnim *anim; + int animNum; + idVec3 offset; + const idDeclModelDef *modelDef; + + if ( !model || model->IsDefaultModel() ) { + return NULL; + } + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + memset( &ent, 0, sizeof( ent ) ); + + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( !animNum ) { + return NULL; + } + anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + md5anim = anim->MD5Anim( 0 ); + ent.customSkin = modelDef->GetDefaultSkin(); + offset = modelDef->GetVisualOffset(); + } else { + filename = animname; + filename.ExtractFileExtension( extension ); + if ( !extension.Length() ) { + animname = args->GetString( va( "anim %s", animname ) ); + } + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( animname ); +// RAVEN END + offset.Zero(); + } + + if ( !md5anim ) { + return NULL; + } + + temp = args->GetString( "skin", "" ); + if ( temp[ 0 ] ) { + ent.customSkin = declManager->FindSkin( temp ); + } + + ent.numJoints = model->NumJoints(); +//RAVEN BEGIN +//amccarthy: Added allocation tag + ent.joints = ( idJointMat * )Mem_Alloc16( ent.numJoints * sizeof( *ent.joints ), MA_ANIM ); +//RAVEN END + + ANIM_CreateAnimFrame( model, md5anim, ent.numJoints, ent.joints, FRAME2MS( frame ), offset, remove_origin_offset ); + + newmodel = model->InstantiateDynamicModel( &ent, NULL, NULL, ~SURF_COLLISION ); + + Mem_Free16( ent.joints ); + ent.joints = NULL; + + return newmodel; +} + +// RAVEN BEGIN +// jsinger: allow for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +idAnim::idAnim( idDeclModelDef const *def, SerialInputStream &stream ) +{ + modelDef = def; + numAnims = stream.ReadIntValue(); + for(int i=0; i(); + for(int i=0; iAppend(stream.ReadStringValue()); + } + } + else + { + com.parmList = NULL; + } + } + flags.prevent_idle_override = stream.ReadBoolValue(); + flags.random_cycle_start = stream.ReadBoolValue(); + flags.ai_no_turn = stream.ReadBoolValue(); + flags.anim_turn = stream.ReadBoolValue(); + flags.sync_cycle = stream.ReadBoolValue(); + flags.ai_no_look = stream.ReadBoolValue(); + flags.ai_look_head_only = stream.ReadBoolValue(); + rate = stream.ReadFloatValue(); +} + +void idAnim::Write( SerialOutputStream &stream ) const +{ + stream.Write(numAnims); + for(int i=0; iName()); + } + stream.Write(name); + stream.Write(realname); + stream.Write(frameLookup.Num()); + for(int i=0; iNum()); + for(int j=0; jNum(); j++) + { + stream.Write((*frameCommands[i].parmList)[j]); + } + } + } + stream.Write((bool)flags.prevent_idle_override); + stream.Write((bool)flags.random_cycle_start); + stream.Write((bool)flags.ai_no_turn); + stream.Write((bool)flags.anim_turn); + stream.Write((bool)flags.sync_cycle); + stream.Write((bool)flags.ai_no_look); + stream.Write((bool)flags.ai_look_head_only); + stream.Write(rate); +} +#endif +// RAVEN END diff --git a/source/mpgame/anim/Anim_Import.cpp b/source/mpgame/anim/Anim_Import.cpp new file mode 100644 index 0000000..d9b0910 --- /dev/null +++ b/source/mpgame/anim/Anim_Import.cpp @@ -0,0 +1,604 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "../../MayaImport/maya_main.h" + +/*********************************************************************** + + Maya conversion functions + +***********************************************************************/ + +static idStr Maya_Error; + +static exporterInterface_t Maya_ConvertModel = NULL; +static exporterShutdown_t Maya_Shutdown = NULL; +static int importDLL = 0; + +bool idModelExport::initialized = false; + + +// RAVEN BEGIN +// scork: now needed to cleanup after last Maya model conversion since I cache them for speed during "exportmodels" +bool g_bMayaConversionCleanerRunning = false; // so we can check all calls to Maya_ConvertModel have been updated to include this ;-) +struct MayaConversionCleaner +{ + MayaConversionCleaner() + { + g_bMayaConversionCleanerRunning = true; + } + ~MayaConversionCleaner() + { + if ( Maya_ConvertModel ) + { + Maya_ConvertModel( NULL, NULL, NULL ); + } + g_bMayaConversionCleanerRunning = false; + } +}; +// RAVEN END + + +/* +==================== +idModelExport::idModelExport +==================== +*/ +idModelExport::idModelExport() { + Reset(); +} + +/* +==================== +idModelExport::Shutdown +==================== +*/ +void idModelExport::Shutdown( void ) { + if ( Maya_Shutdown ) { + Maya_Shutdown(); + } + + if ( importDLL ) { + sys->DLL_Unload( importDLL ); + } + + importDLL = 0; + Maya_Shutdown = NULL; + Maya_ConvertModel = NULL; + Maya_Error.Clear(); + initialized = false; +} + +/* +===================== +idModelExport::CheckMayaInstall + +Determines if Maya is installed on the user's machine +===================== +*/ +bool idModelExport::CheckMayaInstall( void ) { +// RAVEN BEGIN +// jscott:revisit - this should go into the exe +//#ifdef _WIN32 +#ifndef _WINDOWS + return false; +#elif 0 + HKEY hKey; + long lres, lType; + + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya\\4.5\\Setup\\InstallPath", &hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + + lres = RegQueryValueEx( hKey, "MAYA_INSTALL_LOCATION", NULL, (unsigned long*)&lType, (unsigned char*)NULL, (unsigned long*)NULL ); + + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#else + HKEY hKey; + long lres; + + // only check the non-version specific key so that we only have to update the maya dll when new versions are released + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya", &hKey ); + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#endif +} + +/* +===================== +idModelExport::LoadMayaDll + +Checks to see if we can load the Maya export dll +===================== +*/ +void idModelExport::LoadMayaDll( void ) { + exporterDLLEntry_t dllEntry; + char dllPath[ MAX_OSPATH ]; + + fileSystem->FindDLL( "MayaImport", dllPath, false ); + if ( !dllPath[ 0 ] ) { + return; + } + importDLL = sys->DLL_Load( dllPath ); + if ( !importDLL ) { + return; + } + + // look up the dll interface functions + dllEntry = ( exporterDLLEntry_t )sys->DLL_GetProcAddress( importDLL, "dllEntry" ); + Maya_ConvertModel = ( exporterInterface_t )sys->DLL_GetProcAddress( importDLL, "Maya_ConvertModel" ); + Maya_Shutdown = ( exporterShutdown_t )sys->DLL_GetProcAddress( importDLL, "Maya_Shutdown" ); + if ( !Maya_ConvertModel || !dllEntry || !Maya_Shutdown ) { + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Invalid interface on export DLL." ); + return; + } + + // initialize the DLL +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to fix some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR + if ( !dllEntry( MD5_VERSION, common, sys, Memory::Allocate, Memory::Free, Memory::MSize ) ) { +#else + if ( !dllEntry( MD5_VERSION, common, sys ) ) { +#endif +// RAVEN END + // init failed + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Export DLL init failed." ); + return; + } +// RAVEN BEGIN +// jscott: maya needs the source control in the tools dll + common->LoadToolsDLL(); +// RAVEN END +} + +/* +===================== +idModelExport::ConvertMayaToMD5 + +Checks if a Maya model should be converted to an MD5, and converts if if the time/date or +version number has changed. +===================== +*/ +bool idModelExport::ConvertMayaToMD5( void ) { + unsigned sourceTime; + unsigned destTime; + int version; + idToken cmdLine; + idStr path; + + // check if our DLL got loaded + if ( initialized && !Maya_ConvertModel ) { + Maya_Error = "MayaImport dll not loaded."; + return false; + } + + // if idAnimManager::forceExport is set then we always reexport Maya models + if ( idAnimManager::forceExport ) { + force = true; + } + +// RAVEN BEGIN +// bdube: fs_import path + + // we need to make sure we have a full path, so convert the filename to an OS path + path.AppendPath( cvarSystem->GetCVarString ("fs_importpath")); + path.AppendPath(src); + + idFile* f = fileSystem->OpenExplicitFileRead ( path ); + if ( !f ) { + Maya_Error = "could not open source file"; + return false; + } + sourceTime = f->Timestamp ( ); + fileSystem->CloseFile ( f ); + +/* + // get the source file's time + if ( fileSystem->ReadFile( src, NULL, &sourceTime ) < 0 ) { + // source file doesn't exist + return true; + } +*/ + +// RAVEN END + + // get the destination file's time + if ( !force && ( fileSystem->ReadFile( dest, NULL, &destTime ) >= 0 ) ) { + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + + parser.LoadFile( dest ); + + // read the file version + if ( parser.CheckTokenString( MD5_VERSION_STRING ) ) { + version = parser.ParseInt(); + + // check the command line + if ( parser.CheckTokenString( "commandline" ) ) { + parser.ReadToken( &cmdLine ); + + // check the file time, scale, and version + if ( ( destTime >= sourceTime ) && ( version == MD5_VERSION ) && ( cmdLine == commandLine ) ) { + // don't convert it + return true; + } + } + } + } + + // if this is the first time we've been run, check if Maya is installed and load our DLL + if ( !initialized ) { + initialized = true; + + if ( !CheckMayaInstall() ) { + Maya_Error = "Maya not installed in registry."; + return false; + } + + LoadMayaDll(); + + // check if our DLL got loaded + if ( !Maya_ConvertModel ) { + Maya_Error = "Could not load MayaImport dll."; + return false; + } + } + + // we need to make sure we have a full path, so convert the filename to an OS path + src = fileSystem->RelativePathToOSPath( src ); + dest = fileSystem->RelativePathToOSPath( dest ); + + dest.ExtractFilePath( path ); + if ( path.Length() ) { + fileSystem->CreateOSPath( path ); + } + + // get the os path in case it needs to create one + path = fileSystem->RelativePathToOSPath( "" ); + + common->SetRefreshOnPrint( true ); +// RAVEN BEGIN +// scork: if this assert ever triggers it means there's a call to ConvertMayaToMD5() that doesn't have a 'MayaConversionCleaner' object above it. Go and put one in. Now. + assert( g_bMayaConversionCleanerRunning ); +// bdube: support src and dest ospath + Maya_Error = Maya_ConvertModel( cvarSystem->GetCVarString ( "fs_importpath" ), path, commandLine ); +// RAVEN END + common->SetRefreshOnPrint( false ); + if ( Maya_Error != "Ok" ) { + return false; + } + + // conversion succeded + return true; +} + +/* +==================== +idModelExport::Reset +==================== +*/ +void idModelExport::Reset( void ) { + force = false; + commandLine = ""; + src = ""; + dest = ""; +} + +/* +==================== +idModelExport::ExportModel +==================== +*/ +bool idModelExport::ExportModel( const char *model ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + const char *game = cvarSystem->GetCVarString( "fs_game" ); + + if ( strlen(game) == 0 ) { + game = BASE_GAMEDIR; + } + + Reset(); + src = model; + dest = model; + dest.SetFileExtension( MD5_MESH_EXT ); + + sprintf( commandLine, "mesh %s -dest %s -game %s", src.c_str(), dest.c_str(), game ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ExportAnim +==================== +*/ +bool idModelExport::ExportAnim( const char *anim ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + Reset(); + src = anim; + dest = anim; + dest.SetFileExtension( MD5_ANIM_EXT ); + + sprintf( commandLine, "anim %s -dest %s -game %s", src.c_str(), dest.c_str(), CD_BASEDIR ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ParseOptions +==================== +*/ +bool idModelExport::ParseOptions( idLexer &lex ) { + idToken token; + idStr destdir; + idStr sourcedir; + + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expected filename" ); + return false; + } + + src = token; + dest = token; + + while( lex.ReadToken( &token ) ) { + if ( token == "-" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expecting option" ); + return false; + } + if ( token == "sourcedir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -sourcedir" ); + return false; + } + sourcedir = token; + } else if ( token == "destdir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -destdir" ); + return false; + } + destdir = token; + } else if ( token == "dest" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing filename after -dest" ); + return false; + } + dest = token; + } else { + commandLine += va( " -%s", token.c_str() ); + } + } else { + commandLine += va( " %s", token.c_str() ); + } + } + + if ( sourcedir.Length() ) { + src.StripPath(); + sourcedir.BackSlashesToSlashes(); + sprintf( src, "%s/%s", sourcedir.c_str(), src.c_str() ); + } + + if ( destdir.Length() ) { + dest.StripPath(); + destdir.BackSlashesToSlashes(); + sprintf( dest, "%s/%s", destdir.c_str(), dest.c_str() ); + } + + return true; +} + +/* +==================== +idModelExport::ParseExportSection +==================== +*/ +int idModelExport::ParseExportSection( idParser &parser ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + idToken command; + idToken token; + idStr defaultCommands; + idLexer lex; + idStr temp; + idStr parms; + int count; + + // only export sections that match our export mask + if ( g_exportMask.GetString()[ 0 ] ) { + if ( parser.CheckTokenString( "{" ) ) { + parser.SkipBracedSection( false ); + return 0; + } + + parser.ReadToken( &token ); + if ( token.Icmp( g_exportMask.GetString() ) ) { + parser.SkipBracedSection(); + return 0; + } + parser.ExpectTokenString( "{" ); + } else if ( !parser.CheckTokenString( "{" ) ) { + // skip the export mask + parser.ReadToken( &token ); + parser.ExpectTokenString( "{" ); + } + + count = 0; + + lex.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + while( 1 ) { + + if ( !parser.ReadToken( &command ) ) { + parser.Error( "Unexpoected end-of-file" ); + break; + } + + if ( command == "}" ) { + break; + } + + if ( command == "options" ) { + parser.ParseRestOfLine( defaultCommands ); + } else if ( command == "addoptions" ) { + parser.ParseRestOfLine( temp ); + defaultCommands += " "; + defaultCommands += temp; + } else if ( ( command == "mesh" ) || ( command == "anim" ) || ( command == "camera" ) ) { + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Expected filename" ); + } + + temp = token; + parser.ParseRestOfLine( parms ); + + if ( defaultCommands.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), defaultCommands.c_str() ); + } + + if ( parms.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), parms.c_str() ); + } + + lex.LoadMemory( temp, temp.Length(), parser.GetFileName() ); + + Reset(); + if ( ParseOptions( lex ) ) { + if ( command == "mesh" ) { + dest.SetFileExtension( MD5_MESH_EXT ); + } else if ( command == "anim" ) { + dest.SetFileExtension( MD5_ANIM_EXT ); + } else if ( command == "camera" ) { + dest.SetFileExtension( MD5_CAMERA_EXT ); + } else { + dest.SetFileExtension( command ); + } + idStr back = commandLine; + sprintf( commandLine, "%s %s -dest %s -game %s%s", command.c_str(), src.c_str(), dest.c_str(), CD_BASEDIR, commandLine.c_str() ); + if ( ConvertMayaToMD5() ) { + count++; + } else { + parser.Warning( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + } + } + lex.FreeSource(); + } else { + parser.Error( "Unknown token: %s", command.c_str() ); + parser.SkipBracedSection( false ); + break; + } + } + + return count; +} + +/* +================ +idModelExport::ExportDefFile +================ +*/ +int idModelExport::ExportDefFile( const char *filename ) { + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + idToken token; + int count; + + count = 0; + + if ( !parser.LoadFile( filename ) ) { + gameLocal.Printf( "Could not load '%s'\n", filename ); + return 0; + } + + while( parser.ReadToken( &token ) ) { + if ( token == "export" ) { + count += ParseExportSection( parser ); + } else { + parser.ReadToken( &token ); + parser.SkipBracedSection(); + } + } + + return count; +} + +/* +================ +idModelExport::ExportModels +================ +*/ +int idModelExport::ExportModels( const char *pathname, const char *extension ) { + int count; + + count = 0; + + idFileList *files; + int i; + + if ( !CheckMayaInstall() ) { + // if Maya isn't installed, don't bother checking if we have anims to export + return 0; + } + + gameLocal.Printf( "--------- Exporting models --------\n" ); + if ( !g_exportMask.GetString()[ 0 ] ) { + gameLocal.Printf( " Export mask: '%s'\n", g_exportMask.GetString() ); + } + + count = 0; + + files = fileSystem->ListFiles( pathname, extension ); + for( i = 0; i < files->GetNumFiles(); i++ ) { + count += ExportDefFile( va( "%s/%s", pathname, files->GetFile( i ) ) ); + } + fileSystem->FreeFileList( files ); + + gameLocal.Printf( "...%d models exported.\n", count ); + gameLocal.Printf( "-----------------------------------\n" ); + + return count; +} diff --git a/source/mpgame/anim/Anim_Testmodel.cpp b/source/mpgame/anim/Anim_Testmodel.cpp new file mode 100644 index 0000000..8dc4deb --- /dev/null +++ b/source/mpgame/anim/Anim_Testmodel.cpp @@ -0,0 +1,940 @@ +/* +============================================================================= + + MODEL TESTING + +Model viewing can begin with either "testmodel " + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Extension will default to ".ase" if not specified. + +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. + + g_testModelRotate + g_testModelAnimate + g_testModelBlend + +============================================================================= +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idAnimatedEntity, idTestModel ) + EVENT( EV_FootstepLeft, idTestModel::Event_Footstep ) + EVENT( EV_FootstepRight, idTestModel::Event_Footstep ) +END_CLASS + +/* +================ +idTestModel::idTestModel +================ +*/ +idTestModel::idTestModel() { + head = NULL; + headAnimator = NULL; + anim = 0; + headAnim = 0; + starttime = 0; + animtime = 0; + mode = 0; + frame = 0; +} + +/* +================ +idTestModel::Save +================ +*/ +void idTestModel::Save( idSaveGame *savefile ) { +} + +/* +================ +idTestModel::Restore +================ +*/ +void idTestModel::Restore( idRestoreGame *savefile ) { + // FIXME: one day we may actually want to save/restore test models, but for now we'll just delete them + delete this; +} + +/* +================ +idTestModel::Spawn +================ +*/ +void idTestModel::Spawn( void ) { + idVec3 size; + idBounds bounds; + const char *headModel; + jointHandle_t joint; + idStr jointName; + idVec3 origin, modelOffset; + idMat3 axis; + const idKeyValue *kv; + copyJoints_t copyJoint; +// RAVEN BEGIN +// ddynerman: new heads + idAFAttachment *headEnt; +// RAVEN END + + if ( renderEntity.hModel && renderEntity.hModel->IsDefaultModel() && !animator.ModelDef() ) { + gameLocal.Warning( "Unable to create testmodel for '%s' : model defaulted", spawnArgs.GetString( "model" ) ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + mode = g_testModelAnimate.GetInteger(); + animator.RemoveOriginOffset( g_testModelAnimate.GetInteger() == 1 ); + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) ) { + spawnArgs.GetVector( "maxs", NULL, bounds[1] ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } + + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + // add the head model if it has one + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { +// RAVEN BEGIN +// ddynerman: new heads + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found for 'joint_head'", jointName.c_str() ); + } else { + // copy any sounds in case we have frame commands on the head + idDict args; + const idKeyValue *sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + args.Set( "classname", headModel ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idTestModel::Spawn() - Unknown head model '%s'\n", headModel ); + return; + } + headEnt->spawnArgs.Set( "classname", headModel ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + + headEnt->BindToJoint( this, joint, true ); + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->InitCopyJoints ( ); + + head = headEnt; +// RAVEN END + + headAnimator = head.GetEntity()->GetAnimator(); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + jointName = kv->GetKey(); + + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s'", jointName.c_str() ); + continue; + } + + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head", jointName.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + } + + // start any shader effects based off of the spawn time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + SetPhysics( &physicsObj ); + + // always keep updating so we can see the skeleton for the default pose + fl.forcePhysicsUpdate = true; + + gameLocal.Printf( "Added testmodel at origin = '%s', angles = '%s'\n", GetPhysics()->GetOrigin().ToString(), GetPhysics()->GetAxis().ToAngles().ToString() ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTestModel::~idTestModel +================ +*/ +idTestModel::~idTestModel() { + StopSound( SND_CHANNEL_ANY, false ); + if ( renderEntity.hModel ) { + gameLocal.Printf( "Removing testmodel %s\n", renderEntity.hModel->Name() ); + } else { + gameLocal.Printf( "Removing testmodel\n" ); + } + + if ( gameLocal.testmodel == this ) { + gameLocal.testmodel = NULL; + } + if ( head.GetEntity() ) { +// RAVEN BEGIN +// ddynerman: allow instant respawning of head + head.GetEntity()->SetName( va( "%s_oldhead", head.GetEntity()->name.c_str() ) ); +// RAVEN END + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + SetPhysics( NULL ); +} + +/* +=============== +idTestModel::Event_Footstep +=============== +*/ +void idTestModel::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idTestModel::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idTestModel::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +idTestModel::Think +================ +*/ +void idTestModel::Think( void ) { + idVec3 pos; + idMat3 axis; + idAngles ang; + int i; + frameBlend_t frameBlend = { 0 }; + + if ( thinkFlags & TH_THINK ) { + if ( anim && ( gameLocal.testmodel == this ) && ( mode != g_testModelAnimate.GetInteger() ) ) { + StopSound( SND_CHANNEL_ANY, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + } + switch( g_testModelAnimate.GetInteger() ) { + default: + case 0: + // cycle anim with origin reset + if ( animator.NumFrames( anim ) <= 1 ) { + // single frame animations end immediately, so just cycle it since it's the same result + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + } else { + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + animator.RemoveOriginOffset( false ); + break; + + case 1: + // cycle anim with fixed origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 2: + // cycle anim with continuous origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 3: +// RAVEN BEGIN + // frame by frame with continuous origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + + case 4: + // play anim once + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 5: +// RAVEN BEGIN + // frame by frame with fixed origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + } + + mode = g_testModelAnimate.GetInteger(); + } + + if ( ( mode == 0 ) && ( gameLocal.time >= starttime + animtime ) ) { + starttime = gameLocal.time; + StopSound( SND_CHANNEL_ANY, false ); + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + + if ( headAnimator ) { + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + idMat3 mat = head.GetEntity()->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= head.GetEntity()->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } + } + + // update rotation + RunPhysics(); + + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, idAngles( 0, g_testModelRotate.GetFloat() * 360.0f / 60.0f, 0 ), ang_zero ); + + idClipModel *clip = physicsObj.GetClipModel(); + if ( clip && animator.ModelDef() ) { + idVec3 neworigin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle( "origin" ); + animator.GetJointTransform( joint, gameLocal.time, neworigin, axis ); + neworigin = ( ( neworigin - animator.ModelDef()->GetVisualOffset() ) * physicsObj.GetAxis() ) + GetPhysics()->GetOrigin(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, neworigin, clip->GetAxis() ); +// RAVEN END + } + } + + UpdateAnimation(); + Present(); + + if ( ( gameLocal.testmodel == this ) && g_showTestModelFrame.GetInteger() && anim ) { + gameLocal.Printf( "^5 Anim: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n", animator.AnimFullName( anim ), animator.CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + animator.CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - animator.CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + if ( headAnim ) { + gameLocal.Printf( "^5 Head: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n\n", headAnimator->AnimFullName( headAnim ), headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + } else { + gameLocal.Printf( "\n\n" ); + } + } +} + +/* +================ +idTestModel::NextAnim +================ +*/ +void idTestModel::NextAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim++; + if ( anim >= animator.NumAnims() ) { + // anim 0 is no anim + anim = 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::PrevAnim +================ +*/ +void idTestModel::PrevAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + headAnim = 0; + anim--; + if ( anim < 0 ) { + anim = animator.NumAnims() - 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::NextFrame +================ +*/ +void idTestModel::NextFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame++; + if ( frame > animator.NumFrames( anim ) ) { + frame = 1; + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::PrevFrame +================ +*/ +void idTestModel::PrevFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame--; + if ( frame < 1 ) { + frame = animator.NumFrames( anim ); + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::TestAnim +================ +*/ +void idTestModel::TestAnim( const idCmdArgs &args ) { + idStr name; + int animNum; + const idAnim *newanim; + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: testanim \n" ); + return; + } + + newanim = NULL; + + name = args.Argv( 1 ); +#if 0 + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + idModelExport exporter; + exporter.ExportAnim( name ); + name.SetFileExtension( MD5_ANIM_EXT ); + md5anims[ 0 ] = animationLib.GetAnim( name ); + if ( md5anims[ 0 ] ) { + customAnim.SetAnim( animator.ModelDef(), name, name, 1, md5anims ); + newanim = &customAnim; + } + } else { + animNum = animator.GetAnim( name ); + } +#else + animNum = animator.GetAnim( name ); +#endif + + if ( !animNum ) { + gameLocal.Printf( "Animation '%s' not found.\n", name.c_str() ); + return; + } + + anim = animNum; + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + if ( !headAnim ) { + gameLocal.Printf( "Missing 'idle' anim for head.\n" ); + } + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + animname = name; + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +===================== +idTestModel::BlendAnim +===================== +*/ +void idTestModel::BlendAnim( const idCmdArgs &args ) { + int anim1; + int anim2; + + if ( args.Argc() < 4 ) { + gameLocal.Printf( "usage: testblend \n" ); + return; + } + + anim1 = gameLocal.testmodel->animator.GetAnim( args.Argv( 1 ) ); + if ( !anim1 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 1 ) ); + return; + } + + anim2 = gameLocal.testmodel->animator.GetAnim( args.Argv( 2 ) ); + if ( !anim2 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 2 ) ); + return; + } + + animname = args.Argv( 2 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim1, gameLocal.time, 0 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, FRAME2MS( atoi( args.Argv( 3 ) ) ) ); + + anim = anim2; + headAnim = 0; +} + +/*********************************************************************** + + Testmodel console commands + +***********************************************************************/ + +/* +================= +idTestModel::KeepTestModel_f + +Makes the current test model permanent, allowing you to place +multiple test models +================= +*/ +void idTestModel::KeepTestModel_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel.\n" ); + return; + } + + gameLocal.Printf( "modelDef %i kept\n", gameLocal.testmodel->renderEntity.hModel ); + + gameLocal.testmodel = NULL; +} + +/* +================= +idTestModel::TestSkin_f + +Sets a skin on an existing testModel +================= +*/ +void idTestModel::TestSkin_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "removing testSkin.\n" ); + gameLocal.testmodel->SetSkin( NULL ); + return; + } + + name = args.Argv( 1 ); + gameLocal.testmodel->SetSkin( declManager->FindSkin( name ) ); +} + +/* +================= +idTestModel::TestShaderParm_f + +Sets a shaderParm on an existing testModel +================= +*/ +void idTestModel::TestShaderParm_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() != 3 ) { + gameLocal.Printf( "USAGE: testShaderParm \n" ); + return; + } + + int parm = atoi( args.Argv( 1 ) ); + if ( parm < 0 || parm >= MAX_ENTITY_SHADER_PARMS ) { + gameLocal.Printf( "parmNum %i out of range\n", parm ); + return; + } + + float value; + if ( !stricmp( args.Argv( 2 ), "time" ) ) { + value = gameLocal.time * -0.001; + } else { + value = atof( args.Argv( 2 ) ); + } + + gameLocal.testmodel->SetShaderParm( parm, value ); +} + +/* +================= +idTestModel::TestModel_f + +Creates a static modelDef in front of the current position, which +can then be moved around +================= +*/ +void idTestModel::TestModel_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + const idDict * entityDef; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testmodel ) { + delete gameLocal.testmodel; + gameLocal.testmodel = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + entityDef = gameLocal.FindEntityDefDict( name, false ); + if ( entityDef ) { + dict = *entityDef; + } else { + if ( declManager->FindType( DECL_MODELDEF, name, false ) ) { + dict.Set( "model", name ); + } else { + // allow map models with underscore prefixes to be tested during development + // without appending an ase + if ( name[ 0 ] != '_' ) { + name.DefaultFileExtension( ".ase" ); + } + + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + idModelExport exporter; + exporter.ExportModel( name ); + name.SetFileExtension( MD5_MESH_EXT ); + } + + if ( !renderModelManager->CheckModel( name ) ) { + gameLocal.Printf( "Can't register model\n" ); + return; + } + dict.Set( "model", name ); + } + } + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "angle", va( "%f", player->viewAngles.yaw + 180.0f ) ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + gameLocal.testmodel = ( idTestModel * )gameLocal.SpawnEntityType( idTestModel::GetClassType(), &dict ); +// RAVEN END + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); +} + +/* +===================== +idTestModel::ArgCompletion_TestModel +===================== +*/ +void idTestModel::ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i, num; + + num = declManager->GetNumDecls( DECL_ENTITYDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_ENTITYDEF, i , false )->GetName() ); + } + num = declManager->GetNumDecls( DECL_MODELDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_MODELDEF, i , false )->GetName() ); + } + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", ".mb", NULL ); +} + +/* +===================== +idTestModel::TestParticleStopTime_f +===================== +*/ +void idTestModel::TestParticleStopTime_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + gameLocal.testmodel->UpdateVisuals(); +} + +/* +===================== +idTestModel::TestAnim_f +===================== +*/ +void idTestModel::TestAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->TestAnim( args ); +} + + +/* +===================== +idTestModel::ArgCompletion_TestAnim +===================== +*/ +void idTestModel::ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ) { + if ( gameLocal.testmodel ) { + idAnimator *animator = gameLocal.testmodel->GetAnimator(); + for( int i = 0; i < animator->NumAnims(); i++ ) { + callback( va( "%s %s", args.Argv( 0 ), animator->AnimFullName( i ) ) ); + } + } +} + +/* +===================== +idTestModel::TestBlend_f +===================== +*/ +void idTestModel::TestBlend_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->BlendAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextAnim_f +===================== +*/ +void idTestModel::TestModelNextAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextAnim( args ); +} + +/* +===================== +idTestModel::TestModelPrevAnim_f +===================== +*/ +void idTestModel::TestModelPrevAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextFrame_f +===================== +*/ +void idTestModel::TestModelNextFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextFrame( args ); +} + +/* +===================== +idTestModel::TestModelPrevFrame_f +===================== +*/ +void idTestModel::TestModelPrevFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevFrame( args ); +} diff --git a/source/mpgame/anim/Anim_Testmodel.h b/source/mpgame/anim/Anim_Testmodel.h new file mode 100644 index 0000000..3a9f189 --- /dev/null +++ b/source/mpgame/anim/Anim_Testmodel.h @@ -0,0 +1,71 @@ + +#ifndef __ANIM_TESTMODEL_H__ +#define __ANIM_TESTMODEL_H__ + +/* +============================================================================================== + + idTestModel + +============================================================================================== +*/ + +class idTestModel : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idTestModel ); + + idTestModel(); + ~idTestModel(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + + void NextAnim( const idCmdArgs &args ); + void PrevAnim( const idCmdArgs &args ); + void NextFrame( const idCmdArgs &args ); + void PrevFrame( const idCmdArgs &args ); + void TestAnim( const idCmdArgs &args ); + void BlendAnim( const idCmdArgs &args ); + + static void KeepTestModel_f( const idCmdArgs &args ); + static void TestModel_f( const idCmdArgs &args ); + static void ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestSkin_f( const idCmdArgs &args ); + static void TestShaderParm_f( const idCmdArgs &args ); + static void TestParticleStopTime_f( const idCmdArgs &args ); + static void TestAnim_f( const idCmdArgs &args ); + static void ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestBlend_f( const idCmdArgs &args ); + static void TestModelNextAnim_f( const idCmdArgs &args ); + static void TestModelPrevAnim_f( const idCmdArgs &args ); + static void TestModelNextFrame_f( const idCmdArgs &args ); + static void TestModelPrevFrame_f( const idCmdArgs &args ); + +private: +// RAVEN BEGIN +// ddynerman: new head system + idEntityPtr head; +// RAVEN END + idAnimator *headAnimator; + idAnim customAnim; + idPhysics_Parametric physicsObj; + idStr animname; + int anim; + int headAnim; + int mode; + int frame; + int starttime; + int animtime; + + idList copyJoints; + + virtual void Think( void ); + + void Event_Footstep( void ); +}; + +#endif /* !__ANIM_TESTMODEL_H__*/ diff --git a/source/mpgame/client/ClientAFEntity.cpp b/source/mpgame/client/ClientAFEntity.cpp new file mode 100644 index 0000000..f543971 --- /dev/null +++ b/source/mpgame/client/ClientAFEntity.cpp @@ -0,0 +1,696 @@ +//---------------------------------------------------------------- +// ClientAFEntity.cpp +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + +rvClientAFEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAnimatedClientEntity, rvClientAFEntity ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +rvClientAFEntity::rvClientAFEntity +================ +*/ +rvClientAFEntity::rvClientAFEntity( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); +} + + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +rvClientAFEntity::~rvClientAFEntity( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +rvClientAFEntity::Spawn +================ +*/ +void rvClientAFEntity::Spawn( void ) { + spawnOrigin = worldOrigin; + spawnAxis = worldAxis; + nextSoundTime = 0; + + //if ( !LoadAF() ) { + // gameLocal.Error( "Couldn't load af file on entity %d", entityNumber ); + //} + + SetCombatModel(); + + //af.GetPhysics()->PutToRest(); + //if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + // af.GetPhysics()->Activate(); + //} +} + +/* +================ +rvClientAFEntity::LoadAF +================ +*/ +bool rvClientAFEntity::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + + idDict args = gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs; + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = spawnArgs; + + if ( !af.Load( gameLocal.entities[ ENTITYNUM_CLIENT ], fileName ) ) { + gameLocal.Error( "rvClientAFEntity::LoadAF: Couldn't load af file '%s' on client entity %d", fileName.c_str(), entityNumber ); + } + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = args; + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +rvClientAFEntity::Think +================ +*/ +void rvClientAFEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + + Present(); + LinkCombat(); +} + +/* +================ +rvClientAFEntity::BodyForClipModelId +================ +*/ +int rvClientAFEntity::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +rvClientAFEntity::SaveState +================ +*/ +void rvClientAFEntity::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +rvClientAFEntity::LoadState +================ +*/ +void rvClientAFEntity::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +rvClientAFEntity::AddBindConstraints +================ +*/ +void rvClientAFEntity::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +rvClientAFEntity::RemoveBindConstraints +================ +*/ +void rvClientAFEntity::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +rvClientAFEntity::GetImpactInfo +================ +*/ +void rvClientAFEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + rvClientEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFEntity::ApplyImpulse +================ +*/ +void rvClientAFEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + rvClientEntity::ApplyImpulse( ent, id, point, impulse, splash ); + } +} + +/* +================ +rvClientAFEntity::AddForce +================ +*/ +void rvClientAFEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + rvClientEntity::AddForce( ent, id, point, force ); + } +} + +bool rvClientAFEntity::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + // stubbed out + return true; +} + +/* +================ +rvClientAFEntity::Collide +================ +*/ +bool rvClientAFEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + // RAVEN BEGIN + // jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } + // RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +rvClientAFEntity::GetPhysicsToVisualTransform +================ +*/ +bool rvClientAFEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return rvClientModel::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +rvClientAFEntity::UpdateAnimationControllers +================ +*/ +bool rvClientAFEntity::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +rvClientAFEntity::SetCombatModel +================ +*/ +void rvClientAFEntity::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } +} + +/* +================ +rvClientAFEntity::GetCombatModel +================ +*/ +idClipModel *rvClientAFEntity::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFEntity::SetCombatContents +================ +*/ +void rvClientAFEntity::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +rvClientAFEntity::LinkCombat +================ +*/ +void rvClientAFEntity::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFEntity::UnlinkCombat +================ +*/ +void rvClientAFEntity::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +rvClientAFEntity::FreeEntityDef +================ +*/ +void rvClientAFEntity::FreeEntityDef( void ) { + UnlinkCombat(); + rvClientEntity::FreeEntityDef(); +} + +/* +================ +rvClientAFEntity::GetNoPlayerImpactFX +================ +*/ +bool rvClientAFEntity::GetNoPlayerImpactFX( void ) { + // stubbed out + return false; +} + + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientAFEntity, rvClientAFAttachment ) +END_CLASS + +/* +===================== +rvClientAFAttachment::rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::rvClientAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; +} + +/* +===================== +rvClientAFAttachment::~rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::~rvClientAFAttachment( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +===================== +rvClientAFAttachment::Spawn +===================== +*/ +void rvClientAFAttachment::Spawn( void ) { + idleAnim = animator.GetAnim( "idle" ); +} + +/* +===================== +rvClientAFAttachment::InitCopyJoints +===================== +*/ +void rvClientAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on client entity %d", jointName, entityNumber ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %d", kv->GetValue().c_str(), entityNumber ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +rvClientAFAttachment::CopyJointsFromBody +===================== +*/ +void rvClientAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +rvClientAFAttachment::SetBody +===================== +*/ +void rvClientAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +rvClientAFAttachment::ClearBody +===================== +*/ +void rvClientAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +rvClientAFAttachment::GetBody +===================== +*/ +idEntity *rvClientAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void rvClientAFAttachment::Hide( void ) { + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void rvClientAFAttachment::Show( void ) { + LinkCombat(); +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void rvClientAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +rvClientAFAttachment::GetImpactInfo +================ +*/ +void rvClientAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + rvClientAFAttachment::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFAttachment::CanPlayImpactEffect +================ +*/ +bool rvClientAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + return rvClientAFEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +rvClientAFAttachment::ApplyImpulse +================ +*/ +void rvClientAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + rvClientAFEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +rvClientAFAttachment::AddForce +================ +*/ +void rvClientAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + rvClientAFEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +rvClientAFAttachment::PlayIdleAnim +================ +*/ +void rvClientAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +} + +/* +================ +rvClientAFAttachment::Think +================ +*/ +void rvClientAFAttachment::Think( void ) { + rvClientAFEntity::Think(); +} + +/* +================ +rvClientAFAttachment::UpdateAnimationControllers +================ +*/ +bool rvClientAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return rvClientAFEntity::UpdateAnimationControllers( ); +} + +/* +================ +rvClientAFAttachment::SetCombatModel +================ +*/ +void rvClientAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } + combatModel->SetOwner( body ); +} + +/* +================ +rvClientAFAttachment::GetCombatModel +================ +*/ +idClipModel *rvClientAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFAttachment::LinkCombat +================ +*/ +void rvClientAFAttachment::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFAttachment::UnlinkCombat +================ +*/ +void rvClientAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} diff --git a/source/mpgame/client/ClientAFEntity.h b/source/mpgame/client/ClientAFEntity.h new file mode 100644 index 0000000..6894533 --- /dev/null +++ b/source/mpgame/client/ClientAFEntity.h @@ -0,0 +1,126 @@ +//---------------------------------------------------------------- +// ClientAFEntity.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_AFENTITY_H__ +#define __GAME_CLIENT_AFENTITY_H__ + +/* +=============================================================================== + +rvClientAFEntity - a regular idAFEntity_Base spawned client-side + +=============================================================================== +*/ + +class rvClientAFEntity : public rvAnimatedClientEntity { +public: + CLASS_PROTOTYPE( rvClientAFEntity ); + + rvClientAFEntity( void ); + virtual ~rvClientAFEntity( void ); + + void Spawn( void ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeEntityDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound +}; + + +/* +=============================================================================== + +rvClientAFAttachment - a regular idAFAttachment spawned client-side - links to an + idAnimatedEntity rather than rvClientAnimatedEntity + +=============================================================================== +*/ +class rvClientAFAttachment : public rvClientAFEntity { +public: + CLASS_PROTOTYPE( rvClientAFAttachment ); + + rvClientAFAttachment( void ); + virtual ~rvClientAFAttachment( void ); + + void Spawn( void ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + +protected: + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + idList copyJoints; // copied from the body animation to the head model +}; + +#endif diff --git a/source/mpgame/client/ClientEffect.cpp b/source/mpgame/client/ClientEffect.cpp new file mode 100644 index 0000000..b734c13 --- /dev/null +++ b/source/mpgame/client/ClientEffect.cpp @@ -0,0 +1,497 @@ +//---------------------------------------------------------------- +// ClientEffect.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientEffect.h" + +/* +=============================================================================== + +rvClientEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEntity, rvClientEffect ) +END_CLASS + +/* +================ +rvClientEffect::rvClientEffect +================ +*/ +rvClientEffect::rvClientEffect ( void ) { + Init( NULL ); + Spawn(); +} + +rvClientEffect::rvClientEffect ( const idDecl *effect ) { + Init( effect ); + Spawn(); +} + +void rvClientEffect::Init ( const idDecl *effect ) { + memset( &renderEffect, 0, sizeof( renderEffect ) ); + + renderEffect.declEffect = effect; + renderEffect.startTime = -1.0f; + renderEffect.referenceSoundHandle = -1; + effectDefHandle = -1; + endOriginJoint = INVALID_JOINT; +} + +/* +================ +rvClientEffect::~rvClientEffect +================ +*/ +rvClientEffect::~rvClientEffect( void ) { + FreeEffectDef( ); + // Prevent a double free of a SoundEmitter resulting in broken in-game sounds, when + // the second free releases a emitter that was reallocated to another sound. rvBSE caches + // this referenceSoundHandle and rvBSE::Destroy also frees the sound. rvBSE::Destroy + // is triggered by FreeEffectDef. Disable this free and let rvBSE do the releasing + + // Actually, the freeing should be done here and not in BSE. The client effect allocates and + // maintains the handle. Handling this here also allows emitters to be recycled for sparse + // looping effects. + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, renderEffect.referenceSoundHandle, true ); + renderEffect.referenceSoundHandle = -1; +} + +/* +================ +rvClientEffect::GetEffectIndex +================ +*/ +int rvClientEffect::GetEffectIndex( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->Index() ); + } + return( -1 ); +} + +/* +================ +rvClientEffect::GetEffectName +================ +*/ +const char *rvClientEffect::GetEffectName( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->GetName() ); + } + return( "unknown" ); +} + +/* +================ +rvClientEffect::FreeEffectDef +================ +*/ +void rvClientEffect::FreeEffectDef ( void ) { + if ( effectDefHandle != -1 && gameRenderWorld ) { + gameRenderWorld->FreeEffectDef( effectDefHandle ); + } + effectDefHandle = -1; +} + +/* +================ +rvClientEffect::UpdateBind +================ +*/ +void rvClientEffect::UpdateBind ( void ) { + rvClientEntity::UpdateBind ( ); + + renderEffect.origin = worldOrigin; + + if ( endOriginJoint != INVALID_JOINT && bindMaster ) { + idMat3 axis; + idVec3 endOrigin; + idVec3 dir; + + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( endOriginJoint, gameLocal.time, endOrigin, axis ); + SetEndOrigin ( endOrigin ); + + dir = (endOrigin - worldOrigin); + dir.Normalize (); + renderEffect.axis = dir.ToMat3 ( ); + } else { + renderEffect.axis = worldAxis; + } + + if ( bindMaster ) { + renderEffect.groupID = bindMaster->entityNumber + 1; + } else { + renderEffect.groupID = 0; + } +} + +/* +================ +rvClientEffect::Think +================ +*/ +void rvClientEffect::Think ( void ) { + // If we are bound to an entity that is now hidden we can just not render if looping, otherwise stop the effect + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + if ( renderEffect.loop ) { + return; + } + Stop ( ); + } + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_EFFECT); +// RAVEN END + // If there is a valid effect handle and we havent started playing + // and effect yet then see if its time + if( effectDefHandle < 0 && renderEffect.declEffect ) { + if( renderEffect.startTime >= 0.0f ) { + // Make sure our origins are all straight before starting the effect + UpdateBind(); + renderEffect.attenuation = 1.0f; + + // if the rendereffect needs sound give it an emitter. + if( renderEffect.referenceSoundHandle <= 0 ) { + if( gameRenderWorld->EffectDefHasSound( &renderEffect) ) + { + renderEffect.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } else { + renderEffect.referenceSoundHandle = -1; + } + } + // Add the render effect + effectDefHandle = gameRenderWorld->AddEffectDef( &renderEffect, gameLocal.time ); + if ( effectDefHandle < 0 ) { + PostEventMS( &EV_Remove, 0 ); + } + } + + return; + } + + // If we lost our effect def handle then just remove ourself + if( effectDefHandle < 0 ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Dont do anything else if its not a new client frame + if( !gameLocal.isNewFrame ) { + return; + } + + // Check to see if the player can possibly see the effect or not + renderEffect.inConnectedArea = true; + if( bindMaster ) { + renderEffect.inConnectedArea = gameLocal.InPlayerConnectedArea( bindMaster ); + } + + // Update the bind + UpdateBind(); + + // Update the actual render effect now + if( gameRenderWorld->UpdateEffectDef( effectDefHandle, &renderEffect, gameLocal.time ) ) { + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + return; + } +} + +/* +================ +rvClientEffect::Play +================ +*/ +bool rvClientEffect::Play ( int _startTime, bool _loop, const idVec3& endOrigin ) { + if ( !renderEffect.declEffect ) { + return false; + } + + // Initialize the render entity + if ( bindMaster ) { + renderEntity_t* renderEnt = bindMaster->GetRenderEntity ( ); + assert( renderEnt ); + + // Copy suppress values from parent entity + renderEffect.allowSurfaceInViewID = renderEnt->allowSurfaceInViewID; + renderEffect.suppressSurfaceInViewID = renderEnt->suppressSurfaceInViewID; + renderEffect.weaponDepthHackInViewID = renderEnt->weaponDepthHackInViewID; + } + + renderEffect.shaderParms[SHADERPARM_RED] = 1.0f; + renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f; + renderEffect.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + renderEffect.hasEndOrigin = ( endOrigin != vec3_origin ); + renderEffect.endOrigin = endOrigin; + renderEffect.loop = _loop; + + assert( effectDefHandle < 0 ); + + renderEffect.startTime = MS2SEC( _startTime ); + + return true; +} + +/* +================ +rvClientEffect::Stop +================ +*/ +void rvClientEffect::Stop ( bool destroyParticles ) { + if( effectDefHandle < 0 ) { + renderEffect.startTime = -1.0f; + renderEffect.declEffect = NULL; + return; + } + + if ( destroyParticles ) { + // Clear the effect index to make sure the effect isnt started again. This is + // an indirect way of making the effect not think + renderEffect.declEffect = NULL; + + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + gameRenderWorld->StopEffectDef( effectDefHandle ); + // this will ensure the effect doesn't re-up when loaded from a save. + renderEffect.startTime = -1.0f; + Unbind ( ); + } +} + +/* +================ +rvClientEffect::Restart +================ +*/ +void rvClientEffect::Restart ( void ) { + FreeEffectDef ( ); + + if ( renderEffect.loop ) { + Play ( gameLocal.time, true, renderEffect.endOrigin ); + } +} + +/* +================ +rvClientEffect::Attenuate +================ +*/ +void rvClientEffect::Attenuate ( float attenuation ) { + renderEffect.attenuation = attenuation; +} + +/* +================ +rvClientEffect::GetDuration +================ +*/ +float rvClientEffect::GetDuration( void ) const { + if( effectDefHandle < 0 ) { + return 0.0f; + } + + return bse->EffectDuration( gameRenderWorld->GetEffectDef( effectDefHandle ) ); +} + +/* +================ +rvClientEffect::DrawDebugInfo +================ +*/ +void rvClientEffect::DrawDebugInfo ( void ) const { + rvClientEntity::DrawDebugInfo ( ); + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + if( !renderEffect.declEffect ) { + return; + } + + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + gameRenderWorld->DrawText ( renderEffect.declEffect->GetName(), worldOrigin + up, 0.1f, colorWhite, axis, 1 ); +} + +/* +================ +rvClientEffect::Save +================ +*/ +void rvClientEffect::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEffect( renderEffect ); + savefile->WriteJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::Restore +================ +*/ +void rvClientEffect::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEffect( renderEffect ); + effectDefHandle = -1; + savefile->ReadJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::FreeEntityDef +================ +*/ +void rvClientEffect::FreeEntityDef( void ) { + FreeEffectDef(); +} + +/* +=============================================================================== + +rvClientCrawlEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEffect, rvClientCrawlEffect ) +END_CLASS + +/* +================ +rvClientCrawlEffect::rvClientCrawlEffect +================ +*/ +rvClientCrawlEffect::rvClientCrawlEffect ( void ) { +} + +rvClientCrawlEffect::rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int _crawlTime, idList* joints ) : rvClientEffect ( effect ) { + int i; + + // Crawl effects require an animated entity + crawlEnt = dynamic_cast(ent); + if ( !crawlEnt) { + return; + } + + // Specific joint list provided? + if ( joints && joints->Num () ) { + crawlJoints.Clear ( ); + for ( i = 0; i < joints->Num(); i ++ ) { + crawlJoints.Append ( (*joints)[i] ); + } + } else { + // Use only parent joints and skip joint zero which is presumed to be the origin + for ( i = ent->GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != ent->GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + crawlJoints.Append ( (jointHandle_t)i ); + } + } + } + + //no joints? abort! + if ( !crawlJoints.Num() ) { + return; + } + + jointStart = gameLocal.random.RandomInt ( crawlJoints.Num() ); + crawlDir = gameLocal.random.RandomInt ( 2 ) > 0 ? 1 : -1; + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + crawlTime = _crawlTime; + nextCrawl = gameLocal.time + crawlTime; +} + +/* +================ +rvClientCrawlEffect::Think +================ +*/ +void rvClientCrawlEffect::Think ( void ) { + + // If there is no crawl entity or no crawl joints then just free ourself + if ( !crawlEnt || !crawlJoints.Num() ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Move to the next joint if its time + if ( gameLocal.time > nextCrawl ) { + jointStart = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + nextCrawl = gameLocal.time + crawlTime; + } + + idVec3 offsetStart; + idVec3 offsetEnd; + idVec3 dir; + idMat3 axis; + + // Get the start origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointStart], gameLocal.time, offsetStart, axis ); + SetOrigin ( offsetStart ); + + // Get the end origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointEnd], gameLocal.time, offsetEnd, axis ); + SetEndOrigin ( offsetEnd ); + + // Update the axis to point at the bone + dir = offsetEnd - offsetStart; + dir.Normalize(); + SetAxis ( dir.ToMat3( ) ); + + rvClientEffect::Think ( ); +} + +/* +================ +rvClientCrawlEffect::Save +================ +*/ +void rvClientCrawlEffect::Save( idSaveGame *savefile ) const { + savefile->WriteInt( crawlJoints.Num() ); + for( int ix = 0; ix < crawlJoints.Num(); ++ix ) { + savefile->WriteJoint( crawlJoints[ix] ); + } + + savefile->WriteInt( crawlTime ); + savefile->WriteInt( nextCrawl ); + savefile->WriteInt( jointStart ); + savefile->WriteInt( jointEnd ); + savefile->WriteInt( crawlDir ); + crawlEnt.Save( savefile ); +} + +/* +================ +rvClientCrawlEffect::Restore +================ +*/ +void rvClientCrawlEffect::Restore( idRestoreGame *savefile ) { + int numJoints = 0; + savefile->ReadInt( numJoints ); + crawlJoints.SetNum( numJoints ); + for( int ix = 0; ix < numJoints; ++ix ) { + savefile->ReadJoint( crawlJoints[ix] ); + } + + savefile->ReadInt( crawlTime ); + savefile->ReadInt( nextCrawl ); + savefile->ReadInt( jointStart ); + savefile->ReadInt( jointEnd ); + savefile->ReadInt( crawlDir ); + crawlEnt.Restore( savefile ); +} diff --git a/source/mpgame/client/ClientEffect.h b/source/mpgame/client/ClientEffect.h new file mode 100644 index 0000000..58d600e --- /dev/null +++ b/source/mpgame/client/ClientEffect.h @@ -0,0 +1,102 @@ +//---------------------------------------------------------------- +// ClientEffect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_EFFECT_H__ +#define __GAME_CLIENT_EFFECT_H__ + +class rvClientEffect : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientEffect ); + + rvClientEffect( void ); + rvClientEffect( const idDecl *effect ); + virtual ~rvClientEffect( void ); + + virtual void Think ( void ); + virtual void DrawDebugInfo ( void ) const; + virtual void FreeEntityDef ( void ); + virtual void UpdateBind ( void ); + + bool Play ( int startTime, bool loop = false, const idVec3& origin = vec3_origin ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + int GetEffectIndex ( void ); + const char * GetEffectName ( void ); + + void Attenuate ( float attenuation ); + + float GetDuration ( void ) const; + renderEffect_t* GetRenderEffect ( void ) { return &renderEffect; } + + void SetEndOrigin ( const idVec3& endOrigin ); + void SetEndOrigin ( jointHandle_t joint ) { endOriginJoint = joint; } + + void SetGravity ( const idVec3& gravity ) { renderEffect.gravity = gravity; } + + void SetColor ( const idVec4& color ); + void SetBrightness ( float brightness ) { renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = brightness; } + void SetAmbient ( bool in ) { renderEffect.ambient = in; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +protected: + + void Init ( const idDecl *effect ); + void FreeEffectDef ( void ); + + renderEffect_t renderEffect; + int effectDefHandle; + jointHandle_t endOriginJoint; +}; + +ID_INLINE void rvClientEffect::SetEndOrigin ( const idVec3& endOrigin ) { + renderEffect.endOrigin = endOrigin; + renderEffect.hasEndOrigin = !(endOrigin == vec3_origin); +} + +ID_INLINE void rvClientEffect::SetColor ( const idVec4& color ) { + renderEffect.shaderParms[SHADERPARM_RED] = color[0]; + renderEffect.shaderParms[SHADERPARM_GREEN] = color[1]; + renderEffect.shaderParms[SHADERPARM_BLUE] = color[2]; + renderEffect.shaderParms[SHADERPARM_ALPHA] = color[3]; +} + +//---------------------------------------------------------------- +// rvClientCrawlEffect +//---------------------------------------------------------------- + +class idAnimatedEntity; + +class rvClientCrawlEffect : public rvClientEffect { +public: + + CLASS_PROTOTYPE( rvClientCrawlEffect ); + + rvClientCrawlEffect ( void ); + rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int crawlTime, idList* joints = NULL ); + ~rvClientCrawlEffect ( void ) {} + + virtual void Think ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + idList crawlJoints; + int crawlTime; + int nextCrawl; + int jointStart; + int jointEnd; + int crawlDir; + idEntityPtr crawlEnt; +}; + +typedef rvClientEntityPtr rvClientEffectPtr; + +#endif // __GAME_CLIENT_EFFECT_H__ diff --git a/source/mpgame/client/ClientEntity.cpp b/source/mpgame/client/ClientEntity.cpp new file mode 100644 index 0000000..8929907 --- /dev/null +++ b/source/mpgame/client/ClientEntity.cpp @@ -0,0 +1,651 @@ +//---------------------------------------------------------------- +// ClientEntity.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, rvClientEntity ) +END_CLASS + +/* +================ +rvClientEntity::rvClientEntity +================ +*/ +rvClientEntity::rvClientEntity( void ) { + bindMaster = NULL; + entityNumber = -1; + + bindOrigin.Zero(); + bindAxis.Identity(); + bindJoint = INVALID_JOINT; + bindOrientated = false; + + memset( &refSound, 0, sizeof(refSound) ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::~rvClientEntity +================ +*/ +rvClientEntity::~rvClientEntity( void ) { + Unbind(); + gameLocal.UnregisterClientEntity( this ); + + // Free sound emitter + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::Spawn +================ +*/ +void rvClientEntity::Spawn( void ) { + idVec3 origin; + idMat3 axis; + + gameLocal.RegisterClientEntity( this ); + + spawnNode.SetOwner( this ); + bindNode.SetOwner( this ); + + origin = spawnArgs.GetVector( "origin", "0 0 0" ); + axis = spawnArgs.GetMatrix( "axis", "1 0 0 0 1 0 0 0 1" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); +} + +/* +================ +rvClientEntity::Present +================ +*/ +void rvClientEntity::Present ( void ) { +} + +/* +================ +rvClientEntity::FreeEntityDef +================ +*/ +void rvClientEntity::FreeEntityDef ( void ) { +} + +/* +================ +rvClientEntity::Think +================ +*/ +void rvClientEntity::Think ( void ) { + UpdateBind(); + UpdateSound(); + Present(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Bind ( idEntity* master, jointHandle_t joint, bool isOrientated ) { + Unbind(); + + if ( joint != INVALID_JOINT && !dynamic_cast(master) ) { + gameLocal.Warning( "rvClientEntity::Bind: entity '%s' cannot support skeletal models.", master->GetName() ); + joint = INVALID_JOINT; + } + + bindMaster = master; + bindJoint = joint; + bindOrigin = worldOrigin; + bindAxis = worldAxis; + + bindNode.AddToEnd ( bindMaster->clientEntities ); + + bindOrientated = isOrientated; + if( physics ) { + physics->SetMaster( bindMaster, bindOrientated ); + } + + UpdateBind(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Unbind ( void ) { + if ( !bindMaster ) { + return; + } + + bindMaster = NULL; + bindNode.Remove ( ); +} + +/* +================ +rvClientEntity::SetOrigin +================ +*/ +void rvClientEntity::SetOrigin( const idVec3& origin ) { + if ( bindMaster ) { + bindOrigin = origin; + } else { + worldOrigin = origin; + } +} + +/* +================ +rvClientEntity::SetAxis +================ +*/ +void rvClientEntity::SetAxis( const idMat3& axis ) { + if ( bindMaster ) { + bindAxis = axis * bindMaster->GetRenderEntity()->axis.Transpose(); + } else { + worldAxis = axis; + } +} + +/* +================ +rvClientEntity::UpdateBind +================ +*/ +void rvClientEntity::UpdateBind ( void ) { + if ( !bindMaster ) { + return; + } + + if ( bindJoint != INVALID_JOINT ) { + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( bindJoint, gameLocal.time, worldOrigin, worldAxis ); + } else { + bindMaster->GetPosition( worldOrigin, worldAxis ); + //if ( !bindMaster->GetPhysicsToVisualTransform( worldOrigin, worldAxis ) ) { + // bindMaster->GetPosition( worldOrigin, worldAxis ); + //} + } + + worldOrigin += (bindOrigin * worldAxis); + worldAxis = bindAxis * worldAxis; +} + +/* +================ +rvClientEntity::IsClient +================ +*/ +bool rvClientEntity::IsClient ( void ) const { + return true; +} + +/* +================ +rvClientEntity::DrawDebugInfo +================ +*/ +void rvClientEntity::DrawDebugInfo ( void ) const { + idBounds bounds ( idVec3(-8,-8,-8), idVec3(8,8,8) ); + + gameRenderWorld->DebugBounds ( colorGreen, bounds, worldOrigin ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText ( GetClassname ( ), worldOrigin, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } +} + +/* +================ +rvClientEntity::UpdateSound +================ +*/ +void rvClientEntity::UpdateSound( void ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } +} + +/* +================ +rvClientEntity::SetSoundVolume +================ +*/ +void rvClientEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + + +/* +================ +rvClientEntity::StartSound +================ +*/ +bool rvClientEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags ); +} + + +/* +================ +rvClientEntity::StartSoundShader +================ +*/ +int rvClientEntity::StartSoundShader ( const idSoundShader* shader, const s_channelType channel, int soundShaderFlags ) { + if ( !shader ) { + return 0; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return( 0 ); + } + + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + return emitter->StartSound( shader, channel, gameLocal.random.RandomFloat(), soundShaderFlags ); +} + +/* +================ +rvClientEntity::Size + +Returns Returns memory size of an rvClientEntity. +================ +*/ + +size_t rvClientEntity::Size ( void ) const { + return sizeof( rvClientEntity ); +} + +/* +================ +rvClientEntity::Save +================ +*/ +void rvClientEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->WriteVec3( worldOrigin ); + savefile->WriteVec3( worldVelocity ); + savefile->WriteMat3( worldAxis ); + + bindMaster.Save( savefile ); + savefile->WriteVec3( bindOrigin ); + savefile->WriteMat3( bindAxis ); + savefile->WriteJoint( bindJoint ); + + savefile->WriteRefSound( refSound ); +} + +/* +================ +rvClientEntity::Restore +================ +*/ +void rvClientEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->ReadVec3( worldOrigin ); + savefile->ReadVec3( worldVelocity ); + savefile->ReadMat3( worldAxis ); + + bindMaster.Restore( savefile ); + savefile->ReadVec3( bindOrigin ); + savefile->ReadMat3( bindAxis ); + savefile->ReadJoint( bindJoint ); + + savefile->ReadRefSound( refSound ); +} + +/* +================ +rvClientEntity::RunPhysics +================ +*/ +void rvClientEntity::RunPhysics ( void ) { + idPhysics* physics = GetPhysics( ); + if( !physics ) { + return; + } + + rvClientPhysics* clientPhysics = (rvClientPhysics*)gameLocal.entities[ENTITYNUM_CLIENT]; + static_cast( clientPhysics )->currentEntityNumber = entityNumber; + + // order important: 1) set client physics bind master to client ent's bind master + // 2) set physics to client ent's physics, which sets physics + // master to client ent's master + // 3) set client physics origin to client ent origin, depends on + // proper bind master from 1 + clientPhysics->PushBindInfo( bindMaster, bindJoint, bindOrientated ); + clientPhysics->SetPhysics( physics ); + clientPhysics->PushOriginInfo( bindMaster ? bindOrigin : worldOrigin, bindMaster ? bindAxis : worldAxis ); + + physics->Evaluate ( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + worldOrigin = physics->GetOrigin(); + worldVelocity = physics->GetLinearVelocity(); + worldAxis = physics->GetAxis(); + + // order important: 1) restore previous bind master + // 2) reset physics with previous bind master + // 3) reset origin with previous bind master + clientPhysics->PopBindInfo(); + clientPhysics->SetPhysics( NULL ); + clientPhysics->PopOriginInfo(); + + UpdateAnimationControllers(); +} + +/* +================ +rvClientEntity::GetPhysics +================ +*/ +idPhysics* rvClientEntity::GetPhysics ( void ) const { + return physics; +} + +/* +================ +rvClientEntity::Collide +================ +*/ +bool rvClientEntity::Collide ( const trace_t &collision, const idVec3 &velocity ) { + return false; +} + +/* +================ +rvClientEntity::GetImpactInfo +================ +*/ +void rvClientEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +rvClientEntity::ApplyImpulse +================ +*/ +void rvClientEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +rvClientEntity::AddForce +================ +*/ +void rvClientEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +rvClientEntity::UpdateAnimationControllers +================ +*/ +bool rvClientEntity::UpdateAnimationControllers( void ) { + return false; +} + +/* +================ +rvClientEntity::InitDefaultPhysics +================ +*/ +void rvClientEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( temp ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on client entity '%d'", bounds[0].ToString(), bounds[1].ToString(), entityNumber ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on client entity '%d'", size.ToString(), entityNumber ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); + // RAVEN BEGIN + // bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); + // RAVEN END + } else { + trm.SetupBox( bounds ); + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( trm ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { + // RAVEN BEGIN + // jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( gameLocal.entities[ENTITYNUM_CLIENT] ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; + + // by default no collision + physics->SetContents( 0 ); +} + + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvClientPhysics ) +END_CLASS + +/* +===================== +rvClientPhysics::Spawn +===================== +*/ +void rvClientPhysics::Spawn( void ) { + pushedOrientated = false; +} + +/* +===================== +rvClientPhysics::Collide +===================== +*/ +bool rvClientPhysics::Collide( const trace_t &collision, const idVec3 &velocity ) { + assert ( currentEntityNumber >= 0 && currentEntityNumber < MAX_CENTITIES ); + + rvClientEntity* cent; + cent = gameLocal.clientEntities [ currentEntityNumber ]; + if ( cent ) { + return cent->Collide ( collision, velocity ); + } + + return false; +} + +/* +===================== +rvClientPhysics::PushBindInfo +===================== +*/ +void rvClientPhysics::PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ) { + pushedBindJoint = joint; + pushedBindMaster = master; + pushedOrientated = fl.bindOrientated; + + bindMaster = master; + bindJoint = joint; + fl.bindOrientated = orientated; +} + +/* +===================== +rvClientPhysics::PopBindInfo +===================== +*/ +void rvClientPhysics::PopBindInfo( void ) { + bindMaster = pushedBindMaster; + bindJoint = pushedBindJoint; + fl.bindOrientated = pushedOrientated; +} + +/* +===================== +rvClientPhysics::PushOriginInfo +===================== +*/ +void rvClientPhysics::PushOriginInfo( const idVec3& origin, const idMat3& axis ) { + if( !GetPhysics() ) { + return; + } + + pushedOrigin = GetPhysics()->GetOrigin(); + pushedAxis = GetPhysics()->GetAxis(); + + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( axis ); +} + +/* +===================== +rvClientPhysics::PopOriginInfo +===================== +*/ +void rvClientPhysics::PopOriginInfo( void ) { + if( !GetPhysics() ) { + return; + } + + GetPhysics()->SetOrigin( pushedOrigin ); + GetPhysics()->SetAxis( pushedAxis ); +} diff --git a/source/mpgame/client/ClientEntity.h b/source/mpgame/client/ClientEntity.h new file mode 100644 index 0000000..f0b8a99 --- /dev/null +++ b/source/mpgame/client/ClientEntity.h @@ -0,0 +1,219 @@ +//---------------------------------------------------------------- +// ClientEntity.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_ENTITY_H__ +#define __GAME_CLIENT_ENTITY_H__ + +class rvClientEntity : public idClass { +public: + + ABSTRACT_PROTOTYPE( rvClientEntity ); + + rvClientEntity ( void ); + + virtual ~rvClientEntity ( void ); + + void Spawn ( void ); + + virtual void Present ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); + const idVec3& GetOrigin ( void ); + const idMat3& GetAxis ( void ); + + void Bind ( idEntity* master, jointHandle_t joint = INVALID_JOINT, bool isOrientated = false ); + void Unbind ( void ); + + virtual bool IsClient ( void ) const; + virtual void DrawDebugInfo ( void ) const; + + void SetSoundVolume( float volume ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + int StartSoundShader ( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags ); + + // I'm guessing someone may decide to derive from rvClientEntity, so this virtual. + virtual size_t Size ( void ) const; + + virtual void FreeEntityDef ( void ); + + const idEntityPtr& GetBindMaster( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetImpactInfo ( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ); + virtual void AddForce ( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool UpdateAnimationControllers( void ); + + void InitDefaultPhysics ( const idVec3 &origin, const idMat3 &axis ); +protected: + + void RunPhysics ( void ); + + virtual void UpdateBind ( void ); + void UpdateSound ( void ); + +public: + + int entityNumber; + + idLinkList spawnNode; + idLinkList bindNode; + + idDict spawnArgs; + +protected: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics* physics; + + idVec3 worldOrigin; + idVec3 worldVelocity; + idMat3 worldAxis; + + idEntityPtr bindMaster; + idVec3 bindOrigin; + idMat3 bindAxis; + jointHandle_t bindJoint; + bool bindOrientated; + refSound_t refSound; +}; + +ID_INLINE const idVec3& rvClientEntity::GetOrigin ( void ) { + return worldOrigin; +} + +ID_INLINE const idMat3& rvClientEntity::GetAxis ( void ) { + return worldAxis; +} + +ID_INLINE const idEntityPtr& rvClientEntity::GetBindMaster( void ) const { + return bindMaster; +} + +//============================================================================ + +template< class type > +class rvClientEntityPtr { +public: + rvClientEntityPtr(); + + rvClientEntityPtr & operator=( type* cent ); + + int GetSpawnId ( void ) const { return spawnId; } + bool SetSpawnId ( int id ); + bool UpdateSpawnId ( void ); + + bool IsValid ( void ) const; + type * GetEntity ( void ) const; + int GetEntityNum ( void ) const; + + type * operator-> ( void ) const { return GetEntity ( ); } + operator type* ( void ) const { return GetEntity ( ); } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +private: + int spawnId; +}; + +template< class type > +ID_INLINE rvClientEntityPtr::rvClientEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE rvClientEntityPtr &rvClientEntityPtr::operator=( type *cent ) { + if ( cent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.clientSpawnIds[cent->entityNumber] << CENTITYNUM_BITS ) | cent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::SetSpawnId( int id ) { + if ( ( id >> CENTITYNUM_BITS ) == gameLocal.clientSpawnIds[ id & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::IsValid( void ) const { + return ( gameLocal.clientSpawnIds[ spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ); +} + +template< class type > +ID_INLINE type *rvClientEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.clientSpawnIds[ entityNum ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ) ) { + return static_cast( gameLocal.clientEntities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int rvClientEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +class rvClientPhysics : public idEntity { +public: + + CLASS_PROTOTYPE( rvClientPhysics ); + + rvClientPhysics( void ) { currentEntityNumber = -1; } + + void Spawn( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ); + virtual void PopBindInfo( void ); + + virtual void PushOriginInfo( const idVec3& origin, const idMat3& axis ); + virtual void PopOriginInfo( void ); + + // client side entities. make sure the networking doesn't fuck around with this + virtual bool ClientStale( void ) { assert( false ); return false; } + virtual void ClientUnstale( void ) { assert( false ); } + + int currentEntityNumber; +private: + idEntity* pushedBindMaster; + jointHandle_t pushedBindJoint; + idVec3 pushedOrigin; + idMat3 pushedAxis; + bool pushedOrientated; +}; + +#endif // __GAME_CLIENT_ENTITY_H__ diff --git a/source/mpgame/client/ClientModel.cpp b/source/mpgame/client/ClientModel.cpp new file mode 100644 index 0000000..3bf6bd1 --- /dev/null +++ b/source/mpgame/client/ClientModel.cpp @@ -0,0 +1,450 @@ +//---------------------------------------------------------------- +// ClientModel.cpp +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientModel.h" + +/* +=============================================================================== + +rvClientModel + +=============================================================================== +*/ +CLASS_DECLARATION( rvClientEntity, rvClientModel ) +END_CLASS + + +/* +================ +rvClientModel::rvClientModel +================ +*/ +rvClientModel::rvClientModel ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + worldAxis = mat3_identity; + entityDefHandle = -1; +} + +/* +================ +rvClientModel::~rvClientModel +================ +*/ +rvClientModel::~rvClientModel ( void ) { + FreeEntityDef ( ); +} + +/* +================ +rvClientModel::FreeEntityDef +================ +*/ +void rvClientModel::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +/* +================ +rvClientModel::Spawn +================ +*/ +void rvClientModel::Spawn ( void ) { + const char* spawnarg; + + spawnArgs.GetString ( "classname", "", classname ); + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + renderEntity.entityNum = entityNumber; + + spawnarg = spawnArgs.GetString( "model" ); + if ( spawnarg && *spawnarg ) { + SetModel( spawnarg ); + } +} + +/* +================ +rvClientModel::Think +================ +*/ +void rvClientModel::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + UpdateBind(); + Present(); +} + +/* +================ +rvClientModel::Present +================ +*/ +void rvClientModel::Present(void) { + // Hide client entities bound to a hidden entity + if ( bindMaster && (bindMaster->IsHidden ( ) || (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) ) { + return; + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis; + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientModel::SetCustomShader +================ +*/ +bool rvClientModel::SetCustomShader ( const char* shaderName ) { + if ( shaderName == NULL ) { + return false; + } + + const idMaterial* material = declManager->FindMaterial( shaderName ); + + if ( material == NULL ) { + return false; + } + + renderEntity.customShader = material; + + return true; +} + +/* +================ +rvClientModel::Save +================ +*/ +void rvClientModel::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + savefile->WriteString ( classname ); // cnicholson: Added unsaved var + +} + +/* +================ +rvClientModel::Restore +================ +*/ +void rvClientModel::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + savefile->ReadString ( classname ); // cnicholson: Added unrestored var + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } +} + +/* +================ +rvClientModel::SetModel +================ +*/ +void rvClientModel::SetModel( const char* modelname ) { + FreeEntityDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } +} + +/* +============== +rvClientModel::ProjectOverlay +============== +*/ +void rvClientModel::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( entityDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( entityDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +rvClientModel::UpdateRenderEntity +================ +*/ +bool rvClientModel::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +rvClientModel::ModelCallback + +NOTE: may not change the game state whatsoever! +================ +*/ +bool rvClientModel::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + rvClientEntity *cent; + + cent = gameLocal.clientEntities[ renderEntity->entityNum ]; + if ( !cent ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with NULL client entity '%d'", renderEntity->entityNum ); + return false; + } + + if( !cent->IsType( rvClientModel::GetClassType() ) ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with non-client model on client entity '%d'", renderEntity->entityNum ); + return false; + } + + return ((rvClientModel*)cent)->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +rvClientModel::GetPhysicsToVisualTransform +================ +*/ +bool rvClientModel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +rvClientModel::UpdateModelTransform +================ +*/ +void rvClientModel::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * worldAxis; + renderEntity.origin = worldOrigin + origin * renderEntity.axis; + } else { + renderEntity.axis = worldAxis; + renderEntity.origin = worldOrigin; + } +} + +/* +================ +rvClientModel::UpdateModel +================ +*/ +void rvClientModel::UpdateModel( void ) { + UpdateModelTransform(); + + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + } +} + +/* +================ +rvClientModel::UpdateVisuals +================ +*/ +void rvClientModel::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +rvClientModel::SetSkin +================ +*/ +void rvClientModel::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} + +/* +=============================================================================== + +rvAnimatedClientEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientModel, rvAnimatedClientEntity ) +END_CLASS + +/* +================ +rvAnimatedClientEntity::rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::~rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::~rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::Spawn +================ +*/ +void rvAnimatedClientEntity::Spawn( void ) { + SetModel( spawnArgs.GetString( "model" ) ); +} +/* +================ +rvAnimatedClientEntity::Think +================ +*/ +void rvAnimatedClientEntity::Think ( void ) { + UpdateAnimation(); + + rvClientEntity::Think(); +} + +/* +================ +rvAnimatedClientEntity::UpdateAnimation +================ +*/ +void rvAnimatedClientEntity::UpdateAnimation( void ) { + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + + // call any frame commands that have happened in the past frame + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() ) { + gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + Present(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +rvAnimatedClientEntity::SetModel +================ +*/ +void rvAnimatedClientEntity::SetModel( const char *modelname ) { + FreeEntityDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + rvClientModel::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + //UpdateVisuals(); + Present(); +} diff --git a/source/mpgame/client/ClientModel.h b/source/mpgame/client/ClientModel.h new file mode 100644 index 0000000..96a0376 --- /dev/null +++ b/source/mpgame/client/ClientModel.h @@ -0,0 +1,92 @@ +//---------------------------------------------------------------- +// ClientModel.h +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MODEL_H__ +#define __GAME_CLIENT_MODEL_H__ + +class rvClientModel : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientModel ); + + rvClientModel ( void ); + virtual ~rvClientModel ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + virtual renderEntity_t* GetRenderEntity ( void ); + const char* GetClassname ( void ) const; + + virtual bool SetCustomShader ( const char* shaderName ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void FreeEntityDef ( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return NULL; } + virtual idAnimator* GetAnimator( void ) { return NULL; } + + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + void UpdateVisuals( void ); + void UpdateModel( void ); + void UpdateModelTransform( void ); + + void SetSkin( const idDeclSkin* skin ); + int GetModelDefHandle( void ); +protected: + void Present( void ); + + renderEntity_t renderEntity; + int entityDefHandle; + + idStr classname; +}; + +ID_INLINE renderEntity_t* rvClientModel::GetRenderEntity ( void ) { + return &renderEntity; +} + +ID_INLINE const char* rvClientModel::GetClassname ( void ) const { + return classname.c_str(); +} + +ID_INLINE int rvClientModel::GetModelDefHandle( void ) { + return entityDefHandle; +} + +class rvAnimatedClientEntity : public rvClientModel { +public: + CLASS_PROTOTYPE( rvAnimatedClientEntity ); + + rvAnimatedClientEntity ( void ); + virtual ~rvAnimatedClientEntity ( void ); + + void Spawn( void ); + virtual void Think( void ); + virtual void UpdateAnimation( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return &animator; } + virtual idAnimator* GetAnimator( void ) { return &animator; } + +protected: + idAnimator animator; +}; + +#endif // __GAME_CLIENT_MODEL_H__ diff --git a/source/mpgame/client/ClientMoveable.cpp b/source/mpgame/client/ClientMoveable.cpp new file mode 100644 index 0000000..f492782 --- /dev/null +++ b/source/mpgame/client/ClientMoveable.cpp @@ -0,0 +1,379 @@ +//---------------------------------------------------------------- +// ClientMoveable.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + +rvClientMoveable + +=============================================================================== +*/ +const idEventDef CL_FadeOut( "", "d" ); +const idEventDef CL_ClearDepthHack ( "" ); + +static const float BOUNCE_SOUND_MIN_VELOCITY = 100.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; +static const int BOUNCE_SOUND_DELAY = 200; + +CLASS_DECLARATION( rvClientEntity, rvClientMoveable ) + EVENT( CL_FadeOut, rvClientMoveable::Event_FadeOut ) + EVENT( CL_ClearDepthHack, rvClientMoveable::Event_ClearDepthHack ) +END_CLASS + + +/* +================ +rvClientMoveable::rvClientMoveable +================ +*/ +rvClientMoveable::rvClientMoveable ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + entityDefHandle = -1; + scale.Init( 0, 0, 1.0f, 1.0f ); +} + +/* +================ +rvClientMoveable::~rvClientMoveable +================ +*/ +rvClientMoveable::~rvClientMoveable ( void ) { + FreeEntityDef ( ); + + // Remove any trail effect if there is one + if ( trailEffect ) { + trailEffect->Stop ( ); + } +} + +/* +================ +rvClientMoveable::FreeEntityDef +================ +*/ +void rvClientMoveable::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +idVec3 simpleTri[3] = +{ + idVec3( -1.0, -1.0, 0.0 ), + idVec3( 0.0, 2.0, 0.0 ), + idVec3( 2.0, 0.0, 0.0 ) +}; + +/* +================ +rvClientMoveable::Spawn +================ +*/ +void rvClientMoveable::Spawn ( void ) { + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + idTraceModel trm; + int clipShrink; + idStr clipModelName; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName.Length () ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( clipModelName == SIMPLE_TRI_NAME ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvClientMoveable '%d': cannot load collision model %s", entityNumber, clipModelName.c_str() ); + return; + } + } + + // if the model should be shrunk + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + physicsObj.SetSelf ( gameLocal.entities[ENTITYNUM_CLIENT] ); + physicsObj.SetClipModel ( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "0.5" ), entityNumber ); + + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + physicsObj.SetBouncyness( spawnArgs.GetFloat( "bouncyness", "0.6" ) ); + physicsObj.SetFriction( spawnArgs.GetFloat("linear_friction", "0.6"), spawnArgs.GetFloat( "angular_friction", "0.6"), spawnArgs.GetFloat("friction", "0.05") ); + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + physicsObj.SetContents( 0 ); + // abahr: changed to MASK_SHOT_RENDERMODEL because brass was getting pinched between the player and the wall in some cases + // may want to try something cheaper. + physicsObj.SetClipMask( CONTENTS_OPAQUE ); // MASK_SHOT_RENDERMODEL | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + physicsObj.Activate ( ); + + trailEffect = gameLocal.PlayEffect ( spawnArgs, "fx_trail", physicsObj.GetCenterMass(), GetAxis(), true ); + trailAttenuateSpeed = spawnArgs.GetFloat ( "trailAttenuateSpeed", "200" ); + + bounceSoundShader = declManager->FindSound ( spawnArgs.GetString ( "snd_bounce" ), false ); + bounceSoundTime = 0; + mPlayBounceSoundOnce = spawnArgs.GetBool("bounce_sound_once"); + mHasBounced = false; + + scale.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("scale_reset_duration", "0.2")), Max(VECTOR_EPSILON, spawnArgs.GetFloat("scale", "1.0f")), 1.0f ); +} + +/* +================ +rvClientMoveable::SetOrigin +================ +*/ +void rvClientMoveable::SetOrigin( const idVec3& origin ) { + rvClientEntity::SetOrigin( origin ); + physicsObj.SetOrigin( origin ); +} + +/* +================ +rvClientMoveable::SetAxis +================ +*/ +void rvClientMoveable::SetAxis( const idMat3& axis ) { + rvClientEntity::SetAxis( axis ); + physicsObj.SetAxis( axis ); +} + +/* +================ +rvClientMoveable::SetOwner +================ +*/ +void rvClientMoveable::SetOwner( idEntity* owner ) { + physicsObj.GetClipModel()->SetOwner( owner ); + physicsObj.GetClipModel()->SetEntity( owner ); +} + +/* +================ +rvClientMoveable::Think +================ +*/ +void rvClientMoveable::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + + RunPhysics ( ); + + // Special case the sound update to use the center mass since the origin may be in an odd place + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = worldVelocity; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } + + // Keep the trail effect following + if ( trailEffect ) { + float speed; + speed = idMath::ClampFloat ( 0, trailAttenuateSpeed, worldVelocity.LengthFast ( ) ); + if ( physicsObj.IsAtRest ( ) ) { + trailEffect->Stop ( ); + trailEffect = NULL; + } else { + trailEffect->SetOrigin ( worldOrigin ); + trailEffect->SetAxis ( worldAxis ); + trailEffect->Attenuate ( speed / trailAttenuateSpeed ); + } + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis * scale.GetCurrentValue( gameLocal.GetTime() ); + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientMoveable::GetPhysics +================ +*/ +idPhysics* rvClientMoveable::GetPhysics ( void ) const { + return (idPhysics*)&physicsObj; +} + +/* +================ +rvClientMoveable::Collide +================ +*/ +bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { + if (mPlayBounceSoundOnce && mHasBounced) + { + return false; + } + if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { + float speed; + speed = velocity.LengthFast ( ); + if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { + StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 ); + bounceSoundTime = BOUNCE_SOUND_DELAY; + mHasBounced = true; + } + } + + return false; +} + +/* +================ +rvClientMoveable::Save +================ +*/ +void rvClientMoveable::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + trailEffect.Save( savefile ); + savefile->WriteFloat( trailAttenuateSpeed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( bounceSoundTime ); + savefile->WriteSoundShader( bounceSoundShader ); + + savefile->WriteBool(mPlayBounceSoundOnce); + savefile->WriteBool(mHasBounced); + + // TOSAVE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Restore +================ +*/ +void rvClientMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + trailEffect.Restore( savefile ); + savefile->ReadFloat( trailAttenuateSpeed ); + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadInt( bounceSoundTime ); + savefile->ReadSoundShader( bounceSoundShader ); + + savefile->ReadBool(mPlayBounceSoundOnce); + savefile->ReadBool(mHasBounced); + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + // TORESTORE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Event_FadeOut +================ +*/ +void rvClientMoveable::Event_FadeOut ( int duration ) { + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + PostEventMS ( &EV_Remove, duration ); +} + +/* +================ +rvClientMoveable::Event_ClearDepthHack +================ +*/ +void rvClientMoveable::Event_ClearDepthHack ( void ) { + renderEntity.weaponDepthHackInViewID = 0; +} + +/* +================ +rvClientMoveable::SpawnClientMoveables +================ +*/ +void rvClientMoveable::SpawnClientMoveables( idEntity* ent, const char *type, idList* list ) { + const idKeyValue *kv; + idVec3 origin; + idMat3 axis; + + if( list == NULL || type == NULL ) { + return; + } + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), NULL ); + while ( kv ) { + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( ent->IsType( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + idAnimatedEntity* animEnt = static_cast(ent); + jointHandle_t clientMoveableJoint; + + const char* clientMoveableJointName = ent->spawnArgs.GetString( va( "%s_joint", kv->GetKey().c_str() + 4 ) ); + + // use a joint if specified + if ( idStr::Icmp( clientMoveableJointName, "") ) { + clientMoveableJoint = animEnt->GetAnimator()->GetJointHandle( clientMoveableJointName ); + + if ( !animEnt->GetJointWorldTransform( clientMoveableJoint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", va( "%s_joint", kv->GetKey().c_str() + 4 ), clientMoveableJointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + } + } + + // spawn the entity + const idDict* entityDef = gameLocal.FindEntityDefDict ( kv->GetValue().c_str(), false ); + + if ( entityDef == NULL ) { + gameLocal.Warning( "%s refers to invalid entity def '%s' on entity '%s'\n", kv->GetKey().c_str(), kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + + rvClientMoveable* newModel = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientMoveable" ); + + if( !newModel ) { + gameLocal.Warning( "error spawning client moveable (invalid entity def '%s' on entity '%s')\n", kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + newModel->SetOrigin ( origin ); + newModel->SetAxis( axis ); + + list->Append( newModel ); + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), kv ); + } +} + diff --git a/source/mpgame/client/ClientMoveable.h b/source/mpgame/client/ClientMoveable.h new file mode 100644 index 0000000..0ec1fab --- /dev/null +++ b/source/mpgame/client/ClientMoveable.h @@ -0,0 +1,70 @@ +//---------------------------------------------------------------- +// ClientMoveable.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MOVEABLE_H__ +#define __GAME_CLIENT_MOVEABLE_H__ + +class rvClientMoveable : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientMoveable ); + + rvClientMoveable ( void ); + virtual ~rvClientMoveable ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + renderEntity_t* GetRenderEntity ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + static void SpawnClientMoveables ( idEntity* ent, const char *type, idList* list ); + + virtual void FreeEntityDef ( void ); + + void SetOwner ( idEntity* ent ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); +protected: + renderEntity_t renderEntity; + int entityDefHandle; + + rvClientEffectPtr trailEffect; + float trailAttenuateSpeed; + + idPhysics_RigidBody physicsObj; + + int bounceSoundTime; + const idSoundShader* bounceSoundShader; + bool mPlayBounceSoundOnce; + bool mHasBounced; + + idInterpolate scale; + +private: + + void Event_FadeOut ( int duration ); + void Event_ClearDepthHack ( void ); +}; + +ID_INLINE renderEntity_t* rvClientMoveable::GetRenderEntity ( void ) { + return &renderEntity; +} + +extern const idEventDef CL_FadeOut; +extern const idEventDef CL_ClearDepthHack; + +#define SIMPLE_TRI_NAME "simpletri" + +extern idVec3 simpleTri[3]; + + +#endif // __GAME_CLIENT_MOVEABLE_H__ diff --git a/source/mpgame/gamesys/Callbacks.cpp b/source/mpgame/gamesys/Callbacks.cpp new file mode 100644 index 0000000..9c2663d --- /dev/null +++ b/source/mpgame/gamesys/Callbacks.cpp @@ -0,0 +1,2600 @@ +// generated file - see CREATE_EVENT_CODE + + /******************************************************* + + 1 args + + *******************************************************/ + + case 512 : + typedef void ( idClass::*eventCallback_i_t )( const int ); + ( this->*( eventCallback_i_t )callback )( data[ 0 ] ); + break; + + case 513 : + typedef void ( idClass::*eventCallback_f_t )( const float ); + ( this->*( eventCallback_f_t )callback )( *( float * )&data[ 0 ] ); + break; + + /******************************************************* + + 2 args + + *******************************************************/ + + case 1024 : + typedef void ( idClass::*eventCallback_ii_t )( const int, const int ); + ( this->*( eventCallback_ii_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 1025 : + typedef void ( idClass::*eventCallback_fi_t )( const float, const int ); + ( this->*( eventCallback_fi_t )callback )( *( float * )&data[ 0 ], data[ 1 ] ); + break; + + case 1026 : + typedef void ( idClass::*eventCallback_if_t )( const int, const float ); + ( this->*( eventCallback_if_t )callback )( data[ 0 ], *( float * )&data[ 1 ] ); + break; + + case 1027 : + typedef void ( idClass::*eventCallback_ff_t )( const float, const float ); + ( this->*( eventCallback_ff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ] ); + break; + + /******************************************************* + + 3 args + + *******************************************************/ + + case 2048 : + typedef void ( idClass::*eventCallback_iii_t )( const int, const int, const int ); + ( this->*( eventCallback_iii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2049 : + typedef void ( idClass::*eventCallback_fii_t )( const float, const int, const int ); + ( this->*( eventCallback_fii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2050 : + typedef void ( idClass::*eventCallback_ifi_t )( const int, const float, const int ); + ( this->*( eventCallback_ifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2051 : + typedef void ( idClass::*eventCallback_ffi_t )( const float, const float, const int ); + ( this->*( eventCallback_ffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2052 : + typedef void ( idClass::*eventCallback_iif_t )( const int, const int, const float ); + ( this->*( eventCallback_iif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2053 : + typedef void ( idClass::*eventCallback_fif_t )( const float, const int, const float ); + ( this->*( eventCallback_fif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2054 : + typedef void ( idClass::*eventCallback_iff_t )( const int, const float, const float ); + ( this->*( eventCallback_iff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2055 : + typedef void ( idClass::*eventCallback_fff_t )( const float, const float, const float ); + ( this->*( eventCallback_fff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + /******************************************************* + + 4 args + + *******************************************************/ + + case 4096 : + typedef void ( idClass::*eventCallback_iiii_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_iiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4097 : + typedef void ( idClass::*eventCallback_fiii_t )( const float, const int, const int, const int ); + ( this->*( eventCallback_fiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4098 : + typedef void ( idClass::*eventCallback_ifii_t )( const int, const float, const int, const int ); + ( this->*( eventCallback_ifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4099 : + typedef void ( idClass::*eventCallback_ffii_t )( const float, const float, const int, const int ); + ( this->*( eventCallback_ffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4100 : + typedef void ( idClass::*eventCallback_iifi_t )( const int, const int, const float, const int ); + ( this->*( eventCallback_iifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4101 : + typedef void ( idClass::*eventCallback_fifi_t )( const float, const int, const float, const int ); + ( this->*( eventCallback_fifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4102 : + typedef void ( idClass::*eventCallback_iffi_t )( const int, const float, const float, const int ); + ( this->*( eventCallback_iffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4103 : + typedef void ( idClass::*eventCallback_fffi_t )( const float, const float, const float, const int ); + ( this->*( eventCallback_fffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4104 : + typedef void ( idClass::*eventCallback_iiif_t )( const int, const int, const int, const float ); + ( this->*( eventCallback_iiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4105 : + typedef void ( idClass::*eventCallback_fiif_t )( const float, const int, const int, const float ); + ( this->*( eventCallback_fiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4106 : + typedef void ( idClass::*eventCallback_ifif_t )( const int, const float, const int, const float ); + ( this->*( eventCallback_ifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4107 : + typedef void ( idClass::*eventCallback_ffif_t )( const float, const float, const int, const float ); + ( this->*( eventCallback_ffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4108 : + typedef void ( idClass::*eventCallback_iiff_t )( const int, const int, const float, const float ); + ( this->*( eventCallback_iiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4109 : + typedef void ( idClass::*eventCallback_fiff_t )( const float, const int, const float, const float ); + ( this->*( eventCallback_fiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4110 : + typedef void ( idClass::*eventCallback_ifff_t )( const int, const float, const float, const float ); + ( this->*( eventCallback_ifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4111 : + typedef void ( idClass::*eventCallback_ffff_t )( const float, const float, const float, const float ); + ( this->*( eventCallback_ffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + /******************************************************* + + 5 args + + *******************************************************/ + + case 8192 : + typedef void ( idClass::*eventCallback_iiiii_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8193 : + typedef void ( idClass::*eventCallback_fiiii_t )( const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8194 : + typedef void ( idClass::*eventCallback_ifiii_t )( const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8195 : + typedef void ( idClass::*eventCallback_ffiii_t )( const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8196 : + typedef void ( idClass::*eventCallback_iifii_t )( const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8197 : + typedef void ( idClass::*eventCallback_fifii_t )( const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8198 : + typedef void ( idClass::*eventCallback_iffii_t )( const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8199 : + typedef void ( idClass::*eventCallback_fffii_t )( const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8200 : + typedef void ( idClass::*eventCallback_iiifi_t )( const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8201 : + typedef void ( idClass::*eventCallback_fiifi_t )( const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8202 : + typedef void ( idClass::*eventCallback_ififi_t )( const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8203 : + typedef void ( idClass::*eventCallback_ffifi_t )( const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8204 : + typedef void ( idClass::*eventCallback_iiffi_t )( const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8205 : + typedef void ( idClass::*eventCallback_fiffi_t )( const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8206 : + typedef void ( idClass::*eventCallback_ifffi_t )( const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8207 : + typedef void ( idClass::*eventCallback_ffffi_t )( const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8208 : + typedef void ( idClass::*eventCallback_iiiif_t )( const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8209 : + typedef void ( idClass::*eventCallback_fiiif_t )( const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8210 : + typedef void ( idClass::*eventCallback_ifiif_t )( const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8211 : + typedef void ( idClass::*eventCallback_ffiif_t )( const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8212 : + typedef void ( idClass::*eventCallback_iifif_t )( const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8213 : + typedef void ( idClass::*eventCallback_fifif_t )( const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8214 : + typedef void ( idClass::*eventCallback_iffif_t )( const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8215 : + typedef void ( idClass::*eventCallback_fffif_t )( const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8216 : + typedef void ( idClass::*eventCallback_iiiff_t )( const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8217 : + typedef void ( idClass::*eventCallback_fiiff_t )( const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8218 : + typedef void ( idClass::*eventCallback_ififf_t )( const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8219 : + typedef void ( idClass::*eventCallback_ffiff_t )( const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8220 : + typedef void ( idClass::*eventCallback_iifff_t )( const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8221 : + typedef void ( idClass::*eventCallback_fifff_t )( const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8222 : + typedef void ( idClass::*eventCallback_iffff_t )( const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8223 : + typedef void ( idClass::*eventCallback_fffff_t )( const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + /******************************************************* + + 6 args + + *******************************************************/ + + case 16384 : + typedef void ( idClass::*eventCallback_iiiiii_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16385 : + typedef void ( idClass::*eventCallback_fiiiii_t )( const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16386 : + typedef void ( idClass::*eventCallback_ifiiii_t )( const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16387 : + typedef void ( idClass::*eventCallback_ffiiii_t )( const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16388 : + typedef void ( idClass::*eventCallback_iifiii_t )( const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iifiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16389 : + typedef void ( idClass::*eventCallback_fifiii_t )( const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16390 : + typedef void ( idClass::*eventCallback_iffiii_t )( const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16391 : + typedef void ( idClass::*eventCallback_fffiii_t )( const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16392 : + typedef void ( idClass::*eventCallback_iiifii_t )( const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16393 : + typedef void ( idClass::*eventCallback_fiifii_t )( const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16394 : + typedef void ( idClass::*eventCallback_ififii_t )( const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16395 : + typedef void ( idClass::*eventCallback_ffifii_t )( const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16396 : + typedef void ( idClass::*eventCallback_iiffii_t )( const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16397 : + typedef void ( idClass::*eventCallback_fiffii_t )( const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16398 : + typedef void ( idClass::*eventCallback_ifffii_t )( const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16399 : + typedef void ( idClass::*eventCallback_ffffii_t )( const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16400 : + typedef void ( idClass::*eventCallback_iiiifi_t )( const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16401 : + typedef void ( idClass::*eventCallback_fiiifi_t )( const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16402 : + typedef void ( idClass::*eventCallback_ifiifi_t )( const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16403 : + typedef void ( idClass::*eventCallback_ffiifi_t )( const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16404 : + typedef void ( idClass::*eventCallback_iififi_t )( const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16405 : + typedef void ( idClass::*eventCallback_fififi_t )( const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16406 : + typedef void ( idClass::*eventCallback_iffifi_t )( const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16407 : + typedef void ( idClass::*eventCallback_fffifi_t )( const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16408 : + typedef void ( idClass::*eventCallback_iiiffi_t )( const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16409 : + typedef void ( idClass::*eventCallback_fiiffi_t )( const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16410 : + typedef void ( idClass::*eventCallback_ififfi_t )( const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16411 : + typedef void ( idClass::*eventCallback_ffiffi_t )( const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16412 : + typedef void ( idClass::*eventCallback_iifffi_t )( const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16413 : + typedef void ( idClass::*eventCallback_fifffi_t )( const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16414 : + typedef void ( idClass::*eventCallback_iffffi_t )( const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16415 : + typedef void ( idClass::*eventCallback_fffffi_t )( const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16416 : + typedef void ( idClass::*eventCallback_iiiiif_t )( const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16417 : + typedef void ( idClass::*eventCallback_fiiiif_t )( const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16418 : + typedef void ( idClass::*eventCallback_ifiiif_t )( const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16419 : + typedef void ( idClass::*eventCallback_ffiiif_t )( const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16420 : + typedef void ( idClass::*eventCallback_iifiif_t )( const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iifiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16421 : + typedef void ( idClass::*eventCallback_fifiif_t )( const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16422 : + typedef void ( idClass::*eventCallback_iffiif_t )( const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16423 : + typedef void ( idClass::*eventCallback_fffiif_t )( const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16424 : + typedef void ( idClass::*eventCallback_iiifif_t )( const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16425 : + typedef void ( idClass::*eventCallback_fiifif_t )( const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16426 : + typedef void ( idClass::*eventCallback_ififif_t )( const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16427 : + typedef void ( idClass::*eventCallback_ffifif_t )( const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16428 : + typedef void ( idClass::*eventCallback_iiffif_t )( const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16429 : + typedef void ( idClass::*eventCallback_fiffif_t )( const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16430 : + typedef void ( idClass::*eventCallback_ifffif_t )( const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16431 : + typedef void ( idClass::*eventCallback_ffffif_t )( const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16432 : + typedef void ( idClass::*eventCallback_iiiiff_t )( const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16433 : + typedef void ( idClass::*eventCallback_fiiiff_t )( const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16434 : + typedef void ( idClass::*eventCallback_ifiiff_t )( const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16435 : + typedef void ( idClass::*eventCallback_ffiiff_t )( const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16436 : + typedef void ( idClass::*eventCallback_iififf_t )( const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16437 : + typedef void ( idClass::*eventCallback_fififf_t )( const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16438 : + typedef void ( idClass::*eventCallback_iffiff_t )( const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16439 : + typedef void ( idClass::*eventCallback_fffiff_t )( const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16440 : + typedef void ( idClass::*eventCallback_iiifff_t )( const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16441 : + typedef void ( idClass::*eventCallback_fiifff_t )( const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16442 : + typedef void ( idClass::*eventCallback_ififff_t )( const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16443 : + typedef void ( idClass::*eventCallback_ffifff_t )( const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16444 : + typedef void ( idClass::*eventCallback_iiffff_t )( const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16445 : + typedef void ( idClass::*eventCallback_fiffff_t )( const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16446 : + typedef void ( idClass::*eventCallback_ifffff_t )( const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16447 : + typedef void ( idClass::*eventCallback_ffffff_t )( const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + /******************************************************* + + 7 args + + *******************************************************/ + + case 32768 : + typedef void ( idClass::*eventCallback_iiiiiii_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32769 : + typedef void ( idClass::*eventCallback_fiiiiii_t )( const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32770 : + typedef void ( idClass::*eventCallback_ifiiiii_t )( const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32771 : + typedef void ( idClass::*eventCallback_ffiiiii_t )( const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32772 : + typedef void ( idClass::*eventCallback_iifiiii_t )( const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32773 : + typedef void ( idClass::*eventCallback_fifiiii_t )( const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32774 : + typedef void ( idClass::*eventCallback_iffiiii_t )( const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32775 : + typedef void ( idClass::*eventCallback_fffiiii_t )( const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32776 : + typedef void ( idClass::*eventCallback_iiifiii_t )( const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32777 : + typedef void ( idClass::*eventCallback_fiifiii_t )( const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32778 : + typedef void ( idClass::*eventCallback_ififiii_t )( const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ififiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32779 : + typedef void ( idClass::*eventCallback_ffifiii_t )( const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32780 : + typedef void ( idClass::*eventCallback_iiffiii_t )( const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32781 : + typedef void ( idClass::*eventCallback_fiffiii_t )( const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32782 : + typedef void ( idClass::*eventCallback_ifffiii_t )( const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ifffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32783 : + typedef void ( idClass::*eventCallback_ffffiii_t )( const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32784 : + typedef void ( idClass::*eventCallback_iiiifii_t )( const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32785 : + typedef void ( idClass::*eventCallback_fiiifii_t )( const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32786 : + typedef void ( idClass::*eventCallback_ifiifii_t )( const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32787 : + typedef void ( idClass::*eventCallback_ffiifii_t )( const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32788 : + typedef void ( idClass::*eventCallback_iififii_t )( const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iififii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32789 : + typedef void ( idClass::*eventCallback_fififii_t )( const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32790 : + typedef void ( idClass::*eventCallback_iffifii_t )( const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32791 : + typedef void ( idClass::*eventCallback_fffifii_t )( const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32792 : + typedef void ( idClass::*eventCallback_iiiffii_t )( const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32793 : + typedef void ( idClass::*eventCallback_fiiffii_t )( const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32794 : + typedef void ( idClass::*eventCallback_ififfii_t )( const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ififfii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32795 : + typedef void ( idClass::*eventCallback_ffiffii_t )( const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32796 : + typedef void ( idClass::*eventCallback_iifffii_t )( const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iifffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32797 : + typedef void ( idClass::*eventCallback_fifffii_t )( const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32798 : + typedef void ( idClass::*eventCallback_iffffii_t )( const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32799 : + typedef void ( idClass::*eventCallback_fffffii_t )( const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32800 : + typedef void ( idClass::*eventCallback_iiiiifi_t )( const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32801 : + typedef void ( idClass::*eventCallback_fiiiifi_t )( const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32802 : + typedef void ( idClass::*eventCallback_ifiiifi_t )( const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32803 : + typedef void ( idClass::*eventCallback_ffiiifi_t )( const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32804 : + typedef void ( idClass::*eventCallback_iifiifi_t )( const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32805 : + typedef void ( idClass::*eventCallback_fifiifi_t )( const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32806 : + typedef void ( idClass::*eventCallback_iffiifi_t )( const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32807 : + typedef void ( idClass::*eventCallback_fffiifi_t )( const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32808 : + typedef void ( idClass::*eventCallback_iiififi_t )( const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32809 : + typedef void ( idClass::*eventCallback_fiififi_t )( const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32810 : + typedef void ( idClass::*eventCallback_ifififi_t )( const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32811 : + typedef void ( idClass::*eventCallback_ffififi_t )( const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32812 : + typedef void ( idClass::*eventCallback_iiffifi_t )( const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32813 : + typedef void ( idClass::*eventCallback_fiffifi_t )( const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32814 : + typedef void ( idClass::*eventCallback_ifffifi_t )( const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ifffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32815 : + typedef void ( idClass::*eventCallback_ffffifi_t )( const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32816 : + typedef void ( idClass::*eventCallback_iiiiffi_t )( const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32817 : + typedef void ( idClass::*eventCallback_fiiiffi_t )( const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32818 : + typedef void ( idClass::*eventCallback_ifiiffi_t )( const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32819 : + typedef void ( idClass::*eventCallback_ffiiffi_t )( const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32820 : + typedef void ( idClass::*eventCallback_iififfi_t )( const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iififfi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32821 : + typedef void ( idClass::*eventCallback_fififfi_t )( const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32822 : + typedef void ( idClass::*eventCallback_iffiffi_t )( const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32823 : + typedef void ( idClass::*eventCallback_fffiffi_t )( const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32824 : + typedef void ( idClass::*eventCallback_iiifffi_t )( const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32825 : + typedef void ( idClass::*eventCallback_fiifffi_t )( const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32826 : + typedef void ( idClass::*eventCallback_ififffi_t )( const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ififffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32827 : + typedef void ( idClass::*eventCallback_ffifffi_t )( const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32828 : + typedef void ( idClass::*eventCallback_iiffffi_t )( const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32829 : + typedef void ( idClass::*eventCallback_fiffffi_t )( const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32830 : + typedef void ( idClass::*eventCallback_ifffffi_t )( const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32831 : + typedef void ( idClass::*eventCallback_ffffffi_t )( const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32832 : + typedef void ( idClass::*eventCallback_iiiiiif_t )( const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32833 : + typedef void ( idClass::*eventCallback_fiiiiif_t )( const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32834 : + typedef void ( idClass::*eventCallback_ifiiiif_t )( const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32835 : + typedef void ( idClass::*eventCallback_ffiiiif_t )( const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32836 : + typedef void ( idClass::*eventCallback_iifiiif_t )( const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32837 : + typedef void ( idClass::*eventCallback_fifiiif_t )( const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32838 : + typedef void ( idClass::*eventCallback_iffiiif_t )( const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32839 : + typedef void ( idClass::*eventCallback_fffiiif_t )( const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32840 : + typedef void ( idClass::*eventCallback_iiifiif_t )( const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32841 : + typedef void ( idClass::*eventCallback_fiifiif_t )( const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32842 : + typedef void ( idClass::*eventCallback_ififiif_t )( const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ififiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32843 : + typedef void ( idClass::*eventCallback_ffifiif_t )( const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32844 : + typedef void ( idClass::*eventCallback_iiffiif_t )( const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32845 : + typedef void ( idClass::*eventCallback_fiffiif_t )( const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32846 : + typedef void ( idClass::*eventCallback_ifffiif_t )( const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ifffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32847 : + typedef void ( idClass::*eventCallback_ffffiif_t )( const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32848 : + typedef void ( idClass::*eventCallback_iiiifif_t )( const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32849 : + typedef void ( idClass::*eventCallback_fiiifif_t )( const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32850 : + typedef void ( idClass::*eventCallback_ifiifif_t )( const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32851 : + typedef void ( idClass::*eventCallback_ffiifif_t )( const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32852 : + typedef void ( idClass::*eventCallback_iififif_t )( const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iififif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32853 : + typedef void ( idClass::*eventCallback_fififif_t )( const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32854 : + typedef void ( idClass::*eventCallback_iffifif_t )( const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32855 : + typedef void ( idClass::*eventCallback_fffifif_t )( const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32856 : + typedef void ( idClass::*eventCallback_iiiffif_t )( const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32857 : + typedef void ( idClass::*eventCallback_fiiffif_t )( const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32858 : + typedef void ( idClass::*eventCallback_ififfif_t )( const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ififfif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32859 : + typedef void ( idClass::*eventCallback_ffiffif_t )( const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32860 : + typedef void ( idClass::*eventCallback_iifffif_t )( const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iifffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32861 : + typedef void ( idClass::*eventCallback_fifffif_t )( const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32862 : + typedef void ( idClass::*eventCallback_iffffif_t )( const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32863 : + typedef void ( idClass::*eventCallback_fffffif_t )( const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32864 : + typedef void ( idClass::*eventCallback_iiiiiff_t )( const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32865 : + typedef void ( idClass::*eventCallback_fiiiiff_t )( const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32866 : + typedef void ( idClass::*eventCallback_ifiiiff_t )( const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32867 : + typedef void ( idClass::*eventCallback_ffiiiff_t )( const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32868 : + typedef void ( idClass::*eventCallback_iifiiff_t )( const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32869 : + typedef void ( idClass::*eventCallback_fifiiff_t )( const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32870 : + typedef void ( idClass::*eventCallback_iffiiff_t )( const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32871 : + typedef void ( idClass::*eventCallback_fffiiff_t )( const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32872 : + typedef void ( idClass::*eventCallback_iiififf_t )( const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32873 : + typedef void ( idClass::*eventCallback_fiififf_t )( const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32874 : + typedef void ( idClass::*eventCallback_ifififf_t )( const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32875 : + typedef void ( idClass::*eventCallback_ffififf_t )( const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32876 : + typedef void ( idClass::*eventCallback_iiffiff_t )( const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32877 : + typedef void ( idClass::*eventCallback_fiffiff_t )( const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32878 : + typedef void ( idClass::*eventCallback_ifffiff_t )( const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ifffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32879 : + typedef void ( idClass::*eventCallback_ffffiff_t )( const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32880 : + typedef void ( idClass::*eventCallback_iiiifff_t )( const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32881 : + typedef void ( idClass::*eventCallback_fiiifff_t )( const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32882 : + typedef void ( idClass::*eventCallback_ifiifff_t )( const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32883 : + typedef void ( idClass::*eventCallback_ffiifff_t )( const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32884 : + typedef void ( idClass::*eventCallback_iififff_t )( const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iififff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32885 : + typedef void ( idClass::*eventCallback_fififff_t )( const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32886 : + typedef void ( idClass::*eventCallback_iffifff_t )( const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32887 : + typedef void ( idClass::*eventCallback_fffifff_t )( const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32888 : + typedef void ( idClass::*eventCallback_iiiffff_t )( const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32889 : + typedef void ( idClass::*eventCallback_fiiffff_t )( const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32890 : + typedef void ( idClass::*eventCallback_ififfff_t )( const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ififfff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32891 : + typedef void ( idClass::*eventCallback_ffiffff_t )( const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32892 : + typedef void ( idClass::*eventCallback_iifffff_t )( const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iifffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32893 : + typedef void ( idClass::*eventCallback_fifffff_t )( const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32894 : + typedef void ( idClass::*eventCallback_iffffff_t )( const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32895 : + typedef void ( idClass::*eventCallback_fffffff_t )( const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + /******************************************************* + + 8 args + + *******************************************************/ + + case 65536 : + typedef void ( idClass::*eventCallback_iiiiiiii_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65537 : + typedef void ( idClass::*eventCallback_fiiiiiii_t )( const float, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65538 : + typedef void ( idClass::*eventCallback_ifiiiiii_t )( const int, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65539 : + typedef void ( idClass::*eventCallback_ffiiiiii_t )( const float, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65540 : + typedef void ( idClass::*eventCallback_iifiiiii_t )( const int, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65541 : + typedef void ( idClass::*eventCallback_fifiiiii_t )( const float, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65542 : + typedef void ( idClass::*eventCallback_iffiiiii_t )( const int, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65543 : + typedef void ( idClass::*eventCallback_fffiiiii_t )( const float, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65544 : + typedef void ( idClass::*eventCallback_iiifiiii_t )( const int, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiifiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65545 : + typedef void ( idClass::*eventCallback_fiifiiii_t )( const float, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65546 : + typedef void ( idClass::*eventCallback_ififiiii_t )( const int, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ififiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65547 : + typedef void ( idClass::*eventCallback_ffifiiii_t )( const float, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffifiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65548 : + typedef void ( idClass::*eventCallback_iiffiiii_t )( const int, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiffiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65549 : + typedef void ( idClass::*eventCallback_fiffiiii_t )( const float, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiffiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65550 : + typedef void ( idClass::*eventCallback_ifffiiii_t )( const int, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65551 : + typedef void ( idClass::*eventCallback_ffffiiii_t )( const float, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65552 : + typedef void ( idClass::*eventCallback_iiiifiii_t )( const int, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65553 : + typedef void ( idClass::*eventCallback_fiiifiii_t )( const float, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65554 : + typedef void ( idClass::*eventCallback_ifiifiii_t )( const int, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65555 : + typedef void ( idClass::*eventCallback_ffiifiii_t )( const float, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65556 : + typedef void ( idClass::*eventCallback_iififiii_t )( const int, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iififiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65557 : + typedef void ( idClass::*eventCallback_fififiii_t )( const float, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fififiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65558 : + typedef void ( idClass::*eventCallback_iffifiii_t )( const int, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iffifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65559 : + typedef void ( idClass::*eventCallback_fffifiii_t )( const float, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65560 : + typedef void ( idClass::*eventCallback_iiiffiii_t )( const int, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiffiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65561 : + typedef void ( idClass::*eventCallback_fiiffiii_t )( const float, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65562 : + typedef void ( idClass::*eventCallback_ififfiii_t )( const int, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ififfiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65563 : + typedef void ( idClass::*eventCallback_ffiffiii_t )( const float, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65564 : + typedef void ( idClass::*eventCallback_iifffiii_t )( const int, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iifffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65565 : + typedef void ( idClass::*eventCallback_fifffiii_t )( const float, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fifffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65566 : + typedef void ( idClass::*eventCallback_iffffiii_t )( const int, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65567 : + typedef void ( idClass::*eventCallback_fffffiii_t )( const float, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65568 : + typedef void ( idClass::*eventCallback_iiiiifii_t )( const int, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65569 : + typedef void ( idClass::*eventCallback_fiiiifii_t )( const float, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65570 : + typedef void ( idClass::*eventCallback_ifiiifii_t )( const int, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65571 : + typedef void ( idClass::*eventCallback_ffiiifii_t )( const float, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65572 : + typedef void ( idClass::*eventCallback_iifiifii_t )( const int, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifiifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65573 : + typedef void ( idClass::*eventCallback_fifiifii_t )( const float, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fifiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65574 : + typedef void ( idClass::*eventCallback_iffiifii_t )( const int, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iffiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65575 : + typedef void ( idClass::*eventCallback_fffiifii_t )( const float, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65576 : + typedef void ( idClass::*eventCallback_iiififii_t )( const int, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiififii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65577 : + typedef void ( idClass::*eventCallback_fiififii_t )( const float, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65578 : + typedef void ( idClass::*eventCallback_ifififii_t )( const int, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65579 : + typedef void ( idClass::*eventCallback_ffififii_t )( const float, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffififii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65580 : + typedef void ( idClass::*eventCallback_iiffifii_t )( const int, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiffifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65581 : + typedef void ( idClass::*eventCallback_fiffifii_t )( const float, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiffifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65582 : + typedef void ( idClass::*eventCallback_ifffifii_t )( const int, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65583 : + typedef void ( idClass::*eventCallback_ffffifii_t )( const float, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65584 : + typedef void ( idClass::*eventCallback_iiiiffii_t )( const int, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65585 : + typedef void ( idClass::*eventCallback_fiiiffii_t )( const float, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65586 : + typedef void ( idClass::*eventCallback_ifiiffii_t )( const int, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ifiiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65587 : + typedef void ( idClass::*eventCallback_ffiiffii_t )( const float, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65588 : + typedef void ( idClass::*eventCallback_iififfii_t )( const int, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iififfii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65589 : + typedef void ( idClass::*eventCallback_fififfii_t )( const float, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fififfii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65590 : + typedef void ( idClass::*eventCallback_iffiffii_t )( const int, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65591 : + typedef void ( idClass::*eventCallback_fffiffii_t )( const float, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65592 : + typedef void ( idClass::*eventCallback_iiifffii_t )( const int, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiifffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65593 : + typedef void ( idClass::*eventCallback_fiifffii_t )( const float, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65594 : + typedef void ( idClass::*eventCallback_ififffii_t )( const int, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ififffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65595 : + typedef void ( idClass::*eventCallback_ffifffii_t )( const float, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffifffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65596 : + typedef void ( idClass::*eventCallback_iiffffii_t )( const int, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65597 : + typedef void ( idClass::*eventCallback_fiffffii_t )( const float, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65598 : + typedef void ( idClass::*eventCallback_ifffffii_t )( const int, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65599 : + typedef void ( idClass::*eventCallback_ffffffii_t )( const float, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65600 : + typedef void ( idClass::*eventCallback_iiiiiifi_t )( const int, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65601 : + typedef void ( idClass::*eventCallback_fiiiiifi_t )( const float, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65602 : + typedef void ( idClass::*eventCallback_ifiiiifi_t )( const int, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65603 : + typedef void ( idClass::*eventCallback_ffiiiifi_t )( const float, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65604 : + typedef void ( idClass::*eventCallback_iifiiifi_t )( const int, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65605 : + typedef void ( idClass::*eventCallback_fifiiifi_t )( const float, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65606 : + typedef void ( idClass::*eventCallback_iffiiifi_t )( const int, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65607 : + typedef void ( idClass::*eventCallback_fffiiifi_t )( const float, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65608 : + typedef void ( idClass::*eventCallback_iiifiifi_t )( const int, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65609 : + typedef void ( idClass::*eventCallback_fiifiifi_t )( const float, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65610 : + typedef void ( idClass::*eventCallback_ififiifi_t )( const int, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ififiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65611 : + typedef void ( idClass::*eventCallback_ffifiifi_t )( const float, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffifiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65612 : + typedef void ( idClass::*eventCallback_iiffiifi_t )( const int, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiffiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65613 : + typedef void ( idClass::*eventCallback_fiffiifi_t )( const float, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiffiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65614 : + typedef void ( idClass::*eventCallback_ifffiifi_t )( const int, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65615 : + typedef void ( idClass::*eventCallback_ffffiifi_t )( const float, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65616 : + typedef void ( idClass::*eventCallback_iiiififi_t )( const int, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65617 : + typedef void ( idClass::*eventCallback_fiiififi_t )( const float, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65618 : + typedef void ( idClass::*eventCallback_ifiififi_t )( const int, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifiififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65619 : + typedef void ( idClass::*eventCallback_ffiififi_t )( const float, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65620 : + typedef void ( idClass::*eventCallback_iifififi_t )( const int, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iifififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65621 : + typedef void ( idClass::*eventCallback_fifififi_t )( const float, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fifififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65622 : + typedef void ( idClass::*eventCallback_iffififi_t )( const int, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iffififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65623 : + typedef void ( idClass::*eventCallback_fffififi_t )( const float, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65624 : + typedef void ( idClass::*eventCallback_iiiffifi_t )( const int, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiffifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65625 : + typedef void ( idClass::*eventCallback_fiiffifi_t )( const float, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65626 : + typedef void ( idClass::*eventCallback_ififfifi_t )( const int, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ififfifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65627 : + typedef void ( idClass::*eventCallback_ffiffifi_t )( const float, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65628 : + typedef void ( idClass::*eventCallback_iifffifi_t )( const int, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iifffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65629 : + typedef void ( idClass::*eventCallback_fifffifi_t )( const float, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fifffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65630 : + typedef void ( idClass::*eventCallback_iffffifi_t )( const int, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65631 : + typedef void ( idClass::*eventCallback_fffffifi_t )( const float, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65632 : + typedef void ( idClass::*eventCallback_iiiiiffi_t )( const int, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65633 : + typedef void ( idClass::*eventCallback_fiiiiffi_t )( const float, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65634 : + typedef void ( idClass::*eventCallback_ifiiiffi_t )( const int, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65635 : + typedef void ( idClass::*eventCallback_ffiiiffi_t )( const float, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65636 : + typedef void ( idClass::*eventCallback_iifiiffi_t )( const int, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iifiiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65637 : + typedef void ( idClass::*eventCallback_fifiiffi_t )( const float, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fifiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65638 : + typedef void ( idClass::*eventCallback_iffiiffi_t )( const int, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65639 : + typedef void ( idClass::*eventCallback_fffiiffi_t )( const float, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65640 : + typedef void ( idClass::*eventCallback_iiififfi_t )( const int, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiififfi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65641 : + typedef void ( idClass::*eventCallback_fiififfi_t )( const float, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65642 : + typedef void ( idClass::*eventCallback_ifififfi_t )( const int, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65643 : + typedef void ( idClass::*eventCallback_ffififfi_t )( const float, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffififfi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65644 : + typedef void ( idClass::*eventCallback_iiffiffi_t )( const int, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65645 : + typedef void ( idClass::*eventCallback_fiffiffi_t )( const float, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65646 : + typedef void ( idClass::*eventCallback_ifffiffi_t )( const int, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65647 : + typedef void ( idClass::*eventCallback_ffffiffi_t )( const float, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65648 : + typedef void ( idClass::*eventCallback_iiiifffi_t )( const int, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65649 : + typedef void ( idClass::*eventCallback_fiiifffi_t )( const float, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65650 : + typedef void ( idClass::*eventCallback_ifiifffi_t )( const int, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifiifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65651 : + typedef void ( idClass::*eventCallback_ffiifffi_t )( const float, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65652 : + typedef void ( idClass::*eventCallback_iififffi_t )( const int, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iififffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65653 : + typedef void ( idClass::*eventCallback_fififffi_t )( const float, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fififffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65654 : + typedef void ( idClass::*eventCallback_iffifffi_t )( const int, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iffifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65655 : + typedef void ( idClass::*eventCallback_fffifffi_t )( const float, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65656 : + typedef void ( idClass::*eventCallback_iiiffffi_t )( const int, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiffffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65657 : + typedef void ( idClass::*eventCallback_fiiffffi_t )( const float, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65658 : + typedef void ( idClass::*eventCallback_ififfffi_t )( const int, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ififfffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65659 : + typedef void ( idClass::*eventCallback_ffiffffi_t )( const float, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65660 : + typedef void ( idClass::*eventCallback_iifffffi_t )( const int, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65661 : + typedef void ( idClass::*eventCallback_fifffffi_t )( const float, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65662 : + typedef void ( idClass::*eventCallback_iffffffi_t )( const int, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65663 : + typedef void ( idClass::*eventCallback_fffffffi_t )( const float, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65664 : + typedef void ( idClass::*eventCallback_iiiiiiif_t )( const int, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65665 : + typedef void ( idClass::*eventCallback_fiiiiiif_t )( const float, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65666 : + typedef void ( idClass::*eventCallback_ifiiiiif_t )( const int, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65667 : + typedef void ( idClass::*eventCallback_ffiiiiif_t )( const float, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65668 : + typedef void ( idClass::*eventCallback_iifiiiif_t )( const int, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65669 : + typedef void ( idClass::*eventCallback_fifiiiif_t )( const float, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65670 : + typedef void ( idClass::*eventCallback_iffiiiif_t )( const int, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65671 : + typedef void ( idClass::*eventCallback_fffiiiif_t )( const float, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65672 : + typedef void ( idClass::*eventCallback_iiifiiif_t )( const int, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiifiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65673 : + typedef void ( idClass::*eventCallback_fiifiiif_t )( const float, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65674 : + typedef void ( idClass::*eventCallback_ififiiif_t )( const int, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ififiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65675 : + typedef void ( idClass::*eventCallback_ffifiiif_t )( const float, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffifiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65676 : + typedef void ( idClass::*eventCallback_iiffiiif_t )( const int, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiffiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65677 : + typedef void ( idClass::*eventCallback_fiffiiif_t )( const float, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiffiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65678 : + typedef void ( idClass::*eventCallback_ifffiiif_t )( const int, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65679 : + typedef void ( idClass::*eventCallback_ffffiiif_t )( const float, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65680 : + typedef void ( idClass::*eventCallback_iiiifiif_t )( const int, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65681 : + typedef void ( idClass::*eventCallback_fiiifiif_t )( const float, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65682 : + typedef void ( idClass::*eventCallback_ifiifiif_t )( const int, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65683 : + typedef void ( idClass::*eventCallback_ffiifiif_t )( const float, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65684 : + typedef void ( idClass::*eventCallback_iififiif_t )( const int, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iififiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65685 : + typedef void ( idClass::*eventCallback_fififiif_t )( const float, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fififiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65686 : + typedef void ( idClass::*eventCallback_iffifiif_t )( const int, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iffifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65687 : + typedef void ( idClass::*eventCallback_fffifiif_t )( const float, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65688 : + typedef void ( idClass::*eventCallback_iiiffiif_t )( const int, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiffiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65689 : + typedef void ( idClass::*eventCallback_fiiffiif_t )( const float, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65690 : + typedef void ( idClass::*eventCallback_ififfiif_t )( const int, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ififfiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65691 : + typedef void ( idClass::*eventCallback_ffiffiif_t )( const float, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65692 : + typedef void ( idClass::*eventCallback_iifffiif_t )( const int, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iifffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65693 : + typedef void ( idClass::*eventCallback_fifffiif_t )( const float, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fifffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65694 : + typedef void ( idClass::*eventCallback_iffffiif_t )( const int, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65695 : + typedef void ( idClass::*eventCallback_fffffiif_t )( const float, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65696 : + typedef void ( idClass::*eventCallback_iiiiifif_t )( const int, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65697 : + typedef void ( idClass::*eventCallback_fiiiifif_t )( const float, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65698 : + typedef void ( idClass::*eventCallback_ifiiifif_t )( const int, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65699 : + typedef void ( idClass::*eventCallback_ffiiifif_t )( const float, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65700 : + typedef void ( idClass::*eventCallback_iifiifif_t )( const int, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifiifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65701 : + typedef void ( idClass::*eventCallback_fifiifif_t )( const float, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fifiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65702 : + typedef void ( idClass::*eventCallback_iffiifif_t )( const int, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iffiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65703 : + typedef void ( idClass::*eventCallback_fffiifif_t )( const float, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65704 : + typedef void ( idClass::*eventCallback_iiififif_t )( const int, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiififif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65705 : + typedef void ( idClass::*eventCallback_fiififif_t )( const float, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65706 : + typedef void ( idClass::*eventCallback_ifififif_t )( const int, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65707 : + typedef void ( idClass::*eventCallback_ffififif_t )( const float, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffififif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65708 : + typedef void ( idClass::*eventCallback_iiffifif_t )( const int, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiffifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65709 : + typedef void ( idClass::*eventCallback_fiffifif_t )( const float, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiffifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65710 : + typedef void ( idClass::*eventCallback_ifffifif_t )( const int, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65711 : + typedef void ( idClass::*eventCallback_ffffifif_t )( const float, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65712 : + typedef void ( idClass::*eventCallback_iiiiffif_t )( const int, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65713 : + typedef void ( idClass::*eventCallback_fiiiffif_t )( const float, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65714 : + typedef void ( idClass::*eventCallback_ifiiffif_t )( const int, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ifiiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65715 : + typedef void ( idClass::*eventCallback_ffiiffif_t )( const float, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65716 : + typedef void ( idClass::*eventCallback_iififfif_t )( const int, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iififfif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65717 : + typedef void ( idClass::*eventCallback_fififfif_t )( const float, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fififfif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65718 : + typedef void ( idClass::*eventCallback_iffiffif_t )( const int, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65719 : + typedef void ( idClass::*eventCallback_fffiffif_t )( const float, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65720 : + typedef void ( idClass::*eventCallback_iiifffif_t )( const int, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiifffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65721 : + typedef void ( idClass::*eventCallback_fiifffif_t )( const float, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65722 : + typedef void ( idClass::*eventCallback_ififffif_t )( const int, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ififffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65723 : + typedef void ( idClass::*eventCallback_ffifffif_t )( const float, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffifffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65724 : + typedef void ( idClass::*eventCallback_iiffffif_t )( const int, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65725 : + typedef void ( idClass::*eventCallback_fiffffif_t )( const float, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65726 : + typedef void ( idClass::*eventCallback_ifffffif_t )( const int, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65727 : + typedef void ( idClass::*eventCallback_ffffffif_t )( const float, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65728 : + typedef void ( idClass::*eventCallback_iiiiiiff_t )( const int, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65729 : + typedef void ( idClass::*eventCallback_fiiiiiff_t )( const float, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65730 : + typedef void ( idClass::*eventCallback_ifiiiiff_t )( const int, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65731 : + typedef void ( idClass::*eventCallback_ffiiiiff_t )( const float, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65732 : + typedef void ( idClass::*eventCallback_iifiiiff_t )( const int, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65733 : + typedef void ( idClass::*eventCallback_fifiiiff_t )( const float, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65734 : + typedef void ( idClass::*eventCallback_iffiiiff_t )( const int, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65735 : + typedef void ( idClass::*eventCallback_fffiiiff_t )( const float, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65736 : + typedef void ( idClass::*eventCallback_iiifiiff_t )( const int, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiifiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65737 : + typedef void ( idClass::*eventCallback_fiifiiff_t )( const float, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65738 : + typedef void ( idClass::*eventCallback_ififiiff_t )( const int, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ififiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65739 : + typedef void ( idClass::*eventCallback_ffifiiff_t )( const float, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffifiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65740 : + typedef void ( idClass::*eventCallback_iiffiiff_t )( const int, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiffiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65741 : + typedef void ( idClass::*eventCallback_fiffiiff_t )( const float, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiffiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65742 : + typedef void ( idClass::*eventCallback_ifffiiff_t )( const int, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65743 : + typedef void ( idClass::*eventCallback_ffffiiff_t )( const float, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65744 : + typedef void ( idClass::*eventCallback_iiiififf_t )( const int, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65745 : + typedef void ( idClass::*eventCallback_fiiififf_t )( const float, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65746 : + typedef void ( idClass::*eventCallback_ifiififf_t )( const int, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifiififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65747 : + typedef void ( idClass::*eventCallback_ffiififf_t )( const float, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65748 : + typedef void ( idClass::*eventCallback_iifififf_t )( const int, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iifififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65749 : + typedef void ( idClass::*eventCallback_fifififf_t )( const float, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fifififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65750 : + typedef void ( idClass::*eventCallback_iffififf_t )( const int, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iffififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65751 : + typedef void ( idClass::*eventCallback_fffififf_t )( const float, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65752 : + typedef void ( idClass::*eventCallback_iiiffiff_t )( const int, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiffiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65753 : + typedef void ( idClass::*eventCallback_fiiffiff_t )( const float, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65754 : + typedef void ( idClass::*eventCallback_ififfiff_t )( const int, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ififfiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65755 : + typedef void ( idClass::*eventCallback_ffiffiff_t )( const float, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65756 : + typedef void ( idClass::*eventCallback_iifffiff_t )( const int, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iifffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65757 : + typedef void ( idClass::*eventCallback_fifffiff_t )( const float, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fifffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65758 : + typedef void ( idClass::*eventCallback_iffffiff_t )( const int, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65759 : + typedef void ( idClass::*eventCallback_fffffiff_t )( const float, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65760 : + typedef void ( idClass::*eventCallback_iiiiifff_t )( const int, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65761 : + typedef void ( idClass::*eventCallback_fiiiifff_t )( const float, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65762 : + typedef void ( idClass::*eventCallback_ifiiifff_t )( const int, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65763 : + typedef void ( idClass::*eventCallback_ffiiifff_t )( const float, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65764 : + typedef void ( idClass::*eventCallback_iifiifff_t )( const int, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifiifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65765 : + typedef void ( idClass::*eventCallback_fifiifff_t )( const float, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fifiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65766 : + typedef void ( idClass::*eventCallback_iffiifff_t )( const int, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iffiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65767 : + typedef void ( idClass::*eventCallback_fffiifff_t )( const float, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65768 : + typedef void ( idClass::*eventCallback_iiififff_t )( const int, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiififff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65769 : + typedef void ( idClass::*eventCallback_fiififff_t )( const float, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65770 : + typedef void ( idClass::*eventCallback_ifififff_t )( const int, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65771 : + typedef void ( idClass::*eventCallback_ffififff_t )( const float, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffififff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65772 : + typedef void ( idClass::*eventCallback_iiffifff_t )( const int, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiffifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65773 : + typedef void ( idClass::*eventCallback_fiffifff_t )( const float, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiffifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65774 : + typedef void ( idClass::*eventCallback_ifffifff_t )( const int, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65775 : + typedef void ( idClass::*eventCallback_ffffifff_t )( const float, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65776 : + typedef void ( idClass::*eventCallback_iiiiffff_t )( const int, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65777 : + typedef void ( idClass::*eventCallback_fiiiffff_t )( const float, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65778 : + typedef void ( idClass::*eventCallback_ifiiffff_t )( const int, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ifiiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65779 : + typedef void ( idClass::*eventCallback_ffiiffff_t )( const float, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65780 : + typedef void ( idClass::*eventCallback_iififfff_t )( const int, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iififfff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65781 : + typedef void ( idClass::*eventCallback_fififfff_t )( const float, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fififfff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65782 : + typedef void ( idClass::*eventCallback_iffiffff_t )( const int, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65783 : + typedef void ( idClass::*eventCallback_fffiffff_t )( const float, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65784 : + typedef void ( idClass::*eventCallback_iiifffff_t )( const int, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiifffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65785 : + typedef void ( idClass::*eventCallback_fiifffff_t )( const float, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65786 : + typedef void ( idClass::*eventCallback_ififffff_t )( const int, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ififffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65787 : + typedef void ( idClass::*eventCallback_ffifffff_t )( const float, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffifffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65788 : + typedef void ( idClass::*eventCallback_iiffffff_t )( const int, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65789 : + typedef void ( idClass::*eventCallback_fiffffff_t )( const float, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65790 : + typedef void ( idClass::*eventCallback_ifffffff_t )( const int, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65791 : + typedef void ( idClass::*eventCallback_ffffffff_t )( const float, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + diff --git a/source/mpgame/gamesys/Class.cpp b/source/mpgame/gamesys/Class.cpp new file mode 100644 index 0000000..51cae34 --- /dev/null +++ b/source/mpgame/gamesys/Class.cpp @@ -0,0 +1,1434 @@ +/* + +Base class for all C++ objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +// this is the head of a singly linked list of all the idTypes +static idTypeInfo *typelist = NULL; +static idHierarchy classHierarchy; +static int eventCallbackMemory = 0; + +/* +================ +idTypeInfo::idClassType() + +Constructor for class. Should only be called from CLASS_DECLARATION macro. +Handles linking class definition into class hierarchy. This should only happen +at startup as idTypeInfos are statically defined. Since static variables can be +initialized in any order, the constructor must handle the case that subclasses +are initialized before superclasses. +================ +*/ +idTypeInfo::idTypeInfo( const char *classname, const char *superclass, idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), +// RAVEN BEGIN +// bdube: added states + void ( idClass::*Spawn )( void ), + rvStateFunc* stateCallbacks, + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ) { +// RAVEN END + + idTypeInfo *type; + idTypeInfo **insert; + + this->classname = classname; + this->superclass = superclass; + this->eventCallbacks = eventCallbacks; + this->eventMap = NULL; + this->Spawn = Spawn; + this->Save = Save; + this->Restore = Restore; + this->CreateInstance = CreateInstance; + this->super = idClass::GetClass( superclass ); + this->freeEventMap = false; + typeNum = 0; + lastChild = 0; + +// RAVEN BEGIN +// bdube: added states + this->stateCallbacks = stateCallbacks; +// RAVEN END + + // Check if any subclasses were initialized before their superclass + for( type = typelist; type != NULL; type = type->next ) { + if ( ( type->super == NULL ) && !idStr::Cmp( type->superclass, this->classname ) && + idStr::Cmp( type->classname, "idClass" ) ) { + type->super = this; + } + } + + // Insert sorted + for ( insert = &typelist; *insert; insert = &(*insert)->next ) { + assert( idStr::Cmp( classname, (*insert)->classname ) ); + if ( idStr::Cmp( classname, (*insert)->classname ) < 0 ) { + next = *insert; + *insert = this; + break; + } + } + if ( !*insert ) { + *insert = this; + next = NULL; + } +} + +/* +================ +idTypeInfo::~idTypeInfo +================ +*/ +idTypeInfo::~idTypeInfo() { + Shutdown(); +} + +/* +================ +idTypeInfo::Init + +Initializes the event callback table for the class. Creates a +table for fast lookups of event functions. Should only be called once. +================ +*/ +void idTypeInfo::Init( void ) { + idTypeInfo *c; + idEventFunc *def; + int ev; + int i; + bool *set; + int num; + + if ( eventMap ) { + // we've already been initialized by a subclass + return; + } + + // make sure our superclass is initialized first + if ( super && !super->eventMap ) { + super->Init(); + } + + // add to our node hierarchy + if ( super ) { + node.ParentTo( super->node ); + } else { + node.ParentTo( classHierarchy ); + } + node.SetOwner( this ); + + // keep track of the number of children below each class + for( c = super; c != NULL; c = c->super ) { + c->lastChild++; + } + + // if we're not adding any new event callbacks, we can just use our superclass's table + if ( ( !eventCallbacks || !eventCallbacks->event ) && super ) { + eventMap = super->eventMap; + return; + } + + // set a flag so we know to delete the eventMap table + freeEventMap = true; + + // Allocate our new table. It has to have as many entries as there + // are events. NOTE: could save some space by keeping track of the maximum + // event that the class responds to and doing range checking. + num = idEventDef::NumEventCommands(); + eventMap = new eventCallback_t[ num ]; + memset( eventMap, 0, sizeof( eventCallback_t ) * num ); + eventCallbackMemory += sizeof( eventCallback_t ) * num; + + // allocate temporary memory for flags so that the subclass's event callbacks + // override the superclass's event callback + set = new bool[ num ]; + memset( set, 0, sizeof( bool ) * num ); + + // go through the inheritence order and copies the event callback function into + // a list indexed by the event number. This allows fast lookups of + // event functions. + for( c = this; c != NULL; c = c->super ) { + def = c->eventCallbacks; + if ( !def ) { + continue; + } + + // go through each entry until we hit the NULL terminator + for( i = 0; def[ i ].event != NULL; i++ ) { + ev = def[ i ].event->GetEventNum(); + + if ( set[ ev ] ) { + continue; + } + set[ ev ] = true; + eventMap[ ev ] = def[ i ].function; + } + } + + delete[] set; +} + +/* +================ +idTypeInfo::Shutdown + +Should only be called when DLL or EXE is being shutdown. +Although it cleans up any allocated memory, it doesn't bother to remove itself +from the class list since the program is shutting down. +================ +*/ +void idTypeInfo::Shutdown() { + // free up the memory used for event lookups + if ( eventMap ) { + if ( freeEventMap ) { + delete[] eventMap; + } + eventMap = NULL; + } + typeNum = 0; + lastChild = 0; +} + + +/*********************************************************************** + + idClass + +***********************************************************************/ + +const idEventDef EV_PostRestore( "", NULL ); +const idEventDef EV_Remove( "", NULL ); +const idEventDef EV_SafeRemove( "remove", NULL ); + +ABSTRACT_DECLARATION( NULL, idClass ) +// RAVEN BEGIN + EVENT( EV_PostRestore, idClass::Event_PostRestore ) +// RAVEN END + EVENT( EV_Remove, idClass::Event_Remove ) + EVENT( EV_SafeRemove, idClass::Event_SafeRemove ) +END_CLASS + +CLASS_STATES_DECLARATION(idClass) +END_CLASS_STATES + +// alphabetical order +idList idClass::types; +// typenum order +idList idClass::typenums; + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; + +/* +================ +idClass::CallSpawn +================ +*/ +void idClass::CallSpawn( void ) { + idTypeInfo *type; + + type = GetType(); + CallSpawnFunc( type ); +} + +/* +================ +idClass::CallSpawnFunc +================ +*/ +classSpawnFunc_t idClass::CallSpawnFunc( idTypeInfo *cls ) { + classSpawnFunc_t func; + + if ( cls->super ) { + func = CallSpawnFunc( cls->super ); + if ( func == cls->Spawn ) { + // don't call the same function twice in a row. + // this can happen when subclasses don't have their own spawn function. + return func; + } + } + + // RAVEN BEGIN + // hmmm.... stompage of memory has occured + assert(cls->Spawn != 0); + // RAVEN END + + ( this->*cls->Spawn )(); + + return cls->Spawn; +} + +/* +================ +idClass::FindUninitializedMemory +================ +*/ +void idClass::FindUninitializedMemory( void ) { +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = ( ( unsigned long * )this ) - 1; + int size = *ptr; + assert( ( size & 3 ) == 0 ); + size >>= 2; + for ( int i = 0; i < size; i++ ) { + if ( ptr[i] == 0xcdcdcdcd ) { +// RAVEN BEGIN + gameLocal.Warning( "type '%s' has uninitialized variable (offset %d)", GetClassname(), i << 2 ); +// RAVEN END + } + } +#endif +} + +/* +================ +idClass::Spawn +================ +*/ +void idClass::Spawn( void ) { +} + +/* +================ +idClass::~idClass + +Destructor for object. Cancels any events that depend on this object. +================ +*/ +idClass::~idClass() { + idEvent::CancelEvents( this ); +} + +/* +================ +idClass::DisplayInfo_f +================ +*/ +void idClass::DisplayInfo_f( const idCmdArgs &args ) { + gameLocal.Printf( "Class memory status: %i bytes allocated in %i objects\n", memused, numobjects ); +} + +/* +================ +idClass::ListClasses_f +================ +*/ +void idClass::ListClasses_f( const idCmdArgs &args ) { + int i; + idTypeInfo *type; + + gameLocal.Printf( "%-24s %-24s %-6s %-6s\n", "Classname", "Superclass", "Type", "Subclasses" ); + gameLocal.Printf( "----------------------------------------------------------------------\n" ); + + for( i = 0; i < types.Num(); i++ ) { + type = types[ i ]; + gameLocal.Printf( "%-24s %-24s %6d %6d\n", type->classname, type->superclass, type->typeNum, type->lastChild - type->typeNum ); + } + + gameLocal.Printf( "...%d classes", types.Num() ); +} + +/* +================ +idClass::CreateInstance +================ +*/ +idClass *idClass::CreateInstance( const char *name ) { + const idTypeInfo *type; + idClass *obj; + + type = idClass::GetClass( name ); + if ( !type ) { + return NULL; + } + + obj = type->CreateInstance(); + return obj; +} + +/* +================ +idClass::Init + +Should be called after all idTypeInfos are initialized, so must be called +manually upon game code initialization. Tells all the idTypeInfos to initialize +their event callback table for the associated class. This should only be called +once during the execution of the program or DLL. +================ +*/ +void idClass::Init( void ) { + idTypeInfo *c; + int num; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CLASS); +// RAVEN END + + gameLocal.Printf( "Initializing class hierarchy\n" ); + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + return; + } + + // init the event callback tables for all the classes + for( c = typelist; c != NULL; c = c->next ) { +// RAVEN BEGIN +// jnewquist: Make sure the superclass was actually registered! + if ( c->super == NULL && (c->superclass && idStr::Cmp(c->superclass, "NULL")) ) { + common->Error("Superclass %s of %s was never registered!", c->superclass, c->classname); + } +// RAVEN END + c->Init(); + } + + // number the types according to the class hierarchy so we can quickly determine if a class + // is a subclass of another + num = 0; + for( c = classHierarchy.GetNext(); c != NULL; c = c->node.GetNext(), num++ ) { + c->typeNum = num; + c->lastChild += num; + } + + // number of bits needed to send types over network + typeNumBits = idMath::BitsForInteger( num ); + + // create a list of the types so we can do quick lookups + // one list in alphabetical order, one in typenum order + types.SetGranularity( 1 ); + types.SetNum( num ); + typenums.SetGranularity( 1 ); + typenums.SetNum( num ); + num = 0; + for( c = typelist; c != NULL; c = c->next, num++ ) { + types[ num ] = c; + typenums[ c->typeNum ] = c; + } + + initialized = true; + + gameLocal.Printf( "...%i classes, %i bytes for event callbacks\n", types.Num(), eventCallbackMemory ); +} + +/* +================ +idClass::Shutdown +================ +*/ +void idClass::Shutdown( void ) { + idTypeInfo *c; + + for( c = typelist; c != NULL; c = c->next ) { + c->Shutdown(); + } + types.Clear(); + typenums.Clear(); + + initialized = false; +} + +/* +================ +idClass::new +================ +*/ +#ifdef ID_DEBUG_MEMORY +#undef new +#endif + +void * idClass::operator new( size_t s ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +void * idClass::operator new( size_t s, int, int, char *, int ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +#ifdef ID_DEBUG_MEMORY +#define new ID_DEBUG_NEW +#endif + +/* +================ +idClass::delete +================ +*/ +void idClass::operator delete( void *ptr ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +void idClass::operator delete( void *ptr, int, int, char *, int ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +/* +================ +idClass::GetClass + +Returns the idTypeInfo for the name of the class passed in. This is a static function +so it must be called as idClass::GetClass( classname ) +================ +*/ +idTypeInfo *idClass::GetClass( const char *name ) { + idTypeInfo *c; + int order; + int mid; + int min; + int max; + + if ( !initialized ) { + // idClass::Init hasn't been called yet, so do a slow lookup + for( c = typelist; c != NULL; c = c->next ) { + if ( !idStr::Cmp( c->classname, name ) ) { + return c; + } + } + } else { + // do a binary search through the list of types + min = 0; + max = types.Num() - 1; + while( min <= max ) { + mid = ( min + max ) / 2; + c = types[ mid ]; + order = idStr::Cmp( c->classname, name ); + if ( !order ) { + return c; + } else if ( order > 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + } + + return NULL; +} + +/* +================ +idClass::GetType +================ +*/ +idTypeInfo *idClass::GetType( const int typeNum ) { + idTypeInfo *c; + + if ( !initialized ) { + for( c = typelist; c != NULL; c = c->next ) { + if ( c->typeNum == typeNum ) { + return c; + } + } + } else if ( ( typeNum >= 0 ) && ( typeNum < types.Num() ) ) { + return typenums[ typeNum ]; + } + + return NULL; +} + +/* +================ +idClass::GetClassname + +Returns the text classname of the object. +================ +*/ +const char *idClass::GetClassname( void ) const { + idTypeInfo *type; + + type = GetType(); + return type->classname; +} + +/* +================ +idClass::GetSuperclass + +Returns the text classname of the superclass. +================ +*/ +const char *idClass::GetSuperclass( void ) const { + idTypeInfo *cls; + + cls = GetType(); + return cls->superclass; +} + +/* +================ +idClass::CancelEvents +================ +*/ +void idClass::CancelEvents( const idEventDef *ev ) { + idEvent::CancelEvents( this, ev ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idClass::EventIsPosted +================ +*/ +bool idClass::EventIsPosted( const idEventDef *ev ) const { + return idEvent::EventIsPosted( this, ev ); +} +// RAVEN END + +/* +================ +idClass::PostEventArgs +================ +*/ +bool idClass::PostEventArgs( const idEventDef *ev, int time, int numargs, ... ) { + idTypeInfo *c; + idEvent *event; + va_list args; + + assert( ev ); + + if ( !idEvent::initialized ) { + return false; + } + + c = GetType(); + if ( !c->eventMap[ ev->GetEventNum() ] ) { + // we don't respond to this event, so ignore it + return false; + } + + // we service events on the client to avoid any bad code filling up the event pool + // we don't want them processed usually, unless when the map is (re)loading. + // we allow threads to run fine, though. +// RAVEN BEGIN +// bdube: added a check to see if this is a client entity +// jnewquist: Use accessor for static class type + bool isClient = !IsClient() && gameLocal.isClient; + +#ifdef _XENON + // nrausch: We want to allow selectWeapon to pass through, but that's all + if ( idStr::Cmp( ev->GetName(), "selectWeapon" ) == 0 ) { + isClient = false; + } +#endif + + if( isClient && ( gameLocal.GameState() != GAMESTATE_STARTUP ) && ( gameLocal.GameState() != GAMESTATE_RESTART ) && !IsType( idThread::GetClassType() ) ) { +// RAVEN END + return true; + } + + va_start( args, numargs ); + event = idEvent::Alloc( ev, numargs, args ); + va_end( args ); + + event->Schedule( this, c, time ); + + return true; +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time ) { + return PostEventArgs( ev, time, 0 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ) { + return PostEventArgs( ev, time, 1, &arg1 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, time, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, time, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, time, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, time, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, time, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, time, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, time, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time ) { + return PostEventArgs( ev, SEC2MS( time ), 0 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ) { + return PostEventArgs( ev, SEC2MS( time ), 1, &arg1 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, SEC2MS( time ), 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, SEC2MS( time ), 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, SEC2MS( time ), 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, SEC2MS( time ), 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, SEC2MS( time ), 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, SEC2MS( time ), 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, SEC2MS( time ), 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgs +================ +*/ +bool idClass::ProcessEventArgs( const idEventDef *ev, int numargs, ... ) { + idTypeInfo *c; + int num; + int data[ D_EVENT_MAXARGS ]; + va_list args; + + assert( ev ); + assert( idEvent::initialized ); + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + va_start( args, numargs ); + idEvent::CopyArgs( ev, numargs, args, data ); + va_end( args ); + + ProcessEventArgPtr( ev, data ); + + return true; +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev ) { + return ProcessEventArgs( ev, 0 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1 ) { + return ProcessEventArgs( ev, 1, &arg1 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ) { + return ProcessEventArgs( ev, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return ProcessEventArgs( ev, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return ProcessEventArgs( ev, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return ProcessEventArgs( ev, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return ProcessEventArgs( ev, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return ProcessEventArgs( ev, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return ProcessEventArgs( ev, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgPtr +================ +*/ +bool idClass::ProcessEventArgPtr( const idEventDef *ev, int *data ) { + idTypeInfo *c; + int num; + eventCallback_t callback; + + assert( ev ); + assert( idEvent::initialized ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( g_debugTriggers.GetBool() && ( ev == &EV_Activate ) && IsType( idEntity::GetClassType() ) ) { +// RAVEN END + const idEntity *ent = *reinterpret_cast( data ); + gameLocal.Printf( "%d: '%s' activated by '%s'\n", gameLocal.framenum, static_cast( this )->GetName(), ent ? ent->GetName() : "NULL" ); + } + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + callback = c->eventMap[ num ]; + +#if !CPU_EASYARGS + +/* +on ppc architecture, floats are passed in a seperate set of registers +the function prototypes must have matching float declaration + +http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_5.html +*/ + + switch( ev->GetFormatspecIndex() ) { + case 1 << D_EVENT_MAXARGS : + ( this->*callback )(); + break; + +// generated file - see CREATE_EVENT_CODE +#include "Callbacks.cpp" + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#else + + assert( D_EVENT_MAXARGS == 8 ); + + switch( ev->GetNumArgs() ) { + case 0 : + ( this->*callback )(); + break; + + case 1 : + typedef void ( idClass::*eventCallback_1_t )( const int ); + ( this->*( eventCallback_1_t )callback )( data[ 0 ] ); + break; + + case 2 : + typedef void ( idClass::*eventCallback_2_t )( const int, const int ); + ( this->*( eventCallback_2_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 3 : + typedef void ( idClass::*eventCallback_3_t )( const int, const int, const int ); + ( this->*( eventCallback_3_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 4 : + typedef void ( idClass::*eventCallback_4_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_4_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 5 : + typedef void ( idClass::*eventCallback_5_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_5_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 6 : + typedef void ( idClass::*eventCallback_6_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_6_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 7 : + typedef void ( idClass::*eventCallback_7_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_7_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 8 : + typedef void ( idClass::*eventCallback_8_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_8_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#endif + + return true; +} + +/* +================ +idClass::Event_Remove +================ +*/ +void idClass::Event_Remove( void ) { + delete this; +} + +/* +================ +idClass::Event_SafeRemove +================ +*/ +void idClass::Event_SafeRemove( void ) { + // Forces the remove to be done at a safe time + PostEventMS( &EV_Remove, 0 ); +} + +// RAVEN BEGIN +// bdube: client entities +/* +================ +idClass::IsClient +================ +*/ +bool idClass::IsClient ( void ) const { + return false; +} + +/* +================ +idClass::GetDebugInfo +================ +*/ +void idClass::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { +} + +/* +================ +idClass::ProcessState +================ +*/ +stateResult_t idClass::ProcessState ( const rvStateFunc* state, const stateParms_t& parms ) { + return (this->*(state->function)) ( parms ); +} + +stateResult_t idClass::ProcessState ( const char* name, const stateParms_t& parms ) { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return (this->*(cls->stateCallbacks[i].function)) ( parms ); + } + } + } + + return SRESULT_ERROR; +} + +/* +================ +idClass::FindState +================ +*/ +const rvStateFunc* idClass::FindState ( const char* name ) const { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return &cls->stateCallbacks[i]; + } + } + } + + return NULL; +} + +/* +================ +idClass::RegisterClasses +================ +*/ +void idClass::RegisterClasses( void ) +{ +// jnewquist: Register subclasses explicitly so they aren't dead-stripped +#define REGISTER(name) void Register_##name(void); Register_##name(); + REGISTER(idAFAttachment); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Base); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_ClawFourFingers); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Generic); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Gibbable); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_SteamPipe); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Vehicle); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleFourWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleSixWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_WithAttachedHead); // ..\..\code\game\AFEntity.cpp + REGISTER(idAI); // ..\..\code\game\ai\AI_events.cpp + REGISTER(idActivator); // ..\..\code\game\Misc.cpp + REGISTER(idActor); // ..\..\code\game\Actor.cpp + REGISTER(idAnimated); // ..\..\code\game\Misc.cpp + REGISTER(idAnimatedEntity); // ..\..\code\game\Entity.cpp + REGISTER(idBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idBeam); // ..\..\code\game\Misc.cpp + REGISTER(idBobber); // ..\..\code\game\Mover.cpp + REGISTER(idBrittleFracture); // ..\..\code\game\BrittleFracture.cpp + REGISTER(idCamera); // ..\..\code\game\Camera.cpp + REGISTER(idCameraAnim); // ..\..\code\game\Camera.cpp + REGISTER(idCameraView); // ..\..\code\game\Camera.cpp + REGISTER(idChain); // ..\..\code\game\AFEntity.cpp + REGISTER(idClass); // ..\..\code\game\gamesys\Class.cpp + REGISTER(idCursor3D); // ..\..\code\game\GameEdit.cpp + REGISTER(idDamagable); // ..\..\code\game\Misc.cpp + REGISTER(idDoor); // ..\..\code\game\Mover.cpp + REGISTER(idEarthQuake); // ..\..\code\game\Misc.cpp + REGISTER(idElevator); // ..\..\code\game\Mover.cpp + REGISTER(idEntity); // ..\..\code\game\Entity.cpp + REGISTER(idExplodable); // ..\..\code\game\Misc.cpp + REGISTER(idExplodingBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idForce); // ..\..\code\game\physics\Force.cpp + REGISTER(idForceField); // ..\..\code\game\Misc.cpp + REGISTER(idForce_Constant); // ..\..\code\game\physics\Force_Constant.cpp + REGISTER(idForce_Drag); // ..\..\code\game\physics\Force_Drag.cpp + REGISTER(idForce_Field); // ..\..\code\game\physics\Force_Field.cpp + REGISTER(idForce_Spring); // ..\..\code\game\physics\Force_Spring.cpp + REGISTER(idFuncAASObstacle); // ..\..\code\game\Misc.cpp + REGISTER(idFuncAASPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncEmitter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncRadioChatter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncSplat); // ..\..\code\game\Misc.cpp + REGISTER(idGuidedProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idItem); // ..\..\code\game\Item.cpp + REGISTER(idItemPowerup); // ..\..\code\game\Item.cpp + REGISTER(idItemRemover); // ..\..\code\game\Item.cpp + REGISTER(idLight); // ..\..\code\game\Light.cpp + REGISTER(idLiquid); // ..\..\code\game\Misc.cpp + REGISTER(idLocationEntity); // ..\..\code\game\Misc.cpp + REGISTER(idLocationSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idMoveable); // ..\..\code\game\Moveable.cpp + REGISTER(idMoveableItem); // ..\..\code\game\Item.cpp + REGISTER(idMover); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Binary); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Periodic); // ..\..\code\game\Mover.cpp + REGISTER(idMultiModelAF); // ..\..\code\game\AFEntity.cpp + REGISTER(idObjective); // ..\..\code\game\Item.cpp + REGISTER(idObjectiveComplete); // ..\..\code\game\Item.cpp + REGISTER(idPathCorner); // ..\..\code\game\Misc.cpp + REGISTER(idPendulum); // ..\..\code\game\Mover.cpp + REGISTER(idPhantomObjects); // ..\..\code\game\Misc.cpp + REGISTER(idPhysics); // ..\..\code\game\physics\Physics.cpp + REGISTER(idPhysics_AF); // ..\..\code\game\physics\Physics_AF.cpp + REGISTER(idPhysics_Actor); // ..\..\code\game\physics\Physics_Actor.cpp + REGISTER(idPhysics_Base); // ..\..\code\game\physics\Physics_Base.cpp + REGISTER(idPhysics_Monster); // ..\..\code\game\physics\Physics_Monster.cpp + REGISTER(idPhysics_Parametric); // ..\..\code\game\physics\Physics_Parametric.cpp + REGISTER(idPhysics_Player); // ..\..\code\game\physics\Physics_Player.cpp + REGISTER(idPhysics_RigidBody); // ..\..\code\game\physics\Physics_RigidBody.cpp + REGISTER(idPhysics_Static); // ..\..\code\game\physics\Physics_Static.cpp + REGISTER(idPhysics_StaticMulti); // ..\..\code\game\physics\Physics_StaticMulti.cpp + REGISTER(idPlat); // ..\..\code\game\Mover.cpp + REGISTER(idPlayer); // ..\..\code\game\Player.cpp + REGISTER(idPlayerStart); // ..\..\code\game\Misc.cpp + REGISTER(idProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idRiser); // ..\..\code\game\Mover.cpp + REGISTER(idRotater); // ..\..\code\game\Mover.cpp + REGISTER(idSecurityCamera); // ..\..\code\game\SecurityCamera.cpp + REGISTER(idShaking); // ..\..\code\game\Misc.cpp + REGISTER(idSound); // ..\..\code\game\Sound.cpp + REGISTER(idSpawnableEntity); // ..\..\code\game\Misc.cpp + REGISTER(idSplinePath); // ..\..\code\game\Mover.cpp + REGISTER(idSpring); // ..\..\code\game\Misc.cpp + REGISTER(idStaticEntity); // ..\..\code\game\Misc.cpp + REGISTER(idTarget); // ..\..\code\game\Target.cpp + REGISTER(idTarget_CallObjectFunction); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Damage); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableLevelWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableStamina); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\EndLevel.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeEntity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeSoundClass); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Give); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveEmail); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveSecurity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LevelTrigger); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeIn); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeOut); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LockDoor); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Remove); // ..\..\code\game\Target.cpp + REGISTER(idTarget_RemoveWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SessionCommand); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetFov); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetGlobalShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetInfluence); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetKeyVal); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetModel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetPrimaryObjective); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderParm); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Show); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Tip); // ..\..\code\game\Target.cpp + REGISTER(idTarget_WaitForButton); // ..\..\code\game\Target.cpp + REGISTER(idTestModel); // ..\..\code\game\anim\Anim_Testmodel.cpp + REGISTER(idTextEntity); // ..\..\code\game\Misc.cpp + REGISTER(idThread); // ..\..\code\game\script\Script_Thread.cpp + REGISTER(idTrigger); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Count); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_EntityName); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Fade); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Hurt); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Multi); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Timer); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Touch); // ..\..\code\game\Trigger.cpp + REGISTER(idVacuumEntity); // ..\..\code\game\Misc.cpp + REGISTER(idVacuumSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idWorldspawn); // ..\..\code\game\WorldSpawn.cpp + REGISTER(rvAFAttractor); // ..\..\code\game\AFEntity.cpp + REGISTER(rvAIAvoid); // ..\..\code\game\ai\AI_Helper.cpp + REGISTER(rvAITactical); // ..\..\code\game\ai\AI_Tactical.cpp + REGISTER(rvAIMedic); // ..\..\code\game\ai\AI_Medic.cpp + REGISTER(rvAITether); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherRadius); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherBehind); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherClear); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomePassive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomeAggressive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITrigger); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvCTFAssaultPlayerStart); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCTF_AssaultPoint); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCameraPlayback); // ..\..\code\game\Camera.cpp + REGISTER(rvCameraPortalSky); // ..\..\code\game\Camera.cpp + REGISTER(rvClientCrawlEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEntity); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvClientModel); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvAnimatedClientEntity); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvClientAFEntity); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientAFAttachment); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientMoveable); // ..\..\code\game\client\ClientMoveable.cpp + REGISTER(rvClientPhysics); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvConveyor); // ..\..\code\game\Mover.cpp + REGISTER(rvDarkMatterProjectile); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvDebugJumpPoint); // ..\..\code\game\Misc.cpp + REGISTER(rvDriftingProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvEffect); // ..\..\code\game\Effect.cpp + REGISTER(rvFuncSaveGame); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_Static); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_SurfaceNormal); // ..\..\code\game\Misc.cpp + REGISTER(rvGravitySeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(rvHealingStation); // ..\..\code\game\Healing_Station.cpp + REGISTER(rvItemCTFFlag); // ..\..\code\game\Item.cpp + REGISTER(rvJumpPad); // ..\..\code\game\Misc.cpp + REGISTER(rvMIRVProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvModviewModel); // ..\..\code\game\GameEdit.cpp + REGISTER(rvPusher); // ..\..\code\game\Mover.cpp +#ifndef _MPBETA + REGISTER(rvMonsterBerserker); // ..\..\code\game\ai\Monster_Berserker.cpp + REGISTER(rvMonsterBossBuddy); // ..\..\code\game\ai\Monster_BossBuddy.cpp + REGISTER(rvMonsterBossMakron); // ..\..\code\game\ai\Monster_BossMakron.cpp + REGISTER(rvMonsterConvoyGround); // ..\..\code\game\ai\Monster_ConvoyGround.cpp + REGISTER(rvMonsterConvoyHover); // ..\..\code\game\ai\Monster_ConvoyHover.cpp + REGISTER(rvMonsterFailedTransfer); // ..\..\code\game\ai\Monster_FailedTransfer.cpp + REGISTER(rvMonsterFatty); // ..\..\code\game\ai\Monster_Fatty.cpp + REGISTER(rvMonsterGladiator); // ..\..\code\game\ai\Monster_Gladiator.cpp + REGISTER(rvMonsterGrunt); // ..\..\code\game\ai\Monster_Grunt.cpp + REGISTER(rvMonsterGunner); // ..\..\code\game\ai\Monster_Gunner.cpp + REGISTER(rvMonsterHarvester); // ..\..\code\game\ai\Monster_Harvester.cpp + REGISTER(rvMonsterHarvesterDispersal); // ..\..\code\game\ai\Monster_HarvesterDispersal.cpp + REGISTER(rvMonsterHeavyHoverTank); // ..\..\code\game\ai\Monster_HeavyHoverTank.cpp + REGISTER(rvMonsterIronMaiden); // ..\..\code\game\ai\Monster_IronMaiden.cpp + REGISTER(rvMonsterLightTank); // ..\..\code\game\ai\Monster_LightTank.cpp + REGISTER(rvMonsterNetworkGuardian); // ..\..\code\game\ai\Monster_NetworkGuardian.cpp + REGISTER(rvMonsterRepairBot); // ..\..\code\game\ai\Monster_RepairBot.cpp + REGISTER(rvMonsterScientist); // ..\..\code\game\ai\Monster_Scientist.cpp + REGISTER(rvMonsterSentry); // ..\..\code\game\ai\Monster_Sentry.cpp + REGISTER(rvMonsterSlimyTransfer); // ..\..\code\game\ai\Monster_SlimyTransfer.cpp + REGISTER(rvMonsterStroggMarine);// ..\..\code\game\ai\Monster_StroggMarine.cpp + REGISTER(rvMonsterStreamProtector); // ..\..\code\game\ai\Monster_StreamProtector.cpp + REGISTER(rvMonsterStroggFlyer); // ..\..\code\game\ai\Monster_StroggFlyer.cpp + REGISTER(rvMonsterStroggHover); // ..\..\code\game\ai\Monster_StroggHover.cpp + REGISTER(rvMonsterTeleportDropper); // ..\..\code\game\ai\Monster_TeleportDropper.cpp + REGISTER(rvMonsterTurret); // ..\..\code\game\ai\Monster_Turret.cpp + REGISTER(rvMonsterTurretFlying); // ..\..\code\game\ai\Monster_TurretFlying.cpp +#endif // !_MPBETA + REGISTER(rvObjectiveFailed); // ..\..\code\game\Item.cpp + REGISTER(rvPhysics_Particle); // ..\..\code\game\physics\Physics_Particle.cpp + REGISTER(rvPhysics_VehicleMonster); // ..\..\code\game\physics\Physics_VehicleMonster.cpp + REGISTER(rvPhysics_Spline); // ..\..\code\game\SplineMover.cpp + REGISTER(rvSpawner); // ..\..\code\game\spawner.cpp + REGISTER(rvSpawnerProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvSplineMover); // ..\..\code\game\SplineMover.cpp + // twhitaker: removed database support + // REGISTER(rvTarget_AddDatabaseEntry); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_AmmoStash); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_BossBattle); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_ExitAreaAlert); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_LaunchProjectile); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_Nailable); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_SecretArea); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_TetherAI); // ..\..\code\game\Target.cpp + REGISTER(rvTramCar); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Marine); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Strogg); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramGate); // ..\..\code\game\TramGate.cpp + REGISTER(rvVehicle); // ..\..\code\game\vehicle\Vehicle.cpp + REGISTER(rvVehicleAI); // ..\..\code\game\ai\VehicleAI.cpp + REGISTER(rvVehicleAnimated); // ..\..\code\game\vehicle\VehicleAnimated.cpp + REGISTER(rvVehicleDriver); // ..\..\code\game\vehicle\VehicleDriver.cpp + REGISTER(rvVehicleDropPod); // ..\..\code\game\vehicle\Vehicle_DropPod.cpp + REGISTER(rvVehicleHoverpad); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleLight); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleMonster); // ..\..\code\game\vehicle\VehicleMonster.cpp + REGISTER(rvVehiclePart); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleRigid); // ..\..\code\game\vehicle\VehicleRigid.cpp + REGISTER(rvVehicleSound); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleSpline); // ..\..\code\game\vehicle\VehicleSpline.cpp + REGISTER(rvVehicleStatic); // ..\..\code\game\vehicle\VehicleStatic.cpp + REGISTER(rvVehicleThruster); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleTurret); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleUserAnimated); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleWalker); // ..\..\code\game\vehicle\Vehicle_Walker.cpp + REGISTER(rvVehicleWeapon); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvViewWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeaponBlaster); // ..\..\code\game\weapon\WeaponBlaster.cpp + REGISTER(rvWeaponDarkMatterGun); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvWeaponGauntlet); // ..\..\code\game\weapon\WeaponGauntlet.cpp + REGISTER(rvWeaponGrenadeLauncher); // ..\..\code\game\weapon\WeaponGrenadeLauncher.cpp + REGISTER(rvWeaponHyperblaster); // ..\..\code\game\weapon\WeaponHyperblaster.cpp + REGISTER(rvWeaponLightningGun); // ..\..\code\game\weapon\WeaponLightningGun.cpp + REGISTER(rvWeaponMachinegun); // ..\..\code\game\weapon\WeaponMachinegun.cpp + REGISTER(rvWeaponNailgun); // ..\..\code\game\weapon\WeaponNailgun.cpp + REGISTER(rvWeaponRailgun); // ..\..\code\game\weapon\WeaponRailgun.cpp + REGISTER(rvWeaponRocketLauncher); // ..\..\code\game\weapon\WeaponRocketLauncher.cpp + REGISTER(rvWeaponShotgun); // ..\..\code\game\weapon\WeaponShotgun.cpp +// RITUAL BEGIN + REGISTER(riDeadZonePowerup); // ..\..\code\game\Item.cpp + REGISTER(WeaponNapalmGun); // ..\..\code\game\weapon\WeaponNapalmGun.cpp +// RITUAL END +#undef REGISTER +} + +// RAVEN END + diff --git a/source/mpgame/gamesys/Class.h b/source/mpgame/gamesys/Class.h new file mode 100644 index 0000000..b551461 --- /dev/null +++ b/source/mpgame/gamesys/Class.h @@ -0,0 +1,526 @@ +/* + +Base class for all game objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#ifndef __SYS_CLASS_H__ +#define __SYS_CLASS_H__ + +class idClass; +class idTypeInfo; + +// RAVEN BEGIN +extern const idEventDef EV_PostRestore; +// RAVEN END +extern const idEventDef EV_Remove; +extern const idEventDef EV_SafeRemove; + +typedef void ( idClass::*eventCallback_t )( void ); + +template< class Type > +struct idEventFunc { + const idEventDef *event; + eventCallback_t function; +}; + +// added & so gcc could compile this +#define EVENT( event, function ) { &( event ), ( void ( idClass::* )( void ) )( &function ) }, +#define END_CLASS { NULL, NULL } }; + +class idEventArg { +public: + int type; + int value; + + idEventArg() { type = D_EVENT_INTEGER; value = 0; }; + idEventArg( int data ) { type = D_EVENT_INTEGER; value = data; }; + idEventArg( float data ) { type = D_EVENT_FLOAT; value = *reinterpret_cast( &data ); }; + idEventArg( const idVec3 &data ) { type = D_EVENT_VECTOR; value = reinterpret_cast( &data ); }; + idEventArg( const idStr &data ) { type = D_EVENT_STRING; value = reinterpret_cast( data.c_str() ); }; + idEventArg( const char *data ) { type = D_EVENT_STRING; value = reinterpret_cast( data ); }; + idEventArg( const class idEntity *data ) { type = D_EVENT_ENTITY; value = reinterpret_cast( data ); }; + idEventArg( const trace_t *data ) { type = D_EVENT_TRACE; value = reinterpret_cast( data ); }; +}; + +class idAllocError : public idException { +public: + idAllocError( const char *text = "" ) : idException( text ) {} +}; + +/*********************************************************************** + + idClass + +***********************************************************************/ + +/* +================ +CLASS_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance concrete classes only. +================ +*/ +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +CLASS_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking and run-time instanciation. It also defines the list +of events that the class responds to. Take special care to ensure that the +proper superclass is indicated or the run-time type information will be +incorrect. Use this on concrete classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: Added states +// jnewquist: Use accessor for static class type +// mwhitlock: Dynamic memory consolidation +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + + +/* +================ +ABSTRACT_PROTOTYPE + +This macro must be included in the definition of any abstract subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +ABSTRACT_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking. It also defines the list of events that the class +responds to. Take special care to ensure that the proper superclass is +indicated or the run-time tyep information will be incorrect. Use this +on abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: added states +// jnewquist: Use accessor for static class type +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +typedef void ( idClass::*classSpawnFunc_t )( void ); + +class idSaveGame; +class idRestoreGame; + +class idClass { +public: + ABSTRACT_PROTOTYPE( idClass ); + +#ifdef ID_REDIRECT_NEWDELETE +#undef new +#endif + void * operator new( size_t ); + void * operator new( size_t s, int, int, char *, int ); + void operator delete( void * ); + void operator delete( void *, int, int, char *, int ); +#ifdef ID_REDIRECT_NEWDELETE +#define new ID_DEBUG_NEW +#endif + + virtual ~idClass(); + + void Spawn( void ); + void CallSpawn( void ); + bool IsType( const idTypeInfo &c ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *c ) const { return IsType(*c); } +// RAVEN END + const char * GetClassname( void ) const; + const char * GetSuperclass( void ) const; + void FindUninitializedMemory( void ); + + void Save( idSaveGame *savefile ) const {}; + void Restore( idRestoreGame *savefile ) {}; + + bool RespondsTo( const idEventDef &ev ) const; + +// RAVEN BEGIN +// bdube: states + stateResult_t ProcessState ( const rvStateFunc* state, const stateParms_t& parms ); + stateResult_t ProcessState ( const char* name, const stateParms_t& parms ); + const rvStateFunc* FindState ( const char* name ) const; + +// bdube: client entities + virtual bool IsClient ( void ) const; + +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + static void RegisterClasses( void ); +// RAVEN END + + bool PostEventMS( const idEventDef *ev, int time ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool PostEventSec( const idEventDef *ev, float time ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEvent( const idEventDef *ev ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEventArgPtr( const idEventDef *ev, int *data ); + void CancelEvents( const idEventDef *ev ); +// RAVEN BEGIN +// abahr: + bool EventIsPosted( const idEventDef *ev ) const; + + void Event_PostRestore( void ) {} +// RAVEN END + void Event_Remove( void ); + + // Static functions + static void Init( void ); + static void Shutdown( void ); + static idTypeInfo * GetClass( const char *name ); + static void DisplayInfo_f( const idCmdArgs &args ); + static void ListClasses_f( const idCmdArgs &args ); + static idClass * CreateInstance( const char *name ); + static int GetNumTypes( void ) { return types.Num(); } + static int GetTypeNumBits( void ) { return typeNumBits; } + static idTypeInfo * GetType( int num ); + +// RAVEN BEGIN +// jscott: for memory profiling + static size_t GetUsedMemory( void ) { return( memused ); } + +// bdube: debug info + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +private: + classSpawnFunc_t CallSpawnFunc( idTypeInfo *cls ); + + bool PostEventArgs( const idEventDef *ev, int time, int numargs, ... ); + bool ProcessEventArgs( const idEventDef *ev, int numargs, ... ); + + void Event_SafeRemove( void ); + + static bool initialized; + static idList types; + static idList typenums; + static int typeNumBits; + static int memused; + static int numobjects; + +// RAVEN BEGIN +// bdube: states + CLASS_STATES_PROTOTYPE(idClass); +// RAVEN END +}; + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +class idTypeInfo { +public: + const char * classname; + const char * superclass; + idClass * ( *CreateInstance )( void ); + void ( idClass::*Spawn )( void ); + void ( idClass::*Save )( idSaveGame *savefile ) const; + void ( idClass::*Restore )( idRestoreGame *savefile ); + +// RAVEN BEGIN +// bdube: added + rvStateFunc * stateCallbacks; +// RAVEN END + + idEventFunc * eventCallbacks; + eventCallback_t * eventMap; + idTypeInfo * super; + idTypeInfo * next; + bool freeEventMap; + int typeNum; + int lastChild; + + idHierarchy node; + + idTypeInfo( const char *classname, const char *superclass, + idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), void ( idClass::*Spawn )( void ), +// RAVEN BEGIN +// bdube: added + rvStateFunc *stateCallbacks, +// RAVEN END + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ); + ~idTypeInfo(); + + void Init( void ); + void Shutdown( void ); + + bool IsType( const idTypeInfo &superclass ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *superclass ) const { return IsType(*superclass); } +// RAVEN END + bool RespondsTo( const idEventDef &ev ) const; +}; + +/* +================ +idTypeInfo::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idTypeInfo::IsType( const idTypeInfo &type ) const { + return ( ( typeNum >= type.typeNum ) && ( typeNum <= type.lastChild ) ); +} + +/* +================ +idTypeInfo::RespondsTo +================ +*/ +ID_INLINE bool idTypeInfo::RespondsTo( const idEventDef &ev ) const { + assert( idEvent::initialized ); + if ( !eventMap[ ev.GetEventNum() ] ) { + // we don't respond to this event + return false; + } + + return true; +} + +/* +================ +idClass::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idClass::IsType( const idTypeInfo &superclass ) const { + idTypeInfo *subclass; + + subclass = GetType(); + return subclass->IsType( superclass ); +} + +/* +================ +idClass::RespondsTo +================ +*/ +ID_INLINE bool idClass::RespondsTo( const idEventDef &ev ) const { + const idTypeInfo *c; + + assert( idEvent::initialized ); + c = GetType(); + return c->RespondsTo( ev ); +} + +#endif /* !__SYS_CLASS_H__ */ diff --git a/source/mpgame/gamesys/DebugGraph.cpp b/source/mpgame/gamesys/DebugGraph.cpp new file mode 100644 index 0000000..8e2f8d2 --- /dev/null +++ b/source/mpgame/gamesys/DebugGraph.cpp @@ -0,0 +1,68 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idDebugGraph::idDebugGraph +================ +*/ +idDebugGraph::idDebugGraph() { + index = 0; +} + +/* +================ +idDebugGraph::SetNumSamples +================ +*/ +void idDebugGraph::SetNumSamples( int num ) { + index = 0; + samples.Clear(); + samples.SetNum( num ); + memset( samples.Ptr(), 0, samples.MemoryUsed() ); +} + +/* +================ +idDebugGraph::AddValue +================ +*/ +void idDebugGraph::AddValue( float value ) { + samples[ index ] = value; + index++; + if ( index >= samples.Num() ) { + index = 0; + } +} + +/* +================ +idDebugGraph::Draw +================ +*/ +void idDebugGraph::Draw( const idVec4 &color, float scale ) const { + int i; + float value1; + float value2; + idVec3 vec1; + idVec3 vec2; + + const idMat3 &axis = gameLocal.GetLocalPlayer()->viewAxis; + const idVec3 pos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() + axis[ 1 ] * samples.Num() * 0.5f; + + value1 = samples[ index ] * scale; + for( i = 1; i < samples.Num(); i++ ) { + value2 = samples[ ( i + index ) % samples.Num() ] * scale; + + vec1 = pos + axis[ 2 ] * value1 - axis[ 1 ] * ( i - 1 ) + axis[ 0 ] * samples.Num(); + vec2 = pos + axis[ 2 ] * value2 - axis[ 1 ] * i + axis[ 0 ] * samples.Num(); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + gameRenderWorld->DebugLine( color, vec1, vec2, gameLocal.GetMSec ( ), false ); +// RAVEN END + value1 = value2; + } +} diff --git a/source/mpgame/gamesys/DebugGraph.h b/source/mpgame/gamesys/DebugGraph.h new file mode 100644 index 0000000..bafc30e --- /dev/null +++ b/source/mpgame/gamesys/DebugGraph.h @@ -0,0 +1,13 @@ +// DebugGraph.h + +class idDebugGraph { +public: + idDebugGraph(); + void SetNumSamples( int num ); + void AddValue( float value ); + void Draw( const idVec4 &color, float scale ) const; + +private: + idList samples; + int index; +}; diff --git a/source/mpgame/gamesys/Event.cpp b/source/mpgame/gamesys/Event.cpp new file mode 100644 index 0000000..e063c46 --- /dev/null +++ b/source/mpgame/gamesys/Event.cpp @@ -0,0 +1,813 @@ +/* +sys_event.cpp + +Event are used for scheduling tasks and for linking script commands. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_EVENTSPERFRAME 8192 // Upped from 4096 +//#define CREATE_EVENT_CODE + +/*********************************************************************** + + idEventDef + +***********************************************************************/ + +idEventDef *idEventDef::eventDefList[MAX_EVENTS]; +int idEventDef::numEventDefs = 0; + +static bool eventError = false; +static char eventErrorMsg[ 128 ]; + +/* +================ +idEventDef::idEventDef +================ +*/ +idEventDef::idEventDef( const char *command, const char *formatspec, char returnType ) { + idEventDef *ev; + int i; + unsigned int bits; + + assert( command ); + assert( !idEvent::initialized ); + + // Allow NULL to indicate no args, but always store it as "" + // so we don't have to check for it. + if ( !formatspec ) { + formatspec = ""; + } + + this->name = command; + this->formatspec = formatspec; + this->returnType = returnType; + + numargs = strlen( formatspec ); + assert( numargs <= D_EVENT_MAXARGS ); + if ( numargs > D_EVENT_MAXARGS ) { + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Too many args for '%s' event.", name ); + return; + } + + // make sure the format for the args is valid, calculate the formatspecindex, and the offsets for each arg + bits = 0; + argsize = 0; + memset( argOffset, 0, sizeof( argOffset ) ); + for( i = 0; i < numargs; i++ ) { + argOffset[ i ] = argsize; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + bits |= 1 << i; + argsize += sizeof( float ); + break; + + case D_EVENT_INTEGER : + argsize += sizeof( int ); + break; + + case D_EVENT_VECTOR : + argsize += sizeof( idVec3 ); + break; + + case D_EVENT_STRING : + argsize += MAX_STRING_LEN; + break; + + case D_EVENT_ENTITY : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_ENTITY_NULL : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_TRACE : + argsize += sizeof( trace_t ) + MAX_STRING_LEN + sizeof( bool ); + break; + + default : + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Invalid arg format '%s' string for '%s' event.", formatspec, name ); + return; + break; + } + } + + // calculate the formatspecindex + formatspecIndex = ( 1 << ( numargs + D_EVENT_MAXARGS ) ) | bits; + + // go through the list of defined events and check for duplicates + // and mismatched format strings + eventnum = numEventDefs; + for( i = 0; i < eventnum; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( command, ev->name ) == 0 ) { + if ( idStr::Cmp( formatspec, ev->formatspec ) != 0 ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing format strings ('%s'!='%s').", + command, formatspec, ev->formatspec ); + return; + } + + if ( ev->returnType != returnType ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing return types ('%c'!='%c').", + command, returnType, ev->returnType ); + return; + } + // Don't bother putting the duplicate event in list. + eventnum = ev->eventnum; + return; + } + } + + ev = this; + + if ( numEventDefs >= MAX_EVENTS ) { + eventError = true; + sprintf( eventErrorMsg, "numEventDefs >= MAX_EVENTS" ); + return; + } + eventDefList[numEventDefs] = ev; + numEventDefs++; +} + +/* +================ +idEventDef::NumEventCommands +================ +*/ +int idEventDef::NumEventCommands( void ) { + return numEventDefs; +} + +/* +================ +idEventDef::GetEventCommand +================ +*/ +const idEventDef *idEventDef::GetEventCommand( int eventnum ) { + return eventDefList[ eventnum ]; +} + +/* +================ +idEventDef::FindEvent +================ +*/ +const idEventDef *idEventDef::FindEvent( const char *name ) { + idEventDef *ev; + int num; + int i; + + assert( name ); + + num = numEventDefs; + for( i = 0; i < num; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( name, ev->name ) == 0 ) { + return ev; + } + } + + return NULL; +} + +/*********************************************************************** + + idEvent + +***********************************************************************/ + +static idLinkList FreeEvents; +static idLinkList EventQueue; +static idEvent EventPool[ MAX_EVENTS ]; + +bool idEvent::initialized = false; + +idDynamicBlockAlloc idEvent::eventDataAllocator; + +/* +================ +idEvent::~idEvent() +================ +*/ +idEvent::~idEvent() { + Free(); +} + + +void idEvent::WriteDebugInfo( void ) { + idEvent *event; + int count = 0; + + idFile *FH = fileSystem->OpenFileAppend( "idEvents.txt" ); + + FH->Printf( "Num Events = %d\n", EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + count++; + + FH->Printf( "%d. %d - %s - %s - %s\n", count, event->time, event->eventdef->GetName(), event->typeinfo->classname, event->object->GetClassname() ); + + event = event->eventNode.Next(); + } + + FH->Printf( "\n\n" ); + fileSystem->CloseFile( FH ); +} + +/* +================ +idEvent::Alloc +================ +*/ +idEvent *idEvent::Alloc( const idEventDef *evdef, int numargs, va_list args ) { + idEvent *ev; + size_t size; + const char *format; + idEventArg *arg; + byte *dataPtr; + int i; + const char *materialName; + + if ( FreeEvents.IsListEmpty() ) { + WriteDebugInfo( ); + gameLocal.Error( "idEvent::Alloc : No more free events for '%s' event.", evdef->GetName() ); + } + + ev = FreeEvents.Next(); + ev->eventNode.Remove(); + + ev->eventdef = evdef; + + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::Alloc : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + size = evdef->GetArgSize(); + if ( size ) { + ev->data = eventDataAllocator.Alloc( size ); + memset( ev->data, 0, size ); + } else { + ev->data = NULL; + } + + format = evdef->GetArgFormat(); + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) || ( format[ i ] == D_EVENT_TRACE ) ) { + // when NULL is passed in for an entity or trace, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + dataPtr = &ev->data[ evdef->GetArgOffset( i ) ]; + + switch( format[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + *reinterpret_cast( dataPtr ) = arg->value; + break; + + case D_EVENT_VECTOR : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = *reinterpret_cast( arg->value ); + } + break; + + case D_EVENT_STRING : + if ( arg->value ) { + idStr::Copynz( reinterpret_cast( dataPtr ), reinterpret_cast( arg->value ), MAX_STRING_LEN ); + } + break; + +// RAVEN BEGIN +// abahr: type checking change as per Jim D. +// jshepard: TODO FIXME HACK this never ever produces desired, positive results. Events should be built to prepare for null entities, especially when dealing with +// script events. This will throw a warning, and events should be prepared to deal with null entities. + case D_EVENT_ENTITY : + if ( reinterpret_cast( arg->value ) == NULL ) { + gameLocal.Warning( "idEvent::Alloc : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, evdef->GetName() ); + } + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; +//RAVEN END + + case D_EVENT_TRACE : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = true; + *reinterpret_cast( dataPtr + sizeof( bool ) ) = *reinterpret_cast( arg->value ); + + // save off the material as a string since the pointer won't be valid in save games. + // since we save off the entire trace_t structure, if the material is NULL here, + // it will be NULL when we process it, so we don't need to save off anything in that case. + if ( reinterpret_cast( arg->value )->c.material ) { + materialName = reinterpret_cast( arg->value )->c.material->GetName(); + idStr::Copynz( reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ), materialName, MAX_STRING_LEN ); + } + } else { + *reinterpret_cast( dataPtr ) = false; + } + break; + + default : + gameLocal.Error( "idEvent::Alloc : Invalid arg format '%s' string for '%s' event.", format, evdef->GetName() ); + break; + } + } + + return ev; +} + +/* +================ +idEvent::CopyArgs +================ +*/ +void idEvent::CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ) { + int i; + const char *format; + idEventArg *arg; + + format = evdef->GetArgFormat(); + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + data[ i ] = arg->value; + } +} + +/* +================ +idEvent::Free +================ +*/ +void idEvent::Free( void ) { + if ( data ) { + eventDataAllocator.Free( data ); + data = NULL; + } + + eventdef = NULL; + time = 0; + object = NULL; + typeinfo = NULL; + + eventNode.SetOwner( this ); + eventNode.AddToEnd( FreeEvents ); +} + +/* +================ +idEvent::Schedule +================ +*/ +void idEvent::Schedule( idClass *obj, const idTypeInfo *type, int time ) { + idEvent *event; + + assert( initialized ); + if ( !initialized ) { + return; + } + + object = obj; + typeinfo = type; + + // wraps after 24 days...like I care. ;) + this->time = gameLocal.time + time; + + eventNode.Remove(); + + event = EventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( EventQueue ); + } +} + +/* +================ +idEvent::CancelEvents +================ +*/ +void idEvent::CancelEvents( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEvent::EventIsPosted +================ +*/ +bool idEvent::EventIsPosted( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return false; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if( event->object == obj && evdef == event->eventdef ) { + return true; + } + } + + return false; +} +// RAVEN END + +/* +================ +idEvent::ClearEventList +================ +*/ +void idEvent::ClearEventList( void ) { + int i; + + // + // initialize lists + // + FreeEvents.Clear(); + EventQueue.Clear(); + + // + // add the events to the free list + // + for( i = 0; i < MAX_EVENTS; i++ ) { + EventPool[ i ].Free(); + } +} + +/* +================ +idEvent::ServiceEvents +================ +*/ +void idEvent::ServiceEvents( void ) { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !EventQueue.IsListEmpty() ) { + +#ifdef _XENON + session->PacifierUpdate(); +#endif + + event = EventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.time ) { + break; + } + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + case D_EVENT_ENTITY : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + if ( *reinterpret_cast( &args[ i ] ) == NULL ) { + gameLocal.Warning( "idEvent::ServiceEvents : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, ev->GetName() ); + } + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; +// RAVEN END + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + + // savegames can trash the object, so do this for safety + if ( event->object ) { + event->object->ProcessEventArgPtr( ev, args ); + } + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, ev->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::Init +================ +*/ +void idEvent::Init( void ) { + gameLocal.Printf( "Initializing event system\n" ); + + if ( eventError ) { + gameLocal.Error( "%s", eventErrorMsg ); + } + +#ifdef CREATE_EVENT_CODE + void CreateEventCallbackHandler(); + CreateEventCallbackHandler(); + gameLocal.Error( "Wrote event callback handler" ); +#endif + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + ClearEventList(); + return; + } + + ClearEventList(); + + eventDataAllocator.Init(); + + gameLocal.Printf( "...%i event definitions\n", idEventDef::NumEventCommands() ); + + // the event system has started + initialized = true; +} + +/* +================ +idEvent::Shutdown +================ +*/ +void idEvent::Shutdown( void ) { + gameLocal.Printf( "Shutdown event system\n" ); + + if ( !initialized ) { + gameLocal.Printf( "...not started\n" ); + return; + } + + ClearEventList(); + + eventDataAllocator.Shutdown(); + + // say it is now shutdown + initialized = false; +} + +/* +================ +idEvent::Save +================ +*/ +void idEvent::Save( idSaveGame *savefile ) { + idEvent *event; + + savefile->WriteInt( EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + savefile->Write( event->data, event->eventdef->GetArgSize() ); + + event = event->eventNode.Next(); + } +} + +/* +================ +idEvent::Restore +================ +*/ +void idEvent::Restore( idRestoreGame *savefile ) { + int i; + int num; + int argsize; + idStr name; + idEvent *event; + + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( EventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( !event->eventdef ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( !event->typeinfo ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + } + + savefile->ReadObject( event->object ); + assert( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + savefile->Read( event->data, argsize ); + } else { + event->data = NULL; + } + } +} + +#ifdef CREATE_EVENT_CODE +/* +================ +CreateEventCallbackHandler +================ +*/ +void CreateEventCallbackHandler( void ) { + int num; + int count; + int i, j, k; + char argString[ D_EVENT_MAXARGS + 1 ]; + idStr string1; + idStr string2; + idFile *file; + + file = fileSystem->OpenFileWrite( "Callbacks.cpp" ); + + file->Printf( "// generated file - see CREATE_EVENT_CODE\n\n" ); + + for( i = 1; i <= D_EVENT_MAXARGS; i++ ) { + + file->Printf( "\t/*******************************************************\n\n\t\t%d args\n\n\t*******************************************************/\n\n", i ); + + for ( j = 0; j < ( 1 << i ); j++ ) { + for ( k = 0; k < i; k++ ) { + argString[ k ] = j & ( 1 << k ) ? 'f' : 'i'; + } + argString[ i ] = '\0'; + + string1.Empty(); + string2.Empty(); + + for( k = 0; k < i; k++ ) { + if ( j & ( 1 << k ) ) { + string1 += "const float"; + string2 += va( "*( float * )&data[ %d ]", k ); + } else { + string1 += "const int"; + string2 += va( "data[ %d ]", k ); + } + + if ( k < i - 1 ) { + string1 += ", "; + string2 += ", "; + } + } + + file->Printf( "\tcase %d :\n\t\ttypedef void ( idClass::*eventCallback_%s_t )( %s );\n", ( 1 << ( i + D_EVENT_MAXARGS ) ) + j, argString, string1.c_str() ); + file->Printf( "\t\t( this->*( eventCallback_%s_t )callback )( %s );\n\t\tbreak;\n\n", argString, string2.c_str() ); + + } + } + + fileSystem->CloseFile( file ); +} + +#endif diff --git a/source/mpgame/gamesys/Event.h b/source/mpgame/gamesys/Event.h new file mode 100644 index 0000000..1bf6d7a --- /dev/null +++ b/source/mpgame/gamesys/Event.h @@ -0,0 +1,184 @@ +/* +sys_event.h + +Event are used for scheduling tasks and for linking script commands. +*/ +#ifndef __SYS_EVENT_H__ +#define __SYS_EVENT_H__ + +#define D_EVENT_MAXARGS 8 // if changed, enable the CREATE_EVENT_CODE define in Event.cpp to generate switch statement for idClass::ProcessEventArgPtr. + // running the game will then generate c:\doom\base\events.txt, the contents of which should be copied into the switch statement. + +#define D_EVENT_VOID ( ( char )0 ) +#define D_EVENT_INTEGER 'd' +#define D_EVENT_FLOAT 'f' +#define D_EVENT_VECTOR 'v' +#define D_EVENT_STRING 's' +#define D_EVENT_ENTITY 'e' +#define D_EVENT_ENTITY_NULL 'E' // event can handle NULL entity pointers +#define D_EVENT_TRACE 't' + +#define MAX_EVENTS 8192 // Upped from 4096 + +class idClass; +class idTypeInfo; + +class idEventDef { +private: + const char *name; + const char *formatspec; + unsigned int formatspecIndex; + int returnType; + int numargs; + size_t argsize; + int argOffset[ D_EVENT_MAXARGS ]; + int eventnum; + const idEventDef * next; + + static idEventDef * eventDefList[MAX_EVENTS]; + static int numEventDefs; + +public: + idEventDef( const char *command, const char *formatspec = NULL, char returnType = 0 ); + + const char *GetName( void ) const; + const char *GetArgFormat( void ) const; + unsigned int GetFormatspecIndex( void ) const; + char GetReturnType( void ) const; + int GetEventNum( void ) const; + int GetNumArgs( void ) const; + size_t GetArgSize( void ) const; + int GetArgOffset( int arg ) const; + + static int NumEventCommands( void ); + static const idEventDef *GetEventCommand( int eventnum ); + static const idEventDef *FindEvent( const char *name ); +}; + +class idSaveGame; +class idRestoreGame; + +class idEvent { +private: + const idEventDef *eventdef; + byte *data; + int time; + idClass *object; + const idTypeInfo *typeinfo; + + idLinkList eventNode; + + static idDynamicBlockAlloc eventDataAllocator; + + +public: + static bool initialized; + + ~idEvent(); + + static void WriteDebugInfo( void ); + static idEvent *Alloc( const idEventDef *evdef, int numargs, va_list args ); + static void CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ); + + void Free( void ); + void Schedule( idClass *object, const idTypeInfo *cls, int time ); + byte *GetData( void ); + + static void CancelEvents( const idClass *obj, const idEventDef *evdef = NULL ); +// RAVEN BEGIN +// abahr: + static bool EventIsPosted( const idClass *obj, const idEventDef *evdef ); +// RAVEN END + static void ClearEventList( void ); + static void ServiceEvents( void ); + static void Init( void ); + static void Shutdown( void ); + + // save games + static void Save( idSaveGame *savefile ); // archives object for save game file + static void Restore( idRestoreGame *savefile ); // unarchives object from save game file +}; + +/* +================ +idEvent::GetData +================ +*/ +ID_INLINE byte *idEvent::GetData( void ) { + return data; +} + +/* +================ +idEventDef::GetName +================ +*/ +ID_INLINE const char *idEventDef::GetName( void ) const { + return name; +} + +/* +================ +idEventDef::GetArgFormat +================ +*/ +ID_INLINE const char *idEventDef::GetArgFormat( void ) const { + return formatspec; +} + +/* +================ +idEventDef::GetFormatspecIndex +================ +*/ +ID_INLINE unsigned int idEventDef::GetFormatspecIndex( void ) const { + return formatspecIndex; +} + +/* +================ +idEventDef::GetReturnType +================ +*/ +ID_INLINE char idEventDef::GetReturnType( void ) const { + return returnType; +} + +/* +================ +idEventDef::GetNumArgs +================ +*/ +ID_INLINE int idEventDef::GetNumArgs( void ) const { + return numargs; +} + +/* +================ +idEventDef::GetArgSize +================ +*/ +ID_INLINE size_t idEventDef::GetArgSize( void ) const { + return argsize; +} + +/* +================ +idEventDef::GetArgOffset +================ +*/ +ID_INLINE int idEventDef::GetArgOffset( int arg ) const { + assert( ( arg >= 0 ) && ( arg < D_EVENT_MAXARGS ) ); + return argOffset[ arg ]; +} + +/* +================ +idEventDef::GetEventNum +================ +*/ +ID_INLINE int idEventDef::GetEventNum( void ) const { + return eventnum; +} + +#endif /* !__SYS_EVENT_H__ */ diff --git a/source/mpgame/gamesys/NoGameTypeInfo.h b/source/mpgame/gamesys/NoGameTypeInfo.h new file mode 100644 index 0000000..532df85 --- /dev/null +++ b/source/mpgame/gamesys/NoGameTypeInfo.h @@ -0,0 +1,56 @@ + +#ifndef __GAMETYPEINFO_H__ +#define __GAMETYPEINFO_H__ + +/* +=================================================================================== + + This file has been generated with the Type Info Generator v1.0 (c) 2004 id Software + +=================================================================================== +*/ + +typedef struct { + const char * name; + const char * type; + const char * value; +} constantInfo_t; + +typedef struct { + const char * name; + int value; +} enumValueInfo_t; + +typedef struct { + const char * typeName; + const enumValueInfo_t * values; +} enumTypeInfo_t; + +typedef struct { + const char * type; + const char * name; + int offset; + int size; +} classVariableInfo_t; + +typedef struct { + const char * typeName; + const char * superType; + int size; + const classVariableInfo_t * variables; +} classTypeInfo_t; + + +static constantInfo_t constantInfo[] = { + { NULL, NULL, NULL } +}; + +static enumTypeInfo_t enumTypeInfo[] = { + { NULL, NULL } +}; + +static classTypeInfo_t classTypeInfo[] = { + { NULL, NULL, 0, NULL } +}; + +#endif /* !__GAMETYPEINFO_H__ */ diff --git a/source/mpgame/gamesys/SaveGame.cpp b/source/mpgame/gamesys/SaveGame.cpp new file mode 100644 index 0000000..b475bc0 --- /dev/null +++ b/source/mpgame/gamesys/SaveGame.cpp @@ -0,0 +1,2101 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +Save game related helper classes. + +Save games are implemented in two classes, idSaveGame and idRestoreGame, that implement write/read functions for +common types. They're passed in to each entity and object for them to archive themselves. Each class +implements save/restore functions for it's own data. When restoring, all the objects are instantiated, +then the restore function is called on each, superclass first, then subclasses. + +Pointers are restored by saving out an object index for each unique object pointer and adding them to a list of +objects that are to be saved. Restore instantiates all the objects in the list before calling the Restore function +on each object so that the pointers returned are valid. No object's restore function should rely on any other objects +being fully instantiated until after the restore process is complete. Post restore fixup should be done by posting +events with 0 delay. + +The savegame header will have the Game Name, Version, Map Name, and Player Persistent Info. + +Changes in version make savegames incompatible, and the game will start from the beginning of the level with +the player's persistent info. + +Changes to classes that don't need to break compatibilty can use the build number as the savegame version. +Later versions are responsible for restoring from previous versions by ignoring any unused data and initializing +variables that weren't in previous versions with safe information. + +At the head of the save game is enough information to restore the player to the beginning of the level should the +file be unloadable in some way (for example, due to script changes). +*/ + +// RAVEN BEGIN +// jscott: sanity length check for strings +#define MAX_PRINT_MSG 4096 +// RAVEN END + +/* +================ +idSaveGame::idSaveGame() +================ +*/ +idSaveGame::idSaveGame( idFile *savefile ) { + + file = savefile; + + // Put NULL at the start of the list so we can skip over it. + objects.Clear(); + objects.Append( NULL ); +} + +/* +================ +idSaveGame::~idSaveGame() +================ +*/ +idSaveGame::~idSaveGame( void ) { + if ( objects.Num() ) { + Close(); + } +} + +/* +================ +idSaveGame::Close +================ +*/ +void idSaveGame::Close( void ) { + int i; + + WriteSoundCommands(); + + // read trace models + idClipModel::SaveTraceModels( this ); + + for( i = 1; i < objects.Num(); i++ ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( objects[ i ]->GetType(), objects[ i ] ); + } + + objects.Clear(); + +#ifdef ID_DEBUG_MEMORY +// RAVEN BEGIN +// jscott: don't use type info +// idStr gameState = file->GetName(); +// gameState.StripFileExtension(); +// WriteGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ), false ) ); +// RAVEN END +#endif +} + +/* +================ +idSaveGame::WriteObjectList +================ +*/ +void idSaveGame::WriteObjectList( void ) { + int i; + + WriteInt( objects.Num() - 1 ); + for( i = 1; i < objects.Num(); i++ ) { + WriteString( objects[ i ]->GetClassname() ); + } +} + +/* +================ +idSaveGame::CallSave_r +================ +*/ +void idSaveGame::CallSave_r( const idTypeInfo *cls, const idClass *obj ) { + if ( cls->super ) { + CallSave_r( cls->super, obj ); + if ( cls->super->Save == cls->Save ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + WriteSyncId(); + ( obj->*cls->Save )( this ); + WriteSyncId(); +} + +/* +================ +idSaveGame::AddObject +================ +*/ +void idSaveGame::AddObject( const idClass *obj ) { + objects.AddUnique( obj ); +} + +/* +================ +idSaveGame::WriteSyncId +================ +*/ +void idSaveGame::WriteSyncId( void ) { + file->WriteSyncId(); +} + +/* +================ +idSaveGame::Write +================ +*/ +void idSaveGame::Write( const void *buffer, int len ) { + file->Write( buffer, len ); +} + +/* +================ +idSaveGame::WriteInt +================ +*/ +void idSaveGame::WriteInt( const int value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteJoint +================ +*/ +void idSaveGame::WriteJoint( const jointHandle_t value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteShort +================ +*/ +void idSaveGame::WriteShort( const short value ) { + file->WriteShort( value ); +} + +/* +================ +idSaveGame::WriteByte +================ +*/ +void idSaveGame::WriteByte( const byte value ) { + file->WriteUnsignedChar( value ); +} + +/* +================ +idSaveGame::WriteSignedChar +================ +*/ +void idSaveGame::WriteSignedChar( const signed char value ) { + file->WriteChar( value ); +} + +/* +================ +idSaveGame::WriteFloat +================ +*/ +void idSaveGame::WriteFloat( const float value ) { + file->WriteFloat( value ); +} + +/* +================ +idSaveGame::WriteBool +================ +*/ +void idSaveGame::WriteBool( const bool value ) { + file->WriteBool( value ); +} + +/* +================ +idSaveGame::WriteString +================ +*/ +void idSaveGame::WriteString( const char *string ) { + int len; + + len = strlen( string ); + +// RAVEN BEGIN +// jscott: added safety check for silly length strings + if( len < 0 || len >= MAX_PRINT_MSG ) { + + common->Error( "idSaveGame::WriteString invalid string length (%d)", len ); + } +// RAVEN END + + file->WriteString( string ); +} + +/* +================ +idSaveGame::WriteVec2 +================ +*/ +void idSaveGame::WriteVec2( const idVec2 &vec ) { + file->WriteVec2( vec ); +} + +/* +================ +idSaveGame::WriteVec3 +================ +*/ +void idSaveGame::WriteVec3( const idVec3 &vec ) { + file->WriteVec3( vec ); +} + +/* +================ +idSaveGame::WriteVec4 +================ +*/ +void idSaveGame::WriteVec4( const idVec4 &vec ) { + file->WriteVec4( vec ); +} + +/* +================ +idSaveGame::WriteVec5 +================ +*/ +void idSaveGame::WriteVec5( const idVec5 &vec ) { + file->WriteVec5( vec ); +} + +/* +================ +idSaveGame::WriteVec6 +================ +*/ +void idSaveGame::WriteVec6( const idVec6 &vec ) { + file->WriteVec6( vec ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteBounds( const idBounds &bounds ) { + file->Write( &bounds, sizeof( bounds ) ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteWinding( const idWinding &w ) +{ + int i, num; + num = w.GetNumPoints(); + WriteInt( num ); + for ( i = 0; i < num; i++ ) { + WriteVec5( w[i] ); + } +} + + +/* +================ +idSaveGame::WriteMat3 +================ +*/ +void idSaveGame::WriteMat3( const idMat3 &mat ) { + file->WriteMat3( mat ); +} + +/* +================ +idSaveGame::WriteAngles +================ +*/ +void idSaveGame::WriteAngles( const idAngles &angles ) { + file->Write( &angles, sizeof( angles ) ); +} + +/* +================ +idSaveGame::WriteObject +================ +*/ +void idSaveGame::WriteObject( const idClass *obj ) { + int index; + + index = objects.FindIndex( obj ); + if ( index < 0 ) { + gameLocal.DPrintf( "idSaveGame::WriteObject - WriteObject FindIndex failed\n" ); + + // Use the NULL index + index = 0; + } + + WriteInt( index ); +} + +/* +================ +idSaveGame::WriteStaticObject +================ +*/ +void idSaveGame::WriteStaticObject( const idClass &obj ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( obj.GetType(), &obj ); +} + +/* +================ +idSaveGame::WriteDict +================ +*/ +void idSaveGame::WriteDict( const idDict *dict ) { + int num; + int i; + const idKeyValue *kv; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !dict ) { + WriteInt( -1 ); + } else { + num = dict->GetNumKeyVals(); + WriteInt( num ); + for( i = 0; i < num; i++ ) { + kv = dict->GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + } + } +} + +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterial( const idMaterial *material ) { + if ( !material ) { + WriteString( "" ); + } else { + WriteString( material->GetName() ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterialType ( const rvDeclMatType* materialType ) { + if ( !materialType ) { + WriteString( "" ); + } else { + WriteString( materialType->GetName() ); + } +} + +/* +================ +idSaveGame::WriteTable +================ +*/ +void idSaveGame::WriteTable ( const idDeclTable* table ) { + if ( !table ) { + WriteString( "" ); + } else { + WriteString( table->GetName() ); + } +} +// RAVEN END + +/* +================ +idSaveGame::WriteSkin +================ +*/ +void idSaveGame::WriteSkin( const idDeclSkin *skin ) { + if ( !skin ) { + WriteString( "" ); + } else { + WriteString( skin->GetName() ); + } +} + +/* +================ +idSaveGame::WriteModelDef +================ +*/ +void idSaveGame::WriteModelDef( const idDeclModelDef *modelDef ) { + if ( !modelDef ) { + WriteString( "" ); + } else { + WriteString( modelDef->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSoundShader +================ +*/ +void idSaveGame::WriteSoundShader( const idSoundShader *shader ) { + const char *name; + + if ( !shader ) { + WriteString( "" ); + } else { + name = shader->GetName(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteModel +================ +*/ +void idSaveGame::WriteModel( const idRenderModel *model ) { + const char *name; + + if ( !model ) { + WriteString( "" ); + } else { + name = model->Name(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteUserInterface +================ +*/ +void idSaveGame::WriteUserInterface( const idUserInterface *ui, bool unique ) { + const char *name; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !ui ) { + WriteString( "" ); + } else { + name = ui->Name(); + WriteString( name ); + WriteBool( unique ); + if ( ui->WriteToSaveGame( file ) == false ) { + gameLocal.Error( "idSaveGame::WriteUserInterface: ui failed to write properly\n" ); + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteInt( extrap.GetStartValue() ); + WriteInt( extrap.GetBaseSpeed() ); + WriteInt( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteFloat( extrap.GetStartValue() ); + WriteFloat( extrap.GetBaseSpeed() ); + WriteFloat( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteVec3( extrap.GetStartValue() ); + WriteVec3( extrap.GetBaseSpeed() ); + WriteVec3( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteRenderEffect +================ +*/ +void idSaveGame::WriteRenderEffect( const renderEffect_t &renderEffect ) { + WriteSyncId(); + + WriteFloat( renderEffect.startTime ); + WriteInt( renderEffect.suppressSurfaceInViewID ); + WriteInt( renderEffect.allowSurfaceInViewID ); + WriteInt( renderEffect.groupID ); + + WriteVec3( renderEffect.origin ); + WriteMat3( renderEffect.axis ); + + WriteVec3( renderEffect.gravity ); + WriteVec3( renderEffect.endOrigin ); + + WriteFloat( renderEffect.attenuation ); + WriteBool( renderEffect.hasEndOrigin ); + WriteBool( renderEffect.loop ); + WriteBool( renderEffect.ambient ); + WriteBool( renderEffect.inConnectedArea ); + WriteInt( renderEffect.weaponDepthHackInViewID ); + WriteFloat( renderEffect.modelDepthHack ); + + WriteInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + WriteFloat( renderEffect.shaderParms[ ix ] ); + } + + if( renderEffect.declEffect ) { + WriteString( renderEffect.declEffect->GetName() ); + } else { + WriteString( "" ); + } +} + +/* +================ +idSaveGame::WriteFrustum +================ +*/ +void idSaveGame::WriteFrustum( const idFrustum& frustum ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteVec3( frustum.GetOrigin() ); + WriteMat3( frustum.GetAxis() ); + WriteFloat( frustum.GetNearDistance() ); + WriteFloat( frustum.GetFarDistance() ); + WriteFloat( frustum.GetLeft() ); + WriteFloat( frustum.GetUp() ); +} + +/* +================ +idSaveGame::WriteRenderEntity +================ +*/ +void idSaveGame::WriteRenderEntity( const renderEntity_t &renderEntity ) { + int i; + + WriteSyncId(); + + WriteModel( renderEntity.hModel ); + + WriteInt( renderEntity.entityNum ); + WriteInt( renderEntity.bodyId ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + WriteBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + + WriteInt( renderEntity.suppressSurfaceInViewID ); + WriteInt( renderEntity.suppressShadowInViewID ); + WriteInt( renderEntity.suppressShadowInLightID ); + WriteInt( renderEntity.allowSurfaceInViewID ); + + WriteInt( renderEntity.suppressSurfaceMask ); + + WriteVec3( renderEntity.origin ); + WriteMat3( renderEntity.axis ); + + WriteMaterial( renderEntity.customShader ); + WriteMaterial( renderEntity.referenceShader ); + WriteMaterial( renderEntity.overlayShader ); + WriteSkin( renderEntity.customSkin ); + + WriteInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + WriteUserInterface( renderEntity.gui[ i ], renderEntity.gui[ i ] ? renderEntity.gui[ i ]->IsUniqued() : false ); + } + + WriteFloat( renderEntity.modelDepthHack ); + + WriteBool( renderEntity.noSelfShadow ); + WriteBool( renderEntity.noShadow ); + WriteBool( renderEntity.noDynamicInteractions ); + WriteBool( renderEntity.forceUpdate ); + + WriteInt( renderEntity.weaponDepthHackInViewID ); + WriteFloat( renderEntity.shadowLODDistance ); + WriteInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idSaveGame::WriteRenderLight +================ +*/ +void idSaveGame::WriteRenderLight( const renderLight_t &renderLight ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteMat3( renderLight.axis ); + WriteVec3( renderLight.origin ); + + WriteInt( renderLight.suppressLightInViewID ); + WriteInt( renderLight.allowLightInViewID ); + WriteBool( renderLight.noShadows ); + WriteBool( renderLight.noSpecular ); + WriteBool( renderLight.noDynamicShadows ); + WriteBool( renderLight.pointLight ); + WriteBool( renderLight.parallel ); + WriteBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + WriteFloat( renderLight.detailLevel ); +// RAVEN END + + WriteVec3( renderLight.lightRadius ); + WriteVec3( renderLight.lightCenter ); + + WriteVec3( renderLight.target ); + WriteVec3( renderLight.right ); + WriteVec3( renderLight.up ); + WriteVec3( renderLight.start ); + WriteVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // WriteModel( renderLight.prelightModel ); + + WriteInt( renderLight.lightId ); + + WriteMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + WriteInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idSaveGame::WriteRefSound +================ +*/ +void idSaveGame::WriteRefSound( const refSound_t &refSound ) { +// RAVEN BEGIN + WriteSyncId(); + + WriteInt( refSound.referenceSoundHandle ); +// RAVEN END + WriteVec3( refSound.origin ); +// RAVEN BEGIN + WriteVec3( refSound.velocity ); +// RAVEN END + WriteInt( refSound.listenerId ); + WriteSoundShader( refSound.shader ); + WriteFloat( refSound.diversity ); + WriteBool( refSound.waitfortrigger ); + + WriteFloat( refSound.parms.minDistance ); + WriteFloat( refSound.parms.maxDistance ); + WriteFloat( refSound.parms.volume ); + WriteFloat( refSound.parms.shakes ); + WriteInt( refSound.parms.soundShaderFlags ); + WriteInt( refSound.parms.soundClass ); +} + +/* +================ +idSaveGame::WriteRenderView +================ +*/ +void idSaveGame::WriteRenderView( const renderView_t &view ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteInt( view.viewID ); + WriteInt( view.x ); + WriteInt( view.y ); + WriteInt( view.width ); + WriteInt( view.height ); + + WriteFloat( view.fov_x ); + WriteFloat( view.fov_y ); + WriteVec3( view.vieworg ); + WriteMat3( view.viewaxis ); + + WriteBool( view.cramZNear ); + + WriteInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + WriteFloat( view.shaderParms[ i ] ); + } +} + +/* +=================== +idSaveGame::WriteUsercmd +=================== +*/ +void idSaveGame::WriteUsercmd( const usercmd_t &usercmd ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteInt( usercmd.gameFrame ); + WriteInt( usercmd.gameTime ); + WriteInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + WriteShort( usercmd.buttons ); +// RAVEN END + WriteSignedChar( usercmd.forwardmove ); + WriteSignedChar( usercmd.rightmove ); + WriteSignedChar( usercmd.upmove ); + WriteShort( usercmd.angles[0] ); + WriteShort( usercmd.angles[1] ); + WriteShort( usercmd.angles[2] ); + WriteShort( usercmd.mx ); + WriteShort( usercmd.my ); + WriteSignedChar( usercmd.impulse ); + WriteByte( usercmd.flags ); + WriteInt( usercmd.sequence ); +} + +/* +=================== +idSaveGame::WriteContactInfo +=================== +*/ +void idSaveGame::WriteContactInfo( const contactInfo_t &contactInfo ) { + WriteInt( (int)contactInfo.type ); + WriteVec3( contactInfo.point ); + WriteVec3( contactInfo.normal ); + WriteFloat( contactInfo.dist ); + WriteInt( contactInfo.contents ); + WriteMaterial( contactInfo.material ); + WriteInt( contactInfo.modelFeature ); + WriteInt( contactInfo.trmFeature ); + WriteInt( contactInfo.entityNum ); + WriteInt( contactInfo.id ); + WriteMaterialType( contactInfo.materialType ); +} + +/* +=================== +idSaveGame::WriteTrace +=================== +*/ +void idSaveGame::WriteTrace( const trace_t &trace ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteFloat( trace.fraction ); + WriteVec3( trace.endpos ); + WriteMat3( trace.endAxis ); + WriteContactInfo( trace.c ); +} + +/* +=================== +idSaveGame::WriteClipModel +=================== +*/ +void idSaveGame::WriteClipModel( const idClipModel *clipModel ) { + if ( clipModel != NULL ) { + WriteBool( true ); + clipModel->Save( this ); + } else { + WriteBool( false ); + } +} + +/* +=================== +idSaveGame::WriteSoundCommands +=================== +*/ +void idSaveGame::WriteSoundCommands( void ) { + soundSystem->WriteToSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +====================== +idSaveGame::WriteBuildNumber +====================== +*/ +void idSaveGame::WriteBuildNumber( const int value ) { + WriteInt( value ); +} + + + + + + +/*********************************************************************** + + idRestoreGame + +***********************************************************************/ + +/* +================ +idRestoreGame::RestoreGame +================ +*/ +idRestoreGame::idRestoreGame( idFile *savefile ) { + file = savefile; +} + +/* +================ +idRestoreGame::~idRestoreGame() +================ +*/ +idRestoreGame::~idRestoreGame() { +} + +// RAVEN BEGIN +/* +================ +void idRestoreGame::CreateObjects +================ +*/ +void idRestoreGame::CreateObjects( void ) { + int i, num; + idStr classname; + idTypeInfo *type; + + ReadInt( num ); + + // create all the objects + objects.SetNum( num + 1 ); + memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); + + for( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); + type = idClass::GetClass( classname ); + if ( !type ) { + Error( "idRestoreGame::CreateObjects: Unknown class '%s'", classname.c_str() ); + } + objects[ i ] = type->CreateInstance(); + } +} + +/* +================ +void idRestoreGame::RestoreObjects +================ +*/ +void idRestoreGame::RestoreObjects( void ) { + int i; + + ReadSoundCommands(); + + // read trace models + idClipModel::RestoreTraceModels( this ); + + // restore all the objects + for( i = 1; i < objects.Num(); i++ ) { + file->ReadSyncId( "Restore objects", objects[ i ]->GetClassname() ); + CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); + } + + // regenerate render entities and render lights because are not saved + for( i = 1; i < objects.Num(); i++ ) { + if ( objects[ i ]->IsType( idEntity::GetClassType() ) ) { + idEntity *ent = static_cast( objects[ i ] ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} +// RAVEN END + +/* +==================== +void idRestoreGame::DeleteObjects +==================== +*/ +void idRestoreGame::DeleteObjects( void ) { + + // Remove the NULL object before deleting + objects.RemoveIndex( 0 ); + + objects.DeleteContents( true ); +} + +/* +================ +idRestoreGame::Error +================ +*/ +void idRestoreGame::Error( const char *fmt, ... ) { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN + // FIXME: this crashes. It now leaks, but that's better than crashing. + // The problem is that some entities delete attached ents that are also in this list. When this call gets to them + // it tries to delete an already deleted object +// objects.DeleteContents( true ); +// RAVEN END + + gameLocal.Error( "%s", text ); +} + +/* +================ +idRestoreGame::CallRestore_r +================ +*/ +void idRestoreGame::CallRestore_r( const idTypeInfo *cls, idClass *obj ) { + if ( cls->super ) { + CallRestore_r( cls->super, obj ); + if ( cls->super->Restore == cls->Restore ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + file->ReadSyncId( "Callrestore_r start ", cls->classname ); + ( obj->*cls->Restore )( this ); + file->ReadSyncId( "Callrestore_r end ", cls->classname ); +} + +/* +================ +idRestoreGame::Read +================ +*/ +void idRestoreGame::Read( void *buffer, int len ) { + file->Read( buffer, len ); +} + +/* +================ +idRestoreGame::ReadInt +================ +*/ +void idRestoreGame::ReadInt( int &value ) { + file->ReadInt( value ); +} + +/* +================ +idRestoreGame::ReadJoint +================ +*/ +void idRestoreGame::ReadJoint( jointHandle_t &value ) { + file->ReadInt( ( int &)value ); +} + +/* +================ +idRestoreGame::ReadShort +================ +*/ +void idRestoreGame::ReadShort( short &value ) { + file->ReadShort( value ); +} + +/* +================ +idRestoreGame::ReadByte +================ +*/ +void idRestoreGame::ReadByte( byte &value ) { + file->ReadUnsignedChar( value ); +} + +/* +================ +idRestoreGame::ReadSignedChar +================ +*/ +void idRestoreGame::ReadSignedChar( signed char &value ) { + file->ReadChar( ( char & )value ); +} + +/* +================ +idRestoreGame::ReadFloat +================ +*/ +void idRestoreGame::ReadFloat( float &value ) { + file->ReadFloat( value ); +} + +/* +================ +idRestoreGame::ReadBool +================ +*/ +void idRestoreGame::ReadBool( bool &value ) { + file->ReadBool( value ); +} + +/* +================ +idRestoreGame::ReadString +================ +*/ +void idRestoreGame::ReadString( idStr &string ) { +/* int len; + + ReadInt( len ); +// RAVEN BEGIN +// jscott: added max check - should be big enough + if ( len < 0 || len > MAX_PRINT_MSG ) { + Error( "idRestoreGame::ReadString: invalid length (%d)", len ); +// RAVEN END + } + + string.Fill( ' ', len );*/ + file->ReadString( string ); +} + +/* +================ +idRestoreGame::ReadVec2 +================ +*/ +void idRestoreGame::ReadVec2( idVec2 &vec ) { + file->ReadVec2( vec ); +} + +/* +================ +idRestoreGame::ReadVec3 +================ +*/ +void idRestoreGame::ReadVec3( idVec3 &vec ) { + file->ReadVec3( vec ); +} + +/* +================ +idRestoreGame::ReadVec4 +================ +*/ +void idRestoreGame::ReadVec4( idVec4 &vec ) { + file->ReadVec4( vec ); +} + +/* +================ +idRestoreGame::ReadVec5 +================ +*/ +void idRestoreGame::ReadVec5( idVec5 &vec ) { + file->ReadVec5( vec ); +} + +/* +================ +idRestoreGame::ReadVec6 +================ +*/ +void idRestoreGame::ReadVec6( idVec6 &vec ) { + file->ReadVec6( vec ); +} + +/* +================ +idRestoreGame::ReadBounds +================ +*/ +void idRestoreGame::ReadBounds( idBounds &bounds ) { + file->Read( &bounds, sizeof( bounds ) ); +} + +/* +================ +idRestoreGame::ReadWinding +================ +*/ +void idRestoreGame::ReadWinding( idWinding &w ) +{ + int i, num; + file->ReadInt( num ); + w.SetNumPoints( num ); + for ( i = 0; i < num; i++ ) { + file->ReadVec5( w[i] ); + } +} + +/* +================ +idRestoreGame::ReadMat3 +================ +*/ +void idRestoreGame::ReadMat3( idMat3 &mat ) { + file->ReadMat3( mat ); +} + +/* +================ +idRestoreGame::ReadAngles +================ +*/ +void idRestoreGame::ReadAngles( idAngles &angles ) { + file->Read( &angles, sizeof( angles ) ); +} + +/* +================ +idRestoreGame::ReadObject +================ +*/ +void idRestoreGame::ReadObject( idClass *&obj ) { + int index; + + ReadInt( index ); + if ( ( index < 0 ) || ( index >= objects.Num() ) ) { + Error( "idRestoreGame::ReadObject: invalid object index" ); + } + obj = objects[ index ]; +} + +/* +================ +idRestoreGame::ReadStaticObject +================ +*/ +void idRestoreGame::ReadStaticObject( idClass &obj ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadStaticObject", obj.GetClassname() ); +// RAVEN END + + CallRestore_r( obj.GetType(), &obj ); + +// RAVEN BEGIN + obj.PostEventMS( &EV_PostRestore, 0 ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadDict +================ +*/ +void idRestoreGame::ReadDict( idDict *dict ) { + int num; + int i; + idStr key; + idStr value; + +// RAVEN BEGIN + file->ReadSyncId( "ReadDict" ); +// RAVEN END + + ReadInt( num ); + + if ( num < 0 ) { + dict = NULL; + } else { + dict->Clear(); + for( i = 0; i < num; i++ ) { + ReadString( key ); + ReadString( value ); + dict->Set( key, value ); + } + } +} + +/* +================ +idRestoreGame::ReadMaterial +================ +*/ +void idRestoreGame::ReadMaterial( const idMaterial *&material ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + material = NULL; + } else { + material = declManager->FindMaterial( name ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idRestoreGame::ReadMaterialType +================ +*/ +void idRestoreGame::ReadMaterialType ( const rvDeclMatType* &materialType ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + materialType = NULL; + } else { + materialType = declManager->FindMaterialType ( name ); + } +} + +/* +================ +idRestoreGame::ReadTable +================ +*/ +void idRestoreGame::ReadTable ( const idDeclTable* &table ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + table = NULL; + } else { + table = declManager->FindTable( name ); + } +} + +// RAVEN END + +/* +================ +idRestoreGame::ReadSkin +================ +*/ +void idRestoreGame::ReadSkin( const idDeclSkin *&skin ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( name ); + } +} + +/* +================ +idRestoreGame::ReadSoundShader +================ +*/ +void idRestoreGame::ReadSoundShader( const idSoundShader *&shader ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + shader = NULL; + } else { + shader = declManager->FindSound( name ); + } +} + +/* +================ +idRestoreGame::ReadModelDef +================ +*/ +void idRestoreGame::ReadModelDef( const idDeclModelDef *&modelDef ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + modelDef = NULL; + } else { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + } +} + +/* +================ +idRestoreGame::ReadModel +================ +*/ +void idRestoreGame::ReadModel( idRenderModel *&model ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + model = NULL; + } else { + model = renderModelManager->FindModel( name ); + } +} + +/* +================ +idRestoreGame::ReadUserInterface +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadUserInterface( idUserInterface *&ui, const idDict *args ) { +// RAVEN END + idStr name; + +// RAVEN BEGIN + file->ReadSyncId( "ReadUserInterface" ); +// RAVEN END + + ReadString( name ); + if ( !name.Length() ) { + ui = NULL; + } else { + bool unique; + ReadBool( unique ); + ui = uiManager->FindGui( name, true, unique ); + if ( ui ) { + if ( ui->ReadFromSaveGame( file ) == false ) { + Error( "idSaveGame::ReadUserInterface: ui failed to read properly\n" ); + } else { +// RAVEN BEGIN + UpdateGuiParms( ui, args ); +// RAVEN END + } + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + int startValue; + int baseSpeed; + int speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( baseSpeed ); + ReadInt( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + float startValue; + float baseSpeed; + float speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( baseSpeed ); + ReadFloat( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + idVec3 startValue; + idVec3 baseSpeed; + idVec3 speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( baseSpeed ); + ReadVec3( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} +/* +================ +idRestoreGame::ReadRenderEffect +================ +*/ +void idRestoreGame::ReadRenderEffect( renderEffect_t &renderEffect ) { + idStr name; + + file->ReadSyncId( "ReadRenderEffect" ); + + renderEffect.declEffect = NULL; + + ReadFloat( renderEffect.startTime ); + ReadInt( renderEffect.suppressSurfaceInViewID ); + ReadInt( renderEffect.allowSurfaceInViewID ); + ReadInt( renderEffect.groupID ); + + ReadVec3( renderEffect.origin ); + ReadMat3( renderEffect.axis ); + + ReadVec3( renderEffect.gravity ); + ReadVec3( renderEffect.endOrigin ); + + ReadFloat( renderEffect.attenuation ); + ReadBool( renderEffect.hasEndOrigin ); + ReadBool( renderEffect.loop ); + ReadBool( renderEffect.ambient ); + ReadBool( renderEffect.inConnectedArea ); + ReadInt( renderEffect.weaponDepthHackInViewID ); + ReadFloat( renderEffect.modelDepthHack ); + + ReadInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + ReadFloat( renderEffect.shaderParms[ ix ] ); + } + + ReadString( name ); + if( name.Length() ) { + renderEffect.declEffect = declManager->FindType( DECL_EFFECT, name ); + } +} + +/* +================ +idRestoreGame::ReadFrustum +================ +*/ +void idRestoreGame::ReadFrustum( idFrustum& frustum ) { + idVec3 origin; + idMat3 axis; + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; +// RAVEN BEGIN + file->ReadSyncId( "ReadFrustum" ); +// RAVEN END + ReadVec3( origin ); + frustum.SetOrigin( origin ); + + ReadMat3( axis ); + frustum.SetAxis( axis ); + + ReadFloat( dNear ); + ReadFloat( dFar ); + ReadFloat( dLeft ); + ReadFloat( dUp ); + frustum.SetSize( dNear, dFar, dLeft, dUp ); +} + +/* +================ +idRestoreGame::ReadRenderEntity +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ) { +// RAVEN END + int i; + + file->ReadSyncId( "ReadRenderEntity" ); + + ReadModel( renderEntity.hModel ); + + ReadInt( renderEntity.entityNum ); + ReadInt( renderEntity.bodyId ); + + ReadBounds( renderEntity.bounds ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + // callback is set by class's Restore function + renderEntity.callback = NULL; + renderEntity.callbackData = NULL; + + ReadInt( renderEntity.suppressSurfaceInViewID ); + ReadInt( renderEntity.suppressShadowInViewID ); + ReadInt( renderEntity.suppressShadowInLightID ); + ReadInt( renderEntity.allowSurfaceInViewID ); + + ReadInt( renderEntity.suppressSurfaceMask ); + + ReadVec3( renderEntity.origin ); + ReadMat3( renderEntity.axis ); + + ReadMaterial( renderEntity.customShader ); + ReadMaterial( renderEntity.referenceShader ); + ReadMaterial( renderEntity.overlayShader ); + ReadSkin( renderEntity.customSkin ); + + ReadInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { +// RAVEN BEGIN + ReadUserInterface( renderEntity.gui[ i ], args ); +// RAVEN END + } + + // idEntity will restore "cameraTarget", which will be used in idEntity::Present to restore the remoteRenderView + renderEntity.remoteRenderView = NULL; + + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + + ReadFloat( renderEntity.modelDepthHack ); + + ReadBool( renderEntity.noSelfShadow ); + ReadBool( renderEntity.noShadow ); + ReadBool( renderEntity.noDynamicInteractions ); + ReadBool( renderEntity.forceUpdate ); + + ReadInt( renderEntity.weaponDepthHackInViewID ); + ReadFloat( renderEntity.shadowLODDistance ); + ReadInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idRestoreGame::ReadRenderLight +================ +*/ +void idRestoreGame::ReadRenderLight( renderLight_t &renderLight ) { + int i; + + file->ReadSyncId( "ReadRenderLight" ); + + ReadMat3( renderLight.axis ); + ReadVec3( renderLight.origin ); + + ReadInt( renderLight.suppressLightInViewID ); + ReadInt( renderLight.allowLightInViewID ); + ReadBool( renderLight.noShadows ); + ReadBool( renderLight.noSpecular ); + ReadBool( renderLight.noDynamicShadows ); + ReadBool( renderLight.pointLight ); + ReadBool( renderLight.parallel ); + ReadBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + ReadFloat( renderLight.detailLevel ); +// RAVEN END + + ReadVec3( renderLight.lightRadius ); + ReadVec3( renderLight.lightCenter ); + + ReadVec3( renderLight.target ); + ReadVec3( renderLight.right ); + ReadVec3( renderLight.up ); + ReadVec3( renderLight.start ); + ReadVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // ReadModel( renderLight.prelightModel ); + renderLight.prelightModel = NULL; + + ReadInt( renderLight.lightId ); + + ReadMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + ReadInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadRefSound +================ +*/ +void idRestoreGame::ReadRefSound( refSound_t &refSound ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadRefSound" ); +// RAVEN END + + ReadInt( refSound.referenceSoundHandle ); + ReadVec3( refSound.origin ); +// RAVEN BEGIN + ReadVec3( refSound.velocity ); +// RAVEN END + ReadInt( refSound.listenerId ); + ReadSoundShader( refSound.shader ); + ReadFloat( refSound.diversity ); + ReadBool( refSound.waitfortrigger ); + + ReadFloat( refSound.parms.minDistance ); + ReadFloat( refSound.parms.maxDistance ); + ReadFloat( refSound.parms.volume ); + ReadFloat( refSound.parms.shakes ); + ReadInt( refSound.parms.soundShaderFlags ); + ReadInt( refSound.parms.soundClass ); +} + +/* +================ +idRestoreGame::ReadRenderView +================ +*/ +void idRestoreGame::ReadRenderView( renderView_t &view ) { + int i; + +// RAVEN BEGIN + file->ReadSyncId( "ReadRenderView" ); +// RAVEN END + + ReadInt( view.viewID ); + ReadInt( view.x ); + ReadInt( view.y ); + ReadInt( view.width ); + ReadInt( view.height ); + + ReadFloat( view.fov_x ); + ReadFloat( view.fov_y ); + ReadVec3( view.vieworg ); + ReadMat3( view.viewaxis ); + + ReadBool( view.cramZNear ); + + ReadInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + ReadFloat( view.shaderParms[ i ] ); + } +} + +/* +================= +idRestoreGame::ReadUsercmd +================= +*/ +void idRestoreGame::ReadUsercmd( usercmd_t &usercmd ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadUsercmd" ); +// RAVEN END + ReadInt( usercmd.gameFrame ); + ReadInt( usercmd.gameTime ); + ReadInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + ReadShort( usercmd.buttons ); +// RAVEN END + ReadSignedChar( usercmd.forwardmove ); + ReadSignedChar( usercmd.rightmove ); + ReadSignedChar( usercmd.upmove ); + ReadShort( usercmd.angles[0] ); + ReadShort( usercmd.angles[1] ); + ReadShort( usercmd.angles[2] ); + ReadShort( usercmd.mx ); + ReadShort( usercmd.my ); + ReadSignedChar( usercmd.impulse ); + ReadByte( usercmd.flags ); + ReadInt( usercmd.sequence ); +} + +/* +=================== +idRestoreGame::ReadContactInfo +=================== +*/ +void idRestoreGame::ReadContactInfo( contactInfo_t &contactInfo ) { + ReadInt( (int &)contactInfo.type ); + ReadVec3( contactInfo.point ); + ReadVec3( contactInfo.normal ); + ReadFloat( contactInfo.dist ); + ReadInt( contactInfo.contents ); + ReadMaterial( contactInfo.material ); + ReadInt( contactInfo.modelFeature ); + ReadInt( contactInfo.trmFeature ); + ReadInt( contactInfo.entityNum ); + ReadInt( contactInfo.id ); + ReadMaterialType( contactInfo.materialType ); +} + +/* +=================== +idRestoreGame::ReadTrace +=================== +*/ +void idRestoreGame::ReadTrace( trace_t &trace ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadTrace" ); +// RAVEN END + ReadFloat( trace.fraction ); + ReadVec3( trace.endpos ); + ReadMat3( trace.endAxis ); + ReadContactInfo( trace.c ); +} + +/* +===================== +idRestoreGame::ReadClipModel +===================== +*/ +void idRestoreGame::ReadClipModel( idClipModel *&clipModel ) { + bool restoreClipModel; + + ReadBool( restoreClipModel ); + if ( restoreClipModel ) { + clipModel = new idClipModel(); + clipModel->Restore( this ); + } else { + clipModel = NULL; + } +} + +/* +===================== +idRestoreGame::ReadSoundCommands +===================== +*/ +void idRestoreGame::ReadSoundCommands( void ) { + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + soundSystem->ReadFromSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +===================== +idRestoreGame::ReadBuildNumber +===================== +*/ +void idRestoreGame::ReadBuildNumber( void ) { + ReadInt( buildNumber ); +} + +/* +===================== +idRestoreGame::GetBuildNumber +===================== +*/ +int idRestoreGame::GetBuildNumber( void ) { + return buildNumber; +} + + + +void Cmd_CheckSave_f( const idCmdArgs &args ) +{ + idPlayer *lp = gameLocal.GetLocalPlayer(); + idFile *mp = fileSystem->GetNewFileMemory(); + idSaveGame sg( mp ); + + sg.CallSave_r( lp->GetType(), lp ); + + + mp->Rewind(); + idPlayer test; + idRestoreGame rg( mp ); + + rg.CallRestore_r( test.GetType(), &test ); +} + diff --git a/source/mpgame/gamesys/SaveGame.h b/source/mpgame/gamesys/SaveGame.h new file mode 100644 index 0000000..4abd3c3 --- /dev/null +++ b/source/mpgame/gamesys/SaveGame.h @@ -0,0 +1,183 @@ + +#ifndef __SAVEGAME_H__ +#define __SAVEGAME_H__ + +/* + +Save game related helper classes. + +*/ + +const int INITIAL_RELEASE_BUILD_NUMBER = 1262; + +class idSaveGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idSaveGame( idFile *savefile ); + ~idSaveGame(); + + void Close( void ); + + void AddObject( const idClass *obj ); + void WriteObjectList( void ); + + void Write( const void *buffer, int len ); + void WriteInt( const int value ); + void WriteJoint( const jointHandle_t value ); + void WriteShort( const short value ); + void WriteByte( const byte value ); + void WriteSignedChar( const signed char value ); + void WriteFloat( const float value ); + void WriteBool( const bool value ); + void WriteString( const char *string ); + void WriteVec2( const idVec2 &vec ); + void WriteVec3( const idVec3 &vec ); + void WriteVec4( const idVec4 &vec ); + void WriteVec5( const idVec5 &vec ); + void WriteVec6( const idVec6 &vec ); + void WriteWinding( const idWinding &winding ); + void WriteBounds( const idBounds &bounds ); + void WriteMat3( const idMat3 &mat ); + void WriteAngles( const idAngles &angles ); + void WriteObject( const idClass *obj ); + void WriteStaticObject( const idClass &obj ); + void WriteDict( const idDict *dict ); + void WriteMaterial( const idMaterial *material ); + void WriteSkin( const idDeclSkin *skin ); +// RAVEN BEGIN +// jscott: not using +// void WriteParticle( const idDeclParticle *particle ); +// void WriteFX( const idDeclFX *fx ); +// RAVEN END + void WriteSoundShader( const idSoundShader *shader ); + void WriteModelDef( const class idDeclModelDef *modelDef ); + void WriteModel( const idRenderModel *model ); +// RAVEN BEGIN +// bdube: material type + void WriteMaterialType ( const rvDeclMatType* matType ); + void WriteTable ( const idDeclTable* table ); +// abahr + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteRenderEffect( const renderEffect_t &renderEffect ); + void WriteFrustum( const idFrustum& frustum ); + void WriteSyncId( void ); +// RAVEN END + void WriteUserInterface( const idUserInterface *ui, bool unique ); + void WriteRenderEntity( const renderEntity_t &renderEntity ); + void WriteRenderLight( const renderLight_t &renderLight ); + void WriteRefSound( const refSound_t &refSound ); + void WriteRenderView( const renderView_t &view ); + void WriteUsercmd( const usercmd_t &usercmd ); + void WriteContactInfo( const contactInfo_t &contactInfo ); + void WriteTrace( const trace_t &trace ); + void WriteClipModel( const class idClipModel *clipModel ); + void WriteSoundCommands( void ); + + void WriteBuildNumber( const int value ); + +protected: + idFile * file; + + idList objects; + + void CallSave_r( const idTypeInfo *cls, const idClass *obj ); +}; + +class idRestoreGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idRestoreGame( idFile *savefile ); + ~idRestoreGame(); + + void CreateObjects( void ); + void RestoreObjects( void ); + void DeleteObjects( void ); + + void Error( const char *fmt, ... ); + + void Read( void *buffer, int len ); + void ReadInt( int &value ); + void ReadJoint( jointHandle_t &value ); + void ReadShort( short &value ); + void ReadByte( byte &value ); + void ReadSignedChar( signed char &value ); + void ReadFloat( float &value ); + void ReadBool( bool &value ); + void ReadString( idStr &string ); + void ReadVec2( idVec2 &vec ); + void ReadVec3( idVec3 &vec ); + void ReadVec4( idVec4 &vec ); + void ReadVec5( idVec5 &vec ); + void ReadVec6( idVec6 &vec ); + void ReadWinding( idWinding &winding ); + void ReadBounds( idBounds &bounds ); + void ReadMat3( idMat3 &mat ); + void ReadAngles( idAngles &angles ); + void ReadObject( idClass *&obj ); + void ReadStaticObject( idClass &obj ); + void ReadDict( idDict *dict ); + void ReadMaterial( const idMaterial *&material ); + void ReadSkin( const idDeclSkin *&skin ); +// RAVEN BEGIN +// bdube: not using +// void ReadParticle( const idDeclParticle *&particle ); +// void ReadFX( const idDeclFX *&fx ); +// RAVEN END + void ReadSoundShader( const idSoundShader *&shader ); + void ReadModelDef( const idDeclModelDef *&modelDef ); + void ReadModel( idRenderModel *&model ); +// RAVEN BEGIN + void ReadUserInterface( idUserInterface *&ui, const idDict *args ); +// bdube: material type + void ReadMaterialType ( const rvDeclMatType* &matType ); + void ReadTable ( const idDeclTable* &table ); +// abahr + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadRenderEffect( renderEffect_t &renderEffect ); + void ReadFrustum( idFrustum& frustum ); + void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) { file->ReadSyncId( detail, classname ); } + void ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ); +// RAVEN END + void ReadRenderLight( renderLight_t &renderLight ); + void ReadRefSound( refSound_t &refSound ); + void ReadRenderView( renderView_t &view ); + void ReadUsercmd( usercmd_t &usercmd ); + void ReadContactInfo( contactInfo_t &contactInfo ); + void ReadTrace( trace_t &trace ); + void ReadClipModel( idClipModel *&clipModel ); + void ReadSoundCommands( void ); + + void ReadBuildNumber( void ); + + // Used to retrieve the saved game buildNumber from within class Restore methods + int GetBuildNumber( void ); + +private: + int buildNumber; + + idFile * file; + + idList objects; + + void CallRestore_r( const idTypeInfo *cls, idClass *obj ); +}; + +#endif /* !__SAVEGAME_H__*/ diff --git a/source/mpgame/gamesys/State.cpp b/source/mpgame/gamesys/State.cpp new file mode 100644 index 0000000..e485203 --- /dev/null +++ b/source/mpgame/gamesys/State.cpp @@ -0,0 +1,424 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const int HISTORY_COUNT = 50; + +/* +===================== +stateParms_t::Save +===================== +*/ +void stateParms_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( blendFrames ); + saveFile->WriteInt( time ); + saveFile->WriteInt( stage ); +} + +/* +===================== +stateParms_t::Restore +===================== +*/ +void stateParms_t::Restore( idRestoreGame *saveFile ) { + saveFile->ReadInt( blendFrames ); + saveFile->ReadInt( time ); + saveFile->ReadInt( stage ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( state->name ); + // TOSAVE: idLinkList node; + saveFile->WriteInt( flags ); + saveFile->WriteInt( delay ); + parms.Save( saveFile ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Restore( idRestoreGame *saveFile, const idClass* owner ) { + idStr name; + + saveFile->ReadString( name ); + state = owner->FindState( name ); + + saveFile->ReadInt( flags ); + saveFile->ReadInt( delay ); + parms.Restore( saveFile ); +} + +/* +===================== +rvStateThread::rvStateThread +===================== +*/ +rvStateThread::rvStateThread ( void ) { + owner = NULL; + insertAfter = NULL; + lastResult = SRESULT_DONE; + + states.Clear ( ); + interrupted.Clear ( ); + + memset ( &fl, 0, sizeof(fl) ); +} + +/* +===================== +rvStateThread::~rvStateThread +===================== +*/ +rvStateThread::~rvStateThread ( void ) { + Clear ( true ); +} + +/* +===================== +rvStateThread::SetOwner +===================== +*/ +void rvStateThread::SetOwner ( idClass* _owner ) { + owner = _owner; +} + +/* +===================== +rvStateThread::Post +===================== +*/ +stateResult_t rvStateThread::PostState ( const char* name, int blendFrames, int delay, int flags ) { + const rvStateFunc* func; + + // Make sure the state exists before queueing it + if ( NULL == (func = owner->FindState ( name ) ) ) { + return SRESULT_ERROR; + } + + stateCall_t* call; + call = new stateCall_t; + call->state = func; + call->delay = delay; + call->flags = flags; + call->parms.blendFrames = blendFrames; + call->parms.time = -1; + call->parms.stage = 0; + + call->node.SetOwner ( call ); + + if ( fl.executing && insertAfter ) { + call->node.InsertAfter ( insertAfter->node ); + } else { + call->node.AddToEnd ( states ); + } + + insertAfter = call; + + return SRESULT_OK; +} + +/* +===================== +rvStateThread::Set +===================== +*/ +stateResult_t rvStateThread::SetState ( const char* name, int blendFrames, int delay, int flags ) { + Clear ( ); + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::InterruptState +===================== +*/ +stateResult_t rvStateThread::InterruptState ( const char* name, int blendFrames, int delay, int flags ) { + stateCall_t* call; + + // Move all states to the front of the interrupted list in the same order + for ( call = states.Prev(); call; call = states.Prev() ) { + call->node.Remove ( ); + call->node.AddToFront ( interrupted ); + } + + // Nothing to insert after anymore + insertAfter = NULL; + fl.stateInterrupted = true; + + // Post the state now + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::CurrentStateIs +===================== +*/ +bool rvStateThread::CurrentStateIs( const char* name ) const { + return ( !IsIdle() ) ? owner->FindState(name) == GetState()->state : false; +} + +/* +===================== +rvStateThread::Clear +===================== +*/ +void rvStateThread::Clear ( bool ignoreStateCalls ) { + stateCall_t* call; + + // Clear all states from the main state list + for( call = states.Next(); call != NULL; call = states.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + // Clear all interrupted states + for( call = interrupted.Next(); call != NULL; call = interrupted.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + insertAfter = NULL; + fl.stateCleared = true; + + states.Clear ( ); + interrupted.Clear ( ); +} + +/* +===================== +rvStateThread::Execute +===================== +*/ +stateResult_t rvStateThread::Execute ( void ) { + stateCall_t* call = NULL; + int count; + const char* stateName; + int stateStage; + const char* historyState[HISTORY_COUNT]; + int historyStage[HISTORY_COUNT]; + int historyStart; + int historyEnd; + + // If our main state loop is empty copy over any states in the interrupted state + if ( !states.Next ( ) ) { + for ( call = interrupted.Next(); call; call = interrupted.Next() ) { + call->node.Remove ( ); + call->node.AddToEnd ( states ); + } + assert ( !interrupted.Next ( ) ); + } + + // State thread is idle if there are no states + if ( !states.Next() ) { + return SRESULT_IDLE; + } + + fl.executing = true; + + // Run through the states until there are no more or one of them tells us to wait + count = 0; + historyStart = 0; + historyEnd = 0; + + for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count ) { + insertAfter = call; + fl.stateCleared = false; + fl.stateInterrupted = false; + + // If this state is only called when being cleared then just skip it + if ( call->flags & SFLAG_ONCLEARONLY ) { + call->node.Remove ( ); + delete call; + continue; + } + + // If the call has a delay on it the time will be set to negative initially and then + // converted to game time. + if ( call->parms.time <= 0 ) { + call->parms.time = gameLocal.time; + } + + // Check for delayed states + if ( call->delay && gameLocal.time < call->parms.time + call->delay ) { + fl.executing = false; + return SRESULT_WAIT; + } + + // Debugging + if ( lastResult != SRESULT_WAIT ) { + if ( *g_debugState.GetString ( ) && (*g_debugState.GetString ( ) == '*' || !idStr::Icmp ( g_debugState.GetString ( ), name ) ) ) { + if ( call->parms.stage ) { + gameLocal.Printf ( "%s: %s (%d)\n", name.c_str(), call->state->name, call->parms.stage ); + } else { + gameLocal.Printf ( "%s: %s\n", name.c_str(), call->state->name ); + } + } + + // Keep a history of the called states so we can dump them on an overflow + historyState[historyEnd] = call->state->name; + historyStage[historyEnd] = call->parms.stage; + historyEnd = (historyEnd+1) % HISTORY_COUNT; + if ( historyEnd == historyStart ) { + historyStart = (historyEnd+1) % HISTORY_COUNT; + } + } + + // Cache name and stage for error messages + stateName = call->state->name; + stateStage = call->parms.stage; + + // Actually call the state function + lastResult = owner->ProcessState ( call->state, call->parms ); + switch ( lastResult ) { + case SRESULT_WAIT: + fl.executing = false; + return SRESULT_WAIT; + + case SRESULT_ERROR: + gameLocal.Error ( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage ); + fl.executing = false; + return SRESULT_ERROR; + } + + // Dont remove the node if it was interrupted or cleared in the last process + if ( !fl.stateCleared && !fl.stateInterrupted ) { + if( lastResult >= SRESULT_SETDELAY ) { + call->delay = lastResult - SRESULT_SETDELAY; + call->parms.time = gameLocal.GetTime(); + continue; + } else if ( lastResult >= SRESULT_SETSTAGE ) { + call->parms.stage = lastResult - SRESULT_SETSTAGE; + continue; + } + + // Done with state so remove it from list + call->node.Remove ( ); + delete call; + } + + // Finished the last state but wait a frame for next one + if ( lastResult == SRESULT_DONE_WAIT ) { + fl.executing = false; + return SRESULT_WAIT; + } + } + + // Runaway state loop? + if ( count >= HISTORY_COUNT ) { + idFile *file; + + fileSystem->RemoveFile ( "statedump.txt" ); + file = fileSystem->OpenFileWrite( "statedump.txt" ); + + for ( ; historyStart != historyEnd; historyStart = (historyStart + 1) % HISTORY_COUNT ) { + if ( historyStage[historyStart] ) { + gameLocal.Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + gameLocal.Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + if ( file ) { + if ( historyStage[historyStart] ) { + file->Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + file->Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + } + } + if ( file ) { + fileSystem->CloseFile( file ); + } + + gameLocal.Error ( "rvStateThread: run away state loop '%s'", name.c_str() ); + } + + insertAfter = NULL; + fl.executing = false; + + // Move interrupted states back into the main state list when the main state list is empty + if ( !states.Next() && interrupted.Next ( ) ) { + return Execute ( ); + } + + return lastResult; +} + +/* +===================== +rvStateThread::Save +===================== +*/ +void rvStateThread::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( name.c_str() ); + + // No need to save owner, its setup in restore + + saveFile->WriteInt( lastResult ); + saveFile->Write ( &fl, sizeof(fl) ); + + saveFile->WriteInt( states.Num() ); + for( idLinkList* node = states.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + saveFile->WriteInt( interrupted.Num() ); + for( idLinkList* node = interrupted.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + // TOSAVE: stateCall_t* insertAfter; + // TOSAVE: stateResult_t lastResult; +} + +/* +===================== +rvStateThread::Restore +===================== +*/ +void rvStateThread::Restore( idRestoreGame *saveFile, idClass* owner ) { + int numStates; + stateCall_t* call = NULL; + + saveFile->ReadString( name ); + + this->owner = owner; + + saveFile->ReadInt( (int&)lastResult ); + saveFile->Read ( &fl, sizeof(fl) ); + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( states ); + } + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( interrupted ); + } +} diff --git a/source/mpgame/gamesys/State.h b/source/mpgame/gamesys/State.h new file mode 100644 index 0000000..27ca629 --- /dev/null +++ b/source/mpgame/gamesys/State.h @@ -0,0 +1,157 @@ +#ifndef __SYS_STATE_H__ +#define __SYS_STATE_H__ + +typedef enum { + SRESULT_OK, // Call was made successfully + SRESULT_ERROR, // An unrecoverable error occurred + SRESULT_DONE, // Done with current state, move to next + SRESULT_DONE_WAIT, // Done with current state, wait a frame then move to next + SRESULT_WAIT, // Wait a frame and re-run current state + SRESULT_IDLE, // State thread is currently idle (ie. no states) + SRESULT_SETSTAGE, // Sets the current stage of the current state and reruns the state + // NOTE: this has to be the last result becuase the stage is added to + // the result. + SRESULT_SETDELAY = SRESULT_SETSTAGE + 20 +} stateResult_t; + +#define MAX_STATE_CALLS 50 + +#define SRESULT_STAGE(x) ((stateResult_t)((int)SRESULT_SETSTAGE + (int)(x))) +#define SRESULT_DELAY(x) ((stateResult_t)((int)SRESULT_SETDELAY + (int)(x))) + +struct stateParms_t { + int blendFrames; + int time; + int stage; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); +}; + +typedef stateResult_t ( idClass::*stateCallback_t )( const stateParms_t& parms ); + +template< class Type > +struct rvStateFunc { + const char* name; + stateCallback_t function; +}; + +/* +================ +CLASS_STATES_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass that +wishes to have its own custom states. Its prototypes variables used in the process +of managing states. +================ +*/ +#define CLASS_STATES_PROTOTYPE(nameofclass) \ +protected: \ + static rvStateFunc stateCallbacks[] + +/* +================ +CLASS_STATES_DECLARATION + +This macro must be included in the code to properly initialize variables +used in state processing for a idClass dervied class +================ +*/ +#define CLASS_STATES_DECLARATION(nameofclass) \ +rvStateFunc nameofclass::stateCallbacks[] = { + +/* +================ +STATE + +This macro declares a single state. It must be surrounded by the CLASS_STATES_DECLARATION +and END_CLASS_STATES macros. +================ +*/ +#define STATE(statename,function) { statename, (stateCallback_t)( &function ) }, + +/* +================ +END_CLASS_STATES + +Terminates a state block +================ +*/ +#define END_CLASS_STATES { NULL, NULL } }; + +struct stateCall_t { + const rvStateFunc* state; + idLinkList node; + int flags; + int delay; + stateParms_t parms; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, const idClass* owner ); +}; + +class idClass; + +const int SFLAG_ONCLEAR = BIT(0); // Executes, even if the state queue is cleared +const int SFLAG_ONCLEARONLY = BIT(1); // Executes only if the state queue is cleared + +class rvStateThread { +public: + + rvStateThread ( void ); + ~rvStateThread ( void ); + + void SetName ( const char* name ); + void SetOwner ( idClass* owner ); + + bool Interrupt ( void ); + + stateResult_t InterruptState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t PostState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t SetState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateCall_t* GetState ( void ) const; + bool CurrentStateIs ( const char* name ) const; + + stateResult_t Execute ( void ); + + void Clear ( bool ignoreStateCalls = false ); + + bool IsIdle ( void ) const; + bool IsExecuting ( void ) const; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, idClass* owner ); + +protected: + + struct flags { + bool stateCleared :1; // State list was cleared + bool stateInterrupted :1; // State list was interrupted + bool executing :1; // Execute is currently processing states + } fl; + + idStr name; + idClass* owner; + idLinkList states; + idLinkList interrupted; + stateCall_t* insertAfter; + stateResult_t lastResult; +}; + +ID_INLINE void rvStateThread::SetName ( const char* _name ) { + name = _name; +} + +ID_INLINE stateCall_t* rvStateThread::GetState ( void ) const { + return states.Next(); +} + +ID_INLINE bool rvStateThread::IsIdle ( void ) const { + return !states.Next() && !interrupted.Next(); +} + +ID_INLINE bool rvStateThread::IsExecuting ( void ) const { + return fl.executing; +} + +#endif // __SYS_STATE_H__ diff --git a/source/mpgame/gamesys/SysCmds.cpp b/source/mpgame/gamesys/SysCmds.cpp new file mode 100644 index 0000000..6521693 --- /dev/null +++ b/source/mpgame/gamesys/SysCmds.cpp @@ -0,0 +1,3260 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +#include "../ai/AI.h" +#if !defined(__GAME_PROJECTILE_H__) + #include "../Projectile.h" +#endif +#if !defined(__GAME_WEAPON_H__) + #include "../Weapon.h" +#endif +#if !defined(__GAME_SPAWNER_H__) + #include "../spawner.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "../Vehicle/Vehicle.h" +#endif +#if !defined(__AI_MANAGER_H__) + #include "../ai/AI_Manager.h" +#endif +#if !defined(__INSTANCE_H__) + #include "../Instance.h" +#endif +// RAVEN END + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +================== +Cmd_GetFloatArg +================== +*/ +float Cmd_GetFloatArg( const idCmdArgs &args, int &argNum ) { + const char *value; + + value = args.Argv( argNum++ ); + return atof( value ); +} + +/* +=================== +Cmd_EntityList_f +=================== +*/ +void Cmd_EntityList_f( const idCmdArgs &args ) { + int e; + idEntity *check; + int count; + size_t size; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + size = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + + if ( !check ) { + continue; + } + + if ( !check->name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s\n", e, + check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + + count++; + size += check->spawnArgs.Allocated(); + } + + gameLocal.Printf( "...%d entities\n...%d bytes of spawnargs\n", count, size ); +} + +/* +=================== +Cmd_ClientEntityList_f +=================== +*/ +void Cmd_ClientEntityList_f( const idCmdArgs &args ) { + int e; + rvClientEntity *check; + int count; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + + gameLocal.Printf( "%-4s %-20s\n", " Num", "Classname" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_CENTITIES; e++ ) { + check = gameLocal.clientEntities[ e ]; + + idStr name( check->GetClassType().classname ); + + if ( !check ) { + continue; + } + + if ( !name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s\n", e, name.c_str() ); + + count++; + } + + gameLocal.Printf( "...%d entities\n", count ); +} + + +/* +=================== +Cmd_ActiveEntityList_f +=================== +*/ +void Cmd_ActiveEntityList_f( const idCmdArgs &args ) { + idEntity *check; + int count; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( check = gameLocal.activeEntities.Next(); check != NULL; check = check->activeNode.Next() ) { + char dormant = check->fl.isDormant ? '-' : ' '; + gameLocal.Printf( "%4i:%c%-20s %-20s %s\n", check->entityNumber, dormant, check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + count++; + } + + gameLocal.Printf( "...%d active entities\n", count ); +} + +/* +=================== +Cmd_ListSpawnArgs_f +=================== +*/ +void Cmd_ListSpawnArgs_f( const idCmdArgs &args ) { + int i; + idEntity *ent; + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + for ( i = 0; i < ent->spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = ent->spawnArgs.GetKeyVal( i ); + gameLocal.Printf( "\"%s\" "S_COLOR_WHITE"\"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +=================== +Cmd_ReloadScript_f +=================== +*/ +void Cmd_ReloadScript_f( const idCmdArgs &args ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + // error out so that the user can rerun the scripts + gameLocal.Error( "Exiting map to reload scripts" ); +} + +/* +=================== +Cmd_Script_f +=================== +*/ +void Cmd_Script_f( const idCmdArgs &args ) { + const char * script; + idStr text; + idStr funcname; + static int funccount = 0; + idThread * thread; + const function_t *func; + idEntity *ent; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + sprintf( funcname, "ConsoleFunction_%d", funccount++ ); + + script = args.Args(); +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + text = va( "void %s() {%s;}\n", funcname.c_str(), script ); +// RAVEN END + if ( gameLocal.program.CompileText( "console", text, true ) ) { + func = gameLocal.program.FindFunction( funcname ); + if ( func ) { + // set all the entity names in case the user named one in the script that wasn't referenced in the default script + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + gameLocal.program.SetEntity( ent->name, ent ); + } + + thread = new idThread( func ); + thread->Start(); + } + } +} + +// RAVEN BEGIN +// jscott: exports for tracking memory +/* +================== +idGameEdit::ScriptSummary +================== +*/ +size_t idGameEdit::ScriptSummary( const idCmdArgs &args ) const { + + return( gameLocal.program.ScriptSummary( args ) ); +} + +/* +================== +idGameEdit::ClassSummary +================== +*/ +size_t idGameEdit::ClassSummary( const idCmdArgs &args ) const { + + common->Printf( "Classes - %dK\n", idClass::GetUsedMemory() / 1024 ); + + return( idClass::GetUsedMemory() / 1024 ); +} + +/* +================== +idGameEdit::EntitySummary +================== +*/ + +size_t idGameEdit::EntitySummary( const idCmdArgs &args ) const { + + common->Printf( "CL & SV ents - %dK\n", gameLocal.GetEntityMemoryUsage () / 1024); + + return gameLocal.GetEntityMemoryUsage() / 1024; +} +// RAVEN END + +/* +================== +KillEntities + +Kills all the entities of the given class in a level. +================== +*/ +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { + idEntity *ent; + idStrList ignore; + const char *name; + int i; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( i = 1; i < args.Argc(); i++ ) { + name = args.Argv( i ); + ignore.Append( name ); + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( superClass ) ) { + for( i = 0; i < ignore.Num(); i++ ) { + if ( ignore[ i ] == ent->name ) { + break; + } + } + + if ( i >= ignore.Num() ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + } +} + +/* +================== +Cmd_KillMonsters_f + +Kills all the monsters in a level. +================== +*/ +void Cmd_KillMonsters_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAI::GetClassType() ); +// nmckenzie: rvSpawners + KillEntities( args, rvSpawner::GetClassType() ); + + // kill any projectiles as well since they have pointers to the monster that created them + KillEntities( args, idProjectile::GetClassType() ); +// RAVEN END +} + +/* +================== +Cmd_KillMovables_f + +Kills all the moveables in a level. +================== +*/ +void Cmd_KillMovables_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idMoveable::GetClassType() ); +// RAVEN END +} + +// RAVEN BEGIN +// bdube: vehicle code +/* +================== +Cmd_KillVehicles_f +================== +*/ +void Cmd_KillVehicles_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + rvVehicleController::KillVehicles ( ); +} + +void Cmd_KillMessage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + gameLocal.mpGame.SendDeathMessage( gameLocal.GetLocalPlayer(), gameLocal.GetLocalPlayer(), 2, false ); +} + +void Cmd_APState_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for ( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + gameLocal.Printf ( "Assault point #%d: owner: %d\n", gameLocal.mpGame.assaultPoints[i]->GetIndex(), gameLocal.mpGame.assaultPoints[i]->GetOwner() ); + } +} +// RAVEN END + +/* +================== +Cmd_KillRagdolls_f + +Kills all the ragdolls in a level. +================== +*/ +void Cmd_KillRagdolls_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAFEntity_Generic::GetClassType() ); + KillEntities( args, idAFEntity_WithAttachedHead::GetClassType() ); +// RAVEN END +} + + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================== +GiveStuffToPlayer + +Used by the "give" and "buy" command line cmds +================== +*/ +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ) +{ + int i; + bool give_all; +// idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player || !name ) { + return; + } + + if( !value ) { + value = ""; + } + + if ( idStr::Icmp( name, "all" ) == 0 ) { + give_all = true; + } else { + give_all = false; + } + + if ( give_all || ( idStr::Cmpn( name, "weapon", 6 ) == 0 ) ) { + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", false ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, gameLocal.entities[ i ]->spawnArgs.GetString( "def_weapon1" ) ); + } + } + } + } + + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) || ( idStr::Icmp( name, "ammorefill" ) == 0 ) ) { + player->GiveItem( name ); + return; + } + + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { + player->health = player->inventory.maxHealth; + if ( player->IsInVehicle() ) { + player->GetVehicleController().Give ( "health", "9999" ); + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "weapons" ) == 0 ) { + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "ammo" ) == 0 ) { +// RAVEN BEGIN +// bdube: define changed + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, rvWeapon::GetAmmoNameForIndex( i ) ); +// RAVEN END + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "armor" ) == 0 ) { + player->inventory.armor = player->inventory.maxarmor; + if ( !give_all ) { + return; + } + } +// RAVEN BEGIN + if (idStr::Icmp(name, "quad") == 0) { + player->GivePowerUp( POWERUP_QUADDAMAGE, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "invis" ) == 0 ) { + player->GivePowerUp( POWERUP_INVISIBILITY, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "regen" ) == 0 ) { + player->GivePowerUp( POWERUP_REGENERATION, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "haste" ) == 0 ) { + player->GivePowerUp( POWERUP_HASTE, SEC2MS( 30.0f ) ); + return; + } + + if (idStr::Icmp(name, "ammoregen") == 0) { + player->GivePowerUp( POWERUP_AMMOREGEN, -1 ); + return; + } + + if (idStr::Icmp(name, "scout") == 0) { + player->GivePowerUp( POWERUP_SCOUT, -1 ); + return; + } + + if (idStr::Icmp(name, "doubler") == 0) { + player->GivePowerUp( POWERUP_DOUBLER, -1 ); + return; + } + + if (idStr::Icmp(name, "guard") == 0) { + player->GivePowerUp( POWERUP_GUARD, -1 ); + return; + } +// RAVEN END + + if ( !idStr::Icmp ( name, "wpmod_all" ) ) { + player->GiveWeaponMods ( 0xFFFFFFFF ); + return; + } else if ( !idStr::Cmpn( name, "wpmod_", 6 ) ) { + player->GiveWeaponMod(name); + return; + } + + if ( !idStr::Cmpn( name, "stroggmod_", 10 ) ) { + player->Give ( name, "" ); + return; + } + + if ( !give_all && !player->Give( name, value ) ) { + gameLocal.Printf( "unknown item\n" ); + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + GiveStuffToPlayer( player, args.Argv(1), args.Argv(2) ); +} +// RITUAL END + +/* +================== +Cmd_CenterView_f + +Centers the players pitch +================== +*/ +void Cmd_CenterView_f( const idCmdArgs &args ) { + idPlayer *player; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + ang = player->viewAngles; + ang.pitch = 0.0f; + player->SetViewAngles( ang ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->godmode ) { + player->godmode = false; + msg = "godmode OFF\n"; + } else { + player->godmode = true; + msg = "godmode ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Undying_f + +Sets client to undying + +argv(0) undying +================== +*/ +void Cmd_Undying_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->undying ) { + player->undying = false; + msg = "undying OFF\n"; + } else { + player->undying = true; + msg = "undying ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->fl.notarget ) { + player->fl.notarget = false; + msg = "notarget OFF\n"; + } else { + player->fl.notarget = true; + msg = "notarget ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + player->noclip = !player->noclip; + + gameLocal.Printf( "%s", msg ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_KILL ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "kill or kill \n" ); + return; + } + player->Kill( false, false ); +// RAVEN BEGIN +// rhummer: localized this string.. (killed client) + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say %s %d '%s^0'\n", common->GetLocalizedString( "#str_108022" ), player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + } else { + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + player->Kill( false, false ); + } +} + +// RAVEN BEGIN +// bdube: jump points +/* +================= +Cmd_DebugJump_f +================= +*/ +void Cmd_DebugJump_f( const idCmdArgs &args ) { + if (args.Argc() > 1) { + // going to a specific jump point as specified by second argument + gameDebug.JumpTo ( args.Argv( 1 ) ); + } else { + // just go to next jump point as specified + gameDebug.JumpNext ( ); + } +} + +/* +================= +Cmd_DebugNextJumpPoint_f +================= +*/ +void Cmd_DebugNextJumpPoint_f( const idCmdArgs &args ) { + // just go to next jump point as specified + gameDebug.JumpNext ( ); +} + +/* +================= +Cmd_DebugPrevJumpPoint_f +================= +*/ +void Cmd_DebugPrevJumpPoint_f( const idCmdArgs &args ) { + // just go to previous jump point as specified + gameDebug.JumpPrev ( ); +} + +/* +================= +Cmd_AASExtractTactical_f +================= +*/ +void Cmd_AASExtractTactical_f( const idCmdArgs &args ) { + if (gameLocal.GetLocalPlayer()) + { + gameLocal.GetLocalPlayer()->aasSensor->SearchDebug(); + } +} + +/* +================= +Cmd_CallScriptFunc_f +================= +*/ +void Cmd_CallScriptFunc_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: call FuncName ...\n" ); + return; + } + + idDict returnDict; + rvScriptFuncUtility util; + + if( !util.Init(args) ) { + return; + } + + util.CallFunc( &returnDict ); + + if( util.ReturnsAVal() && util.GetReturnKey() && util.GetReturnKey()[0] ) { + gameLocal.Printf( "%s: %s\n", util.GetReturnKey(), returnDict.GetString(util.GetReturnKey()) ); + } +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: setPlayerGravity 'x_magnitude y_magnitude z_magnitude\n" ); + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( !player ) { + return; + } + + idVec3 gravity; + sscanf( args.Argv(1), "%f %f %f", &gravity.x, &gravity.y, &gravity.z ); + player->GetPhysics()->SetGravity( gravity ); +} +// RAVEN END + +/* +================= +Cmd_PlayerModel_f +================= +*/ +void Cmd_PlayerModel_f( const idCmdArgs &args ) { + idPlayer *player; + const char *name; + idVec3 pos; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: playerModel \n" ); + return; + } + + name = args.Argv( 1 ); + player->spawnArgs.Set( "model", name ); + + pos = player->GetPhysics()->GetOrigin(); + ang = player->viewAngles; + player->SpawnToPoint( pos, ang ); +} + +/* +================== +Cmd_Say +================== +*/ +static void Cmd_Say( bool team, const idCmdArgs &args ) { + const char *name; + idStr text; + const char *cmd = team ? "sayTeam" : "say" ; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "%s can only be used in a multiplayer game\n", cmd ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text = args.Args(); +// RAVEN BEGIN +// bdube: make sure text was specified + if ( text.Length() == 0 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text.Replace( "&bsl;", "\\" ); + text.Replace( "&", "&" ); + +// asalmon: check to see if the text passes the live decency standard +#ifdef _XENON + if(!Sys_VerifyString(text.c_str())) + { + gameLocal.Printf( "Your message did not pass Xbox decency standards\n"); + return; + } +#endif + + +// ddynerman: team speak only in team games + if ( team && !gameLocal.IsTeamGame() ) { + team = false; + } +// RAVEN END + if ( text[ text.Length() - 1 ] == '\n' ) { + text[ text.Length() - 1 ] = '\0'; + } + name = "player"; + + idPlayer * player; + + // here we need to special case a listen server to use the real client name instead of "server" + // "server" will only appear on a dedicated server + if ( gameLocal.isClient || cvarSystem->GetCVarInteger( "net_serverDedicated" ) == 0 ) { + player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->GetUserInfo() ) { + name = player->GetUserInfo()->GetString( "ui_name", "player" ); + } + +// RAVEN BEGIN +// mekberg: activate the mphud gui so the time is right before receiving the chat message + if ( player->mphud ) { + player->mphud->Activate( true, gameLocal.time ); + } + + if (player->IsFakeClient()) + { + name = cvarSystem->GetCVarString( "ui_name" ); + } + } +// RAVEN END + } else { + name = "server"; + } + + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( team ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( "" ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + gameLocal.mpGame.ProcessChatMessage( gameLocal.localClientNum, team, name, text, NULL ); + } +} + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( const idCmdArgs &args ) { + Cmd_Say( false, args ); +} + +/* +================== +Cmd_SayTeam_f +================== +*/ +static void Cmd_SayTeam_f( const idCmdArgs &args ) { + Cmd_Say( true, args ); +} + +/* +================== +Cmd_AddChatLine_f +================== +*/ +static void Cmd_AddChatLine_f( const idCmdArgs &args ) { + gameLocal.mpGame.AddChatLine( args.Argv( 1 ) ); +} + +/* +================== +Cmd_Kick_f +================== +*/ +static void Cmd_Kick_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "kick can only be used in a multiplayer game\n" ); + return; + } + + if ( gameLocal.isClient ) { + gameLocal.Printf( "You have no such power. This is a server command\n" ); + return; + } + + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "usage: kick or kick \n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say kicking out client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d\n", player->entityNumber ) ); +} + +/* +================== +Cmd_GetViewpos_f +================== +*/ +void Cmd_GetViewpos_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( view ) { + gameLocal.Printf( "(%s) %.1f\n", view->vieworg.ToString(), view->viewaxis[0].ToYaw() ); + } else { + player->GetViewPos( origin, axis ); + gameLocal.Printf( "(%s) %.1f\n", origin.ToString(), axis[0].ToYaw() ); + } +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + int i; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( ( args.Argc() != 4 ) && ( args.Argc() != 5 ) ) { + gameLocal.Printf( "usage: setviewpos \n" ); + return; + } + + angles.Zero(); + if ( args.Argc() == 5 ) { + angles.yaw = atof( args.Argv( 4 ) ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( args.Argv( i + 1 ) ); + } + origin.z -= pm_normalviewheight.GetFloat() - 0.25f; + + player->Teleport( origin, angles, NULL ); +} + +/* +================= +Cmd_Teleport_f +================= +*/ +void Cmd_Teleport_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: teleport \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + angles.Zero(); + angles.yaw = ent->GetPhysics()->GetAxis()[ 0 ].ToYaw(); + origin = ent->GetPhysics()->GetOrigin(); + + player->Teleport( origin, angles, ent ); +} + +/* +================= +Cmd_Trigger_f +================= +*/ +void Cmd_Trigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); +} + +/* +=================== +Cmd_Spawn_f +=================== +*/ +void Cmd_Spawn_f( const idCmdArgs &args ) { +#ifndef _MPBETA + const char *key, *value; + int i; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname [key/value pairs]\n" ); + return; + } + + yaw = player->viewAngles.yaw; + + value = args.Argv( 1 ); + dict.Set( "classname", value ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + +// RAVEN BEGIN +// kfuller: want to know the name of the entity I spawned + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } +// RAVEN END +#endif // !_MPBETA +} + +// RAVEN BEGIN +// ddynerman: MP spawning command for performance testing +/* +=================== +Cmd_EvaluateMPPerformance_f +=================== +*/ +void Cmd_EvaluateMPPerformance_f( const idCmdArgs &args ) { + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + int num = 15; + + if ( args.Argc() > 1 ) { + num = atoi( args.Argv( 1 ) ); + } + + float angleStep = 360.0f / num; + + const char* className = "char_marine"; + + yaw = player->viewAngles.yaw; + + for( int i = 0; i < num; i++ ) { + dict.Set( "classname", className ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw + (i * angleStep), 0 ).ToForward() * 120 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } + } +} +// RAVEN END + + +/* +================== +Cmd_Damage_f + +Damages the specified entity +================== +*/ +void Cmd_Damage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: damage \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); +} + + +/* +================== +Cmd_Flashlight_f + +Toggles flashlight on specified entity +================== +*/ +void Cmd_Flashlight_f( const idCmdArgs &args ) { + if ( gameLocal.IsMultiplayer() || !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: flashight <0 = off, 1 = on>\n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent || !ent->IsType( idActor::GetClassType() ) ) { + gameLocal.Printf( "entity not found or not an actor\n" ); + return; + } + ent->ProcessEvent( &AI_Flashlight, atoi( args.Argv( 2 ) ) ); +} + +/* +================== +Cmd_Remove_f + +Removes the specified entity +================== +*/ +void Cmd_Remove_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: remove \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + delete ent; +} + +/* +================== +Cmd_AI_DebugFilter_f + +Makes the targeted entity the only one ai_debugMove & ai_debugTactical cares about +================== +*/ +void Cmd_AI_DebugFilter_f( const idCmdArgs &args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + idEntity *ent = NULL; + if ( args.Argc() != 2 ) + { + //trace ahead + trace_t trace; + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAngles.ToForward() * 2048.0f; + gameLocal.TracePoint( player, trace, start, end, MASK_SHOT_RENDERMODEL, player ); + ent = gameLocal.GetTraceEntity( trace ); + } + else + { + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + } + + if ( !ent || !ent->IsType( idAI::GetClassType() ) ) { + ai_debugFilterString.SetString( "" ); + } else { + ai_debugFilterString.SetString( ent->GetName() ); + } +} + +/* +=================== +Cmd_TestLight_f +=================== +*/ +void Cmd_TestLight_f( const idCmdArgs &args ) { + int i; + idStr filename; + const char *key, *value, *name = NULL; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + renderView_t *rv = player->GetRenderView(); + + float fov = idMath::Tan( idMath::M_DEG2RAD * rv->fov_x / 2 ); + + dict.SetMatrix( "rotation", mat3_default ); + dict.SetVector( "origin", rv->vieworg ); + dict.SetVector( "light_target", rv->viewaxis[0] ); + dict.SetVector( "light_right", rv->viewaxis[1] * -fov ); + dict.SetVector( "light_up", rv->viewaxis[2] * fov ); + dict.SetVector( "light_start", rv->viewaxis[0] * 16 ); + dict.SetVector( "light_end", rv->viewaxis[0] * 1000 ); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + filename = args.Argv(1); + filename.DefaultFileExtension( ".tga" ); + dict.Set( "texture", filename ); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "spawned_light_%d", i ); // not just light_, or it might pick up a prelight shadow + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new light\n"); +} + +/* +=================== +Cmd_TestPointLight_f +=================== +*/ +void Cmd_TestPointLight_f( const idCmdArgs &args ) { + const char *key, *value, *name = NULL; + int i; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + dict.SetVector("origin", player->GetRenderView()->vieworg); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + dict.Set("light", value); + } else { + dict.Set("light", "300"); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "light_%d", i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new point light\n"); +} + +/* +================== +Cmd_PopLight_f +================== +*/ +void Cmd_PopLight_f( const idCmdArgs &args ) { + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idLight *lastLight; + int last; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + bool removeFromMap = ( args.Argc() > 1 ); + + lastLight = NULL; + last = -1; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( gameLocal.spawnIds[ ent->entityNumber ] > last ) { + last = gameLocal.spawnIds[ ent->entityNumber ]; + lastLight = static_cast( ent ); + } + } + + if ( lastLight ) { + // find map file entity + mapEnt = mapFile->FindEntity( lastLight->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + gameLocal.Printf( "Removing light %i\n", lastLight->GetLightDefHandle() ); + delete lastLight; + } else { + gameLocal.Printf( "No lights to clear.\n" ); + } + +} + +/* +==================== +Cmd_ClearLights_f +==================== +*/ +void Cmd_ClearLights_f( const idCmdArgs &args ) { + idEntity *ent; + idEntity *next; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + + bool removeFromMap = ( args.Argc() > 1 ); + + gameLocal.Printf( "Clearing all lights.\n" ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + light = static_cast( ent ); + mapEnt = mapFile->FindEntity( light->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + + delete light; + } +} + +// RAVEN BEGIN +// bdube: not using id effects +/* +================== +Cmd_TestFx_f +================== +void Cmd_TestFx_f( const idCmdArgs &args ) { + idVec3 offset; + const char *name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testFx ) { + delete gameLocal.testFx; + gameLocal.testFx = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "test", "1"); + dict.Set( "fx", name ); + gameLocal.testFx = ( idEntityFx * )gameLocal.SpawnEntityType( idEntityFx::Type, &dict ); +} +*/ +// RAVEN END + +#define MAX_DEBUGLINES 128 + +typedef struct { + bool used; + idVec3 start, end; + int color; + bool blink; + bool arrow; +} gameDebugLine_t; + +gameDebugLine_t debugLines[MAX_DEBUGLINES]; + +/* +================== +Cmd_AddDebugLine_f +================== +*/ +static void Cmd_AddDebugLine_f( const idCmdArgs &args ) { + int i, argNum; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 7 ) { + gameLocal.Printf( "usage: addline \n" ); + return; + } + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( !debugLines[i].used ) { + break; + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "no free debug lines\n" ); + return; + } + value = args.Argv( 0 ); + if ( !idStr::Icmp( value, "addarrow" ) ) { + debugLines[i].arrow = true; + } else { + debugLines[i].arrow = false; + } + debugLines[i].used = true; + debugLines[i].blink = false; + argNum = 1; + debugLines[i].start.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].color = Cmd_GetFloatArg( args, argNum ); +} + +/* +================== +Cmd_RemoveDebugLine_f +================== +*/ +static void Cmd_RemoveDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: removeline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi(value); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].used = false; +} + +/* +================== +Cmd_BlinkDebugLine_f +================== +*/ +static void Cmd_BlinkDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: blinkline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi( value ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].blink = !debugLines[i].blink; +} + +/* +================== +PrintFloat +================== +*/ +static void PrintFloat( float f ) { + char buf[128], i; + + for ( i = sprintf( buf, "%3.2f", f ); i < 7; i++ ) { + buf[i] = ' '; + } + buf[i] = '\0'; + gameLocal.Printf( buf ); +} + +/* +================== +Cmd_ListDebugLines_f +================== +*/ +static void Cmd_ListDebugLines_f( const idCmdArgs &args ) { + int i, num; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + num = 0; + gameLocal.Printf( "line num: x1 y1 z1 x2 y2 z2 c b a\n" ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + gameLocal.Printf( "line %3d: ", num ); + PrintFloat( debugLines[i].start.x ); + PrintFloat( debugLines[i].start.y ); + PrintFloat( debugLines[i].start.z ); + PrintFloat( debugLines[i].end.x ); + PrintFloat( debugLines[i].end.y ); + PrintFloat( debugLines[i].end.z ); + gameLocal.Printf( "%d %d %d\n", debugLines[i].color, debugLines[i].blink, debugLines[i].arrow ); + num++; + } + } + if ( !num ) { + gameLocal.Printf( "no debug lines\n" ); + } +} + +/* +================== +D_DrawDebugLines +================== +*/ +void D_DrawDebugLines( void ) { +// RAVEN BEGIN +// ddynerman: this eats about 5k us in release +#ifdef _DEBUG + int i; + idVec3 forward, right, up, p1, p2; + idVec4 color; + float l; + + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( !debugLines[i].blink || (gameLocal.time & (1<<9)) ) { + color = idVec4( debugLines[i].color&1, (debugLines[i].color>>1)&1, (debugLines[i].color>>2)&1, 1 ); + gameRenderWorld->DebugLine( color, debugLines[i].start, debugLines[i].end ); + // + if ( debugLines[i].arrow ) { + // draw a nice arrow + forward = debugLines[i].end - debugLines[i].start; + l = forward.Normalize() * 0.2f; + forward.NormalVectors( right, up); + + if ( l > 3.0f ) { + l = 3.0f; + } + p1 = debugLines[i].end - l * forward + (l * 0.4f) * right; + p2 = debugLines[i].end - l * forward - (l * 0.4f) * right; + gameRenderWorld->DebugLine( color, debugLines[i].end, p1 ); + gameRenderWorld->DebugLine( color, debugLines[i].end, p2 ); + gameRenderWorld->DebugLine( color, p1, p2 ); + } + } + } + } +#else + return; +#endif +// RAVEN END +} + +/* +================== +Cmd_ListCollisionModels_f +================== +*/ +static void Cmd_ListCollisionModels_f( const idCmdArgs &args ) { + if ( !gameLocal.CheatsOk() ) { + return; + } + + collisionModelManager->ListModels(); +} + +/* +================== +Cmd_CollisionModelInfo_f +================== +*/ +static void Cmd_CollisionModelInfo_f( const idCmdArgs &args ) { + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: collisionModelInfo \n" + "use 'all' instead of the model number for accumulated info\n" ); + return; + } + + value = args.Argv( 1 ); + if ( !idStr::Icmp( value, "all" ) ) { + collisionModelManager->ModelInfo( -1 ); + } else { + collisionModelManager->ModelInfo( atoi(value) ); + } +} + +/* +================== +Cmd_ExportModels_f +================== +*/ +static void Cmd_ExportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } +} + +/* +================== +Cmd_ReexportModels_f +================== +*/ +static void Cmd_ReexportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + idAnimManager::forceExport = true; + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } + idAnimManager::forceExport = false; +} + +/* +================== +Cmd_ReloadAnims_f +================== +*/ +static void Cmd_ReloadAnims_f( const idCmdArgs &args ) { + + // don't allow reloading anims when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + +// RAVEN BEGIN +// mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + +// jsinger: animationLib changed to a pointer + animationLib->ReloadAnims(); + +// mekberg: enable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( false ); +// RAVEN END +} + +/* +================== +Cmd_ListAnims_f +================== +*/ +static void Cmd_ListAnims_f( const idCmdArgs &args ) { + idEntity * ent; + int num; + size_t size; + size_t alloced; + idAnimator * animator; + const char * classname; + const idDict * dict; + int i; + + if ( args.Argc() > 1 ) { + idAnimator animator; + + classname = args.Argv( 1 ); + + dict = gameLocal.FindEntityDefDict( classname, false ); + if ( !dict ) { + gameLocal.Printf( "Entitydef '%s' not found\n", classname ); + return; + } + animator.SetModel( dict->GetString( "model" ) ); + + gameLocal.Printf( "----------------\n" ); + num = animator.NumAnims(); +// RAVEN BEGIN +// rjohnson: more output for animators + idFile *FH = fileSystem->OpenFileAppend( "animations.txt" ); + for( i = 0; i < num; i++ ) { + for( int j = 0; ; j++ ) { + const char *fileName = animator.AnimMD5Name( i, j ); + + if ( !fileName[0] ) { + break; + } + gameLocal.Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + if ( FH ) { + FH->Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + } + } + } + gameLocal.Printf( "%d anims\n", num ); + fileSystem->CloseFile( FH ); +// RAVEN END + } else { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->ListAnims(); +// RAVEN END + + size = 0; + num = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + animator = ent->GetAnimator(); + if ( animator ) { + alloced = animator->Allocated(); + size += alloced; + num++; + } + } + + gameLocal.Printf( "%d memory used in %d entity animators\n", size, num ); + } +} + +// RAVEN BEGIN +// jscott: export for memory tracking +/* +================ +idGameEdit::ListAnims +================ +*/ +void idGameEdit::PrintMemInfo( MemInfo *mi ) { + + int i, count, totalSize; + idAAS *aas; + + totalSize = 0; + count = 0; + for( i = 0; i < gameLocal.GetNumAAS(); i++ ) { + + aas = gameLocal.GetAAS( i ); + if( aas ) { + + totalSize += aas->StatsSummary(); + count++; + } + } + + mi->aasAssetsTotal = totalSize; + mi->aasAssetsCount = count; + + // jsinger: animationLib changed to a pointer + animationLib->PrintMemInfo( mi ); +} +// RAVEN END + +/* +================== +Cmd_AASStats_f +================== +*/ +static void Cmd_AASStats_f( const idCmdArgs &args ) { + int aasNum; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + aasNum = aas_test.GetInteger(); + idAAS *aas = gameLocal.GetAAS( aasNum ); + if ( !aas ) { + gameLocal.Printf( "No aas #%d loaded\n", aasNum ); + } else { + aas->Stats(); + } +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDamage_f( const idCmdArgs &args ) { + idPlayer *player; + const char *damageDefName; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + gameLocal.Printf( "usage: testDamage [angle]\n" ); + return; + } + + damageDefName = args.Argv( 1 ); + + idVec3 dir; + if ( args.Argc() == 3 ) { + float angle = atof( args.Argv( 2 ) ); + + idMath::SinCos( DEG2RAD( angle ), dir[1], dir[0] ); + dir[2] = 0; + } else { + dir.Zero(); + } + + // give the player full health before and after + // running the damage + player->health = player->inventory.maxHealth; + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->health = player->inventory.maxHealth; +} + +/* +================== +Cmd_TestBoneFx_f +================== +*/ +// RAVEN BEGIN +// bdube: not using +/* +static void Cmd_TestBoneFx_f( const idCmdArgs &args ) { + idPlayer *player; + const char *bone, *fx; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 3 || args.Argc() > 4 ) { + gameLocal.Printf( "usage: testBoneFx \n" ); + return; + } + + fx = args.Argv( 1 ); + bone = args.Argv( 2 ); + + player->StartFxOnBone( fx, bone ); +} +*/ +// RAVEN END + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDeath_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + idVec3 dir; + idMath::SinCos( DEG2RAD( 45.0f ), dir[1], dir[0] ); + dir[2] = 0; + + g_testDeath.SetBool( 1 ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + if ( args.Argc() >= 2) { + player->SpawnGibs( dir, "damage_triggerhurt_1000" ); + } + +} + +/* +================== +Cmd_WeaponSplat_f +================== +*/ +static void Cmd_WeaponSplat_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->weapon->BloodSplat( 2.0f ); +} + +/* +================== +Cmd_SaveSelected_f +================== +*/ +static void Cmd_SaveSelected_f( const idCmdArgs &args ) { + int i; + idPlayer *player; + idEntity *s; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + s = player->dragEntity.GetSelected(); + if ( !s ) { + gameLocal.Printf( "no entity selected, set g_dragShowSelection 1 to show the current selection\n" ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + // find map file entity + mapEnt = mapFile->FindEntity( s->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", s->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + s->name = name; + mapEnt->epairs.Set( "classname", s->GetEntityDefName() ); + mapEnt->epairs.Set( "name", s->name ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( s->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // save the moveable state + mapEnt->epairs.Set( "origin", s->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", s->GetPhysics()->GetAxis().ToString( 8 ) ); + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + else if ( s->IsType( idAFEntity_Generic::GetClassType() ) || s->IsType( idAFEntity_WithAttachedHead::GetClassType() ) ) { +// RAVEN END + // save the articulated figure state + dict.Clear(); + static_cast(s)->SaveState( dict ); + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_DeleteSelected_f +================== +*/ +static void Cmd_DeleteSelected_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.DeleteSelected(); + } +} + +/* +================== +Cmd_SaveMoveables_f +================== +*/ +static void Cmd_SaveMoveables_f( const idCmdArgs &args ) { + int e, i; + idMoveable *m; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m || !m->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + if ( !m->IsAtRest() ) { + break; + } + } + + if ( e < MAX_GENTITIES ) { + gameLocal.Warning( "map not saved because the moveable entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m ) + { + continue; + } +// jdischler: need to check for idMoveableItem as well + if ( !m->IsType( idMoveable::GetClassType()) && !m->IsType( idMoveableItem::GetClassType()) ) + { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + // find map file entity + mapEnt = mapFile->FindEntity( m->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", m->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + m->name = name; + mapEnt->epairs.Set( "classname", m->GetEntityDefName() ); + mapEnt->epairs.Set( "name", m->name ); + } + // save the moveable state + mapEnt->epairs.Set( "origin", m->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", m->GetPhysics()->GetAxis().ToString( 8 ) ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_SaveRagdolls_f +================== +*/ +static void Cmd_SaveRagdolls_f( const idCmdArgs &args ) { + int e, i; + idAFEntity_Base *af; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + af = static_cast(gameLocal.entities[ e ]); + + if ( !af ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af->IsType( idAFEntity_WithAttachedHead::GetClassType() ) && !af->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( af->IsBound() ) { + continue; + } + + if ( !af->IsAtRest() ) { + gameLocal.Warning( "the articulated figure for entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + } + + dict.Clear(); + af->SaveState( dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( af->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", af->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + af->name = name; + mapEnt->epairs.Set( "classname", af->GetEntityDefName() ); + mapEnt->epairs.Set( "name", af->name ); + } + // save the articulated figure state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_BindRagdoll_f +================== +*/ +static void Cmd_BindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.BindSelected(); + } +} + +/* +================== +Cmd_UnbindRagdoll_f +================== +*/ +static void Cmd_UnbindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.UnbindSelected(); + } +} + +/* +================== +Cmd_GameError_f +================== +*/ +static void Cmd_GameError_f( const idCmdArgs &args ) { + gameLocal.Error( "game error" ); +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +/* +================== +Cmd_ListEntityStats_f +================== +*/ +static void Cmd_ListEntityStats_f( const idCmdArgs &args ) { + gameLocal.ListEntityStats( args ); +} +// RAVEN END + +/* +================== +Cmd_SaveLights_f +================== +*/ +static void Cmd_SaveLights_f( const idCmdArgs &args ) { + int e, i; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + light = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !light || !light->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + dict.Clear(); + light->SaveState( &dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( light->name ); + // create new map file entity if there isn't one for this light + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", light->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + light->name = name; + mapEnt->epairs.Set( "classname", light->GetEntityDefName() ); + mapEnt->epairs.Set( "name", light->name ); + } + // save the light state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_SaveParticles_f +================== +*/ +static void Cmd_SaveParticles_f( const idCmdArgs &args ) { + int e; + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName, strModel; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + + ent = static_cast ( gameLocal.entities[ e ] ); + + if ( !ent ) { + continue; + } + + strModel = ent->spawnArgs.GetString( "model" ); + if ( strModel.Length() && strModel.Find( ".prt") > 0 ) { + dict.Clear(); + dict.Set( "model", ent->spawnArgs.GetString( "model" ) ); + dict.SetVector( "origin", ent->GetPhysics()->GetOrigin() ); + + // find map file entity + mapEnt = mapFile->FindEntity( ent->name ); + // create new map file entity if there isn't one for this entity + if ( !mapEnt ) { + continue; + } + // save the particle state + mapEnt->epairs.Copy( dict ); + } + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_DisasmScript_f +================== +*/ +static void Cmd_DisasmScript_f( const idCmdArgs &args ) { + gameLocal.program.Disassemble(); +} + +/* +================== +Cmd_TestSave_f +================== +*/ +static void Cmd_TestSave_f( const idCmdArgs &args ) { + idFile *f; + + f = fileSystem->OpenFileWrite( "test.sav" ); + gameLocal.SaveGame( f ); + fileSystem->CloseFile( f ); +} + +// RAVEN BEGIN +#if 0 +/* +================== +Cmd_RecordViewNotes_f +================== +*/ +static void Cmd_RecordViewNotes_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + if ( args.Argc() <= 3 ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + player->GetViewPos( origin, axis ); + + // Argv(1) = filename for map (viewnotes/mapname/person) + // Argv(2) = note number (person0001) + // Argv(3) = comments + + idStr str = args.Argv(1); + str.SetFileExtension( ".txt" ); + idFile *file = fileSystem->OpenFileAppend( str ); + if ( file ) { + file->WriteFloatString( "\"view\"\t( %s )\t( %s )\r\n", origin.ToString(), axis.ToString() ); + file->WriteFloatString( "\"comments\"\t\"%s: %s\"\r\n\r\n", args.Argv(2), args.Argv(3) ); + fileSystem->CloseFile( file ); + } + + idStr viewComments = args.Argv(1); + viewComments.StripLeading("viewnotes/"); + viewComments += " -- Loc: "; + viewComments += origin.ToString(); + viewComments += "\n"; + viewComments += args.Argv(3); + player->hud->SetStateString( "viewcomments", viewComments ); + player->hud->HandleNamedEvent( "showViewComments" ); +} + +/* +================== +Cmd_CloseViewNotes_f +================== +*/ +static void Cmd_CloseViewNotes_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); +} + +/* +================== +Cmd_ShowViewNotes_f +================== +*/ +static void Cmd_ShowViewNotes_f( const idCmdArgs &args ) { + static idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_NOFATALERRORS ); + idToken token; + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( !parser.IsLoaded() ) { + idStr str = "viewnotes/"; + str += gameLocal.GetMapName(); + str.StripFileExtension(); + str += "/"; + if ( args.Argc() > 1 ) { + str += args.Argv( 1 ); + } else { + str += "comments"; + } + str.SetFileExtension( ".txt" ); + if ( !parser.LoadFile( str ) ) { + gameLocal.Printf( "No view notes for %s\n", gameLocal.GetMapName() ); + return; + } + } + + if ( parser.ExpectTokenString( "view" ) && parser.Parse1DMatrix( 3, origin.ToFloatPtr() ) && + parser.Parse1DMatrix( 9, axis.ToFloatPtr() ) && parser.ExpectTokenString( "comments" ) && parser.ReadToken( &token ) ) { + player->hud->SetStateString( "viewcomments", token ); + player->hud->HandleNamedEvent( "showViewComments" ); + player->Teleport( origin, axis.ToAngles(), NULL ); + } else { + parser.FreeSource(); + player->hud->HandleNamedEvent( "hideViewComments" ); + return; + } +} +#endif +// RAVEN END + +/* +================= +FindEntityGUIs + +helper function for Cmd_NextGUI_f. Checks the passed entity to determine if it +has any valid gui surfaces. +================= +*/ +bool FindEntityGUIs( idEntity *ent, const modelSurface_t ** surfaces, int maxSurfs, int &guiSurfaces ) { + renderEntity_t *renderEnt; + idRenderModel *renderModel; + const modelSurface_t *surf; + const idMaterial *shader; + int i; + + assert( surfaces != NULL ); + assert( ent != NULL ); + + memset( surfaces, 0x00, sizeof( modelSurface_t *) * maxSurfs ); + guiSurfaces = 0; + + renderEnt = ent->GetRenderEntity(); + renderModel = renderEnt->hModel; + if ( renderModel == NULL ) { + return false; + } + + for( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf == NULL ) { + continue; + } + shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( shader->GetEntityGui() > 0 ) { + surfaces[ guiSurfaces++ ] = surf; + } + } + + return ( guiSurfaces != 0 ); +} + +/* +================= +Cmd_NextGUI_f +================= +*/ +void Cmd_NextGUI_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + int guiSurfaces; + bool newEnt; + renderEntity_t *renderEnt; + int surfIndex; + srfTriangles_t *geom; + idMat4 modelMatrix; + idVec3 normal; + idVec3 center; + const modelSurface_t *surfaces[ MAX_RENDERENTITY_GUI ]; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 1 ) { + gameLocal.Printf( "usage: nextgui\n" ); + return; + } + + // start at the last entity + ent = gameLocal.lastGUIEnt.GetEntity(); + + // see if we have any gui surfaces left to go to on the current entity. + guiSurfaces = 0; + newEnt = false; + if ( ent == NULL ) { + newEnt = true; + } else if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == true ) { + if ( gameLocal.lastGUI >= guiSurfaces ) { + newEnt = true; + } + } else { + // no actual gui surfaces on this ent, so skip it + newEnt = true; + } + + if ( newEnt == true ) { + // go ahead and skip to the next entity with a gui... + if ( ent == NULL ) { + ent = gameLocal.spawnedEntities.Next(); + } else { + ent = ent->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->spawnArgs.GetString( "gui", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui2", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui3", NULL ) != NULL ) { + break; + } + + // try the next entity + gameLocal.lastGUIEnt = ent; + } + + gameLocal.lastGUIEnt = ent; + gameLocal.lastGUI = 0; + + if ( !ent ) { + gameLocal.Printf( "No more gui entities. Starting over...\n" ); + return; + } + } + + if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == false ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces.\n", ent->name.c_str() ); + } + + if ( guiSurfaces == 0 ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces!\n", ent->name.c_str() ); + return; + } + + gameLocal.Printf( "Teleporting to gui entity \"%s\", gui #%d.\n" , ent->name.c_str (), gameLocal.lastGUI ); + + renderEnt = ent->GetRenderEntity(); + surfIndex = gameLocal.lastGUI++; + geom = surfaces[ surfIndex ]->geometry; + if ( geom == NULL ) { + gameLocal.Printf( "Entity \"%s\" has gui surface %d without geometry!\n", ent->name.c_str(), surfIndex ); + return; + } + + assert( geom->facePlanes != NULL ); + + modelMatrix = idMat4( renderEnt->axis, renderEnt->origin ); + normal = geom->facePlanes[ 0 ].Normal() * renderEnt->axis; + center = geom->bounds.GetCenter() * modelMatrix; + + origin = center + (normal * 32.0f); + origin.z -= player->EyeHeight(); + normal *= -1.0f; + angles = normal.ToAngles (); + + // make sure the player is in noclip + player->noclip = true; + player->Teleport( origin, angles, NULL ); +} + +static void ArgCompletion_DefFile( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "def/", true, ".def", NULL ); +} + +/* +=============== +Cmd_TestId_f +outputs a string from the string table for the specified id +=============== +*/ +void Cmd_TestId_f( const idCmdArgs &args ) { + idStr id; + int i; + if ( args.Argc() == 1 ) { + common->Printf( "usage: testid \n" ); + return; + } + + for ( i = 1; i < args.Argc(); i++ ) { + id += args.Argv( i ); + } + if ( idStr::Cmpn( id, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + id = STRTABLE_ID + id; + } + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( id ), "", "", "" ); +} + +// RAVEN BEGIN +// ddynerman: instance testing commands +void Cmd_SetInstance_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_SetInstance_f(): setInstance can only be run on a server\n" ); + return; + } + + if ( args.Argc() <= 2 ) { + common->Printf( "usage: setInstance \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + int instanceID = atoi( args.Argv( 2 ) ); + + if( client < 0 || client >= MAX_CLIENTS || !gameLocal.entities[ client ] ) { + gameLocal.Warning( "Cmd_SetInstance_f(): Invalid clientnum %d\n", client ); + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ client ]; + + gameLocal.Printf( "Cmd_SetInstance_f(): Switching %d: %s to instance %d\n", client, gameLocal.userInfo[ client ].GetString( "ui_name", "unknown" ), instanceID ); + + player->JoinInstance( instanceID ); +} + +void Cmd_ListInstances_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_ListInstances_f(): listInstances can only be run on a server\n" ); + return; + } + + for( int i = 0; i < gameLocal.GetNumInstances(); i++ ) { + gameLocal.Printf("Instance %d:\n", i ); + for( int j = 0; j < MAX_CLIENTS; j++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ j ]; + if( player && player->GetInstance() == i ) { + gameLocal.Printf( "\t%d: %s\n", j, player->GetUserInfo()->GetString( "ui_name" ) ); + } + } + } +} + +void Cmd_AddIcon_f( const idCmdArgs& args ) { + if ( args.Argc() <= 1 ) { + common->Printf( "usage: addIcon \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + + iconManager->AddIcon( client, "textures/mp/awards/capture" ); +} +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +void Cmd_ToggleBuyMenu_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->CanBuy() ) + { + gameLocal.mpGame.OpenLocalBuyMenu(); + } +} + +void Cmd_BuyItem_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + common->Printf( "ERROR: Cmd_BuyItem_f() failed, since GetLocalPlayer() was NULL.\n", player ); + return; + } + + player->GenerateImpulseForBuyAttempt( args.Argv(1) ); +} +// RITUAL END + +void Cmd_PlayerEmote_f( const idCmdArgs& args ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + gameLocal.Warning( "Cmd_Emote_f() - local player is NULL" ); + return; + } + + if ( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: emote \n" ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab_a'\n" ); + return; + } + + if( !idStr::Icmp( args.Argv( 1 ), "taunt" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_TAUNT ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_a" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_A ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_b" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_B ); + } else if( !idStr::Icmp( args.Argv( 1), "salute" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_SALUTE ); + } else if( !idStr::Icmp( args.Argv( 1), "cheer" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_CHEER ); + } else { + gameLocal.Printf( "Invalid emote '%s'\n", args.Argv( 1 ) ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab'\n" ); + } + +} + +// mekberg: added +void Cmd_SetPMCVars_f ( const idCmdArgs &args ) { + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( gameLocal.GetLocalPlayer( ) ) { + gameLocal.GetLocalPlayer( )->SetPMCVars( ); + } +} + +void Cmd_FadeSound_f( const idCmdArgs &args ) { + + if( args.Argc() < 2) { + return; + } + + float fadeDB = 0.0f; + float fadeTime = 0.0f; + + idStr _fadeDB = args.Argv( 1); + fadeDB = atof( _fadeDB ); + + idStr _fadeTime = args.Argv( 2); + fadeTime = atof( _fadeTime ); + + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, SOUND_CLASS_MUSICAL, 0.0f - fadeDB, fadeTime ); + +} + +void Cmd_TestClientModel_f( const idCmdArgs& args ) { + rvClientEntity* face; + const idDict* dict = gameLocal.FindEntityDefDict( "player_marine_client", false ); + + gameLocal.SpawnClientEntityDef( *dict, &face, false ); + +// face = new rvClientAFEntity( *dict ); +// face->Spawn( dict ); +// face->SetOrigin( vec3_zero ); +// face->SetAxis( mat3_identity ); +// face->SetModel( "model_player_marine" ); +} + + +// RAVEN END + +void Cmd_CheckSave_f( const idCmdArgs &args ); + +void Cmd_ShuffleTeams_f( const idCmdArgs& args ) { + gameLocal.mpGame.ShuffleTeams(); +} + +#ifndef _FINAL +void Cmd_ClientOverflowReliable_f( const idCmdArgs& args ) { + idBitMsg outMsg; + byte msgBuf[ 114688 ]; + + for( int i = 0; i < 10; i++ ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( -1 ); + for( int j = 0; j < 8190; j++ ) { + outMsg.WriteByte( j ); + } + networkSystem->ClientSendReliableMessage( outMsg ); + } +} +#endif + +void Cmd_ListMaps_f( const idCmdArgs& args ) { + gameLocal.mpGame.ListMaps(); +} + +/* +================= +idGameLocal::InitConsoleCommands + +Let the system know about all of our commands +so it can perform tab completion +================= +*/ +void idGameLocal::InitConsoleCommands( void ) { +// RAVEN BEGIN +// jscott: typeinfo gone - didn't work, it was unfinished +// cmdSystem->AddCommand( "listTypeInfo", ListTypeInfo_f, CMD_FL_GAME, "list type info" ); +// cmdSystem->AddCommand( "writeGameState", WriteGameState_f, CMD_FL_GAME, "write game state" ); +// cmdSystem->AddCommand( "testSaveGame", TestSaveGame_f, CMD_FL_GAME|CMD_FL_CHEAT, "test a save game for a level" ); +// RAVEN END + cmdSystem->AddCommand( "game_memory", idClass::DisplayInfo_f, CMD_FL_GAME, "displays game class info" ); + cmdSystem->AddCommand( "listClasses", idClass::ListClasses_f, CMD_FL_GAME, "lists game classes" ); + cmdSystem->AddCommand( "listThreads", idThread::ListThreads_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists script threads" ); + cmdSystem->AddCommand( "listEntities", Cmd_EntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists game entities" ); + cmdSystem->AddCommand( "listClientEntities", Cmd_ClientEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists client entities" ); + cmdSystem->AddCommand( "listActiveEntities", Cmd_ActiveEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists active game entities" ); + cmdSystem->AddCommand( "listMonsters", idAI::List_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists monsters" ); + cmdSystem->AddCommand( "listSpawnArgs", Cmd_ListSpawnArgs_f, CMD_FL_GAME|CMD_FL_CHEAT, "list the spawn args of an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "listMaps", Cmd_ListMaps_f, CMD_FL_GAME, "list the available maps" ); + cmdSystem->AddCommand( "say", Cmd_Say_f, CMD_FL_GAME, "text chat" ); + cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); + cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); + cmdSystem->AddCommand( "gameKick", Cmd_Kick_f, CMD_FL_GAME, "same as kick, but recognizes player names" ); + cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "undying", Cmd_Undying_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables undying mode (take damage down to 1 health, but do not die)" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "kill", Cmd_Kill_f, CMD_FL_GAME, "kills the player" ); + cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); + cmdSystem->AddCommand( "teleport", Cmd_Teleport_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleports the player to an entity location", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "trigger", Cmd_Trigger_f, CMD_FL_GAME|CMD_FL_CHEAT, "triggers an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); + cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); + cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); + cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); + cmdSystem->AddCommand( "addarrow", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug arrow" ); + cmdSystem->AddCommand( "removeline", Cmd_RemoveDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes a debug line" ); + cmdSystem->AddCommand( "blinkline", Cmd_BlinkDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "blinks a debug line" ); + cmdSystem->AddCommand( "listLines", Cmd_ListDebugLines_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists all debug lines" ); + cmdSystem->AddCommand( "playerModel", Cmd_PlayerModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the given model on the player", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "flashlight", Cmd_Flashlight_f, CMD_FL_GAME|CMD_FL_CHEAT, "toggle actor's flashlight", idGameLocal::ArgCompletion_AIName ); + + cmdSystem->AddCommand( "shuffleTeams", Cmd_ShuffleTeams_f, CMD_FL_GAME, "shuffle teams" ); +// RAVEN BEGIN +// bdube: not using id effect system +// cmdSystem->AddCommand( "testFx", Cmd_TestFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system", idCmdSystem::ArgCompletion_Decl ); +// cmdSystem->AddCommand( "testBoneFx", Cmd_TestBoneFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system bound to a joint", idCmdSystem::ArgCompletion_Decl ); +// RAVEN END + cmdSystem->AddCommand( "testLight", Cmd_TestLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a light" ); + cmdSystem->AddCommand( "testPointLight", Cmd_TestPointLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a point light" ); + cmdSystem->AddCommand( "popLight", Cmd_PopLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes the last created light" ); + cmdSystem->AddCommand( "testDeath", Cmd_TestDeath_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests death" ); + cmdSystem->AddCommand( "testSave", Cmd_TestSave_f, CMD_FL_GAME|CMD_FL_CHEAT, "writes out a test savegame" ); + cmdSystem->AddCommand( "testModel", idTestModel::TestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a model", idTestModel::ArgCompletion_TestModel ); + cmdSystem->AddCommand( "testSkin", idTestModel::TestSkin_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a skin on an existing testModel", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testShaderParm", idTestModel::TestShaderParm_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets a shaderParm on an existing testModel" ); + cmdSystem->AddCommand( "keepTestModel", idTestModel::KeepTestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "keeps the last test model in the game" ); + cmdSystem->AddCommand( "testAnim", idTestModel::TestAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an animation", idTestModel::ArgCompletion_TestAnim ); + cmdSystem->AddCommand( "testParticleStopTime", idTestModel::TestParticleStopTime_f,CMD_FL_GAME|CMD_FL_CHEAT, "tests particle stop time on a test model" ); + cmdSystem->AddCommand( "nextAnim", idTestModel::TestModelNextAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation on test model" ); + cmdSystem->AddCommand( "prevAnim", idTestModel::TestModelPrevAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation on test model" ); + cmdSystem->AddCommand( "nextFrame", idTestModel::TestModelNextFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation frame on test model" ); + cmdSystem->AddCommand( "prevFrame", idTestModel::TestModelPrevFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation frame on test model" ); + cmdSystem->AddCommand( "testBlend", idTestModel::TestBlend_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests animation blending" ); + cmdSystem->AddCommand( "reloadScript", Cmd_ReloadScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads scripts" ); + cmdSystem->AddCommand( "script", Cmd_Script_f, CMD_FL_GAME|CMD_FL_CHEAT, "executes a line of script" ); + cmdSystem->AddCommand( "listCollisionModels", Cmd_ListCollisionModels_f, CMD_FL_GAME, "lists collision models" ); + cmdSystem->AddCommand( "collisionModelInfo", Cmd_CollisionModelInfo_f, CMD_FL_GAME, "shows collision model info" ); + cmdSystem->AddCommand( "reexportmodels", Cmd_ReexportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "reexports models", ArgCompletion_DefFile ); + cmdSystem->AddCommand( "reloadanims", Cmd_ReloadAnims_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads animations" ); + cmdSystem->AddCommand( "listAnims", Cmd_ListAnims_f, CMD_FL_GAME, "lists all animations" ); + cmdSystem->AddCommand( "aasStats", Cmd_AASStats_f, CMD_FL_GAME, "shows AAS stats" ); + cmdSystem->AddCommand( "testDamage", Cmd_TestDamage_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a damage def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "weaponSplat", Cmd_WeaponSplat_f, CMD_FL_GAME|CMD_FL_CHEAT, "projects a blood splat on the player weapon" ); + cmdSystem->AddCommand( "saveSelected", Cmd_SaveSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves the selected entity to the .map file" ); + cmdSystem->AddCommand( "deleteSelected", Cmd_DeleteSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "deletes selected entity" ); + cmdSystem->AddCommand( "saveMoveables", Cmd_SaveMoveables_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all moveables to the .map file" ); + cmdSystem->AddCommand( "saveRagdolls", Cmd_SaveRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all ragdoll poses to the .map file" ); + cmdSystem->AddCommand( "bindRagdoll", Cmd_BindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "binds ragdoll at the current drag position" ); + cmdSystem->AddCommand( "unbindRagdoll", Cmd_UnbindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "unbinds the selected ragdoll" ); + cmdSystem->AddCommand( "saveLights", Cmd_SaveLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "saveParticles", Cmd_SaveParticles_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "clearLights", Cmd_ClearLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "clears all lights" ); + cmdSystem->AddCommand( "gameError", Cmd_GameError_f, CMD_FL_GAME|CMD_FL_CHEAT, "causes a game error" ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + cmdSystem->AddCommand( "listEntityStats", Cmd_ListEntityStats_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists global entity stats" ); +// ddynerman: mp spawning test command + cmdSystem->AddCommand( "evaluateMPPerformance", Cmd_EvaluateMPPerformance_f,CMD_FL_GAME|CMD_FL_CHEAT, "spawns serveral player models", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "listMapEntities", idGameLocal::Cmd_PrintMapEntityNumbers_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); + cmdSystem->AddCommand( "listSpawnIds", idGameLocal::Cmd_PrintSpawnIds_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); +// RAVEN END + +#ifndef ID_DEMO_BUILD + cmdSystem->AddCommand( "disasmScript", Cmd_DisasmScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "disassembles script" ); +// RAVEN BEGIN +// rjohnson: removed old not taking system +/* + cmdSystem->AddCommand( "recordViewNotes", Cmd_RecordViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "record the current view position with notes" ); + cmdSystem->AddCommand( "showViewNotes", Cmd_ShowViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "show any view notes for the current map, successive calls will cycle to the next note" ); + cmdSystem->AddCommand( "closeViewNotes", Cmd_CloseViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "close the view showing any notes for this map" ); +*/ +// RAVEN END + cmdSystem->AddCommand( "exportmodels", Cmd_ExportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "exports models", ArgCompletion_DefFile ); + + // multiplayer client commands ( replaces old impulses stuff ) + //cmdSystem->AddCommand( "clientDropWeapon", idMultiplayerGame::DropWeapon_f, CMD_FL_GAME, "drop current weapon" ); + cmdSystem->AddCommand( "clientMessageMode", idMultiplayerGame::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); + // FIXME: implement + cmdSystem->AddCommand( "clientVote", idMultiplayerGame::Vote_f, CMD_FL_GAME, "cast your vote: clientVote yes | no" ); + cmdSystem->AddCommand( "clientCallVote", idMultiplayerGame::CallVote_f, CMD_FL_GAME, "call a vote: clientCallVote si_.. proposed_value" ); + cmdSystem->AddCommand( "clientVoiceChat", idMultiplayerGame::VoiceChat_f, CMD_FL_GAME, "voice chats: clientVoiceChat " ); + cmdSystem->AddCommand( "clientVoiceChatTeam", idMultiplayerGame::VoiceChatTeam_f, CMD_FL_GAME, "team voice chats: clientVoiceChat " ); +// RAVEN BEGIN + // jshepard + cmdSystem->AddCommand( "forceTeamChange", idMultiplayerGame::ForceTeamChange_f, CMD_FL_GAME, "force team change: forceTeamChange " ); + cmdSystem->AddCommand( "removeClientFromBanList", idMultiplayerGame::RemoveClientFromBanList_f, CMD_FL_GAME, "removes a client id from the ban list: removeClientFromBanList " ); + +#ifndef _XBOX +// shouchard: more voice chat stuff (non-XBOX) + cmdSystem->AddCommand( "clientvoicemute", idMultiplayerGame::VoiceMute_f, CMD_FL_GAME, "mute the specified player's incoming voicechat" ); + cmdSystem->AddCommand( "clientvoiceunmute", idMultiplayerGame::VoiceUnmute_f, CMD_FL_GAME, "unmute the specified player's incoming voicechat" ); +#endif // _XBOX +// RAVEN END + + // multiplayer server commands + cmdSystem->AddCommand( "verifyServerSettings", idGameLocal::VerifyServerSettings_f, CMD_FL_GAME, "verifies the game type can be played on the map" ); + cmdSystem->AddCommand( "serverMapRestart", idGameLocal::MapRestart_f, CMD_FL_GAME, "restart the current game" ); + cmdSystem->AddCommand( "serverForceReady", idMultiplayerGame::ForceReady_f,CMD_FL_GAME, "force all players ready" ); + cmdSystem->AddCommand( "serverNextMap", idGameLocal::NextMap_f, CMD_FL_GAME, "change to the next map" ); +#endif + + cmdSystem->AddCommand( "CheckTeamBalance", idMultiplayerGame::CheckTeamBalance_f, CMD_FL_GAME, "helper for team switching in the guis - " ); + + // localization help commands + cmdSystem->AddCommand( "nextGUI", Cmd_NextGUI_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleport the player to the next func_static with a gui" ); + cmdSystem->AddCommand( "testid", Cmd_TestId_f, CMD_FL_GAME|CMD_FL_CHEAT, "output the string for the specified id." ); + +// RAVEN BEGIN +// bdube: vehicle code + cmdSystem->AddCommand( "killVehicles", Cmd_KillVehicles_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all vehicles" ); + cmdSystem->AddCommand( "killMessage", Cmd_KillMessage_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints a fake death message" ); + cmdSystem->AddCommand( "apState", Cmd_APState_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints AP state" ); +// bdube: jump points + cmdSystem->AddCommand( "jump", Cmd_DebugJump_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to a specific debug jump point" ); + cmdSystem->AddCommand( "nextjumppoint", Cmd_DebugNextJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the next debug jump point " ); + cmdSystem->AddCommand( "prevjumppoint", Cmd_DebugPrevJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the previous debug jump point" ); +// cdr: Added Extract Tactical + cmdSystem->AddCommand( "extract_tactical", Cmd_AASExtractTactical_f, CMD_FL_GAME, "pulls tactical information for the current position." ); +// RAVEN END + +// RAVEN BEGIN +// abahr + cmdSystem->AddCommand( "call", Cmd_CallScriptFunc_f, CMD_FL_GAME|CMD_FL_CHEAT, "calls script function and prints out return val" ); + cmdSystem->AddCommand( "setPlayerGravity", Cmd_SetPlayerGravity_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets players local gravity" ); +// cdr + cmdSystem->AddCommand( "ai_debugFilter", Cmd_AI_DebugFilter_f, CMD_FL_GAME, "ai_debugMove and ai_debugTactical only work on the specified entity (if none, does one you're looking at)", idGameLocal::ArgCompletion_AIName ); +// ddynerman: multiple arena/CW stuff + cmdSystem->AddCommand( "setInstance", Cmd_SetInstance_f, CMD_FL_GAME, "sets a player's world instance" ); + cmdSystem->AddCommand( "addIcon", Cmd_AddIcon_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a test icon" ); + cmdSystem->AddCommand( "listInstances", Cmd_ListInstances_f, CMD_FL_GAME, "lists instances" ); +// ddynerman: emote anims + cmdSystem->AddCommand( "emote", Cmd_PlayerEmote_f, CMD_FL_GAME, "plays an emote" ); + + cmdSystem->AddCommand( "checkSave", Cmd_CheckSave_f, CMD_FL_GAME, "tests save system" ); + +// jshepard: fade music in / out + cmdSystem->AddCommand( "fadeSound", Cmd_FadeSound_f, CMD_FL_GAME|CMD_FL_CHEAT, "fades all sound by X decibles over Y seconds" ); + +// mekberg: added. + cmdSystem->AddCommand( "setPMCVars", Cmd_SetPMCVars_f, CMD_FL_GAME, "Resets player movement cvars" ); + + cmdSystem->AddCommand( "testClientModel", Cmd_TestClientModel_f, CMD_FL_GAME, "" ); +#ifndef _FINAL + cmdSystem->AddCommand( "clientOverflowReliable", Cmd_ClientOverflowReliable_f, CMD_FL_GAME, "" ); +#endif +// RAVEN END +// RITUAL START +// squirrel: Mode-agnostic buymenus + cmdSystem->AddCommand( "buyMenu", Cmd_ToggleBuyMenu_f, CMD_FL_GAME, "Toggle buy menu (if in a buy zone and the game type supports it)" ); + cmdSystem->AddCommand( "buy", Cmd_BuyItem_f, CMD_FL_GAME, "Buy an item (if in a buy zone and the game type supports it)" ); +// RITUAL END + +} + +/* +================= +idGameLocal::ShutdownConsoleCommands +================= +*/ +void idGameLocal::ShutdownConsoleCommands( void ) { + cmdSystem->RemoveFlaggedCommands( CMD_FL_GAME ); +} diff --git a/source/mpgame/gamesys/SysCmds.h b/source/mpgame/gamesys/SysCmds.h new file mode 100644 index 0000000..cb60f62 --- /dev/null +++ b/source/mpgame/gamesys/SysCmds.h @@ -0,0 +1,11 @@ + +#ifndef __SYS_CMDS_H__ +#define __SYS_CMDS_H__ + +void D_DrawDebugLines( void ); + +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ); + +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ); + +#endif /* !__SYS_CMDS_H__ */ diff --git a/source/mpgame/gamesys/SysCvar.cpp b/source/mpgame/gamesys/SysCvar.cpp new file mode 100644 index 0000000..09dc58f --- /dev/null +++ b/source/mpgame/gamesys/SysCvar.cpp @@ -0,0 +1,685 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../sys/AutoVersion.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "-release" +#endif + +/* + +All game cvars should be defined here. + +*/ + +// RAVEN BEGIN +// ddynerman: our gameplay modes +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +const char *si_gameTypeArgs[] = { "singleplayer", "DM", "Tourney", "Team DM", "CTF", "Arena CTF", "DeadZone", NULL }; +const int si_numGameTypeArgs = sizeof( si_gameTypeArgs ) / sizeof( si_gameTypeArgs[0] ); +// RITUAL END +// RAVEN END +const char *si_readyArgs[] = { "Not Ready", "Ready", NULL }; +const char *si_spectateArgs[] = { "Play", "Spectate", NULL }; + +// RAVEN BEGIN +// ddynerman: our teams +const char *ui_teamArgs[] = { "Marine", "Strogg", NULL }; +// RAVEN END + +struct gameVersion_s { + gameVersion_s( void ) { sprintf( string, "%s %s V%s %s %s", GAME_NAME, GAME_BUILD_TYPE, VERSION_STRING_DOTTED, BUILD_STRING, __DATE__ ); } + char string[256]; +} gameVersion; + +idCVar g_version( "g_version", gameVersion.string, CVAR_GAME | CVAR_ROM, "game version" ); + +// noset vars +idCVar gamedate( "gamedate", __DATE__, CVAR_GAME | CVAR_ROM, "" ); + +// server info +idCVar si_name( "si_name", "Quake 4 Server", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "name of the server" ); +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +//idCVar sq_numRoundsPerMatch( "dz_numRoundsPerMatch", "5", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of rounds per match in DeadZone", 1, 999999 ); +//idCVar sq_buyFreezeSeconds( "dz_buyFreezeSeconds", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of seconds players are frozen at the start of each round in DeadZone", 0, 30 ); +//idCVar sq_buyTimeSeconds( "dz_buyTimeSeconds", "20", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of additional seconds after buy freeze that buy zones are active", 0, 999999 ); +// squirrel: Mode-agnostic buymenus +idCVar si_isBuyingEnabled( "si_isBuyingEnabled", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable buying in current mode" ); +idCVar si_dropWeaponsInBuyingModes( "si_dropWeaponsInBuyingModes", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "dead players drop weapons, even in Buying game modes" ); +// RITUAL END +// RAVEN BEGIN +// ddynerman: new gametype strings +idCVar si_gameType( "si_gameType", si_gameTypeArgs[ 0 ], CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "game type - singleplayer, DM, Tourney, Team DM, CTF, Arena CTF, or DeadZone", si_gameTypeArgs, idCmdSystem::ArgCompletion_String ); +idCVar si_map( "si_map", "mp/q4dm1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map to be played next on server", idCmdSystem::ArgCompletion_MapName ); +idCVar si_mapCycle( "si_mapCycle", "", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map cycle list semicolon delimited" ); +// bdube: raise player limit +idCVar si_maxPlayers( "si_maxPlayers", "12", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "max number of players allowed on the server", 1, 16 ); +// ddynerman: min players to start +idCVar si_minPlayers( "si_minPlayers", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "min number of players to start a game (only when warmup is enabled)", 1, 16 ); +// ddynerman: CTF +idCVar si_captureLimit( "si_captureLimit", "5", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "score limit for CTF", 1, MP_PLAYER_MAXFRAGS ); +// shouchard: for tourney +idCVar si_tourneyLimit( "si_tourneyLimit", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of times a tourney will be run before cycling maps", 1, MP_PLAYER_MAXFRAGS ); +idCVar si_useReady( "si_useReady", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "require players to ready before starting a match" ); +idCVar si_allowVoting( "si_allowVoting", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable or disable server option voting" ); +// ddynerman: disable hitscan tint +idCVar si_allowHitscanTint( "si_allowHitscanTint", "2", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "use hitscan tint (e.g. rail color) 0 - no tinting allowed, 1 - player hitscan tinting allowed in DM and NO hitscan tinting in team games, 2 - player hitscan tinting allowed in DM and use team-color hitscan tints in team games" ); +idCVar si_privatePlayers( "si_privatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "number of private player slots reserved on the server. subtracts from si_maxPlayers, so a server with si_maxPlayers 16 and 4 private player slots will only allow 12 public players to connect - see g_privatePassword, privatePassword", 0, 16 ); +idCVar g_privatePassword( "g_privatePassword", "", CVAR_GAME | PC_CVAR_ARCHIVE, "server-side password to access reserved client slots, clients set privatePassword" ); +idCVar privatePassword( "privatePassword", "", CVAR_GAME | CVAR_NOCHEAT, "client password used to access a servers private player slots" ); +idCVar si_numPrivatePlayers( "si_numPrivatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "number of private slots currently in use" ); +idCVar si_suddenDeathRestart( "si_suddenDeathRestart", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "toggles whether or not to respawn players/items when team games enter sudden death" ); +// RAVEN END +idCVar si_fragLimit( "si_fragLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "frag limit", 0, MP_PLAYER_MAXFRAGS ); +idCVar si_timeLimit( "si_timeLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "time limit in minutes", 0, 60 ); +idCVar si_teamDamage( "si_teamDamage", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable team damage" ); +idCVar si_warmup( "si_warmup", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "do pre-game warmup" ); +idCVar si_usePass( "si_usePass", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable client password checking" ); +#ifdef _MPBETA + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | CVAR_ROM, "server is pure and does not allow modified data" ); +#else + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "server is pure and does not allow modified data" ); +#endif // _MPBETA +idCVar si_spectators( "si_spectators", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "allow spectators or require all clients to play" ); +idCVar si_shuffle( "si_shuffle", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "shuffle teams after each round" ); +// shouchard: g_balanceTDM->si_autobalance so we can also use it for CTF +// asalmon: Changed to archive only on PC +idCVar si_autobalance( "si_autobalance", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | PC_CVAR_ARCHIVE, "maintain even teams" ); + +// RAVEN BEGIN +// jscott: added entity filtering +idCVar si_entityFilter( "si_entityFilter", "", CVAR_GAME | CVAR_SERVERINFO, "filter to use when spawning entities" ); +idCVar si_countDown( "si_countDown", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "pregame countdown in seconds", 4, 3600 ); +// MCG: added "weapon stay" option +idCVar si_weaponStay( "si_weaponStay", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "cannot pick up weapons you already have (get no ammo from them)" ); +// RAVEN END + +// RITUAL BEGIN +// DeadZone Mode and Buying related CVARS +idCVar si_deadZonePowerupTime( "si_deadZonePowerupTime", "45", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of time the dead zone powerup lasts" ); +idCVar si_buyModeStartingCredits( "si_buyModeStartingCredits", "1000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of credits players start with in buying enable games" ); +idCVar si_buyModeMaxCredits( "si_buyModeMaxCredits", "25000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Maximum amount of credits in buying enable games" ); +idCVar si_buyModeMinCredits( "si_buyModeMinCredits", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Minimum amount of credits in buying enable games" ); +idCVar si_controlTime( "si_controlTime", "120", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Time required to hold the dead zone", 1, 999 ); +// RITUAL END + +idCVar si_fps( "si_fps", "60", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Server framerate/game tick rate", 30, 90 ); + +idCVar ri_useViewerPass( "ri_useViewerPass", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "use g_viewerPassword for viewers/repeaters" ); +idCVar g_viewerPassword( "g_viewerPassword", "", CVAR_GAME | CVAR_ARCHIVE, "password for viewers/repeaters" ); + +idCVar ri_privateViewers( "ri_privateViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of private viewer slots" ); +idCVar g_privateViewerPassword( "g_privateViewerPassword", "", CVAR_GAME | CVAR_ARCHIVE, "privatePassword for private viewer slots" ); +idCVar g_repeaterPassword( "g_repeaterPassword", "", CVAR_GAME | CVAR_ARCHIVE, "privatePassword for repeaters" ); + +idCVar ri_numViewers( "ri_numViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_INTEGER | CVAR_ROM, "number of viewer slots in use" ); +idCVar ri_numPrivateViewers( "ri_numPrivateViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_INTEGER | CVAR_ROM, "number of private viewer slots in use" ); + +idCVar ri_name( "ri_name", "", CVAR_GAME | CVAR_ARCHIVE, "override the server's si_name with this for relays" ); + +idCVar g_noTVChat( "g_noTVChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Server enable/disable flag for viewer chat on Q4TV" ); + +// user info +idCVar ui_name( "ui_name", "Player", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player name" ); +idCVar ui_team( "ui_team", ui_teamArgs[ 0 ], CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player team", ui_teamArgs, idCmdSystem::ArgCompletion_String ); +// RAVEN BEGIN +// ddynerman: new UI cvars +idCVar ui_model( "ui_model", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model, blank uses default model" ); +idCVar ui_model_backup( "ui_model_backup", "", CVAR_GAME | CVAR_USERINFO, "player model backup" ); +idCVar ui_model_marine( "ui_model_marine", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on marine team in team games, blank uses default model" ); +idCVar ui_model_strogg( "ui_model_strogg", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on strogg team in team games, blank uses default model" ); +idCVar ui_clan( "ui_clan", "", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player clan" ); +idCVar ui_hitscanTint( "ui_hitscanTint", "120.0 0.6 1.0", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "a tint applied to select hitscan effects. Specified as a value in HSV color space. Hue [0.0-360.0] Saturation [0.0-1.0] Value [0.75-1.0]" ); +// RAVEN END +idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); +idCVar ui_autoReload( "ui_autoReload", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); +idCVar ui_showGun( "ui_showGun", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "show gun" ); +idCVar ui_ready( "ui_ready", si_readyArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "player is ready to start playing", idCmdSystem::ArgCompletion_String ); +idCVar ui_spectate( "ui_spectate", si_spectateArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "play or spectate", idCmdSystem::ArgCompletion_String ); +idCVar ui_chat( "ui_chat", "0", CVAR_GAME | CVAR_USERINFO | CVAR_BOOL | CVAR_ROM | CVAR_CHEAT, "player is chatting" ); +idCVar ui_handicap( "ui_handicap", "100", CVAR_GAME | CVAR_USERINFO | CVAR_INTEGER | CVAR_ARCHIVE, "player damage output handicap" ); + +idCVar hud_showSpeed( "hud_showSpeed", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Show player's movement speed, measured in Units Per Second." ); +idCVar hud_showInput( "hud_showInput", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "When set to 1, shows the players movement controls on the HUD"); +idCVar hud_inputPosition( "hud_inputPosition", "580 90", CVAR_GAME | CVAR_ARCHIVE, "Input display position (x y)"); +idCVar hud_inputColor( "hud_inputColor", "1 0 0", CVAR_GAME | CVAR_ARCHIVE, "Input display color (r g b, range 0 to 1)"); + +// change anytime vars +idCVar developer( "developer", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_forceModel( "g_forceModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces all players to this model in non-team gameplay modes. See g_forceStroggModel, g_forceMarineModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModel ); +idCVar g_forceStroggModel( "g_forceStroggModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Strogg team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelStrogg ); +idCVar g_forceMarineModel( "g_forceMarineModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Marine team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelMarine ); + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +idCVar g_fixedHorizFOV( "r_fixedHorizFOV", "0", CVAR_RENDERER | CVAR_BOOL, "vertical stretch for letterboxed cinematics authored for 4:3 aspect" ); +idCVar g_cinematic( "g_cinematic", "1", CVAR_GAME | CVAR_BOOL, "skips updating entities that aren't marked 'cinematic' '1' during cinematics" ); +idCVar g_cinematicMaxSkipTime( "g_cinematicMaxSkipTime", "600", CVAR_GAME | CVAR_FLOAT, "# of seconds to allow game to run when skipping cinematic. prevents lock-up when cinematic doesn't end.", 0, 3600 ); + +idCVar g_muzzleFlash( "g_muzzleFlash", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show muzzle flashes" ); +idCVar g_projectileLights( "g_projectileLights", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show dynamic lights on projectiles" ); +idCVar g_doubleVision( "g_doubleVision", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show double vision when taking damage" ); +idCVar g_monsters( "g_monsters", "1", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_decals( "g_decals", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "show decals such as bullet holes" ); +idCVar g_knockback( "g_knockback", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_skill( "g_skill", "1", CVAR_GAME | CVAR_INTEGER, "difficulty level", 0, MAX_SKILL_LEVELS - 1 ); +idCVar g_nightmare( "g_nightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed" ); +idCVar g_gravity( "g_gravity", DEFAULT_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "singleplayer gravity" ); +idCVar g_mp_gravity( "g_mp_gravity", DEFAULT_MP_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "multiplayer gravity" ); +idCVar g_skipFX( "g_skipFX", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_skipParticles( "g_skipParticles", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_nailTrail( "g_nailTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on nail projectiles" ); +idCVar g_grenadeTrail( "g_grenadeTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on grenade projectiles" ); +idCVar g_rocketTrail( "g_rocketTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on rocket projectiles" ); +idCVar g_railTrail( "g_railTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show spiral fx on rail trail" ); +idCVar g_napalmTrail( "g_napalmTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show fiery trails on napalm projectiles" ); + +idCVar g_predictedProjectiles( "g_predictedProjectiles", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "predict projectiles on the client" ); + +idCVar g_disasm( "g_disasm", "0", CVAR_GAME | CVAR_BOOL, "disassemble script into base/script/disasm.txt on the local drive when script is compiled" ); +idCVar g_debugBounds( "g_debugBounds", "0", CVAR_GAME | CVAR_BOOL, "checks for models with bounds > 2048" ); +idCVar g_debugAnim( "g_debugAnim", "-1", CVAR_GAME | CVAR_INTEGER, "displays information on which animations are playing on the specified entity number. set to -1 to disable." ); +idCVar g_debugMove( "g_debugMove", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugDamage( "g_debugDamage", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugWeapon( "g_debugWeapon", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugScript( "g_debugScript", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugMover( "g_debugMover", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME, "set to the name of the state you want to debug or * for all" ); +// RAVEN BEGIN +// bdube: added +idCVar g_debugState( "g_debugState", "0", CVAR_GAME, "" ); +idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); +//idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); +// RAVEN END +idCVar g_armorProtection( "g_armorProtection", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage" ); +idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); +idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how often to take health in nightmare mode" ); +idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how much health to take in nightmare mode" ); +idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); + + + +idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and thier targets. hidden entities are drawn grey." ); +idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and thier targets (green). disabled triggers are drawn grey." ); +idCVar g_showCollisionWorld( "g_showCollisionWorld", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionModels( "g_showCollisionModels", "0", CVAR_GAME | CVAR_INTEGER, "0 = off, 1 = draw collision models, 2 = only draw player collision models. g_maxShowDistance controls distance." ); +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces +idCVar g_showCollisionTraces( "g_showCollisionTraces", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +// ddynerman: SD's clip sector code +idCVar g_showClipSectors( "g_showClipSectors", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showClipSectorFilter( "g_showClipSectorFilter", "0", CVAR_GAME, "" ); +idCVar g_showAreaClipSectors( "g_showAreaClipSectors", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN END +idCVar g_maxShowDistance( "g_maxShowDistance", "128", CVAR_GAME | CVAR_FLOAT, "Distance at which to draw clipmodels and clipworld - Will significantly hurt performance at values above 512" ); +idCVar g_showEntityInfo( "g_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showviewpos( "g_showviewpos", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showcamerainfo( "g_showcamerainfo", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "displays the current frame # for the camera when playing cinematics" ); +idCVar g_showTestModelFrame( "g_showTestModelFrame", "0", CVAR_GAME | CVAR_BOOL, "displays the current animation and frame # for testmodels" ); +idCVar g_showActiveEntities( "g_showActiveEntities", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around thinking entities. dormant entities (outside of pvs) are drawn yellow. non-dormant are green." ); +idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around monsters that have targeted the the player" ); + +idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); +idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); + +// RAVEN BEGIN +// bdube: frame command debugging +idCVar g_showFrameCmds( "g_showFrameCmds", "0", CVAR_GAME | CVAR_BOOL, "displays frame commands as they are executed" ); +idCVar g_showGodDamage( "g_showGodDamage", "0", CVAR_GAME | CVAR_BOOL, "displays the amount of damage taken while in god mode on the hud" ); +idCVar g_debugVehicle( "g_debugVehicle", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +// RAVEN BEGIN +// twhitaker: for rvVehicleDriver +idCVar g_debugVehicleDriver( "g_debugVehicleDriver", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the func_vehicle_driver" ); +idCVar g_debugVehicleAI( "g_debugVehicleAI", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the vehicle ai system" ); +idCVar g_vehicleMode( "g_vehicleMode", "1", CVAR_GAME | CVAR_INTEGER, "enables the new vehicle control system for the GEV." ); +// RAVEN END +idCVar g_allowVehicleGunOverheat( "g_allowVehicleGunOverheat","1", CVAR_GAME | CVAR_BOOL, "allows disabling the gun overheating mechanism for vehicles that use it." ); + +idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); +idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); +idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); +idCVar ai_debugTactical( "ai_debugTactical", "0", CVAR_GAME, "draws tactical information for monsters" ); +idCVar ai_debugHelpers( "ai_debugHelpers", "0", CVAR_GAME, "draws ai helpers" ); +idCVar ai_debugFilterString( "ai_debugFilterString", "", CVAR_GAME, "see ai_debugFilter" ); +idCVar ai_testPredictPath( "ai_testPredictPath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCombatNodes( "ai_showCombatNodes", "0", CVAR_GAME | CVAR_BOOL, "draws attack cones for monsters" ); +idCVar ai_showPaths( "ai_showPaths", "0", CVAR_GAME | CVAR_BOOL, "draws path_* entities" ); +idCVar ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "0", CVAR_GAME | CVAR_INTEGER, "draws obstacle avoidance information for monsters. if 2, draws obstacles for player, as well", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar ai_blockedFailSafe( "ai_blockedFailSafe", "1", CVAR_GAME | CVAR_BOOL, "enable blocked fail safe handling" ); +idCVar ai_debugSquad( "ai_debugSquad", "0", CVAR_GAME | CVAR_BOOL, "draws squad info for allies" ); +idCVar ai_debugStealth( "ai_debugStealth", "0", CVAR_GAME | CVAR_INTEGER, "draws suspicion info for enemies" ); +idCVar ai_allowTacticalRush( "ai_allowTacticalRush", "1", CVAR_GAME | CVAR_BOOL, "allows tactical ai to rush an enemy when hurt" ); + + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +idCVar ai_speeds( "ai_speeds", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_freeze( "ai_freeze", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_animShow( "ai_animShow", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCover( "ai_showCover", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_showTacticalFeatures( "ai_showTacticalFeatures", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_disableEntTactical( "ai_disableEntTactical", "0", CVAR_GAME | CVAR_BOOL, "disables tactical points around entities" ); +idCVar ai_disableAttacks( "ai_disableAttacks", "0", CVAR_GAME | CVAR_BOOL, "disables attack decisions" ); +idCVar ai_disableSimpleThink( "ai_disableSimpleThink", "0", CVAR_GAME | CVAR_BOOL, "disables simple thinking in AI entities" ); +idCVar ai_disableCover( "ai_disableCover", "0", CVAR_GAME | CVAR_BOOL, "disables AI using cover points" ); +//cdr: use new master move functions +idCVar ai_useRVMasterMove( "ai_useRVMasterMove", "0", CVAR_GAME | CVAR_BOOL, "changes AI to use new master move function" ); +//jshepard: allow out of date AAS files to be used, for testing +idCVar ai_allowOldAAS( "ai_allowOldAAS", "0", CVAR_GAME | CVAR_BOOL, "allows AI to use most recent AAS file, even if it is not up-to-date. Enable only for testing."); +// twhitaker: debugging support for eye focus +idCVar ai_debugEyeFocus( "ai_debugEyeFocus", "0", CVAR_GAME | CVAR_BOOL, "draws eye focus info" ); +//mcg: always allow player to push buddies, unless scripted +idCVar ai_playerPushAlways( "ai_playerPushAlways", "1", CVAR_GAME | CVAR_BOOL, "always allow player to push buddies, unless scripted" ); +// RAVEN END + +idCVar g_dvTime( "g_dvTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvAmplitude( "g_dvAmplitude", "0.001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvFrequency( "g_dvFrequency", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_kickTime( "g_kickTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_kickAmplitude( "g_kickAmplitude", "0.0001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobTime( "g_blobTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobSize( "g_blobSize", "1", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_testHealthVision( "g_testHealthVision", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_editEntityMode( "g_editEntityMode", "0", CVAR_GAME | CVAR_INTEGER, "0 = off\n" + "1 = lights\n" + "2 = sounds\n" + "3 = articulated figures\n" + "4 = particle systems\n" + "5 = monsters\n" + "6 = entity names\n" +// RAVEN BEGIN +// bdube: extended + "7 = entity models\n" + "8 = effects", 0, 8, idCmdSystem::ArgCompletion_Integer<0,8> ); +// rhummer: Added archive flag. +idCVar g_editEntityDistance( "g_editEntityDistance", "512", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit" ); +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. Also added archive flag. +idCVar g_editEntityTextDistance( "g_editEntityTextDistance", "256", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit text information"); +idCVar g_testCTF( "g_testCTF", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "" ); +// rjohnson: entity usage stats +idCVar g_keepEntityStats( "g_keepEntityStats", "0", CVAR_GAME | CVAR_CHEAT |CVAR_BOOL, "keep track of entity usage stats" ); +// RAVEN END +idCVar g_dragEntity( "g_dragEntity", "0", CVAR_GAME | CVAR_BOOL, "allows dragging physics objects around by placing the crosshair over them and holding the fire button" ); +idCVar g_dragDamping( "g_dragDamping", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dragShowSelection( "g_dragShowSelection", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_dropItemRotation( "g_dropItemRotation", "", CVAR_GAME, "" ); + +idCVar g_vehicleVelocity( "g_vehicleVelocity", "1000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleForce( "g_vehicleForce", "50000", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar ik_enable( "ik_enable", "1", CVAR_GAME | CVAR_BOOL, "enable IK" ); +idCVar ik_debug( "ik_debug", "0", CVAR_GAME | CVAR_BOOL, "show IK debug lines" ); + +idCVar af_useLinearTime( "af_useLinearTime", "1", CVAR_GAME | CVAR_BOOL, "use linear time algorithm for tree-like structures" ); +idCVar af_useImpulseFriction( "af_useImpulseFriction", "0", CVAR_GAME | CVAR_BOOL, "use impulse based contact friction" ); +idCVar af_useJointImpulseFriction( "af_useJointImpulseFriction","0", CVAR_GAME | CVAR_BOOL, "use impulse based joint friction" ); +idCVar af_useSymmetry( "af_useSymmetry", "1", CVAR_GAME | CVAR_BOOL, "use constraint matrix symmetry" ); +idCVar af_skipSelfCollision( "af_skipSelfCollision", "0", CVAR_GAME | CVAR_BOOL, "skip self collision detection" ); +idCVar af_skipLimits( "af_skipLimits", "0", CVAR_GAME | CVAR_BOOL, "skip joint limits" ); +idCVar af_skipFriction( "af_skipFriction", "0", CVAR_GAME | CVAR_BOOL, "skip friction" ); +idCVar af_forceFriction( "af_forceFriction", "-1", CVAR_GAME | CVAR_FLOAT, "force the given friction value" ); +idCVar af_maxLinearVelocity( "af_maxLinearVelocity", "128", CVAR_GAME | CVAR_FLOAT, "maximum linear velocity" ); +idCVar af_maxAngularVelocity( "af_maxAngularVelocity", "1.57", CVAR_GAME | CVAR_FLOAT, "maximum angular velocity" ); +idCVar af_timeScale( "af_timeScale", "1", CVAR_GAME | CVAR_FLOAT, "scales the time" ); +idCVar af_jointFrictionScale( "af_jointFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the joint friction" ); +idCVar af_contactFrictionScale( "af_contactFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the contact friction" ); +idCVar af_highlightBody( "af_highlightBody", "", CVAR_GAME, "name of the body to highlight" ); +idCVar af_highlightConstraint( "af_highlightConstraint", "", CVAR_GAME, "name of the constraint to highlight" ); +idCVar af_showTimings( "af_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show articulated figure cpu usage" ); +idCVar af_showConstraints( "af_showConstraints", "0", CVAR_GAME | CVAR_BOOL, "show constraints" ); +idCVar af_showConstraintNames( "af_showConstraintNames", "0", CVAR_GAME | CVAR_BOOL, "show constraint names" ); +idCVar af_showConstrainedBodies( "af_showConstrainedBodies", "0", CVAR_GAME | CVAR_BOOL, "show the two bodies contrained by the highlighted constraint" ); +idCVar af_showPrimaryOnly( "af_showPrimaryOnly", "0", CVAR_GAME | CVAR_BOOL, "show primary constraints only" ); +idCVar af_showTrees( "af_showTrees", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures" ); +idCVar af_showLimits( "af_showLimits", "0", CVAR_GAME | CVAR_BOOL, "show joint limits" ); +idCVar af_showBodies( "af_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show bodies" ); +idCVar af_showBodyNames( "af_showBodyNames", "0", CVAR_GAME | CVAR_BOOL, "show body names" ); +idCVar af_showMass( "af_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each body" ); +idCVar af_showTotalMass( "af_showTotalMass", "0", CVAR_GAME | CVAR_BOOL, "show the total mass of each articulated figure" ); +idCVar af_showInertia( "af_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each body" ); +idCVar af_showVelocity( "af_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each body" ); +idCVar af_showActive( "af_showActive", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures of articulated figures not at rest" ); +idCVar af_testSolid( "af_testSolid", "1", CVAR_GAME | CVAR_BOOL, "test for bodies initially stuck in solid" ); + +idCVar rb_showTimings( "rb_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show rigid body cpu usage" ); +idCVar rb_showBodies( "rb_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies" ); +idCVar rb_showMass( "rb_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each rigid body" ); +idCVar rb_showInertia( "rb_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each rigid body" ); +idCVar rb_showVelocity( "rb_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each rigid body" ); +idCVar rb_showActive( "rb_showActive", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies that are not at rest" ); + +// RAVEN BEGIN +// bdube: more rigid body debug +idCVar rb_showContacts( "rb_showContacts", "0", CVAR_GAME | CVAR_BOOL, "show rigid body contacts" ); +// RAVEN END + +// The default values for player movement cvars are set in def/player.def +idCVar pm_jumpheight( "pm_jumpheight", "48", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "approximate hieght the player can jump" ); +idCVar pm_stepsize( "pm_stepsize", "16", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "maximum height the player can step up without jumping" ); +idCVar pm_crouchspeed( "pm_crouchspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while crouched" ); +// RAVEN BEGIN +idCVar pm_speed( "pm_speed", "160", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while running" ); +idCVar pm_walkspeed( "pm_walkspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while walking" ); +// RAVEN END +idCVar pm_noclipspeed( "pm_noclipspeed", "270", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while in noclip" ); +idCVar pm_spectatespeed( "pm_spectatespeed", "450", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while spectating" ); +idCVar pm_spectatebbox( "pm_spectatebbox", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "size of the spectator bounding box" ); +idCVar pm_usecylinder( "pm_usecylinder", "1", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | CVAR_NORESET | CVAR_ARCHIVE, "use a cylinder approximation instead of a bounding box for player collision detection (>= 3 - custom number of sides)" ); +idCVar pm_minviewpitch( "pm_minviewpitch", "-89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look up (negative values are up)" ); +idCVar pm_maxviewpitch( "pm_maxviewpitch", "89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look down" ); +idCVar pm_stamina( "pm_stamina", "24", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "length of time player can run" ); +idCVar pm_staminathreshold( "pm_staminathreshold", "45", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "when stamina drops below this value, player gradually slows to a walk" ); +idCVar pm_staminarate( "pm_staminarate", "0.75", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "rate that player regains stamina. divide pm_stamina by this value to determine how long it takes to fully recharge." ); + +// ddynerman: adjusted bboxes to actual height +idCVar pm_normalheight( "pm_normalheight", "77", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while standing" ); +idCVar pm_crouchheight( "pm_crouchheight", "49", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while crouched" ); + +idCVar pm_crouchviewheight( "pm_crouchviewheight", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while crouched" ); +idCVar pm_normalviewheight( "pm_normalviewheight", "68", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while standing" ); + +idCVar pm_deadheight( "pm_deadheight", "20", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while dead" ); +idCVar pm_deadviewheight( "pm_deadviewheight", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while dead" ); +idCVar pm_crouchrate( "pm_crouchrate", "0.87", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "time it takes for player's view to change from standing to crouching" ); +idCVar pm_bboxwidth( "pm_bboxwidth", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "x/y size of player's bounding box" ); +idCVar pm_crouchbob( "pm_crouchbob", "0.5", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob much faster when crouched" ); +idCVar pm_walkbob( "pm_walkbob", "0.3", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob slowly when walking" ); +idCVar pm_runbob( "pm_runbob", "0.4", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob faster when running" ); +idCVar pm_runpitch( "pm_runpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_runroll( "pm_runroll", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobup( "pm_bobup", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobpitch( "pm_bobpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobroll( "pm_bobroll", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +idCVar pm_thirdPersonRange( "pm_thirdPersonRange", "80", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "camera distance from player in 3rd person" ); +idCVar pm_thirdPersonHeight( "pm_thirdPersonHeight", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "height of camera from normal view height in 3rd person" ); +idCVar pm_thirdPersonAngle( "pm_thirdPersonAngle", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "direction of camera from player in 3rd person in degrees (0 = behind player, 180 = in front)" ); +idCVar pm_thirdPersonClip( "pm_thirdPersonClip", "1", CVAR_GAME | CVAR_BOOL, "clip third person view into world space" ); +idCVar pm_thirdPerson( "pm_thirdPerson", "0", CVAR_GAME | CVAR_BOOL, "enables third person view" ); +idCVar pm_thirdPersonDeath( "pm_thirdPersonDeath", "0", CVAR_GAME | CVAR_BOOL, "enables third person view when player dies" ); +idCVar pm_modelView( "pm_modelView", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "draws camera from POV of player model (1 = always, 2 = when dead)", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar pm_airTics( "pm_air", "1800", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "how long in milliseconds the player can go without air before he starts taking damage" ); + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon - or a non-final pc build so Caryn can edit the guis +#if defined( _XBOX ) || !defined( _FINAL ) +idCVar pm_AimAssist( "pm_AimAssist", "2", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE , "Enable Xbox aim assistance. 1 to use change player view method. 2 to use change muzzle aim method.\n"); +idCVar pm_AimAssistDistance( "pm_AimAssistDistance", "1000", CVAR_GAME | CVAR_INTEGER, "The max aim assist distance.\n"); +idCVar pm_AimAssistThreshold( "pm_AimAssistThreshold", "1.0", CVAR_GAME | CVAR_FLOAT, "Threshold by which the projectile is aimed at the offending target.\n"); +idCVar pm_AimAssistFOV( "pm_AimAssistFOV", "10", CVAR_GAME | CVAR_INTEGER, "The field of view for aim assistance.\n"); +idCVar pm_AimAssistBump( "pm_AimAssistBump", "10", CVAR_GAME | CVAR_INTEGER, "The percentage of correction applied either to the view or the muzzle aim.\n"); +idCVar pm_showAimAssist( "pm_showAimAssist", "0", CVAR_GAME | CVAR_BOOL, "Draw aim assist frustum and bounding boxes.\n"); +idCVar pm_AimAssistSlow( "pm_AimAssistSlow", "50", CVAR_GAME | CVAR_INTEGER, "The percentage to slow the turning motion by when targeting an enemy.\n"); + +//asalmon: xenon controller config cvars +idCVar pm_ThumbstickConfig( "pm_ThumbstickConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the thumbstick config on Xenon. 0 right handed, 1 left handed.\n"); +idCVar pm_ButtonConfig( "pm_ButtonConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the button configuration for Xenon.\n"); +idCVar pm_Inversion( "pm_Inversion", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "invert look up and down\n"); + +idCVar pm_VLookSens( "pm_VLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HLookSens( "pm_HLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_VMoveSens( "pm_VMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HMoveSens( "pm_HMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); + +idCVar pm_voiceEnabled( "pm_voiceEnabled", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_GUI, "Enable/disable voice.\n"); + +//asalmon: Xenon leaderboard cvars +idCVar ui_LeaderboardView( "ui_LeaderboardView", "17", CVAR_INTEGER | CVAR_NOCHEAT, "Which leaderboard to show.\n"); +idCVar ui_LeaderboardSort( "ui_LeaderboardSort", "1", CVAR_INTEGER | CVAR_NOCHEAT, "How to sort the leaderboard. 0 for rating, 1 for ranking, 2 for friends, 3 find logged in player.\n"); + +//nrausch +idCVar pm_RocketJumpAutocenter( "pm_RocketJumpAutocenter", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Automatic autocentering following a rocket jump\n"); +idCVar pm_IAmACheater( "pm_IAmACheater", "0", CVAR_GAME | CVAR_BOOL, "Whomever is playing is a dirty, rotten cheater\n"); + +idCVar g_systemLinkMatch( "g_systemLinkMatch", "0", CVAR_INTEGER, "In a system link game\n"); + +//asalmon: cvars for Live teams. Teams will now be a post launch feature but this was left here in case it is of use on future projects +//idCVar ui_LiveClanName( "ui_LiveClanName", "My Clan", CVAR_GAME | CVAR_USERINFO, "The name of the live clan being created\n"); +//idCVar ui_LiveClanDesc( "ui_LiveClanDesc", "A Quake 4 clan", CVAR_GAME | CVAR_USERINFO, "The description of the live clan being created\n"); +//idCVar ui_LiveClanMotto( "ui_LiveClanMotto", "We love Quake 4", CVAR_GAME | CVAR_USERINFO, "The motto of the live clan being created\n"); +//idCVar ui_LiveClanUrl( "ui_LiveClanUrl", "www.ravensoft.com", CVAR_GAME | CVAR_USERINFO, "The url of the live clan being created\n"); +// +//idCVar ui_LiveRecruitName( "ui_LiveRecruitName", "Recruit Name Here", CVAR_GAME | CVAR_USERINFO, "name of the gamer you are trying recruit\n"); +//idCVar ui_LiveRecruitPDelete( "ui_LiveRecruitPDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit delete permissions\n"); +//idCVar ui_LiveRecruitPData( "ui_LiveRecruitPData", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit modify data permissions\n"); +//idCVar ui_LiveRecruitPMemberPermissions("ui_LiveRecruitPMemberPermissions", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member modify permissions\n"); +//idCVar ui_LiveRecruitPMemberDelete( "ui_LiveRecruitPMemberDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member delete permissions\n"); +//idCVar ui_LiveRecruitPMemberRecruit( "ui_LiveRecruitPMemberRecruit", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member recruit permissions\n"); +#endif + +idCVar pm_zoomedSlow( "pm_zoomedSlow", "100", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "Slow look speed while zoomed 0..100% of speed"); + +#ifndef _XENON +idCVar pm_isZoomed( "pm_isZoomed", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "if nonzero, is the slow speed"); +#endif + +// nmckenzie: added ability to try alternate accelerations. +idCVar pm_acceloverride( "pm_acceloverride", "0", CVAR_GAME | CVAR_FLOAT, "Adjust the player acceleration." ); +idCVar pm_frictionoverride( "pm_frictionoverride", "-1", CVAR_GAME | CVAR_FLOAT, "Adjust the player friciton." ); +idCVar pm_forcespectatormove( "pm_forcespectatormove", "0", CVAR_GAME | CVAR_FLOAT, "Force the player to move like a spectator (fly)." ); +// bdube: added vehicle cvars +idCVar pm_vehicleCameraSnap( "pm_vehicleCameraSnap", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraMinDist( "pm_vehicleCameraMinDist", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraSpeedScale( "pm_vehicleCameraSpeedScale", "0.5", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraScaleMax( "pm_vehicleCameraScaleMax", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleSoundLerpScale( "pm_vehicleSoundLerpScale", "10", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +// RAVEN END + +idCVar g_showPlayerShadow( "g_showPlayerShadow", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables shadow of player model" ); + +idCVar g_skipPlayerShadowsMP( "g_skipPlayerShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all player shadows in multiplayer" ); +idCVar g_skipItemShadowsMP( "g_skipItemShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all item shadows in multiplayer" ); +idCVar g_simpleItems( "g_simpleItems", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "render icon representations of items instead of the actual model" ); +idCVar g_showHud( "g_showHud", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar g_showProjectilePct( "g_showProjectilePct", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables display of player hit percentage" ); +// RAVEN BEGIN +// dluetscher: changed to g_brassTime +idCVar g_brassTime( "g_brassTime", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "amount of time brass should stay in the world before dissapearing, set to 0 to disable brass" ); +// RAVEN END +idCVar g_gun_x( "g_gunX", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_weaponFovEffect( "g_weaponFovEffect", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "Adjusts the position of the weapon model with increased fov" ); + +idCVar g_viewNodalX( "g_viewNodalX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalZ( "g_viewNodalZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN BEGIN +// jshepard: fov as a float for smoother transitions? +idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "" ); +// RAVEN END +idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); +idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); + +// RAVEN BEGIN +// bdube: crosshairs +// mekberg: custom size +idCVar g_crosshairSize( "g_crosshairSize", "32", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "crosshair size: 16,24,32,40,48", 16, 48 ); +//idCVar g_crosshairColor( "g_crosshairColor", "0.458 0.894 0.247 .75", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +idCVar g_crosshairColor( "g_crosshairColor", "1 1 1 1", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +// cnicholson: Custom crosshair +idCVar g_crosshairCustom( "g_crosshairCustom", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "sets the custom combat crosshair" ); +idCVar g_crosshairCustomFile( "g_crosshairCustomFile", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "stores the custom crosshair's filename" ); +idCVar g_crosshairCharInfoFar( "g_crosshairCharInfoFar", "1", CVAR_GAME | CVAR_BOOL, "instead of a green crosshair from far away, full character info always draws" ); +// bdube: database entries +idCVar g_showHudPopups( "g_showHudPopups", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "displays objective and database popups on the hud" ); +idCVar g_showRange( "g_showRange", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "shows the range from the player to the first collision under the players crosshair" ); +// bdube: debug hud +idCVar g_showDebugHud( "g_showDebugHud", "0", CVAR_GAME | CVAR_INTEGER, "displays the debug hud\n" + "0 = off\n" + "1 = player\n" + "2 = physics\n" + "3 = AI\n" + "4 = vehicle\n" + "5 = performance\n" + "6 = effects\n" + "7 = map information\n" + "8 = AI performance\n" + "9 = MP\n" + "10 = Sound\n" + "32 = scratch\n" ); +// bdube: cvar for messing with foreshortening and gun position +idCVar g_gun_pitch( "g_gunPitch", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_yaw( "g_gunYaw", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_roll( "g_gunRoll", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// abahr: +idCVar g_gunViewStyle( "g_gunViewStyle", "0", CVAR_GAME | CVAR_NOCHEAT | PC_CVAR_ARCHIVE | CVAR_INTEGER, "style presets\n" + "0 = Q3 style\n" + "1 = Shouldered style\n"); +// jscott: cvar for debugging playbacks +idCVar g_showPlayback( "g_showPlayback", "0", CVAR_GAME | CVAR_INTEGER, "show g_currentPlayback" ); +idCVar g_currentPlayback( "g_currentPlayback", "", CVAR_GAME, "name of playback shown by g_showPlayback" ); +// jscott: unused +//idCVar g_testParticle( "g_testParticle", "0", CVAR_GAME | CVAR_INTEGER, "test particle visualation, set by the particle editor" ); +//idCVar g_testParticleName( "g_testParticleName", "", CVAR_GAME, "name of the particle being tested by the particle editor" ); +// RAVEN END +idCVar g_testModelRotate( "g_testModelRotate", "0", CVAR_GAME, "test model rotation speed" ); +idCVar g_testPostProcess( "g_testPostProcess", "", CVAR_GAME, "name of material to draw over screen" ); +idCVar g_testModelAnimate( "g_testModelAnimate", "0", CVAR_GAME | CVAR_INTEGER, "test model animation,\n" + "0 = cycle anim with origin reset\n" + "1 = cycle anim with fixed origin\n" + "2 = cycle anim with continuous origin\n" + "3 = frame by frame with continuous origin\n" + "4 = play anim once\n" + "5 = frame by frame with fixed origin", 0, 5, idCmdSystem::ArgCompletion_Integer<0,5> ); +idCVar g_testModelBlend( "g_testModelBlend", "0", CVAR_GAME | CVAR_INTEGER, "number of frames to blend" ); +idCVar g_testDeath( "g_testDeath", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// bdube: added scoreboard testing +idCVar g_testScoreboard( "g_testScoreboard", "0", CVAR_GAME | CVAR_INTEGER, "number of clients to test in the scoreboard gui" ); +idCVar g_testPlayer( "g_testPlayer", "", CVAR_GAME, "test player classname" ); +// RAVEN END +idCVar g_exportMask( "g_exportMask", "", CVAR_GAME, "" ); +idCVar g_flushSave( "g_flushSave", "0", CVAR_GAME | CVAR_BOOL, "1 = don't buffer file writing for save games." ); + +idCVar aas_test( "aas_test", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreas( "aas_showAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreaBounds( "aas_showAreaBounds", "0", CVAR_GAME | CVAR_INTEGER, "When show areas is on, this draws the bounds of the areas, too..." ); +idCVar aas_showPath( "aas_showPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showFlyPath( "aas_showFlyPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showWallEdges( "aas_showWallEdges", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showHideArea( "aas_showHideArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_pullPlayer( "aas_pullPlayer", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_randomPullPlayer( "aas_randomPullPlayer", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_goalArea( "aas_goalArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showPushIntoArea( "aas_showPushIntoArea", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// rjohnson: added aas help +idCVar aas_showProblemAreas( "aas_showProblemAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// cdr: added rev reach +idCVar aas_showRevReach( "aas_showRevReach", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +idCVar g_password( "g_password", "", CVAR_GAME | PC_CVAR_ARCHIVE, "game password" ); +idCVar password( "password", "", CVAR_GAME | CVAR_NOCHEAT, "client password used when connecting" ); + +// RAVEN BEGIN +idCVar g_gameReviewPause( "g_gameReviewPause", "30", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | PC_CVAR_ARCHIVE, "scores review time in seconds (at end game)", 2, 3600 ); +// RAVEN END +idCVar net_clientPredictGUI( "net_clientPredictGUI", "1", CVAR_GAME | CVAR_BOOL, "test guis in networking without prediction" ); + +idCVar si_voteFlags( "si_voteFlags", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER | PC_CVAR_ARCHIVE, "vote flags. bit mask of votes not allowed on this server\n" + "bit 0 (+1) restart now\n" + "bit 1 (+2) min players\n" + "bit 2 (+4) auto balance teams\n" + "bit 3 (+8) shuffle teams\n" + "bit 4 (+16) kick player\n" + "bit 5 (+32) change map\n" + "bit 6 (+64) change gametype\n" + "bit 7 (+128) time limit\n" + "bit 8 (+256) tourney limit\n" + "bit 9 (+512) capture limit\n" + "bit 10 (+1024) frag limit" ); + +idCVar g_mapCycle( "g_mapCycle", "mapcycle", CVAR_GAME | CVAR_ARCHIVE, "map cycling script for multiplayer games - see mapcycle.scriptcfg" ); + +// RAVEN BEGIN +// bdube: client entitiy cvars +idCVar g_gamelog( "g_gamelog", "0", CVAR_GAME | CVAR_BOOL, "enables game logging" ); +idCVar cl_showEntityInfo( "cl_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +// ddynerman: announcer delay time +idCVar g_announcerDelay( "g_announcerDelay", "1000", CVAR_SOUND | PC_CVAR_ARCHIVE, "no more than one announcer sound will be played in this many ms" ); +// jnewquist: Option to force undying state +idCVar g_forceUndying( "g_forceUndying", "0", CVAR_GAME | CVAR_BOOL, "forces undying state" ); +// mcg: combat performance testing cvars +idCVar g_perfTest_weaponNoFX( "g_perfTest_weaponNoFX", "0", CVAR_GAME | CVAR_BOOL, "no muzzle flash, brass eject, muzzle fx, tracers, impact fx, blood decals or blood splats (whew!)" ); +idCVar g_perfTest_hitscanShort( "g_perfTest_hitscanShort", "0", CVAR_GAME | CVAR_BOOL, "all hitscans capped at 2048" ); +idCVar g_perfTest_hitscanBBox( "g_perfTest_hitscanBBox", "0", CVAR_GAME | CVAR_BOOL, "all hitscans vs bbox, not rendermodel" ); +idCVar g_perfTest_aiStationary( "g_perfTest_aiStationary", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no combat movement" ); +idCVar g_perfTest_aiNoDodge( "g_perfTest_aiNoDodge", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no dodging" ); +idCVar g_perfTest_aiNoRagdoll( "g_perfTest_aiNoRagdoll", "0", CVAR_GAME | CVAR_BOOL, "ai does not ragdoll" ); +idCVar g_perfTest_aiNoObstacleAvoid( "g_perfTest_aiNoObstacleAvoid", "0", CVAR_GAME | CVAR_BOOL, "ai does not attempt obstacle avoidance" ); +idCVar g_perfTest_aiUndying( "g_perfTest_aiUndying", "0", CVAR_GAME | CVAR_BOOL, "makes all AI undying" ); +idCVar g_perfTest_aiNoVisTrace( "g_perfTest_aiNoVisTrace", "0", CVAR_GAME | CVAR_BOOL, "ai does no vis traces" ); +idCVar g_perfTest_noJointTransform( "g_perfTest_noJointTransform", "0", CVAR_GAME | CVAR_BOOL, "all joint transforms return origin" ); +idCVar g_perfTest_noPlayerFocus( "g_perfTest_noPlayerFocus", "0", CVAR_GAME | CVAR_BOOL, "doesn't do player focus traces/logic" ); +idCVar g_perfTest_noProjectiles( "g_perfTest_noProjectiles", "0", CVAR_GAME | CVAR_BOOL, "all projectiles are removed instantly" ); + +idCVar net_serverDownload( "net_serverDownload", "0", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "enable server download redirects. 0: off 1: client exits and opens si_serverURL in web browser 2: client downloads pak files from an URL and connects again 3: client downloads pak files from built-in http server and connects again. See net_serverDl* cvars for configuration" ); +idCVar net_serverDlBaseURL( "net_serverDlBaseURL", "", CVAR_GAME | CVAR_ARCHIVE, "base URL for the download redirection. also overrides the URL when net_serverDownload is set to 3 (built-in HTTP server)." ); +idCVar net_serverDlTable( "net_serverDlTable", "", CVAR_GAME | CVAR_ARCHIVE, "pak names for which download is provided, seperated by ; - use a * to mark all paks" ); + +idCVar si_serverURL( "si_serverURL", "", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "server information page" ); + +idCVar g_repeaterReliableOnly( "g_repeaterReliableOnly", "1", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "send unreliable messages reliably. 1: to repeaters 2: to repeaters and viewers" ); + +idCVar net_warnStale( "net_warnStale", "1", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "Warn stale entity occurences on network client - == 1: only on ClientStale call, > 1 all times" ); + +idCVar pm_slidevelocity( "pm_slidevelocity", "1", CVAR_GAME | CVAR_BOOL | CVAR_NETWORKSYNC, "what to do with velocity when hitting a surface at an angle. 0: use horizontal speed, 1: keep some of the impact speed to push along the slide" ); +idCVar pm_powerslide( "pm_powerslide", "0.09", CVAR_GAME | CVAR_FLOAT | CVAR_NETWORKSYNC, "adjust the push when pm_slidevelocity == 1, set power < 1 -> more speed, > 1 -> closer to pm_slidevelocity 0", 0, 4 ); + +idCVar g_playerLean( "g_playerLean", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale down or disable client-side player lean" ); + +idCVar net_clientPredictWeaponSwitch( "net_clientPredictWeaponSwitch", "1", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "predict weapon switches locally (most noticeable on high ping servers)" ); + + +// RAVEN BEGIN +// bdube: cvar helps +static idCVarHelp help_g_showHud ( "g_showHud", "Show Player HUD", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showGun ( "g_showGun", "Show Player Weapon", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTargets ( "g_showTargets", "Show Targets", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTriggers ( "g_showTriggers", "Show Triggers", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showEntityInfo ( "g_showEntityInfo", "Show Entity Information", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_timeentities ( "g_timeentities", "Show Entity Times", "Off;0.5s;1.0s", "0;0.5;1.0", CVARHELP_GAME ); +static idCVarHelp help_g_showActiveEntities ( "g_showActiveEntities", "Show Active Entities", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_frametime ( "g_frametime", "Show Game Frame Times", "Off;On", "0;1", CVARHELP_GAME ); + +static idCVarHelp help_g_showCollisionWorld ( "g_showCollisionWorld", "Show Collision World", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionModels ( "g_showCollisionModels", "Show Collision Models", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionTraces ( "g_showCollisionTraces", "Show Collision Traces", "Off;Info;Lines", "0;1;2", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showActive ( "rb_showActive", "Show Active Rigid Bodies", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showTimings ( "rb_showTimings", "Show Rigid Body Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_af_showTimings ( "af_showTimings", "Show AF Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); + +// nmckenzie: ai cvar helps +static idCVarHelp help_g_aas_showAreas( "aas_showAreas", "Show AAS areas", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showProblemAreas( "aas_showProblemAreas", "Show AAS areas with Problems", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showPath( "aas_showPath", "Show AAS Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showFlyPath( "aas_showFlyPath", "Show AAS Flying Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showWallEdges( "aas_showWallEdges", "Show AAS Wall Edges", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showHideArea( "aas_showHideArea", "Show AAS Hide Areas", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_goalArea( "aas_goalArea", "Show AAS Goal Areas", "Off;On", "0;1", CVARHELP_AI ); + +static idCVarHelp help_g_ai_debugMove( "ai_debugMove", "Show Movement for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_debugTrajectory( "ai_debugTrajectory", "Show Grenade tests for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showCombatNodes( "ai_showCombatNodes", "Show attack cones for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showPaths( "ai_showPaths", "Show all path_* entities", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "Show obstacle avoidance", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_speeds( "ai_speeds", "Show performance load of AI", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_animShow( "ai_animShow", "List animations when used.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showTacticalFeatures( "ai_showTacticalFeatures", "Show player view tactical features.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_useRVMasterMove( "ai_useRVMasterMove", "Use new master move functions.", "Off;On", "0;1", CVARHELP_AI ); +// RAVEN END diff --git a/source/mpgame/gamesys/SysCvar.h b/source/mpgame/gamesys/SysCvar.h new file mode 100644 index 0000000..84fe84c --- /dev/null +++ b/source/mpgame/gamesys/SysCvar.h @@ -0,0 +1,429 @@ + +#ifndef __SYS_CVAR_H__ +#define __SYS_CVAR_H__ + +extern idCVar developer; + +extern idCVar g_cinematic; +extern idCVar g_cinematicMaxSkipTime; + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +extern idCVar g_fixedHorizFOV; +// RAVEN END + +extern idCVar g_monsters; +extern idCVar g_decals; +extern idCVar g_knockback; +extern idCVar g_skill; +extern idCVar g_gravity; +extern idCVar g_mp_gravity; +extern idCVar g_skipFX; +extern idCVar g_skipParticles; +extern idCVar g_projectileLights; +extern idCVar g_doubleVision; +extern idCVar g_muzzleFlash; + +extern idCVar g_nailTrail; +extern idCVar g_grenadeTrail; +extern idCVar g_rocketTrail; +extern idCVar g_railTrail; +extern idCVar g_napalmTrail; + +extern idCVar g_predictedProjectiles; + +extern idCVar g_disasm; +extern idCVar g_debugBounds; +extern idCVar g_debugAnim; +extern idCVar g_debugMove; +extern idCVar g_debugDamage; +extern idCVar g_debugWeapon; +extern idCVar g_debugScript; +extern idCVar g_debugMover; +extern idCVar g_debugTriggers; +extern idCVar g_debugCinematic; +// RAVEN BEGIN +// bdube: added +extern idCVar g_debugState; +extern idCVar g_stopTime; +extern idCVar g_armorProtection; +extern idCVar g_armorProtectionMP; +//extern idCVar g_damageScale; +// jsinger: added to support binary read/write +extern idCVar com_BinaryRead; +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFile; +#endif +extern idCVar com_BinaryWrite; +// RAVEN END +extern idCVar g_useDynamicProtection; +extern idCVar g_healthTakeTime; +extern idCVar g_healthTakeAmt; +extern idCVar g_healthTakeLimit; + +extern idCVar g_showPVS; +extern idCVar g_showTargets; +extern idCVar g_showTriggers; +extern idCVar g_showCollisionWorld; +extern idCVar g_showCollisionModels; +extern idCVar g_showCollisionTraces; +// RAVEN BEGIN +// ddynerman: SD's clip sector code +extern idCVar g_showClipSectors; +extern idCVar g_showClipSectorFilter; +extern idCVar g_showAreaClipSectors; +// RAVEN END +extern idCVar g_maxShowDistance; +extern idCVar g_showEntityInfo; +extern idCVar g_showviewpos; +extern idCVar g_showcamerainfo; +extern idCVar g_showTestModelFrame; +extern idCVar g_showActiveEntities; +extern idCVar g_showEnemies; +extern idCVar g_frametime; +extern idCVar g_timeentities; + +// RAVEN BEGIN +// bdube: new debug cvar +extern idCVar g_debugVehicle; +extern idCVar g_showFrameCmds; +extern idCVar g_showGodDamage; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: debug cvars for rvVehicleDriver +extern idCVar g_debugVehicleDriver; +extern idCVar g_debugVehicleAI; +extern idCVar g_vehicleMode; +// RAVEN END +extern idCVar g_allowVehicleGunOverheat; + +extern idCVar ai_debugScript; +extern idCVar ai_debugMove; +extern idCVar ai_debugTrajectory; +extern idCVar ai_debugTactical; +extern idCVar ai_debugFilterString; +extern idCVar ai_testPredictPath; +extern idCVar ai_showCombatNodes; +extern idCVar ai_showPaths; +extern idCVar ai_showObstacleAvoidance; +extern idCVar ai_blockedFailSafe; +extern idCVar ai_debugSquad; +extern idCVar ai_debugStealth; +extern idCVar ai_allowTacticalRush; + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +extern idCVar ai_speeds; +extern idCVar ai_freeze; +extern idCVar ai_animShow; +extern idCVar ai_showCover; +extern idCVar ai_showTacticalFeatures; +extern idCVar ai_disableEntTactical; +extern idCVar ai_disableAttacks; +extern idCVar ai_disableSimpleThink; +extern idCVar ai_disableCover; +extern idCVar ai_debugHelpers; +// cdr: added new master move type +extern idCVar ai_useRVMasterMove; +//jshepard: allow old AAS files +extern idCVar ai_allowOldAAS; +// twhitaker: debugging support for eye focus +extern idCVar ai_debugEyeFocus; +//mcg: always allow player to push buddies, unless scripted +extern idCVar ai_playerPushAlways; +// RAVEN END + +extern idCVar g_dvTime; +extern idCVar g_dvAmplitude; +extern idCVar g_dvFrequency; + +extern idCVar g_kickTime; +extern idCVar g_kickAmplitude; +extern idCVar g_blobTime; +extern idCVar g_blobSize; + +extern idCVar g_testHealthVision; +extern idCVar g_editEntityMode; +// RAVEN BEGIN +extern idCVar g_editEntityDistance; +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. +extern idCVar g_editEntityTextDistance; +// rjohnson: entity usage stats +extern idCVar g_keepEntityStats; +// RAVEN END +extern idCVar g_dragEntity; +extern idCVar g_dragDamping; +extern idCVar g_dragShowSelection; +extern idCVar g_dropItemRotation; + +extern idCVar g_vehicleVelocity; +extern idCVar g_vehicleForce; + +extern idCVar hud_showSpeed; +extern idCVar hud_showInput; +extern idCVar hud_inputPosition; +extern idCVar hud_inputColor; + +extern idCVar ik_enable; +extern idCVar ik_debug; + +extern idCVar af_useLinearTime; +extern idCVar af_useImpulseFriction; +extern idCVar af_useJointImpulseFriction; +extern idCVar af_useSymmetry; +extern idCVar af_skipSelfCollision; +extern idCVar af_skipLimits; +extern idCVar af_skipFriction; +extern idCVar af_forceFriction; +extern idCVar af_maxLinearVelocity; +extern idCVar af_maxAngularVelocity; +extern idCVar af_timeScale; +extern idCVar af_jointFrictionScale; +extern idCVar af_contactFrictionScale; +extern idCVar af_highlightBody; +extern idCVar af_highlightConstraint; +extern idCVar af_showTimings; +extern idCVar af_showConstraints; +extern idCVar af_showConstraintNames; +extern idCVar af_showConstrainedBodies; +extern idCVar af_showPrimaryOnly; +extern idCVar af_showTrees; +extern idCVar af_showLimits; +extern idCVar af_showBodies; +extern idCVar af_showBodyNames; +extern idCVar af_showMass; +extern idCVar af_showTotalMass; +extern idCVar af_showInertia; +extern idCVar af_showVelocity; +extern idCVar af_showActive; +extern idCVar af_testSolid; + +extern idCVar rb_showTimings; +extern idCVar rb_showBodies; +extern idCVar rb_showMass; +extern idCVar rb_showInertia; +extern idCVar rb_showVelocity; +extern idCVar rb_showActive; + +extern idCVar pm_jumpheight; +extern idCVar pm_stepsize; +extern idCVar pm_crouchspeed; +// RAVEN BEGIN +extern idCVar pm_speed; +extern idCVar pm_walkspeed; +extern idCVar pm_zoomedSlow; +extern idCVar pm_isZoomed; +// RAVEN END +extern idCVar pm_noclipspeed; +extern idCVar pm_spectatespeed; +extern idCVar pm_spectatebbox; +extern idCVar pm_usecylinder; +extern idCVar pm_minviewpitch; +extern idCVar pm_maxviewpitch; +extern idCVar pm_stamina; +extern idCVar pm_staminathreshold; +extern idCVar pm_staminarate; +extern idCVar pm_crouchheight; +extern idCVar pm_crouchviewheight; +extern idCVar pm_normalheight; +extern idCVar pm_normalviewheight; +extern idCVar pm_deadheight; +extern idCVar pm_deadviewheight; +extern idCVar pm_crouchrate; +extern idCVar pm_bboxwidth; +extern idCVar pm_crouchbob; +extern idCVar pm_walkbob; +extern idCVar pm_runbob; +extern idCVar pm_runpitch; +extern idCVar pm_runroll; +extern idCVar pm_bobup; +extern idCVar pm_bobpitch; +extern idCVar pm_bobroll; +extern idCVar pm_thirdPersonRange; +extern idCVar pm_thirdPersonHeight; +extern idCVar pm_thirdPersonAngle; +extern idCVar pm_thirdPersonClip; +extern idCVar pm_thirdPerson; +extern idCVar pm_thirdPersonDeath; +extern idCVar pm_modelView; +extern idCVar pm_airTics; + +// nmckenzie: added ability to try alternate accelerations. +extern idCVar pm_acceloverride; +extern idCVar pm_frictionoverride; +extern idCVar pm_forcespectatormove; +extern idCVar pm_thirdPersonTarget; +// bdube: vehicle +extern idCVar pm_vehicleLean; +extern idCVar pm_vehicleCameraSnap; +extern idCVar pm_vehicleCameraScaleMax; +extern idCVar pm_vehicleSoundLerpScale; +extern idCVar pm_vehicleCameraSpeedScale; +extern idCVar pm_vehicleCameraMinDist; +// RAVEN END + +extern idCVar g_showPlayerShadow; + +extern idCVar g_skipPlayerShadowsMP; +extern idCVar g_skipItemShadowsMP; + +extern idCVar g_simpleItems; +extern idCVar g_showHud; +// RAVEN BEGIN +extern idCVar g_crosshairColor; +// cnicholson: Custom Crosshair +extern idCVar g_crosshairCustom; +extern idCVar g_crosshairCustomFile; +extern idCVar g_crosshairCharInfoFar; +// bdube: hud popups +extern idCVar g_showHudPopups; +// bdube: range +extern idCVar g_showRange; +// bdube: debug hud +extern idCVar g_showDebugHud; +// RAVEN END +extern idCVar g_showProjectilePct; +// RAVEN BEGIN +// bdube: brass time +extern idCVar g_brassTime; +// RAVEN END +extern idCVar g_gun_x; +extern idCVar g_gun_y; +extern idCVar g_gun_z; +extern idCVar g_weaponFovEffect; +// RAVEN BEGIN +// bdube: cvar for messing with foreshortening +extern idCVar g_gun_pitch; +extern idCVar g_gun_yaw; +extern idCVar g_gun_roll; +// abahr: +extern idCVar g_gunViewStyle; +// jscott: for playbacks +extern idCVar g_showPlayback; +extern idCVar g_currentPlayback; +// RAVEN END +extern idCVar g_viewNodalX; +extern idCVar g_viewNodalZ; +extern idCVar g_fov; +extern idCVar g_testDeath; +extern idCVar g_skipViewEffects; +extern idCVar g_mpWeaponAngleScale; + +extern idCVar g_testParticle; +extern idCVar g_testParticleName; +// RAVEN BEGIN +// bdube: more rigid body debug +extern idCVar rb_showContacts; +// RAVEN END + +extern idCVar g_testPostProcess; + +extern idCVar g_testModelRotate; +extern idCVar g_testModelAnimate; +extern idCVar g_testModelBlend; + +extern idCVar g_forceModel; +extern idCVar g_forceStroggModel; +extern idCVar g_forceMarineModel; + +// RAVEN BEGIN +// bdube: test scoreboard +extern idCVar g_testScoreboard; +extern idCVar g_testPlayer; +// RAVEN END +extern idCVar g_exportMask; +extern idCVar g_flushSave; + +extern idCVar aas_test; +extern idCVar aas_showAreas; +extern idCVar aas_showAreaBounds; +extern idCVar aas_showPath; +extern idCVar aas_showFlyPath; +extern idCVar aas_showWallEdges; +extern idCVar aas_showHideArea; +extern idCVar aas_pullPlayer; +extern idCVar aas_randomPullPlayer; +extern idCVar aas_goalArea; +extern idCVar aas_showPushIntoArea; +// RAVEN BEGIN +// rjohnson: added aas help +extern idCVar aas_showProblemAreas; +// cdr: added rev reach +extern idCVar aas_showRevReach; +// RAVEN END + +extern idCVar net_clientPredictGUI; + +extern idCVar si_voteFlags; +extern idCVar g_mapCycle; +// RAVEN BEGIN +// shouchard: g_balanceTDM->g_balanceTeams so we can also use it for CTF +extern idCVar si_autobalance; +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +extern idCVar si_isBuyingEnabled; +extern idCVar si_dropWeaponsInBuyingModes; +extern idCVar si_controlTime; +// RITUAL END + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_gameType; +extern idCVar si_map; +extern idCVar si_mapCycle; +extern idCVar si_spectators; +extern idCVar si_minPlayers; +// RAVEN BEGIN +// shouchard: CTF +extern idCVar si_captureLimit; +// shouchard: Tourney +extern idCVar si_tourneyLimit; +// RAVEN END + +extern const char *si_gameTypeArgs[]; + +extern idCVar g_gamelog; +extern idCVar cl_showEntityInfo; +extern idCVar g_forceUndying; +extern idCVar g_perfTest_weaponNoFX; +extern idCVar g_perfTest_hitscanShort; +extern idCVar g_perfTest_hitscanBBox; +extern idCVar g_perfTest_aiStationary; +extern idCVar g_perfTest_aiNoDodge; +extern idCVar g_perfTest_aiNoRagdoll; +extern idCVar g_perfTest_aiNoObstacleAvoid; +extern idCVar g_perfTest_aiUndying; +extern idCVar g_perfTest_aiNoVisTrace; +extern idCVar g_perfTest_noJointTransform; +extern idCVar g_perfTest_noPlayerFocus; +extern idCVar g_perfTest_noProjectiles; + +extern idCVar net_clientLagOMeter; +extern idCVar net_clientLagOMeterResolution; + +extern idCVar net_warnStale; + +extern idCVar ri_useViewerPass; +extern idCVar ri_privateViewers; +extern idCVar ri_numViewers; +extern idCVar ri_numPrivateViewers; +extern idCVar ri_name; + +extern idCVar g_noTVChat; + +extern idCVar pm_slidevelocity; +extern idCVar pm_powerslide; + +extern idCVar g_playerLean; + +extern idCVar net_clientPredictWeaponSwitch; + +#endif /* !__SYS_CVAR_H__ */ diff --git a/source/mpgame/mp/Buying.cpp b/source/mpgame/mp/Buying.cpp new file mode 100644 index 0000000..fcec627 --- /dev/null +++ b/source/mpgame/mp/Buying.cpp @@ -0,0 +1,68 @@ +//---------------------------------------------------------------- +// Buying.cpp +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.cpp and Player.cpp, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Buying.h" + + +riBuyingManager::riBuyingManager() : + _buyingGameBalanceConstants( NULL ), + opponentKillCashAward( 0 ), + opponentKillFragCount( -1 ) { } + +riBuyingManager::~riBuyingManager() { } + +int riBuyingManager::GetIntValueForKey( const char* keyName, int defaultValue ) { + if( !keyName ) + { + return defaultValue; + } + + if( !_buyingGameBalanceConstants ) + { + _buyingGameBalanceConstants = static_cast( declManager->FindType( DECL_ENTITYDEF, "BuyingGameBalanceConstants", false ) ); + + if( !_buyingGameBalanceConstants ) + { + return defaultValue; + } + } + + for( int i = 0; i < _buyingGameBalanceConstants->dict.GetNumKeyVals(); i++ ) + { + const idKeyValue* keyValuePair = _buyingGameBalanceConstants->dict.GetKeyVal( i ); + if( !keyValuePair->GetKey().Icmp( keyName ) ) + { + return atoi( keyValuePair->GetValue() ); + } + } + + return defaultValue; +} + +int riBuyingManager::GetOpponentKillCashAward( void ) { + int targetFragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + if ( opponentKillFragCount != targetFragCount ) { + opponentKillFragCount = targetFragCount; + if ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) && idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) ) { + // only do frag reward scaling in DM/TDM + opponentKillCashAward = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + } else { + targetFragCount = idMath::ClampInt( GetIntValueForKey( "killingOpponent_minFragAdjust", 10 ), GetIntValueForKey( "killingOpponent_maxFragAdjust",50 ), targetFragCount ); + int baseVal = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + int fragTarget = GetIntValueForKey( "killingOpponent_bestFragCount", 25 ); + opponentKillCashAward = ( baseVal * fragTarget ) / targetFragCount; + } + } + return opponentKillCashAward; +} diff --git a/source/mpgame/mp/Buying.h b/source/mpgame/mp/Buying.h new file mode 100644 index 0000000..66b2e44 --- /dev/null +++ b/source/mpgame/mp/Buying.h @@ -0,0 +1,37 @@ +//---------------------------------------------------------------- +// Buying.h +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.h and Player.h, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#ifndef __BUYING_H__ +#define __BUYING_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + + +class riBuyingManager +{ +private: + const idDeclEntityDef* _buyingGameBalanceConstants; + int opponentKillCashAward; // latch + int opponentKillFragCount; + +public: + riBuyingManager(); + ~riBuyingManager(); + + int GetIntValueForKey( const char* keyName, int defaultValue ); + int GetOpponentKillCashAward( void ); + + void Reset( void ) { opponentKillFragCount = -1; } +}; + + +#endif // __BUYING_H__ diff --git a/source/mpgame/mp/CTF.cpp b/source/mpgame/mp/CTF.cpp new file mode 100644 index 0000000..7887f1f --- /dev/null +++ b/source/mpgame/mp/CTF.cpp @@ -0,0 +1,308 @@ +//---------------------------------------------------------------- +// CTF.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "CTF.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ +CLASS_DECLARATION( idEntity, rvCTF_AssaultPoint ) + EVENT( EV_PostSpawn, rvCTF_AssaultPoint::Event_InitializeLinks ) + EVENT( EV_Touch, rvCTF_AssaultPoint::Event_Touch ) +END_CLASS + +rvCTF_AssaultPoint::rvCTF_AssaultPoint() { + trigger = NULL; + linked = false; + owner = AS_NEUTRAL; +} + +rvCTF_AssaultPoint::~rvCTF_AssaultPoint() { + delete trigger; + trigger = NULL; +} + +/* +================ +rvCTF_AssaultPoint::Spawn +================ +*/ +void rvCTF_AssaultPoint::Spawn( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvCTF_AssaultPoint::InitializeLinks +================ +*/ +void rvCTF_AssaultPoint::Event_InitializeLinks( void ) { + if( linked ) { + return; + } + + // pull in our targets + toStrogg = gameLocal.FindEntity( spawnArgs.GetString( "targetStroggAP" ) ); + toMarine = gameLocal.FindEntity( spawnArgs.GetString( "targetMarineAP" ) ); + + ResetIndices(); + + trigger = new idClipModel( idTraceModel( idBounds( vec3_origin ).Expand( 16.0f ) ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + trigger->SetContents ( CONTENTS_TRIGGER ); + + GetPhysics()->SetClipModel( NULL, 1.0f ); + + linked = true; +} + +/* +================ +rvCTF_AssaultPoint::ResetIndices +================ +*/ +void rvCTF_AssaultPoint::ResetIndices( void ) { + // find the closest AP to the marine flag, which is index 0, then work towards strogg flag + if ( !toMarine || !toMarine->IsType ( rvItemCTFFlag::GetClassType() ) ) { + return; + } + + idEntityPtr ptr; + ptr = this; + gameLocal.mpGame.assaultPoints.Append( ptr ); + int assignIndices = 0; + index = assignIndices++; + rvCTF_AssaultPoint* ap = this; + while( !ap->toStrogg->IsType ( rvItemCTFFlag::GetClassType() ) ) { + if( !ap->toStrogg->IsType ( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - non assault point targeted in assault point chain" ); + } + ap = static_cast(ap->toStrogg.GetEntity()); + ap->index = assignIndices++; + + ptr = ap; + gameLocal.mpGame.assaultPoints.Append( ptr ); + + if ( !ap->linked ) { + ap->Event_InitializeLinks(); + } + + if( !ap->toStrogg ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - break in assault point chain" ); + } + } +} + +/* +================ +rvCTF_AssaultPoint::Event_Activate +================ +*/ +void rvCTF_AssaultPoint::Event_Touch( idEntity *activator, trace_t *trace ) { + if( !activator->IsType( idPlayer::GetClassType() ) || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return; + } + + idPlayer* player = static_cast(activator); + int oldOwner = owner; + + if ( owner == player->team ) + return; + + int enemyPowerup = -1; + int friendlyPowerup = -1; + + if ( owner == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } else if ( owner == TEAM_STROGG ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + // neutral assault point + if ( player->team == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } + } + + if ( !player->PowerUpActive ( enemyPowerup ) ) { + return; + } + + + switch( player->team ) { + case TEAM_MARINE: { + if( !toMarine || owner == TEAM_MARINE ) { + break; + } + + if( toMarine->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } else if( toMarine->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toMarine.GetEntity())->owner == TEAM_MARINE ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } + } + break; + } + case TEAM_STROGG: { + if( !toStrogg || owner == TEAM_STROGG ) { + break; + } + + if( toStrogg->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } else if( toStrogg->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toStrogg.GetEntity())->owner == TEAM_STROGG ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } + } + break; + } + } + + + if( oldOwner != owner ) { + // we switched hands, reset forward spawns + gameLocal.ClearForwardSpawns(); + + if( oldOwner == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( oldOwner ); + } + } else if( oldOwner == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( oldOwner ); + } + } + + rvItemCTFFlag::ResetFlag ( enemyPowerup ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetAPOwner( index, owner ); + + ActivateTargets( this ); + + SetOwnerColor (); + } +} + +/* +================ +rvCTF_AssaultPoint::ResetSpawns +================ +*/ +void rvCTF_AssaultPoint::ResetSpawns( int team ) { + // check to see if team can spawn at me, if not pass down + if( owner == team ) { + ActivateTargets( this ); + } else { + if( team == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( team ); + } + } else if( team == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( team ); + } + } + } +} + +void rvCTF_AssaultPoint::SetOwner ( int newOwner ) { + if ( !gameLocal.isClient ) { + // server should set owner via EVENT_ACTIVATE, not here + return; + } + + owner = newOwner; + SetOwnerColor (); +} + +void rvCTF_AssaultPoint::Reset ( void ) { + owner = AS_NEUTRAL; + SetOwnerColor(); +} + +/* +================ +rvCTF_AssaultPoint::ChangeColor +================ +*/ +void rvCTF_AssaultPoint::SetOwnerColor ( void ) { + const idDeclSkin* skin = NULL; + if( owner == TEAM_MARINE ) { + skin = declManager->FindSkin( "skins/ddynerman/green_glow", false ); + } else if ( owner == TEAM_STROGG ) { + skin = declManager->FindSkin( "skins/ddynerman/orange_glow", false ); + } else { + skin = declManager->FindSkin( "skins/ddynerman/white", false ); + } + + if ( skin ) { + SetSkin( skin ); + } +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ + +CLASS_DECLARATION( idPlayerStart, rvCTFAssaultPlayerStart ) + EVENT( EV_Activate, rvCTFAssaultPlayerStart::Event_Activate ) +END_CLASS + +/* +================ +rvCTFAssaultPlayerStart::Spawn +================ +*/ +void rvCTFAssaultPlayerStart::Spawn(void) { + if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else { + gameLocal.Error("rvCTFAssaultPlayerStart::Spawn() - unknown team\n"); + team = -1; + } +} + +void rvCTFAssaultPlayerStart::Event_Activate( idEntity *activator ) { + if ( !activator->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Warning( "rvCTFAssaultPlayerStart::Event_Activate() - was activated by something other than an assault point\n" ); + return; + } + + rvCTF_AssaultPoint* ap = static_cast(activator); + + if( ap->GetOwner() == team ) { + gameLocal.UpdateForwardSpawns( this, team ); + } +} diff --git a/source/mpgame/mp/CTF.h b/source/mpgame/mp/CTF.h new file mode 100644 index 0000000..b0758b3 --- /dev/null +++ b/source/mpgame/mp/CTF.h @@ -0,0 +1,84 @@ +//---------------------------------------------------------------- +// CTF.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __CTF_H__ +#define __CTF_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ + +class rvCTF_AssaultPoint : public idEntity { +public: + CLASS_PROTOTYPE( rvCTF_AssaultPoint ); + void Spawn( void ); + + rvCTF_AssaultPoint(); + ~rvCTF_AssaultPoint(); + + int GetOwner( void ); + int GetIndex( void ); + + void SetOwnerColor ( void ); + void SetOwner ( int newOwner ); + void Reset ( void ); + +private: + void Event_Touch( idEntity *activator, trace_t *trace ); + void Event_InitializeLinks( void ); + void ResetSpawns( int team ); + + void ResetIndices( void ); + + // these could be maintained as lists to allow multiple AP paths + // the assault point one step closer to the Strogg base + idEntityPtr toStrogg; + // the assault point one step closer to the Marine base + idEntityPtr toMarine; + + // who currently owns this assault point + int owner; + int index; + bool linked; + + idClipModel* trigger; +}; + +ID_INLINE int rvCTF_AssaultPoint::GetOwner( void ) { + return owner; +} + +ID_INLINE int rvCTF_AssaultPoint::GetIndex( void ) { + return index; +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ +class rvCTFAssaultPlayerStart : public idPlayerStart { +public: + CLASS_PROTOTYPE( rvCTFAssaultPlayerStart ); + + void Spawn( void ); + + int GetTeam( void ); +private: + void Event_Activate( idEntity *activator ); + int team; +}; + +#endif diff --git a/source/mpgame/mp/GameState.cpp b/source/mpgame/mp/GameState.cpp new file mode 100644 index 0000000..b483952 --- /dev/null +++ b/source/mpgame/mp/GameState.cpp @@ -0,0 +1,3133 @@ +//---------------------------------------------------------------- +// GameState.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "GameState.h" + +/* +=============================================================================== + +rvGameState + +Game state info for deathmatch, team deathmatch + +=============================================================================== +*/ + +/* +================ +rvGameState::rvGameState +================ +*/ +rvGameState::rvGameState( bool allocPrevious ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvGameState::~rvGameState +================ +*/ +rvGameState::~rvGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +rvGameState::Clear +================ +*/ +void rvGameState::Clear( void ) { + currentState = INACTIVE; + nextState = INACTIVE; + nextStateTime = 0; + fragLimitTimeout = 0; +} + +/* +================ +rvGameState::SendState +================ +*/ +void rvGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious ); + + if( clientNum == -1 && (*this) == (*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvGameState::WriteState +=============== +*/ +void rvGameState::WriteState( idBitMsg &msg ) { + PackState( msg ); +} + +/* +================ +rvGameState::SendInitialState +================ +*/ +void rvGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + rvGameState* previousState = previousGameState; + + rvGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvGameState::ReceiveState +================ +*/ +void rvGameState::ReceiveState( const idBitMsg& msg ) { + if ( !BaseUnpackState( msg ) ) { + return; + } + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (*previousGameState) = (*this); +} + +/* +================ +rvGameState::PackState +================ +*/ +void rvGameState::PackState( idBitMsg& outMsg ) { + // server and client changing their rvGameState subclass by guesswork + // you can't rely on it being in sync at all times, so read and verify the type first + outMsg.WriteByte( gameLocal.gameType ); + + // for now, we only transmit 3 bytes. If we need to sync more data, we should + // only transmit the diff + outMsg.WriteByte( currentState ); + outMsg.WriteByte( nextState ); + outMsg.WriteLong( nextStateTime ); +} + +/* +================ +rvGameState::BaseUnpackState +================ +*/ +bool rvGameState::BaseUnpackState( const idBitMsg& inMsg ) { + gameType_t t = (gameType_t)inMsg.ReadByte(); + if ( t != gameLocal.gameType ) { + common->Warning( "rvGameState::UnpackState: client gametype (%d) is out of sync with server (%d). Ignoring", gameLocal.gameType, t ); + return false; + } + + currentState = (mpGameState_t)inMsg.ReadByte(); + nextState = (mpGameState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + return true; +} + +/* +================ +rvGameState::GameStateChanged +================ +*/ +void rvGameState::GameStateChanged( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + // Check for a currentState change + if( currentState != previousGameState->currentState ) { + if( currentState == WARMUP ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld( true ); + + // reset stats on the client-side + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == COUNTDOWN ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld(true); + if( gameLocal.gameType != GAME_TOURNEY && previousGameState->currentState != INACTIVE ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, nextStateTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, nextStateTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, nextStateTime - 1000 ); + } + } else if( currentState == GAMEON ) { + if ( !player->vsMsgState ) { + player->GUIMainNotice( "" ); + player->GUIFragNotice( "" ); + } else { + player->vsMsgState = false; + } + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + } + //if ( gameLocal.gameType == GAME_DEADZONE ) { + // if ( player->team == TEAM_MARINE ) + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time ); + // else + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time ); + //} + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + soundSystem->SetActiveSoundWorld( true ); + + // clear stats on client + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == SUDDENDEATH ) { + soundSystem->SetActiveSoundWorld( true ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } else if( currentState == GAMEREVIEW ) { + // RITUAL BEGIN + gameLocal.mpGame.isBuyingAllowedRightNow = false; + // RITUAL END + gameLocal.mpGame.ShowStatSummary(); + } + + gameLocal.mpGame.ScheduleTimeAnnouncements( ); + } +} + +/* +================ +rvGameState::SpawnDeadZonePowerup +================ +*/ +void rvGameState::SpawnDeadZonePowerup( void ) { + idEntity *ent; + riDeadZonePowerup* spawnSpot = 0; + int count = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::GetClassType() ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != POWERUP_DEADZONE || flag->IsVisible() ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && !flag->IsVisible() ) { + flag->PostEventMS( &EV_Remove, 0 ); + + } else { + count++; + if ( !(rand()%(count)) ) { + spawnSpot = flag; + } + } + } + if ( spawnSpot ) { + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + spawnSpot->srvReady = 1; // Go ahead and set this, so the loop works properly. + } else { + gameLocal.Error("Couldn't find enough dead zone spawn spots for the number of dead zone artifacts specified in the map def!"); + } +} + +/* +================ +rvGameState::Run +================ +*/ +void rvGameState::Run( void ) { + if ( currentState == INACTIVE ) { + +#ifdef _XENON + if(Live()->RoundsPlayed() < cvarSystem->GetCVarInteger("si_matchRounds")) +#endif + { + NewState( WARMUP ); + } + } + + if ( nextState != INACTIVE && gameLocal.time > nextStateTime ) { + NewState( nextState ); + nextState = INACTIVE; + } + + switch( currentState ) { + case INACTIVE: + break; + + case GAMEREVIEW: { + if ( nextState == INACTIVE ) { + + statManager->SendAllStats(); + + nextState = NEXTGAME; + + // allow a little extra time in tourney since we have to display end brackets + if( gameLocal.gameType == GAME_TOURNEY ) { + nextStateTime = gameLocal.time + 5000 + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } else { + nextStateTime = gameLocal.time + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } + } + break; + } + case NEXTGAME: { + + // the core will force a restart at 12 hours max + // but it's nicer if we can wait for a game transition to perform the restart so the game is not interrupted + // perform a restart once we are past 8 hours + if ( networkSystem->ServerGetServerTime() > 8*60*60*1000 ) { + gameLocal.sessionCommand = "nextMap"; + return; + } + + if ( nextState == INACTIVE ) { + // game rotation, new map, gametype etc. + // only cycle in tourney if tourneylimit is higher than the specified value + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)this)->GetTourneyCount() >= gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + // whether we switch to the next map or not, reset the tourney count + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)this)->SetTourneyCount( 0 ); + } + + + if ( gameLocal.NextMap() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart\n" ); + return; + } + } + + NewState( WARMUP ); + + // put everyone back in from endgame spectate + for ( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->wantSpectate ) { + gameLocal.mpGame.CheckRespawns( static_cast( ent ) ); + } + } + } + } + break; + } + case WARMUP: { + // check to see if we actually want to do a warmup, or if we fall through + +//RAVEN BEGIN +//asalmon: Live has its own rules for ending warm up +#ifdef _XENON + if(!Live()->RollCall()) + { + break; + } + +#endif +//RAVEN END + if( !gameLocal.serverInfo.GetBool( "si_warmup" ) && gameLocal.gameType != GAME_TOURNEY ) { + // tourney always needs a warmup, to ensure that at least 2 players get seeded for the tournament. + NewState( GAMEON ); + } else if ( gameLocal.mpGame.AllPlayersReady() ) { + NewState( COUNTDOWN ); + nextState = GAMEON; + nextStateTime = gameLocal.time + 1000 * gameLocal.serverInfo.GetInt( "si_countDown" ); + } + break; + } + case COUNTDOWN: { + break; + } + } +} + +/* +================ +rvGameState::NewState +================ +*/ +void rvGameState::NewState( mpGameState_t newState ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + int i; + + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + // asalmon: start the stat manager as soon as the game starts + statManager->Init(); + statManager->BeginGame(); + + // if shuffle is on, shuffle the teams around + if( gameLocal.IsTeamGame() && gameLocal.serverInfo.GetBool( "si_shuffle" ) ) { + gameLocal.mpGame.ShuffleTeams(); + } + + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( false ); + + //asalmon: clear out lingering team scores. + gameLocal.mpGame.ClearTeamScores(); + + if( gameLocal.gameType != GAME_TOURNEY ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + ((idPlayer*)ent)->JoinInstance( 0 ); + } + } + + break; + } + case GAMEON: { + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart(); +// RITUAL BEGIN +// squirrel: Buying & Deadzone + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + // If the buy menu is up during a server restart, + // make sure to refresh it. + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) + gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iInit(); + statManager->BeginGame(); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + gameLocal.mpGame.SetMatchStartedTime( gameLocal.time ); + + fragLimitTimeout = 0; + + // write server initial reliable messages to give everyone new base + for( i = 0; i < MAX_CLIENTS; i++ ) { + // dont send this to server - we have all the data already and this will + // just trigger extra gamestate detection + if ( gameLocal.entities[ i ] && i != gameLocal.localClientNum ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( serverReliableSender.To( i, true ), i ); + } + } + + if ( gameLocal.isRepeater ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE ); + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + gameLocal.mpGame.SetPlayerScore( p, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( p, 0 ); + + // in normal gameplay modes, spawn the player in. For tourney, the tourney manager handles spawning + if( gameLocal.gameType != GAME_TOURNEY ) { + p->JoinInstance( 0 ); + // in team games, let UserInfoChanged() spawn people on the right teams + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( static_cast(ent)->wantSpectate ); + } + } + } + + gameLocal.mpGame.ClearTeamScores(); + + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + gameLocal.mpGame.switchThrottle[ 1 ] = 0; // passby the throttle + break; + } + case GAMEREVIEW: { + statManager->EndGame(); + + //statManager->DebugPrint(); + nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } +// RITUAL BEGIN +// squirrel: support for Buying in multiplayer + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + player->forcedReady = false; + player->ServerSpectate( true ); + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); +// RITUAL END + } + break; + } + case SUDDENDEATH: { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_SUDDENDEATH ); + //unmark all leaders, so we make sure we only let the proper people respawn + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + } + + // only restart in team games if si_suddenDeathRestart is set. + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) { + gameLocal.LocalMapRestart(); + } + + // Mark everyone tied for the lead as leaders + i = 0; + idPlayer* leader = gameLocal.mpGame.GetRankedPlayer( i ); + if( leader ) { + int highScore = gameLocal.mpGame.GetScore( leader ); + while( leader ) { + if( gameLocal.mpGame.GetScore( leader ) < highScore ) { + break; + } + leader->SetLeader( true ); + leader = gameLocal.mpGame.GetRankedPlayer( ++i ); + } + } + +// RITUAL BEGIN +// squirrel: Buying & Deadzone + /// Reset players' cash and inventory if si_suddenDeathRestart is set + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) + { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + } + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) + gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iIsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } + if ( static_cast< idPlayer *>( ent )->IsLeader() ) { + static_cast(ent)->ServerSpectate( false ); + continue; + } + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); + } + } + + break; + } + default: { + break; + } + } + + currentState = newState; +} + +/* +================ +rvGameState::ClientDisconnect +================ +*/ +void rvGameState::ClientDisconnect( idPlayer* player ) { + return; +} + +/* +================ +rvGameState::Spectate +================ +*/ +void rvGameState::Spectate( idPlayer* player ) { + if( player->spectating && player->wantSpectate ) { + gameLocal.mpGame.ClearFrags( player->entityNumber ); + } + return; +} + +/* +================ +rvGameState::operator== +================ +*/ +bool rvGameState::operator==( const rvGameState& rhs ) const { + return ( + ( currentState == rhs.currentState ) && + ( nextState == rhs.nextState ) && + ( nextStateTime == rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator!= +================ +*/ +bool rvGameState::operator!=( const rvGameState& rhs ) const { + return ( + ( currentState != rhs.currentState ) || + ( nextState != rhs.nextState ) || + ( nextStateTime != rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator= +================ +*/ +rvGameState& rvGameState::operator=( const rvGameState& rhs ) { + currentState = rhs.currentState; + nextState = rhs.nextState; + nextStateTime = rhs.nextStateTime; + return (*this); +} + +/* +=============== +rvGameState::WriteNetworkInfo +=============== +*/ +void rvGameState::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteState( msg ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); +} + +/* +=============== +rvGameState::ReadNetworkInfo +=============== +*/ +void rvGameState::ReadNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, size ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ReceiveState( msg ); +} + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +rvDMGameState::rvDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvDMGameState::Run( void ) { + idPlayer* player = NULL; + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + player = gameLocal.mpGame.FragLimitHit(); + + bool tiedForFirst = false; + idPlayer* first = gameLocal.mpGame.GetRankedPlayer( 0 ); + idPlayer* second = gameLocal.mpGame.GetRankedPlayer( 1 ); + + if( player == NULL ) { + if( first && second && gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second ) ) { + tiedForFirst = true; + } + } + + if ( player ) { + // delay between detecting frag limit and ending game. let the death anims play + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( first && second && (gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second )) ) { + //this is a tie... + if( gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it must be tied at fraglimit, so sudden death. + NewState( SUDDENDEATH ); + } + } + //otherwise, just keep playing as normal. + fragLimitTimeout = 0; + + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + // check for the rare case that two players both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + break; + } + + case SUDDENDEATH: { + player = gameLocal.mpGame.FragLeader(); + + if ( player ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + + } +} + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +rvTeamDMGameState::rvTeamDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvTeamDMGameState::Run( void ) { + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) <= 0 ) { + // no fraglimit + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( tiedForFirst ) { + //this is a tie + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it's tied at the fraglimit. + NewState( SUDDENDEATH ); + } + //not a tie, game on. + fragLimitTimeout = 0; + } + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +/* +================ +rvCTFGameState::rvCTFGameState +================ +*/ +rvCTFGameState::rvCTFGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvCTFGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvCTFGameState::Clear +================ +*/ +void rvCTFGameState::Clear( void ) { + rvGameState::Clear(); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ].state = FS_AT_BASE; + flagStatus[ i ].clientNum = -1; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = AS_NEUTRAL; + } +} + +/* +================ +rvCTFGameState::SendState +================ +*/ +void rvCTFGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvCTFGameState::GetClassType() ) ); + + if( clientNum == -1 && (rvCTFGameState&)(*this) == (rvCTFGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvCTFGameState::WriteState +=============== +*/ +void rvCTFGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add CTF info + PackState( msg ); +} + +/* +================ +rvCTFGameState::ReceiveState +================ +*/ +void rvCTFGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvCTFGameState&)(*previousGameState) = (rvCTFGameState&)(*this); +} + +/* +================ +rvCTFGameState::PackState +================ +*/ +void rvCTFGameState::PackState( idBitMsg& outMsg ) { + // use indexing to pack in info + int index = 0; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( flagStatus[ i ].state ); + outMsg.WriteByte( flagStatus[ i ].clientNum ); + } + index++; + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != ((rvCTFGameState*)previousGameState)->apState[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( apState[ i ] ); + } + index++; + } +} + +/* +================ +rvCTFGameState::UnpackState +================ +*/ +void rvCTFGameState::UnpackState( const idBitMsg& inMsg ) { + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + if( index >= 0 && index < TEAM_MAX ) { + flagStatus[ index ].state = (flagState_t)inMsg.ReadByte(); + flagStatus[ index ].clientNum = inMsg.ReadByte(); + } else if( index >= TEAM_MAX && index < ( TEAM_MAX + MAX_AP ) ) { + apState[ index - TEAM_MAX ] = (apState_t)inMsg.ReadByte(); + } else { + gameLocal.Error( "rvCTFGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + } +} + +/* +================ +rvCTFGameState::SendInitialState +================ +*/ +void rvCTFGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvCTFGameState* previousState = (rvCTFGameState*)previousGameState; + + rvCTFGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvCTFGameState::GameStateChanged +================ +*/ +void rvCTFGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // CTF specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvCTFGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + bool noSounds = false; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] == ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + continue; + } + + // don't play flag messages when flag state changes as a result of the gamestate changing + if( currentState != ((rvCTFGameState*)previousGameState)->currentState && ((rvCTFGameState*)previousGameState)->currentState != INACTIVE ) { + continue; + } + + if ( ((rvCTFGameState*)previousGameState)->currentState == INACTIVE ) { + noSounds = true; + } + + // flagTeam - used to tell the HUD which flag to update + int flagTeam = i; + + // in one flag CTF flagTeam is set to whichever team has the flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if( flagStatus[ i ].state == FS_TAKEN_MARINE ) { + flagTeam = TEAM_MARINE; + } else if( flagStatus[ i ].state == FS_TAKEN_STROGG ) { + flagTeam = TEAM_STROGG; + } + } + + if( flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + if ( !player->spectating ) { + if( flagTeam == player->team ) { + // our flag was dropped, so the enemy dropped it + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_DROPS_FLAG, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_DROPS_FLAG, gameLocal.time, -1, true ); + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagDrop" ); + + if ( !player->spectating ) { + if ( flagTeam == player->team ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107723" ) ); + } else { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + } + + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else if( flagStatus[ i ].state == FS_AT_BASE ) { + if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_TAKEN && !noSounds ) { + // team scores + if ( !player->spectating ) { + if( flagTeam == player->team ) { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_ENEMY_SCORES, gameLocal.time ); + } + } else { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_YOU_SCORE, gameLocal.time ); + } + } + } + } else if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + // flag returned + if ( !player->spectating ) { + if( flagTeam == player->team ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_FLAG_RETURNED, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_RETURNS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagReturn" ); + } + } else if( flagStatus[ i ].state == FS_TAKEN || flagStatus[ i ].state == FS_TAKEN_STROGG || flagStatus[ i ].state == FS_TAKEN_MARINE ) { + // flag taken + if( flagTeam == player->team ) { + if ( !player->spectating ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_HAS_FLAG, gameLocal.time, -1, true ); + } + } + + if ( !player->spectating ) { + if ( player->mphud ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107722" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else { + if ( flagStatus[ i ].clientNum == gameLocal.localClientNum ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOU_HAVE_FLAG, gameLocal.time, -1, true ); + } + + if ( !player->spectating ) { + // shouchard: inform the GUI that you've taken the flag + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } else if ( !noSounds ) { + if ( !player->spectating ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_HAS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent ( "flagTaken" ); + } + } + } +} + +/* +================ +rvCTFGameState::Run +================ +*/ +void rvCTFGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_captureLimit" ) <= 0 ) { + // no capture limit games + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter capture limit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // OR the winner lost a point in the frag delay, and there's no tie, so no one wins, game on. + if( tiedForFirst && ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) )) { + NewState( SUDDENDEATH ); + } + fragLimitTimeout = 0; + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +================ +rvCTFGameState::SetFlagState +================ +*/ +void rvCTFGameState::SetFlagState( int flag, flagState_t newState ) { + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].state = newState; +} + +/* +================ +rvCTFGameState::SetFlagCarrier +================ +*/ +void rvCTFGameState::SetFlagCarrier( int flag, int clientNum ) { + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && (clientNum >= 0 && clientNum < MAX_CLIENTS) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].clientNum = clientNum; +} + +/* +================ +rvCTFGameState::operator== +================ +*/ +bool rvCTFGameState::operator==( const rvCTFGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != rhs.flagStatus[ i ] ) { + return false; + } + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != rhs.apState[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvCTFGameState::operator= +================ +*/ +rvCTFGameState& rvCTFGameState::operator=( const rvCTFGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ] = rhs.flagStatus[ i ]; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = rhs.apState[ i ]; + } + + return (*this); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +/* +================ +rvTourneyGameState::rvTourneyGameState +================ +*/ +rvTourneyGameState::rvTourneyGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvTourneyGameState( false ); + } else { + previousGameState = NULL; + } + + packTourneyHistory = false; + + trackPrevious = allocPrevious; +} + +/* +================ +rvTourneyGameState::Clear +================ +*/ +void rvTourneyGameState::Clear( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear(); + } + + rvGameState::Clear(); + + tourneyState = TS_INVALID; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + tourneyCount = 0; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Reset +================ +*/ +void rvTourneyGameState::Reset( void ) { + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Run +================ +*/ +void rvTourneyGameState::Run( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + // check to see if we need to advance to the next round + if( nextState == GAMEREVIEW ) { + return; + } + + bool roundDone = true; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_INACTIVE ) { + continue; + } + + arenas[ i ].UpdateState(); + + if( arenas[ i ].GetState() != AS_DONE ) { + // check to see if we're done + roundDone = false; + } + } + if( roundDone && !roundTimeout ) { + roundTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + if( roundDone && gameLocal.time > roundTimeout ) { + int pastRound = round - 1; //rounds are 1 indexed + idList > advancingPlayers; + + round++; + roundTimeout = 0; + + assert( round >= 2 ); + + // copy over tourney history for the previous round + UpdateTourneyHistory( pastRound ); + + // transmit history + forceTourneyHistory = true; + + // add who will advance to the next round + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() ) { + advancingPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + gameLocal.mpGame.AddPlayerWin( arenas[ i ].GetWinner(), 1 ); + } + } + + // when only one player advances, that player has won the tournament + if( advancingPlayers.Num() == 1 ) { + idPlayer* winner = advancingPlayers[ 0 ].First(); + assert( winner ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear(); + } + + gameLocal.Printf( "Round %d: player %d (%s) has won the tourney!\n", round - 1, winner->entityNumber, gameLocal.userInfo[ winner->entityNumber ].GetString( "ui_name" ) ); + // award 5 wins for winning the entire tournament + gameLocal.mpGame.AddPlayerWin( winner, 5 ); + + nextState = GAMEREVIEW; + nextStateTime = gameLocal.time + 5000; + + return; + } + + // see if we have to swap a player from the byeArena this round + bool swapBye = false; + int thisByeArena = -1; + + if( byeArena >= 0 ) { + // look at the bye arena from the round that just ended + thisByeArena = byeArena / (round - 1); + + // if the bye arena is playing in the next round, there's no need to switch players + if( !(thisByeArena % 2) ) { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena + 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena + 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } else { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena - 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena - 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } + } + + idPlayer* oldByePlayer = NULL; + idPlayer* newByePlayer = NULL; + + if( swapBye ) { + oldByePlayer = arenas[ thisByeArena ].GetWinner(); + + // pick a new bye player only from players who will be fighting next round + // i.e., don't swap the bye player with a player who will have a disconnect bye this upcoming round + idList > nextRoundPlayers; + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() && arenas[ i + 1 ].GetState() == AS_DONE && arenas[ i + 1 ].GetWinner() ) { + nextRoundPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + nextRoundPlayers.Append( rvPair( arenas[ i + 1 ].GetWinner(), i + 1 ) ); + } + } + + if ( nextRoundPlayers.Num() ) { + newByePlayer = nextRoundPlayers[ gameLocal.random.RandomInt( nextRoundPlayers.Num() ) ].First(); + } + } + + // assign arenas for the next round + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + idPlayer* advanceOne = arenas[ i ].GetWinner(); + idPlayer* advanceTwo = arenas[ i + 1 ].GetWinner(); + + // #13266 - bug: bystander from prev round is spectator in his match arena after round change + // we call rvTourneyInstance::Clear when setting up rounds (below), which sets it's players as spectator + // if the former bystander is placed in one of the new arenas and is still referenced in the bye arena then he'll get set spectating + // so just clear him up once identified + // #13631 - we used to call RemovePlayer before entering that loop, but that was messing up the selection of the next bystander + // so have to do it inside the loop now, just before it happens + if ( ( i == thisByeArena || i + 1 == thisByeArena ) && arenas[ thisByeArena ].HasPlayer( oldByePlayer ) ) { + arenas[ thisByeArena ].RemovePlayer( oldByePlayer ); + } + + arenas[ i ].Clear(); + arenas[ i + 1 ].Clear(); + + // assign new arenas, swapping oldByePlayer with newByePlayer + if ( newByePlayer && oldByePlayer ) { + if ( advanceOne == newByePlayer ) { + advanceOne = oldByePlayer; + } else if ( advanceTwo == newByePlayer ) { + advanceTwo = oldByePlayer; + } else if ( advanceOne == oldByePlayer ) { + advanceOne = newByePlayer; + } else if ( advanceTwo == oldByePlayer ) { + advanceTwo = newByePlayer; + } + } + + if ( advanceOne || advanceTwo ) { + arenas[ (i / 2) ].AddPlayers( advanceOne, advanceTwo ); + + if ( advanceOne ) { + advanceOne->JoinInstance( (i / 2) ); + advanceOne->ServerSpectate( false ); + } + + if ( advanceTwo ) { + advanceTwo->JoinInstance( (i / 2) ); + advanceTwo->ServerSpectate( false ); + } + + gameLocal.Printf( "Round %d: Arena %d is starting play with players %d (%s) and %d (%s)\n", round, (i / 2), advanceOne ? advanceOne->entityNumber : -1, advanceOne ? advanceOne->GetUserInfo()->GetString( "ui_name" ) : "NULL", advanceTwo ? advanceTwo->entityNumber : -1, advanceTwo ? advanceTwo->GetUserInfo()->GetString( "ui_name" ) : "NULL" ); + + arenas[ (i / 2) ].Ready(); + } + } + + // once the new round is setup, go through and make sure all the spectators are in valid arena + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + + // re-select our arena since round changed + if( p && p->spectating ) { + rvTourneyArena& thisArena = arenas[ p->GetArena() ]; + if( thisArena.GetState() != AS_WARMUP ) { + p->JoinInstance( GetNextActiveArena( p->GetArena() ) ); + } + } + } + } + + break; + } + } +} + +/* +================ +rvTourneyGameState::NewState +================ +*/ +void rvTourneyGameState::NewState( mpGameState_t newState ) { + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + Reset(); + // force everyone to spectate + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] ) { + ((idPlayer*)gameLocal.entities[ i ])->ServerSpectate( true ); + } + } + break; + } + case GAMEON: { + tourneyCount++; + SetupInitialBrackets(); + break; + } + } + + rvGameState::NewState( newState ); +} + +/* +================ +rvTourneyGameState::GameStateChanged +================ +*/ +void rvTourneyGameState::GameStateChanged( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + rvGameState::GameStateChanged(); + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + int oldRound = ((rvTourneyGameState*)previousGameState)->round; + + if( round != oldRound ) { + if ( round > maxRound && gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->ForceScoreboard( true, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_DONE, gameLocal.time ); + } + + // we're starting a new round + gameLocal.mpGame.tourneyGUI.RoundStart(); + + // skip announce if the round number doesn't make sense + if ( round >= 1 && round <= MAX_ROUNDS ) { + // play the sound a bit after round restart to let spawn sounds settle + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)( AS_TOURNEY_PRELIMS + round - 1 ), gameLocal.time + 1500); + } + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& thisArena = arenas[ i ]; + rvTourneyArena& previousArena = ((rvTourneyGameState*)previousGameState)->arenas[ i ]; + + if( ((thisArena.GetPlayers()[ 0 ] != previousArena.GetPlayers()[ 0 ]) || + (thisArena.GetPlayers()[ 1 ] != previousArena.GetPlayers()[ 1 ]) || + (round != ((rvTourneyGameState*)previousGameState)->round )) /*&& + !((thisArena.GetPlayers()[ 0 ] == NULL && thisArena.GetPlayers()[ 1 ] != NULL) || + (thisArena.GetPlayers()[ 0 ] != NULL && thisArena.GetPlayers()[ 1 ] == NULL) ) */) { + + // don't re-init arenas that have changed and been done for a while + if( thisArena.GetState() != AS_DONE || previousArena.GetState() != AS_DONE ) { + gameLocal.mpGame.tourneyGUI.ArenaStart( i ); + } + + if( localPlayer && thisArena.GetPlayers()[ 0 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_ONE ); + } + if( localPlayer && thisArena.GetPlayers()[ 1 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_TWO ); + } + } + + if( localPlayer->GetArena() == thisArena.GetArenaID() && thisArena.GetState() == AS_SUDDEN_DEATH && thisArena.GetState() != previousArena.GetState() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time, thisArena.GetArenaID() ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } + + if( thisArena.GetState() == AS_DONE && thisArena.GetState() != previousArena.GetState() ) { + if( thisArena.GetWinner() != previousArena.GetWinner() && thisArena.GetWinner() == gameLocal.GetLocalPlayer() ) { + if( round >= maxRound ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_WON, gameLocal.time ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ADVANCE, gameLocal.time ); + } + } else if( thisArena.GetLoser() != previousArena.GetLoser() && thisArena.GetLoser() == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ELIMINATED, gameLocal.time ); + } + + if( previousArena.GetWinner() == NULL && thisArena.GetWinner() != NULL ) { + gameLocal.mpGame.tourneyGUI.ArenaDone( i ); + } + + // on the client, add this result to our tourneyHistory + //if( gameLocal.isClient && thisArena.GetPlayers()[ 0 ] && thisArena.GetPlayers()[ 1 ] && (round - 1 ) >= 0 && (round - 1) < MAX_ROUNDS ) { + // tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 0 ] ); + // tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 1 ] ); + //} + } + + if( localPlayer && (thisArena.GetState() == AS_WARMUP) && (thisArena.GetState() != previousArena.GetState()) && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + int warmupEndTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countdown" ) * 1000 ) + 5000; + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, warmupEndTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, warmupEndTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, warmupEndTime - 1000 ); + + localPlayer->vsMsgState = true; + localPlayer->GUIMainNotice( va( "%s^0 vs. %s^0", thisArena.GetPlayerName( 0 ), thisArena.GetPlayerName( 1 ) ), true ); + } + + if( (thisArena.GetState() == AS_ROUND) && (thisArena.GetState() != previousArena.GetState()) ) { + if( localPlayer && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleTimeAnnouncements(); + } + } + } + + if( ((rvTourneyGameState*)previousGameState)->currentState != currentState ) { + if( currentState == WARMUP ) { + gameLocal.mpGame.tourneyGUI.PreTourney(); + } else if( currentState == COUNTDOWN ) { + if( currentState == COUNTDOWN && ((rvTourneyGameState*)previousGameState)->currentState != INACTIVE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_START, gameLocal.time); + } + } else if( currentState == GAMEON ) { + gameLocal.mpGame.tourneyGUI.TourneyStart(); + } + } + + if( packTourneyHistory ) { + // apply history + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); + packTourneyHistory = false; + } + + if( localPlayer && localPlayer->spectating ) { + + rvTourneyArena& thisArena = arenas[ localPlayer->GetArena() ]; + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL || (localPlayer->spectating && localPlayer->spectator != thisArena.GetPlayers()[ 0 ]->entityNumber && localPlayer->spectator != thisArena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_BRACKET ); + } else if ( thisArena.GetPlayers()[ 0 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 0 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_ONE ); + } else if ( thisArena.GetPlayers()[ 1 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 1 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_TWO ); + } + } + +} + +/* +================ +msgTourney_t +Identifiers for transmitted state +================ +*/ +typedef enum { + MSG_TOURNEY_TOURNEYSTATE, + MSG_TOURNEY_STARTINGROUND, + MSG_TOURNEY_ROUND, + MSG_TOURNEY_MAXROUND, + MSG_TOURNEY_HISTORY, + MSG_TOURNEY_TOURNEYCOUNT, + MSG_TOURNEY_ARENAINFO +} msgTourney_t; + +/* +================ +rvTourneyGameState::PackState +================ +*/ +void rvTourneyGameState::PackState( idBitMsg& outMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( tourneyState != ((rvTourneyGameState*)previousGameState)->tourneyState ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYSTATE ); + outMsg.WriteByte( tourneyState ); + } + + if ( startingRound != ((rvTourneyGameState*)previousGameState)->startingRound ) { + outMsg.WriteByte( MSG_TOURNEY_STARTINGROUND ); + outMsg.WriteByte( startingRound ); + } + + if( round != ((rvTourneyGameState*)previousGameState)->round ) { + outMsg.WriteByte( MSG_TOURNEY_ROUND ); + outMsg.WriteByte( round ); + } + + if( maxRound != ((rvTourneyGameState*)previousGameState)->maxRound ) { + outMsg.WriteByte( MSG_TOURNEY_MAXROUND ); + outMsg.WriteByte( maxRound ); + } + + for ( int i = 0; i < MAX_ARENAS; i++ ) { + if ( arenas[ i ] != ((rvTourneyGameState*)previousGameState)->arenas[ i ] ) { + outMsg.WriteByte( MSG_TOURNEY_ARENAINFO + i ); + arenas[ i ].PackState( outMsg ); + } + } + + if ( packTourneyHistory || forceTourneyHistory ) { + if ( forceTourneyHistory ) { + common->Warning( "forcing down tourney history\n" ); + } + // don't write out uninitialized tourney history + if ( startingRound > 0 && round > 1 ) { + outMsg.WriteByte( MSG_TOURNEY_HISTORY ); + + // client might not yet have startingRound or round + outMsg.WriteByte( startingRound ); + outMsg.WriteChar( round ); + outMsg.WriteByte( maxRound ); + + for( int i = startingRound - 1; i <= Min( (round - 1), (maxRound - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + outMsg.WriteString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerOneScore ) ); + outMsg.WriteString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerTwoScore ) ); + outMsg.WriteChar( tourneyHistory[ i ][ j ].playerOneNum ); + outMsg.WriteChar( tourneyHistory[ i ][ j ].playerTwoNum ); + } + } + packTourneyHistory = false; + } + } + + if ( tourneyCount != ((rvTourneyGameState*)previousGameState)->tourneyCount ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYCOUNT ); + outMsg.WriteByte( tourneyCount ); + } +} + +/* +================ +rvTourneyGameState::UnpackState +================ +*/ +void rvTourneyGameState::UnpackState( const idBitMsg& inMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + switch( index ) { + case MSG_TOURNEY_TOURNEYSTATE: { + tourneyState = (tourneyState_t)inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_STARTINGROUND: { + startingRound = inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_ROUND: { + round = inMsg.ReadByte(); + // not a great way of doing this, should clamp the values + if ( round == 255 ) { + round = -1; + } + break; + } + case MSG_TOURNEY_MAXROUND: { + maxRound = inMsg.ReadByte(); + if ( maxRound == 255 ) { + maxRound = -1; + } + break; + } + case MSG_TOURNEY_HISTORY: { + int startRound = inMsg.ReadByte(); + int rnd = inMsg.ReadChar(); + int maxr = inMsg.ReadByte(); + + assert( rnd >= 1 ); // something is uninitialized + + for( int i = startRound - 1; i <= Min( (rnd - 1), (maxr - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + inMsg.ReadString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneScore = inMsg.ReadChar(); + inMsg.ReadString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoScore = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerOneNum = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerTwoNum = inMsg.ReadChar(); + + if( tourneyHistory[ i ][ j ].playerOneNum < 0 || tourneyHistory[ i ][ j ].playerOneNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerOneNum = -1; + } + + if( tourneyHistory[ i ][ j ].playerTwoNum < 0 || tourneyHistory[ i ][ j ].playerTwoNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // client side (and listen server) no reason to check for history change all the time + packTourneyHistory = true; + break; + } + case MSG_TOURNEY_TOURNEYCOUNT: { + tourneyCount = inMsg.ReadByte(); + break; + } + default: { + if ( index >= MSG_TOURNEY_ARENAINFO && index < MSG_TOURNEY_ARENAINFO + MAX_ARENAS ) { + arenas[ index - MSG_TOURNEY_ARENAINFO ].UnpackState( inMsg ); + } else { + gameLocal.Error( "rvTourneyGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + break; + } + } + + } +} + +/* +================ +rvTourneyGameState::SendState +================ +*/ +void rvTourneyGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvTourneyGameState::GetClassType() ) ); + + if ( clientNum == -1 && (rvTourneyGameState&)(*this) == (rvTourneyGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + if( clientNum == -1 && forceTourneyHistory ) { + forceTourneyHistory = false; + } + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvTourneyGameState::WriteState +=============== +*/ +void rvTourneyGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add Tourney info + PackState( msg ); +} + +/* +================ +rvTourneyGameState::ReceiveState +================ +*/ +void rvTourneyGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvTourneyGameState&)(*previousGameState) = (rvTourneyGameState&)(*this); +} + +/* +================ +rvTourneyGameState::SendInitialState +================ +*/ +void rvTourneyGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvTourneyGameState* previousState = (rvTourneyGameState*)previousGameState; + + rvTourneyGameState invalidState; + + previousGameState = &invalidState; + + + // if the tourney has been going on, transmit the tourney history + if( round > 0 ) { + // comparing the tourney history for all state changes is wasteful when we really just want to send it to new clients + packTourneyHistory = true; + } + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + + +/* +================ +rvTourneyGameState::operator== +================ +*/ +bool rvTourneyGameState::operator==( const rvTourneyGameState& rhs ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + if( round != rhs.round || startingRound != rhs.startingRound || maxRound != rhs.maxRound || tourneyState != rhs.tourneyState ) { + return false; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != rhs.arenas[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvTourneyGameState::operator= +================ +*/ +rvTourneyGameState& rvTourneyGameState::operator=( const rvTourneyGameState& rhs ) { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + (rvGameState&)(*this) = (rvGameState&)rhs; + + round = rhs.round; + startingRound = rhs.startingRound; + maxRound = rhs.maxRound; + tourneyState = rhs.tourneyState; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ] = rhs.arenas[ i ]; + } + + return (*this); +} + +/* +================ +rvTourneyGameState::GetNumArenas +Returns number of active arenas +================ +*/ +int rvTourneyGameState::GetNumArenas( void ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + int num = 0; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + num++; + } + } + + return num; +} + +/* +================ +rvTourneyGameState::SetupInitialBrackets + +Sets up the brackets for a new match. fragCount in playerstate is the player's +persistant frag count over an entire level. In teamFragCount we store this +rounds score. + +Initial bracket seeds are random +================ +*/ +void rvTourneyGameState::SetupInitialBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + idList unseededPlayers; + int numRankedPlayers = gameLocal.mpGame.GetNumRankedPlayers(); + + // all this crazy math does is figure out: + // 8 arenas to 4 rounds ( round 1: 8 arenas, round 2: 4 arenas, round 3: 2 arenas, round 4: 1 arena ) + // 16 arenas to 5 rounds ( round 1: 16 arenas, round 2: 8 arenas, round 3: 4 arenas, round 4: 2 arenas, round 5: 1 arena ) + // etc + int newMaxRound = idMath::ILog2( Max( idMath::CeilPowerOfTwo( MAX_ARENAS * 2 ), 2 ) ); + + // we start at a round appropriate for our # of people + // If you have 1-2 players, start in maxRound, if you have 3-4 players start in maxRound - 1, if you have 5-8 players + // start in maxRound - 2, if you have 9 - 16 players start in maxRound - 3, etc. + int newRound = newMaxRound - idMath::ILog2( Max( idMath::CeilPowerOfTwo( gameLocal.mpGame.GetNumRankedPlayers() ), 2 ) ) + 1; + + round = newRound; + maxRound = newMaxRound; + startingRound = round; + + for( int i = 0; i < numRankedPlayers; i++ ) { + unseededPlayers.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + int numArenas = 0; + + while ( unseededPlayers.Num() > 1 ) { + idPlayer* playerOne = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerOne ); + + idPlayer* playerTwo = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerTwo ); + + rvTourneyArena& arena = arenas[ numArenas ]; + + arena.Clear(); + arena.AddPlayers( playerOne, playerTwo ); + + // place the players in the correct instance + playerOne->JoinInstance( numArenas ); + playerTwo->JoinInstance( numArenas ); + + playerOne->ServerSpectate( false ); + playerTwo->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( playerOne, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( playerTwo, 0 ); + + gameLocal.Printf( "rvTourneyGameState::SetupInitialBrackets() - %s will face %s in arena %d\n", playerOne->GetUserInfo()->GetString( "ui_name" ), playerTwo->GetUserInfo()->GetString( "ui_name" ), numArenas ); + + // this arena is ready to play + arena.Ready(); + + numArenas++; + } + + assert( unseededPlayers.Num() == 0 || unseededPlayers.Num() == 1 ); + + if( unseededPlayers.Num() ) { + assert( unseededPlayers[ 0 ] ); + // the mid player gets a bye + unseededPlayers[ 0 ]->SetTourneyStatus( PTS_UNKNOWN ); + unseededPlayers[ 0 ]->ServerSpectate( true ); + + arenas[ numArenas ].AddPlayers( unseededPlayers[ 0 ], NULL ); + arenas[ numArenas ].Ready(); + + // mark the ancestor arena of the bye player + byeArena = numArenas * startingRound; + } else { + byeArena = -1; + } +} + +/* +================ +rvTourneyGameState::ClientDisconnect +A player has disconnected from the server +================ +*/ +void rvTourneyGameState::ClientDisconnect( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + if( gameLocal.isClient ) { + return; + } + + // go through the tourney history and copy over the disconnecting player's name + if( startingRound > 0 && round <= MAX_ROUNDS ) { + for( int i = startingRound - 1; i < round - 1; i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + if( tourneyHistory[ i ][ j ].playerOneNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneNum = -1; + } else if( tourneyHistory[ i ][ j ].playerTwoNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // copy over the current rounds info if the player is playing + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetPlayers()[ 0 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( player ); + } else if( arenas[ i ].GetPlayers()[ 1 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( player ); + } + } + + // retransmit tourney history to everyone + packTourneyHistory = true; + } + + RemovePlayer( player ); + + // give RemovePlayer() a chance to update tourney history if needed + if( packTourneyHistory ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if( i == player->entityNumber ) { + continue; + } + + if( gameLocal.entities[ i ] ) { + packTourneyHistory = true; + SendState( serverReliableSender.To( i, true ), i ); + } + } + + if ( gameLocal.isRepeater ) { + packTourneyHistory = true; + SendState( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE ); + } + packTourneyHistory = false; + } +} + +/* +================ +rvTourneyGameState::Spectate +================ +*/ +void rvTourneyGameState::Spectate( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + if( player->spectating && player->wantSpectate ) { + RemovePlayer( player ); + } +} + +/* +================ +rvTourneyGameState::RemovePlayer +Removes the specified player from the arena +================ +*/ +void rvTourneyGameState::RemovePlayer( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( round < 1 || round > maxRound ) { + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + idPlayer** players = GetArenaPlayers( i ); + + if( players[ 0 ] == player || players[ 1 ] == player ) { + rvTourneyArena& arena = arenas[ i ]; + + idPlayer* remainingPlayer = players[ 0 ] == player ? players[ 1 ] : players[ 0 ]; + + bool arenaInProgress = arena.GetState() == AS_ROUND || arena.GetState() == AS_WARMUP || arena.GetState() == AS_SUDDEN_DEATH; + bool remainingIsWinner = (remainingPlayer == arena.GetWinner()); + int remainingIndex = (remainingPlayer == arena.GetPlayers()[ 0 ]) ? 0 : 1; + + arena.RemovePlayer( player ); + + // both players have disconnected from this arena, or the winner has disconnected, just return + if( (!arena.GetPlayers()[ 0 ] && !arena.GetPlayers()[ 1 ]) || (!arenaInProgress && remainingPlayer && !remainingIsWinner) ) { + arena.Clear(); + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + return; + } + + assert( remainingPlayer ); + + // if this arena is in progress, try restarting + // ATVI DevTrack #13196 - do not put the bye player into a game with the abandonned player anymore + // if ( arenaInProgress && byeArena >= 0 && arenas[ byeArena / round ].GetWinner() ) { + if ( 0 ) { + idPlayer* byePlayer = arenas[ byeArena / round ].GetWinner(); + + // reset history for this new round + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + + arena.Clear(); + arenas[ byeArena / round ].Clear(); + + arena.AddPlayers( remainingPlayer, byePlayer ); + + // place the players in the correct instance + remainingPlayer->JoinInstance( i ); + byePlayer->JoinInstance( i ); + + remainingPlayer->ServerSpectate( false ); + byePlayer->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( remainingPlayer, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( byePlayer, 0 ); + + gameLocal.Printf( "rvTourneyManager::RemovePlayer() - %s will face %s in arena %d\n", remainingPlayer->GetUserInfo()->GetString( "ui_name" ), byePlayer->GetUserInfo()->GetString( "ui_name" ), i ); + + byeArena = -1; + // this arena is ready to play + arena.Ready(); + } else { + // if the arena was in progress and didn't get a bye player to step in, this becomes a bye + // arena - clear the tourney history + if( arenaInProgress ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = -1; + + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = -1; + } else { + // since the player is disconnecting, write the history for this round + if( remainingIndex == 0 ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } else { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } + } + } + + return; + } + } +} + +int rvTourneyGameState::GetNextActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena + 1; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = 0; i < arena; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +int rvTourneyGameState::GetPrevActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena - 1; i >= 0; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = MAX_ARENAS - 1; i > arena; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +void rvTourneyGameState::SpectateCycleNext( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = players[ 1 ]->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetNextActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + player->spectator = player->entityNumber; + } + } + + // this is where the listen server updates it's gui spectating elements + if ( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::SpectateCyclePrev( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetPrevActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + + rvTourneyArena& newSpectatingArena = arenas[ player->GetArena() ]; + + idPlayer** newPlayers = newSpectatingArena.GetPlayers(); + + if( !newPlayers[ 0 ] || !newPlayers[ 1 ] || newPlayers[ 0 ]->spectating || newPlayers[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + player->spectator = newPlayers[ 1 ]->entityNumber; + } + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = player->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } + + // this is where the listen server updates it gui spectating elements + if( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::UpdateTourneyBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); +} + +void rvTourneyGameState::UpdateTourneyHistory( int round ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( round >= MAX_ROUNDS ) { + assert( false ); + gameLocal.Error( "rvTourneyGameState::UpdateTourneyHistory: MAX_ROUNDS exceeded" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + // sometimes tourney history might have been updated for the current + // round, so don't clobber any data + + if( (tourneyHistory[ round ][ i ].playerOne[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerOneNum != -1) || + (tourneyHistory[ round ][ i ].playerTwo[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerTwoNum != -1) ) { + continue; + } + + tourneyHistory[ round ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneNum = -1; + tourneyHistory[ round ][ i ].playerOneScore = 0; + + tourneyHistory[ round ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoNum = -1; + tourneyHistory[ round ][ i ].playerTwoScore = 0; + + if( arenas[ i ].GetState() == AS_DONE ) { + idPlayer* playerOne = arenas[ i ].GetPlayers()[ 0 ]; + idPlayer* playerTwo = arenas[ i ].GetPlayers()[ 1 ]; + + if( playerOne ) { + tourneyHistory[ round ][ i ].playerOneNum = playerOne->entityNumber; + tourneyHistory[ round ][ i ].playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( playerOne ); + } + + if( playerTwo ) { + tourneyHistory[ round ][ i ].playerTwoNum = playerTwo->entityNumber; + tourneyHistory[ round ][ i ].playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( playerTwo ); + } + } + } +} + + +/* +================ +rvTourneyGameState::FirstAvailableArena +Returns the first non-WARMUP arena available for use in the next round +================ +*/ +int rvTourneyGameState::FirstAvailableArena( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_WARMUP ) { + return i; + } + } + + // no available arenas + assert( 0 ); + return 0; +} + +arenaOutcome_t* rvTourneyGameState::GetArenaOutcome( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( arenas[ arena ].GetState() != AS_DONE || (arenas[ arena ].GetPlayers()[ 0 ] && arenas[ arena ].GetPlayers()[ 1 ]) ) { + return NULL; + } + + return &(tourneyHistory[ round - 1 ][ arena ]); +} + +bool rvTourneyGameState::HasBye( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( player == NULL || player->GetArena() < 0 || player->GetArena() >= MAX_ARENAS ) { + return false; + } + + // if we're one of the players in the arena we're in, and the other player is NULL, we have a bye + if( (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == player || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == player) && + (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == NULL || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == NULL) ) { + return true; + } + + return false; +} + +// simple RTTI +gameStateType_t rvGameState::type = GS_BASE; +gameStateType_t rvDMGameState::type = GS_DM; +gameStateType_t rvTeamDMGameState::type = GS_TEAMDM; +gameStateType_t rvCTFGameState::type = GS_CTF; +gameStateType_t rvTourneyGameState::type = GS_TOURNEY; + +bool rvGameState::IsType( gameStateType_t type ) const { + return ( type == rvGameState::type ); +} + +bool rvDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvDMGameState::type ); +} + +bool rvTeamDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvTeamDMGameState::type ); +} + +bool rvCTFGameState::IsType( gameStateType_t type ) const { + return ( type == rvCTFGameState::type ); +} + +bool rvTourneyGameState::IsType( gameStateType_t type ) const { + return ( type == rvTourneyGameState::type ); +} + +gameStateType_t rvGameState::GetClassType( void ) { + return rvGameState::type; +} + +gameStateType_t rvDMGameState::GetClassType( void ) { + return rvDMGameState::type; +} + +gameStateType_t rvTeamDMGameState::GetClassType( void ) { + return rvTeamDMGameState::type; +} + +gameStateType_t rvCTFGameState::GetClassType( void ) { + return rvCTFGameState::type; +} + +gameStateType_t rvTourneyGameState::GetClassType( void ) { + return rvTourneyGameState::type; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +/* +================ +riDZGameState::riDZGameState +================ +*/ +riDZGameState::riDZGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new riDZGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; + + type = GS_DZ; +} + +/* +================ +riDZGameState::~riDZGameState +================ +*/ +riDZGameState::~riDZGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +riDZGameState::Clear +================ +*/ +void riDZGameState::Clear( void ) { + rvGameState::Clear(); + + if ( previousGameState ) { + riDZGameState* prevState = (riDZGameState*)previousGameState; + prevState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + + dzTriggerEnt = -1; +} + +/* +================ +riDZGameState::SendState +================ +*/ +void riDZGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && type == GS_DZ ); + + if( clientNum == -1 && (riDZGameState&)(*this) == (riDZGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +riDZGameState::WriteState +=============== +*/ +void riDZGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add DZ info + PackState( msg ); +} + +/* +================ +riDZGameState::ReceiveState +================ +*/ +void riDZGameState::ReceiveState( const idBitMsg& msg ) { + assert( type == GS_DZ ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (riDZGameState&)(*previousGameState) = (riDZGameState&)(*this); +} + +/* +================ +riDZGameState::PackState +================ +*/ +void riDZGameState::PackState( idBitMsg& outMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + outMsg.WriteByte( dzStatus[ i ].state ); + outMsg.WriteBits( dzStatus[ i ].clientNum, -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + + outMsg.WriteLong(dzTriggerEnt); + outMsg.WriteLong(dzShaderParm); +} + +/* +================ +riDZGameState::UnpackState +================ +*/ +void riDZGameState::UnpackState( const idBitMsg& inMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = (dzState_t)inMsg.ReadByte(); + dzStatus[ i ].clientNum = inMsg.ReadBits( -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + dzTriggerEnt = inMsg.ReadLong(); + dzShaderParm = inMsg.ReadLong(); +} + +/* +================ +riDZGameState::SendInitialState +================ +*/ +void riDZGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( type == GS_DZ ); + + riDZGameState* previousState = (riDZGameState*)previousGameState; + + riDZGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + + +/* +================ +riDZGameState::ControlZoneStateChanged +================ +*/ +void riDZGameState::ControlZoneStateChanged( int team ) { + if( !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + return; + } + + if ( dzTriggerEnt < 0 ) + return; + + idEntity* ent = gameLocal.FindEntity(dzTriggerEnt); + if ( ent ) { + ent->SetShaderParm(7, dzShaderParm); + } + + dzTriggerEnt = -1; +} + + +/* +================ +riDZGameState::GameStateChanged +================ +*/ +void riDZGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // DZ specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + //gameLocal.Error( "riDZGameState::GameStateChanged() - NULL local player\n" ); + return; + } + + if( currentState == GAMEREVIEW ) + { + // Need to clear the deadzone state at this point. + ((riDZGameState*)previousGameState)->Clear(); + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] == ((riDZGameState*)previousGameState)->dzStatus[ i ] ) { + continue; + } + + ControlZoneStateChanged(i); + } +} + +/* +================ +riDZGameState::Run +================ +*/ +void riDZGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: + { + /// Check if we're frozen (in buy mode, etc.) + if( gameLocal.GetIsFrozen() ) + { + /// Check if it's time for freeze to wear off + int unFreezeTime = gameLocal.GetUnFreezeTime(); + if( gameLocal.time >= unFreezeTime ) + { + gameLocal.SetIsFrozen( false ); + // FIXME: say "fight" + } + } + else + { + /// Check if time limit has been reached + if ( gameLocal.mpGame.TimeLimitHit() ) + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + else + { + /// Teams are tied and time limit was hit - go to sudden death! + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } + } + } + + break; + } + + case SUDDENDEATH: + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + + break; + } + } +} + +/* +================ +riDZGameState::SetDZState +================ +*/ +void riDZGameState::SetDZState( int dz, dzState_t newState ) { + assert( gameLocal.isServer && ( dz >= 0 && dz < TEAM_MAX ) && type == GS_DZ ); + + dzStatus[ dz ].state = newState; +} + +/* +================ +riDZGameState::operator== +================ +*/ +bool riDZGameState::operator==( const riDZGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] != rhs.dzStatus[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +riDZGameState::operator= +================ +*/ +riDZGameState& riDZGameState::operator=( const riDZGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ] = rhs.dzStatus[ i ]; + } + + return (*this); +} + diff --git a/source/mpgame/mp/GameState.h b/source/mpgame/mp/GameState.h new file mode 100644 index 0000000..5cf6221 --- /dev/null +++ b/source/mpgame/mp/GameState.h @@ -0,0 +1,483 @@ +//---------------------------------------------------------------- +// GameState.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAMESTATE_H__ +#define __GAMESTATE_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" +#include "Tourney.h" + +/* +=============================================================================== + +rvGameState + +Game state info common for all gametypes + +=============================================================================== +*/ + +typedef enum { + GS_BASE, + GS_DM, + GS_TEAMDM, + GS_CTF, + GS_TOURNEY, + GS_DZ +} gameStateType_t; + +typedef enum { + INACTIVE = 0, // not running + WARMUP, // warming up + COUNTDOWN, // post warmup pre-game + GAMEON, // game is on + SUDDENDEATH, // game is on but in sudden death, first frag wins + GAMEREVIEW, // game is over, scoreboard is up. we wait si_gameReviewPause seconds (which has a min value) + NEXTGAME, + STATE_COUNT +} mpGameState_t; + +class rvGameState { +public: + + rvGameState( bool allocPrevious = true ); + virtual ~rvGameState( void ); + + // clientNum == -1 except for SendInitialState + // when clientNum >= 0, always send the state + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual bool BaseUnpackState( const idBitMsg& inMsg ); // validates the gametype class + + virtual void GameStateChanged( void ); + + virtual void Run( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + virtual void Clear( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + + mpGameState_t GetMPGameState( void ) { return currentState; } + + mpGameState_t GetNextMPGameState( void ) { return nextState; } + int GetNextMPGameStateTime( void ) { return nextStateTime; } + void SetNextMPGameState( mpGameState_t newState ) { nextState = newState; } + void SetNextMPGameStateTime( int time ) { nextStateTime = time; } + + bool operator==( const rvGameState& rhs ) const; + bool operator!=( const rvGameState& rhs ) const; + rvGameState& operator=( const rvGameState& rhs ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile *file, int clientNum ); + + void SpawnDeadZonePowerup(); +protected: + static gameStateType_t type; + rvGameState* previousGameState; + bool trackPrevious; + + mpGameState_t currentState; + mpGameState_t nextState; + int nextStateTime; + + int fragLimitTimeout; +}; + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +class rvDMGameState : public rvGameState { +public: + rvDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +class rvTeamDMGameState : public rvGameState { +public: + rvTeamDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +// assault point state for CTF +enum apState_t { + AS_MARINE, + AS_STROGG, + AS_NEUTRAL +}; + +// current flag state for CTF +enum flagState_t { + FS_AT_BASE = 0, + FS_DROPPED, + FS_TAKEN, + // for one flag CTF + FS_TAKEN_STROGG, // taken by strogg team + FS_TAKEN_MARINE // taken by marine team +}; + +struct flagStatus_t { + flagState_t state; + int clientNum; +}; + +class rvCTFGameState : public rvGameState { +public: + rvCTFGameState( bool allocPrevious = true ); + virtual void Clear( void ); + + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetAPOwner( int ap, int owner ); + int GetAPOwner( int ap ); + + void SetFlagState( int flag, flagState_t newState ); + flagState_t GetFlagState( int flag ); + int GetFlagCarrier( int flag ); + + void SetFlagCarrier( int flag, int clientNum ); + + bool operator==( const rvCTFGameState& rhs ) const; + rvCTFGameState& operator=( const rvCTFGameState& rhs ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + flagStatus_t flagStatus[ TEAM_MAX ]; + apState_t apState[ MAX_AP ]; + + static gameStateType_t type; +}; + +ID_INLINE void rvCTFGameState::SetAPOwner( int ap, int owner ) { + assert( (ap >= 0 && ap < MAX_AP) && (owner >= 0 && owner < TEAM_MAX) ); + + apState[ ap ] = (apState_t)owner; +} + +ID_INLINE int rvCTFGameState::GetAPOwner( int ap ) { + return apState[ ap ]; +} + +ID_INLINE flagState_t rvCTFGameState::GetFlagState( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].state; +} + +ID_INLINE int rvCTFGameState::GetFlagCarrier( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].clientNum; +} + +ID_INLINE bool operator==( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +enum tourneyState_t { + TS_INVALID = 0, + TS_PREMATCH, + TS_MATCH, + TS_END_GAME +}; + +class rvTourneyGameState : public rvGameState { +public: + rvTourneyGameState( bool allocPrevious = true ); + + virtual void Clear( void ); + + virtual void Run( void ); + + virtual void Reset( void ); + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + void RemovePlayer( idPlayer* player ); + + bool operator==( const rvTourneyGameState& rhs ) const; + rvTourneyGameState& operator=( const rvTourneyGameState& rhs ); + + int GetNumArenas( void ) const; + bool HasBye( idPlayer* player ); + + int GetMaxRound( void ) const; + int GetRound( void ) const; + int GetStartingRound( void ) const; + + idPlayer** GetArenaPlayers( int arena ); + + rvTourneyArena& GetArena( int arena ); + + const char* GetRoundDescription( void ); + int GetNextActiveArena( int arena ); + int GetPrevActiveArena( int arena ); + + void SpectateCycleNext( idPlayer* player ); + void SpectateCyclePrev( idPlayer* player ); + int GetTourneyCount( void ); + void SetTourneyCount( int count ) { tourneyCount = count; } + + void UpdateTourneyBrackets( void ); + + void UpdateTourneyHistory( int round ); + int FirstAvailableArena( void ); + + arenaOutcome_t* GetArenaOutcome( int arena ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + void SetupInitialBrackets( void ); + + tourneyState_t tourneyState; + + // startingRound - The starting round, with the current # of players + int startingRound; + // round - current round ( 1-indexed ) + int round; + // maxRound - the most rounds we'll run (based on MAX_ARENAS) + int maxRound; + // arenas - current brackets, an extra arena to serve as the waiting room + rvTourneyArena arenas[ MAX_ARENAS + 1 ]; + // tourneyHistory - past winners + arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ]; + // byeArena - the arena that natural (non disconnection) bye players go in + int byeArena; + // packTourneyHistory - whether or not we should transmit tourney history to one client through SendState + bool packTourneyHistory; + // forceTourneyHistory - if true will sync down tourney history for all clients + bool forceTourneyHistory; + // tourneyCount - how many tourneys to run per map + int tourneyCount; + // roundTimeout - a delay between all arenas finishing and starting the next round + int roundTimeout; + + static gameStateType_t type; +}; + +ID_INLINE int rvTourneyGameState::GetMaxRound( void ) const { + assert( type == GS_TOURNEY ); + return maxRound; +} + +ID_INLINE int rvTourneyGameState::GetRound( void ) const { + assert( type == GS_TOURNEY ); + return round; +} + +ID_INLINE int rvTourneyGameState::GetStartingRound( void ) const { + assert( type == GS_TOURNEY ); + return startingRound; +} + +ID_INLINE idPlayer** rvTourneyGameState::GetArenaPlayers( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ].GetPlayers(); +} + +ID_INLINE rvTourneyArena& rvTourneyGameState::GetArena( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ]; +} + +ID_INLINE const char* rvTourneyGameState::GetRoundDescription( void ) { + assert( type == GS_TOURNEY ); + + if( round == maxRound ) { + return common->GetLocalizedString( "#str_107720" ); + } else if( round == maxRound - 1 ) { + return common->GetLocalizedString( "#str_107719" ); + } else if( round == maxRound - 2 ) { + return common->GetLocalizedString( "#str_107718" ); + } else if( round == maxRound - 3 ) { + return common->GetLocalizedString( "#str_107717" ); + } else { + // shouldn't happen in production + return va( "ROUND %d", round ); + } +} + +ID_INLINE int rvTourneyGameState::GetTourneyCount( void ) { + assert( type == GS_TOURNEY ); + return tourneyCount; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +// current control zone state +enum dzState_t { + DZ_NONE = 0, + DZ_TAKEN, + DZ_LOST, + DZ_DEADLOCK, + DZ_MARINES_TAKEN, + DZ_MARINES_LOST, + DZ_STROGG_TAKEN, + DZ_STROGG_LOST, + DZ_MARINE_TO_STROGG, + DZ_STROGG_TO_MARINE, + DZ_MARINE_DEADLOCK, + DZ_STROGG_DEADLOCK, + DZ_MARINE_REGAIN, + DZ_STROGG_REGAIN +}; + +struct dzStatus_t { + dzState_t state; + int clientNum; +}; + +class riDZGameState : public rvGameState { +public: + riDZGameState( bool allocPrevious = true ); + ~riDZGameState( void ); + virtual void Clear( void ); + + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetDZState( int dz, dzState_t newState ); + dzState_t GetDZState( int dz ); + + bool operator==( const riDZGameState& rhs ) const; + riDZGameState& operator=( const riDZGameState& rhs ); + + int dzTriggerEnt; + int dzShaderParm; + +private: + dzStatus_t dzStatus[ TEAM_MAX ]; + + void ControlZoneStateChanged( int team ); +}; + + +ID_INLINE dzState_t riDZGameState::GetDZState( int dz ) { + assert( dz >= 0 && dz < TEAM_MAX ); + + return dzStatus[ dz ].state; +} + +ID_INLINE bool operator==( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} +#endif diff --git a/source/mpgame/mp/Tourney.cpp b/source/mpgame/mp/Tourney.cpp new file mode 100644 index 0000000..548ec31 --- /dev/null +++ b/source/mpgame/mp/Tourney.cpp @@ -0,0 +1,1021 @@ +//---------------------------------------------------------------- +// Tourney.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Tourney.h" + + +/* +=============================================================================== + +rvTourneyArena + +=============================================================================== +*/ + +/* +================ +rvTourneyArena::rvTourneyArena +================ +*/ +rvTourneyArena::rvTourneyArena() { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; +} + +/* +================ +rvTourneyArena::AddPlayer +================ +*/ +void rvTourneyArena::AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ) { + players[ 0 ] = playerOne; + players[ 1 ] = playerTwo; + if( playerOne ) { + playerOne->SetTourneyStatus( PTS_PLAYING ); + } + + if( playerTwo ) { + playerTwo->SetTourneyStatus( PTS_PLAYING ); + } +} + +/* +================ +rvTourneyArena::ClearPlayers +Clears player references if clearPlayer is NULL (client-side) +Clears specific player if clearPlayer is not NULL (server-side) +================ +*/ +void rvTourneyArena::ClearPlayers( idPlayer* clearPlayer /* = NULL */ ) { + if( gameLocal.isServer ) { + if( clearPlayer ) { + assert( clearPlayer == players[ 0 ] || clearPlayer == players[ 1 ] ); + if( clearPlayer == players[ 0 ] ) { + players[ 0 ] = NULL; + } else { + players[ 1 ] = NULL; + } + } + return; + } else { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + } +} + +/* +================ +rvTourneyArena::Ready +================ +*/ +void rvTourneyArena::Ready( void ) { + arenaState = AS_WARMUP; + nextStateTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countDown" ) * 1000 ) + 5000; + + matchStartTime = 0; + + if( !players[ 0 ] || !players[ 1 ] ) { + // if we don't have both players available, bye this arena + NewState( AS_DONE ); + } +} + +/* +================ +rvTourneyArena::Clear +Clears player list and state, but not round # or arena ID +================ +*/ +void rvTourneyArena::Clear( bool respawnPlayers ) { + if( gameLocal.isServer && respawnPlayers ) { + if( players[ 0 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 0 ]->ServerSpectate( true ); + } + + if( players[ 1 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 1 ]->ServerSpectate( true ); + } + + // This arena is being cleared so we must also clear out any spectators + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + } + + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; + SetState( AS_INACTIVE ); +} + +/* +================ +rvTourneyArena::GetLeader +Returns winning player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLeader( void ) { + if( players[ 0 ] == NULL || players[ 1 ] == NULL ) { + if( players[ 0 ] ) { + return players[ 0 ]; + } else if( players[ 1 ] ) { + return players[ 1 ]; + } + + return NULL; + } + + int playerOneScore = gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ); + int playerTwoScore = gameLocal.mpGame.GetTeamScore( players[ 1 ]->entityNumber ); + + if ( playerOneScore == playerTwoScore ) { + return NULL; + } + + return ( playerOneScore > playerTwoScore ? players[ 0 ] : players[ 1 ] ); +} + +/* +================ +rvTourneyArena::GetLoser +Returns losing player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLoser( void ) { + if( winner == NULL ) { + return NULL; + } else { + return ( winner == players[ 0 ] ? players[ 1 ] : players[ 0 ] ); + } +} + +/* +================ +rvTourneyArena::UpdateState +Updates this arena's state +================ +*/ +void rvTourneyArena::UpdateState( void ) { + if( (players[ 0 ] == NULL || players[ 1 ] == NULL) && arenaState != AS_DONE ) { + gameLocal.Warning( "rvTourneyArena::UpdateState() - UpdateState() called on non-full and non-done arena\n" ); + NewState( AS_DONE ); + return; + } + + switch( arenaState ) { + case AS_DONE: { + // not both players will neccesarily be valid here (if a player wins the arena then his opponent disconnects) + + break; + } + case AS_WARMUP: { + if( gameLocal.time > nextStateTime ) { + SetState( AS_ROUND ); + + // allow damage in warmup + //players[ 0 ]->fl.takedamage = true; + //players[ 1 ]->fl.takedamage = true; + // respawn the players + //players[ 0 ]->ServerSpectate( false ); + //players[ 1 ]->ServerSpectate( false ); + // respawn items + gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart( arenaID ); + + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + matchStartTime = gameLocal.time; + } + + break; + } + + case AS_ROUND: { + if( GetLeader() && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetTeamScore( GetLeader()->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + if( fragLimitTimeout && fragLimitTimeout < gameLocal.time ) { + NewState( AS_DONE ); + } else if ( !fragLimitTimeout ) { + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + } else if( fragLimitTimeout ) { + if( !GetLeader() && gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + // players tied at fraglimit + NewState( AS_SUDDEN_DEATH ); + } else { + // player hit fraglimit, but then killed himself and dropped below fraglimit, return to normal play + fragLimitTimeout = 0; + } + + //back to normal play? + } else if ( TimeLimitHit() ) { + // only send to clients in this arena, the times may be getting out of sync between the arenas during a round ( #13196 ) + int i; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if ( !player ) { + continue; + } + if ( player->GetArena() == arenaID ) { + gameLocal.mpGame.PrintMessageEvent( i, MSG_TIMELIMIT ); + } + } + if( GetLeader() == NULL ) { + // if tied at timelimit hit, goto sudden death + NewState( AS_SUDDEN_DEATH ); + } else { + // or just end the game + NewState( AS_DONE ); + } + } + break; + } + case AS_SUDDEN_DEATH: { + if ( GetLeader() ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", GetLeader()->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( AS_DONE ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, GetLeader()->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + } +} + +/* +================ +rvTourneyArena::NewState +================ +*/ +void rvTourneyArena::NewState( arenaState_t newState ) { + switch( newState ) { + case AS_DONE: { + winner = GetLeader(); + assert( winner ); + gameLocal.Printf( "rvTourneyArena::UpdateState() - %s has won this arena\n", GetLeader()->GetUserInfo()->GetString( "ui_name" ) ); + + winner->SetTourneyStatus( PTS_ADVANCED ); + + if( GetLoser() ) { + GetLoser()->SetTourneyStatus( PTS_ELIMINATED ); + } + + + if( players[ 0 ] ) { + players[ 0 ]->ServerSpectate( true ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + if( players[ 1 ] ) { + players[ 1 ]->ServerSpectate( true ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + // when we're done, put anyone who was spectating into another arena + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + + matchStartTime = 0; + break; + } + case AS_SUDDEN_DEATH: { + fragLimitTimeout = 0; + + // respawn the arena + players[ 0 ]->ServerSpectate( false ); + players[ 1 ]->ServerSpectate( false ); + gameLocal.LocalMapRestart( arenaID ); + break; + } + default: { + gameLocal.Error( "rvTourneyArena::NewState() - Unknown state '%d'\n", newState ); + return; + } + } + SetState( newState ); +} + +/* +================ +rvTourneyArena::RemovePlayer +The specified client is being removed from the server +================ +*/ +void rvTourneyArena::RemovePlayer( idPlayer* player ) { + // when we call Clear() the arena will clean up (set player instances to 0, spectate them, etc) + // we don't want it to do this for the disconnecting player, since he may not be entirely valid + // anymore. If we NULL out the disconnecting player, Clear() will properly reset the remaining player + // but will leave the volatile disconnecting player's data lone. + + bool playerInArena = false; + if( player == players[ 0 ] ) { + players[ 0 ] = NULL; + playerInArena = true; + } + + if( player == players[ 1 ] ) { + players[ 1 ] = NULL; + playerInArena = true; + } + + if( player == winner ) { + winner = NULL; + } + + if( !playerInArena ) { + // occasionally happens if a client has dropped ( the bystander maybe ) + common->Warning( "rvTourneyArena::RemovePlayer() - Called on player who is not in arena '%d'\n", arenaID ); + } +} + +/* +================ +rvTourneyArena::PackState +================ +*/ +void rvTourneyArena::PackState( idBitMsg& outMsg ) { + if ( players[ 0 ] ) { + outMsg.WriteChar( players[ 0 ]->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + if ( players[ 1 ] ) { + outMsg.WriteChar( players[ 1 ]->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + if ( winner ) { + outMsg.WriteChar( winner->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + outMsg.WriteByte( arenaState ); + outMsg.WriteLong( nextStateTime ); + outMsg.WriteLong( fragLimitTimeout ); + outMsg.WriteLong( matchStartTime ); +} + +/* +================ +rvTourneyArena::UnpackState +================ +*/ +void rvTourneyArena::UnpackState( const idBitMsg& inMsg ) { + int playerOneNum = inMsg.ReadChar(); + int playerTwoNum = inMsg.ReadChar(); + int winnerNum = inMsg.ReadChar(); + + if ( playerOneNum >= 0 && playerOneNum < MAX_CLIENTS ) { + players[ 0 ] = (idPlayer*)gameLocal.entities[ playerOneNum ]; + } else { + players[ 0 ] = NULL; + } + + if ( playerTwoNum >= 0 && playerTwoNum < MAX_CLIENTS ) { + players[ 1 ] = (idPlayer*)gameLocal.entities[ playerTwoNum ]; + } else { + players[ 1 ] = NULL; + } + + if ( winnerNum >= 0 && winnerNum < MAX_CLIENTS ) { + winner = (idPlayer*)gameLocal.entities[ winnerNum ]; + } else { + winner = NULL; + } + + arenaState = (arenaState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + fragLimitTimeout = inMsg.ReadLong(); + matchStartTime = inMsg.ReadLong(); +} + +/* +================ +rvTourneyArena::SetState +Set's this arena's state - client side only based on UpdateState() results from server +================ +*/ +void rvTourneyArena::SetState( arenaState_t newState ) { + arenaState = newState; +} + +/* +================ +rvTourneyArena::SetNextStateTime +================ +*/ +void rvTourneyArena::SetNextStateTime( int time ) { + nextStateTime = time; +} + +/* +================ +rvTourneyArena::GetNextStateTime +================ +*/ +int rvTourneyArena::GetNextStateTime( void ) { + return nextStateTime; +} + + +/* +================ +rvTourneyArena::GetState +================ +*/ +arenaState_t rvTourneyArena::GetState( void ) const { + return arenaState; +} + +/* +================ +rvTourneyArena::GetPlayerName +================ +*/ +const char* rvTourneyArena::GetPlayerName( int player ) { + assert( player >= 0 && player < 2 ); + + if( players[ player ] ) { + return players[ player ]->GetUserInfo()->GetString( "ui_name" ); + } else { + return NULL; + } +} + +/* +================ +rvTourneyArena::GetPlayerScore +================ +*/ +int rvTourneyArena::GetPlayerScore( int player ) { + assert( player >= 0 && player < 2 ); + + + if( players[ player ] ) { + return gameLocal.mpGame.GetTeamScore( players[ player ] ); + } else { + return 0; + } +} + +/* +================ +rvTourneyArena::TimeLimitHit +================ +*/ +bool rvTourneyArena::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +=============================================================================== + +rvTourneyGUI + +=============================================================================== +*/ + +rvTourneyGUI::rvTourneyGUI() { + tourneyGUI = NULL; + tourneyScoreboard = NULL; +} + +void rvTourneyGUI::SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ) { + tourneyGUI = newTourneyGUI; + tourneyScoreboard = newTourneyScoreboard; +} + +void rvTourneyGUI::RoundStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "roundTransitionIn" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "roundTransitionIn" ); + + // set bye player for new round - the actual byePlayer may not have changed, so the gamestate won't move him over +// UpdateByePlayer(); +} + +void rvTourneyGUI::ArenaStart( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyGUI" ); + return; + } + + //arenaInit sets names to white and scores to orange. needs values "gui::round" , "gui::bracket" and "gui::empty" + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( arena ); + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyGUI->SetStateBool( "empty", true ); + } else { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +} + +void rvTourneyGUI::ArenaDone( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& tourneyArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + if( tourneyArena.GetPlayers()[ 0 ] == NULL || tourneyArena.GetPlayers()[ 1 ] == NULL ) { + // we don't transition bye arenas to done + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + idPlayer* winner = tourneyArena.GetWinner(); + idPlayer** players = tourneyArena.GetPlayers(); + + if( winner == NULL ) { + common->Error( "rvTourneyGUI::ArenaDone() - Called on arena '%d' which is not done!\n", arena ); + return; + } + + if( winner != players[ 0 ] && winner != players[ 1 ] ) { + common->Error( "rvTourneyGUI::ArenaDone() - Arena '%d' is reporting a winner that is not in the arena!\n", arena ); + return; + } + + tourneyGUI->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaDone" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyScoreboard" ); + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + tourneyScoreboard->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); +} + +void rvTourneyGUI::ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& thisArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + // don't select empty arenas + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL ) { + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + tourneyGUI->SetStateInt( "sel", (int)highlightType ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaFocus" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyScoreboard" ); + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + tourneyScoreboard->SetStateInt( "sel", (int)highlightType ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaFocus" ); +} + +void rvTourneyGUI::UpdateScores( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + if( round < 0 ) { + // not now + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyGUI->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyGUI->SetStateBool( "empty", false ); + } + } + + tourneyGUI->StateChanged( gameLocal.time ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyScoreboard->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyScoreboard->SetStateBool( "empty", false ); + } + } + + tourneyScoreboard->StateChanged( gameLocal.time ); +} + +void rvTourneyGUI::PreTourney( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::PreTourney() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->HandleNamedEvent( "warmupTransitionIn" ); +} + +void rvTourneyGUI::TourneyStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + int maxRound = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetMaxRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "warmupTransitionOut" ); + + // setup and clear the scoreboard + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 1; i <= maxRound; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyScoreboard->SetStateInt( "round", i ); + tourneyScoreboard->SetStateInt( "bracket", j + 1 ); + + if( i < round ) { + // we aren't using these brackets + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } else if ( i == round ) { + // this is our initial round + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( j ); + + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + } else { + // this is our future bracket + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + } + } + + // we might have overwritten a bye player, so update it again +// UpdateByePlayer(); +} + +/*void rvTourneyGUI::UpdateByePlayer( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyGUI" ); + return; + } + + int arena; + for( arena = 0; arena < MAX_ARENAS; arena++ ) { + if( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ).GetState() == AS_INACTIVE ) { + break; + } + } + + if( arena >= MAX_ARENAS ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Bye player with no inactive arenas!" ); + return; + } + + idPlayer* byePlayer = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetByePlayer(); + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyGUI->SetStateBool( "empty", true ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +}*/ + +void rvTourneyGUI::SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ) { + if ( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::SetupTourneyHistory() - Invalid tourneyScoreboard" ); + return; + } + + if( startHistory <= 0 ) { + return; + } + + for ( int round = startHistory; round <= endHistory; round++ ) { + tourneyScoreboard->SetStateInt( "round", round ); + + for ( int arena = 0; arena < MAX_ARENAS / round; arena++ ) { + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + // whether we want to send arenaDone to this arena + // don't send for empty brackets or bye rounds + bool arenaNotDone = tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne) && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo); + + if( arenaNotDone ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + + bool firstSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne)); + bool secondSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo)); + + assert( !(firstSlotBye && secondSlotBye) ); + + if( firstSlotBye || secondSlotBye ) { + // this was a bye round + + if( secondSlotBye ) { + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + } + + arenaNotDone = true; + } else { + // regular round + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOneScore ); + + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwoScore ); + + tourneyScoreboard->SetStateInt( "winner", tourneyHistory[ round - 1 ][ arena ].playerOneScore > tourneyHistory[ round - 1 ][ arena ].playerTwoScore ? 1 : 2 ); + } + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + if( !arenaNotDone ) { + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); + } + } + } + + UpdateScores(); +} diff --git a/source/mpgame/mp/Tourney.h b/source/mpgame/mp/Tourney.h new file mode 100644 index 0000000..7ef571d --- /dev/null +++ b/source/mpgame/mp/Tourney.h @@ -0,0 +1,156 @@ +//---------------------------------------------------------------- +// Tourney.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __TOURNEY_H__ +#define __TOURNEY_H__ + +#include "../Game_local.h" + +enum arenaState_t { + AS_INACTIVE = 0, + AS_WARMUP, + AS_ROUND, + AS_SUDDEN_DEATH, + AS_DONE +}; + +#define MAX_TOURNEY_HISTORY_NAME_LEN 32 + +typedef struct arenaOutcome_s { + // for clients that have disconnected, copy their names for history purposes + char playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + char playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + + // for currently connected clients, use clientnum to get current name + int playerOneNum; + int playerTwoNum; + + int playerOneScore; + int playerTwoScore; +} arenaOutcome_t; + +// shouldn't exceed MAX_INSTANCES from idMultiplayerGame +const int MAX_ARENAS = 8; + +const int MAX_ROUNDS = 4; + +class rvTourneyArena { +public: + rvTourneyArena(); + + void AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ); + void ClearPlayers( idPlayer* clearPlayer = NULL ); + void Clear( bool respawnPlayers = true ); + void Ready( void ); + + idPlayer* GetLeader( void ); + idPlayer* GetLoser( void ); + idPlayer* GetWinner( void ) { return winner; } + void UpdateState( void ); + void NewState( arenaState_t newState ); + + idPlayer** GetPlayers( void ); + + void SetArenaID( int id ); + int GetArenaID( void ) const; + + arenaState_t GetState( void ) const; + void SetState( arenaState_t newState ); + void SetNextStateTime( int time ); + int GetNextStateTime( void ); + int GetMatchStartTime( void ) { return matchStartTime; } + + void PackState( idBitMsg& outMsg ); + void UnpackState( const idBitMsg& inMsg ); + + void RemovePlayer( idPlayer* player ); + bool TimeLimitHit( void ); + bool IsPlaying( idPlayer* player ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE && ( player == players[ 0 ] || player == players[ 1 ] ) ); } + bool HasPlayer( idPlayer* player ) { return ( player == players[0] || player == players[1] ); } + bool IsPlaying( void ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE ); } + + const char* GetPlayerName( int player ); + int GetPlayerScore( int player ); + int GetFraglimitTimeout( void ) { return fragLimitTimeout; } + bool operator==( const rvTourneyArena& rhs ) const; + bool operator!=( const rvTourneyArena& rhs ) const; + rvTourneyArena& operator=( const rvTourneyArena& rhs ); + +private: + // players - players in arena + idPlayer* players[ 2 ]; + // arenaID - this arena's ID + int arenaID; + // arenaState - state of the arena + arenaState_t arenaState; + // nextStateTime - transition time to next state + int nextStateTime; + // winner - the winner of the arena + idPlayer* winner; + // fragLimitTimeout - timeout to let death anims play + int fragLimitTimeout; + // matchStartTime - time arena started + int matchStartTime; +}; + +ID_INLINE idPlayer** rvTourneyArena::GetPlayers( void ) { + return players; +} + +ID_INLINE void rvTourneyArena::SetArenaID( int id ) { + arenaID = id; +} + +ID_INLINE int rvTourneyArena::GetArenaID( void ) const { + return arenaID; +} + +ID_INLINE bool rvTourneyArena::operator==( const rvTourneyArena& rhs ) const { + return ( arenaState == rhs.arenaState && players[ 0 ] == rhs.players[ 0 ] && players[ 1 ] == rhs.players[ 1 ] && nextStateTime == rhs.nextStateTime && arenaID == rhs.arenaID && fragLimitTimeout == rhs.fragLimitTimeout && matchStartTime == rhs.matchStartTime ); +} + +ID_INLINE bool rvTourneyArena::operator!=( const rvTourneyArena& rhs ) const { + return ( arenaState != rhs.arenaState || players[ 0 ] != rhs.players[ 0 ] || players[ 1 ] != rhs.players[ 1 ] || nextStateTime != rhs.nextStateTime || arenaID != rhs.arenaID || fragLimitTimeout != rhs.fragLimitTimeout || matchStartTime != rhs.matchStartTime ); +} + +ID_INLINE rvTourneyArena& rvTourneyArena::operator=( const rvTourneyArena& rhs ) { + players[ 0 ] = rhs.players[ 0 ]; + players[ 1 ] = rhs.players[ 1 ]; + arenaState = rhs.arenaState; + nextStateTime = rhs.nextStateTime; + arenaID = rhs.arenaID; + fragLimitTimeout = rhs.fragLimitTimeout; + matchStartTime = rhs.matchStartTime; + return (*this); +} + +typedef enum { + TGH_BRACKET, + TGH_PLAYER_ONE, + TGH_PLAYER_TWO +} tourneyGUIHighlight_t; + +class rvTourneyGUI { +public: + rvTourneyGUI(); + void SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ); + + void RoundStart( void ); + void ArenaStart( int arena ); + void ArenaDone( int arena ); + void ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ); + void UpdateScores( void ); + void PreTourney( void ); + void TourneyStart( void ); + //void UpdateByePlayer( void ); + void SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ); + +private: + idUserInterface* tourneyGUI; + idUserInterface* tourneyScoreboard; +}; + +#endif diff --git a/source/mpgame/mp/VoiceComms.cpp b/source/mpgame/mp/VoiceComms.cpp new file mode 100644 index 0000000..41565f1 --- /dev/null +++ b/source/mpgame/mp/VoiceComms.cpp @@ -0,0 +1,196 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop +#include "../Game_local.h" + +// This equates to about 1 second +#define MAX_VOICE_PACKET_SIZE 382 + +idCVar si_voiceChat( "si_voiceChat", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_INFO, "enables or disables voice chat through the server" ); +idCVar g_voiceChatDebug( "g_voiceChatDebug", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "display info on voicechat net traffic" ); + +void idMultiplayerGame::ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + + idBitMsg outMsg; + int i; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 2]; + idPlayer * from; + + from = ( idPlayer * )gameLocal.entities[clientNum]; + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) || !from ) { + return; + } + + // Create a new packet with forwarded data + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteData( inMsg.GetReadData(), inMsg.GetRemainingData() ); + + if( g_voiceChatDebug.GetInteger() & 2 ) { + common->Printf( "VC: Received %d bytes, forwarding...\n", inMsg.GetRemainingData() ); + } + + // Forward to appropriate parties + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* to = ( idPlayer * )gameLocal.entities[i]; + if( to && to->GetUserInfo() && to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( i != gameLocal.localClientNum && CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to client %d\n", to->entityNumber ); + } + } + else + { + if( g_voiceChatDebug.GetInteger() ) + { + common->Printf( " ... suppressed packet to client %d\n", to->entityNumber ); + } + } + } + } + } + } + +#ifdef _USE_VOICECHAT + // Listen servers need to manually call the receive function + if ( gameLocal.isListenServer ) { + // Skip over control byte + outMsg.ReadByte(); + + idPlayer* to = gameLocal.GetLocalPlayer(); + if( to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + ReceiveAndPlayVoiceData( outMsg ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to local client %d\n", gameLocal.localClientNum ); + } + ReceiveAndPlayVoiceData( outMsg ); + } + } + } + } + } +#endif +} + +bool idMultiplayerGame::CanTalk( idPlayer *from, idPlayer *to, bool echo ) { + if ( !to ) { + return false; + } + + if ( !from ) { + return false; + } + + if ( from->spectating != to->spectating ) { + return false; + } + + if ( to->IsPlayerMuted( from ) ) { + return false; + } + + if ( to->GetArena() != from->GetArena() ) { + return false; + } + + if ( gameLocal.IsTeamGame() && from->team != to->team ) { + return false; + } + + if ( from == to ) { + return echo; + } + + return true; +} + +#ifdef _USE_VOICECHAT + +void idMultiplayerGame::XmitVoiceData( void ) +{ + idBitMsg outMsg; + int count; + byte work; + byte buffer[MAX_VOICE_PACKET_SIZE]; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 1]; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + + // Grab any new input and send up to the server + count = soundSystem->GetVoiceData( buffer, MAX_VOICE_PACKET_SIZE ); + if( count ) + { + outMsg.BeginWriting(); + outMsg.BeginReading(); + + work = GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT + cvarSystem->GetCVarBool( "s_voiceChatEcho" ) + ( cvarSystem->GetCVarInteger( "s_voiceChatTest" ) * 2 ); + outMsg.WriteByte( work ); + outMsg.WriteData( buffer, count ); + + // FIXME!!! FIXME!!! This should be unreliable - this is very bad + networkSystem->ClientSendReliableMessage( outMsg ); + + if( g_voiceChatDebug.GetInteger() & 1 ) + { + common->Printf( "VC: sent %d bytes at %d\n", count, gameLocal.time ); + } + } +} + +void idMultiplayerGame::ReceiveAndPlayVoiceData( const idBitMsg &inMsg ) +{ + int clientNum; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + clientNum = inMsg.ReadByte(); + soundSystem->PlayVoiceData( clientNum, inMsg.GetReadData(), inMsg.GetRemainingData() ); + if( g_voiceChatDebug.GetInteger() & 4 ) + { + common->Printf( "VC: Playing %d bytes\n", inMsg.GetRemainingData() ); + } +} + +#endif + +// end diff --git a/source/mpgame/mp/stats/StatEvent.cpp b/source/mpgame/mp/stats/StatEvent.cpp new file mode 100644 index 0000000..867bc83 --- /dev/null +++ b/source/mpgame/mp/stats/StatEvent.cpp @@ -0,0 +1,331 @@ +//---------------------------------------------------------------- +// StatEvent.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + + +rvStatHit::rvStatHit( int t, int p, int v, int w, bool countForAccuracy ) : rvStat( t ) { + playerClientNum = p; + victimClientNum = v; + weapon = w; + trackAccuracy = countForAccuracy; + type = ST_HIT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatHit::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS && weapon >= 0 && weapon < MAX_WEAPONS && trackAccuracy ) { + stats[ playerClientNum ].weaponHits[ weapon ]++; + } + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_railgun" ) ) { + + // Apparently it does the rail hit event before the rail fired event, so this is backwards for a reason + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_HIT ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_RAIL_HIT; + } + + if ( rvStatManager::lastRailShot[ playerClientNum ] < stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } else { + if ( rvStatManager::lastRailShot[ playerClientNum ] >= stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + if ( (rvStatManager::lastRailShotHits[ playerClientNum ] % 2) == 0 ) { + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + ++rvStatManager::lastRailShotHits[ playerClientNum ]; + } else { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } + } + +/* + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 8000, 2, (rvStat**)lastHits ); + if( lastHits[0] ) { + if( !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[0])->weapon ) ), "weapon_railgun" ) ) { + + //Check to make sure we don't chain impressive awards. + if(lastHits[1] + && !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[1])->weapon ) ), "weapon_railgun" ) + && (lastHits[1]->GetTimeStamp() - lastHits[0]->GetTimeStamp()) < 4000) { + return; + + } + + if(gameLocal.time - lastHits[0]->GetTimeStamp() < 4000){ + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + } + } +*/ + } else if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_rocketlauncher" ) ) { + + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_FIRED ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_ROCKET_HIT; + } + + } +} + +rvStatKill::rvStatKill( int t, int p, int v, bool g, int mod ) : rvStat( t ) { + type = ST_KILL; + playerClientNum = p; + victimClientNum = v; + gibbed = g; + methodOfDeath = mod; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatKill::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum < 0 || playerClientNum >= MAX_CLIENTS || victimClientNum < 0 || victimClientNum >= MAX_CLIENTS ) { + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + idPlayer* victim = (idPlayer*)gameLocal.entities[ victimClientNum ]; + + // no award processing for suicides + if( victimClientNum == playerClientNum ) { + stats[ playerClientNum ].suicides++; + return; + } + + bool teamKill = false; + + // don't track team kills + if( !gameLocal.IsTeamGame() || player->team != victim->team ) { + stats[ playerClientNum ].kills++; + + if( methodOfDeath >= 0 && methodOfDeath < MAX_WEAPONS ) { + stats[ playerClientNum ].weaponKills[ methodOfDeath ]++; + } + } + + if( gameLocal.IsTeamGame() && player->team == victim->team ) { + teamKill = true; + } + + + // check for humiliation award + if( !teamKill && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_gauntlet" ) ) { + statManager->GiveInGameAward( IGA_HUMILIATION, playerClientNum ); + } + + rvStatKill* lastKills[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_KILL, gameLocal.time - 5000, 2, (rvStat**)lastKills ); + + // check for excellent award + if( !teamKill && lastKills[ 0 ] && lastKills[ 0 ]->GetTimeStamp() >= (gameLocal.time - 2000) && lastKills[ 0 ]->victimClientNum != playerClientNum && victimClientNum != playerClientNum ) { + statManager->GiveInGameAward( IGA_EXCELLENT, playerClientNum ); + } + + // check for rampage award + if( !teamKill && ( gibbed && victimClientNum != playerClientNum ) && + ( lastKills[ 0 ] && lastKills[ 0 ]->gibbed && lastKills[ 0 ]->victimClientNum != playerClientNum ) && + ( lastKills[ 1 ] && lastKills[ 1 ]->gibbed && lastKills[ 1 ]->victimClientNum != playerClientNum ) ) { + statManager->GiveInGameAward( IGA_RAMPAGE, playerClientNum ); + } + + + // check for combo kill award + if( victimClientNum != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_railgun" ) ) { + // the rail killing shot is the last hit, so look for the one past that + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 3000, 2, (rvStat**)lastHits ); + + if( lastHits[ 1 ] && lastHits[ 1 ]->GetVictimClientNum() != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", lastHits[ 1 ]->GetWeapon() ) ), "weapon_rocketlauncher" ) ) { + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_RAIL_HIT ) { + statManager->GiveInGameAward( IGA_COMBO_KILL, playerClientNum ); + } + } + } + + rvStatManager::comboKillState[ playerClientNum ] = CKS_NONE; + + + // check for defense award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + // defense is given for two conditions +// assert( gameLocal.mpGame.GetFlagEntity( TEAM_STROGG ) && gameLocal.mpGame.GetFlagEntity( TEAM_MARINE ) ); + assert( player->team >= 0 && player->team < TEAM_MAX ); + assert( gameLocal.mpGame.GetGameState()->IsType( rvCTFGameState::GetClassType() ) ); + + if ( gameLocal.mpGame.GetFlagEntity( player->team ) ) { + // defending your flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_AT_BASE || ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_DROPPED ) { + if( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if you're close to flag and you kill an enemy + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if ( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if enemy is close to flag and you kill them + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + + // defending your teammate carrying the enemy flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( gameLocal.mpGame.OpposingTeam( player->team ) ) == FS_TAKEN ) { + int clientNum = ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagCarrier( gameLocal.mpGame.OpposingTeam( player->team ) ); + + idPlayer* flagCarrier = NULL; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + flagCarrier = (idPlayer*)gameLocal.entities[ clientNum ]; + } + + if( flagCarrier ) { + if( (flagCarrier->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed enemy while close to the flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if( (flagCarrier->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed an enemy who was close to teh flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + } + + // check for holy shit award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + if( (player->team == TEAM_MARINE && victim->PowerUpActive( POWERUP_CTF_MARINEFLAG )) || + (player->team == TEAM_STROGG && victim->PowerUpActive( POWERUP_CTF_STROGGFLAG )) ) { + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( victim->team ) == FS_AT_BASE ) { + + // fixme: something is broken with the mpgame state + if ( gameLocal.mpGame.GetFlagEntity( victim->team ) ) { + if( (gameLocal.mpGame.GetFlagEntity( victim->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 40000 ) { + statManager->GiveInGameAward( IGA_HOLY_SHIT, playerClientNum ); + } + } + } + } + } +} + +/* +================ +rvStatDeath + +A player died +================ +*/ +rvStatDeath::rvStatDeath( int t, int p, int mod ) : rvStat( t ) { + type = ST_DEATH; + playerClientNum = p; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDeath::RegisterInGame( rvPlayerStat* stats ) { + stats[ playerClientNum ].deaths++; +} + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +rvStatDamageDealt::rvStatDamageDealt( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_DEALT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageDealt::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageGiven += damage; + } +} + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +rvStatDamageTaken::rvStatDamageTaken( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_TAKEN; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageTaken::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageTaken += damage; + } +} + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +rvStatFlagDrop::rvStatFlagDrop( int t, int p, int a, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + attacker = a; + type = ST_CTF_FLAG_DROP; +} + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +rvStatFlagReturn::rvStatFlagReturn( int t, int p, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + type = ST_CTF_FLAG_RETURN; +} + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +rvStatFlagCapture::rvStatFlagCapture( int t, int p, int f, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + flagTeam = f; + type = ST_CTF_FLAG_CAPTURE; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatFlagCapture::RegisterInGame( rvPlayerStat* stats ) { + statManager->GiveInGameAward( IGA_CAPTURE, playerClientNum ); + // figure out if we need to give an assist + + // see if someone carried the flag a bit, lost it, but it was capped + rvStatFlagDrop* flagDrop = (rvStatFlagDrop*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_DROP, gameLocal.time - 10000 ); + if( flagDrop && flagDrop->GetTeam() == team ) { + // only give the award if there was no flag return between the flag drop and the flag capture + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( flagTeam, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn == NULL ) { + statManager->GiveInGameAward( IGA_ASSIST, flagDrop->GetPlayerClientNum() ); + } + } + + // see if someone returned the flag, allowing a cap + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn ) { + statManager->GiveInGameAward( IGA_ASSIST, flagReturn->GetPlayerClientNum() ); + } +} diff --git a/source/mpgame/mp/stats/StatEvent.h b/source/mpgame/mp/stats/StatEvent.h new file mode 100644 index 0000000..b2cdaa2 --- /dev/null +++ b/source/mpgame/mp/stats/StatEvent.h @@ -0,0 +1,361 @@ +//---------------------------------------------------------------- +// StatEvent.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATEVENT_H__ +#define __STATEVENT_H__ + +/* +=============================================================================== + +Multiplayer statistics events + +This file contains statistic event definitions. rvStatManager exposes +game-related functions (i.e. 'Kill', 'FlagCaptured') which create the +appropriate statistic event and add it to the statQueue in rvStatManager. + +The statQueue contains a complete picture of an MP game. It can be parsed to +calculate accuracies, award end-game awards, etc. + +Statistic events also have functionality to register in-game information for +on-the-fly statistics. + +=============================================================================== +*/ + +class rvPlayerStat; + +/* +================ +statType_t + +Type identifiers for RTTI of stat events, rvStatTeam-derived events must go +after ST_STAT_TEAM +================ +*/ +enum statType_t { + // not an actual event, undefined marker + ST_NONE = 0, + // rvStat derived + ST_BEGIN_GAME, + ST_END_GAME, + ST_CLIENT_CONNECT, + ST_HIT, + ST_KILL, + ST_DEATH, + ST_DAMAGE_DEALT, + ST_DAMAGE_TAKEN, + // not an actual event, team marker + ST_STAT_TEAM, + // rvStatTeam derived + ST_CTF_FLAG_CAPTURE, + ST_CTF_FLAG_DROP, + ST_CTF_FLAG_RETURN, + + ST_COUNT +}; + +/* +================ +rvStat + +An individual event we want to know about for stats. +================ +*/ +class rvStat { + + friend class rvStatAllocator; + +public: + + statType_t GetType( void ) const { return type; }; + int GetTimeStamp( void ) const { return timeStamp; }; + byte GetPlayerClientNum( void ) const { return playerClientNum; }; + +protected: + + // moved to protected to prevent allocating these on the normal heap. + // these should only be allocatd by rvStatAllocator. + rvStat( int t ) { timeStamp = t; playerClientNum = -1; type = ST_NONE; }; + virtual ~rvStat( void ) {}; + + virtual void RegisterInGame( rvPlayerStat* stats ) {}; + + // the entity number of the player associated with this statistic + byte playerClientNum; + + statType_t type; + int timeStamp; + +private: + // because of the way memory is handled for these we want the compiler + // on our side to find abuses. note that we aren't defining these. + // this change is propagated to all derived classes. + rvStat(); + rvStat( const rvStat &rhs ); + const rvStat &operator=( const rvStat &rhs ); +}; + +/* +================ +rvStatTeam + +A team-related rvStat +================ +*/ +class rvStatTeam : public rvStat { + + friend class rvStatAllocator; + +public: + virtual ~rvStatTeam( void ) {}; + + int GetTeam( void ) { return team; }; +protected: + + // see comment in rvStat + rvStatTeam( int t, int tm ) : rvStat( t ) { team = tm; }; + + byte team; + +private: + // see comment in rvStat + rvStatTeam(); + rvStatTeam( const rvStatTeam &rhs ); + const rvStatTeam &operator=( const rvStat &rhs ); + +}; + +/* +=============================================================================== + +rvStat/rvStatTeam-derived classes + +These are the individual events that get stored in the +statManager's stat queue + +=============================================================================== +*/ + +/* +================ +rvStatBeginGame + +A game has begun +================ +*/ +class rvStatBeginGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatBeginGame( int t ) : rvStat( t ) { type = ST_BEGIN_GAME; }; +}; + +/* +================ +rvStatEndGame + +The current game has ended +================ +*/ +class rvStatEndGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatEndGame( int t ) : rvStat( t ) { type = ST_END_GAME; }; +}; + +/* +================ +rvStatClientConnect + +A player has connected +================ +*/ +class rvStatClientConnect : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatClientConnect( int t, int p ) : rvStat( t ) { type = ST_CLIENT_CONNECT; playerClientNum = p; }; +}; + +/* +================ +rvStatHit + +A player hit another player +================ +*/ +class rvStatHit : public rvStat { + + friend class rvStatAllocator; + +public: + int GetVictimClientNum() const { return victimClientNum; } + int GetWeapon() const { return weapon; } + +protected: + rvStatHit( int t, int p, int v, int w, bool countForAccuracy ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + byte victimClientNum; + bool trackAccuracy; +}; + +/* +================ +rvStatKill + +A player killed another player +================ +*/ +class rvStatKill : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatKill( int t, int p, int v, bool g, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; + byte victimClientNum; + bool gibbed; +}; + +/* +================ +rvStatDeath + +A player died +================ +*/ +class rvStatDeath : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatDeath( int t, int p, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; +}; + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +class rvStatDamageDealt : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageDealt( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +class rvStatDamageTaken : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageTaken( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +class rvStatFlagDrop : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagDrop( int t, int p, int a, int tm ); + + byte attacker; // enemy who caused the flag drop + +private: + // see comment in rvStat + rvStatFlagDrop(); + rvStatFlagDrop( const rvStatFlagDrop &rhs ); + const rvStatFlagDrop &operator=( const rvStatFlagDrop &rhs ); +}; + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +class rvStatFlagReturn : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagReturn( int t, int p, int tm ); + +private: + // see comment in rvStat + rvStatFlagReturn(); + rvStatFlagReturn( const rvStatFlagReturn &rhs ); + const rvStatFlagReturn &operator=( const rvStatFlagReturn &rhs ); +}; + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +class rvStatFlagCapture : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagCapture( int t, int p, int f, int tm ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte flagTeam; // team of flag was captured + +private: + // see comment in rvStat + rvStatFlagCapture(); + rvStatFlagCapture( const rvStatFlagCapture &rhs ); + const rvStatFlagCapture &operator=( const rvStatFlagCapture &rhs ); +}; + +#endif diff --git a/source/mpgame/mp/stats/StatManager.cpp b/source/mpgame/mp/stats/StatManager.cpp new file mode 100644 index 0000000..708cb79 --- /dev/null +++ b/source/mpgame/mp/stats/StatManager.cpp @@ -0,0 +1,1345 @@ +//---------------------------------------------------------------- +// StatManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + +// TTimo - *????????* +#include + +rvStatManager statManagerLocal; +rvStatManager* statManager = &statManagerLocal; + +comboKillState_t rvStatManager::comboKillState[ MAX_CLIENTS ] = { CKS_NONE }; +int rvStatManager::lastRailShot[ MAX_CLIENTS ] = { -2 }; +int rvStatManager::lastRailShotHits[ MAX_CLIENTS ] = { 0 }; + +inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ] = { + //IGA_INVALID + { + NULL + }, + //IGA_CAPTURE + { + "capture" + }, + //IGA_HUMILIATION + { + "humiliation" + }, + //IGA_IMPRESSIVE + { + "impressive" + }, + //IGA_EXCELLENT + { + "excellent" + }, + //IGA_ASSIST + { + "assist" + }, + //IGA_DEFENSE + { + "defense" + }, + //IGA_COMBO_KILL + { + "combo_kill" + }, + //IGA_RAMPAGE + { + "rampage" + }, + //IGA_HOLY_SHIT + { + "holy_shit" + } +}; + +// RAVEN BEGIN +// rhummer: localized these strings. +endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ] = { + //EGA_INVALID + { + NULL + }, + //EGA_LEMMING + { + "#str_107260" + }, + //EGA_RAIL_MASTER + { + "#str_107261" + }, + //EGA_ROCKET_SAUCE + { + "#str_107262" + }, + //EGA_BRAWLER + { + "#str_107263" + }, + //EGA_SNIPER + { + "#str_107264" + }, + //EGA_CRITICAL_FAILURE + { + "#str_107265" + }, + //EGA_TEAM_PLAYER + { + "#str_107266" + }, + //EGA_ACCURACY + { + "#str_107267" + }, + //EGA_FRAGS + { + "#str_107268" + }, + //EGA_PERFECT + { + "#str_107269" + } +}; +// RAVEN END + +void showStats_f( const idCmdArgs &args ) { + + statManager->DebugPrint(); +} + + +/* +=============================================================================== + + rvStatAllocator + + Handles allocating stat events + +=============================================================================== +*/ + +void *rvStatAllocator::GetBlock( size_t blockSize, int* blockNumOut /* = NULL */ ) { + // if not enough bytes for this allocation, get a new block + if ( GetBytesLeftInBlock() < blockSize ) { + // recycle & reuse a block + currentBlock++; + placeInBlock = 0; + if( currentBlock >= MAX_BLOCKS ) { + currentBlock = 0; + if( statManager->GetStat( 0 ) && gameLocal.isMultiplayer ) { +// int delta = gameLocal.time - statManager->GetStat( 0 )->GetTimeStamp(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Tracked %g seconds of stats before recycle\n", delta / 1000.0f ); + } + } +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Using block %d\n", currentBlock ); +// statManager->DebugPrint(); +// int numFreed = + statManager->FreeEvents( currentBlock ); +// statManager->DebugPrint(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - stat manager freed %d events which used block %d\n", numFreed, currentBlock ); + } + + // if this fires our objects are too large or our alloc size is too small + assert( GetBytesLeftInBlock() >= blockSize ); + + byte *newBlock = blocks + ( BLOCK_SIZE * currentBlock ) + placeInBlock; + placeInBlock += blockSize; + totalAllocations++; + totalBytesUsed += blockSize; + if( blockNumOut ) { + // return the current block number if requested + (*blockNumOut) = currentBlock; + } + return newBlock; +} + +void rvStatAllocator::Reset() { + currentBlock = 0; + placeInBlock = 0; + + totalBytesUsed = 0; + totalAllocations = 0; + totalBytesAllocated = 0; + for ( int i = 0; i < ST_COUNT; i++ ) { + allocationsByType[ i ] = 0; + } +} + +rvStatAllocator::rvStatAllocator() { + Reset(); +} + +void rvStatAllocator::Report() +{ + // shouchard: for debugging and tuning only + common->Printf( "rvStatAllocator: dump of usage stats\n" ); + common->Printf( "\t%d total bytes handed out in %d requests\n", GetTotalBytesUsed(), GetTotalAllocations() ); + common->Printf( "\tbegin game: %3d; end game: %3d\n", + GetAllocationsByType( ST_BEGIN_GAME ), + GetAllocationsByType( ST_END_GAME ) ); + common->Printf( "\tplayer hit: %3d; player kill: %3d\n", + GetAllocationsByType( ST_HIT ), + GetAllocationsByType( ST_KILL ) ); + common->Printf( "\tplayer death: %3d;\n", + GetAllocationsByType( ST_DEATH ) ); + common->Printf( "\tdamage dealt: %3d; damage taken: %3d\n", + GetAllocationsByType( ST_DAMAGE_DEALT ), + GetAllocationsByType( ST_DAMAGE_TAKEN ) ); + common->Printf( "\tstat team: %3d\n", + GetAllocationsByType( ST_STAT_TEAM ) ); + common->Printf( "\tflag capture: %3d;\n", + GetAllocationsByType( ST_CTF_FLAG_CAPTURE ) ), + common->Printf( "\tflag drop: %3d; flag return: %3d\n", + GetAllocationsByType( ST_CTF_FLAG_DROP ), + GetAllocationsByType( ST_CTF_FLAG_RETURN ) ); +} + +// object allocators + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new new +#endif // _INLINEDEBUGMEMORY + +rvStatBeginGame *rvStatAllocator::AllocStatBeginGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatBeginGame ), blockNumOut ); + assert( newBlock ); + rvStatBeginGame *stat = new( newBlock ) rvStatBeginGame( t ); + allocationsByType[ ST_BEGIN_GAME ]++; + return stat; +} + +rvStatEndGame *rvStatAllocator::AllocStatEndGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatEndGame ), blockNumOut ); + assert( newBlock ); + rvStatEndGame *stat = new( newBlock ) rvStatEndGame( t ); + allocationsByType[ ST_END_GAME ]++; + return stat; +} + +rvStatClientConnect *rvStatAllocator::AllocStatClientConnect( int t, int client, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatClientConnect ), blockNumOut ); + assert( newBlock ); + rvStatClientConnect *stat = new( newBlock ) rvStatClientConnect( t, client ); + allocationsByType[ ST_CLIENT_CONNECT ]++; + return stat; +} + +rvStatHit *rvStatAllocator::AllocStatHit( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatHit ), blockNumOut ); + assert( newBlock ); + rvStatHit *stat = new( newBlock ) rvStatHit( t, p, v, w, countForAccuracy ); + allocationsByType[ ST_HIT ]++; + return stat; +} + +rvStatKill *rvStatAllocator::AllocStatKill( int t, int p, int v, bool g, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatKill ), blockNumOut ); + assert( newBlock ); + rvStatKill *stat = new( newBlock ) rvStatKill( t, p, v, g, mod ); + allocationsByType[ ST_KILL ]++; + return stat; +} + +rvStatDeath *rvStatAllocator::AllocStatDeath( int t, int p, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDeath ), blockNumOut ); + assert( newBlock ); + rvStatDeath *stat = new( newBlock ) rvStatDeath( t, p, mod ); + allocationsByType[ ST_DEATH ]++; + return stat; +} + +rvStatDamageDealt *rvStatAllocator::AllocStatDamageDealt( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageDealt ), blockNumOut ); + assert( newBlock ); + rvStatDamageDealt *stat = new( newBlock ) rvStatDamageDealt( t, p, w, d ); + allocationsByType[ ST_DAMAGE_DEALT ]++; + return stat; +} + +rvStatDamageTaken *rvStatAllocator::AllocStatDamageTaken( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageTaken ), blockNumOut ); + assert( newBlock ); + rvStatDamageTaken *stat = new( newBlock ) rvStatDamageTaken( t, p, w, d ); + allocationsByType[ ST_DAMAGE_TAKEN ]++; + return stat; +} + +rvStatFlagDrop *rvStatAllocator::AllocStatFlagDrop( int t, int p, int a, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagDrop ), blockNumOut ); + assert( newBlock ); + rvStatFlagDrop *stat = new( newBlock ) rvStatFlagDrop( t, p, a, tm ); + allocationsByType[ ST_CTF_FLAG_DROP ]++; + return stat; +} + +rvStatFlagReturn *rvStatAllocator::AllocStatFlagReturn( int t, int p, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagReturn ), blockNumOut ); + assert( newBlock ); + rvStatFlagReturn *stat = new( newBlock ) rvStatFlagReturn( t, p, tm ); + allocationsByType[ ST_CTF_FLAG_CAPTURE ]++; + return stat; +} + +rvStatFlagCapture *rvStatAllocator::AllocStatFlagCapture( int t, int p, int f, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagCapture ), blockNumOut ); + assert( newBlock ); + rvStatFlagCapture *stat = new( newBlock ) rvStatFlagCapture( t, p, f, tm ); + allocationsByType[ ST_CTF_FLAG_RETURN ]++; + return stat; +} + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new ID_DEBUG_NEW +#endif // _INLINEDEBUGMEMORY + +/* +=============================================================================== + + rvStatManager + + Stores game statistic events in statQueue + +=============================================================================== +*/ + +// shouchard: stat manager start with 1 meg; we'll tune as we get better data +rvStatManager::rvStatManager() { + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Init( void ) { + Shutdown(); + statQueue.Clear(); + awardQueue.Clear(); + statQueue.SetGranularity( 1024 ); + endGameSetup = false; + cmdSystem->AddCommand( "ShowInGameStats", showStats_f, CMD_FL_SYSTEM, "show in game stats." ); + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Shutdown( void ) { + statAllocator.Report(); + statAllocator.Reset(); + statQueue.Clear(); + awardQueue.Clear(); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::BeginGame( void ) { + int blockNum; + rvStatBeginGame* stat = statAllocator.AllocStatBeginGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + endGameSetup = false; +#if ID_TRAFFICSTATS + startTime = gameLocal.time; + networkSystem->GetTrafficStats( startSent, startPacketsSent, startReceived, startPacketsReceived ); +#endif + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::EndGame( void ) { + int blockNum; + rvStatEndGame* stat = statAllocator.AllocStatEndGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + CalculateEndGameStats(); + awardQueue.Clear(); +#if ID_TRAFFICSTATS + int sent, packetsSent, received, packetsReceived, time; + networkSystem->GetTrafficStats( sent, packetsSent, received, packetsReceived ); + sent -= startSent; + packetsSent -= startPacketsSent; + received -= startReceived; + packetsReceived -= startPacketsReceived; + time = gameLocal.time - startTime; + common->Printf( "EndGame. bytes sent: %d packets sent: %d bytes received: %d packets received: %d\n", sent, packetsSent, received, packetsReceived ); + // compute averages, including packet overhead + // adjust the UDP overhead, may depend on your TCP stack implementation ( 42 comes from ethereal analysis of the traffic ) + sent += packetsSent * 42; + received += packetsReceived * 42; + float sentBps, recvBps; + sentBps = (float)( sent ) * 1000.0f / time; + recvBps = (float)( received ) * 1000.0f / time; + common->Printf( "avg sent %g B/s, received %g B/s\n", sentBps, recvBps ); +#endif +} + +void rvStatManager::ClientConnect( int clientNum ) { + // push a client connected event into the queue so we don't get confused with old + // events detailing the previous owner of this clientNum + int blockNum; + rvStatClientConnect* stat = statAllocator.AllocStatClientConnect( gameLocal.time, clientNum, &blockNum ); + statQueue.Append( rvPair( (rvStat*)stat, blockNum ) ); +} + +void rvStatManager::ClientDisconnect( int clientNum ) { + // re-init player stats + playerStats[ clientNum ] = rvPlayerStat(); +} + +void rvStatManager::Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ) { + int deathBlock, killBlock; + rvStatDeath* statDeath = statAllocator.AllocStatDeath( gameLocal.time, victim->entityNumber, methodOfDeath, &deathBlock ); + + statQueue.Append( rvPair( (rvStat*)statDeath, deathBlock ) ); + + if( killer && killer->IsType( idPlayer::GetClassType() ) ) { + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, killer->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } else if( !killer ) { + // basically a suicide + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, victim->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } +} + +void rvStatManager::FlagCaptured( const idPlayer* player, int flagTeam ) { + int blockNum; + rvStatFlagCapture* stat = statAllocator.AllocStatFlagCapture( gameLocal.time, player->entityNumber, flagTeam, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::WeaponFired( const idPlayer* player, int weapon, int num ) { + playerStats[ player->entityNumber ].weaponShots[ weapon ] += num; + lastRailShotHits[ player->entityNumber ] = 0; + + comboKillState_t cks = comboKillState[ player->entityNumber ]; + comboKillState[ player->entityNumber ] = CKS_NONE; + if ( player->GetWeaponIndex( "weapon_rocketlauncher" ) == weapon ) { + if ( cks == CKS_NONE ) { + comboKillState[ player->entityNumber ] = CKS_ROCKET_FIRED; + } + } else if ( player->GetWeaponIndex( "weapon_railgun" ) == weapon ) { + // apparently it processes hits before it does the fire.... + if ( cks == CKS_RAIL_HIT ) { + comboKillState[ player->entityNumber ] = CKS_RAIL_FIRED; + } + } + +} + +void rvStatManager::WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if( victim && victim != attacker ) { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + // if attacker was a player, track hit and damage dealt + int hitBlock; + rvStatHit* statHit = statAllocator.AllocStatHit( gameLocal.time, attacker->entityNumber, victim->entityNumber, weapon, countForAccuracy, &hitBlock ); + + statQueue.Append( rvPair( (rvStat*)(statHit), hitBlock ) ); + } + } +} + +//asalmon modified to work for single player stats on Xenon +void rvStatManager::Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if(attacker) + { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + int damageBlock; + rvStatDamageDealt* statDamage = statAllocator.AllocStatDamageDealt( gameLocal.time, attacker->entityNumber, weapon, damage, &damageBlock ); + statQueue.Append( rvPair( (rvStat*)(statDamage), damageBlock ) ); + } + } + + if(victim) + { + if( victim->IsType( idPlayer::GetClassType() ) ) { + // if victim was a player, track damage taken + int blockNum; + rvStatDamageTaken* stat = statAllocator.AllocStatDamageTaken( gameLocal.time, victim->entityNumber, weapon, damage, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + } + } +} + +void rvStatManager::FlagDropped( const idPlayer* player, const idEntity* attacker ) { + int blockNum; + rvStatFlagDrop* stat = statAllocator.AllocStatFlagDrop( gameLocal.time, player->entityNumber, attacker->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} +void rvStatManager::FlagReturned( const idPlayer* player ) { + int blockNum; + rvStatFlagReturn* stat = statAllocator.AllocStatFlagReturn( gameLocal.time, player->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::DebugPrint( void ) { + if( !gameLocal.isMultiplayer ) { + return; + } + //gameLocal.Printf( "Begin statistics debug dump:\n" ); + + //gameLocal.Printf( "Statistics queue:\n" ); + for( int i = 0; i < Min( statQueue.Num(), 50 ); i++ ) { + gameLocal.Printf( "{%d, %d} ", statQueue[i].First()->GetType(), statQueue[i].First()->GetTimeStamp() ); + } + gameLocal.Printf("\n"); + + //gameLocal.Printf( "In-game statistics\n\n" ); + //for( int i = 0; i < inGameStats.Num(); i++ ) { + // gameLocal.Printf( "\t%d - %s:\n", i, statIndex->index[ i ].GetName().c_str() ); + // gameLocal.Printf( "\t\tKills: %d\n", inGameStats[i].kills ); + // gameLocal.Printf( "\t\tDeaths: %d\n", inGameStats[i].deaths ); + // gameLocal.Printf( "\t\tFlag Caps: %d\n", inGameStats[i].flagCaptures ); + + // for( int j = 0; j < MAX_WEAPONS; j++ ) { + // gameLocal.Printf( "\t\tWeapon %d hits: %d\n\t\tWeapon %d shots: %d\n", j, inGameStats[i].weaponHits[j], j, inGameStats[i].weaponShots[j] ); + // } + //} +} + +int rvStatManager::FreeEvents( int blockNum ) { + int blockStart = -1; + int blockEnd = -1; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( blockStart == -1 && statQueue[ i ].Second() == blockNum ) { + blockStart = i; + } else if( blockStart != -1 && statQueue[ i ].Second() != blockNum ) { + blockEnd = i; + break; + } + } + + if( blockStart == -1 || blockEnd == -1 ) { +// rjohnson: commented out warning - I take it this is not a bad message? +// gameLocal.Warning( "rvStatManager::FreeEvents() - Could not find events with block num '%d'\n", blockNum ); + return 0; + } + + statQueue.RemoveRange( blockStart, blockEnd - 1 ); + + return (blockEnd - blockStart); +} + +void rvStatManager::SendInGameAward( inGameAward_t award, int clientNum ) { + assert( gameLocal.isServer ); + + idBitMsg msg; + byte msgBuf[1024]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_RELIABLE_MESSAGE_INGAMEAWARD ); + + msg.WriteByte( award ); + msg.WriteByte( clientNum ); + + networkSystem->ServerSendReliableMessage( -1, msg ); + + if( gameLocal.isListenServer ) { + msg.ReadByte(); + ReceiveInGameAward( msg ); + } +} + + +void rvStatManager::ReceiveInGameAward( const idBitMsg& msg ) { + assert( gameLocal.isClient || gameLocal.isListenServer ); + int numAwards = 0; + + inGameAward_t award = (inGameAward_t)msg.ReadByte(); + int client = msg.ReadByte(); + + // display award on hud + idPlayer* player = gameLocal.GetLocalPlayer(); + idPlayer* remote = gameLocal.GetClientByNum(client); + bool justSound = false; + if ( client != gameLocal.localClientNum ) { + justSound = true; + } + + if ( ( gameLocal.time - inGameAwardHudTime ) < 3000 || awardQueue.Num() > 0 ) { + if ( gameLocal.GetDemoFollowClient() == client || ( player != NULL && remote != NULL && player->GetInstance() == remote->GetInstance() ) ) { + rvPair awardPair(award, justSound); + awardQueue.StackAdd(awardPair); + return; + } + } + + if( client == gameLocal.localClientNum ) { + // don't count awards during warmup + + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + localInGameAwards[ award ]++; + numAwards = localInGameAwards[ award ]; + } else { + numAwards = 1; + } + + if( player && player->mphud ) { + player->mphud->HandleNamedEvent( "clearIGA" ); + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, numAwards ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if( numAwards < 10 ) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, numAwards ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + } else { + player->mphud->SetStateInt( "ig_award_num", numAwards ); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + //inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + + if ( player ) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + else if ( player && remote && ( player->GetInstance() == remote->GetInstance() ) && (award == IGA_HOLY_SHIT || award == IGA_CAPTURE || award == IGA_HUMILIATION )) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + inGameAwardHudTime = gameLocal.time; + + idPlayer* awardee = gameLocal.GetClientByNum( client ); + if ( awardee ) { + if ( player && player->GetInstance() == awardee->GetInstance() ) { + iconManager->AddIcon( client, va( "mtr_award_%s", inGameAwardInfo[ award ].name ) ); + } + } +} + +void rvStatManager::CheckAwardQueue() { + + if(((gameLocal.time - inGameAwardHudTime) < 3000 || awardQueue.Num() == 0)) + { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int award = awardQueue.StackTop().First(); + bool justSound = awardQueue.StackTop().Second(); + awardQueue.StackPop(); + + if ( player ) { + if(!justSound || award == IGA_HOLY_SHIT || award == IGA_CAPTURE) + { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + if( player && player->mphud && !justSound) { + + player->mphud->HandleNamedEvent( "clearIGA" ); + localInGameAwards[ award ]++; + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, localInGameAwards[ award ] ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if(localInGameAwards[ award ] < 10) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, localInGameAwards[ award ] ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + + } + else { + player->mphud->SetStateInt( "ig_award_num", localInGameAwards[ award ]); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + +} + + +void rvStatManager::GivePlayerCashForAward( idPlayer* player, inGameAward_t award ) +{ + if( !player ) + return; + + if( !gameLocal.isMultiplayer ) + return; + + if( !gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + return; + + mpGameState_t mpGameState = gameLocal.mpGame.GetGameState()->GetMPGameState(); + if( mpGameState != GAMEON && mpGameState != SUDDENDEATH ) + return; + + const char* awardCashValueName = NULL; + switch( award ) + { + case IGA_CAPTURE: awardCashValueName = "playerCashAward_mpAward_capture"; break; + case IGA_HUMILIATION: awardCashValueName = "playerCashAward_mpAward_humiliation"; break; + case IGA_IMPRESSIVE: awardCashValueName = "playerCashAward_mpAward_impressive"; break; + case IGA_EXCELLENT: awardCashValueName = "playerCashAward_mpAward_excellent"; break; + case IGA_ASSIST: awardCashValueName = "playerCashAward_mpAward_assist"; break; + case IGA_DEFENSE: awardCashValueName = "playerCashAward_mpAward_defense"; break; + case IGA_COMBO_KILL: awardCashValueName = "playerCashAward_mpAward_combo_kill"; break; + case IGA_RAMPAGE: awardCashValueName = "playerCashAward_mpAward_rampage"; break; + case IGA_HOLY_SHIT: awardCashValueName = "playerCashAward_mpAward_holy_shit"; break; + } + + if( awardCashValueName ) + { + player->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( awardCashValueName, 0 ) ); + } +} + + +void rvStatManager::GiveInGameAward( inGameAward_t award, int clientNum ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ clientNum ]; + + if( gameLocal.isMultiplayer ) { + // show in-game awards during warmup, but don't actually let players accumulate them + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + playerStats[ clientNum ].inGameAwards[ award ]++; + GivePlayerCashForAward( player, award ); + } + SendInGameAward( award, clientNum ); + } +} + +void rvStatManager::SetupStatWindow( idUserInterface* statHud ) { + statWindow.SetupStatWindow( statHud ); +} + +void rvStatManager::SelectStatWindow( int selectionIndex, int selectionTeam ) { + statWindow.SelectPlayer( statWindow.ClientNumFromSelection( selectionIndex, selectionTeam ) ); +} + +int rvStatManager::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + return statWindow.GetSelectedClientNum( selectionIndexOut, selectionTeamOut ); +} + +void rvStatManager::UpdateInGameHud( idUserInterface* statHud, bool visible ) { + idPlayer* player = NULL; + + if( gameLocal.GetLocalPlayer() ) { + player = gameLocal.GetLocalPlayer(); + player->GetHud()->SetStateInt( "stat_visible", visible? 1 : 0); + } + + if( !visible ) { + statHud->SetStateInt( "stat_visible", 0 ); + return; + } else { + statHud->SetStateInt( "stat_visible", 1 ); + } + + if( player ) { + statWindow.SetupStatWindow( statHud, player->spectating ); + } +} + +void rvStatManager::SendStat( int toClient, int statClient ) { + if( statClient < 0 || statClient >= MAX_CLIENTS ) { + gameLocal.Warning( "rvStatManager::SendStat() - Stats requested for invalid client num '%d'\n", statClient ); + return; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( statClient ); + + playerStats[ statClient ].PackStats( outMsg ); + + networkSystem->ServerSendReliableMessage( toClient, outMsg ); +} + +void rvStatManager::ReceiveStat( const idBitMsg& msg ) { + //asalmon: added because this is used to restore single player stats from saves on Xbox 360 + if(gameLocal.IsMultiplayer()) + { + assert( gameLocal.isClient ); + } + + int client = msg.ReadByte(); + + playerStats[ client ].UnpackStats( msg ); + playerStats[ client ].lastUpdateTime = gameLocal.time; + + // display the updated stat + if(gameLocal.IsMultiplayer()) + { + statWindow.SelectPlayer( client ); + } +} + +void rvStatManager::SendAllStats( int clientNum, bool full ) { + + assert( gameLocal.isServer ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ALL_STATS ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = 0; + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( gameLocal.entities[ i ] ) { + sentClients |= 1 << i; + } + } + + outMsg.WriteBits( sentClients, MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].PackStats( outMsg ); + } + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + //common->Printf("SENT ALL STATS %i\n", Sys_Milliseconds()); + +} + + +void rvStatManager::ReceiveAllStats( const idBitMsg& msg ) { + assert( gameLocal.isClient ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = msg.ReadBits( MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].UnpackStats( msg ); + } else { + playerStats[ i ].Clear(); + } + + playerStats[ i ].lastUpdateTime = gameLocal.time; + } + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == GAMEREVIEW ) { + gameLocal.mpGame.ShowStatSummary(); + } +} + + +void rvStatManager::ClearStats( void ) { + // clear connected stats + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +#ifdef _XENON + lastFullUpdate = -50000; +#endif +} + +void rvStatManager::CalculateEndGameStats( void ) { + int maxKills = idMath::INT_MIN; + int maxKillPlayer = -1; + + int maxSuicides = idMath::INT_MIN; + int maxSuicidesPlayer = -1; + + int maxGauntletKills = idMath::INT_MIN; + int maxGauntletKillsPlayer = -1; + + float maxDamageKillsRatio = 0.0; + int maxDamageKillsRatioPlayer = -1; + +//Dump the stats to a log file + idFile *log = NULL; + if(cvarSystem->GetCVarBool("com_logMPStats")) + { + log = fileSystem->OpenFileAppend("StatisticsLog.txt"); + } + idStr toFile; + if ( !log ) { +// common->Warning("Statistics log will not be written\n"); + } + else + { + struct tm *newtime; + time_t aclock; + time( &aclock ); + newtime = localtime( &aclock ); + toFile = va("Match on map %s played on %s\n", gameLocal.GetMapName(), asctime( newtime )); + log->Write(toFile.c_str(), toFile.Length()); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( !gameLocal.entities[ i ] ) { + continue; + } + + if(log) + { + toFile = va("Statistics for %s\n", gameLocal.userInfo[ i ].GetString("ui_name")); + log->Write(toFile.c_str(), toFile.Length()); + toFile = va("Kills: %i Deaths: %i Suicides: %i\n", playerStats[ i ].kills, playerStats[ i ].deaths, playerStats[ i ].suicides); + log->Write(toFile.c_str(), toFile.Length()); + } + + gameLocal.Printf( "Calculating stats for client %d (%s)\n", i, gameLocal.userInfo[ i ].GetString("ui_name") ); + // overall accuracy award and sniper accuracy award + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + int railgunIndex = player->GetWeaponIndex( "weapon_railgun" ); + int rocketIndex = player->GetWeaponIndex( "weapon_rocketlauncher" ); + + float accuracyAverage = 0.0f; + int numAccuracies = 0; + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats[ i ].weaponShots[ j ] == 0 ) { + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s not used\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name"))); + log->Write(toFile.c_str(), toFile.Length()); + } + } + continue; + } + + float weaponAccuracy = (float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]; + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s: %i%%\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name")), (int)(((float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]) * 100.0f)); + log->Write(toFile.c_str(), toFile.Length()); + } + } + if( j == railgunIndex ) { + // sniper award + if( weaponAccuracy >= 0.9f && playerStats[ i ].weaponShots[ railgunIndex ] >= 10 ) { + playerStats[ i ].endGameAwards.Append( EGA_SNIPER ); + } + } + + accuracyAverage += weaponAccuracy; + numAccuracies++; + } + + + if ( numAccuracies && ( accuracyAverage / (float)numAccuracies >= 0.5f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ACCURACY ); + } + + + // rail master award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ railgunIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_RAIL_MASTER ); + } + + // rocket sauce award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ rocketIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ROCKET_SAUCE ); + } + + // critical failure award + if( playerStats[ i ].kills == 0 ) { + playerStats[ i ].endGameAwards.Append( EGA_CRITICAL_FAILURE ); + } + + // frags award +//asalmon: Made the limit more reasonable for the shorter time limits and less players of Xenon +#ifdef _XENON + if( playerStats[ i ].kills >= 50 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#else + if( playerStats[ i ].kills >= 100 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#endif + + + if( playerStats[ i ].kills > maxKills ) { + maxKills = playerStats[ i ].kills; + maxKillPlayer = i; + } + + if( playerStats[ i ].suicides > maxSuicides ) { + maxSuicides = playerStats[ i ].suicides; + maxSuicidesPlayer = i; + } + + if( playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ] > maxGauntletKills ) { + maxGauntletKills = playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ]; + maxGauntletKillsPlayer = i; + } + +//asalmon: Calculate the damage ratio: + if(playerStats[i].kills > 0) + { + playerStats[i].damageRatio = playerStats[i].damageGiven / playerStats[i].kills; + } + else + { + playerStats[i].damageRatio = playerStats[i].damageGiven; + } + + if( playerStats[ i ].damageRatio > maxDamageKillsRatio ) { + maxDamageKillsRatio = playerStats[ i ].damageRatio; + maxDamageKillsRatioPlayer = i; + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + } + + +//asalmon: hack to test certain achievement awards. +#ifdef _XENON + if(cvarSystem->GetCVarInteger("si_overrideFrags")) + { + common->Printf("Overriding Frags to: %i for player %i\n", cvarSystem->GetCVarInteger("si_overrideFrags"), i); + playerStats[i].kills = cvarSystem->GetCVarInteger("si_overrideFrags"); + } +#endif + + } + + + + // Perfect award + if( maxKillPlayer >= 0 && playerStats[ maxKillPlayer ].deaths == 0 ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ maxKillPlayer ]; + if( !gameLocal.IsTeamGame() || ( gameLocal.IsTeamGame() && player && gameLocal.mpGame.TeamLeader() == player->team ) ) { + playerStats[ maxKillPlayer ].endGameAwards.Append( EGA_PERFECT ); + } + } + + // Lemming award + if( maxSuicidesPlayer >= 0 && maxSuicides >= 5 ) { + playerStats[ maxSuicidesPlayer ].endGameAwards.Append( EGA_LEMMING ); + } + + // Brawler award + if( maxGauntletKillsPlayer >= 0 && maxGauntletKills >= 3 ) { + playerStats[ maxGauntletKillsPlayer ].endGameAwards.Append( EGA_BRAWLER ); + } + + // Team player award + if( maxDamageKillsRatioPlayer >= 0 && maxDamageKillsRatio > 500 ) { + playerStats[ maxDamageKillsRatioPlayer ].endGameAwards.Append( EGA_TEAM_PLAYER ); + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + log->Flush(); + fileSystem->CloseFile(log); + } +} + +void rvStatManager::GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ) { + memset( accuracyLeaders, -1, sizeof( int ) * MAX_WEAPONS ); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] == NULL ) { + continue; + } + + rvPlayerStat* playerStats = GetPlayerStat( i ); + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats->weaponShots[ j ] == 0 ) { + continue; + } + + float playerAccuracy = (float)playerStats->weaponHits[ j ] / (float)playerStats->weaponShots[ j ]; + float leaderAccuracy = -1.0f; + if( accuracyLeaders[ j ] != -1 ) { + rvPlayerStat* leaderStats = GetPlayerStat( accuracyLeaders[ j ] ); + if( leaderStats->weaponShots[ j ] != 0 ) { + leaderAccuracy = (float)leaderStats->weaponHits[ j ] / (float)leaderStats->weaponShots[ j ]; + } + } + if( playerAccuracy > leaderAccuracy ) { + accuracyLeaders[ j ] = i; + } + } + } +} + +int rvStatManager::DamageGiven( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_DEALT ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && (statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +int rvStatManager::DamageTaken( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_TAKEN ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && ( statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound ) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +rvStat* rvStatManager::GetLastClientStat( int clientNum, statType_t type, int time ) { + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + return statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ) { + int numFound = 0; + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + results[ numFound++ ] = statQueue[ i ].First(); + if( numFound >= num ) { + return; + } + } + } + } + + return; +} + + +rvStatTeam* rvStatManager::GetLastTeamStat( int team, statType_t type, int time ) { + assert( type > ST_STAT_TEAM ); + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( ((rvStatTeam*)statQueue[ i ].First())->GetTeam() == team ) { + return (rvStatTeam*)statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::SetupEndGameHud( idUserInterface* statHud ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = gameLocal.mpGame.GetRankedPlayer( i ); + + if( p && gameLocal.mpGame.IsInGame( p->entityNumber ) ) { + statHud->SetStateString( va( "player%d_name", i + 1 ), va( "%d. %s", i + 1, gameLocal.userInfo[ p->entityNumber ].GetString( "ui_name" ) ) ); + statHud->SetStateString( va( "player%d_score", i + 1 ), va( "%d", gameLocal.mpGame.GetScore( i ) ) ); + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 1 ); + statHud->SetStateInt( va( "player%d_team", i + 1 ), gameLocal.IsTeamGame() ? p->team : 0 ); + } else { + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 0 ); + } + } + statHud->HandleNamedEvent( "Setup" ); +} + +rvPlayerStat* rvStatManager::GetPlayerStat( int clientNum ) { + return &playerStats[ clientNum ]; +} + +void rvStatManager::UpdateEndGameHud( idUserInterface* statHud, int clientNum ) { + /*if( !endGameSetup ) { + // no info yet + SetupEndGameHud( statHud ); + endGameSetup = true; + } + + rvPlayerStat* clientStat = &(playerStats[ clientNum ]); + + statHud->HandleNamedEvent( "clear" ); + statHud->SetStateString( "stat_name", gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ); + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", (int)(clientStat->weaponAccuracy[ i ] * 100) ) ); + } + + // in-game awards + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < clientStat->inGameAwards.Num(); i++ ) { + igAwardCount[ clientStat->inGameAwards[ i ] ]++; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < clientStat->endGameAwards.Num(); i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); + statHud->SetStateString( va( "eg_award%d_text", i ), endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ); + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );*/ +} + +/* +=============================================================================== + + rvStatSummary + + Stores one player's summary information. Transmitted to clients for + intermission summary screen. + +=============================================================================== +*/ +rvPlayerStat::rvPlayerStat() { + Clear(); +} + +void rvPlayerStat::Clear( void ) { + memset( weaponShots, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponHits, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponKills, 0, sizeof(int) * MAX_WEAPONS ); + memset( inGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + + kills = deaths = suicides = lastUpdateTime = damageTaken = damageGiven = 0; + damageRatio = 0.0f; +} + +void rvPlayerStat::PackStats( idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponShots[ i ] ); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponHits[ i ] ); + } + + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + msg.WriteByte( inGameAwards[ i ] ); + } + + msg.WriteByte( endGameAwards.Num() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + msg.WriteByte( endGameAwards[ i ] ); + } + + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXDEATHS, deaths ), ASYNC_PLAYER_DEATH_BITS ); + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXKILLS, kills ), ASYNC_PLAYER_KILL_BITS ); +} + +void rvPlayerStat::UnpackStats( const idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponShots[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponHits[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + inGameAwards[ i ] = msg.ReadByte(); + } + + endGameAwards.SetNum( msg.ReadByte() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + endGameAwards[ i ] = (endGameAward_t)msg.ReadByte(); + } + + deaths = msg.ReadBits( ASYNC_PLAYER_DEATH_BITS ); + kills = msg.ReadBits( ASYNC_PLAYER_KILL_BITS ); +} diff --git a/source/mpgame/mp/stats/StatManager.h b/source/mpgame/mp/stats/StatManager.h new file mode 100644 index 0000000..35ede7a --- /dev/null +++ b/source/mpgame/mp/stats/StatManager.h @@ -0,0 +1,349 @@ +//---------------------------------------------------------------- +// StatManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATMANAGER_H__ +#define __STATMANAGER_H__ + +#include "StatEvent.h" +#include "StatWindow.h" + +#ifndef ID_TRAFFICSTATS + #define ID_TRAFFICSTATS 0 +#endif + +/* +=============================================================================== + + Multiplayer game statistics + +=============================================================================== +*/ + +/* +================ +endGameAward_t + +End-game award IDs +================ +*/ +enum endGameAward_t { + EGA_INVALID = 0, + EGA_LEMMING, + EGA_RAIL_MASTER, + EGA_ROCKET_SAUCE, + EGA_BRAWLER, + EGA_SNIPER, + EGA_CRITICAL_FAILURE, + EGA_TEAM_PLAYER, + EGA_ACCURACY, + EGA_FRAGS, + EGA_PERFECT, + EGA_NUM_AWARDS +}; + +/* +================ +endGameAwardInfo_t + +end-game award info +================ +*/ +struct endGameAwardInfo_t { + char* name; +}; + +/* +================ +inGameAward_t + +in-game award IDs +================ +*/ +enum inGameAward_t { + IGA_INVALID = 0, + IGA_CAPTURE, + IGA_HUMILIATION, + IGA_IMPRESSIVE, + IGA_EXCELLENT, + IGA_ASSIST, + IGA_DEFENSE, + IGA_COMBO_KILL, + IGA_RAMPAGE, + IGA_HOLY_SHIT, + IGA_NUM_AWARDS +}; + +enum comboKillState_t { + CKS_NONE, + CKS_ROCKET_FIRED, + CKS_ROCKET_HIT, + CKS_RAIL_FIRED, + CKS_RAIL_HIT, +}; + +/* +================ +inGameAwardInfo_t + +in-game award info +================ +*/ +struct inGameAwardInfo_t { + char* name; +}; + +/* +================ +rvStatAllocator + +A simple block allocator for different stat objects. +Note: this isn't a real heap; it has no delete or tracking of free blocks. +It matches the memory usage of the rvStatManager which dumps its entire +memory all at once at very specific times. If that ever changes, this will +need to change. +================ +*/ + +const int BLOCK_SIZE = 1024; +const int MAX_BLOCKS = 128; + +class rvStatAllocator { + protected: + byte blocks[ BLOCK_SIZE * MAX_BLOCKS ]; + short currentBlock; // current block + size_t placeInBlock; // current position, offset from start of block + size_t totalBytesUsed; // total bytes handed out from the allocator + size_t totalAllocations; // total blocks handed out from the allocator + size_t totalBytesAllocated; // total bytes in all allocated blocks + size_t allocationsByType[ST_COUNT]; // one spare at [0]; allocations per type of event + + void *GetBlock( size_t blockSize, int* blockNumOut = NULL ); + + public: + rvStatAllocator(); + void Report(); + void Reset(); + + // object allocators + rvStatBeginGame *AllocStatBeginGame ( int t, int* blockNumOut = NULL ); + rvStatEndGame *AllocStatEndGame ( int t, int* blockNumOut = NULL ); + rvStatClientConnect *AllocStatClientConnect ( int t, int client, int* blockNumOut = NULL ); + rvStatHit *AllocStatHit ( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut = NULL ); + rvStatKill *AllocStatKill ( int t, int p, int v, bool g, int mod, int* blockNumOut = NULL ); + rvStatDeath *AllocStatDeath ( int t, int p, int mod, int* blockNumOut = NULL ); + rvStatDamageDealt *AllocStatDamageDealt ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatDamageTaken *AllocStatDamageTaken ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatFlagDrop *AllocStatFlagDrop ( int t, int p, int a, int tm, int* blockNumOut = NULL ); + rvStatFlagReturn *AllocStatFlagReturn ( int t, int p, int tm, int* blockNumOut = NULL ); + rvStatFlagCapture *AllocStatFlagCapture ( int t, int p, int f, int tm, int* blockNumOut = NULL ); + + // bookkeeping readers + ID_INLINE size_t GetBytesLeftInBlock() const { + assert( placeInBlock <= BLOCK_SIZE ); + return ( size_t )( BLOCK_SIZE - placeInBlock ); + } + + ID_INLINE size_t GetPlaceInBlock() const { + return placeInBlock; + } + + ID_INLINE size_t GetTotalBytesUsed() const { + return totalBytesUsed; + } + + ID_INLINE size_t GetTotalAllocations() const { + return totalAllocations; + } + + ID_INLINE size_t GetAllocationsByType( statType_t type ) const { + return allocationsByType[ type ]; + } + + ID_INLINE size_t GetTotalBytesAllocated() const { + return totalBytesAllocated; + } +}; + + +/* +================ +rvPlayerStat + +Stores one player's stat information. +================ +*/ +class rvPlayerStat { +public: + rvPlayerStat(); + + void Clear( void ); + void CalculateStats( int p, const idList& playerStats ); + + void PackStats( idBitMsg& msg ); + void UnpackStats( const idBitMsg& msg ); + + int weaponShots[ MAX_WEAPONS ]; + int weaponHits[ MAX_WEAPONS ]; + + int weaponKills[ MAX_WEAPONS ]; + + int kills; + int deaths; + int suicides; + float damageRatio; + +// asalmon: added for Xenon +// ddynerman: also used on PC + int damageGiven; + int damageTaken; + + idList endGameAwards; +// asalmon: changed this to just an array of counts + int inGameAwards[IGA_NUM_AWARDS]; + + int lastUpdateTime; +}; + +/* +================ +rvStatManager + +The statistics management interface. This is the entrypoint into +statistics from the game. + +The game calls into rvStatManager at appropriate times (i.e. 'FlagCaptured') + +rvStatManager creates stat events (see StatEvent.h) with the appropriate +information and append them to the statQueue. + +The statQueue is parsed at the end of a game to tabulate statistics, give +end-game awards. + +================ +*/ +class rvStatManager { +public: + rvStatManager(); + + void Init( void ); + void Shutdown( void ); + + // generic events + void BeginGame( void ); + void EndGame( void ); + void ClientConnect( int clientNum ); + void ClientDisconnect( int clientNum ); + void WeaponFired( const idPlayer* player, int weapon, int num ); + void WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy = true ); + void Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ); + + + // ctf-specific events + void FlagCaptured( const idPlayer* player, int flagTeam ); + void FlagDropped( const idPlayer* player, const idEntity* attacker ); + void FlagReturned( const idPlayer* player ); + + + // shared events + void Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ); + + void DebugPrint( void ); + + rvPlayerStat* GetPlayerStats( void ) { return playerStats; }; + rvPlayerStat* GetPlayerStat( int clientNum ); + + void CalculateEndGameStats( void ); + + void GivePlayerCashForAward( idPlayer* player, inGameAward_t award ); + void GiveInGameAward( inGameAward_t award, int clientNum ); + + // network transmission + void SendStat( int toClient, int statClient ); + void ReceiveStat( const idBitMsg& msg ); + + void SendInGameAward( inGameAward_t award, int clientNum ); + void ReceiveInGameAward( const idBitMsg& msg ); + void CheckAwardQueue(); + + void ClearStats( void ); + + int DamageGiven( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + int DamageTaken( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + + void UpdateInGameHud( idUserInterface* statHud, bool visible ); + void UpdateEndGameHud( idUserInterface* statHud, int clientNum ); + void SetupEndGameHud( idUserInterface* statHud ); + + rvStat* GetLastClientStat( int clientNum, statType_t type, int time ); + void GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ); + rvStatTeam* GetLastTeamStat( int team, statType_t type, int time ); + rvStat* GetStat( int i ); + + void GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ); + + void SetupStatWindow( idUserInterface* statHud ); + void SelectStatWindow( int selectionIndex, int selectionTeam ); + int GetSelectedClientNum( int* selectionIndexOut = NULL, int* selectionTeamOut = NULL ); + + //asalmon: Sends all stats to all clients. For Xenon periodic update of stats. + void SendAllStats( int clientNum = -1, bool full = true ); + void ReceiveAllStats( const idBitMsg& msg ); + + void SetDamageGiven(int client, int damage) { playerStats[client].damageGiven = damage; } + void SetDamageTaken(int client, int damage) { playerStats[client].damageTaken = damage; } + + int FreeEvents( int blockNum ); + + static comboKillState_t comboKillState[ MAX_CLIENTS ]; + static int lastRailShot[ MAX_CLIENTS ]; + static int lastRailShotHits[ MAX_CLIENTS ]; + +private: + idList > statQueue; + idList > awardQueue; +// idList inGameStats; +// rvStatInGame previousInGameStats[ MAX_CLIENTS ]; +// rvStatInGame localClientInGameStats; + + // local hud support + int localInGameAwards[ IGA_NUM_AWARDS ]; + int inGameAwardHudTime; + + rvPlayerStat playerStats[ MAX_CLIENTS ]; + + bool endGameSetup; + + rvStatWindow statWindow; + + // shouchard: stat allocator to avoid lots of little news + rvStatAllocator statAllocator; + +#ifdef _XENON + int lastFullUpdate; +#endif + +#if ID_TRAFFICSTATS + int startSent; + int startPacketsSent; + int startReceived; + int startPacketsReceived; + int startTime; +#endif +}; + +ID_INLINE rvStat* rvStatManager::GetStat( int i ) { + if( i < 0 || i >= statQueue.Num() ) { + return NULL; + } + + return statQueue[ i ].First(); +} + +extern rvStatManager* statManager; +extern inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ]; +extern endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ]; + +#endif /* !__STATMANAGER_H__ */ diff --git a/source/mpgame/mp/stats/StatWindow.cpp b/source/mpgame/mp/stats/StatWindow.cpp new file mode 100644 index 0000000..353afc8 --- /dev/null +++ b/source/mpgame/mp/stats/StatWindow.cpp @@ -0,0 +1,461 @@ +//---------------------------------------------------------------- +// StatWindow.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" + +#include "StatWindow.h" +#include "StatManager.h" + +/* +================ +rvStatWindow::rvStatWindow() +================ +*/ +rvStatWindow::rvStatWindow() { + statHud = NULL; +} + +/* +================ +rvStatWindow::SetupStatWindow() + +Sets up a selectable window of current stats +================ +*/ +void rvStatWindow::SetupStatWindow( idUserInterface* hud, bool useSpectator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + assert( player ); + if ( !player ) { + return; + } + +// mekberg: added + idList marineScores; + idList stroggScores; + + int selectionIndex = -1; + int selectionTeam = -1; + + statHud = hud; + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + stroggPlayers.Clear(); + marinePlayers.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + idPlayer* player = gameLocal.mpGame.GetRankedPlayer( i ); + + if( player->team == TEAM_MARINE ) { + marinePlayers.Append( player ); + marineScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } else if ( player->team == TEAM_STROGG ) { + stroggPlayers.Append( player ); + stroggScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < marinePlayers.Num() ) { + statHud->SetStateString( va( "team_1_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( marinePlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( marinePlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + marinePlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + marineScores[ i ] ) ); + + if( useSpectator ) { + if( marinePlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } else { + if( marinePlayers[ i ] == player ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_1_names_item_%d", i ), "" ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < stroggPlayers.Num() ) { + statHud->SetStateString( va( "team_2_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( stroggPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( stroggPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + stroggPlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + stroggScores[ i ] ) ); + if( useSpectator ) { + if( stroggPlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } else { + if( stroggPlayers[ i ] == player ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_2_names_item_%d", i ), "" ); + } + } + + statHud->SetStateInt ( "num_strogg_players", stroggPlayers.Num() ); + statHud->SetStateInt ( "num_marine_players", marinePlayers.Num() ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + + players.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + players.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < players.Num() ) { + statHud->SetStateString( va( "dm_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( players[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( players[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + players[ i ]->GetUserInfo()->GetString( "ui_name"), + + gameLocal.mpGame.GetScore( players[ i ] ) ) ); + if( useSpectator ) { + if( players[ i ]->entityNumber == player->spectator ) { + selectionTeam = 0; + selectionIndex = i; + } + } else { + if( players[ i ] == player ) { + selectionTeam = 0; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "dm_names_item_%d", i ), "" ); + } + } + statHud->SetStateInt ( "num_players", players.Num() ); + } + + // spectators + spectators.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumUnrankedPlayers(); i++ ) { + spectators.Append( gameLocal.mpGame.GetUnrankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < spectators.Num() ) { + statHud->SetStateString( va( "spec_names_item_%d", i ), + va( "%s\t%s\t%s\t", ( player->IsFriend( spectators[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( spectators[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + spectators[ i ]->GetUserInfo()->GetString( "ui_name") ) ); + + if( useSpectator ) { + if( spectators[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } else { + if( spectators[ i ] == player ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "spec_names_item_%d", i ), "" ); + + } + } + + statHud->SetStateInt( "gametype", gameLocal.gameType ); + statHud->SetStateInt( "playerteam", gameLocal.GetLocalPlayer()->team ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); + + // we shouldn't ever draw a hud unless we're in-game + if ( selectionIndex >= 0 && selectionTeam >= 0 ) { + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + } +} + +/* +================ +rvStatWindow::ClearWindow() + +Clears the stat part of the stat window, but not the player lists boxes +================ +*/ +void rvStatWindow::ClearWindow( void ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), "" ); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, "" ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 0 ); + statHud->SetStateString( va( "eg_award%d_text", i ), "" ); + } + + // kills + statHud->SetStateString( "stat_frags", "" ); + + // deaths + statHud->SetStateString( "stat_deaths", "" ); + + // score + statHud->SetStateString( "stat_score", "" ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + + +/* +================ +rvStatWindow::SelectPlayer() + +Selects the specified player +================ +*/ +void rvStatWindow::SelectPlayer( int clientNum ) { + if( statHud == NULL ) { + return; + } + + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + ClearWindow(); + return; + } + + assert( gameLocal.GetLocalPlayer() ); + + rvPlayerStat* clientStat = statManager->GetPlayerStat( clientNum ); + + if( gameLocal.isClient && ( clientStat == NULL || ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) ) { + // get new stats + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + int weaponAccuracy = 0; + if( clientStat->weaponShots[ i ] != 0 ) { + weaponAccuracy = (int)(((float)clientStat->weaponHits[ i ] / (float)clientStat->weaponShots[ i ]) * 100.0f); + } + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", weaponAccuracy ) ); + } + + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + igAwardCount[i] = clientStat->inGameAwards[i]; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + if( i < clientStat->endGameAwards.Num() ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); +// RAVEN BEGIN +// rhummer: Localized the award strings.. + statHud->SetStateString( va( "eg_award%d_text", i ), common->GetLocalizedString( endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ) ); +// RAVEN END + } else { + statHud->SetStateInt( va( "eg_award%d", i), 0 ); + } + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) ); + + // score + int score = gameLocal.IsTeamGame() ? (gameLocal.mpGame.GetTeamScore( clientNum ) + gameLocal.mpGame.GetScore( clientNum )): gameLocal.mpGame.GetScore( clientNum ); + statHud->SetStateString( "stat_score", va( "%d", score ) ); + + if( gameLocal.GetLocalPlayer()->IsFriend( clientNum ) ) { + // remove friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200249" ) ); + } else { + // add friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200248" ) ); + } + + if( gameLocal.GetLocalPlayer()->IsPlayerMuted( clientNum ) ) { + // unmute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200251" ) ); + } else { + // mute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200250" ) ); + } + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + +/* +================ +rvStatWindow::ClientNumFromSelection() + +Parses a selection index and team into a clientNum +================ +*/ +int rvStatWindow::ClientNumFromSelection( int selectionIndex, int selectionTeam ) { + int clientNum = -1; + + if( gameLocal.IsTeamGame() ) { + if( selectionTeam == TEAM_MARINE ) { + if( selectionIndex < 0 || selectionIndex >= marinePlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = marinePlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_1_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else if( selectionTeam == TEAM_STROGG ) { + if( selectionIndex < 0 || selectionIndex >= stroggPlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = stroggPlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_2_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } else { + if( selectionTeam == TEAM_MAX ) { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= players.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = players[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "dm_names_sel_0", selectionIndex ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } + + return clientNum; +} + +/* +================ +rvStatWindow::GetSelectedClientNum() + +Queries the gui dict to figure out the currently selected selectionIndex/selectionTeam. +Uses the selectionIndex/selectionTeam to return the selected client num. +================ +*/ +int rvStatWindow::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + if( statHud == NULL ) { + return -1; + } + int selectionIndex = -1; + int selectionTeam = -1; + + // StatWindow update code should assure that only one selection of all these listDefs is is valid + // Find the valid one + if( statHud->State().GetInt( "spec_names_sel_0", "-1" ) >= 0 ) { + selectionIndex = statHud->State().GetInt( "spec_names_sel_0", "-1" ); + selectionTeam = TEAM_MAX; + } + + if ( statHud->State().GetInt( "dm_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "dm_names_sel_0", "-1" ); + selectionTeam = 0; + } + + if ( statHud->State().GetInt( "team_1_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_1_names_sel_0", "-1" ); + selectionTeam = TEAM_MARINE; + } + + if ( statHud->State().GetInt( "team_2_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_2_names_sel_0", "-1" ); + selectionTeam = TEAM_STROGG; + } + + // return the selection index & team + if( selectionIndexOut ) { + (*selectionIndexOut) = selectionIndex; + } + if( selectionTeamOut ) { + (*selectionTeamOut) = selectionTeam; + } + + return ClientNumFromSelection( selectionIndex, selectionTeam ); +} diff --git a/source/mpgame/mp/stats/StatWindow.h b/source/mpgame/mp/stats/StatWindow.h new file mode 100644 index 0000000..f4e9263 --- /dev/null +++ b/source/mpgame/mp/stats/StatWindow.h @@ -0,0 +1,36 @@ +//---------------------------------------------------------------- +// StatWindow.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATWINDOW_H__ +#define __STATWINDOW_H__ + +/* +=============================================================================== + +Stat selection window + +=============================================================================== +*/ + +class rvStatWindow { +public: + rvStatWindow(); + void SetupStatWindow( idUserInterface* statHud, bool useSpectator = false ); + void SelectPlayer( int clientNum ); + int ClientNumFromSelection( int selectionIndex, int selectionTeam ); + void ClearWindow( void ); + int GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ); +private: + idList stroggPlayers; + idList marinePlayers; + idList players; + idList spectators; + + idUserInterface* statHud; +}; + + +#endif diff --git a/source/mpgame/mpgame.def b/source/mpgame/mpgame.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/source/mpgame/mpgame.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/source/mpgame/physics/Clip.cpp b/source/mpgame/physics/Clip.cpp new file mode 100644 index 0000000..b383bc7 --- /dev/null +++ b/source/mpgame/physics/Clip.cpp @@ -0,0 +1,2157 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_SECTOR_DEPTH 12 +#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1) + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +typedef struct clipSector_s { + int contents; + int dynamicContents; + struct clipLink_s * clipLinks; +} clipSector_t; +// RAVEN END + +typedef struct clipLink_s { + idClipModel * clipModel; + struct clipSector_s * sector; + struct clipLink_s * prevInSector; + struct clipLink_s * nextInSector; + struct clipLink_s * nextLink; +} clipLink_t; + +typedef struct trmCache_s { + idTraceModel trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; + const idMaterial * material; + idCollisionModel * collisionModel; + int hash; // only used to identify non-hashed trm's in a save. +} trmCache_t; + +idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON ); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc clipLinkAllocator; +// RAVEN END + +typedef enum { + CPT_NONE = -1, + CPT_TRANSLATION, + CPT_CONTACTS, + CPT_CONTENTS, + + CPT_MAX_TYPES +} CLIP_PROFILE_TYPES; + +#if 0 + +static idTimer clipProfileTimer[ CPT_MAX_TYPES ]; +static CLIP_PROFILE_TYPES currentProfileType = CPT_NONE; +static int clipProfileDepth = 0; + +static void BeginClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth++; + + if ( clipProfileDepth == 1 ) { + clipProfileTimer[ type ].Start(); + currentProfileType = type; + } +} + +static void EndClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth--; + + if ( clipProfileDepth == 0 ) { + clipProfileTimer[ currentProfileType ].Stop(); + currentProfileType = CPT_NONE; + } +} + +void ClearClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + clipProfileTimer[ i ].Clear(); + } +} + +void DisplayClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + common->Printf( "%d:%d ", i, ( int )clipProfileTimer[ i ].Milliseconds() ); + } + common->Printf( "\n" ); +} +#else + +#define BeginClipProfile( type ) + +#define EndClipProfile( type ) + +#endif + + +/* +=============================================================== + + idClipModel trace model cache + +=============================================================== +*/ + +static idList traceModelCache; +static idHashIndex traceModelHash; + +/* +=============== +idClipModel::ClearTraceModelCache +=============== +*/ +void idClipModel::ClearTraceModelCache( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + collisionModelManager->FreeModel( traceModelCache[i]->collisionModel ); + traceModelCache[i]->collisionModel = NULL; + } + traceModelCache.DeleteContents( true ); + traceModelHash.Free(); +} + +/* +=============== +idClipModel::CacheCollisionModels +=============== +*/ +void idClipModel::CacheCollisionModels( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + if ( traceModelCache[i]->collisionModel == NULL ) { + traceModelCache[i]->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", i ), traceModelCache[i]->trm, traceModelCache[i]->material ); + } + } +} + +/* +=============== +idClipModel::TraceModelCacheSize +=============== +*/ +int idClipModel::TraceModelCacheSize( void ) { + return traceModelCache.Num() * sizeof( idTraceModel ); +} + +/* +=============== +idClipModel::AllocTraceModel +=============== +*/ +int idClipModel::AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + int i, hashKey, traceModelIndex; + trmCache_t *entry; + + if ( notHashed ) { + hashKey = 0xffffffff; + } else { + hashKey = GetTraceModelHashKey( trm ); + + for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) { + if ( traceModelCache[i]->trm == trm ) { + traceModelCache[i]->refCount++; + return i; + } + } + } + + + entry = new trmCache_t; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->material = material; + entry->hash = hashKey; + traceModelIndex = traceModelCache.Append( entry ); + + if ( !notHashed ) { + traceModelHash.Add( hashKey, traceModelIndex ); + } + + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", traceModelIndex ), trm, material ); + + return traceModelIndex; +} + +/* +=============== +idClipModel::Replace +=============== +*/ +void idClipModel::ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed ) { + common->Error( "ReplaceTraceModel was misused. Replace can only be used on non-hashed models right now.\n" ); + return; + } + + trmCache_t *entry = traceModelCache[ index ]; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->hash = 0xffffffff; + entry->material = material; + + if(entry->collisionModel) + { + collisionModelManager->FreeModel( entry->collisionModel ); + } + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", index ), trm, material ); +} + +/* +=============== +idClipModel::FreeTraceModel +=============== +*/ +void idClipModel::FreeTraceModel( int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache[traceModelIndex]->refCount--; +} + +/* +=============== +idClipModel::CopyTraceModel +=============== +*/ +int idClipModel::CopyTraceModel( const int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::CopyTraceModel: tried to copy an uncached trace model" ); + return -1; + } + traceModelCache[traceModelIndex]->refCount++; + return traceModelIndex; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) { + return &traceModelCache[traceModelIndex]->trm; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idCollisionModel *idClipModel::GetCachedCollisionModel( int traceModelIndex ) { + return traceModelCache[traceModelIndex]->collisionModel; +} + +/* +=============== +idClipModel::GetTraceModelHashKey +=============== +*/ +int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) { + const idVec3 &v = trm.bounds[0]; + return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() ); +} + +/* +=============== +idClipModel::SaveTraceModels +=============== +*/ +void idClipModel::SaveTraceModels( idSaveGame *savefile ) { + int i; + + savefile->WriteInt( traceModelCache.Num() ); + for ( i = 0; i < traceModelCache.Num(); i++ ) { + trmCache_t *entry = traceModelCache[i]; + + savefile->Write( &entry->trm, sizeof( entry->trm ) ); + savefile->WriteFloat( entry->volume ); + savefile->WriteVec3( entry->centerOfMass ); + savefile->WriteMat3( entry->inertiaTensor ); + savefile->WriteMaterial( entry->material ); + savefile->WriteInt( entry->hash ); + } +} + +/* +=============== +idClipModel::RestoreTraceModels +=============== +*/ +void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) { + int i, num; + + ClearTraceModelCache(); + + savefile->ReadInt( num ); + traceModelCache.SetNum( num ); + for ( i = 0; i < num; i++ ) { + trmCache_t *entry = new trmCache_t; + + savefile->Read( &entry->trm, sizeof( entry->trm ) ); + savefile->ReadFloat( entry->volume ); + savefile->ReadVec3( entry->centerOfMass ); + savefile->ReadMat3( entry->inertiaTensor ); + savefile->ReadMaterial( entry->material ); + savefile->ReadInt( entry->hash ); + + entry->refCount = 0; + entry->collisionModel = NULL; + + traceModelCache[i] = entry; + if ( entry->hash != 0xffffffff ) { + traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i ); + } + } + + CacheCollisionModels(); +} + + +/* +=============================================================== + + idClipModel + +=============================================================== +*/ + +/* +================ +idClipModel::FreeModel +================ +*/ +void idClipModel::FreeModel( void ) { + + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } + + if ( collisionModel != NULL ) { + collisionModelManager->FreeModel( collisionModel ); + collisionModel = NULL; + } + + renderModelHandle = -1; +} + +/* +================ +idClipModel::UpdateDynamicContents +================ +*/ +void idClipModel::UpdateDynamicContents( void ) { + idClip::UpdateDynamicContents( this ); +} + +/* +================ +idClipModel::LoadModel +================ +*/ +bool idClipModel::LoadModel( const char *name ) { + FreeModel(); + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), name ); + if ( collisionModel != NULL ) { + collisionModel->GetBounds( bounds ); + collisionModel->GetContents( contents ); + return true; + } else { + bounds.Zero(); + return false; + } +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed || traceModelIndex == -1 ) { + FreeModel(); + traceModelIndex = AllocTraceModel( trm, material, notHashed ); + } else { + ReplaceTraceModel( traceModelIndex, trm, material, notHashed ); + } + + bounds = trm.bounds; +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const int renderModelHandle ) { + FreeModel(); + this->renderModelHandle = renderModelHandle; + if ( renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + bounds = renderEntity->bounds; + } + } +} + +/* +================ +idClipModel::Init +================ +*/ +void idClipModel::Init( void ) { + enabled = true; + entity = NULL; + id = 0; + owner = NULL; + origin.Zero(); + axis.Identity(); + bounds.Zero(); + absBounds.Zero(); + contents = CONTENTS_BODY; + collisionModel = NULL; + renderModelHandle = -1; + traceModelIndex = -1; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( void ) { + Init(); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const char *name ) { + Init(); + LoadModel( name ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm, const idMaterial *material ) { + Init(); + LoadModel( trm, material ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const int renderModelHandle ) { + Init(); + contents = CONTENTS_RENDERMODEL; + LoadModel( renderModelHandle ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idClipModel *model ) { + enabled = model->enabled; + entity = model->entity; + id = model->id; + owner = model->owner; + origin = model->origin; + axis = model->axis; + bounds = model->bounds; + absBounds = model->absBounds; + contents = model->contents; + collisionModel = NULL; + if ( model->collisionModel != NULL ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), model->collisionModel->GetName() ); + } + traceModelIndex = -1; + if ( model->traceModelIndex != -1 ) { + traceModelIndex = CopyTraceModel( model->traceModelIndex ); + } + renderModelHandle = model->renderModelHandle; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::~idClipModel +================ +*/ +idClipModel::~idClipModel( void ) { + // make sure the clip model is no longer linked + Unlink(); + FreeModel(); +} + +/* +================ +idClipModel::Save +================ +*/ +void idClipModel::Save( idSaveGame *savefile ) const { + savefile->WriteBool( enabled ); + savefile->WriteObject( entity ); + savefile->WriteInt( id ); + savefile->WriteObject( owner ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); + savefile->WriteBounds( bounds ); + savefile->WriteBounds( absBounds ); + savefile->WriteInt( contents ); + if ( collisionModel != NULL ) { + savefile->WriteString( collisionModel->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteInt( traceModelIndex ); + savefile->WriteInt( renderModelHandle ); + savefile->WriteBool( clipLinks != NULL ); + savefile->WriteInt( touchCount ); + + savefile->WriteBool ( checked ); // cnicholson: Added unsaved var +} + +/* +================ +idClipModel::Restore +================ +*/ +void idClipModel::Restore( idRestoreGame *savefile ) { + idStr collisionModelName; + bool linked; + + savefile->ReadBool( enabled ); + savefile->ReadObject( reinterpret_cast( entity ) ); + savefile->ReadInt( id ); + savefile->ReadObject( reinterpret_cast( owner ) ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadBounds( bounds ); + savefile->ReadBounds( absBounds ); + savefile->ReadInt( contents ); + savefile->ReadString( collisionModelName ); + if ( collisionModelName.Length() ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), collisionModelName ); + } else { + collisionModel = NULL; + } + savefile->ReadInt( traceModelIndex ); + if ( traceModelIndex >= 0 ) { + traceModelCache[traceModelIndex]->refCount++; + } + savefile->ReadInt( renderModelHandle ); + savefile->ReadBool( linked ); + savefile->ReadInt( touchCount ); + + savefile->ReadBool ( checked ); // cnicholson: Added unrestored var + + // the render model will be set when the clip model is linked + renderModelHandle = -1; + clipLinks = NULL; + touchCount = -1; + + if ( linked ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Link( entity, id, origin, axis, renderModelHandle ); +// RAVEN END + } +} + +/* +================ +idClipModel::SetPosition +================ +*/ +void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) { + if ( clipLinks ) { + Unlink(); // unlink from old position + } + origin = newOrigin; + axis = newAxis; +} + +/* +================ +idClipModel::GetCollisionModel +================ +*/ +idCollisionModel * idClipModel::GetCollisionModel( void ) const { + assert( renderModelHandle == -1 ); + if ( collisionModel != NULL ) { + return collisionModel; + } else if ( traceModelIndex != -1 ) { + return GetCachedCollisionModel( traceModelIndex ); + } else { + // this happens in multiplayer on the combat models + if ( entity ) { + gameLocal.Warning( "idClipModel::GetCollisionModel: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber ); + } + return 0; + } +} + +/* +================ +idClipModel::GetMassProperties +================ +*/ +void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + if ( traceModelIndex == -1 ) { + gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() ); + } + + trmCache_t *entry = traceModelCache[traceModelIndex]; + mass = entry->volume * density; + centerOfMass = entry->centerOfMass; + inertiaTensor = density * entry->inertiaTensor; +} + +/* +=============== +idClipModel::Unlink +=============== +*/ +void idClipModel::Unlink( void ) { + clipLink_t *link; + + for ( link = clipLinks; link; link = clipLinks ) { + clipLinks = link->nextLink; + if ( link->prevInSector ) { + link->prevInSector->nextInSector = link->nextInSector; + } else { + link->sector->clipLinks = link->nextInSector; + } + if ( link->nextInSector ) { + link->nextInSector->prevInSector = link->prevInSector; + } +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idClip::UpdateDynamicContents( link->sector ); +// RAVEN END + + clipLinkAllocator.Free( link ); + } +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( void ) { + + assert( idClipModel::entity ); + if ( !idClipModel::entity ) { + return; + } + + idClip* clp = gameLocal.GetEntityClipWorld( idClipModel::entity ); + + if ( clipLinks ) { + Unlink(); // unlink from old position + } + + if ( bounds.IsCleared() ) { + return; + } + + // set the abs box + if ( axis.IsRotated() ) { + // expand for rotation + absBounds.FromTransformedBounds( bounds, origin, axis ); + } else { + // normal + absBounds[0] = bounds[0] + origin; + absBounds[1] = bounds[1] + origin; + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + absBounds[0] -= vec3_boxEpsilon; + absBounds[1] += vec3_boxEpsilon; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + clp->CoordsForBounds( coords, absBounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clp->clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + sector->dynamicContents |= GetContents(); + + clipLink_t* link = clipLinkAllocator.Alloc(); + link->clipModel = this; + link->sector = sector; + link->nextInSector = sector->clipLinks; + link->prevInSector = NULL; + if ( sector->clipLinks ) { + sector->clipLinks->prevInSector = link; + } + sector->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; + } + } +// RAVEN END +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) { + this->entity = ent; + this->id = newId; + this->origin = newOrigin; + this->axis = newAxis; + if ( renderModelHandle != -1 ) { + this->renderModelHandle = renderModelHandle; + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + this->bounds = renderEntity->bounds; + } + } + this->Link(); +} +// RAVEN END + + +/* +=============================================================== + + idClip + +=============================================================== +*/ + +// RAVEN BEGIN +// ddynerman: change to static +idClipModel idClip::defaultClipModel; + +idClipModel *idClip::DefaultClipModel( void ) { + // initialize a default clip model + if( defaultClipModel.traceModelIndex == -1 ) { + defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ), NULL ); + } + + return &defaultClipModel; +} + +void idClip::FreeDefaultClipModel( void ) { + if ( defaultClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex ); + defaultClipModel.traceModelIndex = -1; + } +} +// RAVEN END + +/* +=============== +idClip::idClip +=============== +*/ +idClip::idClip( void ) { + clipSectors = NULL; + world = NULL; + worldBounds.Zero(); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::CreateClipSectors_r + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) { +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if( clipSectors ) { + delete[] clipSectors; + clipSectors = NULL; + } + nodeOffsetVisual = bounds[ 0 ]; + + int i; + for( i = 0; i < 3; i++ ) { +//jshepard: this crashes too often +#ifdef _DEBUG + if( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ) { + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + } else { + gameLocal.Error("zero size bounds while creating clipsectors"); + nodeScale[ i ] = depth; + } + if( nodeScale[ i ] ) { + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + } else { + gameLocal.Error("zero size nodeScale while creating clipsectors"); + nodeOffset[ i] = nodeOffset[ i] + 0.5f; + } +#else + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + +#endif + } + + clipSectors = new clipSector_t[ Square( depth ) ]; + memset( clipSectors, 0, Square( depth ) * sizeof( clipSector_t ) ); + return clipSectors; +// RAVEN END +} + +/* +=============== +idClip::Init +=============== +*/ +void idClip::Init( void ) { + idVec3 size, maxSector = vec3_origin; + + // get world map bounds + world = collisionModelManager->LoadModel( gameLocal.GetMapName(), WORLD_MODEL_NAME ); + world->GetBounds( worldBounds ); + + // create world sectors +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// ddynerman: SD's clip sector code + CreateClipSectors_r( CLIPSECTOR_WIDTH, worldBounds, maxSector ); + GetClipSectorsStaticContents(); +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + size = worldBounds[1] - worldBounds[0]; + gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] ); + gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] ); + + // set counters to zero + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::Shutdown +=============== +*/ +void idClip::Shutdown( void ) { + delete[] clipSectors; + clipSectors = NULL; + + // free the trace model used for the temporaryClipModel + if ( temporaryClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex ); + temporaryClipModel.traceModelIndex = -1; + } + + clipLinkAllocator.Shutdown(); +} + +/* +==================== +idClip::ClipModelsTouchingBounds_r +==================== +*/ +typedef struct listParms_s { + idBounds bounds; + int contentMask; + idClipModel ** list; + int count; + int maxCount; +} listParms_t; + + +/* +================ +idClip::ClipModelsTouchingBounds +================ +*/ +int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + listParms_t parms; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int clipCount = 0; + static idClipModel* clipModels[ MAX_GENTITIES ]; + + assert( maxCount <= MAX_GENTITIES ); + if( maxCount > MAX_GENTITIES ) { + maxCount = MAX_GENTITIES; + } +// RAVEN END + + if ( bounds[0][0] > bounds[1][0] || + bounds[0][1] > bounds[1][1] || + bounds[0][2] > bounds[1][2] ) { + // we should not go through the tree for degenerate or backwards bounds + assert( false ); + return 0; + } + + parms.bounds[0] = bounds[0] - vec3_boxEpsilon; + parms.bounds[1] = bounds[1] + vec3_boxEpsilon; + parms.contentMask = contentMask; + parms.list = clipModelList; + parms.count = 0; + parms.maxCount = maxCount; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + CoordsForBounds( coords, parms.bounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + if( !( sector->dynamicContents & contentMask ) ) { + continue; + } + + for ( clipLink_t* link = sector->clipLinks; link && clipCount < MAX_GENTITIES; link = link->nextInSector ) { + idClipModel* model = link->clipModel; + + if( model->checked || !model->enabled || !( model->GetContents() & contentMask ) ) { + continue; + } + + model->checked = true; + clipModels[ clipCount++ ] = model; + } + } + } + + for( x = 0; x < clipCount; x++ ) { + clipModels[ x ]->checked = false; + } + + for( x = 0; x < clipCount; x++ ) { + idClipModel* model = clipModels[ x ]; + + // if the bounds really do overlap + if ( model->absBounds[0].x > parms.bounds[1].x || + model->absBounds[1].x < parms.bounds[0].x || + model->absBounds[0].y > parms.bounds[1].y || + model->absBounds[1].y < parms.bounds[0].y || + model->absBounds[0].z > parms.bounds[1].z || + model->absBounds[1].z < parms.bounds[0].z ) { + continue; + } + + if( parms.count >= parms.maxCount ) { +// gameLocal.Warning( "idClip::ClipModelsTouchingBounds Max Count Hit\n" ); + break; + } + + parms.list[ parms.count++ ] = model; + } +// RAVEN END + + return parms.count; +} + +/* +================ +idClip::EntitiesTouchingBounds +================ +*/ +int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, entCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + entCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < entCount; j++ ) { + if ( entityList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= entCount ) { + if ( entCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return entCount; + } + entityList[entCount] = clipModelList[i]->entity; + entCount++; + } + } + + return entCount; +} + +// RAVEN BEGIN +// ddynerman: MP helper function +/* +================ +idClip::PlayersTouchingBounds +================ +*/ +int idClip::PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **playerList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, playerCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + playerCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < playerCount; j++ ) { + if ( playerList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= playerCount ) { + if ( playerCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return playerCount; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( clipModelList[i]->entity->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerList[playerCount] = static_cast(clipModelList[i]->entity); + playerCount++; + } + } + } + + return playerCount; +} +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + +/* +==================== +idClip::DrawAreaClipSectors +==================== +*/ +void idClip::DrawAreaClipSectors( float range ) const { + idClipModel* clipModels[ MAX_GENTITIES ]; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idBounds bounds; + bounds[0] = player->GetPhysics()->GetOrigin() - idVec3( range, range, range ); + bounds[1] = player->GetPhysics()->GetOrigin() + idVec3( range, range, range ); + + int count = ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + + int i; + for ( i = 0; i < count; i++ ) { + idEntity* owner = clipModels[ i ]->GetEntity(); + + const idVec3& org = clipModels[ i ]->GetOrigin(); + const idBounds& bounds = clipModels[ i ]->GetBounds(); + + gameRenderWorld->DebugBounds( colorCyan, bounds, org ); + gameRenderWorld->DrawText( owner->GetClassname(), org, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + } +} + +/* +==================== +idClip::DrawClipSectors +==================== +*/ +void idClip::DrawClipSectors( void ) const { + idBounds bounds; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + int i; + idVec3 inverseNodeScale; + for( i = 0; i < 3; i++ ) { + inverseNodeScale[ i ] = 1 / nodeScale[ i ]; + } + + const char* filter = g_showClipSectorFilter.GetString(); + idTypeInfo* type = idClass::GetClass( filter ); + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { +// idWinding w( 4 ); + + bounds[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffsetVisual.x + 1; + bounds[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffsetVisual.y + 1; + bounds[ 0 ].z = player->GetPhysics()->GetBounds()[0].z; + + bounds[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffsetVisual.x - 1; + bounds[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffsetVisual.y - 1; + bounds[ 1 ].z = player->GetPhysics()->GetBounds()[1].z; + + idVec3 point; + point.x = ( bounds[ 0 ].x + bounds[ 1 ].x ) * 0.5f; + point.y = ( bounds[ 0 ].y + bounds[ 1 ].y ) * 0.5f; + point.z = 0.f; + +/* point.x = bounds[ 0 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point ); + + point.x = bounds[ 0 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point );*/ + + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + clipLink_t* link = sector->clipLinks; + while ( link ) { + if ( type && !link->clipModel->GetEntity()->IsType( *type ) ) { + link = link->nextInSector; + } else { + break; + } + } + + if( link ) { + + gameRenderWorld->DrawText( link->clipModel->GetEntity()->GetClassname(), point, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DebugBounds( colorMagenta, bounds ); + gameRenderWorld->DebugBounds( colorYellow, link->clipModel->GetBounds(), link->clipModel->GetOrigin() ); + + } else { + +// gameRenderWorld->DrawText( sector->clipLinks->clipModel->GetEntity()->GetClassname(), point, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + + } + } + } +} + +/* +==================== +idClip::GetClipSectorsStaticContents +==================== +*/ +void idClip::GetClipSectorsStaticContents( void ) { + idBounds bounds; + + bounds[ 0 ].x = 0; + bounds[ 0 ].y = 0; + bounds[ 0 ].z = worldBounds[ 0 ].z; + + bounds[ 1 ].x = 1 / nodeScale.x; + bounds[ 1 ].y = 1 / nodeScale.y; + bounds[ 1 ].z = worldBounds[ 1 ].z; + + idTraceModel* trm = new idTraceModel( bounds ); + + idVec3 org; + org.z = 0; + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { + org.x = ( x / nodeScale.x ) + nodeOffset.x; + org.y = ( y / nodeScale.y ) + nodeOffset.y; + + int contents = collisionModelManager->Contents( org, trm, mat3_identity, -1, world, vec3_origin, mat3_default ); + clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ].contents = contents; + } + } + // mwhitlock: Fix leak in SD's code. + delete trm; +} + +// RAVEN END + +/* +==================== +idClip::GetTraceClipModels + + an ent will be excluded from testing if: + cm->entity == passEntity ( don't clip against the pass entity ) + cm->entity == passOwner ( missiles don't clip with owner ) + cm->owner == passEntity ( don't interact with your own missiles ) + cm->owner == passOwner ( don't interact with other missiles from same owner ) +==================== +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 ) const { +// RAVEN END + int i, num; + idClipModel *cm; + idEntity *passOwner; + + num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + + if ( !passEntity ) { + return num; + } + + if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) { + passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner(); + } else { + passOwner = NULL; + } + + for ( i = 0; i < num; i++ ) { + + cm = clipModelList[i]; + + // check if we should ignore this entity + if ( cm->entity == passEntity ) { + clipModelList[i] = NULL; // don't clip against the pass entity + } +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + else if ( cm->entity == passEntity2 ){ + clipModelList[i] = NULL; +// RAVEN END + } else if ( cm->entity == passOwner ) { + clipModelList[i] = NULL; // missiles don't clip with their owner + } else if ( cm->owner ) { + if ( cm->owner == passEntity ) { + clipModelList[i] = NULL; // don't clip against own missiles + } else if ( cm->owner == passOwner ) { + clipModelList[i] = NULL; // don't clip against other missiles from same owner + } + } + } + + return num; +} + +/* +============ +idClip::TraceRenderModel +============ +*/ +void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { + trace.fraction = 1.0f; + + // if the trace is passing through the bounds + if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { + modelTrace_t modelTrace; + + // test with exact render model and modify trace_t structure accordingly + if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { + trace.fraction = modelTrace.fraction; + trace.endAxis = axis; + trace.endpos = modelTrace.point; + trace.c.normal = modelTrace.normal; + trace.c.dist = modelTrace.point * modelTrace.normal; + trace.c.point = modelTrace.point; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.modelFeature = 0; + trace.c.trmFeature = 0; + trace.c.contents = modelTrace.material->GetContentFlags(); + trace.c.material = modelTrace.material; + +// RAVEN BEGIN +// jscott: for material types + trace.c.materialType = modelTrace.materialType; +// RAVEN END + + // NOTE: trace.c.id will be the joint number + touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); + } + } +} + +/* +============ +idClip::TraceModelForClipModel +============ +*/ +const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const { + if ( !mdl ) { + return NULL; + } else { + if ( !mdl->IsTraceModel() ) { + if ( mdl->GetEntity() ) { + gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() ); + } else { + gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() ); + } + } + return idClipModel::GetCachedTraceModel( mdl->traceModelIndex ); + } +} + +/* +============ +idClip::TestHugeTranslation +============ +*/ +ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { + if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + assert( 0 ); + + results.fraction = 0.0f; + results.endpos = start; + results.endAxis = trmAxis; + memset( &results.c, 0, sizeof( results.c ) ); + results.c.point = start; + results.c.entityNum = ENTITYNUM_WORLD; + + if ( mdl->GetEntity() ) { + gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + } else { + gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + } + return true; + } + return false; +} + +/* +============ +idClip::TranslationEntities +============ +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return; + } + + trm = TraceModelForClipModel( mdl ); + + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + + if ( !trm ) { + traceBounds.FromPointTranslation( start, end - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } +} + +/* +============ +idClip::Translation +============ +*/ +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation +bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + BeginClipProfile( CPT_TRANSLATION ); + + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces + if ( g_showCollisionTraces.GetInteger() >= 2 && !g_stopTime.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, start, end, 1000 ); + } +// RAVEN END + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + EndClipProfile( CPT_TRANSLATION ); + return true; + } + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + EndClipProfile( CPT_TRANSLATION ); + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + } + + if ( !trm ) { + traceBounds.FromPointTranslation( start, results.endpos - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start ); + radius = trm->bounds.GetRadius(); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + + results.c.materialType = trace.c.materialType; + + if ( touch->IsTraceModel( ) ) { + results.c.contents = touch->GetContents( ); + } + + if ( results.fraction == 0.0f ) { + break; + } + } + } + + EndClipProfile( CPT_TRANSLATION ); + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Rotation +============ +*/ +bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + trace_t trace; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( !trm ) { + traceBounds.FromPointRotation( start, rotation ); + } else { + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Motion +============ +*/ +bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idVec3 dir, endPosition; + idBounds traceBounds; + float radius; + trace_t translationalTrace, rotationalTrace, trace; + idRotation endRotation; + const idTraceModel *trm; + + assert( rotation.GetOrigin() == start ); + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) { + // if no translation + if ( start == end ) { + // pure rotation + return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + } else if ( start != end ) { + // pure translation + return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity ); + } else { + // no motion + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis; + return false; + } + + trm = TraceModelForClipModel( mdl ); + + radius = trm->bounds.GetRadius(); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // translational collision with world + idClip::numTranslations++; + collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &translationalTrace, 0, sizeof( translationalTrace ) ); + translationalTrace.fraction = 1.0f; + translationalTrace.endpos = end; + translationalTrace.endAxis = trmAxis; + } + + if ( translationalTrace.fraction != 0.0f ) { + + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + dir = translationalTrace.endpos - start; + for ( i = 0; i < 3; i++ ) { + if ( dir[i] < 0.0f ) { + traceBounds[0][i] += dir[i]; + } + else { + traceBounds[1][i] += dir[i]; + } + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < translationalTrace.fraction ) { + translationalTrace = trace; + translationalTrace.c.entityNum = touch->entity->entityNumber; + translationalTrace.c.id = touch->id; + if ( translationalTrace.fraction == 0.0f ) { + break; + } + } + } + } else { + num = -1; + } + + endPosition = translationalTrace.endpos; + endRotation = rotation; + endRotation.SetOrigin( endPosition ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // rotational collision with world + idClip::numRotations++; + collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &rotationalTrace, 0, sizeof( rotationalTrace ) ); + rotationalTrace.fraction = 1.0f; + rotationalTrace.endpos = endPosition; + rotationalTrace.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( rotationalTrace.fraction != 0.0f ) { + + if ( num == -1 ) { + traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation ); + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + } + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision detection with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < rotationalTrace.fraction ) { + rotationalTrace = trace; + rotationalTrace.c.entityNum = touch->entity->entityNumber; + rotationalTrace.c.id = touch->id; + if ( rotationalTrace.fraction == 0.0f ) { + break; + } + } + } + } + + if ( rotationalTrace.fraction < 1.0f ) { + results = rotationalTrace; + } else { + results = translationalTrace; + results.endAxis = rotationalTrace.endAxis; + } + + results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction ); + + return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f ); +} + +/* +============ +idClip::Contacts +============ +*/ +int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + BeginClipProfile( CPT_CONTACTS ); + + int i, j, num, n, numContacts; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContacts++; + numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + numContacts = 0; + } + + for ( i = 0; i < numContacts; i++ ) { + contacts[i].entityNum = ENTITYNUM_WORLD; + contacts[i].id = 0; + } + + if ( numContacts >= maxContacts ) { + EndClipProfile( CPT_CONTACTS ); + return numContacts; + } + + if ( !trm ) { + traceBounds = idBounds( start ).Expand( depth ); + } else { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + traceBounds.ExpandSelf( depth ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contacts with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numContacts++; + n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts, + start, dir, depth, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + for ( j = 0; j < n; j++ ) { + contacts[numContacts].entityNum = touch->entity->entityNumber; + contacts[numContacts].id = touch->id; + numContacts++; + } + + if ( numContacts >= maxContacts ) { + break; + } + } + + EndClipProfile( CPT_CONTACTS ); + + return numContacts; +} + +/* +============ +idClip::Contents +============ +*/ +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. +int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { +// RAVEN END + BeginClipProfile( CPT_CONTENTS ); + + int i, num, contents; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContents++; + contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + contents = 0; + } + + if ( !trm ) { + traceBounds[0] = start; + traceBounds[1] = start; + } else if ( trmAxis.IsRotated() ) { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + } else { + traceBounds[0] = trm->bounds[0] + start; + traceBounds[1] = trm->bounds[1] + start; + } + + num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contents test with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + // if the entity does not have any contents we are looking for + if ( ( touch->contents & contentMask ) == 0 ) { + continue; + } + + // if the entity has no new contents flags + if ( ( touch->contents & contents ) == touch->contents ) { + continue; + } + + idClip::numContents++; + if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->GetCollisionModel(), touch->origin, touch->axis ) ) { + contents |= ( touch->contents & contentMask ); + } + +// RAVEN BEGIN + // Only sends back one entity for now. Ahh well, if this is a problem, have it send back a list... + if ( touchedEntity ) + { + *touchedEntity = touch->GetEntity(); + } +// RAVEN END + } + + EndClipProfile( CPT_CONTENTS ); + + return contents; +} + +/* +============ +idClip::TranslationModel +============ +*/ +void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::RotationModel +============ +*/ +void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContactsModel +============ +*/ +int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContacts++; + return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContentsModel +============ +*/ +int idClip::ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContents++; + return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::GetModelContactFeature +============ +*/ +bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + int i; + idCollisionModel *model; + idVec3 start, end; + + model = NULL; + winding.Clear(); + + if ( clipModel == NULL ) { + model = NULL; + } else { + if ( clipModel->renderModelHandle != -1 ) { + winding += contact.point; + return true; + } else if ( clipModel->traceModelIndex != -1 ) { + model = idClipModel::GetCachedCollisionModel( clipModel->traceModelIndex ); + } else { + model = clipModel->collisionModel; + } + } + + // if contact with a collision model + if ( model != NULL ) { + switch( contact.type ) { + case CONTACT_EDGE: { + // the model contact feature is a collision model edge + model->GetEdge( contact.modelFeature, start, end ); + winding += start; + winding += end; + break; + } + case CONTACT_MODELVERTEX: { + // the model contact feature is a collision model vertex + model->GetVertex( contact.modelFeature, start ); + winding += start; + break; + } + case CONTACT_TRMVERTEX: { + // the model contact feature is a collision model polygon + model->GetPolygon( contact.modelFeature, winding ); + break; + } + } + } + + // transform the winding to world space + if ( clipModel ) { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() *= clipModel->axis; + winding[i].ToVec3() += clipModel->origin; + } + } + + return true; +} + +/* +============ +idClip::PrintStatistics +============ +*/ +void idClip::PrintStatistics( void ) { +// RAVEN BEGIN +// rjohnson: added trace model cache size + gameLocal.Printf( "t=%-3d, r=%-3d, m=%-3d, render=%-3d, contents=%-3d, contacts=%-3d, cache=%d\n", + numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts, traceModelCache.Num() * sizeof( idTraceModel ) ); +// RAVEN END +} + +/* +============ +idClip::DrawClipModels +============ +*/ +void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type ) { + int i, num; + idBounds bounds; + idClipModel *clipModelList[MAX_GENTITIES]; + idClipModel *clipModel; + + bounds = idBounds( eye ).Expand( radius ); + + num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < num; i++ ) { + clipModel = clipModelList[i]; + if ( clipModel->GetEntity() == passEntity ) { + continue; + } + if ( type && !clipModel->GetEntity()->IsType( *type ) ) { + continue; + } + if ( clipModel->renderModelHandle != -1 ) { + gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() ); + continue; + } + + idCollisionModel *model = clipModel->GetCollisionModel(); + if ( model != NULL ) { + collisionModelManager->DrawModel( model, clipModel->GetOrigin(), clipModel->GetAxis(), eye, mat3_identity, radius ); + } + } +} + +/* +============ +idClip::DrawModelContactFeature +============ +*/ +bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const { + int i; + idMat3 axis; + idFixedWinding winding; + + if ( !GetModelContactFeature( contact, clipModel, winding ) ) { + return false; + } + + axis = contact.normal.ToMat3(); + + if ( winding.GetNumPoints() == 1 ) { + gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime ); + } else { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime ); + } + } + + axis[0] = -axis[0]; + axis[2] = -axis[2]; + gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added debug hud support + +void idClip::DebugHudStatistics( void ) +{ + gameDebug.SetInt( "physics_translations", numTranslations ); + gameDebug.SetInt( "physics_rotations", numRotations ); + gameDebug.SetInt( "physics_motions", numMotions ); + gameDebug.SetInt( "physics_render_model_traces", numRenderModelTraces ); + gameDebug.SetInt( "physics_contents", numContents ); + gameDebug.SetInt( "physics_contacts", numContacts ); +} + +void idClip::ClearStatistics( void ) +{ + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( clipSector_t* sector ) { + sector->dynamicContents = 0; + + clipLink_t* link; + for( link = sector->clipLinks; link; link = link->nextInSector ) { + sector->dynamicContents |= link->clipModel->GetContents(); + } +} + +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( idClipModel* clipModel ) { + clipLink_s* link = clipModel->clipLinks; + while ( link ) { + idClip::UpdateDynamicContents( link->sector ); + link = link->nextLink; + } +} +// RAVEN END diff --git a/source/mpgame/physics/Clip.h b/source/mpgame/physics/Clip.h new file mode 100644 index 0000000..b7bf837 --- /dev/null +++ b/source/mpgame/physics/Clip.h @@ -0,0 +1,407 @@ + +#ifndef __CLIP_H__ +#define __CLIP_H__ + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +const int CLIPSECTOR_DEPTH = 6; +const int CLIPSECTOR_WIDTH = 1 << CLIPSECTOR_DEPTH; +// RAVEN END + +/* +=============================================================================== + + Handles collision detection with the world and between physics objects. + +=============================================================================== +*/ + +#define CLIPMODEL_ID_TO_JOINT_HANDLE( id ) ( ( id ) >= 0 ? INVALID_JOINT : ((jointHandle_t) ( -1 - id )) ) +#define JOINT_HANDLE_TO_CLIPMODEL_ID( id ) ( -1 - id ) + +class idClip; +class idClipModel; +class idEntity; + + +//=============================================================== +// +// idClipModel +// +//=============================================================== + +class idClipModel { + + friend class idClip; + +public: + idClipModel( void ); + explicit idClipModel( const char *name ); + explicit idClipModel( const idTraceModel &trm, const idMaterial *material = NULL ); + explicit idClipModel( const int renderModelHandle ); + explicit idClipModel( const idClipModel *model ); + ~idClipModel( void ); +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void UpdateDynamicContents( void ); +// RAVEN END + + bool LoadModel( const char *name ); + void LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + void LoadModel( const int renderModelHandle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void Link( void ); // must have been linked with an entity and id before + void Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); +// RAVEN END + void Unlink( void ); // unlink from sectors + void SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ); // unlinks the clip model + void Translate( const idVec3 &translation ); // unlinks the clip model + void Rotate( const idRotation &rotation ); // unlinks the clip model + void Enable( void ); // enable for clipping + void Disable( void ); // keep linked but disable for clipping + void SetContents( int newContents ); // override contents + int GetContents( void ) const; + void SetEntity( idEntity *newEntity ); + idEntity * GetEntity( void ) const; + void SetId( int newId ); + int GetId( void ) const; + void SetOwner( idEntity *newOwner ); + idEntity * GetOwner( void ) const; + const idBounds & GetBounds( void ) const; + const idBounds & GetAbsBounds( void ) const; + const idVec3 & GetOrigin( void ) const; + const idMat3 & GetAxis( void ) const; + bool IsTraceModel( void ) const; // returns true if this is a trace model + bool IsRenderModel( void ) const; // returns true if this is a render model + bool IsLinked( void ) const; // returns true if the clip model is linked + bool IsEnabled( void ) const; // returns true if enabled for collision detection + bool IsEqual( const idTraceModel &trm ) const; + idCollisionModel * GetCollisionModel( void ) const; // returns handle used to collide vs this model + const idTraceModel * GetTraceModel( void ) const; + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + + static void ClearTraceModelCache( void ); + static int TraceModelCacheSize( void ); + static void SaveTraceModels( idSaveGame *savefile ); + static void RestoreTraceModels( idRestoreGame *savefile ); + +private: + bool enabled; // true if this clip model is used for clipping +// RAVEN BEGIN + bool checked; // Splash's clip model code +// RAVEN END + idEntity * entity; // entity using this clip model + int id; // id for entities that use multiple clip models + idEntity * owner; // owner of the entity that owns this clip model + idVec3 origin; // origin of clip model + idMat3 axis; // orientation of clip model + idBounds bounds; // bounds + idBounds absBounds; // absolute bounds + int contents; // all contents ored together + idCollisionModel * collisionModel; // handle to collision model + int traceModelIndex; // trace model used for collision detection + int renderModelHandle; // render model def handle + + struct clipLink_s * clipLinks; // links into sectors + int touchCount; + + void Init( void ); // initialize + void FreeModel( void ); + void Link_r( struct clipSector_s *node ); + + static void CacheCollisionModels( void ); + static int AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void FreeTraceModel( int traceModelIndex ); + static int CopyTraceModel( const int traceModelIndex ); + static idTraceModel * GetCachedTraceModel( int traceModelIndex ); + static idCollisionModel*GetCachedCollisionModel( int traceModelIndex ); + static int GetTraceModelHashKey( const idTraceModel &trm ); +}; + + +ID_INLINE void idClipModel::Translate( const idVec3 &translation ) { + Unlink(); + origin += translation; +} + +ID_INLINE void idClipModel::Rotate( const idRotation &rotation ) { + Unlink(); + origin *= rotation; + axis *= rotation.ToMat3(); +} + +ID_INLINE void idClipModel::Enable( void ) { + enabled = true; +} + +ID_INLINE void idClipModel::Disable( void ) { + enabled = false; +} + +ID_INLINE void idClipModel::SetContents( int newContents ) { + contents = newContents; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + UpdateDynamicContents(); +// RAVEN END +} + +ID_INLINE int idClipModel::GetContents( void ) const { + return contents; +} + +ID_INLINE void idClipModel::SetEntity( idEntity *newEntity ) { + entity = newEntity; +} + +ID_INLINE idEntity *idClipModel::GetEntity( void ) const { + return entity; +} + +ID_INLINE void idClipModel::SetId( int newId ) { + id = newId; +} + +ID_INLINE int idClipModel::GetId( void ) const { + return id; +} + +ID_INLINE void idClipModel::SetOwner( idEntity *newOwner ) { + owner = newOwner; +} + +ID_INLINE idEntity *idClipModel::GetOwner( void ) const { + return owner; +} + +ID_INLINE const idBounds &idClipModel::GetBounds( void ) const { + return bounds; +} + +ID_INLINE const idBounds &idClipModel::GetAbsBounds( void ) const { + return absBounds; +} + +ID_INLINE const idVec3 &idClipModel::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idClipModel::GetAxis( void ) const { + return axis; +} + +ID_INLINE bool idClipModel::IsRenderModel( void ) const { + return ( renderModelHandle != -1 ); +} + +ID_INLINE bool idClipModel::IsTraceModel( void ) const { + return ( traceModelIndex != -1 ); +} + +ID_INLINE bool idClipModel::IsLinked( void ) const { + return ( clipLinks != NULL ); +} + +ID_INLINE bool idClipModel::IsEnabled( void ) const { + return enabled; +} + +ID_INLINE bool idClipModel::IsEqual( const idTraceModel &trm ) const { + return ( traceModelIndex != -1 && *GetCachedTraceModel( traceModelIndex ) == trm ); +} + +ID_INLINE const idTraceModel *idClipModel::GetTraceModel( void ) const { + if ( !IsTraceModel() ) { + return NULL; + } + return idClipModel::GetCachedTraceModel( traceModelIndex ); +} + + +//=============================================================== +// +// idClip +// +//=============================================================== + +class idClip { + + friend class idClipModel; + +public: + idClip( void ); + + void Init( void ); + void Shutdown( void ); + + // clip versus the rest of the world +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + bool Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + bool Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. + int Contents( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); +// RAVEN END + + // special case translations versus the rest of the world + bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, + int contentMask, const idEntity *passEntity ); + bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, + int contentMask, const idEntity *passEntity ); + + // clip versus a specific model + void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // clip versus all entities but not the world +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + void TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + + // get a contact feature + bool GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + +// RAVEN BEGIN +// ddynerman: another helper function, useful in MP + int PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; +// RAVEN END + + const idBounds & GetWorldBounds( void ) const; + idCollisionModel * GetWorldCollisionModel( void ) const { return world; } + +// RAVEN BEGIN +// ddynerman: change to static + static idClipModel * DefaultClipModel( void ); + static void FreeDefaultClipModel( void ); +// RAVEN END + + // stats and debug drawing + void PrintStatistics( void ); + void DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type = NULL ); + bool DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const; + +// RAVEN BEGIN +// rjohnson: added debug hud support + void DebugHudStatistics( void ); + void ClearStatistics( void ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void CoordsForBounds( int* coords, idBounds& bounds ) const; + void DrawClipSectors( void ) const; + void DrawAreaClipSectors( float range ) const; + static void UpdateDynamicContents( struct clipSector_s* sector ); + static void UpdateDynamicContents( idClipModel* clipModel ); +// RAVEN END + +private: +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idVec3 nodeScale; + idVec3 nodeOffset; + idVec3 nodeOffsetVisual; +// ddynerman: change to static + static idClipModel defaultClipModel; +// RAVEN END + struct clipSector_s * clipSectors; + idCollisionModel * world; + idBounds worldBounds; + idClipModel temporaryClipModel; + + mutable int touchCount; + // statistics + int numTranslations; + int numRotations; + int numMotions; + int numRenderModelTraces; + int numContents; + int numContacts; + +private: + struct clipSector_s * CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ); + void ClipModelsTouchingBounds_r( const struct clipSector_s *node, struct listParms_s &parms ) const; + const idTraceModel * TraceModelForClipModel( const idClipModel *mdl ) const; +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + int GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 = 0 ) const; +// RAVEN END + void TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void GetClipSectorsStaticContents( void ); +// RAVEN END +}; + + +ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + Translation( results, start, end, NULL, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + temporaryClipModel.LoadModel( idTraceModel( bounds ), NULL, true ); + Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE const idBounds & idClip::GetWorldBounds( void ) const { + return worldBounds; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +ID_INLINE void idClip::CoordsForBounds( int* coords, idBounds& bounds ) const { + float fCoords[ 4 ]; + + fCoords[ 0 ] = ( bounds[ 0 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 1 ] = ( bounds[ 0 ].y - nodeOffset.y ) * nodeScale.y; + fCoords[ 2 ] = ( bounds[ 1 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 3 ] = ( bounds[ 1 ].y - nodeOffset.y ) * nodeScale.y; + + int i; + for( i = 0; i < 4; i++ ) { + + coords[ i ] = idMath::FtoiFast( fCoords[ i ] ); + + if( coords[ i ] < 0 ) { + coords[ i ] = 0; + } else if( coords[ i ] > CLIPSECTOR_WIDTH - 1 ) { + coords[ i ] = CLIPSECTOR_WIDTH - 1; + } + } + coords[ 2 ]++; coords[ 3 ]++; +} +// RAVEN END + +#endif /* !__CLIP_H__ */ diff --git a/source/mpgame/physics/Force.cpp b/source/mpgame/physics/Force.cpp new file mode 100644 index 0000000..9925a4f --- /dev/null +++ b/source/mpgame/physics/Force.cpp @@ -0,0 +1,66 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idClass, idForce ) +END_CLASS + +idList idForce::forceList; + +/* +================ +idForce::idForce +================ +*/ +idForce::idForce( void ) { + forceList.Append( this ); +} + +/* +================ +idForce::~idForce +================ +*/ +idForce::~idForce( void ) { + forceList.Remove( this ); +} + +/* +================ +idForce::DeletePhysics +================ +*/ +void idForce::DeletePhysics( const idPhysics *phys ) { + int i; + + for ( i = 0; i < forceList.Num(); i++ ) { + forceList[i]->RemovePhysics( phys ); + } +} + +/* +================ +idForce::ClearForceList +================ +*/ +void idForce::ClearForceList( void ) { + forceList.Clear(); +} + +/* +================ +idForce::Evaluate +================ +*/ +void idForce::Evaluate( int time ) { +} + +/* +================ +idForce::RemovePhysics +================ +*/ +void idForce::RemovePhysics( const idPhysics *phys ) { +} diff --git a/source/mpgame/physics/Force.h b/source/mpgame/physics/Force.h new file mode 100644 index 0000000..4bd3b2d --- /dev/null +++ b/source/mpgame/physics/Force.h @@ -0,0 +1,39 @@ + +#ifndef __FORCE_H__ +#define __FORCE_H__ + +/* +=============================================================================== + + Force base class + + A force object applies a force to a physics object. + +=============================================================================== +*/ + +class idEntity; +class idPhysics; + +class idForce : public idClass { + +public: + CLASS_PROTOTYPE( idForce ); + + idForce( void ); + virtual ~idForce( void ); + static void DeletePhysics( const idPhysics *phys ); + static void ClearForceList( void ); + +public: // common force interface + // evalulate the force up to the given time + virtual void Evaluate( int time ); + // removes any pointers to the physics object + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + static idList forceList; +}; + +#endif /* !__FORCE_H__ */ diff --git a/source/mpgame/physics/Force_Constant.cpp b/source/mpgame/physics/Force_Constant.cpp new file mode 100644 index 0000000..50ca1f3 --- /dev/null +++ b/source/mpgame/physics/Force_Constant.cpp @@ -0,0 +1,109 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Constant ) +END_CLASS + +/* +================ +idForce_Constant::idForce_Constant +================ +*/ +idForce_Constant::idForce_Constant( void ) { + force = vec3_zero; + physics = NULL; + id = 0; + point = vec3_zero; +} + +/* +================ +idForce_Constant::~idForce_Constant +================ +*/ +idForce_Constant::~idForce_Constant( void ) { +} + +/* +================ +idForce_Constant::Save +================ +*/ +void idForce_Constant::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( force ); + // TOSAVE: idPhysics * physics + savefile->WriteInt( id ); + savefile->WriteVec3( point ); +} + +/* +================ +idForce_Constant::Restore +================ +*/ +void idForce_Constant::Restore( idRestoreGame *savefile ) { + // Owner needs to call SetPhysics!! + savefile->ReadVec3( force ); + savefile->ReadInt( id ); + savefile->ReadVec3( point ); +} + +/* +================ +idForce_Constant::SetPosition +================ +*/ +void idForce_Constant::SetPosition( idPhysics *physics, int id, const idVec3 &point ) { + this->physics = physics; + this->id = id; + this->point = point; +} + +/* +================ +idForce_Constant::SetForce +================ +*/ +void idForce_Constant::SetForce( const idVec3 &force ) { + this->force = force; +} + +/* +================ +idForce_Constant::SetPhysics +================ +*/ +void idForce_Constant::SetPhysics( idPhysics *physics ) { + this->physics = physics; +} + +/* +================ +idForce_Constant::Evaluate +================ +*/ +void idForce_Constant::Evaluate( int time ) { + idVec3 p; + + if ( !physics ) { + return; + } + + p = physics->GetOrigin( id ) + point * physics->GetAxis( id ); + + physics->AddForce( id, p, force ); +} + +/* +================ +idForce_Constant::RemovePhysics +================ +*/ +void idForce_Constant::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/source/mpgame/physics/Force_Constant.h b/source/mpgame/physics/Force_Constant.h new file mode 100644 index 0000000..3aa5d43 --- /dev/null +++ b/source/mpgame/physics/Force_Constant.h @@ -0,0 +1,44 @@ + +#ifndef __FORCE_CONSTANT_H__ +#define __FORCE_CONSTANT_H__ + +/* +=============================================================================== + + Constant force + +=============================================================================== +*/ + +class idForce_Constant : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Constant ); + + idForce_Constant( void ); + virtual ~idForce_Constant( void ); + + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // constant force + void SetForce( const idVec3 &force ); + // set force position + void SetPosition( idPhysics *physics, int id, const idVec3 &point ); + + void SetPhysics( idPhysics *physics ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + // force properties + idVec3 force; + idPhysics * physics; + int id; + idVec3 point; +}; + +#endif /* !__FORCE_CONSTANT_H__ */ diff --git a/source/mpgame/physics/Force_Drag.cpp b/source/mpgame/physics/Force_Drag.cpp new file mode 100644 index 0000000..9e21f39 --- /dev/null +++ b/source/mpgame/physics/Force_Drag.cpp @@ -0,0 +1,131 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Drag ) +END_CLASS + +/* +================ +idForce_Drag::idForce_Drag +================ +*/ +idForce_Drag::idForce_Drag( void ) { + damping = 0.5f; + dragPosition = vec3_zero; + physics = NULL; + id = 0; + p = vec3_zero; + dragPosition = vec3_zero; +} + +/* +================ +idForce_Drag::~idForce_Drag +================ +*/ +idForce_Drag::~idForce_Drag( void ) { +} + +/* +================ +idForce_Drag::Init +================ +*/ +void idForce_Drag::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Drag::SetPhysics +================ +*/ +void idForce_Drag::SetPhysics( idPhysics *phys, int id, const idVec3 &p ) { + this->physics = phys; + this->id = id; + this->p = p; +} + +/* +================ +idForce_Drag::SetDragPosition +================ +*/ +void idForce_Drag::SetDragPosition( const idVec3 &pos ) { + this->dragPosition = pos; +} + +/* +================ +idForce_Drag::GetDragPosition +================ +*/ +const idVec3 &idForce_Drag::GetDragPosition( void ) const { + return this->dragPosition; +} + +/* +================ +idForce_Drag::GetDraggedPosition +================ +*/ +const idVec3 idForce_Drag::GetDraggedPosition( void ) const { + return ( physics->GetOrigin( id ) + p * physics->GetAxis( id ) ); +} + +/* +================ +idForce_Drag::Evaluate +================ +*/ +void idForce_Drag::Evaluate( int time ) { + float l1, l2, mass; + idVec3 dragOrigin, dir1, dir2, velocity, centerOfMass; + idMat3 inertiaTensor; + idRotation rotation; + idClipModel *clipModel; + + if ( !physics ) { + return; + } + + clipModel = physics->GetClipModel( id ); + if ( clipModel != NULL && clipModel->IsTraceModel() ) { + clipModel->GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + } else { + centerOfMass.Zero(); + } + + centerOfMass = physics->GetOrigin( id ) + centerOfMass * physics->GetAxis( id ); + dragOrigin = physics->GetOrigin( id ) + p * physics->GetAxis( id ); + + dir1 = dragPosition - centerOfMass; + dir2 = dragOrigin - centerOfMass; + l1 = dir1.Normalize(); + l2 = dir2.Normalize(); + + rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.GetMSec() ), id ); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.GetMSec ( ) ) ); +// RAVEN END + physics->SetLinearVelocity( velocity, id ); +} + +/* +================ +idForce_Drag::RemovePhysics +================ +*/ +void idForce_Drag::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/source/mpgame/physics/Force_Drag.h b/source/mpgame/physics/Force_Drag.h new file mode 100644 index 0000000..e829fab --- /dev/null +++ b/source/mpgame/physics/Force_Drag.h @@ -0,0 +1,47 @@ + +#ifndef __FORCE_DRAG_H__ +#define __FORCE_DRAG_H__ + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Drag : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Drag ); + + idForce_Drag( void ); + virtual ~idForce_Drag( void ); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &p ); + // set position to drag towards + void SetDragPosition( const idVec3 &pos ); + // get the position dragged towards + const idVec3 & GetDragPosition( void ) const; + // get the position on the dragged physics object + const idVec3 GetDraggedPosition( void ) const; + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // properties + float damping; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object + idVec3 p; // position on clip model + idVec3 dragPosition; // drag towards this position +}; + +#endif /* !__FORCE_DRAG_H__ */ diff --git a/source/mpgame/physics/Force_Field.cpp b/source/mpgame/physics/Force_Field.cpp new file mode 100644 index 0000000..77e6a90 --- /dev/null +++ b/source/mpgame/physics/Force_Field.cpp @@ -0,0 +1,272 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Field ) +END_CLASS + +/* +================ +idForce_Field::idForce_Field +================ +*/ +idForce_Field::idForce_Field( void ) { + type = FORCEFIELD_UNIFORM; + applyType = FORCEFIELD_APPLY_FORCE; + magnitude = 0.0f; + dir.Set( 0, 0, 1 ); + randomTorque = 0.0f; + playerOnly = false; + monsterOnly = false; + clipModel = NULL; +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = -1; + owner = NULL; +// RAVEN END +} + +/* +================ +idForce_Field::~idForce_Field +================ +*/ +idForce_Field::~idForce_Field( void ) { + if ( this->clipModel ) { + delete this->clipModel; + } +} + +/* +================ +idForce_Field::Save +================ +*/ +void idForce_Field::Save( idSaveGame *savefile ) const { + savefile->WriteInt( type ); + savefile->WriteInt( applyType); + savefile->WriteFloat( magnitude ); + savefile->WriteVec3( dir ); + savefile->WriteFloat( randomTorque ); + savefile->WriteBool( playerOnly ); + savefile->WriteBool( monsterOnly ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteInt ( lastApplyTime ); // cnicholson: Added unsaved var + // TOSAVE: idEntity* owner; +} + +/* +================ +idForce_Field::Restore +================ +*/ +void idForce_Field::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)type ); + savefile->ReadInt( (int &)applyType); + savefile->ReadFloat( magnitude ); + savefile->ReadVec3( dir ); + savefile->ReadFloat( randomTorque ); + savefile->ReadBool( playerOnly ); + savefile->ReadBool( monsterOnly ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadInt ( lastApplyTime ); // cnicholson: Added unrestored var +} + +/* +================ +idForce_Field::SetClipModel +================ +*/ +void idForce_Field::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && clipModel != this->clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idForce_Field::Uniform +================ +*/ +void idForce_Field::Uniform( const idVec3 &force ) { + dir = force; + magnitude = dir.Normalize(); + type = FORCEFIELD_UNIFORM; +} + +/* +================ +idForce_Field::Explosion +================ +*/ +void idForce_Field::Explosion( float force ) { + magnitude = force; + type = FORCEFIELD_EXPLOSION; +} + +/* +================ +idForce_Field::Implosion +================ +*/ +void idForce_Field::Implosion( float force ) { + magnitude = force; + type = FORCEFIELD_IMPLOSION; +} + +/* +================ +idForce_Field::RandomTorque +================ +*/ +void idForce_Field::RandomTorque( float force ) { + randomTorque = force; +} + +/* +================ +idForce_Field::Evaluate +================ +*/ +void idForce_Field::Evaluate( int time ) { + int numClipModels, i; + idBounds bounds; + idVec3 force, torque, angularVelocity; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + assert( clipModel ); + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( owner, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + idPhysics *physics = entity->GetPhysics(); + + if ( playerOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Player::GetClassType() ) ) { +// RAVEN END + continue; + } + } else if ( monsterOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Monster::GetClassType() ) ) { + + continue; + } + } + +// nrausch: It was undesireable to have gibs and discarded weapons bouncing on jump pads + if ( gameLocal.isMultiplayer ) { + if ( entity->IsType( idItem::GetClassType() ) ) { + continue; + } + if ( entity->IsType( rvClientPhysics::GetClassType() ) ) { + continue; + } + if ( entity->IsType( idPlayer::GetClassType() ) ) { + if ( ((idPlayer*)entity)->health <= 0 ) { + continue; + } + } + + } + +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( owner, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + switch( type ) { + case FORCEFIELD_UNIFORM: { + force = dir; + break; + } + case FORCEFIELD_EXPLOSION: { + force = cm->GetOrigin() - clipModel->GetOrigin(); + force.Normalize(); + break; + } + case FORCEFIELD_IMPLOSION: { + force = clipModel->GetOrigin() - cm->GetOrigin(); + force.Normalize(); + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid type" ); + break; + } + } + + if ( randomTorque != 0.0f ) { + torque[0] = gameLocal.random.CRandomFloat(); + torque[1] = gameLocal.random.CRandomFloat(); + torque[2] = gameLocal.random.CRandomFloat(); + if ( torque.Normalize() == 0.0f ) { + torque[2] = 1.0f; + } + } + + switch( applyType ) { + case FORCEFIELD_APPLY_FORCE: { + if ( randomTorque != 0.0f ) { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + case FORCEFIELD_APPLY_VELOCITY: { + physics->SetLinearVelocity( force * magnitude, cm->GetId() ); + if ( randomTorque != 0.0f ) { + angularVelocity = physics->GetAngularVelocity( cm->GetId() ); + physics->SetAngularVelocity( 0.5f * (angularVelocity + torque * randomTorque), cm->GetId() ); + } + break; + } + case FORCEFIELD_APPLY_IMPULSE: { + if ( randomTorque != 0.0f ) { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid apply type" ); + break; + } + } + +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = time; +// RAVEN END + } +} diff --git a/source/mpgame/physics/Force_Field.h b/source/mpgame/physics/Force_Field.h new file mode 100644 index 0000000..c9ec802 --- /dev/null +++ b/source/mpgame/physics/Force_Field.h @@ -0,0 +1,92 @@ + +#ifndef __FORCE_FIELD_H__ +#define __FORCE_FIELD_H__ + +/* +=============================================================================== + + Force field + +=============================================================================== +*/ + +enum forceFieldType { + FORCEFIELD_UNIFORM, + FORCEFIELD_EXPLOSION, + FORCEFIELD_IMPLOSION +}; + +enum forceFieldApplyType { + FORCEFIELD_APPLY_FORCE, + FORCEFIELD_APPLY_VELOCITY, + FORCEFIELD_APPLY_IMPULSE +}; + +class idForce_Field : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Field ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Field( void ); + virtual ~idForce_Field( void ); + // uniform constant force + void Uniform( const idVec3 &force ); + // explosion from clip model origin + void Explosion( float force ); + // implosion towards clip model origin + void Implosion( float force ); + // add random torque + void RandomTorque( float force ); + // should the force field apply a force, velocity or impulse + void SetApplyType( const forceFieldApplyType type ) { applyType = type; } + // make the force field only push players + void SetPlayerOnly( bool set ) { playerOnly = set; } + // make the force field only push monsters + void SetMonsterOnly( bool set ) { monsterOnly = set; } + // clip model describing the extents of the force field + void SetClipModel( idClipModel *clipModel ); + +// RAVEN BEGIN +// ddynerman: owner information + void SetOwner( idEntity* ent ); +// bdube: added last apply time + int GetLastApplyTime( void ) const; +// RAVEN END + +public: // common force interface + virtual void Evaluate( int time ); + +private: + // force properties + forceFieldType type; + forceFieldApplyType applyType; + float magnitude; + idVec3 dir; + float randomTorque; + bool playerOnly; + bool monsterOnly; + idClipModel * clipModel; + +// RAVEN BEGIN +// bdube: added last apply time + int lastApplyTime; + idEntity* owner; +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added last apply time +ID_INLINE int idForce_Field::GetLastApplyTime ( void ) const { + return lastApplyTime; +} + +// ddynerman: owner information +ID_INLINE void idForce_Field::SetOwner( idEntity* ent ) { + owner = ent; +} +// RAVEN END + +#endif /* !__FORCE_FIELD_H__ */ diff --git a/source/mpgame/physics/Force_Spring.cpp b/source/mpgame/physics/Force_Spring.cpp new file mode 100644 index 0000000..dfc8f4b --- /dev/null +++ b/source/mpgame/physics/Force_Spring.cpp @@ -0,0 +1,163 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Spring ) +END_CLASS + +/* +================ +idForce_Spring::idForce_Spring +================ +*/ +idForce_Spring::idForce_Spring( void ) { + Kstretch = 100.0f; + Kcompress = 100.0f; + damping = 0.0f; + restLength = 0.0f; + physics1 = NULL; + id1 = 0; + p1 = vec3_zero; + physics2 = NULL; + id2 = 0; + p2 = vec3_zero; +} + +/* +================ +idForce_Spring::~idForce_Spring +================ +*/ +idForce_Spring::~idForce_Spring( void ) { +} + +/* +================ +idForce_Spring::InitSpring +================ +*/ +void idForce_Spring::InitSpring( float Kstretch, float Kcompress, float damping, float restLength ) { + this->Kstretch = Kstretch; + this->Kcompress = Kcompress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idForce_Spring::SetPosition +================ +*/ +void idForce_Spring::SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, idPhysics *physics2, int id2, const idVec3 &p2 ) { + this->physics1 = physics1; + this->id1 = id1; + this->p1 = p1; + this->physics2 = physics2; + this->id2 = id2; + this->p2 = p2; +} + +/* +================ +idForce_Spring::Evaluate +================ +*/ +void idForce_Spring::Evaluate( int time ) { + float length; + idMat3 axis; + idVec3 pos1, pos2, velocity1, velocity2, force, dampingForce; + impactInfo_t info; + + pos1 = p1; + pos2 = p2; + velocity1 = velocity2 = vec3_origin; + + if ( physics1 ) { + axis = physics1->GetAxis( id1 ); + pos1 = physics1->GetOrigin( id1 ); + pos1 += p1 * axis; + if ( damping > 0.0f ) { + physics1->GetImpactInfo( id1, pos1, &info ); + velocity1 = info.velocity; + } + } + + if ( physics2 ) { + axis = physics2->GetAxis( id2 ); + pos2 = physics2->GetOrigin( id2 ); + pos2 += p2 * axis; + if ( damping > 0.0f ) { + physics2->GetImpactInfo( id2, pos2, &info ); + velocity2 = info.velocity; + } + } + + force = pos2 - pos1; + dampingForce = ( damping * ( ((velocity2 - velocity1) * force) / (force * force) ) ) * force; + length = force.Normalize(); + + // if the spring is stretched + if ( length > restLength ) { + if ( Kstretch > 0.0f ) { + force = ( Square( length - restLength ) * Kstretch ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, -force ); + } + } + } + else { + if ( Kcompress > 0.0f ) { + force = ( Square( length - restLength ) * Kcompress ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, -force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, force ); + } + } + } +} + +/* +================ +idForce_Spring::RemovePhysics +================ +*/ +void idForce_Spring::RemovePhysics( const idPhysics *phys ) { + if ( physics1 == phys ) { + physics1 = NULL; + } + if ( physics2 == phys ) { + physics2 = NULL; + } +} + +/* +================ +idForce_Spring::Save +================ +*/ +void idForce_Spring::Save( idSaveGame *savefile ) const { + savefile->WriteFloat ( Kstretch ); + savefile->WriteFloat ( Kcompress ); + savefile->WriteFloat ( damping ); + savefile->WriteFloat ( restLength ); +} + +/* +================ +idForce_Spring::Restore +================ +*/ +void idForce_Spring::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat ( Kstretch ); + savefile->ReadFloat ( Kcompress ); + savefile->ReadFloat ( damping ); + savefile->ReadFloat ( restLength ); +} + diff --git a/source/mpgame/physics/Force_Spring.h b/source/mpgame/physics/Force_Spring.h new file mode 100644 index 0000000..a0518b2 --- /dev/null +++ b/source/mpgame/physics/Force_Spring.h @@ -0,0 +1,51 @@ + +#ifndef __FORCE_SPRING_H__ +#define __FORCE_SPRING_H__ + +/* +=============================================================================== + + Spring force + +=============================================================================== +*/ + +class idForce_Spring : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Spring ); + + idForce_Spring( void ); + virtual ~idForce_Spring( void ); + // initialize the spring + void InitSpring( float Kstretch, float Kcompress, float damping, float restLength ); + // set the entities and positions on these entities the spring is attached to + void SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, + idPhysics *physics2, int id2, const idVec3 &p2 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // spring properties + float Kstretch; + float Kcompress; + float damping; + float restLength; + + // positioning + idPhysics * physics1; // first physics object + int id1; // clip model id of first physics object + idVec3 p1; // position on clip model + idPhysics * physics2; // second physics object + int id2; // clip model id of second physics object + idVec3 p2; // position on clip model + +}; + +#endif /* !__FORCE_SPRING_H__ */ diff --git a/source/mpgame/physics/Physics.cpp b/source/mpgame/physics/Physics.cpp new file mode 100644 index 0000000..a772643 --- /dev/null +++ b/source/mpgame/physics/Physics.cpp @@ -0,0 +1,56 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, idPhysics ) +END_CLASS + + +/* +================ +idPhysics::~idPhysics +================ +*/ +idPhysics::~idPhysics( void ) { +} + +/* +================ +idPhysics::Save +================ +*/ +void idPhysics::Save( idSaveGame *savefile ) const { +} + +/* +================ +idPhysics::Restore +================ +*/ +void idPhysics::Restore( idRestoreGame *savefile ) { +} + +/* +================ +idPhysics::SetClipBox +================ +*/ +void idPhysics::SetClipBox( const idBounds &bounds, float density ) { + SetClipModel( new idClipModel( idTraceModel( bounds ) ), density ); +} + +/* +================ +idPhysics::SnapTimeToPhysicsFrame +================ +*/ +int idPhysics::SnapTimeToPhysicsFrame( int t ) { + int s; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + s = t + gameLocal.GetMSec() - 1; + return ( s - s % gameLocal.GetMSec() ); +// RAVEN END +} diff --git a/source/mpgame/physics/Physics.h b/source/mpgame/physics/Physics.h new file mode 100644 index 0000000..bc5f740 --- /dev/null +++ b/source/mpgame/physics/Physics.h @@ -0,0 +1,179 @@ + +#ifndef __PHYSICS_H__ +#define __PHYSICS_H__ + +/* +=============================================================================== + + Physics abstract class + + A physics object is a tool to manipulate the position and orientation of + an entity. The physics object is a container for idClipModels used for + collision detection. The physics deals with moving these collision models + through the world according to the laws of physics or other rules. + + The mass of a clip model is the volume of the clip model times the density. + An arbitrary mass can however be set for specific clip models or the + whole physics object. The contents of a clip model is a set of bit flags + that define the contents. The clip mask defines the contents a clip model + collides with. + + The linear velocity of a physics object is a vector that defines the + translation of the center of mass in units per second. The angular velocity + of a physics object is a vector that passes through the center of mass. The + direction of this vector defines the axis of rotation and the magnitude + defines the rate of rotation about the axis in radians per second. + The gravity is the change in velocity per second due to gravitational force. + + Entities update their visual position and orientation from the physics + using GetOrigin() and GetAxis(). Direct origin and axis changes of + entities should go through the physics. In other words the physics origin + and axis are updated first and the entity updates it's visual position + from the physics. + +=============================================================================== +*/ + +#define CONTACT_EPSILON 0.25f // maximum contact seperation distance + +class idEntity; + +typedef struct impactInfo_s { + float invMass; // inverse mass + idMat3 invInertiaTensor; // inverse inertia tensor + idVec3 position; // impact position relative to center of mass + idVec3 velocity; // velocity at the impact position +} impactInfo_t; + + +class idPhysics : public idClass { + +public: + ABSTRACT_PROTOTYPE( idPhysics ); + + virtual ~idPhysics( void ); + static int SnapTimeToPhysicsFrame( int t ); + + // Must not be virtual + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + // set pointer to entity using physics + virtual void SetSelf( idEntity *e ) = 0; + // clip models + virtual void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ) = 0; + virtual void SetClipBox( const idBounds &bounds, float density ); + virtual idClipModel * GetClipModel( int id = 0 ) const = 0; + virtual int GetNumClipModels( void ) const = 0; + // get/set the mass of a specific clip model or the whole physics object + virtual void SetMass( float mass, int id = -1 ) = 0; + virtual float GetMass( int id = -1 ) const = 0; + +// RAVEN BEGIN +// bdube: added center mass + // get the center of mass origin + virtual idVec3 GetCenterMass ( int id = -1 ) const = 0; +// RAVEN END + + // get/set the contents of a specific clip model or the whole physics object + virtual void SetContents( int contents, int id = -1 ) = 0; + virtual int GetContents( int id = -1 ) const = 0; + // get/set the contents a specific clip model or the whole physics object collides with + virtual void SetClipMask( int mask, int id = -1 ) = 0; + virtual int GetClipMask( int id = -1 ) const = 0; + // get the bounds of a specific clip model or the whole physics object + virtual const idBounds & GetBounds( int id = -1 ) const = 0; + virtual const idBounds & GetAbsBounds( int id = -1 ) const = 0; + // evaluate the physics with the given time step, returns true if the object moved + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ) = 0; + // update the time without moving + virtual void UpdateTime( int endTimeMSec ) = 0; + // get the last physics update time + virtual int GetTime( void ) const = 0; + // collision interaction between different physics objects + virtual void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const = 0; + virtual void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) = 0; + virtual void AddForce( const int id, const idVec3 &point, const idVec3 &force ) = 0; + virtual void Activate( void ) = 0; + virtual void PutToRest( void ) = 0; + virtual bool IsAtRest( void ) const = 0; + virtual int GetRestStartTime( void ) const = 0; + virtual bool IsPushable( void ) const = 0; +// RAVEN BEGIN +// bdube: water interraction + virtual bool IsInWater ( void ) const = 0; +// RAVEN END + // save and restore the physics state + virtual void SaveState( void ) = 0; + virtual void RestoreState( void ) = 0; + // set the position and orientation in master space or world space if no master set + virtual void SetOrigin( const idVec3 &newOrigin, int id = -1 ) = 0; + virtual void SetAxis( const idMat3 &newAxis, int id = -1 ) = 0; + // translate or rotate the physics object in world space + virtual void Translate( const idVec3 &translation, int id = -1 ) = 0; + virtual void Rotate( const idRotation &rotation, int id = -1 ) = 0; + // get the position and orientation in world space + virtual const idVec3 & GetOrigin( int id = 0 ) const = 0; + virtual const idMat3 & GetAxis( int id = 0 ) const = 0; + // set linear and angular velocity + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ) = 0; + virtual void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ) = 0; + // get linear and angular velocity + virtual const idVec3 & GetLinearVelocity( int id = 0 ) const = 0; + virtual const idVec3 & GetAngularVelocity( int id = 0 ) const = 0; + // gravity + virtual void SetGravity( const idVec3 &newGravity ) = 0; + virtual const idVec3 & GetGravity( void ) const = 0; + virtual const idVec3 & GetGravityNormal( void ) const = 0; + // get first collision when translating or rotating this physics object + virtual void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const = 0; + virtual void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const = 0; + virtual int ClipContents( const idClipModel *model ) const = 0; + // disable/enable the clip models contained by this physics object + virtual void DisableClip( void ) = 0; + virtual void EnableClip( void ) = 0; + // link/unlink the clip models contained by this physics object + virtual void UnlinkClip( void ) = 0; + virtual void LinkClip( void ) = 0; + // contacts + virtual bool EvaluateContacts( void ) = 0; + virtual int GetNumContacts( void ) const = 0; + virtual const contactInfo_t &GetContact( int num ) const = 0; + virtual void ClearContacts( void ) = 0; + virtual void AddContactEntity( idEntity *e ) = 0; + virtual void RemoveContactEntity( idEntity *e ) = 0; + // ground contacts + virtual bool HasGroundContacts( void ) const = 0; + virtual bool IsGroundEntity( int entityNum ) const = 0; + virtual bool IsGroundClipModel( int entityNum, int id ) const = 0; +// RAVEN BEGIN +// abahr + virtual const idVec3 GetContactNormal() const { return vec3_zero; } + virtual const idVec3 GetGroundContactNormal() const { return vec3_zero; } + virtual idEntity* GetSelf() const = 0; +// RAVEN END + // set the master entity for objects bound to a master + virtual void SetMaster( idEntity *master, const bool orientated = true ) = 0; + // set pushed state + virtual void SetPushed( int deltaTime ) = 0; + virtual const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const = 0; + virtual const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const = 0; + // get blocking info, returns NULL if the object is not blocked + virtual const trace_t * GetBlockingInfo( void ) const = 0; + virtual idEntity * GetBlockingEntity( void ) const = 0; + // movement end times in msec for reached events at the end of predefined motion + virtual int GetLinearEndTime( void ) const = 0; + virtual int GetAngularEndTime( void ) const = 0; + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const = 0; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ) = 0; + +// RAVEN BEGIN +// kfuller: we want to debug draw the bbox + virtual void DebugDraw() {} +// RAVEN END + +}; + +#endif /* !__PHYSICS_H__ */ diff --git a/source/mpgame/physics/Physics_AF.cpp b/source/mpgame/physics/Physics_AF.cpp new file mode 100644 index 0000000..054de40 --- /dev/null +++ b/source/mpgame/physics/Physics_AF.cpp @@ -0,0 +1,8076 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_AF ) +END_CLASS + +const float ERROR_REDUCTION = 0.5f; +const float ERROR_REDUCTION_MAX = 256.0f; +const float LIMIT_ERROR_REDUCTION = 0.3f; +const float LCP_EPSILON = 1e-7f; +const float LIMIT_LCP_EPSILON = 1e-4f; +const float CONTACT_LCP_EPSILON = 1e-6f; +const float CENTER_OF_MASS_EPSILON = 1e-4f; +const float NO_MOVE_TIME = 1.0f; +const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f; +const float NO_MOVE_ROTATION_TOLERANCE = 10.0f; +const float MIN_MOVE_TIME = -1.0f; +const float MAX_MOVE_TIME = -1.0f; +const float IMPULSE_THRESHOLD = 500.0f; +const float SUSPEND_LINEAR_VELOCITY = 10.0f; +const float SUSPEND_ANGULAR_VELOCITY = 15.0f; +const float SUSPEND_LINEAR_ACCELERATION = 20.0f; +const float SUSPEND_ANGULAR_ACCELERATION = 30.0f; +const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON, + LCP_EPSILON, LCP_EPSILON, LCP_EPSILON ); + +#define AF_TIMINGS + +#ifdef AF_TIMINGS +static int lastTimerReset = 0; +static int numArticulatedFigures = 0; +static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp; +#endif + + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +/* +================ +idAFConstraint::idAFConstraint +================ +*/ +idAFConstraint::idAFConstraint( void ) { + type = CONSTRAINT_INVALID; + name = "noname"; + body1 = NULL; + body2 = NULL; + physics = NULL; + + lo.Zero( 6 ); + lo.SubVec6(0) = -vec6_infinity; + hi.Zero( 6 ); + hi.SubVec6(0) = vec6_infinity; + e.SetSize( 6 ); + e.SubVec6(0) = vec6_lcp_epsilon; + + boxConstraint = NULL; + boxIndex[0] = -1; + boxIndex[1] = -1; + boxIndex[2] = -1; + boxIndex[3] = -1; + boxIndex[4] = -1; + boxIndex[5] = -1; + + firstIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); +} + +/* +================ +idAFConstraint::~idAFConstraint +================ +*/ +idAFConstraint::~idAFConstraint( void ) { +} + +/* +================ +idAFConstraint::SetBody1 +================ +*/ +void idAFConstraint::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::SetBody2 +================ +*/ +void idAFConstraint::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::GetMultiplier +================ +*/ +const idVecX &idAFConstraint::GetMultiplier( void ) { + return lm; +} + +/* +================ +idAFConstraint::Evaluate +================ +*/ +void idAFConstraint::Evaluate( float invTimeStep ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::ApplyFriction +================ +*/ +void idAFConstraint::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint::GetForce +================ +*/ +void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) { + idVecX v; + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + if ( body == body1 ) { + J1.TransposeMultiply( v, lm ); + } + else if ( body == body2 ) { + J2.TransposeMultiply( v, lm ); + } + else { + v.Zero(); + } + force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5]; +} + +/* +================ +idAFConstraint::Translate +================ +*/ +void idAFConstraint::Translate( const idVec3 &translation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::Rotate +================ +*/ +void idAFConstraint::Rotate( const idRotation &rotation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::GetCenter +================ +*/ +void idAFConstraint::GetCenter( idVec3 ¢er ) { + center.Zero(); +} + +/* +================ +idAFConstraint::DebugDraw +================ +*/ +void idAFConstraint::DebugDraw( void ) { +} + +/* +================ +idAFConstraint::InitSize +================ +*/ +void idAFConstraint::InitSize( int size ) { + J1.Zero( size, 6 ); + J2.Zero( size, 6 ); + c1.Zero( size ); + c2.Zero( size ); + s.Zero( size ); + lm.Zero( size ); +} + +/* +================ +idAFConstraint::Save +================ +*/ +void idAFConstraint::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( type ); + saveFile->WriteString ( name ); // cnicholson: Added unsaved var + // TOSAVE: idAFBody * body1; + // TOSAVE: idAFBody * body2; + // TOSAVE: idPhysics_AF * physics; + + // TOSAVE: idMatX J1, J2; + // TOSAVE: idVecX c1, c2; + // TOSAVE: idVecX lo, hi, e; + // TOSAVE: idAFConstraint * boxConstraint; + for (int i=0; i<6; ++i) { // cnicholson: Added unsaved var + saveFile->WriteInt ( boxIndex[i] ); + } + + // TOSAVE: idMatX invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX lm; + saveFile->WriteInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ));// cnicholson: Added unsaved var +} + +/* +================ +idAFConstraint::Restore +================ +*/ +void idAFConstraint::Restore( idRestoreGame *saveFile ) { + constraintType_t t; + saveFile->ReadInt( (int &)t ); + assert( t == type ); + + saveFile->ReadString ( name ); // cnicholson: Added unrestored var + + for (int i=0; i<6; ++i) { + saveFile->ReadInt ( boxIndex[i] ); + } + + saveFile->ReadInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Read( &fl, sizeof( fl )); // cnicholson: Added unsaved var +} + + +//=============================================================== +// +// idAFConstraint_Fixed +// +//=============================================================== + +/* +================ +idAFConstraint_Fixed::idAFConstraint_Fixed +================ +*/ +idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_FIXED; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 6 ); + fl.allowPrimary = true; + fl.noCollision = true; + + InitOffset(); +} + +/* +================ +idAFConstraint_Fixed::InitOffset +================ +*/ +void idAFConstraint_Fixed::InitOffset( void ) { + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Fixed::SetBody1 +================ +*/ +void idAFConstraint_Fixed::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::SetBody2 +================ +*/ +void idAFConstraint_Fixed::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::Evaluate +================ +*/ +void idAFConstraint_Fixed::Evaluate( float invTimeStep ) { + idVec3 ofs, a2; + idMat3 ax; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + a2 = offset * master->GetWorldAxis(); + ofs = a2 + master->GetWorldOrigin(); + ax = relAxis * master->GetWorldAxis(); + } + else { + a2.Zero(); + ofs = offset; + ax = relAxis; + } + + J1.Set( mat3_identity, mat3_zero, + mat3_zero, mat3_identity ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, -mat3_identity ); + } + else { + J2.Zero( 6, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() ); + r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation(); + c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Fixed::ApplyFriction +================ +*/ +void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Fixed::Translate +================ +*/ +void idAFConstraint_Fixed::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Fixed::Rotate +================ +*/ +void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + relAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Fixed::GetCenter +================ +*/ +void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin(); +} + +/* +================ +idAFConstraint_Fixed::DebugDraw +================ +*/ +void idAFConstraint_Fixed::DebugDraw( void ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() ); + } + else { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin ); + } +} + +/* +================ +idAFConstraint_Fixed::Save +================ +*/ +void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Fixed::Restore +================ +*/ +void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJoint +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_BALLANDSOCKETJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 3 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetAnchor +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetNoLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetConeLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetFriction +================ +*/ +float idAFConstraint_BallAndSocketJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + J1.Set( mat3_identity, -SkewSymmetric( a1 ) ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ) ); + } + else { + J2.Zero( 3, 6 ); + } + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_BallAndSocketJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetForce +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Translate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetCenter +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::DebugDraw +================ +*/ +void idAFConstraint_BallAndSocketJoint::DebugDraw( void ) { + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Save +================ +*/ +void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( friction ); +// cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); // cnicholson: Bad way to save and restore + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_BallAndSocketJointFriction *fc; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Restore +================ +*/ +void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( friction ); + + // cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_BallAndSocketJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction +================ +*/ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "ballAndSocketJointFriction"; + InitSize( 3 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Setup +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) { + this->joint = bsj; + body1 = bsj->GetBody1(); + body2 = bsj->GetBody2(); +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Add +================ +*/ +bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = lo[2] = -f; + hi[0] = hi[1] = hi[2] = f; + + J1.Zero( 3, 6 ); + J1[0][3] = J1[1][4] = J1[2][5] = 1.0f; + + if ( body2 ) { + + J2.Zero( 3, 6 ); + J2[0][3] = J2[1][4] = J2[2][5] = 1.0f; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Translate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_UniversalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_UNIVERSALJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 4 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetAnchor +================ +*/ +void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_UniversalJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_UniversalJoint::SetShafts +================ +*/ +void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) { + idVec3 cardanAxis; + float l; + + shaft1 = cardanShaft1; + l = shaft1.Normalize(); + assert( l != 0.0f ); + shaft2 = cardanShaft2; + l = shaft2.Normalize(); + assert( l != 0.0f ); + + // the cardan axis is a vector orthogonal to both cardan shafts + cardanAxis = shaft1.Cross( shaft2 ); + if ( cardanAxis.Normalize() == 0.0f ) { + idVec3 vecY; + shaft1.OrthogonalBasis( cardanAxis, vecY ); + cardanAxis.Normalize(); + } + + shaft1 *= body1->GetWorldAxis().Transpose(); + axis1 = cardanAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + shaft2 *= body2->GetWorldAxis().Transpose(); + axis2 = cardanAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = cardanAxis; + } + + if ( coneLimit ) { + coneLimit->SetBody1Axis( shaft1 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetBody1Axis( shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetNoLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetConeLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetFriction +================ +*/ +float idAFConstraint_UniversalJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_UniversalJoint::Evaluate + + NOTE: this joint is homokinetic +================ +*/ +void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = s1.Cross( axis1 * body1->GetWorldAxis() ); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( s1[0], s1[1], s1[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 4, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( s2[0], s2[1], s2[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 4, 6 ); + } + else { + J2.Zero( 4, 6 ); + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_UniversalJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetForce +================ +*/ +void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_UniversalJoint::Translate +================ +*/ +void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Rotate +================ +*/ +void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + shaft2 *= rotation.ToMat3(); + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetCenter +================ +*/ +void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_UniversalJoint::DebugDraw +================ +*/ +void idAFConstraint_UniversalJoint::DebugDraw( void ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = axis1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f ); + gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f ); + gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f ); + gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_UniversalJoint::Save +================ +*/ +void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( shaft1 ); + saveFile->WriteVec3( shaft2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteFloat( friction ); + + // cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_UniversalJointFriction *fc; +} + +/* +================ +idAFConstraint_UniversalJoint::Restore +================ +*/ +void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( shaft1 ); + saveFile->ReadVec3( shaft2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadFloat( friction ); + +// cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_UniversalJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_UniversalJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction +================ +*/ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "universalJointFriction"; + InitSize( 2 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Setup +================ +*/ +void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) { + this->joint = uj; + body1 = uj->GetBody1(); + body2 = uj->GetBody2(); +} + +/* +================ +idAFConstraint_UniversalJointFriction::Evaluate +================ +*/ +void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::Add +================ +*/ +bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 s1, s2, dir1, dir2; + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = -f; + hi[0] = hi[1] = f; + + joint->GetShafts( s1, s2 ); + + s1 *= body1->GetWorldAxis(); + s1.NormalVectors( dir1, dir2 ); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = dir1; + J1.SubVec6(1).SubVec3(0).Zero(); + J1.SubVec6(1).SubVec3(1) = dir2; + + if ( body2 ) { + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -dir1; + J2.SubVec6(1).SubVec3(0).Zero(); + J2.SubVec6(1).SubVec3(1) = -dir2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Translate +================ +*/ +void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_UniversalJointFriction::Rotate +================ +*/ +void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_CylindricalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint +================ +*/ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Evaluate +================ +*/ +void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::ApplyFriction +================ +*/ +void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Translate +================ +*/ +void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Rotate +================ +*/ +void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::DebugDraw +================ +*/ +void idAFConstraint_CylindricalJoint::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Hinge +// +//=============================================================== + +/* +================ +idAFConstraint_Hinge::idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_HINGE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + coneLimit = NULL; + steering = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; + initialAxis = body1->GetWorldAxis(); + if ( body2 ) { + initialAxis *= body2->GetWorldAxis().Transpose(); + } +} + +/* +================ +idAFConstraint_Hinge::~idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::~idAFConstraint_Hinge( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( fc ) { + delete fc; + } + if ( steering ) { + delete steering; + } +} + +/* +================ +idAFConstraint_Hinge::SetAnchor +================ +*/ +void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_Hinge::GetAnchor +================ +*/ +idVec3 idAFConstraint_Hinge::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_Hinge::SetAxis +================ +*/ +void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) { + idVec3 normAxis; + + normAxis = axis; + normAxis.Normalize(); + + // get axis relative to body1 + axis1 = normAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get axis relative to body2 + axis2 = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = normAxis; + } +} + +/* +================ +idAFConstraint_Hinge::GetAxis +================ +*/ +idVec3 idAFConstraint_Hinge::GetAxis( void ) const { + if ( body2 ) { + return axis2 * body2->GetWorldAxis(); + } + return axis2; +} + +/* +================ +idAFConstraint_Hinge::SetNoLimit +================ +*/ +void idAFConstraint_Hinge::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } +} + +/* +================ +idAFConstraint_Hinge::SetLimit +================ +*/ +void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_Hinge::SetLimitEpsilon +================ +*/ +void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_Hinge::GetFriction +================ +*/ +float idAFConstraint_Hinge::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_Hinge::GetAngle +================ +*/ +float idAFConstraint_Hinge::GetAngle( void ) const { + idMat3 axis; + idRotation rotation; + float angle; + + axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose(); + rotation = axis.ToRotation(); + angle = rotation.GetAngle(); + if ( rotation.GetVec() * axis1 < 0.0f ) { + return -angle; + } + return angle; +} + +/* +================ +idAFConstraint_Hinge::SetSteerAngle +================ +*/ +void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !steering ) { + steering = new idAFConstraint_HingeSteering(); + steering->Setup( this ); + } + steering->SetSteerAngle( degrees ); +} + +/* +================ +idAFConstraint_Hinge::SetSteerSpeed +================ +*/ +void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) { + if ( steering ) { + steering->SetSteerSpeed( speed ); + } +} + +/* +================ +idAFConstraint_Hinge::Evaluate +================ +*/ +void idAFConstraint_Hinge::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idVec3 x1, x2, cross; + idVec3 vecX, vecY; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space + x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space + + a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); // anchor in master space + x2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + x2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( vecX[0], vecX[1], vecX[2], + vecY[0], vecY[1], vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2], + -vecY[0], -vecY[1], -vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + cross = x1.Cross( x2 ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( steering ) { + steering->Add( physics, invTimeStep ); + } + else if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::ApplyFriction +================ +*/ +void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::GetForce +================ +*/ +void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_Hinge::Translate +================ +*/ +void idAFConstraint_Hinge::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_Hinge::Rotate +================ +*/ +void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_Hinge::GetCenter +================ +*/ +void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Hinge::DebugDraw +================ +*/ +void idAFConstraint_Hinge::DebugDraw( void ) { + idVec3 vecX, vecY; + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + idVec3 x1 = axis1 * body1->GetWorldAxis(); + x1.OrthogonalBasis( vecX, vecY ); + + gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_Hinge::Save +================ +*/ +void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteMat3( initialAxis ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + saveFile->WriteBool( true ); + coneLimit->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( steering ) { + saveFile->WriteBool( true ); + steering->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( fc ) { + saveFile->WriteBool( true ); + fc->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } +} + +/* +================ +idAFConstraint_Hinge::Restore +================ +*/ +void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) { + bool b; + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadMat3( initialAxis ); + saveFile->ReadFloat( friction ); + + saveFile->ReadBool( b ); + if ( b ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + } + coneLimit->SetPhysics( physics ); + coneLimit->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !steering ) { + steering = new idAFConstraint_HingeSteering; + } + steering->Setup( this ); + steering->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + } + fc->Setup( this ); + fc->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_HingeFriction +// +//=============================================================== + +/* +================ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction +================ +*/ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_HingeFriction::Setup +================ +*/ +void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeFriction::Evaluate +================ +*/ +void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::ApplyFriction +================ +*/ +void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::Add +================ +*/ +bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 a1, a2; + float f; + + physics = phys; + + f = hinge->GetFriction() * hinge->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = -f; + hi[0] = f; + + hinge->GetAxis( a1, a2 ); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeFriction::Translate +================ +*/ +void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeFriction::Rotate +================ +*/ +void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_HingeSteering +// +//=============================================================== + +/* +================ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering +================ +*/ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering( void ) { + type = CONSTRAINT_HINGESTEERING; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; + steerSpeed = 0.0f; + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_HingeSteering::Save +================ +*/ +void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const { + + saveFile->WriteFloat(steerAngle); + saveFile->WriteFloat(steerSpeed); + saveFile->WriteFloat(epsilon); + + //if ( hinge ) { // cnicholson: Added unsaved var (doesnt work) + // saveFile->WriteBool( true ); + // hinge->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Restore +================ +*/ +void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadFloat(steerAngle); + saveFile->ReadFloat(steerSpeed); + saveFile->ReadFloat(epsilon); + +// cnicholson: Added unrestored var (doesnt work) + //bool b; + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !hinge ) { + // hinge = new idAFConstraint_Hinge; + // } + // hinge->SetSteerAngle( steerAngle ); + // hinge->SetSteerSpeed( steerSpeed ); + // hinge->SetLimitEpsilon( epsilon ); + // hinge->Restore( saveFile ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Setup +================ +*/ +void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeSteering::Evaluate +================ +*/ +void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::ApplyFriction +================ +*/ +void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::Add +================ +*/ +bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) { + float angle, speed; + idVec3 a1, a2; + + physics = phys; + + hinge->GetAxis( a1, a2 ); + angle = hinge->GetAngle(); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + speed = steerAngle - angle; + if ( steerSpeed != 0.0f ) { + if ( speed > steerSpeed ) { + speed = steerSpeed; + } + else if ( speed < -steerSpeed ) { + speed = -steerSpeed; + } + } + + c1[0] = DEG2RAD( speed ) * invTimeStep; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeSteering::Translate +================ +*/ +void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeSteering::Rotate +================ +*/ +void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_Slider +// +//=============================================================== + +/* +================ +idAFConstraint_Slider::idAFConstraint_Slider +================ +*/ +idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SLIDER; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + fl.allowPrimary = true; + fl.noCollision = true; + + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Slider::SetAxis +================ +*/ +void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) { + idVec3 normAxis; + + // get normalized axis relative to body1 + normAxis = ax; + normAxis.Normalize(); + if ( body2 ) { + axis = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis = normAxis; + } +} + +/* +================ +idAFConstraint_Slider::Evaluate +================ +*/ +void idAFConstraint_Slider::Evaluate( float invTimeStep ) { + idVec3 vecX, vecY, ofs; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + (axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY ); + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation(); + } + else { + axis.OrthogonalBasis( vecX, vecY ); + ofs = offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation(); + } + + J1.Set( mat3_zero, mat3_identity, + idMat3( vecX, vecY, vec3_origin ), mat3_zero ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + + J2.Set( mat3_zero, -mat3_identity, + idMat3( -vecX, -vecY, vec3_origin ), mat3_zero ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Slider::ApplyFriction +================ +*/ +void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Slider::Translate +================ +*/ +void idAFConstraint_Slider::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Slider::Rotate +================ +*/ +void idAFConstraint_Slider::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + } +} + +/* +================ +idAFConstraint_Slider::GetCenter +================ +*/ +void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + center = offset - body1->GetWorldOrigin(); + } +} + +/* +================ +idAFConstraint_Slider::DebugDraw +================ +*/ +void idAFConstraint_Slider::DebugDraw( void ) { + idVec3 ofs; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + ofs = offset - body1->GetWorldOrigin(); + } + gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() ); +} + +/* +================ +idAFConstraint_Slider::Save +================ +*/ +void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( axis ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Slider::Restore +================ +*/ +void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( axis ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_Line +// +//=============================================================== + +/* +================ +idAFConstraint_Line::idAFConstraint_Line +================ +*/ +idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Evaluate +================ +*/ +void idAFConstraint_Line::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::ApplyFriction +================ +*/ +void idAFConstraint_Line::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Translate +================ +*/ +void idAFConstraint_Line::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Rotate +================ +*/ +void idAFConstraint_Line::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::DebugDraw +================ +*/ +void idAFConstraint_Line::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Plane +// +//=============================================================== + +/* +================ +idAFConstraint_Plane::idAFConstraint_Plane +================ +*/ +idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_PLANE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_Plane::SetPlane +================ +*/ +void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) { + // get anchor relative to center of mass of body1 + anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + planeNormal = normal * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = anchor; + planeNormal = normal; + } +} + +/* +================ +idAFConstraint_Plane::Evaluate +================ +*/ +void idAFConstraint_Plane::Evaluate( float invTimeStep ) { + idVec3 a1, a2, normal, p; + idVec6 v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + normal = planeNormal * master->GetWorldAxis(); + } + else { + a2 = anchor2; + normal = planeNormal; + } + + p = a1 - body1->GetWorldOrigin(); + v.SubVec3(0) = normal; + v.SubVec3(1) = p.Cross( normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + + if ( body2 ) { + p = a1 - body2->GetWorldOrigin(); + v.SubVec3(0) = -normal; + v.SubVec3(1) = p.Cross( -normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + } + + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Plane::ApplyFriction +================ +*/ +void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Plane::Translate +================ +*/ +void idAFConstraint_Plane::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Plane::Rotate +================ +*/ +void idAFConstraint_Plane::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + planeNormal *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Plane::DebugDraw +================ +*/ +void idAFConstraint_Plane::DebugDraw( void ) { + idVec3 a1, normal, right, up; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + normal = planeNormal * master->GetWorldAxis(); + } + else { + normal = planeNormal; + } + normal.NormalVectors( right, up ); + normal *= 4.0f; + right *= 4.0f; + up *= 4.0f; + + gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right ); + gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up ); + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 ); +} + +/* +================ +idAFConstraint_Plane::Save +================ +*/ +void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( planeNormal ); +} + +/* +================ +idAFConstraint_Plane::Restore +================ +*/ +void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( planeNormal ); +} + + +//=============================================================== +// +// idAFConstraint_Spring +// +//=============================================================== + +/* +================ +idAFConstraint_Spring::idAFConstraint_Spring +================ +*/ +idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SPRING; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = false; + kstretch = kcompress = damping = 1.0f; + minLength = maxLength = restLength = 0.0f; +} + +/* +================ +idAFConstraint_Spring::SetAnchor +================ +*/ +void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldAnchor2; + } +} + +/* +================ +idAFConstraint_Spring::SetSpring +================ +*/ +void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) { + assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f ); + this->kstretch = stretch; + this->kcompress = compress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idAFConstraint_Spring::SetLimit +================ +*/ +void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) { + assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength ); + this->minLength = minLength; + this->maxLength = maxLength; +} + +/* +================ +idAFConstraint_Spring::Evaluate +================ +*/ +void idAFConstraint_Spring::Evaluate( float invTimeStep ) { + idVec3 a1, a2, velocity1, velocity2, force; + idVec6 v1, v2; + float d, dampingForce, length, error; + bool limit; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + velocity1 = body1->GetPointVelocity( a1 ); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + velocity2 = master->GetPointVelocity( a2 ); + } + else { + a2 = anchor2; + velocity2.Zero(); + } + + force = a2 - a1; + d = force * force; + if ( d != 0.0f ) { + dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d; + } + else { + dampingForce = 0.0f; + } + length = force.Normalize(); + + if ( length > restLength ) { + if ( kstretch > 0.0f ) { + idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + else { + if ( kcompress > 0.0f ) { + idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + + // check for spring limits + if ( length < minLength ) { + force = -force; + error = minLength - length; + limit = true; + } + else if ( maxLength > 0.0f && length > maxLength ) { + error = length - maxLength; + limit = true; + } + else { + error = 0.0f; + limit = false; + } + + if ( limit ) { + a1 -= body1->GetWorldOrigin(); + v1.SubVec3(0) = force; + v1.SubVec3(1) = a1.Cross( force ); + J1.Set( 1, 6, v1.ToFloatPtr() ); + if ( body2 ) { + a2 -= body2->GetWorldOrigin(); + v2.SubVec3(0) = -force; + v2.SubVec3(1) = a2.Cross( -force ); + J2.Set( 1, 6, v2.ToFloatPtr() ); + } + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error; + lo[0] = 0.0f; + } + else { + J1.Zero( 0, 0 ); + J2.Zero( 0, 0 ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Spring::ApplyFriction +================ +*/ +void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Spring::Translate +================ +*/ +void idAFConstraint_Spring::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Spring::Rotate +================ +*/ +void idAFConstraint_Spring::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } +} + +/* +================ +idAFConstraint_Spring::GetCenter +================ +*/ +void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) { + idAFBody *master; + idVec3 a1, a2; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + center = ( a1 + a2 ) * 0.5f; +} + +/* +================ +idAFConstraint_Spring::DebugDraw +================ +*/ +void idAFConstraint_Spring::DebugDraw( void ) { + idAFBody *master; + float length; + idVec3 a1, a2, dir, mid, p; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + dir = a2 - a1; + mid = a1 + 0.5f * dir; + length = dir.Normalize(); + + // draw spring + gameRenderWorld->DebugLine( colorGreen, a1, a2 ); + + // draw rest length + p = restLength * 0.5f * dir; + gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 ); + gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 ); + if ( restLength > length ) { + gameRenderWorld->DebugLine( colorWhite, a2, mid + p ); + gameRenderWorld->DebugLine( colorWhite, a1, mid - p ); + } + + if ( minLength > 0.0f ) { + // draw min length + gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 ); + } + + if ( maxLength > 0.0f ) { + // draw max length + gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 ); + } +} + +/* +================ +idAFConstraint_Spring::Save +================ +*/ +void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( kstretch ); + saveFile->WriteFloat( kcompress ); + saveFile->WriteFloat( damping ); + saveFile->WriteFloat( restLength ); + saveFile->WriteFloat( minLength ); + saveFile->WriteFloat( maxLength ); +} + +/* +================ +idAFConstraint_Spring::Restore +================ +*/ +void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( kstretch ); + saveFile->ReadFloat( kcompress ); + saveFile->ReadFloat( damping ); + saveFile->ReadFloat( restLength ); + saveFile->ReadFloat( minLength ); + saveFile->ReadFloat( maxLength ); +} + + +//=============================================================== +// +// idAFConstraint_Contact +// +//=============================================================== + +/* +================ +idAFConstraint_Contact::idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::idAFConstraint_Contact( void ) { + name = "contact"; + type = CONSTRAINT_CONTACT; + InitSize( 1 ); + fc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_Contact::~idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::~idAFConstraint_Contact( void ) { + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_Contact::Setup +================ +*/ +void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) { + idVec3 p; + idVec6 v; + float vel; + + assert( b1 ); + + body1 = b1; + body2 = b2; + contact = c; + + p = c.point - body1->GetWorldOrigin(); + v.SubVec3(0) = c.normal; + v.SubVec3(1) = p.Cross( c.normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + p = c.point - body2->GetWorldOrigin(); + v.SubVec3(0) = -c.normal; + v.SubVec3(1) = p.Cross( -c.normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity(); + c2[0] = 0.0f; + } + + if ( body1->GetBouncyness() > 0.0f ) { + c1[0] = body1->GetBouncyness() * -vel; + } + else { + c1[0] = 0.0f; + } + + e[0] = CONTACT_LCP_EPSILON; + lo[0] = 0.0f; + hi[0] = idMath::INFINITY; + boxConstraint = NULL; + boxIndex[0] = -1; +} + +/* +================ +idAFConstraint_Contact::Evaluate +================ +*/ +void idAFConstraint_Contact::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Contact::ApplyFriction +================ +*/ +void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) { + idVec3 r, velocity, normal, dir1, dir2; + float friction, magnitude, forceNumerator, forceDenominator; + idVecX impulse, dv; + + friction = body1->GetContactFriction(); + if ( body2 && body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + + friction *= physics->GetContactFrictionScale(); + + if ( friction <= 0.0f ) { + return; + } + + // seperate friction per contact is silly but it's fast and often looks close enough + if ( af_useImpulseFriction.GetBool() ) { + + impulse.SetData( 6, VECX_ALLOCA( 6 ) ); + dv.SetData( 6, VECX_ALLOCA( 6 ) ); + + // calculate velocity in the contact plane + r = contact.point - body1->GetWorldOrigin(); + velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r ); + velocity -= contact.normal * velocity * contact.normal; + + // get normalized direction of friction and magnitude of velocity + normal = -velocity; + magnitude = normal.Normalize(); + + forceNumerator = friction * magnitude; + forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal; + impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) ); + body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() ); + + // modify velocity with friction force + body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) ); + body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) ); + } + else { + + if ( !fc ) { + fc = new idAFConstraint_ContactFriction; + } + // call setup each frame because contact constraints are re-used for different bodies + fc->Setup( this ); + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Contact::Translate +================ +*/ +void idAFConstraint_Contact::Translate( const idVec3 &translation ) { + assert( 0 ); // contact should never be translated +} + +/* +================ +idAFConstraint_Contact::Rotate +================ +*/ +void idAFConstraint_Contact::Rotate( const idRotation &rotation ) { + assert( 0 ); // contact should never be rotated +} + +/* +================ +idAFConstraint_Contact::GetCenter +================ +*/ +void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) { + center = contact.point; +} + +/* +================ +idAFConstraint_Contact::DebugDraw +================ +*/ +void idAFConstraint_Contact::DebugDraw( void ) { + idVec3 x, y; + contact.normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y ); +} + + +//=============================================================== +// +// idAFConstraint_ContactFriction +// +//=============================================================== + +/* +================ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction +================ +*/ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "contactFriction"; + InitSize( 2 ); + cc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ContactFriction::Setup +================ +*/ +void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) { + this->cc = cc; + body1 = cc->GetBody1(); + body2 = cc->GetBody2(); +} + +/* +================ +idAFConstraint_ContactFriction::Evaluate +================ +*/ +void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::ApplyFriction +================ +*/ +void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::Add +================ +*/ +bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 r, dir1, dir2; + float friction; + int newRow; + + physics = phys; + + friction = body1->GetContactFriction() * physics->GetContactFrictionScale(); + + // if the body only has friction in one direction + if ( body1->GetFrictionDirection( dir1 ) ) { + // project the friction direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + c1.SetSize( 1 ); + c1[0] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + c2.SetSize( 1 ); + c2[0] = 0.0f; + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + } + else { + // get two friction directions orthogonal to contact normal + cc->GetContact().normal.NormalVectors( dir1, dir2 ); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + J1.SubVec6(1).SubVec3(0) = dir2; + J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 ); + c1.SetSize( 2 ); + c1[0] = c1[1] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + J2.SubVec6(1).SubVec3(0) = -dir2; + J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 ); + c2.SetSize( 2 ); + c2[0] = c2[1] = 0.0f; + + if ( body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + lo[1] = -friction; + hi[1] = friction; + boxIndex[1] = 0; + } + + if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) { + // project the motor force direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + newRow = J1.GetNumRows(); + J1.ChangeSize( newRow+1, J1.GetNumColumns() ); + J1.SubVec6(newRow).SubVec3(0) = -dir1; + J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c1.ChangeSize( newRow+1 ); + c1[newRow] = body1->GetContactMotorVelocity(); + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.ChangeSize( newRow+1, J2.GetNumColumns() ); + J2.SubVec6(newRow).SubVec3(0) = -dir1; + J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c2.ChangeSize( newRow+1 ); + c2[newRow] = 0.0f; + } + + lo[newRow] = -body1->GetContactMotorForce(); + hi[newRow] = body1->GetContactMotorForce(); + boxIndex[newRow] = -1; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ContactFriction::Translate +================ +*/ +void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_ContactFriction::Rotate +================ +*/ +void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_ContactFriction::DebugDraw +================ +*/ +void idAFConstraint_ContactFriction::DebugDraw( void ) { +} + + +//=============================================================== +// +// idAFConstraint_ConeLimit +// +//=============================================================== + +/* +================ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit +================ +*/ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit( void ) { + type = CONSTRAINT_CONELIMIT; + name = "coneLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ConeLimit::Setup + + the coneAnchor is the top of the cone in body2 space + the coneAxis is the axis of the cone in body2 space + the coneAngle is the angle the cone hull makes at the top + the body1Axis is the axis in body1 space that should stay within the cone +================ +*/ +void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + this->body1 = b1; + this->body2 = b2; + this->coneAxis = coneAxis; + this->coneAxis.Normalize(); + this->coneAnchor = coneAnchor; + this->body1Axis = body1Axis; + this->body1Axis.Normalize(); + this->cosAngle = idMath::Cos( DEG2RAD( coneAngle * 0.5f ) ); + this->sinHalfAngle = idMath::Sin( DEG2RAD( coneAngle * 0.25f ) ); + this->cosHalfAngle = idMath::Cos( DEG2RAD( coneAngle * 0.25f ) ); +} + +/* +================ +idAFConstraint_ConeLimit::SetAnchor +================ +*/ +void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) { + this->coneAnchor = coneAnchor; +} + +/* +================ +idAFConstraint_ConeLimit::SetBody1Axis +================ +*/ +void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_ConeLimit::Evaluate +================ +*/ +void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ConeLimit::ApplyFriction +================ +*/ +void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_ConeLimit::Add +================ +*/ +bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + float a; + idVec6 J1row, J2row; + idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + a = ax * body1ax; + + // if the body1 axis is inside the cone + if ( a > cosAngle ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward cone normal for the position the body1 axis went outside the cone + normal = body1ax.Cross( ax ); + normal.Normalize(); + q.x = normal.x * sinHalfAngle; + q.y = normal.y * sinHalfAngle; + q.z = normal.z * sinHalfAngle; + q.w = cosHalfAngle; + coneVector = ax * q.ToMat3(); + normal = coneVector.Cross( ax ).Cross( coneVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ConeLimit::Translate +================ +*/ +void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + coneAnchor += translation; + } +} + +/* +================ +idAFConstraint_ConeLimit::Rotate +================ +*/ +void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + coneAnchor *= rotation; + coneAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_ConeLimit::DebugDraw +================ +*/ +void idAFConstraint_ConeLimit::DebugDraw( void ) { + idVec3 ax, anchor, x, y, z, start, end; + float sinAngle, a, size = 10.0f; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw cone + ax.NormalVectors( x, y ); + sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle ); + x *= size * sinAngle; + y *= size * sinAngle; + z = anchor + ax * size * cosAngle; + start = x + z; + for ( a = 0.0f; a < 360.0f; a += 45.0f ) { + end = x * idMath::Cos( DEG2RAD(a + 45.0f) ) + y * idMath::Sin( DEG2RAD(a + 45.0f) ) + z; + gameRenderWorld->DebugLine( colorMagenta, anchor, start ); + gameRenderWorld->DebugLine( colorMagenta, start, end ); + start = end; + } +} + +/* +================ +idAFConstraint_ConeLimit::Save +================ +*/ +void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( coneAnchor ); + saveFile->WriteVec3( coneAxis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle ); + saveFile->WriteFloat( sinHalfAngle ); + saveFile->WriteFloat( cosHalfAngle ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_ConeLimit::Restore +================ +*/ +void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( coneAnchor ); + saveFile->ReadVec3( coneAxis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle ); + saveFile->ReadFloat( sinHalfAngle ); + saveFile->ReadFloat( cosHalfAngle ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_PyramidLimit +// +//=============================================================== + +/* +================ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit +================ +*/ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit( void ) { + type = CONSTRAINT_PYRAMIDLIMIT; + name = "pyramidLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_PyramidLimit::Setup +================ +*/ +void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) { + body1 = b1; + body2 = b2; + // setup the base and make sure the basis is orthonormal + pyramidBasis[2] = pyramidAxis; + pyramidBasis[2].Normalize(); + pyramidBasis[0] = baseAxis; + pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2]; + pyramidBasis[0].Normalize(); + pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] ); + // pyramid top + this->pyramidAnchor = pyramidAnchor; + // angles + cosAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.5f ) ); + cosAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.5f ) ); + sinHalfAngle[0] = idMath::Sin( DEG2RAD( pyramidAngle1 * 0.25f ) ); + sinHalfAngle[1] = idMath::Sin( DEG2RAD( pyramidAngle2 * 0.25f ) ); + cosHalfAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.25f ) ); + cosHalfAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.25f ) ); + + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::SetAnchor +================ +*/ +void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) { + this->pyramidAnchor = pyramidAnchor; +} + +/* +================ +idAFConstraint_PyramidLimit::SetBody1Axis +================ +*/ +void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::Evaluate +================ +*/ +void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_PyramidLimit::ApplyFriction +================ +*/ +void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_PyramidLimit::Add +================ +*/ +bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + int i; + float a[2]; + idVec6 J1row, J2row; + idMat3 worldBase; + idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + for ( i = 0; i < 2; i++ ) { + ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i]; + ax[i].Normalize(); + a[i] = worldBase[2] * ax[i]; + } + + // if the body1 axis is inside the pyramid + if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward pyramid normal for the position the body1 axis went outside the pyramid + pyramidVector = worldBase[2]; + for ( i = 0; i < 2; i++ ) { + if ( a[i] <= cosAngle[i] ) { + v = ax[i].Cross( worldBase[2] ); + v.Normalize(); + q.x = v.x * sinHalfAngle[i]; + q.y = v.y * sinHalfAngle[i]; + q.z = v.z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + pyramidVector *= q.ToMat3(); + } + } + normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_PyramidLimit::Translate +================ +*/ +void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + pyramidAnchor += translation; + } +} + +/* +================ +idAFConstraint_PyramidLimit::Rotate +================ +*/ +void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + pyramidAnchor *= rotation; + pyramidBasis[0] *= rotation.ToMat3(); + pyramidBasis[1] *= rotation.ToMat3(); + pyramidBasis[2] *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_PyramidLimit::DebugDraw +================ +*/ +void idAFConstraint_PyramidLimit::DebugDraw( void ) { + int i; + float size = 10.0f; + idVec3 anchor, dir, p[4]; + idMat3 worldBase, m[2]; + idQuat q; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw the pyramid + for ( i = 0; i < 2; i++ ) { + q.x = worldBase[!i].x * sinHalfAngle[i]; + q.y = worldBase[!i].y * sinHalfAngle[i]; + q.z = worldBase[!i].z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + m[i] = q.ToMat3(); + } + + dir = worldBase[2] * size; + p[0] = anchor + m[0] * (m[1] * dir); + p[1] = anchor + m[0] * (m[1].Transpose() * dir); + p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir); + p[3] = anchor + m[0].Transpose() * (m[1] * dir); + + for ( i = 0; i < 4; i++ ) { + gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] ); + gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] ); + } +} + +/* +================ +idAFConstraint_PyramidLimit::Save +================ +*/ +void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( pyramidAnchor ); + saveFile->WriteMat3( pyramidBasis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle[0] ); + saveFile->WriteFloat( cosAngle[1] ); + saveFile->WriteFloat( sinHalfAngle[0] ); + saveFile->WriteFloat( sinHalfAngle[1] ); + saveFile->WriteFloat( cosHalfAngle[0] ); + saveFile->WriteFloat( cosHalfAngle[1] ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_PyramidLimit::Restore +================ +*/ +void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( pyramidAnchor ); + saveFile->ReadMat3( pyramidBasis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle[0] ); + saveFile->ReadFloat( cosAngle[1] ); + saveFile->ReadFloat( sinHalfAngle[0] ); + saveFile->ReadFloat( sinHalfAngle[1] ); + saveFile->ReadFloat( cosHalfAngle[0] ); + saveFile->ReadFloat( cosHalfAngle[1] ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( void ) { + Init(); +} + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) { + + assert( clipModel ); + assert( clipModel->IsTraceModel() ); + + Init(); + + this->name = name; + this->clipModel = NULL; + + SetClipModel( clipModel ); + SetDensity( density ); + + current->worldOrigin = clipModel->GetOrigin(); + current->worldAxis = clipModel->GetAxis(); + *next = *current; + +} + +/* +================ +idAFBody::~idAFBody +================ +*/ +idAFBody::~idAFBody( void ) { + delete clipModel; +} + +/* +================ +idAFBody::Init +================ +*/ +void idAFBody::Init( void ) { + name = "noname"; + parent = NULL; + clipModel = NULL; + primaryConstraint = NULL; + tree = NULL; + + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + clipMask = 0; + + frictionDir = vec3_zero; + contactMotorDir = vec3_zero; + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + + mass = 1.0f; + invMass = 1.0f; + centerOfMass = vec3_zero; + inertiaTensor = mat3_identity; + inverseInertiaTensor = mat3_identity; + + current = &state[0]; + next = &state[1]; + current->worldOrigin = vec3_zero; + current->worldAxis = mat3_identity; + current->spatialVelocity = vec6_zero; + current->externalForce = vec6_zero; + *next = *current; + saved = *current; + atRestOrigin = vec3_zero; + atRestAxis = mat3_identity; + + s.Zero( 6 ); + totalForce.Zero( 6 ); + auxForce.Zero( 6 ); + acceleration.Zero( 6 ); + + response = NULL; + responseIndex = NULL; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); + + fl.selfCollision = true; + fl.isZero = true; +} + +/* +================ +idAFBody::SetClipModel +================ +*/ +void idAFBody::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && this->clipModel != clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idAFBody::SetFriction +================ +*/ +void idAFBody::SetFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f ) { + gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact ); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idAFBody::SetBouncyness +================ +*/ +void idAFBody::SetBouncyness( float bounce ) { + if ( bounce < 0.0f || bounce > 1.0f ) { + gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce ); + return; + } + bouncyness = bounce; +} + +/* +================ +idAFBody::SetDensity +================ +*/ +void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) { + + // get the body mass properties + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // make sure we have a valid mass + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // make sure the center of mass is at the body origin + if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() ); + } + centerOfMass.Zero(); + + // calculate the inverse mass and inverse inertia tensor + invMass = 1.0f / mass; + if ( inertiaScale != mat3_identity ) { + inertiaTensor *= inertiaScale; + } + if ( inertiaTensor.IsDiagonal( 1e-3f ) ) { + inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f; + inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f; + inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f; + inverseInertiaTensor.Identity(); + inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0]; + inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1]; + inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2]; + } + else { + inverseInertiaTensor = inertiaTensor.Inverse(); + } +} + +/* +================ +idAFBody::SetFrictionDirection +================ +*/ +void idAFBody::SetFrictionDirection( const idVec3 &dir ) { + frictionDir = dir * current->worldAxis.Transpose(); + fl.useFrictionDir = true; +} + +/* +================ +idAFBody::GetFrictionDirection +================ +*/ +bool idAFBody::GetFrictionDirection( idVec3 &dir ) const { + if ( fl.useFrictionDir ) { + dir = frictionDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::SetContactMotorDirection +================ +*/ +void idAFBody::SetContactMotorDirection( const idVec3 &dir ) { + contactMotorDir = dir * current->worldAxis.Transpose(); + fl.useContactMotorDir = true; +} + +/* +================ +idAFBody::GetContactMotorDirection +================ +*/ +bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const { + if ( fl.useContactMotorDir ) { + dir = contactMotorDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::GetPointVelocity +================ +*/ +idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const { + idVec3 r = point - current->worldOrigin; + return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r ); +} + +/* +================ +idAFBody::AddForce +================ +*/ +void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) { + current->externalForce.SubVec3(0) += force; + current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force ); +} + +/* +================ +idAFBody::InverseWorldSpatialInertiaMultiply + + dst = this->inverseWorldSpatialInertia * v; +================ +*/ +ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const { + const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr(); + const float *vPtr = v; + float *dstPtr = dst.ToFloatPtr(); + + if ( fl.spatialInertiaSparse ) { + dstPtr[0] = mPtr[0*6+0] * vPtr[0]; + dstPtr[1] = mPtr[1*6+1] * vPtr[1]; + dstPtr[2] = mPtr[2*6+2] * vPtr[2]; + dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5]; + dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5]; + dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5]; + } else { + gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() ); + } +} + +/* +================ +idAFBody::Save +================ +*/ +void idAFBody::Save( idSaveGame *saveFile ) { + + saveFile->WriteString( name ); // cniciholson: Added Unsaved Var + //if ( parent ) { + // saveFile->WriteBool( true ); + // parent->Save( savefile ); // cniciholson: Added Unsaved Var + //} + //else { + // saveFile->WriteBool( false ); + //} + // TOSAVE: idList children + // TOSAVE: idClipModel * clipModel + // TOSAVE: idAFConstraint * primaryConstraint + // TOSAVE: idList constraints + // TOSAVE: idAFTree * tree; + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteInt( clipMask ); + saveFile->WriteVec3( frictionDir ); + saveFile->WriteVec3( contactMotorDir ); + saveFile->WriteFloat( contactMotorVelocity ); + saveFile->WriteFloat( contactMotorForce ); + + saveFile->WriteFloat( mass ); + saveFile->WriteFloat( invMass ); + saveFile->WriteVec3( centerOfMass ); + saveFile->WriteMat3( inertiaTensor ); + saveFile->WriteMat3( inverseInertiaTensor ); + + //saveFile->WriteVec3( state[0].worldOrigin ); // cnicholson: Added unsaved var state[0] + //saveFile->WriteMat3( state[0].worldAxis ); + //saveFile->WriteVec6( state[0].spatialVelocity ); + //saveFile->WriteVec6( state[0].externalForce ); + // + //saveFile->WriteVec3( state[1].worldOrigin ); // cnicholson: Added unsaved var state[1] + //saveFile->WriteMat3( state[1].worldAxis ); + //saveFile->WriteVec6( state[1].spatialVelocity ); + //saveFile->WriteVec6( state[1].externalForce ); + + saveFile->WriteVec3( current->worldOrigin ); + saveFile->WriteMat3( current->worldAxis ); + saveFile->WriteVec6( current->spatialVelocity ); + saveFile->WriteVec6( current->externalForce ); + + //saveFile->WriteVec3( next->worldOrigin ); // cnicholson: Added unsaved var next-> + //saveFile->WriteMat3( next->worldAxis ); + //saveFile->WriteVec6( next->spatialVelocity ); + //saveFile->WriteVec6( next->externalForce ); + // + //saveFile->WriteVec3( saved.worldOrigin ); // cnicholson: Added unsaved var saved + //saveFile->WriteMat3( saved.worldAxis ); + //saveFile->WriteVec6( saved.spatialVelocity ); + //saveFile->WriteVec6( saved.externalForce ); + + saveFile->WriteVec3( atRestOrigin ); + saveFile->WriteMat3( atRestAxis ); + + // TOSAVE: idMatX inverseWorldSpatialInerti + // TOSAVE: idMatX I, invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX totalForce; + // TOSAVE: idVecX auxForce; + // TOSAVE: idVecX acceleration; + // TOSAVE: float * response; + // TOSAVE: int * responseIndex; + saveFile->WriteInt( numResponses ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxAuxiliaryIndex ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ) ); // cnicholson: Added unsaved var +} + +/* +================ +idAFBody::Restore +================ +*/ +void idAFBody::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadString( name ); // cniciholson: Added Unrestored Var + // TORESTORE: idList parent + // TORESTORE: idList children + // TORESTORE: idClipModel * clipModel + // TORESTORE: idAFConstraint * primaryConstraint + // TORESTORE: idList constraints + // TORESTORE: idAFTree * tree; + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadInt( clipMask ); + saveFile->ReadVec3( frictionDir ); + saveFile->ReadVec3( contactMotorDir ); + saveFile->ReadFloat( contactMotorVelocity ); + saveFile->ReadFloat( contactMotorForce ); + + saveFile->ReadFloat( mass ); + saveFile->ReadFloat( invMass ); + saveFile->ReadVec3( centerOfMass ); + saveFile->ReadMat3( inertiaTensor ); + saveFile->ReadMat3( inverseInertiaTensor ); + + //saveFile->ReadVec3( state[0].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[0].worldAxis ); + //saveFile->ReadVec6( state[0].spatialVelocity ); + //saveFile->ReadVec6( state[0].externalForce ); + + //saveFile->ReadVec3( state[1].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[1].worldAxis ); + //saveFile->ReadVec6( state[1].spatialVelocity ); + //saveFile->ReadVec6( state[1].externalForce ); + + saveFile->ReadVec3( current->worldOrigin ); + saveFile->ReadMat3( current->worldAxis ); + saveFile->ReadVec6( current->spatialVelocity ); + saveFile->ReadVec6( current->externalForce ); + + //saveFile->ReadVec3( next->worldOrigin ); // cnicholson: Added unrestored var next-> + //saveFile->ReadMat3( next->worldAxis ); + //saveFile->ReadVec6( next->spatialVelocity ); + //saveFile->ReadVec6( next->externalForce ); + + //saveFile->ReadVec3( saved.worldOrigin ); // cnicholson: Added unrestored var saved + //saveFile->ReadMat3( saved.worldAxis ); + //saveFile->ReadVec6( saved.spatialVelocity ); + //saveFile->ReadVec6( saved.externalForce ); + + saveFile->ReadVec3( atRestOrigin ); + saveFile->ReadMat3( atRestAxis ); + + // TORESTORE: idMatX inverseWorldSpatialInerti + // TORESTORE: idMatX I, invI; + // TORESTORE: idMatX J; + // TORESTORE: idVecX s; + // TORESTORE: idVecX totalForce; + // TORESTORE: idVecX auxForce; + // TORESTORE: idVecX acceleration; + // TORESTORE: float * response; + // TORESTORE: int * responseIndex; + saveFile->ReadInt( numResponses ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxAuxiliaryIndex ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unrestored var + + saveFile->Read( &fl, sizeof( fl ) ); // cnicholson: Added unrestored var +} + + +//=============================================================== +// M +// idAFTree MrE +// E +//=============================================================== + +/* +================ +idAFTree::Factor + + factor matrix for the primary constraints in the tree +================ +*/ +void idAFTree::Factor( void ) const { + int i, j; + idAFBody *body; + idAFConstraint *child; + idMatX childI; + + childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) ); + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + if ( body->children.Num() ) { + +// RAVEN BEGIN +// jscott: fixed warning + child = NULL; +// RAVEN END + + for ( j = 0; j < body->children.Num(); j++ ) { + + child = body->children[j]->primaryConstraint; + + // child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J; + childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() ); + child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J ); + childI.Negate(); + + child->invI = childI; + if ( !child->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'", + child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() ); + } + child->J = child->invI * child->J; + + body->I -= child->J.TransposeMultiply( childI ) * child->J; + } + + body->invI = body->I; + if ( !body->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s", + child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() ); + } + if ( body->primaryConstraint ) { + body->J = body->invI * body->J; + } + } + else if ( body->primaryConstraint ) { + body->J = body->inverseWorldSpatialInertia * body->J; + } + } +} + +/* +================ +idAFTree::Solve + + solve for primary constraints in the tree +================ +*/ +void idAFTree::Solve( int auxiliaryIndex ) const { + int i, j; + idAFBody *body, *child; + idAFConstraint *primaryConstraint; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + primaryConstraint = child->primaryConstraint; + + if ( !child->fl.isZero ) { + child->J.TransposeMultiplySub( primaryConstraint->s, child->s ); + primaryConstraint->fl.isZero = false; + } + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s ); + body->fl.isZero = false; + } + } + } + + bool useSymmetry = af_useSymmetry.GetBool(); + + // from the root down towards the leaves + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + + if ( primaryConstraint ) { + + if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s; + } + primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s ); + + primaryConstraint->lm = primaryConstraint->s; + + if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( body->children.Num() ) { + if ( !body->fl.isZero ) { + body->s = body->invI * body->s; + } + body->J.MultiplySub( body->s, primaryConstraint->s ); + } + } else if ( body->children.Num() ) { + body->s = body->invI * body->s; + } + } +} + +/* +================ +idAFTree::Response + + calculate body forces in the tree in response to a constraint force +================ +*/ +void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const { + int i, j; + idAFBody *body; + idAFConstraint *child, *primaryConstraint; + idVecX v; + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + body = constraint->body1; + if ( body->tree == this ) { + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + else { + body = constraint->body2; + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + return; + } + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + + // initialize right hand side to zero + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->s.Zero(); + primaryConstraint->fl.isZero = true; + } + body->s.Zero(); + body->fl.isZero = true; + body->GetResponseForce( body->numResponses ).Zero(); + } + + // set right hand side for first constrained body + body = constraint->body1; + if ( body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.Multiply( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.Multiply( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + } + + // set right hand side for second constrained body + body = constraint->body2; + if ( body && body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.MultiplyAdd( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + } + + + // solve for primary constraints + Solve( auxiliaryIndex ); + + bool useSymmetry = af_useSymmetry.GetBool(); + + // store body forces in response to the constraint force + idVecX force; + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + force.SetData( 6, body->response + body->numResponses * 8 ); + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( force, child->lm ); + } + + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } +} + +/* +================ +idAFTree::CalculateForces + + calculate forces on the bodies in the tree +================ +*/ +void idAFTree::CalculateForces( float timeStep ) const { + int i, j; + float invStep; + idAFBody *body; + idAFConstraint *child, *c, *primaryConstraint; + + // forces on bodies + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0); + } + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + return; + } + + invStep = 1.0f / timeStep; + + // initialize right hand side + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + // b = ( J * acc + c ) + c = primaryConstraint; + c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 ); + c->fl.isZero = false; + } + body->s.Zero(); + body->fl.isZero = true; + } + + // solve for primary constraints + Solve(); + + // calculate forces on bodies after applying primary constraints + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( body->totalForce, child->lm ); + } + } +} + +/* +================ +idAFTree::SetMaxSubTreeAuxiliaryIndex +================ +*/ +void idAFTree::SetMaxSubTreeAuxiliaryIndex( void ) { + int i, j; + idAFBody *body, *child; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) { + body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex; + } + } + } +} + +/* +================ +idAFTree::SortBodies_r +================ +*/ +void idAFTree::SortBodies_r( idList&sortedList, idAFBody *body ) { + int i; + + for ( i = 0; i < body->children.Num(); i++ ) { + sortedList.Append( body->children[i] ); + } + for ( i = 0; i < body->children.Num(); i++ ) { + SortBodies_r( sortedList, body->children[i] ); + } +} + +/* +================ +idAFTree::SortBodies + + sort body list to make sure parents come first +================ +*/ +void idAFTree::SortBodies( void ) { + int i; + idAFBody *body; + + // find the root + for ( i = 0; i < sortedBodies.Num(); i++ ) { + if ( !sortedBodies[i]->parent ) { + break; + } + } + + if ( i >= sortedBodies.Num() ) { + gameLocal.Error( "Articulated figure tree has no root." ); + } + + body = sortedBodies[i]; + sortedBodies.Clear(); + sortedBodies.Append( body ); + SortBodies_r( sortedBodies, body ); +} + +/* +================ +idAFTree::DebugDraw +================ +*/ +void idAFTree::DebugDraw( const idVec4 &color ) const { + int i; + idAFBody *body; + + for ( i = 1; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 ); + } +} + + +//=============================================================== +// M +// idPhysics_AF MrE +// E +//=============================================================== + +/* +================ +idPhysics_AF::EvaluateConstraints +================ +*/ +void idPhysics_AF::EvaluateConstraints( float timeStep ) { + int i; + float invTimeStep; + idAFBody *body; + idAFConstraint *c; + + invTimeStep = 1.0f / timeStep; + + // setup the constraint equations for the current position and orientation of the bodies + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + c = primaryConstraints[i]; + c->Evaluate( invTimeStep ); + c->J = c->J2; + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->Evaluate( invTimeStep ); + } + + // add contact constraints to the list with frame constraints + for ( i = 0; i < contactConstraints.Num(); i++ ) { + AddFrameConstraint( contactConstraints[i] ); + } + + // setup body primary constraint matrix + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->primaryConstraint ) { + body->J = body->primaryConstraint->J1.Transpose(); + } + } +} + +/* +================ +idPhysics_AF::EvaluateBodies +================ +*/ +void idPhysics_AF::EvaluateBodies( float timeStep ) { + int i; + idAFBody *body; + idMat3 axis; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // we transpose the axis before using it because idMat3 is column-major + axis = body->current->worldAxis.Transpose(); + + // if the center of mass is at the body point of reference + if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() ); + + body->fl.spatialInertiaSparse = true; + } + else { + idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass ); + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, massMoment, + massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia = body->I.InverseFast(); + + body->fl.spatialInertiaSparse = false; + } + + // initialize auxiliary constraint force to zero + body->auxForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddFrameConstraints +================ +*/ +void idPhysics_AF::AddFrameConstraints( void ) { + int i; + + // add frame constraints to auxiliary constraints + for ( i = 0; i < frameConstraints.Num(); i++ ) { + auxiliaryConstraints.Append( frameConstraints[i] ); + } +} + +/* +================ +idPhysics_AF::RemoveFrameConstraints +================ +*/ +void idPhysics_AF::RemoveFrameConstraints( void ) { + // remove all the frame constraints from the auxiliary constraints + auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num(), false ); + frameConstraints.SetNum( 0, false ); +} + +/* +================ +idPhysics_AF::ApplyFriction +================ +*/ +void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) { + int i; + float invTimeStep; + + if ( af_skipFriction.GetBool() ) { + return; + } + + if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f; + if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime; + } else { + jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime; + } + } else { + jointFrictionDentScale = 0.0f; + } + + if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f; + if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime; + } else { + contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime; + } + } else { + contactFrictionDentScale = 0.0f; + } + + invTimeStep = 1.0f / timeStep; + + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + primaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < frameConstraints.Num(); i++ ) { + frameConstraints[i]->ApplyFriction( invTimeStep ); + } +} + +/* +================ +idPhysics_AF::PrimaryFactor +================ +*/ +void idPhysics_AF::PrimaryFactor( void ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->Factor(); + } +} + +/* +================ +idPhysics_AF::PrimaryForces +================ +*/ +void idPhysics_AF::PrimaryForces( float timeStep ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->CalculateForces( timeStep ); + } +} + +/* +================ +idPhysics_AF::AuxiliaryForces +================ +*/ +void idPhysics_AF::AuxiliaryForces( float timeStep ) { + int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex; + float *ptr, *j1, *j2, *dstPtr, *forcePtr; + float invStep, u; + idAFBody *body; + idAFConstraint *constraint; + idVecX tmp; + idMatX jmk; + idVecX rhs, w, lm, lo, hi; + + // get the number of one dimensional auxiliary constraints + for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + if ( numAuxConstraints == 0 ) { + return; + } + + // allocate memory to store the body response to auxiliary constraint forces + forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) ); + index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) ); + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = forcePtr; + body->responseIndex = index; + body->numResponses = 0; + body->maxAuxiliaryIndex = 0; + forcePtr += numAuxConstraints * 8; + index += numAuxConstraints; + } + + // set on each body the largest index of an auxiliary constraint constraining the body + if ( af_useSymmetry.GetBool() ) { + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + if ( k > constraint->body1->maxAuxiliaryIndex ) { + constraint->body1->maxAuxiliaryIndex = k; + } + if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) { + constraint->body2->maxAuxiliaryIndex = k; + } + } + } + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SetMaxSubTreeAuxiliaryIndex(); + } + } + + // calculate forces of primary constraints in response to the auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + // calculate body forces in the tree in response to the constraint force + constraint->body1->tree->Response( constraint, j, k ); + // if there is a second body which is part of a different tree + if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) { + // calculate body forces in the second tree in response to the constraint force + constraint->body2->tree->Response( constraint, j, k ); + } + } + } + + // NOTE: the rows are 16 byte padded + jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) ); + tmp.SetData( 6, VECX_ALLOCA( 6 ) ); + + // create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] ); + j1 = tmp.ToFloatPtr(); + ptr = constraint->body1->response; + index = constraint->body1->responseIndex; + dstPtr = jmk[k]; + s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints; + for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) { + while( l < m ) { + dstPtr[l++] = 0.0f; + } + dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + ptr += 8; + } + + while( l < s ) { + dstPtr[l++] = 0.0f; + } + + if ( constraint->body2 ) { + constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] ); + j2 = tmp.ToFloatPtr(); + ptr = constraint->body2->response; + index = constraint->body2->responseIndex; + for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) { + dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + ptr += 8; + } + } + } + } + + if ( af_useSymmetry.GetBool() ) { + n = jmk.GetNumColumns(); + for ( i = 0; i < numAuxConstraints; i++ ) { + ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i; + dstPtr = jmk.ToFloatPtr() + i * n + i + 1; + for ( j = i+1; j < numAuxConstraints; j++ ) { + *dstPtr++ = *ptr; + ptr += n; + } + } + } + + invStep = 1.0f / timeStep; + + // calculate body acceleration + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + } + + rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) ); + + // set first index for special box constrained variables + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->firstIndex = k; + k += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + // initialize right hand side and low and high bounds for auxiliary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + n = k; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + j1 = constraint->J1[j]; + ptr = constraint->body1->acceleration.ToFloatPtr(); + rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + rhs[k] += constraint->c1[j] * invStep; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->acceleration.ToFloatPtr(); + rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + rhs[k] += constraint->c2[j] * invStep; + } + + rhs[k] = -rhs[k]; + lo[k] = constraint->lo[j]; + hi[k] = constraint->hi[j]; + + if ( constraint->boxIndex[j] >= 0 ) { + if ( constraint->boxConstraint->fl.isPrimary ) { + gameLocal.Error( "cannot reference primary constraints for the box index" ); + } + boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j]; + } + else { + boxIndex[k] = -1; + } + jmk[k][k] += constraint->e[j] * invStep; + } + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Start(); + } +#endif + + // calculate lagrange multipliers for auxiliary constraints + if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) { + return; // bad monkey! + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Stop(); + } +#endif + + // calculate auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->lm[j] = u = lm[k]; + + j1 = constraint->J1[j]; + ptr = constraint->body1->auxForce.ToFloatPtr(); + ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u; + ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->auxForce.ToFloatPtr(); + ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u; + ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u; + } + } + } + + // recalculate primary constraint forces in response to auxiliary constraint forces + PrimaryForces( timeStep ); + + // clear pointers pointing to stack space so tools don't get confused + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = NULL; + body->responseIndex = NULL; + } +} + +/* +================ +idPhysics_AF::VerifyContactConstraints +================ +*/ +void idPhysics_AF::VerifyContactConstraints( void ) { +#if 0 + int i; + float impulseNumerator, impulseDenominator; + idVec3 r, velocity, normalVelocity, normal, impulse; + idAFBody *body; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + + r = contact.point - body->GetCenterOfMass(); + + // calculate velocity at contact point + velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r ); + + // velocity along normal vector + normalVelocity = ( velocity * contact.normal ) * contact.normal; + + // if moving towards the surface at the contact point + if ( normalVelocity * contact.normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f; + + // apply impulse + body->SetLinearVelocity( body->GetLinearVelocity() + impulse ); + body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) ); + } + } +#else + int i; + idAFBody *body; + idVec3 normal; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + normal = contactConstraints[i]->GetContact().normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + body = contactConstraints[i]->body2; + if ( !body ) { + continue; + } + normal = -normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + } +#endif +} + +/* +================ +idPhysics_AF::Evolve +================ +*/ +void idPhysics_AF::Evolve( float timeStep ) { + int i; + float angle; + idVec3 vec; + idAFBody *body; + idVec6 force; + idRotation rotation; + float vSqr, maxLinearVelocity, maxAngularVelocity; + + maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep; + maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // calculate the spatial velocity for the next physics state + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0); + + if ( maxLinearVelocity > 0.0f ) { + // cap the linear velocity + vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr(); + if ( vSqr > Square( maxLinearVelocity ) ) { + body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity; + } + } + + if ( maxAngularVelocity > 0.0f ) { + // cap the angular velocity + vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr(); + if ( vSqr > Square( maxAngularVelocity ) ) { + body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity; + } + } + } + + // make absolutely sure all contact constraints are satisfied + VerifyContactConstraints(); + + // calculate the position of the bodies for the next physics state + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // translate world origin + body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 ); + + // convert angular velocity to a rotation matrix + vec = body->next->spatialVelocity.SubVec3( 1 ); + angle = -timeStep * (float) RAD2DEG( vec.Normalize() ); + rotation = idRotation( vec3_origin, vec, angle ); + rotation.Normalize180(); + + // rotate world axis + body->next->worldAxis = body->current->worldAxis * rotation.ToMat3(); + body->next->worldAxis.OrthoNormalizeSelf(); + + // linear and angular friction + body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0); + body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1); + } +} + +/* +================ +idPhysics_AF::CollisionImpulse + + apply impulse to the colliding bodies + the current state of the body should be set to the moment of impact + this is silly as it doesn't take the AF structure into account +================ +*/ +bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) { + idVec3 r, velocity, impulse; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator; + impactInfo_t info; + idEntity *ent; + + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent == self || !ent ) { + return false; + } + + // get info from other entity involved + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + // collision point relative to the body center of mass + r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis); + // the velocity at the collision point + velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + // never stick + if ( velocity * collision.c.normal > 0.0f ) { + velocity = collision.c.normal; + } + inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis; + impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal ); + impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + + // callback to self to let the entity know about the impact + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_AF::ApplyCollisions +================ +*/ +bool idPhysics_AF::ApplyCollisions( float timeStep ) { + int i; + + for ( i = 0; i < collisions.Num(); i++ ) { + if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_AF::SetupCollisionForBody +================ +*/ +idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const { + int i; + idAFBody *b; + idEntity *passEntity; + + passEntity = NULL; + + if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) { + + // disable all bodies + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } + + // don't collide with world collision model if attached to the world + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } + } + + } else { + + // enable all bodies that have self collision + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->fl.selfCollision ) { + bodies[i]->clipModel->Enable(); + } else { + bodies[i]->clipModel->Disable(); + } + } + + // don't let the body collide with itself + body->clipModel->Disable(); + + // disable any bodies attached with constraints + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } else { + if ( body->constraints[i]->body1 == body ) { + b = body->constraints[i]->body2; + } else if ( body->constraints[i]->body2 == body ) { + b = body->constraints[i]->body1; + } else { + continue; + } + // don't collide with this body + b->clipModel->Disable(); + } + } + } + + return passEntity; +} + +/* +================ +idPhysics_AF::CheckForCollisions + + check for collisions between the current and next state + if there is a collision the next state is set to the state at the moment of impact + assumes all bodies are linked for collision detection and relinks all bodies after moving them +================ +*/ +void idPhysics_AF::CheckForCollisions( float timeStep ) { +// #define TEST_COLLISION_DETECTION + int i, index; + idAFBody *body; + idMat3 axis; + idRotation rotation; + trace_t collision; + idEntity *passEntity; + + // clear list with collisions + collisions.SetNum( 0, false ); + + if ( !enableCollision ) { + return; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask != 0 && !fastEval ) { +// RAVEN END + + passEntity = SetupCollisionForBody( body ); + +#ifdef TEST_COLLISION_DETECTION + bool startsolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->current->worldOrigin, body->clipModel, + body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( body->current->worldOrigin ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, body->current->worldOrigin, body->next->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + body->next->worldOrigin = collision.endpos; + body->next->worldAxis = collision.endAxis; + + // add collision to the list + index = collisions.Num(); + collisions.SetNum( index + 1, false ); + collisions[index].trace = collision; + collisions[index].body = body; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->next->worldOrigin, body->clipModel, + body->next->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::EvaluateContacts +================ +*/ +bool idPhysics_AF::EvaluateContacts( void ) { + int i, j, k, numContacts, numBodyContacts; + idAFBody *body; + contactInfo_t contactInfo[10]; + idEntity *passEntity; + idVecX dir( 6, VECX_ALLOCA( 6 ) ); + + // evaluate bodies + EvaluateBodies( current.lastTimeStep ); + + // remove all existing contacts + ClearContacts(); + + contactBodies.SetNum( 0, false ); + + if ( !enableCollision ) { + return false; + } + + // find all the contacts + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask == 0 || fastEval ) { +// RAVEN END + continue; + } + + passEntity = SetupCollisionForBody( body ); + + body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() ); + dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0); + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numContacts = gameLocal.Contacts( self, contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ); +// RAVEN END +#if 1 + // merge nearby contacts between the same bodies + // and assure there are at most three planar contacts between any pair of bodies + for ( j = 0; j < numContacts; j++ ) { + + numBodyContacts = 0; + for ( k = 0; k < contacts.Num(); k++ ) { + if ( contacts[k].entityNum == contactInfo[j].entityNum ) { + if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) || + ( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) { + + if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) { + break; + } + if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) { + numBodyContacts++; + } + } + } + } + + if ( k >= contacts.Num() && numBodyContacts < 3 ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } + } + +#else + + for ( j = 0; j < numContacts; j++ ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } +#endif + + } + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_AF::SetupContactConstraints +================ +*/ +void idPhysics_AF::SetupContactConstraints( void ) { + int i; + + // make sure enough contact constraints are allocated + contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement ); + contactConstraints.SetNum( contacts.Num(), false ); + + // setup contact constraints + for ( i = 0; i < contacts.Num(); i++ ) { + // add contact constraint + contactConstraints[i]->physics = this; + if ( contacts[i].entityNum == self->entityNumber ) { + contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] ); + } + else { + contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] ); + } + } +} + +/* +================ +idPhysics_AF::ApplyContactForces +================ +*/ +void idPhysics_AF::ApplyContactForces( void ) { +#if 0 + int i; + idEntity *ent; + idVec3 force; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + if ( contactConstraints[i]->body2 != NULL ) { + continue; + } + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + ent = gameLocal.entities[contact.entityNum]; + if ( !ent ) { + continue; + } + force.Zero(); + ent->AddForce( self, contact.id, contact.point, force ); + } +#endif +} + +/* +================ +idPhysics_AF::ClearExternalForce +================ +*/ +void idPhysics_AF::ClearExternalForce( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // clear external force + body->current->externalForce.Zero(); + body->next->externalForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddGravity +================ +*/ +void idPhysics_AF::AddGravity( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + // add gravitational force + body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector; + } +} + +/* +================ +idPhysics_AF::SwapStates +================ +*/ +void idPhysics_AF::SwapStates( void ) { + int i; + idAFBody *body; + AFBodyPState_t *swap; + + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + + // swap the current and next state for next simulation step + swap = body->current; + body->current = body->next; + body->next = swap; + } +} + +/* +================ +idPhysics_AF::UpdateClipModels +================ +*/ +void idPhysics_AF::UpdateClipModels( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::SetSuspendSpeed +================ +*/ +void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) { + this->suspendVelocity = velocity; + this->suspendAcceleration = acceleration; +} + +/* +================ +idPhysics_AF::SetSuspendTime +================ +*/ +void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) { + this->minMoveTime = minTime; + this->maxMoveTime = maxTime; +} + +/* +================ +idPhysics_AF::SetSuspendTolerance +================ +*/ +void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) { + this->noMoveTime = noMoveTime; + this->noMoveTranslation = noMoveTranslation; + this->noMoveRotation = noMoveRotation; +} + +/* +================ +idPhysics_AF::SetTimeScaleRamp +================ +*/ +void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) { + timeScaleRampStart = start; + timeScaleRampEnd = end; +} + +/* +================ +idPhysics_AF::SetJointFrictionDent +================ +*/ +void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) { + jointFrictionDent = dent; + jointFrictionDentStart = start; + jointFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetJointFrictionScale +================ +*/ +float idPhysics_AF::GetJointFrictionScale( void ) const { + if ( jointFrictionDentScale > 0.0f ) { + return jointFrictionDentScale; + } else if ( jointFrictionScale > 0.0f ) { + return jointFrictionScale; + } else if ( af_jointFrictionScale.GetFloat() > 0.0f ) { + return af_jointFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::SetContactFrictionDent +================ +*/ +void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) { + contactFrictionDent = dent; + contactFrictionDentStart = start; + contactFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetContactFrictionScale +================ +*/ +float idPhysics_AF::GetContactFrictionScale( void ) const { + if ( contactFrictionDentScale > 0.0f ) { + return contactFrictionDentScale; + } else if ( contactFrictionScale > 0.0f ) { + return contactFrictionScale; + } else if ( af_contactFrictionScale.GetFloat() > 0.0f ) { + return af_contactFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::TestIfAtRest +================ +*/ +bool idPhysics_AF::TestIfAtRest( float timeStep ) { + int i; + float translationSqr, maxTranslationSqr, rotation, maxRotation; + idAFBody *body; + + if ( current.atRest >= 0 ) { + return true; + } + + current.activateTime += timeStep; + + // if the simulation should never be suspended before a certaint amount of time passed + if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) { + return false; + } + + // if the simulation should always be suspended after a certain amount time passed + if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) { + return true; + } + + // test if all bodies hardly moved over a period of time + if ( current.noMoveTime == 0.0f ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->atRestOrigin = body->current->worldOrigin; + body->atRestAxis = body->current->worldAxis; + } + current.noMoveTime += timeStep; + } + else if ( current.noMoveTime > noMoveTime ) { + current.noMoveTime = 0.0f; + maxTranslationSqr = 0.0f; + maxRotation = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr(); + if ( translationSqr > maxTranslationSqr ) { + maxTranslationSqr = translationSqr; + } + rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle(); + if ( rotation > maxRotation ) { + maxRotation = rotation; + } + } + + if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) { + // hardly moved over a period of time so the articulated figure may come to rest + return true; + } + } else { + current.noMoveTime += timeStep; + } + + // test if the velocity or acceleration of any body is still too large to come to rest + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) { + return false; + } + if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) { + return false; + } + if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) { + return false; + } + if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) { + return false; + } + } + + // all bodies have a velocity and acceleration small enough to come to rest + return true; +} + +/* +================ +idPhysics_AF::Rest +================ +*/ +void idPhysics_AF::Rest( void ) { + int i; + + current.atRest = gameLocal.time; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity.Zero(); + bodies[i]->current->externalForce.Zero(); + } + + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::Activate +================ +*/ +void idPhysics_AF::Activate( void ) { + // if the articulated figure was at rest + if ( current.atRest >= 0 ) { + // normally gravity is added at the end of a simulation frame + // if the figure was at rest add gravity here so it is applied this simulation frame + AddGravity(); + // reset the active time for the max move time + current.activateTime = 0.0f; + } + current.atRest = -1; + current.noMoveTime = 0.0f; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_AF::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_AF::EnableImpact +================ +*/ +void idPhysics_AF::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_AF::DisableImpact +================ +*/ +void idPhysics_AF::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_AF::AddPushVelocity +================ +*/ +void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) { + int i; + + if ( pushVelocity != vec6_origin ) { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity += pushVelocity; + } + } +} + +/* +================ +idPhysics_AF::SetClipModel +================ +*/ +void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_AF::GetClipModel +================ +*/ +idClipModel *idPhysics_AF::GetClipModel( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel(); + } + return NULL; +} + +/* +================ +idPhysics_AF::GetNumClipModels +================ +*/ +int idPhysics_AF::GetNumClipModels( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::SetMass +================ +*/ +void idPhysics_AF::SetMass( float mass, int id ) { + if ( id >= 0 && id < bodies.Num() ) { + } + else { + forceTotalMass = mass; + } + SetChanged(); +} + +/* +================ +idPhysics_AF::GetMass +================ +*/ +float idPhysics_AF::GetMass( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->mass; + } + return totalMass; +} + +/* +================ +idPhysics_AF::SetContents +================ +*/ +void idPhysics_AF::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < bodies.Num() ) { + bodies[id]->GetClipModel()->SetContents( contents ); + } + else { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->GetClipModel()->SetContents( contents ); + } + } +} + +/* +================ +idPhysics_AF::GetContents +================ +*/ +int idPhysics_AF::GetContents( int id ) const { + int i, contents; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetContents(); + } + else { + contents = 0; + for ( i = 0; i < bodies.Num(); i++ ) { + contents |= bodies[i]->GetClipModel()->GetContents(); + } + return contents; + } +} + +/* +================ +idPhysics_AF::GetBounds +================ +*/ +const idBounds &idPhysics_AF::GetBounds( int id ) const { + int i; + static idBounds relBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetBounds(); + } + else if ( !bodies.Num() ) { + relBounds.Zero(); + return relBounds; + } + else { + relBounds = bodies[0]->GetClipModel()->GetBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + idBounds bounds; + idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose(); + idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose(); + bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis ); + relBounds += bounds; + } + return relBounds; + } +} + +/* +================ +idPhysics_AF::GetAbsBounds +================ +*/ +const idBounds &idPhysics_AF::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetAbsBounds(); + } + else if ( !bodies.Num() ) { + absBounds.Zero(); + return absBounds; + } + else { + absBounds = bodies[0]->GetClipModel()->GetAbsBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + absBounds += bodies[i]->GetClipModel()->GetAbsBounds(); + } + return absBounds; + } +} + +/* +================ +idPhysics_AF::Evaluate +================ +*/ +bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { + float timeStep; + + if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) { + timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart ); + } else if ( af_timeScale.GetFloat() != 1.0f ) { + timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat(); + } else { + timeStep = MS2SEC( timeStepMSec ) * timeScale; + } + current.lastTimeStep = timeStep; + + + // if the articulated figure changed + if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) { + BuildTrees(); + changedAF = false; + linearTime = af_useLinearTime.GetBool(); + } + + // get the new master position + if ( masterBody ) { + idVec3 masterOrigin; + idMat3 masterAxis; + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) { + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + + // if the simulation is suspended because the figure is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // move the af velocity into the frame of a pusher + AddPushVelocity( -current.pushVelocity ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Start(); + timer_collision.Start(); + } +#endif + + // evaluate contacts + EvaluateContacts(); + + // setup contact constraints + SetupContactConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // evaluate constraint equations + EvaluateConstraints( timeStep ); + + // apply friction + ApplyFriction( timeStep, endTimeMSec ); + + // add frame constraints + AddFrameConstraints(); + +#ifdef AF_TIMINGS + int numPrimary = 0, numAuxiliary = 0; + if ( af_showTimings.GetInteger() != 0 ) { + int i; + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + numPrimary += primaryConstraints[i]->J1.GetNumRows(); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows(); + } + timer_pc.Start(); + } +#endif + + // factor matrices for primary constraints + PrimaryFactor(); + + // calculate forces on bodies after applying primary constraints + PrimaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_pc.Stop(); + timer_ac.Start(); + } +#endif + + // calculate and apply auxiliary constraint forces + AuxiliaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_ac.Stop(); + } +#endif + + // evolve current state to next state + Evolve( timeStep ); + + // debug graphics + DebugDraw(); + + // clear external forces on all bodies + ClearExternalForce(); + + // apply contact force to other entities + ApplyContactForces(); + + // remove all frame constraints + RemoveFrameConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions between current and next state + CheckForCollisions( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // swap the current and next state + SwapStates(); + + // make sure all clip models are disabled in case they were enabled for self collision + if ( selfCollision && !af_skipSelfCollision.GetBool() ) { + DisableClip(); + } + + // apply collision impulses + if ( ApplyCollisions( timeStep ) ) { + current.atRest = gameLocal.time; + comeToRest = true; + } + + // test if the simulation can be suspended because the whole figure is at rest + if ( comeToRest && TestIfAtRest( timeStep ) ) { + Rest(); + } else { + ActivateContactEntities(); + } + + // add gravitational force + AddGravity(); + + // move the af velocity back into the world frame + AddPushVelocity( current.pushVelocity ); + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { +// RAVEN BEGIN +// kfuller: warnings shouldn't crash the game + if ( bodies.Num() && bodies[0] && bodies[0]->current && self ) { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); + } else { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' -- no body", + self->name.c_str(), self->GetType()->classname ); + } +// RAVEN END + Rest(); + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Stop(); + + if ( af_showTimings.GetInteger() == 1 ) { + gameLocal.Printf( "%12s: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + else if ( af_showTimings.GetInteger() == 2 ) { + numArticulatedFigures++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "af %d: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + numArticulatedFigures, + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + } + + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numArticulatedFigures = 0; + timer_total.Clear(); + timer_pc.Clear(); + timer_ac.Clear(); + timer_collision.Clear(); + timer_lcp.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_AF::UpdateTime +================ +*/ +void idPhysics_AF::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_AF::GetTime +================ +*/ +int idPhysics_AF::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +DrawTraceModelSilhouette +================ +*/ +void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) { + int i, numSilEdges; + int silEdges[MAX_TRACEMODEL_EDGES]; + idVec3 v1, v2; + const idTraceModel *trm = clipModel->GetTraceModel(); + const idVec3 &origin = clipModel->GetOrigin(); + const idMat3 &axis = clipModel->GetAxis(); + + numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges ); + for ( i = 0; i < numSilEdges; i++ ) { + v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITSET( silEdges[i] ) ] ]; + v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITNOTSET( silEdges[i] ) ] ]; + gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 ); + } +} + +/* +================ +idPhysics_AF::DebugDraw +================ +*/ +void idPhysics_AF::DebugDraw( void ) { + int i; + idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL; + idAFConstraint *constraint; + idVec3 center; + idMat3 axis; + + if ( af_highlightConstraint.GetString()[0] ) { + constraint = GetConstraint( af_highlightConstraint.GetString() ); + if ( constraint ) { + constraint->GetCenter( center ); + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 ); + + if ( af_showConstrainedBodies.GetBool() ) { + cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) ); + constrainedBody1 = constraint->body1; + if ( constrainedBody1 ) { + collisionModelManager->DrawModel( constrainedBody1->clipModel->GetCollisionModel(), constrainedBody1->clipModel->GetOrigin(), + constrainedBody1->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) ); + constrainedBody2 = constraint->body2; + if ( constrainedBody2 ) { + collisionModelManager->DrawModel( constrainedBody2->clipModel->GetCollisionModel(), constrainedBody2->clipModel->GetOrigin(), + constrainedBody2->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + } + + if ( af_highlightBody.GetString()[0] ) { + highlightBody = GetBody( af_highlightBody.GetString() ); + if ( highlightBody ) { + cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) ); + collisionModelManager->DrawModel( highlightBody->clipModel->GetCollisionModel(), highlightBody->clipModel->GetOrigin(), + highlightBody->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + + if ( af_showBodies.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + if ( body == constrainedBody1 || body == constrainedBody2 ) { + continue; + } + if ( body == highlightBody ) { + continue; + } + collisionModelManager->DrawModel( body->clipModel->GetCollisionModel(), body->clipModel->GetOrigin(), + body->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + //DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel ); + } + } + + if ( af_showBodyNames.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showMass.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showTotalMass.GetBool() ) { + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 ); + } + + if ( af_showInertia.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + idMat3 &I = body->inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showVelocity.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f ); + } + } + + if ( af_showConstraints.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->DebugDraw(); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->DebugDraw(); + } + } + } + + if ( af_showConstraintNames.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + } + + if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) { + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) ); + } + } +} + +/* +================ +idPhysics_AF::idPhysics_AF +================ +*/ +idPhysics_AF::idPhysics_AF( void ) { + trees.Clear(); + bodies.Clear(); + constraints.Clear(); + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + frameConstraints.Clear(); + contacts.Clear(); + collisions.Clear(); + changedAF = true; + masterBody = NULL; + + lcp = idLCP::AllocSymmetric(); + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + saved = current; + + linearFriction = 0.005f; + angularFriction = 0.005f; + contactFriction = 0.8f; + bouncyness = 0.4f; + totalMass = 0.0f; + forceTotalMass = -1.0f; + + suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY ); + suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION ); + noMoveTime = NO_MOVE_TIME; + noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE; + noMoveRotation = NO_MOVE_ROTATION_TOLERANCE; + minMoveTime = MIN_MOVE_TIME; + maxMoveTime = MAX_MOVE_TIME; + impulseThreshold = IMPULSE_THRESHOLD; + + timeScale = 1.0f; + timeScaleRampStart = 0.0f; + timeScaleRampEnd = 0.0f; + + jointFrictionScale = 0.0f; + jointFrictionDent = 0.0f; + jointFrictionDentStart = 0.0f; + jointFrictionDentEnd = 0.0f; + jointFrictionDentScale = 0.0f; + + contactFrictionScale = 0.0f; + contactFrictionDent = 0.0f; + contactFrictionDentStart = 0.0f; + contactFrictionDentEnd = 0.0f; + contactFrictionDentScale = 0.0f; + + enableCollision = true; + selfCollision = true; + comeToRest = true; + linearTime = true; + noImpact = false; + worldConstraintsLocked = false; + forcePushable = false; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + fastEval = false; +// RAVEN END + +#ifdef AF_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_AF::~idPhysics_AF +================ +*/ +idPhysics_AF::~idPhysics_AF( void ) { + int i; + + trees.DeleteContents( true ); + + for ( i = 0; i < bodies.Num(); i++ ) { + delete bodies[i]; + } + + for ( i = 0; i < constraints.Num(); i++ ) { + delete constraints[i]; + } + + contactConstraints.SetNum( contactConstraints.NumAllocated(), false ); + for ( i = 0; i < contactConstraints.NumAllocated(); i++ ) { + delete contactConstraints[i]; + } + + delete lcp; + + if ( masterBody ) { + delete masterBody; + } +} + +/* +================ +idPhysics_AF_SavePState +================ +*/ +void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) { + saveFile->WriteInt( state.atRest ); + saveFile->WriteFloat( state.noMoveTime ); + saveFile->WriteFloat( state.activateTime ); + saveFile->WriteFloat( state.lastTimeStep ); + saveFile->WriteVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF_RestorePState +================ +*/ +void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) { + saveFile->ReadInt( state.atRest ); + saveFile->ReadFloat( state.noMoveTime ); + saveFile->ReadFloat( state.activateTime ); + saveFile->ReadFloat( state.lastTimeStep ); + saveFile->ReadVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF::Save +================ +*/ +void idPhysics_AF::Save( idSaveGame *saveFile ) const { + int i; + + // the articulated figure structure is handled by the owner + + // TOSAVE: idList trees; + saveFile->WriteInt( bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Save( saveFile ); + } + if ( masterBody ) { + saveFile->WriteBool( true ); + masterBody->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + + saveFile->WriteInt( constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Save( saveFile ); + } + + // TOSAVE: idListprimaryConstraints; + // TOSAVE: idListauxiliaryConstraints; + // TOSAVE: idListframeConstraints; + // TOSAVE: idListcontactConstraints; + // TOSAVE: idList contactBodies; + // TOSAVE: idList collisions; + + saveFile->WriteBool( changedAF ); + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteFloat( totalMass ); + saveFile->WriteFloat( forceTotalMass ); + + saveFile->WriteVec2( suspendVelocity ); + saveFile->WriteVec2( suspendAcceleration ); + saveFile->WriteFloat( noMoveTime ); + saveFile->WriteFloat( noMoveTranslation ); + saveFile->WriteFloat( noMoveRotation ); + saveFile->WriteFloat( minMoveTime ); + saveFile->WriteFloat( maxMoveTime ); + saveFile->WriteFloat( impulseThreshold ); + + saveFile->WriteFloat( timeScale ); + saveFile->WriteFloat( timeScaleRampStart ); + saveFile->WriteFloat( timeScaleRampEnd ); + + saveFile->WriteFloat( jointFrictionScale ); + saveFile->WriteFloat( jointFrictionDent ); + saveFile->WriteFloat( jointFrictionDentStart ); + saveFile->WriteFloat( jointFrictionDentEnd ); + saveFile->WriteFloat( jointFrictionDentScale ); + + saveFile->WriteFloat( contactFrictionScale ); + saveFile->WriteFloat( contactFrictionDent ); + saveFile->WriteFloat( contactFrictionDentStart ); + saveFile->WriteFloat( contactFrictionDentEnd ); + saveFile->WriteFloat( contactFrictionDentScale ); + + saveFile->WriteBool( enableCollision ); + saveFile->WriteBool( selfCollision ); + saveFile->WriteBool( comeToRest ); + saveFile->WriteBool( linearTime ); + saveFile->WriteBool( noImpact ); + saveFile->WriteBool( worldConstraintsLocked ); + saveFile->WriteBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->WriteBool( fastEval ); +// RAVEN END + + idPhysics_AF_SavePState( saveFile, current ); + idPhysics_AF_SavePState( saveFile, saved ); + + // TOSAVE: idAFBody * masterBody; + // TOSAVE: idLCP * lcp; +} + +/* +================ +idPhysics_AF::Restore +================ +*/ +void idPhysics_AF::Restore( idRestoreGame *saveFile ) { + int i, num; + bool hasMaster; + + // the articulated figure structure should have already been restored + + saveFile->ReadInt( num ); + assert( num == bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Restore( saveFile ); + } + saveFile->ReadBool( hasMaster ); + if ( hasMaster ) { + masterBody = new idAFBody(); + masterBody->Restore( saveFile ); + } + + saveFile->ReadInt( num ); + assert( num == constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Restore( saveFile ); + } + + saveFile->ReadBool( changedAF ); + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadFloat( totalMass ); + saveFile->ReadFloat( forceTotalMass ); + + saveFile->ReadVec2( suspendVelocity ); + saveFile->ReadVec2( suspendAcceleration ); + saveFile->ReadFloat( noMoveTime ); + saveFile->ReadFloat( noMoveTranslation ); + saveFile->ReadFloat( noMoveRotation ); + saveFile->ReadFloat( minMoveTime ); + saveFile->ReadFloat( maxMoveTime ); + saveFile->ReadFloat( impulseThreshold ); + + saveFile->ReadFloat( timeScale ); + saveFile->ReadFloat( timeScaleRampStart ); + saveFile->ReadFloat( timeScaleRampEnd ); + + saveFile->ReadFloat( jointFrictionScale ); + saveFile->ReadFloat( jointFrictionDent ); + saveFile->ReadFloat( jointFrictionDentStart ); + saveFile->ReadFloat( jointFrictionDentEnd ); + saveFile->ReadFloat( jointFrictionDentScale ); + + saveFile->ReadFloat( contactFrictionScale ); + saveFile->ReadFloat( contactFrictionDent ); + saveFile->ReadFloat( contactFrictionDentStart ); + saveFile->ReadFloat( contactFrictionDentEnd ); + saveFile->ReadFloat( contactFrictionDentScale ); + + saveFile->ReadBool( enableCollision ); + saveFile->ReadBool( selfCollision ); + saveFile->ReadBool( comeToRest ); + saveFile->ReadBool( linearTime ); + saveFile->ReadBool( noImpact ); + saveFile->ReadBool( worldConstraintsLocked ); + saveFile->ReadBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->ReadBool( fastEval ); +// RAVEN END + + idPhysics_AF_RestorePState( saveFile, current ); + idPhysics_AF_RestorePState( saveFile, saved ); + + changedAF = true; + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::IsClosedLoop +================ +*/ +bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const { + const idAFBody *b1, *b2; + + for ( b1 = body1; b1->parent; b1 = b1->parent ) { + } + for ( b2 = body2; b2->parent; b2 = b2->parent ) { + } + return ( b1 == b2 ); +} + +/* +================ +idPhysics_AF::BuildTrees +================ +*/ +void idPhysics_AF::BuildTrees( void ) { + int i; + float scale; + idAFBody *b; + idAFConstraint *c; + idAFTree *tree; + + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + trees.DeleteContents( true ); + + totalMass = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->parent = NULL; + b->primaryConstraint = NULL; + b->constraints.SetNum( 0, false ); + b->children.Clear(); + b->tree = NULL; + totalMass += b->mass; + } + + if ( forceTotalMass > 0.0f ) { + scale = forceTotalMass / totalMass; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->mass *= scale; + b->invMass = 1.0f / b->mass; + b->inertiaTensor *= scale; + b->inverseInertiaTensor = b->inertiaTensor.Inverse(); + } + totalMass = forceTotalMass; + } + + if ( af_useLinearTime.GetBool() ) { + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + // only bilateral constraints between two non-world bodies that do not + // create loops can be used as primary constraints + if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) { + c->body1->primaryConstraint = c; + c->body1->parent = c->body2; + c->body2->children.Append( c->body1 ); + c->fl.isPrimary = true; + c->firstIndex = 0; + primaryConstraints.Append( c ); + } else { + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } + + // create trees for all parent bodies + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->parent ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + } + + // add each child body to the appropriate tree + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->parent ) { + for ( b = bodies[i]->parent; !b->tree; b = b->parent ) { + } + b->tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = b->tree; + } + } + + if ( trees.Num() > 1 ) { + gameLocal.Warning( "Articulated figure has multiple seperate tree structures for entity '%s' type '%s'.", + self->name.c_str(), self->GetType()->classname ); + } + + // sort bodies in each tree to make sure parents come first + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SortBodies(); + } + + } else { + + // create a tree for each body + for ( i = 0; i < bodies.Num(); i++ ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } +} + +/* +================ +idPhysics_AF::AddBody + + bodies get an id in the order they are added starting at zero + as such the first body added will get id zero +================ +*/ +int idPhysics_AF::AddBody( idAFBody *body ) { + int id = 0; + + if ( !body->clipModel ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() ); + } + + if ( bodies.Find( body ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() ); + } + + if ( GetBody( body->name ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() ); + } + + id = bodies.Num(); + body->clipModel->SetId( id ); + if ( body->linearFriction < 0.0f ) { + body->linearFriction = linearFriction; + body->angularFriction = angularFriction; + body->contactFriction = contactFriction; + } + if ( body->bouncyness < 0.0f ) { + body->bouncyness = bouncyness; + } + if ( !body->fl.clipMaskSet ) { + body->clipMask = clipMask; + } + + bodies.Append( body ); + + changedAF = true; + + return id; +} + +/* +================ +idPhysics_AF::AddConstraint +================ +*/ +void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) { + + if ( constraints.Find( constraint ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() ); + } + if ( GetConstraint( constraint->name ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() ); + } + if ( !constraint->body1 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() ); + } + if ( !bodies.Find( constraint->body1 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body1 == constraint->body2 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() ); + } + + constraints.Append( constraint ); + constraint->physics = this; + + changedAF = true; +} + +/* +================ +idPhysics_AF::AddFrameConstraint +================ +*/ +void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) { + frameConstraints.Append( constraint ); + constraint->physics = this; +} + +/* +================ +idPhysics_AF::ForceBodyId +================ +*/ +void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 ) { + gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + if ( id != newId ) { + idAFBody *b = bodies[newId]; + bodies[newId] = bodies[id]; + bodies[id] = b; + changedAF = true; + } +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( idAFBody *body ) const { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 && body ) { + gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return i; + } + } + gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName ); + return 0; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const { + int id; + + id = constraints.FindIndex( constraint ); + if ( id == -1 && constraint ) { + gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return i; + } + } + gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName ); + return 0; +} + +/* +================ +idPhysics_AF::GetNumBodies +================ +*/ +int idPhysics_AF::GetNumBodies( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::GetNumConstraints +================ +*/ +int idPhysics_AF::GetNumConstraints( void ) const { + return constraints.Num(); +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return bodies[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + gameLocal.Error( "GetBody: no body with id %d exists\n", id ); + return NULL; + } + return bodies[id]; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return constraints[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const { + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id ); + return NULL; + } + return constraints[id]; +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const char *bodyName ) { + int i; + + // find the body with the given name + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + break; + } + } + + if ( i >= bodies.Num() ) { + gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + bodyName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteBody( i ); +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const int id ) { + int j; + + if ( id < 0 || id > bodies.Num() ) { + gameLocal.Error( "DeleteBody: no body with id %d.", id ); + return; + } + + // remove any constraints attached to this body + for ( j = 0; j < constraints.Num(); j++ ) { + if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) { + delete constraints[j]; + constraints.RemoveIndex( j ); + j--; + } + } + + // remove the body + delete bodies[id]; + bodies.RemoveIndex( id ); + + // set new body ids + for ( j = 0; j < bodies.Num(); j++ ) { + bodies[j]->clipModel->SetId( j ); + } + + changedAF = true; +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const char *constraintName ) { + int i; + + // find the constraint with the given name + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !constraints[i]->name.Icmp( constraintName ) ) { + break; + } + } + + if ( i >= constraints.Num() ) { + gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + constraintName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteConstraint( i ); +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const int id ) { + + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id ); + return; + } + + // remove the constraint + delete constraints[id]; + constraints.RemoveIndex( id ); + + changedAF = true; +} + +/* +================ +idPhysics_AF::GetBodyContactConstraints +================ +*/ +int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const { + int i, numContacts; + idAFBody *body; + idAFConstraint_Contact *contact; + + if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) { + return 0; + } + + numContacts = 0; + body = bodies[id]; + for ( i = 0; i < contactConstraints.Num(); i++ ) { + contact = contactConstraints[i]; + if ( contact->body1 == body || contact->body2 == body ) { + contacts[numContacts++] = contact; + if ( numContacts >= maxContacts ) { + return numContacts; + } + } + } + return numContacts; +} + +/* +================ +idPhysics_AF::SetDefaultFriction +================ +*/ +void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_AF::GetImpactInfo +================ +*/ +void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + if ( id < 0 || id >= bodies.Num() ) { + memset( info, 0, sizeof( *info ) ); + return; + } + info->invMass = 1.0f / bodies[id]->mass; + info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + info->position = point - bodies[id]->current->worldOrigin; + info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position ); +} + +/* +================ +idPhysics_AF::ApplyImpulse +================ +*/ +void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) { + return; + } + idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse; + bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_AF::AddForce +================ +*/ +void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->externalForce.SubVec3( 0 ) += force; + bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_AF::IsAtRest +================ +*/ +bool idPhysics_AF::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_AF::GetRestStartTime +================ +*/ +int idPhysics_AF::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_AF::IsPushable +================ +*/ +bool idPhysics_AF::IsPushable( void ) const { + return ( !noImpact && ( masterBody == NULL || forcePushable ) ); +} + +/* +================ +idPhysics_AF::SaveState +================ +*/ +void idPhysics_AF::SaveState( void ) { + int i; + + saved = current; + + for ( i = 0; i < bodies.Num(); i++ ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::RestoreState +================ +*/ +void idPhysics_AF::RestoreState( void ) { + int i; + + current = saved; + + for ( i = 0; i < bodies.Num(); i++ ) { + *(bodies[i]->current) = bodies[i]->saved; + } + + EvaluateContacts(); +} + +/* +================ +idPhysics_AF::SetOrigin +================ +*/ +void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) { + if ( masterBody ) { + Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin ); + } else { + Translate( newOrigin - bodies[0]->current->worldOrigin ); + } +} + +/* +================ +idPhysics_AF::SetAxis +================ +*/ +void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) { + idMat3 axis; + idRotation rotation; + + if ( masterBody ) { + axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis ); + } else { + axis = bodies[0]->current->worldAxis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( bodies[0]->current->worldOrigin ); + + Rotate( rotation ); +} + +/* +================ +idPhysics_AF::Translate +================ +*/ +void idPhysics_AF::Translate( const idVec3 &translation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // translate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Translate( translation ); + } + } + + // translate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + body->current->worldOrigin += translation; + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::Rotate +================ +*/ +void idPhysics_AF::Rotate( const idRotation &rotation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // rotate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Rotate( rotation ); + } + } + + // rotate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + body->current->worldOrigin *= rotation; + body->current->worldAxis *= rotation.ToMat3(); + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::GetOrigin +================ +*/ +const idVec3 &idPhysics_AF::GetOrigin( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->worldOrigin; + } +} + +/* +================ +idPhysics_AF::GetAxis +================ +*/ +const idMat3 &idPhysics_AF::GetAxis( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return mat3_identity; + } + else { + return bodies[id]->current->worldAxis; + } +} + +/* +================ +idPhysics_AF::SetLinearVelocity +================ +*/ +void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::SetAngularVelocity +================ +*/ +void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 0 ); + } +} + +/* +================ +idPhysics_AF::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 1 ); + } +} + +/* +================ +idPhysics_AF::ClipTranslation +================ +*/ +void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { + gameLocal.TranslationModel( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation; + results.endAxis = bodies[0]->current->worldAxis; +} + +/* +================ +idPhysics_AF::ClipRotation +================ +*/ +void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + idRotation partialRotation; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + partialRotation = rotation * results.fraction; + results.endpos = bodies[0]->current->worldOrigin * partialRotation; + results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3(); +} + +/* +================ +idPhysics_AF::ClipContents +================ +*/ +int idPhysics_AF::ClipContents( const idClipModel *model ) const { + int i, contents; + idAFBody *body; + + contents = 0; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + contents |= gameLocal.Contents( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, NULL ); +// RAVEN END + } + } + } + + return contents; +} + +/* +================ +idPhysics_AF::DisableClip +================ +*/ +void idPhysics_AF::DisableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } +} + +/* +================ +idPhysics_AF::EnableClip +================ +*/ +void idPhysics_AF::EnableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Enable(); + } +} + +/* +================ +idPhysics_AF::UnlinkClip +================ +*/ +void idPhysics_AF::UnlinkClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Unlink(); + } +} + +/* +================ +idPhysics_AF::LinkClip +================ +*/ +void idPhysics_AF::LinkClip( void ) { + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::SetPushed +================ +*/ +void idPhysics_AF::SetPushed( int deltaTime ) { + idAFBody *body; + idRotation rotation; + + if ( bodies.Num() ) { + body = bodies[0]; + rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); + } +} + +/* +================ +idPhysics_AF::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_AF::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_AF::SetMaster + + the binding is orientated based on the constraints being used +================ +*/ +void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + idRotation rotation; + + if ( master ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( !masterBody ) { + masterBody = new idAFBody(); + // translate and rotate all the constraints with body2 == NULL from world space to master space + rotation = masterAxis.Transpose().ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Translate( -masterOrigin ); + constraints[i]->Rotate( rotation ); + } + } + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + else { + if ( masterBody ) { + // translate and rotate all the constraints with body2 == NULL from master space to world space + rotation = masterBody->current->worldAxis.ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Rotate( rotation ); + constraints[i]->Translate( masterBody->current->worldOrigin ); + } + } + delete masterBody; + masterBody = NULL; + Activate(); + } + } +} + + +const float AF_VELOCITY_MAX = 16000; +const int AF_VELOCITY_TOTAL_BITS = 16; +const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1; +const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS; +const float AF_FORCE_MAX = 1e20f; +const int AF_FORCE_TOTAL_BITS = 16; +const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1; +const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_AF::WriteToSnapshot +================ +*/ +void idPhysics_AF::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat; + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.noMoveTime ); + msg.WriteFloat( current.activateTime ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + msg.WriteByte( bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + quat = state->worldAxis.ToCQuat(); + + msg.WriteFloat( state->worldOrigin[0] ); + msg.WriteFloat( state->worldOrigin[1] ); + msg.WriteFloat( state->worldOrigin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + } +} + +/* +================ +idPhysics_AF::ReadFromSnapshot +================ +*/ +void idPhysics_AF::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat; + + // TODO: Check that this conditional write to delta message is OK + current.atRest = msg.ReadLong(); + current.noMoveTime = msg.ReadFloat(); + current.activateTime = msg.ReadFloat(); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + num = msg.ReadByte(); + assert( num == bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + + state->worldOrigin[0] = msg.ReadFloat(); + state->worldOrigin[1] = msg.ReadFloat(); + state->worldOrigin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + state->worldAxis = quat.ToMat3(); + } + + UpdateClipModels(); +} diff --git a/source/mpgame/physics/Physics_AF.h b/source/mpgame/physics/Physics_AF.h new file mode 100644 index 0000000..2998972 --- /dev/null +++ b/source/mpgame/physics/Physics_AF.h @@ -0,0 +1,988 @@ + +#ifndef __PHYSICS_AF_H__ +#define __PHYSICS_AF_H__ + +/* +=================================================================================== + + Articulated Figure physics + + Employs a constraint force based dynamic simulation using a lagrangian + multiplier method to solve for the constraint forces. + +=================================================================================== +*/ + +class idAFConstraint; +class idAFConstraint_Fixed; +class idAFConstraint_BallAndSocketJoint; +class idAFConstraint_BallAndSocketJointFriction; +class idAFConstraint_UniversalJoint; +class idAFConstraint_UniversalJointFriction; +class idAFConstraint_CylindricalJoint; +class idAFConstraint_Hinge; +class idAFConstraint_HingeFriction; +class idAFConstraint_HingeSteering; +class idAFConstraint_Slider; +class idAFConstraint_Line; +class idAFConstraint_Plane; +class idAFConstraint_Spring; +class idAFConstraint_Contact; +class idAFConstraint_ContactFriction; +class idAFConstraint_ConeLimit; +class idAFConstraint_PyramidLimit; +class idAFBody; +class idAFTree; +class idPhysics_AF; + +typedef enum { + CONSTRAINT_INVALID, + CONSTRAINT_FIXED, + CONSTRAINT_BALLANDSOCKETJOINT, + CONSTRAINT_UNIVERSALJOINT, + CONSTRAINT_HINGE, + CONSTRAINT_HINGESTEERING, + CONSTRAINT_SLIDER, + CONSTRAINT_CYLINDRICALJOINT, + CONSTRAINT_LINE, + CONSTRAINT_PLANE, + CONSTRAINT_SPRING, + CONSTRAINT_CONTACT, + CONSTRAINT_FRICTION, + CONSTRAINT_CONELIMIT, + CONSTRAINT_PYRAMIDLIMIT +} constraintType_t; + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +// base class for all constraints +class idAFConstraint { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFConstraint( void ); + virtual ~idAFConstraint( void ); + constraintType_t GetType( void ) const { return type; } + const idStr & GetName( void ) const { return name; } + idAFBody * GetBody1( void ) const { return body1; } + idAFBody * GetBody2( void ) const { return body2; } + void SetPhysics( idPhysics_AF *p ) { physics = p; } + const idVecX & GetMultiplier( void ); + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + constraintType_t type; // constraint type + idStr name; // name of constraint + idAFBody * body1; // first constrained body + idAFBody * body2; // second constrained body, NULL for world + idPhysics_AF * physics; // for adding additional constraints like limits + + // simulation variables set by Evaluate + idMatX J1, J2; // matrix with left hand side of constraint equations + idVecX c1, c2; // right hand side of constraint equations + idVecX lo, hi, e; // low and high bounds and lcp epsilon + idAFConstraint * boxConstraint; // constraint the boxIndex refers to + int boxIndex[6]; // indexes for special box constrained variables + + // simulation variables used during calculations + idMatX invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX lm; // lagrange multipliers + int firstIndex; // index of the first constraint row in the lcp matrix + + struct constraintFlags_s { + bool allowPrimary : 1; // true if the constraint can be used as a primary constraint + bool frameConstraint : 1; // true if this constraint is added to the frame constraints + bool noCollision : 1; // true if body1 and body2 never collide with each other + bool isPrimary : 1; // true if this is a primary constraint + bool isZero : 1; // true if 's' is zero during calculations + } fl; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitSize( int size ); +}; + +// fixed or rigid joint which allows zero degrees of freedom +// constrains body1 to have a fixed position and orientation relative to body2 +class idAFConstraint_Fixed : public idAFConstraint { + +public: + idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetRelativeOrigin( const idVec3 &origin ) { this->offset = origin; } + void SetRelativeAxis( const idMat3 &axis ) { this->relAxis = axis; } + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 offset; // offset of body1 relative to body2 in body2 space + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitOffset( void ); +}; + +// ball and socket or spherical joint which allows 3 degrees of freedom +// constrains body1 relative to body2 with a ball and socket joint +class idAFConstraint_BallAndSocketJoint : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_BallAndSocketJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_BallAndSocketJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// ball and socket joint friction +class idAFConstraint_BallAndSocketJointFriction : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJointFriction( void ); + void Setup( idAFConstraint_BallAndSocketJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_BallAndSocketJoint *joint; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal, Cardan or Hooke joint which allows 2 degrees of freedom +// like a ball and socket joint but also constrains the rotation about the cardan shafts +class idAFConstraint_UniversalJoint : public idAFConstraint { + +public: + idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_UniversalJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ); + void GetShafts( idVec3 &cardanShaft1, idVec3 &cardanShaft2 ) { cardanShaft1 = shaft1; cardanShaft2 = shaft2; } + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 shaft1; // body1 cardan shaft in body1 space + idVec3 shaft2; // body2 cardan shaft in body2 space + idVec3 axis1; // cardan axis in body1 space + idVec3 axis2; // cardan axis in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_UniversalJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal joint friction +class idAFConstraint_UniversalJointFriction : public idAFConstraint { + +public: + idAFConstraint_UniversalJointFriction( void ); + void Setup( idAFConstraint_UniversalJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_UniversalJoint *joint; // universal joint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// cylindrical joint which allows 2 degrees of freedom +// constrains body1 to lie on a line relative to body2 and allows only translation along and rotation about the line +class idAFConstraint_CylindricalJoint : public idAFConstraint { + +public: + idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge, revolute or pin joint which allows 1 degree of freedom +// constrains all motion of body1 relative to body2 except the rotation about the hinge axis +class idAFConstraint_Hinge : public idAFConstraint { + +public: + idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_Hinge( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetAxis( const idVec3 &axis ); + void GetAxis( idVec3 &a1, idVec3 &a2 ) const { a1 = axis1; a2 = axis2; } + idVec3 GetAxis( void ) const; + void SetNoLimit( void ); + void SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + float GetAngle( void ) const; + void SetSteerAngle( const float degrees ); + void SetSteerSpeed( const float speed ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 axis1; // axis in body1 space + idVec3 axis2; // axis in body2 space + idMat3 initialAxis; // initial axis of body1 relative to body2 + float friction; // hinge friction + idAFConstraint_ConeLimit *coneLimit; // cone limit + idAFConstraint_HingeSteering *steering; // steering + idAFConstraint_HingeFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge joint friction +class idAFConstraint_HingeFriction : public idAFConstraint { + +public: + idAFConstraint_HingeFriction( void ); + void Setup( idAFConstraint_Hinge *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains two bodies attached to each other with a hinge to get a specified relative orientation +class idAFConstraint_HingeSteering : public idAFConstraint { + +public: + idAFConstraint_HingeSteering( void ); + void Setup( idAFConstraint_Hinge *cc ); + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void SetSteerSpeed( const float speed ) { steerSpeed = speed; } + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + float steerAngle; // desired steer angle in degrees + float steerSpeed; // steer speed + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// slider, prismatic or translational constraint which allows 1 degree of freedom +// constrains body1 to lie on a line relative to body2, the orientation is also fixed relative to body2 +class idAFConstraint_Slider : public idAFConstraint { + +public: + idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAxis( const idVec3 &ax ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 axis; // axis along which body1 slides in body2 space + idVec3 offset; // offset of body1 relative to body2 + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// line constraint which allows 4 degrees of freedom +// constrains body1 to lie on a line relative to body2, does not constrain the orientation. +class idAFConstraint_Line : public idAFConstraint { + +public: + idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// plane constraint which allows 5 degrees of freedom +// constrains body1 to lie in a plane relative to body2, does not constrain the orientation. +class idAFConstraint_Plane : public idAFConstraint { + +public: + idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetPlane( const idVec3 &normal, const idVec3 &anchor ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 planeNormal; // plane normal in body2 space + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// spring constraint which allows 6 or 5 degrees of freedom based on the spring limits +// constrains body1 relative to body2 with a spring +class idAFConstraint_Spring : public idAFConstraint { + +public: + idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ); + void SetSpring( const float stretch, const float compress, const float damping, const float restLength ); + void SetLimit( const float minLength, const float maxLength ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float kstretch; // spring constant when stretched + float kcompress; // spring constant when compressed + float damping; // spring damping + float restLength; // rest length of spring + float minLength; // minimum spring length + float maxLength; // maximum spring length + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains body1 to either be in contact with or move away from body2 +class idAFConstraint_Contact : public idAFConstraint { + +public: + idAFConstraint_Contact( void ); + ~idAFConstraint_Contact( void ); + void Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ); + const contactInfo_t & GetContact( void ) const { return contact; } + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + +protected: + contactInfo_t contact; // contact information + idAFConstraint_ContactFriction *fc; // contact friction + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// contact friction +class idAFConstraint_ContactFriction : public idAFConstraint { + +public: + idAFConstraint_ContactFriction( void ); + void Setup( idAFConstraint_Contact *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Contact *cc; // contact constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a cone relative to body2 +class idAFConstraint_ConeLimit : public idAFConstraint { + +public: + idAFConstraint_ConeLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, + const float coneAngle, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &coneAnchor ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 coneAnchor; // top of the cone in body2 space + idVec3 coneAxis; // cone axis in body2 space + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle; // cos( coneAngle / 2 ) + float sinHalfAngle; // sin( coneAngle / 4 ) + float cosHalfAngle; // cos( coneAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a pyramid relative to body2 +class idAFConstraint_PyramidLimit : public idAFConstraint { + +public: + idAFConstraint_PyramidLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &pyramidAxis ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 pyramidAnchor; // top of the pyramid in body2 space + idMat3 pyramidBasis; // pyramid basis in body2 space with base[2] being the pyramid axis + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle[2]; // cos( pyramidAngle / 2 ) + float sinHalfAngle[2]; // sin( pyramidAngle / 4 ) + float cosHalfAngle[2]; // cos( pyramidAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +typedef struct AFBodyPState_s { + idVec3 worldOrigin; // position in world space + idMat3 worldAxis; // axis at worldOrigin + idVec6 spatialVelocity; // linear and rotational velocity of body + idVec6 externalForce; // external force and torque applied to body +} AFBodyPState_t; + + +class idAFBody { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFBody( void ); + idAFBody( const idStr &name, idClipModel *clipModel, float density ); + ~idAFBody( void ); + + void Init( void ); + const idStr & GetName( void ) const { return name; } + const idVec3 & GetWorldOrigin( void ) const { return current->worldOrigin; } + const idMat3 & GetWorldAxis( void ) const { return current->worldAxis; } + const idVec3 & GetLinearVelocity( void ) const { return current->spatialVelocity.SubVec3(0); } + const idVec3 & GetAngularVelocity( void ) const { return current->spatialVelocity.SubVec3(1); } + idVec3 GetPointVelocity( const idVec3 &point ) const; + const idVec3 & GetCenterOfMass( void ) const { return centerOfMass; } + void SetClipModel( idClipModel *clipModel ); + idClipModel * GetClipModel( void ) const { return clipModel; } + void SetClipMask( const int mask ) { clipMask = mask; fl.clipMaskSet = true; } + int GetClipMask( void ) const { return clipMask; } + void SetSelfCollision( const bool enable ) { fl.selfCollision = enable; } + void SetWorldOrigin( const idVec3 &origin ) { current->worldOrigin = origin; } + void SetWorldAxis( const idMat3 &axis ) { current->worldAxis = axis; } + void SetLinearVelocity( const idVec3 &linear ) const { current->spatialVelocity.SubVec3(0) = linear; } + void SetAngularVelocity( const idVec3 &angular ) const { current->spatialVelocity.SubVec3(1) = angular; } + void SetFriction( float linear, float angular, float contact ); + float GetContactFriction( void ) const { return contactFriction; } + void SetBouncyness( float bounce ); + float GetBouncyness( void ) const { return bouncyness; } + void SetDensity( float density, const idMat3 &inertiaScale = mat3_identity ); + float GetInverseMass( void ) const { return invMass; } + idMat3 GetInverseWorldInertia( void ) const { return current->worldAxis.Transpose() * inverseInertiaTensor * current->worldAxis; } + + void SetFrictionDirection( const idVec3 &dir ); + bool GetFrictionDirection( idVec3 &dir ) const; + + void SetContactMotorDirection( const idVec3 &dir ); + bool GetContactMotorDirection( idVec3 &dir ) const; + void SetContactMotorVelocity( float vel ) { contactMotorVelocity = vel; } + float GetContactMotorVelocity( void ) const { return contactMotorVelocity; } + void SetContactMotorForce( float force ) { contactMotorForce = force; } + float GetContactMotorForce( void ) const { return contactMotorForce; } + + void AddForce( const idVec3 &point, const idVec3 &force ); + void InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const; + idVec6 & GetResponseForce( int index ) { return reinterpret_cast(response[ index * 8 ]); } + + void Save( idSaveGame *saveFile ); + void Restore( idRestoreGame *saveFile ); + +private: + // properties + idStr name; // name of body + idAFBody * parent; // parent of this body + idList children; // children of this body + idClipModel * clipModel; // model used for collision detection + idAFConstraint * primaryConstraint; // primary constraint (this->constraint->body1 = this) + idListconstraints; // all constraints attached to this body + idAFTree * tree; // tree structure this body is part of + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bounce + int clipMask; // contents this body collides with + idVec3 frictionDir; // specifies a single direction of friction in body space + idVec3 contactMotorDir; // contact motor direction + float contactMotorVelocity; // contact motor velocity + float contactMotorForce; // maximum force applied to reach the motor velocity + + // derived properties + float mass; // mass of body + float invMass; // inverse mass + idVec3 centerOfMass; // center of mass of body + idMat3 inertiaTensor; // inertia tensor + idMat3 inverseInertiaTensor; // inverse inertia tensor + + // physics state + AFBodyPState_t state[2]; + AFBodyPState_t * current; // current physics state + AFBodyPState_t * next; // next physics state + AFBodyPState_t saved; // saved physics state + idVec3 atRestOrigin; // origin at rest + idMat3 atRestAxis; // axis at rest + + // simulation variables used during calculations + idMatX inverseWorldSpatialInertia; // inverse spatial inertia in world space + idMatX I, invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX totalForce; // total force acting on body + idVecX auxForce; // force from auxiliary constraints + idVecX acceleration; // acceleration + float * response; // forces on body in response to auxiliary constraint forces + int * responseIndex; // index to response forces + int numResponses; // number of response forces + int maxAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body + int maxSubTreeAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body or one of it's children + + struct bodyFlags_s { + bool clipMaskSet : 1; // true if this body has a clip mask set + bool selfCollision : 1; // true if this body can collide with other bodies of this AF + bool spatialInertiaSparse: 1; // true if the spatial inertia matrix is sparse + bool useFrictionDir : 1; // true if a single friction direction should be used + bool useContactMotorDir : 1; // true if a contact motor should be used + bool isZero : 1; // true if 's' is zero during calculations + } fl; +}; + + +//=============================================================== +// +// idAFTree +// +//=============================================================== + +class idAFTree { + friend class idPhysics_AF; + +public: + void Factor( void ) const; + void Solve( int auxiliaryIndex = 0 ) const; + void Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const; + void CalculateForces( float timeStep ) const; + void SetMaxSubTreeAuxiliaryIndex( void ); + void SortBodies( void ); + void SortBodies_r( idList&sortedList, idAFBody *body ); + void DebugDraw( const idVec4 &color ) const; + +private: + idList sortedBodies; +}; + + +//=============================================================== +// +// idPhysics_AF +// +//=============================================================== + +typedef struct AFPState_s { + int atRest; // >= 0 if articulated figure is at rest + float noMoveTime; // time the articulated figure is hardly moving + float activateTime; // time since last activation + float lastTimeStep; // last time step + idVec6 pushVelocity; // velocity with which the af is pushed +} AFPState_t; + +typedef struct AFCollision_s { + trace_t trace; + idAFBody * body; +} AFCollision_t; + +class idPhysics_AF : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_AF ); + + idPhysics_AF( void ); + ~idPhysics_AF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + int AddBody( idAFBody *body ); // returns body id + void AddConstraint( idAFConstraint *constraint ); + void AddFrameConstraint( idAFConstraint *constraint ); + // force a body to have a certain id + void ForceBodyId( idAFBody *body, int newId ); + // get body or constraint id + int GetBodyId( idAFBody *body ) const; + int GetBodyId( const char *bodyName ) const; + int GetConstraintId( idAFConstraint *constraint ) const; + int GetConstraintId( const char *constraintName ) const; + // number of bodies and constraints + int GetNumBodies( void ) const; + int GetNumConstraints( void ) const; + // retrieve body or constraint + idAFBody * GetBody( const char *bodyName ) const; + idAFBody * GetBody( const int id ) const; + idAFBody * GetMasterBody( void ) const { return masterBody; } + idAFConstraint * GetConstraint( const char *constraintName ) const; + idAFConstraint * GetConstraint( const int id ) const; + // delete body or constraint + void DeleteBody( const char *bodyName ); + void DeleteBody( const int id ); + void DeleteConstraint( const char *constraintName ); + void DeleteConstraint( const int id ); + + // get all the contact constraints acting on the body + int GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const; + // set the default friction for bodies + void SetDefaultFriction( float linear, float angular, float contact ); + // suspend settings + void SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ); + // set the time and tolerances used to determine if the simulation can be suspended when the figure hardly moves for a while + void SetSuspendTolerance( const float noMoveTime, const float translationTolerance, const float rotationTolerance ); + // set minimum and maximum simulation time in seconds + void SetSuspendTime( const float minTime, const float maxTime ); + // set the time scale value + void SetTimeScale( const float ts ) { timeScale = ts; } + // set time scale ramp + void SetTimeScaleRamp( const float start, const float end ); + // set the joint friction scale + void SetJointFrictionScale( const float scale ) { jointFrictionScale = scale; } + // set joint friction dent + void SetJointFrictionDent( const float dent, const float start, const float end ); + // get the current joint friction scale + float GetJointFrictionScale( void ) const; + // set the contact friction scale + void SetContactFrictionScale( const float scale ) { contactFrictionScale = scale; } + // set contact friction dent + void SetContactFrictionDent( const float dent, const float start, const float end ); + // get the current contact friction scale + float GetContactFrictionScale( void ) const; + // enable or disable collision detection + void SetCollision( const bool enable ) { enableCollision = enable; } + // enable or disable self collision + void SetSelfCollision( const bool enable ) { selfCollision = enable; } + // enable or disable coming to a dead stop + void SetComeToRest( bool enable ) { comeToRest = enable; } + // call when structure of articulated figure changes + void SetChanged( void ) { changedAF = true; } +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + // enable or disable fast evaluation + void SetFastEval( const bool enable ) { fastEval = enable; } +// RAVEN END + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + // lock of unlock the world constraints + void LockWorldConstraints( const bool lock ) { worldConstraintsLocked = lock; } + // set force pushable + void SetForcePushable( const bool enable ) { forcePushable = enable; } + // update the clip model positions + void UpdateClipModels( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + //MCG: added SetImpulseThreshold + void SetImpulseThreshold( float newIT ) { impulseThreshold = newIT; }; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + void Activate( void ); + void PutToRest( void ); + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // articulated figure + idList trees; // tree structures + idList bodies; // all bodies + idListconstraints; // all frame independent constraints + idListprimaryConstraints; // list with primary constraints + idListauxiliaryConstraints; // list with auxiliary constraints + idListframeConstraints; // constraints that only live one frame + idListcontactConstraints; // contact constraints + idList contactBodies; // body id for each contact + idList collisions; // collisions + bool changedAF; // true when the articulated figure just changed + + // properties + float linearFriction; // default translational friction + float angularFriction; // default rotational friction + float contactFriction; // default friction with contact surfaces + float bouncyness; // default bouncyness + float totalMass; // total mass of articulated figure + float forceTotalMass; // force this total mass + + idVec2 suspendVelocity; // simulation may not be suspended if a body has more velocity + idVec2 suspendAcceleration; // simulation may not be suspended if a body has more acceleration + float noMoveTime; // suspend simulation if hardly any movement for this many seconds + float noMoveTranslation; // maximum translation considered no movement + float noMoveRotation; // maximum rotation considered no movement + float minMoveTime; // if > 0 the simulation is never suspended before running this many seconds + float maxMoveTime; // if > 0 the simulation is always suspeded after running this many seconds + float impulseThreshold; // threshold below which impulses are ignored to avoid continuous activation + + float timeScale; // the time is scaled with this value for slow motion effects + float timeScaleRampStart; // start of time scale change + float timeScaleRampEnd; // end of time scale change + + float jointFrictionScale; // joint friction scale + float jointFrictionDent; // joint friction dives from 1 to this value and goes up again + float jointFrictionDentStart; // start time of joint friction dent + float jointFrictionDentEnd; // end time of joint friction dent + float jointFrictionDentScale; // dent scale + + float contactFrictionScale; // contact friction scale + float contactFrictionDent; // contact friction dives from 1 to this value and goes up again + float contactFrictionDentStart; // start time of contact friction dent + float contactFrictionDentEnd; // end time of contact friction dent + float contactFrictionDentScale; // dent scale + + bool enableCollision; // if true collision detection is enabled + bool selfCollision; // if true the self collision is allowed + bool comeToRest; // if true the figure can come to rest + bool linearTime; // if true use the linear time algorithm + bool noImpact; // if true do not activate when another object collides + bool worldConstraintsLocked; // if true world constraints cannot be moved + bool forcePushable; // if true can be pushed even when bound to a master +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; // if true the fast eval is on +// RAVEN END + + // physics state + AFPState_t current; + AFPState_t saved; + + idAFBody * masterBody; // master body + idLCP * lcp; // linear complementarity problem solver + +private: + void BuildTrees( void ); + bool IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const; + void PrimaryFactor( void ); + void EvaluateBodies( float timeStep ); + void EvaluateConstraints( float timeStep ); + void AddFrameConstraints( void ); + void RemoveFrameConstraints( void ); + void ApplyFriction( float timeStep, float endTimeMSec ); + void PrimaryForces( float timeStep ); + void AuxiliaryForces( float timeStep ); + void VerifyContactConstraints( void ); + void SetupContactConstraints( void ); + void ApplyContactForces( void ); + void Evolve( float timeStep ); + idEntity * SetupCollisionForBody( idAFBody *body ) const; + bool CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ); + bool ApplyCollisions( float timeStep ); + void CheckForCollisions( float timeStep ); + void ClearExternalForce( void ); + void AddGravity( void ); + void SwapStates( void ); + bool TestIfAtRest( float timeStep ); + void Rest( void ); + void AddPushVelocity( const idVec6 &pushVelocity ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_AF_H__ */ diff --git a/source/mpgame/physics/Physics_Actor.cpp b/source/mpgame/physics/Physics_Actor.cpp new file mode 100644 index 0000000..1fe3dde --- /dev/null +++ b/source/mpgame/physics/Physics_Actor.cpp @@ -0,0 +1,367 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Actor ) +END_CLASS + +/* +================ +idPhysics_Actor::idPhysics_Actor +================ +*/ +idPhysics_Actor::idPhysics_Actor( void ) { + clipModel = NULL; + SetClipModelAxis(); + mass = 100.0f; + invMass = 1.0f / mass; + masterEntity = NULL; + masterYaw = 0.0f; + masterDeltaYaw = 0.0f; + groundEntityPtr = NULL; +} + +/* +================ +idPhysics_Actor::~idPhysics_Actor +================ +*/ +idPhysics_Actor::~idPhysics_Actor( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +idPhysics_Actor::Save +================ +*/ +void idPhysics_Actor::Save( idSaveGame *savefile ) const { + + savefile->WriteClipModel( clipModel ); + savefile->WriteMat3( clipModelAxis ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( invMass ); + + savefile->WriteObject( masterEntity ); + savefile->WriteFloat( masterYaw ); + savefile->WriteFloat( masterDeltaYaw ); + + groundEntityPtr.Save( savefile ); +} + +/* +================ +idPhysics_Actor::Restore +================ +*/ +void idPhysics_Actor::Restore( idRestoreGame *savefile ) { + + savefile->ReadClipModel( clipModel ); + savefile->ReadMat3( clipModelAxis ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( invMass ); + + savefile->ReadObject( reinterpret_cast( masterEntity ) ); + savefile->ReadFloat( masterYaw ); + savefile->ReadFloat( masterDeltaYaw ); + + groundEntityPtr.Restore( savefile ); +} + +/* +================ +idPhysics_Actor::SetClipModelAxis +================ +*/ +void idPhysics_Actor::SetClipModelAxis( void ) { + // align clip model to gravity direction + if ( ( gravityNormal[2] == -1.0f ) || ( gravityNormal == vec3_zero ) ) { + clipModelAxis.Identity(); + } + else { + clipModelAxis[2] = -gravityNormal; + clipModelAxis[2].NormalVectors( clipModelAxis[0], clipModelAxis[1] ); + clipModelAxis[1] = -clipModelAxis[1]; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::GetGravityAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetGravityAxis( void ) const { + return clipModelAxis; +} + +/* +================ +idPhysics_Actor::GetMasterDeltaYaw +================ +*/ +float idPhysics_Actor::GetMasterDeltaYaw( void ) const { + return masterDeltaYaw; +} + +/* +================ +idPhysics_Actor::GetGroundEntity +================ +*/ +idEntity *idPhysics_Actor::GetGroundEntity( void ) const { + return groundEntityPtr.GetEntity(); +} + +/* +================ +idPhysics_Actor::SetClipModel +================ +*/ +void idPhysics_Actor::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + assert( self ); + assert( model ); // a clip model is required + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::GetClipModel +================ +*/ +idClipModel *idPhysics_Actor::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Actor::GetNumClipModels +================ +*/ +int idPhysics_Actor::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_Actor::SetMass +================ +*/ +void idPhysics_Actor::SetMass( float _mass, int id ) { + assert( _mass > 0.0f ); + mass = _mass; + invMass = 1.0f / _mass; +} + +/* +================ +idPhysics_Actor::GetMass +================ +*/ +float idPhysics_Actor::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_Actor::SetContents +================ +*/ +void idPhysics_Actor::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_Actor::GetContents +================ +*/ +int idPhysics_Actor::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_Actor::GetBounds +================ +*/ +const idBounds &idPhysics_Actor::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_Actor::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Actor::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_Actor::IsPushable +================ +*/ +bool idPhysics_Actor::IsPushable( void ) const { + return ( masterEntity == NULL ); +} + +/* +================ +idPhysics_Actor::GetOrigin +================ +*/ +const idVec3 &idPhysics_Actor::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +idPhysics_Player::GetAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetAxis( int id ) const { + return clipModel->GetAxis(); +} + +/* +================ +idPhysics_Actor::SetGravity +================ +*/ +void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { + if ( newGravity != gravityVector ) { + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + } +} + +/* +================ +idPhysics_Actor::ClipTranslation +================ +*/ +void idPhysics_Actor::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipRotation +================ +*/ +void idPhysics_Actor::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipContents +================ +*/ +int idPhysics_Actor::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip models + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::DisableClip +================ +*/ +void idPhysics_Actor::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_Actor::EnableClip +================ +*/ +void idPhysics_Actor::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_Actor::UnlinkClip +================ +*/ +void idPhysics_Actor::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_Actor::LinkClip +================ +*/ +void idPhysics_Actor::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::EvaluateContacts +================ +*/ +bool idPhysics_Actor::EvaluateContacts( void ) { + + // get all the ground contacts + ClearContacts(); + AddGroundContacts( clipModel ); + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} diff --git a/source/mpgame/physics/Physics_Actor.h b/source/mpgame/physics/Physics_Actor.h new file mode 100644 index 0000000..748fb06 --- /dev/null +++ b/source/mpgame/physics/Physics_Actor.h @@ -0,0 +1,89 @@ + +#ifndef __PHYSICS_ACTOR_H__ +#define __PHYSICS_ACTOR_H__ + +/* +=================================================================================== + + Actor physics base class + + An actor typically uses one collision model which is aligned with the gravity + direction. The collision model is usually a simple box with the origin at the + bottom center. + +=================================================================================== +*/ + +class idPhysics_Actor : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Actor ); + + idPhysics_Actor( void ); + ~idPhysics_Actor( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // get delta yaw of master + float GetMasterDeltaYaw( void ) const; + // returns the ground entity + idEntity * GetGroundEntity( void ) const; + // align the clip model with the gravity direction + void SetClipModelAxis( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool IsPushable( void ) const; + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); +// RAVEN BEGIN +// abahr: made virtual + virtual const idMat3& GetGravityAxis( void ) const; +// RAVEN END + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + +protected: + idClipModel * clipModel; // clip model used for collision detection + idMat3 clipModelAxis; // axis of clip model aligned with gravity direction + + // derived properties + float mass; + float invMass; + + // master + idEntity * masterEntity; + float masterYaw; + float masterDeltaYaw; + + // results of last evaluate + idEntityPtr groundEntityPtr; +}; + +#endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/source/mpgame/physics/Physics_Base.cpp b/source/mpgame/physics/Physics_Base.cpp new file mode 100644 index 0000000..1cacbf3 --- /dev/null +++ b/source/mpgame/physics/Physics_Base.cpp @@ -0,0 +1,872 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Base ) +END_CLASS + +/* +================ +idPhysics_Base::idPhysics_Base +================ +*/ +idPhysics_Base::idPhysics_Base( void ) { + self = NULL; + clipMask = 0; + SetGravity( gameLocal.GetGravity() ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::~idPhysics_Base +================ +*/ +idPhysics_Base::~idPhysics_Base( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::Save +================ +*/ +void idPhysics_Base::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + savefile->WriteInt( clipMask ); + savefile->WriteVec3( gravityVector ); + savefile->WriteVec3( gravityNormal ); + + savefile->WriteInt( contacts.Num() ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->WriteContactInfo( contacts[i] ); + } + + savefile->WriteInt( contactEntities.Num() ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Save( savefile ); + } +} + +/* +================ +idPhysics_Base::Restore +================ +*/ +void idPhysics_Base::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadInt( clipMask ); + savefile->ReadVec3( gravityVector ); + savefile->ReadVec3( gravityNormal ); + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Restore( savefile ); + } +} + +/* +================ +idPhysics_Base::SetSelf +================ +*/ +void idPhysics_Base::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Base::SetClipModel +================ +*/ +void idPhysics_Base::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_Base::GetClipModel +================ +*/ +idClipModel *idPhysics_Base::GetClipModel( int id ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetNumClipModels +================ +*/ +int idPhysics_Base::GetNumClipModels( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetMass +================ +*/ +void idPhysics_Base::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Base::GetMass +================ +*/ +float idPhysics_Base::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Base::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Base::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Base::SetContents +================ +*/ +void idPhysics_Base::SetContents( int contents, int id ) { +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +int idPhysics_Base::GetContents( int id ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +void idPhysics_Base::SetClipMask( int mask, int id ) { + clipMask = mask; +} + +/* +================ +idPhysics_Base::GetClipMask +================ +*/ +int idPhysics_Base::GetClipMask( int id ) const { + return clipMask; +} + +/* +================ +idPhysics_Base::GetBounds +================ +*/ +const idBounds &idPhysics_Base::GetBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Base::GetAbsBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::Evaluate +================ +*/ +bool idPhysics_Base::Evaluate( int timeStepMSec, int endTimeMSec ) { + return false; +} + +/* +================ +idPhysics_Base::UpdateTime +================ +*/ +void idPhysics_Base::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Base::GetTime +================ +*/ +int idPhysics_Base::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetImpactInfo +================ +*/ +void idPhysics_Base::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Base::ApplyImpulse +================ +*/ +void idPhysics_Base::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Base::AddForce +================ +*/ +void idPhysics_Base::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Base::Activate +================ +*/ +void idPhysics_Base::Activate( void ) { +} + +/* +================ +idPhysics_Base::PutToRest +================ +*/ +void idPhysics_Base::PutToRest( void ) { +} + +/* +================ +idPhysics_Base::IsAtRest +================ +*/ +bool idPhysics_Base::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Base::GetRestStartTime +================ +*/ +int idPhysics_Base::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::IsPushable +================ +*/ +bool idPhysics_Base::IsPushable( void ) const { + return true; +} + +// RAVEN BEGIN +// bdube: water interraction +bool idPhysics_Base::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Base::SaveState +================ +*/ +void idPhysics_Base::SaveState( void ) { +} + +/* +================ +idPhysics_Base::RestoreState +================ +*/ +void idPhysics_Base::RestoreState( void ) { +} + +/* +================ +idPhysics_Base::SetOrigin +================ +*/ +void idPhysics_Base::SetOrigin( const idVec3 &newOrigin, int id ) { +} + +/* +================ +idPhysics_Base::SetAxis +================ +*/ +void idPhysics_Base::SetAxis( const idMat3 &newAxis, int id ) { +} + +/* +================ +idPhysics_Base::Translate +================ +*/ +void idPhysics_Base::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Base::Rotate +================ +*/ +void idPhysics_Base::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Base::GetOrigin +================ +*/ +const idVec3 &idPhysics_Base::GetOrigin( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAxis +================ +*/ +const idMat3 &idPhysics_Base::GetAxis( int id ) const { + return mat3_identity; +} + +/* +================ +idPhysics_Base::SetLinearVelocity +================ +*/ +void idPhysics_Base::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Base::SetAngularVelocity +================ +*/ +void idPhysics_Base::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Base::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetGravity +================ +*/ +void idPhysics_Base::SetGravity( const idVec3 &newGravity ) { + gravityVector = newGravity; + gravityNormal = newGravity; + gravityNormal.Normalize(); +} + +/* +================ +idPhysics_Base::GetGravity +================ +*/ +const idVec3 &idPhysics_Base::GetGravity( void ) const { + return gravityVector; +} + +/* +================ +idPhysics_Base::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Base::GetGravityNormal( void ) const { + return gravityNormal; +} + +/* +================ +idPhysics_Base::ClipTranslation +================ +*/ +void idPhysics_Base::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipRotation +================ +*/ +void idPhysics_Base::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipContents +================ +*/ +int idPhysics_Base::ClipContents( const idClipModel *model ) const { + return 0; +} + +/* +================ +idPhysics_Base::DisableClip +================ +*/ +void idPhysics_Base::DisableClip( void ) { +} + +/* +================ +idPhysics_Base::EnableClip +================ +*/ +void idPhysics_Base::EnableClip( void ) { +} + +/* +================ +idPhysics_Base::UnlinkClip +================ +*/ +void idPhysics_Base::UnlinkClip( void ) { +} + +/* +================ +idPhysics_Base::LinkClip +================ +*/ +void idPhysics_Base::LinkClip( void ) { +} + +/* +================ +idPhysics_Base::EvaluateContacts +================ +*/ +bool idPhysics_Base::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Base::GetNumContacts +================ +*/ +int idPhysics_Base::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +idPhysics_Base::GetContact +================ +*/ +const contactInfo_t &idPhysics_Base::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +idPhysics_Base::ClearContacts +================ +*/ +void idPhysics_Base::ClearContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent ) { + ent->RemoveContactEntity( self ); + } + } + contacts.SetNum( 0, false ); +} + +/* +================ +idPhysics_Base::AddContactEntity +================ +*/ +void idPhysics_Base::AddContactEntity( idEntity *e ) { + int i; + idEntity *ent; + bool found = false; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent == NULL ) { + contactEntities.RemoveIndex( i-- ); + } + if ( ent == e ) { + found = true; + } + } + if ( !found ) { + contactEntities.Alloc() = e; + } +} + +/* +================ +idPhysics_Base::RemoveContactEntity +================ +*/ +void idPhysics_Base::RemoveContactEntity( idEntity *e ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( !ent ) { + contactEntities.RemoveIndex( i-- ); + continue; + } + if ( ent == e ) { + contactEntities.RemoveIndex( i-- ); + return; + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + normal += GetContact( ix ).normal; + } + + return normal.ToNormal(); +} + +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetGroundContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + if ( GetContact(ix).normal * -gravityNormal > 0.0f ) { + normal += GetContact( ix ).normal; + } + } + + return normal.ToNormal(); +} +// RAVEN END + +/* +================ +idPhysics_Base::HasGroundContacts +================ +*/ +bool idPhysics_Base::HasGroundContacts( void ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundEntity +================ +*/ +bool idPhysics_Base::IsGroundEntity( int entityNum ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundClipModel +================ +*/ +bool idPhysics_Base::IsGroundClipModel( int entityNum, int id ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && contacts[i].id == id && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::SetPushed +================ +*/ +void idPhysics_Base::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Base::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetMaster +================ +*/ +void idPhysics_Base::SetMaster( idEntity *master, const bool orientated ) { +} + +/* +================ +idPhysics_Base::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Base::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Base::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetLinearEndTime +================ +*/ +int idPhysics_Base::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetAngularEndTime +================ +*/ +int idPhysics_Base::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::AddGroundContacts +================ +*/ +void idPhysics_Base::AddGroundContacts( const idClipModel *clipModel ) { + idVec6 dir; + int index, num; + + index = contacts.Num(); + contacts.SetNum( index + 10, false ); + + dir.SubVec3(0) = gravityNormal; + dir.SubVec3(1) = vec3_origin; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[index], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( index + num, false ); +} + +/* +================ +idPhysics_Base::AddContactEntitiesForContacts +================ +*/ +void idPhysics_Base::AddContactEntitiesForContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent && ent != self ) { + ent->AddContactEntity( self ); + } + } +} + +/* +================ +idPhysics_Base::ActivateContactEntities +================ +*/ +void idPhysics_Base::ActivateContactEntities( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent ) { + ent->ActivatePhysics( self ); + } else { + contactEntities.RemoveIndex( i-- ); + } + } +} + +/* +================ +idPhysics_Base::IsOutsideWorld +================ +*/ +bool idPhysics_Base::IsOutsideWorld( void ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.GetWorldBounds( self ).Expand( 128.0f ).IntersectsBounds( GetAbsBounds() ) ) { +// RAVEN END + return true; + } + return false; +} + +/* +================ +idPhysics_Base::DrawVelocity +================ +*/ +void idPhysics_Base::DrawVelocity( int id, float linearScale, float angularScale ) const { + idVec3 dir, org, vec, start, end; + idMat3 axis; + float length, a; + + dir = GetLinearVelocity( id ); + dir *= linearScale; + if ( dir.LengthSqr() > Square( 0.1f ) ) { + dir.Truncate( 10.0f ); + org = GetOrigin( id ); + gameRenderWorld->DebugArrow( colorRed, org, org + dir, 1 ); + } + + dir = GetAngularVelocity( id ); + length = dir.Normalize(); + length *= angularScale; + if ( length > 0.1f ) { + if ( length < 60.0f ) { + length = 60.0f; + } + else if ( length > 360.0f ) { + length = 360.0f; + } + axis = GetAxis( id ); + vec = axis[2]; + if ( idMath::Fabs( dir * vec ) > 0.99f ) { + vec = axis[0]; + } + vec -= vec * dir * vec; + vec.Normalize(); + vec *= 4.0f; + start = org + vec; + for ( a = 20.0f; a < length; a += 20.0f ) { + end = org + idRotation( vec3_origin, dir, -a ).ToMat3() * vec; + gameRenderWorld->DebugLine( colorBlue, start, end, 1 ); + start = end; + } + end = org + idRotation( vec3_origin, dir, -length ).ToMat3() * vec; + gameRenderWorld->DebugArrow( colorBlue, start, end, 1 ); + } +} + +/* +================ +idPhysics_Base::WriteToSnapshot +================ +*/ +void idPhysics_Base::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Base::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} diff --git a/source/mpgame/physics/Physics_Base.h b/source/mpgame/physics/Physics_Base.h new file mode 100644 index 0000000..18d1437 --- /dev/null +++ b/source/mpgame/physics/Physics_Base.h @@ -0,0 +1,160 @@ + +#ifndef __PHYSICS_BASE_H__ +#define __PHYSICS_BASE_H__ + +/* +=============================================================================== + + Physics base for a moving object using one or more collision models. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Changed from #define to typedef to fix compiler issues +typedef idEntityPtr contactEntity_t; +// RAVEN END + +class idPhysics_Base : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Base ); + + idPhysics_Base( void ); + ~idPhysics_Base( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: added center mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + +// RAVEN BEGIN +// abahr: helper function used in projectiles + virtual const idVec3 GetContactNormal() const; + virtual const idVec3 GetGroundContactNormal() const; +// RAVEN END + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object + int clipMask; // contents the physics object collides with + idVec3 gravityVector; // direction and magnitude of gravity + idVec3 gravityNormal; // normalized direction of gravity + idList contacts; // contacts with other physics objects + idList contactEntities; // entities touching this physics object + +protected: + // add ground contacts for the clip model + void AddGroundContacts( const idClipModel *clipModel ); + // add contact entity links to contact entities + void AddContactEntitiesForContacts( void ); + // active all contact entities + void ActivateContactEntities( void ); + // returns true if the whole physics object is outside the world bounds + bool IsOutsideWorld( void ) const; + // draw linear and angular velocity + void DrawVelocity( int id, float linearScale, float angularScale ) const; +}; + +#endif /* !__PHYSICS_BASE_H__ */ diff --git a/source/mpgame/physics/Physics_Monster.cpp b/source/mpgame/physics/Physics_Monster.cpp new file mode 100644 index 0000000..751edb8 --- /dev/null +++ b/source/mpgame/physics/Physics_Monster.cpp @@ -0,0 +1,838 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster ) +END_CLASS + +const float OVERCLIP = 1.001f; + +/* +===================== +idPhysics_Monster::CheckGround +===================== +*/ +void idPhysics_Monster::CheckGround( monsterPState_t &state ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + down = state.origin + gravityNormal * CONTACT_EPSILON; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( groundTrace.fraction == 1.0f ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) { + state.onGround = false; + return; + } + + state.onGround = true; + + // let the entity know about the collision + self->Collide( groundTrace, state.velocity ); + + // apply impact to a non world floor entity + if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +===================== +idPhysics_Monster::SlideMove +===================== +*/ +monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + + if ( tr.c.entityNum != ENTITYNUM_NONE ) { + blockingEntity = gameLocal.entities[ tr.c.entityNum ]; + } + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + + return MM_BLOCKED; +} + +/* +===================== +idPhysics_Monster::StepMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel; + monsterMoveResult_t result1, result2; + float stepdist; + float nostepdist; + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta ); + if ( result1 == MM_OK ) { + velocity = noStepVel; +// RAVEN BEGIN +// bdube: dont step when there is no gravity + if ( gravityNormal == vec3_zero || forceDeltaMove ) { +// RAVEN END + start = noStepPos; + return MM_OK; + } + + // try to step down so that we walk down slopes and stairs at a normal rate + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction < 1.0f ) { + start = tr.endpos; + return MM_STEPPED; + } else { + start = noStepPos; + return MM_OK; + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockingEntity && blockingEntity->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // try to step down in case walking into an actor while going down steps + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = tr.endpos; + velocity = noStepVel; + return MM_BLOCKED; + } + + if ( gravityNormal == vec3_zero ) { + return result1; + } + + // try to step up + up = start - gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction == 0.0f ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta ); + if ( result2 == MM_BLOCKED ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // step down again + down = stepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + stepPos = tr.endpos; + + // if the move is further without stepping up, or the slope is too steap, don't step up + nostepdist = ( noStepPos - start ).LengthSqr(); + stepdist = ( stepPos - start ).LengthSqr(); + if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) { + start = noStepPos; + velocity = noStepVel; + return MM_SLIDING; + } + + start = stepPos; + velocity = stepVel; + + return MM_STEPPED; +} + +/* +================ +idPhysics_Monster::Activate +================ +*/ +void idPhysics_Monster::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::Rest +================ +*/ +void idPhysics_Monster::Rest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::PutToRest +================ +*/ +void idPhysics_Monster::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_Monster::idPhysics_Monster +================ +*/ +idPhysics_Monster::idPhysics_Monster( void ) { + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + saved = current; + + delta.Zero(); + maxStepHeight = 18.0f; + minFloorCosine = 0.7f; + moveResult = MM_OK; + forceDeltaMove = false; + fly = false; + useVelocityMove = false; + noImpact = false; + blockingEntity = NULL; +} + +/* +================ +idPhysics_Monster_SavePState +================ +*/ +void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.onGround ); + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Monster_RestorePState +================ +*/ +void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.onGround ); + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson: Added unrestored var +} + +/* +================ +idPhysics_Monster::Save +================ +*/ +void idPhysics_Monster::Save( idSaveGame *savefile ) const { + + idPhysics_Monster_SavePState( savefile, current ); + idPhysics_Monster_SavePState( savefile, saved ); + + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( minFloorCosine ); + savefile->WriteVec3( delta ); + + savefile->WriteBool( forceDeltaMove ); + savefile->WriteBool( fly ); + savefile->WriteBool( useVelocityMove ); + savefile->WriteBool( noImpact ); + + savefile->WriteInt( (int)moveResult ); + savefile->WriteObject( blockingEntity ); +} + +/* +================ +idPhysics_Monster::Restore +================ +*/ +void idPhysics_Monster::Restore( idRestoreGame *savefile ) { + + idPhysics_Monster_RestorePState( savefile, current ); + idPhysics_Monster_RestorePState( savefile, saved ); + + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( minFloorCosine ); + savefile->ReadVec3( delta ); + + savefile->ReadBool( forceDeltaMove ); + savefile->ReadBool( fly ); + savefile->ReadBool( useVelocityMove ); + savefile->ReadBool( noImpact ); + + savefile->ReadInt( (int &)moveResult ); + savefile->ReadObject( reinterpret_cast( blockingEntity ) ); +} + +/* +================ +idPhysics_Monster::SetDelta +================ +*/ +void idPhysics_Monster::SetDelta( const idVec3 &d ) { + delta = d; + if ( delta != vec3_origin ) { + Activate(); + } +} + +/* +================ +idPhysics_Monster::SetMaxStepHeight +================ +*/ +void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Monster::GetMaxStepHeight +================ +*/ +float idPhysics_Monster::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Monster::OnGround +================ +*/ +bool idPhysics_Monster::OnGround( void ) const { + return current.onGround; +} + +/* +================ +idPhysics_Monster::GetSlideMoveEntity +================ +*/ +idEntity *idPhysics_Monster::GetSlideMoveEntity( void ) const { + return blockingEntity; +} + +/* +================ +idPhysics_Monster::GetMoveResult +================ +*/ +monsterMoveResult_t idPhysics_Monster::GetMoveResult( void ) const { + return moveResult; +} + +/* +================ +idPhysics_Monster::ForceDeltaMove +================ +*/ +void idPhysics_Monster::ForceDeltaMove( bool force ) { + forceDeltaMove = force; +} + +/* +================ +idPhysics_Monster::UseFlyMove +================ +*/ +void idPhysics_Monster::UseFlyMove( bool force ) { + fly = force; +} + +/* +================ +idPhysics_Monster::UseVelocityMove +================ +*/ +void idPhysics_Monster::UseVelocityMove( bool force ) { + useVelocityMove = force; +} + +/* +================ +idPhysics_Monster::EnableImpact +================ +*/ +void idPhysics_Monster::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_Monster::DisableImpact +================ +*/ +void idPhysics_Monster::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_Monster::Evaluate +================ +*/ +bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return true; + } + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + idPhysics_Monster::CheckGround( current ); + + // if not on the ground or moving upwards + float upspeed; + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) { + if ( upspeed < 0.0f ) { + moveResult = MM_FALLING; + } + else { + current.onGround = false; + moveResult = MM_OK; + } + delta = current.velocity * timeStep; + if ( delta != vec3_origin ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly ) { + current.velocity += gravityVector * timeStep; + } + } else { + if ( useVelocityMove ) { + delta = current.velocity * timeStep; + } else { + current.velocity = delta / timeStep; + } + + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + + if ( delta == vec3_origin ) { + Rest(); + } else { + // try moving into the desired direction +// RAVEN BEGIN +// jshepard: flying creatures, even if not using fly move, shouldn't use step move + if( self->IsType( idAI::GetClassType() ) && ((idAI*)self)->move.moveType == MOVETYPE_FLY ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + } else { + moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + } + delta.Zero(); +// RAVEN END + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + Rest(); + } + +// RAVEN BEGIN +// bdube: determine if we moved this frame + return true; +// RAVEN END +} + +/* +================ +idPhysics_Monster::UpdateTime +================ +*/ +void idPhysics_Monster::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Monster::GetTime +================ +*/ +int idPhysics_Monster::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Monster::GetImpactInfo +================ +*/ +void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Monster::ApplyImpulse +================ +*/ +void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.velocity += impulse * invMass; + Activate(); +} + +/* +================ +idPhysics_Monster::IsAtRest +================ +*/ +bool idPhysics_Monster::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Monster::GetRestStartTime +================ +*/ +int idPhysics_Monster::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Monster::SaveState +================ +*/ +void idPhysics_Monster::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Monster::RestoreState +================ +*/ +void idPhysics_Monster::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Translate +================ +*/ +void idPhysics_Monster::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Rotate +================ +*/ +void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::SetLinearVelocity +================ +*/ +void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_Monster::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Monster::SetPushed +================ +*/ +void idPhysics_Monster::SetPushed( int deltaTime ) { + // velocity with which the monster is pushed + current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_Monster::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Monster::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +const float MONSTER_VELOCITY_MAX = 4000; +const int MONSTER_VELOCITY_TOTAL_BITS = 16; +const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1; +const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS; + +/* +================ +idPhysics_Monster::WriteToSnapshot +================ +*/ +void idPhysics_Monster::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteLong( current.atRest ); + msg.WriteBits( current.onGround, 1 ); +} + +/* +================ +idPhysics_Monster::ReadFromSnapshot +================ +*/ +void idPhysics_Monster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.atRest = msg.ReadLong(); + current.onGround = msg.ReadBits( 1 ) != 0; +} diff --git a/source/mpgame/physics/Physics_Monster.h b/source/mpgame/physics/Physics_Monster.h new file mode 100644 index 0000000..f35b7cc --- /dev/null +++ b/source/mpgame/physics/Physics_Monster.h @@ -0,0 +1,129 @@ + +#ifndef __PHYSICS_MONSTER_H__ +#define __PHYSICS_MONSTER_H__ + +/* +=================================================================================== + + Monster physics + + Simulates the motion of a monster through the environment. The monster motion + is typically driven by animations. + +=================================================================================== +*/ + +typedef enum { + MM_OK, + MM_SLIDING, + MM_BLOCKED, + MM_STEPPED, + MM_FALLING +} monsterMoveResult_t; + +typedef struct monsterPState_s { + int atRest; + bool onGround; + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END +} monsterPState_t; + +class idPhysics_Monster : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Monster ); + + idPhysics_Monster( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // maximum step up the monster can take, default 18 units + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + // minimum cosine of floor angle to be able to stand on the floor + void SetMinFloorCosine( const float newMinFloorCosine ); + // set delta for next move + void SetDelta( const idVec3 &d ); + // returns true if monster is standing on the ground + bool OnGround( void ) const; + // returns the movement result + monsterMoveResult_t GetMoveResult( void ) const; + // overrides any velocity for pure delta movement + void ForceDeltaMove( bool force ); + // whether velocity should be affected by gravity + void UseFlyMove( bool force ); + // don't use delta movement + void UseVelocityMove( bool force ); + // get entity blocking the move + idEntity * GetSlideMoveEntity( void ) const; + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // monster physics state + monsterPState_t current; + monsterPState_t saved; + + // properties + float maxStepHeight; // maximum step height + float minFloorCosine; // minimum cosine of floor angle + idVec3 delta; // delta for next move + + bool forceDeltaMove; + bool fly; + bool useVelocityMove; + bool noImpact; // if true do not activate when another object collides + + // results of last evaluate + monsterMoveResult_t moveResult; + idEntity * blockingEntity; + +private: + void CheckGround( monsterPState_t &state ); + monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + monsterMoveResult_t StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + void Rest( void ); +}; + +#endif /* !__PHYSICS_MONSTER_H__ */ diff --git a/source/mpgame/physics/Physics_Parametric.cpp b/source/mpgame/physics/Physics_Parametric.cpp new file mode 100644 index 0000000..854cb81 --- /dev/null +++ b/source/mpgame/physics/Physics_Parametric.cpp @@ -0,0 +1,1191 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Parametric ) +END_CLASS + + +/* +================ +idPhysics_Parametric::Activate +================ +*/ +void idPhysics_Parametric::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::TestIfAtRest +================ +*/ +bool idPhysics_Parametric::TestIfAtRest( void ) const { + + if ( ( current.linearExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + ( current.angularExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + current.linearInterpolation.GetDuration() == 0 && + current.angularInterpolation.GetDuration() == 0 && + current.spline == NULL ) { + return true; + } + + if ( !current.linearExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.linearInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( current.spline != NULL && !current.spline->IsDone( current.time ) ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_Parametric::Rest +================ +*/ +void idPhysics_Parametric::Rest( void ) { + current.atRest = gameLocal.time; + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::idPhysics_Parametric +================ +*/ +idPhysics_Parametric::idPhysics_Parametric( void ) { + + current.time = gameLocal.time; + current.atRest = -1; + current.useSplineAngles = false; + current.origin.Zero(); + current.angles.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAngles.Zero(); + current.linearExtrapolation.Init( 0, 0, vec3_zero, vec3_zero, vec3_zero, EXTRAPOLATION_NONE ); + current.angularExtrapolation.Init( 0, 0, ang_zero, ang_zero, ang_zero, EXTRAPOLATION_NONE ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + current.spline = NULL; + current.splineInterpolate.Init( 0, 1, 1, 2, 0, 0 ); + + saved = current; + + isPusher = false; + pushFlags = 0; + clipModel = NULL; + isBlocked = false; + memset( &pushResults, 0, sizeof( pushResults ) ); + + hasMaster = false; + isOrientated = false; + +// RAVEN BEGIN +// abahr: + useAxisOffset = false; + axisOffset.Identity(); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::~idPhysics_Parametric +================ +*/ +idPhysics_Parametric::~idPhysics_Parametric( void ) { + if ( clipModel != NULL ) { + delete clipModel; + clipModel = NULL; + } + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } +} + +/* +================ +idPhysics_Parametric_SavePState +================ +*/ +void idPhysics_Parametric_SavePState( idSaveGame *savefile, const parametricPState_t &state ) { + savefile->WriteInt( state.time ); + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.useSplineAngles ); + savefile->WriteVec3( state.origin ); + savefile->WriteAngles( state.angles ); + savefile->WriteMat3( state.axis ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteAngles( state.localAngles ); + + savefile->WriteInt( (int)state.linearExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.linearExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.linearExtrapolation.GetDuration() ); + savefile->WriteVec3( state.linearExtrapolation.GetStartValue() ); + savefile->WriteVec3( state.linearExtrapolation.GetBaseSpeed() ); + savefile->WriteVec3( state.linearExtrapolation.GetSpeed() ); + + savefile->WriteInt( (int)state.angularExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.angularExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.angularExtrapolation.GetDuration() ); + savefile->WriteAngles( state.angularExtrapolation.GetStartValue() ); + savefile->WriteAngles( state.angularExtrapolation.GetBaseSpeed() ); + savefile->WriteAngles( state.angularExtrapolation.GetSpeed() ); + + savefile->WriteFloat( state.linearInterpolation.GetStartTime() ); + savefile->WriteFloat( state.linearInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDuration() ); + savefile->WriteVec3( state.linearInterpolation.GetStartValue() ); + savefile->WriteVec3( state.linearInterpolation.GetEndValue() ); + + savefile->WriteFloat( state.angularInterpolation.GetStartTime() ); + savefile->WriteFloat( state.angularInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDuration() ); + savefile->WriteAngles( state.angularInterpolation.GetStartValue() ); + savefile->WriteAngles( state.angularInterpolation.GetEndValue() ); + + // spline is handled by owner + + savefile->WriteFloat( state.splineInterpolate.GetStartTime() ); + savefile->WriteFloat( state.splineInterpolate.GetAcceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetDuration() ); + savefile->WriteFloat( state.splineInterpolate.GetDeceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetStartValue() ); + savefile->WriteFloat( state.splineInterpolate.GetEndValue() ); +} + +/* +================ +idPhysics_Parametric_RestorePState +================ +*/ +void idPhysics_Parametric_RestorePState( idRestoreGame *savefile, parametricPState_t &state ) { + extrapolation_t etype; + float startTime, duration, accelTime, decelTime, startValue, endValue; + idVec3 linearStartValue, linearBaseSpeed, linearSpeed, startPos, endPos; + idAngles angularStartValue, angularBaseSpeed, angularSpeed, startAng, endAng; + + savefile->ReadInt( state.time ); + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.useSplineAngles ); + savefile->ReadVec3( state.origin ); + savefile->ReadAngles( state.angles ); + savefile->ReadMat3( state.axis ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadAngles( state.localAngles ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( linearStartValue ); + savefile->ReadVec3( linearBaseSpeed ); + savefile->ReadVec3( linearSpeed ); + + state.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, etype ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( angularStartValue ); + savefile->ReadAngles( angularBaseSpeed ); + savefile->ReadAngles( angularSpeed ); + + state.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, etype ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( startPos ); + savefile->ReadVec3( endPos ); + + state.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( startAng ); + savefile->ReadAngles( endAng ); + + state.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + // spline is handled by owner + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( startValue ); + savefile->ReadFloat( endValue ); + + state.splineInterpolate.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idPhysics_Parametric::Save +================ +*/ +void idPhysics_Parametric::Save( idSaveGame *savefile ) const { + + idPhysics_Parametric_SavePState( savefile, current ); + idPhysics_Parametric_SavePState( savefile, saved ); + + savefile->WriteBool( isPusher ); + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( pushFlags ); + + savefile->WriteTrace( pushResults ); + savefile->WriteBool( isBlocked ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); + + savefile->WriteBool ( useAxisOffset ); // cnicholson: Added unsaved var + savefile->WriteMat3 ( axisOffset ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Parametric::Restore +================ +*/ +void idPhysics_Parametric::Restore( idRestoreGame *savefile ) { + + idPhysics_Parametric_RestorePState( savefile, current ); + idPhysics_Parametric_RestorePState( savefile, saved ); + + savefile->ReadBool( isPusher ); + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( pushFlags ); + + savefile->ReadTrace( pushResults ); + savefile->ReadBool( isBlocked ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); + + savefile->ReadBool ( useAxisOffset ); // cnicholson: Added unrestored var + savefile->ReadMat3 ( axisOffset ); // cnicholson: Added unrestored var + +} + +/* +================ +idPhysics_Parametric::SetPusher +================ +*/ +void idPhysics_Parametric::SetPusher( int flags ) { + assert( clipModel ); + isPusher = true; + pushFlags = flags; +} + +/* +================ +idPhysics_Parametric::IsPusher +================ +*/ +bool idPhysics_Parametric::IsPusher( void ) const { + return isPusher; +} + +/* +================ +idPhysics_Parametric::SetLinearExtrapolation +================ +*/ +void idPhysics_Parametric::SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ) { + current.time = gameLocal.time; + current.linearExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localOrigin = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularExtrapolation +================ +*/ +void idPhysics_Parametric::SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ) { + current.time = gameLocal.time; + current.angularExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localAngles = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetLinearExtrapolationType( void ) const { + return current.linearExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::GetAngularExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetAngularExtrapolationType( void ) const { + return current.angularExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::SetLinearInterpolation +================ +*/ +void idPhysics_Parametric::SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ) { + current.time = gameLocal.time; + current.linearInterpolation.Init( time, accelTime, decelTime, duration, startPos, endPos ); + current.localOrigin = startPos; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularInterpolation +================ +*/ +void idPhysics_Parametric::SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ) { + current.time = gameLocal.time; + current.angularInterpolation.Init( time, accelTime, decelTime, duration, startAng, endAng ); + current.localAngles = startAng; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetSpline +================ +*/ +void idPhysics_Parametric::SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ) { + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } + current.spline = spline; + if ( current.spline != NULL ) { + float startTime = current.spline->GetTime( 0 ); + float endTime = current.spline->GetTime( current.spline->GetNumValues() - 1 ); + float length = current.spline->GetLengthForTime( endTime ); + current.splineInterpolate.Init( startTime, accelTime, decelTime, endTime - startTime, 0.0f, length ); + } + current.useSplineAngles = useSplineAngles; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetSpline +================ +*/ +idCurve_Spline *idPhysics_Parametric::GetSpline( void ) const { + return current.spline; +} + +/* +================ +idPhysics_Parametric::GetSplineAcceleration +================ +*/ +int idPhysics_Parametric::GetSplineAcceleration( void ) const { + return current.splineInterpolate.GetAcceleration(); +} + +/* +================ +idPhysics_Parametric::GetSplineDeceleration +================ +*/ +int idPhysics_Parametric::GetSplineDeceleration( void ) const { + return current.splineInterpolate.GetDeceleration(); +} + +/* +================ +idPhysics_Parametric::UsingSplineAngles +================ +*/ +bool idPhysics_Parametric::UsingSplineAngles( void ) const { + return current.useSplineAngles; +} + +/* +================ +idPhysics_Parametric::GetLocalOrigin +================ +*/ +void idPhysics_Parametric::GetLocalOrigin( idVec3 &curOrigin ) const { + curOrigin = current.localOrigin; +} + +/* +================ +idPhysics_Parametric::GetLocalAngles +================ +*/ +void idPhysics_Parametric::GetLocalAngles( idAngles &curAngles ) const { + curAngles = current.localAngles; +} + +/* +================ +idPhysics_Parametric::SetClipModel +================ +*/ +void idPhysics_Parametric::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::GetClipModel +================ +*/ +idClipModel *idPhysics_Parametric::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Parametric::GetNumClipModels +================ +*/ +int idPhysics_Parametric::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Parametric::SetMass +================ +*/ +void idPhysics_Parametric::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Parametric::GetMass +================ +*/ +float idPhysics_Parametric::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +void idPhysics_Parametric::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +int idPhysics_Parametric::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Parametric::GetBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return idPhysics_Base::GetBounds(); +} + +/* +================ +idPhysics_Parametric::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetAbsBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + return idPhysics_Base::GetAbsBounds(); +} + +/* +================ +idPhysics_Parametric::Evaluate +================ +*/ +bool idPhysics_Parametric::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 oldLocalOrigin, oldOrigin, masterOrigin; + idAngles oldLocalAngles, oldAngles; + idMat3 oldAxis, masterAxis; + + isBlocked = false; + oldLocalOrigin = current.localOrigin; + oldOrigin = current.origin; + oldLocalAngles = current.localAngles; + oldAngles = current.angles; + oldAxis = current.axis; + + current.localOrigin.Zero(); + current.localAngles.Zero(); + + if ( current.spline != NULL ) { + float length = current.splineInterpolate.GetCurrentValue( endTimeMSec ); + float t = current.spline->GetTimeForLength( length, 0.01f ); + current.localOrigin = current.spline->GetCurrentValue( t ); + if ( current.useSplineAngles ) { + current.localAngles = current.spline->GetCurrentFirstDerivative( t ).ToAngles(); + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + current.localOrigin += current.linearInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localOrigin += current.linearExtrapolation.GetCurrentValue( endTimeMSec ); + } + + if ( current.angularInterpolation.GetDuration() != 0 ) { + current.localAngles += current.angularInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localAngles += current.angularExtrapolation.GetCurrentValue( endTimeMSec ); + } + + current.localAngles.Normalize360(); + current.origin = current.localOrigin; + current.angles = current.localAngles; + current.axis = current.localAngles.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( masterAxis.IsRotated() ) { + current.origin = current.origin * masterAxis + masterOrigin; + if ( isOrientated ) { + current.axis *= masterAxis; + current.angles = current.axis.ToAngles(); + } + } + else { + current.origin += masterOrigin; + } + } + + if ( isPusher ) { + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + if ( pushResults.fraction < 1.0f ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, oldOrigin, oldAxis ); +// RAVEN END + current.localOrigin = oldLocalOrigin; + current.origin = oldOrigin; + current.localAngles = oldLocalAngles; + current.angles = oldAngles; + current.axis = oldAxis; + isBlocked = true; + return false; + } + + current.angles = current.axis.ToAngles(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// abahr: a hack way of hiding gimble lock from movers. + clipModel->Link( self, 0, current.origin, UseAxisOffset() ? GetAxisOffset() * current.axis : current.axis ); +// RAVEN END + } + + current.time = endTimeMSec; + + if ( TestIfAtRest() ) { + Rest(); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); +} + +/* +================ +idPhysics_Parametric::UpdateTime +================ +*/ +void idPhysics_Parametric::UpdateTime( int endTimeMSec ) { + int timeLeap = endTimeMSec - current.time; + + current.time = endTimeMSec; + // move the trajectory start times to sync the trajectory with the current endTime + current.linearExtrapolation.SetStartTime( current.linearExtrapolation.GetStartTime() + timeLeap ); + current.angularExtrapolation.SetStartTime( current.angularExtrapolation.GetStartTime() + timeLeap ); + current.linearInterpolation.SetStartTime( current.linearInterpolation.GetStartTime() + timeLeap ); + current.angularInterpolation.SetStartTime( current.angularInterpolation.GetStartTime() + timeLeap ); + if ( current.spline != NULL ) { + current.spline->ShiftTime( timeLeap ); + current.splineInterpolate.SetStartTime( current.splineInterpolate.GetStartTime() + timeLeap ); + } +} + +/* +================ +idPhysics_Parametric::GetTime +================ +*/ +int idPhysics_Parametric::GetTime( void ) const { + return current.time; +} + +/* +================ +idPhysics_Parametric::IsAtRest +================ +*/ +bool idPhysics_Parametric::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Parametric::GetRestStartTime +================ +*/ +int idPhysics_Parametric::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Parametric::IsPushable +================ +*/ +bool idPhysics_Parametric::IsPushable( void ) const { + return false; +} + +/* +================ +idPhysics_Parametric::SaveState +================ +*/ +void idPhysics_Parametric::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Parametric::RestoreState +================ +*/ +void idPhysics_Parametric::RestoreState( void ) { + + current = saved; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::SetOrigin +================ +*/ +void idPhysics_Parametric::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.linearExtrapolation.SetStartValue( newOrigin ); + current.linearInterpolation.SetStartValue( newOrigin ); + + current.localOrigin = current.linearExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAxis +================ +*/ +void idPhysics_Parametric::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAngles = newAxis.ToAngles(); + + current.angularExtrapolation.SetStartValue( current.localAngles ); + current.angularInterpolation.SetStartValue( current.localAngles ); + + current.localAngles = current.angularExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAngles.ToMat3() * masterAxis; + current.angles = current.axis.ToAngles(); + } + else { + current.axis = current.localAngles.ToMat3(); + current.angles = current.localAngles; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::Move +================ +*/ +void idPhysics_Parametric::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Parametric::Rotate +================ +*/ +void idPhysics_Parametric::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Parametric::GetOrigin +================ +*/ +const idVec3 &idPhysics_Parametric::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Parametric::GetAxis +================ +*/ +const idMat3 &idPhysics_Parametric::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Parametric::GetAngles +================ +*/ +void idPhysics_Parametric::GetAngles( idAngles &curAngles ) const { + curAngles = current.angles; +} + +/* +================ +idPhysics_Parametric::SetLinearVelocity +================ +*/ +void idPhysics_Parametric::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.origin, newLinearVelocity, vec3_origin ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularVelocity +================ +*/ +void idPhysics_Parametric::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + idRotation rotation; + idVec3 vec; + float angle; + + vec = newAngularVelocity; + angle = vec.Normalize(); + rotation.Set( vec3_origin, vec, (float) RAD2DEG( angle ) ); + + SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.angles, rotation.ToAngles(), ang_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + + curLinearVelocity = current.linearExtrapolation.GetCurrentSpeed( gameLocal.time ); + return curLinearVelocity; +} + +/* +================ +idPhysics_Parametric::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idAngles angles; + + angles = current.angularExtrapolation.GetCurrentSpeed( gameLocal.time ); + curAngularVelocity = angles.ToAngularVelocity(); + return curAngularVelocity; +} + +/* +================ +idPhysics_Parametric::DisableClip +================ +*/ +void idPhysics_Parametric::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Parametric::EnableClip +================ +*/ +void idPhysics_Parametric::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Parametric::UnlinkClip +================ +*/ +void idPhysics_Parametric::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Parametric::LinkClip +================ +*/ +void idPhysics_Parametric::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Parametric::GetBlockingInfo( void ) const { + return ( isBlocked ? &pushResults : NULL ); +} + +/* +================ +idPhysics_Parametric::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Parametric::GetBlockingEntity( void ) const { + if ( isBlocked ) { + return gameLocal.entities[ pushResults.c.entityNum ]; + } + return NULL; +} + +/* +================ +idPhysics_Parametric::SetMaster +================ +*/ +void idPhysics_Parametric::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAngles = ( current.axis * masterAxis.Transpose() ).ToAngles(); + } + else { + current.localAngles = current.axis.ToAngles(); + } + + current.linearExtrapolation.SetStartValue( current.localOrigin ); + current.angularExtrapolation.SetStartValue( current.localAngles ); + hasMaster = true; + isOrientated = orientated; + } + } + else { + if ( hasMaster ) { + // transform from master space to world space + current.localOrigin = current.origin; + current.localAngles = current.angles; + SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.origin, vec3_origin, vec3_origin ); + SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.angles, ang_zero, ang_zero ); + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Parametric::GetLinearEndTime +================ +*/ +int idPhysics_Parametric::GetLinearEndTime( void ) const { + if ( current.spline != NULL ) { + if ( current.spline->GetBoundaryType() != idCurve_Spline::BT_CLOSED ) { + return current.spline->GetTime( current.spline->GetNumValues() - 1 ); + } else { + return 0; + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + return current.linearInterpolation.GetEndTime(); + } else { + return current.linearExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::GetAngularEndTime +================ +*/ +int idPhysics_Parametric::GetAngularEndTime( void ) const { + if ( current.angularInterpolation.GetDuration() != 0 ) { + return current.angularInterpolation.GetEndTime(); + } else { + return current.angularExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::WriteToSnapshot +================ +*/ +void idPhysics_Parametric::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.time ); + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.angles[0] ); + msg.WriteFloat( current.angles[1] ); + msg.WriteFloat( current.angles[2] ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( current.angles[0], current.localAngles[0] ); + msg.WriteDeltaFloat( current.angles[1], current.localAngles[1] ); + msg.WriteDeltaFloat( current.angles[2], current.localAngles[2] ); + + msg.WriteBits( current.linearExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteBits( current.angularExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[2] ); +} + +/* +================ +idPhysics_Parametric::ReadFromSnapshot +================ +*/ +void idPhysics_Parametric::ReadFromSnapshot( const idBitMsgDelta &msg ) { + extrapolation_t linearType, angularType; + float startTime, duration, accelTime, decelTime; + idVec3 linearStartValue, linearSpeed, linearBaseSpeed, startPos, endPos; + idAngles angularStartValue, angularSpeed, angularBaseSpeed, startAng, endAng; + + current.time = msg.ReadLong(); + current.atRest = msg.ReadLong(); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.angles[0] = msg.ReadFloat(); + current.angles[1] = msg.ReadFloat(); + current.angles[2] = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAngles[0] = msg.ReadDeltaFloat( current.angles[0] ); + current.localAngles[1] = msg.ReadDeltaFloat( current.angles[1] ); + current.localAngles[2] = msg.ReadDeltaFloat( current.angles[2] ); + + linearType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, linearType ); + + angularType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, angularType ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startPos[0] = msg.ReadDeltaFloat( 0.0f ); + startPos[1] = msg.ReadDeltaFloat( 0.0f ); + startPos[2] = msg.ReadDeltaFloat( 0.0f ); + endPos[0] = msg.ReadDeltaFloat( 0.0f ); + endPos[1] = msg.ReadDeltaFloat( 0.0f ); + endPos[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startAng[0] = msg.ReadDeltaFloat( 0.0f ); + startAng[1] = msg.ReadDeltaFloat( 0.0f ); + startAng[2] = msg.ReadDeltaFloat( 0.0f ); + endAng[0] = msg.ReadDeltaFloat( 0.0f ); + endAng[1] = msg.ReadDeltaFloat( 0.0f ); + endAng[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + current.axis = current.angles.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} diff --git a/source/mpgame/physics/Physics_Parametric.h b/source/mpgame/physics/Physics_Parametric.h new file mode 100644 index 0000000..15f286f --- /dev/null +++ b/source/mpgame/physics/Physics_Parametric.h @@ -0,0 +1,163 @@ + +#ifndef __PHYSICS_PARAMETRIC_H__ +#define __PHYSICS_PARAMETRIC_H__ + +/* +=================================================================================== + + Parametric physics + + Used for predefined or scripted motion. The motion of an object is completely + parametrized. By adjusting the parameters an object is forced to follow a + predefined path. The parametric physics is typically used for doors, bridges, + rotating fans etc. + +=================================================================================== +*/ + +typedef struct parametricPState_s { + int time; // physics time + int atRest; // set when simulation is suspended + idVec3 origin; // world origin + idAngles angles; // world angles + idMat3 axis; // world axis + idVec3 localOrigin; // local origin + idAngles localAngles; // local angles + idExtrapolate linearExtrapolation; // extrapolation based description of the position over time + idExtrapolate angularExtrapolation; // extrapolation based description of the orientation over time + idInterpolateAccelDecelLinear linearInterpolation; // interpolation based description of the position over time + idInterpolateAccelDecelLinear angularInterpolation; // interpolation based description of the orientation over time + idCurve_Spline * spline; // spline based description of the position over time + idInterpolateAccelDecelLinear splineInterpolate; // position along the spline over time + bool useSplineAngles; // set the orientation using the spline +} parametricPState_t; + +class idPhysics_Parametric : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Parametric ); + + idPhysics_Parametric( void ); + ~idPhysics_Parametric( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPusher( int flags ); + bool IsPusher( void ) const; + + void SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ); + void SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ); + extrapolation_t GetLinearExtrapolationType( void ) const; + extrapolation_t GetAngularExtrapolationType( void ) const; + + void SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ); + void SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ); + + void SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ); + idCurve_Spline *GetSpline( void ) const; + int GetSplineAcceleration( void ) const; + int GetSplineDeceleration( void ) const; + bool UsingSplineAngles( void ) const; + + void GetLocalOrigin( idVec3 &curOrigin ) const; + void GetLocalAngles( idAngles &curAngles ) const; + + void GetAngles( idAngles &curAngles ) const; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + void SetAxisOffset( const idMat3& offset ) { axisOffset = offset; useAxisOffset = true; } + const idMat3& GetAxisOffset() const { return axisOffset; } + idMat3& GetAxisOffset() { return axisOffset; } + bool UseAxisOffset() const { return useAxisOffset; } +// RAVEN END + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // parametric physics state + parametricPState_t current; + parametricPState_t saved; + + // pusher + bool isPusher; + idClipModel * clipModel; + int pushFlags; + + // results of last evaluate + trace_t pushResults; + bool isBlocked; + + // master + bool hasMaster; + bool isOrientated; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + bool useAxisOffset; + idMat3 axisOffset; +// RAVEN END + +private: + bool TestIfAtRest( void ) const; + void Rest( void ); +}; + +#endif /* !__PHYSICS_PARAMETRIC_H__ */ diff --git a/source/mpgame/physics/Physics_Particle.cpp b/source/mpgame/physics/Physics_Particle.cpp new file mode 100644 index 0000000..0130edf --- /dev/null +++ b/source/mpgame/physics/Physics_Particle.cpp @@ -0,0 +1,1027 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Particle ) +END_CLASS + +const float PRT_OVERCLIP = 1.001f; +const float PRT_BOUNCESTOP = 10.0f; + +/* +================ +rvPhysics_Particle::DropToFloorAndRest + +Drops the object straight down to the floor +================ +*/ +void rvPhysics_Particle::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + testSolid = false; + if ( gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ) ) { + gameLocal.DWarning( "entity in solid '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.origin + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + current.origin = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, clipModel->GetAxis() ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + PutToRest(); + EvaluateContacts();//Do a final contact check. Items that drop to floor never do this check otherwise + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "entity outside world bounds '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + } +} + +/* +================ +rvPhysics_Particle::DebugDraw +================ +*/ +void rvPhysics_Particle::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +} + +/* +================ +rvPhysics_Particle::rvPhysics_Particle +================ +*/ +rvPhysics_Particle::rvPhysics_Particle( void ) { + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f, true ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + current.origin.Zero(); + saved = current; + + dropToFloor = false; + testSolid = false; + hasMaster = false; + + SetFriction( 0.6f, 0.6f, 0.0f ); + SetBouncyness ( 0.5f, true ); + + gravityNormal.Zero(); +} + +/* +================ +rvPhysics_Particle::~rvPhysics_Particle +================ +*/ +rvPhysics_Particle::~rvPhysics_Particle( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +rvPhysics_Particle::Save +================ +*/ +void rvPhysics_Particle::Save( idSaveGame *savefile ) const { + savefile->WriteInt( current.atRest ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteVec3( current.pushVelocity ); + savefile->WriteVec3( current.origin ); + savefile->WriteVec3( current.velocity ); + savefile->WriteBool( current.onGround ); + savefile->WriteBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->WriteInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->WriteVec3( saved.localOrigin ); + savefile->WriteMat3( saved.localAxis ); + savefile->WriteVec3( saved.pushVelocity ); + savefile->WriteVec3( saved.origin ); + savefile->WriteVec3( saved.velocity ); + savefile->WriteBool( saved.onGround ); + savefile->WriteBool( saved.inWater ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteBool ( allowBounce ); + savefile->WriteBounds ( clipModel->GetBounds ( ) ); // cnicholson: Added unsaved var + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); // cnicholson: Added unsaved var + extraPassEntity.Save( savefile ); + + savefile->WriteClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::Restore +================ +*/ +void rvPhysics_Particle::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( current.atRest ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadVec3( current.pushVelocity ); + savefile->ReadVec3( current.origin ); + savefile->ReadVec3( current.velocity ); + savefile->ReadBool( current.onGround ); + savefile->ReadBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->ReadInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->ReadVec3( saved.localOrigin ); + savefile->ReadMat3( saved.localAxis ); + savefile->ReadVec3( saved.pushVelocity ); + savefile->ReadVec3( saved.origin ); + savefile->ReadVec3( saved.velocity ); + savefile->ReadBool( saved.onGround ); + savefile->ReadBool( saved.inWater ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadBool ( allowBounce ); + + idBounds bounds; // cnicholson: Added unrestored var + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); // cnicholson: Added unrestored var + extraPassEntity.Restore( savefile ); + + savefile->ReadClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::SetClipModel +================ +*/ +void rvPhysics_Particle::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::GetClipModel +================ +*/ +idClipModel *rvPhysics_Particle::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Particle::GetNumClipModels +================ +*/ +int rvPhysics_Particle::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +rvPhysics_Particle::SetBouncyness +================ +*/ +void rvPhysics_Particle::SetBouncyness( const float b, bool _allowBounce ) { + allowBounce = _allowBounce; + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +rvPhysics_Particle::SetFriction +================ +*/ +void rvPhysics_Particle::SetFriction( const float linear, const float angular, const float contact ) { + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + + +/* +================ +rvPhysics_Particle::PutToRest +================ +*/ +void rvPhysics_Particle::PutToRest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::DropToFloor +================ +*/ +void rvPhysics_Particle::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +rvPhysics_Particle::Activate +================ +*/ +void rvPhysics_Particle::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::EvaluateContacts +================ +*/ +bool rvPhysics_Particle::EvaluateContacts( void ) { + ClearContacts(); + AddGroundContacts( clipModel ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +rvPhysics_Particle::SetContents +================ +*/ +void rvPhysics_Particle::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Particle::GetContents +================ +*/ +int rvPhysics_Particle::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Particle::GetBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Particle::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Particle::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_Particle::Evaluate( int timeStepMSec, int endTimeMSec ) { + particlePState_t next; + float timeStep; + float upspeed; + + timeStep = MS2SEC( timeStepMSec ); + + // if bound to a master + if ( hasMaster ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 oldOrigin; + + oldOrigin = current.origin; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, current.localAxis * masterAxis ); +// RAVEN END + + trace_t tr; + gameLocal.Translation( self, tr, oldOrigin, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.fraction < 1.0f ) { + self->Collide ( tr, current.origin - oldOrigin ); + } + + DebugDraw(); + + return true; + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + return true; + } + + clipModel->Unlink(); + + // Determine if currently on the ground + CheckGround ( ); + + // Determine the current upward velocity + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + + // If not on the ground, or moving upwards, or bouncing and moving toward gravity then do a straight + // forward slide move and gravity. + if ( !current.onGround || upspeed > 1.0f || (bouncyness > 0.0f && upspeed < -PRT_BOUNCESTOP && !current.inWater) ) { + // Force ground off when moving upward + if ( upspeed > 0.0f ) { + current.onGround = false; + } + SlideMove( current.origin, current.velocity, current.velocity * timeStep ); + if ( current.onGround && upspeed < PRT_BOUNCESTOP ) { + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } else { + current.velocity += (gravityVector * timeStep); + } + } else { + idVec3 delta; + + // Slow down due to friction + ApplyFriction ( timeStep ); + + delta = current.velocity * timeStep; + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + if ( delta == vec3_origin ) { + PutToRest( ); + } else { + SlideMove( current.origin, current.velocity, delta ); + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + DebugDraw(); + + // get all the ground contacts + EvaluateContacts(); + + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + PutToRest(); + } + + return true; +} + +/* +================ +rvPhysics_Particle::UpdateTime +================ +*/ +void rvPhysics_Particle::UpdateTime( int endTimeMSec ) { +} + +/* +================ +rvPhysics_Particle::GetTime +================ +*/ +int rvPhysics_Particle::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +rvPhysics_Particle::IsAtRest +================ +*/ +bool rvPhysics_Particle::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +rvPhysics_Particle::GetRestStartTime +================ +*/ +int rvPhysics_Particle::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +rvPhysics_Particle::IsPushable +================ +*/ +bool rvPhysics_Particle::IsPushable( void ) const { + return ( !hasMaster ); +} + +/* +================ +rvPhysics_Particle::SaveState +================ +*/ +void rvPhysics_Particle::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Particle::RestoreState +================ +*/ +void rvPhysics_Particle::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Particle::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Particle::SetAxis( const idMat3 &newAxis, int id ) { + current.localAxis = newAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::Translate +================ +*/ +void rvPhysics_Particle::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +rvPhysics_Particle::Rotate( +================ +*/ +void rvPhysics_Particle::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Particle::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +rvPhysics_Particle::GetAxis +================ +*/ +const idMat3 &rvPhysics_Particle::GetAxis( int id ) const { + if ( !clipModel ) { + return idPhysics_Base::GetAxis ( id ); + } + return clipModel->GetAxis(); +} + +/* +================ +rvPhysics_Particle::SetLinearVelocity +================ +*/ +void rvPhysics_Particle::SetLinearVelocity( const idVec3 &velocity, int id ) { + current.velocity = velocity; + Activate(); +} + +/* +================ +rvPhysics_Particle::GetLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +rvPhysics_Particle::ClipTranslation +================ +*/ +void rvPhysics_Particle::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipRotation +================ +*/ +void rvPhysics_Particle::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipContents +================ +*/ +int rvPhysics_Particle::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +rvPhysics_Particle::DisableClip +================ +*/ +void rvPhysics_Particle::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +rvPhysics_Particle::EnableClip +================ +*/ +void rvPhysics_Particle::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +rvPhysics_Particle::UnlinkClip +================ +*/ +void rvPhysics_Particle::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +rvPhysics_Particle::LinkClip +================ +*/ +void rvPhysics_Particle::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::SetPushed +================ +*/ +void rvPhysics_Particle::SetPushed( int deltaTime ) { + // velocity with which the particle is pushed + current.pushVelocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +rvPhysics_Particle::GetPushedLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +rvPhysics_Particle::SetMaster +================ +*/ +void rvPhysics_Particle::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + hasMaster = true; + } + ClearContacts(); + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +/* +===================== +rvPhysics_Particle::CheckGround +===================== +*/ +void rvPhysics_Particle::CheckGround( void ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + current.onGround = false; + return; + } + + down = current.origin + gravityNormal * CONTACT_EPSILON; + gameLocal.Translation( self, groundTrace, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( groundTrace.fraction == 1.0f ) { + current.onGround = false; + return; + } + + if ( ( groundTrace.c.normal * -gravityNormal ) < 0.7f ) { + current.onGround = false; + return; + } + + if ( groundTrace.c.entityNum >= 0 && groundTrace.c.entityNum < MAX_CLIENTS ) { + current.onGround = false; + return; + } + + current.onGround = true; +} + +/* +================ +rvPhysics_Particle::ApplyFriction +================ +*/ +void rvPhysics_Particle::ApplyFriction( float timeStep ) { + idVec3 vel; + float speed; + float newspeed; + + // ignore slope movement, remove all velocity in gravity direction + vel = current.velocity + (current.velocity * gravityNormal) * gravityNormal; + + speed = vel.Length(); + if ( speed < 1.0f ) { + // remove all movement orthogonal to gravity, allows for sinking underwater + current.velocity = (current.velocity * gravityNormal) * gravityNormal; + return; + } + + // scale the velocity + if ( current.onGround ) { + newspeed = speed - ((speed * contactFriction) * timeStep); + } else { + newspeed = speed - ((speed * linearFriction) * timeStep); + } + + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= ( newspeed / speed ); +} + +/* +================ +rvPhysics_Particle::SlideMove +================ +*/ +bool rvPhysics_Particle::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + bool collide, rtnValue = false; + + move = delta; + for( i = 0; i < 3; i++ ) { // be sure if you change this upper value in the for() to update the exit condition below!!!!! + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self, extraPassEntity ); + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return false; + } + return true; + } + + bool hitTeleporter = false; + + // let the entity know about the collision + collide = self->Collide( tr, current.velocity, hitTeleporter ); + + idEntity* ent; + ent = gameLocal.entities[tr.c.entityNum]; + assert ( ent ); + + // If we hit water just clip the move for now and keep on going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + // Make sure we dont collide with water again + clipMask &= ~CONTENTS_WATER; + + current.inWater = true; + + // Allow the loop to go one more round to push us through the water + i--; + + velocity *= 0.4f; + + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + continue; + // bounce the projectile + } else if ( !current.inWater && allowBounce && bouncyness ) { + if ( !hitTeleporter ) { + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ) * bouncyness; + } + return true; +//RAVEN BEGIN +//jshepard: tr.c.material can (did) crash here if null + } else if ( allowBounce && tr.c.material && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) ) { +//RAVEN END + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ); + return true; + } +// RAVEN BEGIN +// dluetscher: removed redundant trace calls + else { + i = 4; + rtnValue = true; + } +// RAVEN END + + // clip the movement delta and velocity + if( collide ) { + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + } + } + + return rtnValue; +} + +const float PRT_VELOCITY_MAX = 16000; +const int PRT_VELOCITY_TOTAL_BITS = 16; +const int PRT_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PRT_VELOCITY_MAX ) ) + 1; +const int PRT_VELOCITY_MANTISSA_BITS = PRT_VELOCITY_TOTAL_BITS - 1 - PRT_VELOCITY_EXPONENT_BITS; + +/* +================ +rvPhysics_Particle::WriteToSnapshot +================ +*/ +void rvPhysics_Particle::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.atRest ); + msg.WriteBits ( current.onGround, 1 ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + // TODO: Check that this conditional write to delta message is OK + if ( hasMaster ) { + idCQuat localQuat; + localQuat = current.localAxis.ToCQuat(); + + msg.WriteBits ( 1, 1 ); + msg.WriteFloat( localQuat.x ); + msg.WriteFloat( localQuat.y ); + msg.WriteFloat( localQuat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + } else { + msg.WriteBits ( 0, 1 ); + } +} + +/* +================ +rvPhysics_Particle::ReadFromSnapshot +================ +*/ +void rvPhysics_Particle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.atRest = msg.ReadLong(); + current.onGround = ( msg.ReadBits( 1 ) != 0 ); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); +// current.velocity[0] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + if ( msg.ReadBits ( 1 ) ) { + idCQuat localQuat; + localQuat.x = msg.ReadFloat( ); + localQuat.y = msg.ReadFloat( ); + localQuat.z = msg.ReadFloat( ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAxis = localQuat.ToMat3(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + } +} diff --git a/source/mpgame/physics/Physics_Particle.h b/source/mpgame/physics/Physics_Particle.h new file mode 100644 index 0000000..a440db3 --- /dev/null +++ b/source/mpgame/physics/Physics_Particle.h @@ -0,0 +1,152 @@ +#ifndef __PHYSICS_PARTICLE_H__ +#define __PHYSICS_PARTICLE_H__ + +/* +=================================================================================== + + Particle Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct particlePState_s { + int atRest; // set when simulation is suspended + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec3 pushVelocity; // push velocity + idVec3 origin; + idVec3 velocity; + bool onGround; + bool inWater; +} particlePState_t; + +class rvPhysics_Particle : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Particle ); + + rvPhysics_Particle( void ); + ~rvPhysics_Particle( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b, bool allowBounce ); + + // put to rest untill something collides with this physics object + void PutToRest( void ); + // same as above but drop to the floor first + void DropToFloor( void ); + + // returns true if touching the ground + bool IsOnGround ( void ) const; + bool IsInWater ( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + bool CanBounce ( void ) const; + + bool EvaluateContacts( void ); + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idEntityPtr extraPassEntity; + +private: + // state of the particle + particlePState_t current; + particlePState_t saved; + + // particle properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + bool allowBounce; // Allowed to bounce at all? + idClipModel * clipModel; // clip model used for collision detection + + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for inside solid during a drop + + // master + bool hasMaster; + bool isOrientated; + +private: + + void DropToFloorAndRest ( void ); + bool SlideMove ( idVec3& start, idVec3& velocity, const idVec3& delta ); + void CheckGround ( void ); + void ApplyFriction ( float timeStep ); + void DebugDraw ( void ); +}; + +ID_INLINE bool rvPhysics_Particle::IsOnGround ( void ) const { + return current.onGround; +} + +ID_INLINE bool rvPhysics_Particle::IsInWater ( void ) const { + return current.inWater; +} + +ID_INLINE bool rvPhysics_Particle::CanBounce ( void ) const { + return allowBounce; +} + +#endif /* !__PHYSICS_PARTICLE_H__ */ diff --git a/source/mpgame/physics/Physics_Player.cpp b/source/mpgame/physics/Physics_Player.cpp new file mode 100644 index 0000000..ce21bac --- /dev/null +++ b/source/mpgame/physics/Physics_Player.cpp @@ -0,0 +1,2257 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player ) +END_CLASS + +// movement parameters +const float PM_STOPSPEED = 100.0f; +const float PM_SWIMSCALE = 0.5f; +const float PM_LADDERSPEED = 100.0f; +const float PM_STEPSCALE = 1.0f; + +const float PM_ACCELERATE_SP = 10.0f; +const float PM_AIRACCELERATE_SP = 1.0f; +const float PM_ACCELERATE_MP = 15.0f; +const float PM_AIRACCELERATE_MP = 1.18f; +const float PM_WATERACCELERATE = 4.0f; +const float PM_FLYACCELERATE = 8.0f; + +const float PM_FRICTION = 6.0f; +const float PM_AIRFRICTION = 0.0f; +const float PM_WATERFRICTION = 2.0f; +const float PM_FLYFRICTION = 3.0f; +const float PM_NOCLIPFRICTION = 12.0f; +const float PM_SLIDEFRICTION = 0.5f; + +const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes +const float OVERCLIP = 1.001f; + +// movementFlags +const int PMF_DUCKED = 1; // set when ducking +const int PMF_JUMPED = 2; // set when the player jumped this frame +const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame +const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame +const int PMF_JUMP_HELD = 16; // set when jump button is held down +const int PMF_TIME_LAND = 32; // movementTime is time before rejump +const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time +const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump +const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK); + +float idPhysics_Player::Pm_Accelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_ACCELERATE_MP : PM_ACCELERATE_SP; +} + +float idPhysics_Player::Pm_AirAccelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_AIRACCELERATE_MP : PM_AIRACCELERATE_SP; +} + +/* +============ +idPhysics_Player::CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const { + int max; + float total; + float scale; + int forwardmove; + int rightmove; + int upmove; + + forwardmove = cmd.forwardmove; + rightmove = cmd.rightmove; + + // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground + // otherwise crouch speed will be lower than specified + if ( walking ) { + upmove = 0; + } else { + upmove = cmd.upmove; + } + + max = abs( forwardmove ); + if ( abs( rightmove ) > max ) { + max = abs( rightmove ); + } + if ( abs( upmove ) > max ) { + max = abs( upmove ); + } + + if ( !max ) { + return 0.0f; + } + + total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); + scale = (float) playerSpeed * max / ( 127.0f * total ); + + return scale; +} + +/* +============== +idPhysics_Player::Accelerate + +Handles user intended acceleration +============== +*/ +void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) { +#if 1 + // q2 style + float addspeed, accelspeed, currentspeed; + + currentspeed = current.velocity * wishdir; + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } +// RAVEN BEGIN +// nmckenzie: added ability to try alternate accelerations. + if ( pm_acceloverride.GetFloat() > 0.0f ) { + accelspeed = pm_acceloverride.GetFloat() * frametime * wishspeed; + } else { + accelspeed = accel * frametime * wishspeed; + } +// RAVEN END + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + current.velocity += accelspeed * wishdir; + +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + idVec3 wishVelocity; + idVec3 pushDir; + float pushLen; + float canPush; + + wishVelocity = wishdir * wishspeed; + pushDir = wishVelocity - current.velocity; + pushLen = pushDir.Normalize(); + + canPush = accel * frametime * wishspeed; + if ( canPush > pushLen ) { + canPush = pushLen; + } + + current.velocity += canPush * pushDir; +#endif +} + +/* +=============== +idPhysics_Player::AdjustVertically +given a walkable plane normal, adjust z to be in the plane without changing the horizontal direction +=============== +*/ +idVec3 idPhysics_Player::AdjustVertically( const idVec3 &normal, const idVec3 &_in, int forceLength ) const { + idVec3 out; + idVec3 in = _in; + + idVec3 velocityPlane; + velocityPlane[0] = in[1]; + velocityPlane[1] = -in[0]; + velocityPlane[2] = 0.0f; + + // same length as horizontal input, and in the plane + out = velocityPlane.Cross( normal ); + + // make sure direction is ok + if ( in * out < 0 ) { + out = -out; + } + + switch ( forceLength ) { + case 0: { + if ( pm_slidevelocity.GetInteger() == 1 ) { + double inSpeed = in.Normalize(); + double hSpeed = out.Normalize(); + // incidence angle, between 0 (just touching, result speed at inSpeed) and M_PI/2 (perpendicular hit, result speed at hSpeed) + float angle = idMath::ACos( in * out ); + double ratio = idMath::Pow64( angle * 2.0f / idMath::PI, pm_powerslide.GetFloat() ); + float targetSpeed = ratio * hSpeed + ( 1.0 - ratio ) * inSpeed; + out *= targetSpeed; + } + break; + } + case 1: { + double inSpeed = in.Normalize(); + out.Normalize(); + out *= inSpeed; + break; + } + case 2: { + // scale the out vector so horizontal speed is maintained + idVec3 hout = out; hout.z = 0.0f; + float ratio = velocityPlane.Length() / hout.Length(); + out *= ratio; + break; + } + } + + // and maintain an OVERCLIP + if ( out.z >= 0 ) { + out.z *= OVERCLIP; + } else { + out.z /= OVERCLIP; + } + + return out; +} + + +/* +================== +idPhysics_Player::SlideMove + +Returns true if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) { + int i, j, k, pushFlags; + int bumpcount, numbumps, numplanes; + float d, time_left, into, totalMass; + idVec3 dir; + idVec3 planes[MAX_CLIP_PLANES]; + idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity; + trace_t trace, stepTrace, downTrace; + bool nearGround, stepped, pushed; + bool sliding = ( current.crouchSlideTime > 0 ) && ( command.upmove < 0 ) && groundPlane; + int forceLength = sliding ? 2 : 0; + + numbumps = 4; + + primal_velocity = current.velocity; + + if ( gravity ) { + endVelocity = current.velocity + gravityVector * frametime; + current.velocity = ( current.velocity + endVelocity ) * 0.5f; + primal_velocity = endVelocity; + if ( groundPlane ) { + // slide along the ground plane + if ( groundTrace.c.normal.z >= MIN_WALK_NORMAL ) { + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity, forceLength ); + } else { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + } + } else { + endVelocity = current.velocity; + } + + time_left = frametime; + + // never turn against the ground plane + if ( groundPlane ) { + numplanes = 1; + planes[0] = groundTrace.c.normal; + } else { + numplanes = 0; + } + + // never turn against original velocity + planes[numplanes] = current.velocity; + planes[numplanes].Normalize(); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { + + // calculate position we are trying to move to + end = current.origin + time_left * current.velocity; + + // see if we can make it there + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + current.origin = trace.endpos; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + + time_left -= time_left * trace.fraction; + stepped = pushed = false; + + // if we are allowed to step up + if ( stepUp && ( trace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + nearGround = groundPlane | ladder; + + if ( !nearGround ) { + // trace down to see if the player is near the ground + // step checking when near the ground allows the player to move up stairs smoothly while jumping + stepEnd = current.origin + maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + nearGround = ( downTrace.fraction < 1.0f && ( downTrace.c.normal * -gravityNormal ) > MIN_WALK_NORMAL ); + } + + // may only step up if near the ground or on a ladder + if ( nearGround ) { + + // step up + stepEnd = current.origin - maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // trace along velocity + stepEnd = downTrace.endpos + time_left * current.velocity; + gameLocal.Translation( self, stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // step down + stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( downTrace.fraction >= 1.0f || ( downTrace.c.normal * -gravityNormal ) > MIN_WALK_NORMAL ) { + // if moved the entire distance + if ( stepTrace.fraction >= 1.0f ) { + time_left = 0; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + break; + } + + // if the move is further when stepping up + if ( stepTrace.fraction > trace.fraction ) { + time_left -= time_left * stepTrace.fraction; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + trace = stepTrace; + stepped = true; + } + } + } + } + + // if we can push other entities and not blocked by the world + if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) { + + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + // clip movement, only push idMoveables, don't push entities the player is standing on + // apply impact to pushed objects + pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f ); + pushed = true; + } + + current.origin = trace.endpos; + time_left -= time_left * trace.fraction; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + } + + if ( !stepped && self ) { + // let the entity know about the collision + self->Collide( trace, current.velocity ); + } + + if ( numplanes >= MAX_CLIP_PLANES ) { + // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris + // where it may hit the max clip planes + current.velocity = vec3_origin; + return true; + } + + // + // if this is the same plane we hit before, nudge velocity out along it, + // which fixes some epsilon issues with non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( ( trace.c.normal * planes[i] ) > 0.999f ) { + + if ( planes[i].z >= MIN_WALK_NORMAL ) { + // cargo cult? + current.velocity = AdjustVertically( trace.c.normal, current.velocity, forceLength ); + } else { + // clip into the trace normal just in case this normal is almost but not exactly the same as the groundTrace normal + current.velocity.ProjectOntoPlane( trace.c.normal, OVERCLIP ); + // also add the normal to nudge the velocity out + current.velocity += trace.c.normal; + } + + break; + } + } + if ( i < numplanes ) { + continue; + } + planes[numplanes] = trace.c.normal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = current.velocity * planes[i]; + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + if ( planes[i].z >= MIN_WALK_NORMAL ) { + clipVelocity = AdjustVertically( planes[i], current.velocity, forceLength ); + endClipVelocity = AdjustVertically( planes[i], endVelocity, forceLength ); + } else { + // slide along the plane + clipVelocity = current.velocity; + clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // slide along the plane + endClipVelocity = endVelocity; + endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + } + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( ( clipVelocity * planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + if ( planes[j].z >= MIN_WALK_NORMAL ) { + clipVelocity = AdjustVertically( planes[j], clipVelocity, forceLength ); + endClipVelocity = AdjustVertically( planes[j], endClipVelocity, forceLength ); + } else { + // try clipping the move to the plane + clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + } + + // see if it goes back into the first clip plane + if ( ( clipVelocity * planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * current.velocity; + clipVelocity = d * dir; + + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * endVelocity; + endClipVelocity = d * dir; + + // see if there is a third plane the the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( ( clipVelocity * planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction (duh) + current.velocity = vec3_origin; + return true; + } + } + + // if we have fixed all interactions, try another move + current.velocity = clipVelocity; + endVelocity = endClipVelocity; + + break; + } + } + + // step down + if ( stepDown && groundPlane ) { + stepEnd = current.origin + gravityNormal * maxStepHeight; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) { + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_DOWN; + } + } + + if ( gravity ) { + current.velocity = endVelocity; + } + + // come to a dead stop when the velocity orthogonal to the gravity flipped + clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal; + endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal; + if ( clipVelocity * endClipVelocity < 0.0f ) { + current.velocity = gravityNormal * current.velocity * gravityNormal; + } + + return ( bumpcount == 0 ); +} + +/* +================== +idPhysics_Player::Friction + +Handles both ground friction and water friction +================== +*/ +void idPhysics_Player::Friction( void ) { + idVec3 vel; + float speed, newspeed, control; + float drop; + + vel = current.velocity; + if ( walking ) { + // ignore slope movement, remove all velocity in gravity direction + vel += (vel * gravityNormal) * gravityNormal; + } + + speed = vel.Length(); + if ( speed < 1.0f ) { + current.velocity.Zero(); + return; + } + + drop = 0; + + // spectator friction + if ( pm_frictionoverride.GetFloat() > -1 ) { + drop += speed * pm_frictionoverride.GetFloat() * frametime; + } else if ( current.movementType == PM_SPECTATOR ) { + drop += speed * PM_FLYFRICTION * frametime; + } + // apply ground friction + else if ( walking && waterLevel <= WATERLEVEL_FEET ) { + // no friction on slick surfaces + if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) { + control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed; + if ( current.crouchSlideTime > 0 ) { + drop += control * PM_SLIDEFRICTION * frametime; + } else { + drop += control * PM_FRICTION * frametime; + } + } + } + } + // apply water friction even if just wading + else if ( waterLevel ) { + drop += speed * PM_WATERFRICTION * waterLevel * frametime; + } + // apply air friction + else { + drop += speed * PM_AIRFRICTION * frametime; + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + current.velocity *= ( newspeed / speed ); + + // TTimo - snap to avoid denormals + if ( fabs( current.velocity.x ) < 1.0e-5f ) { + current.velocity.x = 0.0f; + } + if ( fabs( current.velocity.y ) < 1.0e-5f ) { + current.velocity.y = 0.0f; + } + if ( fabs( current.velocity.z ) < 1.0e-5f ) { + current.velocity.z = 0.0f; + } +} + +/* +=================== +idPhysics_Player::WaterJumpMove + +Flying out of the water +=================== +*/ +void idPhysics_Player::WaterJumpMove( void ) { + + // waterjump has no control, but falls + idPhysics_Player::SlideMove( true, true, false, false ); + + // add gravity + current.velocity += gravityNormal * frametime; + // if falling down + if ( current.velocity * gravityNormal > 0.0f ) { + // cancel as soon as we are falling down again + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } +} + +/* +=================== +idPhysics_Player::WaterMove +=================== +*/ +void idPhysics_Player::WaterMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + float vel; + + if ( idPhysics_Player::CheckWaterJump() ) { + idPhysics_Player::WaterJumpMove(); + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // user intentions + if ( !scale ) { + wishvel = gravityNormal * 60; // sink towards bottom + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + if ( wishspeed > playerSpeed * PM_SWIMSCALE ) { + wishspeed = playerSpeed * PM_SWIMSCALE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE ); + + // make sure we can go up slopes easily under water + if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) { + vel = current.velocity.Length(); + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + current.velocity.Normalize(); + current.velocity *= vel; + } + + idPhysics_Player::SlideMove( false, true, false, false ); +} + +/* +=================== +idPhysics_Player::FlyMove +=================== +*/ +void idPhysics_Player::FlyMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +=================== +idPhysics_Player::AirMove +=================== +*/ +void idPhysics_Player::AirMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + + // if the player isnt pressing crouch and heading down then accumulate slide time + if ( command.upmove >= 0 && current.velocity * gravityNormal > 0 ) { + current.crouchSlideTime = idMath::ClampInt( 0, 2000, current.crouchSlideTime + framemsec * 2 ); + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishvel -= (wishvel * gravityNormal) * gravityNormal; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // not on ground, so little effect on velocity + idPhysics_Player::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 ( groundPlane ) { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + + // NOTE: enable stair checking while moving through the air in multiplayer to allow bunny hopping onto stairs + idPhysics_Player::SlideMove( true, gameLocal.isMultiplayer, false, false ); +} + +/* +=================== +idPhysics_Player::WalkMove +=================== +*/ +void idPhysics_Player::WalkMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + float accelerate; + idVec3 vel; + + if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) { + // begin swimming + idPhysics_Player::WaterMove(); + return; + } + + if ( idPhysics_Player::CheckJump() ) { + // jumped away + if ( waterLevel > WATERLEVEL_FEET ) { + idPhysics_Player::WaterMove(); + } else { + idPhysics_Player::AirMove(); + } + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + + assert( groundTrace.c.normal.z >= MIN_WALK_NORMAL ); + viewForward = AdjustVertically( groundTrace.c.normal, viewForward ); + viewRight = AdjustVertically( groundTrace.c.normal, viewRight ); + + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( waterLevel ) { + float waterScale; + + waterScale = waterLevel / 3.0f; + waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale; + if ( wishspeed > playerSpeed * waterScale ) { + wishspeed = playerSpeed * waterScale; + } + } + + // lower acceleration (control) when on slippery stuff or being smacked around + bool fLowControl = ( ( current.movementFlags & PMF_TIME_KNOCKBACK ) || ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) ); + accelerate = fLowControl ? Pm_AirAccelerate() : Pm_Accelerate(); + idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate ); + if ( fLowControl ) { + current.velocity += gravityVector * frametime; + } + + // slide along the ground plane + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity, 1 ); + + // don't do anything if standing still + vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal; + if ( vel.IsZero() ) { + return; + } + + gameLocal.push.InitSavingPushedEntityPositions(); + + idPhysics_Player::SlideMove( false, true, true, !gameLocal.isMultiplayer ); +} + +/* +============== +idPhysics_Player::DeadMove +============== +*/ +void idPhysics_Player::DeadMove( void ) { + float forward; + + if ( !walking ) { + return; + } + + // extra friction + forward = current.velocity.Length(); + forward -= 20; + if ( forward <= 0 ) { + current.velocity = vec3_origin; + } + else { + current.velocity.Normalize(); + current.velocity *= forward; + } +} + +/* +=============== +idPhysics_Player::NoclipMove +=============== +*/ +void idPhysics_Player::NoclipMove( void ) { + float speed, drop, friction, newspeed, stopspeed; + float scale, wishspeed; + idVec3 wishdir; + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat ( ) > -1 ) { + idPhysics_Player::Friction(); + } else { +// RAVEN END + + // friction + speed = current.velocity.Length(); + if ( speed < 20.0f ) { + current.velocity = vec3_origin; + } + else { + stopspeed = playerSpeed * 0.3f; + if ( speed < stopspeed ) { + speed = stopspeed; + } + friction = PM_NOCLIPFRICTION; + drop = speed * friction * frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= newspeed / speed; + } + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + } +// RAVEN END + + // accelerate + scale = idPhysics_Player::CmdScale( command ); + + wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishdir -= scale * gravityNormal * command.upmove; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_Accelerate() ); + + // move + current.origin += frametime * current.velocity; +} + +/* +=============== +idPhysics_Player::SpectatorMove +=============== +*/ +void idPhysics_Player::SpectatorMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + trace_t trace; + idVec3 end; + + // fly movement + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +============ +idPhysics_Player::LadderMove +============ +*/ +void idPhysics_Player::LadderMove( void ) { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * ladderNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (ladderNormal * right) * ladderNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( ladderNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.upmove ) { + wishvel += -0.5f * gravityNormal * scale * (float) command.upmove; + } + + // do strafe friction + idPhysics_Player::Friction(); + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, Pm_Accelerate() ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + +/* +============= +idPhysics_Player::CorrectAllSolid +============= +*/ +void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) { + // FIXME: jitter around to find a free spot ? + + if ( trace.fraction >= 1.0f ) { + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = current.origin; + trace.endAxis = clipModelAxis; + trace.fraction = 0.0f; + trace.c.dist = current.origin.z; + trace.c.normal.Set( 0, 0, 1 ); + trace.c.point = current.origin; + trace.c.entityNum = ENTITYNUM_WORLD; + trace.c.id = 0; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.material = NULL; + trace.c.contents = contents; + } +} + +/* +============= +idPhysics_Player::CheckGround +============= +*/ +void idPhysics_Player::CheckGround( bool checkStuck ) { + int i, contents; + idVec3 point; + bool hadGroundContacts; + + hadGroundContacts = HasGroundContacts(); + + // set the clip model origin before getting the contacts + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); + + // setup a ground trace from the contacts + groundTrace.endpos = current.origin; + groundTrace.endAxis = clipModel->GetAxis(); + if ( contacts.Num() ) { + groundTrace.fraction = 0.0f; + groundTrace.c = contacts[0]; + for ( i = 1; i < contacts.Num(); i++ ) { + groundTrace.c.normal += contacts[i].normal; + } + groundTrace.c.normal.Normalize(); + } else { + groundTrace.fraction = 1.0f; + } + + if ( checkStuck && contacts.Num() ) { + contents = gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), -1, self ); + if ( contents & MASK_SOLID ) { + // do something corrective if stuck in solid + idPhysics_Player::CorrectAllSolid( groundTrace, contents ); + } + } + + // if the trace didn't hit anything, we are in free fall + if ( groundTrace.fraction == 1.0f ) { + groundPlane = false; + walking = false; + groundEntityPtr = NULL; + return; + } + + groundMaterial = groundTrace.c.material; + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + // check if getting thrown off the ground + if ( ( current.velocity * -gravityNormal ) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 1.0f ) { + groundPlane = false; + walking = false; + return; + } + + // slopes that are too steep will not be considered onground + if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + // in multiplayer, instead of sliding push the player out from the normal for some free fall + current.origin += groundTrace.c.normal; + + groundPlane = false; + walking = false; + + return; + } + + groundPlane = true; + walking = true; + + // hitting solid ground will end a waterjump + if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + current.movementTime = 0; + } + + // if the player didn't have ground contacts the previous frame + if ( !hadGroundContacts ) { + // don't do landing time if we were just going down a slope + if ( (current.velocity * -gravityNormal) < -200.0f ) { + // don't allow another jump for a little while + current.movementFlags |= PMF_TIME_LAND; + current.movementTime = 250; + } + } + + // let the entity know about the collision + if ( self ) { + self->Collide( groundTrace, current.velocity ); + } + + if ( groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +============== +idPhysics_Player::CheckDuck + +Sets clip model size +============== +*/ +void idPhysics_Player::CheckDuck( void ) { + trace_t trace; + idVec3 end; + idBounds bounds; + float maxZ; + + if ( current.movementType == PM_DEAD ) { + maxZ = pm_deadheight.GetFloat(); + } else { + // stand up when up against a ladder + if ( command.upmove < 0 && !ladder ) { + // duck + current.movementFlags |= PMF_DUCKED; + } else { + // stand up if possible + if ( current.movementFlags & PMF_DUCKED ) { + // try to stand up + end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal; + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( trace.fraction >= 1.0f ) { + current.movementFlags &= ~PMF_DUCKED; + } + } + } + + if ( current.movementFlags & PMF_DUCKED ) { + if ( !current.crouchSlideTime ) { + playerSpeed = crouchSpeed; + } + maxZ = pm_crouchheight.GetFloat(); + } else { + maxZ = pm_normalheight.GetFloat(); + if ( groundPlane && current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } + } + } + // if the clipModel height should change + if ( clipModel->GetBounds()[1][2] != maxZ ) { + + bounds = clipModel->GetBounds(); + bounds[1][2] = maxZ; + + // < 0: don't use alternate alignment, > 0: use alternate alignment (faces aligned with axes for even-sided cylinders) + // 0: use AABB; 1: use 8-sided cylinder; 3+: use custom number of sides for cylinder + int sides = pm_usecylinder.GetInteger(); + bool alt_align = (sides > 0); + sides = idMath::Abs( sides ); + + if ( sides >= 1 ) { + if ( sides < 3 ) { + sides = 8; + } + idTraceModel trm( bounds, sides, alt_align ); +// trm.findWalkSurfaces = true; + clipModel->LoadModel( trm, NULL ); + } else { + idTraceModel trm( bounds ); +// trm.findWalkSurfaces = true; + clipModel->LoadModel( trm, NULL ); + } + } +} + +/* +================ +idPhysics_Player::CheckLadder +================ +*/ +void idPhysics_Player::CheckLadder( void ) { + idVec3 forward, start, end; + trace_t trace; + float tracedist; + + if ( current.movementTime ) { + return; + } + + // if on the ground moving backwards + if ( walking && command.forwardmove <= 0 ) { + return; + } + + // forward vector orthogonal to gravity + forward = viewForward - (gravityNormal * viewForward) * gravityNormal; + forward.Normalize(); + + if ( walking ) { + // don't want to get sucked towards the ladder when still walking + tracedist = 1.0f; + } else { + tracedist = 48.0f; + } + + end = current.origin + tracedist * forward; + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + // if near a surface + if ( trace.fraction < 1.0f ) { + + // if a ladder surface + if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) { + + // check a step height higher + end = current.origin - gravityNormal * ( maxStepHeight * 0.75f ); + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + start = trace.endpos; + end = start + tracedist * forward; + gameLocal.Translation( self, trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self ); + + // if also near a surface a step height higher + if ( trace.fraction < 1.0f ) { + + // if it also is a ladder surface + if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) { + ladder = true; + ladderNormal = trace.c.normal; + } + } + } + } +} + +/* +============= +idPhysics_Player::CheckJump +============= +*/ +bool idPhysics_Player::CheckJump( void ) { + idVec3 addVelocity; + + // CheckJump only called from WalkMove, therefore with walking == true + // in MP game we always have groundPlane == walking + // (this mostly matters to velocity clipping against ground when the jump is ok'ed) + assert( groundPlane ); + + if ( command.upmove < 10 ) { + // not holding jump + return false; + } + + // must wait for jump to be released + if ( current.movementFlags & PMF_JUMP_HELD ) { + return false; + } + + // don't jump if we can't stand up + if ( current.movementFlags & PMF_DUCKED ) { + return false; + } + + // start by setting up the normal ground slide velocity + // this will make sure that when we add the jump velocity we actually get off of the ground plane + if ( current.velocity * groundTrace.c.normal < 0.0f ) { + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity ); + } + + addVelocity = 2.0f * maxJumpHeight * -gravityVector; + addVelocity *= idMath::Sqrt( addVelocity.Normalize() ); + current.velocity += addVelocity; + + groundPlane = false; // jumping away + walking = false; + groundEntityPtr = NULL; + current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED; + + // crouch slide + current.crouchSlideTime = 0; + + return true; +} + +/* +============= +idPhysics_Player::CheckWaterJump +============= +*/ +bool idPhysics_Player::CheckWaterJump( void ) { + idVec3 spot; + int cont; + idVec3 flatforward; + + if ( current.movementTime ) { + return false; + } + + // check for water jump + if ( waterLevel != WATERLEVEL_WAIST ) { + return false; + } + + flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal; + flatforward.Normalize(); + + spot = current.origin + 30.0f * flatforward; + spot -= 4.0f * gravityNormal; + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); + if ( !(cont & CONTENTS_SOLID) ) { + return false; + } + + spot -= 16.0f * gravityNormal; + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); + if ( cont ) { + return false; + } + + // jump out of water + current.velocity = 200.0f * viewForward - 350.0f * gravityNormal; + current.movementFlags |= PMF_TIME_WATERJUMP; + current.movementTime = 2000; + + return true; +} + +/* +============= +idPhysics_Player::SetWaterLevel +============= +*/ +void idPhysics_Player::SetWaterLevel( void ) { + idVec3 point; + idBounds bounds; + int contents; + + // + // get waterlevel, accounting for ducking + // + waterLevel = WATERLEVEL_NONE; + waterType = 0; + + bounds = clipModel->GetBounds(); + +// AReis: Get back the water entity (if there is one), so we can grab his density +// then apply some force to the fluid since we're moving through it. + idEntity *other = NULL; + + // check at feet level + point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self, &other ); + if ( contents & MASK_WATER ) { + + waterType = contents; + waterLevel = WATERLEVEL_FEET; + + // check at waist level + point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + + waterLevel = WATERLEVEL_WAIST; + + // check at head level + point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + waterLevel = WATERLEVEL_HEAD; + } + } + } +} + +/* +================ +idPhysics_Player::DropTimers +================ +*/ +void idPhysics_Player::DropTimers( void ) { + // drop misc timing counter + if ( current.movementTime ) { + if ( framemsec >= current.movementTime ) { + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } + else { + current.movementTime -= framemsec; + } + } + + if ( groundPlane && current.crouchSlideTime ) { + if ( framemsec >= current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } else { + current.crouchSlideTime -= framemsec; + } + } +} + +/* +================ +idPhysics_Player::MovePlayer +================ +*/ +void idPhysics_Player::MovePlayer( int msec ) { + walking = false; + groundPlane = false; + ladder = false; + + // determine the time + framemsec = msec; + frametime = framemsec * 0.001f; + + // default speed + playerSpeed = walkSpeed; + + // remove jumped and stepped up flag + current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN); + current.stepUp = 0.0f; + + if ( command.upmove < 10 ) { + // not holding jump + current.movementFlags &= ~PMF_JUMP_HELD; + } + + // if no movement at all + if ( current.movementType == PM_FREEZE ) { + return; + } + + // move the player velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + // view vectors + viewAngles.ToVectors( &viewForward, NULL, NULL ); + viewForward *= clipModelAxis; + viewRight = gravityNormal.Cross( viewForward ); + viewRight.Normalize(); + + // fly in spectator mode +// RAVEN BEGIN +// nmckenzie: Allowing ways to force spectator movement. + if ( current.movementType == PM_SPECTATOR || pm_forcespectatormove.GetBool() ) { +// RAVEN END + SpectatorMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // special no clip mode + if ( current.movementType == PM_NOCLIP ) { + idPhysics_Player::NoclipMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // no control when dead + if ( current.movementType == PM_DEAD ) { + command.forwardmove = 0; + command.rightmove = 0; + command.upmove = 0; + } + + // set watertype and waterlevel +// RAVEN BEGIN +// ddynerman: water disabled in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + // check for ground + idPhysics_Player::CheckGround( true ); + + // check if up against a ladder +// RAVEN BEGIN +// MrE: no ladders in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::CheckLadder(); + } +// RAVEN END + + // set clip model size + idPhysics_Player::CheckDuck(); + + // handle timers + idPhysics_Player::DropTimers(); + + // move + if ( current.movementType == PM_DEAD ) { + // dead + idPhysics_Player::DeadMove(); + } + else if ( ladder ) { + // going up or down a ladder + idPhysics_Player::LadderMove(); + } +// RAVEN BEGIN +// ddynerman: water disabled in MP + else if ( !gameLocal.isMultiplayer && current.movementFlags & PMF_TIME_WATERJUMP ) { + + // jumping out of water + idPhysics_Player::WaterJumpMove(); + } + else if ( !gameLocal.isMultiplayer && waterLevel > 1 ) { +// RAVEN END + // swimming + idPhysics_Player::WaterMove(); + } + else if ( walking ) { + // walking on ground + idPhysics_Player::WalkMove(); + } + else { + // airborne + idPhysics_Player::AirMove(); + } + + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } + + idPhysics_Player::CheckGround( false ); + + // move the player velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); +} + + +/* +================ +idPhysics_Player::GetWaterLevel +================ +*/ +waterLevel_t idPhysics_Player::GetWaterLevel( void ) const { + return waterLevel; +} + +/* +================ +idPhysics_Player::GetWaterType +================ +*/ +int idPhysics_Player::GetWaterType( void ) const { + return waterType; +} + +/* +================ +idPhysics_Player::HasJumped +================ +*/ +bool idPhysics_Player::HasJumped( void ) const { + return ( ( current.movementFlags & PMF_JUMPED ) != 0 ); +} + +/* +================ +idPhysics_Player::HasSteppedUp +================ +*/ +bool idPhysics_Player::HasSteppedUp( void ) const { + return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 ); +} + +/* +================ +idPhysics_Player::GetStepUp +================ +*/ +float idPhysics_Player::GetStepUp( void ) const { + return current.stepUp; +} + +/* +================ +idPhysics_Player::IsCrouching +================ +*/ +bool idPhysics_Player::IsCrouching( void ) const { + //MCG: if bound, never think we're crouched + return ( !masterEntity&&( current.movementFlags & PMF_DUCKED ) != 0 ); +} + +/* +================ +idPhysics_Player::IsJumping +================ +*/ +bool idPhysics_Player::IsJumping( void ) const { + return ( !masterEntity&&( current.movementFlags & PMF_JUMP_HELD ) != 0 ); +} + +/* +================ +idPhysics_Player::OnLadder +================ +*/ +bool idPhysics_Player::OnLadder( void ) const { + return ladder; +} + +/* +================ +idPhysics_Player::idPhysics_Player +================ +*/ +idPhysics_Player::idPhysics_Player( void ) { + debugLevel = false; + clipModel = NULL; + clipMask = 0; + memset( ¤t, 0, sizeof( current ) ); + saved = current; + walkSpeed = 0; + crouchSpeed = 0; + maxStepHeight = 0; + maxJumpHeight = 0; + memset( &command, 0, sizeof( command ) ); + viewAngles.Zero(); + framemsec = 0; + frametime = 0; + playerSpeed = 0; + viewForward.Zero(); + viewRight.Zero(); + walking = false; + groundPlane = false; + memset( &groundTrace, 0, sizeof( groundTrace ) ); + groundMaterial = NULL; + ladder = false; + ladderNormal.Zero(); + waterLevel = WATERLEVEL_NONE; + waterType = 0; +} + +/* +================ +idPhysics_Player_SavePState +================ +*/ +void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson Added unsaved var + + savefile->WriteFloat( state.stepUp ); + savefile->WriteInt( state.movementType ); + savefile->WriteInt( state.movementFlags ); + savefile->WriteInt( state.movementTime ); + + savefile->WriteInt( state.crouchSlideTime ); // cnicholson Added unsaved var +} + +/* +================ +idPhysics_Player_RestorePState +================ +*/ +void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson Added unrestored var + + savefile->ReadFloat( state.stepUp ); + savefile->ReadInt( state.movementType ); + savefile->ReadInt( state.movementFlags ); + savefile->ReadInt( state.movementTime ); + + savefile->ReadInt( state.crouchSlideTime ); // cnicholson Added unrestored var +} + +/* +================ +idPhysics_Player::Save +================ +*/ +void idPhysics_Player::Save( idSaveGame *savefile ) const { + + idPhysics_Player_SavePState( savefile, current ); + idPhysics_Player_SavePState( savefile, saved ); + + savefile->WriteFloat( walkSpeed ); + savefile->WriteFloat( crouchSpeed ); + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( maxJumpHeight ); + savefile->WriteInt( debugLevel ); + + savefile->WriteUsercmd( command ); + savefile->WriteAngles( viewAngles ); + + savefile->WriteInt( framemsec ); + savefile->WriteFloat( frametime ); + savefile->WriteFloat( playerSpeed ); + savefile->WriteVec3( viewForward ); + savefile->WriteVec3( viewRight ); + + savefile->WriteBool( walking ); + savefile->WriteBool( groundPlane ); + savefile->WriteTrace( groundTrace ); + savefile->WriteMaterial( groundMaterial ); + + savefile->WriteBool( ladder ); + savefile->WriteVec3( ladderNormal ); + + savefile->WriteInt( (int)waterLevel ); + savefile->WriteInt( waterType ); +} + +/* +================ +idPhysics_Player::Restore +================ +*/ +void idPhysics_Player::Restore( idRestoreGame *savefile ) { + + idPhysics_Player_RestorePState( savefile, current ); + idPhysics_Player_RestorePState( savefile, saved ); + + savefile->ReadFloat( walkSpeed ); + savefile->ReadFloat( crouchSpeed ); + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( maxJumpHeight ); + savefile->ReadInt( debugLevel ); + + savefile->ReadUsercmd( command ); + savefile->ReadAngles( viewAngles ); + + savefile->ReadInt( framemsec ); + savefile->ReadFloat( frametime ); + savefile->ReadFloat( playerSpeed ); + savefile->ReadVec3( viewForward ); + savefile->ReadVec3( viewRight ); + + savefile->ReadBool( walking ); + savefile->ReadBool( groundPlane ); + savefile->ReadTrace( groundTrace ); + savefile->ReadMaterial( groundMaterial ); + + savefile->ReadBool( ladder ); + savefile->ReadVec3( ladderNormal ); + + savefile->ReadInt( (int &)waterLevel ); + savefile->ReadInt( waterType ); +} + +/* +================ +idPhysics_Player::SetPlayerInput +================ +*/ +void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) { + command = cmd; + viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles +} + +/* +================ +idPhysics_Player::SetSpeed +================ +*/ +void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) { + walkSpeed = newWalkSpeed; + crouchSpeed = newCrouchSpeed; +} + +/* +================ +idPhysics_Player::SetMaxStepHeight +================ +*/ +void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Player::GetMaxStepHeight +================ +*/ +float idPhysics_Player::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Player::SetMaxJumpHeight +================ +*/ +void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) { + maxJumpHeight = newMaxJumpHeight; +} + +/* +================ +idPhysics_Player::SetMovementType +================ +*/ +void idPhysics_Player::SetMovementType( const pmtype_t type ) { + current.movementType = type; +} + +/* +================ +idPhysics_Player::SetKnockBack +================ +*/ +void idPhysics_Player::SetKnockBack( const int knockBackTime ) { + if ( current.movementTime ) { + return; + } + current.movementFlags |= PMF_TIME_KNOCKBACK; + current.movementTime = knockBackTime; +} + +/* +================ +idPhysics_Player::SetDebugLevel +================ +*/ +void idPhysics_Player::SetDebugLevel( bool set ) { + debugLevel = set; +} + +/* +================ +idPhysics_Player::Evaluate +================ +*/ +bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + + waterLevel = WATERLEVEL_NONE; + waterType = 0; + oldOrigin = current.origin; + + clipModel->Unlink(); + + // if bound to a master + if ( masterEntity ) { + assert( self ); + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f ); + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + ActivateContactEntities(); + + idPhysics_Player::MovePlayer( timeStepMSec ); + // I think the self is a remnant from early TV free fly code, can be removed now? + if ( self ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + + // IsOutsideWorld uses self, so it needs to be non null + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self ? "NULL" : self->name.c_str(), current.origin.ToString(0) ); + } + } + + return true; //( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Player::UpdateTime +================ +*/ +void idPhysics_Player::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Player::GetTime +================ +*/ +int idPhysics_Player::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Player::GetImpactInfo +================ +*/ +void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Player::ApplyImpulse +================ +*/ +void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( current.movementType != PM_NOCLIP ) { + current.velocity += impulse * invMass; + } +} + +/* +================ +idPhysics_Player::IsAtRest +================ +*/ +bool idPhysics_Player::IsAtRest( void ) const { + return false; +} + +/* +================ +idPhysics_Player::GetRestStartTime +================ +*/ +int idPhysics_Player::GetRestStartTime( void ) const { + return -1; +} + +/* +================ +idPhysics_Player::SaveState +================ +*/ +void idPhysics_Player::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Player::RestoreState +================ +*/ +void idPhysics_Player::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + assert( self ); + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); + } +// RAVEN END +} + +/* +================ +idPhysics_Player::GetOrigin +================ +*/ +const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const { + return current.origin; +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Translate +================ +*/ +void idPhysics_Player::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Rotate +================ +*/ +void idPhysics_Player::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::SetLinearVelocity +================ +*/ +void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; +} + +/* +================ +idPhysics_Player::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushed( int deltaTime ) { + idVec3 velocity; + float d; + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Player::ClearPushedVelocity +================ +*/ +void idPhysics_Player::ClearPushedVelocity( void ) { + current.pushVelocity.Zero(); + current.lastPushVelocity.Zero(); +} + +/* +================ +idPhysics_Player::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } else { + if ( masterEntity ) { + masterEntity = NULL; + } + } +} + +const float PLAYER_VELOCITY_MAX = 4000; +const int PLAYER_VELOCITY_TOTAL_BITS = 16; +const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1; +const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS; +const int PLAYER_MOVEMENT_TYPE_BITS = 3; +const int PLAYER_MOVEMENT_FLAGS_BITS = 8; + +/* +================ +idPhysics_Player::WriteToSnapshot +================ +*/ +void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); + + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + msg.WriteDeltaFloat( 0.0f, current.stepUp ); + msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS ); + msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS ); + msg.WriteDeltaLong( 0, current.movementTime ); + msg.WriteDeltaLong( 0, current.crouchSlideTime ); +} + +/* +================ +idPhysics_Player::ReadFromSnapshot +================ +*/ +void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) { + + idVec3 oldOrigin = current.origin; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + + GAMELOG_SET( "origin_delta_x", (current.origin - oldOrigin).x ); + GAMELOG_SET( "origin_delta_y", (current.origin - oldOrigin).y ); + GAMELOG_SET( "origin_delta_z", (current.origin - oldOrigin).z ); + + idVec3 oldVelocity = current.velocity; + + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); + + GAMELOG_SET( "velocity_delta_x", (current.velocity - oldVelocity).x ); + GAMELOG_SET( "velocity_delta_y", (current.velocity - oldVelocity).y ); + GAMELOG_SET( "velocity_delta_z", (current.velocity - oldVelocity).z ); + + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + current.stepUp = msg.ReadDeltaFloat( 0.0f ); + current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS ); + + current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS ); + + current.movementTime = msg.ReadDeltaLong( 0 ); + current.crouchSlideTime = msg.ReadDeltaLong( 0 ); + + if ( clipModel ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + } +} + +/* +=============== +idPhysics_Player::SetClipModelNoLink +=============== +*/ +void idPhysics_Player::SetClipModelNoLink( idClipModel *model ) { + assert( model ); + assert( model->IsTraceModel() ); + + if ( clipModel && clipModel != model ) { + delete clipModel; + } + clipModel = model; +} diff --git a/source/mpgame/physics/Physics_Player.h b/source/mpgame/physics/Physics_Player.h new file mode 100644 index 0000000..ab6a7bb --- /dev/null +++ b/source/mpgame/physics/Physics_Player.h @@ -0,0 +1,198 @@ + +#ifndef __PHYSICS_PLAYER_H__ +#define __PHYSICS_PLAYER_H__ + +/* +=================================================================================== + + Player physics + + Simulates the motion of a player through the environment. Input from the + player is used to allow a certain degree of control over the motion. + +=================================================================================== +*/ + +// movementType +typedef enum { + PM_NORMAL, // normal physics + PM_DEAD, // no acceleration or turning, but free falling + PM_SPECTATOR, // flying without gravity but with collision detection + PM_FREEZE, // stuck in place without control + PM_NOCLIP // flying without collision detection nor gravity +} pmtype_t; + +typedef enum { + WATERLEVEL_NONE, + WATERLEVEL_FEET, + WATERLEVEL_WAIST, + WATERLEVEL_HEAD +} waterLevel_t; + +#define MAXTOUCH 32 + +typedef struct playerPState_s { + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; + idVec3 lastPushVelocity; + float stepUp; + int movementType; + int movementFlags; + int movementTime; + int crouchSlideTime; +} playerPState_t; + +class idPhysics_Player : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Player ); + + idPhysics_Player( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ); + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + void SetMaxJumpHeight( const float newMaxJumpHeight ); + void SetMovementType( const pmtype_t type ); + void SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ); + void SetKnockBack( const int knockBackTime ); + void SetDebugLevel( bool set ); + // feed back from last physics frame + waterLevel_t GetWaterLevel( void ) const; + int GetWaterType( void ) const; + bool HasJumped( void ) const; + bool HasSteppedUp( void ) const; + float GetStepUp( void ) const; + bool IsCrouching( void ) const; + bool IsJumping( void ) const; + bool OnLadder( void ) const; + bool OnGround( void ) const; + const idVec3 & PlayerGetOrigin( void ) const; // != GetOrigin + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + void ClearPushedVelocity( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + bool IsNoclip( void ) const; + bool IsDead( void ) const; + + void SetClipModelNoLink( idClipModel *clip ); + +private: + // player physics state + playerPState_t current; + playerPState_t saved; + + // properties + float walkSpeed; + float crouchSpeed; + float maxStepHeight; + float maxJumpHeight; + int debugLevel; // if set, diagnostic output will be printed + + // player input + usercmd_t command; + idAngles viewAngles; + + // run-time variables + int framemsec; + float frametime; + float playerSpeed; + idVec3 viewForward; + idVec3 viewRight; + + // walk movement + bool walking; + bool groundPlane; + trace_t groundTrace; + const idMaterial * groundMaterial; + + // ladder movement + bool ladder; + idVec3 ladderNormal; + + // results of last evaluate + waterLevel_t waterLevel; + int waterType; + +private: + float CmdScale( const usercmd_t &cmd ) const; + void Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ); + bool SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ); + void Friction( void ); + void WaterJumpMove( void ); + void WaterMove( void ); + void FlyMove( void ); + void AirMove( void ); + void WalkMove( void ); + void DeadMove( void ); + void NoclipMove( void ); + void SpectatorMove( void ); + void LadderMove( void ); + void CorrectAllSolid( trace_t &trace, int contents ); + void CheckGround( bool checkStuck ); + void CheckDuck( void ); + void CheckLadder( void ); + bool CheckJump( void ); + bool CheckWaterJump( void ); + void SetWaterLevel( void ); + void DropTimers( void ); + void MovePlayer( int msec ); + + // forceLength: + // 0 => use the pm_slideVelocity / pm_powerSlide settings + // 1 => maintain velocity + // 2 => maintain horizontal velocity + idVec3 AdjustVertically( const idVec3 &normal, const idVec3 &in, int forceLength = 0 ) const; + + float Pm_Accelerate( void ); + float Pm_AirAccelerate( void ); +}; + +ID_INLINE bool idPhysics_Player::IsNoclip( void ) const { + return current.movementType == PM_NOCLIP; +} + +ID_INLINE bool idPhysics_Player::IsDead( void ) const { + return current.movementType == PM_DEAD; +} + +ID_INLINE bool idPhysics_Player::OnGround( void ) const { + return groundPlane; +} + +#endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/source/mpgame/physics/Physics_RigidBody.cpp b/source/mpgame/physics/Physics_RigidBody.cpp new file mode 100644 index 0000000..1c28d6b --- /dev/null +++ b/source/mpgame/physics/Physics_RigidBody.cpp @@ -0,0 +1,1623 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_RigidBody ) +END_CLASS + +const float STOP_SPEED = 10.0f; + + +#undef RB_TIMINGS + +#ifdef RB_TIMINGS +static int lastTimerReset = 0; +static int numRigidBodies = 0; +static idTimer timer_total, timer_collision; +#endif + + +/* +================ +RigidBodyDerivatives +================ +*/ +void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const idPhysics_RigidBody *p = (idPhysics_RigidBody *) clientData; + rigidBodyIState_t *s = (rigidBodyIState_t *) state; + // NOTE: this struct should be build conform rigidBodyIState_t + struct rigidBodyDerivatives_s { + idVec3 linearVelocity; + idMat3 angularMatrix; + idVec3 force; + idVec3 torque; + } *d = (struct rigidBodyDerivatives_s *) derivatives; + idVec3 angularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = s->orientation * p->inverseInertiaTensor * s->orientation.Transpose(); + angularVelocity = inverseWorldInertiaTensor * s->angularMomentum; + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix = SkewSymmetric( angularVelocity ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque = - p->angularFriction * s->angularMomentum + p->current.externalTorque; +} + +/* +================ +idPhysics_RigidBody::Integrate + + Calculate next state from the current state using an integrator. +================ +*/ +void idPhysics_RigidBody::Integrate( float deltaTime, rigidBodyPState_t &next ) { + idVec3 position; + + position = current.i.position; + current.i.position += centerOfMass * current.i.orientation; + + current.i.orientation.TransposeSelf(); + + integrator->Evaluate( (float *) ¤t.i, (float *) &next.i, 0, deltaTime ); + next.i.orientation.OrthoNormalizeSelf(); + + // apply gravity + next.i.linearMomentum += deltaTime * gravityVector * mass; + + current.i.orientation.TransposeSelf(); + next.i.orientation.TransposeSelf(); + + current.i.position = position; + next.i.position -= centerOfMass * next.i.orientation; + + next.atRest = current.atRest; +} + +/* +================ +idPhysics_RigidBody::CollisionImpulse + + Calculates the collision impulse using the velocity relative to the collision object. + The current state should be set to the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CollisionImpulse( const trace_t &collision, idVec3 &impulse ) { + idVec3 r, linearVelocity, angularVelocity, velocity; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator, vel; + impactInfo_t info; + idEntity *ent; + + // get info from other entity involved + ent = gameLocal.entities[collision.c.entityNum]; + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + +// RAVEN BEGIN +// bdube: once in water take out the water flag and increase friction + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + clipMask &= ~CONTENTS_WATER; + linearFriction *= 20.0f; + angularFriction *= 20.0f; + } +// RAVEN END + + // collision point relative to the body center of mass + r = collision.c.point - (current.i.position + centerOfMass * current.i.orientation); + + // the velocity at the collision point + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + + // velocity in normal direction + vel = velocity * collision.c.normal; + + if ( vel > -STOP_SPEED ) { + impulseNumerator = STOP_SPEED; + } + else { + impulseNumerator = -( 1.0f + bouncyness ) * vel; + } + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // update linear and angular momentum with impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if no movement at all don't blow up + if ( collision.fraction < 0.0001f ) { + current.i.linearMomentum *= 0.5f; + current.i.angularMomentum *= 0.5f; + } + + // callback to self to let the entity know about the collision + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_RigidBody::CheckForCollisions + + Check for collisions between the current and next state. + If there is a collision the next state is set to the state at the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { +//#define TEST_COLLISION_DETECTION + idMat3 axis; + idRotation rotation; + bool collided = false; + +#ifdef TEST_COLLISION_DETECTION + bool startsolid; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( current.i.orientation, next.i.orientation, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( current.i.position ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, current.i.position, next.i.position, rotation, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + next.i.linearMomentum = current.i.linearMomentum; + next.i.angularMomentum = current.i.angularMomentum; + collided = true; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, next.i.position, clipModel, next.i.orientation, clipMask, self ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return collided; +} + +/* +================ +idPhysics_RigidBody::ContactFriction + + Does not solve friction for multiple simultaneous contacts but applies contact friction in isolation. + Uses absolute velocity at the contact points instead of the velocity relative to the contact object. +================ +*/ +void idPhysics_RigidBody::ContactFriction( float deltaTime ) { + int i; + float magnitude, impulseNumerator, impulseDenominator; + idMat3 inverseWorldInertiaTensor; + idVec3 linearVelocity, angularVelocity; + idVec3 massCenter, r, velocity, normal, impulse, normalVelocity; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + + massCenter = current.i.position + centerOfMass * current.i.orientation; + + for ( i = 0; i < contacts.Num(); i++ ) { + + r = contacts[i].point - massCenter; + + // calculate velocity at contact point + linearVelocity = inverseMass * current.i.linearMomentum; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + + // velocity along normal vector + normalVelocity = ( velocity * contacts[i].normal ) * contacts[i].normal; + + // calculate friction impulse + normal = -( velocity - normalVelocity ); + magnitude = normal.Normalize(); + impulseNumerator = contactFriction * magnitude; + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply friction impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if moving towards the surface at the contact point + if ( normalVelocity * contacts[i].normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross( impulse ); + } + } +} + +/* +================ +idPhysics_RigidBody::TestIfAtRest + + Returns true if the body is considered at rest. + Does not catch all cases where the body is at rest but is generally good enough. +================ +*/ +bool idPhysics_RigidBody::TestIfAtRest( void ) const { + int i; + float gv; + idVec3 v, av, normal, point; + idMat3 inverseWorldInertiaTensor; + idFixedWinding contactWinding; + + if ( current.atRest >= 0 ) { + return true; + } + + // need at least 3 contact points to come to rest + if ( contacts.Num() < 3 ) { + return false; + } + + // get average contact plane normal + normal.Zero(); + for ( i = 0; i < contacts.Num(); i++ ) { + normal += contacts[i].normal; + } + normal /= (float) contacts.Num(); + normal.Normalize(); + + // if on a too steep surface + if ( (normal * gravityNormal) > -0.7f ) { + return false; + } + + // create bounds for contact points + contactWinding.Clear(); + for ( i = 0; i < contacts.Num(); i++ ) { + // project point onto plane through origin orthogonal to the gravity + point = contacts[i].point - (contacts[i].point * gravityNormal) * gravityNormal; + contactWinding.AddToConvexHull( point, gravityNormal ); + } + + // need at least 3 contact points to come to rest + if ( contactWinding.GetNumPoints() < 3 ) { + return false; + } + + // center of mass in world space + point = current.i.position + centerOfMass * current.i.orientation; + point -= (point * gravityNormal) * gravityNormal; + + // if the point is not inside the winding + if ( !contactWinding.PointInside( gravityNormal, point, 0 ) ) { + return false; + } + + // linear velocity of body + v = inverseMass * current.i.linearMomentum; + // linear velocity in gravity direction + gv = v * gravityNormal; + // linear velocity orthogonal to gravity direction + v -= gv * gravityNormal; + + // if too much velocity orthogonal to gravity direction + if ( v.Length() > STOP_SPEED ) { + return false; + } + // if too much velocity in gravity direction + if ( gv > 2.0f * STOP_SPEED || gv < -2.0f * STOP_SPEED ) { + return false; + } + + // calculate rotational velocity + inverseWorldInertiaTensor = current.i.orientation * inverseInertiaTensor * current.i.orientation.Transpose(); + av = inverseWorldInertiaTensor * current.i.angularMomentum; + + // if too much rotational velocity + if ( av.LengthSqr() > STOP_SPEED ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_RigidBody::DropToFloorAndRest + + Drops the object straight down to the floor and verifies if the object is at rest on the floor. +================ +*/ +void idPhysics_RigidBody::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + + testSolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.i.position + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.i.position, down, clipModel, current.i.orientation, clipMask, self ); +// RAVEN END + current.i.position = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, current.i.orientation ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + // test if we are really at rest + EvaluateContacts(); + if ( !TestIfAtRest() ) { + gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + } + Rest(); + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + } +} + +/* +================ +idPhysics_RigidBody::DebugDraw +================ +*/ +void idPhysics_RigidBody::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showMass.GetBool() ) { +// RAVEN BEGIN +// bdube: draw center of mass at the center of mass + gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.i.position + centerOfMass * current.i.orientation, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +// RAVEN END + } + + if ( rb_showInertia.GetBool() ) { + idMat3 &I = inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + current.i.position, 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showVelocity.GetBool() ) { + DrawVelocity( clipModel->GetId(), 0.1f, 4.0f ); + } + +// RAVEN BEGIN +// bdube: added more debug info + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::idPhysics_RigidBody( void ) { + + // set default rigid body properties + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f ); + SetFriction( 0.6f, 0.6f, 0.0f ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + + current.i.position.Zero(); + current.i.orientation.Identity(); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + + saved = current; + + mass = 1.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + inverseInertiaTensor.Identity(); + + // use the least expensive euler integrator + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), RigidBodyDerivatives, this ); + + dropToFloor = false; + noImpact = false; + noContact = false; + + hasMaster = false; + isOrientated = false; + +#ifdef RB_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_RigidBody::~idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::~idPhysics_RigidBody( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } + delete integrator; +} + +/* +================ +idPhysics_RigidBody_SavePState +================ +*/ +void idPhysics_RigidBody_SavePState( idSaveGame *savefile, const rigidBodyPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteFloat( state.lastTimeStep ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteMat3( state.localAxis ); + savefile->Write( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->WriteVec3( state.externalForce ); + savefile->WriteVec3( state.externalTorque ); + + savefile->WriteVec3( state.i.position ); + savefile->WriteMat3( state.i.orientation ); + savefile->WriteVec3( state.i.linearMomentum ); + savefile->WriteVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody_RestorePState +================ +*/ +void idPhysics_RigidBody_RestorePState( idRestoreGame *savefile, rigidBodyPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadFloat( state.lastTimeStep ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadMat3( state.localAxis ); + savefile->Read( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->ReadVec3( state.externalForce ); + savefile->ReadVec3( state.externalTorque ); + + savefile->ReadVec3( state.i.position ); + savefile->ReadMat3( state.i.orientation ); + savefile->ReadVec3( state.i.linearMomentum ); + savefile->ReadVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody::Save +================ +*/ +void idPhysics_RigidBody::Save( idSaveGame *savefile ) const { + + idPhysics_RigidBody_SavePState( savefile, current ); + idPhysics_RigidBody_SavePState( savefile, saved ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( inverseMass ); + savefile->WriteVec3( centerOfMass ); + savefile->WriteMat3( inertiaTensor ); + savefile->WriteMat3( inverseInertiaTensor ); + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + savefile->WriteBool( noImpact ); + savefile->WriteBool( noContact ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::Restore +================ +*/ +void idPhysics_RigidBody::Restore( idRestoreGame *savefile ) { + + idPhysics_RigidBody_RestorePState( savefile, current ); + idPhysics_RigidBody_RestorePState( savefile, saved ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( inverseMass ); + savefile->ReadVec3( centerOfMass ); + savefile->ReadMat3( inertiaTensor ); + savefile->ReadMat3( inverseInertiaTensor ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + savefile->ReadBool( noImpact ); + savefile->ReadBool( noContact ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::SetClipModel +================ +*/ +#define MAX_INERTIA_SCALE 10.0f + +void idPhysics_RigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + int minIndex; + idMat3 inertiaScale; + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.i.position, current.i.orientation ); +// RAVEN END + + // get mass properties from the trace model + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // check whether or not the clip model has valid mass properties + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: invalid mass for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // check whether or not the inertia tensor is balanced + minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] ); + inertiaScale.Identity(); + inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex]; + inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex]; + inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex]; + + if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) { + gameLocal.DWarning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + float min = inertiaTensor[minIndex][minIndex] * MAX_INERTIA_SCALE; + inertiaScale[(minIndex+1)%3][(minIndex+1)%3] = min / inertiaTensor[(minIndex+1)%3][(minIndex+1)%3]; + inertiaScale[(minIndex+2)%3][(minIndex+2)%3] = min / inertiaTensor[(minIndex+2)%3][(minIndex+2)%3]; + inertiaTensor *= inertiaScale; + } + + inverseMass = 1.0f / mass; + inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f ); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); +} + +/* +================ +idPhysics_RigidBody::GetClipModel +================ +*/ +idClipModel *idPhysics_RigidBody::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_RigidBody::GetNumClipModels +================ +*/ +int idPhysics_RigidBody::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_RigidBody::SetMass +================ +*/ +void idPhysics_RigidBody::SetMass( float mass, int id ) { + assert( mass > 0.0f ); + inertiaTensor *= mass / this->mass; + inverseInertiaTensor = inertiaTensor.Inverse() * (1.0f / 6.0f); + this->mass = mass; + inverseMass = 1.0f / mass; +} + +/* +================ +idPhysics_RigidBody::GetMass +================ +*/ +float idPhysics_RigidBody::GetMass( int id ) const { + return mass; +} + +// RAVEN BEGIN +// bdube: means of getting center of mass +/* +================ +idPhysics_RigidBody::GetCenterMass +================ +*/ +idVec3 idPhysics_RigidBody::GetCenterMass( int id ) const +{ + return (current.i.position + centerOfMass * current.i.orientation); +} +// RAVEN END + +/* +================ +idPhysics_RigidBody::SetFriction +================ +*/ +void idPhysics_RigidBody::SetFriction( const float linear, const float angular, const float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_RigidBody::SetBouncyness +================ +*/ +void idPhysics_RigidBody::SetBouncyness( const float b ) { + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +idPhysics_RigidBody::Rest +================ +*/ +void idPhysics_RigidBody::Rest( void ) { + current.atRest = gameLocal.time; + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::DropToFloor +================ +*/ +void idPhysics_RigidBody::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +idPhysics_RigidBody::NoContact +================ +*/ +void idPhysics_RigidBody::NoContact( void ) { + noContact = true; +} + +/* +================ +idPhysics_RigidBody::Activate +================ +*/ +void idPhysics_RigidBody::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_RigidBody::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_RigidBody::EnableImpact +================ +*/ +void idPhysics_RigidBody::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_RigidBody::DisableImpact +================ +*/ +void idPhysics_RigidBody::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_RigidBody::SetContents +================ +*/ +void idPhysics_RigidBody::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_RigidBody::GetContents +================ +*/ +int idPhysics_RigidBody::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_RigidBody::GetBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_RigidBody::GetAbsBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_RigidBody::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { + rigidBodyPState_t next; + idAngles angles; + trace_t collision; + idVec3 impulse; + idEntity *ent; + idVec3 oldOrigin, masterOrigin; + idMat3 oldAxis, masterAxis; + float timeStep; + bool collided, cameToRest = false; + + timeStep = MS2SEC( timeStepMSec ); + current.lastTimeStep = timeStep; + + if ( hasMaster ) { + oldOrigin = current.i.position; + oldAxis = current.i.orientation; + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.i.orientation = current.localAxis * masterAxis; + } + else { + current.i.orientation = current.localAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + current.i.linearMomentum = mass * ( ( current.i.position - oldOrigin ) / timeStep ); + current.i.angularMomentum = inertiaTensor * ( ( current.i.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep ); + current.externalForce.Zero(); + current.externalTorque.Zero(); + + return ( current.i.position != oldOrigin || current.i.orientation != oldAxis ); + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + current.externalForce.Zero(); + current.externalTorque.Zero(); + return true; + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Start(); + } +#endif + + // move the rigid body velocity into the frame of a pusher +// current.i.linearMomentum -= current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum -= current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + + clipModel->Unlink(); + + next = current; + + // calculate next position and orientation + Integrate( timeStep, next ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions from the current to the next state + collided = CheckForCollisions( timeStep, next, collision ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // set the new state + current = next; + +// trace_t pushResults; +// gameLocal.push.ClipPush( pushResults, self, PUSHFL_CRUSH | PUSHFL_CLIP, saved.localOrigin, saved.localAxis, current.localOrigin, current.localAxis ); + + if ( collided ) { + // apply collision impulse + if ( CollisionImpulse( collision, impulse ) ) { + current.atRest = gameLocal.time; + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + DebugDraw(); + + if ( !noContact ) { + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + // get contacts + EvaluateContacts(); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // check if the body has come to rest + if ( TestIfAtRest() ) { + // put to rest + Rest(); + cameToRest = true; + } else { + // apply contact friction + ContactFriction( timeStep ); + } + } + + if ( current.atRest < 0 ) { + ActivateContactEntities(); + } + + if ( collided ) { + // if the rigid body didn't come to rest or the other entity is not at rest + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ( !cameToRest || !ent->IsAtRest() ) ) { + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + } + } + + // move the rigid body velocity back into the world frame +// current.i.linearMomentum += current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum += current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + current.pushVelocity.Zero(); + + current.lastTimeStep = timeStep; + current.externalForce.Zero(); + current.externalTorque.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Stop(); + + if ( rb_showTimings->integer == 1 ) { + gameLocal.Printf( "%12s: t %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + lastTimerReset = 0; + } + else if ( rb_showTimings->integer == 2 ) { + numRigidBodies++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "rb %d: t %1.4f cd %1.4f\n", + numRigidBodies, + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + } + } + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numRigidBodies = 0; + timer_total.Clear(); + timer_collision.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_RigidBody::UpdateTime +================ +*/ +void idPhysics_RigidBody::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_RigidBody::GetTime +================ +*/ +int idPhysics_RigidBody::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_RigidBody::GetImpactInfo +================ +*/ +void idPhysics_RigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + idVec3 linearVelocity, angularVelocity; + idMat3 inverseWorldInertiaTensor; + + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + + info->invMass = inverseMass; + info->invInertiaTensor = inverseWorldInertiaTensor; + info->position = point - ( current.i.position + centerOfMass * current.i.orientation ); + info->velocity = linearVelocity + angularVelocity.Cross( info->position ); +} + +/* +================ +idPhysics_RigidBody::ApplyImpulse +================ +*/ +void idPhysics_RigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.i.linearMomentum += impulse; + current.i.angularMomentum += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::AddForce +================ +*/ +void idPhysics_RigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + current.externalForce += force; + current.externalTorque += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::IsAtRest +================ +*/ +bool idPhysics_RigidBody::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_RigidBody::GetRestStartTime +================ +*/ +int idPhysics_RigidBody::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_RigidBody::IsPushable +================ +*/ +bool idPhysics_RigidBody::IsPushable( void ) const { + return ( !noImpact && !hasMaster ); +} + +/* +================ +idPhysics_RigidBody::SaveState +================ +*/ +void idPhysics_RigidBody::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_RigidBody::RestoreState +================ +*/ +void idPhysics_RigidBody::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void idPhysics_RigidBody::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + newOrigin * masterAxis; + } + else { + current.i.position = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void idPhysics_RigidBody::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.orientation = newAxis * masterAxis; + } + else { + current.i.orientation = newAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), clipModel->GetOrigin(), current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Move +================ +*/ +void idPhysics_RigidBody::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.i.position += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Rotate +================ +*/ +void idPhysics_RigidBody::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.i.orientation *= rotation.ToMat3(); + current.i.position *= rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + current.localOrigin = current.i.position; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetOrigin +================ +*/ +const idVec3 &idPhysics_RigidBody::GetOrigin( int id ) const { + return current.i.position; +} + +/* +================ +idPhysics_RigidBody::GetAxis +================ +*/ +const idMat3 &idPhysics_RigidBody::GetAxis( int id ) const { + return current.i.orientation; +} + +/* +================ +idPhysics_RigidBody::SetLinearVelocity +================ +*/ +void idPhysics_RigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.i.linearMomentum = newLinearVelocity * mass; + Activate(); +} + +/* +================ +idPhysics_RigidBody::SetAngularVelocity +================ +*/ +void idPhysics_RigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + current.i.angularMomentum = newAngularVelocity * inertiaTensor; + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + curLinearVelocity = current.i.linearMomentum * inverseMass; + return curLinearVelocity; +} + +/* +================ +idPhysics_RigidBody::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + curAngularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + return curAngularVelocity; +} + +/* +================ +idPhysics_RigidBody::ClipTranslation +================ +*/ +void idPhysics_RigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipRotation +================ +*/ +void idPhysics_RigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipContents +================ +*/ +int idPhysics_RigidBody::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::DisableClip +================ +*/ +void idPhysics_RigidBody::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_RigidBody::EnableClip +================ +*/ +void idPhysics_RigidBody::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_RigidBody::UnlinkClip +================ +*/ +void idPhysics_RigidBody::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_RigidBody::LinkClip +================ +*/ +void idPhysics_RigidBody::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::EvaluateContacts +================ +*/ +bool idPhysics_RigidBody::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0) = current.i.linearMomentum + current.lastTimeStep * gravityVector * mass; + dir.SubVec3(1) = current.i.angularMomentum; + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_RigidBody::SetPushed +================ +*/ +void idPhysics_RigidBody::SetPushed( int deltaTime ) { + idRotation rotation; + + rotation = ( saved.i.orientation * current.i.orientation ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( current.i.position - saved.i.position ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_RigidBody::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_RigidBody::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_RigidBody::SetMaster +================ +*/ +void idPhysics_RigidBody::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.i.orientation * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + } + hasMaster = true; + isOrientated = orientated; + ClearContacts(); + } + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +const float RB_VELOCITY_MAX = 16000; +const int RB_VELOCITY_TOTAL_BITS = 16; +const int RB_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_VELOCITY_MAX ) ) + 1; +const int RB_VELOCITY_MANTISSA_BITS = RB_VELOCITY_TOTAL_BITS - 1 - RB_VELOCITY_EXPONENT_BITS; +const float RB_MOMENTUM_MAX = 1e20f; +const int RB_MOMENTUM_TOTAL_BITS = 16; +const int RB_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_MOMENTUM_MAX ) ) + 1; +const int RB_MOMENTUM_MANTISSA_BITS = RB_MOMENTUM_TOTAL_BITS - 1 - RB_MOMENTUM_EXPONENT_BITS; +const float RB_FORCE_MAX = 1e20f; +const int RB_FORCE_TOTAL_BITS = 16; +const int RB_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_FORCE_MAX ) ) + 1; +const int RB_FORCE_MANTISSA_BITS = RB_FORCE_TOTAL_BITS - 1 - RB_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_RigidBody::WriteToSnapshot +================ +*/ +void idPhysics_RigidBody::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.i.position[0] ); + msg.WriteFloat( current.i.position[1] ); + msg.WriteFloat( current.i.position[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( current.i.linearMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.i.position[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.i.position[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.i.position[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); +} + +/* +================ +idPhysics_RigidBody::ReadFromSnapshot +================ +*/ +void idPhysics_RigidBody::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.atRest = msg.ReadLong(); + current.i.position[0] = msg.ReadFloat(); + current.i.position[1] = msg.ReadFloat(); + current.i.position[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.i.linearMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.i.position[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.i.position[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.i.position[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + } +} diff --git a/source/mpgame/physics/Physics_RigidBody.h b/source/mpgame/physics/Physics_RigidBody.h new file mode 100644 index 0000000..24d7587 --- /dev/null +++ b/source/mpgame/physics/Physics_RigidBody.h @@ -0,0 +1,172 @@ + +#ifndef __PHYSICS_RIGIDBODY_H__ +#define __PHYSICS_RIGIDBODY_H__ + +/* +=================================================================================== + + Rigid body physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct rididBodyIState_s { + idVec3 position; // position of trace model + idMat3 orientation; // orientation of trace model + idVec3 linearMomentum; // translational momentum relative to center of mass + idVec3 angularMomentum; // rotational momentum relative to center of mass +} rigidBodyIState_t; + +typedef struct rigidBodyPState_s { + int atRest; // set when simulation is suspended + float lastTimeStep; // length of last time step + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec6 pushVelocity; // push velocity + idVec3 externalForce; // external force relative to center of mass + idVec3 externalTorque; // external torque relative to center of mass + rigidBodyIState_t i; // state used for integration +} rigidBodyPState_t; + +class idPhysics_RigidBody : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( idPhysics_RigidBody ); + + idPhysics_RigidBody( void ); + ~idPhysics_RigidBody( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b ); + // same as above but drop to the floor first + void DropToFloor( void ); + // no contact determination and contact friction + void NoContact( void ); + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + // state of the rigid body + rigidBodyPState_t current; + rigidBodyPState_t saved; + +private: + + // rigid body properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + idClipModel * clipModel; // clip model used for collision detection + + // derived properties + float mass; // mass of body + float inverseMass; // 1 / mass + idVec3 centerOfMass; // center of mass of trace model + idMat3 inertiaTensor; // mass distribution + idMat3 inverseInertiaTensor; // inverse inertia tensor + + idODE * integrator; // integrator + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for solid when dropping to the floor + bool noImpact; // if true do not activate when another object collides + bool noContact; // if true do not determine contacts and no contact friction + + // master + bool hasMaster; + bool isOrientated; + +private: + friend void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); + void Integrate( const float deltaTime, rigidBodyPState_t &next ); + bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + bool CollisionImpulse( const trace_t &collision, idVec3 &impulse ); + void ContactFriction( float deltaTime ); + void DropToFloorAndRest( void ); + bool TestIfAtRest( void ) const; + void Rest( void ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_RIGIDBODY_H__ */ diff --git a/source/mpgame/physics/Physics_Static.cpp b/source/mpgame/physics/Physics_Static.cpp new file mode 100644 index 0000000..fdc3505 --- /dev/null +++ b/source/mpgame/physics/Physics_Static.cpp @@ -0,0 +1,882 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Static ) +END_CLASS + +/* +================ +idPhysics_Static::idPhysics_Static +================ +*/ +idPhysics_Static::idPhysics_Static( void ) { + self = NULL; + clipModel = NULL; + current.origin.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAxis.Identity(); + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Static::~idPhysics_Static +================ +*/ +idPhysics_Static::~idPhysics_Static( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + if ( clipModel ) { + delete clipModel; + } +} + +/* +================ +idPhysics_Static::Save +================ +*/ +void idPhysics_Static::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + + savefile->WriteVec3( current.origin ); + savefile->WriteMat3( current.axis ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Static::Restore +================ +*/ +void idPhysics_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadVec3( current.origin ); + savefile->ReadMat3( current.axis ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Static::SetSelf +================ +*/ +void idPhysics_Static::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Static::SetClipModel +================ +*/ +void idPhysics_Static::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + assert( self ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetClipModel +================ +*/ +idClipModel *idPhysics_Static::GetClipModel( int id ) const { + if ( clipModel ) { + return clipModel; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_Static::GetNumClipModels +================ +*/ +int idPhysics_Static::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Static::SetMass +================ +*/ +void idPhysics_Static::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Static::GetMass +================ +*/ +float idPhysics_Static::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Static::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Static::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Static::SetContents +================ +*/ +void idPhysics_Static::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Static::GetContents +================ +*/ +int idPhysics_Static::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Static::SetClipMask +================ +*/ +void idPhysics_Static::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_Static::GetClipMask +================ +*/ +int idPhysics_Static::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetBounds +================ +*/ +const idBounds &idPhysics_Static::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return bounds_zero; +} + +/* +================ +idPhysics_Static::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Static::GetAbsBounds( int id ) const { + static idBounds absBounds; + + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + absBounds[0] = absBounds[1] = current.origin; + return absBounds; +} + +/* +================ +idPhysics_Static::Evaluate +================ +*/ +bool idPhysics_Static::Evaluate( int timeStepMSec, int endTimeMSec ) { +// RAVEN BEGIN +// bdube: draw bbox + if ( hasMaster ) { + idVec3 masterOrigin; + idVec3 oldOrigin; + idMat3 masterAxis; + idMat3 oldAxis; +// RAVEN END + + oldOrigin = current.origin; + oldAxis = current.axis; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.axis = current.localAxis * masterAxis; + } else { + current.axis = current.localAxis; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); + } + return false; +} + +/* +================ +idPhysics_Static::UpdateTime +================ +*/ +void idPhysics_Static::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Static::GetTime +================ +*/ +int idPhysics_Static::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetImpactInfo +================ +*/ +void idPhysics_Static::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Static::ApplyImpulse +================ +*/ +void idPhysics_Static::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Static::AddForce +================ +*/ +void idPhysics_Static::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Static::Activate +================ +*/ +void idPhysics_Static::Activate( void ) { +} + +/* +================ +idPhysics_Static::PutToRest +================ +*/ +void idPhysics_Static::PutToRest( void ) { +} + +/* +================ +idPhysics_Static::IsAtRest +================ +*/ +bool idPhysics_Static::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Static::GetRestStartTime +================ +*/ +int idPhysics_Static::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::IsPushable +================ +*/ +bool idPhysics_Static::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_Static::IsInWater +================ +*/ +bool idPhysics_Static::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Static::SaveState +================ +*/ +void idPhysics_Static::SaveState( void ) { +} + +/* +================ +idPhysics_Static::RestoreState +================ +*/ +void idPhysics_Static::RestoreState( void ) { +} + +/* +================ +idPhysics_Static::SetOrigin +================ +*/ +void idPhysics_Static::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } else { + current.origin = newOrigin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::SetAxis +================ +*/ +void idPhysics_Static::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } else { + current.axis = newAxis; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Translate +================ +*/ +void idPhysics_Static::Translate( const idVec3 &translation, int id ) { + current.localOrigin += translation; + current.origin += translation; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Rotate +================ +*/ +void idPhysics_Static::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + current.axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + current.localOrigin = current.origin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetOrigin +================ +*/ +const idVec3 &idPhysics_Static::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Static::GetAxis +================ +*/ +const idMat3 &idPhysics_Static::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Static::SetLinearVelocity +================ +*/ +void idPhysics_Static::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Static::SetAngularVelocity +================ +*/ +void idPhysics_Static::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Static::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetGravity +================ +*/ +void idPhysics_Static::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_Static::GetGravity +================ +*/ +const idVec3 &idPhysics_Static::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_Static::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Static::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_Static::ClipTranslation +================ +*/ +void idPhysics_Static::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Translation( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::ClipRotation +================ +*/ +void idPhysics_Static::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, current.origin, rotation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Rotation( self, results, current.origin, rotation, clipModel, current.axis, MASK_SOLID, self ); + } +// RAVEN END +} + +/* +================ +idPhysics_Static::ClipContents +================ +*/ +int idPhysics_Static::ClipContents( const idClipModel *model ) const { + if ( clipModel ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } + } + return 0; +} + +/* +================ +idPhysics_Static::DisableClip +================ +*/ +void idPhysics_Static::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Static::EnableClip +================ +*/ +void idPhysics_Static::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Static::UnlinkClip +================ +*/ +void idPhysics_Static::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Static::LinkClip +================ +*/ +void idPhysics_Static::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::EvaluateContacts +================ +*/ +bool idPhysics_Static::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Static::GetNumContacts +================ +*/ +int idPhysics_Static::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetContact +================ +*/ +const contactInfo_t &idPhysics_Static::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_Static::ClearContacts +================ +*/ +void idPhysics_Static::ClearContacts( void ) { +} + +/* +================ +idPhysics_Static::AddContactEntity +================ +*/ +void idPhysics_Static::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::RemoveContactEntity +================ +*/ +void idPhysics_Static::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::HasGroundContacts +================ +*/ +bool idPhysics_Static::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundEntity +================ +*/ +bool idPhysics_Static::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundClipModel +================ +*/ +bool idPhysics_Static::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_Static::SetPushed +================ +*/ +void idPhysics_Static::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Static::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetMaster +================ +*/ +void idPhysics_Static::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.axis * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Static::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Static::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Static::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetLinearEndTime +================ +*/ +int idPhysics_Static::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetAngularEndTime +================ +*/ +int idPhysics_Static::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::WriteToSnapshot +================ +*/ +void idPhysics_Static::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.axis.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Static::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current.axis = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); +} diff --git a/source/mpgame/physics/Physics_Static.h b/source/mpgame/physics/Physics_Static.h new file mode 100644 index 0000000..0f92daf --- /dev/null +++ b/source/mpgame/physics/Physics_Static.h @@ -0,0 +1,155 @@ + +#ifndef __PHYSICS_STATIC_H__ +#define __PHYSICS_STATIC_H__ + +/* +=============================================================================== + + Physics for a non moving object using at most one collision model. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +class idStaticPState { + +public: + idVec3 origin; + idMat3 axis; + idVec3 localOrigin; + idMat3 localAxis; + + idStaticPState( void ) { } +}; +// RAVEN END + +class idPhysics_Static : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Static ); + + idPhysics_Static( void ); + ~idPhysics_Static( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idStaticPState current; // physics state +// RAVEN END + idClipModel * clipModel; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATIC_H__ */ diff --git a/source/mpgame/physics/Physics_StaticMulti.cpp b/source/mpgame/physics/Physics_StaticMulti.cpp new file mode 100644 index 0000000..7611b91 --- /dev/null +++ b/source/mpgame/physics/Physics_StaticMulti.cpp @@ -0,0 +1,1088 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_StaticMulti ) +END_CLASS + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +idStaticPState defaultState; +// RAVEN END + +/* +================ +idPhysics_StaticMulti::idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::idPhysics_StaticMulti( void ) { + self = NULL; + hasMaster = false; + isOrientated = false; + + defaultState.origin.Zero(); + defaultState.axis.Identity(); + defaultState.localOrigin.Zero(); + defaultState.localAxis.Identity(); + + current.SetNum( 1 ); + current[0] = defaultState; + clipModels.SetNum( 1 ); + clipModels[0] = NULL; +} + +/* +================ +idPhysics_StaticMulti::~idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::~idPhysics_StaticMulti( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + for ( int i = 0; i < clipModels.Num(); i++ ) { + delete clipModels[i]; + } +} + +/* +================ +idPhysics_StaticMulti::Save +================ +*/ +void idPhysics_StaticMulti::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + + savefile->WriteInt(current.Num()); + for ( i = 0; i < current.Num(); i++ ) { + savefile->WriteVec3( current[i].origin ); + savefile->WriteMat3( current[i].axis ); + savefile->WriteVec3( current[i].localOrigin ); + savefile->WriteMat3( current[i].localAxis ); + } + + savefile->WriteInt( clipModels.Num() ); + for ( i = 0; i < clipModels.Num(); i++ ) { + savefile->WriteClipModel( clipModels[i] ); + } + + savefile->WriteBool(hasMaster); + savefile->WriteBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::Restore +================ +*/ +void idPhysics_StaticMulti::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadInt(num); + current.AssureSize( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadVec3( current[i].origin ); + savefile->ReadMat3( current[i].axis ); + savefile->ReadVec3( current[i].localOrigin ); + savefile->ReadMat3( current[i].localAxis ); + } + + savefile->ReadInt(num); + clipModels.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadClipModel( clipModels[i] ); + } + + savefile->ReadBool(hasMaster); + savefile->ReadBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::SetSelf +================ +*/ +void idPhysics_StaticMulti::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_StaticMulti::RemoveIndex +================ +*/ +void idPhysics_StaticMulti::RemoveIndex( int id, bool freeClipModel ) { + if ( id < 0 || id >= clipModels.Num() ) { + return; + } + if ( clipModels[id] && freeClipModel ) { + delete clipModels[id]; + clipModels[id] = NULL; + } + clipModels.RemoveIndex( id ); + current.RemoveIndex( id ); +} + +/* +================ +idPhysics_StaticMulti::SetClipModel +================ +*/ +void idPhysics_StaticMulti::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + int i; + + assert( self ); + + if ( id >= clipModels.Num() ) { + current.AssureSize( id+1, defaultState ); + clipModels.AssureSize( id+1, NULL ); + } + + if ( clipModels[id] && clipModels[id] != model && freeOld ) { + delete clipModels[id]; + } + clipModels[id] = model; + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + + for ( i = clipModels.Num() - 1; i >= 1; i-- ) { + if ( clipModels[i] ) { + break; + } + } + current.SetNum( i+1, false ); + clipModels.SetNum( i+1, false ); +} + +/* +================ +idPhysics_StaticMulti::GetClipModel +================ +*/ +idClipModel *idPhysics_StaticMulti::GetClipModel( int id ) const { + if ( id >= 0 && id < clipModels.Num() && clipModels[id] ) { + return clipModels[id]; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_StaticMulti::GetNumClipModels +================ +*/ +int idPhysics_StaticMulti::GetNumClipModels( void ) const { + return clipModels.Num(); +} + +/* +================ +idPhysics_StaticMulti::SetMass +================ +*/ +void idPhysics_StaticMulti::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetMass +================ +*/ +float idPhysics_StaticMulti::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_StaticMulti::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_StaticMulti::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SetContents +================ +*/ +void idPhysics_StaticMulti::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + clipModels[id]->SetContents( contents ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->SetContents( contents ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetContents +================ +*/ +int idPhysics_StaticMulti::GetContents( int id ) const { + int i, contents = 0; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + contents = clipModels[id]->GetContents(); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + contents |= clipModels[i]->GetContents(); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::SetClipMask +================ +*/ +void idPhysics_StaticMulti::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetClipMask +================ +*/ +int idPhysics_StaticMulti::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetBounds( int id ) const { + int i; + static idBounds bounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetBounds(); + } + } + if ( id == -1 ) { + bounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds[0] -= clipModels[i]->GetOrigin(); + bounds[1] -= clipModels[i]->GetOrigin(); + break; + } + } + return bounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::GetAbsBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetAbsBounds(); + } + } + if ( id == -1 ) { + absBounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + absBounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + return absBounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::Evaluate +================ +*/ +bool idPhysics_StaticMulti::Evaluate( int timeStepMSec, int endTimeMSec ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin = masterOrigin + current[i].localOrigin * masterAxis; + if ( isOrientated ) { + current[i].axis = current[i].localAxis * masterAxis; + } else { + current[i].axis = current[i].localAxis; + } + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + + // FIXME: return false if master did not move + return true; + } + return false; +} + +/* +================ +idPhysics_StaticMulti::UpdateTime +================ +*/ +void idPhysics_StaticMulti::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_StaticMulti::GetTime +================ +*/ +int idPhysics_StaticMulti::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetImpactInfo +================ +*/ +void idPhysics_StaticMulti::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_StaticMulti::ApplyImpulse +================ +*/ +void idPhysics_StaticMulti::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_StaticMulti::AddForce +================ +*/ +void idPhysics_StaticMulti::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_StaticMulti::Activate +================ +*/ +void idPhysics_StaticMulti::Activate( void ) { +} + +/* +================ +idPhysics_StaticMulti::PutToRest +================ +*/ +void idPhysics_StaticMulti::PutToRest( void ) { +} + +/* +================ +idPhysics_StaticMulti::IsAtRest +================ +*/ +bool idPhysics_StaticMulti::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_StaticMulti::GetRestStartTime +================ +*/ +int idPhysics_StaticMulti::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::IsPushable +================ +*/ +bool idPhysics_StaticMulti::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_StaticMulti::IsInWater +================ +*/ +bool idPhysics_StaticMulti::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SaveState +================ +*/ +void idPhysics_StaticMulti::SaveState( void ) { +} + +/* +================ +idPhysics_StaticMulti::RestoreState +================ +*/ +void idPhysics_StaticMulti::RestoreState( void ) { +} + +/* +================ +idPhysics_StaticMulti::SetOrigin +================ +*/ +void idPhysics_StaticMulti::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].origin = masterOrigin + newOrigin * masterAxis; + } else { + current[id].origin = newOrigin; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + Translate( masterOrigin + masterAxis * newOrigin - current[0].origin ); + } else { + Translate( newOrigin - current[0].origin ); + } + } +} + +/* +================ +idPhysics_StaticMulti::SetAxis +================ +*/ +void idPhysics_StaticMulti::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].axis = newAxis * masterAxis; + } else { + current[id].axis = newAxis; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + idMat3 axis; + idRotation rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + axis = current[0].axis.Transpose() * ( newAxis * masterAxis ); + } else { + axis = current[0].axis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( current[0].origin ); + + Rotate( rotation ); + } +} + +/* +================ +idPhysics_StaticMulti::Translate +================ +*/ +void idPhysics_StaticMulti::Translate( const idVec3 &translation, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin += translation; + current[id].origin += translation; + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin += translation; + current[i].origin += translation; + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::Rotate +================ +*/ +void idPhysics_StaticMulti::Rotate( const idRotation &rotation, int id ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].origin *= rotation; + current[id].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].localAxis *= rotation.ToMat3(); + current[id].localOrigin = ( current[id].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[id].localAxis = current[id].axis; + current[id].localOrigin = current[id].origin; + } + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin *= rotation; + current[i].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[i].localAxis *= rotation.ToMat3(); + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + current[i].localOrigin = current[i].origin; + } + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetOrigin +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetOrigin( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].origin; + } + if ( clipModels.Num() ) { + return current[0].origin; + } else { + return vec3_origin; + } +} + +/* +================ +idPhysics_StaticMulti::GetAxis +================ +*/ +const idMat3 &idPhysics_StaticMulti::GetAxis( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].axis; + } + if ( clipModels.Num() ) { + return current[0].axis; + } else { + return mat3_identity; + } +} + +/* +================ +idPhysics_StaticMulti::SetLinearVelocity +================ +*/ +void idPhysics_StaticMulti::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::SetAngularVelocity +================ +*/ +void idPhysics_StaticMulti::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetGravity +================ +*/ +void idPhysics_StaticMulti::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_StaticMulti::GetGravity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_StaticMulti::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::ClipTranslation +================ +*/ +void idPhysics_StaticMulti::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipTranslation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipRotation +================ +*/ +void idPhysics_StaticMulti::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipRotation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipContents +================ +*/ +int idPhysics_StaticMulti::ClipContents( const idClipModel *model ) const { + int i, contents; + + contents = 0; + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + contents |= gameLocal.Contents( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, NULL ); +// RAVEN END + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::DisableClip +================ +*/ +void idPhysics_StaticMulti::DisableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Disable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::EnableClip +================ +*/ +void idPhysics_StaticMulti::EnableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Enable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::UnlinkClip +================ +*/ +void idPhysics_StaticMulti::UnlinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Unlink(); + } + } +} + +/* +================ +idPhysics_StaticMulti::LinkClip +================ +*/ +void idPhysics_StaticMulti::LinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } +} + +/* +================ +idPhysics_StaticMulti::EvaluateContacts +================ +*/ +bool idPhysics_StaticMulti::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_StaticMulti::GetNumContacts +================ +*/ +int idPhysics_StaticMulti::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetContact +================ +*/ +const contactInfo_t &idPhysics_StaticMulti::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_StaticMulti::ClearContacts +================ +*/ +void idPhysics_StaticMulti::ClearContacts( void ) { +} + +/* +================ +idPhysics_StaticMulti::AddContactEntity +================ +*/ +void idPhysics_StaticMulti::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::RemoveContactEntity +================ +*/ +void idPhysics_StaticMulti::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::HasGroundContacts +================ +*/ +bool idPhysics_StaticMulti::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundEntity +================ +*/ +bool idPhysics_StaticMulti::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundClipModel +================ +*/ +bool idPhysics_StaticMulti::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SetPushed +================ +*/ +void idPhysics_StaticMulti::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_StaticMulti::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetMaster +================ +*/ +void idPhysics_StaticMulti::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current[i].localAxis = current[i].axis * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + } + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_StaticMulti::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_StaticMulti::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetBlockingEntity +================ +*/ +idEntity *idPhysics_StaticMulti::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetLinearEndTime +================ +*/ +int idPhysics_StaticMulti::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetAngularEndTime +================ +*/ +int idPhysics_StaticMulti::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::WriteToSnapshot +================ +*/ +void idPhysics_StaticMulti::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat, localQuat; + + // TODO: Check that this conditional write to delta message is OK + msg.WriteByte( current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + quat = current[i].axis.ToCQuat(); + localQuat = current[i].localAxis.ToCQuat(); + + msg.WriteFloat( current[i].origin[0] ); + msg.WriteFloat( current[i].origin[1] ); + msg.WriteFloat( current[i].origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current[i].origin[0], current[i].localOrigin[0] ); + msg.WriteDeltaFloat( current[i].origin[1], current[i].localOrigin[1] ); + msg.WriteDeltaFloat( current[i].origin[2], current[i].localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + } +} + +/* +================ +idPhysics_StaticMulti::ReadFromSnapshot +================ +*/ +void idPhysics_StaticMulti::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat, localQuat; + + num = msg.ReadByte(); + assert( num == current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + current[i].origin[0] = msg.ReadFloat(); + current[i].origin[1] = msg.ReadFloat(); + current[i].origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current[i].localOrigin[0] = msg.ReadDeltaFloat( current[i].origin[0] ); + current[i].localOrigin[1] = msg.ReadDeltaFloat( current[i].origin[1] ); + current[i].localOrigin[2] = msg.ReadDeltaFloat( current[i].origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current[i].axis = quat.ToMat3(); + current[i].localAxis = localQuat.ToMat3(); + } +} diff --git a/source/mpgame/physics/Physics_StaticMulti.h b/source/mpgame/physics/Physics_StaticMulti.h new file mode 100644 index 0000000..26a12fa --- /dev/null +++ b/source/mpgame/physics/Physics_StaticMulti.h @@ -0,0 +1,143 @@ + +#ifndef __PHYSICS_STATICMULTI_H__ +#define __PHYSICS_STATICMULTI_H__ + +/* +=============================================================================== + + Physics for a non moving object using no or multiple collision models. + +=============================================================================== +*/ + +class idPhysics_StaticMulti : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_StaticMulti ); + + idPhysics_StaticMulti( void ); + ~idPhysics_StaticMulti( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void RemoveIndex( int id = 0, bool freeClipModel = true ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idList current; // physics state +// RAVEN END + idList clipModels; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATICMULTI_H__ */ diff --git a/source/mpgame/physics/Physics_VehicleMonster.cpp b/source/mpgame/physics/Physics_VehicleMonster.cpp new file mode 100644 index 0000000..df444fa --- /dev/null +++ b/source/mpgame/physics/Physics_VehicleMonster.cpp @@ -0,0 +1,37 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, rvPhysics_VehicleMonster ) +END_CLASS + +/* +================ +rvPhysics_VehicleMonster::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_VehicleMonster::Evaluate( int timeStepMSec, int endTimeMSec ) { + if ( idPhysics_RigidBody::Evaluate( timeStepMSec, endTimeMSec ) ) { + + idAngles euler = current.i.orientation.ToAngles(); + euler.pitch = 0.0f; + euler.roll = 0.0f; + current.i.orientation = euler.ToMat3(); + + return true; + } + + return false; +} + +void rvPhysics_VehicleMonster::SetGravity ( const idVec3 & v ) { + gravityVector = v; + gravityNormal = gameLocal.GetGravity( ); + gravityNormal.Normalize(); +} diff --git a/source/mpgame/physics/Physics_VehicleMonster.h b/source/mpgame/physics/Physics_VehicleMonster.h new file mode 100644 index 0000000..a74a0ae --- /dev/null +++ b/source/mpgame/physics/Physics_VehicleMonster.h @@ -0,0 +1,25 @@ + +#ifndef __PHYSICS_VEHICLE_MONSTER_H__ +#define __PHYSICS_VEHICLE_MONSTER_H__ + +/* +=================================================================================== + + Vehicle Monster Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + Extents particle physics with the ability to apply impulses. + +=================================================================================== +*/ + +class rvPhysics_VehicleMonster : public idPhysics_RigidBody { +public: + CLASS_PROTOTYPE( rvPhysics_VehicleMonster ); + + bool Evaluate ( int timeStepMSec, int endTimeMSec ); + void SetGravity ( const idVec3 & v ); +}; + +#endif /* !__PHYSICS_VEHICLE_MONSTER_H__ */ diff --git a/source/mpgame/physics/Push.cpp b/source/mpgame/physics/Push.cpp new file mode 100644 index 0000000..3908959 --- /dev/null +++ b/source/mpgame/physics/Push.cpp @@ -0,0 +1,1503 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +// RAVEN BEGIN +// bdube: projectile +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif +// RAVEN END + +/* +============ +idPush::InitSavingPushedEntityPositions +============ +*/ +void idPush::InitSavingPushedEntityPositions( void ) { + numPushed = 0; +} + +/* +============ +idPush::SaveEntityPosition +============ +*/ +void idPush::SaveEntityPosition( idEntity *ent ) { + int i; + + // if already saved the physics state for this entity + for ( i = 0; i < numPushed; i++ ) { + if ( pushed[i].ent == ent ) { + return; + } + } + + // don't overflow + if ( numPushed >= MAX_GENTITIES ) { + gameLocal.Error( "more than MAX_GENTITIES pushed entities" ); + return; + } + + pushed[numPushed].ent = ent; + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // save the delta view angles + pushed[numPushed].deltaViewAngles = static_cast(ent)->GetDeltaViewAngles(); + } + + // save the physics state + ent->GetPhysics()->SaveState(); + + numPushed++; +} + +/* +============ +idPush::RestorePushedEntityPositions +============ +*/ +void idPush::RestorePushedEntityPositions( void ) { + int i; + + for ( i = 0; i < numPushed; i++ ) { + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( pushed[i].ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // set back the delta view angles + static_cast(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles ); + } + + // restore the physics state + pushed[i].ent->GetPhysics()->RestoreState(); + } +} + +/* +============ +idPush::RotateEntityToAxial +============ +*/ +bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) { + int i; + trace_t trace; + idRotation rotation; + idMat3 axis; + idPhysics *physics; + + physics = ent->GetPhysics(); + axis = physics->GetAxis(); + if ( !axis.IsRotated() ) { + return true; + } + +// RAVEN BEGIN +// abahr: caching current upVector to reduce visual effects of fp error + idVec3 upVector = -physics->GetGravityNormal(); +// RAVEN END + + // try to rotate the bbox back to axial with at most four rotations + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + axis = physics->GetAxis()[2].ToMat3( 2 ); +// RAVEN END + rotation = axis.ToRotation(); + rotation.Scale( -1 ); + rotation.SetOrigin( rotationPoint ); + // tiny float numbers in the clip axis, this can get the entity stuck + if ( rotation.GetAngle() == 0.0f ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // + ent->GetPhysics()->ClipRotation( trace, rotation, NULL ); + // if the full rotation is possible + if ( trace.fraction >= 1.0f ) { + // set bbox in final axial position + physics->SetOrigin( trace.endpos ); +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // if partial rotation was possible + else if ( trace.fraction > 0.0f ) { + // partial rotation + physics->SetOrigin( trace.endpos ); + physics->SetAxis( trace.endAxis ); + } + // next rotate around collision point + rotationPoint = trace.c.point; + } + return false; +} + +#ifdef NEW_PUSH + +/* +============ +idPush::CanPushEntity +============ +*/ +bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) { + + // if the physics object is not pushable + if ( !ent->GetPhysics()->IsPushable() ) { + return false; + } + + // if the entity doesn't clip with this pusher + if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + return false; + } + + // don't push players in noclip mode + if ( ent->client && ent->client->noclip ) { + return false; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) { + return false; + } + + // if we shouldn't push entities the original pusher rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) { + return false; + } + } + + return true; +} + +/* +============ +idPush::AddEntityToPushedGroup +============ +*/ +void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) { + int i, j; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + if ( fraction > pushedGroup[i].fraction ) { + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact &= groundContact; + pushedGroup[i].test = true; + } + return; + } + else if ( fraction > pushedGroup[i].fraction ) { + for ( j = pushedGroupSize; j > i; j-- ) { + pushedGroup[j] = pushedGroup[j-1]; + } + break; + } + } + + // put the entity in the group + pushedGroupSize++; + pushedGroup[i].ent = ent; + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact = groundContact; + pushedGroup[i].test = true; + + // remove any further occurances of the same entity in the group + for ( i++; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + for ( j = i+1; j < pushedGroupSize; j++ ) { + pushedGroup[j-1] = pushedGroup[j]; + } + pushedGroupSize--; + break; + } + } +} + +/* +============ +idPush::IsFullyPushed +============ +*/ +bool idPush::IsFullyPushed( idEntity *ent ) { + int i; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( pushedGroup[i].fraction < 1.0f ) { + return false; + } + if ( ent == pushedGroup[i].ent ) { + return true; + } + } + return false; +} + +/* +============ +idPush::ClipTranslationAgainstPusher +============ +*/ +bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForTranslation +============ +*/ +int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( this, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by translating the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idVec3 realTranslation, partialTranslation; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = pusher->GetPhysics()->GetAxis(); + memset( results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip translation + pusher->GetPhysics()->ClipTranslation( results, translation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realTranslation = results.fraction * translation; + } + else { + realTranslation = translation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 0; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // translate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // translate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + } + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationAgainstPusher +============ +*/ +bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForRotation +============ +*/ +int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by rotating the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idRotation realRotation, partialRotation; + idMat3 oldAxis; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = newAxis; + memset( results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip rotation + pusher->GetPhysics()->ClipRotation( results, rotation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realRotation = results.fraction * rotation; + } + else { + realRotation = rotation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipRotation( results, realRotation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // rotate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // rotate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + pusher->GetPhysics()->GetClipModel(i)->Enable(); + } + // rotate any actors back to axial + for ( i = 1; i < pushedGroupSize; i++ ) { + // if the entity is using actor physics + if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) { + // don't allow rotation if the bbox is no longer axial + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + } + } + + return totalMass; +} + +#else /* !NEW_PUSH */ + +enum { + PUSH_NO, // not pushed + PUSH_OK, // pushed ok + PUSH_BLOCKED // blocked +}; + +/* +============ +idPush::ClipEntityRotation +============ +*/ +void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipRotation( trace, rotation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::ClipEntityTranslation +============ +*/ +void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipTranslation( trace, translation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::TryRotatePushEntity +============ +*/ +#ifdef _DEBUG +// #define ROTATIONAL_PUSH_DEBUG +#endif + +int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + trace_t trace; + idVec3 rotationPoint; + idRotation newRotation; + float checkAngle; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef ROTATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate the entity colliding with all other entities except the pusher itself + ClipEntityRotation( trace, check, NULL, clipModel, rotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle() * trace.fraction; + // test if the entity can stay at it's partly pushed position by rotating + // the entity in reverse only colliding with pusher + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) ); + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle(); + } + // point to rotate entity bbox around back to axial + rotationPoint = physics->GetOrigin(); + } + else { + // rotate entity in reverse only colliding with pusher + newRotation = rotation; + newRotation.Scale( -1 ); + // + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { +#ifdef ROTATIONAL_PUSH_DEBUG + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return PUSH_NO; + } + // get point to rotate bbox around back to axial + rotationPoint = results.c.point; + // angle along which the entity will be pushed + checkAngle = rotation.GetAngle() * (1.0f - results.fraction); + // rotate the entity colliding with all other entities except the pusher itself + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + ClipEntityRotation( trace, check, NULL, clipModel, newRotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + + SaveEntityPosition( check ); + + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + // NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in + // release builds moving less floats than it should + static float shit = checkAngle; + + newRotation.RotatePoint( rotationPoint ); + + // rotate the entity + physics->Rotate( newRotation ); + + // set pusher into final position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity uses actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( check, rotationPoint ) ) { + // don't allow rotation if the bbox is no longer axial + return PUSH_BLOCKED; + } + } + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity is an actor using actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idActor::GetClassType() ) && physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // if the entity is standing ontop of the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate actor view + idActor *actor = static_cast(check); + idAngles delta = actor->GetDeltaViewAngles(); + delta.yaw += newRotation.ToMat3()[0].ToYaw(); + actor->SetDeltaViewAngles( delta ); + } + } + + return PUSH_OK; +} + +/* +============ +idPush::TryTranslatePushEntity +============ +*/ +#ifdef _DEBUG +// #define TRANSLATIONAL_PUSH_DEBUG +#endif + +int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ) { + trace_t trace; + idVec3 checkMove; + idVec3 oldOrigin; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, move ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // vector along which the entity is pushed + checkMove = move * trace.fraction; + // test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // vector along which the entity is pushed + checkMove = move; + } + } + else { + // move entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -move ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { + return PUSH_NO; + } + // vector along which the entity is pushed + checkMove = move * (1.0f - results.fraction); + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, checkMove ); + // if there is a collisions + if ( trace.fraction < 1.0f ) { + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // FIXME: try to push the blocking entity as well ? + // FIXME: handle sliding along more than one collision plane ? + // FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map" + +/* + oldOrigin = physics->GetOrigin(); + + // movement still remaining + checkMove *= (1.0f - trace.fraction); + + // project the movement along the collision plane + if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) { + return PUSH_BLOCKED; + } + checkMove *= 1.001f; + + // move entity from collision point along the collision plane + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, NULL, NULL, checkMove ); + + if ( trace.fraction < 1.0f ) { + physics->SetOrigin( oldOrigin ); + return PUSH_BLOCKED; + } + + checkMove = trace.endpos - oldOrigin; + + // move entity in reverse only colliding with pusher + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, clipModel, NULL, -move ); + + physics->SetOrigin( oldOrigin ); +*/ + if ( trace.fraction < 1.0f ) { + return PUSH_BLOCKED; + } + } + } + + SaveEntityPosition( check ); + + // translate the entity + physics->Translate( checkMove ); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + return PUSH_OK; +} + +/* +============ +idPush::DiscardEntities +============ +*/ +int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) { + int i, num; + idEntity *check; + + // remove all entities we cannot or should not push from the list + for ( num = i = 0; i < numEntities; i++ ) { + check = entityList[ i ]; + + // if the physics object is not pushable + if ( !check->GetPhysics()->IsPushable() ) { + continue; + } + + // if the entity doesn't clip with this pusher + if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + continue; + } + + // don't push players in noclip mode +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idPlayer::GetClassType() ) && static_cast(check)->noclip ) { +// RAVEN END + continue; + } + + // if we should only push idMoveable entities +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + // if we shouldn't push entities the clip model rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) { + continue; + } + } + + // keep entity in list + entityList[ num++ ] = entityList[i]; + } + + return num; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + dir = translation; + dir.Normalize(); + dir.z += 1.0f; + dir *= 10.0f; + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( pusher, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipMove = results.endpos - clipModel->GetOrigin(); + clipOrigin = results.endpos; + + } + else { + + clipMove = translation; + clipOrigin = newOrigin; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldOrigin = clipModel->GetOrigin(); + + // try to push the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the translated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() ); +// RAVEN END + + // wake up this object + if ( flags & PUSHFL_APPLYIMPULSE ) { + impulse = physics->GetMass() * dir; + } else { + impulse.Zero(); + } + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // if the entity is a moveable item and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idMoveableItem::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idRotation clipRotation; + idMat3 clipAxis, oldAxis; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Rotation( pusher, results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipRotation = rotation * results.fraction; + clipAxis = results.endAxis; + } + else { + + clipRotation = rotation; + clipAxis = newAxis; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldAxis = clipModel->GetAxis(); + + // try to push all the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the rotated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis ); +// RAVEN END + + // wake up this object + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +#endif /* !NEW_PUSH */ + + +/* +============ +idPush::ClipPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ) { + idVec3 translation; + idRotation rotation; + float mass; + + mass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // translational push + translation = newOrigin - oldOrigin; + + // if the pusher translates + if ( translation != vec3_origin ) { + + mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newOrigin = oldOrigin; + } + + // rotational push + rotation = ( oldAxis.Transpose() * newAxis ).ToRotation(); + rotation.SetOrigin( newOrigin ); + rotation.Normalize180(); + rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors + + // if the pusher rotates + if ( rotation.GetAngle() != 0.0f ) { + + // recalculate new axis to avoid floating point rounding problems + newAxis = oldAxis * rotation.ToMat3(); + newAxis.OrthoNormalizeSelf(); + newAxis.FixDenormals(); + newAxis.FixDegeneracies(); + + pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis ); + + mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newAxis = oldAxis; + } + + return mass; +} diff --git a/source/mpgame/physics/Push.h b/source/mpgame/physics/Push.h new file mode 100644 index 0000000..b303089 --- /dev/null +++ b/source/mpgame/physics/Push.h @@ -0,0 +1,86 @@ + +#ifndef __PUSH_H__ +#define __PUSH_H__ + +/* +=============================================================================== + + Allows physics objects to be pushed geometrically. + +=============================================================================== +*/ + +#define PUSHFL_ONLYMOVEABLE 1 // only push moveable entities +#define PUSHFL_NOGROUNDENTITIES 2 // don't push entities the clip model rests upon +#define PUSHFL_CLIP 4 // also clip against all non-moveable entities +#define PUSHFL_CRUSH 8 // kill blocking entities +#define PUSHFL_APPLYIMPULSE 16 // apply impulse to pushed entities + +//#define NEW_PUSH + +class idPush { +public: + // Try to push other entities by moving the given entity. + // If results.fraction < 1.0 the move was blocked by results.c.entityNum + // Returns total mass of all pushed entities. + float ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + + float ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + + float ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ); + + // initialize saving the positions of entities being pushed + void InitSavingPushedEntityPositions( void ); + // move all pushed entities back to their previous position + void RestorePushedEntityPositions( void ); + // returns the number of pushed entities + int GetNumPushedEntities( void ) const { return numPushed; } + // get the ith pushed entity + idEntity * GetPushedEntity( int i ) const { assert( i >= 0 && i < numPushed ); return pushed[i].ent; } + +private: + struct pushed_s { + idEntity * ent; // pushed entity + idAngles deltaViewAngles; // actor delta view angles + } pushed[MAX_GENTITIES]; // pushed entities + int numPushed; // number of pushed entities + + struct pushedGroup_s { + idEntity * ent; + float fraction; + bool groundContact; + bool test; + } pushedGroup[MAX_GENTITIES]; + int pushedGroupSize; + +private: + void SaveEntityPosition( idEntity *ent ); + bool RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ); +#ifdef NEW_PUSH + bool CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ); + void AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ); + bool IsFullyPushed( idEntity *ent ); + bool ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ); + int GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ); + bool ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ); + int GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ); +#else + void ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idRotation &rotation ); + void ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idVec3 &translation ); + int TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + int TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + int DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ); +#endif +}; + +#endif /* !__PUSH_H__ */ diff --git a/source/mpgame/script/ScriptFuncUtility.cpp b/source/mpgame/script/ScriptFuncUtility.cpp new file mode 100644 index 0000000..15d5d2a --- /dev/null +++ b/source/mpgame/script/ScriptFuncUtility.cpp @@ -0,0 +1,469 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility* sfu ) { + Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility& sfu ) { + Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const char* source ) { + Init( source ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const idCmdArgs& args ) { + Init( args ); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const char* source ) { + Clear(); + + if( !source || !source[0] ) { + return SFU_NOFUNC; + } + + idStr::Split( source, parms, ' ' ); + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const idCmdArgs& args ) { + Clear(); + + //Start at index 1 so we ignore 'call' + for( int ix = 1; ix < args.Argc(); ++ix ) { + InsertString( args.Argv(ix), ix ); + } + + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init() { + assert( parms.Num() ); + + func = FindFunction( GetParm(0) ); + if( func ) { + RemoveIndex( 0 );// remove Function name + returnKey.Clear(); + } else if( parms.Num() >= 2 ) { + returnKey = GetParm( 0 ); + RemoveIndex( 0 );// remove key name + func = FindFunction( GetParm(0) ); + RemoveIndex( 0 );// remove Function name + } else { + gameLocal.Warning( "Unable to find function %s in rvScriptFuncUtility::Init\n", parms[0].c_str() ); + } + + return func != NULL ? SFU_OK : SFU_ERROR; +} + +/* +================ +rvScriptFuncUtility::Clear +================ +*/ +void rvScriptFuncUtility::Clear() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::Save +================ +*/ +void rvScriptFuncUtility::Save( idSaveGame *savefile ) const { + bool validFunc = GetFunc() != NULL; + savefile->WriteBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->WriteString( GetReturnKey() ); + + savefile->WriteString( GetFunc()->Name() ); + + savefile->WriteInt( NumParms() ); + for( int ix = 0; ix < NumParms(); ++ix ) { + savefile->WriteString( func->Name() ); + } +} + +/* +================ +rvScriptFuncUtility::Restore +================ +*/ +void rvScriptFuncUtility::Restore( idRestoreGame *savefile ) { + idStr value; + idStr element; + int numParms = 0; + bool validFunc = false; + + savefile->ReadBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadInt( numParms ); + for( int ix = 0; ix < numParms; ++ix ) { + savefile->ReadString( element ); + value += element; + value += ' '; + } + + value.StripTrailing( ' ' ); + sfuReturnType status = Init(value.c_str()); + if ( status != SFU_OK ) { + assert( 0 ); + } +} + +/* +================ +rvScriptFuncUtility::NumParms +================ +*/ +int rvScriptFuncUtility::NumParms() const { + return (func && func->type) ? func->type->NumParameters() : 0; +} + +/* +================ +rvScriptFuncUtility::ReturnsAVal +================ +*/ +bool rvScriptFuncUtility::ReturnsAVal() const { + return GetReturnType() != &type_void; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +idTypeDef* rvScriptFuncUtility::GetParmType( int index ) const { + return (func && func->type) ? func->type->GetParmType( index ) : NULL; +} + +/* +================ +rvScriptFuncUtility::GetReturnType +================ +*/ +idTypeDef* rvScriptFuncUtility::GetReturnType() const { + return (func && func->type) ? func->type->ReturnType() : &type_void; +} + +/* +================ +rvScriptFuncUtility::SetFunction +================ +*/ +void rvScriptFuncUtility::SetFunction( const function_t* func ) { + this->func = func; +} + +/* +================ +rvScriptFuncUtility::SetParms +================ +*/ +void rvScriptFuncUtility::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +const char* rvScriptFuncUtility::GetParm( int index ) const { + return parms[ index ].c_str(); +} + +/* +================ +rvScriptFuncUtility::GetFuncName +================ +*/ +const char* rvScriptFuncUtility::GetFuncName() const { + if( !func ) { + return NULL; + } + + return func->Name(); +} + +/* +================ +rvScriptFuncUtility::InsertInt +================ +*/ +void rvScriptFuncUtility::InsertInt( int parm, int index ) { + InsertString( va("%d", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertFloat +================ +*/ +void rvScriptFuncUtility::InsertFloat( float parm, int index ) { + InsertString( va("%f", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertVec3 +================ +*/ +void rvScriptFuncUtility::InsertVec3( const idVec3& parm, int index ) { + InsertString( parm.ToString(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertEntity +================ +*/ +void rvScriptFuncUtility::InsertEntity( const idEntity* parm, int index ) { + assert( parm ); + + InsertString( parm->GetName(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertString +================ +*/ +void rvScriptFuncUtility::InsertString( const char* parm, int index ) { + assert( parm ); + + parms.Insert( parm, index ); +} + +/* +================ +rvScriptFuncUtility::InsertBool +================ +*/ +void rvScriptFuncUtility::InsertBool( bool parm, int index ) { + InsertInt( (int)parm, index ); +} + +/* +================ +rvScriptFuncUtility::RemoveIndex +================ +*/ +void rvScriptFuncUtility::RemoveIndex( int index ) { + parms.RemoveIndex( index ); +} + +/* +================ +rvScriptFuncUtility::FindFunction +================ +*/ +const function_t* rvScriptFuncUtility::FindFunction( const char* name ) const { + idTypeDef* type = gameLocal.program.FindType( name ); + if( type ) {//Find based on type + return gameLocal.program.FindFunction( name, type ); + } + + //Find based on scope + return gameLocal.program.FindFunction( name ); +} + +/* +================ +rvScriptFuncUtility::CallFunc +================ +*/ +void rvScriptFuncUtility::CallFunc( idDict* returnDict ) const { + idTypeDef* type = NULL; + + if( !Valid() ) { + return; + } + + idThread* thread = new idThread(); + if( !thread ) { + return; + } + + thread->ClearStack(); + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + type->PushOntoStack( thread, GetParm(ix) ); + } + + thread->CallFunction( func, false ); + + if( thread->Execute() && returnDict && ReturnsAVal() ) { + returnDict->Set( GetReturnKey(), GetReturnType()->GetReturnedValAsString(gameLocal.program) ); + } +} + +/* +================ +rvScriptFuncUtility::Valid +================ +*/ +bool rvScriptFuncUtility::Valid() const { +//#if _DEBUG + idTypeDef* type = NULL; + + if( !GetFunc() ) { + return false; + } + + if( GetFunc()->eventdef ) { + gameLocal.Warning( "Function, %s, is an event\n", GetFunc()->Name() ); + return false; + } + + // FIXME: designers call functions with no parms even though parms are pushed on stack + //if( NumParms() != parms.Num() ) { + // gameLocal.Warning( "Number of parms doesn't equal the num required by function %s\n", func->Name() ); + // return false; + //} + + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + if( !type->IsValid( GetParm(ix) ) ) { + gameLocal.Warning( "(Func: %s) Parm '%s' doesn't match expected type '%s'\n", GetFunc()->Name(), GetParm(ix), type->Name() ); + return false; + } + } + + if( returnKey.Length() && !ReturnsAVal() ) { + gameLocal.Warning( "Expecting return val from function %s which doesn't return one\n", func->Name() ); + return false; + } +//#endif + + return true; +} + +/* +================ +rvScriptFuncUtility::Assign +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::Assign( const rvScriptFuncUtility* sfu ) { + assert( sfu ); + func = sfu->func; + parms = sfu->parms; + returnKey = sfu->returnKey; + + return *this; +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility* sfu ) { + return Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility& sfu ) { + return Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::IsEqualTo +================ +*/ +bool rvScriptFuncUtility::IsEqualTo( const rvScriptFuncUtility* sfu ) const { + assert( sfu ); + return GetFunc() == sfu->GetFunc(); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility* sfu ) const { + return IsEqualTo( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility& sfu ) const { + return IsEqualTo( &sfu ); +} diff --git a/source/mpgame/script/ScriptFuncUtility.h b/source/mpgame/script/ScriptFuncUtility.h new file mode 100644 index 0000000..0e89ebe --- /dev/null +++ b/source/mpgame/script/ScriptFuncUtility.h @@ -0,0 +1,71 @@ +#ifndef __RV_SCRIPT_FUNC_UTILITY_H +#define __RV_SCRIPT_FUNC_UTILITY_H + +class idThread; + +enum sfuReturnType { + SFU_NOFUNC = -1, + SFU_ERROR = 0, + SFU_OK = 1 +}; + +class rvScriptFuncUtility { +public: + rvScriptFuncUtility(); + explicit rvScriptFuncUtility( const rvScriptFuncUtility* sfu ); + explicit rvScriptFuncUtility( const rvScriptFuncUtility& sfu ); + explicit rvScriptFuncUtility( const char* source ); + explicit rvScriptFuncUtility( const idCmdArgs& args ); + + sfuReturnType Init( const char* source ); + sfuReturnType Init( const idCmdArgs& args ); + void Clear(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idTypeDef* GetParmType( int index ) const; + idTypeDef* GetReturnType() const ; + int NumParms() const; + bool ReturnsAVal() const; + + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ) { returnKey = key; } + + const char* GetFuncName() const; + const function_t* GetFunc() const { return func; } + const char* GetParm( int index ) const; + const char* GetReturnKey() const { return returnKey.c_str(); } + + void InsertInt( int parm, int index ); + void InsertFloat( float parm, int index ); + void InsertVec3( const idVec3& parm, int index ); + void InsertEntity( const idEntity* parm, int index ); + void InsertString( const char* parm, int index ); + void InsertBool( bool parm, int index ); + void RemoveIndex( int index ); + + const function_t* FindFunction( const char* name ) const; + void CallFunc( idDict* returnDict ) const; + + bool Valid() const; + + rvScriptFuncUtility& Assign( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility& sfu ); + + bool IsEqualTo( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility& sfu ) const; + +private: + sfuReturnType Init(); + +protected: + const function_t* func; + idList parms; + idStr returnKey; +}; + +#endif diff --git a/source/mpgame/script/Script_Compiler.cpp b/source/mpgame/script/Script_Compiler.cpp new file mode 100644 index 0000000..87b1389 --- /dev/null +++ b/source/mpgame/script/Script_Compiler.cpp @@ -0,0 +1,2643 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define FUNCTION_PRIORITY 2 +#define INT_PRIORITY 2 +#define NOT_PRIORITY 5 +#define TILDE_PRIORITY 5 +#define TOP_PRIORITY 7 + +bool idCompiler::punctuationValid[ 256 ]; +char *idCompiler::punctuation[] = { + "+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", "::", ";", ",", + "~", "!", "*", "/", "%", "(", ")", "-", "+", + "=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL +}; + +opcode_t idCompiler::opcodes[] = { + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float }, + + { "~", "COMP_F", -1, false, &def_float, &def_void, &def_float }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + { "%", "MOD_F", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float }, + { "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float }, + { "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float }, + { "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float }, + { "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float }, + { ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector }, + { ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string }, + { ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity }, + { ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean }, + { ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object }, + + { ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer }, + + { ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void }, + { ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void }, + { ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean }, + { "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object }, + { "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object }, + { "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + { "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string }, + { "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string }, + { "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean }, + { "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float }, + + { "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float }, + { "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector }, + { "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string }, + { "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity }, + { "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field }, + { "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean }, + { "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object }, + { "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object }, + + { "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string }, + { "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string }, + { "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string }, + { "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean }, + { "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void }, + { "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float }, + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + + { "", "NEG_F", -1, false, &def_float, &def_void, &def_float }, + { "", "NEG_V", -1, false, &def_vector, &def_void, &def_vector }, + + { "int", "INT_F", -1, false, &def_float, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_jumpoffset, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_argsize, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_argsize, &def_void }, + { "", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_float, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_string, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void }, + { "", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void }, + { "", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void }, + { "", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void }, + { "", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void }, + { "", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void }, + { "", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void }, + + { "", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { "", "BREAK", -1, false, &def_float, &def_void, &def_void }, + { "", "CONTINUE", -1, false, &def_float, &def_void, &def_void }, + + { NULL } +}; + +/* +================ +idCompiler::idCompiler() +================ +*/ +idCompiler::idCompiler() { + char **ptr; + int id; + + // make sure we have the right # of opcodes in the table + assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) ); + + eof = true; + parserPtr = &parser; + + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + basetype = NULL; + currentLineNumber = 0; + currentFileNumber = 0; + errorCount = 0; + console = false; + scope = &def_namespace; + + memset( &immediate, 0, sizeof( immediate ) ); + memset( punctuationValid, 0, sizeof( punctuationValid ) ); + for( ptr = punctuation; *ptr != NULL; ptr++ ) { + id = parserPtr->GetPunctuationId( *ptr ); + if ( ( id >= 0 ) && ( id < 256 ) ) { + punctuationValid[ id ] = true; + } + } +} + +/* +============ +idCompiler::Error + +Aborts the current file load +============ +*/ +void idCompiler::Error( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + throw idCompileError( string ); +} + +/* +============ +idCompiler::Warning + +Prints a warning about the current line +============ +*/ +void idCompiler::Warning( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + parserPtr->Warning( "%s", string ); +} + +/* +============ +idCompiler::VirtualFunctionConstant + +Creates a def for an index into a virtual function table +============ +*/ +ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr ); + if ( eval._int < 0 ) { +// RAVEN BEGIN +// bdube: added scope to print + if( !func->scope || !func->scope->initialized ) { + Error( "Function '%s' not found in uninitialized scope. Make sure function is an object method.", func->Name() ); + } else { + Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() ); + } +// RAVEN END + } + + return GetImmediate( &type_virtualfunction, &eval, "" ); +} + +/* +============ +idCompiler::SizeConstant + +Creates a def for a size constant +============ +*/ +ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = size; + return GetImmediate( &type_argsize, &eval, "" ); +} + +/* +============ +idCompiler::JumpConstant + +Creates a def for a jump constant +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = value; + return GetImmediate( &type_jumpoffset, &eval, "" ); +} + +/* +============ +idCompiler::JumpDef + +Creates a def for a relative jump from one code location to another +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) { + return JumpConstant( jumpto - jumpfrom ); +} + +/* +============ +idCompiler::JumpTo + +Creates a def for a relative jump from current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) { + return JumpDef( gameLocal.program.NumStatements(), jumpto ); +} + +/* +============ +idCompiler::JumpFrom + +Creates a def for a relative jump from code location to current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) { + return JumpDef( jumpfrom, gameLocal.program.NumStatements() ); +} + +/* +============ +idCompiler::Divide +============ +*/ +ID_INLINE float idCompiler::Divide( float numerator, float denominator ) { + if ( denominator == 0 ) { + Error( "Divide by zero" ); + return 0; + } + + return numerator / denominator; +} + +/* +============ +idCompiler::FindImmediate + +tries to find an existing immediate with the same value +============ +*/ +idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const { + idVarDef *def; + etype_t etype; + + etype = type->Type(); + + // check for a constant with the same value + for( def = gameLocal.program.GetDefList( "" ); def != NULL; def = def->Next() ) { + if ( def->TypeDef() != type ) { + continue; + } + + switch( etype ) { + case ev_field : + if ( *def->value.intPtr == eval->_int ) { + return def; + } + break; + + case ev_argsize : + if ( def->value.argSize == eval->_int ) { + return def; + } + break; + + case ev_jumpoffset : + if ( def->value.jumpOffset == eval->_int ) { + return def; + } + break; + + case ev_entity : + if ( *def->value.intPtr == eval->entity ) { + return def; + } + break; + + case ev_string : + if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) { + return def; + } + break; + + case ev_float : + if ( *def->value.floatPtr == eval->_float ) { + return def; + } + break; + + case ev_virtualfunction : + if ( def->value.virtualFunction == eval->_int ) { + return def; + } + break; + + + case ev_vector : + if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) && + ( def->value.vectorPtr->y == eval->vector[ 1 ] ) && + ( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) { + return def; + } + break; + + default : + Error( "weird immediate type" ); + break; + } + } + + return NULL; +} + +/* +============ +idCompiler::GetImmediate + +returns an existing immediate with the same value, or allocates a new one +============ +*/ +idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) { + idVarDef *def; + + def = FindImmediate( type, eval, string ); + if ( def ) { + def->numUsers++; + } else { + // allocate a new def + def = gameLocal.program.AllocDef( type, "", &def_namespace, true ); + if ( type->Type() == ev_string ) { + def->SetString( string, true ); + } else { + def->SetValue( *eval, true ); + } + } + + return def; +} + +/* +============ +idCompiler::OptimizeOpcode + +try to optimize when the operator works on constants only +============ +*/ +idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + eval_t c; + idTypeDef *type; + + if ( var_a && var_a->initialized != idVarDef::initializedConstant ) { + return NULL; + } + if ( var_b && var_b->initialized != idVarDef::initializedConstant ) { + return NULL; + } + + idVec3 &vec_c = *reinterpret_cast( &c.vector[ 0 ] ); + + memset( &c, 0, sizeof( c ) ); + switch( op - opcodes ) { + case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break; + case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break; + case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break; + case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break; + case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break; + case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break; + case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break; + case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break; + case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break; + case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break; + case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break; + case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break; + case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break; + case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break; + case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break; + case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break; + case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break; + case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break; + case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break; + case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break; + case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break; + case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break; + case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break; + case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break; + case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break; + case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break; + case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break; + case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break; + case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break; + case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break; + case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break; + case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break; + default: type = NULL; break; + } + + if ( !type ) { + return NULL; + } + + if ( var_a ) { + var_a->numUsers--; + if ( var_a->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_a, NULL ); + } + } + if ( var_b ) { + var_b->numUsers--; + if ( var_b->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_b, NULL ); + } + } + + return GetImmediate( type, &c, "" ); +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + statement_t *statement; + idVarDef *var_c; + + var_c = OptimizeOpcode( op, var_a, var_b ); + if ( var_c ) { + return var_c; + } + + if ( var_a && !idStr::Cmp( var_a->Name(), RESULT_STRING ) ) { + var_a->numUsers++; + } + if ( var_b && !idStr::Cmp( var_b->Name(), RESULT_STRING ) ) { + var_b->numUsers++; + } + + statement = gameLocal.program.AllocStatement(); + statement->linenumber = currentLineNumber; + statement->file = currentFileNumber; + + if ( ( op->type_c == &def_void ) || op->rightAssociative ) { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + } else { + // allocate result space + // try to reuse result defs as much as possible + var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b ); + // set user count back to 1, a result def needs to be used twice before it can be reused + var_c->numUsers = 1; + } + + statement->op = op - opcodes; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + + if ( op->rightAssociative ) { + return var_a; + } + + return var_c; +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) { + return EmitOpcode( &opcodes[ op ], var_a, var_b ); +} + +/* +============ +idCompiler::EmitPush + +Emits an opcode to push the variable onto the stack. +============ +*/ +bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) { + opcode_t *op; + opcode_t *out; + + out = NULL; + for( op = &opcodes[ OP_PUSH_F ]; op->name && !idStr::Cmp( op->name, "" ); op++ ) { + if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) { + out = op; + break; + } + } + + if ( !out ) { + if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) { + return false; + } + + out = &opcodes[ OP_PUSH_ENT ]; + } + + EmitOpcode( out, expression, 0 ); + + return true; +} + +/* +============== +idCompiler::NextToken + +Sets token, immediateType, and possibly immediate +============== +*/ +void idCompiler::NextToken( void ) { + int i; + + // reset our type + immediateType = NULL; + memset( &immediate, 0, sizeof( immediate ) ); + + // Save the token's line number and filename since when we emit opcodes the current + // token is always the next one to be read + currentLineNumber = token.line; + currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() ); + + if ( !parserPtr->ReadToken( &token ) ) { + eof = true; + return; + } + + if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) { + if ( ( braceDepth > 0 ) && ( token != "}" ) ) { + // missing a closing brace. try to give as much info as possible. + if ( scope->Type() == ev_function ) { + Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_object ) { + Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_namespace ) { + Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() ); + } else { + Error( "Unexpected end of file inside braced section" ); + } + } + } + + switch( token.type ) { + case TT_STRING: + // handle quoted strings as a unit + immediateType = &type_string; + return; + + case TT_LITERAL: { + // handle quoted vectors as a unit + immediateType = &type_vector; + idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS ); + idToken token2; + for( i = 0; i < 3; i++ ) { + if ( !lex.ReadToken( &token2 ) ) { + Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() ); + } + if ( token2.type == TT_PUNCTUATION && token2 == "-" ) { + if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) { + Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() ); + } + immediate.vector[ i ] = -token2.GetFloatValue(); + } else if ( token2.type == TT_NUMBER ) { + immediate.vector[ i ] = token2.GetFloatValue(); + } else { + Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() ); + } + } + return; + } + + case TT_NUMBER: + immediateType = &type_float; + immediate._float = token.GetFloatValue(); + return; + + case TT_PUNCTUATION: + // entity names + if ( token == "$" ) { + immediateType = &type_entity; + parserPtr->ReadToken( &token ); + return; + } + + if ( token == "{" ) { + braceDepth++; + return; + } + + if ( token == "}" ) { + braceDepth--; + return; + } + + if ( punctuationValid[ token.subtype ] ) { + return; + } + + Error( "Unknown punctuation '%s'", token.c_str() ); + break; + + case TT_NAME: + return; + + default: + Error( "Unknown token '%s'", token.c_str() ); + } +} + +/* +============= +idCompiler::ExpectToken + +Issues an Error if the current token isn't equal to string +Gets the next token +============= +*/ +void idCompiler::ExpectToken( const char *string ) { + if ( token != string ) { + Error( "expected '%s', found '%s'", string, token.c_str() ); + } + + NextToken(); +} + +/* +============= +idCompiler::CheckToken + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool idCompiler::CheckToken( const char *string ) { + if ( token != string ) { + return false; + } + + NextToken(); + + return true; +} + +/* +============ +idCompiler::ParseName + +Checks to see if the current token is a valid name +============ +*/ +void idCompiler::ParseName( idStr &name ) { + if ( token.type != TT_NAME ) { + Error( "'%s' is not a name", token.c_str() ); + } + + name = token; + NextToken(); +} + +/* +============ +idCompiler::SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void idCompiler::SkipOutOfFunction( void ) { + while( braceDepth ) { + parserPtr->SkipBracedSection( false ); + braceDepth--; + } + NextToken(); +} + +/* +============ +idCompiler::SkipToSemicolon + +For error recovery +============ +*/ +void idCompiler::SkipToSemicolon( void ) { + do { + if ( CheckToken( ";" ) ) { + return; + } + + NextToken(); + } while( !eof ); +} + +/* +============ +idCompiler::CheckType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::CheckType( void ) { + idTypeDef *type; + + if ( token == "float" ) { + type = &type_float; + } else if ( token == "vector" ) { + type = &type_vector; + } else if ( token == "entity" ) { + type = &type_entity; + } else if ( token == "string" ) { + type = &type_string; + } else if ( token == "void" ) { + type = &type_void; + } else if ( token == "object" ) { + type = &type_object; + } else if ( token == "boolean" ) { + type = &type_boolean; + } else if ( token == "namespace" ) { + type = &type_namespace; + } else if ( token == "scriptEvent" ) { + type = &type_scriptevent; + } else { + type = gameLocal.program.FindType( token.c_str() ); + if ( type && !type->Inherits( &type_object ) ) { + type = NULL; + } + } + + return type; +} + +/* +============ +idCompiler::ParseType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::ParseType( void ) { + idTypeDef *type; + + type = CheckType(); + if ( !type ) { + Error( "\"%s\" is not a type", token.c_str() ); + } + + if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) { + Error( "scriptEvents can only defined in the global namespace" ); + } + + if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) { + Error( "A namespace may only be defined globally, or within another namespace" ); + } + + NextToken(); + + return type; +} + +/* +============ +idCompiler::ParseImmediate + +Looks for a preexisting constant +============ +*/ +idVarDef *idCompiler::ParseImmediate( void ) { + idVarDef *def; + + def = GetImmediate( immediateType, &immediate, token.c_str() ); + NextToken(); + + return def; +} + +/* +============ +idCompiler::EmitFunctionParms +============ +*/ +idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) { + idVarDef *e; + const idTypeDef *type; + const idTypeDef *funcArg; + idVarDef *returnDef; + idTypeDef *returnType; + int arg; + int size; + int resultOp; + + type = func->TypeDef(); + if ( func->Type() != ev_function ) { + Error( "'%s' is not a function", func->Name() ); + } + + // copy the parameters to the global parameter variables + arg = startarg; + size = startsize; + if ( !CheckToken( ")" ) ) { + do { + if ( arg >= type->NumParameters() ) { + Error( "too many parameters" ); + } + + e = GetExpression( TOP_PRIORITY ); + + funcArg = type->GetParmType( arg ); + if ( !EmitPush( e, funcArg ) ) { + Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() ); + } + + if ( funcArg->Type() == ev_object ) { + size += type_object.Size(); + } else { + size += funcArg->Size(); + } + + arg++; + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + if ( arg < type->NumParameters() ) { + Error( "too few parameters for function '%s'", func->Name() ); + } + + if ( op == OP_CALL ) { + EmitOpcode( op, func, 0 ); + } else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) { + EmitOpcode( op, object, VirtualFunctionConstant( func ) ); + + // need arg size seperate since script object may be NULL + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = SizeConstant( func->value.functionPtr->parmTotal ); + } else { + EmitOpcode( op, func, SizeConstant( size ) ); + } + + // we need to copy off the result into a temporary result location, so figure out the opcode + returnType = type->ReturnType(); + if ( returnType->Type() == ev_string ) { + resultOp = OP_STORE_S; + returnDef = gameLocal.program.returnStringDef; + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + returnDef = gameLocal.program.returnDef; + + switch( returnType->Type() ) { + case ev_void : + resultOp = OP_STORE_F; + break; + + case ev_boolean : + resultOp = OP_STORE_BOOL; + break; + + case ev_float : + resultOp = OP_STORE_F; + break; + + case ev_vector : + resultOp = OP_STORE_V; + break; + + case ev_entity : + resultOp = OP_STORE_ENT; + break; + + case ev_object : + resultOp = OP_STORE_OBJ; + break; + + default : + Error( "Invalid return type for function '%s'", func->Name() ); + // shut up compiler + resultOp = OP_STORE_OBJ; + break; + } + } + + if ( returnType->Type() == ev_void ) { + // don't need result space since there's no result, so just return the normal result def. + return returnDef; + } + + // allocate result space + // try to reuse result defs as much as possible + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b ); + // set user count back to 0, a result def needs to be used twice before it can be reused + resultDef->numUsers = 0; + + EmitOpcode( resultOp, returnDef, resultDef ); + + return resultDef; +} + +/* +============ +idCompiler::ParseFunctionCall +============ +*/ +idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) { + assert( funcDef ); + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->initialized == idVarDef::uninitialized ) { + Error( "Function '%s' has not been defined yet", funcDef->GlobalName() ); + } + + assert( funcDef->value.functionPtr ); + if ( callthread ) { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + Error( "Built-in functions cannot be called as threads" ); + } + callthread = false; + return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL ); + } else { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + if ( !thisdef ) { + Error( "No 'self' within scope" ); + } + + return ParseEventCall( thisdef, funcDef ); + } else { + Error( "Built-in functions cannot be called without an object" ); + } + } + + return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL ); + } +} + +/* +============ +idCompiler::ParseObjectCall +============ +*/ +idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) { + EmitPush( object, object->TypeDef() ); + if ( callthread ) { + callthread = false; + return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object ); + } else { + return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object ); + } +} + +/* +============ +idCompiler::ParseEventCall +============ +*/ +idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + if ( object->Type() == ev_object ) { + EmitPush( object, &type_entity ); + } else { + EmitPush( object, object->TypeDef() ); + } + + return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL ); +} + +/* +============ +idCompiler::ParseSysObjectCall +============ +*/ +idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !idThread::GetClassType().RespondsTo( *funcDef->value.functionPtr->eventdef ) ) { +// RAVEN END + Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() ); + } + + return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL ); +} + +/* +============ +idCompiler::LookupDef +============ +*/ +idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) { + idVarDef *def; + idVarDef *field; + etype_t type_b; + etype_t type_c; + opcode_t *op; + + // check if we're accessing a field + if ( baseobj && ( baseobj->Type() == ev_object ) ) { + const idVarDef *tdef; + + def = NULL; + for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = gameLocal.program.GetDef( NULL, name, tdef ); + if ( def ) { + break; + } + } + } else { + // first look through the defs in our scope + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + // if we're in a member function, check types local to the object + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + + field = LookupDef( name, scope->scope->TypeDef()->def ); + if ( !field ) { + Error( "Unknown value \"%s\"", name ); + } + + // type check + type_b = field->Type(); + if ( field->Type() == ev_function ) { + type_c = field->TypeDef()->ReturnType()->Type(); + } else { + type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field + if ( CheckToken( "++" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for ++" ); + } + def = EmitOpcode( OP_UINCP_F, thisdef, field ); + return def; + } else if ( CheckToken( "--" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for --" ); + } + def = EmitOpcode( OP_UDECP_F, thisdef, field ); + return def; + } + } + + op = &opcodes[ OP_INDIRECT_F ]; + while( ( op->type_a->Type() != ev_object ) + || ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) && + ( type_c != op->type_c->Type() ) ) { + // catches object calls that return a value + break; + } + op++; + if ( !op->name || idStr::Cmp( op->name, "." ) ) { + Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() ); + } + } + + if ( ( op - opcodes ) == OP_OBJECTCALL ) { + ExpectToken( "(" ); + def = ParseObjectCall( thisdef, field ); + } else { + // emit the conversion opcode + def = EmitOpcode( op, thisdef, field ); + + // field access gets type from field + def->SetTypeDef( field->TypeDef()->FieldType() ); + } + } + } + } + + return def; +} + +/* +============ +idCompiler::ParseValue + +Returns the def for the current token +============ +*/ +idVarDef *idCompiler::ParseValue( void ) { + idVarDef *def; + idVarDef *namespaceDef; + idStr name; + + if ( immediateType == &type_entity ) { + // if an immediate entity ($-prefaced name) then create or lookup a def for it. + // when entities are spawned, they'll lookup the def and point it to them. + def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace ); + if ( !def ) { + def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true ); + } + NextToken(); + return def; + } else if ( immediateType ) { + // if the token is an immediate, allocate a constant for it + return ParseImmediate(); + } + + ParseName( name ); + def = LookupDef( name, basetype ); + if ( !def ) { + if ( basetype ) { + Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + // if namespace, then look up the variable in that namespace + } else if ( def->Type() == ev_namespace ) { + while( def->Type() == ev_namespace ) { + ExpectToken( "::" ); + ParseName( name ); + namespaceDef = def; + def = gameLocal.program.GetDef( NULL, name, namespaceDef ); + if ( !def ) { + Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() ); + } + } + //def = LookupDef( name, basetype ); + } + + return def; +} + +/* +============ +idCompiler::GetTerm +============ +*/ +idVarDef *idCompiler::GetTerm( void ) { + idVarDef *e; + int op; + + if ( !immediateType && CheckToken( "~" ) ) { + e = GetExpression( TILDE_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_COMP_F; + break; + + default : + Error( "type mismatch for ~" ); + + // shut up compiler + op = OP_COMP_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + if ( !immediateType && CheckToken( "!" ) ) { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_boolean : + op = OP_NOT_BOOL; + break; + + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_function : + Error( "Invalid type for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + + case ev_object : + op = OP_NOT_ENT; + break; + + default : + Error( "type mismatch for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + // check for negation operator + if ( !immediateType && CheckToken( "-" ) ) { + // constants are directly negated without an instruction + if ( immediateType == &type_float ) { + immediate._float = -immediate._float; + return ParseImmediate(); + } else if ( immediateType == &type_vector ) { + immediate.vector[0] = -immediate.vector[0]; + immediate.vector[1] = -immediate.vector[1]; + immediate.vector[2] = -immediate.vector[2]; + return ParseImmediate(); + } else { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_NEG_F; + break; + + case ev_vector : + op = OP_NEG_V; + break; + default : + Error( "type mismatch for -" ); + + // shut up compiler + op = OP_NEG_F; + break; + } + return EmitOpcode( &opcodes[ op ], e, 0 ); + } + } + + if ( CheckToken( "int" ) ) { + ExpectToken( "(" ); + + e = GetExpression( INT_PRIORITY ); + if ( e->Type() != ev_float ) { + Error( "type mismatch for int()" ); + } + + ExpectToken( ")" ); + + return EmitOpcode( OP_INT_F, e, 0 ); + } + + if ( CheckToken( "thread" ) ) { + callthread = true; + e = GetExpression( FUNCTION_PRIORITY ); + + if ( callthread ) { + Error( "Invalid thread call" ); + } + + // threads return the thread number + gameLocal.program.returnDef->SetTypeDef( &type_float ); + return gameLocal.program.returnDef; + } + + if ( !immediateType && CheckToken( "(" ) ) { + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +idCompiler::TypeMatches +============== +*/ +bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const { + if ( type1 == type2 ) { + return true; + } + + //if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) { + // return true; + //} + + //if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) { + // return true; + //} + + return false; +} + +/* +============== +idCompiler::GetExpression +============== +*/ +idVarDef *idCompiler::GetExpression( int priority ) { + opcode_t *op; + opcode_t *oldop; + idVarDef *e; + idVarDef *e2; + const idVarDef *oldtype; + etype_t type_a; + etype_t type_b; + etype_t type_c; + + if ( priority == 0 ) { + return GetTerm(); + } + + e = GetExpression( priority - 1 ); + if ( token == ";" ) { + // save us from searching through the opcodes unneccesarily + return e; + } + + while( 1 ) { + if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) { + return ParseFunctionCall( e ); + } + + // has to be a punctuation + if ( immediateType ) { + break; + } + + for( op = opcodes; op->name; op++ ) { + if ( ( op->priority == priority ) && CheckToken( op->name ) ) { + break; + } + } + + if ( !op->name ) { + // next token isn't at this priority level + break; + } + + // unary operators act only on the left operand + if ( op->type_b == &def_void ) { + e = EmitOpcode( op, e, 0 ); + return e; + } + + // preserve our base type + oldtype = basetype; + + // field access needs scope from object + if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) { + // save off what type this field is part of + basetype = e->TypeDef()->def; + } + + if ( op->rightAssociative ) { + // if last statement is an indirect, change it to an address of + if ( gameLocal.program.NumStatements() > 0 ) { + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) { + statement.op = OP_ADDRESS; + type_pointer.SetPointerType( e->TypeDef() ); + e->SetTypeDef( &type_pointer ); + } + } + + e2 = GetExpression( priority ); + } else { + e2 = GetExpression( priority - 1 ); + } + + // restore type + basetype = oldtype; + + // type check + type_a = e->Type(); + type_b = e2->Type(); + + // field access gets type from field + if ( op->name[ 0 ] == '.' ) { + if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) { + type_c = e2->TypeDef()->ReturnType()->Type(); + } else if ( e2->TypeDef()->FieldType() ) { + type_c = e2->TypeDef()->FieldType()->Type(); + } else { + // not a field + type_c = ev_error; + } + } else { + type_c = ev_void; + } + + oldop = op; + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) || + ( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) { + break; + } + + op++; + if ( !op->name || idStr::Cmp( op->name, oldop->name ) ) { + Error( "type mismatch for '%s'", oldop->name ); + } + } + + switch( op - opcodes ) { + case OP_SYSCALL : + ExpectToken( "(" ); + e = ParseSysObjectCall( e2 ); + break; + + case OP_OBJECTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + case OP_EVENTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + default: + if ( callthread ) { + Error( "Expecting function call after 'thread'" ); + } + + if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) { + // FIXME: need to make a general case for this + if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) { + // copy from float to boolean pointer + op = &opcodes[ OP_STOREP_FTOBOOL ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) { + // copy from boolean to float pointer + op = &opcodes[ OP_STOREP_BOOLTOF ]; + } else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from float to string pointer + op = &opcodes[ OP_STOREP_FTOS ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from boolean to string pointer + op = &opcodes[ OP_STOREP_BTOS ]; + } else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from vector to string pointer + op = &opcodes[ OP_STOREP_VTOS ]; + } else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) { + // store an entity into an object pointer + op = &opcodes[ OP_STOREP_OBJENT ]; + } else { + Error( "type mismatch for '%s'", op->name ); + } + } + + if ( op->rightAssociative ) { + e = EmitOpcode( op, e2, e ); + } else { + e = EmitOpcode( op, e, e2 ); + } + + if ( op - opcodes == OP_STOREP_OBJENT ) { + // statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = type_pointer.PointerType()->def; + } + + // field access gets type from field + if ( type_c != ev_void ) { + e->SetTypeDef( e2->TypeDef()->FieldType() ); + } + break; + } + } + + return e; +} + +/* +================ +idCompiler::PatchLoop +================ +*/ +void idCompiler::PatchLoop( int start, int continuePos ) { + int i; + statement_t *pos; + + pos = &gameLocal.program.GetStatement( start ); + for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_BREAK ) { + pos->op = OP_GOTO; + pos->a = JumpFrom( i ); + } else if ( pos->op == OP_CONTINUE ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, continuePos ); + } + } +} + +/* +================ +idCompiler::ParseReturnStatement +================ +*/ +void idCompiler::ParseReturnStatement( void ) { + idVarDef *e; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( CheckToken( ";" ) ) { + if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) { + Error( "expecting return value" ); + } + + EmitOpcode( OP_RETURN, 0, 0 ); + return; + } + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + type_a = e->Type(); + type_b = scope->TypeDef()->ReturnType()->Type(); + + if ( TypeMatches( type_a, type_b ) ) { + EmitOpcode( OP_RETURN, e, 0 ); + return; + } + + for( op = opcodes; op->name; op++ ) { + if ( !idStr::Cmp( op->name, "=" ) ) { + break; + } + } + + assert( op->name ); + + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) { + op++; + if ( !op->name || idStr::Cmp( op->name, "=" ) ) { + Error( "type mismatch for return value" ); + } + } + + idTypeDef *returnType = scope->TypeDef()->ReturnType(); + if ( returnType->Type() == ev_string ) { + EmitOpcode( op, e, gameLocal.program.returnStringDef ); + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + EmitOpcode( op, e, gameLocal.program.returnDef ); + } + EmitOpcode( OP_RETURN, 0, 0 ); +} + +/* +================ +idCompiler::ParseWhileStatement +================ +*/ +void idCompiler::ParseWhileStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + loopDepth++; + + ExpectToken( "(" ); + + patch2 = gameLocal.program.NumStatements(); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) { + //FIXME: we can completely skip generation of this code in the opposite case + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + } else { + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } + + // fixup breaks and continues + PatchLoop( patch2, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseForStatement + +Form of for statement with a counter: + + a = 0; +start: << patch4 + if ( !( a < 10 ) ) { + goto end; << patch1 + } else { + goto process; << patch3 + } + +increment: << patch2 + a = a + 1; + goto start; << goto patch4 + +process: + statements; + goto increment; << goto patch2 + +end: + +Form of for statement without a counter: + + a = 0; +start: << patch2 + if ( !( a < 10 ) ) { + goto end; << patch1 + } + +process: + statements; + goto start; << goto patch2 + +end: +================ +*/ +void idCompiler::ParseForStatement( void ) { + idVarDef *e; + int start; + int patch1; + int patch2; + int patch3; + int patch4; + + loopDepth++; + + start = gameLocal.program.NumStatements(); + + ExpectToken( "(" ); + + // init + if ( !CheckToken( ";" ) ) { + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ";" ); + } + + // condition + patch2 = gameLocal.program.NumStatements(); + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + // counter + if ( !CheckToken( ")" ) ) { + patch3 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IF, e, 0 ); + + patch4 = patch2; + patch2 = gameLocal.program.NumStatements(); + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + + // goto patch4 + EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 ); + + // fixup patch3 + gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 ); + } + + ParseStatement(); + + // goto patch2 + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + + // fixup patch1 + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + + // fixup breaks and continues + PatchLoop( start, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseDoWhileStatement +================ +*/ +void idCompiler::ParseDoWhileStatement( void ) { + idVarDef *e; + int patch1; + + loopDepth++; + + patch1 = gameLocal.program.NumStatements(); + ParseStatement(); + ExpectToken( "while" ); + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + ExpectToken( ";" ); + + EmitOpcode( OP_IF, e, JumpTo( patch1 ) ); + + // fixup breaks and continues + PatchLoop( patch1, patch1 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseIfStatement +================ +*/ +void idCompiler::ParseIfStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + ParseStatement(); + + if ( CheckToken( "else" ) ) { + patch2 = gameLocal.program.NumStatements(); + EmitOpcode( OP_GOTO, 0, 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + ParseStatement(); + gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 ); + } else { + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } +} + +/* +============ +idCompiler::ParseStatement +============ +*/ +void idCompiler::ParseStatement( void ) { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + if ( CheckToken( "{" ) ) { + do { + ParseStatement(); + } while( !CheckToken( "}" ) ); + + return; + } + + if ( CheckToken( "return" ) ) { + ParseReturnStatement(); + return; + } + + if ( CheckToken( "while" ) ) { + ParseWhileStatement(); + return; + } + + if ( CheckToken( "for" ) ) { + ParseForStatement(); + return; + } + + if ( CheckToken( "do" ) ) { + ParseDoWhileStatement(); + return; + } + + if ( CheckToken( "break" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot break outside of a loop" ); + } + EmitOpcode( OP_BREAK, 0, 0 ); + return; + } + + if ( CheckToken( "continue" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot contine outside of a loop" ); + } + EmitOpcode( OP_CONTINUE, 0, 0 ); + return; + } + + if ( CheckType() != NULL ) { + ParseDefs(); + return; + } + + if ( CheckToken( "if" ) ) { + ParseIfStatement(); + return; + } + + GetExpression( TOP_PRIORITY ); + ExpectToken(";"); +} + +/* +================ +idCompiler::ParseObjectDef +================ +*/ +void idCompiler::ParseObjectDef( const char *objname ) { + idTypeDef *objtype; + idTypeDef *type; + idTypeDef *parentType; + idTypeDef *fieldtype; + idStr name; + const char *fieldname; + idTypeDef newtype( ev_field, NULL, "", 0, NULL ); + idVarDef *oldscope; + int num; + int i; + + oldscope = scope; + if ( scope->Type() != ev_namespace ) { + Error( "Objects cannot be defined within functions or other objects" ); + } + + // make sure it doesn't exist before we create it + if ( gameLocal.program.FindType( objname ) != NULL ) { + Error( "'%s' : redefinition; different basic types", objname ); + } + + // base type + if ( !CheckToken( ":" ) ) { + parentType = &type_object; + } else { + parentType = ParseType(); + if ( !parentType->Inherits( &type_object ) ) { + Error( "Objects may only inherit from objects." ); + } + } + + objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType ); + objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true ); + scope = objtype->def; + + // inherit all the functions + num = parentType->NumFunctions(); + for( i = 0; i < parentType->NumFunctions(); i++ ) { + const function_t *func = parentType->GetFunction( i ); + objtype->AddFunction( func ); + } + + ExpectToken( "{" ); + + do { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + continue; + } + + fieldtype = ParseType(); + newtype.SetFieldType( fieldtype ); + + fieldname = va( "%s field", fieldtype->Name() ); + newtype.SetName( fieldname ); + + ParseName( name ); + + // check for a function prototype or declaraction + if ( CheckToken( "(" ) ) { + ParseFunctionDef( newtype.FieldType(), name ); + } else { + type = gameLocal.program.GetType( newtype, true ); + assert( !type->def ); + gameLocal.program.AllocDef( type, name, scope, true ); + objtype->AddField( type, name ); + ExpectToken( ";" ); + } + } while( !CheckToken( "}" ) ); + + scope = oldscope; + + ExpectToken( ";" ); +} + +/* +============ +idCompiler::ParseFunction + +parse a function type +============ +*/ +idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) { + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + idTypeDef *type; + + if ( scope->Type() != ev_namespace ) { + // create self pointer + newtype.AddFunctionParm( scope->TypeDef(), "self" ); + } + + if ( !CheckToken( ")" ) ) { + idStr parmName; + do { + type = ParseType(); + ParseName( parmName ); + newtype.AddFunctionParm( type, parmName ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + return gameLocal.program.GetType( newtype, true ); +} + +/* +================ +idCompiler::ParseFunctionDef +================ +*/ +void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) { + idTypeDef *type; + idVarDef *def; + const idVarDef *parm; + idVarDef *oldscope; + int i; + int numParms; + const idTypeDef *parmType; + function_t *func; + statement_t *pos; + + if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) { + Error( "Functions may not be defined within other functions" ); + } + + type = ParseFunction( returnType, name ); + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + type->def = def; + + func = &gameLocal.program.AllocFunction( def ); + if ( scope->TypeDef()->Inherits( &type_object ) ) { + scope->TypeDef()->AddFunction( func ); + } + } else { + func = def->value.functionPtr; + assert( func ); + if ( func->firstStatement ) { + Error( "%s redeclared", def->GlobalName() ); + } + } + +// RAVEN BEGIN +// abahr: moved to below parm size calc as per Jim D +// RAVEN END + + // calculate stack space used by parms + numParms = type->NumParameters(); +// RAVEN BEGIN +// abahr + func->parmTotal = 0; + func->parmSize.Clear(); +// RAVEN END + func->parmSize.SetNum( numParms ); + for( i = 0; i < numParms; i++ ) { + parmType = type->GetParmType( i ); + if ( parmType->Inherits( &type_object ) ) { + func->parmSize[ i ] = type_object.Size(); + } else { + func->parmSize[ i ] = parmType->Size(); + } + func->parmTotal += func->parmSize[ i ]; + } + +// RAVEN BEGIN +// abahr: moved here so prototypes calculate parm sizes + // check if this is a prototype or declaration + if ( !CheckToken( "{" ) ) { + // it's just a prototype, so get the ; and move on + ExpectToken( ";" ); + return; + } +// RAVEN END + + // define the parms + for( i = 0; i < numParms; i++ ) { + if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) { + Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) ); + } + parm = gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false ); + } + + oldscope = scope; + scope = def; + + func->firstStatement = gameLocal.program.NumStatements(); + + // check if we should call the super class constructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "init" ) ) { + idTypeDef *superClass; + function_t *constructorFunc = NULL; + + // find the superclass constructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) ); + if ( constructorFunc ) { + break; + } + } + + // emit the call to the constructor + if ( constructorFunc ) { + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 ); + } + } + + // parse regular statements + while( !CheckToken( "}" ) ) { + ParseStatement(); + } + + // check if we should call the super class destructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "destroy" ) ) { + idTypeDef *superClass; + function_t *destructorFunc = NULL; + + // find the superclass destructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) ); + if ( destructorFunc ) { + break; + } + } + + if ( destructorFunc ) { + // change all returns to point to the call to the destructor + pos = &gameLocal.program.GetStatement( func->firstStatement ); + for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_RETURN ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, gameLocal.program.NumStatements() ); + } + } + + // emit the call to the destructor + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 ); + } + } + +// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }" +#if 0 + // don't bother adding a return opcode if the "return" statement was used. + if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) { + // emit an end of statements opcode + EmitOpcode( OP_RETURN, 0, 0 ); + } +#else + // always emit the return opcode + EmitOpcode( OP_RETURN, 0, 0 ); +#endif + + // record the number of statements in the function + func->numStatements = gameLocal.program.NumStatements() - func->firstStatement; + + scope = oldscope; +} + +/* +================ +idCompiler::ParseVariableDef +================ +*/ +void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) { + idVarDef *def, *def2; + bool negate; + + def = gameLocal.program.GetDef( type, name, scope ); + if ( def ) { + Error( "%s redeclared", name ); + } + + def = gameLocal.program.AllocDef( type, name, scope, false ); + + // check for an initialization + if ( CheckToken( "=" ) ) { + // if a local variable in a function then write out interpreter code to initialize variable + if ( scope->Type() == ev_function ) { + def2 = GetExpression( TOP_PRIORITY ); + if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_F, def2, def ); + } else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_V, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) { + EmitOpcode( OP_STORE_S, def2, def ); + } else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) { + EmitOpcode( OP_STORE_ENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) { + EmitOpcode( OP_STORE_OBJENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) { + EmitOpcode( OP_STORE_OBJ, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOL, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_VTOS, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOBOOL, def2, def ); + } else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOLTOF, def2, def ); + } else { + Error( "bad initialization for '%s'", name ); + } + } else { + // global variables can only be initialized with immediate values + negate = false; + if ( token.type == TT_PUNCTUATION && token == "-" ) { + negate = true; + NextToken(); + if ( immediateType != &type_float ) { + Error( "wrong immediate type for '-' on variable '%s'", name ); + } + } + + if ( immediateType != type ) { + Error( "wrong immediate type for '%s'", name ); + } + + // global variables are initialized at start up + if ( type == &type_string ) { + def->SetString( token, false ); + } else { + if ( negate ) { + immediate._float = -immediate._float; + } + def->SetValue( immediate, false ); + } + NextToken(); + } + } else if ( type == &type_string ) { + // local strings on the stack are initialized in the interpreter + if ( scope->Type() != ev_function ) { + def->SetString( "", false ); + } + } else if ( type->Inherits( &type_object ) ) { + if ( scope->Type() != ev_function ) { + def->SetObject( NULL ); + } + } +} + +/* +================ +idCompiler::GetTypeForEventArg +================ +*/ +idTypeDef *idCompiler::GetTypeForEventArg( char argType ) { + idTypeDef *type; + + switch( argType ) { + case D_EVENT_INTEGER : + // this will get converted to int by the interpreter + type = &type_float; + break; + + case D_EVENT_FLOAT : + type = &type_float; + break; + + case D_EVENT_VECTOR : + type = &type_vector; + break; + + case D_EVENT_STRING : + type = &type_string; + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + type = &type_entity; + break; + + case D_EVENT_VOID : + type = &type_void; + break; + + case D_EVENT_TRACE : + // This data type isn't available from script + type = NULL; + break; + + default: + // probably a typo + type = NULL; + break; + } + + return type; +} + +/* +================ +idCompiler::ParseEventDef +================ +*/ +void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) { + const idTypeDef *expectedType; + idTypeDef *argType; + idTypeDef *type; + int i; + int num; + const char *format; + const idEventDef *ev; + idStr parmName; + + ev = idEventDef::FindEvent( name ); + if ( !ev ) { + Error( "Unknown event '%s'", name ); + } + + // set the return type + expectedType = GetTypeForEventArg( ev->GetReturnType() ); + if ( !expectedType ) { + Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name ); + } + if ( returnType != expectedType ) { + Error( "Return type doesn't match internal return type '%s'", expectedType->Name() ); + } + + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + + ExpectToken( "(" ); + + format = ev->GetArgFormat(); + num = strlen( format ); + for( i = 0; i < num; i++ ) { + expectedType = GetTypeForEventArg( format[ i ] ); + if ( !expectedType || ( expectedType == &type_void ) ) { + Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name ); + } + + argType = ParseType(); + ParseName( parmName ); + if ( argType != expectedType ) { + Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.", + i + 1, parmName.c_str(), expectedType->Name(), name ); + } + + newtype.AddFunctionParm( argType, "" ); + + if ( i < num - 1 ) { + if ( CheckToken( ")" ) ) { + Error( "Too few parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( "," ); + } + } + if ( !CheckToken( ")" ) ) { + Error( "Too many parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( ";" ); + + type = gameLocal.program.FindType( name ); + if ( type ) { + if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) { + Error( "Type mismatch on redefinition of '%s'", name ); + } + } else { + type = gameLocal.program.AllocType( newtype ); + type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true ); + + function_t &func = gameLocal.program.AllocFunction( type->def ); + func.eventdef = ev; + func.parmSize.SetNum( num ); + for( i = 0; i < num; i++ ) { + argType = newtype.GetParmType( i ); + func.parmTotal += argType->Size(); + func.parmSize[ i ] = argType->Size(); + } + + // mark the parms as local + func.locals = func.parmTotal; + } +} + +/* +================ +idCompiler::ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void idCompiler::ParseDefs( void ) { + idStr name; + idTypeDef *type; + idVarDef *def; + idVarDef *oldscope; + + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + type = ParseType(); + if ( type == &type_scriptevent ) { + type = ParseType(); + ParseName( name ); + ParseEventDef( type, name ); + return; + } + + ParseName( name ); + + if ( type == &type_namespace ) { + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + } + ParseNamespace( def ); + } else if ( CheckToken( "::" ) ) { + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + Error( "Unknown object name '%s'", name.c_str() ); + } + ParseName( name ); + oldscope = scope; + scope = def; + + ExpectToken( "(" ); + ParseFunctionDef( type, name.c_str() ); + scope = oldscope; + } else if ( type == &type_object ) { + ParseObjectDef( name.c_str() ); + } else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction + ParseFunctionDef( type, name.c_str() ); + } else { + ParseVariableDef( type, name.c_str() ); + while( CheckToken( "," ) ) { + ParseName( name ); + ParseVariableDef( type, name.c_str() ); + } + ExpectToken( ";" ); + } +} + +/* +================ +idCompiler::ParseNamespace + +Parses anything within a namespace definition +================ +*/ +void idCompiler::ParseNamespace( idVarDef *newScope ) { + idVarDef *oldscope; + + oldscope = scope; + if ( newScope != &def_namespace ) { + ExpectToken( "{" ); + } + + while( !eof ) { + scope = newScope; + callthread = false; + + if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) { + break; + } + + ParseDefs(); + } + + scope = oldscope; +} + +/* +============ +idCompiler::CompileFile + +compiles the 0 terminated text, adding definitions to the program structure +============ +*/ +void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) { + idStr orig = filename; + idTimer compile_time; + bool error; + + compile_time.Start(); + + scope = &def_namespace; + basetype = NULL; + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + currentLineNumber = 0; + console = toConsole; + + memset( &immediate, 0, sizeof( immediate ) ); + + parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS ); + parser.LoadMemory( text, strlen( text ), filename ); + parserPtr = &parser; + + // unread tokens to include script defines + token = SCRIPT_DEFAULTDEFS; + token.type = TT_STRING; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "include"; + token.type = TT_NAME; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "#"; + token.type = TT_PUNCTUATION; + token.subtype = P_PRECOMP; + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + // init the current token line to be the first line so that currentLineNumber is set correctly in NextToken + token.line = 1; + + error = false; + try { + // read first token + NextToken(); + while( !eof && !error ) { + // parse from global namespace + ParseNamespace( &def_namespace ); + } + } + + catch( idCompileError &err ) { + idStr error; + + if ( console ) { + // don't print line number of an error if were calling script from the console using the "script" command + sprintf( error, "Error: %s\n", err.error ); + } else { + sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.error ); + } + + parser.FreeSource(); + + throw idCompileError( error ); + } + + parser.FreeSource(); + + compile_time.Stop(); + if ( !toConsole ) { + gameLocal.Printf( "Compiled '%s': %.1f ms\n", orig.c_str(), compile_time.Milliseconds() ); + } +} diff --git a/source/mpgame/script/Script_Compiler.h b/source/mpgame/script/Script_Compiler.h new file mode 100644 index 0000000..3e12ee3 --- /dev/null +++ b/source/mpgame/script/Script_Compiler.h @@ -0,0 +1,251 @@ +#ifndef __SCRIPT_COMPILER_H__ +#define __SCRIPT_COMPILER_H__ + +const char * const RESULT_STRING = ""; + +typedef struct opcode_s { + char *name; + char *opname; + int priority; + bool rightAssociative; + idVarDef *type_a; + idVarDef *type_b; + idVarDef *type_c; +} opcode_t; + +// These opcodes are no longer necessary: +// OP_PUSH_OBJ: +// OP_PUSH_OBJENT: + +enum { + OP_RETURN, + + OP_UINC_F, + OP_UINCP_F, + OP_UDEC_F, + OP_UDECP_F, + OP_COMP_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_MOD_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_EO, + OP_EQ_OE, + OP_EQ_OO, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_EO, + OP_NE_OE, + OP_NE_OO, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_INDIRECT_F, + OP_INDIRECT_V, + OP_INDIRECT_S, + OP_INDIRECT_ENT, + OP_INDIRECT_BOOL, + OP_INDIRECT_OBJ, + + OP_ADDRESS, + + OP_EVENTCALL, + OP_OBJECTCALL, + OP_SYSCALL, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_BOOL, + OP_STORE_OBJENT, + OP_STORE_OBJ, + OP_STORE_ENTOBJ, + + OP_STORE_FTOS, + OP_STORE_BTOS, + OP_STORE_VTOS, + OP_STORE_FTOBOOL, + OP_STORE_BOOLTOF, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_BOOL, + OP_STOREP_OBJ, + OP_STOREP_OBJENT, + + OP_STOREP_FTOS, + OP_STOREP_BTOS, + OP_STOREP_VTOS, + OP_STOREP_FTOBOOL, + OP_STOREP_BOOLTOF, + + OP_UMUL_F, + OP_UMUL_V, + OP_UDIV_F, + OP_UDIV_V, + OP_UMOD_F, + OP_UADD_F, + OP_UADD_V, + OP_USUB_F, + OP_USUB_V, + OP_UAND_F, + OP_UOR_F, + + OP_NOT_BOOL, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + + OP_NEG_F, + OP_NEG_V, + + OP_INT_F, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_THREAD, + OP_OBJTHREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_OBJ, + OP_PUSH_OBJENT, + OP_PUSH_FTOS, + OP_PUSH_BTOF, + OP_PUSH_FTOB, + OP_PUSH_VTOS, + OP_PUSH_BTOS, + + OP_GOTO, + + OP_AND, + OP_AND_BOOLF, + OP_AND_FBOOL, + OP_AND_BOOLBOOL, + OP_OR, + OP_OR_BOOLF, + OP_OR_FBOOL, + OP_OR_BOOLBOOL, + + OP_BITAND, + OP_BITOR, + + OP_BREAK, // placeholder op. not used in final code + OP_CONTINUE, // placeholder op. not used in final code + + NUM_OPCODES +}; + +class idCompiler { +private: + static bool punctuationValid[ 256 ]; + static char *punctuation[]; + + idParser parser; + idParser *parserPtr; + idToken token; + + idTypeDef *immediateType; + eval_t immediate; + + bool eof; + bool console; + bool callthread; + int braceDepth; + int loopDepth; + int currentLineNumber; + int currentFileNumber; + int errorCount; + + idVarDef *scope; // the function being parsed, or NULL + const idVarDef *basetype; // for accessing fields + + float Divide( float numerator, float denominator ); + void Error( const char *error, ... ) const; + void Warning( const char *message, ... ) const; + idVarDef *OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ); + bool EmitPush( idVarDef *expression, const idTypeDef *funcArg ); + void NextToken( void ); + void ExpectToken( const char *string ); + bool CheckToken( const char *string ); + void ParseName( idStr &name ); + void SkipOutOfFunction( void ); + void SkipToSemicolon( void ); + idTypeDef *CheckType( void ); + idTypeDef *ParseType( void ); + idVarDef *FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const; + idVarDef *GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ); + idVarDef *VirtualFunctionConstant( idVarDef *func ); + idVarDef *SizeConstant( int size ); + idVarDef *JumpConstant( int value ); + idVarDef *JumpDef( int jumpfrom, int jumpto ); + idVarDef *JumpTo( int jumpto ); + idVarDef *JumpFrom( int jumpfrom ); + idVarDef *ParseImmediate( void ); + idVarDef *EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ); + idVarDef *ParseFunctionCall( idVarDef *func ); + idVarDef *ParseObjectCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseEventCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseSysObjectCall( idVarDef *func ); + idVarDef *LookupDef( const char *name, const idVarDef *baseobj ); + idVarDef *ParseValue( void ); + idVarDef *GetTerm( void ); + bool TypeMatches( etype_t type1, etype_t type2 ) const; + idVarDef *GetExpression( int priority ); + idTypeDef *GetTypeForEventArg( char argType ); + void PatchLoop( int start, int continuePos ); + void ParseReturnStatement( void ); + void ParseWhileStatement( void ); + void ParseForStatement( void ); + void ParseDoWhileStatement( void ); + void ParseIfStatement( void ); + void ParseStatement( void ); + void ParseObjectDef( const char *objname ); + idTypeDef *ParseFunction( idTypeDef *returnType, const char *name ); + void ParseFunctionDef( idTypeDef *returnType, const char *name ); + void ParseVariableDef( idTypeDef *type, const char *name ); + void ParseEventDef( idTypeDef *type, const char *name ); + void ParseDefs( void ); + void ParseNamespace( idVarDef *newScope ); + +public : + static opcode_t opcodes[]; + + idCompiler(); + void CompileFile( const char *text, const char *filename, bool console ); +}; + +#endif /* !__SCRIPT_COMPILER_H__ */ diff --git a/source/mpgame/script/Script_Interpreter.cpp b/source/mpgame/script/Script_Interpreter.cpp new file mode 100644 index 0000000..456fea8 --- /dev/null +++ b/source/mpgame/script/Script_Interpreter.cpp @@ -0,0 +1,1891 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idInterpreter::idInterpreter() +================ +*/ +idInterpreter::idInterpreter() { + localstackUsed = 0; + terminateOnExit = true; + debug = 0; + LastScriptVariable = 0; + memset( localstack, 0, sizeof( localstack ) ); + memset( callStack, 0, sizeof( callStack ) ); + Reset(); +} + +/* +================ +idInterpreter::Save +================ +*/ +void idInterpreter::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->WriteInt( callStack[i].s ); + if ( callStack[i].f ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( callStack[i].f ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( callStack[i].stackbase ); + } + savefile->WriteInt( maxStackDepth ); + + savefile->WriteInt( localstackUsed ); + savefile->Write( &localstack, localstackUsed ); + + savefile->WriteInt( localstackBase ); + savefile->WriteInt( maxLocalstackUsed ); + + if ( currentFunction ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( currentFunction ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( instructionPointer ); + + savefile->WriteInt( popParms ); + + // TOSAVE: idVarDef *LastScriptVariable; + if ( multiFrameEvent ) { + savefile->WriteString( multiFrameEvent->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteObject( eventEntity ); + + savefile->WriteObject( thread ); + + savefile->WriteBool( doneProcessing ); + savefile->WriteBool( threadDying ); + savefile->WriteBool( terminateOnExit ); + savefile->WriteBool( debug ); +} + +/* +================ +idInterpreter::Restore +================ +*/ +void idInterpreter::Restore( idRestoreGame *savefile ) { + int i; + idStr funcname; + int func_index; + + savefile->ReadInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->ReadInt( callStack[i].s ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + callStack[i].f = gameLocal.program.GetFunction( func_index ); + } else { + callStack[i].f = NULL; + } + + savefile->ReadInt( callStack[i].stackbase ); + } + savefile->ReadInt( maxStackDepth ); + + savefile->ReadInt( localstackUsed ); + savefile->Read( &localstack, localstackUsed ); + + savefile->ReadInt( localstackBase ); + savefile->ReadInt( maxLocalstackUsed ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + currentFunction = gameLocal.program.GetFunction( func_index ); + } else { + currentFunction = NULL; + } + savefile->ReadInt( instructionPointer ); + + savefile->ReadInt( popParms ); + + // TORESTORE: idVarDef *LastScriptVariable; + + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + multiFrameEvent = idEventDef::FindEvent( funcname ); + } + + savefile->ReadObject( reinterpret_cast( eventEntity ) ); + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadBool( doneProcessing ); + savefile->ReadBool( threadDying ); + savefile->ReadBool( terminateOnExit ); + savefile->ReadBool( debug ); +} + +/* +================ +idInterpreter::Reset +================ +*/ +void idInterpreter::Reset( void ) { + callStackDepth = 0; + localstackUsed = 0; + localstackBase = 0; + + maxLocalstackUsed = 0; + maxStackDepth = 0; + + popParms = 0; + multiFrameEvent = NULL; + eventEntity = NULL; + + currentFunction = 0; + NextInstruction( 0 ); + + threadDying = false; + doneProcessing = true; +} + +/* +================ +idInterpreter::GetRegisterValue + +Returns a string representation of the value of the register. This is +used primarily for the debugger and debugging + +//FIXME: This is pretty much wrong. won't access data in most situations. +================ +*/ +// RAVEN BEGIN +// bdube: took this over again +bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { + varEval_t reg; + idVarDef *d; + char funcObject[ 1024 ]; + char *funcName; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; + const idTypeDef *field; + const function_t *func; + + out.Empty(); + + if ( scopeDepth == -1 ) { + scopeDepth = callStackDepth; + } + + if ( scopeDepth == callStackDepth ) { + func = currentFunction; + } else { + func = callStack[ scopeDepth ].f; + } + if ( !func ) { + return false; + } + + idStr::Copynz( funcObject, func->Name(), sizeof( funcObject ) ); + funcName = strstr( funcObject, "::" ); + if ( funcName ) { + *funcName = '\0'; + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } + } else { + funcName = funcObject; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; + } + + if ( !scope ) + { + return false; + } + + d = gameLocal.program.GetDef( NULL, name, scope ); + + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; + } + } + } + + if ( !d ) + { + out = "???"; + return false; + } + + reg = GetVariable( d ); + switch( d->Type() ) { + case ev_float: + if ( reg.floatPtr ) { + out = va("%g", *reg.floatPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_vector: + if ( reg.vectorPtr ) { + out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); + } else { + out = "0,0,0"; + } + return true; + break; + + case ev_boolean: + if ( reg.intPtr ) { + out = va( "%d", *reg.intPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_field: + { + idEntity* entity; + idScriptObject* obj; + + if ( scope == &def_namespace ) { + // should never happen, but handle it safely anyway + return false; + } + + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { + return false; + } + + obj = &entity->scriptObject; + if ( !obj ) { + return false; + } + + switch ( field->Type() ) { + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } + + default: + return false; + } + + break; + } + + case ev_string: + if ( reg.stringPtr ) { + out = "\""; + out += reg.stringPtr; + out += "\""; + } else { + out = "\"\""; + } + return true; + + default: + return false; + } +} +// RAVEN END + +/* +================ +idInterpreter::GetCallstackDepth +================ +*/ +int idInterpreter::GetCallstackDepth( void ) const { + return callStackDepth; +} + +/* +================ +idInterpreter::GetCallstack +================ +*/ +const prstack_t *idInterpreter::GetCallstack( void ) const { + return &callStack[ 0 ]; +} + +/* +================ +idInterpreter::GetCurrentFunction +================ +*/ +const function_t *idInterpreter::GetCurrentFunction( void ) const { + return currentFunction; +} + +/* +================ +idInterpreter::GetThread +================ +*/ +idThread *idInterpreter::GetThread( void ) const { + return thread; +} + + +/* +================ +idInterpreter::SetThread +================ +*/ +void idInterpreter::SetThread( idThread *pThread ) { + thread = pThread; +} + +/* +================ +idInterpreter::CurrentLine +================ +*/ +int idInterpreter::CurrentLine( void ) const { + if ( instructionPointer < 0 ) { + return 0; + } + return gameLocal.program.GetLineNumberForStatement( instructionPointer ); +} + +/* +================ +idInterpreter::CurrentFile +================ +*/ +const char *idInterpreter::CurrentFile( void ) const { + if ( instructionPointer < 0 ) { + return ""; + } + return gameLocal.program.GetFilenameForStatement( instructionPointer ); +} + +/* +============ +idInterpreter::StackTrace +============ +*/ +void idInterpreter::StackTrace( void ) const { + const function_t *f; + int i; + int top; + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + return; + } + + top = callStackDepth; + if ( top >= MAX_STACK_DEPTH ) { + top = MAX_STACK_DEPTH - 1; + } + + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = top; i >= 0; i-- ) { + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } +} + +/* +============ +idInterpreter::Error + +Aborts the currently executing function +============ +*/ +void idInterpreter::Error( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + StackTrace(); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Error( "%s(%d): Thread '%s': %s\n", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Error( "Thread '%s': %s\n", thread->GetThreadName(), text ); + } +} + +/* +============ +idInterpreter::Warning + +Prints file and line number information with warning. +============ +*/ +void idInterpreter::Warning( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Warning( "%s(%d): Thread '%s': %s", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Warning( "Thread '%s' : %s", thread->GetThreadName(), text ); + } +} + +/* +================ +idInterpreter::DisplayInfo +================ +*/ +void idInterpreter::DisplayInfo( void ) const { + const function_t *f; + int i; + + gameLocal.Printf( " Stack depth: %d bytes, %d max\n", localstackUsed, maxLocalstackUsed ); + gameLocal.Printf( " Call depth: %d, %d max\n", callStackDepth, maxStackDepth ); + gameLocal.Printf( " Call Stack: " ); + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + } else { + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = callStackDepth; i > 0; i-- ) { + gameLocal.Printf( " " ); + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } + } +} + +/* +==================== +idInterpreter::ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void idInterpreter::ThreadCall( idInterpreter *source, const function_t *func, int args ) { + Reset(); + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( localstack, &source->localstack[ source->localstackUsed - args ], args ); +// RAVEN END + + localstackUsed = args; + localstackBase = 0; + + maxLocalstackUsed = localstackUsed; + EnterFunction( func, false ); + + thread->SetThreadName( currentFunction->Name() ); +} + +/* +================ +idInterpreter::EnterObjectFunction + +Calls a function on a script object. + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +================ +*/ +void idInterpreter::EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ) { + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + Push( self->entityNumber + 1 ); + EnterFunction( func, false ); +} + +/* +==================== +idInterpreter::EnterFunction + +Returns the new program statement counter + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +==================== +*/ +void idInterpreter::EnterFunction( const function_t *func, bool clearStack ) { + int c; + prstack_t *stack; + + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + + if ( callStackDepth >= MAX_STACK_DEPTH ) { + Error( "call stack overflow" ); + } + + stack = &callStack[ callStackDepth ]; + + stack->s = instructionPointer + 1; // point to the next instruction to execute + stack->f = currentFunction; + stack->stackbase = localstackBase; + + callStackDepth++; + if ( callStackDepth > maxStackDepth ) { + maxStackDepth = callStackDepth; + } + + if ( !func ) { + Error( "NULL function" ); + } + + if ( debug ) { + if ( currentFunction ) { + gameLocal.Printf( "%d: call '%s' from '%s'(line %d)%s\n", gameLocal.time, func->Name(), currentFunction->Name(), + gameLocal.program.GetStatement( instructionPointer ).linenumber, clearStack ? " clear stack" : "" ); + } else { + gameLocal.Printf( "%d: call '%s'%s\n", gameLocal.time, func->Name(), clearStack ? " clear stack" : "" ); + } + } + + currentFunction = func; + assert( !func->eventdef ); + NextInstruction( func->firstStatement ); + + // allocate space on the stack for locals + // parms are already on stack + c = func->locals - func->parmTotal; + assert( c >= 0 ); + + if ( localstackUsed + c > LOCALSTACK_SIZE ) { + Error( "EnterFuncton: locals stack overflow\n" ); + } + + // initialize local stack variables to zero + memset( &localstack[ localstackUsed ], 0, c ); + + localstackUsed += c; + localstackBase = localstackUsed - func->locals; + + if ( localstackUsed > maxLocalstackUsed ) { + maxLocalstackUsed = localstackUsed ; + } +} + +/* +==================== +idInterpreter::LeaveFunction +==================== +*/ +void idInterpreter::LeaveFunction( idVarDef *returnDef ) { + prstack_t *stack; + varEval_t ret; + + if ( callStackDepth <= 0 ) { + Error( "prog stack underflow" ); + } + + // return value + if ( returnDef ) { + switch( returnDef->Type() ) { + case ev_string : + gameLocal.program.ReturnString( GetString( returnDef ) ); + break; + + case ev_vector : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnVector( *ret.vectorPtr ); + break; + + default : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnInteger( *ret.intPtr ); + } + } + + // remove locals from the stack + PopParms( currentFunction->locals ); + assert( localstackUsed == localstackBase ); + + if ( debug ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + gameLocal.Printf( "%d: %s(%d): exit %s", gameLocal.time, gameLocal.program.GetFilename( line.file ), line.linenumber, currentFunction->Name() ); + if ( callStackDepth > 1 ) { + gameLocal.Printf( " return to %s(line %d)\n", callStack[ callStackDepth - 1 ].f->Name(), gameLocal.program.GetStatement( callStack[ callStackDepth - 1 ].s ).linenumber ); + } else { + gameLocal.Printf( " done\n" ); + } + } + + // up stack + callStackDepth--; + stack = &callStack[ callStackDepth ]; + currentFunction = stack->f; + localstackBase = stack->stackbase; + NextInstruction( stack->s ); + + if ( !callStackDepth ) { + // all done + doneProcessing = true; + threadDying = true; + currentFunction = 0; + } +} + +/* +================ +idInterpreter::CallEvent +================ +*/ +void idInterpreter::CallEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t var; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + var.intPtr = ( int * )&localstack[ start ]; + eventEntity = GetEntity( *var.entityNumberPtr ); + + if ( !eventEntity || !eventEntity->RespondsTo( *evdef ) ) { +// RAVEN BEGIN +// jshepard: added catch for entities that don't exist + if ( eventEntity && developer.GetBool() ) { + // give a warning in developer mode + Warning( "Function '%s' not supported on entity '%s'", evdef->GetName(), eventEntity->name.c_str() ); + } else if( !eventEntity ) { + //check the last script variable. + if(!!LastScriptVariable) { + //if it's $null_entity, then we should ignore this. + if(idStr::Cmp( "$null_entity", LastScriptVariable->Name()) ) { + Warning( "Entity '%s' is a null entity", LastScriptVariable->Name() ); + } + } else { + Warning( "Null entity referenced -- check entity name spelling."); + } + } +// RAVEN END + // always return a safe value when an object doesn't exist + switch( evdef->GetReturnType() ) { + case D_EVENT_INTEGER : + gameLocal.program.ReturnInteger( 0 ); + break; + + case D_EVENT_FLOAT : + gameLocal.program.ReturnFloat( 0 ); + break; + + case D_EVENT_VECTOR : + gameLocal.program.ReturnVector( vec3_zero ); + break; + + case D_EVENT_STRING : + gameLocal.program.ReturnString( "" ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + gameLocal.program.ReturnEntity( ( idEntity * )NULL ); + break; + + case D_EVENT_TRACE : + default: + // unsupported data type + break; + } + + PopParms( argsize ); + eventEntity = NULL; + return; + } + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = type_object.Size(); ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + var.intPtr = ( int * )&localstack[ start + pos ]; + data[ i ] = int( *var.floatPtr ); + break; + + case D_EVENT_FLOAT : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( float * )&data[ i ] ) = *var.floatPtr; + break; + + case D_EVENT_VECTOR : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idVec3 ** )&data[ i ] ) = var.vectorPtr; + break; + + case D_EVENT_STRING : + ( *( const char ** )&data[ i ] ) = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + if ( !( *( idEntity ** )&data[ i ] ) ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + eventEntity->ProcessEventArgPtr( evdef, data ); + if ( !multiFrameEvent ) { + if ( popParms ) { + PopParms( popParms ); + } + eventEntity = NULL; + } else { + doneProcessing = true; + } + popParms = 0; +} + +/* +================ +idInterpreter::BeginMultiFrameEvent +================ +*/ +bool idInterpreter::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( eventEntity != ent ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong entity" ); + } + if ( multiFrameEvent ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong event" ); + } + return false; + } + + multiFrameEvent = event; + return true; +} + +/* +================ +idInterpreter::EndMultiFrameEvent +================ +*/ +void idInterpreter::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::EndMultiFrameEvent called with wrong event" ); + } + + multiFrameEvent = NULL; +} + +/* +================ +idInterpreter::MultiFrameEventInProgress +================ +*/ +bool idInterpreter::MultiFrameEventInProgress( void ) const { + return multiFrameEvent != NULL; +} + +/* +================ +idInterpreter::CallSysEvent +================ +*/ +void idInterpreter::CallSysEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t source; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = 0; ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( int * )&data[ i ] = int( *source.floatPtr ); + break; + + case D_EVENT_FLOAT : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( float * )&data[ i ] = *source.floatPtr; + break; + + case D_EVENT_VECTOR : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idVec3 ** )&data[ i ] = source.vectorPtr; + break; + + case D_EVENT_STRING : + *( const char ** )&data[ i ] = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + if ( !*( idEntity ** )&data[ i ] ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + thread->ProcessEventArgPtr( evdef, data ); + if ( popParms ) { + PopParms( popParms ); + } + popParms = 0; +} + +/* +==================== +idInterpreter::Execute +==================== +*/ +bool idInterpreter::Execute( void ) { + varEval_t var_a; + varEval_t var_b; + varEval_t var_c; + varEval_t var; + statement_t *st; + int runaway; + idThread *newThread; + float floatVal; + idScriptObject *obj; + const function_t *func; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_SCRIPT); +// RAVEN END + + if ( threadDying || !currentFunction ) { + return true; + } + + if ( multiFrameEvent ) { + // move to previous instruction and call it again + instructionPointer--; + } + + runaway = 5000000; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) { + instructionPointer++; + + if ( !--runaway ) { + Error( "runaway loop error" ); + } + + // next statement + st = &gameLocal.program.GetStatement( instructionPointer ); + +// RAVEN BEGIN +// bdube: if the debugger is running then we need to check to see if any breakpoints have beeng hit + if ( gameLocal.editors & EDITOR_DEBUGGER ) { + common->DebuggerCheckBreakpoint ( this, &gameLocal.program, instructionPointer ); + } else if ( g_debugScript.GetBool ( ) ) { + static int lastLineNumber = -1; + if ( lastLineNumber != gameLocal.program.GetStatement ( instructionPointer ).linenumber ) { + gameLocal.Printf ( "%s (%d)\n", + gameLocal.program.GetFilename ( gameLocal.program.GetStatement ( instructionPointer ).file ), + gameLocal.program.GetStatement ( instructionPointer ).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement ( instructionPointer ).linenumber; + } + } +// RAVEN END + + switch( st->op ) { + case OP_RETURN: + LeaveFunction( st->a ); + break; + + case OP_THREAD: + newThread = new idThread( this, st->a->value.functionPtr, st->b->value.argSize ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + PopParms( st->b->value.argSize ); + break; + + case OP_OBJTHREAD: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + assert( st->c->value.argSize == func->parmTotal ); + newThread = new idThread( this, GetEntity( *var_a.entityNumberPtr ), func, func->parmTotal ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + } else { + // return a null thread to the script + gameLocal.program.ReturnFloat( 0.0f ); + } + PopParms( st->c->value.argSize ); + break; + + case OP_CALL: + EnterFunction( st->a->value.functionPtr, false ); + break; + + case OP_EVENTCALL: + CallEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_OBJECTCALL: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + EnterFunction( func, false ); + } else { + // return a 'safe' value + gameLocal.program.ReturnVector( vec3_zero ); + gameLocal.program.ReturnString( "" ); + PopParms( st->c->value.argSize ); + } + break; + + case OP_SYSCALL: + CallSysEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_IFNOT: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr == 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_IF: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr != 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_GOTO: + NextInstruction( instructionPointer + st->a->value.jumpOffset ); + break; + + case OP_ADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr + *var_b.floatPtr; + break; + + case OP_ADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr + *var_b.vectorPtr; + break; + + case OP_ADD_S: + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_FS: + var_a = GetVariable( st->a ); + SetString( st->c, FloatToString( *var_a.floatPtr ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SF: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, FloatToString( *var_b.floatPtr ) ); + break; + + case OP_ADD_VS: + var_a = GetVariable( st->a ); + SetString( st->c, var_a.vectorPtr->ToString() ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SV: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, var_b.vectorPtr->ToString() ); + break; + + case OP_SUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr - *var_b.floatPtr; + break; + + case OP_SUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr - *var_b.vectorPtr; + break; + + case OP_MUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr * *var_b.floatPtr; + break; + + case OP_MUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.vectorPtr * *var_b.vectorPtr; + break; + + case OP_MUL_FV: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.floatPtr * *var_b.vectorPtr; + break; + + case OP_MUL_VF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr * *var_b.floatPtr; + break; + + case OP_DIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = idMath::INFINITY; + } else { + *var_c.floatPtr = *var_a.floatPtr / *var_b.floatPtr; + } + break; + + case OP_MOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable ( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = *var_a.floatPtr; + } else { + *var_c.floatPtr = static_cast( *var_a.floatPtr ) % static_cast( *var_b.floatPtr ); + } + break; + + case OP_BITAND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) & static_cast( *var_b.floatPtr ); + break; + + case OP_BITOR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) | static_cast( *var_b.floatPtr ); + break; + + case OP_GE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr >= *var_b.floatPtr ); + break; + + case OP_LE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr <= *var_b.floatPtr ); + break; + + case OP_GT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr > *var_b.floatPtr ); + break; + + case OP_LT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr < *var_b.floatPtr ); + break; + + case OP_AND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.intPtr != 0 ); + break; + + case OP_AND_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.intPtr != 0 ); + break; + + case OP_OR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.intPtr != 0 ); + break; + + case OP_OR_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.intPtr != 0 ); + break; + + case OP_NOT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr == 0 ); + break; + + case OP_NOT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == 0.0f ); + break; + + case OP_NOT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == vec3_zero ); + break; + + case OP_NOT_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( strlen( GetString( st->a ) ) == 0 ); + break; + + case OP_NOT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( GetEntity( *var_a.entityNumberPtr ) == NULL ); + break; + + case OP_NEG_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = -*var_a.floatPtr; + break; + + case OP_NEG_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = -*var_a.vectorPtr; + break; + + case OP_INT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ); + break; + + case OP_EQ_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == *var_b.floatPtr ); + break; + + case OP_EQ_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == *var_b.vectorPtr ); + break; + + case OP_EQ_S: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) == 0 ); + break; + + case OP_EQ_E: + case OP_EQ_EO: + case OP_EQ_OE: + case OP_EQ_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr == *var_b.entityNumberPtr ); + break; + + case OP_NE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != *var_b.floatPtr ); + break; + + case OP_NE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr != *var_b.vectorPtr ); + break; + + case OP_NE_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) != 0 ); + break; + + case OP_NE_E: + case OP_NE_EO: + case OP_NE_OE: + case OP_NE_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr != *var_b.entityNumberPtr ); + break; + + case OP_UADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr += *var_a.floatPtr; + break; + + case OP_UADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr += *var_a.vectorPtr; + break; + + case OP_USUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr -= *var_a.floatPtr; + break; + + case OP_USUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr -= *var_a.vectorPtr; + break; + + case OP_UMUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr *= *var_a.floatPtr; + break; + + case OP_UMUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr *= *var_a.floatPtr; + break; + + case OP_UDIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = idMath::INFINITY; + } else { + *var_b.floatPtr = *var_b.floatPtr / *var_a.floatPtr; + } + break; + + case OP_UDIV_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + var_b.vectorPtr->Set( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + } else { + *var_b.vectorPtr = *var_b.vectorPtr / *var_a.floatPtr; + } + break; + + case OP_UMOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = *var_a.floatPtr; + } else { + *var_b.floatPtr = static_cast( *var_b.floatPtr ) % static_cast( *var_a.floatPtr ); + } + break; + + case OP_UOR_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) | static_cast( *var_a.floatPtr ); + break; + + case OP_UAND_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) & static_cast( *var_a.floatPtr ); + break; + + case OP_UINC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )++; + break; + + case OP_UINCP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )++; + } + break; + + case OP_UDEC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )--; + break; + + case OP_UDECP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )--; + } + break; + + case OP_COMP_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ~static_cast( *var_a.floatPtr ); + break; + + case OP_STORE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = *var_a.floatPtr; + break; + + case OP_STORE_ENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_BOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.intPtr = *var_a.intPtr; + break; + + case OP_STORE_OBJENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.entityNumberPtr = 0; + } else if ( !obj->GetTypeDef()->Inherits( st->b->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->b->TypeDef()->Name() ); + *var_b.entityNumberPtr = 0; + } else { + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STORE_OBJ: + case OP_STORE_ENTOBJ: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_S: + SetString( st->b, GetString( st->a ) ); + break; + + case OP_STORE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr = *var_a.vectorPtr; + break; + + case OP_STORE_FTOS: + var_a = GetVariable( st->a ); + SetString( st->b, FloatToString( *var_a.floatPtr ) ); + break; + + case OP_STORE_BTOS: + var_a = GetVariable( st->a ); + SetString( st->b, *var_a.intPtr ? "true" : "false" ); + break; + + case OP_STORE_VTOS: + var_a = GetVariable( st->a ); + SetString( st->b, var_a.vectorPtr->ToString() ); + break; + + case OP_STORE_FTOBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.intPtr = 1; + } else { + *var_b.intPtr = 0; + } + break; + + case OP_STORE_BOOLTOF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_a.intPtr ); + break; + + case OP_STOREP_F: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = *var_a.floatPtr; + } + break; + + case OP_STOREP_ENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_FLD: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_BOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_S: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + idStr::Copynz( var_b.evalPtr->stringPtr, GetString( st->a ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_V: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->vectorPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->vectorPtr = *var_a.vectorPtr; + } + break; + + case OP_STOREP_FTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, FloatToString( *var_a.floatPtr ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_BTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + idStr::Copynz( var_b.evalPtr->stringPtr, "true", MAX_STRING_LEN ); + } else { + idStr::Copynz( var_b.evalPtr->stringPtr, "false", MAX_STRING_LEN ); + } + } + break; + + case OP_STOREP_VTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, var_a.vectorPtr->ToString(), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_FTOBOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.evalPtr->intPtr = 1; + } else { + *var_b.evalPtr->intPtr = 0; + } + } + break; + + case OP_STOREP_BOOLTOF: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = static_cast( *var_a.intPtr ); + } + break; + + case OP_STOREP_OBJ: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_OBJENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.evalPtr->entityNumberPtr = 0; + + // st->b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in st->c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + } else if ( !obj->GetTypeDef()->Inherits( st->c->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->c->TypeDef()->Name() ); + *var_b.evalPtr->entityNumberPtr = 0; + } else { + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + } + break; + + case OP_ADDRESS: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var_c.evalPtr->bytePtr = &obj->data[ st->b->value.ptrOffset ]; + } else { + var_c.evalPtr->bytePtr = NULL; + } + break; + + case OP_INDIRECT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.floatPtr = *var.floatPtr; + } else { + *var_c.floatPtr = 0.0f; + } + break; + + case OP_INDIRECT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } else { + *var_c.entityNumberPtr = 0; + } + break; + + case OP_INDIRECT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.intPtr = *var.intPtr; + } else { + *var_c.intPtr = 0; + } + break; + + case OP_INDIRECT_S: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + SetString( st->c, var.stringPtr ); + } else { + SetString( st->c, "" ); + } + break; + + case OP_INDIRECT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.vectorPtr = *var.vectorPtr; + } else { + var_c.vectorPtr->Zero(); + } + break; + + case OP_INDIRECT_OBJ: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_c.entityNumberPtr = 0; + } else { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } + break; + + case OP_PUSH_F: + var_a = GetVariable( st->a ); + Push( *var_a.intPtr ); + break; + + case OP_PUSH_FTOS: + var_a = GetVariable( st->a ); + PushString( FloatToString( *var_a.floatPtr ) ); + break; + + case OP_PUSH_BTOF: + var_a = GetVariable( st->a ); + floatVal = *var_a.intPtr; + Push( *reinterpret_cast( &floatVal ) ); + break; + + case OP_PUSH_FTOB: + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + Push( 1 ); + } else { + Push( 0 ); + } + break; + + case OP_PUSH_VTOS: + var_a = GetVariable( st->a ); + PushString( var_a.vectorPtr->ToString() ); + break; + + case OP_PUSH_BTOS: + var_a = GetVariable( st->a ); + PushString( *var_a.intPtr ? "true" : "false" ); + break; + + case OP_PUSH_ENT: + var_a = GetVariable( st->a ); +// RAVEN BEGIN +// jshepard: keep tabs on this guy, he's the last referenced script variable. + assert(st->a); + LastScriptVariable = st->a; + //This line leads to memory corruption, I need to come up with a better way of keeping track. + //LastScriptVariable->initialized = idVarDef::stackVariable; +// RAVEN END + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_S: + PushString( GetString( st->a ) ); + break; + + case OP_PUSH_V: + var_a = GetVariable( st->a ); + Push( *reinterpret_cast( &var_a.vectorPtr->x ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->y ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->z ) ); + break; + + case OP_PUSH_OBJ: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_OBJENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_BREAK: + case OP_CONTINUE: + default: + Error( "Bad opcode %i", st->op ); + break; + } + } + + return threadDying; +} diff --git a/source/mpgame/script/Script_Interpreter.h b/source/mpgame/script/Script_Interpreter.h new file mode 100644 index 0000000..80a65e2 --- /dev/null +++ b/source/mpgame/script/Script_Interpreter.h @@ -0,0 +1,254 @@ + +#ifndef __SCRIPT_INTERPRETER_H__ +#define __SCRIPT_INTERPRETER_H__ + +#define MAX_STACK_DEPTH 64 +#define LOCALSTACK_SIZE 6144 + +typedef struct prstack_s { + int s; + const function_t *f; + int stackbase; +} prstack_t; + +class idInterpreter { +private: + prstack_t callStack[ MAX_STACK_DEPTH ]; + int callStackDepth; + int maxStackDepth; + + byte localstack[ LOCALSTACK_SIZE ]; + int localstackUsed; + int localstackBase; + int maxLocalstackUsed; + + const function_t *currentFunction; + int instructionPointer; + + int popParms; + const idEventDef *multiFrameEvent; + idEntity *eventEntity; + + idThread *thread; + + void PopParms( int numParms ); +// RAVEN BEGIN +// abahr: making Push public to allow parms to be put on stack +public: + void PushString( const char *string ); + void Push( int value ); +private: +// RAVEN END + const char *FloatToString( float value ); + void AppendString( idVarDef *def, const char *from ); + void SetString( idVarDef *def, const char *from ); + const char *GetString( idVarDef *def ); + varEval_t GetVariable( idVarDef *def ); + idEntity *GetEntity( int entnum ) const; + idScriptObject *GetScriptObject( int entnum ) const; + void NextInstruction( int position ); + + void LeaveFunction( idVarDef *returnDef ); + void CallEvent( const function_t *func, int argsize ); + void CallSysEvent( const function_t *func, int argsize ); +// RAVEN BEGIN +// jshepard: last variable referenced in the script-- keep tabs on it so we can print it for warnings. + idVarDef *LastScriptVariable; +// RAVEN END + + +public: + bool doneProcessing; + bool threadDying; + bool terminateOnExit; + bool debug; + + idInterpreter(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetThread( idThread *pThread ); + + void StackTrace( void ) const; + + int CurrentLine( void ) const; + const char *CurrentFile( void ) const; + + void Error( char *fmt, ... ) const; + void Warning( char *fmt, ... ) const; + void DisplayInfo( void ) const; + + bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + bool MultiFrameEventInProgress( void ) const; + + void ThreadCall( idInterpreter *source, const function_t *func, int args ); + void EnterFunction( const function_t *func, bool clearStack ); + void EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ); + + bool Execute( void ); + void Reset( void ); + + bool GetRegisterValue( const char *name, idStr &out, int scopeDepth ); + int GetCallstackDepth( void ) const; + const prstack_t *GetCallstack( void ) const; + const function_t *GetCurrentFunction( void ) const; + idThread *GetThread( void ) const; + +}; + +/* +==================== +idInterpreter::PopParms +==================== +*/ +ID_INLINE void idInterpreter::PopParms( int numParms ) { + // pop our parms off the stack + if ( localstackUsed < numParms ) { + Error( "locals stack underflow\n" ); + } + + localstackUsed -= numParms; +} + +/* +==================== +idInterpreter::Push +==================== +*/ +ID_INLINE void idInterpreter::Push( int value ) { + if ( localstackUsed + sizeof( int ) > LOCALSTACK_SIZE ) { + Error( "Push: locals stack overflow\n" ); + } + *( int * )&localstack[ localstackUsed ] = value; + localstackUsed += sizeof( int ); +} + +/* +==================== +idInterpreter::PushString +==================== +*/ +ID_INLINE void idInterpreter::PushString( const char *string ) { + if ( localstackUsed + MAX_STRING_LEN > LOCALSTACK_SIZE ) { + Error( "PushString: locals stack overflow\n" ); + } + idStr::Copynz( ( char * )&localstack[ localstackUsed ], string, MAX_STRING_LEN ); + localstackUsed += MAX_STRING_LEN; +} + +/* +==================== +idInterpreter::FloatToString +==================== +*/ +ID_INLINE const char *idInterpreter::FloatToString( float value ) { + static char text[ 32 ]; + + if ( value == ( float )( int )value ) { + sprintf( text, "%d", ( int )value ); + } else { + sprintf( text, "%f", value ); + } + return text; +} + +/* +==================== +idInterpreter::AppendString +==================== +*/ +ID_INLINE void idInterpreter::AppendString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Append( ( char * )&localstack[ localstackBase + def->value.stackOffset ], MAX_STRING_LEN, from ); + } else { + idStr::Append( def->value.stringPtr, MAX_STRING_LEN, from ); + } +} + +/* +==================== +idInterpreter::SetString +==================== +*/ +ID_INLINE void idInterpreter::SetString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Copynz( ( char * )&localstack[ localstackBase + def->value.stackOffset ], from, MAX_STRING_LEN ); + } else { + idStr::Copynz( def->value.stringPtr, from, MAX_STRING_LEN ); + } +} + +/* +==================== +idInterpreter::GetString +==================== +*/ +ID_INLINE const char *idInterpreter::GetString( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + return ( char * )&localstack[ localstackBase + def->value.stackOffset ]; + } else { + return def->value.stringPtr; + } +} + +/* +==================== +idInterpreter::GetVariable +==================== +*/ +ID_INLINE varEval_t idInterpreter::GetVariable( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + varEval_t val; + val.intPtr = ( int * )&localstack[ localstackBase + def->value.stackOffset ]; + return val; + } else { + return def->value; + } +} + +/* +================ +idInterpreter::GetEntity +================ +*/ +ID_INLINE idEntity *idInterpreter::GetEntity( int entnum ) const{ + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + return gameLocal.entities[ entnum - 1 ]; + } + return NULL; +} + +/* +================ +idInterpreter::GetScriptObject +================ +*/ +ID_INLINE idScriptObject *idInterpreter::GetScriptObject( int entnum ) const { + idEntity *ent; + + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + ent = gameLocal.entities[ entnum - 1 ]; + if ( ent && ent->scriptObject.data ) { + return &ent->scriptObject; + } + } + return NULL; +} + +/* +==================== +idInterpreter::NextInstruction +==================== +*/ +ID_INLINE void idInterpreter::NextInstruction( int position ) { + // Before we execute an instruction, we increment instructionPointer, + // therefore we need to compensate for that here. + instructionPointer = position - 1; +} + +#endif /* !__SCRIPT_INTERPRETER_H__ */ diff --git a/source/mpgame/script/Script_Program.cpp b/source/mpgame/script/Script_Program.cpp new file mode 100644 index 0000000..8fc9952 --- /dev/null +++ b/source/mpgame/script/Script_Program.cpp @@ -0,0 +1,2536 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +// simple types. function types are dynamically allocated +idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); +idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( void * ), NULL ); +idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( void * ), NULL ); +// RAVEN BEGIN +// abahr +rvTypeDefString type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); +rvTypeDefFloat type_float( ev_float, &def_float, "float", sizeof( float ), NULL ); +rvTypeDefVec3 type_vector( ev_vector, &def_vector, "vector", sizeof( idVec3 ), NULL ); +rvTypeDefEntity type_entity( ev_entity, &def_entity, "entity", sizeof( int * ), NULL ); // stored as entity number pointer +// RAVEN END +idTypeDef type_field( ev_field, &def_field, "field", sizeof( void * ), NULL ); +idTypeDef type_function( ev_function, &def_function, "function", sizeof( void * ), &type_void ); +idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( int ), NULL ); +idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( void * ), NULL ); +idTypeDef type_object( ev_object, &def_object, "object", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( int ), NULL ); // only used for jump opcodes +idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( int ), NULL ); // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +rvTypeDefBool type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( int ), NULL ); +// RAVEN END + +idVarDef def_void( &type_void ); +idVarDef def_scriptevent( &type_scriptevent ); +idVarDef def_namespace( &type_namespace ); +idVarDef def_string( &type_string ); +idVarDef def_float( &type_float ); +idVarDef def_vector( &type_vector ); +idVarDef def_entity( &type_entity ); +idVarDef def_field( &type_field ); +idVarDef def_function( &type_function ); +idVarDef def_virtualfunction( &type_virtualfunction ); +idVarDef def_pointer( &type_pointer ); +idVarDef def_object( &type_object ); +idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes +idVarDef def_argsize( &type_argsize ); +idVarDef def_boolean( &type_boolean ); + +/*********************************************************************** + + function_t + +***********************************************************************/ + +/* +================ +function_t::function_t +================ +*/ +function_t::function_t() { + Clear(); +} + +/* +================ +function_t::Allocated +================ +*/ +size_t function_t::Allocated( void ) const { + return name.Allocated() + parmSize.Allocated(); +} + +/* +================ +function_t::SetName +================ +*/ +void function_t::SetName( const char *name ) { + this->name = name; +} + +/* +================ +function_t::Name +================ +*/ +const char *function_t::Name( void ) const { + return name; +} + +/* +================ +function_t::Clear +================ +*/ +void function_t::Clear( void ) { + eventdef = NULL; + def = NULL; + type = NULL; + firstStatement = 0; + numStatements = 0; + parmTotal = 0; + locals = 0; + filenum = 0; + name.Clear(); + parmSize.Clear(); +} + +/*********************************************************************** + + idTypeDef + +***********************************************************************/ + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + name = ename; + type = etype; + def = edef; + size = esize; + auxType = aux; + + parmTypes.SetGranularity( 1 ); + parmNames.SetGranularity( 1 ); + functions.SetGranularity( 1 ); +} + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( const idTypeDef &other ) { + *this = other; +} + +/* +================ +idTypeDef::operator= +================ +*/ +void idTypeDef::operator=( const idTypeDef& other ) { + type = other.type; + def = other.def; + name = other.name; + size = other.size; + auxType = other.auxType; + parmTypes = other.parmTypes; + parmNames = other.parmNames; + functions = other.functions; +} + +/* +================ +idTypeDef::Allocated +================ +*/ +size_t idTypeDef::Allocated( void ) const { + size_t memsize; + int i; + + memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); + for( i = 0; i < parmTypes.Num(); i++ ) { + memsize += parmNames[ i ].Allocated(); + } + + return memsize; +} + +/* +================ +idTypeDef::Inherits + +Returns true if basetype is an ancestor of this type. +================ +*/ +bool idTypeDef::Inherits( const idTypeDef *basetype ) const { + idTypeDef *superType; + + if ( type != ev_object ) { + return false; + } + + if ( this == basetype ) { + return true; + } + for( superType = auxType; superType != NULL; superType = superType->auxType ) { + if ( superType == basetype ) { + return true; + } + } + + return false; +} + +/* +================ +idTypeDef::MatchesType + +Returns true if both types' base types and parameters match +================ +*/ +bool idTypeDef::MatchesType( const idTypeDef &matchtype ) const { + int i; + + if ( this == &matchtype ) { + return true; + } + + if ( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchtype.parmTypes.Num() ) { + return false; + } + + for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::MatchesVirtualFunction + +Returns true if both functions' base types and parameters match +================ +*/ +bool idTypeDef::MatchesVirtualFunction( const idTypeDef &matchfunc ) const { + int i; + + if ( this == &matchfunc ) { + return true; + } + + if ( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchfunc.parmTypes.Num() ) { + return false; + } + + if ( parmTypes.Num() > 0 ) { + if ( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { + return false; + } + } + + for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::AddFunctionParm + +Adds a new parameter for a function type. +================ +*/ +void idTypeDef::AddFunctionParm( idTypeDef *parmtype, const char *name ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); + } + + parmTypes.Append( parmtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; +} + +/* +================ +idTypeDef::AddField + +Adds a new field to an object type. +================ +*/ +void idTypeDef::AddField( idTypeDef *fieldtype, const char *name ) { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); + } + + parmTypes.Append( fieldtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; + + if ( fieldtype->FieldType()->Inherits( &type_object ) ) { + size += type_object.Size(); + } else { + size += fieldtype->FieldType()->Size(); + } +} + +/* +================ +idTypeDef::SetName +================ +*/ +void idTypeDef::SetName( const char *newname ) { + name = newname; +} + +/* +================ +idTypeDef::Name +================ +*/ +const char *idTypeDef::Name( void ) const { + return name; +} + +/* +================ +idTypeDef::Type +================ +*/ +etype_t idTypeDef::Type( void ) const { + return type; +} + +/* +================ +idTypeDef::Size +================ +*/ +int idTypeDef::Size( void ) const { + return size; +} + +/* +================ +idTypeDef::SuperClass + +If type is an object, then returns the object's superclass +================ +*/ +idTypeDef *idTypeDef::SuperClass( void ) const { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::ReturnType + +If type is a function, then returns the function's return type +================ +*/ +idTypeDef *idTypeDef::ReturnType( void ) const { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetReturnType + +If type is a function, then sets the function's return type +================ +*/ +void idTypeDef::SetReturnType( idTypeDef *returntype ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); + } + + auxType = returntype; +} + +/* +================ +idTypeDef::FieldType + +If type is a field, then returns it's type +================ +*/ +idTypeDef *idTypeDef::FieldType( void ) const { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetFieldType + +If type is a field, then sets the function's return type +================ +*/ +void idTypeDef::SetFieldType( idTypeDef *fieldtype ) { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); + } + + auxType = fieldtype; +} + +/* +================ +idTypeDef::PointerType + +If type is a pointer, then returns the type it points to +================ +*/ +idTypeDef *idTypeDef::PointerType( void ) const { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetPointerType + +If type is a pointer, then sets the pointer's type +================ +*/ +void idTypeDef::SetPointerType( idTypeDef *pointertype ) { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); + } + + auxType = pointertype; +} + +/* +================ +idTypeDef::NumParameters +================ +*/ +int idTypeDef::NumParameters( void ) const { + return parmTypes.Num(); +} + +/* +================ +idTypeDef::GetParmType +================ +*/ +idTypeDef *idTypeDef::GetParmType( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmTypes[ parmNumber ]; +} + +/* +================ +idTypeDef::GetParmName +================ +*/ +const char *idTypeDef::GetParmName( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmNames[ parmNumber ]; +} + +/* +================ +idTypeDef::NumFunctions +================ +*/ +int idTypeDef::NumFunctions( void ) const { + return functions.Num(); +} + +/* +================ +idTypeDef::GetFunctionNumber +================ +*/ +int idTypeDef::GetFunctionNumber( const function_t *func ) const { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( functions[ i ] == func ) { + return i; + } + } + return -1; +} + +/* +================ +idTypeDef::GetFunction +================ +*/ +const function_t *idTypeDef::GetFunction( int funcNumber ) const { + assert( funcNumber >= 0 ); + assert( funcNumber < functions.Num() ); + return functions[ funcNumber ]; +} + +/* +================ +idTypeDef::AddFunction +================ +*/ +void idTypeDef::AddFunction( const function_t *func ) { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( !idStr::Cmp( functions[ i ]->def->Name(), func->def->Name() ) ) { + if ( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { + functions[ i ] = func; + return; + } + } + } + functions.Append( func ); +} + +// RAVEN BEGIN +// abahr +/* +================ +rvTypeDefInt::Parse +================ +*/ +int rvTypeDefInt::Parse( const char* source ) const { + int i; + + sscanf( source, Format(), &i ); + + return i; +} + +/* +================ +rvTypeDefInt::GetReturnedValAsString +================ +*/ +const char* rvTypeDefInt::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedInteger() ); +} + +/* +================ +rvTypeDefInt::PushOntoStack +================ +*/ +void rvTypeDefInt::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushInt( Parse(source) ); +} + +/* +================ +rvTypeDefInt::IsValid +================ +*/ +bool rvTypeDefInt::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ + rvTypeDefFloat::Parse +================ +*/ +float rvTypeDefFloat::Parse( const char* source ) const { + float f; + + sscanf( source, Format(), &f ); + + return f; +} + +/* +================ +rvTypeDefFloat::GetReturnedValAsString +================ +*/ +const char* rvTypeDefFloat::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedFloat() ); +} + +/* +================ +rvTypeDefFloat::PushOntoStack +================ +*/ +void rvTypeDefFloat::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushFloat( Parse(source) ); +} + +/* +================ +rvTypeDefFloat::IsValid +================ +*/ +bool rvTypeDefFloat::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ +rvTypeDefVec3::Parse +================ +*/ +idVec3 rvTypeDefVec3::Parse( const char* source ) const { + idVec3 v; + + sscanf( source, Format(), &v[0], &v[1], &v[2] ); + + return v; +} + +/* +================ +rvTypeDefVec3::GetReturnedValAsString +================ +*/ +const char* rvTypeDefVec3::GetReturnedValAsString( idProgram& program ) { + idVec3 v( program.GetReturnedVec3() ); + return va( Format(), v[0], v[1], v[2] ); +} + +/* +================ +rvTypeDefVec3::PushOntoStack +================ +*/ +void rvTypeDefVec3::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushVec3( Parse(source) ); +} + +/* +================ +rvTypeDefVec3::IsValid +================ +*/ +bool rvTypeDefVec3::IsValid( const char* source ) const { + //Looking for two ' ' + return idStr::FindChar(source, ' ', idStr::FindChar(source, ' ') ) != -1; +} + +/* +================ +rvTypeDefEntity::Parse +================ +*/ +idEntity* rvTypeDefEntity::Parse( const char* source ) const { + return gameLocal.FindEntity( source ); +} + +/* +================ +rvTypeDefEntity::GetReturnedValAsString +================ +*/ + +const char* rvTypeDefEntity::GetReturnedValAsString( idProgram& program ) { + idEntity* entity = program.GetReturnedEntity(); + return (entity) ? entity->GetName() : ""; +} + +/* +================ +rvTypeDefEntity::PushOntoStack +================ +*/ +void rvTypeDefEntity::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushEntity( Parse(source) ); +} + +/* +================ +rvTypeDefEntity::IsValid +================ +*/ +bool rvTypeDefEntity::IsValid( const char* source ) const { + return Parse( source ) != NULL; +} + +/* +================ +rvTypeDefString::Parse +================ +*/ +const char* rvTypeDefString::Parse( const char* source ) const { + return source; +} + +/* +================ +rvTypeDefString::GetReturnedValAsString +================ +*/ +const char* rvTypeDefString::GetReturnedValAsString( idProgram& program ) { + return program.GetReturnedString(); +} + +/* +================ +rvTypeDefString::PushOntoStack +================ +*/ +void rvTypeDefString::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushString( Parse(source) ); +} + +/* +================ +vTypeDefString::IsValid +================ +*/ +bool rvTypeDefString::IsValid( const char* source ) const { + return true; +} + +/* +================ +rvTypeDefBool::Parse +================ +*/ +bool rvTypeDefBool::Parse( const char* source ) const { + unsigned int b; + + sscanf( source, Format(), &b ); + + return !!b; +} + +/* +================ +rvTypeDefBool::GetReturnedValAsString +================ +*/ +const char* rvTypeDefBool::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedBool() ); +} + +/* +================ +rvTypeDefBool::PushOntoStack +================ +*/ +void rvTypeDefBool::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushBool( Parse(source) ); +} + +/* +================ +rvTypeDefBool::IsValid +================ +*/ +bool rvTypeDefBool::IsValid( const char* source ) const { + return !idStr::Icmp(source, "true") || !idStr::Icmp(source, "false") || !idStr::Icmp(source, "1") || !idStr::Icmp(source, "0"); +} + +/* +================ +idProgram::GetReturnedEntity +================ +*/ +idEntity* idProgram::GetReturnedEntity() { + //This is here because gameLocal isn't known about yet in the header + int entityNumber = *returnDef->value.entityNumberPtr; + if( !entityNumber ) { + return NULL; + } + + return gameLocal.entities[ entityNumber - 1 ]; +} +// RAVEN END + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +================ +idVarDef::idVarDef() +================ +*/ +idVarDef::idVarDef( idTypeDef *typeptr ) { + typeDef = typeptr; + num = 0; + scope = NULL; + numUsers = 0; + initialized = idVarDef::uninitialized; + memset( &value, 0, sizeof( value ) ); + name = NULL; + next = NULL; +} + +/* +============ +idVarDef::~idVarDef +============ +*/ +idVarDef::~idVarDef() { + if ( name ) { + name->RemoveDef( this ); + } +} + +/* +============ +idVarDef::Name +============ +*/ +const char *idVarDef::Name( void ) const { + return name->Name(); +} + +/* +============ +idVarDef::GlobalName +============ +*/ +const char *idVarDef::GlobalName( void ) const { + if ( scope != &def_namespace ) { + return va( "%s::%s", scope->GlobalName(), name->Name() ); + } else { + return name->Name(); + } +} + +/* +============ +idVarDef::DepthOfScope +============ +*/ +int idVarDef::DepthOfScope( const idVarDef *otherScope ) const { + const idVarDef *def; + int depth; + + depth = 1; + for( def = otherScope; def != NULL; def = def->scope ) { + if ( def == scope ) { + return depth; + } + depth++; + } + + return 0; +} + +/* +============ +idVarDef::SetFunction +============ +*/ +void idVarDef::SetFunction( function_t *func ) { + assert( typeDef ); + initialized = initializedConstant; + assert( typeDef->Type() == ev_function ); + value.functionPtr = func; +} + +/* +============ +idVarDef::SetObject +============ +*/ +void idVarDef::SetObject( idScriptObject *object ) { + assert( typeDef ); + initialized = initialized; + assert( typeDef->Inherits( &type_object ) ); + *value.objectPtrPtr = object; +} + +/* +============ +idVarDef::SetValue +============ +*/ +void idVarDef::SetValue( const eval_t &_value, bool constant ) { + assert( typeDef ); + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + switch( typeDef->Type() ) { + case ev_pointer : + case ev_boolean : + case ev_field : + *value.intPtr = _value._int; + break; + + case ev_jumpoffset : + value.jumpOffset = _value._int; + break; + + case ev_argsize : + value.argSize = _value._int; + break; + + case ev_entity : + *value.entityNumberPtr = _value.entity; + break; + + case ev_string : + idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); + break; + + case ev_float : + *value.floatPtr = _value._float; + break; + + case ev_vector : + value.vectorPtr->x = _value.vector[ 0 ]; + value.vectorPtr->y = _value.vector[ 1 ]; + value.vectorPtr->z = _value.vector[ 2 ]; + break; + + case ev_function : + value.functionPtr = _value.function; + break; + + case ev_virtualfunction : + value.virtualFunction = _value._int; + break; + + case ev_object : + *value.entityNumberPtr = _value.entity; + break; + + default : + throw idCompileError( va( "weird type on '%s'", Name() ) ); + break; + } +} + +/* +============ +idVarDef::SetString +============ +*/ +void idVarDef::SetString( const char *string, bool constant ) { + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + assert( typeDef && ( typeDef->Type() == ev_string ) ); + idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +============ +idVarDef::PrintInfo +============ +*/ +void idVarDef::PrintInfo( idFile *file, int instructionPointer ) const { + statement_t *jumpst; + int jumpto; + etype_t etype; + int i; + int len; + const char *ch; + + if ( initialized == initializedConstant ) { + file->Printf( "const " ); + } + + etype = typeDef->Type(); + switch( etype ) { + case ev_jumpoffset : + jumpto = instructionPointer + value.jumpOffset; + jumpst = &gameLocal.program.GetStatement( jumpto ); + file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); + break; + + case ev_function : + if ( value.functionPtr->eventdef ) { + file->Printf( "event %s", GlobalName() ); + } else { + file->Printf( "function %s", GlobalName() ); + } + break; + + case ev_field : + file->Printf( "field %d", value.ptrOffset ); + break; + + case ev_argsize: + file->Printf( "args %d", value.argSize ); + break; + + default: + file->Printf( "%s ", typeDef->Name() ); + if ( initialized == initializedConstant ) { + switch( etype ) { + case ev_string : + file->Printf( "\"" ); + len = strlen( value.stringPtr ); + ch = value.stringPtr; + for( i = 0; i < len; i++, ch++ ) { + if ( idStr::CharIsPrintable( *ch ) ) { + file->Printf( "%c", *ch ); + } else if ( *ch == '\n' ) { + file->Printf( "\\n" ); + } else { + file->Printf( "\\x%.2x", static_cast( *ch ) ); + } + } + file->Printf( "\"" ); + break; + + case ev_vector : + file->Printf( "'%s'", value.vectorPtr->ToString() ); + break; + + case ev_float : + file->Printf( "%f", *value.floatPtr ); + break; + + case ev_virtualfunction : + file->Printf( "vtable[ %d ]", value.virtualFunction ); + break; + + default : + file->Printf( "%d", *value.intPtr ); + break; + } + } else if ( initialized == stackVariable ) { + file->Printf( "stack[%d]", value.stackOffset ); + } else { + file->Printf( "global[%d]", num ); + } + break; + } +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +============ +idVarDefName::AddDef +============ +*/ +void idVarDefName::AddDef( idVarDef *def ) { + assert( def->next == NULL ); + def->name = this; + def->next = defs; + defs = def; +} + +/* +============ +idVarDefName::RemoveDef +============ +*/ +void idVarDefName::RemoveDef( idVarDef *def ) { + if ( defs == def ) { + defs = def->next; + } else { + for ( idVarDef *d = defs; d->next != NULL; d = d->next ) { + if ( d->next == def ) { + d->next = def->next; + break; + } + } + } + def->next = NULL; + def->name = NULL; +} + +/*********************************************************************** + + idScriptObject + +***********************************************************************/ + +/* +============ +idScriptObject::idScriptObject +============ +*/ +idScriptObject::idScriptObject() { + data = NULL; + type = &type_object; +} + +/* +============ +idScriptObject::~idScriptObject +============ +*/ +idScriptObject::~idScriptObject() { + Free(); +} + +/* +============ +idScriptObject::Free +============ +*/ +void idScriptObject::Free( void ) { + if ( data ) { + Mem_Free( data ); + } + + data = NULL; + type = &type_object; +} + +/* +================ +idScriptObject::Save +================ +*/ +void idScriptObject::Save( idSaveGame *savefile ) const { + size_t size; + + if ( type == &type_object && data == NULL ) { + // Write empty string for uninitialized object + savefile->WriteString( "" ); + } else { + savefile->WriteString( type->Name() ); + size = type->Size(); + savefile->WriteInt( size ); + savefile->Write( data, size ); + } +} + +/* +================ +idScriptObject::Restore +================ +*/ +void idScriptObject::Restore( idRestoreGame *savefile ) { + idStr typeName; + size_t size; + + savefile->ReadString( typeName ); + + // Empty string signals uninitialized object + if ( typeName.Length() == 0 ) { + return; + } + + if ( !SetType( typeName ) ) { + savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); + } + + savefile->ReadInt( (int &)size ); + if ( size != type->Size() ) { + savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); + } + + savefile->Read( data, size ); +} + +/* +============ +idScriptObject::SetType + +Allocates an object and initializes memory. +============ +*/ +bool idScriptObject::SetType( const char *typeName ) { + size_t size; + idTypeDef *newtype; + + // lookup the type + newtype = gameLocal.program.FindType( typeName ); + + // only allocate memory if the object type changes + if ( newtype != type ) { + Free(); + if ( !newtype ) { + gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); + return false; + } + + if ( !newtype->Inherits( &type_object ) ) { + gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); + return false; + } + + // set the type + type = newtype; + + // allocate the memory + size = type->Size(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + data = ( byte * )Mem_Alloc( size, MA_SCRIPT ); +//RAVEN END + } + + // init object memory + ClearObject(); + + return true; +} + +/* +============ +idScriptObject::ClearObject + +Resets the memory for the script object without changing its type. +============ +*/ +void idScriptObject::ClearObject( void ) { + size_t size; + + if ( type != &type_object ) { + // init object memory + size = type->Size(); + memset( data, 0, size ); + } +} + +/* +============ +idScriptObject::HasObject +============ +*/ +bool idScriptObject::HasObject( void ) const { + return ( type != &type_object ); +} + +/* +============ +idScriptObject::GetTypeDef +============ +*/ +idTypeDef *idScriptObject::GetTypeDef( void ) const { + return type; +} + +/* +============ +idScriptObject::GetTypeName +============ +*/ +const char *idScriptObject::GetTypeName( void ) const { + return type->Name(); +} + +/* +============ +idScriptObject::GetConstructor +============ +*/ +const function_t *idScriptObject::GetConstructor( void ) const { + const function_t *func; + + func = GetFunction( "init" ); + return func; +} + +/* +============ +idScriptObject::GetDestructor +============ +*/ +const function_t *idScriptObject::GetDestructor( void ) const { + const function_t *func; + + func = GetFunction( "destroy" ); + return func; +} + +/* +============ +idScriptObject::GetFunction +============ +*/ +const function_t *idScriptObject::GetFunction( const char *name ) const { + const function_t *func; + + if ( type == &type_object ) { + return NULL; + } + + func = gameLocal.program.FindFunction( name, type ); + return func; +} + +/* +============ +idScriptObject::GetVariable +============ +*/ +byte *idScriptObject::GetVariable( const char *name, etype_t etype ) const { + int i; + int pos; + const idTypeDef *t; + const idTypeDef *parm; + + if ( type == &type_object ) { + return NULL; + } + + t = type; + do { + if ( t->SuperClass() != &type_object ) { + pos = t->SuperClass()->Size(); + } else { + pos = 0; + } + for( i = 0; i < t->NumParameters(); i++ ) { + parm = t->GetParmType( i ); + if ( !idStr::Cmp( t->GetParmName( i ), name ) ) { + if ( etype != parm->FieldType()->Type() ) { + return NULL; + } + return &data[ pos ]; + } + + if ( parm->FieldType()->Inherits( &type_object ) ) { + pos += type_object.Size(); + } else { + pos += parm->FieldType()->Size(); + } + } + t = t->SuperClass(); + } while( t && ( t != &type_object ) ); + + return NULL; +} + +/*********************************************************************** + + idProgram + +***********************************************************************/ + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( idTypeDef &type ) { + idTypeDef *newtype; + + newtype = new idTypeDef( type ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + idTypeDef *newtype; + + newtype = new idTypeDef( etype, edef, ename, esize, aux ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::GetType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +idTypeDef *idProgram::GetType( idTypeDef &type, bool allocate ) { + int i; + + //FIXME: linear search == slow + for( i = types.Num() - 1; i >= 0; i-- ) { + if ( types[ i ]->MatchesType( type ) && !idStr::Cmp( types[ i ]->Name(), type.Name() ) ) { + return types[ i ]; + } + } + + if ( !allocate ) { + return NULL; + } + + // allocate a new one + return AllocType( type ); +} + +/* +============ +idProgram::FindType + +Returns a preexisting complex type that matches the name, or returns NULL if not found +============ +*/ +idTypeDef *idProgram::FindType( const char *name ) { + idTypeDef *check; + int i; + + for( i = types.Num() - 1; i >= 0; i-- ) { + check = types[ i ]; + if ( !idStr::Cmp( check->Name(), name ) ) { + return check; + } + } + + return NULL; +} + +/* +============ +idProgram::GetDefList +============ +*/ +idVarDef *idProgram::GetDefList( const char *name ) const { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + return varDefNames[i]->GetDefs(); + } + } + return NULL; +} + +/* +============ +idProgram::AddDefToNameList +============ +*/ +void idProgram::AddDefToNameList( idVarDef *def, const char *name ) { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + break; + } + } + if ( i == -1 ) { + i = varDefNames.Append( new idVarDefName( name ) ); + varDefNameHash.Add( hash, i ); + } + varDefNames[i]->AddDef( def ); +} + +/* +============ +idProgram::AllocDef +============ +*/ +idVarDef *idProgram::AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ) { + idVarDef *def; + idStr element; + idVarDef *def_x; + idVarDef *def_y; + idVarDef *def_z; + + // allocate a new def + def = new idVarDef( type ); + def->scope = scope; + def->numUsers = 1; + def->num = varDefs.Append( def ); + def->compileStamp = compileStamp; + + // add the def to the list with defs with this name and set the name pointer + AddDefToNameList( def, name ); + + if ( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { + // + // vector + // + if ( !idStr::Cmp( name, RESULT_STRING ) ) { + // vector defs don't need the _x, _y and _z components + assert( scope->Type() == ev_function ); + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + scope->value.functionPtr->locals += type->Size(); + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); + idTypeDef *type = GetType( newtype, true ); + + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( type, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( type, element, scope, constant ); + def_y->value.ptrOffset = def_x->value.ptrOffset + type_float.Size(); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( type, element, scope, constant ); + def_z->value.ptrOffset = def_y->value.ptrOffset + type_float.Size(); + } else { + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( &type_float, element, scope, constant ); + + // point the vector def to the x coordinate + def->value = def_x->value; + def->initialized = def_x->initialized; + } + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + // + // object variable + // + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + } else if ( scope->Type() == ev_function ) { + // + // stack variable + // + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + + if ( type->Inherits( &type_object ) ) { + // objects only have their entity number on the stack, not the entire object + scope->value.functionPtr->locals += type_object.Size(); + } else { + scope->value.functionPtr->locals += type->Size(); + } + } else { + // + // global variable + // + def->value.bytePtr = &variables[ numVariables ]; + numVariables += def->TypeDef()->Size(); + if ( numVariables > sizeof( variables ) ) { + throw idCompileError( va( "Exceeded global memory size (%d bytes)", sizeof( variables ) ) ); + } + + memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); + } + + return def; +} + +/* +============ +idProgram::GetDef + +If type is NULL, it will match any type +============ +*/ +idVarDef *idProgram::GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const { + idVarDef *def; + idVarDef *bestDef; + int bestDepth; + int depth; + + bestDepth = 0; + bestDef = NULL; + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def->scope->Type() == ev_namespace ) { + depth = def->DepthOfScope( scope ); + if ( !depth ) { + // not in the same namespace + continue; + } + } else if ( def->scope != scope ) { + // in a different function + continue; + } else { + depth = 1; + } + + if ( !bestDef || ( depth < bestDepth ) ) { + bestDepth = depth; + bestDef = def; + } + } + + // see if the name is already in use for another type + if ( bestDef && type && ( bestDef->TypeDef() != type ) ) { + throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); + } + + return bestDef; +} + +/* +============ +idProgram::FreeDef +============ +*/ +void idProgram::FreeDef( idVarDef *def, const idVarDef *scope ) { + idVarDef *e; + int i; + + if ( def->Type() == ev_vector ) { + idStr name; + + sprintf( name, "%s_x", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_y", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_z", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + } + + varDefs.RemoveIndex( def->num ); + for( i = def->num; i < varDefs.Num(); i++ ) { + varDefs[ i ]->num = i; + } + + delete def; +} + +/* +============ +idProgram::FindFreeResultDef +============ +*/ +idVarDef *idProgram::FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ) { + idVarDef *def; + + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def == a || def == b ) { + continue; + } + if ( def->TypeDef() != type ) { + continue; + } + if ( def->scope != scope ) { + continue; + } + if ( def->numUsers <= 1 ) { + continue; + } + return def; + } + + return AllocDef( type, name, scope, false ); +} + +/* +================ +idProgram::FindFunction + +Searches for the specified function in the currently loaded script. A full namespace should be +specified if not in the global namespace. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name ) const { + int start; + int pos; + idVarDef *namespaceDef; + idVarDef *def; + + assert( name ); + + idStr fullname = name; + start = 0; + namespaceDef = &def_namespace; + do { + pos = fullname.Find( "::", true, start ); + if ( pos < 0 ) { + break; + } + + idStr namespaceName = fullname.Mid( start, pos - start ); + def = GetDef( NULL, namespaceName, namespaceDef ); + if ( !def ) { + // couldn't find namespace + return NULL; + } + namespaceDef = def; + + // skip past the :: + start = pos + 2; + } while( def->Type() == ev_namespace ); + + idStr funcName = fullname.Right( fullname.Length() - start ); + def = GetDef( NULL, funcName, namespaceDef ); + if ( !def ) { + // couldn't find function + return NULL; + } + + if ( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { + return def->value.functionPtr; + } + + // is not a function, or is an eventdef + return NULL; +} + +// RAVEN BEGIN +// bgeisler: list functions +/* +================ +idProgram::ListFunctions +================ +*/ +void idProgram::ListStates( void ) +{ + gameLocal.Printf( "Script States: \n"); + // function 0 is a NULL function + for( int i = 1; i < functions.Num(); i++ ) + { + gameLocal.Printf( "%s \n", functions[ i ].Name() ); + } + +} +// RAVEN END + +/* +================ +idProgram::FindFunction + +Searches for the specified object function in the currently loaded script. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name, const idTypeDef *type ) const { + const idVarDef *tdef; + const idVarDef *def; + + // look for the function + def = NULL; + for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = GetDef( NULL, name, tdef ); + if ( def ) { + return def->value.functionPtr; + } + } + + return NULL; +} + +/* +================ +idProgram::AllocFunction +================ +*/ +function_t &idProgram::AllocFunction( idVarDef *def ) { + if ( functions.Num() >= functions.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); + } + + // fill in the dfunction + function_t &func = *functions.Alloc(); + func.eventdef = NULL; + func.def = def; + func.type = def->TypeDef(); + func.firstStatement = 0; + func.numStatements = 0; + func.parmTotal = 0; + func.locals = 0; + func.filenum = filenum; + func.parmSize.SetGranularity( 1 ); + func.SetName( def->GlobalName() ); + + def->SetFunction( &func ); + + return func; +} + +/* +================ +idProgram::SetEntity +================ +*/ +void idProgram::SetEntity( const char *name, idEntity *ent ) { + idVarDef *def; + idStr defName( "$" ); + + defName += name; + + def = GetDef( &type_entity, defName, &def_namespace ); + if ( def && ( def->initialized != idVarDef::stackVariable ) ) { + // 0 is reserved for NULL entity + if ( !ent ) { + *def->value.entityNumberPtr = 0; + } else { + *def->value.entityNumberPtr = ent->entityNumber + 1; + } + } +} + +/* +================ +idProgram::AllocStatement +================ +*/ +statement_t *idProgram::AllocStatement( void ) { + if ( statements.Num() >= statements.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); + } + return statements.Alloc(); +} + +/* +============== +idProgram::BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void idProgram::BeginCompilation( void ) { + statement_t *statement; + + FreeData(); + + try { + // make the first statement a return for a "NULL" function + statement = AllocStatement(); + statement->linenumber = 0; + statement->file = 0; + statement->op = OP_RETURN; + statement->a = NULL; + statement->b = NULL; + statement->c = NULL; + + // define NULL + //AllocDef( &type_void, "", &def_namespace, true ); + + // define the return def + returnDef = AllocDef( &type_vector, "", &def_namespace, false ); + + // define the return def for strings + returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); + + // define the sys object + sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); + } + + catch( idCompileError &err ) { + gameLocal.Error( "%s", err.error ); + } +} + +/* +============== +idProgram::DisassembleStatement +============== +*/ +void idProgram::DisassembleStatement( idFile *file, int instructionPointer ) const { + opcode_t *op; + const statement_t *statement; + + statement = &statements[ instructionPointer ]; + op = &idCompiler::opcodes[ statement->op ]; + file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); + + if ( statement->a ) { + file->Printf( "\ta: " ); + statement->a->PrintInfo( file, instructionPointer ); + } + + if ( statement->b ) { + file->Printf( "\tb: " ); + statement->b->PrintInfo( file, instructionPointer ); + } + + if ( statement->c ) { + file->Printf( "\tc: " ); + statement->c->PrintInfo( file, instructionPointer ); + } + + file->Printf( "\n" ); +} + +/* +============== +idProgram::Disassemble +============== +*/ +void idProgram::Disassemble( void ) const { + int i; + int instructionPointer; + const function_t *func; + idFile *file; + + file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); + + for( i = 0; i < functions.Num(); i++ ) { + func = &functions[ i ]; + if ( func->eventdef ) { + // skip eventdefs + continue; + } + + file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); + + for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { + DisassembleStatement( file, func->firstStatement + instructionPointer ); + } + + file->Printf( "}\n" ); + } + + fileSystem->CloseFile( file ); +} + +/* +============== +idProgram::FinishCompilation + +Called after all files are compiled to check for errors +============== +*/ +void idProgram::FinishCompilation( void ) { + int i; + + top_functions = functions.Num(); + top_statements = statements.Num(); + top_types = types.Num(); + top_defs = varDefs.Num(); + top_files = fileList.Num(); + + variableDefaults.Clear(); + variableDefaults.SetNum( numVariables ); + + for( i = 0; i < numVariables; i++ ) { + variableDefaults[ i ] = variables[ i ]; + } +} + +/* +============== +idProgram::CompileStats + +called after all files are compiled to report memory usage. +============== +*/ +void idProgram::CompileStats( void ) { + int memused; + int memallocated; + int numdefs; + int stringspace; + int funcMem; + int i; + + gameLocal.Printf( "-------------- Compile stats ----------------\n" ); + gameLocal.DPrintf( "Files loaded:\n" ); + + stringspace = 0; + for( i = 0; i < fileList.Num(); i++ ) { + gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); + stringspace += fileList[ i ].Allocated(); + } + stringspace += fileList.Size(); + + numdefs = varDefs.Num(); + memused = varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + memused += stringspace; + + for( i = 0; i < types.Num(); i++ ) { + memused += types[ i ]->Allocated(); + } + + funcMem = functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + funcMem += functions[ i ].Allocated(); + } + + memallocated = funcMem + memused + sizeof( idProgram ); + + memused += statements.MemoryUsed(); + memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them + memused += sizeof( variables ); + + gameLocal.Printf( "\nMemory usage:\n" ); + gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); + gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); + gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); + gameLocal.Printf( " Variables: %d bytes\n", numVariables ); + gameLocal.Printf( " Mem used: %d bytes\n", memused ); + gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); + gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); + gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); +} + +// RAVEN BEGIN +// jscott: summary of script memory usage +/* +================ +idProgram::ScriptSummary +================ +*/ +size_t idProgram::ScriptSummary( const idCmdArgs &args ) { + + int memused; + int i; + + memused = 0; + for( i = 0; i < fileList.Num(); i++ ) { + + memused += fileList[i].Allocated(); + } + memused += fileList.Size(); + + memused += varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + + for( i = 0; i < types.Num(); i++ ) { + + memused += types[i]->Allocated(); + } + + memused += functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + + memused += functions[i].Allocated(); + } + + memused += statements.MemoryUsed(); + memused += sizeof( variables ); + memused += sizeof( idProgram ); + memused += sizeof( idThread ); + + common->Printf( "Scripts - %dK\n", memused >> 10 ); + + return( memused >> 10 ); +} +// RAVEN END + +/* +================ +idProgram::CompileText +================ +*/ +bool idProgram::CompileText( const char *source, const char *text, bool console, bool fatal ) { + idCompiler compiler; + int i; + idVarDef *def; + idStr ospath; + +// RAVEN BEGIN +// bdube: Make sure the file hasnt already been loaded + if ( -1 != fileList.FindIndex ( idStr(source) ) ) { + return true; + } +// RAVEN END + + // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser + ospath = fileSystem->RelativePathToOSPath( source ); + filenum = GetFilenum( ospath ); + + ++compileStamp; + + try { + compiler.CompileFile( text, filename, console ); + + // check to make sure all functions prototyped have code + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + if ( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { + if ( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { + throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); + } + } + } + } + + catch( idCompileError &err ) { + // attempt to recover by deleting anything new in the script + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + + if ( def->compileStamp == compileStamp ) { + FreeDef( def, def->scope ); + i--; + } + } + + if ( console || !fatal ) { + gameLocal.Printf( "%s\n", err.error ); + return false; + } else { + gameLocal.Error( "%s\n", err.error ); + } + }; + + if ( !console ) { + CompileStats(); + } + + return true; +} + +/* +================ +idProgram::CompileFunction +================ +*/ +const function_t *idProgram::CompileFunction( const char *functionName, const char *text ) { + bool result; + + result = CompileText( functionName, text, false ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed." ); + } + + return FindFunction( functionName ); +} + +/* +================ +idProgram::CompileFile +================ +*/ +bool idProgram::CompileFile( const char *filename, bool fatal ) { + char *src; + bool result; + + if ( fileSystem->ReadFile( filename, ( void ** )&src, NULL ) < 0 ) { + gameLocal.Error( "Couldn't load %s\n", filename ); + } + + result = CompileText( filename, src, false, fatal ); + + fileSystem->FreeFile( src ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + if ( fatal ) { + gameLocal.Error( "Compile failed in file %s.", filename ); + } else { + gameLocal.Warning( "Compile failed in file %s.", filename ); + } + return false; + } + + return true; +} + +/* +================ +idProgram::FreeData +================ +*/ +void idProgram::FreeData( void ) { + int i; + + // free the defs + varDefs.DeleteContents( true ); + varDefNames.DeleteContents( true ); + varDefNameHash.Free(); + + returnDef = NULL; + returnStringDef = NULL; + sysDef = NULL; + + // free any special types we've created + types.DeleteContents( true ); + + filenum = 0; + + numVariables = 0; + memset( variables, 0, sizeof( variables ) ); + + // clear all the strings in the functions so that it doesn't look like we're leaking memory. + for( i = 0; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + + filename.Clear(); + fileList.Clear(); + statements.Clear(); + functions.Clear(); + + top_functions = 0; + top_statements = 0; + top_types = 0; + top_defs = 0; + top_files = 0; + + filename = ""; + + compileStamp = 0; +} + +/* +================ +idProgram::Startup +================ +*/ +void idProgram::Startup( const char *defaultScript ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag, MA_SCRIPT); +// RAVEN END + gameLocal.Printf( "Initializing scripts\n" ); + + // make sure all data is freed up + idThread::Restart(); + + // get ready for loading scripts + BeginCompilation(); + + // load the default script + if ( defaultScript && *defaultScript ) { + CompileFile( defaultScript ); + } + + FinishCompilation(); +} + +/* +================ +idProgram::Save +================ +*/ +void idProgram::Save( idSaveGame *savefile ) const { + int i; + int currentFileNum = top_files; + + savefile->WriteInt( (fileList.Num() - currentFileNum) ); + while ( currentFileNum < fileList.Num() ) { + savefile->WriteString( fileList[ currentFileNum ] ); + currentFileNum++; + } + savefile->WriteString( filename ); // cnicholson: Added unsaved var + savefile->WriteInt ( filenum ); // cnicholson: Added unsaved var + + for ( i = 0; i < variableDefaults.Num(); i++ ) { + if ( variables[i] != variableDefaults[i] ) { + savefile->WriteInt( i ); + savefile->WriteByte( variables[i] ); + } + } + // Mark the end of the diff with default variables with -1 + savefile->WriteInt( -1 ); + + savefile->WriteInt( numVariables ); + for ( i = variableDefaults.Num(); i < numVariables; i++ ) { + savefile->WriteByte( variables[i] ); + } + + savefile->WriteInt ( top_functions ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_statements ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_types ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_defs ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_files ); // cnicholson: Added unsaved var + + int checksum = CalculateChecksum(); + savefile->WriteInt( checksum ); +} + +/* +================ +idProgram::Restore +================ +*/ +bool idProgram::Restore( idRestoreGame *savefile ) { + int i, num, index; + bool result = true; + idStr scriptname; + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( scriptname ); + CompileFile( scriptname ); + } + + savefile->ReadString( filename ); // cnicholson: Added unrestored var + savefile->ReadInt ( filenum ); // cnicholson: Added unrestored var + + savefile->ReadInt( index ); + while( index >= 0 ) { + savefile->ReadByte( variables[index] ); + savefile->ReadInt( index ); + } + + savefile->ReadInt( num ); + for ( i = variableDefaults.Num(); i < num; i++ ) { + savefile->ReadByte( variables[i] ); + } + + savefile->ReadInt ( top_functions ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_statements ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_types ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_defs ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_files ); // cnicholson: Added unrestored var + + int saved_checksum, checksum; + + savefile->ReadInt( saved_checksum ); + checksum = CalculateChecksum(); + + if ( saved_checksum != checksum ) { + result = false; + } + + return result; +} + +/* +================ +idProgram::CalculateChecksum +================ +*/ +int idProgram::CalculateChecksum( void ) const { + int i, result; + + typedef struct { + unsigned short op; + int a; + int b; + int c; + unsigned short linenumber; + unsigned short file; + } statementBlock_t; + + statementBlock_t *statementList = new statementBlock_t[ statements.Num() ]; + + memset( statementList, 0, ( sizeof(statementBlock_t) * statements.Num() ) ); + + // Copy info into new list, using the variable numbers instead of a pointer to the variable + for( i = 0; i < statements.Num(); i++ ) { + statementList[i].op = statements[i].op; + + if ( statements[i].a ) { + statementList[i].a = statements[i].a->num; + } else { + statementList[i].a = -1; + } + if ( statements[i].b ) { + statementList[i].b = statements[i].b->num; + } else { + statementList[i].b = -1; + } + if ( statements[i].c ) { + statementList[i].c = statements[i].c->num; + } else { + statementList[i].c = -1; + } + + statementList[i].linenumber = statements[i].linenumber; + statementList[i].file = statements[i].file; + } + + result = MD4_BlockChecksum( statementList, ( sizeof(statementBlock_t) * statements.Num() ) ); + + delete [] statementList; + + return result; +} + +/* +============== +idProgram::Restart + +Restores all variables to their initial value +============== +*/ +void idProgram::Restart( void ) { + int i; + + idThread::Restart(); + + // + // since there may have been a script loaded by the map or the user may + // have typed "script" from the console, free up any types and vardefs that + // have been allocated after the initial startup + // + for( i = top_types; i < types.Num(); i++ ) { + delete types[ i ]; + } + types.SetNum( top_types, false ); + + for( i = top_defs; i < varDefs.Num(); i++ ) { + delete varDefs[ i ]; + } + varDefs.SetNum( top_defs, false ); + + for( i = top_functions; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + functions.SetNum( top_functions ); + + statements.SetNum( top_statements ); + fileList.SetNum( top_files, false ); + filename.Clear(); + + // reset the variables to their default values + numVariables = variableDefaults.Num(); + for( i = 0; i < numVariables; i++ ) { + variables[ i ] = variableDefaults[ i ]; + } +} + +/* +================ +idProgram::GetFilenum +================ +*/ +int idProgram::GetFilenum( const char *name ) { + if ( filename == name ) { + return filenum; + } + + idStr strippedName; + strippedName = fileSystem->OSPathToRelativePath( name ); + if ( !strippedName.Length() ) { + // not off the base path so just use the full path + filenum = fileList.AddUnique( name ); + } else { + filenum = fileList.AddUnique( strippedName ); + } + + // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum + filename = name; + + return filenum; +} + +/* +================ +idProgram::idProgram +================ +*/ +idProgram::idProgram() { + FreeData(); +} + +/* +================ +idProgram::~idProgram +================ +*/ +idProgram::~idProgram() { + FreeData(); +} + +// RAVEN BEGIN +// jscott: for debug with inlines and memory log +/* +================ +idProgram::Shutdown +================ +*/ +void idProgram::Shutdown( void ) +{ + FreeData(); +} +// RAVEN END + +/* +================ +idProgram::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idProgram::ReturnEntity( const idEntity *ent ) { + if ( ent ) { + *returnDef->value.entityNumberPtr = ent->entityNumber + 1; + } else { + *returnDef->value.entityNumberPtr = 0; + } +} + diff --git a/source/mpgame/script/Script_Program.h b/source/mpgame/script/Script_Program.h new file mode 100644 index 0000000..7065e40 --- /dev/null +++ b/source/mpgame/script/Script_Program.h @@ -0,0 +1,800 @@ + +#ifndef __SCRIPT_PROGRAM_H__ +#define __SCRIPT_PROGRAM_H__ + +class idScriptObject; +class idEventDef; +class idVarDef; +class idTypeDef; +class idEntity; +class idThread; +class idSaveGame; +class idRestoreGame; + +#define MAX_STRING_LEN 128 +// RAVEN BEGIN +// ddynerman: higher max limit, initially 196608 +// jshepard: ... then 393216 +#define MAX_GLOBALS 589824 // in bytes + +// jshepard: raise the limits. Formerly 1024, 3072 and 81920. +#define MAX_STRINGS 2048 +#define MAX_FUNCS 6144 +#define MAX_STATEMENTS 163840 // statement_t - 18 bytes last I checked +// RAVEN END +typedef enum { + ev_error = -1, ev_void, ev_scriptevent, ev_namespace, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_virtualfunction, ev_pointer, ev_object, ev_jumpoffset, ev_argsize, ev_boolean +} etype_t; + +class function_t { +public: + function_t(); + + size_t Allocated( void ) const; + void SetName( const char *name ); + const char *Name( void ) const; + void Clear( void ); + +private: + idStr name; +public: + const idEventDef *eventdef; + idVarDef *def; + const idTypeDef *type; + int firstStatement; + int numStatements; + int parmTotal; + int locals; // total ints of parms + locals + int filenum; // source file defined in + idList parmSize; +}; + +typedef union eval_s { + const char *stringPtr; + float _float; + float vector[ 3 ]; + function_t *function; + int _int; + int entity; +} eval_t; + +/*********************************************************************** + +idTypeDef + +Contains type information for variables and functions. + +***********************************************************************/ + +class idTypeDef { +private: + etype_t type; + idStr name; + int size; + + // function types are more complex + idTypeDef *auxType; // return type + idList parmTypes; + idStrList parmNames; + idList functions; + +public: + idVarDef *def; // a def that points to this type + + idTypeDef( const idTypeDef &other ); + idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + virtual ~idTypeDef( void ) { } + void operator=( const idTypeDef& other ); + size_t Allocated( void ) const; + + bool Inherits( const idTypeDef *basetype ) const; + bool MatchesType( const idTypeDef &matchtype ) const; + bool MatchesVirtualFunction( const idTypeDef &matchfunc ) const; + void AddFunctionParm( idTypeDef *parmtype, const char *name ); + void AddField( idTypeDef *fieldtype, const char *name ); + + void SetName( const char *newname ); + const char *Name( void ) const; + + etype_t Type( void ) const; + int Size( void ) const; + + idTypeDef *SuperClass( void ) const; + + idTypeDef *ReturnType( void ) const; + void SetReturnType( idTypeDef *type ); + + idTypeDef *FieldType( void ) const; + void SetFieldType( idTypeDef *type ); + + idTypeDef *PointerType( void ) const; + void SetPointerType( idTypeDef *type ); + + int NumParameters( void ) const; + idTypeDef *GetParmType( int parmNumber ) const; + const char *GetParmName( int parmNumber ) const; + + int NumFunctions( void ) const; + int GetFunctionNumber( const function_t *func ) const; + const function_t *GetFunction( int funcNumber ) const; + void AddFunction( const function_t *func ); + +// RAVEN BEGIN + // abahr + virtual const char* GetReturnedValAsString( idProgram& program ) { return ""; } + virtual void PushOntoStack( idThread* thread, const char* source ) { assert(0); } + virtual bool IsValid( const char* source ) const { return true; } + virtual const char* Format() const { return ""; } +// RAVEN END +}; + +// RAVEN BEGIN +// abahr: subclasses for overwritting helper functions + +/*********************************************************************** + +rvTypeDefInt + +***********************************************************************/ +class rvTypeDefInt : public idTypeDef { +public: + rvTypeDefInt( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefInt( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%d"; } + int Parse( const char* source ) const; + +}; + +/*********************************************************************** + +rvTypeDefFloat + +***********************************************************************/ +class rvTypeDefFloat : public idTypeDef { +public: + rvTypeDefFloat( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefFloat( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f"; } + float Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefVec3 + +***********************************************************************/ +class rvTypeDefVec3 : public idTypeDef { +public: + rvTypeDefVec3( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefVec3( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f %f %f"; } + idVec3 Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefEntity + +***********************************************************************/ +class rvTypeDefEntity : public idTypeDef { +public: + rvTypeDefEntity( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefEntity( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + idEntity* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefString + +***********************************************************************/ +class rvTypeDefString : public idTypeDef { +public: + rvTypeDefString( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefString( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefBool + +***********************************************************************/ +class rvTypeDefBool : public idTypeDef { +public: + rvTypeDefBool( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefBool( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%u"; } + bool Parse( const char* source ) const; +}; +// RAVEN END + +/*********************************************************************** + +idScriptObject + +In-game representation of objects in scripts. Use the idScriptVariable template +(below) to access variables. + +***********************************************************************/ + +class idScriptObject { +private: + idTypeDef *type; + +public: + byte *data; + + idScriptObject(); + ~idScriptObject(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Free( void ); + bool SetType( const char *typeName ); + void ClearObject( void ); + bool HasObject( void ) const; + idTypeDef *GetTypeDef( void ) const; + const char *GetTypeName( void ) const; + const function_t *GetConstructor( void ) const; + const function_t *GetDestructor( void ) const; + const function_t *GetFunction( const char *name ) const; + + byte *GetVariable( const char *name, etype_t etype ) const; +}; + +/*********************************************************************** + +idScriptVariable + +Helper template that handles looking up script variables stored in objects. +If the specified variable doesn't exist, or is the wrong data type, idScriptVariable +will cause an error. + +***********************************************************************/ + +template +class idScriptVariable { +private: + type *data; + +public: + idScriptVariable(); + bool IsLinked( void ) const; + void Unlink( void ); + void LinkTo( idScriptObject &obj, const char *name ); + idScriptVariable &operator=( const returnType &value ); + operator returnType() const; +}; + +template +ID_INLINE idScriptVariable::idScriptVariable() { + data = NULL; +} + +template +ID_INLINE bool idScriptVariable::IsLinked( void ) const { + return ( data != NULL ); +} + +template +ID_INLINE void idScriptVariable::Unlink( void ) { + data = NULL; +} + +template +ID_INLINE void idScriptVariable::LinkTo( idScriptObject &obj, const char *name ) { + data = ( type * )obj.GetVariable( name, etype ); + if ( !data ) { + gameError( "Missing '%s' field in script object '%s'", name, obj.GetTypeName() ); + } +} + +template +ID_INLINE idScriptVariable &idScriptVariable::operator=( const returnType &value ) { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + *data = ( type )value; + } + return *this; +} + +template +ID_INLINE idScriptVariable::operator returnType() const { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + return ( const returnType )*data; + } else { + // reasonably safe value + return ( const returnType )0; + } +} + +/*********************************************************************** + +Script object variable access template instantiations + +These objects will automatically handle looking up of the current value +of a variable in a script object. They can be stored as part of a class +for up-to-date values of the variable, or can be used in functions to +sample the data for non-dynamic values. + +***********************************************************************/ + +typedef idScriptVariable idScriptBool; +typedef idScriptVariable idScriptFloat; +typedef idScriptVariable idScriptInt; +typedef idScriptVariable idScriptVector; +typedef idScriptVariable idScriptString; + +/*********************************************************************** + +idCompileError + +Causes the compiler to exit out of compiling the current function and +display an error message with line and file info. + +***********************************************************************/ + +class idCompileError : public idException { +public: + idCompileError( const char *text ) : idException( text ) {} +}; + +/*********************************************************************** + +idVarDef + +Define the name, type, and location of variables, functions, and objects +defined in script. + +***********************************************************************/ + +typedef union varEval_s { + idScriptObject **objectPtrPtr; + char *stringPtr; + float *floatPtr; + idVec3 *vectorPtr; + function_t *functionPtr; + int *intPtr; + byte *bytePtr; + int *entityNumberPtr; + int virtualFunction; + int jumpOffset; + int stackOffset; // offset in stack for local variables + int argSize; + varEval_s *evalPtr; + int ptrOffset; +} varEval_t; + +class idVarDefName; + +class idVarDef { + friend class idVarDefName; + +public: + int num; + varEval_t value; + idVarDef * scope; // function, namespace, or object the var was defined in + int numUsers; // number of users if this is a constant + int compileStamp; // compilation stamp for deleting failed compiles + + typedef enum { + uninitialized, initializedVariable, initializedConstant, stackVariable + } initialized_t; + + initialized_t initialized; + +public: + idVarDef( idTypeDef *typeptr = NULL ); + ~idVarDef(); + + const char * Name( void ) const; + const char * GlobalName( void ) const; + + void SetTypeDef( idTypeDef *_type ) { typeDef = _type; } + idTypeDef * TypeDef( void ) const { return typeDef; } + etype_t Type( void ) const { return ( typeDef != NULL ) ? typeDef->Type() : ev_void; } + + int DepthOfScope( const idVarDef *otherScope ) const; + + void SetFunction( function_t *func ); + void SetObject( idScriptObject *object ); + void SetValue( const eval_t &value, bool constant ); + void SetString( const char *string, bool constant ); + + idVarDef * Next( void ) const { return next; } // next var def with same name + + void PrintInfo( idFile *file, int instructionPointer ) const; + +private: + idTypeDef * typeDef; + idVarDefName * name; // name of this var + idVarDef * next; // next var with the same name +}; + +/*********************************************************************** + + idVarDefName + +***********************************************************************/ + +class idVarDefName { +public: + idVarDefName( void ) { defs = NULL; } + idVarDefName( const char *n ) { name = n; defs = NULL; } + + const char * Name( void ) const { return name; } + idVarDef * GetDefs( void ) const { return defs; } + + void AddDef( idVarDef *def ); + void RemoveDef( idVarDef *def ); + +private: + idStr name; + idVarDef * defs; +}; + +/*********************************************************************** + + Variable and type defintions + +***********************************************************************/ + +extern idTypeDef type_void; +extern idTypeDef type_scriptevent; +extern idTypeDef type_namespace; +// RAVEN BEGIN +// abahr +extern rvTypeDefString type_string; +extern rvTypeDefFloat type_float; +extern rvTypeDefVec3 type_vector; +extern rvTypeDefEntity type_entity; +// RAVEN END +extern idTypeDef type_field; +extern idTypeDef type_function; +extern idTypeDef type_virtualfunction; +extern idTypeDef type_pointer; +extern idTypeDef type_object; +extern idTypeDef type_jumpoffset; // only used for jump opcodes +extern idTypeDef type_argsize; // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +extern rvTypeDefBool type_boolean; +// RAVEN END + +extern idVarDef def_void; +extern idVarDef def_scriptevent; +extern idVarDef def_namespace; +extern idVarDef def_string; +extern idVarDef def_float; +extern idVarDef def_vector; +extern idVarDef def_entity; +extern idVarDef def_field; +extern idVarDef def_function; +extern idVarDef def_virtualfunction; +extern idVarDef def_pointer; +extern idVarDef def_object; +extern idVarDef def_jumpoffset; // only used for jump opcodes +extern idVarDef def_argsize; // only used for function call and thread opcodes +extern idVarDef def_boolean; + +typedef struct statement_s { + unsigned short op; + idVarDef *a; + idVarDef *b; + idVarDef *c; + unsigned short linenumber; + unsigned short file; +} statement_t; + +/*********************************************************************** + +idProgram + +Handles compiling and storage of script data. Multiple idProgram objects +would represent seperate programs with no knowledge of each other. Scripts +meant to access shared data and functions should all be compiled by a +single idProgram. + +***********************************************************************/ + +class idProgram { +private: + idStrList fileList; + idStr filename; + int filenum; + + int numVariables; + byte variables[ MAX_GLOBALS ]; + idStaticList variableDefaults; + idStaticList functions; + idStaticList statements; + idList types; + idList varDefNames; + idHashIndex varDefNameHash; + idList varDefs; + + idVarDef *sysDef; + + int top_functions; + int top_statements; + int top_types; + int top_defs; + int top_files; + + void CompileStats( void ); + + int compileStamp; + +public: + idVarDef *returnDef; + idVarDef *returnStringDef; + + idProgram(); + ~idProgram(); + + // save games + void Save( idSaveGame *savefile ) const; + bool Restore( idRestoreGame *savefile ); + int CalculateChecksum( void ) const; // Used to insure program code has not + // changed between savegames + + void Startup( const char *defaultScript ); + void Restart( void ); + bool CompileText( const char *source, const char *text, bool console, bool fatal = true ); + const function_t *CompileFunction( const char *functionName, const char *text ); + bool CompileFile( const char *filename, bool fatal = true ); + void BeginCompilation( void ); + void FinishCompilation( void ); + void DisassembleStatement( idFile *file, int instructionPointer ) const; + void Disassemble( void ) const; + void FreeData( void ); + + const char *GetFilename( int num ); + int GetFilenum( const char *name ); + int GetLineNumberForStatement( int index ); + const char *GetFilenameForStatement( int index ); + + idTypeDef *AllocType( idTypeDef &type ); + idTypeDef *AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + idTypeDef *GetType( idTypeDef &type, bool allocate ); + idTypeDef *FindType( const char *name ); + + idVarDef *AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ); + idVarDef *GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const; + void FreeDef( idVarDef *d, const idVarDef *scope ); + idVarDef *FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ); + idVarDef *GetDefList( const char *name ) const; + void AddDefToNameList( idVarDef *def, const char *name ); + + function_t *FindFunction( const char *name ) const; // returns NULL if function not found + function_t *FindFunction( const char *name, const idTypeDef *type ) const; // returns NULL if function not found + function_t &AllocFunction( idVarDef *def ); + function_t *GetFunction( int index ); + int GetFunctionIndex( const function_t *func ); + + void SetEntity( const char *name, idEntity *ent ); + + statement_t *AllocStatement( void ); + statement_t &GetStatement( int index ); + int NumStatements( void ) { return statements.Num(); } + + int GetReturnedInteger( void ); + +// RAVEN BEGIN +// abahr: adding helper functions for other types + float GetReturnedFloat(); + idVec3 GetReturnedVec3(); + idEntity* GetReturnedEntity(); + const char* GetReturnedString(); + bool GetReturnedBool(); +// RAVEN END + + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( idVec3 const &vec ); + void ReturnString( const char *string ); +// RAVEN BEGIN +// abahr: added const + void ReturnEntity( const idEntity *ent ); +// RAVEN END + + int NumFilenames( void ) { return fileList.Num( ); } + +// RAVEN BEGIN +// bdube: added + void ListStates( void ); +// jscott: added for debug with inlines and memory log + void Shutdown( void ); +// jscott: added for stats + size_t ScriptSummary( const idCmdArgs &args ); + size_t ClassSummary( const idCmdArgs &args ); +// RAVEN END +}; + +/* +================ +idProgram::GetStatement +================ +*/ +ID_INLINE statement_t &idProgram::GetStatement( int index ) { + return statements[ index ]; +} + +/* +================ +idProgram::GetFunction +================ +*/ +ID_INLINE function_t *idProgram::GetFunction( int index ) { + return &functions[ index ]; +} + +/* +================ +idProgram::GetFunctionIndex +================ +*/ +ID_INLINE int idProgram::GetFunctionIndex( const function_t *func ) { + return func - &functions[0]; +} + +/* +================ +idProgram::GetReturnedInteger +================ +*/ +ID_INLINE int idProgram::GetReturnedInteger( void ) { +// RAVEN BEGIN +// abahr: because we only return floats in idThread we need to only get floats + return (int)GetReturnedFloat(); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: adding helper functions for other types +/* +================ +idProgram::GetReturnedFloat +================ +*/ +ID_INLINE float idProgram::GetReturnedFloat() { + return *returnDef->value.floatPtr; +} + +/* +================ +idProgram::GetReturnedVec3 +================ +*/ +ID_INLINE idVec3 idProgram::GetReturnedVec3() { + return *returnDef->value.vectorPtr; +} + +/* +================ +idProgram::GetReturnedString +================ +*/ +ID_INLINE const char* idProgram::GetReturnedString() { + return returnDef->value.stringPtr; +} + +/* +================ +idProgram::GetReturnedBool +================ +*/ +ID_INLINE bool idProgram::GetReturnedBool() { + return GetReturnedInteger() != 0; +} +// RAVEN END + +/* +================ +idProgram::ReturnFloat +================ +*/ +ID_INLINE void idProgram::ReturnFloat( float value ) { + *returnDef->value.floatPtr = value; +} + +/* +================ +idProgram::ReturnInteger +================ +*/ +ID_INLINE void idProgram::ReturnInteger( int value ) { + *returnDef->value.intPtr = value; +} + +/* +================ +idProgram::ReturnVector +================ +*/ +ID_INLINE void idProgram::ReturnVector( idVec3 const &vec ) { + *returnDef->value.vectorPtr = vec; +} + +/* +================ +idProgram::ReturnString +================ +*/ +ID_INLINE void idProgram::ReturnString( const char *string ) { + idStr::Copynz( returnStringDef->value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +================ +idProgram::GetFilename +================ +*/ +ID_INLINE const char *idProgram::GetFilename( int num ) { + return fileList[ num ]; +} + +/* +================ +idProgram::GetLineNumberForStatement +================ +*/ +ID_INLINE int idProgram::GetLineNumberForStatement( int index ) { + return statements[ index ].linenumber; +} + +/* +================ +idProgram::GetFilenameForStatement +================ +*/ +ID_INLINE const char *idProgram::GetFilenameForStatement( int index ) { + return GetFilename( statements[ index ].file ); +} + +#endif /* !__SCRIPT_PROGRAM_H__ */ diff --git a/source/mpgame/script/Script_Thread.cpp b/source/mpgame/script/Script_Thread.cpp new file mode 100644 index 0000000..5a1f9d9 --- /dev/null +++ b/source/mpgame/script/Script_Thread.cpp @@ -0,0 +1,2309 @@ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const idEventDef EV_Thread_Execute( "", NULL ); +const idEventDef EV_Thread_SetCallback( "", NULL ); + +// script callable events +const idEventDef EV_Thread_TerminateThread( "terminate", "d" ); +const idEventDef EV_Thread_Pause( "pause", NULL ); +const idEventDef EV_Thread_Wait( "wait", "f" ); +const idEventDef EV_Thread_WaitFrame( "waitFrame" ); +const idEventDef EV_Thread_WaitFor( "waitFor", "e" ); +const idEventDef EV_Thread_WaitForThread( "waitForThread", "d" ); +const idEventDef EV_Thread_Print( "print", "s" ); +const idEventDef EV_Thread_PrintLn( "println", "s" ); +const idEventDef EV_Thread_Say( "say", "s" ); +const idEventDef EV_Thread_Assert( "assert", "f" ); +const idEventDef EV_Thread_Trigger( "trigger", "e" ); +const idEventDef EV_Thread_SetCvar( "setcvar", "ss" ); +const idEventDef EV_Thread_GetCvar( "getcvar", "s", 's' ); +const idEventDef EV_Thread_Random( "random", "f", 'f' ); +const idEventDef EV_Thread_GetTime( "getTime", NULL, 'f' ); +const idEventDef EV_Thread_KillThread( "killthread", "s" ); +const idEventDef EV_Thread_SetThreadName( "threadname", "s" ); +const idEventDef EV_Thread_GetEntity( "getEntity", "s", 'e' ); +const idEventDef EV_Thread_Spawn( "spawn", "s", 'e' ); +const idEventDef EV_Thread_CopySpawnArgs( "copySpawnArgs", "e" ); +const idEventDef EV_Thread_SetSpawnArg( "setSpawnArg", "ss" ); +const idEventDef EV_Thread_SpawnString( "SpawnString", "ss", 's' ); +const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); +const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); +const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); +const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); +const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); +const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); +const idEventDef EV_Thread_AngToForward( "angToForward", "v", 'v' ); +const idEventDef EV_Thread_AngToRight( "angToRight", "v", 'v' ); +const idEventDef EV_Thread_AngToUp( "angToUp", "v", 'v' ); +const idEventDef EV_Thread_Sine( "sin", "f", 'f' ); +const idEventDef EV_Thread_Cosine( "cos", "f", 'f' ); +const idEventDef EV_Thread_SquareRoot( "sqrt", "f", 'f' ); +const idEventDef EV_Thread_Normalize( "vecNormalize", "v", 'v' ); +const idEventDef EV_Thread_VecLength( "vecLength", "v", 'f' ); +const idEventDef EV_Thread_VecDotProduct( "DotProduct", "vv", 'f' ); +const idEventDef EV_Thread_VecCrossProduct( "CrossProduct", "vv", 'v' ); +const idEventDef EV_Thread_VecToAngles( "VecToAngles", "v", 'v' ); +const idEventDef EV_Thread_OnSignal( "onSignal", "des" ); +const idEventDef EV_Thread_ClearSignal( "clearSignalThread", "de" ); +const idEventDef EV_Thread_SetCamera( "setCamera", "e" ); +const idEventDef EV_Thread_FirstPerson( "firstPerson", NULL ); +const idEventDef EV_Thread_Trace( "trace", "vvvvde", 'f' ); +const idEventDef EV_Thread_TracePoint( "tracePoint", "vvde", 'f' ); +const idEventDef EV_Thread_GetTraceFraction( "getTraceFraction", NULL, 'f' ); +const idEventDef EV_Thread_GetTraceEndPos( "getTraceEndPos", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceNormal( "getTraceNormal", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceEntity( "getTraceEntity", NULL, 'e' ); +const idEventDef EV_Thread_GetTraceJoint( "getTraceJoint", NULL, 's' ); +const idEventDef EV_Thread_GetTraceBody( "getTraceBody", NULL, 's' ); +const idEventDef EV_Thread_FadeIn( "fadeIn", "vf" ); +const idEventDef EV_Thread_FadeOut( "fadeOut", "vf" ); +const idEventDef EV_Thread_FadeTo( "fadeTo", "vff" ); +const idEventDef EV_Thread_StartMusic( "music", "s" ); +const idEventDef EV_Thread_Error( "error", "s" ); +const idEventDef EV_Thread_Warning( "warning", "s" ); +const idEventDef EV_Thread_StrLen( "strLength", "s", 'd' ); +const idEventDef EV_Thread_StrLeft( "strLeft", "sd", 's' ); +const idEventDef EV_Thread_StrRight( "strRight", "sd", 's' ); +const idEventDef EV_Thread_StrSkip( "strSkip", "sd", 's' ); +const idEventDef EV_Thread_StrMid( "strMid", "sdd", 's' ); +const idEventDef EV_Thread_StrToFloat( "strToFloat", "s", 'f' ); +const idEventDef EV_Thread_RadiusDamage( "radiusDamage", "vEEEsf" ); +const idEventDef EV_Thread_IsClient( "isClient", NULL, 'f' ); +const idEventDef EV_Thread_IsMultiplayer( "isMultiplayer", NULL, 'f' ); +const idEventDef EV_Thread_GetFrameTime( "getFrameTime", NULL, 'f' ); +const idEventDef EV_Thread_GetTicsPerSecond( "getTicsPerSecond", NULL, 'f' ); +const idEventDef EV_Thread_DebugLine( "debugLine", "vvvf" ); +const idEventDef EV_Thread_DebugArrow( "debugArrow", "vvvdf" ); +const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); +const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); +const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); +const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); + +// RAVEN BEGIN +// kfuller: added everything below the above block +const idEventDef EV_Thread_SetSpawnVector( "setSpawnVector", "sv" ); +const idEventDef EV_Thread_ArcSine( "asin", "f", 'f' ); +const idEventDef EV_Thread_ArcCosine( "acos", "f", 'f' ); +const idEventDef EV_Thread_VecRotate( "vecRotate", "vv", 'v'); +const idEventDef EV_Thread_ClearSignalAllThreads( "clearSignalAllThreads", "de" ); +// bgeisler: added everything below the added block that was added by keith +const idEventDef EV_Thread_PlayWorldEffect( "playWorldEffect", "svv"); +// abahr: +const idEventDef EV_Thread_ReferenceScriptObjectProxy( "refProxy", "s", 'e' ); +const idEventDef EV_Thread_ReleaseScriptObjectProxy( "releaseProxy", "s" ); +const idEventDef EV_Thread_ClampFloat( "clampFloat", "fff", 'f' ); +const idEventDef EV_Thread_MinFloat( "minFloat", "ff", 'f' ); +const idEventDef EV_Thread_MaxFloat( "maxFloat", "ff", 'f' ); +const idEventDef EV_Thread_StrFind( "strFind", "ss", 'f' ); +const idEventDef EV_Thread_RandomInt( "randomInt", "f", 'f' ); +// rjohnson: new blur special effect +const idEventDef EV_Thread_SetSpecialEffect( "setSpecialEffect", "dd" ); +const idEventDef EV_Thread_SetSpecialEffectParm( "setSpecialEffectParm", "ddf" ); +// asalmon: award achievement +const idEventDef EV_Thread_AwardAchievement( "awardAchievement", "s" ); +// twhitaker: ceil, floor and intVal +const idEventDef EV_Thread_Ceil( "ceil", "f", 'f' ); +const idEventDef EV_Thread_Floor( "floor", "f", 'f' ); +const idEventDef EV_Thread_ToInt( "intVal", "f", 'f' ); +// jdischler: send named event string to specified gui +const idEventDef EV_Thread_SendNamedEvent( "sendNamedEvent", "ds" ); +// material streaming +const idEventDef EV_Thread_BeginManualStreaming( "beginManualStreaming" ); +const idEventDef EV_Thread_EndManualStreaming( "endManualStreaming" ); + +// nrausch: change material sort order on the fly +const idEventDef EV_Thread_SetMatSort( "setMatSort", "ss", 0 ); +// RAVEN END + +CLASS_DECLARATION( idClass, idThread ) + EVENT( EV_Thread_Execute, idThread::Event_Execute ) + EVENT( EV_Thread_TerminateThread, idThread::Event_TerminateThread ) + EVENT( EV_Thread_Pause, idThread::Event_Pause ) + EVENT( EV_Thread_Wait, idThread::Event_Wait ) + EVENT( EV_Thread_WaitFrame, idThread::Event_WaitFrame ) + EVENT( EV_Thread_WaitFor, idThread::Event_WaitFor ) + EVENT( EV_Thread_WaitForThread, idThread::Event_WaitForThread ) + EVENT( EV_Thread_Print, idThread::Event_Print ) + EVENT( EV_Thread_PrintLn, idThread::Event_PrintLn ) + EVENT( EV_Thread_Say, idThread::Event_Say ) + EVENT( EV_Thread_Assert, idThread::Event_Assert ) + EVENT( EV_Thread_Trigger, idThread::Event_Trigger ) + EVENT( EV_Thread_SetCvar, idThread::Event_SetCvar ) + EVENT( EV_Thread_GetCvar, idThread::Event_GetCvar ) + EVENT( EV_Thread_Random, idThread::Event_Random ) + EVENT( EV_Thread_GetTime, idThread::Event_GetTime ) + EVENT( EV_Thread_KillThread, idThread::Event_KillThread ) + EVENT( EV_Thread_SetThreadName, idThread::Event_SetThreadName ) + EVENT( EV_Thread_GetEntity, idThread::Event_GetEntity ) + EVENT( EV_Thread_Spawn, idThread::Event_Spawn ) + EVENT( EV_Thread_CopySpawnArgs, idThread::Event_CopySpawnArgs ) + EVENT( EV_Thread_SetSpawnArg, idThread::Event_SetSpawnArg ) + EVENT( EV_Thread_SpawnString, idThread::Event_SpawnString ) + EVENT( EV_Thread_SpawnFloat, idThread::Event_SpawnFloat ) + EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) + EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) + EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) + EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) + EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) + EVENT( EV_Thread_AngToForward, idThread::Event_AngToForward ) + EVENT( EV_Thread_AngToRight, idThread::Event_AngToRight ) + EVENT( EV_Thread_AngToUp, idThread::Event_AngToUp ) + EVENT( EV_Thread_Sine, idThread::Event_GetSine ) + EVENT( EV_Thread_Cosine, idThread::Event_GetCosine ) + EVENT( EV_Thread_SquareRoot, idThread::Event_GetSquareRoot ) + EVENT( EV_Thread_Normalize, idThread::Event_VecNormalize ) + EVENT( EV_Thread_VecLength, idThread::Event_VecLength ) + EVENT( EV_Thread_VecDotProduct, idThread::Event_VecDotProduct ) + EVENT( EV_Thread_VecCrossProduct, idThread::Event_VecCrossProduct ) + EVENT( EV_Thread_VecToAngles, idThread::Event_VecToAngles ) + EVENT( EV_Thread_OnSignal, idThread::Event_OnSignal ) + EVENT( EV_Thread_ClearSignal, idThread::Event_ClearSignalThread ) + EVENT( EV_Thread_SetCamera, idThread::Event_SetCamera ) + EVENT( EV_Thread_FirstPerson, idThread::Event_FirstPerson ) + EVENT( EV_Thread_Trace, idThread::Event_Trace ) + EVENT( EV_Thread_TracePoint, idThread::Event_TracePoint ) + EVENT( EV_Thread_GetTraceFraction, idThread::Event_GetTraceFraction ) + EVENT( EV_Thread_GetTraceEndPos, idThread::Event_GetTraceEndPos ) + EVENT( EV_Thread_GetTraceNormal, idThread::Event_GetTraceNormal ) + EVENT( EV_Thread_GetTraceEntity, idThread::Event_GetTraceEntity ) + EVENT( EV_Thread_GetTraceJoint, idThread::Event_GetTraceJoint ) + EVENT( EV_Thread_GetTraceBody, idThread::Event_GetTraceBody ) + EVENT( EV_Thread_FadeIn, idThread::Event_FadeIn ) + EVENT( EV_Thread_FadeOut, idThread::Event_FadeOut ) + EVENT( EV_Thread_FadeTo, idThread::Event_FadeTo ) + EVENT( EV_SetShaderParm, idThread::Event_SetShaderParm ) + EVENT( EV_Thread_StartMusic, idThread::Event_StartMusic ) + EVENT( EV_Thread_Warning, idThread::Event_Warning ) + EVENT( EV_Thread_Error, idThread::Event_Error ) + EVENT( EV_Thread_StrLen, idThread::Event_StrLen ) + EVENT( EV_Thread_StrLeft, idThread::Event_StrLeft ) + EVENT( EV_Thread_StrRight, idThread::Event_StrRight ) + EVENT( EV_Thread_StrSkip, idThread::Event_StrSkip ) + EVENT( EV_Thread_StrMid, idThread::Event_StrMid ) + EVENT( EV_Thread_StrToFloat, idThread::Event_StrToFloat ) + EVENT( EV_Thread_RadiusDamage, idThread::Event_RadiusDamage ) + EVENT( EV_Thread_IsClient, idThread::Event_IsClient ) + EVENT( EV_Thread_IsMultiplayer, idThread::Event_IsMultiplayer ) + EVENT( EV_Thread_GetFrameTime, idThread::Event_GetFrameTime ) + EVENT( EV_Thread_GetTicsPerSecond, idThread::Event_GetTicsPerSecond ) + EVENT( EV_CacheSoundShader, idThread::Event_CacheSoundShader ) + EVENT( EV_Thread_DebugLine, idThread::Event_DebugLine ) + EVENT( EV_Thread_DebugArrow, idThread::Event_DebugArrow ) + EVENT( EV_Thread_DebugCircle, idThread::Event_DebugCircle ) + EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) + EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) + EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) + +// RAVEN BEGIN +// kfuller: added events + EVENT( EV_Thread_SetSpawnVector, idThread::Event_SetSpawnVector ) + EVENT( EV_Thread_ArcSine, idThread::Event_GetArcSine ) + EVENT( EV_Thread_ArcCosine, idThread::Event_GetArcCosine ) + EVENT( EV_Thread_VecRotate, idThread::Event_VecRotate ) + EVENT( EV_Thread_ClearSignalAllThreads, idThread::Event_ClearSignalAllThreads ) +// nmckenzie: added signal strings + EVENT( EV_Thread_PlayWorldEffect, idThread::Event_PlayWorldEffect ) +// abahr: + EVENT( EV_Thread_ReferenceScriptObjectProxy, idThread::Event_ReferenceScriptObjectProxy ) + EVENT( EV_Thread_ReleaseScriptObjectProxy, idThread::Event_ReleaseScriptObjectProxy ) + EVENT( EV_Thread_ClampFloat, idThread::Event_ClampFloat ) + EVENT( EV_Thread_MinFloat, idThread::Event_MinFloat ) + EVENT( EV_Thread_MaxFloat, idThread::Event_MaxFloat ) + EVENT( EV_Thread_StrFind, idThread::Event_StrFind ) + EVENT( EV_Thread_RandomInt, idThread::Event_RandomInt ) +// rjohnson: new blur special effect + EVENT( EV_Thread_SetSpecialEffect, idThread::Event_SetSpecialEffect ) + EVENT( EV_Thread_SetSpecialEffectParm, idThread::Event_SetSpecialEffectParm ) +// asalmon: award a Xenon achievement + EVENT( EV_Thread_AwardAchievement, idThread::Event_AwardAchievement ) +// twhitaker: ceil and floor + EVENT( EV_Thread_Ceil, idThread::Event_GetCeil ) + EVENT( EV_Thread_Floor, idThread::Event_GetFloor ) + EVENT( EV_Thread_ToInt, idThread::Event_ToInt ) +// jdischler: send named event string to specified gui + EVENT( EV_Thread_SendNamedEvent, idThread::Event_SendNamedEvent ) +// manual streaming + EVENT( EV_Thread_BeginManualStreaming, idThread::Event_BeginManualStreaming ) + EVENT( EV_Thread_EndManualStreaming, idThread::Event_EndManualStreaming ) + EVENT( EV_Thread_SetMatSort, idThread::Event_SetMatSort ) +// RAVEN END +END_CLASS + +idThread *idThread::currentThread = NULL; +int idThread::threadIndex = 0; +idList idThread::threadList; +trace_t idThread::trace; + +/* +================ +idThread::CurrentThread +================ +*/ +idThread *idThread::CurrentThread( void ) { + return currentThread; +} + +/* +================ +idThread::CurrentThreadNum +================ +*/ +int idThread::CurrentThreadNum( void ) { + if ( currentThread ) { + return currentThread->GetThreadNum(); + } else { + return 0; + } +} + +/* +================ +idThread::BeginMultiFrameEvent +================ +*/ +bool idThread::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::BeginMultiFrameEvent called without a current thread" ); + } + return currentThread->interpreter.BeginMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::EndMultiFrameEvent +================ +*/ +void idThread::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::EndMultiFrameEvent called without a current thread" ); + } + currentThread->interpreter.EndMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread() { + Init(); + SetThreadName( va( "thread_%d", threadIndex ) ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idEntity *self, const function_t *func ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.EnterObjectFunction( self, func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( const function_t *func ) { + assert( func ); + + Init(); + SetThreadName( func->Name() ); + interpreter.EnterFunction( func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, const function_t *func, int args ) { + Init(); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::~idThread +================ +*/ +idThread::~idThread() { + idThread *thread; + int i; + int n; + + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: end thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } + threadList.Remove( this ); + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->WaitingOnThread() == this ) { + thread->ThreadCallback( this ); + } + } + + if ( currentThread == this ) { + currentThread = NULL; + } +} + +/* +================ +idThread::ManualDelete +================ +*/ +void idThread::ManualDelete( void ) { + interpreter.terminateOnExit = false; +} + +/* +================ +idThread::Save +================ +*/ +void idThread::Save( idSaveGame *savefile ) const { + savefile->WriteObject( currentThread ); + + savefile->WriteObject( waitingForThread ); + savefile->WriteInt( waitingFor ); + savefile->WriteInt( waitingUntil ); + interpreter.Save( savefile ); + + savefile->WriteDict( &spawnArgs ); + + savefile->WriteInt( threadNum ); + savefile->WriteString( threadName ); + + savefile->WriteInt( lastExecuteTime ); + savefile->WriteInt( creationTime ); + + savefile->WriteBool( manualControl ); + + savefile->WriteInt( threadIndex ); +} + +/* +================ +idThread::Restore +================ +*/ +void idThread::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( currentThread ) ); + + savefile->ReadObject( reinterpret_cast( waitingForThread ) ); + savefile->ReadInt( waitingFor ); + savefile->ReadInt( waitingUntil ); + interpreter.Restore( savefile ); + + savefile->ReadDict( &spawnArgs ); + + savefile->ReadInt( threadNum ); + savefile->ReadString( threadName ); + + savefile->ReadInt( lastExecuteTime ); + savefile->ReadInt( creationTime ); + + savefile->ReadBool( manualControl ); + + savefile->ReadInt( threadIndex ); +} + +/* +================ +idThread::Init +================ +*/ +void idThread::Init( void ) { + // create a unique threadNum + do { + threadIndex++; + if ( threadIndex == 0 ) { + threadIndex = 1; + } + } while( GetThread( threadIndex ) ); + + threadNum = threadIndex; + threadList.Append( this ); + + creationTime = gameLocal.time; + lastExecuteTime = 0; + manualControl = false; + + ClearWaitFor(); + + interpreter.SetThread( this ); +} + +/* +================ +idThread::GetThread +================ +*/ +idThread *idThread::GetThread( int num ) { + int i; + int n; + idThread *thread; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->GetThreadNum() == num ) { + return thread; + } + } + + return NULL; +} + +/* +================ +idThread::DisplayInfo +================ +*/ +void idThread::DisplayInfo( void ) { + gameLocal.Printf( + "%12i: '%s'\n" + " File: %s(%d)\n" + " Created: %d (%d ms ago)\n" + " Status: ", + threadNum, threadName.c_str(), + interpreter.CurrentFile(), interpreter.CurrentLine(), + creationTime, gameLocal.time - creationTime ); + + if ( interpreter.threadDying ) { + gameLocal.Printf( "Dying\n" ); + } else if ( interpreter.doneProcessing ) { + gameLocal.Printf( + "Paused since %d (%d ms)\n" + " Reason: ", lastExecuteTime, gameLocal.time - lastExecuteTime ); + if ( waitingForThread ) { + gameLocal.Printf( "Waiting for thread #%3i '%s'\n", waitingForThread->GetThreadNum(), waitingForThread->GetThreadName() ); + } else if ( ( waitingFor != ENTITYNUM_NONE ) && ( gameLocal.entities[ waitingFor ] ) ) { + gameLocal.Printf( "Waiting for entity #%3i '%s'\n", waitingFor, gameLocal.entities[ waitingFor ]->name.c_str() ); + } else if ( waitingUntil ) { + gameLocal.Printf( "Waiting until %d (%d ms total wait time)\n", waitingUntil, waitingUntil - lastExecuteTime ); + } else { + gameLocal.Printf( "None\n" ); + } + } else { + gameLocal.Printf( "Processing\n" ); + } + + interpreter.DisplayInfo(); + + gameLocal.Printf( "\n" ); +} + +/* +================ +idThread::ListThreads_f +================ +*/ +void idThread::ListThreads_f( const idCmdArgs &args ) { + int i; + int n; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + //threadList[ i ]->DisplayInfo(); + gameLocal.Printf( "%3i: %-20s : %s(%d)\n", threadList[ i ]->threadNum, threadList[ i ]->threadName.c_str(), threadList[ i ]->interpreter.CurrentFile(), threadList[ i ]->interpreter.CurrentLine() ); + } + gameLocal.Printf( "%d active threads\n\n", n ); +} + +/* +================ +idThread::Restart +================ +*/ +void idThread::Restart( void ) { + int i; + int n; + + // reset the threadIndex + threadIndex = 0; + + currentThread = NULL; + n = threadList.Num(); + for( i = n - 1; i >= 0; i-- ) { + delete threadList[ i ]; + } + threadList.Clear(); + + memset( &trace, 0, sizeof( trace ) ); + trace.c.entityNum = ENTITYNUM_NONE; +} + +/* +================ +idThread::DelayedStart +================ +*/ +void idThread::DelayedStart( int delay ) { + CancelEvents( &EV_Thread_Execute ); + if ( gameLocal.time <= 0 ) { + delay++; + } + PostEventMS( &EV_Thread_Execute, delay ); +} + +/* +================ +idThread::Start +================ +*/ +bool idThread::Start( void ) { + bool result; + + CancelEvents( &EV_Thread_Execute ); + result = Execute(); + + return result; +} + +/* +================ +idThread::SetThreadName +================ +*/ +void idThread::SetThreadName( const char *name ) { + threadName = name; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( int threadnum, idEntity *obj ) { + idThread *thread; + + if ( !threadnum ) { + return; + } + + thread = GetThread( threadnum ); + if ( thread ) { + thread->ObjectMoveDone( obj ); + } +} + +/* +================ +idThread::End +================ +*/ +void idThread::End( void ) { + // Tell thread to die. It will exit on its own. + Pause(); + interpreter.threadDying = true; +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( const char *name ) { + int i; + int num; + int len; + const char *ptr; + idThread *thread; + + // see if the name uses a wild card + ptr = strchr( name, '*' ); + if ( ptr ) { + len = ptr - name; + } else { + len = strlen( name ); + } + + // kill only those threads whose name matches name + num = threadList.Num(); + for( i = 0; i < num; i++ ) { + thread = threadList[ i ]; + if ( !idStr::Cmpn( thread->GetThreadName(), name, len ) ) { + thread->End(); + } + } +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( thread ) { + // Tell thread to die. It will delete itself on it's own. + thread->End(); + } +} + +/* +================ +idThread::Execute +================ +*/ +bool idThread::Execute( void ) { + idThread *oldThread; + bool done; + + if ( manualControl && ( waitingUntil > gameLocal.time ) ) { + return false; + } + + oldThread = currentThread; + currentThread = this; + + lastExecuteTime = gameLocal.time; + ClearWaitFor(); + done = interpreter.Execute(); + if ( done ) { + End(); + if ( interpreter.terminateOnExit ) { + PostEventMS( &EV_Remove, 0 ); + } + } else if ( !manualControl ) { + if ( waitingUntil > lastExecuteTime ) { + PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); + } else if ( interpreter.MultiFrameEventInProgress() ) { + PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + } + } + + currentThread = oldThread; + + return done; +} + +/* +================ +idThread::IsWaiting + +Checks if thread is still waiting for some event to occur. +================ +*/ +bool idThread::IsWaiting( void ) { + if ( waitingForThread || ( waitingFor != ENTITYNUM_NONE ) ) { + return true; + } + + if ( waitingUntil && ( waitingUntil > gameLocal.time ) ) { + return true; + } + + return false; +} + +// RAVEN BEGIN +// bgeisler: +/* +================ +idThread::ListFunctions +================ +*/ +void idThread::ListStates(void) +{ + gameLocal.program.ListStates(); +} + +// abahr: added helper functions +/* +================ +idThread::ClearStack +================ +*/ +void idThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +idThread::PushInt +================ +*/ +void idThread::PushInt( int value ) { + interpreter.Push( value ); +} + +/* +================ +idThread::PushFloat +================ +*/ +void idThread::PushFloat( float value ) { + interpreter.Push( *(int*)&value ); +} + +/* +================ +idThread::PushVec3 +================ +*/ +void idThread::PushVec3( const idVec3& value ) { + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + PushFloat( value[ix] ); + } +} + +/* +================ +idThread::PushEntity +================ +*/ +void idThread::PushEntity( const idEntity* ent ) { + assert( ent ); + + PushInt( ent->entityNumber + 1 ); +} + +/* +================ +idThread::PushString +================ +*/ +void idThread::PushString( const char* string ) { + interpreter.PushString( string ); +} + +/* +================ +idThread::PushBool +================ +*/ +void idThread::PushBool( bool value ) { + PushInt( (int)value ); +} +// RAVEN END + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( const function_t *func, bool clearStack ) { + ClearWaitFor(); + interpreter.EnterFunction( func, clearStack ); +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( idEntity *self, const function_t *func, bool clearStack ) { + assert( self ); + ClearWaitFor(); + interpreter.EnterObjectFunction( self, func, clearStack ); +} + +/* +================ +idThread::ClearWaitFor +================ +*/ +void idThread::ClearWaitFor( void ) { + waitingFor = ENTITYNUM_NONE; + waitingForThread = NULL; + waitingUntil = 0; +} + +/* +================ +idThread::IsWaitingFor +================ +*/ +bool idThread::IsWaitingFor( idEntity *obj ) { + assert( obj ); + return waitingFor == obj->entityNumber; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( idEntity *obj ) { + assert( obj ); + + if ( IsWaitingFor( obj ) ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::ThreadCallback +================ +*/ +void idThread::ThreadCallback( idThread *thread ) { + if ( interpreter.threadDying ) { + return; + } + + if ( thread == waitingForThread ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::Event_SetThreadName +================ +*/ +void idThread::Event_SetThreadName( const char *name ) { + SetThreadName( name ); +} + +/* +================ +idThread::Error +================ +*/ +void idThread::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Error( text ); +} + +/* +================ +idThread::Warning +================ +*/ +void idThread::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Warning( text ); +} + +/* +================ +idThread::ReturnString +================ +*/ +void idThread::ReturnString( const char *text ) { + gameLocal.program.ReturnString( text ); +} + +/* +================ +idThread::ReturnFloat +================ +*/ +void idThread::ReturnFloat( float value ) { + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnInt +================ +*/ +void idThread::ReturnInt( int value ) { + // true integers aren't supported in the compiler, + // so int values are stored as floats + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnVector +================ +*/ +void idThread::ReturnVector( idVec3 const &vec ) { + gameLocal.program.ReturnVector( vec ); +} + +/* +================ +idThread::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idThread::ReturnEntity( const idEntity *ent ) { + gameLocal.program.ReturnEntity( ent ); +} + +/* +================ +idThread::Event_Execute +================ +*/ +void idThread::Event_Execute( void ) { + Execute(); +} + +/* +================ +idThread::Pause +================ +*/ +void idThread::Pause( void ) { + ClearWaitFor(); + interpreter.doneProcessing = true; +} + +/* +================ +idThread::WaitMS +================ +*/ +void idThread::WaitMS( int time ) { + Pause(); +// RAVEN BEGIN +// bdube: add 1 ms to ensure a time of zero still works + waitingUntil = gameLocal.time + time + 1; +// RAVEN END +} + +/* +================ +idThread::WaitSec +================ +*/ +void idThread::WaitSec( float time ) { + WaitMS( SEC2MS( time ) ); +} + +/* +================ +idThread::WaitFrame +================ +*/ +void idThread::WaitFrame( void ) { + Pause(); + + // manual control threads don't set waitingUntil so that they can be run again + // that frame if necessary. + if ( !manualControl ) { + waitingUntil = gameLocal.time + gameLocal.msec; + } +} + +/*********************************************************************** + + Script callable events + +***********************************************************************/ + +/* +================ +idThread::Event_TerminateThread +================ +*/ +void idThread::Event_TerminateThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + KillThread( num ); +} + +/* +================ +idThread::Event_Pause +================ +*/ +void idThread::Event_Pause( void ) { + Pause(); +} + +/* +================ +idThread::Event_Wait +================ +*/ +void idThread::Event_Wait( float time ) { + WaitSec( time ); +} + +/* +================ +idThread::Event_WaitFrame +================ +*/ +void idThread::Event_WaitFrame( void ) { + WaitFrame(); +} + +/* +================ +idThread::Event_WaitFor +================ +*/ +void idThread::Event_WaitFor( idEntity *ent ) { + if ( ent && ent->RespondsTo( EV_Thread_SetCallback ) ) { + ent->ProcessEvent( &EV_Thread_SetCallback ); + if ( gameLocal.program.GetReturnedInteger() ) { + Pause(); + waitingFor = ent->entityNumber; + } + } +} + +/* +================ +idThread::Event_WaitForThread +================ +*/ +void idThread::Event_WaitForThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( !thread ) { + if ( g_debugScript.GetBool() ) { + // just print a warning and continue executing + Warning( "Thread %d not running", num ); + } + } else { + Pause(); + waitingForThread = thread; + } +} + +/* +================ +idThread::Event_Print +================ +*/ +void idThread::Event_Print( const char *text ) { + gameLocal.Printf( "%s", text ); +} + +/* +================ +idThread::Event_PrintLn +================ +*/ +void idThread::Event_PrintLn( const char *text ) { + gameLocal.Printf( "%s\n", text ); +} + +/* +================ +idThread::Event_Say +================ +*/ +void idThread::Event_Say( const char *text ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); +} + +/* +================ +idThread::Event_Assert +================ +*/ +void idThread::Event_Assert( float value ) { +// RAVEN BEGIN +// jnewquist: assert with a useful callstack, since this is script +#ifdef _DEBUG + if ( !value ) { + Error("Script assert fired"); + } +#endif +// RAVEN END +} + +/* +================ +idThread::Event_Trigger +================ +*/ +void idThread::Event_Trigger( idEntity *ent ) { + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + ent->TriggerGuis(); + } +} + +/* +================ +idThread::Event_SetCvar +================ +*/ +void idThread::Event_SetCvar( const char *name, const char *value ) const { + cvarSystem->SetCVarString( name, value ); +} + +/* +================ +idThread::Event_GetCvar +================ +*/ +void idThread::Event_GetCvar( const char *name ) const { + ReturnString( cvarSystem->GetCVarString( name ) ); +} + +/* +================ +idThread::Event_Random +================ +*/ +void idThread::Event_Random( float range ) const { + float result; + + result = gameLocal.random.RandomFloat(); + ReturnFloat( range * result ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idThread::Event_RandomInt +================ +*/ +void idThread::Event_RandomInt( float range ) const { + ReturnFloat( rvRandom::irand(0, range) ); +} + + +// rjohnson: new blur special effect +void idThread::Event_SetSpecialEffect( int Effect, int Enabled ) { + renderSystem->SetSpecialEffect( (ESpecialEffectType)Effect, !!Enabled ); +} + +void idThread::Event_SetSpecialEffectParm( int Effect, int Parm, float Value ) { + renderSystem->SetSpecialEffectParm( (ESpecialEffectType)Effect, Parm, Value ); +} + +// RAVEN END + +/* +================ +idThread::Event_GetTime +================ +*/ +void idThread::Event_GetTime( void ) { + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); +} + +/* +================ +idThread::Event_KillThread +================ +*/ +void idThread::Event_KillThread( const char *name ) { + KillThread( name ); +} + +/* +================ +idThread::Event_GetEntity +================ +*/ +void idThread::Event_GetEntity( const char *name ) { + int entnum; + idEntity *ent; + + assert( name ); + + if ( name[ 0 ] == '*' ) { + entnum = atoi( &name[ 1 ] ); + if ( ( entnum < 0 ) || ( entnum >= MAX_GENTITIES ) ) { + Error( "Entity number in string out of range." ); + } + ReturnEntity( gameLocal.entities[ entnum ] ); + } else { + ent = gameLocal.FindEntity( name ); + ReturnEntity( ent ); + } +} + +/* +================ +idThread::Event_Spawn +================ +*/ +void idThread::Event_Spawn( const char *classname ) { + idEntity *ent; + + spawnArgs.Set( "classname", classname ); + gameLocal.SpawnEntityDef( spawnArgs, &ent ); + ReturnEntity( ent ); + spawnArgs.Clear(); +} + +/* +================ +idThread::Event_CopySpawnArgs +================ +*/ +void idThread::Event_CopySpawnArgs( idEntity *ent ) { + spawnArgs.Copy( ent->spawnArgs ); +} + +/* +================ +idThread::Event_SetSpawnArg +================ +*/ +void idThread::Event_SetSpawnArg( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +// RAVEN BEGIN +// kfuller: added events + +/* +================ +idThread::Event_SetSpawnVector +================ +*/ +void idThread::Event_SetSpawnVector( const char *key, idVec3 &vec) { + spawnArgs.SetVector(key, vec); +} +// RAVEN END + +/* +================ +idThread::Event_SpawnString +================ +*/ +void idThread::Event_SpawnString( const char *key, const char *defaultvalue ) { + const char *result; + + spawnArgs.GetString( key, defaultvalue, &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_SpawnFloat +================ +*/ +void idThread::Event_SpawnFloat( const char *key, float defaultvalue ) { + float result; + + spawnArgs.GetFloat( key, va( "%f", defaultvalue ), result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_SpawnVector +================ +*/ +void idThread::Event_SpawnVector( const char *key, idVec3 &defaultvalue ) { + idVec3 result; + + spawnArgs.GetVector( key, va( "%f %f %f", defaultvalue.x, defaultvalue.y, defaultvalue.z ), result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_ClearPersistantArgs +================ +*/ +void idThread::Event_ClearPersistantArgs( void ) { + gameLocal.persistentLevelInfo.Clear(); +} + + +/* +================ +idThread::Event_SetPersistantArg +================ +*/ +void idThread::Event_SetPersistantArg( const char *key, const char *value ) { + gameLocal.persistentLevelInfo.Set( key, value ); +} + +/* +================ +idThread::Event_GetPersistantString +================ +*/ +void idThread::Event_GetPersistantString( const char *key ) { + const char *result; + + gameLocal.persistentLevelInfo.GetString( key, "", &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_GetPersistantFloat +================ +*/ +void idThread::Event_GetPersistantFloat( const char *key ) { + float result; + + gameLocal.persistentLevelInfo.GetFloat( key, "0", result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_GetPersistantVector +================ +*/ +void idThread::Event_GetPersistantVector( const char *key ) { + idVec3 result; + + gameLocal.persistentLevelInfo.GetVector( key, "0 0 0", result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_AngToForward +================ +*/ +void idThread::Event_AngToForward( idAngles &ang ) { + ReturnVector( ang.ToForward() ); +} + +/* +================ +idThread::Event_AngToRight +================ +*/ +void idThread::Event_AngToRight( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_AngToUp +================ +*/ +void idThread::Event_AngToUp( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, NULL, &vec ); + ReturnVector( vec ); +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_GetArcSine +================ +*/ +void idThread::Event_GetArcSine( float sinValue ) { + ReturnFloat( asinf( sinValue ) ); +} + +/* +================ +idThread::Event_GetArcCosine +================ +*/ +void idThread::Event_GetArcCosine( float cosValue ) { + ReturnFloat( acosf( cosValue ) ); +} +// RAVEN END + +/* +================ +idThread::Event_GetSine +================ +*/ +void idThread::Event_GetSine( float angle ) { + ReturnFloat( idMath::Sin( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetCosine +================ +*/ +void idThread::Event_GetCosine( float angle ) { + ReturnFloat( idMath::Cos( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetSquareRoot +================ +*/ +void idThread::Event_GetSquareRoot( float theSquare ) { + ReturnFloat( idMath::Sqrt( theSquare ) ); +} + +/* +================ +idThread::Event_VecNormalize +================ +*/ +void idThread::Event_VecNormalize( idVec3 &vec ) { + idVec3 n; + + n = vec; + n.Normalize(); + ReturnVector( n ); +} + +/* +================ +idThread::Event_VecLength +================ +*/ +void idThread::Event_VecLength( idVec3 &vec ) { + ReturnFloat( vec.Length() ); +} + +/* +================ +idThread::Event_VecDotProduct +================ +*/ +void idThread::Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnFloat( vec1 * vec2 ); +} + +/* +================ +idThread::Event_VecCrossProduct +================ +*/ +void idThread::Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnVector( vec1.Cross( vec2 ) ); +} + +/* +================ +idThread::Event_VecToAngles +================ +*/ +void idThread::Event_VecToAngles( idVec3 &vec ) { + idAngles ang = vec.ToAngles(); + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +// RAVEN BEGIN +// kef 12/2/02 -- made these into events + +void idThread::Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch) +{ + idVec3 vecResult; + +// VectorRotate3(vecToBeRotated, rotateHowMuch, vecResult); + gameLocal.Printf("'vecRotate' doesn't work -- I guess Keith should find a replacement for VectorRotate3 now...\n"); + ReturnVector(vecResult); +} + +// RAVEN END + +/* +================ +idThread::Event_OnSignal +================ +*/ +void idThread::Event_OnSignal( int signal, idEntity *ent, const char *func ) { + const function_t *function; + + assert( func ); + + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + function = gameLocal.program.FindFunction( func ); + if ( !function ) { + Error( "Function '%s' not found", func ); + } + + ent->SetSignal( ( signalNum_t )signal, this, function ); +} + +/* +================ +idThread::Event_ClearSignalThread +================ +*/ +void idThread::Event_ClearSignalThread( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignalThread( ( signalNum_t )signal, this ); +} + +// RAVEN BEGIN +// kfuller: added +/* +================ +idThread::Event_ClearSignalAllThreads +================ +*/ +void idThread::Event_ClearSignalAllThreads( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignal(this, ( signalNum_t )signal); +} +// RAVEN END + +/* +================ +idThread::Event_SetCamera +================ +*/ +void idThread::Event_SetCamera( idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idCamera::GetClassType() ) ) { +// RAVEN END + Error( "Entity is not a camera" ); + return; + } + + gameLocal.SetCamera( ( idCamera * )ent ); +} + +/* +================ +idThread::Event_FirstPerson +================ +*/ +void idThread::Event_FirstPerson( void ) { + gameLocal.SetCamera( NULL ); +} + +/* +================ +idThread::Event_Trace +================ +*/ +void idThread::Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ) { + if ( mins == vec3_origin && maxs == vec3_origin ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); + } else { + gameLocal.TraceBounds( NULL, trace, start, end, idBounds( mins, maxs ), contents_mask, passEntity ); +// RAVEN END + } + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_TracePoint +================ +*/ +void idThread::Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); +// RAVEN END + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceFraction +================ +*/ +void idThread::Event_GetTraceFraction( void ) { + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceEndPos +================ +*/ +void idThread::Event_GetTraceEndPos( void ) { + ReturnVector( trace.endpos ); +} + +/* +================ +idThread::Event_GetTraceNormal +================ +*/ +void idThread::Event_GetTraceNormal( void ) { + if ( trace.fraction < 1.0f ) { + ReturnVector( trace.c.normal ); + } else { + ReturnVector( vec3_origin ); + } +} + +/* +================ +idThread::Event_GetTraceEntity +================ +*/ +void idThread::Event_GetTraceEntity( void ) { + if ( trace.fraction < 1.0f ) { + ReturnEntity( gameLocal.entities[ trace.c.entityNum ] ); + } else { + ReturnEntity( ( idEntity * )NULL ); + } +} + +/* +================ +idThread::Event_GetTraceJoint +================ +*/ +void idThread::Event_GetTraceJoint( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + ReturnString( af->GetAnimator()->GetJointName( CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ) ) ); + return; + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_GetTraceBody +================ +*/ +void idThread::Event_GetTraceBody( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + int bodyId = af->BodyForClipModelId( trace.c.id ); + idAFBody *body = af->GetAFPhysics()->GetBody( bodyId ); + if ( body ) { + ReturnString( body->GetName() ); + return; + } + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_FadeIn +================ +*/ +void idThread::Event_FadeIn( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 0.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeOut +================ +*/ +void idThread::Event_FadeOut( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 1.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeTo +================ +*/ +void idThread::Event_FadeTo( idVec3 &color, float alpha, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], alpha ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_SetShaderParm +================ +*/ +void idThread::Event_SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_GLOBAL_SHADER_PARMS ) ) { + Error( "shader parm index (%d) out of range", parmnum ); + } + + gameLocal.globalShaderParms[ parmnum ] = value; +} + +/* +================ +idThread::Event_StartMusic +================ +*/ +void idThread::Event_StartMusic( const char *text ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, text ); +} + +/* +================ +idThread::Event_Warning +================ +*/ +void idThread::Event_Warning( const char *text ) { + Warning( "%s", text ); +} + +/* +================ +idThread::Event_Error +================ +*/ +void idThread::Event_Error( const char *text ) { + Error( "%s", text ); +} + +/* +================ +idThread::Event_StrLen +================ +*/ +void idThread::Event_StrLen( const char *string ) { + int len; + + len = strlen( string ); + idThread::ReturnInt( len ); +} + +/* +================ +idThread::Event_StrLeft +================ +*/ +void idThread::Event_StrLeft( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idStr result( string, 0, num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrRight +================ +*/ +void idThread::Event_StrRight( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idThread::ReturnString( string + len - num ); +} + +/* +================ +idThread::Event_StrSkip +================ +*/ +void idThread::Event_StrSkip( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( string ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( string + num ); +} + +/* +================ +idThread::Event_StrMid +================ +*/ +void idThread::Event_StrMid( const char *string, int start, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + if ( start < 0 ) { + start = 0; + } + len = strlen( string ); + if ( start > len ) { + start = len; + } + + if ( start + num > len ) { + num = len - start; + } + + idStr result( string, start, start + num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrToFloat( const char *string ) +================ +*/ +void idThread::Event_StrToFloat( const char *string ) { + float result; + + result = atof( string ); + idThread::ReturnFloat( result ); +} + +/* +================ +idThread::Event_RadiusDamage +================ +*/ +void idThread::Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ) { + gameLocal.RadiusDamage( origin, inflictor, attacker, ignore, ignore, damageDefName, dmgPower ); +} + +/* +================ +idThread::Event_IsClient +================ +*/ +void idThread::Event_IsClient( void ) { + idThread::ReturnFloat( gameLocal.isClient ); +} + +/* +================ +idThread::Event_IsMultiplayer +================ +*/ +void idThread::Event_IsMultiplayer( void ) { + idThread::ReturnFloat( gameLocal.isMultiplayer ); +} + +/* +================ +idThread::Event_GetFrameTime +================ +*/ +void idThread::Event_GetFrameTime( void ) { + idThread::ReturnFloat( MS2SEC( gameLocal.msec ) ); +} + +/* +================ +idThread::Event_GetTicsPerSecond +================ +*/ +void idThread::Event_GetTicsPerSecond( void ) { + idThread::ReturnFloat( gameLocal.GetMHz() ); +} + +/* +================ +idThread::Event_CacheSoundShader +================ +*/ +void idThread::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idThread::Event_DebugLine +================ +*/ +void idThread::Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ) { + gameRenderWorld->DebugLine( idVec4( color.x, color.y, color.z, 0.0f ), start, end, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugArrow +================ +*/ +void idThread::Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ) { + gameRenderWorld->DebugArrow( idVec4( color.x, color.y, color.z, 0.0f ), start, end, size, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugCircle +================ +*/ +void idThread::Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ) { + gameRenderWorld->DebugCircle( idVec4( color.x, color.y, color.z, 0.0f ), origin, dir, radius, numSteps, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugBounds +================ +*/ +void idThread::Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ) { + gameRenderWorld->DebugBounds( idVec4( color.x, color.y, color.z, 0.0f ), idBounds( mins, maxs ), vec3_origin, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DrawText +================ +*/ +void idThread::Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ) { + gameRenderWorld->DrawText( text, origin, scale, idVec4( color.x, color.y, color.z, 0.0f ), gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), align, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_InfluenceActive +================ +*/ +void idThread::Event_InfluenceActive( void ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player && player->GetInfluenceLevel() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_PlayWorldEffect +================ +*/ +void idThread::Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ) { + gameLocal.PlayEffect ( ( const idDecl * )declManager->FindEffect( effectName ), org, angle.ToMat3() ); +} + +// abahr: +/* +================ +idThread::Event_ReferenceScriptObjectProxy +================ +*/ +void idThread::Event_ReferenceScriptObjectProxy( const char* scriptObjectName ) { + ReturnEntity( gameLocal.ReferenceScriptObjectProxy(scriptObjectName) ); + Event_WaitFrame();// Needed because the constructor call is delayed by one frame +} + +/* +================ +idThread::Event_ReleaseScriptObjectProxy +================ +*/ +void idThread::Event_ReleaseScriptObjectProxy( const char* proxyName ) { + gameLocal.ReleaseScriptObjectProxy( proxyName ); + Event_WaitFrame();// Needed because the destructor call is delayed by one frame +} + +/* +================ +idThread::Event_ClampFloat +================ +*/ +void idThread::Event_ClampFloat( float min, float max, float val ) { + ReturnFloat( idMath::ClampFloat(min, max, val) ); +} + +/* +================ +idThread::Event_MinFloat +================ +*/ +void idThread::Event_MinFloat( float val1, float val2 ) { + ReturnFloat( Min(val1, val2) ); +} + +/* +================ +idThread::Event_MaxFloat +================ +*/ +void idThread::Event_MaxFloat( float val1, float val2 ) { + ReturnFloat( Max(val1, val2) ); +} + +/* +================ +idThread::Event_StrFind +================ +*/ +void idThread::Event_StrFind( idStr& sourceStr, idStr& subStr ) { + idThread::ReturnInt( sourceStr.Find(subStr.c_str()) ); +} + +// asalmon: award achievement +/* +================ +idThread::Event_AwardAchievement +================ +*/ +void idThread::Event_AwardAchievement( const char *name ) { +#ifdef _XENON + Live()->AwardAchievement(name); +#endif +} +// twhitaker: ceil, floor and intVal +/* +================ +idThread::Event_GetCeil +================ +*/ +void idThread::Event_GetCeil( float val ) { + ReturnFloat( idMath::Ceil( val ) ); +} + +/* +================ +idThread::Event_GetFloor +================ +*/ +void idThread::Event_GetFloor( float val ) { + ReturnFloat( idMath::Floor( val ) ); +} + +/* +================ +idThread::Event_ToInt +================ +*/ +void idThread::Event_ToInt( float val ) { + ReturnFloat( idMath::Ftoi( val ) ); +// ReturnFloat( idMath::FtoiFast( val ) ); +} + +// jdischler: send named event string to specified gui +/* +=============================== +idThread::Event_SendNamedEvent +=============================== +*/ +void idThread::Event_SendNamedEvent( int guiEnum, const char *namedEvent ) { + + typedef enum { + SNE_PLAYERHUD = 0, + SNE_CINEMATICHUD, + SNE_VEHICLECHUD, + SNE_CURSORHUD, + }; + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + switch( guiEnum ) { + case SNE_PLAYERHUD: + if ( player->hud ) { + player->hud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_CINEMATICHUD: + if ( player->cinematicHud ) { + player->cinematicHud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_VEHICLECHUD: + // twhitaker: I'd really just rather use player->GetHud() ... which returns the vehicle hud if the player is in a vehicle + // but this way the scripter will be able to strictly enforce which hud the named event gets played on. See idPlayer::GetHud() + if ( player->vehicleController.GetVehicle() ) { + idUserInterface * hud = player->vehicleController.GetVehicle()->GetHud(); + + if ( hud ) { + hud->HandleNamedEvent( namedEvent ); + } + } + break; + case SNE_CURSORHUD: + if ( player->GetCursorGUI() ) { + player->GetCursorGUI()->HandleNamedEvent( namedEvent ); + } + break; + } +} + +/* +================ +idThread::Event_SetMatSort +================ +*/ +void idThread::Event_SetMatSort( const char *name, const char *val ) const { + const idMaterial *mat = declManager->FindMaterial( name ); + if ( mat ) { + int srt = SS_DECAL; + if ( idStr::Icmp( val, "SS_MIN" ) == 0 ) { + srt = SS_MIN; + } else if ( idStr::Icmp( val, "SS_SUBVIEW" ) == 0 ) { + srt = SS_SUBVIEW; + } else if ( idStr::Icmp( val, "SS_PREGUI" ) == 0 ) { + srt = SS_PREGUI; + } else if ( idStr::Icmp( val, "SS_GUI" ) == 0 ) { + srt = SS_GUI; + } else if ( idStr::Icmp( val, "SS_BAD" ) == 0 ) { + srt = SS_BAD; + } else if ( idStr::Icmp( val, "SS_OPAQUE" ) == 0 ) { + srt = SS_OPAQUE; + } else if ( idStr::Icmp( val, "SS_PORTAL_SKY" ) == 0 ) { + srt = SS_PORTAL_SKY; + } else if ( idStr::Icmp( val, "SS_DECAL" ) == 0 ) { + srt = SS_DECAL; + } else if ( idStr::Icmp( val, "SS_FAR" ) == 0 ) { + srt = SS_FAR; + } else if ( idStr::Icmp( val, "SS_MEDIUM" ) == 0 ) { + srt = SS_MEDIUM; + } else if ( idStr::Icmp( val, "SS_CLOSE" ) == 0 ) { + srt = SS_CLOSE; + } else if ( idStr::Icmp( val, "SS_ALMOST_NEAREST" ) == 0 ) { + srt = SS_ALMOST_NEAREST; + } else if ( idStr::Icmp( val, "SS_NEAREST" ) == 0 ) { + srt = SS_NEAREST; + } else if ( idStr::Icmp( val, "SS_POST_PROCESS" ) == 0 ) { + srt = SS_POST_PROCESS; + } + + mat->SetSort( srt ); + } +} + +// jdischler: Adding ability to create texture streams from scripts +/* +================ +idThread::Event_BeginManualStreaming +================ +*/ +void idThread::Event_BeginManualStreaming() +{ +} + +/* +================ +idThread::Event_EndManualStreaming +================ +*/ +void idThread::Event_EndManualStreaming() +{ +} +// RAVEN END diff --git a/source/mpgame/script/Script_Thread.h b/source/mpgame/script/Script_Thread.h new file mode 100644 index 0000000..8ed76c0 --- /dev/null +++ b/source/mpgame/script/Script_Thread.h @@ -0,0 +1,365 @@ + +#ifndef __SCRIPT_THREAD_H__ +#define __SCRIPT_THREAD_H__ + +extern const idEventDef EV_Thread_Execute; +extern const idEventDef EV_Thread_SetCallback; +extern const idEventDef EV_Thread_TerminateThread; +extern const idEventDef EV_Thread_Pause; +extern const idEventDef EV_Thread_Wait; +extern const idEventDef EV_Thread_WaitFrame; +extern const idEventDef EV_Thread_WaitFor; +extern const idEventDef EV_Thread_WaitForThread; +extern const idEventDef EV_Thread_Print; +extern const idEventDef EV_Thread_PrintLn; +extern const idEventDef EV_Thread_Say; +extern const idEventDef EV_Thread_Assert; +extern const idEventDef EV_Thread_Trigger; +extern const idEventDef EV_Thread_SetCvar; +extern const idEventDef EV_Thread_GetCvar; +extern const idEventDef EV_Thread_Random; +extern const idEventDef EV_Thread_GetTime; +extern const idEventDef EV_Thread_KillThread; +extern const idEventDef EV_Thread_SetThreadName; +extern const idEventDef EV_Thread_GetEntity; +extern const idEventDef EV_Thread_Spawn; +extern const idEventDef EV_Thread_SetSpawnArg; +extern const idEventDef EV_Thread_SpawnString; +extern const idEventDef EV_Thread_SpawnFloat; +extern const idEventDef EV_Thread_SpawnVector; +extern const idEventDef EV_Thread_AngToForward; +extern const idEventDef EV_Thread_AngToRight; +extern const idEventDef EV_Thread_AngToUp; +extern const idEventDef EV_Thread_Sine; +extern const idEventDef EV_Thread_Cosine; +extern const idEventDef EV_Thread_Normalize; +extern const idEventDef EV_Thread_VecLength; +extern const idEventDef EV_Thread_VecDotProduct; +extern const idEventDef EV_Thread_VecCrossProduct; +extern const idEventDef EV_Thread_OnSignal; +extern const idEventDef EV_Thread_ClearSignal; +extern const idEventDef EV_Thread_SetCamera; +extern const idEventDef EV_Thread_FirstPerson; +extern const idEventDef EV_Thread_TraceFraction; +extern const idEventDef EV_Thread_TracePos; +extern const idEventDef EV_Thread_FadeIn; +extern const idEventDef EV_Thread_FadeOut; +extern const idEventDef EV_Thread_FadeTo; +extern const idEventDef EV_Thread_Restart; +extern const idEventDef EV_Thread_SetMatSort; + +// RAVEN BEGIN +// rjohnson: new blur special effect +extern const idEventDef EV_Thread_SetSpecialEffect; +extern const idEventDef EV_Thread_SetSpecialEffectParm; +// RAVEN END + +class idThread : public idClass { +private: + static idThread *currentThread; + + idThread *waitingForThread; + int waitingFor; + int waitingUntil; + idInterpreter interpreter; + + idDict spawnArgs; + + int threadNum; + idStr threadName; + + int lastExecuteTime; + int creationTime; + + bool manualControl; + + static int threadIndex; + static idList threadList; + + static trace_t trace; + + void Init( void ); + void Pause( void ); + + void Event_Execute( void ); + void Event_SetThreadName( const char *name ); + + // + // script callable Events + // + void Event_TerminateThread( int num ); + void Event_Pause( void ); + void Event_Wait( float time ); + void Event_WaitFrame( void ); + void Event_WaitFor( idEntity *ent ); + void Event_WaitForThread( int num ); + void Event_Print( const char *text ); + void Event_PrintLn( const char *text ); + void Event_Say( const char *text ); + void Event_Assert( float value ); + void Event_Trigger( idEntity *ent ); + void Event_SetCvar( const char *name, const char *value ) const; + void Event_GetCvar( const char *name ) const; + void Event_Random( float range ) const; + void Event_GetTime( void ); + void Event_KillThread( const char *name ); + void Event_GetEntity( const char *name ); + void Event_Spawn( const char *classname ); + void Event_CopySpawnArgs( idEntity *ent ); + void Event_SetSpawnArg( const char *key, const char *value ); + void Event_SpawnString( const char *key, const char *defaultvalue ); + void Event_SpawnFloat( const char *key, float defaultvalue ); + void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); + void Event_ClearPersistantArgs( void ); + void Event_SetPersistantArg( const char *key, const char *value ); + void Event_GetPersistantString( const char *key ); + void Event_GetPersistantFloat( const char *key ); + void Event_GetPersistantVector( const char *key ); + void Event_AngToForward( idAngles &ang ); + void Event_AngToRight( idAngles &ang ); + void Event_AngToUp( idAngles &ang ); + void Event_GetSine( float angle ); + void Event_GetCosine( float angle ); + void Event_GetSquareRoot( float theSquare ); + void Event_VecNormalize( idVec3 &vec ); + void Event_VecLength( idVec3 &vec ); + void Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecToAngles( idVec3 &vec ); + void Event_OnSignal( int signal, idEntity *ent, const char *func ); + void Event_ClearSignalThread( int signal, idEntity *ent ); + void Event_SetCamera( idEntity *ent ); + void Event_FirstPerson( void ); + void Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ); + void Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ); + void Event_GetTraceFraction( void ); + void Event_GetTraceEndPos( void ); + void Event_GetTraceNormal( void ); + void Event_GetTraceEntity( void ); + void Event_GetTraceJoint( void ); + void Event_GetTraceBody( void ); + void Event_FadeIn( idVec3 &color, float time ); + void Event_FadeOut( idVec3 &color, float time ); + void Event_FadeTo( idVec3 &color, float alpha, float time ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_StartMusic( const char *name ); + void Event_Warning( const char *text ); + void Event_Error( const char *text ); + void Event_StrLen( const char *string ); + void Event_StrLeft( const char *string, int num ); + void Event_StrRight( const char *string, int num ); + void Event_StrSkip( const char *string, int num ); + void Event_StrMid( const char *string, int start, int num ); + void Event_StrToFloat( const char *string ); + void Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ); + void Event_IsClient( void ); + void Event_IsMultiplayer( void ); + void Event_GetFrameTime( void ); + void Event_GetTicsPerSecond( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ); + void Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ); + void Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ); + void Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ); + void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); + void Event_InfluenceActive( void ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetSpawnVector( const char *key, idVec3 &vec ); + void Event_GetArcSine( float sinValue ); + void Event_GetArcCosine( float cosValue ); + void Event_ClearSignalAllThreads( int signal, idEntity *ent ); + void Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch); + void Event_IsStringEmpty( const char *checkString ); + void Event_AnnounceToAI( const char *announcement); + void Event_ChangeCrossings(const char *originalType, const char *newType); + +// abahr: so we can fake having pure script objects + void Event_ReferenceScriptObjectProxy( const char* scriptObjectName ); + void Event_ReleaseScriptObjectProxy( const char* proxyName ); + void Event_ClampFloat( float min, float max, float val ); + void Event_MinFloat( float val1, float val2 ); + void Event_MaxFloat( float val1, float val2 ); + void Event_StrFind( idStr& sourceStr, idStr& subStr ); + void Event_RandomInt( float range ) const; + +// rjohnson: new blur special effect + void Event_SetSpecialEffect( int Effect, int Enabled ); + void Event_SetSpecialEffectParm( int Effect, int Parm, float Value ); + +// nmckenzie: string signaling + void Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ); +// asalmon: achievements for Xenon + void Event_AwardAchievement( const char *name); +// twhitaker: ceil, floor and intVal + void Event_GetCeil( float val ); + void Event_GetFloor( float val ); + void Event_ToInt( float val ); +// jdischler: send named event string to specified gui + void Event_SendNamedEvent( int guiEnum, const char *namedEvent ); + void Event_BeginManualStreaming( void ); + void Event_EndManualStreaming( void ); + void Event_SetMatSort( const char *name, const char *val ) const; +// RAVEN END + +public: + CLASS_PROTOTYPE( idThread ); + + idThread(); + idThread( idEntity *self, const function_t *func ); + idThread( const function_t *func ); + idThread( idInterpreter *source, const function_t *func, int args ); + idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ); + + virtual ~idThread(); + + // tells the thread manager not to delete this thread when it ends + void ManualDelete( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void EnableDebugInfo( void ) { interpreter.debug = true; }; + void DisableDebugInfo( void ) { interpreter.debug = false; }; + + void WaitMS( int time ); + void WaitSec( float time ); + void WaitFrame( void ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( const function_t *func, bool clearStack ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( idEntity *obj, const function_t *func, bool clearStack ); + +// RAVEN BEGIN +// bgeisler: added way to list functions + void ListStates( void ); + +// abahr: added helper functions for pushing parms onto stack + void ClearStack( void ); + void PushInt( int value ); + void PushFloat( float value ); + void PushVec3( const idVec3& value ); + void PushEntity( const idEntity* ent ); + void PushString( const char* str ); + void PushBool( bool value ); +// RAVEN END + + void DisplayInfo( void ); + static idThread *GetThread( int num ); + static void ListThreads_f( const idCmdArgs &args ); + static void Restart( void ); + static void ObjectMoveDone( int threadnum, idEntity *obj ); + + static idList& GetThreads( void ); + + bool IsDoneProcessing( void ); + bool IsDying ( void ); + + void End( void ); + static void KillThread( const char *name ); + static void KillThread( int num ); + bool Execute( void ); + void ManualControl( void ) { manualControl = true; CancelEvents( &EV_Thread_Execute ); }; + void DoneProcessing( void ) { interpreter.doneProcessing = true; }; + void ContinueProcessing( void ) { interpreter.doneProcessing = false; }; + bool ThreadDying( void ) { return interpreter.threadDying; }; + void EndThread( void ) { interpreter.threadDying = true; }; + bool IsWaiting( void ); + void ClearWaitFor( void ); + bool IsWaitingFor( idEntity *obj ); + void ObjectMoveDone( idEntity *obj ); + void ThreadCallback( idThread *thread ); + void DelayedStart( int delay ); + bool Start( void ); + idThread *WaitingOnThread( void ); + void SetThreadNum( int num ); + int GetThreadNum( void ); + void SetThreadName( const char *name ); + const char *GetThreadName( void ); + + void Error( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + + static idThread *CurrentThread( void ); + static int CurrentThreadNum( void ); + static bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + static void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + + static void ReturnString( const char *text ); + static void ReturnFloat( float value ); + static void ReturnInt( int value ); + static void ReturnVector( idVec3 const &vec ); +// RAVEN BEGIN +// abahr: added const + static void ReturnEntity( const idEntity *ent ); +// RAVEN END +}; + +/* +================ +idThread::WaitingOnThread +================ +*/ +ID_INLINE idThread *idThread::WaitingOnThread( void ) { + return waitingForThread; +} + +/* +================ +idThread::SetThreadNum +================ +*/ +ID_INLINE void idThread::SetThreadNum( int num ) { + threadNum = num; +} + +/* +================ +idThread::GetThreadNum +================ +*/ +ID_INLINE int idThread::GetThreadNum( void ) { + return threadNum; +} + +/* +================ +idThread::GetThreadName +================ +*/ +ID_INLINE const char *idThread::GetThreadName( void ) { + return threadName.c_str(); +} + +/* +================ +idThread::GetThreads +================ +*/ +ID_INLINE idList& idThread::GetThreads ( void ) { + return threadList; +} + +/* +================ +idThread::IsDoneProcessing +================ +*/ +ID_INLINE bool idThread::IsDoneProcessing ( void ) { + return interpreter.doneProcessing; +} + +/* +================ +idThread::IsDying +================ +*/ +ID_INLINE bool idThread::IsDying ( void ) { + return interpreter.threadDying; +} + +#endif /* !__SCRIPT_THREAD_H__ */ diff --git a/source/mpgame/spawner.cpp b/source/mpgame/spawner.cpp new file mode 100644 index 0000000..807ee6c --- /dev/null +++ b/source/mpgame/spawner.cpp @@ -0,0 +1,600 @@ +/* +================ + +Spawner.cpp + +================ +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" +#include "vehicle/Vehicle.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AI_Util.h" + +const idEventDef EV_Spawner_RemoveNullActiveEntities( "removeNullActiveEntities" ); +const idEventDef EV_Spawner_NumActiveEntities( "numActiveEntities", "", 'd' ); +const idEventDef EV_Spawner_GetActiveEntity( "getActiveEntity", "d", 'e' ); + +CLASS_DECLARATION( idEntity, rvSpawner ) + EVENT( EV_Activate, rvSpawner::Event_Activate ) + EVENT( EV_Spawner_RemoveNullActiveEntities, rvSpawner::Event_RemoveNullActiveEntities ) + EVENT( EV_Spawner_NumActiveEntities, rvSpawner::Event_NumActiveEntities ) + EVENT( EV_Spawner_GetActiveEntity, rvSpawner::Event_GetActiveEntity ) +END_CLASS + +/* +============== +rvSpawner::Spawn +============== +*/ +void rvSpawner::Spawn( void ){ + GetPhysics()->SetContents( 0 ); + + // TEMP: read max_team_test until we can get it out of all the current maps + if ( !spawnArgs.GetInt ( "max_active", "4", maxActive ) ) { + if ( spawnArgs.GetInt ( "max_team_test", "4", maxActive ) ) { + gameLocal.Warning ( "spawner '%s' using outdated 'max_team_test', please change to 'max_active'", GetName() ); + } + } + + maxToSpawn = spawnArgs.GetInt( "count", "-1" ); + skipVisible = spawnArgs.GetBool ( "skipvisible", "1" ); + spawnWaves = spawnArgs.GetInt( "waves", "1" ); + spawnDelay = SEC2MS( spawnArgs.GetFloat( "delay", "2" ) ); + numSpawned = 0; + nextSpawnTime = 0; + + // Spawn waves has to be less than max active + if ( spawnWaves > maxActive ) { + spawnWaves = maxActive; + } + + FindSpawnTypes ( ); +} + +/* +============== +rvSpawner::Save +============== +*/ +void rvSpawner::Save ( idSaveGame *savefile ) const{ + savefile->WriteInt( numSpawned ); + savefile->WriteInt( maxToSpawn ); + savefile->WriteFloat( nextSpawnTime ); + savefile->WriteInt( maxActive ); + + int i; + savefile->WriteInt( currentActive.Num() ); + for( i = 0; i < currentActive.Num(); i++ ) { + currentActive[i].Save ( savefile ); + } + + savefile->WriteInt( spawnWaves ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( skipVisible ); + + savefile->WriteInt( spawnPoints.Num() ); + for ( i = 0; i < spawnPoints.Num(); i++ ) { + spawnPoints[ i ].Save ( savefile ); + } + + savefile->WriteInt ( callbacks.Num() ); + for ( i = 0; i < callbacks.Num(); i ++ ) { + callbacks[i].ent.Save ( savefile ); + savefile->WriteString ( callbacks[i].event ); + } +} + +/* +============== +rvSpawner::Restore +============== +*/ +void rvSpawner::Restore ( idRestoreGame *savefile ){ + int num; + int i; + + savefile->ReadInt( numSpawned ); + savefile->ReadInt( maxToSpawn ); + savefile->ReadFloat( nextSpawnTime ); + savefile->ReadInt( maxActive ); + + savefile->ReadInt( num ); + currentActive.Clear ( ); + currentActive.SetNum( num ); + for( i = 0; i < num; i++ ) { + currentActive[i].Restore ( savefile ); + } + + savefile->ReadInt( spawnWaves ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( skipVisible ); + + savefile->ReadInt( num ); + spawnPoints.SetNum( num); + for( i = 0; i < num; i ++ ) { + spawnPoints[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + callbacks.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + callbacks[i].ent.Restore ( savefile ); + savefile->ReadString ( callbacks[i].event ); + } + + FindSpawnTypes (); +} + +/* +============== +rvSpawner::FindSpawnTypes + +Generate the list of classnames to spawn from the spawnArgs. Anything matching the +prefix "def_spawn" will be included in the list. +============== +*/ +void rvSpawner::FindSpawnTypes ( void ){ + const idKeyValue *kv; + for ( kv = spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + spawnTypes.Append ( kv->GetValue ( ) ); + + // precache decls + declManager->FindType( DECL_AF, kv->GetValue(), true, false ); + } +} + +/* +============== +rvSpawner::FindTargets +============== +*/ +void rvSpawner::FindTargets ( void ) { + int i; + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + trace_t tr; + + idEntity::FindTargets ( ); + + // Copy the relevant targets to the spawn point list (right now only target_null entities) + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = ent; + + if( !spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + gameLocal.TraceBounds( this, tr, ent->GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin(), bounds, MASK_MONSTERSOLID, NULL ); + if ( gameLocal.entities[tr.c.entityNum] && !gameLocal.entities[tr.c.entityNum]->IsType ( idActor::GetClassType ( ) ) ) { + //drop a console warning here + gameLocal.Warning ( "Spawner '%s' can't spawn at point '%s', the monster won't fit.", GetName(), ent->GetName() ); + } + } + } +} + +/* +============== +rvSpawner::ValidateSpawnPoint +============== +*/ +bool rvSpawner::ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ){ + trace_t tr; + if( spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + return true; + } + + gameLocal.TraceBounds( this, tr, origin, origin, bounds, MASK_MONSTERSOLID, NULL ); + return tr.fraction >= 1.0f; +} + +/* +============== +rvSpawner::AddSpawnPoint +============== +*/ +void rvSpawner::AddSpawnPoint ( idEntity* point ) { + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = point; + + // If there were no spawnPoints then start with the delay + if ( spawnPoints.Num () == 1 ) { + nextSpawnTime = gameLocal.time + spawnDelay; + } +} + +/* +============== +rvSpawner::RemoveSpawnPoint +============== +*/ +void rvSpawner::RemoveSpawnPoint ( idEntity* point ) { + int i; + for ( i = spawnPoints.Num()-1; i >= 0; i -- ) { + if ( spawnPoints[i] == point ) { + spawnPoints.RemoveIndex ( i ); + break; + } + } +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +void rvSpawner::AddCallback ( idEntity* owner, const idEventDef* ev ) { + spawnerCallback_t& callback = callbacks.Alloc ( ); + callback.event = ev->GetName ( ); + callback.ent = owner; +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +idEntity *rvSpawner::GetSpawnPoint ( void ) { + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + idList< idEntityPtr > spawns; + int spawnIndex; + idEntity* spawnEnt; + + // Run through all spawnPoints and choose a random one. Each time a spawn point is excluded + // it will be removed from the list until there are no more items in the list. + for ( spawns = spawnPoints ; spawns.Num(); spawns.RemoveIndex ( spawnIndex ) ) { + spawnIndex = gameLocal.random.RandomInt ( spawns.Num() ); + spawnEnt = spawns[spawnIndex]; + + if ( !spawnEnt || !spawnEnt->GetPhysics() ) { + continue; + } + + // Check to see if something is in the way at this spawn point + if ( !ValidateSpawnPoint ( spawnEnt->GetPhysics()->GetOrigin(), bounds ) ) { + continue; + } + + // Skip the spawn point because its currently visible? + if ( skipVisible && gameLocal.GetLocalPlayer()->CanSee ( spawnEnt, true ) ) { + continue; + } + + // Found one! + return spawnEnt; + } + + return NULL; +} + +/* +============== +rvSpawner::GetSpawnType +============== +*/ +const char* rvSpawner::GetSpawnType ( idEntity* spawnPoint ) { + const idKeyValue* kv; + + if ( spawnPoint ) { + // If the spawn point has any "def_spawn" keys then they override the normal spawn keys + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", NULL ); + if ( kv ) { + const char* types [ MAX_SPAWN_TYPES ]; + int typeCount; + + for ( typeCount = 0; + typeCount < MAX_SPAWN_TYPES && kv; + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", kv ) ) { + types [ typeCount++ ] = kv->GetValue ( ).c_str(); + } + + return types[ gameLocal.random.RandomInt( typeCount ) ]; + } + } + + // No spawn types? + if ( !spawnTypes.Num ( ) ) { + return ""; + } + + // Return from the spawners list of types + return spawnTypes[ gameLocal.random.RandomInt( spawnTypes.Num() ) ]; +} + +/* +============== +rvSpawner::CopyPrefixedSpawnArgs +============== +*/ +void rvSpawner::CopyPrefixedSpawnArgs( idEntity *src, const char *prefix, idDict &args ){ + const idKeyValue *kv = src->spawnArgs.MatchPrefix( prefix, NULL ); + while( kv ) { + args.Set( kv->GetKey().c_str() + idStr::Length( prefix ), kv->GetValue() ); + kv = src->spawnArgs.MatchPrefix( prefix, kv ); + } +} + +/* +============== +rvSpawner::SpawnEnt +============== +*/ +bool rvSpawner::SpawnEnt( void ){ + idDict args; + idEntity* spawnPoint; + idEntity* spawnedEnt; + const char* temp; + + // Find a spawn point to spawn the entity + spawnPoint = GetSpawnPoint ( ); + if( !spawnPoint ){ + return false; + } + + // No valid spawn types for this point + temp = GetSpawnType ( spawnPoint ); + if ( !temp || !*temp ) { + gameLocal.Warning ( "Spawner '%s' could not find any valid spawn types for spawn point '%s'", GetName(), spawnPoint->GetName() ); + return false; + } + + // Build the spawn parameters for the entity about to be spawned + args.Set ( "origin", spawnPoint->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", spawnPoint->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", temp ); + args.SetBool ( "forceEnemy", spawnArgs.GetBool ( "auto_target", "1" ) ); + args.SetBool ( "faceEnemy", spawnArgs.GetBool ( "faceEnemy", "0" ) ); + + // Copy all keywords prefixed with "spawn_" to the entity being spawned. + CopyPrefixedSpawnArgs( this, "spawn_", args ); + if( spawnPoint != this ) { + CopyPrefixedSpawnArgs( spawnPoint, "spawn_", args ); + } + + // Spawn the entity + if ( !gameLocal.SpawnEntityDef ( args, &spawnedEnt ) ) { + return false; + } + + // Activate the spawned entity + spawnedEnt->ProcessEvent( &EV_Activate, this ); + + // Play a spawning effect if it has one - do we possibly want some script hooks in here? + gameLocal.PlayEffect ( spawnArgs, "fx_spawning", spawnPoint->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3() ); + + // script function for spawning guys + if( spawnArgs.GetString( "call", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + + // script function for the guy being spawned + if ( spawnArgs.GetString( "call_spawned", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( spawnedEnt, temp ); + } + + // Call all of our callbacks + int c; + for ( c = callbacks.Num() - 1; c >= 0; c-- ) { + if ( callbacks[c].ent ) { + callbacks[c].ent->ProcessEvent ( idEventDef::FindEvent ( callbacks[c].event ), this, spawnedEnt ); + } + } + + // Activate the spawn point entity when an enemy is spawned there and all of its targets + if( spawnPoint != this ){ + spawnPoint->ProcessEvent( &EV_Activate, spawnPoint ); + spawnPoint->ActivateTargets( spawnedEnt ); + + // One time use on this target? + if ( spawnPoint->spawnArgs.GetBool ( "remove" ) ) { + spawnPoint->PostEventMS ( &EV_Remove, 0 ); + } + } + + // Increment the total number spawned + numSpawned++; + + return true; +} + +/* +============== +rvSpawner::Think +============== +*/ +void rvSpawner::Think( void ){ + if ( thinkFlags & TH_THINK ) { + if( ActiveListChanged() ) {// If an entity has been removed and we have not been informed via Detach + nextSpawnTime = gameLocal.GetTime() + spawnDelay; + } + + CheckSpawn ( ); + } +} + +/* +============== +rvSpawner::CheckSpawn +============== +*/ +void rvSpawner::CheckSpawn ( void ) { + int count; + + // Any spawn points? + if ( !spawnPoints.Num ( ) ) { + return; + } + + // Is it time to spawn yet? + if ( nextSpawnTime == 0 || gameLocal.time < nextSpawnTime ) { + return; + } + + // Any left to spawn? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ){ + return; + } + + // Spawn in waves? + for ( count = 0; count < spawnWaves; count ++ ) { + // Too many active? + if( currentActive.Num() >= maxActive ) { + return; + } + + // Spawn a new entity + SpawnEnt ( ); + + // Are we at the limit now? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ) { + CallScriptEvents( "script_used_up", this ); + PostEventMS ( &EV_Remove, 0 ); + break; + } + } + + // Dont spawn again until after the delay + nextSpawnTime = gameLocal.time + spawnDelay; +} + +/* +============== +rvSpawner::CallScriptEvents +============== +*/ +void rvSpawner::CallScriptEvents( const char* prefixKey, idEntity* parm ) { + if( !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spawnArgs.MatchPrefix(prefixKey); kv; kv = spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( parm, 0 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +============== +rvSpawner::ActiveListChanged +============== +*/ +bool rvSpawner::ActiveListChanged() { + int previousCount = currentActive.Num(); + + currentActive.RemoveNull(); + + return previousCount > currentActive.Num(); +} + +/* +============== +rvSpawner::Attach + +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Attach ( idEntity* ent ) { + currentActive.AddUnique( ent ); +} + +/* +============== +rvSpawner::Detach + +Detaches the given AI from the spawner which will free up an active spot for spawning. +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Detach ( idEntity* ent ){ + currentActive.Remove( ent ); + + nextSpawnTime = gameLocal.GetTime() + spawnDelay; +} + +/* +============== +rvSpawner::Event_Activate +============== +*/ +void rvSpawner::Event_Activate ( idEntity *activator ) { + + // "trigger_only" spawners will attempt to spawn when triggered + if ( spawnArgs.GetBool ( "trigger_only" ) ) { + // Update next spawn time to follo CheckSpawn into thinking its time to spawn again + nextSpawnTime = gameLocal.time; + CheckSpawn ( ); + return; + } + + // If nextSpawnTime is zero then the spawner is currently deactivated + if ( nextSpawnTime == 0 ) { + // Start thinking + BecomeActive( TH_THINK ); + + // Allow immediate spawn + nextSpawnTime = gameLocal.time; + + // Spawn any ai targets and add them to the current count + ActivateTargets ( this ); + } else { + nextSpawnTime = 0; + BecomeInactive( TH_THINK ); + + // Remove the spawner if need be + if ( spawnArgs.GetBool ( "remove", "1" ) ) { + PostEventMS ( &EV_Remove, 0 ); + } + } +} + +/* +============== +rvSpawner::Event_RemoveNullActiveEntities +============== +*/ +void rvSpawner::Event_RemoveNullActiveEntities( void ) { + for( int ix = currentActive.Num() - 1; ix >= 0; --ix ) { + if( !currentActive[ix].IsValid() ) { + currentActive.RemoveIndex( ix ); + } + } +} + +/* +============== +rvSpawner::Event_NumActiveEntities +============== +*/ +void rvSpawner::Event_NumActiveEntities( void ) { + idThread::ReturnInt( currentActive.Num() ); +} + +/* +============== +rvSpawner::Event_GetActiveEntity +============== +*/ +void rvSpawner::Event_GetActiveEntity( int index ) { + idThread::ReturnEntity( (index < 0 || index >= currentActive.Num()) ? NULL : currentActive[index] ); +} + diff --git a/source/mpgame/spawner.h b/source/mpgame/spawner.h new file mode 100644 index 0000000..22595aa --- /dev/null +++ b/source/mpgame/spawner.h @@ -0,0 +1,120 @@ +/* +================ + +Spawner.h + +================ +*/ + +#ifndef __GAME_SPAWNER_H__ +#define __GAME_SPAWNER_H__ + +const int MAX_SPAWN_TYPES = 32; + +class rvSpawner; + +typedef void (*spawnerCallbackProc_t) ( rvSpawner* spawner, idEntity* spawned, int userdata ); + +typedef struct { + idEntityPtr ent; + idStr event; +} spawnerCallback_t; + +/* +=============================================================================== + + rvSpawner + +=============================================================================== +*/ +class rvSpawner : public idEntity { +public: + CLASS_PROTOTYPE( rvSpawner ); + + void Spawn ( void ); + void Think ( void ); + + void Attach ( idEntity* ent ); + void Detach ( idEntity* ent ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void AddSpawnPoint ( idEntity* point ); + void RemoveSpawnPoint ( idEntity* point ); + + int GetNumSpawnPoints ( void ) const; + int GetNumActive ( void ) const; + int GetMaxActive ( void ) const; + idEntity* GetSpawnPoint ( int index ); + + virtual void FindTargets ( void ); + bool ActiveListChanged ( void ); + + void CallScriptEvents ( const char* prefixKey, idEntity* parm ); + + void AddCallback ( idEntity* owner, const idEventDef* ev ); + +protected: + + int numSpawned; + int maxToSpawn; + float nextSpawnTime; + int maxActive; + idList< idEntityPtr > currentActive; + int spawnWaves; + int spawnDelay; + bool skipVisible; + idStrList spawnTypes; + + idList< idEntityPtr > spawnPoints; + + idList< spawnerCallback_t > callbacks; + + // Check to see if its time to spawn + void CheckSpawn ( void ); + + // Spawn a new entity + bool SpawnEnt ( void ); + + // Populate the spawnType list with the available spawn types + void FindSpawnTypes ( void ); + + // Get a random spawnpoint to spawn at + idEntity* GetSpawnPoint ( void ); + + // Get a random spawn type + const char* GetSpawnType ( idEntity* spawnPoint ); + + // Validate the given spawn point for spawning + bool ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ); + + // Copy key/values from the given entity to the given dictionary using the specified prefix + void CopyPrefixedSpawnArgs ( idEntity *src, const char *prefix, idDict &args ); + +private: + + void Event_Activate ( idEntity *activator ); + void Event_RemoveNullActiveEntities( void ); + void Event_NumActiveEntities ( void ); + void Event_GetActiveEntity ( int index ); +}; + + +ID_INLINE int rvSpawner::GetNumSpawnPoints( void ) const { + return spawnPoints.Num ( ); +} + +ID_INLINE idEntity* rvSpawner::GetSpawnPoint( int index ) { + return spawnPoints[index]; +} + +ID_INLINE int rvSpawner::GetNumActive( void ) const { + return currentActive.Num(); +} + +ID_INLINE int rvSpawner::GetMaxActive( void ) const { + return maxActive; +} + +#endif // __GAME_SPAWNER_H__ diff --git a/source/mpgame/vehicle/Vehicle.cpp b/source/mpgame/vehicle/Vehicle.cpp new file mode 100644 index 0000000..9661193 --- /dev/null +++ b/source/mpgame/vehicle/Vehicle.cpp @@ -0,0 +1,1682 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Effect.h" +#include "../ai/AI_Manager.h" +#include "../Projectile.h" + +#define VEHICLE_CRASH_DELAY 500 +#define VEHICLE_HAZARD_TIMEOUT 5000 +#define VEHICLE_LOCK_TIMEOUT 5000 + +const idEventDef EV_LaunchProjectiles( "launchProjectiles", "d" ); +const idEventDef EV_HUDShockWarningOff( ""); +const idEventDef EV_StalledRestart( "", "dd" ); +const idEventDef EV_GetViewAngles( "getViewAngles", NULL, 'v' ); + +CLASS_DECLARATION( idActor, rvVehicle ) + EVENT( EV_Door_Lock, rvVehicle::Event_Lock ) + EVENT( EV_Door_IsLocked, rvVehicle::Event_IsLocked ) + EVENT( AI_EnableMovement, rvVehicle::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvVehicle::Event_DisableMovement ) + EVENT( EV_Player_EnableWeapon, rvVehicle::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, rvVehicle::Event_DisableWeapon ) + EVENT( AI_EnableClip, rvVehicle::Event_EnableClip ) + EVENT( AI_DisableClip, rvVehicle::Event_DisableClip ) + EVENT( EV_Activate, rvVehicle::Event_Activate ) + EVENT( EV_LaunchProjectiles, rvVehicle::Event_LaunchProjectiles ) + EVENT( AI_SetScript, rvVehicle::Event_SetScript ) + EVENT( AI_SetHealth, rvVehicle::Event_SetHealth ) + EVENT( EV_HUDShockWarningOff, rvVehicle::Event_HUDShockWarningOff ) + EVENT( EV_StalledRestart, rvVehicle::Event_StalledRestart ) + EVENT( EV_GetViewAngles, rvVehicle::Event_GetViewAngles ) +END_CLASS + +/* +===================== +rvVehicle::rvVehicle +===================== +*/ +rvVehicle::rvVehicle ( void ) { + autoRight = false; + hud = NULL; + shieldModel = NULL; + shieldMaxHealth = 0; + hazardWarningTime = 0; + lockWarningTime = 0; + godModeDamage = 0; + drivers = 0; + + autoCorrectionBegin = 0; + + crashEffect = 0; + crashTime = 0; + crashNextSound = 0; + + fl.networkSync = true; +} + +rvVehicle::~rvVehicle ( void ) { + int i; + + // Force all the drivers out + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver ( ); + if ( !driver ) { + continue; + } + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + } + + positions.Clear ( ); +} + +/* +================ +rvVehicle::Spawn +================ +*/ +void rvVehicle::Spawn( void ) { + const char* temp; + + memset ( &vfl, 0, sizeof(vfl) ); + + SetPositions ( ); + + healthMax = health; + healthLow = spawnArgs.GetInt ( "lowhealth", va("%d", health / 10 ) ); + damageStaticChance = spawnArgs.GetFloat ( "damageStaticChance", "0" ); + crashSpeedSmall = spawnArgs.GetFloat ( "crashSpeedSmall", "50" ); + crashSpeedMedium = spawnArgs.GetFloat ( "crashSpeedMedium", "125" ); + crashSpeedLarge = spawnArgs.GetFloat ( "crashSpeedLarge", "200" ); + crashDamage = spawnArgs.GetString ( "def_crashDamage" ); + + healthRegenDelay = SEC2MS(spawnArgs.GetFloat( "healthRegenDelay", "0" )); + healthRegenRate = spawnArgs.GetInt( "healthRegenRate", "0" ); + healthRegenAmount.Init( gameLocal.time, 0, healthMax, healthMax ); + + vfl.disableMovement = spawnArgs.GetBool ( "disableMovement", "0" ); + vfl.disableWeapons = spawnArgs.GetBool ( "disableWeapon", "0" ); + vfl.scripted = 0; + vfl.flipEject = spawnArgs.GetBool( "allowFlipEject", "1" ); + + health = spawnArgs.GetInt ( "health", "100" ); + fl.takedamage = ( health > 0 ); + + // Load the HUD + if ( NULL != ( temp = spawnArgs.GetString( "gui_hud", "" ) ) ) { + hud = uiManager->FindGui( temp, true, false, false ); + if ( hud ) { + hud->SetStateInt ( "vehicle_id", spawnArgs.GetInt ( "hudid" ) ); + hud->Activate( true, gameLocal.time ); + } + } + + // Get shield parameters + shieldMaxHealth = spawnArgs.GetInt ( "shieldHealth", "0" ); + shieldRegenTime = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenTime", "0" ) ); + shieldRegenDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenDelay", "0" ) ); + shieldHitTime = 0; + shieldHealth.Init ( gameLocal.time, 0, shieldMaxHealth, shieldMaxHealth ); + + SetCombatModel(); + + cachedContents = GetPhysics()->GetContents(); + + funcs.enter.Init( spawnArgs.GetString( "enter_script" ) ); + funcs.exit.Init( spawnArgs.GetString( "exit_script" ) ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::Save +================ +*/ +void rvVehicle::Save ( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt ( positions.Num ( ) ); + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].Save ( savefile ); + } + savefile->WriteInt ( drivers ); + + savefile->WriteUserInterface( hud, false ); + + savefile->WriteFloat ( crashSpeedSmall ); + savefile->WriteFloat ( crashSpeedMedium ); + savefile->WriteFloat ( crashSpeedLarge ); + savefile->WriteString ( crashDamage ); + crashEffect.Save ( savefile ); + savefile->WriteInt ( crashNextSound ); + savefile->WriteInt ( crashTime ); + + savefile->WriteFloat ( autoRightDir ); + savefile->WriteBool ( autoRight ); + + savefile->WriteInt( autoCorrectionBegin ); + + savefile->Write( &vfl, sizeof( vfl ) ); + + savefile->WriteFloat ( damageStaticChance ); + + savefile->WriteFloat ( shieldMaxHealth ); + savefile->WriteInterpolate( shieldHealth ); + savefile->WriteInt ( shieldHitTime ); + savefile->WriteFloat ( shieldRegenTime ); + savefile->WriteInt ( shieldRegenDelay ); +// TOSAVE: idClipModel* shieldModel; + + savefile->WriteInt( healthRegenDelay ); + savefile->WriteInt( healthRegenRate ); + savefile->WriteInterpolate( healthRegenAmount ); + + savefile->WriteInt ( hazardWarningTime ); + savefile->WriteInt ( lockWarningTime ); + savefile->WriteInt ( healthMax ); + savefile->WriteInt ( healthLow ); + savefile->WriteInt ( godModeDamage ); + + savefile->WriteInt ( cachedContents ); + + funcs.enter.Save( savefile ); + funcs.exit.Save ( savefile ); + +// cnicholson: Don't save crash Velocities, they are assigned in Restore +// savefile->WriteFloat( crashVelocitySmall ); // cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityMedium );// cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityLarge ); // cnicholson: Added unsaved var + +// TOSAVE: idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; + +} + +/* +================ +rvVehicle::Restore +================ +*/ +void rvVehicle::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + positions.Clear(); + positions.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + positions[i].Restore ( savefile ); + } + savefile->ReadInt ( drivers ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + + savefile->ReadFloat ( crashSpeedSmall ); + savefile->ReadFloat ( crashSpeedMedium ); + savefile->ReadFloat ( crashSpeedLarge ); + savefile->ReadString ( crashDamage ); + crashEffect.Restore ( savefile ); + savefile->ReadInt ( crashNextSound ); + savefile->ReadInt ( crashTime ); + + savefile->ReadFloat ( autoRightDir ); + savefile->ReadBool ( autoRight ); + + savefile->ReadInt( (int&)autoCorrectionBegin ); + + savefile->Read( &vfl, sizeof( vfl ) ); + + savefile->ReadFloat ( damageStaticChance ); + + savefile->ReadFloat ( shieldMaxHealth ); + savefile->ReadInterpolate( shieldHealth ); + savefile->ReadInt ( shieldHitTime ); + savefile->ReadFloat ( shieldRegenTime ); + savefile->ReadInt ( shieldRegenDelay ); +// TORESTORE: idClipModel* shieldModel; + + savefile->ReadInt( healthRegenDelay ); + savefile->ReadInt( healthRegenRate ); + savefile->ReadInterpolate( healthRegenAmount ); + + savefile->ReadInt ( hazardWarningTime ); + savefile->ReadInt ( lockWarningTime ); + savefile->ReadInt ( healthMax ); + savefile->ReadInt ( healthLow ); + savefile->ReadInt ( godModeDamage ); + + savefile->ReadInt ( cachedContents ); + + funcs.enter.Restore ( savefile ); + funcs.exit.Restore ( savefile ); + + SetCombatModel ( ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::SetPositions +================ +*/ +void rvVehicle::SetPositions ( void ) { + int positionCount; + int index; + const idKeyValue* kv; + + // Count the positions first so we can allocate them all at once + positionCount = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + positionCount++; + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } + + // Every vehicle needs a def_position in it's def file + if ( positionCount == 0) { + //gameLocal.Error ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + gameLocal.Warning ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + positionCount = 1; + spawnArgs.Set( "def_position", "vehicle_ai_null_position" ); + } + + // Initialize the positions + positions.SetNum ( positionCount ); + + // Initialize all of the positions + index = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + const idDict* dict; + + // Get the position dictionary + dict = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Initialize the position + positions[index++].Init ( this, *dict ); + + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } +} + +/* +================ +rvVehicle::SetCombatModel +================ +*/ +void rvVehicle::SetCombatModel ( void ) { + idActor::SetCombatModel ( ); + + if ( shieldMaxHealth ) { + idBounds bounds; + bounds.Clear ( ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMins", "0 0 0" ) ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMaxs", "0 0 0" ) ); + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + shieldModel = NULL; + } + + //twhitaker: dodecahedron support + idStr shieldModelName; + if ( spawnArgs.GetString( "shieldModel", "", shieldModelName ) ) { + if ( shieldModelName.Length() && !shieldModelName.Icmp( "dodecahedron" ) ) { + idTraceModel trm; + trm.SetupDodecahedron ( GetPhysics()->GetBounds() ); + shieldModel = new idClipModel ( trm ); + } + } + //twhitaker: end + + if ( !shieldModel ) { + shieldModel = new idClipModel ( idTraceModel ( bounds, spawnArgs.GetInt ( "shieldSides", "6" ) ) ); + } + + shieldModel->SetOwner ( this ); + shieldModel->SetContents ( CONTENTS_SOLID ); + } else { + shieldModel = NULL; + } +} + +/* +================ +rvVehicle::LinkCombat +================ +*/ +void rvVehicle::LinkCombat ( void ) { + if ( fl.hidden ) { + return; + } + + if ( g_debugVehicle.GetInteger() == 1 && shieldModel ) { + collisionModelManager->DrawModel( shieldModel->GetCollisionModel(), renderEntity.origin, renderEntity.axis, vec3_origin, mat3_identity, 0.0f ); + } + + if ( shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 && HasDrivers ( ) ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + shieldModel->Link( this, 0, renderEntity.origin, renderEntity.axis ); +// RAVEN END + + if ( combatModel ) { + combatModel->Unlink ( ); + } + } else { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + + if ( shieldModel ) { + shieldModel->Unlink ( ); + } + } +} + +/* +================ +rvVehicle::ClientPredictionThink +================ +*/ +void rvVehicle::ClientPredictionThink ( void ) { + Think ( ); +} + +/* +================ +rvVehicle::WriteToSnapshot +================ +*/ +void rvVehicle::WriteToSnapshot ( idBitMsgDelta &msg ) const { + int i; + // TODO: Check that this conditional write to delta message is OK + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].WriteToSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::ReadFromSnapshot +================ +*/ +void rvVehicle::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + int i; + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].ReadFromSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::UpdateState +================ +*/ +void rvVehicle::UpdateState ( void ) { + rvVehiclePosition* pos; + pos = &positions[0]; + + vfl.forward = (pos->IsOccupied() && pos->mInputCmd.forwardmove > 0); + vfl.backward = (pos->IsOccupied() && pos->mInputCmd.forwardmove < 0); + vfl.right = (pos->IsOccupied() && pos->mInputCmd.rightmove > 0); + vfl.left = (pos->IsOccupied() && pos->mInputCmd.rightmove < 0); + vfl.driver = pos->IsOccupied(); + vfl.strafe = (pos->IsOccupied() && pos->mInputCmd.buttons & BUTTON_STRAFE ); +} + + +/* +================ +rvVehicle::Think +================ +*/ +void rvVehicle::Think ( void ) { + UpdateState(); + UpdateAnimState ( ); + UpdateIncomingProjectiles(); + + // If we are the current debug entity then output some info to the hud + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, this ) ) { + gameDebug.SetInt ( "vehicle", 1 ); + gameDebug.SetInt ( "shields", shieldHealth.GetCurrentValue(gameLocal.time) ); + gameDebug.SetInt ( "positions", positions.Num() ); + gameDebug.SetInt ( "drivers", drivers ); + } + + if ( g_debugVehicle.GetInteger() == 1 ) { + idMat3 flatAxis = idAngles(0,GetPhysics()->GetAxis().ToAngles().yaw,0).ToMat3(); + gameRenderWorld->DebugLine ( colorOrange, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[0] ); + gameRenderWorld->DebugLine ( colorYellow, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[1] ); + gameRenderWorld->DebugLine ( colorCyan, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() - 50.0f * flatAxis[2] ); + + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + +/* VEH_FIXME: where to put this? + // For engine glow + renderEntity.shaderParms[7] = mEngineStatus.GetCurrentValue ( gameLocal.time ); +*/ + + if( thinkFlags & TH_THINK ) { + int i; + + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPrePhysics( ); + } + + if( autoRight ) { + if( idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) < 30.0f ) { + GetPhysics()->SetLinearVelocity( vec3_origin ); + autoRight = false; + } else { + idVec3 upSpeed = -GetPhysics()->GetGravityNormal() * 72.0f; + idMat3 axis = GetPhysics()->GetAxis(); + + // Rotate around the forward axis + axis.RotateRelative ( 0, (renderEntity.axis.ToAngles().roll<0?180:-180) * MS2SEC(gameLocal.GetMSec()) * 1.5f ); + GetPhysics()->SetAxis ( axis ); + + // Move up to make room for the rotation + GetPhysics()->SetLinearVelocity( upSpeed ); + } + } + + // twhitaker: nothing special here + RunPrePhysics(); + + // Run the physics + RunPhysics(); + + // twhitaker: nothing special here + RunPostPhysics(); + + // Give each position a chance to think after physics has been run + vfl.godmode = false; + fl.notarget = false; + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPostPhysics( ); + + // Include the eye origin into the bounds to ensure the driver + // is inside the render bounds when the vehicle is rendered. + if ( positions[i].IsOccupied ( ) ) { + AddToBounds ( positions[i].GetEyeOrigin ( ) ); + + // Transfer godmode from driving players + if ( positions[i].mDriver && positions[i].mDriver->IsType ( idPlayer::GetClassType() ) ) { + if ( static_cast(positions[i].mDriver.GetEntity())->godmode ) { + vfl.godmode = true; + } + } + + // Inherit notarget if our driver has it on + if ( positions[i].mDriver && positions[i].mDriver->fl.notarget ) { + fl.notarget = true; + } + } + } + + // If the vehicle is flipped then kick out all drivers + if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) { + if ( spawnArgs.GetBool( "locked_flip_death", false ) ) { + Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 ); + } else if ( vfl.flipEject ) { + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver (); + if ( driver ) { + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + } + } + } + } + + // Regenerate the shield + if ( shieldMaxHealth && shieldHealth.GetCurrentValue(gameLocal.time) < shieldMaxHealth ) { + if ( gameLocal.time > shieldHitTime + shieldRegenDelay && shieldHealth.IsDone ( gameLocal.time ) ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldRecharge", SND_CHANNEL_BODY2, 0, false, NULL ); + + float regenTime = shieldHealth.GetCurrentValue(gameLocal.time); + regenTime /= (float)shieldMaxHealth; + regenTime = 1.0f - regenTime; + regenTime *= (float)shieldRegenTime; + shieldHealth.Init ( gameLocal.time, regenTime, shieldHealth.GetCurrentValue(gameLocal.time), shieldMaxHealth ); + } + } + + // Regenerate health, if needed + // do nothing if the vehicle is at full hit points + if (health < healthMax) + { + // also do nothing if we've been damaged less than healthRegenDelay seconds ago + if ( healthRegenRate && shieldHitTime + healthRegenDelay <= gameLocal.time ) + { + // do we need to start the interpolation? + if ( healthRegenAmount.IsDone( gameLocal.time )) + { + healthRegenAmount.Init( gameLocal.time, SEC2MS((float)(healthMax - health)/healthRegenRate), + health, healthMax ); + } + else // get our interpolated health value for the current time. + { + health = healthRegenAmount.GetCurrentValue( gameLocal.time ); + } + } + } + + // Check hazards + if ( hazardWarningTime && gameLocal.time > hazardWarningTime + VEHICLE_HAZARD_TIMEOUT ) { + hazardWarningTime = 0; + StartSound ( "snd_voiceSafe", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "info_safe" ); + } + } + + // Stop the crash effect if no collide happened this frame + if ( crashEffect && crashTime != gameLocal.time ) { + crashEffect->Stop ( ); + crashEffect = NULL; + } + + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } +} + +/* +================ +rvVehicle::UpdateDrivers +================ +*/ +void rvVehicle::UpdateDrivers ( int delta ) { + int oldDrivers = drivers; + + drivers = idMath::ClampInt ( 0, positions.Num(), drivers + delta ); + + if ( drivers && !oldDrivers ) { + if ( fl.takedamage ) { + aiManager.AddTeammate ( this ); + } + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_enter", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } else if ( !drivers ) { + aiManager.RemoveTeammate ( this ); + + refSound.listenerId = entityNumber + 1; + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_exit", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } +} + +/* +================ +rvVehicle::GetAxis +================ +*/ +const idMat3& rvVehicle::GetAxis( int id ) const { + return GetPhysics()->GetAxis( id ); +} + +/* +================ +rvVehicle::GetOrigin +================ +*/ +const idVec3& rvVehicle::GetOrigin( int id ) const { + return GetPhysics()->GetOrigin(); +} + +/* +================ +rvVehicle::GetEyePosition +================ +*/ +void rvVehicle::GetEyePosition ( int pos, idVec3& origin, idMat3& axis ) { + axis = positions[pos].GetEyeAxis ( ); + origin = positions[pos].GetEyeOrigin ( ); +} + +/* +================ +rvVehicle::GetDriverPosition +================ +*/ +void rvVehicle::GetDriverPosition ( int pos, idVec3& origin, idMat3& axis ) { + const rvVehiclePosition *position = GetPosition( pos ); + + position->GetDriverPosition( origin, axis ); +} + +/* +===================== +rvVehicle::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicle::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + idMat3 identity; + identity.Identity( ); + + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + + // mekberg: vehicle bounds are resized based on rotation but not rotated with the axis. Get transformed bounds. + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, identity ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +================ +rvVehicle::AddDriver + +Add a driver to the vehicle at the given position. Its possible that the given position is +occupied, in that case the driver will be assigned the closest valid position. If no position +can be found then (-1) will be returned, otherwise the position the driver is now driving will +be returned. +================ +*/ +int rvVehicle::AddDriver ( int position, idActor* driver ) { + int wraparound; + + // Ensure the given position is valid + if ( position < 0 || position >= positions.Num() ) { + gameLocal.Warning ( "position %d is invalid for vehicle '%s'", position, name.c_str() ); + return -1; + } + + wraparound = position; + + do { + // If the position isnt occupied then set the driver + if ( !positions[position].IsOccupied ( ) ) { + positions[position].SetDriver ( driver ); + + // Vehicle is on same team as driver + team = driver->team; + + UpdateDrivers ( 1 ); + + // The local player will hear all private sounds of the vehicle + if ( driver == gameLocal.GetLocalPlayer ( ) ) { + refSound.listenerId = driver->GetListenerId ( ); + } + + return position; + } + + // Check the next position, wrap around if necessary + position = (position + 1) % positions.Num(); + + } while ( wraparound != position ); + + return -1; +} + +/* +================ +rvVehicle::RemoveDriver + +Removes the given driver from the vehicle. This includes physically taking the driver out +of the vehicle and placing them back into the world and will follow all constraints that limit +the driver from exiting the vehicle. +================ +*/ +bool rvVehicle::RemoveDriver ( int position, bool force ) { + if ( !positions[position].EjectDriver ( force ) ) { + return false; + } + + UpdateDrivers ( -1 ); + return true; +} + +/* +================ +rvVehicle::EjectAllDrivers +================ +*/ +void rvVehicle::EjectAllDrivers( bool force ) { + for( int ix = positions.Num() - 1; ix >= 0; --ix ) { + if( !GetPosition(ix)->GetDriver() ) { + continue; + } + + GetPosition(ix)->GetDriver()->ProcessEvent( &AI_ExitVehicle, force ); + } +} + +/* +============ +rvVehicle::Damage +============ +*/ +void rvVehicle::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, int location ) { + int damage; + + if ( !fl.takedamage ) { + return; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->GetBool( "ignore_vehicles", "0" ) ) { + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damage *= damageScale; + + int save = HasDrivers() ? shieldHealth.GetCurrentValue ( gameLocal.time ) : 0; + + // If one of the drivers is the player, do the player HUD effects + for ( int i = 0; i < positions.Num(); i++ ) { + rvVehiclePosition & pos = positions[ i ]; + + if ( pos.mDriver.IsValid() && pos.mDriver->IsType( idPlayer::GetClassType() ) ) { + idPlayer & driver = static_cast< idPlayer & >( *pos.mDriver.GetEntity() ); + + if ( driver.GetHud() ) { + driver.GetHud()->HandleNamedEvent( "vehicleHit" ); + } + + if ( !stricmp( damageDef->GetString( "filter" ), "electrical" ) ) { + driver.GetHud()->HandleNamedEvent( "electricWarningOn" ); + PostEventMS( &EV_HUDShockWarningOff, spawnArgs.GetInt( "hud_shock_warning_time", "2500" ) ); + + if ( damage >= spawnArgs.GetInt( "electric_stall_damage", "20" ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect( spawnArgs , "fx_electrical_stall" ), this, SEC2MS( spawnArgs.GetFloat ( "hud_shock_warning_time", "2.5" ) ) ); + effect->Play ( gameLocal.time, false ); + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall" ); + } + + vfl.stalled = true; + PostEventMS( &EV_StalledRestart, spawnArgs.GetInt( "electric_stall_delay", "3500" ), save, damage ); + } + } + } + } + + + shieldHitTime = gameLocal.time; + + // Shields off when there is no controller + if ( !damageDef->GetBool( "noShields", "0" ) ) + { + if ( save > 0 ) { + int shield; + save = Min( save, damage ); + damage -= save; + + shield = shieldHealth.GetCurrentValue ( gameLocal.time ) - save; + shieldHealth.Init ( gameLocal.time, 0, shield, shield ); + // always stop the sound because we may be playing the recharge just now. + StopSound ( SND_CHANNEL_BODY2, false ); + + // Looping warning sound for shield + if ( shield <= 0 && !vfl.stalled ) { + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + } + } + + // God Mode? + if ( vfl.godmode && !damageDef->GetBool( "noGod" ) ) { + godModeDamage += damage; + damage = 0; + } + + if ( !damage ) { + return; + } + + // Static on the gui when hit + if ( renderEntity.gui[0] && gameLocal.random.RandomFloat() < damageStaticChance ) { + renderEntity.gui[0]->HandleNamedEvent ( "shot_static" ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + health -= damage; + if ( health < 1 && damageDef->GetBool( "nonLethal" ) ) { + health = 1; + } + if ( health <= 0 ) { + // keep the driver from being killed if we're forcing undying state + if ( g_forceUndying.GetBool() && HasDrivers() ) { + idActor * driver = NULL; + for ( int i = 0; i < positions.Num(); i++ ) { + idActor * currentDriver = positions[ i ].GetDriver(); + if ( currentDriver && currentDriver->IsType( idPlayer::GetClassType() ) ) { + driver = currentDriver; + break; + } + } + + if ( driver ) { + health = 1; + Pain( inflictor, attacker, damage, dir, 0 ); + return; + } + } + + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, 0 ); + } +} + +/* +================ +rvVehicle::Killed +================ +*/ +void rvVehicle::Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int i; + + lockWarningTime = 0; + hazardWarningTime = 0; + vfl.locked = false; + vfl.dead = true; + + // Try removing the vehicle from all lists it could be part of. + aiManager.RemoveTeammate ( this ); + + // Remove all drivers from the vehicle and kill them + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver(); + if ( !driver ) { + continue; + } + + // Dump the driver out of the vehicle and kill them + driver->health = 0; + driver->ProcessEvent ( &AI_ExitVehicle, true ); + driver->Killed( inflictor, attacker, damage, dir, location ); + } + + OnDeath(); + CheckDeathObjectives(); + + if ( spawnArgs.GetBool( "remove_on_death", "1" ) ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + + Hide ( ); + + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +rvVehicle::AddDamageEffect +================ +*/ +void rvVehicle::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + if ( HasDrivers() && shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 ) { + jointHandle_t joint = animator.GetJointHandle( spawnArgs.GetString( "fx_shield_joint", "" ) ); + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + dir.NormalizeFast ( ); + if ( INVALID_JOINT == joint ) { + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + } else { + PlayEffect ( "fx_shield", joint, false, vec3_origin, true ); + } + } +} + +/* +================ +rvVehicle::Collide +================ +*/ +bool rvVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir; + float speed; + float collisionSelfDamage = 0.0f; + + dir = velocity; + speed = dir.Normalize(); + + // No collision effect when hitting a no impact surfac +// if ( collision.c.material && collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) { +// return false; +// } + + if ( vfl.dead ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + Hide ( ); + PostEventMS( &EV_Remove, 0 ); + } + + if ( !alwaysImpactDamage ) { + if ( speed < crashSpeedSmall ) { + return false; + } + } + + // When colliding with the world play a collision effect + if ( collision.c.entityNum == ENTITYNUM_WORLD ) { + // TODO: MAterial types + if ( !crashEffect ) { + crashEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_crash" ), collision.c.point, collision.c.normal.ToMat3(), true ); + } + if ( crashEffect ) { + crashEffect->SetOrigin ( collision.c.point ); + crashEffect->SetAxis ( collision.c.normal.ToMat3() ); + crashEffect->Attenuate ( idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ) ); + } + } + + idStr collDmgDef = "damage_gev_collision_self"; + if ( gameLocal.time > crashNextSound ) { + + float dot = dir * collision.c.normal; + if( dot < -0.5f ) { + + // Crash impact sounds + if ( speed > crashSpeedLarge ) { + collisionSelfDamage += 50.0f; + StartSound ( "snd_crash_large", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedMedium ) { + collisionSelfDamage += 25.0f; + StartSound ( "snd_crash_medium", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedSmall ) { + StartSound ( "snd_crash_small", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + } + } + } + + crashTime = gameLocal.time; + float vel = GetPhysics()->GetLinearVelocity().Length(); + + if ( vel > crashVelocitySmall && crashDamage.Length ( ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + + if ( ent ) { + float f = vel > crashVelocityLarge ? 1.0f : idMath::Sqrt( vel - crashVelocityMedium ) * ( 1.0f / idMath::Sqrt( crashVelocityLarge - crashVelocityMedium ) ); + + // Now hurt him + if ( !( team != gameLocal.GetLocalPlayer()->team && ent->IsType( idPlayer::GetClassType() ) ) ) { + + const idDict* damageDef = gameLocal.FindEntityDefDict ( crashDamage, false ); + //MCG NOTE: This used to call vehicleController.GetDriver(), which was always NULL... + idActor* theDriver = positions[0].GetDriver(); + + if ( ent->fl.takedamage + && ent->health >= 0 + && !ent->spawnArgs.GetBool( "ignore_vehicle_damage" ) + && (vel > 100.0f || !ent->IsType(idPlayer::GetClassType())) ) { + //MCG NOTE: now that theDriver is being set correctly, this damage will get credited to the driver (as the attacker), unlike before + float dScale = f * ent->spawnArgs.GetFloat( "vehicle_damage_scale", "1" ); + ent->Damage( this, theDriver, dir, crashDamage, dScale, INVALID_JOINT ); + if ( vel > 100.0f ) { + //NOTE: we PURPOSELY override this here, so you don't take damage from slamming into damageable things, only invulnerable things... + collisionSelfDamage = ent->spawnArgs.GetFloat( "vehicle_impact_damage", "0" ); + collDmgDef = "damage_gev_collision"; + } + } + + // ApplyImpulse doesn't like it when you give it a null driver + // MCG: okay, now ApplyImpulse actually is getting called, + // but I'm only going to allow it on idMoveables since ApplyImpulse is + // rejected by idActors if it comes from an idActor and this code was + // never being called before. + if ( theDriver && damageDef && !ent->spawnArgs.GetBool("ignore_vehicle_push") && ent->IsType( idMoveable::GetClassType() ) ) { + float push = damageDef->GetFloat( "push" ) * speed / idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ); + idVec3 impulse = -push * f * collision.c.normal; + impulse[2] = push * f * 0.75f; + + // Send him flying + ent->ApplyImpulse( theDriver, collision.c.id, collision.c.point, impulse ); + + if ( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugArrow ( colorGreen, collision.c.point, collision.c.point + impulse, 3, 10000 ); + } + } + } + } + } + if ( collisionSelfDamage ) { + idVec3 dmgDir = GetPhysics()->GetLinearVelocity()*-1.0f; + Damage( this, this, dmgDir, collDmgDef.c_str(), collisionSelfDamage, 0 ); + } + return false; +} + +/* +=============== +rvVehicle::Give +=============== +*/ +bool rvVehicle::Give( const char *statname, const char *value ) { + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= healthMax ) { + return false; + } + health = Min ( atoi( value ) + health, healthMax ); + } + + return true; +} + +/* +================ +rvVehicle::AutoRight +================ +*/ +void rvVehicle::AutoRight( idEntity* activator ) { + if( autoRight ) { + return; + } + + autoRight = true; + +/* + idVec3 vec = renderEntity.origin - activator->renderEntity.origin; + vec.Normalize(); + + float dot = DotProduct( vec, renderEntity.axis[ 1 ] ); + + if( dot < 0 ) + { + autoRightDir = -1.0f; + } + else + { + autoRightDir = 1.0f; + } +*/ +} + +/* +================ +rvVehicle::GetPositionIndex +================ +*/ +int rvVehicle::GetPositionIndex ( const rvVehiclePosition* position ) const { + int i; + for ( i = positions.Num() - 1; i >= 0; i -- ) { + if ( &positions[i] == position ) { + return i; + } + } + return -1; +} + +/* +================ +rvVehicle::UpdateHUD +================ +*/ +void rvVehicle::UpdateHUD ( int position, idUserInterface* gui ) { +// VEH_FIXME: godmode ? +/* + if ( mController && mController->GetDriver() && mController->GetDriver()->IsType ( idPlayer::Type ) ) + { + idPlayer* player = static_cast(mController->GetDriver()); + gui->State()->SetInt ( "vehicle_god", player->godmode && g_showGodDamage->integer ); + gui->State()->SetInt ( "vehicle_god_damage", godModeDamage ); + } + else + { + gui->State()->SetInt ( "vehicle_god", 0 ); + } +*/ + gui->SetStateFloat( "vehicle_health", health / spawnArgs.GetFloat( "health", "1" ) ); + gui->SetStateInt ( "vehicle_armor", 0 ); + gui->SetStateInt ( "vehicle_position", position ); + gui->SetStateFloat ( "vehicle_shield", (float)shieldHealth.GetCurrentValue(gameLocal.time) / (float)shieldMaxHealth ); + gui->SetStateInt ( "vehicle_haz", hazardWarningTime != 0 ); + gui->SetStateInt ( "vehicle_locked", IsLocked ( ) ); + + // Update position specific information + positions[position].UpdateHUD ( gui ); +} + +/* +================ +rvVehicle::UpdateCursorGUI +================ +*/ +void rvVehicle::UpdateCursorGUI ( int position, idUserInterface* gui ) { + positions[position].UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehicle::DrawHUD +================ +*/ +void rvVehicle::DrawHUD ( int position ) { + if ( !hud || gameLocal.GetLocalPlayer()->IsObjectiveUp() || gameLocal.GetLocalPlayer()->objectiveSystemOpen ) { + return; + } + + UpdateHUD ( position, hud ); + + hud->Redraw ( gameLocal.time ); +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueHazardWarning ( void ) { + if ( !hazardWarningTime ) { + StartSound ( "snd_voiceHazard", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_hazard" ); + } + } + + hazardWarningTime = gameLocal.time; +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueLockedWarning ( void ) { + if ( gameLocal.time > lockWarningTime + VEHICLE_LOCK_TIMEOUT ) { + StartSound ( "snd_voiceNoExit", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_locked" ); + } + + lockWarningTime = gameLocal.time; + } +} + +/* +================== +rvVehicle::Hide +================== +*/ +void rvVehicle::Hide ( void ) { + idActor::Hide ( ); + + GetPhysics()->SetContents( 0 ); + GetPhysics()->GetClipModel()->Unlink(); +} + +/* +================== +rvVehicle::Show +================== +*/ +void rvVehicle::Show ( void ) { + idActor::Show ( ); + + GetPhysics()->SetContents ( CONTENTS_BODY ); + GetPhysics()->GetClipModel()->Link(); +} + +/* +================== +rvVehicle::Lock +================== +*/ +void rvVehicle::Lock( void ) { + Event_Lock( true ); +} + +/* +================== +rvVehicle::Unlock +================== +*/ +void rvVehicle::Unlock( void ) { + Event_Lock( false ); +} + +/* +================== +rvVehicle::GuidedProjectileLocked +================== +*/ +void rvVehicle::GuidedProjectileIncoming( idGuidedProjectile * projectile ) { + if ( projectile ) { + incomingProjectiles.Insert( projectile ); + } +} + +/* +================== +rvVehicle::UpdateIncomingProjectiles +================== +*/ +void rvVehicle::UpdateIncomingProjectiles( void ) { + idGuidedProjectile * proj = NULL; + float dist = 0.0f; + + for( int i = incomingProjectiles.Num() - 1; i >= 0; i-- ) { + if ( !incomingProjectiles[ i ].IsValid() ) { + incomingProjectiles.RemoveIndex( i ); + continue; + } + + if ( proj ) { + float d = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist > d ) { + proj = incomingProjectiles[ i ]; + dist = d; + } + } else { + proj = incomingProjectiles[ i ]; + dist = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + } + } + + if ( proj ) { + idVec3 localDir; + int length; + + GetPosition( 0 )->mEyeAxis.ProjectVector( proj->GetPhysics()->GetOrigin() - GetOrigin(), localDir ); + GetHud()->SetStateFloat ( "missiledir", localDir.ToAngles()[YAW] ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + + if ( !vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatUp" ); + } + + if ( emitter ) { + StartSound( "snd_incomingProjectile", SND_CHANNEL_BODY3, 0, false, &length ); + } + + vfl.missileWarningOn = true; + } + + if ( emitter ) { + //float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist / 100000.0f ) ); + //refSound.parms.volume = lerp * 0.6f + 0.5f; + //refSound.parms.frequencyShift = lerp * 0.4f + 0.8f; + //emitter->ModifySound ( SND_CHANNEL_ANY, &refSound.parms ); + } + } else if ( vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatDown" ); + } + + StopSound( SND_CHANNEL_BODY3, false ); + + vfl.missileWarningOn = false; + } +} + +/* +================== +rvVehicle::Event_Lock +================== +*/ +void rvVehicle::Event_Lock ( bool lock ) { + vfl.locked = lock; +} + +/* +================== +rvVehicle::Event_IsLocked +================== +*/ +void rvVehicle::Event_IsLocked ( void ) { + idThread::ReturnFloat( (float)IsLocked() ); +} + +/* +================== +rvVehicle::Event_EnableWeapon +================== +*/ +void rvVehicle::Event_EnableWeapon ( void ) { + vfl.disableWeapons = false; +} + +/* +================== +rvVehicle::Event_DisableWeapon +================== +*/ +void rvVehicle::Event_DisableWeapon ( void ) { + vfl.disableWeapons = true; +} + + +/* +================== +rvVehicle::Event_EnableMovement +================== +*/ +void rvVehicle::Event_EnableMovement( void ) { + vfl.disableMovement = false; +} + +/* +================== +rvVehicle::Event_DisableMovement +================== +*/ +void rvVehicle::Event_DisableMovement ( void ) { + vfl.disableMovement = true; +} + +/* +================== +rvVehicle::Event_EnableClip +================== +*/ +void rvVehicle::Event_EnableClip( void ) { + GetPhysics()->SetContents( cachedContents ); +} + +/* +================== +rvVehicle::Event_DisableClip +================== +*/ +void rvVehicle::Event_DisableClip( void ) { + cachedContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); +} + +/* +================== +rvVehicle::Event_Activate +================== +*/ +void rvVehicle::Event_Activate( idEntity* activator ) { + RemoveNullTargets(); + + if( !targets.Num() && activator && activator->IsType(idPlayer::GetClassType()) ) { + static_cast( activator )->EnterVehicle( this ); + } +} + +/* +================== +rvVehicle::Event_LaunchProjectiles +================== +*/ +void rvVehicle::Event_LaunchProjectiles( const idList* parms ) { + int pos = -1; + + assert( parms && parms->Num() ); + + sscanf( (*parms)[0].c_str(), "%d", &pos ); + GetPosition( pos )->FireWeapon(); +} + +/* +================== +rvVehicle::Event_SetScript +================== +*/ +void rvVehicle::Event_SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "enter" ) ) { + funcs.enter.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "exit" ) ) { + funcs.exit.Init( funcName ); + } else { + gameLocal.Warning ( "unknown script '%s' specified on vehicle '%s'", scriptName, name.c_str() ); + } +} + +/* +================ +rvVehicle::Event_SetHealth +================ +*/ +void rvVehicle::Event_SetHealth ( float health ) { + this->health = health; +} + +/* +================ +rvVehicle::Event_HUDShockWarningOff +================ +*/ +void rvVehicle::Event_HUDShockWarningOff ( ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "electricWarningOff" ); + } +} + +/* +================ +rvVehicle::Event_StalledRestart +================ +*/ +void rvVehicle::Event_StalledRestart ( float shield, float damage ) { + vfl.stalled = false; + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + // Looping warning sound for shield + if ( shield <= 0 ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +/* +================ +rvVehicle::Event_GetViewAngles +================ +*/ +void rvVehicle::Event_GetViewAngles ( ) { + + idThread::ReturnVector( GetPosition( 0)->GetEyeAxis()[0]); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicle ) + STATE ( "Wait_Driver", rvVehicle::State_Wait_Driver ) +END_CLASS_STATES + +/* +================ +rvVehicle::State_Wait_Driver +================ +*/ +stateResult_t rvVehicle::State_Wait_Driver ( int blendFrames ) { + if ( !vfl.driver || vfl.stalled ) { + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/source/mpgame/vehicle/Vehicle.h b/source/mpgame/vehicle/Vehicle.h new file mode 100644 index 0000000..5945624 --- /dev/null +++ b/source/mpgame/vehicle/Vehicle.h @@ -0,0 +1,416 @@ +//---------------------------------------------------------------- +// Vehicle.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +class rvVehiclePosition { +public: + + rvVehiclePosition( void ); + virtual ~rvVehiclePosition( void ); + + usercmd_t mInputCmd; + idAngles mInputAngles; + usercmd_t mOldInputCmd; + idAngles mOldInputAngles; + int mOldInputFlags; + + int mCurrentWeapon; + + idEntityPtr mDriver; + idEntityPtr mParent; + + rvVehiclePartList_t mParts; + rvVehiclePartList_t mWeapons; + + idVec3 mEyeOrigin; + idMat3 mEyeAxis; + + jointHandle_t mEyeJoint; + idVec3 mEyeOffset; + idMat3 mEyeJointTransform; + idAngles mDeltaEyeAxisScale; + int mEyeJointAxisMap[3]; + int mEyeJointDirMap[3]; + + idMat3 mAxisOffset; + + jointHandle_t mDriverJoint; + idVec3 mDriverOffset; + idMat3 mDriverJointTransform; + idAngles mDeltaDriverAxisScale; + int mDriverJointAxisMap[3]; + int mDriverJointDirMap[3]; + + idVec3 mExitPosOffset; + idMat3 mExitAxisOffset; + + idStr mDriverAnim; + + idStr mInternalSurface; + + struct positionFlags_s { + bool inputValid :1; + bool engine :1; + bool driverVisible :1; + bool depthHack :1; + bool internalView :1; + bool bindDriver :1; + bool stalled :1; + } fl; + + void Init ( rvVehicle* parent, const idDict& args ); + + int AddPart ( const idTypeInfo &classdef, const idDict& args ); + rvVehiclePart* GetPart ( int partIndex ); + + bool IsOccupied ( void ) const; + bool IsEngine ( void ) const; + + bool SetDriver ( idActor* driver ); + void SetInput ( const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( usercmd_t& cmd, idAngles& newAngles ) const; + + bool EjectDriver ( bool force = false ); + + void RunPrePhysics ( void ); + void RunPostPhysics ( void ); + + void SelectWeapon ( int weapon ); + + void UpdateHUD ( idUserInterface* gui ); + void UpdateCursorGUI ( idUserInterface* gui ); + void UpdateInternalView ( bool force = false ); + + virtual idMat3 GetAxis ( void ) const; + virtual idVec3 GetOrigin ( const idVec3& offset = vec3_zero ) const; + + const idVec3& GetEyeOrigin ( void ) const; + const idMat3& GetEyeAxis ( void ) const; + rvVehicle* GetParent ( void ) const; + idActor* GetDriver ( void ) const; + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + void GetPosition ( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const; + + void FireWeapon ( void ); + + rvVehicleWeapon * GetWeapon ( int weaponIndex ); + rvVehicleWeapon * GetActiveWeapon ( void ); + +private: + + void SetParts ( const idDict& args ); + void ActivateParts ( bool active ); + + int mSoundPart; + float mSoundMaxSpeed; +}; + +typedef struct rvVehicleFuncs_s { + rvScriptFuncUtility enter; // script to run when the vehicle becomes occupied + rvScriptFuncUtility exit; // script to run when the vehicle becomes unoccupied +} rvVehicleFuncs_t; + +class rvVehicle : public idActor { +public: + + CLASS_PROTOTYPE( rvVehicle ); + + rvVehicle ( void ); + ~rvVehicle ( void ); + + void Spawn ( void ); + void Think ( void ); + bool Give ( const char *statname, const char *value ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { return false; } + + virtual const idMat3& GetAxis ( int id = 0 ) const; + virtual const idVec3& GetOrigin ( int id = 0 ) const; + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + virtual void EjectAllDrivers ( bool force = false ); + rvVehiclePosition* GetPosition ( int index ); + const rvVehiclePosition* GetPosition ( int index ) const; + int GetPositionIndex ( const rvVehiclePosition* position ) const; + + int GetNumPositions ( void ) const; + int GetNumDrivers ( void ) const; + bool HasOpenPositions ( void ) const; + + bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + virtual void UpdateState (); + + float GetEngineOffTime ( void ) const; + float GetEngineOnTime ( void ) const; + + bool IsFlipped ( void ) const; + bool IsFrozen ( void ) const; + bool IsLocked ( void ) const; + bool HasDrivers ( void ) const; + + bool IsShootingEnabled ( void ) const; + bool IsMovementEnabled ( void ) const; + + void Lock ( void ); + void Unlock ( void ); + + void AutoRight ( idEntity* activator ); + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( int position, idVec3& origin, idMat3& axis ); + virtual void GetEyePosition ( int position, idVec3& origin, idMat3& axis ); + void DrawHUD ( int position ); + idUserInterface* GetHud ( void ); + void UpdateCursorGUI ( int position, idUserInterface* ui ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( int position, usercmd_t& cmd, idAngles& newAngles ) const; + + void IssueHazardWarning ( void ); + void IssueLockedWarning ( void ); + + void AddToBounds ( const idVec3& vec ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ) {} + + float FocusLength ( void ) const { return spawnArgs.GetFloat("focusLength_enter", "60"); } + + bool IsAutoCorrecting ( void ) const { return autoCorrectionBegin != 0; } + bool IsStalled ( void ) const { return vfl.stalled; } + + void OnEnter ( void ) { funcs.enter.CallFunc( NULL ); fl.exitedVehicle=false; }; + void OnExit ( void ) { if ( !fl.exitedVehicle ) { fl.exitedVehicle=true; funcs.exit.CallFunc( NULL );} }; + + bool IsStrafing ( void ) const { return vfl.strafe; } +// void PlayAnim ( int channel, const char *name, int blendFrames ); + + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); +protected: + + void SelectWeapon ( int weapon ); + + void UpdateDrivers ( int delta ); + virtual void UpdateHUD ( int position, idUserInterface* gui ); + + void SetPositions ( void ); + + void SetCombatModel ( void ); + void LinkCombat ( void ); + + // twhitaker: + friend class rvVehicleDriver; + virtual void RunPrePhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + + idList positions; + int drivers; + + idUserInterface * hud; + + float crashSpeedSmall; + float crashSpeedMedium; + float crashSpeedLarge; + idStr crashDamage; + rvClientEffectPtr crashEffect; + int crashNextSound; + int crashTime; + + float autoRightDir; + bool autoRight; + + // twhitaker: for rvVehicleDriver, to avoid getting stuck. + unsigned autoCorrectionBegin; // time when obstacle avoidance state began (0 if not correcting) + + struct vehicleFlags_s { + bool forward :1; + bool backward :1; + bool left :1; + bool right :1; + bool driver :1; + bool locked :1; + bool godmode :1; + bool frozen :1; + bool disableWeapons :1; + bool disableMovement :1; + bool scripted :1; + bool endWithIdle :1; + bool flipEject :1; + bool dead :1; + bool strafe :1; + bool missileWarningOn :1; + bool stalled :1; + } vfl; + + float damageStaticChance; + + // Shields + float shieldMaxHealth; + idInterpolate shieldHealth; + int shieldHitTime; + float shieldRegenTime; + int shieldRegenDelay; + idClipModel* shieldModel; + + int healthRegenDelay; + int healthRegenRate; + idInterpolate healthRegenAmount; + + int hazardWarningTime; + int lockWarningTime; + int healthMax; + int healthLow; + int godModeDamage; + + int cachedContents; + + rvVehicleFuncs_t funcs; + + float crashVelocitySmall; + float crashVelocityMedium; + float crashVelocityLarge; + + bool alwaysImpactDamage; + + void UpdateIncomingProjectiles( void ); + +public: + idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; +private: + + void Event_Lock ( bool locked ); + void Event_IsLocked ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + void Event_EnableClip ( void ); + void Event_DisableClip ( void ); + void Event_Activate ( idEntity* activator ); + void Event_LaunchProjectiles ( const idList* parms ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetHealth ( float health ); + void Event_HUDShockWarningOff( void ); + void Event_StalledRestart ( float shield, float damage ); + void Event_GetViewAngles ( void ); + + virtual void OnDeath ( void ) { } + + stateResult_t State_Wait_Driver ( int blendFrames ); + + CLASS_STATES_PROTOTYPE( rvVehicle ); +}; + +/* +=============== +rvVehiclePosition inlines +=============== +*/ + +ID_INLINE bool rvVehiclePosition::IsOccupied ( void ) const { return mDriver.IsValid();} +ID_INLINE bool rvVehiclePosition::IsEngine ( void ) const { return fl.engine; } +ID_INLINE const idVec3& rvVehiclePosition::GetEyeOrigin ( void ) const { return mEyeOrigin; } +ID_INLINE const idMat3& rvVehiclePosition::GetEyeAxis ( void ) const { return mEyeAxis; } +ID_INLINE rvVehicle* rvVehiclePosition::GetParent ( void ) const { return mParent; } +ID_INLINE idActor* rvVehiclePosition::GetDriver ( void ) const { return mDriver; } +ID_INLINE rvVehiclePart* rvVehiclePosition::GetPart ( int partIndex ) { return mParts[partIndex]; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetWeapon ( int weaponIndex ){ return ( weaponIndex >= 0 && weaponIndex < mWeapons.Num() ) ? static_cast(mWeapons[weaponIndex]) : 0; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetActiveWeapon ( void ) { return GetWeapon( mCurrentWeapon ); } + +/* +=============== +rvVehicle inlines +=============== +*/ + +ID_INLINE bool rvVehicle::IsFlipped( void ) const { + return (( !vfl.flipEject ) ? false : + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) > 60.f || + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().pitch ) > 60.0f )); +} + +ID_INLINE bool rvVehicle::IsFrozen( void ) const { + return vfl.frozen; +} + +ID_INLINE idUserInterface* rvVehicle::GetHud ( void ) { + return hud; +} + +ID_INLINE bool rvVehicle::IsLocked ( void ) const { + return vfl.locked || hazardWarningTime != 0; +} + +ID_INLINE rvVehiclePosition* rvVehicle::GetPosition ( int index ) { + return &positions[index]; +} + +ID_INLINE const rvVehiclePosition* rvVehicle::GetPosition ( int index ) const { + return &positions[index]; +} + +ID_INLINE void rvVehicle::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +ID_INLINE void rvVehicle::GetInput( int position, usercmd_t& cmd, idAngles& newAngles ) const { + GetPosition(position)->GetInput( cmd, newAngles ); +} + +ID_INLINE bool rvVehicle::HasDrivers ( void ) const { + return drivers > 0; +} + +ID_INLINE void rvVehicle::AddToBounds ( const idVec3& vec ) { + renderEntity.bounds.AddPoint ( (vec-renderEntity.origin) * GetPhysics()->GetAxis().Transpose() ); +} + +ID_INLINE bool rvVehicle::IsShootingEnabled ( void ) const { + return !vfl.disableWeapons; +} + +ID_INLINE bool rvVehicle::IsMovementEnabled ( void ) const { + return !vfl.disableMovement; +} + +ID_INLINE int rvVehicle::GetNumPositions ( void ) const { + return positions.Num(); +} + +ID_INLINE int rvVehicle::GetNumDrivers ( void ) const { + return drivers; +} + +ID_INLINE bool rvVehicle::HasOpenPositions ( void ) const { + return GetNumPositions() > GetNumDrivers(); +} + +#endif // __GAME_VEHICLE_H__ diff --git a/source/mpgame/vehicle/VehicleAnimated.cpp b/source/mpgame/vehicle/VehicleAnimated.cpp new file mode 100644 index 0000000..380e4f8 --- /dev/null +++ b/source/mpgame/vehicle/VehicleAnimated.cpp @@ -0,0 +1,237 @@ + +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleAnimated ) +END_CLASS + +rvVehicleAnimated::rvVehicleAnimated ( void ) { +} + +rvVehicleAnimated::~rvVehicleAnimated ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleAnimated::Spawn +================ +*/ +void rvVehicleAnimated::Spawn( void ) { + + turnRate = spawnArgs.GetFloat ( "turnRate", "90" ); + viewAngles = GetPhysics()->GetAxis ( ).ToAngles ( ); + viewAxis = viewAngles.ToMat3(); + + // Initialize the physics object + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetMaxStepHeight( spawnArgs.GetFloat ( "stepheight", "14" ) ); + + // Start just a tad above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetDelta( vec3_origin ); + + // Gravity + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat ( ); + physicsObj.SetGravity( gravity ); + SetPhysics( &physicsObj ); + + animator.RemoveOriginOffset( true ); + + additionalDelta.Zero(); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleAnimated::ClientPredictionThink +================ +*/ +void rvVehicleAnimated::ClientPredictionThink ( void ) { + Think(); +} + +/* +================ +rvVehicleAnimated::Think +================ +*/ +void rvVehicleAnimated::Think ( void ) { + + rvVehicle::Think(); + + float rate = 0.0f; + usercmd_t& cmd = positions[0].mInputCmd; + idVec3 delta; + + if( positions[0].IsOccupied() && !IsFrozen() && IsMovementEnabled() ) { + + if (( g_vehicleMode.GetInteger() == 0 && !( cmd.buttons & BUTTON_STRAFE )) || // If we're in the old driving mode and we aren't strafing or... + ( g_vehicleMode.GetInteger() != 0 && ( cmd.buttons & BUTTON_STRAFE ))) // If we're in the new driving mode and we are strafing + { + rate = SignZero(cmd.forwardmove) * turnRate; + rate *= idMath::MidPointLerp( 0.0f, 0.9f, 1.0f, idMath::Fabs(cmd.rightmove) / 127.0f ); + viewAngles.yaw += Sign(cmd.rightmove) * rate * MS2SEC(gameLocal.GetMSec()); + } + } + + viewAngles.Normalize360(); + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + delta += additionalDelta; + + idStr alignmentJoint; + if ( g_vehicleMode.GetInteger() != 0 && spawnArgs.GetString( "alignment_joint", 0, alignmentJoint ) ) { + idVec3 offset; + idMat3 axis; + GetJointWorldTransform( animator.GetJointHandle( alignmentJoint ), gameLocal.time, offset, axis ); + delta *= axis; + } else { + viewAxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + delta *= viewAxis; + } + + physicsObj.SetDelta( delta ); + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetAxis +================ +*/ +const idMat3& rvVehicleAnimated::GetAxis( int id ) const { + return viewAxis; +} + +/* +================ +rvVehicleAnimated::WriteToSnapshot +================ +*/ +void rvVehicleAnimated::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot ( msg ); + + physicsObj.WriteToSnapshot( msg ); + msg.WriteFloat( viewAngles[0] ); + msg.WriteFloat( viewAngles[1] ); + msg.WriteFloat( viewAngles[2] ); +} + +/* +================ +rvVehicleAnimated::ReadFromSnapshot +================ +*/ +void rvVehicleAnimated::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot ( msg ); + + physicsObj.ReadFromSnapshot( msg ); + viewAngles[0] = msg.ReadFloat( ); + viewAngles[1] = msg.ReadFloat( ); + viewAngles[2] = msg.ReadFloat( ); +} + +/* +================ +rvVehicleAnimated::Save +================ +*/ +void rvVehicleAnimated::Save ( idSaveGame *savefile ) const { + savefile->WriteVec3( storedPosition ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->WriteAngles ( viewAngles ); + savefile->WriteFloat ( turnRate ); + + // why are we writing out the viewAxis here??? + savefile->WriteMat3 ( viewAxis ); + + // TEMP + animator.Save( savefile ); +} + +/* +================ +rvVehicleAnimated::Restore +================ +*/ +void rvVehicleAnimated::Restore ( idRestoreGame *savefile ) { + savefile->ReadVec3( storedPosition ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadAngles ( viewAngles ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadMat3 ( viewAxis ); + + // TEMP - restore animator, because it was cleared when loading the AF + animator.Restore( savefile ); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + // TEMP + + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleAnimated::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +rvVehicleAnimated::RunPrePhysics +================ +*/ +void rvVehicleAnimated::RunPrePhysics ( void ) { + storedPosition = physicsObj.GetOrigin(); +} + +/* +================ +rvVehicleAnimated::RunPostPhysics +================ +*/ +void rvVehicleAnimated::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 3000 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + idVec3 delta; + idVec3 actualDelta = physicsObj.GetOrigin() - storedPosition; + + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( !autoCorrectionBegin && delta.LengthSqr() * 0.2f > actualDelta.LengthSqr() ) { + autoCorrectionBegin = gameLocal.time; + } + } +} + + diff --git a/source/mpgame/vehicle/VehicleAnimated.h b/source/mpgame/vehicle/VehicleAnimated.h new file mode 100644 index 0000000..c06251d --- /dev/null +++ b/source/mpgame/vehicle/VehicleAnimated.h @@ -0,0 +1,49 @@ +//---------------------------------------------------------------- +// VehicleAnimated.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEANIMATED_H__ +#define __GAME_VEHICLEANIMATED_H__ + +#include "Vehicle.h" +#include "../physics/Physics_Monster.h" + +class rvVehicleAnimated : public rvVehicle { +public: + + CLASS_PROTOTYPE( rvVehicleAnimated ); + + rvVehicleAnimated ( void ); + ~rvVehicleAnimated ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual const idMat3& GetAxis ( int id = 0 ) const; + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +protected: + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedPosition; + // end twhitaker: + + idPhysics_Monster physicsObj; + + idAngles viewAngles; + float turnRate; + idVec3 additionalDelta; +}; + +#endif // __GAME_VEHICLEANIMATED_H__ diff --git a/source/mpgame/vehicle/VehicleController.cpp b/source/mpgame/vehicle/VehicleController.cpp new file mode 100644 index 0000000..48a9724 --- /dev/null +++ b/source/mpgame/vehicle/VehicleController.cpp @@ -0,0 +1,301 @@ +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleController.h" +#include "Vehicle.h" + +/* +===================== +rvVehicleController::rvVehicleController +===================== +*/ +rvVehicleController::rvVehicleController ( void ) { + mVehicle = NULL; + //mDriver = NULL; + mPosition = 0; +} + +/* +===================== +rvVehicleController::Save +===================== +*/ +void rvVehicleController::Save ( idSaveGame *savefile ) const { + mVehicle.Save ( savefile ); + savefile->WriteInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Restore +===================== +*/ +void rvVehicleController::Restore ( idRestoreGame *savefile ) { + mVehicle.Restore ( savefile ); + savefile->ReadInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Drive +===================== +*/ +bool rvVehicleController::Drive ( rvVehicle* vehicle, idActor* driver ) { + assert ( vehicle && driver ); + + // Flip the vehicle back over? + if ( vehicle->IsFlipped ( ) ) { + vehicle->AutoRight ( vehicle ); + return false; + } + + // Add the driver to the vehicle and cache the position it was added. + if ( -1 == (mPosition = vehicle->AddDriver ( 0, driver ) ) ) { + return false; + } + + mVehicle = vehicle; + + //twhitaker: for scripted callback events + vehicle->OnEnter(); + + if ( driver->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( driver ); + + if ( player->GetHud() ) { + GetHud()->Activate( true, gameLocal.time ); + GetHud()->HandleNamedEvent( "showExitmessage" ); + } + } + + return true; +} + +/* +===================== +rvVehicleController::Eject +===================== +*/ +bool rvVehicleController::Eject ( bool force ) { + if ( !GetVehicle() ) { + return true; + } + + if ( GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( GetDriver() ); + + if ( player->GetHud() ) { + GetHud()->HandleNamedEvent( "hideExitmessage" ); + } + } + + if ( mVehicle->RemoveDriver ( GetPosition(), force ) ) { + mVehicle->OnExit(); + mVehicle = NULL; + + return true; + } + + return false; +} + +/* +===================== +rvVehicleController::FindClearExitPoint +===================== +*/ +bool rvVehicleController::FindClearExitPoint( idVec3& origin, idMat3& axis ) const { + return GetVehicle()->FindClearExitPoint( mPosition, origin, axis ); +} + +/* +===================== +rvVehicleController::KillVehicles +===================== +*/ +void rvVehicleController::KillVehicles ( void ) { + KillEntities( idCmdArgs(), rvVehicle::GetClassType() ); +} + +/* +===================== +rvVehicleController::DrawHUD +===================== +*/ +void rvVehicleController::DrawHUD ( void ) { + assert ( mVehicle ); + + if ( GetDriver() && GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast( GetDriver() ); + rvVehicleWeapon * weapon = mVehicle->GetPosition( mPosition )->GetActiveWeapon(); + if ( weapon ) { + if ( weapon->CanZoom() && player->IsZoomed() && player == gameLocal.GetLocalPlayer() ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } +// if ( mVehicle->GetHud() ) { +// mVehicle->GetHud()->SetStateFloat( "tram_heatpct", weapon->GetOverheatPercent()); +// mVehicle->GetHud()->SetStateFloat( "tram_overheat", weapon->GetOverheatState()); +// } + } + } + + mVehicle->DrawHUD ( mPosition ); +} + +/* +===================== +rvVehicleController::StartRadioChatter +===================== +*/ +void rvVehicleController::StartRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterUp" ); + } +} + +/* +===================== +rvVehicleController::StopRadioChatter +===================== +*/ +void rvVehicleController::StopRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterDown" ); + } +} + +/* +===================== +rvVehicleController::Give +===================== +*/ +void rvVehicleController::Give ( const char* statname, const char* value ) { + if( mVehicle.IsValid() ) { + mVehicle->Give ( statname, value ); + } +} + +/* +===================== +rvVehicleController::GetEyePosition +===================== +*/ +void rvVehicleController::GetEyePosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetEyePosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetDriverPosition +===================== +*/ +void rvVehicleController::GetDriverPosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetDriverPosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetVehicle +===================== +*/ +rvVehicle* rvVehicleController::GetVehicle ( void ) const { + return mVehicle; +} + +idActor* rvVehicleController::GetDriver ( void ) const { + return (GetVehicle()) ? GetVehicle()->GetPosition(GetPosition())->GetDriver() : NULL; +} + +/* +===================== +rvVehicleController::SetInput +===================== +*/ +void rvVehicleController::SetInput ( const usercmd_t& cmd, const idAngles &angles ) { + if( mVehicle.IsValid() ) { + mVehicle->SetInput ( mPosition, cmd, angles ); + } +} + +/* +===================== +rvVehicleController::GetInput +===================== +*/ +void rvVehicleController::GetInput( usercmd_t& cmd, idAngles &newAngles ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetInput( mPosition, cmd, newAngles ); + } +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +idUserInterface* rvVehicleController::GetHud( void ) { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +const idUserInterface* rvVehicleController::GetHud( void ) const { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::WriteToSnapshot +================ +*/ +void rvVehicleController::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteLong ( mPosition ); + msg.WriteLong ( mVehicle.GetSpawnId ( ) ); +} + +/* +================ +rvVehicleController::ReadFromSnapshot +================ +*/ +void rvVehicleController::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mPosition = msg.ReadLong ( ); + mVehicle.SetSpawnId ( msg.ReadLong ( ) ); +} + +/* +================ +rvVehicleController::UpdateCursorGUI +================ +*/ +void rvVehicleController::UpdateCursorGUI ( idUserInterface* ui ) { + assert ( mVehicle ); + mVehicle->UpdateCursorGUI ( mPosition, ui ); +} + +/* +================ +rvVehicleController::SelectWeapon +================ +*/ +void rvVehicleController::SelectWeapon ( int weapon ) { + if( mVehicle.IsValid() ) { + mVehicle->GetPosition( mPosition )->SelectWeapon( weapon ); + } +} diff --git a/source/mpgame/vehicle/VehicleController.h b/source/mpgame/vehicle/VehicleController.h new file mode 100644 index 0000000..bf2c4c5 --- /dev/null +++ b/source/mpgame/vehicle/VehicleController.h @@ -0,0 +1,69 @@ +//---------------------------------------------------------------- +// VehicleController.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLECONTROLLER_H__ +#define __GAME_VEHICLECONTROLLER_H__ + +class rvVehicle; +class rvVehiclePosition; + +class rvVehicleController { +public: + + rvVehicleController ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Drive ( rvVehicle* vehicle, idActor* driver ); + bool Eject ( bool force = false ); + + bool FindClearExitPoint ( idVec3& origin, idMat3& axis ) const; + + bool IsDriving ( void ) const; + + rvVehicle* GetVehicle ( void ) const; + idActor* GetDriver ( void ) const; + int GetPosition ( void ) const; + + void SetInput ( const usercmd_t& cmd, const idAngles &newAngles ); + void GetInput ( usercmd_t& cmd, idAngles &newAngles ) const; + + idUserInterface* GetHud ( void ); + const idUserInterface* GetHud ( void ) const; + void DrawHUD ( void ); + void UpdateCursorGUI ( idUserInterface* ui ); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void Give ( const char* statname, const char* value ); + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + + static void KillVehicles ( void ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void SelectWeapon ( int weapon ); + +protected: + + idEntityPtr mVehicle; + int mPosition; +}; + +ID_INLINE bool rvVehicleController::IsDriving ( void ) const { + return mVehicle.IsValid ( ); +} + +ID_INLINE int rvVehicleController::GetPosition ( void ) const { + return mPosition; +} + +#endif // __GAME_VEHICLECONTROLLER_H__ + diff --git a/source/mpgame/vehicle/VehicleDriver.cpp b/source/mpgame/vehicle/VehicleDriver.cpp new file mode 100644 index 0000000..2e5e315 --- /dev/null +++ b/source/mpgame/vehicle/VehicleDriver.cpp @@ -0,0 +1,836 @@ + +// VehicleDriver.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include + +const idEventDef VD_ChoosePathTarget( "choosePathTarget", "e", 'e' ); +const idEventDef EV_FollowOffset( "followOffset", "v" ); +const idEventDef EV_FireWeapon ( "fireWeapon", "ff" ); + +CLASS_DECLARATION( idActor, rvVehicleDriver ) + EVENT( EV_PostSpawn , rvVehicleDriver::Event_PostSpawn ) + EVENT( AI_EnterVehicle , rvVehicleDriver::Event_EnterVehicle ) + EVENT( AI_ExitVehicle , rvVehicleDriver::Event_ExitVehicle ) + EVENT( AI_ScriptedMove , rvVehicleDriver::Event_ScriptedMove ) + EVENT( AI_ScriptedDone , rvVehicleDriver::Event_ScriptedDone ) + EVENT( AI_ScriptedStop , rvVehicleDriver::Event_ScriptedStop ) + EVENT( EV_Activate , rvVehicleDriver::Event_Trigger ) + EVENT( EV_Speed , rvVehicleDriver::Event_SetSpeed ) + EVENT( EV_FireWeapon , rvVehicleDriver::Event_FireWeapon ) + EVENT( AI_FaceEntity , rvVehicleDriver::Event_FaceEntity ) + EVENT( AI_LookAt , rvVehicleDriver::Event_LookAt ) + EVENT( AI_SetLeader , rvVehicleDriver::Event_SetLeader ) + +//twhitaker: remove - begin + EVENT( EV_FollowOffset , rvVehicleDriver::Event_SetFollowOffset ) +//twhitaker: remove - end +END_CLASS + +/* +================ +rvVehicleDriver::rvVehicleDriver +================ +*/ +rvVehicleDriver::rvVehicleDriver ( void ) { +} + +/* +================ +rvVehicleDriver::Spawn +================ +*/ +void rvVehicleDriver::Spawn ( void ) { + currentThrottle = 1.0f; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; + leaderFlags = 0; + decelDistance = 0.0f; + minDistance = 0.0f; + fireEndTime = 0.0f; + isMoving = false; + avoidingLeader = false; + pathingMode = VDPM_Random; + pathingOrigin = vec3_origin; + pathingEntity = NULL; + + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + SIMDProcessor->Memset( &lastPathTargetInfo, 0, sizeof( PathTargetInfo ) ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleDriver::Think +================ +*/ +void rvVehicleDriver::Think ( void ) { + if ( !IsDriving() ) { + if ( leader ) { + leader->SetLeaderHint( VDLH_Continue ); + leader = NULL; + } + return; + } + + if ( pathTargetInfo.node ) { + float targetDistance = 0.0f; + float dotForward = 0.0f; + float dotRight = 0.0f; + float desiredThrottle = 1.0f; + idEntity* currentPathTarget = pathTargetInfo.node; + rvVehicle* vehicle = vehicleController.GetVehicle(); + idMat3 vehicleAxis = vehicle->GetAxis(); + idVec3 targetOrigin; + idVec3 dirToTarget; + usercmd_t cmd; + idAngles ang; + + // We may want to hack the auto correction variable based on what the state of the driver and the driver's leader. + UpdateAutoCorrection(); + + if ( vehicle->IsAutoCorrecting( ) ) { + if ( lastPathTargetInfo.node ) { + currentPathTarget = lastPathTargetInfo.node; + } + + if ( g_debugVehicleDriver.GetInteger() != 0 ) { + gameRenderWorld->DebugBounds( colorCyan, vehicle->GetPhysics()->GetAbsBounds().Expand( 30 ) ); + } + } + + // Touch all the triggers that the vehicle touches + vehicle->TouchTriggers(); + + GetTargetInfo( currentPathTarget, &targetOrigin, &dirToTarget, &dotForward, &dotRight, &targetDistance ); + + // The primary purpose of the following portions of code is to set the desiredThrottle variable. + if ( IsMoving() ) { + + // if we're slowing down lerp throttle. + if ( pathTargetInfo.throttle < lastPathTargetInfo.throttle ) { + desiredThrottle = idMath::Lerp( lastPathTargetInfo.throttle, pathTargetInfo.throttle, targetDistance / pathTargetInfo.initialDistance ); + } else { + // otherwise accelerate rapidly or maintain throttle. + desiredThrottle = pathTargetInfo.throttle; + } + + // we could potentially be a leader, so check the leader flags + if ( leaderFlags & VDLH_SlowDown ) { + desiredThrottle = 0.1f; + } else if ( leaderFlags & VDLH_Wait ) { + desiredThrottle = 0.0f; + } + + // if we're following a someone ... + if ( leader ) { + bool canSeeLeader = vehicle->CanSee( leader->vehicleController.GetVehicle(), false ); + float distanceToLeader = ( leader->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + avoidingLeader = false; + + // update the leaders flags based on distance and visibility + if ( !canSeeLeader ) { + // if we can't see the leader, tell him to stop + leader->SetLeaderHint( VDLH_Wait ); + + } else if ( distanceToLeader > decelDistance ) { + // if we're too far from the leader, tell him to slow down + leader->SetLeaderHint( VDLH_SlowDown ); + + } else { + // we're within range of the leader so let him move along at a normal rate + leader->SetLeaderHint( VDLH_Continue ); + + // if we're too close to the leader, we need to slow ourself down + if ( distanceToLeader < minDistance ) { + desiredThrottle = -0.2f; + avoidingLeader = true; + } + } + } + + // the desiredThrottle variable should be set at this point, the only other thing that could change it + // would be in SimulateKeys, if the dotForward < 0. + } + + vehicleController.GetInput( cmd, ang ); + + // Simulate Input + SimulateKeys( cmd, dotForward, dotRight, desiredThrottle * currentThrottle, targetDistance ); + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + + // Node Transition + if( (!vehicle->IsAutoCorrecting() || !IsValidPathNode( currentPathTarget )) && isMoving ) { + idVec3 point = targetOrigin - dirToTarget * pathTargetInfo.minDistance; + + if( vehicle->GetPhysics()->GetAbsBounds().ContainsPoint( point ) ) { + int numTargets = NumValidTargets( currentPathTarget ); + lastPathTargetInfo = pathTargetInfo; + //TODO: ponder - should I be setting lastPathTargetInfo.node to pathTargetInfo.node or currentPathTarget??? + + if( numTargets ) { + Event_ScriptedMove( ChooseNextNode( currentPathTarget ), 0, 0 ); + } + + if( lastPathTargetInfo.exitVehicle ) { + Event_ExitVehicle( true ); + } else if( lastPathTargetInfo.throttle == 0 || !numTargets ) { + Event_ScriptedStop(); + + if( !numTargets ) { + pathTargetInfo.node = NULL; + } + } + + // Debug Output + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-5, -5, -5), idVec3(5, 5, 5)), point ); + } + } + } + } else { // no path + float dotForward; + float dotRight; + usercmd_t cmd; + idAngles ang; + + vehicleController.GetInput( cmd, ang ); + + if ( GetTargetInfo( faceTarget, NULL, NULL, &dotForward, &dotRight, NULL) ) { + if( ( 1.0f - dotForward ) < VECTOR_EPSILON ) { + Event_ScriptedStop(); + } else { + SimulateKeys( cmd, dotForward, dotRight, 0.0f ); + } + } + + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + } +} + +/* +================ +rvVehicleDriver::GetTargetInfo +================ +*/ +bool rvVehicleDriver::GetTargetInfo( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, float* dotForward, float* dotRight, float* distance ) const { + if( !target || !IsDriving() ) { + return false; + } + + idMat3 vehicleAxis = vehicleController.GetVehicle()->GetAxis(); + idVec3 vehicleOrigin = vehicleController.GetVehicle()->GetOrigin(); + idVec3 forward = vehicleAxis[ 0 ]; + idVec3 localTargetOrigin = target->GetPhysics()->GetOrigin(); + localTargetOrigin.z = vehicleOrigin.z;// Find way to include vehicleAxis here + idVec3 vectorToTarget = localTargetOrigin - vehicleOrigin; + idVec3 localDirToTarget = vectorToTarget; + float distToTarget = localDirToTarget.Normalize(); + + if( targetOrigin ) { + *targetOrigin = localTargetOrigin; + } + + if( dirToTarget ) { + *dirToTarget = localDirToTarget; + } + + if( distance ) { + *distance = distToTarget; + } + + if( dotForward ) { + *dotForward = localDirToTarget * forward; + } + + if( dotRight ) { + *dotRight = localDirToTarget * vehicleAxis[ 1 ]; + } + + // Debug help + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + idStr temp; + const idVec3 & origin = GetPhysics()->GetOrigin(); + gameRenderWorld->DebugLine( colorBlue, origin, target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DebugBounds( colorBlue, GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DebugBounds( colorBlue, target->GetPhysics()->GetBounds(), target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DrawText( target->GetName(), vehicleOrigin + vectorToTarget + vehicleAxis[2] * 5.0f, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + gameRenderWorld->DrawText( idStr( "State: " ) + LeaderHintsString( leaderFlags, temp ), origin, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + + if ( vehicleController.GetVehicle()->IsAutoCorrecting() ) { + gameRenderWorld->DebugBounds( colorPurple, vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DrawText( "Auto-Correcting", vehicleOrigin, 1.0f, colorPurple, gameLocal.GetLocalPlayer()->viewAxis ); + } + + if ( pathTargetInfo.node && g_debugVehicleDriver.GetInteger( ) == 2 ) { + gameRenderWorld->DebugBounds( colorOrange, pathTargetInfo.node->GetPhysics()->GetBounds() ); + + for ( int ix = 0; ix < pathTargetInfo.node->targets.Num(); ix++ ) { + gameRenderWorld->DebugLine( colorOrange, pathTargetInfo.node->GetPhysics()->GetOrigin(), pathTargetInfo.node->targets[ ix ]->GetPhysics()->GetOrigin(), 0, true ); + } + } + if ( leader ) { + idVec4 & color = colorGreen; + + if ( leader->GetLeaderHint() & VDLH_SlowDown ) { + color = colorYellow; + } else if ( leader->GetLeaderHint() & VDLH_Wait ) { + color = colorRed; + } + + idStr str = idStr( "decel_distance: " ) + idStr( decelDistance ) + idStr( "\nmin_distance: " ) + idStr( minDistance ); + + gameRenderWorld->DrawText( str, leader->GetPhysics()->GetOrigin(), 1.0f, color, gameLocal.GetLocalPlayer()->viewAxis, 1, 0, true ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), leader->GetPhysics()->GetOrigin(), 0, true ); + gameRenderWorld->DebugBounds( color, leader->vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds(), vec3_origin, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), decelDistance, 10, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), minDistance, 10, 0, true ); + } + } + + return true; +} + +/* +================ +rvVehicleDriver::ChooseNextNode +================ +*/ +idEntity* rvVehicleDriver::ChooseNextNode( idEntity* target ) { + if( !target ) { + return NULL; + } + + if( func.Init( target->spawnArgs.GetString( "call_nextNode" )) <= 0 ) { + idEntity * best = NULL; + float bestDist; + + switch ( pathingMode ) { + case VDPM_MoveTo: + bestDist = FLT_MAX; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist > distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + break; + + case VDPM_MoveAway: + bestDist = FLT_MIN; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist < distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + case VDPM_Custom: + if ( pathingEntity ) { +// best = pathingCallback( target ); + pathingEntity->ProcessEvent( &VD_ChoosePathTarget, target ); + best = gameLocal.program.GetReturnedEntity(); + } + } + + return ( best ) ? best : RandomValidTarget( target ); + } + + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + + return ( func.ReturnsAVal()) ? gameLocal.FindEntity( spawnArgs.GetString( func.GetReturnKey() )) : const_cast( target ); +} + +/* +================ +rvVehicleDriver::SimulateButtons +================ +*/ +void rvVehicleDriver::SimulateButtons( usercmd_t& cmd ) { + cmd.buttons = (fireEndTime >= gameLocal.time) ? BUTTON_ATTACK : 0; +} + +/* +================ +rvVehicleDriver::SimulateMouseMove +================ +*/ +void rvVehicleDriver::SimulateMouseMove( usercmd_t& cmd ) { + idVec3 origin; + idMat3 axis; + + idVec3 vectorToTarget; + idAngles anglesToTarget; + idAngles turretAngles; + idAngles deltaAngles; + + if( !lookTarget ) { + for( int ix = 0; ix < 3; ++ix ) { + cmd.angles[ix] = 0; + } + return; + } + + vehicleController.GetEyePosition( origin, axis ); + vectorToTarget = (lookTarget->GetPhysics()->GetOrigin() - origin).ToNormal(); + anglesToTarget = vectorToTarget.ToAngles().Normalize360(); + turretAngles = (axis[0]).ToAngles().Normalize360(); + deltaAngles = (anglesToTarget - turretAngles).Normalize180(); + + for( int ix = 0; ix < deltaAngles.GetDimension(); ++ix ) { + cmd.angles[ix] += ANGLE2SHORT( deltaAngles[ix] ); + } + + // Debug Output + if( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugLine( colorGreen, origin, origin + anglesToTarget.ToForward() * 100.0f, 17, true ); + gameRenderWorld->DebugLine( colorYellow, origin, origin + turretAngles.ToForward() * 100.0f, 17, true ); + } +} + +/* +================ +rvVehicleDriver::SimulateKeys +================ +*/ +void rvVehicleDriver::SimulateKeys( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + if( !vehicle ) { + cmd.forwardmove = 0; + cmd.rightmove = 0; + return; + } + + cmd.forwardmove = static_cast< signed char >( (!vehicle->IsAutoCorrecting() ? 127.0f : forwardThrustScale) * dotForward * speed ); + cmd.rightmove = static_cast< signed char >( ( ( dotForward < 0.0f ) ? rightThrustScale : -rightThrustScale ) * dotRight ); +} + + +/* +================ +rvVehicleDriver::SortValid +================ +*/ +int rvVehicleDriver::SortValid( const void* a, const void* b ) { + idEntityPtr A = *(idEntityPtr*)a; + idEntityPtr B = *(idEntityPtr*)b; + + return rvVehicleDriver::IsValidTarget( B ) - rvVehicleDriver::IsValidTarget( A ); +} + +/* +================ +rvVehicleDriver::SortTargetList +================ +*/ +int rvVehicleDriver::SortTargetList( idEntity* ent ) const { + if( !ent ) { + return 0; + } + + int numValidEntities = 0; + idEntity * target; + + ent->RemoveNullTargets(); + qsort( ent->targets.Ptr(), NumTargets( ent ), ent->targets.TypeSize(), rvVehicleDriver::SortValid ); + + for( int ix = NumTargets( ent ) - 1; ix >= 0; --ix ) { + target = GetTarget( ent, ix ); + + if( IsValidTarget( target ) ) { + ++numValidEntities; + } + } + + return numValidEntities; +} + +/* +================ +rvVehicleDriver::RandomValidTarget +================ +*/ +idEntity* rvVehicleDriver::RandomValidTarget( idEntity* ent ) const { + int numValid = NumValidTargets( ent ); + return (!numValid) ? NULL : ent->targets[ rvRandom::irand(0, numValid - 1) ]; +} + +/* +================ +rvVehicleDriver::NumValidTargets +================ +*/ +int rvVehicleDriver::NumValidTargets( idEntity* ent ) const { + return SortTargetList(ent); +} + +/* +================ +rvVehicleDriver::NumTargets +================ +*/ +int rvVehicleDriver::NumTargets( const idEntity* ent ) const { + return (ent) ? ent->targets.Num() : 0; +} + +/* +================ +rvVehicleDriver::GetTarget +================ +*/ +idEntity* rvVehicleDriver::GetTarget( const idEntity* ent, int index ) const { + return (ent) ? ent->targets[index] : NULL; +} + +/* +================ +rvVehicleDriver::IsValidPathNode +================ +*/ +bool rvVehicleDriver::IsValidPathNode( const idEntity* ent ) { + if( !ent ) { + return false; + } + + return ent->IsType( idTarget::GetClassType() ); +} + +/* +================ +rvVehicleDriver::IsValidTarget +================ +*/ +bool rvVehicleDriver::IsValidTarget( const idEntity* ent ) { + return IsValidPathNode( ent ) || ent->IsType( rvVehicle::GetClassType() ) || ent->IsType( idPlayer::GetClassType() ); +} + +/* +================ +rvVehicleDriver::SetLeader +================ +*/ +bool rvVehicleDriver::SetLeader( idEntity* ent ) { + if ( !ent || !ent->IsType( rvVehicleDriver::GetClassType() ) ) { + return false; + } + + leader = static_cast( ent ); + minDistance = leader->spawnArgs.GetFloat( "min_distance", "500" ); + idStr decel_distance( minDistance * 3.0f ); + decelDistance = leader->spawnArgs.GetFloat( "decel_distance", decel_distance ); + + return true; +} + +/* +================ +rvVehicleDriver::Event_PostSpawn +================ +*/ +void rvVehicleDriver::Event_PostSpawn ( void ) { + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + Event_EnterVehicle( ent ); + } + } + + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( idTarget::GetClassType() ) ) { + Event_ScriptedMove( ent, 0, 0 ); + } + if ( ent->IsType( rvVehicleDriver::GetClassType() ) ) { + SetLeader( ent ); + } + } +} + +/* +================ +rvVehicleDriver::Event_EnterVehicle +================ +*/ +void rvVehicleDriver::Event_EnterVehicle ( idEntity * vehicle ) { + if ( vehicle ) { + forwardThrustScale = vehicle->spawnArgs.GetFloat( "driver_forward_thrust", ".75" ) * 127.0f; + rightThrustScale = vehicle->spawnArgs.GetFloat( "driver_right_thrust", "1" ) * 127.0f; + + EnterVehicle( vehicle ); + } +} + +/* +================ +rvVehicleDriver::Event_ExitVehicle +================ +*/ +void rvVehicleDriver::Event_ExitVehicle( bool force ) { + Event_ScriptedStop(); + + if( vehicleController.GetVehicle() && force && !ExitVehicle(force) ) { + vehicleController.GetVehicle()->RemoveDriver( vehicleController.GetPosition(), force ); + } + + pathTargetInfo.node = NULL; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; +} + +/* +================ +rvVehicleDriver::Event_ScriptedMove +================ +*/ +void rvVehicleDriver::Event_ScriptedMove( idEntity *target, float minDist, bool exitVehicle ) { + isMoving = false; + + if( !target ) { + return; + } + + if( IsValidTarget( target ) ) { + isMoving = true; + faceTarget = NULL; + pathTargetInfo.node = target; + pathTargetInfo.initialDistance = ( target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if ( !target->IsType( idPlayer::GetClassType() ) ) { + pathTargetInfo.minDistance = target->spawnArgs.GetFloat( "min_distance", va("%f", Max(200.0f, minDist)) ); + pathTargetInfo.throttle = idMath::ClampFloat( 0.0f, 1.0f, target->spawnArgs.GetFloat( "throttle", "1" ) ); + pathTargetInfo.exitVehicle = target->spawnArgs.GetBool( "exit_vehicle", exitVehicle ? "1" : "0" ); + + if( pathTargetInfo.exitVehicle ) { + pathTargetInfo.throttle = 0.0f; + } + } else { + pathTargetInfo.minDistance = minDist; + pathTargetInfo.throttle = 1.0f; + pathTargetInfo.exitVehicle = false; + } + + } else { + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + } +} + +/* +================ +rvVehicleDriver::Event_ScriptedDone +================ +*/ +void rvVehicleDriver::Event_ScriptedDone( void ) { + idThread::ReturnFloat( !IsMoving() ); +} + +/* +================ +rvVehicleDriver::Event_ScriptedStop +================ +*/ +void rvVehicleDriver::Event_ScriptedStop( void ) { + if( IsDriving() ) { + usercmd_t cmd = { 0 }; + + vehicleController.SetInput( cmd, ang_zero ); + + if( pathTargetInfo.node ) { + if( func.Init( pathTargetInfo.node->spawnArgs.GetString( "call_doneMoving" )) > 0 ) { + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + } + + pathTargetInfo.node->ActivateTargets(this); + pathTargetInfo.node = NULL; + } + + isMoving = false; + } +} + +/* +================ +rvVehicleDriver::Event_Trigger +================ +*/ +void rvVehicleDriver::Event_Trigger( idEntity *activator ) { + if( IsDriving() && pathTargetInfo.node ) { + isMoving = true; + } +} + +/* +================ +rvVehicleDriver::Event_SetSpeed +================ +*/ +void rvVehicleDriver::Event_SetSpeed( float speed ) { + currentThrottle = speed; +} + +//twhitaker: remove - begin +/* +================ +rvVehicleDriver::Event_SetFollowOffset +================ +*/ +void rvVehicleDriver::Event_SetFollowOffset( const idVec3 &offset ) { + gameLocal.Warning( "Script Event \"followOffset\" is deprecated, please remove it immediately to avoid errors." ); +} +//twhitaker: remove - end + +/* +================ +rvVehicleDriver::Event_FireWeapon +================ +*/ +void rvVehicleDriver::Event_FireWeapon( float weapon_index, float time ) { + if( IsDriving() ) { + fireEndTime = gameLocal.GetTime() + SEC2MS( time ); + vehicleController.SelectWeapon( weapon_index ); + } +} + +/* +================ +rvVehicleDriver::Event_FaceEntity +================ +*/ +void rvVehicleDriver::Event_FaceEntity( const idEntity* entity ) { + if( IsMoving() ) { + return; + } + + faceTarget = entity; + isMoving = true; +} + +/* +================ +rvVehicleDriver::Event_LookAt +================ +*/ +void rvVehicleDriver::Event_LookAt( const idEntity* entity ) { + lookTarget = entity; +} + +/* +================ +rvVehicleDriver::Event_SetLeader +================ +*/ +void rvVehicleDriver::Event_SetLeader( idEntity* newLeader ) { + SetLeader( newLeader ); +} + +/* +================ +rvVehicleDriver::UpdateAutoCorrection +================ +*/ +void rvVehicleDriver::UpdateAutoCorrection ( void ) { + if ( IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + // Disregard your autocorrection state if we're slowing down to avoid collision with the leader. + if ( vehicle->IsAutoCorrecting() ) { + if ( avoidingLeader == true ) { + vehicle->autoCorrectionBegin = 0; + } + } + } +} + +/* +================ +rvVehicleDriver::Save +================ +*/ +void rvVehicleDriver::Save ( idSaveGame *savefile ) const { + savefile->Write ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Write ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->WriteFloat ( currentThrottle ); + + faceTarget.Save ( savefile ); + lookTarget.Save ( savefile ); + + leader.Save ( savefile ); + savefile->WriteInt ( leaderFlags ); + savefile->WriteFloat ( decelDistance ); + savefile->WriteFloat ( minDistance ); + savefile->WriteBool ( avoidingLeader ); + + savefile->WriteFloat ( fireEndTime ); + + func.Save ( savefile ); + + savefile->WriteBool ( isMoving ); + + savefile->WriteInt ( (int&)pathingMode ); + pathingEntity.Save ( savefile ); + savefile->WriteVec3 ( pathingOrigin ); + + savefile->WriteFloat( forwardThrustScale ); // cnicholson: Added unsaved var + savefile->WriteFloat( rightThrustScale ); // cnicholson: Added unsaved var + +} + +/* +================ +rvVehicleDriver::Restore +================ +*/ +void rvVehicleDriver::Restore ( idRestoreGame *savefile ) { + savefile->Read ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Read ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->ReadFloat ( currentThrottle ); + + faceTarget.Restore ( savefile ); + lookTarget.Restore ( savefile ); + + leader.Restore ( savefile ); + savefile->ReadInt ( leaderFlags ); + savefile->ReadFloat ( decelDistance ); + savefile->ReadFloat ( minDistance ); + savefile->ReadBool ( avoidingLeader ); + + savefile->ReadFloat ( fireEndTime ); + + func.Restore ( savefile ); + + savefile->ReadBool ( isMoving ); + + savefile->ReadInt ( (int&)pathingMode ); + pathingEntity.Restore ( savefile ); + savefile->ReadVec3 ( pathingOrigin ); + + savefile->ReadFloat( forwardThrustScale ); // cnicholson: Added unrestored var + savefile->ReadFloat( rightThrustScale ); // cnicholson: Added unrestored var +} diff --git a/source/mpgame/vehicle/VehicleDriver.h b/source/mpgame/vehicle/VehicleDriver.h new file mode 100644 index 0000000..95258e6 --- /dev/null +++ b/source/mpgame/vehicle/VehicleDriver.h @@ -0,0 +1,185 @@ +//---------------------------------------------------------------- +// VehicleDriver.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +extern const idEventDef VD_ChoosePathTarget; + +class rvVehicleDriver : public idActor { + friend class rvVehicleAI; + friend class rvVehicleMonster; +public: + + CLASS_PROTOTYPE( rvVehicleDriver ); + + rvVehicleDriver ( void ); + + void Think ( void ); + void Spawn ( void ); + + static bool IsValidTarget ( const idEntity* ent ); // valid entity for looking or facing + static bool IsValidPathNode ( const idEntity* ent ); // valid entity for pathing + static bool IsValidLeader ( const idEntity* ent ); // valid entity to set as a leader + + static int SortValid ( const void* a, const void* b ); + + virtual void Present ( void ) { } + +protected: + void SimulateKeys ( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance = 0 ); + void SimulateButtons ( usercmd_t& cmd ); + void SimulateMouseMove ( usercmd_t& cmd ); + + bool GetTargetInfo ( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, + float* dotForward, float* dotRight, float* distance ) const; + idEntity* ChooseNextNode ( idEntity* target ); + + bool IsMoving ( void ) const { return isMoving && IsDriving(); } + bool IsDriving ( void ) const { return vehicleController.IsDriving(); } + + bool SetLeader ( idEntity* newLeader ); + const idEntity* GetLeader ( void ) const { return leader; } + void SetLeaderHint ( int flags ) { leaderFlags = flags; } + int GetLeaderHint ( void ) const { return leaderFlags; } + + int SortTargetList ( idEntity* ent ) const; + idEntity* RandomValidTarget ( idEntity* ent ) const; + int NumValidTargets ( idEntity* ent ) const; + int NumTargets ( const idEntity* ent ) const; + idEntity* GetTarget ( const idEntity* ent, int index ) const; + + void UpdateAutoCorrection ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum VD_PathingMode { + VDPM_Random = 0, + VDPM_MoveTo = 1, + VDPM_MoveAway = 2, + VDPM_Custom = 4, + }; + + + void SetPathingMode ( VD_PathingMode mode, const idVec3 & origin ); +// CustomPathingFunc SetPathingMode ( VD_PathingMode mode, CustomPathingFunc cb ); + idEntity * SetPathingMode ( VD_PathingMode mode, idEntity * ); + VD_PathingMode GetPathingMode ( void ) const; + const idVec3 & GetPathingOrigin ( void ) const; +// CustomPathingFunc GetPathingCustomCallback ( void ) const; + idEntity * GetPathingEntity ( void ) const; + +protected: + void Event_PostSpawn ( void ); + void Event_EnterVehicle ( idEntity * vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_ScriptedMove ( idEntity *target, float minDist, bool exitVehicle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_Trigger ( idEntity *activator ); + void Event_SetSpeed ( float speed ); + void Event_FireWeapon ( float weapon_index, float time ); + void Event_FaceEntity ( const idEntity* entity ); + void Event_LookAt ( const idEntity* entity ); + void Event_SetLeader ( idEntity* newLeader ); + +//twhitaker: remove - begin + void Event_SetFollowOffset ( const idVec3 &offset ); +//twhitaker: remove - end + + // Leader hints : a driver will send the following driving hints to it's leader + enum VD_LeaderHints { + VDLH_SlowDown = 1, // Set when distance is too great + VDLH_Wait = 2, // Set when there is no line of site + VDLH_Continue = 0, // Default following behavior + }; + + idStr & LeaderHintsString( int hints, idStr & out ) const { + switch ( hints ) { + case VDLH_SlowDown: out = "SlowDown"; break; + case VDLH_Wait: out = "Wait"; break; + default: out = "Continue"; break; + } + return out; + } + + // Stores information about a path target + struct PathTargetInfo { + idEntityPtr node; // the target entity + float initialDistance; // the distance to this entity when we first decide to move there + float minDistance; // the range that specifies when this entity has been reached + float throttle; // 0 -> 1 speed setting + bool exitVehicle; // set to true to exit a vehicle when we reach this target + }; + + struct PathNavigationData { + float dotForward; + float dotRight; + }; + + // standard movement + PathTargetInfo pathTargetInfo; + PathTargetInfo lastPathTargetInfo; + float currentThrottle; + + // facing + idEntityPtr faceTarget; + idEntityPtr lookTarget; + + // following + idEntityPtr leader; //TODO: twhitaker: see if I can make this an rvVehicle (so entities can follow player) + int leaderFlags; + float decelDistance; + float minDistance; + bool avoidingLeader; + + // firing + float fireEndTime; + + // event callbacks + rvScriptFuncUtility func; + + // state + bool isMoving; + + VD_PathingMode pathingMode; +// idEntity * (* pathingCallback)( idEntity * ); + idEntityPtr pathingEntity; + idVec3 pathingOrigin; + + float forwardThrustScale; + float rightThrustScale; +}; + + +ID_INLINE void rvVehicleDriver::SetPathingMode( VD_PathingMode mode, const idVec3 & origin ) { + if ( mode != VDPM_Custom ) { + pathingMode = mode; + pathingOrigin = origin; + } +} + +ID_INLINE idEntity * rvVehicleDriver::SetPathingMode( VD_PathingMode mode, idEntity * entity ) { + if ( mode != VDPM_Custom ) { + return NULL; + } + + pathingMode = mode; + idEntity * prev = pathingEntity; + pathingEntity = entity; + return prev; +} + +ID_INLINE rvVehicleDriver::VD_PathingMode rvVehicleDriver::GetPathingMode ( void ) const { + return pathingMode; +} + +ID_INLINE const idVec3 & rvVehicleDriver::GetPathingOrigin ( void ) const { + return pathingOrigin; +} + +ID_INLINE idEntity * rvVehicleDriver::GetPathingEntity ( void ) const { + return pathingEntity; +} + diff --git a/source/mpgame/vehicle/VehicleMonster.cpp b/source/mpgame/vehicle/VehicleMonster.cpp new file mode 100644 index 0000000..01a97f5 --- /dev/null +++ b/source/mpgame/vehicle/VehicleMonster.cpp @@ -0,0 +1,188 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleMonster.h" +#include "../ai/VehicleAI.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleMonster ) +END_CLASS + +rvVehicleMonster::rvVehicleMonster ( void ) { +} + +rvVehicleMonster::~rvVehicleMonster ( void ) { +} + +/* +================ +rvVehicleMonster::Spawn +================ +*/ +void rvVehicleMonster::Spawn( void ) { + idDict dict; + int count = 0; + const idKeyValue * val = NULL; + + for (;;) { + val = spawnArgs.MatchPrefix( "target", val ); + + if ( !val ) + break; + + dict.Set( "target" + (( count ) ? idStr( count ) : idStr( "" ) ), val->GetValue() ); + count++; + } + + dict.Set( "bind", name ); + dict.SetBool( "hide", true ); + + driver = static_cast( gameLocal.SpawnEntityDef( "ai_vehicle_driver", &dict ) ); + + if ( driver ) { + driver->SetVehicle( this ); + driver->GetPhysics()->SetOrigin( vec3_zero ); + } else { + gameLocal.Warning( "Unable to find \"entityDef ai_vehicle_driver\"." ); + } +} + +/* +================ +rvVehicleMonster::SetClipModel +================ +*/ +void rvVehicleMonster::SetClipModel ( idPhysics & physicsObj ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleMonster '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + +/* +================ +rvVehicleMonster::Save +================ +*/ +void rvVehicleMonster::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); +} + +/* +================ +rvVehicleMonster::Restore +================ +*/ +void rvVehicleMonster::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); +} + +/* +================ +rvVehicleMonster::Think +================ +*/ +void rvVehicleMonster::Think ( void ) { + if ( !driver ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + stateThread.Execute(); + rvVehicle::Think(); +} + +/* +================ +rvVehicleMonster::GetTargetOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetTargetOrigin ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToTarget +================ +*/ +idVec3 rvVehicleMonster::GetVectorToTarget ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin() - GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetEnemyOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetEnemyOrigin ( void ) { + if ( driver && driver->enemy.ent ) { + return driver->enemy.ent->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToEnemy +================ +*/ +idVec3 rvVehicleMonster::GetVectorToEnemy ( void ) { + if ( driver && driver->enemy.ent ) { + //HACK: this is hideous, I'm ashamed. + return ( driver->enemy.ent->GetPhysics()->GetOrigin() + idVec3( 0, 0, 36 ) )- GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::LookAtEntity +================ +*/ +void rvVehicleMonster::LookAtEntity ( idEntity *ent, float duration ) { + const idVec3 entOrigin = ent->GetPhysics()->GetOrigin(); + const idVec3 origin = GetOrigin(); + idVec3 toEnt = entOrigin - origin; + toEnt.Normalize(); + GetPhysics()->SetAxis( toEnt.ToMat3() ); +} + +/* +================ +rvVehicleMonster::SkipImpulse +================ +*/ +bool rvVehicleMonster::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/source/mpgame/vehicle/VehicleMonster.h b/source/mpgame/vehicle/VehicleMonster.h new file mode 100644 index 0000000..f8e1e7c --- /dev/null +++ b/source/mpgame/vehicle/VehicleMonster.h @@ -0,0 +1,45 @@ +//---------------------------------------------------------------- +// VehicleMonster.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEMONSTER_H__ +#define __GAME_VEHICLEMONSTER_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleAI; + +class rvVehicleMonster : public rvVehicle { + friend class rvVehicleAI; +public: + + CLASS_PROTOTYPE( rvVehicleMonster ); + + rvVehicleMonster ( void ); + ~rvVehicleMonster ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( idPhysics & physicsObj ); + + const idVec3 & GetTargetOrigin ( void ); + idVec3 GetVectorToTarget ( void ); + const idVec3 & GetEnemyOrigin ( void ); + idVec3 GetVectorToEnemy ( void ); + void LookAtEntity ( idEntity *ent, float duration ); + + idEntityPtr driver; +}; + +#endif // __GAME_VEHICLEMONSTER_H__ diff --git a/source/mpgame/vehicle/VehicleParts.cpp b/source/mpgame/vehicle/VehicleParts.cpp new file mode 100644 index 0000000..18a5392 --- /dev/null +++ b/source/mpgame/vehicle/VehicleParts.cpp @@ -0,0 +1,2386 @@ +//---------------------------------------------------------------- +// VehicleParts.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "../Effect.h" +#include "Vehicle.h" +#include "VehicleParts.h" +#include "../ai/AI_Manager.h" + +/*********************************************************************** + + rvVehiclePart + +***********************************************************************/ + +ABSTRACT_DECLARATION( idClass, rvVehiclePart ) +END_CLASS + +/* +===================== +rvVehiclePart::Init +===================== +*/ +bool rvVehiclePart::Init ( rvVehiclePosition* _position, const idDict& args, s_channelType _soundChannel ) { + spawnArgs.Copy ( args ); + + soundChannel = _soundChannel; + position = _position; + parent = position->GetParent(); + fl.active = false; + fl.useCenterMass = false; + + return true; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehiclePart::Spawn ( void ) { + // Get joint for orienting the part + joint = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint", "" ) ); + + // More position information + fl.useCenterMass = spawnArgs.GetBool ( "useCenterMass", "0" ); + spawnArgs.GetVector ( "offset", "0 0 0", localOffset ); + + fl.active = false; + + fl.useViewAxis = spawnArgs.GetBool( "useViewAxis", "0" ); + + // Initialize the origin so we can determine which side of the vehicle we are on + UpdateOrigin ( ); + + // Determine if this part is on the left and/or front side of the vehicle + idVec3 localOrigin; + localOrigin = (worldOrigin - parent->GetPhysics()->GetCenterMass()); + fl.front = (localOrigin * parent->GetPhysics()->GetAxis()[0] < 0.0f); + fl.left = (localOrigin * parent->GetPhysics()->GetAxis()[1] < 0.0f); +} + +/* +================ +rvVehiclePart::UpdateOrigin +================ +*/ +void rvVehiclePart::UpdateOrigin ( void ) { + if ( joint != INVALID_JOINT ) { + parent->GetJointWorldTransform ( joint, gameLocal.time, worldOrigin, worldAxis ); + } else { + if ( fl.useViewAxis ) { + worldAxis = parent->viewAxis; + } else { + worldAxis = parent->GetPhysics()->GetAxis(); + } + worldOrigin = fl.useCenterMass ? parent->GetPhysics()->GetCenterMass() : parent->GetPhysics()->GetOrigin(); + } + + worldOrigin += (localOffset * worldAxis); + + // Include this part in the total bounds + // FIXME: bounds are local + parent->AddToBounds ( worldOrigin ); +} + +/* +================ +rvVehiclePart::Save +================ +*/ +void rvVehiclePart::Save ( idSaveGame* savefile ) const { + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteDict ( &spawnArgs ); + parent.Save ( savefile ); + savefile->WriteJoint ( joint ); + savefile->WriteInt ( soundChannel ); + savefile->WriteInt ( parent->GetPositionIndex ( position ) ); + + savefile->WriteVec3 ( worldOrigin ); + savefile->WriteMat3 ( worldAxis ); + savefile->WriteVec3 ( localOffset ); +} + +/* +================ +rvVehiclePart::Restore +================ +*/ +void rvVehiclePart::Restore ( idRestoreGame* savefile ) { + int temp; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadDict ( &spawnArgs ); + parent.Restore ( savefile ); + savefile->ReadJoint ( joint ); + savefile->ReadInt ( soundChannel ); + + savefile->ReadInt ( temp ); + position = parent->GetPosition ( temp ); + + savefile->ReadVec3 ( worldOrigin ); + savefile->ReadMat3 ( worldAxis ); + savefile->ReadVec3 ( localOffset ); +} + +/*********************************************************************** + + rvVehicleSound + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleSound ) +END_CLASS + +/* +===================== +rvVehicleSound::rvVehicleSound +===================== +*/ +rvVehicleSound::rvVehicleSound ( void ) { + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + fade = false; + autoActivate = true; +} + +/* +===================== +rvVehicleSound::~rvVehicleSound +===================== +*/ +rvVehicleSound::~rvVehicleSound ( void ) { + Stop(); + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehicleSound::Spawn ( void ) { + soundName = spawnArgs.GetString ( "snd_loop" ); + + spawnArgs.GetVec2 ( "freqShift", "1 1", freqShift ); + spawnArgs.GetVec2 ( "volume", "0 0", volume ); + + // Temp fix for volume + volume[0] = idMath::dBToScale( volume[0] ); + volume[1] = idMath::dBToScale( volume[1] ); + + declManager->FindSound ( soundName )->GetParms ( &refSound.parms ); +} + +/* +===================== +rvVehicleSound::RunPostPhysics +===================== +*/ +void rvVehicleSound::RunPostPhysics ( void ) { + Update ( ); +} + +/* +===================== +rvVehicleSound::Activate +===================== +*/ +void rvVehicleSound::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( autoActivate ) { + if ( active ) { + Play ( ); + } else { + Stop ( ); + } + } +} + +/* +===================== +rvVehicleSound::Play +===================== +*/ +void rvVehicleSound::Play ( void ) { + if ( !soundName.Length ( ) ) { + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + Attenuate ( 0.0f, 0.0f ); + + Update ( true ); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound ( declManager->FindSound( soundName ), soundChannel, 0, 0 ); + } +} + +/* +===================== +rvVehicleSound::Stop +===================== +*/ +void rvVehicleSound::Stop ( void ) { + if ( IsPlaying ( ) ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->StopSound( soundChannel ); + } + } +} + +/* +===================== +rvVehicleSound::Update +===================== +*/ +void rvVehicleSound::Update ( bool force ) { + if ( !force && !IsPlaying ( ) ) { + return; + } + + if ( fade && currentVolume.IsDone ( gameLocal.time ) ) { + Stop ( ); + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return; + } + + UpdateOrigin ( ); + + refSound.parms.volume = currentVolume.GetCurrentValue ( gameLocal.time ); + refSound.parms.frequencyShift = currentFreqShift.GetCurrentValue ( gameLocal.time ); + emitter->ModifySound ( soundChannel, &refSound.parms ); + + refSound.origin = worldOrigin; + // bdube: please put something sensible here if you want doppler from the sound system + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, parent->entityNumber + 1, &refSound.parms ); +} + +/* +===================== +rvVehicleSound::Attenuate +===================== +*/ +void rvVehicleSound::Attenuate ( float volumeAttenuate, float freqAttenuate ) { + float f; + + fade = false; + + f = volume[0] + (volume[1]-volume[0]) * volumeAttenuate; + currentVolume.Init ( gameLocal.time, 100, currentVolume.GetCurrentValue(gameLocal.time), f ); + + f = freqShift[0] + (freqShift[1]-freqShift[0]) * freqAttenuate; + currentFreqShift.Init ( gameLocal.time, 100, currentFreqShift.GetCurrentValue(gameLocal.time), f ); +} + +/* +===================== +rvVehicleSound::Fade +===================== +*/ +void rvVehicleSound::Fade ( int duration, float toVolume, float toFreq ) { + currentVolume.Init ( gameLocal.time, duration, currentVolume.GetCurrentValue(gameLocal.time), toVolume ); + currentFreqShift.Init ( gameLocal.time, duration, currentFreqShift.GetCurrentValue(gameLocal.time), toFreq ); +} + +/* +===================== +rvVehicleSound::Save +===================== +*/ +void rvVehicleSound::Save ( idSaveGame* savefile ) const { + savefile->WriteVec2 ( volume ); + savefile->WriteVec2 ( freqShift ); + savefile->WriteString ( soundName ); + savefile->WriteRefSound ( refSound ); + + savefile->WriteInterpolate ( currentVolume ); + savefile->WriteInterpolate ( currentFreqShift ); + + savefile->WriteBool ( fade ); + savefile->WriteBool ( autoActivate ); +} + +/* +===================== +rvVehicleSound::Restore +===================== +*/ +void rvVehicleSound::Restore ( idRestoreGame* savefile ) { + savefile->ReadVec2 ( volume ); + savefile->ReadVec2 ( freqShift ); + savefile->ReadString ( soundName ); + savefile->ReadRefSound ( refSound ); + + savefile->ReadInterpolate ( currentVolume ); + savefile->ReadInterpolate ( currentFreqShift ); + + savefile->ReadBool ( fade ); + savefile->ReadBool ( autoActivate ); +} + +//---------------------------------------------------------------- +// +// rvVehicleLight +// +//---------------------------------------------------------------- + +CLASS_DECLARATION( rvVehiclePart, rvVehicleLight ) +END_CLASS + +/* +===================== +rvVehicleLight::rvVehicleLight +===================== +*/ +rvVehicleLight::rvVehicleLight ( void ) { + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::~rvVehicleLight +===================== +*/ +rvVehicleLight::~rvVehicleLight ( void ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +/* +===================== +rvVehicleLight::Spawn +===================== +*/ +void rvVehicleLight::Spawn ( void ) { + idVec3 color; + const char* temp; + + memset ( &renderLight, 0, sizeof(renderLight) ); + + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "pointlight", "1" ); + + spawnArgs.GetVector( "color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "target" ); + renderLight.up = spawnArgs.GetVector( "up" ); + renderLight.right = spawnArgs.GetVector( "right" ); + renderLight.end = spawnArgs.GetVector( "target" );; + } + + // Hide flare surface if there is one + temp = spawnArgs.GetString ( "flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + + // Sounds shader when turning light + spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + spawnArgs.GetString ( "snd_off", "", soundOff); + + lightOn = spawnArgs.GetBool( "start_on", "1" ); + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::RunPostPhysics +===================== +*/ +void rvVehicleLight::RunPostPhysics ( void ) { + if ( lightHandle == -1 || !lightOn ) { + return; + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::UpdateLightDef +===================== +*/ +void rvVehicleLight::UpdateLightDef ( void ) { + UpdateOrigin ( ); + + renderLight.origin = worldOrigin; + renderLight.axis = worldAxis; + + if ( lightHandle == -1 ) { + return; + } + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); +} + +/* +===================== +rvVehicleLight::Activate +===================== +*/ +void rvVehicleLight::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active && lightOn ) { + TurnOn ( ); + } else { + TurnOff ( ); + } +} + +/* +===================== +rvVehicleLight::TurnOff +===================== +*/ +void rvVehicleLight::TurnOff ( void ) { + const char* surface; + + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + + // Play off sound + if ( soundOff.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOff ), soundChannel, 0, false, NULL ); + } + } + + // Hide flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_HideSurface, surface ); + } +} + +/* +===================== +rvVehicleLight::TurnOn +===================== +*/ +void rvVehicleLight::TurnOn ( void ) { + const char* surface; + + if ( lightHandle == -1 ) { + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + // Play off sound + if ( soundOn.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOn ), soundChannel, 0, false, NULL ); + } + + // Show flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_ShowSurface, surface ); + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::Impulse +===================== +*/ +void rvVehicleLight::Impulse ( int impulse ) { + switch ( impulse ) { + case IMPULSE_50: + if ( lightOn ) { + lightOn = false; + TurnOff ( ); + } else { + lightOn = true; + TurnOn ( ); + } + break; + } +} + +/* +===================== +rvVehicleLight::Save +===================== +*/ +void rvVehicleLight::Save ( idSaveGame* savefile ) const { + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + savefile->WriteBool ( lightOn ); + savefile->WriteString ( soundOn ); + savefile->WriteString ( soundOff ); +} + +/* +===================== +rvVehicleLight::Restore +===================== +*/ +void rvVehicleLight::Restore ( idRestoreGame* savefile ) { + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadBool ( lightOn ); + savefile->ReadString ( soundOn ); + savefile->ReadString ( soundOff ); +} + +/*********************************************************************** + + rvVehicleWeapon + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleWeapon ) +END_CLASS + +#define WEAPON_DELAY_FIRE 500 + +/* +===================== +rvVehicleWeapon::rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::rvVehicleWeapon ( void ) { +#ifdef _XENON + bestEnemy = 0; +#endif + nextFireTime = 0; + fireDelay = 0; + hitScanDef = NULL; + projectileDef = NULL; + animNum = 0; +} + +/* +===================== +rvVehicleWeapon::~rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::~rvVehicleWeapon ( void ) { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + StopTargetEffect( ); +} + +/* +===================== +rvVehicleWeapon::Spawn +===================== +*/ +void rvVehicleWeapon::Spawn ( void ) { + int i; + idStr temp; + idVec3 color; + +#ifdef _XENON + bestEnemy = 0; +#endif + + launchFromJoint = spawnArgs.GetBool ( "launchFromJoint", "0" ); + lockScanning = spawnArgs.GetBool ( "lockScanning", "0" ); + lastLockTime = 0; + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + fireDelay = SEC2MS ( spawnArgs.GetFloat ( "firedelay" ) ); + spread = spawnArgs.GetFloat ( "spread" ); + jointIndex = 0; + count = spawnArgs.GetInt ( "count", "1" ); + lockRange = spawnArgs.GetFloat ( "lockrange", "0" ); + ammoPerCharge = spawnArgs.GetInt ( "ammopercharge", "-1" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargetime" ) ); + currentAmmo = ammoPerCharge; + muzzleFlashHandle = -1; + + if( spawnArgs.GetString("anim", "", temp) && *temp ) { + animNum = parent->GetAnimator()->GetAnim( temp ); + animChannel = spawnArgs.GetInt( "animChannel" ); + } + + spawnArgs.GetVector ( "force", "0 0 0", force ); + + // Vehicle guns can have multiple joints to fire from. They will be cycled through + // when firing. + joints.Append ( joint ); + for ( i = 2; ; i ++ ) { + jointHandle_t joint2; + joint2 = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint%d", i ) ) ); + if ( joint2 == INVALID_JOINT ) { + break; + } + + joints.Append ( joint2 ); + } + + // Muzzle Flash + memset ( &muzzleFlash, 0, sizeof(muzzleFlash) ); + + spawnArgs.GetVector ( "flashOffset", "0 0 0", muzzleFlashOffset ); + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_flashShader", "lights/muzzleflash" ), false ); + muzzleFlash.pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + muzzleFlash.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + spawnArgs.GetVector( "flashColor", "0 0 0", color ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = color[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = color[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = color[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + muzzleFlash.lightRadius[0] = muzzleFlash.lightRadius[1] = muzzleFlash.lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); + + if ( !muzzleFlash.pointLight ) { + muzzleFlash.target = spawnArgs.GetVector( "flashTarget" ); + muzzleFlash.up = spawnArgs.GetVector( "flashUp" ); + muzzleFlash.right = spawnArgs.GetVector( "flashRight" ); + muzzleFlash.end = spawnArgs.GetVector( "flashTarget" ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // get the brass def + idStr name; + brassDict = NULL; + if ( spawnArgs.GetString( "def_ejectBrass", "", name ) && *name ) { + brassDict = gameLocal.FindEntityDefDict( name, false ); + + if ( !brassDict ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon on vehicle '%s'", name, position->mParent->name ); + } + } + + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); +// wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); +} + +/* +===================== +rvVehicleWeapon::Activate +===================== +*/ +void rvVehicleWeapon::Activate ( bool activate ) { + rvVehiclePart::Activate ( activate ); + + nextFireTime = gameLocal.time + WEAPON_DELAY_FIRE; + +#ifdef _XENON + bestEnemy = 0; +#endif +} + +/* +===================== +rvVehicleWeapon::StopTargetEffect +===================== +*/ +void rvVehicleWeapon::StopTargetEffect ( void ) { + if ( targetEffect ) { + targetEffect->Stop( ); + targetEffect = NULL; + } +} + +/* +===================== +rvVehicleWeapon::UpdateLock +===================== +*/ +void rvVehicleWeapon::UpdateLock ( void ) { + +#ifdef _XENON + + bestEnemy = 0; + + // Handle auto-aim if it's enabled + if ( cvarSystem->GetCVarBool("pm_AimAssist") ) { + + const float testDist = 2000.0f; // huge because we're outdoors + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + + idVec3 start = position->GetEyeOrigin(); + idVec3 end = start + (position->GetEyeAxis().ToAngles().ToForward() * testDist); + + idBounds bounds( start ); + bounds.AddPoint( end ); + + idClipModel *clipModelList[ MAX_GENTITIES ]; + int numClipModels = gameLocal.ClipModelsTouchingBounds( player, bounds, -1, clipModelList, MAX_GENTITIES ); + + float bDist = testDist; + + float fovX, fovY; + gameLocal.CalcFov( player->CalcFov( true ), fovX, fovY ); + float dNear = cvarSystem->GetCVarFloat( "r_znear" ); + float dFar = testDist; + float size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * float(cvarSystem->GetCVarInteger("pm_AimAssistFOV")) / 100.0f; + + idFrustum aimArea; + aimArea.SetOrigin( start ); + aimArea.SetAxis( position->GetEyeAxis() ); + aimArea.SetSize( dNear, dFar, size, size ); + + for ( int i = 0; i < numClipModels; i++ ) { + + idClipModel *clip = clipModelList[ i ]; + idEntity *ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + if ( static_cast( ent )->team == player->team ) { + continue; + } + } else { + continue; + } + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + + const idBounds &b = ent->GetPhysics()->GetAbsBounds(); + bool inside = aimArea.IntersectsBounds(b); + + if ( inside ) { + float dist = b.ShortestDistance(start); + if ( bDist > dist ) { + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } +#endif + + if ( !position->GetDriver() || parent->health <= 0 || !lockScanning) { + StopTargetEffect( ); + } else if ( lockScanning && position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //always update locking info + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + // Update the guide effect + if ( targetEnt && targetEnt.IsValid() && targetEnt->health > 0 && targetEnt->IsType( idActor::GetClassType() ) ) { + idVec3 eyePos; + if ( targetJoint != INVALID_JOINT ) { + idMat3 jointAxis; + targetEnt->GetAnimator()->GetJointTransform( targetJoint, gameLocal.GetTime(), eyePos, jointAxis ); + eyePos = targetEnt->GetRenderEntity()->origin + (eyePos*targetEnt->GetRenderEntity()->axis); + if ( !targetPos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * targetEnt->GetRenderEntity()->axis; + eyePos += jointAxis[0]*targetPos[0]; + eyePos += jointAxis[1]*targetPos[1]; + eyePos += jointAxis[2]*targetPos[2]; + } + } else { + eyePos = static_cast(targetEnt.GetEntity())->GetEyePosition(); + eyePos += targetEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + eyePos *= 0.5f; + } + if ( targetEffect ) { + targetEffect->SetOrigin ( eyePos ); + targetEffect->SetAxis ( player->firstPersonViewAxis.Transpose() ); + } else { + targetEffect = gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_guide" ), eyePos, player->firstPersonViewAxis.Transpose(), true, vec3_origin, false ); + if ( targetEffect ) { + targetEffect->GetRenderEffect()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + targetEffect->GetRenderEffect()->allowSurfaceInViewID = position->GetDriver()->entityNumber + 1; + } + } + } else { + StopTargetEffect( ); + } + } + } +} + +/* +===================== +rvVehicleWeapon::RunPostPhysics +===================== +*/ +void rvVehicleWeapon::RunPostPhysics ( void ) { + if ( !IsActive() || !parent->IsShootingEnabled ( ) ) { + return; + } + + if ( currentAmmo <= 0 && currentCharge.IsDone( gameLocal.GetTime() ) ) { + currentAmmo = ammoPerCharge; + } + + UpdateLock(); + + if ( !(position->mInputCmd.buttons & BUTTON_ATTACK ) ) { + return; + } + + if ( gameLocal.time <= nextFireTime || !currentCharge.IsDone(gameLocal.GetTime()) ) { + return; + } + + // Gun animation? + if ( animNum ) { + parent->GetAnimator()->PlayAnim ( animChannel, animNum, gameLocal.time, 0 ); + } + + if( !spawnArgs.GetBool("launchOnFrameCommand") ) { + if (!Fire()) + { + return; + } + } + + nextFireTime = gameLocal.time + fireDelay; +} + +/* +================ +rvVehicleWeapon::WeaponFeedback +================ +*/ +void rvVehicleWeapon::WeaponFeedback( const idDict* dict ) { + if( !dict || !GetPosition() || !GetPosition()->IsOccupied() ) { + return; + } + + idActor* actor = GetPosition()->GetDriver(); + if( !actor || !actor->IsType( idPlayer::GetClassType() ) ) { + return; + } + + //abahr: This feels like a hack. I hate using def files for logic but it just seems the easiest way to do it + idPlayer* player = static_cast( actor ); + if( dict->GetInt("recoilTime") ) { + player->playerView.WeaponFireFeedback( dict ); + } + if( dict->GetInt("shakeTime") ) { + player->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + dict->GetInt("shakeTime")), dict->GetFloat("shakeMagnitude") ); + } + EjectBrass(); +} + +/* +================ +rvVehicleWeapon::MuzzleFlashLight +================ +*/ +void rvVehicleWeapon::MuzzleFlashLight( const idVec3& origin, const idMat3& axis ) { + if ( !muzzleFlash.lightRadius[0] ) { + return; + } + + muzzleFlash.origin = origin; + muzzleFlash.axis = axis; + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = parent->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +===================== +rvVehicleWeapon::UpdateCursorGUI +===================== +*/ +void rvVehicleWeapon::UpdateCursorGUI ( idUserInterface* gui ) const { + // cnicholson: I do't see a reason why this if statement is here. Its ALWAYS false and so this block never executed, + // thus if the player was in a vehicle, the crosshair was always the player's last held weapon, not the crosshair defined in the vehicle .def. + // So I commented the if part out. + //if ( spawnArgs.GetBool( "hide_crosshair", "0" ) ) { + gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString ( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + gui->SetStateString ( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt ( "crossOffsetX", spawnArgs.GetInt ( "crosshairOffsetX", "0" ) ); + gui->SetStateInt ( "crossOffsetY", spawnArgs.GetInt ( "crosshairOffsetY", "0" ) ); + gui->StateChanged ( gameLocal.time ); + //} +} + +/* +===================== +rvVehicleWeapon::Save +===================== +*/ +void rvVehicleWeapon::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteInt ( nextFireTime ); + savefile->WriteInt ( fireDelay ); + savefile->WriteInt ( count ); + // TOSAVE: const idDict* projectileDef; + // TOSVAE: const idDict* hitScanDef; + + savefile->WriteFloat ( spread ); + savefile->WriteBool ( launchFromJoint ); + savefile->WriteBool ( lockScanning ); + savefile->WriteInt ( lastLockTime ); + savefile->WriteFloat ( lockRange ); + + savefile->WriteInt ( joints.Num() ); + for ( i = 0; i < joints.Num(); i ++ ) { + savefile->WriteJoint ( joints[i] ); + } + savefile->WriteInt ( jointIndex ); + + savefile->WriteVec3 ( force ); + + savefile->WriteInt ( ammoPerCharge ); + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( currentAmmo ); + savefile->WriteInterpolate ( currentCharge ); + + savefile->WriteRenderLight ( muzzleFlash ); + savefile->WriteInt ( muzzleFlashHandle ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashOffset ); + + savefile->WriteInt ( animNum ); + savefile->WriteInt ( animChannel ); + + targetEnt.Save( savefile ); + savefile->WriteJoint( targetJoint ); + savefile->WriteVec3( targetPos ); + targetEffect.Save( savefile ); + // Don't save shaderFire or shaderReload, they are setup in Restore + + savefile->WriteInt( zoomFov ); + savefile->WriteUserInterface( zoomGui, true ); + savefile->WriteFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +void rvVehicleWeapon::Restore ( idRestoreGame* savefile ) { + int i; + int num; + idStr temp; + +#ifdef _XENON + bestEnemy = 0; +#endif + + savefile->ReadInt ( nextFireTime ); + savefile->ReadInt ( fireDelay ); + savefile->ReadInt ( count ); + savefile->ReadFloat ( spread ); + savefile->ReadBool ( launchFromJoint ); + savefile->ReadBool ( lockScanning ); + savefile->ReadInt ( lastLockTime ); + savefile->ReadFloat ( lockRange ); + + savefile->ReadInt ( num ); + joints.Clear ( ); + joints.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadJoint ( joints[i] ); + } + savefile->ReadInt ( jointIndex ); + + savefile->ReadVec3 ( force ); + + savefile->ReadInt ( ammoPerCharge ); + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( currentAmmo ); + savefile->ReadInterpolate( currentCharge ); + + savefile->ReadRenderLight ( muzzleFlash ); + savefile->ReadInt ( muzzleFlashHandle ); + if ( muzzleFlashHandle != -1 ) { + //get the handle again as it's out of date after a restore! + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashOffset ); + + savefile->ReadInt ( animNum ); + savefile->ReadInt ( animChannel ); + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // Brass Def + brassDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_ejectBrass" ), false ); + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt.Restore( savefile ); + savefile->ReadJoint( targetJoint ); + savefile->ReadVec3( targetPos ); + targetEffect.Restore( savefile ); + + savefile->ReadInt( zoomFov ); + savefile->ReadUserInterface( zoomGui, &spawnArgs ); + savefile->ReadFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +bool rvVehicleWeapon::Fire() { + if ( muzzleFlashHandle != -1 && gameLocal.time >= muzzleFlashEnd ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + +// twhitaker: moved to rvVehicleWeapon::RunPostPhysics so that the HUD would update without having to fire the gun +// if ( currentAmmo == 0 ) { +// currentAmmo = ammoPerCharge; +// } + + LaunchProjectiles(); + + if ( currentAmmo == -1 ) { + currentCharge.Init ( gameLocal.time, fireDelay, 0.0, 1.0f ); + } else { + currentAmmo--; + if ( currentAmmo <= 0 ) { + currentAmmo = 0; + if ( chargeTime ) { + parent->StartSoundShader( shaderReload, SND_CHANNEL_ANY, 0, false, NULL ); + currentCharge.Init ( gameLocal.time, chargeTime, 0.0f, 1.0f ); + } + } + } + return true; +} + +#ifdef _XENON +/* +===================== +rvVehicleWeapon::AutoAim +===================== +*/ +void rvVehicleWeapon::AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ) { + + if ( player ) { + + // If we have a enemy selected, handle aim correction + if ( bestEnemy ) { + + idVec3 dif = bestEnemy->GetRenderEntity()->origin - origin; + idVec3 muzzleDest = origin + (dir * dif.Length() ); + + // Transform start and end into the enemy's body space + idVec3 localStart = bestEnemy->GetRenderEntity()->axis / (origin - bestEnemy->GetRenderEntity()->origin); + idVec3 localEnd = bestEnemy->GetRenderEntity()->axis / (muzzleDest - bestEnemy->GetRenderEntity()->origin); + + if ( bestEnemy->GetAnimator() ) { + int numJoints = bestEnemy->GetAnimator()->NumJoints(); + + if ( numJoints ) { + + jointHandle_t nearJoint = bestEnemy->GetAnimator()->GetNearestJoint( localStart, localEnd, gameLocal.time, 0, numJoints ); + + // If we found a valid joint to snap to... + if ( nearJoint > 0 ) { + idMat3 dummy; + bestEnemy->GetJointWorldTransform( nearJoint, gameLocal.time, muzzleDest, dummy ); + dir = muzzleDest - origin; + dir.Normalize(); + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + } + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + + } + } +} +#endif + +/* +===================== +rvVehicleWeapon::LaunchHitScan +===================== +*/ +void rvVehicleWeapon::LaunchHitScan( const idVec3& origin, const idVec3& _dir, const idVec3& jointOrigin ) { + idPlayer* player = 0; + + idVec3 dir = _dir; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + gameLocal.HitScan ( *hitScanDef, origin, dir, jointOrigin, position->GetDriver(), false, 1.0f, parent ); +} + +/* +===================== +rvVehicleWeapon::LaunchProjectile +===================== +*/ +void rvVehicleWeapon::LaunchProjectile( const idVec3& origin, const idVec3& _dir, const idVec3& pushVelocity ) { + + idPlayer *player = 0; + idEntity* ent; + idProjectile* projectile; + + idVec3 dir = _dir; + + gameLocal.SpawnEntityDef ( *projectileDef, &ent ); + if ( !ent ) { + return; + } + + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + + projectile = ( idProjectile * )ent; + projectile->Create( position->GetDriver(), origin, dir, parent ); + projectile->Launch( origin, dir, pushVelocity, 0.0f, 1.0f ); + + if ( projectile->IsType ( idGuidedProjectile::GetClassType() ) ) { + if ( spawnArgs.GetBool("guideTowardsDir") && (!targetEnt || targetJoint == INVALID_JOINT) ) { +#ifndef _XENON + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); +#else + if ( bestEnemy ) { + static_cast(projectile)->GuideTo ( origin, dir ); + } else { + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); + } +#endif + + } else if ( targetEnt ) { + static_cast(projectile)->GuideTo ( targetEnt, targetJoint, targetPos ); + } else { + static_cast(projectile)->GuideTo ( targetPos ); + } + } +} + +/* +===================== +rvVehicleWeapon::LaunchProjectiles +===================== +*/ +void rvVehicleWeapon::LaunchProjectiles() { + idVec3 jointOrigin; + idMat3 jointAxis; + idVec3 origin; + idMat3 axis; + + float spreadRad = DEG2RAD( spread ); + idVec3 dir; + float ang = 0.0f; + float spin = 0.0f; + + parent->GetJointWorldTransform ( joints[jointIndex], gameLocal.time, jointOrigin, jointAxis ); + MuzzleFlashLight ( jointOrigin + muzzleFlashOffset * jointAxis, jointAxis ); + + if( launchFromJoint ) { + origin = jointOrigin; + axis = jointAxis; + } else { + origin = position->GetEyeOrigin(); + axis = position->GetEyeAxis(); + } + + if ( !lockScanning || !targetEnt || !position->GetDriver() || !position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //don't do this continuously, so have to do it here + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + } + + parent->StartSoundShader( shaderFire, SND_CHANNEL_WEAPON, 0, false, NULL ); + + parent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_muzzleflash" ), joints[jointIndex], vec3_origin, mat3_identity ); + + gameLocal.AlertAI( parent ); + + for( int i = 0; i < count; i ++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[0] + axis[ 2 ] * ( ang * idMath::Sin( spin ) ) - axis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine ( colorBlue, origin, origin + dir * 10000.0f, 10000 ); + } + + if ( hitScanDef ) { + LaunchHitScan( origin, dir, jointOrigin ); + } else { + LaunchProjectile( origin, dir, parent->GetPhysics()->GetPushedLinearVelocity() ); + } + } + + jointIndex = (jointIndex+1) % joints.Num(); + + parent->GetPhysics()->ApplyImpulse ( 0, origin, force * axis ); + + WeaponFeedback( &spawnArgs ); +} + +/* +===================== +rvVehicleWeapon::GetLockInfo +===================== +*/ +void rvVehicleWeapon::GetLockInfo( const idVec3& eyeOrigin, const idMat3& eyeAxis ) { + if ( lastLockTime < gameLocal.GetTime() - 2000 + || (targetEnt && (!targetEnt.IsValid() || targetEnt.GetEntity()->health <= 0)) ) { + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + } + + if ( lockRange > 0.0f ) { + idVec3 end; + trace_t tr; + bool lockFound = false; + idEntity* newTargetEnt = NULL; + jointHandle_t newTargetJoint = INVALID_JOINT; + + end = eyeOrigin + eyeAxis[0] * lockRange; +// gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_BOUNDINGBOX, NULL ); + gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_RENDERMODEL, NULL ); + + if ( tr.fraction < 1.0 ) { + newTargetEnt = gameLocal.entities[ tr.c.entityNum ]; + lockFound = true; + + // Make sure the target is an actor and is alive + if ( !(newTargetEnt->IsType ( idActor::GetClassType() ) || newTargetEnt->IsType ( idProjectile::GetClassType() )) || newTargetEnt->health <= 0 ) { + newTargetEnt = NULL; + lockFound = false; + } else { + //see if there's a joint to lock onto + if ( newTargetEnt->spawnArgs.MatchPrefix ( "lockJoint" ) ) { + jointHandle_t testJointHandle; + idVec3 testJointOffset; + idStr lockJointName; + idVec3 jointOrg; + idMat3 jointAxis; + + int lockJointNum = 1; + float bestDist = 100.0f; + float dist = 0.0f; + + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",lockJointNum), NULL ); + while ( lockJointName.Length() ) { + testJointHandle = newTargetEnt->GetAnimator()->GetJointHandle( lockJointName ); + if ( testJointHandle != INVALID_JOINT ) { + newTargetEnt->GetAnimator()->GetJointTransform( testJointHandle, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = newTargetEnt->GetRenderEntity()->origin + (jointOrg*newTargetEnt->GetRenderEntity()->axis); + jointAxis = jointAxis * newTargetEnt->GetRenderEntity()->axis; + testJointOffset = newTargetEnt->spawnArgs.GetVector( va("lockJointOffset%d",lockJointNum), "0 0 0" ); + jointOrg += jointAxis*testJointOffset; + dist = (jointOrg - tr.endpos).Length(); + if ( dist < bestDist ) { + bestDist = dist; + newTargetJoint = testJointHandle; + targetPos = testJointOffset; + } + } + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",++lockJointNum), NULL ); + } + } + } + if ( spawnArgs.GetBool("guideTowardsDir") ) { + if ( newTargetJoint == INVALID_JOINT ) { + newTargetEnt = NULL; + lockFound = false; + } + } + } else if ( spawnArgs.GetBool( "guideTowardsDir" ) && !targetEnt ) { + targetPos = tr.endpos; + } + if ( lockFound ) { + targetEnt = newTargetEnt; + targetJoint = newTargetJoint; + lastLockTime = gameLocal.GetTime(); + } + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, end, 3, 10000 ); + gameRenderWorld->DebugArrow ( colorWhite, eyeOrigin, tr.endpos, 4, 10000 ); + } + } +} + +/* +===================== +rvVehicleWeapon::EjectBrass +===================== +*/ +void rvVehicleWeapon::EjectBrass ( void ) { + if ( !brassDict || gameLocal.time > brassEjectNext || g_brassTime.GetFloat() <= 0.0f || gameLocal.isMultiplayer || brassEjectJoint == INVALID_JOINT || !brassDict->GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !parent->GetJointWorldTransform( brassEjectJoint, gameLocal.time, origin, axis ) ) { + return; + } + + brassEjectNext += brassEjectDelay; + + // Spawn the client side moveable for the brass + idVec3 offset; + idMat3 playerViewAxis; + gameLocal.GetPlayerView( offset, playerViewAxis ); + rvClientMoveable* cent = NULL; + gameLocal.SpawnClientEntityDef( *brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOrigin ( origin + axis * brassEjectOffset ); + cent->SetAxis ( playerViewAxis ); + cent->SetOwner ( position->GetDriver() ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 6.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict->GetVector("linear_velocity").Random( brassDict->GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( position->GetDriver()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict->GetAngles("angular_velocity").Random( brassDict->GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/*********************************************************************** + + rvVehicleTurret + +***********************************************************************/ + +#define TURRET_MOVESOUND_FADE 200 + +CLASS_DECLARATION( rvVehiclePart, rvVehicleTurret ) +END_CLASS + +/* +===================== +rvVehicleTurret::rvVehicleTurret +===================== +*/ +rvVehicleTurret::rvVehicleTurret ( void ) { + moveTime = 0; + soundPart = -1; +} + +/* +===================== +rvVehicleTurret::Spawn +===================== +*/ +void rvVehicleTurret::Spawn ( void ) { + angles[0][PITCH] = spawnArgs.GetFloat ( "minpitch", "0" ); + angles[1][PITCH] = spawnArgs.GetFloat ( "maxpitch", "0" ); + axisMap[PITCH] = spawnArgs.GetInt ( "pitch", "-1" ); + invert[PITCH] = spawnArgs.GetBool ( "pitchinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][ROLL] = spawnArgs.GetFloat ( "minroll", "0" ); + angles[1][ROLL] = spawnArgs.GetFloat ( "maxroll", "0" ); + axisMap[ROLL] = spawnArgs.GetInt ( "roll", "-1" ); + invert[ROLL] = spawnArgs.GetBool ( "rollinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][YAW] = spawnArgs.GetFloat ( "minyaw", "0" ); + angles[1][YAW] = spawnArgs.GetFloat ( "maxyaw", "0" ); + axisMap[YAW] = spawnArgs.GetInt ( "yaw", "-1" ); + invert[YAW] = spawnArgs.GetBool ( "yawinvert", "0" ) ? -1.0f : 1.0f; + + turnRate = spawnArgs.GetFloat ( "turnrate", "360" ); + + currentAngles.Zero ( ); + + //the parent is *not* stuck on spawn. + parentStuck = false; + + + // Find the vehicle part for the turret sound + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } +} + +/* +===================== +rvVehicleTurret::Activate +===================== +*/ +void rvVehicleTurret::Activate ( bool active ) { + rvVehiclePart::Activate( active ); + + if ( !IsActive() && soundPart >= 0 ) { + static_cast(position->GetPart(soundPart))->Stop ( ); + } + +} + +/* +===================== +rvVehicleTurret::RunPostPhysics +===================== +*/ +void rvVehicleTurret::RunPostPhysics ( void ) { + int i; + idAngles inputAngles; + idAngles lastInputAngles; + idMat3 mat[3]; + idAngles oldAngles; + + if ( IsActive ( ) ) { + for ( i = 0; i < 3; i++ ) { + inputAngles[i] = SHORT2ANGLE( position->mInputCmd.angles[i] ); + lastInputAngles[i] = SHORT2ANGLE( position->mOldInputCmd.angles[i] ); + } + } else { + inputAngles.Zero ( ); + lastInputAngles.Zero ( ); + } + + oldAngles = currentAngles; + + mat[PITCH].Identity(); + mat[YAW].Identity(); + mat[ROLL].Identity(); + for ( i = 0; i < 3; i ++ ) { + if ( axisMap[i] != -1 ) { + float diff = (invert[i] * idMath::AngleDelta ( inputAngles[i], lastInputAngles[i] )); + + diff = SignZero( diff ) * idMath::ClampFloat ( 0.0f, turnRate * MS2SEC(gameLocal.GetMSec()), idMath::Fabs ( diff ) ); + if ( angles[0][i] == angles[1][i] ) { + currentAngles[axisMap[i]] = idMath::AngleNormalize360( currentAngles[axisMap[i]] + diff ); + } else { + currentAngles[axisMap[i]] = idMath::ClampFloat ( angles[0][i], angles[1][i], currentAngles[axisMap[i]] + diff ); + } + + idAngles angles; + angles.Zero(); + angles[axisMap[i]] = currentAngles[axisMap[i]]; + mat[axisMap[i]] = angles.ToMat3(); + } + } + + // Update the turret moving sound + if ( soundPart >= 0 ) { + float speed; + speed = idMath::Fabs( idMath::AngleDelta( oldAngles[YAW], currentAngles[YAW] ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[PITCH], currentAngles[PITCH] ) ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[ROLL], currentAngles[ROLL] ) ) ); + + if ( speed ) { + if ( !moveTime ) { + static_cast(position->GetPart(soundPart))->Play ( ); + } + moveTime = gameLocal.time + TURRET_MOVESOUND_FADE; + } else if ( moveTime && gameLocal.time > moveTime ) { + static_cast(position->GetPart(soundPart))->Fade ( TURRET_MOVESOUND_FADE, 0.0f, 1.0f ); + moveTime = 0; + } + + // Update the volume of the turret move sound using the current speed of the + // turret as well as how long it has been since it has last moved. + if ( moveTime ) { + float f; + f = idMath::ClampFloat ( 0.0f, 1.0f, fabs(speed) / (turnRate * MS2SEC(gameLocal.GetMSec())) ); + static_cast(position->GetPart(soundPart))->Attenuate ( f, f ); + } + } + + // Rotate the turret with the mouse + parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_LOCAL, mat[YAW] * mat[PITCH] * mat[ROLL] ); + + if ( g_vehicleMode.GetInteger() != 0 && joint != INVALID_JOINT && parent->GetAnimator()->GetJointHandle( spawnArgs.GetString( "alignment_joint" ) ) == joint ) { + idMat3 turretAxis; + idVec3 temp; + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + const idMat3 & vehicleAxis = parent->GetPhysics()->GetAxis(); + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + float dotRight = vehicleAxis[1] * turretAxis[alignmentAxis]; + float dotForward = vehicleAxis[0] * turretAxis[alignmentAxis]; + float acosForward = idMath::ACos( dotForward ); + + idAngles oldAngles = currentAngles; + idMat3 oldAxis = vehicleAxis; + + if ( idMath::Fabs(1.0f - dotForward) > VECTOR_EPSILON ) { + float sinus = idMath::Cos( dotForward ); + float power = idMath::Pow( sinus, spawnArgs.GetFloat( "alignment_power", "5" ) ); + float deg = idMath::ClampFloat( 0.0f, spawnArgs.GetFloat( "alignment_delta", "2.4" ), RAD2DEG( acosForward ) ) * power; + idAngles delta( 0, deg * SignZero(dotRight), 0 ); + + //move the tank + parent->GetPhysics()->SetAxis( (delta).ToMat3() * vehicleAxis ); + currentAngles -= delta; + trace_t tr; + + //trace the tank against the world. + idBounds traceBounds; + idVec3 raiseVector( 0, 0, 80); + idVec3 reduceVector( 0, 0, -16); + idVec3 parentOrigin = parent->GetPhysics()->GetOrigin(); + idVec3 parentVecForward = parent->GetPhysics()->GetAxis()[0]; + + traceBounds.Zero(); + traceBounds = parent->GetPhysics()->GetAbsBounds(); + traceBounds.ExpandSelf( reduceVector ); + + //zero the tracebounds-- abs bounds is the right size but not the right location :) + traceBounds.TranslateSelf( traceBounds.GetCenter() * -1 ); + + //raise the bounds up so the hover tank can hover. + traceBounds.TranslateSelf( raiseVector ); + + gameLocal.TraceBounds( parent, tr, parentOrigin, parentOrigin, traceBounds, MASK_SOLID | CONTENTS_VEHICLECLIP ,parent); + + //draw the debug turning bounds if we need to. It will be redder if there's collision. + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec4 vecColor(1.0f - ( tr.fraction) , 0.25f, (0.5f * tr.fraction), 1.0f); + gameRenderWorld->DebugBounds( vecColor, traceBounds, parentOrigin, 10, true ); + } + + //roll it back if it's colliding. + if( tr.fraction < 1.0f) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + gameLocal.Warning("Turret move caused vehicle block %d distance %f", tr.c.entityNum, tr.fraction ); + } + parent->GetPhysics()->SetAxis( oldAxis ); + currentAngles = oldAngles; + + //apply a push to the parent based on the direction of the collision? + parentStuck = true; + } + } + + } +} +/* +===================== +rvVehicleTurret::RunPrePhysics +===================== +*/ +void rvVehicleTurret::RunPrePhysics ( void ) { + + //in case we're stuck, apply an impulse to the parent based on the direction the turret is facing. This will help us pull out in the right direction. + if( parentStuck ) { + + idVec3 impulseForce( 0,0,0); + idMat3 turretAxis; + idVec3 temp; + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + impulseForce += ( turretAxis[alignmentAxis] * 50 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass()); + //impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * 25 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + + // Apply the impulse to the parent entity + + parent->GetPhysics()->ApplyImpulse( 0, parent->GetPhysics()->GetOrigin(), impulseForce); + + parentStuck = false; + } + + +} + + +/* +===================== +rvVehicleTurret::Save +===================== +*/ +void rvVehicleTurret::Save ( idSaveGame* savefile ) const { + savefile->WriteBounds ( angles ); + savefile->WriteInt ( axisMap[0] ); + savefile->WriteInt ( axisMap[1] ); + savefile->WriteInt ( axisMap[2] ); + + savefile->WriteFloat ( invert[0] ); + savefile->WriteFloat ( invert[1] ); + savefile->WriteFloat ( invert[2] ); + + savefile->WriteAngles ( currentAngles ); + + savefile->WriteInt ( moveTime ); + savefile->WriteFloat ( turnRate ); + + savefile->WriteInt ( soundPart ); +} + +/* +===================== +rvVehicleTurret::Restore +===================== +*/ +void rvVehicleTurret::Restore ( idRestoreGame* savefile ) { + savefile->ReadBounds ( angles ); + savefile->ReadInt ( axisMap[0] ); + savefile->ReadInt ( axisMap[1] ); + savefile->ReadInt ( axisMap[2] ); + + savefile->ReadFloat ( invert[0] ); + savefile->ReadFloat ( invert[1] ); + savefile->ReadFloat ( invert[2] ); + + savefile->ReadAngles ( currentAngles ); + + savefile->ReadInt ( moveTime ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadInt ( soundPart ); +} + +/*********************************************************************** + + rvVehicleHoverpad + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleHoverpad ) +END_CLASS + +rvVehicleHoverpad::rvVehicleHoverpad ( void ) { + clipModel = NULL; + effectDust = NULL; + soundPart = -1; + effectDustMaterialType = NULL; +} + +rvVehicleHoverpad::~rvVehicleHoverpad ( void ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + delete clipModel; +} + +/* +================ +rvVehicleHoverpad::Spawn +================ +*/ +void rvVehicleHoverpad::Spawn ( void ) { + float size; + + spawnArgs.GetFloat ( "size", "10", size ); + spawnArgs.GetFloat ( "height", "70", height ); + spawnArgs.GetFloat ( "dampen", "10", dampen ); + spawnArgs.GetFloat ( "forceRandom", "10", forceRandom ); + spawnArgs.GetFloat ( "thrustForward", "0", thrustForward ); + spawnArgs.GetFloat ( "thrustLeft", "0", thrustLeft ); + + maxRestAngle = idMath::Cos ( DEG2RAD(spawnArgs.GetFloat ( "maxRestAngle", "20" ) ) ); + + fadeTime = SEC2MS(spawnArgs.GetFloat ( "fadetime", ".5" )); + + effectDust = NULL; + atRest = true; + + delete clipModel; + clipModel = new idClipModel ( idTraceModel ( idBounds ( spawnArgs.GetVector("mins"),spawnArgs.GetVector("maxs")) ) ); + + force = spawnArgs.GetFloat ( "force", "1066" ); + forceUpTime = SEC2MS ( spawnArgs.GetFloat ( "forceUpTime", "1" ) ); + forceDownTime = SEC2MS ( spawnArgs.GetFloat ( "forceDownTime", "1" ) ); + forceTable = declManager->FindTable( spawnArgs.GetString ( "forceTable", "linear" ), false ); + + currentForce.Init ( 0, 0, 0, 0 ); + + // Is a sound part specified? + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } + + Activate ( false ); +} + +/* +================ +rvVehicleHoverpad::Activate +================ +*/ +void rvVehicleHoverpad::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active ) { + currentForce.Init ( gameLocal.time, forceUpTime, currentForce.GetCurrentValue( gameLocal.time ), force ); + atRest = false; + } else { + currentForce.Init ( gameLocal.time, forceDownTime, currentForce.GetCurrentValue( gameLocal.time ), 0 ); + } +} + +/* +================ +rvVehicleHoverpad::RunPrePhysics + +Called before RunPhysics. Since impact velocities are altered by calls to ApplyImpulse +we need to cache them to ensure other hoverpads are not altering the data. The start +position of the hoverpad is also cached here for efficiency. +================ +*/ +void rvVehicleHoverpad::RunPrePhysics ( void ) { + impactInfo_t info; + + UpdateOrigin ( ); + + // Cache velocity at start point + parent->GetPhysics()->GetImpactInfo( 0, worldOrigin, &info ); + velocity = info.velocity; +} + +/* +================ +rvVehicleHoverpad::RunPhysics +================ +*/ +void rvVehicleHoverpad::RunPhysics ( void ) { + idVec3 end; + idMat3 axis; + trace_t tr; + idVec3 impulseForce; + idVec3 dampingForce; + float hoverForce; + float curlength; + + // Disable pad? + if ( !IsActive() && !atRest ) { + if ( parent->IsAtRest ( ) || currentForce.GetCurrentValue ( gameLocal.time ) <= 0.0f || parent->IsFlipped( ) || parent->IsStalled() ) { + if ( soundPart >= 0 ) { + float fadeVolume; + fadeVolume = idMath::dBToScale ( spawnArgs.GetFloat ( "fadevolume", "0" ) ); + static_cast(position->GetPart(soundPart))->Fade ( fadeTime, fadeVolume, spawnArgs.GetFloat ( "fadefreqshift", "0.1" ) ); + } + + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + atRest = true; + } + } + + // If the vehicle isnt activate then kill the effect on it and dont think + if ( atRest ) { + return; + } + + // Prepare for trace + axis[0] = -parent->GetPhysics()->GetAxis()[2]; + axis[1] = parent->GetPhysics()->GetAxis()[0]; + axis[2] = parent->GetPhysics()->GetAxis()[1]; + + // Always point the hover jets towards gravity + end = worldOrigin + (parent->GetPhysics()->GetGravityNormal ( ) * height); + + // Determine how far away the hoverpad is from the ground + gameLocal.Translation ( parent.GetEntity(), tr, worldOrigin, end, clipModel, axis, MASK_SOLID|CONTENTS_VEHICLECLIP, parent ); + if ( tr.fraction >= 1.0f && tr.endpos == end ) { + UpdateDustEffect ( worldOrigin, axis, 0.0f, NULL ); + return; + } + + // Determine spring properties from trace + tr.c.point = worldOrigin + (end-worldOrigin) * (tr.fraction + 0.001f); + curlength = height - (worldOrigin - tr.c.point).Length ( ); + + // Dampening + dampingForce = tr.c.point - worldOrigin; + dampingForce = ( dampen * ( (velocity * dampingForce) / (dampingForce * dampingForce) ) ) * dampingForce; + + // Random swap forces + float rnd = 0.0f; + if ( !position->mInputCmd.forwardmove && !position->mInputCmd.rightmove ) { + float angle = DEG2RAD( ( (float)( gameLocal.time % 5000 ) / 5000.0f ) * 360.f ); + if ( fl.left ) { + angle += 90; + } + if ( fl.front ) { + angle += 180; + } + rnd = forceRandom * idMath::Sin( angle ); + } + + // Determine how much force should be applied at the center of the hover spring + hoverForce = (currentForce.GetCurrentValue(gameLocal.time) + rnd) * parent->GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()); + + // If the slope under the hoverpad is < 30 degress then use the gravity vector to allow the hovertank to + // stop on slopes, otherwise use the vehicles axis which will cause it to slide down steep slopes + float f = (-parent->GetPhysics()->GetGravityNormal ( ) * tr.c.normal); + if ( f < maxRestAngle || (thrustForward && position->mInputCmd.forwardmove != 0 ) ) { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -axis[0] - dampingForce; + } else { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -parent->GetPhysics()->GetGravityNormal ( ) - dampingForce; + } + + // No thrust if movement is disabled + if ( parent->IsMovementEnabled ( ) ) { + idMat3 axis; + idVec3 offset; + jointHandle_t axisJoint = joint; + if ( axisJoint == INVALID_JOINT ) { + axisJoint = parent->GetAnimator()->GetJointHandle( "gun_pivot" ); + } + parent->GetJointWorldTransform( axisJoint, gameLocal.time, offset, axis ); + + // Add forward/backward thrust + if ( thrustForward ) { + if ( g_vehicleMode.GetInteger() == 2 ) { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * axis[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } + } + + // Add right/left thrust. The front pads will add force to the right if the right button is pressed and the rear will do the opposite. If moving backward the directions are flipped again + if ( thrustLeft ) { + if ( !parent->IsStrafing() && g_vehicleMode.GetInteger() != 0 ) { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * -axis[2] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * (position->mInputCmd.forwardmove<0?-1:1) * parent->GetPhysics()->GetAxis()[1] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass () * (fl.front ? 1 : -1); + } + } + } + + // Apply the impulse to the parent entity + parent->GetPhysics()->ApplyImpulse( 0, worldOrigin, impulseForce); + + // Update the position and intensity of the dust effect + UpdateDustEffect ( tr.c.point, tr.c.normal.ToMat3( ), (curlength / height), tr.c.materialType ); + + // Debug information + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec3 offset; + offset = worldOrigin - parent->GetPhysics()->GetOrigin(); + offset *= parent->GetPhysics()->GetAxis().Transpose(); + gameDebug.AppendList ( "hover", va("%c%c %d\t%0.1f\t%0.1f\t%d\t", (offset[0]>0?'F':'B'),(offset[1]<0?'R':'L'), (int)impulseForce.Length(), curlength, dampingForce.Length(), (int)(velocity*axis[0] ) ) ); + } + + // Draw debug information + if ( g_debugVehicle.GetInteger() == 2 ) { + collisionModelManager->DrawModel ( clipModel->GetCollisionModel(), tr.c.point, axis, vec3_origin, mat3_identity, 0.0f ); + gameRenderWorld->DebugCircle ( colorGreen, tr.c.point, axis[0], 5, 10 ); + gameRenderWorld->DebugLine ( colorMagenta, worldOrigin, worldOrigin + idVec3(0,0,-height)); + gameRenderWorld->DebugLine ( colorWhite, worldOrigin, end ); + gameRenderWorld->DebugCircle ( colorBlue, worldOrigin, axis[0], 5, 10 ); + gameRenderWorld->DebugCircle ( colorBlue, end, axis[0], 5, 10 ); + + if ( thrustForward && position->mInputCmd.forwardmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[1] * 50.0f * (fl.front ? 1 : -1) * ( position->mInputCmd.rightmove / 127.0f ), 10); + } + + if ( thrustLeft && position->mInputCmd.rightmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[0] * 50.0f * ( position->mInputCmd.forwardmove / 127.0f ), 10); + } + } +} + +/* +================ +rvVehicleHoverpad::UpdateDustEffect +================ +*/ +void rvVehicleHoverpad::UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ) { + if ( !effectDust || (mtype && effectDustMaterialType != mtype) ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust", mtype ), origin, axis, true ); + effectDustMaterialType = mtype; + } + + if ( effectDust ) { + effectDust->Attenuate ( attenuation ); + effectDust->SetOrigin ( origin ); + effectDust->SetAxis ( axis ); + } + + // If there is a sound playing update it as well + if ( soundPart >= 0 ) { + if ( static_cast(position->GetPart(soundPart))->IsPlaying ( ) ) { + static_cast(position->GetPart(soundPart))->Attenuate ( attenuation, attenuation ); + } else { + static_cast(position->GetPart(soundPart))->Play ( ); + } + } +} + +/* +===================== +rvVehicleHoverpad::Save +===================== +*/ +void rvVehicleHoverpad::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( height ); + savefile->WriteFloat ( dampen ); + savefile->WriteBool ( atRest ); + + savefile->WriteBounds ( clipModel->GetBounds ( ) ); + + savefile->WriteInterpolate ( currentForce ); + savefile->WriteFloat ( force ); + savefile->WriteTable ( forceTable ); + savefile->WriteFloat ( forceRandom ); + + savefile->WriteFloat ( fadeTime ); + savefile->WriteVec3 ( velocity ); + + savefile->WriteFloat ( thrustForward ); + savefile->WriteFloat ( thrustLeft ); + + savefile->WriteFloat ( maxRestAngle ); + + savefile->WriteInt ( soundPart ); + + savefile->WriteInt ( forceUpTime ); + savefile->WriteInt ( forceDownTime ); + + effectDust.Save ( savefile ); + savefile->WriteMaterialType ( effectDustMaterialType ); +} + +/* +===================== +rvVehicleHoverpad::Restore +===================== +*/ +void rvVehicleHoverpad::Restore ( idRestoreGame* savefile ) { + idBounds bounds; + + savefile->ReadFloat ( height ); + savefile->ReadFloat ( dampen ); + savefile->ReadBool ( atRest ); + + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadInterpolate ( currentForce ); + savefile->ReadFloat ( force ); + savefile->ReadTable ( forceTable ); + savefile->ReadFloat ( forceRandom ); + + savefile->ReadFloat ( fadeTime ); + savefile->ReadVec3 ( velocity ); + + savefile->ReadFloat ( thrustForward ); + savefile->ReadFloat ( thrustLeft ); + + savefile->ReadFloat ( maxRestAngle ); + + savefile->ReadInt ( soundPart ); + + savefile->ReadInt ( forceUpTime ); + savefile->ReadInt ( forceDownTime ); + + effectDust.Restore ( savefile ); + savefile->ReadMaterialType ( effectDustMaterialType ); +} + +/*********************************************************************** + + rvVehicleThruster + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleThruster ) +END_CLASS + +rvVehicleThruster::rvVehicleThruster ( void ) { +} + +rvVehicleThruster::~rvVehicleThruster ( void ) { +} + +/* +===================== +rvVehicleThruster::Save +===================== +*/ +void rvVehicleThruster::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( force ); + savefile->WriteInt ( forceAxis ); + savefile->WriteInt ( key ); +} + +/* +===================== +rvVehicleThruster::Restore +===================== +*/ +void rvVehicleThruster::Restore ( idRestoreGame* savefile ) { + savefile->ReadFloat ( force ); + savefile->ReadInt ( forceAxis ); + savefile->ReadInt ( (int&)key ); +} + +/* +================ +rvVehicleThruster::Spawn +================ +*/ +void rvVehicleThruster::Spawn ( void ) { + idStr keyName; + + force = spawnArgs.GetFloat ( "force", "0" ); + forceAxis = spawnArgs.GetInt ( "forceAxis", "0" ); + + // Determine which key controls the thruster + spawnArgs.GetString ( "key", "", keyName ); + if ( !keyName.Icmp ( "forward" ) ) { + key = KEY_FORWARD; + } else if ( !keyName.Icmp ( "right" ) ) { + key = KEY_RIGHT; + } else if ( !keyName.Icmp ( "up" ) ) { + key = KEY_UP; + } +} + +/* +================ +rvVehicleThruster::RunPhysics +================ +*/ +void rvVehicleThruster::RunPhysics ( void ) { + float mult; + + if ( !IsActive ( ) ) { + return; + } + + // Determine the force multiplier from the key being pressed + mult = 0.0f; + switch ( key ) { + case KEY_FORWARD: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.forwardmove ); + break; + + case KEY_RIGHT: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.rightmove ); + break; + + case KEY_UP: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.upmove ); + break; + } + + // No multiplier, no move + if ( mult == 0.0f ) { + return; + } + + UpdateOrigin ( ); + + // Apply the force + parent->GetPhysics()->ApplyImpulse ( 0, worldOrigin, worldAxis[forceAxis] * force * mult ); + + // Debug Information + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugBounds ( colorCyan, idBounds(idVec3(-4,-4,-4), idVec3(4,4,4) ), worldOrigin ); + gameRenderWorld->DebugArrow ( colorCyan, worldOrigin, worldOrigin + worldAxis[forceAxis] * 100.0f * mult * (force < 0 ? -1 : 1), 10 ); + } +} + +/*********************************************************************** + + rvVehicleUserAnimated + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleUserAnimated ) + EVENT( EV_PostSpawn, rvVehicleUserAnimated::Event_PostSpawn ) +END_CLASS + +rvVehicleUserAnimated::rvVehicleUserAnimated ( void ) { +} + +/* +===================== +rvVehicleUserAnimated::Save +===================== +*/ +void rvVehicleUserAnimated::Save ( idSaveGame* savefile ) const { +} + +/* +===================== +rvVehicleUserAnimated::Restore +===================== +*/ +void rvVehicleUserAnimated::Restore ( idRestoreGame* savefile ) { + Event_PostSpawn(); +} + +/* +================ +rvVehicleUserAnimated::Spawn +================ +*/ +void rvVehicleUserAnimated::Spawn ( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleUserAnimated::Event_PostSpawn +================ +*/ +void rvVehicleUserAnimated::Event_PostSpawn ( void ) { + InitAnim( "forward", anims[ VUAA_Forward ] ); + InitAnim( "strafe", anims[ VUAA_Strafe ] ); + InitAnim( "crouch", anims[ VUAA_Crouch ] ); + InitAnim( "attack", anims[ VUAA_Attack ] ); + + InitFunc( "forward", funcs[ VUAF_Forward ] ); + InitFunc( "strafe", funcs[ VUAF_Strafe ] ); + InitFunc( "crouch", funcs[ VUAF_Crouch ] ); + InitFunc( "attack", funcs[ VUAF_Attack ] ); +} + + +/* +================ +rvVehicleUserAnimated::RunPrePhysics +================ +*/ +void rvVehicleUserAnimated::RunPrePhysics ( void ) { + if ( !IsActive ( ) ) { + return; + } + + rvVehicle *vehicle = position->GetParent(); + const rvVehiclePosition *position = vehicle->GetPosition( 0 ); + const usercmd_t & input = position->mInputCmd; + + if ( position->IsOccupied() ) { + LateralMove( input.forwardmove, VUAA_Forward, VUAF_Forward ); + LateralMove( input.rightmove, VUAA_Strafe, VUAF_Strafe ); + LateralMove( input.upmove, VUAA_Crouch, VUAF_Crouch ); + + // cheat and use LateralMove for the attack + LateralMove( input.buttons & BUTTON_ATTACK, VUAA_Attack, VUAF_Attack ); + } +} + +/* +================ +rvVehicleUserAnimated::InitAnim +================ +*/ +void rvVehicleUserAnimated::InitAnim( const char * action, rvUserAnimatedAnim_t & anim ) { + idStr prefix( action ); + rvVehicle * vehicle = position->GetParent(); + + anim.index = vehicle->GetAnimator()->GetAnim( spawnArgs.GetString( prefix + "_anim" ) ); + anim.rate = spawnArgs.GetFloat( prefix + "_rate", "1" ); + anim.frame = spawnArgs.GetInt( prefix + "_frame" ) * anim.rate; + anim.channel = spawnArgs.GetInt( prefix + "_channel" ); + anim.loop = spawnArgs.GetBool( prefix + "_loop" ); + anim.length = vehicle->GetAnimator()->NumFrames( anim.index ); + + // NOTE: I may want to add the suffix "_play" in the InitAnim() function + // this would specify that the animation should be played rather than stepped through + // which could be useful especially for the attack command +} + +/* +================ +rvVehicleUserAnimated::InitFunc +================ +*/ +void rvVehicleUserAnimated::InitFunc( const char * action, rvScriptFuncUtility & func ) { + idStr temp = idStr( action ) + "_script"; + + if ( spawnArgs.GetString( temp, NULL, temp ) ) { + func.Init( temp ); + } +} + +/* +================ +rvVehicleUserAnimated::SetFrame +================ +*/ +void rvVehicleUserAnimated::SetFrame( const rvUserAnimatedAnim_t & anim ) { + float lerp = anim.frame - int( anim.frame ); + frameBlend_t frameBlend = { 0, anim.frame, anim.frame + 1.0f, 1.0f - lerp, lerp }; + position->GetParent()->GetAnimator()->SetFrame( anim.channel, anim.index, frameBlend ); +} + +/* +================ +rvVehicleUserAnimated::LateralMove +================ +*/ +void rvVehicleUserAnimated::LateralMove( signed char input, int anim, int func ) { + if ( !input ) { + return; + } + + rvUserAnimatedAnim_t & theAnim = anims[ anim ]; + + if ( theAnim.index ) { + + float sign = ( ( input > 0 ) ? 1.0f : -1.0f ); + theAnim.frame += theAnim.rate * sign; + + if ( theAnim.frame < 1 ) { + theAnim.frame = ( ( theAnim.loop ) ? theAnim.length : 1 ); + } else if ( theAnim.frame > theAnim.length ) { + theAnim.frame = ( ( theAnim.loop ) ? 1 : theAnim.length ); + } + + SetFrame( theAnim ); + } + + if ( funcs[ func ].Valid( ) ) + funcs[ func ].CallFunc( &spawnArgs ); +} diff --git a/source/mpgame/vehicle/VehicleParts.h b/source/mpgame/vehicle/VehicleParts.h new file mode 100644 index 0000000..9744189 --- /dev/null +++ b/source/mpgame/vehicle/VehicleParts.h @@ -0,0 +1,444 @@ +//---------------------------------------------------------------- +// VehicleParts.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEPARTS_H__ +#define __GAME_VEHICLEPARTS_H__ + +class idWeapon; +class rvVehicle; +class rvVehiclePosition; +class rvEffect; + +//---------------------------------------------------------------- +// Base Part +//---------------------------------------------------------------- + +class rvVehiclePart : public idClass +{ +public: + + ABSTRACT_PROTOTYPE( rvVehiclePart ); + + virtual ~rvVehiclePart( void ) { } + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual bool Init ( rvVehiclePosition* position, const idDict& args, s_channelType soundChannel ); + + virtual void RunPrePhysics ( void ) { } + virtual void RunPhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + bool IsLeft ( void ) const; + bool IsFront ( void ) const; + bool IsUsingCenterMass ( void ) const; + bool IsActive ( void ) const; + + virtual void Activate ( bool activate ) { fl.active = activate; } + + virtual void Impulse ( int impulse ) { } + + virtual rvVehiclePosition* GetPosition ( void ) const; + const idVec3& GetOrigin ( void ) const; + const idMat3& GetAxis ( void ) const; + +protected: + + void UpdateOrigin ( void ); + + typedef struct partFlags_s { + bool active : 1; // Is part active + bool left : 1; // Is part on left side of vehicle + bool front : 1; // Is part on right side of vehicle + bool useCenterMass : 1; // Use center of mass for origin + bool useViewAxis : 1; // Uses parent->viewAxis instead of the physics axis (::UpdateOrigin()) + } partFlags_t; + + partFlags_t fl; + + idDict spawnArgs; + idEntityPtr parent; + jointHandle_t joint; + s_channelType soundChannel; + rvVehiclePosition* position; + + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 localOffset; +}; + +ID_INLINE rvVehiclePosition* rvVehiclePart::GetPosition ( void ) const { return position; } +ID_INLINE const idVec3& rvVehiclePart::GetOrigin ( void ) const { return worldOrigin; } +ID_INLINE const idMat3& rvVehiclePart::GetAxis ( void ) const { return worldAxis; } + +ID_INLINE bool rvVehiclePart::IsLeft ( void ) const { return fl.left; } +ID_INLINE bool rvVehiclePart::IsFront ( void ) const { return fl.front; } +ID_INLINE bool rvVehiclePart::IsUsingCenterMass ( void ) const { return fl.useCenterMass; } +ID_INLINE bool rvVehiclePart::IsActive ( void ) const { return fl.active; } + +//---------------------------------------------------------------- +// Sound +//---------------------------------------------------------------- + +class rvVehicleSound : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleSound ); + + rvVehicleSound ( void ); + ~rvVehicleSound ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + bool IsPlaying ( void ) const; + + void Play ( void ); + void Stop ( void ); + void Update ( bool force = false ); + + void Fade ( int time, float toVolume, float toFreq ); + void Attenuate ( float volumeAttenuate, float freqAttenuate ); + + void SetAutoActivate ( bool activate ); + +protected: + + idVec2 volume; + idVec2 freqShift; + idStr soundName; + refSound_t refSound; + idInterpolate currentVolume; + idInterpolate currentFreqShift; + bool fade; + bool autoActivate; +}; + +ID_INLINE bool rvVehicleSound::IsPlaying ( void ) const { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return( false ); +} + +ID_INLINE void rvVehicleSound::SetAutoActivate ( bool activate ){ + autoActivate = activate; +} + +//---------------------------------------------------------------- +// Hoverpad +//---------------------------------------------------------------- + +class rvVehicleHoverpad : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleHoverpad ); + + rvVehicleHoverpad ( void ); + ~rvVehicleHoverpad ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + void UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ); + + float height; + float dampen; + bool atRest; + + idClipModel* clipModel; + + idInterpolate currentForce; + float force; + const idDeclTable* forceTable; + float forceRandom; + + float fadeTime; + idVec3 velocity; + + float thrustForward; + float thrustLeft; + + float maxRestAngle; + + int soundPart; + + int forceUpTime; + int forceDownTime; + + // Dust effects + rvClientEntityPtr effectDust; + const rvDeclMatType* effectDustMaterialType; +}; + +//---------------------------------------------------------------- +// Thruster +//---------------------------------------------------------------- + +class rvVehicleThruster : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleThruster ); + + rvVehicleThruster ( void ); + ~rvVehicleThruster ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPhysics ( void ); + +protected: + + enum EThrusterKey + { + KEY_FORWARD, + KEY_RIGHT, + KEY_UP + }; + + float force; + int forceAxis; + EThrusterKey key; +}; + +//---------------------------------------------------------------- +// Light +//---------------------------------------------------------------- + +class rvVehicleLight : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleLight ); + + rvVehicleLight ( void ); + ~rvVehicleLight ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + virtual void Impulse ( int impulse ); + +protected: + + void TurnOff ( void ); + void TurnOn ( void ); + + void UpdateLightDef ( void ); + + renderLight_t renderLight; + int lightHandle; + bool lightOn; + + idStr soundOn; + idStr soundOff; +}; + +//---------------------------------------------------------------- +// Weapon +//---------------------------------------------------------------- +class rvVehicleWeapon : public rvVehiclePart { +public: + CLASS_PROTOTYPE( rvVehicleWeapon ); + + rvVehicleWeapon ( void ); + ~rvVehicleWeapon ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool activate ); + + int GetCurrentAmmo ( void ) const; + float GetCurrentCharge ( void ) const; + + void UpdateCursorGUI ( idUserInterface* gui ) const; + + bool Fire (); + + int GetZoomFov ( void ) const; + idUserInterface * GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + bool CanZoom ( void ) const; + + void StopTargetEffect ( void ); + +protected: +#ifdef _XENON + idActor* bestEnemy; + void AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ); +#endif + void LaunchHitScan ( const idVec3& origin, const idVec3& dir, const idVec3& jointOrigin ); + void LaunchProjectile ( const idVec3& origin, const idVec3& dir, const idVec3& pushVelocity ); + void LaunchProjectiles (); + + void UpdateLock ( void ); + void GetLockInfo ( const idVec3& eyeOrigin, const idMat3& eyeAxis ); + + void EjectBrass ( void ); + + void MuzzleFlashLight ( const idVec3& origin, const idMat3& axis ); + void WeaponFeedback ( const idDict* dict ); + + int nextFireTime; + int fireDelay; + int count; + const idDict* projectileDef; + const idDict* hitScanDef; + float spread; + bool launchFromJoint; + bool lockScanning; + int lastLockTime; + float lockRange; + + idEntityPtr targetEnt; + jointHandle_t targetJoint; + idVec3 targetPos; + rvClientEntityPtr targetEffect; + + idList joints; + int jointIndex; + + idVec3 force; + + int ammoPerCharge; + int chargeTime; + int currentAmmo; + idInterpolate currentCharge; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashOffset; + + int animNum; + int animChannel; + + const idSoundShader* shaderFire; + const idSoundShader* shaderReload; + + const idDict* brassDict; + jointHandle_t brassEjectJoint; + idVec3 brassEjectOffset; + int brassEjectNext; + int brassEjectDelay; + + int zoomFov; + idUserInterface * zoomGui; + float zoomTime; +}; + +ID_INLINE int rvVehicleWeapon::GetCurrentAmmo ( void ) const { return currentAmmo; } +ID_INLINE float rvVehicleWeapon::GetCurrentCharge ( void ) const { return currentCharge.IsDone(gameLocal.time) ? 1.0f : currentCharge.GetCurrentValue(gameLocal.time); } +ID_INLINE int rvVehicleWeapon::GetZoomFov ( void ) const { return zoomFov; } +ID_INLINE idUserInterface* rvVehicleWeapon::GetZoomGui ( void ) const { return zoomGui; } +ID_INLINE float rvVehicleWeapon::GetZoomTime ( void ) const { return zoomTime; } +ID_INLINE bool rvVehicleWeapon::CanZoom ( void ) const { return zoomFov != -1; } + + +//---------------------------------------------------------------- +// Turret +//---------------------------------------------------------------- + +class rvVehicleTurret : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleTurret ); + + rvVehicleTurret ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + idBounds angles; + int axisMap[3]; + + float invert[3]; + + idAngles currentAngles; + + int moveTime; + float turnRate; + + int soundPart; + + bool parentStuck; +}; + +//---------------------------------------------------------------- +// Animated Vehicle Part +//---------------------------------------------------------------- + +class rvVehicleUserAnimated : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleUserAnimated ); + + rvVehicleUserAnimated ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + + void Event_PostSpawn ( void ); + +protected: + + typedef struct rvUserAnimatedAnim_s { + int index; + float frame; + short length; + short channel; + float rate; + bool loop; + } rvUserAnimatedAnim_t; + + enum { VUAA_Forward, VUAA_Strafe, VUAA_Crouch, VUAA_Attack, VUAA_Count }; + enum { VUAF_Forward, VUAF_Strafe, VUAF_Crouch, VUAF_Attack, VUAF_Count }; + + rvUserAnimatedAnim_t anims[ VUAA_Count ]; + rvScriptFuncUtility funcs[ VUAF_Count ]; + + void InitAnim( const char * action, rvUserAnimatedAnim_t & anim ); + void InitFunc( const char * action, rvScriptFuncUtility & func ); + void SetFrame( const rvUserAnimatedAnim_t & anim ); + void LateralMove( signed char input, int anim, int func ); +}; + +typedef idList rvVehiclePartList_t; + +#endif // __GAME_VEHICLEPARTS_H__ diff --git a/source/mpgame/vehicle/VehiclePosition.cpp b/source/mpgame/vehicle/VehiclePosition.cpp new file mode 100644 index 0000000..3f8a0b0 --- /dev/null +++ b/source/mpgame/vehicle/VehiclePosition.cpp @@ -0,0 +1,845 @@ +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "Vehicle.h" + +/* +================ +rvVehiclePosition::rvVehiclePosition +================ +*/ +rvVehiclePosition::rvVehiclePosition ( void ) { + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + + memset ( &fl, 0, sizeof(fl) ); + + mCurrentWeapon = -1; + mSoundPart = -1; + + mDriver = NULL; + mParent = NULL; + + mEyeOrigin.Zero(); + mEyeAxis.Identity(); + + mEyeOffset.Zero(); + mEyeJoint = INVALID_JOINT; + + mDriverOffset.Zero ( ); + mDriverJoint = INVALID_JOINT; +} + +/* +================ +rvVehiclePosition::~rvVehiclePosition +================ +*/ +rvVehiclePosition::~rvVehiclePosition ( void ) { + mParts.DeleteContents ( true ); + mWeapons.DeleteContents ( true ); +} + +void InitIntArray( const idVec3& vec, int array[] ) { + int ix; + for( ix = 0; ix < vec.GetDimension(); ++ix ) { + array[ix] = (int)vec[ix]; + } +} + +/* +================ +rvVehiclePosition::Init +================ +*/ +void rvVehiclePosition::Init ( rvVehicle* parent, const idDict& args ) { + idVec3 garbage; + + mParent = parent; + + mEyeJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "eyeJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mEyeJoint, gameLocal.GetTime(), garbage, mEyeJointTransform) ) { + mEyeJointTransform.Identity(); + //} + //mEyeJointTransform.TransposeSelf(); + mEyeOffset = args.GetVector ( "eyeOffset", "0 0 0" ); + mDeltaEyeAxisScale = args.GetAngles( "deltaEyeAxisScale", "1 1 0" ); + mDeltaEyeAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("eyeJointAxisMap", "0 1 2"), mEyeJointAxisMap ); + InitIntArray( args.GetVector("eyeJointDirMap", "1 1 1"), mEyeJointDirMap ); + + mAxisOffset = args.GetAngles( "angles_offset" ).ToMat3(); + + mDriverJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "driverJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mDriverJoint, gameLocal.GetTime(), garbage, mDriverJointTransform) ) { + mDriverJointTransform.Identity(); + //} + //mDriverJointTransform.TransposeSelf( ); + mDriverOffset = args.GetVector ( "driverOffset", "0 0 0" ); + mDeltaDriverAxisScale = args.GetAngles( "deltaDriverAxisScale", "1 1 0" ); + mDeltaDriverAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("driverJointAxisMap", "0 1 2"), mDriverJointAxisMap ); + InitIntArray( args.GetVector("driverJointDirMap", "1 1 1"), mDriverJointDirMap ); + + mExitPosOffset = args.GetVector( "exitPosOffset" ); + mExitAxisOffset = args.GetAngles( "exitAxisOffset" ).ToMat3(); + + mDriverAnim = args.GetString ( "driverAnim", "driver" ); + + fl.driverVisible = args.GetBool ( "driverVisible", "0" ); + fl.engine = args.GetBool ( "engine", "0" ); + fl.depthHack = args.GetBool ( "depthHack", "1" ); + fl.bindDriver = args.GetBool ( "bindDriver", "1" ); + + args.GetString ( "internalSurface", "", mInternalSurface ); + + SetParts ( args ); + + mSoundMaxSpeed = args.GetFloat ( "maxsoundspeed", "0" ); + + // Looping sound when occupied? + if ( *args.GetString ( "snd_loop", "" ) ) { + mSoundPart = AddPart ( rvVehicleSound::GetClassType(), args ); + static_cast(mParts[mSoundPart])->SetAutoActivate ( false ); + } + + SelectWeapon ( 0 ); + + UpdateInternalView ( true ); +} + +/* +================ +rvVehiclePosition::SetParts +================ +*/ +void rvVehiclePosition::SetParts ( const idDict& args ) { + const idKeyValue* kv; + + // Spawn all parts + kv = args.MatchPrefix( "def_part", NULL ); + while ( kv ) { + const idDict* dict; + idTypeInfo* typeInfo; + + // Skip empty strings + if ( !kv->GetValue().Length() ) { + kv = args.MatchPrefix( "def_part", kv ); + continue; + } + + // Get the dictionary for the part + dict = gameLocal.FindEntityDefDict ( kv->GetValue() ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Determine the part type + typeInfo = idClass::GetClass ( dict->GetString ( "spawnclass" ) ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) { + gameLocal.Error ( "Class '%s' is not a vehicle part", dict->GetString ( "spawnclass" ) ); + } + + // Add the new part + AddPart ( *typeInfo, *dict ); + + kv = args.MatchPrefix( "def_part", kv ); + } +} + +/* +================ +rvVehiclePosition::AddPart +================ +*/ +int rvVehiclePosition::AddPart ( const idTypeInfo &classdef, const idDict& args ) { + rvVehiclePart* part; + int soundChannel; + + // Get a sound channel + soundChannel = SND_CHANNEL_CUSTOM; + soundChannel += mParts.Num(); + + // Allocate the new part + part = static_cast(classdef.CreateInstance ( )); + part->Init ( this, args, (s_channelType)soundChannel ); + part->CallSpawn ( ); + + // Weapons go into their own list since only one can be active + // and any given point in time. + if ( part->IsType ( rvVehicleWeapon::GetClassType() ) ) { + return mWeapons.Append ( part ); + } + + return mParts.Append ( part ); +} + +/* +================ +rvVehiclePosition::SetDriver +================ +*/ +bool rvVehiclePosition::SetDriver ( idActor* driver ) { + if ( mDriver == driver ) { + return false; + } + + fl.inputValid = false; + + if ( driver ) { + mDriver = driver; + + // Keep the driver visible if the position is exposed + if ( fl.driverVisible ) { + mDriver->Show ( ); + } else { + mDriver->Hide ( ); + + // Dont let the player take damage when inside the if not visible + mDriver->fl.takedamage = false; + } + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Play ( ); + } + + // Bind the driver to a joint or to the vehicles origin? + if( fl.bindDriver ) { + if ( INVALID_JOINT != mDriverJoint ) { + mDriver->GetPhysics()->SetAxis ( mParent->GetAxis( ) );// Not sure if this is needed + mDriver->BindToJoint ( mParent, mDriverJoint, true ); + mDriver->GetPhysics()->SetOrigin ( vec3_origin ); + } else { + mDriver->Bind ( mParent, true ); + mDriver->GetPhysics()->SetOrigin( mDriverOffset ); + } + } else {// If not bound put the vehicle first in the active list + mParent->activeNode.Remove(); + mParent->activeNode.AddToFront( gameLocal.activeEntities ); + } + + // Play a certain animation on the driver + if ( mDriverAnim.Length() ) { + mDriver->GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, mDriver->GetAnimator()->GetAnim( mDriverAnim ), gameLocal.time, 0 ); + } + } else { + if ( !fl.driverVisible ) { + mDriver->Show ( ); + + // Take damage again + mDriver->fl.takedamage = true; + } + + // Driver is no longer bound to the vehicle + mDriver->Unbind(); + + mDriver = driver; + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Stop ( ); + } + + // Clear out the input commands so the guns dont keep firing and what not when + // the player gets out + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + } + + return true; +} + +/* +================ +rvVehiclePosition::GetAxis +================ +*/ +idMat3 rvVehiclePosition::GetAxis() const { + return mAxisOffset * GetParent()->GetPhysics()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetOrigin +================ +*/ +idVec3 rvVehiclePosition::GetOrigin( const idVec3& offset ) const { + return GetParent()->GetPhysics()->GetOrigin() + (mDriverOffset + offset) * GetAxis(); +} + +/* +================ +rvVehiclePosition::ActivateParts +================ +*/ +void rvVehiclePosition::ActivateParts ( bool active ) { + int i; + + // Activate or deactive the parts based on whether there is a driver + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + mParts[i]->Activate ( active ); + } + + for ( i = mWeapons.Num() - 1; i >= 0; i -- ) { + mWeapons[i]->Activate ( active ); + } +} + +/* +================ +rvVehiclePosition::EjectDriver +================ +*/ +bool rvVehiclePosition::EjectDriver ( bool force ) { + if ( !IsOccupied ( ) ) { + return false; + } + + // Physically eject the actor from the position + if ( !force && mParent->IsLocked ( ) ) { + mParent->IssueLockedWarning ( ); + return false; + } + + // Remove the driver and if successful disable all parts for + // this position + if ( SetDriver ( NULL ) ) { + ActivateParts ( false ); + return true; + } + + return false; +} + +/* +================ +rvVehiclePosition::SetInput +================ +*/ +void rvVehiclePosition::SetInput ( const usercmd_t& cmd, const idAngles& newAngles ) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE ) ) { + if ( mDriver == gameLocal.GetLocalPlayer ( ) ) { + gameDebug.SetFocusEntity ( mParent ); + } + } + + if ( fl.inputValid || !mDriver ) { + mOldInputCmd = mInputCmd; + mOldInputAngles = mInputAngles; + } else { + mOldInputCmd = cmd; + mInputCmd = cmd; + mInputAngles = newAngles; + + // We have valid input now and there is a driver so activate all of the parts + if ( !fl.inputValid && mDriver ) { + ActivateParts ( true ); + } + } + + fl.inputValid = true; + mInputCmd = cmd; + mInputAngles = newAngles; +} + +/* +================ +rvVehiclePosition::GetInput +================ +*/ +void rvVehiclePosition::GetInput( usercmd_t& cmd, idAngles& newAngles ) const { + cmd = mInputCmd; + newAngles = mInputAngles; +} + +/* +================ +rvVehiclePosition::UpdateHUD +================ +*/ +void rvVehiclePosition::UpdateHUD ( idUserInterface* gui ) { + + // HACK: twhitaker: this is ugly but since the GEV and the Walker now have a unified HUD, it is a necessary evil + int guiWeaponID; + + if ( !stricmp( mParent->spawnArgs.GetString( "classname" ), "vehicle_walker" ) ) { + guiWeaponID = mCurrentWeapon + 2; + } else { + guiWeaponID = mCurrentWeapon; + } + + gui->SetStateInt ( "vehicle_weapon", guiWeaponID ); + // HACK: twhitaker end + + gui->SetStateInt ( "vehicle_weaponcount", mWeapons.Num() ); + if ( mCurrentWeapon >= 0 && mCurrentWeapon < mWeapons.Num() ) { + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + gui->SetStateFloat ( "vehicle_weaponcharge", weapon->GetCurrentCharge() ); + gui->SetStateInt ( "vehicle_weaponammo", weapon->GetCurrentAmmo() ); + } + + // Calculate the rotation of the view in relation to the vehicle itself + gui->SetStateFloat ( "vehicle_rotate", -idMath::AngleDelta ( mEyeAxis.ToAngles()[YAW], mParent->GetAxis ( ).ToAngles()[YAW] ) ); + + if( GetParent() ) { + GetParent()->UpdateHUD( GetDriver(), gui ); + } +} + +/* +================ +rvVehiclePosition::UpdateCursorGUI +================ +*/ +void rvVehiclePosition::UpdateCursorGUI ( idUserInterface* gui ) { + if ( mCurrentWeapon < 0 || mCurrentWeapon >= mWeapons.Num() ) { + return; + } + + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + assert ( weapon ); + weapon->UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehiclePosition::UpdateInternalView +================ +*/ +void rvVehiclePosition::UpdateInternalView ( bool force ) { + bool internal = false; + + // If the local player is driving and not in third person then use internal + internal = ( mDriver == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetInteger() ); + + // force the update? + if ( !force && internal == fl.internalView ) { + return; + } + + fl.internalView = internal; + + if ( fl.depthHack ) { + mParent->GetRenderEntity()->weaponDepthHackInViewID = (IsOccupied() && fl.internalView) ? GetDriver()->entityNumber + 1 : 0; + } + + // Show and hide the internal surface + if ( mInternalSurface.Length() ) { + mParent->ProcessEvent ( fl.internalView ? &EV_ShowSurface : &EV_HideSurface, mInternalSurface.c_str() ); + } +} + +/* +================ +rvVehiclePosition::RunPrePhysics +================ +*/ +void rvVehiclePosition::RunPrePhysics ( void ) { + int i; + + UpdateInternalView ( ); + + if ( mParent->IsStalled() ) { + if ( !fl.stalled ) { + // we just stalled + fl.stalled = true; + ActivateParts( false ); + } + return; + } else if ( IsOccupied() && !mParent->IsStalled() && fl.stalled ) { + // we just restarted + fl.stalled = false; + ActivateParts( true ); + } + + // Give the parts a chance to set up for physics + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + mParts[i]->RunPrePhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPrePhysics ( ); + } + + // Run physics for each part + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + + mParts[i]->RunPhysics ( ); + } + + // Attenuate the attached sound if speed based + if ( mSoundPart != -1 && mSoundMaxSpeed > 0.0f ) { + float f; + float speed; + idVec3 vel; + + // Only interested in forward or backward velocity + vel = mParent->GetPhysics()->GetLinearVelocity ( ) * mParent->GetPhysics()->GetAxis ( ); + speed = idMath::ClampFloat ( 0.0f, mSoundMaxSpeed, vel.Normalize ( ) ); + f = speed / mSoundMaxSpeed; + + static_cast(mParts[mSoundPart])->Attenuate ( f, f ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPhysics ( ); + } +} + +/* +================ +rvVehiclePosition::RunPostPhysics +================ +*/ +void rvVehiclePosition::RunPostPhysics ( void ) { + int i; + for ( i = 0; i < mParts.Num(); i ++ ) { + assert ( mParts[i] ); + mParts[i]->RunPostPhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPostPhysics ( ); + } + + if ( !IsOccupied ( ) ) { + return; + } + + if ( fl.inputValid ) { + if ( mInputCmd.upmove > 0 && !mParent->IsLocked() ) { + // inform the driver that its time to get out of the vehicle. + if( !mDriver->EventIsPosted(&AI_ExitVehicle) ) { + mDriver->PostEventMS( &AI_ExitVehicle, 250, false );// To remove jump when exiting + } + + // If the position isnt occupied anymore then the vehicle exit was successful and there + // is nothing else to do + if ( !IsOccupied ( ) ) { + return; + } + } else if ( (mInputCmd.flags & UCF_IMPULSE_SEQUENCE) != (mOldInputFlags & UCF_IMPULSE_SEQUENCE) ) { + int i; + + if ( mInputCmd.impulse >= IMPULSE_0 && mInputCmd.impulse <= IMPULSE_12 ) { + SelectWeapon( mInputCmd.impulse - IMPULSE_0 ); + } + + if ( mWeapons.Num () ) { + switch ( mInputCmd.impulse ) { + case IMPULSE_14: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() + 1) % mWeapons.Num() ); + break; + + case IMPULSE_15: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() - 1) % mWeapons.Num() ); + break; + } + } + + for( i = 0; i < mParts.Num(); i++ ) { + mParts[ i ]->Impulse ( mInputCmd.impulse ); + } + + mOldInputFlags = mInputCmd.flags; + } + } + + // Update the eye origin and axis + GetEyePosition( mEyeOrigin, mEyeAxis ); + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow( colorMagenta, mEyeOrigin, mEyeOrigin + mEyeAxis[0] * 30.0f, 3 ); + } +} + +/* +================ +rvVehiclePosition::GetEyePosition +================ +*/ +void rvVehiclePosition::GetEyePosition( idVec3& origin, idMat3& axis ) const { + GetPosition( mEyeJoint, mEyeOffset, mEyeJointTransform, mDeltaEyeAxisScale, mEyeJointAxisMap, mEyeJointDirMap, origin, axis ); + axis *= GetParent()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetDriverPosition +================ +*/ +void rvVehiclePosition::GetDriverPosition( idVec3& origin, idMat3& axis ) const { + if( fl.bindDriver ) { + GetPosition( mDriverJoint, mDriverOffset, mDriverJointTransform, mDeltaDriverAxisScale, mDriverJointAxisMap, mDriverJointDirMap, origin, axis ); + } else { + origin = GetDriver()->GetPhysics()->GetOrigin(); + axis = GetDriver()->viewAxis; + } +} + +/* +================ +rvVehiclePosition::GetPosition +================ +*/ +void rvVehiclePosition::GetPosition( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const { + if( GetParent()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.GetTime(), origin, axis) ) { + axis *= jointTransform; + idAngles ang( axis.ToAngles().Remap(axisMap, dirMap).Scale(scale) ); + axis = ang.Normalize360().ToMat3() * mAxisOffset; + + origin = GetParent()->GetOrigin() + (origin + offset * axis) * GetParent()->GetAxis(); + } else { + origin = GetOrigin( mEyeOffset ); + axis = GetAxis(); + } +} + +/* +================ +rvVehiclePosition::FireWeapon +================ +*/ +void rvVehiclePosition::FireWeapon( void ) { + if ( mCurrentWeapon >= 0 ) { + static_cast( mWeapons[mCurrentWeapon] )->Fire(); + } +} + +/* +================ +rvVehiclePosition::SelectWeapon +================ +*/ +void rvVehiclePosition::SelectWeapon ( int weapon ) { + if ( weapon < 0 || weapon >= mWeapons.Num ( ) ) { + return; + } + +// mekberg: clear effect + if ( mCurrentWeapon != -1 ) { + static_cast ( mWeapons[ mCurrentWeapon ] )->StopTargetEffect( ); + } + + mCurrentWeapon = weapon; +} + +/* +================ +rvVehiclePosition::WriteToSnapshot +================ +*/ +void rvVehiclePosition::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteBits ( mCurrentWeapon, 3 ); +} + +/* +================ +rvVehiclePosition::ReadFromSnapshot +================ +*/ +void rvVehiclePosition::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mCurrentWeapon = msg.ReadBits ( 3 ); +} + +/* +================ +rvVehiclePosition::Save +================ +*/ +void rvVehiclePosition::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteUsercmd( mInputCmd ); + savefile->WriteAngles ( mInputAngles ); + savefile->WriteUsercmd ( mOldInputCmd ); + savefile->WriteAngles ( mOldInputAngles ); + savefile->WriteInt ( mOldInputFlags ); + + savefile->WriteInt ( mCurrentWeapon ); + + mDriver.Save ( savefile ); + mParent.Save ( savefile ); + + savefile->WriteInt ( mParts.Num ( ) ); + for ( i = 0; i < mParts.Num(); i ++ ) { + savefile->WriteString ( mParts[i]->GetClassname() ); + savefile->WriteStaticObject ( *mParts[i] ); + } + + savefile->WriteInt ( mWeapons.Num ( ) ); + for ( i = 0; i < mWeapons.Num(); i ++ ) { + savefile->WriteString ( mWeapons[i]->GetClassname() ); + savefile->WriteStaticObject ( *mWeapons[i] ); + } + + savefile->WriteVec3 ( mEyeOrigin ); + savefile->WriteMat3 ( mEyeAxis ); + + savefile->WriteJoint ( mEyeJoint ); + savefile->WriteVec3 ( mEyeOffset ); + savefile->WriteMat3( mEyeJointTransform ); + savefile->WriteAngles( mDeltaEyeAxisScale ); + savefile->Write ( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Write ( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->WriteMat3 ( mAxisOffset ); + + savefile->WriteJoint ( mDriverJoint ); + savefile->WriteVec3 ( mDriverOffset ); + savefile->WriteMat3 ( mDriverJointTransform ); + savefile->WriteAngles( mDeltaDriverAxisScale ); + savefile->Write ( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Write ( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->WriteVec3 ( mExitPosOffset ); + savefile->WriteMat3 ( mExitAxisOffset ); + + savefile->WriteString ( mDriverAnim ); + + savefile->WriteString ( mInternalSurface ); + + savefile->Write ( &fl, sizeof ( fl ) ); + + savefile->WriteInt ( mSoundPart ); + savefile->WriteFloat ( mSoundMaxSpeed ); +} + +/* +================ +rvVehiclePosition::Restore +================ +*/ +void rvVehiclePosition::Restore ( idRestoreGame* savefile ) { + int i; + int num; + + savefile->ReadUsercmd( mInputCmd ); + savefile->ReadAngles ( mInputAngles ); + savefile->ReadUsercmd ( mOldInputCmd ); + savefile->ReadAngles ( mOldInputAngles ); + savefile->ReadInt ( mOldInputFlags ); + + savefile->ReadInt ( mCurrentWeapon ); + + mDriver.Restore ( savefile ); + mParent.Restore ( savefile ); + + savefile->ReadInt ( num ); + mParts.Clear ( ); + mParts.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mParts[i] = part; + } + + savefile->ReadInt ( num ); + mWeapons.Clear ( ); + mWeapons.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mWeapons[i] = part; + } + + savefile->ReadVec3 ( mEyeOrigin ); + savefile->ReadMat3 ( mEyeAxis ); + + savefile->ReadJoint ( mEyeJoint ); + savefile->ReadVec3 ( mEyeOffset ); + savefile->ReadMat3 ( mEyeJointTransform ); + savefile->ReadAngles( mDeltaEyeAxisScale ); + savefile->Read( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Read( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->ReadMat3 ( mAxisOffset ); + + savefile->ReadJoint ( mDriverJoint ); + savefile->ReadVec3 ( mDriverOffset ); + savefile->ReadMat3 ( mDriverJointTransform ); + savefile->ReadAngles( mDeltaDriverAxisScale ); + savefile->Read( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Read( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->ReadVec3 ( mExitPosOffset ); + savefile->ReadMat3 ( mExitAxisOffset ); + + savefile->ReadString ( mDriverAnim ); + + savefile->ReadString ( mInternalSurface ); + + savefile->Read ( &fl, sizeof(fl) ); + savefile->ReadInt ( mSoundPart ); + savefile->ReadFloat ( mSoundMaxSpeed ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/mpgame/vehicle/VehicleRigid.cpp b/source/mpgame/vehicle/VehicleRigid.cpp new file mode 100644 index 0000000..68c7188 --- /dev/null +++ b/source/mpgame/vehicle/VehicleRigid.cpp @@ -0,0 +1,163 @@ +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleRigid.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleRigid ) +END_CLASS + +rvVehicleRigid::rvVehicleRigid ( void ) { +} + +rvVehicleRigid::~rvVehicleRigid ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleRigid::Spawn +================ +*/ +void rvVehicleRigid::Spawn( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleRigid::SetClipModel +================ +*/ +void rvVehicleRigid::SetClipModel ( void ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleRigid '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + + +/* +================ +rvVehicleRigid::RunPrePhysics +================ +*/ +void rvVehicleRigid::RunPrePhysics ( void ) { + storedVelocity = physicsObj.GetLinearVelocity(); +} + +/* +================ +rvVehicleRigid::RunPostPhysics +================ +*/ +void rvVehicleRigid::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 1250 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + float lengthSq = physicsObj.GetLinearVelocity().LengthSqr(); + if ( !autoCorrectionBegin && ( ( storedVelocity * 0.4f ).LengthSqr() >= lengthSq ) || ( lengthSq < 0.01f ) ) { + autoCorrectionBegin = gameLocal.time; + } + } + + if ( g_debugVehicle.GetInteger() == 10 ) { + gameLocal.Printf( "Speed: %f\n", physicsObj.GetLinearVelocity().Length() ); + } +} + +/* +================ +rvVehicleRigid::WriteToSnapshot +================ +*/ +void rvVehicleRigid::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot( msg ); + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::ReadFromSnapshot +================ +*/ +void rvVehicleRigid::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot( msg ); + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::Save +================ +*/ +void rvVehicleRigid::Save ( idSaveGame *savefile ) const { + + savefile->WriteVec3 ( storedVelocity ); // cnicholson: Added unsaved var + savefile->WriteStaticObject ( physicsObj ); +} + +/* +================ +rvVehicleRigid::Restore +================ +*/ +void rvVehicleRigid::Restore ( idRestoreGame *savefile ) { + + savefile->ReadVec3 ( storedVelocity ); // cnicholson: Added unrestored var + + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +===================== +rvVehicleRigid::SkipImpulse +===================== +*/ +bool rvVehicleRigid::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/source/mpgame/vehicle/VehicleRigid.h b/source/mpgame/vehicle/VehicleRigid.h new file mode 100644 index 0000000..eba3ea5 --- /dev/null +++ b/source/mpgame/vehicle/VehicleRigid.h @@ -0,0 +1,45 @@ +//---------------------------------------------------------------- +// VehicleRigid.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLERIGID_H__ +#define __GAME_VEHICLERIGID_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleRigid : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleRigid ); + + rvVehicleRigid ( void ); + ~rvVehicleRigid ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( void ); + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedVelocity; + // end twhitaker: + + idPhysics_RigidBody physicsObj; // physics object +}; + +#endif // __GAME_VEHICLERIGID_H__ diff --git a/source/mpgame/vehicle/VehicleSpline.cpp b/source/mpgame/vehicle/VehicleSpline.cpp new file mode 100644 index 0000000..169e3c2 --- /dev/null +++ b/source/mpgame/vehicle/VehicleSpline.cpp @@ -0,0 +1,123 @@ +//---------------------------------------------------------------- +// VehicleSplineCoupling.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleSpline.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleSpline ) + EVENT( EV_PostSpawn, rvVehicleSpline::Event_PostSpawn ) + EVENT( EV_SetSpline, rvVehicleSpline::Event_SetSpline ) + EVENT( EV_DoneMoving, rvVehicleSpline::Event_DoneMoving ) +END_CLASS + +rvVehicleSpline::rvVehicleSpline ( void ) { +} + +rvVehicleSpline::~rvVehicleSpline ( void ) { + SetPhysics( NULL ); +} + +void rvVehicleSpline::Spawn ( void ) { + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat( "accel", "200" ) ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat( "decel", "200" ) ); + + viewAxis = idAngles( 0, spawnArgs.GetFloat( "angle" ) , 0 ).ToMat3(); + + physicsObj.SetAxis( GetPhysics()->GetAxis() * viewAxis ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + BecomeActive( TH_THINK ); + + accelWithStrafe = Sign( spawnArgs.GetFloat("accel_strafe") ); + + idealSpeed = spawnArgs.GetFloat( "speed", "200" ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleSpline::Save +================ +*/ +void rvVehicleSpline::Save ( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteMat3( angleOffset ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( accelWithStrafe ); +} + +/* +================ +rvVehicleSpline::Restore +================ +*/ +void rvVehicleSpline::Restore ( idRestoreGame *savefile ) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadMat3( angleOffset ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( accelWithStrafe ); +} + +void rvVehicleSpline::Think( void ) { + float moveAmount = 0.0f; + + if ( positions[0].IsOccupied ( ) && !IsFrozen () && IsMovementEnabled ( ) ) { + + if ( accelWithStrafe != 0.0f ) { + moveAmount = positions[0].mInputCmd.rightmove * accelWithStrafe; + } else { + moveAmount = positions[0].mInputCmd.forwardmove; + } + + moveAmount = Sign( moveAmount ); + } + + physicsObj.SetSpeed( idealSpeed * moveAmount ); + + rvVehicle::Think( ); +} + +void rvVehicleSpline::Event_PostSpawn( void ) { + idStr splinePath; + + if ( spawnArgs.GetString( "spline_path", "", splinePath ) ) { + Event_SetSpline( gameLocal.FindEntity( splinePath.c_str() ) ); + } else { + gameLocal.Warning( "\"spline_path\" key not found on %s (rvVehicleSpline)", this->GetName() ); + } +} + +void rvVehicleSpline::Event_SetSpline ( idEntity * spline ) { + if ( spline && spline->IsType( idSplinePath::GetClassType() ) ) { + physicsObj.SetSplineEntity( static_cast< idSplinePath * >( spline ) ); + //HACK: force an intitial physics update so that the object orients itself with the spline + //TEMP: this should be fixed after the build on friday (Jan 28, 2005) + physicsObj.SetSpeed( 0.1f ); + RunPhysics(); + //END HACK + } +} + +void rvVehicleSpline::Event_DoneMoving() { + idThread::ReturnInt( true ); +} diff --git a/source/mpgame/vehicle/VehicleSpline.h b/source/mpgame/vehicle/VehicleSpline.h new file mode 100644 index 0000000..2ab006b --- /dev/null +++ b/source/mpgame/vehicle/VehicleSpline.h @@ -0,0 +1,34 @@ +//---------------------------------------------------------------- +// VehicleSpline.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESPLINECOUPLING_H__ +#define __GAME_VEHICLESPLINECOUPLING_H__ + +class rvVehicleSpline : public rvVehicle { +public: + CLASS_PROTOTYPE( rvVehicleSpline ); + + rvVehicleSpline ( void ); + ~rvVehicleSpline ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Think ( void ); + + void Event_PostSpawn ( void ); + void Event_SetSpline ( idEntity * spline ); + void Event_DoneMoving ( void ); + +protected: + rvPhysics_Spline physicsObj; + idMat3 angleOffset; + float idealSpeed; + float accelWithStrafe; +}; + +#endif // __GAME_VEHICLESPLINECOUPLING_H__ diff --git a/source/mpgame/vehicle/VehicleStatic.cpp b/source/mpgame/vehicle/VehicleStatic.cpp new file mode 100644 index 0000000..0ccb901 --- /dev/null +++ b/source/mpgame/vehicle/VehicleStatic.cpp @@ -0,0 +1,118 @@ +//---------------------------------------------------------------- +// VehicleStatic.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleStatic ) + EVENT( AI_ScriptedAnim, rvVehicleStatic::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleStatic::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleStatic::Event_ScriptedStop ) +END_CLASS + +rvVehicleStatic::rvVehicleStatic ( void ) { +} + +rvVehicleStatic::~rvVehicleStatic ( void ) { +} + +/* +================ +rvVehicleStatic::Spawn +================ +*/ +void rvVehicleStatic::Spawn( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleStatic::AddDriver +================ +*/ +int rvVehicleStatic::AddDriver ( int position, idActor* driver ) { + int pos = rvVehicle::AddDriver( position, driver ); + + if( pos < 0 ) { + return pos; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "hideGunInfo" ); + } + + return pos; +} + +/* +================ +rvVehicleStatic::RemoveDriver +================ +*/ +bool rvVehicleStatic::RemoveDriver ( int position, bool force ) { + bool result = rvVehicle::RemoveDriver( position, force ); + + if( !result ) { + return result; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "showGunInfo" ); + } + + return result; +} + +/* +================ +rvVehicleStatic::UpdateHUD +================ +*/ +void rvVehicleStatic::UpdateHUD( idActor* driver, idUserInterface* gui ) { + if( driver && driver->IsType( idPlayer::GetClassType() ) ) { + static_cast(driver)->UpdateHudStats( gui ); + } +} + +/* +================ +rvVehicleStatic::Event_ScriptedAnim +================ +*/ +void rvVehicleStatic::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + vfl.scripted = true; +} + +/* +================ +rvVehicleStatic::Event_ScriptedDone +================ +*/ +void rvVehicleStatic::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleStatic::Event_ScriptedStop +================ +*/ +void rvVehicleStatic::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + PlayCycle( ANIMCHANNEL_TORSO, spawnArgs.GetString( "idle" ), 2 ); + } +} diff --git a/source/mpgame/vehicle/VehicleStatic.h b/source/mpgame/vehicle/VehicleStatic.h new file mode 100644 index 0000000..7a0d861 --- /dev/null +++ b/source/mpgame/vehicle/VehicleStatic.h @@ -0,0 +1,35 @@ +//---------------------------------------------------------------- +// VehicleStatic.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESTATIC_H__ +#define __GAME_VEHICLESTATIC_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleStatic : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleStatic ); + + rvVehicleStatic ( void ); + ~rvVehicleStatic ( void ); + + void Spawn ( void ); + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); +}; + +#endif // __GAME_VEHICLESTATIC_H__ diff --git a/source/mpgame/vehicle/Vehicle_DropPod.cpp b/source/mpgame/vehicle/Vehicle_DropPod.cpp new file mode 100644 index 0000000..bc72e9e --- /dev/null +++ b/source/mpgame/vehicle/Vehicle_DropPod.cpp @@ -0,0 +1,177 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +class rvVehicleDropPod : public rvVehicleStatic { +public: + + CLASS_PROTOTYPE( rvVehicleDropPod ); + + rvVehicleDropPod ( void ); + + void Spawn ( void ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleDropPod ); +}; + +CLASS_DECLARATION( rvVehicleStatic, rvVehicleDropPod ) + EVENT( AI_ScriptedAnim, rvVehicleDropPod::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleDropPod::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleDropPod::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleDropPod::rvVehicleDropPod +================ +*/ +rvVehicleDropPod::rvVehicleDropPod ( void ) { +} + +/* +================ +rvVehicleDropPod::Spawn +================ +*/ +void rvVehicleDropPod::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleDropPod ) + STATE ( "State_Idle", rvVehicleDropPod::State_Idle ) + STATE ( "State_IdleThink", rvVehicleDropPod::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleDropPod::State_IdleOffline ) + + STATE ( "State_ScriptedAnim", rvVehicleDropPod::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleDropPod::State_IdleOffline +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 0 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_Idle +================ +*/ +stateResult_t rvVehicleDropPod::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_IdleThink +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", parms.blendFrames ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleDropPod::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleDropPod::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedAnim +================ +*/ +void rvVehicleDropPod::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedDone +================ +*/ +void rvVehicleDropPod::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleDropPod::Event_ScriptedStop +================ +*/ +void rvVehicleDropPod::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 1 ); + } +} + +/* +================ +rvVehicleDropPod::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleDropPod::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { +// if ( GetBindMaster() ) { +// axis = viewAxis; +// origin.Zero(); +// return true; +// } + return false; +} + diff --git a/source/mpgame/vehicle/Vehicle_Walker.cpp b/source/mpgame/vehicle/Vehicle_Walker.cpp new file mode 100644 index 0000000..66192ff --- /dev/null +++ b/source/mpgame/vehicle/Vehicle_Walker.cpp @@ -0,0 +1,593 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +class rvVehicleWalker : public rvVehicleAnimated { +public: + + CLASS_PROTOTYPE( rvVehicleWalker ); + + rvVehicleWalker ( void ); + + void Think ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void UpdateState ( void ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + + const char* stopAnimName; + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + +private: + void HandleStrafing ( void ); + + + stateResult_t Frame_ForwardRight ( int ); + stateResult_t Frame_ForwardLeft ( int ); + stateResult_t Frame_BackwardRight ( int ); + stateResult_t Frame_BackwardLeft ( int ); + + stateResult_t State_Wait_OnlineAnim ( const stateParms_t& parms ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_Offline ( const stateParms_t& parms ); + stateResult_t State_Online ( const stateParms_t& parms ); + + stateResult_t State_ForwardStart ( const stateParms_t& parms ); + stateResult_t State_Forward ( const stateParms_t& parms ); + stateResult_t State_BackwardStart ( const stateParms_t& parms ); + stateResult_t State_Backward ( const stateParms_t& parms ); + stateResult_t State_Stop ( const stateParms_t& parms ); + stateResult_t State_Turn ( const stateParms_t& parms ); + stateResult_t State_TurnThink ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleWalker ); +}; + +CLASS_DECLARATION( rvVehicleAnimated, rvVehicleWalker ) + EVENT( AI_ScriptedAnim, rvVehicleWalker::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleWalker::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleWalker::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleWalker::rvVehicleWalker +================ +*/ +rvVehicleWalker::rvVehicleWalker ( void ) { + stopAnimName = ""; +} + +/* +================ +rvVehicleWalker::Think +================ +*/ +void rvVehicleWalker::Think ( void ) { + rvVehicleAnimated::Think(); + + if ( !HasDrivers() || IsStalled() ) { + return; + } + + idVec3 delta; + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( delta.LengthSqr() > 0.1f ) { + gameLocal.RadiusDamage( GetOrigin(), this, this, this, this, spawnArgs.GetString( "def_stompDamage", "damage_Smallexplosion" ) ); + } +} + +/* +================ +rvVehicleWalker::Spawn +================ +*/ +void rvVehicleWalker::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +================ +rvVehicleWalker::Save +================ +*/ +void rvVehicleWalker::Save ( idSaveGame *savefile ) const { + savefile->WriteString( stopAnimName ); +} + +/* +================ +rvVehicleWalker::Restore +================ +*/ +void rvVehicleWalker::Restore ( idRestoreGame *savefile ) { + //twhitaker: I just happened to see this, while going through this code which I originally wrote (at 3am or so). + //TODO: fix this. Make stopAnimName an idStr? + idStr str; + savefile->ReadString( str ); + stopAnimName = str; +} + +/* +================ +rvVehicleWalker::UpdateState +================ +*/ +void rvVehicleWalker::UpdateState ( void ) { + rvVehiclePosition& pos = positions[0]; + usercmd_t& cmd = pos.mInputCmd; + + vfl.driver = pos.IsOccupied(); + vfl.forward = (vfl.driver && cmd.forwardmove > 0); + vfl.backward = (vfl.driver && cmd.forwardmove < 0); + vfl.right = (vfl.driver && cmd.rightmove < 0); + vfl.left = (vfl.driver && cmd.rightmove > 0); + vfl.strafe = (vfl.driver && cmd.buttons & BUTTON_STRAFE ); + + if ( g_vehicleMode.GetInteger() != 0 ) { + vfl.strafe = !vfl.strafe; + } +} + +/* +================ +rvVehicleWalker::SetInput +================ +*/ +void rvVehicleWalker::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + usercmd_t* pcmd = const_cast( &cmd ); + pcmd->rightmove *= -1; + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +/* +================ +rvVehicleWalker::HandleStrafing +================ +*/ +void rvVehicleWalker::HandleStrafing ( void ) { + if ( vfl.right ) { + additionalDelta -= spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } + if ( vfl.left ) { + additionalDelta += spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } +} + +// mekberg: overloaded this because physics bounds code is significantly different +/* +===================== +rvVehicleWalker::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicleWalker::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, GetPhysics()->GetAxis() ); + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleWalker ) + STATE ( "ForwardLeft", rvVehicleWalker::Frame_ForwardLeft ) + STATE ( "ForwardRight", rvVehicleWalker::Frame_ForwardRight ) + STATE ( "BackwardLeft", rvVehicleWalker::Frame_BackwardLeft ) + STATE ( "BackwardRight", rvVehicleWalker::Frame_BackwardRight ) + + STATE ( "Wait_OnlineAnim", rvVehicleWalker::State_Wait_OnlineAnim ) + + STATE ( "State_Idle", rvVehicleWalker::State_Idle ) + STATE ( "State_IdleThink", rvVehicleWalker::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleWalker::State_IdleOffline ) + STATE ( "State_Offline", rvVehicleWalker::State_Offline ) + STATE ( "State_Online", rvVehicleWalker::State_Online ) + + STATE ( "State_ForwardStart", rvVehicleWalker::State_ForwardStart ) + STATE ( "State_Forward", rvVehicleWalker::State_Forward ) + STATE ( "State_BackwardStart", rvVehicleWalker::State_BackwardStart ) + STATE ( "State_Backward", rvVehicleWalker::State_Backward ) + STATE ( "State_Stop", rvVehicleWalker::State_Stop ) + STATE ( "State_Turn", rvVehicleWalker::State_Turn ) + STATE ( "State_TurnThink", rvVehicleWalker::State_TurnThink ) + STATE ( "State_ScriptedAnim", rvVehicleWalker::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleWalker::State_IdleOffline +================ +*/ +stateResult_t rvVehicleWalker::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle_offline", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Online", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Online +================ +*/ +stateResult_t rvVehicleWalker::State_Online ( const stateParms_t& parms ) { + vfl.frozen = false; + + PlayAnim ( ANIMCHANNEL_LEGS, "start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_OnlineAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 4 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Offline +================ +*/ +stateResult_t rvVehicleWalker::State_Offline ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "stop", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 4 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleThink", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver || vfl.stalled ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Offline", parms.blendFrames ); + return SRESULT_DONE; + } + + if ( IsMovementEnabled ( ) ) { + if ( vfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_ForwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_BackwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.right || vfl.left ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 2 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_ForwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_ForwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "forward_start", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Forward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Forward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.forward ) { + stopAnimName = "forward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 4 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "forward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_BackwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_BackwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "backward_start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Backward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Backward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.backward ) { + stopAnimName = "backward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "backward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_Stop +================ +*/ +stateResult_t rvVehicleWalker::State_Stop ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, stopAnimName, parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Turn +================ +*/ +stateResult_t rvVehicleWalker::State_Turn ( const stateParms_t& parms ) { + if ( vfl.left ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_left", parms.blendFrames ); + } else if ( vfl.right ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_right", parms.blendFrames ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "State_TurnThink", 16 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_TurnThink +================ +*/ +stateResult_t rvVehicleWalker::State_TurnThink ( const stateParms_t& parms ) { + // If moving again bail on the turn, reguardless of whether its in mid animation + if ( vfl.forward || vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + // If the animation is done then repeat + if ( AnimDone ( ANIMCHANNEL_LEGS, 8 ) ){ + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 8 ); + return SRESULT_DONE; + } + + HandleStrafing(); + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::Frame_ForwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardLeft ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_ForwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardRight ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + + +/* +================ +rvVehicleWalker::Frame_BackwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardLeft ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_BackwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardRight ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::State_Wait_OnlineAnim +================ +*/ +stateResult_t rvVehicleWalker::State_Wait_OnlineAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) && vfl.driver ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleWalker::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::Event_ScriptedAnim +================ +*/ +void rvVehicleWalker::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleWalker::Event_ScriptedDone +================ +*/ +void rvVehicleWalker::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleWalker::Event_ScriptedStop +================ +*/ +void rvVehicleWalker::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + } +} diff --git a/source/mpgame/weapon/WeaponBlaster.cpp b/source/mpgame/weapon/WeaponBlaster.cpp new file mode 100644 index 0000000..e6c182b --- /dev/null +++ b/source/mpgame/weapon/WeaponBlaster.cpp @@ -0,0 +1,487 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +#define BLASTER_SPARM_CHARGEGLOW 6 + +class rvWeaponBlaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponBlaster ); + + rvWeaponBlaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + bool UpdateAttack ( void ); + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + int chargeTime; + int chargeDelay; + idVec2 chargeGlow; + bool fireForced; + int fireHeldTime; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Charge ( const stateParms_t& parms ); + stateResult_t State_Charged ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponBlaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponBlaster ) +END_CLASS + +/* +================ +rvWeaponBlaster::rvWeaponBlaster +================ +*/ +rvWeaponBlaster::rvWeaponBlaster ( void ) { +} + +/* +================ +rvWeaponBlaster::UpdateFlashlight +================ +*/ +bool rvWeaponBlaster::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponBlaster::Flashlight +================ +*/ +void rvWeaponBlaster::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +================ +rvWeaponBlaster::UpdateAttack +================ +*/ +bool rvWeaponBlaster::UpdateAttack ( void ) { + // Clear fire forced + if ( fireForced ) { + if ( !wsfl.attack ) { + fireForced = false; + } else { + return false; + } + } + + // If the player is pressing the fire button and they have enough ammo for a shot + // then start the shooting process. + if ( wsfl.attack && gameLocal.time >= nextAttackTime ) { + // Save the time which the fire button was pressed + if ( fireHeldTime == 0 ) { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + fireHeldTime = gameLocal.time; + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + } + } + + // If they have the charge mod and they have overcome the initial charge + // delay then transition to the charge state. + if ( fireHeldTime != 0 ) { + if ( gameLocal.time - fireHeldTime > chargeDelay ) { + SetState ( "Charge", 4 ); + return true; + } + + // If the fire button was let go but was pressed at one point then + // release the shot. + if ( !wsfl.attack ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if( player ) { + + if( player->GuiActive()) { + //make sure the player isn't looking at a gui first + SetState ( "Lower", 0 ); + } else { + SetState ( "Fire", 0 ); + } + } + return true; + } + } + + return false; +} + +/* +================ +rvWeaponBlaster::Spawn +================ +*/ +void rvWeaponBlaster::Spawn ( void ) { + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + SetState ( "Raise", 0 ); + + chargeGlow = spawnArgs.GetVec2 ( "chargeGlow" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargeTime" ) ); + chargeDelay = SEC2MS ( spawnArgs.GetFloat ( "chargeDelay" ) ); + + fireHeldTime = 0; + fireForced = false; + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponBlaster::Save +================ +*/ +void rvWeaponBlaster::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( chargeDelay ); + savefile->WriteVec2 ( chargeGlow ); + savefile->WriteBool ( fireForced ); + savefile->WriteInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::Restore +================ +*/ +void rvWeaponBlaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( chargeDelay ); + savefile->ReadVec2 ( chargeGlow ); + savefile->ReadBool ( fireForced ); + savefile->ReadInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::PreSave +================ +*/ +void rvWeaponBlaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponBlaster::PostSave +================ +*/ +void rvWeaponBlaster::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponBlaster ) + STATE ( "Raise", rvWeaponBlaster::State_Raise ) + STATE ( "Lower", rvWeaponBlaster::State_Lower ) + STATE ( "Idle", rvWeaponBlaster::State_Idle) + STATE ( "Charge", rvWeaponBlaster::State_Charge ) + STATE ( "Charged", rvWeaponBlaster::State_Charged ) + STATE ( "Fire", rvWeaponBlaster::State_Fire ) + STATE ( "Flashlight", rvWeaponBlaster::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponBlaster::State_Raise +================ +*/ +stateResult_t rvWeaponBlaster::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Lower +================ +*/ +stateResult_t rvWeaponBlaster::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Idle +================ +*/ +stateResult_t rvWeaponBlaster::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + if ( UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charge +================ +*/ +stateResult_t rvWeaponBlaster::State_Charge ( const stateParms_t& parms ) { + enum { + CHARGE_INIT, + CHARGE_WAIT, + }; + switch ( parms.stage ) { + case CHARGE_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + StartSound ( "snd_charge", SND_CHANNEL_ITEM, 0, false, NULL ); + PlayCycle( ANIMCHANNEL_ALL, "charging", parms.blendFrames ); + return SRESULT_STAGE ( CHARGE_WAIT ); + + case CHARGE_WAIT: + if ( gameLocal.time - fireHeldTime < chargeTime ) { + float f; + f = (float)(gameLocal.time - fireHeldTime) / (float)chargeTime; + f = chargeGlow[0] + f * (chargeGlow[1] - chargeGlow[0]); + f = idMath::ClampFloat ( chargeGlow[0], chargeGlow[1], f ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, f ); + + if ( !wsfl.attack ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + SetState ( "Charged", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charged +================ +*/ +stateResult_t rvWeaponBlaster::State_Charged ( const stateParms_t& parms ) { + enum { + CHARGED_INIT, + CHARGED_WAIT, + }; + switch ( parms.stage ) { + case CHARGED_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 1.0f ); + + StopSound ( SND_CHANNEL_ITEM, false ); + StartSound ( "snd_charge_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound ( "snd_charge_click", SND_CHANNEL_BODY, 0, false, NULL ); + return SRESULT_STAGE(CHARGED_WAIT); + + case CHARGED_WAIT: + if ( !wsfl.attack ) { + fireForced = true; + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Fire +================ +*/ +stateResult_t rvWeaponBlaster::State_Fire ( const stateParms_t& parms ) { + enum { + FIRE_INIT, + FIRE_WAIT, + }; + switch ( parms.stage ) { + case FIRE_INIT: + + StopSound ( SND_CHANNEL_ITEM, false ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + //don't fire if we're targeting a gui. + idPlayer* player; + player = gameLocal.GetLocalPlayer(); + + //make sure the player isn't looking at a gui first + if( player && player->GuiActive() ) { + fireHeldTime = 0; + SetState ( "Lower", 0 ); + return SRESULT_DONE; + } + + if( player && !player->CanFire() ) { + fireHeldTime = 0; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + + + + if ( gameLocal.time - fireHeldTime > chargeTime ) { + Attack ( true, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_chargedflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "chargedfire", parms.blendFrames ); + } else { + Attack ( false, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_normalflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + fireHeldTime = 0; + + return SRESULT_STAGE(FIRE_WAIT); + + case FIRE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) || UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Flashlight +================ +*/ +stateResult_t rvWeaponBlaster::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/weapon/WeaponDarkMatterGun.cpp b/source/mpgame/weapon/WeaponDarkMatterGun.cpp new file mode 100644 index 0000000..39fd58b --- /dev/null +++ b/source/mpgame/weapon/WeaponDarkMatterGun.cpp @@ -0,0 +1,470 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../Projectile.h" + +class rvWeaponDarkMatterGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponDarkMatterGun ); + + rvWeaponDarkMatterGun ( void ); + ~rvWeaponDarkMatterGun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + enum darkMatterRing_t { + RING_OUTER, + RING_MIDDLE, + RING_INNER, + RING_MAX + }; + + struct rings_s { + idAngles angularVelocity; + jointHandle_t joint; + }; + rings_s rings[ RING_MAX ]; + + int nextRotateTime; + int ringStartTime; + int chargeDuration; + bool clientReload; + rvClientEffectPtr coreEffect; + rvClientEffectPtr coreStartEffect; + jointHandle_t jointCore; + + void InitRing ( darkMatterRing_t ring, const char* name ); + void StartRings ( bool chargeUp ); + void StopRings ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponDarkMatterGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponDarkMatterGun ) +END_CLASS + +/* +================ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun ( void ) { + coreStartEffect = NULL; + coreEffect = NULL; + ringStartTime = -1; + clientReload = false; +} + +/* +================ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun ( void ) { + StopRings ( ); +} + +/* +================ +rvWeaponDarkMatterGun::Spawn +================ +*/ +void rvWeaponDarkMatterGun::Spawn ( void ) { + SetState ( "Raise", 0 ); + + InitRing ( RING_OUTER, "outer" ); + InitRing ( RING_INNER, "inner" ); + InitRing ( RING_MIDDLE, "middle" ); + + nextRotateTime = 0; + + chargeDuration = SEC2MS ( spawnArgs.GetFloat ( "chargeDuration", ".5" ) ); + + jointCore = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_core" ) ); +} + +/* +================ +rvWeaponDarkMatterGun::Save +================ +*/ +void rvWeaponDarkMatterGun::Save ( idSaveGame *savefile ) const { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->WriteAngles ( rings[ i ].angularVelocity ); + savefile->WriteJoint ( rings[ i ].joint ); + } + savefile->WriteInt ( nextRotateTime ); + savefile->WriteInt ( ringStartTime ); + savefile->WriteInt ( chargeDuration ); + savefile->WriteObject( coreEffect.GetEntity() ); + savefile->WriteObject( coreStartEffect.GetEntity() ); + savefile->WriteJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::Restore +================ +*/ +void rvWeaponDarkMatterGun::Restore ( idRestoreGame *savefile ) { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->ReadAngles ( rings[ i ].angularVelocity ); + savefile->ReadJoint ( rings[ i ].joint ); + } + savefile->ReadInt ( nextRotateTime ); + savefile->ReadInt ( ringStartTime ); + savefile->ReadInt ( chargeDuration ); + savefile->ReadObject( reinterpret_cast( coreEffect ) ); + savefile->ReadObject( reinterpret_cast( coreStartEffect ) ); + savefile->ReadJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::PreSave +================ +*/ +void rvWeaponDarkMatterGun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + +} + +/* +================ +rvWeaponDarkMatterGun::PostSave +================ +*/ +void rvWeaponDarkMatterGun::PostSave ( void ) { + + //start the ring sounds + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); +} + + +/* +================ +rvWeaponDarkMatterGun::InitRing +================ +*/ +void rvWeaponDarkMatterGun::InitRing ( darkMatterRing_t ring, const char* name ) { + rings[ring].angularVelocity = spawnArgs.GetAngles ( va("ring_%s_velocity", name ) ); + rings[ring].joint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("ring_%s_joint", name ) ) ); +} + +/* +================ +rvWeaponDarkMatterGun::StartRings +================ +*/ +void rvWeaponDarkMatterGun::StartRings ( bool chargeUp ) { + int i; + + if ( ringStartTime == -1 ) { + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); + ringStartTime = gameLocal.time; + } + + if ( chargeUp ) { + coreStartEffect = viewModel->PlayEffect( "fx_core_start", jointCore ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, chargeDuration / 2 ); + } + } else if ( !coreEffect ) { + coreEffect = viewModel->PlayEffect( "fx_core", jointCore, true ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, 0 ); + } + } +} + +/* +================ +rvWeaponDarkMatterGun::StopRings +================ +*/ +void rvWeaponDarkMatterGun::StopRings ( void ) { + int i; + + if ( !viewModel ) { + return; + } + + viewModel->StopSound ( SND_CHANNEL_VOICE, false ); + + if ( coreEffect ) { + coreEffect->Stop ( ); + coreEffect = NULL; + } + + if ( coreStartEffect ) { + coreStartEffect->Stop ( ); + coreStartEffect = NULL; + } + + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->ClearJoint ( rings[i].joint ); + } + + ringStartTime = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponDarkMatterGun ) + STATE ( "Idle", rvWeaponDarkMatterGun::State_Idle) + STATE ( "Fire", rvWeaponDarkMatterGun::State_Fire ) + STATE ( "Reload", rvWeaponDarkMatterGun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponDarkMatterGun::State_Idle +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + // Auto reload? + if ( !AmmoInClip ( ) && AmmoAvailable () && !clientReload ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + clientReload = false; + + StartRings ( false ); + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload ) { + if ( owner->entityNumber != gameLocal.localClientNum ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } else { + wsfl.netReload = false; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Fire +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopRings ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 2 ) || (gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Reload +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + StartRings ( true ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + clientReload = true; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + AddToClip ( ClipSize() ); + SetStatus ( WP_READY ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + rvDarkMatterProjectile + +=============================================================================== +*/ + +class rvDarkMatterProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDarkMatterProjectile ); + + rvDarkMatterProjectile ( void ); + ~rvDarkMatterProjectile ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + +protected: + + int nextDamageTime; + const idDict* radiusDamageDef; +}; + +CLASS_DECLARATION( idProjectile, rvDarkMatterProjectile ) +END_CLASS + +/* +================ +rvDarkMatterProjectile::rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::rvDarkMatterProjectile ( void ) { + radiusDamageDef = NULL; +} + +/* +================ +rvDarkMatterProjectile::~rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::~rvDarkMatterProjectile ( void ) { +} + +/* +================ +rvDarkMatterProjectile::Spawn +================ +*/ +void rvDarkMatterProjectile::Spawn ( void ) { + nextDamageTime = 0; + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Save +================ +*/ +void rvDarkMatterProjectile::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( nextDamageTime ); +} + +/* +================ +rvDarkMatterProjectile::Restore +================ +*/ +void rvDarkMatterProjectile::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( nextDamageTime ); + + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Think +================ +*/ +void rvDarkMatterProjectile::Think ( void ) { + physicsObj.SetClipMask( MASK_DMGSOLID ); + idProjectile::Think ( ); + + if ( gameLocal.time > nextDamageTime ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, owner, owner, NULL, spawnArgs.GetString( "def_radius_damage" ), 1.0f, &hitCount ); + nextDamageTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "damageRate", ".05" ) ); + } +} + + diff --git a/source/mpgame/weapon/WeaponGauntlet.cpp b/source/mpgame/weapon/WeaponGauntlet.cpp new file mode 100644 index 0000000..6c79b03 --- /dev/null +++ b/source/mpgame/weapon/WeaponGauntlet.cpp @@ -0,0 +1,515 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGauntlet : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGauntlet ); + + rvWeaponGauntlet( void ); + ~rvWeaponGauntlet( void ); + + virtual void Spawn ( void ); + virtual void CleanupWeapon ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idAngles bladeSpinFast; + idAngles bladeSpinSlow; + jointHandle_t bladeJoint; + jointHandle_t bladeJoint_world; + int bladeAccel; + + float range; + + rvClientEffectPtr impactEffect; + int impactMaterial; + + void Attack ( void ); + void StartBlade ( void ); + void StopBlade ( void ); + +private: + + void PlayLoopSound ( int sndType ); + int loopSound; + enum { + LOOP_NONE, + LOOP_WALL, + LOOP_FLESH + }; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponGauntlet ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGauntlet ) +END_CLASS + +/* +================ +rvWeaponGauntlet::rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::rvWeaponGauntlet( void ) { + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::~rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::~rvWeaponGauntlet(void) +{ + if ( viewModel ) { + StopSound( SND_CHANNEL_WEAPON, false ); + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + +} +/* +================ +rvWeaponGauntlet::Spawn +================ +*/ +void rvWeaponGauntlet::Spawn ( void ) { + SetState ( "Raise", 0 ); + + bladeJoint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + bladeJoint_world= GetWorldModel()->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + + bladeSpinFast = spawnArgs.GetAngles ( "blade_spinfast" ); + bladeSpinSlow = spawnArgs.GetAngles ( "blade_spinslow" ); + bladeAccel = SEC2MS ( spawnArgs.GetFloat ( "blade_accel", ".25" ) ); + + range = spawnArgs.GetFloat ( "range", "32" ); + + impactMaterial = -1; + impactEffect = NULL; + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::Save +================ +*/ +void rvWeaponGauntlet::Save ( idSaveGame *savefile ) const { + savefile->WriteAngles ( bladeSpinFast ); + savefile->WriteAngles ( bladeSpinSlow ); + savefile->WriteJoint ( bladeJoint ); + savefile->WriteJoint ( bladeJoint_world ); + + savefile->WriteInt ( bladeAccel ); + + savefile->WriteFloat ( range ); + + savefile->WriteObject ( impactEffect ); + savefile->WriteInt ( impactMaterial ); + savefile->WriteInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::Restore +================ +*/ +void rvWeaponGauntlet::Restore ( idRestoreGame *savefile ) { + savefile->ReadAngles ( bladeSpinFast ); + savefile->ReadAngles ( bladeSpinSlow ); + savefile->ReadJoint ( bladeJoint ); + savefile->ReadJoint ( bladeJoint_world ); + + savefile->ReadInt ( bladeAccel ); + + savefile->ReadFloat ( range ); + + savefile->ReadObject ( reinterpret_cast( impactEffect ) ); + savefile->ReadInt ( impactMaterial ); + savefile->ReadInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::PreSave +================ +*/ +void rvWeaponGauntlet::PreSave ( void ) { + +} + +/* +================ +rvWeaponGauntlet::PostSave +================ +*/ +void rvWeaponGauntlet::PostSave( void ) { +} + +void rvWeaponGauntlet::PlayLoopSound( int sndType ) { + if ( loopSound == sndType ) { + return; + } + const char *loopSoundString = NULL; + switch ( sndType ) { + case LOOP_NONE: + default: + loopSoundString = "snd_spin_loop"; + break; + case LOOP_WALL: + loopSoundString = "snd_spin_wall"; + break; + case LOOP_FLESH: + loopSoundString = "snd_spin_flesh"; + break; + } + if ( loopSoundString ) { + loopSound = sndType; + StartSound( loopSoundString, SND_CHANNEL_WEAPON, 0, false, 0 ); + } +} + +/* +================ +rvWeaponGauntlet::CleanupWeapon +================ +*/ +void rvWeaponGauntlet::CleanupWeapon( void ) { + + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); +} + +/* +================ +rvWeaponGauntlet::Attack +================ +*/ +void rvWeaponGauntlet::Attack ( void ) { + trace_t tr; + idEntity* ent; + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + owner->WeaponFireFeedback( &weaponDef->dict ); + + if ( tr.fraction >= 1.0f ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); + return; + } + + // Entity we hit? + ent = gameLocal.entities[tr.c.entityNum]; + + // If the impact material changed then stop the impact effect + if ( (tr.c.materialType && tr.c.materialType->Index ( ) != impactMaterial) || + (!tr.c.materialType && impactMaterial != -1) ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + } + + // In singleplayer-- the gauntlet never effects marine AI + if( !gameLocal.isMultiplayer ) { + idActor* actor_ent = 0; + + //ignore both the body and the head. + if (ent->IsType( idActor::GetClassType()) ) { + actor_ent = static_cast(ent); + } else if (ent->IsType ( idAFAttachment::GetClassType()) ) { + actor_ent = static_cast(ent->GetBindMaster()); + } + + if ( actor_ent && actor_ent->team == gameLocal.GetLocalPlayer()->team ) { + PlayLoopSound( LOOP_NONE ); + return; + } + } + + //multiplayer-- don't gauntlet dead stuff + if( gameLocal.isMultiplayer ) { + idPlayer * player; + if ( ent->IsType( idPlayer::GetClassType() )) { + player = static_cast< idPlayer* >(ent); + if (player->health <= 0) { + return; + } + } + + } + + if ( !impactEffect ) { + impactMaterial = tr.c.materialType ? tr.c.materialType->Index() : -1; + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_impact", tr.c.materialType ), tr.endpos, tr.c.normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( tr.endpos ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + } + + // Do damage? + if ( gameLocal.time > nextAttackTime ) { + if ( ent ) { + if ( ent->fl.takedamage ) { + float dmgScale = 1.0f; + dmgScale *= owner->PowerUpModifier( PMOD_MELEE_DAMAGE ); + ent->Damage ( owner, owner, playerViewAxis[0], spawnArgs.GetString ( "def_damage" ), dmgScale, 0 ); + StartSound( "snd_hit", SND_CHANNEL_ANY, 0, false, NULL ); + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + PlayLoopSound( LOOP_FLESH ); + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_NONE ); + } + nextAttackTime = gameLocal.time + fireRate; + } +} + +/* +================ +rvWeaponGauntlet::StartBlade +================ +*/ +void rvWeaponGauntlet::StartBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_ITEM, false ); +// StartSound ( "snd_blade_fast", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_spin_up", SND_CHANNEL_ITEM, 0, false, 0 ); +} + +/* +================ +rvWeaponGauntlet::StopBlade +================ +*/ +void rvWeaponGauntlet::StopBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_WEAPON, false ); +// StartSound ( "snd_blade_slow", SND_CHANNEL_ITEM, 0, false, NULL ); + + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGauntlet ) + STATE ( "Raise", rvWeaponGauntlet::State_Raise ) + STATE ( "Lower", rvWeaponGauntlet::State_Lower ) + STATE ( "Idle", rvWeaponGauntlet::State_Idle) + STATE ( "Fire", rvWeaponGauntlet::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponGauntlet::State_Raise +================ +*/ +stateResult_t rvWeaponGauntlet::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( RAISE_WAIT ); + case RAISE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Lower +================ +*/ +stateResult_t rvWeaponGauntlet::State_Lower( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "lower", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Idle +================ +*/ +stateResult_t rvWeaponGauntlet::State_Idle( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus( WP_READY ); + StopBlade(); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack ) { + SetState( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Fire +================ +*/ +stateResult_t rvWeaponGauntlet::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim( ANIMCHANNEL_ALL, "attack_start", parms.blendFrames ); + StartBlade(); + loopSound = LOOP_NONE; + // #32 - no gauntlet spin up + //return SRESULT_STAGE( STAGE_START_WAIT ); + return SRESULT_STAGE( STAGE_LOOP ); + + case STAGE_START_WAIT: + if ( !wsfl.attack ) { + return SRESULT_STAGE( STAGE_END ); + } + if ( AnimDone( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle( ANIMCHANNEL_ALL, "attack_loop", parms.blendFrames ); + StartSound( "snd_spin_loop", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE(STAGE_LOOP_WAIT); + + case STAGE_LOOP_WAIT: + if ( !wsfl.attack || wsfl.lowerWeapon ) { + return SRESULT_STAGE( STAGE_END ); + } + Attack(); + return SRESULT_WAIT; + + case STAGE_END: + PlayAnim( ANIMCHANNEL_ALL, "attack_end", parms.blendFrames ); + StopBlade(); + StartSound( "snd_spin_down", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( wsfl.attack || AnimDone( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + PostState( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/weapon/WeaponGrenadeLauncher.cpp b/source/mpgame/weapon/WeaponGrenadeLauncher.cpp new file mode 100644 index 0000000..c3ae03c --- /dev/null +++ b/source/mpgame/weapon/WeaponGrenadeLauncher.cpp @@ -0,0 +1,202 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGrenadeLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGrenadeLauncher ); + + rvWeaponGrenadeLauncher ( void ); + + virtual void Spawn ( void ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + const char* GetFireAnim() const { return (!AmmoInClip()) ? "fire_empty" : "fire"; } + const char* GetIdleAnim() const { return (!AmmoInClip()) ? "idle_empty" : "idle"; } + + CLASS_STATES_PROTOTYPE ( rvWeaponGrenadeLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGrenadeLauncher ) +END_CLASS + +/* +================ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher +================ +*/ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::Spawn +================ +*/ +void rvWeaponGrenadeLauncher::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponGrenadeLauncher::PreSave +================ +*/ +void rvWeaponGrenadeLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::PostSave +================ +*/ +void rvWeaponGrenadeLauncher::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGrenadeLauncher ) + STATE ( "Idle", rvWeaponGrenadeLauncher::State_Idle) + STATE ( "Fire", rvWeaponGrenadeLauncher::State_Fire ) + STATE ( "Reload", rvWeaponGrenadeLauncher::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponGrenadeLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, GetIdleAnim(), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, GetFireAnim(), 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/mpgame/weapon/WeaponHyperblaster.cpp b/source/mpgame/weapon/WeaponHyperblaster.cpp new file mode 100644 index 0000000..661b6d2 --- /dev/null +++ b/source/mpgame/weapon/WeaponHyperblaster.cpp @@ -0,0 +1,295 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int HYPERBLASTER_SPARM_BATTERY = 6; +const int HYPERBLASTER_SPIN_SPEED = 300; + +class rvWeaponHyperblaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponHyperblaster ); + + rvWeaponHyperblaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + jointHandle_t jointBatteryView; + bool spinning; + + void SpinUp ( void ); + void SpinDown ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponHyperblaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponHyperblaster ) +END_CLASS + +/* +================ +rvWeaponHyperblaster::rvWeaponHyperblaster +================ +*/ +rvWeaponHyperblaster::rvWeaponHyperblaster ( void ) { +} + +/* +================ +rvWeaponHyperblaster::Spawn +================ +*/ +void rvWeaponHyperblaster::Spawn ( void ) { + jointBatteryView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_battery" ) ); + spinning = false; + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponHyperblaster::Save +================ +*/ +void rvWeaponHyperblaster::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint ( jointBatteryView ); + savefile->WriteBool ( spinning ); +} + +/* +================ +rvWeaponHyperblaster::Restore +================ +*/ +void rvWeaponHyperblaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint ( jointBatteryView ); + savefile->ReadBool ( spinning ); +} + +/* +================ +rvWeaponHyperBlaster::PreSave +================ +*/ +void rvWeaponHyperblaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, false ); + StopSound( SND_CHANNEL_BODY, false ); + StopSound( SND_CHANNEL_ITEM, false ); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponHyperBlaster::PostSave +================ +*/ +void rvWeaponHyperblaster::PostSave ( void ) { +} + +/* +================ +rvWeaponHyperblaster::SpinUp +================ +*/ +void rvWeaponHyperblaster::SpinUp ( void ) { + if ( spinning ) { + return; + } + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,HYPERBLASTER_SPIN_SPEED,0), gameLocal.time, 50 ); + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spin", SND_CHANNEL_BODY2, 0, false, NULL ); + spinning = true; +} + +/* +================ +rvWeaponHyperblaster::SpinDown +================ +*/ +void rvWeaponHyperblaster::SpinDown ( void ) { + if ( !spinning ) { + return; + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spindown", SND_CHANNEL_BODY2, 0, false, NULL ); + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,0,0), gameLocal.time, 500 ); + } + + spinning = false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponHyperblaster ) + STATE ( "Idle", rvWeaponHyperblaster::State_Idle) + STATE ( "Fire", rvWeaponHyperblaster::State_Fire ) + STATE ( "Reload", rvWeaponHyperblaster::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponHyperblaster::State_Idle +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + SpinDown ( ); + + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Fire +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SpinUp ( ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( (!wsfl.attack || !AmmoInClip() || wsfl.lowerWeapon) && AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Reload +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SpinDown ( ); + + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 0 ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/mpgame/weapon/WeaponLightningGun.cpp b/source/mpgame/weapon/WeaponLightningGun.cpp new file mode 100644 index 0000000..911fec2 --- /dev/null +++ b/source/mpgame/weapon/WeaponLightningGun.cpp @@ -0,0 +1,983 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" +#include "../ai/AI_Manager.h" + +const int LIGHTNINGGUN_NUM_TUBES = 3; +const int LIGHTNINGGUN_MAX_PATHS = 3; + +const idEventDef EV_Lightninggun_RestoreHum( "", "" ); + +class rvLightningPath { +public: + idEntityPtr target; + idVec3 origin; + idVec3 normal; + rvClientEffectPtr trailEffect; + rvClientEffectPtr impactEffect; + + void StopEffects ( void ); + void UpdateEffects ( const idVec3& from, const idDict& dict ); + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvWeaponLightningGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponLightningGun ); + + rvWeaponLightningGun( void ); + ~rvWeaponLightningGun( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + virtual void ClientStale ( void ); + virtual void ClientUnstale( void ); + + void PreSave ( void ); + void PostSave ( void ); + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + bool NoFireWhileSwitching( void ) const { return true; } + +protected: + + void UpdateTubes ( void ); + + // Tube effects + rvClientEntityPtr tubeEffects[LIGHTNINGGUN_NUM_TUBES]; + idInterpolate tubeOffsets[LIGHTNINGGUN_NUM_TUBES]; + jointHandle_t tubeJoints[LIGHTNINGGUN_NUM_TUBES]; + float tubeMaxOffset; + float tubeThreshold; + int tubeTime; + + rvClientEntityPtr trailEffectView; + + int nextCrawlTime; + + float range; + jointHandle_t spireJointView; + jointHandle_t chestJointView; + + + rvLightningPath currentPath; + + // Chain lightning mod + idList chainLightning; + idVec3 chainLightningRange; + +private: + + void Attack ( idEntity* ent, const idVec3& dir, float power = 1.0f ); + + void UpdateChainLightning ( void ); + void StopChainLightning ( void ); + void UpdateEffects ( const idVec3& origin ); + void UpdateTrailEffect ( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view = false ); + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponLightningGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponLightningGun ) +EVENT( EV_Lightninggun_RestoreHum, rvWeaponLightningGun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponLightningGun::rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::rvWeaponLightningGun( void ) { +} + +/* +================ +rvWeaponLightningGun::~rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::~rvWeaponLightningGun( void ) { + int i; + + if ( trailEffectView ) { + trailEffectView->Stop( ); + } + currentPath.StopEffects( ); + StopChainLightning( ); + + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop( ); + } + } +} + +/* +================ +rvWeaponLightningGun::Spawn +================ +*/ +void rvWeaponLightningGun::Spawn( void ) { + int i; + + trailEffectView = NULL; + nextCrawlTime = 0; + + chainLightning.Clear( ); + + // get hitscan range for our firing + range = weaponDef->dict.GetFloat( "range", "10000" ); + + // Initialize tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeJoints[i] = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint_tube_%d",i), "" ) ); + tubeOffsets[i].Init ( gameLocal.time, 0, 0, 0 ); + } + + // Cache the max ammo for the weapon and the max tube offset + tubeMaxOffset = spawnArgs.GetFloat ( "tubeoffset" ); + tubeThreshold = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ) / (float)LIGHTNINGGUN_NUM_TUBES; + tubeTime = SEC2MS ( spawnArgs.GetFloat ( "tubeTime", ".25" ) ); + + spireJointView = viewModel->GetAnimator ( )->GetJointHandle ( "spire_1" ); + + if( gameLocal.GetLocalPlayer()) { + chestJointView = gameLocal.GetLocalPlayer()->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_hideGun_flash" ) ); + } else { + chestJointView = spireJointView; + } + + chainLightningRange = spawnArgs.GetVec2( "chainLightningRange", "150 300" ); + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponLightningGun::Save +================ +*/ +void rvWeaponLightningGun::Save ( idSaveGame* savefile ) const { + int i; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Save ( savefile ); + savefile->WriteInterpolate ( tubeOffsets[i] ); + savefile->WriteJoint ( tubeJoints[i] ); + } + savefile->WriteFloat ( tubeMaxOffset ); + savefile->WriteFloat ( tubeThreshold ); + savefile->WriteInt ( tubeTime ); + + // General + trailEffectView.Save ( savefile ); + savefile->WriteInt ( nextCrawlTime ); + savefile->WriteFloat ( range ); + savefile->WriteJoint ( spireJointView ); + savefile->WriteJoint ( chestJointView ); + + currentPath.Save ( savefile ); + + // Chain Lightning mod + savefile->WriteInt ( chainLightning.Num() ); + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].Save ( savefile ); + } + savefile->WriteVec3 ( chainLightningRange ); +} + +/* +================ +rvWeaponLightningGun::Restore +================ +*/ +void rvWeaponLightningGun::Restore ( idRestoreGame* savefile ) { + int i; + int num; + float f; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Restore ( savefile ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartTime ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetDuration ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartValue ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetEndValue ( f ); + savefile->ReadJoint ( tubeJoints[i] ); + } + savefile->ReadFloat ( tubeMaxOffset ); + savefile->ReadFloat ( tubeThreshold ); + savefile->ReadInt ( tubeTime ); + + // General + trailEffectView.Restore ( savefile ); + + savefile->ReadInt ( nextCrawlTime ); + savefile->ReadFloat ( range ); + savefile->ReadJoint ( spireJointView ); + savefile->ReadJoint ( chestJointView ); + + currentPath.Restore ( savefile ); + + // Chain lightning mod + savefile->ReadInt ( num ); + chainLightning.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + chainLightning[i].Restore ( savefile ); + } + savefile->ReadVec3 ( chainLightningRange ); + + +} + +/* +================ +rvWeaponLightningGun::Think +================ +*/ +void rvWeaponLightningGun::Think ( void ) { + trace_t tr; + + rvWeapon::Think(); + + UpdateTubes(); + + // If no longer firing or out of ammo then nothing to do in the think + if ( !wsfl.attack || !IsReady() || !AmmoAvailable() ) { + if ( trailEffectView ) { + trailEffectView->Stop ( ); + trailEffectView = NULL; + } + + currentPath.StopEffects(); + StopChainLightning(); + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// jshepard: allow projectile hits + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + (MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE), owner ); +// RAVEN END + // Calculate the direction of the lightning effect using the barrel joint of the weapon + // and the end point of the trace + idVec3 origin; + idMat3 axis; + + //fire from chest if show gun models is off. + if( !cvarSystem->GetCVarBool("ui_showGun")) { + GetGlobalJointTransform( true, chestJointView, origin, axis ); + } else { + GetGlobalJointTransform( true, barrelJointView, origin, axis ); + } + + // Cache the target we are hitting + currentPath.origin = tr.endpos; + currentPath.normal = tr.c.normal; + currentPath.target = gameLocal.entities[tr.c.entityNum]; + + UpdateChainLightning(); + + UpdateEffects( origin ); + + MuzzleFlash(); + + // Inflict damage on all targets being attacked + if ( !gameLocal.isClient && gameLocal.time >= nextAttackTime ) { + int i; + float power = 1.0f; + idVec3 dir; + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + + dir = tr.endpos - origin; + dir.Normalize ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( currentPath.target, dir, power ); + for ( i = 0; i < chainLightning.Num(); i ++, power *= 0.75f ) { + Attack ( chainLightning[i].target, chainLightning[i].normal, power ); + } + + statManager->WeaponFired( owner, owner->GetCurrentWeapon(), chainLightning.Num() + 1 ); + } + + // Play the lightning crawl effect every so often when doing damage + if ( gameLocal.time > nextCrawlTime ) { + nextCrawlTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat ( "crawlDelay", ".3" )); + } +} + +/* +================ +rvWeaponLightningGun::Attack +================ +*/ +void rvWeaponLightningGun::Attack ( idEntity* ent, const idVec3& dir, float power ) { + // Double check + if ( !ent || !ent->fl.takedamage ) { + return; + } + + // Start a lightning crawl effect every so often + // we don't synchronize it, so let's not show it in multiplayer for a listen host. also fixes seeing it on the host from other instances + if ( !gameLocal.isMultiplayer && gameLocal.time > nextCrawlTime ) { + if ( ent->IsType( idActor::GetClassType() ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( weaponDef->dict, "fx_crawl" ), ent, SEC2MS( spawnArgs.GetFloat ( "crawlTime", ".2" ) ) ); + effect->Play( gameLocal.time, false ); + } + } + +// RAVEN BEGIN +// mekberg: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, owner->GetCurrentWeapon() ); + } +// RAVEN END + ent->Damage( owner, owner, dir, spawnArgs.GetString ( "def_damage" ), power * owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ), 0 ); +} + +/* +================ +rvWeaponLightningGun::UpdateChainLightning +================ +*/ +void rvWeaponLightningGun::UpdateChainLightning ( void ) { + int i; + rvLightningPath* parent; + rvLightningPath* path; + idActor* target; + + // Chain lightning not enabled + if ( !chainLightningRange[0] ) { + return; + } + + // Need to have a primary target that is not on the same team for chain lightning to work + path = ¤tPath; + target = dynamic_cast(path->target.GetEntity()); + if ( !target || !target->health || target->team == owner->team ) { + StopChainLightning ( ); + return; + } + + currentPath.target->fl.takedamage = false; + + // Look through the chain lightning list and remove any paths that are no longer valid due + // to their range or visibility + for ( i = 0; i < chainLightning.Num(); i ++ ) { + parent = path; + path = &chainLightning[i]; + target = dynamic_cast(path->target.GetEntity()); + + // If the entity isnt valid anymore or is dead then remove it from the list + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Choose a destination origin the chain lightning path target + path->origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + path->origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); + path->normal = path->origin - parent->origin; + path->normal.Normalize(); + + // Make sure the entity is still within range of its parent + if ( (path->origin - parent->origin).LengthSqr ( ) > Square ( chainLightningRange[1] ) ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Trace to make sure we can still hit them + trace_t tr; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, parent->origin, path->origin, MASK_SHOT_RENDERMODEL, parent->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + path->origin = tr.endpos; + + // Temporarily disable taking damage to flag this entity is used + target->fl.takedamage = false; + } + + // Start path at the end of the current path + if ( chainLightning.Num () ) { + path = &chainLightning[chainLightning.Num()-1]; + } else { + path = ¤tPath; + } + + // Cap the number of chain lightning jumps + while ( chainLightning.Num ( ) + 1 < LIGHTNINGGUN_MAX_PATHS ) { + for ( target = aiManager.GetEnemyTeam ( (aiTeam_t)owner->team ); target; target = target->teamNode.Next() ) { + // Must be a valid entity that takes damage to chain lightning too + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + continue; + } + // Must be within starting chain path range + if ( (target->GetPhysics()->GetOrigin() - path->target->GetPhysics()->GetOrigin()).LengthSqr() > Square ( chainLightningRange[0] ) ) { + continue; + } + + // Make sure we can trace to the target from the current path + trace_t tr; + idVec3 origin; + origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, path->origin, origin, MASK_SHOT_RENDERMODEL, path->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + continue; + } + + path = &chainLightning.Alloc ( ); + path->target = target; + path->normal = tr.endpos - path->origin; + path->normal.Normalize(); + path->origin = tr.endpos; + + // Flag this entity to ensure it is skipped + target->fl.takedamage = false; + break; + } + // Found nothing? just break out early + if ( !target ) { + break; + } + } + + // Reset the take damage flag + currentPath.target->fl.takedamage = true; + for ( i = chainLightning.Num() - 1; i >= 0; i -- ) { + chainLightning[i].target->fl.takedamage = true; + } +} + +/* +================ +rvWeaponLightningGun::UpdateEffects +================ +*/ +void rvWeaponLightningGun::UpdateEffects( const idVec3& origin ) { + int i; + rvLightningPath* parent; + idVec3 dir; + + // Main path (world effects) + currentPath.UpdateEffects ( origin, weaponDef->dict ); + if ( currentPath.trailEffect ) { + currentPath.trailEffect->GetRenderEffect()->suppressSurfaceInViewID = owner->entityNumber + 1; + } + + // In view trail effect + dir = currentPath.origin - origin; + dir.Normalize(); + if ( !trailEffectView ) { + trailEffectView = gameLocal.PlayEffect ( gameLocal.GetEffect ( weaponDef->dict, "fx_trail" ), origin, dir.ToMat3(), true, currentPath.origin ); + } else { + trailEffectView->SetOrigin( origin ); + trailEffectView->SetAxis( dir.ToMat3() ); + trailEffectView->SetEndOrigin( currentPath.origin ); + } + if ( trailEffectView ) { + trailEffectView->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;; + } + + if ( !currentPath.target ) { + return; + } + + // Chain lightning effects + parent = ¤tPath; + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].UpdateEffects( parent->origin, weaponDef->dict ); + parent = &chainLightning[i]; + } +} + +/* +================ +rvWeaponLightningGun::UpdateTrailEffect +================ +*/ +void rvWeaponLightningGun::UpdateTrailEffect( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view ) { + idVec3 dir; + dir = end - start; + dir.Normalize(); + + if ( !effect ) { + effect = gameLocal.PlayEffect( gameLocal.GetEffect( weaponDef->dict, view ? "fx_trail" : "fx_trail_world" ), start, dir.ToMat3(), true, end ); + } else { + effect->SetOrigin( start ); + effect->SetAxis( dir.ToMat3() ); + effect->SetEndOrigin( end ); + } +} + +/* +================ +rvWeaponLightningGun::StopChainLightning +================ +*/ +void rvWeaponLightningGun::StopChainLightning( void ) { + int i; + + if ( !chainLightning.Num( ) ) { + return; + } + + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].StopEffects( ); + } + + chainLightning.Clear( ); +} + +/* +================ +rvWeaponLightningGun::ClientStale +================ +*/ +void rvWeaponLightningGun::ClientStale( void ) { + rvWeapon::ClientStale( ); + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + + currentPath.StopEffects( ); +} + +/* +================ +rvWeaponLightningGun::PreSave +================ +*/ +void rvWeaponLightningGun::PreSave( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_BODY2, 0); + StopSound( SND_CHANNEL_BODY3, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + + viewModel->StopSound( SND_CHANNEL_ANY, false ); + viewModel->StopSound( SND_CHANNEL_BODY, 0); + + worldModel->StopSound( SND_CHANNEL_ANY, false ); + worldModel->StopSound( SND_CHANNEL_BODY, 0); + + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + for ( int i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Event_Remove(); + } + } + currentPath.StopEffects( ); + +} + +/* +================ +rvWeaponLightningGun::PostSave +================ +*/ +void rvWeaponLightningGun::PostSave( void ) { + //restore the humming + PostEventMS( &EV_Lightninggun_RestoreHum, 10 ); +} + +/* +================ +rvWeaponLightningGun::UpdateTubes +================ +*/ +void rvWeaponLightningGun::UpdateTubes( void ) { + idAnimator* animator; + animator = viewModel->GetAnimator ( ); + assert ( animator ); + + int i; + float ammo; + + ammo = AmmoAvailable ( ); + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + float offset; + if ( ammo > tubeThreshold * i ) { + offset = tubeMaxOffset; + + if ( !tubeEffects[i] ) { + tubeEffects[i] = viewModel->PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_tube" ), tubeJoints[i], vec3_origin, mat3_identity, true ); + if( tubeEffects[i] ) { + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } else { + offset = 0; + + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop ( ); + tubeEffects[i] = NULL; + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + // Attenuate the tube effect to how much ammo is left in the tube + if ( tubeEffects[i] ) { + tubeEffects[i]->Attenuate ( (ammo - tubeThreshold * (float)i) / tubeThreshold ); + } + + if ( offset > tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (1.0f - (current/tubeMaxOffset)) * (float)tubeTime, current, offset ); + } else if ( offset < tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (current/tubeMaxOffset) * (float)tubeTime, current, offset ); + } + + animator->SetJointPos ( tubeJoints[i], JOINTMOD_LOCAL, idVec3(tubeOffsets[i].GetCurrentValue(gameLocal.time),0,0) ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponLightningGun ) + STATE ( "Raise", rvWeaponLightningGun::State_Raise ) + STATE ( "Lower", rvWeaponLightningGun::State_Lower ) + STATE ( "Idle", rvWeaponLightningGun::State_Idle) + STATE ( "Fire", rvWeaponLightningGun::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponLightningGun::State_Raise +================ +*/ +stateResult_t rvWeaponLightningGun::State_Raise( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus( WP_RISING ); + viewModel->SetShaderParm( 6, 1 ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + // Wait for the weapon to finish raising and allow it to be cancelled by + // lowering the weapon + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Lower +================ +*/ +stateResult_t rvWeaponLightningGun::State_Lower( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Idle +================ +*/ +stateResult_t rvWeaponLightningGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time > nextAttackTime && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Fire +================ +*/ +stateResult_t rvWeaponLightningGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACKLOOP, + STAGE_DONE, + STAGE_DONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + StartSound( "snd_fire_stereo", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_fire_loop", SND_CHANNEL_BODY2, 0, false, NULL ); + + viewModel->SetShaderParm( 6, 0 ); + + viewModel->PlayEffect( "fx_spire", spireJointView, true ); + viewModel->PlayEffect( "fx_flash", barrelJointView, true ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict,"fx_flash_world"), flashJointWorld, vec3_origin, mat3_identity, true ); + } + + PlayAnim( ANIMCHANNEL_ALL, "shoot_start", parms.blendFrames ); + return SRESULT_STAGE( STAGE_ATTACKLOOP ); + + case STAGE_ATTACKLOOP: + if ( !wsfl.attack || wsfl.lowerWeapon || !AmmoAvailable ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + PlayCycle( ANIMCHANNEL_ALL, "shoot_loop", 0 ); + if ( !gameLocal.isMultiplayer + && owner == gameLocal.GetLocalPlayer() ) { + owner->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + 500), 2.0f ); + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + StopSound( SND_CHANNEL_BODY2, false ); + + viewModel->StopEffect( "fx_spire" ); + viewModel->StopEffect( "fx_flash" ); + if ( worldModel ) { + worldModel->StopEffect( gameLocal.GetEffect( weaponDef->dict, "fx_flash_world" ) ); + } + viewModel->SetShaderParm( 6, 1 ); + + PlayAnim( ANIMCHANNEL_ALL, "shoot_end", 0 ); + return SRESULT_STAGE( STAGE_DONEWAIT ); + + case STAGE_DONEWAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( !wsfl.lowerWeapon && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponLightningGun::Event_RestoreHum +================ +*/ +void rvWeaponLightningGun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); +} + +/* +================ +rvWeaponLightningGun::ClientUnStale +================ +*/ +void rvWeaponLightningGun::ClientUnstale( void ) { + Event_RestoreHum(); +} + +/* +=============================================================================== + + rvLightningPath + +=============================================================================== +*/ + +/* +================ +rvLightningPath::StopEffects +================ +*/ +void rvLightningPath::StopEffects( void ) { + if ( trailEffect ) { + trailEffect->Stop( ); + trailEffect->Event_Remove( ); + trailEffect = NULL; + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } +} + +/* +================ +rvLightningPath::UpdateEffects +================ +*/ +void rvLightningPath::UpdateEffects ( const idVec3& from, const idDict& dict ) { + idVec3 dir; + dir = origin - from; + dir.Normalize(); + + // Trail effect + if ( !trailEffect ) { + trailEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_trail_world" ), from, dir.ToMat3(), true, origin ); + } else { + trailEffect->SetOrigin ( from ); + trailEffect->SetAxis ( dir.ToMat3() ); + trailEffect->SetEndOrigin ( origin ); + } + + // Impact effect + if ( !target || target.GetEntityNum ( ) == ENTITYNUM_NONE ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } + } else { + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_impact" ), origin, normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( origin ); + impactEffect->SetAxis ( normal.ToMat3 ( ) ); + } + } +} + +/* +================ +rvLightningPath::Save +================ +*/ +void rvLightningPath::Save( idSaveGame* savefile ) const { + target.Save( savefile ); + savefile->WriteVec3( origin ); + savefile->WriteVec3( normal ); + trailEffect.Save( savefile ); + impactEffect.Save( savefile ); +} + +/* +================ +rvLightningPath::Restore +================ +*/ +void rvLightningPath::Restore( idRestoreGame* savefile ) { + target.Restore( savefile ); + savefile->ReadVec3( origin ); + savefile->ReadVec3( normal ); + trailEffect.Restore( savefile ); + impactEffect.Restore( savefile ); +} diff --git a/source/mpgame/weapon/WeaponMachinegun.cpp b/source/mpgame/weapon/WeaponMachinegun.cpp new file mode 100644 index 0000000..c9d453e --- /dev/null +++ b/source/mpgame/weapon/WeaponMachinegun.cpp @@ -0,0 +1,327 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponMachinegun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponMachinegun ); + + rvWeaponMachinegun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + float spreadZoom; + bool fireHeld; + + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponMachinegun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponMachinegun ) +END_CLASS + +/* +================ +rvWeaponMachinegun::rvWeaponMachinegun +================ +*/ +rvWeaponMachinegun::rvWeaponMachinegun ( void ) { +} + +/* +================ +rvWeaponMachinegun::Spawn +================ +*/ +void rvWeaponMachinegun::Spawn ( void ) { + spreadZoom = spawnArgs.GetFloat ( "spreadZoom" ); + fireHeld = false; + + SetState ( "Raise", 0 ); + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponMachinegun::Save +================ +*/ +void rvWeaponMachinegun::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( spreadZoom ); + savefile->WriteBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::Restore +================ +*/ +void rvWeaponMachinegun::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( spreadZoom ); + savefile->ReadBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::PreSave +================ +*/ +void rvWeaponMachinegun::PreSave ( void ) { +} + +/* +================ +rvWeaponMachinegun::PostSave +================ +*/ +void rvWeaponMachinegun::PostSave ( void ) { +} + + +/* +================ +rvWeaponMachinegun::Think +================ +*/ +void rvWeaponMachinegun::Think() +{ + rvWeapon::Think(); + if ( zoomGui && owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat( "playerYaw", playerViewAxis.ToAngles().yaw ); + } +} + +/* +================ +rvWeaponMachinegun::UpdateFlashlight +================ +*/ +bool rvWeaponMachinegun::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponMachinegun::Flashlight +================ +*/ +void rvWeaponMachinegun::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponMachinegun ) + STATE ( "Idle", rvWeaponMachinegun::State_Idle) + STATE ( "Fire", rvWeaponMachinegun::State_Fire ) + STATE ( "Reload", rvWeaponMachinegun::State_Reload ) + STATE ( "Flashlight", rvWeaponMachinegun::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponMachinegun::State_Idle +================ +*/ +stateResult_t rvWeaponMachinegun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + + if ( fireHeld && !wsfl.attack ) { + fireHeld = false; + } + if ( !clipSize ) { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Fire +================ +*/ +stateResult_t rvWeaponMachinegun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spreadZoom, 0, 1.0f ); + fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !fireHeld && wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Reload +================ +*/ +stateResult_t rvWeaponMachinegun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponMachinegun::State_Flashlight +================ +*/ +stateResult_t rvWeaponMachinegun::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/source/mpgame/weapon/WeaponNailgun.cpp b/source/mpgame/weapon/WeaponNailgun.cpp new file mode 100644 index 0000000..17df27b --- /dev/null +++ b/source/mpgame/weapon/WeaponNailgun.cpp @@ -0,0 +1,929 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" + +// Available drum speeds +const int NAILGUN_DRUMSPEED_STOPPED = 0; +const int NAILGUN_DRUMSPEED_SLOW = 1; +const int NAILGUN_DRUMSPEED_FAST = 2; + +// Spinup and spindown times +const int NAILGUN_SPINDOWN_TIME = 1000; +const int NAILGUN_SPINUP_TIME = 1000; + +// Nailgun shader parms +const int NAILGUN_SPARM_PLAYLEADIN = 7; + +// Nailgun weapon modifications +const int NAILGUN_MOD_ROF_AMMO = BIT(0); // power mod combines Rate of fire and ammoo +const int NAILGUN_MOD_SEEK = BIT(1); +//const int NAILGUN_MOD_AMMO = BIT(2); + +const int NAILGUN_SPIN_SNDCHANNEL = SND_CHANNEL_BODY2; + +class rvWeaponNailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponNailgun ); + + rvWeaponNailgun ( void ); + ~rvWeaponNailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idEntityPtr guideEnt; + int guideTime; + int guideStartTime; + rvClientEntityPtr guideEffect; + bool guideLocked; + float guideRange; + int guideHoldTime; + int guideAquireTime; + + jointHandle_t jointDrumView; + jointHandle_t jointPinsView; + jointHandle_t jointSteamRightView; + jointHandle_t jointSteamLeftView; + + jointHandle_t jointGuideEnt; + + int drumSpeed; + int drumSpeedIdeal; + float drumMultiplier; + + virtual void OnLaunchProjectile ( idProjectile* proj ); + +private: + + void UpdateGuideStatus ( float range = 0.0f ); + void CancelGuide ( void ); + bool DrumSpin ( int speed, int blendFrames ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_DrumSpinDown ( const stateParms_t& parms ); + stateResult_t State_DrumSpinUp ( const stateParms_t& parms ); + + stateResult_t Frame_ClaspOpen ( const stateParms_t& parms ); + stateResult_t Frame_ClaspClose ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponNailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponNailgun ) +END_CLASS + +/* +================ +rvWeaponNailgun::rvWeaponNailgun +================ +*/ +rvWeaponNailgun::rvWeaponNailgun ( void ) { +} + +/* +================ +rvWeaponNailgun::~rvWeaponNailgun +================ +*/ +rvWeaponNailgun::~rvWeaponNailgun ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } + if( viewModel ) { + viewModel->StopSound( NAILGUN_SPIN_SNDCHANNEL, false ); + } +} + + +/* +================ +rvWeaponNailgun::Spawn +================ +*/ +void rvWeaponNailgun::Spawn ( void ) { + spawnArgs.GetFloat ( "lockRange", "1000", guideRange ); + guideHoldTime = SEC2MS ( spawnArgs.GetFloat ( "lockHoldTime", "10" ) ); + guideAquireTime = SEC2MS ( spawnArgs.GetFloat ( "lockAquireTime", ".1" ) ); + + jointDrumView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_drum" ) ); + jointPinsView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_pins" ) ); + jointSteamRightView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamRight" ) ); + jointSteamLeftView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamLeft" ) ); + + jointGuideEnt = INVALID_JOINT; + + drumSpeed = NAILGUN_DRUMSPEED_STOPPED; + drumSpeedIdeal = drumSpeed; + drumMultiplier = spawnArgs.GetFloat ( "drumSpeed" ); + + ExecuteState ( "ClaspClose" ); + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponNailgun::Save +================ +*/ +void rvWeaponNailgun::Save ( idSaveGame *savefile ) const { + guideEnt.Save( savefile ); + savefile->WriteInt( guideTime ); + savefile->WriteInt( guideStartTime ); + guideEffect.Save( savefile ); + savefile->WriteBool ( guideLocked ); + savefile->WriteFloat ( guideRange ); + savefile->WriteInt ( guideHoldTime ); + savefile->WriteInt ( guideAquireTime ); + + savefile->WriteJoint ( jointDrumView ); + savefile->WriteJoint ( jointPinsView ); + savefile->WriteJoint ( jointSteamRightView ); + savefile->WriteJoint ( jointSteamLeftView ); + savefile->WriteJoint ( jointGuideEnt ); + + savefile->WriteInt ( drumSpeed ); + savefile->WriteInt ( drumSpeedIdeal ); + savefile->WriteFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::Restore +================ +*/ +void rvWeaponNailgun::Restore ( idRestoreGame *savefile ) { + guideEnt.Restore( savefile ); + savefile->ReadInt( guideTime ); + savefile->ReadInt( guideStartTime ); + guideEffect.Restore( savefile ); + savefile->ReadBool ( guideLocked ); + savefile->ReadFloat ( guideRange ); + savefile->ReadInt ( guideHoldTime ); + savefile->ReadInt ( guideAquireTime ); + + savefile->ReadJoint ( jointDrumView ); + savefile->ReadJoint ( jointPinsView ); + savefile->ReadJoint ( jointSteamRightView ); + savefile->ReadJoint ( jointSteamLeftView ); + savefile->ReadJoint ( jointGuideEnt ); + + + savefile->ReadInt ( drumSpeed ); + savefile->ReadInt ( drumSpeedIdeal ); + savefile->ReadFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::PreSave +================ +*/ +void rvWeaponNailgun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + + //remove the guide gui cursor if there is one. + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect->Event_Remove(); + guideEffect = NULL; + } +} + +/* +================ +rvWeaponNailgun::PostSave +================ +*/ +void rvWeaponNailgun::PostSave ( void ) { + + //restore sounds-- but which one? + if( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } else if( drumSpeed == NAILGUN_DRUMSPEED_SLOW ) { + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } + + //the guide gui effect will restore itself naturally +} + +/* +================ +rvWeaponNailgun::CancelGuide +================ +*/ +void rvWeaponNailgun::CancelGuide ( void ) { + if ( zoomGui && guideEnt ) { + zoomGui->HandleNamedEvent ( "lockStop" ); + } + + guideEnt = NULL; + guideLocked = false; + jointGuideEnt = INVALID_JOINT; + UpdateGuideStatus ( ); +} + +/* +================ +rvWeaponNailgun::UpdateGuideStatus +================ +*/ +void rvWeaponNailgun::UpdateGuideStatus ( float range ) { + // Update the zoom GUI variables + if ( zoomGui ) { + float lockStatus; + if ( guideEnt ) { + if ( guideLocked ) { + lockStatus = 1.0f; + } else { + lockStatus = Min((float)(gameLocal.time - guideStartTime)/(float)guideTime, 1.0f); + } + } else { + lockStatus = 0.0f; + } + + if ( owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat ( "lockStatus", lockStatus ); + zoomGui->SetStateFloat ( "playerYaw", playerViewAxis.ToAngles().yaw ); + } + + if ( guideEnt ) { + idVec3 diff; + diff = guideEnt->GetPhysics()->GetOrigin ( ) - playerViewOrigin; + diff.NormalizeFast ( ); + zoomGui->SetStateFloat ( "lockYaw", idMath::AngleDelta ( diff.ToAngles ( ).yaw, playerViewAxis.ToAngles().yaw ) ); + zoomGui->SetStateString ( "lockRange", va("%4.2f", range) ); + zoomGui->SetStateString ( "lockTime", va("%02d", (int)MS2SEC(guideStartTime + guideTime - gameLocal.time) + 1) ); + } + } + + // Update the guide effect + if ( guideEnt ) { + idVec3 eyePos = static_cast(guideEnt.GetEntity())->GetEyePosition(); + if( jointGuideEnt == INVALID_JOINT ) { + eyePos += guideEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + } else { + idMat3 jointAxis; + idVec3 jointLoc; + static_cast(guideEnt.GetEntity())->GetJointWorldTransform( jointGuideEnt, gameLocal.GetTime(),jointLoc,jointAxis ); + eyePos += jointLoc; + } + eyePos *= 0.5f; + if ( guideEffect ) { + guideEffect->SetOrigin ( eyePos ); + guideEffect->SetAxis ( playerViewAxis.Transpose() ); + } else { + guideEffect = gameLocal.PlayEffect ( + gameLocal.GetEffect( spawnArgs, guideLocked ? "fx_guide" : "fx_guidestart" ), + eyePos, playerViewAxis.Transpose(), true, vec3_origin, false ); + if ( guideEffect ) { + guideEffect->GetRenderEffect()->weaponDepthHackInViewID = owner->entityNumber + 1; + guideEffect->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1; + } + } + } else { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + } +} + +/* +================ +rvWeaponNailgun::Think +================ +*/ +void rvWeaponNailgun::Think ( void ) { + idEntity* ent; + trace_t tr; + + // Let the real weapon think first + rvWeapon::Think ( ); + + // If no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + // If the zoom button isnt down then dont update the lock + if ( !wsfl.zoom || IsReloading ( ) || IsHolstered ( ) ) { + CancelGuide ( ); + return; + } + + // Dont update the target if the current target is still alive, unhidden and we have already locked + if ( guideEnt && guideEnt->health > 0 && !guideEnt->IsHidden() && guideLocked ) { + float range; + range = (guideEnt->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast(); + if ( range > guideRange || gameLocal.time > guideStartTime + guideTime ) { + CancelGuide ( ); + } else { + UpdateGuideStatus ( range ); + return; + } + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_BOUNDINGBOX, owner ); +// RAVEN END + + if ( tr.fraction >= 1.0f ) { + CancelGuide( ); + return; + } + + ent = gameLocal.entities[tr.c.entityNum]; + + //if we're using a target nailable... + if( ent->IsType ( rvTarget_Nailable::GetClassType() ) ) { + const char* jointName = ent->spawnArgs.GetString("lock_joint"); + ent = ent->targets[ 0 ].GetEntity(); + if( !ent) { + CancelGuide( ); + return; + } + + if( ent->GetAnimator() ) { + jointGuideEnt = ent->GetAnimator()->GetJointHandle( jointName ); + } + } + + + if ( !ent->IsType ( idActor::GetClassType() ) ) { + CancelGuide( ); + return; + } + if ( gameLocal.GetLocalPlayer() && static_cast(ent)->team == gameLocal.GetLocalPlayer()->team ) { + CancelGuide( ); + return; + } + + if ( guideEnt != ent ) { + guideStartTime = gameLocal.time; + guideTime = guideAquireTime; + guideEnt = ent; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockStart" ); + } + } else if ( gameLocal.time > guideStartTime + guideTime ) { + // Stop the guide effect since it was just the guide_Start effect + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + guideLocked = true; + guideTime = guideHoldTime; + guideStartTime = gameLocal.time; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockAquired" ); + } + } + + UpdateGuideStatus ( (ent->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast() ); +} + +/* +================ +rvWeaponNailgun::OnLaunchProjectile +================ +*/ +void rvWeaponNailgun::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + idGuidedProjectile* guided; + guided = dynamic_cast(proj); + if ( guided ) { + guided->GuideTo ( guideEnt, jointGuideEnt ); + } +} + +/* +================ +rvWeaponNailgun::DrumSpin + +Set the drum spin speed +================ +*/ +bool rvWeaponNailgun::DrumSpin ( int speed, int blendFrames ) { + // Dont bother if the drum is already spinning at the desired speed + if ( drumSpeedIdeal == speed ) { + return false; + } + + drumSpeedIdeal = speed; + + switch ( speed ) { + case NAILGUN_DRUMSPEED_STOPPED: + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,0), gameLocal.time, 250 ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,0), gameLocal.time, 250 ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_SLOW: + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,45), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-35), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_FAST: + // Start the barrel spinning faster + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,360.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-300.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + + PostState ( "DrumSpinUp", blendFrames ); + return true; + } + + drumSpeed = drumSpeedIdeal; + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponNailgun ) + STATE ( "Raise", rvWeaponNailgun::State_Raise ) + STATE ( "Lower", rvWeaponNailgun::State_Lower ) + STATE ( "Idle", rvWeaponNailgun::State_Idle) + STATE ( "Fire", rvWeaponNailgun::State_Fire ) + STATE ( "Reload", rvWeaponNailgun::State_Reload ) + STATE ( "DrumSpinDown", rvWeaponNailgun::State_DrumSpinDown ) + STATE ( "DrumSpinUp", rvWeaponNailgun::State_DrumSpinUp ) + + STATE ( "ClaspOpen", rvWeaponNailgun::Frame_ClaspOpen ) + STATE ( "ClaspClose", rvWeaponNailgun::Frame_ClaspClose ) +END_CLASS_STATES + +/* +================ +rvWeaponNailgun::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + // Stop any looping sounds + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Idle + +Manage the idle state of the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoInClip ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + // Do we need to spin the drum down? + if ( DrumSpin ( NAILGUN_DRUMSPEED_SLOW, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Fire + +Fire the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_DONE, + STAGE_SPINEMPTY, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !wsfl.attack ) { + SetState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + if ( DrumSpin ( NAILGUN_DRUMSPEED_FAST, 2 ) ) { + PostState ( "Fire", 2 ); + return SRESULT_DONE; + } + nextAttackTime = gameLocal.time; + + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || !AmmoInClip ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_fast", 4 ); + } else { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_slow", 4 ); + } + + if ( wsfl.zoom ) { + Attack ( true, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } else { + Attack ( false, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } + + // Play the exhaust effects + viewModel->PlayEffect ( "fx_exhaust", jointSteamRightView, false ); + viewModel->PlayEffect ( "fx_exhaust", jointSteamLeftView, false ); + + viewModel->StartSound ( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + viewModel->StartSound ( "snd_fireStereo", SND_CHANNEL_ITEM, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || !AmmoInClip ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( gameLocal.time >= nextAttackTime ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( clipSize && wsfl.attack && !wsfl.lowerWeapon && !wsfl.reload ) { + PlayCycle ( ANIMCHANNEL_LEGS, "spinempty", 4 ); + return SRESULT_STAGE ( STAGE_SPINEMPTY ); + } + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 4 ); + if ( !wsfl.attack && !AmmoInClip() && AmmoAvailable() && AutoReload ( ) && !wsfl.lowerWeapon ) { + PostState ( "Reload", 4 ); + } else { + PostState ( "Idle", 4 ); + } + return SRESULT_DONE; + + case STAGE_SPINEMPTY: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Reload +================ +*/ +stateResult_t rvWeaponNailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_RELOAD, + STAGE_RELOADWAIT, + STAGE_RELOADLEFT, + STAGE_RELOADLEFTWAIT, + STAGE_RELOADRIGHT, + STAGE_RELOADRIGHTWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( DrumSpin ( NAILGUN_DRUMSPEED_STOPPED, parms.blendFrames ) ) { + PostState ( "reload", parms.blendFrames ); + return SRESULT_DONE; + } + + SetStatus ( WP_RELOAD ); + + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + return SRESULT_STAGE( STAGE_RELOADLEFT ); + } + return SRESULT_STAGE( STAGE_RELOAD ); + + case STAGE_RELOAD: + PlayAnim( ANIMCHANNEL_LEGS, "reload", parms.blendFrames ); + return SRESULT_STAGE( STAGE_RELOADWAIT ); + + case STAGE_RELOADWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize ( ) ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLEFT: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1hold", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADLEFTWAIT ); + + case STAGE_RELOADLEFTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + AddToClip ( ClipSize() / 2 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHT ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADRIGHT: + if ( wsfl.attack || AmmoInClip() >= ClipSize() || !AmmoAvailable() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip2", 0 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHTWAIT ); + + case STAGE_RELOADRIGHTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize() / 2 ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1finish", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinUp + +Spin the drum from a slow speed to a fast speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinUp ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spinup", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL); + PlayAnim ( ANIMCHANNEL_LEGS, "spinup", 4 ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( !wsfl.attack ) { + int oldSpeed = drumSpeed; + drumSpeed = drumSpeedIdeal; + DrumSpin ( oldSpeed, 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinDown + +Spin the drum down from a faster speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinDown ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spindown", SND_CHANNEL_ANY, 0, false, 0 ); + + // Spin down animation + PlayAnim( ANIMCHANNEL_LEGS, "spindown", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoAvailable() > AmmoInClip() && AmmoInClip() < ClipSize()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Open the clasp that holds in the clips +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspOpen ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, "clasp_open", 0 ); + return SRESULT_OK; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Close the clasp that holds in the clips and make sure to use the +correct positioning depending on whether you have one or two clips +in the gun. +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspClose ( const stateParms_t& parms ) { + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_2clip", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_1clip", 0 ); + } + return SRESULT_OK; +} diff --git a/source/mpgame/weapon/WeaponNapalmGun.cpp b/source/mpgame/weapon/WeaponNapalmGun.cpp new file mode 100644 index 0000000..ccec34b --- /dev/null +++ b/source/mpgame/weapon/WeaponNapalmGun.cpp @@ -0,0 +1,443 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +const int NAPALM_GUN_NUM_CYLINDERS = 5; + +class WeaponNapalmGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( WeaponNapalmGun ); + + WeaponNapalmGun ( void ); + ~WeaponNapalmGun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + + virtual void SpectatorCycle ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + +protected: + + void UpdateCylinders(void); + + typedef enum {CYLINDER_RESET_POSITION,CYLINDER_MOVE_POSITION, CYLINDER_UPDATE_POSITION } CylinderState; + CylinderState cylinderState; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_EmptyReload ( const stateParms_t& parms ); + + stateResult_t Frame_MoveCylinder ( const stateParms_t& parms ); + stateResult_t Frame_ResetCylinder ( const stateParms_t& parms ); + + + float cylinderMaxOffsets[NAPALM_GUN_NUM_CYLINDERS]; + idInterpolate cylinderOffsets[NAPALM_GUN_NUM_CYLINDERS]; + jointHandle_t cylinderJoints[NAPALM_GUN_NUM_CYLINDERS]; + + + int cylinderMoveTime; + int previousAmmo; + bool zoomed; + + CLASS_STATES_PROTOTYPE ( WeaponNapalmGun ); +}; + +CLASS_DECLARATION( rvWeapon, WeaponNapalmGun ) +END_CLASS + +/* +================ +WeaponNapalmGun::WeaponNapalmGun +================ +*/ +WeaponNapalmGun::WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::~WeaponNapalmGun +================ +*/ +WeaponNapalmGun::~WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::Spawn +================ +*/ +void WeaponNapalmGun::Spawn( void ) { + assert(viewModel); + idAnimator* animator = viewModel->GetAnimator(); + assert(animator); + + SetState( "Raise", 0 ); + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + idStr argName = "cylinder_offset"; + argName += i; + cylinderMaxOffsets[i] = spawnArgs.GetFloat(argName, "0.0"); + + argName = "cylinder_joint"; + argName += i; + cylinderJoints[i] = animator->GetJointHandle( spawnArgs.GetString( argName, "" ) ); + + cylinderOffsets[i].Init( gameLocal.time, 0.0f, 0, 0); + } + + previousAmmo = AmmoInClip(); + cylinderMoveTime = spawnArgs.GetFloat( "cylinderMoveTime", "500" ); + cylinderState = CYLINDER_RESET_POSITION; + zoomed = false; +} + +/* +================ +WeaponNapalmGun::Think +================ +*/ +void WeaponNapalmGun::Think( void ) { + + rvWeapon::Think(); + + //Check to see if the ammo level has changed. + //This is to account for ammo pickups. + if ( previousAmmo != AmmoInClip() ) { + // don't do this in MP, the weap script doesn't sync the canisters anyway + if ( !gameLocal.isMultiplayer ) { + //change the cylinder state to reflect the new change in ammo. + cylinderState = CYLINDER_MOVE_POSITION; + } + previousAmmo = AmmoInClip(); + } + + UpdateCylinders(); +} + +/* +=============== +WeaponNapalmGun::MuzzleRise +=============== +*/ +void WeaponNapalmGun::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if ( wsfl.zoom ) + return; + + rvWeapon::MuzzleRise( origin, axis ); +} + +/* +=============== +WeaponNapalmGun::UpdateCylinders +=============== +*/ +void WeaponNapalmGun::UpdateCylinders(void) +{ + idAnimator* animator; + animator = viewModel->GetAnimator(); + assert( animator ); + + float ammoInClip = AmmoInClip(); + float clipSize = ClipSize(); + if ( clipSize <= idMath::FLOAT_EPSILON ) { + clipSize = maxAmmo; + } + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + // move the local position of the joint along the x-axis. + float currentOffset = cylinderOffsets[i].GetCurrentValue(gameLocal.time); + + switch(cylinderState) + { + case CYLINDER_MOVE_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, cylinderMoveTime, currentOffset, endValue ); + } + break; + + case CYLINDER_RESET_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, 0, endValue, endValue ); + } + break; + } + + + animator->SetJointPos( cylinderJoints[i], JOINTMOD_LOCAL, idVec3( currentOffset, 0.0f, 0.0f ) ); + } + + cylinderState = CYLINDER_UPDATE_POSITION; +} + + +/* +===================== +WeaponNapalmGun::Save +===================== +*/ +void WeaponNapalmGun::Save( idSaveGame *saveFile ) const +{ + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->WriteFloat(cylinderMaxOffsets[i]); + saveFile->WriteInterpolate(cylinderOffsets[i]); + saveFile->WriteJoint(cylinderJoints[i]); + } + + saveFile->WriteInt(cylinderMoveTime); + saveFile->WriteInt(previousAmmo); +} + +/* +===================== +WeaponNapalmGun::Restore +===================== +*/ +void WeaponNapalmGun::Restore( idRestoreGame *saveFile ) { + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->ReadFloat(cylinderMaxOffsets[i]); + saveFile->ReadInterpolate(cylinderOffsets[i]); + saveFile->ReadJoint(cylinderJoints[i]); + } + + saveFile->ReadInt(cylinderMoveTime); + saveFile->ReadInt(previousAmmo); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( WeaponNapalmGun ) + STATE ( "Idle", WeaponNapalmGun::State_Idle) + STATE ( "Fire", WeaponNapalmGun::State_Fire ) + STATE ( "Reload", WeaponNapalmGun::State_Reload ) + STATE ( "EmptyReload", WeaponNapalmGun::State_EmptyReload ) + STATE ( "MoveCylinder", WeaponNapalmGun::Frame_MoveCylinder ) + STATE ( "ResetCylinder", WeaponNapalmGun::Frame_ResetCylinder) +END_CLASS_STATES + + + +stateResult_t WeaponNapalmGun::State_Reload( const stateParms_t& parms) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t WeaponNapalmGun::State_EmptyReload( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_empty", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip ( ClipSize() ); + + cylinderState = CYLINDER_MOVE_POSITION; + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Idle +================ +*/ +stateResult_t WeaponNapalmGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + if ( wsfl.zoom ) + PlayCycle( ANIMCHANNEL_LEGS, "altidle", parms.blendFrames ); + else + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.zoom && !zoomed ) { + SetState ( "Idle", 4 ); + zoomed = true; + return SRESULT_DONE; + } + + if ( !wsfl.zoom && zoomed ) { + SetState ( "Idle", 4 ); + zoomed = false; + return SRESULT_DONE; + } + + if(!clipSize) + { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } + else + { + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Fire +================ +*/ +stateResult_t WeaponNapalmGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + //fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + + int animNum = viewModel->GetAnimator()->GetAnim ( "fire" ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim ( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + + previousAmmo = AmmoInClip(); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + if ( !wsfl.zoom ) + SetState ( "Reload", 4 ); + else + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t WeaponNapalmGun::Frame_MoveCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_MOVE_POSITION; + return SRESULT_OK; +} + +stateResult_t WeaponNapalmGun::Frame_ResetCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_RESET_POSITION; + return SRESULT_OK; +} + +void WeaponNapalmGun::SpectatorCycle( void ) { + cylinderState = CYLINDER_RESET_POSITION; +} diff --git a/source/mpgame/weapon/WeaponRailgun.cpp b/source/mpgame/weapon/WeaponRailgun.cpp new file mode 100644 index 0000000..92a8ef9 --- /dev/null +++ b/source/mpgame/weapon/WeaponRailgun.cpp @@ -0,0 +1,270 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const idEventDef EV_Railgun_RestoreHum( "", "" ); + +class rvWeaponRailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRailgun ); + + rvWeaponRailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + void ClientUnstale ( void ); + +protected: + jointHandle_t jointBatteryView; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRailgun ) + EVENT( EV_Railgun_RestoreHum, rvWeaponRailgun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponRailgun::rvWeaponRailgun +================ +*/ +rvWeaponRailgun::rvWeaponRailgun ( void ) { +} + +/* +================ +rvWeaponRailgun::Spawn +================ +*/ +void rvWeaponRailgun::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponRailgun::Save +================ +*/ +void rvWeaponRailgun::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::Restore +================ +*/ +void rvWeaponRailgun::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::PreSave +================ +*/ +void rvWeaponRailgun::PreSave ( void ) { + + //this should shoosh the humming but not the shooting sound. + StopSound( SND_CHANNEL_BODY2, 0); +} + +/* +================ +rvWeaponRailgun::PostSave +================ +*/ +void rvWeaponRailgun::PostSave ( void ) { + + //restore the humming + PostEventMS( &EV_Railgun_RestoreHum, 10); +} + +/* +================ +rvWeaponRailgun::Think +================ +*/ +void rvWeaponRailgun::Think ( void ) { + + // Let the real weapon think first + rvWeapon::Think ( ); + + if ( zoomGui && wsfl.zoom && !gameLocal.isMultiplayer ) { + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + zoomGui->SetStateInt( "player_ammo", ammo ); + } + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRailgun ) + STATE ( "Idle", rvWeaponRailgun::State_Idle) + STATE ( "Fire", rvWeaponRailgun::State_Fire ) + STATE ( "Reload", rvWeaponRailgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponRailgun::State_Idle +================ +*/ +stateResult_t rvWeaponRailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); + SetStatus ( WP_READY ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + // Auto reload? + if ( AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRailgun::State_Fire +================ +*/ +stateResult_t rvWeaponRailgun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( ( gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime ) || + ( !gameLocal.isMultiplayer && ( AnimDone ( ANIMCHANNEL_ALL, 2 ) ) ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +stateResult_t rvWeaponRailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Event + +=============================================================================== +*/ + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +void rvWeaponRailgun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); +} + +/* +================ +rvWeaponRailgun::ClientUnStale +================ +*/ +void rvWeaponRailgun::ClientUnstale( void ) { + Event_RestoreHum(); +} + diff --git a/source/mpgame/weapon/WeaponRocketLauncher.cpp b/source/mpgame/weapon/WeaponRocketLauncher.cpp new file mode 100644 index 0000000..bbd3623 --- /dev/null +++ b/source/mpgame/weapon/WeaponRocketLauncher.cpp @@ -0,0 +1,583 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +class rvWeaponRocketLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRocketLauncher ); + + rvWeaponRocketLauncher ( void ); + ~rvWeaponRocketLauncher ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + void PreSave ( void ); + void PostSave ( void ); + + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetRocketState ( const char* state, int blendFrames ); + + rvClientEntityPtr guideEffect; + idList< idEntityPtr > guideEnts; + float guideSpeedSlow; + float guideSpeedFast; + float guideRange; + float guideAccelTime; + + rvStateThread rocketThread; + + float reloadRate; + + bool idleEmpty; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_Rocket_Idle ( const stateParms_t& parms ); + stateResult_t State_Rocket_Reload ( const stateParms_t& parms ); + + stateResult_t Frame_AddToClip ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRocketLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRocketLauncher ) +END_CLASS + +/* +================ +rvWeaponRocketLauncher::rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::rvWeaponRocketLauncher ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } +} + +/* +================ +rvWeaponRocketLauncher::Spawn +================ +*/ +void rvWeaponRocketLauncher::Spawn ( void ) { + float f; + + idleEmpty = false; + + spawnArgs.GetFloat ( "lockRange", "0", guideRange ); + + spawnArgs.GetFloat ( "lockSlowdown", ".25", f ); + attackDict.GetFloat ( "speed", "0", guideSpeedFast ); + guideSpeedSlow = guideSpeedFast * f; + + reloadRate = SEC2MS ( spawnArgs.GetFloat ( "reloadRate", ".8" ) ); + + guideAccelTime = SEC2MS ( spawnArgs.GetFloat ( "lockAccelTime", ".25" ) ); + + // Start rocket thread + rocketThread.SetName ( viewModel->GetName ( ) ); + rocketThread.SetOwner ( this ); + + // Adjust reload animations to match the fire rate + idAnim* anim; + int animNum; + float rate; + animNum = viewModel->GetAnimator()->GetAnim ( "reload" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + animNum = viewModel->GetAnimator()->GetAnim ( "reload_empty" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + SetState ( "Raise", 0 ); + SetRocketState ( "Rocket_Idle", 0 ); +} + +/* +================ +rvWeaponRocketLauncher::Think +================ +*/ +void rvWeaponRocketLauncher::Think ( void ) { + trace_t tr; + int i; + + rocketThread.Execute ( ); + + // Let the real weapon think first + rvWeapon::Think ( ); + + // IF no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + if ( !wsfl.zoom ) { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden ( ) ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket is still guiding then stop the guide and slow it down + if ( proj->GetGuideType ( ) != idGuidedProjectile::GUIDE_NONE ) { + proj->CancelGuide ( ); + proj->SetSpeed ( guideSpeedFast, (1.0f - (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow)) * guideAccelTime ); + } + } + + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden() ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket isnt guiding yet then adjust its speed back to normal + if ( proj->GetGuideType ( ) == idGuidedProjectile::GUIDE_NONE ) { + proj->SetSpeed ( guideSpeedSlow, (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow) * guideAccelTime ); + } + proj->GuideTo ( tr.endpos ); + } + + if ( !guideEffect ) { + guideEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_guide" ), tr.endpos, tr.c.normal.ToMat3(), true, vec3_origin, true ); + } else { + guideEffect->SetOrigin ( tr.endpos ); + guideEffect->SetAxis ( tr.c.normal.ToMat3() ); + } +} + +/* +================ +rvWeaponRocketLauncher::OnLaunchProjectile +================ +*/ +void rvWeaponRocketLauncher::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + // Double check that its actually a guided projectile + if ( !proj || !proj->IsType ( idGuidedProjectile::GetClassType() ) ) { + return; + } + + // Launch the projectile + idEntityPtr ptr; + ptr = proj; + guideEnts.Append ( ptr ); +} + +/* +================ +rvWeaponRocketLauncher::SetRocketState +================ +*/ +void rvWeaponRocketLauncher::SetRocketState ( const char* state, int blendFrames ) { + rocketThread.SetState ( state, blendFrames ); +} + +/* +===================== +rvWeaponRocketLauncher::Save +===================== +*/ +void rvWeaponRocketLauncher::Save( idSaveGame *saveFile ) const { + saveFile->WriteObject( guideEffect ); + + idEntity* ent = NULL; + saveFile->WriteInt( guideEnts.Num() ); + for( int ix = 0; ix < guideEnts.Num(); ++ix ) { + ent = guideEnts[ ix ].GetEntity(); + if( ent ) { + saveFile->WriteObject( ent ); + } + } + + saveFile->WriteFloat( guideSpeedSlow ); + saveFile->WriteFloat( guideSpeedFast ); + saveFile->WriteFloat( guideRange ); + saveFile->WriteFloat( guideAccelTime ); + + saveFile->WriteFloat ( reloadRate ); + + rocketThread.Save( saveFile ); +} + +/* +===================== +rvWeaponRocketLauncher::Restore +===================== +*/ +void rvWeaponRocketLauncher::Restore( idRestoreGame *saveFile ) { + int numEnts = 0; + idEntity* ent = NULL; + rvClientEffect* clientEffect = NULL; + + saveFile->ReadObject( reinterpret_cast(clientEffect) ); + guideEffect = clientEffect; + + saveFile->ReadInt( numEnts ); + guideEnts.Clear(); + guideEnts.SetNum( numEnts ); + for( int ix = 0; ix < numEnts; ++ix ) { + saveFile->ReadObject( reinterpret_cast(ent) ); + guideEnts[ ix ] = ent; + } + + saveFile->ReadFloat( guideSpeedSlow ); + saveFile->ReadFloat( guideSpeedFast ); + saveFile->ReadFloat( guideRange ); + saveFile->ReadFloat( guideAccelTime ); + + saveFile->ReadFloat ( reloadRate ); + + rocketThread.Restore( saveFile, this ); +} + +/* +================ +rvWeaponRocketLauncher::PreSave +================ +*/ +void rvWeaponRocketLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::PostSave +================ +*/ +void rvWeaponRocketLauncher::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRocketLauncher ) + STATE ( "Idle", rvWeaponRocketLauncher::State_Idle) + STATE ( "Fire", rvWeaponRocketLauncher::State_Fire ) + STATE ( "Raise", rvWeaponRocketLauncher::State_Raise ) + STATE ( "Lower", rvWeaponRocketLauncher::State_Lower ) + + STATE ( "Rocket_Idle", rvWeaponRocketLauncher::State_Rocket_Idle ) + STATE ( "Rocket_Reload", rvWeaponRocketLauncher::State_Rocket_Reload ) + + STATE ( "AddToClip", rvWeaponRocketLauncher::Frame_AddToClip ) +END_CLASS_STATES + + +/* +================ +rvWeaponRocketLauncher::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && ( gameLocal.isClient || AmmoInClip ( ) ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_LEGS, "fire", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && ( gameLocal.isClient || AmmoInClip ( ) ) && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITEMPTY, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) <= AmmoInClip() ) { + PlayAnim( ANIMCHANNEL_TORSO, "idle_empty", parms.blendFrames ); + idleEmpty = true; + } else { + PlayAnim( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AmmoAvailable ( ) > AmmoInClip() ) { + if ( idleEmpty ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } else if ( ClipSize ( ) > 1 ) { + if ( gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) ) { + if ( !AmmoInClip() || !wsfl.attack ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } else { + if ( AmmoInClip ( ) == 0 ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Reload +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: { + const char* animName; + int animNum; + + if ( idleEmpty ) { + animName = "ammo_pickup"; + idleEmpty = false; + } else if ( AmmoAvailable ( ) == AmmoInClip( ) + 1 ) { + animName = "reload_empty"; + } else { + animName = "reload"; + } + + animNum = viewModel->GetAnimator()->GetAnim ( animName ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (reloadRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( !wsfl.attack && gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) && AmmoAvailable() > AmmoInClip() ) { + SetRocketState ( "Rocket_Reload", 0 ); + } else { + SetRocketState ( "Rocket_Idle", 0 ); + } + return SRESULT_DONE; + } + /* + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + if ( AmmoInClip ( ) == 0 ) + { + AddToClip ( ClipSize() ); + } + SetRocketState ( "Rocket_Idle", 0 ); + return SRESULT_DONE; + } + */ + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::Frame_AddToClip +================ +*/ +stateResult_t rvWeaponRocketLauncher::Frame_AddToClip ( const stateParms_t& parms ) { + AddToClip ( 1 ); + return SRESULT_OK; +} + diff --git a/source/mpgame/weapon/WeaponShotgun.cpp b/source/mpgame/weapon/WeaponShotgun.cpp new file mode 100644 index 0000000..c904afa --- /dev/null +++ b/source/mpgame/weapon/WeaponShotgun.cpp @@ -0,0 +1,289 @@ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int SHOTGUN_MOD_AMMO = BIT(0); + +class rvWeaponShotgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponShotgun ); + + rvWeaponShotgun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + int hitscans; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( rvWeaponShotgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponShotgun ) +END_CLASS + +/* +================ +rvWeaponShotgun::rvWeaponShotgun +================ +*/ +rvWeaponShotgun::rvWeaponShotgun( void ) { +} + +/* +================ +rvWeaponShotgun::Spawn +================ +*/ +void rvWeaponShotgun::Spawn( void ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); + + SetState( "Raise", 0 ); +} + +/* +================ +rvWeaponShotgun::Save +================ +*/ +void rvWeaponShotgun::Save( idSaveGame *savefile ) const { +} + +/* +================ +rvWeaponShotgun::Restore +================ +*/ +void rvWeaponShotgun::Restore( idRestoreGame *savefile ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); +} + +/* +================ +rvWeaponShotgun::PreSave +================ +*/ +void rvWeaponShotgun::PreSave ( void ) { +} + +/* +================ +rvWeaponShotgun::PostSave +================ +*/ +void rvWeaponShotgun::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION( rvWeaponShotgun ) + STATE( "Idle", rvWeaponShotgun::State_Idle) + STATE( "Fire", rvWeaponShotgun::State_Fire ) + STATE( "Reload", rvWeaponShotgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponShotgun::State_Idle +================ +*/ +stateResult_t rvWeaponShotgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable( ) ) { + SetStatus( WP_OUTOFAMMO ); + } else { + SetStatus( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Fire +================ +*/ +stateResult_t rvWeaponShotgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack( false, hitscans, spread, 0, 1.0f ); + PlayAnim( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( (!gameLocal.isMultiplayer && (wsfl.lowerWeapon || AnimDone( ANIMCHANNEL_ALL, 0 )) ) || AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetState( "Idle", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( clipSize ) { + if ( (wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip())) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Reload +================ +*/ +stateResult_t rvWeaponShotgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_RELOADSTARTWAIT, + STAGE_RELOADLOOP, + STAGE_RELOADLOOPWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + + if ( mods & SHOTGUN_MOD_AMMO ) { + PlayAnim ( ANIMCHANNEL_ALL, "reload_clip", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_ALL, "reload_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADSTARTWAIT ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADSTARTWAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLOOP: + if ( (wsfl.attack && AmmoInClip() ) || AmmoAvailable ( ) <= AmmoInClip ( ) || AmmoInClip() == ClipSize() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_ALL, "reload_loop", 0 ); + return SRESULT_STAGE ( STAGE_RELOADLOOPWAIT ); + + case STAGE_RELOADLOOPWAIT: + if ( (wsfl.attack && AmmoInClip() ) || wsfl.netEndReload ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip( 1 ); + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + NetEndReload ( ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_end", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) && gameLocal.time > nextAttackTime ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/source/q4sdk.sln b/source/q4sdk.sln new file mode 100755 index 0000000..2f4df2a --- /dev/null +++ b/source/q4sdk.sln @@ -0,0 +1,46 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "game.vcproj", "{49BEC5C6-B964-417A-851E-808886B57430}" + ProjectSection(ProjectDependencies) = postProject + {49BEC5C6-B964-417A-851E-808886B57400} = {49BEC5C6-B964-417A-851E-808886B57400} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idLib", "idlib.vcproj", "{49BEC5C6-B964-417A-851E-808886B57400}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MPGame", "mpgame.vcproj", "{F2EF9123-B7C3-4F2F-A351-747B595BB534}" + ProjectSection(ProjectDependencies) = postProject + {49BEC5C6-B964-417A-851E-808886B57400} = {49BEC5C6-B964-417A-851E-808886B57400} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + DebugInline = DebugInline + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {49BEC5C6-B964-417A-851E-808886B57430}.Debug.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Debug.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.DebugInline.ActiveCfg = DebugInline|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.DebugInline.Build.0 = DebugInline|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Release.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57430}.Release.Build.0 = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.DebugInline.ActiveCfg = DebugInline|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.DebugInline.Build.0 = DebugInline|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release.Build.0 = Release|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.Debug.ActiveCfg = Debug|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.Debug.Build.0 = Debug|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.DebugInline.ActiveCfg = DebugInline|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.DebugInline.Build.0 = DebugInline|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.Release.ActiveCfg = Release|Win32 + {F2EF9123-B7C3-4F2F-A351-747B595BB534}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/source/renderer/Cinematic.h b/source/renderer/Cinematic.h new file mode 100644 index 0000000..e9b8fe7 --- /dev/null +++ b/source/renderer/Cinematic.h @@ -0,0 +1,132 @@ +// Copyright (C) 2004 Id Software, Inc. +// +#ifndef __CINEMATIC_H__ +#define __CINEMATIC_H__ + +// RAVEN BEGIN +//nrausch: I made some semi-heavy changes to this entire file +// - changed to idCinematic to use a private implementation which +// is determined & allocated when InitFromFile is called. A different +// PIMPL is used depending on if the video file is a roq or a wmv. +// This replaces the functionality that was in a few versions ago under the +// "StandaloneCinematic" name. +// RAVEN END + +/* +=============================================================================== + + RoQ cinematic + + Multiple idCinematics can run simultaniously. + A single idCinematic can be reused for multiple files if desired. + +=============================================================================== +*/ + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} cinStatus_t; + +// a cinematic stream generates an image buffer, which the caller will upload to a texture +typedef struct { + int imageWidth, imageHeight; // will be a power of 2 + const byte * image; // RGBA format, alpha will be 255 + int status; +} cinData_t; + +class idCinematic { + idCinematic* PIMPL; + + // Store off the current mode - wmv or roq + // If the cinematic is in the same mode if InitFromFile + // is called again on it, this will prevent reallocation + // of the PIMPL + int mode; +public: + // initialize cinematic play back data + static void InitCinematic( void ); + + // shutdown cinematic play back data + static void ShutdownCinematic( void ); + + // allocates and returns a private subclass that implements the methods + // This should be used instead of new + static idCinematic *Alloc(); + + idCinematic(); + + // frees all allocated memory + virtual ~idCinematic(); + + enum { + SUPPORT_DRAW = 1, + SUPPORT_IMAGEFORTIME = 2, + SUPPORT_DEFAULT = SUPPORT_IMAGEFORTIME + }; + + // returns false if it failed to load + // this interface can take either a wmv or roq file + // wmv will imply movie audio, unless there is no audio encoded in the stream + // right now there is no way to disable this. + virtual bool InitFromFile(const char *qpath, bool looping, int options = SUPPORT_DEFAULT); + + // returns the length of the animation in milliseconds + virtual int AnimationLength(); + + // the pointers in cinData_t will remain valid until the next UpdateForTime() call + // will do nothing if InitFromFile was not called with SUPPORT_IMAGEFORTIME + virtual cinData_t ImageForTime(int milliseconds); + + // closes the file and frees all allocated memory + virtual void Close(); + + // closes the file and frees all allocated memory + virtual void ResetTime(int time); + + // draw the current animation frame to screen + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void Draw(); + + // Set draw position & size + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void SetScreenRect(int left, int right, int bottom, int top); + + // Get draw position & size + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void GetScreenRect(int &left, int &right, int &bottom, int &top); + + // True if the video is playing + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual bool IsPlaying(); +}; + +/* +=============================================== + + Sound meter. + +=============================================== +*/ + +class idSndWindow : public idCinematic { +public: + + idSndWindow() { showWaveform = false; } + ~idSndWindow() {} + + bool InitFromFile( const char *qpath, bool looping, int options ); + cinData_t ImageForTime( int milliseconds ); + int AnimationLength(); + +private: + bool showWaveform; +}; + +#endif /* !__CINEMATIC_H__ */ diff --git a/source/renderer/Material.h b/source/renderer/Material.h new file mode 100644 index 0000000..3dc67bb --- /dev/null +++ b/source/renderer/Material.h @@ -0,0 +1,882 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MATERIAL_H__ +#define __MATERIAL_H__ + +/* +=============================================================================== + + Material + +=============================================================================== +*/ + +class idImage; +class idCinematic; +class idUserInterface; +class idMegaTexture; +// RAVEN BEGIN +// rjohnson: new shader stage system +class rvNewShaderStage; +class rvGLSLShaderStage; +// RAVEN END + +// moved from image.h for default parm +typedef enum { + TR_REPEAT, + TR_CLAMP, + TR_CLAMP_TO_BORDER, // this should replace TR_CLAMP_TO_ZERO and TR_CLAMP_TO_ZERO_ALPHA, + // but I don't want to risk changing it right now + TR_CLAMP_TO_ZERO, // guarantee 0,0,0,255 edge for projected textures, + // set AFTER image format selection + TR_CLAMP_TO_ZERO_ALPHA, // guarantee 0 alpha edge for projected textures, + // set AFTER image format selection + TR_MIRRORED_REPEAT, +} textureRepeat_t; + +typedef struct { + int stayTime; // msec for no change + float maxAngle; // maximum dot product to reject projection angles +} decalInfo_t; + +typedef enum { + DFRM_NONE, + DFRM_SPRITE, + DFRM_TUBE, + DFRM_FLARE, + DFRM_EXPAND, + DFRM_MOVE, + DFRM_EYEBALL, +// ddynerman: rectangular sprites + DFRM_RECTSPRITE, +// RAVEN END + DFRM_TURB +} deform_t; + +typedef enum { + DI_STATIC, + DI_SCRATCH, // video, screen wipe, etc + DI_CUBE_RENDER, + DI_MIRROR_RENDER, +// RAVEN BEGIN +// AReis: Used for water reflection/refraction. + DI_REFLECTION_RENDER, + DI_REFRACTION_RENDER, +// RAVEN END + DI_REMOTE_RENDER +} dynamicidImage_t; + +// note: keep opNames[] in sync with changes +typedef enum { + OP_TYPE_ADD, + OP_TYPE_SUBTRACT, + OP_TYPE_MULTIPLY, + OP_TYPE_DIVIDE, + OP_TYPE_MOD, + OP_TYPE_TABLE, + OP_TYPE_GT, + OP_TYPE_GE, + OP_TYPE_LT, + OP_TYPE_LE, + OP_TYPE_EQ, + OP_TYPE_NE, + OP_TYPE_AND, + OP_TYPE_OR, + OP_TYPE_SOUND +// RAVEN BEGIN +// rjohnson: new shader stage system + , + OP_TYPE_GLSL_ENABLED, + OP_TYPE_POT_X, + OP_TYPE_POT_Y, +// RAVEN END +} expOpType_t; + +typedef enum { + EXP_REG_TIME, + + EXP_REG_PARM0, + EXP_REG_PARM1, + EXP_REG_PARM2, + EXP_REG_PARM3, + EXP_REG_PARM4, + EXP_REG_PARM5, + EXP_REG_PARM6, + EXP_REG_PARM7, + EXP_REG_PARM8, + EXP_REG_PARM9, + EXP_REG_PARM10, + EXP_REG_PARM11, + + EXP_REG_GLOBAL0, + EXP_REG_GLOBAL1, + EXP_REG_GLOBAL2, + EXP_REG_GLOBAL3, + EXP_REG_GLOBAL4, + EXP_REG_GLOBAL5, + EXP_REG_GLOBAL6, + EXP_REG_GLOBAL7, + +// RAVEN BEGIN +// rjohnson: added vertex randomizing + EXP_REG_VERTEX_RANDOMIZER, +// RAVEN END + + EXP_REG_NUM_PREDEFINED +} expRegister_t; + +// RAVEN BEGIN +// rjohnson: added new decal support + +// decal registers +#define EXP_REG_DECAL_LIFE EXP_REG_PARM4 +#define REG_DECAL_LIFE 4 +#define EXP_REG_DECAL_SPAWN EXP_REG_PARM5 +#define REG_DECAL_SPAWN 5 +// RAVEN END + +typedef struct { + expOpType_t opType; + int a, b, c; +} expOp_t; + +typedef struct { + int registers[4]; +} colorStage_t; + +typedef enum { + TG_EXPLICIT, + TG_DIFFUSE_CUBE, + TG_REFLECT_CUBE, + TG_SKYBOX_CUBE, + TG_WOBBLESKY_CUBE, + TG_SCREEN // screen aligned, for mirrorRenders and screen space temporaries +} texgen_t; + +typedef struct { + idCinematic * cinematic; + idImage * image; + texgen_t texgen; + bool hasMatrix; + int matrix[2][3]; // we only allow a subset of the full projection matrix + + // dynamic image variables + dynamicidImage_t dynamic; + int width, height; + int dynamicFrameCount; +} textureStage_t; + +// the order BUMP / DIFFUSE / SPECULAR is necessary for interactions to draw correctly on low end cards +typedef enum { + SL_AMBIENT, // execute after lighting + SL_BUMP, + SL_DIFFUSE, + SL_SPECULAR +} stageLighting_t; + +// cross-blended terrain textures need to modulate the color by +// the vertex color to smoothly blend between two textures +typedef enum { + SVC_IGNORE, + SVC_MODULATE, + SVC_INVERSE_MODULATE +} stageVertexColor_t; + +static const int MAX_FRAGMENT_IMAGES = 8; +// RAVEN BEGIN +// AReis: Increased MAX_VERTEX_PARMS from 4 to 16 and added MAX_FRAGMENT_PARMS. +static const int MAX_VERTEX_PARMS = 16; +static const int MAX_FRAGMENT_PARMS = 8; +// RAVEN END + +typedef struct { + int vertexProgram; + +// RAVEN BEGIN +// dluetscher: added support for specifying MD5R specfic vertex programs +// Q4SDK: maintain compatible structure padding +#if defined( _MD5R_SUPPORT ) || defined( Q4SDK_MD5R ) + int md5rVertexProgram; +#endif +// RAVEN END + + int numVertexParms; + int vertexParms[MAX_VERTEX_PARMS][4]; // evaluated register indexes + +// RAVEN BEGIN +// AReis: New Fragment Parm stuff. + int numFragmentParms; + int fragmentParms[MAX_FRAGMENT_PARMS][4]; // evaluated register indexes +// RAVEN END + + int fragmentProgram; + int numFragmentProgramImages; + idImage * fragmentProgramImages[MAX_FRAGMENT_IMAGES]; + +// RAVEN BEGIN +// AReis: Custom Bindings. These override existing parm values. + bool vertexParmsBindings[MAX_VERTEX_PARMS]; + bool fragmentParmsBindings[MAX_FRAGMENT_PARMS]; + +// AReis: So fragment images can be bound to program specified binding, this +// list keeps track of which image to use (if any, 0 if use specified image). + int fragmentProgramBindings[MAX_FRAGMENT_IMAGES]; +// RAVEN END + + idMegaTexture *megaTexture; // handles all the binding and parameter setting +} newShaderStage_t; + +typedef struct { + int conditionRegister; // if registers[conditionRegister] == 0, skip stage + stageLighting_t lighting; // determines which passes interact with lights + int drawStateBits; + colorStage_t color; + bool hasAlphaTest; +// RAVEN BEGIN + bool hasAlphaFunc; + int alphaTestMode; +// RAVEN END + int alphaTestRegister; + textureStage_t texture; + stageVertexColor_t vertexColor; + bool ignoreAlphaTest; // this stage should act as translucent, even + // if the surface is alpha tested + float privatePolygonOffset; // a per-stage polygon offset + + newShaderStage_t *newStage; // vertex / fragment program based stage + +// RAVEN BEGIN +// rjohnson: new shader stage system + rvNewShaderStage *newShaderStage; + +// rjohnson: added new decal support + int mStageRegisterStart; + int mNumStageRegisters; + int mStageOpsStart; + int mNumStageOps; +// RAVEN END +} shaderStage_t; + +typedef enum { + MC_BAD, + MC_OPAQUE, // completely fills the triangle, will have black drawn on fillDepthBuffer + MC_PERFORATED, // may have alpha tested holes + MC_TRANSLUCENT // blended with background +} materialCoverage_t; + +typedef enum { + SS_MIN = -10000, +// RAVEN BEGIN + SS_SUBVIEW = -4, // mirrors, viewscreens, etc + SS_PREGUI = -3, // guis +// RAVEN END + SS_GUI = -2, // guis + SS_BAD = -1, + SS_OPAQUE, // opaque + + SS_PORTAL_SKY, + + SS_DECAL, // scorch marks, etc. + + SS_FAR, + SS_MEDIUM, // normal translucent + SS_CLOSE, + + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST, // screen blood blobs + + SS_POST_PROCESS = 100, // after a screen copy to texture + SS_MAX = 10000 +} materialSort_t; + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +// these don't effect per-material storage, so they can be very large +const int MAX_SHADER_STAGES = 256; + +const int MAX_TEXGEN_REGISTERS = 4; + +const int MAX_ENTITY_SHADER_PARMS = 12; + +// material flags +typedef enum { + MF_DEFAULTED = BIT(0), + MF_POLYGONOFFSET = BIT(1), + MF_NOSHADOWS = BIT(2), + MF_FORCESHADOWS = BIT(3), + MF_NOSELFSHADOW = BIT(4), + MF_NOPORTALFOG = BIT(5), // this fog volume won't ever consider a portal fogged out + MF_EDITOR_VISIBLE = BIT(6) // in use (visible) per editor +// RAVEN BEGIN +// jscott: for portal skies + , + MF_SKY = BIT(7), + MF_NEED_CURRENT_RENDER = BIT(8) // for hud guis that need sort order preseved but need back end too +// RAVEN END +} materialFlags_t; + +// contents flags, NOTE: make sure to keep the defines in doom_defs.script up to date with these! +typedef enum { + CONTENTS_SOLID = BIT(0), // an eye is never valid in a solid + CONTENTS_OPAQUE = BIT(1), // blocks visibility (for ai) + CONTENTS_WATER = BIT(2), // used for water + CONTENTS_PLAYERCLIP = BIT(3), // solid to players + CONTENTS_MONSTERCLIP = BIT(4), // solid to monsters + CONTENTS_MOVEABLECLIP = BIT(5), // solid to moveable entities + CONTENTS_IKCLIP = BIT(6), // solid to IK + CONTENTS_BLOOD = BIT(7), // used to detect blood decals + CONTENTS_BODY = BIT(8), // used for actors + CONTENTS_PROJECTILE = BIT(9), // used for projectiles + CONTENTS_CORPSE = BIT(10), // used for dead bodies + CONTENTS_RENDERMODEL = BIT(11), // used for render models for collision detection + CONTENTS_TRIGGER = BIT(12), // used for triggers + CONTENTS_AAS_SOLID = BIT(13), // solid for AAS + CONTENTS_AAS_OBSTACLE = BIT(14), // used to compile an obstacle into AAS that can be enabled/disabled + CONTENTS_FLASHLIGHT_TRIGGER = BIT(15), // used for triggers that are activated by the flashlight +// RAVEN BEGIN +// bdube: new clip that blocks monster visibility + CONTENTS_SIGHTCLIP = BIT(16), // used for blocking sight for actors and cameras + CONTENTS_LARGESHOTCLIP = BIT(17), // used to block large shots (fence that allows bullets through but not rockets for example) +// cdr: AASTactical + CONTENTS_NOTACTICALFEATURES = BIT(18), // don't place tactical features here + CONTENTS_VEHICLECLIP = BIT(19), // solid to vehicles + + // contents used by utils + CONTENTS_AREAPORTAL = BIT(20), // portal separating renderer areas + CONTENTS_NOCSG = BIT(21), // don't cut this brush with CSG operations in the editor + CONTENTS_FLYCLIP = BIT(22), // solid to vehicles + +// mekberg: added + CONTENTS_ITEMCLIP = BIT(23), // so items can collide + CONTENTS_PROJECTILECLIP = BIT(24), // unlike contents_projectile, projectiles only NOT hitscans +// RAVEN END + + CONTENTS_REMOVE_UTIL = ~(CONTENTS_AREAPORTAL|CONTENTS_NOCSG) +} contentsFlags_t; + +// surface types +const int NUM_SURFACE_BITS = 4; +const int MAX_SURFACE_TYPES = 1 << NUM_SURFACE_BITS; + +typedef enum { + SURFTYPE_NONE, // default type + SURFTYPE_METAL, + SURFTYPE_STONE, + SURFTYPE_FLESH, + SURFTYPE_WOOD, + SURFTYPE_CARDBOARD, + SURFTYPE_LIQUID, + SURFTYPE_GLASS, + SURFTYPE_PLASTIC, + SURFTYPE_RICOCHET, + SURFTYPE_10, + SURFTYPE_11, + SURFTYPE_12, + SURFTYPE_13, + SURFTYPE_14, + SURFTYPE_15 +} surfTypes_t; + +// surface flags +typedef enum { + SURF_TYPE_BIT0 = BIT(0), // encodes the material type (metal, flesh, concrete, etc.) + SURF_TYPE_BIT1 = BIT(1), // " + SURF_TYPE_BIT2 = BIT(2), // " + SURF_TYPE_BIT3 = BIT(3), // " + SURF_TYPE_MASK = ( 1 << NUM_SURFACE_BITS ) - 1, + + SURF_NODAMAGE = BIT(4), // never give falling damage + SURF_SLICK = BIT(5), // effects game physics + SURF_COLLISION = BIT(6), // collision surface + SURF_LADDER = BIT(7), // player can climb up this surface + SURF_NOIMPACT = BIT(8), // don't make missile explosions + SURF_NOSTEPS = BIT(9), // no footstep sounds + SURF_DISCRETE = BIT(10), // not clipped or merged by utilities + SURF_NOFRAGMENT = BIT(11), // dmap won't cut surface at each bsp boundary + SURF_NULLNORMAL = BIT(12), // renderbump will draw this surface as 0x80 0x80 0x80, which + // won't collect light from any angle +// RAVEN BEGIN +// bdube: added bounce + SURF_BOUNCE = BIT(13), // projectiles should bounce off this surface + +// dluetscher: added no T fix + SURF_NO_T_FIX = BIT(14), // merge surfaces (like decals), but does not try to T-fix them + +// RAVEN END +} surfaceFlags_t; + +class idSoundEmitter; + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idMaterial : public idDecl, public Serializable<'IMAT'> { +public: +// jsinger: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idMaterial( SerialInputStream &stream ); +#else +class idMaterial : public idDecl { +#endif +// rjohnson: new shader stage system + friend class rvNewShaderStage; + friend class rvGLSLShaderStage; +// RAVEN END + +public: + idMaterial(); + virtual ~idMaterial(); + + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + + // returns the internal image name for stage 0, which can be used + // for the renderer CaptureRenderToImage() call + // I'm not really sure why this needs to be virtual... + virtual const char *ImageName( void ) const; + + void ReloadImages( bool force ) const; +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + int streamCount; + int streamTimeStamp; + static int masterStreamTimeStamp; + bool StreamImages( bool inBackground ); + void UnstreamImages( void ); + void UpdateImage( int stage, const byte *data, int width, int height ); +#endif +// RAVEN END + // returns number of stages this material contains + const int GetNumStages( void ) const { return numStages; } + + // get a specific stage + const shaderStage_t *GetStage( const int index ) const { assert(index >= 0 && index < numStages); return &stages[index]; } + + // get the first bump map stage, or NULL if not present. + // used for bumpy-specular + const shaderStage_t *GetBumpStage( void ) const; + + // returns true if the material will draw anything at all. Triggers, portals, + // etc, will not have anything to draw. A not drawn surface can still castShadow, + // which can be used to make a simplified shadow hull for a complex object set + // as noShadow + bool IsDrawn( void ) const { return ( numStages > 0 || entityGui != 0 || gui != NULL ); } + + // returns true if the material will draw any non light interaction stages + bool HasAmbient( void ) const { return ( numAmbientStages > 0 ); } + + // returns true if material has a gui + bool HasGui( void ) const { return ( entityGui != 0 || gui != NULL ); } + + // returns true if the material will generate another view, either as + // a mirror or dynamic rendered image + bool HasSubview( void ) const { return hasSubview; } + + // returns true if the material will generate shadows, not making a + // distinction between global and no-self shadows + bool SurfaceCastsShadow( void ) const { return TestMaterialFlag( MF_FORCESHADOWS ) || !TestMaterialFlag( MF_NOSHADOWS ); } + + // returns true if the material will generate interactions with fog/blend lights + // All non-translucent surfaces receive fog unless they are explicitly noFog + bool ReceivesFog( void ) const { return ( IsDrawn() && !noFog && coverage != MC_TRANSLUCENT ); } + + // returns true if the material will generate interactions with normal lights + // Many special effect surfaces don't have any bump/diffuse/specular + // stages, and don't interact with lights at all + bool ReceivesLighting( void ) const { return numAmbientStages != numStages; } + + // returns true if the material should generate interactions on sides facing away + // from light centers, as with noshadow and noselfshadow options + bool ReceivesLightingOnBackSides( void ) const { return ( materialFlags & (MF_NOSELFSHADOW|MF_NOSHADOWS) ) != 0; } + + // Standard two-sided triangle rendering won't work with bump map lighting, because + // the normal and tangent vectors won't be correct for the back sides. When two + // sided lighting is desired. typically for alpha tested surfaces, this is + // addressed by having CleanupModelSurfaces() create duplicates of all the triangles + // with apropriate order reversal. + bool ShouldCreateBackSides( void ) const { return shouldCreateBackSides; } + + // characters and models that are created by a complete renderbump can use a faster + // method of tangent and normal vector generation than surfaces which have a flat + // renderbump wrapped over them. + bool UseUnsmoothedTangents( void ) const { return unsmoothedTangents; } + + // by default, monsters can have blood overlays placed on them, but this can + // be overrided on a per-material basis with the "noOverlays" material command. + // This will always return false for translucent surfaces + bool AllowOverlays( void ) const { return allowOverlays; } + + // MC_OPAQUE, MC_PERFORATED, or MC_TRANSLUCENT, for interaction list linking and + // dmap flood filling + // The depth buffer will not be filled for MC_TRANSLUCENT surfaces + // FIXME: what do nodraw surfaces return? + materialCoverage_t Coverage( void ) const { return coverage; } + + // returns true if this material takes precedence over other in coplanar cases + bool HasHigherDmapPriority( const idMaterial &other ) const { return ( IsDrawn() && !other.IsDrawn() ) || + ( Coverage() < other.Coverage() ); } + + // returns a idUserInterface if it has a global gui, or NULL if no gui + idUserInterface * GlobalGui( void ) const { return gui; } + + // a discrete surface will never be merged with other surfaces by dmap, which is + // necessary to prevent mutliple gui surfaces, mirrors, autosprites, and some other + // special effects from being combined into a single surface + // guis, merging sprites or other effects, mirrors and remote views are always discrete + bool IsDiscrete( void ) const { return ( entityGui || gui || deform != DFRM_NONE || sort == SS_SUBVIEW || + ( surfaceFlags & SURF_DISCRETE ) != 0 ); } + + // Normally, dmap chops each surface by every BSP boundary, then reoptimizes. + // For gigantic polygons like sky boxes, this can cause a huge number of planar + // triangles that make the optimizer take forever to turn back into a single + // triangle. The "noFragment" option causes dmap to only break the polygons at + // area boundaries, instead of every BSP boundary. This has the negative effect + // of not automatically fixing up interpenetrations, so when this is used, you + // should manually make the edges of your sky box exactly meet, instead of poking + // into each other. + bool NoFragment( void ) const { return ( surfaceFlags & SURF_NOFRAGMENT ) != 0; } + +// RAVEN BEGIN +// dluetscher: added SURF_NO_T_FIX to merge surfaces (like decals), but skipping any T-junction fixing + bool NoTFix( void ) const { return ( surfaceFlags & SURF_NO_T_FIX ) != 0; } +// RAVEN END + + //------------------------------------------------------------------ + +// RAVEN BEGIN +// jscott: added accessor + const rvDeclMatType * GetMaterialType( void ) const { return( materialType ); } + const rvDeclMatType * GetMaterialType( idVec2 &tc ) const; + byte * GetMaterialTypeArray( void ) const { return( materialTypeArray ); } + const char * GetMaterialTypeArrayName( void ) const { return( materialTypeArrayName.c_str() ); } + +// jscott: for profiling + int GetTexelCount( void ) const; + +// jscott: for error checking + bool HasDefaultedImage( void ) const; + +// jscott: for Radiant + idImage * GetDiffuseImage( void ) const; + +// AReis: New portal distance culling stuff. + float GetPortalNear( void ) const { return( portalDistanceNear ); } + float GetPortalFar( void ) const { return( portalDistanceFar ); } + const idImage * GetPortalImage( void ) const { return( portalImage ); } + +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + //================================================================== + // light shader specific functions, only called for light entities + + // lightshader option to fill with fog from viewer instead of light from center + bool IsFogLight() const { return fogLight; } + + // perform simple blending of the projection, instead of interacting with bumps and textures + bool IsBlendLight() const { return blendLight; } + + // an ambient light has non-directional bump mapping and no specular + bool IsAmbientLight() const { return ambientLight; } + + // implicitly no-shadows lights (ambients, fogs, etc) will never cast shadows + // but individual light entities can also override this value + bool LightCastsShadows() const { return TestMaterialFlag( MF_FORCESHADOWS ) || + ( !fogLight && !ambientLight && !blendLight && !TestMaterialFlag( MF_NOSHADOWS ) ); } + + // fog lights, blend lights, ambient lights, etc will all have to have interaction + // triangles generated for sides facing away from the light as well as those + // facing towards the light. It is debatable if noshadow lights should effect back + // sides, making everything "noSelfShadow", but that would make noshadow lights + // potentially slower than normal lights, which detracts from their optimization + // ability, so they currently do not. + bool LightEffectsBackSides() const { return fogLight || ambientLight || blendLight; } + + // NULL unless an image is explicitly specified in the shader with "lightFalloffShader " + idImage * LightFalloffImage() const { return lightFalloffImage; } + + //------------------------------------------------------------------ + + // returns the renderbump command line for this shader, or an empty string if not present + const char * GetRenderBump() const { return renderBump; }; + + // set specific material flag(s) + void SetMaterialFlag( const int flag ) const { materialFlags |= flag; } + + // clear specific material flag(s) + void ClearMaterialFlag( const int flag ) const { materialFlags &= ~flag; } + + // test for existance of specific material flag(s) + bool TestMaterialFlag( const int flag ) const { return ( materialFlags & flag ) != 0; } + + // get content flags + const int GetContentFlags( void ) const { return contentFlags; } + + // get surface flags + const int GetSurfaceFlags( void ) const { return surfaceFlags; } + + // gets name for surface type (stone, metal, flesh, etc.) + const surfTypes_t GetSurfaceType( void ) const { return static_cast( surfaceFlags & SURF_TYPE_MASK ); } + + // get material description + const char * GetDescription( void ) const { return desc; } + + // get sort order + const float GetSort( void ) const { return sort; } + // this is only used by the gui system to force sorting order + // on images referenced from tga's instead of materials. + // this is done this way as there are 2000 tgas the guis use + void SetSort( float s ) const { sort = s; }; + + // DFRM_NONE, DFRM_SPRITE, etc + deform_t Deform( void ) const { return deform; } + + // flare size, expansion size, etc + const int GetDeformRegister( int index ) const { return deformRegisters[index]; } + + // particle system to emit from surface and table for turbulent + const idDecl *GetDeformDecl( void ) const { return deformDecl; } + + // currently a surface can only have one unique texgen for all the stages + texgen_t Texgen() const; + + // wobble sky parms + const int * GetTexGenRegisters( void ) const { return texGenRegisters; } + + // get cull type + const cullType_t GetCullType( void ) const { return cullType; } + + float GetEditorAlpha( void ) const { return editorAlpha; } + + int GetEntityGui( void ) const { return entityGui; } + + decalInfo_t GetDecalInfo( void ) const { return decalInfo; } + + // spectrums are used for "invisible writing" that can only be + // illuminated by a light of matching spectrum + int Spectrum( void ) const { return spectrum; } + + float GetPolygonOffset( void ) const { return polygonOffset; } + + float GetSurfaceArea( void ) const { return surfaceArea; } + void AddToSurfaceArea( float area ) { surfaceArea += area; } + + //------------------------------------------------------------------ + + // returns the length, in milliseconds, of the videoMap on this material, + // or zero if it doesn't have one + int CinematicLength( void ) const; + + void CloseCinematic( void ) const; + + void ResetCinematicTime( int time ) const; + + void UpdateCinematic( int time ) const; + + //------------------------------------------------------------------ + + // gets an image for the editor to use + idImage * GetEditorImage( void ) const; + int GetImageWidth( void ) const; + int GetImageHeight( void ) const; + + void SetGui( const char *_gui ) const; + + // just for resource tracking + void SetImageClassifications( int tag ) const; + + //------------------------------------------------------------------ + + // returns number of registers this material contains + const int GetNumRegisters() const { return numRegisters; } + +// RAVEN BEGIN +// rjohnson: added vertex randomizing + // regs should point to a float array large enough to hold GetNumRegisters() floats + void EvaluateRegisters( float *regs, const float entityParms[MAX_ENTITY_SHADER_PARMS], + const struct viewDef_s *view, int soundEmitter = 0, idVec3 *randomizer = NULL ) const; +// RAVEN END + +// RAVEN BEGIN +// rjohnson: added new decal support + void EvaluateStageRegisters( int StageIndex, float *registers, const float shaderParms[MAX_ENTITY_SHADER_PARMS], float FloatTime) const; + +// rjohnson: started tracking image/material usage + void ClearUseCount( void ) { useCount = 0; } + void IncreaseUseCount( void ) { useCount++; globalUseCount++; } + int GetUseCount( void ) { return useCount; } + int GetGlobalUseCount( void ) const { return globalUseCount; } + void ResolveUse( void ); +// RAVEN END + + // if a material only uses constants (no entityParm or globalparm references), this + // will return a pointer to an internal table, and EvaluateRegisters will not need + // to be called. If NULL is returned, EvaluateRegisters must be used. + const float * ConstantRegisters() const; + + bool SuppressInSubview() const { return suppressInSubview; }; + bool IsPortalSky() const { return portalSky; }; + void AddReference(); + +private: + // parse the entire material + void CommonInit(); +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool ParseMaterial( idLexer &src ); +// RAVEN END + bool MatchToken( idLexer &src, const char *match ); + void ParseSort( idLexer &src ); + void ParseBlend( idLexer &src, shaderStage_t *stage ); + void ParseVertexParm( idLexer &src, newShaderStage_t *newStage ); +// RAVEN BEGIN +// AReis: New fragment parm stuff. + void ParseFragmentParm( idLexer &src, newShaderStage_t *newStage ); +// RAVEN END + void ParseFragmentMap( idLexer &src, newShaderStage_t *newStage ); + +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool ParseStage( idLexer &src, const textureRepeat_t trpDefault = TR_REPEAT ); +// RAVEN END + void ParseDeform( idLexer &src ); + void ParseDecalInfo( idLexer &src ); + bool CheckSurfaceParm( idToken *token ); + int GetExpressionConstant( float f ); + int GetExpressionTemporary( void ); + expOp_t * GetExpressionOp( void ); + int EmitOp( int a, int b, expOpType_t opType ); + int ParseEmitOp( idLexer &src, int a, expOpType_t opType, int priority ); + int ParseTerm( idLexer &src ); + int ParseExpressionPriority( idLexer &src, int priority ); + int ParseExpression( idLexer &src ); + void ClearStage( shaderStage_t *ss ); + int NameToSrcBlendMode( const idStr &name ); + int NameToDstBlendMode( const idStr &name ); + void MultiplyTextureMatrix( textureStage_t *ts, int registers[2][3] ); // FIXME: for some reason the const is bad for gcc and Mac + void SortInteractionStages(); +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool AddImplicitStages( const textureRepeat_t trpDefault = TR_REPEAT ); +// RAVEN END + void CheckForConstantRegisters(); + +private: + idStr desc; // description + idStr renderBump; // renderbump command options, without the "renderbump" at the start + + idImage * lightFalloffImage; + + int entityGui; // draw a gui with the idUserInterface from the renderEntity_t + // non zero will draw gui, gui2, or gui3 from renderEnitty_t + mutable idUserInterface *gui; // non-custom guis are shared by all users of a material + +// RAVEN BEGIN +// jscott: for material types + const rvDeclMatType *materialType; + byte *materialTypeArray; // an array of material type indices generated from the hit image + idStr materialTypeArrayName; + int MTAWidth; + int MTAHeight; + +// rjohnson: started tracking image/material usage + int useCount; + int globalUseCount; + +// AReis: New portal distance culling stuff. + float portalDistanceNear; + float portalDistanceFar; + idImage * portalImage; +// RAVEN END + + bool noFog; // surface does not create fog interactions + + int spectrum; // for invisible writing, used for both lights and surfaces + + float polygonOffset; + + int contentFlags; // content flags + int surfaceFlags; // surface flags + mutable int materialFlags; // material flags + + decalInfo_t decalInfo; + + + mutable float sort; // lower numbered shaders draw before higher numbered + deform_t deform; + int deformRegisters[4]; // numeric parameter for deforms + const idDecl *deformDecl; // for surface emitted particle deforms and tables + + int texGenRegisters[MAX_TEXGEN_REGISTERS]; // for wobbleSky + + materialCoverage_t coverage; + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + bool shouldCreateBackSides; + + bool fogLight; + bool blendLight; + bool ambientLight; + bool unsmoothedTangents; + bool hasSubview; // mirror, remote render, etc + bool allowOverlays; + + int numOps; + expOp_t * ops; // evaluate to make expressionRegisters + + int numRegisters; // + float * expressionRegisters; + + float * constantRegisters; // NULL if ops ever reference globalParms or entityParms + + int numStages; + int numAmbientStages; + + shaderStage_t * stages; + + struct mtrParsingData_s *pd; // only used during parsing + + float surfaceArea; // only for listSurfaceAreas + + // we defer loading of the editor image until it is asked for, so the game doesn't load up + // all the invisible and uncompressed images. + // If editorImage is NULL, it will atempt to load editorImageName, and set editorImage to that or defaultImage + idStr editorImageName; + mutable idImage * editorImage; // image used for non-shaded preview + float editorAlpha; + + bool suppressInSubview; + bool portalSky; + int refCount; +}; + +typedef idList idMatList; + +// RAVEN BEGIN +class rvMaterialEdit +{ +public: + virtual ~rvMaterialEdit() {} + virtual void SetGui( idMaterial *edit, const char *name ) = 0; + virtual int GetImageWidth( const idMaterial *edit ) const = 0; + virtual int GetImageHeight( const idMaterial *edit ) const = 0; +}; + +extern rvMaterialEdit *materialEdit; +// RAVEN END + +#endif /* !__MATERIAL_H__ */ diff --git a/source/renderer/Model.h b/source/renderer/Model.h new file mode 100644 index 0000000..81daa2d --- /dev/null +++ b/source/renderer/Model.h @@ -0,0 +1,567 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +/* +=============================================================================== + + Render Model + +=============================================================================== +*/ + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#include "../idlib/rvMemSys.h" +// RAVEN END + +// RAVEN BEGIN +// dluetscher: declare some classes for MD5R support +#ifdef _MD5R_SUPPORT +class rvMesh; +#endif +// RAVEN END + +// shared between the renderer, game, and Maya export DLL +#define MD5_VERSION_STRING "MD5Version" +#define MD5_MESH_EXT "md5mesh" +#define MD5_ANIM_EXT "md5anim" +#define MD5_CAMERA_EXT "md5camera" +#define MD5_VERSION 10 + +// using shorts for triangle indexes can save a significant amount of traffic, but +// to support the large models that renderBump loads, they need to be 32 bits +#if 1 + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef int glIndex_t; + +#else + +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef short glIndex_t; + +#endif + + +typedef struct { + // NOTE: making this a glIndex is dubious, as there can be 2x the faces as verts + glIndex_t p1, p2; // planes defining the edge + glIndex_t v1, v2; // verts defining the edge +} silEdge_t; + +// this is used for calculating unsmoothed normals and tangents for deformed models +typedef struct dominantTri_s { + glIndex_t v2, v3; + float normalizationScale[3]; +} dominantTri_t; + +typedef struct lightingCache_s { + idVec3 localLightVector; // this is the statically computed vector to the light + // in texture space for cards without vertex programs +} lightingCache_t; + +typedef struct shadowCache_s { + idVec4 xyz; // we use homogenous coordinate tricks +} shadowCache_t; + +// RAVEN BEGIN +// DLuetscher: Added the vertex cache neded for penumbra map support +#ifdef _PENUMBRA_MAP_SUPPORT +typedef struct penumbraCache_s { + idVec3 xyz; // local coordinates + idVec2 colorParam; +} penumbraCache_t; +#endif +// RAVEN END + +const int SHADOW_CAP_INFINITE = 64; + +// our only drawing geometry type +typedef struct srfTriangles_s { + idBounds bounds; // for culling + + int ambientViewCount; // if == tr.viewCount, it is visible this view + + bool generateNormals; // create normals from geometry, instead of using explicit ones + bool tangentsCalculated; // set when the vertex tangents have been calculated + bool facePlanesCalculated; // set when the face planes have been calculated + bool perfectHull; // true if there aren't any dangling edges + bool deformedSurface; // if true, indexes, silIndexes, mirrorVerts, and silEdges are + // pointers into the original surface, and should not be freed + + int numVerts; // number of vertices + idDrawVert * verts; // vertices, allocated with special allocator +// RAVEN BEGIN +// dluetscher: added support for the rvSilTraceVertT as a replacement for some system-memory idDrawVerts (MD5R case) +#ifdef _MD5R_SUPPORT + rvSilTraceVertT * silTraceVerts; // sil-trace vertices (system memory copy of verts for sil-trace usage) + rvSilTraceVertT * silTraceVertsAlloc; // if not NULL, same array of sil-trace vertices as above, but must be freed +#elif defined( Q4SDK_MD5R ) +// Q4SDK: maintain compatible structure padding + void* silTraceVerts; + void* silTraceVertsAlloc; +#endif +// RAVEN END + + int numIndexes; // for shadows, this has both front and rear end caps and silhouette planes + glIndex_t * indexes; // indexes, allocated with special allocator + + glIndex_t * silIndexes; // indexes changed to be the first vertex with same XYZ, ignoring normal and texcoords + + int numMirroredVerts; // this many verts at the end of the vert list are tangent mirrors + int * mirroredVerts; // tri->mirroredVerts[0] is the mirror of tri->numVerts - tri->numMirroredVerts + 0 + + int numDupVerts; // number of duplicate vertexes + int * dupVerts; // pairs of the number of the first vertex and the number of the duplicate vertex + + int numSilEdges; // number of silhouette edges + silEdge_t * silEdges; // silhouette edges + + idPlane * facePlanes; // [numIndexes/3] plane equations + + dominantTri_t * dominantTris; // [numVerts] for deformed surface fast tangent calculation + + int numShadowIndexesNoFrontCaps; // shadow volumes with front caps omitted + int numShadowIndexesNoCaps; // shadow volumes with the front and rear caps omitted + + int shadowCapPlaneBits; // bits 0-5 are set when that plane of the interacting light has triangles + // projected on it, which means that if the view is on the outside of that + // plane, we need to draw the rear caps of the shadow volume + // turboShadows will have SHADOW_CAP_INFINITE + + shadowCache_t * shadowVertexes; // these will be copied to shadowCache when it is going to be drawn. + // these are NULL when vertex programs are available + + struct srfTriangles_s * ambientSurface; // for light interactions, point back at the original surface that generated + // the interaction, which we will get the ambientCache from + + struct srfTriangles_s * nextDeferredFree; // chain of tris to free next frame + + // data in vertex object space, not directly readable by the CPU + struct vertCache_s * indexCache; // int + struct vertCache_s * ambientCache; // idDrawVert + struct vertCache_s * lightingCache; // lightingCache_t + struct vertCache_s * shadowCache; // shadowCache_t + +// RAVEN BEGIN +// dluetscher: Added the vertex cache neded for penumbra map support +#ifdef _PENUMBRA_MAP_SUPPORT + struct vertCache_s * penumbraCache; // penumbraCache_t +#endif +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added support for new style of meshes (rvMesh used by rvRenderModelMD5R) that +// are based on primitive batches of "static" geometry (can be skinned) whose +// vertices always live on the video card for the purposes of drawing +#ifdef _MD5R_SUPPORT + rvMesh * primBatchMesh; // rvMesh that is based on static vertex buffers, index buffers, and primitive batches + float * skinToModelTransforms; // array of skin-to-model transforms, 4x4, stored in row-major array ordering, with translation in last column (column-major matrix) + float * skinToModelTransformsAlloc; // if not NULL, same array of skin-to-model transforms as above, but must be freed + int numSkinToModelTransforms; // the number of skin-to-model transforms stored in the above array +#elif defined( Q4SDK_MD5R ) +// Q4SDK: maintain compatible structure padding + void* primBatchMesh; + float* skinToModelTransforms; + float* skinToModelTransformsAlloc; + int numSkinToModelTransforms; +#endif +// RAVEN END + +// RAVEN BEGIN +// jscott: for modview + bool modviewSelected; +// jscott: for security + int numAllocedVerts; + int numAllocedIndices; +// rjohnson: attempt to fix editor crashes + int referenceCount; + struct srfTriangles_s * topAmbientSurface; + int myID; + bool noAmbientSurfaces; + bool didSilPremultiply; + bool tempAmbientCache; +#ifdef _DEBUG + char description[64]; +#endif +// RAVEN END +} srfTriangles_t; + +typedef idList idTriList; + +typedef struct modelSurface_s { + int id; + const idMaterial * shader; + srfTriangles_t * geometry; + +// RAVEN BEGIN +// rjohnson: added block + idStr* mOriginalSurfaceName; +// RAVEN END +} modelSurface_t; + +// RAVEN BEGIN +// bdube: tag system +typedef struct modelTag_s { + idStr name; + idVec3 t; + idMat3 m; +} modelTag_t; +// RAVEN END + +typedef enum { + DM_STATIC, // never creates a dynamic model + DM_CACHED, // once created, stays constant until the entity is updated (animating characters) + DM_CONTINUOUS // must be recreated for every single view (time dependent things like particles) +} dynamicModel_t; + +typedef enum { + INVALID_JOINT = -1 +} jointHandle_t; + +struct jointWeight_t { + float weight; // joint weight + int jointMatOffset; // offset in bytes to the joint matrix + int nextVertexOffset; // offset in bytes to the first weight for the next vertex +}; + +// offsets for SIMD code +#define BASEVECTOR_SIZE (4*4) // sizeof( idVec4 ) +#define JOINTWEIGHT_SIZE (3*4) // sizeof( jointWeight_t ) +#define JOINTWEIGHT_WEIGHT_OFFSET (0*4) // offsetof( jointWeight_t, weight ) +#define JOINTWEIGHT_JOINTMATOFFSET_OFFSET (1*4) // offsetof( jointWeight_t, jointMatOffset ) +#define JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET (2*4) // offsetof( jointWeight_t, nextVertexOffset ) + +assert_sizeof( idVec4, BASEVECTOR_SIZE ); +assert_sizeof( jointWeight_t, JOINTWEIGHT_SIZE ); +assert_offsetof( jointWeight_t, weight, JOINTWEIGHT_WEIGHT_OFFSET ); +assert_offsetof( jointWeight_t, jointMatOffset, JOINTWEIGHT_JOINTMATOFFSET_OFFSET ); +assert_offsetof( jointWeight_t, nextVertexOffset, JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET ); + +class idMD5Joint { +public: + idMD5Joint() { parent = NULL; } + idStr name; + const idMD5Joint * parent; +}; + +// RAVEN BEGIN +// AReis: Used for callback. +class idRenderModel; +typedef bool(*modelCallback_t)( idRenderModel *model, void *callbackData ); +// RAVEN END + +// RAVEN BEGIN +// Used for Fluid Interaction. +typedef struct fluidImpact_s +{ + // The absolute position of the impact. + idVec3 vAbsPos; + + // The force of the impact. + float fForce; + + float radius; + +} fluidImpact_t; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + +// Create a new idRenderModel instance or one of it's derivitive classes, such +// that any further memory allocated by the instance can be directed to the same +// heap. +template +T* NewRenderModel(Rv_Sys_Heap_ID_t heapID) +{ + // Push the heap context to be used for both instance and all allocations + // that the instance makes. + RV_PUSH_SYS_HEAP_ID(heapID); + + T* model=new T; + assert(static_cast(model)!=0); +#if defined(_DEBUG) + if(model) + { + model->heapID=heapID; + } +#endif + + // Pop the heap context. + RV_POP_HEAP(); + + return model; +} + +// Create a new idRenderModel instance or one of it's derivitive classes, such +// that any further memory allocated by the instance can be directed to the same +// heap. +template +T* NewRenderModel(const idRenderModel* m) +{ + // Push the heap context to be used for both instance and all allocations + // that the instance makes. + bool ok=rvPushHeapContainingMemory(m); + + T* model=new T; + assert(static_cast(model)!=0); +#if defined(_DEBUG) + if(model) + { + model->heapPtr=currentHeapArena->GetHeap(const_cast((const void*)m)); + } +#endif + + // Pop the heap context. + if(ok) + { + RV_POP_HEAP(); + } + + return model; +} + +#else + +// Single heap memory model. +template +T* NewRenderModel(Rv_Sys_Heap_ID_t) +{ + return new T; +} + +// Single heap memory model. +template +T* NewRenderModel(const idRenderModel*) +{ + return new T; +} + +#endif +// RAVEN END + +// the init methods may be called again on an already created model when +// a reloadModels is issued +class idRenderModel { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) && defined(_DEBUG) +public: + // Debug info... the heap this instance was allocated into. + Rv_Sys_Heap_ID_t heapID; + rvHeap* heapPtr; +#endif +// RAVEN END + +public: +// RAVEN BEGIN +// AReis: Needed to send data to model. + // Callbacks to a model specified function. + modelCallback_t callback; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // All the materials that are referenced by an instance of an idRenderModel. + idList allMaterials; +#endif + +// AReis: Specific just to a fluid model. + // Dampen a grid element that intersects the world. + virtual void DampenFluidGrid( int iX, int iY, float fAmount ) {} + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) && defined(_DEBUG) + idRenderModel( void ) : + heapID(RV_HEAP_ID_DEFAULT), + heapPtr(0) + { + } +#endif + +// rjohnson: added debugging code to try and catch a free error + // purges all the data before deleting + virtual ~idRenderModel( void ); +// RAVEN END + + // Loads static models only, dynamic models must be loaded by the modelManager + virtual void InitFromFile( const char *fileName ) = 0; + + // renderBump uses this to load the very high poly count models, skipping the + // shadow and tangent generation, along with some surface cleanup to make it load faster + virtual void PartialInitFromFile( const char *fileName ) = 0; + + // this is used for dynamically created surfaces, which are assumed to not be reloadable. + // It can be called again to clear out the surfaces of a dynamic model for regeneration. + virtual void InitEmpty( const char *name ) = 0; + +// RAVEN BEGIN +// AReis: Added this function for the height map model. + // Like InitEmpty but allows a set of arguments to be passed in through a dict. + virtual void InitEmptyFromArgs( const char *name, idDict &Args ) = 0; +// RAVEN END + + // dynamic model instantiations will be created with this + // the geometry data will be owned by the model, and freed when it is freed + // the geoemtry should be raw triangles, with no extra processing + virtual void AddSurface( modelSurface_t surface ) = 0; + + // cleans all the geometry and performs cross-surface processing + // like shadow hulls + // Creates the duplicated back side geometry for two sided, alpha tested, lit materials + // This does not need to be called if none of the surfaces added with AddSurface require + // light interaction, and all the triangles are already well formed. + virtual void FinishSurfaces( void ) = 0; + + // frees all the data, but leaves the class around for dangling references, + // which can regenerate the data with LoadModel() + virtual void PurgeModel() = 0; + + // resets any model information that needs to be reset on a same level load etc.. + // currently only implemented for liquids + virtual void Reset() = 0; + + // used for initial loads, reloadModel, and reloading the data of purged models + // Upon exit, the model will absolutely be valid, but possibly as a default model + virtual void LoadModel() = 0; + + // internal use + virtual bool IsLoaded() = 0; + virtual void SetLevelLoadReferenced( bool referenced ) = 0; + virtual bool IsLevelLoadReferenced() = 0; + + // models that are already loaded at level start time + // will still touch their data to make sure they + // are kept loaded + virtual void TouchData() = 0; + + // dump any ambient caches on the model surfaces + virtual void FreeVertexCache() = 0; + + // returns the name of the model + virtual const char * Name() const = 0; + + // prints a detailed report on the model for printModel + virtual void Print() const = 0; + + // prints a single line report for listModels + virtual void List() const = 0; + + // reports the amount of memory (roughly) consumed by the model + virtual int Memory() const = 0; + + // for reloadModels + virtual unsigned int Timestamp() const = 0; + + // returns the number of surfaces + virtual int NumSurfaces() const = 0; + + // NumBaseSurfaces will not count any overlays added to dynamic models + virtual int NumBaseSurfaces() const = 0; + + // get a pointer to a surface + virtual const modelSurface_t *Surface( int surfaceNum ) const = 0; + + // Allocates surface triangles. + // Allocates memory for srfTriangles_t::verts and srfTriangles_t::indexes + // The allocated memory is not initialized. + // srfTriangles_t::numVerts and srfTriangles_t::numIndexes are set to zero. + virtual srfTriangles_t * AllocSurfaceTriangles( int numVerts, int numIndexes ) const = 0; + + // Frees surfaces triangles. + virtual void FreeSurfaceTriangles( srfTriangles_t *tris ) const = 0; + + // created at load time by stitching together all surfaces and sharing + // the maximum number of edges. This may be incorrect if a skin file + // remaps surfaces between shadow casting and non-shadow casting, or + // if some surfaces are noSelfShadow and others aren't + virtual srfTriangles_t * ShadowHull( void ) const = 0; + + // models of the form "_area*" may have a prelight shadow model associated with it + virtual bool IsStaticWorldModel( void ) const = 0; + + // models parsed from inside map files or dynamically created cannot be reloaded by + // reloadmodels + virtual bool IsReloadable( void ) const = 0; + + // md3, md5, particles, etc + virtual dynamicModel_t IsDynamicModel( void ) const = 0; + + // if the load failed for any reason, this will return true + virtual bool IsDefaultModel( void ) const = 0; + + // dynamic models should return a fast, conservative approximation + // static models should usually return the exact value + virtual idBounds Bounds( const struct renderEntity_s *ent = NULL ) const = 0; + + // returns value != 0.0f if the model requires the depth hack + virtual float DepthHack( void ) const = 0; + +// RAVEN BEGIN +// dluetscher: added call to determine if a collision surface exists within this model + virtual bool HasCollisionSurface( const struct renderEntity_s *ent ) const = 0; +// RAVEN END + + // returns a static model based on the definition and view + // currently, this will be regenerated for every view, even though + // some models, like character meshes, could be used for multiple (mirror) + // views in a frame, or may stay static for multiple frames (corpses) + // The renderer will delete the returned dynamic model the next view + // This isn't const, because it may need to reload a purged model if it + // wasn't precached correctly. +// RAVEN BEGIN +// dluetscher: added surface mask parameter + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask = ~SURF_COLLISION ) = 0; +// RAVEN END + + // Returns the number of joints or 0 if the model is not an MD5 + virtual int NumJoints( void ) const = 0; + + // Returns the MD5 joints or NULL if the model is not an MD5 + virtual const idMD5Joint * GetJoints( void ) const = 0; + + // Returns the handle for the joint with the given name. + virtual jointHandle_t GetJointHandle( const char *name ) const = 0; + + // Returns the name for the joint with the given handle. + virtual const char * GetJointName( jointHandle_t handle ) const = 0; + + // Returns the default animation pose or NULL if the model is not an MD5. + virtual const idJointQuat * GetDefaultPose( void ) const = 0; + + // Returns number of the joint nearest to the given triangle. + virtual int NearestJoint( int surfaceNum, int a, int c, int b ) const = 0; + + // Writing to and reading from a demo file. + virtual void ReadFromDemo( class idDemoFile *f ) = 0; + virtual void WriteToDemo( class idDemoFile *f ) = 0; + +// RAVEN BEGIN +// bdube: surface flag manipulation + virtual int GetSurfaceMask ( const char* surface ) const = 0;; + +// jscott: for portal skies + virtual void SetHasSky( bool on ) = 0; + virtual bool GetHasSky( void ) const = 0; +// ddynerman: Wolf LOD code + virtual void SetViewEntity( const struct viewEntity_s *ve ) = 0; +// RAVEN END + +// RAVEN BEGIN +#if defined( _MD5R_SUPPORT ) +// dluetscher: added method to determine if model maintains system-memory dynamic meshes (used +// for traces and silhouette determination) that are separate from pairs of video-memory +// meshes - one used for shadow volume drawing and one used for normal interaction drawing +// NOTE: currently, only MD5R models return true, all others return false + virtual bool HasSeparateSilTraceMeshes( void ) const; +#endif +// RAVEN END + +// RAVEN END +}; + +#endif /* !__MODEL_H__ */ diff --git a/source/renderer/ModelManager.h b/source/renderer/ModelManager.h new file mode 100644 index 0000000..39f5e03 --- /dev/null +++ b/source/renderer/ModelManager.h @@ -0,0 +1,109 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MODELMANAGER_H__ +#define __MODELMANAGER_H__ + +// RAVEN BEGIN +// Modview +#define MVJOINT_SELECTED BIT( 0 ) +#define MVJOINT_HIDDEN BIT( 1 ) +// RAVEN END + +/* +=============================================================================== + + Model Manager + + Temporarily created models do not need to be added to the model manager. + +=============================================================================== +*/ + +class idRenderModelManager { +public: + virtual ~idRenderModelManager() {} + + // registers console commands and clears the list + virtual void Init() = 0; + + // frees all the models + virtual void Shutdown() = 0; + +// RAVEN BEGIN + // Reset list of model to initial state (i.e. destroys all models except the default models). + // This also will void the incremental creep in memory consumption as one progresses form + // one level to the next, since we delete the idRenderModel instances which can add up to + // several MB. + virtual void Reset(void) = 0; +// RAVEN END + + // called only by renderer::BeginLevelLoad + virtual void BeginLevelLoad() = 0; + + // called only by renderer::EndLevelLoad + virtual void EndLevelLoad() = 0; + + // allocates a new empty render model. + virtual idRenderModel * AllocModel() = 0; + + // frees a render model + virtual void FreeModel( idRenderModel *model ) = 0; + + // returns NULL if modelName is NULL or an empty string, otherwise + // it will create a default model if not loadable + virtual idRenderModel * FindModel( const char *modelName ) = 0; + + // returns NULL if not loadable + virtual idRenderModel * CheckModel( const char *modelName ) = 0; + +// RAVEN BEGIN +// jscott: for tools + virtual srfTriangles_t *AllocStaticTriSurf( int verts, int indices ) = 0; + virtual void FreeStaticTriSurf( srfTriangles_t *tris ) = 0; + virtual srfTriangles_t *CopyStaticTriSurf( const srfTriangles_t *tri ) = 0; + virtual srfTriangles_t *PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ) = 0; + virtual void CreateSilIndexes( srfTriangles_t *tris ) = 0; + virtual void DeriveFacePlanes( srfTriangles_t *tris ) = 0; + virtual void BoundTriSurf( srfTriangles_t *tri ) = 0; + virtual void CleanupTriangles( srfTriangles_t *tris, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents, bool needSilMultiply ) = 0; + virtual void SimpleCleanupTriangles( srfTriangles_t *tri ) = 0; + virtual srfTriangles_t *CreateShadowVolume( const srfTriangles_t *tri, const class idRenderLight *light, int optimize ) = 0; + + virtual class idRenderLight *CreateLightDef( void ) = 0; + virtual void FreeLightDef( class idRenderLight *light ) = 0; + +// rjohnson: added debugging code to try and catch a free error + virtual bool CheckModel( idRenderModel *model ) = 0; +// RAVEN END + + // returns the default cube model + virtual idRenderModel * DefaultModel() = 0; + + // world map parsing will add all the inline models with this call + virtual void AddModel( idRenderModel *model ) = 0; + + // when a world map unloads, it removes its internal models from the list + // before freeing them. + // There may be an issue with multiple renderWorlds that share data... + virtual void RemoveModel( idRenderModel *model ) = 0; + + // the reloadModels console command calls this, but it can + // also be explicitly invoked + virtual void ReloadModels( bool forceAll = false ) = 0; + + // write "touchModel " commands for each non-world-map model + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // called during vid_restart + virtual void FreeModelVertexCaches() = 0; + + // print memory info + virtual void PrintMemInfo( MemInfo *mi ) = 0; + virtual size_t ListModelSummary( void ) = 0; +}; + +// this will be statically pointed at a private implementation +extern idRenderModelManager *renderModelManager; + +#endif /* !__MODELMANAGER_H__ */ diff --git a/source/renderer/RenderSystem.h b/source/renderer/RenderSystem.h new file mode 100644 index 0000000..86db303 --- /dev/null +++ b/source/renderer/RenderSystem.h @@ -0,0 +1,373 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __RENDERER_H__ +#define __RENDERER_H__ + +/* +=============================================================================== + + idRenderSystem is responsible for managing the screen, which can have + multiple idRenderWorld and 2D drawing done on it. + +=============================================================================== +*/ + + +// Contains variables specific to the OpenGL configuration being run right now. +// These are constant once the OpenGL subsystem is initialized. +typedef struct glconfig_s { + const char *renderer_string; + const char *vendor_string; + const char *version_string; + const char *extensions_string; + const char *wgl_extensions_string; + + float glVersion; // atof( version_string ) + + + int maxTextureSize; // queried from GL + int maxTextureUnits; + int maxTextureCoords; + int maxTextureImageUnits; + float maxTextureAnisotropy; + + int colorBits, depthBits, stencilBits; +// RAVEN BEGIN + int alphaBits; +// RAVEN END + + bool multitextureAvailable; + bool anisotropicAvailable; + bool textureLODBiasAvailable; + bool textureEnvAddAvailable; + bool textureEnvCombineAvailable; +// RAVEN BEGIN +// jscott: added + bool blendSquareAvailable; +// RAVEN END + bool registerCombinersAvailable; + bool cubeMapAvailable; + bool envDot3Available; + bool texture3DAvailable; + bool sharedTexturePaletteAvailable; +// RAVEN BEGIN +// dluetscher: added + bool drawRangeElementsAvailable; + bool blendMinMaxAvailable; + bool floatBufferAvailable; +// RAVEN END + bool ARBVertexBufferObjectAvailable; + bool ARBVertexProgramAvailable; + bool ARBFragmentProgramAvailable; + bool twoSidedStencilAvailable; + bool textureNonPowerOfTwoAvailable; + bool depthBoundsTestAvailable; + +// RAVEN BEGIN +// rjohnson: new shader stage system + bool GLSLProgramAvailable; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added check for NV_vertex_program and NV_fragment_program support + bool nvProgramsAvailable; +// RAVEN END + + // ati r200 extensions + bool atiFragmentShaderAvailable; + + // ati r300 + bool atiTwoSidedStencilAvailable; + + int vidWidth, vidHeight; // passed to R_BeginFrame + + int displayFrequency; + + bool isFullscreen; + +#ifndef _CONSOLE + bool preferNV20Path; // for FX5200 cards +// RAVEN BEGIN +// dluetscher: added preferSimpleLighting flag + bool preferSimpleLighting; // for the ATI 9700 cards +// RAVEN END +#endif + + bool allowNV20Path; + bool allowNV10Path; + bool allowR200Path; + bool allowARB2Path; + + bool isInitialized; +// INTEL BEGIN +// Anu adding support to toggle SMP +#ifdef ENABLE_INTEL_SMP + bool isSmpAvailable; + int isSmpActive; + int rearmSmp; +#endif +// INTEL END + +} glconfig_t; + + +// font support +#define GLYPH_COUNT 256 + +typedef struct glyphInfo_s +{ + float width; // number of pixels wide + float height; // number of scan lines + float horiAdvance; // number of pixels to advance to the next char + float horiBearingX; // x offset into space to render glyph + float horiBearingY; // y offset + float s1; // x start tex coord + float t1; // y start tex coord + float s2; // x end tex coord + float t2; // y end tex coord +} glyphInfo_t; + +typedef struct fontInfo_s +{ + glyphInfo_t glyphs[GLYPH_COUNT]; + + float pointSize; + float fontHeight; // max height of font + float ascender; + float descender; + + idMaterial *material; +} fontInfo_t; + +typedef struct { + fontInfo_t fontInfoSmall; + fontInfo_t fontInfoMedium; + fontInfo_t fontInfoLarge; + float maxHeight; + float maxWidth; + float maxHeightSmall; + float maxWidthSmall; + float maxHeightMedium; + float maxWidthMedium; + float maxHeightLarge; + float maxWidthLarge; + char name[64]; +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END +} fontInfoEx_t; + +const int TINYCHAR_WIDTH = 4; +const int TINYCHAR_HEIGHT = 8; +const int SMALLCHAR_WIDTH = 8; +const int SMALLCHAR_HEIGHT = 16; +const int BIGCHAR_WIDTH = 16; +const int BIGCHAR_HEIGHT = 16; + +// all drawing is done to a 640 x 480 virtual screen size +// and will be automatically scaled to the real resolution +const int SCREEN_WIDTH = 640; +const int SCREEN_HEIGHT = 480; + +// RAVEN BEGIN +// rjohnson: new blur special effect +typedef enum +{ + SPECIAL_EFFECT_NONE = 0, + SPECIAL_EFFECT_BLUR = 0x00000001, + SPECIAL_EFFECT_AL = 0x00000002, + SPECIAL_EFFECT_MAX, +} ESpecialEffectType; +// RAVEN END + +class idRenderWorld; + + +class idRenderSystem { +public: + + virtual ~idRenderSystem() {} + + // set up cvars and basic data structures, but don't + // init OpenGL, so it can also be used for dedicated servers +// RAVEN BEGIN + // nrausch: Init things that touch the render state seperately + virtual void DeferredInit( void ) { } +// RAVEN END + virtual void Init( void ) = 0; + + // only called before quitting + virtual void Shutdown( void ) = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushLevelImages( void ) = 0; +#endif +// RAVEN END + + virtual void InitOpenGL( void ) = 0; + + virtual void ShutdownOpenGL( void ) = 0; + + virtual bool IsOpenGLRunning( void ) const = 0; + virtual void GetValidModes( idStr &Mode4x3Text, idStr &Mode4x3Values, idStr &Mode16x9Text, idStr &Mode16x9Values, + idStr &Mode16x10Text, idStr &Mode16x10Values ) = 0; + + virtual bool IsFullScreen( void ) const = 0; + virtual int GetScreenWidth( void ) const = 0; + virtual int GetScreenHeight( void ) const = 0; + + // allocate a renderWorld to be used for drawing + virtual idRenderWorld * AllocRenderWorld( void ) = 0; + virtual void FreeRenderWorld( idRenderWorld * rw ) = 0; + +// RAVEN BEGIN + virtual void RemoveAllModelReferences( idRenderModel *model ) = 0; +// RAVEN BEGIN + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) + // Like BeginLevelLoad and EndLevelLoad, but only deals with textures. + virtual void XenonBeginLevelLoadTextures( void ) = 0; + virtual void XenonEndLevelLoadTextures( void ) = 0; +#endif +// RAVEN END + +#ifdef Q4SDK_MD5R + + virtual void ExportMD5R( bool compressed ) = 0; + virtual void CopyPrimBatchTriangles( idDrawVert *destDrawVerts, glIndex_t *destIndices, void *primBatchMesh, void *silTraceVerts ) = 0; + +#else // Q4SDK_MD5R + +// RAVEN BEGIN +// dluetscher: added call to write out the MD5R models that have been converted at load, +// also added call to retrieve idDrawVert geometry from a MD5R primitive batch +// from the game DLL +#if defined( _MD5R_WRITE_SUPPORT ) && defined( _MD5R_SUPPORT ) + virtual void ExportMD5R( bool compressed ) = 0; +#endif +#if defined( _MD5R_SUPPORT ) + virtual void CopyPrimBatchTriangles( idDrawVert *destDrawVerts, glIndex_t *destIndices, rvMesh *primBatchMesh, const rvSilTraceVertT *silTraceVerts ) = 0; +#endif + +#endif // !Q4SDK_MD5R + +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + enum TextureTrackCommand { + TEXTURE_TRACK_BEGIN, + TEXTURE_TRACK_UPDATE, + TEXTURE_TRACK_END + }; + virtual void TrackTextureUsage( TextureTrackCommand command, int frametime = 0, const char *name=NULL ) = 0; +#endif +// RAVEN END + + // font support + virtual bool RegisterFont( const char *fontName, fontInfoEx_t &font ) = 0; + // GUI drawing just involves shader parameter setting and axial image subsections + virtual void SetColor( const idVec4 &rgba ) = 0; + virtual void SetColor4( float r, float g, float b, float a ) = 0; + + virtual void DrawStretchPic( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *material, bool clip = true ) = 0; + virtual void DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) = 0; +// RAVEN BEGIN +// jnewquist: Deal with flipped back-buffer copies on Xenon + virtual void DrawStretchCopy( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) = 0; +// RAVEN END + + virtual void DrawStretchTri ( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ) = 0; + virtual void GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) = 0; + virtual void GetGLSettings( int& width, int& height ) = 0; + virtual void PrintMemInfo( MemInfo *mi ) = 0; + + virtual void DrawTinyChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawTinyStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + virtual void DrawSmallChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + virtual void DrawBigChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + + // dump all 2D drawing so far this frame to the demo file + virtual void WriteDemoPics() = 0; + + // draw the 2D pics that were saved out with the current demo frame + virtual void DrawDemoPics() = 0; + + // FIXME: add an interface for arbitrary point/texcoord drawing + + + // a frame cam consist of 2D drawing and potentially multiple 3D scenes + // window sizes are needed to convert SCREEN_WIDTH / SCREEN_HEIGHT values + virtual void BeginFrame( int windowWidth, int windowHeight ) = 0; +// RAVEN BEGIN + virtual void BeginFrame( struct viewDef_s *viewDef, int windowWidth, int windowHeight ) = 0; + virtual void RenderLightFrustum( const struct renderLight_s &renderLight, idPlane lightFrustum[6] ) = 0; + virtual void LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ) = 0; + virtual void ToggleSmpFrame( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// rjohnson: new blur special effect + virtual void SetSpecialEffect( ESpecialEffectType Which, bool Enabled ) = 0; + virtual void SetSpecialEffectParm( ESpecialEffectType Which, int Parm, float Value ) = 0; + virtual void ShutdownSpecialEffects( void ) = 0; +// RAVEN END + + // if the pointers are not NULL, timing info will be returned + virtual void EndFrame( int *frontEndMsec = NULL, int *backEndMsec = NULL, int *numVerts = NULL, int *numIndexes = NULL ) = 0; + + // aviDemo uses this. + // Will automatically tile render large screen shots if necessary + // Samples is the number of jittered frames for anti-aliasing + // If ref == NULL, session->updateScreen will be used + // This will perform swapbuffers, so it is NOT an approppriate way to + // generate image files that happen during gameplay, as for savegame + // markers. Use WriteRender() instead. +// RAVEN BEGIN +// rjohnson: added basePath + virtual void TakeJPGScreenshot( int width, int height, const char *fileName, int blends, struct renderView_s *ref, const char *basePath = "fs_savepath" ) = 0; + virtual void TakeScreenshot( int width, int height, const char *fileName, int blends, struct renderView_s *ref, const char *basePath = "fs_savepath" ) = 0; +// RAVEN END + + // the render output can be cropped down to a subset of the real screen, as + // for save-game reviews and split-screen multiplayer. Users of the renderer + // will not know the actual pixel size of the area they are rendering to + + // the x,y,width,height values are in virtual SCREEN_WIDTH / SCREEN_HEIGHT coordinates + + // to render to a texture, first set the crop size with makePowerOfTwo = true, + // then perform all desired rendering, then capture to an image + // if the specified physical dimensions are larger than the current cropped region, they will be cut down to fit + virtual void CropRenderSize( int width, int height, bool makePowerOfTwo = false, bool forceDimensions = false ) = 0; + virtual void CaptureRenderToImage( const char *imageName ) = 0; + virtual void CaptureRenderToMemory( void *buffer ) = 0; + // fixAlpha will set all the alpha channel values to 0xff, which allows screen captures + // to use the default tga loading code without having dimmed down areas in many places + virtual void CaptureRenderToFile( const char *fileName, bool fixAlpha = false ) = 0; + virtual void UnCrop() = 0; + virtual void GetCardCaps( bool &oldCard, bool &nv10or20 ) = 0; + virtual bool UploadImage( const char *imageName, const byte *data, int width, int height ) = 0; + + virtual void DebugGraph( float cur, float min, float max, const idVec4 &color ) = 0; + virtual void ShowDebugGraph( void ) = 0; +}; + +extern idRenderSystem * renderSystem; + +#endif /* !__RENDERER_H__ */ diff --git a/source/renderer/RenderWorld.h b/source/renderer/RenderWorld.h new file mode 100644 index 0000000..c8f328f --- /dev/null +++ b/source/renderer/RenderWorld.h @@ -0,0 +1,628 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __RENDERWORLD_H__ +#define __RENDERWORLD_H__ + +/* +=============================================================================== + + Render World + +=============================================================================== +*/ + +// RAVEN BEGIN +// jscott: new proc format +//#define PROC_FILE_ID "mapProcFile003" +#define PROC_FILE_ID "PROC" +#define PROC_FILE_EXT "proc" +#define PROC_FILEVERSION 4 +// RAVEN END + +// RAVEN BEGIN +// dluetscher: new MD5RProc file format +#define MD5R_PROC_FILE_ID "MD5RProcVersion" +#define MD5R_PROC_FILE_EXT "MD5RProc" +#define MD5R_PROC_FILEVERSION 4 +// RAVEN END + +// shader parms +const int MAX_GLOBAL_SHADER_PARMS = 12; + +const int SHADERPARM_RED = 0; +const int SHADERPARM_GREEN = 1; +const int SHADERPARM_BLUE = 2; +const int SHADERPARM_ALPHA = 3; +const int SHADERPARM_TIMESCALE = 3; +const int SHADERPARM_TIMEOFFSET = 4; +const int SHADERPARM_DIVERSITY = 5; // random between 0.0 and 1.0 for some effects (muzzle flashes, etc) +const int SHADERPARM_MODE = 7; // for selecting which shader passes to enable +const int SHADERPARM_TIME_OF_DEATH = 7; // for the monster skin-burn-away effect enable and time offset + +// model parms +const int SHADERPARM_MD5_SKINSCALE = 8; // for scaling vertex offsets on md5 models (jack skellington effect) + +const int SHADERPARM_MD3_FRAME = 8; +const int SHADERPARM_MD3_LASTFRAME = 9; +const int SHADERPARM_MD3_BACKLERP = 10; + +const int SHADERPARM_BEAM_END_X = 8; // for _beam models +const int SHADERPARM_BEAM_END_Y = 9; +const int SHADERPARM_BEAM_END_Z = 10; +const int SHADERPARM_BEAM_WIDTH = 11; + +const int SHADERPARM_SPRITE_WIDTH = 8; +const int SHADERPARM_SPRITE_HEIGHT = 9; + +const int SHADERPARM_PARTICLE_STOPTIME = 8; // don't spawn any more particles after this time + +// guis +const int MAX_RENDERENTITY_GUI = 3; + +// RAVEN BEGIN +// jscott: for effect brightness +const int SHADERPARM_BRIGHTNESS = 6; // for the overall brightness of effects +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added a default value for light detail levels +#define DEFAULT_LIGHT_DETAIL_LEVEL 10.f +// RAVEN END + +enum { + RD_MISC = 0, + RD_RENDERVIEW, + RD_RENDER_STATE, + + RD_RENDERENT_UPDATE, + RD_RENDERENT_FREE, + RD_RENDERENT_ADD, + RD_RENDERENT, + + RD_RENDERLIGHT_UPDATE, + RD_RENDERLIGHT_FREE, + RD_RENDERLIGHT_ADD, + RD_RENDERLIGHT, + + RD_RENDEREFFECT_UPDATE, + RD_RENDEREFFECT_FREE, + RD_RENDEREFFECT_STOP, + RD_RENDEREFFECT_ADD, + RD_RENDEREFFECT, + + RD_SOUND_STATE, + RD_SOUND, + + RD_HUD, + + RD_MAX_STATS +}; + +typedef bool(*deferredEntityCallback_t)( renderEntity_s *, const renderView_s * ); + + +// RAVEN BEGIN +// bdube: attachments +typedef struct attachedModel_s { + idRenderModel* model; + jointHandle_t joint; +} attachedModel_t; +// RAVEN END + +typedef struct renderEntity_s { + idRenderModel * hModel; // this can only be null if callback is set + + int entityNum; + int bodyId; + + // Entities that are expensive to generate, like skeletal models, can be + // deferred until their bounds are found to be in view, in the frustum + // of a shadowing light that is in view, or contacted by a trace / overlay test. + // This is also used to do visual cueing on items in the view + // The renderView may be NULL if the callback is being issued for a non-view related + // source. + // The callback function should clear renderEntity->callback if it doesn't + // want to be called again next time the entity is referenced (ie, if the + // callback has now made the entity valid until the next updateEntity) + idBounds bounds; // only needs to be set for deferred models and md5s + deferredEntityCallback_t callback; + + void * callbackData; // used for whatever the callback wants + + // player bodies and possibly player shadows should be suppressed in views from + // that player's eyes, but will show up in mirrors and other subviews + // security cameras could suppress their model in their subviews if we add a way + // of specifying a view number for a remoteRenderMap view + int suppressSurfaceInViewID; + int suppressShadowInViewID; + + // world models for the player and weapons will not cast shadows from view weapon + // muzzle flashes + int suppressShadowInLightID; + + // if non-zero, the surface and shadow (if it casts one) + // will only show up in the specific view, ie: player weapons + int allowSurfaceInViewID; + +// RAVEN BEGIN +// bdube: hiding surfaces + // Mask of surfaces which should be suppresesed when rendering the entity + int suppressSurfaceMask; +// RAVEN END + + // positioning + // axis rotation vectors must be unit length for many + // R_LocalToGlobal functions to work, so don't scale models! + // axis vectors are [0] = forward, [1] = left, [2] = up + idVec3 origin; + idMat3 axis; + + // texturing + const idMaterial * customShader; // if non-0, all surfaces will use this + const idMaterial * referenceShader; // used so flares can reference the proper light shader +// RAVEN BEGIN +// bdube: overlay shaders + const idMaterial * overlayShader; // overlays the model on top of itself with this shader (originally for powerups) +// RAVEN END + const idDeclSkin * customSkin; // 0 for no remappings +// RAVEN BEGIN + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds +// RAVEN END + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; // can be used in any way by shader or model generation + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END + + // networking: see WriteGUIToSnapshot / ReadGUIFromSnapshot + class idUserInterface * gui[ MAX_RENDERENTITY_GUI ]; + + struct renderView_s * remoteRenderView; // any remote camera surfaces will use this + + int numJoints; + idJointMat * joints; // array of joints that will modify vertices. + // NULL if non-deformable model. NOT freed by renderer + + float modelDepthHack; // squash depth range so particle effects don't clip into walls + + // options to override surface shader flags (replace with material parameters?) + bool noSelfShadow; // cast shadows onto other objects,but not self + bool noShadow; // no shadow at all + + bool noDynamicInteractions; // don't create any light / shadow interactions after + // the level load is completed. This is a performance hack + // for the gigantic outdoor meshes in the monorail map, so + // all the lights in the moving monorail don't touch the meshes + +// RAVEN BEGIN + bool forceUpdate; // force an update + +// bdube: weapon depth hack only in a given view id + int weaponDepthHackInViewID;// squash depth range so view weapons don't poke into walls +// RAVEN END + // this automatically implies noShadow + +// RAVEN BEGIN +// ddynerman: Wolf LOD code + float shadowLODDistance; + int suppressLOD; +// RAVEN END +} renderEntity_t; + +typedef struct renderLight_s { + idMat3 axis; // rotation vectors, must be unit length + idVec3 origin; + + // if non-zero, the light will not show up in the specific view, + // which may be used if we want to have slightly different muzzle + // flash lights for the player and other views + int suppressLightInViewID; + + // if non-zero, the light will only show up in the specific view + // which can allow player gun gui lights and such to not effect everyone + int allowLightInViewID; + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + float detailLevel; +// RAVEN END + + // I am sticking the four bools together so there are no unused gaps in + // the padded structure, which could confuse the memcmp that checks for redundant + // updates + bool noShadows; // (should we replace this with material parameters on the shader?) + bool noSpecular; // (should we replace this with material parameters on the shader?) +// RAVEN BEGIN +// ddynerman: no dynamic shadows - allows dmap to create optimized static shadows, but prevents dynamic shadows + bool noDynamicShadows; +// RAVEN END + + bool pointLight; // otherwise a projection light (should probably invert the sense of this, because points are way more common) + bool parallel; // lightCenter gives the direction to the light at infinity + bool globalLight; // Whether this is a global light or not. + idVec3 lightRadius; // xyz radius for point lights + idVec3 lightCenter; // offset the lighting direction for shading and + // shadows, relative to origin + + // frustum definition for projected lights, all reletive to origin + // FIXME: we should probably have real plane equations here, and offer + // a helper function for conversion from this format + idVec3 target; + idVec3 right; + idVec3 up; + idVec3 start; + idVec3 end; + + // Dmap will generate an optimized shadow volume named _prelight_ + // for the light against all the _area* models in the map. The renderer will + // ignore this value if the light has been moved after initial creation + idRenderModel * prelightModel; + + // muzzle flash lights will not cast shadows from player and weapon world models + int lightId; + + + const idMaterial * shader; // NULL = either lights/defaultPointLight or lights/defaultProjectedLight + float shaderParms[MAX_ENTITY_SHADER_PARMS]; // can be used in any way by shader +// RAVEN BEGIN + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END +} renderLight_t; + +// RAVEN BEGIN +// jscott: for handling of effects +typedef struct renderEffect_s { + + const idDecl *declEffect; + + float startTime; + int suppressSurfaceInViewID; + int allowSurfaceInViewID; + int groupID; + + idVec3 origin; + idMat3 axis; + + idVec3 gravity; + idVec3 endOrigin; + + float attenuation; + bool hasEndOrigin; + bool loop; // effect is looping + bool ambient; // effect is from an entity + bool inConnectedArea; + int weaponDepthHackInViewID; // squash depth range so view weapons don't poke into walls + float modelDepthHack; + + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds + + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; // can be used in any way by shader or model generation +} renderEffect_t; +// RAVEN END + +typedef struct renderView_s { + // player views will set this to a non-zero integer for model suppress / allow + // subviews (mirrors, cameras, etc) will always clear it to zero + int viewID; + + // sized from 0 to SCREEN_WIDTH / SCREEN_HEIGHT (640/480), not actual resolution + int x, y, width, height; + + float fov_x, fov_y; + idVec3 vieworg; + idMat3 viewaxis; // transformation matrix, view looks down the positive X axis + + bool cramZNear; // for cinematics, we want to set ZNear much lower + bool forceUpdate; // for an update + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + float shaderParms[MAX_GLOBAL_SHADER_PARMS]; // can be used in any way by shader + const idMaterial *globalMaterial; // used to override everything draw + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + bool streamingPrecache; // If true, we precache all the textures that are visible in the world +#endif +// RAVEN END +} renderView_t; + + +// exitPortal_t is returned by idRenderWorld::GetPortal() +typedef struct { + int areas[2]; // areas connected by this portal + const idWinding * w; // winding points have counter clockwise ordering seen from areas[0] + int blockingBits; // PS_BLOCK_VIEW, PS_BLOCK_AIR, etc + qhandle_t portalHandle; +} exitPortal_t; + + +// guiPoint_t is returned by idRenderWorld::GuiTrace() +typedef struct { + float x, y; // 0.0 to 1.0 range if trace hit a gui, otherwise -1 + int guiId; // id of gui ( 0, 1, or 2 ) that the trace happened against +} guiPoint_t; + + +// modelTrace_t is for tracing vs. visual geometry +typedef struct modelTrace_s { + float fraction; // fraction of trace completed + idVec3 point; // end point of trace in global space + idVec3 normal; // hit triangle normal vector in global space + const idMaterial * material; // material of hit surface + const renderEntity_t * entity; // render entity that was hit + int jointNumber; // md5 joint nearest to the hit triangle + +// RAVEN BEGIN +// jscott: added block + const rvDeclMatType * materialType; +// RAVEN END +} modelTrace_t; + +// RAVEN BEGIN +// abahr: changed to 4 to include gravity +static const int NUM_PORTAL_ATTRIBUTES = 4; +// RAVEN END + +typedef enum { + PS_BLOCK_NONE = 0, + + PS_BLOCK_VIEW = 1, + PS_BLOCK_LOCATION = 2, // game map location strings often stop in hallways + PS_BLOCK_AIR = 4, // windows between pressurized and unpresurized areas +// RAVEN BEGIN +// abahr + PS_BLOCK_GRAVITY = 8, // PS_BLOCK_ALL does not block gravity. Must use info_gravityseperator + + PS_BLOCK_ALL = PS_BLOCK_VIEW|PS_BLOCK_LOCATION|PS_BLOCK_AIR +// RAVEN END +} portalConnection_t; + + +// RAVEN BEGIN +// AReis: Render flags. +enum +{ + RF_NORMAL = 0, + // Let the renderer know its in a editor of sorts. + RF_IS_EDITOR = BIT( 0 ), + // Viewdef is fullscreen and is 2d (mostly for main menu) + RF_IS_FULLSCREEN_2D = BIT( 1 ), + // Don't draw the GUI, just the world. + RF_NO_GUI = BIT( 2 ), + // Only draw the GUI, not the world. + RF_GUI_ONLY = BIT( 3 ), +// RAVEN BEGIN +// dluetscher: added render flag to denote that penumbra map rendering is desired + RF_PENUMBRA_MAP = BIT( 4 ), +// dluetscher: added render flag that defers the command buffer submission of render +// commands until the first non-deferred render command (with the exception of +// certain render commands like RC_DRAW_PENUMBRA_MAPS - which ignores deferred +// render commands and lets them get submitted past) + RF_DEFER_COMMAND_SUBMIT = BIT( 5 ), + // this is a portal sky view + RF_PORTAL_SKY = BIT( 6 ), + // this the primary view - when this is rendered, we then know we can capture the screen buffer + RF_PRIMARY_VIEW = BIT( 7 ), +// RAVEN END +}; + +// RAVEN END + +class idRenderWorld { +public: + virtual ~idRenderWorld( void ) {}; + + // The same render world can be reinitialized as often as desired + // a NULL or empty mapName will create an empty, single area world + virtual bool InitFromMap( const char *mapName ) = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Flush( void ) = 0; +#endif +// RAVEN END + + //-------------- Entity and Light Defs ----------------- + + // entityDefs and lightDefs are added to a given world to determine + // what will be drawn for a rendered scene. Most update work is defered + // until it is determined that it is actually needed for a given view. + virtual qhandle_t AddEntityDef( const renderEntity_t *re ) = 0; + virtual void UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ) = 0; + virtual void FreeEntityDef( qhandle_t entityHandle ) = 0; + virtual const renderEntity_t *GetRenderEntity( qhandle_t entityHandle ) const = 0; + + virtual qhandle_t AddLightDef( const renderLight_t *rlight ) = 0; + virtual void UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ) = 0; + virtual void FreeLightDef( qhandle_t lightHandle ) = 0; + virtual const renderLight_t *GetRenderLight( qhandle_t lightHandle ) const = 0; +// RAVEN BEGIN + virtual void WriteRenderLight( idDemoFile *writeDemo, const renderLight_t *light ) = 0; + virtual void ReadRenderLight( idDemoFile *readDemo, renderLight_t &light ) = 0; + +// jscott: handling of effects + virtual qhandle_t AddEffectDef( const renderEffect_t *reffect, int time ) = 0; + virtual bool UpdateEffectDef( qhandle_t effectHandle, const renderEffect_t *reffect, int time ) = 0; + virtual void StopEffectDef( qhandle_t effectHandle ) = 0; + virtual const class rvRenderEffectLocal* GetEffectDef( qhandle_t effectHandle ) const = 0; + virtual void FreeEffectDef( qhandle_t effectHandle ) = 0; + virtual bool EffectDefHasSound( const renderEffect_s *reffect ) = 0; + +// jscott: for optimised pushes + virtual void PushMarkedDefs( void ) = 0; + virtual void ClearMarkedDefs( void ) = 0; + virtual void FreeDefs( void ) = 0; + +// jscott: fix for FreeWorld crash + virtual void RemoveAllModelReferences( idRenderModel *model ) = 0; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + virtual void StreamAreaTextures(bool inBackground) = 0; + virtual void ClearAreaTextureStreamer(void) = 0; +#endif +// RAVEN END + + // Force the generation of all light / surface interactions at the start of a level + // If this isn't called, they will all be dynamically generated + virtual void GenerateAllInteractions( void ) = 0; + + //-------------- Decals and Overlays ----------------- + + // Creates decals on all world surfaces that the winding projects onto. + // The projection origin should be infront of the winding plane. + // The decals are projected onto world geometry between the winding plane and the projection origin. + // The decals are depth faded from the winding plane to a certain distance infront of the + // winding plane and the same distance from the projection origin towards the winding. + virtual void ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) = 0; + + // Creates decals on static models. + virtual void ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) = 0; + + // Creates overlays on dynamic models. + virtual void ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material ) = 0; + + // Removes all decals and overlays from the given entity def. + virtual void RemoveDecals( qhandle_t entityHandle ) = 0; + + //-------------- Scene Rendering ----------------- + + // some calls to material functions use the current renderview time when servicing cinematics. this function + // ensures that any parms accessed (such as time) are properly set. + virtual void SetRenderView( const renderView_t *renderView ) = 0; + + // rendering a scene may actually render multiple subviews for mirrors and portals, and + // may render composite textures for gui console screens and light projections + // It would also be acceptable to render a scene multiple times, for "rear view mirrors", etc +// RAVEN BEGIN +// AReis: Modified RenderScene() signature to include renderFlags variable. + virtual void RenderScene( const renderView_t *renderView, int renderFlags = RF_NORMAL ) = 0; +// jscott: for portal skies + virtual bool HasSkybox( int areaNum ) = 0; + virtual void FindVisibleAreas( idVec3 origin, int areaNum, bool *visibleAreas ) = 0; +// AReis: This is where we draw the portal fadeout polygon + virtual void RenderPortalFades( void ) = 0; +// ddynerman: Helper function + virtual idVec3 WorldToScreen( renderView_t* view, idVec3 point ) = 0; + virtual idBounds WorldToScreen( renderView_t* view, idBounds bounds ) = 0; +// RAVEN END + + //-------------- Portal Area Information ----------------- + + // returns the number of portals + virtual int NumPortals( void ) const = 0; + + // returns 0 if no portal contacts the bounds + // This is used by the game to identify portals that are contained + // inside doors, so the connection between areas can be topologically + // terminated when the door shuts. + virtual qhandle_t FindPortal( const idBounds &b ) const = 0; + + // doors explicitly close off portals when shut + // multiple bits can be set to block multiple things, ie: ( PS_VIEW | PS_LOCATION | PS_AIR ) + virtual void SetPortalState( qhandle_t portal, int blockingBits ) = 0; + virtual int GetPortalState( qhandle_t portal ) = 0; + + // returns true only if a chain of portals without the given connection bits set + // exists between the two areas (a door doesn't separate them, etc) + virtual bool AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) = 0; + + // returns the number of portal areas in a map, so game code can build information + // tables for the different areas + virtual int NumAreas( void ) const = 0; + + // Will return -1 if the point is not in an area, otherwise + // it will return 0 <= value < NumAreas() + virtual int PointInArea( const idVec3 &point ) const = 0; + + // fills the *areas array with the numbers of the areas the bounds cover + // returns the total number of areas the bounds cover + virtual int BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const = 0; + + // Used by the sound system to do area flowing + virtual int NumPortalsInArea( int areaNum ) = 0; + + // returns one portal from an area + virtual void GetPortals( int areaNum, exitPortal_t *ret, int size ) = 0; + virtual void GetPortal( int areaNum, int portalNum, exitPortal_t *ret ) = 0; + virtual exitPortal_t GetPortal( int areaNum, int portalNum ) = 0; + + //-------------- Tracing ----------------- + + // Checks a ray trace against any gui surfaces in an entity, returning the + // fraction location of the trace on the gui surface, or -1,-1 if no hit. + // This doesn't do any occlusion testing, simply ignoring non-gui surfaces. + // start / end are in global world coordinates. + virtual guiPoint_t GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const = 0; + + // Traces vs the render model, possibly instantiating a dynamic version, and returns true if something was hit + virtual bool ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const = 0; + + // Traces vs the whole rendered world. FIXME: we need some kind of material flags. + virtual bool Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic = true, bool skipPlayer = false ) const = 0; + + // Traces vs the world model bsp tree. + virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + + //-------------- Demo Control ----------------- + + // Writes a loadmap command to the demo, and clears archive counters. + virtual void StartWritingDemo( idDemoFile *demo ) = 0; + virtual void StopWritingDemo( void ) = 0; + + // Returns true when demoRenderView has been filled in. + // adds/updates/frees entityDefs and lightDefs based on the current demo file + // and returns the renderView to be used to render this frame. + // a demo file may need to be advanced multiple times if the framerate + // is less than 30hz + // demoTimeOffset will be set if a new map load command was processed before + // the next renderScene + virtual bool ProcessDemoCommand( idDemoFile *readDemo, renderView_t *demoRenderView, renderView_t *portalSkyRenderView, int *demoTimeOffset ) = 0; + + // this is used to regenerate all interactions ( which is currently only done during influences ), there may be a less + // expensive way to do it + virtual void RegenerateWorld( void ) = 0; + + //-------------- Debug Visualization ----------------- + + // Line drawing for debug visualization + virtual void DebugClear( int time ) = 0; // a time of 0 will clear all lines and text + virtual void DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime = 0 ) = 0; + virtual void DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime = 0, bool depthTest = false ) = 0; +// RAVEN BEGIN +// jscott: want to be able to specify depth test + virtual void DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org = vec3_origin, const int lifetime = 0, bool depthTest = false ) = 0; + virtual size_t MemorySummary( const idCmdArgs &args ) = 0; + virtual void ShowDebugLines( void ) = 0; + virtual void ShowDebugPolygons( void ) = 0; + virtual void ShowDebugText( void ) = 0; +// cdr: added debug FOV + virtual void DebugFOV( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, float farDot, float farDist, float nearDot=1.0f, float nearDist=0.0f, float alpha=0.3f, int lifetime = 0 ) = 0; +// RAVEN END + virtual void DebugBox( const idVec4 &color, const idBox &box, const int lifetime = 0 ) = 0; + virtual void DebugFrustum( const idVec4 &color, const idFrustum &frustum, const bool showFromOrigin = false, const int lifetime = 0 ) = 0; + virtual void DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime = 0 ) = 0; + virtual void DebugAxis( const idVec3 &origin, const idMat3 &axis ) = 0; + + // Polygon drawing for debug visualization. + virtual void DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime = 0, const bool depthTest = false ) = 0; + + // Text drawing for debug visualization. + virtual void DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align = 1, const int lifetime = 0, bool depthTest = false ) = 0; +}; + +#endif /* !__RENDERWORLD_H__ */ diff --git a/source/renderer/glext.h b/source/renderer/glext.h new file mode 100644 index 0000000..63fea9d --- /dev/null +++ b/source/renderer/glext.h @@ -0,0 +1,5929 @@ +// Copyright (C) 2004 Id Software, Inc. +// +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated 2004/2/23 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define GL_GLEXT_VERSION 22 + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_FOG_COORD_SOURCE GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_env_add +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#endif + +#ifndef GL_ARB_shading_language_100 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT GL_BLEND_EQUATION +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for handling shader object handles and characters */ +typedef char GLcharARB; /* native character */ +typedef unsigned int GLhandleARB; /* shader object handle */ +#endif + +#ifndef GL_NV_half_float +/* GL type for representing NVIDIA "half" floating point type in host memory */ +typedef unsigned short GLhalfNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +GLAPI void APIENTRY glBlendEquation (GLenum); +GLAPI void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +GLAPI void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogram (GLenum); +GLAPI void APIENTRY glResetMinmax (GLenum); +GLAPI void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum); +GLAPI void APIENTRY glClientActiveTexture (GLenum); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum, const GLshort *); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glSampleCoverage (GLclampf, GLboolean); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glFogCoordf (GLfloat); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *); +GLAPI void APIENTRY glFogCoordd (GLdouble); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointer (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glMultiDrawArrays (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElements (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +GLAPI void APIENTRY glPointParameterf (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfv (GLenum, const GLfloat *); +GLAPI void APIENTRY glPointParameteri (GLenum, GLint); +GLAPI void APIENTRY glPointParameteriv (GLenum, const GLint *); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3i (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *); +GLAPI void APIENTRY glSecondaryColor3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3us (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointer (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glWindowPos2d (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos2f (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos2i (GLint, GLint); +GLAPI void APIENTRY glWindowPos2iv (const GLint *); +GLAPI void APIENTRY glWindowPos2s (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *); +GLAPI void APIENTRY glWindowPos3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos3i (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3iv (const GLint *); +GLAPI void APIENTRY glWindowPos3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueries (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQuery (GLuint); +GLAPI void APIENTRY glBeginQuery (GLenum, GLuint); +GLAPI void APIENTRY glEndQuery (GLenum); +GLAPI void APIENTRY glGetQueryiv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint, GLenum, GLuint *); +GLAPI void APIENTRY glBindBuffer (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffers (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffers (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint); +GLAPI void APIENTRY glBufferData (GLenum, GLsizeiptr, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubData (GLenum, GLintptr, GLsizeiptr, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubData (GLenum, GLintptr, GLsizeiptr, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointerv (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvARB (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint, const GLbyte *); +GLAPI void APIENTRY glWeightsvARB (GLint, const GLshort *); +GLAPI void APIENTRY glWeightivARB (GLint, const GLint *); +GLAPI void APIENTRY glWeightfvARB (GLint, const GLfloat *); +GLAPI void APIENTRY glWeightdvARB (GLint, const GLdouble *); +GLAPI void APIENTRY glWeightubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glWeightusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glWeightuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glWeightPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexBlendARB (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iARB (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos2sARB (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos3sARB (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glProgramStringARB (GLenum, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBindProgramARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenProgramsARB (GLsizei, GLuint *); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringARB (GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffersARB (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint); +GLAPI void APIENTRY glBufferDataARB (GLenum, GLsizeiptrARB, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint); +GLAPI void APIENTRY glBeginQueryARB (GLenum, GLuint); +GLAPI void APIENTRY glEndQueryARB (GLenum); +GLAPI void APIENTRY glGetQueryivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB, GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB, GLsizei, const GLcharARB* *, const GLint *); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB, GLhandleARB); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB); +GLAPI void APIENTRY glUniform1fARB (GLint, GLfloat); +GLAPI void APIENTRY glUniform2fARB (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3fARB (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4fARB (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1iARB (GLint, GLint); +GLAPI void APIENTRY glUniform2iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3iARB (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4iARB (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB, GLenum, GLint *); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB, GLsizei, GLsizei *, GLhandleARB *); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB, const GLcharARB *); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB, GLint, GLint *); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB, GLuint, const GLcharARB *); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB, const GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogramEXT (GLenum); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindTextureEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint); +GLAPI void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +GLAPI void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +GLAPI void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, const GLdouble *); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, const GLfloat *); +GLAPI void APIENTRY glDeformSGIX (GLbitfield); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +GLAPI void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +GLAPI void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint, GLsizei); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum); +GLAPI void APIENTRY glTextureLightEXT (GLenum); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint, GLsizei); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +GLAPI void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *); +GLAPI void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *); +GLAPI void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *); +GLAPI void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *); +GLAPI void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *); +GLAPI void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *); +GLAPI void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *); +GLAPI void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iMESA (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *, const GLint *, const GLsizei *, GLsizei, GLint); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* const *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFencesNV (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint); +GLAPI void APIENTRY glGetFenceivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFinishFenceNV (GLuint); +GLAPI void APIENTRY glSetFenceNV (GLuint, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLint, GLint, GLboolean, const GLvoid *); +GLAPI void APIENTRY glMapParameterivNV (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glMapParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLboolean, GLvoid *); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glEvalMapsNV (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindProgramNV (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glExecuteProgramNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGenProgramsNV (GLsizei, GLuint *); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum, GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetProgramivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringNV (GLuint, GLenum, GLubyte *); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint); +GLAPI void APIENTRY glLoadProgramNV (GLenum, GLuint, GLsizei, const GLubyte *); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum, GLuint, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum, GLuint, GLuint, const GLfloat *); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glTrackMatrixNV (GLenum, GLuint, GLenum, GLenum); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint, GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint, GLsizei, const GLubyte *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum, const GLint *); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum, GLint *); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glSampleMapATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum, GLint); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei, const GLvoid *, GLenum); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint, GLuint, GLsizei, const GLvoid *, GLenum); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint); +GLAPI void APIENTRY glArrayObjectATI (GLenum, GLint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint); +GLAPI void APIENTRY glShaderOp1EXT (GLenum, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp2EXT (GLenum, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp3EXT (GLenum, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSwizzleEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glWriteMaskEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glInsertComponentEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glExtractComponentEXT (GLuint, GLuint, GLuint); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glSetInvariantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glVariantbvEXT (GLuint, const GLbyte *); +GLAPI void APIENTRY glVariantsvEXT (GLuint, const GLshort *); +GLAPI void APIENTRY glVariantivEXT (GLuint, const GLint *); +GLAPI void APIENTRY glVariantfvEXT (GLuint, const GLfloat *); +GLAPI void APIENTRY glVariantdvEXT (GLuint, const GLdouble *); +GLAPI void APIENTRY glVariantubvEXT (GLuint, const GLubyte *); +GLAPI void APIENTRY glVariantusvEXT (GLuint, const GLushort *); +GLAPI void APIENTRY glVariantuivEXT (GLuint, const GLuint *); +GLAPI void APIENTRY glVariantPointerEXT (GLuint, GLenum, GLuint, const GLvoid *); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum, GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint, GLenum); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint, GLenum, GLvoid* *); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum, GLshort); +GLAPI void APIENTRY glVertexStream1svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream1iATI (GLenum, GLint); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream1fATI (GLenum, GLfloat); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream1dATI (GLenum, GLdouble); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream2sATI (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream2svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream2iATI (GLenum, GLint, GLint); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream2fATI (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream2dATI (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream4sATI (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream4svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream4iATI (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream4fATI (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream4dATI (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glNormalStream3bATI (GLenum, GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum, const GLbyte *); +GLAPI void APIENTRY glNormalStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glNormalStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glNormalStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glNormalStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glNormalStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum, GLint); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum, GLuint, GLuint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glPointParameterivNV (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, GLint, GLsizei); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum, const GLint *, const GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, const GLint *, const GLsizei *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum, GLuint); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint, GLsizei, const GLubyte *, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint, GLsizei, const GLubyte *, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint, GLsizei, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint, GLsizei, const GLubyte *, const GLdouble *); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint, GLsizei, const GLubyte *, GLfloat *); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint, GLsizei, const GLubyte *, GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint, GLsizei, const GLhalfNV *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum, GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum, GLenum, GLint, GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint, GLint, GLenum, GLboolean, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/source/renderer/qgl.h b/source/renderer/qgl.h new file mode 100644 index 0000000..018855a --- /dev/null +++ b/source/renderer/qgl.h @@ -0,0 +1,631 @@ +// Copyright (C) 2004 Id Software, Inc. +// +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#if defined( _WINDOWS ) + +#include + +#elif defined( MACOS_X ) + +// magic flag to keep tiger gl.h from loading glext.h +#define GL_GLEXT_LEGACY +#include + +#elif defined( __linux__ ) + +// using our local glext.h +// http://oss.sgi.com/projects/ogl-sample/ABI/ +#define GL_GLEXT_LEGACY +#define GLX_GLXEXT_LEGACY +#include +#include + +// TTimo - X.h has a '#define Success 0' line.. +#undef Success + +#elif defined( _XENON ) + +#include "../sys/dx/gl_stub.h" +#include "../sys/dx/glext_stub.h" + +#else + +#include + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + +// only use local glext.h if we are not using the system one already +// http://oss.sgi.com/projects/ogl-sample/ABI/ +#ifndef GL_GLEXT_VERSION + +#include "glext.h" + +#endif + +typedef void (*GLExtension_t)(void); + +#ifdef __cplusplus + extern "C" { +#endif + +GLExtension_t GLimp_ExtensionPointer( const char *name ); + +#ifdef __cplusplus + } +#endif + +// multitexture +extern void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglMultiTexCoord2fvARB )( GLenum texture, GLfloat * RESTRICT st ); +extern void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +// ARB_vertex_buffer_object +extern PFNGLBINDBUFFERARBPROC qglBindBufferARB; +extern PFNGLDELETEBUFFERSARBPROC qglDeleteBuffersARB; +extern PFNGLGENBUFFERSARBPROC qglGenBuffersARB; +extern PFNGLISBUFFERARBPROC qglIsBufferARB; +extern PFNGLBUFFERDATAARBPROC qglBufferDataARB; +extern PFNGLBUFFERSUBDATAARBPROC qglBufferSubDataARB; +extern PFNGLGETBUFFERSUBDATAARBPROC qglGetBufferSubDataARB; +extern PFNGLMAPBUFFERARBPROC qglMapBufferARB; +extern PFNGLUNMAPBUFFERARBPROC qglUnmapBufferARB; +extern PFNGLGETBUFFERPARAMETERIVARBPROC qglGetBufferParameterivARB; +extern PFNGLGETBUFFERPOINTERVARBPROC qglGetBufferPointervARB; + + +// NV_register_combiners +extern void ( APIENTRY *qglCombinerParameterfvNV )( GLenum pname, const GLfloat * RESTRICT params ); +extern void ( APIENTRY *qglCombinerParameterivNV )( GLenum pname, const GLint * RESTRICT params ); +extern void ( APIENTRY *qglCombinerParameterfNV )( GLenum pname, const GLfloat param ); +extern void ( APIENTRY *qglCombinerParameteriNV )( GLenum pname, const GLint param ); +extern void ( APIENTRY *qglCombinerInputNV )( GLenum stage, GLenum portion, GLenum variable, GLenum input, + GLenum mapping, GLenum componentUsage ); +extern void ( APIENTRY *qglCombinerOutputNV )( GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, + GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, + GLboolean cdDotProduct, GLboolean muxSum ); +extern void ( APIENTRY *qglFinalCombinerInputNV )( GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage ); + +// RAVEN BEGIN +// dluetscher: added support for NV_vertex_program and NV_fragment_program extensions +extern void ( APIENTRY *qglBindProgramNV ) (GLenum, GLuint); +extern void ( APIENTRY *qglLoadProgramNV ) (GLenum, GLuint, GLsizei, const GLubyte * RESTRICT ); +extern void ( APIENTRY *qglProgramParameter4fvNV ) (GLenum, GLuint, const GLfloat * RESTRICT ); +// RAVEN END + +// 3D textures +extern void ( APIENTRY *qglTexImage3D)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid * RESTRICT ); + +// shared texture palette +extern void ( APIENTRY *qglColorTableEXT)( int, int, int, int, int, const void * RESTRICT ); + +// RAVEN BEGIN +// dluetscher: added the BlendEquationEXT() from the blend minmax extension +extern void ( APIENTRY *qglBlendEquationEXT)( GLenum mode ); +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added support for the EXT_draw_range_elements extension +extern void ( APIENTRY * qglDrawRangeElementsEXT )(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * RESTRICT indices); +// RAVEN END + +// ATI_fragment_shader +extern PFNGLGENFRAGMENTSHADERSATIPROC qglGenFragmentShadersATI; +extern PFNGLBINDFRAGMENTSHADERATIPROC qglBindFragmentShaderATI; +extern PFNGLDELETEFRAGMENTSHADERATIPROC qglDeleteFragmentShaderATI; +extern PFNGLBEGINFRAGMENTSHADERATIPROC qglBeginFragmentShaderATI; +extern PFNGLENDFRAGMENTSHADERATIPROC qglEndFragmentShaderATI; +extern PFNGLPASSTEXCOORDATIPROC qglPassTexCoordATI; +extern PFNGLSAMPLEMAPATIPROC qglSampleMapATI; +extern PFNGLCOLORFRAGMENTOP1ATIPROC qglColorFragmentOp1ATI; +extern PFNGLCOLORFRAGMENTOP2ATIPROC qglColorFragmentOp2ATI; +extern PFNGLCOLORFRAGMENTOP3ATIPROC qglColorFragmentOp3ATI; +extern PFNGLALPHAFRAGMENTOP1ATIPROC qglAlphaFragmentOp1ATI; +extern PFNGLALPHAFRAGMENTOP2ATIPROC qglAlphaFragmentOp2ATI; +extern PFNGLALPHAFRAGMENTOP3ATIPROC qglAlphaFragmentOp3ATI; +extern PFNGLSETFRAGMENTSHADERCONSTANTATIPROC qglSetFragmentShaderConstantATI; + +// EXT_stencil_two_side +extern PFNGLACTIVESTENCILFACEEXTPROC qglActiveStencilFaceEXT; + + +// ATI_separate_stencil +extern PFNGLSTENCILOPSEPARATEATIPROC qglStencilOpSeparateATI; +extern PFNGLSTENCILFUNCSEPARATEATIPROC qglStencilFuncSeparateATI; + +// ARB_texture_compression +extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB; +extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB; + +// ARB_vertex_program / ARB_fragment_program +extern PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB; +extern PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB; +extern PFNGLDISABLEVERTEXATTRIBARRAYARBPROC qglDisableVertexAttribArrayARB; +extern PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +extern PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +extern PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +extern PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; + +// RAVEN BEGIN +// rjohnson: new shader stage system +// GLSL fragment +extern PFNGLCREATESHADEROBJECTARBPROC qglCreateShaderObjectARB; +extern PFNGLDELETEOBJECTARBPROC qglDeleteObjectARB; +extern PFNGLSHADERSOURCEARBPROC qglShaderSourceARB; +extern PFNGLCOMPILESHADERARBPROC qglCompileShaderARB; +extern PFNGLGETOBJECTPARAMETERIVARBPROC qglGetObjectParameterivARB; +extern PFNGLCREATEPROGRAMOBJECTARBPROC qglCreateProgramObjectARB; +extern PFNGLATTACHOBJECTARBPROC qglAttachObjectARB; +extern PFNGLDETACHOBJECTARBPROC qglDetachObjectARB; +extern PFNGLLINKPROGRAMARBPROC qglLinkProgramARB; +extern PFNGLUSEPROGRAMOBJECTARBPROC qglUseProgramObjectARB; +extern PFNGLGETUNIFORMLOCATIONARBPROC qglGetUniformLocationARB; +extern PFNGLUNIFORM1FARBPROC qglUniform1fARB; +extern PFNGLUNIFORM1IARBPROC qglUniform1iARB; +extern PFNGLUNIFORM1FVARBPROC qglUniform1fvARB; +extern PFNGLUNIFORM2FVARBPROC qglUniform2fvARB; +extern PFNGLUNIFORM3FVARBPROC qglUniform3fvARB; +extern PFNGLUNIFORM4FVARBPROC qglUniform4fvARB; +extern PFNGLGETINFOLOGARBPROC qglGetInfoLogARB; +// RAVEN END + +// GL_EXT_depth_bounds_test +extern PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT; + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if defined( __APPLE__ ) || defined( ID_GL_HARDLINK ) + +#include "qgl_linked.h" + +#else + +// windows systems use a function pointer for each call so we can do our log file intercepts + +extern void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint * RESTRICT textures, GLboolean * RESTRICT residences); +extern void ( APIENTRY * qglArrayElement )(GLint i); +extern void ( APIENTRY * qglBegin )(GLenum mode); +extern void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * RESTRICT bitmap); +extern void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( APIENTRY * qglCallList )(GLuint list); +extern void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid * RESTRICT lists); +extern void ( APIENTRY * qglClear )(GLbitfield mask); +extern void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern void ( APIENTRY * qglClearIndex )(GLfloat c); +extern void ( APIENTRY * qglClearStencil )(GLint s); +extern void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble * RESTRICT equation); +extern void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( APIENTRY * qglColor3bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( APIENTRY * qglColor3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( APIENTRY * qglColor3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( APIENTRY * qglColor3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( APIENTRY * qglColor3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( APIENTRY * qglColor3ubv )(const GLubyte * RESTRICT v); +extern void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( APIENTRY * qglColor3uiv )(const GLuint * RESTRICT v); +extern void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( APIENTRY * qglColor3usv )(const GLushort * RESTRICT v); +extern void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( APIENTRY * qglColor4bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( APIENTRY * qglColor4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglColor4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( APIENTRY * qglColor4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( APIENTRY * qglColor4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( APIENTRY * qglColor4ubv )(const GLubyte * RESTRICT v); +extern void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( APIENTRY * qglColor4uiv )(const GLuint * RESTRICT v); +extern void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( APIENTRY * qglColor4usv )(const GLushort * RESTRICT v); +extern void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglCullFace )(GLenum mode); +extern void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint * RESTRICT textures); +extern void ( APIENTRY * qglDepthFunc )(GLenum func); +extern void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( APIENTRY * qglDisable )(GLenum cap); +extern void ( APIENTRY * qglDisableClientState )(GLenum array); +extern void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid * RESTRICT indices); +extern void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglEdgeFlagv )(const GLboolean * RESTRICT flag); +extern void ( APIENTRY * qglEnable )(GLenum cap); +extern void ( APIENTRY * qglEnableClientState )(GLenum array); +extern void ( APIENTRY * qglEnd )(void); +extern void ( APIENTRY * qglEndList )(void); +extern void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern void ( APIENTRY * qglEvalCoord1dv )(const GLdouble * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern void ( APIENTRY * qglEvalCoord1fv )(const GLfloat * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( APIENTRY * qglEvalCoord2dv )(const GLdouble * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( APIENTRY * qglEvalCoord2fv )(const GLfloat * RESTRICT u); +extern void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat * RESTRICT buffer); +extern void ( APIENTRY * qglFinish )(void); +extern void ( APIENTRY * qglFlush )(void); +extern void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglFogiv )(GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglFrontFace )(GLenum mode); +extern void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint * RESTRICT textures); +extern void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean * RESTRICT params); +extern void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *RESTRICT equation); +extern void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble * RESTRICT params); +extern GLenum ( APIENTRY * qglGetError )(void); +extern void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble * RESTRICT v); +extern void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat * RESTRICT v); +extern void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint * RESTRICT v); +extern void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat * RESTRICT values); +extern void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint * RESTRICT values); +extern void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort * RESTRICT values); +extern void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* * RESTRICT params); +extern void ( APIENTRY * qglGetPolygonStipple )(GLubyte * RESTRICT mask); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble * RESTRICT params); +extern void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern void ( APIENTRY * qglIndexMask )(GLuint mask); +extern void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglIndexd )(GLdouble c); +extern void ( APIENTRY * qglIndexdv )(const GLdouble * RESTRICT c); +extern void ( APIENTRY * qglIndexf )(GLfloat c); +extern void ( APIENTRY * qglIndexfv )(const GLfloat * RESTRICT c); +extern void ( APIENTRY * qglIndexi )(GLint c); +extern void ( APIENTRY * qglIndexiv )(const GLint * RESTRICT c); +extern void ( APIENTRY * qglIndexs )(GLshort c); +extern void ( APIENTRY * qglIndexsv )(const GLshort * RESTRICT c); +extern void ( APIENTRY * qglIndexub )(GLubyte c); +extern void ( APIENTRY * qglIndexubv )(const GLubyte * RESTRICT c); +extern void ( APIENTRY * qglInitNames )(void); +extern void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid * RESTRICT pointer); +extern GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern GLboolean ( APIENTRY * qglIsList )(GLuint list); +extern GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( APIENTRY * qglLineWidth )(GLfloat width); +extern void ( APIENTRY * qglListBase )(GLuint base); +extern void ( APIENTRY * qglLoadIdentity )(void); +extern void ( APIENTRY * qglLoadMatrixd )(const GLdouble * RESTRICT m); +extern void ( APIENTRY * qglLoadMatrixf )(const GLfloat * RESTRICT m); +extern void ( APIENTRY * qglLoadName )(GLuint name); +extern void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble * RESTRICT points); +extern void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat * RESTRICT points); +extern void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble * RESTRICT points); +extern void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat * RESTRICT points); +extern void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern void ( APIENTRY * qglMultMatrixd )(const GLdouble * RESTRICT m); +extern void ( APIENTRY * qglMultMatrixf )(const GLfloat * RESTRICT m); +extern void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( APIENTRY * qglNormal3bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( APIENTRY * qglNormal3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( APIENTRY * qglNormal3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( APIENTRY * qglNormal3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( APIENTRY * qglNormal3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( APIENTRY * qglPassThrough )(GLfloat token); +extern void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat * RESTRICT values); +extern void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint * RESTRICT values); +extern void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort * RESTRICT values); +extern void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( APIENTRY * qglPointSize )(GLfloat size); +extern void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( APIENTRY * qglPolygonStipple )(const GLubyte * RESTRICT mask); +extern void ( APIENTRY * qglPopAttrib )(void); +extern void ( APIENTRY * qglPopClientAttrib )(void); +extern void ( APIENTRY * qglPopMatrix )(void); +extern void ( APIENTRY * qglPopName )(void); +extern void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint * RESTRICT textures, const GLclampf * RESTRICT priorities); +extern void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushMatrix )(void); +extern void ( APIENTRY * qglPushName )(GLuint name); +extern void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglRasterPos2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglRasterPos2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern void ( APIENTRY * qglRasterPos2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglRasterPos2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRasterPos3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglRasterPos3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglRasterPos3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglRasterPos3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglRasterPos4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglRasterPos4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglRasterPos4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglRasterPos4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( APIENTRY * qglRectdv )(const GLdouble * RESTRICT v1, const GLdouble * RESTRICT v2); +extern void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( APIENTRY * qglRectfv )(const GLfloat * RESTRICT v1, const GLfloat * RESTRICT v2); +extern void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( APIENTRY * qglRectiv )(const GLint * RESTRICT v1, const GLint * RESTRICT v2); +extern void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( APIENTRY * qglRectsv )(const GLshort * RESTRICT v1, const GLshort * RESTRICT v2); +extern GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint * RESTRICT buffer); +extern void ( APIENTRY * qglShadeModel )(GLenum mode); +extern void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( APIENTRY * qglStencilMask )(GLuint mask); +extern void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern void ( APIENTRY * qglTexCoord1dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern void ( APIENTRY * qglTexCoord1fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1i )(GLint s); +extern void ( APIENTRY * qglTexCoord1iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern void ( APIENTRY * qglTexCoord1sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( APIENTRY * qglTexCoord2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( APIENTRY * qglTexCoord2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern void ( APIENTRY * qglTexCoord2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( APIENTRY * qglTexCoord2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( APIENTRY * qglTexCoord3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( APIENTRY * qglTexCoord3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( APIENTRY * qglTexCoord3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( APIENTRY * qglTexCoord3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( APIENTRY * qglTexCoord4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( APIENTRY * qglTexCoord4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( APIENTRY * qglTexCoord4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( APIENTRY * qglTexCoord4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble * RESTRICT params); +extern void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglVertex2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglVertex2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern void ( APIENTRY * qglVertex2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglVertex2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglVertex3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglVertex3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglVertex3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglVertex4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglVertex4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglVertex4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglVertex4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +// RAVEN BEGIN +// dluetscher: added some wgl calls to the XENON version, as well as the Windows version +#if defined( _WINDOWS ) || defined( _XENON ) +typedef struct tagPIXELFORMATDESCRIPTOR PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, FAR *LPPIXELFORMATDESCRIPTOR; +// RAVEN END + +extern int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR * RESTRICT ); +extern int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +extern int ( WINAPI * qwglGetPixelFormat)(HDC); +extern BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR * RESTRICT ); +extern BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +// RAVEN BEGIN +// dluetscher: added block of functions from WGL ARB pbuffer and render texture extensions +typedef struct HPBUFFERARB__ *HPBUFFERARB; +extern BOOL ( WINAPI * qwglBindTexImageARB)(HPBUFFERARB, int); +extern BOOL ( WINAPI * qwglChoosePixelFormatARB)(HDC hdc, const int * RESTRICT piAttribIList, const FLOAT * RESTRICT pfAttribFList, UINT nMaxFormats, int * RESTRICT piFormats, UINT * RESTRICT nNumFormats); +extern HPBUFFERARB ( WINAPI * qwglCreatePbufferARB)(HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int * RESTRICT piAttribList); +extern BOOL ( WINAPI * qwglDestroyPbufferARB)(HPBUFFERARB); +extern HDC ( WINAPI * qwglGetPbufferDCARB)(HPBUFFERARB); +extern int ( WINAPI * qwglReleasePbufferDCARB)(HPBUFFERARB, HDC); +extern BOOL ( WINAPI * qwglReleaseTexImageARB)(HPBUFFERARB, int); +extern BOOL ( WINAPI * qwglSetPbufferAttribARB)(HPBUFFERARB, const int * RESTRICT ); +// RAVEN END + +extern BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +extern BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +extern HDC ( WINAPI * qwglGetCurrentDC)(VOID); +extern PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +extern BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +extern BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); + +// RAVEN BEGIN +// dluetscher: removed these wgl calls from the XENON version +#if !defined( _XENON ) +// RAVEN END +extern BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +extern BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +extern int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF * RESTRICT ); +extern int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF * RESTRICT ); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); +#endif + +#endif // _WINDOWS + +#if defined( __linux__ ) + +//GLX Functions +extern XVisualInfo * (*qglXChooseVisual)( Display * RESTRICT dpy, int screen, int * RESTRICT attribList ); +extern GLXContext (*qglXCreateContext)( Display * RESTRICT dpy, XVisualInfo * RESTRICT vis, GLXContext shareList, Bool direct ); +extern void (*qglXDestroyContext)( Display * RESTRICT dpy, GLXContext ctx ); +extern Bool (*qglXMakeCurrent)( Display * RESTRICT dpy, GLXDrawable drawable, GLXContext ctx); +extern void (*qglXSwapBuffers)( Display * RESTRICT dpy, GLXDrawable drawable ); +extern GLExtension_t (*qglXGetProcAddressARB)( const GLubyte * RESTRICT procname ); +extern GLXContext (*qglXGetCurrentContext)( void ); + +// make sure the code is correctly using qgl everywhere +// don't enable that when building glimp itself obviously.. +#if !defined( GLIMP ) + #include "../sys/linux/qgl_enforce.h" +#endif + +#endif // __linux__ + +// RAVEN BEGIN +// mwhitlock: loading from .xbr resource bundles +#if defined( _XENON ) +extern void ( APIENTRY * qglSetTexCacheDefault2DImageId ) (int id); +extern void ( APIENTRY * qglSetTexCacheDefaultCubeImageId ) (int id); +extern GLboolean ( APIENTRY * qglTexImageExistsInBundles ) ( unsigned long texNameCRC32 ); +extern void ( APIENTRY * qglTexImageFromCache )(int id, unsigned long texNameCRC32 ); + +// nrausch: capture backbuffer to memory +extern void xglCapture( int width, int height, void * RESTRICT pixels ); + +// mwhitlock: allow us to directly use the results of resolving from the EDRAM rendertarget as texture. +extern void ( APIENTRY * qglTexImageFromFrontBuffer )( void ); + +#endif //_XENON +// RAVEN END + +#endif // hardlinlk vs dlopen + +#endif diff --git a/source/renderer/qgl_linked.h b/source/renderer/qgl_linked.h new file mode 100644 index 0000000..b18edf3 --- /dev/null +++ b/source/renderer/qgl_linked.h @@ -0,0 +1,346 @@ + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexEnviv glGetTexEnviv +#define qglGetTexEnvfv glGetTexEnvfv +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +#ifdef GLX_VERSION_1_1 // catch all for any GLX-aware situation +#define qglXChooseVisual glXChooseVisual +#define qglXCreateContext glXCreateContext +#define qglXDestroyContext glXDestroyContext +#define qglXMakeCurrent glXMakeCurrent +#define qglXSwapBuffers glXSwapBuffers +#define qglXGetProcAddressARB glXGetProcAddressARB +#endif diff --git a/source/sound/sound.h b/source/sound/sound.h new file mode 100644 index 0000000..16cda20 --- /dev/null +++ b/source/sound/sound.h @@ -0,0 +1,533 @@ + +#ifndef __SOUND__ +#define __SOUND__ + +// Resolution of sound shakes in fps +#define SHAKE_FPS 30 + +/* +=============================================================================== + + SOUND WORLD IDS + +=============================================================================== +*/ + +#define SOUNDWORLD_ANY -1 +#define SOUNDWORLD_NONE 0 +#define SOUNDWORLD_GAME 1 +#define SOUNDWORLD_MENU 2 +#define SOUNDWORLD_EDITOR 3 +#define SOUNDWORLD_MAX 4 + +/* +=============================================================================== + + SOUND SHADER DECL + +=============================================================================== +*/ + +// RAVEN BEGIN +class rvCommonSample +{ +public: + rvCommonSample( void ); + virtual ~rvCommonSample( void ) {} + + virtual const byte *GetSampleData( void ) const { return( NULL ); } + virtual int GetNumChannels( void ) const { return( 0 ); } + virtual int GetNumSamples( void ) const { return( 0 ); } + virtual int GetSampleRate( void ) const { return( 0 ); } + virtual int GetMemoryUsed( void ) const { return( 0 ); } + virtual int GetDurationMS( void ) const { return( 0 ); } + virtual float GetDuration( void ) const { return( 0.0f ); } + virtual void Load( int langIndex = -1 ) {} + virtual void PurgeSoundSample( void ) {} + virtual bool IsOgg( void ) const { return false; } // is false for an expanded ogg + virtual void Expand( bool force ) {} // expand oggs to pcm + + void SetReferencedThisLevel( void ) { levelLoadReferenced = true; } + + idStr name; // name of the sample file + unsigned int timestamp; // the most recent of all images used in creation, for reloadImages command + + bool defaultSound; // error during loading, now a beep + bool purged; + bool levelLoadReferenced; // so we can tell which samples aren't needed any more +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool referencedOutsideLevelLoad; +#endif +// RAVEN END +}; +// RAVEN END + +// sound shader flags +static const int SSF_PRIVATE_SOUND = BIT(0); // only plays for the current listenerId +static const int SSF_ANTI_PRIVATE_SOUND =BIT(1); // plays for everyone but the current listenerId +static const int SSF_NO_OCCLUSION = BIT(2); // don't flow through portals, only use straight line +static const int SSF_GLOBAL = BIT(3); // play full volume to all speakers and all listeners +static const int SSF_OMNIDIRECTIONAL = BIT(4); // fall off with distance, but play same volume in all speakers +static const int SSF_LOOPING = BIT(5); // repeat the sound continuously +static const int SSF_PLAY_ONCE = BIT(6); // never restart if already playing on any channel of a given emitter +static const int SSF_UNCLAMPED = BIT(7); // don't clamp calculated volumes at 1.0 +static const int SSF_NO_FLICKER = BIT(8); // always return 1.0 for volume queries +static const int SSF_NO_DUPS = BIT(9); // try not to play the same sound twice in a row +// RAVEN BEGIN +static const int SSF_USEDOPPLER = BIT(10); // allow doppler pitch shifting effects +static const int SSF_NO_RANDOMSTART = BIT(11); // don't offset the start position for looping sounds +static const int SSF_VO_FOR_PLAYER = BIT(12); // Notifies a funcRadioChatter that this shader is directed at the player +static const int SSF_IS_VO = BIT(13); // this sound is VO +static const int SSF_CAUSE_RUMBLE = BIT(14); // causes joystick rumble +static const int SSF_CENTER = BIT(15); // sound through center channel only +static const int SSF_HILITE = BIT(16); // display debug info for this emitter +// RAVEN END + +// these options can be overriden from sound shader defaults on a per-emitter and per-channel basis +typedef struct { + float minDistance; + float maxDistance; + float volume; + float attenuatedVolume; + float shakes; + int soundShaderFlags; // SSF_* bit flags + int soundClass; // for global fading of sounds +// RAVEN BEGIN +// bdube: frequency shift + float frequencyShift; + float wetLevel; + float dryLevel; +// RAVEN END +} soundShaderParms_t; + +const int SOUND_MAX_LIST_WAVS = 32; + +// sound classes are used to fade most sounds down inside cinematics, leaving dialog +// flagged with a non-zero class full volume +const int SOUND_CLASS_MUSICAL = 3; +const int SOUND_MAX_CLASSES = 4; + +// it is somewhat tempting to make this a virtual class to hide the private +// details here, but that doesn't fit easily with the decl manager at the moment. +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idSoundShader : public idDecl, public Serializable<'ISS '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idSoundShader( SerialInputStream &stream ); +#else +class idSoundShader : public idDecl { +#endif +public: + idSoundShader( void ) { Init(); } + virtual ~idSoundShader( void ) {} + + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); +// RAVEN BEGIN + virtual void SetReferencedThisLevel( void ); +// RAVEN END + virtual void List( void ) const; + + virtual const char * GetDescription( void ) const { return( desc ); } + + // so the editor can draw correct default sound spheres + // this is currently defined as meters, which sucks, IMHO. + virtual float GetMinDistance( void ) const { return( parms.minDistance ); } + virtual float GetMaxDistance( void ) const { return( parms.maxDistance ); } + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; + +// jscott: implemented id's code + virtual bool RebuildTextSource( void ); + +// jscott: required access functions + bool IsPrivateSound( void ) const { return( !!( parms.soundShaderFlags & SSF_PRIVATE_SOUND ) ); } + bool IsAntiPrivateSound( void ) const { return( !!( parms.soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) ); } + bool IsNoOcclusion( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_OCCLUSION ) ); } + bool IsGlobal( void ) const { return( !!( parms.soundShaderFlags & SSF_GLOBAL ) ); } + bool IsOmnidirectional( void ) const { return( !!( parms.soundShaderFlags & SSF_OMNIDIRECTIONAL ) ); } + bool IsLooping( void ) const { return( !!( parms.soundShaderFlags & SSF_LOOPING ) ); } + bool IsPlayOnce( void ) const { return( !!( parms.soundShaderFlags & SSF_PLAY_ONCE ) ); } + bool IsUnclamped( void ) const { return( !!( parms.soundShaderFlags & SSF_UNCLAMPED ) ); } + bool IsNoFlicker( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_FLICKER ) ); } + bool IsNoDupes( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_DUPS ) ); } + bool IsDoppler( void ) const { return( !!( parms.soundShaderFlags & SSF_USEDOPPLER ) ); } + bool IsNoRandomStart( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_RANDOMSTART ) ); } + bool IsVO_ForPlayer( void ) const { return( !!( parms.soundShaderFlags & SSF_VO_FOR_PLAYER ) ); } + bool IsVO( void ) const { return( !!( parms.soundShaderFlags & SSF_IS_VO ) ); } + bool IsCauseRumble( void ) const { return( !!( parms.soundShaderFlags & SSF_CAUSE_RUMBLE ) ); } + bool IsCenter( void ) const { return( !!( parms.soundShaderFlags & SSF_CENTER ) ); } + + float GetVolume( void ) const { return( parms.volume ); } + float GetShakes( void ) const { return( parms.shakes ); } + float GetTimeLength( void ) const; + void GetParms( soundShaderParms_t *out ) const { *out = parms; } + void SetNoShakes( bool in ) { noShakes = in; } + bool GetNoShakes( void ) const { return( noShakes ); } + + void IncPlayCount( void ) { playCount++; } + int GetPlayCount( void ) const { return( playCount ); } +// RAVEN END + + float GetLeadinVolume( void ) const { return( leadinVolume ); } + int GetNumEntries( void ) const { return( numEntries ); } + rvCommonSample *GetLeadin( int index ) const { return( leadins[index] ); } + rvCommonSample *GetEntry( int index ) const { return( entries[index] ); } + const char *GetShakeData( int index ) const; + void SetShakeData( int index, const char *ampData ); + void Purge( bool freeBaseBlocks ); + void LoadSampleData( int langIndex = -1 ); + + const char * GetSampleName( int index ) const; + int GetSamplesPerSec( int index ) const; + int GetNumChannels( int index ) const; + int GetNumSamples( int index ) const; + int GetMemorySize( int index ) const; + const byte * GetNonCacheData( int index ) const; + + void ExpandSmallOggs( bool force ); +// RAVEN END + + // returns NULL if an AltSound isn't defined in the shader. + // we use this for pairing a specific broken light sound with a normal light sound + virtual const idSoundShader *GetAltSound( void ) const { return( altSound ); } + + virtual bool HasDefaultSound( void ) const; + + virtual const soundShaderParms_t *GetParms( void ) const { return( &parms ); } + virtual int GetNumSounds( void ) const; + virtual const char * GetSound( int index ) const; + +private: + friend class idSoundEmitterLocal; + friend class idSoundChannel; + friend class idSoundCache; + + // options from sound shader text + soundShaderParms_t parms; // can be overriden on a per-channel basis + + const idSoundShader * altSound; + idStr desc; // description + bool errorDuringParse; +// RAVEN BEGIN + bool noShakes; // Don't generate shake data + bool frequentlyUsed; // Expand this to pcm data no matter how long it is +// RAVEN END + float leadinVolume; // allows light breaking leadin sounds to be much louder than the broken loop + +// RAVEN BEGIN + rvCommonSample * leadins[SOUND_MAX_LIST_WAVS]; +// RAVEN END + int numLeadins; +// RAVEN BEGIN + rvCommonSample * entries[SOUND_MAX_LIST_WAVS]; + idStrList shakes; +// RAVEN END + int numEntries; + +// RAVEN BEGIN +// bdube: frequency shift code from splash + float minFrequencyShift; + float maxFrequencyShift; + + int playCount; // For profiling +// RAVEN END + +private: + void Init( void ); + bool ParseShader( idLexer &src ); +}; + +class rvSoundShaderEdit +{ +public: + virtual ~rvSoundShaderEdit() {} + virtual const char * GetSampleName( const idSoundShader *sound, int index ) const = 0; + virtual int GetSamplesPerSec( const idSoundShader *sound, int index ) const = 0; + virtual int GetNumChannels( const idSoundShader *sound, int index ) const = 0; + virtual int GetNumSamples( const idSoundShader *sound, int index ) const = 0; + virtual int GetMemorySize( const idSoundShader *sound, int index ) const = 0; + virtual const byte * GetNonCacheData( const idSoundShader *sound, int index ) const = 0; + virtual void LoadSampleData( idSoundShader *sound, int langIndex = -1 ) = 0; + virtual void ExpandSmallOggs( idSoundShader *sound, bool force ) = 0; + virtual const char *GetShakeData( idSoundShader *sound, int index ) = 0; + virtual void SetShakeData( idSoundShader *sound, int index, const char *ampData ) = 0; + virtual void Purge( idSoundShader *sound, bool freeBaseBlocks ) = 0; +}; + +extern rvSoundShaderEdit *soundShaderEdit; + +/* +=============================================================================== + + SOUND EMITTER + +=============================================================================== +*/ + +// sound channels +static const int SCHANNEL_ANY = 0; // used in queries and commands to effect every channel at once, in + // startSound to have it not override any other channel +static const int SCHANNEL_ONE = 1; // any following integer can be used as a channel number +typedef int s_channelType; // the game uses its own series of enums, and we don't want to require casts + + +class idSoundEmitter { +public: + virtual ~idSoundEmitter( void ) {} + + // the parms specified will be the default overrides for all sounds started on this emitter. + // NULL is acceptable for parms + virtual void UpdateEmitter( const idVec3 &origin, const idVec3 &velocity, int listenerId, const soundShaderParms_t *parms ) = 0; + + // returns the length of the started sound in msec + virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0.0f, int shaderFlags = 0 ) = 0; + + // pass SCHANNEL_ANY to effect all channels + virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) = 0; + virtual void StopSound( const s_channelType channel ) = 0; + // to is in Db (sigh), over is in seconds + virtual void FadeSound( const s_channelType channel, float to, float over ) = 0; + + // returns true if there are any sounds playing from this emitter. There is some conservative + // slop at the end to remove inconsistent race conditions with the sound thread updates. + // FIXME: network game: on a dedicated server, this will always be false + virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const = 0; + + // returns a 0.0 to 1.0 value based on the current sound amplitude, allowing + // graphic effects to be modified in time with the audio. + // just samples the raw wav file, it doesn't account for volume overrides in the + virtual float CurrentAmplitude( int channelFlags = -1, bool factorDistance = false ) = 0; + + // Returns true if the emitter is in the passed in world + virtual bool AttachedToWorld( int id ) const = 0; + + // for save games. Index will always be > 0 + virtual int Handle( void ) const = 0; +}; + +/* +=============================================================================== + + SOUND SYSTEM + +=============================================================================== +*/ + +typedef struct { + idStr name; + idStr format; + int numChannels; + int numSamplesPerSecond; + int num44kHzSamples; + int numBytes; + bool looping; + float lastVolume; + int start44kHzTime; + int current44kHzTime; +} soundDecoderInfo_t; + +typedef struct soundPortalTrace_s { + int portalArea; + const struct soundPortalTrace_s *prevStack; +} soundPortalTrace_t; + +class idSoundSystem { +public: + virtual ~idSoundSystem( void ) {} + + // all non-hardware initialization + virtual void Init( void ) = 0; + + // shutdown routine + virtual void Shutdown( void ) = 0; + + // call ClearBuffer if there is a chance that the AsyncUpdate won't get called + // for 20+ msec, which would cause a stuttering repeat of the current + // buffer contents + virtual void ClearBuffer( void ) = 0; + + // sound is attached to the window, and must be recreated when the window is changed + virtual bool InitHW( void ) = 0; + virtual void InitVoiceComms( void ) = 0; + virtual bool ShutdownHW( void ) = 0; + virtual void ShutdownVoiceComms( void ) = 0; + + // Called once per common frame to check on changes to the sound system + virtual void Frame( void ) = 0; + // Service the sound system + virtual void ForegroundUpdate( void ) = 0; + + // asyn loop, called at 60Hz + virtual int AsyncUpdate( int time ) = 0; + + // direct mixing for OSes that support it + virtual int AsyncMix( int soundTime, float *mixBuffer ) = 0; + + // async loop, when the sound driver uses a write strategy + virtual int AsyncUpdateWrite( int time ) = 0; + + // it is a good idea to mute everything when starting a new level, + // because sounds may be started before a valid listener origin + // is specified + virtual void SetMute( bool mute ) = 0; + + // for the sound level meter window + virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ) = 0; + + // get sound decoder info + virtual int GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ) = 0; + + // Mark all soundSamples as currently unused, + // but don't free anything. + virtual void BeginLevelLoad( const char *mapName ) = 0; + + // Free all soundSamples marked as unused + // We might want to defer the loading of new sounds to this point, + // as we do with images, to avoid having a union in memory at one time. + virtual void EndLevelLoad( const char *mapName ) = 0; + +// RAVEN BEGIN +// jnewquist: Free all sounds +#ifdef _XENON + virtual void FlushLevelSoundSamples( void ) = 0; +#endif +// RAVEN END + + // Frees the empty base blocks in the appropriate soundCache + virtual void CleanCache( void ) = 0; + + // prints memory info + virtual void PrintMemInfo( MemInfo *mi ) = 0; +#ifdef _USE_OPENAL + // is EAX support present - -1: disabled at compile time. 0: no suitable hardwre 1: ok + virtual int IsEAXAvailable( void ) = 0; + virtual const char * GetDeviceName( int index ) = 0; + virtual const char * GetDefaultDeviceName( void ) = 0; +#endif + + // SoundWorld stuff + + // call at each map start + virtual void SetRenderWorld( idRenderWorld *rw ) = 0; + virtual void StopAllSounds( int worldId ) = 0; + +// RAVEN BEGIN + // dissociate all virtual channels from hardware + virtual void DisableAllSounds( void ) = 0; + + // get a new emitter that can play sounds in this world + virtual int AllocSoundEmitter( int worldId ) = 0; + virtual void FreeSoundEmitter( int worldId, int handle, bool immediate ) = 0; +// RAVEN END + + // for load games, index 0 will return NULL + virtual idSoundEmitter *EmitterForIndex( int worldId, int index ) = 0; + virtual int GetNumEmitters( void ) const = 0; + + // query sound samples from all emitters reaching a given position + virtual float CurrentShakeAmplitudeForPosition( int worldId, const int time, const idVec3 &listenerPosition ) = 0; + + // where is the camera/microphone + // listenerId allows listener-private and antiPrivate sounds to be filtered + // gameTime is in msec, and is used to time sound queries and removals so that they are independent + // of any race conditions with the async update + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId, const int gameTime, const idStr &areaName ) = 0; + + // reset the listener portal to invalid during level transitions + virtual void ResetListener( void ) = 0; + + // fade all sounds in the world with a given shader soundClass + // to is in Db (sigh), over is in seconds + virtual void FadeSoundClasses( int worldId, const int soundClass, float to, const float over ) = 0; + + // background music + virtual void PlayShaderDirectly( int worldId, const char *name, int channel = -1 ) = 0; + + // dumps the current state and begins archiving commands + virtual void StartWritingDemo( int worldId, idDemoFile *demo ) = 0; + virtual void StopWritingDemo( int worldId ) = 0; + + // read a sound command from a demo file + virtual void ProcessDemoCommand( int worldId, idDemoFile *demo ) = 0; + + virtual int GetHardwareTime( void ) const = 0; + + // unpauses the selected soundworld, pauses all others + virtual int SetActiveSoundWorld( bool on ) = 0; +// RAVEN BEGIN +// jnewquist: Accessor for active sound world + virtual int GetActiveSoundWorld( void ) = 0; +// RAVEN END + + // Write the sound output to multiple wav files. Note that this does not use the + // work done by AsyncUpdate, it mixes explicitly in the foreground every PlaceOrigin(), + // under the assumption that we are rendering out screenshots and the gameTime is going + // much slower than real time. + // path should not include an extension, and the generated filenames will be: + // _left.raw, _right.raw, or _51left.raw, _51right.raw, + // _51center.raw, _51lfe.raw, _51backleft.raw, _51backright.raw, + // If only two channel mixing is enabled, the left and right .raw files will also be + // combined into a stereo .wav file. + virtual void AVIOpen( int worldId, const char *path, const char *name ) = 0; + virtual void AVIClose( int worldId ) = 0; + + // SaveGame / demo Support + virtual void WriteToSaveGame( int worldId, idFile *savefile ) = 0; + virtual void ReadFromSaveGame( int worldId, idFile *savefile ) = 0; + +// RAVEN BEGIN +// rjohnson: added list active sounds + virtual void ListActiveSounds( int worldId ) = 0; +// RAVEN END + // End SoundWorld stuff + +// RAVEN BEGIN +// jscott: added + virtual size_t ListSoundSummary( void ) = 0; + + virtual bool HasCache( void ) const = 0; + virtual rvCommonSample *FindSample( const idStr &filename ) = 0; + virtual int SamplesToMilliseconds( int samples ) const = 0; + virtual void * AllocSoundSample( int size ) = 0; + virtual void FreeSoundSample( const byte *address ) = 0; + + virtual bool GetInsideLevelLoad( void ) const = 0; + virtual bool ValidateSoundShader( idSoundShader *shader ) = 0; + +// jscott: voice comm support + virtual bool EnableRecording( bool enable, bool test, float &micLevel ) = 0; + virtual int GetVoiceData( byte *buffer, int maxSize ) = 0; + virtual void PlayVoiceData( int clientNum, const byte *buffer, int bytes ) = 0; + virtual void BufferVoiceData( void ) = 0; + virtual void MixVoiceData( float *finalMixBuffer, int numSpeakers, int newTime ) = 0; +// ddynerman: voice comm utility + virtual int GetCommClientNum( int channel ) const = 0; + virtual int GetNumVoiceChannels( void ) const = 0; + +// jscott: reverb editor support + virtual const char *GetReverbName( int reverb ) = 0; + virtual int GetNumAreas( void ) = 0; + virtual int GetReverb( int area ) = 0; + virtual bool SetReverb( int area, const char *reverbName, const char *fileName ) = 0; + + virtual void EndCinematic() = 0; + +// RAVEN END +}; + +extern idSoundSystem *soundSystem; + +void S_InitSoundSystem( void ); + +#endif /* !__SOUND__ */ diff --git a/source/sys/AutoVersion.h b/source/sys/AutoVersion.h new file mode 100644 index 0000000..5adf02a --- /dev/null +++ b/source/sys/AutoVersion.h @@ -0,0 +1,13 @@ +#ifndef __AUTO_VERSION_HEADER +#define __AUTO_VERSION_HEADER + +// used by the .rc files +#define VERSION_MAJOR_RELEASE 1 +#define VERSION_MINOR_RELEASE 4 +#define VERSION_INTERNAL_BUILD 2 +#define VERSION_BUILD_NUMBER 0 + +#define VERSION_STRING "1, 4, 2, 0" +#define VERSION_STRING_DOTTED "1.4.2" + +#endif // __AUTO_VERSION_HEADER diff --git a/source/sys/linux/qgl_enforce.h b/source/sys/linux/qgl_enforce.h new file mode 100644 index 0000000..84f4c70 --- /dev/null +++ b/source/sys/linux/qgl_enforce.h @@ -0,0 +1,347 @@ + +#define glAccum use_qglAccum +#define glAlphaFunc use_qglAlphaFunc +#define glAreTexturesResident use_qglAreTexturesResident +#define glArrayElement use_qglArrayElement +#define glBegin use_qglBegin +#define glBindTexture use_qglBindTexture +#define glBitmap use_qglBitmap +#define glBlendFunc use_qglBlendFunc +#define glCallList use_qglCallList +#define glCallLists use_qglCallLists +#define glClear use_qglClear +#define glClearAccum use_qglClearAccum +#define glClearColor use_qglClearColor +#define glClearDepth use_qglClearDepth +#define glClearIndex use_qglClearIndex +#define glClearStencil use_qglClearStencil +#define glClipPlane use_qglClipPlane +#define glColor3b use_qglColor3b +#define glColor3bv use_qglColor3bv +#define glColor3d use_qglColor3d +#define glColor3dv use_qglColor3dv +#define glColor3f use_qglColor3f +#define glColor3fv use_qglColor3fv +#define glColor3i use_qglColor3i +#define glColor3iv use_qglColor3iv +#define glColor3s use_qglColor3s +#define glColor3sv use_qglColor3sv +#define glColor3ub use_qglColor3ub +#define glColor3ubv use_qglColor3ubv +#define glColor3ui use_qglColor3ui +#define glColor3uiv use_qglColor3uiv +#define glColor3us use_qglColor3us +#define glColor3usv use_qglColor3usv +#define glColor4b use_qglColor4b +#define glColor4bv use_qglColor4bv +#define glColor4d use_qglColor4d +#define glColor4dv use_qglColor4dv +#define glColor4f use_qglColor4f +#define glColor4fv use_qglColor4fv +#define glColor4i use_qglColor4i +#define glColor4iv use_qglColor4iv +#define glColor4s use_qglColor4s +#define glColor4sv use_qglColor4sv +#define glColor4ub use_qglColor4ub +#define glColor4ubv use_qglColor4ubv +#define glColor4ui use_qglColor4ui +#define glColor4uiv use_qglColor4uiv +#define glColor4us use_qglColor4us +#define glColor4usv use_qglColor4usv +#define glColorMask use_qglColorMask +#define glColorMaterial use_qglColorMaterial +#define glColorPointer use_qglColorPointer +#define glCopyPixels use_qglCopyPixels +#define glCopyTexImage1D use_qglCopyTexImage1D +#define glCopyTexImage2D use_qglCopyTexImage2D +#define glCopyTexSubImage1D use_qglCopyTexSubImage1D +#define glCopyTexSubImage2D use_qglCopyTexSubImage2D +#define glCullFace use_qglCullFace +#define glDeleteLists use_qglDeleteLists +#define glDeleteTextures use_qglDeleteTextures +#define glDepthFunc use_qglDepthFunc +#define glDepthMask use_qglDepthMask +#define glDepthRange use_qglDepthRange +#define glDisable use_qglDisable +#define glDisableClientState use_qglDisableClientState +#define glDrawArrays use_qglDrawArrays +#define glDrawBuffer use_qglDrawBuffer +#define glDrawElements use_qglDrawElements +#define glDrawPixels use_qglDrawPixels +#define glEdgeFlag use_qglEdgeFlag +#define glEdgeFlagPointer use_qglEdgeFlagPointer +#define glEdgeFlagv use_qglEdgeFlagv +#define glEnable use_qglEnable +#define glEnableClientState use_qglEnableClientState +#define glEnd use_qglEnd +#define glEndList use_qglEndList +#define glEvalCoord1d use_qglEvalCoord1d +#define glEvalCoord1dv use_qglEvalCoord1dv +#define glEvalCoord1f use_qglEvalCoord1f +#define glEvalCoord1fv use_qglEvalCoord1fv +#define glEvalCoord2d use_qglEvalCoord2d +#define glEvalCoord2dv use_qglEvalCoord2dv +#define glEvalCoord2f use_qglEvalCoord2f +#define glEvalCoord2fv use_qglEvalCoord2fv +#define glEvalMesh1 use_qglEvalMesh1 +#define glEvalMesh2 use_qglEvalMesh2 +#define glEvalPoint1 use_qglEvalPoint1 +#define glEvalPoint2 use_qglEvalPoint2 +#define glFeedbackBuffer use_qglFeedbackBuffer +#define glFinish use_qglFinish +#define glFlush use_qglFlush +#define glFogf use_qglFogf +#define glFogfv use_qglFogfv +#define glFogi use_qglFogi +#define glFogiv use_qglFogiv +#define glFrontFace use_qglFrontFace +#define glFrustum use_qglFrustum +#define glGenLists use_qglGenLists +#define glGenTextures use_qglGenTextures +#define glGetBooleanv use_qglGetBooleanv +#define glGetClipPlane use_qglGetClipPlane +#define glGetDoublev use_qglGetDoublev +#define glGetError use_qglGetError +#define glGetFloatv use_qglGetFloatv +#define glGetIntegerv use_qglGetIntegerv +#define glGetLightfv use_qglGetLightfv +#define glGetLightiv use_qglGetLightiv +#define glGetMapdv use_qglGetMapdv +#define glGetMapfv use_qglGetMapfv +#define glGetMapiv use_qglGetMapiv +#define glGetMaterialfv use_qglGetMaterialfv +#define glGetMaterialiv use_qglGetMaterialiv +#define glGetPixelMapfv use_qglGetPixelMapfv +#define glGetPixelMapuiv use_qglGetPixelMapuiv +#define glGetPixelMapusv use_qglGetPixelMapusv +#define glGetPointerv use_qglGetPointerv +#define glGetPolygonStipple use_qglGetPolygonStipple +#define glGetString use_qglGetString +#define glGetTexEnvfv use_qglGetTexEnvfv +#define glGetTexEnviv use_qglGetTexEnviv +#define glGetTexGendv use_qglGetTexGendv +#define glGetTexGenfv use_qglGetTexGenfv +#define glGetTexGeniv use_qglGetTexGeniv +#define glGetTexImage use_qglGetTexImage +#define glGetTexLevelParameterfv use_qglGetTexLevelParameterfv +#define glGetTexLevelParameteriv use_qglGetTexLevelParameteriv +#define glGetTexParameterfv use_qglGetTexParameterfv +#define glGetTexParameteriv use_qglGetTexParameteriv +#define glHint use_qglHint +#define glIndexMask use_qglIndexMask +#define glIndexPointer use_qglIndexPointer +#define glIndexd use_qglIndexd +#define glIndexdv use_qglIndexdv +#define glIndexf use_qglIndexf +#define glIndexfv use_qglIndexfv +#define glIndexi use_qglIndexi +#define glIndexiv use_qglIndexiv +#define glIndexs use_qglIndexs +#define glIndexsv use_qglIndexsv +#define glIndexub use_qglIndexub +#define glIndexubv use_qglIndexubv +#define glInitNames use_qglInitNames +#define glInterleavedArrays use_qglInterleavedArrays +#define glIsEnabled use_qglIsEnabled +#define glIsList use_qglIsList +#define glIsTexture use_qglIsTexture +#define glLightModelf use_qglLightModelf +#define glLightModelfv use_qglLightModelfv +#define glLightModeli use_qglLightModeli +#define glLightModeliv use_qglLightModeliv +#define glLightf use_qglLightf +#define glLightfv use_qglLightfv +#define glLighti use_qglLighti +#define glLightiv use_qglLightiv +#define glLineStipple use_qglLineStipple +#define glLineWidth use_qglLineWidth +#define glListBase use_qglListBase +#define glLoadIdentity use_qglLoadIdentity +#define glLoadMatrixd use_qglLoadMatrixd +#define glLoadMatrixf use_qglLoadMatrixf +#define glLoadName use_qglLoadName +#define glLogicOp use_qglLogicOp +#define glMap1d use_qglMap1d +#define glMap1f use_qglMap1f +#define glMap2d use_qglMap2d +#define glMap2f use_qglMap2f +#define glMapGrid1d use_qglMapGrid1d +#define glMapGrid1f use_qglMapGrid1f +#define glMapGrid2d use_qglMapGrid2d +#define glMapGrid2f use_qglMapGrid2f +#define glMaterialf use_qglMaterialf +#define glMaterialfv use_qglMaterialfv +#define glMateriali use_qglMateriali +#define glMaterialiv use_qglMaterialiv +#define glMatrixMode use_qglMatrixMode +#define glMultMatrixd use_qglMultMatrixd +#define glMultMatrixf use_qglMultMatrixf +#define glNewList use_qglNewList +#define glNormal3b use_qglNormal3b +#define glNormal3bv use_qglNormal3bv +#define glNormal3d use_qglNormal3d +#define glNormal3dv use_qglNormal3dv +#define glNormal3f use_qglNormal3f +#define glNormal3fv use_qglNormal3fv +#define glNormal3i use_qglNormal3i +#define glNormal3iv use_qglNormal3iv +#define glNormal3s use_qglNormal3s +#define glNormal3sv use_qglNormal3sv +#define glNormalPointer use_qglNormalPointer +#define glOrtho use_qglOrtho +#define glPassThrough use_qglPassThrough +#define glPixelMapfv use_qglPixelMapfv +#define glPixelMapuiv use_qglPixelMapuiv +#define glPixelMapusv use_qglPixelMapusv +#define glPixelStoref use_qglPixelStoref +#define glPixelStorei use_qglPixelStorei +#define glPixelTransferf use_qglPixelTransferf +#define glPixelTransferi use_qglPixelTransferi +#define glPixelZoom use_qglPixelZoom +#define glPointSize use_qglPointSize +#define glPolygonMode use_qglPolygonMode +#define glPolygonOffset use_qglPolygonOffset +#define glPolygonStipple use_qglPolygonStipple +#define glPopAttrib use_qglPopAttrib +#define glPopClientAttrib use_qglPopClientAttrib +#define glPopMatrix use_qglPopMatrix +#define glPopName use_qglPopName +#define glPrioritizeTextures use_qglPrioritizeTextures +#define glPushAttrib use_qglPushAttrib +#define glPushClientAttrib use_qglPushClientAttrib +#define glPushMatrix use_qglPushMatrix +#define glPushName use_qglPushName +#define glRasterPos2d use_qglRasterPos2d +#define glRasterPos2dv use_qglRasterPos2dv +#define glRasterPos2f use_qglRasterPos2f +#define glRasterPos2fv use_qglRasterPos2fv +#define glRasterPos2i use_qglRasterPos2i +#define glRasterPos2iv use_qglRasterPos2iv +#define glRasterPos2s use_qglRasterPos2s +#define glRasterPos2sv use_qglRasterPos2sv +#define glRasterPos3d use_qglRasterPos3d +#define glRasterPos3dv use_qglRasterPos3dv +#define glRasterPos3f use_qglRasterPos3f +#define glRasterPos3fv use_qglRasterPos3fv +#define glRasterPos3i use_qglRasterPos3i +#define glRasterPos3iv use_qglRasterPos3iv +#define glRasterPos3s use_qglRasterPos3s +#define glRasterPos3sv use_qglRasterPos3sv +#define glRasterPos4d use_qglRasterPos4d +#define glRasterPos4dv use_qglRasterPos4dv +#define glRasterPos4f use_qglRasterPos4f +#define glRasterPos4fv use_qglRasterPos4fv +#define glRasterPos4i use_qglRasterPos4i +#define glRasterPos4iv use_qglRasterPos4iv +#define glRasterPos4s use_qglRasterPos4s +#define glRasterPos4sv use_qglRasterPos4sv +#define glReadBuffer use_qglReadBuffer +#define glReadPixels use_qglReadPixels +#define glRectd use_qglRectd +#define glRectdv use_qglRectdv +#define glRectf use_qglRectf +#define glRectfv use_qglRectfv +#define glRecti use_qglRecti +#define glRectiv use_qglRectiv +#define glRects use_qglRects +#define glRectsv use_qglRectsv +#define glRenderMode use_qglRenderMode +#define glRotated use_qglRotated +#define glRotatef use_qglRotatef +#define glScaled use_qglScaled +#define glScalef use_qglScalef +#define glScissor use_qglScissor +#define glSelectBuffer use_qglSelectBuffer +#define glShadeModel use_qglShadeModel +#define glStencilFunc use_qglStencilFunc +#define glStencilMask use_qglStencilMask +#define glStencilOp use_qglStencilOp +#define glTexCoord1d use_qglTexCoord1d +#define glTexCoord1dv use_qglTexCoord1dv +#define glTexCoord1f use_qglTexCoord1f +#define glTexCoord1fv use_qglTexCoord1fv +#define glTexCoord1i use_qglTexCoord1i +#define glTexCoord1iv use_qglTexCoord1iv +#define glTexCoord1s use_qglTexCoord1s +#define glTexCoord1sv use_qglTexCoord1sv +#define glTexCoord2d use_qglTexCoord2d +#define glTexCoord2dv use_qglTexCoord2dv +#define glTexCoord2f use_qglTexCoord2f +#define glTexCoord2fv use_qglTexCoord2fv +#define glTexCoord2i use_qglTexCoord2i +#define glTexCoord2iv use_qglTexCoord2iv +#define glTexCoord2s use_qglTexCoord2s +#define glTexCoord2sv use_qglTexCoord2sv +#define glTexCoord3d use_qglTexCoord3d +#define glTexCoord3dv use_qglTexCoord3dv +#define glTexCoord3f use_qglTexCoord3f +#define glTexCoord3fv use_qglTexCoord3fv +#define glTexCoord3i use_qglTexCoord3i +#define glTexCoord3iv use_qglTexCoord3iv +#define glTexCoord3s use_qglTexCoord3s +#define glTexCoord3sv use_qglTexCoord3sv +#define glTexCoord4d use_qglTexCoord4d +#define glTexCoord4dv use_qglTexCoord4dv +#define glTexCoord4f use_qglTexCoord4f +#define glTexCoord4fv use_qglTexCoord4fv +#define glTexCoord4i use_qglTexCoord4i +#define glTexCoord4iv use_qglTexCoord4iv +#define glTexCoord4s use_qglTexCoord4s +#define glTexCoord4sv use_qglTexCoord4sv +#define glTexCoordPointer use_qglTexCoordPointer +#define glTexEnvf use_qglTexEnvf +#define glTexEnvfv use_qglTexEnvfv +#define glTexEnvi use_qglTexEnvi +#define glTexEnviv use_qglTexEnviv +#define glTexGend use_qglTexGend +#define glTexGendv use_qglTexGendv +#define glTexGenf use_qglTexGenf +#define glTexGenfv use_qglTexGenfv +#define glTexGeni use_qglTexGeni +#define glTexGeniv use_qglTexGeniv +#define glTexImage1D use_qglTexImage1D +#define glTexImage2D use_qglTexImage2D +#define glTexParameterf use_qglTexParameterf +#define glTexParameterfv use_qglTexParameterfv +#define glTexParameteri use_qglTexParameteri +#define glTexParameteriv use_qglTexParameteriv +#define glTexSubImage1D use_qglTexSubImage1D +#define glTexSubImage2D use_qglTexSubImage2D +#define glTranslated use_qglTranslated +#define glTranslatef use_qglTranslatef +#define glVertex2d use_qglVertex2d +#define glVertex2dv use_qglVertex2dv +#define glVertex2f use_qglVertex2f +#define glVertex2fv use_qglVertex2fv +#define glVertex2i use_qglVertex2i +#define glVertex2iv use_qglVertex2iv +#define glVertex2s use_qglVertex2s +#define glVertex2sv use_qglVertex2sv +#define glVertex3d use_qglVertex3d +#define glVertex3dv use_qglVertex3dv +#define glVertex3f use_qglVertex3f +#define glVertex3fv use_qglVertex3fv +#define glVertex3i use_qglVertex3i +#define glVertex3iv use_qglVertex3iv +#define glVertex3s use_qglVertex3s +#define glVertex3sv use_qglVertex3sv +#define glVertex4d use_qglVertex4d +#define glVertex4dv use_qglVertex4dv +#define glVertex4f use_qglVertex4f +#define glVertex4fv use_qglVertex4fv +#define glVertex4i use_qglVertex4i +#define glVertex4iv use_qglVertex4iv +#define glVertex4s use_qglVertex4s +#define glVertex4sv use_qglVertex4sv +#define glVertexPointer use_qglVertexPointer +#define glViewport use_qglViewport + +#define glChooseVisual use_qglChooseVisual +#define glCreateContext use_qglCreateContext +#define glDestroyContext use_qglDestroyContext +#define glMakeCurrent use_qglMakeCurrent +#define glSwapBuffers use_qglSwapBuffers +#define glGetProcAddressARB use_qglGetProcAddressARB +#define glGetCurrentContext use_qglGetCurrentContext + + diff --git a/source/sys/osx/Resources/Game.plist b/source/sys/osx/Resources/Game.plist new file mode 100644 index 0000000..473d5ad --- /dev/null +++ b/source/sys/osx/Resources/Game.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + game.so + CFBundleIdentifier + com.aspyr.quakeiv.bundle + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + QKIV + CFBundleVersion + 1.0 + CSResourcesFileMapped + yes + + diff --git a/source/sys/osx/Resources/Info.plist b/source/sys/osx/Resources/Info.plist new file mode 100644 index 0000000..288cc6d --- /dev/null +++ b/source/sys/osx/Resources/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + Quake 4 + CFBundleExecutable + Quake 4 + CFBundleIconFile + Quake4 + CFBundleIdentifier + com.aspyr.Quake4 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Quake 4 + CFBundlePackageType + APPL + CFBundleSignature + QKIV + CFBundleVersion + 1.4.2 ASPYR_BUILD_NUMBER + NSMainNibFile + SDLMain + NSPrincipalClass + NSApplication + + diff --git a/source/sys/osx/Subprojects/game.xcodeproj/project.pbxproj b/source/sys/osx/Subprojects/game.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f58c0f4 --- /dev/null +++ b/source/sys/osx/Subprojects/game.xcodeproj/project.pbxproj @@ -0,0 +1,1245 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 253A77B10A67159100A3CFB3 /* Buying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 253A77AD0A67159100A3CFB3 /* Buying.cpp */; }; + 253A77B40A6715A300A3CFB3 /* WeaponNapalmGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */; }; + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8114A1A2090D686B0025E084 /* libidlib_pic.a */; }; + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */; }; + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */; }; + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */; }; + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */; }; + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */; }; + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */; }; + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405A092A64D300B2BF95 /* Sound.cpp */; }; + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */; }; + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400E092A64D300B2BF95 /* StatWindow.cpp */; }; + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */; }; + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */; }; + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */; }; + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */; }; + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */; }; + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */; }; + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */; }; + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400C092A64D300B2BF95 /* StatManager.cpp */; }; + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */; }; + 81114FF8093E194300A5C91A /* AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F75092A64D300B2BF95 /* AF.cpp */; }; + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */; }; + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBF092A64D300B2BF95 /* Camera.cpp */; }; + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */; }; + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */; }; + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */; }; + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F89092A64D300B2BF95 /* AI_events.cpp */; }; + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */; }; + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */; }; + 81115001093E194300A5C91A /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCA092A64D300B2BF95 /* Effect.cpp */; }; + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */; }; + 81115003093E194300A5C91A /* Player_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4040092A64D300B2BF95 /* Player_States.cpp */; }; + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */; }; + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */; }; + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */; }; + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */; }; + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */; }; + 81115009093E194300A5C91A /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE8092A64D300B2BF95 /* State.cpp */; }; + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */; }; + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */; }; + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */; }; + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */; }; + 8111500E093E194300A5C91A /* Mover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4002092A64D300B2BF95 /* Mover.cpp */; }; + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */; }; + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */; }; + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4050092A64D300B2BF95 /* Script_Program.cpp */; }; + 81115012093E194300A5C91A /* Push.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403A092A64D300B2BF95 /* Push.cpp */; }; + 81115013093E194300A5C91A /* IconManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */; }; + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */; }; + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */; }; + 81115016093E194300A5C91A /* Moveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4000092A64D300B2BF95 /* Moveable.cpp */; }; + 81115017093E194300A5C91A /* Clip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4016092A64D300B2BF95 /* Clip.cpp */; }; + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */; }; + 81115019093E194300A5C91A /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4060092A64D300B2BF95 /* Target.cpp */; }; + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4064092A64D300B2BF95 /* Trigger.cpp */; }; + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4088092A64D300B2BF95 /* Weapon.cpp */; }; + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */; }; + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */; }; + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */; }; + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */; }; + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */; }; + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */; }; + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */; }; + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */; }; + 81115024093E194300A5C91A /* Playback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403C092A64D300B2BF95 /* Playback.cpp */; }; + 81115025093E194300A5C91A /* Force.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4018092A64D300B2BF95 /* Force.cpp */; }; + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400A092A64D300B2BF95 /* StatEvent.cpp */; }; + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */; }; + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */; }; + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */; }; + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */; }; + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */; }; + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */; }; + 8111502D093E194300A5C91A /* Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403D092A64D300B2BF95 /* Player.cpp */; }; + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */; }; + 8111502F093E194300A5C91A /* IK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF4092A64D300B2BF95 /* IK.cpp */; }; + 81115030093E194300A5C91A /* Projectile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4043092A64D300B2BF95 /* Projectile.cpp */; }; + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4067092A64D300B2BF95 /* Vehicle.cpp */; }; + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */; }; + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */; }; + 81115034093E194300A5C91A /* Pvs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4045092A64D300B2BF95 /* Pvs.cpp */; }; + 81115035093E194300A5C91A /* Light.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFA092A64D300B2BF95 /* Light.cpp */; }; + 81115036093E194300A5C91A /* Entity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCE092A64D300B2BF95 /* Entity.cpp */; }; + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */; }; + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */; }; + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */; }; + 8111503A093E194300A5C91A /* AAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7A092A64D300B2BF95 /* AAS.cpp */; }; + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */; }; + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */; }; + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */; }; + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */; }; + 81115040093E194300A5C91A /* AI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F84092A64D300B2BF95 /* AI.cpp */; }; + 81115041093E194300A5C91A /* Tourney.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4010092A64D300B2BF95 /* Tourney.cpp */; }; + 81115042093E194300A5C91A /* TramGate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4062092A64D300B2BF95 /* TramGate.cpp */; }; + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */; }; + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */; }; + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406D092A64D300B2BF95 /* VehicleController.cpp */; }; + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */; }; + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */; }; + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */; }; + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */; }; + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */; }; + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */; }; + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F92092A64D300B2BF95 /* AI_States.cpp */; }; + 8111504D093E194300A5C91A /* Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE3092A64D300B2BF95 /* Event.cpp */; }; + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */; }; + 8111504F093E194300A5C91A /* CTF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4005092A64D300B2BF95 /* CTF.cpp */; }; + 81115050093E194300A5C91A /* GameState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4007092A64D300B2BF95 /* GameState.cpp */; }; + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */; }; + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */; }; + 81115053093E194300A5C91A /* Physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4022092A64D300B2BF95 /* Physics.cpp */; }; + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */; }; + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */; }; + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */; }; + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */; }; + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */; }; + 81115059093E194300A5C91A /* Class.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDF092A64D300B2BF95 /* Class.cpp */; }; + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */; }; + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */; }; + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */; }; + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */; }; + 8111505E093E194300A5C91A /* Misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFE092A64D300B2BF95 /* Misc.cpp */; }; + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */; }; + 81115060093E194300A5C91A /* Icon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF0092A64D300B2BF95 /* Icon.cpp */; }; + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */; }; + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */; }; + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */; }; + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */; }; + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */; }; + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */; }; + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */; }; + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */; }; + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */; }; + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */; }; + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */; }; + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */; }; + 8111506D093E194300A5C91A /* Anim.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB7092A64D300B2BF95 /* Anim.cpp */; }; + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */; }; + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */; }; + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405E092A64D300B2BF95 /* SplineMover.cpp */; }; + 81115071093E194300A5C91A /* spawner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405C092A64D300B2BF95 /* spawner.cpp */; }; + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */; }; + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */; }; + 81115074093E194300A5C91A /* LipSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */; }; + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */; }; + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */; }; + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */; }; + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */; }; + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */; }; + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401E092A64D300B2BF95 /* Force_Field.cpp */; }; + 8111507B093E194300A5C91A /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F73092A64D300B2BF95 /* Actor.cpp */; }; + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4041092A64D300B2BF95 /* PlayerView.cpp */; }; + 8111507D093E194300A5C91A /* Item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF8092A64D300B2BF95 /* Item.cpp */; }; + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */; }; + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */; }; + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */; }; + 81115081093E194300A5C91A /* Instance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF6092A64D300B2BF95 /* Instance.cpp */; }; + 814E1D11097C19DD009E5F86 /* FreeView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D0D097C19DD009E5F86 /* FreeView.cpp */; }; + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */; }; + 816AD2E00981819D00CAC29C /* Game.plist in Resources */ = {isa = PBXBuildFile; fileRef = 816AD2DF0981819D00CAC29C /* Game.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 81114FE2093E189800A5C91A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = idlib_pic; + }; + 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 253A77AD0A67159100A3CFB3 /* Buying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Buying.cpp; sourceTree = ""; }; + 253A77AE0A67159100A3CFB3 /* Buying.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Buying.h; sourceTree = ""; }; + 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNapalmGun.cpp; sourceTree = ""; }; + 81114DFB093E117D00A5C91A /* game.so.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = game.so.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8114A19C090D686B0025E084 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = idlib.xcodeproj; sourceTree = ""; }; + 814E1D0D097C19DD009E5F86 /* FreeView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FreeView.cpp; sourceTree = ""; }; + 814E1D0E097C19DD009E5F86 /* FreeView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FreeView.h; sourceTree = ""; }; + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientAFEntity.cpp; sourceTree = ""; }; + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientAFEntity.h; sourceTree = ""; }; + 816AD2DF0981819D00CAC29C /* Game.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; name = Game.plist; path = ../Resources/Game.plist; sourceTree = SOURCE_ROOT; }; + 81CC3F73092A64D300B2BF95 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 81CC3F74092A64D300B2BF95 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 81CC3F75092A64D300B2BF95 /* AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AF.cpp; sourceTree = ""; }; + 81CC3F76092A64D300B2BF95 /* AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AF.h; sourceTree = ""; }; + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AFEntity.cpp; sourceTree = ""; }; + 81CC3F78092A64D300B2BF95 /* AFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AFEntity.h; sourceTree = ""; }; + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS.cpp; sourceTree = ""; }; + 81CC3F7B092A64D300B2BF95 /* AAS.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS.h; sourceTree = ""; }; + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_debug.cpp; sourceTree = ""; }; + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_Find.cpp; sourceTree = ""; }; + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_Find.h; sourceTree = ""; }; + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_local.h; sourceTree = ""; }; + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_pathing.cpp; sourceTree = ""; }; + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_routing.cpp; sourceTree = ""; }; + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_tactical.cpp; sourceTree = ""; }; + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_tactical.h; sourceTree = ""; }; + 81CC3F84092A64D300B2BF95 /* AI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI.cpp; sourceTree = ""; }; + 81CC3F85092A64D300B2BF95 /* AI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI.h; sourceTree = ""; }; + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Actions.cpp; sourceTree = ""; }; + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Announcements.cpp; sourceTree = ""; }; + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Debug.cpp; sourceTree = ""; }; + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_events.cpp; sourceTree = ""; }; + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Manager.cpp; sourceTree = ""; }; + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Manager.h; sourceTree = ""; }; + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Medic.cpp; sourceTree = ""; }; + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Medic.h; sourceTree = ""; }; + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Move.cpp; sourceTree = ""; }; + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Move.h; sourceTree = ""; }; + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_pathing.cpp; sourceTree = ""; }; + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_States.cpp; sourceTree = ""; }; + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Tactical.cpp; sourceTree = ""; }; + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Tactical.h; sourceTree = ""; }; + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Util.cpp; sourceTree = ""; }; + 81CC3F96092A64D300B2BF95 /* AI_Util.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Util.h; sourceTree = ""; }; + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Berserker.cpp; sourceTree = ""; }; + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossBuddy.cpp; sourceTree = ""; }; + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossMakron.cpp; sourceTree = ""; }; + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyGround.cpp; sourceTree = ""; }; + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyHover.cpp; sourceTree = ""; }; + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_FailedTransfer.cpp; sourceTree = ""; }; + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Fatty.cpp; sourceTree = ""; }; + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gladiator.cpp; sourceTree = ""; }; + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Grunt.cpp; sourceTree = ""; }; + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gunner.cpp; sourceTree = ""; }; + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Harvester.cpp; sourceTree = ""; }; + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HarvesterDispersal.cpp; sourceTree = ""; }; + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HeavyHoverTank.cpp; sourceTree = ""; }; + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_IronMaiden.cpp; sourceTree = ""; }; + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_LightTank.cpp; sourceTree = ""; }; + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_NetworkGuardian.cpp; sourceTree = ""; }; + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_RepairBot.cpp; sourceTree = ""; }; + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Scientist.cpp; sourceTree = ""; }; + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Sentry.cpp; sourceTree = ""; }; + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_SlimyTransfer.cpp; sourceTree = ""; }; + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StreamProtector.cpp; sourceTree = ""; }; + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggFlyer.cpp; sourceTree = ""; }; + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggHover.cpp; sourceTree = ""; }; + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggMarine.cpp; sourceTree = ""; }; + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TeleportDropper.cpp; sourceTree = ""; }; + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Turret.cpp; sourceTree = ""; }; + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TurretFlying.cpp; sourceTree = ""; }; + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAI.cpp; sourceTree = ""; }; + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAI.h; sourceTree = ""; }; + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim.cpp; sourceTree = ""; }; + 81CC3FB8092A64D300B2BF95 /* Anim.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim.h; sourceTree = ""; }; + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Blend.cpp; sourceTree = ""; }; + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Import.cpp; sourceTree = ""; }; + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Testmodel.cpp; sourceTree = ""; }; + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim_Testmodel.h; sourceTree = ""; }; + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BrittleFracture.cpp; sourceTree = ""; }; + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BrittleFracture.h; sourceTree = ""; }; + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Camera.cpp; sourceTree = ""; }; + 81CC3FC0092A64D300B2BF95 /* Camera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Camera.h; sourceTree = ""; }; + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEffect.cpp; sourceTree = ""; }; + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEffect.h; sourceTree = ""; }; + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEntity.cpp; sourceTree = ""; }; + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEntity.h; sourceTree = ""; }; + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientModel.cpp; sourceTree = ""; }; + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientModel.h; sourceTree = ""; }; + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientMoveable.cpp; sourceTree = ""; }; + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientMoveable.h; sourceTree = ""; }; + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; + 81CC3FCB092A64D300B2BF95 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Entity.cpp; sourceTree = ""; }; + 81CC3FCF092A64D300B2BF95 /* Entity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Entity.h; sourceTree = ""; }; + 81CC3FD3092A64D300B2BF95 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Debug.cpp; sourceTree = ""; }; + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Debug.h; sourceTree = ""; }; + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_local.cpp; sourceTree = ""; }; + 81CC3FD7092A64D300B2BF95 /* Game_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_local.h; sourceTree = ""; }; + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Log.cpp; sourceTree = ""; }; + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Log.h; sourceTree = ""; }; + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_network.cpp; sourceTree = ""; }; + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameEdit.cpp; sourceTree = ""; }; + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameEdit.h; sourceTree = ""; }; + 81CC3FDF092A64D300B2BF95 /* Class.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Class.cpp; sourceTree = ""; }; + 81CC3FE0092A64D300B2BF95 /* Class.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Class.h; sourceTree = ""; }; + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DebugGraph.cpp; sourceTree = ""; }; + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DebugGraph.h; sourceTree = ""; }; + 81CC3FE3092A64D300B2BF95 /* Event.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Event.cpp; sourceTree = ""; }; + 81CC3FE4092A64D300B2BF95 /* Event.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = ""; }; + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NoGameTypeInfo.h; sourceTree = ""; }; + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SaveGame.cpp; sourceTree = ""; }; + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SaveGame.h; sourceTree = ""; }; + 81CC3FE8092A64D300B2BF95 /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = ""; }; + 81CC3FE9092A64D300B2BF95 /* State.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = State.h; sourceTree = ""; }; + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCmds.cpp; sourceTree = ""; }; + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCmds.h; sourceTree = ""; }; + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCvar.cpp; sourceTree = ""; }; + 81CC3FED092A64D300B2BF95 /* SysCvar.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCvar.h; sourceTree = ""; }; + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Healing_Station.cpp; sourceTree = ""; }; + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Healing_Station.h; sourceTree = ""; }; + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Icon.cpp; sourceTree = ""; }; + 81CC3FF1092A64D300B2BF95 /* Icon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Icon.h; sourceTree = ""; }; + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IconManager.cpp; sourceTree = ""; }; + 81CC3FF3092A64D300B2BF95 /* IconManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IconManager.h; sourceTree = ""; }; + 81CC3FF4092A64D300B2BF95 /* IK.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IK.cpp; sourceTree = ""; }; + 81CC3FF5092A64D300B2BF95 /* IK.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IK.h; sourceTree = ""; }; + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Instance.cpp; sourceTree = ""; }; + 81CC3FF7092A64D300B2BF95 /* Instance.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Instance.h; sourceTree = ""; }; + 81CC3FF8092A64D300B2BF95 /* Item.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Item.cpp; sourceTree = ""; }; + 81CC3FF9092A64D300B2BF95 /* Item.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Item.h; sourceTree = ""; }; + 81CC3FFA092A64D300B2BF95 /* Light.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Light.cpp; sourceTree = ""; }; + 81CC3FFB092A64D300B2BF95 /* Light.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Light.h; sourceTree = ""; }; + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LipSync.cpp; sourceTree = ""; }; + 81CC3FFD092A64D300B2BF95 /* LipSync.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LipSync.h; sourceTree = ""; }; + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Misc.cpp; sourceTree = ""; }; + 81CC3FFF092A64D300B2BF95 /* Misc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Misc.h; sourceTree = ""; }; + 81CC4000092A64D300B2BF95 /* Moveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Moveable.cpp; sourceTree = ""; }; + 81CC4001092A64D300B2BF95 /* Moveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Moveable.h; sourceTree = ""; }; + 81CC4002092A64D300B2BF95 /* Mover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Mover.cpp; sourceTree = ""; }; + 81CC4003092A64D300B2BF95 /* Mover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mover.h; sourceTree = ""; }; + 81CC4005092A64D300B2BF95 /* CTF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CTF.cpp; sourceTree = ""; }; + 81CC4006092A64D300B2BF95 /* CTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CTF.h; sourceTree = ""; }; + 81CC4007092A64D300B2BF95 /* GameState.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameState.cpp; sourceTree = ""; }; + 81CC4008092A64D300B2BF95 /* GameState.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameState.h; sourceTree = ""; }; + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatEvent.cpp; sourceTree = ""; }; + 81CC400B092A64D300B2BF95 /* StatEvent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatEvent.h; sourceTree = ""; }; + 81CC400C092A64D300B2BF95 /* StatManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatManager.cpp; sourceTree = ""; }; + 81CC400D092A64D300B2BF95 /* StatManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatManager.h; sourceTree = ""; }; + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatWindow.cpp; sourceTree = ""; }; + 81CC400F092A64D300B2BF95 /* StatWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatWindow.h; sourceTree = ""; }; + 81CC4010092A64D300B2BF95 /* Tourney.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Tourney.cpp; sourceTree = ""; }; + 81CC4011092A64D300B2BF95 /* Tourney.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Tourney.h; sourceTree = ""; }; + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VoiceComms.cpp; sourceTree = ""; }; + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerGame.cpp; sourceTree = ""; }; + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerGame.h; sourceTree = ""; }; + 81CC4016092A64D300B2BF95 /* Clip.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Clip.cpp; sourceTree = ""; }; + 81CC4017092A64D300B2BF95 /* Clip.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Clip.h; sourceTree = ""; }; + 81CC4018092A64D300B2BF95 /* Force.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force.cpp; sourceTree = ""; }; + 81CC4019092A64D300B2BF95 /* Force.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force.h; sourceTree = ""; }; + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Constant.cpp; sourceTree = ""; }; + 81CC401B092A64D300B2BF95 /* Force_Constant.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Constant.h; sourceTree = ""; }; + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Drag.cpp; sourceTree = ""; }; + 81CC401D092A64D300B2BF95 /* Force_Drag.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Drag.h; sourceTree = ""; }; + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Field.cpp; sourceTree = ""; }; + 81CC401F092A64D300B2BF95 /* Force_Field.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Field.h; sourceTree = ""; }; + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Spring.cpp; sourceTree = ""; }; + 81CC4021092A64D300B2BF95 /* Force_Spring.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Spring.h; sourceTree = ""; }; + 81CC4022092A64D300B2BF95 /* Physics.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics.cpp; sourceTree = ""; }; + 81CC4023092A64D300B2BF95 /* Physics.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics.h; sourceTree = ""; }; + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Actor.cpp; sourceTree = ""; }; + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Actor.h; sourceTree = ""; }; + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_AF.cpp; sourceTree = ""; }; + 81CC4027092A64D300B2BF95 /* Physics_AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_AF.h; sourceTree = ""; }; + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Base.cpp; sourceTree = ""; }; + 81CC4029092A64D300B2BF95 /* Physics_Base.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Base.h; sourceTree = ""; }; + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Monster.cpp; sourceTree = ""; }; + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Monster.h; sourceTree = ""; }; + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Parametric.cpp; sourceTree = ""; }; + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Parametric.h; sourceTree = ""; }; + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Particle.cpp; sourceTree = ""; }; + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Particle.h; sourceTree = ""; }; + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Player.cpp; sourceTree = ""; }; + 81CC4031092A64D300B2BF95 /* Physics_Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Player.h; sourceTree = ""; }; + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_RigidBody.cpp; sourceTree = ""; }; + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_RigidBody.h; sourceTree = ""; }; + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Static.cpp; sourceTree = ""; }; + 81CC4035092A64D300B2BF95 /* Physics_Static.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Static.h; sourceTree = ""; }; + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_StaticMulti.cpp; sourceTree = ""; }; + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_StaticMulti.h; sourceTree = ""; }; + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_VehicleMonster.cpp; sourceTree = ""; }; + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_VehicleMonster.h; sourceTree = ""; }; + 81CC403A092A64D300B2BF95 /* Push.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Push.cpp; sourceTree = ""; }; + 81CC403B092A64D300B2BF95 /* Push.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Push.h; sourceTree = ""; }; + 81CC403C092A64D300B2BF95 /* Playback.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Playback.cpp; sourceTree = ""; }; + 81CC403D092A64D300B2BF95 /* Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player.cpp; sourceTree = ""; }; + 81CC403E092A64D300B2BF95 /* Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Player.h; sourceTree = ""; }; + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_Cheats.cpp; sourceTree = ""; }; + 81CC4040092A64D300B2BF95 /* Player_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_States.cpp; sourceTree = ""; }; + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PlayerView.cpp; sourceTree = ""; }; + 81CC4042092A64D300B2BF95 /* PlayerView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlayerView.h; sourceTree = ""; }; + 81CC4043092A64D300B2BF95 /* Projectile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Projectile.cpp; sourceTree = ""; }; + 81CC4044092A64D300B2BF95 /* Projectile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Projectile.h; sourceTree = ""; }; + 81CC4045092A64D300B2BF95 /* Pvs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pvs.cpp; sourceTree = ""; }; + 81CC4046092A64D300B2BF95 /* Pvs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pvs.h; sourceTree = ""; }; + 81CC404A092A64D300B2BF95 /* AI_public.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_public.h; sourceTree = ""; }; + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Compiler.cpp; sourceTree = ""; }; + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Compiler.h; sourceTree = ""; }; + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Interpreter.cpp; sourceTree = ""; }; + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Interpreter.h; sourceTree = ""; }; + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Program.cpp; sourceTree = ""; }; + 81CC4051092A64D300B2BF95 /* Script_Program.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Program.h; sourceTree = ""; }; + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Thread.cpp; sourceTree = ""; }; + 81CC4053092A64D300B2BF95 /* Script_Thread.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Thread.h; sourceTree = ""; }; + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptFuncUtility.cpp; sourceTree = ""; }; + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ScriptFuncUtility.h; sourceTree = ""; }; + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityCamera.cpp; sourceTree = ""; }; + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SecurityCamera.h; sourceTree = ""; }; + 81CC405A092A64D300B2BF95 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; + 81CC405B092A64D300B2BF95 /* Sound.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sound.h; sourceTree = ""; }; + 81CC405C092A64D300B2BF95 /* spawner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = spawner.cpp; sourceTree = ""; }; + 81CC405D092A64D300B2BF95 /* spawner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = spawner.h; sourceTree = ""; }; + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SplineMover.cpp; sourceTree = ""; }; + 81CC405F092A64D300B2BF95 /* SplineMover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SplineMover.h; sourceTree = ""; }; + 81CC4060092A64D300B2BF95 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = ""; }; + 81CC4061092A64D300B2BF95 /* Target.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + 81CC4062092A64D300B2BF95 /* TramGate.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TramGate.cpp; sourceTree = ""; }; + 81CC4063092A64D300B2BF95 /* TramGate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TramGate.h; sourceTree = ""; }; + 81CC4064092A64D300B2BF95 /* Trigger.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Trigger.cpp; sourceTree = ""; }; + 81CC4065092A64D300B2BF95 /* Trigger.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Trigger.h; sourceTree = ""; }; + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle.cpp; sourceTree = ""; }; + 81CC4068092A64D300B2BF95 /* Vehicle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vehicle.h; sourceTree = ""; }; + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_DropPod.cpp; sourceTree = ""; }; + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_Walker.cpp; sourceTree = ""; }; + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAnimated.cpp; sourceTree = ""; }; + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAnimated.h; sourceTree = ""; }; + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleController.cpp; sourceTree = ""; }; + 81CC406E092A64D300B2BF95 /* VehicleController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleController.h; sourceTree = ""; }; + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleDriver.cpp; sourceTree = ""; }; + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleDriver.h; sourceTree = ""; }; + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleMonster.cpp; sourceTree = ""; }; + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleMonster.h; sourceTree = ""; }; + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleParts.cpp; sourceTree = ""; }; + 81CC4074092A64D300B2BF95 /* VehicleParts.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleParts.h; sourceTree = ""; }; + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehiclePosition.cpp; sourceTree = ""; }; + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleRigid.cpp; sourceTree = ""; }; + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleRigid.h; sourceTree = ""; }; + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleSpline.cpp; sourceTree = ""; }; + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleSpline.h; sourceTree = ""; }; + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleStatic.cpp; sourceTree = ""; }; + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleStatic.h; sourceTree = ""; }; + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponBlaster.cpp; sourceTree = ""; }; + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponDarkMatterGun.cpp; sourceTree = ""; }; + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGauntlet.cpp; sourceTree = ""; }; + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGrenadeLauncher.cpp; sourceTree = ""; }; + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponHyperblaster.cpp; sourceTree = ""; }; + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponLightningGun.cpp; sourceTree = ""; }; + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponMachinegun.cpp; sourceTree = ""; }; + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNailgun.cpp; sourceTree = ""; }; + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRailgun.cpp; sourceTree = ""; }; + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRocketLauncher.cpp; sourceTree = ""; }; + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponShotgun.cpp; sourceTree = ""; }; + 81CC4088092A64D300B2BF95 /* Weapon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Weapon.cpp; sourceTree = ""; }; + 81CC4089092A64D300B2BF95 /* Weapon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Weapon.h; sourceTree = ""; }; + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WorldSpawn.cpp; sourceTree = ""; }; + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WorldSpawn.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 81114DF9093E117D00A5C91A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* game */ = { + isa = PBXGroup; + children = ( + 81CC3F72092A64D300B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + 816AD2DF0981819D00CAC29C /* Game.plist */, + 8114A19C090D686B0025E084 /* idlib.xcodeproj */, + ); + name = game; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 81114DFB093E117D00A5C91A /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 8114A19D090D686B0025E084 /* Products */ = { + isa = PBXGroup; + children = ( + 8114A1A2090D686B0025E084 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3F72092A64D300B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 81CC3F73092A64D300B2BF95 /* Actor.cpp */, + 81CC3F74092A64D300B2BF95 /* Actor.h */, + 81CC3F75092A64D300B2BF95 /* AF.cpp */, + 81CC3F76092A64D300B2BF95 /* AF.h */, + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */, + 81CC3F78092A64D300B2BF95 /* AFEntity.h */, + 81CC3F79092A64D300B2BF95 /* ai */, + 81CC3FB6092A64D300B2BF95 /* anim */, + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */, + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */, + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */, + 81CC3FC0092A64D300B2BF95 /* Camera.h */, + 81CC3FC1092A64D300B2BF95 /* client */, + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */, + 81CC3FCB092A64D300B2BF95 /* Effect.h */, + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */, + 81CC3FCF092A64D300B2BF95 /* Entity.h */, + 814E1D0D097C19DD009E5F86 /* FreeView.cpp */, + 814E1D0E097C19DD009E5F86 /* FreeView.h */, + 81CC3FD3092A64D300B2BF95 /* Game.h */, + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */, + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */, + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */, + 81CC3FD7092A64D300B2BF95 /* Game_local.h */, + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */, + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */, + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */, + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */, + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */, + 81CC3FDD092A64D300B2BF95 /* gamesys */, + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */, + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */, + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */, + 81CC3FF1092A64D300B2BF95 /* Icon.h */, + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */, + 81CC3FF3092A64D300B2BF95 /* IconManager.h */, + 81CC3FF4092A64D300B2BF95 /* IK.cpp */, + 81CC3FF5092A64D300B2BF95 /* IK.h */, + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */, + 81CC3FF7092A64D300B2BF95 /* Instance.h */, + 81CC3FF8092A64D300B2BF95 /* Item.cpp */, + 81CC3FF9092A64D300B2BF95 /* Item.h */, + 81CC3FFA092A64D300B2BF95 /* Light.cpp */, + 81CC3FFB092A64D300B2BF95 /* Light.h */, + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */, + 81CC3FFD092A64D300B2BF95 /* LipSync.h */, + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */, + 81CC3FFF092A64D300B2BF95 /* Misc.h */, + 81CC4000092A64D300B2BF95 /* Moveable.cpp */, + 81CC4001092A64D300B2BF95 /* Moveable.h */, + 81CC4002092A64D300B2BF95 /* Mover.cpp */, + 81CC4003092A64D300B2BF95 /* Mover.h */, + 81CC4004092A64D300B2BF95 /* mp */, + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */, + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */, + 81CC4015092A64D300B2BF95 /* physics */, + 81CC403C092A64D300B2BF95 /* Playback.cpp */, + 81CC403D092A64D300B2BF95 /* Player.cpp */, + 81CC403E092A64D300B2BF95 /* Player.h */, + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */, + 81CC4040092A64D300B2BF95 /* Player_States.cpp */, + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */, + 81CC4042092A64D300B2BF95 /* PlayerView.h */, + 81CC4043092A64D300B2BF95 /* Projectile.cpp */, + 81CC4044092A64D300B2BF95 /* Projectile.h */, + 81CC4045092A64D300B2BF95 /* Pvs.cpp */, + 81CC4046092A64D300B2BF95 /* Pvs.h */, + 81CC4047092A64D300B2BF95 /* Resource */, + 81CC4049092A64D300B2BF95 /* rvAI */, + 81CC404B092A64D300B2BF95 /* script */, + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */, + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */, + 81CC405A092A64D300B2BF95 /* Sound.cpp */, + 81CC405B092A64D300B2BF95 /* Sound.h */, + 81CC405C092A64D300B2BF95 /* spawner.cpp */, + 81CC405D092A64D300B2BF95 /* spawner.h */, + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */, + 81CC405F092A64D300B2BF95 /* SplineMover.h */, + 81CC4060092A64D300B2BF95 /* Target.cpp */, + 81CC4061092A64D300B2BF95 /* Target.h */, + 81CC4062092A64D300B2BF95 /* TramGate.cpp */, + 81CC4063092A64D300B2BF95 /* TramGate.h */, + 81CC4064092A64D300B2BF95 /* Trigger.cpp */, + 81CC4065092A64D300B2BF95 /* Trigger.h */, + 81CC4066092A64D300B2BF95 /* vehicle */, + 81CC407C092A64D300B2BF95 /* weapon */, + 81CC4088092A64D300B2BF95 /* Weapon.cpp */, + 81CC4089092A64D300B2BF95 /* Weapon.h */, + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */, + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */, + ); + name = Source; + path = ../../../game; + sourceTree = SOURCE_ROOT; + }; + 81CC3F79092A64D300B2BF95 /* ai */ = { + isa = PBXGroup; + children = ( + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */, + 81CC3F7B092A64D300B2BF95 /* AAS.h */, + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */, + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */, + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */, + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */, + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */, + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */, + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */, + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */, + 81CC3F84092A64D300B2BF95 /* AI.cpp */, + 81CC3F85092A64D300B2BF95 /* AI.h */, + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */, + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */, + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */, + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */, + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */, + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */, + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */, + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */, + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */, + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */, + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */, + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */, + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */, + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */, + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */, + 81CC3F96092A64D300B2BF95 /* AI_Util.h */, + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */, + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */, + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */, + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */, + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */, + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */, + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */, + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */, + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */, + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */, + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */, + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */, + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */, + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */, + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */, + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */, + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */, + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */, + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */, + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */, + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */, + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */, + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */, + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */, + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */, + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */, + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */, + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */, + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */, + ); + path = ai; + sourceTree = ""; + }; + 81CC3FB6092A64D300B2BF95 /* anim */ = { + isa = PBXGroup; + children = ( + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */, + 81CC3FB8092A64D300B2BF95 /* Anim.h */, + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */, + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */, + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */, + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */, + ); + path = anim; + sourceTree = ""; + }; + 81CC3FC1092A64D300B2BF95 /* client */ = { + isa = PBXGroup; + children = ( + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */, + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */, + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */, + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */, + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */, + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */, + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */, + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */, + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */, + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */, + ); + path = client; + sourceTree = ""; + }; + 81CC3FDD092A64D300B2BF95 /* gamesys */ = { + isa = PBXGroup; + children = ( + 81CC3FDF092A64D300B2BF95 /* Class.cpp */, + 81CC3FE0092A64D300B2BF95 /* Class.h */, + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */, + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */, + 81CC3FE3092A64D300B2BF95 /* Event.cpp */, + 81CC3FE4092A64D300B2BF95 /* Event.h */, + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */, + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */, + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */, + 81CC3FE8092A64D300B2BF95 /* State.cpp */, + 81CC3FE9092A64D300B2BF95 /* State.h */, + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */, + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */, + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */, + 81CC3FED092A64D300B2BF95 /* SysCvar.h */, + ); + path = gamesys; + sourceTree = ""; + }; + 81CC4004092A64D300B2BF95 /* mp */ = { + isa = PBXGroup; + children = ( + 253A77AD0A67159100A3CFB3 /* Buying.cpp */, + 253A77AE0A67159100A3CFB3 /* Buying.h */, + 81CC4005092A64D300B2BF95 /* CTF.cpp */, + 81CC4006092A64D300B2BF95 /* CTF.h */, + 81CC4007092A64D300B2BF95 /* GameState.cpp */, + 81CC4008092A64D300B2BF95 /* GameState.h */, + 81CC4009092A64D300B2BF95 /* stats */, + 81CC4010092A64D300B2BF95 /* Tourney.cpp */, + 81CC4011092A64D300B2BF95 /* Tourney.h */, + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */, + ); + path = mp; + sourceTree = ""; + }; + 81CC4009092A64D300B2BF95 /* stats */ = { + isa = PBXGroup; + children = ( + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */, + 81CC400B092A64D300B2BF95 /* StatEvent.h */, + 81CC400C092A64D300B2BF95 /* StatManager.cpp */, + 81CC400D092A64D300B2BF95 /* StatManager.h */, + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */, + 81CC400F092A64D300B2BF95 /* StatWindow.h */, + ); + path = stats; + sourceTree = ""; + }; + 81CC4015092A64D300B2BF95 /* physics */ = { + isa = PBXGroup; + children = ( + 81CC4016092A64D300B2BF95 /* Clip.cpp */, + 81CC4017092A64D300B2BF95 /* Clip.h */, + 81CC4018092A64D300B2BF95 /* Force.cpp */, + 81CC4019092A64D300B2BF95 /* Force.h */, + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */, + 81CC401B092A64D300B2BF95 /* Force_Constant.h */, + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */, + 81CC401D092A64D300B2BF95 /* Force_Drag.h */, + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */, + 81CC401F092A64D300B2BF95 /* Force_Field.h */, + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */, + 81CC4021092A64D300B2BF95 /* Force_Spring.h */, + 81CC4022092A64D300B2BF95 /* Physics.cpp */, + 81CC4023092A64D300B2BF95 /* Physics.h */, + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */, + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */, + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */, + 81CC4027092A64D300B2BF95 /* Physics_AF.h */, + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */, + 81CC4029092A64D300B2BF95 /* Physics_Base.h */, + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */, + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */, + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */, + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */, + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */, + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */, + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */, + 81CC4031092A64D300B2BF95 /* Physics_Player.h */, + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */, + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */, + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */, + 81CC4035092A64D300B2BF95 /* Physics_Static.h */, + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */, + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */, + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */, + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */, + 81CC403A092A64D300B2BF95 /* Push.cpp */, + 81CC403B092A64D300B2BF95 /* Push.h */, + ); + path = physics; + sourceTree = ""; + }; + 81CC4047092A64D300B2BF95 /* Resource */ = { + isa = PBXGroup; + children = ( + ); + path = Resource; + sourceTree = ""; + }; + 81CC4049092A64D300B2BF95 /* rvAI */ = { + isa = PBXGroup; + children = ( + 81CC404A092A64D300B2BF95 /* AI_public.h */, + ); + path = rvAI; + sourceTree = ""; + }; + 81CC404B092A64D300B2BF95 /* script */ = { + isa = PBXGroup; + children = ( + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */, + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */, + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */, + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */, + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */, + 81CC4051092A64D300B2BF95 /* Script_Program.h */, + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */, + 81CC4053092A64D300B2BF95 /* Script_Thread.h */, + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */, + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */, + ); + path = script; + sourceTree = ""; + }; + 81CC4066092A64D300B2BF95 /* vehicle */ = { + isa = PBXGroup; + children = ( + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */, + 81CC4068092A64D300B2BF95 /* Vehicle.h */, + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */, + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */, + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */, + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */, + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */, + 81CC406E092A64D300B2BF95 /* VehicleController.h */, + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */, + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */, + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */, + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */, + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */, + 81CC4074092A64D300B2BF95 /* VehicleParts.h */, + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */, + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */, + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */, + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */, + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */, + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */, + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */, + ); + path = vehicle; + sourceTree = ""; + }; + 81CC407C092A64D300B2BF95 /* weapon */ = { + isa = PBXGroup; + children = ( + 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */, + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */, + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */, + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */, + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */, + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */, + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */, + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */, + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */, + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */, + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */, + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */, + ); + path = weapon; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 81114DFA093E117D00A5C91A /* game bundle */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */; + buildPhases = ( + 81114DF7093E117D00A5C91A /* Resources */, + 81114DF8093E117D00A5C91A /* Sources */, + 81114DF9093E117D00A5C91A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 81114FE3093E189800A5C91A /* PBXTargetDependency */, + ); + name = "game bundle"; + productName = "game bundle"; + productReference = 81114DFB093E117D00A5C91A /* game.so.bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "game" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* game */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 8114A19D090D686B0025E084 /* Products */; + ProjectRef = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + }, + ); + targets = ( + 81114DFA093E117D00A5C91A /* game bundle */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 8114A1A2090D686B0025E084 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 81114DF7093E117D00A5C91A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 816AD2E00981819D00CAC29C /* Game.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 81114DF8093E117D00A5C91A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */, + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */, + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */, + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */, + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */, + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */, + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */, + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */, + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */, + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */, + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */, + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */, + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */, + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */, + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */, + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */, + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */, + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */, + 81114FF8093E194300A5C91A /* AF.cpp in Sources */, + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */, + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */, + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */, + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */, + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */, + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */, + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */, + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */, + 81115001093E194300A5C91A /* Effect.cpp in Sources */, + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */, + 81115003093E194300A5C91A /* Player_States.cpp in Sources */, + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */, + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */, + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */, + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */, + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */, + 81115009093E194300A5C91A /* State.cpp in Sources */, + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */, + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */, + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */, + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */, + 8111500E093E194300A5C91A /* Mover.cpp in Sources */, + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */, + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */, + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */, + 81115012093E194300A5C91A /* Push.cpp in Sources */, + 81115013093E194300A5C91A /* IconManager.cpp in Sources */, + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */, + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */, + 81115016093E194300A5C91A /* Moveable.cpp in Sources */, + 81115017093E194300A5C91A /* Clip.cpp in Sources */, + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */, + 81115019093E194300A5C91A /* Target.cpp in Sources */, + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */, + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */, + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */, + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */, + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */, + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */, + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */, + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */, + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */, + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */, + 81115024093E194300A5C91A /* Playback.cpp in Sources */, + 81115025093E194300A5C91A /* Force.cpp in Sources */, + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */, + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */, + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */, + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */, + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */, + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */, + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */, + 8111502D093E194300A5C91A /* Player.cpp in Sources */, + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */, + 8111502F093E194300A5C91A /* IK.cpp in Sources */, + 81115030093E194300A5C91A /* Projectile.cpp in Sources */, + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */, + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */, + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */, + 81115034093E194300A5C91A /* Pvs.cpp in Sources */, + 81115035093E194300A5C91A /* Light.cpp in Sources */, + 81115036093E194300A5C91A /* Entity.cpp in Sources */, + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */, + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */, + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */, + 8111503A093E194300A5C91A /* AAS.cpp in Sources */, + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */, + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */, + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */, + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */, + 81115040093E194300A5C91A /* AI.cpp in Sources */, + 81115041093E194300A5C91A /* Tourney.cpp in Sources */, + 81115042093E194300A5C91A /* TramGate.cpp in Sources */, + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */, + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */, + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */, + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */, + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */, + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */, + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */, + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */, + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */, + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */, + 8111504D093E194300A5C91A /* Event.cpp in Sources */, + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */, + 8111504F093E194300A5C91A /* CTF.cpp in Sources */, + 81115050093E194300A5C91A /* GameState.cpp in Sources */, + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */, + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */, + 81115053093E194300A5C91A /* Physics.cpp in Sources */, + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */, + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */, + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */, + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */, + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */, + 81115059093E194300A5C91A /* Class.cpp in Sources */, + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */, + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */, + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */, + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */, + 8111505E093E194300A5C91A /* Misc.cpp in Sources */, + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */, + 81115060093E194300A5C91A /* Icon.cpp in Sources */, + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */, + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */, + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */, + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */, + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */, + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */, + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */, + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */, + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */, + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */, + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */, + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */, + 8111506D093E194300A5C91A /* Anim.cpp in Sources */, + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */, + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */, + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */, + 81115071093E194300A5C91A /* spawner.cpp in Sources */, + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */, + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */, + 81115074093E194300A5C91A /* LipSync.cpp in Sources */, + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */, + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */, + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */, + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */, + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */, + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */, + 8111507B093E194300A5C91A /* Actor.cpp in Sources */, + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */, + 8111507D093E194300A5C91A /* Item.cpp in Sources */, + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */, + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */, + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */, + 81115081093E194300A5C91A /* Instance.cpp in Sources */, + 814E1D11097C19DD009E5F86 /* FreeView.cpp in Sources */, + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */, + 253A77B10A67159100A3CFB3 /* Buying.cpp in Sources */, + 253A77B40A6715A300A3CFB3 /* WeaponNapalmGun.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 81114FE3093E189800A5C91A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = idlib_pic; + targetProxy = 81114FE2093E189800A5C91A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 81114DFE093E117E00A5C91A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_CFLAGS = "-fvisibility=hidden"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81114DFF093E117E00A5C91A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149F3C090D5FDC0025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _DEBUG, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149F3D090D5FDC0025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _FINAL, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81114DFE093E117E00A5C91A /* Debug */, + 81114DFF093E117E00A5C91A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "game" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149F3C090D5FDC0025E084 /* Debug */, + 81149F3D090D5FDC0025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/source/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj b/source/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8ccd78a --- /dev/null +++ b/source/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj @@ -0,0 +1,905 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 81CC3D6F092A630E00B2BF95 /* MultifieldSort.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */; }; + 81CC3D70092A630E00B2BF95 /* AutoPtr.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */; }; + 81CC3D71092A630E00B2BF95 /* Base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CCC092A630E00B2BF95 /* Base64.cpp */; }; + 81CC3D72092A630E00B2BF95 /* Base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCD092A630E00B2BF95 /* Base64.h */; }; + 81CC3D73092A630E00B2BF95 /* BitMsg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */; }; + 81CC3D74092A630E00B2BF95 /* BitMsg.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCF092A630E00B2BF95 /* BitMsg.h */; }; + 81CC3D76092A630E00B2BF95 /* Bounds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */; }; + 81CC3D77092A630E00B2BF95 /* Bounds.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD3092A630E00B2BF95 /* Bounds.h */; }; + 81CC3D78092A630E00B2BF95 /* Box.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD4092A630E00B2BF95 /* Box.cpp */; }; + 81CC3D79092A630E00B2BF95 /* Box.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD5092A630E00B2BF95 /* Box.h */; }; + 81CC3D7A092A630E00B2BF95 /* Frustum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */; }; + 81CC3D7B092A630E00B2BF95 /* Frustum.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD7092A630E00B2BF95 /* Frustum.h */; }; + 81CC3D7C092A630E00B2BF95 /* Sphere.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */; }; + 81CC3D7D092A630E00B2BF95 /* Sphere.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD9092A630E00B2BF95 /* Sphere.h */; }; + 81CC3D7E092A630E00B2BF95 /* CmdArgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */; }; + 81CC3D7F092A630E00B2BF95 /* CmdArgs.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */; }; + 81CC3D80092A630E00B2BF95 /* BinSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDD092A630E00B2BF95 /* BinSearch.h */; }; + 81CC3D81092A630E00B2BF95 /* BTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDE092A630E00B2BF95 /* BTree.h */; }; + 81CC3D82092A630E00B2BF95 /* HashIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */; }; + 81CC3D83092A630E00B2BF95 /* HashIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE0092A630E00B2BF95 /* HashIndex.h */; }; + 81CC3D84092A630E00B2BF95 /* HashTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE1092A630E00B2BF95 /* HashTable.h */; }; + 81CC3D85092A630E00B2BF95 /* Hierarchy.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */; }; + 81CC3D86092A630E00B2BF95 /* LinkList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE3092A630E00B2BF95 /* LinkList.h */; }; + 81CC3D87092A630E00B2BF95 /* List.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE4092A630E00B2BF95 /* List.h */; }; + 81CC3D88092A630E00B2BF95 /* ListGame.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE5092A630E00B2BF95 /* ListGame.h */; }; + 81CC3D89092A630E00B2BF95 /* Pair.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE6092A630E00B2BF95 /* Pair.h */; }; + 81CC3D8A092A630E00B2BF95 /* PlaneSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */; }; + 81CC3D8B092A630E00B2BF95 /* Queue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE8092A630E00B2BF95 /* Queue.h */; }; + 81CC3D8C092A630E00B2BF95 /* rvBlockPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */; }; + 81CC3D8D092A630E00B2BF95 /* Stack.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEA092A630E00B2BF95 /* Stack.h */; }; + 81CC3D8E092A630E00B2BF95 /* StaticList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEB092A630E00B2BF95 /* StaticList.h */; }; + 81CC3D8F092A630E00B2BF95 /* StrList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEC092A630E00B2BF95 /* StrList.h */; }; + 81CC3D90092A630E00B2BF95 /* StrPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CED092A630E00B2BF95 /* StrPool.h */; }; + 81CC3D91092A630E00B2BF95 /* VectorSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEE092A630E00B2BF95 /* VectorSet.h */; }; + 81CC3D92092A630E00B2BF95 /* Dict.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CEF092A630E00B2BF95 /* Dict.cpp */; }; + 81CC3D93092A630E00B2BF95 /* Dict.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF0092A630E00B2BF95 /* Dict.h */; }; + 81CC3D95092A630E00B2BF95 /* DrawVert.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF3092A630E00B2BF95 /* DrawVert.h */; }; + 81CC3D96092A630E00B2BF95 /* JointTransform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */; }; + 81CC3D97092A630E00B2BF95 /* JointTransform.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF5092A630E00B2BF95 /* JointTransform.h */; }; + 81CC3D98092A630E00B2BF95 /* rvVertex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF6092A630E00B2BF95 /* rvVertex.h */; }; + 81CC3D99092A630E00B2BF95 /* Surface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF7092A630E00B2BF95 /* Surface.cpp */; }; + 81CC3D9A092A630E00B2BF95 /* Surface.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF8092A630E00B2BF95 /* Surface.h */; }; + 81CC3D9B092A630E00B2BF95 /* Surface_Patch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */; }; + 81CC3D9C092A630E00B2BF95 /* Surface_Patch.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */; }; + 81CC3D9D092A630E00B2BF95 /* Surface_Polytope.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */; }; + 81CC3D9E092A630E00B2BF95 /* Surface_Polytope.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */; }; + 81CC3D9F092A630E00B2BF95 /* Surface_SweptSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */; }; + 81CC3DA0092A630E00B2BF95 /* Surface_SweptSpline.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */; }; + 81CC3DA1092A630E00B2BF95 /* TraceModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */; }; + 81CC3DA2092A630E00B2BF95 /* TraceModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D00092A630E00B2BF95 /* TraceModel.h */; }; + 81CC3DA3092A630E00B2BF95 /* Winding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D01092A630E00B2BF95 /* Winding.cpp */; }; + 81CC3DA4092A630E00B2BF95 /* Winding.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D02092A630E00B2BF95 /* Winding.h */; }; + 81CC3DA5092A630E00B2BF95 /* Winding2D.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */; }; + 81CC3DA6092A630E00B2BF95 /* Winding2D.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D04092A630E00B2BF95 /* Winding2D.h */; }; + 81CC3DA7092A630E00B2BF95 /* CRC16.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D06092A630E00B2BF95 /* CRC16.cpp */; }; + 81CC3DA8092A630E00B2BF95 /* CRC16.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D07092A630E00B2BF95 /* CRC16.h */; }; + 81CC3DA9092A630E00B2BF95 /* CRC32.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D08092A630E00B2BF95 /* CRC32.cpp */; }; + 81CC3DAA092A630E00B2BF95 /* CRC32.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D09092A630E00B2BF95 /* CRC32.h */; }; + 81CC3DAB092A630E00B2BF95 /* CRC8.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */; }; + 81CC3DAC092A630E00B2BF95 /* CRC8.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0B092A630E00B2BF95 /* CRC8.h */; }; + 81CC3DAD092A630E00B2BF95 /* Honeyman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */; }; + 81CC3DAE092A630E00B2BF95 /* Honeyman.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0D092A630E00B2BF95 /* Honeyman.h */; }; + 81CC3DAF092A630E00B2BF95 /* MD4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0E092A630E00B2BF95 /* MD4.cpp */; }; + 81CC3DB0092A630E00B2BF95 /* MD4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0F092A630E00B2BF95 /* MD4.h */; }; + 81CC3DB1092A630E00B2BF95 /* MD5.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D10092A630E00B2BF95 /* MD5.cpp */; }; + 81CC3DB2092A630E00B2BF95 /* MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D11092A630E00B2BF95 /* MD5.h */; }; + 81CC3DB3092A630E00B2BF95 /* Heap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D12092A630E00B2BF95 /* Heap.cpp */; }; + 81CC3DB4092A630E00B2BF95 /* Heap.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D13092A630E00B2BF95 /* Heap.h */; }; + 81CC3DB5092A630E00B2BF95 /* LangDict.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D14092A630E00B2BF95 /* LangDict.cpp */; }; + 81CC3DB6092A630E00B2BF95 /* LangDict.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D15092A630E00B2BF95 /* LangDict.h */; }; + 81CC3DB7092A630E00B2BF95 /* Lexer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D16092A630E00B2BF95 /* Lexer.cpp */; }; + 81CC3DB8092A630E00B2BF95 /* Lexer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D17092A630E00B2BF95 /* Lexer.h */; }; + 81CC3DB9092A630E00B2BF95 /* LexerFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */; }; + 81CC3DBA092A630E00B2BF95 /* LexerFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D19092A630E00B2BF95 /* LexerFactory.h */; }; + 81CC3DBB092A630E00B2BF95 /* Lib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1A092A630E00B2BF95 /* Lib.cpp */; }; + 81CC3DBC092A630E00B2BF95 /* Lib.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D1B092A630E00B2BF95 /* Lib.h */; }; + 81CC3DBD092A630E00B2BF95 /* mapfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */; }; + 81CC3DBE092A630E00B2BF95 /* MapFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D1D092A630E00B2BF95 /* MapFile.h */; }; + 81CC3DBF092A630E00B2BF95 /* Angles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1F092A630E00B2BF95 /* Angles.cpp */; }; + 81CC3DC0092A630E00B2BF95 /* Angles.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D20092A630E00B2BF95 /* Angles.h */; }; + 81CC3DC1092A630E00B2BF95 /* Complex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D21092A630E00B2BF95 /* Complex.cpp */; }; + 81CC3DC2092A630E00B2BF95 /* Complex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D22092A630E00B2BF95 /* Complex.h */; }; + 81CC3DC3092A630E00B2BF95 /* Curve.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D23092A630E00B2BF95 /* Curve.h */; }; + 81CC3DC4092A630E00B2BF95 /* Extrapolate.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D24092A630E00B2BF95 /* Extrapolate.h */; }; + 81CC3DC5092A630E00B2BF95 /* FFT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D25092A630E00B2BF95 /* FFT.cpp */; }; + 81CC3DC6092A630E00B2BF95 /* FFT.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D26092A630E00B2BF95 /* FFT.h */; }; + 81CC3DC7092A630E00B2BF95 /* Interpolate.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D27092A630E00B2BF95 /* Interpolate.h */; }; + 81CC3DC8092A630E00B2BF95 /* Lcp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D28092A630E00B2BF95 /* Lcp.cpp */; }; + 81CC3DC9092A630E00B2BF95 /* Lcp.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D29092A630E00B2BF95 /* Lcp.h */; }; + 81CC3DCB092A630E00B2BF95 /* Mat3x4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */; }; + 81CC3DCC092A630E00B2BF95 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D2C092A630E00B2BF95 /* Math.cpp */; }; + 81CC3DCE092A630E00B2BF95 /* Matrix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */; }; + 81CC3DCF092A630E00B2BF95 /* Matrix.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D2F092A630E00B2BF95 /* Matrix.h */; }; + 81CC3DD0092A630E00B2BF95 /* Ode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D30092A630E00B2BF95 /* Ode.cpp */; }; + 81CC3DD1092A630E00B2BF95 /* Ode.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D31092A630E00B2BF95 /* Ode.h */; }; + 81CC3DD2092A630E00B2BF95 /* Plane.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D32092A630E00B2BF95 /* Plane.cpp */; }; + 81CC3DD3092A630E00B2BF95 /* Plane.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D33092A630E00B2BF95 /* Plane.h */; }; + 81CC3DD4092A630E00B2BF95 /* Pluecker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */; }; + 81CC3DD5092A630E00B2BF95 /* Pluecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D35092A630E00B2BF95 /* Pluecker.h */; }; + 81CC3DD6092A630E00B2BF95 /* Polynomial.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */; }; + 81CC3DD7092A630E00B2BF95 /* Polynomial.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D37092A630E00B2BF95 /* Polynomial.h */; }; + 81CC3DD8092A630E00B2BF95 /* Quat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D38092A630E00B2BF95 /* Quat.cpp */; }; + 81CC3DD9092A630E00B2BF95 /* Quat.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D39092A630E00B2BF95 /* Quat.h */; }; + 81CC3DDA092A630E00B2BF95 /* Radians.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3A092A630E00B2BF95 /* Radians.cpp */; }; + 81CC3DDB092A630E00B2BF95 /* Radians.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3B092A630E00B2BF95 /* Radians.h */; }; + 81CC3DDC092A630E00B2BF95 /* Random.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3C092A630E00B2BF95 /* Random.h */; }; + 81CC3DDD092A630E00B2BF95 /* Rotation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */; }; + 81CC3DDE092A630E00B2BF95 /* Rotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3E092A630E00B2BF95 /* Rotation.h */; }; + 81CC3DDF092A630E00B2BF95 /* Simd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3F092A630E00B2BF95 /* Simd.cpp */; }; + 81CC3DE0092A630E00B2BF95 /* Simd.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D40092A630E00B2BF95 /* Simd.h */; }; + 81CC3DE1092A630E00B2BF95 /* Simd_3DNow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */; }; + 81CC3DE2092A630E00B2BF95 /* Simd_3DNow.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */; }; + 81CC3DE3092A630E00B2BF95 /* Simd_AltiVec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */; }; + 81CC3DE4092A630E00B2BF95 /* Simd_AltiVec.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */; }; + 81CC3DE5092A630E00B2BF95 /* Simd_generic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */; }; + 81CC3DE6092A630E00B2BF95 /* Simd_generic.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D46092A630E00B2BF95 /* Simd_generic.h */; }; + 81CC3DE7092A630E00B2BF95 /* Simd_InstructionMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */; }; + 81CC3DE8092A630E00B2BF95 /* Simd_MMX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */; }; + 81CC3DE9092A630E00B2BF95 /* Simd_MMX.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */; }; + 81CC3DEA092A630E00B2BF95 /* Simd_SSE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */; }; + 81CC3DEB092A630E00B2BF95 /* Simd_SSE.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */; }; + 81CC3DEC092A630E00B2BF95 /* Simd_SSE2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */; }; + 81CC3DED092A630E00B2BF95 /* Simd_SSE2.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */; }; + 81CC3DEE092A630E00B2BF95 /* Simd_SSE3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */; }; + 81CC3DEF092A630E00B2BF95 /* Simd_SSE3.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */; }; + 81CC3DF2092A630E00B2BF95 /* Vector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D52092A630E00B2BF95 /* Vector.cpp */; }; + 81CC3DF3092A630E00B2BF95 /* Vector.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D53092A630E00B2BF95 /* Vector.h */; }; + 81CC3DF4092A630E00B2BF95 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D54092A630E00B2BF95 /* Parser.cpp */; }; + 81CC3DF5092A630E00B2BF95 /* Parser.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D55092A630E00B2BF95 /* Parser.h */; }; + 81CC3DF6092A630E00B2BF95 /* precompiled.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D56092A630E00B2BF95 /* precompiled.h */; }; + 81CC3DF7092A630E00B2BF95 /* rvHeap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */; }; + 81CC3DF8092A630E00B2BF95 /* rvHeap.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D58092A630E00B2BF95 /* rvHeap.h */; }; + 81CC3DF9092A630E00B2BF95 /* rvHeapArena.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */; }; + 81CC3DFA092A630E00B2BF95 /* rvHeapArena.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */; }; + 81CC3DFB092A630E00B2BF95 /* rvMemSys.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */; }; + 81CC3DFC092A630E00B2BF95 /* rvMemSys.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */; }; + 81CC3DFD092A630E00B2BF95 /* Str.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5D092A630E00B2BF95 /* Str.cpp */; }; + 81CC3DFE092A630E00B2BF95 /* Str.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5E092A630E00B2BF95 /* Str.h */; }; + 81CC3DFF092A630E00B2BF95 /* TextCompiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */; }; + 81CC3E00092A630E00B2BF95 /* TextCompiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D60092A630E00B2BF95 /* TextCompiler.h */; }; + 81CC3E03092A630E00B2BF95 /* AutoCrit.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D64092A630E00B2BF95 /* AutoCrit.h */; }; + 81CC3E08092A630E00B2BF95 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D69092A630E00B2BF95 /* Timer.cpp */; }; + 81CC3E09092A630E00B2BF95 /* Timer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6A092A630E00B2BF95 /* Timer.h */; }; + 81CC3E0A092A630E00B2BF95 /* TimingCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */; }; + 81CC3E0B092A630E00B2BF95 /* TimingCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */; }; + 81CC3E0C092A630E00B2BF95 /* Token.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D6D092A630E00B2BF95 /* Token.cpp */; }; + 81CC3E0D092A630E00B2BF95 /* Token.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6E092A630E00B2BF95 /* Token.h */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultifieldSort.h; sourceTree = ""; }; + 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AutoPtr.h; sourceTree = ""; }; + 81CC3CCC092A630E00B2BF95 /* Base64.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Base64.cpp; sourceTree = ""; }; + 81CC3CCD092A630E00B2BF95 /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Base64.h; sourceTree = ""; }; + 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BitMsg.cpp; sourceTree = ""; }; + 81CC3CCF092A630E00B2BF95 /* BitMsg.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BitMsg.h; sourceTree = ""; }; + 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Bounds.cpp; sourceTree = ""; }; + 81CC3CD3092A630E00B2BF95 /* Bounds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Bounds.h; sourceTree = ""; }; + 81CC3CD4092A630E00B2BF95 /* Box.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Box.cpp; sourceTree = ""; }; + 81CC3CD5092A630E00B2BF95 /* Box.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Box.h; sourceTree = ""; }; + 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Frustum.cpp; sourceTree = ""; }; + 81CC3CD7092A630E00B2BF95 /* Frustum.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Frustum.h; sourceTree = ""; }; + 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sphere.cpp; sourceTree = ""; }; + 81CC3CD9092A630E00B2BF95 /* Sphere.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sphere.h; sourceTree = ""; }; + 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CmdArgs.cpp; sourceTree = ""; }; + 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CmdArgs.h; sourceTree = ""; }; + 81CC3CDD092A630E00B2BF95 /* BinSearch.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BinSearch.h; sourceTree = ""; }; + 81CC3CDE092A630E00B2BF95 /* BTree.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BTree.h; sourceTree = ""; }; + 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = HashIndex.cpp; sourceTree = ""; }; + 81CC3CE0092A630E00B2BF95 /* HashIndex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HashIndex.h; sourceTree = ""; }; + 81CC3CE1092A630E00B2BF95 /* HashTable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HashTable.h; sourceTree = ""; }; + 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Hierarchy.h; sourceTree = ""; }; + 81CC3CE3092A630E00B2BF95 /* LinkList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LinkList.h; sourceTree = ""; }; + 81CC3CE4092A630E00B2BF95 /* List.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = List.h; sourceTree = ""; }; + 81CC3CE5092A630E00B2BF95 /* ListGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ListGame.h; sourceTree = ""; }; + 81CC3CE6092A630E00B2BF95 /* Pair.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pair.h; sourceTree = ""; }; + 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlaneSet.h; sourceTree = ""; }; + 81CC3CE8092A630E00B2BF95 /* Queue.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Queue.h; sourceTree = ""; }; + 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvBlockPool.h; sourceTree = ""; }; + 81CC3CEA092A630E00B2BF95 /* Stack.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Stack.h; sourceTree = ""; }; + 81CC3CEB092A630E00B2BF95 /* StaticList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StaticList.h; sourceTree = ""; }; + 81CC3CEC092A630E00B2BF95 /* StrList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StrList.h; sourceTree = ""; }; + 81CC3CED092A630E00B2BF95 /* StrPool.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StrPool.h; sourceTree = ""; }; + 81CC3CEE092A630E00B2BF95 /* VectorSet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VectorSet.h; sourceTree = ""; }; + 81CC3CEF092A630E00B2BF95 /* Dict.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Dict.cpp; sourceTree = ""; }; + 81CC3CF0092A630E00B2BF95 /* Dict.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Dict.h; sourceTree = ""; }; + 81CC3CF3092A630E00B2BF95 /* DrawVert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DrawVert.h; sourceTree = ""; }; + 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = JointTransform.cpp; sourceTree = ""; }; + 81CC3CF5092A630E00B2BF95 /* JointTransform.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = JointTransform.h; sourceTree = ""; }; + 81CC3CF6092A630E00B2BF95 /* rvVertex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvVertex.h; sourceTree = ""; }; + 81CC3CF7092A630E00B2BF95 /* Surface.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface.cpp; sourceTree = ""; }; + 81CC3CF8092A630E00B2BF95 /* Surface.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface.h; sourceTree = ""; }; + 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_Patch.cpp; sourceTree = ""; }; + 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_Patch.h; sourceTree = ""; }; + 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_Polytope.cpp; sourceTree = ""; }; + 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_Polytope.h; sourceTree = ""; }; + 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_SweptSpline.cpp; sourceTree = ""; }; + 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_SweptSpline.h; sourceTree = ""; }; + 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TraceModel.cpp; sourceTree = ""; }; + 81CC3D00092A630E00B2BF95 /* TraceModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TraceModel.h; sourceTree = ""; }; + 81CC3D01092A630E00B2BF95 /* Winding.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Winding.cpp; sourceTree = ""; }; + 81CC3D02092A630E00B2BF95 /* Winding.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Winding.h; sourceTree = ""; }; + 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Winding2D.cpp; sourceTree = ""; }; + 81CC3D04092A630E00B2BF95 /* Winding2D.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Winding2D.h; sourceTree = ""; }; + 81CC3D06092A630E00B2BF95 /* CRC16.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC16.cpp; sourceTree = ""; }; + 81CC3D07092A630E00B2BF95 /* CRC16.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC16.h; sourceTree = ""; }; + 81CC3D08092A630E00B2BF95 /* CRC32.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC32.cpp; sourceTree = ""; }; + 81CC3D09092A630E00B2BF95 /* CRC32.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC32.h; sourceTree = ""; }; + 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC8.cpp; sourceTree = ""; }; + 81CC3D0B092A630E00B2BF95 /* CRC8.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC8.h; sourceTree = ""; }; + 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Honeyman.cpp; sourceTree = ""; }; + 81CC3D0D092A630E00B2BF95 /* Honeyman.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Honeyman.h; sourceTree = ""; }; + 81CC3D0E092A630E00B2BF95 /* MD4.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MD4.cpp; sourceTree = ""; }; + 81CC3D0F092A630E00B2BF95 /* MD4.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MD4.h; sourceTree = ""; }; + 81CC3D10092A630E00B2BF95 /* MD5.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MD5.cpp; sourceTree = ""; }; + 81CC3D11092A630E00B2BF95 /* MD5.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MD5.h; sourceTree = ""; }; + 81CC3D12092A630E00B2BF95 /* Heap.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Heap.cpp; sourceTree = ""; }; + 81CC3D13092A630E00B2BF95 /* Heap.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Heap.h; sourceTree = ""; }; + 81CC3D14092A630E00B2BF95 /* LangDict.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LangDict.cpp; sourceTree = ""; }; + 81CC3D15092A630E00B2BF95 /* LangDict.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LangDict.h; sourceTree = ""; }; + 81CC3D16092A630E00B2BF95 /* Lexer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lexer.cpp; sourceTree = ""; }; + 81CC3D17092A630E00B2BF95 /* Lexer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lexer.h; sourceTree = ""; }; + 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LexerFactory.cpp; sourceTree = ""; }; + 81CC3D19092A630E00B2BF95 /* LexerFactory.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LexerFactory.h; sourceTree = ""; }; + 81CC3D1A092A630E00B2BF95 /* Lib.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lib.cpp; sourceTree = ""; }; + 81CC3D1B092A630E00B2BF95 /* Lib.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lib.h; sourceTree = ""; }; + 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = mapfile.cpp; sourceTree = ""; }; + 81CC3D1D092A630E00B2BF95 /* MapFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MapFile.h; sourceTree = ""; }; + 81CC3D1F092A630E00B2BF95 /* Angles.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Angles.cpp; sourceTree = ""; }; + 81CC3D20092A630E00B2BF95 /* Angles.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Angles.h; sourceTree = ""; }; + 81CC3D21092A630E00B2BF95 /* Complex.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Complex.cpp; sourceTree = ""; }; + 81CC3D22092A630E00B2BF95 /* Complex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Complex.h; sourceTree = ""; }; + 81CC3D23092A630E00B2BF95 /* Curve.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Curve.h; sourceTree = ""; }; + 81CC3D24092A630E00B2BF95 /* Extrapolate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Extrapolate.h; sourceTree = ""; }; + 81CC3D25092A630E00B2BF95 /* FFT.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FFT.cpp; sourceTree = ""; }; + 81CC3D26092A630E00B2BF95 /* FFT.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FFT.h; sourceTree = ""; }; + 81CC3D27092A630E00B2BF95 /* Interpolate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Interpolate.h; sourceTree = ""; }; + 81CC3D28092A630E00B2BF95 /* Lcp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lcp.cpp; sourceTree = ""; }; + 81CC3D29092A630E00B2BF95 /* Lcp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lcp.h; sourceTree = ""; }; + 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mat3x4.h; sourceTree = ""; }; + 81CC3D2C092A630E00B2BF95 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Matrix.cpp; sourceTree = ""; }; + 81CC3D2F092A630E00B2BF95 /* Matrix.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Matrix.h; sourceTree = ""; }; + 81CC3D30092A630E00B2BF95 /* Ode.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Ode.cpp; sourceTree = ""; }; + 81CC3D31092A630E00B2BF95 /* Ode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Ode.h; sourceTree = ""; }; + 81CC3D32092A630E00B2BF95 /* Plane.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Plane.cpp; sourceTree = ""; }; + 81CC3D33092A630E00B2BF95 /* Plane.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Plane.h; sourceTree = ""; }; + 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pluecker.cpp; sourceTree = ""; }; + 81CC3D35092A630E00B2BF95 /* Pluecker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pluecker.h; sourceTree = ""; }; + 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Polynomial.cpp; sourceTree = ""; }; + 81CC3D37092A630E00B2BF95 /* Polynomial.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Polynomial.h; sourceTree = ""; }; + 81CC3D38092A630E00B2BF95 /* Quat.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Quat.cpp; sourceTree = ""; }; + 81CC3D39092A630E00B2BF95 /* Quat.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Quat.h; sourceTree = ""; }; + 81CC3D3A092A630E00B2BF95 /* Radians.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Radians.cpp; sourceTree = ""; }; + 81CC3D3B092A630E00B2BF95 /* Radians.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Radians.h; sourceTree = ""; }; + 81CC3D3C092A630E00B2BF95 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; + 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Rotation.cpp; sourceTree = ""; }; + 81CC3D3E092A630E00B2BF95 /* Rotation.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Rotation.h; sourceTree = ""; }; + 81CC3D3F092A630E00B2BF95 /* Simd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd.cpp; sourceTree = ""; }; + 81CC3D40092A630E00B2BF95 /* Simd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd.h; sourceTree = ""; }; + 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_3DNow.cpp; sourceTree = ""; }; + 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_3DNow.h; sourceTree = ""; }; + 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_AltiVec.cpp; sourceTree = ""; }; + 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_AltiVec.h; sourceTree = ""; }; + 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_generic.cpp; sourceTree = ""; }; + 81CC3D46092A630E00B2BF95 /* Simd_generic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_generic.h; sourceTree = ""; }; + 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_InstructionMacros.h; sourceTree = ""; }; + 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_MMX.cpp; sourceTree = ""; }; + 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_MMX.h; sourceTree = ""; }; + 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE.cpp; sourceTree = ""; }; + 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE.h; sourceTree = ""; }; + 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE2.cpp; sourceTree = ""; }; + 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE2.h; sourceTree = ""; }; + 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE3.cpp; sourceTree = ""; }; + 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE3.h; sourceTree = ""; }; + 81CC3D52092A630E00B2BF95 /* Vector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vector.cpp; sourceTree = ""; }; + 81CC3D53092A630E00B2BF95 /* Vector.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vector.h; sourceTree = ""; }; + 81CC3D54092A630E00B2BF95 /* Parser.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Parser.cpp; sourceTree = ""; }; + 81CC3D55092A630E00B2BF95 /* Parser.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Parser.h; sourceTree = ""; }; + 81CC3D56092A630E00B2BF95 /* precompiled.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = precompiled.h; sourceTree = ""; }; + 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvHeap.cpp; sourceTree = ""; }; + 81CC3D58092A630E00B2BF95 /* rvHeap.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvHeap.h; sourceTree = ""; }; + 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvHeapArena.cpp; sourceTree = ""; }; + 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvHeapArena.h; sourceTree = ""; }; + 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvMemSys.cpp; sourceTree = ""; }; + 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvMemSys.h; sourceTree = ""; }; + 81CC3D5D092A630E00B2BF95 /* Str.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Str.cpp; sourceTree = ""; }; + 81CC3D5E092A630E00B2BF95 /* Str.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Str.h; sourceTree = ""; }; + 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TextCompiler.cpp; sourceTree = ""; }; + 81CC3D60092A630E00B2BF95 /* TextCompiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TextCompiler.h; sourceTree = ""; }; + 81CC3D64092A630E00B2BF95 /* AutoCrit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AutoCrit.h; sourceTree = ""; }; + 81CC3D69092A630E00B2BF95 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = ""; }; + 81CC3D6A092A630E00B2BF95 /* Timer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Timer.h; sourceTree = ""; }; + 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TimingCollection.cpp; sourceTree = ""; }; + 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TimingCollection.h; sourceTree = ""; }; + 81CC3D6D092A630E00B2BF95 /* Token.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Token.cpp; sourceTree = ""; }; + 81CC3D6E092A630E00B2BF95 /* Token.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Token.h; sourceTree = ""; }; + D2AAC046055464E500DB518D /* libidlib_pic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libidlib_pic.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D289987405E68DCB004EDB86 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* idlib */ = { + isa = PBXGroup; + children = ( + 81CC3CC8092A630E00B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = idlib; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC046055464E500DB518D /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3CC8092A630E00B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 81CC3CC9092A630E00B2BF95 /* algorithms */, + 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */, + 81CC3CCC092A630E00B2BF95 /* Base64.cpp */, + 81CC3CCD092A630E00B2BF95 /* Base64.h */, + 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */, + 81CC3CCF092A630E00B2BF95 /* BitMsg.h */, + 81CC3CD1092A630E00B2BF95 /* bv */, + 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */, + 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */, + 81CC3CDC092A630E00B2BF95 /* containers */, + 81CC3CEF092A630E00B2BF95 /* Dict.cpp */, + 81CC3CF0092A630E00B2BF95 /* Dict.h */, + 81CC3CF1092A630E00B2BF95 /* geometry */, + 81CC3D05092A630E00B2BF95 /* hashing */, + 81CC3D12092A630E00B2BF95 /* Heap.cpp */, + 81CC3D13092A630E00B2BF95 /* Heap.h */, + 81CC3D14092A630E00B2BF95 /* LangDict.cpp */, + 81CC3D15092A630E00B2BF95 /* LangDict.h */, + 81CC3D16092A630E00B2BF95 /* Lexer.cpp */, + 81CC3D17092A630E00B2BF95 /* Lexer.h */, + 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */, + 81CC3D19092A630E00B2BF95 /* LexerFactory.h */, + 81CC3D1A092A630E00B2BF95 /* Lib.cpp */, + 81CC3D1B092A630E00B2BF95 /* Lib.h */, + 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */, + 81CC3D1D092A630E00B2BF95 /* MapFile.h */, + 81CC3D1E092A630E00B2BF95 /* math */, + 81CC3D54092A630E00B2BF95 /* Parser.cpp */, + 81CC3D55092A630E00B2BF95 /* Parser.h */, + 81CC3D56092A630E00B2BF95 /* precompiled.h */, + 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */, + 81CC3D58092A630E00B2BF95 /* rvHeap.h */, + 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */, + 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */, + 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */, + 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */, + 81CC3D5D092A630E00B2BF95 /* Str.cpp */, + 81CC3D5E092A630E00B2BF95 /* Str.h */, + 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */, + 81CC3D60092A630E00B2BF95 /* TextCompiler.h */, + 81CC3D61092A630E00B2BF95 /* threads */, + 81CC3D69092A630E00B2BF95 /* Timer.cpp */, + 81CC3D6A092A630E00B2BF95 /* Timer.h */, + 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */, + 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */, + 81CC3D6D092A630E00B2BF95 /* Token.cpp */, + 81CC3D6E092A630E00B2BF95 /* Token.h */, + ); + name = Source; + path = ../../../idlib; + sourceTree = SOURCE_ROOT; + }; + 81CC3CC9092A630E00B2BF95 /* algorithms */ = { + isa = PBXGroup; + children = ( + 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */, + ); + path = algorithms; + sourceTree = ""; + }; + 81CC3CD1092A630E00B2BF95 /* bv */ = { + isa = PBXGroup; + children = ( + 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */, + 81CC3CD3092A630E00B2BF95 /* Bounds.h */, + 81CC3CD4092A630E00B2BF95 /* Box.cpp */, + 81CC3CD5092A630E00B2BF95 /* Box.h */, + 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */, + 81CC3CD7092A630E00B2BF95 /* Frustum.h */, + 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */, + 81CC3CD9092A630E00B2BF95 /* Sphere.h */, + ); + path = bv; + sourceTree = ""; + }; + 81CC3CDC092A630E00B2BF95 /* containers */ = { + isa = PBXGroup; + children = ( + 81CC3CDD092A630E00B2BF95 /* BinSearch.h */, + 81CC3CDE092A630E00B2BF95 /* BTree.h */, + 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */, + 81CC3CE0092A630E00B2BF95 /* HashIndex.h */, + 81CC3CE1092A630E00B2BF95 /* HashTable.h */, + 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */, + 81CC3CE3092A630E00B2BF95 /* LinkList.h */, + 81CC3CE4092A630E00B2BF95 /* List.h */, + 81CC3CE5092A630E00B2BF95 /* ListGame.h */, + 81CC3CE6092A630E00B2BF95 /* Pair.h */, + 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */, + 81CC3CE8092A630E00B2BF95 /* Queue.h */, + 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */, + 81CC3CEA092A630E00B2BF95 /* Stack.h */, + 81CC3CEB092A630E00B2BF95 /* StaticList.h */, + 81CC3CEC092A630E00B2BF95 /* StrList.h */, + 81CC3CED092A630E00B2BF95 /* StrPool.h */, + 81CC3CEE092A630E00B2BF95 /* VectorSet.h */, + ); + path = containers; + sourceTree = ""; + }; + 81CC3CF1092A630E00B2BF95 /* geometry */ = { + isa = PBXGroup; + children = ( + 81CC3CF3092A630E00B2BF95 /* DrawVert.h */, + 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */, + 81CC3CF5092A630E00B2BF95 /* JointTransform.h */, + 81CC3CF6092A630E00B2BF95 /* rvVertex.h */, + 81CC3CF7092A630E00B2BF95 /* Surface.cpp */, + 81CC3CF8092A630E00B2BF95 /* Surface.h */, + 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */, + 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */, + 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */, + 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */, + 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */, + 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */, + 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */, + 81CC3D00092A630E00B2BF95 /* TraceModel.h */, + 81CC3D01092A630E00B2BF95 /* Winding.cpp */, + 81CC3D02092A630E00B2BF95 /* Winding.h */, + 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */, + 81CC3D04092A630E00B2BF95 /* Winding2D.h */, + ); + path = geometry; + sourceTree = ""; + }; + 81CC3D05092A630E00B2BF95 /* hashing */ = { + isa = PBXGroup; + children = ( + 81CC3D06092A630E00B2BF95 /* CRC16.cpp */, + 81CC3D07092A630E00B2BF95 /* CRC16.h */, + 81CC3D08092A630E00B2BF95 /* CRC32.cpp */, + 81CC3D09092A630E00B2BF95 /* CRC32.h */, + 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */, + 81CC3D0B092A630E00B2BF95 /* CRC8.h */, + 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */, + 81CC3D0D092A630E00B2BF95 /* Honeyman.h */, + 81CC3D0E092A630E00B2BF95 /* MD4.cpp */, + 81CC3D0F092A630E00B2BF95 /* MD4.h */, + 81CC3D10092A630E00B2BF95 /* MD5.cpp */, + 81CC3D11092A630E00B2BF95 /* MD5.h */, + ); + path = hashing; + sourceTree = ""; + }; + 81CC3D1E092A630E00B2BF95 /* math */ = { + isa = PBXGroup; + children = ( + 81CC3D1F092A630E00B2BF95 /* Angles.cpp */, + 81CC3D20092A630E00B2BF95 /* Angles.h */, + 81CC3D21092A630E00B2BF95 /* Complex.cpp */, + 81CC3D22092A630E00B2BF95 /* Complex.h */, + 81CC3D23092A630E00B2BF95 /* Curve.h */, + 81CC3D24092A630E00B2BF95 /* Extrapolate.h */, + 81CC3D25092A630E00B2BF95 /* FFT.cpp */, + 81CC3D26092A630E00B2BF95 /* FFT.h */, + 81CC3D27092A630E00B2BF95 /* Interpolate.h */, + 81CC3D28092A630E00B2BF95 /* Lcp.cpp */, + 81CC3D29092A630E00B2BF95 /* Lcp.h */, + 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */, + 81CC3D2C092A630E00B2BF95 /* Math.cpp */, + 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */, + 81CC3D2F092A630E00B2BF95 /* Matrix.h */, + 81CC3D30092A630E00B2BF95 /* Ode.cpp */, + 81CC3D31092A630E00B2BF95 /* Ode.h */, + 81CC3D32092A630E00B2BF95 /* Plane.cpp */, + 81CC3D33092A630E00B2BF95 /* Plane.h */, + 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */, + 81CC3D35092A630E00B2BF95 /* Pluecker.h */, + 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */, + 81CC3D37092A630E00B2BF95 /* Polynomial.h */, + 81CC3D38092A630E00B2BF95 /* Quat.cpp */, + 81CC3D39092A630E00B2BF95 /* Quat.h */, + 81CC3D3A092A630E00B2BF95 /* Radians.cpp */, + 81CC3D3B092A630E00B2BF95 /* Radians.h */, + 81CC3D3C092A630E00B2BF95 /* Random.h */, + 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */, + 81CC3D3E092A630E00B2BF95 /* Rotation.h */, + 81CC3D3F092A630E00B2BF95 /* Simd.cpp */, + 81CC3D40092A630E00B2BF95 /* Simd.h */, + 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */, + 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */, + 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */, + 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */, + 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */, + 81CC3D46092A630E00B2BF95 /* Simd_generic.h */, + 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */, + 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */, + 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */, + 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */, + 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */, + 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */, + 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */, + 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */, + 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */, + 81CC3D52092A630E00B2BF95 /* Vector.cpp */, + 81CC3D53092A630E00B2BF95 /* Vector.h */, + ); + path = math; + sourceTree = ""; + }; + 81CC3D61092A630E00B2BF95 /* threads */ = { + isa = PBXGroup; + children = ( + 81CC3D64092A630E00B2BF95 /* AutoCrit.h */, + ); + path = threads; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC043055464E500DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 81CC3D6F092A630E00B2BF95 /* MultifieldSort.h in Headers */, + 81CC3D70092A630E00B2BF95 /* AutoPtr.h in Headers */, + 81CC3D72092A630E00B2BF95 /* Base64.h in Headers */, + 81CC3D74092A630E00B2BF95 /* BitMsg.h in Headers */, + 81CC3D77092A630E00B2BF95 /* Bounds.h in Headers */, + 81CC3D79092A630E00B2BF95 /* Box.h in Headers */, + 81CC3D7B092A630E00B2BF95 /* Frustum.h in Headers */, + 81CC3D7D092A630E00B2BF95 /* Sphere.h in Headers */, + 81CC3D7F092A630E00B2BF95 /* CmdArgs.h in Headers */, + 81CC3D80092A630E00B2BF95 /* BinSearch.h in Headers */, + 81CC3D81092A630E00B2BF95 /* BTree.h in Headers */, + 81CC3D83092A630E00B2BF95 /* HashIndex.h in Headers */, + 81CC3D84092A630E00B2BF95 /* HashTable.h in Headers */, + 81CC3D85092A630E00B2BF95 /* Hierarchy.h in Headers */, + 81CC3D86092A630E00B2BF95 /* LinkList.h in Headers */, + 81CC3D87092A630E00B2BF95 /* List.h in Headers */, + 81CC3D88092A630E00B2BF95 /* ListGame.h in Headers */, + 81CC3D89092A630E00B2BF95 /* Pair.h in Headers */, + 81CC3D8A092A630E00B2BF95 /* PlaneSet.h in Headers */, + 81CC3D8B092A630E00B2BF95 /* Queue.h in Headers */, + 81CC3D8C092A630E00B2BF95 /* rvBlockPool.h in Headers */, + 81CC3D8D092A630E00B2BF95 /* Stack.h in Headers */, + 81CC3D8E092A630E00B2BF95 /* StaticList.h in Headers */, + 81CC3D8F092A630E00B2BF95 /* StrList.h in Headers */, + 81CC3D90092A630E00B2BF95 /* StrPool.h in Headers */, + 81CC3D91092A630E00B2BF95 /* VectorSet.h in Headers */, + 81CC3D93092A630E00B2BF95 /* Dict.h in Headers */, + 81CC3D95092A630E00B2BF95 /* DrawVert.h in Headers */, + 81CC3D97092A630E00B2BF95 /* JointTransform.h in Headers */, + 81CC3D98092A630E00B2BF95 /* rvVertex.h in Headers */, + 81CC3D9A092A630E00B2BF95 /* Surface.h in Headers */, + 81CC3D9C092A630E00B2BF95 /* Surface_Patch.h in Headers */, + 81CC3D9E092A630E00B2BF95 /* Surface_Polytope.h in Headers */, + 81CC3DA0092A630E00B2BF95 /* Surface_SweptSpline.h in Headers */, + 81CC3DA2092A630E00B2BF95 /* TraceModel.h in Headers */, + 81CC3DA4092A630E00B2BF95 /* Winding.h in Headers */, + 81CC3DA6092A630E00B2BF95 /* Winding2D.h in Headers */, + 81CC3DA8092A630E00B2BF95 /* CRC16.h in Headers */, + 81CC3DAA092A630E00B2BF95 /* CRC32.h in Headers */, + 81CC3DAC092A630E00B2BF95 /* CRC8.h in Headers */, + 81CC3DAE092A630E00B2BF95 /* Honeyman.h in Headers */, + 81CC3DB0092A630E00B2BF95 /* MD4.h in Headers */, + 81CC3DB2092A630E00B2BF95 /* MD5.h in Headers */, + 81CC3DB4092A630E00B2BF95 /* Heap.h in Headers */, + 81CC3DB6092A630E00B2BF95 /* LangDict.h in Headers */, + 81CC3DB8092A630E00B2BF95 /* Lexer.h in Headers */, + 81CC3DBA092A630E00B2BF95 /* LexerFactory.h in Headers */, + 81CC3DBC092A630E00B2BF95 /* Lib.h in Headers */, + 81CC3DBE092A630E00B2BF95 /* MapFile.h in Headers */, + 81CC3DC0092A630E00B2BF95 /* Angles.h in Headers */, + 81CC3DC2092A630E00B2BF95 /* Complex.h in Headers */, + 81CC3DC3092A630E00B2BF95 /* Curve.h in Headers */, + 81CC3DC4092A630E00B2BF95 /* Extrapolate.h in Headers */, + 81CC3DC6092A630E00B2BF95 /* FFT.h in Headers */, + 81CC3DC7092A630E00B2BF95 /* Interpolate.h in Headers */, + 81CC3DC9092A630E00B2BF95 /* Lcp.h in Headers */, + 81CC3DCB092A630E00B2BF95 /* Mat3x4.h in Headers */, + 81CC3DCF092A630E00B2BF95 /* Matrix.h in Headers */, + 81CC3DD1092A630E00B2BF95 /* Ode.h in Headers */, + 81CC3DD3092A630E00B2BF95 /* Plane.h in Headers */, + 81CC3DD5092A630E00B2BF95 /* Pluecker.h in Headers */, + 81CC3DD7092A630E00B2BF95 /* Polynomial.h in Headers */, + 81CC3DD9092A630E00B2BF95 /* Quat.h in Headers */, + 81CC3DDB092A630E00B2BF95 /* Radians.h in Headers */, + 81CC3DDC092A630E00B2BF95 /* Random.h in Headers */, + 81CC3DDE092A630E00B2BF95 /* Rotation.h in Headers */, + 81CC3DE0092A630E00B2BF95 /* Simd.h in Headers */, + 81CC3DE2092A630E00B2BF95 /* Simd_3DNow.h in Headers */, + 81CC3DE4092A630E00B2BF95 /* Simd_AltiVec.h in Headers */, + 81CC3DE6092A630E00B2BF95 /* Simd_generic.h in Headers */, + 81CC3DE7092A630E00B2BF95 /* Simd_InstructionMacros.h in Headers */, + 81CC3DE9092A630E00B2BF95 /* Simd_MMX.h in Headers */, + 81CC3DEB092A630E00B2BF95 /* Simd_SSE.h in Headers */, + 81CC3DED092A630E00B2BF95 /* Simd_SSE2.h in Headers */, + 81CC3DEF092A630E00B2BF95 /* Simd_SSE3.h in Headers */, + 81CC3DF3092A630E00B2BF95 /* Vector.h in Headers */, + 81CC3DF5092A630E00B2BF95 /* Parser.h in Headers */, + 81CC3DF6092A630E00B2BF95 /* precompiled.h in Headers */, + 81CC3DF8092A630E00B2BF95 /* rvHeap.h in Headers */, + 81CC3DFA092A630E00B2BF95 /* rvHeapArena.h in Headers */, + 81CC3DFC092A630E00B2BF95 /* rvMemSys.h in Headers */, + 81CC3DFE092A630E00B2BF95 /* Str.h in Headers */, + 81CC3E00092A630E00B2BF95 /* TextCompiler.h in Headers */, + 81CC3E03092A630E00B2BF95 /* AutoCrit.h in Headers */, + 81CC3E09092A630E00B2BF95 /* Timer.h in Headers */, + 81CC3E0B092A630E00B2BF95 /* TimingCollection.h in Headers */, + 81CC3E0D092A630E00B2BF95 /* Token.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC045055464E500DB518D /* idlib_pic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81149CB4090D3FD10025E084 /* Build configuration list for PBXNativeTarget "idlib_pic" */; + buildPhases = ( + D2AAC043055464E500DB518D /* Headers */, + D2AAC044055464E500DB518D /* Sources */, + D289987405E68DCB004EDB86 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = idlib_pic; + productName = idlib; + productReference = D2AAC046055464E500DB518D /* libidlib_pic.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149CB8090D3FD10025E084 /* Build configuration list for PBXProject "idlib" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* idlib */; + projectDirPath = ""; + targets = ( + D2AAC045055464E500DB518D /* idlib_pic */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC044055464E500DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81CC3D71092A630E00B2BF95 /* Base64.cpp in Sources */, + 81CC3D73092A630E00B2BF95 /* BitMsg.cpp in Sources */, + 81CC3D76092A630E00B2BF95 /* Bounds.cpp in Sources */, + 81CC3D78092A630E00B2BF95 /* Box.cpp in Sources */, + 81CC3D7A092A630E00B2BF95 /* Frustum.cpp in Sources */, + 81CC3D7C092A630E00B2BF95 /* Sphere.cpp in Sources */, + 81CC3D7E092A630E00B2BF95 /* CmdArgs.cpp in Sources */, + 81CC3D82092A630E00B2BF95 /* HashIndex.cpp in Sources */, + 81CC3D92092A630E00B2BF95 /* Dict.cpp in Sources */, + 81CC3D96092A630E00B2BF95 /* JointTransform.cpp in Sources */, + 81CC3D99092A630E00B2BF95 /* Surface.cpp in Sources */, + 81CC3D9B092A630E00B2BF95 /* Surface_Patch.cpp in Sources */, + 81CC3D9D092A630E00B2BF95 /* Surface_Polytope.cpp in Sources */, + 81CC3D9F092A630E00B2BF95 /* Surface_SweptSpline.cpp in Sources */, + 81CC3DA1092A630E00B2BF95 /* TraceModel.cpp in Sources */, + 81CC3DA3092A630E00B2BF95 /* Winding.cpp in Sources */, + 81CC3DA5092A630E00B2BF95 /* Winding2D.cpp in Sources */, + 81CC3DA7092A630E00B2BF95 /* CRC16.cpp in Sources */, + 81CC3DA9092A630E00B2BF95 /* CRC32.cpp in Sources */, + 81CC3DAB092A630E00B2BF95 /* CRC8.cpp in Sources */, + 81CC3DAD092A630E00B2BF95 /* Honeyman.cpp in Sources */, + 81CC3DAF092A630E00B2BF95 /* MD4.cpp in Sources */, + 81CC3DB1092A630E00B2BF95 /* MD5.cpp in Sources */, + 81CC3DB3092A630E00B2BF95 /* Heap.cpp in Sources */, + 81CC3DB5092A630E00B2BF95 /* LangDict.cpp in Sources */, + 81CC3DB7092A630E00B2BF95 /* Lexer.cpp in Sources */, + 81CC3DB9092A630E00B2BF95 /* LexerFactory.cpp in Sources */, + 81CC3DBB092A630E00B2BF95 /* Lib.cpp in Sources */, + 81CC3DBD092A630E00B2BF95 /* mapfile.cpp in Sources */, + 81CC3DBF092A630E00B2BF95 /* Angles.cpp in Sources */, + 81CC3DC1092A630E00B2BF95 /* Complex.cpp in Sources */, + 81CC3DC5092A630E00B2BF95 /* FFT.cpp in Sources */, + 81CC3DC8092A630E00B2BF95 /* Lcp.cpp in Sources */, + 81CC3DCC092A630E00B2BF95 /* Math.cpp in Sources */, + 81CC3DCE092A630E00B2BF95 /* Matrix.cpp in Sources */, + 81CC3DD0092A630E00B2BF95 /* Ode.cpp in Sources */, + 81CC3DD2092A630E00B2BF95 /* Plane.cpp in Sources */, + 81CC3DD4092A630E00B2BF95 /* Pluecker.cpp in Sources */, + 81CC3DD6092A630E00B2BF95 /* Polynomial.cpp in Sources */, + 81CC3DD8092A630E00B2BF95 /* Quat.cpp in Sources */, + 81CC3DDA092A630E00B2BF95 /* Radians.cpp in Sources */, + 81CC3DDD092A630E00B2BF95 /* Rotation.cpp in Sources */, + 81CC3DDF092A630E00B2BF95 /* Simd.cpp in Sources */, + 81CC3DE1092A630E00B2BF95 /* Simd_3DNow.cpp in Sources */, + 81CC3DE3092A630E00B2BF95 /* Simd_AltiVec.cpp in Sources */, + 81CC3DE5092A630E00B2BF95 /* Simd_generic.cpp in Sources */, + 81CC3DE8092A630E00B2BF95 /* Simd_MMX.cpp in Sources */, + 81CC3DEA092A630E00B2BF95 /* Simd_SSE.cpp in Sources */, + 81CC3DEC092A630E00B2BF95 /* Simd_SSE2.cpp in Sources */, + 81CC3DEE092A630E00B2BF95 /* Simd_SSE3.cpp in Sources */, + 81CC3DF2092A630E00B2BF95 /* Vector.cpp in Sources */, + 81CC3DF4092A630E00B2BF95 /* Parser.cpp in Sources */, + 81CC3DF7092A630E00B2BF95 /* rvHeap.cpp in Sources */, + 81CC3DF9092A630E00B2BF95 /* rvHeapArena.cpp in Sources */, + 81CC3DFB092A630E00B2BF95 /* rvMemSys.cpp in Sources */, + 81CC3DFD092A630E00B2BF95 /* Str.cpp in Sources */, + 81CC3DFF092A630E00B2BF95 /* TextCompiler.cpp in Sources */, + 81CC3E08092A630E00B2BF95 /* Timer.cpp in Sources */, + 81CC3E0A092A630E00B2BF95 /* TimingCollection.cpp in Sources */, + 81CC3E0C092A630E00B2BF95 /* Token.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 81149CB5090D3FD10025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET = 10.3; + OTHER_CFLAGS = ( + "-fvisibility-inlines-hidden", + ); + PREBINDING = NO; + PRODUCT_NAME = idlib_pic; + REZ_PREPROCESSOR_DEFINITIONS = MACOS_X; + USE_SEPARATE_HEADERMAPS = YES; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81149CB6090D3FD10025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G4; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET = 10.3; + OTHER_CFLAGS = ( + "-fvisibility-inlines-hidden", + "-finline", + "-finline-limit=1000", + "-fomit-frame-pointer", + "-fno-common", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + PREBINDING = NO; + PRODUCT_NAME = idlib_pic; + REZ_PREPROCESSOR_DEFINITIONS = MACOS_X; + USE_SEPARATE_HEADERMAPS = YES; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149CB9090D3FD10025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + GCC_ALTIVEC_EXTENSIONS = YES; + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _DEBUG, + Q4SDK, + ); + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + USE_SEPARATE_HEADERMAPS = YES; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149CBA090D3FD10025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + GCC_ALTIVEC_EXTENSIONS = YES; + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _FINAL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + USE_SEPARATE_HEADERMAPS = YES; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81149CB4090D3FD10025E084 /* Build configuration list for PBXNativeTarget "idlib_pic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149CB5090D3FD10025E084 /* Debug */, + 81149CB6090D3FD10025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149CB8090D3FD10025E084 /* Build configuration list for PBXProject "idlib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149CB9090D3FD10025E084 /* Debug */, + 81149CBA090D3FD10025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/source/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj b/source/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj new file mode 100644 index 0000000..29e1414 --- /dev/null +++ b/source/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj @@ -0,0 +1,1242 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 255BA9290BB85D6C00D112AC /* Lagometer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 255BA9270BB85D6C00D112AC /* Lagometer.cpp */; }; + 2CCDAFD60A2CD52800D4AD93 /* Buying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */; }; + 2CE8A5580A2CD90C00929ACB /* WeaponNapalmGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */; }; + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8114A1A2090D686B0025E084 /* libidlib_pic.a */; }; + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */; }; + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */; }; + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */; }; + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */; }; + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */; }; + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */; }; + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405A092A64D300B2BF95 /* Sound.cpp */; }; + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */; }; + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400E092A64D300B2BF95 /* StatWindow.cpp */; }; + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */; }; + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */; }; + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */; }; + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */; }; + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */; }; + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */; }; + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */; }; + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400C092A64D300B2BF95 /* StatManager.cpp */; }; + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */; }; + 81114FF8093E194300A5C91A /* AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F75092A64D300B2BF95 /* AF.cpp */; }; + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */; }; + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBF092A64D300B2BF95 /* Camera.cpp */; }; + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */; }; + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */; }; + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */; }; + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F89092A64D300B2BF95 /* AI_events.cpp */; }; + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */; }; + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */; }; + 81115001093E194300A5C91A /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCA092A64D300B2BF95 /* Effect.cpp */; }; + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */; }; + 81115003093E194300A5C91A /* Player_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4040092A64D300B2BF95 /* Player_States.cpp */; }; + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */; }; + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */; }; + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */; }; + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */; }; + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */; }; + 81115009093E194300A5C91A /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE8092A64D300B2BF95 /* State.cpp */; }; + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */; }; + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */; }; + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */; }; + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */; }; + 8111500E093E194300A5C91A /* Mover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4002092A64D300B2BF95 /* Mover.cpp */; }; + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */; }; + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */; }; + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4050092A64D300B2BF95 /* Script_Program.cpp */; }; + 81115012093E194300A5C91A /* Push.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403A092A64D300B2BF95 /* Push.cpp */; }; + 81115013093E194300A5C91A /* IconManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */; }; + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */; }; + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */; }; + 81115016093E194300A5C91A /* Moveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4000092A64D300B2BF95 /* Moveable.cpp */; }; + 81115017093E194300A5C91A /* Clip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4016092A64D300B2BF95 /* Clip.cpp */; }; + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */; }; + 81115019093E194300A5C91A /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4060092A64D300B2BF95 /* Target.cpp */; }; + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4064092A64D300B2BF95 /* Trigger.cpp */; }; + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4088092A64D300B2BF95 /* Weapon.cpp */; }; + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */; }; + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */; }; + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */; }; + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */; }; + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */; }; + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */; }; + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */; }; + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */; }; + 81115024093E194300A5C91A /* Playback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403C092A64D300B2BF95 /* Playback.cpp */; }; + 81115025093E194300A5C91A /* Force.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4018092A64D300B2BF95 /* Force.cpp */; }; + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400A092A64D300B2BF95 /* StatEvent.cpp */; }; + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */; }; + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */; }; + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */; }; + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */; }; + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */; }; + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */; }; + 8111502D093E194300A5C91A /* Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403D092A64D300B2BF95 /* Player.cpp */; }; + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */; }; + 8111502F093E194300A5C91A /* IK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF4092A64D300B2BF95 /* IK.cpp */; }; + 81115030093E194300A5C91A /* Projectile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4043092A64D300B2BF95 /* Projectile.cpp */; }; + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4067092A64D300B2BF95 /* Vehicle.cpp */; }; + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */; }; + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */; }; + 81115034093E194300A5C91A /* Pvs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4045092A64D300B2BF95 /* Pvs.cpp */; }; + 81115035093E194300A5C91A /* Light.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFA092A64D300B2BF95 /* Light.cpp */; }; + 81115036093E194300A5C91A /* Entity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCE092A64D300B2BF95 /* Entity.cpp */; }; + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */; }; + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */; }; + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */; }; + 8111503A093E194300A5C91A /* AAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7A092A64D300B2BF95 /* AAS.cpp */; }; + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */; }; + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */; }; + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */; }; + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */; }; + 81115040093E194300A5C91A /* AI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F84092A64D300B2BF95 /* AI.cpp */; }; + 81115041093E194300A5C91A /* Tourney.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4010092A64D300B2BF95 /* Tourney.cpp */; }; + 81115042093E194300A5C91A /* TramGate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4062092A64D300B2BF95 /* TramGate.cpp */; }; + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */; }; + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */; }; + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406D092A64D300B2BF95 /* VehicleController.cpp */; }; + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */; }; + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */; }; + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */; }; + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */; }; + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */; }; + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */; }; + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F92092A64D300B2BF95 /* AI_States.cpp */; }; + 8111504D093E194300A5C91A /* Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE3092A64D300B2BF95 /* Event.cpp */; }; + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */; }; + 8111504F093E194300A5C91A /* CTF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4005092A64D300B2BF95 /* CTF.cpp */; }; + 81115050093E194300A5C91A /* GameState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4007092A64D300B2BF95 /* GameState.cpp */; }; + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */; }; + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */; }; + 81115053093E194300A5C91A /* Physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4022092A64D300B2BF95 /* Physics.cpp */; }; + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */; }; + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */; }; + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */; }; + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */; }; + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */; }; + 81115059093E194300A5C91A /* Class.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDF092A64D300B2BF95 /* Class.cpp */; }; + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */; }; + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */; }; + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */; }; + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */; }; + 8111505E093E194300A5C91A /* Misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFE092A64D300B2BF95 /* Misc.cpp */; }; + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */; }; + 81115060093E194300A5C91A /* Icon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF0092A64D300B2BF95 /* Icon.cpp */; }; + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */; }; + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */; }; + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */; }; + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */; }; + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */; }; + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */; }; + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */; }; + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */; }; + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */; }; + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */; }; + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */; }; + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */; }; + 8111506D093E194300A5C91A /* Anim.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB7092A64D300B2BF95 /* Anim.cpp */; }; + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */; }; + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */; }; + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405E092A64D300B2BF95 /* SplineMover.cpp */; }; + 81115071093E194300A5C91A /* spawner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405C092A64D300B2BF95 /* spawner.cpp */; }; + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */; }; + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */; }; + 81115074093E194300A5C91A /* LipSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */; }; + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */; }; + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */; }; + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */; }; + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */; }; + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */; }; + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401E092A64D300B2BF95 /* Force_Field.cpp */; }; + 8111507B093E194300A5C91A /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F73092A64D300B2BF95 /* Actor.cpp */; }; + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4041092A64D300B2BF95 /* PlayerView.cpp */; }; + 8111507D093E194300A5C91A /* Item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF8092A64D300B2BF95 /* Item.cpp */; }; + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */; }; + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */; }; + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */; }; + 81115081093E194300A5C91A /* Instance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF6092A64D300B2BF95 /* Instance.cpp */; }; + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */; }; + 816AD2E00981819D00CAC29C /* Game.plist in Resources */ = {isa = PBXBuildFile; fileRef = 816AD2DF0981819D00CAC29C /* Game.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 81114FE2093E189800A5C91A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = idlib_pic; + }; + 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 255BA9270BB85D6C00D112AC /* Lagometer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lagometer.cpp; sourceTree = ""; }; + 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Buying.cpp; path = mp/Buying.cpp; sourceTree = ""; }; + 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNapalmGun.cpp; sourceTree = ""; }; + 81114DFB093E117D00A5C91A /* game.so.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = game.so.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8114A19C090D686B0025E084 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = idlib.xcodeproj; sourceTree = ""; }; + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientAFEntity.cpp; sourceTree = ""; }; + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientAFEntity.h; sourceTree = ""; }; + 816AD2DF0981819D00CAC29C /* Game.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; name = Game.plist; path = ../Resources/Game.plist; sourceTree = SOURCE_ROOT; }; + 81CC3F73092A64D300B2BF95 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 81CC3F74092A64D300B2BF95 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 81CC3F75092A64D300B2BF95 /* AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AF.cpp; sourceTree = ""; }; + 81CC3F76092A64D300B2BF95 /* AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AF.h; sourceTree = ""; }; + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AFEntity.cpp; sourceTree = ""; }; + 81CC3F78092A64D300B2BF95 /* AFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AFEntity.h; sourceTree = ""; }; + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS.cpp; sourceTree = ""; }; + 81CC3F7B092A64D300B2BF95 /* AAS.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS.h; sourceTree = ""; }; + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_debug.cpp; sourceTree = ""; }; + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_Find.cpp; sourceTree = ""; }; + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_Find.h; sourceTree = ""; }; + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_local.h; sourceTree = ""; }; + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_pathing.cpp; sourceTree = ""; }; + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_routing.cpp; sourceTree = ""; }; + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_tactical.cpp; sourceTree = ""; }; + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_tactical.h; sourceTree = ""; }; + 81CC3F84092A64D300B2BF95 /* AI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI.cpp; sourceTree = ""; }; + 81CC3F85092A64D300B2BF95 /* AI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI.h; sourceTree = ""; }; + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Actions.cpp; sourceTree = ""; }; + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Announcements.cpp; sourceTree = ""; }; + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Debug.cpp; sourceTree = ""; }; + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_events.cpp; sourceTree = ""; }; + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Manager.cpp; sourceTree = ""; }; + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Manager.h; sourceTree = ""; }; + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Medic.cpp; sourceTree = ""; }; + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Medic.h; sourceTree = ""; }; + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Move.cpp; sourceTree = ""; }; + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Move.h; sourceTree = ""; }; + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_pathing.cpp; sourceTree = ""; }; + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_States.cpp; sourceTree = ""; }; + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Tactical.cpp; sourceTree = ""; }; + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Tactical.h; sourceTree = ""; }; + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Util.cpp; sourceTree = ""; }; + 81CC3F96092A64D300B2BF95 /* AI_Util.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Util.h; sourceTree = ""; }; + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Berserker.cpp; sourceTree = ""; }; + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossBuddy.cpp; sourceTree = ""; }; + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossMakron.cpp; sourceTree = ""; }; + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyGround.cpp; sourceTree = ""; }; + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyHover.cpp; sourceTree = ""; }; + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_FailedTransfer.cpp; sourceTree = ""; }; + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Fatty.cpp; sourceTree = ""; }; + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gladiator.cpp; sourceTree = ""; }; + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Grunt.cpp; sourceTree = ""; }; + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gunner.cpp; sourceTree = ""; }; + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Harvester.cpp; sourceTree = ""; }; + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HarvesterDispersal.cpp; sourceTree = ""; }; + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HeavyHoverTank.cpp; sourceTree = ""; }; + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_IronMaiden.cpp; sourceTree = ""; }; + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_LightTank.cpp; sourceTree = ""; }; + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_NetworkGuardian.cpp; sourceTree = ""; }; + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_RepairBot.cpp; sourceTree = ""; }; + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Scientist.cpp; sourceTree = ""; }; + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Sentry.cpp; sourceTree = ""; }; + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_SlimyTransfer.cpp; sourceTree = ""; }; + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StreamProtector.cpp; sourceTree = ""; }; + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggFlyer.cpp; sourceTree = ""; }; + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggHover.cpp; sourceTree = ""; }; + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggMarine.cpp; sourceTree = ""; }; + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TeleportDropper.cpp; sourceTree = ""; }; + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Turret.cpp; sourceTree = ""; }; + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TurretFlying.cpp; sourceTree = ""; }; + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAI.cpp; sourceTree = ""; }; + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAI.h; sourceTree = ""; }; + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim.cpp; sourceTree = ""; }; + 81CC3FB8092A64D300B2BF95 /* Anim.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim.h; sourceTree = ""; }; + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Blend.cpp; sourceTree = ""; }; + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Import.cpp; sourceTree = ""; }; + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Testmodel.cpp; sourceTree = ""; }; + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim_Testmodel.h; sourceTree = ""; }; + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BrittleFracture.cpp; sourceTree = ""; }; + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BrittleFracture.h; sourceTree = ""; }; + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Camera.cpp; sourceTree = ""; }; + 81CC3FC0092A64D300B2BF95 /* Camera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Camera.h; sourceTree = ""; }; + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEffect.cpp; sourceTree = ""; }; + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEffect.h; sourceTree = ""; }; + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEntity.cpp; sourceTree = ""; }; + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEntity.h; sourceTree = ""; }; + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientModel.cpp; sourceTree = ""; }; + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientModel.h; sourceTree = ""; }; + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientMoveable.cpp; sourceTree = ""; }; + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientMoveable.h; sourceTree = ""; }; + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; + 81CC3FCB092A64D300B2BF95 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Entity.cpp; sourceTree = ""; }; + 81CC3FCF092A64D300B2BF95 /* Entity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Entity.h; sourceTree = ""; }; + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Debug.cpp; sourceTree = ""; }; + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Debug.h; sourceTree = ""; }; + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_local.cpp; sourceTree = ""; }; + 81CC3FD7092A64D300B2BF95 /* Game_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_local.h; sourceTree = ""; }; + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Log.cpp; sourceTree = ""; }; + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Log.h; sourceTree = ""; }; + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_network.cpp; sourceTree = ""; }; + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameEdit.cpp; sourceTree = ""; }; + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameEdit.h; sourceTree = ""; }; + 81CC3FDF092A64D300B2BF95 /* Class.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Class.cpp; sourceTree = ""; }; + 81CC3FE0092A64D300B2BF95 /* Class.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Class.h; sourceTree = ""; }; + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DebugGraph.cpp; sourceTree = ""; }; + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DebugGraph.h; sourceTree = ""; }; + 81CC3FE3092A64D300B2BF95 /* Event.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Event.cpp; sourceTree = ""; }; + 81CC3FE4092A64D300B2BF95 /* Event.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = ""; }; + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NoGameTypeInfo.h; sourceTree = ""; }; + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SaveGame.cpp; sourceTree = ""; }; + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SaveGame.h; sourceTree = ""; }; + 81CC3FE8092A64D300B2BF95 /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = ""; }; + 81CC3FE9092A64D300B2BF95 /* State.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = State.h; sourceTree = ""; }; + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCmds.cpp; sourceTree = ""; }; + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCmds.h; sourceTree = ""; }; + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCvar.cpp; sourceTree = ""; }; + 81CC3FED092A64D300B2BF95 /* SysCvar.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCvar.h; sourceTree = ""; }; + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Healing_Station.cpp; sourceTree = ""; }; + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Healing_Station.h; sourceTree = ""; }; + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Icon.cpp; sourceTree = ""; }; + 81CC3FF1092A64D300B2BF95 /* Icon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Icon.h; sourceTree = ""; }; + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IconManager.cpp; sourceTree = ""; }; + 81CC3FF3092A64D300B2BF95 /* IconManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IconManager.h; sourceTree = ""; }; + 81CC3FF4092A64D300B2BF95 /* IK.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IK.cpp; sourceTree = ""; }; + 81CC3FF5092A64D300B2BF95 /* IK.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IK.h; sourceTree = ""; }; + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Instance.cpp; sourceTree = ""; }; + 81CC3FF7092A64D300B2BF95 /* Instance.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Instance.h; sourceTree = ""; }; + 81CC3FF8092A64D300B2BF95 /* Item.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Item.cpp; sourceTree = ""; }; + 81CC3FF9092A64D300B2BF95 /* Item.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Item.h; sourceTree = ""; }; + 81CC3FFA092A64D300B2BF95 /* Light.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Light.cpp; sourceTree = ""; }; + 81CC3FFB092A64D300B2BF95 /* Light.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Light.h; sourceTree = ""; }; + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LipSync.cpp; sourceTree = ""; }; + 81CC3FFD092A64D300B2BF95 /* LipSync.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LipSync.h; sourceTree = ""; }; + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Misc.cpp; sourceTree = ""; }; + 81CC3FFF092A64D300B2BF95 /* Misc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Misc.h; sourceTree = ""; }; + 81CC4000092A64D300B2BF95 /* Moveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Moveable.cpp; sourceTree = ""; }; + 81CC4001092A64D300B2BF95 /* Moveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Moveable.h; sourceTree = ""; }; + 81CC4002092A64D300B2BF95 /* Mover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Mover.cpp; sourceTree = ""; }; + 81CC4003092A64D300B2BF95 /* Mover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mover.h; sourceTree = ""; }; + 81CC4005092A64D300B2BF95 /* CTF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CTF.cpp; sourceTree = ""; }; + 81CC4006092A64D300B2BF95 /* CTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CTF.h; sourceTree = ""; }; + 81CC4007092A64D300B2BF95 /* GameState.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameState.cpp; sourceTree = ""; }; + 81CC4008092A64D300B2BF95 /* GameState.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameState.h; sourceTree = ""; }; + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatEvent.cpp; sourceTree = ""; }; + 81CC400B092A64D300B2BF95 /* StatEvent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatEvent.h; sourceTree = ""; }; + 81CC400C092A64D300B2BF95 /* StatManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatManager.cpp; sourceTree = ""; }; + 81CC400D092A64D300B2BF95 /* StatManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatManager.h; sourceTree = ""; }; + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatWindow.cpp; sourceTree = ""; }; + 81CC400F092A64D300B2BF95 /* StatWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatWindow.h; sourceTree = ""; }; + 81CC4010092A64D300B2BF95 /* Tourney.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Tourney.cpp; sourceTree = ""; }; + 81CC4011092A64D300B2BF95 /* Tourney.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Tourney.h; sourceTree = ""; }; + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VoiceComms.cpp; sourceTree = ""; }; + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerGame.cpp; sourceTree = ""; }; + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerGame.h; sourceTree = ""; }; + 81CC4016092A64D300B2BF95 /* Clip.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Clip.cpp; sourceTree = ""; }; + 81CC4017092A64D300B2BF95 /* Clip.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Clip.h; sourceTree = ""; }; + 81CC4018092A64D300B2BF95 /* Force.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force.cpp; sourceTree = ""; }; + 81CC4019092A64D300B2BF95 /* Force.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force.h; sourceTree = ""; }; + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Constant.cpp; sourceTree = ""; }; + 81CC401B092A64D300B2BF95 /* Force_Constant.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Constant.h; sourceTree = ""; }; + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Drag.cpp; sourceTree = ""; }; + 81CC401D092A64D300B2BF95 /* Force_Drag.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Drag.h; sourceTree = ""; }; + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Field.cpp; sourceTree = ""; }; + 81CC401F092A64D300B2BF95 /* Force_Field.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Field.h; sourceTree = ""; }; + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Spring.cpp; sourceTree = ""; }; + 81CC4021092A64D300B2BF95 /* Force_Spring.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Spring.h; sourceTree = ""; }; + 81CC4022092A64D300B2BF95 /* Physics.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics.cpp; sourceTree = ""; }; + 81CC4023092A64D300B2BF95 /* Physics.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics.h; sourceTree = ""; }; + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Actor.cpp; sourceTree = ""; }; + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Actor.h; sourceTree = ""; }; + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_AF.cpp; sourceTree = ""; }; + 81CC4027092A64D300B2BF95 /* Physics_AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_AF.h; sourceTree = ""; }; + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Base.cpp; sourceTree = ""; }; + 81CC4029092A64D300B2BF95 /* Physics_Base.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Base.h; sourceTree = ""; }; + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Monster.cpp; sourceTree = ""; }; + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Monster.h; sourceTree = ""; }; + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Parametric.cpp; sourceTree = ""; }; + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Parametric.h; sourceTree = ""; }; + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Particle.cpp; sourceTree = ""; }; + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Particle.h; sourceTree = ""; }; + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Player.cpp; sourceTree = ""; }; + 81CC4031092A64D300B2BF95 /* Physics_Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Player.h; sourceTree = ""; }; + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_RigidBody.cpp; sourceTree = ""; }; + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_RigidBody.h; sourceTree = ""; }; + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Static.cpp; sourceTree = ""; }; + 81CC4035092A64D300B2BF95 /* Physics_Static.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Static.h; sourceTree = ""; }; + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_StaticMulti.cpp; sourceTree = ""; }; + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_StaticMulti.h; sourceTree = ""; }; + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_VehicleMonster.cpp; sourceTree = ""; }; + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_VehicleMonster.h; sourceTree = ""; }; + 81CC403A092A64D300B2BF95 /* Push.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Push.cpp; sourceTree = ""; }; + 81CC403B092A64D300B2BF95 /* Push.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Push.h; sourceTree = ""; }; + 81CC403C092A64D300B2BF95 /* Playback.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Playback.cpp; sourceTree = ""; }; + 81CC403D092A64D300B2BF95 /* Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player.cpp; sourceTree = ""; }; + 81CC403E092A64D300B2BF95 /* Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Player.h; sourceTree = ""; }; + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_Cheats.cpp; sourceTree = ""; }; + 81CC4040092A64D300B2BF95 /* Player_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_States.cpp; sourceTree = ""; }; + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PlayerView.cpp; sourceTree = ""; }; + 81CC4042092A64D300B2BF95 /* PlayerView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlayerView.h; sourceTree = ""; }; + 81CC4043092A64D300B2BF95 /* Projectile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Projectile.cpp; sourceTree = ""; }; + 81CC4044092A64D300B2BF95 /* Projectile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Projectile.h; sourceTree = ""; }; + 81CC4045092A64D300B2BF95 /* Pvs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pvs.cpp; sourceTree = ""; }; + 81CC4046092A64D300B2BF95 /* Pvs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pvs.h; sourceTree = ""; }; + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Compiler.cpp; sourceTree = ""; }; + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Compiler.h; sourceTree = ""; }; + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Interpreter.cpp; sourceTree = ""; }; + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Interpreter.h; sourceTree = ""; }; + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Program.cpp; sourceTree = ""; }; + 81CC4051092A64D300B2BF95 /* Script_Program.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Program.h; sourceTree = ""; }; + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Thread.cpp; sourceTree = ""; }; + 81CC4053092A64D300B2BF95 /* Script_Thread.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Thread.h; sourceTree = ""; }; + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptFuncUtility.cpp; sourceTree = ""; }; + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ScriptFuncUtility.h; sourceTree = ""; }; + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityCamera.cpp; sourceTree = ""; }; + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SecurityCamera.h; sourceTree = ""; }; + 81CC405A092A64D300B2BF95 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; + 81CC405B092A64D300B2BF95 /* Sound.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sound.h; sourceTree = ""; }; + 81CC405C092A64D300B2BF95 /* spawner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = spawner.cpp; sourceTree = ""; }; + 81CC405D092A64D300B2BF95 /* spawner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = spawner.h; sourceTree = ""; }; + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SplineMover.cpp; sourceTree = ""; }; + 81CC405F092A64D300B2BF95 /* SplineMover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SplineMover.h; sourceTree = ""; }; + 81CC4060092A64D300B2BF95 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = ""; }; + 81CC4061092A64D300B2BF95 /* Target.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + 81CC4062092A64D300B2BF95 /* TramGate.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TramGate.cpp; sourceTree = ""; }; + 81CC4063092A64D300B2BF95 /* TramGate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TramGate.h; sourceTree = ""; }; + 81CC4064092A64D300B2BF95 /* Trigger.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Trigger.cpp; sourceTree = ""; }; + 81CC4065092A64D300B2BF95 /* Trigger.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Trigger.h; sourceTree = ""; }; + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle.cpp; sourceTree = ""; }; + 81CC4068092A64D300B2BF95 /* Vehicle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vehicle.h; sourceTree = ""; }; + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_DropPod.cpp; sourceTree = ""; }; + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_Walker.cpp; sourceTree = ""; }; + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAnimated.cpp; sourceTree = ""; }; + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAnimated.h; sourceTree = ""; }; + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleController.cpp; sourceTree = ""; }; + 81CC406E092A64D300B2BF95 /* VehicleController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleController.h; sourceTree = ""; }; + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleDriver.cpp; sourceTree = ""; }; + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleDriver.h; sourceTree = ""; }; + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleMonster.cpp; sourceTree = ""; }; + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleMonster.h; sourceTree = ""; }; + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleParts.cpp; sourceTree = ""; }; + 81CC4074092A64D300B2BF95 /* VehicleParts.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleParts.h; sourceTree = ""; }; + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehiclePosition.cpp; sourceTree = ""; }; + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleRigid.cpp; sourceTree = ""; }; + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleRigid.h; sourceTree = ""; }; + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleSpline.cpp; sourceTree = ""; }; + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleSpline.h; sourceTree = ""; }; + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleStatic.cpp; sourceTree = ""; }; + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleStatic.h; sourceTree = ""; }; + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponBlaster.cpp; sourceTree = ""; }; + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponDarkMatterGun.cpp; sourceTree = ""; }; + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGauntlet.cpp; sourceTree = ""; }; + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGrenadeLauncher.cpp; sourceTree = ""; }; + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponHyperblaster.cpp; sourceTree = ""; }; + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponLightningGun.cpp; sourceTree = ""; }; + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponMachinegun.cpp; sourceTree = ""; }; + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNailgun.cpp; sourceTree = ""; }; + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRailgun.cpp; sourceTree = ""; }; + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRocketLauncher.cpp; sourceTree = ""; }; + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponShotgun.cpp; sourceTree = ""; }; + 81CC4088092A64D300B2BF95 /* Weapon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Weapon.cpp; sourceTree = ""; }; + 81CC4089092A64D300B2BF95 /* Weapon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Weapon.h; sourceTree = ""; }; + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WorldSpawn.cpp; sourceTree = ""; }; + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WorldSpawn.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 81114DF9093E117D00A5C91A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* game */ = { + isa = PBXGroup; + children = ( + 81CC3F72092A64D300B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + 816AD2DF0981819D00CAC29C /* Game.plist */, + 8114A19C090D686B0025E084 /* idlib.xcodeproj */, + ); + name = game; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 81114DFB093E117D00A5C91A /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 8114A19D090D686B0025E084 /* Products */ = { + isa = PBXGroup; + children = ( + 8114A1A2090D686B0025E084 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3F72092A64D300B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 255BA9270BB85D6C00D112AC /* Lagometer.cpp */, + 81CC3F73092A64D300B2BF95 /* Actor.cpp */, + 81CC3F74092A64D300B2BF95 /* Actor.h */, + 81CC3F75092A64D300B2BF95 /* AF.cpp */, + 81CC3F76092A64D300B2BF95 /* AF.h */, + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */, + 81CC3F78092A64D300B2BF95 /* AFEntity.h */, + 81CC3F79092A64D300B2BF95 /* ai */, + 81CC3FB6092A64D300B2BF95 /* anim */, + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */, + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */, + 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */, + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */, + 81CC3FC0092A64D300B2BF95 /* Camera.h */, + 81CC3FC1092A64D300B2BF95 /* client */, + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */, + 81CC3FCB092A64D300B2BF95 /* Effect.h */, + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */, + 81CC3FCF092A64D300B2BF95 /* Entity.h */, + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */, + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */, + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */, + 81CC3FD7092A64D300B2BF95 /* Game_local.h */, + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */, + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */, + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */, + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */, + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */, + 81CC3FDD092A64D300B2BF95 /* gamesys */, + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */, + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */, + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */, + 81CC3FF1092A64D300B2BF95 /* Icon.h */, + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */, + 81CC3FF3092A64D300B2BF95 /* IconManager.h */, + 81CC3FF4092A64D300B2BF95 /* IK.cpp */, + 81CC3FF5092A64D300B2BF95 /* IK.h */, + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */, + 81CC3FF7092A64D300B2BF95 /* Instance.h */, + 81CC3FF8092A64D300B2BF95 /* Item.cpp */, + 81CC3FF9092A64D300B2BF95 /* Item.h */, + 81CC3FFA092A64D300B2BF95 /* Light.cpp */, + 81CC3FFB092A64D300B2BF95 /* Light.h */, + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */, + 81CC3FFD092A64D300B2BF95 /* LipSync.h */, + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */, + 81CC3FFF092A64D300B2BF95 /* Misc.h */, + 81CC4000092A64D300B2BF95 /* Moveable.cpp */, + 81CC4001092A64D300B2BF95 /* Moveable.h */, + 81CC4002092A64D300B2BF95 /* Mover.cpp */, + 81CC4003092A64D300B2BF95 /* Mover.h */, + 81CC4004092A64D300B2BF95 /* mp */, + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */, + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */, + 81CC4015092A64D300B2BF95 /* physics */, + 81CC403C092A64D300B2BF95 /* Playback.cpp */, + 81CC403D092A64D300B2BF95 /* Player.cpp */, + 81CC403E092A64D300B2BF95 /* Player.h */, + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */, + 81CC4040092A64D300B2BF95 /* Player_States.cpp */, + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */, + 81CC4042092A64D300B2BF95 /* PlayerView.h */, + 81CC4043092A64D300B2BF95 /* Projectile.cpp */, + 81CC4044092A64D300B2BF95 /* Projectile.h */, + 81CC4045092A64D300B2BF95 /* Pvs.cpp */, + 81CC4046092A64D300B2BF95 /* Pvs.h */, + 81CC4047092A64D300B2BF95 /* Resource */, + 81CC4049092A64D300B2BF95 /* rvAI */, + 81CC404B092A64D300B2BF95 /* script */, + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */, + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */, + 81CC405A092A64D300B2BF95 /* Sound.cpp */, + 81CC405B092A64D300B2BF95 /* Sound.h */, + 81CC405C092A64D300B2BF95 /* spawner.cpp */, + 81CC405D092A64D300B2BF95 /* spawner.h */, + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */, + 81CC405F092A64D300B2BF95 /* SplineMover.h */, + 81CC4060092A64D300B2BF95 /* Target.cpp */, + 81CC4061092A64D300B2BF95 /* Target.h */, + 81CC4062092A64D300B2BF95 /* TramGate.cpp */, + 81CC4063092A64D300B2BF95 /* TramGate.h */, + 81CC4064092A64D300B2BF95 /* Trigger.cpp */, + 81CC4065092A64D300B2BF95 /* Trigger.h */, + 81CC4066092A64D300B2BF95 /* vehicle */, + 81CC407C092A64D300B2BF95 /* weapon */, + 81CC4088092A64D300B2BF95 /* Weapon.cpp */, + 81CC4089092A64D300B2BF95 /* Weapon.h */, + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */, + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */, + ); + name = Source; + path = ../../../mpgame; + sourceTree = SOURCE_ROOT; + }; + 81CC3F79092A64D300B2BF95 /* ai */ = { + isa = PBXGroup; + children = ( + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */, + 81CC3F7B092A64D300B2BF95 /* AAS.h */, + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */, + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */, + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */, + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */, + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */, + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */, + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */, + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */, + 81CC3F84092A64D300B2BF95 /* AI.cpp */, + 81CC3F85092A64D300B2BF95 /* AI.h */, + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */, + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */, + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */, + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */, + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */, + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */, + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */, + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */, + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */, + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */, + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */, + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */, + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */, + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */, + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */, + 81CC3F96092A64D300B2BF95 /* AI_Util.h */, + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */, + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */, + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */, + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */, + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */, + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */, + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */, + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */, + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */, + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */, + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */, + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */, + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */, + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */, + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */, + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */, + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */, + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */, + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */, + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */, + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */, + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */, + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */, + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */, + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */, + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */, + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */, + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */, + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */, + ); + path = ai; + sourceTree = ""; + }; + 81CC3FB6092A64D300B2BF95 /* anim */ = { + isa = PBXGroup; + children = ( + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */, + 81CC3FB8092A64D300B2BF95 /* Anim.h */, + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */, + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */, + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */, + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */, + ); + path = anim; + sourceTree = ""; + }; + 81CC3FC1092A64D300B2BF95 /* client */ = { + isa = PBXGroup; + children = ( + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */, + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */, + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */, + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */, + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */, + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */, + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */, + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */, + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */, + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */, + ); + path = client; + sourceTree = ""; + }; + 81CC3FDD092A64D300B2BF95 /* gamesys */ = { + isa = PBXGroup; + children = ( + 81CC3FDF092A64D300B2BF95 /* Class.cpp */, + 81CC3FE0092A64D300B2BF95 /* Class.h */, + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */, + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */, + 81CC3FE3092A64D300B2BF95 /* Event.cpp */, + 81CC3FE4092A64D300B2BF95 /* Event.h */, + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */, + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */, + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */, + 81CC3FE8092A64D300B2BF95 /* State.cpp */, + 81CC3FE9092A64D300B2BF95 /* State.h */, + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */, + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */, + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */, + 81CC3FED092A64D300B2BF95 /* SysCvar.h */, + ); + path = gamesys; + sourceTree = ""; + }; + 81CC4004092A64D300B2BF95 /* mp */ = { + isa = PBXGroup; + children = ( + 81CC4005092A64D300B2BF95 /* CTF.cpp */, + 81CC4006092A64D300B2BF95 /* CTF.h */, + 81CC4007092A64D300B2BF95 /* GameState.cpp */, + 81CC4008092A64D300B2BF95 /* GameState.h */, + 81CC4009092A64D300B2BF95 /* stats */, + 81CC4010092A64D300B2BF95 /* Tourney.cpp */, + 81CC4011092A64D300B2BF95 /* Tourney.h */, + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */, + ); + path = mp; + sourceTree = ""; + }; + 81CC4009092A64D300B2BF95 /* stats */ = { + isa = PBXGroup; + children = ( + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */, + 81CC400B092A64D300B2BF95 /* StatEvent.h */, + 81CC400C092A64D300B2BF95 /* StatManager.cpp */, + 81CC400D092A64D300B2BF95 /* StatManager.h */, + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */, + 81CC400F092A64D300B2BF95 /* StatWindow.h */, + ); + path = stats; + sourceTree = ""; + }; + 81CC4015092A64D300B2BF95 /* physics */ = { + isa = PBXGroup; + children = ( + 81CC4016092A64D300B2BF95 /* Clip.cpp */, + 81CC4017092A64D300B2BF95 /* Clip.h */, + 81CC4018092A64D300B2BF95 /* Force.cpp */, + 81CC4019092A64D300B2BF95 /* Force.h */, + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */, + 81CC401B092A64D300B2BF95 /* Force_Constant.h */, + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */, + 81CC401D092A64D300B2BF95 /* Force_Drag.h */, + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */, + 81CC401F092A64D300B2BF95 /* Force_Field.h */, + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */, + 81CC4021092A64D300B2BF95 /* Force_Spring.h */, + 81CC4022092A64D300B2BF95 /* Physics.cpp */, + 81CC4023092A64D300B2BF95 /* Physics.h */, + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */, + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */, + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */, + 81CC4027092A64D300B2BF95 /* Physics_AF.h */, + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */, + 81CC4029092A64D300B2BF95 /* Physics_Base.h */, + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */, + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */, + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */, + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */, + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */, + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */, + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */, + 81CC4031092A64D300B2BF95 /* Physics_Player.h */, + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */, + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */, + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */, + 81CC4035092A64D300B2BF95 /* Physics_Static.h */, + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */, + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */, + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */, + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */, + 81CC403A092A64D300B2BF95 /* Push.cpp */, + 81CC403B092A64D300B2BF95 /* Push.h */, + ); + path = physics; + sourceTree = ""; + }; + 81CC4047092A64D300B2BF95 /* Resource */ = { + isa = PBXGroup; + children = ( + ); + path = Resource; + sourceTree = ""; + }; + 81CC4049092A64D300B2BF95 /* rvAI */ = { + isa = PBXGroup; + children = ( + ); + path = rvAI; + sourceTree = ""; + }; + 81CC404B092A64D300B2BF95 /* script */ = { + isa = PBXGroup; + children = ( + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */, + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */, + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */, + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */, + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */, + 81CC4051092A64D300B2BF95 /* Script_Program.h */, + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */, + 81CC4053092A64D300B2BF95 /* Script_Thread.h */, + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */, + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */, + ); + path = script; + sourceTree = ""; + }; + 81CC4066092A64D300B2BF95 /* vehicle */ = { + isa = PBXGroup; + children = ( + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */, + 81CC4068092A64D300B2BF95 /* Vehicle.h */, + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */, + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */, + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */, + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */, + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */, + 81CC406E092A64D300B2BF95 /* VehicleController.h */, + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */, + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */, + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */, + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */, + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */, + 81CC4074092A64D300B2BF95 /* VehicleParts.h */, + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */, + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */, + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */, + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */, + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */, + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */, + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */, + ); + path = vehicle; + sourceTree = ""; + }; + 81CC407C092A64D300B2BF95 /* weapon */ = { + isa = PBXGroup; + children = ( + 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */, + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */, + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */, + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */, + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */, + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */, + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */, + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */, + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */, + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */, + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */, + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */, + ); + path = weapon; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 81114DFA093E117D00A5C91A /* game bundle */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */; + buildPhases = ( + 81114DF7093E117D00A5C91A /* Resources */, + 81114DF8093E117D00A5C91A /* Sources */, + 81114DF9093E117D00A5C91A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 81114FE3093E189800A5C91A /* PBXTargetDependency */, + ); + name = "game bundle"; + productName = "game bundle"; + productReference = 81114DFB093E117D00A5C91A /* game.so.bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "mpgame" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* game */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 8114A19D090D686B0025E084 /* Products */; + ProjectRef = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + }, + ); + targets = ( + 81114DFA093E117D00A5C91A /* game bundle */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 8114A1A2090D686B0025E084 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 81114DF7093E117D00A5C91A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 816AD2E00981819D00CAC29C /* Game.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 81114DF8093E117D00A5C91A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */, + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */, + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */, + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */, + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */, + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */, + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */, + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */, + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */, + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */, + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */, + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */, + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */, + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */, + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */, + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */, + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */, + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */, + 81114FF8093E194300A5C91A /* AF.cpp in Sources */, + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */, + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */, + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */, + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */, + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */, + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */, + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */, + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */, + 81115001093E194300A5C91A /* Effect.cpp in Sources */, + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */, + 81115003093E194300A5C91A /* Player_States.cpp in Sources */, + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */, + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */, + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */, + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */, + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */, + 81115009093E194300A5C91A /* State.cpp in Sources */, + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */, + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */, + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */, + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */, + 8111500E093E194300A5C91A /* Mover.cpp in Sources */, + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */, + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */, + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */, + 81115012093E194300A5C91A /* Push.cpp in Sources */, + 81115013093E194300A5C91A /* IconManager.cpp in Sources */, + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */, + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */, + 81115016093E194300A5C91A /* Moveable.cpp in Sources */, + 81115017093E194300A5C91A /* Clip.cpp in Sources */, + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */, + 81115019093E194300A5C91A /* Target.cpp in Sources */, + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */, + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */, + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */, + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */, + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */, + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */, + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */, + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */, + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */, + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */, + 81115024093E194300A5C91A /* Playback.cpp in Sources */, + 81115025093E194300A5C91A /* Force.cpp in Sources */, + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */, + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */, + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */, + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */, + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */, + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */, + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */, + 8111502D093E194300A5C91A /* Player.cpp in Sources */, + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */, + 8111502F093E194300A5C91A /* IK.cpp in Sources */, + 81115030093E194300A5C91A /* Projectile.cpp in Sources */, + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */, + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */, + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */, + 81115034093E194300A5C91A /* Pvs.cpp in Sources */, + 81115035093E194300A5C91A /* Light.cpp in Sources */, + 81115036093E194300A5C91A /* Entity.cpp in Sources */, + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */, + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */, + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */, + 8111503A093E194300A5C91A /* AAS.cpp in Sources */, + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */, + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */, + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */, + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */, + 81115040093E194300A5C91A /* AI.cpp in Sources */, + 81115041093E194300A5C91A /* Tourney.cpp in Sources */, + 81115042093E194300A5C91A /* TramGate.cpp in Sources */, + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */, + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */, + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */, + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */, + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */, + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */, + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */, + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */, + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */, + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */, + 8111504D093E194300A5C91A /* Event.cpp in Sources */, + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */, + 8111504F093E194300A5C91A /* CTF.cpp in Sources */, + 81115050093E194300A5C91A /* GameState.cpp in Sources */, + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */, + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */, + 81115053093E194300A5C91A /* Physics.cpp in Sources */, + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */, + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */, + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */, + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */, + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */, + 81115059093E194300A5C91A /* Class.cpp in Sources */, + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */, + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */, + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */, + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */, + 8111505E093E194300A5C91A /* Misc.cpp in Sources */, + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */, + 81115060093E194300A5C91A /* Icon.cpp in Sources */, + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */, + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */, + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */, + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */, + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */, + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */, + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */, + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */, + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */, + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */, + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */, + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */, + 8111506D093E194300A5C91A /* Anim.cpp in Sources */, + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */, + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */, + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */, + 81115071093E194300A5C91A /* spawner.cpp in Sources */, + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */, + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */, + 81115074093E194300A5C91A /* LipSync.cpp in Sources */, + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */, + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */, + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */, + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */, + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */, + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */, + 8111507B093E194300A5C91A /* Actor.cpp in Sources */, + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */, + 8111507D093E194300A5C91A /* Item.cpp in Sources */, + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */, + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */, + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */, + 81115081093E194300A5C91A /* Instance.cpp in Sources */, + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */, + 2CCDAFD60A2CD52800D4AD93 /* Buying.cpp in Sources */, + 2CE8A5580A2CD90C00929ACB /* WeaponNapalmGun.cpp in Sources */, + 255BA9290BB85D6C00D112AC /* Lagometer.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 81114FE3093E189800A5C91A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = idlib_pic; + targetProxy = 81114FE2093E189800A5C91A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 81114DFE093E117E00A5C91A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)/q4mp"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_CFLAGS = "-fvisibility=hidden"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81114DFF093E117E00A5C91A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)/q4mp"; + COPY_PHASE_STRIP = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149F3C090D5FDC0025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + GAME_MPAPI, + MACOS_X, + _DEBUG, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149F3D090D5FDC0025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + GAME_MPAPI, + MACOS_X, + _FINAL, + GAME_DLL, + Q4SDK, + ); + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81114DFE093E117E00A5C91A /* Debug */, + 81114DFF093E117E00A5C91A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "mpgame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149F3C090D5FDC0025E084 /* Debug */, + 81149F3D090D5FDC0025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/source/sys/osx/apple_bool.h b/source/sys/osx/apple_bool.h new file mode 100644 index 0000000..9060de9 --- /dev/null +++ b/source/sys/osx/apple_bool.h @@ -0,0 +1,18 @@ + +// With GCC and xCode, a bool is 4 bytes. There are two approaches to +// making bool one byte. You could #define bool as an unsigned char. The downside +// is that a function that take bool and one that takes a char will no longer have a unique function +// signature. You could #define bool to custom class that defines a bool operator, which +// fixes the function signature but includes others problems such as bool bitfields, +// classes that have a bool operator, C function that accept ... as a param, the keyword +// volatile. + +// The following approach works for the best for Doom because of the above issues, bitfields +// especially + +#ifdef bool +#undef bool +#endif + +#define bool unsigned char + diff --git a/source/sys/osx/q4sdk.xcodeproj/project.pbxproj b/source/sys/osx/q4sdk.xcodeproj/project.pbxproj new file mode 100644 index 0000000..982aa04 --- /dev/null +++ b/source/sys/osx/q4sdk.xcodeproj/project.pbxproj @@ -0,0 +1,231 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXAggregateTarget section */ + 251B05430C186CC600504968 /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 251B05440C186CEA00504968 /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + 251B05480C186CF700504968 /* PBXTargetDependency */, + 251B054A0C186CFB00504968 /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXContainerItemProxy section */ + 25136BA80A473DEA00D3D311 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 81114DFB093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 25136BAC0A473DEA00D3D311 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib_pic; + }; + 251B05410C186CBA00504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 81114DFB093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 251B05470C186CF700504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 81114DFA093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 251B05490C186CFB00504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 81114DFA093E117D00A5C91A; + remoteInfo = "game bundle"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 25136B9B0A473DEA00D3D311 /* game.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = game.xcodeproj; path = Subprojects/game.xcodeproj; sourceTree = ""; }; + 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = idlib.xcodeproj; path = Subprojects/idlib.xcodeproj; sourceTree = ""; }; + 251B05360C186CBA00504968 /* mpgame.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = mpgame.xcodeproj; path = Subprojects/mpgame.xcodeproj; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 25136B850A473D9D00D3D311 = { + isa = PBXGroup; + children = ( + 251B05360C186CBA00504968 /* mpgame.xcodeproj */, + 25136B9B0A473DEA00D3D311 /* game.xcodeproj */, + 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */, + ); + sourceTree = ""; + }; + 25136B9C0A473DEA00D3D311 /* Products */ = { + isa = PBXGroup; + children = ( + 25136BA90A473DEA00D3D311 /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 25136B9F0A473DEA00D3D311 /* Products */ = { + isa = PBXGroup; + children = ( + 25136BAD0A473DEA00D3D311 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 251B05370C186CBA00504968 /* Products */ = { + isa = PBXGroup; + children = ( + 251B05420C186CBA00504968 /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXProject section */ + 25136B870A473D9D00D3D311 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 25136B880A473D9D00D3D311 /* Build configuration list for PBXProject "q4sdk" */; + hasScannedForEncodings = 0; + mainGroup = 25136B850A473D9D00D3D311; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 25136B9C0A473DEA00D3D311 /* Products */; + ProjectRef = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + }, + { + ProductGroup = 25136B9F0A473DEA00D3D311 /* Products */; + ProjectRef = 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */; + }, + { + ProductGroup = 251B05370C186CBA00504968 /* Products */; + ProjectRef = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + }, + ); + targets = ( + 251B05430C186CC600504968 /* All */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 25136BA90A473DEA00D3D311 /* game.so.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = game.so.bundle; + remoteRef = 25136BA80A473DEA00D3D311 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 25136BAD0A473DEA00D3D311 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 25136BAC0A473DEA00D3D311 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 251B05420C186CBA00504968 /* game.so.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = game.so.bundle; + remoteRef = 251B05410C186CBA00504968 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXTargetDependency section */ + 251B05480C186CF700504968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "game bundle"; + targetProxy = 251B05470C186CF700504968 /* PBXContainerItemProxy */; + }; + 251B054A0C186CFB00504968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "game bundle"; + targetProxy = 251B05490C186CFB00504968 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 25136B890A473D9D00D3D311 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Debug; + }; + 25136B8A0A473D9D00D3D311 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; + 251B05450C186CEA00504968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = All; + }; + name = Debug; + }; + 251B05460C186CEA00504968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + PRODUCT_NAME = All; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 25136B880A473D9D00D3D311 /* Build configuration list for PBXProject "q4sdk" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 25136B890A473D9D00D3D311 /* Debug */, + 25136B8A0A473D9D00D3D311 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 251B05440C186CEA00504968 /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 251B05450C186CEA00504968 /* Debug */, + 251B05460C186CEA00504968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 25136B870A473D9D00D3D311 /* Project object */; +} diff --git a/source/sys/scons/SConscript.game b/source/sys/scons/SConscript.game new file mode 100644 index 0000000..f3fa6ec --- /dev/null +++ b/source/sys/scons/SConscript.game @@ -0,0 +1,44 @@ +# -*- mode: python -*- +# Quake4 build script +# TTimo +# http://scons.sourceforge.net + +import sys, os +import scons_utils + +Import( 'GLOBALS' ) +Import( GLOBALS ) + +if ( local_mpgame == 1 ): + proj_list = scons_utils.ExtractSource( File( '#mpgame.vcproj' ).abspath ) +else: + proj_list = scons_utils.ExtractSource( File( '#game.vcproj' ).abspath ) + +for i in range( len( proj_list ) ): + proj_list[ i ] = '../../' + proj_list[ i ] + +local_env = g_game_env.Copy() + +if ( local_mpgame == 1 ): + local_env.Append( CPPDEFINES = [ 'GAME_MPAPI' ] ) + +if ( local_demo == 1 ): + local_env.Append( CPPDEFINES = [ 'ID_DEMO_BUILD' ] ) + +if ( local_gamedll == 1 ): + local_env.Append( CPPDEFINES = [ 'GAME_DLL' ] ) + if ( local_mpgame == 1 ): + ret = local_env.SharedLibrarySafe( local_env, 'mpgame', proj_list + idlib_objects ) + else: + ret = local_env.SharedLibrarySafe( local_env, 'game', proj_list + idlib_objects ) + if ( OSX_BUILDSTYLE == '2' ): + local_env.Append( CPPFLAGS = [ '-fvisibility=hidden' ] ) + Return( 'ret' ) +else: + local_env.Append( CPPDEFINES = [ 'GAME_MONO' ] ) + ret_list = [] + for i in proj_list: + ret_list += local_env.StaticObject( source = i ) + Return( 'ret_list' ) + + diff --git a/source/sys/scons/SConscript.idlib b/source/sys/scons/SConscript.idlib new file mode 100644 index 0000000..cef5b7e --- /dev/null +++ b/source/sys/scons/SConscript.idlib @@ -0,0 +1,41 @@ +# -*- mode: python -*- +# Quake4 build script +# TTimo +# http://scons.sourceforge.net + +import os.path, scons_utils + +Import( 'GLOBALS' ) +Import( GLOBALS ) + +idlib_list = scons_utils.ExtractSource( File( '#idlib.vcproj' ).abspath ) +asm_files = [ 'idlib/math/Simd_MMX.cpp' ] + +for i in asm_files: + idlib_list.remove( i ) + +local_env = g_env.Copy() +if ( local_smp == 1 and local_idlibpic == 0 ): + # idlib compiled for SMP engine code needs the define + local_env.Append( CPPDEFINES = [ 'ENABLE_INTEL_SMP' ] ) +if ( GCC_X86_ASM == '1' ): + local_env.Append( CPPDEFINES = [ 'ID_GCC_X86_ASM' ] ) + +with_asm_env = g_env_noopt.Copy() +with_asm_env.Append( CPPFLAGS = [ '-masm=intel' ] ) + +ret_list = [] +for f in idlib_list: + if ( local_idlibpic == 0 ): + ret_list += local_env.StaticObject( source = os.path.join( '../..', f ) ) + else: + ret_list += local_env.SharedObject( source = os.path.join( '../..', f ) ) + +if ( GCC_X86_ASM == '1' ): + for f in asm_files: + if ( local_idlibpic == 0 ): + ret_list += with_asm_env.StaticObject( source = os.path.join( '../..', f ) ) + else: + ret_list += with_asm_env.SharedObject( source = os.path.join( '../..', f ) ) + +Return( 'ret_list' ) diff --git a/source/sys/scons/scons_utils.py b/source/sys/scons/scons_utils.py new file mode 100644 index 0000000..5d3c301 --- /dev/null +++ b/source/sys/scons/scons_utils.py @@ -0,0 +1,194 @@ +# -*- mode: python -*- +import sys, os, string, time, commands, re, pickle, StringIO, popen2, commands, pdb, zipfile +import SCons + +# need an Environment and a matching buffered_spawn API .. encapsulate +class idBuffering: + + silent = False + + def buffered_spawn( self, sh, escape, cmd, args, env ): + stderr = StringIO.StringIO() + stdout = StringIO.StringIO() + command_string = '' + for i in args: + if ( len( command_string ) ): + command_string += ' ' + command_string += i + try: + retval = self.env['PSPAWN']( sh, escape, cmd, args, env, stdout, stderr ) + except OSError, x: + if x.errno != 10: + raise x + print 'OSError ignored on command: %s' % command_string + retval = 0 + print command_string + if ( retval != 0 or not self.silent ): + sys.stdout.write( stdout.getvalue() ) + sys.stderr.write( stderr.getvalue() ) + return retval + +class idSetupBase: + + def SimpleCommand( self, cmd ): + print cmd + ret = commands.getstatusoutput( cmd ) + if ( len( ret[ 1 ] ) ): + sys.stdout.write( ret[ 1 ] ) + sys.stdout.write( '\n' ) + if ( ret[ 0 ] != 0 ): + raise 'command failed' + return ret[ 1 ] + + def TrySimpleCommand( self, cmd ): + print cmd + ret = commands.getstatusoutput( cmd ) + sys.stdout.write( ret[ 1 ] ) + + def M4Processing( self, file, d ): + file_out = file[:-3] + cmd = 'm4 ' + for ( key, val ) in d.items(): + cmd += '--define=%s="%s" ' % ( key, val ) + cmd += '%s > %s' % ( file, file_out ) + self.SimpleCommand( cmd ) + + def ExtractProtocolVersion( self ): + f = open( 'framework/Licensee.h' ) + l = f.readlines() + f.close() + + major = 'X' + p = re.compile( '^#define ASYNC_PROTOCOL_MAJOR\t*(.*)' ) + for i in l: + if ( p.match( i ) ): + major = p.match( i ).group(1) + break + + f = open( 'framework/async/AsyncNetwork.h' ) + l = f.readlines() + f.close() + + minor = 'X' + p = re.compile( '^const int ASYNC_PROTOCOL_MINOR\t*= (.*);' ) + for i in l: + if ( p.match( i ) ): + minor = p.match( i ).group(1) + break + + return '%s.%s' % ( major, minor ) + + def ExtractEngineVersion( self ): + f = open( 'framework/Licensee.h' ) + l = f.readlines() + f.close() + + version = 'X' + p = re.compile( '^#define.*ENGINE_VERSION\t*"DOOM (.*)"' ) + for i in l: + if ( p.match( i ) ): + version = p.match( i ).group(1) + break + + return version + + def ExtractBuildVersion( self ): + f = open( 'framework/BuildVersion.h' ) + l = f.readlines()[ 4 ] + f.close() + pat = re.compile( '.* = (.*);\n' ) + return pat.split( l )[ 1 ] + +def checkLDD( target, source, env ): + file = target[0] + if (not os.path.isfile(file.abspath)): + print('ERROR: CheckLDD: target %s not found\n' % target[0]) + Exit(1) + ( status, output ) = commands.getstatusoutput( 'ldd -r %s' % file ) + if ( status != 0 ): + print 'ERROR: ldd command returned with exit code %d' % ldd_ret + os.system( 'rm %s' % target[ 0 ] ) + sys.exit(1) + lines = string.split( output, '\n' ) + have_undef = 0 + for i_line in lines: + #print repr(i_line) + regex = re.compile('undefined symbol: (.*)\t\\((.*)\\)') + if ( regex.match(i_line) ): + symbol = regex.sub('\\1', i_line) + try: + env['ALLOWED_SYMBOLS'].index(symbol) + except: + have_undef = 1 + if ( have_undef ): + print output + print "ERROR: undefined symbols" + os.system('rm %s' % target[0]) + sys.exit(1) + +def SharedLibrarySafe( env, target, source ): + ret = env.SharedLibrary( target, source ) + if ( env['OS'] != 'Darwin' ): + env.AddPostAction( ret, checkLDD ) + return ret + +def NotImplementedStub( *whatever ): + print 'Not Implemented' + sys.exit( 1 ) + +# -------------------------------------------------------------------- + +# get a clean error output when running multiple jobs +def SetupBufferedOutput( env, silent ): + buf = idBuffering() + buf.silent = silent + buf.env = env + env['SPAWN'] = buf.buffered_spawn + +# setup utilities on an environement +def SetupUtils( env ): + env.SharedLibrarySafe = SharedLibrarySafe + if ( os.path.exists( 'sys/scons/SDK.py' ) ): + import SDK + sdk = SDK.idSDK() + env.BuildSDK = sdk.BuildSDK + else: + env.BuildSDK = NotImplementedStub + + if ( os.path.exists( 'sys/scons/Setup.py' ) ): + import Setup + setup = Setup.idSetup() + env.Prepare = setup.Prepare + env.BuildSetup = setup.BuildSetup + env.BuildGamePak = setup.BuildGamePak + else: + env.Prepare = NotImplementedStub + env.BuildSetup = NotImplementedStub + env.BuildGamePak = NotImplementedStub + + if ( os.path.exists( 'sys/scons/OSX.py' ) ): + import OSX + OSX = OSX.idOSX() + env.BuildBundle = OSX.BuildBundle + else: + env.BuildBundle = NotImplementedStub + +def BuildList( s_prefix, s_string ): + s_list = string.split( s_string ) + for i in range( len( s_list ) ): + s_list[ i ] = s_prefix + '/' + s_list[ i ] + return s_list + +def ExtractSource( file ): + from xml.dom.minidom import parse + dom = parse( file ) + files = dom.getElementsByTagName( 'File' ) + l = [] + for i in files: + s = i.getAttribute( 'RelativePath' ) + s = s.encode('ascii', 'ignore') + s = re.sub( '\\\\', '/', s ) + s = re.sub( '^\./', '', s ) + if ( string.lower( s[-4:] ) == '.cpp' or string.lower( s[-2:] ) == '.c' ): + l.append( s ) + return l diff --git a/source/sys/sys_public.h b/source/sys/sys_public.h new file mode 100644 index 0000000..71e97a7 --- /dev/null +++ b/source/sys/sys_public.h @@ -0,0 +1,943 @@ + +#ifndef __SYS_PUBLIC__ +#define __SYS_PUBLIC__ + + +/* +=============================================================================== + + Non-portable system services. + +=============================================================================== +*/ + + +// Win32 +#ifdef _WINDOWS + +#define BUILD_STRING "win-x86" +#define BUILD_OS_ID 0 +#define CPUSTRING "x86" +#define CPU_EASYARGS 1 + +#define ALIGN16( x ) __declspec(align(16)) x +#define PACKED + +#define _alloca16( x ) ((void *)((((int)_alloca( (x)+15 )) + 15) & ~15)) + +#define ID_INLINE __forceinline +#define ID_STATIC_TEMPLATE static + +#define assertmem( x, y ) assert( _CrtIsValidPointer( x, y, true ) ) + +#endif + +#ifdef __GNUC__ +#define id_attribute(x) __attribute__(x) +#else +#define id_attribute(x) +#endif + +// Mac OSX +#if defined(MACOS_X) || defined(__APPLE__) + +#include + +#if __GNUC__ < 4 +#include "osx/apple_bool.h" +#endif + +#define BUILD_STRING "MacOSX-universal" +#define BUILD_OS_ID 1 +#ifdef __ppc__ + #define CPUSTRING "ppc" + #define CPU_EASYARGS 0 +#elif defined(__i386__) + #define CPUSTRING "x86" + #define CPU_EASYARGS 1 +#endif + +#define ALIGN16( x ) x __attribute__ ((aligned (16))) +#ifdef __MWERKS__ +#define PACKED +#else +#define PACKED __attribute__((packed)) +#endif + +#define _alloca alloca +#define _alloca16( x ) ((void *)((((int)alloca( (x)+15 )) + 15) & ~15)) + +#define __cdecl +#define ASSERT assert + +#define ID_STATIC_TEMPLATE + +#define assertmem( x, y ) + +#endif + + +// Linux +#ifdef __linux__ + +#ifdef __i386__ + #define BUILD_STRING "linux-x86" + #define BUILD_OS_ID 2 + #define CPUSTRING "x86" + #define CPU_EASYARGS 1 +#elif defined(__ppc__) + #define BUILD_STRING "linux-ppc" + #define CPUSTRING "ppc" + #define CPU_EASYARGS 0 +#endif + +#define _alloca alloca +#define _alloca16( x ) ((void *)((((int)alloca( (x)+15 )) + 15) & ~15)) + +#define ALIGN16( x ) x +#define PACKED __attribute__((packed)) + +#define __cdecl +#define ASSERT assert + +#define ID_INLINE inline +#define ID_STATIC_TEMPLATE + +#define assertmem( x, y ) + +#endif + +typedef enum { + CPUID_NONE = 0x00000, + CPUID_UNSUPPORTED = 0x00001, // unsupported (386/486) + CPUID_GENERIC = 0x00002, // unrecognized processor + CPUID_INTEL = 0x00004, // Intel + CPUID_AMD = 0x00008, // AMD + CPUID_MMX = 0x00010, // Multi Media Extensions + CPUID_3DNOW = 0x00020, // 3DNow! + CPUID_SSE = 0x00040, // Streaming SIMD Extensions + CPUID_SSE2 = 0x00080, // Streaming SIMD Extensions 2 + CPUID_SSE3 = 0x00100, // Streaming SIMD Extentions 3 aka Prescott's New Instructions + CPUID_ALTIVEC = 0x00200, // AltiVec + CPUID_HTT = 0x01000, // Hyper-Threading Technology + CPUID_CMOV = 0x02000, // Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions + CPUID_FTZ = 0x04000, // Flush-To-Zero mode (denormal results are flushed to zero) + CPUID_DAZ = 0x08000, // Denormals-Are-Zero mode (denormal source operands are set to zero) + CPUID_EM64T = 0x10000, // 64-bit Memory Extensions + CPUID_PENTIUMM = 0x20000, // Pentium M technology - high performance per MHz +#ifdef MACOS_X + CPUID_PPC = 0x40000, // PowerPC G4/G5 +#endif +// RAVEN BEGIN + CPUID_XENON = 0x80000 // Xenon PPC processor +// RAVEN END +} cpuid_t; + +typedef enum { + FPU_EXCEPTION_INVALID_OPERATION = 1, + FPU_EXCEPTION_DENORMALIZED_OPERAND = 2, + FPU_EXCEPTION_DIVIDE_BY_ZERO = 4, + FPU_EXCEPTION_NUMERIC_OVERFLOW = 8, + FPU_EXCEPTION_NUMERIC_UNDERFLOW = 16, + FPU_EXCEPTION_INEXACT_RESULT = 32 +} fpuExceptions_t; + +typedef enum { + FPU_PRECISION_SINGLE = 0, + FPU_PRECISION_DOUBLE = 1, + FPU_PRECISION_DOUBLE_EXTENDED = 2 +} fpuPrecision_t; + +typedef enum { + FPU_ROUNDING_TO_NEAREST = 0, + FPU_ROUNDING_DOWN = 1, + FPU_ROUNDING_UP = 2, + FPU_ROUNDING_TO_ZERO = 3 +} fpuRounding_t; + +typedef enum { + SE_NONE, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE // evPtr is a char*, from typing something at a non-game console +// RAVEN BEGIN +// rjohnson: debug event overflow stuff + , + SE_MAX +// RAVEN END +} sysEventType_t; + +typedef enum { + M_ACTION1, + M_ACTION2, + M_ACTION3, + M_ACTION4, + M_ACTION5, + M_ACTION6, + M_ACTION7, + M_ACTION8, + M_DELTAX, + M_DELTAY, + M_DELTAZ +} sys_mEvents; + +// RAVEN BEGIN +// rjohnson: new joystick code +#define MAX_AXIS_RANGE 127 +#define JOY_TO_CURSOR_SPEED ( idMath::M_MS2SEC * common->GetUserCmdMSec() * 1.5f * 140.f ) + +typedef enum { + AXIS_LEFT_HORIZONTAL, + AXIS_LEFT_VERTICAL, + AXIS_RIGHT_HORIZONTAL, + AXIS_RIGHT_VERTICAL, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +typedef enum { + J_ACTION_BUTTON_LEFT_SHOULDER, // K_JOY1 + J_ACTION_BUTTON_RIGHT_SHOULDER, // K_JOY2 + J_ACTION_BUTTON_A, // K_JOY3 + J_ACTION_BUTTON_B, // K_JOY4 + J_ACTION_BUTTON_Y, // K_JOY5 + J_ACTION_BUTTON_X, // K_JOY6 + J_ACTION_BUTTON_START, // K_JOY7 + J_ACTION_BUTTON_BACK, // K_JOY8 + J_ACTION_BUTTON_DPAD_UP, // K_JOY9 + J_ACTION_BUTTON_DPAD_DOWN, // K_JOY10 + J_ACTION_BUTTON_DPAD_RIGHT, // K_JOY11 + J_ACTION_BUTTON_DPAD_LEFT, // K_JOY12 + J_ACTION_BUTTON_AXIS_LEFT, // K_JOY13 + J_ACTION_BUTTON_AXIS_RIGHT, // K_JOY14 + J_ACTION_BUTTON_LEFT_TRIGGER, // K_JOY16 + J_ACTION_BUTTON_RIGHT_TRIGGER, // K_JOY15 + + J_DELTA_LEFT_HORIZONTAL, + J_DELTA_LEFT_VERTICAL, + J_DELTA_RIGHT_HORIZONTAL, + J_DELTA_RIGHT_VERTICAL, + + J_ACTION_BUTTON_GARBAGE, +} sys_jEvents; +// RAVEN END + +typedef struct sysEvent_s { + sysEventType_t evType; + int evValue; + int evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void * evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +typedef struct sysMemoryStats_s { + int memoryLoad; + int totalPhysical; + int availPhysical; + int totalPageFile; + int availPageFile; + int totalVirtual; + int availVirtual; + int availExtendedVirtual; +} sysMemoryStats_t; + +typedef unsigned long address_t; + +template class idList; // for Sys_ListFiles + +struct sysTime_t { + int tm_sec; /* seconds after the minute - [0,59] */ + int tm_min; /* minutes after the hour - [0,59] */ + int tm_hour; /* hours since midnight - [0,23] */ + int tm_mday; /* day of the month - [1,31] */ + int tm_mon; /* months since January - [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - [0,6] */ + int tm_yday; /* days since January 1 - [0,365] */ + int tm_isdst; /* daylight savings time flag */ +}; + +void Sys_Init( void ); +void Sys_Shutdown( void ); +void Sys_Error( const char *error, ...); +void Sys_Quit( void ); + +bool Sys_AlreadyRunning( void ); + +// note that this isn't journaled... +char * Sys_GetClipboardData( void ); +void Sys_SetClipboardData( const char *string ); + +// will go to the various text consoles +// NOT thread safe - never use in the async paths +void Sys_Printf( const char *msg, ... )id_attribute((format(printf,1,2))); + +// guaranteed to be thread-safe +void Sys_DebugPrintf( const char *fmt, ... )id_attribute((format(printf,1,2))); +void Sys_DebugVPrintf( const char *fmt, va_list arg ); + +// a decent minimum sleep time to avoid going below the process scheduler speeds +#define SYS_MINSLEEP 20 + +// allow game to yield CPU time +// NOTE: due to SYS_MINSLEEP this is very bad portability karma, and should be completely removed +void Sys_Sleep( int msec ); + +// returns whether the main rendering window has focus +bool Sys_IsAppActive( void ); + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds( void ); + +// for accurate performance testing +double Sys_GetClockTicks( void ); +double Sys_ClockTicksPerSecond( void ); + +// returns a selection of the CPUID_* flags +cpuid_t Sys_GetProcessorId( void ); +const char * Sys_GetProcessorString( void ); + +// returns true if the FPU stack is empty +bool Sys_FPU_StackIsEmpty( void ); + +// empties the FPU stack +void Sys_FPU_ClearStack( void ); + +// returns the FPU state as a string +const char * Sys_FPU_GetState( void ); + +// enables the given FPU exceptions +void Sys_FPU_EnableExceptions( int exceptions ); + +// sets the FPU precision +void Sys_FPU_SetPrecision( int precision ); + +// sets the FPU rounding mode +void Sys_FPU_SetRounding( int rounding ); + +// sets Flush-To-Zero mode (only available when CPUID_FTZ is set) +void Sys_FPU_SetFTZ( bool enable ); + +// sets Denormals-Are-Zero mode (only available when CPUID_DAZ is set) +void Sys_FPU_SetDAZ( bool enable ); + +// returns amount of system ram +int Sys_GetSystemRam( void ); + +// returns amount of video ram +int Sys_GetVideoRam( void ); + +// returns amount of drive space in path +int Sys_GetDriveFreeSpace( const char *path ); + +// returns memory stats +void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ); +void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats ); + +// lock and unlock memory +bool Sys_LockMemory( void *ptr, int bytes ); +bool Sys_UnlockMemory( void *ptr, int bytes ); + +// set amount of physical work memory +void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ); + +// allows retrieving the call stack at execution points +void Sys_GetCallStack( address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackCurStr( int depth ); +const char * Sys_GetCallStackCurAddressStr( int depth ); +void Sys_ShutdownSymbols( void ); + +#ifdef _LOAD_DLL +// DLL loading, the path should be a fully qualified OS path to the DLL file to be loaded +int Sys_DLL_Load( const char *dllName ); +void * Sys_DLL_GetProcAddress( int dllHandle, const char *procName ); +void Sys_DLL_Unload( int dllHandle ); +#endif // _LOAD_DLL + +// event generation +void Sys_GenerateEvents( void ); +sysEvent_t Sys_GetEvent( void ); +void Sys_ClearEvents( void ); + +// input is tied to windows, so it needs to be started up and shut down whenever +// the main window is recreated +void Sys_InitInput( void ); +void Sys_ShutdownInput( void ); +// keyboard input polling +int Sys_PollKeyboardInputEvents( void ); +int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ); +void Sys_EndKeyboardInputEvents( void ); +int Sys_MapKey( unsigned long key, unsigned short wParam ); + +// mouse input polling +int Sys_PollMouseInputEvents( void ); +int Sys_ReturnMouseInputEvent( const int n, int &action, int &value ); +void Sys_EndMouseInputEvents( void ); + +// RAVEN BEGIN +// ksergent: joystick input polling +int Sys_PollJoystickInputEvents( void ); +bool Sys_IsJoystickEnabled( void ); +bool Sys_IsJoystickConnected( int index ); +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ); +void Sys_EndJoystickInputEvents( void ); +// RAVEN END + +// when the console is down, or the game is about to perform a lengthy +// operation like map loading, the system can release the mouse cursor +// when in windowed mode +bool Sys_IsWindowVisible( void ); +void Sys_Mkdir( const char *path ); + +// RAVEN BEGIN +// jscott: thread handling +void Sys_StartAsyncThread( void ); +void Sys_EndAsyncThread( void ); + +// jscott: VTune interface +#ifndef _FINAL +void Sys_StartProfiling( void ); +void Sys_StopProfiling( void ); +#endif +// RAVEN END + +// NOTE: do we need to guarantee the same output on all platforms? +const char * Sys_TimeStampToStr( long timeStamp ); +const char * Sys_DefaultCDPath( void ); +const char * Sys_DefaultBasePath( void ); +const char * Sys_DefaultSavePath( void ); +const char * Sys_EXEPath( void ); + +// for getting current system (real world) time +int Sys_RealTime( sysTime_t* sysTime ); + +// use fs_debug to verbose Sys_ListFiles +// returns -1 if directory was not found (the list is cleared) +int Sys_ListFiles( const char *directory, const char *extension, idList &list ); + +// RAVEN BEGIN +// rjohnson: added block +bool Sys_AppShouldSleep ( void ); +// RAVEN END + +/* +============================================================== + + Networking + +============================================================== +*/ + +typedef enum { + NA_BAD, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, +// RAVEN BEGIN +// rjohnson: add fake clients + NA_FAKE, +// RAVEN END + NA_GAME, // bots, etc +} netadrtype_t; + +typedef struct { + netadrtype_t type; + unsigned char ip[4]; + unsigned short port; +} netadr_t; + +#define PORT_ANY -1 + +class idPort { +public: + idPort(); // this just zeros netSocket and port + ~idPort(); + + // if the InitForPort fails, the idPort.port field will remain 0 +// RAVEN BEGIN +// asalmon: option for xbox to create a VDP socket +#ifdef _XBOX + bool InitForPort( int portNumber, bool vdp = false ); +#else + bool InitForPort( int portNumber ); +#endif +// RAVEN END + int GetPort( void ) const { return port; } +// RAVEN BEGIN +// amccarthy: For Xbox this needs to be an unsigned int +#ifdef _XBOX + unsigned int GetSocket( void ) const { return netSocket; } +#endif +// RAVEN END + void Close(); + + bool GetPacket( netadr_t &from, void *data, int &size, int maxSize ); + bool GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, int timeout ); + void SendPacket( const netadr_t to, const void *data, int size ); + +//RAVEN BEGIN +//asalmon: second version of sendPacket for Xbox avoids netadr_t +#ifdef _XBOX + bool SendPacketVDP( const struct sockaddr *to, const void *data, int size ); +#endif +//RAVEN END + + void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + + void SetSilent( bool silent ); + bool GetSilent( void ) const; + +private: + int packetsRead; + int bytesRead; + + int packetsWritten; + int bytesWritten; + + int port; // UDP port +//RAVEN BEGIN +//amccarthy: For Xbox this needs to be an unsigned int +#ifdef _XBOX + unsigned int netSocket; // OS specific socket +#else + int netSocket; +#endif +//RAVEN END + + bool silent; // don't emit anything for a while +}; + +/* +=============== +idPort::GetTrafficStats +=============== +*/ +ID_INLINE void idPort::GetTrafficStats( int &_bytesSent, int &_packetsSent, int &_bytesReceived, int &_packetsReceived ) const { + _bytesSent = bytesWritten; + _packetsSent = packetsWritten; + _bytesReceived = bytesRead; + _packetsReceived = packetsRead; +} + +/* +=============== +idPort::SetSilent +=============== +*/ +ID_INLINE void idPort::SetSilent( bool _silent ) { silent = _silent; } + +/* +=============== +idPort::GetSilent +=============== +*/ +ID_INLINE bool idPort::GetSilent( void ) const { return silent; } + +class idTCP; +class idTCPServer; + +const int IDPOLL_READ = (1<<0); +const int IDPOLL_WRITE = (1<<1); +const int IDPOLL_ERROR = (1<<2); + +class idPoller { +public: + idPoller(); + + void Clear( void ); + + // will replace existing entries + void Add( int fd, int which = IDPOLL_READ ); + void Add( const idTCP &tcp, int which = IDPOLL_READ ); + void Add( const idTCPServer &tcp, int which = IDPOLL_READ ); + + void Remove( int fd ) { Add(fd, 0); } + void Remove( const idTCP &tcp ) { Add(tcp, 0); } + void Remove( const idTCPServer &serv ) { Add(serv, 0); } + + // returns IDPOLL_ flags + int Check( int fd ); + int Check( const idTCP &tcp ); + int Check( const idTCPServer &serv ); + + // returns the number of fds set, timeout is in ms, <0 is forever + int Poll( int timeout = -1 ); + +private: + int max_fd; + fd_set readfds, writefds, exceptfds; + fd_set rreadfds, rwritefds, rexceptfds; +}; + +class idTCPServer { +public: + idTCPServer(); + virtual ~idTCPServer(); + + bool Listen( const char *net_interface, short port ); + bool Accept( idTCP &client ); + void Close(); + + const netadr_t &GetAddress( void ) const { return address; } + +private: + netadr_t address; // local address + int fd; + + friend void idPoller::Add( const idTCPServer &serv, int which ); + friend void idPoller::Remove( const idTCPServer &serv ); + friend int idPoller::Check( const idTCPServer &serv ); +}; + +class idTCP { +public: + idTCP(); + idTCP( const netadr_t &address, int fd ); + idTCP( const idTCP &tcp ); + virtual ~idTCP(); + + idTCP & operator = (const idTCP &tcp); + + // if host is host:port, the value of port is ignored + bool Init( const char *host, short port ); + void Close(); + + // returns -1 on failure (and closes socket) + // those are non blocking, can be used for polling + // there is no buffering, you are not guaranteed to Read or Write everything in a single call + // (specially on win32, see recv and send documentation) + int Read( void *data, int size ); + int Write( const void *data, int size ); + + const netadr_t &GetAddress( void ) const { return address; } + +private: + netadr_t address; // remote address + int fd; // OS specific socket + + friend void idPoller::Add( const idTCP &tcp, int which ); + friend void idPoller::Remove( const idTCP &tcp ); + friend int idPoller::Check( const idTCP &tcp ); +}; + + // parses the port number + // can also do DNS resolve if you ask for it. + // NOTE: DNS resolve is a slow/blocking call, think before you use + // ( could be exploited for server DoS ) +bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ); +const char * Sys_NetAdrToString( const netadr_t a ); +bool Sys_IsLANAddress( const netadr_t a ); +bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ); + +void Sys_InitNetworking( void ); +void Sys_ShutdownNetworking( void ); + +// read proxy information from environment +#define MAX_PROXY_LENGTH 128 +bool Sys_GetHTTPProxyAddress( char proxy[ MAX_PROXY_LENGTH ] ); + +// RAVEN BEGIN +// ddynerman: utility functions +// TTimo: FIXME if exposed, call them Sys_ +int Net_GetNumInterfaces( void ); +netadr_t Net_GetInterface( int index ); + +// asalmon: Xbox live related functions +#ifdef _XBOX +#define NONCE_SIZE 8 +bool Sys_CreateLiveMatch( void ); +bool Sys_CreateSystemLinkMatch( void ); +bool Sys_VerifyString(const char *string); +#endif +// RAVEN END + + +/* +============================================================== + + Multi-threading + +============================================================== +*/ + +typedef unsigned int (*xthread_t)( void * ); + +typedef enum { + THREAD_NORMAL, + THREAD_ABOVE_NORMAL, + THREAD_HIGHEST +} xthreadPriority; + +typedef struct { + const char * name; + int threadHandle; + unsigned long threadId; +// RAVEN BEGIN +// ksergent: included to track multiprocessor system +#ifdef _XBOX + unsigned char cpuID; +#endif +// RAVEN END +} xthreadInfo; + +const int MAX_THREADS = 10; +extern xthreadInfo *g_threads[MAX_THREADS]; +extern int g_thread_count; + +void Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, xthreadInfo &info, const char *name, xthreadInfo *threads[MAX_THREADS], int *thread_count ); +void Sys_DestroyThread( xthreadInfo& info ); // sets threadHandle back to 0 + +// find the name of the calling thread +// if index != NULL, set the index in g_threads array (use -1 for "main" thread) +const char * Sys_GetThreadName( int *index = 0 ); + +const int MAX_CRITICAL_SECTIONS = 4; + +enum { + CRITICAL_SECTION_ZERO = 0, + CRITICAL_SECTION_ONE, + CRITICAL_SECTION_TWO, + CRITICAL_SECTION_THREE +}; + +void Sys_EnterCriticalSection( int index = CRITICAL_SECTION_ZERO ); +void Sys_LeaveCriticalSection( int index = CRITICAL_SECTION_ZERO ); + +const int MAX_TRIGGER_EVENTS = 4; + +enum { + TRIGGER_EVENT_ZERO = 0, + TRIGGER_EVENT_ONE, + TRIGGER_EVENT_TWO, + TRIGGER_EVENT_THREE +}; + +void Sys_WaitForEvent( int index = TRIGGER_EVENT_ZERO ); +void Sys_TriggerEvent( int index = TRIGGER_EVENT_ZERO ); + +/* +============================================================== + + idSys + +============================================================== +*/ + +class idSys { +public: + virtual ~idSys() { } + virtual void DebugPrintf( const char *fmt, ... )id_attribute((format(printf,2,3))) = 0; + virtual void DebugVPrintf( const char *fmt, va_list arg ) = 0; + + virtual double GetClockTicks( void ) = 0; + virtual double ClockTicksPerSecond( void ) = 0; + virtual cpuid_t GetProcessorId( void ) = 0; + virtual const char * GetProcessorString( void ) = 0; + virtual const char * FPU_GetState( void ) = 0; + virtual bool FPU_StackIsEmpty( void ) = 0; + virtual void FPU_SetFTZ( bool enable ) = 0; + virtual void FPU_SetDAZ( bool enable ) = 0; +// RAVEN BEGIN + virtual void FPU_SetPrecision( int flags ) = 0; +// RAVEN END + + virtual bool LockMemory( void *ptr, int bytes ) = 0; + virtual bool UnlockMemory( void *ptr, int bytes ) = 0; + + virtual void GetCallStack( address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackCurStr( int depth ) = 0; + virtual void ShutdownSymbols( void ) = 0; + + virtual int DLL_Load( const char *dllName ) = 0; + virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ) = 0; + virtual void DLL_Unload( int dllHandle ) = 0; + virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) = 0; + + virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ) = 0; + virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ) = 0; + +// RAVEN BEGIN + virtual int MapKey( unsigned long lParam, unsigned short wParam ) = 0; + virtual void AddKeyPress( int key, bool state ) = 0; + virtual int GetNumKeyPresses( void ) = 0; + virtual bool GetKeyPress( const int n, int &key, bool &state ) = 0; + + virtual void * CreateWindowEx( const char *className, const char *windowName, int style, int x, int y, int w, int h, void *parent, void *menu, void *instance, void *param, int extStyle = 0 ) = 0; + virtual void * GetDC( void *hWnd ) = 0; + virtual void ReleaseDC( void *hWnd, void *hDC ) = 0; + virtual void ShowWindow( void *hWnd, int show ) = 0; + virtual void UpdateWindow( void *hWnd ) = 0; + virtual bool IsWindowVisible( void *hWnd ) = 0; + virtual void SetForegroundWindow( void *hWnd ) = 0; + virtual void SetFocus( void *hWnd ) = 0; + virtual void DestroyWindow( void *hWnd ) = 0; + + virtual void ShowConsole( int visLevel, bool quitOnClose ) = 0; + virtual void UpdateConsole( void ) = 0; + virtual void SetConsoleName( const char* consoleName ) = 0; + virtual bool IsAppActive( void ) const = 0; + virtual int Milliseconds( void ) = 0; + virtual void InitInput( void ) = 0; + virtual void ShutdownInput( void ) = 0; + virtual void GenerateEvents( void ) = 0; + virtual void GrabMouseCursor( bool grabIt ) = 0; + + virtual FILE *FOpen( const char *name, const char *mode ) = 0; + virtual void FPrintf( FILE *file, const char *fmt ) = 0; + virtual int FTell( FILE *file ) = 0; + virtual int FSeek( FILE *file, long offset, int mode ) = 0; + virtual void FClose( FILE *file ) = 0; + virtual int FRead( void *buffer, int size, int count, FILE *file ) = 0; + virtual int FWrite( void *buffer, int size, int count, FILE *file ) = 0; + virtual long FileTimeStamp( FILE *file ) = 0; + virtual int FEof( FILE *stream ) = 0; + virtual char *FGets( char *string, int n, FILE *stream ) = 0; + virtual void FFlush( FILE *f ) = 0; + virtual int SetVBuf( FILE *stream, char *buffer, int mode, size_t size ) = 0; +// RAVEN END + + virtual void OpenURL( const char *url, bool quit ) = 0; + virtual void StartProcess( const char *exePath, bool quit ) = 0; + + virtual int GetGUID( char *buf, int buflen ) = 0; +}; + +extern idSys * sys; + +// RAVEN BEGIN +// jnewquist: Scope timing tools +#if defined(_XENON) +class ScopeAutoMeasure { +public: + ID_INLINE ScopeAutoMeasure(const char *name) { + mName = name; + QueryPerformanceCounter( &mStartTime ); + } + ID_INLINE ~ScopeAutoMeasure() { + LARGE_INTEGER endTime; + QueryPerformanceCounter( &endTime ); + double time = (double)(endTime.QuadPart - mStartTime.QuadPart) / (Sys_ClockTicksPerSecond() * 0.000001); + printf("Time %s: %f us\n", mName, time); + } +protected: + LARGE_INTEGER mStartTime; + const char * mName; +}; + +#if defined(TIME_CAPTURE) //&& defined(_PROFILE) + +class TimedScope { +public: + ID_INLINE TimedScope(const char *name) { + mName = name; + mNext = mFirst; + mFirst = this; + mTime.QuadPart = 0; + } + static void ComputeCost() { + LARGE_INTEGER TicksPerSecond; + QueryPerformanceFrequency( &TicksPerSecond ); + sTicksPerMicrosecond = (double)TicksPerSecond.QuadPart * 0.000001; + + // get a rough time estimate for the cost of a call to Sys_Milliseconds so that we can remove it from the function costs + LARGE_INTEGER before; + QueryPerformanceCounter( &before ); + + LARGE_INTEGER test; + for(int i=0; i<1000000; i++) + { + QueryPerformanceCounter( &test ); + } + LARGE_INTEGER after; + QueryPerformanceCounter( &after ); + + __int64 Ticks = after.QuadPart - before.QuadPart; + sQueryPerformanceCounterCost.QuadPart = (double)Ticks/1000000.0f; + } + + // automatically deducts the cost of the Sys_Milliseconds() call used to gather the timing + ID_INLINE void AddTime(unsigned long long ticks) { + // add number of microseconds + mTime.QuadPart += (ticks - sQueryPerformanceCounterCost.QuadPart)/sTicksPerMicrosecond; + } + ID_INLINE void AddCall() { + mCalls++; + } + ID_INLINE static void PrintTimes(void) { + if (!mFirst) { + return; + } + printf("Start Frame\n"); + for (TimedScope* p=mFirst; p; p = p->mNext) { + printf("\t%20s: %d us\t%d calls\t %f us/call\n", p->mName, p->mTime, p->mCalls, (p->mCalls)?(double)p->mTime.QuadPart/(double)p->mCalls:0.0f); + } + printf("End Frame\n\n"); + } + ID_INLINE static void ClearTimes(void) { + if (!mFirst) { + return; + } + for (TimedScope* p=mFirst; p; p = p->mNext) { + p->mTime.QuadPart = 0; + } + } + ID_INLINE static void ClearCalls(void) { + if (!mFirst) { + return; + } + for (TimedScope* p=mFirst; p; p = p->mNext) { + p->mCalls = 0; + } + } +protected: + static TimedScope * mFirst; + TimedScope * mNext; + const char * mName; + LARGE_INTEGER mTime; + unsigned int mCalls; + static LARGE_INTEGER sQueryPerformanceCounterCost; + static double sTicksPerMicrosecond; +}; + +class ScopeAutoTimer { +public: + ID_INLINE ScopeAutoTimer(TimedScope *scope) { + QueryPerformanceCounter( &mStartTime ); + //mStartTime = Sys_Milliseconds(); + mScope = scope; + } + ID_INLINE ~ScopeAutoTimer() { + LARGE_INTEGER endTime; + QueryPerformanceCounter( &endTime ); + + //unsigned int time = (unsigned int)Sys_Milliseconds() - mStartTime; + // pass in number of ticks used and TimedScope will adjust it to a number of microseconds + mScope->AddTime(endTime.QuadPart - mStartTime.QuadPart); + mScope->AddCall(); + } +protected: + LARGE_INTEGER mStartTime; + TimedScope * mScope; +}; + +#define TIME_THIS_SCOPE(name) \ + static TimedScope scopeTime(name); \ + ScopeAutoTimer autoTimer(&scopeTime) +#else +#define TIME_THIS_SCOPE(name) +#endif +#endif + +#define STRINGIZE_INDIRECT(F, X) F(X) +#define STRINGIZE(X) #X +#define __LINESTR__ STRINGIZE_INDIRECT(STRINGIZE, __LINE__) +#define __FILELINEFUNC__ (__FILE__ " " __LINESTR__ " " __FUNCTION__) +#define __FUNCLINE__ ( __FUNCTION__ " " __LINESTR__ ) + +// RAVEN END + +#endif /* !__SYS_PUBLIC__ */ diff --git a/source/ui/ListGUI.h b/source/ui/ListGUI.h new file mode 100644 index 0000000..7515b53 --- /dev/null +++ b/source/ui/ListGUI.h @@ -0,0 +1,39 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __LISTGUI_H__ +#define __LISTGUI_H__ + +/* +=============================================================================== + + feed data to a listDef + each item has an id and a display string + +=============================================================================== +*/ + +class idListGUI { +public: + virtual ~idListGUI() { } + + virtual void Config( idUserInterface *pGUI, const char *name ) = 0; +// RAVEN BEGIN +// shouchard: added greyed support + virtual void Add( int id, const idStr& s, bool greyed = false ) = 0; + // use the element count as index for the ids + virtual void Push( const idStr& s, bool greyed = false ) = 0; +// RAVEN END + virtual bool Del( int id ) = 0; + virtual void Clear( void ) = 0; + virtual int Num( void ) = 0; + virtual int GetSelection( char *s, int size, int sel = 0 ) const = 0; // returns the id, not the list index (or -1) + virtual void SetSelection( int sel ) = 0; + virtual int GetNumSelections() = 0; + virtual bool IsConfigured( void ) const = 0; + // by default, any modification to the list will trigger a full GUI refresh immediately + virtual void SetStateChanges( bool enable ) = 0; + virtual void Shutdown( void ) = 0; +}; + +#endif /* !__LISTGUI_H__ */ diff --git a/source/ui/UserInterface.h b/source/ui/UserInterface.h new file mode 100644 index 0000000..51c78be --- /dev/null +++ b/source/ui/UserInterface.h @@ -0,0 +1,204 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __USERINTERFACE_H__ +#define __USERINTERFACE_H__ + +struct wrapInfo_t { + int lastWhitespace; + int maxIndex; + wrapInfo_t ( void ) { + lastWhitespace = -1; + maxIndex = -1; + } +}; + +/* +=============================================================================== + + Draws an interactive 2D surface. + Used for all user interaction with the game. + +=============================================================================== +*/ + +class idFile; +class idDemoFile; + + +class idUserInterface { +public: + virtual ~idUserInterface( void ) {} + + // Returns the name of the gui. + virtual const char * Name( void ) const = 0; + + // Returns a comment on the gui. + virtual const char * Comment( void ) const = 0; + + // Returns true if the gui is interactive. + virtual bool IsInteractive() const = 0; + +// RAVEN BEGIN +// bdube: added + // Changes the interactive of the gui + virtual void SetInteractive ( bool interactive ) = 0 ; +// RAVEN END + + virtual bool IsUniqued() const = 0; + + virtual void SetUniqued( bool b ) = 0; + // returns false if it failed to load + virtual bool InitFromFile( const char *qpath, bool rebuild = true, bool cache = true ) = 0; + + // handles an event, can return an action string, the caller interprets + // any return and acts accordingly + virtual const char * HandleEvent( const sysEvent_t *event, int time, bool *updateVisuals = NULL ) = 0; + + // handles a named event + virtual void HandleNamedEvent( const char *eventName ) = 0; + + // repaints the ui + virtual void Redraw( int time ) = 0; + + // repaints the cursor + virtual void DrawCursor( void ) = 0; + + // Provides read access to the idDict that holds this gui's state. + virtual const idDict & State( void ) const = 0; + + // Removes a gui state variable + virtual void DeleteStateVar( const char *varName ) = 0; + + // Sets a gui state variable. + virtual void SetStateString( const char *varName, const char *value ) = 0; + virtual void SetStateBool( const char *varName, const bool value ) = 0; + virtual void SetStateInt( const char *varName, const int value ) = 0; + virtual void SetStateFloat( const char *varName, const float value ) = 0; + virtual void SetStateVector( const char *varName, const idVec3& vector ) = 0; + virtual void SetStateVec4( const char *varName, const idVec4& vector ) = 0; +// RAVEN BEGIN +// bdube: added way to clear state + virtual void ClearState( void ) = 0; +// rjohnson: added + virtual void DeleteState( const char *varName ) = 0; + + virtual idVec4 GetLightColor ( void ) = 0; + + // Gets a gui state variable + virtual const char* GetStateString( const char *varName, const char* defaultString = "" ) const = 0; + virtual bool GetStateBool( const char *varName, const char* defaultString = "0" ) const = 0; + virtual int GetStateInt( const char *varName, const char* defaultString = "0" ) const = 0; + virtual float GetStateFloat( const char *varName, const char* defaultString = "0" ) const = 0; + virtual idVec3 GetStateVector( const char *varName, const char* defaultString = "0 0 0" ) const = 0; + virtual idVec4 GetStateVec4( const char *varName, const char* defaultString = "0 0 0 0" ) const = 0; + +// jscott: added + virtual class idWindow * GetDesktop( void ) const = 0; +// RAVEN END + + // The state has changed and the gui needs to update from the state idDict. + virtual void StateChanged( int time, bool redraw = false ) = 0; + + // Activated the gui. + virtual const char * Activate( bool activate, int time ) = 0; + + // Triggers the gui and runs the onTrigger scripts. + virtual void Trigger( int time ) = 0; + + virtual void ReadFromDemo( class idDemoFile *f ) = 0; + virtual void WriteToDemo( class idDemoFile *f ) = 0; + + virtual bool WriteToSaveGame( idFile *savefile ) const = 0; + virtual bool ReadFromSaveGame( idFile *savefile ) = 0; + virtual void SetKeyBindingNames( void ) = 0; + + virtual void SetCursor( float x, float y ) = 0; + virtual float CursorX( void ) = 0; + virtual float CursorY( void ) = 0; + +// RAVEN BEGIN +// mekberg: Returns the index of the string where width in pixels <= specified val. Can return index of last whitespace. + virtual bool GetMaxTextIndex( const char *windowName, const char *text, wrapInfo_t& wrapInfo ) const = 0; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + virtual const idList& GetMaterialsList(void) = 0; +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual bool IsLevelLoadReferenced( void ) = 0; + virtual void SetLevelLoadReferenced( bool refd ) = 0; +#endif +// RAVEN END +}; + + +class idUserInterfaceManager { +public: + virtual ~idUserInterfaceManager( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Touch( const char *name ) = 0; + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // Sets the size for 640x480 adjustment. + virtual void SetSize( float width, float height ) = 0; + + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushGUIs( void ) = 0; +#endif +// RAVEN END + + // Reloads changed guis, or all guis. + virtual void Reload( bool all ) = 0; + + // lists all guis + virtual void ListGuis( void ) const = 0; + + // Returns true if gui exists. + virtual bool CheckGui( const char *qpath ) const = 0; + + // Allocates a new gui. + virtual idUserInterface * Alloc( void ) const = 0; + + // De-allocates a gui.. ONLY USE FOR PRECACHING + virtual void DeAlloc( idUserInterface *gui ) = 0; + + // Returns NULL if gui by that name does not exist. + virtual idUserInterface * FindGui( const char *qpath, bool autoLoad = false, bool needUnique = false, bool forceUnique = false ) = 0; + + // Returns the index into the global gui list + virtual int GuiIndex( idUserInterface *gui ) = 0; + + // Returns the gui at location index, or allocates a new one at location index + virtual idUserInterface * FindGuiByIndex( int index ) = 0; + + // Clears out the in game guis before loading a renderdemo + virtual void ClearGameGuis( void ) = 0; + + // Allocates a new GUI list handler + virtual idListGUI * AllocListGUI( void ) const = 0; + + // De-allocates a list gui + virtual void FreeListGUI( idListGUI *listgui ) = 0; + +// RAVEN BEGIN +// rjohnson: added option for guis to always think + virtual void RunAlwaysThinkGUIs ( int time ) = 0; +// bdube: embedded icons + virtual void RegisterIcon ( const char* code, const char* shader, int x = -1, int y = -1, int w = -1, int h = -1 ) = 0; +// RAVEN END +}; + +extern idUserInterfaceManager * uiManager; + +#endif /* !__USERINTERFACE_H__ */